[
  {
    "path": ".gitignore",
    "content": "/.git\n/.idea\n*.log\n*.DS_Store\n/yshop-drink-boot3/target/\n/yshop-drink-vue3/node_modules\n/yshop-drink-uniapp-vue3/node_moduls\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\r\n\r\nCopyright (c) 2023 yshop\r\n\r\nPermission is hereby granted, free of charge, to any person obtaining a copy\r\nof this software and associated documentation files (the \"Software\"), to deal\r\nin the Software without restriction, including without limitation the rights\r\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r\ncopies of the Software, and to permit persons to whom the Software is\r\nfurnished to do so, subject to the following conditions:\r\n\r\nThe above copyright notice and this permission notice shall be included in all\r\ncopies or substantial portions of the Software.\r\n\r\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\r\nSOFTWARE.\r\n"
  },
  {
    "path": "README.md",
    "content": "\n\n## 平台简介\n\n意象点餐(扫码点餐)系统，在线点餐(外卖与自取)小程序模式，支持多门店模式，SaaS多租户模式,基础技术Java17，springboot3、vue3、uniapp(vue3)（支持H5、微信小程序）\n采用当前流行技术组合的前后端分离点餐系统： SpringBoot3、Spring Security OAuth2、MybatisPlus、SpringSecurity、jwt、redis、Vue3的前后端分离的系统， \n包含外卖与自取、商品管理(多规格sku)、店铺管理、云小票打印、图片素材库、订单管理、积分兑换(积分+金额)、充值、优惠券、充值、多门店、微信公众号、商家中心、提前预约、桌面扫码点餐(单人或者多人协同)、收银台(支持扫码枪与扫码盒子收款)、会员卡、桌台点餐等功能，更适合企业或个人二次开发.\n\n官网地址：[https://www.yixiang.co/](https://www.yixiang.co/)\n\n\n\n## 演示地址\n\n| 后台登陆：  | https://dc.yixiang.co   账号/密码：admin/admin123  |\n|---|---|\n| 门店登陆： | https://dc.yixiang.co   账号/密码：yixiang001/123456789  |\n|  移动端演示：关注右边公众号点击菜单其他系统体验点餐小程序与点餐H5，其中如果演示使用验证码登陆的点击下验证码默认验证码是9999 | ![输入图片说明](assets/77a93e8c07a913b838a756abadb383b9.png) |\n\n## 视频资料\n如果对您有帮助，您可以点右上角 \"Star\" 支持一下，这样我们才有继续免费下去的动力，谢谢！ QQ交流群 (入群前，请在网页右上角点 \"Star\" )，群里有视频教程与开发文档哦！！\n\n交流QQ群：544263002\n\n## 项目说明\n    \n\n```\n    yshop-drink.             Java工程\n    yshop-drink-vue          后台前端vue3工程\n    yshop-drink-uniapp-vue3  移动端uniapp(vue3版本)工程，支持微信小程序、h5\n```\n\n\n## 本地快速启动\n  ##### 1、环境要求\n   \n    ```\n        jdk17\n        mysql8\n        redis6+\n        node16+\n        maven3.8+\n    \n    ```\n  ##### 2、开发工具\n   \n    ```\n        idea\n        vscode\n        hbuilder\n    \n    ```\n ##### 3、后端启动\n\n\n-   3.1 请使用idea打开Java工程，自动会安装依赖\n-   3.2 创建数据库且导入工程目录下sql/yixiang-drink.sql 文件\n-   3.3 找到项目下的yshop-server 的yml,修改数据库相关信息和redis相关信息，如图：\n     ![输入图片说明](assets/image.png)\n-   3.4 工程下输入\n    ``` \n    mvn clean install package '-Dmaven.test.skip=true\n    ```\n-   3.5 启动项目，如图\n    ![输入图片说明](assets/1702544439568.jpg)\n\n##### 4、后台vue启动\n\n - 4.1 vscode 打开vue工程，在目录下输入命令: \n    ``` \n    pnpm install\n    ```\n - 4.2 配置api如图\n ![输入图片说明](assets/1702544756749.jpg)\n - 4.3 本地启动:\n    ```\n     npm run dev\n    ```\n\n##### 5 移动端uniapp启动\n \n  - 5.1 hbuilder导入uniapp项目，\n  - 5.2 配置api\n   ![输入图片说明](assets/WX20231214-171211@2x.png)\n  - 5.3 配置小程序\n   ![输入图片说明](assets/WX20231214-171416@2x.png)\n  - 5.4 运行小程序\n    ![输入图片说明](assets/WX20231214-171514@2x.png)\n  - 5.5 运行h5\n   \n    ![输入图片说明](assets/1702545370856.jpg)\n-\n\n\n\n## 小程序截图\n\n| ![输入图片说明](assets/1000.jpg)| ![输入图片说明](assets/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20260309235851_552_6.png) |\n|---|---|\n| ![输入图片说明](assets/200000.jpg)  |  ![输入图片说明](assets/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20260309235857_557_6.png) |\n| ![输入图片说明](assets/10003.jpg)  | ![输入图片说明](assets/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20260309235856_556_6.png) | \n\n## 后台截图\n\n| ![输入图片说明](assets/3000.png) | \n|---|---|\n| ![输入图片说明](assets/3001.png)  | \n| ![输入图片说明](assets/3002.png)  | \n| ![输入图片说明](assets/3003.png)  | ![输入图片说明](assets/3004.png) |\n![输入图片说明](assets/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20260310000501_559_6.png)\n![输入图片说明](assets/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20260310001028_564_6.png)\n![输入图片说明](assets/%E5%BE%AE%E4%BF%A1%E5%9B%BE%E7%89%87_20260310001057_565_6.png)\n\n\n## 技术栈\n- Spring Boot3\n\n- Spring Security oauth2\n\n- MyBatis\n\n- MyBatisPlus\n\n- Redis\n\n- lombok\n\n- hutool\n\n- Vue3\n\n- Element UI\n\n- uniapp(vue3)\n\n## 特别鸣谢\n\n\n- ruoyi-vue-pro:https://gitee.com/zhijiantianya/ruoyi-vue-pro\n- element-plus:https://element-plus.gitee.io/zh-CN/\n- vue:https://cn.vuejs.org/\n- pay-java-parent:https://gitee.com/egzosn/pay-java-parent\n- uvui：https://www.uvui.cn/\n- uniapp:https://uniapp.dcloud.net.cn/\n\n\n## 开源协议\n\n本项目采用比 Apache 2.0 更宽松的 [MIT License](https://gitee.com/guchengwuyue/yshop-drink/blob/master/LICENSE) 开源协议，个人与企业可 100% 免费使用，不用保留类作者、Copyright 信息。\n\n"
  },
  {
    "path": "yshop-drink-boot3/.gitignore",
    "content": "######################################################################\n# Build Tools\n\n.gradle\n/build/\n!gradle/wrapper/gradle-wrapper.jar\n\ntarget/\n!.mvn/wrapper/maven-wrapper.jar\n\n.flattened-pom.xml\n\n######################################################################\n# IDE\n\n### STS ###\n.apt_generated\n.classpath\n.factorypath\n.project\n.settings\n.springBeans\n\n### IntelliJ IDEA ###\n.idea\n*.iws\n*.iml\n*.ipr\n\n### NetBeans ###\nnbproject/private/\nbuild/*\nnbbuild/\ndist/\nnbdist/\n.nb-gradle/\n\n######################################################################\n# Others\n*.log\n*.xml.versionsBackup\n*.swp\n\n!*/build/*.java\n!*/build/*.html\n!*/build/*.xml\n\n### JRebel ###\nrebel.xml\n\napplication-my.yaml\n\n\n"
  },
  {
    "path": "yshop-drink-boot3/README.md",
    "content": "意向订餐系统，类似肯德基点餐小程序模式，支持多门店模式，基础技术Java，uniapp（支持H5、微信小程序）\n采用当前流行技术组合的前后端分离点餐系统： SpringBoot3+jdk17、Spring Security OAuth2、MybatisPlus、SpringSecurity、jwt、redis、Vue3的前后端分离的系统，\n包含店铺管理、积分兑换、云小票打印、图片素材库、订单管理、多规格sku、积分、优惠券、充值、多门店、微信公众号等功能，更适合企业或个人二次开发."
  },
  {
    "path": "yshop-drink-boot3/lombok.config",
    "content": "config.stopBubbling = true\nlombok.tostring.callsuper=CALL\nlombok.equalsandhashcode.callsuper=CALL\nlombok.accessors.chain=true\n"
  },
  {
    "path": "yshop-drink-boot3/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <groupId>co.yixiang.boot</groupId>\n    <artifactId>yshop</artifactId>\n    <version>${revision}</version>\n    <packaging>pom</packaging>\n    <modules>\n        <module>yshop-dependencies</module>\n        <module>yshop-framework</module>\n        <!-- Server 主项目 -->\n        <module>yshop-server</module>\n        <!-- 各种 module 拓展 -->\n        <module>yshop-module-system</module>\n        <module>yshop-module-infra</module>\n        <module>yshop-module-pay</module>\n        <module>yshop-module-mp</module>\n        <module>yshop-module-mall</module>\n        <module>yshop-module-message</module>\n        <module>yshop-module-score</module>\n        <module>yshop-module-express</module>\n        <module>yshop-module-marketing</module>\n        <module>yshop-module-member</module>\n    </modules>\n\n    <name>${project.artifactId}</name>\n    <description>yshop项目基础脚手架</description>\n    <url>https://gitee.com/guchengwuyue/yshop-drink</url>\n\n    <properties>\n        <revision>3.0.0</revision>\n        <!-- Maven 相关 -->\n        <java.version>17</java.version>\n        <maven.compiler.source>${java.version}</maven.compiler.source>\n        <maven.compiler.target>${java.version}</maven.compiler.target>\n        <maven-surefire-plugin.version>3.2.2</maven-surefire-plugin.version>\n        <maven-compiler-plugin.version>3.11.0</maven-compiler-plugin.version>\n        <flatten-maven-plugin.version>1.5.0</flatten-maven-plugin.version>\n        <!-- 看看咋放到 bom 里 -->\n        <lombok.version>1.18.30</lombok.version>\n        <spring.boot.version>3.2.2</spring.boot.version>\n        <mapstruct.version>1.5.5.Final</mapstruct.version>\n        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>\n    </properties>\n\n    <dependencyManagement>\n        <dependencies>\n            <dependency>\n                <groupId>co.yixiang.boot</groupId>\n                <artifactId>yshop-dependencies</artifactId>\n                <version>${revision}</version>\n                <type>pom</type>\n                <scope>import</scope>\n            </dependency>\n        </dependencies>\n    </dependencyManagement>\n\n    <build>\n        <pluginManagement>\n            <plugins>\n                <!-- maven-surefire-plugin 插件，用于运行单元测试。 -->\n                <!-- 注意，需要使用 3.0.X+，因为要支持 Junit 5 版本 -->\n                <plugin>\n                    <groupId>org.apache.maven.plugins</groupId>\n                    <artifactId>maven-surefire-plugin</artifactId>\n                    <version>${maven-surefire-plugin.version}</version>\n                </plugin>\n                <!-- maven-compiler-plugin 插件，解决 spring-boot-configuration-processor + Lombok + MapStruct 组合 -->\n                <!-- https://stackoverflow.com/questions/33483697/re-run-spring-boot-configuration-annotation-processor-to-update-generated-metada -->\n                <plugin>\n                    <groupId>org.apache.maven.plugins</groupId>\n                    <artifactId>maven-compiler-plugin</artifactId>\n                    <version>${maven-compiler-plugin.version}</version>\n                    <configuration>\n                        <annotationProcessorPaths>\n                            <path>\n                                <groupId>org.springframework.boot</groupId>\n                                <artifactId>spring-boot-configuration-processor</artifactId>\n                                <version>${spring.boot.version}</version>\n                            </path>\n                            <path>\n                                <groupId>org.projectlombok</groupId>\n                                <artifactId>lombok</artifactId>\n                                <version>${lombok.version}</version>\n                            </path>\n                            <path>\n                                <groupId>org.mapstruct</groupId>\n                                <artifactId>mapstruct-processor</artifactId>\n                                <version>${mapstruct.version}</version>\n                            </path>\n                        </annotationProcessorPaths>\n                        <!-- 编译参数写在 arg 内，解决 Spring Boot 3.2 的 Parameter Name Discovery 问题 -->\n                        <debug>false</debug>\n                        <compilerArgs>\n                            <arg>-parameters</arg>\n                        </compilerArgs>\n                    </configuration>\n                </plugin>\n                <plugin>\n                    <groupId>org.codehaus.mojo</groupId>\n                    <artifactId>flatten-maven-plugin</artifactId>\n                </plugin>\n            </plugins>\n        </pluginManagement>\n\n        <plugins>\n            <!-- 统一 revision 版本 -->\n            <plugin>\n                <groupId>org.codehaus.mojo</groupId>\n                <artifactId>flatten-maven-plugin</artifactId>\n                <version>${flatten-maven-plugin.version}</version>\n                <configuration>\n                    <flattenMode>resolveCiFriendliesOnly</flattenMode>\n                    <updatePomFile>true</updatePomFile>\n                </configuration>\n                <executions>\n                    <execution>\n                        <goals>\n                            <goal>flatten</goal>\n                        </goals>\n                        <id>flatten</id>\n                        <phase>process-resources</phase>\n                    </execution>\n                    <execution>\n                        <goals>\n                            <goal>clean</goal>\n                        </goals>\n                        <id>flatten.clean</id>\n                        <phase>clean</phase>\n                    </execution>\n                </executions>\n            </plugin>\n        </plugins>\n    </build>\n\n    <!-- 使用 huawei / aliyun 的 Maven 源，提升下载速度 -->\n    <repositories>\n        <repository>\n            <id>huaweicloud</id>\n            <name>huawei</name>\n            <url>https://mirrors.huaweicloud.com/repository/maven/</url>\n        </repository>\n        <repository>\n            <id>aliyunmaven</id>\n            <name>aliyun</name>\n            <url>https://maven.aliyun.com/repository/public</url>\n        </repository>\n    </repositories>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/script/docker/Docker-HOWTO.md",
    "content": "# Docker Build & Up\n\n目标: 快速部署体验系统，帮助了解系统之间的依赖关系。\n依赖：docker compose v2，删除`name: yshop-system`，降低`version`版本为`3.3`以下，支持`docker-compose`。\n\n## 功能文件列表\n\n```text\n.\n├── Docker-HOWTO.md                 \n├── docker-compose.yml              \n├── docker.env                      <-- 提供docker-compose环境变量配置\n├── yshop-server\n│   └── Dockerfile\n└── yshop-ui-admin\n    ├── .dockerignore\n    ├── Dockerfile\n    └── nginx.conf                  <-- 提供基础配置，gzip压缩、api转发\n```\n\n## 构建 jar 包\n\n```shell\n# 创建maven缓存volume\ndocker volume create --name yshop-maven-repo\n\ndocker run -it --rm --name yshop-maven \\\n    -v yshop-maven-repo:/root/.m2 \\\n    -v $PWD:/usr/src/mymaven \\\n    -w /usr/src/mymaven \\\n    maven mvn clean install package '-Dmaven.test.skip=true'\n```\n\n## 构建启动服务\n\n```shell\ndocker compose --env-file docker.env up -d\n```\n\n首次运行会自动构建容器。可以通过`docker compose build [service]`来手动构建所有或某个docker镜像\n\n`--env-file docker.env`为可选参数，只是展示了通过`.env`文件配置容器启动的环境变量，`docker-compose.yml`本身已经提供足够的默认参数来正常运行系统。\n\n## 服务器的宿主机端口映射\n\n- admin ui: http://localhost:8080\n- api server: http://localhost:48080\n- mysql: root/123456, port: 3306\n- redis: port: 6379\n"
  },
  {
    "path": "yshop-drink-boot3/script/docker/docker-compose.yml",
    "content": "version: \"3.4\"\n\nname: yshop-system\n\nservices:\n  mysql:\n    container_name: yshop-mysql\n    image: mysql:8\n    restart: unless-stopped\n    tty: true\n    ports:\n      - \"3306:3306\"\n    environment:\n      MYSQL_DATABASE: ${MYSQL_DATABASE:-yixiang-drink}\n      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD:-123456}\n    volumes:\n      - mysql:/var/lib/mysql/\n      - ./sql/mysql/yixiang-drink.sql:/docker-entrypoint-initdb.d/yixiang-drink.sql:ro\n\n  redis:\n    container_name: yshop-redis\n    image: redis:6-alpine\n    restart: unless-stopped\n    ports:\n      - \"6379:6379\"\n    volumes:\n      - redis:/data\n\n  server:\n    container_name: yshop-server\n    build:\n      context: ./yshop-server/\n    image: yshop-server\n    restart: unless-stopped\n    ports:\n      - \"48080:48080\"\n    environment:\n      # https://github.com/polovyivan/docker-pass-configs-to-container\n      SPRING_PROFILES_ACTIVE: local\n      JAVA_OPTS:\n        ${JAVA_OPTS:-\n          -Xms512m\n          -Xmx512m\n          -Djava.security.egd=file:/dev/./urandom\n        }\n      ARGS:\n        --spring.datasource.dynamic.datasource.master.url=${MASTER_DATASOURCE_URL:-jdbc:mysql://yshop-mysql:3306/yixiang-drink?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true}\n        --spring.datasource.dynamic.datasource.master.username=${MASTER_DATASOURCE_USERNAME:-root}\n        --spring.datasource.dynamic.datasource.master.password=${MASTER_DATASOURCE_PASSWORD:-123456}\n        --spring.datasource.dynamic.datasource.slave.url=${SLAVE_DATASOURCE_URL:-jdbc:mysql://yshop-mysql:3306/yixiang-drink?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true}\n        --spring.datasource.dynamic.datasource.slave.username=${SLAVE_DATASOURCE_USERNAME:-root}\n        --spring.datasource.dynamic.datasource.slave.password=${SLAVE_DATASOURCE_PASSWORD:-123456}\n        --spring.data.redis.host=${REDIS_HOST:-yshop-redis}\n    depends_on:\n      - mysql\n      - redis\n\n  admin:\n    container_name: yshop-admin\n    build:\n      context: ./yshop-ui-admin\n      args:\n        NODE_ENV:\n          ENV=${NODE_ENV:-production}\n          PUBLIC_PATH=${PUBLIC_PATH:-/}\n          VUE_APP_TITLE=${VUE_APP_TITLE:-意象商城管理系统}\n          VUE_APP_BASE_API=${VUE_APP_BASE_API:-/prod-api}\n          VUE_APP_APP_NAME=${VUE_APP_APP_NAME:-/}\n          VUE_APP_TENANT_ENABLE=${VUE_APP_TENANT_ENABLE:-true}\n          VUE_APP_CAPTCHA_ENABLE=${VUE_APP_CAPTCHA_ENABLE:-true}\n          VUE_APP_DOC_ENABLE=${VUE_APP_DOC_ENABLE:-true}\n          VUE_APP_BAIDU_CODE=${VUE_APP_BAIDU_CODE:-fadc1bd5db1a1d6f581df60a1807f8ab}\n    image: yshop-admin\n    restart: unless-stopped\n    ports:\n      - \"8080:80\"\n    depends_on:\n      - server\n\nvolumes:\n  mysql:\n    driver: local\n  redis:\n    driver: local\n"
  },
  {
    "path": "yshop-drink-boot3/script/docker/docker.env",
    "content": "## mysql\nMYSQL_DATABASE=yixiang-drink\nMYSQL_ROOT_PASSWORD=123456\n\n## server\nJAVA_OPTS=-Xms512m -Xmx512m -Djava.security.egd=file:/dev/./urandom\n\nMASTER_DATASOURCE_URL=jdbc:mysql://yshop-mysql:3306/${MYSQL_DATABASE}?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true\nMASTER_DATASOURCE_USERNAME=root\nMASTER_DATASOURCE_PASSWORD=${MYSQL_ROOT_PASSWORD}\nSLAVE_DATASOURCE_URL=${MASTER_DATASOURCE_URL}\nSLAVE_DATASOURCE_USERNAME=${MASTER_DATASOURCE_USERNAME}\nSLAVE_DATASOURCE_PASSWORD=${MASTER_DATASOURCE_PASSWORD}\nREDIS_HOST=yshop-redis\n\n## admin\nNODE_ENV=production\nPUBLIC_PATH=/\nVUE_APP_TITLE=意象商城管理系统\nVUE_APP_BASE_API=/prod-api\nVUE_APP_APP_NAME=/\nVUE_APP_TENANT_ENABLE=true\nVUE_APP_CAPTCHA_ENABLE=true\nVUE_APP_DOC_ENABLE=true\nVUE_APP_BAIDU_CODE=fadc1bd5db1a1d6f581df60a1807f8ab\n"
  },
  {
    "path": "yshop-drink-boot3/script/shell/deploy.sh",
    "content": "#!/bin/bash\nset -e\n\nDATE=$(date +%Y%m%d%H%M)\n# 基础路径\nBASE_PATH=/work/projects/yshop-server\n# 编译后 jar 的地址。部署时，Jenkins 会上传 jar 包到该目录下\nSOURCE_PATH=$BASE_PATH/build\n# 服务名称。同时约定部署服务的 jar 包名字也为它。\nSERVER_NAME=yshop-server\n# 环境\nPROFILES_ACTIVE=development\n# 健康检查 URL\nHEALTH_CHECK_URL=http://127.0.0.1:48080/actuator/health/\n\n# heapError 存放路径\nHEAP_ERROR_PATH=$BASE_PATH/heapError\n# JVM 参数\nJAVA_OPS=\"-Xms512m -Xmx512m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=$HEAP_ERROR_PATH\"\n\n# SkyWalking Agent 配置\n#export SW_AGENT_NAME=$SERVER_NAME\n#export SW_AGENT_COLLECTOR_BACKEND_SERVICES=192.168.0.84:11800\n#export SW_GRPC_LOG_SERVER_HOST=192.168.0.84\n#export SW_AGENT_TRACE_IGNORE_PATH=\"Redisson/PING,/actuator/**,/admin/**\"\n#export JAVA_AGENT=-javaagent:/work/skywalking/apache-skywalking-apm-bin/agent/skywalking-agent.jar\n\n# 备份\nfunction backup() {\n    # 如果不存在，则无需备份\n    if [ ! -f \"$BASE_PATH/$SERVER_NAME.jar\" ]; then\n        echo \"[backup] $BASE_PATH/$SERVER_NAME.jar 不存在，跳过备份\"\n    # 如果存在，则备份到 backup 目录下，使用时间作为后缀\n    else\n        echo \"[backup] 开始备份 $SERVER_NAME ...\"\n        cp $BASE_PATH/$SERVER_NAME.jar $BASE_PATH/backup/$SERVER_NAME-$DATE.jar\n        echo \"[backup] 备份 $SERVER_NAME 完成\"\n    fi\n}\n\n# 最新构建代码 移动到项目环境\nfunction transfer() {\n    echo \"[transfer] 开始转移 $SERVER_NAME.jar\"\n\n    # 删除原 jar 包\n    if [ ! -f \"$BASE_PATH/$SERVER_NAME.jar\" ]; then\n        echo \"[transfer] $BASE_PATH/$SERVER_NAME.jar 不存在，跳过删除\"\n    else\n        echo \"[transfer] 移除 $BASE_PATH/$SERVER_NAME.jar 完成\"\n        rm $BASE_PATH/$SERVER_NAME.jar\n    fi\n\n    # 复制新 jar 包\n    echo \"[transfer] 从 $SOURCE_PATH 中获取 $SERVER_NAME.jar 并迁移至 $BASE_PATH ....\"\n    cp $SOURCE_PATH/$SERVER_NAME.jar $BASE_PATH\n\n    echo \"[transfer] 转移 $SERVER_NAME.jar 完成\"\n}\n\n# 停止：优雅关闭之前已经启动的服务\nfunction stop() {\n    echo \"[stop] 开始停止 $BASE_PATH/$SERVER_NAME\"\n    PID=$(ps -ef | grep $BASE_PATH/$SERVER_NAME | grep -v \"grep\" | awk '{print $2}')\n    # 如果 Java 服务启动中，则进行关闭\n    if [ -n \"$PID\" ]; then\n        # 正常关闭\n        echo \"[stop] $BASE_PATH/$SERVER_NAME 运行中，开始 kill [$PID]\"\n        kill -15 $PID\n        # 等待最大 120 秒，直到关闭完成。\n        for ((i = 0; i < 120; i++))\n            do\n                sleep 1\n                PID=$(ps -ef | grep $BASE_PATH/$SERVER_NAME | grep -v \"grep\" | awk '{print $2}')\n                if [ -n \"$PID\" ]; then\n                    echo -e \".\\c\"\n                else\n                    echo \"[stop] 停止 $BASE_PATH/$SERVER_NAME 成功\"\n                    break\n                fi\n\t\t    done\n\n        # 如果正常关闭失败，那么进行强制 kill -9 进行关闭\n        if [ -n \"$PID\" ]; then\n            echo \"[stop] $BASE_PATH/$SERVER_NAME 失败，强制 kill -9 $PID\"\n            kill -9 $PID\n        fi\n    # 如果 Java 服务未启动，则无需关闭\n    else\n        echo \"[stop] $BASE_PATH/$SERVER_NAME 未启动，无需停止\"\n    fi\n}\n\n# 启动：启动后端项目\nfunction start() {\n    # 开启启动前，打印启动参数\n    echo \"[start] 开始启动 $BASE_PATH/$SERVER_NAME\"\n    echo \"[start] JAVA_OPS: $JAVA_OPS\"\n    echo \"[start] JAVA_AGENT: $JAVA_AGENT\"\n    echo \"[start] PROFILES: $PROFILES_ACTIVE\"\n\n    # 开始启动\n    BUILD_ID=dontKillMe nohup java -server $JAVA_OPS $JAVA_AGENT -jar $BASE_PATH/$SERVER_NAME.jar --spring.profiles.active=$PROFILES_ACTIVE &\n    echo \"[start] 启动 $BASE_PATH/$SERVER_NAME 完成\"\n}\n\n# 健康检查：自动判断后端项目是否正常启动\nfunction healthCheck() {\n    # 如果配置健康检查，则进行健康检查\n    if [ -n \"$HEALTH_CHECK_URL\" ]; then\n        # 健康检查最大 120 秒，直到健康检查通过\n        echo \"[healthCheck] 开始通过 $HEALTH_CHECK_URL 地址，进行健康检查\";\n        for ((i = 0; i < 120; i++))\n            do\n                # 请求健康检查地址，只获取状态码。\n                result=`curl -I -m 10 -o /dev/null -s -w %{http_code} $HEALTH_CHECK_URL || echo \"000\"`\n                # 如果状态码为 200，则说明健康检查通过\n                if [ \"$result\" == \"200\" ]; then\n                    echo \"[healthCheck] 健康检查通过\";\n                    break\n                # 如果状态码非 200，则说明未通过。sleep 1 秒后，继续重试\n                else\n                    echo -e \".\\c\"\n                    sleep 1\n                fi\n            done\n\n        # 健康检查未通过，则异常退出 shell 脚本，不继续部署。\n        if [ ! \"$result\" == \"200\" ]; then\n            echo \"[healthCheck] 健康检查不通过，可能部署失败。查看日志，自行判断是否启动成功\";\n            tail -n 10 nohup.out\n            exit 1;\n        # 健康检查通过，打印最后 10 行日志，可能部署的人想看下日志。\n        else\n            tail -n 10 nohup.out\n        fi\n    # 如果未配置健康检查，则 sleep 120 秒，人工看日志是否部署成功。\n    else\n        echo \"[healthCheck] HEALTH_CHECK_URL 未配置，开始 sleep 120 秒\";\n        sleep 120\n        echo \"[healthCheck] sleep 120 秒完成，查看日志，自行判断是否启动成功\";\n        tail -n 50 nohup.out\n    fi\n}\n\n# 部署\nfunction deploy() {\n    cd $BASE_PATH\n    # 备份原 jar\n    backup\n    # 停止 Java 服务\n    stop\n    # 部署新 jar\n    transfer\n    # 启动 Java 服务\n    start\n    # 健康检查\n    healthCheck\n}\n\ndeploy\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-dependencies/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n\n    <groupId>co.yixiang.boot</groupId>\n    <artifactId>yshop-dependencies</artifactId>\n    <version>${revision}</version>\n    <packaging>pom</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>基础 bom 文件，管理整个项目的依赖版本</description>\n    <url>https://gitee.com/guchengwuyue/yshop-drink</url>\n\n    <properties>\n        <revision>3.0.0</revision>\n        <flatten-maven-plugin.version>1.5.0</flatten-maven-plugin.version>\n        <!-- 统一依赖管理 -->\n        <spring.boot.version>3.2.2</spring.boot.version>\n        <!-- Web 相关 -->\n        <springdoc.version>2.2.0</springdoc.version>\n        <knife4j.version>4.3.0</knife4j.version>\n        <!-- DB 相关 -->\n        <druid.version>1.2.21</druid.version>\n        <mybatis-plus.version>3.5.5</mybatis-plus.version>\n        <mybatis-plus-generator.version>3.5.5</mybatis-plus-generator.version>\n        <dynamic-datasource.version>4.3.0</dynamic-datasource.version>\n        <mybatis-plus-join.version>1.4.10</mybatis-plus-join.version>\n        <easy-trans.version>2.2.11</easy-trans.version>\n        <redisson.version>3.26.0</redisson.version>\n        <dm8.jdbc.version>8.1.3.62</dm8.jdbc.version>\n        <!-- 消息队列 -->\n        <rocketmq-spring.version>2.3.0</rocketmq-spring.version>\n        <!-- 服务保障相关 -->\n        <lock4j.version>2.2.7</lock4j.version>\n        <!-- 监控相关 -->\n        <skywalking.version>9.0.0</skywalking.version>\n        <spring-boot-admin.version>3.2.1</spring-boot-admin.version>\n        <opentracing.version>0.33.0</opentracing.version>\n        <!-- Test 测试相关 -->\n        <podam.version>8.0.1.RELEASE</podam.version>\n        <jedis-mock.version>1.0.13</jedis-mock.version>\n        <mockito-inline.version>5.2.0</mockito-inline.version>\n        <!-- Bpm 工作流相关 -->\n        <flowable.version>7.0.1</flowable.version>\n        <!-- 工具类相关 -->\n        <captcha-plus.version>2.0.3</captcha-plus.version>\n        <jsoup.version>1.17.2</jsoup.version>\n        <lombok.version>1.18.30</lombok.version>\n        <mapstruct.version>1.5.5.Final</mapstruct.version>\n        <hutool-5.version>5.8.25</hutool-5.version>\n        <hutool-6.version>6.0.0-M10</hutool-6.version>\n        <easyexcel.verion>3.3.3</easyexcel.verion>\n        <velocity.version>2.3</velocity.version>\n        <screw.version>1.0.5</screw.version>\n        <fastjson.version>1.2.83</fastjson.version>\n        <guava.version>33.0.0-jre</guava.version>\n        <guice.version>5.1.0</guice.version>\n        <transmittable-thread-local.version>2.14.5</transmittable-thread-local.version>\n        <commons-net.version>3.10.0</commons-net.version>\n        <jsch.version>0.1.55</jsch.version>\n        <tika-core.version>2.9.1</tika-core.version>\n        <ip2region.version>2.7.0</ip2region.version>\n        <bizlog-sdk.version>3.0.6</bizlog-sdk.version>\n        <!-- 三方云服务相关 -->\n        <okio.version>3.5.0</okio.version>\n        <okhttp3.version>4.11.0</okhttp3.version>\n        <commons-io.version>2.15.1</commons-io.version>\n        <minio.version>8.5.7</minio.version>\n        <aliyun-java-sdk-core.version>4.6.4</aliyun-java-sdk-core.version>\n        <aliyun-java-sdk-dysmsapi.version>2.2.1</aliyun-java-sdk-dysmsapi.version>\n        <tencentcloud-sdk-java.version>3.1.880</tencentcloud-sdk-java.version>\n        <justauth.version>2.0.5</justauth.version>\n        <jimureport.version>1.6.6-beta2</jimureport.version>\n        <xercesImpl.version>2.12.2</xercesImpl.version>\n        <weixin-java.version>4.6.0</weixin-java.version>\n        <pay.boot.version>1.0.5</pay.boot.version>\n        <pay.version>2.14.9</pay.version>\n        <zxing.version>3.3.3</zxing.version>\n    </properties>\n\n    <dependencyManagement>\n        <dependencies>\n            <!-- 统一依赖管理 -->\n            <dependency>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-dependencies</artifactId>\n                <version>${spring.boot.version}</version>\n                <type>pom</type>\n                <scope>import</scope>\n            </dependency>\n\n            <!-- 业务组件 -->\n            <dependency>\n                <groupId>io.github.mouzt</groupId>\n                <artifactId>bizlog-sdk</artifactId>\n                <version>${bizlog-sdk.version}</version>\n                <exclusions>\n                    <exclusion> <!-- 排除掉springboot依赖使用项目的 -->\n                        <groupId>org.springframework.boot</groupId>\n                        <artifactId>spring-boot-starter</artifactId>\n                    </exclusion>\n                </exclusions>\n            </dependency>\n            <dependency>\n                <groupId>co.yixiang.boot</groupId>\n                <artifactId>yshop-spring-boot-starter-biz-tenant</artifactId>\n                <version>${revision}</version>\n            </dependency>\n            <dependency>\n                <groupId>co.yixiang.boot</groupId>\n                <artifactId>yshop-spring-boot-starter-biz-data-permission</artifactId>\n                <version>${revision}</version>\n            </dependency>\n            <dependency>\n                <groupId>co.yixiang.boot</groupId>\n                <artifactId>yshop-spring-boot-starter-biz-ip</artifactId>\n                <version>${revision}</version>\n            </dependency>\n\n            <!-- Spring 核心 -->\n            <dependency>\n                <!-- 用于生成自定义的 Spring @ConfigurationProperties 配置类的说明文件 -->\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-configuration-processor</artifactId>\n                <version>${spring.boot.version}</version>\n            </dependency>\n\n            <!-- Web 相关 -->\n            <dependency>\n                <groupId>co.yixiang.boot</groupId>\n                <artifactId>yshop-spring-boot-starter-web</artifactId>\n                <version>${revision}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>co.yixiang.boot</groupId>\n                <artifactId>yshop-spring-boot-starter-security</artifactId>\n                <version>${revision}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>co.yixiang.boot</groupId>\n                <artifactId>yshop-spring-boot-starter-websocket</artifactId>\n                <version>${revision}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>com.github.xiaoymin</groupId>\n                <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>\n                <version>${knife4j.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>org.springdoc</groupId>\n                <artifactId>springdoc-openapi-starter-webmvc-api</artifactId>\n                <version>${springdoc.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>org.springdoc</groupId>\n                <artifactId>springdoc-openapi-ui</artifactId>\n                <version>${springdoc.version}</version>\n            </dependency>\n\n            <!-- DB 相关 -->\n            <dependency>\n                <groupId>co.yixiang.boot</groupId>\n                <artifactId>yshop-spring-boot-starter-mybatis</artifactId>\n                <version>${revision}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>com.alibaba</groupId>\n                <artifactId>druid-spring-boot-3-starter</artifactId>\n                <version>${druid.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>com.baomidou</groupId>\n                <artifactId>mybatis-plus-spring-boot3-starter</artifactId>\n                <version>${mybatis-plus.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>com.baomidou</groupId>\n                <artifactId>mybatis-plus-generator</artifactId> <!-- 代码生成器，使用它解析表结构 -->\n                <version>${mybatis-plus-generator.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>com.baomidou</groupId>\n                <artifactId>dynamic-datasource-spring-boot3-starter</artifactId> <!-- 多数据源 -->\n                <version>${dynamic-datasource.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>com.github.yulichang</groupId>\n                <artifactId>mybatis-plus-join-boot-starter</artifactId> <!-- MyBatis 联表查询 -->\n                <version>${mybatis-plus-join.version}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>com.fhs-opensource</groupId> <!-- VO 数据翻译 -->\n                <artifactId>easy-trans-spring-boot-starter</artifactId>\n                <version>${easy-trans.version}</version>\n                <exclusions>\n                    <exclusion>\n                        <groupId>org.springframework</groupId>\n                        <artifactId>spring-context</artifactId>\n                    </exclusion>\n                    <exclusion>\n                        <groupId>org.springframework.cloud</groupId>\n                        <artifactId>spring-cloud-commons</artifactId>\n                    </exclusion>\n                </exclusions>\n            </dependency>\n            <dependency>\n                <groupId>com.fhs-opensource</groupId>\n                <artifactId>easy-trans-mybatis-plus-extend</artifactId>\n                <version>${easy-trans.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>com.fhs-opensource</groupId>\n                <artifactId>easy-trans-anno</artifactId>\n                <version>${easy-trans.version}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>co.yixiang.boot</groupId>\n                <artifactId>yshop-spring-boot-starter-redis</artifactId>\n                <version>${revision}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>org.redisson</groupId>\n                <artifactId>redisson-spring-boot-starter</artifactId>\n                <version>${redisson.version}</version>\n                <exclusions>\n                    <exclusion>\n                        <groupId>org.springframework.boot</groupId>\n                        <artifactId>spring-boot-starter-actuator</artifactId>\n                    </exclusion>\n                </exclusions>\n            </dependency>\n\n            <dependency>\n                <groupId>com.dameng</groupId>\n                <artifactId>DmJdbcDriver18</artifactId>\n                <version>${dm8.jdbc.version}</version>\n            </dependency>\n\n            <!-- Job 定时任务相关 -->\n            <dependency>\n                <groupId>co.yixiang.boot</groupId>\n                <artifactId>yshop-spring-boot-starter-job</artifactId>\n                <version>${revision}</version>\n            </dependency>\n\n            <!-- 消息队列相关 -->\n            <dependency>\n                <groupId>co.yixiang.boot</groupId>\n                <artifactId>yshop-spring-boot-starter-mq</artifactId>\n                <version>${revision}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>org.apache.rocketmq</groupId>\n                <artifactId>rocketmq-spring-boot-starter</artifactId>\n                <version>${rocketmq-spring.version}</version>\n            </dependency>\n\n            <!-- 服务保障相关 -->\n            <dependency>\n                <groupId>co.yixiang.boot</groupId>\n                <artifactId>yshop-spring-boot-starter-protection</artifactId>\n                <version>${revision}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>com.baomidou</groupId>\n                <artifactId>lock4j-redisson-spring-boot-starter</artifactId>\n                <version>${lock4j.version}</version>\n                <exclusions>\n                    <exclusion>\n                        <artifactId>redisson-spring-boot-starter</artifactId>\n                        <groupId>org.redisson</groupId>\n                    </exclusion>\n                </exclusions>\n            </dependency>\n\n            <!-- 监控相关 -->\n            <dependency>\n                <groupId>co.yixiang.boot</groupId>\n                <artifactId>yshop-spring-boot-starter-monitor</artifactId>\n                <version>${revision}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>org.apache.skywalking</groupId>\n                <artifactId>apm-toolkit-trace</artifactId>\n                <version>${skywalking.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>org.apache.skywalking</groupId>\n                <artifactId>apm-toolkit-logback-1.x</artifactId>\n                <version>${skywalking.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>org.apache.skywalking</groupId>\n                <artifactId>apm-toolkit-opentracing</artifactId>\n                <version>${skywalking.version}</version>\n                <!--                <exclusions>-->\n                <!--                    <exclusion>-->\n                <!--                        <artifactId>opentracing-api</artifactId>-->\n                <!--                        <groupId>io.opentracing</groupId>-->\n                <!--                    </exclusion>-->\n                <!--                    <exclusion>-->\n                <!--                        <artifactId>opentracing-util</artifactId>-->\n                <!--                        <groupId>io.opentracing</groupId>-->\n                <!--                    </exclusion>-->\n                <!--                </exclusions>-->\n            </dependency>\n            <dependency>\n                <groupId>io.opentracing</groupId>\n                <artifactId>opentracing-api</artifactId>\n                <version>${opentracing.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>io.opentracing</groupId>\n                <artifactId>opentracing-util</artifactId>\n                <version>${opentracing.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>io.opentracing</groupId>\n                <artifactId>opentracing-noop</artifactId>\n                <version>${opentracing.version}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>de.codecentric</groupId>\n                <artifactId>spring-boot-admin-starter-server</artifactId> <!-- 实现 Spring Boot Admin Server 服务端 -->\n                <version>${spring-boot-admin.version}</version>\n                <exclusions>\n                    <exclusion>\n                        <groupId>de.codecentric</groupId>\n                        <artifactId>spring-boot-admin-server-cloud</artifactId>\n                    </exclusion>\n                </exclusions>\n            </dependency>\n            <dependency>\n                <groupId>de.codecentric</groupId>\n                <artifactId>spring-boot-admin-starter-client</artifactId> <!-- 实现 Spring Boot Admin Server 服务端 -->\n                <version>${spring-boot-admin.version}</version>\n            </dependency>\n\n            <!-- Test 测试相关 -->\n            <dependency>\n                <groupId>co.yixiang.boot</groupId>\n                <artifactId>yshop-spring-boot-starter-test</artifactId>\n                <version>${revision}</version>\n                <scope>test</scope>\n            </dependency>\n\n            <dependency>\n                <groupId>org.mockito</groupId>\n                <artifactId>mockito-inline</artifactId>\n                <version>${mockito-inline.version}</version> <!-- 支持 Mockito 的 final 类与 static 方法的 mock -->\n            </dependency>\n\n            <dependency>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-starter-test</artifactId>\n                <version>${spring.boot.version}</version>\n                <exclusions>\n                    <exclusion>\n                        <artifactId>asm</artifactId>\n                        <groupId>org.ow2.asm</groupId>\n                    </exclusion>\n                    <exclusion>\n                        <groupId>org.mockito</groupId>\n                        <artifactId>mockito-core</artifactId>\n                    </exclusion>\n                </exclusions>\n            </dependency>\n\n            <dependency>\n                <groupId>com.github.fppt</groupId> <!-- 单元测试，我们采用内嵌的 Redis 数据库 -->\n                <artifactId>jedis-mock</artifactId>\n                <version>${jedis-mock.version}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>uk.co.jemos.podam</groupId> <!-- 单元测试，随机生成 POJO 类 -->\n                <artifactId>podam</artifactId>\n                <version>${podam.version}</version>\n            </dependency>\n\n            <!-- 工作流相关 -->\n            <dependency>\n                <groupId>org.flowable</groupId>\n                <artifactId>flowable-spring-boot-starter-process</artifactId>\n                <version>${flowable.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>org.flowable</groupId>\n                <artifactId>flowable-spring-boot-starter-actuator</artifactId>\n                <version>${flowable.version}</version>\n            </dependency>\n            <!-- 工作流相关结束 -->\n\n            <!-- 工具类相关 -->\n            <dependency>\n                <groupId>co.yixiang.boot</groupId>\n                <artifactId>yshop-common</artifactId>\n                <version>${revision}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>co.yixiang.boot</groupId>\n                <artifactId>yshop-spring-boot-starter-excel</artifactId>\n                <version>${revision}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>org.projectlombok</groupId>\n                <artifactId>lombok</artifactId>\n                <version>${lombok.version}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>org.mapstruct</groupId>\n                <artifactId>mapstruct</artifactId> <!-- use mapstruct-jdk8 for Java 8 or higher -->\n                <version>${mapstruct.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>org.mapstruct</groupId>\n                <artifactId>mapstruct-jdk8</artifactId>\n                <version>${mapstruct.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>org.mapstruct</groupId>\n                <artifactId>mapstruct-processor</artifactId>\n                <version>${mapstruct.version}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>cn.hutool</groupId>\n                <artifactId>hutool-all</artifactId>\n                <version>${hutool-5.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>org.dromara.hutool</groupId>\n                <artifactId>hutool-extra</artifactId>\n                <version>${hutool-6.version}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>com.alibaba</groupId>\n                <artifactId>easyexcel</artifactId>\n                <version>${easyexcel.verion}</version>\n            </dependency>\n            <dependency>\n                <groupId>commons-io</groupId>\n                <artifactId>commons-io</artifactId>\n                <version>${commons-io.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>org.apache.tika</groupId>\n                <artifactId>tika-core</artifactId> <!-- 文件类型的识别 -->\n                <version>${tika-core.version}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>org.apache.velocity</groupId>\n                <artifactId>velocity-engine-core</artifactId>\n                <version>${velocity.version}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>com.alibaba</groupId>\n                <artifactId>fastjson</artifactId>\n                <version>${fastjson.version}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>com.google.guava</groupId>\n                <artifactId>guava</artifactId>\n                <version>${guava.version}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>com.google.inject</groupId>\n                <artifactId>guice</artifactId>\n                <version>${guice.version}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>com.alibaba</groupId>\n                <artifactId>transmittable-thread-local</artifactId> <!-- 解决 ThreadLocal 父子线程的传值问题 -->\n                <version>${transmittable-thread-local.version}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>commons-net</groupId>\n                <artifactId>commons-net</artifactId> <!-- 解决 ftp 连接 -->\n                <version>${commons-net.version}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>com.jcraft</groupId>\n                <artifactId>jsch</artifactId> <!-- 解决 sftp 连接 -->\n                <version>${jsch.version}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>com.xingyuv</groupId>\n                <artifactId>spring-boot-starter-captcha-plus</artifactId>\n                <version>${captcha-plus.version}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>org.lionsoul</groupId>\n                <artifactId>ip2region</artifactId>\n                <version>${ip2region.version}</version>\n            </dependency>\n\n            <dependency>\n                <groupId>org.jsoup</groupId>\n                <artifactId>jsoup</artifactId>\n                <version>${jsoup.version}</version>\n            </dependency>\n\n            <!-- 三方云服务相关 -->\n            <dependency>\n                <groupId>com.squareup.okio</groupId>\n                <artifactId>okio</artifactId>\n                <version>${okio.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>com.squareup.okhttp3</groupId>\n                <artifactId>okhttp</artifactId>\n                <version>${okhttp3.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>io.minio</groupId>\n                <artifactId>minio</artifactId>\n                <version>${minio.version}</version>\n            </dependency>\n\n            <!-- SMS SDK begin -->\n            <dependency>\n                <groupId>com.aliyun</groupId>\n                <artifactId>aliyun-java-sdk-core</artifactId>\n                <version>${aliyun-java-sdk-core.version}</version>\n                <exclusions>\n                    <exclusion>\n                        <artifactId>opentracing-api</artifactId>\n                        <groupId>io.opentracing</groupId>\n                    </exclusion>\n                    <exclusion>\n                        <artifactId>opentracing-util</artifactId>\n                        <groupId>io.opentracing</groupId>\n                    </exclusion>\n                </exclusions>\n            </dependency>\n            <dependency>\n                <groupId>com.aliyun</groupId>\n                <artifactId>aliyun-java-sdk-dysmsapi</artifactId>\n                <version>${aliyun-java-sdk-dysmsapi.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>com.tencentcloudapi</groupId>\n                <artifactId>tencentcloud-sdk-java-sms</artifactId>\n                <version>${tencentcloud-sdk-java.version}</version>\n            </dependency>\n            <!-- SMS SDK end -->\n\n            <dependency>\n                <groupId>com.xingyuv</groupId>\n                <artifactId>spring-boot-starter-justauth</artifactId> <!-- 社交登陆（例如说，个人微信、企业微信等等） -->\n                <version>${justauth.version}</version>\n                <exclusions>\n                    <exclusion>\n                        <groupId>cn.hutool</groupId>\n                        <artifactId>hutool-core</artifactId>\n                    </exclusion>\n                </exclusions>\n            </dependency>\n\n            <dependency>\n                <groupId>com.github.binarywang</groupId>\n                <artifactId>weixin-java-pay</artifactId>\n                <version>${weixin-java.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>com.github.binarywang</groupId>\n                <artifactId>wx-java-mp-spring-boot-starter</artifactId>\n                <version>${weixin-java.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>com.github.binarywang</groupId>\n                <artifactId>wx-java-miniapp-spring-boot-starter</artifactId>\n                <version>${weixin-java.version}</version>\n            </dependency>\n\n            <!-- 积木报表-->\n            <dependency>\n                <groupId>org.jeecgframework.jimureport</groupId>\n                <artifactId>jimureport-spring-boot3-starter</artifactId>\n                <version>${jimureport.version}</version>\n                <exclusions>\n                    <exclusion>\n                        <groupId>com.alibaba</groupId>\n                        <artifactId>druid</artifactId>\n                    </exclusion>\n                </exclusions>\n            </dependency>\n            <dependency>\n                <groupId>xerces</groupId>\n                <artifactId>xercesImpl</artifactId>\n                <version>${xercesImpl.version}</version>\n            </dependency>\n            <!-- pay -->\n            <dependency>\n                <groupId>com.egzosn</groupId>\n                <artifactId>pay-spring-boot-starter</artifactId>\n                <version>${pay.boot.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>com.egzosn</groupId>\n                <artifactId>pay-java-ali</artifactId>\n                <version>${pay.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>com.egzosn</groupId>\n                <artifactId>pay-java-wx</artifactId>\n                <version>${pay.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>com.egzosn</groupId>\n                <artifactId>pay-java-web-support</artifactId>\n                <version>${pay.version}</version>\n            </dependency>\n            <dependency>\n                <groupId>com.google.zxing</groupId>\n                <artifactId>core</artifactId>\n                <version>${zxing.version}</version>\n            </dependency>\n\n        </dependencies>\n    </dependencyManagement>\n\n    <build>\n        <plugins>\n            <!-- 统一 revision 版本 -->\n            <plugin>\n                <groupId>org.codehaus.mojo</groupId>\n                <artifactId>flatten-maven-plugin</artifactId>\n                <version>${flatten-maven-plugin.version}</version>\n                <configuration>\n                    <flattenMode>resolveCiFriendliesOnly</flattenMode>\n                    <updatePomFile>true</updatePomFile>\n                </configuration>\n                <executions>\n                    <execution>\n                        <goals>\n                            <goal>flatten</goal>\n                        </goals>\n                        <id>flatten</id>\n                        <phase>process-resources</phase>\n                    </execution>\n                    <execution>\n                        <goals>\n                            <goal>clean</goal>\n                        </goals>\n                        <id>flatten.clean</id>\n                        <phase>clean</phase>\n                    </execution>\n                </executions>\n            </plugin>\n        </plugins>\n    </build>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <parent>\n        <artifactId>yshop</artifactId>\n        <groupId>co.yixiang.boot</groupId>\n        <version>${revision}</version>\n    </parent>\n    <packaging>pom</packaging>\n    <modules>\n        <module>yshop-common</module>\n        <module>yshop-spring-boot-starter-mybatis</module>\n        <module>yshop-spring-boot-starter-redis</module>\n        <module>yshop-spring-boot-starter-web</module>\n        <module>yshop-spring-boot-starter-security</module>\n        <module>yshop-spring-boot-starter-websocket</module>\n\n        <module>yshop-spring-boot-starter-monitor</module>\n        <module>yshop-spring-boot-starter-protection</module>\n        <module>yshop-spring-boot-starter-job</module>\n        <module>yshop-spring-boot-starter-mq</module>\n\n        <module>yshop-spring-boot-starter-excel</module>\n        <module>yshop-spring-boot-starter-test</module>\n\n        <module>yshop-spring-boot-starter-biz-tenant</module>\n        <module>yshop-spring-boot-starter-biz-data-permission</module>\n        <module>yshop-spring-boot-starter-biz-ip</module>\n    </modules>\n\n    <artifactId>yshop-framework</artifactId>\n    <description>\n        该包是技术组件，每个子包，代表一个组件。每个组件包括两部分：\n            1. core 包：是该组件的核心封装\n            2. config 包：是该组件基于 Spring 的配置\n\n        技术组件，也分成两类：\n            1. 框架组件：和我们熟悉的 MyBatis、Redis 等等的拓展\n            2. 业务组件：和业务相关的组件的封装，例如说数据字典、操作日志等等。\n        如果是业务组件，Maven 名字会包含 biz\n    </description>\n    <url>https://gitee.com/guchengwuyue/yshop-drink</url>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>co.yixiang.boot</groupId>\n        <artifactId>yshop-framework</artifactId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>yshop-common</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>定义基础 pojo 类、枚举、工具类等等</description>\n    <url>https://gitee.com/guchengwuyue/yshop-drink</url>\n\n    <dependencies>\n        <!-- Spring 核心 -->\n        <dependency>\n            <groupId>org.springframework</groupId>\n            <artifactId>spring-core</artifactId>\n            <scope>provided</scope> <!-- 设置为 provided，只有工具类需要使用到 -->\n        </dependency>\n        <dependency>\n            <groupId>org.springframework</groupId>\n            <artifactId>spring-expression</artifactId>\n            <scope>provided</scope> <!-- 设置为 provided，只有工具类需要使用到 -->\n        </dependency>\n        <dependency>\n            <groupId>org.springframework</groupId>\n            <artifactId>spring-aop</artifactId>\n            <scope>provided</scope> <!-- 设置为 provided，只有工具类需要使用到 -->\n        </dependency>\n        <dependency>\n            <groupId>org.aspectj</groupId>\n            <artifactId>aspectjweaver</artifactId>\n            <scope>provided</scope> <!-- 设置为 provided，只有工具类需要使用到 -->\n        </dependency>\n\n        <dependency>\n            <!-- 用于生成自定义的 Spring @ConfigurationProperties 配置类的说明文件 -->\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-configuration-processor</artifactId>\n            <optional>true</optional>\n        </dependency>\n\n        <!-- Web 相关 -->\n        <dependency>\n            <groupId>org.springframework</groupId>\n            <artifactId>spring-web</artifactId>\n            <scope>provided</scope> <!-- 设置为 provided，只有工具类需要使用到 -->\n        </dependency>\n\n        <dependency>\n            <groupId>jakarta.servlet</groupId>\n            <artifactId>jakarta.servlet-api</artifactId>\n            <scope>provided</scope> <!-- 设置为 provided，只有工具类需要使用到 -->\n        </dependency>\n\n        <dependency>\n            <groupId>org.springdoc</groupId>\n            <artifactId>springdoc-openapi-starter-webmvc-api</artifactId>\n            <scope>provided</scope> <!-- 设置为 provided，主要是 PageParam 使用到 -->\n        </dependency>\n\n        <!-- 监控相关 -->\n        <dependency>\n            <groupId>org.apache.skywalking</groupId>\n            <artifactId>apm-toolkit-trace</artifactId>\n        </dependency>\n\n        <!-- 工具类相关 -->\n        <dependency>\n            <groupId>org.projectlombok</groupId>\n            <artifactId>lombok</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>org.mapstruct</groupId>\n            <artifactId>mapstruct</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.mapstruct</groupId>\n            <artifactId>mapstruct-jdk8</artifactId> <!-- use mapstruct-jdk8 for Java 8 or higher -->\n        </dependency>\n        <dependency>\n            <groupId>org.mapstruct</groupId>\n            <artifactId>mapstruct-processor</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>com.google.guava</groupId>\n            <artifactId>guava</artifactId>\n            <scope>provided</scope> <!-- 设置为 provided，只有工具类需要使用到 -->\n        </dependency>\n\n        <dependency>\n            <groupId>com.fasterxml.jackson.core</groupId>\n            <artifactId>jackson-databind</artifactId>\n            <scope>provided</scope> <!-- 设置为 provided，只有工具类需要使用到 -->\n        </dependency>\n        <dependency>\n            <groupId>com.fasterxml.jackson.core</groupId>\n            <artifactId>jackson-core</artifactId>\n            <scope>provided</scope> <!-- 设置为 provided，只有工具类需要使用到 -->\n        </dependency>\n        <dependency>\n            <groupId>com.fasterxml.jackson.datatype</groupId>\n            <artifactId>jackson-datatype-jsr310</artifactId>\n            <scope>provided</scope> <!-- 设置为 provided，只有工具类需要使用到 -->\n        </dependency>\n\n        <dependency>\n            <groupId>org.slf4j</groupId>\n            <artifactId>slf4j-api</artifactId>\n            <scope>provided</scope> <!-- 设置为 provided，只有工具类需要使用到 -->\n        </dependency>\n\n        <dependency>\n            <groupId>jakarta.validation</groupId>\n            <artifactId>jakarta.validation-api</artifactId>\n            <scope>provided</scope> <!-- 设置为 provided，主要是 PageParam 使用到 -->\n        </dependency>\n\n        <dependency>\n            <groupId>cn.hutool</groupId>\n            <artifactId>hutool-all</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>com.alibaba</groupId>\n            <artifactId>transmittable-thread-local</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>com.fhs-opensource</groupId> <!-- VO 数据翻译 -->\n            <artifactId>easy-trans-anno</artifactId> <!-- 默认引入的原因，方便 xxx-module-api 包使用 -->\n        </dependency>\n\n        <!-- Test 测试相关 -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/constant/ShopConstants.java",
    "content": "/**\n * Copyright (C) 2018-2022\n * All rights reserved, Designed By www.yixiang.co\n\n */\npackage co.yixiang.yshop.framework.common.constant;\n\n/**\n * 商城统一常量\n * @author hupeng\n * @since 2020-02-27\n */\npublic interface ShopConstants {\n\n\t/**\n\t * 订单自动取消时间（分钟）\n\t */\n\tlong ORDER_OUTTIME_UNPAY = 30;\n\t/**\n\t * 订单自动收货时间（分钟）\n\t */\n\tlong ORDER_OUTTIME_UNCONFIRM = 60;\n\t/**\n\t * redis订单未付款key\n\t */\n\tString REDIS_ORDER_OUTTIME_UNPAY_QUEUE = \"order-unpay-cancel-queue\";\n\t/**\n\t * redis订单收货key\n\t */\n\tString REDIS_ORDER_OUTTIME_UNCONFIRM = \"order:unconfirm:\";\n\n\t/**\n\t * redis拼团key\n\t */\n\tString REDIS_PINK_CANCEL_KEY = \"pink:cancel:\";\n\n\t/**\n\t * 微信支付service\n\t */\n\tString YSHOP_WEIXIN_PAY_SERVICE = \"yshop_weixin_pay_service\";\n\n\t/**\n\t * 微信支付小程序service\n\t */\n\tString YSHOP_WEIXIN_MINI_PAY_SERVICE = \"yshop_weixin_mini_pay_service\";\n\n\t/**\n\t * 微信支付app service\n\t */\n\tString YSHOP_WEIXIN_APP_PAY_SERVICE = \"yshop_weixin_app_pay_service\";\n\n\t/**\n\t * 微信公众号service\n\t */\n\tString YSHOP_WEIXIN_MP_SERVICE = \"yshop_weixin_mp_service\";\n\t/**\n\t * 微信小程序service\n\t */\n\tString YSHOP_WEIXIN_MA_SERVICE = \"yshop_weixin_ma_service\";\n\n\t/**\n\t * 商城默认密码\n\t */\n\tString YSHOP_DEFAULT_PWD = \"123456\";\n\n\t/**\n\t * 商城默认注册图片\n\t */\n\tString YSHOP_DEFAULT_AVATAR = \"https://image.dayouqiantu.cn/5e79f6cfd33b6.png\";\n\n\t/**\n\t * 腾讯地图地址解析\n\t */\n\tString QQ_MAP_URL = \"https://apis.map.qq.com/ws/geocoder/v1/\";\n\n\t/**\n\t * redis首页键\n\t */\n\tString YSHOP_REDIS_INDEX_KEY = \"yshop:index_data\";\n\n\t/**\n\t * 配置列表缓存\n\t */\n\tString YSHOP_REDIS_CONFIG_DATAS = \"yshop:config_datas\";\n\n\t/**\n\t * 充值方案\n\t */\n\tString YSHOP_RECHARGE_PRICE_WAYS = \"yshop_recharge_price_ways\";\n\t/**\n\t * 首页banner\n\t */\n\tString YSHOP_HOME_BANNER = \"yshop_home_banner\";\n\t/**\n\t * 首页菜单\n\t */\n\tString YSHOP_HOME_MENUS = \"yshop_home_menus\";\n\t/**\n\t * 首页滚动新闻\n\t */\n\tString YSHOP_HOME_ROLL_NEWS = \"yshop_home_roll_news\";\n\t/**\n\t * 热门搜索\n\t */\n\tString YSHOP_HOT_SEARCH = \"yshop_hot_search\";\n\t/**\n\t * 个人中心菜单\n\t */\n\tString YSHOP_MY_MENUES = \"yshop_my_menus\";\n\t/**\n\t * 秒杀时间段\n\t */\n\tString YSHOP_SECKILL_TIME = \"yshop_seckill_time\";\n\t/**\n\t * 签到天数\n\t */\n\tString YSHOP_SIGN_DAY_NUM = \"yshop_sign_day_num\";\n\n\t/**\n\t * 打印机配置\n\t */\n\tString YSHOP_ORDER_PRINT_COUNT = \"order_print_count\";\n\t/**\n\t * 飞蛾用户信息\n\t */\n\tString YSHOP_FEI_E_USER = \"fei_e_user\";\n\t/**\n\t * 飞蛾用户密钥\n\t */\n\tString YSHOP_FEI_E_UKEY= \"fei_e_ukey\";\n\n\t/**\n\t * 打印机配置\n\t */\n\tString YSHOP_ORDER_PRINT_COUNT_DETAIL = \"order_print_count_detail\";\n\n\t/**\n\t * 短信验证码长度\n\t */\n\tint YSHOP_SMS_SIZE = 6;\n\n\t/**\n\t * 短信缓存时间\n\t */\n\tlong YSHOP_SMS_REDIS_TIME = 600L;\n\n\t//零标识\n\tString YSHOP_ZERO =  \"0\";\n\n\t//业务标识标识\n\tString YSHOP_ONE =  \"1\";\n\n\t//目前完成任务数量是3\n\tint TASK_FINISH_COUNT = 3;\n\n\tint YSHOP_ONE_NUM = 1;\n\n\tString YSHOP_ORDER_CACHE_KEY = \"yshop:order\";\n\n\tString YSHOP_ORDER_SALE_STATUS_KEY = \"yshop:order:sale:status\";\n\n\tlong YSHOP_ORDER_CACHE_TIME = 3600L;\n\n\tString WECHAT_MENUS =  \"wechat_menus\";\n\n\tString YSHOP_EXPRESS_SERVICE = \"yshop_express_service\";\n\n\tString YSHOP_REDIS_SYS_CITY_KEY = \"yshop:city_list\";\n\n\tString YSHOP_REDIS_CITY_KEY = \"yshop:city\";\n\n\tString YSHOP_APP_LOGIN_USER = \"app-online-token:\";\n\n\tString YSHOP_WECHAT_PUSH_REMARK = \"yshop为您服务！\";\n\n\tString DEFAULT_UNI_H5_URL = \"https://h5.yixiang.co\";\n\n\tString YSHOP_MINI_SESSION_KET = \"yshop:session_key:\";\n\n\t/**公众号二维码*/\n\tString WECHAT_FOLLOW_IMG=\"wechat_follow_img\";\n\t/**后台api地址*/\n\tString ADMIN_API_URL=\"admin_api_url\";\n\n\t//快递查询接口Logistic\n\tString KDNIAO_LOGISTIC_QUERY=\"https://api.kdniao.com/Ebusiness/EbusinessOrderHandle.aspx\";\n\n\t//跳转到扫码页面\n\tString PAGE_GOOD_HOME = \"pages/components/pages/scan/scan\";\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/constant/SystemConfigConstants.java",
    "content": "package co.yixiang.yshop.framework.common.constant;\n\npublic class SystemConfigConstants {\n    //地址配置\n    public final static String API=\"api\";\n    public final static String API_URL=\"api_url\";\n    public final static String SITE_URL=\"site_url\";\n    public final static String UNI_SITE_URL=\"uni_site_url\";\n    public final static String TENGXUN_MAP_KEY=\"tengxun_map_key\";\n    public final static String FILE_STORE_MODE=\"file_store_mode\";\n    //业务相关配置\n    public final static String IMAGEARR=\"imageArr\";\n    public final static String INTERGRAL_FULL=\"integral_full\";\n    public final static String INTERGRAL_MAX=\"integral_max\";\n    public final static String INTERGRAL_RATIO=\"integral_ratio\";\n    public final static String ORDER_CANCEL_JOB_TIME=\"order_cancel_job_time\";\n    public final static String STORE_BROKERAGE_OPEN=\"store_brokerage_open\";\n    public final static String STORE_BROKERAGE_RATIO=\"store_brokerage_ratio\";\n    public final static String STORE_BROKERAGE_STATU=\"store_brokerage_statu\";\n    public final static String STORE_BROKERAGE_TWO=\"store_brokerage_two\";\n    public final static String STORE_FREE_POSTAGE=\"store_free_postage\";\n    public final static String STORE_POSTAGE=\"store_postage\";\n    public final static String STORE_SEFL_MENTION=\"store_self_mention\";\n    public final static String STORE_USER_MIN_RECHARGE=\"store_user_min_recharge\";\n    public final static String USER_EXTRACT_MIN_PRICE=\"user_extract_min_price\";\n    public final static String YSHOP_SHOW_RECHARGE = \"yshop_show_recharge\";\n    //微信相关配置\n    public final static String WECHAT_APPID=\"wechat_appid\";\n    public final static String WECHAT_APPSECRET=\"wechat_appsecret\";\n    public final static String WECHAT_AVATAR=\"wechat_avatar\";\n    public final static String WECHAT_ENCODE=\"wechat_encode\";\n    public final static String WECHAT_ENCODINGAESKEY=\"wechat_encodingaeskey\";\n    public final static String WECHAT_ID=\"wechat_id\";\n    public final static String WECHAT_NAME=\"wechat_name\";\n    public final static String WECHAT_QRCODE=\"wechat_qrcode\";\n    public final static String WECHAT_SHARE_IMG=\"wechat_share_img\";\n    public final static String WECHAT_SHARE_SYNOPSIS=\"wechat_share_synopsis\";\n    public final static String WECHAT_SHARE_TITLE=\"wechat_share_title\";\n    public final static String WECHAT_SOURCEID=\"wechat_sourceid\";\n    public final static String WECHAT_TOKEN=\"wechat_token\";\n    public final static String WECHAT_MA_TOKEN=\"wechat_ma_token\";\n    public final static String WECHAT_MA_ENCODINGAESKEY=\"wechat_ma_encodingaeskey\";\n    public final static String WECHAT_TYPE=\"wechat_type\";\n    public final static String WXAPP_APPID=\"wxapp_appId\";\n    public final static String WXAPP_SECRET=\"wxapp_secret\";\n    public final static String WXPAY_APPID=\"wxpay_appId\";\n    public final static String WXPAY_KEYPATH=\"wxpay_keyPath\";\n    public final static String WXPAY_MCHID=\"wxpay_mchId\";\n    public final static String WXPAY_MCHKEY=\"wxpay_mchKey\";\n    public final static String WX_NATIVE_APP_APPID=\"wx_native_app_appId\";\n    public final static String EXP_APPID = \"exp_appId\";\n\n\n    //播放状态变化事件，detail = {code}\n    public static final String BINDSTATECHANGE = \"bindstatechange\";\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/core/IntArrayValuable.java",
    "content": "package co.yixiang.yshop.framework.common.core;\n\n/**\n * 可生成 Int 数组的接口\n *\n * @author yshop\n */\npublic interface IntArrayValuable {\n\n    /**\n     * @return int 数组\n     */\n    int[] array();\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/core/KeyValue.java",
    "content": "package co.yixiang.yshop.framework.common.core;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\nimport java.io.Serializable;\n\n/**\n * Key Value 的键值对\n *\n * @author yshop\n */\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\npublic class KeyValue<K, V> implements Serializable {\n\n    private K key;\n    private V value;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/enums/CommonStatusEnum.java",
    "content": "package co.yixiang.yshop.framework.common.enums;\n\nimport cn.hutool.core.util.ObjUtil;\nimport co.yixiang.yshop.framework.common.core.IntArrayValuable;\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\nimport java.util.Arrays;\n\n/**\n * 通用状态枚举\n *\n * @author yshop\n */\n@Getter\n@AllArgsConstructor\npublic enum CommonStatusEnum implements IntArrayValuable {\n\n    ENABLE(0, \"开启\"),\n    DISABLE(1, \"关闭\");\n\n    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(CommonStatusEnum::getStatus).toArray();\n\n    /**\n     * 状态值\n     */\n    private final Integer status;\n    /**\n     * 状态名\n     */\n    private final String name;\n\n    @Override\n    public int[] array() {\n        return ARRAYS;\n    }\n\n    public static boolean isEnable(Integer status) {\n        return ObjUtil.equal(ENABLE.status, status);\n    }\n\n    public static boolean isDisable(Integer status) {\n        return ObjUtil.equal(DISABLE.status, status);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/enums/DateIntervalEnum.java",
    "content": "package co.yixiang.yshop.framework.common.enums;\n\nimport cn.hutool.core.util.ArrayUtil;\nimport co.yixiang.yshop.framework.common.core.IntArrayValuable;\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\nimport java.util.Arrays;\n\n/**\n * 时间间隔的枚举\n *\n * @author dhb52\n */\n@Getter\n@AllArgsConstructor\npublic enum DateIntervalEnum implements IntArrayValuable {\n\n    DAY(1, \"天\"),\n    WEEK(2, \"周\"),\n    MONTH(3, \"月\"),\n    QUARTER(4, \"季度\"),\n    YEAR(5, \"年\")\n    ;\n\n    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(DateIntervalEnum::getInterval).toArray();\n\n    /**\n     * 类型\n     */\n    private final Integer interval;\n    /**\n     * 名称\n     */\n    private final String name;\n\n    @Override\n    public int[] array() {\n        return ARRAYS;\n    }\n\n    public static DateIntervalEnum valueOf(Integer interval) {\n        return ArrayUtil.firstMatch(item -> item.getInterval().equals(interval), DateIntervalEnum.values());\n    }\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/enums/DocumentEnum.java",
    "content": "package co.yixiang.yshop.framework.common.enums;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * 文档地址\n *\n * @author yshop\n */\n@Getter\n@AllArgsConstructor\npublic enum DocumentEnum {\n\n    REDIS_INSTALL(\"https://gitee.com/zhijiantianya/yixiang-drink/issues/I4VCSJ\", \"Redis 安装文档\"),\n    TENANT(\"https://www.yixiang.co\", \"SaaS 多租户文档\");\n\n    private final String url;\n    private final String memo;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/enums/OrderInfoEnum.java",
    "content": "/**\n * Copyright (C) 2018-2022\n * All rights reserved, Designed By www.yixiang.co\n\n */\npackage co.yixiang.yshop.framework.common.enums;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\nimport java.util.stream.Stream;\n\n/**\n * @author hupeng\n * 订单相关枚举\n */\n@Getter\n@AllArgsConstructor\npublic enum OrderInfoEnum {\n\n\tSTATUS_NE1(-1,\"申请退款\"),\n\tSTATUS_NE2(-2,\"退款成功\"),\n\tSTATUS_0(0,\"默认\"),\n\tSTATUS_1(1,\"待收货\"),\n\tSTATUS_2(2,\"已收货\"),\n\tSTATUS_3(3,\"已完成\"),\n\n\tPAY_STATUS_0(0,\"未支付\"),\n\tPAY_STATUS_1(1,\"已支付\"),\n\n\tREFUND_STATUS_0(0,\"正常\"),\n\tREFUND_STATUS_1(1,\"退款中\"),\n\tREFUND_STATUS_2(2,\"已退款\"),\n\n\tBARGAIN_STATUS_1(1,\"参与中\"),\n\tBARGAIN_STATUS_2(2,\"参与失败\"),\n\tBARGAIN_STATUS_3(3,\"参与成功\"),\n\n\tPINK_STATUS_1(1,\"进行中\"),\n\tPINK_STATUS_2(2,\"已完成\"),\n\tPINK_STATUS_3(3,\"未完成\"),\n\n\tPINK_REFUND_STATUS_0(0,\"拼团正常\"),\n\tPINK_REFUND_STATUS_1(1,\"拼团已退款\"),\n\n\tCANCEL_STATUS_0(0,\"正常\"),\n\tCANCEL_STATUS_1(1,\"已取消\"),\n\n\tCONFIRM_STATUS_0(0,\"正常\"),\n\tCONFIRM_STATUS_1(1,\"确认\"),\n\n\tPAY_CHANNEL_0(0,\"公众号/H5支付渠道\"),\n\tPAY_CHANNEL_1(1,\"小程序支付渠道\"),\n\n\tDESK_ORDER_STATUS_CONFIRM(0,\"确认完成\"),\n\tDESK_ORDER_STATUS_ING(1,\"就餐中\"),\n\n\n\tSHIPPIING_TYPE_1(1,\"快递\"),\n\tSHIPPIING_TYPE_2(2,\"门店自提\");\n\n\n\n\tprivate Integer value;\n\tprivate String desc;\n\n\tpublic static OrderInfoEnum toType(int value) {\n\t\treturn Stream.of(OrderInfoEnum.values())\n\t\t\t\t.filter(p -> p.value == value)\n\t\t\t\t.findAny()\n\t\t\t\t.orElse(null);\n\t}\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/enums/OrderTypeEnum.java",
    "content": "package co.yixiang.yshop.framework.common.enums;\r\n\r\n\r\nimport lombok.AllArgsConstructor;\r\nimport lombok.Getter;\r\n\r\n/**\r\n * @author hupeng\r\n * 类型枚举\r\n */\r\n@Getter\r\n@AllArgsConstructor\r\npublic enum OrderTypeEnum {\r\n    TYPE_WORK(\"work\",\"工作台\"),\r\n    TYPE_COMMON(\"common\",\"普通\");\r\n\r\n    private String value;\r\n    private String desc;\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/enums/PayIdEnum.java",
    "content": "package co.yixiang.yshop.framework.common.enums;\r\n\r\n\r\nimport lombok.AllArgsConstructor;\r\nimport lombok.Getter;\r\n\r\n/**\r\n * @author hupeng\r\n * 支付ID枚举\r\n */\r\n@Getter\r\n@AllArgsConstructor\r\npublic enum PayIdEnum {\r\n    WX_H5(\"wx_h5\",\"微信支付H5\"),\r\n    WX_MINIAPP(\"wx_miniapp\",\"微信支付小程序\"),\r\n    WX_WECHAT(\"wx_wechat\",\"微信支付公众号\"),\r\n    WX_PC(\"wx_pc\",\"微信支付pc\"),\r\n    WX_APP(\"wx_app\",\"微信支付app\"),\r\n    ALI_H5(\"ali_h5\",\"支付宝H5\"),\r\n    ALI_MINIAPP(\"ali_miniapp\",\"支付宝小程序\"),\r\n    ALI_WECHAT(\"ali_wechat\",\"支付宝公众号\"),\r\n    ALI_PC(\"ali_pc\",\"支付宝pc\"),\r\n    ALI_APP(\"ali_app\",\"支付宝app\");\r\n\r\n    private String value;\r\n    private String desc;\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/enums/ShopCommonEnum.java",
    "content": "package co.yixiang.yshop.framework.common.enums;\r\n\r\nimport lombok.AllArgsConstructor;\r\nimport lombok.Getter;\r\n\r\n/**\r\n * @author hupeng\r\n * 商城常用枚举\r\n */\r\n@Getter\r\n@AllArgsConstructor\r\npublic enum ShopCommonEnum {\r\n\r\n    STORE_MODE_1(1,\"本地存储\"),\r\n    STORE_MODE_2(2,\"云存储\"),\r\n\r\n    ENABLE_1(1,\"开启\"),\r\n    ENABLE_2(2,\"关闭\"),\r\n\r\n    EXTRACT_MINUS_1(-1,\"提现未通过\"),\r\n    EXTRACT_0(0,\"提现审核中\"),\r\n    EXTRACT_1(1,\"提现已完成\"),\r\n\r\n    IS_FINISH_0(0,\"未完成\"),\r\n    IS_FINISH_1(1,\"已完成\"),\r\n\r\n    IS_FOREVER_0(0,\"不是永久\"),\r\n    IS_FOREVER_1(1,\"永久\"),\r\n\r\n    AGREE_1(1,\"同意\"),\r\n    AGREE_2(2,\"拒绝\"),\r\n\r\n    IS_PERMANENT_0(0,\"限制\"),\r\n    IS_PERMANENT_1(1,\"不限制\"),\r\n\r\n    IS_STATUS_0(0,\"否\"),\r\n    IS_STATUS_1(1,\"是\"),\r\n\r\n\r\n    IS_PROMOTER_0(0,\"默认\"),\r\n    IS_PROMOTER_1(1,\"是客服\"),\r\n\r\n    IS_NEW_0(0,\"默认\"),\r\n    IS_NEW_1(1,\"新品\"),\r\n\r\n    IS_SUB_0(0,\"不单独分佣\"),\r\n    IS_SUB_1(1,\"单独分佣\"),\r\n\r\n\r\n    GRADE_0(0,\"一级推荐人\"),\r\n    GRADE_1(1,\"二级推荐人\"),\r\n\r\n    REPLY_0(0,\"未回复\"),\r\n    REPLY_1(1,\"已回复\"),\r\n\r\n    ADD_1(1,\"增加\"),\r\n    ADD_2(2,\"减少\"),\r\n\r\n    DELETE_0(0,\"未删除\"),\r\n    DELETE_1(1,\"已删除\"),\r\n\r\n    SHOW_0(0,\"不显示\"),\r\n    SHOW_1(1,\"显示\"),\r\n\r\n    DEFAULT_0(0,\"不是默认\"),\r\n    DEFAULT_1(1,\"默认\");\r\n\r\n\r\n\r\n\r\n    private Integer value;\r\n    private String desc;\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/enums/TerminalEnum.java",
    "content": "package co.yixiang.yshop.framework.common.enums;\n\nimport co.yixiang.yshop.framework.common.core.IntArrayValuable;\nimport lombok.Getter;\nimport lombok.RequiredArgsConstructor;\n\nimport java.util.Arrays;\n\n/**\n * 终端的枚举\n *\n * @author yshop\n */\n@RequiredArgsConstructor\n@Getter\npublic enum TerminalEnum implements IntArrayValuable {\n\n    UNKNOWN(0, \"未知\"), // 目的：在无法解析到 terminal 时，使用它\n    WECHAT_MINI_PROGRAM(10, \"微信小程序\"),\n    WECHAT_WAP(11, \"微信公众号\"),\n    H5(20, \"H5 网页\"),\n    APP(31, \"手机 App\"),\n    ;\n\n    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(TerminalEnum::getTerminal).toArray();\n\n    /**\n     * 终端\n     */\n    private final Integer terminal;\n    /**\n     * 终端名\n     */\n    private final String name;\n\n    @Override\n    public int[] array() {\n        return ARRAYS;\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/enums/UserTypeEnum.java",
    "content": "package co.yixiang.yshop.framework.common.enums;\n\nimport cn.hutool.core.util.ArrayUtil;\nimport co.yixiang.yshop.framework.common.core.IntArrayValuable;\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\nimport java.util.Arrays;\n\n/**\n * 全局用户类型枚举\n */\n@AllArgsConstructor\n@Getter\npublic enum UserTypeEnum implements IntArrayValuable {\n\n    MEMBER(1, \"会员\"), // 面向 c 端，普通用户\n    ADMIN(2, \"管理员\"); // 面向 b 端，管理后台\n\n    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(UserTypeEnum::getValue).toArray();\n\n    /**\n     * 类型\n     */\n    private final Integer value;\n    /**\n     * 类型名\n     */\n    private final String name;\n\n    public static UserTypeEnum valueOf(Integer value) {\n        return ArrayUtil.firstMatch(userType -> userType.getValue().equals(value), UserTypeEnum.values());\n    }\n\n    @Override\n    public int[] array() {\n        return ARRAYS;\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/enums/WebFilterOrderEnum.java",
    "content": "package co.yixiang.yshop.framework.common.enums;\n\n/**\n * Web 过滤器顺序的枚举类，保证过滤器按照符合我们的预期\n *\n *  考虑到每个 starter 都需要用到该工具类，所以放到 common 模块下的 enums 包下\n *\n * @author yshop\n */\npublic interface WebFilterOrderEnum {\n\n    int CORS_FILTER = Integer.MIN_VALUE;\n\n    int TRACE_FILTER = CORS_FILTER + 1;\n\n    int REQUEST_BODY_CACHE_FILTER = Integer.MIN_VALUE + 500;\n\n    // OrderedRequestContextFilter 默认为 -105，用于国际化上下文等等\n\n    int TENANT_CONTEXT_FILTER = - 104; // 需要保证在 ApiAccessLogFilter 前面\n\n    int API_ACCESS_LOG_FILTER = -103; // 需要保证在 RequestBodyCacheFilter 后面\n\n    int XSS_FILTER = -102;  // 需要保证在 RequestBodyCacheFilter 后面\n\n    // Spring Security Filter 默认为 -100，可见 org.springframework.boot.autoconfigure.security.SecurityProperties 配置属性类\n\n    int TENANT_SECURITY_FILTER = -99; // 需要保证在 Spring Security 过滤器后面\n\n    int FLOWABLE_FILTER = -98; // 需要保证在 Spring Security 过滤后面\n\n    int DEMO_FILTER = Integer.MAX_VALUE;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/exception/ErrorCode.java",
    "content": "package co.yixiang.yshop.framework.common.exception;\n\nimport co.yixiang.yshop.framework.common.exception.enums.GlobalErrorCodeConstants;\nimport co.yixiang.yshop.framework.common.exception.enums.ServiceErrorCodeRange;\nimport lombok.Data;\n\n/**\n * 错误码对象\n *\n * 全局错误码，占用 [0, 999], 参见 {@link GlobalErrorCodeConstants}\n * 业务异常错误码，占用 [1 000 000 000, +∞)，参见 {@link ServiceErrorCodeRange}\n *\n * TODO 错误码设计成对象的原因，为未来的 i18 国际化做准备\n */\n@Data\npublic class ErrorCode {\n\n    /**\n     * 错误码\n     */\n    private final Integer code;\n    /**\n     * 错误提示\n     */\n    private final String msg;\n\n    public ErrorCode(Integer code, String message) {\n        this.code = code;\n        this.msg = message;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/exception/ServerException.java",
    "content": "package co.yixiang.yshop.framework.common.exception;\n\nimport co.yixiang.yshop.framework.common.exception.enums.GlobalErrorCodeConstants;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\n/**\n * 服务器异常 Exception\n */\n@Data\n@EqualsAndHashCode(callSuper = true)\npublic final class ServerException extends RuntimeException {\n\n    /**\n     * 全局错误码\n     *\n     * @see GlobalErrorCodeConstants\n     */\n    private Integer code;\n    /**\n     * 错误提示\n     */\n    private String message;\n\n    /**\n     * 空构造方法，避免反序列化问题\n     */\n    public ServerException() {\n    }\n\n    public ServerException(ErrorCode errorCode) {\n        this.code = errorCode.getCode();\n        this.message = errorCode.getMsg();\n    }\n\n    public ServerException(Integer code, String message) {\n        this.code = code;\n        this.message = message;\n    }\n\n    public Integer getCode() {\n        return code;\n    }\n\n    public ServerException setCode(Integer code) {\n        this.code = code;\n        return this;\n    }\n\n    @Override\n    public String getMessage() {\n        return message;\n    }\n\n    public ServerException setMessage(String message) {\n        this.message = message;\n        return this;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/exception/ServiceException.java",
    "content": "package co.yixiang.yshop.framework.common.exception;\n\nimport co.yixiang.yshop.framework.common.exception.enums.ServiceErrorCodeRange;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\n/**\n * 业务逻辑异常 Exception\n */\n@Data\n@EqualsAndHashCode(callSuper = true)\npublic final class ServiceException extends RuntimeException {\n\n    /**\n     * 业务错误码\n     *\n     * @see ServiceErrorCodeRange\n     */\n    private Integer code;\n    /**\n     * 错误提示\n     */\n    private String message;\n\n    /**\n     * 空构造方法，避免反序列化问题\n     */\n    public ServiceException() {\n    }\n\n    public ServiceException(ErrorCode errorCode) {\n        this.code = errorCode.getCode();\n        this.message = errorCode.getMsg();\n    }\n\n    public ServiceException(Integer code, String message) {\n        this.code = code;\n        this.message = message;\n    }\n\n    public Integer getCode() {\n        return code;\n    }\n\n    public ServiceException setCode(Integer code) {\n        this.code = code;\n        return this;\n    }\n\n    @Override\n    public String getMessage() {\n        return message;\n    }\n\n    public ServiceException setMessage(String message) {\n        this.message = message;\n        return this;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/exception/enums/GlobalErrorCodeConstants.java",
    "content": "package co.yixiang.yshop.framework.common.exception.enums;\n\nimport co.yixiang.yshop.framework.common.exception.ErrorCode;\n\n/**\n * 全局错误码枚举\n * 0-999 系统异常编码保留\n *\n * 一般情况下，使用 HTTP 响应状态码 https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status\n * 虽然说，HTTP 响应状态码作为业务使用表达能力偏弱，但是使用在系统层面还是非常不错的\n * 比较特殊的是，因为之前一直使用 0 作为成功，就不使用 200 啦。\n *\n * @author yshop\n */\npublic interface GlobalErrorCodeConstants {\n\n    ErrorCode SUCCESS = new ErrorCode(0, \"成功\");\n\n    // ========== 客户端错误段 ==========\n\n    ErrorCode BAD_REQUEST = new ErrorCode(400, \"请求参数不正确\");\n    ErrorCode UNAUTHORIZED = new ErrorCode(401, \"账号未登录\");\n    ErrorCode FORBIDDEN = new ErrorCode(403, \"没有该操作权限\");\n    ErrorCode NOT_FOUND = new ErrorCode(404, \"请求未找到\");\n    ErrorCode METHOD_NOT_ALLOWED = new ErrorCode(405, \"请求方法不正确\");\n    ErrorCode LOCKED = new ErrorCode(423, \"请求失败，请稍后重试\"); // 并发请求，不允许\n    ErrorCode TOO_MANY_REQUESTS = new ErrorCode(429, \"请求过于频繁，请稍后重试\");\n\n    // ========== 服务端错误段 ==========\n\n    ErrorCode INTERNAL_SERVER_ERROR = new ErrorCode(500, \"系统异常\");\n    ErrorCode NOT_IMPLEMENTED = new ErrorCode(501, \"功能未实现/未开启\");\n    ErrorCode ERROR_CONFIGURATION = new ErrorCode(502, \"错误的配置项\");\n\n    // ========== 自定义错误段 ==========\n    ErrorCode REPEATED_REQUESTS = new ErrorCode(900, \"重复请求，请稍后重试\"); // 重复请求\n    ErrorCode DEMO_DENY = new ErrorCode(901, \"演示模式，禁止写操作\");\n\n    ErrorCode UNKNOWN = new ErrorCode(999, \"未知错误\");\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/exception/enums/ServiceErrorCodeRange.java",
    "content": "package co.yixiang.yshop.framework.common.exception.enums;\n\n/**\n * 业务异常的错误码区间，解决：解决各模块错误码定义，避免重复，在此只声明不做实际使用\n *\n * 一共 10 位，分成四段\n *\n * 第一段，1 位，类型\n *      1 - 业务级别异常\n *      x - 预留\n * 第二段，3 位，系统类型\n *      001 - 用户系统\n *      002 - 商品系统\n *      003 - 订单系统\n *      004 - 支付系统\n *      005 - 优惠劵系统\n *      ... - ...\n * 第三段，3 位，模块\n *      不限制规则。\n *      一般建议，每个系统里面，可能有多个模块，可以再去做分段。以用户系统为例子：\n *          001 - OAuth2 模块\n *          002 - User 模块\n *          003 - MobileCode 模块\n * 第四段，3 位，错误码\n *       不限制规则。\n *       一般建议，每个模块自增。\n *\n * @author yshop\n */\npublic class ServiceErrorCodeRange {\n\n    // 模块 infra 错误码区间 [1-001-000-000 ~ 1-002-000-000)\n    // 模块 system 错误码区间 [1-002-000-000 ~ 1-003-000-000)\n    // 模块 report 错误码区间 [1-003-000-000 ~ 1-004-000-000)\n    // 模块 member 错误码区间 [1-004-000-000 ~ 1-005-000-000)\n    // 模块 mp 错误码区间 [1-006-000-000 ~ 1-007-000-000)\n    // 模块 pay 错误码区间 [1-007-000-000 ~ 1-008-000-000)\n    // 模块 bpm 错误码区间 [1-009-000-000 ~ 1-010-000-000)\n\n    // 模块 product 错误码区间 [1-008-000-000 ~ 1-009-000-000)\n    // 模块 trade 错误码区间 [1-011-000-000 ~ 1-012-000-000)\n    // 模块 promotion 错误码区间 [1-013-000-000 ~ 1-014-000-000)\n\n    // 模块 crm 错误码区间 [1-020-000-000 ~ 1-021-000-000)\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/exception/util/ServiceExceptionUtil.java",
    "content": "package co.yixiang.yshop.framework.common.exception.util;\n\nimport co.yixiang.yshop.framework.common.exception.ErrorCode;\nimport co.yixiang.yshop.framework.common.exception.ServiceException;\nimport co.yixiang.yshop.framework.common.exception.enums.GlobalErrorCodeConstants;\nimport com.google.common.annotations.VisibleForTesting;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * {@link ServiceException} 工具类\n *\n * 目的在于，格式化异常信息提示。\n * 考虑到 String.format 在参数不正确时会报错，因此使用 {} 作为占位符，并使用 {@link #doFormat(int, String, Object...)} 方法来格式化\n *\n */\n@Slf4j\npublic class ServiceExceptionUtil {\n\n    // ========== 和 ServiceException 的集成 ==========\n\n    public static ServiceException exception(ErrorCode errorCode) {\n        return exception0(errorCode.getCode(), errorCode.getMsg());\n    }\n\n    public static ServiceException exception(ErrorCode errorCode, Object... params) {\n        return exception0(errorCode.getCode(), errorCode.getMsg(), params);\n    }\n\n    public static ServiceException exception0(Integer code, String messagePattern, Object... params) {\n        String message = doFormat(code, messagePattern, params);\n        return new ServiceException(code, message);\n    }\n\n    public static ServiceException invalidParamException(String messagePattern, Object... params) {\n        return exception0(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), messagePattern, params);\n    }\n\n    // ========== 格式化方法 ==========\n\n    /**\n     * 将错误编号对应的消息使用 params 进行格式化。\n     *\n     * @param code           错误编号\n     * @param messagePattern 消息模版\n     * @param params         参数\n     * @return 格式化后的提示\n     */\n    @VisibleForTesting\n    public static String doFormat(int code, String messagePattern, Object... params) {\n        StringBuilder sbuf = new StringBuilder(messagePattern.length() + 50);\n        int i = 0;\n        int j;\n        int l;\n        for (l = 0; l < params.length; l++) {\n            j = messagePattern.indexOf(\"{}\", i);\n            if (j == -1) {\n                log.error(\"[doFormat][参数过多：错误码({})|错误内容({})|参数({})\", code, messagePattern, params);\n                if (i == 0) {\n                    return messagePattern;\n                } else {\n                    sbuf.append(messagePattern.substring(i));\n                    return sbuf.toString();\n                }\n            } else {\n                sbuf.append(messagePattern, i, j);\n                sbuf.append(params[l]);\n                i = j + 2;\n            }\n        }\n        if (messagePattern.indexOf(\"{}\", i) != -1) {\n            log.error(\"[doFormat][参数过少：错误码({})|错误内容({})|参数({})\", code, messagePattern, params);\n        }\n        sbuf.append(messagePattern.substring(i));\n        return sbuf.toString();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/params/QueryParam.java",
    "content": "/**\n * Copyright (C) 2018-2022\n * All rights reserved, Designed By www.yixiang.co\n\n */\npackage co.yixiang.yshop.framework.common.params;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.io.Serializable;\n\n\n@Data\n@Schema(description = \"用户 APP - 查询参数对象\")\npublic abstract class QueryParam implements Serializable{\n    private static final long serialVersionUID = -3263921252635611410L;\n\n    @Schema(description = \"页码,默认为1\", required = true)\n\tprivate Integer page =1;\n\n    @Schema(description = \"页大小,默认为10\", required = true)\n\tprivate Integer limit = 10;\n\n    @Schema(description = \"搜索字符串\", required = true)\n    private String keyword;\n\n//    @Schema(description = \"当前第几页\", required = true)\n//    public void setCurrent(Integer current) {\n//\t    if (current == null || current <= 0){\n//\t        this.page = 1;\n//        }else{\n//            this.page = current;\n//        }\n//    }\n//\n//    public void setSize(Integer size) {\n//\t    if (size == null || size <= 0){\n//\t        this.limit = 10;\n//        }else{\n//            this.limit = size;\n//        }\n//    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/pojo/CommonResult.java",
    "content": "package co.yixiang.yshop.framework.common.pojo;\n\nimport co.yixiang.yshop.framework.common.exception.ErrorCode;\nimport co.yixiang.yshop.framework.common.exception.ServiceException;\nimport co.yixiang.yshop.framework.common.exception.enums.GlobalErrorCodeConstants;\nimport com.fasterxml.jackson.annotation.JsonIgnore;\nimport lombok.Data;\nimport org.springframework.util.Assert;\n\nimport java.io.Serializable;\nimport java.util.Objects;\n\n/**\n * 通用返回\n *\n * @param <T> 数据泛型\n */\n@Data\npublic class CommonResult<T> implements Serializable {\n\n    /**\n     * 错误码\n     *\n     * @see ErrorCode#getCode()\n     */\n    private Integer code;\n    /**\n     * 返回数据\n     */\n    private T data;\n    /**\n     * 错误提示，用户可阅读\n     *\n     * @see ErrorCode#getMsg() ()\n     */\n    private String msg;\n\n    /**\n     * 将传入的 result 对象，转换成另外一个泛型结果的对象\n     *\n     * 因为 A 方法返回的 CommonResult 对象，不满足调用其的 B 方法的返回，所以需要进行转换。\n     *\n     * @param result 传入的 result 对象\n     * @param <T>    返回的泛型\n     * @return 新的 CommonResult 对象\n     */\n    public static <T> CommonResult<T> error(CommonResult<?> result) {\n        return error(result.getCode(), result.getMsg());\n    }\n\n    public static <T> CommonResult<T> error(Integer code, String message) {\n        Assert.isTrue(!GlobalErrorCodeConstants.SUCCESS.getCode().equals(code), \"code 必须是错误的！\");\n        CommonResult<T> result = new CommonResult<>();\n        result.code = code;\n        result.msg = message;\n        return result;\n    }\n\n    public static <T> CommonResult<T> error(ErrorCode errorCode) {\n        return error(errorCode.getCode(), errorCode.getMsg());\n    }\n\n    public static <T> CommonResult<T> success(T data) {\n        CommonResult<T> result = new CommonResult<>();\n        result.code = GlobalErrorCodeConstants.SUCCESS.getCode();\n        result.data = data;\n        result.msg = \"\";\n        return result;\n    }\n\n    public static boolean isSuccess(Integer code) {\n        return Objects.equals(code, GlobalErrorCodeConstants.SUCCESS.getCode());\n    }\n\n    @JsonIgnore // 避免 jackson 序列化\n    public boolean isSuccess() {\n        return isSuccess(code);\n    }\n\n    @JsonIgnore // 避免 jackson 序列化\n    public boolean isError() {\n        return !isSuccess();\n    }\n\n    // ========= 和 Exception 异常体系集成 =========\n\n    /**\n     * 判断是否有异常。如果有，则抛出 {@link ServiceException} 异常\n     */\n    public void checkError() throws ServiceException {\n        if (isSuccess()) {\n            return;\n        }\n        // 业务异常\n        throw new ServiceException(code, msg);\n    }\n\n    /**\n     * 判断是否有异常。如果有，则抛出 {@link ServiceException} 异常\n     * 如果没有，则返回 {@link #data} 数据\n     */\n    @JsonIgnore // 避免 jackson 序列化\n    public T getCheckedData() {\n        checkError();\n        return data;\n    }\n\n    public static <T> CommonResult<T> error(ServiceException serviceException) {\n        return error(serviceException.getCode(), serviceException.getMessage());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/pojo/PageParam.java",
    "content": "package co.yixiang.yshop.framework.common.pojo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.Min;\nimport jakarta.validation.constraints.Max;\nimport jakarta.validation.constraints.NotNull;\nimport java.io.Serializable;\n\n@Schema(description=\"分页参数\")\n@Data\npublic class PageParam implements Serializable {\n\n    private static final Integer PAGE_NO = 1;\n    private static final Integer PAGE_SIZE = 10;\n\n    /**\n     * 每页条数 - 不分页\n     *\n     * 例如说，导出接口，可以设置 {@link #pageSize} 为 -1 不分页，查询所有数据。\n     */\n    public static final Integer PAGE_SIZE_NONE = -1;\n\n    @Schema(description = \"页码，从 1 开始\", requiredMode = Schema.RequiredMode.REQUIRED,example = \"1\")\n    @NotNull(message = \"页码不能为空\")\n    @Min(value = 1, message = \"页码最小值为 1\")\n    private Integer pageNo = PAGE_NO;\n\n    @Schema(description = \"每页条数，最大值为 100\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"10\")\n    @NotNull(message = \"每页条数不能为空\")\n    @Min(value = 1, message = \"每页条数最小值为 1\")\n    @Max(value = 100, message = \"每页条数最大值为 100\")\n    private Integer pageSize = PAGE_SIZE;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/pojo/PageResult.java",
    "content": "package co.yixiang.yshop.framework.common.pojo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.io.Serializable;\nimport java.util.ArrayList;\nimport java.util.List;\n\n@Schema(description = \"分页结果\")\n@Data\npublic final class PageResult<T> implements Serializable {\n\n    @Schema(description = \"数据\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private List<T> list;\n\n    @Schema(description = \"总量\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private Long total;\n\n    public PageResult() {\n    }\n\n    public PageResult(List<T> list, Long total) {\n        this.list = list;\n        this.total = total;\n    }\n\n    public PageResult(Long total) {\n        this.list = new ArrayList<>();\n        this.total = total;\n    }\n\n    public static <T> PageResult<T> empty() {\n        return new PageResult<>(0L);\n    }\n\n    public static <T> PageResult<T> empty(Long total) {\n        return new PageResult<>(total);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/pojo/SortablePageParam.java",
    "content": "package co.yixiang.yshop.framework.common.pojo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\n\nimport java.util.List;\n\n@Schema(description = \"可排序的分页参数\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class SortablePageParam extends PageParam {\n\n    @Schema(description = \"排序字段\")\n    private List<SortingField> sortingFields;\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/pojo/SortingField.java",
    "content": "package co.yixiang.yshop.framework.common.pojo;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\nimport java.io.Serializable;\n\n/**\n * 排序字段 DTO\n *\n * 类名加了 ing 的原因是，避免和 ES SortField 重名。\n */\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\npublic class SortingField implements Serializable {\n\n    /**\n     * 顺序 - 升序\n     */\n    public static final String ORDER_ASC = \"asc\";\n    /**\n     * 顺序 - 降序\n     */\n    public static final String ORDER_DESC = \"desc\";\n\n    /**\n     * 字段\n     */\n    private String field;\n    /**\n     * 顺序\n     */\n    private String order;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/serializer/BigDecimalSerializer.java",
    "content": "package co.yixiang.yshop.framework.common.serializer;\n\nimport com.fasterxml.jackson.core.JsonGenerator;\nimport com.fasterxml.jackson.databind.JsonSerializer;\nimport com.fasterxml.jackson.databind.SerializerProvider;\n\nimport java.io.IOException;\nimport java.math.BigDecimal;\nimport java.text.DecimalFormat;\n\n/**\n * @author ：LionCity\n * @date ：Created in 2020-05-30 14:12\n * @description：\n * @modified By：\n * @version:\n */\npublic class BigDecimalSerializer extends JsonSerializer<BigDecimal> {\n    @Override\n    public void serialize(BigDecimal value, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException {\n        if (value != null && !\"\".equals(value)) {\n            DecimalFormat df2 =new DecimalFormat(\"0.00\");\n            gen.writeString(df2.format(value));\n        } else {\n            gen.writeString(value + \"\");\n        }\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/serializer/DoubleSerializer.java",
    "content": "package co.yixiang.yshop.framework.common.serializer;\n\nimport com.fasterxml.jackson.core.JsonGenerator;\nimport com.fasterxml.jackson.databind.JsonSerializer;\nimport com.fasterxml.jackson.databind.SerializerProvider;\n\nimport java.io.IOException;\nimport java.text.DecimalFormat;\n\n/**\n * @author ：LionCity\n * @date ：Created in 2020-05-30 14:12\n * @description：\n * @modified By：\n * @version:\n */\npublic class DoubleSerializer extends JsonSerializer<Double> {\n    @Override\n    public void serialize(Double value, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException {\n        if (value != null && !\"\".equals(value)) {\n            DecimalFormat df2 =new DecimalFormat(\"0.00\");\n            gen.writeString(df2.format(value));\n        } else {\n            gen.writeString(value + \"\");\n        }\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/util/cache/CacheUtils.java",
    "content": "package co.yixiang.yshop.framework.common.util.cache;\n\nimport com.google.common.cache.CacheBuilder;\nimport com.google.common.cache.CacheLoader;\nimport com.google.common.cache.LoadingCache;\n\nimport java.time.Duration;\nimport java.util.concurrent.Executors;\n\n/**\n * Cache 工具类\n *\n * @author yshop\n */\npublic class CacheUtils {\n\n    /**\n     * 构建异步刷新的 LoadingCache 对象\n     *\n     * 注意：如果你的缓存和 ThreadLocal 有关系，要么自己处理 ThreadLocal 的传递，要么使用 {@link #buildCache(Duration, CacheLoader)} 方法\n     *\n     * 或者简单理解：\n     * 1、和“人”相关的，使用 {@link #buildCache(Duration, CacheLoader)} 方法\n     * 2、和“全局”、“系统”相关的，使用当前缓存方法\n     *\n     * @param duration 过期时间\n     * @param loader  CacheLoader 对象\n     * @return LoadingCache 对象\n     */\n    public static <K, V> LoadingCache<K, V> buildAsyncReloadingCache(Duration duration, CacheLoader<K, V> loader) {\n        return CacheBuilder.newBuilder()\n                // 只阻塞当前数据加载线程，其他线程返回旧值\n                .refreshAfterWrite(duration)\n                // 通过 asyncReloading 实现全异步加载，包括 refreshAfterWrite 被阻塞的加载线程\n                .build(CacheLoader.asyncReloading(loader, Executors.newCachedThreadPool())); // TODO yshop：可能要思考下，未来要不要做成可配置\n    }\n\n    /**\n     * 构建同步刷新的 LoadingCache 对象\n     *\n     * @param duration 过期时间\n     * @param loader  CacheLoader 对象\n     * @return LoadingCache 对象\n     */\n    public static <K, V> LoadingCache<K, V> buildCache(Duration duration, CacheLoader<K, V> loader) {\n        return CacheBuilder.newBuilder().refreshAfterWrite(duration).build(loader);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/util/collection/ArrayUtils.java",
    "content": "package co.yixiang.yshop.framework.common.util.collection;\n\nimport cn.hutool.core.collection.CollectionUtil;\nimport cn.hutool.core.collection.IterUtil;\nimport cn.hutool.core.util.ArrayUtil;\n\nimport java.util.Collection;\nimport java.util.function.Consumer;\nimport java.util.function.Function;\n\nimport static co.yixiang.yshop.framework.common.util.collection.CollectionUtils.convertList;\n\n/**\n * Array 工具类\n *\n * @author yshop\n */\npublic class ArrayUtils {\n\n    /**\n     * 将 object 和 newElements 合并成一个数组\n     *\n     * @param object 对象\n     * @param newElements 数组\n     * @param <T> 泛型\n     * @return 结果数组\n     */\n    @SafeVarargs\n    public static <T> Consumer<T>[] append(Consumer<T> object, Consumer<T>... newElements) {\n        if (object == null) {\n            return newElements;\n        }\n        Consumer<T>[] result = ArrayUtil.newArray(Consumer.class, 1 + newElements.length);\n        result[0] = object;\n        System.arraycopy(newElements, 0, result, 1, newElements.length);\n        return result;\n    }\n\n    public static <T, V> V[] toArray(Collection<T> from, Function<T, V> mapper) {\n        return toArray(convertList(from, mapper));\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    public static <T> T[] toArray(Collection<T> from) {\n        if (CollectionUtil.isEmpty(from)) {\n            return (T[]) (new Object[0]);\n        }\n        return ArrayUtil.toArray(from, (Class<T>) IterUtil.getElementType(from.iterator()));\n    }\n\n    public static <T> T get(T[] array, int index) {\n        if (null == array || index >= array.length) {\n            return null;\n        }\n        return array[index];\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/util/collection/CollectionUtils.java",
    "content": "package co.yixiang.yshop.framework.common.util.collection;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.collection.CollectionUtil;\nimport cn.hutool.core.util.ArrayUtil;\nimport com.google.common.collect.ImmutableMap;\n\nimport java.util.*;\nimport java.util.function.*;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\nimport static java.util.Arrays.asList;\n\n/**\n * Collection 工具类\n *\n * @author yshop\n */\npublic class CollectionUtils {\n\n    public static boolean containsAny(Object source, Object... targets) {\n        return asList(targets).contains(source);\n    }\n\n    public static boolean isAnyEmpty(Collection<?>... collections) {\n        return Arrays.stream(collections).anyMatch(CollectionUtil::isEmpty);\n    }\n\n    public static <T> boolean anyMatch(Collection<T> from, Predicate<T> predicate) {\n        return from.stream().anyMatch(predicate);\n    }\n\n    public static <T> List<T> filterList(Collection<T> from, Predicate<T> predicate) {\n        if (CollUtil.isEmpty(from)) {\n            return new ArrayList<>();\n        }\n        return from.stream().filter(predicate).collect(Collectors.toList());\n    }\n\n    public static <T, R> List<T> distinct(Collection<T> from, Function<T, R> keyMapper) {\n        if (CollUtil.isEmpty(from)) {\n            return new ArrayList<>();\n        }\n        return distinct(from, keyMapper, (t1, t2) -> t1);\n    }\n\n    public static <T, R> List<T> distinct(Collection<T> from, Function<T, R> keyMapper, BinaryOperator<T> cover) {\n        if (CollUtil.isEmpty(from)) {\n            return new ArrayList<>();\n        }\n        return new ArrayList<>(convertMap(from, keyMapper, Function.identity(), cover).values());\n    }\n\n    public static <T, U> List<U> convertList(T[] from, Function<T, U> func) {\n        if (ArrayUtil.isEmpty(from)) {\n            return new ArrayList<>();\n        }\n        return convertList(Arrays.asList(from), func);\n    }\n\n    public static <T, U> List<U> convertList(Collection<T> from, Function<T, U> func) {\n        if (CollUtil.isEmpty(from)) {\n            return new ArrayList<>();\n        }\n        return from.stream().map(func).filter(Objects::nonNull).collect(Collectors.toList());\n    }\n\n    public static <T, U> List<U> convertList(Collection<T> from, Function<T, U> func, Predicate<T> filter) {\n        if (CollUtil.isEmpty(from)) {\n            return new ArrayList<>();\n        }\n        return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toList());\n    }\n\n    public static <T, U> List<U> convertListByFlatMap(Collection<T> from,\n                                                      Function<T, ? extends Stream<? extends U>> func) {\n        if (CollUtil.isEmpty(from)) {\n            return new ArrayList<>();\n        }\n        return from.stream().filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toList());\n    }\n\n    public static <T, U, R> List<R> convertListByFlatMap(Collection<T> from,\n                                                         Function<? super T, ? extends U> mapper,\n                                                         Function<U, ? extends Stream<? extends R>> func) {\n        if (CollUtil.isEmpty(from)) {\n            return new ArrayList<>();\n        }\n        return from.stream().map(mapper).filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toList());\n    }\n\n    public static <K, V> List<V> mergeValuesFromMap(Map<K, List<V>> map) {\n        return map.values()\n                .stream()\n                .flatMap(List::stream)\n                .collect(Collectors.toList());\n    }\n\n    public static <T> Set<T> convertSet(Collection<T> from) {\n        return convertSet(from, v -> v);\n    }\n\n    public static <T, U> Set<U> convertSet(Collection<T> from, Function<T, U> func) {\n        if (CollUtil.isEmpty(from)) {\n            return new HashSet<>();\n        }\n        return from.stream().map(func).filter(Objects::nonNull).collect(Collectors.toSet());\n    }\n\n    public static <T, U> Set<U> convertSet(Collection<T> from, Function<T, U> func, Predicate<T> filter) {\n        if (CollUtil.isEmpty(from)) {\n            return new HashSet<>();\n        }\n        return from.stream().filter(filter).map(func).filter(Objects::nonNull).collect(Collectors.toSet());\n    }\n\n    public static <T, K> Map<K, T> convertMapByFilter(Collection<T> from, Predicate<T> filter, Function<T, K> keyFunc) {\n        if (CollUtil.isEmpty(from)) {\n            return new HashMap<>();\n        }\n        return from.stream().filter(filter).collect(Collectors.toMap(keyFunc, v -> v));\n    }\n\n    public static <T, U> Set<U> convertSetByFlatMap(Collection<T> from,\n                                                    Function<T, ? extends Stream<? extends U>> func) {\n        if (CollUtil.isEmpty(from)) {\n            return new HashSet<>();\n        }\n        return from.stream().filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toSet());\n    }\n\n    public static <T, U, R> Set<R> convertSetByFlatMap(Collection<T> from,\n                                                       Function<? super T, ? extends U> mapper,\n                                                       Function<U, ? extends Stream<? extends R>> func) {\n        if (CollUtil.isEmpty(from)) {\n            return new HashSet<>();\n        }\n        return from.stream().map(mapper).filter(Objects::nonNull).flatMap(func).filter(Objects::nonNull).collect(Collectors.toSet());\n    }\n\n    public static <T, K> Map<K, T> convertMap(Collection<T> from, Function<T, K> keyFunc) {\n        if (CollUtil.isEmpty(from)) {\n            return new HashMap<>();\n        }\n        return convertMap(from, keyFunc, Function.identity());\n    }\n\n    public static <T, K> Map<K, T> convertMap(Collection<T> from, Function<T, K> keyFunc, Supplier<? extends Map<K, T>> supplier) {\n        if (CollUtil.isEmpty(from)) {\n            return supplier.get();\n        }\n        return convertMap(from, keyFunc, Function.identity(), supplier);\n    }\n\n    public static <T, K, V> Map<K, V> convertMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {\n        if (CollUtil.isEmpty(from)) {\n            return new HashMap<>();\n        }\n        return convertMap(from, keyFunc, valueFunc, (v1, v2) -> v1);\n    }\n\n    public static <T, K, V> Map<K, V> convertMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc, BinaryOperator<V> mergeFunction) {\n        if (CollUtil.isEmpty(from)) {\n            return new HashMap<>();\n        }\n        return convertMap(from, keyFunc, valueFunc, mergeFunction, HashMap::new);\n    }\n\n    public static <T, K, V> Map<K, V> convertMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc, Supplier<? extends Map<K, V>> supplier) {\n        if (CollUtil.isEmpty(from)) {\n            return supplier.get();\n        }\n        return convertMap(from, keyFunc, valueFunc, (v1, v2) -> v1, supplier);\n    }\n\n    public static <T, K, V> Map<K, V> convertMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc, BinaryOperator<V> mergeFunction, Supplier<? extends Map<K, V>> supplier) {\n        if (CollUtil.isEmpty(from)) {\n            return new HashMap<>();\n        }\n        return from.stream().collect(Collectors.toMap(keyFunc, valueFunc, mergeFunction, supplier));\n    }\n\n    public static <T, K> Map<K, List<T>> convertMultiMap(Collection<T> from, Function<T, K> keyFunc) {\n        if (CollUtil.isEmpty(from)) {\n            return new HashMap<>();\n        }\n        return from.stream().collect(Collectors.groupingBy(keyFunc, Collectors.mapping(t -> t, Collectors.toList())));\n    }\n\n    public static <T, K, V> Map<K, List<V>> convertMultiMap(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {\n        if (CollUtil.isEmpty(from)) {\n            return new HashMap<>();\n        }\n        return from.stream()\n                .collect(Collectors.groupingBy(keyFunc, Collectors.mapping(valueFunc, Collectors.toList())));\n    }\n\n    // 暂时没想好名字，先以 2 结尾噶\n    public static <T, K, V> Map<K, Set<V>> convertMultiMap2(Collection<T> from, Function<T, K> keyFunc, Function<T, V> valueFunc) {\n        if (CollUtil.isEmpty(from)) {\n            return new HashMap<>();\n        }\n        return from.stream().collect(Collectors.groupingBy(keyFunc, Collectors.mapping(valueFunc, Collectors.toSet())));\n    }\n\n    public static <T, K> Map<K, T> convertImmutableMap(Collection<T> from, Function<T, K> keyFunc) {\n        if (CollUtil.isEmpty(from)) {\n            return Collections.emptyMap();\n        }\n        ImmutableMap.Builder<K, T> builder = ImmutableMap.builder();\n        from.forEach(item -> builder.put(keyFunc.apply(item), item));\n        return builder.build();\n    }\n\n    /**\n     * 对比老、新两个列表，找出新增、修改、删除的数据\n     *\n     * @param oldList  老列表\n     * @param newList  新列表\n     * @param sameFunc 对比函数，返回 true 表示相同，返回 false 表示不同\n     *                 注意，same 是通过每个元素的“标识”，判断它们是不是同一个数据\n     * @return [新增列表、修改列表、删除列表]\n     */\n    public static <T> List<List<T>> diffList(Collection<T> oldList, Collection<T> newList,\n                                             BiFunction<T, T, Boolean> sameFunc) {\n        List<T> createList = new LinkedList<>(newList); // 默认都认为是新增的，后续会进行移除\n        List<T> updateList = new ArrayList<>();\n        List<T> deleteList = new ArrayList<>();\n\n        // 通过以 oldList 为主遍历，找出 updateList 和 deleteList\n        for (T oldObj : oldList) {\n            // 1. 寻找是否有匹配的\n            T foundObj = null;\n            for (Iterator<T> iterator = createList.iterator(); iterator.hasNext(); ) {\n                T newObj = iterator.next();\n                // 1.1 不匹配，则直接跳过\n                if (!sameFunc.apply(oldObj, newObj)) {\n                    continue;\n                }\n                // 1.2 匹配，则移除，并结束寻找\n                iterator.remove();\n                foundObj = newObj;\n                break;\n            }\n            // 2. 匹配添加到 updateList；不匹配则添加到 deleteList 中\n            if (foundObj != null) {\n                updateList.add(foundObj);\n            } else {\n                deleteList.add(oldObj);\n            }\n        }\n        return asList(createList, updateList, deleteList);\n    }\n\n    public static boolean containsAny(Collection<?> source, Collection<?> candidates) {\n        return org.springframework.util.CollectionUtils.containsAny(source, candidates);\n    }\n\n    public static <T> T getFirst(List<T> from) {\n        return !CollectionUtil.isEmpty(from) ? from.get(0) : null;\n    }\n\n    public static <T> T findFirst(Collection<T> from, Predicate<T> predicate) {\n        return findFirst(from, predicate, Function.identity());\n    }\n\n    public static <T, U> U findFirst(Collection<T> from, Predicate<T> predicate, Function<T, U> func) {\n        if (CollUtil.isEmpty(from)) {\n            return null;\n        }\n        return from.stream().filter(predicate).findFirst().map(func).orElse(null);\n    }\n\n    public static <T, V extends Comparable<? super V>> V getMaxValue(Collection<T> from, Function<T, V> valueFunc) {\n        if (CollUtil.isEmpty(from)) {\n            return null;\n        }\n        assert !from.isEmpty(); // 断言，避免告警\n        T t = from.stream().max(Comparator.comparing(valueFunc)).get();\n        return valueFunc.apply(t);\n    }\n\n    public static <T, V extends Comparable<? super V>> V getMinValue(List<T> from, Function<T, V> valueFunc) {\n        if (CollUtil.isEmpty(from)) {\n            return null;\n        }\n        assert from.size() > 0; // 断言，避免告警\n        T t = from.stream().min(Comparator.comparing(valueFunc)).get();\n        return valueFunc.apply(t);\n    }\n\n    public static <T, V extends Comparable<? super V>> V getSumValue(List<T> from, Function<T, V> valueFunc,\n                                                                     BinaryOperator<V> accumulator) {\n        return getSumValue(from, valueFunc, accumulator, null);\n    }\n\n    public static <T, V extends Comparable<? super V>> V getSumValue(Collection<T> from, Function<T, V> valueFunc,\n                                                                     BinaryOperator<V> accumulator, V defaultValue) {\n        if (CollUtil.isEmpty(from)) {\n            return defaultValue;\n        }\n        assert !from.isEmpty(); // 断言，避免告警\n        return from.stream().map(valueFunc).filter(Objects::nonNull).reduce(accumulator).orElse(defaultValue);\n    }\n\n    public static <T> void addIfNotNull(Collection<T> coll, T item) {\n        if (item == null) {\n            return;\n        }\n        coll.add(item);\n    }\n\n    public static <T> Collection<T> singleton(T obj) {\n        return obj == null ? Collections.emptyList() : Collections.singleton(obj);\n    }\n\n    public static <T> List<T> newArrayList(List<List<T>> list) {\n        return list.stream().flatMap(Collection::stream).collect(Collectors.toList());\n    }\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/util/collection/MapUtils.java",
    "content": "package co.yixiang.yshop.framework.common.util.collection;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.collection.CollectionUtil;\nimport cn.hutool.core.util.ObjUtil;\nimport co.yixiang.yshop.framework.common.core.KeyValue;\nimport com.google.common.collect.Maps;\nimport com.google.common.collect.Multimap;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.function.Consumer;\n\n/**\n * Map 工具类\n *\n * @author yshop\n */\npublic class MapUtils {\n\n    /**\n     * 从哈希表表中，获得 keys 对应的所有 value 数组\n     *\n     * @param multimap 哈希表\n     * @param keys keys\n     * @return value 数组\n     */\n    public static <K, V> List<V> getList(Multimap<K, V> multimap, Collection<K> keys) {\n        List<V> result = new ArrayList<>();\n        keys.forEach(k -> {\n            Collection<V> values = multimap.get(k);\n            if (CollectionUtil.isEmpty(values)) {\n                return;\n            }\n            result.addAll(values);\n        });\n        return result;\n    }\n\n    /**\n     * 从哈希表查找到 key 对应的 value，然后进一步处理\n     * key 为 null 时, 不处理\n     * 注意，如果查找到的 value 为 null 时，不进行处理\n     *\n     * @param map 哈希表\n     * @param key key\n     * @param consumer 进一步处理的逻辑\n     */\n    public static <K, V> void findAndThen(Map<K, V> map, K key, Consumer<V> consumer) {\n        if (ObjUtil.isNull(key) || CollUtil.isEmpty(map)) {\n            return;\n        }\n        V value = map.get(key);\n        if (value == null) {\n            return;\n        }\n        consumer.accept(value);\n    }\n\n    public static <K, V> Map<K, V> convertMap(List<KeyValue<K, V>> keyValues) {\n        Map<K, V> map = Maps.newLinkedHashMapWithExpectedSize(keyValues.size());\n        keyValues.forEach(keyValue -> map.put(keyValue.getKey(), keyValue.getValue()));\n        return map;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/util/collection/SetUtils.java",
    "content": "package co.yixiang.yshop.framework.common.util.collection;\n\nimport cn.hutool.core.collection.CollUtil;\n\nimport java.util.Set;\n\n/**\n * Set 工具类\n *\n * @author yshop\n */\npublic class SetUtils {\n\n    @SafeVarargs\n    public static <T> Set<T> asSet(T... objs) {\n        return CollUtil.newHashSet(objs);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/util/date/DateUtils.java",
    "content": "package co.yixiang.yshop.framework.common.util.date;\n\nimport cn.hutool.core.date.LocalDateTimeUtil;\n\nimport java.time.*;\nimport java.util.Calendar;\nimport java.util.Date;\n\n/**\n * 时间工具类\n *\n * @author yshop\n */\npublic class DateUtils {\n\n    /**\n     * 时区 - 默认\n     */\n    public static final String TIME_ZONE_DEFAULT = \"GMT+8\";\n\n    /**\n     * 秒转换成毫秒\n     */\n    public static final long SECOND_MILLIS = 1000;\n\n    public static final String FORMAT_YEAR_MONTH_DAY = \"yyyy-MM-dd\";\n\n    public static final String FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND = \"yyyy-MM-dd HH:mm:ss\";\n\n    /**\n     * 将 LocalDateTime 转换成 Date\n     *\n     * @param date LocalDateTime\n     * @return LocalDateTime\n     */\n    public static Date of(LocalDateTime date) {\n        if (date == null) {\n            return null;\n        }\n        // 将此日期时间与时区相结合以创建 ZonedDateTime\n        ZonedDateTime zonedDateTime = date.atZone(ZoneId.systemDefault());\n        // 本地时间线 LocalDateTime 到即时时间线 Instant 时间戳\n        Instant instant = zonedDateTime.toInstant();\n        // UTC时间(世界协调时间,UTC + 00:00)转北京(北京,UTC + 8:00)时间\n        return Date.from(instant);\n    }\n\n    /**\n     * 将 Date 转换成 LocalDateTime\n     *\n     * @param date Date\n     * @return LocalDateTime\n     */\n    public static LocalDateTime of(Date date) {\n        if (date == null) {\n            return null;\n        }\n        // 转为时间戳\n        Instant instant = date.toInstant();\n        // UTC时间(世界协调时间,UTC + 00:00)转北京(北京,UTC + 8:00)时间\n        return LocalDateTime.ofInstant(instant, ZoneId.systemDefault());\n    }\n\n    public static Date addTime(Duration duration) {\n        return new Date(System.currentTimeMillis() + duration.toMillis());\n    }\n\n    public static boolean isExpired(LocalDateTime time) {\n        LocalDateTime now = LocalDateTime.now();\n        return now.isAfter(time);\n    }\n\n    /**\n     * 创建指定时间\n     *\n     * @param year  年\n     * @param mouth 月\n     * @param day   日\n     * @return 指定时间\n     */\n    public static Date buildTime(int year, int mouth, int day) {\n        return buildTime(year, mouth, day, 0, 0, 0);\n    }\n\n    /**\n     * 创建指定时间\n     *\n     * @param year   年\n     * @param mouth  月\n     * @param day    日\n     * @param hour   小时\n     * @param minute 分钟\n     * @param second 秒\n     * @return 指定时间\n     */\n    public static Date buildTime(int year, int mouth, int day,\n                                 int hour, int minute, int second) {\n        Calendar calendar = Calendar.getInstance();\n        calendar.set(Calendar.YEAR, year);\n        calendar.set(Calendar.MONTH, mouth - 1);\n        calendar.set(Calendar.DAY_OF_MONTH, day);\n        calendar.set(Calendar.HOUR_OF_DAY, hour);\n        calendar.set(Calendar.MINUTE, minute);\n        calendar.set(Calendar.SECOND, second);\n        calendar.set(Calendar.MILLISECOND, 0); // 一般情况下，都是 0 毫秒\n        return calendar.getTime();\n    }\n\n    public static Date max(Date a, Date b) {\n        if (a == null) {\n            return b;\n        }\n        if (b == null) {\n            return a;\n        }\n        return a.compareTo(b) > 0 ? a : b;\n    }\n\n    public static LocalDateTime max(LocalDateTime a, LocalDateTime b) {\n        if (a == null) {\n            return b;\n        }\n        if (b == null) {\n            return a;\n        }\n        return a.isAfter(b) ? a : b;\n    }\n\n    /**\n     * 是否今天\n     *\n     * @param date 日期\n     * @return 是否\n     */\n    public static boolean isToday(LocalDateTime date) {\n        return LocalDateTimeUtil.isSameDay(date, LocalDateTime.now());\n    }\n\n    /**\n     * 是否昨天\n     *\n     * @param date 日期\n     * @return 是否\n     */\n    public static boolean isYesterday(LocalDateTime date) {\n        return LocalDateTimeUtil.isSameDay(date, LocalDateTime.now().minusDays(1));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/util/date/LocalDateTimeUtils.java",
    "content": "package co.yixiang.yshop.framework.common.util.date;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.date.DatePattern;\nimport cn.hutool.core.date.LocalDateTimeUtil;\nimport cn.hutool.core.lang.Assert;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.enums.DateIntervalEnum;\n\nimport java.time.*;\nimport java.time.format.DateTimeParseException;\nimport java.time.temporal.ChronoUnit;\nimport java.time.temporal.TemporalAdjusters;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * 时间工具类，用于 {@link java.time.LocalDateTime}\n *\n * @author yshop\n */\npublic class LocalDateTimeUtils {\n\n    /**\n     * 空的 LocalDateTime 对象，主要用于 DB 唯一索引的默认值\n     */\n    public static LocalDateTime EMPTY = buildTime(1970, 1, 1);\n\n    /**\n     * 解析时间\n     *\n     * 相比 {@link LocalDateTimeUtil#parse(CharSequence)} 方法来说，会尽量去解析，直到成功\n     *\n     * @param time 时间\n     * @return 时间字符串\n     */\n    public static LocalDateTime parse(String time) {\n        try {\n            return LocalDateTimeUtil.parse(time, DatePattern.NORM_DATE_PATTERN);\n        } catch (DateTimeParseException e) {\n            return LocalDateTimeUtil.parse(time);\n        }\n    }\n\n    public static LocalDateTime addTime(Duration duration) {\n        return LocalDateTime.now().plus(duration);\n    }\n\n    public static LocalDateTime minusTime(Duration duration) {\n        return LocalDateTime.now().minus(duration);\n    }\n\n    public static boolean beforeNow(LocalDateTime date) {\n        return date.isBefore(LocalDateTime.now());\n    }\n\n    public static boolean afterNow(LocalDateTime date) {\n        return date.isAfter(LocalDateTime.now());\n    }\n\n    /**\n     * 创建指定时间\n     *\n     * @param year  年\n     * @param mouth 月\n     * @param day   日\n     * @return 指定时间\n     */\n    public static LocalDateTime buildTime(int year, int mouth, int day) {\n        return LocalDateTime.of(year, mouth, day, 0, 0, 0);\n    }\n\n    public static LocalDateTime[] buildBetweenTime(int year1, int mouth1, int day1,\n                                                   int year2, int mouth2, int day2) {\n        return new LocalDateTime[]{buildTime(year1, mouth1, day1), buildTime(year2, mouth2, day2)};\n    }\n\n    /**\n     * 判指定断时间，是否在该时间范围内\n     *\n     * @param startTime 开始时间\n     * @param endTime 结束时间\n     * @param time 指定时间\n     * @return 是否\n     */\n    public static boolean isBetween(LocalDateTime startTime, LocalDateTime endTime, String time) {\n        if (startTime == null || endTime == null || time == null) {\n            return false;\n        }\n        return LocalDateTimeUtil.isIn(parse(time), startTime, endTime);\n    }\n\n    /**\n     * 判断当前时间是否在该时间范围内\n     *\n     * @param startTime 开始时间\n     * @param endTime   结束时间\n     * @return 是否\n     */\n    public static boolean isBetween(LocalDateTime startTime, LocalDateTime endTime) {\n        if (startTime == null || endTime == null) {\n            return false;\n        }\n        return LocalDateTimeUtil.isIn(LocalDateTime.now(), startTime, endTime);\n    }\n\n    /**\n     * 判断当前时间是否在该时间范围内\n     *\n     * @param startTime 开始时间\n     * @param endTime   结束时间\n     * @return 是否\n     */\n    public static boolean isBetween(String startTime, String endTime) {\n        if (startTime == null || endTime == null) {\n            return false;\n        }\n        LocalDate nowDate = LocalDate.now();\n        return LocalDateTimeUtil.isIn(LocalDateTime.now(),\n                LocalDateTime.of(nowDate, LocalTime.parse(startTime)),\n                LocalDateTime.of(nowDate, LocalTime.parse(endTime)));\n    }\n\n    /**\n     * 判断时间段是否重叠\n     *\n     * @param startTime1 开始 time1\n     * @param endTime1   结束 time1\n     * @param startTime2 开始 time2\n     * @param endTime2   结束 time2\n     * @return 重叠：true 不重叠：false\n     */\n    public static boolean isOverlap(LocalTime startTime1, LocalTime endTime1, LocalTime startTime2, LocalTime endTime2) {\n        LocalDate nowDate = LocalDate.now();\n        return LocalDateTimeUtil.isOverlap(LocalDateTime.of(nowDate, startTime1), LocalDateTime.of(nowDate, endTime1),\n                LocalDateTime.of(nowDate, startTime2), LocalDateTime.of(nowDate, endTime2));\n    }\n\n    /**\n     * 获取指定日期所在的月份的开始时间\n     * 例如：2023-09-30 00:00:00,000\n     *\n     * @param date 日期\n     * @return 月份的开始时间\n     */\n    public static LocalDateTime beginOfMonth(LocalDateTime date) {\n        return date.with(TemporalAdjusters.firstDayOfMonth()).with(LocalTime.MIN);\n    }\n\n    /**\n     * 获取指定日期所在的月份的最后时间\n     * 例如：2023-09-30 23:59:59,999\n     *\n     * @param date 日期\n     * @return 月份的结束时间\n     */\n    public static LocalDateTime endOfMonth(LocalDateTime date) {\n        return date.with(TemporalAdjusters.lastDayOfMonth()).with(LocalTime.MAX);\n    }\n\n    /**\n     * 获得指定日期所在季度\n     *\n     * @param date 日期\n     * @return 所在季度\n     */\n    public static int getQuarterOfYear(LocalDateTime date) {\n        return (date.getMonthValue() - 1) / 3 + 1;\n    }\n\n    /**\n     * 获取指定日期到现在过了几天，如果指定日期在当前日期之后，获取结果为负\n     *\n     * @param dateTime 日期\n     * @return 相差天数\n     */\n    public static Long between(LocalDateTime dateTime) {\n        return LocalDateTimeUtil.between(dateTime, LocalDateTime.now(), ChronoUnit.DAYS);\n    }\n\n    /**\n     * 获取今天的开始时间\n     *\n     * @return 今天\n     */\n    public static LocalDateTime getToday() {\n        return LocalDateTimeUtil.beginOfDay(LocalDateTime.now());\n    }\n\n    /**\n     * 获取昨天的开始时间\n     *\n     * @return 昨天\n     */\n    public static LocalDateTime getYesterday() {\n        return LocalDateTimeUtil.beginOfDay(LocalDateTime.now().minusDays(1));\n    }\n\n    /**\n     * 获取本月的开始时间\n     *\n     * @return 本月\n     */\n    public static LocalDateTime getMonth() {\n        return beginOfMonth(LocalDateTime.now());\n    }\n\n    /**\n     * 获取本年的开始时间\n     *\n     * @return 本年\n     */\n    public static LocalDateTime getYear() {\n        return LocalDateTime.now().with(TemporalAdjusters.firstDayOfYear()).with(LocalTime.MIN);\n    }\n\n    public static List<LocalDateTime[]> getDateRangeList(LocalDateTime startTime,\n                                                         LocalDateTime endTime,\n                                                         Integer interval) {\n        // 1.1 找到枚举\n        DateIntervalEnum intervalEnum = DateIntervalEnum.valueOf(interval);\n        Assert.notNull(intervalEnum, \"interval({}} 找不到对应的枚举\", interval);\n        // 1.2 将时间对齐\n        startTime = LocalDateTimeUtil.beginOfDay(startTime);\n        endTime = LocalDateTimeUtil.endOfDay(endTime);\n\n        // 2. 循环，生成时间范围\n        List<LocalDateTime[]> timeRanges = new ArrayList<>();\n        switch (intervalEnum) {\n            case DAY:\n                while (startTime.isBefore(endTime)) {\n                    timeRanges.add(new LocalDateTime[]{startTime, startTime.plusDays(1).minusNanos(1)});\n                    startTime = startTime.plusDays(1);\n                }\n                break;\n            case WEEK:\n                while (startTime.isBefore(endTime)) {\n                    LocalDateTime endOfWeek = startTime.with(DayOfWeek.SUNDAY).plusDays(1).minusNanos(1);\n                    timeRanges.add(new LocalDateTime[]{startTime, endOfWeek});\n                    startTime = endOfWeek.plusNanos(1);\n                }\n                break;\n            case MONTH:\n                while (startTime.isBefore(endTime)) {\n                    LocalDateTime endOfMonth = startTime.with(TemporalAdjusters.lastDayOfMonth()).plusDays(1).minusNanos(1);\n                    timeRanges.add(new LocalDateTime[]{startTime, endOfMonth});\n                    startTime = endOfMonth.plusNanos(1);\n                }\n                break;\n            case QUARTER:\n                while (startTime.isBefore(endTime)) {\n                    int quarterOfYear = getQuarterOfYear(startTime);\n                    LocalDateTime quarterEnd = quarterOfYear == 4\n                            ? startTime.with(TemporalAdjusters.lastDayOfYear()).plusDays(1).minusNanos(1)\n                            : startTime.withMonth(quarterOfYear * 3 + 1).withDayOfMonth(1).minusNanos(1);\n                    timeRanges.add(new LocalDateTime[]{startTime, quarterEnd});\n                    startTime = quarterEnd.plusNanos(1);\n                }\n                break;\n            case YEAR:\n                while (startTime.isBefore(endTime)) {\n                    LocalDateTime endOfYear = startTime.with(TemporalAdjusters.lastDayOfYear()).plusDays(1).minusNanos(1);\n                    timeRanges.add(new LocalDateTime[]{startTime, endOfYear});\n                    startTime = endOfYear.plusNanos(1);\n                }\n                break;\n            default:\n                throw new IllegalArgumentException(\"Invalid interval: \" + interval);\n        }\n        // 3. 兜底，最后一个时间，需要保持在 endTime 之前\n        LocalDateTime[] lastTimeRange = CollUtil.getLast(timeRanges);\n        if (lastTimeRange != null) {\n            lastTimeRange[1] = endTime;\n        }\n        return timeRanges;\n    }\n\n    /**\n     * 格式化时间范围\n     *\n     * @param startTime 开始时间\n     * @param endTime   结束时间\n     * @param interval  时间间隔\n     * @return 时间范围\n     */\n    public static String formatDateRange(LocalDateTime startTime, LocalDateTime endTime, Integer interval) {\n        // 1. 找到枚举\n        DateIntervalEnum intervalEnum = DateIntervalEnum.valueOf(interval);\n        Assert.notNull(intervalEnum, \"interval({}} 找不到对应的枚举\", interval);\n\n        // 2. 循环，生成时间范围\n        switch (intervalEnum) {\n            case DAY:\n                return LocalDateTimeUtil.format(startTime, DatePattern.NORM_DATE_PATTERN);\n            case WEEK:\n                return LocalDateTimeUtil.format(startTime, DatePattern.NORM_DATE_PATTERN)\n                        + StrUtil.format(\"(第 {} 周)\", LocalDateTimeUtil.weekOfYear(startTime));\n            case MONTH:\n                return LocalDateTimeUtil.format(startTime, DatePattern.NORM_MONTH_PATTERN);\n            case QUARTER:\n                return StrUtil.format(\"{}-Q{}\", startTime.getYear(), getQuarterOfYear(startTime));\n            case YEAR:\n                return LocalDateTimeUtil.format(startTime, DatePattern.NORM_YEAR_PATTERN);\n            default:\n                throw new IllegalArgumentException(\"Invalid interval: \" + interval);\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/util/http/HttpUtils.java",
    "content": "package co.yixiang.yshop.framework.common.util.http;\n\nimport cn.hutool.core.codec.Base64;\nimport cn.hutool.core.map.TableMap;\nimport cn.hutool.core.net.url.UrlBuilder;\nimport cn.hutool.core.util.ReflectUtil;\nimport cn.hutool.core.util.StrUtil;\nimport org.springframework.util.StringUtils;\nimport org.springframework.web.util.UriComponents;\nimport org.springframework.web.util.UriComponentsBuilder;\n\nimport jakarta.servlet.http.HttpServletRequest;\nimport java.net.URI;\nimport java.nio.charset.Charset;\nimport java.util.Map;\n\n/**\n * HTTP 工具类\n *\n * @author yshop\n */\npublic class HttpUtils {\n\n    @SuppressWarnings(\"unchecked\")\n    public static String replaceUrlQuery(String url, String key, String value) {\n        UrlBuilder builder = UrlBuilder.of(url, Charset.defaultCharset());\n        // 先移除\n        TableMap<CharSequence, CharSequence> query = (TableMap<CharSequence, CharSequence>)\n                ReflectUtil.getFieldValue(builder.getQuery(), \"query\");\n        query.remove(key);\n        // 后添加\n        builder.addQuery(key, value);\n        return builder.build();\n    }\n\n    private String append(String base, Map<String, ?> query, boolean fragment) {\n        return append(base, query, null, fragment);\n    }\n\n    /**\n     * 拼接 URL\n     *\n     * copy from Spring Security OAuth2 的 AuthorizationEndpoint 类的 append 方法\n     *\n     * @param base 基础 URL\n     * @param query 查询参数\n     * @param keys query 的 key，对应的原本的 key 的映射。例如说 query 里有个 key 是 xx，实际它的 key 是 extra_xx，则通过 keys 里添加这个映射\n     * @param fragment URL 的 fragment，即拼接到 # 中\n     * @return 拼接后的 URL\n     */\n    public static String append(String base, Map<String, ?> query, Map<String, String> keys, boolean fragment) {\n        UriComponentsBuilder template = UriComponentsBuilder.newInstance();\n        UriComponentsBuilder builder = UriComponentsBuilder.fromUriString(base);\n        URI redirectUri;\n        try {\n            // assume it's encoded to start with (if it came in over the wire)\n            redirectUri = builder.build(true).toUri();\n        } catch (Exception e) {\n            // ... but allow client registrations to contain hard-coded non-encoded values\n            redirectUri = builder.build().toUri();\n            builder = UriComponentsBuilder.fromUri(redirectUri);\n        }\n        template.scheme(redirectUri.getScheme()).port(redirectUri.getPort()).host(redirectUri.getHost())\n                .userInfo(redirectUri.getUserInfo()).path(redirectUri.getPath());\n\n        if (fragment) {\n            StringBuilder values = new StringBuilder();\n            if (redirectUri.getFragment() != null) {\n                String append = redirectUri.getFragment();\n                values.append(append);\n            }\n            for (String key : query.keySet()) {\n                if (values.length() > 0) {\n                    values.append(\"&\");\n                }\n                String name = key;\n                if (keys != null && keys.containsKey(key)) {\n                    name = keys.get(key);\n                }\n                values.append(name).append(\"={\").append(key).append(\"}\");\n            }\n            if (values.length() > 0) {\n                template.fragment(values.toString());\n            }\n            UriComponents encoded = template.build().expand(query).encode();\n            builder.fragment(encoded.getFragment());\n        } else {\n            for (String key : query.keySet()) {\n                String name = key;\n                if (keys != null && keys.containsKey(key)) {\n                    name = keys.get(key);\n                }\n                template.queryParam(name, \"{\" + key + \"}\");\n            }\n            template.fragment(redirectUri.getFragment());\n            UriComponents encoded = template.build().expand(query).encode();\n            builder.query(encoded.getQuery());\n        }\n        return builder.build().toUriString();\n    }\n\n    public static String[] obtainBasicAuthorization(HttpServletRequest request) {\n        String clientId;\n        String clientSecret;\n        // 先从 Header 中获取\n        String authorization = request.getHeader(\"Authorization\");\n        authorization = StrUtil.subAfter(authorization, \"Basic \", true);\n        if (StringUtils.hasText(authorization)) {\n            authorization = Base64.decodeStr(authorization);\n            clientId = StrUtil.subBefore(authorization, \":\", false);\n            clientSecret = StrUtil.subAfter(authorization, \":\", false);\n        // 再从 Param 中获取\n        } else {\n            clientId = request.getParameter(\"client_id\");\n            clientSecret = request.getParameter(\"client_secret\");\n        }\n\n        // 如果两者非空，则返回\n        if (StrUtil.isNotEmpty(clientId) && StrUtil.isNotEmpty(clientSecret)) {\n            return new String[]{clientId, clientSecret};\n        }\n        return null;\n    }\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/util/io/FileUtils.java",
    "content": "package co.yixiang.yshop.framework.common.util.io;\n\nimport cn.hutool.core.io.FileTypeUtil;\nimport cn.hutool.core.io.FileUtil;\nimport cn.hutool.core.io.file.FileNameUtil;\nimport cn.hutool.core.util.IdUtil;\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.crypto.digest.DigestUtil;\nimport lombok.SneakyThrows;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.File;\n\n/**\n * 文件工具类\n *\n * @author yshop\n */\npublic class FileUtils {\n\n    /**\n     * 创建临时文件\n     * 该文件会在 JVM 退出时，进行删除\n     *\n     * @param data 文件内容\n     * @return 文件\n     */\n    @SneakyThrows\n    public static File createTempFile(String data) {\n        File file = createTempFile();\n        // 写入内容\n        FileUtil.writeUtf8String(data, file);\n        return file;\n    }\n\n    /**\n     * 创建临时文件\n     * 该文件会在 JVM 退出时，进行删除\n     *\n     * @param data 文件内容\n     * @return 文件\n     */\n    @SneakyThrows\n    public static File createTempFile(byte[] data) {\n        File file = createTempFile();\n        // 写入内容\n        FileUtil.writeBytes(data, file);\n        return file;\n    }\n\n    /**\n     * 创建临时文件，无内容\n     * 该文件会在 JVM 退出时，进行删除\n     *\n     * @return 文件\n     */\n    @SneakyThrows\n    public static File createTempFile() {\n        // 创建文件，通过 UUID 保证唯一\n        File file = File.createTempFile(IdUtil.simpleUUID(), null);\n        // 标记 JVM 退出时，自动删除\n        file.deleteOnExit();\n        return file;\n    }\n\n    /**\n     * 生成文件路径\n     *\n     * @param content      文件内容\n     * @param originalName 原始文件名\n     * @return path，唯一不可重复\n     */\n    public static String generatePath(byte[] content, String originalName) {\n        String sha256Hex = DigestUtil.sha256Hex(content);\n        // 情况一：如果存在 name，则优先使用 name 的后缀\n        if (StrUtil.isNotBlank(originalName)) {\n            String extName = FileNameUtil.extName(originalName);\n            return StrUtil.isBlank(extName) ? sha256Hex : sha256Hex + \".\" + extName;\n        }\n        // 情况二：基于 content 计算\n        return sha256Hex + '.' + FileTypeUtil.getType(new ByteArrayInputStream(content));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/util/io/IoUtils.java",
    "content": "package co.yixiang.yshop.framework.common.util.io;\n\nimport cn.hutool.core.io.IORuntimeException;\nimport cn.hutool.core.io.IoUtil;\nimport cn.hutool.core.util.StrUtil;\n\nimport java.io.InputStream;\n\n/**\n * IO 工具类，用于 {@link cn.hutool.core.io.IoUtil} 缺失的方法\n *\n * @author yshop\n */\npublic class IoUtils {\n\n    /**\n     * 从流中读取 UTF8 编码的内容\n     *\n     * @param in 输入流\n     * @param isClose 是否关闭\n     * @return 内容\n     * @throws IORuntimeException IO 异常\n     */\n    public static String readUtf8(InputStream in, boolean isClose) throws IORuntimeException {\n        return StrUtil.utf8Str(IoUtil.read(in, isClose));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/util/json/JsonUtils.java",
    "content": "package co.yixiang.yshop.framework.common.util.json;\n\nimport cn.hutool.core.util.ArrayUtil;\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.json.JSONUtil;\nimport com.fasterxml.jackson.annotation.JsonInclude;\nimport com.fasterxml.jackson.core.type.TypeReference;\nimport com.fasterxml.jackson.databind.DeserializationFeature;\nimport com.fasterxml.jackson.databind.JsonNode;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.SerializationFeature;\nimport com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;\nimport lombok.SneakyThrows;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.io.IOException;\nimport java.lang.reflect.Type;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * JSON 工具类\n *\n * @author yshop\n */\n@Slf4j\npublic class JsonUtils {\n\n    private static ObjectMapper objectMapper = new ObjectMapper();\n\n    static {\n        objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);\n        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);\n        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); // 忽略 null 值\n        objectMapper.registerModules(new JavaTimeModule()); // 解决 LocalDateTime 的序列化\n    }\n\n    /**\n     * 初始化 objectMapper 属性\n     * <p>\n     * 通过这样的方式，使用 Spring 创建的 ObjectMapper Bean\n     *\n     * @param objectMapper ObjectMapper 对象\n     */\n    public static void init(ObjectMapper objectMapper) {\n        JsonUtils.objectMapper = objectMapper;\n    }\n\n    @SneakyThrows\n    public static String toJsonString(Object object) {\n        return objectMapper.writeValueAsString(object);\n    }\n\n    @SneakyThrows\n    public static byte[] toJsonByte(Object object) {\n        return objectMapper.writeValueAsBytes(object);\n    }\n\n    @SneakyThrows\n    public static String toJsonPrettyString(Object object) {\n        return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(object);\n    }\n\n    public static <T> T parseObject(String text, Class<T> clazz) {\n        if (StrUtil.isEmpty(text)) {\n            return null;\n        }\n        try {\n            return objectMapper.readValue(text, clazz);\n        } catch (IOException e) {\n            log.error(\"json parse err,json:{}\", text, e);\n            throw new RuntimeException(e);\n        }\n    }\n\n    public static <T> T parseObject(String text, String path, Class<T> clazz) {\n        if (StrUtil.isEmpty(text)) {\n            return null;\n        }\n        try {\n            JsonNode treeNode = objectMapper.readTree(text);\n            JsonNode pathNode = treeNode.path(path);\n            return objectMapper.readValue(pathNode.toString(), clazz);\n        } catch (IOException e) {\n            log.error(\"json parse err,json:{}\", text, e);\n            throw new RuntimeException(e);\n        }\n    }\n\n    public static <T> T parseObject(String text, Type type) {\n        if (StrUtil.isEmpty(text)) {\n            return null;\n        }\n        try {\n            return objectMapper.readValue(text, objectMapper.getTypeFactory().constructType(type));\n        } catch (IOException e) {\n            log.error(\"json parse err,json:{}\", text, e);\n            throw new RuntimeException(e);\n        }\n    }\n\n    /**\n     * 将字符串解析成指定类型的对象\n     * 使用 {@link #parseObject(String, Class)} 时，在@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS) 的场景下，\n     * 如果 text 没有 class 属性，则会报错。此时，使用这个方法，可以解决。\n     *\n     * @param text 字符串\n     * @param clazz 类型\n     * @return 对象\n     */\n    public static <T> T parseObject2(String text, Class<T> clazz) {\n        if (StrUtil.isEmpty(text)) {\n            return null;\n        }\n        return JSONUtil.toBean(text, clazz);\n    }\n\n    public static <T> T parseObject(byte[] bytes, Class<T> clazz) {\n        if (ArrayUtil.isEmpty(bytes)) {\n            return null;\n        }\n        try {\n            return objectMapper.readValue(bytes, clazz);\n        } catch (IOException e) {\n            log.error(\"json parse err,json:{}\", bytes, e);\n            throw new RuntimeException(e);\n        }\n    }\n\n    public static <T> T parseObject(String text, TypeReference<T> typeReference) {\n        try {\n            return objectMapper.readValue(text, typeReference);\n        } catch (IOException e) {\n            log.error(\"json parse err,json:{}\", text, e);\n            throw new RuntimeException(e);\n        }\n    }\n\n    /**\n     * 解析 JSON 字符串成指定类型的对象，如果解析失败，则返回 null\n     *\n     * @param text 字符串\n     * @param typeReference 类型引用\n     * @return 指定类型的对象\n     */\n    public static <T> T parseObjectQuietly(String text, TypeReference<T> typeReference) {\n        try {\n            return objectMapper.readValue(text, typeReference);\n        } catch (IOException e) {\n            return null;\n        }\n    }\n\n    public static <T> List<T> parseArray(String text, Class<T> clazz) {\n        if (StrUtil.isEmpty(text)) {\n            return new ArrayList<>();\n        }\n        try {\n            return objectMapper.readValue(text, objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));\n        } catch (IOException e) {\n            log.error(\"json parse err,json:{}\", text, e);\n            throw new RuntimeException(e);\n        }\n    }\n\n    public static <T> List<T> parseArray(String text, String path, Class<T> clazz) {\n        if (StrUtil.isEmpty(text)) {\n            return null;\n        }\n        try {\n            JsonNode treeNode = objectMapper.readTree(text);\n            JsonNode pathNode = treeNode.path(path);\n            return objectMapper.readValue(pathNode.toString(), objectMapper.getTypeFactory().constructCollectionType(List.class, clazz));\n        } catch (IOException e) {\n            log.error(\"json parse err,json:{}\", text, e);\n            throw new RuntimeException(e);\n        }\n    }\n\n    public static JsonNode parseTree(String text) {\n        try {\n            return objectMapper.readTree(text);\n        } catch (IOException e) {\n            log.error(\"json parse err,json:{}\", text, e);\n            throw new RuntimeException(e);\n        }\n    }\n\n    public static JsonNode parseTree(byte[] text) {\n        try {\n            return objectMapper.readTree(text);\n        } catch (IOException e) {\n            log.error(\"json parse err,json:{}\", text, e);\n            throw new RuntimeException(e);\n        }\n    }\n\n    public static boolean isJson(String text) {\n        return JSONUtil.isTypeJSON(text);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/util/monitor/TracerUtils.java",
    "content": "package co.yixiang.yshop.framework.common.util.monitor;\n\nimport org.apache.skywalking.apm.toolkit.trace.TraceContext;\n\n/**\n * 链路追踪工具类\n *\n * 考虑到每个 starter 都需要用到该工具类，所以放到 common 模块下的 util 包下\n *\n * @author yshop\n */\npublic class TracerUtils {\n\n    /**\n     * 私有化构造方法\n     */\n    private TracerUtils() {\n    }\n\n    /**\n     * 获得链路追踪编号，直接返回 SkyWalking 的 TraceId。\n     * 如果不存在的话为空字符串！！！\n     *\n     * @return 链路追踪编号\n     */\n    public static String getTraceId() {\n        return TraceContext.traceId();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/util/number/MoneyUtils.java",
    "content": "package co.yixiang.yshop.framework.common.util.number;\n\nimport cn.hutool.core.math.Money;\nimport cn.hutool.core.util.NumberUtil;\n\nimport java.math.BigDecimal;\nimport java.math.RoundingMode;\n\n/**\n * 金额工具类\n *\n * @author yshop\n */\npublic class MoneyUtils {\n\n    /**\n     * 金额的小数位数\n     */\n    private static final int PRICE_SCALE = 2;\n\n    /**\n     * 百分比对应的 BigDecimal 对象\n     */\n    public static final BigDecimal PERCENT_100 = BigDecimal.valueOf(100);\n\n    /**\n     * 计算百分比金额，四舍五入\n     *\n     * @param price 金额\n     * @param rate  百分比，例如说 56.77% 则传入 56.77\n     * @return 百分比金额\n     */\n    public static Integer calculateRatePrice(Integer price, Double rate) {\n        return calculateRatePrice(price, rate, 0, RoundingMode.HALF_UP).intValue();\n    }\n\n    /**\n     * 计算百分比金额，向下传入\n     *\n     * @param price 金额\n     * @param rate  百分比，例如说 56.77% 则传入 56.77\n     * @return 百分比金额\n     */\n    public static Integer calculateRatePriceFloor(Integer price, Double rate) {\n        return calculateRatePrice(price, rate, 0, RoundingMode.FLOOR).intValue();\n    }\n\n    /**\n     * 计算百分比金额\n     *\n     * @param price   金额（单位分）\n     * @param count   数量\n     * @param percent 折扣（单位分），列如 60.2%，则传入 6020\n     * @return 商品总价\n     */\n    public static Integer calculator(Integer price, Integer count, Integer percent) {\n        price = price * count;\n        if (percent == null) {\n            return price;\n        }\n        return MoneyUtils.calculateRatePriceFloor(price, (double) (percent / 100));\n    }\n\n    /**\n     * 计算百分比金额\n     *\n     * @param price        金额\n     * @param rate         百分比，例如说 56.77% 则传入 56.77\n     * @param scale        保留小数位数\n     * @param roundingMode 舍入模式\n     */\n    public static BigDecimal calculateRatePrice(Number price, Number rate, int scale, RoundingMode roundingMode) {\n        return NumberUtil.toBigDecimal(price).multiply(NumberUtil.toBigDecimal(rate)) // 乘以\n                .divide(BigDecimal.valueOf(100), scale, roundingMode); // 除以 100\n    }\n\n    /**\n     * 分转元\n     *\n     * @param fen 分\n     * @return 元\n     */\n    public static BigDecimal fenToYuan(int fen) {\n        return new Money(0, fen).getAmount();\n    }\n\n    /**\n     * 分转元（字符串）\n     *\n     * 例如说 fen 为 1 时，则结果为 0.01\n     *\n     * @param fen 分\n     * @return 元\n     */\n    public static String fenToYuanStr(int fen) {\n        return new Money(0, fen).toString();\n    }\n\n    /**\n     * 金额相乘，默认进行四舍五入\n     *\n     * 位数：{@link #PRICE_SCALE}\n     *\n     * @param price 金额\n     * @param count 数量\n     * @return 金额相乘结果\n     */\n    public static BigDecimal priceMultiply(BigDecimal price, BigDecimal count) {\n        if (price == null || count == null) {\n            return null;\n        }\n        return price.multiply(count).setScale(PRICE_SCALE, RoundingMode.HALF_UP);\n    }\n\n    /**\n     * 金额相乘（百分比），默认进行四舍五入\n     *\n     * 位数：{@link #PRICE_SCALE}\n     *\n     * @param price  金额\n     * @param percent 百分比\n     * @return 金额相乘结果\n     */\n    public static BigDecimal priceMultiplyPercent(BigDecimal price, BigDecimal percent) {\n        if (price == null || percent == null) {\n            return null;\n        }\n        return price.multiply(percent).divide(PERCENT_100, PRICE_SCALE, RoundingMode.HALF_UP);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/util/number/NumberUtils.java",
    "content": "package co.yixiang.yshop.framework.common.util.number;\n\nimport cn.hutool.core.util.NumberUtil;\nimport cn.hutool.core.util.StrUtil;\n\nimport java.math.BigDecimal;\n\n/**\n * 数字的工具类，补全 {@link cn.hutool.core.util.NumberUtil} 的功能\n *\n * @author yshop\n */\npublic class NumberUtils {\n\n    public static Long parseLong(String str) {\n        return StrUtil.isNotEmpty(str) ? Long.valueOf(str) : null;\n    }\n\n    public static Integer parseInt(String str) {\n        return StrUtil.isNotEmpty(str) ? Integer.valueOf(str) : null;\n    }\n\n    /**\n     * 通过经纬度获取地球上两点之间的距离\n     *\n     * 参考 <<a href=\"https://gitee.com/dromara/hutool/blob/1caabb586b1f95aec66a21d039c5695df5e0f4c1/hutool-core/src/main/java/cn/hutool/core/util/DistanceUtil.java\">DistanceUtil</a>> 实现，目前它已经被 hutool 删除\n     *\n     * @param lat1 经度1\n     * @param lng1 纬度1\n     * @param lat2 经度2\n     * @param lng2 纬度2\n     * @return 距离，单位：千米\n     */\n    public static double getDistance(double lat1, double lng1, double lat2, double lng2) {\n        double radLat1 = lat1 * Math.PI / 180.0;\n        double radLat2 = lat2 * Math.PI / 180.0;\n        double a = radLat1 - radLat2;\n        double b = lng1 * Math.PI / 180.0 - lng2 * Math.PI / 180.0;\n        double distance = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2)\n                + Math.cos(radLat1) * Math.cos(radLat2)\n                * Math.pow(Math.sin(b / 2), 2)));\n        distance = distance * 6378.137;\n        distance = Math.round(distance * 10000d) / 10000d;\n        return distance;\n    }\n\n    /**\n     * 提供精确的乘法运算\n     *\n     * 和 hutool {@link NumberUtil#mul(BigDecimal...)} 的差别是，如果存在 null，则返回 null\n     *\n     * @param values 多个被乘值\n     * @return 积\n     */\n    public static BigDecimal mul(BigDecimal... values) {\n        for (BigDecimal value : values) {\n            if (value == null) {\n                return null;\n            }\n        }\n        return NumberUtil.mul(values);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/util/object/BeanUtils.java",
    "content": "package co.yixiang.yshop.framework.common.util.object;\n\nimport cn.hutool.core.bean.BeanUtil;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.collection.CollectionUtils;\n\nimport java.util.List;\nimport java.util.function.Consumer;\n\n/**\n * Bean 工具类\n *\n * 1. 默认使用 {@link cn.hutool.core.bean.BeanUtil} 作为实现类，虽然不同 bean 工具的性能有差别，但是对绝大多数同学的项目，不用在意这点性能\n * 2. 针对复杂的对象转换，可以搜参考 AuthConvert 实现，通过 mapstruct + default 配合实现\n *\n * @author yshop\n */\npublic class BeanUtils {\n\n    public static <T> T toBean(Object source, Class<T> targetClass) {\n        return BeanUtil.toBean(source, targetClass);\n    }\n\n    public static <T> T toBean(Object source, Class<T> targetClass, Consumer<T> peek) {\n        T target = toBean(source, targetClass);\n        if (target != null) {\n            peek.accept(target);\n        }\n        return target;\n    }\n\n    public static <S, T> List<T> toBean(List<S> source, Class<T> targetType) {\n        if (source == null) {\n            return null;\n        }\n        return CollectionUtils.convertList(source, s -> toBean(s, targetType));\n    }\n\n    public static <S, T> List<T> toBean(List<S> source, Class<T> targetType, Consumer<T> peek) {\n        List<T> list = toBean(source, targetType);\n        if (list != null) {\n            list.forEach(peek);\n        }\n        return list;\n    }\n\n    public static <S, T> PageResult<T> toBean(PageResult<S> source, Class<T> targetType) {\n        return toBean(source, targetType, null);\n    }\n\n    public static <S, T> PageResult<T> toBean(PageResult<S> source, Class<T> targetType, Consumer<T> peek) {\n        if (source == null) {\n            return null;\n        }\n        List<T> list = toBean(source.getList(), targetType);\n        if (peek != null) {\n            list.forEach(peek);\n        }\n        return new PageResult<>(list, source.getTotal());\n    }\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/util/object/ObjectUtils.java",
    "content": "package co.yixiang.yshop.framework.common.util.object;\n\nimport cn.hutool.core.util.ObjectUtil;\nimport cn.hutool.core.util.ReflectUtil;\n\nimport java.lang.reflect.Field;\nimport java.util.Arrays;\nimport java.util.function.Consumer;\n\n/**\n * Object 工具类\n *\n * @author yshop\n */\npublic class ObjectUtils {\n\n    /**\n     * 复制对象，并忽略 Id 编号\n     *\n     * @param object 被复制对象\n     * @param consumer 消费者，可以二次编辑被复制对象\n     * @return 复制后的对象\n     */\n    public static <T> T cloneIgnoreId(T object, Consumer<T> consumer) {\n        T result = ObjectUtil.clone(object);\n        // 忽略 id 编号\n        Field field = ReflectUtil.getField(object.getClass(), \"id\");\n        if (field != null) {\n            ReflectUtil.setFieldValue(result, field, null);\n        }\n        // 二次编辑\n        if (result != null) {\n            consumer.accept(result);\n        }\n        return result;\n    }\n\n    public static <T extends Comparable<T>> T max(T obj1, T obj2) {\n        if (obj1 == null) {\n            return obj2;\n        }\n        if (obj2 == null) {\n            return obj1;\n        }\n        return obj1.compareTo(obj2) > 0 ? obj1 : obj2;\n    }\n\n    @SafeVarargs\n    public static <T> T defaultIfNull(T... array) {\n        for (T item : array) {\n            if (item != null) {\n                return item;\n            }\n        }\n        return null;\n    }\n\n    @SafeVarargs\n    public static <T> boolean equalsAny(T obj, T... array) {\n        return Arrays.asList(array).contains(obj);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/util/object/PageUtils.java",
    "content": "package co.yixiang.yshop.framework.common.util.object;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.lang.func.Func1;\nimport cn.hutool.core.lang.func.LambdaUtil;\nimport cn.hutool.core.util.ArrayUtil;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport co.yixiang.yshop.framework.common.pojo.SortablePageParam;\nimport co.yixiang.yshop.framework.common.pojo.SortingField;\nimport org.springframework.util.Assert;\n\nimport static java.util.Collections.singletonList;\n\n/**\n * {@link co.yixiang.yshop.framework.common.pojo.PageParam} 工具类\n *\n * @author yshop\n */\npublic class PageUtils {\n\n    private static final Object[] ORDER_TYPES = new String[]{SortingField.ORDER_ASC, SortingField.ORDER_DESC};\n\n    public static int getStart(PageParam pageParam) {\n        return (pageParam.getPageNo() - 1) * pageParam.getPageSize();\n    }\n\n    /**\n     * 构建排序字段（默认倒序）\n     *\n     * @param func 排序字段的 Lambda 表达式\n     * @param <T>  排序字段所属的类型\n     * @return 排序字段\n     */\n    public static <T> SortingField buildSortingField(Func1<T, ?> func) {\n        return buildSortingField(func, SortingField.ORDER_DESC);\n    }\n\n    /**\n     * 构建排序字段\n     *\n     * @param func  排序字段的 Lambda 表达式\n     * @param order 排序类型 {@link SortingField#ORDER_ASC} {@link SortingField#ORDER_DESC}\n     * @param <T>   排序字段所属的类型\n     * @return 排序字段\n     */\n    public static <T> SortingField buildSortingField(Func1<T, ?> func, String order) {\n        Assert.isTrue(ArrayUtil.contains(ORDER_TYPES, order), String.format(\"字段的排序类型只能是 %s/%s\", ORDER_TYPES));\n\n        String fieldName = LambdaUtil.getFieldName(func);\n        return new SortingField(fieldName, order);\n    }\n\n    /**\n     * 构建默认的排序字段\n     * 如果排序字段为空，则设置排序字段；否则忽略\n     *\n     * @param sortablePageParam 排序分页查询参数\n     * @param func              排序字段的 Lambda 表达式\n     * @param <T>               排序字段所属的类型\n     */\n    public static <T> void buildDefaultSortingField(SortablePageParam sortablePageParam, Func1<T, ?> func) {\n        if (sortablePageParam != null && CollUtil.isEmpty(sortablePageParam.getSortingFields())) {\n            sortablePageParam.setSortingFields(singletonList(buildSortingField(func)));\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/util/servlet/ServletUtils.java",
    "content": "package co.yixiang.yshop.framework.common.util.servlet;\n\nimport cn.hutool.core.io.IoUtil;\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.extra.servlet.JakartaServletUtil;\nimport co.yixiang.yshop.framework.common.util.json.JsonUtils;\nimport jakarta.servlet.ServletRequest;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport org.springframework.http.MediaType;\nimport org.springframework.web.context.request.RequestAttributes;\nimport org.springframework.web.context.request.RequestContextHolder;\nimport org.springframework.web.context.request.ServletRequestAttributes;\n\nimport java.io.IOException;\nimport java.net.URLEncoder;\nimport java.util.Map;\n\n/**\n * 客户端工具类\n *\n * @author yshop\n */\npublic class ServletUtils {\n\n    /**\n     * 返回 JSON 字符串\n     *\n     * @param response 响应\n     * @param object   对象，会序列化成 JSON 字符串\n     */\n    @SuppressWarnings(\"deprecation\") // 必须使用 APPLICATION_JSON_UTF8_VALUE，否则会乱码\n    public static void writeJSON(HttpServletResponse response, Object object) {\n        String content = JsonUtils.toJsonString(object);\n        JakartaServletUtil.write(response, content, MediaType.APPLICATION_JSON_UTF8_VALUE);\n    }\n\n    /**\n     * @param request 请求\n     * @return ua\n     */\n    public static String getUserAgent(HttpServletRequest request) {\n        String ua = request.getHeader(\"User-Agent\");\n        return ua != null ? ua : \"\";\n    }\n\n    /**\n     * 获得请求\n     *\n     * @return HttpServletRequest\n     */\n    public static HttpServletRequest getRequest() {\n        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();\n        if (!(requestAttributes instanceof ServletRequestAttributes)) {\n            return null;\n        }\n        return ((ServletRequestAttributes) requestAttributes).getRequest();\n    }\n\n    public static String getUserAgent() {\n        HttpServletRequest request = getRequest();\n        if (request == null) {\n            return null;\n        }\n        return getUserAgent(request);\n    }\n\n    public static String getClientIP() {\n        HttpServletRequest request = getRequest();\n        if (request == null) {\n            return null;\n        }\n        return JakartaServletUtil.getClientIP(request);\n    }\n\n    public static boolean isJsonRequest(ServletRequest request) {\n        return StrUtil.startWithIgnoreCase(request.getContentType(), MediaType.APPLICATION_JSON_VALUE);\n    }\n\n    public static String getBody(HttpServletRequest request) {\n        // 只有在 json 请求在读取，因为只有 CacheRequestBodyFilter 才会进行缓存，支持重复读取\n        if (isJsonRequest(request)) {\n            return JakartaServletUtil.getBody(request);\n        }\n        return null;\n    }\n\n    public static byte[] getBodyBytes(HttpServletRequest request) {\n        // 只有在 json 请求在读取，因为只有 CacheRequestBodyFilter 才会进行缓存，支持重复读取\n        if (isJsonRequest(request)) {\n            return JakartaServletUtil.getBodyBytes(request);\n        }\n        return null;\n    }\n\n    public static String getClientIP(HttpServletRequest request) {\n        return JakartaServletUtil.getClientIP(request);\n    }\n\n    public static Map<String, String> getParamMap(HttpServletRequest request) {\n        return JakartaServletUtil.getParamMap(request);\n    }\n\n    public static String getRequstHost(HttpServletRequest request){\n        String port =  \":\" + request.getServerPort();\n        if(request.getServerPort() == 80 || request.getServerPort() == 443){\n            port = \"\";\n        }\n        return request.getScheme()+\"://\" + request.getServerName() + port;\n    }\n\n    /**\n     * 返回附件\n     *\n     * @param response 响应\n     * @param filename 文件名\n     * @param content 附件内容\n     */\n    public static void writeAttachment(HttpServletResponse response, String filename, byte[] content) throws IOException {\n        // 设置 header 和 contentType\n        response.setHeader(\"Content-Disposition\", \"attachment;filename=\" + URLEncoder.encode(filename, \"UTF-8\"));\n        response.setContentType(MediaType.APPLICATION_OCTET_STREAM_VALUE);\n        // 输出附件\n        IoUtil.write(response.getOutputStream(), false, content);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/util/spring/SpringExpressionUtils.java",
    "content": "package co.yixiang.yshop.framework.common.util.spring;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.map.MapUtil;\nimport cn.hutool.core.util.ArrayUtil;\nimport org.aspectj.lang.JoinPoint;\nimport org.aspectj.lang.reflect.MethodSignature;\nimport org.springframework.core.DefaultParameterNameDiscoverer;\nimport org.springframework.core.ParameterNameDiscoverer;\nimport org.springframework.expression.EvaluationContext;\nimport org.springframework.expression.ExpressionParser;\nimport org.springframework.expression.spel.standard.SpelExpressionParser;\nimport org.springframework.expression.spel.support.StandardEvaluationContext;\n\nimport java.lang.reflect.Method;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Spring EL 表达式的工具类\n *\n * @author mashu\n */\npublic class SpringExpressionUtils {\n\n    /**\n     * Spring EL 表达式解析器\n     */\n    private static final ExpressionParser EXPRESSION_PARSER = new SpelExpressionParser();\n    /**\n     * 参数名发现器\n     */\n    private static final ParameterNameDiscoverer PARAMETER_NAME_DISCOVERER = new DefaultParameterNameDiscoverer();\n\n    private SpringExpressionUtils() {\n    }\n\n    /**\n     * 从切面中，单个解析 EL 表达式的结果\n     *\n     * @param joinPoint        切面点\n     * @param expressionString EL 表达式数组\n     * @return 执行界面\n     */\n    public static Object parseExpression(JoinPoint joinPoint, String expressionString) {\n        Map<String, Object> result = parseExpressions(joinPoint, Collections.singletonList(expressionString));\n        return result.get(expressionString);\n    }\n\n    /**\n     * 从切面中，批量解析 EL 表达式的结果\n     *\n     * @param joinPoint         切面点\n     * @param expressionStrings EL 表达式数组\n     * @return 结果，key 为表达式，value 为对应值\n     */\n    public static Map<String, Object> parseExpressions(JoinPoint joinPoint, List<String> expressionStrings) {\n        // 如果为空，则不进行解析\n        if (CollUtil.isEmpty(expressionStrings)) {\n            return MapUtil.newHashMap();\n        }\n\n        // 第一步，构建解析的上下文 EvaluationContext\n        // 通过 joinPoint 获取被注解方法\n        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();\n        Method method = methodSignature.getMethod();\n        // 使用 spring 的 ParameterNameDiscoverer 获取方法形参名数组\n        String[] paramNames = PARAMETER_NAME_DISCOVERER.getParameterNames(method);\n        // Spring 的表达式上下文对象\n        EvaluationContext context = new StandardEvaluationContext();\n        // 给上下文赋值\n        if (ArrayUtil.isNotEmpty(paramNames)) {\n            Object[] args = joinPoint.getArgs();\n            for (int i = 0; i < paramNames.length; i++) {\n                context.setVariable(paramNames[i], args[i]);\n            }\n        }\n\n        // 第二步，逐个参数解析\n        Map<String, Object> result = MapUtil.newHashMap(expressionStrings.size(), true);\n        expressionStrings.forEach(key -> {\n            Object value = EXPRESSION_PARSER.parseExpression(key).getValue(context);\n            result.put(key, value);\n        });\n        return result;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/util/spring/SpringUtils.java",
    "content": "package co.yixiang.yshop.framework.common.util.spring;\n\nimport cn.hutool.extra.spring.SpringUtil;\n\nimport java.util.Objects;\n\n/**\n * Spring 工具类\n *\n * @author yshop\n */\npublic class SpringUtils extends SpringUtil {\n\n    /**\n     * 是否为生产环境\n     *\n     * @return 是否生产环境\n     */\n    public static boolean isProd() {\n        String activeProfile = getActiveProfile();\n        return Objects.equals(\"prod\", activeProfile);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/util/string/StrUtils.java",
    "content": "package co.yixiang.yshop.framework.common.util.string;\n\nimport cn.hutool.core.text.StrPool;\nimport cn.hutool.core.util.ArrayUtil;\nimport cn.hutool.core.util.StrUtil;\nimport org.springframework.util.CollectionUtils;\n\nimport java.text.Collator;\nimport java.util.*;\nimport java.util.stream.Collectors;\n\n/**\n * 字符串工具类\n *\n * @author yshop\n */\npublic class StrUtils {\n\n    public static String maxLength(CharSequence str, int maxLength) {\n        return StrUtil.maxLength(str, maxLength - 3); // -3 的原因，是该方法会补充 ... 恰好\n    }\n\n    /**\n     * 给定字符串是否以任何一个字符串开始\n     * 给定字符串和数组为空都返回 false\n     *\n     * @param str      给定字符串\n     * @param prefixes 需要检测的开始字符串\n     * @since 3.0.6\n     */\n    public static boolean startWithAny(String str, Collection<String> prefixes) {\n        if (StrUtil.isEmpty(str) || ArrayUtil.isEmpty(prefixes)) {\n            return false;\n        }\n\n        for (CharSequence suffix : prefixes) {\n            if (StrUtil.startWith(str, suffix, false)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    public static List<Long> splitToLong(String value, CharSequence separator) {\n        long[] longs = StrUtil.splitToLong(value, separator);\n        return Arrays.stream(longs).boxed().collect(Collectors.toList());\n    }\n\n    public static Set<Long> splitToLongSet(String value) {\n        return splitToLongSet(value, StrPool.COMMA);\n    }\n\n    public static Set<Long> splitToLongSet(String value, CharSequence separator) {\n        long[] longs = StrUtil.splitToLong(value, separator);\n        return Arrays.stream(longs).boxed().collect(Collectors.toSet());\n    }\n\n    public static List<Integer> splitToInteger(String value, CharSequence separator) {\n        int[] integers = StrUtil.splitToInt(value, separator);\n        return Arrays.stream(integers).boxed().collect(Collectors.toList());\n    }\n\n    /**\n     * 移除字符串中，包含指定字符串的行\n     *\n     * @param content 字符串\n     * @param sequence 包含的字符串\n     * @return 移除后的字符串\n     */\n    public static String removeLineContains(String content, String sequence) {\n        if (StrUtil.isEmpty(content) || StrUtil.isEmpty(sequence)) {\n            return content;\n        }\n        return Arrays.stream(content.split(\"\\n\"))\n                .filter(line -> !line.contains(sequence))\n                .collect(Collectors.joining(\"\\n\"));\n    }\n\n    /**\n     * 数字排在最前，英文字母其次，汉字则按照拼音进行排序\n     */\n    public static List<String> compareTo(List<String> stringList) {\n        if (CollectionUtils.isEmpty(stringList)) {\n            return Collections.emptyList();\n        }\n        Comparator<String> comparator = (text, texts) -> {\n            Collator collator = Collator.getInstance(java.util.Locale.CHINESE);\n            return collator.getCollationKey(text).compareTo(\n                    collator.getCollationKey(texts));\n        };\n        Collections.sort(stringList, comparator);\n        return stringList;\n\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/util/validation/ValidationUtils.java",
    "content": "package co.yixiang.yshop.framework.common.util.validation;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.lang.Assert;\nimport org.springframework.util.StringUtils;\n\nimport jakarta.validation.ConstraintViolation;\nimport jakarta.validation.ConstraintViolationException;\nimport jakarta.validation.Validation;\nimport jakarta.validation.Validator;\nimport java.util.Set;\nimport java.util.regex.Pattern;\n\n/**\n * 校验工具类\n *\n * @author yshop\n */\npublic class ValidationUtils {\n\n    private static final Pattern PATTERN_MOBILE = Pattern.compile(\"^(?:(?:\\\\+|00)86)?1(?:(?:3[\\\\d])|(?:4[0,1,4-9])|(?:5[0-3,5-9])|(?:6[2,5-7])|(?:7[0-8])|(?:8[\\\\d])|(?:9[0-3,5-9]))\\\\d{8}$\");\n\n    private static final Pattern PATTERN_URL = Pattern.compile(\"^(https?|ftp|file)://[-a-zA-Z0-9+&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]\");\n\n    private static final Pattern PATTERN_XML_NCNAME = Pattern.compile(\"[a-zA-Z_][\\\\-_.0-9_a-zA-Z$]*\");\n\n    public static boolean isMobile(String mobile) {\n        return StringUtils.hasText(mobile)\n                && PATTERN_MOBILE.matcher(mobile).matches();\n    }\n\n    public static boolean isURL(String url) {\n        return StringUtils.hasText(url)\n                && PATTERN_URL.matcher(url).matches();\n    }\n\n    public static boolean isXmlNCName(String str) {\n        return StringUtils.hasText(str)\n                && PATTERN_XML_NCNAME.matcher(str).matches();\n    }\n\n    public static void validate(Object object, Class<?>... groups) {\n        Validator validator = Validation.buildDefaultValidatorFactory().getValidator();\n        Assert.notNull(validator);\n        validate(validator, object, groups);\n    }\n\n    public static void validate(Validator validator, Object object, Class<?>... groups) {\n        Set<ConstraintViolation<Object>> constraintViolations = validator.validate(object, groups);\n        if (CollUtil.isNotEmpty(constraintViolations)) {\n            throw new ConstraintViolationException(constraintViolations);\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/util/yshop/LocationUtils.java",
    "content": "package co.yixiang.yshop.framework.common.util.yshop;\n\nimport cn.hutool.core.util.NumberUtil;\n\n\n/**\n * 定位工具\n * @author hupeng\n */\npublic class LocationUtils {\n\n    private static double EARTH_RADIUS = 6378.137;\n\n    private static double rad(double d) {\n        return d * Math.PI / 180.0;\n    }\n\n\n\n    /**\n     * 通过经纬度获取距离(单位：千米)\n     *\n     * @param lat1\n     * @param lng1\n     * @param lat2\n     * @param lng2\n     * @return\n     */\n    public static double getDistance(double lat1, double lng1, double lat2,\n                                     double lng2) {\n        double radLat1 = rad(lat1);\n        double radLat2 = rad(lat2);\n        double a = radLat1 - radLat2;\n        double b = rad(lng1) - rad(lng2);\n        double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a / 2), 2)\n                + Math.cos(radLat1) * Math.cos(radLat2)\n                * Math.pow(Math.sin(b / 2), 2)));\n        s = s * EARTH_RADIUS;\n        s = Math.round(s * 10000d) / 10000d;\n        //s = s*1000;\n        return NumberUtil.round(s, 2).doubleValue();\n        //return s;\n    }\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/validation/InEnum.java",
    "content": "package co.yixiang.yshop.framework.common.validation;\n\nimport co.yixiang.yshop.framework.common.core.IntArrayValuable;\n\nimport jakarta.validation.Constraint;\nimport jakarta.validation.Payload;\nimport java.lang.annotation.*;\n\n@Target({\n        ElementType.METHOD,\n        ElementType.FIELD,\n        ElementType.ANNOTATION_TYPE,\n        ElementType.CONSTRUCTOR,\n        ElementType.PARAMETER,\n        ElementType.TYPE_USE\n})\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\n@Constraint(\n        validatedBy = {InEnumValidator.class, InEnumCollectionValidator.class}\n)\npublic @interface InEnum {\n\n    /**\n     * @return 实现 EnumValuable 接口的\n     */\n    Class<? extends IntArrayValuable> value();\n\n    String message() default \"必须在指定范围 {value}\";\n\n    Class<?>[] groups() default {};\n\n    Class<? extends Payload>[] payload() default {};\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/validation/InEnumCollectionValidator.java",
    "content": "package co.yixiang.yshop.framework.common.validation;\n\nimport cn.hutool.core.collection.CollUtil;\nimport co.yixiang.yshop.framework.common.core.IntArrayValuable;\n\nimport jakarta.validation.ConstraintValidator;\nimport jakarta.validation.ConstraintValidatorContext;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\npublic class InEnumCollectionValidator implements ConstraintValidator<InEnum, Collection<Integer>> {\n\n    private List<Integer> values;\n\n    @Override\n    public void initialize(InEnum annotation) {\n        IntArrayValuable[] values = annotation.value().getEnumConstants();\n        if (values.length == 0) {\n            this.values = Collections.emptyList();\n        } else {\n            this.values = Arrays.stream(values[0].array()).boxed().collect(Collectors.toList());\n        }\n    }\n\n    @Override\n    public boolean isValid(Collection<Integer> list, ConstraintValidatorContext context) {\n        // 校验通过\n        if (CollUtil.containsAll(values, list)) {\n            return true;\n        }\n        // 校验不通过，自定义提示语句（因为，注解上的 value 是枚举类，无法获得枚举类的实际值）\n        context.disableDefaultConstraintViolation(); // 禁用默认的 message 的值\n        context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()\n                .replaceAll(\"\\\\{value}\", CollUtil.join(list, \",\"))).addConstraintViolation(); // 重新添加错误提示语句\n        return false;\n    }\n\n}\n\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/validation/InEnumValidator.java",
    "content": "package co.yixiang.yshop.framework.common.validation;\n\nimport co.yixiang.yshop.framework.common.core.IntArrayValuable;\n\nimport jakarta.validation.ConstraintValidator;\nimport jakarta.validation.ConstraintValidatorContext;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\npublic class InEnumValidator implements ConstraintValidator<InEnum, Integer> {\n\n    private List<Integer> values;\n\n    @Override\n    public void initialize(InEnum annotation) {\n        IntArrayValuable[] values = annotation.value().getEnumConstants();\n        if (values.length == 0) {\n            this.values = Collections.emptyList();\n        } else {\n            this.values = Arrays.stream(values[0].array()).boxed().collect(Collectors.toList());\n        }\n    }\n\n    @Override\n    public boolean isValid(Integer value, ConstraintValidatorContext context) {\n        // 为空时，默认不校验，即认为通过\n        if (value == null) {\n            return true;\n        }\n        // 校验通过\n        if (values.contains(value)) {\n            return true;\n        }\n        // 校验不通过，自定义提示语句（因为，注解上的 value 是枚举类，无法获得枚举类的实际值）\n        context.disableDefaultConstraintViolation(); // 禁用默认的 message 的值\n        context.buildConstraintViolationWithTemplate(context.getDefaultConstraintMessageTemplate()\n                .replaceAll(\"\\\\{value}\", values.toString())).addConstraintViolation(); // 重新添加错误提示语句\n        return false;\n    }\n\n}\n\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/validation/Mobile.java",
    "content": "package co.yixiang.yshop.framework.common.validation;\n\nimport jakarta.validation.Constraint;\nimport jakarta.validation.Payload;\nimport java.lang.annotation.*;\n\n@Target({\n        ElementType.METHOD,\n        ElementType.FIELD,\n        ElementType.ANNOTATION_TYPE,\n        ElementType.CONSTRUCTOR,\n        ElementType.PARAMETER,\n        ElementType.TYPE_USE\n})\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\n@Constraint(\n        validatedBy = MobileValidator.class\n)\npublic @interface Mobile {\n\n    String message() default \"手机号格式不正确\";\n\n    Class<?>[] groups() default {};\n\n    Class<? extends Payload>[] payload() default {};\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/validation/MobileValidator.java",
    "content": "package co.yixiang.yshop.framework.common.validation;\n\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.util.validation.ValidationUtils;\n\nimport jakarta.validation.ConstraintValidator;\nimport jakarta.validation.ConstraintValidatorContext;\n\npublic class MobileValidator implements ConstraintValidator<Mobile, String> {\n\n    @Override\n    public void initialize(Mobile annotation) {\n    }\n\n    @Override\n    public boolean isValid(String value, ConstraintValidatorContext context) {\n        // 如果手机号为空，默认不校验，即校验通过\n        if (StrUtil.isEmpty(value)) {\n            return true;\n        }\n        // 校验手机\n        return ValidationUtils.isMobile(value);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/validation/Telephone.java",
    "content": "package co.yixiang.yshop.framework.common.validation;\n\nimport jakarta.validation.Constraint;\nimport jakarta.validation.Payload;\nimport java.lang.annotation.*;\n\n@Target({\n        ElementType.METHOD,\n        ElementType.FIELD,\n        ElementType.ANNOTATION_TYPE,\n        ElementType.CONSTRUCTOR,\n        ElementType.PARAMETER,\n        ElementType.TYPE_USE\n})\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\n@Constraint(\n        validatedBy = TelephoneValidator.class\n)\npublic @interface Telephone {\n\n    String message() default \"电话格式不正确\";\n\n    Class<?>[] groups() default {};\n\n    Class<? extends Payload>[] payload() default {};\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/validation/TelephoneValidator.java",
    "content": "package co.yixiang.yshop.framework.common.validation;\n\nimport cn.hutool.core.text.CharSequenceUtil;\nimport cn.hutool.core.util.PhoneUtil;\n\nimport jakarta.validation.ConstraintValidator;\nimport jakarta.validation.ConstraintValidatorContext;\n\npublic class TelephoneValidator implements ConstraintValidator<Telephone, String> {\n\n    @Override\n    public void initialize(Telephone annotation) {\n    }\n\n    @Override\n    public boolean isValid(String value, ConstraintValidatorContext context) {\n        // 如果手机号为空，默认不校验，即校验通过\n        if (CharSequenceUtil.isEmpty(value)) {\n            return true;\n        }\n        // 校验手机\n        return PhoneUtil.isTel(value) || PhoneUtil.isPhone(value);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/main/java/co/yixiang/yshop/framework/common/validation/package-info.java",
    "content": "/**\n * 使用 Hibernate Validator 实现参数校验\n */\npackage co.yixiang.yshop.framework.common.validation;\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-common/src/test/java/co/yixiang/yshop/framework/common/util/collection/CollectionUtilsTest.java",
    "content": "package co.yixiang.yshop.framework.common.util.collection;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport org.junit.jupiter.api.Test;\n\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.function.BiFunction;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n/**\n * {@link CollectionUtils} 的单元测试\n */\npublic class CollectionUtilsTest {\n\n    @Data\n    @AllArgsConstructor\n    private static class Dog {\n\n        private Integer id;\n        private String name;\n        private String code;\n\n    }\n\n    @Test\n    public void testDiffList() {\n        // 准备参数\n        Collection<Dog> oldList = Arrays.asList(\n                new Dog(1, \"花花\", \"hh\"),\n                new Dog(2, \"旺财\", \"wc\")\n        );\n        Collection<Dog> newList = Arrays.asList(\n                new Dog(null, \"花花2\", \"hh\"),\n                new Dog(null, \"小白\", \"xb\")\n        );\n        BiFunction<Dog, Dog, Boolean> sameFunc = (oldObj, newObj) -> {\n            boolean same = oldObj.getCode().equals(newObj.getCode());\n            // 如果相等的情况下，需要设置下 id，后续好更新\n            if (same) {\n                newObj.setId(oldObj.getId());\n            }\n            return same;\n        };\n\n        // 调用\n        List<List<Dog>> result = CollectionUtils.diffList(oldList, newList, sameFunc);\n        // 断言\n        assertEquals(result.size(), 3);\n        // 断言 create\n        assertEquals(result.get(0).size(), 1);\n        assertEquals(result.get(0).get(0), new Dog(null, \"小白\", \"xb\"));\n        // 断言 update\n        assertEquals(result.get(1).size(), 1);\n        assertEquals(result.get(1).get(0), new Dog(1, \"花花2\", \"hh\"));\n        // 断言 delete\n        assertEquals(result.get(2).size(), 1);\n        assertEquals(result.get(2).get(0), new Dog(2, \"旺财\", \"wc\"));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-data-permission/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>yshop-framework</artifactId>\n        <groupId>co.yixiang.boot</groupId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>yshop-spring-boot-starter-biz-data-permission</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>数据权限</description>\n    <url>https://gitee.com/guchengwuyue/yshop-drink</url>\n\n    <dependencies>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-common</artifactId>\n        </dependency>\n\n        <!-- Web 相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-security</artifactId>\n            <optional>true</optional> <!-- 可选，如果使用 DeptDataPermissionRule 必须提供 -->\n        </dependency>\n\n        <!-- DB 相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-mybatis</artifactId>\n        </dependency>\n\n        <!-- 业务组件 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-system-api</artifactId> <!-- 需要使用它，进行数据权限的获取 -->\n            <version>${revision}</version>\n        </dependency>\n\n        <!-- Test 测试相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-data-permission/src/main/java/co/yixiang/yshop/framework/datapermission/config/YshopDataPermissionAutoConfiguration.java",
    "content": "package co.yixiang.yshop.framework.datapermission.config;\n\nimport co.yixiang.yshop.framework.datapermission.core.aop.DataPermissionAnnotationAdvisor;\nimport co.yixiang.yshop.framework.datapermission.core.db.DataPermissionDatabaseInterceptor;\nimport co.yixiang.yshop.framework.datapermission.core.rule.DataPermissionRule;\nimport co.yixiang.yshop.framework.datapermission.core.rule.DataPermissionRuleFactory;\nimport co.yixiang.yshop.framework.datapermission.core.rule.DataPermissionRuleFactoryImpl;\nimport co.yixiang.yshop.framework.mybatis.core.util.MyBatisUtils;\nimport com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;\nimport org.springframework.boot.autoconfigure.AutoConfiguration;\nimport org.springframework.context.annotation.Bean;\n\nimport java.util.List;\n\n/**\n * 数据权限的自动配置类\n *\n * @author yshop\n */\n@AutoConfiguration\npublic class YshopDataPermissionAutoConfiguration {\n\n    @Bean\n    public DataPermissionRuleFactory dataPermissionRuleFactory(List<DataPermissionRule> rules) {\n        return new DataPermissionRuleFactoryImpl(rules);\n    }\n\n    @Bean\n    public DataPermissionDatabaseInterceptor dataPermissionDatabaseInterceptor(MybatisPlusInterceptor interceptor,\n                                                                               DataPermissionRuleFactory ruleFactory) {\n        // 创建 DataPermissionDatabaseInterceptor 拦截器\n        DataPermissionDatabaseInterceptor inner = new DataPermissionDatabaseInterceptor(ruleFactory);\n        // 添加到 interceptor 中\n        // 需要加在首个，主要是为了在分页插件前面。这个是 MyBatis Plus 的规定\n        MyBatisUtils.addInterceptor(interceptor, inner, 0);\n        return inner;\n    }\n\n    @Bean\n    public DataPermissionAnnotationAdvisor dataPermissionAnnotationAdvisor() {\n        return new DataPermissionAnnotationAdvisor();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-data-permission/src/main/java/co/yixiang/yshop/framework/datapermission/config/YshopDeptDataPermissionAutoConfiguration.java",
    "content": "package co.yixiang.yshop.framework.datapermission.config;\n\nimport co.yixiang.yshop.framework.datapermission.core.rule.dept.DeptDataPermissionRule;\nimport co.yixiang.yshop.framework.datapermission.core.rule.dept.DeptDataPermissionRuleCustomizer;\nimport co.yixiang.yshop.framework.security.core.LoginUser;\nimport co.yixiang.yshop.module.system.api.permission.PermissionApi;\nimport org.springframework.boot.autoconfigure.AutoConfiguration;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnBean;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnClass;\nimport org.springframework.context.annotation.Bean;\n\nimport java.util.List;\n\n/**\n * 基于部门的数据权限 AutoConfiguration\n *\n * @author yshop\n */\n@AutoConfiguration\n@ConditionalOnClass(LoginUser.class)\n@ConditionalOnBean(value = {PermissionApi.class, DeptDataPermissionRuleCustomizer.class})\npublic class YshopDeptDataPermissionAutoConfiguration {\n\n    @Bean\n    public DeptDataPermissionRule deptDataPermissionRule(PermissionApi permissionApi,\n                                                         List<DeptDataPermissionRuleCustomizer> customizers) {\n        // 创建 DeptDataPermissionRule 对象\n        DeptDataPermissionRule rule = new DeptDataPermissionRule(permissionApi);\n        // 补全表配置\n        customizers.forEach(customizer -> customizer.customize(rule));\n        return rule;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-data-permission/src/main/java/co/yixiang/yshop/framework/datapermission/core/annotation/DataPermission.java",
    "content": "package co.yixiang.yshop.framework.datapermission.core.annotation;\n\nimport co.yixiang.yshop.framework.datapermission.core.rule.DataPermissionRule;\n\nimport java.lang.annotation.*;\n\n/**\n * 数据权限注解\n * 可声明在类或者方法上，标识使用的数据权限规则\n *\n * @author yshop\n */\n@Target({ElementType.TYPE, ElementType.METHOD})\n@Retention(RetentionPolicy.RUNTIME)\n@Documented\npublic @interface DataPermission {\n\n    /**\n     * 当前类或方法是否开启数据权限\n     * 即使不添加 @DataPermission 注解，默认是开启状态\n     * 可通过设置 enable 为 false 禁用\n     */\n    boolean enable() default true;\n\n    /**\n     * 生效的数据权限规则数组，优先级高于 {@link #excludeRules()}\n     */\n    Class<? extends DataPermissionRule>[] includeRules() default {};\n\n    /**\n     * 排除的数据权限规则数组，优先级最低\n     */\n    Class<? extends DataPermissionRule>[] excludeRules() default {};\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-data-permission/src/main/java/co/yixiang/yshop/framework/datapermission/core/aop/DataPermissionAnnotationAdvisor.java",
    "content": "package co.yixiang.yshop.framework.datapermission.core.aop;\n\nimport co.yixiang.yshop.framework.datapermission.core.annotation.DataPermission;\nimport lombok.EqualsAndHashCode;\nimport lombok.Getter;\nimport org.aopalliance.aop.Advice;\nimport org.springframework.aop.Pointcut;\nimport org.springframework.aop.support.AbstractPointcutAdvisor;\nimport org.springframework.aop.support.ComposablePointcut;\nimport org.springframework.aop.support.annotation.AnnotationMatchingPointcut;\n\n/**\n * {@link co.yixiang.yshop.framework.datapermission.core.annotation.DataPermission} 注解的 Advisor 实现类\n *\n * @author yshop\n */\n@Getter\n@EqualsAndHashCode(callSuper = true)\npublic class DataPermissionAnnotationAdvisor extends AbstractPointcutAdvisor {\n\n    private final Advice advice;\n\n    private final Pointcut pointcut;\n\n    public DataPermissionAnnotationAdvisor() {\n        this.advice = new DataPermissionAnnotationInterceptor();\n        this.pointcut = this.buildPointcut();\n    }\n\n    protected Pointcut buildPointcut() {\n        Pointcut classPointcut = new AnnotationMatchingPointcut(DataPermission.class, true);\n        Pointcut methodPointcut = new AnnotationMatchingPointcut(null, DataPermission.class, true);\n        return new ComposablePointcut(classPointcut).union(methodPointcut);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-data-permission/src/main/java/co/yixiang/yshop/framework/datapermission/core/aop/DataPermissionAnnotationInterceptor.java",
    "content": "package co.yixiang.yshop.framework.datapermission.core.aop;\n\nimport co.yixiang.yshop.framework.datapermission.core.annotation.DataPermission;\nimport lombok.Getter;\nimport org.aopalliance.intercept.MethodInterceptor;\nimport org.aopalliance.intercept.MethodInvocation;\nimport org.springframework.core.MethodClassKey;\nimport org.springframework.core.annotation.AnnotationUtils;\n\nimport java.lang.reflect.Method;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * {@link DataPermission} 注解的拦截器\n * 1. 在执行方法前，将 @DataPermission 注解入栈\n * 2. 在执行方法后，将 @DataPermission 注解出栈\n *\n * @author yshop\n */\n@DataPermission // 该注解，用于 {@link DATA_PERMISSION_NULL} 的空对象\npublic class DataPermissionAnnotationInterceptor implements MethodInterceptor {\n\n    /**\n     * DataPermission 空对象，用于方法无 {@link DataPermission} 注解时，使用 DATA_PERMISSION_NULL 进行占位\n     */\n    static final DataPermission DATA_PERMISSION_NULL = DataPermissionAnnotationInterceptor.class.getAnnotation(DataPermission.class);\n\n    @Getter\n    private final Map<MethodClassKey, DataPermission> dataPermissionCache = new ConcurrentHashMap<>();\n\n    @Override\n    public Object invoke(MethodInvocation methodInvocation) throws Throwable {\n        // 入栈\n        DataPermission dataPermission = this.findAnnotation(methodInvocation);\n        if (dataPermission != null) {\n            DataPermissionContextHolder.add(dataPermission);\n        }\n        try {\n            // 执行逻辑\n            return methodInvocation.proceed();\n        } finally {\n            // 出栈\n            if (dataPermission != null) {\n                DataPermissionContextHolder.remove();\n            }\n        }\n    }\n\n    private DataPermission findAnnotation(MethodInvocation methodInvocation) {\n        // 1. 从缓存中获取\n        Method method = methodInvocation.getMethod();\n        Object targetObject = methodInvocation.getThis();\n        Class<?> clazz = targetObject != null ? targetObject.getClass() : method.getDeclaringClass();\n        MethodClassKey methodClassKey = new MethodClassKey(method, clazz);\n        DataPermission dataPermission = dataPermissionCache.get(methodClassKey);\n        if (dataPermission != null) {\n            return dataPermission != DATA_PERMISSION_NULL ? dataPermission : null;\n        }\n\n        // 2.1 从方法中获取\n        dataPermission = AnnotationUtils.findAnnotation(method, DataPermission.class);\n        // 2.2 从类上获取\n        if (dataPermission == null) {\n            dataPermission = AnnotationUtils.findAnnotation(clazz, DataPermission.class);\n        }\n        // 2.3 添加到缓存中\n        dataPermissionCache.put(methodClassKey, dataPermission != null ? dataPermission : DATA_PERMISSION_NULL);\n        return dataPermission;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-data-permission/src/main/java/co/yixiang/yshop/framework/datapermission/core/aop/DataPermissionContextHolder.java",
    "content": "package co.yixiang.yshop.framework.datapermission.core.aop;\n\nimport co.yixiang.yshop.framework.datapermission.core.annotation.DataPermission;\nimport com.alibaba.ttl.TransmittableThreadLocal;\n\nimport java.util.LinkedList;\nimport java.util.List;\n\n/**\n * {@link DataPermission} 注解的 Context 上下文\n *\n * @author yshop\n */\npublic class DataPermissionContextHolder {\n\n    /**\n     * 使用 List 的原因，可能存在方法的嵌套调用\n     */\n    private static final ThreadLocal<LinkedList<DataPermission>> DATA_PERMISSIONS =\n            TransmittableThreadLocal.withInitial(LinkedList::new);\n\n    /**\n     * 获得当前的 DataPermission 注解\n     *\n     * @return DataPermission 注解\n     */\n    public static DataPermission get() {\n        return DATA_PERMISSIONS.get().peekLast();\n    }\n\n    /**\n     * 入栈 DataPermission 注解\n     *\n     * @param dataPermission DataPermission 注解\n     */\n    public static void add(DataPermission dataPermission) {\n        DATA_PERMISSIONS.get().addLast(dataPermission);\n    }\n\n    /**\n     * 出栈 DataPermission 注解\n     *\n     * @return DataPermission 注解\n     */\n    public static DataPermission remove() {\n        DataPermission dataPermission = DATA_PERMISSIONS.get().removeLast();\n        // 无元素时，清空 ThreadLocal\n        if (DATA_PERMISSIONS.get().isEmpty()) {\n            DATA_PERMISSIONS.remove();\n        }\n        return dataPermission;\n    }\n\n    /**\n     * 获得所有 DataPermission\n     *\n     * @return DataPermission 队列\n     */\n    public static List<DataPermission> getAll() {\n        return DATA_PERMISSIONS.get();\n    }\n\n    /**\n     * 清空上下文\n     *\n     * 目前仅仅用于单测\n     */\n    public static void clear() {\n        DATA_PERMISSIONS.remove();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-data-permission/src/main/java/co/yixiang/yshop/framework/datapermission/core/db/DataPermissionDatabaseInterceptor.java",
    "content": "package co.yixiang.yshop.framework.datapermission.core.db;\n\nimport cn.hutool.core.collection.CollUtil;\nimport co.yixiang.yshop.framework.common.util.collection.SetUtils;\nimport co.yixiang.yshop.framework.datapermission.core.rule.DataPermissionRule;\nimport co.yixiang.yshop.framework.datapermission.core.rule.DataPermissionRuleFactory;\nimport co.yixiang.yshop.framework.mybatis.core.util.MyBatisUtils;\nimport com.baomidou.mybatisplus.core.toolkit.CollectionUtils;\nimport com.baomidou.mybatisplus.core.toolkit.PluginUtils;\nimport com.baomidou.mybatisplus.extension.parser.JsqlParserSupport;\nimport com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;\nimport lombok.Getter;\nimport lombok.RequiredArgsConstructor;\nimport net.sf.jsqlparser.expression.*;\nimport net.sf.jsqlparser.expression.operators.conditional.AndExpression;\nimport net.sf.jsqlparser.expression.operators.conditional.OrExpression;\nimport net.sf.jsqlparser.expression.operators.relational.ExistsExpression;\nimport net.sf.jsqlparser.expression.operators.relational.ExpressionList;\nimport net.sf.jsqlparser.expression.operators.relational.InExpression;\nimport net.sf.jsqlparser.schema.Table;\nimport net.sf.jsqlparser.statement.delete.Delete;\nimport net.sf.jsqlparser.statement.select.*;\nimport net.sf.jsqlparser.statement.update.Update;\nimport org.apache.ibatis.executor.Executor;\nimport org.apache.ibatis.executor.statement.StatementHandler;\nimport org.apache.ibatis.mapping.BoundSql;\nimport org.apache.ibatis.mapping.MappedStatement;\nimport org.apache.ibatis.mapping.SqlCommandType;\nimport org.apache.ibatis.session.ResultHandler;\nimport org.apache.ibatis.session.RowBounds;\n\nimport java.sql.Connection;\nimport java.util.*;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * 数据权限拦截器，通过 {@link DataPermissionRule} 数据权限规则，重写 SQL 的方式来实现\n * 主要的 SQL 重写方法，可见 {@link #builderExpression(Expression, List)} 方法\n *\n * 整体的代码实现上，参考 {@link com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor} 实现。\n * 所以每次 MyBatis Plus 升级时，需要 Review 下其具体的实现是否有变更！\n *\n * @author yshop\n */\n@RequiredArgsConstructor\npublic class DataPermissionDatabaseInterceptor extends JsqlParserSupport implements InnerInterceptor {\n\n    private final DataPermissionRuleFactory ruleFactory;\n\n    @Getter\n    private final MappedStatementCache mappedStatementCache = new MappedStatementCache();\n\n    @Override // SELECT 场景\n    public void beforeQuery(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {\n        // 获得 Mapper 对应的数据权限的规则\n        List<DataPermissionRule> rules = ruleFactory.getDataPermissionRule(ms.getId());\n        if (mappedStatementCache.noRewritable(ms, rules)) { // 如果无需重写，则跳过\n            return;\n        }\n\n        PluginUtils.MPBoundSql mpBs = PluginUtils.mpBoundSql(boundSql);\n        try {\n            // 初始化上下文\n            ContextHolder.init(rules);\n            // 处理 SQL\n            mpBs.sql(parserSingle(mpBs.sql(), null));\n        } finally {\n            // 添加是否需要重写的缓存\n            addMappedStatementCache(ms);\n            // 清空上下文\n            ContextHolder.clear();\n        }\n    }\n\n    @Override // 只处理 UPDATE / DELETE 场景，不处理 INSERT 场景（因为 INSERT 不需要数据权限)\n    public void beforePrepare(StatementHandler sh, Connection connection, Integer transactionTimeout) {\n        PluginUtils.MPStatementHandler mpSh = PluginUtils.mpStatementHandler(sh);\n        MappedStatement ms = mpSh.mappedStatement();\n        SqlCommandType sct = ms.getSqlCommandType();\n        if (sct == SqlCommandType.UPDATE || sct == SqlCommandType.DELETE) {\n            // 获得 Mapper 对应的数据权限的规则\n            List<DataPermissionRule> rules = ruleFactory.getDataPermissionRule(ms.getId());\n            if (mappedStatementCache.noRewritable(ms, rules)) { // 如果无需重写，则跳过\n                return;\n            }\n\n            PluginUtils.MPBoundSql mpBs = mpSh.mPBoundSql();\n            try {\n                // 初始化上下文\n                ContextHolder.init(rules);\n                // 处理 SQL\n                mpBs.sql(parserMulti(mpBs.sql(), null));\n            } finally {\n                // 添加是否需要重写的缓存\n                addMappedStatementCache(ms);\n                // 清空上下文\n                ContextHolder.clear();\n            }\n        }\n    }\n\n    @Override\n    protected void processSelect(Select select, int index, String sql, Object obj) {\n        processSelectBody(select.getSelectBody());\n        List<WithItem> withItemsList = select.getWithItemsList();\n        if (!CollectionUtils.isEmpty(withItemsList)) {\n            withItemsList.forEach(this::processSelectBody);\n        }\n    }\n\n    /**\n     * update 语句处理\n     */\n    @Override\n    protected void processUpdate(Update update, int index, String sql, Object obj) {\n        final Table table = update.getTable();\n        update.setWhere(this.builderExpression(update.getWhere(), table));\n    }\n\n    /**\n     * delete 语句处理\n     */\n    @Override\n    protected void processDelete(Delete delete, int index, String sql, Object obj) {\n        delete.setWhere(this.builderExpression(delete.getWhere(), delete.getTable()));\n    }\n\n    // ========== 和 TenantLineInnerInterceptor 一致的逻辑 ==========\n\n    protected void processSelectBody(SelectBody selectBody) {\n        if (selectBody == null) {\n            return;\n        }\n        if (selectBody instanceof PlainSelect) {\n            processPlainSelect((PlainSelect) selectBody);\n        } else if (selectBody instanceof WithItem) {\n            WithItem withItem = (WithItem) selectBody;\n            processSelectBody(withItem.getSubSelect().getSelectBody());\n        } else {\n            SetOperationList operationList = (SetOperationList) selectBody;\n            List<SelectBody> selectBodyList = operationList.getSelects();\n            if (CollectionUtils.isNotEmpty(selectBodyList)) {\n                selectBodyList.forEach(this::processSelectBody);\n            }\n        }\n    }\n\n    /**\n     * 处理 PlainSelect\n     */\n    protected void processPlainSelect(PlainSelect plainSelect) {\n        //#3087 github\n        List<SelectItem> selectItems = plainSelect.getSelectItems();\n        if (CollectionUtils.isNotEmpty(selectItems)) {\n            selectItems.forEach(this::processSelectItem);\n        }\n\n        // 处理 where 中的子查询\n        Expression where = plainSelect.getWhere();\n        processWhereSubSelect(where);\n\n        // 处理 fromItem\n        FromItem fromItem = plainSelect.getFromItem();\n        List<Table> list = processFromItem(fromItem);\n        List<Table> mainTables = new ArrayList<>(list);\n\n        // 处理 join\n        List<Join> joins = plainSelect.getJoins();\n        if (CollectionUtils.isNotEmpty(joins)) {\n            mainTables = processJoins(mainTables, joins);\n        }\n\n        // 当有 mainTable 时，进行 where 条件追加\n        if (CollectionUtils.isNotEmpty(mainTables)) {\n            plainSelect.setWhere(builderExpression(where, mainTables));\n        }\n    }\n\n    private List<Table> processFromItem(FromItem fromItem) {\n        // 处理括号括起来的表达式\n        while (fromItem instanceof ParenthesisFromItem) {\n            fromItem = ((ParenthesisFromItem) fromItem).getFromItem();\n        }\n\n        List<Table> mainTables = new ArrayList<>();\n        // 无 join 时的处理逻辑\n        if (fromItem instanceof Table) {\n            Table fromTable = (Table) fromItem;\n            mainTables.add(fromTable);\n        } else if (fromItem instanceof SubJoin) {\n            // SubJoin 类型则还需要添加上 where 条件\n            List<Table> tables = processSubJoin((SubJoin) fromItem);\n            mainTables.addAll(tables);\n        } else {\n            // 处理下 fromItem\n            processOtherFromItem(fromItem);\n        }\n        return mainTables;\n    }\n\n    /**\n     * 处理where条件内的子查询\n     * <p>\n     * 支持如下:\n     * 1. in\n     * 2. =\n     * 3. >\n     * 4. <\n     * 5. >=\n     * 6. <=\n     * 7. <>\n     * 8. EXISTS\n     * 9. NOT EXISTS\n     * <p>\n     * 前提条件:\n     * 1. 子查询必须放在小括号中\n     * 2. 子查询一般放在比较操作符的右边\n     *\n     * @param where where 条件\n     */\n    protected void processWhereSubSelect(Expression where) {\n        if (where == null) {\n            return;\n        }\n        if (where instanceof FromItem) {\n            processOtherFromItem((FromItem) where);\n            return;\n        }\n        if (where.toString().indexOf(\"SELECT\") > 0) {\n            // 有子查询\n            if (where instanceof BinaryExpression) {\n                // 比较符号 , and , or , 等等\n                BinaryExpression expression = (BinaryExpression) where;\n                processWhereSubSelect(expression.getLeftExpression());\n                processWhereSubSelect(expression.getRightExpression());\n            } else if (where instanceof InExpression) {\n                // in\n                InExpression expression = (InExpression) where;\n                Expression inExpression = expression.getRightExpression();\n                if (inExpression instanceof SubSelect) {\n                    processSelectBody(((SubSelect) inExpression).getSelectBody());\n                }\n            } else if (where instanceof ExistsExpression) {\n                // exists\n                ExistsExpression expression = (ExistsExpression) where;\n                processWhereSubSelect(expression.getRightExpression());\n            } else if (where instanceof NotExpression) {\n                // not exists\n                NotExpression expression = (NotExpression) where;\n                processWhereSubSelect(expression.getExpression());\n            } else if (where instanceof Parenthesis) {\n                Parenthesis expression = (Parenthesis) where;\n                processWhereSubSelect(expression.getExpression());\n            }\n        }\n    }\n\n    protected void processSelectItem(SelectItem selectItem) {\n        if (selectItem instanceof SelectExpressionItem) {\n            SelectExpressionItem selectExpressionItem = (SelectExpressionItem) selectItem;\n            if (selectExpressionItem.getExpression() instanceof SubSelect) {\n                processSelectBody(((SubSelect) selectExpressionItem.getExpression()).getSelectBody());\n            } else if (selectExpressionItem.getExpression() instanceof Function) {\n                processFunction((Function) selectExpressionItem.getExpression());\n            }\n        }\n    }\n\n    /**\n     * 处理函数\n     * <p>支持: 1. select fun(args..) 2. select fun1(fun2(args..),args..)<p>\n     * <p> fixed gitee pulls/141</p>\n     *\n     * @param function\n     */\n    protected void processFunction(Function function) {\n        ExpressionList parameters = function.getParameters();\n        if (parameters != null) {\n            parameters.getExpressions().forEach(expression -> {\n                if (expression instanceof SubSelect) {\n                    processSelectBody(((SubSelect) expression).getSelectBody());\n                } else if (expression instanceof Function) {\n                    processFunction((Function) expression);\n                }\n            });\n        }\n    }\n\n    /**\n     * 处理子查询等\n     */\n    protected void processOtherFromItem(FromItem fromItem) {\n        // 去除括号\n        while (fromItem instanceof ParenthesisFromItem) {\n            fromItem = ((ParenthesisFromItem) fromItem).getFromItem();\n        }\n\n        if (fromItem instanceof SubSelect) {\n            SubSelect subSelect = (SubSelect) fromItem;\n            if (subSelect.getSelectBody() != null) {\n                processSelectBody(subSelect.getSelectBody());\n            }\n        } else if (fromItem instanceof ValuesList) {\n            logger.debug(\"Perform a subQuery, if you do not give us feedback\");\n        } else if (fromItem instanceof LateralSubSelect) {\n            LateralSubSelect lateralSubSelect = (LateralSubSelect) fromItem;\n            if (lateralSubSelect.getSubSelect() != null) {\n                SubSelect subSelect = lateralSubSelect.getSubSelect();\n                if (subSelect.getSelectBody() != null) {\n                    processSelectBody(subSelect.getSelectBody());\n                }\n            }\n        }\n    }\n\n    /**\n     * 处理 sub join\n     *\n     * @param subJoin subJoin\n     * @return Table subJoin 中的主表\n     */\n    private List<Table> processSubJoin(SubJoin subJoin) {\n        List<Table> mainTables = new ArrayList<>();\n        if (subJoin.getJoinList() != null) {\n            List<Table> list = processFromItem(subJoin.getLeft());\n            mainTables.addAll(list);\n            mainTables = processJoins(mainTables, subJoin.getJoinList());\n        }\n        return mainTables;\n    }\n\n    /**\n     * 处理 joins\n     *\n     * @param mainTables 可以为 null\n     * @param joins      join 集合\n     * @return List<Table> 右连接查询的 Table 列表\n     */\n    private List<Table> processJoins(List<Table> mainTables, List<Join> joins) {\n        // join 表达式中最终的主表\n        Table mainTable = null;\n        // 当前 join 的左表\n        Table leftTable = null;\n\n        if (mainTables == null) {\n            mainTables = new ArrayList<>();\n        } else if (mainTables.size() == 1) {\n            mainTable = mainTables.get(0);\n            leftTable = mainTable;\n        }\n\n        //对于 on 表达式写在最后的 join，需要记录下前面多个 on 的表名\n        Deque<List<Table>> onTableDeque = new LinkedList<>();\n        for (Join join : joins) {\n            // 处理 on 表达式\n            FromItem joinItem = join.getRightItem();\n\n            // 获取当前 join 的表，subJoint 可以看作是一张表\n            List<Table> joinTables = null;\n            if (joinItem instanceof Table) {\n                joinTables = new ArrayList<>();\n                joinTables.add((Table) joinItem);\n            } else if (joinItem instanceof SubJoin) {\n                joinTables = processSubJoin((SubJoin) joinItem);\n            }\n\n            if (joinTables != null) {\n\n                // 如果是隐式内连接\n                if (join.isSimple()) {\n                    mainTables.addAll(joinTables);\n                    continue;\n                }\n\n                // 当前表是否忽略\n                Table joinTable = joinTables.get(0);\n\n                List<Table> onTables = null;\n                // 如果不要忽略，且是右连接，则记录下当前表\n                if (join.isRight()) {\n                    mainTable = joinTable;\n                    if (leftTable != null) {\n                        onTables = Collections.singletonList(leftTable);\n                    }\n                } else if (join.isLeft()) {\n                    onTables = Collections.singletonList(joinTable);\n                } else if (join.isInner()) {\n                    if (mainTable == null) {\n                        onTables = Collections.singletonList(joinTable);\n                    } else {\n                        onTables = Arrays.asList(mainTable, joinTable);\n                    }\n                    mainTable = null;\n                }\n\n                mainTables = new ArrayList<>();\n                if (mainTable != null) {\n                    mainTables.add(mainTable);\n                }\n\n                // 获取 join 尾缀的 on 表达式列表\n                Collection<Expression> originOnExpressions = join.getOnExpressions();\n                // 正常 join on 表达式只有一个，立刻处理\n                if (originOnExpressions.size() == 1 && onTables != null) {\n                    List<Expression> onExpressions = new LinkedList<>();\n                    onExpressions.add(builderExpression(originOnExpressions.iterator().next(), onTables));\n                    join.setOnExpressions(onExpressions);\n                    leftTable = joinTable;\n                    continue;\n                }\n                // 表名压栈，忽略的表压入 null，以便后续不处理\n                onTableDeque.push(onTables);\n                // 尾缀多个 on 表达式的时候统一处理\n                if (originOnExpressions.size() > 1) {\n                    Collection<Expression> onExpressions = new LinkedList<>();\n                    for (Expression originOnExpression : originOnExpressions) {\n                        List<Table> currentTableList = onTableDeque.poll();\n                        if (CollectionUtils.isEmpty(currentTableList)) {\n                            onExpressions.add(originOnExpression);\n                        } else {\n                            onExpressions.add(builderExpression(originOnExpression, currentTableList));\n                        }\n                    }\n                    join.setOnExpressions(onExpressions);\n                }\n                leftTable = joinTable;\n            } else {\n                processOtherFromItem(joinItem);\n                leftTable = null;\n            }\n        }\n\n        return mainTables;\n    }\n\n    // ========== 和 TenantLineInnerInterceptor 存在差异的逻辑：关键，实现权限条件的拼接 ==========\n\n    /**\n     * 处理条件\n     *\n     * @param currentExpression 当前 where 条件\n     * @param table             单个表\n     */\n    protected Expression builderExpression(Expression currentExpression, Table table) {\n        return this.builderExpression(currentExpression, Collections.singletonList(table));\n    }\n\n    /**\n     * 处理条件\n     *\n     * @param currentExpression 当前 where 条件\n     * @param tables 多个表\n     */\n    protected Expression builderExpression(Expression currentExpression, List<Table> tables) {\n        // 没有表需要处理直接返回\n        if (CollectionUtils.isEmpty(tables)) {\n            return currentExpression;\n        }\n\n        // 第一步，获得 Table 对应的数据权限条件\n        Expression dataPermissionExpression = null;\n        for (Table table : tables) {\n            // 构建每个表的权限 Expression 条件\n            Expression expression = buildDataPermissionExpression(table);\n            if (expression == null) {\n                continue;\n            }\n            // 合并到 dataPermissionExpression 中\n            dataPermissionExpression = dataPermissionExpression == null ? expression\n                    : new AndExpression(dataPermissionExpression, expression);\n        }\n\n        // 第二步，合并多个 Expression 条件\n        if (dataPermissionExpression == null) {\n            return currentExpression;\n        }\n        if (currentExpression == null) {\n            return dataPermissionExpression;\n        }\n        // ① 如果表达式为 Or，则需要 (currentExpression) AND dataPermissionExpression\n        if (currentExpression instanceof OrExpression) {\n            return new AndExpression(new Parenthesis(currentExpression), dataPermissionExpression);\n        }\n        // ② 如果表达式为 And，则直接返回 where AND dataPermissionExpression\n        return new AndExpression(currentExpression, dataPermissionExpression);\n    }\n\n    /**\n     * 构建指定表的数据权限的 Expression 过滤条件\n     *\n     * @param table 表\n     * @return Expression 过滤条件\n     */\n    private Expression buildDataPermissionExpression(Table table) {\n        // 生成条件\n        Expression allExpression = null;\n        for (DataPermissionRule rule : ContextHolder.getRules()) {\n            // 判断表名是否匹配\n            String tableName = MyBatisUtils.getTableName(table);\n            if (!rule.getTableNames().contains(tableName)) {\n                continue;\n            }\n            // 如果有匹配的规则，说明可重写。\n            // 为什么不是有 allExpression 非空才重写呢？在生成 column = value 过滤条件时，会因为 value 不存在，导致未重写。\n            // 这样导致第一次无 value，被标记成无需重写；但是第二次有 value，此时会需要重写。\n            ContextHolder.setRewrite(true);\n\n            // 单条规则的条件\n            Expression oneExpress = rule.getExpression(tableName, table.getAlias());\n            if (oneExpress == null){\n                continue;\n            }\n            // 拼接到 allExpression 中\n            allExpression = allExpression == null ? oneExpress\n                    : new AndExpression(allExpression, oneExpress);\n        }\n\n        return allExpression;\n    }\n\n    /**\n     * 判断 SQL 是否重写。如果没有重写，则添加到 {@link MappedStatementCache} 中\n     *\n     * @param ms MappedStatement\n     */\n    private void addMappedStatementCache(MappedStatement ms) {\n        if (ContextHolder.getRewrite()) {\n            return;\n        }\n        // 无重写，进行添加\n        mappedStatementCache.addNoRewritable(ms, ContextHolder.getRules());\n    }\n\n    /**\n     * SQL 解析上下文，方便透传 {@link DataPermissionRule} 规则\n     *\n     * @author yshop\n     */\n    static final class ContextHolder {\n\n        /**\n         * 该 {@link MappedStatement} 对应的规则\n         */\n        private static final ThreadLocal<List<DataPermissionRule>> RULES = ThreadLocal.withInitial(Collections::emptyList);\n        /**\n         * SQL 是否进行重写\n         */\n        private static final ThreadLocal<Boolean> REWRITE = ThreadLocal.withInitial(() -> Boolean.FALSE);\n\n        public static void init(List<DataPermissionRule> rules) {\n            RULES.set(rules);\n            REWRITE.set(false);\n        }\n\n        public static void clear() {\n            RULES.remove();\n            REWRITE.remove();\n        }\n\n        public static boolean getRewrite() {\n            return REWRITE.get();\n        }\n\n        public static void setRewrite(boolean rewrite) {\n            REWRITE.set(rewrite);\n        }\n\n        public static List<DataPermissionRule> getRules() {\n            return RULES.get();\n        }\n\n    }\n\n    /**\n     * {@link MappedStatement} 缓存\n     * 目前主要用于，记录 {@link DataPermissionRule} 是否对指定 {@link MappedStatement} 无效\n     * 如果无效，则可以避免 SQL 的解析，加快速度\n     *\n     * @author yshop\n     */\n    static final class MappedStatementCache {\n\n        /**\n         * 指定数据权限规则，对指定 MappedStatement 无需重写（不生效)的缓存\n         *\n         * value：{@link MappedStatement#getId()} 编号\n         */\n        @Getter\n        private final Map<Class<? extends DataPermissionRule>, Set<String>> noRewritableMappedStatements = new ConcurrentHashMap<>();\n\n        /**\n         * 判断是否无需重写\n         * ps：虽然有点中文式英语，但是容易读懂即可\n         *\n         * @param ms MappedStatement\n         * @param rules 数据权限规则数组\n         * @return 是否无需重写\n         */\n        public boolean noRewritable(MappedStatement ms, List<DataPermissionRule> rules) {\n            // 如果规则为空，说明无需重写\n            if (CollUtil.isEmpty(rules)) {\n                return true;\n            }\n            // 任一规则不在 noRewritableMap 中，则说明可能需要重写\n            for (DataPermissionRule rule : rules) {\n                Set<String> mappedStatementIds = noRewritableMappedStatements.get(rule.getClass());\n                if (!CollUtil.contains(mappedStatementIds, ms.getId())) {\n                    return false;\n                }\n            }\n            return true;\n        }\n\n        /**\n         * 添加无需重写的 MappedStatement\n         *\n         * @param ms MappedStatement\n         * @param rules 数据权限规则数组\n         */\n        public void addNoRewritable(MappedStatement ms, List<DataPermissionRule> rules) {\n            for (DataPermissionRule rule : rules) {\n                Set<String> mappedStatementIds = noRewritableMappedStatements.get(rule.getClass());\n                if (CollUtil.isNotEmpty(mappedStatementIds)) {\n                    mappedStatementIds.add(ms.getId());\n                } else {\n                    noRewritableMappedStatements.put(rule.getClass(), SetUtils.asSet(ms.getId()));\n                }\n            }\n        }\n\n        /**\n         * 清空缓存\n         * 目前主要提供给单元测试\n         */\n        public void clear() {\n            noRewritableMappedStatements.clear();\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-data-permission/src/main/java/co/yixiang/yshop/framework/datapermission/core/rule/DataPermissionRule.java",
    "content": "package co.yixiang.yshop.framework.datapermission.core.rule;\n\nimport com.baomidou.mybatisplus.core.metadata.TableInfoHelper;\nimport net.sf.jsqlparser.expression.Alias;\nimport net.sf.jsqlparser.expression.Expression;\n\nimport java.util.Set;\n\n/**\n * 数据权限规则接口\n * 通过实现接口，自定义数据规则。例如说，\n *\n * @author yshop\n */\npublic interface DataPermissionRule {\n\n    /**\n     * 返回需要生效的表名数组\n     * 为什么需要该方法？Data Permission 数组基于 SQL 重写，通过 Where 返回只有权限的数据\n     *\n     * 如果需要基于实体名获得表名，可调用 {@link TableInfoHelper#getTableInfo(Class)} 获得\n     *\n     * @return 表名数组\n     */\n    Set<String> getTableNames();\n\n    /**\n     * 根据表名和别名，生成对应的 WHERE / OR 过滤条件\n     *\n     * @param tableName 表名\n     * @param tableAlias 别名，可能为空\n     * @return 过滤条件 Expression 表达式\n     */\n    Expression getExpression(String tableName, Alias tableAlias);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-data-permission/src/main/java/co/yixiang/yshop/framework/datapermission/core/rule/DataPermissionRuleFactory.java",
    "content": "package co.yixiang.yshop.framework.datapermission.core.rule;\n\nimport java.util.List;\n\n/**\n * {@link DataPermissionRule} 工厂接口\n * 作为 {@link DataPermissionRule} 的容器，提供管理能力\n *\n * @author yshop\n */\npublic interface DataPermissionRuleFactory {\n\n    /**\n     * 获得所有数据权限规则数组\n     *\n     * @return 数据权限规则数组\n     */\n    List<DataPermissionRule> getDataPermissionRules();\n\n    /**\n     * 获得指定 Mapper 的数据权限规则数组\n     *\n     * @param mappedStatementId 指定 Mapper 的编号\n     * @return 数据权限规则数组\n     */\n    List<DataPermissionRule> getDataPermissionRule(String mappedStatementId);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-data-permission/src/main/java/co/yixiang/yshop/framework/datapermission/core/rule/DataPermissionRuleFactoryImpl.java",
    "content": "package co.yixiang.yshop.framework.datapermission.core.rule;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.util.ArrayUtil;\nimport co.yixiang.yshop.framework.datapermission.core.annotation.DataPermission;\nimport co.yixiang.yshop.framework.datapermission.core.aop.DataPermissionContextHolder;\nimport lombok.RequiredArgsConstructor;\n\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n/**\n * 默认的 DataPermissionRuleFactoryImpl 实现类\n * 支持通过 {@link DataPermissionContextHolder} 过滤数据权限\n *\n * @author yshop\n */\n@RequiredArgsConstructor\npublic class DataPermissionRuleFactoryImpl implements DataPermissionRuleFactory {\n\n    /**\n     * 数据权限规则数组\n     */\n    private final List<DataPermissionRule> rules;\n\n    @Override\n    public List<DataPermissionRule> getDataPermissionRules() {\n        return rules;\n    }\n\n    @Override // mappedStatementId 参数，暂时没有用。以后，可以基于 mappedStatementId + DataPermission 进行缓存\n    public List<DataPermissionRule> getDataPermissionRule(String mappedStatementId) {\n        // 1. 无数据权限\n        if (CollUtil.isEmpty(rules)) {\n            return Collections.emptyList();\n        }\n        // 2. 未配置，则默认开启\n        DataPermission dataPermission = DataPermissionContextHolder.get();\n        if (dataPermission == null) {\n            return rules;\n        }\n        // 3. 已配置，但禁用\n        if (!dataPermission.enable()) {\n            return Collections.emptyList();\n        }\n\n        // 4. 已配置，只选择部分规则\n        if (ArrayUtil.isNotEmpty(dataPermission.includeRules())) {\n            return rules.stream().filter(rule -> ArrayUtil.contains(dataPermission.includeRules(), rule.getClass()))\n                    .collect(Collectors.toList()); // 一般规则不会太多，所以不采用 HashSet 查询\n        }\n        // 5. 已配置，只排除部分规则\n        if (ArrayUtil.isNotEmpty(dataPermission.excludeRules())) {\n            return rules.stream().filter(rule -> !ArrayUtil.contains(dataPermission.excludeRules(), rule.getClass()))\n                    .collect(Collectors.toList()); // 一般规则不会太多，所以不采用 HashSet 查询\n        }\n        // 6. 已配置，全部规则\n        return rules;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-data-permission/src/main/java/co/yixiang/yshop/framework/datapermission/core/rule/dept/DeptDataPermissionRule.java",
    "content": "package co.yixiang.yshop.framework.datapermission.core.rule.dept;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.util.ObjectUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.common.util.collection.CollectionUtils;\nimport co.yixiang.yshop.framework.common.util.json.JsonUtils;\nimport co.yixiang.yshop.framework.datapermission.core.rule.DataPermissionRule;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport co.yixiang.yshop.framework.mybatis.core.util.MyBatisUtils;\nimport co.yixiang.yshop.framework.security.core.LoginUser;\nimport co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils;\nimport co.yixiang.yshop.module.system.api.permission.PermissionApi;\nimport co.yixiang.yshop.module.system.api.permission.dto.DeptDataPermissionRespDTO;\nimport com.baomidou.mybatisplus.core.metadata.TableInfoHelper;\nimport lombok.AllArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport net.sf.jsqlparser.expression.*;\nimport net.sf.jsqlparser.expression.operators.conditional.OrExpression;\nimport net.sf.jsqlparser.expression.operators.relational.EqualsTo;\nimport net.sf.jsqlparser.expression.operators.relational.ExpressionList;\nimport net.sf.jsqlparser.expression.operators.relational.InExpression;\n\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.Map;\nimport java.util.Set;\n\n/**\n * 基于部门的 {@link DataPermissionRule} 数据权限规则实现\n *\n * 注意，使用 DeptDataPermissionRule 时，需要保证表中有 dept_id 部门编号的字段，可自定义。\n *\n * 实际业务场景下，会存在一个经典的问题？当用户修改部门时，冗余的 dept_id 是否需要修改？\n * 1. 一般情况下，dept_id 不进行修改，则会导致用户看不到之前的数据。【yshop-server 采用该方案】\n * 2. 部分情况下，希望该用户还是能看到之前的数据，则有两种方式解决：【需要你改造该 DeptDataPermissionRule 的实现代码】\n *  1）编写洗数据的脚本，将 dept_id 修改成新部门的编号；【建议】\n *      最终过滤条件是 WHERE dept_id = ?\n *  2）洗数据的话，可能涉及的数据量较大，也可以采用 user_id 进行过滤的方式，此时需要获取到 dept_id 对应的所有 user_id 用户编号；\n *      最终过滤条件是 WHERE user_id IN (?, ?, ? ...)\n *  3）想要保证原 dept_id 和 user_id 都可以看的到，此时使用 dept_id 和 user_id 一起过滤；\n *      最终过滤条件是 WHERE dept_id = ? OR user_id IN (?, ?, ? ...)\n *\n * @author yshop\n */\n@AllArgsConstructor\n@Slf4j\npublic class DeptDataPermissionRule implements DataPermissionRule {\n\n    /**\n     * LoginUser 的 Context 缓存 Key\n     */\n    protected static final String CONTEXT_KEY = DeptDataPermissionRule.class.getSimpleName();\n\n    private static final String DEPT_COLUMN_NAME = \"dept_id\";\n    private static final String USER_COLUMN_NAME = \"user_id\";\n\n    static final Expression EXPRESSION_NULL = new NullValue();\n\n    private final PermissionApi permissionApi;\n\n    /**\n     * 基于部门的表字段配置\n     * 一般情况下，每个表的部门编号字段是 dept_id，通过该配置自定义。\n     *\n     * key：表名\n     * value：字段名\n     */\n    private final Map<String, String> deptColumns = new HashMap<>();\n    /**\n     * 基于用户的表字段配置\n     * 一般情况下，每个表的部门编号字段是 dept_id，通过该配置自定义。\n     *\n     * key：表名\n     * value：字段名\n     */\n    private final Map<String, String> userColumns = new HashMap<>();\n    /**\n     * 所有表名，是 {@link #deptColumns} 和 {@link #userColumns} 的合集\n     */\n    private final Set<String> TABLE_NAMES = new HashSet<>();\n\n    @Override\n    public Set<String> getTableNames() {\n        return TABLE_NAMES;\n    }\n\n    @Override\n    public Expression getExpression(String tableName, Alias tableAlias) {\n        // 只有有登陆用户的情况下，才进行数据权限的处理\n        LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();\n        if (loginUser == null) {\n            return null;\n        }\n        // 只有管理员类型的用户，才进行数据权限的处理\n        if (ObjectUtil.notEqual(loginUser.getUserType(), UserTypeEnum.ADMIN.getValue())) {\n            return null;\n        }\n\n        // 获得数据权限\n        DeptDataPermissionRespDTO deptDataPermission = loginUser.getContext(CONTEXT_KEY, DeptDataPermissionRespDTO.class);\n        // 从上下文中拿不到，则调用逻辑进行获取\n        if (deptDataPermission == null) {\n            deptDataPermission = permissionApi.getDeptDataPermission(loginUser.getId());\n            if (deptDataPermission == null) {\n                log.error(\"[getExpression][LoginUser({}) 获取数据权限为 null]\", JsonUtils.toJsonString(loginUser));\n                throw new NullPointerException(String.format(\"LoginUser(%d) Table(%s/%s) 未返回数据权限\",\n                        loginUser.getId(), tableName, tableAlias.getName()));\n            }\n            // 添加到上下文中，避免重复计算\n            loginUser.setContext(CONTEXT_KEY, deptDataPermission);\n        }\n\n        // 情况一，如果是 ALL 可查看全部，则无需拼接条件\n        if (deptDataPermission.getAll()) {\n            return null;\n        }\n\n        // 情况二，即不能查看部门，又不能查看自己，则说明 100% 无权限\n        if (CollUtil.isEmpty(deptDataPermission.getDeptIds())\n            && Boolean.FALSE.equals(deptDataPermission.getSelf())) {\n            return new EqualsTo(null, null); // WHERE null = null，可以保证返回的数据为空\n        }\n\n        // 情况三，拼接 Dept 和 User 的条件，最后组合\n        Expression deptExpression = buildDeptExpression(tableName,tableAlias, deptDataPermission.getDeptIds());\n        Expression userExpression = buildUserExpression(tableName, tableAlias, deptDataPermission.getSelf(), loginUser.getId());\n        if (deptExpression == null && userExpression == null) {\n            // TODO yshop：获得不到条件的时候，暂时不抛出异常，而是不返回数据\n            log.warn(\"[getExpression][LoginUser({}) Table({}/{}) DeptDataPermission({}) 构建的条件为空]\",\n                    JsonUtils.toJsonString(loginUser), tableName, tableAlias, JsonUtils.toJsonString(deptDataPermission));\n//            throw new NullPointerException(String.format(\"LoginUser(%d) Table(%s/%s) 构建的条件为空\",\n//                    loginUser.getId(), tableName, tableAlias.getName()));\n            return EXPRESSION_NULL;\n        }\n        if (deptExpression == null) {\n            return userExpression;\n        }\n        if (userExpression == null) {\n            return deptExpression;\n        }\n        // 目前，如果有指定部门 + 可查看自己，采用 OR 条件。即，WHERE (dept_id IN ? OR user_id = ?)\n        return new Parenthesis(new OrExpression(deptExpression, userExpression));\n    }\n\n    private Expression buildDeptExpression(String tableName, Alias tableAlias, Set<Long> deptIds) {\n        // 如果不存在配置，则无需作为条件\n        String columnName = deptColumns.get(tableName);\n        if (StrUtil.isEmpty(columnName)) {\n            return null;\n        }\n        // 如果为空，则无条件\n        if (CollUtil.isEmpty(deptIds)) {\n            return null;\n        }\n        // 拼接条件\n        return new InExpression(MyBatisUtils.buildColumn(tableName, tableAlias, columnName),\n                new ExpressionList(CollectionUtils.convertList(deptIds, LongValue::new)));\n    }\n\n    private Expression buildUserExpression(String tableName, Alias tableAlias, Boolean self, Long userId) {\n        // 如果不查看自己，则无需作为条件\n        if (Boolean.FALSE.equals(self)) {\n            return null;\n        }\n        String columnName = userColumns.get(tableName);\n        if (StrUtil.isEmpty(columnName)) {\n            return null;\n        }\n        // 拼接条件\n        return new EqualsTo(MyBatisUtils.buildColumn(tableName, tableAlias, columnName), new LongValue(userId));\n    }\n\n    // ==================== 添加配置 ====================\n\n    public void addDeptColumn(Class<? extends BaseDO> entityClass) {\n        addDeptColumn(entityClass, DEPT_COLUMN_NAME);\n    }\n\n    public void addDeptColumn(Class<? extends BaseDO> entityClass, String columnName) {\n        String tableName = TableInfoHelper.getTableInfo(entityClass).getTableName();\n       addDeptColumn(tableName, columnName);\n    }\n\n    public void addDeptColumn(String tableName, String columnName) {\n        deptColumns.put(tableName, columnName);\n        TABLE_NAMES.add(tableName);\n    }\n\n    public void addUserColumn(Class<? extends BaseDO> entityClass) {\n        addUserColumn(entityClass, USER_COLUMN_NAME);\n    }\n\n    public void addUserColumn(Class<? extends BaseDO> entityClass, String columnName) {\n        String tableName = TableInfoHelper.getTableInfo(entityClass).getTableName();\n        addUserColumn(tableName, columnName);\n    }\n\n    public void addUserColumn(String tableName, String columnName) {\n        userColumns.put(tableName, columnName);\n        TABLE_NAMES.add(tableName);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-data-permission/src/main/java/co/yixiang/yshop/framework/datapermission/core/rule/dept/DeptDataPermissionRuleCustomizer.java",
    "content": "package co.yixiang.yshop.framework.datapermission.core.rule.dept;\n\n/**\n * {@link DeptDataPermissionRule} 的自定义配置接口\n *\n * @author yshop\n */\n@FunctionalInterface\npublic interface DeptDataPermissionRuleCustomizer {\n\n    /**\n     * 自定义该权限规则\n     * 1. 调用 {@link DeptDataPermissionRule#addDeptColumn(Class, String)} 方法，配置基于 dept_id 的过滤规则\n     * 2. 调用 {@link DeptDataPermissionRule#addUserColumn(Class, String)} 方法，配置基于 user_id 的过滤规则\n     *\n     * @param rule 权限规则\n     */\n    void customize(DeptDataPermissionRule rule);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-data-permission/src/main/java/co/yixiang/yshop/framework/datapermission/core/rule/dept/package-info.java",
    "content": "/**\n * 基于部门的数据权限规则\n *\n * @author yshop\n */\npackage co.yixiang.yshop.framework.datapermission.core.rule.dept;\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-data-permission/src/main/java/co/yixiang/yshop/framework/datapermission/core/util/DataPermissionUtils.java",
    "content": "package co.yixiang.yshop.framework.datapermission.core.util;\n\nimport co.yixiang.yshop.framework.datapermission.core.annotation.DataPermission;\nimport co.yixiang.yshop.framework.datapermission.core.aop.DataPermissionContextHolder;\nimport lombok.SneakyThrows;\n\nimport java.util.concurrent.Callable;\n\n/**\n * 数据权限 Util\n *\n * @author yshop\n */\npublic class DataPermissionUtils {\n\n    private static DataPermission DATA_PERMISSION_DISABLE;\n\n    @DataPermission(enable = false)\n    @SneakyThrows\n    private static DataPermission getDisableDataPermissionDisable() {\n        if (DATA_PERMISSION_DISABLE == null) {\n            DATA_PERMISSION_DISABLE = DataPermissionUtils.class\n                    .getDeclaredMethod(\"getDisableDataPermissionDisable\")\n                    .getAnnotation(DataPermission.class);\n        }\n        return DATA_PERMISSION_DISABLE;\n    }\n\n    /**\n     * 忽略数据权限，执行对应的逻辑\n     *\n     * @param runnable 逻辑\n     */\n    public static void executeIgnore(Runnable runnable) {\n        DataPermission dataPermission = getDisableDataPermissionDisable();\n        DataPermissionContextHolder.add(dataPermission);\n        try {\n            // 执行 runnable\n            runnable.run();\n        } finally {\n            DataPermissionContextHolder.remove();\n        }\n    }\n\n    /**\n     * 忽略数据权限，执行对应的逻辑\n     *\n     * @param callable 逻辑\n     * @return 执行结果\n     */\n    @SneakyThrows\n    public static <T> T executeIgnore(Callable<T> callable) {\n        DataPermission dataPermission = getDisableDataPermissionDisable();\n        DataPermissionContextHolder.add(dataPermission);\n        try {\n            // 执行 callable\n            return callable.call();\n        } finally {\n            DataPermissionContextHolder.remove();\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-data-permission/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports",
    "content": "co.yixiang.yshop.framework.datapermission.config.YshopDataPermissionAutoConfiguration\nco.yixiang.yshop.framework.datapermission.config.YshopDeptDataPermissionAutoConfiguration"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-data-permission/src/test/java/co/yixiang/yshop/framework/datapermission/core/aop/DataPermissionAnnotationInterceptorTest.java",
    "content": "package co.yixiang.yshop.framework.datapermission.core.aop;\n\nimport cn.hutool.core.collection.CollUtil;\nimport co.yixiang.yshop.framework.datapermission.core.annotation.DataPermission;\nimport co.yixiang.yshop.framework.test.core.ut.BaseMockitoUnitTest;\nimport org.aopalliance.intercept.MethodInvocation;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\n\nimport java.lang.reflect.Method;\n\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.when;\n\n/**\n * {@link DataPermissionAnnotationInterceptor} 的单元测试\n *\n * @author yshop\n */\npublic class DataPermissionAnnotationInterceptorTest extends BaseMockitoUnitTest {\n\n    @InjectMocks\n    private DataPermissionAnnotationInterceptor interceptor;\n\n    @Mock\n    private MethodInvocation methodInvocation;\n\n    @BeforeEach\n    public void setUp() {\n        interceptor.getDataPermissionCache().clear();\n    }\n\n    @Test // 无 @DataPermission 注解\n    public void testInvoke_none() throws Throwable {\n        // 参数\n        mockMethodInvocation(TestNone.class);\n\n        // 调用\n        Object result = interceptor.invoke(methodInvocation);\n        // 断言\n        assertEquals(\"none\", result);\n        assertEquals(1, interceptor.getDataPermissionCache().size());\n        assertTrue(CollUtil.getFirst(interceptor.getDataPermissionCache().values()).enable());\n    }\n\n    @Test // 在 Method 上有 @DataPermission 注解\n    public void testInvoke_method() throws Throwable {\n        // 参数\n        mockMethodInvocation(TestMethod.class);\n\n        // 调用\n        Object result = interceptor.invoke(methodInvocation);\n        // 断言\n        assertEquals(\"method\", result);\n        assertEquals(1, interceptor.getDataPermissionCache().size());\n        assertFalse(CollUtil.getFirst(interceptor.getDataPermissionCache().values()).enable());\n    }\n\n    @Test // 在 Class 上有 @DataPermission 注解\n    public void testInvoke_class() throws Throwable {\n        // 参数\n        mockMethodInvocation(TestClass.class);\n\n        // 调用\n        Object result = interceptor.invoke(methodInvocation);\n        // 断言\n        assertEquals(\"class\", result);\n        assertEquals(1, interceptor.getDataPermissionCache().size());\n        assertFalse(CollUtil.getFirst(interceptor.getDataPermissionCache().values()).enable());\n    }\n\n    private void mockMethodInvocation(Class<?> clazz) throws Throwable {\n        Object targetObject = clazz.newInstance();\n        Method method = targetObject.getClass().getMethod(\"echo\");\n        when(methodInvocation.getThis()).thenReturn(targetObject);\n        when(methodInvocation.getMethod()).thenReturn(method);\n        when(methodInvocation.proceed()).then(invocationOnMock -> method.invoke(targetObject));\n    }\n\n    static class TestMethod {\n\n        @DataPermission(enable = false)\n        public String echo() {\n            return \"method\";\n        }\n\n    }\n\n    @DataPermission(enable = false)\n    static class TestClass {\n\n        public String echo() {\n            return \"class\";\n        }\n\n    }\n\n    static class TestNone {\n\n        public String echo() {\n            return \"none\";\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-data-permission/src/test/java/co/yixiang/yshop/framework/datapermission/core/aop/DataPermissionContextHolderTest.java",
    "content": "package co.yixiang.yshop.framework.datapermission.core.aop;\n\nimport co.yixiang.yshop.framework.datapermission.core.annotation.DataPermission;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertSame;\nimport static org.mockito.Mockito.mock;\n\n/**\n * {@link DataPermissionContextHolder} 的单元测试\n *\n * @author yshop\n */\nclass DataPermissionContextHolderTest {\n\n    @BeforeEach\n    public void setUp() {\n        DataPermissionContextHolder.clear();\n    }\n\n    @Test\n    public void testGet() {\n        // mock 方法\n        DataPermission dataPermission01 = mock(DataPermission.class);\n        DataPermissionContextHolder.add(dataPermission01);\n        DataPermission dataPermission02 = mock(DataPermission.class);\n        DataPermissionContextHolder.add(dataPermission02);\n\n        // 调用\n        DataPermission result = DataPermissionContextHolder.get();\n        // 断言\n        assertSame(result, dataPermission02);\n    }\n\n    @Test\n    public void testPush() {\n        // 调用\n        DataPermission dataPermission01 = mock(DataPermission.class);\n        DataPermissionContextHolder.add(dataPermission01);\n        DataPermission dataPermission02 = mock(DataPermission.class);\n        DataPermissionContextHolder.add(dataPermission02);\n        // 断言\n        DataPermission first = DataPermissionContextHolder.getAll().get(0);\n        DataPermission second = DataPermissionContextHolder.getAll().get(1);\n        assertSame(dataPermission01, first);\n        assertSame(dataPermission02, second);\n    }\n\n    @Test\n    public void testRemove() {\n        // mock 方法\n        DataPermission dataPermission01 = mock(DataPermission.class);\n        DataPermissionContextHolder.add(dataPermission01);\n        DataPermission dataPermission02 = mock(DataPermission.class);\n        DataPermissionContextHolder.add(dataPermission02);\n\n        // 调用\n        DataPermission result = DataPermissionContextHolder.remove();\n        // 断言\n        assertSame(result, dataPermission02);\n        assertEquals(1, DataPermissionContextHolder.getAll().size());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-data-permission/src/test/java/co/yixiang/yshop/framework/datapermission/core/db/DataPermissionDatabaseInterceptorTest.java",
    "content": "package co.yixiang.yshop.framework.datapermission.core.db;\n\nimport co.yixiang.yshop.framework.common.util.collection.SetUtils;\nimport co.yixiang.yshop.framework.datapermission.core.rule.DataPermissionRule;\nimport co.yixiang.yshop.framework.datapermission.core.rule.DataPermissionRuleFactory;\nimport co.yixiang.yshop.framework.mybatis.core.util.MyBatisUtils;\nimport co.yixiang.yshop.framework.test.core.ut.BaseMockitoUnitTest;\nimport com.baomidou.mybatisplus.core.toolkit.PluginUtils;\nimport net.sf.jsqlparser.expression.Alias;\nimport net.sf.jsqlparser.expression.Expression;\nimport net.sf.jsqlparser.expression.LongValue;\nimport net.sf.jsqlparser.expression.operators.relational.EqualsTo;\nimport net.sf.jsqlparser.schema.Column;\nimport org.apache.ibatis.executor.Executor;\nimport org.apache.ibatis.executor.statement.StatementHandler;\nimport org.apache.ibatis.mapping.BoundSql;\nimport org.apache.ibatis.mapping.MappedStatement;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\nimport org.mockito.MockedStatic;\n\nimport java.sql.Connection;\nimport java.util.*;\n\nimport static java.util.Collections.singletonList;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.*;\n\n/**\n * {@link DataPermissionDatabaseInterceptor} 的单元测试\n * 主要测试 {@link DataPermissionDatabaseInterceptor#beforePrepare(StatementHandler, Connection, Integer)}\n * 和 {@link DataPermissionDatabaseInterceptor#beforeUpdate(Executor, MappedStatement, Object)}\n * 以及在这个过程中，ContextHolder 和 MappedStatementCache\n *\n * @author yshop\n */\npublic class DataPermissionDatabaseInterceptorTest extends BaseMockitoUnitTest {\n\n    @InjectMocks\n    private DataPermissionDatabaseInterceptor interceptor;\n\n    @Mock\n    private DataPermissionRuleFactory ruleFactory;\n\n    @BeforeEach\n    public void setUp() {\n        // 清理上下文\n        DataPermissionDatabaseInterceptor.ContextHolder.clear();\n        // 清空缓存\n        interceptor.getMappedStatementCache().clear();\n    }\n\n    @Test // 不存在规则，且不匹配\n    public void testBeforeQuery_withoutRule() {\n        try (MockedStatic<PluginUtils> pluginUtilsMock = mockStatic(PluginUtils.class)) {\n            // 准备参数\n            MappedStatement mappedStatement = mock(MappedStatement.class);\n            BoundSql boundSql = mock(BoundSql.class);\n\n            // 调用\n            interceptor.beforeQuery(null, mappedStatement, null, null, null, boundSql);\n            // 断言\n            pluginUtilsMock.verify(() -> PluginUtils.mpBoundSql(boundSql), never());\n        }\n    }\n\n    @Test // 存在规则，且不匹配\n    public void testBeforeQuery_withMatchRule() {\n        try (MockedStatic<PluginUtils> pluginUtilsMock = mockStatic(PluginUtils.class)) {\n            // 准备参数\n            MappedStatement mappedStatement = mock(MappedStatement.class);\n            BoundSql boundSql = mock(BoundSql.class);\n            // mock 方法(数据权限)\n            when(ruleFactory.getDataPermissionRule(same(mappedStatement.getId())))\n                    .thenReturn(singletonList(new DeptDataPermissionRule()));\n            // mock 方法(MPBoundSql)\n            PluginUtils.MPBoundSql mpBs = mock(PluginUtils.MPBoundSql.class);\n            pluginUtilsMock.when(() -> PluginUtils.mpBoundSql(same(boundSql))).thenReturn(mpBs);\n            // mock 方法(SQL)\n            String sql = \"select * from t_user where id = 1\";\n            when(mpBs.sql()).thenReturn(sql);\n            // 针对 ContextHolder 和 MappedStatementCache 暂时不 mock，主要想校验过程中，数据是否正确\n\n            // 调用\n            interceptor.beforeQuery(null, mappedStatement, null, null, null, boundSql);\n            // 断言\n            verify(mpBs, times(1)).sql(\n                    eq(\"SELECT * FROM t_user WHERE id = 1 AND t_user.dept_id = 100\"));\n            // 断言缓存\n            assertTrue(interceptor.getMappedStatementCache().getNoRewritableMappedStatements().isEmpty());\n        }\n    }\n\n    @Test // 存在规则，但不匹配\n    public void testBeforeQuery_withoutMatchRule() {\n        try (MockedStatic<PluginUtils> pluginUtilsMock = mockStatic(PluginUtils.class)) {\n            // 准备参数\n            MappedStatement mappedStatement = mock(MappedStatement.class);\n            BoundSql boundSql = mock(BoundSql.class);\n            // mock 方法(数据权限)\n            when(ruleFactory.getDataPermissionRule(same(mappedStatement.getId())))\n                    .thenReturn(singletonList(new DeptDataPermissionRule()));\n            // mock 方法(MPBoundSql)\n            PluginUtils.MPBoundSql mpBs = mock(PluginUtils.MPBoundSql.class);\n            pluginUtilsMock.when(() -> PluginUtils.mpBoundSql(same(boundSql))).thenReturn(mpBs);\n            // mock 方法(SQL)\n            String sql = \"select * from t_role where id = 1\";\n            when(mpBs.sql()).thenReturn(sql);\n            // 针对 ContextHolder 和 MappedStatementCache 暂时不 mock，主要想校验过程中，数据是否正确\n\n            // 调用\n            interceptor.beforeQuery(null, mappedStatement, null, null, null, boundSql);\n            // 断言\n            verify(mpBs, times(1)).sql(\n                    eq(\"SELECT * FROM t_role WHERE id = 1\"));\n            // 断言缓存\n            assertFalse(interceptor.getMappedStatementCache().getNoRewritableMappedStatements().isEmpty());\n        }\n    }\n\n    @Test\n    public void testAddNoRewritable() {\n        // 准备参数\n        MappedStatement ms = mock(MappedStatement.class);\n        List<DataPermissionRule> rules = singletonList(new DeptDataPermissionRule());\n        // mock 方法\n        when(ms.getId()).thenReturn(\"selectById\");\n\n        // 调用\n        interceptor.getMappedStatementCache().addNoRewritable(ms, rules);\n        // 断言\n        Map<Class<? extends DataPermissionRule>, Set<String>> noRewritableMappedStatements =\n                interceptor.getMappedStatementCache().getNoRewritableMappedStatements();\n        assertEquals(1, noRewritableMappedStatements.size());\n        assertEquals(SetUtils.asSet(\"selectById\"), noRewritableMappedStatements.get(DeptDataPermissionRule.class));\n    }\n\n    @Test\n    public void testNoRewritable() {\n        // 准备参数\n        MappedStatement ms = mock(MappedStatement.class);\n        // mock 方法\n        when(ms.getId()).thenReturn(\"selectById\");\n        // mock 数据\n        List<DataPermissionRule> rules = singletonList(new DeptDataPermissionRule());\n        interceptor.getMappedStatementCache().addNoRewritable(ms, rules);\n\n        // 场景一，rules 为空\n        assertTrue(interceptor.getMappedStatementCache().noRewritable(ms, null));\n        // 场景二，rules 非空，可重写\n        assertFalse(interceptor.getMappedStatementCache().noRewritable(ms, singletonList(new EmptyDataPermissionRule())));\n        // 场景三，rule 非空，不可重写\n        assertTrue(interceptor.getMappedStatementCache().noRewritable(ms, rules));\n    }\n\n    private static class DeptDataPermissionRule implements DataPermissionRule {\n\n        private static final String COLUMN = \"dept_id\";\n\n        @Override\n        public Set<String> getTableNames() {\n            return SetUtils.asSet(\"t_user\");\n        }\n\n        @Override\n        public Expression getExpression(String tableName, Alias tableAlias) {\n            Column column = MyBatisUtils.buildColumn(tableName, tableAlias, COLUMN);\n            LongValue value = new LongValue(100L);\n            return new EqualsTo(column, value);\n        }\n\n    }\n\n    private static class EmptyDataPermissionRule implements DataPermissionRule {\n\n        @Override\n        public Set<String> getTableNames() {\n            return Collections.emptySet();\n        }\n\n        @Override\n        public Expression getExpression(String tableName, Alias tableAlias) {\n            return null;\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-data-permission/src/test/java/co/yixiang/yshop/framework/datapermission/core/db/DataPermissionDatabaseInterceptorTest2.java",
    "content": "package co.yixiang.yshop.framework.datapermission.core.db;\n\nimport co.yixiang.yshop.framework.datapermission.core.rule.DataPermissionRule;\nimport co.yixiang.yshop.framework.datapermission.core.rule.DataPermissionRuleFactory;\nimport co.yixiang.yshop.framework.mybatis.core.util.MyBatisUtils;\nimport co.yixiang.yshop.framework.test.core.ut.BaseMockitoUnitTest;\nimport net.sf.jsqlparser.expression.Alias;\nimport net.sf.jsqlparser.expression.Expression;\nimport net.sf.jsqlparser.expression.LongValue;\nimport net.sf.jsqlparser.expression.operators.relational.EqualsTo;\nimport net.sf.jsqlparser.expression.operators.relational.ExpressionList;\nimport net.sf.jsqlparser.expression.operators.relational.InExpression;\nimport net.sf.jsqlparser.schema.Column;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\n\nimport java.util.Arrays;\nimport java.util.Set;\n\nimport static co.yixiang.yshop.framework.common.util.collection.SetUtils.asSet;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n/**\n * {@link DataPermissionDatabaseInterceptor} 的单元测试\n * 主要复用了 MyBatis Plus 的 TenantLineInnerInterceptorTest 的单元测试\n * 不过它的单元测试不是很规范，考虑到是复用的，所以暂时不进行修改~\n *\n * @author yshop\n */\npublic class DataPermissionDatabaseInterceptorTest2 extends BaseMockitoUnitTest {\n\n    @InjectMocks\n    private DataPermissionDatabaseInterceptor interceptor;\n\n    @Mock\n    private DataPermissionRuleFactory ruleFactory;\n\n    @BeforeEach\n    public void setUp() {\n        // 租户的数据权限规则\n        DataPermissionRule tenantRule = new DataPermissionRule() {\n\n            private static final String COLUMN = \"tenant_id\";\n\n            @Override\n            public Set<String> getTableNames() {\n                return asSet(\"entity\", \"entity1\", \"entity2\", \"entity3\", \"t1\", \"t2\", \"sys_dict_item\", // 支持 MyBatis Plus 的单元测试\n                        \"t_user\", \"t_role\"); // 满足自己的单元测试\n            }\n\n            @Override\n            public Expression getExpression(String tableName, Alias tableAlias) {\n                Column column = MyBatisUtils.buildColumn(tableName, tableAlias, COLUMN);\n                LongValue value = new LongValue(1L);\n                return new EqualsTo(column, value);\n            }\n\n        };\n        // 部门的数据权限规则\n        DataPermissionRule deptRule = new DataPermissionRule() {\n\n            private static final String COLUMN = \"dept_id\";\n\n            @Override\n            public Set<String> getTableNames() {\n                return asSet(\"t_user\");  // 满足自己的单元测试\n            }\n\n            @Override\n            public Expression getExpression(String tableName, Alias tableAlias) {\n                Column column = MyBatisUtils.buildColumn(tableName, tableAlias, COLUMN);\n                ExpressionList values = new ExpressionList(new LongValue(10L),\n                        new LongValue(20L));\n                return new InExpression(column, values);\n            }\n\n        };\n        // 设置到上下文，保证\n        DataPermissionDatabaseInterceptor.ContextHolder.init(Arrays.asList(tenantRule, deptRule));\n    }\n\n    @Test\n    void delete() {\n        assertSql(\"delete from entity where id = ?\",\n                \"DELETE FROM entity WHERE id = ? AND entity.tenant_id = 1\");\n    }\n\n    @Test\n    void update() {\n        assertSql(\"update entity set name = ? where id = ?\",\n                \"UPDATE entity SET name = ? WHERE id = ? AND entity.tenant_id = 1\");\n    }\n\n    @Test\n    void selectSingle() {\n        // 单表\n        assertSql(\"select * from entity where id = ?\",\n                \"SELECT * FROM entity WHERE id = ? AND entity.tenant_id = 1\");\n\n        assertSql(\"select * from entity where id = ? or name = ?\",\n                \"SELECT * FROM entity WHERE (id = ? OR name = ?) AND entity.tenant_id = 1\");\n\n        assertSql(\"SELECT * FROM entity WHERE (id = ? OR name = ?)\",\n                \"SELECT * FROM entity WHERE (id = ? OR name = ?) AND entity.tenant_id = 1\");\n\n        /* not */\n        assertSql(\"SELECT * FROM entity WHERE not (id = ? OR name = ?)\",\n                \"SELECT * FROM entity WHERE NOT (id = ? OR name = ?) AND entity.tenant_id = 1\");\n    }\n\n    @Test\n    void selectSubSelectIn() {\n        /* in */\n        assertSql(\"SELECT * FROM entity e WHERE e.id IN (select e1.id from entity1 e1 where e1.id = ?)\",\n                \"SELECT * FROM entity e WHERE e.id IN (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1\");\n        // 在最前\n        assertSql(\"SELECT * FROM entity e WHERE e.id IN \" +\n                        \"(select e1.id from entity1 e1 where e1.id = ?) and e.id = ?\",\n                \"SELECT * FROM entity e WHERE e.id IN \" +\n                        \"(SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.id = ? AND e.tenant_id = 1\");\n        // 在最后\n        assertSql(\"SELECT * FROM entity e WHERE e.id = ? and e.id IN \" +\n                        \"(select e1.id from entity1 e1 where e1.id = ?)\",\n                \"SELECT * FROM entity e WHERE e.id = ? AND e.id IN \" +\n                        \"(SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1\");\n        // 在中间\n        assertSql(\"SELECT * FROM entity e WHERE e.id = ? and e.id IN \" +\n                        \"(select e1.id from entity1 e1 where e1.id = ?) and e.id = ?\",\n                \"SELECT * FROM entity e WHERE e.id = ? AND e.id IN \" +\n                        \"(SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.id = ? AND e.tenant_id = 1\");\n    }\n\n    @Test\n    void selectSubSelectEq() {\n        /* = */\n        assertSql(\"SELECT * FROM entity e WHERE e.id = (select e1.id from entity1 e1 where e1.id = ?)\",\n                \"SELECT * FROM entity e WHERE e.id = (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1\");\n    }\n\n    @Test\n    void selectSubSelectInnerNotEq() {\n        /* inner not = */\n        assertSql(\"SELECT * FROM entity e WHERE not (e.id = (select e1.id from entity1 e1 where e1.id = ?))\",\n                \"SELECT * FROM entity e WHERE NOT (e.id = (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1)) AND e.tenant_id = 1\");\n\n        assertSql(\"SELECT * FROM entity e WHERE not (e.id = (select e1.id from entity1 e1 where e1.id = ?) and e.id = ?)\",\n                \"SELECT * FROM entity e WHERE NOT (e.id = (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.id = ?) AND e.tenant_id = 1\");\n    }\n\n    @Test\n    void selectSubSelectExists() {\n        /* EXISTS */\n        assertSql(\"SELECT * FROM entity e WHERE EXISTS (select e1.id from entity1 e1 where e1.id = ?)\",\n                \"SELECT * FROM entity e WHERE EXISTS (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1\");\n\n\n        /* NOT EXISTS */\n        assertSql(\"SELECT * FROM entity e WHERE NOT EXISTS (select e1.id from entity1 e1 where e1.id = ?)\",\n                \"SELECT * FROM entity e WHERE NOT EXISTS (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1\");\n    }\n\n    @Test\n    void selectSubSelect() {\n        /* >= */\n        assertSql(\"SELECT * FROM entity e WHERE e.id >= (select e1.id from entity1 e1 where e1.id = ?)\",\n                \"SELECT * FROM entity e WHERE e.id >= (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1\");\n\n\n        /* <= */\n        assertSql(\"SELECT * FROM entity e WHERE e.id <= (select e1.id from entity1 e1 where e1.id = ?)\",\n                \"SELECT * FROM entity e WHERE e.id <= (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1\");\n\n\n        /* <> */\n        assertSql(\"SELECT * FROM entity e WHERE e.id <> (select e1.id from entity1 e1 where e1.id = ?)\",\n                \"SELECT * FROM entity e WHERE e.id <> (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1\");\n    }\n\n    @Test\n    void selectFromSelect() {\n        assertSql(\"SELECT * FROM (select e.id from entity e WHERE e.id = (select e1.id from entity1 e1 where e1.id = ?))\",\n                \"SELECT * FROM (SELECT e.id FROM entity e WHERE e.id = (SELECT e1.id FROM entity1 e1 WHERE e1.id = ? AND e1.tenant_id = 1) AND e.tenant_id = 1)\");\n    }\n\n    @Test\n    void selectBodySubSelect() {\n        assertSql(\"select t1.col1,(select t2.col2 from t2 t2 where t1.col1=t2.col1) from t1 t1\",\n                \"SELECT t1.col1, (SELECT t2.col2 FROM t2 t2 WHERE t1.col1 = t2.col1 AND t2.tenant_id = 1) FROM t1 t1 WHERE t1.tenant_id = 1\");\n    }\n\n    @Test\n    void selectLeftJoin() {\n        // left join\n        assertSql(\"SELECT * FROM entity e \" +\n                        \"left join entity1 e1 on e1.id = e.id \" +\n                        \"WHERE e.id = ? OR e.name = ?\",\n                \"SELECT * FROM entity e \" +\n                        \"LEFT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 \" +\n                        \"WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1\");\n\n        assertSql(\"SELECT * FROM entity e \" +\n                        \"left join entity1 e1 on e1.id = e.id \" +\n                        \"WHERE (e.id = ? OR e.name = ?)\",\n                \"SELECT * FROM entity e \" +\n                        \"LEFT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 \" +\n                        \"WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1\");\n\n        assertSql(\"SELECT * FROM entity e \" +\n                        \"left join entity1 e1 on e1.id = e.id \" +\n                        \"left join entity2 e2 on e1.id = e2.id\",\n                \"SELECT * FROM entity e \" +\n                        \"LEFT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 \" +\n                        \"LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1 \" +\n                        \"WHERE e.tenant_id = 1\");\n    }\n\n    @Test\n    void selectRightJoin() {\n        // right join\n        assertSql(\"SELECT * FROM entity e \" +\n                        \"right join entity1 e1 on e1.id = e.id\",\n                \"SELECT * FROM entity e \" +\n                        \"RIGHT JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 \" +\n                        \"WHERE e1.tenant_id = 1\");\n\n        assertSql(\"SELECT * FROM with_as_1 e \" +\n                        \"right join entity1 e1 on e1.id = e.id\",\n                \"SELECT * FROM with_as_1 e \" +\n                        \"RIGHT JOIN entity1 e1 ON e1.id = e.id \" +\n                        \"WHERE e1.tenant_id = 1\");\n\n        assertSql(\"SELECT * FROM entity e \" +\n                        \"right join entity1 e1 on e1.id = e.id \" +\n                        \"WHERE e.id = ? OR e.name = ?\",\n                \"SELECT * FROM entity e \" +\n                        \"RIGHT JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 \" +\n                        \"WHERE (e.id = ? OR e.name = ?) AND e1.tenant_id = 1\");\n\n        assertSql(\"SELECT * FROM entity e \" +\n                        \"right join entity1 e1 on e1.id = e.id \" +\n                        \"right join entity2 e2 on e1.id = e2.id \",\n                \"SELECT * FROM entity e \" +\n                        \"RIGHT JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 \" +\n                        \"RIGHT JOIN entity2 e2 ON e1.id = e2.id AND e1.tenant_id = 1 \" +\n                        \"WHERE e2.tenant_id = 1\");\n    }\n\n    @Test\n    void selectMixJoin() {\n        assertSql(\"SELECT * FROM entity e \" +\n                        \"right join entity1 e1 on e1.id = e.id \" +\n                        \"left join entity2 e2 on e1.id = e2.id\",\n                \"SELECT * FROM entity e \" +\n                        \"RIGHT JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 \" +\n                        \"LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1 \" +\n                        \"WHERE e1.tenant_id = 1\");\n\n        assertSql(\"SELECT * FROM entity e \" +\n                        \"left join entity1 e1 on e1.id = e.id \" +\n                        \"right join entity2 e2 on e1.id = e2.id\",\n                \"SELECT * FROM entity e \" +\n                        \"LEFT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 \" +\n                        \"RIGHT JOIN entity2 e2 ON e1.id = e2.id AND e1.tenant_id = 1 \" +\n                        \"WHERE e2.tenant_id = 1\");\n\n        assertSql(\"SELECT * FROM entity e \" +\n                        \"left join entity1 e1 on e1.id = e.id \" +\n                        \"inner join entity2 e2 on e1.id = e2.id\",\n                \"SELECT * FROM entity e \" +\n                        \"LEFT JOIN entity1 e1 ON e1.id = e.id AND e1.tenant_id = 1 \" +\n                        \"INNER JOIN entity2 e2 ON e1.id = e2.id AND e.tenant_id = 1 AND e2.tenant_id = 1\");\n    }\n\n\n    @Test\n    void selectJoinSubSelect() {\n        assertSql(\"select * from (select * from entity) e1 \" +\n                        \"left join entity2 e2 on e1.id = e2.id\",\n                \"SELECT * FROM (SELECT * FROM entity WHERE entity.tenant_id = 1) e1 \" +\n                        \"LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1\");\n\n        assertSql(\"select * from entity1 e1 \" +\n                        \"left join (select * from entity2) e2 \" +\n                        \"on e1.id = e2.id\",\n                \"SELECT * FROM entity1 e1 \" +\n                        \"LEFT JOIN (SELECT * FROM entity2 WHERE entity2.tenant_id = 1) e2 \" +\n                        \"ON e1.id = e2.id \" +\n                        \"WHERE e1.tenant_id = 1\");\n    }\n\n    @Test\n    void selectSubJoin() {\n\n        assertSql(\"select * FROM \" +\n                        \"(entity1 e1 right JOIN entity2 e2 ON e1.id = e2.id)\",\n                \"SELECT * FROM \" +\n                        \"(entity1 e1 RIGHT JOIN entity2 e2 ON e1.id = e2.id AND e1.tenant_id = 1) \" +\n                        \"WHERE e2.tenant_id = 1\");\n\n        assertSql(\"select * FROM \" +\n                        \"(entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id)\",\n                \"SELECT * FROM \" +\n                        \"(entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1) \" +\n                        \"WHERE e1.tenant_id = 1\");\n\n\n        assertSql(\"select * FROM \" +\n                        \"(entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id) \" +\n                        \"right join entity3 e3 on e1.id = e3.id\",\n                \"SELECT * FROM \" +\n                        \"(entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1) \" +\n                        \"RIGHT JOIN entity3 e3 ON e1.id = e3.id AND e1.tenant_id = 1 \" +\n                        \"WHERE e3.tenant_id = 1\");\n\n\n        assertSql(\"select * FROM entity e \" +\n                        \"LEFT JOIN (entity1 e1 right join entity2 e2 ON e1.id = e2.id) \" +\n                        \"on e.id = e2.id\",\n                \"SELECT * FROM entity e \" +\n                        \"LEFT JOIN (entity1 e1 RIGHT JOIN entity2 e2 ON e1.id = e2.id AND e1.tenant_id = 1) \" +\n                        \"ON e.id = e2.id AND e2.tenant_id = 1 \" +\n                        \"WHERE e.tenant_id = 1\");\n\n        assertSql(\"select * FROM entity e \" +\n                        \"LEFT JOIN (entity1 e1 left join entity2 e2 ON e1.id = e2.id) \" +\n                        \"on e.id = e2.id\",\n                \"SELECT * FROM entity e \" +\n                        \"LEFT JOIN (entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1) \" +\n                        \"ON e.id = e2.id AND e1.tenant_id = 1 \" +\n                        \"WHERE e.tenant_id = 1\");\n\n        assertSql(\"select * FROM entity e \" +\n                        \"RIGHT JOIN (entity1 e1 left join entity2 e2 ON e1.id = e2.id) \" +\n                        \"on e.id = e2.id\",\n                \"SELECT * FROM entity e \" +\n                        \"RIGHT JOIN (entity1 e1 LEFT JOIN entity2 e2 ON e1.id = e2.id AND e2.tenant_id = 1) \" +\n                        \"ON e.id = e2.id AND e.tenant_id = 1 \" +\n                        \"WHERE e1.tenant_id = 1\");\n    }\n\n\n    @Test\n    void selectLeftJoinMultipleTrailingOn() {\n        // 多个 on 尾缀的\n        assertSql(\"SELECT * FROM entity e \" +\n                        \"LEFT JOIN entity1 e1 \" +\n                        \"LEFT JOIN entity2 e2 ON e2.id = e1.id \" +\n                        \"ON e1.id = e.id \" +\n                        \"WHERE (e.id = ? OR e.NAME = ?)\",\n                \"SELECT * FROM entity e \" +\n                        \"LEFT JOIN entity1 e1 \" +\n                        \"LEFT JOIN entity2 e2 ON e2.id = e1.id AND e2.tenant_id = 1 \" +\n                        \"ON e1.id = e.id AND e1.tenant_id = 1 \" +\n                        \"WHERE (e.id = ? OR e.NAME = ?) AND e.tenant_id = 1\");\n\n        assertSql(\"SELECT * FROM entity e \" +\n                        \"LEFT JOIN entity1 e1 \" +\n                        \"LEFT JOIN with_as_A e2 ON e2.id = e1.id \" +\n                        \"ON e1.id = e.id \" +\n                        \"WHERE (e.id = ? OR e.NAME = ?)\",\n                \"SELECT * FROM entity e \" +\n                        \"LEFT JOIN entity1 e1 \" +\n                        \"LEFT JOIN with_as_A e2 ON e2.id = e1.id \" +\n                        \"ON e1.id = e.id AND e1.tenant_id = 1 \" +\n                        \"WHERE (e.id = ? OR e.NAME = ?) AND e.tenant_id = 1\");\n    }\n\n    @Test\n    void selectInnerJoin() {\n        // inner join\n        assertSql(\"SELECT * FROM entity e \" +\n                        \"inner join entity1 e1 on e1.id = e.id \" +\n                        \"WHERE e.id = ? OR e.name = ?\",\n                \"SELECT * FROM entity e \" +\n                        \"INNER JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 AND e1.tenant_id = 1 \" +\n                        \"WHERE e.id = ? OR e.name = ?\");\n\n        assertSql(\"SELECT * FROM entity e \" +\n                        \"inner join entity1 e1 on e1.id = e.id \" +\n                        \"WHERE (e.id = ? OR e.name = ?)\",\n                \"SELECT * FROM entity e \" +\n                        \"INNER JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 AND e1.tenant_id = 1 \" +\n                        \"WHERE (e.id = ? OR e.name = ?)\");\n\n        // 隐式内连接\n        assertSql(\"SELECT * FROM entity,entity1 \" +\n                        \"WHERE entity.id = entity1.id\",\n                \"SELECT * FROM entity, entity1 \" +\n                        \"WHERE entity.id = entity1.id AND entity.tenant_id = 1 AND entity1.tenant_id = 1\");\n\n        // 隐式内连接\n        assertSql(\"SELECT * FROM entity a, with_as_entity1 b \" +\n                        \"WHERE a.id = b.id\",\n                \"SELECT * FROM entity a, with_as_entity1 b \" +\n                        \"WHERE a.id = b.id AND a.tenant_id = 1\");\n\n        assertSql(\"SELECT * FROM with_as_entity a, with_as_entity1 b \" +\n                        \"WHERE a.id = b.id\",\n                \"SELECT * FROM with_as_entity a, with_as_entity1 b \" +\n                        \"WHERE a.id = b.id\");\n\n        // SubJoin with 隐式内连接\n        assertSql(\"SELECT * FROM (entity,entity1) \" +\n                        \"WHERE entity.id = entity1.id\",\n                \"SELECT * FROM (entity, entity1) \" +\n                        \"WHERE entity.id = entity1.id \" +\n                        \"AND entity.tenant_id = 1 AND entity1.tenant_id = 1\");\n\n        assertSql(\"SELECT * FROM ((entity,entity1),entity2) \" +\n                        \"WHERE entity.id = entity1.id and entity.id = entity2.id\",\n                \"SELECT * FROM ((entity, entity1), entity2) \" +\n                        \"WHERE entity.id = entity1.id AND entity.id = entity2.id \" +\n                        \"AND entity.tenant_id = 1 AND entity1.tenant_id = 1 AND entity2.tenant_id = 1\");\n\n        assertSql(\"SELECT * FROM (entity,(entity1,entity2)) \" +\n                        \"WHERE entity.id = entity1.id and entity.id = entity2.id\",\n                \"SELECT * FROM (entity, (entity1, entity2)) \" +\n                        \"WHERE entity.id = entity1.id AND entity.id = entity2.id \" +\n                        \"AND entity.tenant_id = 1 AND entity1.tenant_id = 1 AND entity2.tenant_id = 1\");\n\n        // 沙雕的括号写法\n        assertSql(\"SELECT * FROM (((entity,entity1))) \" +\n                        \"WHERE entity.id = entity1.id\",\n                \"SELECT * FROM (((entity, entity1))) \" +\n                        \"WHERE entity.id = entity1.id \" +\n                        \"AND entity.tenant_id = 1 AND entity1.tenant_id = 1\");\n\n    }\n\n\n    @Test\n    void selectWithAs() {\n        assertSql(\"with with_as_A as (select * from entity) select * from with_as_A\",\n                \"WITH with_as_A AS (SELECT * FROM entity WHERE entity.tenant_id = 1) SELECT * FROM with_as_A\");\n    }\n\n\n    @Test\n    void selectIgnoreTable() {\n        assertSql(\" SELECT dict.dict_code, item.item_text AS \\\"text\\\", item.item_value AS \\\"value\\\" FROM sys_dict_item item INNER JOIN sys_dict dict ON dict.id = item.dict_id WHERE dict.dict_code IN (1, 2, 3) AND item.item_value IN (1, 2, 3)\",\n                \"SELECT dict.dict_code, item.item_text AS \\\"text\\\", item.item_value AS \\\"value\\\" FROM sys_dict_item item INNER JOIN sys_dict dict ON dict.id = item.dict_id AND item.tenant_id = 1 WHERE dict.dict_code IN (1, 2, 3) AND item.item_value IN (1, 2, 3)\");\n    }\n\n    private void assertSql(String sql, String targetSql) {\n        assertEquals(targetSql, interceptor.parserSingle(sql, null));\n    }\n\n\n    // ========== 额外的测试 ==========\n\n    @Test\n    public void testSelectSingle() {\n        // 单表\n        assertSql(\"select * from t_user where id = ?\",\n                \"SELECT * FROM t_user WHERE id = ? AND t_user.tenant_id = 1 AND t_user.dept_id IN (10, 20)\");\n\n        assertSql(\"select * from t_user where id = ? or name = ?\",\n                \"SELECT * FROM t_user WHERE (id = ? OR name = ?) AND t_user.tenant_id = 1 AND t_user.dept_id IN (10, 20)\");\n\n        assertSql(\"SELECT * FROM t_user WHERE (id = ? OR name = ?)\",\n                \"SELECT * FROM t_user WHERE (id = ? OR name = ?) AND t_user.tenant_id = 1 AND t_user.dept_id IN (10, 20)\");\n\n        /* not */\n        assertSql(\"SELECT * FROM t_user WHERE not (id = ? OR name = ?)\",\n                \"SELECT * FROM t_user WHERE NOT (id = ? OR name = ?) AND t_user.tenant_id = 1 AND t_user.dept_id IN (10, 20)\");\n    }\n\n    @Test\n    public void testSelectLeftJoin() {\n        // left join\n        assertSql(\"SELECT * FROM t_user e \" +\n                        \"left join t_role e1 on e1.id = e.id \" +\n                        \"WHERE e.id = ? OR e.name = ?\",\n                \"SELECT * FROM t_user e \" +\n                        \"LEFT JOIN t_role e1 ON e1.id = e.id AND e1.tenant_id = 1 \" +\n                        \"WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1 AND e.dept_id IN (10, 20)\");\n\n        // 条件 e.id = ? OR e.name = ? 带括号\n        assertSql(\"SELECT * FROM t_user e \" +\n                        \"left join t_role e1 on e1.id = e.id \" +\n                        \"WHERE (e.id = ? OR e.name = ?)\",\n                \"SELECT * FROM t_user e \" +\n                        \"LEFT JOIN t_role e1 ON e1.id = e.id AND e1.tenant_id = 1 \" +\n                        \"WHERE (e.id = ? OR e.name = ?) AND e.tenant_id = 1 AND e.dept_id IN (10, 20)\");\n    }\n\n    @Test\n    public void testSelectRightJoin() {\n        // right join\n        assertSql(\"SELECT * FROM t_user e \" +\n                        \"right join t_role e1 on e1.id = e.id \" +\n                        \"WHERE e.id = ? OR e.name = ?\",\n                \"SELECT * FROM t_user e \" +\n                        \"RIGHT JOIN t_role e1 ON e1.id = e.id AND e.tenant_id = 1 AND e.dept_id IN (10, 20) \" +\n                        \"WHERE (e.id = ? OR e.name = ?) AND e1.tenant_id = 1\");\n\n        // 条件 e.id = ? OR e.name = ? 带括号\n        assertSql(\"SELECT * FROM t_user e \" +\n                        \"right join t_role e1 on e1.id = e.id \" +\n                        \"WHERE (e.id = ? OR e.name = ?)\",\n                \"SELECT * FROM t_user e \" +\n                        \"RIGHT JOIN t_role e1 ON e1.id = e.id AND e.tenant_id = 1 AND e.dept_id IN (10, 20) \" +\n                        \"WHERE (e.id = ? OR e.name = ?) AND e1.tenant_id = 1\");\n    }\n\n    @Test\n    public void testSelectInnerJoin() {\n        // inner join\n        assertSql(\"SELECT * FROM t_user e \" +\n                        \"inner join entity1 e1 on e1.id = e.id \" +\n                        \"WHERE e.id = ? OR e.name = ?\",\n                \"SELECT * FROM t_user e \" +\n                        \"INNER JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 AND e.dept_id IN (10, 20) AND e1.tenant_id = 1 \" +\n                        \"WHERE e.id = ? OR e.name = ?\");\n\n        // 条件 e.id = ? OR e.name = ? 带括号\n        assertSql(\"SELECT * FROM t_user e \" +\n                        \"inner join entity1 e1 on e1.id = e.id \" +\n                        \"WHERE (e.id = ? OR e.name = ?)\",\n                \"SELECT * FROM t_user e \" +\n                        \"INNER JOIN entity1 e1 ON e1.id = e.id AND e.tenant_id = 1 AND e.dept_id IN (10, 20) AND e1.tenant_id = 1 \" +\n                        \"WHERE (e.id = ? OR e.name = ?)\");\n\n        // 没有 On 的 inner join\n        assertSql(\"SELECT * FROM entity,entity1 \" +\n                \"WHERE entity.id = entity1.id\",\n            \"SELECT * FROM entity, entity1 \" +\n                    \"WHERE entity.id = entity1.id AND entity.tenant_id = 1 AND entity1.tenant_id = 1\");\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-data-permission/src/test/java/co/yixiang/yshop/framework/datapermission/core/rule/DataPermissionRuleFactoryImplTest.java",
    "content": "package co.yixiang.yshop.framework.datapermission.core.rule;\n\nimport co.yixiang.yshop.framework.datapermission.core.annotation.DataPermission;\nimport co.yixiang.yshop.framework.datapermission.core.aop.DataPermissionContextHolder;\nimport co.yixiang.yshop.framework.test.core.ut.BaseMockitoUnitTest;\nimport net.sf.jsqlparser.expression.Alias;\nimport net.sf.jsqlparser.expression.Expression;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.InjectMocks;\nimport org.mockito.Spy;\nimport org.springframework.core.annotation.AnnotationUtils;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Set;\n\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.randomString;\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * {@link DataPermissionRuleFactoryImpl} 单元测试\n *\n * @author yshop\n */\nclass DataPermissionRuleFactoryImplTest extends BaseMockitoUnitTest {\n\n    @InjectMocks\n    private DataPermissionRuleFactoryImpl dataPermissionRuleFactory;\n\n    @Spy\n    private List<DataPermissionRule> rules = Arrays.asList(new DataPermissionRule01(),\n            new DataPermissionRule02());\n\n    @BeforeEach\n    public void setUp() {\n        DataPermissionContextHolder.clear();\n    }\n\n    @Test\n    public void testGetDataPermissionRule_02() {\n        // 准备参数\n        String mappedStatementId = randomString();\n\n        // 调用\n        List<DataPermissionRule> result = dataPermissionRuleFactory.getDataPermissionRule(mappedStatementId);\n        // 断言\n        assertSame(rules, result);\n    }\n\n    @Test\n    public void testGetDataPermissionRule_03() {\n        // 准备参数\n        String mappedStatementId = randomString();\n        // mock 方法\n        DataPermissionContextHolder.add(AnnotationUtils.findAnnotation(TestClass03.class, DataPermission.class));\n\n        // 调用\n        List<DataPermissionRule> result = dataPermissionRuleFactory.getDataPermissionRule(mappedStatementId);\n        // 断言\n        assertTrue(result.isEmpty());\n    }\n\n    @Test\n    public void testGetDataPermissionRule_04() {\n        // 准备参数\n        String mappedStatementId = randomString();\n        // mock 方法\n        DataPermissionContextHolder.add(AnnotationUtils.findAnnotation(TestClass04.class, DataPermission.class));\n\n        // 调用\n        List<DataPermissionRule> result = dataPermissionRuleFactory.getDataPermissionRule(mappedStatementId);\n        // 断言\n        assertEquals(1, result.size());\n        assertEquals(DataPermissionRule01.class, result.get(0).getClass());\n    }\n\n    @Test\n    public void testGetDataPermissionRule_05() {\n        // 准备参数\n        String mappedStatementId = randomString();\n        // mock 方法\n        DataPermissionContextHolder.add(AnnotationUtils.findAnnotation(TestClass05.class, DataPermission.class));\n\n        // 调用\n        List<DataPermissionRule> result = dataPermissionRuleFactory.getDataPermissionRule(mappedStatementId);\n        // 断言\n        assertEquals(1, result.size());\n        assertEquals(DataPermissionRule02.class, result.get(0).getClass());\n    }\n\n    @Test\n    public void testGetDataPermissionRule_06() {\n        // 准备参数\n        String mappedStatementId = randomString();\n        // mock 方法\n        DataPermissionContextHolder.add(AnnotationUtils.findAnnotation(TestClass06.class, DataPermission.class));\n\n        // 调用\n        List<DataPermissionRule> result = dataPermissionRuleFactory.getDataPermissionRule(mappedStatementId);\n        // 断言\n        assertSame(rules, result);\n    }\n\n    @DataPermission(enable = false)\n    static class TestClass03 {}\n\n    @DataPermission(includeRules = DataPermissionRule01.class)\n    static class TestClass04 {}\n\n    @DataPermission(excludeRules = DataPermissionRule01.class)\n    static class TestClass05 {}\n\n    @DataPermission\n    static class TestClass06 {}\n\n    static class DataPermissionRule01 implements DataPermissionRule {\n\n        @Override\n        public Set<String> getTableNames() {\n            return null;\n        }\n\n        @Override\n        public Expression getExpression(String tableName, Alias tableAlias) {\n            return null;\n        }\n\n    }\n\n    static class DataPermissionRule02 implements DataPermissionRule {\n\n        @Override\n        public Set<String> getTableNames() {\n            return null;\n        }\n\n        @Override\n        public Expression getExpression(String tableName, Alias tableAlias) {\n            return null;\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-data-permission/src/test/java/co/yixiang/yshop/framework/datapermission/core/rule/dept/DeptDataPermissionRuleTest.java",
    "content": "package co.yixiang.yshop.framework.datapermission.core.rule.dept;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.util.ReflectUtil;\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.common.util.collection.SetUtils;\nimport co.yixiang.yshop.framework.security.core.LoginUser;\nimport co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils;\nimport co.yixiang.yshop.framework.test.core.ut.BaseMockitoUnitTest;\nimport co.yixiang.yshop.module.system.api.permission.PermissionApi;\nimport co.yixiang.yshop.module.system.api.permission.dto.DeptDataPermissionRespDTO;\nimport net.sf.jsqlparser.expression.Alias;\nimport net.sf.jsqlparser.expression.Expression;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\nimport org.mockito.MockedStatic;\n\nimport java.util.Map;\n\nimport static co.yixiang.yshop.framework.datapermission.core.rule.dept.DeptDataPermissionRule.EXPRESSION_NULL;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.randomPojo;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.randomString;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.ArgumentMatchers.same;\nimport static org.mockito.Mockito.mockStatic;\nimport static org.mockito.Mockito.when;\n\n/**\n * {@link DeptDataPermissionRule} 的单元测试\n *\n * @author yshop\n */\nclass DeptDataPermissionRuleTest extends BaseMockitoUnitTest {\n\n    @InjectMocks\n    private DeptDataPermissionRule rule;\n\n    @Mock\n    private PermissionApi permissionApi;\n\n    @BeforeEach\n    @SuppressWarnings(\"unchecked\")\n    public void setUp() {\n        // 清空 rule\n        rule.getTableNames().clear();\n        ((Map<String, String>) ReflectUtil.getFieldValue(rule, \"deptColumns\")).clear();\n        ((Map<String, String>) ReflectUtil.getFieldValue(rule, \"deptColumns\")).clear();\n    }\n\n    @Test // 无 LoginUser\n    public void testGetExpression_noLoginUser() {\n        // 准备参数\n        String tableName = randomString();\n        Alias tableAlias = new Alias(randomString());\n        // mock 方法\n\n        // 调用\n        Expression expression = rule.getExpression(tableName, tableAlias);\n        // 断言\n        assertNull(expression);\n    }\n\n    @Test // 无数据权限时\n    public void testGetExpression_noDeptDataPermission() {\n        try (MockedStatic<SecurityFrameworkUtils> securityFrameworkUtilsMock\n                     = mockStatic(SecurityFrameworkUtils.class)) {\n            // 准备参数\n            String tableName = \"t_user\";\n            Alias tableAlias = new Alias(\"u\");\n            // mock 方法\n            LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)\n                    .setUserType(UserTypeEnum.ADMIN.getValue()));\n            securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);\n            // mock 方法（permissionApi 返回 null）\n            when(permissionApi.getDeptDataPermission(eq(loginUser.getId()))).thenReturn(null);\n\n            // 调用\n            NullPointerException exception = assertThrows(NullPointerException.class,\n                    () -> rule.getExpression(tableName, tableAlias));\n            // 断言\n            assertEquals(\"LoginUser(1) Table(t_user/u) 未返回数据权限\", exception.getMessage());\n        }\n    }\n\n    @Test // 全部数据权限\n    public void testGetExpression_allDeptDataPermission() {\n        try (MockedStatic<SecurityFrameworkUtils> securityFrameworkUtilsMock\n                     = mockStatic(SecurityFrameworkUtils.class)) {\n            // 准备参数\n            String tableName = \"t_user\";\n            Alias tableAlias = new Alias(\"u\");\n            // mock 方法（LoginUser）\n            LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)\n                    .setUserType(UserTypeEnum.ADMIN.getValue()));\n            securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);\n            // mock 方法（DeptDataPermissionRespDTO）\n            DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO().setAll(true);\n            when(permissionApi.getDeptDataPermission(same(1L))).thenReturn(deptDataPermission);\n\n            // 调用\n            Expression expression = rule.getExpression(tableName, tableAlias);\n            // 断言\n            assertNull(expression);\n            assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class));\n        }\n    }\n\n    @Test // 即不能查看部门，又不能查看自己，则说明 100% 无权限\n    public void testGetExpression_noDept_noSelf() {\n        try (MockedStatic<SecurityFrameworkUtils> securityFrameworkUtilsMock\n                     = mockStatic(SecurityFrameworkUtils.class)) {\n            // 准备参数\n            String tableName = \"t_user\";\n            Alias tableAlias = new Alias(\"u\");\n            // mock 方法（LoginUser）\n            LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)\n                    .setUserType(UserTypeEnum.ADMIN.getValue()));\n            securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);\n            // mock 方法（DeptDataPermissionRespDTO）\n            DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO();\n            when(permissionApi.getDeptDataPermission(same(1L))).thenReturn(deptDataPermission);\n\n            // 调用\n            Expression expression = rule.getExpression(tableName, tableAlias);\n            // 断言\n            assertEquals(\"null = null\", expression.toString());\n            assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class));\n        }\n    }\n\n    @Test // 拼接 Dept 和 User 的条件（字段都不符合）\n    public void testGetExpression_noDeptColumn_noSelfColumn() {\n        try (MockedStatic<SecurityFrameworkUtils> securityFrameworkUtilsMock\n                     = mockStatic(SecurityFrameworkUtils.class)) {\n            // 准备参数\n            String tableName = \"t_user\";\n            Alias tableAlias = new Alias(\"u\");\n            // mock 方法（LoginUser）\n            LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)\n                    .setUserType(UserTypeEnum.ADMIN.getValue()));\n            securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);\n            // mock 方法（DeptDataPermissionRespDTO）\n            DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO()\n                    .setDeptIds(SetUtils.asSet(10L, 20L)).setSelf(true);\n            when(permissionApi.getDeptDataPermission(same(1L))).thenReturn(deptDataPermission);\n\n            // 调用\n            Expression expression = rule.getExpression(tableName, tableAlias);\n            // 断言\n            assertSame(EXPRESSION_NULL, expression);\n            assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class));\n        }\n    }\n\n    @Test // 拼接 Dept 和 User 的条件（self 符合）\n    public void testGetExpression_noDeptColumn_yesSelfColumn() {\n        try (MockedStatic<SecurityFrameworkUtils> securityFrameworkUtilsMock\n                     = mockStatic(SecurityFrameworkUtils.class)) {\n            // 准备参数\n            String tableName = \"t_user\";\n            Alias tableAlias = new Alias(\"u\");\n            // mock 方法（LoginUser）\n            LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)\n                    .setUserType(UserTypeEnum.ADMIN.getValue()));\n            securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);\n            // mock 方法（DeptDataPermissionRespDTO）\n            DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO()\n                    .setSelf(true);\n            when(permissionApi.getDeptDataPermission(same(1L))).thenReturn(deptDataPermission);\n            // 添加 user 字段配置\n            rule.addUserColumn(\"t_user\", \"id\");\n\n            // 调用\n            Expression expression = rule.getExpression(tableName, tableAlias);\n            // 断言\n            assertEquals(\"u.id = 1\", expression.toString());\n            assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class));\n        }\n    }\n\n    @Test // 拼接 Dept 和 User 的条件（dept 符合）\n    public void testGetExpression_yesDeptColumn_noSelfColumn() {\n        try (MockedStatic<SecurityFrameworkUtils> securityFrameworkUtilsMock\n                     = mockStatic(SecurityFrameworkUtils.class)) {\n            // 准备参数\n            String tableName = \"t_user\";\n            Alias tableAlias = new Alias(\"u\");\n            // mock 方法（LoginUser）\n            LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)\n                    .setUserType(UserTypeEnum.ADMIN.getValue()));\n            securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);\n            // mock 方法（DeptDataPermissionRespDTO）\n            DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO()\n                    .setDeptIds(CollUtil.newLinkedHashSet(10L, 20L));\n            when(permissionApi.getDeptDataPermission(same(1L))).thenReturn(deptDataPermission);\n            // 添加 dept 字段配置\n            rule.addDeptColumn(\"t_user\", \"dept_id\");\n\n            // 调用\n            Expression expression = rule.getExpression(tableName, tableAlias);\n            // 断言\n            assertEquals(\"u.dept_id IN (10, 20)\", expression.toString());\n            assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class));\n        }\n    }\n\n    @Test // 拼接 Dept 和 User 的条件（dept + self 符合）\n    public void testGetExpression_yesDeptColumn_yesSelfColumn() {\n        try (MockedStatic<SecurityFrameworkUtils> securityFrameworkUtilsMock\n                     = mockStatic(SecurityFrameworkUtils.class)) {\n            // 准备参数\n            String tableName = \"t_user\";\n            Alias tableAlias = new Alias(\"u\");\n            // mock 方法（LoginUser）\n            LoginUser loginUser = randomPojo(LoginUser.class, o -> o.setId(1L)\n                    .setUserType(UserTypeEnum.ADMIN.getValue()));\n            securityFrameworkUtilsMock.when(SecurityFrameworkUtils::getLoginUser).thenReturn(loginUser);\n            // mock 方法（DeptDataPermissionRespDTO）\n            DeptDataPermissionRespDTO deptDataPermission = new DeptDataPermissionRespDTO()\n                    .setDeptIds(CollUtil.newLinkedHashSet(10L, 20L)).setSelf(true);\n            when(permissionApi.getDeptDataPermission(same(1L))).thenReturn(deptDataPermission);\n            // 添加 user 字段配置\n            rule.addUserColumn(\"t_user\", \"id\");\n            // 添加 dept 字段配置\n            rule.addDeptColumn(\"t_user\", \"dept_id\");\n\n            // 调用\n            Expression expression = rule.getExpression(tableName, tableAlias);\n            // 断言\n            assertEquals(\"(u.dept_id IN (10, 20) OR u.id = 1)\", expression.toString());\n            assertSame(deptDataPermission, loginUser.getContext(DeptDataPermissionRule.CONTEXT_KEY, DeptDataPermissionRespDTO.class));\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-data-permission/src/test/java/co/yixiang/yshop/framework/datapermission/core/util/DataPermissionUtilsTest.java",
    "content": "package co.yixiang.yshop.framework.datapermission.core.util;\n\nimport co.yixiang.yshop.framework.datapermission.core.aop.DataPermissionContextHolder;\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.*;\n\npublic class DataPermissionUtilsTest {\n\n    @Test\n    public void testExecuteIgnore() {\n        DataPermissionUtils.executeIgnore(() -> assertFalse(DataPermissionContextHolder.get().enable()));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-ip/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>yshop-framework</artifactId>\n        <groupId>co.yixiang.boot</groupId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>yshop-spring-boot-starter-biz-ip</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>IP 拓展，支持如下功能：\n        1. IP 功能：查询 IP 对应的城市信息\n            基于 https://gitee.com/lionsoul/ip2region 实现\n        2. 城市功能：查询城市编码对应的城市信息\n            基于 https://github.com/modood/Administrative-divisions-of-China 实现\n    </description>\n    <url>https://gitee.com/guchengwuyue/yshop-drink</url>\n\n    <dependencies>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-common</artifactId>\n        </dependency>\n\n        <!-- IP地址检索 -->\n        <dependency>\n            <groupId>org.lionsoul</groupId>\n            <artifactId>ip2region</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>org.projectlombok</groupId>\n            <artifactId>lombok</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>org.slf4j</groupId>\n            <artifactId>slf4j-api</artifactId>\n            <scope>provided</scope> <!-- 设置为 provided，只有工具类需要使用到 -->\n        </dependency>\n\n        <!-- Test 测试相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-ip/src/main/java/co/yixiang/yshop/framework/ip/core/Area.java",
    "content": "package co.yixiang.yshop.framework.ip.core;\n\nimport co.yixiang.yshop.framework.ip.core.enums.AreaTypeEnum;\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\nimport java.util.List;\n\n/**\n * 区域节点，包括国家、省份、城市、地区等信息\n *\n * 数据可见 resources/area.csv 文件\n *\n * @author yshop\n */\n@Data\n@AllArgsConstructor\n@NoArgsConstructor\npublic class Area {\n\n    /**\n     * 编号 - 全球，即根目录\n     */\n    public static final Integer ID_GLOBAL = 0;\n    /**\n     * 编号 - 中国\n     */\n    public static final Integer ID_CHINA = 1;\n\n    /**\n     * 编号\n     */\n    private Integer id;\n    /**\n     * 名字\n     */\n    private String name;\n    /**\n     * 类型\n     *\n     * 枚举 {@link AreaTypeEnum}\n     */\n    private Integer type;\n\n    /**\n     * 父节点\n     */\n    private Area parent;\n    /**\n     * 子节点\n     */\n    private List<Area> children;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-ip/src/main/java/co/yixiang/yshop/framework/ip/core/enums/AreaTypeEnum.java",
    "content": "package co.yixiang.yshop.framework.ip.core.enums;\n\nimport co.yixiang.yshop.framework.common.core.IntArrayValuable;\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\nimport java.util.Arrays;\n\n/**\n * 区域类型枚举\n *\n * @author yshop\n */\n@AllArgsConstructor\n@Getter\npublic enum AreaTypeEnum implements IntArrayValuable {\n\n    COUNTRY(1, \"国家\"),\n    PROVINCE(2, \"省份\"),\n    CITY(3, \"城市\"),\n    DISTRICT(4, \"地区\"), // 县、镇、区等\n    ;\n\n    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(AreaTypeEnum::getType).toArray();\n\n    /**\n     * 类型\n     */\n    private final Integer type;\n    /**\n     * 名字\n     */\n    private final String name;\n\n    @Override\n    public int[] array() {\n        return ARRAYS;\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-ip/src/main/java/co/yixiang/yshop/framework/ip/core/utils/AreaUtils.java",
    "content": "package co.yixiang.yshop.framework.ip.core.utils;\n\nimport cn.hutool.core.io.resource.ResourceUtil;\nimport cn.hutool.core.lang.Assert;\nimport cn.hutool.core.text.csv.CsvRow;\nimport cn.hutool.core.text.csv.CsvUtil;\nimport co.yixiang.yshop.framework.common.util.object.ObjectUtils;\nimport co.yixiang.yshop.framework.ip.core.Area;\nimport co.yixiang.yshop.framework.ip.core.enums.AreaTypeEnum;\nimport lombok.NonNull;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.function.Function;\n\nimport static co.yixiang.yshop.framework.common.util.collection.CollectionUtils.convertList;\nimport static co.yixiang.yshop.framework.common.util.collection.CollectionUtils.findFirst;\n\n/**\n * 区域工具类\n *\n * @author yshop\n */\n@Slf4j\npublic class AreaUtils {\n\n    /**\n     * 初始化 SEARCHER\n     */\n    @SuppressWarnings(\"InstantiationOfUtilityClass\")\n    private final static AreaUtils INSTANCE = new AreaUtils();\n\n    /**\n     * Area 内存缓存，提升访问速度\n     */\n    private static Map<Integer, Area> areas;\n\n    private AreaUtils() {\n        long now = System.currentTimeMillis();\n        areas = new HashMap<>();\n        areas.put(Area.ID_GLOBAL, new Area(Area.ID_GLOBAL, \"全球\", 0,\n                null, new ArrayList<>()));\n        // 从 csv 中加载数据\n        List<CsvRow> rows = CsvUtil.getReader().read(ResourceUtil.getUtf8Reader(\"area.csv\")).getRows();\n        rows.remove(0); // 删除 header\n        for (CsvRow row : rows) {\n            // 创建 Area 对象\n            Area area = new Area(Integer.valueOf(row.get(0)), row.get(1), Integer.valueOf(row.get(2)),\n                    null, new ArrayList<>());\n            // 添加到 areas 中\n            areas.put(area.getId(), area);\n        }\n\n        // 构建父子关系：因为 Area 中没有 parentId 字段，所以需要重复读取\n        for (CsvRow row : rows) {\n            Area area = areas.get(Integer.valueOf(row.get(0))); // 自己\n            Area parent = areas.get(Integer.valueOf(row.get(3))); // 父\n            Assert.isTrue(area != parent, \"{}:父子节点相同\", area.getName());\n            area.setParent(parent);\n            parent.getChildren().add(area);\n        }\n        log.info(\"启动加载 AreaUtils 成功，耗时 ({}) 毫秒\", System.currentTimeMillis() - now);\n    }\n\n    /**\n     * 获得指定编号对应的区域\n     *\n     * @param id 区域编号\n     * @return 区域\n     */\n    public static Area getArea(Integer id) {\n        return areas.get(id);\n    }\n\n    /**\n     * 获得指定区域对应的编号\n     *\n     * @param pathStr 区域路径，例如说：河南省/石家庄市/新华区\n     * @return 区域\n     */\n    public static Area parseArea(String pathStr) {\n        String[] paths = pathStr.split(\"/\");\n        Area area = null;\n        for (String path : paths) {\n            if (area == null) {\n                area = findFirst(areas.values(), item -> item.getName().equals(path));\n            } else {\n                area = findFirst(area.getChildren(), item -> item.getName().equals(path));\n            }\n        }\n        return area;\n    }\n\n    /**\n     * 获取所有节点的全路径名称如：河南省/石家庄市/新华区\n     *\n     * @param areas 地区树\n     * @return 所有节点的全路径名称\n     */\n    public static List<String> getAreaNodePathList(List<Area> areas) {\n        List<String> paths = new ArrayList<>();\n        areas.forEach(area -> getAreaNodePathList(area, \"\", paths));\n        return paths;\n    }\n\n    /**\n     * 构建一棵树的所有节点的全路径名称，并将其存储为 \"祖先/父级/子级\" 的形式\n     *\n     * @param node  父节点\n     * @param path  全路径名称\n     * @param paths 全路径名称列表，省份/城市/地区\n     */\n    private static void getAreaNodePathList(Area node, String path, List<String> paths) {\n        if (node == null) {\n            return;\n        }\n        // 构建当前节点的路径\n        String currentPath = path.isEmpty() ? node.getName() : path + \"/\" + node.getName();\n        paths.add(currentPath);\n        // 递归遍历子节点\n        for (Area child : node.getChildren()) {\n            getAreaNodePathList(child, currentPath, paths);\n        }\n    }\n\n    /**\n     * 格式化区域\n     *\n     * @param id 区域编号\n     * @return 格式化后的区域\n     */\n    public static String format(Integer id) {\n        return format(id, \" \");\n    }\n\n    /**\n     * 格式化区域\n     *\n     * 例如说：\n     * 1. id = “静安区”时：上海 上海市 静安区\n     * 2. id = “上海市”时：上海 上海市\n     * 3. id = “上海”时：上海\n     * 4. id = “美国”时：美国\n     * 当区域在中国时，默认不显示中国\n     *\n     * @param id        区域编号\n     * @param separator 分隔符\n     * @return 格式化后的区域\n     */\n    public static String format(Integer id, String separator) {\n        // 获得区域\n        Area area = areas.get(id);\n        if (area == null) {\n            return null;\n        }\n\n        // 格式化\n        StringBuilder sb = new StringBuilder();\n        for (int i = 0; i < AreaTypeEnum.values().length; i++) { // 避免死循环\n            sb.insert(0, area.getName());\n            // “递归”父节点\n            area = area.getParent();\n            if (area == null\n                    || ObjectUtils.equalsAny(area.getId(), Area.ID_GLOBAL, Area.ID_CHINA)) { // 跳过父节点为中国的情况\n                break;\n            }\n            sb.insert(0, separator);\n        }\n        return sb.toString();\n    }\n\n    /**\n     * 获取指定类型的区域列表\n     *\n     * @param type 区域类型\n     * @param func 转换函数\n     * @param <T>  结果类型\n     * @return 区域列表\n     */\n    public static <T> List<T> getByType(AreaTypeEnum type, Function<Area, T> func) {\n        return convertList(areas.values(), func, area -> type.getType().equals(area.getType()));\n    }\n\n    /**\n     * 根据区域编号、上级区域类型，获取上级区域编号\n     *\n     * @param id   区域编号\n     * @param type 区域类型\n     * @return 上级区域编号\n     */\n    public static Integer getParentIdByType(Integer id, @NonNull AreaTypeEnum type) {\n        for (int i = 0; i < Byte.MAX_VALUE; i++) {\n            Area area = AreaUtils.getArea(id);\n            if (area == null) {\n                return null;\n            }\n            // 情况一：匹配到，返回它\n            if (type.getType().equals(area.getType())) {\n                return area.getId();\n            }\n            // 情况二：找到根节点，返回空\n            if (area.getParent() == null || area.getParent().getId() == null) {\n                return null;\n            }\n            // 其它：继续向上查找\n            id = area.getParent().getId();\n        }\n        return null;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-ip/src/main/java/co/yixiang/yshop/framework/ip/core/utils/IPUtils.java",
    "content": "package co.yixiang.yshop.framework.ip.core.utils;\n\nimport cn.hutool.core.io.resource.ResourceUtil;\nimport co.yixiang.yshop.framework.ip.core.Area;\nimport lombok.SneakyThrows;\nimport lombok.extern.slf4j.Slf4j;\nimport org.lionsoul.ip2region.xdb.Searcher;\n\nimport java.io.IOException;\n\n/**\n * IP 工具类\n *\n * IP 数据源来自 ip2region.xdb 精简版，基于 <a href=\"https://gitee.com/zhijiantianya/ip2region\"/> 项目\n *\n * @author wanglhup\n */\n@Slf4j\npublic class IPUtils {\n\n    /**\n     * 初始化 SEARCHER\n     */\n    @SuppressWarnings(\"InstantiationOfUtilityClass\")\n    private final static IPUtils INSTANCE = new IPUtils();\n\n    /**\n     * IP 查询器，启动加载到内存中\n     */\n    private static Searcher SEARCHER;\n\n    /**\n     * 私有化构造\n     */\n    private IPUtils() {\n        try {\n            long now = System.currentTimeMillis();\n            byte[] bytes = ResourceUtil.readBytes(\"ip2region.xdb\");\n            SEARCHER = Searcher.newWithBuffer(bytes);\n            log.info(\"启动加载 IPUtils 成功，耗时 ({}) 毫秒\", System.currentTimeMillis() - now);\n        } catch (IOException e) {\n            log.error(\"启动加载 IPUtils 失败\", e);\n        }\n    }\n\n    /**\n     * 查询 IP 对应的地区编号\n     *\n     * @param ip IP 地址，格式为 127.0.0.1\n     * @return 地区id\n     */\n    @SneakyThrows\n    public static Integer getAreaId(String ip) {\n        return Integer.parseInt(SEARCHER.search(ip.trim()));\n    }\n\n    /**\n     * 查询 IP 对应的地区编号\n     *\n     * @param ip IP 地址的时间戳，格式参考{@link Searcher#checkIP(String)} 的返回\n     * @return 地区编号\n     */\n    @SneakyThrows\n    public static Integer getAreaId(long ip) {\n        return Integer.parseInt(SEARCHER.search(ip));\n    }\n\n    /**\n     * 查询 IP 对应的地区\n     *\n     * @param ip IP 地址，格式为 127.0.0.1\n     * @return 地区\n     */\n    public static Area getArea(String ip) {\n        return AreaUtils.getArea(getAreaId(ip));\n    }\n\n    /**\n     * 查询 IP 对应的地区\n     *\n     * @param ip IP 地址的时间戳，格式参考{@link Searcher#checkIP(String)} 的返回\n     * @return 地区\n     */\n    public static Area getArea(long ip) {\n        return AreaUtils.getArea(getAreaId(ip));\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-ip/src/main/resources/area.csv",
    "content": "id,name,type,parentId\n1,中国,1,0\n2,蒙古,1,0\n3,朝鲜,1,0\n4,韩国,1,0\n5,日本,1,0\n6,菲律宾,1,0\n7,越南,1,0\n8,老挝,1,0\n9,柬埔寨,1,0\n10,缅甸,1,0\n11,泰国,1,0\n12,马来西亚,1,0\n13,文莱,1,0\n14,新加坡,1,0\n15,印度尼西亚,1,0\n16,东帝汶,1,0\n17,尼泊尔,1,0\n18,不丹,1,0\n19,孟加拉国,1,0\n20,印度,1,0\n21,巴基斯坦,1,0\n22,斯里兰卡,1,0\n23,马尔代夫,1,0\n24,哈萨克斯坦,1,0\n25,吉尔吉斯斯坦,1,0\n26,塔吉克斯坦,1,0\n27,乌兹别克斯坦,1,0\n28,土库曼斯坦,1,0\n29,阿富汗,1,0\n30,伊拉克,1,0\n31,伊朗,1,0\n32,叙利亚,1,0\n33,约旦,1,0\n34,黎巴嫩,1,0\n35,以色列,1,0\n36,巴勒斯坦,1,0\n37,沙特阿拉伯,1,0\n38,巴林,1,0\n39,卡塔尔,1,0\n40,科威特,1,0\n41,阿拉伯联合酋长国,1,0\n42,阿曼,1,0\n43,也门,1,0\n44,格鲁吉亚,1,0\n45,亚美尼亚,1,0\n46,阿塞拜疆,1,0\n47,土耳其,1,0\n48,塞浦路斯,1,0\n49,芬兰,1,0\n50,瑞典,1,0\n51,挪威,1,0\n52,冰岛,1,0\n53,丹麦,1,0\n54,爱沙尼亚,1,0\n55,拉脱维亚,1,0\n56,立陶宛,1,0\n57,白俄罗斯,1,0\n58,俄罗斯,1,0\n59,乌克兰,1,0\n60,摩尔多瓦,1,0\n61,波兰,1,0\n62,捷克,1,0\n63,斯洛伐克,1,0\n64,匈牙利,1,0\n65,德国,1,0\n66,奥地利,1,0\n67,瑞士,1,0\n68,列支敦士登,1,0\n69,英国,1,0\n70,爱尔兰,1,0\n71,荷兰,1,0\n72,比利时,1,0\n73,卢森堡,1,0\n74,法国,1,0\n75,摩纳哥,1,0\n76,罗马尼亚,1,0\n77,保加利亚,1,0\n78,塞尔维亚,1,0\n79,马其顿,1,0\n80,阿尔巴尼亚,1,0\n81,希腊,1,0\n82,斯洛文尼亚,1,0\n83,克罗地亚,1,0\n84,波斯尼亚和墨塞哥维那,1,0\n85,意大利,1,0\n86,梵蒂冈,1,0\n87,圣马力诺,1,0\n88,马耳他,1,0\n89,西班牙,1,0\n90,葡萄牙,1,0\n91,安道尔共和国,1,0\n92,埃及,1,0\n93,利比亚,1,0\n94,苏丹,1,0\n95,突尼斯,1,0\n96,阿尔及利亚,1,0\n97,摩洛哥,1,0\n98,亚速尔群岛,1,0\n99,马德拉群岛,1,0\n100,埃塞俄比亚,1,0\n101,厄立特里亚,1,0\n102,索马里,1,0\n103,吉布提,1,0\n104,肯尼亚,1,0\n105,坦桑尼亚,1,0\n106,乌干达,1,0\n107,卢旺达,1,0\n108,布隆迪,1,0\n109,塞舌尔,1,0\n110,圣多美及普林西比,1,0\n111,塞内加尔,1,0\n112,冈比亚,1,0\n113,马里,1,0\n114,布基纳法索,1,0\n115,几内亚,1,0\n116,几内亚比绍,1,0\n117,佛得角,1,0\n118,塞拉利昂,1,0\n119,利比里亚,1,0\n120,科特迪瓦,1,0\n121,加纳,1,0\n122,多哥,1,0\n123,贝宁,1,0\n124,尼日尔,1,0\n125,加那利群岛,1,0\n126,赞比亚,1,0\n127,安哥拉,1,0\n128,津巴布韦,1,0\n129,马拉维,1,0\n130,莫桑比克,1,0\n131,博茨瓦纳,1,0\n132,纳米比亚,1,0\n133,南非,1,0\n134,斯威士兰,1,0\n135,莱索托,1,0\n136,马达加斯加,1,0\n137,科摩罗,1,0\n138,毛里求斯,1,0\n139,留尼旺,1,0\n140,圣赫勒拿,1,0\n141,澳大利亚,1,0\n142,新西兰,1,0\n143,巴布亚新几内亚,1,0\n144,所罗门群岛,1,0\n145,瓦努阿图共和国,1,0\n146,密克罗尼西亚,1,0\n147,马绍尔群岛,1,0\n148,帕劳,1,0\n149,瑙鲁,1,0\n150,基里巴斯,1,0\n151,图瓦卢,1,0\n152,萨摩亚,1,0\n153,斐济,1,0\n154,汤加,1,0\n155,库克群岛,1,0\n156,关岛,1,0\n157,新喀里多尼亚,1,0\n158,法属波利尼西亚,1,0\n159,皮特凯恩岛,1,0\n160,瓦利斯与富图纳,1,0\n161,纽埃,1,0\n162,托克劳,1,0\n163,美属萨摩亚,1,0\n164,北马里亚纳,1,0\n165,加拿大,1,0\n166,美国,1,0\n167,墨西哥,1,0\n168,格陵兰,1,0\n169,危地马拉,1,0\n170,伯利兹,1,0\n171,萨尔瓦多,1,0\n172,洪都拉斯,1,0\n173,尼加拉瓜,1,0\n174,哥斯达黎加,1,0\n175,巴拿马,1,0\n176,巴哈马,1,0\n177,古巴,1,0\n178,牙买加,1,0\n179,海地,1,0\n180,多米尼加共和国,1,0\n181,安提瓜和巴布达,1,0\n182,圣基茨和尼维斯,1,0\n183,多米尼克,1,0\n184,圣卢西亚,1,0\n185,圣文森特和格林纳丁斯,1,0\n186,格林纳达,1,0\n187,巴巴多斯,1,0\n188,特立尼达和多巴哥,1,0\n189,波多黎各,1,0\n190,英属维尔京群岛,1,0\n191,美属维尔京群岛,1,0\n192,安圭拉,1,0\n193,蒙特塞拉特岛,1,0\n194,瓜德罗普,1,0\n195,马提尼克,1,0\n196,荷属安的列斯,1,0\n197,阿鲁巴,1,0\n198,特克斯和凯科斯群岛,1,0\n199,开曼群岛,1,0\n200,百慕大,1,0\n201,哥伦比亚,1,0\n202,委内瑞拉,1,0\n203,圭亚那,1,0\n204,法属圭亚那,1,0\n205,苏里南,1,0\n206,厄瓜多尔,1,0\n207,秘鲁,1,0\n208,玻利维亚,1,0\n209,巴西,1,0\n210,智利,1,0\n211,阿根廷,1,0\n212,乌拉圭,1,0\n213,巴拉圭,1,0\n214,波黑,1,0\n215,直布罗陀,1,0\n216,新喀里多尼亚群岛,1,0\n217,瓦利斯和富图纳群岛,1,0\n218,泽西岛,1,0\n219,黑山,1,0\n220,英属马恩岛,1,0\n221,尼日利亚,1,0\n222,喀麦隆,1,0\n223,加蓬,1,0\n224,乍得,1,0\n225,刚果共和国,1,0\n226,中非共和国,1,0\n227,南苏丹,1,0\n228,赤道几内亚,1,0\n229,毛里塔尼亚,1,0\n230,刚果民主共和国,1,0\n231,留尼汪岛,1,0\n232,格陵兰岛,1,0\n233,法罗群岛,1,0\n234,根西岛,1,0\n235,百慕大群岛,1,0\n236,圣皮埃尔和密克隆群岛,1,0\n237,法属圣马丁,1,0\n238,奥兰群岛,1,0\n239,北马里亚纳群岛,1,0\n240,库拉索,1,0\n241,博内尔岛,1,0\n242,圣马丁岛,1,0\n243,圣巴泰勒米岛,1,0\n244,福克兰群岛,1,0\n245,圣多美和普林西比,1,0\n246,英属印度洋领地,1,0\n247,东萨摩亚,1,0\n248,诺福克岛,1,0\n110000,北京,2,1\n120000,天津,2,1\n130000,河北省,2,1\n140000,山西省,2,1\n150000,内蒙古自治区,2,1\n210000,辽宁省,2,1\n220000,吉林省,2,1\n230000,黑龙江省,2,1\n310000,上海,2,1\n320000,江苏省,2,1\n330000,浙江省,2,1\n340000,安徽省,2,1\n350000,福建省,2,1\n360000,江西省,2,1\n370000,山东省,2,1\n410000,河南省,2,1\n420000,湖北省,2,1\n430000,湖南省,2,1\n440000,广东省,2,1\n450000,广西壮族自治区,2,1\n460000,海南省,2,1\n500000,重庆,2,1\n510000,四川省,2,1\n520000,贵州省,2,1\n530000,云南省,2,1\n540000,西藏自治区,2,1\n610000,陕西省,2,1\n620000,甘肃省,2,1\n630000,青海省,2,1\n640000,宁夏回族自治区,2,1\n650000,新疆维吾尔自治区,2,1\n110100,北京市,3,110000\n120100,天津市,3,120000\n130100,石家庄市,3,130000\n130200,唐山市,3,130000\n130300,秦皇岛市,3,130000\n130400,邯郸市,3,130000\n130500,邢台市,3,130000\n130600,保定市,3,130000\n130700,张家口市,3,130000\n130800,承德市,3,130000\n130900,沧州市,3,130000\n131000,廊坊市,3,130000\n131100,衡水市,3,130000\n140100,太原市,3,140000\n140200,大同市,3,140000\n140300,阳泉市,3,140000\n140400,长治市,3,140000\n140500,晋城市,3,140000\n140600,朔州市,3,140000\n140700,晋中市,3,140000\n140800,运城市,3,140000\n140900,忻州市,3,140000\n141000,临汾市,3,140000\n141100,吕梁市,3,140000\n150100,呼和浩特市,3,150000\n150200,包头市,3,150000\n150300,乌海市,3,150000\n150400,赤峰市,3,150000\n150500,通辽市,3,150000\n150600,鄂尔多斯市,3,150000\n150700,呼伦贝尔市,3,150000\n150800,巴彦淖尔市,3,150000\n150900,乌兰察布市,3,150000\n152200,兴安盟,3,150000\n152500,锡林郭勒盟,3,150000\n152900,阿拉善盟,3,150000\n210100,沈阳市,3,210000\n210200,大连市,3,210000\n210300,鞍山市,3,210000\n210400,抚顺市,3,210000\n210500,本溪市,3,210000\n210600,丹东市,3,210000\n210700,锦州市,3,210000\n210800,营口市,3,210000\n210900,阜新市,3,210000\n211000,辽阳市,3,210000\n211100,盘锦市,3,210000\n211200,铁岭市,3,210000\n211300,朝阳市,3,210000\n211400,葫芦岛市,3,210000\n220100,长春市,3,220000\n220200,吉林市,3,220000\n220300,四平市,3,220000\n220400,辽源市,3,220000\n220500,通化市,3,220000\n220600,白山市,3,220000\n220700,松原市,3,220000\n220800,白城市,3,220000\n222400,延边朝鲜族自治州,3,220000\n230100,哈尔滨市,3,230000\n230200,齐齐哈尔市,3,230000\n230300,鸡西市,3,230000\n230400,鹤岗市,3,230000\n230500,双鸭山市,3,230000\n230600,大庆市,3,230000\n230700,伊春市,3,230000\n230800,佳木斯市,3,230000\n230900,七台河市,3,230000\n231000,牡丹江市,3,230000\n231100,黑河市,3,230000\n231200,绥化市,3,230000\n232700,大兴安岭地区,3,230000\n310100,上海市,3,310000\n320100,南京市,3,320000\n320200,无锡市,3,320000\n320300,徐州市,3,320000\n320400,常州市,3,320000\n320500,苏州市,3,320000\n320600,南通市,3,320000\n320700,连云港市,3,320000\n320800,淮安市,3,320000\n320900,盐城市,3,320000\n321000,扬州市,3,320000\n321100,镇江市,3,320000\n321200,泰州市,3,320000\n321300,宿迁市,3,320000\n330100,杭州市,3,330000\n330200,宁波市,3,330000\n330300,温州市,3,330000\n330400,嘉兴市,3,330000\n330500,湖州市,3,330000\n330600,绍兴市,3,330000\n330700,金华市,3,330000\n330800,衢州市,3,330000\n330900,舟山市,3,330000\n331000,台州市,3,330000\n331100,丽水市,3,330000\n340100,合肥市,3,340000\n340200,芜湖市,3,340000\n340300,蚌埠市,3,340000\n340400,淮南市,3,340000\n340500,马鞍山市,3,340000\n340600,淮北市,3,340000\n340700,铜陵市,3,340000\n340800,安庆市,3,340000\n341000,黄山市,3,340000\n341100,滁州市,3,340000\n341200,阜阳市,3,340000\n341300,宿州市,3,340000\n341500,六安市,3,340000\n341600,亳州市,3,340000\n341700,池州市,3,340000\n341800,宣城市,3,340000\n350100,福州市,3,350000\n350200,厦门市,3,350000\n350300,莆田市,3,350000\n350400,三明市,3,350000\n350500,泉州市,3,350000\n350600,漳州市,3,350000\n350700,南平市,3,350000\n350800,龙岩市,3,350000\n350900,宁德市,3,350000\n360100,南昌市,3,360000\n360200,景德镇市,3,360000\n360300,萍乡市,3,360000\n360400,九江市,3,360000\n360500,新余市,3,360000\n360600,鹰潭市,3,360000\n360700,赣州市,3,360000\n360800,吉安市,3,360000\n360900,宜春市,3,360000\n361000,抚州市,3,360000\n361100,上饶市,3,360000\n370100,济南市,3,370000\n370200,青岛市,3,370000\n370300,淄博市,3,370000\n370400,枣庄市,3,370000\n370500,东营市,3,370000\n370600,烟台市,3,370000\n370700,潍坊市,3,370000\n370800,济宁市,3,370000\n370900,泰安市,3,370000\n371000,威海市,3,370000\n371100,日照市,3,370000\n371300,临沂市,3,370000\n371400,德州市,3,370000\n371500,聊城市,3,370000\n371600,滨州市,3,370000\n371700,菏泽市,3,370000\n410100,郑州市,3,410000\n410200,开封市,3,410000\n410300,洛阳市,3,410000\n410400,平顶山市,3,410000\n410500,安阳市,3,410000\n410600,鹤壁市,3,410000\n410700,新乡市,3,410000\n410800,焦作市,3,410000\n410900,濮阳市,3,410000\n411000,许昌市,3,410000\n411100,漯河市,3,410000\n411200,三门峡市,3,410000\n411300,南阳市,3,410000\n411400,商丘市,3,410000\n411500,信阳市,3,410000\n411600,周口市,3,410000\n411700,驻马店市,3,410000\n419000,省直辖县级行政区划,3,410000\n420100,武汉市,3,420000\n420200,黄石市,3,420000\n420300,十堰市,3,420000\n420500,宜昌市,3,420000\n420600,襄阳市,3,420000\n420700,鄂州市,3,420000\n420800,荆门市,3,420000\n420900,孝感市,3,420000\n421000,荆州市,3,420000\n421100,黄冈市,3,420000\n421200,咸宁市,3,420000\n421300,随州市,3,420000\n422800,恩施土家族苗族自治州,3,420000\n429000,省直辖县级行政区划,3,420000\n430100,长沙市,3,430000\n430200,株洲市,3,430000\n430300,湘潭市,3,430000\n430400,衡阳市,3,430000\n430500,邵阳市,3,430000\n430600,岳阳市,3,430000\n430700,常德市,3,430000\n430800,张家界市,3,430000\n430900,益阳市,3,430000\n431000,郴州市,3,430000\n431100,永州市,3,430000\n431200,怀化市,3,430000\n431300,娄底市,3,430000\n433100,湘西土家族苗族自治州,3,430000\n440100,广州市,3,440000\n440200,韶关市,3,440000\n440300,深圳市,3,440000\n440400,珠海市,3,440000\n440500,汕头市,3,440000\n440600,佛山市,3,440000\n440700,江门市,3,440000\n440800,湛江市,3,440000\n440900,茂名市,3,440000\n441200,肇庆市,3,440000\n441300,惠州市,3,440000\n441400,梅州市,3,440000\n441500,汕尾市,3,440000\n441600,河源市,3,440000\n441700,阳江市,3,440000\n441800,清远市,3,440000\n441900,东莞市,3,440000\n441901,莞城区,4,441900\n441902,南城区,4,441900\n441904,万江区,4,441900\n441905,石碣镇,4,441900\n441906,石龙镇,4,441900\n441907,茶山镇,4,441900\n441908,石排镇,4,441900\n441909,企石镇,4,441900\n441910,横沥镇,4,441900\n441911,桥头镇,4,441900\n441912,谢岗镇,4,441900\n441913,东坑镇,4,441900\n441914,常平镇,4,441900\n441915,寮步镇,4,441900\n441916,大朗镇,4,441900\n441917,麻涌镇,4,441900\n441918,中堂镇,4,441900\n441919,高埗镇,4,441900\n441920,樟木头镇,4,441900\n441921,大岭山镇,4,441900\n441922,望牛墩镇,4,441900\n441923,黄江镇,4,441900\n441924,洪梅镇,4,441900\n441925,清溪镇,4,441900\n441926,沙田镇,4,441900\n441927,道滘镇,4,441900\n441928,塘厦镇,4,441900\n441929,虎门镇,4,441900\n441930,厚街镇,4,441900\n441931,凤岗镇,4,441900\n441932,长安镇,4,441900\n442000,中山市,3,440000\n442001,石岐街道,4,442000\n442002,东区街道,4,442000\n442003,中山港街道,4,442000\n442004,西区街道,4,442000\n442005,南区街道,4,442000\n442006,五桂山街道,4,442000\n442007,民众街道,4,442000\n442008,南朗街道,4,442000\n442009,黄圃镇,4,442000\n442010,东凤镇,4,442000\n442011,古镇镇,4,442000\n442012,沙溪镇,4,442000\n442013,坦洲镇,4,442000\n442014,港口镇,4,442000\n442015,三角镇,4,442000\n442016,横栏镇,4,442000\n442017,南头镇,4,442000\n442018,阜沙镇,4,442000\n442019,三乡镇,4,442000\n442020,板芙镇,4,442000\n442021,大涌镇,4,442000\n442022,神湾镇,4,442000\n442023,小榄镇,4,442000\n445100,潮州市,3,440000\n445200,揭阳市,3,440000\n445300,云浮市,3,440000\n450100,南宁市,3,450000\n450200,柳州市,3,450000\n450300,桂林市,3,450000\n450400,梧州市,3,450000\n450500,北海市,3,450000\n450600,防城港市,3,450000\n450700,钦州市,3,450000\n450800,贵港市,3,450000\n450900,玉林市,3,450000\n451000,百色市,3,450000\n451100,贺州市,3,450000\n451200,河池市,3,450000\n451300,来宾市,3,450000\n451400,崇左市,3,450000\n460100,海口市,3,460000\n460200,三亚市,3,460000\n460300,三沙市,3,460000\n460400,儋州市,3,460000\n469000,省直辖县级行政区划,3,460000\n500100,重庆市,3,500000\n510100,成都市,3,510000\n510300,自贡市,3,510000\n510400,攀枝花市,3,510000\n510500,泸州市,3,510000\n510600,德阳市,3,510000\n510700,绵阳市,3,510000\n510800,广元市,3,510000\n510900,遂宁市,3,510000\n511000,内江市,3,510000\n511100,乐山市,3,510000\n511300,南充市,3,510000\n511400,眉山市,3,510000\n511500,宜宾市,3,510000\n511600,广安市,3,510000\n511700,达州市,3,510000\n511800,雅安市,3,510000\n511900,巴中市,3,510000\n512000,资阳市,3,510000\n513200,阿坝藏族羌族自治州,3,510000\n513300,甘孜藏族自治州,3,510000\n513400,凉山彝族自治州,3,510000\n520100,贵阳市,3,520000\n520200,六盘水市,3,520000\n520300,遵义市,3,520000\n520400,安顺市,3,520000\n520500,毕节市,3,520000\n520600,铜仁市,3,520000\n522300,黔西南布依族苗族自治州,3,520000\n522600,黔东南苗族侗族自治州,3,520000\n522700,黔南布依族苗族自治州,3,520000\n530100,昆明市,3,530000\n530300,曲靖市,3,530000\n530400,玉溪市,3,530000\n530500,保山市,3,530000\n530600,昭通市,3,530000\n530700,丽江市,3,530000\n530800,普洱市,3,530000\n530900,临沧市,3,530000\n532300,楚雄彝族自治州,3,530000\n532500,红河哈尼族彝族自治州,3,530000\n532600,文山壮族苗族自治州,3,530000\n532800,西双版纳傣族自治州,3,530000\n532900,大理白族自治州,3,530000\n533100,德宏傣族景颇族自治州,3,530000\n533300,怒江傈僳族自治州,3,530000\n533400,迪庆藏族自治州,3,530000\n540100,拉萨市,3,540000\n540200,日喀则市,3,540000\n540300,昌都市,3,540000\n540400,林芝市,3,540000\n540500,山南市,3,540000\n540600,那曲市,3,540000\n542500,阿里地区,3,540000\n610100,西安市,3,610000\n610200,铜川市,3,610000\n610300,宝鸡市,3,610000\n610400,咸阳市,3,610000\n610500,渭南市,3,610000\n610600,延安市,3,610000\n610700,汉中市,3,610000\n610800,榆林市,3,610000\n610900,安康市,3,610000\n611000,商洛市,3,610000\n620100,兰州市,3,620000\n620200,嘉峪关市,3,620000\n620300,金昌市,3,620000\n620400,白银市,3,620000\n620500,天水市,3,620000\n620600,武威市,3,620000\n620700,张掖市,3,620000\n620800,平凉市,3,620000\n620900,酒泉市,3,620000\n621000,庆阳市,3,620000\n621100,定西市,3,620000\n621200,陇南市,3,620000\n622900,临夏回族自治州,3,620000\n623000,甘南藏族自治州,3,620000\n630100,西宁市,3,630000\n630200,海东市,3,630000\n632200,海北藏族自治州,3,630000\n632300,黄南藏族自治州,3,630000\n632500,海南藏族自治州,3,630000\n632600,果洛藏族自治州,3,630000\n632700,玉树藏族自治州,3,630000\n632800,海西蒙古族藏族自治州,3,630000\n640100,银川市,3,640000\n640200,石嘴山市,3,640000\n640300,吴忠市,3,640000\n640400,固原市,3,640000\n640500,中卫市,3,640000\n650100,乌鲁木齐市,3,650000\n650200,克拉玛依市,3,650000\n650400,吐鲁番市,3,650000\n650500,哈密市,3,650000\n652300,昌吉回族自治州,3,650000\n652700,博尔塔拉蒙古自治州,3,650000\n652800,巴音郭楞蒙古自治州,3,650000\n652900,阿克苏地区,3,650000\n653000,克孜勒苏柯尔克孜自治州,3,650000\n653100,喀什地区,3,650000\n653200,和田地区,3,650000\n654000,伊犁哈萨克自治州,3,650000\n654200,塔城地区,3,650000\n654300,阿勒泰地区,3,650000\n659000,自治区直辖县级行政区划,3,650000\n110101,东城区,4,110100\n110102,西城区,4,110100\n110105,朝阳区,4,110100\n110106,丰台区,4,110100\n110107,石景山区,4,110100\n110108,海淀区,4,110100\n110109,门头沟区,4,110100\n110111,房山区,4,110100\n110112,通州区,4,110100\n110113,顺义区,4,110100\n110114,昌平区,4,110100\n110115,大兴区,4,110100\n110116,怀柔区,4,110100\n110117,平谷区,4,110100\n110118,密云区,4,110100\n110119,延庆区,4,110100\n120101,和平区,4,120100\n120102,河东区,4,120100\n120103,河西区,4,120100\n120104,南开区,4,120100\n120105,河北区,4,120100\n120106,红桥区,4,120100\n120110,东丽区,4,120100\n120111,西青区,4,120100\n120112,津南区,4,120100\n120113,北辰区,4,120100\n120114,武清区,4,120100\n120115,宝坻区,4,120100\n120116,滨海新区,4,120100\n120117,宁河区,4,120100\n120118,静海区,4,120100\n120119,蓟州区,4,120100\n130102,长安区,4,130100\n130104,桥西区,4,130100\n130105,新华区,4,130100\n130107,井陉矿区,4,130100\n130108,裕华区,4,130100\n130109,藁城区,4,130100\n130110,鹿泉区,4,130100\n130111,栾城区,4,130100\n130121,井陉县,4,130100\n130123,正定县,4,130100\n130125,行唐县,4,130100\n130126,灵寿县,4,130100\n130127,高邑县,4,130100\n130128,深泽县,4,130100\n130129,赞皇县,4,130100\n130130,无极县,4,130100\n130131,平山县,4,130100\n130132,元氏县,4,130100\n130133,赵县,4,130100\n130171,石家庄高新技术产业开发区,4,130100\n130172,石家庄循环化工园区,4,130100\n130181,辛集市,4,130100\n130183,晋州市,4,130100\n130184,新乐市,4,130100\n130202,路南区,4,130200\n130203,路北区,4,130200\n130204,古冶区,4,130200\n130205,开平区,4,130200\n130207,丰南区,4,130200\n130208,丰润区,4,130200\n130209,曹妃甸区,4,130200\n130224,滦南县,4,130200\n130225,乐亭县,4,130200\n130227,迁西县,4,130200\n130229,玉田县,4,130200\n130271,河北唐山芦台经济开发区,4,130200\n130272,唐山市汉沽管理区,4,130200\n130273,唐山高新技术产业开发区,4,130200\n130274,河北唐山海港经济开发区,4,130200\n130281,遵化市,4,130200\n130283,迁安市,4,130200\n130284,滦州市,4,130200\n130302,海港区,4,130300\n130303,山海关区,4,130300\n130304,北戴河区,4,130300\n130306,抚宁区,4,130300\n130321,青龙满族自治县,4,130300\n130322,昌黎县,4,130300\n130324,卢龙县,4,130300\n130371,秦皇岛市经济技术开发区,4,130300\n130372,北戴河新区,4,130300\n130402,邯山区,4,130400\n130403,丛台区,4,130400\n130404,复兴区,4,130400\n130406,峰峰矿区,4,130400\n130407,肥乡区,4,130400\n130408,永年区,4,130400\n130423,临漳县,4,130400\n130424,成安县,4,130400\n130425,大名县,4,130400\n130426,涉县,4,130400\n130427,磁县,4,130400\n130430,邱县,4,130400\n130431,鸡泽县,4,130400\n130432,广平县,4,130400\n130433,馆陶县,4,130400\n130434,魏县,4,130400\n130435,曲周县,4,130400\n130471,邯郸经济技术开发区,4,130400\n130473,邯郸冀南新区,4,130400\n130481,武安市,4,130400\n130502,襄都区,4,130500\n130503,信都区,4,130500\n130505,任泽区,4,130500\n130506,南和区,4,130500\n130522,临城县,4,130500\n130523,内丘县,4,130500\n130524,柏乡县,4,130500\n130525,隆尧县,4,130500\n130528,宁晋县,4,130500\n130529,巨鹿县,4,130500\n130530,新河县,4,130500\n130531,广宗县,4,130500\n130532,平乡县,4,130500\n130533,威县,4,130500\n130534,清河县,4,130500\n130535,临西县,4,130500\n130571,河北邢台经济开发区,4,130500\n130581,南宫市,4,130500\n130582,沙河市,4,130500\n130602,竞秀区,4,130600\n130606,莲池区,4,130600\n130607,满城区,4,130600\n130608,清苑区,4,130600\n130609,徐水区,4,130600\n130623,涞水县,4,130600\n130624,阜平县,4,130600\n130626,定兴县,4,130600\n130627,唐县,4,130600\n130628,高阳县,4,130600\n130629,容城县,4,130600\n130630,涞源县,4,130600\n130631,望都县,4,130600\n130632,安新县,4,130600\n130633,易县,4,130600\n130634,曲阳县,4,130600\n130635,蠡县,4,130600\n130636,顺平县,4,130600\n130637,博野县,4,130600\n130638,雄县,4,130600\n130671,保定高新技术产业开发区,4,130600\n130672,保定白沟新城,4,130600\n130681,涿州市,4,130600\n130682,定州市,4,130600\n130683,安国市,4,130600\n130684,高碑店市,4,130600\n130702,桥东区,4,130700\n130703,桥西区,4,130700\n130705,宣化区,4,130700\n130706,下花园区,4,130700\n130708,万全区,4,130700\n130709,崇礼区,4,130700\n130722,张北县,4,130700\n130723,康保县,4,130700\n130724,沽源县,4,130700\n130725,尚义县,4,130700\n130726,蔚县,4,130700\n130727,阳原县,4,130700\n130728,怀安县,4,130700\n130730,怀来县,4,130700\n130731,涿鹿县,4,130700\n130732,赤城县,4,130700\n130771,张家口经济开发区,4,130700\n130772,张家口市察北管理区,4,130700\n130773,张家口市塞北管理区,4,130700\n130802,双桥区,4,130800\n130803,双滦区,4,130800\n130804,鹰手营子矿区,4,130800\n130821,承德县,4,130800\n130822,兴隆县,4,130800\n130824,滦平县,4,130800\n130825,隆化县,4,130800\n130826,丰宁满族自治县,4,130800\n130827,宽城满族自治县,4,130800\n130828,围场满族蒙古族自治县,4,130800\n130871,承德高新技术产业开发区,4,130800\n130881,平泉市,4,130800\n130902,新华区,4,130900\n130903,运河区,4,130900\n130921,沧县,4,130900\n130922,青县,4,130900\n130923,东光县,4,130900\n130924,海兴县,4,130900\n130925,盐山县,4,130900\n130926,肃宁县,4,130900\n130927,南皮县,4,130900\n130928,吴桥县,4,130900\n130929,献县,4,130900\n130930,孟村回族自治县,4,130900\n130971,河北沧州经济开发区,4,130900\n130972,沧州高新技术产业开发区,4,130900\n130973,沧州渤海新区,4,130900\n130981,泊头市,4,130900\n130982,任丘市,4,130900\n130983,黄骅市,4,130900\n130984,河间市,4,130900\n131002,安次区,4,131000\n131003,广阳区,4,131000\n131022,固安县,4,131000\n131023,永清县,4,131000\n131024,香河县,4,131000\n131025,大城县,4,131000\n131026,文安县,4,131000\n131028,大厂回族自治县,4,131000\n131071,廊坊经济技术开发区,4,131000\n131081,霸州市,4,131000\n131082,三河市,4,131000\n131102,桃城区,4,131100\n131103,冀州区,4,131100\n131121,枣强县,4,131100\n131122,武邑县,4,131100\n131123,武强县,4,131100\n131124,饶阳县,4,131100\n131125,安平县,4,131100\n131126,故城县,4,131100\n131127,景县,4,131100\n131128,阜城县,4,131100\n131171,河北衡水高新技术产业开发区,4,131100\n131172,衡水滨湖新区,4,131100\n131182,深州市,4,131100\n140105,小店区,4,140100\n140106,迎泽区,4,140100\n140107,杏花岭区,4,140100\n140108,尖草坪区,4,140100\n140109,万柏林区,4,140100\n140110,晋源区,4,140100\n140121,清徐县,4,140100\n140122,阳曲县,4,140100\n140123,娄烦县,4,140100\n140171,山西转型综合改革示范区,4,140100\n140181,古交市,4,140100\n140212,新荣区,4,140200\n140213,平城区,4,140200\n140214,云冈区,4,140200\n140215,云州区,4,140200\n140221,阳高县,4,140200\n140222,天镇县,4,140200\n140223,广灵县,4,140200\n140224,灵丘县,4,140200\n140225,浑源县,4,140200\n140226,左云县,4,140200\n140271,山西大同经济开发区,4,140200\n140302,城区,4,140300\n140303,矿区,4,140300\n140311,郊区,4,140300\n140321,平定县,4,140300\n140322,盂县,4,140300\n140403,潞州区,4,140400\n140404,上党区,4,140400\n140405,屯留区,4,140400\n140406,潞城区,4,140400\n140423,襄垣县,4,140400\n140425,平顺县,4,140400\n140426,黎城县,4,140400\n140427,壶关县,4,140400\n140428,长子县,4,140400\n140429,武乡县,4,140400\n140430,沁县,4,140400\n140431,沁源县,4,140400\n140471,山西长治高新技术产业园区,4,140400\n140502,城区,4,140500\n140521,沁水县,4,140500\n140522,阳城县,4,140500\n140524,陵川县,4,140500\n140525,泽州县,4,140500\n140581,高平市,4,140500\n140602,朔城区,4,140600\n140603,平鲁区,4,140600\n140621,山阴县,4,140600\n140622,应县,4,140600\n140623,右玉县,4,140600\n140671,山西朔州经济开发区,4,140600\n140681,怀仁市,4,140600\n140702,榆次区,4,140700\n140703,太谷区,4,140700\n140721,榆社县,4,140700\n140722,左权县,4,140700\n140723,和顺县,4,140700\n140724,昔阳县,4,140700\n140725,寿阳县,4,140700\n140727,祁县,4,140700\n140728,平遥县,4,140700\n140729,灵石县,4,140700\n140781,介休市,4,140700\n140802,盐湖区,4,140800\n140821,临猗县,4,140800\n140822,万荣县,4,140800\n140823,闻喜县,4,140800\n140824,稷山县,4,140800\n140825,新绛县,4,140800\n140826,绛县,4,140800\n140827,垣曲县,4,140800\n140828,夏县,4,140800\n140829,平陆县,4,140800\n140830,芮城县,4,140800\n140881,永济市,4,140800\n140882,河津市,4,140800\n140902,忻府区,4,140900\n140921,定襄县,4,140900\n140922,五台县,4,140900\n140923,代县,4,140900\n140924,繁峙县,4,140900\n140925,宁武县,4,140900\n140926,静乐县,4,140900\n140927,神池县,4,140900\n140928,五寨县,4,140900\n140929,岢岚县,4,140900\n140930,河曲县,4,140900\n140931,保德县,4,140900\n140932,偏关县,4,140900\n140971,五台山风景名胜区,4,140900\n140981,原平市,4,140900\n141002,尧都区,4,141000\n141021,曲沃县,4,141000\n141022,翼城县,4,141000\n141023,襄汾县,4,141000\n141024,洪洞县,4,141000\n141025,古县,4,141000\n141026,安泽县,4,141000\n141027,浮山县,4,141000\n141028,吉县,4,141000\n141029,乡宁县,4,141000\n141030,大宁县,4,141000\n141031,隰县,4,141000\n141032,永和县,4,141000\n141033,蒲县,4,141000\n141034,汾西县,4,141000\n141081,侯马市,4,141000\n141082,霍州市,4,141000\n141102,离石区,4,141100\n141121,文水县,4,141100\n141122,交城县,4,141100\n141123,兴县,4,141100\n141124,临县,4,141100\n141125,柳林县,4,141100\n141126,石楼县,4,141100\n141127,岚县,4,141100\n141128,方山县,4,141100\n141129,中阳县,4,141100\n141130,交口县,4,141100\n141181,孝义市,4,141100\n141182,汾阳市,4,141100\n150102,新城区,4,150100\n150103,回民区,4,150100\n150104,玉泉区,4,150100\n150105,赛罕区,4,150100\n150121,土默特左旗,4,150100\n150122,托克托县,4,150100\n150123,和林格尔县,4,150100\n150124,清水河县,4,150100\n150125,武川县,4,150100\n150172,呼和浩特经济技术开发区,4,150100\n150202,东河区,4,150200\n150203,昆都仑区,4,150200\n150204,青山区,4,150200\n150205,石拐区,4,150200\n150206,白云鄂博矿区,4,150200\n150207,九原区,4,150200\n150221,土默特右旗,4,150200\n150222,固阳县,4,150200\n150223,达尔罕茂明安联合旗,4,150200\n150271,包头稀土高新技术产业开发区,4,150200\n150302,海勃湾区,4,150300\n150303,海南区,4,150300\n150304,乌达区,4,150300\n150402,红山区,4,150400\n150403,元宝山区,4,150400\n150404,松山区,4,150400\n150421,阿鲁科尔沁旗,4,150400\n150422,巴林左旗,4,150400\n150423,巴林右旗,4,150400\n150424,林西县,4,150400\n150425,克什克腾旗,4,150400\n150426,翁牛特旗,4,150400\n150428,喀喇沁旗,4,150400\n150429,宁城县,4,150400\n150430,敖汉旗,4,150400\n150502,科尔沁区,4,150500\n150521,科尔沁左翼中旗,4,150500\n150522,科尔沁左翼后旗,4,150500\n150523,开鲁县,4,150500\n150524,库伦旗,4,150500\n150525,奈曼旗,4,150500\n150526,扎鲁特旗,4,150500\n150571,通辽经济技术开发区,4,150500\n150581,霍林郭勒市,4,150500\n150602,东胜区,4,150600\n150603,康巴什区,4,150600\n150621,达拉特旗,4,150600\n150622,准格尔旗,4,150600\n150623,鄂托克前旗,4,150600\n150624,鄂托克旗,4,150600\n150625,杭锦旗,4,150600\n150626,乌审旗,4,150600\n150627,伊金霍洛旗,4,150600\n150702,海拉尔区,4,150700\n150703,扎赉诺尔区,4,150700\n150721,阿荣旗,4,150700\n150722,莫力达瓦达斡尔族自治旗,4,150700\n150723,鄂伦春自治旗,4,150700\n150724,鄂温克族自治旗,4,150700\n150725,陈巴尔虎旗,4,150700\n150726,新巴尔虎左旗,4,150700\n150727,新巴尔虎右旗,4,150700\n150781,满洲里市,4,150700\n150782,牙克石市,4,150700\n150783,扎兰屯市,4,150700\n150784,额尔古纳市,4,150700\n150785,根河市,4,150700\n150802,临河区,4,150800\n150821,五原县,4,150800\n150822,磴口县,4,150800\n150823,乌拉特前旗,4,150800\n150824,乌拉特中旗,4,150800\n150825,乌拉特后旗,4,150800\n150826,杭锦后旗,4,150800\n150902,集宁区,4,150900\n150921,卓资县,4,150900\n150922,化德县,4,150900\n150923,商都县,4,150900\n150924,兴和县,4,150900\n150925,凉城县,4,150900\n150926,察哈尔右翼前旗,4,150900\n150927,察哈尔右翼中旗,4,150900\n150928,察哈尔右翼后旗,4,150900\n150929,四子王旗,4,150900\n150981,丰镇市,4,150900\n152201,乌兰浩特市,4,152200\n152202,阿尔山市,4,152200\n152221,科尔沁右翼前旗,4,152200\n152222,科尔沁右翼中旗,4,152200\n152223,扎赉特旗,4,152200\n152224,突泉县,4,152200\n152501,二连浩特市,4,152500\n152502,锡林浩特市,4,152500\n152522,阿巴嘎旗,4,152500\n152523,苏尼特左旗,4,152500\n152524,苏尼特右旗,4,152500\n152525,东乌珠穆沁旗,4,152500\n152526,西乌珠穆沁旗,4,152500\n152527,太仆寺旗,4,152500\n152528,镶黄旗,4,152500\n152529,正镶白旗,4,152500\n152530,正蓝旗,4,152500\n152531,多伦县,4,152500\n152571,乌拉盖管委会,4,152500\n152921,阿拉善左旗,4,152900\n152922,阿拉善右旗,4,152900\n152923,额济纳旗,4,152900\n152971,内蒙古阿拉善高新技术产业开发区,4,152900\n210102,和平区,4,210100\n210103,沈河区,4,210100\n210104,大东区,4,210100\n210105,皇姑区,4,210100\n210106,铁西区,4,210100\n210111,苏家屯区,4,210100\n210112,浑南区,4,210100\n210113,沈北新区,4,210100\n210114,于洪区,4,210100\n210115,辽中区,4,210100\n210123,康平县,4,210100\n210124,法库县,4,210100\n210181,新民市,4,210100\n210202,中山区,4,210200\n210203,西岗区,4,210200\n210204,沙河口区,4,210200\n210211,甘井子区,4,210200\n210212,旅顺口区,4,210200\n210213,金州区,4,210200\n210214,普兰店区,4,210200\n210224,长海县,4,210200\n210281,瓦房店市,4,210200\n210283,庄河市,4,210200\n210302,铁东区,4,210300\n210303,铁西区,4,210300\n210304,立山区,4,210300\n210311,千山区,4,210300\n210321,台安县,4,210300\n210323,岫岩满族自治县,4,210300\n210381,海城市,4,210300\n210402,新抚区,4,210400\n210403,东洲区,4,210400\n210404,望花区,4,210400\n210411,顺城区,4,210400\n210421,抚顺县,4,210400\n210422,新宾满族自治县,4,210400\n210423,清原满族自治县,4,210400\n210502,平山区,4,210500\n210503,溪湖区,4,210500\n210504,明山区,4,210500\n210505,南芬区,4,210500\n210521,本溪满族自治县,4,210500\n210522,桓仁满族自治县,4,210500\n210602,元宝区,4,210600\n210603,振兴区,4,210600\n210604,振安区,4,210600\n210624,宽甸满族自治县,4,210600\n210681,东港市,4,210600\n210682,凤城市,4,210600\n210702,古塔区,4,210700\n210703,凌河区,4,210700\n210711,太和区,4,210700\n210726,黑山县,4,210700\n210727,义县,4,210700\n210781,凌海市,4,210700\n210782,北镇市,4,210700\n210802,站前区,4,210800\n210803,西市区,4,210800\n210804,鲅鱼圈区,4,210800\n210811,老边区,4,210800\n210881,盖州市,4,210800\n210882,大石桥市,4,210800\n210902,海州区,4,210900\n210903,新邱区,4,210900\n210904,太平区,4,210900\n210905,清河门区,4,210900\n210911,细河区,4,210900\n210921,阜新蒙古族自治县,4,210900\n210922,彰武县,4,210900\n211002,白塔区,4,211000\n211003,文圣区,4,211000\n211004,宏伟区,4,211000\n211005,弓长岭区,4,211000\n211011,太子河区,4,211000\n211021,辽阳县,4,211000\n211081,灯塔市,4,211000\n211102,双台子区,4,211100\n211103,兴隆台区,4,211100\n211104,大洼区,4,211100\n211122,盘山县,4,211100\n211202,银州区,4,211200\n211204,清河区,4,211200\n211221,铁岭县,4,211200\n211223,西丰县,4,211200\n211224,昌图县,4,211200\n211281,调兵山市,4,211200\n211282,开原市,4,211200\n211302,双塔区,4,211300\n211303,龙城区,4,211300\n211321,朝阳县,4,211300\n211322,建平县,4,211300\n211324,喀喇沁左翼蒙古族自治县,4,211300\n211381,北票市,4,211300\n211382,凌源市,4,211300\n211402,连山区,4,211400\n211403,龙港区,4,211400\n211404,南票区,4,211400\n211421,绥中县,4,211400\n211422,建昌县,4,211400\n211481,兴城市,4,211400\n220102,南关区,4,220100\n220103,宽城区,4,220100\n220104,朝阳区,4,220100\n220105,二道区,4,220100\n220106,绿园区,4,220100\n220112,双阳区,4,220100\n220113,九台区,4,220100\n220122,农安县,4,220100\n220171,长春经济技术开发区,4,220100\n220172,长春净月高新技术产业开发区,4,220100\n220173,长春高新技术产业开发区,4,220100\n220174,长春汽车经济技术开发区,4,220100\n220182,榆树市,4,220100\n220183,德惠市,4,220100\n220184,公主岭市,4,220100\n220202,昌邑区,4,220200\n220203,龙潭区,4,220200\n220204,船营区,4,220200\n220211,丰满区,4,220200\n220221,永吉县,4,220200\n220271,吉林经济开发区,4,220200\n220272,吉林高新技术产业开发区,4,220200\n220273,吉林中国新加坡食品区,4,220200\n220281,蛟河市,4,220200\n220282,桦甸市,4,220200\n220283,舒兰市,4,220200\n220284,磐石市,4,220200\n220302,铁西区,4,220300\n220303,铁东区,4,220300\n220322,梨树县,4,220300\n220323,伊通满族自治县,4,220300\n220382,双辽市,4,220300\n220402,龙山区,4,220400\n220403,西安区,4,220400\n220421,东丰县,4,220400\n220422,东辽县,4,220400\n220502,东昌区,4,220500\n220503,二道江区,4,220500\n220521,通化县,4,220500\n220523,辉南县,4,220500\n220524,柳河县,4,220500\n220581,梅河口市,4,220500\n220582,集安市,4,220500\n220602,浑江区,4,220600\n220605,江源区,4,220600\n220621,抚松县,4,220600\n220622,靖宇县,4,220600\n220623,长白朝鲜族自治县,4,220600\n220681,临江市,4,220600\n220702,宁江区,4,220700\n220721,前郭尔罗斯蒙古族自治县,4,220700\n220722,长岭县,4,220700\n220723,乾安县,4,220700\n220771,吉林松原经济开发区,4,220700\n220781,扶余市,4,220700\n220802,洮北区,4,220800\n220821,镇赉县,4,220800\n220822,通榆县,4,220800\n220871,吉林白城经济开发区,4,220800\n220881,洮南市,4,220800\n220882,大安市,4,220800\n222401,延吉市,4,222400\n222402,图们市,4,222400\n222403,敦化市,4,222400\n222404,珲春市,4,222400\n222405,龙井市,4,222400\n222406,和龙市,4,222400\n222424,汪清县,4,222400\n222426,安图县,4,222400\n230102,道里区,4,230100\n230103,南岗区,4,230100\n230104,道外区,4,230100\n230108,平房区,4,230100\n230109,松北区,4,230100\n230110,香坊区,4,230100\n230111,呼兰区,4,230100\n230112,阿城区,4,230100\n230113,双城区,4,230100\n230123,依兰县,4,230100\n230124,方正县,4,230100\n230125,宾县,4,230100\n230126,巴彦县,4,230100\n230127,木兰县,4,230100\n230128,通河县,4,230100\n230129,延寿县,4,230100\n230183,尚志市,4,230100\n230184,五常市,4,230100\n230202,龙沙区,4,230200\n230203,建华区,4,230200\n230204,铁锋区,4,230200\n230205,昂昂溪区,4,230200\n230206,富拉尔基区,4,230200\n230207,碾子山区,4,230200\n230208,梅里斯达斡尔族区,4,230200\n230221,龙江县,4,230200\n230223,依安县,4,230200\n230224,泰来县,4,230200\n230225,甘南县,4,230200\n230227,富裕县,4,230200\n230229,克山县,4,230200\n230230,克东县,4,230200\n230231,拜泉县,4,230200\n230281,讷河市,4,230200\n230302,鸡冠区,4,230300\n230303,恒山区,4,230300\n230304,滴道区,4,230300\n230305,梨树区,4,230300\n230306,城子河区,4,230300\n230307,麻山区,4,230300\n230321,鸡东县,4,230300\n230381,虎林市,4,230300\n230382,密山市,4,230300\n230402,向阳区,4,230400\n230403,工农区,4,230400\n230404,南山区,4,230400\n230405,兴安区,4,230400\n230406,东山区,4,230400\n230407,兴山区,4,230400\n230421,萝北县,4,230400\n230422,绥滨县,4,230400\n230502,尖山区,4,230500\n230503,岭东区,4,230500\n230505,四方台区,4,230500\n230506,宝山区,4,230500\n230521,集贤县,4,230500\n230522,友谊县,4,230500\n230523,宝清县,4,230500\n230524,饶河县,4,230500\n230602,萨尔图区,4,230600\n230603,龙凤区,4,230600\n230604,让胡路区,4,230600\n230605,红岗区,4,230600\n230606,大同区,4,230600\n230621,肇州县,4,230600\n230622,肇源县,4,230600\n230623,林甸县,4,230600\n230624,杜尔伯特蒙古族自治县,4,230600\n230671,大庆高新技术产业开发区,4,230600\n230717,伊美区,4,230700\n230718,乌翠区,4,230700\n230719,友好区,4,230700\n230722,嘉荫县,4,230700\n230723,汤旺县,4,230700\n230724,丰林县,4,230700\n230725,大箐山县,4,230700\n230726,南岔县,4,230700\n230751,金林区,4,230700\n230781,铁力市,4,230700\n230803,向阳区,4,230800\n230804,前进区,4,230800\n230805,东风区,4,230800\n230811,郊区,4,230800\n230822,桦南县,4,230800\n230826,桦川县,4,230800\n230828,汤原县,4,230800\n230881,同江市,4,230800\n230882,富锦市,4,230800\n230883,抚远市,4,230800\n230902,新兴区,4,230900\n230903,桃山区,4,230900\n230904,茄子河区,4,230900\n230921,勃利县,4,230900\n231002,东安区,4,231000\n231003,阳明区,4,231000\n231004,爱民区,4,231000\n231005,西安区,4,231000\n231025,林口县,4,231000\n231071,牡丹江经济技术开发区,4,231000\n231081,绥芬河市,4,231000\n231083,海林市,4,231000\n231084,宁安市,4,231000\n231085,穆棱市,4,231000\n231086,东宁市,4,231000\n231102,爱辉区,4,231100\n231123,逊克县,4,231100\n231124,孙吴县,4,231100\n231181,北安市,4,231100\n231182,五大连池市,4,231100\n231183,嫩江市,4,231100\n231202,北林区,4,231200\n231221,望奎县,4,231200\n231222,兰西县,4,231200\n231223,青冈县,4,231200\n231224,庆安县,4,231200\n231225,明水县,4,231200\n231226,绥棱县,4,231200\n231281,安达市,4,231200\n231282,肇东市,4,231200\n231283,海伦市,4,231200\n232701,漠河市,4,232700\n232721,呼玛县,4,232700\n232722,塔河县,4,232700\n232761,加格达奇区,4,232700\n232762,松岭区,4,232700\n232763,新林区,4,232700\n232764,呼中区,4,232700\n310101,黄浦区,4,310100\n310104,徐汇区,4,310100\n310105,长宁区,4,310100\n310106,静安区,4,310100\n310107,普陀区,4,310100\n310109,虹口区,4,310100\n310110,杨浦区,4,310100\n310112,闵行区,4,310100\n310113,宝山区,4,310100\n310114,嘉定区,4,310100\n310115,浦东新区,4,310100\n310116,金山区,4,310100\n310117,松江区,4,310100\n310118,青浦区,4,310100\n310120,奉贤区,4,310100\n310151,崇明区,4,310100\n320102,玄武区,4,320100\n320104,秦淮区,4,320100\n320105,建邺区,4,320100\n320106,鼓楼区,4,320100\n320111,浦口区,4,320100\n320113,栖霞区,4,320100\n320114,雨花台区,4,320100\n320115,江宁区,4,320100\n320116,六合区,4,320100\n320117,溧水区,4,320100\n320118,高淳区,4,320100\n320205,锡山区,4,320200\n320206,惠山区,4,320200\n320211,滨湖区,4,320200\n320213,梁溪区,4,320200\n320214,新吴区,4,320200\n320281,江阴市,4,320200\n320282,宜兴市,4,320200\n320302,鼓楼区,4,320300\n320303,云龙区,4,320300\n320305,贾汪区,4,320300\n320311,泉山区,4,320300\n320312,铜山区,4,320300\n320321,丰县,4,320300\n320322,沛县,4,320300\n320324,睢宁县,4,320300\n320371,徐州经济技术开发区,4,320300\n320381,新沂市,4,320300\n320382,邳州市,4,320300\n320402,天宁区,4,320400\n320404,钟楼区,4,320400\n320411,新北区,4,320400\n320412,武进区,4,320400\n320413,金坛区,4,320400\n320481,溧阳市,4,320400\n320505,虎丘区,4,320500\n320506,吴中区,4,320500\n320507,相城区,4,320500\n320508,姑苏区,4,320500\n320509,吴江区,4,320500\n320571,苏州工业园区,4,320500\n320581,常熟市,4,320500\n320582,张家港市,4,320500\n320583,昆山市,4,320500\n320585,太仓市,4,320500\n320612,通州区,4,320600\n320613,崇川区,4,320600\n320614,海门区,4,320600\n320623,如东县,4,320600\n320671,南通经济技术开发区,4,320600\n320681,启东市,4,320600\n320682,如皋市,4,320600\n320685,海安市,4,320600\n320703,连云区,4,320700\n320706,海州区,4,320700\n320707,赣榆区,4,320700\n320722,东海县,4,320700\n320723,灌云县,4,320700\n320724,灌南县,4,320700\n320771,连云港经济技术开发区,4,320700\n320772,连云港高新技术产业开发区,4,320700\n320803,淮安区,4,320800\n320804,淮阴区,4,320800\n320812,清江浦区,4,320800\n320813,洪泽区,4,320800\n320826,涟水县,4,320800\n320830,盱眙县,4,320800\n320831,金湖县,4,320800\n320871,淮安经济技术开发区,4,320800\n320902,亭湖区,4,320900\n320903,盐都区,4,320900\n320904,大丰区,4,320900\n320921,响水县,4,320900\n320922,滨海县,4,320900\n320923,阜宁县,4,320900\n320924,射阳县,4,320900\n320925,建湖县,4,320900\n320971,盐城经济技术开发区,4,320900\n320981,东台市,4,320900\n321002,广陵区,4,321000\n321003,邗江区,4,321000\n321012,江都区,4,321000\n321023,宝应县,4,321000\n321071,扬州经济技术开发区,4,321000\n321081,仪征市,4,321000\n321084,高邮市,4,321000\n321102,京口区,4,321100\n321111,润州区,4,321100\n321112,丹徒区,4,321100\n321171,镇江新区,4,321100\n321181,丹阳市,4,321100\n321182,扬中市,4,321100\n321183,句容市,4,321100\n321202,海陵区,4,321200\n321203,高港区,4,321200\n321204,姜堰区,4,321200\n321271,泰州医药高新技术产业开发区,4,321200\n321281,兴化市,4,321200\n321282,靖江市,4,321200\n321283,泰兴市,4,321200\n321302,宿城区,4,321300\n321311,宿豫区,4,321300\n321322,沭阳县,4,321300\n321323,泗阳县,4,321300\n321324,泗洪县,4,321300\n321371,宿迁经济技术开发区,4,321300\n330102,上城区,4,330100\n330105,拱墅区,4,330100\n330106,西湖区,4,330100\n330108,滨江区,4,330100\n330109,萧山区,4,330100\n330110,余杭区,4,330100\n330111,富阳区,4,330100\n330112,临安区,4,330100\n330113,临平区,4,330100\n330114,钱塘区,4,330100\n330122,桐庐县,4,330100\n330127,淳安县,4,330100\n330182,建德市,4,330100\n330203,海曙区,4,330200\n330205,江北区,4,330200\n330206,北仑区,4,330200\n330211,镇海区,4,330200\n330212,鄞州区,4,330200\n330213,奉化区,4,330200\n330225,象山县,4,330200\n330226,宁海县,4,330200\n330281,余姚市,4,330200\n330282,慈溪市,4,330200\n330302,鹿城区,4,330300\n330303,龙湾区,4,330300\n330304,瓯海区,4,330300\n330305,洞头区,4,330300\n330324,永嘉县,4,330300\n330326,平阳县,4,330300\n330327,苍南县,4,330300\n330328,文成县,4,330300\n330329,泰顺县,4,330300\n330371,温州经济技术开发区,4,330300\n330381,瑞安市,4,330300\n330382,乐清市,4,330300\n330383,龙港市,4,330300\n330402,南湖区,4,330400\n330411,秀洲区,4,330400\n330421,嘉善县,4,330400\n330424,海盐县,4,330400\n330481,海宁市,4,330400\n330482,平湖市,4,330400\n330483,桐乡市,4,330400\n330502,吴兴区,4,330500\n330503,南浔区,4,330500\n330521,德清县,4,330500\n330522,长兴县,4,330500\n330523,安吉县,4,330500\n330602,越城区,4,330600\n330603,柯桥区,4,330600\n330604,上虞区,4,330600\n330624,新昌县,4,330600\n330681,诸暨市,4,330600\n330683,嵊州市,4,330600\n330702,婺城区,4,330700\n330703,金东区,4,330700\n330723,武义县,4,330700\n330726,浦江县,4,330700\n330727,磐安县,4,330700\n330781,兰溪市,4,330700\n330782,义乌市,4,330700\n330783,东阳市,4,330700\n330784,永康市,4,330700\n330802,柯城区,4,330800\n330803,衢江区,4,330800\n330822,常山县,4,330800\n330824,开化县,4,330800\n330825,龙游县,4,330800\n330881,江山市,4,330800\n330902,定海区,4,330900\n330903,普陀区,4,330900\n330921,岱山县,4,330900\n330922,嵊泗县,4,330900\n331002,椒江区,4,331000\n331003,黄岩区,4,331000\n331004,路桥区,4,331000\n331022,三门县,4,331000\n331023,天台县,4,331000\n331024,仙居县,4,331000\n331081,温岭市,4,331000\n331082,临海市,4,331000\n331083,玉环市,4,331000\n331102,莲都区,4,331100\n331121,青田县,4,331100\n331122,缙云县,4,331100\n331123,遂昌县,4,331100\n331124,松阳县,4,331100\n331125,云和县,4,331100\n331126,庆元县,4,331100\n331127,景宁畲族自治县,4,331100\n331181,龙泉市,4,331100\n340102,瑶海区,4,340100\n340103,庐阳区,4,340100\n340104,蜀山区,4,340100\n340111,包河区,4,340100\n340121,长丰县,4,340100\n340122,肥东县,4,340100\n340123,肥西县,4,340100\n340124,庐江县,4,340100\n340171,合肥高新技术产业开发区,4,340100\n340172,合肥经济技术开发区,4,340100\n340173,合肥新站高新技术产业开发区,4,340100\n340181,巢湖市,4,340100\n340202,镜湖区,4,340200\n340207,鸠江区,4,340200\n340209,弋江区,4,340200\n340210,湾沚区,4,340200\n340212,繁昌区,4,340200\n340223,南陵县,4,340200\n340271,芜湖经济技术开发区,4,340200\n340272,安徽芜湖三山经济开发区,4,340200\n340281,无为市,4,340200\n340302,龙子湖区,4,340300\n340303,蚌山区,4,340300\n340304,禹会区,4,340300\n340311,淮上区,4,340300\n340321,怀远县,4,340300\n340322,五河县,4,340300\n340323,固镇县,4,340300\n340371,蚌埠市高新技术开发区,4,340300\n340372,蚌埠市经济开发区,4,340300\n340402,大通区,4,340400\n340403,田家庵区,4,340400\n340404,谢家集区,4,340400\n340405,八公山区,4,340400\n340406,潘集区,4,340400\n340421,凤台县,4,340400\n340422,寿县,4,340400\n340503,花山区,4,340500\n340504,雨山区,4,340500\n340506,博望区,4,340500\n340521,当涂县,4,340500\n340522,含山县,4,340500\n340523,和县,4,340500\n340602,杜集区,4,340600\n340603,相山区,4,340600\n340604,烈山区,4,340600\n340621,濉溪县,4,340600\n340705,铜官区,4,340700\n340706,义安区,4,340700\n340711,郊区,4,340700\n340722,枞阳县,4,340700\n340802,迎江区,4,340800\n340803,大观区,4,340800\n340811,宜秀区,4,340800\n340822,怀宁县,4,340800\n340825,太湖县,4,340800\n340826,宿松县,4,340800\n340827,望江县,4,340800\n340828,岳西县,4,340800\n340871,安徽安庆经济开发区,4,340800\n340881,桐城市,4,340800\n340882,潜山市,4,340800\n341002,屯溪区,4,341000\n341003,黄山区,4,341000\n341004,徽州区,4,341000\n341021,歙县,4,341000\n341022,休宁县,4,341000\n341023,黟县,4,341000\n341024,祁门县,4,341000\n341102,琅琊区,4,341100\n341103,南谯区,4,341100\n341122,来安县,4,341100\n341124,全椒县,4,341100\n341125,定远县,4,341100\n341126,凤阳县,4,341100\n341171,中新苏滁高新技术产业开发区,4,341100\n341172,滁州经济技术开发区,4,341100\n341181,天长市,4,341100\n341182,明光市,4,341100\n341202,颍州区,4,341200\n341203,颍东区,4,341200\n341204,颍泉区,4,341200\n341221,临泉县,4,341200\n341222,太和县,4,341200\n341225,阜南县,4,341200\n341226,颍上县,4,341200\n341271,阜阳合肥现代产业园区,4,341200\n341272,阜阳经济技术开发区,4,341200\n341282,界首市,4,341200\n341302,埇桥区,4,341300\n341321,砀山县,4,341300\n341322,萧县,4,341300\n341323,灵璧县,4,341300\n341324,泗县,4,341300\n341371,宿州马鞍山现代产业园区,4,341300\n341372,宿州经济技术开发区,4,341300\n341502,金安区,4,341500\n341503,裕安区,4,341500\n341504,叶集区,4,341500\n341522,霍邱县,4,341500\n341523,舒城县,4,341500\n341524,金寨县,4,341500\n341525,霍山县,4,341500\n341602,谯城区,4,341600\n341621,涡阳县,4,341600\n341622,蒙城县,4,341600\n341623,利辛县,4,341600\n341702,贵池区,4,341700\n341721,东至县,4,341700\n341722,石台县,4,341700\n341723,青阳县,4,341700\n341802,宣州区,4,341800\n341821,郎溪县,4,341800\n341823,泾县,4,341800\n341824,绩溪县,4,341800\n341825,旌德县,4,341800\n341871,宣城市经济开发区,4,341800\n341881,宁国市,4,341800\n341882,广德市,4,341800\n350102,鼓楼区,4,350100\n350103,台江区,4,350100\n350104,仓山区,4,350100\n350105,马尾区,4,350100\n350111,晋安区,4,350100\n350112,长乐区,4,350100\n350121,闽侯县,4,350100\n350122,连江县,4,350100\n350123,罗源县,4,350100\n350124,闽清县,4,350100\n350125,永泰县,4,350100\n350128,平潭县,4,350100\n350181,福清市,4,350100\n350203,思明区,4,350200\n350205,海沧区,4,350200\n350206,湖里区,4,350200\n350211,集美区,4,350200\n350212,同安区,4,350200\n350213,翔安区,4,350200\n350302,城厢区,4,350300\n350303,涵江区,4,350300\n350304,荔城区,4,350300\n350305,秀屿区,4,350300\n350322,仙游县,4,350300\n350404,三元区,4,350400\n350405,沙县区,4,350400\n350421,明溪县,4,350400\n350423,清流县,4,350400\n350424,宁化县,4,350400\n350425,大田县,4,350400\n350426,尤溪县,4,350400\n350428,将乐县,4,350400\n350429,泰宁县,4,350400\n350430,建宁县,4,350400\n350481,永安市,4,350400\n350502,鲤城区,4,350500\n350503,丰泽区,4,350500\n350504,洛江区,4,350500\n350505,泉港区,4,350500\n350521,惠安县,4,350500\n350524,安溪县,4,350500\n350525,永春县,4,350500\n350526,德化县,4,350500\n350527,金门县,4,350500\n350581,石狮市,4,350500\n350582,晋江市,4,350500\n350583,南安市,4,350500\n350602,芗城区,4,350600\n350603,龙文区,4,350600\n350604,龙海区,4,350600\n350605,长泰区,4,350600\n350622,云霄县,4,350600\n350623,漳浦县,4,350600\n350624,诏安县,4,350600\n350626,东山县,4,350600\n350627,南靖县,4,350600\n350628,平和县,4,350600\n350629,华安县,4,350600\n350702,延平区,4,350700\n350703,建阳区,4,350700\n350721,顺昌县,4,350700\n350722,浦城县,4,350700\n350723,光泽县,4,350700\n350724,松溪县,4,350700\n350725,政和县,4,350700\n350781,邵武市,4,350700\n350782,武夷山市,4,350700\n350783,建瓯市,4,350700\n350802,新罗区,4,350800\n350803,永定区,4,350800\n350821,长汀县,4,350800\n350823,上杭县,4,350800\n350824,武平县,4,350800\n350825,连城县,4,350800\n350881,漳平市,4,350800\n350902,蕉城区,4,350900\n350921,霞浦县,4,350900\n350922,古田县,4,350900\n350923,屏南县,4,350900\n350924,寿宁县,4,350900\n350925,周宁县,4,350900\n350926,柘荣县,4,350900\n350981,福安市,4,350900\n350982,福鼎市,4,350900\n360102,东湖区,4,360100\n360103,西湖区,4,360100\n360104,青云谱区,4,360100\n360111,青山湖区,4,360100\n360112,新建区,4,360100\n360113,红谷滩区,4,360100\n360121,南昌县,4,360100\n360123,安义县,4,360100\n360124,进贤县,4,360100\n360202,昌江区,4,360200\n360203,珠山区,4,360200\n360222,浮梁县,4,360200\n360281,乐平市,4,360200\n360302,安源区,4,360300\n360313,湘东区,4,360300\n360321,莲花县,4,360300\n360322,上栗县,4,360300\n360323,芦溪县,4,360300\n360402,濂溪区,4,360400\n360403,浔阳区,4,360400\n360404,柴桑区,4,360400\n360423,武宁县,4,360400\n360424,修水县,4,360400\n360425,永修县,4,360400\n360426,德安县,4,360400\n360428,都昌县,4,360400\n360429,湖口县,4,360400\n360430,彭泽县,4,360400\n360481,瑞昌市,4,360400\n360482,共青城市,4,360400\n360483,庐山市,4,360400\n360502,渝水区,4,360500\n360521,分宜县,4,360500\n360602,月湖区,4,360600\n360603,余江区,4,360600\n360681,贵溪市,4,360600\n360702,章贡区,4,360700\n360703,南康区,4,360700\n360704,赣县区,4,360700\n360722,信丰县,4,360700\n360723,大余县,4,360700\n360724,上犹县,4,360700\n360725,崇义县,4,360700\n360726,安远县,4,360700\n360728,定南县,4,360700\n360729,全南县,4,360700\n360730,宁都县,4,360700\n360731,于都县,4,360700\n360732,兴国县,4,360700\n360733,会昌县,4,360700\n360734,寻乌县,4,360700\n360735,石城县,4,360700\n360781,瑞金市,4,360700\n360783,龙南市,4,360700\n360802,吉州区,4,360800\n360803,青原区,4,360800\n360821,吉安县,4,360800\n360822,吉水县,4,360800\n360823,峡江县,4,360800\n360824,新干县,4,360800\n360825,永丰县,4,360800\n360826,泰和县,4,360800\n360827,遂川县,4,360800\n360828,万安县,4,360800\n360829,安福县,4,360800\n360830,永新县,4,360800\n360881,井冈山市,4,360800\n360902,袁州区,4,360900\n360921,奉新县,4,360900\n360922,万载县,4,360900\n360923,上高县,4,360900\n360924,宜丰县,4,360900\n360925,靖安县,4,360900\n360926,铜鼓县,4,360900\n360981,丰城市,4,360900\n360982,樟树市,4,360900\n360983,高安市,4,360900\n361002,临川区,4,361000\n361003,东乡区,4,361000\n361021,南城县,4,361000\n361022,黎川县,4,361000\n361023,南丰县,4,361000\n361024,崇仁县,4,361000\n361025,乐安县,4,361000\n361026,宜黄县,4,361000\n361027,金溪县,4,361000\n361028,资溪县,4,361000\n361030,广昌县,4,361000\n361102,信州区,4,361100\n361103,广丰区,4,361100\n361104,广信区,4,361100\n361123,玉山县,4,361100\n361124,铅山县,4,361100\n361125,横峰县,4,361100\n361126,弋阳县,4,361100\n361127,余干县,4,361100\n361128,鄱阳县,4,361100\n361129,万年县,4,361100\n361130,婺源县,4,361100\n361181,德兴市,4,361100\n370102,历下区,4,370100\n370103,市中区,4,370100\n370104,槐荫区,4,370100\n370105,天桥区,4,370100\n370112,历城区,4,370100\n370113,长清区,4,370100\n370114,章丘区,4,370100\n370115,济阳区,4,370100\n370116,莱芜区,4,370100\n370117,钢城区,4,370100\n370124,平阴县,4,370100\n370126,商河县,4,370100\n370171,济南高新技术产业开发区,4,370100\n370202,市南区,4,370200\n370203,市北区,4,370200\n370211,黄岛区,4,370200\n370212,崂山区,4,370200\n370213,李沧区,4,370200\n370214,城阳区,4,370200\n370215,即墨区,4,370200\n370271,青岛高新技术产业开发区,4,370200\n370281,胶州市,4,370200\n370283,平度市,4,370200\n370285,莱西市,4,370200\n370302,淄川区,4,370300\n370303,张店区,4,370300\n370304,博山区,4,370300\n370305,临淄区,4,370300\n370306,周村区,4,370300\n370321,桓台县,4,370300\n370322,高青县,4,370300\n370323,沂源县,4,370300\n370402,市中区,4,370400\n370403,薛城区,4,370400\n370404,峄城区,4,370400\n370405,台儿庄区,4,370400\n370406,山亭区,4,370400\n370481,滕州市,4,370400\n370502,东营区,4,370500\n370503,河口区,4,370500\n370505,垦利区,4,370500\n370522,利津县,4,370500\n370523,广饶县,4,370500\n370571,东营经济技术开发区,4,370500\n370572,东营港经济开发区,4,370500\n370602,芝罘区,4,370600\n370611,福山区,4,370600\n370612,牟平区,4,370600\n370613,莱山区,4,370600\n370614,蓬莱区,4,370600\n370671,烟台高新技术产业开发区,4,370600\n370672,烟台经济技术开发区,4,370600\n370681,龙口市,4,370600\n370682,莱阳市,4,370600\n370683,莱州市,4,370600\n370685,招远市,4,370600\n370686,栖霞市,4,370600\n370687,海阳市,4,370600\n370702,潍城区,4,370700\n370703,寒亭区,4,370700\n370704,坊子区,4,370700\n370705,奎文区,4,370700\n370724,临朐县,4,370700\n370725,昌乐县,4,370700\n370772,潍坊滨海经济技术开发区,4,370700\n370781,青州市,4,370700\n370782,诸城市,4,370700\n370783,寿光市,4,370700\n370784,安丘市,4,370700\n370785,高密市,4,370700\n370786,昌邑市,4,370700\n370811,任城区,4,370800\n370812,兖州区,4,370800\n370826,微山县,4,370800\n370827,鱼台县,4,370800\n370828,金乡县,4,370800\n370829,嘉祥县,4,370800\n370830,汶上县,4,370800\n370831,泗水县,4,370800\n370832,梁山县,4,370800\n370871,济宁高新技术产业开发区,4,370800\n370881,曲阜市,4,370800\n370883,邹城市,4,370800\n370902,泰山区,4,370900\n370911,岱岳区,4,370900\n370921,宁阳县,4,370900\n370923,东平县,4,370900\n370982,新泰市,4,370900\n370983,肥城市,4,370900\n371002,环翠区,4,371000\n371003,文登区,4,371000\n371071,威海火炬高技术产业开发区,4,371000\n371072,威海经济技术开发区,4,371000\n371073,威海临港经济技术开发区,4,371000\n371082,荣成市,4,371000\n371083,乳山市,4,371000\n371102,东港区,4,371100\n371103,岚山区,4,371100\n371121,五莲县,4,371100\n371122,莒县,4,371100\n371171,日照经济技术开发区,4,371100\n371302,兰山区,4,371300\n371311,罗庄区,4,371300\n371312,河东区,4,371300\n371321,沂南县,4,371300\n371322,郯城县,4,371300\n371323,沂水县,4,371300\n371324,兰陵县,4,371300\n371325,费县,4,371300\n371326,平邑县,4,371300\n371327,莒南县,4,371300\n371328,蒙阴县,4,371300\n371329,临沭县,4,371300\n371371,临沂高新技术产业开发区,4,371300\n371402,德城区,4,371400\n371403,陵城区,4,371400\n371422,宁津县,4,371400\n371423,庆云县,4,371400\n371424,临邑县,4,371400\n371425,齐河县,4,371400\n371426,平原县,4,371400\n371427,夏津县,4,371400\n371428,武城县,4,371400\n371471,德州经济技术开发区,4,371400\n371472,德州运河经济开发区,4,371400\n371481,乐陵市,4,371400\n371482,禹城市,4,371400\n371502,东昌府区,4,371500\n371503,茌平区,4,371500\n371521,阳谷县,4,371500\n371522,莘县,4,371500\n371524,东阿县,4,371500\n371525,冠县,4,371500\n371526,高唐县,4,371500\n371581,临清市,4,371500\n371602,滨城区,4,371600\n371603,沾化区,4,371600\n371621,惠民县,4,371600\n371622,阳信县,4,371600\n371623,无棣县,4,371600\n371625,博兴县,4,371600\n371681,邹平市,4,371600\n371702,牡丹区,4,371700\n371703,定陶区,4,371700\n371721,曹县,4,371700\n371722,单县,4,371700\n371723,成武县,4,371700\n371724,巨野县,4,371700\n371725,郓城县,4,371700\n371726,鄄城县,4,371700\n371728,东明县,4,371700\n371771,菏泽经济技术开发区,4,371700\n371772,菏泽高新技术开发区,4,371700\n410102,中原区,4,410100\n410103,二七区,4,410100\n410104,管城回族区,4,410100\n410105,金水区,4,410100\n410106,上街区,4,410100\n410108,惠济区,4,410100\n410122,中牟县,4,410100\n410171,郑州经济技术开发区,4,410100\n410172,郑州高新技术产业开发区,4,410100\n410173,郑州航空港经济综合实验区,4,410100\n410181,巩义市,4,410100\n410182,荥阳市,4,410100\n410183,新密市,4,410100\n410184,新郑市,4,410100\n410185,登封市,4,410100\n410202,龙亭区,4,410200\n410203,顺河回族区,4,410200\n410204,鼓楼区,4,410200\n410205,禹王台区,4,410200\n410212,祥符区,4,410200\n410221,杞县,4,410200\n410222,通许县,4,410200\n410223,尉氏县,4,410200\n410225,兰考县,4,410200\n410302,老城区,4,410300\n410303,西工区,4,410300\n410304,瀍河回族区,4,410300\n410305,涧西区,4,410300\n410307,偃师区,4,410300\n410308,孟津区,4,410300\n410311,洛龙区,4,410300\n410323,新安县,4,410300\n410324,栾川县,4,410300\n410325,嵩县,4,410300\n410326,汝阳县,4,410300\n410327,宜阳县,4,410300\n410328,洛宁县,4,410300\n410329,伊川县,4,410300\n410371,洛阳高新技术产业开发区,4,410300\n410402,新华区,4,410400\n410403,卫东区,4,410400\n410404,石龙区,4,410400\n410411,湛河区,4,410400\n410421,宝丰县,4,410400\n410422,叶县,4,410400\n410423,鲁山县,4,410400\n410425,郏县,4,410400\n410471,平顶山高新技术产业开发区,4,410400\n410472,平顶山市城乡一体化示范区,4,410400\n410481,舞钢市,4,410400\n410482,汝州市,4,410400\n410502,文峰区,4,410500\n410503,北关区,4,410500\n410505,殷都区,4,410500\n410506,龙安区,4,410500\n410522,安阳县,4,410500\n410523,汤阴县,4,410500\n410526,滑县,4,410500\n410527,内黄县,4,410500\n410571,安阳高新技术产业开发区,4,410500\n410581,林州市,4,410500\n410602,鹤山区,4,410600\n410603,山城区,4,410600\n410611,淇滨区,4,410600\n410621,浚县,4,410600\n410622,淇县,4,410600\n410671,鹤壁经济技术开发区,4,410600\n410702,红旗区,4,410700\n410703,卫滨区,4,410700\n410704,凤泉区,4,410700\n410711,牧野区,4,410700\n410721,新乡县,4,410700\n410724,获嘉县,4,410700\n410725,原阳县,4,410700\n410726,延津县,4,410700\n410727,封丘县,4,410700\n410771,新乡高新技术产业开发区,4,410700\n410772,新乡经济技术开发区,4,410700\n410773,新乡市平原城乡一体化示范区,4,410700\n410781,卫辉市,4,410700\n410782,辉县市,4,410700\n410783,长垣市,4,410700\n410802,解放区,4,410800\n410803,中站区,4,410800\n410804,马村区,4,410800\n410811,山阳区,4,410800\n410821,修武县,4,410800\n410822,博爱县,4,410800\n410823,武陟县,4,410800\n410825,温县,4,410800\n410871,焦作城乡一体化示范区,4,410800\n410882,沁阳市,4,410800\n410883,孟州市,4,410800\n410902,华龙区,4,410900\n410922,清丰县,4,410900\n410923,南乐县,4,410900\n410926,范县,4,410900\n410927,台前县,4,410900\n410928,濮阳县,4,410900\n410971,河南濮阳工业园区,4,410900\n410972,濮阳经济技术开发区,4,410900\n411002,魏都区,4,411000\n411003,建安区,4,411000\n411024,鄢陵县,4,411000\n411025,襄城县,4,411000\n411071,许昌经济技术开发区,4,411000\n411081,禹州市,4,411000\n411082,长葛市,4,411000\n411102,源汇区,4,411100\n411103,郾城区,4,411100\n411104,召陵区,4,411100\n411121,舞阳县,4,411100\n411122,临颍县,4,411100\n411171,漯河经济技术开发区,4,411100\n411202,湖滨区,4,411200\n411203,陕州区,4,411200\n411221,渑池县,4,411200\n411224,卢氏县,4,411200\n411271,河南三门峡经济开发区,4,411200\n411281,义马市,4,411200\n411282,灵宝市,4,411200\n411302,宛城区,4,411300\n411303,卧龙区,4,411300\n411321,南召县,4,411300\n411322,方城县,4,411300\n411323,西峡县,4,411300\n411324,镇平县,4,411300\n411325,内乡县,4,411300\n411326,淅川县,4,411300\n411327,社旗县,4,411300\n411328,唐河县,4,411300\n411329,新野县,4,411300\n411330,桐柏县,4,411300\n411371,南阳高新技术产业开发区,4,411300\n411372,南阳市城乡一体化示范区,4,411300\n411381,邓州市,4,411300\n411402,梁园区,4,411400\n411403,睢阳区,4,411400\n411421,民权县,4,411400\n411422,睢县,4,411400\n411423,宁陵县,4,411400\n411424,柘城县,4,411400\n411425,虞城县,4,411400\n411426,夏邑县,4,411400\n411471,豫东综合物流产业聚集区,4,411400\n411472,河南商丘经济开发区,4,411400\n411481,永城市,4,411400\n411502,浉河区,4,411500\n411503,平桥区,4,411500\n411521,罗山县,4,411500\n411522,光山县,4,411500\n411523,新县,4,411500\n411524,商城县,4,411500\n411525,固始县,4,411500\n411526,潢川县,4,411500\n411527,淮滨县,4,411500\n411528,息县,4,411500\n411571,信阳高新技术产业开发区,4,411500\n411602,川汇区,4,411600\n411603,淮阳区,4,411600\n411621,扶沟县,4,411600\n411622,西华县,4,411600\n411623,商水县,4,411600\n411624,沈丘县,4,411600\n411625,郸城县,4,411600\n411627,太康县,4,411600\n411628,鹿邑县,4,411600\n411671,河南周口经济开发区,4,411600\n411681,项城市,4,411600\n411702,驿城区,4,411700\n411721,西平县,4,411700\n411722,上蔡县,4,411700\n411723,平舆县,4,411700\n411724,正阳县,4,411700\n411725,确山县,4,411700\n411726,泌阳县,4,411700\n411727,汝南县,4,411700\n411728,遂平县,4,411700\n411729,新蔡县,4,411700\n411771,河南驻马店经济开发区,4,411700\n419001,济源市,4,419000\n420102,江岸区,4,420100\n420103,江汉区,4,420100\n420104,硚口区,4,420100\n420105,汉阳区,4,420100\n420106,武昌区,4,420100\n420107,青山区,4,420100\n420111,洪山区,4,420100\n420112,东西湖区,4,420100\n420113,汉南区,4,420100\n420114,蔡甸区,4,420100\n420115,江夏区,4,420100\n420116,黄陂区,4,420100\n420117,新洲区,4,420100\n420202,黄石港区,4,420200\n420203,西塞山区,4,420200\n420204,下陆区,4,420200\n420205,铁山区,4,420200\n420222,阳新县,4,420200\n420281,大冶市,4,420200\n420302,茅箭区,4,420300\n420303,张湾区,4,420300\n420304,郧阳区,4,420300\n420322,郧西县,4,420300\n420323,竹山县,4,420300\n420324,竹溪县,4,420300\n420325,房县,4,420300\n420381,丹江口市,4,420300\n420502,西陵区,4,420500\n420503,伍家岗区,4,420500\n420504,点军区,4,420500\n420505,猇亭区,4,420500\n420506,夷陵区,4,420500\n420525,远安县,4,420500\n420526,兴山县,4,420500\n420527,秭归县,4,420500\n420528,长阳土家族自治县,4,420500\n420529,五峰土家族自治县,4,420500\n420581,宜都市,4,420500\n420582,当阳市,4,420500\n420583,枝江市,4,420500\n420602,襄城区,4,420600\n420606,樊城区,4,420600\n420607,襄州区,4,420600\n420624,南漳县,4,420600\n420625,谷城县,4,420600\n420626,保康县,4,420600\n420682,老河口市,4,420600\n420683,枣阳市,4,420600\n420684,宜城市,4,420600\n420702,梁子湖区,4,420700\n420703,华容区,4,420700\n420704,鄂城区,4,420700\n420802,东宝区,4,420800\n420804,掇刀区,4,420800\n420822,沙洋县,4,420800\n420881,钟祥市,4,420800\n420882,京山市,4,420800\n420902,孝南区,4,420900\n420921,孝昌县,4,420900\n420922,大悟县,4,420900\n420923,云梦县,4,420900\n420981,应城市,4,420900\n420982,安陆市,4,420900\n420984,汉川市,4,420900\n421002,沙市区,4,421000\n421003,荆州区,4,421000\n421022,公安县,4,421000\n421024,江陵县,4,421000\n421071,荆州经济技术开发区,4,421000\n421081,石首市,4,421000\n421083,洪湖市,4,421000\n421087,松滋市,4,421000\n421088,监利市,4,421000\n421102,黄州区,4,421100\n421121,团风县,4,421100\n421122,红安县,4,421100\n421123,罗田县,4,421100\n421124,英山县,4,421100\n421125,浠水县,4,421100\n421126,蕲春县,4,421100\n421127,黄梅县,4,421100\n421171,龙感湖管理区,4,421100\n421181,麻城市,4,421100\n421182,武穴市,4,421100\n421202,咸安区,4,421200\n421221,嘉鱼县,4,421200\n421222,通城县,4,421200\n421223,崇阳县,4,421200\n421224,通山县,4,421200\n421281,赤壁市,4,421200\n421303,曾都区,4,421300\n421321,随县,4,421300\n421381,广水市,4,421300\n422801,恩施市,4,422800\n422802,利川市,4,422800\n422822,建始县,4,422800\n422823,巴东县,4,422800\n422825,宣恩县,4,422800\n422826,咸丰县,4,422800\n422827,来凤县,4,422800\n422828,鹤峰县,4,422800\n429004,仙桃市,4,429000\n429005,潜江市,4,429000\n429006,天门市,4,429000\n429021,神农架林区,4,429000\n430102,芙蓉区,4,430100\n430103,天心区,4,430100\n430104,岳麓区,4,430100\n430105,开福区,4,430100\n430111,雨花区,4,430100\n430112,望城区,4,430100\n430121,长沙县,4,430100\n430181,浏阳市,4,430100\n430182,宁乡市,4,430100\n430202,荷塘区,4,430200\n430203,芦淞区,4,430200\n430204,石峰区,4,430200\n430211,天元区,4,430200\n430212,渌口区,4,430200\n430223,攸县,4,430200\n430224,茶陵县,4,430200\n430225,炎陵县,4,430200\n430271,云龙示范区,4,430200\n430281,醴陵市,4,430200\n430302,雨湖区,4,430300\n430304,岳塘区,4,430300\n430321,湘潭县,4,430300\n430371,湖南湘潭高新技术产业园区,4,430300\n430372,湘潭昭山示范区,4,430300\n430373,湘潭九华示范区,4,430300\n430381,湘乡市,4,430300\n430382,韶山市,4,430300\n430405,珠晖区,4,430400\n430406,雁峰区,4,430400\n430407,石鼓区,4,430400\n430408,蒸湘区,4,430400\n430412,南岳区,4,430400\n430421,衡阳县,4,430400\n430422,衡南县,4,430400\n430423,衡山县,4,430400\n430424,衡东县,4,430400\n430426,祁东县,4,430400\n430471,衡阳综合保税区,4,430400\n430472,湖南衡阳高新技术产业园区,4,430400\n430473,湖南衡阳松木经济开发区,4,430400\n430481,耒阳市,4,430400\n430482,常宁市,4,430400\n430502,双清区,4,430500\n430503,大祥区,4,430500\n430511,北塔区,4,430500\n430522,新邵县,4,430500\n430523,邵阳县,4,430500\n430524,隆回县,4,430500\n430525,洞口县,4,430500\n430527,绥宁县,4,430500\n430528,新宁县,4,430500\n430529,城步苗族自治县,4,430500\n430581,武冈市,4,430500\n430582,邵东市,4,430500\n430602,岳阳楼区,4,430600\n430603,云溪区,4,430600\n430611,君山区,4,430600\n430621,岳阳县,4,430600\n430623,华容县,4,430600\n430624,湘阴县,4,430600\n430626,平江县,4,430600\n430671,岳阳市屈原管理区,4,430600\n430681,汨罗市,4,430600\n430682,临湘市,4,430600\n430702,武陵区,4,430700\n430703,鼎城区,4,430700\n430721,安乡县,4,430700\n430722,汉寿县,4,430700\n430723,澧县,4,430700\n430724,临澧县,4,430700\n430725,桃源县,4,430700\n430726,石门县,4,430700\n430771,常德市西洞庭管理区,4,430700\n430781,津市市,4,430700\n430802,永定区,4,430800\n430811,武陵源区,4,430800\n430821,慈利县,4,430800\n430822,桑植县,4,430800\n430902,资阳区,4,430900\n430903,赫山区,4,430900\n430921,南县,4,430900\n430922,桃江县,4,430900\n430923,安化县,4,430900\n430971,益阳市大通湖管理区,4,430900\n430972,湖南益阳高新技术产业园区,4,430900\n430981,沅江市,4,430900\n431002,北湖区,4,431000\n431003,苏仙区,4,431000\n431021,桂阳县,4,431000\n431022,宜章县,4,431000\n431023,永兴县,4,431000\n431024,嘉禾县,4,431000\n431025,临武县,4,431000\n431026,汝城县,4,431000\n431027,桂东县,4,431000\n431028,安仁县,4,431000\n431081,资兴市,4,431000\n431102,零陵区,4,431100\n431103,冷水滩区,4,431100\n431122,东安县,4,431100\n431123,双牌县,4,431100\n431124,道县,4,431100\n431125,江永县,4,431100\n431126,宁远县,4,431100\n431127,蓝山县,4,431100\n431128,新田县,4,431100\n431129,江华瑶族自治县,4,431100\n431171,永州经济技术开发区,4,431100\n431173,永州市回龙圩管理区,4,431100\n431181,祁阳市,4,431100\n431202,鹤城区,4,431200\n431221,中方县,4,431200\n431222,沅陵县,4,431200\n431223,辰溪县,4,431200\n431224,溆浦县,4,431200\n431225,会同县,4,431200\n431226,麻阳苗族自治县,4,431200\n431227,新晃侗族自治县,4,431200\n431228,芷江侗族自治县,4,431200\n431229,靖州苗族侗族自治县,4,431200\n431230,通道侗族自治县,4,431200\n431271,怀化市洪江管理区,4,431200\n431281,洪江市,4,431200\n431302,娄星区,4,431300\n431321,双峰县,4,431300\n431322,新化县,4,431300\n431381,冷水江市,4,431300\n431382,涟源市,4,431300\n433101,吉首市,4,433100\n433122,泸溪县,4,433100\n433123,凤凰县,4,433100\n433124,花垣县,4,433100\n433125,保靖县,4,433100\n433126,古丈县,4,433100\n433127,永顺县,4,433100\n433130,龙山县,4,433100\n440103,荔湾区,4,440100\n440104,越秀区,4,440100\n440105,海珠区,4,440100\n440106,天河区,4,440100\n440111,白云区,4,440100\n440112,黄埔区,4,440100\n440113,番禺区,4,440100\n440114,花都区,4,440100\n440115,南沙区,4,440100\n440117,从化区,4,440100\n440118,增城区,4,440100\n440203,武江区,4,440200\n440204,浈江区,4,440200\n440205,曲江区,4,440200\n440222,始兴县,4,440200\n440224,仁化县,4,440200\n440229,翁源县,4,440200\n440232,乳源瑶族自治县,4,440200\n440233,新丰县,4,440200\n440281,乐昌市,4,440200\n440282,南雄市,4,440200\n440303,罗湖区,4,440300\n440304,福田区,4,440300\n440305,南山区,4,440300\n440306,宝安区,4,440300\n440307,龙岗区,4,440300\n440308,盐田区,4,440300\n440309,龙华区,4,440300\n440310,坪山区,4,440300\n440311,光明区,4,440300\n440402,香洲区,4,440400\n440403,斗门区,4,440400\n440404,金湾区,4,440400\n440507,龙湖区,4,440500\n440511,金平区,4,440500\n440512,濠江区,4,440500\n440513,潮阳区,4,440500\n440514,潮南区,4,440500\n440515,澄海区,4,440500\n440523,南澳县,4,440500\n440604,禅城区,4,440600\n440605,南海区,4,440600\n440606,顺德区,4,440600\n440607,三水区,4,440600\n440608,高明区,4,440600\n440703,蓬江区,4,440700\n440704,江海区,4,440700\n440705,新会区,4,440700\n440781,台山市,4,440700\n440783,开平市,4,440700\n440784,鹤山市,4,440700\n440785,恩平市,4,440700\n440802,赤坎区,4,440800\n440803,霞山区,4,440800\n440804,坡头区,4,440800\n440811,麻章区,4,440800\n440823,遂溪县,4,440800\n440825,徐闻县,4,440800\n440881,廉江市,4,440800\n440882,雷州市,4,440800\n440883,吴川市,4,440800\n440902,茂南区,4,440900\n440904,电白区,4,440900\n440981,高州市,4,440900\n440982,化州市,4,440900\n440983,信宜市,4,440900\n441202,端州区,4,441200\n441203,鼎湖区,4,441200\n441204,高要区,4,441200\n441223,广宁县,4,441200\n441224,怀集县,4,441200\n441225,封开县,4,441200\n441226,德庆县,4,441200\n441284,四会市,4,441200\n441302,惠城区,4,441300\n441303,惠阳区,4,441300\n441322,博罗县,4,441300\n441323,惠东县,4,441300\n441324,龙门县,4,441300\n441402,梅江区,4,441400\n441403,梅县区,4,441400\n441422,大埔县,4,441400\n441423,丰顺县,4,441400\n441424,五华县,4,441400\n441426,平远县,4,441400\n441427,蕉岭县,4,441400\n441481,兴宁市,4,441400\n441502,城区,4,441500\n441521,海丰县,4,441500\n441523,陆河县,4,441500\n441581,陆丰市,4,441500\n441602,源城区,4,441600\n441621,紫金县,4,441600\n441622,龙川县,4,441600\n441623,连平县,4,441600\n441624,和平县,4,441600\n441625,东源县,4,441600\n441702,江城区,4,441700\n441704,阳东区,4,441700\n441721,阳西县,4,441700\n441781,阳春市,4,441700\n441802,清城区,4,441800\n441803,清新区,4,441800\n441821,佛冈县,4,441800\n441823,阳山县,4,441800\n441825,连山壮族瑶族自治县,4,441800\n441826,连南瑶族自治县,4,441800\n441881,英德市,4,441800\n441882,连州市,4,441800\n445102,湘桥区,4,445100\n445103,潮安区,4,445100\n445122,饶平县,4,445100\n445202,榕城区,4,445200\n445203,揭东区,4,445200\n445222,揭西县,4,445200\n445224,惠来县,4,445200\n445281,普宁市,4,445200\n445302,云城区,4,445300\n445303,云安区,4,445300\n445321,新兴县,4,445300\n445322,郁南县,4,445300\n445381,罗定市,4,445300\n450102,兴宁区,4,450100\n450103,青秀区,4,450100\n450105,江南区,4,450100\n450107,西乡塘区,4,450100\n450108,良庆区,4,450100\n450109,邕宁区,4,450100\n450110,武鸣区,4,450100\n450123,隆安县,4,450100\n450124,马山县,4,450100\n450125,上林县,4,450100\n450126,宾阳县,4,450100\n450181,横州市,4,450100\n450202,城中区,4,450200\n450203,鱼峰区,4,450200\n450204,柳南区,4,450200\n450205,柳北区,4,450200\n450206,柳江区,4,450200\n450222,柳城县,4,450200\n450223,鹿寨县,4,450200\n450224,融安县,4,450200\n450225,融水苗族自治县,4,450200\n450226,三江侗族自治县,4,450200\n450302,秀峰区,4,450300\n450303,叠彩区,4,450300\n450304,象山区,4,450300\n450305,七星区,4,450300\n450311,雁山区,4,450300\n450312,临桂区,4,450300\n450321,阳朔县,4,450300\n450323,灵川县,4,450300\n450324,全州县,4,450300\n450325,兴安县,4,450300\n450326,永福县,4,450300\n450327,灌阳县,4,450300\n450328,龙胜各族自治县,4,450300\n450329,资源县,4,450300\n450330,平乐县,4,450300\n450332,恭城瑶族自治县,4,450300\n450381,荔浦市,4,450300\n450403,万秀区,4,450400\n450405,长洲区,4,450400\n450406,龙圩区,4,450400\n450421,苍梧县,4,450400\n450422,藤县,4,450400\n450423,蒙山县,4,450400\n450481,岑溪市,4,450400\n450502,海城区,4,450500\n450503,银海区,4,450500\n450512,铁山港区,4,450500\n450521,合浦县,4,450500\n450602,港口区,4,450600\n450603,防城区,4,450600\n450621,上思县,4,450600\n450681,东兴市,4,450600\n450702,钦南区,4,450700\n450703,钦北区,4,450700\n450721,灵山县,4,450700\n450722,浦北县,4,450700\n450802,港北区,4,450800\n450803,港南区,4,450800\n450804,覃塘区,4,450800\n450821,平南县,4,450800\n450881,桂平市,4,450800\n450902,玉州区,4,450900\n450903,福绵区,4,450900\n450921,容县,4,450900\n450922,陆川县,4,450900\n450923,博白县,4,450900\n450924,兴业县,4,450900\n450981,北流市,4,450900\n451002,右江区,4,451000\n451003,田阳区,4,451000\n451022,田东县,4,451000\n451024,德保县,4,451000\n451026,那坡县,4,451000\n451027,凌云县,4,451000\n451028,乐业县,4,451000\n451029,田林县,4,451000\n451030,西林县,4,451000\n451031,隆林各族自治县,4,451000\n451081,靖西市,4,451000\n451082,平果市,4,451000\n451102,八步区,4,451100\n451103,平桂区,4,451100\n451121,昭平县,4,451100\n451122,钟山县,4,451100\n451123,富川瑶族自治县,4,451100\n451202,金城江区,4,451200\n451203,宜州区,4,451200\n451221,南丹县,4,451200\n451222,天峨县,4,451200\n451223,凤山县,4,451200\n451224,东兰县,4,451200\n451225,罗城仫佬族自治县,4,451200\n451226,环江毛南族自治县,4,451200\n451227,巴马瑶族自治县,4,451200\n451228,都安瑶族自治县,4,451200\n451229,大化瑶族自治县,4,451200\n451302,兴宾区,4,451300\n451321,忻城县,4,451300\n451322,象州县,4,451300\n451323,武宣县,4,451300\n451324,金秀瑶族自治县,4,451300\n451381,合山市,4,451300\n451402,江州区,4,451400\n451421,扶绥县,4,451400\n451422,宁明县,4,451400\n451423,龙州县,4,451400\n451424,大新县,4,451400\n451425,天等县,4,451400\n451481,凭祥市,4,451400\n460105,秀英区,4,460100\n460106,龙华区,4,460100\n460107,琼山区,4,460100\n460108,美兰区,4,460100\n460202,海棠区,4,460200\n460203,吉阳区,4,460200\n460204,天涯区,4,460200\n460205,崖州区,4,460200\n460321,西沙群岛,4,460300\n460322,南沙群岛,4,460300\n460323,中沙群岛的岛礁及其海域,4,460300\n469001,五指山市,4,469000\n469002,琼海市,4,469000\n469005,文昌市,4,469000\n469006,万宁市,4,469000\n469007,东方市,4,469000\n469021,定安县,4,469000\n469022,屯昌县,4,469000\n469023,澄迈县,4,469000\n469024,临高县,4,469000\n469025,白沙黎族自治县,4,469000\n469026,昌江黎族自治县,4,469000\n469027,乐东黎族自治县,4,469000\n469028,陵水黎族自治县,4,469000\n469029,保亭黎族苗族自治县,4,469000\n469030,琼中黎族苗族自治县,4,469000\n500101,万州区,4,500100\n500102,涪陵区,4,500100\n500103,渝中区,4,500100\n500104,大渡口区,4,500100\n500105,江北区,4,500100\n500106,沙坪坝区,4,500100\n500107,九龙坡区,4,500100\n500108,南岸区,4,500100\n500109,北碚区,4,500100\n500110,綦江区,4,500100\n500111,大足区,4,500100\n500112,渝北区,4,500100\n500113,巴南区,4,500100\n500114,黔江区,4,500100\n500115,长寿区,4,500100\n500116,江津区,4,500100\n500117,合川区,4,500100\n500118,永川区,4,500100\n500119,南川区,4,500100\n500120,璧山区,4,500100\n500151,铜梁区,4,500100\n500152,潼南区,4,500100\n500153,荣昌区,4,500100\n500154,开州区,4,500100\n500155,梁平区,4,500100\n500156,武隆区,4,500100\n500229,城口县,4,500100\n500230,丰都县,4,500100\n500231,垫江县,4,500100\n500233,忠县,4,500100\n500235,云阳县,4,500100\n500236,奉节县,4,500100\n500237,巫山县,4,500100\n500238,巫溪县,4,500100\n500240,石柱土家族自治县,4,500100\n500241,秀山土家族苗族自治县,4,500100\n500242,酉阳土家族苗族自治县,4,500100\n500243,彭水苗族土家族自治县,4,500100\n510104,锦江区,4,510100\n510105,青羊区,4,510100\n510106,金牛区,4,510100\n510107,武侯区,4,510100\n510108,成华区,4,510100\n510112,龙泉驿区,4,510100\n510113,青白江区,4,510100\n510114,新都区,4,510100\n510115,温江区,4,510100\n510116,双流区,4,510100\n510117,郫都区,4,510100\n510118,新津区,4,510100\n510121,金堂县,4,510100\n510129,大邑县,4,510100\n510131,蒲江县,4,510100\n510181,都江堰市,4,510100\n510182,彭州市,4,510100\n510183,邛崃市,4,510100\n510184,崇州市,4,510100\n510185,简阳市,4,510100\n510302,自流井区,4,510300\n510303,贡井区,4,510300\n510304,大安区,4,510300\n510311,沿滩区,4,510300\n510321,荣县,4,510300\n510322,富顺县,4,510300\n510402,东区,4,510400\n510403,西区,4,510400\n510411,仁和区,4,510400\n510421,米易县,4,510400\n510422,盐边县,4,510400\n510502,江阳区,4,510500\n510503,纳溪区,4,510500\n510504,龙马潭区,4,510500\n510521,泸县,4,510500\n510522,合江县,4,510500\n510524,叙永县,4,510500\n510525,古蔺县,4,510500\n510603,旌阳区,4,510600\n510604,罗江区,4,510600\n510623,中江县,4,510600\n510681,广汉市,4,510600\n510682,什邡市,4,510600\n510683,绵竹市,4,510600\n510703,涪城区,4,510700\n510704,游仙区,4,510700\n510705,安州区,4,510700\n510722,三台县,4,510700\n510723,盐亭县,4,510700\n510725,梓潼县,4,510700\n510726,北川羌族自治县,4,510700\n510727,平武县,4,510700\n510781,江油市,4,510700\n510802,利州区,4,510800\n510811,昭化区,4,510800\n510812,朝天区,4,510800\n510821,旺苍县,4,510800\n510822,青川县,4,510800\n510823,剑阁县,4,510800\n510824,苍溪县,4,510800\n510903,船山区,4,510900\n510904,安居区,4,510900\n510921,蓬溪县,4,510900\n510923,大英县,4,510900\n510981,射洪市,4,510900\n511002,市中区,4,511000\n511011,东兴区,4,511000\n511024,威远县,4,511000\n511025,资中县,4,511000\n511071,内江经济开发区,4,511000\n511083,隆昌市,4,511000\n511102,市中区,4,511100\n511111,沙湾区,4,511100\n511112,五通桥区,4,511100\n511113,金口河区,4,511100\n511123,犍为县,4,511100\n511124,井研县,4,511100\n511126,夹江县,4,511100\n511129,沐川县,4,511100\n511132,峨边彝族自治县,4,511100\n511133,马边彝族自治县,4,511100\n511181,峨眉山市,4,511100\n511302,顺庆区,4,511300\n511303,高坪区,4,511300\n511304,嘉陵区,4,511300\n511321,南部县,4,511300\n511322,营山县,4,511300\n511323,蓬安县,4,511300\n511324,仪陇县,4,511300\n511325,西充县,4,511300\n511381,阆中市,4,511300\n511402,东坡区,4,511400\n511403,彭山区,4,511400\n511421,仁寿县,4,511400\n511423,洪雅县,4,511400\n511424,丹棱县,4,511400\n511425,青神县,4,511400\n511502,翠屏区,4,511500\n511503,南溪区,4,511500\n511504,叙州区,4,511500\n511523,江安县,4,511500\n511524,长宁县,4,511500\n511525,高县,4,511500\n511526,珙县,4,511500\n511527,筠连县,4,511500\n511528,兴文县,4,511500\n511529,屏山县,4,511500\n511602,广安区,4,511600\n511603,前锋区,4,511600\n511621,岳池县,4,511600\n511622,武胜县,4,511600\n511623,邻水县,4,511600\n511681,华蓥市,4,511600\n511702,通川区,4,511700\n511703,达川区,4,511700\n511722,宣汉县,4,511700\n511723,开江县,4,511700\n511724,大竹县,4,511700\n511725,渠县,4,511700\n511771,达州经济开发区,4,511700\n511781,万源市,4,511700\n511802,雨城区,4,511800\n511803,名山区,4,511800\n511822,荥经县,4,511800\n511823,汉源县,4,511800\n511824,石棉县,4,511800\n511825,天全县,4,511800\n511826,芦山县,4,511800\n511827,宝兴县,4,511800\n511902,巴州区,4,511900\n511903,恩阳区,4,511900\n511921,通江县,4,511900\n511922,南江县,4,511900\n511923,平昌县,4,511900\n511971,巴中经济开发区,4,511900\n512002,雁江区,4,512000\n512021,安岳县,4,512000\n512022,乐至县,4,512000\n513201,马尔康市,4,513200\n513221,汶川县,4,513200\n513222,理县,4,513200\n513223,茂县,4,513200\n513224,松潘县,4,513200\n513225,九寨沟县,4,513200\n513226,金川县,4,513200\n513227,小金县,4,513200\n513228,黑水县,4,513200\n513230,壤塘县,4,513200\n513231,阿坝县,4,513200\n513232,若尔盖县,4,513200\n513233,红原县,4,513200\n513301,康定市,4,513300\n513322,泸定县,4,513300\n513323,丹巴县,4,513300\n513324,九龙县,4,513300\n513325,雅江县,4,513300\n513326,道孚县,4,513300\n513327,炉霍县,4,513300\n513328,甘孜县,4,513300\n513329,新龙县,4,513300\n513330,德格县,4,513300\n513331,白玉县,4,513300\n513332,石渠县,4,513300\n513333,色达县,4,513300\n513334,理塘县,4,513300\n513335,巴塘县,4,513300\n513336,乡城县,4,513300\n513337,稻城县,4,513300\n513338,得荣县,4,513300\n513401,西昌市,4,513400\n513402,会理市,4,513400\n513422,木里藏族自治县,4,513400\n513423,盐源县,4,513400\n513424,德昌县,4,513400\n513426,会东县,4,513400\n513427,宁南县,4,513400\n513428,普格县,4,513400\n513429,布拖县,4,513400\n513430,金阳县,4,513400\n513431,昭觉县,4,513400\n513432,喜德县,4,513400\n513433,冕宁县,4,513400\n513434,越西县,4,513400\n513435,甘洛县,4,513400\n513436,美姑县,4,513400\n513437,雷波县,4,513400\n520102,南明区,4,520100\n520103,云岩区,4,520100\n520111,花溪区,4,520100\n520112,乌当区,4,520100\n520113,白云区,4,520100\n520115,观山湖区,4,520100\n520121,开阳县,4,520100\n520122,息烽县,4,520100\n520123,修文县,4,520100\n520181,清镇市,4,520100\n520201,钟山区,4,520200\n520203,六枝特区,4,520200\n520204,水城区,4,520200\n520281,盘州市,4,520200\n520302,红花岗区,4,520300\n520303,汇川区,4,520300\n520304,播州区,4,520300\n520322,桐梓县,4,520300\n520323,绥阳县,4,520300\n520324,正安县,4,520300\n520325,道真仡佬族苗族自治县,4,520300\n520326,务川仡佬族苗族自治县,4,520300\n520327,凤冈县,4,520300\n520328,湄潭县,4,520300\n520329,余庆县,4,520300\n520330,习水县,4,520300\n520381,赤水市,4,520300\n520382,仁怀市,4,520300\n520402,西秀区,4,520400\n520403,平坝区,4,520400\n520422,普定县,4,520400\n520423,镇宁布依族苗族自治县,4,520400\n520424,关岭布依族苗族自治县,4,520400\n520425,紫云苗族布依族自治县,4,520400\n520502,七星关区,4,520500\n520521,大方县,4,520500\n520523,金沙县,4,520500\n520524,织金县,4,520500\n520525,纳雍县,4,520500\n520526,威宁彝族回族苗族自治县,4,520500\n520527,赫章县,4,520500\n520581,黔西市,4,520500\n520602,碧江区,4,520600\n520603,万山区,4,520600\n520621,江口县,4,520600\n520622,玉屏侗族自治县,4,520600\n520623,石阡县,4,520600\n520624,思南县,4,520600\n520625,印江土家族苗族自治县,4,520600\n520626,德江县,4,520600\n520627,沿河土家族自治县,4,520600\n520628,松桃苗族自治县,4,520600\n522301,兴义市,4,522300\n522302,兴仁市,4,522300\n522323,普安县,4,522300\n522324,晴隆县,4,522300\n522325,贞丰县,4,522300\n522326,望谟县,4,522300\n522327,册亨县,4,522300\n522328,安龙县,4,522300\n522601,凯里市,4,522600\n522622,黄平县,4,522600\n522623,施秉县,4,522600\n522624,三穗县,4,522600\n522625,镇远县,4,522600\n522626,岑巩县,4,522600\n522627,天柱县,4,522600\n522628,锦屏县,4,522600\n522629,剑河县,4,522600\n522630,台江县,4,522600\n522631,黎平县,4,522600\n522632,榕江县,4,522600\n522633,从江县,4,522600\n522634,雷山县,4,522600\n522635,麻江县,4,522600\n522636,丹寨县,4,522600\n522701,都匀市,4,522700\n522702,福泉市,4,522700\n522722,荔波县,4,522700\n522723,贵定县,4,522700\n522725,瓮安县,4,522700\n522726,独山县,4,522700\n522727,平塘县,4,522700\n522728,罗甸县,4,522700\n522729,长顺县,4,522700\n522730,龙里县,4,522700\n522731,惠水县,4,522700\n522732,三都水族自治县,4,522700\n530102,五华区,4,530100\n530103,盘龙区,4,530100\n530111,官渡区,4,530100\n530112,西山区,4,530100\n530113,东川区,4,530100\n530114,呈贡区,4,530100\n530115,晋宁区,4,530100\n530124,富民县,4,530100\n530125,宜良县,4,530100\n530126,石林彝族自治县,4,530100\n530127,嵩明县,4,530100\n530128,禄劝彝族苗族自治县,4,530100\n530129,寻甸回族彝族自治县,4,530100\n530181,安宁市,4,530100\n530302,麒麟区,4,530300\n530303,沾益区,4,530300\n530304,马龙区,4,530300\n530322,陆良县,4,530300\n530323,师宗县,4,530300\n530324,罗平县,4,530300\n530325,富源县,4,530300\n530326,会泽县,4,530300\n530381,宣威市,4,530300\n530402,红塔区,4,530400\n530403,江川区,4,530400\n530423,通海县,4,530400\n530424,华宁县,4,530400\n530425,易门县,4,530400\n530426,峨山彝族自治县,4,530400\n530427,新平彝族傣族自治县,4,530400\n530428,元江哈尼族彝族傣族自治县,4,530400\n530481,澄江市,4,530400\n530502,隆阳区,4,530500\n530521,施甸县,4,530500\n530523,龙陵县,4,530500\n530524,昌宁县,4,530500\n530581,腾冲市,4,530500\n530602,昭阳区,4,530600\n530621,鲁甸县,4,530600\n530622,巧家县,4,530600\n530623,盐津县,4,530600\n530624,大关县,4,530600\n530625,永善县,4,530600\n530626,绥江县,4,530600\n530627,镇雄县,4,530600\n530628,彝良县,4,530600\n530629,威信县,4,530600\n530681,水富市,4,530600\n530702,古城区,4,530700\n530721,玉龙纳西族自治县,4,530700\n530722,永胜县,4,530700\n530723,华坪县,4,530700\n530724,宁蒗彝族自治县,4,530700\n530802,思茅区,4,530800\n530821,宁洱哈尼族彝族自治县,4,530800\n530822,墨江哈尼族自治县,4,530800\n530823,景东彝族自治县,4,530800\n530824,景谷傣族彝族自治县,4,530800\n530825,镇沅彝族哈尼族拉祜族自治县,4,530800\n530826,江城哈尼族彝族自治县,4,530800\n530827,孟连傣族拉祜族佤族自治县,4,530800\n530828,澜沧拉祜族自治县,4,530800\n530829,西盟佤族自治县,4,530800\n530902,临翔区,4,530900\n530921,凤庆县,4,530900\n530922,云县,4,530900\n530923,永德县,4,530900\n530924,镇康县,4,530900\n530925,双江拉祜族佤族布朗族傣族自治县,4,530900\n530926,耿马傣族佤族自治县,4,530900\n530927,沧源佤族自治县,4,530900\n532301,楚雄市,4,532300\n532302,禄丰市,4,532300\n532322,双柏县,4,532300\n532323,牟定县,4,532300\n532324,南华县,4,532300\n532325,姚安县,4,532300\n532326,大姚县,4,532300\n532327,永仁县,4,532300\n532328,元谋县,4,532300\n532329,武定县,4,532300\n532501,个旧市,4,532500\n532502,开远市,4,532500\n532503,蒙自市,4,532500\n532504,弥勒市,4,532500\n532523,屏边苗族自治县,4,532500\n532524,建水县,4,532500\n532525,石屏县,4,532500\n532527,泸西县,4,532500\n532528,元阳县,4,532500\n532529,红河县,4,532500\n532530,金平苗族瑶族傣族自治县,4,532500\n532531,绿春县,4,532500\n532532,河口瑶族自治县,4,532500\n532601,文山市,4,532600\n532622,砚山县,4,532600\n532623,西畴县,4,532600\n532624,麻栗坡县,4,532600\n532625,马关县,4,532600\n532626,丘北县,4,532600\n532627,广南县,4,532600\n532628,富宁县,4,532600\n532801,景洪市,4,532800\n532822,勐海县,4,532800\n532823,勐腊县,4,532800\n532901,大理市,4,532900\n532922,漾濞彝族自治县,4,532900\n532923,祥云县,4,532900\n532924,宾川县,4,532900\n532925,弥渡县,4,532900\n532926,南涧彝族自治县,4,532900\n532927,巍山彝族回族自治县,4,532900\n532928,永平县,4,532900\n532929,云龙县,4,532900\n532930,洱源县,4,532900\n532931,剑川县,4,532900\n532932,鹤庆县,4,532900\n533102,瑞丽市,4,533100\n533103,芒市,4,533100\n533122,梁河县,4,533100\n533123,盈江县,4,533100\n533124,陇川县,4,533100\n533301,泸水市,4,533300\n533323,福贡县,4,533300\n533324,贡山独龙族怒族自治县,4,533300\n533325,兰坪白族普米族自治县,4,533300\n533401,香格里拉市,4,533400\n533422,德钦县,4,533400\n533423,维西傈僳族自治县,4,533400\n540102,城关区,4,540100\n540103,堆龙德庆区,4,540100\n540104,达孜区,4,540100\n540121,林周县,4,540100\n540122,当雄县,4,540100\n540123,尼木县,4,540100\n540124,曲水县,4,540100\n540127,墨竹工卡县,4,540100\n540171,格尔木藏青工业园区,4,540100\n540172,拉萨经济技术开发区,4,540100\n540173,西藏文化旅游创意园区,4,540100\n540174,达孜工业园区,4,540100\n540202,桑珠孜区,4,540200\n540221,南木林县,4,540200\n540222,江孜县,4,540200\n540223,定日县,4,540200\n540224,萨迦县,4,540200\n540225,拉孜县,4,540200\n540226,昂仁县,4,540200\n540227,谢通门县,4,540200\n540228,白朗县,4,540200\n540229,仁布县,4,540200\n540230,康马县,4,540200\n540231,定结县,4,540200\n540232,仲巴县,4,540200\n540233,亚东县,4,540200\n540234,吉隆县,4,540200\n540235,聂拉木县,4,540200\n540236,萨嘎县,4,540200\n540237,岗巴县,4,540200\n540302,卡若区,4,540300\n540321,江达县,4,540300\n540322,贡觉县,4,540300\n540323,类乌齐县,4,540300\n540324,丁青县,4,540300\n540325,察雅县,4,540300\n540326,八宿县,4,540300\n540327,左贡县,4,540300\n540328,芒康县,4,540300\n540329,洛隆县,4,540300\n540330,边坝县,4,540300\n540402,巴宜区,4,540400\n540421,工布江达县,4,540400\n540422,米林县,4,540400\n540423,墨脱县,4,540400\n540424,波密县,4,540400\n540425,察隅县,4,540400\n540426,朗县,4,540400\n540502,乃东区,4,540500\n540521,扎囊县,4,540500\n540522,贡嘎县,4,540500\n540523,桑日县,4,540500\n540524,琼结县,4,540500\n540525,曲松县,4,540500\n540526,措美县,4,540500\n540527,洛扎县,4,540500\n540528,加查县,4,540500\n540529,隆子县,4,540500\n540530,错那县,4,540500\n540531,浪卡子县,4,540500\n540602,色尼区,4,540600\n540621,嘉黎县,4,540600\n540622,比如县,4,540600\n540623,聂荣县,4,540600\n540624,安多县,4,540600\n540625,申扎县,4,540600\n540626,索县,4,540600\n540627,班戈县,4,540600\n540628,巴青县,4,540600\n540629,尼玛县,4,540600\n540630,双湖县,4,540600\n542521,普兰县,4,542500\n542522,札达县,4,542500\n542523,噶尔县,4,542500\n542524,日土县,4,542500\n542525,革吉县,4,542500\n542526,改则县,4,542500\n542527,措勤县,4,542500\n610102,新城区,4,610100\n610103,碑林区,4,610100\n610104,莲湖区,4,610100\n610111,灞桥区,4,610100\n610112,未央区,4,610100\n610113,雁塔区,4,610100\n610114,阎良区,4,610100\n610115,临潼区,4,610100\n610116,长安区,4,610100\n610117,高陵区,4,610100\n610118,鄠邑区,4,610100\n610122,蓝田县,4,610100\n610124,周至县,4,610100\n610202,王益区,4,610200\n610203,印台区,4,610200\n610204,耀州区,4,610200\n610222,宜君县,4,610200\n610302,渭滨区,4,610300\n610303,金台区,4,610300\n610304,陈仓区,4,610300\n610305,凤翔区,4,610300\n610323,岐山县,4,610300\n610324,扶风县,4,610300\n610326,眉县,4,610300\n610327,陇县,4,610300\n610328,千阳县,4,610300\n610329,麟游县,4,610300\n610330,凤县,4,610300\n610331,太白县,4,610300\n610402,秦都区,4,610400\n610403,杨陵区,4,610400\n610404,渭城区,4,610400\n610422,三原县,4,610400\n610423,泾阳县,4,610400\n610424,乾县,4,610400\n610425,礼泉县,4,610400\n610426,永寿县,4,610400\n610428,长武县,4,610400\n610429,旬邑县,4,610400\n610430,淳化县,4,610400\n610431,武功县,4,610400\n610481,兴平市,4,610400\n610482,彬州市,4,610400\n610502,临渭区,4,610500\n610503,华州区,4,610500\n610522,潼关县,4,610500\n610523,大荔县,4,610500\n610524,合阳县,4,610500\n610525,澄城县,4,610500\n610526,蒲城县,4,610500\n610527,白水县,4,610500\n610528,富平县,4,610500\n610581,韩城市,4,610500\n610582,华阴市,4,610500\n610602,宝塔区,4,610600\n610603,安塞区,4,610600\n610621,延长县,4,610600\n610622,延川县,4,610600\n610625,志丹县,4,610600\n610626,吴起县,4,610600\n610627,甘泉县,4,610600\n610628,富县,4,610600\n610629,洛川县,4,610600\n610630,宜川县,4,610600\n610631,黄龙县,4,610600\n610632,黄陵县,4,610600\n610681,子长市,4,610600\n610702,汉台区,4,610700\n610703,南郑区,4,610700\n610722,城固县,4,610700\n610723,洋县,4,610700\n610724,西乡县,4,610700\n610725,勉县,4,610700\n610726,宁强县,4,610700\n610727,略阳县,4,610700\n610728,镇巴县,4,610700\n610729,留坝县,4,610700\n610730,佛坪县,4,610700\n610802,榆阳区,4,610800\n610803,横山区,4,610800\n610822,府谷县,4,610800\n610824,靖边县,4,610800\n610825,定边县,4,610800\n610826,绥德县,4,610800\n610827,米脂县,4,610800\n610828,佳县,4,610800\n610829,吴堡县,4,610800\n610830,清涧县,4,610800\n610831,子洲县,4,610800\n610881,神木市,4,610800\n610902,汉滨区,4,610900\n610921,汉阴县,4,610900\n610922,石泉县,4,610900\n610923,宁陕县,4,610900\n610924,紫阳县,4,610900\n610925,岚皋县,4,610900\n610926,平利县,4,610900\n610927,镇坪县,4,610900\n610929,白河县,4,610900\n610981,旬阳市,4,610900\n611002,商州区,4,611000\n611021,洛南县,4,611000\n611022,丹凤县,4,611000\n611023,商南县,4,611000\n611024,山阳县,4,611000\n611025,镇安县,4,611000\n611026,柞水县,4,611000\n620102,城关区,4,620100\n620103,七里河区,4,620100\n620104,西固区,4,620100\n620105,安宁区,4,620100\n620111,红古区,4,620100\n620121,永登县,4,620100\n620122,皋兰县,4,620100\n620123,榆中县,4,620100\n620171,兰州新区,4,620100\n620201,嘉峪关市,4,620200\n620302,金川区,4,620300\n620321,永昌县,4,620300\n620402,白银区,4,620400\n620403,平川区,4,620400\n620421,靖远县,4,620400\n620422,会宁县,4,620400\n620423,景泰县,4,620400\n620502,秦州区,4,620500\n620503,麦积区,4,620500\n620521,清水县,4,620500\n620522,秦安县,4,620500\n620523,甘谷县,4,620500\n620524,武山县,4,620500\n620525,张家川回族自治县,4,620500\n620602,凉州区,4,620600\n620621,民勤县,4,620600\n620622,古浪县,4,620600\n620623,天祝藏族自治县,4,620600\n620702,甘州区,4,620700\n620721,肃南裕固族自治县,4,620700\n620722,民乐县,4,620700\n620723,临泽县,4,620700\n620724,高台县,4,620700\n620725,山丹县,4,620700\n620802,崆峒区,4,620800\n620821,泾川县,4,620800\n620822,灵台县,4,620800\n620823,崇信县,4,620800\n620825,庄浪县,4,620800\n620826,静宁县,4,620800\n620881,华亭市,4,620800\n620902,肃州区,4,620900\n620921,金塔县,4,620900\n620922,瓜州县,4,620900\n620923,肃北蒙古族自治县,4,620900\n620924,阿克塞哈萨克族自治县,4,620900\n620981,玉门市,4,620900\n620982,敦煌市,4,620900\n621002,西峰区,4,621000\n621021,庆城县,4,621000\n621022,环县,4,621000\n621023,华池县,4,621000\n621024,合水县,4,621000\n621025,正宁县,4,621000\n621026,宁县,4,621000\n621027,镇原县,4,621000\n621102,安定区,4,621100\n621121,通渭县,4,621100\n621122,陇西县,4,621100\n621123,渭源县,4,621100\n621124,临洮县,4,621100\n621125,漳县,4,621100\n621126,岷县,4,621100\n621202,武都区,4,621200\n621221,成县,4,621200\n621222,文县,4,621200\n621223,宕昌县,4,621200\n621224,康县,4,621200\n621225,西和县,4,621200\n621226,礼县,4,621200\n621227,徽县,4,621200\n621228,两当县,4,621200\n622901,临夏市,4,622900\n622921,临夏县,4,622900\n622922,康乐县,4,622900\n622923,永靖县,4,622900\n622924,广河县,4,622900\n622925,和政县,4,622900\n622926,东乡族自治县,4,622900\n622927,积石山保安族东乡族撒拉族自治县,4,622900\n623001,合作市,4,623000\n623021,临潭县,4,623000\n623022,卓尼县,4,623000\n623023,舟曲县,4,623000\n623024,迭部县,4,623000\n623025,玛曲县,4,623000\n623026,碌曲县,4,623000\n623027,夏河县,4,623000\n630102,城东区,4,630100\n630103,城中区,4,630100\n630104,城西区,4,630100\n630105,城北区,4,630100\n630106,湟中区,4,630100\n630121,大通回族土族自治县,4,630100\n630123,湟源县,4,630100\n630202,乐都区,4,630200\n630203,平安区,4,630200\n630222,民和回族土族自治县,4,630200\n630223,互助土族自治县,4,630200\n630224,化隆回族自治县,4,630200\n630225,循化撒拉族自治县,4,630200\n632221,门源回族自治县,4,632200\n632222,祁连县,4,632200\n632223,海晏县,4,632200\n632224,刚察县,4,632200\n632301,同仁市,4,632300\n632322,尖扎县,4,632300\n632323,泽库县,4,632300\n632324,河南蒙古族自治县,4,632300\n632521,共和县,4,632500\n632522,同德县,4,632500\n632523,贵德县,4,632500\n632524,兴海县,4,632500\n632525,贵南县,4,632500\n632621,玛沁县,4,632600\n632622,班玛县,4,632600\n632623,甘德县,4,632600\n632624,达日县,4,632600\n632625,久治县,4,632600\n632626,玛多县,4,632600\n632701,玉树市,4,632700\n632722,杂多县,4,632700\n632723,称多县,4,632700\n632724,治多县,4,632700\n632725,囊谦县,4,632700\n632726,曲麻莱县,4,632700\n632801,格尔木市,4,632800\n632802,德令哈市,4,632800\n632803,茫崖市,4,632800\n632821,乌兰县,4,632800\n632822,都兰县,4,632800\n632823,天峻县,4,632800\n632857,大柴旦行政委员会,4,632800\n640104,兴庆区,4,640100\n640105,西夏区,4,640100\n640106,金凤区,4,640100\n640121,永宁县,4,640100\n640122,贺兰县,4,640100\n640181,灵武市,4,640100\n640202,大武口区,4,640200\n640205,惠农区,4,640200\n640221,平罗县,4,640200\n640302,利通区,4,640300\n640303,红寺堡区,4,640300\n640323,盐池县,4,640300\n640324,同心县,4,640300\n640381,青铜峡市,4,640300\n640402,原州区,4,640400\n640422,西吉县,4,640400\n640423,隆德县,4,640400\n640424,泾源县,4,640400\n640425,彭阳县,4,640400\n640502,沙坡头区,4,640500\n640521,中宁县,4,640500\n640522,海原县,4,640500\n650102,天山区,4,650100\n650103,沙依巴克区,4,650100\n650104,新市区,4,650100\n650105,水磨沟区,4,650100\n650106,头屯河区,4,650100\n650107,达坂城区,4,650100\n650109,米东区,4,650100\n650121,乌鲁木齐县,4,650100\n650202,独山子区,4,650200\n650203,克拉玛依区,4,650200\n650204,白碱滩区,4,650200\n650205,乌尔禾区,4,650200\n650402,高昌区,4,650400\n650421,鄯善县,4,650400\n650422,托克逊县,4,650400\n650502,伊州区,4,650500\n650521,巴里坤哈萨克自治县,4,650500\n650522,伊吾县,4,650500\n652301,昌吉市,4,652300\n652302,阜康市,4,652300\n652323,呼图壁县,4,652300\n652324,玛纳斯县,4,652300\n652325,奇台县,4,652300\n652327,吉木萨尔县,4,652300\n652328,木垒哈萨克自治县,4,652300\n652701,博乐市,4,652700\n652702,阿拉山口市,4,652700\n652722,精河县,4,652700\n652723,温泉县,4,652700\n652801,库尔勒市,4,652800\n652822,轮台县,4,652800\n652823,尉犁县,4,652800\n652824,若羌县,4,652800\n652825,且末县,4,652800\n652826,焉耆回族自治县,4,652800\n652827,和静县,4,652800\n652828,和硕县,4,652800\n652829,博湖县,4,652800\n652871,库尔勒经济技术开发区,4,652800\n652901,阿克苏市,4,652900\n652902,库车市,4,652900\n652922,温宿县,4,652900\n652924,沙雅县,4,652900\n652925,新和县,4,652900\n652926,拜城县,4,652900\n652927,乌什县,4,652900\n652928,阿瓦提县,4,652900\n652929,柯坪县,4,652900\n653001,阿图什市,4,653000\n653022,阿克陶县,4,653000\n653023,阿合奇县,4,653000\n653024,乌恰县,4,653000\n653101,喀什市,4,653100\n653121,疏附县,4,653100\n653122,疏勒县,4,653100\n653123,英吉沙县,4,653100\n653124,泽普县,4,653100\n653125,莎车县,4,653100\n653126,叶城县,4,653100\n653127,麦盖提县,4,653100\n653128,岳普湖县,4,653100\n653129,伽师县,4,653100\n653130,巴楚县,4,653100\n653131,塔什库尔干塔吉克自治县,4,653100\n653201,和田市,4,653200\n653221,和田县,4,653200\n653222,墨玉县,4,653200\n653223,皮山县,4,653200\n653224,洛浦县,4,653200\n653225,策勒县,4,653200\n653226,于田县,4,653200\n653227,民丰县,4,653200\n654002,伊宁市,4,654000\n654003,奎屯市,4,654000\n654004,霍尔果斯市,4,654000\n654021,伊宁县,4,654000\n654022,察布查尔锡伯自治县,4,654000\n654023,霍城县,4,654000\n654024,巩留县,4,654000\n654025,新源县,4,654000\n654026,昭苏县,4,654000\n654027,特克斯县,4,654000\n654028,尼勒克县,4,654000\n654201,塔城市,4,654200\n654202,乌苏市,4,654200\n654203,沙湾市,4,654200\n654221,额敏县,4,654200\n654224,托里县,4,654200\n654225,裕民县,4,654200\n654226,和布克赛尔蒙古自治县,4,654200\n654301,阿勒泰市,4,654300\n654321,布尔津县,4,654300\n654322,富蕴县,4,654300\n654323,福海县,4,654300\n654324,哈巴河县,4,654300\n654325,青河县,4,654300\n654326,吉木乃县,4,654300\n659001,石河子市,4,659000\n659002,阿拉尔市,4,659000\n659003,图木舒克市,4,659000\n659004,五家渠市,4,659000\n659005,北屯市,4,659000\n659006,铁门关市,4,659000\n659007,双河市,4,659000\n659008,可克达拉市,4,659000\n659009,昆玉市,4,659000\n659010,胡杨河市,4,659000\n659011,新星市,4,659000"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-ip/src/test/java/co/yixiang/yshop/framework/ip/core/utils/AreaUtilsTest.java",
    "content": "package co.yixiang.yshop.framework.ip.core.utils;\n\n\nimport co.yixiang.yshop.framework.ip.core.Area;\nimport co.yixiang.yshop.framework.ip.core.enums.AreaTypeEnum;\nimport org.junit.jupiter.api.Test;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n/**\n * {@link AreaUtils} 的单元测试\n *\n * @author yshop\n */\npublic class AreaUtilsTest {\n\n    @Test\n    public void testGetArea() {\n        // 调用：北京\n        Area area = AreaUtils.getArea(110100);\n        // 断言\n        assertEquals(area.getId(), 110100);\n        assertEquals(area.getName(), \"北京市\");\n        assertEquals(area.getType(), AreaTypeEnum.CITY.getType());\n        assertEquals(area.getParent().getId(), 110000);\n        assertEquals(area.getChildren().size(), 16);\n    }\n\n    @Test\n    public void testFormat() {\n        assertEquals(AreaUtils.format(110105), \"北京 北京市 朝阳区\");\n        assertEquals(AreaUtils.format(1), \"中国\");\n        assertEquals(AreaUtils.format(2), \"蒙古\");\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-ip/src/test/java/co/yixiang/yshop/framework/ip/core/utils/IPUtilsTest.java",
    "content": "package co.yixiang.yshop.framework.ip.core.utils;\n\nimport co.yixiang.yshop.framework.ip.core.Area;\nimport org.junit.jupiter.api.Test;\nimport org.lionsoul.ip2region.xdb.Searcher;\n\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n/**\n * {@link IPUtils} 的单元测试\n *\n * @author wanglhup\n */\npublic class IPUtilsTest {\n\n    @Test\n    public void testGetAreaId_string() {\n        // 120.202.4.0|120.202.4.255|420600\n        Integer areaId = IPUtils.getAreaId(\"120.202.4.50\");\n        assertEquals(420600, areaId);\n    }\n\n    @Test\n    public void testGetAreaId_long() throws Exception {\n        // 120.203.123.0|120.203.133.255|360900\n        long ip = Searcher.checkIP(\"120.203.123.250\");\n        Integer areaId = IPUtils.getAreaId(ip);\n        assertEquals(360900, areaId);\n    }\n\n    @Test\n    public void testGetArea_string() {\n        // 120.202.4.0|120.202.4.255|420600\n        Area area = IPUtils.getArea(\"120.202.4.50\");\n        assertEquals(\"襄阳市\", area.getName());\n    }\n\n    @Test\n    public void testGetArea_long() throws Exception {\n        // 120.203.123.0|120.203.133.255|360900\n        long ip = Searcher.checkIP(\"120.203.123.252\");\n        Area area = IPUtils.getArea(ip);\n        assertEquals(\"宜春市\", area.getName());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-tenant/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>yshop-framework</artifactId>\n        <groupId>co.yixiang.boot</groupId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>yshop-spring-boot-starter-biz-tenant</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>多租户</description>\n    <url>https://gitee.com/guchengwuyue/yshop-drink</url>\n\n    <dependencies>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-common</artifactId>\n        </dependency>\n\n        <!-- Web 相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-security</artifactId>\n        </dependency>\n\n        <!-- DB 相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-mybatis</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-redis</artifactId>\n        </dependency>\n\n        <!-- Job 定时任务相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-job</artifactId>\n        </dependency>\n\n        <!-- 消息队列相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-mq</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.kafka</groupId>\n            <artifactId>spring-kafka</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.amqp</groupId>\n            <artifactId>spring-rabbit</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.rocketmq</groupId>\n            <artifactId>rocketmq-spring-boot-starter</artifactId>\n            <optional>true</optional>\n        </dependency>\n\n        <!-- Test 测试相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n\n        <!-- 工具类相关 -->\n        <dependency>\n            <groupId>com.google.guava</groupId>\n            <artifactId>guava</artifactId>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-tenant/src/main/java/co/yixiang/yshop/framework/tenant/config/TenantProperties.java",
    "content": "package co.yixiang.yshop.framework.tenant.config;\n\nimport lombok.Data;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\n\nimport java.util.Collections;\nimport java.util.Set;\n\n/**\n * 多租户配置\n *\n * @author yshop\n */\n@ConfigurationProperties(prefix = \"yshop.tenant\")\n@Data\npublic class TenantProperties {\n\n    /**\n     * 租户是否开启\n     */\n    private static final Boolean ENABLE_DEFAULT = true;\n\n    /**\n     * 是否开启\n     */\n    private Boolean enable = ENABLE_DEFAULT;\n\n    /**\n     * 需要忽略多租户的请求\n     *\n     * 默认情况下，每个请求需要带上 tenant-id 的请求头。但是，部分请求是无需带上的，例如说短信回调、支付回调等 Open API！\n     */\n    private Set<String> ignoreUrls = Collections.emptySet();\n\n    /**\n     * 需要忽略多租户的表\n     *\n     * 即默认所有表都开启多租户的功能，所以记得添加对应的 tenant_id 字段哟\n     */\n    private Set<String> ignoreTables = Collections.emptySet();\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-tenant/src/main/java/co/yixiang/yshop/framework/tenant/config/YshopTenantAutoConfiguration.java",
    "content": "package co.yixiang.yshop.framework.tenant.config;\n\nimport co.yixiang.yshop.framework.common.enums.WebFilterOrderEnum;\nimport co.yixiang.yshop.framework.mybatis.core.util.MyBatisUtils;\nimport co.yixiang.yshop.framework.redis.config.YshopCacheProperties;\nimport co.yixiang.yshop.framework.tenant.core.aop.TenantIgnoreAspect;\nimport co.yixiang.yshop.framework.tenant.core.db.TenantDatabaseInterceptor;\nimport co.yixiang.yshop.framework.tenant.core.job.TenantJobAspect;\nimport co.yixiang.yshop.framework.tenant.core.mq.rabbitmq.TenantRabbitMQInitializer;\nimport co.yixiang.yshop.framework.tenant.core.mq.redis.TenantRedisMessageInterceptor;\nimport co.yixiang.yshop.framework.tenant.core.mq.rocketmq.TenantRocketMQInitializer;\nimport co.yixiang.yshop.framework.tenant.core.redis.TenantRedisCacheManager;\nimport co.yixiang.yshop.framework.tenant.core.security.TenantSecurityWebFilter;\nimport co.yixiang.yshop.framework.tenant.core.service.TenantFrameworkService;\nimport co.yixiang.yshop.framework.tenant.core.service.TenantFrameworkServiceImpl;\nimport co.yixiang.yshop.framework.tenant.core.web.TenantContextWebFilter;\nimport co.yixiang.yshop.framework.web.config.WebProperties;\nimport co.yixiang.yshop.framework.web.core.handler.GlobalExceptionHandler;\nimport co.yixiang.yshop.module.system.api.tenant.TenantApi;\nimport com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;\nimport com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;\nimport org.springframework.boot.autoconfigure.AutoConfiguration;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnClass;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.boot.web.servlet.FilterRegistrationBean;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Primary;\nimport org.springframework.data.redis.cache.BatchStrategies;\nimport org.springframework.data.redis.cache.RedisCacheConfiguration;\nimport org.springframework.data.redis.cache.RedisCacheManager;\nimport org.springframework.data.redis.cache.RedisCacheWriter;\nimport org.springframework.data.redis.connection.RedisConnectionFactory;\nimport org.springframework.data.redis.core.RedisTemplate;\n\nimport java.util.Objects;\n\n@AutoConfiguration\n@ConditionalOnProperty(prefix = \"yshop.tenant\", value = \"enable\", matchIfMissing = true) // 允许使用 yshop.tenant.enable=false 禁用多租户\n@EnableConfigurationProperties(TenantProperties.class)\npublic class YshopTenantAutoConfiguration {\n\n    @Bean\n    public TenantFrameworkService tenantFrameworkService(TenantApi tenantApi) {\n        return new TenantFrameworkServiceImpl(tenantApi);\n    }\n\n    // ========== AOP ==========\n\n    @Bean\n    public TenantIgnoreAspect tenantIgnoreAspect() {\n        return new TenantIgnoreAspect();\n    }\n\n    // ========== DB ==========\n\n    @Bean\n    public TenantLineInnerInterceptor tenantLineInnerInterceptor(TenantProperties properties,\n                                                                 MybatisPlusInterceptor interceptor) {\n        TenantLineInnerInterceptor inner = new TenantLineInnerInterceptor(new TenantDatabaseInterceptor(properties));\n        // 添加到 interceptor 中\n        // 需要加在首个，主要是为了在分页插件前面。这个是 MyBatis Plus 的规定\n        MyBatisUtils.addInterceptor(interceptor, inner, 0);\n        return inner;\n    }\n\n    // ========== WEB ==========\n\n    @Bean\n    public FilterRegistrationBean<TenantContextWebFilter> tenantContextWebFilter() {\n        FilterRegistrationBean<TenantContextWebFilter> registrationBean = new FilterRegistrationBean<>();\n        registrationBean.setFilter(new TenantContextWebFilter());\n        registrationBean.setOrder(WebFilterOrderEnum.TENANT_CONTEXT_FILTER);\n        return registrationBean;\n    }\n\n    // ========== Security ==========\n\n    @Bean\n    public FilterRegistrationBean<TenantSecurityWebFilter> tenantSecurityWebFilter(TenantProperties tenantProperties,\n                                                                                   WebProperties webProperties,\n                                                                                   GlobalExceptionHandler globalExceptionHandler,\n                                                                                   TenantFrameworkService tenantFrameworkService) {\n        FilterRegistrationBean<TenantSecurityWebFilter> registrationBean = new FilterRegistrationBean<>();\n        registrationBean.setFilter(new TenantSecurityWebFilter(tenantProperties, webProperties,\n                globalExceptionHandler, tenantFrameworkService));\n        registrationBean.setOrder(WebFilterOrderEnum.TENANT_SECURITY_FILTER);\n        return registrationBean;\n    }\n\n    // ========== MQ ==========\n\n    @Bean\n    public TenantRedisMessageInterceptor tenantRedisMessageInterceptor() {\n        return new TenantRedisMessageInterceptor();\n    }\n\n    @Bean\n    @ConditionalOnClass(name = \"org.springframework.amqp.rabbit.core.RabbitTemplate\")\n    public TenantRabbitMQInitializer tenantRabbitMQInitializer() {\n        return new TenantRabbitMQInitializer();\n    }\n\n    @Bean\n    @ConditionalOnClass(name = \"org.apache.rocketmq.spring.core.RocketMQTemplate\")\n    public TenantRocketMQInitializer tenantRocketMQInitializer() {\n        return new TenantRocketMQInitializer();\n    }\n\n    // ========== Job ==========\n\n    @Bean\n    public TenantJobAspect tenantJobAspect(TenantFrameworkService tenantFrameworkService) {\n        return new TenantJobAspect(tenantFrameworkService);\n    }\n\n    // ========== Redis ==========\n\n    @Bean\n    @Primary // 引入租户时，tenantRedisCacheManager 为主 Bean\n    public RedisCacheManager tenantRedisCacheManager(RedisTemplate<String, Object> redisTemplate,\n                                                     RedisCacheConfiguration redisCacheConfiguration,\n                                                     YshopCacheProperties yshopCacheProperties) {\n        // 创建 RedisCacheWriter 对象\n        RedisConnectionFactory connectionFactory = Objects.requireNonNull(redisTemplate.getConnectionFactory());\n        RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory,\n                BatchStrategies.scan(yshopCacheProperties.getRedisScanBatchSize()));\n        // 创建 TenantRedisCacheManager 对象\n        return new TenantRedisCacheManager(cacheWriter, redisCacheConfiguration);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-tenant/src/main/java/co/yixiang/yshop/framework/tenant/core/aop/TenantIgnore.java",
    "content": "package co.yixiang.yshop.framework.tenant.core.aop;\n\nimport java.lang.annotation.*;\n\n/**\n * 忽略租户，标记指定方法不进行租户的自动过滤\n *\n * 注意，只有 DB 的场景会过滤，其它场景暂时不过滤：\n * 1、Redis 场景：因为是基于 Key 实现多租户的能力，所以忽略没有意义，不像 DB 是一个 column 实现的\n * 2、MQ 场景：有点难以抉择，目前可以通过 Consumer 手动在消费的方法上，添加 @TenantIgnore 进行忽略\n *\n * @author yshop\n */\n@Target({ElementType.METHOD})\n@Retention(RetentionPolicy.RUNTIME)\n@Inherited\npublic @interface TenantIgnore {\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-tenant/src/main/java/co/yixiang/yshop/framework/tenant/core/aop/TenantIgnoreAspect.java",
    "content": "package co.yixiang.yshop.framework.tenant.core.aop;\n\nimport co.yixiang.yshop.framework.tenant.core.context.TenantContextHolder;\nimport co.yixiang.yshop.framework.tenant.core.util.TenantUtils;\nimport lombok.extern.slf4j.Slf4j;\nimport org.aspectj.lang.ProceedingJoinPoint;\nimport org.aspectj.lang.annotation.Around;\nimport org.aspectj.lang.annotation.Aspect;\n\n/**\n * 忽略多租户的 Aspect，基于 {@link TenantIgnore} 注解实现，用于一些全局的逻辑。\n * 例如说，一个定时任务，读取所有数据，进行处理。\n * 又例如说，读取所有数据，进行缓存。\n *\n * 整体逻辑的实现，和 {@link TenantUtils#executeIgnore(Runnable)} 需要保持一致\n *\n * @author yshop\n */\n@Aspect\n@Slf4j\npublic class TenantIgnoreAspect {\n\n    @Around(\"@annotation(tenantIgnore)\")\n    public Object around(ProceedingJoinPoint joinPoint, TenantIgnore tenantIgnore) throws Throwable {\n        Boolean oldIgnore = TenantContextHolder.isIgnore();\n        try {\n            TenantContextHolder.setIgnore(true);\n            // 执行逻辑\n            return joinPoint.proceed();\n        } finally {\n            TenantContextHolder.setIgnore(oldIgnore);\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-tenant/src/main/java/co/yixiang/yshop/framework/tenant/core/context/TenantContextHolder.java",
    "content": "package co.yixiang.yshop.framework.tenant.core.context;\n\nimport co.yixiang.yshop.framework.common.enums.DocumentEnum;\nimport com.alibaba.ttl.TransmittableThreadLocal;\n\n/**\n * 多租户上下文 Holder\n *\n * @author yshop\n */\npublic class TenantContextHolder {\n\n    /**\n     * 当前租户编号\n     */\n    private static final ThreadLocal<Long> TENANT_ID = new TransmittableThreadLocal<>();\n\n    /**\n     * 是否忽略租户\n     */\n    private static final ThreadLocal<Boolean> IGNORE = new TransmittableThreadLocal<>();\n\n    /**\n     * 获得租户编号\n     *\n     * @return 租户编号\n     */\n    public static Long getTenantId() {\n        return TENANT_ID.get();\n    }\n\n    /**\n     * 获得租户编号。如果不存在，则抛出 NullPointerException 异常\n     *\n     * @return 租户编号\n     */\n    public static Long getRequiredTenantId() {\n        Long tenantId = getTenantId();\n        if (tenantId == null) {\n            throw new NullPointerException(\"TenantContextHolder 不存在租户编号！可参考文档：\"\n                + DocumentEnum.TENANT.getUrl());\n        }\n        return tenantId;\n    }\n\n    public static void setTenantId(Long tenantId) {\n        TENANT_ID.set(tenantId);\n    }\n\n    public static void setIgnore(Boolean ignore) {\n        IGNORE.set(ignore);\n    }\n\n    /**\n     * 当前是否忽略租户\n     *\n     * @return 是否忽略\n     */\n    public static boolean isIgnore() {\n        return Boolean.TRUE.equals(IGNORE.get());\n    }\n\n    public static void clear() {\n        TENANT_ID.remove();\n        IGNORE.remove();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-tenant/src/main/java/co/yixiang/yshop/framework/tenant/core/db/TenantBaseDO.java",
    "content": "package co.yixiang.yshop.framework.tenant.core.db;\n\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\n/**\n * 拓展多租户的 BaseDO 基类\n *\n * @author yshop\n */\n@Data\n@EqualsAndHashCode(callSuper = true)\npublic abstract class TenantBaseDO extends BaseDO {\n\n    /**\n     * 多租户编号\n     */\n    private Long tenantId;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-tenant/src/main/java/co/yixiang/yshop/framework/tenant/core/db/TenantDatabaseInterceptor.java",
    "content": "package co.yixiang.yshop.framework.tenant.core.db;\n\nimport cn.hutool.core.collection.CollUtil;\nimport co.yixiang.yshop.framework.tenant.config.TenantProperties;\nimport co.yixiang.yshop.framework.tenant.core.context.TenantContextHolder;\nimport com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;\nimport net.sf.jsqlparser.expression.Expression;\nimport net.sf.jsqlparser.expression.LongValue;\n\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * 基于 MyBatis Plus 多租户的功能，实现 DB 层面的多租户的功能\n *\n * @author yshop\n */\npublic class TenantDatabaseInterceptor implements TenantLineHandler {\n\n    private final Set<String> ignoreTables = new HashSet<>();\n\n    public TenantDatabaseInterceptor(TenantProperties properties) {\n        // 不同 DB 下，大小写的习惯不同，所以需要都添加进去\n        properties.getIgnoreTables().forEach(table -> {\n            ignoreTables.add(table.toLowerCase());\n            ignoreTables.add(table.toUpperCase());\n        });\n        // 在 OracleKeyGenerator 中，生成主键时，会查询这个表，查询这个表后，会自动拼接 TENANT_ID 导致报错\n        ignoreTables.add(\"DUAL\");\n    }\n\n    @Override\n    public Expression getTenantId() {\n        return new LongValue(TenantContextHolder.getRequiredTenantId());\n    }\n\n    @Override\n    public boolean ignoreTable(String tableName) {\n        return TenantContextHolder.isIgnore() // 情况一，全局忽略多租户\n            || CollUtil.contains(ignoreTables, tableName); // 情况二，忽略多租户的表\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-tenant/src/main/java/co/yixiang/yshop/framework/tenant/core/job/TenantJob.java",
    "content": "package co.yixiang.yshop.framework.tenant.core.job;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * 多租户 Job 注解\n */\n@Target({ElementType.METHOD})\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface TenantJob {\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-tenant/src/main/java/co/yixiang/yshop/framework/tenant/core/job/TenantJobAspect.java",
    "content": "package co.yixiang.yshop.framework.tenant.core.job;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.exceptions.ExceptionUtil;\nimport co.yixiang.yshop.framework.common.util.json.JsonUtils;\nimport co.yixiang.yshop.framework.tenant.core.service.TenantFrameworkService;\nimport co.yixiang.yshop.framework.tenant.core.util.TenantUtils;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.aspectj.lang.ProceedingJoinPoint;\nimport org.aspectj.lang.annotation.Around;\nimport org.aspectj.lang.annotation.Aspect;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.ConcurrentHashMap;\n\n/**\n * 多租户 JobHandler AOP\n * 任务执行时，会按照租户逐个执行 Job 的逻辑\n *\n * 注意，需要保证 JobHandler 的幂等性。因为 Job 因为某个租户执行失败重试时，之前执行成功的租户也会再次执行。\n *\n * @author yshop\n */\n@Aspect\n@RequiredArgsConstructor\n@Slf4j\npublic class TenantJobAspect {\n\n    private final TenantFrameworkService tenantFrameworkService;\n\n    @Around(\"@annotation(tenantJob)\")\n    public String around(ProceedingJoinPoint joinPoint, TenantJob tenantJob) {\n        // 获得租户列表\n        List<Long> tenantIds = tenantFrameworkService.getTenantIds();\n        if (CollUtil.isEmpty(tenantIds)) {\n            return null;\n        }\n\n        // 逐个租户，执行 Job\n        Map<Long, String> results = new ConcurrentHashMap<>();\n        tenantIds.parallelStream().forEach(tenantId -> {\n            // TODO yshop：先通过 parallel 实现并行；1）多个租户，是一条执行日志；2）异常的情况\n            TenantUtils.execute(tenantId, () -> {\n                try {\n                    joinPoint.proceed();\n                } catch (Throwable e) {\n                    results.put(tenantId, ExceptionUtil.getRootCauseMessage(e));\n                }\n            });\n        });\n        return JsonUtils.toJsonString(results);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-tenant/src/main/java/co/yixiang/yshop/framework/tenant/core/mq/kafka/TenantKafkaEnvironmentPostProcessor.java",
    "content": "package co.yixiang.yshop.framework.tenant.core.mq.kafka;\n\nimport cn.hutool.core.util.StrUtil;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.env.EnvironmentPostProcessor;\nimport org.springframework.core.env.ConfigurableEnvironment;\n\n/**\n * 多租户的 Kafka 的 {@link EnvironmentPostProcessor} 实现类\n *\n * Kafka Producer 发送消息时，增加 {@link TenantKafkaProducerInterceptor} 拦截器\n *\n * @author yshop\n */\n@Slf4j\npublic class TenantKafkaEnvironmentPostProcessor implements EnvironmentPostProcessor {\n\n    private static final String PROPERTY_KEY_INTERCEPTOR_CLASSES = \"spring.kafka.producer.properties.interceptor.classes\";\n\n    @Override\n    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {\n        // 添加 TenantKafkaProducerInterceptor 拦截器\n        try {\n            String value = environment.getProperty(PROPERTY_KEY_INTERCEPTOR_CLASSES);\n            if (StrUtil.isEmpty(value)) {\n                value = TenantKafkaProducerInterceptor.class.getName();\n            } else {\n                value += \",\" + TenantKafkaProducerInterceptor.class.getName();\n            }\n            environment.getSystemProperties().put(PROPERTY_KEY_INTERCEPTOR_CLASSES, value);\n        } catch (NoClassDefFoundError ignore) {\n            // 如果触发 NoClassDefFoundError 异常，说明 TenantKafkaProducerInterceptor 类不存在，即没引入 kafka-spring 依赖\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-tenant/src/main/java/co/yixiang/yshop/framework/tenant/core/mq/kafka/TenantKafkaProducerInterceptor.java",
    "content": "package co.yixiang.yshop.framework.tenant.core.mq.kafka;\n\nimport cn.hutool.core.util.ReflectUtil;\nimport co.yixiang.yshop.framework.tenant.core.context.TenantContextHolder;\nimport org.apache.kafka.clients.producer.ProducerInterceptor;\nimport org.apache.kafka.clients.producer.ProducerRecord;\nimport org.apache.kafka.clients.producer.RecordMetadata;\nimport org.apache.kafka.common.header.Headers;\nimport org.springframework.messaging.handler.invocation.InvocableHandlerMethod;\n\nimport java.util.Map;\n\nimport static co.yixiang.yshop.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;\n\n/**\n * Kafka 消息队列的多租户 {@link ProducerInterceptor} 实现类\n *\n * 1. Producer 发送消息时，将 {@link TenantContextHolder} 租户编号，添加到消息的 Header 中\n * 2. Consumer 消费消息时，将消息的 Header 的租户编号，添加到 {@link TenantContextHolder} 中，通过 {@link InvocableHandlerMethod} 实现\n *\n * @author yshop\n */\npublic class TenantKafkaProducerInterceptor implements ProducerInterceptor<Object, Object> {\n\n    @Override\n    public ProducerRecord<Object, Object> onSend(ProducerRecord<Object, Object> record) {\n        Long tenantId = TenantContextHolder.getTenantId();\n        if (tenantId != null) {\n            Headers headers = (Headers) ReflectUtil.getFieldValue(record, \"headers\"); // private 属性，没有 get 方法，智能反射\n            headers.add(HEADER_TENANT_ID, tenantId.toString().getBytes());\n        }\n        return record;\n    }\n\n    @Override\n    public void onAcknowledgement(RecordMetadata metadata, Exception exception) {\n    }\n\n    @Override\n    public void close() {\n    }\n\n    @Override\n    public void configure(Map<String, ?> configs) {\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-tenant/src/main/java/co/yixiang/yshop/framework/tenant/core/mq/rabbitmq/TenantRabbitMQInitializer.java",
    "content": "package co.yixiang.yshop.framework.tenant.core.mq.rabbitmq;\n\nimport org.springframework.amqp.rabbit.core.RabbitTemplate;\nimport org.springframework.beans.BeansException;\nimport org.springframework.beans.factory.config.BeanPostProcessor;\n\n/**\n * 多租户的 RabbitMQ 初始化器\n *\n * @author yshop\n */\npublic class TenantRabbitMQInitializer implements BeanPostProcessor {\n\n    @Override\n    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {\n        if (bean instanceof RabbitTemplate) {\n            RabbitTemplate rabbitTemplate = (RabbitTemplate) bean;\n            rabbitTemplate.addBeforePublishPostProcessors(new TenantRabbitMQMessagePostProcessor());\n        }\n        return bean;\n    }\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-tenant/src/main/java/co/yixiang/yshop/framework/tenant/core/mq/rabbitmq/TenantRabbitMQMessagePostProcessor.java",
    "content": "package co.yixiang.yshop.framework.tenant.core.mq.rabbitmq;\n\nimport co.yixiang.yshop.framework.tenant.core.context.TenantContextHolder;\nimport org.apache.kafka.clients.producer.ProducerInterceptor;\nimport org.springframework.amqp.AmqpException;\nimport org.springframework.amqp.core.Message;\nimport org.springframework.amqp.core.MessagePostProcessor;\nimport org.springframework.messaging.handler.invocation.InvocableHandlerMethod;\n\nimport static co.yixiang.yshop.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;\n\n/**\n * RabbitMQ 消息队列的多租户 {@link ProducerInterceptor} 实现类\n *\n * 1. Producer 发送消息时，将 {@link TenantContextHolder} 租户编号，添加到消息的 Header 中\n * 2. Consumer 消费消息时，将消息的 Header 的租户编号，添加到 {@link TenantContextHolder} 中，通过 {@link InvocableHandlerMethod} 实现\n *\n * @author yshop\n */\npublic class TenantRabbitMQMessagePostProcessor implements MessagePostProcessor {\n\n    @Override\n    public Message postProcessMessage(Message message) throws AmqpException {\n        Long tenantId = TenantContextHolder.getTenantId();\n        if (tenantId != null) {\n            message.getMessageProperties().getHeaders().put(HEADER_TENANT_ID, tenantId);\n        }\n        return message;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-tenant/src/main/java/co/yixiang/yshop/framework/tenant/core/mq/redis/TenantRedisMessageInterceptor.java",
    "content": "package co.yixiang.yshop.framework.tenant.core.mq.redis;\n\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.mq.redis.core.interceptor.RedisMessageInterceptor;\nimport co.yixiang.yshop.framework.mq.redis.core.message.AbstractRedisMessage;\nimport co.yixiang.yshop.framework.tenant.core.context.TenantContextHolder;\n\nimport static co.yixiang.yshop.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;\n\n/**\n * 多租户 {@link AbstractRedisMessage} 拦截器\n *\n * 1. Producer 发送消息时，将 {@link TenantContextHolder} 租户编号，添加到消息的 Header 中\n * 2. Consumer 消费消息时，将消息的 Header 的租户编号，添加到 {@link TenantContextHolder} 中\n *\n * @author yshop\n */\npublic class TenantRedisMessageInterceptor implements RedisMessageInterceptor {\n\n    @Override\n    public void sendMessageBefore(AbstractRedisMessage message) {\n        Long tenantId = TenantContextHolder.getTenantId();\n        if (tenantId != null) {\n            message.addHeader(HEADER_TENANT_ID, tenantId.toString());\n        }\n    }\n\n    @Override\n    public void consumeMessageBefore(AbstractRedisMessage message) {\n        String tenantIdStr = message.getHeader(HEADER_TENANT_ID);\n        if (StrUtil.isNotEmpty(tenantIdStr)) {\n            TenantContextHolder.setTenantId(Long.valueOf(tenantIdStr));\n        }\n    }\n\n    @Override\n    public void consumeMessageAfter(AbstractRedisMessage message) {\n        // 注意，Consumer 是一个逻辑的入口，所以不考虑原本上下文就存在租户编号的情况\n        TenantContextHolder.clear();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-tenant/src/main/java/co/yixiang/yshop/framework/tenant/core/mq/rocketmq/TenantRocketMQConsumeMessageHook.java",
    "content": "package co.yixiang.yshop.framework.tenant.core.mq.rocketmq;\n\nimport cn.hutool.core.lang.Assert;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.tenant.core.context.TenantContextHolder;\nimport org.apache.rocketmq.client.hook.ConsumeMessageContext;\nimport org.apache.rocketmq.client.hook.ConsumeMessageHook;\nimport org.apache.rocketmq.common.message.MessageExt;\nimport org.springframework.messaging.handler.invocation.InvocableHandlerMethod;\n\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;\n\n/**\n * RocketMQ 消息队列的多租户 {@link ConsumeMessageHook} 实现类\n *\n * Consumer 消费消息时，将消息的 Header 的租户编号，添加到 {@link TenantContextHolder} 中，通过 {@link InvocableHandlerMethod} 实现\n *\n * @author yshop\n */\npublic class TenantRocketMQConsumeMessageHook implements ConsumeMessageHook {\n\n    @Override\n    public String hookName() {\n        return getClass().getSimpleName();\n    }\n\n    @Override\n    public void consumeMessageBefore(ConsumeMessageContext context) {\n        // 校验，消息必须是单条，不然设置租户可能不正确\n        List<MessageExt> messages = context.getMsgList();\n        Assert.isTrue(messages.size() == 1, \"消息条数({})不正确\", messages.size());\n        // 设置租户编号\n        String tenantId = messages.get(0).getUserProperty(HEADER_TENANT_ID);\n        if (StrUtil.isNotEmpty(tenantId)) {\n            TenantContextHolder.setTenantId(Long.parseLong(tenantId));\n        }\n    }\n\n    @Override\n    public void consumeMessageAfter(ConsumeMessageContext context) {\n        TenantContextHolder.clear();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-tenant/src/main/java/co/yixiang/yshop/framework/tenant/core/mq/rocketmq/TenantRocketMQInitializer.java",
    "content": "package co.yixiang.yshop.framework.tenant.core.mq.rocketmq;\n\nimport org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;\nimport org.apache.rocketmq.client.impl.consumer.DefaultMQPushConsumerImpl;\nimport org.apache.rocketmq.client.impl.producer.DefaultMQProducerImpl;\nimport org.apache.rocketmq.client.producer.DefaultMQProducer;\nimport org.apache.rocketmq.spring.core.RocketMQTemplate;\nimport org.apache.rocketmq.spring.support.DefaultRocketMQListenerContainer;\nimport org.springframework.beans.BeansException;\nimport org.springframework.beans.factory.config.BeanPostProcessor;\n\n/**\n * 多租户的 RocketMQ 初始化器\n *\n * @author yshop\n */\npublic class TenantRocketMQInitializer implements BeanPostProcessor {\n\n    @Override\n    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {\n        if (bean instanceof DefaultRocketMQListenerContainer) {\n            DefaultRocketMQListenerContainer container = (DefaultRocketMQListenerContainer) bean;\n            initTenantConsumer(container.getConsumer());\n        } else if (bean instanceof RocketMQTemplate) {\n            RocketMQTemplate template = (RocketMQTemplate) bean;\n            initTenantProducer(template.getProducer());\n        }\n        return bean;\n    }\n\n    private void initTenantProducer(DefaultMQProducer producer) {\n        if (producer == null) {\n            return;\n        }\n        DefaultMQProducerImpl producerImpl = producer.getDefaultMQProducerImpl();\n        if (producerImpl == null) {\n            return;\n        }\n        producerImpl.registerSendMessageHook(new TenantRocketMQSendMessageHook());\n    }\n\n    private void initTenantConsumer(DefaultMQPushConsumer consumer) {\n        if (consumer == null) {\n            return;\n        }\n        DefaultMQPushConsumerImpl consumerImpl = consumer.getDefaultMQPushConsumerImpl();\n        if (consumerImpl == null) {\n            return;\n        }\n        consumerImpl.registerConsumeMessageHook(new TenantRocketMQConsumeMessageHook());\n    }\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-tenant/src/main/java/co/yixiang/yshop/framework/tenant/core/mq/rocketmq/TenantRocketMQSendMessageHook.java",
    "content": "package co.yixiang.yshop.framework.tenant.core.mq.rocketmq;\n\nimport co.yixiang.yshop.framework.tenant.core.context.TenantContextHolder;\nimport org.apache.rocketmq.client.hook.SendMessageContext;\nimport org.apache.rocketmq.client.hook.SendMessageHook;\n\nimport static co.yixiang.yshop.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;\n\n/**\n * RocketMQ 消息队列的多租户 {@link SendMessageHook} 实现类\n *\n * Producer 发送消息时，将 {@link TenantContextHolder} 租户编号，添加到消息的 Header 中\n *\n * @author yshop\n */\npublic class TenantRocketMQSendMessageHook implements SendMessageHook {\n\n    @Override\n    public String hookName() {\n        return getClass().getSimpleName();\n    }\n\n    @Override\n    public void sendMessageBefore(SendMessageContext sendMessageContext) {\n        Long tenantId = TenantContextHolder.getTenantId();\n        if (tenantId == null) {\n            return;\n        }\n        sendMessageContext.getMessage().putUserProperty(HEADER_TENANT_ID, tenantId.toString());\n    }\n\n    @Override\n    public void sendMessageAfter(SendMessageContext sendMessageContext) {\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-tenant/src/main/java/co/yixiang/yshop/framework/tenant/core/redis/TenantRedisCacheManager.java",
    "content": "package co.yixiang.yshop.framework.tenant.core.redis;\n\nimport co.yixiang.yshop.framework.redis.core.TimeoutRedisCacheManager;\nimport co.yixiang.yshop.framework.tenant.core.context.TenantContextHolder;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.cache.Cache;\nimport org.springframework.data.redis.cache.RedisCacheConfiguration;\nimport org.springframework.data.redis.cache.RedisCacheManager;\nimport org.springframework.data.redis.cache.RedisCacheWriter;\n\n/**\n * 多租户的 {@link RedisCacheManager} 实现类\n *\n * 操作指定 name 的 {@link Cache} 时，自动拼接租户后缀，格式为 name + \":\" + tenantId + 后缀\n *\n * @author airhead\n */\n@Slf4j\npublic class TenantRedisCacheManager extends TimeoutRedisCacheManager {\n\n    public TenantRedisCacheManager(RedisCacheWriter cacheWriter,\n                                   RedisCacheConfiguration defaultCacheConfiguration) {\n        super(cacheWriter, defaultCacheConfiguration);\n    }\n\n    @Override\n    public Cache getCache(String name) {\n        // 如果开启多租户，则 name 拼接租户后缀\n        if (!TenantContextHolder.isIgnore()\n            && TenantContextHolder.getTenantId() != null) {\n            name = name + \":\" + TenantContextHolder.getTenantId();\n        }\n\n        // 继续基于父方法\n        return super.getCache(name);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-tenant/src/main/java/co/yixiang/yshop/framework/tenant/core/security/TenantSecurityWebFilter.java",
    "content": "package co.yixiang.yshop.framework.tenant.core.security;\n\nimport cn.hutool.core.collection.CollUtil;\nimport co.yixiang.yshop.framework.common.exception.enums.GlobalErrorCodeConstants;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.util.servlet.ServletUtils;\nimport co.yixiang.yshop.framework.security.core.LoginUser;\nimport co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils;\nimport co.yixiang.yshop.framework.tenant.config.TenantProperties;\nimport co.yixiang.yshop.framework.tenant.core.context.TenantContextHolder;\nimport co.yixiang.yshop.framework.tenant.core.service.TenantFrameworkService;\nimport co.yixiang.yshop.framework.web.config.WebProperties;\nimport co.yixiang.yshop.framework.web.core.filter.ApiRequestFilter;\nimport co.yixiang.yshop.framework.web.core.handler.GlobalExceptionHandler;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.util.AntPathMatcher;\n\nimport jakarta.servlet.FilterChain;\nimport jakarta.servlet.ServletException;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport java.io.IOException;\nimport java.util.Objects;\n\n/**\n * 多租户 Security Web 过滤器\n * 1. 如果是登陆的用户，校验是否有权限访问该租户，避免越权问题。\n * 2. 如果请求未带租户的编号，检查是否是忽略的 URL，否则也不允许访问。\n * 3. 校验租户是合法，例如说被禁用、到期\n *\n * @author yshop\n */\n@Slf4j\npublic class TenantSecurityWebFilter extends ApiRequestFilter {\n\n    private final TenantProperties tenantProperties;\n\n    private final AntPathMatcher pathMatcher;\n\n    private final GlobalExceptionHandler globalExceptionHandler;\n    private final TenantFrameworkService tenantFrameworkService;\n\n    public TenantSecurityWebFilter(TenantProperties tenantProperties,\n                                   WebProperties webProperties,\n                                   GlobalExceptionHandler globalExceptionHandler,\n                                   TenantFrameworkService tenantFrameworkService) {\n        super(webProperties);\n        this.tenantProperties = tenantProperties;\n        this.pathMatcher = new AntPathMatcher();\n        this.globalExceptionHandler = globalExceptionHandler;\n        this.tenantFrameworkService = tenantFrameworkService;\n    }\n\n    @Override\n    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)\n            throws ServletException, IOException {\n        Long tenantId = TenantContextHolder.getTenantId();\n        // 1. 登陆的用户，校验是否有权限访问该租户，避免越权问题。\n        LoginUser user = SecurityFrameworkUtils.getLoginUser();\n        if (user != null) {\n            // 如果获取不到租户编号，则尝试使用登陆用户的租户编号\n            if (tenantId == null) {\n                tenantId = user.getTenantId();\n                TenantContextHolder.setTenantId(tenantId);\n            // 如果传递了租户编号，则进行比对租户编号，避免越权问题\n            } else if (!Objects.equals(user.getTenantId(), TenantContextHolder.getTenantId())) {\n                log.error(\"[doFilterInternal][租户({}) User({}/{}) 越权访问租户({}) URL({}/{})]\",\n                        user.getTenantId(), user.getId(), user.getUserType(),\n                        TenantContextHolder.getTenantId(), request.getRequestURI(), request.getMethod());\n                ServletUtils.writeJSON(response, CommonResult.error(GlobalErrorCodeConstants.FORBIDDEN.getCode(),\n                        \"您无权访问该租户的数据\"));\n                return;\n            }\n        }\n\n        // 如果非允许忽略租户的 URL，则校验租户是否合法\n        if (!isIgnoreUrl(request)) {\n            // 2. 如果请求未带租户的编号，不允许访问。\n            if (tenantId == null) {\n                log.error(\"[doFilterInternal][URL({}/{}) 未传递租户编号]\", request.getRequestURI(), request.getMethod());\n                ServletUtils.writeJSON(response, CommonResult.error(GlobalErrorCodeConstants.BAD_REQUEST.getCode(),\n                        \"请求的租户标识未传递，请进行排查\"));\n                return;\n            }\n            // 3. 校验租户是合法，例如说被禁用、到期\n            try {\n                tenantFrameworkService.validTenant(tenantId);\n            } catch (Throwable ex) {\n                CommonResult<?> result = globalExceptionHandler.allExceptionHandler(request, ex);\n                ServletUtils.writeJSON(response, result);\n                return;\n            }\n        } else { // 如果是允许忽略租户的 URL，若未传递租户编号，则默认忽略租户编号，避免报错\n            if (tenantId == null) {\n                TenantContextHolder.setIgnore(true);\n            }\n        }\n\n        // 继续过滤\n        chain.doFilter(request, response);\n    }\n\n    private boolean isIgnoreUrl(HttpServletRequest request) {\n        // 快速匹配，保证性能\n        if (CollUtil.contains(tenantProperties.getIgnoreUrls(), request.getRequestURI())) {\n            return true;\n        }\n        // 逐个 Ant 路径匹配\n        for (String url : tenantProperties.getIgnoreUrls()) {\n            if (pathMatcher.match(url, request.getRequestURI())) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-tenant/src/main/java/co/yixiang/yshop/framework/tenant/core/service/TenantFrameworkService.java",
    "content": "package co.yixiang.yshop.framework.tenant.core.service;\n\nimport java.util.List;\n\n/**\n * Tenant 框架 Service 接口，定义获取租户信息\n *\n * @author yshop\n */\npublic interface TenantFrameworkService {\n\n    /**\n     * 获得所有租户\n     *\n     * @return 租户编号数组\n     */\n    List<Long> getTenantIds();\n\n    /**\n     * 校验租户是否合法\n     *\n     * @param id 租户编号\n     */\n    void validTenant(Long id);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-tenant/src/main/java/co/yixiang/yshop/framework/tenant/core/service/TenantFrameworkServiceImpl.java",
    "content": "package co.yixiang.yshop.framework.tenant.core.service;\n\nimport co.yixiang.yshop.framework.common.exception.ServiceException;\nimport co.yixiang.yshop.framework.common.util.cache.CacheUtils;\nimport co.yixiang.yshop.module.system.api.tenant.TenantApi;\nimport com.google.common.cache.CacheLoader;\nimport com.google.common.cache.LoadingCache;\nimport lombok.RequiredArgsConstructor;\nimport lombok.SneakyThrows;\n\nimport java.time.Duration;\nimport java.util.List;\n\n/**\n * Tenant 框架 Service 实现类\n *\n * @author yshop\n */\n@RequiredArgsConstructor\npublic class TenantFrameworkServiceImpl implements TenantFrameworkService {\n\n    private static final ServiceException SERVICE_EXCEPTION_NULL = new ServiceException();\n\n    private final TenantApi tenantApi;\n\n    /**\n     * 针对 {@link #getTenantIds()} 的缓存\n     */\n    private final LoadingCache<Object, List<Long>> getTenantIdsCache = CacheUtils.buildAsyncReloadingCache(\n            Duration.ofMinutes(1L), // 过期时间 1 分钟\n            new CacheLoader<Object, List<Long>>() {\n\n                @Override\n                public List<Long> load(Object key) {\n                    return tenantApi.getTenantIdList();\n                }\n\n            });\n\n    /**\n     * 针对 {@link #validTenant(Long)} 的缓存\n     */\n    private final LoadingCache<Long, ServiceException> validTenantCache = CacheUtils.buildAsyncReloadingCache(\n            Duration.ofMinutes(1L), // 过期时间 1 分钟\n            new CacheLoader<Long, ServiceException>() {\n\n                @Override\n                public ServiceException load(Long id) {\n                    try {\n                        tenantApi.validateTenant(id);\n                        return SERVICE_EXCEPTION_NULL;\n                    } catch (ServiceException ex) {\n                        return ex;\n                    }\n                }\n\n            });\n\n    @Override\n    @SneakyThrows\n    public List<Long> getTenantIds() {\n        return getTenantIdsCache.get(Boolean.TRUE);\n    }\n\n    @Override\n    public void validTenant(Long id) {\n        ServiceException serviceException = validTenantCache.getUnchecked(id);\n        if (serviceException != SERVICE_EXCEPTION_NULL) {\n            throw serviceException;\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-tenant/src/main/java/co/yixiang/yshop/framework/tenant/core/util/TenantUtils.java",
    "content": "package co.yixiang.yshop.framework.tenant.core.util;\n\nimport co.yixiang.yshop.framework.tenant.core.context.TenantContextHolder;\n\nimport java.util.Map;\nimport java.util.concurrent.Callable;\n\nimport static co.yixiang.yshop.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;\n\n/**\n * 多租户 Util\n *\n * @author yshop\n */\npublic class TenantUtils {\n\n    /**\n     * 使用指定租户，执行对应的逻辑\n     *\n     * 注意，如果当前是忽略租户的情况下，会被强制设置成不忽略租户\n     * 当然，执行完成后，还是会恢复回去\n     *\n     * @param tenantId 租户编号\n     * @param runnable 逻辑\n     */\n    public static void execute(Long tenantId, Runnable runnable) {\n        Long oldTenantId = TenantContextHolder.getTenantId();\n        Boolean oldIgnore = TenantContextHolder.isIgnore();\n        try {\n            TenantContextHolder.setTenantId(tenantId);\n            TenantContextHolder.setIgnore(false);\n            // 执行逻辑\n            runnable.run();\n        } finally {\n            TenantContextHolder.setTenantId(oldTenantId);\n            TenantContextHolder.setIgnore(oldIgnore);\n        }\n    }\n\n    /**\n     * 使用指定租户，执行对应的逻辑\n     *\n     * 注意，如果当前是忽略租户的情况下，会被强制设置成不忽略租户\n     * 当然，执行完成后，还是会恢复回去\n     *\n     * @param tenantId 租户编号\n     * @param callable 逻辑\n     */\n    public static <V> V execute(Long tenantId, Callable<V> callable) {\n        Long oldTenantId = TenantContextHolder.getTenantId();\n        Boolean oldIgnore = TenantContextHolder.isIgnore();\n        try {\n            TenantContextHolder.setTenantId(tenantId);\n            TenantContextHolder.setIgnore(false);\n            // 执行逻辑\n            return callable.call();\n        } catch (Exception e) {\n            throw new RuntimeException(e);\n        } finally {\n            TenantContextHolder.setTenantId(oldTenantId);\n            TenantContextHolder.setIgnore(oldIgnore);\n        }\n    }\n\n    /**\n     * 忽略租户，执行对应的逻辑\n     *\n     * @param runnable 逻辑\n     */\n    public static void executeIgnore(Runnable runnable) {\n        Boolean oldIgnore = TenantContextHolder.isIgnore();\n        try {\n            TenantContextHolder.setIgnore(true);\n            // 执行逻辑\n            runnable.run();\n        } finally {\n            TenantContextHolder.setIgnore(oldIgnore);\n        }\n    }\n\n    /**\n     * 将多租户编号，添加到 header 中\n     *\n     * @param headers HTTP 请求 headers\n     * @param tenantId 租户编号\n     */\n    public static void addTenantHeader(Map<String, String> headers, Long tenantId) {\n        if (tenantId != null) {\n            headers.put(HEADER_TENANT_ID, tenantId.toString());\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-tenant/src/main/java/co/yixiang/yshop/framework/tenant/core/web/TenantContextWebFilter.java",
    "content": "package co.yixiang.yshop.framework.tenant.core.web;\n\nimport co.yixiang.yshop.framework.tenant.core.context.TenantContextHolder;\nimport co.yixiang.yshop.framework.web.core.util.WebFrameworkUtils;\nimport org.springframework.web.filter.OncePerRequestFilter;\n\nimport jakarta.servlet.FilterChain;\nimport jakarta.servlet.ServletException;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport java.io.IOException;\n\n/**\n * 多租户 Context Web 过滤器\n * 将请求 Header 中的 tenant-id 解析出来，添加到 {@link TenantContextHolder} 中，这样后续的 DB 等操作，可以获得到租户编号。\n *\n * @author yshop\n */\npublic class TenantContextWebFilter extends OncePerRequestFilter {\n\n    @Override\n    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)\n            throws ServletException, IOException {\n        // 设置\n        Long tenantId = WebFrameworkUtils.getTenantId(request);\n        if (tenantId != null) {\n            TenantContextHolder.setTenantId(tenantId);\n        }\n        try {\n            chain.doFilter(request, response);\n        } finally {\n            // 清理\n            TenantContextHolder.clear();\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-tenant/src/main/java/org/springframework/messaging/handler/invocation/InvocableHandlerMethod.java",
    "content": "/*\n * Copyright 2002-2023 the original author or authors.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage org.springframework.messaging.handler.invocation;\n\nimport java.lang.reflect.InvocationTargetException;\nimport java.lang.reflect.Method;\nimport java.lang.reflect.Type;\nimport java.util.Arrays;\n\nimport co.yixiang.yshop.framework.tenant.core.context.TenantContextHolder;\nimport co.yixiang.yshop.framework.tenant.core.util.TenantUtils;\nimport org.springframework.core.DefaultParameterNameDiscoverer;\nimport org.springframework.core.MethodParameter;\nimport org.springframework.core.ParameterNameDiscoverer;\nimport org.springframework.core.ResolvableType;\nimport org.springframework.lang.Nullable;\nimport org.springframework.messaging.Message;\nimport org.springframework.messaging.handler.HandlerMethod;\nimport org.springframework.util.ObjectUtils;\n\nimport static co.yixiang.yshop.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;\n\n/**\n * Extension of {@link HandlerMethod} that invokes the underlying method with\n * argument values resolved from the current HTTP request through a list of\n * {@link HandlerMethodArgumentResolver}.\n *\n * 针对 rabbitmq-spring 和 kafka-spring，不存在合适的拓展点，可以实现 Consumer 消费前，读取 Header 中的 tenant-id 设置到 {@link TenantContextHolder} 中\n * TODO yshop：持续跟进，看看有没新的拓展点\n *\n * @author Rossen Stoyanchev\n * @author Juergen Hoeller\n * @since 4.0\n */\npublic class InvocableHandlerMethod extends HandlerMethod {\n\n    private static final Object[] EMPTY_ARGS = new Object[0];\n\n\n    private HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite();\n\n    private ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();\n\n\n    /**\n     * Create an instance from a {@code HandlerMethod}.\n     */\n    public InvocableHandlerMethod(HandlerMethod handlerMethod) {\n        super(handlerMethod);\n    }\n\n    /**\n     * Create an instance from a bean instance and a method.\n     */\n    public InvocableHandlerMethod(Object bean, Method method) {\n        super(bean, method);\n    }\n\n    /**\n     * Construct a new handler method with the given bean instance, method name and parameters.\n     * @param bean the object bean\n     * @param methodName the method name\n     * @param parameterTypes the method parameter types\n     * @throws NoSuchMethodException when the method cannot be found\n     */\n    public InvocableHandlerMethod(Object bean, String methodName, Class<?>... parameterTypes)\n            throws NoSuchMethodException {\n\n        super(bean, methodName, parameterTypes);\n    }\n\n\n    /**\n     * Set {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers} to use for resolving method argument values.\n     */\n    public void setMessageMethodArgumentResolvers(HandlerMethodArgumentResolverComposite argumentResolvers) {\n        this.resolvers = argumentResolvers;\n    }\n\n    /**\n     * Set the ParameterNameDiscoverer for resolving parameter names when needed\n     * (e.g. default request attribute name).\n     * <p>Default is a {@link org.springframework.core.DefaultParameterNameDiscoverer}.\n     */\n    public void setParameterNameDiscoverer(ParameterNameDiscoverer parameterNameDiscoverer) {\n        this.parameterNameDiscoverer = parameterNameDiscoverer;\n    }\n\n\n    /**\n     * Invoke the method after resolving its argument values in the context of the given message.\n     * <p>Argument values are commonly resolved through\n     * {@link HandlerMethodArgumentResolver HandlerMethodArgumentResolvers}.\n     * The {@code providedArgs} parameter however may supply argument values to be used directly,\n     * i.e. without argument resolution.\n     * <p>Delegates to {@link #getMethodArgumentValues} and calls {@link #doInvoke} with the\n     * resolved arguments.\n     * @param message the current message being processed\n     * @param providedArgs \"given\" arguments matched by type, not resolved\n     * @return the raw value returned by the invoked method\n     * @throws Exception raised if no suitable argument resolver can be found,\n     * or if the method raised an exception\n     * @see #getMethodArgumentValues\n     * @see #doInvoke\n     */\n    @Nullable\n    public Object invoke(Message<?> message, Object... providedArgs) throws Exception {\n        Object[] args = getMethodArgumentValues(message, providedArgs);\n        if (logger.isTraceEnabled()) {\n            logger.trace(\"Arguments: \" + Arrays.toString(args));\n        }\n        // 注意：如下是本类的改动点！！！\n        // 情况一：无租户编号的情况\n        Long tenantId= parseTenantId(message);\n        if (tenantId == null) {\n            return doInvoke(args);\n        }\n        // 情况二：有租户的情况下\n        return TenantUtils.execute(tenantId, () -> doInvoke(args));\n    }\n\n    private Long parseTenantId(Message<?> message) {\n        Object tenantId = message.getHeaders().get(HEADER_TENANT_ID);\n        if (tenantId == null) {\n            return null;\n        }\n        if (tenantId instanceof Long) {\n            return (Long) tenantId;\n        }\n        if (tenantId instanceof Number) {\n            return ((Number) tenantId).longValue();\n        }\n        if (tenantId instanceof String) {\n            return Long.parseLong((String) tenantId);\n        }\n        if (tenantId instanceof byte[]) {\n            return Long.parseLong(new String((byte[]) tenantId));\n        }\n        throw new IllegalArgumentException(\"未知的数据类型：\" + tenantId);\n    }\n\n    /**\n     * Get the method argument values for the current message, checking the provided\n     * argument values and falling back to the configured argument resolvers.\n     * <p>The resulting array will be passed into {@link #doInvoke}.\n     * @since 5.1.2\n     */\n    protected Object[] getMethodArgumentValues(Message<?> message, Object... providedArgs) throws Exception {\n        MethodParameter[] parameters = getMethodParameters();\n        if (ObjectUtils.isEmpty(parameters)) {\n            return EMPTY_ARGS;\n        }\n\n        Object[] args = new Object[parameters.length];\n        for (int i = 0; i < parameters.length; i++) {\n            MethodParameter parameter = parameters[i];\n            parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);\n            args[i] = findProvidedArgument(parameter, providedArgs);\n            if (args[i] != null) {\n                continue;\n            }\n            if (!this.resolvers.supportsParameter(parameter)) {\n                throw new MethodArgumentResolutionException(\n                        message, parameter, formatArgumentError(parameter, \"No suitable resolver\"));\n            }\n            try {\n                args[i] = this.resolvers.resolveArgument(parameter, message);\n            }\n            catch (Exception ex) {\n                // Leave stack trace for later, exception may actually be resolved and handled...\n                if (logger.isDebugEnabled()) {\n                    String exMsg = ex.getMessage();\n                    if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {\n                        logger.debug(formatArgumentError(parameter, exMsg));\n                    }\n                }\n                throw ex;\n            }\n        }\n        return args;\n    }\n\n    /**\n     * Invoke the handler method with the given argument values.\n     */\n    @Nullable\n    protected Object doInvoke(Object... args) throws Exception {\n        try {\n            return getBridgedMethod().invoke(getBean(), args);\n        }\n        catch (IllegalArgumentException ex) {\n            assertTargetBean(getBridgedMethod(), getBean(), args);\n            String text = (ex.getMessage() == null || ex.getCause() instanceof NullPointerException) ?\n                    \"Illegal argument\": ex.getMessage();\n            throw new IllegalStateException(formatInvokeError(text, args), ex);\n        }\n        catch (InvocationTargetException ex) {\n            // Unwrap for HandlerExceptionResolvers ...\n            Throwable targetException = ex.getTargetException();\n            if (targetException instanceof RuntimeException runtimeException) {\n                throw runtimeException;\n            }\n            else if (targetException instanceof Error error) {\n                throw error;\n            }\n            else if (targetException instanceof Exception exception) {\n                throw exception;\n            }\n            else {\n                throw new IllegalStateException(formatInvokeError(\"Invocation failure\", args), targetException);\n            }\n        }\n    }\n\n    MethodParameter getAsyncReturnValueType(@Nullable Object returnValue) {\n        return new AsyncResultMethodParameter(returnValue);\n    }\n\n\n    private class AsyncResultMethodParameter extends AnnotatedMethodParameter {\n\n        @Nullable\n        private final Object returnValue;\n\n        private final ResolvableType returnType;\n\n        public AsyncResultMethodParameter(@Nullable Object returnValue) {\n            super(-1);\n            this.returnValue = returnValue;\n            this.returnType = ResolvableType.forType(super.getGenericParameterType()).getGeneric();\n        }\n\n        protected AsyncResultMethodParameter(AsyncResultMethodParameter original) {\n            super(original);\n            this.returnValue = original.returnValue;\n            this.returnType = original.returnType;\n        }\n\n        @Override\n        public Class<?> getParameterType() {\n            if (this.returnValue != null) {\n                return this.returnValue.getClass();\n            }\n            if (!ResolvableType.NONE.equals(this.returnType)) {\n                return this.returnType.toClass();\n            }\n            return super.getParameterType();\n        }\n\n        @Override\n        public Type getGenericParameterType() {\n            return this.returnType.getType();\n        }\n\n        @Override\n        public AsyncResultMethodParameter clone() {\n            return new AsyncResultMethodParameter(this);\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-tenant/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports",
    "content": "co.yixiang.yshop.framework.tenant.config.YshopTenantAutoConfiguration"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-biz-tenant/src/main/resources/META-INF/spring.factories",
    "content": "org.springframework.boot.env.EnvironmentPostProcessor=\\\n  co.yixiang.yshop.framework.tenant.core.mq.kafka.TenantKafkaEnvironmentPostProcessor\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-excel/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>co.yixiang.boot</groupId>\n        <artifactId>yshop-framework</artifactId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>yshop-spring-boot-starter-excel</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>Excel 拓展</description>\n    <url>https://gitee.com/guchengwuyue/yshop-drink</url>\n\n    <dependencies>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-common</artifactId>\n        </dependency>\n\n        <!-- Spring 核心 -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter</artifactId>\n        </dependency>\n\n        <!-- 业务组件 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-system-api</artifactId> <!-- 需要使用它，进行 Dict 的查询 -->\n            <version>${revision}</version>\n        </dependency>\n\n        <!-- Web 相关 -->\n        <dependency>\n            <groupId>org.springframework</groupId>\n            <artifactId>spring-web</artifactId>\n            <scope>provided</scope> <!-- 设置为 provided，只有 ExcelUtils 使用 -->\n        </dependency>\n\n        <dependency>\n            <groupId>jakarta.servlet</groupId>\n            <artifactId>jakarta.servlet-api</artifactId>\n            <scope>provided</scope> <!-- 设置为 provided，只有 ExcelUtils 使用 -->\n        </dependency>\n\n        <!-- 工具类相关 -->\n        <dependency>\n            <groupId>com.alibaba</groupId>\n            <artifactId>easyexcel</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>com.google.guava</groupId>\n            <artifactId>guava</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-biz-ip</artifactId>\n            <optional>true</optional> <!-- 设置为 optional，只有在 AreaConvert 的时候使用 -->\n        </dependency>\n\n        <!-- Test 测试相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-excel/src/main/java/co/yixiang/yshop/framework/dict/config/YshopDictAutoConfiguration.java",
    "content": "package co.yixiang.yshop.framework.dict.config;\n\nimport co.yixiang.yshop.framework.dict.core.DictFrameworkUtils;\nimport co.yixiang.yshop.module.system.api.dict.DictDataApi;\nimport org.springframework.boot.autoconfigure.AutoConfiguration;\nimport org.springframework.context.annotation.Bean;\n\n@AutoConfiguration\npublic class YshopDictAutoConfiguration {\n\n    @Bean\n    @SuppressWarnings(\"InstantiationOfUtilityClass\")\n    public DictFrameworkUtils dictUtils(DictDataApi dictDataApi) {\n        DictFrameworkUtils.init(dictDataApi);\n        return new DictFrameworkUtils();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-excel/src/main/java/co/yixiang/yshop/framework/dict/core/DictFrameworkUtils.java",
    "content": "package co.yixiang.yshop.framework.dict.core;\n\nimport cn.hutool.core.util.ObjectUtil;\nimport co.yixiang.yshop.framework.common.core.KeyValue;\nimport co.yixiang.yshop.framework.common.util.cache.CacheUtils;\nimport co.yixiang.yshop.module.system.api.dict.DictDataApi;\nimport co.yixiang.yshop.module.system.api.dict.dto.DictDataRespDTO;\nimport com.google.common.cache.CacheLoader;\nimport com.google.common.cache.LoadingCache;\nimport lombok.SneakyThrows;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.time.Duration;\nimport java.util.List;\n\n/**\n * 字典工具类\n *\n * @author yshop\n */\n@Slf4j\npublic class DictFrameworkUtils {\n\n    private static DictDataApi dictDataApi;\n\n    private static final DictDataRespDTO DICT_DATA_NULL = new DictDataRespDTO();\n\n    // TODO @puhui999：GET_DICT_DATA_CACHE、GET_DICT_DATA_LIST_CACHE、PARSE_DICT_DATA_CACHE 这 3 个缓存是有点重叠，可以思考下，有没可能减少 1 个。微信讨论好私聊，再具体改哈\n    /**\n     * 针对 {@link #getDictDataLabel(String, String)} 的缓存\n     */\n    private static final LoadingCache<KeyValue<String, String>, DictDataRespDTO> GET_DICT_DATA_CACHE = CacheUtils.buildAsyncReloadingCache(\n            Duration.ofMinutes(1L), // 过期时间 1 分钟\n            new CacheLoader<KeyValue<String, String>, DictDataRespDTO>() {\n\n                @Override\n                public DictDataRespDTO load(KeyValue<String, String> key) {\n                    return ObjectUtil.defaultIfNull(dictDataApi.getDictData(key.getKey(), key.getValue()), DICT_DATA_NULL);\n                }\n\n            });\n\n    /**\n     * 针对 {@link #getDictDataLabelList(String)} 的缓存\n     */\n    private static final LoadingCache<String, List<String>> GET_DICT_DATA_LIST_CACHE = CacheUtils.buildAsyncReloadingCache(\n            Duration.ofMinutes(1L), // 过期时间 1 分钟\n            new CacheLoader<String, List<String>>() {\n\n                @Override\n                public List<String> load(String dictType) {\n                    return dictDataApi.getDictDataLabelList(dictType);\n                }\n\n            });\n\n    /**\n     * 针对 {@link #parseDictDataValue(String, String)} 的缓存\n     */\n    private static final LoadingCache<KeyValue<String, String>, DictDataRespDTO> PARSE_DICT_DATA_CACHE = CacheUtils.buildAsyncReloadingCache(\n            Duration.ofMinutes(1L), // 过期时间 1 分钟\n            new CacheLoader<KeyValue<String, String>, DictDataRespDTO>() {\n\n                @Override\n                public DictDataRespDTO load(KeyValue<String, String> key) {\n                    return ObjectUtil.defaultIfNull(dictDataApi.parseDictData(key.getKey(), key.getValue()), DICT_DATA_NULL);\n                }\n\n            });\n\n    public static void init(DictDataApi dictDataApi) {\n        DictFrameworkUtils.dictDataApi = dictDataApi;\n        log.info(\"[init][初始化 DictFrameworkUtils 成功]\");\n    }\n\n    @SneakyThrows\n    public static String getDictDataLabel(String dictType, Integer value) {\n        return GET_DICT_DATA_CACHE.get(new KeyValue<>(dictType, String.valueOf(value))).getLabel();\n    }\n\n    @SneakyThrows\n    public static String getDictDataLabel(String dictType, String value) {\n        return GET_DICT_DATA_CACHE.get(new KeyValue<>(dictType, value)).getLabel();\n    }\n\n    @SneakyThrows\n    public static List<String> getDictDataLabelList(String dictType) {\n        return GET_DICT_DATA_LIST_CACHE.get(dictType);\n    }\n\n    @SneakyThrows\n    public static String parseDictDataValue(String dictType, String label) {\n        return PARSE_DICT_DATA_CACHE.get(new KeyValue<>(dictType, label)).getValue();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-excel/src/main/java/co/yixiang/yshop/framework/excel/core/annotations/DictFormat.java",
    "content": "package co.yixiang.yshop.framework.excel.core.annotations;\n\nimport java.lang.annotation.*;\n\n/**\n * 字典格式化\n *\n * 实现将字典数据的值，格式化成字典数据的标签\n */\n@Target({ElementType.FIELD})\n@Retention(RetentionPolicy.RUNTIME)\n@Inherited\npublic @interface DictFormat {\n\n    /**\n     * 例如说，SysDictTypeConstants、InfDictTypeConstants\n     *\n     * @return 字典类型\n     */\n    String value();\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-excel/src/main/java/co/yixiang/yshop/framework/excel/core/annotations/ExcelColumnSelect.java",
    "content": "package co.yixiang.yshop.framework.excel.core.annotations;\n\nimport java.lang.annotation.*;\n\n/**\n * 给 Excel 列添加下拉选择数据\n *\n * 其中 {@link #dictType()} 和 {@link #functionName()} 二选一\n *\n * @author HUIHUI\n */\n@Target({ElementType.FIELD})\n@Retention(RetentionPolicy.RUNTIME)\n@Inherited\npublic @interface ExcelColumnSelect {\n\n    /**\n     * @return 字典类型\n     */\n    String dictType() default \"\";\n\n    /**\n     * @return 获取下拉数据源的方法名称\n     */\n    String functionName() default \"\";\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-excel/src/main/java/co/yixiang/yshop/framework/excel/core/convert/AreaConvert.java",
    "content": "package co.yixiang.yshop.framework.excel.core.convert;\n\nimport cn.hutool.core.convert.Convert;\nimport co.yixiang.yshop.framework.ip.core.Area;\nimport co.yixiang.yshop.framework.ip.core.utils.AreaUtils;\nimport com.alibaba.excel.converters.Converter;\nimport com.alibaba.excel.enums.CellDataTypeEnum;\nimport com.alibaba.excel.metadata.GlobalConfiguration;\nimport com.alibaba.excel.metadata.data.ReadCellData;\nimport com.alibaba.excel.metadata.property.ExcelContentProperty;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * Excel 数据地区转换器\n *\n * @author HUIHUI\n */\n@Slf4j\npublic class AreaConvert implements Converter<Object> {\n\n    @Override\n    public Class<?> supportJavaTypeKey() {\n        throw new UnsupportedOperationException(\"暂不支持，也不需要\");\n    }\n\n    @Override\n    public CellDataTypeEnum supportExcelTypeKey() {\n        throw new UnsupportedOperationException(\"暂不支持，也不需要\");\n    }\n\n    @Override\n    public Object convertToJavaData(ReadCellData readCellData, ExcelContentProperty contentProperty,\n                                    GlobalConfiguration globalConfiguration) {\n        // 解析地区编号\n        String label = readCellData.getStringValue();\n        Area area = AreaUtils.parseArea(label);\n        if (area == null) {\n            log.error(\"[convertToJavaData][label({}) 解析不掉]\", label);\n            return null;\n        }\n        // 将 value 转换成对应的属性\n        Class<?> fieldClazz = contentProperty.getField().getType();\n        return Convert.convert(fieldClazz, area.getId());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-excel/src/main/java/co/yixiang/yshop/framework/excel/core/convert/DictConvert.java",
    "content": "package co.yixiang.yshop.framework.excel.core.convert;\n\nimport cn.hutool.core.convert.Convert;\nimport co.yixiang.yshop.framework.dict.core.DictFrameworkUtils;\nimport co.yixiang.yshop.framework.excel.core.annotations.DictFormat;\nimport com.alibaba.excel.converters.Converter;\nimport com.alibaba.excel.enums.CellDataTypeEnum;\nimport com.alibaba.excel.metadata.GlobalConfiguration;\nimport com.alibaba.excel.metadata.data.ReadCellData;\nimport com.alibaba.excel.metadata.data.WriteCellData;\nimport com.alibaba.excel.metadata.property.ExcelContentProperty;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * Excel 数据字典转换器\n *\n * @author yshop\n */\n@Slf4j\npublic class DictConvert implements Converter<Object> {\n\n    @Override\n    public Class<?> supportJavaTypeKey() {\n        throw new UnsupportedOperationException(\"暂不支持，也不需要\");\n    }\n\n    @Override\n    public CellDataTypeEnum supportExcelTypeKey() {\n        throw new UnsupportedOperationException(\"暂不支持，也不需要\");\n    }\n\n    @Override\n    public Object convertToJavaData(ReadCellData readCellData, ExcelContentProperty contentProperty,\n                                    GlobalConfiguration globalConfiguration) {\n        // 使用字典解析\n        String type = getType(contentProperty);\n        String label = readCellData.getStringValue();\n        String value = DictFrameworkUtils.parseDictDataValue(type, label);\n        if (value == null) {\n            log.error(\"[convertToJavaData][type({}) 解析不掉 label({})]\", type, label);\n            return null;\n        }\n        // 将 String 的 value 转换成对应的属性\n        Class<?> fieldClazz = contentProperty.getField().getType();\n        return Convert.convert(fieldClazz, value);\n    }\n\n    @Override\n    public WriteCellData<String> convertToExcelData(Object object, ExcelContentProperty contentProperty,\n                                                    GlobalConfiguration globalConfiguration) {\n        // 空时，返回空\n        if (object == null) {\n            return new WriteCellData<>(\"\");\n        }\n\n        // 使用字典格式化\n        String type = getType(contentProperty);\n        String value = String.valueOf(object);\n        String label = DictFrameworkUtils.getDictDataLabel(type, value);\n        if (label == null) {\n            log.error(\"[convertToExcelData][type({}) 转换不了 label({})]\", type, value);\n            return new WriteCellData<>(\"\");\n        }\n        // 生成 Excel 小表格\n        return new WriteCellData<>(label);\n    }\n\n    private static String getType(ExcelContentProperty contentProperty) {\n        return contentProperty.getField().getAnnotation(DictFormat.class).value();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-excel/src/main/java/co/yixiang/yshop/framework/excel/core/convert/JsonConvert.java",
    "content": "package co.yixiang.yshop.framework.excel.core.convert;\n\nimport co.yixiang.yshop.framework.common.util.json.JsonUtils;\nimport com.alibaba.excel.converters.Converter;\nimport com.alibaba.excel.enums.CellDataTypeEnum;\nimport com.alibaba.excel.metadata.GlobalConfiguration;\nimport com.alibaba.excel.metadata.data.WriteCellData;\nimport com.alibaba.excel.metadata.property.ExcelContentProperty;\n\n/**\n * Excel Json 转换器\n *\n * @author yshop\n */\npublic class JsonConvert implements Converter<Object> {\n\n    @Override\n    public Class<?> supportJavaTypeKey() {\n        throw new UnsupportedOperationException(\"暂不支持，也不需要\");\n    }\n\n    @Override\n    public CellDataTypeEnum supportExcelTypeKey() {\n        throw new UnsupportedOperationException(\"暂不支持，也不需要\");\n    }\n\n    @Override\n    public WriteCellData<String> convertToExcelData(Object value, ExcelContentProperty contentProperty,\n                                                    GlobalConfiguration globalConfiguration) {\n        // 生成 Excel 小表格\n        return new WriteCellData<>(JsonUtils.toJsonString(value));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-excel/src/main/java/co/yixiang/yshop/framework/excel/core/convert/MoneyConvert.java",
    "content": "package co.yixiang.yshop.framework.excel.core.convert;\n\nimport com.alibaba.excel.converters.Converter;\nimport com.alibaba.excel.enums.CellDataTypeEnum;\nimport com.alibaba.excel.metadata.GlobalConfiguration;\nimport com.alibaba.excel.metadata.data.WriteCellData;\nimport com.alibaba.excel.metadata.property.ExcelContentProperty;\n\nimport java.math.BigDecimal;\nimport java.math.RoundingMode;\n\n/**\n * 金额转换器\n *\n * 金额单位：分\n *\n * @author yshop\n */\npublic class MoneyConvert implements Converter<Integer> {\n\n    @Override\n    public Class<?> supportJavaTypeKey() {\n        throw new UnsupportedOperationException(\"暂不支持，也不需要\");\n    }\n\n    @Override\n    public CellDataTypeEnum supportExcelTypeKey() {\n        throw new UnsupportedOperationException(\"暂不支持，也不需要\");\n    }\n\n    @Override\n    public WriteCellData<String> convertToExcelData(Integer value, ExcelContentProperty contentProperty,\n                                                    GlobalConfiguration globalConfiguration) {\n        BigDecimal result = BigDecimal.valueOf(value)\n                .divide(new BigDecimal(100), 2, RoundingMode.HALF_UP);\n        return new WriteCellData<>(result.toString());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-excel/src/main/java/co/yixiang/yshop/framework/excel/core/function/ExcelColumnSelectFunction.java",
    "content": "package co.yixiang.yshop.framework.excel.core.function;\n\nimport java.util.List;\n\n/**\n * Excel 列下拉数据源获取接口\n *\n * 为什么不直接解析字典还搞个接口？考虑到有的下拉数据不是从字典中获取的所有需要做一个兼容\n\n * @author HUIHUI\n */\npublic interface ExcelColumnSelectFunction {\n\n    /**\n     * 获得方法名称\n     *\n     * @return 方法名称\n     */\n    String getName();\n\n    /**\n     * 获得列下拉数据源\n     *\n     * @return 下拉数据源\n     */\n    List<String> getOptions();\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-excel/src/main/java/co/yixiang/yshop/framework/excel/core/handler/SelectSheetWriteHandler.java",
    "content": "package co.yixiang.yshop.framework.excel.core.handler;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.lang.Assert;\nimport cn.hutool.core.map.MapUtil;\nimport cn.hutool.core.util.ObjectUtil;\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.extra.spring.SpringUtil;\nimport cn.hutool.poi.excel.ExcelUtil;\nimport co.yixiang.yshop.framework.common.core.KeyValue;\nimport co.yixiang.yshop.framework.dict.core.DictFrameworkUtils;\nimport co.yixiang.yshop.framework.excel.core.annotations.ExcelColumnSelect;\nimport co.yixiang.yshop.framework.excel.core.function.ExcelColumnSelectFunction;\nimport com.alibaba.excel.annotation.ExcelProperty;\nimport com.alibaba.excel.write.handler.SheetWriteHandler;\nimport com.alibaba.excel.write.metadata.holder.WriteSheetHolder;\nimport com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.poi.hssf.usermodel.HSSFDataValidation;\nimport org.apache.poi.ss.usermodel.*;\nimport org.apache.poi.ss.util.CellRangeAddressList;\n\nimport java.lang.reflect.Field;\nimport java.util.Comparator;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport static co.yixiang.yshop.framework.common.util.collection.CollectionUtils.convertList;\n\n/**\n * 基于固定 sheet 实现下拉框\n *\n * @author HUIHUI\n */\n@Slf4j\npublic class SelectSheetWriteHandler implements SheetWriteHandler {\n\n    /**\n     * 数据起始行从 0 开始\n     *\n     * 约定：本项目第一行有标题所以从 1 开始如果您的 Excel 有多行标题请自行更改\n     */\n    public static final int FIRST_ROW = 1;\n    /**\n     * 下拉列需要创建下拉框的行数，默认两千行如需更多请自行调整\n     */\n    public static final int LAST_ROW = 2000;\n\n    private static final String DICT_SHEET_NAME = \"字典sheet\";\n\n    /**\n     * key: 列 value: 下拉数据源\n     */\n    private final Map<Integer, List<String>> selectMap = new HashMap<>();\n\n    public SelectSheetWriteHandler(Class<?> head) {\n        // 加载下拉数据获取接口\n        Map<String, ExcelColumnSelectFunction> beansMap = SpringUtil.getBeanFactory().getBeansOfType(ExcelColumnSelectFunction.class);\n        if (MapUtil.isEmpty(beansMap)) {\n            return;\n        }\n\n        // 解析下拉数据\n        int colIndex = 0;\n        for (Field field : head.getDeclaredFields()) {\n            if (field.isAnnotationPresent(ExcelColumnSelect.class)) {\n                ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);\n                if (excelProperty != null && excelProperty.index() != -1) {\n                    colIndex = excelProperty.index();\n                }\n                getSelectDataList(colIndex, field);\n            }\n            colIndex++;\n        }\n    }\n\n    /**\n     * 获得下拉数据，并添加到 {@link #selectMap} 中\n     *\n     * @param colIndex 列索引\n     * @param field    字段\n     */\n    private void getSelectDataList(int colIndex, Field field) {\n        ExcelColumnSelect columnSelect = field.getAnnotation(ExcelColumnSelect.class);\n        String dictType = columnSelect.dictType();\n        String functionName = columnSelect.functionName();\n        Assert.isTrue(ObjectUtil.isNotEmpty(dictType) || ObjectUtil.isNotEmpty(functionName),\n                \"Field({}) 的 @ExcelColumnSelect 注解，dictType 和 functionName 不能同时为空\", field.getName());\n\n        // 情况一：使用 dictType 获得下拉数据\n        if (StrUtil.isNotEmpty(dictType)) { // 情况一： 字典数据 （默认）\n            selectMap.put(colIndex, DictFrameworkUtils.getDictDataLabelList(dictType));\n            return;\n        }\n\n        // 情况二：使用 functionName 获得下拉数据\n        Map<String, ExcelColumnSelectFunction> functionMap = SpringUtil.getApplicationContext().getBeansOfType(ExcelColumnSelectFunction.class);\n        ExcelColumnSelectFunction function = CollUtil.findOne(functionMap.values(), item -> item.getName().equals(functionName));\n        Assert.notNull(function, \"未找到对应的 function({})\", functionName);\n        selectMap.put(colIndex, function.getOptions());\n    }\n\n    @Override\n    public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {\n        if (CollUtil.isEmpty(selectMap)) {\n            return;\n        }\n\n        // 1. 获取相应操作对象\n        DataValidationHelper helper = writeSheetHolder.getSheet().getDataValidationHelper(); // 需要设置下拉框的 sheet 页的数据验证助手\n        Workbook workbook = writeWorkbookHolder.getWorkbook(); // 获得工作簿\n        List<KeyValue<Integer, List<String>>> keyValues = convertList(selectMap.entrySet(), entry -> new KeyValue<>(entry.getKey(), entry.getValue()));\n        keyValues.sort(Comparator.comparing(item -> item.getValue().size())); // 升序不然创建下拉会报错\n\n        // 2. 创建数据字典的 sheet 页\n        Sheet dictSheet = workbook.createSheet(DICT_SHEET_NAME);\n        for (KeyValue<Integer, List<String>> keyValue : keyValues) {\n            int rowLength = keyValue.getValue().size();\n            // 2.1 设置字典 sheet 页的值，每一列一个字典项\n            for (int i = 0; i < rowLength; i++) {\n                Row row = dictSheet.getRow(i);\n                if (row == null) {\n                    row = dictSheet.createRow(i);\n                }\n                row.createCell(keyValue.getKey()).setCellValue(keyValue.getValue().get(i));\n            }\n            // 2.2 设置单元格下拉选择\n            setColumnSelect(writeSheetHolder, workbook, helper, keyValue);\n        }\n    }\n\n    /**\n     * 设置单元格下拉选择\n     */\n    private static void setColumnSelect(WriteSheetHolder writeSheetHolder, Workbook workbook, DataValidationHelper helper,\n                                        KeyValue<Integer, List<String>> keyValue) {\n        // 1.1 创建可被其他单元格引用的名称\n        Name name = workbook.createName();\n        String excelColumn = ExcelUtil.indexToColName(keyValue.getKey());\n        // 1.2 下拉框数据来源 eg:字典sheet!$B1:$B2\n        String refers = DICT_SHEET_NAME + \"!$\" + excelColumn + \"$1:$\" + excelColumn + \"$\" + keyValue.getValue().size();\n        name.setNameName(\"dict\" + keyValue.getKey()); // 设置名称的名字\n        name.setRefersToFormula(refers); // 设置公式\n\n        // 2.1 设置约束\n        DataValidationConstraint constraint = helper.createFormulaListConstraint(\"dict\" + keyValue.getKey()); // 设置引用约束\n        // 设置下拉单元格的首行、末行、首列、末列\n        CellRangeAddressList rangeAddressList = new CellRangeAddressList(FIRST_ROW, LAST_ROW,\n                keyValue.getKey(), keyValue.getKey());\n        DataValidation validation = helper.createValidation(constraint, rangeAddressList);\n        if (validation instanceof HSSFDataValidation) {\n            validation.setSuppressDropDownArrow(false);\n        } else {\n            validation.setSuppressDropDownArrow(true);\n            validation.setShowErrorBox(true);\n        }\n        // 2.2 阻止输入非下拉框的值\n        validation.setErrorStyle(DataValidation.ErrorStyle.STOP);\n        validation.createErrorBox(\"提示\", \"此值不存在于下拉选择中！\");\n        // 2.3 添加下拉框约束\n        writeSheetHolder.getSheet().addValidationData(validation);\n    }\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-excel/src/main/java/co/yixiang/yshop/framework/excel/core/util/ExcelUtils.java",
    "content": "package co.yixiang.yshop.framework.excel.core.util;\n\nimport co.yixiang.yshop.framework.excel.core.handler.SelectSheetWriteHandler;\nimport com.alibaba.excel.EasyExcel;\nimport com.alibaba.excel.converters.longconverter.LongStringConverter;\nimport com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;\nimport jakarta.servlet.http.HttpServletResponse;\nimport org.springframework.web.multipart.MultipartFile;\n\nimport java.io.IOException;\nimport java.net.URLEncoder;\nimport java.nio.charset.StandardCharsets;\nimport java.util.List;\n\n/**\n * Excel 工具类\n *\n * @author yshop\n */\npublic class ExcelUtils {\n\n    /**\n     * 将列表以 Excel 响应给前端\n     *\n     * @param response  响应\n     * @param filename  文件名\n     * @param sheetName Excel sheet 名\n     * @param head      Excel head 头\n     * @param data      数据列表哦\n     * @param <T>       泛型，保证 head 和 data 类型的一致性\n     * @throws IOException 写入失败的情况\n     */\n    public static <T> void write(HttpServletResponse response, String filename, String sheetName,\n                                 Class<T> head, List<T> data) throws IOException {\n        // 输出 Excel\n        EasyExcel.write(response.getOutputStream(), head)\n                .autoCloseStream(false) // 不要自动关闭，交给 Servlet 自己处理\n                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy()) // 基于 column 长度，自动适配。最大 255 宽度\n                .registerWriteHandler(new SelectSheetWriteHandler(head)) // 基于固定 sheet 实现下拉框\n                .registerConverter(new LongStringConverter()) // 避免 Long 类型丢失精度\n                .sheet(sheetName).doWrite(data);\n        // 设置 header 和 contentType。写在最后的原因是，避免报错时，响应 contentType 已经被修改了\n        response.addHeader(\"Content-Disposition\", \"attachment;filename=\" + URLEncoder.encode(filename, StandardCharsets.UTF_8.name()));\n        response.setContentType(\"application/vnd.ms-excel;charset=UTF-8\");\n    }\n\n    public static <T> List<T> read(MultipartFile file, Class<T> head) throws IOException {\n        return EasyExcel.read(file.getInputStream(), head, null)\n                .autoCloseStream(false)  // 不要自动关闭，交给 Servlet 自己处理\n                .doReadAllSync();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-excel/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports",
    "content": "co.yixiang.yshop.framework.dict.config.YshopDictAutoConfiguration"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-excel/src/test/java/co/yixiang/yshop/framework/dict/core/util/DictFrameworkUtilsTest.java",
    "content": "package co.yixiang.yshop.framework.dict.core.util;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.dict.core.DictFrameworkUtils;\nimport co.yixiang.yshop.framework.test.core.ut.BaseMockitoUnitTest;\nimport co.yixiang.yshop.module.system.api.dict.DictDataApi;\nimport co.yixiang.yshop.module.system.api.dict.dto.DictDataRespDTO;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.Mock;\n\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.randomPojo;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.when;\n\n/**\n * {@link DictFrameworkUtils} 的单元测试\n */\npublic class DictFrameworkUtilsTest extends BaseMockitoUnitTest {\n\n    @Mock\n    private DictDataApi dictDataApi;\n\n    @BeforeEach\n    public void setUp() {\n        DictFrameworkUtils.init(dictDataApi);\n    }\n\n    @Test\n    public void testGetDictDataLabel() {\n        // mock 数据\n        DictDataRespDTO dataRespDTO = randomPojo(DictDataRespDTO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));\n        // mock 方法\n        when(dictDataApi.getDictData(dataRespDTO.getDictType(), dataRespDTO.getValue())).thenReturn(dataRespDTO);\n\n        // 断言返回值\n        assertEquals(dataRespDTO.getLabel(), DictFrameworkUtils.getDictDataLabel(dataRespDTO.getDictType(), dataRespDTO.getValue()));\n    }\n\n    @Test\n    public void testParseDictDataValue() {\n        // mock 数据\n        DictDataRespDTO resp = randomPojo(DictDataRespDTO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));\n        // mock 方法\n        when(dictDataApi.parseDictData(resp.getDictType(), resp.getLabel())).thenReturn(resp);\n\n        // 断言返回值\n        assertEquals(resp.getValue(), DictFrameworkUtils.parseDictDataValue(resp.getDictType(), resp.getLabel()));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-job/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>co.yixiang.boot</groupId>\n        <artifactId>yshop-framework</artifactId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>yshop-spring-boot-starter-job</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>任务拓展\n        1. 定时任务，基于 Quartz 拓展\n        2. 异步任务，基于 Spring Async 拓展\n    </description>\n    <url>https://gitee.com/guchengwuyue/yshop-drink</url>\n\n    <dependencies>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-common</artifactId>\n        </dependency>\n\n        <!-- Job 定时任务相关 -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-quartz</artifactId>\n        </dependency>\n\n        <!-- 工具类相关 -->\n        <dependency>\n            <groupId>jakarta.validation</groupId>\n            <artifactId>jakarta.validation-api</artifactId>\n        </dependency>\n\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-job/src/main/java/co/yixiang/yshop/framework/quartz/config/YshopAsyncAutoConfiguration.java",
    "content": "package co.yixiang.yshop.framework.quartz.config;\n\nimport com.alibaba.ttl.TtlRunnable;\nimport org.springframework.beans.BeansException;\nimport org.springframework.beans.factory.config.BeanPostProcessor;\nimport org.springframework.boot.autoconfigure.AutoConfiguration;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.scheduling.annotation.EnableAsync;\nimport org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;\n\n/**\n * 异步任务 Configuration\n */\n@AutoConfiguration\n@EnableAsync\npublic class YshopAsyncAutoConfiguration {\n\n    @Bean\n    public BeanPostProcessor threadPoolTaskExecutorBeanPostProcessor() {\n        return new BeanPostProcessor() {\n\n            @Override\n            public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {\n                if (!(bean instanceof ThreadPoolTaskExecutor)) {\n                    return bean;\n                }\n                // 修改提交的任务，接入 TransmittableThreadLocal\n                ThreadPoolTaskExecutor executor = (ThreadPoolTaskExecutor) bean;\n                executor.setTaskDecorator(TtlRunnable::get);\n                return executor;\n            }\n\n        };\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-job/src/main/java/co/yixiang/yshop/framework/quartz/config/YshopQuartzAutoConfiguration.java",
    "content": "package co.yixiang.yshop.framework.quartz.config;\n\nimport co.yixiang.yshop.framework.quartz.core.scheduler.SchedulerManager;\nimport lombok.extern.slf4j.Slf4j;\nimport org.quartz.Scheduler;\nimport org.springframework.boot.autoconfigure.AutoConfiguration;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.scheduling.annotation.EnableScheduling;\n\nimport java.util.Optional;\n\n/**\n * 定时任务 Configuration\n */\n@AutoConfiguration\n@EnableScheduling // 开启 Spring 自带的定时任务\n@Slf4j\npublic class YshopQuartzAutoConfiguration {\n\n    @Bean\n    public SchedulerManager schedulerManager(Optional<Scheduler> scheduler) {\n        if (!scheduler.isPresent()) {\n            log.info(\"[定时任务 - 已禁用]\");\n            return new SchedulerManager(null);\n        }\n        return new SchedulerManager(scheduler.get());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-job/src/main/java/co/yixiang/yshop/framework/quartz/core/enums/JobDataKeyEnum.java",
    "content": "package co.yixiang.yshop.framework.quartz.core.enums;\n\n/**\n * Quartz Job Data 的 key 枚举\n */\npublic enum JobDataKeyEnum {\n\n    JOB_ID,\n    JOB_HANDLER_NAME,\n    JOB_HANDLER_PARAM,\n    JOB_RETRY_COUNT, // 最大重试次数\n    JOB_RETRY_INTERVAL, // 每次重试间隔\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-job/src/main/java/co/yixiang/yshop/framework/quartz/core/handler/JobHandler.java",
    "content": "package co.yixiang.yshop.framework.quartz.core.handler;\n\n/**\n * 任务处理器\n *\n * @author yshop\n */\npublic interface JobHandler {\n\n    /**\n     * 执行任务\n     *\n     * @param param 参数\n     * @return 结果\n     * @throws Exception 异常\n     */\n    String execute(String param) throws Exception;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-job/src/main/java/co/yixiang/yshop/framework/quartz/core/handler/JobHandlerInvoker.java",
    "content": "package co.yixiang.yshop.framework.quartz.core.handler;\n\nimport cn.hutool.core.date.LocalDateTimeUtil;\nimport cn.hutool.core.lang.Assert;\nimport cn.hutool.core.thread.ThreadUtil;\nimport co.yixiang.yshop.framework.quartz.core.enums.JobDataKeyEnum;\nimport co.yixiang.yshop.framework.quartz.core.service.JobLogFrameworkService;\nimport lombok.extern.slf4j.Slf4j;\nimport org.quartz.DisallowConcurrentExecution;\nimport org.quartz.JobExecutionContext;\nimport org.quartz.JobExecutionException;\nimport org.quartz.PersistJobDataAfterExecution;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.scheduling.quartz.QuartzJobBean;\n\nimport jakarta.annotation.Resource;\nimport java.time.LocalDateTime;\n\nimport static cn.hutool.core.exceptions.ExceptionUtil.getRootCauseMessage;\n\n/**\n * 基础 Job 调用者，负责调用 {@link JobHandler#execute(String)} 执行任务\n *\n * @author yshop\n */\n@DisallowConcurrentExecution\n@PersistJobDataAfterExecution\n@Slf4j\npublic class JobHandlerInvoker extends QuartzJobBean {\n\n    @Resource\n    private ApplicationContext applicationContext;\n\n    @Resource\n    private JobLogFrameworkService jobLogFrameworkService;\n\n    @Override\n    protected void executeInternal(JobExecutionContext executionContext) throws JobExecutionException {\n        // 第一步，获得 Job 数据\n        Long jobId = executionContext.getMergedJobDataMap().getLong(JobDataKeyEnum.JOB_ID.name());\n        String jobHandlerName = executionContext.getMergedJobDataMap().getString(JobDataKeyEnum.JOB_HANDLER_NAME.name());\n        String jobHandlerParam = executionContext.getMergedJobDataMap().getString(JobDataKeyEnum.JOB_HANDLER_PARAM.name());\n        int refireCount  = executionContext.getRefireCount();\n        int retryCount = (Integer) executionContext.getMergedJobDataMap().getOrDefault(JobDataKeyEnum.JOB_RETRY_COUNT.name(), 0);\n        int retryInterval = (Integer) executionContext.getMergedJobDataMap().getOrDefault(JobDataKeyEnum.JOB_RETRY_INTERVAL.name(), 0);\n\n        // 第二步，执行任务\n        Long jobLogId = null;\n        LocalDateTime startTime = LocalDateTime.now();\n        String data = null;\n        Throwable exception = null;\n        try {\n            // 记录 Job 日志（初始）\n            jobLogId = jobLogFrameworkService.createJobLog(jobId, startTime, jobHandlerName, jobHandlerParam, refireCount + 1);\n            // 执行任务\n            data = this.executeInternal(jobHandlerName, jobHandlerParam);\n        } catch (Throwable ex) {\n            exception = ex;\n        }\n\n        // 第三步，记录执行日志\n        this.updateJobLogResultAsync(jobLogId, startTime, data, exception, executionContext);\n\n        // 第四步，处理有异常的情况\n        handleException(exception, refireCount, retryCount, retryInterval);\n    }\n\n    private String executeInternal(String jobHandlerName, String jobHandlerParam) throws Exception {\n        // 获得 JobHandler 对象\n        JobHandler jobHandler = applicationContext.getBean(jobHandlerName, JobHandler.class);\n        Assert.notNull(jobHandler, \"JobHandler 不会为空\");\n        // 执行任务\n        return jobHandler.execute(jobHandlerParam);\n    }\n\n    private void updateJobLogResultAsync(Long jobLogId, LocalDateTime startTime, String data, Throwable exception,\n                                         JobExecutionContext executionContext) {\n        LocalDateTime endTime = LocalDateTime.now();\n        // 处理是否成功\n        boolean success = exception == null;\n        if (!success) {\n            data = getRootCauseMessage(exception);\n        }\n        // 更新日志\n        try {\n            jobLogFrameworkService.updateJobLogResultAsync(jobLogId, endTime, (int) LocalDateTimeUtil.between(startTime, endTime).toMillis(), success, data);\n        } catch (Exception ex) {\n            log.error(\"[executeInternal][Job({}) logId({}) 记录执行日志失败({}/{})]\",\n                    executionContext.getJobDetail().getKey(), jobLogId, success, data);\n        }\n    }\n\n    private void handleException(Throwable exception,\n                                 int refireCount, int retryCount, int retryInterval) throws JobExecutionException {\n        // 如果有异常，则进行重试\n        if (exception == null) {\n            return;\n        }\n        // 情况一：如果到达重试上限，则直接抛出异常即可\n        if (refireCount >= retryCount) {\n            throw new JobExecutionException(exception);\n        }\n\n        // 情况二：如果未到达重试上限，则 sleep 一定间隔时间，然后重试\n        // 这里使用 sleep 来实现，主要还是希望实现比较简单。因为，同一时间，不会存在大量失败的 Job。\n        if (retryInterval > 0) {\n            ThreadUtil.sleep(retryInterval);\n        }\n        // 第二个参数，refireImmediately = true，表示立即重试\n        throw new JobExecutionException(exception, true);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-job/src/main/java/co/yixiang/yshop/framework/quartz/core/scheduler/SchedulerManager.java",
    "content": "package co.yixiang.yshop.framework.quartz.core.scheduler;\n\nimport co.yixiang.yshop.framework.quartz.core.enums.JobDataKeyEnum;\nimport co.yixiang.yshop.framework.quartz.core.handler.JobHandlerInvoker;\nimport org.quartz.*;\n\nimport static co.yixiang.yshop.framework.common.exception.enums.GlobalErrorCodeConstants.NOT_IMPLEMENTED;\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception0;\n\n/**\n * {@link org.quartz.Scheduler} 的管理器，负责创建任务\n *\n * 考虑到实现的简洁性，我们使用 jobHandlerName 作为唯一标识，即：\n * 1. Job 的 {@link JobDetail#getKey()}\n * 2. Trigger 的 {@link Trigger#getKey()}\n *\n * 另外，jobHandlerName 对应到 Spring Bean 的名字，直接调用\n *\n * @author yshop\n */\npublic class SchedulerManager {\n\n    private final Scheduler scheduler;\n\n    public SchedulerManager(Scheduler scheduler) {\n        this.scheduler = scheduler;\n    }\n\n    /**\n     * 添加 Job 到 Quartz 中\n     *\n     * @param jobId 任务编号\n     * @param jobHandlerName 任务处理器的名字\n     * @param jobHandlerParam 任务处理器的参数\n     * @param cronExpression CRON 表达式\n     * @param retryCount 重试次数\n     * @param retryInterval 重试间隔\n     * @throws SchedulerException 添加异常\n     */\n    public void addJob(Long jobId, String jobHandlerName, String jobHandlerParam, String cronExpression,\n                       Integer retryCount, Integer retryInterval)\n            throws SchedulerException {\n        validateScheduler();\n        // 创建 JobDetail 对象\n        JobDetail jobDetail = JobBuilder.newJob(JobHandlerInvoker.class)\n                .usingJobData(JobDataKeyEnum.JOB_ID.name(), jobId)\n                .usingJobData(JobDataKeyEnum.JOB_HANDLER_NAME.name(), jobHandlerName)\n                .withIdentity(jobHandlerName).build();\n        // 创建 Trigger 对象\n        Trigger trigger = this.buildTrigger(jobHandlerName, jobHandlerParam, cronExpression, retryCount, retryInterval);\n        // 新增 Job 调度\n        scheduler.scheduleJob(jobDetail, trigger);\n    }\n\n    /**\n     * 更新 Job 到 Quartz\n     *\n     * @param jobHandlerName 任务处理器的名字\n     * @param jobHandlerParam 任务处理器的参数\n     * @param cronExpression CRON 表达式\n     * @param retryCount 重试次数\n     * @param retryInterval 重试间隔\n     * @throws SchedulerException 更新异常\n     */\n    public void updateJob(String jobHandlerName, String jobHandlerParam, String cronExpression,\n                          Integer retryCount, Integer retryInterval)\n            throws SchedulerException {\n        validateScheduler();\n        // 创建新 Trigger 对象\n        Trigger newTrigger = this.buildTrigger(jobHandlerName, jobHandlerParam, cronExpression, retryCount, retryInterval);\n        // 修改调度\n        scheduler.rescheduleJob(new TriggerKey(jobHandlerName), newTrigger);\n    }\n\n    /**\n     * 删除 Quartz 中的 Job\n     *\n     * @param jobHandlerName 任务处理器的名字\n     * @throws SchedulerException 删除异常\n     */\n    public void deleteJob(String jobHandlerName) throws SchedulerException {\n        validateScheduler();\n        // 暂停 Trigger 对象\n        scheduler.pauseTrigger(new TriggerKey(jobHandlerName));\n        // 取消并删除 Job 调度\n        scheduler.unscheduleJob(new TriggerKey(jobHandlerName));\n        scheduler.deleteJob(new JobKey(jobHandlerName));\n    }\n\n    /**\n     * 暂停 Quartz 中的 Job\n     *\n     * @param jobHandlerName 任务处理器的名字\n     * @throws SchedulerException 暂停异常\n     */\n    public void pauseJob(String jobHandlerName) throws SchedulerException {\n        validateScheduler();\n        scheduler.pauseJob(new JobKey(jobHandlerName));\n    }\n\n    /**\n     * 启动 Quartz 中的 Job\n     *\n     * @param jobHandlerName 任务处理器的名字\n     * @throws SchedulerException 启动异常\n     */\n    public void resumeJob(String jobHandlerName) throws SchedulerException {\n        validateScheduler();\n        scheduler.resumeJob(new JobKey(jobHandlerName));\n        scheduler.resumeTrigger(new TriggerKey(jobHandlerName));\n    }\n\n    /**\n     * 立即触发一次 Quartz 中的 Job\n     *\n     * @param jobId 任务编号\n     * @param jobHandlerName 任务处理器的名字\n     * @param jobHandlerParam 任务处理器的参数\n     * @throws SchedulerException 触发异常\n     */\n    public void triggerJob(Long jobId, String jobHandlerName, String jobHandlerParam)\n            throws SchedulerException {\n        validateScheduler();\n        // 触发任务\n        JobDataMap data = new JobDataMap(); // 无需重试，所以不设置 retryCount 和 retryInterval\n        data.put(JobDataKeyEnum.JOB_ID.name(), jobId);\n        data.put(JobDataKeyEnum.JOB_HANDLER_NAME.name(), jobHandlerName);\n        data.put(JobDataKeyEnum.JOB_HANDLER_PARAM.name(), jobHandlerParam);\n        scheduler.triggerJob(new JobKey(jobHandlerName), data);\n    }\n\n    private Trigger buildTrigger(String jobHandlerName, String jobHandlerParam, String cronExpression,\n                                 Integer retryCount, Integer retryInterval) {\n        return TriggerBuilder.newTrigger()\n                .withIdentity(jobHandlerName)\n                .withSchedule(CronScheduleBuilder.cronSchedule(cronExpression))\n                .usingJobData(JobDataKeyEnum.JOB_HANDLER_PARAM.name(), jobHandlerParam)\n                .usingJobData(JobDataKeyEnum.JOB_RETRY_COUNT.name(), retryCount)\n                .usingJobData(JobDataKeyEnum.JOB_RETRY_INTERVAL.name(), retryInterval)\n                .build();\n    }\n\n    private void validateScheduler() {\n        if (scheduler == null) {\n            throw exception0(NOT_IMPLEMENTED.getCode(),\n                    \"[定时任务 - 已禁用]\");\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-job/src/main/java/co/yixiang/yshop/framework/quartz/core/service/JobLogFrameworkService.java",
    "content": "package co.yixiang.yshop.framework.quartz.core.service;\n\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\nimport java.time.LocalDateTime;\n\n/**\n * Job 日志 Framework Service 接口\n *\n * @author yshop\n */\npublic interface JobLogFrameworkService {\n\n    /**\n     * 创建 Job 日志\n     *\n     * @param jobId           任务编号\n     * @param beginTime       开始时间\n     * @param jobHandlerName  Job 处理器的名字\n     * @param jobHandlerParam Job 处理器的参数\n     * @param executeIndex    第几次执行\n     * @return Job 日志的编号\n     */\n    Long createJobLog(@NotNull(message = \"任务编号不能为空\") Long jobId,\n                      @NotNull(message = \"开始时间\") LocalDateTime beginTime,\n                      @NotEmpty(message = \"Job 处理器的名字不能为空\") String jobHandlerName,\n                      String jobHandlerParam,\n                      @NotNull(message = \"第几次执行不能为空\") Integer executeIndex);\n\n    /**\n     * 更新 Job 日志的执行结果\n     *\n     * @param logId    日志编号\n     * @param endTime  结束时间。因为是异步，避免记录时间不准去\n     * @param duration 运行时长，单位：毫秒\n     * @param success  是否成功\n     * @param result   成功数据\n     */\n    void updateJobLogResultAsync(@NotNull(message = \"日志编号不能为空\") Long logId,\n                                 @NotNull(message = \"结束时间不能为空\") LocalDateTime endTime,\n                                 @NotNull(message = \"运行时长不能为空\") Integer duration,\n                                 boolean success, String result);\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-job/src/main/java/co/yixiang/yshop/framework/quartz/core/util/CronUtils.java",
    "content": "package co.yixiang.yshop.framework.quartz.core.util;\n\nimport cn.hutool.core.date.LocalDateTimeUtil;\nimport org.quartz.CronExpression;\n\nimport java.text.ParseException;\nimport java.time.LocalDateTime;\nimport java.util.ArrayList;\nimport java.util.Date;\nimport java.util.List;\n\n/**\n * Quartz Cron 表达式的工具类\n *\n * @author yshop\n */\npublic class CronUtils {\n\n    /**\n     * 校验 CRON 表达式是否有效\n     *\n     * @param cronExpression CRON 表达式\n     * @return 是否有效\n     */\n    public static boolean isValid(String cronExpression) {\n        return CronExpression.isValidExpression(cronExpression);\n    }\n\n    /**\n     * 基于 CRON 表达式，获得下 n 个满足执行的时间\n     *\n     * @param cronExpression CRON 表达式\n     * @param n 数量\n     * @return 满足条件的执行时间\n     */\n    public static List<LocalDateTime> getNextTimes(String cronExpression, int n) {\n        // 获得 CronExpression 对象\n        CronExpression cron;\n        try {\n            cron = new CronExpression(cronExpression);\n        } catch (ParseException e) {\n            throw new IllegalArgumentException(e.getMessage());\n        }\n        // 从当前开始计算，n 个满足条件的\n        Date now = new Date();\n        List<LocalDateTime> nextTimes = new ArrayList<>(n);\n        for (int i = 0; i < n; i++) {\n            Date nextTime = cron.getNextValidTimeAfter(now);\n            nextTimes.add(LocalDateTimeUtil.of(nextTime));\n            // 切换现在，为下一个触发时间；\n            now = nextTime;\n        }\n        return nextTimes;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-job/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports",
    "content": "co.yixiang.yshop.framework.quartz.config.YshopQuartzAutoConfiguration\nco.yixiang.yshop.framework.quartz.config.YshopAsyncAutoConfiguration"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-monitor/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>co.yixiang.boot</groupId>\n        <artifactId>yshop-framework</artifactId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>yshop-spring-boot-starter-monitor</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>服务监控，提供链路追踪、日志服务、指标收集等等功能</description>\n    <url>https://gitee.com/guchengwuyue/yshop-drink</url>\n\n    <dependencies>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-common</artifactId>\n        </dependency>\n\n        <!-- Spring 核心 -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-aop</artifactId>\n        </dependency>\n\n        <!-- Web 相关 -->\n        <dependency>\n            <groupId>org.springframework</groupId>\n            <artifactId>spring-web</artifactId>\n            <scope>provided</scope> <!-- 设置为 provided，只有 TraceFilter 使用 -->\n        </dependency>\n\n        <dependency>\n            <groupId>jakarta.servlet</groupId>\n            <artifactId>jakarta.servlet-api</artifactId>\n            <scope>provided</scope> <!-- 设置为 provided，只有 TraceFilter 使用 -->\n        </dependency>\n\n        <!-- 监控相关 -->\n        <dependency>\n            <groupId>io.opentracing</groupId>\n            <artifactId>opentracing-util</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.skywalking</groupId>\n            <artifactId>apm-toolkit-trace</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.skywalking</groupId>\n            <artifactId>apm-toolkit-logback-1.x</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.skywalking</groupId>\n            <artifactId>apm-toolkit-opentracing</artifactId>\n        </dependency>\n\n        <!-- Micrometer 对 Prometheus 的支持 -->\n        <dependency>\n            <groupId>io.micrometer</groupId>\n            <artifactId>micrometer-registry-prometheus</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>de.codecentric</groupId>\n            <artifactId>spring-boot-admin-starter-client</artifactId> <!-- 实现 Spring Boot Admin Server 服务端 -->\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-monitor/src/main/java/co/yixiang/yshop/framework/tracer/config/TracerProperties.java",
    "content": "package co.yixiang.yshop.framework.tracer.config;\n\nimport lombok.Data;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\n\n/**\n * BizTracer配置类\n *\n * @author 麻薯\n */\n@ConfigurationProperties(\"yshop.tracer\")\n@Data\npublic class TracerProperties {\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-monitor/src/main/java/co/yixiang/yshop/framework/tracer/config/YshopMetricsAutoConfiguration.java",
    "content": "package co.yixiang.yshop.framework.tracer.config;\n\nimport io.micrometer.core.instrument.MeterRegistry;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.boot.actuate.autoconfigure.metrics.MeterRegistryCustomizer;\nimport org.springframework.boot.autoconfigure.AutoConfiguration;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnClass;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.context.annotation.Bean;\n\n/**\n * Metrics 配置类\n *\n * @author yshop\n */\n@AutoConfiguration\n@ConditionalOnClass({MeterRegistryCustomizer.class})\n@ConditionalOnProperty(prefix = \"yshop.metrics\", value = \"enable\", matchIfMissing = true) // 允许使用 yshop.metrics.enable=false 禁用 Metrics\npublic class YshopMetricsAutoConfiguration {\n\n    @Bean\n    public MeterRegistryCustomizer<MeterRegistry> metricsCommonTags(\n            @Value(\"${spring.application.name}\") String applicationName) {\n        return registry -> registry.config().commonTags(\"application\", applicationName);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-monitor/src/main/java/co/yixiang/yshop/framework/tracer/config/YshopTracerAutoConfiguration.java",
    "content": "package co.yixiang.yshop.framework.tracer.config;\n\nimport co.yixiang.yshop.framework.common.enums.WebFilterOrderEnum;\nimport co.yixiang.yshop.framework.tracer.core.aop.BizTraceAspect;\nimport co.yixiang.yshop.framework.tracer.core.filter.TraceFilter;\nimport org.springframework.boot.autoconfigure.AutoConfiguration;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnClass;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.boot.web.servlet.FilterRegistrationBean;\nimport org.springframework.context.annotation.Bean;\n\n/**\n * Tracer 配置类\n *\n * @author mashu\n */\n@AutoConfiguration\n@ConditionalOnClass({BizTraceAspect.class})\n@EnableConfigurationProperties(TracerProperties.class)\n@ConditionalOnProperty(prefix = \"yshop.tracer\", value = \"enable\", matchIfMissing = true)\npublic class YshopTracerAutoConfiguration {\n\n    // TODO @yshop：重要。目前 opentracing 版本存在冲突，要么保证 skywalking，要么保证阿里云短信 sdk\n//    @Bean\n//    public TracerProperties bizTracerProperties() {\n//        return new TracerProperties();\n//    }\n//\n//    @Bean\n//    public BizTraceAspect bizTracingAop() {\n//        return new BizTraceAspect(tracer());\n//    }\n//\n//    @Bean\n//    public Tracer tracer() {\n//        // 创建 SkywalkingTracer 对象\n//        SkywalkingTracer tracer = new SkywalkingTracer();\n//        // 设置为 GlobalTracer 的追踪器\n//        GlobalTracer.register(tracer);\n//        return tracer;\n//    }\n\n    /**\n     * 创建 TraceFilter 过滤器，响应 header 设置 traceId\n     */\n    @Bean\n    public FilterRegistrationBean<TraceFilter> traceFilter() {\n        FilterRegistrationBean<TraceFilter> registrationBean = new FilterRegistrationBean<>();\n        registrationBean.setFilter(new TraceFilter());\n        registrationBean.setOrder(WebFilterOrderEnum.TRACE_FILTER);\n        return registrationBean;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-monitor/src/main/java/co/yixiang/yshop/framework/tracer/core/annotation/BizTrace.java",
    "content": "package co.yixiang.yshop.framework.tracer.core.annotation;\n\nimport java.lang.annotation.*;\n\n/**\n * 打印业务编号 / 业务类型注解\n *\n * 使用时，需要设置 SkyWalking OAP Server 的 application.yaml 配置文件，修改 SW_SEARCHABLE_TAG_KEYS 配置项，\n * 增加 biz.type 和 biz.id 两值，然后重启 SkyWalking OAP Server 服务器。\n *\n * @author 麻薯\n */\n@Target({ElementType.METHOD})\n@Retention(RetentionPolicy.RUNTIME)\n@Inherited\npublic @interface BizTrace {\n\n    /**\n     * 业务编号 tag 名\n     */\n    String ID_TAG = \"biz.id\";\n    /**\n     * 业务类型 tag 名\n     */\n    String TYPE_TAG = \"biz.type\";\n\n    /**\n     * @return 操作名\n     */\n    String operationName() default \"\";\n\n    /**\n     * @return 业务编号\n     */\n    String id();\n\n    /**\n     * @return 业务类型\n     */\n    String type();\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-monitor/src/main/java/co/yixiang/yshop/framework/tracer/core/aop/BizTraceAspect.java",
    "content": "package co.yixiang.yshop.framework.tracer.core.aop;\n\nimport cn.hutool.core.map.MapUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.tracer.core.annotation.BizTrace;\nimport co.yixiang.yshop.framework.common.util.spring.SpringExpressionUtils;\nimport co.yixiang.yshop.framework.tracer.core.util.TracerFrameworkUtils;\nimport io.opentracing.Span;\nimport io.opentracing.Tracer;\nimport io.opentracing.tag.Tags;\nimport lombok.AllArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.aspectj.lang.ProceedingJoinPoint;\nimport org.aspectj.lang.annotation.Around;\nimport org.aspectj.lang.annotation.Aspect;\n\nimport java.util.Map;\n\nimport static java.util.Arrays.asList;\n\n/**\n * {@link BizTrace} 切面，记录业务链路\n *\n * @author mashu\n */\n@Aspect\n@AllArgsConstructor\n@Slf4j\npublic class BizTraceAspect {\n\n    private static final String BIZ_OPERATION_NAME_PREFIX = \"Biz/\";\n\n    private final Tracer tracer;\n\n    @Around(value = \"@annotation(trace)\")\n    public Object around(ProceedingJoinPoint joinPoint, BizTrace trace) throws Throwable {\n        // 创建 span\n        String operationName = getOperationName(joinPoint, trace);\n        Span span = tracer.buildSpan(operationName)\n                .withTag(Tags.COMPONENT.getKey(), \"biz\")\n                .start();\n        try {\n            // 执行原有方法\n            return joinPoint.proceed();\n        } catch (Throwable throwable) {\n            TracerFrameworkUtils.onError(throwable, span);\n            throw throwable;\n        } finally {\n            // 设置 Span 的 biz 属性\n            setBizTag(span, joinPoint, trace);\n            // 完成 Span\n            span.finish();\n        }\n    }\n\n    private String getOperationName(ProceedingJoinPoint joinPoint, BizTrace trace) {\n        // 自定义操作名\n        if (StrUtil.isNotEmpty(trace.operationName())) {\n            return BIZ_OPERATION_NAME_PREFIX + trace.operationName();\n        }\n        // 默认操作名，使用方法名\n        return BIZ_OPERATION_NAME_PREFIX\n                + joinPoint.getSignature().getDeclaringType().getSimpleName()\n                + \"/\" + joinPoint.getSignature().getName();\n    }\n\n    private void setBizTag(Span span, ProceedingJoinPoint joinPoint, BizTrace trace) {\n        try {\n            Map<String, Object> result = SpringExpressionUtils.parseExpressions(joinPoint, asList(trace.type(), trace.id()));\n            span.setTag(BizTrace.TYPE_TAG, MapUtil.getStr(result, trace.type()));\n            span.setTag(BizTrace.ID_TAG, MapUtil.getStr(result, trace.id()));\n        } catch (Exception ex) {\n            log.error(\"[setBizTag][解析 bizType 与 bizId 发生异常]\", ex);\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-monitor/src/main/java/co/yixiang/yshop/framework/tracer/core/filter/TraceFilter.java",
    "content": "package co.yixiang.yshop.framework.tracer.core.filter;\n\nimport co.yixiang.yshop.framework.common.util.monitor.TracerUtils;\nimport org.springframework.web.filter.OncePerRequestFilter;\n\nimport jakarta.servlet.FilterChain;\nimport jakarta.servlet.ServletException;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport java.io.IOException;\n\n/**\n * Trace 过滤器，打印 traceId 到 header 中返回\n *\n * @author yshop\n */\npublic class TraceFilter extends OncePerRequestFilter {\n\n    /**\n     * Header 名 - 链路追踪编号\n     */\n    private static final String HEADER_NAME_TRACE_ID = \"trace-id\";\n\n    @Override\n    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)\n            throws IOException, ServletException {\n        // 设置响应 traceId\n        response.addHeader(HEADER_NAME_TRACE_ID, TracerUtils.getTraceId());\n        // 继续过滤\n        chain.doFilter(request, response);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-monitor/src/main/java/co/yixiang/yshop/framework/tracer/core/util/TracerFrameworkUtils.java",
    "content": "package co.yixiang.yshop.framework.tracer.core.util;\n\nimport io.opentracing.Span;\nimport io.opentracing.tag.Tags;\n\nimport java.io.PrintWriter;\nimport java.io.StringWriter;\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * 链路追踪 Util\n *\n * @author yshop\n */\npublic class TracerFrameworkUtils {\n\n    /**\n     * 将异常记录到 Span 中，参考自 com.aliyuncs.utils.TraceUtils\n     *\n     * @param throwable 异常\n     * @param span Span\n     */\n    public static void onError(Throwable throwable, Span span) {\n        Tags.ERROR.set(span, Boolean.TRUE);\n        if (throwable != null) {\n            span.log(errorLogs(throwable));\n        }\n    }\n\n    private static Map<String, Object> errorLogs(Throwable throwable) {\n        Map<String, Object> errorLogs = new HashMap<String, Object>(10);\n        errorLogs.put(\"event\", Tags.ERROR.getKey());\n        errorLogs.put(\"error.object\", throwable);\n        errorLogs.put(\"error.kind\", throwable.getClass().getName());\n        String message = throwable.getCause() != null ? throwable.getCause().getMessage() : throwable.getMessage();\n        if (message != null) {\n            errorLogs.put(\"message\", message);\n        }\n        StringWriter sw = new StringWriter();\n        throwable.printStackTrace(new PrintWriter(sw));\n        errorLogs.put(\"stack\", sw.toString());\n        return errorLogs;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-monitor/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports",
    "content": "co.yixiang.yshop.framework.tracer.config.YshopTracerAutoConfiguration\nco.yixiang.yshop.framework.tracer.config.YshopMetricsAutoConfiguration"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-mq/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>co.yixiang.boot</groupId>\n        <artifactId>yshop-framework</artifactId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>yshop-spring-boot-starter-mq</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>消息队列，支持 Redis、RocketMQ、RabbitMQ、Kafka 四种</description>\n    <url>https://gitee.com/guchengwuyue/yshop-drink</url>\n\n    <dependencies>\n        <!-- DB 相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-redis</artifactId>\n        </dependency>\n\n        <!-- 消息队列相关 -->\n        <dependency>\n            <groupId>org.springframework.kafka</groupId>\n            <artifactId>spring-kafka</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.amqp</groupId>\n            <artifactId>spring-rabbit</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.rocketmq</groupId>\n            <artifactId>rocketmq-spring-boot-starter</artifactId>\n            <optional>true</optional>\n        </dependency>\n    </dependencies>\n\n</project>"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-mq/src/main/java/co/yixiang/yshop/framework/mq/rabbitmq/config/YshopRabbitMQAutoConfiguration.java",
    "content": "package co.yixiang.yshop.framework.mq.rabbitmq.config;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;\nimport org.springframework.amqp.support.converter.MessageConverter;\nimport org.springframework.boot.autoconfigure.AutoConfiguration;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnClass;\nimport org.springframework.context.annotation.Bean;\n\n/**\n * RabbitMQ 消息队列配置类\n *\n * @author yshop\n */\n@AutoConfiguration\n@Slf4j\n@ConditionalOnClass(name = \"org.springframework.amqp.rabbit.core.RabbitTemplate\")\npublic class YshopRabbitMQAutoConfiguration {\n\n    /**\n     * Jackson2JsonMessageConverter Bean：使用 jackson 序列化消息\n     */\n    @Bean\n    public MessageConverter createMessageConverter() {\n        return new Jackson2JsonMessageConverter();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-mq/src/main/java/co/yixiang/yshop/framework/mq/redis/config/YshopRedisMQConsumerAutoConfiguration.java",
    "content": "package co.yixiang.yshop.framework.mq.redis.config;\n\nimport cn.hutool.core.map.MapUtil;\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.system.SystemUtil;\nimport co.yixiang.yshop.framework.common.enums.DocumentEnum;\nimport co.yixiang.yshop.framework.mq.redis.core.RedisMQTemplate;\nimport co.yixiang.yshop.framework.mq.redis.core.job.RedisPendingMessageResendJob;\nimport co.yixiang.yshop.framework.mq.redis.core.pubsub.AbstractRedisChannelMessageListener;\nimport co.yixiang.yshop.framework.mq.redis.core.stream.AbstractRedisStreamMessageListener;\nimport co.yixiang.yshop.framework.redis.config.YshopRedisAutoConfiguration;\nimport lombok.extern.slf4j.Slf4j;\nimport org.redisson.api.RedissonClient;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.boot.autoconfigure.AutoConfiguration;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnBean;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.data.redis.connection.RedisServerCommands;\nimport org.springframework.data.redis.connection.stream.Consumer;\nimport org.springframework.data.redis.connection.stream.ObjectRecord;\nimport org.springframework.data.redis.connection.stream.ReadOffset;\nimport org.springframework.data.redis.connection.stream.StreamOffset;\nimport org.springframework.data.redis.core.RedisCallback;\nimport org.springframework.data.redis.core.RedisTemplate;\nimport org.springframework.data.redis.listener.ChannelTopic;\nimport org.springframework.data.redis.listener.RedisMessageListenerContainer;\nimport org.springframework.data.redis.stream.StreamMessageListenerContainer;\nimport org.springframework.scheduling.annotation.EnableScheduling;\n\nimport java.util.List;\nimport java.util.Properties;\n\n/**\n * Redis 消息队列 Consumer 配置类\n *\n * @author yshop\n */\n@Slf4j\n@EnableScheduling // 启用定时任务，用于 RedisPendingMessageResendJob 重发消息\n@AutoConfiguration(after = YshopRedisAutoConfiguration.class)\npublic class YshopRedisMQConsumerAutoConfiguration {\n\n    /**\n     * 创建 Redis Pub/Sub 广播消费的容器\n     */\n    @Bean\n    @ConditionalOnBean(AbstractRedisChannelMessageListener.class) // 只有 AbstractChannelMessageListener 存在的时候，才需要注册 Redis pubsub 监听\n    public RedisMessageListenerContainer redisMessageListenerContainer(\n            RedisMQTemplate redisMQTemplate, List<AbstractRedisChannelMessageListener<?>> listeners) {\n        // 创建 RedisMessageListenerContainer 对象\n        RedisMessageListenerContainer container = new RedisMessageListenerContainer();\n        // 设置 RedisConnection 工厂。\n        container.setConnectionFactory(redisMQTemplate.getRedisTemplate().getRequiredConnectionFactory());\n        // 添加监听器\n        listeners.forEach(listener -> {\n            listener.setRedisMQTemplate(redisMQTemplate);\n            container.addMessageListener(listener, new ChannelTopic(listener.getChannel()));\n            log.info(\"[redisMessageListenerContainer][注册 Channel({}) 对应的监听器({})]\",\n                    listener.getChannel(), listener.getClass().getName());\n        });\n        return container;\n    }\n\n    /**\n     * 创建 Redis Stream 重新消费的任务\n     */\n    @Bean\n    @ConditionalOnBean(AbstractRedisStreamMessageListener.class) // 只有 AbstractStreamMessageListener 存在的时候，才需要注册 Redis pubsub 监听\n    public RedisPendingMessageResendJob redisPendingMessageResendJob(List<AbstractRedisStreamMessageListener<?>> listeners,\n                                                                     RedisMQTemplate redisTemplate,\n                                                                     @Value(\"${spring.application.name}\") String groupName,\n                                                                     RedissonClient redissonClient) {\n        return new RedisPendingMessageResendJob(listeners, redisTemplate, groupName, redissonClient);\n    }\n\n    /**\n     * 创建 Redis Stream 集群消费的容器\n     *\n     * 基础知识：<a href=\"https://www.geek-book.com/src/docs/redis/redis/redis.io/commands/xreadgroup.html\">Redis Stream 的 xreadgroup 命令</a>\n     */\n    @Bean(initMethod = \"start\", destroyMethod = \"stop\")\n    @ConditionalOnBean(AbstractRedisStreamMessageListener.class) // 只有 AbstractStreamMessageListener 存在的时候，才需要注册 Redis pubsub 监听\n    public StreamMessageListenerContainer<String, ObjectRecord<String, String>> redisStreamMessageListenerContainer(\n            RedisMQTemplate redisMQTemplate, List<AbstractRedisStreamMessageListener<?>> listeners) {\n        RedisTemplate<String, ?> redisTemplate = redisMQTemplate.getRedisTemplate();\n        checkRedisVersion(redisTemplate);\n        // 第一步，创建 StreamMessageListenerContainer 容器\n        // 创建 options 配置\n        StreamMessageListenerContainer.StreamMessageListenerContainerOptions<String, ObjectRecord<String, String>> containerOptions =\n                StreamMessageListenerContainer.StreamMessageListenerContainerOptions.builder()\n                        .batchSize(10) // 一次性最多拉取多少条消息\n                        .targetType(String.class) // 目标类型。统一使用 String，通过自己封装的 AbstractStreamMessageListener 去反序列化\n                        .build();\n        // 创建 container 对象\n        StreamMessageListenerContainer<String, ObjectRecord<String, String>> container =\n                StreamMessageListenerContainer.create(redisMQTemplate.getRedisTemplate().getRequiredConnectionFactory(), containerOptions);\n\n        // 第二步，注册监听器，消费对应的 Stream 主题\n        String consumerName = buildConsumerName();\n        listeners.parallelStream().forEach(listener -> {\n            log.info(\"[redisStreamMessageListenerContainer][开始注册 StreamKey({}) 对应的监听器({})]\",\n                    listener.getStreamKey(), listener.getClass().getName());\n            // 创建 listener 对应的消费者分组\n            try {\n                redisTemplate.opsForStream().createGroup(listener.getStreamKey(), listener.getGroup());\n            } catch (Exception ignore) {\n            }\n            // 设置 listener 对应的 redisTemplate\n            listener.setRedisMQTemplate(redisMQTemplate);\n            // 创建 Consumer 对象\n            Consumer consumer = Consumer.from(listener.getGroup(), consumerName);\n            // 设置 Consumer 消费进度，以最小消费进度为准\n            StreamOffset<String> streamOffset = StreamOffset.create(listener.getStreamKey(), ReadOffset.lastConsumed());\n            // 设置 Consumer 监听\n            StreamMessageListenerContainer.StreamReadRequestBuilder<String> builder = StreamMessageListenerContainer.StreamReadRequest\n                    .builder(streamOffset).consumer(consumer)\n                    .autoAcknowledge(false) // 不自动 ack\n                    .cancelOnError(throwable -> false); // 默认配置，发生异常就取消消费，显然不符合预期；因此，我们设置为 false\n            container.register(builder.build(), listener);\n            log.info(\"[redisStreamMessageListenerContainer][完成注册 StreamKey({}) 对应的监听器({})]\",\n                    listener.getStreamKey(), listener.getClass().getName());\n        });\n        return container;\n    }\n\n    /**\n     * 构建消费者名字，使用本地 IP + 进程编号的方式。\n     * 参考自 RocketMQ clientId 的实现\n     *\n     * @return 消费者名字\n     */\n    private static String buildConsumerName() {\n        return String.format(\"%s@%d\", SystemUtil.getHostInfo().getAddress(), SystemUtil.getCurrentPID());\n    }\n\n    /**\n     * 校验 Redis 版本号，是否满足最低的版本号要求！\n     */\n    private static void checkRedisVersion(RedisTemplate<String, ?> redisTemplate) {\n        // 获得 Redis 版本\n        Properties info = redisTemplate.execute((RedisCallback<Properties>) RedisServerCommands::info);\n        String version = MapUtil.getStr(info, \"redis_version\");\n        // 校验最低版本必须大于等于 5.0.0\n        int majorVersion = Integer.parseInt(StrUtil.subBefore(version, '.', false));\n        if (majorVersion < 5) {\n            throw new IllegalStateException(StrUtil.format(\"您当前的 Redis 版本为 {}，小于最低要求的 5.0.0 版本！\" +\n                    \"请参考 {} 文档进行安装。\", version, DocumentEnum.REDIS_INSTALL.getUrl()));\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-mq/src/main/java/co/yixiang/yshop/framework/mq/redis/config/YshopRedisMQProducerAutoConfiguration.java",
    "content": "package co.yixiang.yshop.framework.mq.redis.config;\n\nimport co.yixiang.yshop.framework.mq.redis.core.RedisMQTemplate;\nimport co.yixiang.yshop.framework.mq.redis.core.interceptor.RedisMessageInterceptor;\nimport co.yixiang.yshop.framework.redis.config.YshopRedisAutoConfiguration;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.boot.autoconfigure.AutoConfiguration;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.data.redis.core.StringRedisTemplate;\n\nimport java.util.List;\n\n/**\n * Redis 消息队列 Producer 配置类\n *\n * @author yshop\n */\n@Slf4j\n@AutoConfiguration(after = YshopRedisAutoConfiguration.class)\npublic class YshopRedisMQProducerAutoConfiguration {\n\n    @Bean\n    public RedisMQTemplate redisMQTemplate(StringRedisTemplate redisTemplate,\n                                           List<RedisMessageInterceptor> interceptors) {\n        RedisMQTemplate redisMQTemplate = new RedisMQTemplate(redisTemplate);\n        // 添加拦截器\n        interceptors.forEach(redisMQTemplate::addInterceptor);\n        return redisMQTemplate;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-mq/src/main/java/co/yixiang/yshop/framework/mq/redis/core/RedisMQTemplate.java",
    "content": "package co.yixiang.yshop.framework.mq.redis.core;\n\nimport co.yixiang.yshop.framework.common.util.json.JsonUtils;\nimport co.yixiang.yshop.framework.mq.redis.core.interceptor.RedisMessageInterceptor;\nimport co.yixiang.yshop.framework.mq.redis.core.message.AbstractRedisMessage;\nimport co.yixiang.yshop.framework.mq.redis.core.pubsub.AbstractRedisChannelMessage;\nimport co.yixiang.yshop.framework.mq.redis.core.stream.AbstractRedisStreamMessage;\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\nimport org.springframework.data.redis.connection.stream.RecordId;\nimport org.springframework.data.redis.connection.stream.StreamRecords;\nimport org.springframework.data.redis.core.RedisTemplate;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * Redis MQ 操作模板类\n *\n * @author yshop\n */\n@AllArgsConstructor\npublic class RedisMQTemplate {\n\n    @Getter\n    private final RedisTemplate<String, ?> redisTemplate;\n    /**\n     * 拦截器数组\n     */\n    @Getter\n    private final List<RedisMessageInterceptor> interceptors = new ArrayList<>();\n\n    /**\n     * 发送 Redis 消息，基于 Redis pub/sub 实现\n     *\n     * @param message 消息\n     */\n    public <T extends AbstractRedisChannelMessage> void send(T message) {\n        try {\n            sendMessageBefore(message);\n            // 发送消息\n            redisTemplate.convertAndSend(message.getChannel(), JsonUtils.toJsonString(message));\n        } finally {\n            sendMessageAfter(message);\n        }\n    }\n\n    /**\n     * 发送 Redis 消息，基于 Redis Stream 实现\n     *\n     * @param message 消息\n     * @return 消息记录的编号对象\n     */\n    public <T extends AbstractRedisStreamMessage> RecordId send(T message) {\n        try {\n            sendMessageBefore(message);\n            // 发送消息\n            return redisTemplate.opsForStream().add(StreamRecords.newRecord()\n                    .ofObject(JsonUtils.toJsonString(message)) // 设置内容\n                    .withStreamKey(message.getStreamKey())); // 设置 stream key\n        } finally {\n            sendMessageAfter(message);\n        }\n    }\n\n    /**\n     * 添加拦截器\n     *\n     * @param interceptor 拦截器\n     */\n    public void addInterceptor(RedisMessageInterceptor interceptor) {\n        interceptors.add(interceptor);\n    }\n\n    private void sendMessageBefore(AbstractRedisMessage message) {\n        // 正序\n        interceptors.forEach(interceptor -> interceptor.sendMessageBefore(message));\n    }\n\n    private void sendMessageAfter(AbstractRedisMessage message) {\n        // 倒序\n        for (int i = interceptors.size() - 1; i >= 0; i--) {\n            interceptors.get(i).sendMessageAfter(message);\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-mq/src/main/java/co/yixiang/yshop/framework/mq/redis/core/interceptor/RedisMessageInterceptor.java",
    "content": "package co.yixiang.yshop.framework.mq.redis.core.interceptor;\n\nimport co.yixiang.yshop.framework.mq.redis.core.message.AbstractRedisMessage;\n\n/**\n * {@link AbstractRedisMessage} 消息拦截器\n * 通过拦截器，作为插件机制，实现拓展。\n * 例如说，多租户场景下的 MQ 消息处理\n *\n * @author yshop\n */\npublic interface RedisMessageInterceptor {\n\n    default void sendMessageBefore(AbstractRedisMessage message) {\n    }\n\n    default void sendMessageAfter(AbstractRedisMessage message) {\n    }\n\n    default void consumeMessageBefore(AbstractRedisMessage message) {\n    }\n\n    default void consumeMessageAfter(AbstractRedisMessage message) {\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-mq/src/main/java/co/yixiang/yshop/framework/mq/redis/core/job/RedisPendingMessageResendJob.java",
    "content": "package co.yixiang.yshop.framework.mq.redis.core.job;\n\nimport cn.hutool.core.collection.CollUtil;\nimport co.yixiang.yshop.framework.mq.redis.core.RedisMQTemplate;\nimport co.yixiang.yshop.framework.mq.redis.core.stream.AbstractRedisStreamMessageListener;\nimport lombok.AllArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.redisson.api.RLock;\nimport org.redisson.api.RedissonClient;\nimport org.springframework.data.domain.Range;\nimport org.springframework.data.redis.connection.stream.*;\nimport org.springframework.data.redis.core.StreamOperations;\nimport org.springframework.scheduling.annotation.Scheduled;\n\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\n\n/**\n * 这个任务用于处理，crash 之后的消费者未消费完的消息\n */\n@Slf4j\n@AllArgsConstructor\npublic class RedisPendingMessageResendJob {\n\n    private static final String LOCK_KEY = \"redis:pending:msg:lock\";\n\n    /**\n     * 消息超时时间，默认 5 分钟\n     *\n     * 1. 超时的消息才会被重新投递\n     * 2. 由于定时任务 1 分钟一次，消息超时后不会被立即重投，极端情况下消息5分钟过期后，再等 1 分钟才会被扫瞄到\n     */\n    private static final int EXPIRE_TIME = 5 * 60;\n\n    private final List<AbstractRedisStreamMessageListener<?>> listeners;\n    private final RedisMQTemplate redisTemplate;\n    private final String groupName;\n    private final RedissonClient redissonClient;\n\n    /**\n     * 一分钟执行一次,这里选择每分钟的35秒执行，是为了避免整点任务过多的问题\n     */\n    @Scheduled(cron = \"35 * * * * ?\")\n    public void messageResend() {\n        RLock lock = redissonClient.getLock(LOCK_KEY);\n        // 尝试加锁\n        if (lock.tryLock()) {\n            try {\n                execute();\n            } catch (Exception ex) {\n                log.error(\"[messageResend][执行异常]\", ex);\n            } finally {\n                lock.unlock();\n            }\n        }\n    }\n\n    /**\n     * 执行清理逻辑\n     *\n     */\n    private void execute() {\n        StreamOperations<String, Object, Object> ops = redisTemplate.getRedisTemplate().opsForStream();\n        listeners.forEach(listener -> {\n            PendingMessagesSummary pendingMessagesSummary = Objects.requireNonNull(ops.pending(listener.getStreamKey(), groupName));\n            // 每个消费者的 pending 队列消息数量\n            Map<String, Long> pendingMessagesPerConsumer = pendingMessagesSummary.getPendingMessagesPerConsumer();\n            pendingMessagesPerConsumer.forEach((consumerName, pendingMessageCount) -> {\n                log.info(\"[processPendingMessage][消费者({}) 消息数量({})]\", consumerName, pendingMessageCount);\n                // 每个消费者的 pending消息的详情信息\n                PendingMessages pendingMessages = ops.pending(listener.getStreamKey(), Consumer.from(groupName, consumerName), Range.unbounded(), pendingMessageCount);\n                if (pendingMessages.isEmpty()) {\n                    return;\n                }\n                pendingMessages.forEach(pendingMessage -> {\n                    // 获取消息上一次传递到 consumer 的时间,\n                    long lastDelivery = pendingMessage.getElapsedTimeSinceLastDelivery().getSeconds();\n                    if (lastDelivery < EXPIRE_TIME){\n                        return;\n                    }\n                    // 获取指定 id 的消息体\n                    List<MapRecord<String, Object, Object>> records = ops.range(listener.getStreamKey(),\n                            Range.of(Range.Bound.inclusive(pendingMessage.getIdAsString()), Range.Bound.inclusive(pendingMessage.getIdAsString())));\n                    if (CollUtil.isEmpty(records)) {\n                        return;\n                    }\n                    // 重新投递消息\n                    redisTemplate.getRedisTemplate().opsForStream().add(StreamRecords.newRecord()\n                            .ofObject(records.get(0).getValue()) // 设置内容\n                            .withStreamKey(listener.getStreamKey()));\n                    // ack 消息消费完成\n                    redisTemplate.getRedisTemplate().opsForStream().acknowledge(groupName, records.get(0));\n                    log.info(\"[processPendingMessage][消息({})重新投递成功]\", records.get(0).getId());\n                });\n            });\n        });\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-mq/src/main/java/co/yixiang/yshop/framework/mq/redis/core/message/AbstractRedisMessage.java",
    "content": "package co.yixiang.yshop.framework.mq.redis.core.message;\n\nimport lombok.Data;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\n/**\n * Redis 消息抽象基类\n *\n * @author yshop\n */\n@Data\npublic abstract class AbstractRedisMessage {\n\n    /**\n     * 头\n     */\n    private Map<String, String> headers = new HashMap<>();\n\n    public String getHeader(String key) {\n        return headers.get(key);\n    }\n\n    public void addHeader(String key, String value) {\n        headers.put(key, value);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-mq/src/main/java/co/yixiang/yshop/framework/mq/redis/core/pubsub/AbstractRedisChannelMessage.java",
    "content": "package co.yixiang.yshop.framework.mq.redis.core.pubsub;\n\nimport co.yixiang.yshop.framework.mq.redis.core.message.AbstractRedisMessage;\nimport com.fasterxml.jackson.annotation.JsonIgnore;\n\n/**\n * Redis Channel Message 抽象类\n *\n * @author yshop\n */\npublic abstract class AbstractRedisChannelMessage extends AbstractRedisMessage {\n\n    /**\n     * 获得 Redis Channel，默认使用类名\n     *\n     * @return Channel\n     */\n    @JsonIgnore // 避免序列化。原因是，Redis 发布 Channel 消息的时候，已经会指定。\n    public String getChannel() {\n        return getClass().getSimpleName();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-mq/src/main/java/co/yixiang/yshop/framework/mq/redis/core/pubsub/AbstractRedisChannelMessageListener.java",
    "content": "package co.yixiang.yshop.framework.mq.redis.core.pubsub;\n\nimport cn.hutool.core.util.TypeUtil;\nimport co.yixiang.yshop.framework.common.util.json.JsonUtils;\nimport co.yixiang.yshop.framework.mq.redis.core.RedisMQTemplate;\nimport co.yixiang.yshop.framework.mq.redis.core.interceptor.RedisMessageInterceptor;\nimport co.yixiang.yshop.framework.mq.redis.core.message.AbstractRedisMessage;\nimport lombok.Setter;\nimport lombok.SneakyThrows;\nimport org.springframework.data.redis.connection.Message;\nimport org.springframework.data.redis.connection.MessageListener;\n\nimport java.lang.reflect.Type;\nimport java.util.List;\n\n/**\n * Redis Pub/Sub 监听器抽象类，用于实现广播消费\n *\n * @param <T> 消息类型。一定要填写噢，不然会报错\n *\n * @author yshop\n */\npublic abstract class AbstractRedisChannelMessageListener<T extends AbstractRedisChannelMessage> implements MessageListener {\n\n    /**\n     * 消息类型\n     */\n    private final Class<T> messageType;\n    /**\n     * Redis Channel\n     */\n    private final String channel;\n    /**\n     * RedisMQTemplate\n     */\n    @Setter\n    private RedisMQTemplate redisMQTemplate;\n\n    @SneakyThrows\n    protected AbstractRedisChannelMessageListener() {\n        this.messageType = getMessageClass();\n        this.channel = messageType.getDeclaredConstructor().newInstance().getChannel();\n    }\n\n    /**\n     * 获得 Sub 订阅的 Redis Channel 通道\n     *\n     * @return channel\n     */\n    public final String getChannel() {\n        return channel;\n    }\n\n    @Override\n    public final void onMessage(Message message, byte[] bytes) {\n        T messageObj = JsonUtils.parseObject(message.getBody(), messageType);\n        try {\n            consumeMessageBefore(messageObj);\n            // 消费消息\n            this.onMessage(messageObj);\n        } finally {\n            consumeMessageAfter(messageObj);\n        }\n    }\n\n    /**\n     * 处理消息\n     *\n     * @param message 消息\n     */\n    public abstract void onMessage(T message);\n\n    /**\n     * 通过解析类上的泛型，获得消息类型\n     *\n     * @return 消息类型\n     */\n    @SuppressWarnings(\"unchecked\")\n    private Class<T> getMessageClass() {\n        Type type = TypeUtil.getTypeArgument(getClass(), 0);\n        if (type == null) {\n            throw new IllegalStateException(String.format(\"类型(%s) 需要设置消息类型\", getClass().getName()));\n        }\n        return (Class<T>) type;\n    }\n\n    private void consumeMessageBefore(AbstractRedisMessage message) {\n        assert redisMQTemplate != null;\n        List<RedisMessageInterceptor> interceptors = redisMQTemplate.getInterceptors();\n        // 正序\n        interceptors.forEach(interceptor -> interceptor.consumeMessageBefore(message));\n    }\n\n    private void consumeMessageAfter(AbstractRedisMessage message) {\n        assert redisMQTemplate != null;\n        List<RedisMessageInterceptor> interceptors = redisMQTemplate.getInterceptors();\n        // 倒序\n        for (int i = interceptors.size() - 1; i >= 0; i--) {\n            interceptors.get(i).consumeMessageAfter(message);\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-mq/src/main/java/co/yixiang/yshop/framework/mq/redis/core/stream/AbstractRedisStreamMessage.java",
    "content": "package co.yixiang.yshop.framework.mq.redis.core.stream;\n\nimport co.yixiang.yshop.framework.mq.redis.core.message.AbstractRedisMessage;\nimport com.fasterxml.jackson.annotation.JsonIgnore;\n\n/**\n * Redis Stream Message 抽象类\n *\n * @author yshop\n */\npublic abstract class AbstractRedisStreamMessage extends AbstractRedisMessage {\n\n    /**\n     * 获得 Redis Stream Key，默认使用类名\n     *\n     * @return Channel\n     */\n    @JsonIgnore // 避免序列化\n    public String getStreamKey() {\n        return getClass().getSimpleName();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-mq/src/main/java/co/yixiang/yshop/framework/mq/redis/core/stream/AbstractRedisStreamMessageListener.java",
    "content": "package co.yixiang.yshop.framework.mq.redis.core.stream;\n\nimport cn.hutool.core.util.TypeUtil;\nimport co.yixiang.yshop.framework.common.util.json.JsonUtils;\nimport co.yixiang.yshop.framework.mq.redis.core.RedisMQTemplate;\nimport co.yixiang.yshop.framework.mq.redis.core.interceptor.RedisMessageInterceptor;\nimport co.yixiang.yshop.framework.mq.redis.core.message.AbstractRedisMessage;\nimport lombok.Getter;\nimport lombok.Setter;\nimport lombok.SneakyThrows;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.data.redis.connection.stream.ObjectRecord;\nimport org.springframework.data.redis.stream.StreamListener;\n\nimport java.lang.reflect.Type;\nimport java.util.List;\n\n/**\n * Redis Stream 监听器抽象类，用于实现集群消费\n *\n * @param <T> 消息类型。一定要填写噢，不然会报错\n *\n * @author yshop\n */\npublic abstract class AbstractRedisStreamMessageListener<T extends AbstractRedisStreamMessage>\n        implements StreamListener<String, ObjectRecord<String, String>> {\n\n    /**\n     * 消息类型\n     */\n    private final Class<T> messageType;\n    /**\n     * Redis Channel\n     */\n    @Getter\n    private final String streamKey;\n\n    /**\n     * Redis 消费者分组，默认使用 spring.application.name 名字\n     */\n    @Value(\"${spring.application.name}\")\n    @Getter\n    private String group;\n    /**\n     * RedisMQTemplate\n     */\n    @Setter\n    private RedisMQTemplate redisMQTemplate;\n\n    @SneakyThrows\n    protected AbstractRedisStreamMessageListener() {\n        this.messageType = getMessageClass();\n        this.streamKey = messageType.getDeclaredConstructor().newInstance().getStreamKey();\n    }\n\n    @Override\n    public void onMessage(ObjectRecord<String, String> message) {\n        // 消费消息\n        T messageObj = JsonUtils.parseObject(message.getValue(), messageType);\n        try {\n            consumeMessageBefore(messageObj);\n            // 消费消息\n            this.onMessage(messageObj);\n            // ack 消息消费完成\n            redisMQTemplate.getRedisTemplate().opsForStream().acknowledge(group, message);\n            // TODO yshop：需要额外考虑以下几个点：\n            // 1. 处理异常的情况\n            // 2. 发送日志；以及事务的结合\n            // 3. 消费日志；以及通用的幂等性\n            // 4. 消费失败的重试，https://zhuanlan.zhihu.com/p/60501638\n        } finally {\n            consumeMessageAfter(messageObj);\n        }\n    }\n\n    /**\n     * 处理消息\n     *\n     * @param message 消息\n     */\n    public abstract void onMessage(T message);\n\n    /**\n     * 通过解析类上的泛型，获得消息类型\n     *\n     * @return 消息类型\n     */\n    @SuppressWarnings(\"unchecked\")\n    private Class<T> getMessageClass() {\n        Type type = TypeUtil.getTypeArgument(getClass(), 0);\n        if (type == null) {\n            throw new IllegalStateException(String.format(\"类型(%s) 需要设置消息类型\", getClass().getName()));\n        }\n        return (Class<T>) type;\n    }\n\n    private void consumeMessageBefore(AbstractRedisMessage message) {\n        assert redisMQTemplate != null;\n        List<RedisMessageInterceptor> interceptors = redisMQTemplate.getInterceptors();\n        // 正序\n        interceptors.forEach(interceptor -> interceptor.consumeMessageBefore(message));\n    }\n\n    private void consumeMessageAfter(AbstractRedisMessage message) {\n        assert redisMQTemplate != null;\n        List<RedisMessageInterceptor> interceptors = redisMQTemplate.getInterceptors();\n        // 倒序\n        for (int i = interceptors.size() - 1; i >= 0; i--) {\n            interceptors.get(i).consumeMessageAfter(message);\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-mq/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports",
    "content": "co.yixiang.yshop.framework.mq.redis.config.YshopRedisMQProducerAutoConfiguration\nco.yixiang.yshop.framework.mq.redis.config.YshopRedisMQConsumerAutoConfiguration\nco.yixiang.yshop.framework.mq.rabbitmq.config.YshopRabbitMQAutoConfiguration"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-mybatis/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>co.yixiang.boot</groupId>\n        <artifactId>yshop-framework</artifactId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>yshop-spring-boot-starter-mybatis</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>数据库连接池、多数据源、事务、MyBatis 拓展</description>\n    <url>https://gitee.com/guchengwuyue/yshop-drink</url>\n\n    <dependencies>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-common</artifactId>\n        </dependency>\n\n        <!-- Web 相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-web</artifactId>\n            <scope>provided</scope> <!-- 设置为 provided，只有 OncePerRequestFilter 使用到 -->\n        </dependency>\n\n        <!-- DB 相关 -->\n        <dependency>\n            <groupId>com.mysql</groupId>\n            <artifactId>mysql-connector-j</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.oracle.database.jdbc</groupId>\n            <artifactId>ojdbc8</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.postgresql</groupId>\n            <artifactId>postgresql</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>com.microsoft.sqlserver</groupId>\n            <artifactId>mssql-jdbc</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>com.dameng</groupId>\n            <artifactId>DmJdbcDriver18</artifactId>\n            <optional>true</optional>\n        </dependency>\n\n        <dependency>\n            <groupId>com.alibaba</groupId>\n            <artifactId>druid-spring-boot-3-starter</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.baomidou</groupId>\n            <artifactId>mybatis-plus-spring-boot3-starter</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.baomidou</groupId>\n            <artifactId>dynamic-datasource-spring-boot3-starter</artifactId> <!-- 多数据源 -->\n            <exclusions>\n                <exclusion>\n                    <groupId>org.springframework.boot</groupId>\n                    <artifactId>spring-boot-starter-undertow</artifactId>\n                </exclusion>\n            </exclusions>\n        </dependency>\n\n        <dependency>\n            <groupId>com.github.yulichang</groupId>\n            <artifactId>mybatis-plus-join-boot-starter</artifactId> <!-- MyBatis 联表查询 -->\n        </dependency>\n\n        <dependency>\n            <groupId>com.fhs-opensource</groupId> <!-- VO 数据翻译 -->\n            <artifactId>easy-trans-spring-boot-starter</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.fhs-opensource</groupId>\n            <artifactId>easy-trans-mybatis-plus-extend</artifactId>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-mybatis/src/main/java/co/yixiang/yshop/framework/datasource/config/YshopDataSourceAutoConfiguration.java",
    "content": "package co.yixiang.yshop.framework.datasource.config;\n\nimport co.yixiang.yshop.framework.datasource.core.filter.DruidAdRemoveFilter;\nimport com.alibaba.druid.spring.boot3.autoconfigure.properties.DruidStatProperties;\nimport org.springframework.boot.autoconfigure.AutoConfiguration;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.boot.web.servlet.FilterRegistrationBean;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.transaction.annotation.EnableTransactionManagement;\n\n/**\n * 数据库配置类\n *\n * @author yshop\n */\n@AutoConfiguration\n@EnableTransactionManagement(proxyTargetClass = true) // 启动事务管理\n@EnableConfigurationProperties(DruidStatProperties.class)\npublic class YshopDataSourceAutoConfiguration {\n\n    /**\n     * 创建 DruidAdRemoveFilter 过滤器，过滤 common.js 的广告\n     */\n    @Bean\n    @ConditionalOnProperty(name = \"spring.datasource.druid.stat-view-servlet.enabled\", havingValue = \"true\")\n    public FilterRegistrationBean<DruidAdRemoveFilter> druidAdRemoveFilterFilter(DruidStatProperties properties) {\n        // 获取 druid web 监控页面的参数\n        DruidStatProperties.StatViewServlet config = properties.getStatViewServlet();\n        // 提取 common.js 的配置路径\n        String pattern = config.getUrlPattern() != null ? config.getUrlPattern() : \"/druid/*\";\n        String commonJsPattern = pattern.replaceAll(\"\\\\*\", \"js/common.js\");\n        // 创建 DruidAdRemoveFilter Bean\n        FilterRegistrationBean<DruidAdRemoveFilter> registrationBean = new FilterRegistrationBean<>();\n        registrationBean.setFilter(new DruidAdRemoveFilter());\n        registrationBean.addUrlPatterns(commonJsPattern);\n        return registrationBean;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-mybatis/src/main/java/co/yixiang/yshop/framework/datasource/core/enums/DataSourceEnum.java",
    "content": "package co.yixiang.yshop.framework.datasource.core.enums;\n\n/**\n * 对应于多数据源中不同数据源配置\n *\n * 通过在方法上，使用 {@link com.baomidou.dynamic.datasource.annotation.DS} 注解，设置使用的数据源。\n * 注意，默认是 {@link #MASTER} 数据源\n *\n * 对应官方文档为 http://dynamic-datasource.com/guide/customize/Annotation.html\n */\npublic interface DataSourceEnum {\n\n    /**\n     * 主库，推荐使用 {@link com.baomidou.dynamic.datasource.annotation.Master} 注解\n     */\n    String MASTER = \"master\";\n    /**\n     * 从库，推荐使用 {@link com.baomidou.dynamic.datasource.annotation.Slave} 注解\n     */\n    String SLAVE = \"slave\";\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-mybatis/src/main/java/co/yixiang/yshop/framework/datasource/core/filter/DruidAdRemoveFilter.java",
    "content": "package co.yixiang.yshop.framework.datasource.core.filter;\n\nimport com.alibaba.druid.util.Utils;\nimport org.springframework.web.filter.OncePerRequestFilter;\n\nimport jakarta.servlet.FilterChain;\nimport jakarta.servlet.ServletException;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport java.io.IOException;\n\n/**\n * Druid 底部广告过滤器\n *\n * @author yshop\n */\npublic class DruidAdRemoveFilter extends OncePerRequestFilter {\n\n    /**\n     * common.js 的路径\n     */\n    private static final String COMMON_JS_ILE_PATH = \"support/http/resources/js/common.js\";\n\n    @Override\n    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)\n            throws ServletException, IOException {\n        chain.doFilter(request, response);\n        // 重置缓冲区，响应头不会被重置\n        response.resetBuffer();\n        // 获取 common.js\n        String text = Utils.readFromResource(COMMON_JS_ILE_PATH);\n        // 正则替换 banner, 除去底部的广告信息\n        text = text.replaceAll(\"<a.*?banner\\\"></a><br/>\", \"\");\n        text = text.replaceAll(\"powered.*?shrek.wang</a>\", \"\");\n        response.getWriter().write(text);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-mybatis/src/main/java/co/yixiang/yshop/framework/mybatis/config/IdTypeEnvironmentPostProcessor.java",
    "content": "package co.yixiang.yshop.framework.mybatis.config;\n\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.util.collection.SetUtils;\nimport co.yixiang.yshop.framework.mybatis.core.enums.SqlConstants;\nimport co.yixiang.yshop.framework.mybatis.core.util.JdbcUtils;\nimport com.baomidou.mybatisplus.annotation.DbType;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.env.EnvironmentPostProcessor;\nimport org.springframework.core.env.ConfigurableEnvironment;\n\nimport java.util.Set;\n\n/**\n * 当 IdType 为 {@link IdType#NONE} 时，根据 PRIMARY 数据源所使用的数据库，自动设置\n *\n * @author yshop\n */\n@Slf4j\npublic class IdTypeEnvironmentPostProcessor implements EnvironmentPostProcessor {\n\n    private static final String ID_TYPE_KEY = \"mybatis-plus.global-config.db-config.id-type\";\n\n    private static final String DATASOURCE_DYNAMIC_KEY = \"spring.datasource.dynamic\";\n\n    private static final String QUARTZ_JOB_STORE_DRIVER_KEY = \"spring.quartz.properties.org.quartz.jobStore.driverDelegateClass\";\n\n    private static final Set<DbType> INPUT_ID_TYPES = SetUtils.asSet(DbType.ORACLE, DbType.ORACLE_12C,\n            DbType.POSTGRE_SQL, DbType.KINGBASE_ES, DbType.DB2, DbType.H2);\n\n    @Override\n    public void postProcessEnvironment(ConfigurableEnvironment environment, SpringApplication application) {\n        // 如果获取不到 DbType，则不进行处理\n        DbType dbType = getDbType(environment);\n        if (dbType == null) {\n            return;\n        }\n\n        // 设置 Quartz JobStore 对应的 Driver\n        // TODO yshop：暂时没有找到特别合适的地方，先放在这里\n        setJobStoreDriverIfPresent(environment, dbType);\n\n        // 初始化 SQL 静态变量\n        SqlConstants.init(dbType);\n\n        // 如果非 NONE，则不进行处理\n        IdType idType = getIdType(environment);\n        if (idType != IdType.NONE) {\n            return;\n        }\n        // 情况一，用户输入 ID，适合 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库\n        if (INPUT_ID_TYPES.contains(dbType)) {\n            setIdType(environment, IdType.INPUT);\n            return;\n        }\n        // 情况二，自增 ID，适合 MySQL 等直接自增的数据库\n        setIdType(environment, IdType.AUTO);\n    }\n\n    public IdType getIdType(ConfigurableEnvironment environment) {\n        return environment.getProperty(ID_TYPE_KEY, IdType.class);\n    }\n\n    public void setIdType(ConfigurableEnvironment environment, IdType idType) {\n        environment.getSystemProperties().put(ID_TYPE_KEY, idType);\n        log.info(\"[setIdType][修改 MyBatis Plus 的 idType 为({})]\", idType);\n    }\n\n    public void setJobStoreDriverIfPresent(ConfigurableEnvironment environment, DbType dbType) {\n        String driverClass = environment.getProperty(QUARTZ_JOB_STORE_DRIVER_KEY);\n        if (StrUtil.isNotEmpty(driverClass)) {\n            return;\n        }\n        // 根据 dbType 类型，获取对应的 driverClass\n        switch (dbType) {\n            case POSTGRE_SQL:\n                driverClass = \"org.quartz.impl.jdbcjobstore.PostgreSQLDelegate\";\n                break;\n            case ORACLE:\n            case ORACLE_12C:\n                driverClass = \"org.quartz.impl.jdbcjobstore.oracle.OracleDelegate\";\n                break;\n            case SQL_SERVER:\n            case SQL_SERVER2005:\n                driverClass = \"org.quartz.impl.jdbcjobstore.MSSQLDelegate\";\n                break;\n        }\n        // 设置 driverClass 变量\n        if (StrUtil.isNotEmpty(driverClass)) {\n            environment.getSystemProperties().put(QUARTZ_JOB_STORE_DRIVER_KEY, driverClass);\n        }\n    }\n\n    public static DbType getDbType(ConfigurableEnvironment environment) {\n        String primary = environment.getProperty(DATASOURCE_DYNAMIC_KEY + \".\" + \"primary\");\n        if (StrUtil.isEmpty(primary)) {\n            return null;\n        }\n        String url = environment.getProperty(DATASOURCE_DYNAMIC_KEY + \".datasource.\" + primary + \".url\");\n        if (StrUtil.isEmpty(url)) {\n            return null;\n        }\n        return JdbcUtils.getDbType(url);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-mybatis/src/main/java/co/yixiang/yshop/framework/mybatis/config/YshopMybatisAutoConfiguration.java",
    "content": "package co.yixiang.yshop.framework.mybatis.config;\n\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.mybatis.core.handler.DefaultDBFieldHandler;\nimport com.baomidou.mybatisplus.annotation.DbType;\nimport com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;\nimport com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;\nimport com.baomidou.mybatisplus.core.incrementer.IKeyGenerator;\nimport com.baomidou.mybatisplus.extension.incrementer.*;\nimport com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;\nimport com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.mybatis.spring.annotation.MapperScan;\nimport org.springframework.boot.autoconfigure.AutoConfiguration;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.core.env.ConfigurableEnvironment;\n\n/**\n * MyBaits 配置类\n *\n * @author yshop\n */\n@AutoConfiguration(before = MybatisPlusAutoConfiguration.class) // 目的：先于 MyBatis Plus 自动配置，避免 @MapperScan 可能扫描不到 Mapper 打印 warn 日志\n@MapperScan(value = \"${yshop.info.base-package}\", annotationClass = Mapper.class,\n        lazyInitialization = \"${mybatis.lazy-initialization:false}\") // Mapper 懒加载，目前仅用于单元测试\npublic class YshopMybatisAutoConfiguration {\n\n    @Bean\n    public MybatisPlusInterceptor mybatisPlusInterceptor() {\n        MybatisPlusInterceptor mybatisPlusInterceptor = new MybatisPlusInterceptor();\n        mybatisPlusInterceptor.addInnerInterceptor(new PaginationInnerInterceptor()); // 分页插件\n        return mybatisPlusInterceptor;\n    }\n\n    @Bean\n    public MetaObjectHandler defaultMetaObjectHandler(){\n        return new DefaultDBFieldHandler(); // 自动填充参数类\n    }\n\n    @Bean\n    @ConditionalOnProperty(prefix = \"mybatis-plus.global-config.db-config\", name = \"id-type\", havingValue = \"INPUT\")\n    public IKeyGenerator keyGenerator(ConfigurableEnvironment environment) {\n        DbType dbType = IdTypeEnvironmentPostProcessor.getDbType(environment);\n        if (dbType != null) {\n            switch (dbType) {\n                case POSTGRE_SQL:\n                    return new PostgreKeyGenerator();\n                case ORACLE:\n                case ORACLE_12C:\n                    return new OracleKeyGenerator();\n                case H2:\n                    return new H2KeyGenerator();\n                case KINGBASE_ES:\n                    return new KingbaseKeyGenerator();\n                case DM:\n                    return new DmKeyGenerator();\n            }\n        }\n        // 找不到合适的 IKeyGenerator 实现类\n        throw new IllegalArgumentException(StrUtil.format(\"DbType{} 找不到合适的 IKeyGenerator 实现类\", dbType));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-mybatis/src/main/java/co/yixiang/yshop/framework/mybatis/core/dataobject/BaseDO.java",
    "content": "package co.yixiang.yshop.framework.mybatis.core.dataobject;\n\nimport com.baomidou.mybatisplus.annotation.FieldFill;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableLogic;\nimport com.fasterxml.jackson.annotation.JsonIgnoreProperties;\nimport com.fhs.core.trans.vo.TransPojo;\nimport lombok.Data;\nimport org.apache.ibatis.type.JdbcType;\n\nimport java.io.Serializable;\nimport java.time.LocalDateTime;\n\n/**\n * 基础实体对象\n *\n * 为什么实现 {@link TransPojo} 接口？\n * 因为使用 Easy-Trans TransType.SIMPLE 模式，集成 MyBatis Plus 查询\n *\n * @author yshop\n */\n@Data\n@JsonIgnoreProperties(value = \"transMap\") // 由于 Easy-Trans 会添加 transMap 属性，避免 Jackson 在 Spring Cache 反序列化报错\npublic abstract class BaseDO implements Serializable, TransPojo {\n\n    /**\n     * 创建时间\n     */\n    @TableField(fill = FieldFill.INSERT)\n    private LocalDateTime createTime;\n    /**\n     * 最后更新时间\n     */\n    @TableField(fill = FieldFill.INSERT_UPDATE)\n    private LocalDateTime updateTime;\n    /**\n     * 创建者，目前使用 SysUser 的 id 编号\n     *\n     * 使用 String 类型的原因是，未来可能会存在非数值的情况，留好拓展性。\n     */\n    @TableField(fill = FieldFill.INSERT, jdbcType = JdbcType.VARCHAR)\n    private String creator;\n    /**\n     * 更新者，目前使用 SysUser 的 id 编号\n     *\n     * 使用 String 类型的原因是，未来可能会存在非数值的情况，留好拓展性。\n     */\n    @TableField(fill = FieldFill.INSERT_UPDATE, jdbcType = JdbcType.VARCHAR)\n    private String updater;\n    /**\n     * 是否删除\n     */\n    @TableLogic\n    private Boolean deleted;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-mybatis/src/main/java/co/yixiang/yshop/framework/mybatis/core/enums/SqlConstants.java",
    "content": "package co.yixiang.yshop.framework.mybatis.core.enums;\n\nimport com.baomidou.mybatisplus.annotation.DbType;\n\n/**\n * SQL相关常量类\n *\n * @author yshop\n */\npublic class SqlConstants {\n\n    /**\n     * 数据库的类型\n     */\n    public static DbType DB_TYPE;\n\n    public static void init(DbType dbType) {\n        DB_TYPE = dbType;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-mybatis/src/main/java/co/yixiang/yshop/framework/mybatis/core/handler/DefaultDBFieldHandler.java",
    "content": "package co.yixiang.yshop.framework.mybatis.core.handler;\n\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport co.yixiang.yshop.framework.web.core.util.WebFrameworkUtils;\nimport com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;\nimport org.apache.ibatis.reflection.MetaObject;\n\nimport java.time.LocalDateTime;\nimport java.util.Objects;\n\n/**\n * 通用参数填充实现类\n *\n * 如果没有显式的对通用参数进行赋值，这里会对通用参数进行填充、赋值\n *\n * @author hexiaowu\n */\npublic class DefaultDBFieldHandler implements MetaObjectHandler {\n\n    @Override\n    public void insertFill(MetaObject metaObject) {\n        if (Objects.nonNull(metaObject) && metaObject.getOriginalObject() instanceof BaseDO) {\n            BaseDO baseDO = (BaseDO) metaObject.getOriginalObject();\n\n            LocalDateTime current = LocalDateTime.now();\n            // 创建时间为空，则以当前时间为插入时间\n            if (Objects.isNull(baseDO.getCreateTime())) {\n                baseDO.setCreateTime(current);\n            }\n            // 更新时间为空，则以当前时间为更新时间\n            if (Objects.isNull(baseDO.getUpdateTime())) {\n                baseDO.setUpdateTime(current);\n            }\n\n            Long userId = WebFrameworkUtils.getLoginUserId();\n            // 当前登录用户不为空，创建人为空，则当前登录用户为创建人\n            if (Objects.nonNull(userId) && Objects.isNull(baseDO.getCreator())) {\n                baseDO.setCreator(userId.toString());\n            }\n            // 当前登录用户不为空，更新人为空，则当前登录用户为更新人\n            if (Objects.nonNull(userId) && Objects.isNull(baseDO.getUpdater())) {\n                baseDO.setUpdater(userId.toString());\n            }\n        }\n    }\n\n    @Override\n    public void updateFill(MetaObject metaObject) {\n        // 更新时间为空，则以当前时间为更新时间\n        Object modifyTime = getFieldValByName(\"updateTime\", metaObject);\n        if (Objects.isNull(modifyTime)) {\n            setFieldValByName(\"updateTime\", LocalDateTime.now(), metaObject);\n        }\n\n        // 当前登录用户不为空，更新人为空，则当前登录用户为更新人\n        Object modifier = getFieldValByName(\"updater\", metaObject);\n        Long userId = WebFrameworkUtils.getLoginUserId();\n        if (Objects.nonNull(userId) && Objects.isNull(modifier)) {\n            setFieldValByName(\"updater\", userId.toString(), metaObject);\n        }\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-mybatis/src/main/java/co/yixiang/yshop/framework/mybatis/core/mapper/BaseMapperX.java",
    "content": "package co.yixiang.yshop.framework.mybatis.core.mapper;\n\nimport cn.hutool.core.collection.CollUtil;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.pojo.SortablePageParam;\nimport co.yixiang.yshop.framework.common.pojo.SortingField;\nimport co.yixiang.yshop.framework.mybatis.core.enums.SqlConstants;\nimport co.yixiang.yshop.framework.mybatis.core.util.MyBatisUtils;\nimport com.baomidou.mybatisplus.annotation.DbType;\nimport com.baomidou.mybatisplus.core.conditions.Wrapper;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.core.toolkit.support.SFunction;\nimport com.baomidou.mybatisplus.extension.toolkit.Db;\nimport com.github.yulichang.base.MPJBaseMapper;\nimport com.github.yulichang.interfaces.MPJBaseJoin;\nimport com.github.yulichang.wrapper.MPJLambdaWrapper;\nimport org.apache.ibatis.annotations.Param;\n\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Objects;\n\n/**\n * 在 MyBatis Plus 的 BaseMapper 的基础上拓展，提供更多的能力\n *\n * 1. {@link BaseMapper} 为 MyBatis Plus 的基础接口，提供基础的 CRUD 能力\n * 2. {@link MPJBaseMapper} 为 MyBatis Plus Join 的基础接口，提供连表 Join 能力\n */\npublic interface BaseMapperX<T> extends MPJBaseMapper<T> {\n\n    default PageResult<T> selectPage(SortablePageParam pageParam, @Param(\"ew\") Wrapper<T> queryWrapper) {\n        return selectPage(pageParam, pageParam.getSortingFields(), queryWrapper);\n    }\n\n    default PageResult<T> selectPage(PageParam pageParam, @Param(\"ew\") Wrapper<T> queryWrapper) {\n        return selectPage(pageParam, null, queryWrapper);\n    }\n\n    default PageResult<T> selectPage(PageParam pageParam, Collection<SortingField> sortingFields, @Param(\"ew\") Wrapper<T> queryWrapper) {\n        // 特殊：不分页，直接查询全部\n        if (PageParam.PAGE_SIZE_NONE.equals(pageParam.getPageSize())) {\n            List<T> list = selectList(queryWrapper);\n            return new PageResult<>(list, (long) list.size());\n        }\n\n        // MyBatis Plus 查询\n        IPage<T> mpPage = MyBatisUtils.buildPage(pageParam, sortingFields);\n        selectPage(mpPage, queryWrapper);\n        // 转换返回\n        return new PageResult<>(mpPage.getRecords(), mpPage.getTotal());\n    }\n\n    default <D> PageResult<D> selectJoinPage(PageParam pageParam, Class<D> clazz, MPJLambdaWrapper<T> lambdaWrapper) {\n        // 特殊：不分页，直接查询全部\n        if (PageParam.PAGE_SIZE_NONE.equals(pageParam.getPageNo())) {\n            List<D> list = selectJoinList(clazz, lambdaWrapper);\n            return new PageResult<>(list, (long) list.size());\n        }\n\n        // MyBatis Plus Join 查询\n        IPage<D> mpPage = MyBatisUtils.buildPage(pageParam);\n        mpPage = selectJoinPage(mpPage, clazz, lambdaWrapper);\n        // 转换返回\n        return new PageResult<>(mpPage.getRecords(), mpPage.getTotal());\n    }\n\n    default <DTO> PageResult<DTO> selectJoinPage(PageParam pageParam, Class<DTO> resultTypeClass, MPJBaseJoin<T> joinQueryWrapper) {\n        IPage<DTO> mpPage = MyBatisUtils.buildPage(pageParam);\n        selectJoinPage(mpPage, resultTypeClass, joinQueryWrapper);\n        // 转换返回\n        return new PageResult<>(mpPage.getRecords(), mpPage.getTotal());\n    }\n\n    default T selectOne(String field, Object value) {\n        return selectOne(new QueryWrapper<T>().eq(field, value));\n    }\n\n    default T selectOne(SFunction<T, ?> field, Object value) {\n        return selectOne(new LambdaQueryWrapper<T>().eq(field, value));\n    }\n\n    default T selectOne(String field1, Object value1, String field2, Object value2) {\n        return selectOne(new QueryWrapper<T>().eq(field1, value1).eq(field2, value2));\n    }\n\n    default T selectOne(SFunction<T, ?> field1, Object value1, SFunction<T, ?> field2, Object value2) {\n        return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2));\n    }\n\n    default T selectOne(SFunction<T, ?> field1, Object value1, SFunction<T, ?> field2, Object value2,\n                        SFunction<T, ?> field3, Object value3) {\n        return selectOne(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2)\n                .eq(field3, value3));\n    }\n\n    default Long selectCount() {\n        return selectCount(new QueryWrapper<>());\n    }\n\n    default Long selectCount(String field, Object value) {\n        return selectCount(new QueryWrapper<T>().eq(field, value));\n    }\n\n    default Long selectCount(SFunction<T, ?> field, Object value) {\n        return selectCount(new LambdaQueryWrapper<T>().eq(field, value));\n    }\n\n    default List<T> selectList() {\n        return selectList(new QueryWrapper<>());\n    }\n\n    default List<T> selectList(String field, Object value) {\n        return selectList(new QueryWrapper<T>().eq(field, value));\n    }\n\n    default List<T> selectList(SFunction<T, ?> field, Object value) {\n        return selectList(new LambdaQueryWrapper<T>().eq(field, value));\n    }\n\n    default List<T> selectList(String field, Collection<?> values) {\n        if (CollUtil.isEmpty(values)) {\n            return CollUtil.newArrayList();\n        }\n        return selectList(new QueryWrapper<T>().in(field, values));\n    }\n\n    default List<T> selectList(SFunction<T, ?> field, Collection<?> values) {\n        if (CollUtil.isEmpty(values)) {\n            return CollUtil.newArrayList();\n        }\n        return selectList(new LambdaQueryWrapper<T>().in(field, values));\n    }\n\n    @Deprecated\n    default List<T> selectList(SFunction<T, ?> leField, SFunction<T, ?> geField, Object value) {\n        return selectList(new LambdaQueryWrapper<T>().le(leField, value).ge(geField, value));\n    }\n\n    default List<T> selectList(SFunction<T, ?> field1, Object value1, SFunction<T, ?> field2, Object value2) {\n        return selectList(new LambdaQueryWrapper<T>().eq(field1, value1).eq(field2, value2));\n    }\n\n    /**\n     * 批量插入，适合大量数据插入\n     *\n     * @param entities 实体们\n     */\n    default Boolean insertBatch(Collection<T> entities) {\n        // 特殊：SQL Server 批量插入后，获取 id 会报错，因此通过循环处理\n        if (Objects.equals(SqlConstants.DB_TYPE, DbType.SQL_SERVER)) {\n            entities.forEach(this::insert);\n            return CollUtil.isNotEmpty(entities);\n        }\n        return Db.saveBatch(entities);\n    }\n\n    /**\n     * 批量插入，适合大量数据插入\n     *\n     * @param entities 实体们\n     * @param size     插入数量 Db.saveBatch 默认为 1000\n     */\n    default Boolean insertBatch(Collection<T> entities, int size) {\n        // 特殊：SQL Server 批量插入后，获取 id 会报错，因此通过循环处理\n        if (Objects.equals(SqlConstants.DB_TYPE, DbType.SQL_SERVER)) {\n            entities.forEach(this::insert);\n            return CollUtil.isNotEmpty(entities);\n        }\n        return Db.saveBatch(entities, size);\n    }\n\n    default int updateBatch(T update) {\n        return update(update, new QueryWrapper<>());\n    }\n\n    default Boolean updateBatch(Collection<T> entities) {\n        return Db.updateBatchById(entities);\n    }\n\n    default Boolean updateBatch(Collection<T> entities, int size) {\n        return Db.updateBatchById(entities, size);\n    }\n\n    default Boolean insertOrUpdate(T entity) {\n        return  Db.saveOrUpdate(entity);\n    }\n\n    default Boolean insertOrUpdateBatch(Collection<T> collection) {\n        return Db.saveOrUpdateBatch(collection);\n    }\n\n    default int delete(String field, String value) {\n        return delete(new QueryWrapper<T>().eq(field, value));\n    }\n\n    default int delete(SFunction<T, ?> field, Object value) {\n        return delete(new LambdaQueryWrapper<T>().eq(field, value));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-mybatis/src/main/java/co/yixiang/yshop/framework/mybatis/core/query/LambdaQueryWrapperX.java",
    "content": "package co.yixiang.yshop.framework.mybatis.core.query;\n\nimport cn.hutool.core.util.ArrayUtil;\nimport cn.hutool.core.util.ObjectUtil;\nimport co.yixiang.yshop.framework.common.util.collection.ArrayUtils;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport com.baomidou.mybatisplus.core.toolkit.support.SFunction;\nimport org.springframework.util.StringUtils;\n\nimport java.util.Collection;\n\n/**\n * 拓展 MyBatis Plus QueryWrapper 类，主要增加如下功能：\n * <p>\n * 1. 拼接条件的方法，增加 xxxIfPresent 方法，用于判断值不存在的时候，不要拼接到条件中。\n *\n * @param <T> 数据类型\n */\npublic class LambdaQueryWrapperX<T> extends LambdaQueryWrapper<T> {\n\n    public LambdaQueryWrapperX<T> likeIfPresent(SFunction<T, ?> column, String val) {\n        if (StringUtils.hasText(val)) {\n            return (LambdaQueryWrapperX<T>) super.like(column, val);\n        }\n        return this;\n    }\n\n    public LambdaQueryWrapperX<T> inIfPresent(SFunction<T, ?> column, Collection<?> values) {\n        if (ObjectUtil.isAllNotEmpty(values) && !ArrayUtil.isEmpty(values)) {\n            return (LambdaQueryWrapperX<T>) super.in(column, values);\n        }\n        return this;\n    }\n\n    public LambdaQueryWrapperX<T> inIfPresent(SFunction<T, ?> column, Object... values) {\n        if (ObjectUtil.isAllNotEmpty(values) && !ArrayUtil.isEmpty(values)) {\n            return (LambdaQueryWrapperX<T>) super.in(column, values);\n        }\n        return this;\n    }\n\n    public LambdaQueryWrapperX<T> eqIfPresent(SFunction<T, ?> column, Object val) {\n        if (ObjectUtil.isNotEmpty(val)) {\n            return (LambdaQueryWrapperX<T>) super.eq(column, val);\n        }\n        return this;\n    }\n\n    public LambdaQueryWrapperX<T> neIfPresent(SFunction<T, ?> column, Object val) {\n        if (ObjectUtil.isNotEmpty(val)) {\n            return (LambdaQueryWrapperX<T>) super.ne(column, val);\n        }\n        return this;\n    }\n\n    public LambdaQueryWrapperX<T> gtIfPresent(SFunction<T, ?> column, Object val) {\n        if (val != null) {\n            return (LambdaQueryWrapperX<T>) super.gt(column, val);\n        }\n        return this;\n    }\n\n    public LambdaQueryWrapperX<T> geIfPresent(SFunction<T, ?> column, Object val) {\n        if (val != null) {\n            return (LambdaQueryWrapperX<T>) super.ge(column, val);\n        }\n        return this;\n    }\n\n    public LambdaQueryWrapperX<T> ltIfPresent(SFunction<T, ?> column, Object val) {\n        if (val != null) {\n            return (LambdaQueryWrapperX<T>) super.lt(column, val);\n        }\n        return this;\n    }\n\n    public LambdaQueryWrapperX<T> leIfPresent(SFunction<T, ?> column, Object val) {\n        if (val != null) {\n            return (LambdaQueryWrapperX<T>) super.le(column, val);\n        }\n        return this;\n    }\n\n    public LambdaQueryWrapperX<T> betweenIfPresent(SFunction<T, ?> column, Object val1, Object val2) {\n        if (val1 != null && val2 != null) {\n            return (LambdaQueryWrapperX<T>) super.between(column, val1, val2);\n        }\n        if (val1 != null) {\n            return (LambdaQueryWrapperX<T>) ge(column, val1);\n        }\n        if (val2 != null) {\n            return (LambdaQueryWrapperX<T>) le(column, val2);\n        }\n        return this;\n    }\n\n    public LambdaQueryWrapperX<T> betweenIfPresent(SFunction<T, ?> column, Object[] values) {\n        Object val1 = ArrayUtils.get(values, 0);\n        Object val2 = ArrayUtils.get(values, 1);\n        return betweenIfPresent(column, val1, val2);\n    }\n\n    // ========== 重写父类方法，方便链式调用 ==========\n\n    @Override\n    public LambdaQueryWrapperX<T> eq(boolean condition, SFunction<T, ?> column, Object val) {\n        super.eq(condition, column, val);\n        return this;\n    }\n\n    @Override\n    public LambdaQueryWrapperX<T> eq(SFunction<T, ?> column, Object val) {\n        super.eq(column, val);\n        return this;\n    }\n\n    @Override\n    public LambdaQueryWrapperX<T> orderByDesc(SFunction<T, ?> column) {\n        super.orderByDesc(true, column);\n        return this;\n    }\n\n    @Override\n    public LambdaQueryWrapperX<T> last(String lastSql) {\n        super.last(lastSql);\n        return this;\n    }\n\n    @Override\n    public LambdaQueryWrapperX<T> in(SFunction<T, ?> column, Collection<?> coll) {\n        super.in(column, coll);\n        return this;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-mybatis/src/main/java/co/yixiang/yshop/framework/mybatis/core/query/MPJLambdaWrapperX.java",
    "content": "package co.yixiang.yshop.framework.mybatis.core.query;\n\nimport cn.hutool.core.util.ArrayUtil;\nimport cn.hutool.core.util.ObjectUtil;\nimport co.yixiang.yshop.framework.common.util.collection.ArrayUtils;\nimport com.baomidou.mybatisplus.core.toolkit.support.SFunction;\nimport com.github.yulichang.toolkit.MPJWrappers;\nimport com.github.yulichang.wrapper.MPJLambdaWrapper;\nimport org.springframework.util.StringUtils;\n\nimport java.util.Collection;\nimport java.util.function.Consumer;\n\n/**\n * 拓展 MyBatis Plus Join QueryWrapper 类，主要增加如下功能：\n * <p>\n * 1. 拼接条件的方法，增加 xxxIfPresent 方法，用于判断值不存在的时候，不要拼接到条件中。\n *\n * @param <T> 数据类型\n */\npublic class MPJLambdaWrapperX<T> extends MPJLambdaWrapper<T> {\n\n    public MPJLambdaWrapperX<T> likeIfPresent(SFunction<T, ?> column, String val) {\n        MPJWrappers.lambdaJoin().like(column, val);\n        if (StringUtils.hasText(val)) {\n            return (MPJLambdaWrapperX<T>) super.like(column, val);\n        }\n        return this;\n    }\n\n    public MPJLambdaWrapperX<T> inIfPresent(SFunction<T, ?> column, Collection<?> values) {\n        if (ObjectUtil.isAllNotEmpty(values) && !ArrayUtil.isEmpty(values)) {\n            return (MPJLambdaWrapperX<T>) super.in(column, values);\n        }\n        return this;\n    }\n\n    public MPJLambdaWrapperX<T> inIfPresent(SFunction<T, ?> column, Object... values) {\n        if (ObjectUtil.isAllNotEmpty(values) && !ArrayUtil.isEmpty(values)) {\n            return (MPJLambdaWrapperX<T>) super.in(column, values);\n        }\n        return this;\n    }\n\n    public MPJLambdaWrapperX<T> eqIfPresent(SFunction<T, ?> column, Object val) {\n        if (ObjectUtil.isNotEmpty(val)) {\n            return (MPJLambdaWrapperX<T>) super.eq(column, val);\n        }\n        return this;\n    }\n\n    public MPJLambdaWrapperX<T> neIfPresent(SFunction<T, ?> column, Object val) {\n        if (ObjectUtil.isNotEmpty(val)) {\n            return (MPJLambdaWrapperX<T>) super.ne(column, val);\n        }\n        return this;\n    }\n\n    public MPJLambdaWrapperX<T> gtIfPresent(SFunction<T, ?> column, Object val) {\n        if (val != null) {\n            return (MPJLambdaWrapperX<T>) super.gt(column, val);\n        }\n        return this;\n    }\n\n    public MPJLambdaWrapperX<T> geIfPresent(SFunction<T, ?> column, Object val) {\n        if (val != null) {\n            return (MPJLambdaWrapperX<T>) super.ge(column, val);\n        }\n        return this;\n    }\n\n    public MPJLambdaWrapperX<T> ltIfPresent(SFunction<T, ?> column, Object val) {\n        if (val != null) {\n            return (MPJLambdaWrapperX<T>) super.lt(column, val);\n        }\n        return this;\n    }\n\n    public MPJLambdaWrapperX<T> leIfPresent(SFunction<T, ?> column, Object val) {\n        if (val != null) {\n            return (MPJLambdaWrapperX<T>) super.le(column, val);\n        }\n        return this;\n    }\n\n    public MPJLambdaWrapperX<T> betweenIfPresent(SFunction<T, ?> column, Object val1, Object val2) {\n        if (val1 != null && val2 != null) {\n            return (MPJLambdaWrapperX<T>) super.between(column, val1, val2);\n        }\n        if (val1 != null) {\n            return (MPJLambdaWrapperX<T>) ge(column, val1);\n        }\n        if (val2 != null) {\n            return (MPJLambdaWrapperX<T>) le(column, val2);\n        }\n        return this;\n    }\n\n    public MPJLambdaWrapperX<T> betweenIfPresent(SFunction<T, ?> column, Object[] values) {\n        Object val1 = ArrayUtils.get(values, 0);\n        Object val2 = ArrayUtils.get(values, 1);\n        return betweenIfPresent(column, val1, val2);\n    }\n\n    // ========== 重写父类方法，方便链式调用 ==========\n\n    @Override\n    public <X> MPJLambdaWrapperX<T> eq(boolean condition, SFunction<X, ?> column, Object val) {\n        super.eq(condition, column, val);\n        return this;\n    }\n\n    @Override\n    public <X> MPJLambdaWrapperX<T> eq(SFunction<X, ?> column, Object val) {\n        super.eq(column, val);\n        return this;\n    }\n\n    @Override\n    public <X> MPJLambdaWrapperX<T> orderByDesc(SFunction<X, ?> column) {\n        //noinspection unchecked\n        super.orderByDesc(true, column);\n        return this;\n    }\n\n    @Override\n    public MPJLambdaWrapperX<T> last(String lastSql) {\n        super.last(lastSql);\n        return this;\n    }\n\n    @Override\n    public <X> MPJLambdaWrapperX<T> in(SFunction<X, ?> column, Collection<?> coll) {\n        super.in(column, coll);\n        return this;\n    }\n\n    @Override\n    public MPJLambdaWrapperX<T> selectAll(Class<?> clazz) {\n        super.selectAll(clazz);\n        return this;\n    }\n\n    @Override\n    public MPJLambdaWrapperX<T> selectAll(Class<?> clazz, String prefix) {\n        super.selectAll(clazz, prefix);\n        return this;\n    }\n\n    @Override\n    public <S> MPJLambdaWrapperX<T> selectAs(SFunction<S, ?> column, String alias) {\n        super.selectAs(column, alias);\n        return this;\n    }\n\n    @Override\n    public <E> MPJLambdaWrapperX<T> selectAs(String column, SFunction<E, ?> alias) {\n        super.selectAs(column, alias);\n        return this;\n    }\n\n    @Override\n    public <S, X> MPJLambdaWrapperX<T> selectAs(SFunction<S, ?> column, SFunction<X, ?> alias) {\n        super.selectAs(column, alias);\n        return this;\n    }\n\n    @Override\n    public <E, X> MPJLambdaWrapperX<T> selectAs(String index, SFunction<E, ?> column, SFunction<X, ?> alias) {\n        super.selectAs(index, column, alias);\n        return this;\n    }\n\n    @Override\n    public <E> MPJLambdaWrapperX<T> selectAsClass(Class<E> source, Class<?> tag) {\n        super.selectAsClass(source, tag);\n        return this;\n    }\n\n    @Override\n    public <E, F> MPJLambdaWrapperX<T> selectSub(Class<E> clazz, Consumer<MPJLambdaWrapper<E>> consumer, SFunction<F, ?> alias) {\n        super.selectSub(clazz, consumer, alias);\n        return this;\n    }\n\n    @Override\n    public <E, F> MPJLambdaWrapperX<T> selectSub(Class<E> clazz, String st, Consumer<MPJLambdaWrapper<E>> consumer, SFunction<F, ?> alias) {\n        super.selectSub(clazz, st, consumer, alias);\n        return this;\n    }\n\n    @Override\n    public <S> MPJLambdaWrapperX<T> selectCount(SFunction<S, ?> column) {\n        super.selectCount(column);\n        return this;\n    }\n\n    @Override\n    public MPJLambdaWrapperX<T> selectCount(Object column, String alias) {\n        super.selectCount(column, alias);\n        return this;\n    }\n\n    @Override\n    public <X> MPJLambdaWrapperX<T> selectCount(Object column, SFunction<X, ?> alias) {\n        super.selectCount(column, alias);\n        return this;\n    }\n\n    @Override\n    public <S, X> MPJLambdaWrapperX<T> selectCount(SFunction<S, ?> column, String alias) {\n        super.selectCount(column, alias);\n        return this;\n    }\n\n    @Override\n    public <S, X> MPJLambdaWrapperX<T> selectCount(SFunction<S, ?> column, SFunction<X, ?> alias) {\n        super.selectCount(column, alias);\n        return this;\n    }\n\n    @Override\n    public <S> MPJLambdaWrapperX<T> selectSum(SFunction<S, ?> column) {\n        super.selectSum(column);\n        return this;\n    }\n\n    @Override\n    public <S, X> MPJLambdaWrapperX<T> selectSum(SFunction<S, ?> column, String alias) {\n        super.selectSum(column, alias);\n        return this;\n    }\n\n    @Override\n    public <S, X> MPJLambdaWrapperX<T> selectSum(SFunction<S, ?> column, SFunction<X, ?> alias) {\n        super.selectSum(column, alias);\n        return this;\n    }\n\n    @Override\n    public <S> MPJLambdaWrapperX<T> selectMax(SFunction<S, ?> column) {\n        super.selectMax(column);\n        return this;\n    }\n\n    @Override\n    public <S, X> MPJLambdaWrapperX<T> selectMax(SFunction<S, ?> column, String alias) {\n        super.selectMax(column, alias);\n        return this;\n    }\n\n    @Override\n    public <S, X> MPJLambdaWrapperX<T> selectMax(SFunction<S, ?> column, SFunction<X, ?> alias) {\n        super.selectMax(column, alias);\n        return this;\n    }\n\n    @Override\n    public <S> MPJLambdaWrapperX<T> selectMin(SFunction<S, ?> column) {\n        super.selectMin(column);\n        return this;\n    }\n\n    @Override\n    public <S, X> MPJLambdaWrapperX<T> selectMin(SFunction<S, ?> column, String alias) {\n        super.selectMin(column, alias);\n        return this;\n    }\n\n    @Override\n    public <S, X> MPJLambdaWrapperX<T> selectMin(SFunction<S, ?> column, SFunction<X, ?> alias) {\n        super.selectMin(column, alias);\n        return this;\n    }\n\n    @Override\n    public <S> MPJLambdaWrapperX<T> selectAvg(SFunction<S, ?> column) {\n        super.selectAvg(column);\n        return this;\n    }\n\n    @Override\n    public <S, X> MPJLambdaWrapperX<T> selectAvg(SFunction<S, ?> column, String alias) {\n        super.selectAvg(column, alias);\n        return this;\n    }\n\n    @Override\n    public <S, X> MPJLambdaWrapperX<T> selectAvg(SFunction<S, ?> column, SFunction<X, ?> alias) {\n        super.selectAvg(column, alias);\n        return this;\n    }\n\n    @Override\n    public <S> MPJLambdaWrapperX<T> selectLen(SFunction<S, ?> column) {\n        super.selectLen(column);\n        return this;\n    }\n\n    @Override\n    public <S, X> MPJLambdaWrapperX<T> selectLen(SFunction<S, ?> column, String alias) {\n        super.selectLen(column, alias);\n        return this;\n    }\n\n    @Override\n    public <S, X> MPJLambdaWrapperX<T> selectLen(SFunction<S, ?> column, SFunction<X, ?> alias) {\n        super.selectLen(column, alias);\n        return this;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-mybatis/src/main/java/co/yixiang/yshop/framework/mybatis/core/query/QueryWrapperX.java",
    "content": "package co.yixiang.yshop.framework.mybatis.core.query;\n\nimport cn.hutool.core.lang.Assert;\nimport co.yixiang.yshop.framework.mybatis.core.enums.SqlConstants;\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.core.toolkit.ArrayUtils;\nimport com.baomidou.mybatisplus.core.toolkit.CollectionUtils;\nimport org.springframework.util.StringUtils;\n\nimport java.util.Collection;\n\n/**\n * 拓展 MyBatis Plus QueryWrapper 类，主要增加如下功能：\n *\n * 1. 拼接条件的方法，增加 xxxIfPresent 方法，用于判断值不存在的时候，不要拼接到条件中。\n *\n * @param <T> 数据类型\n */\npublic class QueryWrapperX<T> extends QueryWrapper<T> {\n\n    public QueryWrapperX<T> likeIfPresent(String column, String val) {\n        if (StringUtils.hasText(val)) {\n            return (QueryWrapperX<T>) super.like(column, val);\n        }\n        return this;\n    }\n\n    public QueryWrapperX<T> inIfPresent(String column, Collection<?> values) {\n        if (!CollectionUtils.isEmpty(values)) {\n            return (QueryWrapperX<T>) super.in(column, values);\n        }\n        return this;\n    }\n\n    public QueryWrapperX<T> inIfPresent(String column, Object... values) {\n        if (!ArrayUtils.isEmpty(values)) {\n            return (QueryWrapperX<T>) super.in(column, values);\n        }\n        return this;\n    }\n\n    public QueryWrapperX<T> eqIfPresent(String column, Object val) {\n        if (val != null) {\n            return (QueryWrapperX<T>) super.eq(column, val);\n        }\n        return this;\n    }\n\n    public QueryWrapperX<T> neIfPresent(String column, Object val) {\n        if (val != null) {\n            return (QueryWrapperX<T>) super.ne(column, val);\n        }\n        return this;\n    }\n\n    public QueryWrapperX<T> gtIfPresent(String column, Object val) {\n        if (val != null) {\n            return (QueryWrapperX<T>) super.gt(column, val);\n        }\n        return this;\n    }\n\n    public QueryWrapperX<T> geIfPresent(String column, Object val) {\n        if (val != null) {\n            return (QueryWrapperX<T>) super.ge(column, val);\n        }\n        return this;\n    }\n\n    public QueryWrapperX<T> ltIfPresent(String column, Object val) {\n        if (val != null) {\n            return (QueryWrapperX<T>) super.lt(column, val);\n        }\n        return this;\n    }\n\n    public QueryWrapperX<T> leIfPresent(String column, Object val) {\n        if (val != null) {\n            return (QueryWrapperX<T>) super.le(column, val);\n        }\n        return this;\n    }\n\n    public QueryWrapperX<T> betweenIfPresent(String column, Object val1, Object val2) {\n        if (val1 != null && val2 != null) {\n            return (QueryWrapperX<T>) super.between(column, val1, val2);\n        }\n        if (val1 != null) {\n            return (QueryWrapperX<T>) ge(column, val1);\n        }\n        if (val2 != null) {\n            return (QueryWrapperX<T>) le(column, val2);\n        }\n        return this;\n    }\n\n    public QueryWrapperX<T> betweenIfPresent(String column, Object[] values) {\n        if (values!= null && values.length != 0 && values[0] != null && values[1] != null) {\n            return (QueryWrapperX<T>) super.between(column, values[0], values[1]);\n        }\n        if (values!= null && values.length != 0 && values[0] != null) {\n            return (QueryWrapperX<T>) ge(column, values[0]);\n        }\n        if (values!= null && values.length != 0 && values[1] != null) {\n            return (QueryWrapperX<T>) le(column, values[1]);\n        }\n        return this;\n    }\n\n    // ========== 重写父类方法，方便链式调用 ==========\n\n    @Override\n    public QueryWrapperX<T> eq(boolean condition, String column, Object val) {\n        super.eq(condition, column, val);\n        return this;\n    }\n\n    @Override\n    public QueryWrapperX<T> eq(String column, Object val) {\n        super.eq(column, val);\n        return this;\n    }\n\n    @Override\n    public QueryWrapperX<T> orderByDesc(String column) {\n        super.orderByDesc(true, column);\n        return this;\n    }\n\n    @Override\n    public QueryWrapperX<T> last(String lastSql) {\n        super.last(lastSql);\n        return this;\n    }\n\n    @Override\n    public QueryWrapperX<T> in(String column, Collection<?> coll) {\n        super.in(column, coll);\n        return this;\n    }\n\n    /**\n     * 设置只返回最后一条\n     *\n     * TODO yshop：不是完美解，需要在思考下。如果使用多数据源，并且数据源是多种类型时，可能会存在问题：实现之返回一条的语法不同\n     *\n     * @return this\n     */\n    public QueryWrapperX<T> limitN(int n) {\n        Assert.notNull(SqlConstants.DB_TYPE, \"获取不到数据库的类型\");\n        switch (SqlConstants.DB_TYPE) {\n            case ORACLE:\n            case ORACLE_12C:\n                super.le(\"ROWNUM\", n);\n                break;\n            case SQL_SERVER:\n            case SQL_SERVER2005:\n                super.select(\"TOP \" + n + \" *\"); // 由于 SQL Server 是通过 SELECT TOP 1 实现限制一条，所以只好使用 * 查询剩余字段\n                break;\n            default:\n                super.last(\"LIMIT \" + n);\n        }\n        return this;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-mybatis/src/main/java/co/yixiang/yshop/framework/mybatis/core/type/EncryptTypeHandler.java",
    "content": "package co.yixiang.yshop.framework.mybatis.core.type;\n\nimport cn.hutool.core.lang.Assert;\nimport cn.hutool.crypto.SecureUtil;\nimport cn.hutool.crypto.symmetric.AES;\nimport cn.hutool.extra.spring.SpringUtil;\nimport org.apache.ibatis.type.BaseTypeHandler;\nimport org.apache.ibatis.type.JdbcType;\n\nimport java.sql.CallableStatement;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\n\n/**\n * 字段字段的 TypeHandler 实现类，基于 {@link cn.hutool.crypto.symmetric.AES} 实现\n * 可通过 jasypt.encryptor.password 配置项，设置密钥\n *\n * @author yshop\n */\npublic class EncryptTypeHandler extends BaseTypeHandler<String> {\n\n    private static final String ENCRYPTOR_PROPERTY_NAME = \"mybatis-plus.encryptor.password\";\n\n    private static AES aes;\n\n    @Override\n    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {\n        ps.setString(i, encrypt(parameter));\n    }\n\n    @Override\n    public String getNullableResult(ResultSet rs, String columnName) throws SQLException {\n        String value = rs.getString(columnName);\n        return decrypt(value);\n    }\n\n    @Override\n    public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {\n        String value = rs.getString(columnIndex);\n        return decrypt(value);\n    }\n\n    @Override\n    public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {\n        String value = cs.getString(columnIndex);\n        return decrypt(value);\n    }\n\n    private static String decrypt(String value) {\n        if (value == null) {\n            return null;\n        }\n        return getEncryptor().decryptStr(value);\n    }\n\n    public static String encrypt(String rawValue) {\n        if (rawValue == null) {\n            return null;\n        }\n        return getEncryptor().encryptBase64(rawValue);\n    }\n\n    private static AES getEncryptor() {\n        if (aes != null) {\n            return aes;\n        }\n        // 构建 AES\n        String password = SpringUtil.getProperty(ENCRYPTOR_PROPERTY_NAME);\n        Assert.notEmpty(password, \"配置项({}) 不能为空\", ENCRYPTOR_PROPERTY_NAME);\n        aes = SecureUtil.aes(password.getBytes());\n        return aes;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-mybatis/src/main/java/co/yixiang/yshop/framework/mybatis/core/type/IntegerListTypeHandler.java",
    "content": "package co.yixiang.yshop.framework.mybatis.core.type;\n\nimport cn.hutool.core.collection.CollUtil;\nimport co.yixiang.yshop.framework.common.util.string.StrUtils;\nimport org.apache.ibatis.type.JdbcType;\nimport org.apache.ibatis.type.MappedJdbcTypes;\nimport org.apache.ibatis.type.MappedTypes;\nimport org.apache.ibatis.type.TypeHandler;\n\nimport java.sql.CallableStatement;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.util.List;\n\n/**\n * List<Integer> 的类型转换器实现类，对应数据库的 varchar 类型\n *\n * @author jason\n */\n@MappedJdbcTypes(JdbcType.VARCHAR)\n@MappedTypes(List.class)\npublic class IntegerListTypeHandler implements TypeHandler<List<Integer>> {\n\n    private static final String COMMA = \",\";\n\n    @Override\n    public void setParameter(PreparedStatement ps, int i, List<Integer> strings, JdbcType jdbcType) throws SQLException {\n        ps.setString(i, CollUtil.join(strings, COMMA));\n    }\n\n    @Override\n    public List<Integer> getResult(ResultSet rs, String columnName) throws SQLException {\n        String value = rs.getString(columnName);\n        return getResult(value);\n    }\n\n    @Override\n    public List<Integer> getResult(ResultSet rs, int columnIndex) throws SQLException {\n        String value = rs.getString(columnIndex);\n        return getResult(value);\n    }\n\n    @Override\n    public List<Integer> getResult(CallableStatement cs, int columnIndex) throws SQLException {\n        String value = cs.getString(columnIndex);\n        return getResult(value);\n    }\n\n    private List<Integer> getResult(String value) {\n        if (value == null) {\n            return null;\n        }\n        return StrUtils.splitToInteger(value, COMMA);\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-mybatis/src/main/java/co/yixiang/yshop/framework/mybatis/core/type/JsonLongSetTypeHandler.java",
    "content": "package co.yixiang.yshop.framework.mybatis.core.type;\n\nimport co.yixiang.yshop.framework.common.util.json.JsonUtils;\nimport com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;\nimport com.fasterxml.jackson.core.type.TypeReference;\n\nimport java.util.Set;\n\n/**\n * 参考 {@link com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler} 实现\n * 在我们将字符串反序列化为 Set 并且泛型为 Long 时，如果每个元素的数值太小，会被处理成 Integer 类型，导致可能存在隐性的 BUG。\n *\n * 例如说哦，SysUserDO 的 postIds 属性\n *\n * @author yshop\n */\npublic class JsonLongSetTypeHandler extends AbstractJsonTypeHandler<Object> {\n\n    private static final TypeReference<Set<Long>> TYPE_REFERENCE = new TypeReference<Set<Long>>(){};\n\n    @Override\n    protected Object parse(String json) {\n        return JsonUtils.parseObject(json, TYPE_REFERENCE);\n    }\n\n    @Override\n    protected String toJson(Object obj) {\n        return JsonUtils.toJsonString(obj);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-mybatis/src/main/java/co/yixiang/yshop/framework/mybatis/core/type/LongListTypeHandler.java",
    "content": "package co.yixiang.yshop.framework.mybatis.core.type;\n\nimport cn.hutool.core.collection.CollUtil;\nimport co.yixiang.yshop.framework.common.util.string.StrUtils;\nimport org.apache.ibatis.type.JdbcType;\nimport org.apache.ibatis.type.MappedJdbcTypes;\nimport org.apache.ibatis.type.MappedTypes;\nimport org.apache.ibatis.type.TypeHandler;\n\nimport java.sql.CallableStatement;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.util.List;\n\n/**\n * List<Long> 的类型转换器实现类，对应数据库的 varchar 类型\n *\n * @author yshop\n */\n@MappedJdbcTypes(JdbcType.VARCHAR)\n@MappedTypes(List.class)\npublic class LongListTypeHandler implements TypeHandler<List<Long>> {\n\n    private static final String COMMA = \",\";\n\n    @Override\n    public void setParameter(PreparedStatement ps, int i, List<Long> strings, JdbcType jdbcType) throws SQLException {\n        // 设置占位符\n        ps.setString(i, CollUtil.join(strings, COMMA));\n    }\n\n    @Override\n    public List<Long> getResult(ResultSet rs, String columnName) throws SQLException {\n        String value = rs.getString(columnName);\n        return getResult(value);\n    }\n\n    @Override\n    public List<Long> getResult(ResultSet rs, int columnIndex) throws SQLException {\n        String value = rs.getString(columnIndex);\n        return getResult(value);\n    }\n\n    @Override\n    public List<Long> getResult(CallableStatement cs, int columnIndex) throws SQLException {\n        String value = cs.getString(columnIndex);\n        return getResult(value);\n    }\n\n    private List<Long> getResult(String value) {\n        if (value == null) {\n            return null;\n        }\n        return StrUtils.splitToLong(value, COMMA);\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-mybatis/src/main/java/co/yixiang/yshop/framework/mybatis/core/type/StringListTypeHandler.java",
    "content": "package co.yixiang.yshop.framework.mybatis.core.type;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.util.StrUtil;\nimport org.apache.ibatis.type.JdbcType;\nimport org.apache.ibatis.type.MappedJdbcTypes;\nimport org.apache.ibatis.type.MappedTypes;\nimport org.apache.ibatis.type.TypeHandler;\n\nimport java.sql.CallableStatement;\nimport java.sql.PreparedStatement;\nimport java.sql.ResultSet;\nimport java.sql.SQLException;\nimport java.util.List;\n\n/**\n * List<String> 的类型转换器实现类，对应数据库的 varchar 类型\n *\n * @author 永不言败\n * @since 2022 3/23 12:50:15\n */\n@MappedJdbcTypes(JdbcType.VARCHAR)\n@MappedTypes(List.class)\npublic class StringListTypeHandler implements TypeHandler<List<String>> {\n\n    private static final String COMMA = \",\";\n\n    @Override\n    public void setParameter(PreparedStatement ps, int i, List<String> strings, JdbcType jdbcType) throws SQLException {\n        // 设置占位符\n        ps.setString(i, CollUtil.join(strings, COMMA));\n    }\n\n    @Override\n    public List<String> getResult(ResultSet rs, String columnName) throws SQLException {\n        String value = rs.getString(columnName);\n        return getResult(value);\n    }\n\n    @Override\n    public List<String> getResult(ResultSet rs, int columnIndex) throws SQLException {\n        String value = rs.getString(columnIndex);\n        return getResult(value);\n    }\n\n    @Override\n    public List<String> getResult(CallableStatement cs, int columnIndex) throws SQLException {\n        String value = cs.getString(columnIndex);\n        return getResult(value);\n    }\n\n    private List<String> getResult(String value) {\n        if (value == null) {\n            return null;\n        }\n        return StrUtil.splitTrim(value, COMMA);\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-mybatis/src/main/java/co/yixiang/yshop/framework/mybatis/core/util/JdbcUtils.java",
    "content": "package co.yixiang.yshop.framework.mybatis.core.util;\n\nimport com.baomidou.mybatisplus.annotation.DbType;\n\nimport java.sql.Connection;\nimport java.sql.DriverManager;\n\n/**\n * JDBC 工具类\n *\n * @author yshop\n */\npublic class JdbcUtils {\n\n    /**\n     * 判断连接是否正确\n     *\n     * @param url      数据源连接\n     * @param username 账号\n     * @param password 密码\n     * @return 是否正确\n     */\n    public static boolean isConnectionOK(String url, String username, String password) {\n        try (Connection ignored = DriverManager.getConnection(url, username, password)) {\n            return true;\n        } catch (Exception ex) {\n            return false;\n        }\n    }\n\n    /**\n     * 获得 URL 对应的 DB 类型\n     *\n     * @param url URL\n     * @return DB 类型\n     */\n    public static DbType getDbType(String url) {\n        String name = com.alibaba.druid.util.JdbcUtils.getDbType(url, null);\n        return DbType.getDbType(name);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-mybatis/src/main/java/co/yixiang/yshop/framework/mybatis/core/util/MyBatisUtils.java",
    "content": "package co.yixiang.yshop.framework.mybatis.core.util;\n\nimport cn.hutool.core.collection.CollectionUtil;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport co.yixiang.yshop.framework.common.pojo.SortingField;\nimport com.baomidou.mybatisplus.core.metadata.OrderItem;\nimport com.baomidou.mybatisplus.core.toolkit.StringPool;\nimport com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;\nimport com.baomidou.mybatisplus.extension.plugins.inner.InnerInterceptor;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport net.sf.jsqlparser.expression.Alias;\nimport net.sf.jsqlparser.schema.Column;\nimport net.sf.jsqlparser.schema.Table;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n/**\n * MyBatis 工具类\n */\npublic class MyBatisUtils {\n\n    private static final String MYSQL_ESCAPE_CHARACTER = \"`\";\n\n    public static <T> Page<T> buildPage(PageParam pageParam) {\n        return buildPage(pageParam, null);\n    }\n\n    public static <T> Page<T> buildPage(PageParam pageParam, Collection<SortingField> sortingFields) {\n        // 页码 + 数量\n        Page<T> page = new Page<>(pageParam.getPageNo(), pageParam.getPageSize());\n        // 排序字段\n        if (!CollectionUtil.isEmpty(sortingFields)) {\n            page.addOrder(sortingFields.stream().map(sortingField -> SortingField.ORDER_ASC.equals(sortingField.getOrder()) ?\n                    OrderItem.asc(sortingField.getField()) : OrderItem.desc(sortingField.getField()))\n                    .collect(Collectors.toList()));\n        }\n        return page;\n    }\n\n    /**\n     * 将拦截器添加到链中\n     * 由于 MybatisPlusInterceptor 不支持添加拦截器，所以只能全量设置\n     *\n     * @param interceptor 链\n     * @param inner       拦截器\n     * @param index       位置\n     */\n    public static void addInterceptor(MybatisPlusInterceptor interceptor, InnerInterceptor inner, int index) {\n        List<InnerInterceptor> inners = new ArrayList<>(interceptor.getInterceptors());\n        inners.add(index, inner);\n        interceptor.setInterceptors(inners);\n    }\n\n    /**\n     * 获得 Table 对应的表名\n     *\n     * 兼容 MySQL 转义表名 `t_xxx`\n     *\n     * @param table 表\n     * @return 去除转移字符后的表名\n     */\n    public static String getTableName(Table table) {\n        String tableName = table.getName();\n        if (tableName.startsWith(MYSQL_ESCAPE_CHARACTER) && tableName.endsWith(MYSQL_ESCAPE_CHARACTER)) {\n            tableName = tableName.substring(1, tableName.length() - 1);\n        }\n        return tableName;\n    }\n\n    /**\n     * 构建 Column 对象\n     *\n     * @param tableName  表名\n     * @param tableAlias 别名\n     * @param column     字段名\n     * @return Column 对象\n     */\n    public static Column buildColumn(String tableName, Alias tableAlias, String column) {\n        if (tableAlias != null) {\n            tableName = tableAlias.getName();\n        }\n        return new Column(tableName + StringPool.DOT + column);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-mybatis/src/main/java/co/yixiang/yshop/framework/translate/config/YshopTranslateAutoConfiguration.java",
    "content": "package co.yixiang.yshop.framework.translate.config;\n\nimport co.yixiang.yshop.framework.translate.core.TranslateUtils;\nimport com.fhs.trans.service.impl.TransService;\nimport org.springframework.boot.autoconfigure.AutoConfiguration;\nimport org.springframework.context.annotation.Bean;\n\n@AutoConfiguration\npublic class YshopTranslateAutoConfiguration {\n\n    @Bean\n    @SuppressWarnings({\"InstantiationOfUtilityClass\", \"SpringJavaInjectionPointsAutowiringInspection\"})\n    public TranslateUtils translateUtils(TransService transService) {\n        TranslateUtils.init(transService);\n        return new TranslateUtils();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-mybatis/src/main/java/co/yixiang/yshop/framework/translate/core/TranslateUtils.java",
    "content": "package co.yixiang.yshop.framework.translate.core;\n\nimport cn.hutool.core.collection.CollUtil;\nimport com.fhs.core.trans.vo.VO;\nimport com.fhs.trans.service.impl.TransService;\n\nimport java.util.List;\n\n/**\n * VO 数据翻译 Utils\n *\n * @author yshop\n */\npublic class TranslateUtils {\n\n    private static TransService transService;\n\n    public static void init(TransService transService) {\n        TranslateUtils.transService = transService;\n    }\n\n    /**\n     * 数据翻译\n     *\n     * 使用场景：无法使用 @TransMethodResult 注解的场景，只能通过手动触发翻译\n     *\n     * @param data 数据\n     * @return 翻译结果\n     */\n    public static <T extends VO> List<T> translate(List<T> data) {\n        if (CollUtil.isNotEmpty((data))) {\n            transService.transBatch(data);\n        }\n        return data;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-mybatis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports",
    "content": "co.yixiang.yshop.framework.datasource.config.YshopDataSourceAutoConfiguration\nco.yixiang.yshop.framework.mybatis.config.YshopMybatisAutoConfiguration\nco.yixiang.yshop.framework.translate.config.YshopTranslateAutoConfiguration"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-mybatis/src/main/resources/META-INF/spring.factories",
    "content": "org.springframework.boot.env.EnvironmentPostProcessor=\\\n  co.yixiang.yshop.framework.mybatis.config.IdTypeEnvironmentPostProcessor\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-protection/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>co.yixiang.boot</groupId>\n        <artifactId>yshop-framework</artifactId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>yshop-spring-boot-starter-protection</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>服务保证，提供分布式锁、幂等、限流、熔断等等功能</description>\n    <url>https://gitee.com/guchengwuyue/yshop-drink</url>\n\n    <dependencies>\n        <!-- Web 相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-web</artifactId>\n            <scope>provided</scope> <!-- 设置为 provided，只有限流、幂等使用到 -->\n        </dependency>\n\n        <!-- DB 相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-redis</artifactId>\n        </dependency>\n\n        <!-- 服务保障相关 -->\n        <dependency>\n            <groupId>com.baomidou</groupId>\n            <artifactId>lock4j-redisson-spring-boot-starter</artifactId>\n            <optional>true</optional>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-protection/src/main/java/co/yixiang/yshop/framework/idempotent/config/YshopIdempotentConfiguration.java",
    "content": "package co.yixiang.yshop.framework.idempotent.config;\n\nimport co.yixiang.yshop.framework.idempotent.core.aop.IdempotentAspect;\nimport co.yixiang.yshop.framework.idempotent.core.keyresolver.impl.DefaultIdempotentKeyResolver;\nimport co.yixiang.yshop.framework.idempotent.core.keyresolver.impl.ExpressionIdempotentKeyResolver;\nimport co.yixiang.yshop.framework.idempotent.core.keyresolver.IdempotentKeyResolver;\nimport co.yixiang.yshop.framework.idempotent.core.keyresolver.impl.UserIdempotentKeyResolver;\nimport co.yixiang.yshop.framework.idempotent.core.redis.IdempotentRedisDAO;\nimport org.springframework.boot.autoconfigure.AutoConfiguration;\nimport co.yixiang.yshop.framework.redis.config.YshopRedisAutoConfiguration;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.data.redis.core.StringRedisTemplate;\n\nimport java.util.List;\n\n@AutoConfiguration(after = YshopRedisAutoConfiguration.class)\npublic class YshopIdempotentConfiguration {\n\n    @Bean\n    public IdempotentAspect idempotentAspect(List<IdempotentKeyResolver> keyResolvers, IdempotentRedisDAO idempotentRedisDAO) {\n        return new IdempotentAspect(keyResolvers, idempotentRedisDAO);\n    }\n\n    @Bean\n    public IdempotentRedisDAO idempotentRedisDAO(StringRedisTemplate stringRedisTemplate) {\n        return new IdempotentRedisDAO(stringRedisTemplate);\n    }\n\n    // ========== 各种 IdempotentKeyResolver Bean ==========\n\n    @Bean\n    public DefaultIdempotentKeyResolver defaultIdempotentKeyResolver() {\n        return new DefaultIdempotentKeyResolver();\n    }\n\n    @Bean\n    public UserIdempotentKeyResolver userIdempotentKeyResolver() {\n        return new UserIdempotentKeyResolver();\n    }\n\n    @Bean\n    public ExpressionIdempotentKeyResolver expressionIdempotentKeyResolver() {\n        return new ExpressionIdempotentKeyResolver();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-protection/src/main/java/co/yixiang/yshop/framework/idempotent/core/annotation/Idempotent.java",
    "content": "package co.yixiang.yshop.framework.idempotent.core.annotation;\n\nimport co.yixiang.yshop.framework.idempotent.core.keyresolver.impl.DefaultIdempotentKeyResolver;\nimport co.yixiang.yshop.framework.idempotent.core.keyresolver.IdempotentKeyResolver;\nimport co.yixiang.yshop.framework.idempotent.core.keyresolver.impl.ExpressionIdempotentKeyResolver;\nimport co.yixiang.yshop.framework.idempotent.core.keyresolver.impl.UserIdempotentKeyResolver;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * 幂等注解\n *\n * @author yshop\n */\n@Target({ElementType.METHOD})\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface Idempotent {\n\n    /**\n     * 幂等的超时时间，默认为 1 秒\n     *\n     * 注意，如果执行时间超过它，请求还是会进来\n     */\n    int timeout() default 1;\n    /**\n     * 时间单位，默认为 SECONDS 秒\n     */\n    TimeUnit timeUnit() default TimeUnit.SECONDS;\n\n    /**\n     * 提示信息，正在执行中的提示\n     */\n    String message() default \"重复请求，请稍后重试\";\n\n    /**\n     * 使用的 Key 解析器\n     *\n     * @see DefaultIdempotentKeyResolver 全局级别\n     * @see UserIdempotentKeyResolver 用户级别\n     * @see ExpressionIdempotentKeyResolver 自定义表达式，通过 {@link #keyArg()} 计算\n     */\n    Class<? extends IdempotentKeyResolver> keyResolver() default DefaultIdempotentKeyResolver.class;\n    /**\n     * 使用的 Key 参数\n     */\n    String keyArg() default \"\";\n\n    /**\n     * 删除 Key，当发生异常时候\n     *\n     * 问题：为什么发生异常时，需要删除 Key 呢？\n     * 回答：发生异常时，说明业务发生错误，此时需要删除 Key，避免下次请求无法正常执行。\n     *\n     * 问题：为什么不搞 deleteWhenSuccess 执行成功时，需要删除 Key 呢？\n     * 回答：这种情况下，本质上是分布式锁，推荐使用 @Lock4j 注解\n     */\n    boolean deleteKeyWhenException() default true;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-protection/src/main/java/co/yixiang/yshop/framework/idempotent/core/aop/IdempotentAspect.java",
    "content": "package co.yixiang.yshop.framework.idempotent.core.aop;\n\nimport co.yixiang.yshop.framework.common.exception.ServiceException;\nimport co.yixiang.yshop.framework.common.exception.enums.GlobalErrorCodeConstants;\nimport co.yixiang.yshop.framework.common.util.collection.CollectionUtils;\nimport co.yixiang.yshop.framework.idempotent.core.annotation.Idempotent;\nimport co.yixiang.yshop.framework.idempotent.core.keyresolver.IdempotentKeyResolver;\nimport co.yixiang.yshop.framework.idempotent.core.redis.IdempotentRedisDAO;\nimport lombok.extern.slf4j.Slf4j;\nimport org.aspectj.lang.ProceedingJoinPoint;\nimport org.aspectj.lang.annotation.Around;\nimport org.aspectj.lang.annotation.Aspect;\nimport org.springframework.util.Assert;\n\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * 拦截声明了 {@link Idempotent} 注解的方法，实现幂等操作\n *\n * @author yshop\n */\n@Aspect\n@Slf4j\npublic class IdempotentAspect {\n\n    /**\n     * IdempotentKeyResolver 集合\n     */\n    private final Map<Class<? extends IdempotentKeyResolver>, IdempotentKeyResolver> keyResolvers;\n\n    private final IdempotentRedisDAO idempotentRedisDAO;\n\n    public IdempotentAspect(List<IdempotentKeyResolver> keyResolvers, IdempotentRedisDAO idempotentRedisDAO) {\n        this.keyResolvers = CollectionUtils.convertMap(keyResolvers, IdempotentKeyResolver::getClass);\n        this.idempotentRedisDAO = idempotentRedisDAO;\n    }\n\n    @Around(value = \"@annotation(idempotent)\")\n    public Object aroundPointCut(ProceedingJoinPoint joinPoint, Idempotent idempotent) throws Throwable {\n        // 获得 IdempotentKeyResolver\n        IdempotentKeyResolver keyResolver = keyResolvers.get(idempotent.keyResolver());\n        Assert.notNull(keyResolver, \"找不到对应的 IdempotentKeyResolver\");\n        // 解析 Key\n        String key = keyResolver.resolver(joinPoint, idempotent);\n\n        // 1. 锁定 Key\n        boolean success = idempotentRedisDAO.setIfAbsent(key, idempotent.timeout(), idempotent.timeUnit());\n        // 锁定失败，抛出异常\n        if (!success) {\n            log.info(\"[aroundPointCut][方法({}) 参数({}) 存在重复请求]\", joinPoint.getSignature().toString(), joinPoint.getArgs());\n            throw new ServiceException(GlobalErrorCodeConstants.REPEATED_REQUESTS.getCode(), idempotent.message());\n        }\n\n        // 2. 执行逻辑\n        try {\n            return joinPoint.proceed();\n        } catch (Throwable throwable) {\n            // 3. 异常时，删除 Key\n            // 参考美团 GTIS 思路：https://tech.meituan.com/2016/09/29/distributed-system-mutually-exclusive-idempotence-cerberus-gtis.html\n            if (idempotent.deleteKeyWhenException()) {\n                idempotentRedisDAO.delete(key);\n            }\n            throw throwable;\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-protection/src/main/java/co/yixiang/yshop/framework/idempotent/core/keyresolver/IdempotentKeyResolver.java",
    "content": "package co.yixiang.yshop.framework.idempotent.core.keyresolver;\n\nimport co.yixiang.yshop.framework.idempotent.core.annotation.Idempotent;\nimport org.aspectj.lang.JoinPoint;\n\n/**\n * 幂等 Key 解析器接口\n *\n * @author yshop\n */\npublic interface IdempotentKeyResolver {\n\n    /**\n     * 解析一个 Key\n     *\n     * @param idempotent 幂等注解\n     * @param joinPoint  AOP 切面\n     * @return Key\n     */\n    String resolver(JoinPoint joinPoint, Idempotent idempotent);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-protection/src/main/java/co/yixiang/yshop/framework/idempotent/core/keyresolver/impl/DefaultIdempotentKeyResolver.java",
    "content": "package co.yixiang.yshop.framework.idempotent.core.keyresolver.impl;\n\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.crypto.SecureUtil;\nimport co.yixiang.yshop.framework.idempotent.core.annotation.Idempotent;\nimport co.yixiang.yshop.framework.idempotent.core.keyresolver.IdempotentKeyResolver;\nimport org.aspectj.lang.JoinPoint;\n\n/**\n * 默认（全局级别）幂等 Key 解析器，使用方法名 + 方法参数，组装成一个 Key\n *\n * 为了避免 Key 过长，使用 MD5 进行“压缩”\n *\n * @author yshop\n */\npublic class DefaultIdempotentKeyResolver implements IdempotentKeyResolver {\n\n    @Override\n    public String resolver(JoinPoint joinPoint, Idempotent idempotent) {\n        String methodName = joinPoint.getSignature().toString();\n        String argsStr = StrUtil.join(\",\", joinPoint.getArgs());\n        return SecureUtil.md5(methodName + argsStr);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-protection/src/main/java/co/yixiang/yshop/framework/idempotent/core/keyresolver/impl/ExpressionIdempotentKeyResolver.java",
    "content": "package co.yixiang.yshop.framework.idempotent.core.keyresolver.impl;\n\nimport cn.hutool.core.util.ArrayUtil;\nimport co.yixiang.yshop.framework.idempotent.core.annotation.Idempotent;\nimport co.yixiang.yshop.framework.idempotent.core.keyresolver.IdempotentKeyResolver;\nimport org.aspectj.lang.JoinPoint;\nimport org.aspectj.lang.reflect.MethodSignature;\nimport org.springframework.core.DefaultParameterNameDiscoverer;\nimport org.springframework.core.ParameterNameDiscoverer;\nimport org.springframework.expression.Expression;\nimport org.springframework.expression.ExpressionParser;\nimport org.springframework.expression.spel.standard.SpelExpressionParser;\nimport org.springframework.expression.spel.support.StandardEvaluationContext;\n\nimport java.lang.reflect.Method;\n\n/**\n * 基于 Spring EL 表达式，\n *\n * @author yshop\n */\npublic class ExpressionIdempotentKeyResolver implements IdempotentKeyResolver {\n\n    private final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();\n\n    private final ExpressionParser expressionParser = new SpelExpressionParser();\n\n    @Override\n    public String resolver(JoinPoint joinPoint, Idempotent idempotent) {\n        // 获得被拦截方法参数名列表\n        Method method = getMethod(joinPoint);\n        Object[] args = joinPoint.getArgs();\n        String[] parameterNames = this.parameterNameDiscoverer.getParameterNames(method);\n        // 准备 Spring EL 表达式解析的上下文\n        StandardEvaluationContext evaluationContext = new StandardEvaluationContext();\n        if (ArrayUtil.isNotEmpty(parameterNames)) {\n            for (int i = 0; i < parameterNames.length; i++) {\n                evaluationContext.setVariable(parameterNames[i], args[i]);\n            }\n        }\n\n        // 解析参数\n        Expression expression = expressionParser.parseExpression(idempotent.keyArg());\n        return expression.getValue(evaluationContext, String.class);\n    }\n\n    private static Method getMethod(JoinPoint point) {\n        // 处理，声明在类上的情况\n        MethodSignature signature = (MethodSignature) point.getSignature();\n        Method method = signature.getMethod();\n        if (!method.getDeclaringClass().isInterface()) {\n            return method;\n        }\n\n        // 处理，声明在接口上的情况\n        try {\n            return point.getTarget().getClass().getDeclaredMethod(\n                    point.getSignature().getName(), method.getParameterTypes());\n        } catch (NoSuchMethodException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-protection/src/main/java/co/yixiang/yshop/framework/idempotent/core/keyresolver/impl/UserIdempotentKeyResolver.java",
    "content": "package co.yixiang.yshop.framework.idempotent.core.keyresolver.impl;\n\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.crypto.SecureUtil;\nimport co.yixiang.yshop.framework.idempotent.core.annotation.Idempotent;\nimport co.yixiang.yshop.framework.idempotent.core.keyresolver.IdempotentKeyResolver;\nimport co.yixiang.yshop.framework.web.core.util.WebFrameworkUtils;\nimport org.aspectj.lang.JoinPoint;\n\n/**\n * 用户级别的幂等 Key 解析器，使用方法名 + 方法参数 + userId + userType，组装成一个 Key\n *\n * 为了避免 Key 过长，使用 MD5 进行“压缩”\n *\n * @author yshop\n */\npublic class UserIdempotentKeyResolver implements IdempotentKeyResolver {\n\n    @Override\n    public String resolver(JoinPoint joinPoint, Idempotent idempotent) {\n        String methodName = joinPoint.getSignature().toString();\n        String argsStr = StrUtil.join(\",\", joinPoint.getArgs());\n        Long userId = WebFrameworkUtils.getLoginUserId();\n        Integer userType = WebFrameworkUtils.getLoginUserType();\n        return SecureUtil.md5(methodName + argsStr + userId + userType);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-protection/src/main/java/co/yixiang/yshop/framework/idempotent/core/redis/IdempotentRedisDAO.java",
    "content": "package co.yixiang.yshop.framework.idempotent.core.redis;\n\nimport lombok.AllArgsConstructor;\nimport org.springframework.data.redis.core.StringRedisTemplate;\n\nimport java.util.concurrent.TimeUnit;\n\n/**\n * 幂等 Redis DAO\n *\n * @author yshop\n */\n@AllArgsConstructor\npublic class IdempotentRedisDAO {\n\n    /**\n     * 幂等操作\n     *\n     * KEY 格式：idempotent:%s // 参数为 uuid\n     * VALUE 格式：String\n     * 过期时间：不固定\n     */\n    private static final String IDEMPOTENT = \"idempotent:%s\";\n\n    private final StringRedisTemplate redisTemplate;\n\n    public Boolean setIfAbsent(String key, long timeout, TimeUnit timeUnit) {\n        String redisKey = formatKey(key);\n        return redisTemplate.opsForValue().setIfAbsent(redisKey, \"\", timeout, timeUnit);\n    }\n\n    public void delete(String key) {\n        String redisKey = formatKey(key);\n        redisTemplate.delete(redisKey);\n    }\n\n    private static String formatKey(String key) {\n        return String.format(IDEMPOTENT, key);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-protection/src/main/java/co/yixiang/yshop/framework/lock4j/config/YshopLock4jConfiguration.java",
    "content": "package co.yixiang.yshop.framework.lock4j.config;\n\nimport co.yixiang.yshop.framework.lock4j.core.DefaultLockFailureStrategy;\nimport com.baomidou.lock.spring.boot.autoconfigure.LockAutoConfiguration;\nimport org.springframework.boot.autoconfigure.AutoConfiguration;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnClass;\nimport org.springframework.context.annotation.Bean;\n\n@AutoConfiguration(before = LockAutoConfiguration.class)\n@ConditionalOnClass(name = \"com.baomidou.lock.annotation.Lock4j\")\npublic class YshopLock4jConfiguration {\n\n    @Bean\n    public DefaultLockFailureStrategy lockFailureStrategy() {\n        return new DefaultLockFailureStrategy();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-protection/src/main/java/co/yixiang/yshop/framework/lock4j/core/DefaultLockFailureStrategy.java",
    "content": "package co.yixiang.yshop.framework.lock4j.core;\n\nimport co.yixiang.yshop.framework.common.exception.ServiceException;\nimport co.yixiang.yshop.framework.common.exception.enums.GlobalErrorCodeConstants;\nimport com.baomidou.lock.LockFailureStrategy;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.lang.reflect.Method;\n\n/**\n * 自定义获取锁失败策略，抛出 {@link ServiceException} 异常\n */\n@Slf4j\npublic class DefaultLockFailureStrategy implements LockFailureStrategy {\n\n    @Override\n    public void onLockFailure(String key, Method method, Object[] arguments) {\n        log.debug(\"[onLockFailure][线程:{} 获取锁失败，key:{} 获取失败:{} ]\", Thread.currentThread().getName(), key, arguments);\n        throw new ServiceException(GlobalErrorCodeConstants.LOCKED);\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-protection/src/main/java/co/yixiang/yshop/framework/lock4j/core/Lock4jRedisKeyConstants.java",
    "content": "package co.yixiang.yshop.framework.lock4j.core;\n\n/**\n * Lock4j Redis Key 枚举类\n *\n * @author yshop\n */\npublic interface Lock4jRedisKeyConstants {\n\n    /**\n     * 分布式锁\n     *\n     * KEY 格式：lock4j:%s // 参数来自 DefaultLockKeyBuilder 类\n     * VALUE 数据格式：HASH // RLock.class：Redisson 的 Lock 锁，使用 Hash 数据结构\n     * 过期时间：不固定\n     */\n    String LOCK4J = \"lock4j:%s\";\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-protection/src/main/java/co/yixiang/yshop/framework/ratelimiter/config/YshopRateLimiterConfiguration.java",
    "content": "package co.yixiang.yshop.framework.ratelimiter.config;\n\nimport co.yixiang.yshop.framework.ratelimiter.core.aop.RateLimiterAspect;\nimport co.yixiang.yshop.framework.ratelimiter.core.keyresolver.RateLimiterKeyResolver;\nimport co.yixiang.yshop.framework.ratelimiter.core.keyresolver.impl.*;\nimport co.yixiang.yshop.framework.ratelimiter.core.redis.RateLimiterRedisDAO;\nimport co.yixiang.yshop.framework.redis.config.YshopRedisAutoConfiguration;\nimport org.redisson.api.RedissonClient;\nimport org.springframework.boot.autoconfigure.AutoConfiguration;\nimport org.springframework.context.annotation.Bean;\n\nimport java.util.List;\n\n@AutoConfiguration(after = YshopRedisAutoConfiguration.class)\npublic class YshopRateLimiterConfiguration {\n\n    @Bean\n    public RateLimiterAspect rateLimiterAspect(List<RateLimiterKeyResolver> keyResolvers, RateLimiterRedisDAO rateLimiterRedisDAO) {\n        return new RateLimiterAspect(keyResolvers, rateLimiterRedisDAO);\n    }\n\n    @Bean\n    @SuppressWarnings(\"SpringJavaInjectionPointsAutowiringInspection\")\n    public RateLimiterRedisDAO rateLimiterRedisDAO(RedissonClient redissonClient) {\n        return new RateLimiterRedisDAO(redissonClient);\n    }\n\n    // ========== 各种 RateLimiterRedisDAO Bean ==========\n\n    @Bean\n    public DefaultRateLimiterKeyResolver defaultRateLimiterKeyResolver() {\n        return new DefaultRateLimiterKeyResolver();\n    }\n\n    @Bean\n    public UserRateLimiterKeyResolver userRateLimiterKeyResolver() {\n        return new UserRateLimiterKeyResolver();\n    }\n\n    @Bean\n    public ClientIpRateLimiterKeyResolver clientIpRateLimiterKeyResolver() {\n        return new ClientIpRateLimiterKeyResolver();\n    }\n\n    @Bean\n    public ServerNodeRateLimiterKeyResolver serverNodeRateLimiterKeyResolver() {\n        return new ServerNodeRateLimiterKeyResolver();\n    }\n\n    @Bean\n    public ExpressionRateLimiterKeyResolver expressionRateLimiterKeyResolver() {\n        return new ExpressionRateLimiterKeyResolver();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-protection/src/main/java/co/yixiang/yshop/framework/ratelimiter/core/annotation/RateLimiter.java",
    "content": "package co.yixiang.yshop.framework.ratelimiter.core.annotation;\n\nimport co.yixiang.yshop.framework.common.exception.enums.GlobalErrorCodeConstants;\nimport co.yixiang.yshop.framework.idempotent.core.keyresolver.impl.ExpressionIdempotentKeyResolver;\nimport co.yixiang.yshop.framework.ratelimiter.core.keyresolver.RateLimiterKeyResolver;\nimport co.yixiang.yshop.framework.ratelimiter.core.keyresolver.impl.ClientIpRateLimiterKeyResolver;\nimport co.yixiang.yshop.framework.ratelimiter.core.keyresolver.impl.DefaultRateLimiterKeyResolver;\nimport co.yixiang.yshop.framework.ratelimiter.core.keyresolver.impl.ServerNodeRateLimiterKeyResolver;\nimport co.yixiang.yshop.framework.ratelimiter.core.keyresolver.impl.UserRateLimiterKeyResolver;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * 限流注解\n *\n * @author yshop\n */\n@Target({ElementType.METHOD})\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface RateLimiter {\n\n    /**\n     * 限流的时间，默认为 1 秒\n     */\n    int time() default 1;\n    /**\n     * 时间单位，默认为 SECONDS 秒\n     */\n    TimeUnit timeUnit() default TimeUnit.SECONDS;\n\n    /**\n     * 限流次数\n     */\n    int count() default 100;\n\n    /**\n     * 提示信息，请求过快的提示\n     *\n     * @see GlobalErrorCodeConstants#TOO_MANY_REQUESTS\n     */\n    String message() default \"\"; // 为空时，使用 TOO_MANY_REQUESTS 错误提示\n\n    /**\n     * 使用的 Key 解析器\n     *\n     * @see DefaultRateLimiterKeyResolver 全局级别\n     * @see UserRateLimiterKeyResolver 用户 ID 级别\n     * @see ClientIpRateLimiterKeyResolver 用户 IP 级别\n     * @see ServerNodeRateLimiterKeyResolver 服务器 Node 级别\n     * @see ExpressionIdempotentKeyResolver 自定义表达式，通过 {@link #keyArg()} 计算\n     */\n    Class<? extends RateLimiterKeyResolver> keyResolver() default DefaultRateLimiterKeyResolver.class;\n    /**\n     * 使用的 Key 参数\n     */\n    String keyArg() default \"\";\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-protection/src/main/java/co/yixiang/yshop/framework/ratelimiter/core/aop/RateLimiterAspect.java",
    "content": "package co.yixiang.yshop.framework.ratelimiter.core.aop;\n\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.exception.ServiceException;\nimport co.yixiang.yshop.framework.common.exception.enums.GlobalErrorCodeConstants;\nimport co.yixiang.yshop.framework.common.util.collection.CollectionUtils;\nimport co.yixiang.yshop.framework.ratelimiter.core.annotation.RateLimiter;\nimport co.yixiang.yshop.framework.ratelimiter.core.keyresolver.RateLimiterKeyResolver;\nimport co.yixiang.yshop.framework.ratelimiter.core.redis.RateLimiterRedisDAO;\nimport lombok.extern.slf4j.Slf4j;\nimport org.aspectj.lang.JoinPoint;\nimport org.aspectj.lang.annotation.Aspect;\nimport org.aspectj.lang.annotation.Before;\nimport org.springframework.util.Assert;\n\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * 拦截声明了 {@link RateLimiter} 注解的方法，实现限流操作\n *\n * @author yshop\n */\n@Aspect\n@Slf4j\npublic class RateLimiterAspect {\n\n    /**\n     * RateLimiterKeyResolver 集合\n     */\n    private final Map<Class<? extends RateLimiterKeyResolver>, RateLimiterKeyResolver> keyResolvers;\n\n    private final RateLimiterRedisDAO rateLimiterRedisDAO;\n\n    public RateLimiterAspect(List<RateLimiterKeyResolver> keyResolvers, RateLimiterRedisDAO rateLimiterRedisDAO) {\n        this.keyResolvers = CollectionUtils.convertMap(keyResolvers, RateLimiterKeyResolver::getClass);\n        this.rateLimiterRedisDAO = rateLimiterRedisDAO;\n    }\n\n    @Before(\"@annotation(rateLimiter)\")\n    public void beforePointCut(JoinPoint joinPoint, RateLimiter rateLimiter) {\n        // 获得 IdempotentKeyResolver 对象\n        RateLimiterKeyResolver keyResolver = keyResolvers.get(rateLimiter.keyResolver());\n        Assert.notNull(keyResolver, \"找不到对应的 RateLimiterKeyResolver\");\n        // 解析 Key\n        String key = keyResolver.resolver(joinPoint, rateLimiter);\n\n        // 获取 1 次限流\n        boolean success = rateLimiterRedisDAO.tryAcquire(key,\n                rateLimiter.count(), rateLimiter.time(), rateLimiter.timeUnit());\n        if (!success) {\n            log.info(\"[beforePointCut][方法({}) 参数({}) 请求过于频繁]\", joinPoint.getSignature().toString(), joinPoint.getArgs());\n            String message = StrUtil.blankToDefault(rateLimiter.message(),\n                    GlobalErrorCodeConstants.TOO_MANY_REQUESTS.getMsg());\n            throw new ServiceException(GlobalErrorCodeConstants.TOO_MANY_REQUESTS.getCode(), message);\n        }\n    }\n\n}\n\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-protection/src/main/java/co/yixiang/yshop/framework/ratelimiter/core/keyresolver/RateLimiterKeyResolver.java",
    "content": "package co.yixiang.yshop.framework.ratelimiter.core.keyresolver;\n\nimport co.yixiang.yshop.framework.ratelimiter.core.annotation.RateLimiter;\nimport org.aspectj.lang.JoinPoint;\n\n/**\n * 限流 Key 解析器接口\n *\n * @author yshop\n */\npublic interface RateLimiterKeyResolver {\n\n    /**\n     * 解析一个 Key\n     *\n     * @param rateLimiter 限流注解\n     * @param joinPoint  AOP 切面\n     * @return Key\n     */\n    String resolver(JoinPoint joinPoint, RateLimiter rateLimiter);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-protection/src/main/java/co/yixiang/yshop/framework/ratelimiter/core/keyresolver/impl/ClientIpRateLimiterKeyResolver.java",
    "content": "package co.yixiang.yshop.framework.ratelimiter.core.keyresolver.impl;\n\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.crypto.SecureUtil;\nimport co.yixiang.yshop.framework.common.util.servlet.ServletUtils;\nimport co.yixiang.yshop.framework.ratelimiter.core.annotation.RateLimiter;\nimport co.yixiang.yshop.framework.ratelimiter.core.keyresolver.RateLimiterKeyResolver;\nimport org.aspectj.lang.JoinPoint;\n\n/**\n * IP 级别的限流 Key 解析器，使用方法名 + 方法参数 + IP，组装成一个 Key\n *\n * 为了避免 Key 过长，使用 MD5 进行“压缩”\n *\n * @author yshop\n */\npublic class ClientIpRateLimiterKeyResolver implements RateLimiterKeyResolver {\n\n    @Override\n    public String resolver(JoinPoint joinPoint, RateLimiter rateLimiter) {\n        String methodName = joinPoint.getSignature().toString();\n        String argsStr = StrUtil.join(\",\", joinPoint.getArgs());\n        String clientIp = ServletUtils.getClientIP();\n        return SecureUtil.md5(methodName + argsStr + clientIp);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-protection/src/main/java/co/yixiang/yshop/framework/ratelimiter/core/keyresolver/impl/DefaultRateLimiterKeyResolver.java",
    "content": "package co.yixiang.yshop.framework.ratelimiter.core.keyresolver.impl;\n\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.crypto.SecureUtil;\nimport co.yixiang.yshop.framework.ratelimiter.core.annotation.RateLimiter;\nimport co.yixiang.yshop.framework.ratelimiter.core.keyresolver.RateLimiterKeyResolver;\nimport org.aspectj.lang.JoinPoint;\n\n/**\n * 默认（全局级别）限流 Key 解析器，使用方法名 + 方法参数，组装成一个 Key\n *\n * 为了避免 Key 过长，使用 MD5 进行“压缩”\n *\n * @author yshop\n */\npublic class DefaultRateLimiterKeyResolver implements RateLimiterKeyResolver {\n\n    @Override\n    public String resolver(JoinPoint joinPoint, RateLimiter rateLimiter) {\n        String methodName = joinPoint.getSignature().toString();\n        String argsStr = StrUtil.join(\",\", joinPoint.getArgs());\n        return SecureUtil.md5(methodName + argsStr);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-protection/src/main/java/co/yixiang/yshop/framework/ratelimiter/core/keyresolver/impl/ExpressionRateLimiterKeyResolver.java",
    "content": "package co.yixiang.yshop.framework.ratelimiter.core.keyresolver.impl;\n\nimport cn.hutool.core.util.ArrayUtil;\nimport co.yixiang.yshop.framework.ratelimiter.core.annotation.RateLimiter;\nimport co.yixiang.yshop.framework.ratelimiter.core.keyresolver.RateLimiterKeyResolver;\nimport org.aspectj.lang.JoinPoint;\nimport org.aspectj.lang.reflect.MethodSignature;\nimport org.springframework.core.DefaultParameterNameDiscoverer;\nimport org.springframework.core.ParameterNameDiscoverer;\nimport org.springframework.expression.Expression;\nimport org.springframework.expression.ExpressionParser;\nimport org.springframework.expression.spel.standard.SpelExpressionParser;\nimport org.springframework.expression.spel.support.StandardEvaluationContext;\n\nimport java.lang.reflect.Method;\n\n/**\n * 基于 Spring EL 表达式的 {@link RateLimiterKeyResolver} 实现类\n *\n * @author yshop\n */\npublic class ExpressionRateLimiterKeyResolver implements RateLimiterKeyResolver {\n\n    private final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();\n\n    private final ExpressionParser expressionParser = new SpelExpressionParser();\n\n    @Override\n    public String resolver(JoinPoint joinPoint, RateLimiter rateLimiter) {\n        // 获得被拦截方法参数名列表\n        Method method = getMethod(joinPoint);\n        Object[] args = joinPoint.getArgs();\n        String[] parameterNames = this.parameterNameDiscoverer.getParameterNames(method);\n        // 准备 Spring EL 表达式解析的上下文\n        StandardEvaluationContext evaluationContext = new StandardEvaluationContext();\n        if (ArrayUtil.isNotEmpty(parameterNames)) {\n            for (int i = 0; i < parameterNames.length; i++) {\n                evaluationContext.setVariable(parameterNames[i], args[i]);\n            }\n        }\n\n        // 解析参数\n        Expression expression = expressionParser.parseExpression(rateLimiter.keyArg());\n        return expression.getValue(evaluationContext, String.class);\n    }\n\n    private static Method getMethod(JoinPoint point) {\n        // 处理，声明在类上的情况\n        MethodSignature signature = (MethodSignature) point.getSignature();\n        Method method = signature.getMethod();\n        if (!method.getDeclaringClass().isInterface()) {\n            return method;\n        }\n\n        // 处理，声明在接口上的情况\n        try {\n            return point.getTarget().getClass().getDeclaredMethod(\n                    point.getSignature().getName(), method.getParameterTypes());\n        } catch (NoSuchMethodException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-protection/src/main/java/co/yixiang/yshop/framework/ratelimiter/core/keyresolver/impl/ServerNodeRateLimiterKeyResolver.java",
    "content": "package co.yixiang.yshop.framework.ratelimiter.core.keyresolver.impl;\n\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.crypto.SecureUtil;\nimport cn.hutool.system.SystemUtil;\nimport co.yixiang.yshop.framework.ratelimiter.core.annotation.RateLimiter;\nimport co.yixiang.yshop.framework.ratelimiter.core.keyresolver.RateLimiterKeyResolver;\nimport org.aspectj.lang.JoinPoint;\n\n/**\n * Server 节点级别的限流 Key 解析器，使用方法名 + 方法参数 + IP，组装成一个 Key\n *\n * 为了避免 Key 过长，使用 MD5 进行“压缩”\n *\n * @author yshop\n */\npublic class ServerNodeRateLimiterKeyResolver implements RateLimiterKeyResolver {\n\n    @Override\n    public String resolver(JoinPoint joinPoint, RateLimiter rateLimiter) {\n        String methodName = joinPoint.getSignature().toString();\n        String argsStr = StrUtil.join(\",\", joinPoint.getArgs());\n        String serverNode = String.format(\"%s@%d\", SystemUtil.getHostInfo().getAddress(), SystemUtil.getCurrentPID());\n        return SecureUtil.md5(methodName + argsStr + serverNode);\n    }\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-protection/src/main/java/co/yixiang/yshop/framework/ratelimiter/core/keyresolver/impl/UserRateLimiterKeyResolver.java",
    "content": "package co.yixiang.yshop.framework.ratelimiter.core.keyresolver.impl;\n\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.crypto.SecureUtil;\nimport co.yixiang.yshop.framework.ratelimiter.core.annotation.RateLimiter;\nimport co.yixiang.yshop.framework.ratelimiter.core.keyresolver.RateLimiterKeyResolver;\nimport co.yixiang.yshop.framework.web.core.util.WebFrameworkUtils;\nimport org.aspectj.lang.JoinPoint;\n\n/**\n * 用户级别的限流 Key 解析器，使用方法名 + 方法参数 + userId + userType，组装成一个 Key\n *\n * 为了避免 Key 过长，使用 MD5 进行“压缩”\n *\n * @author yshop\n */\npublic class UserRateLimiterKeyResolver implements RateLimiterKeyResolver {\n\n    @Override\n    public String resolver(JoinPoint joinPoint, RateLimiter rateLimiter) {\n        String methodName = joinPoint.getSignature().toString();\n        String argsStr = StrUtil.join(\",\", joinPoint.getArgs());\n        Long userId = WebFrameworkUtils.getLoginUserId();\n        Integer userType = WebFrameworkUtils.getLoginUserType();\n        return SecureUtil.md5(methodName + argsStr + userId + userType);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-protection/src/main/java/co/yixiang/yshop/framework/ratelimiter/core/redis/RateLimiterRedisDAO.java",
    "content": "package co.yixiang.yshop.framework.ratelimiter.core.redis;\n\nimport lombok.AllArgsConstructor;\nimport org.redisson.api.*;\n\nimport java.util.Objects;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * 限流 Redis DAO\n *\n * @author yshop\n */\n@AllArgsConstructor\npublic class RateLimiterRedisDAO {\n\n    /**\n     * 限流操作\n     *\n     * KEY 格式：rate_limiter:%s // 参数为 uuid\n     * VALUE 格式：String\n     * 过期时间：不固定\n     */\n    private static final String RATE_LIMITER = \"rate_limiter:%s\";\n\n    private final RedissonClient redissonClient;\n\n    public Boolean tryAcquire(String key, int count, int time, TimeUnit timeUnit) {\n        // 1. 获得 RRateLimiter，并设置 rate 速率\n        RRateLimiter rateLimiter = getRRateLimiter(key, count, time, timeUnit);\n        // 2. 尝试获取 1 个\n        return rateLimiter.tryAcquire();\n    }\n\n    private static String formatKey(String key) {\n        return String.format(RATE_LIMITER, key);\n    }\n\n    private RRateLimiter getRRateLimiter(String key, long count, int time, TimeUnit timeUnit) {\n        String redisKey = formatKey(key);\n        RRateLimiter rateLimiter = redissonClient.getRateLimiter(redisKey);\n        long rateInterval = timeUnit.toSeconds(time);\n        // 1. 如果不存在，设置 rate 速率\n        RateLimiterConfig config = rateLimiter.getConfig();\n        if (config == null) {\n            rateLimiter.trySetRate(RateType.OVERALL, count, rateInterval, RateIntervalUnit.SECONDS);\n            return rateLimiter;\n        }\n        // 2. 如果存在，并且配置相同，则直接返回\n        if (config.getRateType() == RateType.OVERALL\n                && Objects.equals(config.getRate(), count)\n                && Objects.equals(config.getRateInterval(), TimeUnit.SECONDS.toMillis(rateInterval))) {\n            return rateLimiter;\n        }\n        // 3. 如果存在，并且配置不同，则进行新建\n        rateLimiter.setRate(RateType.OVERALL, count, rateInterval, RateIntervalUnit.SECONDS);\n        return rateLimiter;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-protection/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports",
    "content": "co.yixiang.yshop.framework.idempotent.config.YshopIdempotentConfiguration\nco.yixiang.yshop.framework.lock4j.config.YshopLock4jConfiguration\nco.yixiang.yshop.framework.ratelimiter.config.YshopRateLimiterConfiguration"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-redis/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>co.yixiang.boot</groupId>\n        <artifactId>yshop-framework</artifactId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>yshop-spring-boot-starter-redis</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>Redis 封装拓展</description>\n    <url>https://gitee.com/guchengwuyue/yshop-drink</url>\n\n    <dependencies>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-common</artifactId>\n        </dependency>\n\n        <!-- DB 相关 -->\n        <dependency>\n            <groupId>org.redisson</groupId>\n            <artifactId>redisson-spring-boot-starter</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-cache</artifactId> <!-- 实现对 Caches 的自动化配置 -->\n        </dependency>\n\n        <dependency>\n            <groupId>com.fasterxml.jackson.datatype</groupId>\n            <artifactId>jackson-datatype-jsr310</artifactId>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-redis/src/main/java/co/yixiang/yshop/framework/redis/config/RedissonConfig.java",
    "content": "//package co.yixiang.yshop.framework.redis.config;\n//\n//\n//import cn.hutool.core.util.StrUtil;\n//import org.redisson.Redisson;\n//import org.redisson.api.RedissonClient;\n//import org.redisson.config.Config;\n//import org.springframework.beans.factory.annotation.Value;\n//import org.springframework.context.annotation.Bean;\n//import org.springframework.context.annotation.Configuration;\n//\n//@Configuration\n//public class RedissonConfig {\n//    @Value(\"${spring.data.redis.host}\")\n//    private String host;\n//\n//    @Value(\"${spring.data.redis.port}\")\n//    private String port;\n//\n//    @Value(\"${spring.data.redis.password}\")\n//    private String password;\n//\n//    @Value(\"${spring.data.redis.database}\")\n//    private Integer database;\n//\n//\n//\n//    @Bean(destroyMethod = \"shutdown\")\n//    public RedissonClient redissonClient() {\n//        Config config = new Config();\n//        config.useSingleServer()\n//                .setDatabase(database)\n//                .setAddress(\"redis://\"+host+\":\"+port);\n//\n//                //.setPassword(password);\n//        if(StrUtil.isNotEmpty(password)){\n//            config.useSingleServer().setPassword(password);\n//        }\n//\n//        return Redisson.create(config);\n//    }\n//\n//\n//}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-redis/src/main/java/co/yixiang/yshop/framework/redis/config/YshopCacheAutoConfiguration.java",
    "content": "package co.yixiang.yshop.framework.redis.config;\n\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.redis.core.TimeoutRedisCacheManager;\nimport org.springframework.boot.autoconfigure.AutoConfiguration;\nimport org.springframework.boot.autoconfigure.cache.CacheProperties;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.cache.annotation.EnableCaching;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Primary;\nimport org.springframework.data.redis.cache.BatchStrategies;\nimport org.springframework.data.redis.cache.RedisCacheConfiguration;\nimport org.springframework.data.redis.cache.RedisCacheManager;\nimport org.springframework.data.redis.cache.RedisCacheWriter;\nimport org.springframework.data.redis.connection.RedisConnectionFactory;\nimport org.springframework.data.redis.core.RedisTemplate;\nimport org.springframework.data.redis.serializer.RedisSerializationContext;\nimport org.springframework.util.StringUtils;\n\nimport java.util.Objects;\n\nimport static co.yixiang.yshop.framework.redis.config.YshopRedisAutoConfiguration.buildRedisSerializer;\n\n/**\n * Cache 配置类，基于 Redis 实现\n */\n@AutoConfiguration\n@EnableConfigurationProperties({CacheProperties.class, YshopCacheProperties.class})\n@EnableCaching\npublic class YshopCacheAutoConfiguration {\n\n    /**\n     * RedisCacheConfiguration Bean\n     * <p>\n     * 参考 org.springframework.boot.autoconfigure.cache.RedisCacheConfiguration 的 createConfiguration 方法\n     */\n    @Bean\n    @Primary\n    public RedisCacheConfiguration redisCacheConfiguration(CacheProperties cacheProperties) {\n        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();\n        // 设置使用 : 单冒号，而不是双 :: 冒号，避免 Redis Desktop Manager 多余空格\n        // 详细可见 https://blog.csdn.net/chuixue24/article/details/103928965 博客\n        // 再次修复单冒号，而不是双 :: 冒号问题，Issues 详情：https://gitee.com/zhijiantianya/yshop-cloud/issues/I86VY2\n        config = config.computePrefixWith(cacheName -> {\n            String keyPrefix = cacheProperties.getRedis().getKeyPrefix();\n            if (StringUtils.hasText(keyPrefix)) {\n                keyPrefix = keyPrefix.lastIndexOf(StrUtil.COLON) == -1 ? keyPrefix + StrUtil.COLON : keyPrefix;\n                return keyPrefix + cacheName + StrUtil.COLON;\n            }\n            return cacheName + StrUtil.COLON;\n        });\n        // 设置使用 JSON 序列化方式\n        config = config.serializeValuesWith(\n                RedisSerializationContext.SerializationPair.fromSerializer(buildRedisSerializer()));\n\n        // 设置 CacheProperties.Redis 的属性\n        CacheProperties.Redis redisProperties = cacheProperties.getRedis();\n        if (redisProperties.getTimeToLive() != null) {\n            config = config.entryTtl(redisProperties.getTimeToLive());\n        }\n        if (!redisProperties.isCacheNullValues()) {\n            config = config.disableCachingNullValues();\n        }\n        if (!redisProperties.isUseKeyPrefix()) {\n            config = config.disableKeyPrefix();\n        }\n        return config;\n    }\n\n    @Bean\n    public RedisCacheManager redisCacheManager(RedisTemplate<String, Object> redisTemplate,\n                                               RedisCacheConfiguration redisCacheConfiguration,\n                                               YshopCacheProperties yshopCacheProperties) {\n        // 创建 RedisCacheWriter 对象\n        RedisConnectionFactory connectionFactory = Objects.requireNonNull(redisTemplate.getConnectionFactory());\n        RedisCacheWriter cacheWriter = RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory,\n                BatchStrategies.scan(yshopCacheProperties.getRedisScanBatchSize()));\n        // 创建 TenantRedisCacheManager 对象\n        return new TimeoutRedisCacheManager(cacheWriter, redisCacheConfiguration);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-redis/src/main/java/co/yixiang/yshop/framework/redis/config/YshopCacheProperties.java",
    "content": "package co.yixiang.yshop.framework.redis.config;\n\nimport lombok.Data;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.validation.annotation.Validated;\n\n/**\n * Cache 配置项\n *\n * @author Wanwan\n */\n@ConfigurationProperties(\"yshop.cache\")\n@Data\n@Validated\npublic class YshopCacheProperties {\n\n    /**\n     * {@link #redisScanBatchSize} 默认值\n     */\n    private static final Integer REDIS_SCAN_BATCH_SIZE_DEFAULT = 30;\n\n    /**\n     * redis scan 一次返回数量\n     */\n    private Integer redisScanBatchSize = REDIS_SCAN_BATCH_SIZE_DEFAULT;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-redis/src/main/java/co/yixiang/yshop/framework/redis/config/YshopRedisAutoConfiguration.java",
    "content": "package co.yixiang.yshop.framework.redis.config;\n\nimport cn.hutool.core.util.ReflectUtil;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;\nimport org.redisson.spring.starter.RedissonAutoConfigurationV2;\nimport org.springframework.boot.autoconfigure.AutoConfiguration;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.data.redis.connection.RedisConnectionFactory;\nimport org.springframework.data.redis.core.RedisTemplate;\nimport org.springframework.data.redis.serializer.RedisSerializer;\n\n/**\n * Redis 配置类\n */\n@AutoConfiguration(before = RedissonAutoConfigurationV2.class) // 目的：使用自己定义的 RedisTemplate Bean\npublic class YshopRedisAutoConfiguration {\n\n    /**\n     * 创建 RedisTemplate Bean，使用 JSON 序列化方式\n     */\n    @Bean\n    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {\n        // 创建 RedisTemplate 对象\n        RedisTemplate<String, Object> template = new RedisTemplate<>();\n        // 设置 RedisConnection 工厂。😈 它就是实现多种 Java Redis 客户端接入的秘密工厂。感兴趣的胖友，可以自己去撸下。\n        template.setConnectionFactory(factory);\n        // 使用 String 序列化方式，序列化 KEY 。\n        template.setKeySerializer(RedisSerializer.string());\n        template.setHashKeySerializer(RedisSerializer.string());\n        // 使用 JSON 序列化方式（库是 Jackson ），序列化 VALUE 。\n        template.setValueSerializer(buildRedisSerializer());\n        template.setHashValueSerializer(buildRedisSerializer());\n        return template;\n    }\n\n    public static RedisSerializer<?> buildRedisSerializer() {\n        RedisSerializer<Object> json = RedisSerializer.json();\n        // 解决 LocalDateTime 的序列化\n        ObjectMapper objectMapper = (ObjectMapper) ReflectUtil.getFieldValue(json, \"mapper\");\n        objectMapper.registerModules(new JavaTimeModule());\n        return json;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-redis/src/main/java/co/yixiang/yshop/framework/redis/core/RedisKeyDefine.java",
    "content": "package co.yixiang.yshop.framework.redis.core;\n\nimport com.fasterxml.jackson.annotation.JsonValue;\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.Getter;\n\nimport java.time.Duration;\n\n/**\n * Redis Key 定义类\n *\n * @author yshop\n */\n@Data\npublic class RedisKeyDefine {\n\n    @Getter\n    @AllArgsConstructor\n    public enum KeyTypeEnum {\n\n        STRING(\"String\"),\n        LIST(\"List\"),\n        HASH(\"Hash\"),\n        SET(\"Set\"),\n        ZSET(\"Sorted Set\"),\n        STREAM(\"Stream\"),\n        PUBSUB(\"Pub/Sub\");\n\n        /**\n         * 类型\n         */\n        @JsonValue\n        private final String type;\n\n    }\n\n    @Getter\n    @AllArgsConstructor\n    public enum TimeoutTypeEnum {\n\n        FOREVER(1), // 永不超时\n        DYNAMIC(2), // 动态超时\n        FIXED(3); // 固定超时\n\n        /**\n         * 类型\n         */\n        @JsonValue\n        private final Integer type;\n\n    }\n\n    /**\n     * Key 模板\n     */\n    private final String keyTemplate;\n    /**\n     * Key 类型的枚举\n     */\n    private final KeyTypeEnum keyType;\n    /**\n     * Value 类型\n     *\n     * 如果是使用分布式锁，设置为 {@link java.util.concurrent.locks.Lock} 类型\n     */\n    private final Class<?> valueType;\n    /**\n     * 超时类型\n     */\n    private final TimeoutTypeEnum timeoutType;\n    /**\n     * 过期时间\n     */\n    private final Duration timeout;\n    /**\n     * 备注\n     */\n    private final String memo;\n\n    private RedisKeyDefine(String memo, String keyTemplate, KeyTypeEnum keyType, Class<?> valueType,\n                           TimeoutTypeEnum timeoutType, Duration timeout) {\n        this.memo = memo;\n        this.keyTemplate = keyTemplate;\n        this.keyType = keyType;\n        this.valueType = valueType;\n        this.timeout = timeout;\n        this.timeoutType = timeoutType;\n        // 添加注册表\n        RedisKeyRegistry.add(this);\n    }\n\n    public RedisKeyDefine(String memo, String keyTemplate, KeyTypeEnum keyType, Class<?> valueType, Duration timeout) {\n        this(memo, keyTemplate, keyType, valueType, TimeoutTypeEnum.FIXED, timeout);\n    }\n\n    public RedisKeyDefine(String memo, String keyTemplate, KeyTypeEnum keyType, Class<?> valueType, TimeoutTypeEnum timeoutType) {\n        this(memo, keyTemplate, keyType, valueType, timeoutType, Duration.ZERO);\n    }\n\n    /**\n     * 格式化 Key\n     *\n     * 注意，内部采用 {@link String#format(String, Object...)} 实现\n     *\n     * @param args 格式化的参数\n     * @return Key\n     */\n    public String formatKey(Object... args) {\n        return String.format(keyTemplate, args);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-redis/src/main/java/co/yixiang/yshop/framework/redis/core/RedisKeyRegistry.java",
    "content": "package co.yixiang.yshop.framework.redis.core;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * {@link RedisKeyDefine} 注册表\n */\npublic class RedisKeyRegistry {\n\n    /**\n     * Redis RedisKeyDefine 数组\n     */\n    private static final List<RedisKeyDefine> DEFINES = new ArrayList<>();\n\n    public static void add(RedisKeyDefine define) {\n        DEFINES.add(define);\n    }\n\n    public static List<RedisKeyDefine> list() {\n        return DEFINES;\n    }\n\n    public static int size() {\n        return DEFINES.size();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-redis/src/main/java/co/yixiang/yshop/framework/redis/core/TimeoutRedisCacheManager.java",
    "content": "package co.yixiang.yshop.framework.redis.core;\n\nimport cn.hutool.core.util.NumberUtil;\nimport cn.hutool.core.util.StrUtil;\nimport org.springframework.cache.annotation.Cacheable;\nimport org.springframework.data.redis.cache.RedisCache;\nimport org.springframework.data.redis.cache.RedisCacheConfiguration;\nimport org.springframework.data.redis.cache.RedisCacheManager;\nimport org.springframework.data.redis.cache.RedisCacheWriter;\n\nimport java.time.Duration;\n\n/**\n * 支持自定义过期时间的 {@link RedisCacheManager} 实现类\n *\n * 在 {@link Cacheable#cacheNames()} 格式为 \"key#ttl\" 时，# 后面的 ttl 为过期时间。\n * 单位为最后一个字母（支持的单位有：d 天，h 小时，m 分钟，s 秒），默认单位为 s 秒\n *\n * @author yshop\n */\npublic class TimeoutRedisCacheManager extends RedisCacheManager {\n\n    private static final String SPLIT = \"#\";\n\n    public TimeoutRedisCacheManager(RedisCacheWriter cacheWriter, RedisCacheConfiguration defaultCacheConfiguration) {\n        super(cacheWriter, defaultCacheConfiguration);\n    }\n\n    @Override\n    protected RedisCache createRedisCache(String name, RedisCacheConfiguration cacheConfig) {\n        if (StrUtil.isEmpty(name)) {\n            return super.createRedisCache(name, cacheConfig);\n        }\n        // 如果使用 # 分隔，大小不为 2，则说明不使用自定义过期时间\n        String[] names = StrUtil.splitToArray(name, SPLIT);\n        if (names.length != 2) {\n            return super.createRedisCache(name, cacheConfig);\n        }\n\n        // 核心：通过修改 cacheConfig 的过期时间，实现自定义过期时间\n        if (cacheConfig != null) {\n            // 移除 # 后面的 : 以及后面的内容，避免影响解析\n            String ttlStr = StrUtil.subBefore(names[1], StrUtil.COLON, false); // 获得 ttlStr 时间部分\n            names[1] = StrUtil.subAfter(names[1], ttlStr, false); // 移除掉 ttlStr 时间部分\n            // 解析时间\n            Duration duration = parseDuration(ttlStr);\n            cacheConfig = cacheConfig.entryTtl(duration);\n        }\n\n        // 创建 RedisCache 对象，需要忽略掉 ttlStr\n        return super.createRedisCache(names[0] + names[1], cacheConfig);\n    }\n\n    /**\n     * 解析过期时间 Duration\n     *\n     * @param ttlStr 过期时间字符串\n     * @return 过期时间 Duration\n     */\n    private Duration parseDuration(String ttlStr) {\n        String timeUnit = StrUtil.subSuf(ttlStr, -1);\n        switch (timeUnit) {\n            case \"d\":\n                return Duration.ofDays(removeDurationSuffix(ttlStr));\n            case \"h\":\n                return Duration.ofHours(removeDurationSuffix(ttlStr));\n            case \"m\":\n                return Duration.ofMinutes(removeDurationSuffix(ttlStr));\n            case \"s\":\n                return Duration.ofSeconds(removeDurationSuffix(ttlStr));\n            default:\n                return Duration.ofSeconds(Long.parseLong(ttlStr));\n        }\n    }\n\n    /**\n     * 移除多余的后缀，返回具体的时间\n     *\n     * @param ttlStr 过期时间字符串\n     * @return 时间\n     */\n    private Long removeDurationSuffix(String ttlStr) {\n        return NumberUtil.parseLong(StrUtil.sub(ttlStr, 0, ttlStr.length() - 1));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-redis/src/main/java/co/yixiang/yshop/framework/redis/util/redis/RedisUtil.java",
    "content": "/**\n * Copyright (C) 2018-2022\n * All rights reserved, Designed By www.yixiang.co\n\n */\npackage co.yixiang.yshop.framework.redis.util.redis;\n\nimport cn.hutool.extra.spring.SpringUtil;\nimport org.springframework.data.redis.core.RedisTemplate;\nimport org.springframework.util.CollectionUtils;\n\nimport java.util.Collection;\nimport java.util.concurrent.TimeUnit;\n\n@SuppressWarnings(\"unchecked\")\npublic class RedisUtil {\n//    private static RedisTemplate<String,Object> redisTemplate = SpringContextUtils\n//            .getBean(\"redisTemplate\",RedisTemplate.class);\n\n    private static RedisTemplate<String,Object> redisTemplate =  SpringUtil\n            .getBean(\"redisTemplate\",RedisTemplate.class);\n\n    /**\n     * 指定缓存失效时间\n     * @param key 键\n     * @param time 时间(秒)\n     * @return\n     */\n    public static boolean expire(String key,long time){\n\n        try {\n            if(time>0){\n                redisTemplate.expire(key, time, TimeUnit.SECONDS);\n            }\n            return true;\n        } catch (Exception e) {\n            e.printStackTrace();\n            return false;\n        }\n    }\n\n    /**\n     * 根据key 获取过期时间\n     * @param key 键 不能为null\n     * @return 时间(秒) 返回0代表为永久有效 失效时间为负数，说明该主键未设置失效时间（失效时间默认为-1）\n     */\n    public static long getExpire(String key){\n        return redisTemplate.getExpire(key,TimeUnit.SECONDS);\n    }\n\n    /**\n     * 判断key是否存在\n     * @param key 键\n     * @return true 存在 false 不存在\n     */\n    public static boolean hasKey(String key){\n        try {\n            return redisTemplate.hasKey(key);\n        } catch (Exception e) {\n            e.printStackTrace();\n            return false;\n        }\n    }\n\n    /**\n     * 删除缓存\n     * @param key 可以传一个值 或多个\n     */\n    @SuppressWarnings(\"unchecked\")\n    public static void del(String ... key){\n        if(key!=null&&key.length>0){\n            if(key.length==1){\n                redisTemplate.delete(key[0]);\n            }else{\n                redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(key));\n            }\n        }\n    }\n\n\n    /**\n     * 普通缓存获取\n     * @param key 键\n     * @return 值\n     */\n    @SuppressWarnings(\"unchecked\")\n    public static <T> T get(String key){\n        return key==null?null:(T)redisTemplate.opsForValue().get(key);\n    }\n\n    /**\n     * 普通缓存放入\n     * @param key 键\n     * @param value 值\n     * @return true成功 false失败\n     */\n    public static boolean set(String key,Object value) {\n        try {\n            redisTemplate.opsForValue().set(key, value);\n            return true;\n        } catch (Exception e) {\n            e.printStackTrace();\n            return false;\n        }\n\n    }\n\n    /**\n     * 普通缓存放入并设置时间\n     * @param key 键\n     * @param value 值\n     * @param time 时间(秒) time要大于0 如果time小于等于0 将设置无限期\n     * @return true成功 false 失败\n     */\n    public static boolean set(String key,Object value,long time){\n        try {\n            if(time>0){\n                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);\n            }else{\n                set(key, value);\n            }\n            return true;\n        } catch (Exception e) {\n            e.printStackTrace();\n            return false;\n        }\n    }\n\n    /**\n     * 递增 此时value值必须为int类型 否则报错\n     * @param key 键\n     * @param delta 要增加几(大于0)\n     * @return\n     */\n    public static long incr(String key, long delta){\n        if(delta<0){\n            throw new RuntimeException(\"递增因子必须大于0\");\n        }\n        return redisTemplate.opsForValue().increment(key, delta);\n    }\n\n    /**\n     * 递减\n     * @param key 键\n     * @param delta 要减少几(小于0)\n     * @return\n     */\n    public static long decr(String key, long delta){\n        if(delta<0){\n            throw new RuntimeException(\"递减因子必须大于0\");\n        }\n        return redisTemplate.opsForValue().increment(key, -delta);\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-redis/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports",
    "content": "co.yixiang.yshop.framework.redis.config.YshopRedisAutoConfiguration\nco.yixiang.yshop.framework.redis.config.YshopCacheAutoConfiguration"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-security/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>co.yixiang.boot</groupId>\n        <artifactId>yshop-framework</artifactId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>yshop-spring-boot-starter-security</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>\n        1. security：用户的认证、权限的校验，实现「谁」可以做「什么事」\n        2. operatelog：操作日志，实现「谁」在「什么时间」对「什么」做了「什么事」\n    </description>\n    <url>https://gitee.com/guchengwuyue/yshop-drink</url>\n\n    <dependencies>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-common</artifactId>\n        </dependency>\n\n        <!-- Spring 核心 -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-aop</artifactId>\n        </dependency>\n\n        <!-- Web 相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-web</artifactId>\n        </dependency>\n        <!-- spring boot 配置所需依赖 -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-configuration-processor</artifactId>\n            <optional>true</optional>\n        </dependency>\n\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-security</artifactId>\n        </dependency>\n\n        <!-- 工具类相关 -->\n        <dependency>\n            <groupId>com.google.guava</groupId>\n            <artifactId>guava</artifactId>\n        </dependency>\n\n        <dependency>\n            <!-- Spring Boot 通用操作日志组件，基于注解实现 -->\n            <!-- 此组件解决的问题是：「谁」在「什么时间」对「什么」做了「什么事」 -->\n            <groupId>io.github.mouzt</groupId>\n            <artifactId>bizlog-sdk</artifactId>\n        </dependency>\n\n        <!-- 业务组件 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-system-api</artifactId> <!-- 需要使用它，进行 Token 的校验 -->\n            <version>${revision}</version>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-security/src/main/java/co/yixiang/yshop/framework/operatelog/config/YshopOperateLogConfiguration.java",
    "content": "package co.yixiang.yshop.framework.operatelog.config;\n\nimport co.yixiang.yshop.framework.operatelog.core.service.LogRecordServiceImpl;\nimport com.mzt.logapi.service.ILogRecordService;\nimport com.mzt.logapi.starter.annotation.EnableLogRecord;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.boot.autoconfigure.AutoConfiguration;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Primary;\n\n/**\n * 操作日志配置类\n *\n * @author HUIHUI\n */\n@EnableLogRecord(tenant = \"\") // 貌似用不上 tenant 这玩意给个空好啦\n@AutoConfiguration\n@Slf4j\npublic class YshopOperateLogConfiguration {\n\n    @Bean\n    @Primary\n    public ILogRecordService iLogRecordServiceImpl() {\n        return new LogRecordServiceImpl();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-security/src/main/java/co/yixiang/yshop/framework/operatelog/core/service/LogRecordServiceImpl.java",
    "content": "package co.yixiang.yshop.framework.operatelog.core.service;\n\nimport co.yixiang.yshop.framework.common.util.monitor.TracerUtils;\nimport co.yixiang.yshop.framework.common.util.servlet.ServletUtils;\nimport co.yixiang.yshop.framework.security.core.LoginUser;\nimport co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils;\nimport co.yixiang.yshop.module.system.api.logger.OperateLogApi;\nimport co.yixiang.yshop.module.system.api.logger.dto.OperateLogCreateReqDTO;\nimport com.mzt.logapi.beans.LogRecord;\nimport com.mzt.logapi.service.ILogRecordService;\nimport jakarta.annotation.Resource;\nimport jakarta.servlet.http.HttpServletRequest;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.List;\n\n/**\n * 操作日志 ILogRecordService 实现类\n *\n * 基于 {@link OperateLogApi} 实现，记录操作日志\n *\n * @author HUIHUI\n */\n@Slf4j\npublic class LogRecordServiceImpl implements ILogRecordService {\n\n    @Resource\n    private OperateLogApi operateLogApi;\n\n    @Override\n    public void record(LogRecord logRecord) {\n        // 1. 补全通用字段\n        OperateLogCreateReqDTO reqDTO = new OperateLogCreateReqDTO();\n        reqDTO.setTraceId(TracerUtils.getTraceId());\n        // 补充用户信息\n        fillUserFields(reqDTO);\n        // 补全模块信息\n        fillModuleFields(reqDTO, logRecord);\n        // 补全请求信息\n        fillRequestFields(reqDTO);\n\n        // 2. 异步记录日志\n        operateLogApi.createOperateLog(reqDTO);\n    }\n\n    private static void fillUserFields(OperateLogCreateReqDTO reqDTO) {\n        // 使用 SecurityFrameworkUtils。因为要考虑，rpc、mq、job，它其实不是 web；\n        LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();\n        if (loginUser == null) {\n            return;\n        }\n        reqDTO.setUserId(loginUser.getId());\n        reqDTO.setUserType(loginUser.getUserType());\n    }\n\n    public static void fillModuleFields(OperateLogCreateReqDTO reqDTO, LogRecord logRecord) {\n        reqDTO.setType(logRecord.getType()); // 大模块类型，例如：CRM 客户\n        reqDTO.setSubType(logRecord.getSubType());// 操作名称，例如：转移客户\n        reqDTO.setBizId(Long.parseLong(logRecord.getBizNo())); // 业务编号，例如：客户编号\n        reqDTO.setAction(logRecord.getAction());// 操作内容，例如：修改编号为 1 的用户信息，将性别从男改成女，将姓名从yshop改成源码。\n        reqDTO.setExtra(logRecord.getExtra()); // 拓展字段，有些复杂的业务，需要记录一些字段 ( JSON 格式 )，例如说，记录订单编号，{ orderId: \"1\"}\n    }\n\n    private static void fillRequestFields(OperateLogCreateReqDTO reqDTO) {\n        // 获得 Request 对象\n        HttpServletRequest request = ServletUtils.getRequest();\n        if (request == null) {\n            return;\n        }\n        // 补全请求信息\n        reqDTO.setRequestMethod(request.getMethod());\n        reqDTO.setRequestUrl(request.getRequestURI());\n        reqDTO.setUserIp(ServletUtils.getClientIP(request));\n        reqDTO.setUserAgent(ServletUtils.getUserAgent(request));\n    }\n\n    @Override\n    public List<LogRecord> queryLog(String bizNo, String type) {\n        throw new UnsupportedOperationException(\"使用 OperateLogApi 进行操作日志的查询\");\n    }\n\n    @Override\n    public List<LogRecord> queryLogByBizNo(String bizNo, String type, String subType) {\n        throw new UnsupportedOperationException(\"使用 OperateLogApi 进行操作日志的查询\");\n    }\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-security/src/main/java/co/yixiang/yshop/framework/security/config/AuthorizeRequestsCustomizer.java",
    "content": "package co.yixiang.yshop.framework.security.config;\n\nimport co.yixiang.yshop.framework.web.config.WebProperties;\nimport jakarta.annotation.Resource;\nimport org.springframework.core.Ordered;\nimport org.springframework.security.config.Customizer;\nimport org.springframework.security.config.annotation.web.builders.HttpSecurity;\nimport org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;\n\n/**\n * 自定义的 URL 的安全配置\n * 目的：每个 Maven Module 可以自定义规则！\n *\n * @author yshop\n */\npublic abstract class AuthorizeRequestsCustomizer\n        implements Customizer<AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry>, Ordered {\n\n    @Resource\n    private WebProperties webProperties;\n\n    protected String buildAdminApi(String url) {\n        return webProperties.getAdminApi().getPrefix() + url;\n    }\n\n    protected String buildAppApi(String url) {\n        return webProperties.getAppApi().getPrefix() + url;\n    }\n\n    @Override\n    public int getOrder() {\n        return 0;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-security/src/main/java/co/yixiang/yshop/framework/security/config/SecurityProperties.java",
    "content": "package co.yixiang.yshop.framework.security.config;\n\nimport lombok.Data;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\nimport java.util.Collections;\nimport java.util.List;\n\n@ConfigurationProperties(prefix = \"yshop.security\")\n@Validated\n@Data\npublic class SecurityProperties {\n\n    /**\n     * HTTP 请求时，访问令牌的请求 Header\n     */\n    @NotEmpty(message = \"Token Header 不能为空\")\n    private String tokenHeader = \"Authorization\";\n    /**\n     * HTTP 请求时，访问令牌的请求参数\n     *\n     * 初始目的：解决 WebSocket 无法通过 header 传参，只能通过 token 参数拼接\n     */\n    @NotEmpty(message = \"Token Parameter 不能为空\")\n    private String tokenParameter = \"token\";\n\n    /**\n     * mock 模式的开关\n     */\n    @NotNull(message = \"mock 模式的开关不能为空\")\n    private Boolean mockEnable = false;\n    /**\n     * mock 模式的密钥\n     * 一定要配置密钥，保证安全性\n     */\n    @NotEmpty(message = \"mock 模式的密钥不能为空\") // 这里设置了一个默认值，因为实际上只有 mockEnable 为 true 时才需要配置。\n    private String mockSecret = \"test\";\n\n    /**\n     * 免登录的 URL 列表\n     */\n    private List<String> permitAllUrls = Collections.emptyList();\n\n    /**\n     * PasswordEncoder 加密复杂度，越高开销越大\n     */\n    private Integer passwordEncoderLength = 4;\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-security/src/main/java/co/yixiang/yshop/framework/security/config/YshopSecurityAutoConfiguration.java",
    "content": "package co.yixiang.yshop.framework.security.config;\n\nimport co.yixiang.yshop.framework.security.core.aop.PreAuthenticatedAspect;\nimport co.yixiang.yshop.framework.security.core.context.TransmittableThreadLocalSecurityContextHolderStrategy;\nimport co.yixiang.yshop.framework.security.core.filter.TokenAuthenticationFilter;\nimport co.yixiang.yshop.framework.security.core.handler.AccessDeniedHandlerImpl;\nimport co.yixiang.yshop.framework.security.core.handler.AuthenticationEntryPointImpl;\nimport co.yixiang.yshop.framework.security.core.service.SecurityFrameworkService;\nimport co.yixiang.yshop.framework.security.core.service.SecurityFrameworkServiceImpl;\nimport co.yixiang.yshop.framework.web.core.handler.GlobalExceptionHandler;\nimport co.yixiang.yshop.module.system.api.oauth2.OAuth2TokenApi;\nimport co.yixiang.yshop.module.system.api.permission.PermissionApi;\nimport jakarta.annotation.Resource;\nimport org.springframework.beans.factory.config.MethodInvokingFactoryBean;\nimport org.springframework.boot.autoconfigure.AutoConfiguration;\nimport org.springframework.boot.autoconfigure.AutoConfigureOrder;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.security.core.context.SecurityContextHolder;\nimport org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;\nimport org.springframework.security.crypto.password.PasswordEncoder;\nimport org.springframework.security.web.AuthenticationEntryPoint;\nimport org.springframework.security.web.access.AccessDeniedHandler;\n\n/**\n * Spring Security 自动配置类，主要用于相关组件的配置\n *\n * 注意，不能和 {@link YshopWebSecurityConfigurerAdapter} 用一个，原因是会导致初始化报错。\n * 参见 https://stackoverflow.com/questions/53847050/spring-boot-delegatebuilder-cannot-be-null-on-autowiring-authenticationmanager 文档。\n *\n * @author yshop\n */\n@AutoConfiguration\n@AutoConfigureOrder(-1) // 目的：先于 Spring Security 自动配置，避免一键改包后，org.* 基础包无法生效\n@EnableConfigurationProperties(SecurityProperties.class)\npublic class YshopSecurityAutoConfiguration {\n\n    @Resource\n    private SecurityProperties securityProperties;\n\n    /**\n     * 处理用户未登录拦截的切面的 Bean\n     */\n    @Bean\n    public PreAuthenticatedAspect preAuthenticatedAspect() {\n        return new PreAuthenticatedAspect();\n    }\n\n    /**\n     * 认证失败处理类 Bean\n     */\n    @Bean\n    public AuthenticationEntryPoint authenticationEntryPoint() {\n        return new AuthenticationEntryPointImpl();\n    }\n\n    /**\n     * 权限不够处理器 Bean\n     */\n    @Bean\n    public AccessDeniedHandler accessDeniedHandler() {\n        return new AccessDeniedHandlerImpl();\n    }\n\n    /**\n     * Spring Security 加密器\n     * 考虑到安全性，这里采用 BCryptPasswordEncoder 加密器\n     *\n     * @see <a href=\"http://stackabuse.com/password-encoding-with-spring-security/\">Password Encoding with Spring Security</a>\n     */\n    @Bean\n    public PasswordEncoder passwordEncoder() {\n        return new BCryptPasswordEncoder(securityProperties.getPasswordEncoderLength());\n    }\n\n    /**\n     * Token 认证过滤器 Bean\n     */\n    @Bean\n    public TokenAuthenticationFilter authenticationTokenFilter(GlobalExceptionHandler globalExceptionHandler,\n                                                               OAuth2TokenApi oauth2TokenApi) {\n        return new TokenAuthenticationFilter(securityProperties, globalExceptionHandler, oauth2TokenApi);\n    }\n\n    @Bean(\"ss\") // 使用 Spring Security 的缩写，方便使用\n    public SecurityFrameworkService securityFrameworkService(PermissionApi permissionApi) {\n        return new SecurityFrameworkServiceImpl(permissionApi);\n    }\n\n    /**\n     * 声明调用 {@link SecurityContextHolder#setStrategyName(String)} 方法，\n     * 设置使用 {@link TransmittableThreadLocalSecurityContextHolderStrategy} 作为 Security 的上下文策略\n     */\n    @Bean\n    public MethodInvokingFactoryBean securityContextHolderMethodInvokingFactoryBean() {\n        MethodInvokingFactoryBean methodInvokingFactoryBean = new MethodInvokingFactoryBean();\n        methodInvokingFactoryBean.setTargetClass(SecurityContextHolder.class);\n        methodInvokingFactoryBean.setTargetMethod(\"setStrategyName\");\n        methodInvokingFactoryBean.setArguments(TransmittableThreadLocalSecurityContextHolderStrategy.class.getName());\n        return methodInvokingFactoryBean;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-security/src/main/java/co/yixiang/yshop/framework/security/config/YshopWebSecurityConfigurerAdapter.java",
    "content": "package co.yixiang.yshop.framework.security.config;\n\nimport cn.hutool.core.collection.CollUtil;\nimport co.yixiang.yshop.framework.security.core.filter.TokenAuthenticationFilter;\nimport co.yixiang.yshop.framework.web.config.WebProperties;\nimport com.google.common.collect.HashMultimap;\nimport com.google.common.collect.Multimap;\nimport jakarta.annotation.Resource;\nimport jakarta.annotation.security.PermitAll;\nimport org.springframework.boot.autoconfigure.AutoConfiguration;\nimport org.springframework.boot.autoconfigure.AutoConfigureOrder;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.http.HttpMethod;\nimport org.springframework.security.authentication.AuthenticationManager;\nimport org.springframework.security.config.Customizer;\nimport org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;\nimport org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;\nimport org.springframework.security.config.annotation.web.builders.HttpSecurity;\nimport org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;\nimport org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;\nimport org.springframework.security.config.http.SessionCreationPolicy;\nimport org.springframework.security.web.AuthenticationEntryPoint;\nimport org.springframework.security.web.SecurityFilterChain;\nimport org.springframework.security.web.access.AccessDeniedHandler;\nimport org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;\nimport org.springframework.web.bind.annotation.RequestMethod;\nimport org.springframework.web.method.HandlerMethod;\nimport org.springframework.web.servlet.mvc.method.RequestMappingInfo;\nimport org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;\nimport org.springframework.web.util.pattern.PathPattern;\n\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport static co.yixiang.yshop.framework.common.util.collection.CollectionUtils.convertList;\n\n/**\n * 自定义的 Spring Security 配置适配器实现\n *\n * @author yshop\n */\n@AutoConfiguration\n@AutoConfigureOrder(-1) // 目的：先于 Spring Security 自动配置，避免一键改包后，org.* 基础包无法生效\n@EnableMethodSecurity(securedEnabled = true)\npublic class YshopWebSecurityConfigurerAdapter {\n\n    @Resource\n    private WebProperties webProperties;\n    @Resource\n    private SecurityProperties securityProperties;\n\n    /**\n     * 认证失败处理类 Bean\n     */\n    @Resource\n    private AuthenticationEntryPoint authenticationEntryPoint;\n    /**\n     * 权限不够处理器 Bean\n     */\n    @Resource\n    private AccessDeniedHandler accessDeniedHandler;\n    /**\n     * Token 认证过滤器 Bean\n     */\n    @Resource\n    private TokenAuthenticationFilter authenticationTokenFilter;\n\n    /**\n     * 自定义的权限映射 Bean 们\n     *\n     * @see #filterChain(HttpSecurity)\n     */\n    @Resource\n    private List<AuthorizeRequestsCustomizer> authorizeRequestsCustomizers;\n\n    @Resource\n    private ApplicationContext applicationContext;\n\n    /**\n     * 由于 Spring Security 创建 AuthenticationManager 对象时，没声明 @Bean 注解，导致无法被注入\n     * 通过覆写父类的该方法，添加 @Bean 注解，解决该问题\n     */\n    @Bean\n    public AuthenticationManager authenticationManagerBean(AuthenticationConfiguration authenticationConfiguration) throws Exception {\n        return authenticationConfiguration.getAuthenticationManager();\n    }\n\n    /**\n     * 配置 URL 的安全配置\n     *\n     * anyRequest          |   匹配所有请求路径\n     * access              |   SpringEl表达式结果为true时可以访问\n     * anonymous           |   匿名可以访问\n     * denyAll             |   用户不能访问\n     * fullyAuthenticated  |   用户完全认证可以访问（非remember-me下自动登录）\n     * hasAnyAuthority     |   如果有参数，参数表示权限，则其中任何一个权限可以访问\n     * hasAnyRole          |   如果有参数，参数表示角色，则其中任何一个角色可以访问\n     * hasAuthority        |   如果有参数，参数表示权限，则其权限可以访问\n     * hasIpAddress        |   如果有参数，参数表示IP地址，如果用户IP和参数匹配，则可以访问\n     * hasRole             |   如果有参数，参数表示角色，则其角色可以访问\n     * permitAll           |   用户可以任意访问\n     * rememberMe          |   允许通过remember-me登录的用户访问\n     * authenticated       |   用户登录后可访问\n     */\n    @Bean\n    protected SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {\n        // 登出\n        httpSecurity\n                // 开启跨域\n                .cors(Customizer.withDefaults())\n                // CSRF 禁用，因为不使用 Session\n                .csrf(AbstractHttpConfigurer::disable)\n                // 基于 token 机制，所以不需要 Session\n                .sessionManagement(c -> c.sessionCreationPolicy(SessionCreationPolicy.STATELESS))\n                .headers(c -> c.frameOptions(HeadersConfigurer.FrameOptionsConfig::disable))\n                // 一堆自定义的 Spring Security 处理器\n                .exceptionHandling(c -> c.authenticationEntryPoint(authenticationEntryPoint)\n                        .accessDeniedHandler(accessDeniedHandler));\n        // 登录、登录暂时不使用 Spring Security 的拓展点，主要考虑一方面拓展多用户、多种登录方式相对复杂，一方面用户的学习成本较高\n\n        // 获得 @PermitAll 带来的 URL 列表，免登录\n        Multimap<HttpMethod, String> permitAllUrls = getPermitAllUrlsFromAnnotations();\n        // 设置每个请求的权限\n        httpSecurity\n                // ①：全局共享规则\n                .authorizeHttpRequests(c -> c\n                    // 1.1 静态资源，可匿名访问\n                    .requestMatchers(HttpMethod.GET, \"/*.html\", \"/*.html\", \"/*.css\", \"/*.js\").permitAll()\n                    // 1.1 设置 @PermitAll 无需认证\n                    .requestMatchers(HttpMethod.GET, permitAllUrls.get(HttpMethod.GET).toArray(new String[0])).permitAll()\n                    .requestMatchers(HttpMethod.POST, permitAllUrls.get(HttpMethod.POST).toArray(new String[0])).permitAll()\n                    .requestMatchers(HttpMethod.PUT, permitAllUrls.get(HttpMethod.PUT).toArray(new String[0])).permitAll()\n                    .requestMatchers(HttpMethod.DELETE, permitAllUrls.get(HttpMethod.DELETE).toArray(new String[0])).permitAll()\n                    .requestMatchers(HttpMethod.HEAD, permitAllUrls.get(HttpMethod.HEAD).toArray(new String[0])).permitAll()\n                    .requestMatchers(HttpMethod.PATCH, permitAllUrls.get(HttpMethod.PATCH).toArray(new String[0])).permitAll()\n                    // 1.2 基于 yshop.security.permit-all-urls 无需认证\n                    .requestMatchers(securityProperties.getPermitAllUrls().toArray(new String[0])).permitAll()\n                    // 1.3 设置 App API 无需认证\n                    .requestMatchers(buildAppApi(\"/**\")).permitAll()\n                )\n                // ②：每个项目的自定义规则\n                .authorizeHttpRequests(c -> authorizeRequestsCustomizers.forEach(customizer -> customizer.customize(c)))\n                // ③：兜底规则，必须认证\n                .authorizeHttpRequests(c -> c.anyRequest().authenticated());\n\n        // 添加 Token Filter\n        httpSecurity.addFilterBefore(authenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);\n        return httpSecurity.build();\n    }\n\n    private String buildAppApi(String url) {\n        return webProperties.getAppApi().getPrefix() + url;\n    }\n\n    private Multimap<HttpMethod, String> getPermitAllUrlsFromAnnotations() {\n        Multimap<HttpMethod, String> result = HashMultimap.create();\n        // 获得接口对应的 HandlerMethod 集合\n        RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping)\n                applicationContext.getBean(\"requestMappingHandlerMapping\");\n        Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods();\n        // 获得有 @PermitAll 注解的接口\n        for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : handlerMethodMap.entrySet()) {\n            HandlerMethod handlerMethod = entry.getValue();\n            if (!handlerMethod.hasMethodAnnotation(PermitAll.class)) {\n                continue;\n            }\n            Set<String> urls = new HashSet<>();\n            if (entry.getKey().getPatternsCondition() != null) {\n                urls.addAll(entry.getKey().getPatternsCondition().getPatterns());\n            }\n            if (entry.getKey().getPathPatternsCondition() != null) {\n                urls.addAll(convertList(entry.getKey().getPathPatternsCondition().getPatterns(), PathPattern::getPatternString));\n            }\n            if (urls.isEmpty()) {\n                continue;\n            }\n\n            // 特殊：使用 @RequestMapping 注解，并且未写 method 属性，此时认为都需要免登录\n            Set<RequestMethod> methods = entry.getKey().getMethodsCondition().getMethods();\n            if (CollUtil.isEmpty(methods)) {\n                result.putAll(HttpMethod.GET, urls);\n                result.putAll(HttpMethod.POST, urls);\n                result.putAll(HttpMethod.PUT, urls);\n                result.putAll(HttpMethod.DELETE, urls);\n                result.putAll(HttpMethod.HEAD, urls);\n                result.putAll(HttpMethod.PATCH, urls);\n                continue;\n            }\n            // 根据请求方法，添加到 result 结果\n            entry.getKey().getMethodsCondition().getMethods().forEach(requestMethod -> {\n                switch (requestMethod) {\n                    case GET:\n                        result.putAll(HttpMethod.GET, urls);\n                        break;\n                    case POST:\n                        result.putAll(HttpMethod.POST, urls);\n                        break;\n                    case PUT:\n                        result.putAll(HttpMethod.PUT, urls);\n                        break;\n                    case DELETE:\n                        result.putAll(HttpMethod.DELETE, urls);\n                        break;\n                    case HEAD:\n                        result.putAll(HttpMethod.HEAD, urls);\n                        break;\n                    case PATCH:\n                        result.putAll(HttpMethod.PATCH, urls);\n                        break;\n                }\n            });\n        }\n        return result;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-security/src/main/java/co/yixiang/yshop/framework/security/core/LoginUser.java",
    "content": "package co.yixiang.yshop.framework.security.core;\n\nimport cn.hutool.core.map.MapUtil;\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport com.fasterxml.jackson.annotation.JsonIgnore;\nimport lombok.Data;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * 登录用户信息\n *\n * @author yshop\n */\n@Data\npublic class LoginUser {\n\n    public static final String INFO_KEY_NICKNAME = \"nickname\";\n    public static final String INFO_KEY_DEPT_ID = \"deptId\";\n\n    /**\n     * 用户编号\n     */\n    private Long id;\n    /**\n     * 用户类型\n     *\n     * 关联 {@link UserTypeEnum}\n     */\n    private Integer userType;\n    /**\n     * 额外的用户信息\n     */\n    private Map<String, String> info;\n    /**\n     * 租户编号\n     */\n    private Long tenantId;\n    /**\n     * 授权范围\n     */\n    private List<String> scopes;\n\n    private Long shopId;\n\n    // ========== 上下文 ==========\n    /**\n     * 上下文字段，不进行持久化\n     *\n     * 1. 用于基于 LoginUser 维度的临时缓存\n     */\n    @JsonIgnore\n    private Map<String, Object> context;\n\n    public void setContext(String key, Object value) {\n        if (context == null) {\n            context = new HashMap<>();\n        }\n        context.put(key, value);\n    }\n\n    public <T> T getContext(String key, Class<T> type) {\n        return MapUtil.get(context, key, type);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-security/src/main/java/co/yixiang/yshop/framework/security/core/annotations/PreAuthenticated.java",
    "content": "package co.yixiang.yshop.framework.security.core.annotations;\n\nimport java.lang.annotation.*;\n\n/**\n * 声明用户需要登录\n *\n * 为什么不使用 {@link org.springframework.security.access.prepost.PreAuthorize} 注解，原因是不通过时，抛出的是认证不通过，而不是未登录\n *\n * @author yshop\n */\n@Target({ElementType.METHOD})\n@Retention(RetentionPolicy.RUNTIME)\n@Inherited\n@Documented\npublic @interface PreAuthenticated {\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-security/src/main/java/co/yixiang/yshop/framework/security/core/aop/PreAuthenticatedAspect.java",
    "content": "package co.yixiang.yshop.framework.security.core.aop;\n\nimport co.yixiang.yshop.framework.security.core.annotations.PreAuthenticated;\nimport co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils;\nimport lombok.extern.slf4j.Slf4j;\nimport org.aspectj.lang.ProceedingJoinPoint;\nimport org.aspectj.lang.annotation.Around;\nimport org.aspectj.lang.annotation.Aspect;\n\nimport static co.yixiang.yshop.framework.common.exception.enums.GlobalErrorCodeConstants.UNAUTHORIZED;\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\n\n@Aspect\n@Slf4j\npublic class PreAuthenticatedAspect {\n\n    @Around(\"@annotation(preAuthenticated)\")\n    public Object around(ProceedingJoinPoint joinPoint, PreAuthenticated preAuthenticated) throws Throwable {\n        if (SecurityFrameworkUtils.getLoginUser() == null) {\n            throw exception(UNAUTHORIZED);\n        }\n        return joinPoint.proceed();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-security/src/main/java/co/yixiang/yshop/framework/security/core/context/TransmittableThreadLocalSecurityContextHolderStrategy.java",
    "content": "package co.yixiang.yshop.framework.security.core.context;\n\nimport com.alibaba.ttl.TransmittableThreadLocal;\nimport org.springframework.security.core.context.SecurityContext;\nimport org.springframework.security.core.context.SecurityContextHolderStrategy;\nimport org.springframework.security.core.context.SecurityContextImpl;\nimport org.springframework.util.Assert;\n\n/**\n * 基于 TransmittableThreadLocal 实现的 Security Context 持有者策略\n * 目的是，避免 @Async 等异步执行时，原生 ThreadLocal 的丢失问题\n *\n * @author yshop\n */\npublic class TransmittableThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy {\n\n    /**\n     * 使用 TransmittableThreadLocal 作为上下文\n     */\n    private static final ThreadLocal<SecurityContext> CONTEXT_HOLDER = new TransmittableThreadLocal<>();\n\n    @Override\n    public void clearContext() {\n        CONTEXT_HOLDER.remove();\n    }\n\n    @Override\n    public SecurityContext getContext() {\n        SecurityContext ctx = CONTEXT_HOLDER.get();\n        if (ctx == null) {\n            ctx = createEmptyContext();\n            CONTEXT_HOLDER.set(ctx);\n        }\n        return ctx;\n    }\n\n    @Override\n    public void setContext(SecurityContext context) {\n        Assert.notNull(context, \"Only non-null SecurityContext instances are permitted\");\n        CONTEXT_HOLDER.set(context);\n    }\n\n    @Override\n    public SecurityContext createEmptyContext() {\n        return new SecurityContextImpl();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-security/src/main/java/co/yixiang/yshop/framework/security/core/filter/TokenAuthenticationFilter.java",
    "content": "package co.yixiang.yshop.framework.security.core.filter;\n\nimport cn.hutool.core.util.ObjectUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.exception.ServiceException;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.util.servlet.ServletUtils;\nimport co.yixiang.yshop.framework.security.config.SecurityProperties;\nimport co.yixiang.yshop.framework.security.core.LoginUser;\nimport co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils;\nimport co.yixiang.yshop.framework.web.core.handler.GlobalExceptionHandler;\nimport co.yixiang.yshop.framework.web.core.util.WebFrameworkUtils;\nimport co.yixiang.yshop.module.system.api.oauth2.OAuth2TokenApi;\nimport co.yixiang.yshop.module.system.api.oauth2.dto.OAuth2AccessTokenCheckRespDTO;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.security.access.AccessDeniedException;\nimport org.springframework.web.filter.OncePerRequestFilter;\n\nimport jakarta.servlet.FilterChain;\nimport jakarta.servlet.ServletException;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport java.io.IOException;\n\n/**\n * Token 过滤器，验证 token 的有效性\n * 验证通过后，获得 {@link LoginUser} 信息，并加入到 Spring Security 上下文\n *\n * @author yshop\n */\n@RequiredArgsConstructor\npublic class TokenAuthenticationFilter extends OncePerRequestFilter {\n\n    private final SecurityProperties securityProperties;\n\n    private final GlobalExceptionHandler globalExceptionHandler;\n\n    private final OAuth2TokenApi oauth2TokenApi;\n\n    @Override\n    @SuppressWarnings(\"NullableProblems\")\n    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)\n            throws ServletException, IOException {\n        String token = SecurityFrameworkUtils.obtainAuthorization(request,\n                securityProperties.getTokenHeader(), securityProperties.getTokenParameter());\n        if (StrUtil.isNotEmpty(token)) {\n            Integer userType = WebFrameworkUtils.getLoginUserType(request);\n            try {\n                // 1.1 基于 token 构建登录用户\n                LoginUser loginUser = buildLoginUserByToken(token, userType);\n                // 1.2 模拟 Login 功能，方便日常开发调试\n                if (loginUser == null) {\n                    loginUser = mockLoginUser(request, token, userType);\n                }\n\n                // 2. 设置当前用户\n                if (loginUser != null) {\n                    SecurityFrameworkUtils.setLoginUser(loginUser, request);\n                }\n            } catch (Throwable ex) {\n                CommonResult<?> result = globalExceptionHandler.allExceptionHandler(request, ex);\n                ServletUtils.writeJSON(response, result);\n                return;\n            }\n        }\n\n        // 继续过滤链\n        chain.doFilter(request, response);\n    }\n\n    private LoginUser buildLoginUserByToken(String token, Integer userType) {\n        try {\n            OAuth2AccessTokenCheckRespDTO accessToken = oauth2TokenApi.checkAccessToken(token);\n            if (accessToken == null) {\n                return null;\n            }\n            // 用户类型不匹配，无权限\n            // 注意：只有 /admin-api/* 和 /app-api/* 有 userType，才需要比对用户类型\n            // 类似 WebSocket 的 /ws/* 连接地址，是不需要比对用户类型的\n            if (userType != null\n                    && ObjectUtil.notEqual(accessToken.getUserType(), userType)) {\n                throw new AccessDeniedException(\"错误的用户类型\");\n            }\n            // 构建登录用户\n            return new LoginUser().setId(accessToken.getUserId()).setUserType(accessToken.getUserType())\n                    .setShopId(accessToken.getShopId())\n                    .setInfo(accessToken.getUserInfo()) // 额外的用户信息\n                    .setTenantId(accessToken.getTenantId()).setScopes(accessToken.getScopes());\n        } catch (ServiceException serviceException) {\n            // 校验 Token 不通过时，考虑到一些接口是无需登录的，所以直接返回 null 即可\n            return null;\n        }\n    }\n\n    /**\n     * 模拟登录用户，方便日常开发调试\n     *\n     * 注意，在线上环境下，一定要关闭该功能！！！\n     *\n     * @param request 请求\n     * @param token 模拟的 token，格式为 {@link SecurityProperties#getMockSecret()} + 用户编号\n     * @param userType 用户类型\n     * @return 模拟的 LoginUser\n     */\n    private LoginUser mockLoginUser(HttpServletRequest request, String token, Integer userType) {\n        if (!securityProperties.getMockEnable()) {\n            return null;\n        }\n        // 必须以 mockSecret 开头\n        if (!token.startsWith(securityProperties.getMockSecret())) {\n            return null;\n        }\n        // 构建模拟用户\n        Long userId = Long.valueOf(token.substring(securityProperties.getMockSecret().length()));\n        return new LoginUser().setId(userId).setUserType(userType)\n                .setTenantId(WebFrameworkUtils.getTenantId(request));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-security/src/main/java/co/yixiang/yshop/framework/security/core/handler/AccessDeniedHandlerImpl.java",
    "content": "package co.yixiang.yshop.framework.security.core.handler;\n\nimport co.yixiang.yshop.framework.common.exception.enums.GlobalErrorCodeConstants;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils;\nimport co.yixiang.yshop.framework.common.util.servlet.ServletUtils;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.security.access.AccessDeniedException;\nimport org.springframework.security.web.access.AccessDeniedHandler;\nimport org.springframework.security.web.access.ExceptionTranslationFilter;\n\nimport jakarta.servlet.FilterChain;\nimport jakarta.servlet.ServletException;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport java.io.IOException;\n\nimport static co.yixiang.yshop.framework.common.exception.enums.GlobalErrorCodeConstants.FORBIDDEN;\n\n/**\n * 访问一个需要认证的 URL 资源，已经认证（登录）但是没有权限的情况下，返回 {@link GlobalErrorCodeConstants#FORBIDDEN} 错误码。\n *\n * 补充：Spring Security 通过 {@link ExceptionTranslationFilter#handleAccessDeniedException(HttpServletRequest, HttpServletResponse, FilterChain, AccessDeniedException)} 方法，调用当前类\n *\n * @author yshop\n */\n@Slf4j\n@SuppressWarnings(\"JavadocReference\")\npublic class AccessDeniedHandlerImpl implements AccessDeniedHandler {\n\n    @Override\n    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException e)\n            throws IOException, ServletException {\n        // 打印 warn 的原因是，不定期合并 warn，看看有没恶意破坏\n        log.warn(\"[commence][访问 URL({}) 时，用户({}) 权限不够]\", request.getRequestURI(),\n                SecurityFrameworkUtils.getLoginUserId(), e);\n        // 返回 403\n        ServletUtils.writeJSON(response, CommonResult.error(FORBIDDEN));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-security/src/main/java/co/yixiang/yshop/framework/security/core/handler/AuthenticationEntryPointImpl.java",
    "content": "package co.yixiang.yshop.framework.security.core.handler;\n\nimport co.yixiang.yshop.framework.common.exception.enums.GlobalErrorCodeConstants;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.util.servlet.ServletUtils;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.security.core.AuthenticationException;\nimport org.springframework.security.web.AuthenticationEntryPoint;\nimport org.springframework.security.web.access.ExceptionTranslationFilter;\n\nimport jakarta.servlet.FilterChain;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\n\nimport static co.yixiang.yshop.framework.common.exception.enums.GlobalErrorCodeConstants.UNAUTHORIZED;\n\n/**\n * 访问一个需要认证的 URL 资源，但是此时自己尚未认证（登录）的情况下，返回 {@link GlobalErrorCodeConstants#UNAUTHORIZED} 错误码，从而使前端重定向到登录页\n *\n * 补充：Spring Security 通过 {@link ExceptionTranslationFilter#sendStartAuthentication(HttpServletRequest, HttpServletResponse, FilterChain, AuthenticationException)} 方法，调用当前类\n *\n * @author ruoyi\n */\n@Slf4j\n@SuppressWarnings(\"JavadocReference\") // 忽略文档引用报错\npublic class AuthenticationEntryPointImpl implements AuthenticationEntryPoint {\n\n    @Override\n    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException e) {\n        log.debug(\"[commence][访问 URL({}) 时，没有登录]\", request.getRequestURI(), e);\n        // 返回 401\n        ServletUtils.writeJSON(response, CommonResult.error(UNAUTHORIZED));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-security/src/main/java/co/yixiang/yshop/framework/security/core/service/SecurityFrameworkService.java",
    "content": "package co.yixiang.yshop.framework.security.core.service;\n\n/**\n * Security 框架 Service 接口，定义权限相关的校验操作\n *\n * @author yshop\n */\npublic interface SecurityFrameworkService {\n\n    /**\n     * 判断是否有权限\n     *\n     * @param permission 权限\n     * @return 是否\n     */\n    boolean hasPermission(String permission);\n\n    /**\n     * 判断是否有权限，任一一个即可\n     *\n     * @param permissions 权限\n     * @return 是否\n     */\n    boolean hasAnyPermissions(String... permissions);\n\n    /**\n     * 判断是否有角色\n     *\n     * 注意，角色使用的是 SysRoleDO 的 code 标识\n     *\n     * @param role 角色\n     * @return 是否\n     */\n    boolean hasRole(String role);\n\n    /**\n     * 判断是否有角色，任一一个即可\n     *\n     * @param roles 角色数组\n     * @return 是否\n     */\n    boolean hasAnyRoles(String... roles);\n\n    /**\n     * 判断是否有授权\n     *\n     * @param scope 授权\n     * @return 是否\n     */\n    boolean hasScope(String scope);\n\n    /**\n     * 判断是否有授权范围，任一一个即可\n     *\n     * @param scope 授权范围数组\n     * @return 是否\n     */\n    boolean hasAnyScopes(String... scope);\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-security/src/main/java/co/yixiang/yshop/framework/security/core/service/SecurityFrameworkServiceImpl.java",
    "content": "package co.yixiang.yshop.framework.security.core.service;\n\nimport cn.hutool.core.collection.CollUtil;\nimport co.yixiang.yshop.framework.security.core.LoginUser;\nimport co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils;\nimport co.yixiang.yshop.module.system.api.permission.PermissionApi;\nimport lombok.AllArgsConstructor;\n\nimport java.util.Arrays;\n\nimport static co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;\n\n/**\n * 默认的 {@link SecurityFrameworkService} 实现类\n *\n * @author yshop\n */\n@AllArgsConstructor\npublic class SecurityFrameworkServiceImpl implements SecurityFrameworkService {\n\n    private final PermissionApi permissionApi;\n\n    @Override\n    public boolean hasPermission(String permission) {\n        return hasAnyPermissions(permission);\n    }\n\n    @Override\n    public boolean hasAnyPermissions(String... permissions) {\n        return permissionApi.hasAnyPermissions(getLoginUserId(), permissions);\n    }\n\n    @Override\n    public boolean hasRole(String role) {\n        return hasAnyRoles(role);\n    }\n\n    @Override\n    public boolean hasAnyRoles(String... roles) {\n        return permissionApi.hasAnyRoles(getLoginUserId(), roles);\n    }\n\n    @Override\n    public boolean hasScope(String scope) {\n        return hasAnyScopes(scope);\n    }\n\n    @Override\n    public boolean hasAnyScopes(String... scope) {\n        LoginUser user = SecurityFrameworkUtils.getLoginUser();\n        if (user == null) {\n            return false;\n        }\n        return CollUtil.containsAny(user.getScopes(), Arrays.asList(scope));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-security/src/main/java/co/yixiang/yshop/framework/security/core/util/SecurityFrameworkUtils.java",
    "content": "package co.yixiang.yshop.framework.security.core.util;\n\nimport cn.hutool.core.map.MapUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.security.core.LoginUser;\nimport co.yixiang.yshop.framework.web.core.util.WebFrameworkUtils;\nimport org.springframework.lang.Nullable;\nimport org.springframework.security.authentication.UsernamePasswordAuthenticationToken;\nimport org.springframework.security.core.Authentication;\nimport org.springframework.security.core.context.SecurityContext;\nimport org.springframework.security.core.context.SecurityContextHolder;\nimport org.springframework.security.web.authentication.WebAuthenticationDetailsSource;\nimport org.springframework.util.StringUtils;\n\nimport jakarta.servlet.http.HttpServletRequest;\nimport java.util.Collections;\n\n/**\n * 安全服务工具类\n *\n * @author yshop\n */\npublic class SecurityFrameworkUtils {\n\n    /**\n     * HEADER 认证头 value 的前缀\n     */\n    public static final String AUTHORIZATION_BEARER = \"Bearer\";\n\n    private SecurityFrameworkUtils() {}\n\n    /**\n     * 从请求中，获得认证 Token\n     *\n     * @param request 请求\n     * @param headerName 认证 Token 对应的 Header 名字\n     * @param parameterName 认证 Token 对应的 Parameter 名字\n     * @return 认证 Token\n     */\n    public static String obtainAuthorization(HttpServletRequest request,\n                                             String headerName, String parameterName) {\n        // 1. 获得 Token。优先级：Header > Parameter\n        String token = request.getHeader(headerName);\n        if (StrUtil.isEmpty(token)) {\n            token = request.getParameter(parameterName);\n        }\n        if (!StringUtils.hasText(token)) {\n            return null;\n        }\n        // 2. 去除 Token 中带的 Bearer\n        int index = token.indexOf(AUTHORIZATION_BEARER + \" \");\n        return index >= 0 ? token.substring(index + 7).trim() : token;\n    }\n\n    /**\n     * 获得当前认证信息\n     *\n     * @return 认证信息\n     */\n    public static Authentication getAuthentication() {\n        SecurityContext context = SecurityContextHolder.getContext();\n        if (context == null) {\n            return null;\n        }\n        return context.getAuthentication();\n    }\n\n    /**\n     * 获取当前用户\n     *\n     * @return 当前用户\n     */\n    @Nullable\n    public static LoginUser getLoginUser() {\n        Authentication authentication = getAuthentication();\n        if (authentication == null) {\n            return null;\n        }\n        return authentication.getPrincipal() instanceof LoginUser ? (LoginUser) authentication.getPrincipal() : null;\n    }\n\n    /**\n     * 获得当前用户的编号，从上下文中\n     *\n     * @return 用户编号\n     */\n    @Nullable\n    public static Long getLoginUserId() {\n        LoginUser loginUser = getLoginUser();\n        return loginUser != null ? loginUser.getId() : null;\n    }\n\n    /**\n     * 获得当前用户的昵称，从上下文中\n     *\n     * @return 昵称\n     */\n    @Nullable\n    public static String getLoginUserNickname() {\n        LoginUser loginUser = getLoginUser();\n        return loginUser != null ? MapUtil.getStr(loginUser.getInfo(), LoginUser.INFO_KEY_NICKNAME) : null;\n    }\n\n    /**\n     * 获得当前用户的部门编号，从上下文中\n     *\n     * @return 部门编号\n     */\n    @Nullable\n    public static Long getLoginUserDeptId() {\n        LoginUser loginUser = getLoginUser();\n        return loginUser != null ? MapUtil.getLong(loginUser.getInfo(), LoginUser.INFO_KEY_DEPT_ID) : null;\n    }\n\n    /**\n     * 设置当前用户\n     *\n     * @param loginUser 登录用户\n     * @param request 请求\n     */\n    public static void setLoginUser(LoginUser loginUser, HttpServletRequest request) {\n        // 创建 Authentication，并设置到上下文\n        Authentication authentication = buildAuthentication(loginUser, request);\n        SecurityContextHolder.getContext().setAuthentication(authentication);\n\n        // 额外设置到 request 中，用于 ApiAccessLogFilter 可以获取到用户编号；\n        // 原因是，Spring Security 的 Filter 在 ApiAccessLogFilter 后面，在它记录访问日志时，线上上下文已经没有用户编号等信息\n        WebFrameworkUtils.setLoginUserId(request, loginUser.getId());\n        WebFrameworkUtils.setLoginUserType(request, loginUser.getUserType());\n    }\n\n    private static Authentication buildAuthentication(LoginUser loginUser, HttpServletRequest request) {\n        // 创建 UsernamePasswordAuthenticationToken 对象\n        UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(\n                loginUser, null, Collections.emptyList());\n        authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));\n        return authenticationToken;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-security/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports",
    "content": "co.yixiang.yshop.framework.security.config.YshopSecurityAutoConfiguration\nco.yixiang.yshop.framework.security.config.YshopWebSecurityConfigurerAdapter\nco.yixiang.yshop.framework.operatelog.config.YshopOperateLogConfiguration"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-test/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>co.yixiang.boot</groupId>\n        <artifactId>yshop-framework</artifactId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>yshop-spring-boot-starter-test</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>测试组件，用于单元测试、集成测试</description>\n    <url>https://gitee.com/guchengwuyue/yshop-drink</url>\n\n    <dependencies>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-common</artifactId>\n        </dependency>\n\n        <!-- DB 相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-mybatis</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-redis</artifactId>\n        </dependency>\n\n        <!-- Test 测试相关 -->\n        <dependency>\n            <groupId>org.mockito</groupId>\n            <artifactId>mockito-inline</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>com.h2database</groupId> <!-- 单元测试，我们采用 H2 作为数据库 -->\n            <artifactId>h2</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>com.github.fppt</groupId> <!-- 单元测试，我们采用内嵌的 Redis 数据库 -->\n            <artifactId>jedis-mock</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>uk.co.jemos.podam</groupId> <!-- 单元测试，随机生成 POJO 类 -->\n            <artifactId>podam</artifactId>\n        </dependency>\n    </dependencies>\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-test/src/main/java/co/yixiang/yshop/framework/test/config/RedisTestConfiguration.java",
    "content": "package co.yixiang.yshop.framework.test.config;\n\nimport com.github.fppt.jedismock.RedisServer;\nimport org.springframework.boot.autoconfigure.data.redis.RedisProperties;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.annotation.Lazy;\n\nimport java.io.IOException;\n\n/**\n * Redis 测试 Configuration，主要实现内嵌 Redis 的启动\n *\n * @author yshop\n */\n@Configuration(proxyBeanMethods = false)\n@Lazy(false) // 禁止延迟加载\n@EnableConfigurationProperties(RedisProperties.class)\npublic class RedisTestConfiguration {\n\n    /**\n     * 创建模拟的 Redis Server 服务器\n     */\n    @Bean\n    public RedisServer redisServer(RedisProperties properties) throws IOException {\n        RedisServer redisServer = new RedisServer(properties.getPort());\n        // 一次执行多个单元测试时，貌似创建多个 spring 容器，导致不进行 stop。这样，就导致端口被占用，无法启动。。。\n        try {\n            redisServer.start();\n        } catch (Exception ignore) {}\n        return redisServer;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-test/src/main/java/co/yixiang/yshop/framework/test/config/SqlInitializationTestConfiguration.java",
    "content": "package co.yixiang.yshop.framework.test.config;\n\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnClass;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;\nimport org.springframework.boot.autoconfigure.sql.init.SqlInitializationProperties;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.boot.jdbc.init.DataSourceScriptDatabaseInitializer;\nimport org.springframework.boot.sql.init.AbstractScriptDatabaseInitializer;\nimport org.springframework.boot.sql.init.DatabaseInitializationSettings;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.context.annotation.Lazy;\n\nimport javax.sql.DataSource;\n\n/**\n * SQL 初始化的测试 Configuration\n *\n * 为什么不使用 org.springframework.boot.autoconfigure.sql.init.DataSourceInitializationConfiguration 呢？\n * 因为我们在单元测试会使用 spring.main.lazy-initialization 为 true，开启延迟加载。此时，会导致 DataSourceInitializationConfiguration 初始化\n * 不过呢，当前类的实现代码，基本是复制 DataSourceInitializationConfiguration 的哈！\n *\n * @author yshop\n */\n@Configuration(proxyBeanMethods = false)\n@ConditionalOnMissingBean(AbstractScriptDatabaseInitializer.class)\n@ConditionalOnSingleCandidate(DataSource.class)\n@ConditionalOnClass(name = \"org.springframework.jdbc.datasource.init.DatabasePopulator\")\n@Lazy(value = false) // 禁止延迟加载\n@EnableConfigurationProperties(SqlInitializationProperties.class)\npublic class SqlInitializationTestConfiguration {\n\n\t@Bean\n\tpublic DataSourceScriptDatabaseInitializer dataSourceScriptDatabaseInitializer(DataSource dataSource,\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t   SqlInitializationProperties initializationProperties) {\n\t\tDatabaseInitializationSettings settings = createFrom(initializationProperties);\n\t\treturn new DataSourceScriptDatabaseInitializer(dataSource, settings);\n\t}\n\n\tstatic DatabaseInitializationSettings createFrom(SqlInitializationProperties properties) {\n\t\tDatabaseInitializationSettings settings = new DatabaseInitializationSettings();\n\t\tsettings.setSchemaLocations(properties.getSchemaLocations());\n\t\tsettings.setDataLocations(properties.getDataLocations());\n\t\tsettings.setContinueOnError(properties.isContinueOnError());\n\t\tsettings.setSeparator(properties.getSeparator());\n\t\tsettings.setEncoding(properties.getEncoding());\n\t\tsettings.setMode(properties.getMode());\n\t\treturn settings;\n\t}\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-test/src/main/java/co/yixiang/yshop/framework/test/core/ut/BaseDbAndRedisUnitTest.java",
    "content": "package co.yixiang.yshop.framework.test.core.ut;\n\nimport co.yixiang.yshop.framework.datasource.config.YshopDataSourceAutoConfiguration;\nimport co.yixiang.yshop.framework.mybatis.config.YshopMybatisAutoConfiguration;\nimport co.yixiang.yshop.framework.redis.config.YshopRedisAutoConfiguration;\nimport co.yixiang.yshop.framework.test.config.RedisTestConfiguration;\nimport co.yixiang.yshop.framework.test.config.SqlInitializationTestConfiguration;\nimport com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceAutoConfigure;\nimport com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;\nimport org.redisson.spring.starter.RedissonAutoConfiguration;\nimport org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;\nimport org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;\nimport org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.context.annotation.Import;\nimport org.springframework.test.context.ActiveProfiles;\nimport org.springframework.test.context.jdbc.Sql;\n\n/**\n * 依赖内存 DB + Redis 的单元测试\n *\n * 相比 {@link BaseDbUnitTest} 来说，额外增加了内存 Redis\n *\n * @author yshop\n */\n@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = BaseDbAndRedisUnitTest.Application.class)\n@ActiveProfiles(\"unit-test\") // 设置使用 application-unit-test 配置文件\n@Sql(scripts = \"/sql/clean.sql\", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) // 每个单元测试结束后，清理 DB\npublic class BaseDbAndRedisUnitTest {\n\n    @Import({\n            // DB 配置类\n            YshopDataSourceAutoConfiguration.class, // 自己的 DB 配置类\n            DataSourceAutoConfiguration.class, // Spring DB 自动配置类\n            DataSourceTransactionManagerAutoConfiguration.class, // Spring 事务自动配置类\n            DruidDataSourceAutoConfigure.class, // Druid 自动配置类\n            SqlInitializationTestConfiguration.class, // SQL 初始化\n            // MyBatis 配置类\n            YshopMybatisAutoConfiguration.class, // 自己的 MyBatis 配置类\n            MybatisPlusAutoConfiguration.class, // MyBatis 的自动配置类\n\n            // Redis 配置类\n            RedisTestConfiguration.class, // Redis 测试配置类，用于启动 RedisServer\n            YshopRedisAutoConfiguration.class, // 自己的 Redis 配置类\n            RedisAutoConfiguration.class, // Spring Redis 自动配置类\n            RedissonAutoConfiguration.class, // Redisson 自动配置类\n    })\n    public static class Application {\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-test/src/main/java/co/yixiang/yshop/framework/test/core/ut/BaseDbUnitTest.java",
    "content": "package co.yixiang.yshop.framework.test.core.ut;\n\nimport co.yixiang.yshop.framework.datasource.config.YshopDataSourceAutoConfiguration;\nimport co.yixiang.yshop.framework.mybatis.config.YshopMybatisAutoConfiguration;\nimport co.yixiang.yshop.framework.test.config.SqlInitializationTestConfiguration;\nimport com.alibaba.druid.spring.boot3.autoconfigure.DruidDataSourceAutoConfigure;\nimport com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration;\nimport com.github.yulichang.autoconfigure.MybatisPlusJoinAutoConfiguration;\nimport org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;\nimport org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.context.annotation.Import;\nimport org.springframework.test.context.ActiveProfiles;\nimport org.springframework.test.context.jdbc.Sql;\n\n/**\n * 依赖内存 DB 的单元测试\n *\n * 注意，Service 层同样适用。对于 Service 层的单元测试，我们针对自己模块的 Mapper 走的是 H2 内存数据库，针对别的模块的 Service 走的是 Mock 方法\n *\n * @author yshop\n */\n@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = BaseDbUnitTest.Application.class)\n@ActiveProfiles(\"unit-test\") // 设置使用 application-unit-test 配置文件\n@Sql(scripts = \"/sql/clean.sql\", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) // 每个单元测试结束后，清理 DB\npublic class BaseDbUnitTest {\n\n    @Import({\n            // DB 配置类\n            YshopDataSourceAutoConfiguration.class, // 自己的 DB 配置类\n            DataSourceAutoConfiguration.class, // Spring DB 自动配置类\n            DataSourceTransactionManagerAutoConfiguration.class, // Spring 事务自动配置类\n            DruidDataSourceAutoConfigure.class, // Druid 自动配置类\n            SqlInitializationTestConfiguration.class, // SQL 初始化\n            // MyBatis 配置类\n            YshopMybatisAutoConfiguration.class, // 自己的 MyBatis 配置类\n            MybatisPlusAutoConfiguration.class, // MyBatis 的自动配置类\n            MybatisPlusJoinAutoConfiguration.class, // MyBatis 的Join配置类\n    })\n    public static class Application {\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-test/src/main/java/co/yixiang/yshop/framework/test/core/ut/BaseMockitoUnitTest.java",
    "content": "package co.yixiang.yshop.framework.test.core.ut;\n\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\n/**\n * 纯 Mockito 的单元测试\n *\n * @author yshop\n */\n@ExtendWith(MockitoExtension.class)\npublic class BaseMockitoUnitTest {\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-test/src/main/java/co/yixiang/yshop/framework/test/core/ut/BaseRedisUnitTest.java",
    "content": "package co.yixiang.yshop.framework.test.core.ut;\n\nimport co.yixiang.yshop.framework.redis.config.YshopRedisAutoConfiguration;\nimport co.yixiang.yshop.framework.test.config.RedisTestConfiguration;\nimport org.redisson.spring.starter.RedissonAutoConfiguration;\nimport org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;\nimport org.springframework.boot.test.context.SpringBootTest;\nimport org.springframework.context.annotation.Import;\nimport org.springframework.test.context.ActiveProfiles;\n\n/**\n * 依赖内存 Redis 的单元测试\n *\n * 相比 {@link BaseDbUnitTest} 来说，从内存 DB 改成了内存 Redis\n *\n * @author yshop\n */\n@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE, classes = BaseRedisUnitTest.Application.class)\n@ActiveProfiles(\"unit-test\") // 设置使用 application-unit-test 配置文件\npublic class BaseRedisUnitTest {\n\n    @Import({\n            // Redis 配置类\n            RedisTestConfiguration.class, // Redis 测试配置类，用于启动 RedisServer\n            RedisAutoConfiguration.class, // Spring Redis 自动配置类\n            YshopRedisAutoConfiguration.class, // 自己的 Redis 配置类\n            RedissonAutoConfiguration.class, // Redisson 自动配置类\n    })\n    public static class Application {\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-test/src/main/java/co/yixiang/yshop/framework/test/core/util/AssertUtils.java",
    "content": "package co.yixiang.yshop.framework.test.core.util;\n\nimport cn.hutool.core.util.ArrayUtil;\nimport cn.hutool.core.util.ReflectUtil;\nimport co.yixiang.yshop.framework.common.exception.ErrorCode;\nimport co.yixiang.yshop.framework.common.exception.ServiceException;\nimport co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil;\nimport org.junit.jupiter.api.Assertions;\nimport org.junit.jupiter.api.function.Executable;\n\nimport java.lang.reflect.Field;\nimport java.util.Arrays;\nimport java.util.Objects;\n\nimport static org.junit.jupiter.api.Assertions.assertThrows;\n\n/**\n * 单元测试，assert 断言工具类\n *\n * @author yshop\n */\npublic class AssertUtils {\n\n    /**\n     * 比对两个对象的属性是否一致\n     *\n     * 注意，如果 expected 存在的属性，actual 不存在的时候，会进行忽略\n     *\n     * @param expected 期望对象\n     * @param actual 实际对象\n     * @param ignoreFields 忽略的属性数组\n     */\n    public static void assertPojoEquals(Object expected, Object actual, String... ignoreFields) {\n        Field[] expectedFields = ReflectUtil.getFields(expected.getClass());\n        Arrays.stream(expectedFields).forEach(expectedField -> {\n            // 忽略 jacoco 自动生成的 $jacocoData 属性的情况\n            if (expectedField.isSynthetic()) {\n                return;\n            }\n            // 如果是忽略的属性，则不进行比对\n            if (ArrayUtil.contains(ignoreFields, expectedField.getName())) {\n                return;\n            }\n            // 忽略不存在的属性\n            Field actualField = ReflectUtil.getField(actual.getClass(), expectedField.getName());\n            if (actualField == null) {\n                return;\n            }\n            // 比对\n            Assertions.assertEquals(\n                    ReflectUtil.getFieldValue(expected, expectedField),\n                    ReflectUtil.getFieldValue(actual, actualField),\n                    String.format(\"Field(%s) 不匹配\", expectedField.getName())\n            );\n        });\n    }\n\n    /**\n     * 比对两个对象的属性是否一致\n     *\n     * 注意，如果 expected 存在的属性，actual 不存在的时候，会进行忽略\n     *\n     * @param expected 期望对象\n     * @param actual 实际对象\n     * @param ignoreFields 忽略的属性数组\n     * @return 是否一致\n     */\n    public static boolean isPojoEquals(Object expected, Object actual, String... ignoreFields) {\n        Field[] expectedFields = ReflectUtil.getFields(expected.getClass());\n        return Arrays.stream(expectedFields).allMatch(expectedField -> {\n            // 如果是忽略的属性，则不进行比对\n            if (ArrayUtil.contains(ignoreFields, expectedField.getName())) {\n                return true;\n            }\n            // 忽略不存在的属性\n            Field actualField = ReflectUtil.getField(actual.getClass(), expectedField.getName());\n            if (actualField == null) {\n                return true;\n            }\n            return Objects.equals(ReflectUtil.getFieldValue(expected, expectedField),\n                    ReflectUtil.getFieldValue(actual, actualField));\n        });\n    }\n\n    /**\n     * 执行方法，校验抛出的 Service 是否符合条件\n     *\n     * @param executable 业务异常\n     * @param errorCode 错误码对象\n     * @param messageParams 消息参数\n     */\n    public static void assertServiceException(Executable executable, ErrorCode errorCode, Object... messageParams) {\n        // 调用方法\n        ServiceException serviceException = assertThrows(ServiceException.class, executable);\n        // 校验错误码\n        Assertions.assertEquals(errorCode.getCode(), serviceException.getCode(), \"错误码不匹配\");\n        String message = ServiceExceptionUtil.doFormat(errorCode.getCode(), errorCode.getMsg(), messageParams);\n        Assertions.assertEquals(message, serviceException.getMessage(), \"错误提示不匹配\");\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-test/src/main/java/co/yixiang/yshop/framework/test/core/util/RandomUtils.java",
    "content": "package co.yixiang.yshop.framework.test.core.util;\n\nimport cn.hutool.core.date.LocalDateTimeUtil;\nimport cn.hutool.core.util.ArrayUtil;\nimport cn.hutool.core.util.RandomUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport uk.co.jemos.podam.api.PodamFactory;\nimport uk.co.jemos.podam.api.PodamFactoryImpl;\n\nimport java.lang.reflect.Type;\nimport java.time.LocalDateTime;\nimport java.util.Arrays;\nimport java.util.Date;\nimport java.util.List;\nimport java.util.Set;\nimport java.util.function.Consumer;\nimport java.util.stream.Collectors;\nimport java.util.stream.Stream;\n\n/**\n * 随机工具类\n *\n * @author yshop\n */\npublic class RandomUtils {\n\n    private static final int RANDOM_STRING_LENGTH = 10;\n\n    private static final int TINYINT_MAX = 127;\n\n    private static final int RANDOM_DATE_MAX = 30;\n\n    private static final int RANDOM_COLLECTION_LENGTH = 5;\n\n    private static final PodamFactory PODAM_FACTORY = new PodamFactoryImpl();\n\n    static {\n        // 字符串\n        PODAM_FACTORY.getStrategy().addOrReplaceTypeManufacturer(String.class,\n                (dataProviderStrategy, attributeMetadata, map) -> randomString());\n        // Integer\n        PODAM_FACTORY.getStrategy().addOrReplaceTypeManufacturer(Integer.class, (dataProviderStrategy, attributeMetadata, map) -> {\n            // 如果是 status 的字段，返回 0 或 1\n            if (\"status\".equals(attributeMetadata.getAttributeName())) {\n                return RandomUtil.randomEle(CommonStatusEnum.values()).getStatus();\n            }\n            // 如果是 type、status 结尾的字段，返回 tinyint 范围\n            if (StrUtil.endWithAnyIgnoreCase(attributeMetadata.getAttributeName(),\n                    \"type\", \"status\", \"category\", \"scope\", \"result\")) {\n                return RandomUtil.randomInt(0, TINYINT_MAX + 1);\n            }\n            return RandomUtil.randomInt();\n        });\n        // LocalDateTime\n        PODAM_FACTORY.getStrategy().addOrReplaceTypeManufacturer(LocalDateTime.class,\n                (dataProviderStrategy, attributeMetadata, map) -> randomLocalDateTime());\n        // Boolean\n        PODAM_FACTORY.getStrategy().addOrReplaceTypeManufacturer(Boolean.class, (dataProviderStrategy, attributeMetadata, map) -> {\n            // 如果是 deleted 的字段，返回非删除\n            if (\"deleted\".equals(attributeMetadata.getAttributeName())) {\n                return false;\n            }\n            return RandomUtil.randomBoolean();\n        });\n    }\n\n    public static String randomString() {\n        return RandomUtil.randomString(RANDOM_STRING_LENGTH);\n    }\n\n    public static Long randomLongId() {\n        return RandomUtil.randomLong(0, Long.MAX_VALUE);\n    }\n\n    public static Integer randomInteger() {\n        return RandomUtil.randomInt(0, Integer.MAX_VALUE);\n    }\n\n    public static Date randomDate() {\n        return RandomUtil.randomDay(0, RANDOM_DATE_MAX);\n    }\n\n    public static LocalDateTime randomLocalDateTime() {\n        // 设置 Nano 为零的原因，避免 MySQL、H2 存储不到时间戳\n        return LocalDateTimeUtil.of(randomDate()).withNano(0);\n    }\n\n    public static Short randomShort() {\n        return (short) RandomUtil.randomInt(0, Short.MAX_VALUE);\n    }\n\n    public static <T> Set<T> randomSet(Class<T> clazz) {\n        return Stream.iterate(0, i -> i).limit(RandomUtil.randomInt(1, RANDOM_COLLECTION_LENGTH))\n                .map(i -> randomPojo(clazz)).collect(Collectors.toSet());\n    }\n\n    public static Integer randomCommonStatus() {\n        return RandomUtil.randomEle(CommonStatusEnum.values()).getStatus();\n    }\n\n    public static String randomEmail() {\n        return randomString() + \"@qq.com\";\n    }\n\n    public static String randomURL() {\n        return \"https://www.yixiang.co/\" + randomString();\n    }\n\n    @SafeVarargs\n    public static <T> T randomPojo(Class<T> clazz, Consumer<T>... consumers) {\n        T pojo = PODAM_FACTORY.manufacturePojo(clazz);\n        // 非空时，回调逻辑。通过它，可以实现 Pojo 的进一步处理\n        if (ArrayUtil.isNotEmpty(consumers)) {\n            Arrays.stream(consumers).forEach(consumer -> consumer.accept(pojo));\n        }\n        return pojo;\n    }\n\n    @SafeVarargs\n    public static <T> T randomPojo(Class<T> clazz, Type type, Consumer<T>... consumers) {\n        T pojo = PODAM_FACTORY.manufacturePojo(clazz, type);\n        // 非空时，回调逻辑。通过它，可以实现 Pojo 的进一步处理\n        if (ArrayUtil.isNotEmpty(consumers)) {\n            Arrays.stream(consumers).forEach(consumer -> consumer.accept(pojo));\n        }\n        return pojo;\n    }\n\n    @SafeVarargs\n    public static <T> List<T> randomPojoList(Class<T> clazz, Consumer<T>... consumers) {\n        int size = RandomUtil.randomInt(1, RANDOM_COLLECTION_LENGTH);\n        return Stream.iterate(0, i -> i).limit(size).map(o -> randomPojo(clazz, consumers))\n                .collect(Collectors.toList());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>co.yixiang.boot</groupId>\n        <artifactId>yshop-framework</artifactId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>yshop-spring-boot-starter-web</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>Web 框架，全局异常、API 日志、脱敏、错误码等</description>\n    <url>https://gitee.com/guchengwuyue/yshop-drink</url>\n\n    <dependencies>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-common</artifactId>\n        </dependency>\n\n        <!-- Web 相关 -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-web</artifactId>\n        </dependency>\n        <!-- spring boot 配置所需依赖 -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-configuration-processor</artifactId>\n            <optional>true</optional>\n        </dependency>\n\n        <dependency>\n            <groupId>com.github.xiaoymin</groupId>\n            <artifactId>knife4j-openapi3-jakarta-spring-boot-starter</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springdoc</groupId>\n            <artifactId>springdoc-openapi-starter-webmvc-api</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>org.springframework.security</groupId>\n            <artifactId>spring-security-core</artifactId>\n            <scope>provided</scope> <!-- 设置为 provided，主要是 GlobalExceptionHandler 使用 -->\n        </dependency>\n\n        <!-- 业务组件 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-infra-api</artifactId> <!-- 需要使用它，进行操作日志的记录 -->\n            <version>${revision}</version>\n        </dependency>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-system-api</artifactId> <!-- 需要使用它，进行错误码的记录 -->\n            <version>${revision}</version>\n        </dependency>\n\n        <!-- xss -->\n        <dependency>\n            <groupId>org.jsoup</groupId>\n            <artifactId>jsoup</artifactId>\n        </dependency>\n\n        <!-- Test 测试相关 -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>org.mockito</groupId>\n            <artifactId>mockito-inline</artifactId>\n            <scope>test</scope>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/apilog/config/YshopApiLogAutoConfiguration.java",
    "content": "package co.yixiang.yshop.framework.apilog.config;\n\nimport co.yixiang.yshop.framework.apilog.core.filter.ApiAccessLogFilter;\nimport co.yixiang.yshop.framework.apilog.core.interceptor.ApiAccessLogInterceptor;\nimport co.yixiang.yshop.framework.apilog.core.service.ApiAccessLogFrameworkService;\nimport co.yixiang.yshop.framework.apilog.core.service.ApiAccessLogFrameworkServiceImpl;\nimport co.yixiang.yshop.framework.apilog.core.service.ApiErrorLogFrameworkService;\nimport co.yixiang.yshop.framework.apilog.core.service.ApiErrorLogFrameworkServiceImpl;\nimport co.yixiang.yshop.framework.common.enums.WebFilterOrderEnum;\nimport co.yixiang.yshop.framework.web.config.WebProperties;\nimport co.yixiang.yshop.framework.web.config.YshopWebAutoConfiguration;\nimport co.yixiang.yshop.module.infra.api.logger.ApiAccessLogApi;\nimport co.yixiang.yshop.module.infra.api.logger.ApiErrorLogApi;\nimport jakarta.servlet.Filter;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.boot.autoconfigure.AutoConfiguration;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.boot.web.servlet.FilterRegistrationBean;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.web.servlet.config.annotation.InterceptorRegistry;\nimport org.springframework.web.servlet.config.annotation.WebMvcConfigurer;\n\n@AutoConfiguration(after = YshopWebAutoConfiguration.class)\npublic class YshopApiLogAutoConfiguration implements WebMvcConfigurer {\n\n    @Bean\n    @SuppressWarnings(\"SpringJavaInjectionPointsAutowiringInspection\")\n    public ApiAccessLogFrameworkService apiAccessLogFrameworkService(ApiAccessLogApi apiAccessLogApi) {\n        return new ApiAccessLogFrameworkServiceImpl(apiAccessLogApi);\n    }\n\n    @Bean\n    @SuppressWarnings(\"SpringJavaInjectionPointsAutowiringInspection\")\n    public ApiErrorLogFrameworkService apiErrorLogFrameworkService(ApiErrorLogApi apiErrorLogApi) {\n        return new ApiErrorLogFrameworkServiceImpl(apiErrorLogApi);\n    }\n\n    /**\n     * 创建 ApiAccessLogFilter Bean，记录 API 请求日志\n     */\n    @Bean\n    @ConditionalOnProperty(prefix = \"yshop.access-log\", value = \"enable\", matchIfMissing = true) // 允许使用 yshop.access-log.enable=false 禁用访问日志\n    public FilterRegistrationBean<ApiAccessLogFilter> apiAccessLogFilter(WebProperties webProperties,\n                                                                         @Value(\"${spring.application.name}\") String applicationName,\n                                                                         ApiAccessLogFrameworkService apiAccessLogFrameworkService) {\n        ApiAccessLogFilter filter = new ApiAccessLogFilter(webProperties, applicationName, apiAccessLogFrameworkService);\n        return createFilterBean(filter, WebFilterOrderEnum.API_ACCESS_LOG_FILTER);\n    }\n\n    private static <T extends Filter> FilterRegistrationBean<T> createFilterBean(T filter, Integer order) {\n        FilterRegistrationBean<T> bean = new FilterRegistrationBean<>(filter);\n        bean.setOrder(order);\n        return bean;\n    }\n\n    @Override\n    public void addInterceptors(InterceptorRegistry registry) {\n        registry.addInterceptor(new ApiAccessLogInterceptor());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/apilog/core/annotation/ApiAccessLog.java",
    "content": "package co.yixiang.yshop.framework.apilog.core.annotation;\n\nimport co.yixiang.yshop.framework.apilog.core.enums.OperateTypeEnum;\n\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * 访问日志注解\n *\n * @author yshop\n */\n@Target({ElementType.METHOD})\n@Retention(RetentionPolicy.RUNTIME)\npublic @interface ApiAccessLog {\n\n    // ========== 开关字段 ==========\n\n    /**\n     * 是否记录访问日志\n     */\n    boolean enable() default true;\n    /**\n     * 是否记录请求参数\n     *\n     * 默认记录，主要考虑请求数据一般不大。可手动设置为 false 进行关闭\n     */\n    boolean requestEnable() default true;\n    /**\n     * 是否记录响应结果\n     *\n     * 默认不记录，主要考虑响应数据可能比较大。可手动设置为 true 进行打开\n     */\n    boolean responseEnable() default false;\n    /**\n     * 敏感参数数组\n     *\n     * 添加后，请求参数、响应结果不会记录该参数\n     */\n    String[] sanitizeKeys() default {};\n\n    // ========== 模块字段 ==========\n\n    /**\n     * 操作模块\n     *\n     * 为空时，会尝试读取 {@link io.swagger.v3.oas.annotations.tags.Tag#name()} 属性\n     */\n    String operateModule() default \"\";\n    /**\n     * 操作名\n     *\n     * 为空时，会尝试读取 {@link io.swagger.v3.oas.annotations.Operation#summary()} 属性\n     */\n    String operateName() default \"\";\n    /**\n     * 操作分类\n     *\n     * 实际并不是数组，因为枚举不能设置 null 作为默认值\n     */\n    OperateTypeEnum[] operateType() default {};\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/apilog/core/enums/OperateTypeEnum.java",
    "content": "package co.yixiang.yshop.framework.apilog.core.enums;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * 操作日志的操作类型\n *\n * @author ruoyi\n */\n@Getter\n@AllArgsConstructor\npublic enum OperateTypeEnum {\n\n    /**\n     * 查询\n     */\n    GET(1),\n    /**\n     * 新增\n     */\n    CREATE(2),\n    /**\n     * 修改\n     */\n    UPDATE(3),\n    /**\n     * 删除\n     */\n    DELETE(4),\n    /**\n     * 导出\n     */\n    EXPORT(5),\n    /**\n     * 导入\n     */\n    IMPORT(6),\n    /**\n     * 其它\n     *\n     * 在无法归类时，可以选择使用其它。因为还有操作名可以进一步标识\n     */\n    OTHER(0);\n\n    /**\n     * 类型\n     */\n    private final Integer type;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/apilog/core/filter/ApiAccessLogFilter.java",
    "content": "package co.yixiang.yshop.framework.apilog.core.filter;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.date.LocalDateTimeUtil;\nimport cn.hutool.core.exceptions.ExceptionUtil;\nimport cn.hutool.core.map.MapUtil;\nimport cn.hutool.core.util.ArrayUtil;\nimport cn.hutool.core.util.BooleanUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.apilog.core.annotation.ApiAccessLog;\nimport co.yixiang.yshop.framework.apilog.core.enums.OperateTypeEnum;\nimport co.yixiang.yshop.framework.apilog.core.service.ApiAccessLogFrameworkService;\nimport co.yixiang.yshop.framework.common.exception.enums.GlobalErrorCodeConstants;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.util.json.JsonUtils;\nimport co.yixiang.yshop.framework.common.util.monitor.TracerUtils;\nimport co.yixiang.yshop.framework.common.util.servlet.ServletUtils;\nimport co.yixiang.yshop.framework.web.config.WebProperties;\nimport co.yixiang.yshop.framework.web.core.filter.ApiRequestFilter;\nimport co.yixiang.yshop.framework.web.core.util.WebFrameworkUtils;\nimport co.yixiang.yshop.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO;\nimport com.fasterxml.jackson.databind.JsonNode;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.servlet.FilterChain;\nimport jakarta.servlet.ServletException;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.web.bind.annotation.RequestMethod;\nimport org.springframework.web.method.HandlerMethod;\n\nimport java.io.IOException;\nimport java.time.LocalDateTime;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Iterator;\nimport java.util.Map;\n\nimport static co.yixiang.yshop.framework.apilog.core.interceptor.ApiAccessLogInterceptor.*;\nimport static co.yixiang.yshop.framework.common.util.json.JsonUtils.toJsonString;\n\n/**\n * API 访问日志 Filter\n *\n * 目的：记录 API 访问日志到数据库中\n *\n * @author yshop\n */\n@Slf4j\npublic class ApiAccessLogFilter extends ApiRequestFilter {\n\n    private static final String[] SANITIZE_KEYS = new String[]{\"password\", \"token\", \"accessToken\", \"refreshToken\"};\n\n    private final String applicationName;\n\n    private final ApiAccessLogFrameworkService apiAccessLogFrameworkService;\n\n    public ApiAccessLogFilter(WebProperties webProperties, String applicationName, ApiAccessLogFrameworkService apiAccessLogFrameworkService) {\n        super(webProperties);\n        this.applicationName = applicationName;\n        this.apiAccessLogFrameworkService = apiAccessLogFrameworkService;\n    }\n\n    @Override\n    @SuppressWarnings(\"NullableProblems\")\n    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)\n            throws ServletException, IOException {\n        // 获得开始时间\n        LocalDateTime beginTime = LocalDateTime.now();\n        // 提前获得参数，避免 XssFilter 过滤处理\n        Map<String, String> queryString = ServletUtils.getParamMap(request);\n        String requestBody = ServletUtils.isJsonRequest(request) ? ServletUtils.getBody(request) : null;\n\n        try {\n            // 继续过滤器\n            filterChain.doFilter(request, response);\n            // 正常执行，记录日志\n            createApiAccessLog(request, beginTime, queryString, requestBody, null);\n        } catch (Exception ex) {\n            // 异常执行，记录日志\n            createApiAccessLog(request, beginTime, queryString, requestBody, ex);\n            throw ex;\n        }\n    }\n\n    private void createApiAccessLog(HttpServletRequest request, LocalDateTime beginTime,\n                                    Map<String, String> queryString, String requestBody, Exception ex) {\n        ApiAccessLogCreateReqDTO accessLog = new ApiAccessLogCreateReqDTO();\n        try {\n            boolean enable = buildApiAccessLog(accessLog, request, beginTime, queryString, requestBody, ex);\n            if (!enable) {\n                return;\n            }\n            apiAccessLogFrameworkService.createApiAccessLog(accessLog);\n        } catch (Throwable th) {\n            log.error(\"[createApiAccessLog][url({}) log({}) 发生异常]\", request.getRequestURI(), toJsonString(accessLog), th);\n        }\n    }\n\n    private boolean buildApiAccessLog(ApiAccessLogCreateReqDTO accessLog, HttpServletRequest request, LocalDateTime beginTime,\n                                      Map<String, String> queryString, String requestBody, Exception ex) {\n        // 判断：是否要记录操作日志\n        HandlerMethod handlerMethod = (HandlerMethod) request.getAttribute(ATTRIBUTE_HANDLER_METHOD);\n        ApiAccessLog accessLogAnnotation = null;\n        if (handlerMethod != null) {\n            accessLogAnnotation = handlerMethod.getMethodAnnotation(ApiAccessLog.class);\n            if (accessLogAnnotation != null && BooleanUtil.isFalse(accessLogAnnotation.enable())) {\n                return false;\n            }\n        }\n\n        // 处理用户信息\n        accessLog.setUserId(WebFrameworkUtils.getLoginUserId(request))\n                .setUserType(WebFrameworkUtils.getLoginUserType(request));\n        // 设置访问结果\n        CommonResult<?> result = WebFrameworkUtils.getCommonResult(request);\n        if (result != null) {\n            accessLog.setResultCode(result.getCode()).setResultMsg(result.getMsg());\n        } else if (ex != null) {\n            accessLog.setResultCode(GlobalErrorCodeConstants.INTERNAL_SERVER_ERROR.getCode())\n                    .setResultMsg(ExceptionUtil.getRootCauseMessage(ex));\n        } else {\n            accessLog.setResultCode(GlobalErrorCodeConstants.SUCCESS.getCode()).setResultMsg(\"\");\n        }\n        // 设置请求字段\n        accessLog.setTraceId(TracerUtils.getTraceId()).setApplicationName(applicationName)\n                .setRequestUrl(request.getRequestURI()).setRequestMethod(request.getMethod())\n                .setUserAgent(ServletUtils.getUserAgent(request)).setUserIp(ServletUtils.getClientIP(request));\n        String[] sanitizeKeys = accessLogAnnotation != null ? accessLogAnnotation.sanitizeKeys() : null;\n        Boolean requestEnable = accessLogAnnotation != null ? accessLogAnnotation.requestEnable() : Boolean.TRUE;\n        if (!BooleanUtil.isFalse(requestEnable)) { // 默认记录，所以判断 !false\n            Map<String, Object> requestParams = MapUtil.<String, Object>builder()\n                    .put(\"query\", sanitizeMap(queryString, sanitizeKeys))\n                    .put(\"body\", sanitizeJson(requestBody, sanitizeKeys)).build();\n            accessLog.setRequestParams(toJsonString(requestParams));\n        }\n        Boolean responseEnable = accessLogAnnotation != null ? accessLogAnnotation.responseEnable() : Boolean.FALSE;\n        if (BooleanUtil.isTrue(responseEnable)) { // 默认不记录，默认强制要求 true\n            accessLog.setResponseBody(sanitizeJson(result, sanitizeKeys));\n        }\n        // 持续时间\n        accessLog.setBeginTime(beginTime).setEndTime(LocalDateTime.now())\n                .setDuration((int) LocalDateTimeUtil.between(accessLog.getBeginTime(), accessLog.getEndTime(), ChronoUnit.MILLIS));\n\n        // 操作模块\n        if (handlerMethod != null) {\n            Tag tagAnnotation = handlerMethod.getBeanType().getAnnotation(Tag.class);\n            Operation operationAnnotation = handlerMethod.getMethodAnnotation(Operation.class);\n            String operateModule = accessLogAnnotation != null ? accessLogAnnotation.operateModule() :\n                    tagAnnotation != null ? StrUtil.nullToDefault(tagAnnotation.name(), tagAnnotation.description()) : null;\n            String operateName = accessLogAnnotation != null ? accessLogAnnotation.operateName() :\n                    operationAnnotation != null ? operationAnnotation.summary() : null;\n            OperateTypeEnum operateType = accessLogAnnotation != null && accessLogAnnotation.operateType().length > 0 ?\n                    accessLogAnnotation.operateType()[0] : parseOperateLogType(request);\n            accessLog.setOperateModule(operateModule).setOperateName(operateName).setOperateType(operateType.getType());\n        }\n        return true;\n    }\n\n    // ========== 解析 @ApiAccessLog、@Swagger 注解  ==========\n\n    private static OperateTypeEnum parseOperateLogType(HttpServletRequest request) {\n        RequestMethod requestMethod = RequestMethod.resolve(request.getMethod());\n        if (requestMethod == null) {\n            return OperateTypeEnum.OTHER;\n        }\n        switch (requestMethod) {\n            case GET:\n                return OperateTypeEnum.GET;\n            case POST:\n                return OperateTypeEnum.CREATE;\n            case PUT:\n                return OperateTypeEnum.UPDATE;\n            case DELETE:\n                return OperateTypeEnum.DELETE;\n            default:\n                return OperateTypeEnum.OTHER;\n        }\n    }\n\n    // ========== 请求和响应的脱敏逻辑，移除类似 password、token 等敏感字段 ==========\n\n    private static String sanitizeMap(Map<String, ?> map, String[] sanitizeKeys) {\n        if (CollUtil.isNotEmpty(map)) {\n            return null;\n        }\n        if (sanitizeKeys != null) {\n            MapUtil.removeAny(map, sanitizeKeys);\n        }\n        MapUtil.removeAny(map, SANITIZE_KEYS);\n        return JsonUtils.toJsonString(map);\n    }\n\n    private static String sanitizeJson(String jsonString, String[] sanitizeKeys) {\n        if (StrUtil.isEmpty(jsonString)) {\n            return null;\n        }\n        try {\n            JsonNode rootNode = JsonUtils.parseTree(jsonString);\n            sanitizeJson(rootNode, sanitizeKeys);\n            return JsonUtils.toJsonString(rootNode);\n        } catch (Exception e) {\n            // 脱敏失败的情况下，直接忽略异常，避免影响用户请求\n            log.error(\"[sanitizeJson][脱敏({}) 发生异常]\", jsonString, e);\n            return jsonString;\n        }\n    }\n\n    private static String sanitizeJson(CommonResult<?> commonResult, String[] sanitizeKeys) {\n        if (commonResult == null) {\n            return null;\n        }\n        String jsonString = toJsonString(commonResult);\n        try {\n            JsonNode rootNode = JsonUtils.parseTree(jsonString);\n            sanitizeJson(rootNode.get(\"data\"), sanitizeKeys); // 只处理 data 字段，不处理 code、msg 字段，避免错误被脱敏掉\n            return JsonUtils.toJsonString(rootNode);\n        } catch (Exception e) {\n            // 脱敏失败的情况下，直接忽略异常，避免影响用户请求\n            log.error(\"[sanitizeJson][脱敏({}) 发生异常]\", jsonString, e);\n            return jsonString;\n        }\n    }\n\n    private static void sanitizeJson(JsonNode node, String[] sanitizeKeys) {\n        // 情况一：数组，遍历处理\n        if (node.isArray()) {\n            for (JsonNode childNode : node) {\n                sanitizeJson(childNode, sanitizeKeys);\n            }\n            return;\n        }\n        // 情况二：非 Object，只是某个值，直接返回\n        if (!node.isObject()) {\n            return;\n        }\n        //  情况三：Object，遍历处理\n        Iterator<Map.Entry<String, JsonNode>> iterator = node.properties().iterator();\n        while (iterator.hasNext()) {\n            Map.Entry<String, JsonNode> entry = iterator.next();\n            if (ArrayUtil.contains(sanitizeKeys, entry.getKey())\n                || ArrayUtil.contains(SANITIZE_KEYS, entry.getKey())) {\n                iterator.remove();\n                continue;\n            }\n            sanitizeJson(entry.getValue(), sanitizeKeys);\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/apilog/core/interceptor/ApiAccessLogInterceptor.java",
    "content": "package co.yixiang.yshop.framework.apilog.core.interceptor;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.util.servlet.ServletUtils;\nimport co.yixiang.yshop.framework.common.util.spring.SpringUtils;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.util.StopWatch;\nimport org.springframework.web.method.HandlerMethod;\nimport org.springframework.web.servlet.HandlerInterceptor;\n\nimport java.util.Map;\n\n/**\n * API 访问日志 Interceptor\n *\n * 目的：在非 prod 环境时，打印 request 和 response 两条日志到日志文件（控制台）中。\n *\n * @author yshop\n */\n@Slf4j\npublic class ApiAccessLogInterceptor implements HandlerInterceptor {\n\n    public static final String ATTRIBUTE_HANDLER_METHOD = \"HANDLER_METHOD\";\n\n    private static final String ATTRIBUTE_STOP_WATCH = \"ApiAccessLogInterceptor.StopWatch\";\n\n    @Override\n    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {\n        // 记录 HandlerMethod，提供给 ApiAccessLogFilter 使用\n        HandlerMethod handlerMethod = handler instanceof HandlerMethod ? (HandlerMethod) handler : null;\n        if (handlerMethod != null) {\n            request.setAttribute(ATTRIBUTE_HANDLER_METHOD, handlerMethod);\n        }\n\n        // 打印 request 日志\n        if (!SpringUtils.isProd()) {\n            Map<String, String> queryString = ServletUtils.getParamMap(request);\n            String requestBody = ServletUtils.isJsonRequest(request) ? ServletUtils.getBody(request) : null;\n            if (CollUtil.isEmpty(queryString) && StrUtil.isEmpty(requestBody)) {\n                log.info(\"[preHandle][开始请求 URL({}) 无参数]\", request.getRequestURI());\n            } else {\n                log.info(\"[preHandle][开始请求 URL({}) 参数({})]\", request.getRequestURI(),\n                        StrUtil.blankToDefault(requestBody, queryString.toString()));\n            }\n            // 计时\n            StopWatch stopWatch = new StopWatch();\n            stopWatch.start();\n            request.setAttribute(ATTRIBUTE_STOP_WATCH, stopWatch);\n        }\n        return true;\n    }\n\n    @Override\n    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {\n        // 打印 response 日志\n        if (!SpringUtils.isProd()) {\n            StopWatch stopWatch = (StopWatch) request.getAttribute(ATTRIBUTE_STOP_WATCH);\n            stopWatch.stop();\n            log.info(\"[afterCompletion][完成请求 URL({}) 耗时({} ms)]\",\n                    request.getRequestURI(), stopWatch.getTotalTimeMillis());\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/apilog/core/service/ApiAccessLogFrameworkService.java",
    "content": "package co.yixiang.yshop.framework.apilog.core.service;\n\nimport co.yixiang.yshop.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO;\n\n/**\n * API 访问日志 Framework Service 接口\n *\n * @author yshop\n */\npublic interface ApiAccessLogFrameworkService {\n\n    /**\n     * 创建 API 访问日志\n     *\n     * @param reqDTO API 访问日志\n     */\n    void createApiAccessLog(ApiAccessLogCreateReqDTO reqDTO);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/apilog/core/service/ApiAccessLogFrameworkServiceImpl.java",
    "content": "package co.yixiang.yshop.framework.apilog.core.service;\n\nimport co.yixiang.yshop.module.infra.api.logger.ApiAccessLogApi;\nimport co.yixiang.yshop.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.scheduling.annotation.Async;\n\n/**\n * API 访问日志 Framework Service 实现类\n *\n * 基于 {@link ApiAccessLogApi} 服务，记录访问日志\n *\n * @author yshop\n */\n@RequiredArgsConstructor\npublic class ApiAccessLogFrameworkServiceImpl implements ApiAccessLogFrameworkService {\n\n    private final ApiAccessLogApi apiAccessLogApi;\n\n    @Override\n    @Async\n    public void createApiAccessLog(ApiAccessLogCreateReqDTO reqDTO) {\n        apiAccessLogApi.createApiAccessLog(reqDTO);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/apilog/core/service/ApiErrorLogFrameworkService.java",
    "content": "package co.yixiang.yshop.framework.apilog.core.service;\n\nimport co.yixiang.yshop.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;\n\n/**\n * API 错误日志 Framework Service 接口\n *\n * @author yshop\n */\npublic interface ApiErrorLogFrameworkService {\n\n    /**\n     * 创建 API 错误日志\n     *\n     * @param reqDTO API 错误日志\n     */\n    void createApiErrorLog(ApiErrorLogCreateReqDTO reqDTO);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/apilog/core/service/ApiErrorLogFrameworkServiceImpl.java",
    "content": "package co.yixiang.yshop.framework.apilog.core.service;\n\nimport co.yixiang.yshop.module.infra.api.logger.ApiErrorLogApi;\nimport co.yixiang.yshop.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.scheduling.annotation.Async;\n\n/**\n * API 错误日志 Framework Service 实现类\n *\n * 基于 {@link ApiErrorLogApi} 服务，记录错误日志\n *\n * @author yshop\n */\n@RequiredArgsConstructor\npublic class ApiErrorLogFrameworkServiceImpl implements ApiErrorLogFrameworkService {\n\n    private final ApiErrorLogApi apiErrorLogApi;\n\n    @Override\n    @Async\n    public void createApiErrorLog(ApiErrorLogCreateReqDTO reqDTO) {\n        apiErrorLogApi.createApiErrorLog(reqDTO);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/banner/config/YshopBannerAutoConfiguration.java",
    "content": "package co.yixiang.yshop.framework.banner.config;\n\nimport co.yixiang.yshop.framework.banner.core.BannerApplicationRunner;\nimport org.springframework.boot.autoconfigure.AutoConfiguration;\nimport org.springframework.context.annotation.Bean;\n\n/**\n * Banner 的自动配置类\n *\n * @author yshop\n */\n@AutoConfiguration\npublic class YshopBannerAutoConfiguration {\n\n    @Bean\n    public BannerApplicationRunner bannerApplicationRunner() {\n        return new BannerApplicationRunner();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/banner/core/BannerApplicationRunner.java",
    "content": "package co.yixiang.yshop.framework.banner.core;\n\nimport cn.hutool.core.thread.ThreadUtil;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.boot.ApplicationArguments;\nimport org.springframework.boot.ApplicationRunner;\nimport org.springframework.util.ClassUtils;\n\nimport java.util.concurrent.TimeUnit;\n\n/**\n * 项目启动成功后，提供文档相关的地址\n *\n * @author yshop\n */\n@Slf4j\npublic class BannerApplicationRunner implements ApplicationRunner {\n\n    @Override\n    public void run(ApplicationArguments args) {\n        ThreadUtil.execute(() -> {\n            ThreadUtil.sleep(1, TimeUnit.SECONDS); // 延迟 1 秒，保证输出到结尾\n            log.info(\"\\n----------------------------------------------------------\\n\\t\" +\n                            \"项目启动成功！\\n\\t\" +\n                            \"接口文档: \\t{} \\n\\t\" +\n                            \"开发文档: \\t{} \\n\\t\" +\n                            \"----------------------------------------------------------\",\n                    \"https://www.yixiang.co/api-doc/\",\n                    \"https://www.yixiang.co\");\n\n\n        });\n    }\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/desensitize/core/base/annotation/DesensitizeBy.java",
    "content": "package co.yixiang.yshop.framework.desensitize.core.base.annotation;\n\nimport co.yixiang.yshop.framework.desensitize.core.base.handler.DesensitizationHandler;\nimport co.yixiang.yshop.framework.desensitize.core.base.serializer.StringDesensitizeSerializer;\nimport com.fasterxml.jackson.annotation.JacksonAnnotationsInside;\nimport com.fasterxml.jackson.databind.annotation.JsonSerialize;\n\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * 顶级脱敏注解，自定义注解需要使用此注解\n *\n * @author gaibu\n */\n@Documented\n@Target(ElementType.ANNOTATION_TYPE)\n@Retention(RetentionPolicy.RUNTIME)\n@JacksonAnnotationsInside // 此注解是其他所有 jackson 注解的元注解，打上了此注解的注解表明是 jackson 注解的一部分\n@JsonSerialize(using = StringDesensitizeSerializer.class) // 指定序列化器\npublic @interface DesensitizeBy {\n\n    /**\n     * 脱敏处理器\n     */\n    @SuppressWarnings(\"rawtypes\")\n    Class<? extends DesensitizationHandler> handler();\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/desensitize/core/base/handler/DesensitizationHandler.java",
    "content": "package co.yixiang.yshop.framework.desensitize.core.base.handler;\n\nimport java.lang.annotation.Annotation;\n\n/**\n * 脱敏处理器接口\n *\n * @author gaibu\n */\npublic interface DesensitizationHandler<T extends Annotation> {\n\n    /**\n     * 脱敏\n     *\n     * @param origin     原始字符串\n     * @param annotation 注解信息\n     * @return 脱敏后的字符串\n     */\n    String desensitize(String origin, T annotation);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/desensitize/core/base/serializer/StringDesensitizeSerializer.java",
    "content": "package co.yixiang.yshop.framework.desensitize.core.base.serializer;\n\nimport cn.hutool.core.annotation.AnnotationUtil;\nimport cn.hutool.core.lang.Singleton;\nimport cn.hutool.core.util.ArrayUtil;\nimport cn.hutool.core.util.ReflectUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.desensitize.core.base.annotation.DesensitizeBy;\nimport co.yixiang.yshop.framework.desensitize.core.base.handler.DesensitizationHandler;\nimport com.fasterxml.jackson.core.JsonGenerator;\nimport com.fasterxml.jackson.databind.BeanProperty;\nimport com.fasterxml.jackson.databind.JsonSerializer;\nimport com.fasterxml.jackson.databind.SerializerProvider;\nimport com.fasterxml.jackson.databind.ser.ContextualSerializer;\nimport com.fasterxml.jackson.databind.ser.std.StdSerializer;\nimport lombok.Getter;\nimport lombok.Setter;\n\nimport java.io.IOException;\nimport java.lang.annotation.Annotation;\nimport java.lang.reflect.Field;\n\n/**\n * 脱敏序列化器\n *\n * 实现 JSON 返回数据时，使用 {@link DesensitizationHandler} 对声明脱敏注解的字段，进行脱敏处理。\n *\n * @author gaibu\n */\n@SuppressWarnings(\"rawtypes\")\npublic class StringDesensitizeSerializer extends StdSerializer<String> implements ContextualSerializer {\n\n    @Getter\n    @Setter\n    private DesensitizationHandler desensitizationHandler;\n\n    protected StringDesensitizeSerializer() {\n        super(String.class);\n    }\n\n    @Override\n    public JsonSerializer<?> createContextual(SerializerProvider serializerProvider, BeanProperty beanProperty) {\n        DesensitizeBy annotation = beanProperty.getAnnotation(DesensitizeBy.class);\n        if (annotation == null) {\n            return this;\n        }\n        // 创建一个 StringDesensitizeSerializer 对象，使用 DesensitizeBy 对应的处理器\n        StringDesensitizeSerializer serializer = new StringDesensitizeSerializer();\n        serializer.setDesensitizationHandler(Singleton.get(annotation.handler()));\n        return serializer;\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public void serialize(String value, JsonGenerator gen, SerializerProvider serializerProvider) throws IOException {\n        if (StrUtil.isBlank(value)) {\n            gen.writeNull();\n            return;\n        }\n        // 获取序列化字段\n        Field field = getField(gen);\n\n        // 自定义处理器\n        DesensitizeBy[] annotations = AnnotationUtil.getCombinationAnnotations(field, DesensitizeBy.class);\n        if (ArrayUtil.isEmpty(annotations)) {\n            gen.writeString(value);\n            return;\n        }\n        for (Annotation annotation : field.getAnnotations()) {\n            if (AnnotationUtil.hasAnnotation(annotation.annotationType(), DesensitizeBy.class)) {\n                value = this.desensitizationHandler.desensitize(value, annotation);\n                gen.writeString(value);\n                return;\n            }\n        }\n        gen.writeString(value);\n    }\n\n    /**\n     * 获取字段\n     *\n     * @param generator JsonGenerator\n     * @return 字段\n     */\n    private Field getField(JsonGenerator generator) {\n        String currentName = generator.getOutputContext().getCurrentName();\n        Object currentValue = generator.getCurrentValue();\n        Class<?> currentValueClass = currentValue.getClass();\n        return ReflectUtil.getField(currentValueClass, currentName);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/desensitize/core/regex/annotation/EmailDesensitize.java",
    "content": "package co.yixiang.yshop.framework.desensitize.core.regex.annotation;\n\nimport co.yixiang.yshop.framework.desensitize.core.base.annotation.DesensitizeBy;\nimport co.yixiang.yshop.framework.desensitize.core.regex.handler.EmailDesensitizationHandler;\nimport com.fasterxml.jackson.annotation.JacksonAnnotationsInside;\n\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * 邮箱脱敏注解\n *\n * @author gaibu\n */\n@Documented\n@Target({ElementType.FIELD})\n@Retention(RetentionPolicy.RUNTIME)\n@JacksonAnnotationsInside\n@DesensitizeBy(handler = EmailDesensitizationHandler.class)\npublic @interface EmailDesensitize {\n\n    /**\n     * 匹配的正则表达式\n     */\n    String regex() default \"(^.)[^@]*(@.*$)\";\n\n    /**\n     * 替换规则，邮箱;\n     *\n     * 比如：example@gmail.com 脱敏之后为 e****@gmail.com\n     */\n    String replacer() default \"$1****$2\";\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/desensitize/core/regex/annotation/RegexDesensitize.java",
    "content": "package co.yixiang.yshop.framework.desensitize.core.regex.annotation;\n\nimport co.yixiang.yshop.framework.desensitize.core.base.annotation.DesensitizeBy;\nimport co.yixiang.yshop.framework.desensitize.core.regex.handler.DefaultRegexDesensitizationHandler;\nimport com.fasterxml.jackson.annotation.JacksonAnnotationsInside;\n\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * 正则脱敏注解\n *\n * @author gaibu\n */\n@Documented\n@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})\n@Retention(RetentionPolicy.RUNTIME)\n@JacksonAnnotationsInside\n@DesensitizeBy(handler = DefaultRegexDesensitizationHandler.class)\npublic @interface RegexDesensitize {\n\n    /**\n     * 匹配的正则表达式（默认匹配所有）\n     */\n    String regex() default \"^[\\\\s\\\\S]*$\";\n\n    /**\n     * 替换规则，会将匹配到的字符串全部替换成 replacer\n     *\n     * 例如：regex=123; replacer=******\n     * 原始字符串 123456789\n     * 脱敏后字符串 ******456789\n     */\n    String replacer() default \"******\";\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/desensitize/core/regex/handler/AbstractRegexDesensitizationHandler.java",
    "content": "package co.yixiang.yshop.framework.desensitize.core.regex.handler;\n\nimport co.yixiang.yshop.framework.desensitize.core.base.handler.DesensitizationHandler;\n\nimport java.lang.annotation.Annotation;\n\n/**\n * 正则表达式脱敏处理器抽象类，已实现通用的方法\n *\n * @author gaibu\n */\npublic abstract class AbstractRegexDesensitizationHandler<T extends Annotation>\n        implements DesensitizationHandler<T> {\n\n    @Override\n    public String desensitize(String origin, T annotation) {\n        String regex = getRegex(annotation);\n        String replacer = getReplacer(annotation);\n        return origin.replaceAll(regex, replacer);\n    }\n\n    /**\n     * 获取注解上的 regex 参数\n     *\n     * @param annotation 注解信息\n     * @return 正则表达式\n     */\n    abstract String getRegex(T annotation);\n\n    /**\n     * 获取注解上的 replacer 参数\n     *\n     * @param annotation 注解信息\n     * @return 待替换的字符串\n     */\n    abstract String getReplacer(T annotation);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/desensitize/core/regex/handler/DefaultRegexDesensitizationHandler.java",
    "content": "package co.yixiang.yshop.framework.desensitize.core.regex.handler;\n\nimport co.yixiang.yshop.framework.desensitize.core.regex.annotation.RegexDesensitize;\n\n/**\n * {@link RegexDesensitize} 的正则脱敏处理器\n *\n * @author gaibu\n */\npublic class DefaultRegexDesensitizationHandler extends AbstractRegexDesensitizationHandler<RegexDesensitize> {\n\n    @Override\n    String getRegex(RegexDesensitize annotation) {\n        return annotation.regex();\n    }\n\n    @Override\n    String getReplacer(RegexDesensitize annotation) {\n        return annotation.replacer();\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/desensitize/core/regex/handler/EmailDesensitizationHandler.java",
    "content": "package co.yixiang.yshop.framework.desensitize.core.regex.handler;\n\nimport co.yixiang.yshop.framework.desensitize.core.regex.annotation.EmailDesensitize;\n\n/**\n * {@link EmailDesensitize} 的脱敏处理器\n *\n * @author gaibu\n */\npublic class EmailDesensitizationHandler extends AbstractRegexDesensitizationHandler<EmailDesensitize> {\n\n    @Override\n    String getRegex(EmailDesensitize annotation) {\n        return annotation.regex();\n    }\n\n    @Override\n    String getReplacer(EmailDesensitize annotation) {\n        return annotation.replacer();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/desensitize/core/slider/annotation/BankCardDesensitize.java",
    "content": "package co.yixiang.yshop.framework.desensitize.core.slider.annotation;\n\nimport co.yixiang.yshop.framework.desensitize.core.base.annotation.DesensitizeBy;\nimport co.yixiang.yshop.framework.desensitize.core.slider.handler.BankCardDesensitization;\nimport com.fasterxml.jackson.annotation.JacksonAnnotationsInside;\n\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * 银行卡号\n *\n * @author gaibu\n */\n@Documented\n@Target({ElementType.FIELD})\n@Retention(RetentionPolicy.RUNTIME)\n@JacksonAnnotationsInside\n@DesensitizeBy(handler = BankCardDesensitization.class)\npublic @interface BankCardDesensitize {\n\n    /**\n     * 前缀保留长度\n     */\n    int prefixKeep() default 6;\n\n    /**\n     * 后缀保留长度\n     */\n    int suffixKeep() default 2;\n\n    /**\n     * 替换规则，银行卡号; 比如：9988002866797031 脱敏之后为 998800********31\n     */\n    String replacer() default \"*\";\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/desensitize/core/slider/annotation/CarLicenseDesensitize.java",
    "content": "package co.yixiang.yshop.framework.desensitize.core.slider.annotation;\n\nimport co.yixiang.yshop.framework.desensitize.core.base.annotation.DesensitizeBy;\nimport co.yixiang.yshop.framework.desensitize.core.slider.handler.CarLicenseDesensitization;\nimport com.fasterxml.jackson.annotation.JacksonAnnotationsInside;\n\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * 车牌号\n *\n * @author gaibu\n */\n@Documented\n@Target({ElementType.FIELD})\n@Retention(RetentionPolicy.RUNTIME)\n@JacksonAnnotationsInside\n@DesensitizeBy(handler = CarLicenseDesensitization.class)\npublic @interface CarLicenseDesensitize {\n\n    /**\n     * 前缀保留长度\n     */\n    int prefixKeep() default 3;\n\n    /**\n     * 后缀保留长度\n     */\n    int suffixKeep() default 1;\n\n    /**\n     * 替换规则，车牌号;比如：粤A66666 脱敏之后为粤A6***6\n     */\n    String replacer() default \"*\";\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/desensitize/core/slider/annotation/ChineseNameDesensitize.java",
    "content": "package co.yixiang.yshop.framework.desensitize.core.slider.annotation;\n\nimport co.yixiang.yshop.framework.desensitize.core.base.annotation.DesensitizeBy;\nimport co.yixiang.yshop.framework.desensitize.core.slider.handler.ChineseNameDesensitization;\nimport com.fasterxml.jackson.annotation.JacksonAnnotationsInside;\n\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * 中文名\n *\n * @author gaibu\n */\n@Documented\n@Target({ElementType.FIELD})\n@Retention(RetentionPolicy.RUNTIME)\n@JacksonAnnotationsInside\n@DesensitizeBy(handler = ChineseNameDesensitization.class)\npublic @interface ChineseNameDesensitize {\n\n    /**\n     * 前缀保留长度\n     */\n    int prefixKeep() default 1;\n\n    /**\n     * 后缀保留长度\n     */\n    int suffixKeep() default 0;\n\n    /**\n     * 替换规则，中文名;比如：刘子豪脱敏之后为刘**\n     */\n    String replacer() default \"*\";\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/desensitize/core/slider/annotation/FixedPhoneDesensitize.java",
    "content": "package co.yixiang.yshop.framework.desensitize.core.slider.annotation;\n\nimport co.yixiang.yshop.framework.desensitize.core.base.annotation.DesensitizeBy;\nimport co.yixiang.yshop.framework.desensitize.core.slider.handler.FixedPhoneDesensitization;\nimport com.fasterxml.jackson.annotation.JacksonAnnotationsInside;\n\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * 固定电话\n *\n * @author gaibu\n */\n@Documented\n@Target({ElementType.FIELD})\n@Retention(RetentionPolicy.RUNTIME)\n@JacksonAnnotationsInside\n@DesensitizeBy(handler = FixedPhoneDesensitization.class)\npublic @interface FixedPhoneDesensitize {\n\n    /**\n     * 前缀保留长度\n     */\n    int prefixKeep() default 4;\n\n    /**\n     * 后缀保留长度\n     */\n    int suffixKeep() default 2;\n\n    /**\n     * 替换规则，固定电话;比如：01086551122 脱敏之后为 0108*****22\n     */\n    String replacer() default \"*\";\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/desensitize/core/slider/annotation/IdCardDesensitize.java",
    "content": "package co.yixiang.yshop.framework.desensitize.core.slider.annotation;\n\nimport co.yixiang.yshop.framework.desensitize.core.base.annotation.DesensitizeBy;\nimport co.yixiang.yshop.framework.desensitize.core.slider.handler.IdCardDesensitization;\nimport com.fasterxml.jackson.annotation.JacksonAnnotationsInside;\n\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * 身份证\n *\n * @author gaibu\n */\n@Documented\n@Target({ElementType.FIELD})\n@Retention(RetentionPolicy.RUNTIME)\n@JacksonAnnotationsInside\n@DesensitizeBy(handler = IdCardDesensitization.class)\npublic @interface IdCardDesensitize {\n\n    /**\n     * 前缀保留长度\n     */\n    int prefixKeep() default 6;\n\n    /**\n     * 后缀保留长度\n     */\n    int suffixKeep() default 2;\n\n    /**\n     * 替换规则，身份证号码;比如：530321199204074611 脱敏之后为 530321**********11\n     */\n    String replacer() default \"*\";\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/desensitize/core/slider/annotation/MobileDesensitize.java",
    "content": "package co.yixiang.yshop.framework.desensitize.core.slider.annotation;\n\nimport co.yixiang.yshop.framework.desensitize.core.base.annotation.DesensitizeBy;\nimport co.yixiang.yshop.framework.desensitize.core.slider.handler.MobileDesensitization;\nimport com.fasterxml.jackson.annotation.JacksonAnnotationsInside;\n\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * 手机号\n *\n * @author gaibu\n */\n@Documented\n@Target({ElementType.FIELD})\n@Retention(RetentionPolicy.RUNTIME)\n@JacksonAnnotationsInside\n@DesensitizeBy(handler = MobileDesensitization.class)\npublic @interface MobileDesensitize {\n\n    /**\n     * 前缀保留长度\n     */\n    int prefixKeep() default 3;\n\n    /**\n     * 后缀保留长度\n     */\n    int suffixKeep() default 4;\n\n    /**\n     * 替换规则，手机号;比如：13248765917 脱敏之后为 132****5917\n     */\n    String replacer() default \"*\";\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/desensitize/core/slider/annotation/PasswordDesensitize.java",
    "content": "package co.yixiang.yshop.framework.desensitize.core.slider.annotation;\n\nimport co.yixiang.yshop.framework.desensitize.core.base.annotation.DesensitizeBy;\nimport co.yixiang.yshop.framework.desensitize.core.slider.handler.PasswordDesensitization;\nimport com.fasterxml.jackson.annotation.JacksonAnnotationsInside;\n\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * 密码\n *\n * @author gaibu\n */\n@Documented\n@Target({ElementType.FIELD})\n@Retention(RetentionPolicy.RUNTIME)\n@JacksonAnnotationsInside\n@DesensitizeBy(handler = PasswordDesensitization.class)\npublic @interface PasswordDesensitize {\n\n    /**\n     * 前缀保留长度\n     */\n    int prefixKeep() default 0;\n\n    /**\n     * 后缀保留长度\n     */\n    int suffixKeep() default 0;\n\n    /**\n     * 替换规则，密码;\n     *\n     * 比如：123456 脱敏之后为 ******\n     */\n    String replacer() default \"*\";\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/desensitize/core/slider/annotation/SliderDesensitize.java",
    "content": "package co.yixiang.yshop.framework.desensitize.core.slider.annotation;\n\nimport co.yixiang.yshop.framework.desensitize.core.base.annotation.DesensitizeBy;\nimport co.yixiang.yshop.framework.desensitize.core.slider.handler.DefaultDesensitizationHandler;\nimport com.fasterxml.jackson.annotation.JacksonAnnotationsInside;\n\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * 滑动脱敏注解\n *\n * @author gaibu\n */\n@Documented\n@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})\n@Retention(RetentionPolicy.RUNTIME)\n@JacksonAnnotationsInside\n@DesensitizeBy(handler = DefaultDesensitizationHandler.class)\npublic @interface SliderDesensitize {\n\n    /**\n     * 后缀保留长度\n     */\n    int suffixKeep() default 0;\n\n    /**\n     * 替换规则，会将前缀后缀保留后，全部替换成 replacer\n     *\n     * 例如：prefixKeep = 1; suffixKeep = 2; replacer = \"*\";\n     * 原始字符串  123456\n     * 脱敏后     1***56\n     */\n    String replacer() default \"*\";\n\n    /**\n     * 前缀保留长度\n     */\n    int prefixKeep() default 0;\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/desensitize/core/slider/handler/AbstractSliderDesensitizationHandler.java",
    "content": "package co.yixiang.yshop.framework.desensitize.core.slider.handler;\n\nimport co.yixiang.yshop.framework.desensitize.core.base.handler.DesensitizationHandler;\n\nimport java.lang.annotation.Annotation;\n\n/**\n * 滑动脱敏处理器抽象类，已实现通用的方法\n *\n * @author gaibu\n */\npublic abstract class AbstractSliderDesensitizationHandler<T extends Annotation>\n        implements DesensitizationHandler<T> {\n\n    @Override\n    public String desensitize(String origin, T annotation) {\n        int prefixKeep = getPrefixKeep(annotation);\n        int suffixKeep = getSuffixKeep(annotation);\n        String replacer = getReplacer(annotation);\n        int length = origin.length();\n\n        // 情况一：原始字符串长度小于等于保留长度，则原始字符串全部替换\n        if (prefixKeep >= length || suffixKeep >= length) {\n            return buildReplacerByLength(replacer, length);\n        }\n\n        // 情况二：原始字符串长度小于等于前后缀保留字符串长度，则原始字符串全部替换\n        if ((prefixKeep + suffixKeep) >= length) {\n            return buildReplacerByLength(replacer, length);\n        }\n\n        // 情况三：原始字符串长度大于前后缀保留字符串长度，则替换中间字符串\n        int interval = length - prefixKeep - suffixKeep;\n        return origin.substring(0, prefixKeep) +\n                buildReplacerByLength(replacer, interval) +\n                origin.substring(prefixKeep + interval);\n    }\n\n    /**\n     * 根据长度循环构建替换符\n     *\n     * @param replacer 替换符\n     * @param length   长度\n     * @return 构建后的替换符\n     */\n    private String buildReplacerByLength(String replacer, int length) {\n        StringBuilder builder = new StringBuilder();\n        for (int i = 0; i < length; i++) {\n            builder.append(replacer);\n        }\n        return builder.toString();\n    }\n\n    /**\n     * 前缀保留长度\n     *\n     * @param annotation 注解信息\n     * @return 前缀保留长度\n     */\n    abstract Integer getPrefixKeep(T annotation);\n\n    /**\n     * 后缀保留长度\n     *\n     * @param annotation 注解信息\n     * @return 后缀保留长度\n     */\n    abstract Integer getSuffixKeep(T annotation);\n\n    /**\n     * 替换符\n     *\n     * @param annotation 注解信息\n     * @return 替换符\n     */\n    abstract String getReplacer(T annotation);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/desensitize/core/slider/handler/BankCardDesensitization.java",
    "content": "package co.yixiang.yshop.framework.desensitize.core.slider.handler;\n\nimport co.yixiang.yshop.framework.desensitize.core.slider.annotation.BankCardDesensitize;\n\n/**\n * {@link BankCardDesensitize} 的脱敏处理器\n *\n * @author gaibu\n */\npublic class BankCardDesensitization extends AbstractSliderDesensitizationHandler<BankCardDesensitize> {\n\n    @Override\n    Integer getPrefixKeep(BankCardDesensitize annotation) {\n        return annotation.prefixKeep();\n    }\n\n    @Override\n    Integer getSuffixKeep(BankCardDesensitize annotation) {\n        return annotation.suffixKeep();\n    }\n\n    @Override\n    String getReplacer(BankCardDesensitize annotation) {\n        return annotation.replacer();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/desensitize/core/slider/handler/CarLicenseDesensitization.java",
    "content": "package co.yixiang.yshop.framework.desensitize.core.slider.handler;\n\nimport co.yixiang.yshop.framework.desensitize.core.slider.annotation.CarLicenseDesensitize;\n\n/**\n * {@link CarLicenseDesensitize} 的脱敏处理器\n *\n * @author gaibu\n */\npublic class CarLicenseDesensitization extends AbstractSliderDesensitizationHandler<CarLicenseDesensitize> {\n    @Override\n    Integer getPrefixKeep(CarLicenseDesensitize annotation) {\n        return annotation.prefixKeep();\n    }\n\n    @Override\n    Integer getSuffixKeep(CarLicenseDesensitize annotation) {\n        return annotation.suffixKeep();\n    }\n\n    @Override\n    String getReplacer(CarLicenseDesensitize annotation) {\n        return annotation.replacer();\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/desensitize/core/slider/handler/ChineseNameDesensitization.java",
    "content": "package co.yixiang.yshop.framework.desensitize.core.slider.handler;\n\nimport co.yixiang.yshop.framework.desensitize.core.slider.annotation.ChineseNameDesensitize;\n\n/**\n * {@link ChineseNameDesensitize} 的脱敏处理器\n *\n * @author gaibu\n */\npublic class ChineseNameDesensitization extends AbstractSliderDesensitizationHandler<ChineseNameDesensitize> {\n\n    @Override\n    Integer getPrefixKeep(ChineseNameDesensitize annotation) {\n        return annotation.prefixKeep();\n    }\n\n    @Override\n    Integer getSuffixKeep(ChineseNameDesensitize annotation) {\n        return annotation.suffixKeep();\n    }\n\n    @Override\n    String getReplacer(ChineseNameDesensitize annotation) {\n        return annotation.replacer();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/desensitize/core/slider/handler/DefaultDesensitizationHandler.java",
    "content": "package co.yixiang.yshop.framework.desensitize.core.slider.handler;\n\nimport co.yixiang.yshop.framework.desensitize.core.slider.annotation.SliderDesensitize;\n\n/**\n * {@link SliderDesensitize} 的脱敏处理器\n *\n * @author gaibu\n */\npublic class DefaultDesensitizationHandler extends AbstractSliderDesensitizationHandler<SliderDesensitize> {\n    @Override\n    Integer getPrefixKeep(SliderDesensitize annotation) {\n        return annotation.prefixKeep();\n    }\n\n    @Override\n    Integer getSuffixKeep(SliderDesensitize annotation) {\n        return annotation.suffixKeep();\n    }\n\n    @Override\n    String getReplacer(SliderDesensitize annotation) {\n        return annotation.replacer();\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/desensitize/core/slider/handler/FixedPhoneDesensitization.java",
    "content": "package co.yixiang.yshop.framework.desensitize.core.slider.handler;\n\nimport co.yixiang.yshop.framework.desensitize.core.slider.annotation.FixedPhoneDesensitize;\n\n/**\n * {@link FixedPhoneDesensitize} 的脱敏处理器\n *\n * @author gaibu\n */\npublic class FixedPhoneDesensitization extends AbstractSliderDesensitizationHandler<FixedPhoneDesensitize> {\n    @Override\n    Integer getPrefixKeep(FixedPhoneDesensitize annotation) {\n        return annotation.prefixKeep();\n    }\n\n    @Override\n    Integer getSuffixKeep(FixedPhoneDesensitize annotation) {\n        return annotation.suffixKeep();\n    }\n\n    @Override\n    String getReplacer(FixedPhoneDesensitize annotation) {\n        return annotation.replacer();\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/desensitize/core/slider/handler/IdCardDesensitization.java",
    "content": "package co.yixiang.yshop.framework.desensitize.core.slider.handler;\n\nimport co.yixiang.yshop.framework.desensitize.core.slider.annotation.IdCardDesensitize;\n\n/**\n * {@link IdCardDesensitize} 的脱敏处理器\n *\n * @author gaibu\n */\npublic class IdCardDesensitization extends AbstractSliderDesensitizationHandler<IdCardDesensitize> {\n    @Override\n    Integer getPrefixKeep(IdCardDesensitize annotation) {\n        return annotation.prefixKeep();\n    }\n\n    @Override\n    Integer getSuffixKeep(IdCardDesensitize annotation) {\n        return annotation.suffixKeep();\n    }\n\n    @Override\n    String getReplacer(IdCardDesensitize annotation) {\n        return annotation.replacer();\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/desensitize/core/slider/handler/MobileDesensitization.java",
    "content": "package co.yixiang.yshop.framework.desensitize.core.slider.handler;\n\nimport co.yixiang.yshop.framework.desensitize.core.slider.annotation.MobileDesensitize;\n\n/**\n * {@link MobileDesensitize} 的脱敏处理器\n *\n * @author gaibu\n */\npublic class MobileDesensitization extends AbstractSliderDesensitizationHandler<MobileDesensitize> {\n\n    @Override\n    Integer getPrefixKeep(MobileDesensitize annotation) {\n        return annotation.prefixKeep();\n    }\n\n    @Override\n    Integer getSuffixKeep(MobileDesensitize annotation) {\n        return annotation.suffixKeep();\n    }\n\n    @Override\n    String getReplacer(MobileDesensitize annotation) {\n        return annotation.replacer();\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/desensitize/core/slider/handler/PasswordDesensitization.java",
    "content": "package co.yixiang.yshop.framework.desensitize.core.slider.handler;\n\nimport co.yixiang.yshop.framework.desensitize.core.slider.annotation.PasswordDesensitize;\n\n/**\n * {@link PasswordDesensitize} 的码脱敏处理器\n *\n * @author gaibu\n */\npublic class PasswordDesensitization extends AbstractSliderDesensitizationHandler<PasswordDesensitize> {\n    @Override\n    Integer getPrefixKeep(PasswordDesensitize annotation) {\n        return annotation.prefixKeep();\n    }\n\n    @Override\n    Integer getSuffixKeep(PasswordDesensitize annotation) {\n        return annotation.suffixKeep();\n    }\n\n    @Override\n    String getReplacer(PasswordDesensitize annotation) {\n        return annotation.replacer();\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/jackson/config/YshopJacksonAutoConfiguration.java",
    "content": "package co.yixiang.yshop.framework.jackson.config;\n\nimport cn.hutool.core.collection.CollUtil;\nimport co.yixiang.yshop.framework.common.util.json.JsonUtils;\nimport co.yixiang.yshop.framework.jackson.core.databind.NumberSerializer;\nimport co.yixiang.yshop.framework.jackson.core.databind.TimestampLocalDateTimeDeserializer;\nimport co.yixiang.yshop.framework.jackson.core.databind.TimestampLocalDateTimeSerializer;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport com.fasterxml.jackson.databind.module.SimpleModule;\nimport com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;\nimport com.fasterxml.jackson.datatype.jsr310.deser.LocalTimeDeserializer;\nimport com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;\nimport com.fasterxml.jackson.datatype.jsr310.ser.LocalTimeSerializer;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.boot.autoconfigure.AutoConfiguration;\nimport org.springframework.context.annotation.Bean;\n\nimport java.time.LocalDate;\nimport java.time.LocalDateTime;\nimport java.time.LocalTime;\nimport java.util.List;\n\n@AutoConfiguration\n@Slf4j\npublic class YshopJacksonAutoConfiguration {\n\n    @Bean\n    @SuppressWarnings(\"InstantiationOfUtilityClass\")\n    public JsonUtils jsonUtils(List<ObjectMapper> objectMappers) {\n        // 1.1 创建 SimpleModule 对象\n        SimpleModule simpleModule = new SimpleModule();\n        simpleModule\n                // 新增 Long 类型序列化规则，数值超过 2^53-1，在 JS 会出现精度丢失问题，因此 Long 自动序列化为字符串类型\n                .addSerializer(Long.class, NumberSerializer.INSTANCE)\n                .addSerializer(Long.TYPE, NumberSerializer.INSTANCE)\n                .addSerializer(LocalDate.class, LocalDateSerializer.INSTANCE)\n                .addDeserializer(LocalDate.class, LocalDateDeserializer.INSTANCE)\n                .addSerializer(LocalTime.class, LocalTimeSerializer.INSTANCE)\n                .addDeserializer(LocalTime.class, LocalTimeDeserializer.INSTANCE)\n                // 新增 LocalDateTime 序列化、反序列化规则，使用 Long 时间戳\n                .addSerializer(LocalDateTime.class, TimestampLocalDateTimeSerializer.INSTANCE)\n                .addDeserializer(LocalDateTime.class, TimestampLocalDateTimeDeserializer.INSTANCE);\n        // 1.2 注册到 objectMapper\n        objectMappers.forEach(objectMapper -> objectMapper.registerModule(simpleModule));\n\n        // 2. 设置 objectMapper 到 JsonUtils\n        JsonUtils.init(CollUtil.getFirst(objectMappers));\n        log.info(\"[init][初始化 JsonUtils 成功]\");\n        return new JsonUtils();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/jackson/core/databind/NumberSerializer.java",
    "content": "package co.yixiang.yshop.framework.jackson.core.databind;\n\nimport com.fasterxml.jackson.core.JsonGenerator;\nimport com.fasterxml.jackson.databind.SerializerProvider;\nimport com.fasterxml.jackson.databind.annotation.JacksonStdImpl;\n\nimport java.io.IOException;\n\n/**\n * Long 序列化规则\n *\n * 会将超长 long 值转换为 string，解决前端 JavaScript 最大安全整数是 2^53-1 的问题\n *\n * @author 星语\n */\n@JacksonStdImpl\npublic class NumberSerializer extends com.fasterxml.jackson.databind.ser.std.NumberSerializer {\n\n    private static final long MAX_SAFE_INTEGER = 9007199254740991L;\n    private static final long MIN_SAFE_INTEGER = -9007199254740991L;\n\n    public static final NumberSerializer INSTANCE = new NumberSerializer(Number.class);\n\n    public NumberSerializer(Class<? extends Number> rawType) {\n        super(rawType);\n    }\n\n    @Override\n    public void serialize(Number value, JsonGenerator gen, SerializerProvider serializers) throws IOException {\n        // 超出范围 序列化位字符串\n        if (value.longValue() > MIN_SAFE_INTEGER && value.longValue() < MAX_SAFE_INTEGER) {\n            super.serialize(value, gen, serializers);\n        } else {\n            gen.writeString(value.toString());\n        }\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/jackson/core/databind/TimestampLocalDateTimeDeserializer.java",
    "content": "package co.yixiang.yshop.framework.jackson.core.databind;\n\nimport com.fasterxml.jackson.core.JsonParser;\nimport com.fasterxml.jackson.databind.DeserializationContext;\nimport com.fasterxml.jackson.databind.JsonDeserializer;\n\nimport java.io.IOException;\nimport java.time.Instant;\nimport java.time.LocalDateTime;\nimport java.time.ZoneId;\n\n/**\n * 基于时间戳的 LocalDateTime 反序列化器\n *\n * @author 老五\n */\npublic class TimestampLocalDateTimeDeserializer extends JsonDeserializer<LocalDateTime> {\n\n    public static final TimestampLocalDateTimeDeserializer INSTANCE = new TimestampLocalDateTimeDeserializer();\n\n    @Override\n    public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {\n        // 将 Long 时间戳，转换为 LocalDateTime 对象\n        return LocalDateTime.ofInstant(Instant.ofEpochMilli(p.getValueAsLong()), ZoneId.systemDefault());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/jackson/core/databind/TimestampLocalDateTimeSerializer.java",
    "content": "package co.yixiang.yshop.framework.jackson.core.databind;\n\nimport com.fasterxml.jackson.core.JsonGenerator;\nimport com.fasterxml.jackson.databind.JsonSerializer;\nimport com.fasterxml.jackson.databind.SerializerProvider;\n\nimport java.io.IOException;\nimport java.time.LocalDateTime;\nimport java.time.ZoneId;\n\n/**\n * 基于时间戳的 LocalDateTime 序列化器\n *\n * @author 老五\n */\npublic class TimestampLocalDateTimeSerializer extends JsonSerializer<LocalDateTime> {\n\n    public static final TimestampLocalDateTimeSerializer INSTANCE = new TimestampLocalDateTimeSerializer();\n\n    @Override\n    public void serialize(LocalDateTime value, JsonGenerator gen, SerializerProvider serializers) throws IOException {\n        // 将 LocalDateTime 对象，转换为 Long 时间戳\n        gen.writeNumber(value.atZone(ZoneId.systemDefault()).toInstant().toEpochMilli());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/swagger/config/SwaggerProperties.java",
    "content": "package co.yixiang.yshop.framework.swagger.config;\n\nimport lombok.Data;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\n\nimport jakarta.validation.constraints.NotEmpty;\n\n/**\n * Swagger 配置属性\n *\n * @author yshop\n */\n@ConfigurationProperties(\"yshop.swagger\")\n@Data\npublic class SwaggerProperties {\n\n    /**\n     * 标题\n     */\n    @NotEmpty(message = \"标题不能为空\")\n    private String title;\n    /**\n     * 描述\n     */\n    @NotEmpty(message = \"描述不能为空\")\n    private String description;\n    /**\n     * 作者\n     */\n    @NotEmpty(message = \"作者不能为空\")\n    private String author;\n    /**\n     * 版本\n     */\n    @NotEmpty(message = \"版本不能为空\")\n    private String version;\n    /**\n     * url\n     */\n    @NotEmpty(message = \"扫描的 package 不能为空\")\n    private String url;\n    /**\n     * email\n     */\n    @NotEmpty(message = \"扫描的 email 不能为空\")\n    private String email;\n\n    /**\n     * license\n     */\n    @NotEmpty(message = \"扫描的 license 不能为空\")\n    private String license;\n\n    /**\n     * license-url\n     */\n    @NotEmpty(message = \"扫描的 license-url 不能为空\")\n    private String licenseUrl;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/swagger/config/YshopSwaggerAutoConfiguration.java",
    "content": "package co.yixiang.yshop.framework.swagger.config;\n\nimport io.swagger.v3.oas.models.Components;\nimport io.swagger.v3.oas.models.OpenAPI;\nimport io.swagger.v3.oas.models.info.Contact;\nimport io.swagger.v3.oas.models.info.Info;\nimport io.swagger.v3.oas.models.info.License;\nimport io.swagger.v3.oas.models.media.IntegerSchema;\nimport io.swagger.v3.oas.models.media.StringSchema;\nimport io.swagger.v3.oas.models.parameters.Parameter;\nimport io.swagger.v3.oas.models.security.SecurityRequirement;\nimport io.swagger.v3.oas.models.security.SecurityScheme;\nimport org.springdoc.core.customizers.OpenApiBuilderCustomizer;\nimport org.springdoc.core.customizers.ServerBaseUrlCustomizer;\nimport org.springdoc.core.models.GroupedOpenApi;\nimport org.springdoc.core.properties.SpringDocConfigProperties;\nimport org.springdoc.core.providers.JavadocProvider;\nimport org.springdoc.core.service.OpenAPIService;\nimport org.springdoc.core.service.SecurityService;\nimport org.springdoc.core.utils.PropertyResolverUtils;\nimport org.springframework.boot.autoconfigure.AutoConfiguration;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnClass;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Primary;\nimport org.springframework.http.HttpHeaders;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Optional;\n\nimport static co.yixiang.yshop.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;\n\n/**\n * Swagger 自动配置类，基于 OpenAPI + Springdoc 实现。\n *\n * 友情提示：\n * 1. Springdoc 文档地址：<a href=\"https://github.com/springdoc/springdoc-openapi\">仓库</a>\n * 2. Swagger 规范，于 2015 更名为 OpenAPI 规范，本质是一个东西\n *\n * @author yshop\n */\n@AutoConfiguration\n@ConditionalOnClass({OpenAPI.class})\n@EnableConfigurationProperties(SwaggerProperties.class)\n@ConditionalOnProperty(prefix = \"springdoc.api-docs\", name = \"enabled\", havingValue = \"true\", matchIfMissing = true) // 设置为 false 时，禁用\npublic class YshopSwaggerAutoConfiguration {\n\n    // ========== 全局 OpenAPI 配置 ==========\n\n    @Bean\n    public OpenAPI createApi(SwaggerProperties properties) {\n        Map<String, SecurityScheme> securitySchemas = buildSecuritySchemes();\n        OpenAPI openAPI = new OpenAPI()\n                // 接口信息\n                .info(buildInfo(properties))\n                // 接口安全配置\n                .components(new Components().securitySchemes(securitySchemas))\n                .addSecurityItem(new SecurityRequirement().addList(HttpHeaders.AUTHORIZATION));\n        securitySchemas.keySet().forEach(key -> openAPI.addSecurityItem(new SecurityRequirement().addList(key)));\n        return openAPI;\n    }\n\n    /**\n     * API 摘要信息\n     */\n    private Info buildInfo(SwaggerProperties properties) {\n        return new Info()\n                .title(properties.getTitle())\n                .description(properties.getDescription())\n                .version(properties.getVersion())\n                .contact(new Contact().name(properties.getAuthor()).url(properties.getUrl()).email(properties.getEmail()))\n                .license(new License().name(properties.getLicense()).url(properties.getLicenseUrl()));\n    }\n\n    /**\n     * 安全模式，这里配置通过请求头 Authorization 传递 token 参数\n     */\n    private Map<String, SecurityScheme> buildSecuritySchemes() {\n        Map<String, SecurityScheme> securitySchemes = new HashMap<>();\n        SecurityScheme securityScheme = new SecurityScheme()\n                .type(SecurityScheme.Type.APIKEY) // 类型\n                .name(HttpHeaders.AUTHORIZATION) // 请求头的 name\n                .in(SecurityScheme.In.HEADER); // token 所在位置\n        securitySchemes.put(HttpHeaders.AUTHORIZATION, securityScheme);\n        return securitySchemes;\n    }\n\n    /**\n     * 自定义 OpenAPI 处理器\n     */\n    @Bean\n    @Primary // 目的：以我们创建的 OpenAPIService Bean 为主，避免一键改包后，启动报错！\n    public OpenAPIService openApiBuilder(Optional<OpenAPI> openAPI,\n                                         SecurityService securityParser,\n                                         SpringDocConfigProperties springDocConfigProperties,\n                                         PropertyResolverUtils propertyResolverUtils,\n                                         Optional<List<OpenApiBuilderCustomizer>> openApiBuilderCustomizers,\n                                         Optional<List<ServerBaseUrlCustomizer>> serverBaseUrlCustomizers,\n                                         Optional<JavadocProvider> javadocProvider) {\n        return new OpenAPIService(openAPI, securityParser, springDocConfigProperties,\n                propertyResolverUtils, openApiBuilderCustomizers, serverBaseUrlCustomizers, javadocProvider);\n    }\n\n    // ========== 分组 OpenAPI 配置 ==========\n\n    /**\n     * 所有模块的 API 分组\n     */\n    @Bean\n    public GroupedOpenApi allGroupedOpenApi() {\n        return buildGroupedOpenApi(\"all\", \"\");\n    }\n\n    public static GroupedOpenApi buildGroupedOpenApi(String group) {\n        return buildGroupedOpenApi(group, group);\n    }\n\n    public static GroupedOpenApi buildGroupedOpenApi(String group, String path) {\n        return GroupedOpenApi.builder()\n                .group(group)\n                .pathsToMatch(\"/admin-api/\" + path + \"/**\", \"/app-api/\" + path + \"/**\")\n                .addOperationCustomizer((operation, handlerMethod) -> operation\n                        .addParametersItem(buildTenantHeaderParameter())\n                        .addParametersItem(buildSecurityHeaderParameter()))\n                .build();\n    }\n\n    /**\n     * 构建 Tenant 租户编号请求头参数\n     *\n     * @return 多租户参数\n     */\n    private static Parameter buildTenantHeaderParameter() {\n        return new Parameter()\n                .name(HEADER_TENANT_ID) // header 名\n                .description(\"租户编号\") // 描述\n                .in(String.valueOf(SecurityScheme.In.HEADER)) // 请求 header\n                .schema(new IntegerSchema()._default(1L).name(HEADER_TENANT_ID).description(\"租户编号\")); // 默认：使用租户编号为 1\n    }\n\n    /**\n     * 构建 Authorization 认证请求头参数\n     *\n     * 解决 Knife4j <a href=\"https://gitee.com/xiaoym/knife4j/issues/I69QBU\">Authorize 未生效，请求header里未包含参数</a>\n     *\n     * @return 认证参数\n     */\n    private static Parameter buildSecurityHeaderParameter() {\n        return new Parameter()\n                .name(HttpHeaders.AUTHORIZATION) // header 名\n                .description(\"认证 Token\") // 描述\n                .in(String.valueOf(SecurityScheme.In.HEADER)) // 请求 header\n                .schema(new StringSchema()._default(\"Bearer test1\").name(HEADER_TENANT_ID).description(\"认证 Token\")); // 默认：使用用户编号为 1\n    }\n\n}\n\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/web/config/WebProperties.java",
    "content": "package co.yixiang.yshop.framework.web.config;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.servlet.config.annotation.PathMatchConfigurer;\n\nimport jakarta.validation.Valid;\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\n\n@ConfigurationProperties(prefix = \"yshop.web\")\n@Validated\n@Data\npublic class WebProperties {\n\n    @NotNull(message = \"APP API 不能为空\")\n    private Api appApi = new Api(\"/app-api\", \"**.controller.app.**\");\n    @NotNull(message = \"Admin API 不能为空\")\n    private Api adminApi = new Api(\"/admin-api\", \"**.controller.admin.**\");\n\n    @NotNull(message = \"Admin UI 不能为空\")\n    private Ui adminUi;\n\n    @Data\n    @AllArgsConstructor\n    @NoArgsConstructor\n    @Valid\n    public static class Api {\n\n        /**\n         * API 前缀，实现所有 Controller 提供的 RESTFul API 的统一前缀\n         *\n         *\n         * 意义：通过该前缀，避免 Swagger、Actuator 意外通过 Nginx 暴露出来给外部，带来安全性问题\n         *      这样，Nginx 只需要配置转发到 /api/* 的所有接口即可。\n         *\n         * @see YshopWebAutoConfiguration#configurePathMatch(PathMatchConfigurer)\n         */\n        @NotEmpty(message = \"API 前缀不能为空\")\n        private String prefix;\n\n        /**\n         * Controller 所在包的 Ant 路径规则\n         *\n         * 主要目的是，给该 Controller 设置指定的 {@link #prefix}\n         */\n        @NotEmpty(message = \"Controller 所在包不能为空\")\n        private String controller;\n\n    }\n\n    @Data\n    @Valid\n    public static class Ui {\n\n        /**\n         * 访问地址\n         */\n        private String url;\n\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/web/config/YshopWebAutoConfiguration.java",
    "content": "package co.yixiang.yshop.framework.web.config;\n\nimport co.yixiang.yshop.framework.apilog.core.service.ApiErrorLogFrameworkService;\nimport co.yixiang.yshop.framework.common.enums.WebFilterOrderEnum;\nimport co.yixiang.yshop.framework.web.core.filter.CacheRequestBodyFilter;\nimport co.yixiang.yshop.framework.web.core.filter.DemoFilter;\nimport co.yixiang.yshop.framework.web.core.handler.GlobalExceptionHandler;\nimport co.yixiang.yshop.framework.web.core.handler.GlobalResponseBodyHandler;\nimport co.yixiang.yshop.framework.web.core.util.WebFrameworkUtils;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.boot.autoconfigure.AutoConfiguration;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfiguration;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.boot.web.client.RestTemplateBuilder;\nimport org.springframework.boot.web.servlet.FilterRegistrationBean;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.util.AntPathMatcher;\nimport org.springframework.web.bind.annotation.RestController;\nimport org.springframework.web.client.RestTemplate;\nimport org.springframework.web.cors.CorsConfiguration;\nimport org.springframework.web.cors.UrlBasedCorsConfigurationSource;\nimport org.springframework.web.filter.CorsFilter;\nimport org.springframework.web.servlet.config.annotation.PathMatchConfigurer;\nimport org.springframework.web.servlet.config.annotation.WebMvcConfigurer;\n\nimport jakarta.annotation.Resource;\nimport jakarta.servlet.Filter;\n\n@AutoConfiguration\n@EnableConfigurationProperties(WebProperties.class)\npublic class YshopWebAutoConfiguration implements WebMvcConfigurer {\n\n    @Resource\n    private WebProperties webProperties;\n    /**\n     * 应用名\n     */\n    @Value(\"${spring.application.name}\")\n    private String applicationName;\n\n    @Override\n    public void configurePathMatch(PathMatchConfigurer configurer) {\n        configurePathMatch(configurer, webProperties.getAdminApi());\n        configurePathMatch(configurer, webProperties.getAppApi());\n    }\n\n    /**\n     * 设置 API 前缀，仅仅匹配 controller 包下的\n     *\n     * @param configurer 配置\n     * @param api        API 配置\n     */\n    private void configurePathMatch(PathMatchConfigurer configurer, WebProperties.Api api) {\n        AntPathMatcher antPathMatcher = new AntPathMatcher(\".\");\n        configurer.addPathPrefix(api.getPrefix(), clazz -> clazz.isAnnotationPresent(RestController.class)\n                && antPathMatcher.match(api.getController(), clazz.getPackage().getName())); // 仅仅匹配 controller 包\n    }\n\n    @Bean\n    public GlobalExceptionHandler globalExceptionHandler(ApiErrorLogFrameworkService ApiErrorLogFrameworkService) {\n        return new GlobalExceptionHandler(applicationName, ApiErrorLogFrameworkService);\n    }\n\n    @Bean\n    public GlobalResponseBodyHandler globalResponseBodyHandler() {\n        return new GlobalResponseBodyHandler();\n    }\n\n    @Bean\n    @SuppressWarnings(\"InstantiationOfUtilityClass\")\n    public WebFrameworkUtils webFrameworkUtils(WebProperties webProperties) {\n        // 由于 WebFrameworkUtils 需要使用到 webProperties 属性，所以注册为一个 Bean\n        return new WebFrameworkUtils(webProperties);\n    }\n\n    // ========== Filter 相关 ==========\n\n    /**\n     * 创建 CorsFilter Bean，解决跨域问题\n     */\n    @Bean\n    public FilterRegistrationBean<CorsFilter> corsFilterBean() {\n        // 创建 CorsConfiguration 对象\n        CorsConfiguration config = new CorsConfiguration();\n        config.setAllowCredentials(true);\n        config.addAllowedOriginPattern(\"*\"); // 设置访问源地址\n        config.addAllowedHeader(\"*\"); // 设置访问源请求头\n        config.addAllowedMethod(\"*\"); // 设置访问源请求方法\n        // 创建 UrlBasedCorsConfigurationSource 对象\n        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();\n        source.registerCorsConfiguration(\"/**\", config); // 对接口配置跨域设置\n        return createFilterBean(new CorsFilter(source), WebFilterOrderEnum.CORS_FILTER);\n    }\n\n    /**\n     * 创建 RequestBodyCacheFilter Bean，可重复读取请求内容\n     */\n    @Bean\n    public FilterRegistrationBean<CacheRequestBodyFilter> requestBodyCacheFilter() {\n        return createFilterBean(new CacheRequestBodyFilter(), WebFilterOrderEnum.REQUEST_BODY_CACHE_FILTER);\n    }\n\n    /**\n     * 创建 DemoFilter Bean，演示模式\n     */\n    @Bean\n    @ConditionalOnProperty(value = \"yshop.demo\", havingValue = \"true\")\n    public FilterRegistrationBean<DemoFilter> demoFilter() {\n        return createFilterBean(new DemoFilter(), WebFilterOrderEnum.DEMO_FILTER);\n    }\n\n    public static <T extends Filter> FilterRegistrationBean<T> createFilterBean(T filter, Integer order) {\n        FilterRegistrationBean<T> bean = new FilterRegistrationBean<>(filter);\n        bean.setOrder(order);\n        return bean;\n    }\n\n    /**\n     * 创建 RestTemplate 实例\n     *\n     * @param restTemplateBuilder {@link RestTemplateAutoConfiguration#restTemplateBuilder}\n     */\n    @Bean\n    @ConditionalOnMissingBean\n    public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {\n        return restTemplateBuilder.build();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/web/core/filter/ApiRequestFilter.java",
    "content": "package co.yixiang.yshop.framework.web.core.filter;\n\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.web.config.WebProperties;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.web.filter.OncePerRequestFilter;\n\nimport jakarta.servlet.http.HttpServletRequest;\n\n/**\n * 过滤 /admin-api、/app-api 等 API 请求的过滤器\n *\n * @author yshop\n */\n@RequiredArgsConstructor\npublic abstract class ApiRequestFilter extends OncePerRequestFilter {\n\n    protected final WebProperties webProperties;\n\n    @Override\n    protected boolean shouldNotFilter(HttpServletRequest request) {\n        // 只过滤 API 请求的地址\n        return !StrUtil.startWithAny(request.getRequestURI(), webProperties.getAdminApi().getPrefix(),\n                webProperties.getAppApi().getPrefix());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/web/core/filter/CacheRequestBodyFilter.java",
    "content": "package co.yixiang.yshop.framework.web.core.filter;\n\nimport co.yixiang.yshop.framework.common.util.servlet.ServletUtils;\nimport org.springframework.web.filter.OncePerRequestFilter;\n\nimport jakarta.servlet.FilterChain;\nimport jakarta.servlet.ServletException;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport java.io.IOException;\n\n/**\n * Request Body 缓存 Filter，实现它的可重复读取\n *\n * @author yshop\n */\npublic class CacheRequestBodyFilter extends OncePerRequestFilter {\n\n    @Override\n    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)\n            throws IOException, ServletException {\n        filterChain.doFilter(new CacheRequestBodyWrapper(request), response);\n    }\n\n    @Override\n    protected boolean shouldNotFilter(HttpServletRequest request) {\n        // 只处理 json 请求内容\n        return !ServletUtils.isJsonRequest(request);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/web/core/filter/CacheRequestBodyWrapper.java",
    "content": "package co.yixiang.yshop.framework.web.core.filter;\n\nimport co.yixiang.yshop.framework.common.util.servlet.ServletUtils;\n\nimport jakarta.servlet.ReadListener;\nimport jakarta.servlet.ServletInputStream;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletRequestWrapper;\nimport java.io.BufferedReader;\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStreamReader;\n\n/**\n *  Request Body 缓存 Wrapper\n *\n * @author yshop\n */\npublic class CacheRequestBodyWrapper extends HttpServletRequestWrapper {\n\n    /**\n     * 缓存的内容\n     */\n    private final byte[] body;\n\n    public CacheRequestBodyWrapper(HttpServletRequest request) {\n        super(request);\n        body = ServletUtils.getBodyBytes(request);\n    }\n\n    @Override\n    public BufferedReader getReader() throws IOException {\n        return new BufferedReader(new InputStreamReader(this.getInputStream()));\n    }\n\n    @Override\n    public ServletInputStream getInputStream() throws IOException {\n        final ByteArrayInputStream inputStream = new ByteArrayInputStream(body);\n        // 返回 ServletInputStream\n        return new ServletInputStream() {\n\n            @Override\n            public int read() {\n                return inputStream.read();\n            }\n\n            @Override\n            public boolean isFinished() {\n                return false;\n            }\n\n            @Override\n            public boolean isReady() {\n                return false;\n            }\n\n            @Override\n            public void setReadListener(ReadListener readListener) {}\n\n            @Override\n            public int available() {\n                return body.length;\n            }\n\n        };\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/web/core/filter/DemoFilter.java",
    "content": "package co.yixiang.yshop.framework.web.core.filter;\n\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.util.servlet.ServletUtils;\nimport co.yixiang.yshop.framework.web.core.util.WebFrameworkUtils;\nimport org.springframework.web.filter.OncePerRequestFilter;\n\nimport jakarta.servlet.FilterChain;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\n\nimport static co.yixiang.yshop.framework.common.exception.enums.GlobalErrorCodeConstants.DEMO_DENY;\n\n/**\n * 演示 Filter，禁止用户发起写操作，避免影响测试数据\n *\n * @author yshop\n */\npublic class DemoFilter extends OncePerRequestFilter {\n\n    @Override\n    protected boolean shouldNotFilter(HttpServletRequest request) {\n        String method = request.getMethod();\n        return !StrUtil.equalsAnyIgnoreCase(method, \"POST\", \"PUT\", \"DELETE\")  // 写操作时，不进行过滤率\n                || WebFrameworkUtils.getLoginUserId(request) == null; // 非登录用户时，不进行过滤\n    }\n\n    @Override\n    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) {\n        // 直接返回 DEMO_DENY 的结果。即，请求不继续\n        ServletUtils.writeJSON(response, CommonResult.error(DEMO_DENY));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/web/core/handler/GlobalExceptionHandler.java",
    "content": "package co.yixiang.yshop.framework.web.core.handler;\n\nimport cn.hutool.core.exceptions.ExceptionUtil;\nimport cn.hutool.core.map.MapUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.apilog.core.service.ApiErrorLogFrameworkService;\nimport co.yixiang.yshop.framework.common.exception.ServiceException;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.util.collection.SetUtils;\nimport co.yixiang.yshop.framework.common.util.json.JsonUtils;\nimport co.yixiang.yshop.framework.common.util.monitor.TracerUtils;\nimport co.yixiang.yshop.framework.common.util.servlet.ServletUtils;\nimport co.yixiang.yshop.framework.web.core.util.WebFrameworkUtils;\nimport co.yixiang.yshop.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.validation.ConstraintViolation;\nimport jakarta.validation.ConstraintViolationException;\nimport jakarta.validation.ValidationException;\nimport lombok.AllArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.commons.lang3.exception.ExceptionUtils;\nimport org.springframework.security.access.AccessDeniedException;\nimport org.springframework.util.Assert;\nimport org.springframework.validation.BindException;\nimport org.springframework.validation.FieldError;\nimport org.springframework.web.HttpRequestMethodNotSupportedException;\nimport org.springframework.web.bind.MethodArgumentNotValidException;\nimport org.springframework.web.bind.MissingServletRequestParameterException;\nimport org.springframework.web.bind.annotation.ExceptionHandler;\nimport org.springframework.web.bind.annotation.RestControllerAdvice;\nimport org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;\nimport org.springframework.web.servlet.NoHandlerFoundException;\n\nimport java.time.LocalDateTime;\nimport java.util.Map;\nimport java.util.Set;\n\nimport static co.yixiang.yshop.framework.common.exception.enums.GlobalErrorCodeConstants.*;\n\n/**\n * 全局异常处理器，将 Exception 翻译成 CommonResult + 对应的异常编号\n *\n * @author yshop\n */\n@RestControllerAdvice\n@AllArgsConstructor\n@Slf4j\npublic class GlobalExceptionHandler {\n\n    /**\n     * 忽略的 ServiceException 错误提示，避免打印过多 logger\n     */\n    public static final Set<String> IGNORE_ERROR_MESSAGES = SetUtils.asSet(\"无效的刷新令牌\");\n\n    @SuppressWarnings(\"SpringJavaInjectionPointsAutowiringInspection\")\n    private final String applicationName;\n\n    private final ApiErrorLogFrameworkService apiErrorLogFrameworkService;\n\n    /**\n     * 处理所有异常，主要是提供给 Filter 使用\n     * 因为 Filter 不走 SpringMVC 的流程，但是我们又需要兜底处理异常，所以这里提供一个全量的异常处理过程，保持逻辑统一。\n     *\n     * @param request 请求\n     * @param ex 异常\n     * @return 通用返回\n     */\n    public CommonResult<?> allExceptionHandler(HttpServletRequest request, Throwable ex) {\n        if (ex instanceof MissingServletRequestParameterException) {\n            return missingServletRequestParameterExceptionHandler((MissingServletRequestParameterException) ex);\n        }\n        if (ex instanceof MethodArgumentTypeMismatchException) {\n            return methodArgumentTypeMismatchExceptionHandler((MethodArgumentTypeMismatchException) ex);\n        }\n        if (ex instanceof MethodArgumentNotValidException) {\n            return methodArgumentNotValidExceptionExceptionHandler((MethodArgumentNotValidException) ex);\n        }\n        if (ex instanceof BindException) {\n            return bindExceptionHandler((BindException) ex);\n        }\n        if (ex instanceof ConstraintViolationException) {\n            return constraintViolationExceptionHandler((ConstraintViolationException) ex);\n        }\n        if (ex instanceof ValidationException) {\n            return validationException((ValidationException) ex);\n        }\n        if (ex instanceof NoHandlerFoundException) {\n            return noHandlerFoundExceptionHandler(request, (NoHandlerFoundException) ex);\n        }\n        if (ex instanceof HttpRequestMethodNotSupportedException) {\n            return httpRequestMethodNotSupportedExceptionHandler((HttpRequestMethodNotSupportedException) ex);\n        }\n        if (ex instanceof ServiceException) {\n            return serviceExceptionHandler((ServiceException) ex);\n        }\n        if (ex instanceof AccessDeniedException) {\n            return accessDeniedExceptionHandler(request, (AccessDeniedException) ex);\n        }\n        return defaultExceptionHandler(request, ex);\n    }\n\n    /**\n     * 处理 SpringMVC 请求参数缺失\n     *\n     * 例如说，接口上设置了 @RequestParam(\"xx\") 参数，结果并未传递 xx 参数\n     */\n    @ExceptionHandler(value = MissingServletRequestParameterException.class)\n    public CommonResult<?> missingServletRequestParameterExceptionHandler(MissingServletRequestParameterException ex) {\n        log.warn(\"[missingServletRequestParameterExceptionHandler]\", ex);\n        return CommonResult.error(BAD_REQUEST.getCode(), String.format(\"请求参数缺失:%s\", ex.getParameterName()));\n    }\n\n    /**\n     * 处理 SpringMVC 请求参数类型错误\n     *\n     * 例如说，接口上设置了 @RequestParam(\"xx\") 参数为 Integer，结果传递 xx 参数类型为 String\n     */\n    @ExceptionHandler(MethodArgumentTypeMismatchException.class)\n    public CommonResult<?> methodArgumentTypeMismatchExceptionHandler(MethodArgumentTypeMismatchException ex) {\n        log.warn(\"[missingServletRequestParameterExceptionHandler]\", ex);\n        return CommonResult.error(BAD_REQUEST.getCode(), String.format(\"请求参数类型错误:%s\", ex.getMessage()));\n    }\n\n    /**\n     * 处理 SpringMVC 参数校验不正确\n     */\n    @ExceptionHandler(MethodArgumentNotValidException.class)\n    public CommonResult<?> methodArgumentNotValidExceptionExceptionHandler(MethodArgumentNotValidException ex) {\n        log.warn(\"[methodArgumentNotValidExceptionExceptionHandler]\", ex);\n        FieldError fieldError = ex.getBindingResult().getFieldError();\n        assert fieldError != null; // 断言，避免告警\n        return CommonResult.error(BAD_REQUEST.getCode(), String.format(\"请求参数不正确:%s\", fieldError.getDefaultMessage()));\n    }\n\n    /**\n     * 处理 SpringMVC 参数绑定不正确，本质上也是通过 Validator 校验\n     */\n    @ExceptionHandler(BindException.class)\n    public CommonResult<?> bindExceptionHandler(BindException ex) {\n        log.warn(\"[handleBindException]\", ex);\n        FieldError fieldError = ex.getFieldError();\n        assert fieldError != null; // 断言，避免告警\n        return CommonResult.error(BAD_REQUEST.getCode(), String.format(\"请求参数不正确:%s\", fieldError.getDefaultMessage()));\n    }\n\n    /**\n     * 处理 Validator 校验不通过产生的异常\n     */\n    @ExceptionHandler(value = ConstraintViolationException.class)\n    public CommonResult<?> constraintViolationExceptionHandler(ConstraintViolationException ex) {\n        log.warn(\"[constraintViolationExceptionHandler]\", ex);\n        ConstraintViolation<?> constraintViolation = ex.getConstraintViolations().iterator().next();\n        return CommonResult.error(BAD_REQUEST.getCode(), String.format(\"请求参数不正确:%s\", constraintViolation.getMessage()));\n    }\n\n    /**\n     * 处理 Dubbo Consumer 本地参数校验时，抛出的 ValidationException 异常\n     */\n    @ExceptionHandler(value = ValidationException.class)\n    public CommonResult<?> validationException(ValidationException ex) {\n        log.warn(\"[constraintViolationExceptionHandler]\", ex);\n        // 无法拼接明细的错误信息，因为 Dubbo Consumer 抛出 ValidationException 异常时，是直接的字符串信息，且人类不可读\n        return CommonResult.error(BAD_REQUEST);\n    }\n\n    /**\n     * 处理 SpringMVC 请求地址不存在\n     *\n     * 注意，它需要设置如下两个配置项：\n     * 1. spring.mvc.throw-exception-if-no-handler-found 为 true\n     * 2. spring.mvc.static-path-pattern 为 /statics/**\n     */\n    @ExceptionHandler(NoHandlerFoundException.class)\n    public CommonResult<?> noHandlerFoundExceptionHandler(HttpServletRequest req, NoHandlerFoundException ex) {\n        log.warn(\"[noHandlerFoundExceptionHandler]\", ex);\n        return CommonResult.error(NOT_FOUND.getCode(), String.format(\"请求地址不存在:%s\", ex.getRequestURL()));\n    }\n\n    /**\n     * 处理 SpringMVC 请求方法不正确\n     *\n     * 例如说，A 接口的方法为 GET 方式，结果请求方法为 POST 方式，导致不匹配\n     */\n    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)\n    public CommonResult<?> httpRequestMethodNotSupportedExceptionHandler(HttpRequestMethodNotSupportedException ex) {\n        log.warn(\"[httpRequestMethodNotSupportedExceptionHandler]\", ex);\n        return CommonResult.error(METHOD_NOT_ALLOWED.getCode(), String.format(\"请求方法不正确:%s\", ex.getMessage()));\n    }\n\n    /**\n     * 处理 Spring Security 权限不足的异常\n     *\n     * 来源是，使用 @PreAuthorize 注解，AOP 进行权限拦截\n     */\n    @ExceptionHandler(value = AccessDeniedException.class)\n    public CommonResult<?> accessDeniedExceptionHandler(HttpServletRequest req, AccessDeniedException ex) {\n        log.warn(\"[accessDeniedExceptionHandler][userId({}) 无法访问 url({})]\", WebFrameworkUtils.getLoginUserId(req),\n                req.getRequestURL(), ex);\n        return CommonResult.error(FORBIDDEN);\n    }\n\n    /**\n     * 处理业务异常 ServiceException\n     *\n     * 例如说，商品库存不足，用户手机号已存在。\n     */\n    @ExceptionHandler(value = ServiceException.class)\n    public CommonResult<?> serviceExceptionHandler(ServiceException ex) {\n        if (!IGNORE_ERROR_MESSAGES.contains(ex.getMessage())) {\n            // 不包含的时候，才进行打印，避免 ex 堆栈过多\n            log.info(\"[serviceExceptionHandler]\", ex);\n        }\n        return CommonResult.error(ex.getCode(), ex.getMessage());\n    }\n\n    /**\n     * 处理系统异常，兜底处理所有的一切\n     */\n    @ExceptionHandler(value = Exception.class)\n    public CommonResult<?> defaultExceptionHandler(HttpServletRequest req, Throwable ex) {\n        // 情况一：处理表不存在的异常\n        CommonResult<?> tableNotExistsResult = handleTableNotExists(ex);\n        if (tableNotExistsResult != null) {\n            return tableNotExistsResult;\n        }\n\n        // 情况二：处理异常\n        log.error(\"[defaultExceptionHandler]\", ex);\n        // 插入异常日志\n        this.createExceptionLog(req, ex);\n        // 返回 ERROR CommonResult\n        return CommonResult.error(INTERNAL_SERVER_ERROR.getCode(), INTERNAL_SERVER_ERROR.getMsg());\n    }\n\n    private void createExceptionLog(HttpServletRequest req, Throwable e) {\n        // 插入错误日志\n        ApiErrorLogCreateReqDTO errorLog = new ApiErrorLogCreateReqDTO();\n        try {\n            // 初始化 errorLog\n            buildExceptionLog(errorLog, req, e);\n            // 执行插入 errorLog\n            apiErrorLogFrameworkService.createApiErrorLog(errorLog);\n        } catch (Throwable th) {\n            log.error(\"[createExceptionLog][url({}) log({}) 发生异常]\", req.getRequestURI(),  JsonUtils.toJsonString(errorLog), th);\n        }\n    }\n\n    private void buildExceptionLog(ApiErrorLogCreateReqDTO errorLog, HttpServletRequest request, Throwable e) {\n        // 处理用户信息\n        errorLog.setUserId(WebFrameworkUtils.getLoginUserId(request));\n        errorLog.setUserType(WebFrameworkUtils.getLoginUserType(request));\n        // 设置异常字段\n        errorLog.setExceptionName(e.getClass().getName());\n        errorLog.setExceptionMessage(ExceptionUtil.getMessage(e));\n        errorLog.setExceptionRootCauseMessage(ExceptionUtil.getRootCauseMessage(e));\n        errorLog.setExceptionStackTrace(ExceptionUtils.getStackTrace(e));\n        StackTraceElement[] stackTraceElements = e.getStackTrace();\n        Assert.notEmpty(stackTraceElements, \"异常 stackTraceElements 不能为空\");\n        StackTraceElement stackTraceElement = stackTraceElements[0];\n        errorLog.setExceptionClassName(stackTraceElement.getClassName());\n        errorLog.setExceptionFileName(stackTraceElement.getFileName());\n        errorLog.setExceptionMethodName(stackTraceElement.getMethodName());\n        errorLog.setExceptionLineNumber(stackTraceElement.getLineNumber());\n        // 设置其它字段\n        errorLog.setTraceId(TracerUtils.getTraceId());\n        errorLog.setApplicationName(applicationName);\n        errorLog.setRequestUrl(request.getRequestURI());\n        Map<String, Object> requestParams = MapUtil.<String, Object>builder()\n                .put(\"query\", ServletUtils.getParamMap(request))\n                .put(\"body\", ServletUtils.getBody(request)).build();\n        errorLog.setRequestParams(JsonUtils.toJsonString(requestParams));\n        errorLog.setRequestMethod(request.getMethod());\n        errorLog.setUserAgent(ServletUtils.getUserAgent(request));\n        errorLog.setUserIp(ServletUtils.getClientIP(request));\n        errorLog.setExceptionTime(LocalDateTime.now());\n    }\n\n    /**\n     * 处理 Table 不存在的异常情况\n     *\n     * @param ex 异常\n     * @return 如果是 Table 不存在的异常，则返回对应的 CommonResult\n     */\n    private CommonResult<?> handleTableNotExists(Throwable ex) {\n        String message = ExceptionUtil.getRootCauseMessage(ex);\n        if (!message.contains(\"doesn't exist\")) {\n            return null;\n        }\n        // 1. 数据报表\n        if (message.contains(\"report_\")) {\n            log.error(\"[报表模块 yshop-module-report - 表结构未导入][参考 https://www.yixiang.co/report/ 开启]\");\n            return CommonResult.error(NOT_IMPLEMENTED.getCode(),\n                    \"[报表模块 yshop-module-report - 表结构未导入][参考 https://www.yixiang.co/report/ 开启]\");\n        }\n        // 2. 工作流\n        if (message.contains(\"bpm_\")) {\n            log.error(\"[工作流模块 yshop-module-bpm - 表结构未导入][参考 https://www.yixiang.co/bpm/ 开启]\");\n            return CommonResult.error(NOT_IMPLEMENTED.getCode(),\n                    \"[工作流模块 yshop-module-bpm - 表结构未导入][参考 https://www.yixiang.co/bpm/ 开启]\");\n        }\n        // 3. 微信公众号\n        if (message.contains(\"mp_\")) {\n            log.error(\"[微信公众号 yshop-module-mp - 表结构未导入][参考 https://www.yixiang.co/mp/build/ 开启]\");\n            return CommonResult.error(NOT_IMPLEMENTED.getCode(),\n                    \"[微信公众号 yshop-module-mp - 表结构未导入][参考 https://www.yixiang.co/mp/build/ 开启]\");\n        }\n        // 4. 商城系统\n        if (StrUtil.containsAny(message, \"product_\", \"promotion_\", \"trade_\")) {\n            log.error(\"[商城系统 yshop-module-mall - 已禁用][参考 https://www.yixiang.co/mall/build/ 开启]\");\n            return CommonResult.error(NOT_IMPLEMENTED.getCode(),\n                    \"[商城系统 yshop-module-mall - 已禁用][参考 https://www.yixiang.co/mall/build/ 开启]\");\n        }\n        // 5. ERP 系统\n        if (message.contains(\"erp_\")) {\n            log.error(\"[ERP 系统 yshop-module-erp - 表结构未导入][参考 https://www.yixiang.co/erp/build/ 开启]\");\n            return CommonResult.error(NOT_IMPLEMENTED.getCode(),\n                    \"[ERP 系统 yshop-module-erp - 表结构未导入][参考 https://www.yixiang.co/erp/build/ 开启]\");\n        }\n        // 6. CRM 系统\n        if (message.contains(\"crm_\")) {\n            log.error(\"[CRM 系统 yshop-module-crm - 表结构未导入][参考 https://www.yixiang.co/crm/build/ 开启]\");\n            return CommonResult.error(NOT_IMPLEMENTED.getCode(),\n                    \"[CRM 系统 yshop-module-crm - 表结构未导入][参考 https://www.yixiang.co/crm/build/ 开启]\");\n        }\n        // 7. 支付平台\n        if (message.contains(\"pay_\")) {\n            log.error(\"[支付模块 yshop-module-pay - 表结构未导入][参考 https://www.yixiang.co/pay/build/ 开启]\");\n            return CommonResult.error(NOT_IMPLEMENTED.getCode(),\n                    \"[支付模块 yshop-module-pay - 表结构未导入][参考 https://www.yixiang.co/pay/build/ 开启]\");\n        }\n        return null;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/web/core/handler/GlobalResponseBodyHandler.java",
    "content": "package co.yixiang.yshop.framework.web.core.handler;\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.web.core.util.WebFrameworkUtils;\nimport org.springframework.core.MethodParameter;\nimport org.springframework.http.MediaType;\nimport org.springframework.http.server.ServerHttpRequest;\nimport org.springframework.http.server.ServerHttpResponse;\nimport org.springframework.http.server.ServletServerHttpRequest;\nimport org.springframework.web.bind.annotation.ControllerAdvice;\nimport org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;\n\n/**\n * 全局响应结果（ResponseBody）处理器\n *\n * 不同于在网上看到的很多文章，会选择自动将 Controller 返回结果包上 {@link CommonResult}，\n * 在 onemall 中，是 Controller 在返回时，主动自己包上 {@link CommonResult}。\n * 原因是，GlobalResponseBodyHandler 本质上是 AOP，它不应该改变 Controller 返回的数据结构\n *\n * 目前，GlobalResponseBodyHandler 的主要作用是，记录 Controller 的返回结果，\n * 方便 {@link co.yixiang.yshop.framework.apilog.core.filter.ApiAccessLogFilter} 记录访问日志\n */\n@ControllerAdvice\npublic class GlobalResponseBodyHandler implements ResponseBodyAdvice {\n\n    @Override\n    @SuppressWarnings(\"NullableProblems\") // 避免 IDEA 警告\n    public boolean supports(MethodParameter returnType, Class converterType) {\n        if (returnType.getMethod() == null) {\n            return false;\n        }\n        // 只拦截返回结果为 CommonResult 类型\n        return returnType.getMethod().getReturnType() == CommonResult.class;\n    }\n\n    @Override\n    @SuppressWarnings(\"NullableProblems\") // 避免 IDEA 警告\n    public Object beforeBodyWrite(Object body, MethodParameter returnType, MediaType selectedContentType, Class selectedConverterType,\n                                  ServerHttpRequest request, ServerHttpResponse response) {\n        // 记录 Controller 结果\n        WebFrameworkUtils.setCommonResult(((ServletServerHttpRequest) request).getServletRequest(), (CommonResult<?>) body);\n        return body;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/web/core/util/WebFrameworkUtils.java",
    "content": "package co.yixiang.yshop.framework.web.core.util;\n\nimport cn.hutool.core.util.NumberUtil;\nimport cn.hutool.extra.servlet.ServletUtil;\nimport co.yixiang.yshop.framework.common.enums.TerminalEnum;\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.util.servlet.ServletUtils;\nimport co.yixiang.yshop.framework.web.config.WebProperties;\nimport org.springframework.web.context.request.RequestAttributes;\nimport org.springframework.web.context.request.RequestContextHolder;\nimport org.springframework.web.context.request.ServletRequestAttributes;\n\nimport jakarta.servlet.ServletRequest;\nimport jakarta.servlet.http.HttpServletRequest;\n\n/**\n * 专属于 web 包的工具类\n *\n * @author yshop\n */\npublic class WebFrameworkUtils {\n\n    private static final String REQUEST_ATTRIBUTE_LOGIN_USER_ID = \"login_user_id\";\n    private static final String REQUEST_ATTRIBUTE_LOGIN_USER_TYPE = \"login_user_type\";\n\n    private static final String REQUEST_ATTRIBUTE_COMMON_RESULT = \"common_result\";\n\n    public static final String HEADER_TENANT_ID = \"tenant-id\";\n\n    /**\n     * 终端的 Header\n     *\n     * @see co.yixiang.yshop.framework.common.enums.TerminalEnum\n     */\n    public static final String HEADER_TERMINAL = \"terminal\";\n\n    private static WebProperties properties;\n\n    public WebFrameworkUtils(WebProperties webProperties) {\n        WebFrameworkUtils.properties = webProperties;\n    }\n\n    /**\n     * 获得租户编号，从 header 中\n     * 考虑到其它 framework 组件也会使用到租户编号，所以不得不放在 WebFrameworkUtils 统一提供\n     *\n     * @param request 请求\n     * @return 租户编号\n     */\n    public static Long getTenantId(HttpServletRequest request) {\n        String tenantId = request.getHeader(HEADER_TENANT_ID);\n        return NumberUtil.isNumber(tenantId) ? Long.valueOf(tenantId) : null;\n    }\n\n    public static void setLoginUserId(ServletRequest request, Long userId) {\n        request.setAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_ID, userId);\n    }\n\n    /**\n     * 设置用户类型\n     *\n     * @param request 请求\n     * @param userType 用户类型\n     */\n    public static void setLoginUserType(ServletRequest request, Integer userType) {\n        request.setAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_TYPE, userType);\n    }\n\n    /**\n     * 获得当前用户的编号，从请求中\n     * 注意：该方法仅限于 framework 框架使用！！！\n     *\n     * @param request 请求\n     * @return 用户编号\n     */\n    public static Long getLoginUserId(HttpServletRequest request) {\n        if (request == null) {\n            return null;\n        }\n        return (Long) request.getAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_ID);\n    }\n\n    /**\n     * 获得当前用户的类型\n     * 注意：该方法仅限于 web 相关的 framework 组件使用！！！\n     *\n     * @param request 请求\n     * @return 用户编号\n     */\n    public static Integer getLoginUserType(HttpServletRequest request) {\n        if (request == null) {\n            return null;\n        }\n        // 1. 优先，从 Attribute 中获取\n        Integer userType = (Integer) request.getAttribute(REQUEST_ATTRIBUTE_LOGIN_USER_TYPE);\n        if (userType != null) {\n            return userType;\n        }\n        // 2. 其次，基于 URL 前缀的约定\n        if (request.getServletPath().startsWith(properties.getAdminApi().getPrefix())) {\n            return UserTypeEnum.ADMIN.getValue();\n        }\n        if (request.getServletPath().startsWith(properties.getAppApi().getPrefix())) {\n            return UserTypeEnum.MEMBER.getValue();\n        }\n        return null;\n    }\n\n    public static Integer getLoginUserType() {\n        HttpServletRequest request = getRequest();\n        return getLoginUserType(request);\n    }\n\n    public static Long getLoginUserId() {\n        HttpServletRequest request = getRequest();\n        return getLoginUserId(request);\n    }\n\n    public static Integer getTerminal() {\n        HttpServletRequest request = getRequest();\n        if (request == null) {\n            return TerminalEnum.UNKNOWN.getTerminal();\n        }\n        String terminalValue = request.getHeader(HEADER_TERMINAL);\n        return NumberUtil.parseInt(terminalValue, TerminalEnum.UNKNOWN.getTerminal());\n    }\n\n    public static void setCommonResult(ServletRequest request, CommonResult<?> result) {\n        request.setAttribute(REQUEST_ATTRIBUTE_COMMON_RESULT, result);\n    }\n\n    public static CommonResult<?> getCommonResult(ServletRequest request) {\n        return (CommonResult<?>) request.getAttribute(REQUEST_ATTRIBUTE_COMMON_RESULT);\n    }\n\n    public static HttpServletRequest getRequest() {\n        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();\n        if (!(requestAttributes instanceof ServletRequestAttributes)) {\n            return null;\n        }\n        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;\n        return servletRequestAttributes.getRequest();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/xss/config/XssProperties.java",
    "content": "package co.yixiang.yshop.framework.xss.config;\n\nimport lombok.Data;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.validation.annotation.Validated;\n\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * Xss 配置属性\n *\n * @author yshop\n */\n@ConfigurationProperties(prefix = \"yshop.xss\")\n@Validated\n@Data\npublic class XssProperties {\n\n    /**\n     * 是否开启，默认为 true\n     */\n    private boolean enable = true;\n    /**\n     * 需要排除的 URL，默认为空\n     */\n    private List<String> excludeUrls = Collections.emptyList();\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/xss/config/YshopXssAutoConfiguration.java",
    "content": "package co.yixiang.yshop.framework.xss.config;\n\nimport co.yixiang.yshop.framework.common.enums.WebFilterOrderEnum;\nimport co.yixiang.yshop.framework.xss.core.clean.JsoupXssCleaner;\nimport co.yixiang.yshop.framework.xss.core.clean.XssCleaner;\nimport co.yixiang.yshop.framework.xss.core.filter.XssFilter;\nimport co.yixiang.yshop.framework.xss.core.json.XssStringJsonDeserializer;\nimport com.fasterxml.jackson.databind.ObjectMapper;\nimport org.springframework.boot.autoconfigure.AutoConfiguration;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnBean;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.boot.web.servlet.FilterRegistrationBean;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.util.PathMatcher;\nimport org.springframework.web.servlet.config.annotation.WebMvcConfigurer;\n\nimport static co.yixiang.yshop.framework.web.config.YshopWebAutoConfiguration.createFilterBean;\n\n@AutoConfiguration\n@EnableConfigurationProperties(XssProperties.class)\n@ConditionalOnProperty(prefix = \"yshop.xss\", name = \"enable\", havingValue = \"true\", matchIfMissing = true) // 设置为 false 时，禁用\npublic class YshopXssAutoConfiguration implements WebMvcConfigurer {\n\n    /**\n     * Xss 清理者\n     *\n     * @return XssCleaner\n     */\n    @Bean\n    @ConditionalOnMissingBean(XssCleaner.class)\n    public XssCleaner xssCleaner() {\n        return new JsoupXssCleaner();\n    }\n\n    /**\n     * 注册 Jackson 的序列化器，用于处理 json 类型参数的 xss 过滤\n     *\n     * @return Jackson2ObjectMapperBuilderCustomizer\n     */\n    @Bean\n    @ConditionalOnMissingBean(name = \"xssJacksonCustomizer\")\n    @ConditionalOnBean(ObjectMapper.class)\n    @ConditionalOnProperty(value = \"yshop.xss.enable\", havingValue = \"true\")\n    public Jackson2ObjectMapperBuilderCustomizer xssJacksonCustomizer(XssProperties properties,\n                                                                      PathMatcher pathMatcher,\n                                                                      XssCleaner xssCleaner) {\n        // 在反序列化时进行 xss 过滤，可以替换使用 XssStringJsonSerializer，在序列化时进行处理\n        return builder -> builder.deserializerByType(String.class, new XssStringJsonDeserializer(properties, pathMatcher, xssCleaner));\n    }\n\n    /**\n     * 创建 XssFilter Bean，解决 Xss 安全问题\n     */\n    @Bean\n    @ConditionalOnBean(XssCleaner.class)\n    public FilterRegistrationBean<XssFilter> xssFilter(XssProperties properties, PathMatcher pathMatcher, XssCleaner xssCleaner) {\n        return createFilterBean(new XssFilter(properties, pathMatcher, xssCleaner), WebFilterOrderEnum.XSS_FILTER);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/xss/core/clean/JsoupXssCleaner.java",
    "content": "package co.yixiang.yshop.framework.xss.core.clean;\n\nimport org.jsoup.Jsoup;\nimport org.jsoup.nodes.Document;\nimport org.jsoup.safety.Safelist;\n\n/**\n * 基于 JSONP 实现 XSS 过滤字符串\n */\npublic class JsoupXssCleaner implements XssCleaner {\n\n    private final Safelist safelist;\n\n    /**\n     * 用于在 src 属性使用相对路径时，强制转换为绝对路径。 为空时不处理，值应为绝对路径的前缀（包含协议部分）\n     */\n    private final String baseUri;\n\n    /**\n     * 无参构造，默认使用 {@link JsoupXssCleaner#buildSafelist} 方法构建一个安全列表\n     */\n    public JsoupXssCleaner() {\n        this.safelist = buildSafelist();\n        this.baseUri = \"\";\n    }\n\n    /**\n     * 构建一个 Xss 清理的 Safelist 规则。\n     * 基于 Safelist#relaxed() 的基础上:\n     * 1. 扩展支持了 style 和 class 属性\n     * 2. a 标签额外支持了 target 属性\n     * 3. img 标签额外支持了 data 协议，便于支持 base64\n     *\n     * @return Safelist\n     */\n    private Safelist buildSafelist() {\n        // 使用 jsoup 提供的默认的\n        Safelist relaxedSafelist = Safelist.relaxed();\n        // 富文本编辑时一些样式是使用 style 来进行实现的\n        // 比如红色字体 style=\"color:red;\", 所以需要给所有标签添加 style 属性\n        // 注意：style 属性会有注入风险 <img STYLE=\"background-image:url(javascript:alert('XSS'))\">\n        relaxedSafelist.addAttributes(\":all\", \"style\", \"class\");\n        // 保留 a 标签的 target 属性\n        relaxedSafelist.addAttributes(\"a\", \"target\");\n        // 支持img 为base64\n        relaxedSafelist.addProtocols(\"img\", \"src\", \"data\");\n\n        // 保留相对路径, 保留相对路径时，必须提供对应的 baseUri 属性，否则依然会被删除\n        // WHITELIST.preserveRelativeLinks(false);\n\n        // 移除 a 标签和 img 标签的一些协议限制，这会导致 xss 防注入失效，如 <img src=javascript:alert(\"xss\")>\n        // 虽然可以重写 WhiteList#isSafeAttribute 来处理，但是有隐患，所以暂时不支持相对路径\n        // WHITELIST.removeProtocols(\"a\", \"href\", \"ftp\", \"http\", \"https\", \"mailto\");\n        // WHITELIST.removeProtocols(\"img\", \"src\", \"http\", \"https\");\n        return relaxedSafelist;\n    }\n\n    @Override\n    public String clean(String html) {\n        return Jsoup.clean(html, baseUri, safelist, new Document.OutputSettings().prettyPrint(false));\n    }\n\n}\n\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/xss/core/clean/XssCleaner.java",
    "content": "package co.yixiang.yshop.framework.xss.core.clean;\n\n/**\n * 对 html 文本中的有 Xss 风险的数据进行清理\n */\npublic interface XssCleaner {\n\n    /**\n     * 清理有 Xss 风险的文本\n     *\n     * @param html 原 html\n     * @return 清理后的 html\n     */\n    String clean(String html);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/xss/core/filter/XssFilter.java",
    "content": "package co.yixiang.yshop.framework.xss.core.filter;\n\nimport co.yixiang.yshop.framework.xss.config.XssProperties;\nimport co.yixiang.yshop.framework.xss.core.clean.XssCleaner;\nimport lombok.AllArgsConstructor;\nimport org.springframework.util.PathMatcher;\nimport org.springframework.web.filter.OncePerRequestFilter;\n\nimport jakarta.servlet.FilterChain;\nimport jakarta.servlet.ServletException;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport java.io.IOException;\n\n/**\n * Xss 过滤器\n *\n * @author yshop\n */\n@AllArgsConstructor\npublic class XssFilter extends OncePerRequestFilter {\n\n    /**\n     * 属性\n     */\n    private final XssProperties properties;\n    /**\n     * 路径匹配器\n     */\n    private final PathMatcher pathMatcher;\n\n    private final XssCleaner xssCleaner;\n\n    @Override\n    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)\n            throws IOException, ServletException {\n        filterChain.doFilter(new XssRequestWrapper(request, xssCleaner), response);\n    }\n\n    @Override\n    protected boolean shouldNotFilter(HttpServletRequest request) {\n        // 如果关闭，则不过滤\n        if (!properties.isEnable()) {\n            return true;\n        }\n\n        // 如果匹配到无需过滤，则不过滤\n        String uri = request.getRequestURI();\n        return properties.getExcludeUrls().stream().anyMatch(excludeUrl -> pathMatcher.match(excludeUrl, uri));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/xss/core/filter/XssRequestWrapper.java",
    "content": "package co.yixiang.yshop.framework.xss.core.filter;\n\nimport co.yixiang.yshop.framework.xss.core.clean.XssCleaner;\n\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletRequestWrapper;\nimport java.util.LinkedHashMap;\nimport java.util.Map;\n\n/**\n * Xss 请求 Wrapper\n *\n * @author yshop\n */\npublic class XssRequestWrapper extends HttpServletRequestWrapper {\n\n    private final XssCleaner xssCleaner;\n\n    public XssRequestWrapper(HttpServletRequest request, XssCleaner xssCleaner) {\n        super(request);\n        this.xssCleaner = xssCleaner;\n    }\n\n    // ============================ parameter ============================\n    @Override\n    public Map<String, String[]> getParameterMap() {\n        Map<String, String[]> map = new LinkedHashMap<>();\n        Map<String, String[]> parameters = super.getParameterMap();\n        for (Map.Entry<String, String[]> entry : parameters.entrySet()) {\n            String[] values = entry.getValue();\n            for (int i = 0; i < values.length; i++) {\n                values[i] = xssCleaner.clean(values[i]);\n            }\n            map.put(entry.getKey(), values);\n        }\n        return map;\n    }\n\n    @Override\n    public String[] getParameterValues(String name) {\n        String[] values = super.getParameterValues(name);\n        if (values == null) {\n            return null;\n        }\n        int count = values.length;\n        String[] encodedValues = new String[count];\n        for (int i = 0; i < count; i++) {\n            encodedValues[i] = xssCleaner.clean(values[i]);\n        }\n        return encodedValues;\n    }\n\n    @Override\n    public String getParameter(String name) {\n        String value = super.getParameter(name);\n        if (value == null) {\n            return null;\n        }\n        return xssCleaner.clean(value);\n    }\n\n    // ============================ attribute ============================\n    @Override\n    public Object getAttribute(String name) {\n        Object value = super.getAttribute(name);\n        if (value instanceof String) {\n            return xssCleaner.clean((String) value);\n        }\n        return value;\n    }\n\n    // ============================ header ============================\n    @Override\n    public String getHeader(String name) {\n        String value = super.getHeader(name);\n        if (value == null) {\n            return null;\n        }\n        return xssCleaner.clean(value);\n    }\n\n    // ============================ queryString ============================\n    @Override\n    public String getQueryString() {\n        String value = super.getQueryString();\n        if (value == null) {\n            return null;\n        }\n        return xssCleaner.clean(value);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/java/co/yixiang/yshop/framework/xss/core/json/XssStringJsonDeserializer.java",
    "content": "package co.yixiang.yshop.framework.xss.core.json;\n\nimport co.yixiang.yshop.framework.common.util.servlet.ServletUtils;\nimport co.yixiang.yshop.framework.xss.config.XssProperties;\nimport co.yixiang.yshop.framework.xss.core.clean.XssCleaner;\nimport com.fasterxml.jackson.core.JsonParser;\nimport com.fasterxml.jackson.core.JsonToken;\nimport com.fasterxml.jackson.databind.DeserializationContext;\nimport com.fasterxml.jackson.databind.deser.std.StringDeserializer;\nimport jakarta.servlet.http.HttpServletRequest;\nimport lombok.AllArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.util.PathMatcher;\n\nimport java.io.IOException;\n\n/**\n * XSS 过滤 jackson 反序列化器。\n * 在反序列化的过程中，会对字符串进行 XSS 过滤。\n *\n * @author Hccake\n */\n@Slf4j\n@AllArgsConstructor\npublic class XssStringJsonDeserializer extends StringDeserializer {\n\n    /**\n     * 属性\n     */\n    private final XssProperties properties;\n    /**\n     * 路径匹配器\n     */\n    private final PathMatcher pathMatcher;\n\n    private final XssCleaner xssCleaner;\n\n    @Override\n    public String deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {\n        // 1. 白名单 URL 的处理\n        HttpServletRequest request = ServletUtils.getRequest();\n        if (request != null) {\n            String uri = ServletUtils.getRequest().getRequestURI();\n            if (properties.getExcludeUrls().stream().anyMatch(excludeUrl -> pathMatcher.match(excludeUrl, uri))) {\n                return p.getText();\n            }\n        }\n\n        // 2. 真正使用 xssCleaner 进行过滤\n        if (p.hasToken(JsonToken.VALUE_STRING)) {\n            return xssCleaner.clean(p.getText());\n        }\n        JsonToken t = p.currentToken();\n        // [databind#381]\n        if (t == JsonToken.START_ARRAY) {\n            return _deserializeFromArray(p, ctxt);\n        }\n        // need to gracefully handle byte[] data, as base64\n        if (t == JsonToken.VALUE_EMBEDDED_OBJECT) {\n            Object ob = p.getEmbeddedObject();\n            if (ob == null) {\n                return null;\n            }\n            if (ob instanceof byte[]) {\n                return ctxt.getBase64Variant().encode((byte[]) ob, false);\n            }\n            // otherwise, try conversion using toString()...\n            return ob.toString();\n        }\n        // 29-Jun-2020, tatu: New! \"Scalar from Object\" (mostly for XML)\n        if (t == JsonToken.START_OBJECT) {\n            return ctxt.extractScalarFromObject(p, this, _valueClass);\n        }\n\n        if (t.isScalarValue()) {\n            String text = p.getValueAsString();\n            return xssCleaner.clean(text);\n        }\n        return (String) ctxt.handleUnexpectedToken(_valueClass, p);\n    }\n}\n\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports",
    "content": "co.yixiang.yshop.framework.apilog.config.YshopApiLogAutoConfiguration\nco.yixiang.yshop.framework.jackson.config.YshopJacksonAutoConfiguration\nco.yixiang.yshop.framework.swagger.config.YshopSwaggerAutoConfiguration\nco.yixiang.yshop.framework.web.config.YshopWebAutoConfiguration\nco.yixiang.yshop.framework.xss.config.YshopXssAutoConfiguration\nco.yixiang.yshop.framework.banner.config.YshopBannerAutoConfiguration"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/main/resources/banner.txt",
    "content": "意象电商 http://www.yixiang.co\nApplication Version: ${yshop.info.version}\nSpring Boot Version: ${spring-boot.version}\n\n.__   __.   ______      .______    __    __    _______\n|  \\ |  |  /  __  \\     |   _  \\  |  |  |  |  /  _____|\n|   \\|  | |  |  |  |    |  |_)  | |  |  |  | |  |  __\n|  . `  | |  |  |  |    |   _  <  |  |  |  | |  | |_ |\n|  |\\   | |  `--'  |    |  |_)  | |  `--'  | |  |__| |\n|__| \\__|  \\______/     |______/   \\______/   \\______|\n\n███╗   ██╗ ██████╗     ██████╗ ██╗   ██╗ ██████╗\n████╗  ██║██╔═══██╗    ██╔══██╗██║   ██║██╔════╝\n██╔██╗ ██║██║   ██║    ██████╔╝██║   ██║██║  ███╗\n██║╚██╗██║██║   ██║    ██╔══██╗██║   ██║██║   ██║\n██║ ╚████║╚██████╔╝    ██████╔╝╚██████╔╝╚██████╔╝\n╚═╝  ╚═══╝ ╚═════╝     ╚═════╝  ╚═════╝  ╚═════╝\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/test/java/co/yixiang/yshop/framework/desensitize/core/DesensitizeTest.java",
    "content": "package co.yixiang.yshop.framework.desensitize.core;\n\nimport co.yixiang.yshop.framework.common.util.json.JsonUtils;\nimport co.yixiang.yshop.framework.desensitize.core.annotation.Address;\nimport co.yixiang.yshop.framework.desensitize.core.regex.annotation.EmailDesensitize;\nimport co.yixiang.yshop.framework.desensitize.core.regex.annotation.RegexDesensitize;\nimport co.yixiang.yshop.framework.desensitize.core.slider.annotation.*;\nimport lombok.Data;\nimport org.junit.jupiter.api.Test;\nimport org.junit.jupiter.api.extension.ExtendWith;\nimport org.mockito.junit.jupiter.MockitoExtension;\n\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\n\n/**\n * {@link DesensitizeTest} 的单元测试\n */\n@ExtendWith(MockitoExtension.class)\npublic class DesensitizeTest {\n\n    @Test\n    public void test() {\n        // 准备参数\n        DesensitizeDemo desensitizeDemo = new DesensitizeDemo();\n        desensitizeDemo.setNickname(\"yshop\");\n        desensitizeDemo.setBankCard(\"9988002866797031\");\n        desensitizeDemo.setCarLicense(\"粤A66666\");\n        desensitizeDemo.setFixedPhone(\"01086551122\");\n        desensitizeDemo.setIdCard(\"530321199204074611\");\n        desensitizeDemo.setPassword(\"123456\");\n        desensitizeDemo.setPhoneNumber(\"13248765917\");\n        desensitizeDemo.setSlider1(\"ABCDEFG\");\n        desensitizeDemo.setSlider2(\"ABCDEFG\");\n        desensitizeDemo.setSlider3(\"ABCDEFG\");\n        desensitizeDemo.setEmail(\"1@email.com\");\n        desensitizeDemo.setRegex(\"你好，我是yshop\");\n        desensitizeDemo.setAddress(\"北京市海淀区上地十街10号\");\n        desensitizeDemo.setOrigin(\"yshop\");\n\n        // 调用\n        DesensitizeDemo d = JsonUtils.parseObject(JsonUtils.toJsonString(desensitizeDemo), DesensitizeDemo.class);\n        // 断言\n        assertNotNull(d);\n        assertEquals(\"芋***\", d.getNickname());\n        assertEquals(\"998800********31\", d.getBankCard());\n        assertEquals(\"粤A6***6\", d.getCarLicense());\n        assertEquals(\"0108*****22\", d.getFixedPhone());\n        assertEquals(\"530321**********11\", d.getIdCard());\n        assertEquals(\"******\", d.getPassword());\n        assertEquals(\"132****5917\", d.getPhoneNumber());\n        assertEquals(\"#######\", d.getSlider1());\n        assertEquals(\"ABC*EFG\", d.getSlider2());\n        assertEquals(\"*******\", d.getSlider3());\n        assertEquals(\"1****@email.com\", d.getEmail());\n        assertEquals(\"你好，我是*\", d.getRegex());\n        assertEquals(\"北京市海淀区上地十街10号*\", d.getAddress());\n        assertEquals(\"yshop\", d.getOrigin());\n    }\n\n    @Data\n    public static class DesensitizeDemo {\n\n        @ChineseNameDesensitize\n        private String nickname;\n        @BankCardDesensitize\n        private String bankCard;\n        @CarLicenseDesensitize\n        private String carLicense;\n        @FixedPhoneDesensitize\n        private String fixedPhone;\n        @IdCardDesensitize\n        private String idCard;\n        @PasswordDesensitize\n        private String password;\n        @MobileDesensitize\n        private String phoneNumber;\n        @SliderDesensitize(prefixKeep = 6, suffixKeep = 1, replacer = \"#\")\n        private String slider1;\n        @SliderDesensitize(prefixKeep = 3, suffixKeep = 3)\n        private String slider2;\n        @SliderDesensitize(prefixKeep = 10)\n        private String slider3;\n        @EmailDesensitize\n        private String email;\n        @RegexDesensitize(regex = \"yshop\", replacer = \"*\")\n        private String regex;\n        @Address\n        private String address;\n        private String origin;\n\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/test/java/co/yixiang/yshop/framework/desensitize/core/annotation/Address.java",
    "content": "package co.yixiang.yshop.framework.desensitize.core.annotation;\n\nimport co.yixiang.yshop.framework.desensitize.core.DesensitizeTest;\nimport co.yixiang.yshop.framework.desensitize.core.handler.AddressHandler;\nimport co.yixiang.yshop.framework.desensitize.core.base.annotation.DesensitizeBy;\nimport com.fasterxml.jackson.annotation.JacksonAnnotationsInside;\n\nimport java.lang.annotation.Documented;\nimport java.lang.annotation.ElementType;\nimport java.lang.annotation.Retention;\nimport java.lang.annotation.RetentionPolicy;\nimport java.lang.annotation.Target;\n\n/**\n * 地址\n *\n * 用于 {@link DesensitizeTest} 测试使用\n *\n * @author gaibu\n */\n@Documented\n@Target({ElementType.FIELD})\n@Retention(RetentionPolicy.RUNTIME)\n@JacksonAnnotationsInside\n@DesensitizeBy(handler = AddressHandler.class)\npublic @interface Address {\n\n    String replacer() default \"*\";\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-web/src/test/java/co/yixiang/yshop/framework/desensitize/core/handler/AddressHandler.java",
    "content": "package co.yixiang.yshop.framework.desensitize.core.handler;\n\nimport co.yixiang.yshop.framework.desensitize.core.DesensitizeTest;\nimport co.yixiang.yshop.framework.desensitize.core.base.handler.DesensitizationHandler;\nimport co.yixiang.yshop.framework.desensitize.core.annotation.Address;\n\n/**\n * {@link Address} 的脱敏处理器\n *\n * 用于 {@link DesensitizeTest} 测试使用\n */\npublic class AddressHandler implements DesensitizationHandler<Address> {\n\n    @Override\n    public String desensitize(String origin, Address annotation) {\n        return origin + annotation.replacer();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-websocket/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>co.yixiang.boot</groupId>\n        <artifactId>yshop-framework</artifactId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>yshop-spring-boot-starter-websocket</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>WebSocket 框架，支持多节点的广播</description>\n    <url>https://gitee.com/guchengwuyue/yshop-drink</url>\n\n\n    <dependencies>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-common</artifactId>\n        </dependency>\n\n        <!-- Web 相关 -->\n        <dependency>\n            <!-- 为什么是 websocket 依赖 security 呢？而不是 security 拓展 websocket 呢？\n                 因为 websocket 和 LoginUser 当前登录的用户有一定的相关性，具体可见 WebSocketSessionManagerImpl 逻辑。\n                 如果让 security 拓展 websocket 的话，会导致 websocket 组件的封装很散，进而增大理解成本。\n            -->\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-security</artifactId>\n            <scope>provided</scope>\n        </dependency>\n\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-websocket</artifactId>\n        </dependency>\n\n        <!-- 消息队列相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-mq</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.kafka</groupId>\n            <artifactId>spring-kafka</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.springframework.amqp</groupId>\n            <artifactId>spring-rabbit</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>org.apache.rocketmq</groupId>\n            <artifactId>rocketmq-spring-boot-starter</artifactId>\n            <optional>true</optional>\n        </dependency>\n\n        <!-- 业务组件 -->\n        <dependency>\n            <!-- 为什么要依赖 tenant 组件？\n                因为广播某个类型的用户时候，需要根据租户过滤下，避免广播到别的租户！\n            -->\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-biz-tenant</artifactId>\n            <scope>provided</scope>\n        </dependency>\n    </dependencies>\n\n</project>"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-websocket/src/main/java/co/yixiang/yshop/framework/websocket/config/WebSocketProperties.java",
    "content": "package co.yixiang.yshop.framework.websocket.config;\n\nimport lombok.Data;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\n\n/**\n * WebSocket 配置项\n *\n * @author xingyu4j\n */\n@ConfigurationProperties(\"yshop.websocket\")\n@Data\n@Validated\npublic class WebSocketProperties {\n\n    /**\n     * WebSocket 的连接路径\n     */\n    @NotEmpty(message = \"WebSocket 的连接路径不能为空\")\n    private String path = \"/ws\";\n\n    /**\n     * 消息发送器的类型\n     *\n     * 可选值：local、redis、rocketmq、kafka、rabbitmq\n     */\n    @NotNull(message = \"WebSocket 的消息发送者不能为空\")\n    private String senderType = \"local\";\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-websocket/src/main/java/co/yixiang/yshop/framework/websocket/config/YshopWebSocketAutoConfiguration.java",
    "content": "package co.yixiang.yshop.framework.websocket.config;\n\nimport co.yixiang.yshop.framework.mq.redis.config.YshopRedisMQConsumerAutoConfiguration;\nimport co.yixiang.yshop.framework.mq.redis.core.RedisMQTemplate;\nimport co.yixiang.yshop.framework.websocket.core.handler.JsonWebSocketMessageHandler;\nimport co.yixiang.yshop.framework.websocket.core.listener.WebSocketMessageListener;\nimport co.yixiang.yshop.framework.websocket.core.security.LoginUserHandshakeInterceptor;\nimport co.yixiang.yshop.framework.websocket.core.sender.kafka.KafkaWebSocketMessageConsumer;\nimport co.yixiang.yshop.framework.websocket.core.sender.kafka.KafkaWebSocketMessageSender;\nimport co.yixiang.yshop.framework.websocket.core.sender.local.LocalWebSocketMessageSender;\nimport co.yixiang.yshop.framework.websocket.core.sender.rabbitmq.RabbitMQWebSocketMessageConsumer;\nimport co.yixiang.yshop.framework.websocket.core.sender.rabbitmq.RabbitMQWebSocketMessageSender;\nimport co.yixiang.yshop.framework.websocket.core.sender.redis.RedisWebSocketMessageConsumer;\nimport co.yixiang.yshop.framework.websocket.core.sender.redis.RedisWebSocketMessageSender;\nimport co.yixiang.yshop.framework.websocket.core.sender.rocketmq.RocketMQWebSocketMessageConsumer;\nimport co.yixiang.yshop.framework.websocket.core.sender.rocketmq.RocketMQWebSocketMessageSender;\nimport co.yixiang.yshop.framework.websocket.core.session.WebSocketSessionHandlerDecorator;\nimport co.yixiang.yshop.framework.websocket.core.session.WebSocketSessionManager;\nimport co.yixiang.yshop.framework.websocket.core.session.WebSocketSessionManagerImpl;\nimport org.apache.rocketmq.spring.core.RocketMQTemplate;\nimport org.springframework.amqp.core.TopicExchange;\nimport org.springframework.amqp.rabbit.core.RabbitTemplate;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.boot.autoconfigure.AutoConfiguration;\nimport org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.kafka.core.KafkaTemplate;\nimport org.springframework.web.socket.WebSocketHandler;\nimport org.springframework.web.socket.config.annotation.EnableWebSocket;\nimport org.springframework.web.socket.config.annotation.WebSocketConfigurer;\nimport org.springframework.web.socket.server.HandshakeInterceptor;\n\nimport java.util.List;\n\n/**\n * WebSocket 自动配置\n *\n * @author xingyu4j\n */\n@AutoConfiguration(before = YshopRedisMQConsumerAutoConfiguration.class) // before YshopRedisMQConsumerAutoConfiguration 的原因是，需要保证 RedisWebSocketMessageConsumer 先创建，才能创建 RedisMessageListenerContainer\n@EnableWebSocket // 开启 websocket\n@ConditionalOnProperty(prefix = \"yshop.websocket\", value = \"enable\", matchIfMissing = true) // 允许使用 yshop.websocket.enable=false 禁用 websocket\n@EnableConfigurationProperties(WebSocketProperties.class)\npublic class YshopWebSocketAutoConfiguration {\n\n    @Bean\n    public WebSocketConfigurer webSocketConfigurer(HandshakeInterceptor[] handshakeInterceptors,\n                                                   WebSocketHandler webSocketHandler,\n                                                   WebSocketProperties webSocketProperties) {\n        return registry -> registry\n                // 添加 WebSocketHandler\n                .addHandler(webSocketHandler, webSocketProperties.getPath())\n                .addInterceptors(handshakeInterceptors)\n                // 允许跨域，否则前端连接会直接断开\n                .setAllowedOriginPatterns(\"*\");\n    }\n\n    @Bean\n    public HandshakeInterceptor handshakeInterceptor() {\n        return new LoginUserHandshakeInterceptor();\n    }\n\n    @Bean\n    public WebSocketHandler webSocketHandler(WebSocketSessionManager sessionManager,\n                                             List<? extends WebSocketMessageListener<?>> messageListeners) {\n        // 1. 创建 JsonWebSocketMessageHandler 对象，处理消息\n        JsonWebSocketMessageHandler messageHandler = new JsonWebSocketMessageHandler(messageListeners);\n        // 2. 创建 WebSocketSessionHandlerDecorator 对象，处理连接\n        return new WebSocketSessionHandlerDecorator(messageHandler, sessionManager);\n    }\n\n    @Bean\n    public WebSocketSessionManager webSocketSessionManager() {\n        return new WebSocketSessionManagerImpl();\n    }\n\n    // ==================== Sender 相关 ====================\n\n    @Configuration\n    @ConditionalOnProperty(prefix = \"yshop.websocket\", name = \"sender-type\", havingValue = \"local\", matchIfMissing = true)\n    public class LocalWebSocketMessageSenderConfiguration {\n\n        @Bean\n        public LocalWebSocketMessageSender localWebSocketMessageSender(WebSocketSessionManager sessionManager) {\n            return new LocalWebSocketMessageSender(sessionManager);\n        }\n\n    }\n\n    @Configuration\n    @ConditionalOnProperty(prefix = \"yshop.websocket\", name = \"sender-type\", havingValue = \"redis\", matchIfMissing = true)\n    public class RedisWebSocketMessageSenderConfiguration {\n\n        @Bean\n        public RedisWebSocketMessageSender redisWebSocketMessageSender(WebSocketSessionManager sessionManager,\n                                                                       RedisMQTemplate redisMQTemplate) {\n            return new RedisWebSocketMessageSender(sessionManager, redisMQTemplate);\n        }\n\n        @Bean\n        public RedisWebSocketMessageConsumer redisWebSocketMessageConsumer(\n                RedisWebSocketMessageSender redisWebSocketMessageSender) {\n            return new RedisWebSocketMessageConsumer(redisWebSocketMessageSender);\n        }\n\n    }\n\n    @Configuration\n    @ConditionalOnProperty(prefix = \"yshop.websocket\", name = \"sender-type\", havingValue = \"rocketmq\", matchIfMissing = true)\n    public class RocketMQWebSocketMessageSenderConfiguration {\n\n        @Bean\n        public RocketMQWebSocketMessageSender rocketMQWebSocketMessageSender(\n                WebSocketSessionManager sessionManager, RocketMQTemplate rocketMQTemplate,\n                @Value(\"${yshop.websocket.sender-rocketmq.topic}\") String topic) {\n            return new RocketMQWebSocketMessageSender(sessionManager, rocketMQTemplate, topic);\n        }\n\n        @Bean\n        public RocketMQWebSocketMessageConsumer rocketMQWebSocketMessageConsumer(\n                RocketMQWebSocketMessageSender rocketMQWebSocketMessageSender) {\n            return new RocketMQWebSocketMessageConsumer(rocketMQWebSocketMessageSender);\n        }\n\n    }\n\n    @Configuration\n    @ConditionalOnProperty(prefix = \"yshop.websocket\", name = \"sender-type\", havingValue = \"rabbitmq\", matchIfMissing = true)\n    public class RabbitMQWebSocketMessageSenderConfiguration {\n\n        @Bean\n        public RabbitMQWebSocketMessageSender rabbitMQWebSocketMessageSender(\n                WebSocketSessionManager sessionManager, RabbitTemplate rabbitTemplate,\n                TopicExchange websocketTopicExchange) {\n            return new RabbitMQWebSocketMessageSender(sessionManager, rabbitTemplate, websocketTopicExchange);\n        }\n\n        @Bean\n        public RabbitMQWebSocketMessageConsumer rabbitMQWebSocketMessageConsumer(\n                RabbitMQWebSocketMessageSender rabbitMQWebSocketMessageSender) {\n            return new RabbitMQWebSocketMessageConsumer(rabbitMQWebSocketMessageSender);\n        }\n\n        /**\n         * 创建 Topic Exchange\n         */\n        @Bean\n        public TopicExchange websocketTopicExchange(@Value(\"${yshop.websocket.sender-rabbitmq.exchange}\") String exchange) {\n            return new TopicExchange(exchange,\n                    true,  // durable: 是否持久化\n                    false);  // exclusive: 是否排它\n        }\n\n    }\n\n    @Configuration\n    @ConditionalOnProperty(prefix = \"yshop.websocket\", name = \"sender-type\", havingValue = \"kafka\", matchIfMissing = true)\n    public class KafkaWebSocketMessageSenderConfiguration {\n\n        @Bean\n        public KafkaWebSocketMessageSender kafkaWebSocketMessageSender(\n                WebSocketSessionManager sessionManager, KafkaTemplate<Object, Object> kafkaTemplate,\n                @Value(\"${yshop.websocket.sender-kafka.topic}\") String topic) {\n            return new KafkaWebSocketMessageSender(sessionManager, kafkaTemplate, topic);\n        }\n\n        @Bean\n        public KafkaWebSocketMessageConsumer kafkaWebSocketMessageConsumer(\n                KafkaWebSocketMessageSender kafkaWebSocketMessageSender) {\n            return new KafkaWebSocketMessageConsumer(kafkaWebSocketMessageSender);\n        }\n\n    }\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-websocket/src/main/java/co/yixiang/yshop/framework/websocket/core/handler/JsonWebSocketMessageHandler.java",
    "content": "package co.yixiang.yshop.framework.websocket.core.handler;\n\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.core.util.TypeUtil;\nimport co.yixiang.yshop.framework.common.util.json.JsonUtils;\nimport co.yixiang.yshop.framework.tenant.core.util.TenantUtils;\nimport co.yixiang.yshop.framework.websocket.core.listener.WebSocketMessageListener;\nimport co.yixiang.yshop.framework.websocket.core.message.JsonWebSocketMessage;\nimport co.yixiang.yshop.framework.websocket.core.util.WebSocketFrameworkUtils;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.web.socket.TextMessage;\nimport org.springframework.web.socket.WebSocketHandler;\nimport org.springframework.web.socket.WebSocketSession;\nimport org.springframework.web.socket.handler.TextWebSocketHandler;\n\nimport java.lang.reflect.Type;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.function.Consumer;\n\n/**\n * JSON 格式 {@link WebSocketHandler} 实现类\n *\n * 基于 {@link JsonWebSocketMessage#getType()} 消息类型，调度到对应的 {@link WebSocketMessageListener} 监听器。\n *\n * @author yshop\n */\n@Slf4j\npublic class JsonWebSocketMessageHandler extends TextWebSocketHandler {\n\n    /**\n     * type 与 WebSocketMessageListener 的映射\n     */\n    private final Map<String, WebSocketMessageListener<Object>> listeners = new HashMap<>();\n\n    @SuppressWarnings({\"rawtypes\", \"unchecked\"})\n    public JsonWebSocketMessageHandler(List<? extends WebSocketMessageListener> listenersList) {\n        listenersList.forEach((Consumer<WebSocketMessageListener>)\n                listener -> listeners.put(listener.getType(), listener));\n    }\n\n    @Override\n    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {\n        // 1.1 空消息，跳过\n        if (message.getPayloadLength() == 0) {\n            return;\n        }\n        // 1.2 ping 心跳消息，直接返回 pong 消息。\n        if (message.getPayloadLength() == 4 && Objects.equals(message.getPayload(), \"ping\")) {\n            session.sendMessage(new TextMessage(\"pong\"));\n            return;\n        }\n\n        // 2.1 解析消息\n        try {\n            JsonWebSocketMessage jsonMessage = JsonUtils.parseObject(message.getPayload(), JsonWebSocketMessage.class);\n            if (jsonMessage == null) {\n                log.error(\"[handleTextMessage][session({}) message({}) 解析为空]\", session.getId(), message.getPayload());\n                return;\n            }\n            if (StrUtil.isEmpty(jsonMessage.getType())) {\n                log.error(\"[handleTextMessage][session({}) message({}) 类型为空]\", session.getId(), message.getPayload());\n                return;\n            }\n            // 2.2 获得对应的 WebSocketMessageListener\n            WebSocketMessageListener<Object> messageListener = listeners.get(jsonMessage.getType());\n            if (messageListener == null) {\n                log.error(\"[handleTextMessage][session({}) message({}) 监听器为空]\", session.getId(), message.getPayload());\n                return;\n            }\n            // 2.3 处理消息\n            Type type = TypeUtil.getTypeArgument(messageListener.getClass(), 0);\n            Object messageObj = JsonUtils.parseObject(jsonMessage.getContent(), type);\n            Long tenantId = WebSocketFrameworkUtils.getTenantId(session);\n            TenantUtils.execute(tenantId, () -> messageListener.onMessage(session, messageObj));\n        } catch (Throwable ex) {\n            log.error(\"[handleTextMessage][session({}) message({}) 处理异常]\", session.getId(), message.getPayload());\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-websocket/src/main/java/co/yixiang/yshop/framework/websocket/core/listener/WebSocketMessageListener.java",
    "content": "package co.yixiang.yshop.framework.websocket.core.listener;\n\nimport co.yixiang.yshop.framework.websocket.core.message.JsonWebSocketMessage;\nimport org.springframework.web.socket.WebSocketSession;\n\n/**\n * WebSocket 消息监听器接口\n *\n * 目的：前端发送消息给后端后，处理对应 {@link #getType()} 类型的消息\n *\n * @param <T> 泛型，消息类型\n */\npublic interface WebSocketMessageListener<T> {\n\n    /**\n     * 处理消息\n     *\n     * @param session Session\n     * @param message 消息\n     */\n    void onMessage(WebSocketSession session, T message);\n\n    /**\n     * 获得消息类型\n     *\n     * @see JsonWebSocketMessage#getType()\n     * @return 消息类型\n     */\n    String getType();\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-websocket/src/main/java/co/yixiang/yshop/framework/websocket/core/message/JsonWebSocketMessage.java",
    "content": "package co.yixiang.yshop.framework.websocket.core.message;\n\nimport co.yixiang.yshop.framework.websocket.core.listener.WebSocketMessageListener;\nimport lombok.Data;\n\nimport java.io.Serializable;\n\n/**\n * JSON 格式的 WebSocket 消息帧\n *\n * @author yshop\n */\n@Data\npublic class JsonWebSocketMessage implements Serializable {\n\n    /**\n     * 消息类型\n     *\n     * 目的：用于分发到对应的 {@link WebSocketMessageListener} 实现类\n     */\n    private String type;\n    /**\n     * 消息内容\n     *\n     * 要求 JSON 对象\n     */\n    private String content;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-websocket/src/main/java/co/yixiang/yshop/framework/websocket/core/security/LoginUserHandshakeInterceptor.java",
    "content": "package co.yixiang.yshop.framework.websocket.core.security;\n\nimport co.yixiang.yshop.framework.security.core.LoginUser;\nimport co.yixiang.yshop.framework.security.core.filter.TokenAuthenticationFilter;\nimport co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils;\nimport co.yixiang.yshop.framework.websocket.core.util.WebSocketFrameworkUtils;\nimport org.springframework.http.server.ServerHttpRequest;\nimport org.springframework.http.server.ServerHttpResponse;\nimport org.springframework.web.socket.WebSocketHandler;\nimport org.springframework.web.socket.WebSocketSession;\nimport org.springframework.web.socket.server.HandshakeInterceptor;\n\nimport java.util.Map;\n\n/**\n * 登录用户的 {@link HandshakeInterceptor} 实现类\n *\n * 流程如下：\n * 1. 前端连接 websocket 时，会通过拼接 ?token={token} 到 ws:// 连接后，这样它可以被 {@link TokenAuthenticationFilter} 所认证通过\n * 2. {@link LoginUserHandshakeInterceptor} 负责把 {@link LoginUser} 添加到 {@link WebSocketSession} 中\n *\n * @author yshop\n */\npublic class LoginUserHandshakeInterceptor implements HandshakeInterceptor {\n\n    @Override\n    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,\n                                   WebSocketHandler wsHandler, Map<String, Object> attributes) {\n        LoginUser loginUser = SecurityFrameworkUtils.getLoginUser();\n        if (loginUser != null) {\n            WebSocketFrameworkUtils.setLoginUser(loginUser, attributes);\n        }\n        return true;\n    }\n\n    @Override\n    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,\n                               WebSocketHandler wsHandler, Exception exception) {\n        // do nothing\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-websocket/src/main/java/co/yixiang/yshop/framework/websocket/core/security/WebSocketAuthorizeRequestsCustomizer.java",
    "content": "package co.yixiang.yshop.framework.websocket.core.security;\n\nimport co.yixiang.yshop.framework.security.config.AuthorizeRequestsCustomizer;\nimport co.yixiang.yshop.framework.websocket.config.WebSocketProperties;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.security.config.annotation.web.builders.HttpSecurity;\nimport org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;\n\n/**\n * WebSocket 的权限自定义\n *\n * @author yshop\n */\n@RequiredArgsConstructor\npublic class WebSocketAuthorizeRequestsCustomizer extends AuthorizeRequestsCustomizer {\n\n    private final WebSocketProperties webSocketProperties;\n\n    @Override\n    public void customize(AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry registry) {\n        registry.requestMatchers(webSocketProperties.getPath()).permitAll();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-websocket/src/main/java/co/yixiang/yshop/framework/websocket/core/sender/AbstractWebSocketMessageSender.java",
    "content": "package co.yixiang.yshop.framework.websocket.core.sender;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.util.json.JsonUtils;\nimport co.yixiang.yshop.framework.websocket.core.message.JsonWebSocketMessage;\nimport co.yixiang.yshop.framework.websocket.core.session.WebSocketSessionManager;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.web.socket.TextMessage;\nimport org.springframework.web.socket.WebSocketSession;\n\nimport java.io.IOException;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\n\n/**\n * WebSocketMessageSender 实现类\n *\n * @author yshop\n */\n@Slf4j\n@RequiredArgsConstructor\npublic abstract class AbstractWebSocketMessageSender implements WebSocketMessageSender {\n\n    private final WebSocketSessionManager sessionManager;\n\n    @Override\n    public void send(Integer userType, Long userId, String messageType, String messageContent) {\n        send(null, userType, userId, messageType, messageContent);\n    }\n\n    @Override\n    public void send(Integer userType, String messageType, String messageContent) {\n        send(null, userType, null, messageType, messageContent);\n    }\n\n    @Override\n    public void send(String sessionId, String messageType, String messageContent) {\n        send(sessionId, null, null, messageType, messageContent);\n    }\n\n    /**\n     * 发送消息\n     *\n     * @param sessionId Session 编号\n     * @param userType 用户类型\n     * @param userId 用户编号\n     * @param messageType 消息类型\n     * @param messageContent 消息内容\n     */\n    public void send(String sessionId, Integer userType, Long userId, String messageType, String messageContent) {\n        // 1. 获得 Session 列表\n        List<WebSocketSession> sessions = Collections.emptyList();\n        if (StrUtil.isNotEmpty(sessionId)) {\n            WebSocketSession session = sessionManager.getSession(sessionId);\n            if (session != null) {\n                sessions = Collections.singletonList(session);\n            }\n        } else if (userType != null && userId != null) {\n            sessions = (List<WebSocketSession>) sessionManager.getSessionList(userType, userId);\n        } else if (userType != null) {\n            sessions = (List<WebSocketSession>) sessionManager.getSessionList(userType);\n        }\n        if (CollUtil.isEmpty(sessions)) {\n            log.info(\"[send][sessionId({}) userType({}) userId({}) messageType({}) messageContent({}) 未匹配到会话]\",\n                    sessionId, userType, userId, messageType, messageContent);\n        }\n        // 2. 执行发送\n        doSend(sessions, messageType, messageContent);\n    }\n\n    /**\n     * 发送消息的具体实现\n     *\n     * @param sessions Session 列表\n     * @param messageType 消息类型\n     * @param messageContent 消息内容\n     */\n    public void doSend(Collection<WebSocketSession> sessions, String messageType, String messageContent) {\n        JsonWebSocketMessage message = new JsonWebSocketMessage().setType(messageType).setContent(messageContent);\n        String payload = JsonUtils.toJsonString(message); // 关键，使用 JSON 序列化\n        sessions.forEach(session -> {\n            // 1. 各种校验，保证 Session 可以被发送\n            if (session == null) {\n                log.error(\"[doSend][session 为空, message({})]\", message);\n                return;\n            }\n            if (!session.isOpen()) {\n                log.error(\"[doSend][session({}) 已关闭, message({})]\", session.getId(), message);\n                return;\n            }\n            // 2. 执行发送\n            try {\n                session.sendMessage(new TextMessage(payload));\n                log.info(\"[doSend][session({}) 发送消息成功，message({})]\", session.getId(), message);\n            } catch (IOException ex) {\n                log.error(\"[doSend][session({}) 发送消息失败，message({})]\", session.getId(), message, ex);\n            }\n        });\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-websocket/src/main/java/co/yixiang/yshop/framework/websocket/core/sender/WebSocketMessageSender.java",
    "content": "package co.yixiang.yshop.framework.websocket.core.sender;\n\nimport co.yixiang.yshop.framework.common.util.json.JsonUtils;\n\n/**\n * WebSocket 消息的发送器接口\n *\n * @author yshop\n */\npublic interface WebSocketMessageSender {\n\n    /**\n     * 发送消息给指定用户\n     *\n     * @param userType 用户类型\n     * @param userId 用户编号\n     * @param messageType 消息类型\n     * @param messageContent 消息内容，JSON 格式\n     */\n    void send(Integer userType, Long userId, String messageType, String messageContent);\n\n    /**\n     * 发送消息给指定用户类型\n     *\n     * @param userType 用户类型\n     * @param messageType 消息类型\n     * @param messageContent 消息内容，JSON 格式\n     */\n    void send(Integer userType, String messageType, String messageContent);\n\n    /**\n     * 发送消息给指定 Session\n     *\n     * @param sessionId Session 编号\n     * @param messageType 消息类型\n     * @param messageContent 消息内容，JSON 格式\n     */\n    void send(String sessionId, String messageType, String messageContent);\n\n    default void sendObject(Integer userType, Long userId, String messageType, Object messageContent) {\n        send(userType, userId, messageType, JsonUtils.toJsonString(messageContent));\n    }\n\n    default void sendObject(Integer userType, String messageType, Object messageContent) {\n        send(userType, messageType, JsonUtils.toJsonString(messageContent));\n    }\n\n    default void sendObject(String sessionId, String messageType, Object messageContent) {\n        send(sessionId, messageType, JsonUtils.toJsonString(messageContent));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-websocket/src/main/java/co/yixiang/yshop/framework/websocket/core/sender/kafka/KafkaWebSocketMessage.java",
    "content": "package co.yixiang.yshop.framework.websocket.core.sender.kafka;\n\nimport lombok.Data;\n\n/**\n * Kafka 广播 WebSocket 的消息\n *\n * @author yshop\n */\n@Data\npublic class KafkaWebSocketMessage {\n\n    /**\n     * Session 编号\n     */\n    private String sessionId;\n    /**\n     * 用户类型\n     */\n    private Integer userType;\n    /**\n     * 用户编号\n     */\n    private Long userId;\n\n    /**\n     * 消息类型\n     */\n    private String messageType;\n    /**\n     * 消息内容\n     */\n    private String messageContent;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-websocket/src/main/java/co/yixiang/yshop/framework/websocket/core/sender/kafka/KafkaWebSocketMessageConsumer.java",
    "content": "package co.yixiang.yshop.framework.websocket.core.sender.kafka;\n\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.amqp.rabbit.annotation.RabbitHandler;\nimport org.springframework.kafka.annotation.KafkaListener;\n\n/**\n * {@link KafkaWebSocketMessage} 广播消息的消费者，真正把消息发送出去\n *\n * @author yshop\n */\n@RequiredArgsConstructor\npublic class KafkaWebSocketMessageConsumer {\n\n    private final KafkaWebSocketMessageSender rabbitMQWebSocketMessageSender;\n\n    @RabbitHandler\n    @KafkaListener(\n            topics = \"${yshop.websocket.sender-kafka.topic}\",\n            // 在 Group 上，使用 UUID 生成其后缀。这样，启动的 Consumer 的 Group 不同，以达到广播消费的目的\n            groupId = \"${yshop.websocket.sender-kafka.consumer-group}\" + \"-\" + \"#{T(java.util.UUID).randomUUID()}\")\n    public void onMessage(KafkaWebSocketMessage message) {\n        rabbitMQWebSocketMessageSender.send(message.getSessionId(),\n                message.getUserType(), message.getUserId(),\n                message.getMessageType(), message.getMessageContent());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-websocket/src/main/java/co/yixiang/yshop/framework/websocket/core/sender/kafka/KafkaWebSocketMessageSender.java",
    "content": "package co.yixiang.yshop.framework.websocket.core.sender.kafka;\n\nimport co.yixiang.yshop.framework.websocket.core.sender.AbstractWebSocketMessageSender;\nimport co.yixiang.yshop.framework.websocket.core.sender.WebSocketMessageSender;\nimport co.yixiang.yshop.framework.websocket.core.session.WebSocketSessionManager;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.kafka.core.KafkaTemplate;\n\nimport java.util.concurrent.ExecutionException;\n\n/**\n * 基于 Kafka 的 {@link WebSocketMessageSender} 实现类\n *\n * @author yshop\n */\n@Slf4j\npublic class KafkaWebSocketMessageSender extends AbstractWebSocketMessageSender {\n\n    private final KafkaTemplate<Object, Object> kafkaTemplate;\n\n    private final String topic;\n\n    public KafkaWebSocketMessageSender(WebSocketSessionManager sessionManager,\n                                       KafkaTemplate<Object, Object> kafkaTemplate,\n                                       String topic) {\n        super(sessionManager);\n        this.kafkaTemplate = kafkaTemplate;\n        this.topic = topic;\n    }\n\n    @Override\n    public void send(Integer userType, Long userId, String messageType, String messageContent) {\n        sendKafkaMessage(null, userId, userType, messageType, messageContent);\n    }\n\n    @Override\n    public void send(Integer userType, String messageType, String messageContent) {\n        sendKafkaMessage(null, null, userType, messageType, messageContent);\n    }\n\n    @Override\n    public void send(String sessionId, String messageType, String messageContent) {\n        sendKafkaMessage(sessionId, null, null, messageType, messageContent);\n    }\n\n    /**\n     * 通过 Kafka 广播消息\n     *\n     * @param sessionId Session 编号\n     * @param userId 用户编号\n     * @param userType 用户类型\n     * @param messageType 消息类型\n     * @param messageContent 消息内容\n     */\n    private void sendKafkaMessage(String sessionId, Long userId, Integer userType,\n                                  String messageType, String messageContent) {\n        KafkaWebSocketMessage mqMessage = new KafkaWebSocketMessage()\n                .setSessionId(sessionId).setUserId(userId).setUserType(userType)\n                .setMessageType(messageType).setMessageContent(messageContent);\n        try {\n            kafkaTemplate.send(topic, mqMessage).get();\n        } catch (InterruptedException | ExecutionException e) {\n            log.error(\"[sendKafkaMessage][发送消息({}) 到 Kafka 失败]\", mqMessage, e);\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-websocket/src/main/java/co/yixiang/yshop/framework/websocket/core/sender/local/LocalWebSocketMessageSender.java",
    "content": "package co.yixiang.yshop.framework.websocket.core.sender.local;\n\nimport co.yixiang.yshop.framework.websocket.core.sender.AbstractWebSocketMessageSender;\nimport co.yixiang.yshop.framework.websocket.core.sender.WebSocketMessageSender;\nimport co.yixiang.yshop.framework.websocket.core.session.WebSocketSessionManager;\n\n/**\n * 本地的 {@link WebSocketMessageSender} 实现类\n *\n * 注意：仅仅适合单机场景！！！\n *\n * @author yshop\n */\npublic class LocalWebSocketMessageSender extends AbstractWebSocketMessageSender {\n\n    public LocalWebSocketMessageSender(WebSocketSessionManager sessionManager) {\n        super(sessionManager);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-websocket/src/main/java/co/yixiang/yshop/framework/websocket/core/sender/rabbitmq/RabbitMQWebSocketMessage.java",
    "content": "package co.yixiang.yshop.framework.websocket.core.sender.rabbitmq;\n\nimport lombok.Data;\n\nimport java.io.Serializable;\n\n/**\n * RabbitMQ 广播 WebSocket 的消息\n *\n * @author yshop\n */\n@Data\npublic class RabbitMQWebSocketMessage implements Serializable {\n\n    /**\n     * Session 编号\n     */\n    private String sessionId;\n    /**\n     * 用户类型\n     */\n    private Integer userType;\n    /**\n     * 用户编号\n     */\n    private Long userId;\n\n    /**\n     * 消息类型\n     */\n    private String messageType;\n    /**\n     * 消息内容\n     */\n    private String messageContent;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-websocket/src/main/java/co/yixiang/yshop/framework/websocket/core/sender/rabbitmq/RabbitMQWebSocketMessageConsumer.java",
    "content": "package co.yixiang.yshop.framework.websocket.core.sender.rabbitmq;\n\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.amqp.core.ExchangeTypes;\nimport org.springframework.amqp.rabbit.annotation.*;\n\n/**\n * {@link RabbitMQWebSocketMessage} 广播消息的消费者，真正把消息发送出去\n *\n * @author yshop\n */\n@RabbitListener(\n        bindings = @QueueBinding(\n                value = @Queue(\n                        // 在 Queue 的名字上，使用 UUID 生成其后缀。这样，启动的 Consumer 的 Queue 不同，以达到广播消费的目的\n                        name = \"${yshop.websocket.sender-rabbitmq.queue}\" + \"-\" + \"#{T(java.util.UUID).randomUUID()}\",\n                        // Consumer 关闭时，该队列就可以被自动删除了\n                        autoDelete = \"true\"\n                ),\n                exchange = @Exchange(\n                        name = \"${yshop.websocket.sender-rabbitmq.exchange}\",\n                        type = ExchangeTypes.TOPIC,\n                        declare = \"false\"\n                )\n        )\n)\n@RequiredArgsConstructor\npublic class RabbitMQWebSocketMessageConsumer {\n\n    private final RabbitMQWebSocketMessageSender rabbitMQWebSocketMessageSender;\n\n    @RabbitHandler\n    public void onMessage(RabbitMQWebSocketMessage message) {\n        rabbitMQWebSocketMessageSender.send(message.getSessionId(),\n                message.getUserType(), message.getUserId(),\n                message.getMessageType(), message.getMessageContent());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-websocket/src/main/java/co/yixiang/yshop/framework/websocket/core/sender/rabbitmq/RabbitMQWebSocketMessageSender.java",
    "content": "package co.yixiang.yshop.framework.websocket.core.sender.rabbitmq;\n\nimport co.yixiang.yshop.framework.websocket.core.sender.AbstractWebSocketMessageSender;\nimport co.yixiang.yshop.framework.websocket.core.sender.WebSocketMessageSender;\nimport co.yixiang.yshop.framework.websocket.core.session.WebSocketSessionManager;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.amqp.core.TopicExchange;\nimport org.springframework.amqp.rabbit.core.RabbitTemplate;\n\n/**\n * 基于 RabbitMQ 的 {@link WebSocketMessageSender} 实现类\n *\n * @author yshop\n */\n@Slf4j\npublic class RabbitMQWebSocketMessageSender extends AbstractWebSocketMessageSender {\n\n    private final RabbitTemplate rabbitTemplate;\n\n    private final TopicExchange topicExchange;\n\n    public RabbitMQWebSocketMessageSender(WebSocketSessionManager sessionManager,\n                                          RabbitTemplate rabbitTemplate,\n                                          TopicExchange topicExchange) {\n        super(sessionManager);\n        this.rabbitTemplate = rabbitTemplate;\n        this.topicExchange = topicExchange;\n    }\n\n    @Override\n    public void send(Integer userType, Long userId, String messageType, String messageContent) {\n        sendRabbitMQMessage(null, userId, userType, messageType, messageContent);\n    }\n\n    @Override\n    public void send(Integer userType, String messageType, String messageContent) {\n        sendRabbitMQMessage(null, null, userType, messageType, messageContent);\n    }\n\n    @Override\n    public void send(String sessionId, String messageType, String messageContent) {\n        sendRabbitMQMessage(sessionId, null, null, messageType, messageContent);\n    }\n\n    /**\n     * 通过 RabbitMQ 广播消息\n     *\n     * @param sessionId Session 编号\n     * @param userId 用户编号\n     * @param userType 用户类型\n     * @param messageType 消息类型\n     * @param messageContent 消息内容\n     */\n    private void sendRabbitMQMessage(String sessionId, Long userId, Integer userType,\n                                     String messageType, String messageContent) {\n        RabbitMQWebSocketMessage mqMessage = new RabbitMQWebSocketMessage()\n                .setSessionId(sessionId).setUserId(userId).setUserType(userType)\n                .setMessageType(messageType).setMessageContent(messageContent);\n        rabbitTemplate.convertAndSend(topicExchange.getName(), null, mqMessage);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-websocket/src/main/java/co/yixiang/yshop/framework/websocket/core/sender/redis/RedisWebSocketMessage.java",
    "content": "package co.yixiang.yshop.framework.websocket.core.sender.redis;\n\nimport co.yixiang.yshop.framework.mq.redis.core.pubsub.AbstractRedisChannelMessage;\nimport lombok.Data;\n\n/**\n * Redis 广播 WebSocket 的消息\n */\n@Data\npublic class RedisWebSocketMessage extends AbstractRedisChannelMessage {\n\n    /**\n     * Session 编号\n     */\n    private String sessionId;\n    /**\n     * 用户类型\n     */\n    private Integer userType;\n    /**\n     * 用户编号\n     */\n    private Long userId;\n\n    /**\n     * 消息类型\n     */\n    private String messageType;\n    /**\n     * 消息内容\n     */\n    private String messageContent;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-websocket/src/main/java/co/yixiang/yshop/framework/websocket/core/sender/redis/RedisWebSocketMessageConsumer.java",
    "content": "package co.yixiang.yshop.framework.websocket.core.sender.redis;\n\nimport co.yixiang.yshop.framework.mq.redis.core.pubsub.AbstractRedisChannelMessageListener;\nimport lombok.RequiredArgsConstructor;\n\n/**\n * {@link RedisWebSocketMessage} 广播消息的消费者，真正把消息发送出去\n *\n * @author yshop\n */\n@RequiredArgsConstructor\npublic class RedisWebSocketMessageConsumer extends AbstractRedisChannelMessageListener<RedisWebSocketMessage> {\n\n    private final RedisWebSocketMessageSender redisWebSocketMessageSender;\n\n    @Override\n    public void onMessage(RedisWebSocketMessage message) {\n        redisWebSocketMessageSender.send(message.getSessionId(),\n                message.getUserType(), message.getUserId(),\n                message.getMessageType(), message.getMessageContent());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-websocket/src/main/java/co/yixiang/yshop/framework/websocket/core/sender/redis/RedisWebSocketMessageSender.java",
    "content": "package co.yixiang.yshop.framework.websocket.core.sender.redis;\n\nimport co.yixiang.yshop.framework.mq.redis.core.RedisMQTemplate;\nimport co.yixiang.yshop.framework.websocket.core.sender.AbstractWebSocketMessageSender;\nimport co.yixiang.yshop.framework.websocket.core.sender.WebSocketMessageSender;\nimport co.yixiang.yshop.framework.websocket.core.session.WebSocketSessionManager;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * 基于 Redis 的 {@link WebSocketMessageSender} 实现类\n *\n * @author yshop\n */\n@Slf4j\npublic class RedisWebSocketMessageSender extends AbstractWebSocketMessageSender {\n\n    private final RedisMQTemplate redisMQTemplate;\n\n    public RedisWebSocketMessageSender(WebSocketSessionManager sessionManager,\n                                       RedisMQTemplate redisMQTemplate) {\n        super(sessionManager);\n        this.redisMQTemplate = redisMQTemplate;\n    }\n\n    @Override\n    public void send(Integer userType, Long userId, String messageType, String messageContent) {\n        sendRedisMessage(null, userId, userType, messageType, messageContent);\n    }\n\n    @Override\n    public void send(Integer userType, String messageType, String messageContent) {\n        sendRedisMessage(null, null, userType, messageType, messageContent);\n    }\n\n    @Override\n    public void send(String sessionId, String messageType, String messageContent) {\n        sendRedisMessage(sessionId, null, null, messageType, messageContent);\n    }\n\n    /**\n     * 通过 Redis 广播消息\n     *\n     * @param sessionId Session 编号\n     * @param userId 用户编号\n     * @param userType 用户类型\n     * @param messageType 消息类型\n     * @param messageContent 消息内容\n     */\n    private void sendRedisMessage(String sessionId, Long userId, Integer userType,\n                                  String messageType, String messageContent) {\n        RedisWebSocketMessage mqMessage = new RedisWebSocketMessage()\n                .setSessionId(sessionId).setUserId(userId).setUserType(userType)\n                .setMessageType(messageType).setMessageContent(messageContent);\n        redisMQTemplate.send(mqMessage);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-websocket/src/main/java/co/yixiang/yshop/framework/websocket/core/sender/rocketmq/RocketMQWebSocketMessage.java",
    "content": "package co.yixiang.yshop.framework.websocket.core.sender.rocketmq;\n\nimport lombok.Data;\n\n/**\n * RocketMQ 广播 WebSocket 的消息\n *\n * @author yshop\n */\n@Data\npublic class RocketMQWebSocketMessage {\n\n    /**\n     * Session 编号\n     */\n    private String sessionId;\n    /**\n     * 用户类型\n     */\n    private Integer userType;\n    /**\n     * 用户编号\n     */\n    private Long userId;\n\n    /**\n     * 消息类型\n     */\n    private String messageType;\n    /**\n     * 消息内容\n     */\n    private String messageContent;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-websocket/src/main/java/co/yixiang/yshop/framework/websocket/core/sender/rocketmq/RocketMQWebSocketMessageConsumer.java",
    "content": "package co.yixiang.yshop.framework.websocket.core.sender.rocketmq;\n\nimport lombok.RequiredArgsConstructor;\nimport org.apache.rocketmq.spring.annotation.MessageModel;\nimport org.apache.rocketmq.spring.annotation.RocketMQMessageListener;\nimport org.apache.rocketmq.spring.core.RocketMQListener;\n\n/**\n * {@link RocketMQWebSocketMessage} 广播消息的消费者，真正把消息发送出去\n *\n * @author yshop\n */\n@RocketMQMessageListener( // 重点：添加 @RocketMQMessageListener 注解，声明消费的 topic\n        topic = \"${yshop.websocket.sender-rocketmq.topic}\",\n        consumerGroup = \"${yshop.websocket.sender-rocketmq.consumer-group}\",\n        messageModel = MessageModel.BROADCASTING // 设置为广播模式，保证每个实例都能收到消息\n)\n@RequiredArgsConstructor\npublic class RocketMQWebSocketMessageConsumer implements RocketMQListener<RocketMQWebSocketMessage> {\n\n    private final RocketMQWebSocketMessageSender rocketMQWebSocketMessageSender;\n\n    @Override\n    public void onMessage(RocketMQWebSocketMessage message) {\n        rocketMQWebSocketMessageSender.send(message.getSessionId(),\n                message.getUserType(), message.getUserId(),\n                message.getMessageType(), message.getMessageContent());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-websocket/src/main/java/co/yixiang/yshop/framework/websocket/core/sender/rocketmq/RocketMQWebSocketMessageSender.java",
    "content": "package co.yixiang.yshop.framework.websocket.core.sender.rocketmq;\n\nimport co.yixiang.yshop.framework.websocket.core.sender.AbstractWebSocketMessageSender;\nimport co.yixiang.yshop.framework.websocket.core.sender.WebSocketMessageSender;\nimport co.yixiang.yshop.framework.websocket.core.session.WebSocketSessionManager;\nimport lombok.extern.slf4j.Slf4j;\nimport org.apache.rocketmq.spring.core.RocketMQTemplate;\n\n/**\n * 基于 RocketMQ 的 {@link WebSocketMessageSender} 实现类\n *\n * @author yshop\n */\n@Slf4j\npublic class RocketMQWebSocketMessageSender extends AbstractWebSocketMessageSender {\n\n    private final RocketMQTemplate rocketMQTemplate;\n\n    private final String topic;\n\n    public RocketMQWebSocketMessageSender(WebSocketSessionManager sessionManager,\n                                          RocketMQTemplate rocketMQTemplate,\n                                          String topic) {\n        super(sessionManager);\n        this.rocketMQTemplate = rocketMQTemplate;\n        this.topic = topic;\n    }\n\n    @Override\n    public void send(Integer userType, Long userId, String messageType, String messageContent) {\n        sendRocketMQMessage(null, userId, userType, messageType, messageContent);\n    }\n\n    @Override\n    public void send(Integer userType, String messageType, String messageContent) {\n        sendRocketMQMessage(null, null, userType, messageType, messageContent);\n    }\n\n    @Override\n    public void send(String sessionId, String messageType, String messageContent) {\n        sendRocketMQMessage(sessionId, null, null, messageType, messageContent);\n    }\n\n    /**\n     * 通过 RocketMQ 广播消息\n     *\n     * @param sessionId Session 编号\n     * @param userId 用户编号\n     * @param userType 用户类型\n     * @param messageType 消息类型\n     * @param messageContent 消息内容\n     */\n    private void sendRocketMQMessage(String sessionId, Long userId, Integer userType,\n                                     String messageType, String messageContent) {\n        RocketMQWebSocketMessage mqMessage = new RocketMQWebSocketMessage()\n                .setSessionId(sessionId).setUserId(userId).setUserType(userType)\n                .setMessageType(messageType).setMessageContent(messageContent);\n        rocketMQTemplate.syncSend(topic, mqMessage);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-websocket/src/main/java/co/yixiang/yshop/framework/websocket/core/session/WebSocketSessionHandlerDecorator.java",
    "content": "package co.yixiang.yshop.framework.websocket.core.session;\n\nimport org.springframework.web.socket.CloseStatus;\nimport org.springframework.web.socket.WebSocketHandler;\nimport org.springframework.web.socket.WebSocketSession;\nimport org.springframework.web.socket.handler.ConcurrentWebSocketSessionDecorator;\nimport org.springframework.web.socket.handler.WebSocketHandlerDecorator;\n\n/**\n * {@link WebSocketHandler} 的装饰类，实现了以下功能：\n *\n * 1. {@link WebSocketSession} 连接或关闭时，使用 {@link #sessionManager} 进行管理\n * 2. 封装 {@link WebSocketSession} 支持并发操作\n *\n * @author yshop\n */\npublic class WebSocketSessionHandlerDecorator extends WebSocketHandlerDecorator {\n\n    /**\n     * 发送时间的限制，单位：毫秒\n     */\n    private static final Integer SEND_TIME_LIMIT = 1000 * 5;\n    /**\n     * 发送消息缓冲上线，单位：bytes\n     */\n    private static final Integer BUFFER_SIZE_LIMIT = 1024 * 100;\n\n    private final WebSocketSessionManager sessionManager;\n\n    public WebSocketSessionHandlerDecorator(WebSocketHandler delegate,\n                                            WebSocketSessionManager sessionManager) {\n        super(delegate);\n        this.sessionManager = sessionManager;\n    }\n\n    @Override\n    public void afterConnectionEstablished(WebSocketSession session) {\n        // 实现 session 支持并发，可参考 https://blog.csdn.net/abu935009066/article/details/131218149\n        session = new ConcurrentWebSocketSessionDecorator(session, SEND_TIME_LIMIT, BUFFER_SIZE_LIMIT);\n        // 添加到 WebSocketSessionManager 中\n        sessionManager.addSession(session);\n    }\n\n    @Override\n    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) {\n        sessionManager.removeSession(session);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-websocket/src/main/java/co/yixiang/yshop/framework/websocket/core/session/WebSocketSessionManager.java",
    "content": "package co.yixiang.yshop.framework.websocket.core.session;\n\nimport org.springframework.web.socket.WebSocketSession;\n\nimport java.util.Collection;\n\n/**\n * {@link WebSocketSession} 管理器的接口\n *\n * @author yshop\n */\npublic interface WebSocketSessionManager {\n\n    /**\n     * 添加 Session\n     *\n     * @param session Session\n     */\n    void addSession(WebSocketSession session);\n\n    /**\n     * 移除 Session\n     *\n     * @param session Session\n     */\n    void removeSession(WebSocketSession session);\n\n    /**\n     * 获得指定编号的 Session\n     *\n     * @param id Session 编号\n     * @return Session\n     */\n    WebSocketSession getSession(String id);\n\n    /**\n     * 获得指定用户类型的 Session 列表\n     *\n     * @param userType 用户类型\n     * @return Session 列表\n     */\n    Collection<WebSocketSession> getSessionList(Integer userType);\n\n    /**\n     * 获得指定用户编号的 Session 列表\n     *\n     * @param userType 用户类型\n     * @param userId 用户编号\n     * @return Session 列表\n     */\n    Collection<WebSocketSession> getSessionList(Integer userType, Long userId);\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-websocket/src/main/java/co/yixiang/yshop/framework/websocket/core/session/WebSocketSessionManagerImpl.java",
    "content": "package co.yixiang.yshop.framework.websocket.core.session;\n\nimport cn.hutool.core.collection.CollUtil;\nimport co.yixiang.yshop.framework.security.core.LoginUser;\nimport co.yixiang.yshop.framework.tenant.core.context.TenantContextHolder;\nimport co.yixiang.yshop.framework.websocket.core.util.WebSocketFrameworkUtils;\nimport org.springframework.web.socket.WebSocketSession;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.LinkedList;\nimport java.util.List;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ConcurrentMap;\nimport java.util.concurrent.CopyOnWriteArrayList;\n\n/**\n * 默认的 {@link WebSocketSessionManager} 实现类\n *\n * @author yshop\n */\npublic class WebSocketSessionManagerImpl implements WebSocketSessionManager {\n\n    /**\n     * id 与 WebSocketSession 映射\n     *\n     * key：Session 编号\n     */\n    private final ConcurrentMap<String, WebSocketSession> idSessions = new ConcurrentHashMap<>();\n\n    /**\n     * user 与 WebSocketSession 映射\n     *\n     * key1：用户类型\n     * key2：用户编号\n     */\n    private final ConcurrentMap<Integer, ConcurrentMap<Long, CopyOnWriteArrayList<WebSocketSession>>> userSessions\n            = new ConcurrentHashMap<>();\n\n    @Override\n    public void addSession(WebSocketSession session) {\n        // 添加到 idSessions 中\n        idSessions.put(session.getId(), session);\n        // 添加到 userSessions 中\n        LoginUser user = WebSocketFrameworkUtils.getLoginUser(session);\n        if (user == null) {\n            return;\n        }\n        ConcurrentMap<Long, CopyOnWriteArrayList<WebSocketSession>> userSessionsMap = userSessions.get(user.getUserType());\n        if (userSessionsMap == null) {\n            userSessionsMap = new ConcurrentHashMap<>();\n            if (userSessions.putIfAbsent(user.getUserType(), userSessionsMap) != null) {\n                userSessionsMap = userSessions.get(user.getUserType());\n            }\n        }\n        CopyOnWriteArrayList<WebSocketSession> sessions = userSessionsMap.get(user.getId());\n        if (sessions == null) {\n            sessions = new CopyOnWriteArrayList<>();\n            if (userSessionsMap.putIfAbsent(user.getId(), sessions) != null) {\n                sessions = userSessionsMap.get(user.getId());\n            }\n        }\n        sessions.add(session);\n    }\n\n    @Override\n    public void removeSession(WebSocketSession session) {\n        // 移除从 idSessions 中\n        idSessions.remove(session.getId());\n        // 移除从 idSessions 中\n        LoginUser user = WebSocketFrameworkUtils.getLoginUser(session);\n        if (user == null) {\n            return;\n        }\n        ConcurrentMap<Long, CopyOnWriteArrayList<WebSocketSession>> userSessionsMap = userSessions.get(user.getUserType());\n        if (userSessionsMap == null) {\n            return;\n        }\n        CopyOnWriteArrayList<WebSocketSession> sessions = userSessionsMap.get(user.getId());\n        sessions.removeIf(session0 -> session0.getId().equals(session.getId()));\n        if (CollUtil.isEmpty(sessions)) {\n            userSessionsMap.remove(user.getId(), sessions);\n        }\n    }\n\n    @Override\n    public WebSocketSession getSession(String id) {\n        return idSessions.get(id);\n    }\n\n    @Override\n    public Collection<WebSocketSession> getSessionList(Integer userType) {\n        ConcurrentMap<Long, CopyOnWriteArrayList<WebSocketSession>> userSessionsMap = userSessions.get(userType);\n        if (CollUtil.isEmpty(userSessionsMap)) {\n            return new ArrayList<>();\n        }\n        LinkedList<WebSocketSession> result = new LinkedList<>(); // 避免扩容\n        Long contextTenantId = TenantContextHolder.getTenantId();\n        for (List<WebSocketSession> sessions : userSessionsMap.values()) {\n            if (CollUtil.isEmpty(sessions)) {\n                continue;\n            }\n            // 特殊：如果租户不匹配，则直接排除\n            if (contextTenantId != null) {\n                Long userTenantId = WebSocketFrameworkUtils.getTenantId(sessions.get(0));\n                if (!contextTenantId.equals(userTenantId)) {\n                    continue;\n                }\n            }\n            result.addAll(sessions);\n        }\n        return result;\n    }\n\n    @Override\n    public Collection<WebSocketSession> getSessionList(Integer userType, Long userId) {\n        ConcurrentMap<Long, CopyOnWriteArrayList<WebSocketSession>> userSessionsMap = userSessions.get(userType);\n        if (CollUtil.isEmpty(userSessionsMap)) {\n            return new ArrayList<>();\n        }\n        CopyOnWriteArrayList<WebSocketSession> sessions = userSessionsMap.get(userId);\n        return CollUtil.isNotEmpty(sessions) ? new ArrayList<>(sessions) : new ArrayList<>();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-websocket/src/main/java/co/yixiang/yshop/framework/websocket/core/util/WebSocketFrameworkUtils.java",
    "content": "package co.yixiang.yshop.framework.websocket.core.util;\n\nimport co.yixiang.yshop.framework.security.core.LoginUser;\nimport org.springframework.web.socket.WebSocketSession;\n\nimport java.util.Map;\n\n/**\n * 专属于 web 包的工具类\n *\n * @author yshop\n */\npublic class WebSocketFrameworkUtils {\n\n    public static final String ATTRIBUTE_LOGIN_USER = \"LOGIN_USER\";\n\n    /**\n     * 设置当前用户\n     *\n     * @param loginUser 登录用户\n     * @param attributes Session\n     */\n    public static void setLoginUser(LoginUser loginUser, Map<String, Object> attributes) {\n        attributes.put(ATTRIBUTE_LOGIN_USER, loginUser);\n    }\n\n    /**\n     * 获取当前用户\n     *\n     * @return 当前用户\n     */\n    public static LoginUser getLoginUser(WebSocketSession session) {\n        return (LoginUser) session.getAttributes().get(ATTRIBUTE_LOGIN_USER);\n    }\n\n    /**\n     * 获得当前用户的编号\n     *\n     * @return 用户编号\n     */\n    public static Long getLoginUserId(WebSocketSession session) {\n        LoginUser loginUser = getLoginUser(session);\n        return loginUser != null ? loginUser.getId() : null;\n    }\n\n    /**\n     * 获得当前用户的类型\n     *\n     * @return 用户编号\n     */\n    public static Integer getLoginUserType(WebSocketSession session) {\n        LoginUser loginUser = getLoginUser(session);\n        return loginUser != null ? loginUser.getUserType() : null;\n    }\n\n    /**\n     * 获得当前用户的租户编号\n     *\n     * @param session Session\n     * @return 租户编号\n     */\n    public static Long getTenantId(WebSocketSession session) {\n        LoginUser loginUser = getLoginUser(session);\n        return loginUser != null ? loginUser.getTenantId() : null;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-framework/yshop-spring-boot-starter-websocket/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports",
    "content": "co.yixiang.yshop.framework.websocket.config.YshopWebSocketAutoConfiguration"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-express/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>co.yixiang.boot</groupId>\n        <artifactId>yshop</artifactId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>yshop-module-express</artifactId>\n    <packaging>pom</packaging>\n    <modules>\n        <module>yshop-module-express-api</module>\n        <module>yshop-module-express-biz</module>\n    </modules>\n\n    <name>${project.artifactId}</name>\n    <description>\n        express 模块\n    </description>\n\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-express/yshop-module-express-api/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>yshop-module-express</artifactId>\n        <groupId>co.yixiang.boot</groupId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>yshop-module-express-api</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>\n        pay 模块 API，暴露给其它模块调用\n    </description>\n\n    <dependencies>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-common</artifactId>\n        </dependency>\n\n        <!-- 参数校验 -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-validation</artifactId>\n            <optional>true</optional>\n        </dependency>\n\n        <!-- DB 相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-mybatis</artifactId>\n        </dependency>\n\n\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-express/yshop-module-express-api/src/main/java/co/yixiang/yshop/module/express/enums/ErrorCodeConstants.java",
    "content": "package co.yixiang.yshop.module.express.enums;\n\nimport co.yixiang.yshop.framework.common.exception.ErrorCode;\n\npublic interface ErrorCodeConstants {\n    // ========== 快递公司 1008008000 ==========\n    ErrorCode EXPRESS_NOT_EXISTS = new ErrorCode(1008008000, \"快递公司不存在\");\n    // ========== 电子面单  ==========\n    ErrorCode ELECTRONICS_ORDER_NOT_EXISTS = new ErrorCode(1008009000, \"电子面单不存在\");\n}\n\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-express/yshop-module-express-api/src/main/java/co/yixiang/yshop/module/express/kdniao/enums/KdniaoLogisticsCodeEnum.java",
    "content": "package co.yixiang.yshop.module.express.kdniao.enums;\n\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\nimport lombok.SneakyThrows;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\n\n/**\n * <p> 物流公司-对应编码-枚举 </p>\n *\n * @author hupeng\n * @date 2023/7/219\n */\n@Getter\n@AllArgsConstructor\npublic enum KdniaoLogisticsCodeEnum {\n\n    /**\n     * 申通\n     */\n    STO(\"STO\", \"申通\"),\n    /**\n     * 中通\n     */\n    ZTO(\"ZTO\", \"中通\"),\n    /**\n     * 圆通\n     */\n    YTO(\"YTO\", \"圆通\"),\n    /**\n     * 韵达\n     */\n    YD(\"YD\", \"韵达\"),\n    /**\n     * 顺丰\n     */\n    SF(\"SF\", \"顺丰\");\n\n    /**\n     * 物流编码\n     */\n    private final String code;\n    /**\n     * 物流名\n     */\n    private final String name;\n\n\n    private static final List<KdniaoLogisticsCodeEnum> LIST = new ArrayList();\n\n    static {\n        LIST.addAll(Arrays.asList(KdniaoLogisticsCodeEnum.values()));\n    }\n\n    /**\n     * 根据值查找相应枚举\n     */\n    @SneakyThrows(Exception.class)\n    public static KdniaoLogisticsCodeEnum getEnumByName(String name) {\n        for (KdniaoLogisticsCodeEnum itemEnum : LIST) {\n            if (itemEnum.getName().equals(name)) {\n                return itemEnum;\n            }\n        }\n        throw new Exception(\"暂无此物流编码信息，请联系系统管理员！\");\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-express/yshop-module-express-api/src/main/java/co/yixiang/yshop/module/express/kdniao/enums/KdniaoLogisticsStatusEnum.java",
    "content": "package co.yixiang.yshop.module.express.kdniao.enums;\n\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\n\n/**\n * <p> 物流状态枚举 </p>\n *\n * @author hupeng\n * @date 2023/7/219\n */\n@Getter\n@AllArgsConstructor\npublic enum KdniaoLogisticsStatusEnum {\n\n    /**\n     * 暂无轨迹信息\n     */\n    NO_TRACE(0, \"暂无轨迹信息\"),\n    /**\n     * 已揽收\n     */\n    HAVE_PAID(1, \"已揽收\"),\n    /**\n     * 已揽收 -----------------------------------------------------------------------------\n     */\n    ON_THE_WAY(2, \"在途中\"),\n    /**\n     * 到达派件城市\n     */\n    ARRIVE_AT_THE_DISPATCH_CITY(201, \"到达派件城市\"),\n    /**\n     * 派件中\n     */\n    IN_THE_DELIVERY(202, \"派件中\"),\n    /**\n     * 已放入快递柜或驿站\n     */\n    HAS_STORED(211, \"已放入快递柜或驿站\"),\n    /**\n     * 签收 -----------------------------------------------------------------------------\n     */\n    SIGN(3, \"签收\"),\n    /**\n     * 正常签收\n     */\n    SIGN_NORMAL(301, \"正常签收\"),\n    /**\n     * 派件异常后最终签收\n     */\n    SIGN_ABNORMAL(302, \"派件异常后最终签收\"),\n    /**\n     * 代收签收\n     */\n    SIGN_COLLECTION(304, \"代收签收\"),\n    /**\n     * 快递柜或驿站签收\n     */\n    SIGN_STORED(311, \"快递柜或驿站签收\"),\n    /**\n     * 问题件 -----------------------------------------------------------------------------\n     */\n    PROBLEM_SHIPMENT(4, \"问题件\"),\n    /**\n     * 发货无信息\n     */\n    DELIVERY_NO_INFO(401, \"发货无信息\"),\n    /**\n     * 超时未签收\n     */\n    NO_SIGN_OVER_TIME(402, \"超时未签收\"),\n    /**\n     * 超时未更新\n     */\n    NOT_UPDATED_DUE_TO_TIMEOUT(403, \"超时未更新\"),\n    /**\n     * 拒收(退件)\n     */\n    REJECTION(404, \"拒收(退件)\"),\n    /**\n     * 派件异常\n     */\n    SEND_A_ABNORMAL(405, \"派件异常\"),\n    /**\n     * 退货签收\n     */\n    RETURN_TO_SIGN_FOR(406, \"退货签收\"),\n    /**\n     * 退货未签收\n     */\n    RETURN_NOT_SIGNED_FOR(407, \"退货未签收\"),\n    /**\n     * 快递柜或驿站超时未取\n     */\n    STORED_OVER_TIME(412, \"快递柜或驿站超时未取\"),\n    /**\n     * -\n     */\n    DEFAULT(0, \"-\");\n\n    /**\n     * 状态\n     */\n    private final Integer status;\n    /**\n     * 描述\n     */\n    private final String desc;\n\n    private static final List<KdniaoLogisticsStatusEnum> LIST = new ArrayList();\n\n    static {\n        LIST.addAll(Arrays.asList(KdniaoLogisticsStatusEnum.values()));\n    }\n\n    /**\n     * 根据物流状态查找相应枚举\n     */\n    public static KdniaoLogisticsStatusEnum getEnum(Integer status) {\n        for (KdniaoLogisticsStatusEnum itemEnum : LIST) {\n            if (itemEnum.getStatus().equals(status)) {\n                return itemEnum;\n            }\n        }\n        return KdniaoLogisticsStatusEnum.DEFAULT;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-express/yshop-module-express-api/src/main/java/co/yixiang/yshop/module/express/kdniao/model/dto/KdniaoApiBaseDTO.java",
    "content": "package co.yixiang.yshop.module.express.kdniao.model.dto;\n\n\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport lombok.experimental.SuperBuilder;\n\n/**\n * <p>\n * 快递鸟-物流-查询base参数\n * </p>\n *\n * @author hupeng\n * @date 2023/7/21\n */\n@Data\n@SuperBuilder\n@NoArgsConstructor\n@AllArgsConstructor\n//快递鸟-物流-查询base参数\npublic class KdniaoApiBaseDTO {\n\n    //用户ID\n    private String eBusinessID;\n\n    //API key\n    private String apiKey;\n\n   //请求url\n    //https://api.kdniao.com/Ebusiness/EbusinessOrderHandle.aspx\n    private String reqURL;\n\n    private Boolean isFree;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-express/yshop-module-express-api/src/main/java/co/yixiang/yshop/module/express/kdniao/model/dto/KdniaoApiDTO.java",
    "content": "package co.yixiang.yshop.module.express.kdniao.model.dto;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.NoArgsConstructor;\nimport lombok.experimental.SuperBuilder;\n\n/**\n * <p>\n * 快递鸟-物流-查询参数\n * </p>\n *\n * @author hupeng\n * @date 2023/7/21\n */\n@Data\n@SuperBuilder\n@NoArgsConstructor\n@AllArgsConstructor\n@EqualsAndHashCode(callSuper = true)\n//快递鸟-物流-查询参数\npublic class KdniaoApiDTO extends KdniaoApiBaseDTO {\n\n    //快递公司编码   ZTO\n    private String shipperCode;\n\n    //快递单号\n    private String logisticCode;\n\n    //是否收费 true=免费 false 收费\n    private Boolean isFree;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-express/yshop-module-express-api/src/main/java/co/yixiang/yshop/module/express/kdniao/model/dto/KdniaoElectronicsOrderDTO.java",
    "content": "package co.yixiang.yshop.module.express.kdniao.model.dto;\n\nimport lombok.*;\n\n/**\n * 电子面单 DTO\n *\n * @author yshop\n */\n@Data\n@ToString(callSuper = true)\n@NoArgsConstructor\n@AllArgsConstructor\npublic class KdniaoElectronicsOrderDTO extends KdniaoApiBaseDTO {\n\n    //用户\n    private Long memberID;\n\n    //订单编号\n    private String orderCode;\n\n    //快递公司编码\n    private String shipperCode;\n\n    //快递单号\n    private String logisticCode;\n\n    //其他费用\n    private Double otherCost;\n\n    /**\n     * 运费支付方式:1=现付,2=到付,3=月结,4=第三方付(仅SF支持)\n     */\n    private Integer paytype;\n    /**\n     * 线下网点客户号\n     */\n    private String customerName;\n    /**\n     * 线下网点密码\n     */\n    private String customerPwd;\n    /**\n     * 网点名称\n     */\n    private String sendSite;\n    /**\n     * 网点快递员\n     */\n    private String sendStaff;\n\n    //快递运费\n    private Double cost;\n    /**\n     * 月结编号\n     */\n    private String monthCode;\n    /**\n     * 是否通知揽件:0=通知揽件,1=不通知揽件\n     */\n    private Integer isNotice;\n    /**\n     * 是否返回电子面单模板:0=不返回,1=返回\n     */\n    private Integer isReturnTemp;\n    /**\n     * 是否需要短信提醒:0=否,1=是\n     */\n    private Integer isSendMessage;\n    /**\n     * 模板尺寸\n     */\n    private String templateSize;\n    /**\n     * 签回单操作要求(如：签名、盖章、身份证复印件等)\n     */\n    private String operateRequire;\n\n    /**\n     * 上门揽件开始时间\n     */\n    private Integer startDate;\n    /**\n     * 上门揽件结束时间\n     */\n    private Integer endDate;\n    /**\n     * 备注\n     */\n    private String remark;\n    /**\n     * 快递类型:1=标准快件\n     */\n    private Integer expType;\n    /**\n     * 是否要签回单:0=否,1=是\n     */\n    private Integer isReturnSignBill;\n    /**\n     * 发件人公司\n     */\n    private String company;\n    /**\n     * 发件人省\n     */\n    private String provinceName;\n    /**\n     * 发件人市\n     */\n    private String cityName;\n    /**\n     * 发件人区\n     */\n    private String expAreaName;\n    /**\n     * 发件人详细地址\n     */\n    private String address;\n    /**\n     * 发件人姓名\n     */\n    private String name;\n    /**\n     * 发件人电话\n     */\n    private String tel;\n    /**\n     * 发件人手机号码\n     */\n    private String mobile;\n    /**\n     * 发件地邮编\n     */\n    private String postCode;\n\n\n    /**\n     * 收件人公司\n     */\n    private String receiverCompany;\n    /**\n     * 收件人省\n     */\n    private String receiverProvinceName;\n    /**\n     * 收件人市\n     */\n    private String receiverCityName;\n    /**\n     * 收件人区\n     */\n    private String receiverExpAreaName;\n    /**\n     * 收件人详细地址\n     */\n    private String receiverAddress;\n    /**\n     * 收件人姓名\n     */\n    private String receiverName;\n    /**\n     * 收件人电话\n     */\n    private String receiverTel;\n    /**\n     * 收件人手机号码\n     */\n    private String receiverMobile;\n    /**\n     * 收件地邮编\n     */\n    private String receiverPostCode;\n\n    /**\n     * 重量\n     */\n    private Double weight;\n\n    /**\n     * 重量\n     */\n    private Integer quantity;\n\n    /**\n     * 体积\n     *\n     */\n    private Double volume;\n\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-express/yshop-module-express-api/src/main/java/co/yixiang/yshop/module/express/kdniao/model/dto/KdniaoElectronicsOrderGoodsDTO.java",
    "content": "package co.yixiang.yshop.module.express.kdniao.model.dto;\n\nimport lombok.*;\n\n\n/**\n * 电子面单 DTO\n *\n * @author yshop\n */\n@Data\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class KdniaoElectronicsOrderGoodsDTO {\n\n    /**\n     * 商品名称\n     */\n    private String goodsName;\n\n    /**\n     * 商品编号\n     */\n    private String goodsCode;\n    /**\n     * 商品件数\n     */\n    private Integer goodsQuantity;\n    /**\n     * 商品价格\n     */\n    private Double goodsPrice;\n    /**\n     * 商品重量kg\n     */\n    private Double goodsWeight;\n    /**\n     * 商品描述\n     */\n    private String GoodsDesc;\n    /**\n     * 商品体积m³\n     */\n    private Double GoodsVol;\n\n\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-express/yshop-module-express-api/src/main/java/co/yixiang/yshop/module/express/kdniao/model/vo/KdniaoApiVO.java",
    "content": "package co.yixiang.yshop.module.express.kdniao.model.vo;\n\n\nimport co.yixiang.yshop.module.express.kdniao.enums.KdniaoLogisticsStatusEnum;\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport lombok.experimental.SuperBuilder;\nimport org.springframework.util.CollectionUtils;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * <p>\n * 快递鸟-物流-响应参数\n * </p>\n * @author hupeng\n * @date 2023/7/21\n */\n@Data\n@SuperBuilder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class KdniaoApiVO {\n\n    /**\n     * {@link KdniaoLogisticsStatusEnum }\n     * 增值物流状态：\n     * 0-暂无轨迹信息\n     * 1-已揽收\n     * 2-在途中\n     * 201-到达派件城市, 202-派件中, 211-已放入快递柜或驿站,\n     * 3-已签收\n     * 301-正常签收, 302-派件异常后最终签收, 304-代收签收, 311-快递柜或驿站签收,\n     * 4-问题件\n     * 401-发货无信息, 402-超时未签收, 403-超时未更新, 404-拒收(退件), 405-派件异常, 406-退货签收, 407-退货未签收, 412-快递柜或驿站超时未取\n     */\n\n    //增值物流状态\n    private Integer StateEx;\n\n    //增值物流状态名称\n    private String statusExName;\n\n    //快递单号\n    private String LogisticCode;\n\n    //快递公司编码\n    private String ShipperCode;\n\n\n    //失败原因\n    private String Reason;\n\n\n    //事件轨迹集\n    private List<TraceItem> Traces;\n\n    /**\n     * {@link KdniaoLogisticsStatusEnum }\n     */\n    //物流状态：0-暂无轨迹信息,1-已揽收,2-在途中,3-签收,4-问题件\n    private Integer State;\n\n    //状态名称\n    private String statusName;\n\n    //用户ID\n    private String EBusinessID;\n\n    //送货人\n    private String DeliveryMan;\n\n\n    //送货人电话号码\n    private String DeliveryManTel;\n\n    //成功与否 true/false\n    private String Success;\n\n\n    //所在城市\n    private String Location;\n\n    @Data\n    @Builder\n    @NoArgsConstructor\n    @AllArgsConstructor\n    //事件轨迹集\n    public static class TraceItem {\n        /**\n         * {@link KdniaoLogisticsStatusEnum }\n         */\n        //当前状态(同StateEx)\n        private Integer Action;\n\n        //状态名称\n        private String actionName;\n\n        //描述\n        private String AcceptStation;\n\n        //时间\n        private String AcceptTime;\n\n        //所在城市\n        private String Location;\n    }\n\n\n    public void handleData() {\n        this.statusName = KdniaoLogisticsStatusEnum.getEnum(this.State).getDesc();\n        this.statusExName = KdniaoLogisticsStatusEnum.getEnum(this.StateEx).getDesc();\n        if (CollectionUtils.isEmpty(this.Traces)) {\n            this.Traces = new ArrayList();\n        }\n        this.Traces.forEach(item -> item.actionName = KdniaoLogisticsStatusEnum.getEnum(item.Action).getDesc());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-express/yshop-module-express-api/src/main/java/co/yixiang/yshop/module/express/kdniao/model/vo/KdniaoLogisticsVO.java",
    "content": "package co.yixiang.yshop.module.express.kdniao.model.vo;\n\nimport co.yixiang.yshop.module.express.kdniao.enums.KdniaoLogisticsCodeEnum;\nimport co.yixiang.yshop.module.express.kdniao.enums.KdniaoLogisticsStatusEnum;\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport org.springframework.util.CollectionUtils;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * <p> 快递鸟-物流-参数 </p>\n *\n * @author hupeng\n * @date 2023/7/21\n */\n@Data\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class KdniaoLogisticsVO {\n\n    /**\n     * {@link KdniaoLogisticsCodeEnum }\n     */\n    //快递公司编码\n    private String logisticsCode;\n\n    //快递单号\n    private String logisticsNo;\n\n    //事件轨迹集\n    private List<TraceItem> traceList;\n\n    //送货人名称\n    private String delivererName;\n\n\n    //送货人电话号码\n    private String delivererPhone;\n\n    //所在城市\n    private String location;\n\n    /**\n     * {@link KdniaoLogisticsStatusEnum }\n     */\n    //物流状态\n    private Integer status;\n\n    //状态名称\n    private String statusName;\n\n    /**\n     * {@link KdniaoLogisticsStatusEnum }\n     */\n    //增值物流状态\n    private Integer statusEx;\n\n    //增值物流状态名称\n    private String statusExName;\n\n    @Data\n    @Builder\n    @NoArgsConstructor\n    @AllArgsConstructor\n    //事件轨迹集\n    public static class TraceItem {\n        //状态\n        private Integer status;\n\n        //状态名称\n        private String statusName;\n\n        //描述\n        private String desc;\n\n        //时间\n        private String time;\n\n        //所在城市\n        private String location;\n    }\n\n    public void handleData() {\n        this.statusName = KdniaoLogisticsStatusEnum.getEnum(this.status).getDesc();\n        this.statusExName = KdniaoLogisticsStatusEnum.getEnum(this.statusEx).getDesc();\n        if (CollectionUtils.isEmpty(this.traceList)) {\n            this.traceList = new ArrayList();\n        }\n        this.traceList.forEach(item -> item.statusName = KdniaoLogisticsStatusEnum.getEnum(item.status).getDesc());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-express/yshop-module-express-api/src/main/java/co/yixiang/yshop/module/express/kdniao/model/vo/KdniaoOrderVO.java",
    "content": "package co.yixiang.yshop.module.express.kdniao.model.vo;\n\n\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport lombok.experimental.SuperBuilder;\n\n/**\n * <p>\n * 快递鸟-面单-响应参数\n * </p>\n * @author hupeng\n * @date 2023/7/22\n */\n@Data\n@SuperBuilder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class KdniaoOrderVO {\n    //返回编码\n    private String ResultCode;\n\n    //失败原因\n    private String Reason;\n\n    //成功与否 true/false\n    private String Success;\n\n    //面单打印模板内容(html格式)\n    private String PrintTemplate;\n\n    //唯一标识\n    private String UniquerRequestNumber;\n\n    //子单数量\n    private Integer SubCount;\n\n    //子单单号\n    private String SubOrders;\n\n    //子单模板内容(html格式)\n    private String SubPrintTemplates;\n\n    //签回单模板内容\n    private String SignBillPrintTemplate;\n\n    //订单信息\n    private OrderInfo Order;\n\n\n\n    @Data\n    @Builder\n    @NoArgsConstructor\n    @AllArgsConstructor\n    public static class OrderInfo {\n\n        //订单编号\n        private String OrderCode;\n\n        //快递公司编码\n        private String ShipperCode;\n\n        //快递单号\n        private String LogisticCode;\n\n        //大头笔\n        private String MarkDestination;\n\n        //签回单单号\n        private String SignWaybillCode;\n\n\n        //始发地区域编码\n        private String OriginCode;\n\n        //始发地/始发网点\n        private String OriginName;\n\n        //目的地区域编码\n        private String DestinatioCode;\n\n        //目的地/到达网点\n        private String DestinatioName;\n\n        //分拣编码\n        private String SortingCode;\n\n\n        //集包编码\n        private String PackageCode;\n\n        //集包地\n        private String PackageName;\n\n        //目的地分拨\n        private String DestinationAllocationCentre;\n\n        //配送产品类型\n        private Integer TransType;\n\n        //运输方式(用于自行设计京东模板)：\n        //0：陆运\n        //1：航空\n        private Integer TransportType;\n\n        //自行设计模板用\n        private String ShipperInfo;\n    }\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-express/yshop-module-express-api/src/main/java/co/yixiang/yshop/module/express/kdniao/util/KdniaoUtil.java",
    "content": "package co.yixiang.yshop.module.express.kdniao.util;\n\nimport co.yixiang.yshop.framework.common.exception.ErrorCode;\nimport co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil;\nimport co.yixiang.yshop.module.express.kdniao.model.dto.KdniaoApiDTO;\nimport co.yixiang.yshop.module.express.kdniao.model.dto.KdniaoElectronicsOrderDTO;\nimport co.yixiang.yshop.module.express.kdniao.model.dto.KdniaoElectronicsOrderGoodsDTO;\nimport co.yixiang.yshop.module.express.kdniao.model.vo.KdniaoApiVO;\nimport co.yixiang.yshop.module.express.kdniao.model.vo.KdniaoOrderVO;\nimport com.alibaba.fastjson.JSON;\nimport cn.hutool.core.codec.Base64;\nimport lombok.SneakyThrows;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.io.*;\nimport java.net.HttpURLConnection;\nimport java.net.URL;\nimport java.net.URLEncoder;\nimport java.security.MessageDigest;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\n\n/**\n * <p> 快递鸟工具类 </p>\n *\n * @author hupeng\n * @date 2023/7/219\n */\n@Slf4j\npublic class KdniaoUtil {\n    //查询物流当url\n    private final String KDNIAO_LOGISTIC_QUERY = \"https://api.kdniao.com/Ebusiness/EbusinessOrderHandle.aspx\";\n    //电子面单url\n    private final String KDNIAO_ELECT_QUERY = \"https://api.kdniao.com/api/EOrderService\";\n\n    /**\n     * 快递查询接口\n     *\n     * @param queryDTO 请求参数\n     * @return 物流信息\n     */\n    public static KdniaoApiVO getLogisticInfo(KdniaoApiDTO queryDTO){\n        KdniaoApiVO kdniaoApiVO = new KdniaoUtil().getLogisticBase(queryDTO);\n        if (kdniaoApiVO.getSuccess() == \"false\"){\n           throw ServiceExceptionUtil.exception(new ErrorCode(999999,kdniaoApiVO.getReason()));\n        }\n\n        kdniaoApiVO.handleData();\n        return kdniaoApiVO;\n    }\n\n    /**\n     * 获取电子面单信息\n     * @param queryDTO\n     * @param kdniaoElectronicsOrderGoodsDTOList\n     * @return\n     */\n    public static KdniaoOrderVO getOrderInfo(KdniaoElectronicsOrderDTO queryDTO,\n                                             List<KdniaoElectronicsOrderGoodsDTO> kdniaoElectronicsOrderGoodsDTOList) {\n        KdniaoOrderVO kdniaoOrderVO = new KdniaoUtil().getEleCtBase(queryDTO,kdniaoElectronicsOrderGoodsDTOList);\n        //todo 由于目前快递鸟订单打印需要申请当地营业网店账号 所有目前这个没法测试 如果有其他用户有可以测试反馈给我们官方\n        if (kdniaoOrderVO.getSuccess() == \"false\"){\n            log.error(kdniaoOrderVO.getReason());\n            throw ServiceExceptionUtil.exception(new ErrorCode(999999,kdniaoOrderVO.getReason()));\n        }\n\n        return kdniaoOrderVO;\n    }\n\n    /**\n     * 快递查询接口\n     *\n     * @param queryDTO 请求参数\n     * @return 物流信息\n     */\n    @SneakyThrows(Exception.class)\n    private KdniaoApiVO getLogisticBase(KdniaoApiDTO queryDTO) {\n        String EBusinessID = queryDTO.getEBusinessID();\n        String ApiKey = queryDTO.getApiKey();\n        String shipperCode = queryDTO.getShipperCode();\n        String logisticCode = queryDTO.getLogisticCode();\n\n        // 组装应用级参数\n        Map<String, String> requestParamMap = new HashMap<>();\n        requestParamMap.put(\"shipperCode\", shipperCode);\n        requestParamMap.put(\"LogisticCode\", logisticCode);\n        String RequestData = JSON.toJSONString(requestParamMap);\n        // 组装系统级参数\n        Map<String, String> params =  new HashMap<>();\n        params.put(\"RequestData\", this.urlEncoder(RequestData, \"UTF-8\"));\n        params.put(\"EBusinessID\", EBusinessID);\n        if(queryDTO.getIsFree()) {\n            params.put(\"RequestType\", \"1002\");//免费1002 收费8001\n        }else{\n            params.put(\"RequestType\", \"8001\");\n        }\n        String dataSign = this.encrypt(RequestData, ApiKey, \"UTF-8\");\n        params.put(\"DataSign\", this.urlEncoder(dataSign, \"UTF-8\"));\n        params.put(\"DataType\", \"2\");\n        // 以form表单形式提交post请求，post请求体中包含了应用级参数和系统级参数\n        String resultJson = this.sendPost(KDNIAO_LOGISTIC_QUERY, params);\n        return JSON.parseObject(resultJson, KdniaoApiVO.class);\n    }\n\n    /**\n     * 快递查询接口\n     *\n     * @param queryDTO 请求参数\n     * @return 物流信息\n     */\n    @SneakyThrows(Exception.class)\n    private KdniaoOrderVO getEleCtBase(KdniaoElectronicsOrderDTO queryDTO,\n                                       List<KdniaoElectronicsOrderGoodsDTO> kdniaoElectronicsOrderGoodsDTOList) {\n        String EBusinessID = queryDTO.getEBusinessID();\n        String ApiKey = queryDTO.getApiKey();\n\n\n        Map<String, Object> requestParamMap = this.doMap(queryDTO,kdniaoElectronicsOrderGoodsDTOList);\n        System.out.println(\"map:\"+requestParamMap);\n        String RequestData = JSON.toJSONString(requestParamMap);\n        // 组装系统级参数\n        Map<String, String> params = new HashMap<>();\n        params.put(\"RequestData\", this.urlEncoder(RequestData, \"UTF-8\"));\n        params.put(\"EBusinessID\", EBusinessID);\n        params.put(\"RequestType\", \"1007\");\n        String dataSign = this.encrypt(RequestData, ApiKey, \"UTF-8\");\n        params.put(\"DataSign\", this.urlEncoder(dataSign, \"UTF-8\"));\n        params.put(\"DataType\", \"2\");\n\n        String resultJson = this.sendPost(KDNIAO_ELECT_QUERY, params);\n        return JSON.parseObject(resultJson, KdniaoOrderVO.class);\n    }\n\n    //组合数据\n    private Map<String, Object> doMap(KdniaoElectronicsOrderDTO queryDTO,\n                                      List<KdniaoElectronicsOrderGoodsDTO> kdniaoElectronicsOrderGoodsDTOList){\n\n        // 组装应用级参数\n        Map<String, Object> requestParamMap = new HashMap<>();\n        requestParamMap.put(\"MemberID\", queryDTO.getMemberID());\n        requestParamMap.put(\"OrderCode\", queryDTO.getOrderCode());\n        requestParamMap.put(\"ShipperCode\", queryDTO.getShipperCode());\n        requestParamMap.put(\"LogisticCode\", queryDTO.getLogisticCode());\n        requestParamMap.put(\"CustomerName\", queryDTO.getCustomerName());\n        requestParamMap.put(\"CustomerPwd\", queryDTO.getCustomerPwd());\n        requestParamMap.put(\"SendSite\", queryDTO.getSendSite());\n\n        requestParamMap.put(\"PayType\", queryDTO.getPaytype());\n        requestParamMap.put(\"MonthCode\", queryDTO.getMonthCode());\n        requestParamMap.put(\"IsReturnSignBill\", queryDTO.getIsReturnSignBill());\n        requestParamMap.put(\"OperateRequire\", queryDTO.getOperateRequire());\n        requestParamMap.put(\"ExpType\", queryDTO.getExpType());\n        requestParamMap.put(\"Cost\", queryDTO.getCost());\n\n        requestParamMap.put(\"OtherCost\", queryDTO.getOtherCost());\n\n        Map<String, Object> senderMap = new HashMap<>();\n        senderMap.put(\"Name\", queryDTO.getName());\n        senderMap.put(\"Tel\", queryDTO.getTel());\n        senderMap.put(\"Company\", queryDTO.getCompany());\n        senderMap.put(\"Mobile\", queryDTO.getMobile());\n        senderMap.put(\"PostCode\", queryDTO.getPostCode());\n        senderMap.put(\"ProvinceName\", queryDTO.getProvinceName());\n        senderMap.put(\"CityName\", queryDTO.getCityName());\n        senderMap.put(\"ExpAreaName\", queryDTO.getExpAreaName());\n        senderMap.put(\"Address\", queryDTO.getAddress());\n        requestParamMap.put(\"Sender\", senderMap);\n\n        Map<String, Object> receiverMap = new HashMap<>();\n        receiverMap.put(\"Name\", queryDTO.getReceiverName());\n        receiverMap.put(\"Tel\", queryDTO.getReceiverTel());\n        receiverMap.put(\"Company\", queryDTO.getReceiverCompany());\n        receiverMap.put(\"Mobile\", queryDTO.getReceiverMobile());\n        receiverMap.put(\"PostCode\", queryDTO.getReceiverPostCode());\n        receiverMap.put(\"ProvinceName\", queryDTO.getReceiverProvinceName());\n        receiverMap.put(\"CityName\", queryDTO.getReceiverCityName());\n        receiverMap.put(\"ExpAreaName\", queryDTO.getReceiverExpAreaName());\n        receiverMap.put(\"Address\", queryDTO.getReceiverAddress());\n        requestParamMap.put(\"Receiver\", receiverMap);\n\n        List<Map<String, Object>> commodityMapList = new ArrayList<>();\n        for (KdniaoElectronicsOrderGoodsDTO kdniaoElectronicsOrderGoodsDTO : kdniaoElectronicsOrderGoodsDTOList) {\n            Map<String, Object> commodityMap = new HashMap<>();\n            commodityMap.put(\"GoodsName\", kdniaoElectronicsOrderGoodsDTO.getGoodsName());\n            commodityMap.put(\"GoodsCode\", kdniaoElectronicsOrderGoodsDTO.getGoodsCode());\n            commodityMap.put(\"Goodsquantity\", kdniaoElectronicsOrderGoodsDTO.getGoodsQuantity());\n            commodityMap.put(\"GoodsPrice\", kdniaoElectronicsOrderGoodsDTO.getGoodsPrice());\n            commodityMap.put(\"GoodsWeight\", kdniaoElectronicsOrderGoodsDTO.getGoodsWeight());\n            commodityMap.put(\"GoodsVol\", kdniaoElectronicsOrderGoodsDTO.getGoodsVol());\n            commodityMap.put(\"GoodsDesc\", kdniaoElectronicsOrderGoodsDTO.getGoodsDesc());\n\n            commodityMapList.add(commodityMap);\n        }\n\n        requestParamMap.put(\"Commodity\", commodityMapList);\n\n        requestParamMap.put(\"IsNotice\", queryDTO.getIsNotice());\n        requestParamMap.put(\"StartDate\", queryDTO.getStartDate());\n        requestParamMap.put(\"EndDate\", queryDTO.getEndDate());\n        requestParamMap.put(\"Weight\", queryDTO.getWeight());\n        requestParamMap.put(\"Quantity\", queryDTO.getQuantity());\n        requestParamMap.put(\"Volume\", queryDTO.getVolume());\n        requestParamMap.put(\"IsReturnPrintTemplate\", queryDTO.getIsReturnTemp());\n        requestParamMap.put(\"Remark\", queryDTO.getRemark());\n\n        return requestParamMap;\n\n    }\n\n\n    /**\n     * MD5加密\n     * str 内容\n     * charset 编码方式\n     *\n     * @throws Exception\n     */\n    @SuppressWarnings(\"unused\")\n    private String MD5(String str, String charset) throws Exception {\n        MessageDigest md = MessageDigest.getInstance(\"MD5\");\n        md.update(str.getBytes(charset));\n        byte[] result = md.digest();\n        StringBuffer sb = new StringBuffer(32);\n        for (int i = 0; i < result.length; i++) {\n            int val = result[i] & 0xff;\n            if (val <= 0xf) {\n                sb.append(\"0\");\n            }\n            sb.append(Integer.toHexString(val));\n        }\n        return sb.toString().toLowerCase();\n    }\n\n    /**\n     * base64编码\n     * str 内容\n     * charset 编码方式\n     *\n     * @throws UnsupportedEncodingException\n     */\n    private String base64(String str, String charset) throws UnsupportedEncodingException {\n        String encoded = Base64.encode(str.getBytes(charset));\n        return encoded;\n    }\n\n    @SuppressWarnings(\"unused\")\n    private String urlEncoder(String str, String charset) throws UnsupportedEncodingException {\n        String result = URLEncoder.encode(str, charset);\n        return result;\n    }\n\n    /**\n     * 电商Sign签名生成\n     * content 内容\n     * keyValue ApiKey\n     * charset 编码方式\n     *\n     * @return DataSign签名\n     * @throws UnsupportedEncodingException ,Exception\n     */\n    @SuppressWarnings(\"unused\")\n    private String encrypt(String content, String keyValue, String charset) throws UnsupportedEncodingException, Exception {\n        if (keyValue != null) {\n            return base64(MD5(content + keyValue, charset), charset);\n        }\n        return base64(MD5(content, charset), charset);\n    }\n\n    /**\n     * 向指定 URL 发送POST方法的请求\n     * url 发送请求的 URL\n     * params 请求的参数集合\n     *\n     * @return 远程资源的响应结果\n     */\n    @SuppressWarnings(\"unused\")\n    private String sendPost(String url, Map<String, String> params) {\n        OutputStreamWriter out = null;\n        BufferedReader in = null;\n        StringBuilder result = new StringBuilder();\n        try {\n            URL realUrl = new URL(url);\n            HttpURLConnection conn = (HttpURLConnection) realUrl.openConnection();\n            // 发送POST请求必须设置如下两行\n            conn.setDoOutput(true);\n            conn.setDoInput(true);\n            // POST方法\n            conn.setRequestMethod(\"POST\");\n            // 设置通用的请求属性\n            conn.setRequestProperty(\"accept\", \"*/*\");\n            conn.setRequestProperty(\"connection\", \"Keep-Alive\");\n            conn.setRequestProperty(\"user-agent\",\n                    \"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)\");\n            conn.setRequestProperty(\"Content-Type\", \"application/x-www-form-urlencoded\");\n            conn.connect();\n            // 获取URLConnection对象对应的输出流\n            out = new OutputStreamWriter(conn.getOutputStream(), \"UTF-8\");\n            // 发送请求参数\n            if (params != null) {\n                StringBuilder param = new StringBuilder();\n                for (Map.Entry<String, String> entry : params.entrySet()) {\n                    if (param.length() > 0) {\n                        param.append(\"&\");\n                    }\n                    param.append(entry.getKey());\n                    param.append(\"=\");\n                    param.append(entry.getValue());\n                }\n                log.info(\"[快递鸟] 请求参数: [{}]\", param);\n                out.write(param.toString());\n            }\n            // flush输出流的缓冲\n            out.flush();\n            // 定义BufferedReader输入流来读取URL的响应\n            in = new BufferedReader(\n                    new InputStreamReader(conn.getInputStream(), \"UTF-8\"));\n            String line;\n            while ((line = in.readLine()) != null) {\n                result.append(line);\n            }\n        } catch (Exception e) {\n            e.printStackTrace();\n        }\n        //使用finally块来关闭输出流、输入流\n        finally {\n            try {\n                if (out != null) {\n                    out.close();\n                }\n                if (in != null) {\n                    in.close();\n                }\n            } catch (IOException ex) {\n                ex.printStackTrace();\n            }\n        }\n        return result.toString();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-express/yshop-module-express-biz/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>yshop-module-express</artifactId>\n        <groupId>co.yixiang.boot</groupId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>yshop-module-express-biz</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>\n        快递 模块\n    </description>\n\n    <dependencies>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-express-api</artifactId>\n            <version>${revision}</version>\n        </dependency>\n\n        <!-- 业务组件 -->\n<!--        <dependency>-->\n<!--            <groupId>co.yixiang.boot</groupId>-->\n<!--            <artifactId>yshop-spring-boot-starter-biz-operatelog</artifactId>-->\n<!--        </dependency>-->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-biz-tenant</artifactId>\n        </dependency>\n\n        <!-- Web 相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-security</artifactId>\n        </dependency>\n\n\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-redis</artifactId>\n        </dependency>\n\n\n        <!-- 消息队列相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-mq</artifactId>\n        </dependency>\n\n        <!-- Test 测试相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n\n        <!-- 工具类相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-excel</artifactId>\n        </dependency>\n\n\n\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-express/yshop-module-express-biz/src/main/java/co/yixiang/yshop/module/express/controller/admin/express/ExpressController.java",
    "content": "package co.yixiang.yshop.module.express.controller.admin.express;\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.excel.core.util.ExcelUtils;\nimport co.yixiang.yshop.module.express.controller.admin.express.vo.*;\nimport co.yixiang.yshop.module.express.convert.express.ExpressConvert;\nimport co.yixiang.yshop.module.express.dal.dataobject.express.ExpressDO;\nimport co.yixiang.yshop.module.express.dal.redis.express.ExpressRedisDAO;\nimport co.yixiang.yshop.module.express.kdniao.model.dto.KdniaoApiBaseDTO;\nimport co.yixiang.yshop.module.express.kdniao.model.dto.KdniaoApiDTO;\nimport co.yixiang.yshop.module.express.kdniao.model.vo.KdniaoApiVO;\nimport co.yixiang.yshop.module.express.kdniao.util.KdniaoUtil;\nimport co.yixiang.yshop.module.express.service.express.ExpressService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.Parameters;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport jakarta.annotation.Resource;\nimport jakarta.servlet.http.HttpServletResponse;\nimport jakarta.validation.Valid;\nimport java.io.IOException;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n\n@Tag(name = \"管理后台 - 快递公司\")\n@RestController\n@RequestMapping(\"/order/express\")\n@Validated\npublic class ExpressController {\n\n    @Resource\n    private ExpressService expressService;\n    @Resource\n    private ExpressRedisDAO expressRedisDAO;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建快递公司\")\n    @PreAuthorize(\"@ss.hasPermission('order:express:create')\")\n    public CommonResult<Integer> createExpress(@Valid @RequestBody ExpressCreateReqVO createReqVO) {\n        return success(expressService.createExpress(createReqVO));\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"更新快递公司\")\n    @PreAuthorize(\"@ss.hasPermission('order:express:update')\")\n    public CommonResult<Boolean> updateExpress(@Valid @RequestBody ExpressUpdateReqVO updateReqVO) {\n        expressService.updateExpress(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除快递公司\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('order:express:delete')\")\n    public CommonResult<Boolean> deleteExpress(@RequestParam(\"id\") Integer id) {\n        expressService.deleteExpress(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得快递公司\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('order:express:query')\")\n    public CommonResult<ExpressRespVO> getExpress(@RequestParam(\"id\") Integer id) {\n        ExpressDO express = expressService.getExpress(id);\n        return success(ExpressConvert.INSTANCE.convert(express));\n    }\n\n    @GetMapping(\"/list\")\n    @Operation(summary = \"获得快递公司列表\")\n    @PreAuthorize(\"@ss.hasPermission('order:express:query')\")\n    public CommonResult<List<ExpressRespVO>> getExpressList() {\n        List<ExpressDO> list = expressService.getExpressList();\n        return success(ExpressConvert.INSTANCE.convertList(list));\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得快递公司分页\")\n    @PreAuthorize(\"@ss.hasPermission('order:express:query')\")\n    public CommonResult<PageResult<ExpressRespVO>> getExpressPage(@Valid ExpressPageReqVO pageVO) {\n        PageResult<ExpressDO> pageResult = expressService.getExpressPage(pageVO);\n        return success(ExpressConvert.INSTANCE.convertPage(pageResult));\n    }\n\n    @GetMapping(\"/export-excel\")\n    @Operation(summary = \"导出快递公司 Excel\")\n    @PreAuthorize(\"@ss.hasPermission('order:express:export')\")\n    public void exportExpressExcel(@Valid ExpressExportReqVO exportReqVO,\n              HttpServletResponse response) throws IOException {\n        List<ExpressDO> list = expressService.getExpressList(exportReqVO);\n        // 导出 Excel\n        List<ExpressExcelVO> datas = ExpressConvert.INSTANCE.convertList02(list);\n        ExcelUtils.write(response, \"快递公司.xls\", \"数据\", ExpressExcelVO.class, datas);\n    }\n\n    @GetMapping(\"/set\")\n    @Operation(summary = \"获得快递鸟配置\")\n    public CommonResult<KdniaoApiBaseDTO> getExpressSet() {\n        return success(expressRedisDAO.get());\n    }\n\n    @PostMapping(\"/set\")\n    @Operation(summary = \"快递鸟配置\")\n    public CommonResult<Boolean> postExpressSet(@RequestBody KdniaoApiBaseDTO kdniaoApiBaseDTO) {\n        expressRedisDAO.set(kdniaoApiBaseDTO);\n        return success(true);\n    }\n\n    @GetMapping(\"/getLogistic\")\n    @Parameters({\n            @Parameter(name = \"shipperCode\", description = \"快递公司编码\", required = true),\n            @Parameter(name = \"logisticCode\", description = \"快递单号\", required = true),\n    })\n    @Operation(summary = \"查询物流\")\n    public CommonResult<KdniaoApiVO> getLogistic(@RequestParam(value = \"shipperCode\") String shipperCode,\n                                                 @RequestParam(\"logisticCode\") String logisticCode) {\n        KdniaoApiBaseDTO kdniaoApiBaseDTO = expressRedisDAO.get();\n        KdniaoApiDTO params = new KdniaoApiDTO();\n        params.setLogisticCode(logisticCode);\n        params.setShipperCode(shipperCode);\n        params.setApiKey(kdniaoApiBaseDTO.getApiKey());\n        params.setEBusinessID(kdniaoApiBaseDTO.getEBusinessID());\n        params.setIsFree(kdniaoApiBaseDTO.getIsFree());\n        //此处注意 这个地方分收费与免费当 目前用当免费免费当只支持圆通 申通 百世 如果是收费当 改里面RequestType参数 函数里有说明\n        return success(KdniaoUtil.getLogisticInfo(params));\n    }\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-express/yshop-module-express-biz/src/main/java/co/yixiang/yshop/module/express/controller/admin/express/vo/ExpressBaseVO.java",
    "content": "package co.yixiang.yshop.module.express.controller.admin.express.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotNull;\n\n/**\n* 快递公司 Base VO，提供给添加、修改、详细的子 VO 使用\n* 如果子 VO 存在差异的字段，请不要添加到这里，影响 Swagger 文档生成\n*/\n@Data\npublic class ExpressBaseVO {\n\n    @Schema(description = \"快递公司简称\", required = true)\n    @NotNull(message = \"快递公司简称不能为空\")\n    private String code;\n\n    @Schema(description = \"快递公司全称\", required = true, example = \"yshop\")\n    @NotNull(message = \"快递公司全称不能为空\")\n    private String name;\n\n    @Schema(description = \"排序\", required = true)\n    @NotNull(message = \"排序不能为空\")\n    private Integer sort;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-express/yshop-module-express-biz/src/main/java/co/yixiang/yshop/module/express/controller/admin/express/vo/ExpressCreateReqVO.java",
    "content": "package co.yixiang.yshop.module.express.controller.admin.express.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\n\n@Schema(description = \"管理后台 - 快递公司创建 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class ExpressCreateReqVO extends ExpressBaseVO {\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-express/yshop-module-express-biz/src/main/java/co/yixiang/yshop/module/express/controller/admin/express/vo/ExpressExcelVO.java",
    "content": "package co.yixiang.yshop.module.express.controller.admin.express.vo;\n\nimport com.alibaba.excel.annotation.ExcelProperty;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\n\n/**\n * 快递公司 Excel VO\n *\n * @author yshop\n */\n@Data\npublic class ExpressExcelVO {\n\n    @ExcelProperty(\"快递公司id\")\n    private Integer id;\n\n    @ExcelProperty(\"快递公司简称\")\n    private String code;\n\n    @ExcelProperty(\"快递公司全称\")\n    private String name;\n\n    @ExcelProperty(\"排序\")\n    private Integer sort;\n\n    @ExcelProperty(\"添加时间\")\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-express/yshop-module-express-biz/src/main/java/co/yixiang/yshop/module/express/controller/admin/express/vo/ExpressExportReqVO.java",
    "content": "package co.yixiang.yshop.module.express.controller.admin.express.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\n@Schema(description = \"管理后台 - 快递公司 Excel 导出 Request VO，参数和 ExpressPageReqVO 是一致的\")\n@Data\npublic class ExpressExportReqVO {\n\n    @Schema(description = \"快递公司简称\")\n    private String code;\n\n    @Schema(description = \"快递公司全称\", example = \"yshop\")\n    private String name;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-express/yshop-module-express-biz/src/main/java/co/yixiang/yshop/module/express/controller/admin/express/vo/ExpressPageReqVO.java",
    "content": "package co.yixiang.yshop.module.express.controller.admin.express.vo;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\n\n@Schema(description = \"管理后台 - 快递公司分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class ExpressPageReqVO extends PageParam {\n\n    @Schema(description = \"快递公司简称\")\n    private String code;\n\n    @Schema(description = \"快递公司全称\", example = \"yshop\")\n    private String name;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-express/yshop-module-express-biz/src/main/java/co/yixiang/yshop/module/express/controller/admin/express/vo/ExpressRespVO.java",
    "content": "package co.yixiang.yshop.module.express.controller.admin.express.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\n\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 快递公司 Response VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class ExpressRespVO extends ExpressBaseVO {\n\n    @Schema(description = \"快递公司id\", required = true, example = \"27172\")\n    private Integer id;\n\n    @Schema(description = \"添加时间\")\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-express/yshop-module-express-biz/src/main/java/co/yixiang/yshop/module/express/controller/admin/express/vo/ExpressUpdateReqVO.java",
    "content": "package co.yixiang.yshop.module.express.controller.admin.express.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\n\nimport jakarta.validation.constraints.NotNull;\n\n@Schema(description = \"管理后台 - 快递公司更新 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class ExpressUpdateReqVO extends ExpressBaseVO {\n\n    @Schema(description = \"快递公司id\", required = true, example = \"27172\")\n    @NotNull(message = \"快递公司id不能为空\")\n    private Integer id;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-express/yshop-module-express-biz/src/main/java/co/yixiang/yshop/module/express/controller/app/express/AppExpressController.java",
    "content": "package co.yixiang.yshop.module.express.controller.app.express;\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.module.express.dal.redis.express.ExpressRedisDAO;\nimport co.yixiang.yshop.module.express.kdniao.model.dto.KdniaoApiBaseDTO;\nimport co.yixiang.yshop.module.express.kdniao.model.dto.KdniaoApiDTO;\nimport co.yixiang.yshop.module.express.kdniao.model.vo.KdniaoApiVO;\nimport co.yixiang.yshop.module.express.kdniao.util.KdniaoUtil;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.Parameters;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport jakarta.annotation.Resource;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"app - 查询快递\")\n@RestController\n@RequestMapping(\"/express\")\n@Validated\npublic class AppExpressController {\n\n    @Resource\n    private ExpressRedisDAO expressRedisDAO;\n\n\n\n    @GetMapping(\"/getLogistic\")\n    @Parameters({\n            @Parameter(name = \"shipperCode\", description = \"快递公司编码\", required = true),\n            @Parameter(name = \"logisticCode\", description = \"快递单号\", required = true),\n    })\n    @Operation(summary = \"查询物流\")\n    public CommonResult<KdniaoApiVO> getLogistic(@RequestParam(value = \"shipperCode\") String shipperCode,\n                                                 @RequestParam(\"logisticCode\") String logisticCode) {\n        KdniaoApiBaseDTO kdniaoApiBaseDTO = expressRedisDAO.get();\n        KdniaoApiDTO params = new KdniaoApiDTO();\n        params.setLogisticCode(logisticCode);\n        params.setShipperCode(shipperCode);\n        params.setApiKey(kdniaoApiBaseDTO.getApiKey());\n        params.setEBusinessID(kdniaoApiBaseDTO.getEBusinessID());\n        params.setIsFree(kdniaoApiBaseDTO.getIsFree());\n        //此处注意 这个地方分收费与免费当 目前用当免费免费当只支持圆通 申通 百世 如果是收费当 改里面RequestType参数 函数里有说明\n        return success(KdniaoUtil.getLogisticInfo(params));\n    }\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-express/yshop-module-express-biz/src/main/java/co/yixiang/yshop/module/express/convert/express/ExpressConvert.java",
    "content": "package co.yixiang.yshop.module.express.convert.express;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.express.controller.admin.express.vo.ExpressCreateReqVO;\nimport co.yixiang.yshop.module.express.controller.admin.express.vo.ExpressExcelVO;\nimport co.yixiang.yshop.module.express.controller.admin.express.vo.ExpressRespVO;\nimport co.yixiang.yshop.module.express.controller.admin.express.vo.ExpressUpdateReqVO;\nimport co.yixiang.yshop.module.express.dal.dataobject.express.ExpressDO;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.factory.Mappers;\n\nimport java.util.List;\n\n/**\n * 快递公司 Convert\n *\n * @author yshop\n */\n@Mapper\npublic interface ExpressConvert {\n\n    ExpressConvert INSTANCE = Mappers.getMapper(ExpressConvert.class);\n\n    ExpressDO convert(ExpressCreateReqVO bean);\n\n    ExpressDO convert(ExpressUpdateReqVO bean);\n\n    ExpressRespVO convert(ExpressDO bean);\n\n    List<ExpressRespVO> convertList(List<ExpressDO> list);\n\n    PageResult<ExpressRespVO> convertPage(PageResult<ExpressDO> page);\n\n    List<ExpressExcelVO> convertList02(List<ExpressDO> list);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-express/yshop-module-express-biz/src/main/java/co/yixiang/yshop/module/express/dal/dataobject/express/ExpressDO.java",
    "content": "package co.yixiang.yshop.module.express.dal.dataobject.express;\n\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.*;\n\n/**\n * 快递公司 DO\n *\n * @author yshop\n */\n@TableName(\"yshop_express\")\n@KeySequence(\"yshop_express_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class ExpressDO extends BaseDO {\n\n    /**\n     * 快递公司id\n     */\n    @TableId\n    private Integer id;\n    /**\n     * 快递公司简称\n     */\n    private String code;\n    /**\n     * 快递公司全称\n     */\n    private String name;\n    /**\n     * 排序\n     */\n    private Integer sort;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-express/yshop-module-express-biz/src/main/java/co/yixiang/yshop/module/express/dal/mysql/express/ExpressMapper.java",
    "content": "package co.yixiang.yshop.module.express.dal.mysql.express;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.express.controller.admin.express.vo.ExpressExportReqVO;\nimport co.yixiang.yshop.module.express.controller.admin.express.vo.ExpressPageReqVO;\nimport co.yixiang.yshop.module.express.dal.dataobject.express.ExpressDO;\nimport org.apache.ibatis.annotations.Mapper;\n\nimport java.util.List;\n\n/**\n * 快递公司 Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface ExpressMapper extends BaseMapperX<ExpressDO> {\n\n    default PageResult<ExpressDO> selectPage(ExpressPageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<ExpressDO>()\n                .eqIfPresent(ExpressDO::getCode, reqVO.getCode())\n                .likeIfPresent(ExpressDO::getName, reqVO.getName())\n                .orderByDesc(ExpressDO::getId));\n    }\n\n    default List<ExpressDO> selectList(ExpressExportReqVO reqVO) {\n        return selectList(new LambdaQueryWrapperX<ExpressDO>()\n                .eqIfPresent(ExpressDO::getCode, reqVO.getCode())\n                .likeIfPresent(ExpressDO::getName, reqVO.getName())\n                .orderByDesc(ExpressDO::getId));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-express/yshop-module-express-biz/src/main/java/co/yixiang/yshop/module/express/dal/redis/RedisKeyConstants.java",
    "content": "package co.yixiang.yshop.module.express.dal.redis;\n\nimport co.yixiang.yshop.framework.redis.core.RedisKeyDefine;\nimport co.yixiang.yshop.module.express.kdniao.model.dto.KdniaoApiBaseDTO;\n\nimport static co.yixiang.yshop.framework.redis.core.RedisKeyDefine.KeyTypeEnum.STRING;\n\n/**\n * System Redis Key 枚举类\n *\n * @author yshop\n */\npublic interface RedisKeyConstants {\n\n\n    RedisKeyDefine YSHOP_EXPRESS_CACHE_KEY = new RedisKeyDefine(\"快递鸟配置\",\n            \"yshop_express_cache:\", //\n            STRING, KdniaoApiBaseDTO.class, RedisKeyDefine.TimeoutTypeEnum.FOREVER);\n\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-express/yshop-module-express-biz/src/main/java/co/yixiang/yshop/module/express/dal/redis/express/ExpressRedisDAO.java",
    "content": "package co.yixiang.yshop.module.express.dal.redis.express;\n\nimport co.yixiang.yshop.framework.common.util.json.JsonUtils;\nimport co.yixiang.yshop.framework.tenant.core.context.TenantContextHolder;\nimport co.yixiang.yshop.module.express.kdniao.model.dto.KdniaoApiBaseDTO;\nimport org.springframework.data.redis.core.StringRedisTemplate;\nimport org.springframework.stereotype.Repository;\n\nimport jakarta.annotation.Resource;\n\nimport static co.yixiang.yshop.module.express.dal.redis.RedisKeyConstants.YSHOP_EXPRESS_CACHE_KEY;\n\n\n/**\n * {@link KdniaoApiBaseDTO} 的 RedisDAO\n *\n * @author yshop\n */\n@Repository\npublic class ExpressRedisDAO {\n\n    @Resource\n    private StringRedisTemplate stringRedisTemplate;\n\n\n    public KdniaoApiBaseDTO get() {\n        String redisKey = formatKey();\n        return JsonUtils.parseObject(stringRedisTemplate.opsForValue().get(redisKey), KdniaoApiBaseDTO.class);\n    }\n\n    public void set(KdniaoApiBaseDTO apiBaseDTO) {\n        String redisKey = formatKey();\n        stringRedisTemplate.opsForValue().set(redisKey, JsonUtils.toJsonString(apiBaseDTO));\n    }\n\n    public void delete() {\n        String redisKey = formatKey();\n        stringRedisTemplate.delete(redisKey);\n    }\n\n\n\n    private static String formatKey() {\n        return String.format(YSHOP_EXPRESS_CACHE_KEY.getKeyTemplate());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-express/yshop-module-express-biz/src/main/java/co/yixiang/yshop/module/express/service/express/ExpressService.java",
    "content": "package co.yixiang.yshop.module.express.service.express;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.express.controller.admin.express.vo.ExpressCreateReqVO;\nimport co.yixiang.yshop.module.express.controller.admin.express.vo.ExpressExportReqVO;\nimport co.yixiang.yshop.module.express.controller.admin.express.vo.ExpressPageReqVO;\nimport co.yixiang.yshop.module.express.controller.admin.express.vo.ExpressUpdateReqVO;\nimport co.yixiang.yshop.module.express.dal.dataobject.express.ExpressDO;\n\n\nimport jakarta.validation.Valid;\nimport java.util.List;\n\n/**\n * 快递公司 Service 接口\n *\n * @author yshop\n */\npublic interface ExpressService {\n\n    /**\n     * 创建快递公司\n     *\n     * @param createReqVO 创建信息\n     * @return 编号\n     */\n    Integer createExpress(@Valid ExpressCreateReqVO createReqVO);\n\n    /**\n     * 更新快递公司\n     *\n     * @param updateReqVO 更新信息\n     */\n    void updateExpress(@Valid ExpressUpdateReqVO updateReqVO);\n\n    /**\n     * 删除快递公司\n     *\n     * @param id 编号\n     */\n    void deleteExpress(Integer id);\n\n    /**\n     * 获得快递公司\n     *\n     * @param id 编号\n     * @return 快递公司\n     */\n    ExpressDO getExpress(Integer id);\n\n    /**\n     * 获得快递公司列表\n     *\n     * @return 快递公司列表\n     */\n    List<ExpressDO> getExpressList();\n\n    /**\n     * 获得快递公司分页\n     *\n     * @param pageReqVO 分页查询\n     * @return 快递公司分页\n     */\n    PageResult<ExpressDO> getExpressPage(ExpressPageReqVO pageReqVO);\n\n    /**\n     * 获得快递公司列表, 用于 Excel 导出\n     *\n     * @param exportReqVO 查询条件\n     * @return 快递公司列表\n     */\n    List<ExpressDO> getExpressList(ExpressExportReqVO exportReqVO);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-express/yshop-module-express-biz/src/main/java/co/yixiang/yshop/module/express/service/express/ExpressServiceImpl.java",
    "content": "package co.yixiang.yshop.module.express.service.express;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.express.controller.admin.express.vo.ExpressCreateReqVO;\nimport co.yixiang.yshop.module.express.controller.admin.express.vo.ExpressExportReqVO;\nimport co.yixiang.yshop.module.express.controller.admin.express.vo.ExpressPageReqVO;\nimport co.yixiang.yshop.module.express.controller.admin.express.vo.ExpressUpdateReqVO;\nimport co.yixiang.yshop.module.express.convert.express.ExpressConvert;\nimport co.yixiang.yshop.module.express.dal.dataobject.express.ExpressDO;\nimport co.yixiang.yshop.module.express.dal.mysql.express.ExpressMapper;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.express.enums.ErrorCodeConstants.EXPRESS_NOT_EXISTS;\n\n\n/**\n * 快递公司 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class ExpressServiceImpl implements ExpressService {\n\n    @Resource\n    private ExpressMapper expressMapper;\n\n    @Override\n    public Integer createExpress(ExpressCreateReqVO createReqVO) {\n        // 插入\n        ExpressDO express = ExpressConvert.INSTANCE.convert(createReqVO);\n        expressMapper.insert(express);\n        // 返回\n        return express.getId();\n    }\n\n    @Override\n    public void updateExpress(ExpressUpdateReqVO updateReqVO) {\n        // 校验存在\n        validateExpressExists(updateReqVO.getId());\n        // 更新\n        ExpressDO updateObj = ExpressConvert.INSTANCE.convert(updateReqVO);\n        expressMapper.updateById(updateObj);\n    }\n\n    @Override\n    public void deleteExpress(Integer id) {\n        // 校验存在\n        validateExpressExists(id);\n        // 删除\n        expressMapper.deleteById(id);\n    }\n\n    private void validateExpressExists(Integer id) {\n        if (expressMapper.selectById(id) == null) {\n            throw exception(EXPRESS_NOT_EXISTS);\n        }\n    }\n\n    @Override\n    public ExpressDO getExpress(Integer id) {\n        return expressMapper.selectById(id);\n    }\n\n    @Override\n    public List<ExpressDO> getExpressList() {\n        return expressMapper.selectList();\n    }\n\n    @Override\n    public PageResult<ExpressDO> getExpressPage(ExpressPageReqVO pageReqVO) {\n        return expressMapper.selectPage(pageReqVO);\n    }\n\n    @Override\n    public List<ExpressDO> getExpressList(ExpressExportReqVO exportReqVO) {\n        return expressMapper.selectList(exportReqVO);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>co.yixiang.boot</groupId>\n        <artifactId>yshop</artifactId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <modules>\n        <module>yshop-module-infra-api</module>\n        <module>yshop-module-infra-biz</module>\n    </modules>\n    <artifactId>yshop-module-infra</artifactId>\n    <packaging>pom</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>\n        infra 模块，主要提供两块能力：\n        1. 我们放基础设施的运维与管理，支撑上层的通用与核心业务。 例如说：定时任务的管理、服务器的信息等等\n        2. 研发工具，提升研发效率与质量。 例如说：代码生成器、接口文档等等\n    </description>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-api/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>co.yixiang.boot</groupId>\n        <artifactId>yshop-module-infra</artifactId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>yshop-module-infra-api</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>\n        infra 模块 API，暴露给其它模块调用\n    </description>\n\n    <dependencies>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-common</artifactId>\n        </dependency>\n\n        <!-- 参数校验 -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-validation</artifactId>\n            <optional>true</optional>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-api/src/main/java/co/yixiang/yshop/module/infra/api/file/FileApi.java",
    "content": "package co.yixiang.yshop.module.infra.api.file;\n\n/**\n * 文件 API 接口\n *\n * @author yshop\n */\npublic interface FileApi {\n\n    /**\n     * 保存文件，并返回文件的访问路径\n     *\n     * @param content 文件内容\n     * @return 文件路径\n     */\n    default String createFile(byte[] content) {\n        return createFile(null, null, content);\n    }\n\n    /**\n     * 保存文件，并返回文件的访问路径\n     *\n     * @param path 文件路径\n     * @param content 文件内容\n     * @return 文件路径\n     */\n    default String createFile(String path, byte[] content) {\n        return createFile(null, path, content);\n    }\n\n    /**\n     * 保存文件，并返回文件的访问路径\n     *\n     * @param name 文件名称\n     * @param path 文件路径\n     * @param content 文件内容\n     * @return 文件路径\n     */\n    String createFile(String name, String path, byte[] content);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-api/src/main/java/co/yixiang/yshop/module/infra/api/logger/ApiAccessLogApi.java",
    "content": "package co.yixiang.yshop.module.infra.api.logger;\n\nimport co.yixiang.yshop.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO;\n\nimport jakarta.validation.Valid;\n\n/**\n * API 访问日志的 API 接口\n *\n * @author yshop\n */\npublic interface ApiAccessLogApi {\n\n    /**\n     * 创建 API 访问日志\n     *\n     * @param createDTO 创建信息\n     */\n    void createApiAccessLog(@Valid ApiAccessLogCreateReqDTO createDTO);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-api/src/main/java/co/yixiang/yshop/module/infra/api/logger/ApiErrorLogApi.java",
    "content": "package co.yixiang.yshop.module.infra.api.logger;\n\nimport co.yixiang.yshop.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;\n\nimport jakarta.validation.Valid;\n\n/**\n * API 错误日志的 API 接口\n *\n * @author yshop\n */\npublic interface ApiErrorLogApi {\n\n    /**\n     * 创建 API 错误日志\n     *\n     * @param createDTO 创建信息\n     */\n    void createApiErrorLog(@Valid ApiErrorLogCreateReqDTO createDTO);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-api/src/main/java/co/yixiang/yshop/module/infra/api/logger/dto/ApiAccessLogCreateReqDTO.java",
    "content": "package co.yixiang.yshop.module.infra.api.logger.dto;\n\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotNull;\nimport java.time.LocalDateTime;\n\n/**\n * API 访问日志\n *\n * @author yshop\n */\n@Data\npublic class ApiAccessLogCreateReqDTO {\n\n    /**\n     * 链路追踪编号\n     */\n    private String traceId;\n    /**\n     * 用户编号\n     */\n    private Long userId;\n    /**\n     * 用户类型\n     */\n    private Integer userType;\n    /**\n     * 应用名\n     */\n    @NotNull(message = \"应用名不能为空\")\n    private String applicationName;\n\n    /**\n     * 请求方法名\n     */\n    @NotNull(message = \"http 请求方法不能为空\")\n    private String requestMethod;\n    /**\n     * 访问地址\n     */\n    @NotNull(message = \"访问地址不能为空\")\n    private String requestUrl;\n    /**\n     * 请求参数\n     */\n    private String requestParams;\n    /**\n     * 响应结果\n     */\n    private String responseBody;\n    /**\n     * 用户 IP\n     */\n    @NotNull(message = \"ip 不能为空\")\n    private String userIp;\n    /**\n     * 浏览器 UA\n     */\n    @NotNull(message = \"User-Agent 不能为空\")\n    private String userAgent;\n\n    /**\n     * 操作模块\n     */\n    private String operateModule;\n    /**\n     * 操作名\n     */\n    private String operateName;\n    /**\n     * 操作分类\n     *\n     * 枚举，参见 OperateTypeEnum 类\n     */\n    private Integer operateType;\n\n    /**\n     * 开始请求时间\n     */\n    @NotNull(message = \"开始请求时间不能为空\")\n    private LocalDateTime beginTime;\n    /**\n     * 结束请求时间\n     */\n    @NotNull(message = \"结束请求时间不能为空\")\n    private LocalDateTime endTime;\n    /**\n     * 执行时长，单位：毫秒\n     */\n    @NotNull(message = \"执行时长不能为空\")\n    private Integer duration;\n    /**\n     * 结果码\n     */\n    @NotNull(message = \"错误码不能为空\")\n    private Integer resultCode;\n    /**\n     * 结果提示\n     */\n    private String resultMsg;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-api/src/main/java/co/yixiang/yshop/module/infra/api/logger/dto/ApiErrorLogCreateReqDTO.java",
    "content": "package co.yixiang.yshop.module.infra.api.logger.dto;\n\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotNull;\nimport java.time.LocalDateTime;\n\n/**\n * API 错误日志\n *\n * @author yshop\n */\n@Data\npublic class ApiErrorLogCreateReqDTO {\n\n    /**\n     * 链路编号\n     */\n    private String traceId;\n    /**\n     * 账号编号\n     */\n    private Long userId;\n    /**\n     * 用户类型\n     */\n    private Integer userType;\n    /**\n     * 应用名\n     */\n    @NotNull(message = \"应用名不能为空\")\n    private String applicationName;\n\n    /**\n     * 请求方法名\n     */\n    @NotNull(message = \"http 请求方法不能为空\")\n    private String requestMethod;\n    /**\n     * 访问地址\n     */\n    @NotNull(message = \"访问地址不能为空\")\n    private String requestUrl;\n    /**\n     * 请求参数\n     */\n    @NotNull(message = \"请求参数不能为空\")\n    private String requestParams;\n    /**\n     * 用户 IP\n     */\n    @NotNull(message = \"ip 不能为空\")\n    private String userIp;\n    /**\n     * 浏览器 UA\n     */\n    @NotNull(message = \"User-Agent 不能为空\")\n    private String userAgent;\n\n    /**\n     * 异常时间\n     */\n    @NotNull(message = \"异常时间不能为空\")\n    private LocalDateTime exceptionTime;\n    /**\n     * 异常名\n     */\n    @NotNull(message = \"异常名不能为空\")\n    private String exceptionName;\n    /**\n     * 异常发生的类全名\n     */\n    @NotNull(message = \"异常发生的类全名不能为空\")\n    private String exceptionClassName;\n    /**\n     * 异常发生的类文件\n     */\n    @NotNull(message = \"异常发生的类文件不能为空\")\n    private String exceptionFileName;\n    /**\n     * 异常发生的方法名\n     */\n    @NotNull(message = \"异常发生的方法名不能为空\")\n    private String exceptionMethodName;\n    /**\n     * 异常发生的方法所在行\n     */\n    @NotNull(message = \"异常发生的方法所在行不能为空\")\n    private Integer exceptionLineNumber;\n    /**\n     * 异常的栈轨迹异常的栈轨迹\n     */\n    @NotNull(message = \"异常的栈轨迹不能为空\")\n    private String exceptionStackTrace;\n    /**\n     * 异常导致的根消息\n     */\n    @NotNull(message = \"异常导致的根消息不能为空\")\n    private String exceptionRootCauseMessage;\n    /**\n     * 异常导致的消息\n     */\n    @NotNull(message = \"异常导致的消息不能为空\")\n    private String exceptionMessage;\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-api/src/main/java/co/yixiang/yshop/module/infra/api/websocket/WebSocketSenderApi.java",
    "content": "package co.yixiang.yshop.module.infra.api.websocket;\n\nimport co.yixiang.yshop.framework.common.util.json.JsonUtils;\n\n/**\n * WebSocket 发送器的 API 接口\n *\n * 对 WebSocketMessageSender 进行封装，提供给其它模块使用\n *\n * @author yshop\n */\npublic interface WebSocketSenderApi {\n\n    /**\n     * 发送消息给指定用户\n     *\n     * @param userType 用户类型\n     * @param userId 用户编号\n     * @param messageType 消息类型\n     * @param messageContent 消息内容，JSON 格式\n     */\n    void send(Integer userType, Long userId, String messageType, String messageContent);\n\n    /**\n     * 发送消息给指定用户类型\n     *\n     * @param userType 用户类型\n     * @param messageType 消息类型\n     * @param messageContent 消息内容，JSON 格式\n     */\n    void send(Integer userType, String messageType, String messageContent);\n\n    /**\n     * 发送消息给指定 Session\n     *\n     * @param sessionId Session 编号\n     * @param messageType 消息类型\n     * @param messageContent 消息内容，JSON 格式\n     */\n    void send(String sessionId, String messageType, String messageContent);\n\n    default void sendObject(Integer userType, Long userId, String messageType, Object messageContent) {\n        send(userType, userId, messageType, JsonUtils.toJsonString(messageContent));\n    }\n\n    default void sendObject(Integer userType, String messageType, Object messageContent) {\n        send(userType, messageType, JsonUtils.toJsonString(messageContent));\n    }\n\n    default void sendObject(String sessionId, String messageType, Object messageContent) {\n        send(sessionId, messageType, JsonUtils.toJsonString(messageContent));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-api/src/main/java/co/yixiang/yshop/module/infra/enums/DictTypeConstants.java",
    "content": "package co.yixiang.yshop.module.infra.enums;\n\n/**\n * Infra 字典类型的枚举类\n *\n * @author yshop\n */\npublic interface DictTypeConstants {\n\n    String JOB_STATUS = \"infra_job_status\"; // 定时任务状态的枚举\n    String JOB_LOG_STATUS = \"infra_job_log_status\"; // 定时任务日志状态的枚举\n\n    String API_ERROR_LOG_PROCESS_STATUS = \"infra_api_error_log_process_status\"; // API 错误日志的处理状态的枚举\n\n    String CONFIG_TYPE = \"infra_config_type\"; // 参数配置类型\n    String BOOLEAN_STRING = \"infra_boolean_string\"; // Boolean 是否类型\n\n    String OPERATE_TYPE = \"infra_operate_type\"; // 操作类型\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-api/src/main/java/co/yixiang/yshop/module/infra/enums/ErrorCodeConstants.java",
    "content": "package co.yixiang.yshop.module.infra.enums;\n\nimport co.yixiang.yshop.framework.common.exception.ErrorCode;\n\n/**\n * Infra 错误码枚举类\n *\n * infra 系统，使用 1-001-000-000 段\n */\npublic interface ErrorCodeConstants {\n\n    // ========== 参数配置 1-001-000-000 ==========\n    ErrorCode CONFIG_NOT_EXISTS = new ErrorCode(1_001_000_001, \"参数配置不存在\");\n    ErrorCode CONFIG_KEY_DUPLICATE = new ErrorCode(1_001_000_002, \"参数配置 key 重复\");\n    ErrorCode CONFIG_CAN_NOT_DELETE_SYSTEM_TYPE = new ErrorCode(1_001_000_003, \"不能删除类型为系统内置的参数配置\");\n    ErrorCode CONFIG_GET_VALUE_ERROR_IF_VISIBLE = new ErrorCode(1_001_000_004, \"获取参数配置失败，原因：不允许获取不可见配置\");\n\n    // ========== 定时任务 1-001-001-000 ==========\n    ErrorCode JOB_NOT_EXISTS = new ErrorCode(1_001_001_000, \"定时任务不存在\");\n    ErrorCode JOB_HANDLER_EXISTS = new ErrorCode(1_001_001_001, \"定时任务的处理器已经存在\");\n    ErrorCode JOB_CHANGE_STATUS_INVALID = new ErrorCode(1_001_001_002, \"只允许修改为开启或者关闭状态\");\n    ErrorCode JOB_CHANGE_STATUS_EQUALS = new ErrorCode(1_001_001_003, \"定时任务已经处于该状态，无需修改\");\n    ErrorCode JOB_UPDATE_ONLY_NORMAL_STATUS = new ErrorCode(1_001_001_004, \"只有开启状态的任务，才可以修改\");\n    ErrorCode JOB_CRON_EXPRESSION_VALID = new ErrorCode(1_001_001_005, \"CRON 表达式不正确\");\n    ErrorCode JOB_HANDLER_BEAN_NOT_EXISTS = new ErrorCode(1_001_001_006, \"定时任务的处理器 Bean 不存在\");\n    ErrorCode JOB_HANDLER_BEAN_TYPE_ERROR = new ErrorCode(1_001_001_007, \"定时任务的处理器 Bean 类型不正确，未实现 JobHandler 接口\");\n\n    // ========== API 错误日志 1-001-002-000 ==========\n    ErrorCode API_ERROR_LOG_NOT_FOUND = new ErrorCode(1_001_002_000, \"API 错误日志不存在\");\n    ErrorCode API_ERROR_LOG_PROCESSED = new ErrorCode(1_001_002_001, \"API 错误日志已处理\");\n\n    // ========= 文件相关 1-001-003-000 =================\n    ErrorCode FILE_PATH_EXISTS = new ErrorCode(1_001_003_000, \"文件路径已存在\");\n    ErrorCode FILE_NOT_EXISTS = new ErrorCode(1_001_003_001, \"文件不存在\");\n    ErrorCode FILE_IS_EMPTY = new ErrorCode(1_001_003_002, \"文件为空\");\n\n    // ========== 代码生成器 1-001-004-000 ==========\n    ErrorCode CODEGEN_TABLE_EXISTS = new ErrorCode(1_001_004_002, \"表定义已经存在\");\n    ErrorCode CODEGEN_IMPORT_TABLE_NULL = new ErrorCode(1_001_004_001, \"导入的表不存在\");\n    ErrorCode CODEGEN_IMPORT_COLUMNS_NULL = new ErrorCode(1_001_004_002, \"导入的字段不存在\");\n    ErrorCode CODEGEN_TABLE_NOT_EXISTS = new ErrorCode(1_001_004_004, \"表定义不存在\");\n    ErrorCode CODEGEN_COLUMN_NOT_EXISTS = new ErrorCode(1_001_004_005, \"字段义不存在\");\n    ErrorCode CODEGEN_SYNC_COLUMNS_NULL = new ErrorCode(1_001_004_006, \"同步的字段不存在\");\n    ErrorCode CODEGEN_SYNC_NONE_CHANGE = new ErrorCode(1_001_004_007, \"同步失败，不存在改变\");\n    ErrorCode CODEGEN_TABLE_INFO_TABLE_COMMENT_IS_NULL = new ErrorCode(1_001_004_008, \"数据库的表注释未填写\");\n    ErrorCode CODEGEN_TABLE_INFO_COLUMN_COMMENT_IS_NULL = new ErrorCode(1_001_004_009, \"数据库的表字段({})注释未填写\");\n    ErrorCode CODEGEN_MASTER_TABLE_NOT_EXISTS = new ErrorCode(1_001_004_010, \"主表(id={})定义不存在，请检查\");\n    ErrorCode CODEGEN_SUB_COLUMN_NOT_EXISTS = new ErrorCode(1_001_004_011, \"子表的字段(id={})不存在，请检查\");\n    ErrorCode CODEGEN_MASTER_GENERATION_FAIL_NO_SUB_TABLE = new ErrorCode(1_001_004_012, \"主表生成代码失败，原因：它没有子表\");\n\n    // ========== 文件配置 1-001-006-000 ==========\n    ErrorCode FILE_CONFIG_NOT_EXISTS = new ErrorCode(1_001_006_000, \"文件配置不存在\");\n    ErrorCode FILE_CONFIG_DELETE_FAIL_MASTER = new ErrorCode(1_001_006_001, \"该文件配置不允许删除，原因：它是主配置，删除会导致无法上传文件\");\n\n    // ========== 数据源配置 1-001-007-000 ==========\n    ErrorCode DATA_SOURCE_CONFIG_NOT_EXISTS = new ErrorCode(1_001_007_000, \"数据源配置不存在\");\n    ErrorCode DATA_SOURCE_CONFIG_NOT_OK = new ErrorCode(1_001_007_001, \"数据源配置不正确，无法进行连接\");\n\n    // ========== 学生 1-001-201-000 ==========\n    ErrorCode DEMO01_CONTACT_NOT_EXISTS = new ErrorCode(1_001_201_000, \"示例联系人不存在\");\n    ErrorCode DEMO02_CATEGORY_NOT_EXISTS = new ErrorCode(1_001_201_001, \"示例分类不存在\");\n    ErrorCode DEMO02_CATEGORY_EXITS_CHILDREN = new ErrorCode(1_001_201_002, \"存在存在子示例分类，无法删除\");\n    ErrorCode DEMO02_CATEGORY_PARENT_NOT_EXITS = new ErrorCode(1_001_201_003,\"父级示例分类不存在\");\n    ErrorCode DEMO02_CATEGORY_PARENT_ERROR = new ErrorCode(1_001_201_004, \"不能设置自己为父示例分类\");\n    ErrorCode DEMO02_CATEGORY_NAME_DUPLICATE = new ErrorCode(1_001_201_005, \"已经存在该名字的示例分类\");\n    ErrorCode DEMO02_CATEGORY_PARENT_IS_CHILD = new ErrorCode(1_001_201_006, \"不能设置自己的子示例分类为父示例分类\");\n    ErrorCode DEMO03_STUDENT_NOT_EXISTS = new ErrorCode(1_001_201_007, \"学生不存在\");\n    ErrorCode DEMO03_GRADE_NOT_EXISTS = new ErrorCode(1_001_201_008, \"学生班级不存在\");\n    ErrorCode DEMO03_GRADE_EXISTS = new ErrorCode(1_001_201_009, \"学生班级已存在\");\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>co.yixiang.boot</groupId>\n        <artifactId>yshop-module-infra</artifactId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>yshop-module-infra-biz</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>\n        infra 模块，主要提供两块能力：\n            1. 我们放基础设施的运维与管理，支撑上层的通用与核心业务。 例如说：定时任务的管理、服务器的信息等等\n            2. 研发工具，提升研发效率与质量。 例如说：代码生成器、接口文档等等\n    </description>\n\n    <dependencies>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-system-api</artifactId>\n            <version>${revision}</version>\n        </dependency>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-infra-api</artifactId>\n            <version>${revision}</version>\n        </dependency>\n\n        <!-- 业务组件 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-biz-tenant</artifactId>\n        </dependency>\n\n        <!-- Web 相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-security</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-websocket</artifactId>\n        </dependency>\n\n        <!-- DB 相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-mybatis</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.baomidou</groupId>\n            <artifactId>mybatis-plus-generator</artifactId> <!-- 代码生成器，使用它解析表结构 -->\n        </dependency>\n\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-redis</artifactId>\n        </dependency>\n\n        <!-- Config 配置中心相关 -->\n\n        <!-- Job 定时任务相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-job</artifactId>\n        </dependency>\n\n        <!-- 消息队列相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-mq</artifactId>\n        </dependency>\n\n        <!-- Test 测试相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n\n        <!-- 工具类相关 -->\n\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-excel</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>org.apache.velocity</groupId>\n            <artifactId>velocity-engine-core</artifactId> <!-- 实现代码生成 -->\n        </dependency>\n\n        <!-- 监控相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-monitor</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>de.codecentric</groupId>\n            <artifactId>spring-boot-admin-starter-server</artifactId> <!-- 实现 Spring Boot Admin Server 服务端 -->\n        </dependency>\n\n        <!-- 三方云服务相关 -->\n        <dependency>\n            <groupId>commons-net</groupId>\n            <artifactId>commons-net</artifactId> <!-- 文件客户端：解决 ftp 连接 -->\n        </dependency>\n        <dependency>\n            <groupId>com.jcraft</groupId>\n            <artifactId>jsch</artifactId> <!-- 文件客户端：解决 sftp 连接 -->\n        </dependency>\n        <dependency>\n            <groupId>io.minio</groupId>\n            <artifactId>minio</artifactId> <!-- 文件客户端：解决阿里云、腾讯云、minio 等 S3 连接 -->\n        </dependency>\n\n        <dependency>\n            <groupId>org.apache.tika</groupId>\n            <artifactId>tika-core</artifactId> <!-- 文件客户端：文件类型的识别 -->\n        </dependency>\n\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/api/file/FileApiImpl.java",
    "content": "package co.yixiang.yshop.module.infra.api.file;\n\nimport co.yixiang.yshop.module.infra.service.file.FileService;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\n\n/**\n * 文件 API 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class FileApiImpl implements FileApi {\n\n    @Resource\n    private FileService fileService;\n\n    @Override\n    public String createFile(String name, String path, byte[] content) {\n        return fileService.createFile(name, path, content);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/api/logger/ApiAccessLogApiImpl.java",
    "content": "package co.yixiang.yshop.module.infra.api.logger;\n\nimport co.yixiang.yshop.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO;\nimport co.yixiang.yshop.module.infra.service.logger.ApiAccessLogService;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\n\n/**\n * API 访问日志的 API 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class ApiAccessLogApiImpl implements ApiAccessLogApi {\n\n    @Resource\n    private ApiAccessLogService apiAccessLogService;\n\n    @Override\n    public void createApiAccessLog(ApiAccessLogCreateReqDTO createDTO) {\n        apiAccessLogService.createApiAccessLog(createDTO);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/api/logger/ApiErrorLogApiImpl.java",
    "content": "package co.yixiang.yshop.module.infra.api.logger;\n\nimport co.yixiang.yshop.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;\nimport co.yixiang.yshop.module.infra.service.logger.ApiErrorLogService;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\n\n/**\n * API 访问日志的 API 接口\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class ApiErrorLogApiImpl implements ApiErrorLogApi {\n\n    @Resource\n    private ApiErrorLogService apiErrorLogService;\n\n    @Override\n    public void createApiErrorLog(ApiErrorLogCreateReqDTO createDTO) {\n        apiErrorLogService.createApiErrorLog(createDTO);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/api/websocket/WebSocketSenderApiImpl.java",
    "content": "package co.yixiang.yshop.module.infra.api.websocket;\n\nimport co.yixiang.yshop.framework.websocket.core.sender.WebSocketMessageSender;\nimport org.springframework.stereotype.Component;\n\nimport jakarta.annotation.Resource;\n\n/**\n * WebSocket 发送器的 API 实现类\n *\n * @author yshop\n */\n@Component\npublic class WebSocketSenderApiImpl implements WebSocketSenderApi {\n\n    @Resource\n    private WebSocketMessageSender webSocketMessageSender;\n\n    @Override\n    public void send(Integer userType, Long userId, String messageType, String messageContent) {\n        webSocketMessageSender.send(userType, userId, messageType, messageContent);\n    }\n\n    @Override\n    public void send(Integer userType, String messageType, String messageContent) {\n        webSocketMessageSender.send(userType, messageType, messageContent);\n    }\n\n    @Override\n    public void send(String sessionId, String messageType, String messageContent) {\n        webSocketMessageSender.send(sessionId, messageType, messageContent);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/codegen/CodegenController.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.codegen;\n\nimport cn.hutool.core.io.IoUtil;\nimport cn.hutool.core.util.ZipUtil;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.infra.controller.admin.codegen.vo.CodegenCreateListReqVO;\nimport co.yixiang.yshop.module.infra.controller.admin.codegen.vo.CodegenDetailRespVO;\nimport co.yixiang.yshop.module.infra.controller.admin.codegen.vo.CodegenPreviewRespVO;\nimport co.yixiang.yshop.module.infra.controller.admin.codegen.vo.CodegenUpdateReqVO;\nimport co.yixiang.yshop.module.infra.controller.admin.codegen.vo.table.CodegenTablePageReqVO;\nimport co.yixiang.yshop.module.infra.controller.admin.codegen.vo.table.CodegenTableRespVO;\nimport co.yixiang.yshop.module.infra.controller.admin.codegen.vo.table.DatabaseTableRespVO;\nimport co.yixiang.yshop.module.infra.convert.codegen.CodegenConvert;\nimport co.yixiang.yshop.module.infra.dal.dataobject.codegen.CodegenColumnDO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.codegen.CodegenTableDO;\nimport co.yixiang.yshop.module.infra.service.codegen.CodegenService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.Parameters;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.annotation.Resource;\nimport jakarta.servlet.http.HttpServletResponse;\nimport jakarta.validation.Valid;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Map;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\nimport static co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;\nimport static co.yixiang.yshop.module.infra.framework.file.core.utils.FileTypeUtils.writeAttachment;\n\n@Tag(name = \"管理后台 - 代码生成器\")\n@RestController\n@RequestMapping(\"/infra/codegen\")\n@Validated\npublic class CodegenController {\n\n    @Resource\n    private CodegenService codegenService;\n\n    @GetMapping(\"/db/table/list\")\n    @Operation(summary = \"获得数据库自带的表定义列表\", description = \"会过滤掉已经导入 Codegen 的表\")\n    @Parameters({\n            @Parameter(name = \"dataSourceConfigId\", description = \"数据源配置的编号\", required = true, example = \"1\"),\n            @Parameter(name = \"name\", description = \"表名，模糊匹配\", example = \"yshop\"),\n            @Parameter(name = \"comment\", description = \"描述，模糊匹配\", example = \"yshop\")\n    })\n    @PreAuthorize(\"@ss.hasPermission('infra:codegen:query')\")\n    public CommonResult<List<DatabaseTableRespVO>> getDatabaseTableList(\n            @RequestParam(value = \"dataSourceConfigId\") Long dataSourceConfigId,\n            @RequestParam(value = \"name\", required = false) String name,\n            @RequestParam(value = \"comment\", required = false) String comment) {\n        return success(codegenService.getDatabaseTableList(dataSourceConfigId, name, comment));\n    }\n\n    @GetMapping(\"/table/list\")\n    @Operation(summary = \"获得表定义列表\")\n    @Parameter(name = \"dataSourceConfigId\", description = \"数据源配置的编号\", required = true, example = \"1\")\n    @PreAuthorize(\"@ss.hasPermission('infra:codegen:query')\")\n    public CommonResult<List<CodegenTableRespVO>> getCodegenTableList(@RequestParam(value = \"dataSourceConfigId\") Long dataSourceConfigId) {\n        List<CodegenTableDO> list = codegenService.getCodegenTableList(dataSourceConfigId);\n        return success(BeanUtils.toBean(list, CodegenTableRespVO.class));\n    }\n\n    @GetMapping(\"/table/page\")\n    @Operation(summary = \"获得表定义分页\")\n    @PreAuthorize(\"@ss.hasPermission('infra:codegen:query')\")\n    public CommonResult<PageResult<CodegenTableRespVO>> getCodegenTablePage(@Valid CodegenTablePageReqVO pageReqVO) {\n        PageResult<CodegenTableDO> pageResult = codegenService.getCodegenTablePage(pageReqVO);\n        return success(BeanUtils.toBean(pageResult, CodegenTableRespVO.class));\n    }\n\n    @GetMapping(\"/detail\")\n    @Operation(summary = \"获得表和字段的明细\")\n    @Parameter(name = \"tableId\", description = \"表编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('infra:codegen:query')\")\n    public CommonResult<CodegenDetailRespVO> getCodegenDetail(@RequestParam(\"tableId\") Long tableId) {\n        CodegenTableDO table = codegenService.getCodegenTable(tableId);\n        List<CodegenColumnDO> columns = codegenService.getCodegenColumnListByTableId(tableId);\n        // 拼装返回\n        return success(CodegenConvert.INSTANCE.convert(table, columns));\n    }\n\n    @Operation(summary = \"基于数据库的表结构，创建代码生成器的表和字段定义\")\n    @PostMapping(\"/create-list\")\n    @PreAuthorize(\"@ss.hasPermission('infra:codegen:create')\")\n    public CommonResult<List<Long>> createCodegenList(@Valid @RequestBody CodegenCreateListReqVO reqVO) {\n        return success(codegenService.createCodegenList(getLoginUserId(), reqVO));\n    }\n\n    @Operation(summary = \"更新数据库的表和字段定义\")\n    @PutMapping(\"/update\")\n    @PreAuthorize(\"@ss.hasPermission('infra:codegen:update')\")\n    public CommonResult<Boolean> updateCodegen(@Valid @RequestBody CodegenUpdateReqVO updateReqVO) {\n        codegenService.updateCodegen(updateReqVO);\n        return success(true);\n    }\n\n    @Operation(summary = \"基于数据库的表结构，同步数据库的表和字段定义\")\n    @PutMapping(\"/sync-from-db\")\n    @Parameter(name = \"tableId\", description = \"表编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('infra:codegen:update')\")\n    public CommonResult<Boolean> syncCodegenFromDB(@RequestParam(\"tableId\") Long tableId) {\n        codegenService.syncCodegenFromDB(tableId);\n        return success(true);\n    }\n\n    @Operation(summary = \"删除数据库的表和字段定义\")\n    @DeleteMapping(\"/delete\")\n    @Parameter(name = \"tableId\", description = \"表编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('infra:codegen:delete')\")\n    public CommonResult<Boolean> deleteCodegen(@RequestParam(\"tableId\") Long tableId) {\n        codegenService.deleteCodegen(tableId);\n        return success(true);\n    }\n\n    @Operation(summary = \"预览生成代码\")\n    @GetMapping(\"/preview\")\n    @Parameter(name = \"tableId\", description = \"表编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('infra:codegen:preview')\")\n    public CommonResult<List<CodegenPreviewRespVO>> previewCodegen(@RequestParam(\"tableId\") Long tableId) {\n        Map<String, String> codes = codegenService.generationCodes(tableId);\n        return success(CodegenConvert.INSTANCE.convert(codes));\n    }\n\n    @Operation(summary = \"下载生成代码\")\n    @GetMapping(\"/download\")\n    @Parameter(name = \"tableId\", description = \"表编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('infra:codegen:download')\")\n    public void downloadCodegen(@RequestParam(\"tableId\") Long tableId,\n                                HttpServletResponse response) throws IOException {\n        // 生成代码\n        Map<String, String> codes = codegenService.generationCodes(tableId);\n        // 构建 zip 包\n        String[] paths = codes.keySet().toArray(new String[0]);\n        ByteArrayInputStream[] ins = codes.values().stream().map(IoUtil::toUtf8Stream).toArray(ByteArrayInputStream[]::new);\n        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();\n        ZipUtil.zip(outputStream, paths, ins);\n        // 输出\n        writeAttachment(response, \"codegen.zip\", outputStream.toByteArray());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/codegen/vo/CodegenCreateListReqVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.codegen.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotNull;\nimport java.util.List;\n\n@Schema(description = \"管理后台 - 基于数据库的表结构，创建代码生成器的表和字段定义 Request VO\")\n@Data\npublic class CodegenCreateListReqVO {\n\n    @Schema(description = \"数据源配置的编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @NotNull(message = \"数据源配置的编号不能为空\")\n    private Long dataSourceConfigId;\n\n    @Schema(description = \"表名数组\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"[1, 2, 3]\")\n    @NotNull(message = \"表名数组不能为空\")\n    private List<String> tableNames;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/codegen/vo/CodegenDetailRespVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.codegen.vo;\n\nimport co.yixiang.yshop.module.infra.controller.admin.codegen.vo.column.CodegenColumnRespVO;\nimport co.yixiang.yshop.module.infra.controller.admin.codegen.vo.table.CodegenTableRespVO;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.util.List;\n\n@Schema(description = \"管理后台 - 代码生成表和字段的明细 Response VO\")\n@Data\npublic class CodegenDetailRespVO {\n\n    @Schema(description = \"表定义\")\n    private CodegenTableRespVO table;\n\n    @Schema(description = \"字段定义\")\n    private List<CodegenColumnRespVO> columns;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/codegen/vo/CodegenPreviewRespVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.codegen.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\n@Schema(description = \"管理后台 - 代码生成预览 Response VO，注意，每个文件都是一个该对象\")\n@Data\npublic class CodegenPreviewRespVO {\n\n    @Schema(description = \"文件路径\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"java/co.yixiang.yshop/adminserver/modules/system/controller/test/SysTestDemoController.java\")\n    private String filePath;\n\n    @Schema(description = \"代码\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"Hello World\")\n    private String code;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/codegen/vo/CodegenUpdateReqVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.codegen.vo;\n\nimport co.yixiang.yshop.module.infra.controller.admin.codegen.vo.column.CodegenColumnSaveReqVO;\nimport co.yixiang.yshop.module.infra.controller.admin.codegen.vo.table.CodegenTableSaveReqVO;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.Valid;\nimport jakarta.validation.constraints.NotNull;\nimport java.util.List;\n\n@Schema(description = \"管理后台 - 代码生成表和字段的修改 Request VO\")\n@Data\npublic class CodegenUpdateReqVO {\n\n    @Valid // 校验内嵌的字段\n    @NotNull(message = \"表定义不能为空\")\n    private CodegenTableSaveReqVO table;\n\n    @Valid // 校验内嵌的字段\n    @NotNull(message = \"字段定义不能为空\")\n    private List<CodegenColumnSaveReqVO> columns;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/codegen/vo/column/CodegenColumnRespVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.codegen.vo.column;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 代码生成字段定义 Response VO\")\n@Data\npublic class CodegenColumnRespVO {\n\n    @Schema(description = \"编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    private Long id;\n\n    @Schema(description = \"表编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    private Long tableId;\n\n    @Schema(description = \"字段名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"user_age\")\n    private String columnName;\n\n    @Schema(description = \"字段类型\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"int(11)\")\n    private String dataType;\n\n    @Schema(description = \"字段描述\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"年龄\")\n    private String columnComment;\n\n    @Schema(description = \"是否允许为空\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"true\")\n    private Boolean nullable;\n\n    @Schema(description = \"是否主键\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"false\")\n    private Boolean primaryKey;\n\n    @Schema(description = \"排序\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"10\")\n    private Integer ordinalPosition;\n\n    @Schema(description = \"Java 属性类型\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"userAge\")\n    private String javaType;\n\n    @Schema(description = \"Java 属性名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"Integer\")\n    private String javaField;\n\n    @Schema(description = \"字典类型\", example = \"sys_gender\")\n    private String dictType;\n\n    @Schema(description = \"数据示例\", example = \"1024\")\n    private String example;\n\n    @Schema(description = \"是否为 Create 创建操作的字段\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"true\")\n    private Boolean createOperation;\n\n    @Schema(description = \"是否为 Update 更新操作的字段\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"false\")\n    private Boolean updateOperation;\n\n    @Schema(description = \"是否为 List 查询操作的字段\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"true\")\n    private Boolean listOperation;\n\n    @Schema(description = \"List 查询操作的条件类型，参见 CodegenColumnListConditionEnum 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"LIKE\")\n    private String listOperationCondition;\n\n    @Schema(description = \"是否为 List 查询操作的返回字段\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"true\")\n    private Boolean listOperationResult;\n\n    @Schema(description = \"显示类型\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"input\")\n    private String htmlType;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/codegen/vo/column/CodegenColumnSaveReqVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.codegen.vo.column;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotNull;\n\n@Schema(description = \"管理后台 - 代码生成字段定义创建/修改 Request VO\")\n@Data\npublic class CodegenColumnSaveReqVO {\n\n    @Schema(description = \"编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    private Long id;\n\n    @Schema(description = \"表编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @NotNull(message = \"表编号不能为空\")\n    private Long tableId;\n\n    @Schema(description = \"字段名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"user_age\")\n    @NotNull(message = \"字段名不能为空\")\n    private String columnName;\n\n    @Schema(description = \"字段类型\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"int(11)\")\n    @NotNull(message = \"字段类型不能为空\")\n    private String dataType;\n\n    @Schema(description = \"字段描述\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"年龄\")\n    @NotNull(message = \"字段描述不能为空\")\n    private String columnComment;\n\n    @Schema(description = \"是否允许为空\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"true\")\n    @NotNull(message = \"是否允许为空不能为空\")\n    private Boolean nullable;\n\n    @Schema(description = \"是否主键\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"false\")\n    @NotNull(message = \"是否主键不能为空\")\n    private Boolean primaryKey;\n\n    @Schema(description = \"排序\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"10\")\n    @NotNull(message = \"排序不能为空\")\n    private Integer ordinalPosition;\n\n    @Schema(description = \"Java 属性类型\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"userAge\")\n    @NotNull(message = \"Java 属性类型不能为空\")\n    private String javaType;\n\n    @Schema(description = \"Java 属性名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"Integer\")\n    @NotNull(message = \"Java 属性名不能为空\")\n    private String javaField;\n\n    @Schema(description = \"字典类型\", example = \"sys_gender\")\n    private String dictType;\n\n    @Schema(description = \"数据示例\", example = \"1024\")\n    private String example;\n\n    @Schema(description = \"是否为 Create 创建操作的字段\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"true\")\n    @NotNull(message = \"是否为 Create 创建操作的字段不能为空\")\n    private Boolean createOperation;\n\n    @Schema(description = \"是否为 Update 更新操作的字段\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"false\")\n    @NotNull(message = \"是否为 Update 更新操作的字段不能为空\")\n    private Boolean updateOperation;\n\n    @Schema(description = \"是否为 List 查询操作的字段\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"true\")\n    @NotNull(message = \"是否为 List 查询操作的字段不能为空\")\n    private Boolean listOperation;\n\n    @Schema(description = \"List 查询操作的条件类型，参见 CodegenColumnListConditionEnum 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"LIKE\")\n    @NotNull(message = \"List 查询操作的条件类型不能为空\")\n    private String listOperationCondition;\n\n    @Schema(description = \"是否为 List 查询操作的返回字段\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"true\")\n    @NotNull(message = \"是否为 List 查询操作的返回字段不能为空\")\n    private Boolean listOperationResult;\n\n    @Schema(description = \"显示类型\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"input\")\n    @NotNull(message = \"显示类型不能为空\")\n    private String htmlType;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/codegen/vo/table/CodegenTablePageReqVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.codegen.vo.table;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 表定义分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class CodegenTablePageReqVO extends PageParam {\n\n    @Schema(description = \"表名称，模糊匹配\", example = \"yshop\")\n    private String tableName;\n\n    @Schema(description = \"表描述，模糊匹配\", example = \"yshop\")\n    private String tableComment;\n\n    @Schema(description = \"实体，模糊匹配\", example = \"Yshop\")\n    private String className;\n\n    @Schema(description = \"创建时间\", example = \"[2022-07-01 00:00:00,2022-07-01 23:59:59]\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/codegen/vo/table/CodegenTableRespVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.codegen.vo.table;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 代码生成表定义 Response VO\")\n@Data\npublic class CodegenTableRespVO {\n\n    @Schema(description = \"编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    private Long id;\n\n    @Schema(description = \"生成场景，参见 CodegenSceneEnum 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    private Integer scene;\n\n    @Schema(description = \"表名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    private String tableName;\n\n    @Schema(description = \"表描述\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    private String tableComment;\n\n    @Schema(description = \"备注\", example = \"我是备注\")\n    private String remark;\n\n    @Schema(description = \"模块名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"system\")\n    private String moduleName;\n\n    @Schema(description = \"业务名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"codegen\")\n    private String businessName;\n\n    @Schema(description = \"类名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"CodegenTable\")\n    private String className;\n\n    @Schema(description = \"类描述\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"代码生成器的表定义\")\n    private String classComment;\n\n    @Schema(description = \"作者\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    private String author;\n\n    @Schema(description = \"模板类型，参见 CodegenTemplateTypeEnum 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    private Integer templateType;\n\n    @Schema(description = \"前端类型，参见 CodegenFrontTypeEnum 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"20\")\n    private Integer frontType;\n\n    @Schema(description = \"父菜单编号\", example = \"1024\")\n    private Long parentMenuId;\n\n    @Schema(description = \"主表的编号\", example = \"2048\")\n    private Long masterTableId;\n    @Schema(description = \"子表关联主表的字段编号\", example = \"4096\")\n    private Long subJoinColumnId;\n    @Schema(description = \"主表与子表是否一对多\", example = \"4096\")\n    private Boolean subJoinMany;\n\n    @Schema(description = \"树表的父字段编号\", example = \"8192\")\n    private Long treeParentColumnId;\n    @Schema(description = \"树表的名字字段编号\", example = \"16384\")\n    private Long treeNameColumnId;\n\n    @Schema(description = \"主键编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Integer dataSourceConfigId;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private LocalDateTime createTime;\n\n    @Schema(description = \"更新时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private LocalDateTime updateTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/codegen/vo/table/CodegenTableSaveReqVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.codegen.vo.table;\n\nimport cn.hutool.core.util.ObjectUtil;\nimport co.yixiang.yshop.module.infra.enums.codegen.CodegenSceneEnum;\nimport co.yixiang.yshop.module.infra.enums.codegen.CodegenTemplateTypeEnum;\nimport com.fasterxml.jackson.annotation.JsonIgnore;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.AssertTrue;\nimport jakarta.validation.constraints.NotNull;\n\n@Schema(description = \"管理后台 - 代码生成表定义创建/修改 Response VO\")\n@Data\npublic class CodegenTableSaveReqVO {\n\n    @Schema(description = \"编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    private Long id;\n\n    @Schema(description = \"生成场景，参见 CodegenSceneEnum 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @NotNull(message = \"导入类型不能为空\")\n    private Integer scene;\n\n    @Schema(description = \"表名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    @NotNull(message = \"表名称不能为空\")\n    private String tableName;\n\n    @Schema(description = \"表描述\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    @NotNull(message = \"表描述不能为空\")\n    private String tableComment;\n\n    @Schema(description = \"备注\", example = \"我是备注\")\n    private String remark;\n\n    @Schema(description = \"模块名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"system\")\n    @NotNull(message = \"模块名不能为空\")\n    private String moduleName;\n\n    @Schema(description = \"业务名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"codegen\")\n    @NotNull(message = \"业务名不能为空\")\n    private String businessName;\n\n    @Schema(description = \"类名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"CodegenTable\")\n    @NotNull(message = \"类名称不能为空\")\n    private String className;\n\n    @Schema(description = \"类描述\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"代码生成器的表定义\")\n    @NotNull(message = \"类描述不能为空\")\n    private String classComment;\n\n    @Schema(description = \"作者\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    @NotNull(message = \"作者不能为空\")\n    private String author;\n\n    @Schema(description = \"模板类型，参见 CodegenTemplateTypeEnum 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @NotNull(message = \"模板类型不能为空\")\n    private Integer templateType;\n\n    @Schema(description = \"前端类型，参见 CodegenFrontTypeEnum 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"20\")\n    @NotNull(message = \"前端类型不能为空\")\n    private Integer frontType;\n\n    @Schema(description = \"父菜单编号\", example = \"1024\")\n    private Long parentMenuId;\n\n    @Schema(description = \"主表的编号\", example = \"2048\")\n    private Long masterTableId;\n    @Schema(description = \"子表关联主表的字段编号\", example = \"4096\")\n    private Long subJoinColumnId;\n    @Schema(description = \"主表与子表是否一对多\", example = \"4096\")\n    private Boolean subJoinMany;\n\n    @Schema(description = \"树表的父字段编号\", example = \"8192\")\n    private Long treeParentColumnId;\n    @Schema(description = \"树表的名字字段编号\", example = \"16384\")\n    private Long treeNameColumnId;\n\n    @AssertTrue(message = \"上级菜单不能为空，请前往 [修改生成配置 -> 生成信息] 界面，设置“上级菜单”字段\")\n    @JsonIgnore\n    public boolean isParentMenuIdValid() {\n        // 生成场景为管理后台时，必须设置上级菜单，不然生成的菜单 SQL 是无父级菜单的\n        return ObjectUtil.notEqual(getScene(), CodegenSceneEnum.ADMIN.getScene())\n                || getParentMenuId() != null;\n    }\n\n    @AssertTrue(message = \"关联的父表信息不全\")\n    @JsonIgnore\n    public boolean isSubValid() {\n        return ObjectUtil.notEqual(getTemplateType(), CodegenTemplateTypeEnum.SUB)\n                || (ObjectUtil.isAllNotEmpty(masterTableId, subJoinColumnId, subJoinMany));\n    }\n\n    @AssertTrue(message = \"关联的树表信息不全\")\n    @JsonIgnore\n    public boolean isTreeValid() {\n        return ObjectUtil.notEqual(templateType, CodegenTemplateTypeEnum.TREE)\n                || (ObjectUtil.isAllNotEmpty(treeParentColumnId, treeNameColumnId));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/codegen/vo/table/DatabaseTableRespVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.codegen.vo.table;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\n@Schema(description = \"管理后台 - 数据库的表定义 Response VO\")\n@Data\npublic class DatabaseTableRespVO {\n\n    @Schema(description = \"表名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yuanma\")\n    private String name;\n\n    @Schema(description = \"表描述\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    private String comment;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/config/ConfigController.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.config;\n\nimport co.yixiang.yshop.framework.apilog.core.annotation.ApiAccessLog;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.excel.core.util.ExcelUtils;\nimport co.yixiang.yshop.module.infra.controller.admin.config.vo.*;\nimport co.yixiang.yshop.module.infra.convert.config.ConfigConvert;\nimport co.yixiang.yshop.module.infra.dal.dataobject.config.ConfigDO;\nimport co.yixiang.yshop.module.infra.enums.ErrorCodeConstants;\nimport co.yixiang.yshop.module.infra.service.config.ConfigService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport jakarta.annotation.Resource;\nimport jakarta.servlet.http.HttpServletResponse;\nimport jakarta.validation.Valid;\nimport java.io.IOException;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.apilog.core.enums.OperateTypeEnum.EXPORT;\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 参数配置\")\n@RestController\n@RequestMapping(\"/infra/config\")\n@Validated\npublic class ConfigController {\n\n    @Resource\n    private ConfigService configService;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建参数配置\")\n    @PreAuthorize(\"@ss.hasPermission('infra:config:create')\")\n    public CommonResult<Long> createConfig(@Valid @RequestBody ConfigSaveReqVO createReqVO) {\n        return success(configService.createConfig(createReqVO));\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"修改参数配置\")\n    @PreAuthorize(\"@ss.hasPermission('infra:config:update')\")\n    public CommonResult<Boolean> updateConfig(@Valid @RequestBody ConfigSaveReqVO updateReqVO) {\n        configService.updateConfig(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除参数配置\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('infra:config:delete')\")\n    public CommonResult<Boolean> deleteConfig(@RequestParam(\"id\") Long id) {\n        configService.deleteConfig(id);\n        return success(true);\n    }\n\n    @GetMapping(value = \"/get\")\n    @Operation(summary = \"获得参数配置\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('infra:config:query')\")\n    public CommonResult<ConfigRespVO> getConfig(@RequestParam(\"id\") Long id) {\n        return success(ConfigConvert.INSTANCE.convert(configService.getConfig(id)));\n    }\n\n    @GetMapping(value = \"/get-value-by-key\")\n    @Operation(summary = \"根据参数键名查询参数值\", description = \"不可见的配置，不允许返回给前端\")\n    @Parameter(name = \"key\", description = \"参数键\", required = true, example = \"yshop.biz.username\")\n    public CommonResult<String> getConfigKey(@RequestParam(\"key\") String key) {\n        ConfigDO config = configService.getConfigByKey(key);\n        if (config == null) {\n            return success(null);\n        }\n        if (!config.getVisible()) {\n            throw exception(ErrorCodeConstants.CONFIG_GET_VALUE_ERROR_IF_VISIBLE);\n        }\n        return success(config.getValue());\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获取参数配置分页\")\n    @PreAuthorize(\"@ss.hasPermission('infra:config:query')\")\n    public CommonResult<PageResult<ConfigRespVO>> getConfigPage(@Valid ConfigPageReqVO pageReqVO) {\n        PageResult<ConfigDO> page = configService.getConfigPage(pageReqVO);\n        return success(ConfigConvert.INSTANCE.convertPage(page));\n    }\n\n    @GetMapping(\"/export\")\n    @Operation(summary = \"导出参数配置\")\n    @PreAuthorize(\"@ss.hasPermission('infra:config:export')\")\n    @ApiAccessLog(operateType = EXPORT)\n    public void exportConfig(@Valid ConfigPageReqVO exportReqVO,\n                             HttpServletResponse response) throws IOException {\n        exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);\n        List<ConfigDO> list = configService.getConfigPage(exportReqVO).getList();\n        // 输出\n        ExcelUtils.write(response, \"参数配置.xls\", \"数据\", ConfigRespVO.class,\n                ConfigConvert.INSTANCE.convertList(list));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/config/vo/ConfigPageReqVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.config.vo;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 参数配置分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class ConfigPageReqVO extends PageParam {\n\n    @Schema(description = \"数据源名称，模糊匹配\", example = \"名称\")\n    private String name;\n\n    @Schema(description = \"参数键名，模糊匹配\", example = \"yshop.db.username\")\n    private String key;\n\n    @Schema(description = \"参数类型，参见 SysConfigTypeEnum 枚举\", example = \"1\")\n    private Integer type;\n\n    @Schema(description = \"创建时间\", example = \"[2022-07-01 00:00:00,2022-07-01 23:59:59]\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/config/vo/ConfigRespVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.config.vo;\n\nimport co.yixiang.yshop.framework.excel.core.annotations.DictFormat;\nimport co.yixiang.yshop.framework.excel.core.convert.DictConvert;\nimport co.yixiang.yshop.module.infra.enums.DictTypeConstants;\nimport com.alibaba.excel.annotation.ExcelIgnoreUnannotated;\nimport com.alibaba.excel.annotation.ExcelProperty;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 参数配置信息 Response VO\")\n@Data\n@ExcelIgnoreUnannotated\npublic class ConfigRespVO {\n\n    @Schema(description = \"参数配置序号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @ExcelProperty(\"参数配置序号\")\n    private Long id;\n\n    @Schema(description = \"参数分类\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"biz\")\n    @ExcelProperty(\"参数分类\")\n    private String category;\n\n    @Schema(description = \"参数名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"数据库名\")\n    @ExcelProperty(\"参数名称\")\n    private String name;\n\n    @Schema(description = \"参数键名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop.db.username\")\n    @ExcelProperty(\"参数键名\")\n    private String key;\n\n    @Schema(description = \"参数键值\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @ExcelProperty(\"参数键值\")\n    private String value;\n\n    @Schema(description = \"参数类型，参见 SysConfigTypeEnum 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @ExcelProperty(value = \"参数类型\", converter = DictConvert.class)\n    @DictFormat(DictTypeConstants.CONFIG_TYPE)\n    private Integer type;\n\n    @Schema(description = \"是否可见\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"true\")\n    @ExcelProperty(value = \"是否可见\", converter = DictConvert.class)\n    @DictFormat(DictTypeConstants.BOOLEAN_STRING)\n    private Boolean visible;\n\n    @Schema(description = \"备注\", example = \"备注一下很帅气！\")\n    @ExcelProperty(\"备注\")\n    private String remark;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"时间戳格式\")\n    @ExcelProperty(\"创建时间\")\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/config/vo/ConfigSaveReqVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.config.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotBlank;\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\nimport jakarta.validation.constraints.Size;\n\n@Schema(description = \"管理后台 - 参数配置创建/修改 Request VO\")\n@Data\npublic class ConfigSaveReqVO {\n\n    @Schema(description = \"参数配置序号\", example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"参数分组\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"biz\")\n    @NotEmpty(message = \"参数分组不能为空\")\n    @Size(max = 50, message = \"参数名称不能超过 50 个字符\")\n    private String category;\n\n    @Schema(description = \"参数名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"数据库名\")\n    @NotBlank(message = \"参数名称不能为空\")\n    @Size(max = 100, message = \"参数名称不能超过 100 个字符\")\n    private String name;\n\n    @Schema(description = \"参数键名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop.db.username\")\n    @NotBlank(message = \"参数键名长度不能为空\")\n    @Size(max = 100, message = \"参数键名长度不能超过 100 个字符\")\n    private String key;\n\n    @Schema(description = \"参数键值\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @NotBlank(message = \"参数键值不能为空\")\n    @Size(max = 500, message = \"参数键值长度不能超过 500 个字符\")\n    private String value;\n\n    @Schema(description = \"是否可见\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"true\")\n    @NotNull(message = \"是否可见不能为空\")\n    private Boolean visible;\n\n    @Schema(description = \"备注\", example = \"备注一下很帅气！\")\n    private String remark;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/db/DataSourceConfigController.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.db;\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.infra.controller.admin.db.vo.DataSourceConfigRespVO;\nimport co.yixiang.yshop.module.infra.controller.admin.db.vo.DataSourceConfigSaveReqVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.db.DataSourceConfigDO;\nimport co.yixiang.yshop.module.infra.service.db.DataSourceConfigService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Valid;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 数据源配置\")\n@RestController\n@RequestMapping(\"/infra/data-source-config\")\n@Validated\npublic class DataSourceConfigController {\n\n    @Resource\n    private DataSourceConfigService dataSourceConfigService;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建数据源配置\")\n    @PreAuthorize(\"@ss.hasPermission('infra:data-source-config:create')\")\n    public CommonResult<Long> createDataSourceConfig(@Valid @RequestBody DataSourceConfigSaveReqVO createReqVO) {\n        return success(dataSourceConfigService.createDataSourceConfig(createReqVO));\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"更新数据源配置\")\n    @PreAuthorize(\"@ss.hasPermission('infra:data-source-config:update')\")\n    public CommonResult<Boolean> updateDataSourceConfig(@Valid @RequestBody DataSourceConfigSaveReqVO updateReqVO) {\n        dataSourceConfigService.updateDataSourceConfig(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除数据源配置\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('infra:data-source-config:delete')\")\n    public CommonResult<Boolean> deleteDataSourceConfig(@RequestParam(\"id\") Long id) {\n        dataSourceConfigService.deleteDataSourceConfig(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得数据源配置\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('infra:data-source-config:query')\")\n    public CommonResult<DataSourceConfigRespVO> getDataSourceConfig(@RequestParam(\"id\") Long id) {\n        DataSourceConfigDO config = dataSourceConfigService.getDataSourceConfig(id);\n        return success(BeanUtils.toBean(config, DataSourceConfigRespVO.class));\n    }\n\n    @GetMapping(\"/list\")\n    @Operation(summary = \"获得数据源配置列表\")\n    @PreAuthorize(\"@ss.hasPermission('infra:data-source-config:query')\")\n    public CommonResult<List<DataSourceConfigRespVO>> getDataSourceConfigList() {\n        List<DataSourceConfigDO> list = dataSourceConfigService.getDataSourceConfigList();\n        return success(BeanUtils.toBean(list, DataSourceConfigRespVO.class));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/db/vo/DataSourceConfigRespVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.db.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 数据源配置 Response VO\")\n@Data\npublic class DataSourceConfigRespVO {\n\n    @Schema(description = \"主键编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Integer id;\n\n    @Schema(description = \"数据源名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"test\")\n    private String name;\n\n    @Schema(description = \"数据源连接\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"jdbc:mysql://127.0.0.1:3306/yixiang-drink\")\n    private String url;\n\n    @Schema(description = \"用户名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"root\")\n    private String username;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/db/vo/DataSourceConfigSaveReqVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.db.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport jakarta.validation.constraints.*;\n\n@Schema(description = \"管理后台 - 数据源配置创建/修改 Request VO\")\n@Data\npublic class DataSourceConfigSaveReqVO {\n\n    @Schema(description = \"主键编号\", example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"数据源名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"test\")\n    @NotNull(message = \"数据源名称不能为空\")\n    private String name;\n\n    @Schema(description = \"数据源连接\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"jdbc:mysql://127.0.0.1:3306/yixiang-drink\")\n    @NotNull(message = \"数据源连接不能为空\")\n    private String url;\n\n    @Schema(description = \"用户名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"root\")\n    @NotNull(message = \"用户名不能为空\")\n    private String username;\n\n    @Schema(description = \"密码\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"123456\")\n    @NotNull(message = \"密码不能为空\")\n    private String password;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/demo/demo01/Demo01ContactController.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.demo.demo01;\n\nimport co.yixiang.yshop.framework.apilog.core.annotation.ApiAccessLog;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.framework.excel.core.util.ExcelUtils;\nimport co.yixiang.yshop.module.infra.controller.admin.demo.demo01.vo.Demo01ContactPageReqVO;\nimport co.yixiang.yshop.module.infra.controller.admin.demo.demo01.vo.Demo01ContactRespVO;\nimport co.yixiang.yshop.module.infra.controller.admin.demo.demo01.vo.Demo01ContactSaveReqVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.demo.demo01.Demo01ContactDO;\nimport co.yixiang.yshop.module.infra.service.demo.demo01.Demo01ContactService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.annotation.Resource;\nimport jakarta.servlet.http.HttpServletResponse;\nimport jakarta.validation.Valid;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.io.IOException;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.apilog.core.enums.OperateTypeEnum.EXPORT;\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 示例联系人\")\n@RestController\n@RequestMapping(\"/infra/demo01-contact\")\n@Validated\npublic class Demo01ContactController {\n\n    @Resource\n    private Demo01ContactService demo01ContactService;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建示例联系人\")\n    @PreAuthorize(\"@ss.hasPermission('infra:demo01-contact:create')\")\n    public CommonResult<Long> createDemo01Contact(@Valid @RequestBody Demo01ContactSaveReqVO createReqVO) {\n        return success(demo01ContactService.createDemo01Contact(createReqVO));\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"更新示例联系人\")\n    @PreAuthorize(\"@ss.hasPermission('infra:demo01-contact:update')\")\n    public CommonResult<Boolean> updateDemo01Contact(@Valid @RequestBody Demo01ContactSaveReqVO updateReqVO) {\n        demo01ContactService.updateDemo01Contact(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除示例联系人\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('infra:demo01-contact:delete')\")\n    public CommonResult<Boolean> deleteDemo01Contact(@RequestParam(\"id\") Long id) {\n        demo01ContactService.deleteDemo01Contact(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得示例联系人\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('infra:demo01-contact:query')\")\n    public CommonResult<Demo01ContactRespVO> getDemo01Contact(@RequestParam(\"id\") Long id) {\n        Demo01ContactDO demo01Contact = demo01ContactService.getDemo01Contact(id);\n        return success(BeanUtils.toBean(demo01Contact, Demo01ContactRespVO.class));\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得示例联系人分页\")\n    @PreAuthorize(\"@ss.hasPermission('infra:demo01-contact:query')\")\n    public CommonResult<PageResult<Demo01ContactRespVO>> getDemo01ContactPage(@Valid Demo01ContactPageReqVO pageReqVO) {\n        PageResult<Demo01ContactDO> pageResult = demo01ContactService.getDemo01ContactPage(pageReqVO);\n        return success(BeanUtils.toBean(pageResult, Demo01ContactRespVO.class));\n    }\n\n    @GetMapping(\"/export-excel\")\n    @Operation(summary = \"导出示例联系人 Excel\")\n    @PreAuthorize(\"@ss.hasPermission('infra:demo01-contact:export')\")\n    @ApiAccessLog(operateType = EXPORT)\n    public void exportDemo01ContactExcel(@Valid Demo01ContactPageReqVO pageReqVO,\n              HttpServletResponse response) throws IOException {\n        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);\n        List<Demo01ContactDO> list = demo01ContactService.getDemo01ContactPage(pageReqVO).getList();\n        // 导出 Excel\n        ExcelUtils.write(response, \"示例联系人.xls\", \"数据\", Demo01ContactRespVO.class,\n                        BeanUtils.toBean(list, Demo01ContactRespVO.class));\n    }\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/demo/demo01/vo/Demo01ContactPageReqVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.demo.demo01.vo;\n\nimport lombok.*;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport org.springframework.format.annotation.DateTimeFormat;\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 示例联系人分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class Demo01ContactPageReqVO extends PageParam {\n\n    @Schema(description = \"名字\", example = \"张三\")\n    private String name;\n\n    @Schema(description = \"性别\", example = \"1\")\n    private Integer sex;\n\n    @Schema(description = \"创建时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/demo/demo01/vo/Demo01ContactRespVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.demo.demo01.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.util.*;\nimport java.util.*;\nimport org.springframework.format.annotation.DateTimeFormat;\nimport java.time.LocalDateTime;\nimport com.alibaba.excel.annotation.*;\nimport co.yixiang.yshop.framework.excel.core.annotations.DictFormat;\nimport co.yixiang.yshop.framework.excel.core.convert.DictConvert;\n\n@Schema(description = \"管理后台 - 示例联系人 Response VO\")\n@Data\n@ExcelIgnoreUnannotated\npublic class Demo01ContactRespVO {\n\n    @Schema(description = \"编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"21555\")\n    @ExcelProperty(\"编号\")\n    private Long id;\n\n    @Schema(description = \"名字\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"张三\")\n    @ExcelProperty(\"名字\")\n    private String name;\n\n    @Schema(description = \"性别\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @ExcelProperty(value = \"性别\", converter = DictConvert.class)\n    @DictFormat(\"system_user_sex\") // TODO 代码优化：建议设置到对应的 DictTypeConstants 枚举类中\n    private Integer sex;\n\n    @Schema(description = \"出生年\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @ExcelProperty(\"出生年\")\n    private LocalDateTime birthday;\n\n    @Schema(description = \"简介\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"你说的对\")\n    @ExcelProperty(\"简介\")\n    private String description;\n\n    @Schema(description = \"头像\")\n    @ExcelProperty(\"头像\")\n    private String avatar;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @ExcelProperty(\"创建时间\")\n    private LocalDateTime createTime;\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/demo/demo01/vo/Demo01ContactSaveReqVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.demo.demo01.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 示例联系人新增/修改 Request VO\")\n@Data\npublic class Demo01ContactSaveReqVO {\n\n    @Schema(description = \"编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"21555\")\n    private Long id;\n\n    @Schema(description = \"名字\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"张三\")\n    @NotEmpty(message = \"名字不能为空\")\n    private String name;\n\n    @Schema(description = \"性别\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @NotNull(message = \"性别不能为空\")\n    private Integer sex;\n\n    @Schema(description = \"出生年\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @NotNull(message = \"出生年不能为空\")\n    private LocalDateTime birthday;\n\n    @Schema(description = \"简介\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"你说的对\")\n    @NotEmpty(message = \"简介不能为空\")\n    private String description;\n\n    @Schema(description = \"头像\")\n    private String avatar;\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/demo/demo02/Demo02CategoryController.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.demo.demo02;\n\nimport co.yixiang.yshop.framework.apilog.core.annotation.ApiAccessLog;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.framework.excel.core.util.ExcelUtils;\nimport co.yixiang.yshop.module.infra.controller.admin.demo.demo02.vo.Demo02CategoryListReqVO;\nimport co.yixiang.yshop.module.infra.controller.admin.demo.demo02.vo.Demo02CategoryRespVO;\nimport co.yixiang.yshop.module.infra.controller.admin.demo.demo02.vo.Demo02CategorySaveReqVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.demo.demo02.Demo02CategoryDO;\nimport co.yixiang.yshop.module.infra.service.demo.demo02.Demo02CategoryService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.annotation.Resource;\nimport jakarta.servlet.http.HttpServletResponse;\nimport jakarta.validation.Valid;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.io.IOException;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.apilog.core.enums.OperateTypeEnum.EXPORT;\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 示例分类\")\n@RestController\n@RequestMapping(\"/infra/demo02-category\")\n@Validated\npublic class Demo02CategoryController {\n\n    @Resource\n    private Demo02CategoryService demo02CategoryService;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建示例分类\")\n    @PreAuthorize(\"@ss.hasPermission('infra:demo02-category:create')\")\n    public CommonResult<Long> createDemo02Category(@Valid @RequestBody Demo02CategorySaveReqVO createReqVO) {\n        return success(demo02CategoryService.createDemo02Category(createReqVO));\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"更新示例分类\")\n    @PreAuthorize(\"@ss.hasPermission('infra:demo02-category:update')\")\n    public CommonResult<Boolean> updateDemo02Category(@Valid @RequestBody Demo02CategorySaveReqVO updateReqVO) {\n        demo02CategoryService.updateDemo02Category(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除示例分类\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('infra:demo02-category:delete')\")\n    public CommonResult<Boolean> deleteDemo02Category(@RequestParam(\"id\") Long id) {\n        demo02CategoryService.deleteDemo02Category(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得示例分类\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('infra:demo02-category:query')\")\n    public CommonResult<Demo02CategoryRespVO> getDemo02Category(@RequestParam(\"id\") Long id) {\n        Demo02CategoryDO demo02Category = demo02CategoryService.getDemo02Category(id);\n        return success(BeanUtils.toBean(demo02Category, Demo02CategoryRespVO.class));\n    }\n\n    @GetMapping(\"/list\")\n    @Operation(summary = \"获得示例分类列表\")\n    @PreAuthorize(\"@ss.hasPermission('infra:demo02-category:query')\")\n    public CommonResult<List<Demo02CategoryRespVO>> getDemo02CategoryList(@Valid Demo02CategoryListReqVO listReqVO) {\n        List<Demo02CategoryDO> list = demo02CategoryService.getDemo02CategoryList(listReqVO);\n        return success(BeanUtils.toBean(list, Demo02CategoryRespVO.class));\n    }\n\n    @GetMapping(\"/export-excel\")\n    @Operation(summary = \"导出示例分类 Excel\")\n    @PreAuthorize(\"@ss.hasPermission('infra:demo02-category:export')\")\n    @ApiAccessLog(operateType = EXPORT)\n    public void exportDemo02CategoryExcel(@Valid Demo02CategoryListReqVO listReqVO,\n              HttpServletResponse response) throws IOException {\n        List<Demo02CategoryDO> list = demo02CategoryService.getDemo02CategoryList(listReqVO);\n        // 导出 Excel\n        ExcelUtils.write(response, \"示例分类.xls\", \"数据\", Demo02CategoryRespVO.class,\n                        BeanUtils.toBean(list, Demo02CategoryRespVO.class));\n    }\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/demo/demo02/vo/Demo02CategoryListReqVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.demo.demo02.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 示例分类列表 Request VO\")\n@Data\npublic class Demo02CategoryListReqVO {\n\n    @Schema(description = \"名字\", example = \"yshop\")\n    private String name;\n\n    @Schema(description = \"父级编号\", example = \"6080\")\n    private Long parentId;\n\n    @Schema(description = \"创建时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/demo/demo02/vo/Demo02CategoryRespVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.demo.demo02.vo;\n\nimport com.alibaba.excel.annotation.ExcelIgnoreUnannotated;\nimport com.alibaba.excel.annotation.ExcelProperty;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 示例分类 Response VO\")\n@Data\n@ExcelIgnoreUnannotated\npublic class Demo02CategoryRespVO {\n\n    @Schema(description = \"编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"10304\")\n    @ExcelProperty(\"编号\")\n    private Long id;\n\n    @Schema(description = \"名字\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    @ExcelProperty(\"名字\")\n    private String name;\n\n    @Schema(description = \"父级编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"6080\")\n    @ExcelProperty(\"父级编号\")\n    private Long parentId;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @ExcelProperty(\"创建时间\")\n    private LocalDateTime createTime;\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/demo/demo02/vo/Demo02CategorySaveReqVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.demo.demo02.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\n\n@Schema(description = \"管理后台 - 示例分类新增/修改 Request VO\")\n@Data\npublic class Demo02CategorySaveReqVO {\n\n    @Schema(description = \"编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"10304\")\n    private Long id;\n\n    @Schema(description = \"名字\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    @NotEmpty(message = \"名字不能为空\")\n    private String name;\n\n    @Schema(description = \"父级编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"6080\")\n    @NotNull(message = \"父级编号不能为空\")\n    private Long parentId;\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/demo/demo03/Demo03StudentController.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.demo.demo03;\n\nimport co.yixiang.yshop.framework.apilog.core.annotation.ApiAccessLog;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.framework.excel.core.util.ExcelUtils;\nimport co.yixiang.yshop.module.infra.controller.admin.demo.demo03.vo.Demo03StudentPageReqVO;\nimport co.yixiang.yshop.module.infra.controller.admin.demo.demo03.vo.Demo03StudentRespVO;\nimport co.yixiang.yshop.module.infra.controller.admin.demo.demo03.vo.Demo03StudentSaveReqVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.demo.demo03.Demo03CourseDO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.demo.demo03.Demo03GradeDO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.demo.demo03.Demo03StudentDO;\nimport co.yixiang.yshop.module.infra.service.demo.demo03.Demo03StudentService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.annotation.Resource;\nimport jakarta.servlet.http.HttpServletResponse;\nimport jakarta.validation.Valid;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.io.IOException;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.apilog.core.enums.OperateTypeEnum.EXPORT;\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 学生\")\n@RestController\n@RequestMapping(\"/infra/demo03-student\")\n@Validated\npublic class Demo03StudentController {\n\n    @Resource\n    private Demo03StudentService demo03StudentService;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建学生\")\n    @PreAuthorize(\"@ss.hasPermission('infra:demo03-student:create')\")\n    public CommonResult<Long> createDemo03Student(@Valid @RequestBody Demo03StudentSaveReqVO createReqVO) {\n        return success(demo03StudentService.createDemo03Student(createReqVO));\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"更新学生\")\n    @PreAuthorize(\"@ss.hasPermission('infra:demo03-student:update')\")\n    public CommonResult<Boolean> updateDemo03Student(@Valid @RequestBody Demo03StudentSaveReqVO updateReqVO) {\n        demo03StudentService.updateDemo03Student(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除学生\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('infra:demo03-student:delete')\")\n    public CommonResult<Boolean> deleteDemo03Student(@RequestParam(\"id\") Long id) {\n        demo03StudentService.deleteDemo03Student(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得学生\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('infra:demo03-student:query')\")\n    public CommonResult<Demo03StudentRespVO> getDemo03Student(@RequestParam(\"id\") Long id) {\n        Demo03StudentDO demo03Student = demo03StudentService.getDemo03Student(id);\n        return success(BeanUtils.toBean(demo03Student, Demo03StudentRespVO.class));\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得学生分页\")\n    @PreAuthorize(\"@ss.hasPermission('infra:demo03-student:query')\")\n    public CommonResult<PageResult<Demo03StudentRespVO>> getDemo03StudentPage(@Valid Demo03StudentPageReqVO pageReqVO) {\n        PageResult<Demo03StudentDO> pageResult = demo03StudentService.getDemo03StudentPage(pageReqVO);\n        return success(BeanUtils.toBean(pageResult, Demo03StudentRespVO.class));\n    }\n\n    @GetMapping(\"/export-excel\")\n    @Operation(summary = \"导出学生 Excel\")\n    @PreAuthorize(\"@ss.hasPermission('infra:demo03-student:export')\")\n    @ApiAccessLog(operateType = EXPORT)\n    public void exportDemo03StudentExcel(@Valid Demo03StudentPageReqVO pageReqVO,\n              HttpServletResponse response) throws IOException {\n        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);\n        List<Demo03StudentDO> list = demo03StudentService.getDemo03StudentPage(pageReqVO).getList();\n        // 导出 Excel\n        ExcelUtils.write(response, \"学生.xls\", \"数据\", Demo03StudentRespVO.class,\n                        BeanUtils.toBean(list, Demo03StudentRespVO.class));\n    }\n\n    // ==================== 子表（学生课程） ====================\n\n    @GetMapping(\"/demo03-course/page\")\n    @Operation(summary = \"获得学生课程分页\")\n    @Parameter(name = \"studentId\", description = \"学生编号\")\n    @PreAuthorize(\"@ss.hasPermission('infra:demo03-student:query')\")\n    public CommonResult<PageResult<Demo03CourseDO>> getDemo03CoursePage(PageParam pageReqVO,\n                                                                        @RequestParam(\"studentId\") Long studentId) {\n        return success(demo03StudentService.getDemo03CoursePage(pageReqVO, studentId));\n    }\n\n    @PostMapping(\"/demo03-course/create\")\n    @Operation(summary = \"创建学生课程\")\n    @PreAuthorize(\"@ss.hasPermission('infra:demo03-student:create')\")\n    public CommonResult<Long> createDemo03Course(@Valid @RequestBody Demo03CourseDO demo03Course) {\n        return success(demo03StudentService.createDemo03Course(demo03Course));\n    }\n\n    @PutMapping(\"/demo03-course/update\")\n    @Operation(summary = \"更新学生课程\")\n    @PreAuthorize(\"@ss.hasPermission('infra:demo03-student:update')\")\n    public CommonResult<Boolean> updateDemo03Course(@Valid @RequestBody Demo03CourseDO demo03Course) {\n        demo03StudentService.updateDemo03Course(demo03Course);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/demo03-course/delete\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @Operation(summary = \"删除学生课程\")\n    @PreAuthorize(\"@ss.hasPermission('infra:demo03-student:delete')\")\n    public CommonResult<Boolean> deleteDemo03Course(@RequestParam(\"id\") Long id) {\n        demo03StudentService.deleteDemo03Course(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/demo03-course/get\")\n    @Operation(summary = \"获得学生课程\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('infra:demo03-student:query')\")\n    public CommonResult<Demo03CourseDO> getDemo03Course(@RequestParam(\"id\") Long id) {\n        return success(demo03StudentService.getDemo03Course(id));\n    }\n\n    @GetMapping(\"/demo03-course/list-by-student-id\")\n    @Operation(summary = \"获得学生课程列表\")\n    @Parameter(name = \"studentId\", description = \"学生编号\")\n    @PreAuthorize(\"@ss.hasPermission('infra:demo03-student:query')\")\n    public CommonResult<List<Demo03CourseDO>> getDemo03CourseListByStudentId(@RequestParam(\"studentId\") Long studentId) {\n        return success(demo03StudentService.getDemo03CourseListByStudentId(studentId));\n    }\n\n    // ==================== 子表（学生班级） ====================\n\n    @GetMapping(\"/demo03-grade/page\")\n    @Operation(summary = \"获得学生班级分页\")\n    @Parameter(name = \"studentId\", description = \"学生编号\")\n    @PreAuthorize(\"@ss.hasPermission('infra:demo03-student:query')\")\n    public CommonResult<PageResult<Demo03GradeDO>> getDemo03GradePage(PageParam pageReqVO,\n                                                                      @RequestParam(\"studentId\") Long studentId) {\n        return success(demo03StudentService.getDemo03GradePage(pageReqVO, studentId));\n    }\n\n    @PostMapping(\"/demo03-grade/create\")\n    @Operation(summary = \"创建学生班级\")\n    @PreAuthorize(\"@ss.hasPermission('infra:demo03-student:create')\")\n    public CommonResult<Long> createDemo03Grade(@Valid @RequestBody Demo03GradeDO demo03Grade) {\n        return success(demo03StudentService.createDemo03Grade(demo03Grade));\n    }\n\n    @PutMapping(\"/demo03-grade/update\")\n    @Operation(summary = \"更新学生班级\")\n    @PreAuthorize(\"@ss.hasPermission('infra:demo03-student:update')\")\n    public CommonResult<Boolean> updateDemo03Grade(@Valid @RequestBody Demo03GradeDO demo03Grade) {\n        demo03StudentService.updateDemo03Grade(demo03Grade);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/demo03-grade/delete\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @Operation(summary = \"删除学生班级\")\n    @PreAuthorize(\"@ss.hasPermission('infra:demo03-student:delete')\")\n    public CommonResult<Boolean> deleteDemo03Grade(@RequestParam(\"id\") Long id) {\n        demo03StudentService.deleteDemo03Grade(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/demo03-grade/get\")\n    @Operation(summary = \"获得学生班级\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('infra:demo03-student:query')\")\n    public CommonResult<Demo03GradeDO> getDemo03Grade(@RequestParam(\"id\") Long id) {\n        return success(demo03StudentService.getDemo03Grade(id));\n    }\n\n    @GetMapping(\"/demo03-grade/get-by-student-id\")\n    @Operation(summary = \"获得学生班级\")\n    @Parameter(name = \"studentId\", description = \"学生编号\")\n    @PreAuthorize(\"@ss.hasPermission('infra:demo03-student:query')\")\n    public CommonResult<Demo03GradeDO> getDemo03GradeByStudentId(@RequestParam(\"studentId\") Long studentId) {\n        return success(demo03StudentService.getDemo03GradeByStudentId(studentId));\n    }\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/demo/demo03/package-info.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.demo.demo03;"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/demo/demo03/vo/Demo03StudentPageReqVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.demo.demo03.vo;\n\nimport lombok.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport org.springframework.format.annotation.DateTimeFormat;\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 学生分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class Demo03StudentPageReqVO extends PageParam {\n\n    @Schema(description = \"名字\", example = \"yshop\")\n    private String name;\n\n    @Schema(description = \"性别\")\n    private Integer sex;\n\n    @Schema(description = \"简介\", example = \"随便\")\n    private String description;\n\n    @Schema(description = \"创建时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/demo/demo03/vo/Demo03StudentRespVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.demo.demo03.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\n\nimport java.time.LocalDateTime;\nimport com.alibaba.excel.annotation.*;\nimport co.yixiang.yshop.framework.excel.core.annotations.DictFormat;\nimport co.yixiang.yshop.framework.excel.core.convert.DictConvert;\n\n@Schema(description = \"管理后台 - 学生 Response VO\")\n@Data\n@ExcelIgnoreUnannotated\npublic class Demo03StudentRespVO {\n\n    @Schema(description = \"编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"8525\")\n    @ExcelProperty(\"编号\")\n    private Long id;\n\n    @Schema(description = \"名字\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    @ExcelProperty(\"名字\")\n    private String name;\n\n    @Schema(description = \"性别\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @ExcelProperty(value = \"性别\", converter = DictConvert.class)\n    @DictFormat(\"system_user_sex\") // TODO 代码优化：建议设置到对应的 DictTypeConstants 枚举类中\n    private Integer sex;\n\n    @Schema(description = \"出生日期\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @ExcelProperty(\"出生日期\")\n    private LocalDateTime birthday;\n\n    @Schema(description = \"简介\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"随便\")\n    @ExcelProperty(\"简介\")\n    private String description;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @ExcelProperty(\"创建时间\")\n    private LocalDateTime createTime;\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/demo/demo03/vo/Demo03StudentSaveReqVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.demo.demo03.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.util.*;\nimport jakarta.validation.constraints.*;\nimport java.time.LocalDateTime;\nimport co.yixiang.yshop.module.infra.dal.dataobject.demo.demo03.Demo03CourseDO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.demo.demo03.Demo03GradeDO;\n\n@Schema(description = \"管理后台 - 学生新增/修改 Request VO\")\n@Data\npublic class Demo03StudentSaveReqVO {\n\n    @Schema(description = \"编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"8525\")\n    private Long id;\n\n    @Schema(description = \"名字\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    @NotEmpty(message = \"名字不能为空\")\n    private String name;\n\n    @Schema(description = \"性别\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @NotNull(message = \"性别不能为空\")\n    private Integer sex;\n\n    @Schema(description = \"出生日期\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @NotNull(message = \"出生日期不能为空\")\n    private LocalDateTime birthday;\n\n    @Schema(description = \"简介\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"随便\")\n    @NotEmpty(message = \"简介不能为空\")\n    private String description;\n\n\n    private List<Demo03CourseDO> demo03Courses;\n\n    private Demo03GradeDO demo03Grade;\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/file/FileConfigController.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.file;\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.infra.controller.admin.file.vo.config.FileConfigPageReqVO;\nimport co.yixiang.yshop.module.infra.controller.admin.file.vo.config.FileConfigRespVO;\nimport co.yixiang.yshop.module.infra.controller.admin.file.vo.config.FileConfigSaveReqVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.file.FileConfigDO;\nimport co.yixiang.yshop.module.infra.service.file.FileConfigService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Valid;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 文件配置\")\n@RestController\n@RequestMapping(\"/infra/file-config\")\n@Validated\npublic class FileConfigController {\n\n    @Resource\n    private FileConfigService fileConfigService;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建文件配置\")\n    @PreAuthorize(\"@ss.hasPermission('infra:file-config:create')\")\n    public CommonResult<Long> createFileConfig(@Valid @RequestBody FileConfigSaveReqVO createReqVO) {\n        return success(fileConfigService.createFileConfig(createReqVO));\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"更新文件配置\")\n    @PreAuthorize(\"@ss.hasPermission('infra:file-config:update')\")\n    public CommonResult<Boolean> updateFileConfig(@Valid @RequestBody FileConfigSaveReqVO updateReqVO) {\n        fileConfigService.updateFileConfig(updateReqVO);\n        return success(true);\n    }\n\n    @PutMapping(\"/update-master\")\n    @Operation(summary = \"更新文件配置为 Master\")\n    @PreAuthorize(\"@ss.hasPermission('infra:file-config:update')\")\n    public CommonResult<Boolean> updateFileConfigMaster(@RequestParam(\"id\") Long id) {\n        fileConfigService.updateFileConfigMaster(id);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除文件配置\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('infra:file-config:delete')\")\n    public CommonResult<Boolean> deleteFileConfig(@RequestParam(\"id\") Long id) {\n        fileConfigService.deleteFileConfig(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得文件配置\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('infra:file-config:query')\")\n    public CommonResult<FileConfigRespVO> getFileConfig(@RequestParam(\"id\") Long id) {\n        FileConfigDO config = fileConfigService.getFileConfig(id);\n        return success(BeanUtils.toBean(config, FileConfigRespVO.class));\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得文件配置分页\")\n    @PreAuthorize(\"@ss.hasPermission('infra:file-config:query')\")\n    public CommonResult<PageResult<FileConfigRespVO>> getFileConfigPage(@Valid FileConfigPageReqVO pageVO) {\n        PageResult<FileConfigDO> pageResult = fileConfigService.getFileConfigPage(pageVO);\n        return success(BeanUtils.toBean(pageResult, FileConfigRespVO.class));\n    }\n\n    @GetMapping(\"/test\")\n    @Operation(summary = \"测试文件配置是否正确\")\n    @PreAuthorize(\"@ss.hasPermission('infra:file-config:query')\")\n    public CommonResult<String> testFileConfig(@RequestParam(\"id\") Long id) throws Exception {\n        String url = fileConfigService.testFileConfig(id);\n        return success(url);\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/file/FileController.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.file;\n\nimport cn.hutool.core.io.IoUtil;\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.core.util.URLUtil;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.infra.controller.admin.file.vo.file.*;\nimport co.yixiang.yshop.module.infra.dal.dataobject.file.FileDO;\nimport co.yixiang.yshop.module.infra.service.file.FileService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.annotation.Resource;\nimport jakarta.annotation.security.PermitAll;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.servlet.http.HttpServletResponse;\nimport jakarta.validation.Valid;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.http.HttpStatus;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\nimport org.springframework.web.multipart.MultipartFile;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\nimport static co.yixiang.yshop.module.infra.framework.file.core.utils.FileTypeUtils.writeAttachment;\n\n@Tag(name = \"管理后台 - 文件存储\")\n@RestController\n@RequestMapping(\"/infra/file\")\n@Validated\n@Slf4j\npublic class FileController {\n\n    @Resource\n    private FileService fileService;\n\n    @PostMapping(\"/upload\")\n    @Operation(summary = \"上传文件\", description = \"模式一：后端上传文件\")\n    public CommonResult<String> uploadFile(FileUploadReqVO uploadReqVO) throws Exception {\n        MultipartFile file = uploadReqVO.getFile();\n        String path = uploadReqVO.getPath();\n        return success(fileService.createFile(file.getOriginalFilename(), path, IoUtil.readBytes(file.getInputStream())));\n    }\n\n    @GetMapping(\"/presigned-url\")\n    @Operation(summary = \"获取文件预签名地址\", description = \"模式二：前端上传文件：用于前端直接上传七牛、阿里云 OSS 等文件存储器\")\n    public CommonResult<FilePresignedUrlRespVO> getFilePresignedUrl(@RequestParam(\"path\") String path) throws Exception {\n        return success(fileService.getFilePresignedUrl(path));\n    }\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建文件\", description = \"模式二：前端上传文件：配合 presigned-url 接口，记录上传了上传的文件\")\n    public CommonResult<Long> createFile(@Valid @RequestBody FileCreateReqVO createReqVO) {\n        return success(fileService.createFile(createReqVO));\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除文件\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('infra:file:delete')\")\n    public CommonResult<Boolean> deleteFile(@RequestParam(\"id\") Long id) throws Exception {\n        fileService.deleteFile(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/{configId}/get/**\")\n    @PermitAll\n    @Operation(summary = \"下载文件\")\n    @Parameter(name = \"configId\", description = \"配置编号\", required = true)\n    public void getFileContent(HttpServletRequest request,\n                               HttpServletResponse response,\n                               @PathVariable(\"configId\") Long configId) throws Exception {\n        // 获取请求的路径\n        String path = StrUtil.subAfter(request.getRequestURI(), \"/get/\", false);\n        if (StrUtil.isEmpty(path)) {\n            throw new IllegalArgumentException(\"结尾的 path 路径必须传递\");\n        }\n        // 解码，解决中文路径的问题 https://gitee.com/zhijiantianya/yixiang-drink/pulls/807/\n        path = URLUtil.decode(path);\n\n        // 读取内容\n        byte[] content = fileService.getFileContent(configId, path);\n        if (content == null) {\n            log.warn(\"[getFileContent][configId({}) path({}) 文件不存在]\", configId, path);\n            response.setStatus(HttpStatus.NOT_FOUND.value());\n            return;\n        }\n        writeAttachment(response, path, content);\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得文件分页\")\n    @PreAuthorize(\"@ss.hasPermission('infra:file:query')\")\n    public CommonResult<PageResult<FileRespVO>> getFilePage(@Valid FilePageReqVO pageVO) {\n        PageResult<FileDO> pageResult = fileService.getFilePage(pageVO);\n        return success(BeanUtils.toBean(pageResult, FileRespVO.class));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/file/vo/config/FileConfigPageReqVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.file.vo.config;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 文件配置分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class FileConfigPageReqVO extends PageParam {\n\n    @Schema(description = \"配置名\", example = \"S3 - 阿里云\")\n    private String name;\n\n    @Schema(description = \"存储器\", example = \"1\")\n    private Integer storage;\n\n    @Schema(description = \"创建时间\", example = \"[2022-07-01 00:00:00, 2022-07-01 23:59:59]\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/file/vo/config/FileConfigRespVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.file.vo.config;\n\nimport co.yixiang.yshop.module.infra.framework.file.core.client.FileClientConfig;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 文件配置 Response VO\")\n@Data\npublic class FileConfigRespVO {\n\n    @Schema(description = \"编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    private Long id;\n\n    @Schema(description = \"配置名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"S3 - 阿里云\")\n    private String name;\n\n    @Schema(description = \"存储器，参见 FileStorageEnum 枚举类\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    private Integer storage;\n\n    @Schema(description = \"是否为主配置\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"true\")\n    private Boolean master;\n\n    @Schema(description = \"存储配置\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private FileClientConfig config;\n\n    @Schema(description = \"备注\", example = \"我是备注\")\n    private String remark;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/file/vo/config/FileConfigSaveReqVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.file.vo.config;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotNull;\nimport java.util.Map;\n\n@Schema(description = \"管理后台 - 文件配置创建/修改 Request VO\")\n@Data\npublic class FileConfigSaveReqVO {\n\n    @Schema(description = \"编号\", example = \"1\")\n    private Long id;\n\n    @Schema(description = \"配置名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"S3 - 阿里云\")\n    @NotNull(message = \"配置名不能为空\")\n    private String name;\n\n    @Schema(description = \"存储器，参见 FileStorageEnum 枚举类\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @NotNull(message = \"存储器不能为空\")\n    private Integer storage;\n\n    @Schema(description = \"存储配置,配置是动态参数，所以使用 Map 接收\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @NotNull(message = \"存储配置不能为空\")\n    private Map<String, Object> config;\n\n    @Schema(description = \"备注\", example = \"我是备注\")\n    private String remark;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/file/vo/file/FileCreateReqVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.file.vo.file;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotNull;\nimport lombok.Data;\n\n@Schema(description = \"管理后台 - 文件创建 Request VO\")\n@Data\npublic class FileCreateReqVO {\n\n    @NotNull(message = \"文件配置编号不能为空\")\n    @Schema(description = \"文件配置编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"11\")\n    private Long configId;\n\n    @NotNull(message = \"文件路径不能为空\")\n    @Schema(description = \"文件路径\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop.jpg\")\n    private String path;\n\n    @NotNull(message = \"原文件名不能为空\")\n    @Schema(description = \"原文件名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop.jpg\")\n    private String name;\n\n    @NotNull(message = \"文件 URL不能为空\")\n    @Schema(description = \"文件 URL\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"https://www.yixiang.co/yshop.jpg\")\n    private String url;\n\n    @Schema(description = \"文件 MIME 类型\", example = \"application/octet-stream\")\n    private String type;\n\n    @Schema(description = \"文件大小\", example = \"2048\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private Integer size;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/file/vo/file/FilePageReqVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.file.vo.file;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 文件分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class FilePageReqVO extends PageParam {\n\n    @Schema(description = \"文件路径，模糊匹配\", example = \"yshop\")\n    private String path;\n\n    @Schema(description = \"文件类型，模糊匹配\", example = \"jpg\")\n    private String type;\n\n    @Schema(description = \"创建时间\", example = \"[2022-07-01 00:00:00, 2022-07-01 23:59:59]\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/file/vo/file/FilePresignedUrlRespVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.file.vo.file;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\n@AllArgsConstructor\n@NoArgsConstructor\n@Schema(description = \"管理后台 - 文件预签名地址 Response VO\")\n@Data\npublic class FilePresignedUrlRespVO {\n\n    @Schema(description = \"配置编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"11\")\n    private Long configId;\n\n    @Schema(description = \"文件上传 URL\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"https://s3.cn-south-1.qiniucs.com/yixiang-drink/758d3a5387507358c7236de4c8f96de1c7f5097ff6a7722b34772fb7b76b140f.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=3TvrJ70gl2Gt6IBe7_IZT1F6i_k0iMuRtyEv4EyS%2F20240217%2Fcn-south-1%2Fs3%2Faws4_request&X-Amz-Date=20240217T123222Z&X-Amz-Expires=600&X-Amz-SignedHeaders=host&X-Amz-Signature=a29f33770ab79bf523ccd4034d0752ac545f3c2a3b17baa1eb4e280cfdccfda5\")\n    private String uploadUrl;\n\n    /**\n     * 为什么要返回 url 字段？\n     *\n     * 前端上传完文件后，需要使用该 URL 进行访问\n     */\n    @Schema(description = \"文件访问 URL\", requiredMode = Schema.RequiredMode.REQUIRED,\n            example = \"https://test.yshop.yixiang.co/758d3a5387507358c7236de4c8f96de1c7f5097ff6a7722b34772fb7b76b140f.png\")\n    private String url;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/file/vo/file/FileRespVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.file.vo.file;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 文件 Response VO,不返回 content 字段，太大\")\n@Data\npublic class FileRespVO {\n\n    @Schema(description = \"文件编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"配置编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"11\")\n    private Long configId;\n\n    @Schema(description = \"文件路径\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop.jpg\")\n    private String path;\n\n    @Schema(description = \"原文件名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop.jpg\")\n    private String name;\n\n    @Schema(description = \"文件 URL\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"https://www.yixiang.co/yshop.jpg\")\n    private String url;\n\n    @Schema(description = \"文件MIME类型\", example = \"application/octet-stream\")\n    private String type;\n\n    @Schema(description = \"文件大小\", example = \"2048\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private Integer size;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/file/vo/file/FileUploadReqVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.file.vo.file;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport org.springframework.web.multipart.MultipartFile;\n\nimport jakarta.validation.constraints.NotNull;\n\n@Schema(description = \"管理后台 - 上传文件 Request VO\")\n@Data\npublic class FileUploadReqVO {\n\n    @Schema(description = \"文件附件\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @NotNull(message = \"文件附件不能为空\")\n    private MultipartFile file;\n\n    @Schema(description = \"文件附件\", example = \"yshopyuanma.png\")\n    private String path;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/job/JobController.http",
    "content": "### 请求 /infra/job/sync 接口 => 成功\nPOST {{baseUrl}}/infra/job/sync\nContent-Type: application/json\ntenant-id: {{adminTenentId}}\nAuthorization: Bearer {{token}}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/job/JobController.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.job;\n\nimport co.yixiang.yshop.framework.apilog.core.annotation.ApiAccessLog;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.framework.excel.core.util.ExcelUtils;\nimport co.yixiang.yshop.framework.quartz.core.util.CronUtils;\nimport co.yixiang.yshop.module.infra.controller.admin.job.vo.job.JobPageReqVO;\nimport co.yixiang.yshop.module.infra.controller.admin.job.vo.job.JobRespVO;\nimport co.yixiang.yshop.module.infra.controller.admin.job.vo.job.JobSaveReqVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.job.JobDO;\nimport co.yixiang.yshop.module.infra.service.job.JobService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.Parameters;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.annotation.Resource;\nimport jakarta.servlet.http.HttpServletResponse;\nimport jakarta.validation.Valid;\nimport org.quartz.SchedulerException;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.io.IOException;\nimport java.time.LocalDateTime;\nimport java.util.Collections;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.apilog.core.enums.OperateTypeEnum.EXPORT;\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 定时任务\")\n@RestController\n@RequestMapping(\"/infra/job\")\n@Validated\npublic class JobController {\n\n    @Resource\n    private JobService jobService;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建定时任务\")\n    @PreAuthorize(\"@ss.hasPermission('infra:job:create')\")\n    public CommonResult<Long> createJob(@Valid @RequestBody JobSaveReqVO createReqVO)\n            throws SchedulerException {\n        return success(jobService.createJob(createReqVO));\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"更新定时任务\")\n    @PreAuthorize(\"@ss.hasPermission('infra:job:update')\")\n    public CommonResult<Boolean> updateJob(@Valid @RequestBody JobSaveReqVO updateReqVO)\n            throws SchedulerException {\n        jobService.updateJob(updateReqVO);\n        return success(true);\n    }\n\n    @PutMapping(\"/update-status\")\n    @Operation(summary = \"更新定时任务的状态\")\n    @Parameters({\n            @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\"),\n            @Parameter(name = \"status\", description = \"状态\", required = true, example = \"1\"),\n    })\n    @PreAuthorize(\"@ss.hasPermission('infra:job:update')\")\n    public CommonResult<Boolean> updateJobStatus(@RequestParam(value = \"id\") Long id, @RequestParam(\"status\") Integer status)\n            throws SchedulerException {\n        jobService.updateJobStatus(id, status);\n        return success(true);\n    }\n\n\t@DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除定时任务\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n\t@PreAuthorize(\"@ss.hasPermission('infra:job:delete')\")\n    public CommonResult<Boolean> deleteJob(@RequestParam(\"id\") Long id)\n            throws SchedulerException {\n        jobService.deleteJob(id);\n        return success(true);\n    }\n\n    @PutMapping(\"/trigger\")\n    @Operation(summary = \"触发定时任务\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('infra:job:trigger')\")\n    public CommonResult<Boolean> triggerJob(@RequestParam(\"id\") Long id) throws SchedulerException {\n        jobService.triggerJob(id);\n        return success(true);\n    }\n\n    @PostMapping(\"/sync\")\n    @Operation(summary = \"同步定时任务\")\n    @PreAuthorize(\"@ss.hasPermission('infra:job:create')\")\n    public CommonResult<Boolean> syncJob() throws SchedulerException {\n        jobService.syncJob();\n        return success(true);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得定时任务\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('infra:job:query')\")\n    public CommonResult<JobRespVO> getJob(@RequestParam(\"id\") Long id) {\n        JobDO job = jobService.getJob(id);\n        return success(BeanUtils.toBean(job, JobRespVO.class));\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得定时任务分页\")\n    @PreAuthorize(\"@ss.hasPermission('infra:job:query')\")\n    public CommonResult<PageResult<JobRespVO>> getJobPage(@Valid JobPageReqVO pageVO) {\n        PageResult<JobDO> pageResult = jobService.getJobPage(pageVO);\n        return success(BeanUtils.toBean(pageResult, JobRespVO.class));\n    }\n\n    @GetMapping(\"/export-excel\")\n    @Operation(summary = \"导出定时任务 Excel\")\n    @PreAuthorize(\"@ss.hasPermission('infra:job:export')\")\n    @ApiAccessLog(operateType = EXPORT)\n    public void exportJobExcel(@Valid JobPageReqVO exportReqVO,\n                               HttpServletResponse response) throws IOException {\n        exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);\n        List<JobDO> list = jobService.getJobPage(exportReqVO).getList();\n        // 导出 Excel\n        ExcelUtils.write(response, \"定时任务.xls\", \"数据\", JobRespVO.class,\n                BeanUtils.toBean(list, JobRespVO.class));\n    }\n\n    @GetMapping(\"/get_next_times\")\n    @Operation(summary = \"获得定时任务的下 n 次执行时间\")\n    @Parameters({\n            @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\"),\n            @Parameter(name = \"count\", description = \"数量\", example = \"5\")\n    })\n    @PreAuthorize(\"@ss.hasPermission('infra:job:query')\")\n    public CommonResult<List<LocalDateTime>> getJobNextTimes(\n            @RequestParam(\"id\") Long id,\n            @RequestParam(value = \"count\", required = false, defaultValue = \"5\") Integer count) {\n        JobDO job = jobService.getJob(id);\n        if (job == null) {\n            return success(Collections.emptyList());\n        }\n        return success(CronUtils.getNextTimes(job.getCronExpression(), count));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/job/JobLogController.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.job;\n\nimport co.yixiang.yshop.framework.apilog.core.annotation.ApiAccessLog;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.framework.excel.core.util.ExcelUtils;\nimport co.yixiang.yshop.module.infra.controller.admin.job.vo.log.JobLogPageReqVO;\nimport co.yixiang.yshop.module.infra.controller.admin.job.vo.log.JobLogRespVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.job.JobLogDO;\nimport co.yixiang.yshop.module.infra.service.job.JobLogService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.annotation.Resource;\nimport jakarta.servlet.http.HttpServletResponse;\nimport jakarta.validation.Valid;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport java.io.IOException;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.apilog.core.enums.OperateTypeEnum.EXPORT;\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 定时任务日志\")\n@RestController\n@RequestMapping(\"/infra/job-log\")\n@Validated\npublic class JobLogController {\n\n    @Resource\n    private JobLogService jobLogService;\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得定时任务日志\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('infra:job:query')\")\n    public CommonResult<JobLogRespVO> getJobLog(@RequestParam(\"id\") Long id) {\n        JobLogDO jobLog = jobLogService.getJobLog(id);\n        return success(BeanUtils.toBean(jobLog, JobLogRespVO.class));\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得定时任务日志分页\")\n    @PreAuthorize(\"@ss.hasPermission('infra:job:query')\")\n    public CommonResult<PageResult<JobLogRespVO>> getJobLogPage(@Valid JobLogPageReqVO pageVO) {\n        PageResult<JobLogDO> pageResult = jobLogService.getJobLogPage(pageVO);\n        return success(BeanUtils.toBean(pageResult, JobLogRespVO.class));\n    }\n\n    @GetMapping(\"/export-excel\")\n    @Operation(summary = \"导出定时任务日志 Excel\")\n    @PreAuthorize(\"@ss.hasPermission('infra:job:export')\")\n    @ApiAccessLog(operateType = EXPORT)\n    public void exportJobLogExcel(@Valid JobLogPageReqVO exportReqVO,\n                                  HttpServletResponse response) throws IOException {\n        exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);\n        List<JobLogDO> list = jobLogService.getJobLogPage(exportReqVO).getList();\n        // 导出 Excel\n        ExcelUtils.write(response, \"任务日志.xls\", \"数据\", JobLogRespVO.class,\n                BeanUtils.toBean(list, JobLogRespVO.class));\n    }\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/job/vo/job/JobPageReqVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.job.vo.job;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\n\n@Schema(description = \"管理后台 - 定时任务分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class JobPageReqVO extends PageParam {\n\n    @Schema(description = \"任务名称，模糊匹配\", example = \"测试任务\")\n    private String name;\n\n    @Schema(description = \"任务状态，参见 JobStatusEnum 枚举\", example = \"1\")\n    private Integer status;\n\n    @Schema(description = \"处理器的名字，模糊匹配\", example = \"sysUserSessionTimeoutJob\")\n    private String handlerName;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/job/vo/job/JobRespVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.job.vo.job;\n\nimport co.yixiang.yshop.framework.excel.core.annotations.DictFormat;\nimport co.yixiang.yshop.framework.excel.core.convert.DictConvert;\nimport co.yixiang.yshop.module.infra.enums.DictTypeConstants;\nimport com.alibaba.excel.annotation.ExcelIgnoreUnannotated;\nimport com.alibaba.excel.annotation.ExcelProperty;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotNull;\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 定时任务 Response VO\")\n@Data\n@ExcelIgnoreUnannotated\npublic class JobRespVO {\n\n    @Schema(description = \"任务编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @ExcelProperty(\"任务编号\")\n    private Long id;\n\n    @Schema(description = \"任务名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"测试任务\")\n    @ExcelProperty(\"任务名称\")\n    private String name;\n\n    @Schema(description = \"任务状态\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @ExcelProperty(value = \"任务状态\", converter = DictConvert.class)\n    @DictFormat(DictTypeConstants.JOB_STATUS)\n    private Integer status;\n\n    @Schema(description = \"处理器的名字\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"sysUserSessionTimeoutJob\")\n    @ExcelProperty(\"处理器的名字\")\n    private String handlerName;\n\n    @Schema(description = \"处理器的参数\", example = \"yshop\")\n    @ExcelProperty(\"处理器的参数\")\n    private String handlerParam;\n\n    @Schema(description = \"CRON 表达式\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"0/10 * * * * ? *\")\n    @ExcelProperty(\"CRON 表达式\")\n    private String cronExpression;\n\n    @Schema(description = \"重试次数\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"3\")\n    @NotNull(message = \"重试次数不能为空\")\n    private Integer retryCount;\n\n    @Schema(description = \"重试间隔\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1000\")\n    private Integer retryInterval;\n\n    @Schema(description = \"监控超时时间\", example = \"1000\")\n    @ExcelProperty(\"监控超时时间\")\n    private Integer monitorTimeout;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @ExcelProperty(\"创建时间\")\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/job/vo/job/JobSaveReqVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.job.vo.job;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\n\n@Schema(description = \"管理后台 - 定时任务创建/修改 Request VO\")\n@Data\npublic class JobSaveReqVO {\n\n    @Schema(description = \"任务编号\", example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"任务名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"测试任务\")\n    @NotEmpty(message = \"任务名称不能为空\")\n    private String name;\n\n    @Schema(description = \"处理器的名字\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"sysUserSessionTimeoutJob\")\n    @NotEmpty(message = \"处理器的名字不能为空\")\n    private String handlerName;\n\n    @Schema(description = \"处理器的参数\", example = \"yshop\")\n    private String handlerParam;\n\n    @Schema(description = \"CRON 表达式\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"0/10 * * * * ? *\")\n    @NotEmpty(message = \"CRON 表达式不能为空\")\n    private String cronExpression;\n\n    @Schema(description = \"重试次数\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"3\")\n    @NotNull(message = \"重试次数不能为空\")\n    private Integer retryCount;\n\n    @Schema(description = \"重试间隔\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1000\")\n    @NotNull(message = \"重试间隔不能为空\")\n    private Integer retryInterval;\n\n    @Schema(description = \"监控超时时间\", example = \"1000\")\n    private Integer monitorTimeout;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/job/vo/log/JobLogPageReqVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.job.vo.log;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 定时任务日志分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class JobLogPageReqVO extends PageParam {\n\n    @Schema(description = \"任务编号\", example = \"10\")\n    private Long jobId;\n\n    @Schema(description = \"处理器的名字，模糊匹配\")\n    private String handlerName;\n\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    @Schema(description = \"开始执行时间\")\n    private LocalDateTime beginTime;\n\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    @Schema(description = \"结束执行时间\")\n    private LocalDateTime endTime;\n\n    @Schema(description = \"任务状态，参见 JobLogStatusEnum 枚举\")\n    private Integer status;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/job/vo/log/JobLogRespVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.job.vo.log;\n\nimport co.yixiang.yshop.framework.excel.core.annotations.DictFormat;\nimport co.yixiang.yshop.framework.excel.core.convert.DictConvert;\nimport co.yixiang.yshop.module.infra.enums.DictTypeConstants;\nimport com.alibaba.excel.annotation.ExcelIgnoreUnannotated;\nimport com.alibaba.excel.annotation.ExcelProperty;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 定时任务日志 Response VO\")\n@Data\n@ExcelIgnoreUnannotated\npublic class JobLogRespVO {\n\n    @Schema(description = \"日志编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @ExcelProperty(\"日志编号\")\n    private Long id;\n\n    @Schema(description = \"任务编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @ExcelProperty(\"任务编号\")\n    private Long jobId;\n\n    @Schema(description = \"处理器的名字\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"sysUserSessionTimeoutJob\")\n    @ExcelProperty(\"处理器的名字\")\n    private String handlerName;\n\n    @Schema(description = \"处理器的参数\", example = \"yshop\")\n    @ExcelProperty(\"处理器的参数\")\n    private String handlerParam;\n\n    @Schema(description = \"第几次执行\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @ExcelProperty(\"第几次执行\")\n    private Integer executeIndex;\n\n    @Schema(description = \"开始执行时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @ExcelProperty(\"开始执行时间\")\n    private LocalDateTime beginTime;\n\n    @Schema(description = \"结束执行时间\")\n    @ExcelProperty(\"结束执行时间\")\n    private LocalDateTime endTime;\n\n    @Schema(description = \"执行时长\", example = \"123\")\n    @ExcelProperty(\"执行时长\")\n    private Integer duration;\n\n    @Schema(description = \"任务状态，参见 JobLogStatusEnum 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @ExcelProperty(value = \"任务状态\", converter = DictConvert.class)\n    @DictFormat(DictTypeConstants.JOB_STATUS)\n    private Integer status;\n\n    @Schema(description = \"结果数据\", example = \"执行成功\")\n    @ExcelProperty(\"结果数据\")\n    private String result;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @ExcelProperty(\"创建时间\")\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/logger/ApiAccessLogController.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.logger;\n\nimport co.yixiang.yshop.framework.apilog.core.annotation.ApiAccessLog;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.framework.excel.core.util.ExcelUtils;\nimport co.yixiang.yshop.module.infra.controller.admin.logger.vo.apiaccesslog.ApiAccessLogPageReqVO;\nimport co.yixiang.yshop.module.infra.controller.admin.logger.vo.apiaccesslog.ApiAccessLogRespVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.logger.ApiAccessLogDO;\nimport co.yixiang.yshop.module.infra.service.logger.ApiAccessLogService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport jakarta.annotation.Resource;\nimport jakarta.servlet.http.HttpServletResponse;\nimport jakarta.validation.Valid;\nimport java.io.IOException;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.apilog.core.enums.OperateTypeEnum.EXPORT;\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - API 访问日志\")\n@RestController\n@RequestMapping(\"/infra/api-access-log\")\n@Validated\npublic class ApiAccessLogController {\n\n    @Resource\n    private ApiAccessLogService apiAccessLogService;\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得API 访问日志分页\")\n    @PreAuthorize(\"@ss.hasPermission('infra:api-access-log:query')\")\n    public CommonResult<PageResult<ApiAccessLogRespVO>> getApiAccessLogPage(@Valid ApiAccessLogPageReqVO pageReqVO) {\n        PageResult<ApiAccessLogDO> pageResult = apiAccessLogService.getApiAccessLogPage(pageReqVO);\n        return success(BeanUtils.toBean(pageResult, ApiAccessLogRespVO.class));\n    }\n\n    @GetMapping(\"/export-excel\")\n    @Operation(summary = \"导出API 访问日志 Excel\")\n    @PreAuthorize(\"@ss.hasPermission('infra:api-access-log:export')\")\n    @ApiAccessLog(operateType = EXPORT)\n    public void exportApiAccessLogExcel(@Valid ApiAccessLogPageReqVO exportReqVO,\n                                        HttpServletResponse response) throws IOException {\n        exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);\n        List<ApiAccessLogDO> list = apiAccessLogService.getApiAccessLogPage(exportReqVO).getList();\n        // 导出 Excel\n        ExcelUtils.write(response, \"API 访问日志.xls\", \"数据\", ApiAccessLogRespVO.class,\n                BeanUtils.toBean(list, ApiAccessLogRespVO.class));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/logger/ApiErrorLogController.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.logger;\n\nimport co.yixiang.yshop.framework.apilog.core.annotation.ApiAccessLog;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.framework.excel.core.util.ExcelUtils;\nimport co.yixiang.yshop.module.infra.controller.admin.logger.vo.apierrorlog.ApiErrorLogPageReqVO;\nimport co.yixiang.yshop.module.infra.controller.admin.logger.vo.apierrorlog.ApiErrorLogRespVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.logger.ApiErrorLogDO;\nimport co.yixiang.yshop.module.infra.service.logger.ApiErrorLogService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.Parameters;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport jakarta.annotation.Resource;\nimport jakarta.servlet.http.HttpServletResponse;\nimport jakarta.validation.Valid;\nimport java.io.IOException;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.apilog.core.enums.OperateTypeEnum.EXPORT;\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\nimport static co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;\n\n@Tag(name = \"管理后台 - API 错误日志\")\n@RestController\n@RequestMapping(\"/infra/api-error-log\")\n@Validated\npublic class ApiErrorLogController {\n\n    @Resource\n    private ApiErrorLogService apiErrorLogService;\n\n    @PutMapping(\"/update-status\")\n    @Operation(summary = \"更新 API 错误日志的状态\")\n    @Parameters({\n            @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\"),\n            @Parameter(name = \"processStatus\", description = \"处理状态\", required = true, example = \"1\")\n    })\n    @PreAuthorize(\"@ss.hasPermission('infra:api-error-log:update-status')\")\n    public CommonResult<Boolean> updateApiErrorLogProcess(@RequestParam(\"id\") Long id,\n                                                          @RequestParam(\"processStatus\") Integer processStatus) {\n        apiErrorLogService.updateApiErrorLogProcess(id, processStatus, getLoginUserId());\n        return success(true);\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得 API 错误日志分页\")\n    @PreAuthorize(\"@ss.hasPermission('infra:api-error-log:query')\")\n    public CommonResult<PageResult<ApiErrorLogRespVO>> getApiErrorLogPage(@Valid ApiErrorLogPageReqVO pageReqVO) {\n        PageResult<ApiErrorLogDO> pageResult = apiErrorLogService.getApiErrorLogPage(pageReqVO);\n        return success(BeanUtils.toBean(pageResult, ApiErrorLogRespVO.class));\n    }\n\n    @GetMapping(\"/export-excel\")\n    @Operation(summary = \"导出 API 错误日志 Excel\")\n    @PreAuthorize(\"@ss.hasPermission('infra:api-error-log:export')\")\n    @ApiAccessLog(operateType = EXPORT)\n    public void exportApiErrorLogExcel(@Valid ApiErrorLogPageReqVO exportReqVO,\n              HttpServletResponse response) throws IOException {\n        exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);\n        List<ApiErrorLogDO> list = apiErrorLogService.getApiErrorLogPage(exportReqVO).getList();\n        // 导出 Excel\n        ExcelUtils.write(response, \"API 错误日志.xls\", \"数据\", ApiErrorLogRespVO.class,\n                BeanUtils.toBean(list, ApiErrorLogRespVO.class));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/logger/vo/apiaccesslog/ApiAccessLogPageReqVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.logger.vo.apiaccesslog;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - API 访问日志分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class ApiAccessLogPageReqVO extends PageParam {\n\n    @Schema(description = \"用户编号\", example = \"666\")\n    private Long userId;\n\n    @Schema(description = \"用户类型\", example = \"2\")\n    private Integer userType;\n\n    @Schema(description = \"应用名\", example = \"dashboard\")\n    private String applicationName;\n\n    @Schema(description = \"请求地址，模糊匹配\", example = \"/xxx/yyy\")\n    private String requestUrl;\n\n    @Schema(description = \"开始时间\", example = \"[2022-07-01 00:00:00, 2022-07-01 23:59:59]\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] beginTime;\n\n    @Schema(description = \"执行时长,大于等于，单位：毫秒\", example = \"100\")\n    private Integer duration;\n\n    @Schema(description = \"结果码\", example = \"0\")\n    private Integer resultCode;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/logger/vo/apiaccesslog/ApiAccessLogRespVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.logger.vo.apiaccesslog;\n\nimport co.yixiang.yshop.framework.excel.core.annotations.DictFormat;\nimport co.yixiang.yshop.framework.excel.core.convert.DictConvert;\nimport co.yixiang.yshop.module.system.enums.DictTypeConstants;\nimport com.alibaba.excel.annotation.ExcelIgnoreUnannotated;\nimport com.alibaba.excel.annotation.ExcelProperty;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - API 访问日志 Response VO\")\n@Data\n@ExcelIgnoreUnannotated\npublic class ApiAccessLogRespVO {\n\n    @Schema(description = \"日志主键\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @ExcelProperty(\"日志主键\")\n    private Long id;\n\n    @Schema(description = \"链路追踪编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"66600cb6-7852-11eb-9439-0242ac130002\")\n    @ExcelProperty(\"链路追踪编号\")\n    private String traceId;\n\n    @Schema(description = \"用户编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"666\")\n    @ExcelProperty(\"用户编号\")\n    private Long userId;\n\n    @Schema(description = \"用户类型，参见 UserTypeEnum 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"2\")\n    @ExcelProperty(value = \"用户类型\", converter = DictConvert.class)\n    @DictFormat(DictTypeConstants.USER_TYPE)\n    private Integer userType;\n\n    @Schema(description = \"应用名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"dashboard\")\n    @ExcelProperty(\"应用名\")\n    private String applicationName;\n\n    @Schema(description = \"请求方法名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"GET\")\n    @ExcelProperty(\"请求方法名\")\n    private String requestMethod;\n\n    @Schema(description = \"请求地址\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"/xxx/yyy\")\n    @ExcelProperty(\"请求地址\")\n    private String requestUrl;\n\n    @Schema(description = \"请求参数\")\n    @ExcelProperty(\"请求参数\")\n    private String requestParams;\n\n    @Schema(description = \"响应结果\")\n    @ExcelProperty(\"响应结果\")\n    private String responseBody;\n\n    @Schema(description = \"用户 IP\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"127.0.0.1\")\n    @ExcelProperty(\"用户 IP\")\n    private String userIp;\n\n    @Schema(description = \"浏览器 UA\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"Mozilla/5.0\")\n    @ExcelProperty(\"浏览器 UA\")\n    private String userAgent;\n\n    @Schema(description = \"操作模块\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"商品模块\")\n    @ExcelProperty(\"操作模块\")\n    private String operateModule;\n\n    @Schema(description = \"操作名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"创建商品\")\n    @ExcelProperty(\"操作名\")\n    private String operateName;\n\n    @Schema(description = \"操作分类\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @ExcelProperty(value = \"操作分类\", converter = DictConvert.class)\n    @DictFormat(co.yixiang.yshop.module.infra.enums.DictTypeConstants.OPERATE_TYPE)\n    private Integer operateType;\n\n    @Schema(description = \"开始请求时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @ExcelProperty(\"开始请求时间\")\n    private LocalDateTime beginTime;\n\n    @Schema(description = \"结束请求时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @ExcelProperty(\"结束请求时间\")\n    private LocalDateTime endTime;\n\n    @Schema(description = \"执行时长\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"100\")\n    @ExcelProperty(\"执行时长\")\n    private Integer duration;\n\n    @Schema(description = \"结果码\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"0\")\n    @ExcelProperty(\"结果码\")\n    private Integer resultCode;\n\n    @Schema(description = \"结果提示\", example = \"yshop，牛逼！\")\n    @ExcelProperty(\"结果提示\")\n    private String resultMsg;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/logger/vo/apierrorlog/ApiErrorLogPageReqVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.logger.vo.apierrorlog;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - API 错误日志分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class ApiErrorLogPageReqVO extends PageParam {\n\n    @Schema(description = \"用户编号\", example = \"666\")\n    private Long userId;\n\n    @Schema(description = \"用户类型\", example = \"1\")\n    private Integer userType;\n\n    @Schema(description = \"应用名\", example = \"dashboard\")\n    private String applicationName;\n\n    @Schema(description = \"请求地址\", example = \"/xx/yy\")\n    private String requestUrl;\n\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    @Schema(description = \"异常发生时间\")\n    private LocalDateTime[] exceptionTime;\n\n    @Schema(description = \"处理状态\", example = \"0\")\n    private Integer processStatus;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/logger/vo/apierrorlog/ApiErrorLogRespVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.logger.vo.apierrorlog;\n\nimport co.yixiang.yshop.framework.excel.core.annotations.DictFormat;\nimport co.yixiang.yshop.framework.excel.core.convert.DictConvert;\nimport co.yixiang.yshop.module.infra.enums.DictTypeConstants;\nimport com.alibaba.excel.annotation.ExcelIgnoreUnannotated;\nimport com.alibaba.excel.annotation.ExcelProperty;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - API 错误日志 Response VO\")\n@Data\n@ExcelIgnoreUnannotated\npublic class ApiErrorLogRespVO {\n\n    @Schema(description = \"编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @ExcelProperty(\"编号\")\n    private Integer id;\n\n    @Schema(description = \"链路追踪编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"66600cb6-7852-11eb-9439-0242ac130002\")\n    @ExcelProperty(\"链路追踪编号\")\n    private String traceId;\n\n    @Schema(description = \"用户编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"666\")\n    @ExcelProperty(\"用户编号\")\n    private Integer userId;\n\n    @Schema(description = \"用户类型\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @ExcelProperty(value = \"用户类型\", converter = DictConvert.class)\n    @DictFormat(co.yixiang.yshop.module.system.enums.DictTypeConstants.USER_TYPE)\n    private Integer userType;\n\n    @Schema(description = \"应用名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"dashboard\")\n    @ExcelProperty(\"应用名\")\n    private String applicationName;\n\n    @Schema(description = \"请求方法名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"GET\")\n    @ExcelProperty(\"请求方法名\")\n    private String requestMethod;\n\n    @Schema(description = \"请求地址\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"/xx/yy\")\n    @ExcelProperty(\"请求地址\")\n    private String requestUrl;\n\n    @Schema(description = \"请求参数\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @ExcelProperty(\"请求参数\")\n    private String requestParams;\n\n    @Schema(description = \"用户 IP\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"127.0.0.1\")\n    @ExcelProperty(\"用户 IP\")\n    private String userIp;\n\n    @Schema(description = \"浏览器 UA\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"Mozilla/5.0\")\n    @ExcelProperty(\"浏览器 UA\")\n    private String userAgent;\n\n    @Schema(description = \"异常发生时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @ExcelProperty(\"异常发生时间\")\n    private LocalDateTime exceptionTime;\n\n    @Schema(description = \"异常名\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @ExcelProperty(\"异常名\")\n    private String exceptionName;\n\n    @Schema(description = \"异常导致的消息\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @ExcelProperty(\"异常导致的消息\")\n    private String exceptionMessage;\n\n    @Schema(description = \"异常导致的根消息\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @ExcelProperty(\"异常导致的根消息\")\n    private String exceptionRootCauseMessage;\n\n    @Schema(description = \"异常的栈轨迹\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @ExcelProperty(\"异常的栈轨迹\")\n    private String exceptionStackTrace;\n\n    @Schema(description = \"异常发生的类全名\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @ExcelProperty(\"异常发生的类全名\")\n    private String exceptionClassName;\n\n    @Schema(description = \"异常发生的类文件\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @ExcelProperty(\"异常发生的类文件\")\n    private String exceptionFileName;\n\n    @Schema(description = \"异常发生的方法名\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @ExcelProperty(\"异常发生的方法名\")\n    private String exceptionMethodName;\n\n    @Schema(description = \"异常发生的方法所在行\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @ExcelProperty(\"异常发生的方法所在行\")\n    private Integer exceptionLineNumber;\n\n    @Schema(description = \"处理状态\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"0\")\n    @ExcelProperty(value = \"处理状态\", converter = DictConvert.class)\n    @DictFormat(DictTypeConstants.API_ERROR_LOG_PROCESS_STATUS)\n    private Integer processStatus;\n\n    @Schema(description = \"处理时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @ExcelProperty(\"处理时间\")\n    private LocalDateTime processTime;\n\n    @Schema(description = \"处理用户编号\", example = \"233\")\n    @ExcelProperty(\"处理用户编号\")\n    private Integer processUserId;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @ExcelProperty(\"创建时间\")\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/redis/RedisController.http",
    "content": "### 请求 /infra/redis/get-monitor-info 接口 => 成功\nGET {{baseUrl}}/infra/redis/get-monitor-info\nAuthorization: Bearer {{token}}\ntenant-id: {{adminTenentId}}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/redis/RedisController.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.redis;\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.module.infra.controller.admin.redis.vo.RedisMonitorRespVO;\nimport co.yixiang.yshop.module.infra.convert.redis.RedisConvert;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport org.springframework.data.redis.connection.RedisServerCommands;\nimport org.springframework.data.redis.core.RedisCallback;\nimport org.springframework.data.redis.core.StringRedisTemplate;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport jakarta.annotation.Resource;\nimport java.util.Properties;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - Redis 监控\")\n@RestController\n@RequestMapping(\"/infra/redis\")\npublic class RedisController {\n\n    @Resource\n    private StringRedisTemplate stringRedisTemplate;\n\n    @GetMapping(\"/get-monitor-info\")\n    @Operation(summary = \"获得 Redis 监控信息\")\n    @PreAuthorize(\"@ss.hasPermission('infra:redis:get-monitor-info')\")\n    public CommonResult<RedisMonitorRespVO> getRedisMonitorInfo() {\n        // 获得 Redis 统计信息\n        Properties info = stringRedisTemplate.execute((RedisCallback<Properties>) RedisServerCommands::info);\n        Long dbSize = stringRedisTemplate.execute(RedisServerCommands::dbSize);\n        Properties commandStats = stringRedisTemplate.execute((\n                RedisCallback<Properties>) connection -> connection.serverCommands().info(\"commandstats\"));\n        assert commandStats != null; // 断言，避免警告\n        // 拼接结果返回\n        return success(RedisConvert.INSTANCE.build(info, dbSize, commandStats));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/admin/redis/vo/RedisMonitorRespVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.admin.redis.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\n\nimport java.util.List;\nimport java.util.Properties;\n\n@Schema(description = \"管理后台 - Redis 监控信息 Response VO\")\n@Data\n@Builder\n@AllArgsConstructor\npublic class RedisMonitorRespVO {\n\n    @Schema(description = \"Redis info 指令结果,具体字段，查看 Redis 文档\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private Properties info;\n\n    @Schema(description = \"Redis key 数量\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Long dbSize;\n\n    @Schema(description = \"CommandStat 数组\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private List<CommandStat> commandStats;\n\n    @Schema(description = \"Redis 命令统计结果\")\n    @Data\n    @Builder\n    @AllArgsConstructor\n    public static class CommandStat {\n\n        @Schema(description = \"Redis 命令\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"get\")\n        private String command;\n\n        @Schema(description = \"调用次数\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n        private Long calls;\n\n        @Schema(description = \"消耗 CPU 秒数\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"666\")\n        private Long usec;\n\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/app/file/AppFileController.java",
    "content": "package co.yixiang.yshop.module.infra.controller.app.file;\n\nimport cn.hutool.core.io.IoUtil;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.module.infra.controller.app.file.vo.AppFileUploadReqVO;\nimport co.yixiang.yshop.module.infra.service.file.FileService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.PostMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\nimport org.springframework.web.multipart.MultipartFile;\n\nimport jakarta.annotation.Resource;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"用户 App - 文件存储\")\n@RestController\n@RequestMapping(\"/infra/file\")\n@Validated\n@Slf4j\npublic class AppFileController {\n\n    @Resource\n    private FileService fileService;\n\n    @PostMapping(\"/upload\")\n    @Operation(summary = \"上传文件\")\n    public CommonResult<String> uploadFile(AppFileUploadReqVO uploadReqVO) throws Exception {\n        MultipartFile file = uploadReqVO.getFile();\n        String path = uploadReqVO.getPath();\n        return success(fileService.createFile(file.getOriginalFilename(), path, IoUtil.readBytes(file.getInputStream())));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/controller/app/file/vo/AppFileUploadReqVO.java",
    "content": "package co.yixiang.yshop.module.infra.controller.app.file.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport org.springframework.web.multipart.MultipartFile;\n\nimport jakarta.validation.constraints.NotNull;\n\n@Schema(description = \"用户 App - 上传文件 Request VO\")\n@Data\npublic class AppFileUploadReqVO {\n\n    @Schema(description = \"文件附件\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @NotNull(message = \"文件附件不能为空\")\n    private MultipartFile file;\n\n    @Schema(description = \"文件附件\", example = \"yshopyuanma.png\")\n    private String path;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/convert/codegen/CodegenConvert.java",
    "content": "package co.yixiang.yshop.module.infra.convert.codegen;\n\nimport co.yixiang.yshop.framework.common.util.collection.CollectionUtils;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.infra.controller.admin.codegen.vo.CodegenDetailRespVO;\nimport co.yixiang.yshop.module.infra.controller.admin.codegen.vo.CodegenPreviewRespVO;\nimport co.yixiang.yshop.module.infra.controller.admin.codegen.vo.column.CodegenColumnRespVO;\nimport co.yixiang.yshop.module.infra.controller.admin.codegen.vo.table.CodegenTableRespVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.codegen.CodegenColumnDO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.codegen.CodegenTableDO;\nimport com.baomidou.mybatisplus.generator.config.po.TableField;\nimport com.baomidou.mybatisplus.generator.config.po.TableInfo;\nimport org.apache.ibatis.type.JdbcType;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.Mapping;\nimport org.mapstruct.Mappings;\nimport org.mapstruct.Named;\nimport org.mapstruct.factory.Mappers;\n\nimport java.util.List;\nimport java.util.Map;\n\n@Mapper\npublic interface CodegenConvert {\n\n    CodegenConvert INSTANCE = Mappers.getMapper(CodegenConvert.class);\n\n    // ========== TableInfo 相关 ==========\n\n    @Mappings({\n            @Mapping(source = \"name\", target = \"tableName\"),\n            @Mapping(source = \"comment\", target = \"tableComment\"),\n    })\n    CodegenTableDO convert(TableInfo bean);\n\n    List<CodegenColumnDO> convertList(List<TableField> list);\n\n    @Mappings({\n            @Mapping(source = \"name\", target = \"columnName\"),\n            @Mapping(source = \"metaInfo.jdbcType\", target = \"dataType\", qualifiedByName = \"getDataType\"),\n            @Mapping(source = \"comment\", target = \"columnComment\"),\n            @Mapping(source = \"metaInfo.nullable\", target = \"nullable\"),\n            @Mapping(source = \"keyFlag\", target = \"primaryKey\"),\n            @Mapping(source = \"columnType.type\", target = \"javaType\"),\n            @Mapping(source = \"propertyName\", target = \"javaField\"),\n    })\n    CodegenColumnDO convert(TableField bean);\n\n    @Named(\"getDataType\")\n    default String getDataType(JdbcType jdbcType) {\n        return jdbcType.name();\n    }\n\n    // ========== 其它 ==========\n\n    default CodegenDetailRespVO convert(CodegenTableDO table, List<CodegenColumnDO> columns) {\n        CodegenDetailRespVO respVO = new CodegenDetailRespVO();\n        respVO.setTable(BeanUtils.toBean(table, CodegenTableRespVO.class));\n        respVO.setColumns(BeanUtils.toBean(columns, CodegenColumnRespVO.class));\n        return respVO;\n    }\n\n    default List<CodegenPreviewRespVO> convert(Map<String, String> codes) {\n        return CollectionUtils.convertList(codes.entrySet(),\n                entry -> new CodegenPreviewRespVO().setFilePath(entry.getKey()).setCode(entry.getValue()));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/convert/config/ConfigConvert.java",
    "content": "package co.yixiang.yshop.module.infra.convert.config;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.infra.controller.admin.config.vo.ConfigRespVO;\nimport co.yixiang.yshop.module.infra.controller.admin.config.vo.ConfigSaveReqVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.config.ConfigDO;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.Mapping;\nimport org.mapstruct.factory.Mappers;\n\nimport java.util.List;\n\n@Mapper\npublic interface ConfigConvert {\n\n    ConfigConvert INSTANCE = Mappers.getMapper(ConfigConvert.class);\n\n    PageResult<ConfigRespVO> convertPage(PageResult<ConfigDO> page);\n\n    List<ConfigRespVO> convertList(List<ConfigDO> list);\n\n    @Mapping(source = \"configKey\", target = \"key\")\n    ConfigRespVO convert(ConfigDO bean);\n\n    @Mapping(source = \"key\", target = \"configKey\")\n    ConfigDO convert(ConfigSaveReqVO bean);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/convert/file/FileConfigConvert.java",
    "content": "package co.yixiang.yshop.module.infra.convert.file;\n\nimport co.yixiang.yshop.module.infra.controller.admin.file.vo.config.FileConfigSaveReqVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.file.FileConfigDO;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.Mapping;\nimport org.mapstruct.factory.Mappers;\n\n/**\n * 文件配置 Convert\n *\n * @author yshop\n */\n@Mapper\npublic interface FileConfigConvert {\n\n    FileConfigConvert INSTANCE = Mappers.getMapper(FileConfigConvert.class);\n\n    @Mapping(target = \"config\", ignore = true)\n    FileConfigDO convert(FileConfigSaveReqVO bean);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/convert/redis/RedisConvert.java",
    "content": "package co.yixiang.yshop.module.infra.convert.redis;\n\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.module.infra.controller.admin.redis.vo.RedisMonitorRespVO;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.factory.Mappers;\n\nimport java.util.ArrayList;\nimport java.util.Properties;\n\n@Mapper\npublic interface RedisConvert {\n\n    RedisConvert INSTANCE = Mappers.getMapper(RedisConvert.class);\n\n    default RedisMonitorRespVO build(Properties info, Long dbSize, Properties commandStats) {\n        RedisMonitorRespVO respVO = RedisMonitorRespVO.builder().info(info).dbSize(dbSize)\n                .commandStats(new ArrayList<>(commandStats.size())).build();\n        commandStats.forEach((key, value) -> {\n            respVO.getCommandStats().add(RedisMonitorRespVO.CommandStat.builder()\n                    .command(StrUtil.subAfter((String) key, \"cmdstat_\", false))\n                    .calls(Long.valueOf(StrUtil.subBetween((String) value, \"calls=\", \",\")))\n                    .usec(Long.valueOf(StrUtil.subBetween((String) value, \"usec=\", \",\")))\n                    .build());\n        });\n        return respVO;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/dal/dataobject/codegen/CodegenColumnDO.java",
    "content": "package co.yixiang.yshop.module.infra.dal.dataobject.codegen;\n\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport co.yixiang.yshop.module.infra.enums.codegen.CodegenColumnHtmlTypeEnum;\nimport co.yixiang.yshop.module.infra.enums.codegen.CodegenColumnListConditionEnum;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport com.baomidou.mybatisplus.generator.config.po.TableField;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\n/**\n * 代码生成 column 字段定义\n *\n * @author yshop\n */\n@TableName(value = \"infra_codegen_column\", autoResultMap = true)\n@KeySequence(\"infra_codegen_column_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@Accessors(chain = true)\n@EqualsAndHashCode(callSuper = true)\npublic class CodegenColumnDO extends BaseDO {\n\n    /**\n     * ID 编号\n     */\n    @TableId\n    private Long id;\n    /**\n     * 表编号\n     * <p>\n     * 关联 {@link CodegenTableDO#getId()}\n     */\n    private Long tableId;\n\n    // ========== 表相关字段 ==========\n\n    /**\n     * 字段名\n     *\n     * 关联 {@link TableField#getName()}\n     */\n    private String columnName;\n    /**\n     * 数据库字段类型\n     *\n     * 关联 {@link TableField.MetaInfo#getJdbcType()}\n     */\n    private String dataType;\n    /**\n     * 字段描述\n     *\n     * 关联 {@link TableField#getComment()}\n     */\n    private String columnComment;\n    /**\n     * 是否允许为空\n     *\n     * 关联 {@link TableField.MetaInfo#isNullable()}\n     */\n    private Boolean nullable;\n    /**\n     * 是否主键\n     *\n     * 关联 {@link TableField#isKeyFlag()}\n     */\n    private Boolean primaryKey;\n    /**\n     * 排序\n     */\n    private Integer ordinalPosition;\n\n    // ========== Java 相关字段 ==========\n\n    /**\n     * Java 属性类型\n     *\n     * 例如说 String、Boolean 等等\n     *\n     * 关联 {@link TableField#getColumnType()}\n     */\n    private String javaType;\n    /**\n     * Java 属性名\n     *\n     * 关联 {@link TableField#getPropertyName()}\n     */\n    private String javaField;\n    /**\n     * 字典类型\n     * <p>\n     * 关联 DictTypeDO 的 type 属性\n     */\n    private String dictType;\n    /**\n     * 数据示例，主要用于生成 Swagger 注解的 example 字段\n     */\n    private String example;\n\n    // ========== CRUD 相关字段 ==========\n\n    /**\n     * 是否为 Create 创建操作的字段\n     */\n    private Boolean createOperation;\n    /**\n     * 是否为 Update 更新操作的字段\n     */\n    private Boolean updateOperation;\n    /**\n     * 是否为 List 查询操作的字段\n     */\n    private Boolean listOperation;\n    /**\n     * List 查询操作的条件类型\n     * <p>\n     * 枚举 {@link CodegenColumnListConditionEnum}\n     */\n    private String listOperationCondition;\n    /**\n     * 是否为 List 查询操作的返回字段\n     */\n    private Boolean listOperationResult;\n\n    // ========== UI 相关字段 ==========\n\n    /**\n     * 显示类型\n     * <p>\n     * 枚举 {@link CodegenColumnHtmlTypeEnum}\n     */\n    private String htmlType;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/dal/dataobject/codegen/CodegenTableDO.java",
    "content": "package co.yixiang.yshop.module.infra.dal.dataobject.codegen;\n\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.db.DataSourceConfigDO;\nimport co.yixiang.yshop.module.infra.enums.codegen.CodegenFrontTypeEnum;\nimport co.yixiang.yshop.module.infra.enums.codegen.CodegenSceneEnum;\nimport co.yixiang.yshop.module.infra.enums.codegen.CodegenTemplateTypeEnum;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport com.baomidou.mybatisplus.generator.config.po.TableInfo;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\n/**\n * 代码生成 table 表定义\n *\n * @author yshop\n */\n@TableName(value = \"infra_codegen_table\", autoResultMap = true)\n@KeySequence(\"infra_codegen_table_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@Accessors(chain = true)\n@EqualsAndHashCode(callSuper = true)\npublic class CodegenTableDO extends BaseDO {\n\n    /**\n     * ID 编号\n     */\n    @TableId\n    private Long id;\n\n    /**\n     * 数据源编号\n     *\n     * 关联 {@link DataSourceConfigDO#getId()}\n     */\n    private Long dataSourceConfigId;\n    /**\n     * 生成场景\n     *\n     * 枚举 {@link CodegenSceneEnum}\n     */\n    private Integer scene;\n\n    // ========== 表相关字段 ==========\n\n    /**\n     * 表名称\n     *\n     * 关联 {@link TableInfo#getName()}\n     */\n    private String tableName;\n    /**\n     * 表描述\n     *\n     * 关联 {@link TableInfo#getComment()}\n     */\n    private String tableComment;\n    /**\n     * 备注\n     */\n    private String remark;\n\n    // ========== 类相关字段 ==========\n\n    /**\n     * 模块名，即一级目录\n     *\n     * 例如说，system、infra、tool 等等\n     */\n    private String moduleName;\n    /**\n     * 业务名，即二级目录\n     *\n     * 例如说，user、permission、dict 等等\n     */\n    private String businessName;\n    /**\n     * 类名称（首字母大写）\n     *\n     * 例如说，SysUser、SysMenu、SysDictData 等等\n     */\n    private String className;\n    /**\n     * 类描述\n     */\n    private String classComment;\n    /**\n     * 作者\n     */\n    private String author;\n\n    // ========== 生成相关字段 ==========\n\n    /**\n     * 模板类型\n     *\n     * 枚举 {@link CodegenTemplateTypeEnum}\n     */\n    private Integer templateType;\n    /**\n     * 代码生成的前端类型\n     *\n     * 枚举 {@link CodegenFrontTypeEnum}\n     */\n    private Integer frontType;\n\n    // ========== 菜单相关字段 ==========\n\n    /**\n     * 父菜单编号\n     *\n     * 关联 MenuDO 的 id 属性\n     */\n    private Long parentMenuId;\n\n    // ========== 主子表相关字段 ==========\n\n    /**\n     * 主表的编号\n     *\n     * 关联 {@link CodegenTableDO#getId()}\n     */\n    private Long masterTableId;\n    /**\n     * 【自己】子表关联主表的字段编号\n     *\n     * 关联 {@link CodegenColumnDO#getId()}\n     */\n    private Long subJoinColumnId;\n    /**\n     * 主表与子表是否一对多\n     *\n     * true：一对多\n     * false：一对一\n     */\n    private Boolean subJoinMany;\n\n    // ========== 树表相关字段 ==========\n\n    /**\n     * 树表的父字段编号\n     *\n     * 关联 {@link CodegenColumnDO#getId()}\n     */\n    private Long treeParentColumnId;\n    /**\n     * 树表的名字字段编号\n     *\n     * 名字的用途：新增或修改时，select 框展示的字段\n     *\n     * 关联 {@link CodegenColumnDO#getId()}\n     */\n    private Long treeNameColumnId;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/dal/dataobject/config/ConfigDO.java",
    "content": "package co.yixiang.yshop.module.infra.dal.dataobject.config;\n\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport co.yixiang.yshop.module.infra.enums.config.ConfigTypeEnum;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\n\n/**\n * 参数配置表\n *\n * @author yshop\n */\n@TableName(\"infra_config\")\n@KeySequence(\"infra_config_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class ConfigDO extends BaseDO {\n\n    /**\n     * 参数主键\n     */\n    @TableId\n    private Long id;\n    /**\n     * 参数分类\n     */\n    private String category;\n    /**\n     * 参数名称\n     */\n    private String name;\n    /**\n     * 参数键名\n     *\n     * 支持多 DB 类型时，无法直接使用 key + @TableField(\"config_key\") 来实现转换，原因是 \"config_key\" AS key 而存在报错\n     */\n    private String configKey;\n    /**\n     * 参数键值\n     */\n    private String value;\n    /**\n     * 参数类型\n     *\n     * 枚举 {@link ConfigTypeEnum}\n     */\n    private Integer type;\n    /**\n     * 是否可见\n     *\n     * 不可见的参数，一般是敏感参数，前端不可获取\n     */\n    private Boolean visible;\n    /**\n     * 备注\n     */\n    private String remark;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/dal/dataobject/db/DataSourceConfigDO.java",
    "content": "package co.yixiang.yshop.module.infra.dal.dataobject.db;\n\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport co.yixiang.yshop.framework.mybatis.core.type.EncryptTypeHandler;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.Data;\n\n/**\n * 数据源配置\n *\n * @author yshop\n */\n@TableName(value = \"infra_data_source_config\", autoResultMap = true)\n@KeySequence(\"infra_data_source_config_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\npublic class DataSourceConfigDO extends BaseDO {\n\n    /**\n     * 主键编号 - Master 数据源\n     */\n    public static final Long ID_MASTER = 0L;\n\n    /**\n     * 主键编号\n     */\n    private Long id;\n    /**\n     * 连接名\n     */\n    private String name;\n\n    /**\n     * 数据源连接\n     */\n    private String url;\n    /**\n     * 用户名\n     */\n    private String username;\n    /**\n     * 密码\n     */\n    @TableField(typeHandler = EncryptTypeHandler.class)\n    private String password;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/dal/dataobject/demo/demo01/Demo01ContactDO.java",
    "content": "package co.yixiang.yshop.module.infra.dal.dataobject.demo.demo01;\n\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.*;\n\nimport java.time.LocalDateTime;\n\n/**\n * 示例联系人 DO\n *\n * @author yshop\n */\n@TableName(\"yshop_demo01_contact\")\n@KeySequence(\"yshop_demo01_contact_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class Demo01ContactDO extends BaseDO {\n\n    /**\n     * 编号\n     */\n    @TableId\n    private Long id;\n    /**\n     * 名字\n     */\n    private String name;\n    /**\n     * 性别\n     *\n     * 枚举 {@link TODO system_user_sex 对应的类}\n     */\n    private Integer sex;\n    /**\n     * 出生年\n     */\n    private LocalDateTime birthday;\n    /**\n     * 简介\n     */\n    private String description;\n    /**\n     * 头像\n     */\n    private String avatar;\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/dal/dataobject/demo/demo02/Demo02CategoryDO.java",
    "content": "package co.yixiang.yshop.module.infra.dal.dataobject.demo.demo02;\n\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.*;\n\n/**\n * 示例分类 DO\n *\n * @author yshop\n */\n@TableName(\"yshop_demo02_category\")\n@KeySequence(\"yshop_demo02_category_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class Demo02CategoryDO extends BaseDO {\n\n    public static final Long PARENT_ID_ROOT = 0L;\n\n    /**\n     * 编号\n     */\n    @TableId\n    private Long id;\n    /**\n     * 名字\n     */\n    private String name;\n    /**\n     * 父级编号\n     */\n    private Long parentId;\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/dal/dataobject/demo/demo03/Demo03CourseDO.java",
    "content": "package co.yixiang.yshop.module.infra.dal.dataobject.demo.demo03;\n\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.*;\n\n/**\n * 学生课程 DO\n *\n * @author yshop\n */\n@TableName(\"yshop_demo03_course\")\n@KeySequence(\"yshop_demo03_course_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class Demo03CourseDO extends BaseDO {\n\n    /**\n     * 编号\n     */\n    @TableId\n    private Long id;\n    /**\n     * 学生编号\n     */\n    private Long studentId;\n    /**\n     * 名字\n     */\n    private String name;\n    /**\n     * 分数\n     */\n    private Integer score;\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/dal/dataobject/demo/demo03/Demo03GradeDO.java",
    "content": "package co.yixiang.yshop.module.infra.dal.dataobject.demo.demo03;\n\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.*;\n\n/**\n * 学生班级 DO\n *\n * @author yshop\n */\n@TableName(\"yshop_demo03_grade\")\n@KeySequence(\"yshop_demo03_grade_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class Demo03GradeDO extends BaseDO {\n\n    /**\n     * 编号\n     */\n    @TableId\n    private Long id;\n    /**\n     * 学生编号\n     */\n    private Long studentId;\n    /**\n     * 名字\n     */\n    private String name;\n    /**\n     * 班主任\n     */\n    private String teacher;\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/dal/dataobject/demo/demo03/Demo03StudentDO.java",
    "content": "package co.yixiang.yshop.module.infra.dal.dataobject.demo.demo03;\n\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.*;\n\nimport java.time.LocalDateTime;\n\n/**\n * 学生 DO\n *\n * @author yshop\n */\n@TableName(\"yshop_demo03_student\")\n@KeySequence(\"yshop_demo03_student_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class Demo03StudentDO extends BaseDO {\n\n    /**\n     * 编号\n     */\n    @TableId\n    private Long id;\n    /**\n     * 名字\n     */\n    private String name;\n    /**\n     * 性别\n     *\n     * 枚举 {@link TODO system_user_sex 对应的类}\n     */\n    private Integer sex;\n    /**\n     * 出生日期\n     */\n    private LocalDateTime birthday;\n    /**\n     * 简介\n     */\n    private String description;\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/dal/dataobject/file/FileConfigDO.java",
    "content": "package co.yixiang.yshop.module.infra.dal.dataobject.file;\n\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.util.json.JsonUtils;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport co.yixiang.yshop.module.infra.framework.file.core.client.FileClientConfig;\nimport co.yixiang.yshop.module.infra.framework.file.core.client.db.DBFileClientConfig;\nimport co.yixiang.yshop.module.infra.framework.file.core.client.ftp.FtpFileClientConfig;\nimport co.yixiang.yshop.module.infra.framework.file.core.client.local.LocalFileClientConfig;\nimport co.yixiang.yshop.module.infra.framework.file.core.client.s3.S3FileClientConfig;\nimport co.yixiang.yshop.module.infra.framework.file.core.client.sftp.SftpFileClientConfig;\nimport co.yixiang.yshop.module.infra.framework.file.core.enums.FileStorageEnum;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;\nimport com.fasterxml.jackson.core.type.TypeReference;\nimport lombok.*;\n\n/**\n * 文件配置表\n *\n * @author yshop\n */\n@TableName(value = \"infra_file_config\", autoResultMap = true)\n@KeySequence(\"infra_file_config_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class FileConfigDO extends BaseDO {\n\n    /**\n     * 配置编号，数据库自增\n     */\n    private Long id;\n    /**\n     * 配置名\n     */\n    private String name;\n    /**\n     * 存储器\n     *\n     * 枚举 {@link FileStorageEnum}\n     */\n    private Integer storage;\n    /**\n     * 备注\n     */\n    private String remark;\n    /**\n     * 是否为主配置\n     *\n     * 由于我们可以配置多个文件配置，默认情况下，使用主配置进行文件的上传\n     */\n    private Boolean master;\n\n    /**\n     * 支付渠道配置\n     */\n    @TableField(typeHandler = FileClientConfigTypeHandler.class)\n    private FileClientConfig config;\n\n    public static class FileClientConfigTypeHandler extends AbstractJsonTypeHandler<Object> {\n\n        @Override\n        protected Object parse(String json) {\n            FileClientConfig config = JsonUtils.parseObjectQuietly(json, new TypeReference<>() {});\n            if (config != null) {\n                return config;\n            }\n\n            // 兼容老版本的包路径\n            String className = JsonUtils.parseObject(json, \"@class\", String.class);\n            className = StrUtil.subAfter(className, \".\", true);\n            switch (className) {\n                case \"DBFileClientConfig\":\n                    return JsonUtils.parseObject2(json, DBFileClientConfig.class);\n                case \"FtpFileClientConfig\":\n                    return JsonUtils.parseObject2(json, FtpFileClientConfig.class);\n                case \"LocalFileClientConfig\":\n                    return JsonUtils.parseObject2(json, LocalFileClientConfig.class);\n                case \"SftpFileClientConfig\":\n                    return JsonUtils.parseObject2(json, SftpFileClientConfig.class);\n                case \"S3FileClientConfig\":\n                    return JsonUtils.parseObject2(json, S3FileClientConfig.class);\n                default:\n                    throw new IllegalArgumentException(\"未知的 FileClientConfig 类型：\" + json);\n            }\n        }\n\n        @Override\n        protected String toJson(Object obj) {\n            return JsonUtils.toJsonString(obj);\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/dal/dataobject/file/FileContentDO.java",
    "content": "package co.yixiang.yshop.module.infra.dal.dataobject.file;\n\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport co.yixiang.yshop.module.infra.framework.file.core.client.db.DBFileClient;\nimport com.baomidou.mybatisplus.annotation.IdType;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.*;\n\n/**\n * 文件内容表\n *\n * 专门用于存储 {@link DBFileClient} 的文件内容\n *\n * @author yshop\n */\n@TableName(\"infra_file_content\")\n@KeySequence(\"infra_file_content_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class FileContentDO extends BaseDO {\n\n    /**\n     * 编号，数据库自增\n     */\n    @TableId(type = IdType.INPUT)\n    private String id;\n    /**\n     * 配置编号\n     *\n     * 关联 {@link FileConfigDO#getId()}\n     */\n    private Long configId;\n    /**\n     * 路径，即文件名\n     */\n    private String path;\n    /**\n     * 文件内容\n     */\n    private byte[] content;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/dal/dataobject/file/FileDO.java",
    "content": "package co.yixiang.yshop.module.infra.dal.dataobject.file;\n\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.*;\n\n/**\n * 文件表\n * 每次文件上传，都会记录一条记录到该表中\n *\n * @author yshop\n */\n@TableName(\"infra_file\")\n@KeySequence(\"infra_file_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class FileDO extends BaseDO {\n\n    /**\n     * 编号，数据库自增\n     */\n    private Long id;\n    /**\n     * 配置编号\n     *\n     * 关联 {@link FileConfigDO#getId()}\n     */\n    private Long configId;\n    /**\n     * 原文件名\n     */\n    private String name;\n    /**\n     * 路径，即文件名\n     */\n    private String path;\n    /**\n     * 访问地址\n     */\n    private String url;\n    /**\n     * 文件的 MIME 类型，例如 \"application/octet-stream\"\n     */\n    private String type;\n    /**\n     * 文件大小\n     */\n    private Integer size;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/dal/dataobject/job/JobDO.java",
    "content": "package co.yixiang.yshop.module.infra.dal.dataobject.job;\n\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport co.yixiang.yshop.module.infra.enums.job.JobStatusEnum;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.*;\n\n/**\n * 定时任务 DO\n *\n * @author yshop\n */\n@TableName(\"infra_job\")\n@KeySequence(\"infra_job_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class JobDO extends BaseDO {\n\n    /**\n     * 任务编号\n     */\n    @TableId\n    private Long id;\n    /**\n     * 任务名称\n     */\n    private String name;\n    /**\n     * 任务状态\n     *\n     * 枚举 {@link JobStatusEnum}\n     */\n    private Integer status;\n    /**\n     * 处理器的名字\n     */\n    private String handlerName;\n    /**\n     * 处理器的参数\n     */\n    private String handlerParam;\n    /**\n     * CRON 表达式\n     */\n    private String cronExpression;\n\n    // ========== 重试相关字段 ==========\n    /**\n     * 重试次数\n     * 如果不重试，则设置为 0\n     */\n    private Integer retryCount;\n    /**\n     * 重试间隔，单位：毫秒\n     * 如果没有间隔，则设置为 0\n     */\n    private Integer retryInterval;\n\n    // ========== 监控相关字段 ==========\n    /**\n     * 监控超时时间，单位：毫秒\n     * 为空时，表示不监控\n     *\n     * 注意，这里的超时的目的，不是进行任务的取消，而是告警任务的执行时间过长\n     */\n    private Integer monitorTimeout;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/dal/dataobject/job/JobLogDO.java",
    "content": "package co.yixiang.yshop.module.infra.dal.dataobject.job;\n\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport co.yixiang.yshop.framework.quartz.core.handler.JobHandler;\nimport co.yixiang.yshop.module.infra.enums.job.JobLogStatusEnum;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.*;\n\nimport java.time.LocalDateTime;\n\n/**\n * 定时任务的执行日志\n *\n * @author yshop\n */\n@TableName(\"infra_job_log\")\n@KeySequence(\"infra_job_log_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class JobLogDO extends BaseDO {\n\n    /**\n     * 日志编号\n     */\n    private Long id;\n    /**\n     * 任务编号\n     *\n     * 关联 {@link JobDO#getId()}\n     */\n    private Long jobId;\n    /**\n     * 处理器的名字\n     *\n     * 冗余字段 {@link JobDO#getHandlerName()}\n     */\n    private String handlerName;\n    /**\n     * 处理器的参数\n     *\n     * 冗余字段 {@link JobDO#getHandlerParam()}\n     */\n    private String handlerParam;\n    /**\n     * 第几次执行\n     *\n     * 用于区分是不是重试执行。如果是重试执行，则 index 大于 1\n     */\n    private Integer executeIndex;\n\n    /**\n     * 开始执行时间\n     */\n    private LocalDateTime beginTime;\n    /**\n     * 结束执行时间\n     */\n    private LocalDateTime endTime;\n    /**\n     * 执行时长，单位：毫秒\n     */\n    private Integer duration;\n    /**\n     * 状态\n     *\n     * 枚举 {@link JobLogStatusEnum}\n     */\n    private Integer status;\n    /**\n     * 结果数据\n     *\n     * 成功时，使用 {@link JobHandler#execute(String)} 的结果\n     * 失败时，使用 {@link JobHandler#execute(String)} 的异常堆栈\n     */\n    private String result;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/dal/dataobject/logger/ApiAccessLogDO.java",
    "content": "package co.yixiang.yshop.module.infra.dal.dataobject.logger;\n\nimport co.yixiang.yshop.framework.apilog.core.enums.OperateTypeEnum;\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.*;\n\nimport java.time.LocalDateTime;\n\n/**\n * API 访问日志\n *\n * @author yshop\n */\n@TableName(\"infra_api_access_log\")\n@KeySequence(value = \"infra_api_access_log_seq\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class ApiAccessLogDO extends BaseDO {\n\n    /**\n     * {@link #requestParams} 的最大长度\n     */\n    public static final Integer REQUEST_PARAMS_MAX_LENGTH = 8000;\n\n    /**\n     * {@link #resultMsg} 的最大长度\n     */\n    public static final Integer RESULT_MSG_MAX_LENGTH = 512;\n\n    /**\n     * 编号\n     */\n    @TableId\n    private Long id;\n    /**\n     * 链路追踪编号\n     *\n     * 一般来说，通过链路追踪编号，可以将访问日志，错误日志，链路追踪日志，logger 打印日志等，结合在一起，从而进行排错。\n     */\n    private String traceId;\n    /**\n     * 用户编号\n     */\n    private Long userId;\n    /**\n     * 用户类型\n     *\n     * 枚举 {@link UserTypeEnum}\n     */\n    private Integer userType;\n    /**\n     * 应用名\n     *\n     * 目前读取 `spring.application.name` 配置项\n     */\n    private String applicationName;\n\n    // ========== 请求相关字段 ==========\n\n    /**\n     * 请求方法名\n     */\n    private String requestMethod;\n    /**\n     * 访问地址\n     */\n    private String requestUrl;\n    /**\n     * 请求参数\n     *\n     * query: Query String\n     * body: Quest Body\n     */\n    private String requestParams;\n    /**\n     * 响应结果\n     */\n    private String responseBody;\n    /**\n     * 用户 IP\n     */\n    private String userIp;\n    /**\n     * 浏览器 UA\n     */\n    private String userAgent;\n\n    // ========== 执行相关字段 ==========\n\n    /**\n     * 操作模块\n     */\n    private String operateModule;\n    /**\n     * 操作名\n     */\n    private String operateName;\n    /**\n     * 操作分类\n     *\n     * 枚举 {@link OperateTypeEnum}\n     */\n    private Integer operateType;\n\n    /**\n     * 开始请求时间\n     */\n    private LocalDateTime beginTime;\n    /**\n     * 结束请求时间\n     */\n    private LocalDateTime endTime;\n    /**\n     * 执行时长，单位：毫秒\n     */\n    private Integer duration;\n\n    /**\n     * 结果码\n     *\n     * 目前使用的 {@link CommonResult#getCode()} 属性\n     */\n    private Integer resultCode;\n    /**\n     * 结果提示\n     *\n     * 目前使用的 {@link CommonResult#getMsg()} 属性\n     */\n    private String resultMsg;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/dal/dataobject/logger/ApiErrorLogDO.java",
    "content": "package co.yixiang.yshop.module.infra.dal.dataobject.logger;\n\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport co.yixiang.yshop.module.infra.enums.logger.ApiErrorLogProcessStatusEnum;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.*;\n\nimport java.time.LocalDateTime;\n\n/**\n * API 异常数据\n *\n * @author yshop\n */\n@TableName(\"infra_api_error_log\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\n@KeySequence(value = \"infra_api_error_log_seq\")\npublic class ApiErrorLogDO extends BaseDO {\n\n\n    /**\n     * {@link #requestParams} 的最大长度\n     */\n    public static final Integer REQUEST_PARAMS_MAX_LENGTH = 8000;\n\n    /**\n     * 编号\n     */\n    @TableId\n    private Long id;\n    /**\n     * 用户编号\n     */\n    private Long userId;\n    /**\n     * 链路追踪编号\n     *\n     * 一般来说，通过链路追踪编号，可以将访问日志，错误日志，链路追踪日志，logger 打印日志等，结合在一起，从而进行排错。\n     */\n    private String traceId;\n    /**\n     * 用户类型\n     *\n     * 枚举 {@link UserTypeEnum}\n     */\n    private Integer userType;\n    /**\n     * 应用名\n     *\n     * 目前读取 spring.application.name\n     */\n    private String applicationName;\n\n    // ========== 请求相关字段 ==========\n\n    /**\n     * 请求方法名\n     */\n    private String requestMethod;\n    /**\n     * 访问地址\n     */\n    private String requestUrl;\n    /**\n     * 请求参数\n     *\n     * query: Query String\n     * body: Quest Body\n     */\n    private String requestParams;\n    /**\n     * 用户 IP\n     */\n    private String userIp;\n    /**\n     * 浏览器 UA\n     */\n    private String userAgent;\n\n    // ========== 异常相关字段 ==========\n\n    /**\n     * 异常发生时间\n     */\n    private LocalDateTime exceptionTime;\n    /**\n     * 异常名\n     *\n     * {@link Throwable#getClass()} 的类全名\n     */\n    private String exceptionName;\n    /**\n     * 异常导致的消息\n     *\n     * {@link cn.hutool.core.exceptions.ExceptionUtil#getMessage(Throwable)}\n     */\n    private String exceptionMessage;\n    /**\n     * 异常导致的根消息\n     *\n     * {@link cn.hutool.core.exceptions.ExceptionUtil#getRootCauseMessage(Throwable)}\n     */\n    private String exceptionRootCauseMessage;\n    /**\n     * 异常的栈轨迹\n     *\n     * {@link org.apache.commons.lang3.exception.ExceptionUtils#getStackTrace(Throwable)}\n     */\n    private String exceptionStackTrace;\n    /**\n     * 异常发生的类全名\n     *\n     * {@link StackTraceElement#getClassName()}\n     */\n    private String exceptionClassName;\n    /**\n     * 异常发生的类文件\n     *\n     * {@link StackTraceElement#getFileName()}\n     */\n    private String exceptionFileName;\n    /**\n     * 异常发生的方法名\n     *\n     * {@link StackTraceElement#getMethodName()}\n     */\n    private String exceptionMethodName;\n    /**\n     * 异常发生的方法所在行\n     *\n     * {@link StackTraceElement#getLineNumber()}\n     */\n    private Integer exceptionLineNumber;\n\n    // ========== 处理相关字段 ==========\n\n    /**\n     * 处理状态\n     *\n     * 枚举 {@link ApiErrorLogProcessStatusEnum}\n     */\n    private Integer processStatus;\n    /**\n     * 处理时间\n     */\n    private LocalDateTime processTime;\n    /**\n     * 处理用户编号\n     *\n     * 关联 co.yixiang.yshop.adminserver.modules.system.dal.dataobject.user.SysUserDO.SysUserDO#getId()\n     */\n    private Long processUserId;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/dal/mysql/codegen/CodegenColumnMapper.java",
    "content": "package co.yixiang.yshop.module.infra.dal.mysql.codegen;\n\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.infra.dal.dataobject.codegen.CodegenColumnDO;\nimport org.apache.ibatis.annotations.Mapper;\n\nimport java.util.List;\n\n@Mapper\npublic interface CodegenColumnMapper extends BaseMapperX<CodegenColumnDO> {\n\n    default List<CodegenColumnDO> selectListByTableId(Long tableId) {\n        return selectList(new LambdaQueryWrapperX<CodegenColumnDO>()\n                .eq(CodegenColumnDO::getTableId, tableId)\n                .orderByAsc(CodegenColumnDO::getId));\n    }\n\n    default void deleteListByTableId(Long tableId) {\n        delete(new LambdaQueryWrapperX<CodegenColumnDO>()\n                .eq(CodegenColumnDO::getTableId, tableId));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/dal/mysql/codegen/CodegenTableMapper.java",
    "content": "package co.yixiang.yshop.module.infra.dal.mysql.codegen;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.infra.controller.admin.codegen.vo.table.CodegenTablePageReqVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.codegen.CodegenTableDO;\nimport org.apache.ibatis.annotations.Mapper;\n\nimport java.util.List;\n\n@Mapper\npublic interface CodegenTableMapper extends BaseMapperX<CodegenTableDO> {\n\n    default CodegenTableDO selectByTableNameAndDataSourceConfigId(String tableName, Long dataSourceConfigId) {\n        return selectOne(CodegenTableDO::getTableName, tableName,\n                CodegenTableDO::getDataSourceConfigId, dataSourceConfigId);\n    }\n\n    default PageResult<CodegenTableDO> selectPage(CodegenTablePageReqVO pageReqVO) {\n        return selectPage(pageReqVO, new LambdaQueryWrapperX<CodegenTableDO>()\n                .likeIfPresent(CodegenTableDO::getTableName, pageReqVO.getTableName())\n                .likeIfPresent(CodegenTableDO::getTableComment, pageReqVO.getTableComment())\n                .likeIfPresent(CodegenTableDO::getClassName, pageReqVO.getClassName())\n                .betweenIfPresent(CodegenTableDO::getCreateTime, pageReqVO.getCreateTime())\n                .orderByDesc(CodegenTableDO::getUpdateTime)\n        );\n    }\n\n    default List<CodegenTableDO> selectListByDataSourceConfigId(Long dataSourceConfigId) {\n        return selectList(CodegenTableDO::getDataSourceConfigId, dataSourceConfigId);\n    }\n\n    default List<CodegenTableDO> selectListByTemplateTypeAndMasterTableId(Integer templateType, Long masterTableId) {\n        return selectList(CodegenTableDO::getTemplateType, templateType,\n                CodegenTableDO::getMasterTableId, masterTableId);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/dal/mysql/config/ConfigMapper.java",
    "content": "package co.yixiang.yshop.module.infra.dal.mysql.config;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.infra.controller.admin.config.vo.ConfigPageReqVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.config.ConfigDO;\nimport org.apache.ibatis.annotations.Mapper;\n\n@Mapper\npublic interface ConfigMapper extends BaseMapperX<ConfigDO> {\n\n    default ConfigDO selectByKey(String key) {\n        return selectOne(ConfigDO::getConfigKey, key);\n    }\n\n    default PageResult<ConfigDO> selectPage(ConfigPageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<ConfigDO>()\n                .likeIfPresent(ConfigDO::getName, reqVO.getName())\n                .likeIfPresent(ConfigDO::getConfigKey, reqVO.getKey())\n                .eqIfPresent(ConfigDO::getType, reqVO.getType())\n                .betweenIfPresent(ConfigDO::getCreateTime, reqVO.getCreateTime()));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/dal/mysql/db/DataSourceConfigMapper.java",
    "content": "package co.yixiang.yshop.module.infra.dal.mysql.db;\n\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.module.infra.dal.dataobject.db.DataSourceConfigDO;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * 数据源配置 Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface DataSourceConfigMapper extends BaseMapperX<DataSourceConfigDO> {\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/dal/mysql/demo/demo01/Demo01ContactMapper.java",
    "content": "package co.yixiang.yshop.module.infra.dal.mysql.demo.demo01;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.module.infra.controller.admin.demo.demo01.vo.Demo01ContactPageReqVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.demo.demo01.Demo01ContactDO;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * 示例联系人 Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface Demo01ContactMapper extends BaseMapperX<Demo01ContactDO> {\n\n    default PageResult<Demo01ContactDO> selectPage(Demo01ContactPageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<Demo01ContactDO>()\n                .likeIfPresent(Demo01ContactDO::getName, reqVO.getName())\n                .eqIfPresent(Demo01ContactDO::getSex, reqVO.getSex())\n                .betweenIfPresent(Demo01ContactDO::getCreateTime, reqVO.getCreateTime())\n                .orderByDesc(Demo01ContactDO::getId));\n    }\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/dal/mysql/demo/demo02/Demo02CategoryMapper.java",
    "content": "package co.yixiang.yshop.module.infra.dal.mysql.demo.demo02;\n\nimport java.util.*;\n\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.module.infra.controller.admin.demo.demo02.vo.Demo02CategoryListReqVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.demo.demo02.Demo02CategoryDO;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * 示例分类 Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface Demo02CategoryMapper extends BaseMapperX<Demo02CategoryDO> {\n\n    default List<Demo02CategoryDO> selectList(Demo02CategoryListReqVO reqVO) {\n        return selectList(new LambdaQueryWrapperX<Demo02CategoryDO>()\n                .likeIfPresent(Demo02CategoryDO::getName, reqVO.getName())\n                .eqIfPresent(Demo02CategoryDO::getParentId, reqVO.getParentId())\n                .betweenIfPresent(Demo02CategoryDO::getCreateTime, reqVO.getCreateTime())\n                .orderByDesc(Demo02CategoryDO::getId));\n    }\n\n\tdefault Demo02CategoryDO selectByParentIdAndName(Long parentId, String name) {\n\t    return selectOne(Demo02CategoryDO::getParentId, parentId, Demo02CategoryDO::getName, name);\n\t}\n\n    default Long selectCountByParentId(Long parentId) {\n        return selectCount(Demo02CategoryDO::getParentId, parentId);\n    }\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/dal/mysql/demo/demo03/Demo03CourseMapper.java",
    "content": "package co.yixiang.yshop.module.infra.dal.mysql.demo.demo03;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.infra.dal.dataobject.demo.demo03.Demo03CourseDO;\nimport org.apache.ibatis.annotations.Mapper;\n\nimport java.util.List;\n\n/**\n * 学生课程 Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface Demo03CourseMapper extends BaseMapperX<Demo03CourseDO> {\n\n    default PageResult<Demo03CourseDO> selectPage(PageParam reqVO, Long studentId) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<Demo03CourseDO>()\n                .eq(Demo03CourseDO::getStudentId, studentId)\n                .orderByDesc(Demo03CourseDO::getId));\n    }\n\n    default List<Demo03CourseDO> selectListByStudentId(Long studentId) {\n        return selectList(Demo03CourseDO::getStudentId, studentId);\n    }\n\n    default int deleteByStudentId(Long studentId) {\n        return delete(Demo03CourseDO::getStudentId, studentId);\n    }\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/dal/mysql/demo/demo03/Demo03GradeMapper.java",
    "content": "package co.yixiang.yshop.module.infra.dal.mysql.demo.demo03;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.infra.dal.dataobject.demo.demo03.Demo03GradeDO;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * 学生班级 Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface Demo03GradeMapper extends BaseMapperX<Demo03GradeDO> {\n\n    default PageResult<Demo03GradeDO> selectPage(PageParam reqVO, Long studentId) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<Demo03GradeDO>()\n                .eq(Demo03GradeDO::getStudentId, studentId)\n                .orderByDesc(Demo03GradeDO::getId));\n    }\n\n    default Demo03GradeDO selectByStudentId(Long studentId) {\n        return selectOne(Demo03GradeDO::getStudentId, studentId);\n    }\n\n    default int deleteByStudentId(Long studentId) {\n        return delete(Demo03GradeDO::getStudentId, studentId);\n    }\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/dal/mysql/demo/demo03/Demo03StudentMapper.java",
    "content": "package co.yixiang.yshop.module.infra.dal.mysql.demo.demo03;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.module.infra.dal.dataobject.demo.demo03.Demo03StudentDO;\nimport org.apache.ibatis.annotations.Mapper;\nimport co.yixiang.yshop.module.infra.controller.admin.demo.demo03.vo.*;\n\n/**\n * 学生 Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface Demo03StudentMapper extends BaseMapperX<Demo03StudentDO> {\n\n    default PageResult<Demo03StudentDO> selectPage(Demo03StudentPageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<Demo03StudentDO>()\n                .likeIfPresent(Demo03StudentDO::getName, reqVO.getName())\n                .eqIfPresent(Demo03StudentDO::getSex, reqVO.getSex())\n                .eqIfPresent(Demo03StudentDO::getDescription, reqVO.getDescription())\n                .betweenIfPresent(Demo03StudentDO::getCreateTime, reqVO.getCreateTime())\n                .orderByDesc(Demo03StudentDO::getId));\n    }\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/dal/mysql/file/FileConfigMapper.java",
    "content": "package co.yixiang.yshop.module.infra.dal.mysql.file;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.infra.controller.admin.file.vo.config.FileConfigPageReqVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.file.FileConfigDO;\nimport org.apache.ibatis.annotations.Mapper;\n\n@Mapper\npublic interface FileConfigMapper extends BaseMapperX<FileConfigDO> {\n\n    default PageResult<FileConfigDO> selectPage(FileConfigPageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<FileConfigDO>()\n                .likeIfPresent(FileConfigDO::getName, reqVO.getName())\n                .eqIfPresent(FileConfigDO::getStorage, reqVO.getStorage())\n                .betweenIfPresent(FileConfigDO::getCreateTime, reqVO.getCreateTime())\n                .orderByDesc(FileConfigDO::getId));\n    }\n\n    default FileConfigDO selectByMaster() {\n        return selectOne(FileConfigDO::getMaster, true);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/dal/mysql/file/FileContentMapper.java",
    "content": "package co.yixiang.yshop.module.infra.dal.mysql.file;\n\nimport co.yixiang.yshop.module.infra.dal.dataobject.file.FileContentDO;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport com.baomidou.mybatisplus.core.mapper.BaseMapper;\nimport org.apache.ibatis.annotations.Mapper;\n\nimport java.util.List;\n\n@Mapper\npublic interface FileContentMapper extends BaseMapper<FileContentDO> {\n\n    default void deleteByConfigIdAndPath(Long configId, String path) {\n        this.delete(new LambdaQueryWrapper<FileContentDO>()\n                .eq(FileContentDO::getConfigId, configId)\n                .eq(FileContentDO::getPath, path));\n    }\n\n    default List<FileContentDO> selectListByConfigIdAndPath(Long configId, String path) {\n        return selectList(new LambdaQueryWrapper<FileContentDO>()\n                .eq(FileContentDO::getConfigId, configId)\n                .eq(FileContentDO::getPath, path));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/dal/mysql/file/FileMapper.java",
    "content": "package co.yixiang.yshop.module.infra.dal.mysql.file;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.infra.controller.admin.file.vo.file.FilePageReqVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.file.FileDO;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * 文件操作 Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface FileMapper extends BaseMapperX<FileDO> {\n\n    default PageResult<FileDO> selectPage(FilePageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<FileDO>()\n                .likeIfPresent(FileDO::getPath, reqVO.getPath())\n                .likeIfPresent(FileDO::getType, reqVO.getType())\n                .betweenIfPresent(FileDO::getCreateTime, reqVO.getCreateTime())\n                .orderByDesc(FileDO::getId));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/dal/mysql/job/JobLogMapper.java",
    "content": "package co.yixiang.yshop.module.infra.dal.mysql.job;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.infra.controller.admin.job.vo.log.JobLogPageReqVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.job.JobLogDO;\nimport org.apache.ibatis.annotations.Delete;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.apache.ibatis.annotations.Param;\n\nimport java.time.LocalDateTime;\n\n/**\n * 任务日志 Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface JobLogMapper extends BaseMapperX<JobLogDO> {\n\n    default PageResult<JobLogDO> selectPage(JobLogPageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<JobLogDO>()\n                .eqIfPresent(JobLogDO::getJobId, reqVO.getJobId())\n                .likeIfPresent(JobLogDO::getHandlerName, reqVO.getHandlerName())\n                .geIfPresent(JobLogDO::getBeginTime, reqVO.getBeginTime())\n                .leIfPresent(JobLogDO::getEndTime, reqVO.getEndTime())\n                .eqIfPresent(JobLogDO::getStatus, reqVO.getStatus())\n                .orderByDesc(JobLogDO::getId) // ID 倒序\n        );\n    }\n\n    /**\n     * 物理删除指定时间之前的日志\n     *\n     * @param createTime 最大时间\n     * @param limit 删除条数，防止一次删除太多\n     * @return 删除条数\n     */\n    @Delete(\"DELETE FROM infra_job_log WHERE create_time < #{createTime} LIMIT #{limit}\")\n    Integer deleteByCreateTimeLt(@Param(\"createTime\") LocalDateTime createTime, @Param(\"limit\") Integer limit);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/dal/mysql/job/JobMapper.java",
    "content": "package co.yixiang.yshop.module.infra.dal.mysql.job;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.infra.controller.admin.job.vo.job.JobPageReqVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.job.JobDO;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * 定时任务 Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface JobMapper extends BaseMapperX<JobDO> {\n\n    default JobDO selectByHandlerName(String handlerName) {\n        return selectOne(JobDO::getHandlerName, handlerName);\n    }\n\n    default PageResult<JobDO> selectPage(JobPageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<JobDO>()\n                .likeIfPresent(JobDO::getName, reqVO.getName())\n                .eqIfPresent(JobDO::getStatus, reqVO.getStatus())\n                .likeIfPresent(JobDO::getHandlerName, reqVO.getHandlerName())\n        );\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/dal/mysql/logger/ApiAccessLogMapper.java",
    "content": "package co.yixiang.yshop.module.infra.dal.mysql.logger;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.infra.controller.admin.logger.vo.apiaccesslog.ApiAccessLogPageReqVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.logger.ApiAccessLogDO;\nimport org.apache.ibatis.annotations.Delete;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.apache.ibatis.annotations.Param;\n\nimport java.time.LocalDateTime;\n\n/**\n * API 访问日志 Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface ApiAccessLogMapper extends BaseMapperX<ApiAccessLogDO> {\n\n    default PageResult<ApiAccessLogDO> selectPage(ApiAccessLogPageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<ApiAccessLogDO>()\n                .eqIfPresent(ApiAccessLogDO::getUserId, reqVO.getUserId())\n                .eqIfPresent(ApiAccessLogDO::getUserType, reqVO.getUserType())\n                .eqIfPresent(ApiAccessLogDO::getApplicationName, reqVO.getApplicationName())\n                .likeIfPresent(ApiAccessLogDO::getRequestUrl, reqVO.getRequestUrl())\n                .betweenIfPresent(ApiAccessLogDO::getBeginTime, reqVO.getBeginTime())\n                .geIfPresent(ApiAccessLogDO::getDuration, reqVO.getDuration())\n                .eqIfPresent(ApiAccessLogDO::getResultCode, reqVO.getResultCode())\n                .orderByDesc(ApiAccessLogDO::getId)\n        );\n    }\n\n    /**\n     * 物理删除指定时间之前的日志\n     *\n     * @param createTime 最大时间\n     * @param limit 删除条数，防止一次删除太多\n     * @return 删除条数\n     */\n    @Delete(\"DELETE FROM infra_api_access_log WHERE create_time < #{createTime} LIMIT #{limit}\")\n    Integer deleteByCreateTimeLt(@Param(\"createTime\") LocalDateTime createTime, @Param(\"limit\") Integer limit);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/dal/mysql/logger/ApiErrorLogMapper.java",
    "content": "package co.yixiang.yshop.module.infra.dal.mysql.logger;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.infra.controller.admin.logger.vo.apierrorlog.ApiErrorLogPageReqVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.logger.ApiErrorLogDO;\nimport org.apache.ibatis.annotations.Delete;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.apache.ibatis.annotations.Param;\n\nimport java.time.LocalDateTime;\n\n/**\n * API 错误日志 Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface ApiErrorLogMapper extends BaseMapperX<ApiErrorLogDO> {\n\n    default PageResult<ApiErrorLogDO> selectPage(ApiErrorLogPageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<ApiErrorLogDO>()\n                .eqIfPresent(ApiErrorLogDO::getUserId, reqVO.getUserId())\n                .eqIfPresent(ApiErrorLogDO::getUserType, reqVO.getUserType())\n                .eqIfPresent(ApiErrorLogDO::getApplicationName, reqVO.getApplicationName())\n                .likeIfPresent(ApiErrorLogDO::getRequestUrl, reqVO.getRequestUrl())\n                .betweenIfPresent(ApiErrorLogDO::getExceptionTime, reqVO.getExceptionTime())\n                .eqIfPresent(ApiErrorLogDO::getProcessStatus, reqVO.getProcessStatus())\n                .orderByDesc(ApiErrorLogDO::getId)\n        );\n    }\n\n    /**\n     * 物理删除指定时间之前的日志\n     *\n     * @param createTime 最大时间\n     * @param limit 删除条数，防止一次删除太多\n     * @return 删除条数\n     */\n    @Delete(\"DELETE FROM infra_api_error_log WHERE create_time < #{createTime} LIMIT #{limit}\")\n    Integer deleteByCreateTimeLt(@Param(\"createTime\") LocalDateTime createTime, @Param(\"limit\")Integer limit);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/enums/codegen/CodegenColumnHtmlTypeEnum.java",
    "content": "package co.yixiang.yshop.module.infra.enums.codegen;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * 代码生成器的字段 HTML 展示枚举\n */\n@AllArgsConstructor\n@Getter\npublic enum CodegenColumnHtmlTypeEnum {\n\n    INPUT(\"input\"), // 文本框\n    TEXTAREA(\"textarea\"), // 文本域\n    SELECT(\"select\"), // 下拉框\n    RADIO(\"radio\"), // 单选框\n    CHECKBOX(\"checkbox\"), // 复选框\n    DATETIME(\"datetime\"), // 日期控件\n    IMAGE_UPLOAD(\"imageUpload\"), // 上传图片\n    FILE_UPLOAD(\"fileUpload\"), // 上传文件\n    EDITOR(\"editor\"), // 富文本控件\n    ;\n\n    /**\n     * 条件\n     */\n    private final String type;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/enums/codegen/CodegenColumnListConditionEnum.java",
    "content": "package co.yixiang.yshop.module.infra.enums.codegen;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * 代码生成器的字段过滤条件枚举\n */\n@AllArgsConstructor\n@Getter\npublic enum CodegenColumnListConditionEnum {\n\n    EQ(\"=\"),\n    NE(\"!=\"),\n    GT(\">\"),\n    GTE(\">=\"),\n    LT(\"<\"),\n    LTE(\"<=\"),\n    LIKE(\"LIKE\"),\n    BETWEEN(\"BETWEEN\");\n\n    /**\n     * 条件\n     */\n    private final String condition;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/enums/codegen/CodegenFrontTypeEnum.java",
    "content": "package co.yixiang.yshop.module.infra.enums.codegen;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * 代码生成的前端类型枚举\n *\n * @author yshop\n */\n@AllArgsConstructor\n@Getter\npublic enum CodegenFrontTypeEnum {\n\n    VUE2(10), // Vue2 Element UI 标准模版\n    VUE3(20), // Vue3 Element Plus 标准模版\n    VUE3_SCHEMA(21), // Vue3 Element Plus Schema 模版\n    VUE3_VBEN(30), // Vue3 VBEN 模版\n    ;\n\n    /**\n     * 类型\n     */\n    private final Integer type;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/enums/codegen/CodegenSceneEnum.java",
    "content": "package co.yixiang.yshop.module.infra.enums.codegen;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\nimport static cn.hutool.core.util.ArrayUtil.*;\n\n/**\n * 代码生成的场景枚举\n *\n * @author yshop\n */\n@AllArgsConstructor\n@Getter\npublic enum CodegenSceneEnum {\n\n    ADMIN(1, \"管理后台\", \"admin\", \"\"),\n    APP(2, \"用户 APP\", \"app\", \"App\");\n\n    /**\n     * 场景\n     */\n    private final Integer scene;\n    /**\n     * 场景名\n     */\n    private final String name;\n    /**\n     * 基础包名\n     */\n    private final String basePackage;\n    /**\n     * Controller 和 VO 类的前缀\n     */\n    private final String prefixClass;\n\n    public static CodegenSceneEnum valueOf(Integer scene) {\n        return firstMatch(sceneEnum -> sceneEnum.getScene().equals(scene), values());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/enums/codegen/CodegenTemplateTypeEnum.java",
    "content": "package co.yixiang.yshop.module.infra.enums.codegen;\n\nimport co.yixiang.yshop.framework.common.util.object.ObjectUtils;\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\nimport java.util.Objects;\n\n/**\n * 代码生成模板类型\n *\n * @author yshop\n */\n@AllArgsConstructor\n@Getter\npublic enum CodegenTemplateTypeEnum {\n\n    ONE(1), // 单表（增删改查）\n    TREE(2), // 树表（增删改查）\n\n    MASTER_NORMAL(10), // 主子表 - 主表 - 普通模式\n    MASTER_ERP(11), // 主子表 - 主表 - ERP 模式\n    MASTER_INNER(12), // 主子表 - 主表 - 内嵌模式\n    SUB(15), // 主子表 - 子表\n    ;\n\n    /**\n     * 类型\n     */\n    private final Integer type;\n\n    /**\n     * 是否为主表\n     *\n     * @param type 类型\n     * @return 是否主表\n     */\n    public static boolean isMaster(Integer type) {\n        return ObjectUtils.equalsAny(type,\n                MASTER_NORMAL.type, MASTER_ERP.type, MASTER_INNER.type);\n    }\n\n    /**\n     * 是否为树表\n     *\n     * @param type 类型\n     * @return 是否树表\n     */\n    public static boolean isTree(Integer type) {\n        return Objects.equals(type, TREE.type);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/enums/config/ConfigTypeEnum.java",
    "content": "package co.yixiang.yshop.module.infra.enums.config;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n@Getter\n@AllArgsConstructor\npublic enum ConfigTypeEnum {\n\n    /**\n     * 系统配置\n     */\n    SYSTEM(1),\n    /**\n     * 自定义配置\n     */\n    CUSTOM(2);\n\n    private final Integer type;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/enums/job/JobLogStatusEnum.java",
    "content": "package co.yixiang.yshop.module.infra.enums.job;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * 任务日志的状态枚举\n *\n * @author yshop\n */\n@Getter\n@AllArgsConstructor\npublic enum JobLogStatusEnum {\n\n    RUNNING(0), // 运行中\n    SUCCESS(1), // 成功\n    FAILURE(2); // 失败\n\n    /**\n     * 状态\n     */\n    private final Integer status;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/enums/job/JobStatusEnum.java",
    "content": "package co.yixiang.yshop.module.infra.enums.job;\n\nimport com.google.common.collect.Sets;\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\nimport org.quartz.impl.jdbcjobstore.Constants;\n\nimport java.util.Collections;\nimport java.util.Set;\n\n/**\n * 任务状态的枚举\n *\n * @author yshop\n */\n@Getter\n@AllArgsConstructor\npublic enum JobStatusEnum {\n\n    /**\n     * 初始化中\n     */\n    INIT(0, Collections.emptySet()),\n    /**\n     * 开启\n     */\n    NORMAL(1, Sets.newHashSet(Constants.STATE_WAITING, Constants.STATE_ACQUIRED, Constants.STATE_BLOCKED)),\n    /**\n     * 暂停\n     */\n    STOP(2, Sets.newHashSet(Constants.STATE_PAUSED, Constants.STATE_PAUSED_BLOCKED));\n\n    /**\n     * 状态\n     */\n    private final Integer status;\n    /**\n     * 对应的 Quartz 触发器的状态集合\n     */\n    private final Set<String> quartzStates;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/enums/logger/ApiErrorLogProcessStatusEnum.java",
    "content": "package co.yixiang.yshop.module.infra.enums.logger;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * API 异常数据的处理状态\n *\n * @author yshop\n */\n@AllArgsConstructor\n@Getter\npublic enum ApiErrorLogProcessStatusEnum {\n\n    INIT(0, \"未处理\"),\n    DONE(1, \"已处理\"),\n    IGNORE(2, \"已忽略\");\n\n    /**\n     * 状态\n     */\n    private final Integer status;\n    /**\n     * 资源类型名\n     */\n    private final String name;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/framework/codegen/config/CodegenConfiguration.java",
    "content": "package co.yixiang.yshop.module.infra.framework.codegen.config;\n\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.context.annotation.Configuration;\n\n@Configuration(proxyBeanMethods = false)\n@EnableConfigurationProperties(CodegenProperties.class)\npublic class CodegenConfiguration {\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/framework/codegen/config/CodegenProperties.java",
    "content": "package co.yixiang.yshop.module.infra.framework.codegen.config;\n\nimport co.yixiang.yshop.module.infra.enums.codegen.CodegenFrontTypeEnum;\nimport lombok.Data;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\nimport java.util.Collection;\n\n@ConfigurationProperties(prefix = \"yshop.codegen\")\n@Validated\n@Data\npublic class CodegenProperties {\n\n    /**\n     * 生成的 Java 代码的基础包\n     */\n    @NotNull(message = \"Java 代码的基础包不能为空\")\n    private String basePackage;\n\n    /**\n     * 数据库名数组\n     */\n    @NotEmpty(message = \"数据库不能为空\")\n    private Collection<String> dbSchemas;\n\n    /**\n     * 代码生成的前端类型（默认）\n     *\n     * 枚举 {@link CodegenFrontTypeEnum#getType()}\n     */\n    @NotNull(message = \"代码生成的前端类型不能为空\")\n    private Integer frontType;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/framework/file/config/YshopFileAutoConfiguration.java",
    "content": "package co.yixiang.yshop.module.infra.framework.file.config;\n\nimport co.yixiang.yshop.module.infra.framework.file.core.client.FileClientFactory;\nimport co.yixiang.yshop.module.infra.framework.file.core.client.FileClientFactoryImpl;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n/**\n * 文件配置类\n *\n * @author yshop\n */\n@Configuration(proxyBeanMethods = false)\npublic class YshopFileAutoConfiguration {\n\n    @Bean\n    public FileClientFactory fileClientFactory() {\n        return new FileClientFactoryImpl();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/framework/file/core/client/AbstractFileClient.java",
    "content": "package co.yixiang.yshop.module.infra.framework.file.core.client;\n\nimport cn.hutool.core.util.StrUtil;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * 文件客户端的抽象类，提供模板方法，减少子类的冗余代码\n *\n * @author yshop\n */\n@Slf4j\npublic abstract class AbstractFileClient<Config extends FileClientConfig> implements FileClient {\n\n    /**\n     * 配置编号\n     */\n    private final Long id;\n    /**\n     * 文件配置\n     */\n    protected Config config;\n\n    public AbstractFileClient(Long id, Config config) {\n        this.id = id;\n        this.config = config;\n    }\n\n    /**\n     * 初始化\n     */\n    public final void init() {\n        doInit();\n        log.debug(\"[init][配置({}) 初始化完成]\", config);\n    }\n\n    /**\n     * 自定义初始化\n     */\n    protected abstract void doInit();\n\n    public final void refresh(Config config) {\n        // 判断是否更新\n        if (config.equals(this.config)) {\n            return;\n        }\n        log.info(\"[refresh][配置({})发生变化，重新初始化]\", config);\n        this.config = config;\n        // 初始化\n        this.init();\n    }\n\n    @Override\n    public Long getId() {\n        return id;\n    }\n\n    /**\n     * 格式化文件的 URL 访问地址\n     * 使用场景：local、ftp、db，通过 FileController 的 getFile 来获取文件内容\n     *\n     * @param domain 自定义域名\n     * @param path 文件路径\n     * @return URL 访问地址\n     */\n    protected String formatFileUrl(String domain, String path) {\n        return StrUtil.format(\"{}/admin-api/infra/file/{}/get/{}\", domain, getId(), path);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/framework/file/core/client/FileClient.java",
    "content": "package co.yixiang.yshop.module.infra.framework.file.core.client;\n\nimport co.yixiang.yshop.module.infra.framework.file.core.client.s3.FilePresignedUrlRespDTO;\n\n/**\n * 文件客户端\n *\n * @author yshop\n */\npublic interface FileClient {\n\n    /**\n     * 获得客户端编号\n     *\n     * @return 客户端编号\n     */\n    Long getId();\n\n    /**\n     * 上传文件\n     *\n     * @param content 文件流\n     * @param path    相对路径\n     * @return 完整路径，即 HTTP 访问地址\n     * @throws Exception 上传文件时，抛出 Exception 异常\n     */\n    String upload(byte[] content, String path, String type) throws Exception;\n\n    /**\n     * 删除文件\n     *\n     * @param path 相对路径\n     * @throws Exception 删除文件时，抛出 Exception 异常\n     */\n    void delete(String path) throws Exception;\n\n    /**\n     * 获得文件的内容\n     *\n     * @param path 相对路径\n     * @return 文件的内容\n     */\n    byte[] getContent(String path) throws Exception;\n\n    /**\n     * 获得文件预签名地址\n     *\n     * @param path 相对路径\n     * @return 文件预签名地址\n     */\n    default FilePresignedUrlRespDTO getPresignedObjectUrl(String path) throws Exception {\n        throw new UnsupportedOperationException(\"不支持的操作\");\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/framework/file/core/client/FileClientConfig.java",
    "content": "package co.yixiang.yshop.module.infra.framework.file.core.client;\n\nimport com.fasterxml.jackson.annotation.JsonTypeInfo;\n\n/**\n * 文件客户端的配置\n * 不同实现的客户端，需要不同的配置，通过子类来定义\n *\n * @author yshop\n */\n@JsonTypeInfo(use = JsonTypeInfo.Id.CLASS)\n// @JsonTypeInfo 注解的作用，Jackson 多态\n// 1. 序列化到时数据库时，增加 @class 属性。\n// 2. 反序列化到内存对象时，通过 @class 属性，可以创建出正确的类型\npublic interface FileClientConfig {\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/framework/file/core/client/FileClientFactory.java",
    "content": "package co.yixiang.yshop.module.infra.framework.file.core.client;\n\nimport co.yixiang.yshop.module.infra.framework.file.core.enums.FileStorageEnum;\n\npublic interface FileClientFactory {\n\n    /**\n     * 获得文件客户端\n     *\n     * @param configId 配置编号\n     * @return 文件客户端\n     */\n    FileClient getFileClient(Long configId);\n\n    /**\n     * 创建文件客户端\n     *\n     * @param configId 配置编号\n     * @param storage 存储器的枚举 {@link FileStorageEnum}\n     * @param config 文件配置\n     */\n    <Config extends FileClientConfig> void createOrUpdateFileClient(Long configId, Integer storage, Config config);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/framework/file/core/client/FileClientFactoryImpl.java",
    "content": "package co.yixiang.yshop.module.infra.framework.file.core.client;\n\nimport cn.hutool.core.lang.Assert;\nimport cn.hutool.core.util.ReflectUtil;\nimport co.yixiang.yshop.module.infra.framework.file.core.enums.FileStorageEnum;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ConcurrentMap;\n\n/**\n * 文件客户端的工厂实现类\n *\n * @author yshop\n */\n@Slf4j\npublic class FileClientFactoryImpl implements FileClientFactory {\n\n    /**\n     * 文件客户端 Map\n     * key：配置编号\n     */\n    private final ConcurrentMap<Long, AbstractFileClient<?>> clients = new ConcurrentHashMap<>();\n\n    @Override\n    public FileClient getFileClient(Long configId) {\n        AbstractFileClient<?> client = clients.get(configId);\n        if (client == null) {\n            log.error(\"[getFileClient][配置编号({}) 找不到客户端]\", configId);\n        }\n        return client;\n    }\n\n    @Override\n    @SuppressWarnings(\"unchecked\")\n    public <Config extends FileClientConfig> void createOrUpdateFileClient(Long configId, Integer storage, Config config) {\n        AbstractFileClient<Config> client = (AbstractFileClient<Config>) clients.get(configId);\n        if (client == null) {\n            client = this.createFileClient(configId, storage, config);\n            client.init();\n            clients.put(client.getId(), client);\n        } else {\n            client.refresh(config);\n        }\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    private <Config extends FileClientConfig> AbstractFileClient<Config> createFileClient(\n            Long configId, Integer storage, Config config) {\n        FileStorageEnum storageEnum = FileStorageEnum.getByStorage(storage);\n        Assert.notNull(storageEnum, String.format(\"文件配置(%s) 为空\", storageEnum));\n        // 创建客户端\n        return (AbstractFileClient<Config>) ReflectUtil.newInstance(storageEnum.getClientClass(), configId, config);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/framework/file/core/client/db/DBFileClient.java",
    "content": "package co.yixiang.yshop.module.infra.framework.file.core.client.db;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.extra.spring.SpringUtil;\nimport co.yixiang.yshop.module.infra.dal.dataobject.file.FileContentDO;\nimport co.yixiang.yshop.module.infra.dal.mysql.file.FileContentMapper;\nimport co.yixiang.yshop.module.infra.framework.file.core.client.AbstractFileClient;\n\nimport java.util.Comparator;\nimport java.util.List;\n\n/**\n * 基于 DB 存储的文件客户端的配置类\n *\n * @author yshop\n */\npublic class DBFileClient extends AbstractFileClient<DBFileClientConfig> {\n\n    private FileContentMapper fileContentMapper;\n\n    public DBFileClient(Long id, DBFileClientConfig config) {\n        super(id, config);\n    }\n\n    @Override\n    protected void doInit() {\n        fileContentMapper = SpringUtil.getBean(FileContentMapper.class);\n    }\n\n    @Override\n    public String upload(byte[] content, String path, String type) {\n        FileContentDO contentDO = new FileContentDO().setConfigId(getId())\n                .setPath(path).setContent(content);\n        fileContentMapper.insert(contentDO);\n        // 拼接返回路径\n        return super.formatFileUrl(config.getDomain(), path);\n    }\n\n    @Override\n    public void delete(String path) {\n        fileContentMapper.deleteByConfigIdAndPath(getId(), path);\n    }\n\n    @Override\n    public byte[] getContent(String path) {\n        List<FileContentDO> list = fileContentMapper.selectListByConfigIdAndPath(getId(), path);\n        if (CollUtil.isEmpty(list)) {\n            return null;\n        }\n        // 排序后，拿 id 最大的，即最后上传的\n        list.sort(Comparator.comparing(FileContentDO::getId));\n        return CollUtil.getLast(list).getContent();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/framework/file/core/client/db/DBFileClientConfig.java",
    "content": "package co.yixiang.yshop.module.infra.framework.file.core.client.db;\n\nimport co.yixiang.yshop.module.infra.framework.file.core.client.FileClientConfig;\nimport lombok.Data;\nimport org.hibernate.validator.constraints.URL;\n\nimport jakarta.validation.constraints.NotEmpty;\n\n/**\n * 基于 DB 存储的文件客户端的配置类\n *\n * @author yshop\n */\n@Data\npublic class DBFileClientConfig implements FileClientConfig {\n\n    /**\n     * 自定义域名\n     */\n    @NotEmpty(message = \"domain 不能为空\")\n    @URL(message = \"domain 必须是 URL 格式\")\n    private String domain;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/framework/file/core/client/ftp/FtpFileClient.java",
    "content": "package co.yixiang.yshop.module.infra.framework.file.core.client.ftp;\n\nimport cn.hutool.core.io.FileUtil;\nimport cn.hutool.core.util.CharsetUtil;\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.extra.ftp.Ftp;\nimport cn.hutool.extra.ftp.FtpException;\nimport cn.hutool.extra.ftp.FtpMode;\nimport co.yixiang.yshop.module.infra.framework.file.core.client.AbstractFileClient;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.ByteArrayOutputStream;\n\n/**\n * Ftp 文件客户端\n *\n * @author yshop\n */\npublic class FtpFileClient extends AbstractFileClient<FtpFileClientConfig> {\n\n    private Ftp ftp;\n\n    public FtpFileClient(Long id, FtpFileClientConfig config) {\n        super(id, config);\n    }\n\n    @Override\n    protected void doInit() {\n        // 把配置的 \\ 替换成 /, 如果路径配置 \\a\\test, 替换成 /a/test, 替换方法已经处理 null 情况\n        config.setBasePath(StrUtil.replace(config.getBasePath(), StrUtil.BACKSLASH, StrUtil.SLASH));\n        // ftp的路径是 / 结尾\n        if (!config.getBasePath().endsWith(StrUtil.SLASH)) {\n            config.setBasePath(config.getBasePath() + StrUtil.SLASH);\n        }\n        // 初始化 Ftp 对象\n        this.ftp = new Ftp(config.getHost(), config.getPort(), config.getUsername(), config.getPassword(),\n                CharsetUtil.CHARSET_UTF_8, null, null, FtpMode.valueOf(config.getMode()));\n    }\n\n    @Override\n    public String upload(byte[] content, String path, String type) {\n        // 执行写入\n        String filePath = getFilePath(path);\n        String fileName = FileUtil.getName(filePath);\n        String dir = StrUtil.removeSuffix(filePath, fileName);\n        ftp.reconnectIfTimeout();\n        boolean success = ftp.upload(dir, fileName, new ByteArrayInputStream(content));\n        if (!success) {\n            throw new FtpException(StrUtil.format(\"上传文件到目标目录 ({}) 失败\", filePath));\n        }\n        // 拼接返回路径\n        return super.formatFileUrl(config.getDomain(), path);\n    }\n\n    @Override\n    public void delete(String path) {\n        String filePath = getFilePath(path);\n        ftp.reconnectIfTimeout();\n        ftp.delFile(filePath);\n    }\n\n    @Override\n    public byte[] getContent(String path) {\n        String filePath = getFilePath(path);\n        String fileName = FileUtil.getName(filePath);\n        String dir = StrUtil.removeSuffix(filePath, fileName);\n        ByteArrayOutputStream out = new ByteArrayOutputStream();\n        ftp.reconnectIfTimeout();\n        ftp.download(dir, fileName, out);\n        return out.toByteArray();\n    }\n\n    private String getFilePath(String path) {\n        return config.getBasePath() + path;\n    }\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/framework/file/core/client/ftp/FtpFileClientConfig.java",
    "content": "package co.yixiang.yshop.module.infra.framework.file.core.client.ftp;\n\nimport co.yixiang.yshop.module.infra.framework.file.core.client.FileClientConfig;\nimport lombok.Data;\nimport org.hibernate.validator.constraints.URL;\n\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\n\n/**\n * Ftp 文件客户端的配置类\n *\n * @author yshop\n */\n@Data\npublic class FtpFileClientConfig implements FileClientConfig {\n\n    /**\n     * 基础路径\n     */\n    @NotEmpty(message = \"基础路径不能为空\")\n    private String basePath;\n\n    /**\n     * 自定义域名\n     */\n    @NotEmpty(message = \"domain 不能为空\")\n    @URL(message = \"domain 必须是 URL 格式\")\n    private String domain;\n\n    /**\n     * 主机地址\n     */\n    @NotEmpty(message = \"host 不能为空\")\n    private String host;\n    /**\n     * 主机端口\n     */\n    @NotNull(message = \"port 不能为空\")\n    private Integer port;\n    /**\n     * 用户名\n     */\n    @NotEmpty(message = \"用户名不能为空\")\n    private String username;\n    /**\n     * 密码\n     */\n    @NotEmpty(message = \"密码不能为空\")\n    private String password;\n    /**\n     * 连接模式\n     *\n     * 使用 {@link  cn.hutool.extra.ftp.FtpMode} 对应的字符串\n     */\n    @NotEmpty(message = \"连接模式不能为空\")\n    private String mode;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/framework/file/core/client/local/LocalFileClient.java",
    "content": "package co.yixiang.yshop.module.infra.framework.file.core.client.local;\n\nimport cn.hutool.core.io.FileUtil;\nimport co.yixiang.yshop.module.infra.framework.file.core.client.AbstractFileClient;\n\nimport java.io.File;\n\n/**\n * 本地文件客户端\n *\n * @author yshop\n */\npublic class LocalFileClient extends AbstractFileClient<LocalFileClientConfig> {\n\n    public LocalFileClient(Long id, LocalFileClientConfig config) {\n        super(id, config);\n    }\n\n    @Override\n    protected void doInit() {\n        // 补全风格。例如说 Linux 是 /，Windows 是 \\\n        if (!config.getBasePath().endsWith(File.separator)) {\n            config.setBasePath(config.getBasePath() + File.separator);\n        }\n    }\n\n    @Override\n    public String upload(byte[] content, String path, String type) {\n        // 执行写入\n        String filePath = getFilePath(path);\n        FileUtil.writeBytes(content, filePath);\n        // 拼接返回路径\n        return super.formatFileUrl(config.getDomain(), path);\n    }\n\n    @Override\n    public void delete(String path) {\n        String filePath = getFilePath(path);\n        FileUtil.del(filePath);\n    }\n\n    @Override\n    public byte[] getContent(String path) {\n        String filePath = getFilePath(path);\n        return FileUtil.readBytes(filePath);\n    }\n\n    private String getFilePath(String path) {\n        return config.getBasePath() + path;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/framework/file/core/client/local/LocalFileClientConfig.java",
    "content": "package co.yixiang.yshop.module.infra.framework.file.core.client.local;\n\nimport co.yixiang.yshop.module.infra.framework.file.core.client.FileClientConfig;\nimport lombok.Data;\nimport org.hibernate.validator.constraints.URL;\n\nimport jakarta.validation.constraints.NotEmpty;\n\n/**\n * 本地文件客户端的配置类\n *\n * @author yshop\n */\n@Data\npublic class LocalFileClientConfig implements FileClientConfig {\n\n    /**\n     * 基础路径\n     */\n    @NotEmpty(message = \"基础路径不能为空\")\n    private String basePath;\n\n    /**\n     * 自定义域名\n     */\n    @NotEmpty(message = \"domain 不能为空\")\n    @URL(message = \"domain 必须是 URL 格式\")\n    private String domain;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/framework/file/core/client/s3/FilePresignedUrlRespDTO.java",
    "content": "package co.yixiang.yshop.module.infra.framework.file.core.client.s3;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\n/**\n * 文件预签名地址 Response DTO\n *\n * @author owen\n */\n@Data\n@AllArgsConstructor\n@NoArgsConstructor\npublic class FilePresignedUrlRespDTO {\n\n    /**\n     * 文件上传 URL（用于上传）\n     *\n     * 例如说：\n     */\n    private String uploadUrl;\n\n    /**\n     * 文件 URL（用于读取、下载等）\n     */\n    private String url;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/framework/file/core/client/s3/S3FileClient.java",
    "content": "package co.yixiang.yshop.module.infra.framework.file.core.client.s3;\n\nimport cn.hutool.core.io.IoUtil;\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.http.HttpUtil;\nimport co.yixiang.yshop.module.infra.framework.file.core.client.AbstractFileClient;\nimport io.minio.*;\nimport io.minio.http.Method;\n\nimport java.io.ByteArrayInputStream;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * 基于 S3 协议的文件客户端，实现 MinIO、阿里云、腾讯云、七牛云、华为云等云服务\n * <p>\n * S3 协议的客户端，采用亚马逊提供的 software.amazon.awssdk.s3 库\n *\n * @author yshop\n */\npublic class S3FileClient extends AbstractFileClient<S3FileClientConfig> {\n\n    private MinioClient client;\n\n    public S3FileClient(Long id, S3FileClientConfig config) {\n        super(id, config);\n    }\n\n    @Override\n    protected void doInit() {\n        // 补全 domain\n        if (StrUtil.isEmpty(config.getDomain())) {\n            config.setDomain(buildDomain());\n        }\n        // 初始化客户端\n        client = MinioClient.builder()\n                .endpoint(buildEndpointURL()) // Endpoint URL\n                .region(buildRegion()) // Region\n                .credentials(config.getAccessKey(), config.getAccessSecret()) // 认证密钥\n                .build();\n    }\n\n    /**\n     * 基于 endpoint 构建调用云服务的 URL 地址\n     *\n     * @return URI 地址\n     */\n    private String buildEndpointURL() {\n        // 如果已经是 http 或者 https，则不进行拼接.主要适配 MinIO\n        if (HttpUtil.isHttp(config.getEndpoint()) || HttpUtil.isHttps(config.getEndpoint())) {\n            return config.getEndpoint();\n        }\n        return StrUtil.format(\"https://{}\", config.getEndpoint());\n    }\n\n    /**\n     * 基于 bucket + endpoint 构建访问的 Domain 地址\n     *\n     * @return Domain 地址\n     */\n    private String buildDomain() {\n        // 如果已经是 http 或者 https，则不进行拼接.主要适配 MinIO\n        if (HttpUtil.isHttp(config.getEndpoint()) || HttpUtil.isHttps(config.getEndpoint())) {\n            return StrUtil.format(\"{}/{}\", config.getEndpoint(), config.getBucket());\n        }\n        // 阿里云、腾讯云、华为云都适合。七牛云比较特殊，必须有自定义域名\n        return StrUtil.format(\"https://{}.{}\", config.getBucket(), config.getEndpoint());\n    }\n\n    /**\n     * 基于 bucket 构建 region 地区\n     *\n     * @return region 地区\n     */\n    private String buildRegion() {\n        // 阿里云必须有 region，否则会报错\n        if (config.getEndpoint().contains(S3FileClientConfig.ENDPOINT_ALIYUN)) {\n            return StrUtil.subBefore(config.getEndpoint(), '.', false)\n                    .replaceAll(\"-internal\", \"\")// 去除内网 Endpoint 的后缀\n                    .replaceAll(\"https://\", \"\");\n        }\n        // 腾讯云必须有 region，否则会报错\n        if (config.getEndpoint().contains(S3FileClientConfig.ENDPOINT_TENCENT)) {\n            return StrUtil.subAfter(config.getEndpoint(), \"cos.\", false)\n                    .replaceAll(\".\" + S3FileClientConfig.ENDPOINT_TENCENT, \"\"); // 去除 Endpoint\n        }\n        return null;\n    }\n\n    @Override\n    public String upload(byte[] content, String path, String type) throws Exception {\n        // 执行上传\n        client.putObject(PutObjectArgs.builder()\n                .bucket(config.getBucket()) // bucket 必须传递\n                .contentType(type)\n                .object(path) // 相对路径作为 key\n                .stream(new ByteArrayInputStream(content), content.length, -1) // 文件内容\n                .build());\n        // 拼接返回路径\n        return config.getDomain() + \"/\" + path;\n    }\n\n    @Override\n    public void delete(String path) throws Exception {\n        client.removeObject(RemoveObjectArgs.builder()\n                .bucket(config.getBucket()) // bucket 必须传递\n                .object(path) // 相对路径作为 key\n                .build());\n    }\n\n    @Override\n    public byte[] getContent(String path) throws Exception {\n        GetObjectResponse response = client.getObject(GetObjectArgs.builder()\n                .bucket(config.getBucket()) // bucket 必须传递\n                .object(path) // 相对路径作为 key\n                .build());\n        return IoUtil.readBytes(response);\n    }\n\n    @Override\n    public FilePresignedUrlRespDTO getPresignedObjectUrl(String path) throws Exception {\n        String uploadUrl = client.getPresignedObjectUrl(GetPresignedObjectUrlArgs.builder()\n                .method(Method.PUT)\n                .bucket(config.getBucket())\n                .object(path)\n                .expiry(10, TimeUnit.MINUTES) // 过期时间（秒数）取值范围：1 秒 ~ 7 天\n                .build()\n        );\n        return new FilePresignedUrlRespDTO(uploadUrl, config.getDomain() + \"/\" + path);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/framework/file/core/client/s3/S3FileClientConfig.java",
    "content": "package co.yixiang.yshop.module.infra.framework.file.core.client.s3;\n\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.module.infra.framework.file.core.client.FileClientConfig;\nimport com.fasterxml.jackson.annotation.JsonIgnore;\nimport lombok.Data;\nimport org.hibernate.validator.constraints.URL;\n\nimport jakarta.validation.constraints.AssertTrue;\nimport jakarta.validation.constraints.NotNull;\n\n/**\n * S3 文件客户端的配置类\n *\n * @author yshop\n */\n@Data\npublic class S3FileClientConfig implements FileClientConfig {\n\n    public static final String ENDPOINT_QINIU = \"qiniucs.com\";\n    public static final String ENDPOINT_ALIYUN = \"aliyuncs.com\";\n    public static final String ENDPOINT_TENCENT = \"myqcloud.com\";\n\n    /**\n     * 节点地址\n     * 1. MinIO：https://www.yixiang.co/Spring-Boot/MinIO 。例如说，http://127.0.0.1:9000\n     * 2. 阿里云：https://help.aliyun.com/document_detail/31837.html\n     * 3. 腾讯云：https://cloud.tencent.com/document/product/436/6224\n     * 4. 七牛云：https://developer.qiniu.com/kodo/4088/s3-access-domainname\n     * 5. 华为云：https://developer.huaweicloud.com/endpoint?OBS\n     */\n    @NotNull(message = \"endpoint 不能为空\")\n    private String endpoint;\n    /**\n     * 自定义域名\n     * 1. MinIO：通过 Nginx 配置\n     * 2. 阿里云：https://help.aliyun.com/document_detail/31836.html\n     * 3. 腾讯云：https://cloud.tencent.com/document/product/436/11142\n     * 4. 七牛云：https://developer.qiniu.com/kodo/8556/set-the-custom-source-domain-name\n     * 5. 华为云：https://support.huaweicloud.com/usermanual-obs/obs_03_0032.html\n     */\n    @URL(message = \"domain 必须是 URL 格式\")\n    private String domain;\n    /**\n     * 存储 Bucket\n     */\n    @NotNull(message = \"bucket 不能为空\")\n    private String bucket;\n\n    /**\n     * 访问 Key\n     * 1. MinIO：https://www.yixiang.co/Spring-Boot/MinIO\n     * 2. 阿里云：https://ram.console.aliyun.com/manage/ak\n     * 3. 腾讯云：https://console.cloud.tencent.com/cam/capi\n     * 4. 七牛云：https://portal.qiniu.com/user/key\n     * 5. 华为云：https://support.huaweicloud.com/qs-obs/obs_qs_0005.html\n     */\n    @NotNull(message = \"accessKey 不能为空\")\n    private String accessKey;\n    /**\n     * 访问 Secret\n     */\n    @NotNull(message = \"accessSecret 不能为空\")\n    private String accessSecret;\n\n    @SuppressWarnings(\"RedundantIfStatement\")\n    @AssertTrue(message = \"domain 不能为空\")\n    @JsonIgnore\n    public boolean isDomainValid() {\n        // 如果是七牛，必须带有 domain\n        if (StrUtil.contains(endpoint, ENDPOINT_QINIU) && StrUtil.isEmpty(domain)) {\n            return false;\n        }\n        return true;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/framework/file/core/client/sftp/SftpFileClient.java",
    "content": "package co.yixiang.yshop.module.infra.framework.file.core.client.sftp;\n\nimport cn.hutool.core.io.FileUtil;\nimport cn.hutool.extra.ssh.Sftp;\nimport co.yixiang.yshop.framework.common.util.io.FileUtils;\nimport co.yixiang.yshop.module.infra.framework.file.core.client.AbstractFileClient;\n\nimport java.io.File;\n\n/**\n * Sftp 文件客户端\n *\n * @author yshop\n */\npublic class SftpFileClient extends AbstractFileClient<SftpFileClientConfig> {\n\n    private Sftp sftp;\n\n    public SftpFileClient(Long id, SftpFileClientConfig config) {\n        super(id, config);\n    }\n\n    @Override\n    protected void doInit() {\n        // 补全风格。例如说 Linux 是 /，Windows 是 \\\n        if (!config.getBasePath().endsWith(File.separator)) {\n            config.setBasePath(config.getBasePath() + File.separator);\n        }\n        // 初始化 Ftp 对象\n        this.sftp = new Sftp(config.getHost(), config.getPort(), config.getUsername(), config.getPassword());\n    }\n\n    @Override\n    public String upload(byte[] content, String path, String type) {\n        // 执行写入\n        String filePath = getFilePath(path);\n        File file = FileUtils.createTempFile(content);\n        sftp.upload(filePath, file);\n        // 拼接返回路径\n        return super.formatFileUrl(config.getDomain(), path);\n    }\n\n    @Override\n    public void delete(String path) {\n        String filePath = getFilePath(path);\n        sftp.delFile(filePath);\n    }\n\n    @Override\n    public byte[] getContent(String path) {\n        String filePath = getFilePath(path);\n        File destFile = FileUtils.createTempFile();\n        sftp.download(filePath, destFile);\n        return FileUtil.readBytes(destFile);\n    }\n\n    private String getFilePath(String path) {\n        return config.getBasePath() + path;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/framework/file/core/client/sftp/SftpFileClientConfig.java",
    "content": "package co.yixiang.yshop.module.infra.framework.file.core.client.sftp;\n\nimport co.yixiang.yshop.module.infra.framework.file.core.client.FileClientConfig;\nimport lombok.Data;\nimport org.hibernate.validator.constraints.URL;\n\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\n\n/**\n * Sftp 文件客户端的配置类\n *\n * @author yshop\n */\n@Data\npublic class SftpFileClientConfig implements FileClientConfig {\n\n    /**\n     * 基础路径\n     */\n    @NotEmpty(message = \"基础路径不能为空\")\n    private String basePath;\n\n    /**\n     * 自定义域名\n     */\n    @NotEmpty(message = \"domain 不能为空\")\n    @URL(message = \"domain 必须是 URL 格式\")\n    private String domain;\n\n    /**\n     * 主机地址\n     */\n    @NotEmpty(message = \"host 不能为空\")\n    private String host;\n    /**\n     * 主机端口\n     */\n    @NotNull(message = \"port 不能为空\")\n    private Integer port;\n    /**\n     * 用户名\n     */\n    @NotEmpty(message = \"用户名不能为空\")\n    private String username;\n    /**\n     * 密码\n     */\n    @NotEmpty(message = \"密码不能为空\")\n    private String password;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/framework/file/core/enums/FileStorageEnum.java",
    "content": "package co.yixiang.yshop.module.infra.framework.file.core.enums;\n\nimport cn.hutool.core.util.ArrayUtil;\nimport co.yixiang.yshop.module.infra.framework.file.core.client.FileClient;\nimport co.yixiang.yshop.module.infra.framework.file.core.client.FileClientConfig;\nimport co.yixiang.yshop.module.infra.framework.file.core.client.db.DBFileClient;\nimport co.yixiang.yshop.module.infra.framework.file.core.client.db.DBFileClientConfig;\nimport co.yixiang.yshop.module.infra.framework.file.core.client.ftp.FtpFileClient;\nimport co.yixiang.yshop.module.infra.framework.file.core.client.ftp.FtpFileClientConfig;\nimport co.yixiang.yshop.module.infra.framework.file.core.client.local.LocalFileClient;\nimport co.yixiang.yshop.module.infra.framework.file.core.client.local.LocalFileClientConfig;\nimport co.yixiang.yshop.module.infra.framework.file.core.client.s3.S3FileClient;\nimport co.yixiang.yshop.module.infra.framework.file.core.client.s3.S3FileClientConfig;\nimport co.yixiang.yshop.module.infra.framework.file.core.client.sftp.SftpFileClient;\nimport co.yixiang.yshop.module.infra.framework.file.core.client.sftp.SftpFileClientConfig;\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * 文件存储器枚举\n *\n * @author yshop\n */\n@AllArgsConstructor\n@Getter\npublic enum FileStorageEnum {\n\n    DB(1, DBFileClientConfig.class, DBFileClient.class),\n\n    LOCAL(10, LocalFileClientConfig.class, LocalFileClient.class),\n    FTP(11, FtpFileClientConfig.class, FtpFileClient.class),\n    SFTP(12, SftpFileClientConfig.class, SftpFileClient.class),\n\n    S3(20, S3FileClientConfig.class, S3FileClient.class),\n    ;\n\n    /**\n     * 存储器\n     */\n    private final Integer storage;\n\n    /**\n     * 配置类\n     */\n    private final Class<? extends FileClientConfig> configClass;\n    /**\n     * 客户端类\n     */\n    private final Class<? extends FileClient> clientClass;\n\n    public static FileStorageEnum getByStorage(Integer storage) {\n        return ArrayUtil.firstMatch(o -> o.getStorage().equals(storage), values());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/framework/file/core/utils/FileTypeUtils.java",
    "content": "package co.yixiang.yshop.module.infra.framework.file.core.utils;\n\nimport cn.hutool.core.io.IoUtil;\nimport cn.hutool.core.util.StrUtil;\nimport com.alibaba.ttl.TransmittableThreadLocal;\nimport jakarta.servlet.http.HttpServletResponse;\nimport lombok.SneakyThrows;\nimport org.apache.tika.Tika;\n\nimport java.io.IOException;\nimport java.net.URLEncoder;\n\n/**\n * 文件类型 Utils\n *\n * @author yshop\n */\npublic class FileTypeUtils {\n\n    private static final ThreadLocal<Tika> TIKA = TransmittableThreadLocal.withInitial(Tika::new);\n\n    /**\n     * 获得文件的 mineType，对于doc，jar等文件会有误差\n     *\n     * @param data 文件内容\n     * @return mineType 无法识别时会返回“application/octet-stream”\n     */\n    @SneakyThrows\n    public static String getMineType(byte[] data) {\n        return TIKA.get().detect(data);\n    }\n\n    /**\n     * 已知文件名，获取文件类型，在某些情况下比通过字节数组准确，例如使用jar文件时，通过名字更为准确\n     *\n     * @param name 文件名\n     * @return mineType 无法识别时会返回“application/octet-stream”\n     */\n    public static String getMineType(String name) {\n        return TIKA.get().detect(name);\n    }\n\n    /**\n     * 在拥有文件和数据的情况下，最好使用此方法，最为准确\n     *\n     * @param data 文件内容\n     * @param name 文件名\n     * @return mineType 无法识别时会返回“application/octet-stream”\n     */\n    public static String getMineType(byte[] data, String name) {\n        return TIKA.get().detect(data, name);\n    }\n\n    /**\n     * 返回附件\n     *\n     * @param response 响应\n     * @param filename 文件名\n     * @param content  附件内容\n     */\n    public static void writeAttachment(HttpServletResponse response, String filename, byte[] content) throws IOException {\n        // 设置 header 和 contentType\n        response.setHeader(\"Content-Disposition\", \"attachment;filename=\" + URLEncoder.encode(filename, \"UTF-8\"));\n        String contentType = getMineType(content, filename);\n        response.setContentType(contentType);\n        // 针对 video 的特殊处理，解决视频地址在移动端播放的兼容性问题\n        if (StrUtil.containsIgnoreCase(contentType, \"video\")) {\n            response.setHeader(\"Content-Length\", String.valueOf(content.length - 1));\n            response.setHeader(\"Content-Range\", String.valueOf(content.length - 1));\n            response.setHeader(\"Accept-Ranges\", \"bytes\");\n        }\n        // 输出附件\n        IoUtil.write(response.getOutputStream(), false, content);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/framework/monitor/config/AdminServerConfiguration.java",
    "content": "package co.yixiang.yshop.module.infra.framework.monitor.config;\n\nimport de.codecentric.boot.admin.server.config.EnableAdminServer;\nimport org.springframework.context.annotation.Configuration;\n\n@Configuration(proxyBeanMethods = false)\n@EnableAdminServer\npublic class AdminServerConfiguration {\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/framework/security/config/SecurityConfiguration.java",
    "content": "package co.yixiang.yshop.module.infra.framework.security.config;\n\nimport co.yixiang.yshop.framework.security.config.AuthorizeRequestsCustomizer;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.security.config.annotation.web.builders.HttpSecurity;\nimport org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;\n\n/**\n * Infra 模块的 Security 配置\n */\n@Configuration(proxyBeanMethods = false, value = \"infraSecurityConfiguration\")\npublic class SecurityConfiguration {\n\n    @Value(\"${spring.boot.admin.context-path:''}\")\n    private String adminSeverContextPath;\n\n    @Bean(\"infraAuthorizeRequestsCustomizer\")\n    public AuthorizeRequestsCustomizer authorizeRequestsCustomizer() {\n        return new AuthorizeRequestsCustomizer() {\n\n            @Override\n            public void customize(AuthorizeHttpRequestsConfigurer<HttpSecurity>.AuthorizationManagerRequestMatcherRegistry registry) {\n                // Swagger 接口文档\n                registry.requestMatchers(\"/v3/api-docs/**\").permitAll()\n                        .requestMatchers(\"/swagger-ui.html\").permitAll()\n                        .requestMatchers(\"/swagger-ui/**\").permitAll()\n                        .requestMatchers(\"/swagger-resources/**\").permitAll()\n                        .requestMatchers(\"/webjars/**\").permitAll()\n                        .requestMatchers(\"/*/api-docs\").permitAll();\n                // Spring Boot Actuator 的安全配置\n                registry.requestMatchers(\"/actuator\").permitAll()\n                        .requestMatchers(\"/actuator/**\").permitAll();\n                // Druid 监控\n                registry.requestMatchers(\"/druid/**\").permitAll();\n                // Spring Boot Admin Server 的安全配置\n                registry.requestMatchers(adminSeverContextPath).permitAll()\n                        .requestMatchers(adminSeverContextPath + \"/**\").permitAll();\n                // 文件读取\n                registry.requestMatchers(buildAdminApi(\"/infra/file/*/get/**\")).permitAll();\n            }\n\n        };\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/framework/web/config/InfraWebConfiguration.java",
    "content": "package co.yixiang.yshop.module.infra.framework.web.config;\n\nimport co.yixiang.yshop.framework.swagger.config.YshopSwaggerAutoConfiguration;\nimport org.springdoc.core.models.GroupedOpenApi;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n/**\n * infra 模块的 web 组件的 Configuration\n *\n * @author yshop\n */\n@Configuration(proxyBeanMethods = false)\npublic class InfraWebConfiguration {\n\n    /**\n     * infra 模块的 API 分组\n     */\n    @Bean\n    public GroupedOpenApi infraGroupedOpenApi() {\n        return YshopSwaggerAutoConfiguration.buildGroupedOpenApi(\"infra\");\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/job/job/JobLogCleanJob.java",
    "content": "package co.yixiang.yshop.module.infra.job.job;\n\nimport co.yixiang.yshop.framework.quartz.core.handler.JobHandler;\nimport co.yixiang.yshop.framework.tenant.core.aop.TenantIgnore;\nimport co.yixiang.yshop.module.infra.service.job.JobLogService;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Component;\nimport jakarta.annotation.Resource;\n\n/**\n * 物理删除 N 天前的任务日志的 Job\n *\n * @author j-sentinel\n */\n@Slf4j\n@Component\npublic class JobLogCleanJob implements JobHandler {\n\n    @Resource\n    private JobLogService jobLogService;\n\n    /**\n     * 清理超过（14）天的日志\n     */\n    private static final Integer JOB_CLEAN_RETAIN_DAY = 14;\n\n    /**\n     * 每次删除间隔的条数，如果值太高可能会造成数据库的压力过大\n     */\n    private static final Integer DELETE_LIMIT = 100;\n\n    @Override\n    @TenantIgnore\n    public String execute(String param) {\n        Integer count = jobLogService.cleanJobLog(JOB_CLEAN_RETAIN_DAY, DELETE_LIMIT);\n        log.info(\"[execute][定时执行清理定时任务日志数量 ({}) 个]\", count);\n        return String.format(\"定时执行清理定时任务日志数量 %s 个\", count);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/job/logger/AccessLogCleanJob.java",
    "content": "package co.yixiang.yshop.module.infra.job.logger;\n\nimport co.yixiang.yshop.framework.quartz.core.handler.JobHandler;\nimport co.yixiang.yshop.framework.tenant.core.aop.TenantIgnore;\nimport co.yixiang.yshop.module.infra.service.logger.ApiAccessLogService;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Component;\n\nimport jakarta.annotation.Resource;\n\n/**\n * 物理删除 N 天前的访问日志的 Job\n *\n * @author j-sentinel\n */\n@Component\n@Slf4j\npublic class AccessLogCleanJob implements JobHandler {\n\n    @Resource\n    private ApiAccessLogService apiAccessLogService;\n\n    /**\n     * 清理超过（14）天的日志\n     */\n    private static final Integer JOB_CLEAN_RETAIN_DAY = 14;\n\n    /**\n     * 每次删除间隔的条数，如果值太高可能会造成数据库的压力过大\n     */\n    private static final Integer DELETE_LIMIT = 100;\n\n    @Override\n    @TenantIgnore\n    public String execute(String param) {\n        Integer count = apiAccessLogService.cleanAccessLog(JOB_CLEAN_RETAIN_DAY, DELETE_LIMIT);\n        log.info(\"[execute][定时执行清理访问日志数量 ({}) 个]\", count);\n        return String.format(\"定时执行清理访问日志数量 %s 个\", count);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/job/logger/ErrorLogCleanJob.java",
    "content": "package co.yixiang.yshop.module.infra.job.logger;\n\nimport co.yixiang.yshop.framework.quartz.core.handler.JobHandler;\nimport co.yixiang.yshop.framework.tenant.core.aop.TenantIgnore;\nimport co.yixiang.yshop.module.infra.service.logger.ApiErrorLogService;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Component;\n\nimport jakarta.annotation.Resource;\n\n/**\n * 物理删除 N 天前的错误日志的 Job\n *\n * @author j-sentinel\n */\n@Slf4j\n@Component\npublic class ErrorLogCleanJob implements JobHandler {\n\n    @Resource\n    private ApiErrorLogService apiErrorLogService;\n\n    /**\n     * 清理超过（14）天的日志\n     */\n    private static final Integer JOB_CLEAN_RETAIN_DAY = 14;\n\n    /**\n     * 每次删除间隔的条数，如果值太高可能会造成数据库的压力过大\n     */\n    private static final Integer DELETE_LIMIT = 100;\n\n    @Override\n    @TenantIgnore\n    public String execute(String param) {\n        Integer count = apiErrorLogService.cleanErrorLog(JOB_CLEAN_RETAIN_DAY,DELETE_LIMIT);\n        log.info(\"[execute][定时执行清理错误日志数量 ({}) 个]\", count);\n        return String.format(\"定时执行清理错误日志数量 %s 个\", count);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/service/codegen/CodegenService.java",
    "content": "package co.yixiang.yshop.module.infra.service.codegen;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.infra.controller.admin.codegen.vo.CodegenCreateListReqVO;\nimport co.yixiang.yshop.module.infra.controller.admin.codegen.vo.CodegenUpdateReqVO;\nimport co.yixiang.yshop.module.infra.controller.admin.codegen.vo.table.CodegenTablePageReqVO;\nimport co.yixiang.yshop.module.infra.controller.admin.codegen.vo.table.DatabaseTableRespVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.codegen.CodegenColumnDO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.codegen.CodegenTableDO;\n\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * 代码生成 Service 接口\n *\n * @author yshop\n */\npublic interface CodegenService {\n\n    /**\n     * 基于数据库的表结构，创建代码生成器的表定义\n     *\n     * @param userId 用户编号\n     * @param reqVO 表信息\n     * @return 创建的表定义的编号数组\n     */\n    List<Long> createCodegenList(Long userId, CodegenCreateListReqVO reqVO);\n\n    /**\n     * 更新数据库的表和字段定义\n     *\n     * @param updateReqVO 更新信息\n     */\n    void updateCodegen(CodegenUpdateReqVO updateReqVO);\n\n    /**\n     * 基于数据库的表结构，同步数据库的表和字段定义\n     *\n     * @param tableId 表编号\n     */\n    void syncCodegenFromDB(Long tableId);\n\n    /**\n     * 删除数据库的表和字段定义\n     *\n     * @param tableId 数据编号\n     */\n    void deleteCodegen(Long tableId);\n\n    /**\n     * 获得表定义列表\n     *\n     * @param dataSourceConfigId 数据源配置的编号\n     * @return 表定义列表\n     */\n    List<CodegenTableDO> getCodegenTableList(Long dataSourceConfigId);\n\n    /**\n     * 获得表定义分页\n     *\n     * @param pageReqVO 分页条件\n     * @return 表定义分页\n     */\n    PageResult<CodegenTableDO> getCodegenTablePage(CodegenTablePageReqVO pageReqVO);\n\n    /**\n     * 获得表定义\n     *\n     * @param id 表编号\n     * @return 表定义\n     */\n    CodegenTableDO getCodegenTable(Long id);\n\n    /**\n     * 获得指定表的字段定义数组\n     *\n     * @param tableId 表编号\n     * @return 字段定义数组\n     */\n    List<CodegenColumnDO> getCodegenColumnListByTableId(Long tableId);\n\n    /**\n     * 执行指定表的代码生成\n     *\n     * @param tableId 表编号\n     * @return 生成结果。key 为文件路径，value 为对应的代码内容\n     */\n    Map<String, String> generationCodes(Long tableId);\n\n    /**\n     * 获得数据库自带的表定义列表\n     *\n     * @param dataSourceConfigId 数据源的配置编号\n     * @param name 表名称\n     * @param comment 表描述\n     * @return 表定义列表\n     */\n    List<DatabaseTableRespVO> getDatabaseTableList(Long dataSourceConfigId, String name, String comment);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/service/codegen/CodegenServiceImpl.java",
    "content": "package co.yixiang.yshop.module.infra.service.codegen;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.infra.controller.admin.codegen.vo.CodegenCreateListReqVO;\nimport co.yixiang.yshop.module.infra.controller.admin.codegen.vo.CodegenUpdateReqVO;\nimport co.yixiang.yshop.module.infra.controller.admin.codegen.vo.table.CodegenTablePageReqVO;\nimport co.yixiang.yshop.module.infra.controller.admin.codegen.vo.table.DatabaseTableRespVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.codegen.CodegenColumnDO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.codegen.CodegenTableDO;\nimport co.yixiang.yshop.module.infra.dal.mysql.codegen.CodegenColumnMapper;\nimport co.yixiang.yshop.module.infra.dal.mysql.codegen.CodegenTableMapper;\nimport co.yixiang.yshop.module.infra.enums.codegen.CodegenSceneEnum;\nimport co.yixiang.yshop.module.infra.enums.codegen.CodegenTemplateTypeEnum;\nimport co.yixiang.yshop.module.infra.framework.codegen.config.CodegenProperties;\nimport co.yixiang.yshop.module.infra.service.codegen.inner.CodegenBuilder;\nimport co.yixiang.yshop.module.infra.service.codegen.inner.CodegenEngine;\nimport co.yixiang.yshop.module.infra.service.db.DatabaseTableService;\nimport co.yixiang.yshop.module.system.api.user.AdminUserApi;\nimport com.baomidou.mybatisplus.generator.config.po.TableField;\nimport com.baomidou.mybatisplus.generator.config.po.TableInfo;\nimport com.google.common.annotations.VisibleForTesting;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport jakarta.annotation.Resource;\nimport java.util.*;\nimport java.util.function.BiPredicate;\nimport java.util.stream.Collectors;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.framework.common.util.collection.CollectionUtils.convertMap;\nimport static co.yixiang.yshop.framework.common.util.collection.CollectionUtils.convertSet;\nimport static co.yixiang.yshop.module.infra.enums.ErrorCodeConstants.*;\n\n/**\n * 代码生成 Service 实现类\n *\n * @author yshop\n */\n@Service\npublic class CodegenServiceImpl implements CodegenService {\n\n    @Resource\n    private DatabaseTableService databaseTableService;\n\n    @Resource\n    private CodegenTableMapper codegenTableMapper;\n    @Resource\n    private CodegenColumnMapper codegenColumnMapper;\n\n    @Resource\n    private AdminUserApi userApi;\n\n    @Resource\n    private CodegenBuilder codegenBuilder;\n    @Resource\n    private CodegenEngine codegenEngine;\n\n    @Resource\n    private CodegenProperties codegenProperties;\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public List<Long> createCodegenList(Long userId, CodegenCreateListReqVO reqVO) {\n        List<Long> ids = new ArrayList<>(reqVO.getTableNames().size());\n        // 遍历添加。虽然效率会低一点，但是没必要做成完全批量，因为不会这么大量\n        reqVO.getTableNames().forEach(tableName -> ids.add(createCodegen(userId, reqVO.getDataSourceConfigId(), tableName)));\n        return ids;\n    }\n\n    private Long createCodegen(Long userId, Long dataSourceConfigId, String tableName) {\n        // 从数据库中，获得数据库表结构\n        TableInfo tableInfo = databaseTableService.getTable(dataSourceConfigId, tableName);\n        // 导入\n        return createCodegen0(userId, dataSourceConfigId, tableInfo);\n    }\n\n    private Long createCodegen0(Long userId, Long dataSourceConfigId, TableInfo tableInfo) {\n        // 校验导入的表和字段非空\n        validateTableInfo(tableInfo);\n        // 校验是否已经存在\n        if (codegenTableMapper.selectByTableNameAndDataSourceConfigId(tableInfo.getName(),\n                dataSourceConfigId) != null) {\n            throw exception(CODEGEN_TABLE_EXISTS);\n        }\n\n        // 构建 CodegenTableDO 对象，插入到 DB 中\n        CodegenTableDO table = codegenBuilder.buildTable(tableInfo);\n        table.setDataSourceConfigId(dataSourceConfigId);\n        table.setScene(CodegenSceneEnum.ADMIN.getScene()); // 默认配置下，使用管理后台的模板\n        table.setFrontType(codegenProperties.getFrontType());\n        table.setAuthor(userApi.getUser(userId).getNickname());\n        codegenTableMapper.insert(table);\n\n        // 构建 CodegenColumnDO 数组，插入到 DB 中\n        List<CodegenColumnDO> columns = codegenBuilder.buildColumns(table.getId(), tableInfo.getFields());\n        // 如果没有主键，则使用第一个字段作为主键\n        if (!tableInfo.isHavePrimaryKey()) {\n            columns.get(0).setPrimaryKey(true);\n        }\n        codegenColumnMapper.insertBatch(columns);\n        return table.getId();\n    }\n\n    @VisibleForTesting\n    void validateTableInfo(TableInfo tableInfo) {\n        if (tableInfo == null) {\n            throw exception(CODEGEN_IMPORT_TABLE_NULL);\n        }\n        if (StrUtil.isEmpty(tableInfo.getComment())) {\n            throw exception(CODEGEN_TABLE_INFO_TABLE_COMMENT_IS_NULL);\n        }\n        if (CollUtil.isEmpty(tableInfo.getFields())) {\n            throw exception(CODEGEN_IMPORT_COLUMNS_NULL);\n        }\n        tableInfo.getFields().forEach(field -> {\n            if (StrUtil.isEmpty(field.getComment())) {\n                throw exception(CODEGEN_TABLE_INFO_COLUMN_COMMENT_IS_NULL, field.getName());\n            }\n        });\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void updateCodegen(CodegenUpdateReqVO updateReqVO) {\n        // 校验是否已经存在\n        if (codegenTableMapper.selectById(updateReqVO.getTable().getId()) == null) {\n            throw exception(CODEGEN_TABLE_NOT_EXISTS);\n        }\n        // 校验主表字段存在\n        if (Objects.equals(updateReqVO.getTable().getTemplateType(), CodegenTemplateTypeEnum.SUB.getType())) {\n            if (codegenTableMapper.selectById(updateReqVO.getTable().getMasterTableId()) == null) {\n                throw exception(CODEGEN_MASTER_TABLE_NOT_EXISTS, updateReqVO.getTable().getMasterTableId());\n            }\n            if (CollUtil.findOne(updateReqVO.getColumns(),  // 关联主表的字段不存在\n                    column -> column.getId().equals(updateReqVO.getTable().getSubJoinColumnId())) == null) {\n                throw exception(CODEGEN_SUB_COLUMN_NOT_EXISTS, updateReqVO.getTable().getSubJoinColumnId());\n            }\n        }\n\n        // 更新 table 表定义\n        CodegenTableDO updateTableObj = BeanUtils.toBean(updateReqVO.getTable(), CodegenTableDO.class);\n        codegenTableMapper.updateById(updateTableObj);\n        // 更新 column 字段定义\n        List<CodegenColumnDO> updateColumnObjs = BeanUtils.toBean(updateReqVO.getColumns(), CodegenColumnDO.class);\n        updateColumnObjs.forEach(updateColumnObj -> codegenColumnMapper.updateById(updateColumnObj));\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void syncCodegenFromDB(Long tableId) {\n        // 校验是否已经存在\n        CodegenTableDO table = codegenTableMapper.selectById(tableId);\n        if (table == null) {\n            throw exception(CODEGEN_TABLE_NOT_EXISTS);\n        }\n        // 从数据库中，获得数据库表结构\n        TableInfo tableInfo = databaseTableService.getTable(table.getDataSourceConfigId(), table.getTableName());\n        // 执行同步\n        syncCodegen0(tableId, tableInfo);\n    }\n\n    private void syncCodegen0(Long tableId, TableInfo tableInfo) {\n        // 1. 校验导入的表和字段非空\n        validateTableInfo(tableInfo);\n        List<TableField> tableFields = tableInfo.getFields();\n\n        // 2. 构建 CodegenColumnDO 数组，只同步新增的字段\n        List<CodegenColumnDO> codegenColumns = codegenColumnMapper.selectListByTableId(tableId);\n        Set<String> codegenColumnNames = convertSet(codegenColumns, CodegenColumnDO::getColumnName);\n\n        // 3.1 计算需要【修改】的字段，插入时重新插入，删除时将原来的删除\n        Map<String, CodegenColumnDO> codegenColumnDOMap = convertMap(codegenColumns, CodegenColumnDO::getColumnName);\n        BiPredicate<TableField, CodegenColumnDO> primaryKeyPredicate =\n                (tableField, codegenColumn) -> tableField.getMetaInfo().getJdbcType().name().equals(codegenColumn.getDataType())\n                        && tableField.getMetaInfo().isNullable() == codegenColumn.getNullable()\n                        && tableField.isKeyFlag() == codegenColumn.getPrimaryKey()\n                        && tableField.getComment().equals(codegenColumn.getColumnComment());\n        Set<String> modifyFieldNames = tableFields.stream()\n                .filter(tableField -> codegenColumnDOMap.get(tableField.getColumnName()) != null\n                        && !primaryKeyPredicate.test(tableField, codegenColumnDOMap.get(tableField.getColumnName())))\n                .map(TableField::getColumnName)\n                .collect(Collectors.toSet());\n        // 3.2 计算需要【删除】的字段\n        Set<String> tableFieldNames = convertSet(tableFields, TableField::getName);\n        Set<Long> deleteColumnIds = codegenColumns.stream()\n                .filter(column -> (!tableFieldNames.contains(column.getColumnName())) || modifyFieldNames.contains(column.getColumnName()))\n                .map(CodegenColumnDO::getId).collect(Collectors.toSet());\n        // 移除已经存在的字段\n        tableFields.removeIf(column -> codegenColumnNames.contains(column.getColumnName()) && (!modifyFieldNames.contains(column.getColumnName())));\n        if (CollUtil.isEmpty(tableFields) && CollUtil.isEmpty(deleteColumnIds)) {\n            throw exception(CODEGEN_SYNC_NONE_CHANGE);\n        }\n\n        // 4.1 插入新增的字段\n        List<CodegenColumnDO> columns = codegenBuilder.buildColumns(tableId, tableFields);\n        codegenColumnMapper.insertBatch(columns);\n        // 4.2 删除不存在的字段\n        if (CollUtil.isNotEmpty(deleteColumnIds)) {\n            codegenColumnMapper.deleteBatchIds(deleteColumnIds);\n        }\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void deleteCodegen(Long tableId) {\n        // 校验是否已经存在\n        if (codegenTableMapper.selectById(tableId) == null) {\n            throw exception(CODEGEN_TABLE_NOT_EXISTS);\n        }\n\n        // 删除 table 表定义\n        codegenTableMapper.deleteById(tableId);\n        // 删除 column 字段定义\n        codegenColumnMapper.deleteListByTableId(tableId);\n    }\n\n    @Override\n    public List<CodegenTableDO> getCodegenTableList(Long dataSourceConfigId) {\n        return codegenTableMapper.selectListByDataSourceConfigId(dataSourceConfigId);\n    }\n\n    @Override\n    public PageResult<CodegenTableDO> getCodegenTablePage(CodegenTablePageReqVO pageReqVO) {\n        return codegenTableMapper.selectPage(pageReqVO);\n    }\n\n    @Override\n    public CodegenTableDO getCodegenTable(Long id) {\n        return codegenTableMapper.selectById(id);\n    }\n\n    @Override\n    public List<CodegenColumnDO> getCodegenColumnListByTableId(Long tableId) {\n        return codegenColumnMapper.selectListByTableId(tableId);\n    }\n\n    @Override\n    public Map<String, String> generationCodes(Long tableId) {\n        // 校验是否已经存在\n        CodegenTableDO table = codegenTableMapper.selectById(tableId);\n        if (table == null) {\n            throw exception(CODEGEN_TABLE_NOT_EXISTS);\n        }\n        List<CodegenColumnDO> columns = codegenColumnMapper.selectListByTableId(tableId);\n        if (CollUtil.isEmpty(columns)) {\n            throw exception(CODEGEN_COLUMN_NOT_EXISTS);\n        }\n\n        // 如果是主子表，则加载对应的子表信息\n        List<CodegenTableDO> subTables = null;\n        List<List<CodegenColumnDO>> subColumnsList = null;\n        if (CodegenTemplateTypeEnum.isMaster(table.getTemplateType())) {\n            // 校验子表存在\n            subTables = codegenTableMapper.selectListByTemplateTypeAndMasterTableId(\n                    CodegenTemplateTypeEnum.SUB.getType(), tableId);\n            if (CollUtil.isEmpty(subTables)) {\n                throw exception(CODEGEN_MASTER_GENERATION_FAIL_NO_SUB_TABLE);\n            }\n            // 校验子表的关联字段存在\n            subColumnsList = new ArrayList<>();\n            for (CodegenTableDO subTable : subTables) {\n                List<CodegenColumnDO> subColumns = codegenColumnMapper.selectListByTableId(subTable.getId());\n                if (CollUtil.findOne(subColumns, column -> column.getId().equals(subTable.getSubJoinColumnId())) == null) {\n                    throw exception(CODEGEN_SUB_COLUMN_NOT_EXISTS, subTable.getId());\n                }\n                subColumnsList.add(subColumns);\n            }\n        }\n\n        // 执行生成\n        return codegenEngine.execute(table, columns, subTables, subColumnsList);\n    }\n\n    @Override\n    public List<DatabaseTableRespVO> getDatabaseTableList(Long dataSourceConfigId, String name, String comment) {\n        List<TableInfo> tables = databaseTableService.getTableList(dataSourceConfigId, name, comment);\n        // 移除在 Codegen 中，已经存在的\n        Set<String> existsTables = convertSet(\n                codegenTableMapper.selectListByDataSourceConfigId(dataSourceConfigId), CodegenTableDO::getTableName);\n        tables.removeIf(table -> existsTables.contains(table.getName()));\n        return BeanUtils.toBean(tables, DatabaseTableRespVO.class);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/service/codegen/inner/CodegenBuilder.java",
    "content": "package co.yixiang.yshop.module.infra.service.codegen.inner;\n\nimport cn.hutool.core.map.MapUtil;\nimport cn.hutool.core.util.ReflectUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport co.yixiang.yshop.module.infra.convert.codegen.CodegenConvert;\nimport co.yixiang.yshop.module.infra.dal.dataobject.codegen.CodegenColumnDO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.codegen.CodegenTableDO;\nimport co.yixiang.yshop.module.infra.enums.codegen.CodegenColumnHtmlTypeEnum;\nimport co.yixiang.yshop.module.infra.enums.codegen.CodegenColumnListConditionEnum;\nimport co.yixiang.yshop.module.infra.enums.codegen.CodegenTemplateTypeEnum;\nimport com.baomidou.mybatisplus.generator.config.po.TableField;\nimport com.baomidou.mybatisplus.generator.config.po.TableInfo;\nimport com.google.common.collect.Sets;\nimport org.springframework.stereotype.Component;\n\nimport java.time.LocalDateTime;\nimport java.util.*;\n\nimport static cn.hutool.core.text.CharSequenceUtil.*;\nimport static cn.hutool.core.util.RandomUtil.randomEle;\nimport static cn.hutool.core.util.RandomUtil.randomInt;\n\n/**\n * 代码生成器的 Builder，负责：\n * 1. 将数据库的表 {@link TableInfo} 定义，构建成 {@link CodegenTableDO}\n * 2. 将数据库的列 {@link TableField} 构定义，建成 {@link CodegenColumnDO}\n */\n@Component\npublic class CodegenBuilder {\n\n    /**\n     * 字段名与 {@link CodegenColumnListConditionEnum} 的默认映射\n     * 注意，字段的匹配以后缀的方式\n     */\n    private static final Map<String, CodegenColumnListConditionEnum> COLUMN_LIST_OPERATION_CONDITION_MAPPINGS =\n            MapUtil.<String, CodegenColumnListConditionEnum>builder()\n                    .put(\"name\", CodegenColumnListConditionEnum.LIKE)\n                    .put(\"time\", CodegenColumnListConditionEnum.BETWEEN)\n                    .put(\"date\", CodegenColumnListConditionEnum.BETWEEN)\n                    .build();\n\n    /**\n     * 字段名与 {@link CodegenColumnHtmlTypeEnum} 的默认映射\n     * 注意，字段的匹配以后缀的方式\n     */\n    private static final Map<String, CodegenColumnHtmlTypeEnum> COLUMN_HTML_TYPE_MAPPINGS =\n            MapUtil.<String, CodegenColumnHtmlTypeEnum>builder()\n                    .put(\"status\", CodegenColumnHtmlTypeEnum.RADIO)\n                    .put(\"sex\", CodegenColumnHtmlTypeEnum.RADIO)\n                    .put(\"type\", CodegenColumnHtmlTypeEnum.SELECT)\n                    .put(\"image\", CodegenColumnHtmlTypeEnum.IMAGE_UPLOAD)\n                    .put(\"file\", CodegenColumnHtmlTypeEnum.FILE_UPLOAD)\n                    .put(\"content\", CodegenColumnHtmlTypeEnum.EDITOR)\n                    .put(\"description\", CodegenColumnHtmlTypeEnum.EDITOR)\n                    .put(\"demo\", CodegenColumnHtmlTypeEnum.EDITOR)\n                    .put(\"time\", CodegenColumnHtmlTypeEnum.DATETIME)\n                    .put(\"date\", CodegenColumnHtmlTypeEnum.DATETIME)\n                    .build();\n\n    /**\n     * 多租户编号的字段名\n     */\n    public static final String TENANT_ID_FIELD = \"tenantId\";\n    /**\n     * {@link co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO} 的字段\n     */\n    public static final Set<String> BASE_DO_FIELDS = new HashSet<>();\n    /**\n     * 新增操作，不需要传递的字段\n     */\n    private static final Set<String> CREATE_OPERATION_EXCLUDE_COLUMN = Sets.newHashSet(\"id\");\n    /**\n     * 修改操作，不需要传递的字段\n     */\n    private static final Set<String> UPDATE_OPERATION_EXCLUDE_COLUMN = Sets.newHashSet();\n    /**\n     * 列表操作的条件，不需要传递的字段\n     */\n    private static final Set<String> LIST_OPERATION_EXCLUDE_COLUMN = Sets.newHashSet(\"id\");\n    /**\n     * 列表操作的结果，不需要返回的字段\n     */\n    private static final Set<String> LIST_OPERATION_RESULT_EXCLUDE_COLUMN = Sets.newHashSet();\n\n    static {\n        Arrays.stream(ReflectUtil.getFields(BaseDO.class)).forEach(field -> BASE_DO_FIELDS.add(field.getName()));\n        BASE_DO_FIELDS.add(TENANT_ID_FIELD);\n        // 处理 OPERATION 相关的字段\n        CREATE_OPERATION_EXCLUDE_COLUMN.addAll(BASE_DO_FIELDS);\n        UPDATE_OPERATION_EXCLUDE_COLUMN.addAll(BASE_DO_FIELDS);\n        LIST_OPERATION_EXCLUDE_COLUMN.addAll(BASE_DO_FIELDS);\n        LIST_OPERATION_EXCLUDE_COLUMN.remove(\"createTime\"); // 创建时间，还是可能需要传递的\n        LIST_OPERATION_RESULT_EXCLUDE_COLUMN.addAll(BASE_DO_FIELDS);\n        LIST_OPERATION_RESULT_EXCLUDE_COLUMN.remove(\"createTime\"); // 创建时间，还是需要返回的\n    }\n\n    public CodegenTableDO buildTable(TableInfo tableInfo) {\n        CodegenTableDO table = CodegenConvert.INSTANCE.convert(tableInfo);\n        initTableDefault(table);\n        return table;\n    }\n\n    /**\n     * 初始化 Table 表的默认字段\n     *\n     * @param table 表定义\n     */\n    private void initTableDefault(CodegenTableDO table) {\n        // 以 system_dept 举例子。moduleName 为 system、businessName 为 dept、className 为 Dept\n        // 如果希望以 System 前缀，则可以手动在【代码生成 - 修改生成配置 - 基本信息】，将实体类名称改为 SystemDept 即可\n        String tableName = table.getTableName().toLowerCase();\n        // 第一步，_ 前缀的前面，作为 module 名字；第二步，moduleName 必须小写；\n        table.setModuleName(subBefore(tableName, '_', false).toLowerCase());\n        // 第一步，第一个 _ 前缀的后面，作为 module 名字; 第二步，可能存在多个 _ 的情况，转换成驼峰; 第三步，businessName 必须小写；\n        table.setBusinessName(toCamelCase(subAfter(tableName, '_', false)).toLowerCase());\n        // 驼峰 + 首字母大写；第一步，第一个 _ 前缀的后面，作为 class 名字；第二步，驼峰命名\n        table.setClassName(upperFirst(toCamelCase(subAfter(tableName, '_', false))));\n        // 去除结尾的表，作为类描述\n        table.setClassComment(StrUtil.removeSuffixIgnoreCase(table.getTableComment(), \"表\"));\n        table.setTemplateType(CodegenTemplateTypeEnum.ONE.getType());\n    }\n\n    public List<CodegenColumnDO> buildColumns(Long tableId, List<TableField> tableFields) {\n        List<CodegenColumnDO> columns = CodegenConvert.INSTANCE.convertList(tableFields);\n        int index = 1;\n        for (CodegenColumnDO column : columns) {\n            column.setTableId(tableId);\n            column.setOrdinalPosition(index++);\n            // 特殊处理：Byte => Integer\n            if (Byte.class.getSimpleName().equals(column.getJavaType())) {\n                column.setJavaType(Integer.class.getSimpleName());\n            }\n            // 初始化 Column 列的默认字段\n            processColumnOperation(column); // 处理 CRUD 相关的字段的默认值\n            processColumnUI(column); // 处理 UI 相关的字段的默认值\n            processColumnExample(column); // 处理字段的 swagger example 示例\n        }\n        return columns;\n    }\n\n    private void processColumnOperation(CodegenColumnDO column) {\n        // 处理 createOperation 字段\n        column.setCreateOperation(!CREATE_OPERATION_EXCLUDE_COLUMN.contains(column.getJavaField())\n                && !column.getPrimaryKey()); // 对于主键，创建时无需传递\n        // 处理 updateOperation 字段\n        column.setUpdateOperation(!UPDATE_OPERATION_EXCLUDE_COLUMN.contains(column.getJavaField())\n                || column.getPrimaryKey()); // 对于主键，更新时需要传递\n        // 处理 listOperation 字段\n        column.setListOperation(!LIST_OPERATION_EXCLUDE_COLUMN.contains(column.getJavaField())\n                && !column.getPrimaryKey()); // 对于主键，列表过滤不需要传递\n        // 处理 listOperationCondition 字段\n        COLUMN_LIST_OPERATION_CONDITION_MAPPINGS.entrySet().stream()\n                .filter(entry -> StrUtil.endWithIgnoreCase(column.getJavaField(), entry.getKey()))\n                .findFirst().ifPresent(entry -> column.setListOperationCondition(entry.getValue().getCondition()));\n        if (column.getListOperationCondition() == null) {\n            column.setListOperationCondition(CodegenColumnListConditionEnum.EQ.getCondition());\n        }\n        // 处理 listOperationResult 字段\n        column.setListOperationResult(!LIST_OPERATION_RESULT_EXCLUDE_COLUMN.contains(column.getJavaField()));\n    }\n\n    private void processColumnUI(CodegenColumnDO column) {\n        // 基于后缀进行匹配\n        COLUMN_HTML_TYPE_MAPPINGS.entrySet().stream()\n                .filter(entry -> StrUtil.endWithIgnoreCase(column.getJavaField(), entry.getKey()))\n                .findFirst().ifPresent(entry -> column.setHtmlType(entry.getValue().getType()));\n        // 如果是 Boolean 类型时，设置为 radio 类型.\n        if (Boolean.class.getSimpleName().equals(column.getJavaType())) {\n            column.setHtmlType(CodegenColumnHtmlTypeEnum.RADIO.getType());\n        }\n        // 如果是 LocalDateTime 类型，则设置为 datetime 类型\n        if (LocalDateTime.class.getSimpleName().equals(column.getJavaType())) {\n            column.setHtmlType(CodegenColumnHtmlTypeEnum.DATETIME.getType());\n        }\n        // 兜底，设置默认为 input 类型\n        if (column.getHtmlType() == null) {\n            column.setHtmlType(CodegenColumnHtmlTypeEnum.INPUT.getType());\n        }\n    }\n\n    /**\n     * 处理字段的 swagger example 示例\n     *\n     * @param column 字段\n     */\n    private void processColumnExample(CodegenColumnDO column) {\n        // id、price、count 等可能是整数的后缀\n        if (StrUtil.endWithAnyIgnoreCase(column.getJavaField(), \"id\", \"price\", \"count\")) {\n            column.setExample(String.valueOf(randomInt(1, Short.MAX_VALUE)));\n            return;\n        }\n        // name\n        if (StrUtil.endWithIgnoreCase(column.getJavaField(), \"name\")) {\n            column.setExample(randomEle(new String[]{\"张三\", \"李四\", \"王五\", \"赵六\", \"yshop\"}));\n            return;\n        }\n        // status\n        if (StrUtil.endWithAnyIgnoreCase(column.getJavaField(), \"status\", \"type\")) {\n            column.setExample(randomEle(new String[]{\"1\", \"2\"}));\n            return;\n        }\n        // url\n        if (StrUtil.endWithIgnoreCase(column.getColumnName(), \"url\")) {\n            column.setExample(\"https://www.yixiang.co\");\n            return;\n        }\n        // reason\n        if (StrUtil.endWithIgnoreCase(column.getColumnName(), \"reason\")) {\n            column.setExample(randomEle(new String[]{\"不喜欢\", \"不对\", \"不好\", \"不香\"}));\n            return;\n        }\n        // description、memo、remark\n        if (StrUtil.endWithAnyIgnoreCase(column.getColumnName(), \"description\", \"memo\", \"remark\")) {\n            column.setExample(randomEle(new String[]{\"你猜\", \"随便\", \"你说的对\"}));\n            return;\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/service/codegen/inner/CodegenEngine.java",
    "content": "package co.yixiang.yshop.module.infra.service.codegen.inner;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.map.MapUtil;\nimport cn.hutool.core.util.ObjectUtil;\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.extra.template.TemplateConfig;\nimport cn.hutool.extra.template.TemplateEngine;\nimport cn.hutool.extra.template.engine.velocity.VelocityEngine;\nimport cn.hutool.system.SystemUtil;\nimport co.yixiang.yshop.framework.apilog.core.annotation.ApiAccessLog;\nimport co.yixiang.yshop.framework.apilog.core.enums.OperateTypeEnum;\nimport co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.collection.CollectionUtils;\nimport co.yixiang.yshop.framework.common.util.date.DateUtils;\nimport co.yixiang.yshop.framework.common.util.date.LocalDateTimeUtils;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.framework.common.util.object.ObjectUtils;\nimport co.yixiang.yshop.framework.common.util.string.StrUtils;\nimport co.yixiang.yshop.framework.excel.core.annotations.DictFormat;\nimport co.yixiang.yshop.framework.excel.core.convert.DictConvert;\nimport co.yixiang.yshop.framework.excel.core.util.ExcelUtils;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.infra.dal.dataobject.codegen.CodegenColumnDO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.codegen.CodegenTableDO;\nimport co.yixiang.yshop.module.infra.enums.codegen.CodegenFrontTypeEnum;\nimport co.yixiang.yshop.module.infra.enums.codegen.CodegenSceneEnum;\nimport co.yixiang.yshop.module.infra.enums.codegen.CodegenTemplateTypeEnum;\nimport co.yixiang.yshop.module.infra.framework.codegen.config.CodegenProperties;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.collect.ImmutableTable;\nimport com.google.common.collect.Maps;\nimport com.google.common.collect.Table;\nimport jakarta.annotation.PostConstruct;\nimport jakarta.annotation.Resource;\nimport lombok.Setter;\nimport org.springframework.stereotype.Component;\n\nimport java.util.*;\n\nimport static cn.hutool.core.map.MapUtil.getStr;\nimport static cn.hutool.core.text.CharSequenceUtil.*;\n\n/**\n * 代码生成的引擎，用于具体生成代码\n * 目前基于 {@link org.apache.velocity.app.Velocity} 模板引擎实现\n *\n * 考虑到 Java 模板引擎的框架非常多，Freemarker、Velocity、Thymeleaf 等等，所以我们采用 hutool 封装的 {@link cn.hutool.extra.template.Template} 抽象\n *\n * @author yshop\n */\n@Component\npublic class CodegenEngine {\n\n    /**\n     * 后端的模板配置\n     *\n     * key：模板在 resources 的地址\n     * value：生成的路径\n     */\n    private static final Map<String, String> SERVER_TEMPLATES = MapUtil.<String, String>builder(new LinkedHashMap<>()) // 有序\n            // Java module-biz Main\n            .put(javaTemplatePath(\"controller/vo/pageReqVO\"), javaModuleImplVOFilePath(\"PageReqVO\"))\n            .put(javaTemplatePath(\"controller/vo/listReqVO\"), javaModuleImplVOFilePath(\"ListReqVO\"))\n            .put(javaTemplatePath(\"controller/vo/respVO\"), javaModuleImplVOFilePath(\"RespVO\"))\n            .put(javaTemplatePath(\"controller/vo/saveReqVO\"), javaModuleImplVOFilePath(\"SaveReqVO\"))\n            .put(javaTemplatePath(\"controller/controller\"), javaModuleImplControllerFilePath())\n            .put(javaTemplatePath(\"dal/do\"),\n                    javaModuleImplMainFilePath(\"dal/dataobject/${table.businessName}/${table.className}DO\"))\n            .put(javaTemplatePath(\"dal/do_sub\"), // 特殊：主子表专属逻辑\n                    javaModuleImplMainFilePath(\"dal/dataobject/${table.businessName}/${subTable.className}DO\"))\n            .put(javaTemplatePath(\"dal/mapper\"),\n                    javaModuleImplMainFilePath(\"dal/mysql/${table.businessName}/${table.className}Mapper\"))\n            .put(javaTemplatePath(\"dal/mapper_sub\"), // 特殊：主子表专属逻辑\n                    javaModuleImplMainFilePath(\"dal/mysql/${table.businessName}/${subTable.className}Mapper\"))\n            .put(javaTemplatePath(\"dal/mapper.xml\"), mapperXmlFilePath())\n            .put(javaTemplatePath(\"service/serviceImpl\"),\n                    javaModuleImplMainFilePath(\"service/${table.businessName}/${table.className}ServiceImpl\"))\n            .put(javaTemplatePath(\"service/service\"),\n                    javaModuleImplMainFilePath(\"service/${table.businessName}/${table.className}Service\"))\n            // Java module-biz Test\n            .put(javaTemplatePath(\"test/serviceTest\"),\n                    javaModuleImplTestFilePath(\"service/${table.businessName}/${table.className}ServiceImplTest\"))\n            // Java module-api Main\n            .put(javaTemplatePath(\"enums/errorcode\"), javaModuleApiMainFilePath(\"enums/ErrorCodeConstants_手动操作\"))\n            // SQL\n            .put(\"codegen/sql/sql.vm\", \"sql/sql.sql\")\n            .put(\"codegen/sql/h2.vm\", \"sql/h2.sql\")\n            .build();\n\n    /**\n     * 后端的配置模版\n     *\n     * key1：UI 模版的类型 {@link CodegenFrontTypeEnum#getType()}\n     * key2：模板在 resources 的地址\n     * value：生成的路径\n     */\n    private static final Table<Integer, String, String> FRONT_TEMPLATES = ImmutableTable.<Integer, String, String>builder()\n            // Vue2 标准模版\n            .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath(\"views/index.vue\"),\n                    vueFilePath(\"views/${table.moduleName}/${table.businessName}/index.vue\"))\n            .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath(\"api/api.js\"),\n                    vueFilePath(\"api/${table.moduleName}/${table.businessName}/index.js\"))\n            .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath(\"views/form.vue\"),\n                    vueFilePath(\"views/${table.moduleName}/${table.businessName}/${simpleClassName}Form.vue\"))\n            .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath(\"views/components/form_sub_normal.vue\"),  // 特殊：主子表专属逻辑\n                    vueFilePath(\"views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}Form.vue\"))\n            .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath(\"views/components/form_sub_inner.vue\"),  // 特殊：主子表专属逻辑\n                    vueFilePath(\"views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}Form.vue\"))\n            .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath(\"views/components/form_sub_erp.vue\"),  // 特殊：主子表专属逻辑\n                    vueFilePath(\"views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}Form.vue\"))\n            .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath(\"views/components/list_sub_inner.vue\"),  // 特殊：主子表专属逻辑\n                    vueFilePath(\"views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}List.vue\"))\n            .put(CodegenFrontTypeEnum.VUE2.getType(), vueTemplatePath(\"views/components/list_sub_erp.vue\"),  // 特殊：主子表专属逻辑\n                    vueFilePath(\"views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}List.vue\"))\n            // Vue3 标准模版\n            .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath(\"views/index.vue\"),\n                    vue3FilePath(\"views/${table.moduleName}/${table.businessName}/index.vue\"))\n            .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath(\"views/form.vue\"),\n                    vue3FilePath(\"views/${table.moduleName}/${table.businessName}/${simpleClassName}Form.vue\"))\n            .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath(\"views/components/form_sub_normal.vue\"),  // 特殊：主子表专属逻辑\n                    vue3FilePath(\"views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}Form.vue\"))\n            .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath(\"views/components/form_sub_inner.vue\"),  // 特殊：主子表专属逻辑\n                    vue3FilePath(\"views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}Form.vue\"))\n            .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath(\"views/components/form_sub_erp.vue\"),  // 特殊：主子表专属逻辑\n                    vue3FilePath(\"views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}Form.vue\"))\n            .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath(\"views/components/list_sub_inner.vue\"),  // 特殊：主子表专属逻辑\n                    vue3FilePath(\"views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}List.vue\"))\n            .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath(\"views/components/list_sub_erp.vue\"),  // 特殊：主子表专属逻辑\n                    vue3FilePath(\"views/${table.moduleName}/${table.businessName}/components/${subSimpleClassName}List.vue\"))\n            .put(CodegenFrontTypeEnum.VUE3.getType(), vue3TemplatePath(\"api/api.ts\"),\n                    vue3FilePath(\"api/${table.moduleName}/${table.businessName}/index.ts\"))\n            // Vue3 Schema 模版\n            .put(CodegenFrontTypeEnum.VUE3_SCHEMA.getType(), vue3SchemaTemplatePath(\"views/data.ts\"),\n                    vue3FilePath(\"views/${table.moduleName}/${table.businessName}/${classNameVar}.data.ts\"))\n            .put(CodegenFrontTypeEnum.VUE3_SCHEMA.getType(), vue3SchemaTemplatePath(\"views/index.vue\"),\n                    vue3FilePath(\"views/${table.moduleName}/${table.businessName}/index.vue\"))\n            .put(CodegenFrontTypeEnum.VUE3_SCHEMA.getType(), vue3SchemaTemplatePath(\"views/form.vue\"),\n                    vue3FilePath(\"views/${table.moduleName}/${table.businessName}/${simpleClassName}Form.vue\"))\n            .put(CodegenFrontTypeEnum.VUE3_SCHEMA.getType(), vue3SchemaTemplatePath(\"api/api.ts\"),\n                    vue3FilePath(\"api/${table.moduleName}/${table.businessName}/index.ts\"))\n            // Vue3 vben 模版\n            .put(CodegenFrontTypeEnum.VUE3_VBEN.getType(), vue3VbenTemplatePath(\"views/data.ts\"),\n                    vue3FilePath(\"views/${table.moduleName}/${table.businessName}/${classNameVar}.data.ts\"))\n            .put(CodegenFrontTypeEnum.VUE3_VBEN.getType(), vue3VbenTemplatePath(\"views/index.vue\"),\n                    vue3FilePath(\"views/${table.moduleName}/${table.businessName}/index.vue\"))\n            .put(CodegenFrontTypeEnum.VUE3_VBEN.getType(), vue3VbenTemplatePath(\"views/form.vue\"),\n                    vue3FilePath(\"views/${table.moduleName}/${table.businessName}/${simpleClassName}Modal.vue\"))\n            .put(CodegenFrontTypeEnum.VUE3_VBEN.getType(), vue3VbenTemplatePath(\"api/api.ts\"),\n                    vue3FilePath(\"api/${table.moduleName}/${table.businessName}/index.ts\"))\n            .build();\n\n    @Resource\n    private CodegenProperties codegenProperties;\n\n    /**\n     * 是否使用 jakarta 包，用于解决 Spring Boot 2.X 和 3.X 的兼容性问题\n     *\n     * true  - 使用 jakarta.validation.constraints.*\n     * false - 使用 jakarta.validation.constraints.*\n     */\n    @Setter // 允许设置的原因，是因为单测需要手动改变\n    private Boolean jakartaEnable;\n\n    /**\n     * 模板引擎，由 hutool 实现\n     */\n    private final TemplateEngine templateEngine;\n    /**\n     * 全局通用变量映射\n     */\n    private final Map<String, Object> globalBindingMap = new HashMap<>();\n\n    public CodegenEngine() {\n        // 初始化 TemplateEngine 属性\n        TemplateConfig config = new TemplateConfig();\n        config.setResourceMode(TemplateConfig.ResourceMode.CLASSPATH);\n        this.templateEngine = new VelocityEngine(config);\n        // 设置 javaxEnable，按照是否使用 JDK17 来判断\n        this.jakartaEnable = SystemUtil.getJavaInfo().isJavaVersionAtLeast(1700); // 17.00 * 100\n    }\n\n    @PostConstruct\n    @VisibleForTesting\n    void initGlobalBindingMap() {\n        // 全局配置\n        globalBindingMap.put(\"basePackage\", codegenProperties.getBasePackage());\n        globalBindingMap.put(\"baseFrameworkPackage\", codegenProperties.getBasePackage()\n                + '.' + \"framework\"); // 用于后续获取测试类的 package 地址\n        globalBindingMap.put(\"jakartaPackage\", jakartaEnable ? \"jakarta\" : \"javax\");\n        // 全局 Java Bean\n        globalBindingMap.put(\"CommonResultClassName\", CommonResult.class.getName());\n        globalBindingMap.put(\"PageResultClassName\", PageResult.class.getName());\n        // VO 类，独有字段\n        globalBindingMap.put(\"PageParamClassName\", PageParam.class.getName());\n        globalBindingMap.put(\"DictFormatClassName\", DictFormat.class.getName());\n        // DO 类，独有字段\n        globalBindingMap.put(\"BaseDOClassName\", BaseDO.class.getName());\n        globalBindingMap.put(\"baseDOFields\", CodegenBuilder.BASE_DO_FIELDS);\n        globalBindingMap.put(\"QueryWrapperClassName\", LambdaQueryWrapperX.class.getName());\n        globalBindingMap.put(\"BaseMapperClassName\", BaseMapperX.class.getName());\n        // Util 工具类\n        globalBindingMap.put(\"ServiceExceptionUtilClassName\", ServiceExceptionUtil.class.getName());\n        globalBindingMap.put(\"DateUtilsClassName\", DateUtils.class.getName());\n        globalBindingMap.put(\"ExcelUtilsClassName\", ExcelUtils.class.getName());\n        globalBindingMap.put(\"LocalDateTimeUtilsClassName\", LocalDateTimeUtils.class.getName());\n        globalBindingMap.put(\"ObjectUtilsClassName\", ObjectUtils.class.getName());\n        globalBindingMap.put(\"DictConvertClassName\", DictConvert.class.getName());\n        globalBindingMap.put(\"ApiAccessLogClassName\", ApiAccessLog.class.getName());\n        globalBindingMap.put(\"OperateTypeEnumClassName\", OperateTypeEnum.class.getName());\n        globalBindingMap.put(\"BeanUtils\", BeanUtils.class.getName());\n    }\n\n    /**\n     * 生成代码\n     *\n     * @param table 表定义\n     * @param columns table 的字段定义数组\n     * @param subTables 子表数组，当且仅当主子表时使用\n     * @param subColumnsList subTables 的字段定义数组\n     * @return 生成的代码，key 是路径，value 是对应代码\n     */\n    public Map<String, String> execute(CodegenTableDO table, List<CodegenColumnDO> columns,\n                                       List<CodegenTableDO> subTables, List<List<CodegenColumnDO>> subColumnsList) {\n        // 1.1 初始化 bindMap 上下文\n        Map<String, Object> bindingMap = initBindingMap(table, columns, subTables, subColumnsList);\n        // 1.2 获得模版\n        Map<String, String> templates = getTemplates(table.getFrontType());\n\n        // 2. 执行生成\n        Map<String, String> result = Maps.newLinkedHashMapWithExpectedSize(templates.size()); // 有序\n        templates.forEach((vmPath, filePath) -> {\n            // 2.1 特殊：主子表专属逻辑\n            if (isSubTemplate(vmPath)) {\n                generateSubCode(table, subTables, result, vmPath, filePath, bindingMap);\n                return;\n                // 2.2 特殊：树表专属逻辑\n            } else if (isPageReqVOTemplate(vmPath)) {\n                // 减少多余的类生成，例如说 PageVO.java 类\n                if (CodegenTemplateTypeEnum.isTree(table.getTemplateType())) {\n                    return;\n                }\n            } else if (isListReqVOTemplate(vmPath)) {\n                // 减少多余的类生成，例如说 ListVO.java 类\n                if (!CodegenTemplateTypeEnum.isTree(table.getTemplateType())) {\n                    return;\n                }\n            }\n            // 2.3 默认生成\n            generateCode(result, vmPath, filePath, bindingMap);\n        });\n        return result;\n    }\n\n    private void generateCode(Map<String, String> result, String vmPath,\n                              String filePath, Map<String, Object> bindingMap) {\n        filePath = formatFilePath(filePath, bindingMap);\n        String content = templateEngine.getTemplate(vmPath).render(bindingMap);\n        // 格式化代码\n        content = prettyCode(content);\n        result.put(filePath, content);\n    }\n\n    private void generateSubCode(CodegenTableDO table, List<CodegenTableDO> subTables,\n                                 Map<String, String> result, String vmPath,\n                                 String filePath, Map<String, Object> bindingMap) {\n        // 没有子表，所以不生成\n        if (CollUtil.isEmpty(subTables)) {\n            return;\n        }\n        // 主子表的模式匹配。目的：过滤掉个性化的模版\n        if (vmPath.contains(\"_normal\")\n                && ObjectUtil.notEqual(table.getTemplateType(), CodegenTemplateTypeEnum.MASTER_NORMAL.getType())) {\n            return;\n        }\n        if (vmPath.contains(\"_erp\")\n                && ObjectUtil.notEqual(table.getTemplateType(), CodegenTemplateTypeEnum.MASTER_ERP.getType())) {\n            return;\n        }\n        if (vmPath.contains(\"_inner\")\n                && ObjectUtil.notEqual(table.getTemplateType(), CodegenTemplateTypeEnum.MASTER_INNER.getType())) {\n            return;\n        }\n\n        // 逐个生成\n        for (int i = 0; i < subTables.size(); i++) {\n            bindingMap.put(\"subIndex\", i);\n            generateCode(result, vmPath, filePath, bindingMap);\n        }\n        bindingMap.remove(\"subIndex\");\n    }\n\n    /**\n     * 格式化生成后的代码\n     *\n     * 因为尽量让 vm 模版简单，所以统一的处理都在这个方法。\n     * 如果不处理，Vue 的 Pretty 格式校验可能会报错\n     *\n     * @param content 格式化前的代码\n     * @return 格式化后的代码\n     */\n    private String prettyCode(String content) {\n        // Vue 界面：去除字段后面多余的 , 逗号，解决前端的 Pretty 代码格式检查的报错\n        content = content.replaceAll(\",\\n}\", \"\\n}\").replaceAll(\",\\n  }\", \"\\n  }\");\n        // Vue 界面：去除多的 dateFormatter，只有一个的情况下，说明没使用到\n        if (StrUtil.count(content, \"dateFormatter\") == 1) {\n            content = StrUtils.removeLineContains(content, \"dateFormatter\");\n        }\n        // Vue2 界面：修正 $refs\n        if (StrUtil.count(content, \"this.refs\") >= 1) {\n            content = content.replace(\"this.refs\", \"this.$refs\");\n        }\n        // Vue 界面：去除多的 dict 相关，只有一个的情况下，说明没使用到\n        if (StrUtil.count(content, \"getIntDictOptions\") == 1) {\n            content = content.replace(\"getIntDictOptions, \", \"\");\n        }\n        if (StrUtil.count(content, \"getStrDictOptions\") == 1) {\n            content = content.replace(\"getStrDictOptions, \", \"\");\n        }\n        if (StrUtil.count(content, \"getBoolDictOptions\") == 1) {\n            content = content.replace(\"getBoolDictOptions, \", \"\");\n        }\n        if (StrUtil.count(content, \"DICT_TYPE.\") == 0) {\n            content = StrUtils.removeLineContains(content, \"DICT_TYPE\");\n        }\n        return content;\n    }\n\n    private Map<String, Object> initBindingMap(CodegenTableDO table, List<CodegenColumnDO> columns,\n                                               List<CodegenTableDO> subTables, List<List<CodegenColumnDO>> subColumnsList) {\n        // 创建 bindingMap\n        Map<String, Object> bindingMap = new HashMap<>(globalBindingMap);\n        bindingMap.put(\"table\", table);\n        bindingMap.put(\"columns\", columns);\n        bindingMap.put(\"primaryColumn\", CollectionUtils.findFirst(columns, CodegenColumnDO::getPrimaryKey)); // 主键字段\n        bindingMap.put(\"sceneEnum\", CodegenSceneEnum.valueOf(table.getScene()));\n\n        // className 相关\n        // 去掉指定前缀，将 TestDictType 转换成 DictType. 因为在 create 等方法后，不需要带上 Test 前缀\n        String simpleClassName = removePrefix(table.getClassName(), upperFirst(table.getModuleName()));\n        bindingMap.put(\"simpleClassName\", simpleClassName);\n        bindingMap.put(\"simpleClassName_underlineCase\", toUnderlineCase(simpleClassName)); // 将 DictType 转换成 dict_type\n        bindingMap.put(\"classNameVar\", lowerFirst(simpleClassName)); // 将 DictType 转换成 dictType，用于变量\n        // 将 DictType 转换成 dict-type\n        String simpleClassNameStrikeCase = toSymbolCase(simpleClassName, '-');\n        bindingMap.put(\"simpleClassName_strikeCase\", simpleClassNameStrikeCase);\n        // permission 前缀\n        bindingMap.put(\"permissionPrefix\", table.getModuleName() + \":\" + simpleClassNameStrikeCase);\n\n        // 特殊：树表专属逻辑\n        if (CodegenTemplateTypeEnum.isTree(table.getTemplateType())) {\n            CodegenColumnDO treeParentColumn = CollUtil.findOne(columns,\n                    column -> Objects.equals(column.getId(), table.getTreeParentColumnId()));\n            bindingMap.put(\"treeParentColumn\", treeParentColumn);\n            bindingMap.put(\"treeParentColumn_javaField_underlineCase\", toUnderlineCase(treeParentColumn.getJavaField()));\n            CodegenColumnDO treeNameColumn = CollUtil.findOne(columns,\n                    column -> Objects.equals(column.getId(), table.getTreeNameColumnId()));\n            bindingMap.put(\"treeNameColumn\", treeNameColumn);\n            bindingMap.put(\"treeNameColumn_javaField_underlineCase\", toUnderlineCase(treeNameColumn.getJavaField()));\n        }\n\n        // 特殊：主子表专属逻辑\n        if (CollUtil.isNotEmpty(subTables)) {\n            // 创建 bindingMap\n            bindingMap.put(\"subTables\", subTables);\n            bindingMap.put(\"subColumnsList\", subColumnsList);\n            List<CodegenColumnDO> subPrimaryColumns = new ArrayList<>();\n            List<CodegenColumnDO> subJoinColumns = new ArrayList<>();\n            List<String> subJoinColumnStrikeCases = new ArrayList<>();\n            List<String> subSimpleClassNames = new ArrayList<>();\n            List<String> subClassNameVars = new ArrayList<>();\n            List<String> simpleClassNameUnderlineCases = new ArrayList<>();\n            List<String> subSimpleClassNameStrikeCases = new ArrayList<>();\n            for (int i = 0; i < subTables.size(); i++) {\n                CodegenTableDO subTable = subTables.get(i);\n                List<CodegenColumnDO> subColumns = subColumnsList.get(i);\n                subPrimaryColumns.add(CollectionUtils.findFirst(subColumns, CodegenColumnDO::getPrimaryKey)); //\n                CodegenColumnDO subColumn = CollectionUtils.findFirst(subColumns, // 关联的字段\n                        column -> Objects.equals(column.getId(), subTable.getSubJoinColumnId()));\n                subJoinColumns.add(subColumn);\n                subJoinColumnStrikeCases.add(toSymbolCase(subColumn.getJavaField(), '-')); // 将 DictType 转换成 dict-type\n                // className 相关\n                String subSimpleClassName = removePrefix(subTable.getClassName(), upperFirst(subTable.getModuleName()));\n                subSimpleClassNames.add(subSimpleClassName);\n                simpleClassNameUnderlineCases.add(toUnderlineCase(subSimpleClassName)); // 将 DictType 转换成 dict_type\n                subClassNameVars.add(lowerFirst(subSimpleClassName)); // 将 DictType 转换成 dictType，用于变量\n                subSimpleClassNameStrikeCases.add(toSymbolCase(subSimpleClassName, '-')); // 将 DictType 转换成 dict-type\n            }\n            bindingMap.put(\"subPrimaryColumns\", subPrimaryColumns);\n            bindingMap.put(\"subJoinColumns\", subJoinColumns);\n            bindingMap.put(\"subJoinColumn_strikeCases\", subJoinColumnStrikeCases);\n            bindingMap.put(\"subSimpleClassNames\", subSimpleClassNames);\n            bindingMap.put(\"simpleClassNameUnderlineCases\", simpleClassNameUnderlineCases);\n            bindingMap.put(\"subClassNameVars\", subClassNameVars);\n            bindingMap.put(\"subSimpleClassName_strikeCases\", subSimpleClassNameStrikeCases);\n        }\n        return bindingMap;\n    }\n\n    private Map<String, String> getTemplates(Integer frontType) {\n        Map<String, String> templates = new LinkedHashMap<>();\n        templates.putAll(SERVER_TEMPLATES);\n        templates.putAll(FRONT_TEMPLATES.row(frontType));\n        return templates;\n    }\n\n    @SuppressWarnings(\"unchecked\")\n    private String formatFilePath(String filePath, Map<String, Object> bindingMap) {\n        filePath = StrUtil.replace(filePath, \"${basePackage}\",\n                getStr(bindingMap, \"basePackage\").replaceAll(\"\\\\.\", \"/\"));\n        filePath = StrUtil.replace(filePath, \"${classNameVar}\",\n                getStr(bindingMap, \"classNameVar\"));\n        filePath = StrUtil.replace(filePath, \"${simpleClassName}\",\n                getStr(bindingMap, \"simpleClassName\"));\n        // sceneEnum 包含的字段\n        CodegenSceneEnum sceneEnum = (CodegenSceneEnum) bindingMap.get(\"sceneEnum\");\n        filePath = StrUtil.replace(filePath, \"${sceneEnum.prefixClass}\", sceneEnum.getPrefixClass());\n        filePath = StrUtil.replace(filePath, \"${sceneEnum.basePackage}\", sceneEnum.getBasePackage());\n        // table 包含的字段\n        CodegenTableDO table = (CodegenTableDO) bindingMap.get(\"table\");\n        filePath = StrUtil.replace(filePath, \"${table.moduleName}\", table.getModuleName());\n        filePath = StrUtil.replace(filePath, \"${table.businessName}\", table.getBusinessName());\n        filePath = StrUtil.replace(filePath, \"${table.className}\", table.getClassName());\n        // 特殊：主子表专属逻辑\n        Integer subIndex = (Integer) bindingMap.get(\"subIndex\");\n        if (subIndex != null) {\n            CodegenTableDO subTable = ((List<CodegenTableDO>) bindingMap.get(\"subTables\")).get(subIndex);\n            filePath = StrUtil.replace(filePath, \"${subTable.moduleName}\", subTable.getModuleName());\n            filePath = StrUtil.replace(filePath, \"${subTable.businessName}\", subTable.getBusinessName());\n            filePath = StrUtil.replace(filePath, \"${subTable.className}\", subTable.getClassName());\n            filePath = StrUtil.replace(filePath, \"${subSimpleClassName}\",\n                    ((List<String>) bindingMap.get(\"subSimpleClassNames\")).get(subIndex));\n        }\n        return filePath;\n    }\n\n    private static String javaTemplatePath(String path) {\n        return \"codegen/java/\" + path + \".vm\";\n    }\n\n    private static String javaModuleImplVOFilePath(String path) {\n        return javaModuleFilePath(\"controller/${sceneEnum.basePackage}/${table.businessName}/\" +\n                \"vo/${sceneEnum.prefixClass}${table.className}\" + path, \"biz\", \"main\");\n    }\n\n    private static String javaModuleImplControllerFilePath() {\n        return javaModuleFilePath(\"controller/${sceneEnum.basePackage}/${table.businessName}/\" +\n                \"${sceneEnum.prefixClass}${table.className}Controller\", \"biz\", \"main\");\n    }\n\n    private static String javaModuleImplMainFilePath(String path) {\n        return javaModuleFilePath(path, \"biz\", \"main\");\n    }\n\n    private static String javaModuleApiMainFilePath(String path) {\n        return javaModuleFilePath(path, \"api\", \"main\");\n    }\n\n    private static String javaModuleImplTestFilePath(String path) {\n        return javaModuleFilePath(path, \"biz\", \"test\");\n    }\n\n    private static String javaModuleFilePath(String path, String module, String src) {\n        return \"yshop-module-${table.moduleName}/\" + // 顶级模块\n                \"yshop-module-${table.moduleName}-\" + module + \"/\" + // 子模块\n                \"src/\" + src + \"/java/${basePackage}/module/${table.moduleName}/\" + path + \".java\";\n    }\n\n    private static String mapperXmlFilePath() {\n        return \"yshop-module-${table.moduleName}/\" + // 顶级模块\n                \"yshop-module-${table.moduleName}-biz/\" + // 子模块\n                \"src/main/resources/mapper/${table.businessName}/${table.className}Mapper.xml\";\n    }\n\n    private static String vueTemplatePath(String path) {\n        return \"codegen/vue/\" + path + \".vm\";\n    }\n\n    private static String vueFilePath(String path) {\n        return \"yshop-ui-${sceneEnum.basePackage}-vue2/\" + // 顶级目录\n                \"src/\" + path;\n    }\n\n    private static String vue3TemplatePath(String path) {\n        return \"codegen/vue3/\" + path + \".vm\";\n    }\n\n    private static String vue3FilePath(String path) {\n        return \"yshop-ui-${sceneEnum.basePackage}-vue3/\" + // 顶级目录\n                \"src/\" + path;\n    }\n\n    private static String vue3SchemaTemplatePath(String path) {\n        return \"codegen/vue3_schema/\" + path + \".vm\";\n    }\n\n    private static String vue3VbenTemplatePath(String path) {\n        return \"codegen/vue3_vben/\" + path + \".vm\";\n    }\n\n    private static boolean isSubTemplate(String path) {\n        return path.contains(\"_sub\");\n    }\n\n    private static boolean isPageReqVOTemplate(String path) {\n        return path.contains(\"pageReqVO\");\n    }\n\n    private static boolean isListReqVOTemplate(String path) {\n        return path.contains(\"listReqVO\");\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/service/config/ConfigService.java",
    "content": "package co.yixiang.yshop.module.infra.service.config;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.infra.controller.admin.config.vo.ConfigPageReqVO;\nimport co.yixiang.yshop.module.infra.controller.admin.config.vo.ConfigSaveReqVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.config.ConfigDO;\n\nimport jakarta.validation.Valid;\n\n/**\n * 参数配置 Service 接口\n *\n * @author yshop\n */\npublic interface ConfigService {\n\n    /**\n     * 创建参数配置\n     *\n     * @param createReqVO 创建信息\n     * @return 配置编号\n     */\n    Long createConfig(@Valid ConfigSaveReqVO createReqVO);\n\n    /**\n     * 更新参数配置\n     *\n     * @param updateReqVO 更新信息\n     */\n    void updateConfig(@Valid ConfigSaveReqVO updateReqVO);\n\n    /**\n     * 删除参数配置\n     *\n     * @param id 配置编号\n     */\n    void deleteConfig(Long id);\n\n    /**\n     * 获得参数配置\n     *\n     * @param id 配置编号\n     * @return 参数配置\n     */\n    ConfigDO getConfig(Long id);\n\n    /**\n     * 根据参数键，获得参数配置\n     *\n     * @param key 配置键\n     * @return 参数配置\n     */\n    ConfigDO getConfigByKey(String key);\n\n    /**\n     * 获得参数配置分页列表\n     *\n     * @param reqVO 分页条件\n     * @return 分页列表\n     */\n    PageResult<ConfigDO> getConfigPage(@Valid ConfigPageReqVO reqVO);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/service/config/ConfigServiceImpl.java",
    "content": "package co.yixiang.yshop.module.infra.service.config;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.infra.controller.admin.config.vo.ConfigPageReqVO;\nimport co.yixiang.yshop.module.infra.controller.admin.config.vo.ConfigSaveReqVO;\nimport co.yixiang.yshop.module.infra.convert.config.ConfigConvert;\nimport co.yixiang.yshop.module.infra.dal.dataobject.config.ConfigDO;\nimport co.yixiang.yshop.module.infra.dal.mysql.config.ConfigMapper;\nimport co.yixiang.yshop.module.infra.enums.config.ConfigTypeEnum;\nimport com.google.common.annotations.VisibleForTesting;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.infra.enums.ErrorCodeConstants.*;\n\n/**\n * 参数配置 Service 实现类\n */\n@Service\n@Slf4j\n@Validated\npublic class ConfigServiceImpl implements ConfigService {\n\n    @Resource\n    private ConfigMapper configMapper;\n\n    @Override\n    public Long createConfig(ConfigSaveReqVO createReqVO) {\n        // 校验参数配置 key 的唯一性\n        validateConfigKeyUnique(null, createReqVO.getKey());\n\n        // 插入参数配置\n        ConfigDO config = ConfigConvert.INSTANCE.convert(createReqVO);\n        config.setType(ConfigTypeEnum.CUSTOM.getType());\n        configMapper.insert(config);\n        return config.getId();\n    }\n\n    @Override\n    public void updateConfig(ConfigSaveReqVO updateReqVO) {\n        // 校验自己存在\n        validateConfigExists(updateReqVO.getId());\n        // 校验参数配置 key 的唯一性\n        validateConfigKeyUnique(updateReqVO.getId(), updateReqVO.getKey());\n\n        // 更新参数配置\n        ConfigDO updateObj = ConfigConvert.INSTANCE.convert(updateReqVO);\n        configMapper.updateById(updateObj);\n    }\n\n    @Override\n    public void deleteConfig(Long id) {\n        // 校验配置存在\n        ConfigDO config = validateConfigExists(id);\n        // 内置配置，不允许删除\n        if (ConfigTypeEnum.SYSTEM.getType().equals(config.getType())) {\n            throw exception(CONFIG_CAN_NOT_DELETE_SYSTEM_TYPE);\n        }\n        // 删除\n        configMapper.deleteById(id);\n    }\n\n    @Override\n    public ConfigDO getConfig(Long id) {\n        return configMapper.selectById(id);\n    }\n\n    @Override\n    public ConfigDO getConfigByKey(String key) {\n        return configMapper.selectByKey(key);\n    }\n\n    @Override\n    public PageResult<ConfigDO> getConfigPage(ConfigPageReqVO pageReqVO) {\n        return configMapper.selectPage(pageReqVO);\n    }\n\n    @VisibleForTesting\n    public ConfigDO validateConfigExists(Long id) {\n        if (id == null) {\n            return null;\n        }\n        ConfigDO config = configMapper.selectById(id);\n        if (config == null) {\n            throw exception(CONFIG_NOT_EXISTS);\n        }\n        return config;\n    }\n\n    @VisibleForTesting\n    public void validateConfigKeyUnique(Long id, String key) {\n        ConfigDO config = configMapper.selectByKey(key);\n        if (config == null) {\n            return;\n        }\n        // 如果 id 为空，说明不用比较是否为相同 id 的参数配置\n        if (id == null) {\n            throw exception(CONFIG_KEY_DUPLICATE);\n        }\n        if (!config.getId().equals(id)) {\n            throw exception(CONFIG_KEY_DUPLICATE);\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/service/db/DataSourceConfigService.java",
    "content": "package co.yixiang.yshop.module.infra.service.db;\n\nimport co.yixiang.yshop.module.infra.controller.admin.db.vo.DataSourceConfigSaveReqVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.db.DataSourceConfigDO;\n\nimport jakarta.validation.Valid;\nimport java.util.List;\n\n/**\n * 数据源配置 Service 接口\n *\n * @author yshop\n */\npublic interface DataSourceConfigService {\n\n    /**\n     * 创建数据源配置\n     *\n     * @param createReqVO 创建信息\n     * @return 编号\n     */\n    Long createDataSourceConfig(@Valid DataSourceConfigSaveReqVO createReqVO);\n\n    /**\n     * 更新数据源配置\n     *\n     * @param updateReqVO 更新信息\n     */\n    void updateDataSourceConfig(@Valid DataSourceConfigSaveReqVO updateReqVO);\n\n    /**\n     * 删除数据源配置\n     *\n     * @param id 编号\n     */\n    void deleteDataSourceConfig(Long id);\n\n    /**\n     * 获得数据源配置\n     *\n     * @param id 编号\n     * @return 数据源配置\n     */\n    DataSourceConfigDO getDataSourceConfig(Long id);\n\n    /**\n     * 获得数据源配置列表\n     *\n     * @return 数据源配置列表\n     */\n    List<DataSourceConfigDO> getDataSourceConfigList();\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/service/db/DataSourceConfigServiceImpl.java",
    "content": "package co.yixiang.yshop.module.infra.service.db;\n\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.framework.mybatis.core.util.JdbcUtils;\nimport co.yixiang.yshop.module.infra.controller.admin.db.vo.DataSourceConfigSaveReqVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.db.DataSourceConfigDO;\nimport co.yixiang.yshop.module.infra.dal.mysql.db.DataSourceConfigMapper;\nimport com.baomidou.dynamic.datasource.creator.DataSourceProperty;\nimport com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\nimport java.util.List;\nimport java.util.Objects;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.infra.enums.ErrorCodeConstants.DATA_SOURCE_CONFIG_NOT_EXISTS;\nimport static co.yixiang.yshop.module.infra.enums.ErrorCodeConstants.DATA_SOURCE_CONFIG_NOT_OK;\n\n/**\n * 数据源配置 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class DataSourceConfigServiceImpl implements DataSourceConfigService {\n\n    @Resource\n    private DataSourceConfigMapper dataSourceConfigMapper;\n\n    @Resource\n    private DynamicDataSourceProperties dynamicDataSourceProperties;\n\n    @Override\n    public Long createDataSourceConfig(DataSourceConfigSaveReqVO createReqVO) {\n        DataSourceConfigDO config = BeanUtils.toBean(createReqVO, DataSourceConfigDO.class);\n        validateConnectionOK(config);\n\n        // 插入\n        dataSourceConfigMapper.insert(config);\n        // 返回\n        return config.getId();\n    }\n\n    @Override\n    public void updateDataSourceConfig(DataSourceConfigSaveReqVO updateReqVO) {\n        // 校验存在\n        validateDataSourceConfigExists(updateReqVO.getId());\n        DataSourceConfigDO updateObj = BeanUtils.toBean(updateReqVO, DataSourceConfigDO.class);\n        validateConnectionOK(updateObj);\n\n        // 更新\n        dataSourceConfigMapper.updateById(updateObj);\n    }\n\n    @Override\n    public void deleteDataSourceConfig(Long id) {\n        // 校验存在\n        validateDataSourceConfigExists(id);\n        // 删除\n        dataSourceConfigMapper.deleteById(id);\n    }\n\n    private void validateDataSourceConfigExists(Long id) {\n        if (dataSourceConfigMapper.selectById(id) == null) {\n            throw exception(DATA_SOURCE_CONFIG_NOT_EXISTS);\n        }\n    }\n\n    @Override\n    public DataSourceConfigDO getDataSourceConfig(Long id) {\n        // 如果 id 为 0，默认为 master 的数据源\n        if (Objects.equals(id, DataSourceConfigDO.ID_MASTER)) {\n            return buildMasterDataSourceConfig();\n        }\n        // 从 DB 中读取\n        return dataSourceConfigMapper.selectById(id);\n    }\n\n    @Override\n    public List<DataSourceConfigDO> getDataSourceConfigList() {\n        List<DataSourceConfigDO> result = dataSourceConfigMapper.selectList();\n        // 补充 master 数据源\n        result.add(0, buildMasterDataSourceConfig());\n        return result;\n    }\n\n    private void validateConnectionOK(DataSourceConfigDO config) {\n        boolean success = JdbcUtils.isConnectionOK(config.getUrl(), config.getUsername(), config.getPassword());\n        if (!success) {\n            throw exception(DATA_SOURCE_CONFIG_NOT_OK);\n        }\n    }\n\n    private DataSourceConfigDO buildMasterDataSourceConfig() {\n        String primary = dynamicDataSourceProperties.getPrimary();\n        DataSourceProperty dataSourceProperty = dynamicDataSourceProperties.getDatasource().get(primary);\n        return new DataSourceConfigDO().setId(DataSourceConfigDO.ID_MASTER).setName(primary)\n                .setUrl(dataSourceProperty.getUrl())\n                .setUsername(dataSourceProperty.getUsername())\n                .setPassword(dataSourceProperty.getPassword());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/service/db/DatabaseTableService.java",
    "content": "package co.yixiang.yshop.module.infra.service.db;\n\nimport com.baomidou.mybatisplus.generator.config.po.TableInfo;\n\nimport java.util.List;\n\n/**\n * 数据库表 Service\n *\n * @author yshop\n */\npublic interface DatabaseTableService {\n\n    /**\n     * 获得表列表，基于表名称 + 表描述进行模糊匹配\n     *\n     * @param dataSourceConfigId 数据源配置的编号\n     * @param nameLike 表名称，模糊匹配\n     * @param commentLike 表描述，模糊匹配\n     * @return 表列表\n     */\n    List<TableInfo> getTableList(Long dataSourceConfigId, String nameLike, String commentLike);\n\n    /**\n     * 获得指定表名\n     *\n     * @param dataSourceConfigId 数据源配置的编号\n     * @param tableName 表名称\n     * @return 表\n     */\n    TableInfo getTable(Long dataSourceConfigId, String tableName);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/service/db/DatabaseTableServiceImpl.java",
    "content": "package co.yixiang.yshop.module.infra.service.db;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.lang.Assert;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.mybatis.core.util.JdbcUtils;\nimport co.yixiang.yshop.module.infra.dal.dataobject.db.DataSourceConfigDO;\nimport com.baomidou.mybatisplus.annotation.DbType;\nimport com.baomidou.mybatisplus.generator.config.DataSourceConfig;\nimport com.baomidou.mybatisplus.generator.config.GlobalConfig;\nimport com.baomidou.mybatisplus.generator.config.StrategyConfig;\nimport com.baomidou.mybatisplus.generator.config.builder.ConfigBuilder;\nimport com.baomidou.mybatisplus.generator.config.po.TableInfo;\nimport com.baomidou.mybatisplus.generator.config.rules.DateType;\nimport com.baomidou.mybatisplus.generator.query.SQLQuery;\nimport jakarta.annotation.Resource;\nimport org.springframework.stereotype.Service;\n\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.stream.Collectors;\n\n/**\n * 数据库表 Service 实现类\n *\n * @author yshop\n */\n@Service\npublic class DatabaseTableServiceImpl implements DatabaseTableService {\n\n    @Resource\n    private DataSourceConfigService dataSourceConfigService;\n\n    @Override\n    public List<TableInfo> getTableList(Long dataSourceConfigId, String nameLike, String commentLike) {\n        List<TableInfo> tables = getTableList0(dataSourceConfigId, null);\n        return tables.stream().filter(tableInfo -> (StrUtil.isEmpty(nameLike) || tableInfo.getName().contains(nameLike))\n                        && (StrUtil.isEmpty(commentLike) || tableInfo.getComment().contains(commentLike)))\n                .collect(Collectors.toList());\n    }\n\n    @Override\n    public TableInfo getTable(Long dataSourceConfigId, String name) {\n        return CollUtil.getFirst(getTableList0(dataSourceConfigId, name));\n    }\n\n    private List<TableInfo> getTableList0(Long dataSourceConfigId, String name) {\n        // 获得数据源配置\n        DataSourceConfigDO config = dataSourceConfigService.getDataSourceConfig(dataSourceConfigId);\n        Assert.notNull(config, \"数据源({}) 不存在！\", dataSourceConfigId);\n        DbType dbType = JdbcUtils.getDbType(config.getUrl());\n\n        // 使用 MyBatis Plus Generator 解析表结构\n        DataSourceConfig.Builder dataSourceConfigBuilder = new DataSourceConfig.Builder(config.getUrl(), config.getUsername(),\n                config.getPassword());\n        if (Objects.equals(dbType, DbType.SQL_SERVER)) { // 特殊：SQLServer jdbc 非标准，参见 https://github.com/baomidou/mybatis-plus/issues/5419\n            dataSourceConfigBuilder.databaseQueryClass(SQLQuery.class);\n        }\n        StrategyConfig.Builder strategyConfig = new StrategyConfig.Builder().enableSkipView(); // 忽略视图，业务上一般用不到\n        if (StrUtil.isNotEmpty(name)) {\n            strategyConfig.addInclude(name);\n        } else {\n            // 移除工作流和定时任务前缀的表名\n            strategyConfig.addExclude(\"ACT_[\\\\S\\\\s]+|QRTZ_[\\\\S\\\\s]+|FLW_[\\\\S\\\\s]+\");\n            // 移除 ORACLE 相关的系统表\n            strategyConfig.addExclude(\"IMPDP_[\\\\S\\\\s]+|ALL_[\\\\S\\\\s]+|HS_[\\\\S\\\\\\\\s]+\");\n            strategyConfig.addExclude(\"[\\\\S\\\\s]+\\\\$[\\\\S\\\\s]+|[\\\\S\\\\s]+\\\\$\"); // 表里不能有 $，一般有都是系统的表\n        }\n\n        GlobalConfig globalConfig = new GlobalConfig.Builder().dateType(DateType.TIME_PACK).build(); // 只使用 LocalDateTime 类型，不使用 LocalDate\n        ConfigBuilder builder = new ConfigBuilder(null, dataSourceConfigBuilder.build(), strategyConfig.build(),\n                null, globalConfig, null);\n        // 按照名字排序\n        List<TableInfo> tables = builder.getTableInfoList();\n        tables.sort(Comparator.comparing(TableInfo::getName));\n        return tables;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/service/demo/demo01/Demo01ContactService.java",
    "content": "package co.yixiang.yshop.module.infra.service.demo.demo01;\n\nimport jakarta.validation.*;\n\nimport co.yixiang.yshop.module.infra.controller.admin.demo.demo01.vo.Demo01ContactPageReqVO;\nimport co.yixiang.yshop.module.infra.controller.admin.demo.demo01.vo.Demo01ContactSaveReqVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.demo.demo01.Demo01ContactDO;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\n/**\n * 示例联系人 Service 接口\n *\n * @author yshop\n */\npublic interface Demo01ContactService {\n\n    /**\n     * 创建示例联系人\n     *\n     * @param createReqVO 创建信息\n     * @return 编号\n     */\n    Long createDemo01Contact(@Valid Demo01ContactSaveReqVO createReqVO);\n\n    /**\n     * 更新示例联系人\n     *\n     * @param updateReqVO 更新信息\n     */\n    void updateDemo01Contact(@Valid Demo01ContactSaveReqVO updateReqVO);\n\n    /**\n     * 删除示例联系人\n     *\n     * @param id 编号\n     */\n    void deleteDemo01Contact(Long id);\n\n    /**\n     * 获得示例联系人\n     *\n     * @param id 编号\n     * @return 示例联系人\n     */\n    Demo01ContactDO getDemo01Contact(Long id);\n\n    /**\n     * 获得示例联系人分页\n     *\n     * @param pageReqVO 分页查询\n     * @return 示例联系人分页\n     */\n    PageResult<Demo01ContactDO> getDemo01ContactPage(Demo01ContactPageReqVO pageReqVO);\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/service/demo/demo01/Demo01ContactServiceImpl.java",
    "content": "package co.yixiang.yshop.module.infra.service.demo.demo01;\n\nimport co.yixiang.yshop.module.infra.controller.admin.demo.demo01.vo.Demo01ContactPageReqVO;\nimport co.yixiang.yshop.module.infra.controller.admin.demo.demo01.vo.Demo01ContactSaveReqVO;\nimport org.springframework.stereotype.Service;\nimport jakarta.annotation.Resource;\nimport org.springframework.validation.annotation.Validated;\n\nimport co.yixiang.yshop.module.infra.dal.dataobject.demo.demo01.Demo01ContactDO;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\n\nimport co.yixiang.yshop.module.infra.dal.mysql.demo.demo01.Demo01ContactMapper;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.infra.enums.ErrorCodeConstants.*;\n\n/**\n * 示例联系人 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class Demo01ContactServiceImpl implements Demo01ContactService {\n\n    @Resource\n    private Demo01ContactMapper demo01ContactMapper;\n\n    @Override\n    public Long createDemo01Contact(Demo01ContactSaveReqVO createReqVO) {\n        // 插入\n        Demo01ContactDO demo01Contact = BeanUtils.toBean(createReqVO, Demo01ContactDO.class);\n        demo01ContactMapper.insert(demo01Contact);\n        // 返回\n        return demo01Contact.getId();\n    }\n\n    @Override\n    public void updateDemo01Contact(Demo01ContactSaveReqVO updateReqVO) {\n        // 校验存在\n        validateDemo01ContactExists(updateReqVO.getId());\n        // 更新\n        Demo01ContactDO updateObj = BeanUtils.toBean(updateReqVO, Demo01ContactDO.class);\n        demo01ContactMapper.updateById(updateObj);\n    }\n\n    @Override\n    public void deleteDemo01Contact(Long id) {\n        // 校验存在\n        validateDemo01ContactExists(id);\n        // 删除\n        demo01ContactMapper.deleteById(id);\n    }\n\n    private void validateDemo01ContactExists(Long id) {\n        if (demo01ContactMapper.selectById(id) == null) {\n            throw exception(DEMO01_CONTACT_NOT_EXISTS);\n        }\n    }\n\n    @Override\n    public Demo01ContactDO getDemo01Contact(Long id) {\n        return demo01ContactMapper.selectById(id);\n    }\n\n    @Override\n    public PageResult<Demo01ContactDO> getDemo01ContactPage(Demo01ContactPageReqVO pageReqVO) {\n        return demo01ContactMapper.selectPage(pageReqVO);\n    }\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/service/demo/demo02/Demo02CategoryService.java",
    "content": "package co.yixiang.yshop.module.infra.service.demo.demo02;\n\nimport java.util.*;\nimport jakarta.validation.*;\n\nimport co.yixiang.yshop.module.infra.controller.admin.demo.demo02.vo.Demo02CategoryListReqVO;\nimport co.yixiang.yshop.module.infra.controller.admin.demo.demo02.vo.Demo02CategorySaveReqVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.demo.demo02.Demo02CategoryDO;\n\n/**\n * 示例分类 Service 接口\n *\n * @author yshop\n */\npublic interface Demo02CategoryService {\n\n    /**\n     * 创建示例分类\n     *\n     * @param createReqVO 创建信息\n     * @return 编号\n     */\n    Long createDemo02Category(@Valid Demo02CategorySaveReqVO createReqVO);\n\n    /**\n     * 更新示例分类\n     *\n     * @param updateReqVO 更新信息\n     */\n    void updateDemo02Category(@Valid Demo02CategorySaveReqVO updateReqVO);\n\n    /**\n     * 删除示例分类\n     *\n     * @param id 编号\n     */\n    void deleteDemo02Category(Long id);\n\n    /**\n     * 获得示例分类\n     *\n     * @param id 编号\n     * @return 示例分类\n     */\n    Demo02CategoryDO getDemo02Category(Long id);\n\n    /**\n     * 获得示例分类列表\n     *\n     * @param listReqVO 查询条件\n     * @return 示例分类列表\n     */\n    List<Demo02CategoryDO> getDemo02CategoryList(Demo02CategoryListReqVO listReqVO);\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/service/demo/demo02/Demo02CategoryServiceImpl.java",
    "content": "package co.yixiang.yshop.module.infra.service.demo.demo02;\n\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.infra.controller.admin.demo.demo02.vo.Demo02CategoryListReqVO;\nimport co.yixiang.yshop.module.infra.controller.admin.demo.demo02.vo.Demo02CategorySaveReqVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.demo.demo02.Demo02CategoryDO;\nimport co.yixiang.yshop.module.infra.dal.mysql.demo.demo02.Demo02CategoryMapper;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\nimport java.util.List;\nimport java.util.Objects;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.infra.enums.ErrorCodeConstants.*;\n\n/**\n * 示例分类 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class Demo02CategoryServiceImpl implements Demo02CategoryService {\n\n    @Resource\n    private Demo02CategoryMapper demo02CategoryMapper;\n\n    @Override\n    public Long createDemo02Category(Demo02CategorySaveReqVO createReqVO) {\n        // 校验父级编号的有效性\n        validateParentDemo02Category(null, createReqVO.getParentId());\n        // 校验名字的唯一性\n        validateDemo02CategoryNameUnique(null, createReqVO.getParentId(), createReqVO.getName());\n\n        // 插入\n        Demo02CategoryDO demo02Category = BeanUtils.toBean(createReqVO, Demo02CategoryDO.class);\n        demo02CategoryMapper.insert(demo02Category);\n        // 返回\n        return demo02Category.getId();\n    }\n\n    @Override\n    public void updateDemo02Category(Demo02CategorySaveReqVO updateReqVO) {\n        // 校验存在\n        validateDemo02CategoryExists(updateReqVO.getId());\n        // 校验父级编号的有效性\n        validateParentDemo02Category(updateReqVO.getId(), updateReqVO.getParentId());\n        // 校验名字的唯一性\n        validateDemo02CategoryNameUnique(updateReqVO.getId(), updateReqVO.getParentId(), updateReqVO.getName());\n\n        // 更新\n        Demo02CategoryDO updateObj = BeanUtils.toBean(updateReqVO, Demo02CategoryDO.class);\n        demo02CategoryMapper.updateById(updateObj);\n    }\n\n    @Override\n    public void deleteDemo02Category(Long id) {\n        // 校验存在\n        validateDemo02CategoryExists(id);\n        // 校验是否有子示例分类\n        if (demo02CategoryMapper.selectCountByParentId(id) > 0) {\n            throw exception(DEMO02_CATEGORY_EXITS_CHILDREN);\n        }\n        // 删除\n        demo02CategoryMapper.deleteById(id);\n    }\n\n    private void validateDemo02CategoryExists(Long id) {\n        if (demo02CategoryMapper.selectById(id) == null) {\n            throw exception(DEMO02_CATEGORY_NOT_EXISTS);\n        }\n    }\n\n    private void validateParentDemo02Category(Long id, Long parentId) {\n        if (parentId == null || Demo02CategoryDO.PARENT_ID_ROOT.equals(parentId)) {\n            return;\n        }\n        // 1. 不能设置自己为父示例分类\n        if (Objects.equals(id, parentId)) {\n            throw exception(DEMO02_CATEGORY_PARENT_ERROR);\n        }\n        // 2. 父示例分类不存在\n        Demo02CategoryDO parentDemo02Category = demo02CategoryMapper.selectById(parentId);\n        if (parentDemo02Category == null) {\n            throw exception(DEMO02_CATEGORY_PARENT_NOT_EXITS);\n        }\n        // 3. 递归校验父示例分类，如果父示例分类是自己的子示例分类，则报错，避免形成环路\n        if (id == null) { // id 为空，说明新增，不需要考虑环路\n            return;\n        }\n        for (int i = 0; i < Short.MAX_VALUE; i++) {\n            // 3.1 校验环路\n            parentId = parentDemo02Category.getParentId();\n            if (Objects.equals(id, parentId)) {\n                throw exception(DEMO02_CATEGORY_PARENT_IS_CHILD);\n            }\n            // 3.2 继续递归下一级父示例分类\n            if (parentId == null || Demo02CategoryDO.PARENT_ID_ROOT.equals(parentId)) {\n                break;\n            }\n            parentDemo02Category = demo02CategoryMapper.selectById(parentId);\n            if (parentDemo02Category == null) {\n                break;\n            }\n        }\n    }\n\n    private void validateDemo02CategoryNameUnique(Long id, Long parentId, String name) {\n        Demo02CategoryDO demo02Category = demo02CategoryMapper.selectByParentIdAndName(parentId, name);\n        if (demo02Category == null) {\n            return;\n        }\n        // 如果 id 为空，说明不用比较是否为相同 id 的示例分类\n        if (id == null) {\n            throw exception(DEMO02_CATEGORY_NAME_DUPLICATE);\n        }\n        if (!Objects.equals(demo02Category.getId(), id)) {\n            throw exception(DEMO02_CATEGORY_NAME_DUPLICATE);\n        }\n    }\n\n    @Override\n    public Demo02CategoryDO getDemo02Category(Long id) {\n        return demo02CategoryMapper.selectById(id);\n    }\n\n    @Override\n    public List<Demo02CategoryDO> getDemo02CategoryList(Demo02CategoryListReqVO listReqVO) {\n        return demo02CategoryMapper.selectList(listReqVO);\n    }\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/service/demo/demo03/Demo03StudentService.java",
    "content": "package co.yixiang.yshop.module.infra.service.demo.demo03;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.infra.controller.admin.demo.demo03.vo.Demo03StudentPageReqVO;\nimport co.yixiang.yshop.module.infra.controller.admin.demo.demo03.vo.Demo03StudentSaveReqVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.demo.demo03.Demo03CourseDO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.demo.demo03.Demo03GradeDO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.demo.demo03.Demo03StudentDO;\n\nimport jakarta.validation.Valid;\nimport java.util.List;\n\n/**\n * 学生 Service 接口\n *\n * @author yshop\n */\npublic interface Demo03StudentService {\n\n    /**\n     * 创建学生\n     *\n     * @param createReqVO 创建信息\n     * @return 编号\n     */\n    Long createDemo03Student(@Valid Demo03StudentSaveReqVO createReqVO);\n\n    /**\n     * 更新学生\n     *\n     * @param updateReqVO 更新信息\n     */\n    void updateDemo03Student(@Valid Demo03StudentSaveReqVO updateReqVO);\n\n    /**\n     * 删除学生\n     *\n     * @param id 编号\n     */\n    void deleteDemo03Student(Long id);\n\n    /**\n     * 获得学生\n     *\n     * @param id 编号\n     * @return 学生\n     */\n    Demo03StudentDO getDemo03Student(Long id);\n\n    /**\n     * 获得学生分页\n     *\n     * @param pageReqVO 分页查询\n     * @return 学生分页\n     */\n    PageResult<Demo03StudentDO> getDemo03StudentPage(Demo03StudentPageReqVO pageReqVO);\n\n\n    // ==================== 子表（学生课程） ====================\n\n    /**\n     * 获得学生课程列表\n     *\n     * @param studentId 学生编号\n     * @return 学生课程列表\n     */\n    List<Demo03CourseDO> getDemo03CourseListByStudentId(Long studentId);\n\n    /**\n     * 获得学生课程分页\n     *\n     * @param pageReqVO 分页查询\n     * @param studentId 学生编号\n     * @return 学生课程分页\n     */\n    PageResult<Demo03CourseDO> getDemo03CoursePage(PageParam pageReqVO, Long studentId);\n\n    /**\n     * 创建学生课程\n     *\n     * @param demo03Course 创建信息\n     * @return 编号\n     */\n    Long createDemo03Course(@Valid Demo03CourseDO demo03Course);\n\n    /**\n     * 更新学生课程\n     *\n     * @param demo03Course 更新信息\n     */\n    void updateDemo03Course(@Valid Demo03CourseDO demo03Course);\n\n    /**\n     * 删除学生课程\n     *\n     * @param id 编号\n     */\n    void deleteDemo03Course(Long id);\n\n    /**\n     * 获得学生课程\n     *\n     * @param id 编号\n     * @return 学生课程\n     */\n    Demo03CourseDO getDemo03Course(Long id);\n\n    // ==================== 子表（学生班级） ====================\n\n    /**\n     * 获得学生班级\n     *\n     * @param studentId 学生编号\n     * @return 学生班级\n     */\n    Demo03GradeDO getDemo03GradeByStudentId(Long studentId);\n\n    /**\n     * 获得学生班级分页\n     *\n     * @param pageReqVO 分页查询\n     * @param studentId 学生编号\n     * @return 学生班级分页\n     */\n    PageResult<Demo03GradeDO> getDemo03GradePage(PageParam pageReqVO, Long studentId);\n\n    /**\n     * 创建学生班级\n     *\n     * @param demo03Grade 创建信息\n     * @return 编号\n     */\n    Long createDemo03Grade(@Valid Demo03GradeDO demo03Grade);\n\n    /**\n     * 更新学生班级\n     *\n     * @param demo03Grade 更新信息\n     */\n    void updateDemo03Grade(@Valid Demo03GradeDO demo03Grade);\n\n    /**\n     * 删除学生班级\n     *\n     * @param id 编号\n     */\n    void deleteDemo03Grade(Long id);\n\n    /**\n     * 获得学生班级\n     *\n     * @param id 编号\n     * @return 学生班级\n     */\n    Demo03GradeDO getDemo03Grade(Long id);\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/service/demo/demo03/Demo03StudentServiceImpl.java",
    "content": "package co.yixiang.yshop.module.infra.service.demo.demo03;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.infra.controller.admin.demo.demo03.vo.Demo03StudentPageReqVO;\nimport co.yixiang.yshop.module.infra.controller.admin.demo.demo03.vo.Demo03StudentSaveReqVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.demo.demo03.Demo03CourseDO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.demo.demo03.Demo03GradeDO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.demo.demo03.Demo03StudentDO;\nimport co.yixiang.yshop.module.infra.dal.mysql.demo.demo03.Demo03CourseMapper;\nimport co.yixiang.yshop.module.infra.dal.mysql.demo.demo03.Demo03GradeMapper;\nimport co.yixiang.yshop.module.infra.dal.mysql.demo.demo03.Demo03StudentMapper;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.infra.enums.ErrorCodeConstants.*;\n\n/**\n * 学生 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class Demo03StudentServiceImpl implements Demo03StudentService {\n\n    @Resource\n    private Demo03StudentMapper demo03StudentMapper;\n    @Resource\n    private Demo03CourseMapper demo03CourseMapper;\n    @Resource\n    private Demo03GradeMapper demo03GradeMapper;\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public Long createDemo03Student(Demo03StudentSaveReqVO createReqVO) {\n        // 插入\n        Demo03StudentDO demo03Student = BeanUtils.toBean(createReqVO, Demo03StudentDO.class);\n        demo03StudentMapper.insert(demo03Student);\n\n        // 插入子表\n        createDemo03CourseList(demo03Student.getId(), createReqVO.getDemo03Courses());\n        createDemo03Grade(demo03Student.getId(), createReqVO.getDemo03Grade());\n        // 返回\n        return demo03Student.getId();\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void updateDemo03Student(Demo03StudentSaveReqVO updateReqVO) {\n        // 校验存在\n        validateDemo03StudentExists(updateReqVO.getId());\n        // 更新\n        Demo03StudentDO updateObj = BeanUtils.toBean(updateReqVO, Demo03StudentDO.class);\n        demo03StudentMapper.updateById(updateObj);\n\n        // 更新子表\n        updateDemo03CourseList(updateReqVO.getId(), updateReqVO.getDemo03Courses());\n        updateDemo03Grade(updateReqVO.getId(), updateReqVO.getDemo03Grade());\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void deleteDemo03Student(Long id) {\n        // 校验存在\n        validateDemo03StudentExists(id);\n        // 删除\n        demo03StudentMapper.deleteById(id);\n\n        // 删除子表\n        deleteDemo03CourseByStudentId(id);\n        deleteDemo03GradeByStudentId(id);\n    }\n\n    private void validateDemo03StudentExists(Long id) {\n        if (demo03StudentMapper.selectById(id) == null) {\n            throw exception(DEMO03_STUDENT_NOT_EXISTS);\n        }\n    }\n\n    @Override\n    public Demo03StudentDO getDemo03Student(Long id) {\n        return demo03StudentMapper.selectById(id);\n    }\n\n    @Override\n    public PageResult<Demo03StudentDO> getDemo03StudentPage(Demo03StudentPageReqVO pageReqVO) {\n        return demo03StudentMapper.selectPage(pageReqVO);\n    }\n\n    // ==================== 子表（学生课程） ====================\n\n    @Override\n    public List<Demo03CourseDO> getDemo03CourseListByStudentId(Long studentId) {\n        return demo03CourseMapper.selectListByStudentId(studentId);\n    }\n\n    private void createDemo03CourseList(Long studentId, List<Demo03CourseDO> list) {\n        if (list != null) {\n            list.forEach(o -> o.setStudentId(studentId));\n        }\n        demo03CourseMapper.insertBatch(list);\n    }\n\n    private void updateDemo03CourseList(Long studentId, List<Demo03CourseDO> list) {\n        deleteDemo03CourseByStudentId(studentId);\n\t\tlist.forEach(o -> o.setId(null).setUpdater(null).setUpdateTime(null)); // 解决更新情况下：1）id 冲突；2）updateTime 不更新\n        createDemo03CourseList(studentId, list);\n    }\n\n    private void deleteDemo03CourseByStudentId(Long studentId) {\n        demo03CourseMapper.deleteByStudentId(studentId);\n    }\n\n    @Override\n    public PageResult<Demo03CourseDO> getDemo03CoursePage(PageParam pageReqVO, Long studentId) {\n        return demo03CourseMapper.selectPage(pageReqVO, studentId);\n    }\n\n    @Override\n    public Long createDemo03Course(Demo03CourseDO demo03Course) {\n        demo03CourseMapper.insert(demo03Course);\n        return demo03Course.getId();\n    }\n\n    @Override\n    public void updateDemo03Course(Demo03CourseDO demo03Course) {\n        demo03CourseMapper.updateById(demo03Course);\n    }\n\n    @Override\n    public void deleteDemo03Course(Long id) {\n        demo03CourseMapper.deleteById(id);\n    }\n\n    @Override\n    public Demo03CourseDO getDemo03Course(Long id) {\n        return demo03CourseMapper.selectById(id);\n    }\n\n    // ==================== 子表（学生班级） ====================\n\n    @Override\n    public Demo03GradeDO getDemo03GradeByStudentId(Long studentId) {\n        return demo03GradeMapper.selectByStudentId(studentId);\n    }\n\n    private void createDemo03Grade(Long studentId, Demo03GradeDO demo03Grade) {\n        if (demo03Grade == null) {\n            return;\n        }\n        demo03Grade.setStudentId(studentId);\n        demo03GradeMapper.insert(demo03Grade);\n    }\n\n    private void updateDemo03Grade(Long studentId, Demo03GradeDO demo03Grade) {\n        if (demo03Grade == null) {\n\t\t\treturn;\n        }\n        demo03Grade.setStudentId(studentId);\n        demo03Grade.setUpdater(null).setUpdateTime(null); // 解决更新情况下：updateTime 不更新\n        demo03GradeMapper.insertOrUpdate(demo03Grade);\n    }\n\n    private void deleteDemo03GradeByStudentId(Long studentId) {\n        demo03GradeMapper.deleteByStudentId(studentId);\n    }\n\n    @Override\n    public PageResult<Demo03GradeDO> getDemo03GradePage(PageParam pageReqVO, Long studentId) {\n        return demo03GradeMapper.selectPage(pageReqVO, studentId);\n    }\n\n    @Override\n    public Long createDemo03Grade(Demo03GradeDO demo03Grade) {\n        // 校验是否已经存在\n        if (demo03GradeMapper.selectByStudentId(demo03Grade.getStudentId()) != null) {\n            throw exception(DEMO03_GRADE_EXISTS);\n        }\n        demo03GradeMapper.insert(demo03Grade);\n        return demo03Grade.getId();\n    }\n\n    @Override\n    public void updateDemo03Grade(Demo03GradeDO demo03Grade) {\n        // 校验存在\n        validateDemo03GradeExists(demo03Grade.getId());\n        // 更新\n        demo03GradeMapper.updateById(demo03Grade);\n    }\n\n    @Override\n    public void deleteDemo03Grade(Long id) {\n        // 校验存在\n        validateDemo03GradeExists(id);\n        // 删除\n        demo03GradeMapper.deleteById(id);\n    }\n\n    @Override\n    public Demo03GradeDO getDemo03Grade(Long id) {\n        return demo03GradeMapper.selectById(id);\n    }\n\n    private void validateDemo03GradeExists(Long id) {\n        if (demo03GradeMapper.selectById(id) == null) {\n            throw exception(DEMO03_GRADE_NOT_EXISTS);\n        }\n    }\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/service/file/FileConfigService.java",
    "content": "package co.yixiang.yshop.module.infra.service.file;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.infra.framework.file.core.client.FileClient;\nimport co.yixiang.yshop.module.infra.controller.admin.file.vo.config.FileConfigPageReqVO;\nimport co.yixiang.yshop.module.infra.controller.admin.file.vo.config.FileConfigSaveReqVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.file.FileConfigDO;\n\nimport jakarta.validation.Valid;\n\n/**\n * 文件配置 Service 接口\n *\n * @author yshop\n */\npublic interface FileConfigService {\n\n    /**\n     * 创建文件配置\n     *\n     * @param createReqVO 创建信息\n     * @return 编号\n     */\n    Long createFileConfig(@Valid FileConfigSaveReqVO createReqVO);\n\n    /**\n     * 更新文件配置\n     *\n     * @param updateReqVO 更新信息\n     */\n    void updateFileConfig(@Valid FileConfigSaveReqVO updateReqVO);\n\n    /**\n     * 更新文件配置为 Master\n     *\n     * @param id 编号\n     */\n    void updateFileConfigMaster(Long id);\n\n    /**\n     * 删除文件配置\n     *\n     * @param id 编号\n     */\n    void deleteFileConfig(Long id);\n\n    /**\n     * 获得文件配置\n     *\n     * @param id 编号\n     * @return 文件配置\n     */\n    FileConfigDO getFileConfig(Long id);\n\n    /**\n     * 获得文件配置分页\n     *\n     * @param pageReqVO 分页查询\n     * @return 文件配置分页\n     */\n    PageResult<FileConfigDO> getFileConfigPage(FileConfigPageReqVO pageReqVO);\n\n    /**\n     * 测试文件配置是否正确，通过上传文件\n     *\n     * @param id 编号\n     * @return 文件 URL\n     */\n    String testFileConfig(Long id) throws Exception;\n\n    /**\n     * 获得指定编号的文件客户端\n     *\n     * @param id 配置编号\n     * @return 文件客户端\n     */\n    FileClient getFileClient(Long id);\n\n    /**\n     * 获得 Master 文件客户端\n     *\n     * @return 文件客户端\n     */\n    FileClient getMasterFileClient();\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/service/file/FileConfigServiceImpl.java",
    "content": "package co.yixiang.yshop.module.infra.service.file;\n\nimport cn.hutool.core.io.resource.ResourceUtil;\nimport cn.hutool.core.util.IdUtil;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.json.JsonUtils;\nimport co.yixiang.yshop.framework.common.util.validation.ValidationUtils;\nimport co.yixiang.yshop.module.infra.framework.file.core.client.FileClient;\nimport co.yixiang.yshop.module.infra.framework.file.core.client.FileClientConfig;\nimport co.yixiang.yshop.module.infra.framework.file.core.client.FileClientFactory;\nimport co.yixiang.yshop.module.infra.framework.file.core.enums.FileStorageEnum;\nimport co.yixiang.yshop.module.infra.controller.admin.file.vo.config.FileConfigPageReqVO;\nimport co.yixiang.yshop.module.infra.controller.admin.file.vo.config.FileConfigSaveReqVO;\nimport co.yixiang.yshop.module.infra.convert.file.FileConfigConvert;\nimport co.yixiang.yshop.module.infra.dal.dataobject.file.FileConfigDO;\nimport co.yixiang.yshop.module.infra.dal.mysql.file.FileConfigMapper;\nimport com.google.common.cache.CacheLoader;\nimport com.google.common.cache.LoadingCache;\nimport lombok.Getter;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Validator;\nimport java.time.Duration;\nimport java.util.Map;\nimport java.util.Objects;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.framework.common.util.cache.CacheUtils.buildAsyncReloadingCache;\nimport static co.yixiang.yshop.module.infra.enums.ErrorCodeConstants.FILE_CONFIG_DELETE_FAIL_MASTER;\nimport static co.yixiang.yshop.module.infra.enums.ErrorCodeConstants.FILE_CONFIG_NOT_EXISTS;\n\n/**\n * 文件配置 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\n@Slf4j\npublic class FileConfigServiceImpl implements FileConfigService {\n\n    private static final Long CACHE_MASTER_ID = 0L;\n\n    /**\n     * {@link FileClient} 缓存，通过它异步刷新 fileClientFactory\n     */\n    @Getter\n    private final LoadingCache<Long, FileClient> clientCache = buildAsyncReloadingCache(Duration.ofSeconds(10L),\n            new CacheLoader<Long, FileClient>() {\n\n                @Override\n                public FileClient load(Long id) {\n                    FileConfigDO config = Objects.equals(CACHE_MASTER_ID, id) ?\n                            fileConfigMapper.selectByMaster() : fileConfigMapper.selectById(id);\n                    if (config != null) {\n                        fileClientFactory.createOrUpdateFileClient(config.getId(), config.getStorage(), config.getConfig());\n                    }\n                    return fileClientFactory.getFileClient(null == config ? id : config.getId());\n                }\n\n            });\n\n    @Resource\n    private FileClientFactory fileClientFactory;\n\n    @Resource\n    private FileConfigMapper fileConfigMapper;\n\n    @Resource\n    private Validator validator;\n\n    @Override\n    public Long createFileConfig(FileConfigSaveReqVO createReqVO) {\n        FileConfigDO fileConfig = FileConfigConvert.INSTANCE.convert(createReqVO)\n                .setConfig(parseClientConfig(createReqVO.getStorage(), createReqVO.getConfig()))\n                .setMaster(false); // 默认非 master\n        fileConfigMapper.insert(fileConfig);\n        return fileConfig.getId();\n    }\n\n    @Override\n    public void updateFileConfig(FileConfigSaveReqVO updateReqVO) {\n        // 校验存在\n        FileConfigDO config = validateFileConfigExists(updateReqVO.getId());\n        // 更新\n        FileConfigDO updateObj = FileConfigConvert.INSTANCE.convert(updateReqVO)\n                .setConfig(parseClientConfig(config.getStorage(), updateReqVO.getConfig()));\n        fileConfigMapper.updateById(updateObj);\n\n        // 清空缓存\n        clearCache(config.getId(), null);\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void updateFileConfigMaster(Long id) {\n        // 校验存在\n        validateFileConfigExists(id);\n        // 更新其它为非 master\n        fileConfigMapper.updateBatch(new FileConfigDO().setMaster(false));\n        // 更新\n        fileConfigMapper.updateById(new FileConfigDO().setId(id).setMaster(true));\n\n        // 清空缓存\n        clearCache(null, true);\n    }\n\n    private FileClientConfig parseClientConfig(Integer storage, Map<String, Object> config) {\n        // 获取配置类\n        Class<? extends FileClientConfig> configClass = FileStorageEnum.getByStorage(storage)\n                .getConfigClass();\n        FileClientConfig clientConfig = JsonUtils.parseObject2(JsonUtils.toJsonString(config), configClass);\n        // 参数校验\n        ValidationUtils.validate(validator, clientConfig);\n        // 设置参数\n        return clientConfig;\n    }\n\n    @Override\n    public void deleteFileConfig(Long id) {\n        // 校验存在\n        FileConfigDO config = validateFileConfigExists(id);\n        if (Boolean.TRUE.equals(config.getMaster())) {\n            throw exception(FILE_CONFIG_DELETE_FAIL_MASTER);\n        }\n        // 删除\n        fileConfigMapper.deleteById(id);\n\n        // 清空缓存\n        clearCache(id, null);\n    }\n\n    /**\n     * 清空指定文件配置\n     *\n     * @param id 配置编号\n     * @param master 是否主配置\n     */\n    private void clearCache(Long id, Boolean master) {\n        if (id != null) {\n            clientCache.invalidate(id);\n        }\n        if (Boolean.TRUE.equals(master)) {\n            clientCache.invalidate(CACHE_MASTER_ID);\n        }\n    }\n\n    private FileConfigDO validateFileConfigExists(Long id) {\n        FileConfigDO config = fileConfigMapper.selectById(id);\n        if (config == null) {\n            throw exception(FILE_CONFIG_NOT_EXISTS);\n        }\n        return config;\n    }\n\n    @Override\n    public FileConfigDO getFileConfig(Long id) {\n        return fileConfigMapper.selectById(id);\n    }\n\n    @Override\n    public PageResult<FileConfigDO> getFileConfigPage(FileConfigPageReqVO pageReqVO) {\n        return fileConfigMapper.selectPage(pageReqVO);\n    }\n\n    @Override\n    public String testFileConfig(Long id) throws Exception {\n        // 校验存在\n        validateFileConfigExists(id);\n        // 上传文件\n        byte[] content = ResourceUtil.readBytes(\"file/erweima.jpg\");\n        return getFileClient(id).upload(content, IdUtil.fastSimpleUUID() + \".jpg\", \"image/jpeg\");\n    }\n\n    @Override\n    public FileClient getFileClient(Long id) {\n        return clientCache.getUnchecked(id);\n    }\n\n    @Override\n    public FileClient getMasterFileClient() {\n        return clientCache.getUnchecked(CACHE_MASTER_ID);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/service/file/FileService.java",
    "content": "package co.yixiang.yshop.module.infra.service.file;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.infra.controller.admin.file.vo.file.FileCreateReqVO;\nimport co.yixiang.yshop.module.infra.controller.admin.file.vo.file.FilePageReqVO;\nimport co.yixiang.yshop.module.infra.controller.admin.file.vo.file.FilePresignedUrlRespVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.file.FileDO;\n\n/**\n * 文件 Service 接口\n *\n * @author yshop\n */\npublic interface FileService {\n\n    /**\n     * 获得文件分页\n     *\n     * @param pageReqVO 分页查询\n     * @return 文件分页\n     */\n    PageResult<FileDO> getFilePage(FilePageReqVO pageReqVO);\n\n    /**\n     * 保存文件，并返回文件的访问路径\n     *\n     * @param name    文件名称\n     * @param path    文件路径\n     * @param content 文件内容\n     * @return 文件路径\n     */\n    String createFile(String name, String path, byte[] content);\n\n    /**\n     * 创建文件\n     *\n     * @param createReqVO 创建信息\n     * @return 编号\n     */\n    Long createFile(FileCreateReqVO createReqVO);\n\n    /**\n     * 删除文件\n     *\n     * @param id 编号\n     */\n    void deleteFile(Long id) throws Exception;\n\n    /**\n     * 获得文件内容\n     *\n     * @param configId 配置编号\n     * @param path     文件路径\n     * @return 文件内容\n     */\n    byte[] getFileContent(Long configId, String path) throws Exception;\n\n    /**\n     * 生成文件预签名地址信息\n     *\n     * @param path 文件路径\n     * @return 预签名地址信息\n     */\n    FilePresignedUrlRespVO getFilePresignedUrl(String path) throws Exception;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/service/file/FileServiceImpl.java",
    "content": "package co.yixiang.yshop.module.infra.service.file;\n\nimport cn.hutool.core.lang.Assert;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.io.FileUtils;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.infra.framework.file.core.client.FileClient;\nimport co.yixiang.yshop.module.infra.framework.file.core.client.s3.FilePresignedUrlRespDTO;\nimport co.yixiang.yshop.module.infra.framework.file.core.utils.FileTypeUtils;\nimport co.yixiang.yshop.module.infra.controller.admin.file.vo.file.FileCreateReqVO;\nimport co.yixiang.yshop.module.infra.controller.admin.file.vo.file.FilePageReqVO;\nimport co.yixiang.yshop.module.infra.controller.admin.file.vo.file.FilePresignedUrlRespVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.file.FileDO;\nimport co.yixiang.yshop.module.infra.dal.mysql.file.FileMapper;\nimport jakarta.annotation.Resource;\nimport lombok.SneakyThrows;\nimport org.springframework.stereotype.Service;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.infra.enums.ErrorCodeConstants.FILE_NOT_EXISTS;\n\n/**\n * 文件 Service 实现类\n *\n * @author yshop\n */\n@Service\npublic class FileServiceImpl implements FileService {\n\n    @Resource\n    private FileConfigService fileConfigService;\n\n    @Resource\n    private FileMapper fileMapper;\n\n    @Override\n    public PageResult<FileDO> getFilePage(FilePageReqVO pageReqVO) {\n        return fileMapper.selectPage(pageReqVO);\n    }\n\n    @Override\n    @SneakyThrows\n    public String createFile(String name, String path, byte[] content) {\n        // 计算默认的 path 名\n        String type = FileTypeUtils.getMineType(content, name);\n        if (StrUtil.isEmpty(path)) {\n            path = FileUtils.generatePath(content, name);\n        }\n        // 如果 name 为空，则使用 path 填充\n        if (StrUtil.isEmpty(name)) {\n            name = path;\n        }\n\n        // 上传到文件存储器\n        FileClient client = fileConfigService.getMasterFileClient();\n        Assert.notNull(client, \"客户端(master) 不能为空\");\n        String url = client.upload(content, path, type);\n\n        // 保存到数据库\n        FileDO file = new FileDO();\n        file.setConfigId(client.getId());\n        file.setName(name);\n        file.setPath(path);\n        file.setUrl(url);\n        file.setType(type);\n        file.setSize(content.length);\n        fileMapper.insert(file);\n        return url;\n    }\n\n    @Override\n    public Long createFile(FileCreateReqVO createReqVO) {\n        FileDO file = BeanUtils.toBean(createReqVO, FileDO.class);\n        fileMapper.insert(file);\n        return file.getId();\n    }\n\n    @Override\n    public void deleteFile(Long id) throws Exception {\n        // 校验存在\n        FileDO file = validateFileExists(id);\n\n        // 从文件存储器中删除\n        FileClient client = fileConfigService.getFileClient(file.getConfigId());\n        Assert.notNull(client, \"客户端({}) 不能为空\", file.getConfigId());\n        client.delete(file.getPath());\n\n        // 删除记录\n        fileMapper.deleteById(id);\n    }\n\n    private FileDO validateFileExists(Long id) {\n        FileDO fileDO = fileMapper.selectById(id);\n        if (fileDO == null) {\n            throw exception(FILE_NOT_EXISTS);\n        }\n        return fileDO;\n    }\n\n    @Override\n    public byte[] getFileContent(Long configId, String path) throws Exception {\n        FileClient client = fileConfigService.getFileClient(configId);\n        Assert.notNull(client, \"客户端({}) 不能为空\", configId);\n        return client.getContent(path);\n    }\n\n    @Override\n    public FilePresignedUrlRespVO getFilePresignedUrl(String path) throws Exception {\n        FileClient fileClient = fileConfigService.getMasterFileClient();\n        FilePresignedUrlRespDTO presignedObjectUrl = fileClient.getPresignedObjectUrl(path);\n        return BeanUtils.toBean(presignedObjectUrl, FilePresignedUrlRespVO.class,\n                object -> object.setConfigId(fileClient.getId()));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/service/job/JobLogService.java",
    "content": "package co.yixiang.yshop.module.infra.service.job;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.quartz.core.service.JobLogFrameworkService;\nimport co.yixiang.yshop.module.infra.controller.admin.job.vo.log.JobLogPageReqVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.job.JobLogDO;\n\n/**\n * Job 日志 Service 接口\n *\n * @author yshop\n */\npublic interface JobLogService extends JobLogFrameworkService {\n\n    /**\n     * 获得定时任务\n     *\n     * @param id 编号\n     * @return 定时任务\n     */\n    JobLogDO getJobLog(Long id);\n\n    /**\n     * 获得定时任务分页\n     *\n     * @param pageReqVO 分页查询\n     * @return 定时任务分页\n     */\n    PageResult<JobLogDO> getJobLogPage(JobLogPageReqVO pageReqVO);\n\n    /**\n     * 清理 exceedDay 天前的任务日志\n     *\n     * @param exceedDay 超过多少天就进行清理\n     * @param deleteLimit 清理的间隔条数\n     */\n    Integer cleanJobLog(Integer exceedDay, Integer deleteLimit);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/service/job/JobLogServiceImpl.java",
    "content": "package co.yixiang.yshop.module.infra.service.job;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.infra.controller.admin.job.vo.log.JobLogPageReqVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.job.JobLogDO;\nimport co.yixiang.yshop.module.infra.dal.mysql.job.JobLogMapper;\nimport co.yixiang.yshop.module.infra.enums.job.JobLogStatusEnum;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.scheduling.annotation.Async;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\nimport java.time.LocalDateTime;\n\n/**\n * Job 日志 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\n@Slf4j\npublic class JobLogServiceImpl implements JobLogService {\n\n    @Resource\n    private JobLogMapper jobLogMapper;\n\n    @Override\n    public Long createJobLog(Long jobId, LocalDateTime beginTime,\n                             String jobHandlerName, String jobHandlerParam, Integer executeIndex) {\n        JobLogDO log = JobLogDO.builder().jobId(jobId).handlerName(jobHandlerName)\n                .handlerParam(jobHandlerParam).executeIndex(executeIndex)\n                .beginTime(beginTime).status(JobLogStatusEnum.RUNNING.getStatus()).build();\n        jobLogMapper.insert(log);\n        return log.getId();\n    }\n\n    @Override\n    @Async\n    public void updateJobLogResultAsync(Long logId, LocalDateTime endTime, Integer duration, boolean success, String result) {\n        try {\n            JobLogDO updateObj = JobLogDO.builder().id(logId).endTime(endTime).duration(duration)\n                    .status(success ? JobLogStatusEnum.SUCCESS.getStatus() : JobLogStatusEnum.FAILURE.getStatus())\n                    .result(result).build();\n            jobLogMapper.updateById(updateObj);\n        } catch (Exception ex) {\n            log.error(\"[updateJobLogResultAsync][logId({}) endTime({}) duration({}) success({}) result({})]\",\n                    logId, endTime, duration, success, result);\n        }\n    }\n\n    @Override\n    @SuppressWarnings(\"DuplicatedCode\")\n    public Integer cleanJobLog(Integer exceedDay, Integer deleteLimit) {\n        int count = 0;\n        LocalDateTime expireDate = LocalDateTime.now().minusDays(exceedDay);\n        // 循环删除，直到没有满足条件的数据\n        for (int i = 0; i < Short.MAX_VALUE; i++) {\n            int deleteCount = jobLogMapper.deleteByCreateTimeLt(expireDate, deleteLimit);\n            count += deleteCount;\n            // 达到删除预期条数，说明到底了\n            if (deleteCount < deleteLimit) {\n                break;\n            }\n        }\n        return count;\n    }\n\n    @Override\n    public JobLogDO getJobLog(Long id) {\n        return jobLogMapper.selectById(id);\n    }\n\n    @Override\n    public PageResult<JobLogDO> getJobLogPage(JobLogPageReqVO pageReqVO) {\n        return jobLogMapper.selectPage(pageReqVO);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/service/job/JobService.java",
    "content": "package co.yixiang.yshop.module.infra.service.job;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.infra.controller.admin.job.vo.job.JobPageReqVO;\nimport co.yixiang.yshop.module.infra.controller.admin.job.vo.job.JobSaveReqVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.job.JobDO;\nimport org.quartz.SchedulerException;\n\nimport jakarta.validation.Valid;\n\n/**\n * 定时任务 Service 接口\n *\n * @author yshop\n */\npublic interface JobService {\n\n    /**\n     * 创建定时任务\n     *\n     * @param createReqVO 创建信息\n     * @return 编号\n     */\n    Long createJob(@Valid JobSaveReqVO createReqVO) throws SchedulerException;\n\n    /**\n     * 更新定时任务\n     *\n     * @param updateReqVO 更新信息\n     */\n    void updateJob(@Valid JobSaveReqVO updateReqVO) throws SchedulerException;\n\n    /**\n     * 更新定时任务的状态\n     *\n     * @param id 任务编号\n     * @param status 状态\n     */\n    void updateJobStatus(Long id, Integer status) throws SchedulerException;\n\n    /**\n     * 触发定时任务\n     *\n     * @param id 任务编号\n     */\n    void triggerJob(Long id) throws SchedulerException;\n\n    /**\n     * 同步定时任务\n     *\n     * 目的：自己存储的 Job 信息，强制同步到 Quartz 中\n     */\n    void syncJob() throws SchedulerException;\n\n    /**\n     * 删除定时任务\n     *\n     * @param id 编号\n     */\n    void deleteJob(Long id) throws SchedulerException;\n\n    /**\n     * 获得定时任务\n     *\n     * @param id 编号\n     * @return 定时任务\n     */\n    JobDO getJob(Long id);\n\n    /**\n     * 获得定时任务分页\n     *\n     * @param pageReqVO 分页查询\n     * @return 定时任务分页\n     */\n    PageResult<JobDO> getJobPage(JobPageReqVO pageReqVO);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/service/job/JobServiceImpl.java",
    "content": "package co.yixiang.yshop.module.infra.service.job;\n\nimport cn.hutool.extra.spring.SpringUtil;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.framework.quartz.core.handler.JobHandler;\nimport co.yixiang.yshop.framework.quartz.core.scheduler.SchedulerManager;\nimport co.yixiang.yshop.framework.quartz.core.util.CronUtils;\nimport co.yixiang.yshop.module.infra.controller.admin.job.vo.job.JobPageReqVO;\nimport co.yixiang.yshop.module.infra.controller.admin.job.vo.job.JobSaveReqVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.job.JobDO;\nimport co.yixiang.yshop.module.infra.dal.mysql.job.JobMapper;\nimport co.yixiang.yshop.module.infra.enums.job.JobStatusEnum;\nimport jakarta.annotation.Resource;\nimport lombok.extern.slf4j.Slf4j;\nimport org.quartz.SchedulerException;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport org.springframework.validation.annotation.Validated;\n\nimport java.util.List;\nimport java.util.Objects;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.framework.common.util.collection.CollectionUtils.containsAny;\nimport static co.yixiang.yshop.module.infra.enums.ErrorCodeConstants.*;\n\n/**\n * 定时任务 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\n@Slf4j\npublic class JobServiceImpl implements JobService {\n\n    @Resource\n    private JobMapper jobMapper;\n\n    @Resource\n    private SchedulerManager schedulerManager;\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public Long createJob(JobSaveReqVO createReqVO) throws SchedulerException {\n        validateCronExpression(createReqVO.getCronExpression());\n        // 1.1 校验唯一性\n        if (jobMapper.selectByHandlerName(createReqVO.getHandlerName()) != null) {\n            throw exception(JOB_HANDLER_EXISTS);\n        }\n        // 1.2 校验 JobHandler 是否存在\n        validateJobHandlerExists(createReqVO.getHandlerName());\n\n        // 2. 插入 JobDO\n        JobDO job = BeanUtils.toBean(createReqVO, JobDO.class);\n        job.setStatus(JobStatusEnum.INIT.getStatus());\n        fillJobMonitorTimeoutEmpty(job);\n        jobMapper.insert(job);\n\n        // 3.1 添加 Job 到 Quartz 中\n        schedulerManager.addJob(job.getId(), job.getHandlerName(), job.getHandlerParam(), job.getCronExpression(),\n                createReqVO.getRetryCount(), createReqVO.getRetryInterval());\n        // 3.2 更新 JobDO\n        JobDO updateObj = JobDO.builder().id(job.getId()).status(JobStatusEnum.NORMAL.getStatus()).build();\n        jobMapper.updateById(updateObj);\n        return job.getId();\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void updateJob(JobSaveReqVO updateReqVO) throws SchedulerException {\n        validateCronExpression(updateReqVO.getCronExpression());\n        // 1.1 校验存在\n        JobDO job = validateJobExists(updateReqVO.getId());\n        // 1.2 只有开启状态，才可以修改.原因是，如果出暂停状态，修改 Quartz Job 时，会导致任务又开始执行\n        if (!job.getStatus().equals(JobStatusEnum.NORMAL.getStatus())) {\n            throw exception(JOB_UPDATE_ONLY_NORMAL_STATUS);\n        }\n        // 1.3 校验 JobHandler 是否存在\n        validateJobHandlerExists(updateReqVO.getHandlerName());\n\n        // 2. 更新 JobDO\n        JobDO updateObj = BeanUtils.toBean(updateReqVO, JobDO.class);\n        fillJobMonitorTimeoutEmpty(updateObj);\n        jobMapper.updateById(updateObj);\n\n        // 3. 更新 Job 到 Quartz 中\n        schedulerManager.updateJob(job.getHandlerName(), updateReqVO.getHandlerParam(), updateReqVO.getCronExpression(),\n                updateReqVO.getRetryCount(), updateReqVO.getRetryInterval());\n    }\n\n    private void validateJobHandlerExists(String handlerName) {\n        Object handler = SpringUtil.getBean(handlerName);\n        if (handler == null) {\n            throw exception(JOB_HANDLER_BEAN_NOT_EXISTS);\n        }\n        if (!(handler instanceof JobHandler)) {\n            throw exception(JOB_HANDLER_BEAN_TYPE_ERROR);\n        }\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void updateJobStatus(Long id, Integer status) throws SchedulerException {\n        // 校验 status\n        if (!containsAny(status, JobStatusEnum.NORMAL.getStatus(), JobStatusEnum.STOP.getStatus())) {\n            throw exception(JOB_CHANGE_STATUS_INVALID);\n        }\n        // 校验存在\n        JobDO job = validateJobExists(id);\n        // 校验是否已经为当前状态\n        if (job.getStatus().equals(status)) {\n            throw exception(JOB_CHANGE_STATUS_EQUALS);\n        }\n        // 更新 Job 状态\n        JobDO updateObj = JobDO.builder().id(id).status(status).build();\n        jobMapper.updateById(updateObj);\n\n        // 更新状态 Job 到 Quartz 中\n        if (JobStatusEnum.NORMAL.getStatus().equals(status)) { // 开启\n            schedulerManager.resumeJob(job.getHandlerName());\n        } else { // 暂停\n            schedulerManager.pauseJob(job.getHandlerName());\n        }\n    }\n\n    @Override\n    public void triggerJob(Long id) throws SchedulerException {\n        // 校验存在\n        JobDO job = validateJobExists(id);\n\n        // 触发 Quartz 中的 Job\n        schedulerManager.triggerJob(job.getId(), job.getHandlerName(), job.getHandlerParam());\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void syncJob() throws SchedulerException {\n        // 1. 查询 Job 配置\n        List<JobDO> jobList = jobMapper.selectList();\n\n        // 2. 遍历处理\n        for (JobDO job : jobList) {\n            // 2.1 先删除，再创建\n            schedulerManager.deleteJob(job.getHandlerName());\n            schedulerManager.addJob(job.getId(), job.getHandlerName(), job.getHandlerParam(), job.getCronExpression(),\n                    job.getRetryCount(), job.getRetryInterval());\n            // 2.2 如果 status 为暂停，则需要暂停\n            if (Objects.equals(job.getStatus(), JobStatusEnum.STOP.getStatus())) {\n                schedulerManager.pauseJob(job.getHandlerName());\n            }\n            log.info(\"[syncJob][id({}) handlerName({}) 同步完成]\", job.getId(), job.getHandlerName());\n        }\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void deleteJob(Long id) throws SchedulerException {\n        // 校验存在\n        JobDO job = validateJobExists(id);\n        // 更新\n        jobMapper.deleteById(id);\n\n        // 删除 Job 到 Quartz 中\n        schedulerManager.deleteJob(job.getHandlerName());\n    }\n\n    private JobDO validateJobExists(Long id) {\n        JobDO job = jobMapper.selectById(id);\n        if (job == null) {\n            throw exception(JOB_NOT_EXISTS);\n        }\n        return job;\n    }\n\n    private void validateCronExpression(String cronExpression) {\n        if (!CronUtils.isValid(cronExpression)) {\n            throw exception(JOB_CRON_EXPRESSION_VALID);\n        }\n    }\n\n    @Override\n    public JobDO getJob(Long id) {\n        return jobMapper.selectById(id);\n    }\n\n    @Override\n    public PageResult<JobDO> getJobPage(JobPageReqVO pageReqVO) {\n\t\treturn jobMapper.selectPage(pageReqVO);\n    }\n\n    private static void fillJobMonitorTimeoutEmpty(JobDO job) {\n        if (job.getMonitorTimeout() == null) {\n            job.setMonitorTimeout(0);\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/service/logger/ApiAccessLogService.java",
    "content": "package co.yixiang.yshop.module.infra.service.logger;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO;\nimport co.yixiang.yshop.module.infra.controller.admin.logger.vo.apiaccesslog.ApiAccessLogPageReqVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.logger.ApiAccessLogDO;\n\n/**\n * API 访问日志 Service 接口\n *\n * @author yshop\n */\npublic interface ApiAccessLogService {\n\n    /**\n     * 创建 API 访问日志\n     *\n     * @param createReqDTO API 访问日志\n     */\n    void createApiAccessLog(ApiAccessLogCreateReqDTO createReqDTO);\n\n    /**\n     * 获得 API 访问日志分页\n     *\n     * @param pageReqVO 分页查询\n     * @return API 访问日志分页\n     */\n    PageResult<ApiAccessLogDO> getApiAccessLogPage(ApiAccessLogPageReqVO pageReqVO);\n\n    /**\n     * 清理 exceedDay 天前的访问日志\n     *\n     * @param exceedDay 超过多少天就进行清理\n     * @param deleteLimit 清理的间隔条数\n     */\n    Integer cleanAccessLog(Integer exceedDay, Integer deleteLimit);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/service/logger/ApiAccessLogServiceImpl.java",
    "content": "package co.yixiang.yshop.module.infra.service.logger;\n\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.infra.api.logger.dto.ApiAccessLogCreateReqDTO;\nimport co.yixiang.yshop.module.infra.controller.admin.logger.vo.apiaccesslog.ApiAccessLogPageReqVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.logger.ApiAccessLogDO;\nimport co.yixiang.yshop.module.infra.dal.mysql.logger.ApiAccessLogMapper;\nimport jakarta.annotation.Resource;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.module.infra.dal.dataobject.logger.ApiAccessLogDO.REQUEST_PARAMS_MAX_LENGTH;\nimport static co.yixiang.yshop.module.infra.dal.dataobject.logger.ApiAccessLogDO.RESULT_MSG_MAX_LENGTH;\n\n/**\n * API 访问日志 Service 实现类\n *\n * @author yshop\n */\n@Slf4j\n@Service\n@Validated\npublic class ApiAccessLogServiceImpl implements ApiAccessLogService {\n\n    @Resource\n    private ApiAccessLogMapper apiAccessLogMapper;\n\n    @Override\n    public void createApiAccessLog(ApiAccessLogCreateReqDTO createDTO) {\n        ApiAccessLogDO apiAccessLog = BeanUtils.toBean(createDTO, ApiAccessLogDO.class);\n        apiAccessLog.setRequestParams(StrUtil.maxLength(apiAccessLog.getRequestParams(), REQUEST_PARAMS_MAX_LENGTH));\n        apiAccessLog.setResultMsg(StrUtil.maxLength(apiAccessLog.getResultMsg(), RESULT_MSG_MAX_LENGTH));\n        apiAccessLogMapper.insert(apiAccessLog);\n    }\n\n    @Override\n    public PageResult<ApiAccessLogDO> getApiAccessLogPage(ApiAccessLogPageReqVO pageReqVO) {\n        return apiAccessLogMapper.selectPage(pageReqVO);\n    }\n\n    @Override\n    @SuppressWarnings(\"DuplicatedCode\")\n    public Integer cleanAccessLog(Integer exceedDay, Integer deleteLimit) {\n        int count = 0;\n        LocalDateTime expireDate = LocalDateTime.now().minusDays(exceedDay);\n        // 循环删除，直到没有满足条件的数据\n        for (int i = 0; i < Short.MAX_VALUE; i++) {\n            int deleteCount = apiAccessLogMapper.deleteByCreateTimeLt(expireDate, deleteLimit);\n            count += deleteCount;\n            // 达到删除预期条数，说明到底了\n            if (deleteCount < deleteLimit) {\n                break;\n            }\n        }\n        return count;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/service/logger/ApiErrorLogService.java",
    "content": "package co.yixiang.yshop.module.infra.service.logger;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;\nimport co.yixiang.yshop.module.infra.controller.admin.logger.vo.apierrorlog.ApiErrorLogPageReqVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.logger.ApiErrorLogDO;\n\n/**\n * API 错误日志 Service 接口\n *\n * @author yshop\n */\npublic interface ApiErrorLogService {\n\n    /**\n     * 创建 API 错误日志\n     *\n     * @param createReqDTO API 错误日志\n     */\n    void createApiErrorLog(ApiErrorLogCreateReqDTO createReqDTO);\n\n    /**\n     * 获得 API 错误日志分页\n     *\n     * @param pageReqVO 分页查询\n     * @return API 错误日志分页\n     */\n    PageResult<ApiErrorLogDO> getApiErrorLogPage(ApiErrorLogPageReqVO pageReqVO);\n\n    /**\n     * 更新 API 错误日志已处理\n     *\n     * @param id API 日志编号\n     * @param processStatus 处理结果\n     * @param processUserId 处理人\n     */\n    void updateApiErrorLogProcess(Long id, Integer processStatus, Long processUserId);\n\n    /**\n     * 清理 exceedDay 天前的错误日志\n     *\n     * @param exceedDay 超过多少天就进行清理\n     * @param deleteLimit 清理的间隔条数\n     */\n    Integer cleanErrorLog(Integer exceedDay, Integer deleteLimit);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/service/logger/ApiErrorLogServiceImpl.java",
    "content": "package co.yixiang.yshop.module.infra.service.logger;\n\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.infra.api.logger.dto.ApiErrorLogCreateReqDTO;\nimport co.yixiang.yshop.module.infra.controller.admin.logger.vo.apierrorlog.ApiErrorLogPageReqVO;\nimport co.yixiang.yshop.module.infra.dal.dataobject.logger.ApiErrorLogDO;\nimport co.yixiang.yshop.module.infra.dal.mysql.logger.ApiErrorLogMapper;\nimport co.yixiang.yshop.module.infra.enums.logger.ApiErrorLogProcessStatusEnum;\nimport jakarta.annotation.Resource;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.infra.dal.dataobject.logger.ApiErrorLogDO.REQUEST_PARAMS_MAX_LENGTH;\nimport static co.yixiang.yshop.module.infra.enums.ErrorCodeConstants.API_ERROR_LOG_NOT_FOUND;\nimport static co.yixiang.yshop.module.infra.enums.ErrorCodeConstants.API_ERROR_LOG_PROCESSED;\n\n/**\n * API 错误日志 Service 实现类\n *\n * @author yshop\n */\n@Slf4j\n@Service\n@Validated\npublic class ApiErrorLogServiceImpl implements ApiErrorLogService {\n\n    @Resource\n    private ApiErrorLogMapper apiErrorLogMapper;\n\n    @Override\n    public void createApiErrorLog(ApiErrorLogCreateReqDTO createDTO) {\n        ApiErrorLogDO apiErrorLog = BeanUtils.toBean(createDTO, ApiErrorLogDO.class)\n                .setProcessStatus(ApiErrorLogProcessStatusEnum.INIT.getStatus());\n        apiErrorLog.setRequestParams(StrUtil.maxLength(apiErrorLog.getRequestParams(), REQUEST_PARAMS_MAX_LENGTH));\n        apiErrorLogMapper.insert(apiErrorLog);\n    }\n\n    @Override\n    public PageResult<ApiErrorLogDO> getApiErrorLogPage(ApiErrorLogPageReqVO pageReqVO) {\n        return apiErrorLogMapper.selectPage(pageReqVO);\n    }\n\n    @Override\n    public void updateApiErrorLogProcess(Long id, Integer processStatus, Long processUserId) {\n        ApiErrorLogDO errorLog = apiErrorLogMapper.selectById(id);\n        if (errorLog == null) {\n            throw exception(API_ERROR_LOG_NOT_FOUND);\n        }\n        if (!ApiErrorLogProcessStatusEnum.INIT.getStatus().equals(errorLog.getProcessStatus())) {\n            throw exception(API_ERROR_LOG_PROCESSED);\n        }\n        // 标记处理\n        apiErrorLogMapper.updateById(ApiErrorLogDO.builder().id(id).processStatus(processStatus)\n                .processUserId(processUserId).processTime(LocalDateTime.now()).build());\n    }\n\n    @Override\n    @SuppressWarnings(\"DuplicatedCode\")\n    public Integer cleanErrorLog(Integer exceedDay, Integer deleteLimit) {\n        int count = 0;\n        LocalDateTime expireDate = LocalDateTime.now().minusDays(exceedDay);\n        // 循环删除，直到没有满足条件的数据\n        for (int i = 0; i < Short.MAX_VALUE; i++) {\n            int deleteCount = apiErrorLogMapper.deleteByCreateTimeLt(expireDate, deleteLimit);\n            count += deleteCount;\n            // 达到删除预期条数，说明到底了\n            if (deleteCount < deleteLimit) {\n                break;\n            }\n        }\n        return count;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/websocket/DemoWebSocketMessageListener.java",
    "content": "package co.yixiang.yshop.module.infra.websocket;\n\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.websocket.core.listener.WebSocketMessageListener;\nimport co.yixiang.yshop.framework.websocket.core.sender.WebSocketMessageSender;\nimport co.yixiang.yshop.framework.websocket.core.util.WebSocketFrameworkUtils;\nimport co.yixiang.yshop.module.infra.websocket.message.DemoReceiveMessage;\nimport co.yixiang.yshop.module.infra.websocket.message.DemoSendMessage;\nimport org.springframework.stereotype.Component;\nimport org.springframework.web.socket.WebSocketSession;\n\nimport jakarta.annotation.Resource;\n\n/**\n * WebSocket 示例：单发消息\n *\n * @author yshop\n */\n@Component\npublic class DemoWebSocketMessageListener implements WebSocketMessageListener<DemoSendMessage> {\n\n    @Resource\n    private WebSocketMessageSender webSocketMessageSender;\n\n    @Override\n    public void onMessage(WebSocketSession session, DemoSendMessage message) {\n        Long fromUserId = WebSocketFrameworkUtils.getLoginUserId(session);\n        // 情况一：单发\n        if (message.getToUserId() != null) {\n            DemoReceiveMessage toMessage = new DemoReceiveMessage().setFromUserId(fromUserId)\n                    .setText(message.getText()).setSingle(true);\n            webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), message.getToUserId(), // 给指定用户\n                    \"demo-message-receive\", toMessage);\n            return;\n        }\n        // 情况二：群发\n        DemoReceiveMessage toMessage = new DemoReceiveMessage().setFromUserId(fromUserId)\n                .setText(message.getText()).setSingle(false);\n        webSocketMessageSender.sendObject(UserTypeEnum.ADMIN.getValue(), // 给所有用户\n                \"demo-message-receive\", toMessage);\n    }\n\n    @Override\n    public String getType() {\n        return \"demo-message-send\";\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/websocket/message/DemoReceiveMessage.java",
    "content": "package co.yixiang.yshop.module.infra.websocket.message;\n\nimport lombok.Data;\n\n/**\n * 示例：server -> client 同步消息\n *\n * @author yshop\n */\n@Data\npublic class DemoReceiveMessage {\n\n    /**\n     * 接收人的编号\n     */\n    private Long fromUserId;\n    /**\n     * 内容\n     */\n    private String text;\n\n    /**\n     * 是否单聊\n     */\n    private Boolean single;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/java/co/yixiang/yshop/module/infra/websocket/message/DemoSendMessage.java",
    "content": "package co.yixiang.yshop.module.infra.websocket.message;\n\nimport lombok.Data;\n\n/**\n * 示例：client -> server 发送消息\n *\n * @author yshop\n */\n@Data\npublic class DemoSendMessage {\n\n    /**\n     * 发送给谁\n     *\n     * 如果为空，说明发送给所有人\n     */\n    private Long toUserId;\n    /**\n     * 内容\n     */\n    private String text;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/java/controller/controller.vm",
    "content": "package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName};\n\nimport org.springframework.web.bind.annotation.*;\nimport ${jakartaPackage}.annotation.Resource;\nimport org.springframework.validation.annotation.Validated;\n#if ($sceneEnum.scene == 1)import org.springframework.security.access.prepost.PreAuthorize;#end\n\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.Operation;\n\nimport ${jakartaPackage}.validation.constraints.*;\nimport ${jakartaPackage}.validation.*;\nimport ${jakartaPackage}.servlet.http.*;\nimport java.util.*;\nimport java.io.IOException;\n\nimport ${PageParamClassName};\nimport ${PageResultClassName};\nimport ${CommonResultClassName};\nimport ${BeanUtils};\nimport static ${CommonResultClassName}.success;\n\nimport ${ExcelUtilsClassName};\n\nimport ${ApiAccessLogClassName};\nimport static ${OperateTypeEnumClassName}.*;\n\nimport ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo.*;\nimport ${basePackage}.module.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;\n## 特殊：主子表专属逻辑\n#foreach ($subTable in $subTables)\nimport ${basePackage}.module.${subTable.moduleName}.dal.dataobject.${subTable.businessName}.${subTable.className}DO;\n#end\nimport ${basePackage}.module.${table.moduleName}.service.${table.businessName}.${table.className}Service;\n\n@Tag(name = \"${sceneEnum.name} - ${table.classComment}\")\n@RestController\n##二级的 businessName 暂时不算在 HTTP 路径上，可以根据需要写\n@RequestMapping(\"/${table.moduleName}/${simpleClassName_strikeCase}\")\n@Validated\npublic class ${sceneEnum.prefixClass}${table.className}Controller {\n\n    @Resource\n    private ${table.className}Service ${classNameVar}Service;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建${table.classComment}\")\n#if ($sceneEnum.scene == 1)\n    @PreAuthorize(\"@ss.hasPermission('${permissionPrefix}:create')\")\n#end\n    public CommonResult<${primaryColumn.javaType}> create${simpleClassName}(@Valid @RequestBody ${sceneEnum.prefixClass}${table.className}SaveReqVO createReqVO) {\n        return success(${classNameVar}Service.create${simpleClassName}(createReqVO));\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"更新${table.classComment}\")\n#if ($sceneEnum.scene == 1)\n    @PreAuthorize(\"@ss.hasPermission('${permissionPrefix}:update')\")\n#end\n    public CommonResult<Boolean> update${simpleClassName}(@Valid @RequestBody ${sceneEnum.prefixClass}${table.className}SaveReqVO updateReqVO) {\n        ${classNameVar}Service.update${simpleClassName}(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除${table.classComment}\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n#if ($sceneEnum.scene == 1)\n    @PreAuthorize(\"@ss.hasPermission('${permissionPrefix}:delete')\")\n#end\n    public CommonResult<Boolean> delete${simpleClassName}(@RequestParam(\"id\") ${primaryColumn.javaType} id) {\n        ${classNameVar}Service.delete${simpleClassName}(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得${table.classComment}\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n#if ($sceneEnum.scene == 1)\n    @PreAuthorize(\"@ss.hasPermission('${permissionPrefix}:query')\")\n#end\n    public CommonResult<${sceneEnum.prefixClass}${table.className}RespVO> get${simpleClassName}(@RequestParam(\"id\") ${primaryColumn.javaType} id) {\n        ${table.className}DO ${classNameVar} = ${classNameVar}Service.get${simpleClassName}(id);\n        return success(BeanUtils.toBean(${classNameVar}, ${sceneEnum.prefixClass}${table.className}RespVO.class));\n    }\n\n#if ( $table.templateType != 2 )\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得${table.classComment}分页\")\n#if ($sceneEnum.scene == 1)\n    @PreAuthorize(\"@ss.hasPermission('${permissionPrefix}:query')\")\n#end\n    public CommonResult<PageResult<${sceneEnum.prefixClass}${table.className}RespVO>> get${simpleClassName}Page(@Valid ${sceneEnum.prefixClass}${table.className}PageReqVO pageReqVO) {\n        PageResult<${table.className}DO> pageResult = ${classNameVar}Service.get${simpleClassName}Page(pageReqVO);\n        return success(BeanUtils.toBean(pageResult, ${sceneEnum.prefixClass}${table.className}RespVO.class));\n    }\n\n## 特殊：树表专属逻辑（树不需要分页接口）\n#else\n    @GetMapping(\"/list\")\n    @Operation(summary = \"获得${table.classComment}列表\")\n#if ($sceneEnum.scene == 1)\n    @PreAuthorize(\"@ss.hasPermission('${permissionPrefix}:query')\")\n#end\n    public CommonResult<List<${sceneEnum.prefixClass}${table.className}RespVO>> get${simpleClassName}List(@Valid ${sceneEnum.prefixClass}${table.className}ListReqVO listReqVO) {\n        List<${table.className}DO> list = ${classNameVar}Service.get${simpleClassName}List(listReqVO);\n        return success(BeanUtils.toBean(list, ${sceneEnum.prefixClass}${table.className}RespVO.class));\n    }\n\n#end\n    @GetMapping(\"/export-excel\")\n    @Operation(summary = \"导出${table.classComment} Excel\")\n#if ($sceneEnum.scene == 1)\n    @PreAuthorize(\"@ss.hasPermission('${permissionPrefix}:export')\")\n#end\n    @ApiAccessLog(operateType = EXPORT)\n#if ( $table.templateType != 2 )\n    public void export${simpleClassName}Excel(@Valid ${sceneEnum.prefixClass}${table.className}PageReqVO pageReqVO,\n              HttpServletResponse response) throws IOException {\n        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);\n        List<${table.className}DO> list = ${classNameVar}Service.get${simpleClassName}Page(pageReqVO).getList();\n        // 导出 Excel\n        ExcelUtils.write(response, \"${table.classComment}.xls\", \"数据\", ${table.className}RespVO.class,\n                        BeanUtils.toBean(list, ${table.className}RespVO.class));\n    }\n## 特殊：树表专属逻辑（树不需要分页接口）\n#else\n    public void export${simpleClassName}Excel(@Valid ${sceneEnum.prefixClass}${table.className}ListReqVO listReqVO,\n              HttpServletResponse response) throws IOException {\n        List<${table.className}DO> list = ${classNameVar}Service.get${simpleClassName}List(listReqVO);\n        // 导出 Excel\n        ExcelUtils.write(response, \"${table.classComment}.xls\", \"数据\", ${table.className}RespVO.class,\n                        BeanUtils.toBean(list, ${table.className}RespVO.class));\n    }\n#end\n\n## 特殊：主子表专属逻辑\n#foreach ($subTable in $subTables)\n#set ($index = $foreach.count - 1)\n#set ($subSimpleClassName = $subSimpleClassNames.get($index))\n#set ($subPrimaryColumn = $subPrimaryColumns.get($index))##当前 primary 字段\n#set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段\n#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写\n#set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($index))\n#set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))\n#set ($subClassNameVar = $subClassNameVars.get($index))\n    // ==================== 子表（$subTable.classComment） ====================\n\n## 情况一：MASTER_ERP 时，需要分查询页子表\n#if ( $table.templateType == 11 )\n    @GetMapping(\"/${subSimpleClassName_strikeCase}/page\")\n    @Operation(summary = \"获得${subTable.classComment}分页\")\n    @Parameter(name = \"${subJoinColumn.javaField}\", description = \"${subJoinColumn.columnComment}\")\n#if ($sceneEnum.scene == 1)\n    @PreAuthorize(\"@ss.hasPermission('${permissionPrefix}:query')\")\n#end\n    public CommonResult<PageResult<${subTable.className}DO>> get${subSimpleClassName}Page(PageParam pageReqVO,\n                                                                                        @RequestParam(\"${subJoinColumn.javaField}\") ${subJoinColumn.javaType} ${subJoinColumn.javaField}) {\n        return success(${classNameVar}Service.get${subSimpleClassName}Page(pageReqVO, ${subJoinColumn.javaField}));\n    }\n\n## 情况二：非 MASTER_ERP 时，需要列表查询子表\n#else\n    #if ( $subTable.subJoinMany )\n    @GetMapping(\"/${subSimpleClassName_strikeCase}/list-by-${subJoinColumn_strikeCase}\")\n    @Operation(summary = \"获得${subTable.classComment}列表\")\n    @Parameter(name = \"${subJoinColumn.javaField}\", description = \"${subJoinColumn.columnComment}\")\n#if ($sceneEnum.scene == 1)\n    @PreAuthorize(\"@ss.hasPermission('${permissionPrefix}:query')\")\n#end\n    public CommonResult<List<${subTable.className}DO>> get${subSimpleClassName}ListBy${SubJoinColumnName}(@RequestParam(\"${subJoinColumn.javaField}\") ${subJoinColumn.javaType} ${subJoinColumn.javaField}) {\n        return success(${classNameVar}Service.get${subSimpleClassName}ListBy${SubJoinColumnName}(${subJoinColumn.javaField}));\n    }\n\n    #else\n    @GetMapping(\"/${subSimpleClassName_strikeCase}/get-by-${subJoinColumn_strikeCase}\")\n    @Operation(summary = \"获得${subTable.classComment}\")\n    @Parameter(name = \"${subJoinColumn.javaField}\", description = \"${subJoinColumn.columnComment}\")\n#if ($sceneEnum.scene == 1)\n    @PreAuthorize(\"@ss.hasPermission('${permissionPrefix}:query')\")\n#end\n    public CommonResult<${subTable.className}DO> get${subSimpleClassName}By${SubJoinColumnName}(@RequestParam(\"${subJoinColumn.javaField}\") ${subJoinColumn.javaType} ${subJoinColumn.javaField}) {\n        return success(${classNameVar}Service.get${subSimpleClassName}By${SubJoinColumnName}(${subJoinColumn.javaField}));\n    }\n\n    #end\n#end\n## 特殊：MASTER_ERP 时，支持单个的新增、修改、删除操作\n#if ( $table.templateType == 11 )\n    @PostMapping(\"/${subSimpleClassName_strikeCase}/create\")\n    @Operation(summary = \"创建${subTable.classComment}\")\n#if ($sceneEnum.scene == 1)\n    @PreAuthorize(\"@ss.hasPermission('${permissionPrefix}:create')\")\n#end\n    public CommonResult<${subPrimaryColumn.javaType}> create${subSimpleClassName}(@Valid @RequestBody ${subTable.className}DO ${subClassNameVar}) {\n        return success(${classNameVar}Service.create${subSimpleClassName}(${subClassNameVar}));\n    }\n\n    @PutMapping(\"/${subSimpleClassName_strikeCase}/update\")\n    @Operation(summary = \"更新${subTable.classComment}\")\n#if ($sceneEnum.scene == 1)\n    @PreAuthorize(\"@ss.hasPermission('${permissionPrefix}:update')\")\n#end\n    public CommonResult<Boolean> update${subSimpleClassName}(@Valid @RequestBody ${subTable.className}DO ${subClassNameVar}) {\n        ${classNameVar}Service.update${subSimpleClassName}(${subClassNameVar});\n        return success(true);\n    }\n\n    @DeleteMapping(\"/${subSimpleClassName_strikeCase}/delete\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @Operation(summary = \"删除${subTable.classComment}\")\n#if ($sceneEnum.scene == 1)\n    @PreAuthorize(\"@ss.hasPermission('${permissionPrefix}:delete')\")\n#end\n    public CommonResult<Boolean> delete${subSimpleClassName}(@RequestParam(\"id\") ${subPrimaryColumn.javaType} id) {\n        ${classNameVar}Service.delete${subSimpleClassName}(id);\n        return success(true);\n    }\n\n\t@GetMapping(\"/${subSimpleClassName_strikeCase}/get\")\n\t@Operation(summary = \"获得${subTable.classComment}\")\n\t@Parameter(name = \"id\", description = \"编号\", required = true)\n#if ($sceneEnum.scene == 1)\n    @PreAuthorize(\"@ss.hasPermission('${permissionPrefix}:query')\")\n#end\n\tpublic CommonResult<${subTable.className}DO> get${subSimpleClassName}(@RequestParam(\"id\") ${subPrimaryColumn.javaType} id) {\n\t    return success(${classNameVar}Service.get${subSimpleClassName}(id));\n\t}\n\n#end\n#end\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/java/controller/vo/listReqVO.vm",
    "content": "package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo;\n\nimport lombok.*;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport ${PageParamClassName};\n#foreach ($column in $columns)\n#if (${column.javaType} == \"BigDecimal\")\nimport java.math.BigDecimal;\n#break\n#end\n#end\n## 处理 LocalDateTime 字段的引入\n#foreach ($column in $columns)\n#if (${column.listOperation} && ${column.javaType} == \"LocalDateTime\")\nimport java.time.LocalDateTime;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport static ${DateUtilsClassName}.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n#break\n#end\n#end\n## 字段模板\n#macro(columnTpl $prefix $prefixStr)\n    @Schema(description = \"${prefixStr}${column.columnComment}\"#if (\"$!column.example\" != \"\"), example = \"${column.example}\"#end)\n    private ${column.javaType}#if (\"$!prefix\" != \"\") ${prefix}${JavaField}#else ${column.javaField}#end;\n#end\n\n@Schema(description = \"${sceneEnum.name} - ${table.classComment}列表 Request VO\")\n@Data\npublic class ${sceneEnum.prefixClass}${table.className}ListReqVO {\n\n#foreach ($column in $columns)\n#if (${column.listOperation})##查询操作\n#if (${column.listOperationCondition} == \"BETWEEN\")## 情况一，Between 的时候\n    @Schema(description = \"${column.columnComment}\"#if (\"$!column.example\" != \"\"), example = \"${column.example}\"#end)\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private ${column.javaType}[] ${column.javaField};\n#else##情况二，非 Between 的时间\n    #columnTpl('', '')\n#end\n\n#end\n#end\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/java/controller/vo/pageReqVO.vm",
    "content": "package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo;\n\nimport lombok.*;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport ${PageParamClassName};\n#foreach ($column in $columns)\n#if (${column.javaType} == \"BigDecimal\")\nimport java.math.BigDecimal;\n#break\n#end\n#end\n## 处理 LocalDateTime 字段的引入\n#foreach ($column in $columns)\n#if (${column.listOperationCondition} && ${column.javaType} == \"LocalDateTime\")\nimport org.springframework.format.annotation.DateTimeFormat;\nimport java.time.LocalDateTime;\n\nimport static ${DateUtilsClassName}.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n#break\n#end\n#end\n## 字段模板\n#macro(columnTpl $prefix $prefixStr)\n    @Schema(description = \"${prefixStr}${column.columnComment}\"#if (\"$!column.example\" != \"\"), example = \"${column.example}\"#end)\n    private ${column.javaType}#if (\"$!prefix\" != \"\") ${prefix}${JavaField}#else ${column.javaField}#end;\n#end\n\n@Schema(description = \"${sceneEnum.name} - ${table.classComment}分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class ${sceneEnum.prefixClass}${table.className}PageReqVO extends PageParam {\n\n#foreach ($column in $columns)\n#if (${column.listOperation})##查询操作\n#if (${column.listOperationCondition} == \"BETWEEN\")## 情况一，Between 的时候\n    @Schema(description = \"${column.columnComment}\"#if (\"$!column.example\" != \"\"), example = \"${column.example}\"#end)\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private ${column.javaType}[] ${column.javaField};\n#else##情况二，非 Between 的时间\n    #columnTpl('', '')\n#end\n\n#end\n#end\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/java/controller/vo/respVO.vm",
    "content": "package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.util.*;\n## 处理 BigDecimal 字段的引入\nimport java.util.*;\n#foreach ($column in $columns)\n#if (${column.javaType} == \"BigDecimal\")\nimport java.math.BigDecimal;\n#break\n#end\n#end\n## 处理 LocalDateTime 字段的引入\n#foreach ($column in $columns)\n#if (${column.listOperationResult} && ${column.javaType} == \"LocalDateTime\")\nimport org.springframework.format.annotation.DateTimeFormat;\nimport java.time.LocalDateTime;\n#break\n#end\n#end\n## 处理 Excel 导出\nimport com.alibaba.excel.annotation.*;\n#foreach ($column in $columns)\n#if (\"$!column.dictType\" != \"\")## 有设置数据字典\nimport ${DictFormatClassName};\nimport ${DictConvertClassName};\n#break\n#end\n#end\n\n@Schema(description = \"${sceneEnum.name} - ${table.classComment} Response VO\")\n@Data\n@ExcelIgnoreUnannotated\npublic class ${sceneEnum.prefixClass}${table.className}RespVO {\n\n## 逐个处理字段\n#foreach ($column in $columns)\n#if (${column.listOperationResult})\n## 1. 处理 Swagger 注解\n    @Schema(description = \"${column.columnComment}\"#if (!${column.nullable}), requiredMode = Schema.RequiredMode.REQUIRED#end#if (\"$!column.example\" != \"\"), example = \"${column.example}\"#end)\n## 2. 处理 Excel 导出\n#if (\"$!column.dictType\" != \"\")##处理枚举值\n    @ExcelProperty(value = \"${column.columnComment}\", converter = DictConvert.class)\n    @DictFormat(\"${column.dictType}\") // TODO 代码优化：建议设置到对应的 DictTypeConstants 枚举类中\n#else\n    @ExcelProperty(\"${column.columnComment}\")\n#end\n## 3. 处理字段定义\n    private ${column.javaType} ${column.javaField};\n\n#end\n#end\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/java/controller/vo/saveReqVO.vm",
    "content": "package ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.util.*;\nimport ${jakartaPackage}.validation.constraints.*;\n## 处理 BigDecimal 字段的引入\n#foreach ($column in $columns)\n#if (${column.javaType} == \"BigDecimal\")\nimport java.math.BigDecimal;\n#break\n#end\n#end\n## 处理 LocalDateTime 字段的引入\n#foreach ($column in $columns)\n#if ((${column.createOperation} || ${column.updateOperation}) && ${column.javaType} == \"LocalDateTime\")\nimport org.springframework.format.annotation.DateTimeFormat;\nimport java.time.LocalDateTime;\n#break\n#end\n#end\n## 特殊：主子表专属逻辑\n#foreach ($subTable in $subTables)\nimport ${basePackage}.module.${subTable.moduleName}.dal.dataobject.${subTable.businessName}.${subTable.className}DO;\n#end\n\n@Schema(description = \"${sceneEnum.name} - ${table.classComment}新增/修改 Request VO\")\n@Data\npublic class ${sceneEnum.prefixClass}${table.className}SaveReqVO {\n\n## 逐个处理字段\n#foreach ($column in $columns)\n#if (${column.createOperation} || ${column.updateOperation})\n## 1. 处理 Swagger 注解\n    @Schema(description = \"${column.columnComment}\"#if (!${column.nullable}), requiredMode = Schema.RequiredMode.REQUIRED#end#if (\"$!column.example\" != \"\"), example = \"${column.example}\"#end)\n## 2. 处理 Validator 参数校验\n#if (!${column.nullable} && !${column.primaryKey})\n#if (${column.javaType} == 'String')\n    @NotEmpty(message = \"${column.columnComment}不能为空\")\n#else\n    @NotNull(message = \"${column.columnComment}不能为空\")\n#end\n#end\n## 3. 处理字段定义\n    private ${column.javaType} ${column.javaField};\n\n#end\n#end\n## 特殊：主子表专属逻辑（非 ERP 模式）\n#if ( $subTables && $subTables.size() > 0 && $table.templateType != 11 )\n#foreach ($subTable in $subTables)\n#set ($index = $foreach.count - 1)\n    #if ( $subTable.subJoinMany)\n    @Schema(description = \"${subTable.classComment}列表\")\n    private List<${subTable.className}DO> ${subClassNameVars.get($index)}s;\n\n    #else\n    @Schema(description = \"${subTable.classComment}\")\n    private ${subTable.className}DO ${subClassNameVars.get($index)};\n\n    #end\n#end\n#end\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/java/dal/do.vm",
    "content": "package ${basePackage}.module.${table.moduleName}.dal.dataobject.${table.businessName};\n\nimport lombok.*;\nimport java.util.*;\n#foreach ($column in $columns)\n#if (${column.javaType} == \"BigDecimal\")\nimport java.math.BigDecimal;\n#end\n#if (${column.javaType} == \"LocalDateTime\")\nimport java.time.LocalDateTime;\n#end\n#end\nimport com.baomidou.mybatisplus.annotation.*;\nimport ${BaseDOClassName};\n\n/**\n * ${table.classComment} DO\n *\n * @author ${table.author}\n */\n@TableName(\"${table.tableName.toLowerCase()}\")\n@KeySequence(\"${table.tableName.toLowerCase()}_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class ${table.className}DO extends BaseDO {\n\n## 特殊：树表专属逻辑\n#if ( $table.templateType == 2 )\n    public static final Long ${treeParentColumn_javaField_underlineCase.toUpperCase()}_ROOT = 0L;\n\n#end\n#foreach ($column in $columns)\n#if (!${baseDOFields.contains(${column.javaField})})##排除 BaseDO 的字段\n    /**\n     * ${column.columnComment}\n    #if (\"$!column.dictType\" != \"\")##处理枚举值\n     *\n     * 枚举 {@link TODO ${column.dictType} 对应的类}\n    #end\n     */\n    #if (${column.primaryKey})##处理主键\n    @TableId#if (${column.javaType} == 'String')(type = IdType.INPUT)#end\n    #end\n    private ${column.javaType} ${column.javaField};\n#end\n#end\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/java/dal/do_sub.vm",
    "content": "#set ($subTable = $subTables.get($subIndex))##当前表\n#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组\npackage ${basePackage}.module.${subTable.moduleName}.dal.dataobject.${subTable.businessName};\n\nimport lombok.*;\nimport java.util.*;\n#foreach ($column in $subColumns)\n#if (${column.javaType} == \"BigDecimal\")\nimport java.math.BigDecimal;\n#end\n#if (${column.javaType} == \"LocalDateTime\")\nimport java.time.LocalDateTime;\n#end\n#end\nimport com.baomidou.mybatisplus.annotation.*;\nimport ${BaseDOClassName};\n\n/**\n * ${subTable.classComment} DO\n *\n * @author ${subTable.author}\n */\n@TableName(\"${subTable.tableName.toLowerCase()}\")\n@KeySequence(\"${subTable.tableName.toLowerCase()}_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class ${subTable.className}DO extends BaseDO {\n\n#foreach ($column in $subColumns)\n#if (!${baseDOFields.contains(${column.javaField})})##排除 BaseDO 的字段\n    /**\n     * ${column.columnComment}\n    #if (\"$!column.dictType\" != \"\")##处理枚举值\n     *\n     * 枚举 {@link TODO ${column.dictType} 对应的类}\n    #end\n     */\n    #if (${column.primaryKey})##处理主键\n    @TableId#if (${column.javaType} == 'String')(type = IdType.INPUT)#end\n    #end\n    private ${column.javaType} ${column.javaField};\n#end\n#end\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/java/dal/mapper.vm",
    "content": "package ${basePackage}.module.${table.moduleName}.dal.mysql.${table.businessName};\n\nimport java.util.*;\n\nimport ${PageResultClassName};\nimport ${QueryWrapperClassName};\nimport ${BaseMapperClassName};\nimport ${basePackage}.module.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;\nimport org.apache.ibatis.annotations.Mapper;\nimport ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo.*;\n\n## 字段模板\n#macro(listCondition)\n#foreach ($column in $columns)\n#if (${column.listOperation})\n#set ($JavaField = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})##首字母大写\n#if (${column.listOperationCondition} == \"=\")##情况一，= 的时候\n                .eqIfPresent(${table.className}DO::get${JavaField}, reqVO.get${JavaField}())\n#end\n#if (${column.listOperationCondition} == \"!=\")##情况二，!= 的时候\n                .neIfPresent(${table.className}DO::get${JavaField}, reqVO.get${JavaField}())\n#end\n#if (${column.listOperationCondition} == \">\")##情况三，> 的时候\n                .gtIfPresent(${table.className}DO::get${JavaField}, reqVO.get${JavaField}())\n#end\n#if (${column.listOperationCondition} == \">=\")##情况四，>= 的时候\n                .geIfPresent(${table.className}DO::get${JavaField}, reqVO.get${JavaField}())\n#end\n#if (${column.listOperationCondition} == \"<\")##情况五，< 的时候\n                .ltIfPresent(${table.className}DO::get${JavaField}, reqVO.get${JavaField}())\n#end\n#if (${column.listOperationCondition} == \"<=\")##情况五，<= 的时候\n                .leIfPresent(${table.className}DO::get${JavaField}, reqVO.get${JavaField}())\n#end\n#if (${column.listOperationCondition} == \"LIKE\")##情况七，Like 的时候\n                .likeIfPresent(${table.className}DO::get${JavaField}, reqVO.get${JavaField}())\n#end\n#if (${column.listOperationCondition} == \"BETWEEN\")##情况八，Between 的时候\n                .betweenIfPresent(${table.className}DO::get${JavaField}, reqVO.get${JavaField}())\n#end\n#end\n#end\n#end\n/**\n * ${table.classComment} Mapper\n *\n * @author ${table.author}\n */\n@Mapper\npublic interface ${table.className}Mapper extends BaseMapperX<${table.className}DO> {\n\n## 特殊：树表专属逻辑（树不需要分页接口）\n#if ( $table.templateType != 2 )\n    default PageResult<${table.className}DO> selectPage(${sceneEnum.prefixClass}${table.className}PageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<${table.className}DO>()\n\t\t\t#listCondition()\n                .orderByDesc(${table.className}DO::getId));## 大多数情况下，id 倒序\n\n    }\n#else\n    default List<${table.className}DO> selectList(${sceneEnum.prefixClass}${table.className}ListReqVO reqVO) {\n        return selectList(new LambdaQueryWrapperX<${table.className}DO>()\n\t\t\t#listCondition()\n                .orderByDesc(${table.className}DO::getId));## 大多数情况下，id 倒序\n\n    }\n#end\n\n## 特殊：树表专属逻辑\n#if ( $table.templateType == 2 )\n#set ($TreeParentJavaField = $treeParentColumn.javaField.substring(0,1).toUpperCase() + ${treeParentColumn.javaField.substring(1)})##首字母大写\n#set ($TreeNameJavaField = $treeNameColumn.javaField.substring(0,1).toUpperCase() + ${treeNameColumn.javaField.substring(1)})##首字母大写\n\tdefault ${table.className}DO selectBy${TreeParentJavaField}And${TreeNameJavaField}(Long ${treeParentColumn.javaField}, String ${treeNameColumn.javaField}) {\n\t    return selectOne(${table.className}DO::get${TreeParentJavaField}, ${treeParentColumn.javaField}, ${table.className}DO::get${TreeNameJavaField}, ${treeNameColumn.javaField});\n\t}\n\n    default Long selectCountBy${TreeParentJavaField}(${treeParentColumn.javaType} ${treeParentColumn.javaField}) {\n        return selectCount(${table.className}DO::get${TreeParentJavaField}, ${treeParentColumn.javaField});\n    }\n\n#end\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/java/dal/mapper.xml.vm",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"${basePackage}.module.${table.moduleName}.dal.mysql.${table.businessName}.${table.className}Mapper\">\n\n    <!--\n        一般情况下，尽可能使用 Mapper 进行 CRUD 增删改查即可。\n        无法满足的场景，例如说多表关联查询，才使用 XML 编写 SQL。\n        代码生成器暂时只生成 Mapper XML 文件本身，更多推荐 MybatisX 快速开发插件来生成查询。\n        文档可见：https://www.yixiang.co/MyBatis/x-plugins/\n     -->\n\n</mapper>"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/java/dal/mapper_sub.vm",
    "content": "#set ($subTable = $subTables.get($subIndex))##当前表\n#set ($subColumns = $subJoinColumnsList.get($subIndex))##当前字段数组\n#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段\n#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写\npackage ${basePackage}.module.${subTable.moduleName}.dal.mysql.${subTable.businessName};\n\nimport java.util.*;\n\nimport ${PageResultClassName};\nimport ${PageParamClassName};\nimport ${QueryWrapperClassName};\nimport ${BaseMapperClassName};\nimport ${basePackage}.module.${subTable.moduleName}.dal.dataobject.${subTable.businessName}.${subTable.className}DO;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * ${subTable.classComment} Mapper\n *\n * @author ${subTable.author}\n */\n@Mapper\npublic interface ${subTable.className}Mapper extends BaseMapperX<${subTable.className}DO> {\n\n## 情况一：MASTER_ERP 时，需要分查询页子表\n#if ( $table.templateType == 11 )\n    default PageResult<${subTable.className}DO> selectPage(PageParam reqVO, ${subJoinColumn.javaType} ${subJoinColumn.javaField}) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<${subTable.className}DO>()\n            .eq(${subTable.className}DO::get${SubJoinColumnName}, ${subJoinColumn.javaField})\n            .orderByDesc(${subTable.className}DO::getId));## 大多数情况下，id 倒序\n\n    }\n\n## 情况二：非 MASTER_ERP 时，需要列表查询子表\n#else\n    #if ( $subTable.subJoinMany)\n    default List<${subTable.className}DO> selectListBy${SubJoinColumnName}(${subJoinColumn.javaType} ${subJoinColumn.javaField}) {\n        return selectList(${subTable.className}DO::get${SubJoinColumnName}, ${subJoinColumn.javaField});\n    }\n\n    #else\n    default ${subTable.className}DO selectBy${SubJoinColumnName}(${subJoinColumn.javaType} ${subJoinColumn.javaField}) {\n        return selectOne(${subTable.className}DO::get${SubJoinColumnName}, ${subJoinColumn.javaField});\n    }\n\n    #end\n    #end\n    default int deleteBy${SubJoinColumnName}(${subJoinColumn.javaType} ${subJoinColumn.javaField}) {\n        return delete(${subTable.className}DO::get${SubJoinColumnName}, ${subJoinColumn.javaField});\n    }\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/java/enums/errorcode.vm",
    "content": "// TODO 待办：请将下面的错误码复制到 yshop-module-${table.moduleName}-api 模块的 ErrorCodeConstants 类中。注意，请给“TODO 补充编号”设置一个错误码编号！！！\n// ========== ${table.classComment} TODO 补充编号 ==========\nErrorCode ${simpleClassName_underlineCase.toUpperCase()}_NOT_EXISTS = new ErrorCode(TODO 补充编号, \"${table.classComment}不存在\");\n## 特殊：树表专属逻辑\n#if ( $table.templateType == 2 )\nErrorCode ${simpleClassName_underlineCase.toUpperCase()}_EXITS_CHILDREN = new ErrorCode(TODO 补充编号, \"存在存在子${table.classComment}，无法删除\");\nErrorCode ${simpleClassName_underlineCase.toUpperCase()}_PARENT_NOT_EXITS = new ErrorCode(TODO 补充编号,\"父级${table.classComment}不存在\");\nErrorCode ${simpleClassName_underlineCase.toUpperCase()}_PARENT_ERROR = new ErrorCode(TODO 补充编号, \"不能设置自己为父${table.classComment}\");\nErrorCode ${simpleClassName_underlineCase.toUpperCase()}_${treeNameColumn_javaField_underlineCase.toUpperCase()}_DUPLICATE = new ErrorCode(TODO 补充编号, \"已经存在该${treeNameColumn.columnComment}的${table.classComment}\");\nErrorCode ${simpleClassName_underlineCase.toUpperCase()}_PARENT_IS_CHILD = new ErrorCode(TODO 补充编号, \"不能设置自己的子${table.className}为父${table.className}\");\n#end\n## 特殊：主子表专属逻辑\n#if ( $table.templateType == 11 )## 特殊：ERP 情况\n#foreach ($subTable in $subTables)\n#set ($index = $foreach.count - 1)\n#set ($simpleClassNameUnderlineCase = $simpleClassNameUnderlineCases.get($index))\nErrorCode ${simpleClassNameUnderlineCase.toUpperCase()}_NOT_EXISTS = new ErrorCode(TODO 补充编号, \"${subTable.classComment}不存在\");\n#if ( !$subTable.subJoinMany )\nErrorCode ${simpleClassNameUnderlineCase.toUpperCase()}_EXISTS = new ErrorCode(TODO 补充编号, \"${subTable.classComment}已存在\");\n#end\n#end\n#end"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/java/service/service.vm",
    "content": "package ${basePackage}.module.${table.moduleName}.service.${table.businessName};\n\nimport java.util.*;\nimport ${jakartaPackage}.validation.*;\nimport ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo.*;\nimport ${basePackage}.module.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;\n## 特殊：主子表专属逻辑\n#foreach ($subTable in $subTables)\nimport ${basePackage}.module.${subTable.moduleName}.dal.dataobject.${subTable.businessName}.${subTable.className}DO;\n#end\nimport ${PageResultClassName};\nimport ${PageParamClassName};\n\n/**\n * ${table.classComment} Service 接口\n *\n * @author ${table.author}\n */\npublic interface ${table.className}Service {\n\n    /**\n     * 创建${table.classComment}\n     *\n     * @param createReqVO 创建信息\n     * @return 编号\n     */\n    ${primaryColumn.javaType} create${simpleClassName}(@Valid ${sceneEnum.prefixClass}${table.className}SaveReqVO createReqVO);\n\n    /**\n     * 更新${table.classComment}\n     *\n     * @param updateReqVO 更新信息\n     */\n    void update${simpleClassName}(@Valid ${sceneEnum.prefixClass}${table.className}SaveReqVO updateReqVO);\n\n    /**\n     * 删除${table.classComment}\n     *\n     * @param id 编号\n     */\n    void delete${simpleClassName}(${primaryColumn.javaType} id);\n\n    /**\n     * 获得${table.classComment}\n     *\n     * @param id 编号\n     * @return ${table.classComment}\n     */\n    ${table.className}DO get${simpleClassName}(${primaryColumn.javaType} id);\n\n## 特殊：树表专属逻辑（树不需要分页接口）\n#if ( $table.templateType != 2 )\n    /**\n     * 获得${table.classComment}分页\n     *\n     * @param pageReqVO 分页查询\n     * @return ${table.classComment}分页\n     */\n    PageResult<${table.className}DO> get${simpleClassName}Page(${sceneEnum.prefixClass}${table.className}PageReqVO pageReqVO);\n#else\n    /**\n     * 获得${table.classComment}列表\n     *\n     * @param listReqVO 查询条件\n     * @return ${table.classComment}列表\n     */\n    List<${table.className}DO> get${simpleClassName}List(${sceneEnum.prefixClass}${table.className}ListReqVO listReqVO);\n#end\n\n## 特殊：主子表专属逻辑\n#foreach ($subTable in $subTables)\n#set ($index = $foreach.count - 1)\n#set ($subSimpleClassName = $subSimpleClassNames.get($index))\n#set ($subPrimaryColumn = $subPrimaryColumns.get($index))##当前 primary 字段\n#set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段\n#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写\n#set ($subClassNameVar = $subClassNameVars.get($index))\n    // ==================== 子表（$subTable.classComment） ====================\n\n## 情况一：MASTER_ERP 时，需要分查询页子表\n#if ( $table.templateType == 11 )\n    /**\n     * 获得${subTable.classComment}分页\n     *\n     * @param pageReqVO 分页查询\n     * @param ${subJoinColumn.javaField} ${subJoinColumn.columnComment}\n     * @return ${subTable.classComment}分页\n     */\n    PageResult<${subTable.className}DO> get${subSimpleClassName}Page(PageParam pageReqVO, ${subJoinColumn.javaType} ${subJoinColumn.javaField});\n\n## 情况二：非 MASTER_ERP 时，需要列表查询子表\n#else\n    #if ( $subTable.subJoinMany )\n    /**\n     * 获得${subTable.classComment}列表\n     *\n     * @param ${subJoinColumn.javaField} ${subJoinColumn.columnComment}\n     * @return ${subTable.classComment}列表\n     */\n    List<${subTable.className}DO> get${subSimpleClassName}ListBy${SubJoinColumnName}(${subJoinColumn.javaType} ${subJoinColumn.javaField});\n\n    #else\n    /**\n     * 获得${subTable.classComment}\n     *\n     * @param ${subJoinColumn.javaField} ${subJoinColumn.columnComment}\n     * @return ${subTable.classComment}\n     */\n    ${subTable.className}DO get${subSimpleClassName}By${SubJoinColumnName}(${subJoinColumn.javaType} ${subJoinColumn.javaField});\n\n    #end\n#end\n## 特殊：MASTER_ERP 时，支持单个的新增、修改、删除操作\n#if ( $table.templateType == 11 )\n    /**\n     * 创建${subTable.classComment}\n     *\n     * @param ${subClassNameVar} 创建信息\n     * @return 编号\n     */\n    ${subPrimaryColumn.javaType} create${subSimpleClassName}(@Valid ${subTable.className}DO ${subClassNameVar});\n\n    /**\n     * 更新${subTable.classComment}\n     *\n     * @param ${subClassNameVar} 更新信息\n     */\n    void update${subSimpleClassName}(@Valid ${subTable.className}DO ${subClassNameVar});\n\n    /**\n     * 删除${subTable.classComment}\n     *\n     * @param id 编号\n     */\n    void delete${subSimpleClassName}(${subPrimaryColumn.javaType} id);\n\n\t/**\n\t * 获得${subTable.classComment}\n\t *\n\t * @param id 编号\n     * @return ${subTable.classComment}\n\t */\n    ${subTable.className}DO get${subSimpleClassName}(${subPrimaryColumn.javaType} id);\n\n#end\n#end\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/java/service/serviceImpl.vm",
    "content": "package ${basePackage}.module.${table.moduleName}.service.${table.businessName};\n\nimport org.springframework.stereotype.Service;\nimport ${jakartaPackage}.annotation.Resource;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport java.util.*;\nimport ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo.*;\nimport ${basePackage}.module.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;\n## 特殊：主子表专属逻辑\n#foreach ($subTable in $subTables)\nimport ${basePackage}.module.${subTable.moduleName}.dal.dataobject.${subTable.businessName}.${subTable.className}DO;\n#end\nimport ${PageResultClassName};\nimport ${PageParamClassName};\nimport ${BeanUtils};\n\nimport ${basePackage}.module.${table.moduleName}.dal.mysql.${table.businessName}.${table.className}Mapper;\n## 特殊：主子表专属逻辑\n#foreach ($subTable in $subTables)\n#set ($index = $foreach.count - 1)\nimport ${basePackage}.module.${subTable.moduleName}.dal.mysql.${subTable.businessName}.${subTable.className}Mapper;\n#end\n\nimport static ${ServiceExceptionUtilClassName}.exception;\nimport static ${basePackage}.module.${table.moduleName}.enums.ErrorCodeConstants.*;\n\n/**\n * ${table.classComment} Service 实现类\n *\n * @author ${table.author}\n */\n@Service\n@Validated\npublic class ${table.className}ServiceImpl implements ${table.className}Service {\n\n    @Resource\n    private ${table.className}Mapper ${classNameVar}Mapper;\n## 特殊：主子表专属逻辑\n#foreach ($subTable in $subTables)\n#set ($index = $foreach.count - 1)\n    @Resource\n    private ${subTable.className}Mapper ${subClassNameVars.get($index)}Mapper;\n#end\n\n    @Override\n## 特殊：主子表专属逻辑（非 ERP 模式）\n#if ( $subTables && $subTables.size() > 0 && $table.templateType != 11 )\n    @Transactional(rollbackFor = Exception.class)\n#end\n    public ${primaryColumn.javaType} create${simpleClassName}(${sceneEnum.prefixClass}${table.className}SaveReqVO createReqVO) {\n## 特殊：树表专属逻辑\n#if ( $table.templateType == 2 )\n#set ($TreeParentJavaField = $treeParentColumn.javaField.substring(0,1).toUpperCase() + ${treeParentColumn.javaField.substring(1)})##首字母大写\n#set ($TreeNameJavaField = $treeNameColumn.javaField.substring(0,1).toUpperCase() + ${treeNameColumn.javaField.substring(1)})##首字母大写\n        // 校验${treeParentColumn.columnComment}的有效性\n        validateParent${simpleClassName}(null, createReqVO.get${TreeParentJavaField}());\n        // 校验${treeNameColumn.columnComment}的唯一性\n        validate${simpleClassName}${TreeNameJavaField}Unique(null, createReqVO.get${TreeParentJavaField}(), createReqVO.get${TreeNameJavaField}());\n\n#end\n        // 插入\n        ${table.className}DO ${classNameVar} = BeanUtils.toBean(createReqVO, ${table.className}DO.class);\n        ${classNameVar}Mapper.insert(${classNameVar});\n## 特殊：主子表专属逻辑（非 ERP 模式）\n#if ( $subTables && $subTables.size() > 0 && $table.templateType != 11 )\n\n        // 插入子表\n#foreach ($subTable in $subTables)\n#set ($index = $foreach.count - 1)\n#set ($subSimpleClassName = $subSimpleClassNames.get($index))\n#set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段\n#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写\n    #if ( $subTable.subJoinMany)\n        create${subSimpleClassName}List(${classNameVar}.getId(), createReqVO.get${subSimpleClassNames.get($index)}s());\n    #else\n        create${subSimpleClassName}(${classNameVar}.getId(), createReqVO.get${subSimpleClassNames.get($index)}());\n    #end\n#end\n#end\n        // 返回\n        return ${classNameVar}.getId();\n    }\n\n    @Override\n## 特殊：主子表专属逻辑（非 ERP 模式）\n#if ( $subTables && $subTables.size() > 0 && $table.templateType != 11 )\n    @Transactional(rollbackFor = Exception.class)\n#end\n    public void update${simpleClassName}(${sceneEnum.prefixClass}${table.className}SaveReqVO updateReqVO) {\n        // 校验存在\n        validate${simpleClassName}Exists(updateReqVO.getId());\n## 特殊：树表专属逻辑\n#if ( $table.templateType == 2 )\n#set ($TreeParentJavaField = $treeParentColumn.javaField.substring(0,1).toUpperCase() + ${treeParentColumn.javaField.substring(1)})##首字母大写\n#set ($TreeNameJavaField = $treeNameColumn.javaField.substring(0,1).toUpperCase() + ${treeNameColumn.javaField.substring(1)})##首字母大写\n        // 校验${treeParentColumn.columnComment}的有效性\n        validateParent${simpleClassName}(updateReqVO.getId(), updateReqVO.get${TreeParentJavaField}());\n        // 校验${treeNameColumn.columnComment}的唯一性\n        validate${simpleClassName}${TreeNameJavaField}Unique(updateReqVO.getId(), updateReqVO.get${TreeParentJavaField}(), updateReqVO.get${TreeNameJavaField}());\n\n#end\n        // 更新\n        ${table.className}DO updateObj = BeanUtils.toBean(updateReqVO, ${table.className}DO.class);\n        ${classNameVar}Mapper.updateById(updateObj);\n## 特殊：主子表专属逻辑（非 ERP 模式）\n#if ( $subTables && $subTables.size() > 0 && $table.templateType != 11)\n\n        // 更新子表\n#foreach ($subTable in $subTables)\n#set ($index = $foreach.count - 1)\n#set ($subSimpleClassName = $subSimpleClassNames.get($index))\n#set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段\n#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写\n    #if ( $subTable.subJoinMany)\n        update${subSimpleClassName}List(updateReqVO.getId(), updateReqVO.get${subSimpleClassNames.get($index)}s());\n    #else\n        update${subSimpleClassName}(updateReqVO.getId(), updateReqVO.get${subSimpleClassNames.get($index)}());\n    #end\n#end\n#end\n    }\n\n    @Override\n## 特殊：主子表专属逻辑\n#if ( $subTables && $subTables.size() > 0)\n    @Transactional(rollbackFor = Exception.class)\n#end\n    public void delete${simpleClassName}(${primaryColumn.javaType} id) {\n        // 校验存在\n        validate${simpleClassName}Exists(id);\n## 特殊：树表专属逻辑\n#if ( $table.templateType == 2 )\n#set ($ParentJavaField = $treeParentColumn.javaField.substring(0,1).toUpperCase() + ${treeParentColumn.javaField.substring(1)})##首字母大写\n        // 校验是否有子${table.classComment}\n        if (${classNameVar}Mapper.selectCountBy${ParentJavaField}(id) > 0) {\n            throw exception(${simpleClassName_underlineCase.toUpperCase()}_EXITS_CHILDREN);\n        }\n#end\n        // 删除\n        ${classNameVar}Mapper.deleteById(id);\n## 特殊：主子表专属逻辑\n#if ( $subTables && $subTables.size() > 0)\n\n        // 删除子表\n#foreach ($subTable in $subTables)\n#set ($index = $foreach.count - 1)\n#set ($subSimpleClassName = $subSimpleClassNames.get($index))\n#set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段\n#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写\n        delete${subSimpleClassName}By${SubJoinColumnName}(id);\n#end\n#end\n    }\n\n    private void validate${simpleClassName}Exists(${primaryColumn.javaType} id) {\n        if (${classNameVar}Mapper.selectById(id) == null) {\n            throw exception(${simpleClassName_underlineCase.toUpperCase()}_NOT_EXISTS);\n        }\n    }\n\n## 特殊：树表专属逻辑\n#if ( $table.templateType == 2 )\n#set ($TreeParentJavaField = $treeParentColumn.javaField.substring(0,1).toUpperCase() + ${treeParentColumn.javaField.substring(1)})##首字母大写\n#set ($TreeNameJavaField = $treeNameColumn.javaField.substring(0,1).toUpperCase() + ${treeNameColumn.javaField.substring(1)})##首字母大写\n    private void validateParent${simpleClassName}(Long id, Long ${treeParentColumn.javaField}) {\n        if (${treeParentColumn.javaField} == null || ${simpleClassName}DO.${treeParentColumn_javaField_underlineCase.toUpperCase()}_ROOT.equals(${treeParentColumn.javaField})) {\n            return;\n        }\n        // 1. 不能设置自己为父${table.classComment}\n        if (Objects.equals(id, ${treeParentColumn.javaField})) {\n            throw exception(${simpleClassName_underlineCase.toUpperCase()}_PARENT_ERROR);\n        }\n        // 2. 父${table.classComment}不存在\n        ${simpleClassName}DO parent${simpleClassName} = ${classNameVar}Mapper.selectById(${treeParentColumn.javaField});\n        if (parent${simpleClassName} == null) {\n            throw exception(${simpleClassName_underlineCase.toUpperCase()}_PARENT_NOT_EXITS);\n        }\n        // 3. 递归校验父${table.classComment}，如果父${table.classComment}是自己的子${table.classComment}，则报错，避免形成环路\n        if (id == null) { // id 为空，说明新增，不需要考虑环路\n            return;\n        }\n        for (int i = 0; i < Short.MAX_VALUE; i++) {\n            // 3.1 校验环路\n            ${treeParentColumn.javaField} = parent${simpleClassName}.get${TreeParentJavaField}();\n            if (Objects.equals(id, ${treeParentColumn.javaField})) {\n                throw exception(${simpleClassName_underlineCase.toUpperCase()}_PARENT_IS_CHILD);\n            }\n            // 3.2 继续递归下一级父${table.classComment}\n            if (${treeParentColumn.javaField} == null || ${simpleClassName}DO.${treeParentColumn_javaField_underlineCase.toUpperCase()}_ROOT.equals(${treeParentColumn.javaField})) {\n                break;\n            }\n            parent${simpleClassName} = ${classNameVar}Mapper.selectById(${treeParentColumn.javaField});\n            if (parent${simpleClassName} == null) {\n                break;\n            }\n        }\n    }\n\n    private void validate${simpleClassName}${TreeNameJavaField}Unique(Long id, Long ${treeParentColumn.javaField}, String ${treeNameColumn.javaField}) {\n        ${simpleClassName}DO ${classNameVar} = ${classNameVar}Mapper.selectBy${TreeParentJavaField}And${TreeNameJavaField}(${treeParentColumn.javaField}, ${treeNameColumn.javaField});\n        if (${classNameVar} == null) {\n            return;\n        }\n        // 如果 id 为空，说明不用比较是否为相同 id 的${table.classComment}\n        if (id == null) {\n            throw exception(${simpleClassName_underlineCase.toUpperCase()}_${treeNameColumn_javaField_underlineCase.toUpperCase()}_DUPLICATE);\n        }\n        if (!Objects.equals(${classNameVar}.getId(), id)) {\n            throw exception(${simpleClassName_underlineCase.toUpperCase()}_${treeNameColumn_javaField_underlineCase.toUpperCase()}_DUPLICATE);\n        }\n    }\n\n#end\n    @Override\n    public ${table.className}DO get${simpleClassName}(${primaryColumn.javaType} id) {\n        return ${classNameVar}Mapper.selectById(id);\n    }\n\n## 特殊：树表专属逻辑（树不需要分页接口）\n#if ( $table.templateType != 2 )\n    @Override\n    public PageResult<${table.className}DO> get${simpleClassName}Page(${sceneEnum.prefixClass}${table.className}PageReqVO pageReqVO) {\n        return ${classNameVar}Mapper.selectPage(pageReqVO);\n    }\n#else\n    @Override\n    public List<${table.className}DO> get${simpleClassName}List(${sceneEnum.prefixClass}${table.className}ListReqVO listReqVO) {\n        return ${classNameVar}Mapper.selectList(listReqVO);\n    }\n#end\n\n## 特殊：主子表专属逻辑\n#foreach ($subTable in $subTables)\n#set ($index = $foreach.count - 1)\n#set ($subSimpleClassName = $subSimpleClassNames.get($index))\n#set ($simpleClassNameUnderlineCase = $simpleClassNameUnderlineCases.get($index))\n#set ($subPrimaryColumn = $subPrimaryColumns.get($index))##当前 primary 字段\n#set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段\n#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写\n#set ($subClassNameVar = $subClassNameVars.get($index))\n    // ==================== 子表（$subTable.classComment） ====================\n\n## 情况一：MASTER_ERP 时，需要分查询页子表\n#if ( $table.templateType == 11 )\n    @Override\n    public PageResult<${subTable.className}DO> get${subSimpleClassName}Page(PageParam pageReqVO, ${subJoinColumn.javaType} ${subJoinColumn.javaField}) {\n        return ${subClassNameVars.get($index)}Mapper.selectPage(pageReqVO, ${subJoinColumn.javaField});\n    }\n\n## 情况二：非 MASTER_ERP 时，需要列表查询子表\n#else\n    #if ( $subTable.subJoinMany )\n    @Override\n    public List<${subTable.className}DO> get${subSimpleClassName}ListBy${SubJoinColumnName}(${subJoinColumn.javaType} ${subJoinColumn.javaField}) {\n        return ${subClassNameVars.get($index)}Mapper.selectListBy${SubJoinColumnName}(${subJoinColumn.javaField});\n    }\n\n    #else\n    @Override\n    public ${subTable.className}DO get${subSimpleClassName}By${SubJoinColumnName}(${subJoinColumn.javaType} ${subJoinColumn.javaField}) {\n        return ${subClassNameVars.get($index)}Mapper.selectBy${SubJoinColumnName}(${subJoinColumn.javaField});\n    }\n\n    #end\n#end\n## 情况一：MASTER_ERP 时，支持单个的新增、修改、删除操作\n#if ( $table.templateType == 11 )\n    @Override\n    public ${subPrimaryColumn.javaType} create${subSimpleClassName}(${subTable.className}DO ${subClassNameVar}) {\n## 特殊：一对一时，需要保证只有一条，不能重复插入\n#if ( !$subTable.subJoinMany)\n        // 校验是否已经存在\n        if (${subClassNameVars.get($index)}Mapper.selectBy${SubJoinColumnName}(${subClassNameVar}.get${SubJoinColumnName}()) != null) {\n            throw exception(${simpleClassNameUnderlineCase.toUpperCase()}_EXISTS);\n        }\n        // 插入\n#end\n        ${subClassNameVars.get($index)}Mapper.insert(${subClassNameVar});\n        return ${subClassNameVar}.getId();\n    }\n\n    @Override\n    public void update${subSimpleClassName}(${subTable.className}DO ${subClassNameVar}) {\n        // 校验存在\n        validate${subSimpleClassName}Exists(${subClassNameVar}.getId());\n        // 更新\n        ${subClassNameVars.get($index)}Mapper.updateById(${subClassNameVar});\n    }\n\n    @Override\n    public void delete${subSimpleClassName}(${subPrimaryColumn.javaType} id) {\n        // 校验存在\n        validate${subSimpleClassName}Exists(id);\n        // 删除\n        ${subClassNameVars.get($index)}Mapper.deleteById(id);\n    }\n\n    @Override\n    public ${subTable.className}DO get${subSimpleClassName}(${subPrimaryColumn.javaType} id) {\n        return ${subClassNameVars.get($index)}Mapper.selectById(id);\n    }\n\n    private void validate${subSimpleClassName}Exists(${subPrimaryColumn.javaType} id) {\n        if (${subClassNameVar}Mapper.selectById(id) == null) {\n            throw exception(${simpleClassNameUnderlineCase.toUpperCase()}_NOT_EXISTS);\n        }\n    }\n\n## 情况二：非 MASTER_ERP 时，支持批量的新增、修改操作\n#else\n    #if ( $subTable.subJoinMany)\n    private void create${subSimpleClassName}List(${primaryColumn.javaType} ${subJoinColumn.javaField}, List<${subTable.className}DO> list) {\n        list.forEach(o -> o.set$SubJoinColumnName(${subJoinColumn.javaField}));\n        ${subClassNameVars.get($index)}Mapper.insertBatch(list);\n    }\n\n    private void update${subSimpleClassName}List(${primaryColumn.javaType} ${subJoinColumn.javaField}, List<${subTable.className}DO> list) {\n        delete${subSimpleClassName}By${SubJoinColumnName}(${subJoinColumn.javaField});\n\t\tlist.forEach(o -> o.setId(null).setUpdater(null).setUpdateTime(null)); // 解决更新情况下：1）id 冲突；2）updateTime 不更新\n        create${subSimpleClassName}List(${subJoinColumn.javaField}, list);\n    }\n\n    #else\n    private void create${subSimpleClassName}(${primaryColumn.javaType} ${subJoinColumn.javaField}, ${subTable.className}DO ${subClassNameVar}) {\n        if (${subClassNameVar} == null) {\n            return;\n        }\n        ${subClassNameVar}.set$SubJoinColumnName(${subJoinColumn.javaField});\n        ${subClassNameVars.get($index)}Mapper.insert(${subClassNameVar});\n    }\n\n    private void update${subSimpleClassName}(${primaryColumn.javaType} ${subJoinColumn.javaField}, ${subTable.className}DO ${subClassNameVar}) {\n        if (${subClassNameVar} == null) {\n\t\t\treturn;\n        }\n        ${subClassNameVar}.set$SubJoinColumnName(${subJoinColumn.javaField});\n        ${subClassNameVar}.setUpdater(null).setUpdateTime(null); // 解决更新情况下：updateTime 不更新\n        ${subClassNameVars.get($index)}Mapper.insertOrUpdate(${subClassNameVar});\n    }\n\n    #end\n#end\n    private void delete${subSimpleClassName}By${SubJoinColumnName}(${primaryColumn.javaType} ${subJoinColumn.javaField}) {\n        ${subClassNameVars.get($index)}Mapper.deleteBy${SubJoinColumnName}(${subJoinColumn.javaField});\n    }\n\n#end\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/java/test/serviceTest.vm",
    "content": "package ${basePackage}.module.${table.moduleName}.service.${table.businessName};\n\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.mock.mockito.MockBean;\n\nimport ${jakartaPackage}.annotation.Resource;\n\nimport ${baseFrameworkPackage}.test.core.ut.BaseDbUnitTest;\n\nimport ${basePackage}.module.${table.moduleName}.controller.${sceneEnum.basePackage}.${table.businessName}.vo.*;\nimport ${basePackage}.module.${table.moduleName}.dal.dataobject.${table.businessName}.${table.className}DO;\nimport ${basePackage}.module.${table.moduleName}.dal.mysql.${table.businessName}.${table.className}Mapper;\nimport ${PageResultClassName};\n\nimport ${jakartaPackage}.annotation.Resource;\nimport org.springframework.context.annotation.Import;\nimport java.util.*;\nimport java.time.LocalDateTime;\n\nimport static cn.hutool.core.util.RandomUtil.*;\nimport static ${basePackage}.module.${table.moduleName}.enums.ErrorCodeConstants.*;\nimport static ${baseFrameworkPackage}.test.core.util.AssertUtils.*;\nimport static ${baseFrameworkPackage}.test.core.util.RandomUtils.*;\nimport static ${LocalDateTimeUtilsClassName}.*;\nimport static ${ObjectUtilsClassName}.*;\nimport static ${DateUtilsClassName}.*;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.*;\n\n## 字段模板\n#macro(getPageCondition $VO)\n       // mock 数据\n       ${table.className}DO db${simpleClassName} = randomPojo(${table.className}DO.class, o -> { // 等会查询到\n       #foreach ($column in $columns)\n       #if (${column.listOperation})\n       #set ($JavaField = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})##首字母大写\n           o.set$JavaField(null);\n       #end\n       #end\n       });\n       ${classNameVar}Mapper.insert(db${simpleClassName});\n       #foreach ($column in $columns)\n       #if (${column.listOperation})\n       #set ($JavaField = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})##首字母大写\n       // 测试 ${column.javaField} 不匹配\n       ${classNameVar}Mapper.insert(cloneIgnoreId(db${simpleClassName}, o -> o.set$JavaField(null)));\n       #end\n       #end\n       // 准备参数\n       ${sceneEnum.prefixClass}${table.className}${VO} reqVO = new ${sceneEnum.prefixClass}${table.className}${VO}();\n       #foreach ($column in $columns)\n       #if (${column.listOperation})\n       #set ($JavaField = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})##首字母大写\n       #if (${column.listOperationCondition} == \"BETWEEN\")## BETWEEN 的情况\n       reqVO.set${JavaField}(buildBetweenTime(2023, 2, 1, 2023, 2, 28));\n       #else\n       reqVO.set$JavaField(null);\n       #end\n       #end\n       #end\n#end\n/**\n * {@link ${table.className}ServiceImpl} 的单元测试类\n *\n * @author ${table.author}\n */\n@Import(${table.className}ServiceImpl.class)\npublic class ${table.className}ServiceImplTest extends BaseDbUnitTest {\n\n    @Resource\n    private ${table.className}ServiceImpl ${classNameVar}Service;\n\n    @Resource\n    private ${table.className}Mapper ${classNameVar}Mapper;\n\n    @Test\n    public void testCreate${simpleClassName}_success() {\n        // 准备参数\n        ${sceneEnum.prefixClass}${table.className}SaveReqVO createReqVO = randomPojo(${sceneEnum.prefixClass}${table.className}SaveReqVO.class).setId(null);\n\n        // 调用\n        ${primaryColumn.javaType} ${classNameVar}Id = ${classNameVar}Service.create${simpleClassName}(createReqVO);\n        // 断言\n        assertNotNull(${classNameVar}Id);\n        // 校验记录的属性是否正确\n        ${table.className}DO ${classNameVar} = ${classNameVar}Mapper.selectById(${classNameVar}Id);\n        assertPojoEquals(createReqVO, ${classNameVar}, \"id\");\n    }\n\n    @Test\n    public void testUpdate${simpleClassName}_success() {\n        // mock 数据\n        ${table.className}DO db${simpleClassName} = randomPojo(${table.className}DO.class);\n        ${classNameVar}Mapper.insert(db${simpleClassName});// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        ${sceneEnum.prefixClass}${table.className}SaveReqVO updateReqVO = randomPojo(${sceneEnum.prefixClass}${table.className}SaveReqVO.class, o -> {\n            o.setId(db${simpleClassName}.getId()); // 设置更新的 ID\n        });\n\n        // 调用\n        ${classNameVar}Service.update${simpleClassName}(updateReqVO);\n        // 校验是否更新正确\n        ${table.className}DO ${classNameVar} = ${classNameVar}Mapper.selectById(updateReqVO.getId()); // 获取最新的\n        assertPojoEquals(updateReqVO, ${classNameVar});\n    }\n\n    @Test\n    public void testUpdate${simpleClassName}_notExists() {\n        // 准备参数\n        ${sceneEnum.prefixClass}${table.className}SaveReqVO updateReqVO = randomPojo(${sceneEnum.prefixClass}${table.className}SaveReqVO.class);\n\n        // 调用, 并断言异常\n        assertServiceException(() -> ${classNameVar}Service.update${simpleClassName}(updateReqVO), ${simpleClassName_underlineCase.toUpperCase()}_NOT_EXISTS);\n    }\n\n    @Test\n    public void testDelete${simpleClassName}_success() {\n        // mock 数据\n        ${table.className}DO db${simpleClassName} = randomPojo(${table.className}DO.class);\n        ${classNameVar}Mapper.insert(db${simpleClassName});// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        ${primaryColumn.javaType} id = db${simpleClassName}.getId();\n\n        // 调用\n        ${classNameVar}Service.delete${simpleClassName}(id);\n       // 校验数据不存在了\n       assertNull(${classNameVar}Mapper.selectById(id));\n    }\n\n    @Test\n    public void testDelete${simpleClassName}_notExists() {\n        // 准备参数\n        ${primaryColumn.javaType} id = random${primaryColumn.javaType}Id();\n\n        // 调用, 并断言异常\n        assertServiceException(() -> ${classNameVar}Service.delete${simpleClassName}(id), ${simpleClassName_underlineCase.toUpperCase()}_NOT_EXISTS);\n    }\n\n## 特殊：树表专属逻辑（树不需要分页接口）\n#if ( $table.templateType != 2 )\n    @Test\n    @Disabled  // TODO 请修改 null 为需要的值，然后删除 @Disabled 注解\n    public void testGet${simpleClassName}Page() {\n       #getPageCondition(\"PageReqVO\")\n\n       // 调用\n       PageResult<${table.className}DO> pageResult = ${classNameVar}Service.get${simpleClassName}Page(reqVO);\n       // 断言\n       assertEquals(1, pageResult.getTotal());\n       assertEquals(1, pageResult.getList().size());\n       assertPojoEquals(db${simpleClassName}, pageResult.getList().get(0));\n    }\n#else\n    @Test\n    @Disabled  // TODO 请修改 null 为需要的值，然后删除 @Disabled 注解\n    public void testGet${simpleClassName}List() {\n       #getPageCondition(\"ListReqVO\")\n\n       // 调用\n       List<${table.className}DO> list = ${classNameVar}Service.get${simpleClassName}List(reqVO);\n       // 断言\n       assertEquals(1, list.size());\n       assertPojoEquals(db${simpleClassName}, list.get(0));\n    }\n#end\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/sql/h2.vm",
    "content": "-- 将该建表 SQL 语句，添加到 yshop-module-${table.moduleName}-biz 模块的 test/resources/sql/create_tables.sql 文件里\nCREATE TABLE IF NOT EXISTS \"${table.tableName.toLowerCase()}\" (\n#foreach ($column in $columns)\n#if (${column.javaType} == 'Long')\n    #set ($dataType='bigint')\n#elseif (${column.javaType} == 'Integer')\n    #set ($dataType='int')\n#elseif (${column.javaType} == 'Boolean')\n    #set ($dataType='bit')\n#elseif (${column.javaType} == 'Date')\n    #set ($dataType='datetime')\n#else\n    #set ($dataType='varchar')\n#end\n    #if (${column.primaryKey})##处理主键\n    \"${column.javaField}\"#if (${column.javaType} == 'String') ${dataType} NOT NULL#else ${dataType} NOT NULL GENERATED BY DEFAULT AS IDENTITY#end,\n    #else\n    #if (${column.columnName} == 'create_time')\n    \"create_time\" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    #elseif (${column.columnName} == 'update_time')\n    \"update_time\" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n    #elseif (${column.columnName} == 'creator' || ${column.columnName} == 'updater')\n    \"${column.columnName}\" ${dataType} DEFAULT '',\n    #elseif (${column.columnName} == 'deleted')\n    \"deleted\" bit NOT NULL DEFAULT FALSE,\n    #elseif (${column.columnName} == 'tenantId')\n    \"tenant_id\" bigint NOT NULL DEFAULT 0,\n    #else\n    \"${column.columnName.toLowerCase()}\" ${dataType}#if (${column.nullable} == false) NOT NULL#end,\n    #end\n    #end\n#end\n    PRIMARY KEY (\"${primaryColumn.columnName.toLowerCase()}\")\n) COMMENT '${table.tableComment}';\n\n-- 将该删表 SQL 语句，添加到 yshop-module-${table.moduleName}-biz 模块的 test/resources/sql/clean.sql 文件里\nDELETE FROM \"${table.tableName}\";"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/sql/sql.vm",
    "content": "-- 菜单 SQL\nINSERT INTO system_menu(\n    name, permission, type, sort, parent_id,\n    path, icon, component, status, component_name\n)\nVALUES (\n    '${table.classComment}管理', '', 2, 0, ${table.parentMenuId},\n    '${simpleClassName_strikeCase}', '', '${table.moduleName}/${table.businessName}/index', 0, '${table.className}'\n);\n\n-- 按钮父菜单ID\n-- 暂时只支持 MySQL。如果你是 Oracle、PostgreSQL、SQLServer 的话，需要手动修改 @parentId 的部分的代码\nSELECT @parentId := LAST_INSERT_ID();\n\n-- 按钮 SQL\n#set ($functionNames = ['查询', '创建', '更新', '删除', '导出'])\n#set ($functionOps = ['query', 'create', 'update', 'delete', 'export'])\n#foreach ($functionName in $functionNames)\n#set ($index = $foreach.count - 1)\nINSERT INTO system_menu(\n    name, permission, type, sort, parent_id,\n    path, icon, component, status\n)\nVALUES (\n    '${table.classComment}${functionName}', '${permissionPrefix}:${functionOps.get($index)}', 3, $foreach.count, @parentId,\n    '', '', '', 0\n);\n#end"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/vue/api/api.js.vm",
    "content": "import request from '@/utils/request'\n#set ($baseURL = \"/${table.moduleName}/${simpleClassName_strikeCase}\")\n\n// 创建${table.classComment}\nexport function create${simpleClassName}(data) {\n  return request({\n    url: '${baseURL}/create',\n    method: 'post',\n    data: data\n  })\n}\n\n// 更新${table.classComment}\nexport function update${simpleClassName}(data) {\n  return request({\n    url: '${baseURL}/update',\n    method: 'put',\n    data: data\n  })\n}\n\n// 删除${table.classComment}\nexport function delete${simpleClassName}(id) {\n  return request({\n    url: '${baseURL}/delete?id=' + id,\n    method: 'delete'\n  })\n}\n\n// 获得${table.classComment}\nexport function get${simpleClassName}(id) {\n  return request({\n    url: '${baseURL}/get?id=' + id,\n    method: 'get'\n  })\n}\n\n#if ( $table.templateType != 2 )\n// 获得${table.classComment}分页\nexport function get${simpleClassName}Page(params) {\n  return request({\n    url: '${baseURL}/page',\n    method: 'get',\n    params\n  })\n}\n#else\n// 获得${table.classComment}列表\nexport function get${simpleClassName}List(params) {\n  return request({\n    url: '${baseURL}/list',\n    method: 'get',\n    params\n  })\n}\n#end\n// 导出${table.classComment} Excel\nexport function export${simpleClassName}Excel(params) {\n  return request({\n    url: '${baseURL}/export-excel',\n    method: 'get',\n    params,\n    responseType: 'blob'\n  })\n}\n## 特殊：主子表专属逻辑\n#foreach ($subTable in $subTables)\n  #set ($index = $foreach.count - 1)\n  #set ($subSimpleClassName = $subSimpleClassNames.get($index))\n  #set ($subPrimaryColumn = $subPrimaryColumns.get($index))##当前 primary 字段\n  #set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段\n  #set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写\n  #set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($index))\n  #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))\n  #set ($subClassNameVar = $subClassNameVars.get($index))\n\n// ==================== 子表（$subTable.classComment） ====================\n  ## 情况一：MASTER_ERP 时，需要分查询页子表\n  #if ($table.templateType == 11)\n  // 获得${subTable.classComment}分页\n  export function get${subSimpleClassName}Page(params) {\n    return request({\n      url: '${baseURL}/${subSimpleClassName_strikeCase}/page',\n      method: 'get',\n      params\n    })\n  }\n    ## 情况二：非 MASTER_ERP 时，需要列表查询子表\n  #else\n    #if ($subTable.subJoinMany)\n    // 获得${subTable.classComment}列表\n    export function get${subSimpleClassName}ListBy${SubJoinColumnName}(${subJoinColumn.javaField}) {\n      return request({\n        url: '${baseURL}/${subSimpleClassName_strikeCase}/list-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=' + ${subJoinColumn.javaField},\n        method: 'get'\n      })\n    }\n    #else\n    // 获得${subTable.classComment}\n    export function get${subSimpleClassName}By${SubJoinColumnName}(${subJoinColumn.javaField}) {\n      return request({\n        url: '${baseURL}/${subSimpleClassName_strikeCase}/get-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=' + ${subJoinColumn.javaField},\n        method: 'get'\n      })\n    }\n    #end\n  #end\n  ## 特殊：MASTER_ERP 时，支持单个的新增、修改、删除操作\n  #if ($table.templateType == 11)\n  // 新增${subTable.classComment}\n  export function create${subSimpleClassName}(data) {\n    return request({\n      url: '${baseURL}/${subSimpleClassName_strikeCase}/create',\n      method: 'post',\n      data\n    })\n  }\n  // 修改${subTable.classComment}\n  export function update${subSimpleClassName}(data) {\n    return request({\n      url: '${baseURL}/${subSimpleClassName_strikeCase}/update',\n      method: 'post',\n      data\n    })\n  }\n  // 删除${subTable.classComment}\n  export function delete${subSimpleClassName}(id) {\n    return request({\n      url: '${baseURL}/${subSimpleClassName_strikeCase}/delete?id=' + id,\n      method: 'delete'\n    })\n  }\n  // 获得${subTable.classComment}\n  export function get${subSimpleClassName}(id) {\n    return request({\n      url: '${baseURL}/${subSimpleClassName_strikeCase}/get?id=' + id,\n      method: 'get'\n    })\n  }\n  #end\n#end"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_erp.vue.vm",
    "content": "#set ($subTable = $subTables.get($subIndex))##当前表\n#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组\n#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex))\n#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段\n<template>\n  <div class=\"app-container\">\n    <!-- 对话框(添加 / 修改) -->\n    <el-dialog :title=\"dialogTitle\" :visible.sync=\"dialogVisible\" width=\"45%\" v-dialogDrag append-to-body>\n      <el-form ref=\"formRef\" :model=\"formData\" :rules=\"formRules\" v-loading=\"formLoading\" label-width=\"100px\">\n          #foreach($column in $subColumns)\n              #if ($column.createOperation || $column.updateOperation)\n                  #set ($dictType = $column.dictType)\n                  #set ($javaField = $column.javaField)\n                  #set ($javaType = $column.javaType)\n                  #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})\n                  #set ($comment = $column.columnComment)\n                  #if ( $column.id == $subJoinColumn.id) ## 特殊：忽略主子表的 join 字段，不用填写\n                  #elseif ($column.htmlType == \"input\" && !$column.primaryKey)## 忽略主键，不用在表单里\n                    <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n                      <el-input v-model=\"formData.${javaField}\" placeholder=\"请输入${comment}\" />\n                    </el-form-item>\n                  #elseif($column.htmlType == \"imageUpload\")## 图片上传\n                      #set ($hasImageUploadColumn = true)\n                    <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n                      <ImageUpload v-model=\"formData.${javaField}\"/>\n                    </el-form-item>\n                  #elseif($column.htmlType == \"fileUpload\")## 文件上传\n                      #set ($hasFileUploadColumn = true)\n                    <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n                      <FileUpload v-model=\"formData.${javaField}\"/>\n                    </el-form-item>\n                  #elseif($column.htmlType == \"editor\")## 文本编辑器\n                      #set ($hasEditorColumn = true)\n                    <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n                      <editor v-model=\"formData.${javaField}\" :min-height=\"192\"/>\n                    </el-form-item>\n                  #elseif($column.htmlType == \"select\")## 下拉框\n                    <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n                      <el-select v-model=\"formData.${javaField}\" placeholder=\"请选择${comment}\">\n                          #if (\"\" != $dictType)## 有数据字典\n                            <el-option v-for=\"dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())\"\n                                       :key=\"dict.value\" :label=\"dict.label\" #if ($column.javaType == \"Integer\" || $column.javaType == \"Long\"):value=\"parseInt(dict.value)\"#else:value=\"dict.value\"#end />\n                          #else##没数据字典\n                            <el-option label=\"请选择字典生成\" value=\"\" />\n                          #end\n                      </el-select>\n                    </el-form-item>\n                  #elseif($column.htmlType == \"checkbox\")## 多选框\n                    <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n                      <el-checkbox-group v-model=\"formData.${javaField}\">\n                          #if (\"\" != $dictType)## 有数据字典\n                            <el-checkbox v-for=\"dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())\"\n                                         :key=\"dict.value\" #if($column.javaType == \"Integer\" || $column.javaType == \"Long\"):label=\"parseInt(dict.value)\"#else:label=\"dict.value\"#end>{{dict.label}}</el-checkbox>\n                          #else##没数据字典\n                            <el-checkbox>请选择字典生成</el-checkbox>\n                          #end\n                      </el-checkbox-group>\n                    </el-form-item>\n                  #elseif($column.htmlType == \"radio\")## 单选框\n                    <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n                      <el-radio-group v-model=\"formData.${javaField}\">\n                          #if (\"\" != $dictType)## 有数据字典\n                            <el-radio v-for=\"dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())\"\n                                      :key=\"dict.value\" #if($column.javaType == \"Integer\" || $column.javaType == \"Long\"):label=\"parseInt(dict.value)\"\n                                      #else:label=\"dict.value\"#end>{{dict.label}}</el-radio>\n                          #else##没数据字典\n                            <el-radio label=\"1\">请选择字典生成</el-radio>\n                          #end\n                      </el-radio-group>\n                    </el-form-item>\n                  #elseif($column.htmlType == \"datetime\")## 时间框\n                    <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n                      <el-date-picker clearable v-model=\"formData.${javaField}\" type=\"date\" value-format=\"timestamp\" placeholder=\"选择${comment}\" />\n                    </el-form-item>\n                  #elseif($column.htmlType == \"textarea\")## 文本框\n                    <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n                      <el-input v-model=\"formData.${javaField}\" type=\"textarea\" placeholder=\"请输入内容\" />\n                    </el-form-item>\n                  #end\n              #end\n          #end\n      </el-form>\n      <div slot=\"footer\" class=\"dialog-footer\">\n        <el-button type=\"primary\" @click=\"submitForm\" :disabled=\"formLoading\">确 定</el-button>\n        <el-button @click=\"dialogVisible = false\">取 消</el-button>\n      </div>\n    </el-dialog>\n  </div>\n</template>\n\n<script>\n  import * as ${simpleClassName}Api from '@/api/${table.moduleName}/${table.businessName}';\n      #if ($hasImageUploadColumn)\n      import ImageUpload from '@/components/ImageUpload';\n      #end\n      #if ($hasFileUploadColumn)\n      import FileUpload from '@/components/FileUpload';\n      #end\n      #if ($hasEditorColumn)\n      import Editor from '@/components/Editor';\n      #end\n  export default {\n    name: \"${subSimpleClassName}Form\",\n    components: {\n        #if ($hasImageUploadColumn)\n          ImageUpload,\n        #end\n        #if ($hasFileUploadColumn)\n          FileUpload,\n        #end\n        #if ($hasEditorColumn)\n          Editor,\n        #end\n    },\n    data() {\n      return {\n        // 弹出层标题\n        dialogTitle: \"\",\n        // 是否显示弹出层\n        dialogVisible: false,\n        // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\n        formLoading: false,\n        // 表单参数\n        formData: {\n            #foreach ($column in $subColumns)\n                #if ($column.createOperation || $column.updateOperation)\n                    #if ($column.htmlType == \"checkbox\")\n                            $column.javaField: [],\n                    #else\n                            $column.javaField: undefined,\n                    #end\n                #end\n            #end\n        },\n        // 表单校验\n        formRules: {\n            #foreach ($column in $subColumns)\n                #if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键\n                    #set($comment=$column.columnComment)\n                        $column.javaField: [{ required: true, message: \"${comment}不能为空\", trigger: #if($column.htmlType == \"select\")\"change\"#else\"blur\"#end }],\n                #end\n            #end\n        },\n      };\n    },\n    methods: {\n      /** 打开弹窗 */\n      async open(id, ${subJoinColumn.javaField}) {\n        this.dialogVisible = true;\n        this.reset();\n        this.formData.${subJoinColumn.javaField} = ${subJoinColumn.javaField};\n        // 修改时，设置数据\n        if (id) {\n          this.formLoading = true;\n          try {\n            const res = await ${simpleClassName}Api.get${subSimpleClassName}(id);\n            this.formData = res.data;\n            this.dialogTitle = \"修改${subTable.classComment}\";\n          } finally {\n            this.formLoading = false;\n          }\n        }\n        this.dialogTitle = \"新增${subTable.classComment}\";\n      },\n      /** 提交按钮 */\n      async submitForm() {\n        await this.#[[$]]#refs[\"formRef\"].validate();\n        this.formLoading = true;\n        try {\n            const data = this.formData;\n            // 修改的提交\n            if (data.${primaryColumn.javaField}) {\n            await  ${simpleClassName}Api.update${subSimpleClassName}(data);\n            this.#[[$modal]]#.msgSuccess(\"修改成功\");\n            this.dialogVisible = false;\n            this.#[[$]]#emit('success');\n              return;\n            }\n            // 添加的提交\n              await ${simpleClassName}Api.create${subSimpleClassName}(data);\n              this.#[[$modal]]#.msgSuccess(\"新增成功\");\n              this.dialogVisible = false;\n              this.#[[$]]#emit('success');\n        }finally {\n          this.formLoading = false;\n        }\n      },\n      /** 表单重置 */\n      reset() {\n        this.formData = {\n            #foreach ($column in $subColumns)\n                #if ($column.createOperation || $column.updateOperation)\n                    #if ($column.htmlType == \"checkbox\")\n                            $column.javaField: [],\n                    #else\n                            $column.javaField: undefined,\n                    #end\n                #end\n            #end\n        };\n        this.resetForm(\"formRef\");\n      },\n    }\n  };\n</script>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_inner.vue.vm",
    "content": "## 主表的 normal 和 inner 使用相同的 form 表单\n#parse(\"codegen/vue/views/components/form_sub_normal.vue.vm\")"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/vue/views/components/form_sub_normal.vue.vm",
    "content": "#set ($subTable = $subTables.get($subIndex))##当前表\n#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组\n#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段\n#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex))\n#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段\n#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写\n<template>\n  <div class=\"app-container\">\n    #if ( $subTable.subJoinMany )## 情况一：一对多，table + form\n      <el-form\n        ref=\"formRef\"\n        :model=\"formData\"\n        :rules=\"formRules\"\n        v-loading=\"formLoading\"\n        label-width=\"0px\"\n        :inline-message=\"true\"\n      >\n        <el-table :data=\"formData\" class=\"-mt-10px\">\n          <el-table-column label=\"序号\" type=\"index\" width=\"100\" />\n            #foreach($column in $subColumns)\n                #if ($column.createOperation || $column.updateOperation)\n                    #set ($dictType = $column.dictType)\n                    #set ($javaField = $column.javaField)\n                    #set ($javaType = $column.javaType)\n                    #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})\n                    #set ($comment = $column.columnComment)\n                    #if ( $column.id == $subJoinColumn.id) ## 特殊：忽略主子表的 join 字段，不用填写\n                    #elseif ($column.htmlType == \"input\" && !$column.primaryKey)## 忽略主键，不用在表单里\n                      <el-table-column label=\"${comment}\" min-width=\"150\">\n                        <template v-slot=\"{ row, $index }\">\n                          <el-form-item :prop=\"`${$index}.${javaField}`\" :rules=\"formRules.${javaField}\" class=\"mb-0px!\">\n                            <el-input v-model=\"row.${javaField}\" placeholder=\"请输入${comment}\" />\n                          </el-form-item>\n                        </template>\n                      </el-table-column>\n                    #elseif($column.htmlType == \"imageUpload\")## 图片上传\n                        #set ($hasImageUploadColumn = true)\n                      <el-table-column label=\"${comment}\" min-width=\"200\">\n                        <template v-slot=\"{ row, $index }\">\n                          <el-form-item :prop=\"`${$index}.${javaField}`\" :rules=\"formRules.${javaField}\" class=\"mb-0px!\">\n                            <ImageUpload v-model=\"row.${javaField}\"/>\n                          </el-form-item>\n                        </template>\n                      </el-table-column>\n                    #elseif($column.htmlType == \"fileUpload\")## 文件上传\n                        #set ($hasFileUploadColumn = true)\n                      <el-table-column label=\"${comment}\" min-width=\"200\">\n                        <template v-slot=\"{ row, $index }\">\n                          <el-form-item :prop=\"`${$index}.${javaField}`\" :rules=\"formRules.${javaField}\" class=\"mb-0px!\">\n                            <FileUpload v-model=\"row.${javaField}\"/>\n                          </el-form-item>\n                        </template>\n                      </el-table-column>\n                    #elseif($column.htmlType == \"editor\")## 文本编辑器\n                        #set ($hasEditorColumn = true)\n                      <el-table-column label=\"${comment}\" min-width=\"400\">\n                        <template v-slot=\"{ row, $index }\">\n                          <el-form-item :prop=\"`${$index}.${javaField}`\" :rules=\"formRules.${javaField}\" class=\"mb-0px!\">\n                            <Editor v-model=\"row.${javaField}\" :min-height=\"192\"/>\n                          </el-form-item>\n                        </template>\n                      </el-table-column>\n                    #elseif($column.htmlType == \"select\")## 下拉框\n                      <el-table-column label=\"${comment}\" min-width=\"150\">\n                        <template v-slot=\"{ row, $index }\">\n                          <el-form-item :prop=\"`${$index}.${javaField}`\" :rules=\"formRules.${javaField}\" class=\"mb-0px!\">\n                            <el-select v-model=\"row.${javaField}\" placeholder=\"请选择${comment}\">\n                                #if (\"\" != $dictType)## 有数据字典\n                                  <el-option v-for=\"dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())\"\n                                             :key=\"dict.value\" :label=\"dict.label\" #if ($column.javaType == \"Integer\" || $column.javaType == \"Long\"):value=\"parseInt(dict.value)\"#else:value=\"dict.value\"#end />\n                                #else##没数据字典\n                                  <el-option label=\"请选择字典生成\" value=\"\" />\n                                #end\n                            </el-select>\n                          </el-form-item>\n                        </template>\n                      </el-table-column>\n                    #elseif($column.htmlType == \"checkbox\")## 多选框\n                      <el-table-column label=\"${comment}\" min-width=\"150\">\n                        <template v-slot=\"{ row, $index }\">\n                          <el-form-item :prop=\"`${$index}.${javaField}`\" :rules=\"formRules.${javaField}\" class=\"mb-0px!\">\n                            <el-checkbox-group v-model=\"row.${javaField}\">\n                                #if (\"\" != $dictType)## 有数据字典\n                                  <el-checkbox v-for=\"dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())\"\n                                               :key=\"dict.value\" #if($column.javaType == \"Integer\" || $column.javaType == \"Long\"):label=\"parseInt(dict.value)\"#else:label=\"dict.value\"#end>{{dict.label}}</el-checkbox>\n                                #else##没数据字典\n                                  <el-checkbox>请选择字典生成</el-checkbox>\n                                #end\n                            </el-checkbox-group>\n                          </el-form-item>\n                        </template>\n                      </el-table-column>\n                    #elseif($column.htmlType == \"radio\")## 单选框\n                      <el-table-column label=\"${comment}\" min-width=\"150\">\n                        <template v-slot=\"{ row, $index }\">\n                          <el-form-item :prop=\"`${$index}.${javaField}`\" :rules=\"formRules.${javaField}\" class=\"mb-0px!\">\n                            <el-radio-group v-model=\"row.${javaField}\">\n                                #if (\"\" != $dictType)## 有数据字典\n                                  <el-radio v-for=\"dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())\"\n                                            :key=\"dict.value\" #if($column.javaType == \"Integer\" || $column.javaType == \"Long\"):label=\"parseInt(dict.value)\"\n                                            #else:label=\"dict.value\"#end>{{dict.label}}</el-radio>\n                                #else##没数据字典\n                                  <el-radio label=\"1\">请选择字典生成</el-radio>\n                                #end\n                            </el-radio-group>\n                          </el-form-item>\n                        </template>\n                      </el-table-column>\n                    #elseif($column.htmlType == \"datetime\")## 时间框\n                      <el-table-column label=\"${comment}\" min-width=\"150\">\n                        <template v-slot=\"{ row, $index }\">\n                          <el-form-item :prop=\"`${$index}.${javaField}`\" :rules=\"formRules.${javaField}\" class=\"mb-0px!\">\n                            <el-date-picker clearable v-model=\"row.${javaField}\" type=\"date\" value-format=\"timestamp\" placeholder=\"选择${comment}\" />\n                          </el-form-item>\n                        </template>\n                      </el-table-column>\n                    #elseif($column.htmlType == \"textarea\")## 文本框\n                      <el-table-column label=\"${comment}\" min-width=\"200\">\n                        <template v-slot=\"{ row, $index }\">\n                          <el-form-item :prop=\"`${$index}.${javaField}`\" :rules=\"formRules.${javaField}\" class=\"mb-0px!\">\n                            <el-input v-model=\"row.${javaField}\" type=\"textarea\" placeholder=\"请输入${comment}\" />\n                          </el-form-item>\n                        </template>\n                      </el-table-column>\n                    #end\n                #end\n            #end\n          <el-table-column align=\"center\" fixed=\"right\" label=\"操作\" width=\"60\">\n            <template v-slot=\"{ $index }\">\n              <el-link @click=\"handleDelete($index)\">—</el-link>\n            </template>\n          </el-table-column>\n        </el-table>\n      </el-form>\n      <el-row justify=\"center\" class=\"mt-3\">\n        <el-button @click=\"handleAdd\" round>+ 添加${subTable.classComment}</el-button>\n      </el-row>\n    #else## 情况二：一对一，form\n      <el-form\n        ref=\"formRef\"\n        :model=\"formData\"\n        :rules=\"formRules\"\n        label-width=\"100px\"\n        v-loading=\"formLoading\"\n      >\n          #foreach($column in $subColumns)\n              #if ($column.createOperation || $column.updateOperation)\n                  #set ($dictType = $column.dictType)\n                  #set ($javaField = $column.javaField)\n                  #set ($javaType = $column.javaType)\n                  #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})\n                  #set ($comment = $column.columnComment)\n                  #if ( $column.id == $subJoinColumn.id) ## 特殊：忽略主子表的 join 字段，不用填写\n                  #elseif ($column.htmlType == \"input\" && !$column.primaryKey)\n                    <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n                      <el-input v-model=\"formData.${javaField}\" placeholder=\"请输入${comment}\" />\n                    </el-form-item>\n                  #elseif($column.htmlType == \"imageUpload\")## 图片上传\n                      #set ($hasImageUploadColumn = true)\n                    <el-form-item label=\"${comment}\">\n                      <ImageUpload v-model=\"formData.${javaField}\"/>\n                    </el-form-item>\n                  #elseif($column.htmlType == \"fileUpload\")## 文件上传\n                      #set ($hasFileUploadColumn = true)\n                    <el-form-item label=\"${comment}\">\n                      <FileUpload v-model=\"formData.${javaField}\"/>\n                    </el-form-item>\n                  #elseif($column.htmlType == \"editor\")## 文本编辑器\n                      #set ($hasEditorColumn = true)\n                    <el-form-item label=\"${comment}\">\n                      <Editor v-model=\"formData.${javaField}\" :min-height=\"192\"/>\n                    </el-form-item>\n                  #elseif($column.htmlType == \"select\")## 下拉框\n                    <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n                      <el-select v-model=\"formData.${javaField}\" placeholder=\"请选择${comment}\">\n                          #if (\"\" != $dictType)## 有数据字典\n                            <el-option v-for=\"dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())\"\n                                       :key=\"dict.value\" :label=\"dict.label\" #if ($column.javaType == \"Integer\" || $column.javaType == \"Long\"):value=\"parseInt(dict.value)\"#else:value=\"dict.value\"#end />\n                          #else##没数据字典\n                            <el-option label=\"请选择字典生成\" value=\"\" />\n                          #end\n                      </el-select>\n                    </el-form-item>\n                  #elseif($column.htmlType == \"checkbox\")## 多选框\n                    <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n                      <el-checkbox-group v-model=\"formData.${javaField}\">\n                          #if (\"\" != $dictType)## 有数据字典\n                            <el-checkbox v-for=\"dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())\"\n                                         :key=\"dict.value\" #if($column.javaType == \"Integer\" || $column.javaType == \"Long\"):label=\"parseInt(dict.value)\"#else:label=\"dict.value\"#end>{{dict.label}}</el-checkbox>\n                          #else##没数据字典\n                            <el-checkbox>请选择字典生成</el-checkbox>\n                          #end\n                      </el-checkbox-group>\n                    </el-form-item>\n                  #elseif($column.htmlType == \"radio\")## 单选框\n                    <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n                      <el-radio-group v-model=\"formData.${javaField}\">\n                          #if (\"\" != $dictType)## 有数据字典\n                            <el-radio v-for=\"dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())\"\n                                      :key=\"dict.value\" #if($column.javaType == \"Integer\" || $column.javaType == \"Long\"):label=\"parseInt(dict.value)\"\n                                      #else:label=\"dict.value\"#end>{{dict.label}}</el-radio>\n                          #else##没数据字典\n                            <el-radio label=\"1\">请选择字典生成</el-radio>\n                          #end\n                      </el-radio-group>\n                    </el-form-item>\n                  #elseif($column.htmlType == \"datetime\")## 时间框\n                    <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n                      <el-date-picker clearable v-model=\"formData.${javaField}\" type=\"date\" value-format=\"timestamp\" placeholder=\"选择${comment}\" />\n                    </el-form-item>\n                  #elseif($column.htmlType == \"textarea\")## 文本框\n                    <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n                      <el-input v-model=\"formData.${javaField}\" type=\"textarea\" placeholder=\"请输入${comment}\" />\n                    </el-form-item>\n                  #end\n              #end\n          #end\n      </el-form>\n    #end\n  </div>\n</template>\n\n<script>\n  import * as ${simpleClassName}Api from '@/api/${table.moduleName}/${table.businessName}';\n      #if ($hasImageUploadColumn)\n      import ImageUpload from '@/components/ImageUpload';\n      #end\n      #if ($hasFileUploadColumn)\n      import FileUpload from '@/components/FileUpload';\n      #end\n      #if ($hasEditorColumn)\n      import Editor from '@/components/Editor';\n      #end\n  export default {\n    name: \"${subSimpleClassName}Form\",\n    components: {\n        #if ($hasImageUploadColumn)\n          ImageUpload,\n        #end\n        #if ($hasFileUploadColumn)\n          FileUpload,\n        #end\n        #if ($hasEditorColumn)\n          Editor,\n        #end\n    },\n    props:[\n      '${subJoinColumn.javaField}'\n    ],// ${subJoinColumn.columnComment}（主表的关联字段）\n    data() {\n      return {\n        // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\n        formLoading: false,\n        // 表单参数\n        formData: [],\n        // 表单校验\n        formRules: {\n            #foreach ($column in $subColumns)\n                #if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键\n                    #set($comment=$column.columnComment)\n                        $column.javaField: [{ required: true, message: \"${comment}不能为空\", trigger: #if($column.htmlType == \"select\")\"change\"#else\"blur\"#end }],\n                #end\n            #end\n        },\n      };\n    },\n    watch:{/** 监听主表的关联字段的变化，加载对应的子表数据 */\n      ${subJoinColumn.javaField}:{\n        handler(val) {\n          // 1. 重置表单\n            #if ( $subTable.subJoinMany )\n              this.formData = []\n            #else\n              this.formData = {\n                  #foreach ($column in $subColumns)\n                      #if ($column.createOperation || $column.updateOperation)\n                          #if ($column.htmlType == \"checkbox\")\n                                  $column.javaField: [],\n                          #else\n                                  $column.javaField: undefined,\n                          #end\n                      #end\n                  #end\n              }\n            #end\n          // 2. val 非空，则加载数据\n          if (!val) {\n            return;\n          }\n          try {\n            this.formLoading = true;\n            // 这里还是需要获取一下 this 的不然取不到 formData\n            const that = this;\n            #if ( $subTable.subJoinMany )\n            ${simpleClassName}Api.get${subSimpleClassName}ListBy${SubJoinColumnName}(val).then(function (res){\n              that.formData = res.data;\n            })\n            #else\n            ${simpleClassName}Api.get${subSimpleClassName}By${SubJoinColumnName}(val).then(function (res){\n              const data = res.data;\n              if (!data) {\n                return\n              }\n              that.formData = data;\n            })\n            #end\n          } finally {\n            this.formLoading = false;\n          }\n        },\n        immediate: true\n      }\n    },\n    methods: {\n        #if ( $subTable.subJoinMany )\n          /** 新增按钮操作 */\n          handleAdd() {\n            const row = {\n                #foreach ($column in $subColumns)\n                    #if ($column.createOperation || $column.updateOperation)\n                        #if ($column.htmlType == \"checkbox\")\n                                $column.javaField: [],\n                        #else\n                                $column.javaField: undefined,\n                        #end\n                    #end\n                #end\n            }\n            row.${subJoinColumn.javaField} = this.${subJoinColumn.javaField};\n            this.formData.push(row);\n          },\n          /** 删除按钮操作 */\n          handleDelete(index) {\n            this.formData.splice(index, 1);\n          },\n        #end\n      /** 表单校验 */\n      validate(){\n        return this.#[[$]]#refs[\"formRef\"].validate();\n      },\n      /** 表单值 */\n      getData(){\n        return this.formData;\n      }\n    }\n  };\n</script>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/vue/views/components/list_sub_erp.vue.vm",
    "content": "#set ($subTable = $subTables.get($subIndex))##当前表\n#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组\n#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段\n#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex))\n#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段\n#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写\n<template>\n  <div class=\"app-container\">\n#if ($table.templateType == 11)\n    <!-- 操作工具栏 -->\n    <el-row :gutter=\"10\" class=\"mb8\">\n      <el-col :span=\"1.5\">\n        <el-button type=\"primary\" plain icon=\"el-icon-plus\" size=\"mini\" @click=\"openForm(undefined)\"\n                   v-hasPermi=\"['${permissionPrefix}:create']\">新增</el-button>\n      </el-col>\n    </el-row>\n#end\n      ## 列表\n      <el-table v-loading=\"loading\" :data=\"list\" :stripe=\"true\" :show-overflow-tooltip=\"true\">\n          #foreach($column in $subColumns)\n              #if ($column.listOperationResult)\n                  #set ($dictType=$column.dictType)\n                  #set ($javaField = $column.javaField)\n                  #set ($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})\n                  #set ($comment=$column.columnComment)\n                  #if ( $column.id == $subJoinColumn.id) ## 特殊：忽略主子表的 join 字段，不用填写\n                  #elseif ($column.javaType == \"LocalDateTime\")## 时间类型\n                <el-table-column label=\"${comment}\" align=\"center\" prop=\"${javaField}\" width=\"180\">\n                  <template v-slot=\"scope\">\n                    <span>{{ parseTime(scope.row.${javaField}) }}</span>\n                  </template>\n                </el-table-column>\n                  #elseif($column.dictType && \"\" != $column.dictType)## 数据字典\n                <el-table-column label=\"${comment}\" align=\"center\" prop=\"${javaField}\">\n                  <template v-slot=\"scope\">\n                    <dict-tag :type=\"DICT_TYPE.$dictType.toUpperCase()\" :value=\"scope.row.${column.javaField}\" />\n                  </template>\n                </el-table-column>\n              #else\n                <el-table-column label=\"${comment}\" align=\"center\" prop=\"${javaField}\" />\n              #end\n          #end\n      #end\n    <el-table-column label=\"操作\" align=\"center\" class-name=\"small-padding fixed-width\">\n      <template v-slot=\"scope\">\n        <el-button size=\"mini\" type=\"text\" icon=\"el-icon-edit\" @click=\"openForm(scope.row.${primaryColumn.javaField})\"\n                   v-hasPermi=\"['${permissionPrefix}:update']\">修改</el-button>\n        <el-button size=\"mini\" type=\"text\" icon=\"el-icon-delete\" @click=\"handleDelete(scope.row)\"\n                   v-hasPermi=\"['${permissionPrefix}:delete']\">删除</el-button>\n      </template>\n    </el-table-column>\n  </el-table>\n#if ($table.templateType == 11)\n    <!-- 分页组件 -->\n    <pagination v-show=\"total > 0\" :total=\"total\" :page.sync=\"queryParams.pageNo\" :limit.sync=\"queryParams.pageSize\"\n                @pagination=\"getList\"/>\n  <!-- 对话框(添加 / 修改) -->\n  <${subSimpleClassName}Form ref=\"formRef\" @success=\"getList\" />\n#end\n  </div>\n</template>\n\n<script>\n  import * as ${simpleClassName}Api from '@/api/${table.moduleName}/${table.businessName}';\n  #if ($table.templateType == 11)\n  import ${subSimpleClassName}Form from './${subSimpleClassName}Form.vue';\n  #end\n  export default {\n    name: \"${subSimpleClassName}List\",\n#if ($table.templateType == 11)\n    components: {\n       ${subSimpleClassName}Form\n    },\n#end\n    props:[\n      '${subJoinColumn.javaField}'\n    ],// ${subJoinColumn.columnComment}（主表的关联字段）\n    data() {\n      return {\n        // 遮罩层\n        loading: true,\n        // 列表的数据\n        list: [],\n#if ($table.templateType == 11)\n        // 列表的总页数\n        total: 0,\n        // 查询参数\n        queryParams: {\n          pageNo: 1,\n          pageSize: 10,\n          ${subJoinColumn.javaField}: undefined\n        }\n#end\n      };\n    },\n#if ($table.templateType != 11)\n    created() {\n      this.getList();\n    },\n#end\n    watch:{/** 监听主表的关联字段的变化，加载对应的子表数据 */\n        ${subJoinColumn.javaField}:{\n            handler(val) {\n              this.queryParams.${subJoinColumn.javaField} = val;\n              if (val){\n                this.handleQuery();\n              }\n            },\n            immediate: true\n      }\n    },\n    methods: {\n      /** 查询列表 */\n      async getList() {\n        try {\n          this.loading = true;\n          #if ($table.templateType == 11)\n            const res = await ${simpleClassName}Api.get${subSimpleClassName}Page(this.queryParams);\n            this.list = res.data.list;\n            this.total = res.data.total;\n          #else\n              #if ( $subTable.subJoinMany )\n                const res = await ${simpleClassName}Api.get${subSimpleClassName}ListBy${SubJoinColumnName}(this.${subJoinColumn.javaField});\n                this.list = res.data;\n              #else\n                const res = await  ${simpleClassName}Api.get${subSimpleClassName}By${SubJoinColumnName}(this.${subJoinColumn.javaField});\n                const data = res.data;\n                if (!data) {\n                  return;\n                }\n                this.list.push(data);\n              #end\n          #end\n        } finally {\n          this.loading = false;\n        }\n      },\n      /** 搜索按钮操作 */\n      handleQuery() {\n        this.queryParams.pageNo = 1;\n        this.getList();\n      },\n#if ($table.templateType == 11)\n      /** 添加/修改操作 */\n      openForm(id) {\n        if (!this.${subJoinColumn.javaField}) {\n          this.#[[$modal]]#.msgError('请选择一个${table.classComment}');\n          return;\n        }\n        this.#[[$]]#refs[\"formRef\"].open(id, this.${subJoinColumn.javaField});\n      },\n      /** 删除按钮操作 */\n      async handleDelete(row) {\n        const ${primaryColumn.javaField} = row.${primaryColumn.javaField};\n        await this.#[[$modal]]#.confirm('是否确认删除${table.classComment}编号为\"' + ${primaryColumn.javaField} + '\"的数据项?');\n        try {\n          await ${simpleClassName}Api.delete${subSimpleClassName}(${primaryColumn.javaField});\n          await this.getList();\n          this.#[[$modal]]#.msgSuccess(\"删除成功\");\n        } catch {}\n      },\n#end\n    }\n  };\n</script>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/vue/views/components/list_sub_inner.vue.vm",
    "content": "## 子表的 erp 和 inner 使用相似的 list 列表，差异主要两点：\n## 1）inner 使用 list 不分页，erp 使用 page 分页\n## 2）erp 支持单个子表的新增、修改、删除，inner 不支持\n#parse(\"codegen/vue/views/components/list_sub_erp.vue.vm\")"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/vue/views/form.vue.vm",
    "content": "<template>\n  <div class=\"app-container\">\n    <!-- 对话框(添加 / 修改) -->\n    <el-dialog :title=\"dialogTitle\" :visible.sync=\"dialogVisible\" width=\"45%\" v-dialogDrag append-to-body>\n      <el-form ref=\"formRef\" :model=\"formData\" :rules=\"formRules\" v-loading=\"formLoading\" label-width=\"100px\">\n          #foreach($column in $columns)\n              #if ($column.createOperation || $column.updateOperation)\n                  #set ($dictType = $column.dictType)\n                  #set ($javaField = $column.javaField)\n                  #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})\n                  #set ($comment = $column.columnComment)\n                  #if ( $table.templateType == 2 && $column.id == $treeParentColumn.id )\n                    <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n                      <TreeSelect\n                        v-model=\"formData.${javaField}\"\n                        :options=\"${classNameVar}Tree\"\n                        :normalizer=\"normalizer\"\n                        placeholder=\"请选择${comment}\"\n                      />\n                    </el-form-item>\n                  #elseif ($column.htmlType == \"input\" && !$column.primaryKey)## 忽略主键，不用在表单里\n                    <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n                      <el-input v-model=\"formData.${javaField}\" placeholder=\"请输入${comment}\" />\n                    </el-form-item>\n                  #elseif($column.htmlType == \"imageUpload\")## 图片上传\n                      #set ($hasImageUploadColumn = true)\n                    <el-form-item label=\"${comment}\">\n                      <ImageUpload v-model=\"formData.${javaField}\"/>\n                    </el-form-item>\n                  #elseif($column.htmlType == \"fileUpload\")## 文件上传\n                      #set ($hasFileUploadColumn = true)\n                    <el-form-item label=\"${comment}\">\n                      <FileUpload v-model=\"formData.${javaField}\"/>\n                    </el-form-item>\n                  #elseif($column.htmlType == \"editor\")## 文本编辑器\n                      #set ($hasEditorColumn = true)\n                    <el-form-item label=\"${comment}\">\n                      <Editor v-model=\"formData.${javaField}\" :min-height=\"192\"/>\n                    </el-form-item>\n                  #elseif($column.htmlType == \"select\")## 下拉框\n                    <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n                      <el-select v-model=\"formData.${javaField}\" placeholder=\"请选择${comment}\">\n                          #if (\"\" != $dictType)## 有数据字典\n                            <el-option v-for=\"dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())\"\n                                       :key=\"dict.value\" :label=\"dict.label\" #if ($column.javaType == \"Integer\" || $column.javaType == \"Long\"):value=\"parseInt(dict.value)\"#else:value=\"dict.value\"#end />\n                          #else##没数据字典\n                            <el-option label=\"请选择字典生成\" value=\"\" />\n                          #end\n                      </el-select>\n                    </el-form-item>\n                  #elseif($column.htmlType == \"checkbox\")## 多选框\n                    <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n                      <el-checkbox-group v-model=\"formData.${javaField}\">\n                          #if (\"\" != $dictType)## 有数据字典\n                            <el-checkbox v-for=\"dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())\"\n                                         :key=\"dict.value\" #if($column.javaType == \"Integer\" || $column.javaType == \"Long\"):label=\"parseInt(dict.value)\"#else:label=\"dict.value\"#end>{{dict.label}}</el-checkbox>\n                          #else##没数据字典\n                            <el-checkbox>请选择字典生成</el-checkbox>\n                          #end\n                      </el-checkbox-group>\n                    </el-form-item>\n                  #elseif($column.htmlType == \"radio\")## 单选框\n                    <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n                      <el-radio-group v-model=\"formData.${javaField}\">\n                          #if (\"\" != $dictType)## 有数据字典\n                            <el-radio v-for=\"dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())\"\n                                      :key=\"dict.value\" #if($column.javaType == \"Integer\" || $column.javaType == \"Long\"):label=\"parseInt(dict.value)\"\n                                      #else:label=\"dict.value\"#end>{{dict.label}}</el-radio>\n                          #else##没数据字典\n                            <el-radio label=\"1\">请选择字典生成</el-radio>\n                          #end\n                      </el-radio-group>\n                    </el-form-item>\n                  #elseif($column.htmlType == \"datetime\")## 时间框\n                    <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n                      <el-date-picker clearable v-model=\"formData.${javaField}\" type=\"date\" value-format=\"timestamp\" placeholder=\"选择${comment}\" />\n                    </el-form-item>\n                  #elseif($column.htmlType == \"textarea\")## 文本框\n                    <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n                      <el-input v-model=\"formData.${javaField}\" type=\"textarea\" placeholder=\"请输入内容\" />\n                    </el-form-item>\n                  #end\n              #end\n          #end\n      </el-form>\n        ## 特殊：主子表专属逻辑\n        #if ( $table.templateType == 10 || $table.templateType == 12 )\n          <!-- 子表的表单 -->\n          <el-tabs v-model=\"subTabsName\">\n              #foreach ($subTable in $subTables)\n                  #set ($index = $foreach.count - 1)\n                  #set ($subClassNameVar = $subClassNameVars.get($index))\n                  #set ($subSimpleClassName = $subSimpleClassNames.get($index))\n                  #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))\n                <el-tab-pane label=\"${subTable.classComment}\" name=\"$subClassNameVar\">\n                  <${subSimpleClassName}Form ref=\"${subClassNameVar}FormRef\" :${subJoinColumn_strikeCase}=\"formData.id\" />\n                </el-tab-pane>\n              #end\n          </el-tabs>\n        #end\n      <div slot=\"footer\" class=\"dialog-footer\">\n        <el-button type=\"primary\" @click=\"submitForm\" :disabled=\"formLoading\">确 定</el-button>\n        <el-button @click=\"dialogVisible = false\">取 消</el-button>\n      </div>\n    </el-dialog>\n  </div>\n</template>\n\n<script>\n  import * as ${simpleClassName}Api from '@/api/${table.moduleName}/${table.businessName}';\n  #if ($hasImageUploadColumn)\n  import ImageUpload from '@/components/ImageUpload';\n  #end\n  #if ($hasFileUploadColumn)\n  import FileUpload from '@/components/FileUpload';\n  #end\n  #if ($hasEditorColumn)\n  import Editor from '@/components/Editor';\n  #end\n  ## 特殊：树表专属逻辑\n  #if ( $table.templateType == 2 )\n  import TreeSelect from \"@riophae/vue-treeselect\";\n  import \"@riophae/vue-treeselect/dist/vue-treeselect.css\";\n  #end\n  ## 特殊：主子表专属逻辑\n  #if ( $table.templateType == 10 || $table.templateType == 12 )\n      #foreach ($subSimpleClassName in $subSimpleClassNames)\n      import ${subSimpleClassName}Form from './components/${subSimpleClassName}Form.vue'\n      #end\n  #end\n  export default {\n    name: \"${simpleClassName}Form\",\n    components: {\n        #if ($hasImageUploadColumn)\n          ImageUpload,\n        #end\n        #if ($hasFileUploadColumn)\n          FileUpload,\n        #end\n        #if ($hasEditorColumn)\n          Editor,\n        #end\n        ## 特殊：树表专属逻辑\n        #if ( $table.templateType == 2 )\n          TreeSelect,\n        #end\n        ## 特殊：主子表专属逻辑\n        #if ( $table.templateType == 10 || $table.templateType == 12 )\n            #foreach ($subSimpleClassName in $subSimpleClassNames)\n               ${subSimpleClassName}Form,\n            #end\n        #end\n    },\n    data() {\n      return {\n        // 弹出层标题\n        dialogTitle: \"\",\n        // 是否显示弹出层\n        dialogVisible: false,\n        // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\n        formLoading: false,\n        // 表单参数\n        formData: {\n            #foreach ($column in $columns)\n                #if ($column.createOperation || $column.updateOperation)\n                    #if ($column.htmlType == \"checkbox\")\n                            $column.javaField: [],\n                    #else\n                            $column.javaField: undefined,\n                    #end\n                #end\n            #end\n        },\n        // 表单校验\n        formRules: {\n            #foreach ($column in $columns)\n                #if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键\n                    #set($comment=$column.columnComment)\n                        $column.javaField: [{ required: true, message: '${comment}不能为空', trigger: #if($column.htmlType == 'select')'change'#else'blur'#end }],\n                #end\n            #end\n        },\n          ## 特殊：树表专属逻辑\n          #if ( $table.templateType == 2 )\n             ${classNameVar}Tree: [], // 树形结构\n          #end\n        ## 特殊：主子表专属逻辑\n        #if ( $table.templateType == 10 || $table.templateType == 12 )\n        #if ( $subTables && $subTables.size() > 0 )\n            /** 子表的表单 */\n             subTabsName: '$subClassNameVars.get(0)'\n        #end\n        #end\n      };\n    },\n    methods: {\n      /** 打开弹窗 */\n     async open(id) {\n        this.dialogVisible = true;\n        this.reset();\n        // 修改时，设置数据\n        if (id) {\n          this.formLoading = true;\n          try {\n            const res = await ${simpleClassName}Api.get${simpleClassName}(id);\n            this.formData = res.data;\n            this.title = \"修改${table.classComment}\";\n          } finally {\n            this.formLoading = false;\n          }\n        }\n        this.title = \"新增${table.classComment}\";\n        ## 特殊：树表专属逻辑\n        #if ( $table.templateType == 2 )\n        await this.get${simpleClassName}Tree();\n        #end\n      },\n      /** 提交按钮 */\n      async submitForm() {\n        // 校验主表\n        await this.$refs[\"formRef\"].validate();\n          ## 特殊：主子表专属逻辑\n          #if ( $table.templateType == 10 || $table.templateType == 12 )\n              #if ( $subTables && $subTables.size() > 0 )\n                // 校验子表\n                  #foreach ($subTable in $subTables)\n                      #set ($index = $foreach.count - 1)\n                      #set ($subClassNameVar = $subClassNameVars.get($index))\n                    try {\n                      ## 代码生成后会替换为正确的 refs\n                      await this.refs['${subClassNameVar}FormRef'].validate();\n                    } catch (e) {\n                      this.subTabsName = '${subClassNameVar}';\n                      return;\n                    }\n                  #end\n              #end\n          #end\n        this.formLoading = true;\n        try {\n          const data = this.formData;\n        ## 特殊：主子表专属逻辑\n        #if ( $table.templateType == 10 || $table.templateType == 12 )\n        #if ( $subTables && $subTables.size() > 0 )\n            // 拼接子表的数据\n            #foreach ($subTable in $subTables)\n                #set ($index = $foreach.count - 1)\n                #set ($subClassNameVar = $subClassNameVars.get($index))\n              data.${subClassNameVar}#if ( $subTable.subJoinMany)s#end = this.refs['${subClassNameVar}FormRef'].getData();\n            #end\n        #end\n        #end\n          // 修改的提交\n          if (data.${primaryColumn.javaField}) {\n            await ${simpleClassName}Api.update${simpleClassName}(data);\n            this.#[[$modal]]#.msgSuccess(\"修改成功\");\n            this.dialogVisible = false;\n            this.#[[$]]#emit('success');\n            return;\n          }\n          // 添加的提交\n          await ${simpleClassName}Api.create${simpleClassName}(data);\n          this.#[[$modal]]#.msgSuccess(\"新增成功\");\n          this.dialogVisible = false;\n          this.#[[$]]#emit('success');\n        } finally {\n          this.formLoading = false;\n        }\n      },\n        ## 特殊：树表专属逻辑\n        #if ( $table.templateType == 2 )\n          /** 获得${table.classComment}树 */\n         async get${simpleClassName}Tree() {\n            this.${classNameVar}Tree = [];\n            const res = await ${simpleClassName}Api.get${simpleClassName}List();\n            const root = { id: 0, name: '顶级${table.classComment}', children: [] };\n            root.children = this.handleTree(res.data, 'id', '${treeParentColumn.javaField}')\n            this.${classNameVar}Tree.push(root)\n          },\n        #end\n        ## 特殊：树表专属逻辑\n        #if ( $table.templateType == 2 )\n          /** 转换${table.classComment}数据结构 */\n          normalizer(node) {\n            if (node.children && !node.children.length) {\n              delete node.children;\n            }\n              #if ($treeNameColumn.javaField == \"name\")\n                return {\n                  id: node.id,\n                  label: node.name,\n                  children: node.children\n                };\n              #else\n                return {\n                  id: node.id,\n                  label: node['$treeNameColumn.javaField'],\n                  children: node.children\n                };\n              #end\n          },\n        #end\n      /** 表单重置 */\n      reset() {\n        this.formData = {\n            #foreach ($column in $columns)\n                #if ($column.createOperation || $column.updateOperation)\n                    #if ($column.htmlType == \"checkbox\")\n                            $column.javaField: [],\n                    #else\n                            $column.javaField: undefined,\n                    #end\n                #end\n            #end\n        };\n        this.resetForm(\"formRef\");\n      }\n    }\n  };\n</script>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/vue/views/index.vue.vm",
    "content": "<template>\n  <div class=\"app-container\">\n    <!-- 搜索工作栏 -->\n    <el-form :model=\"queryParams\" ref=\"queryForm\" size=\"small\" :inline=\"true\" v-show=\"showSearch\" label-width=\"68px\">\n#foreach($column in $columns)\n#if ($column.listOperation)\n    #set ($dictType=$column.dictType)\n    #set ($javaField = $column.javaField)\n    #set ($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})\n    #set ($comment=$column.columnComment)\n#if ($column.htmlType == \"input\")\n      <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n        <el-input v-model=\"queryParams.${javaField}\" placeholder=\"请输入${comment}\" clearable @keyup.enter.native=\"handleQuery\"/>\n      </el-form-item>\n#elseif ($column.htmlType == \"select\" || $column.htmlType == \"radio\")\n      <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n        <el-select v-model=\"queryParams.${javaField}\" placeholder=\"请选择${comment}\" clearable size=\"small\">\n    #if (\"\" != $dictType)## 设置了 dictType 数据字典的情况\n          <el-option v-for=\"dict in this.getDictDatas(DICT_TYPE.$dictType.toUpperCase())\"\n                       :key=\"dict.value\" :label=\"dict.label\" :value=\"dict.value\"/>\n    #else## 未设置 dictType 数据字典的情况\n          <el-option label=\"请选择字典生成\" value=\"\" />\n    #end\n        </el-select>\n      </el-form-item>\n#elseif($column.htmlType == \"datetime\")\n    #if ($column.listOperationCondition != \"BETWEEN\")## 非范围\n      <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n        <el-date-picker clearable v-model=\"queryParams.${javaField}\" type=\"date\" value-format=\"yyyy-MM-dd\" placeholder=\"选择${comment}\" />\n      </el-form-item>\n    #else## 范围\n      <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n        <el-date-picker v-model=\"queryParams.${javaField}\" style=\"width: 240px\" value-format=\"yyyy-MM-dd HH:mm:ss\" type=\"daterange\"\n                        range-separator=\"-\" start-placeholder=\"开始日期\" end-placeholder=\"结束日期\" :default-time=\"['00:00:00', '23:59:59']\" />\n      </el-form-item>\n    #end\n#end\n#end\n#end\n      <el-form-item>\n        <el-button type=\"primary\" icon=\"el-icon-search\" @click=\"handleQuery\">搜索</el-button>\n        <el-button icon=\"el-icon-refresh\" @click=\"resetQuery\">重置</el-button>\n      </el-form-item>\n    </el-form>\n\n    <!-- 操作工具栏 -->\n    <el-row :gutter=\"10\" class=\"mb8\">\n      <el-col :span=\"1.5\">\n        <el-button type=\"primary\" plain icon=\"el-icon-plus\" size=\"mini\" @click=\"openForm(undefined)\"\n                   v-hasPermi=\"['${permissionPrefix}:create']\">新增</el-button>\n      </el-col>\n      <el-col :span=\"1.5\">\n        <el-button type=\"warning\" plain icon=\"el-icon-download\" size=\"mini\" @click=\"handleExport\" :loading=\"exportLoading\"\n                   v-hasPermi=\"['${permissionPrefix}:export']\">导出</el-button>\n      </el-col>\n        ## 特殊：树表专属逻辑\n        #if ( $table.templateType == 2 )\n          <el-col :span=\"1.5\">\n            <el-button type=\"danger\" plain icon=\"el-icon-sort\" size=\"mini\" @click=\"toggleExpandAll\">\n              展开/折叠\n            </el-button>\n          </el-col>\n        #end\n      <right-toolbar :showSearch.sync=\"showSearch\" @queryTable=\"getList\"></right-toolbar>\n    </el-row>\n\n      ## 特殊：主子表专属逻辑\n      #if ( $table.templateType == 11 && $subTables && $subTables.size() > 0 )\n      <el-table\n        v-loading=\"loading\"\n        :data=\"list\"\n        :stripe=\"true\"\n        :highlight-current-row=\"true\"\n        :show-overflow-tooltip=\"true\"\n        @current-change=\"handleCurrentChange\"\n      >\n          ## 特殊：树表专属逻辑\n      #elseif ( $table.templateType == 2 )\n      <el-table\n        v-loading=\"loading\"\n        :data=\"list\"\n        :stripe=\"true\"\n        :show-overflow-tooltip=\"true\"\n        v-if=\"refreshTable\"\n        row-key=\"id\"\n        :default-expand-all=\"isExpandAll\"\n        :tree-props=\"{children: 'children', hasChildren: 'hasChildren'}\"\n      >\n      #else\n      <el-table v-loading=\"loading\" :data=\"list\" :stripe=\"true\" :show-overflow-tooltip=\"true\">\n      #end\n      ## 特殊：主子表专属逻辑\n      #if ( $table.templateType == 12 && $subTables && $subTables.size() > 0 )\n        <!-- 子表的列表 -->\n        <el-table-column type=\"expand\">\n          <template #default=\"scope\">\n            <el-tabs value=\"$subClassNameVars.get(0)\">\n                #foreach ($subTable in $subTables)\n                    #set ($index = $foreach.count - 1)\n                    #set ($subClassNameVar = $subClassNameVars.get($index))\n                    #set ($subSimpleClassName = $subSimpleClassNames.get($index))\n                    #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))\n                  <el-tab-pane label=\"${subTable.classComment}\" name=\"$subClassNameVar\">\n                    <${subSimpleClassName}List :${subJoinColumn_strikeCase}=\"scope.row.id\" />\n                  </el-tab-pane>\n                #end\n            </el-tabs>\n          </template>\n        </el-table-column>\n      #end\n#foreach($column in $columns)\n#if ($column.listOperationResult)\n    #set ($dictType=$column.dictType)\n    #set ($javaField = $column.javaField)\n    #set ($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})\n    #set ($comment=$column.columnComment)\n#if ($column.javaType == \"LocalDateTime\")## 时间类型\n      <el-table-column label=\"${comment}\" align=\"center\" prop=\"${javaField}\" width=\"180\">\n        <template v-slot=\"scope\">\n          <span>{{ parseTime(scope.row.${javaField}) }}</span>\n        </template>\n      </el-table-column>\n#elseif(\"\" != $column.dictType)## 数据字典\n      <el-table-column label=\"${comment}\" align=\"center\" prop=\"${javaField}\">\n        <template v-slot=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.$dictType.toUpperCase()\" :value=\"scope.row.${column.javaField}\" />\n        </template>\n      </el-table-column>\n#else\n      <el-table-column label=\"${comment}\" align=\"center\" prop=\"${javaField}\" />\n#end\n#end\n#end\n      <el-table-column label=\"操作\" align=\"center\" class-name=\"small-padding fixed-width\">\n        <template v-slot=\"scope\">\n          <el-button size=\"mini\" type=\"text\" icon=\"el-icon-edit\" @click=\"openForm(scope.row.${primaryColumn.javaField})\"\n                     v-hasPermi=\"['${permissionPrefix}:update']\">修改</el-button>\n          <el-button size=\"mini\" type=\"text\" icon=\"el-icon-delete\" @click=\"handleDelete(scope.row)\"\n                     v-hasPermi=\"['${permissionPrefix}:delete']\">删除</el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n## 特殊：树表专属逻辑（树不需要分页）\n#if ( $table.templateType != 2 )\n    <!-- 分页组件 -->\n    <pagination v-show=\"total > 0\" :total=\"total\" :page.sync=\"queryParams.pageNo\" :limit.sync=\"queryParams.pageSize\"\n                @pagination=\"getList\"/>\n#end\n    <!-- 对话框(添加 / 修改) -->\n    <${simpleClassName}Form ref=\"formRef\" @success=\"getList\" />\n  ## 特殊：主子表专属逻辑\n  #if ( $table.templateType == 11 && $subTables && $subTables.size() > 0 )\n    <!-- 子表的列表 -->\n      <el-tabs v-model=\"subTabsName\">\n          #foreach ($subTable in $subTables)\n              #set ($index = $foreach.count - 1)\n              #set ($subClassNameVar = $subClassNameVars.get($index))\n              #set ($subSimpleClassName = $subSimpleClassNames.get($index))\n              #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))\n            <el-tab-pane label=\"${subTable.classComment}\" name=\"$subClassNameVar\">\n              <${subSimpleClassName}List v-if=\"currentRow.id\" :${subJoinColumn_strikeCase}=\"currentRow.id\" />\n            </el-tab-pane>\n          #end\n      </el-tabs>\n  #end\n  </div>\n</template>\n\n<script>\nimport * as ${simpleClassName}Api from '@/api/${table.moduleName}/${table.businessName}';\nimport ${simpleClassName}Form from './${simpleClassName}Form.vue';\n#if ($hasImageUploadColumn)\nimport ImageUpload from '@/components/ImageUpload';\n#end\n#if ($hasFileUploadColumn)\nimport FileUpload from '@/components/FileUpload';\n#end\n#if ($hasEditorColumn)\nimport Editor from '@/components/Editor';\n#end\n## 特殊：主子表专属逻辑\n#if ( $table.templateType != 10 )\n#if ( $subTables && $subTables.size() > 0 )\n    #foreach ($subSimpleClassName in $subSimpleClassNames)\n    import ${subSimpleClassName}List from './components/${subSimpleClassName}List.vue';\n    #end\n#end\n#end\nexport default {\n  name: \"${simpleClassName}\",\n  components: {\n          ${simpleClassName}Form,\n## 特殊：主子表专属逻辑\n#if ( $table.templateType != 10 )\n#if ( $subTables && $subTables.size() > 0 )\n      #foreach ($subSimpleClassName in $subSimpleClassNames)\n          ${subSimpleClassName}List,\n      #end\n#end\n#end\n#if ($hasImageUploadColumn)\n    ImageUpload,\n#end\n#if ($hasFileUploadColumn)\n    FileUpload,\n#end\n#if ($hasEditorColumn)\n    Editor,\n#end\n  },\n  data() {\n    return {\n      // 遮罩层\n      loading: true,\n      // 导出遮罩层\n      exportLoading: false,\n      // 显示搜索条件\n      showSearch: true,\n      ## 特殊：树表专属逻辑（树不需要分页接口）\n      #if ( $table.templateType != 2 )\n        // 总条数\n        total: 0,\n      #end\n      // ${table.classComment}列表\n      list: [],\n      // 是否展开，默认全部展开\n      isExpandAll: true,\n      // 重新渲染表格状态\n      refreshTable: true,\n      // 选中行\n      currentRow: {},\n      // 查询参数\n      queryParams: {\n        ## 特殊：树表专属逻辑（树不需要分页接口）\n        #if ( $table.templateType != 2 )\n            pageNo: 1,\n            pageSize: 10,\n        #end\n        #foreach ($column in $columns)\n        #if ($column.listOperation)\n        #if ($column.listOperationCondition != 'BETWEEN')\n        $column.javaField: null,\n        #end\n        #if ($column.htmlType == \"datetime\" && $column.listOperationCondition == \"BETWEEN\")\n        $column.javaField: [],\n        #end\n        #end\n        #end\n      },\n        ## 特殊：主子表专属逻辑-erp\n        #if ( $table.templateType == 11)\n            #if ( $subTables && $subTables.size() > 0 )\n              /** 子表的列表 */\n              subTabsName: '$subClassNameVars.get(0)'\n            #end\n        #end\n    };\n  },\n  created() {\n    this.getList();\n  },\n  methods: {\n    /** 查询列表 */\n    async getList() {\n      try {\n      this.loading = true;\n      ## 特殊：树表专属逻辑（树不需要分页接口）\n      #if ( $table.templateType == 2 )\n       const res = await ${simpleClassName}Api.get${simpleClassName}List(this.queryParams);\n       this.list = this.handleTree(res.data, 'id', '${treeParentColumn.javaField}');\n      #else\n        const res = await ${simpleClassName}Api.get${simpleClassName}Page(this.queryParams);\n        this.list = res.data.list;\n        this.total = res.data.total;\n      #end\n      } finally {\n        this.loading = false;\n      }\n    },\n    /** 搜索按钮操作 */\n    handleQuery() {\n      this.queryParams.pageNo = 1;\n      this.getList();\n    },\n    /** 重置按钮操作 */\n    resetQuery() {\n      this.resetForm(\"queryForm\");\n      this.handleQuery();\n    },\n    /** 添加/修改操作 */\n    openForm(id) {\n      this.#[[$]]#refs[\"formRef\"].open(id);\n    },\n    /** 删除按钮操作 */\n    async handleDelete(row) {\n      const ${primaryColumn.javaField} = row.${primaryColumn.javaField};\n      await this.#[[$modal]]#.confirm('是否确认删除${table.classComment}编号为\"' + ${primaryColumn.javaField} + '\"的数据项?')\n      try {\n       await ${simpleClassName}Api.delete${simpleClassName}(${primaryColumn.javaField});\n       await this.getList();\n       this.#[[$modal]]#.msgSuccess(\"删除成功\");\n      } catch {}\n    },\n    /** 导出按钮操作 */\n    async handleExport() {\n      await this.#[[$modal]]#.confirm('是否确认导出所有${table.classComment}数据项?');\n      try {\n        this.exportLoading = true;\n        const res = await ${simpleClassName}Api.export${simpleClassName}Excel(this.queryParams);\n        this.#[[$]]#download.excel(res.data, '${table.classComment}.xls');\n      } catch {\n      } finally {\n        this.exportLoading = false;\n      }\n    },\n      ## 特殊：主子表专属逻辑\n      #if ( $table.templateType == 11 )\n        /** 选中行操作 */\n        handleCurrentChange(row) {\n         this.currentRow = row;\n        #if ( $subTables && $subTables.size() > 0 )\n          /** 子表的列表 */\n          this.subTabsName = '$subClassNameVars.get(0)';\n        #end\n        },\n      #end\n      ## 特殊：树表专属逻辑\n      #if ( $table.templateType == 2 )\n        /** 展开/折叠操作 */\n        toggleExpandAll() {\n          this.refreshTable = false\n          this.isExpandAll = !this.isExpandAll\n          this.$nextTick(function () {\n            this.refreshTable = true\n          })\n        }\n      #end\n  }\n};\n</script>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/vue3/api/api.ts.vm",
    "content": "import request from '@/config/axios'\n#set ($baseURL = \"/${table.moduleName}/${simpleClassName_strikeCase}\")\n\n// ${table.classComment} VO\nexport interface ${simpleClassName}VO {\n#foreach ($column in $columns)\n#if ($column.createOperation || $column.updateOperation)\n#if(${column.javaType.toLowerCase()} == \"long\" || ${column.javaType.toLowerCase()} == \"integer\" || ${column.javaType.toLowerCase()} == \"short\" || ${column.javaType.toLowerCase()} == \"double\" || ${column.javaType.toLowerCase()} == \"bigdecimal\")\n  ${column.javaField}: number // ${column.columnComment}\n#elseif(${column.javaType.toLowerCase()} == \"date\" || ${column.javaType.toLowerCase()} == \"localdate\" || ${column.javaType.toLowerCase()} == \"localdatetime\")\n  ${column.javaField}: Date // ${column.columnComment}\n#else\n  ${column.javaField}: ${column.javaType.toLowerCase()} // ${column.columnComment}\n#end\n#end\n#end\n}\n\n// ${table.classComment} API\nexport const ${simpleClassName}Api = {\n#if ( $table.templateType != 2 )\n  // 查询${table.classComment}分页\n  get${simpleClassName}Page: async (params: any) => {\n    return await request.get({ url: `${baseURL}/page`, params })\n  },\n#else\n  // 查询${table.classComment}列表\n  get${simpleClassName}List: async (params) => {\n    return await request.get({ url: `${baseURL}/list`, params })\n  },\n#end\n\n  // 查询${table.classComment}详情\n  get${simpleClassName}: async (id: number) => {\n    return await request.get({ url: `${baseURL}/get?id=` + id })\n  },\n\n  // 新增${table.classComment}\n  create${simpleClassName}: async (data: ${simpleClassName}VO) => {\n    return await request.post({ url: `${baseURL}/create`, data })\n  },\n\n  // 修改${table.classComment}\n  update${simpleClassName}: async (data: ${simpleClassName}VO) => {\n    return await request.put({ url: `${baseURL}/update`, data })\n  },\n\n  // 删除${table.classComment}\n  delete${simpleClassName}: async (id: number) => {\n    return await request.delete({ url: `${baseURL}/delete?id=` + id })\n  },\n\n  // 导出${table.classComment} Excel\n  export${simpleClassName}: async (params) => {\n    return await request.download({ url: `${baseURL}/export-excel`, params })\n  },\n## 特殊：主子表专属逻辑\n#foreach ($subTable in $subTables)\n#set ($index = $foreach.count - 1)\n#set ($subSimpleClassName = $subSimpleClassNames.get($index))\n#set ($subPrimaryColumn = $subPrimaryColumns.get($index))##当前 primary 字段\n#set ($subJoinColumn = $subJoinColumns.get($index))##当前 join 字段\n#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写\n#set ($subSimpleClassName_strikeCase = $subSimpleClassName_strikeCases.get($index))\n#set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))\n#set ($subClassNameVar = $subClassNameVars.get($index))\n\n// ==================== 子表（$subTable.classComment） ====================\n## 情况一：MASTER_ERP 时，需要分查询页子表\n#if ( $table.templateType == 11 )\n\n  // 获得${subTable.classComment}分页\n  get${subSimpleClassName}Page: async (params) => {\n    return await request.get({ url: `${baseURL}/${subSimpleClassName_strikeCase}/page`, params })\n  },\n## 情况二：非 MASTER_ERP 时，需要列表查询子表\n#else\n  #if ( $subTable.subJoinMany )\n\n  // 获得${subTable.classComment}列表\n  get${subSimpleClassName}ListBy${SubJoinColumnName}: async (${subJoinColumn.javaField}) => {\n    return await request.get({ url: `${baseURL}/${subSimpleClassName_strikeCase}/list-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=` + ${subJoinColumn.javaField} })\n  },\n  #else\n\n  // 获得${subTable.classComment}\n  get${subSimpleClassName}By${SubJoinColumnName}: async (${subJoinColumn.javaField}) => {\n    return await request.get({ url: `${baseURL}/${subSimpleClassName_strikeCase}/get-by-${subJoinColumn_strikeCase}?${subJoinColumn.javaField}=` + ${subJoinColumn.javaField} })\n  },\n  #end\n#end\n## 特殊：MASTER_ERP 时，支持单个的新增、修改、删除操作\n#if ( $table.templateType == 11 )\n  // 新增${subTable.classComment}\n  create${subSimpleClassName}: async (data) => {\n    return await request.post({ url: `${baseURL}/${subSimpleClassName_strikeCase}/create`, data })\n  },\n\n  // 修改${subTable.classComment}\n  update${subSimpleClassName}: async (data) => {\n    return await request.put({ url: `${baseURL}/${subSimpleClassName_strikeCase}/update`, data })\n  },\n\n  // 删除${subTable.classComment}\n  delete${subSimpleClassName}: async (id: number) => {\n    return await request.delete({ url: `${baseURL}/${subSimpleClassName_strikeCase}/delete?id=` + id })\n  },\n\n  // 获得${subTable.classComment}\n  get${subSimpleClassName}: async (id: number) => {\n    return await request.get({ url: `${baseURL}/${subSimpleClassName_strikeCase}/get?id=` + id })\n  },\n#end\n#end\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/vue3/views/components/form_sub_erp.vue.vm",
    "content": "#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组\n#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex))\n#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段\n<template>\n  <Dialog :title=\"dialogTitle\" v-model=\"dialogVisible\">\n    <el-form\n      ref=\"formRef\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"100px\"\n      v-loading=\"formLoading\"\n    >\n#foreach($column in $subColumns)\n    #if ($column.createOperation || $column.updateOperation)\n        #set ($dictType = $column.dictType)\n        #set ($javaField = $column.javaField)\n        #set ($javaType = $column.javaType)\n        #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})\n        #set ($comment = $column.columnComment)\n        #set ($dictMethod = \"getDictOptions\")## 计算使用哪个 dict 字典方法\n        #if ($javaType == \"Integer\" || $javaType == \"Long\" || $javaType == \"Byte\" || $javaType == \"Short\")\n            #set ($dictMethod = \"getIntDictOptions\")\n        #elseif ($javaType == \"String\")\n            #set ($dictMethod = \"getStrDictOptions\")\n        #elseif ($javaType == \"Boolean\")\n            #set ($dictMethod = \"getBoolDictOptions\")\n        #end\n        #if ( $column.id == $subJoinColumn.id) ## 特殊：忽略主子表的 join 字段，不用填写\n        #elseif ($column.htmlType == \"input\" && !$column.primaryKey)## 忽略主键，不用在表单里\n      <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n        <el-input v-model=\"formData.${javaField}\" placeholder=\"请输入${comment}\" />\n      </el-form-item>\n        #elseif($column.htmlType == \"imageUpload\")## 图片上传\n      <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n        <UploadImg v-model=\"formData.${javaField}\" />\n      </el-form-item>\n        #elseif($column.htmlType == \"fileUpload\")## 文件上传\n      <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n        <UploadFile v-model=\"formData.${javaField}\" />\n      </el-form-item>\n        #elseif($column.htmlType == \"editor\")## 文本编辑器\n      <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n        <Editor v-model=\"formData.${javaField}\" height=\"150px\" />\n      </el-form-item>\n        #elseif($column.htmlType == \"select\")## 下拉框\n      <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n        <el-select v-model=\"formData.${javaField}\" placeholder=\"请选择${comment}\">\n                #if (\"\" != $dictType)## 有数据字典\n          <el-option\n            v-for=\"dict in $dictMethod(DICT_TYPE.$dictType.toUpperCase())\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n                #else##没数据字典\n          <el-option label=\"请选择字典生成\" value=\"\" />\n                #end\n        </el-select>\n      </el-form-item>\n        #elseif($column.htmlType == \"checkbox\")## 多选框\n      <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n        <el-checkbox-group v-model=\"formData.${javaField}\">\n                #if (\"\" != $dictType)## 有数据字典\n          <el-checkbox\n            v-for=\"dict in $dictMethod(DICT_TYPE.$dictType.toUpperCase())\"\n            :key=\"dict.value\"\n            :label=\"dict.value\"\n          >\n            {{ dict.label }}\n          </el-checkbox>\n                #else##没数据字典\n          <el-checkbox>请选择字典生成</el-checkbox>\n                #end\n        </el-checkbox-group>\n      </el-form-item>\n        #elseif($column.htmlType == \"radio\")## 单选框\n      <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n        <el-radio-group v-model=\"formData.${javaField}\">\n                #if (\"\" != $dictType)## 有数据字典\n          <el-radio\n            v-for=\"dict in $dictMethod(DICT_TYPE.$dictType.toUpperCase())\"\n            :key=\"dict.value\"\n            :label=\"dict.value\"\n          >\n            {{ dict.label }}\n          </el-radio>\n                #else##没数据字典\n          <el-radio label=\"1\">请选择字典生成</el-radio>\n                #end\n        </el-radio-group>\n      </el-form-item>\n        #elseif($column.htmlType == \"datetime\")## 时间框\n      <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n        <el-date-picker\n          v-model=\"formData.${javaField}\"\n          type=\"date\"\n          value-format=\"x\"\n          placeholder=\"选择${comment}\"\n        />\n      </el-form-item>\n        #elseif($column.htmlType == \"textarea\")## 文本框\n      <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n        <el-input v-model=\"formData.${javaField}\" type=\"textarea\" placeholder=\"请输入${comment}\" />\n      </el-form-item>\n        #end\n    #end\n#end\n    </el-form>\n    <template #footer>\n      <el-button @click=\"submitForm\" type=\"primary\" :disabled=\"formLoading\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script setup lang=\"ts\">\nimport { getIntDictOptions, getStrDictOptions, getBoolDictOptions, DICT_TYPE } from '@/utils/dict'\nimport { ${simpleClassName}Api } from '@/api/${table.moduleName}/${table.businessName}'\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n#foreach ($column in $subColumns)\n    #if ($column.createOperation || $column.updateOperation)\n      #if ($column.htmlType == \"checkbox\")\n  $column.javaField: [],\n      #else\n  $column.javaField: undefined,\n      #end\n    #end\n#end\n})\nconst formRules = reactive({\n#foreach ($column in $subColumns)\n    #if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键\n        #set($comment=$column.columnComment)\n  $column.javaField: [{ required: true, message: '${comment}不能为空', trigger: #if($column.htmlType == 'select')'change'#else'blur'#end }],\n    #end\n#end\n})\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number, ${subJoinColumn.javaField}: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  formData.value.${subJoinColumn.javaField} = ${subJoinColumn.javaField}\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await ${simpleClassName}Api.get${subSimpleClassName}(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  await formRef.value.validate()\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value\n    if (formType.value === 'create') {\n      await ${simpleClassName}Api.create${subSimpleClassName}(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await ${simpleClassName}Api.update${subSimpleClassName}(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n#foreach ($column in $subColumns)\n  #if ($column.createOperation || $column.updateOperation)\n      #if ($column.htmlType == \"checkbox\")\n    $column.javaField: [],\n      #else\n    $column.javaField: undefined,\n      #end\n  #end\n#end\n  }\n  formRef.value?.resetFields()\n}\n</script>"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/vue3/views/components/form_sub_inner.vue.vm",
    "content": "## 主表的 normal 和 inner 使用相同的 form 表单\n#parse(\"codegen/vue3/views/components/form_sub_normal.vue.vm\")"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/vue3/views/components/form_sub_normal.vue.vm",
    "content": "#set ($subTable = $subTables.get($subIndex))##当前表\n#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组\n#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段\n#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex))\n#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段\n#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写\n<template>\n#if ( $subTable.subJoinMany )## 情况一：一对多，table + form\n  <el-form\n    ref=\"formRef\"\n    :model=\"formData\"\n    :rules=\"formRules\"\n    v-loading=\"formLoading\"\n    label-width=\"0px\"\n    :inline-message=\"true\"\n  >\n    <el-table :data=\"formData\" class=\"-mt-10px\">\n      <el-table-column label=\"序号\" type=\"index\" width=\"100\" />\n#foreach($column in $subColumns)\n    #if ($column.createOperation || $column.updateOperation)\n        #set ($dictType = $column.dictType)\n        #set ($javaField = $column.javaField)\n        #set ($javaType = $column.javaType)\n        #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})\n        #set ($comment = $column.columnComment)\n        #set ($dictMethod = \"getDictOptions\")## 计算使用哪个 dict 字典方法\n        #if ($javaType == \"Integer\" || $javaType == \"Long\" || $javaType == \"Byte\" || $javaType == \"Short\")\n            #set ($dictMethod = \"getIntDictOptions\")\n        #elseif ($javaType == \"String\")\n            #set ($dictMethod = \"getStrDictOptions\")\n        #elseif ($javaType == \"Boolean\")\n            #set ($dictMethod = \"getBoolDictOptions\")\n        #end\n        #if ( $column.id == $subJoinColumn.id) ## 特殊：忽略主子表的 join 字段，不用填写\n        #elseif ($column.htmlType == \"input\" && !$column.primaryKey)## 忽略主键，不用在表单里\n      <el-table-column label=\"${comment}\" min-width=\"150\">\n        <template #default=\"{ row, $index }\">\n          <el-form-item :prop=\"`${$index}.${javaField}`\" :rules=\"formRules.${javaField}\" class=\"mb-0px!\">\n            <el-input v-model=\"row.${javaField}\" placeholder=\"请输入${comment}\" />\n          </el-form-item>\n        </template>\n      </el-table-column>\n        #elseif($column.htmlType == \"imageUpload\")## 图片上传\n      <el-table-column label=\"${comment}\" min-width=\"200\">\n        <template #default=\"{ row, $index }\">\n          <el-form-item :prop=\"`${$index}.${javaField}`\" :rules=\"formRules.${javaField}\" class=\"mb-0px!\">\n            <UploadImg v-model=\"row.${javaField}\" />\n          </el-form-item>\n        </template>\n      </el-table-column>\n        #elseif($column.htmlType == \"fileUpload\")## 文件上传\n      <el-table-column label=\"${comment}\" min-width=\"200\">\n        <template #default=\"{ row, $index }\">\n          <el-form-item :prop=\"`${$index}.${javaField}`\" :rules=\"formRules.${javaField}\" class=\"mb-0px!\">\n            <UploadFile v-model=\"row.${javaField}\" />\n          </el-form-item>\n        </template>\n      </el-table-column>\n        #elseif($column.htmlType == \"editor\")## 文本编辑器\n      <el-table-column label=\"${comment}\" min-width=\"400\">\n        <template #default=\"{ row, $index }\">\n          <el-form-item :prop=\"`${$index}.${javaField}`\" :rules=\"formRules.${javaField}\" class=\"mb-0px!\">\n            <Editor v-model=\"row.${javaField}\" height=\"150px\" />\n          </el-form-item>\n        </template>\n      </el-table-column>\n        #elseif($column.htmlType == \"select\")## 下拉框\n      <el-table-column label=\"${comment}\" min-width=\"150\">\n        <template #default=\"{ row, $index }\">\n          <el-form-item :prop=\"`${$index}.${javaField}`\" :rules=\"formRules.${javaField}\" class=\"mb-0px!\">\n            <el-select v-model=\"row.${javaField}\" placeholder=\"请选择${comment}\">\n              #if (\"\" != $dictType)## 有数据字典\n                <el-option\n                  v-for=\"dict in $dictMethod(DICT_TYPE.$dictType.toUpperCase())\"\n                  :key=\"dict.value\"\n                  :label=\"dict.label\"\n                  :value=\"dict.value\"\n                />\n              #else##没数据字典\n                <el-option label=\"请选择字典生成\" value=\"\" />\n              #end\n            </el-select>\n          </el-form-item>\n        </template>\n      </el-table-column>\n        #elseif($column.htmlType == \"checkbox\")## 多选框\n      <el-table-column label=\"${comment}\" min-width=\"150\">\n        <template #default=\"{ row, $index }\">\n          <el-form-item :prop=\"`${$index}.${javaField}`\" :rules=\"formRules.${javaField}\" class=\"mb-0px!\">\n            <el-checkbox-group v-model=\"row.${javaField}\">\n              #if (\"\" != $dictType)## 有数据字典\n                <el-checkbox\n                  v-for=\"dict in $dictMethod(DICT_TYPE.$dictType.toUpperCase())\"\n                  :key=\"dict.value\"\n                  :label=\"dict.value\"\n                >\n                  {{ dict.label }}\n                </el-checkbox>\n              #else##没数据字典\n                <el-checkbox>请选择字典生成</el-checkbox>\n              #end\n            </el-checkbox-group>\n          </el-form-item>\n        </template>\n      </el-table-column>\n        #elseif($column.htmlType == \"radio\")## 单选框\n      <el-table-column label=\"${comment}\" min-width=\"150\">\n        <template #default=\"{ row, $index }\">\n          <el-form-item :prop=\"`${$index}.${javaField}`\" :rules=\"formRules.${javaField}\" class=\"mb-0px!\">\n            <el-radio-group v-model=\"row.${javaField}\">\n              #if (\"\" != $dictType)## 有数据字典\n                <el-radio\n                  v-for=\"dict in $dictMethod(DICT_TYPE.$dictType.toUpperCase())\"\n                  :key=\"dict.value\"\n                  :label=\"dict.value\"\n                >\n                  {{ dict.label }}\n                </el-radio>\n              #else##没数据字典\n                <el-radio label=\"1\">请选择字典生成</el-radio>\n              #end\n            </el-radio-group>\n          </el-form-item>\n        </template>\n      </el-table-column>\n        #elseif($column.htmlType == \"datetime\")## 时间框\n      <el-table-column label=\"${comment}\" min-width=\"150\">\n        <template #default=\"{ row, $index }\">\n          <el-form-item :prop=\"`${$index}.${javaField}`\" :rules=\"formRules.${javaField}\" class=\"mb-0px!\">\n            <el-date-picker\n              v-model=\"row.${javaField}\"\n              type=\"date\"\n              value-format=\"x\"\n              placeholder=\"选择${comment}\"\n            />\n          </el-form-item>\n        </template>\n      </el-table-column>\n        #elseif($column.htmlType == \"textarea\")## 文本框\n      <el-table-column label=\"${comment}\" min-width=\"200\">\n        <template #default=\"{ row, $index }\">\n          <el-form-item :prop=\"`${$index}.${javaField}`\" :rules=\"formRules.${javaField}\" class=\"mb-0px!\">\n            <el-input v-model=\"row.${javaField}\" type=\"textarea\" placeholder=\"请输入${comment}\" />\n          </el-form-item>\n        </template>\n      </el-table-column>\n        #end\n    #end\n#end\n      <el-table-column align=\"center\" fixed=\"right\" label=\"操作\" width=\"60\">\n        <template #default=\"{ $index }\">\n          <el-button @click=\"handleDelete($index)\" link>—</el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n  </el-form>\n  <el-row justify=\"center\" class=\"mt-3\">\n    <el-button @click=\"handleAdd\" round>+ 添加${subTable.classComment}</el-button>\n  </el-row>\n#else## 情况二：一对一，form\n  <el-form\n    ref=\"formRef\"\n    :model=\"formData\"\n    :rules=\"formRules\"\n    label-width=\"100px\"\n    v-loading=\"formLoading\"\n  >\n#foreach($column in $subColumns)\n  #if ($column.createOperation || $column.updateOperation)\n  #set ($dictType = $column.dictType)\n      #set ($javaField = $column.javaField)\n      #set ($javaType = $column.javaType)\n      #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})\n      #set ($comment = $column.columnComment)\n      #set ($dictMethod = \"getDictOptions\")## 计算使用哪个 dict 字典方法\n      #if ($javaType == \"Integer\" || $javaType == \"Long\" || $javaType == \"Byte\" || $javaType == \"Short\")\n        #set ($dictMethod = \"getIntDictOptions\")\n      #elseif ($javaType == \"String\")\n          #set ($dictMethod = \"getStrDictOptions\")\n      #elseif ($javaType == \"Boolean\")\n          #set ($dictMethod = \"getBoolDictOptions\")\n      #end\n      #if ( $column.id == $subJoinColumn.id) ## 特殊：忽略主子表的 join 字段，不用填写\n      #elseif ($column.htmlType == \"input\" && !$column.primaryKey)## 忽略主键，不用在表单里\n    <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n      <el-input v-model=\"formData.${javaField}\" placeholder=\"请输入${comment}\" />\n    </el-form-item>\n      #elseif($column.htmlType == \"imageUpload\")## 图片上传\n    <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n      <UploadImg v-model=\"formData.${javaField}\" />\n    </el-form-item>\n      #elseif($column.htmlType == \"fileUpload\")## 文件上传\n    <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n      <UploadFile v-model=\"formData.${javaField}\" />\n    </el-form-item>\n      #elseif($column.htmlType == \"editor\")## 文本编辑器\n    <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n      <Editor v-model=\"formData.${javaField}\" height=\"150px\" />\n    </el-form-item>\n      #elseif($column.htmlType == \"select\")## 下拉框\n    <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n      <el-select v-model=\"formData.${javaField}\" placeholder=\"请选择${comment}\">\n              #if (\"\" != $dictType)## 有数据字典\n        <el-option\n          v-for=\"dict in $dictMethod(DICT_TYPE.$dictType.toUpperCase())\"\n          :key=\"dict.value\"\n          :label=\"dict.label\"\n          :value=\"dict.value\"\n        />\n              #else##没数据字典\n        <el-option label=\"请选择字典生成\" value=\"\" />\n              #end\n      </el-select>\n    </el-form-item>\n      #elseif($column.htmlType == \"checkbox\")## 多选框\n    <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n      <el-checkbox-group v-model=\"formData.${javaField}\">\n              #if (\"\" != $dictType)## 有数据字典\n        <el-checkbox\n          v-for=\"dict in $dictMethod(DICT_TYPE.$dictType.toUpperCase())\"\n          :key=\"dict.value\"\n          :label=\"dict.value\"\n        >\n          {{ dict.label }}\n        </el-checkbox>\n              #else##没数据字典\n        <el-checkbox>请选择字典生成</el-checkbox>\n              #end\n      </el-checkbox-group>\n    </el-form-item>\n      #elseif($column.htmlType == \"radio\")## 单选框\n    <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n      <el-radio-group v-model=\"formData.${javaField}\">\n              #if (\"\" != $dictType)## 有数据字典\n        <el-radio\n          v-for=\"dict in $dictMethod(DICT_TYPE.$dictType.toUpperCase())\"\n          :key=\"dict.value\"\n          :label=\"dict.value\"\n          >\n          {{ dict.label }}\n        </el-radio>\n              #else##没数据字典\n        <el-radio label=\"1\">请选择字典生成</el-radio>\n              #end\n      </el-radio-group>\n    </el-form-item>\n      #elseif($column.htmlType == \"datetime\")## 时间框\n    <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n      <el-date-picker\n        v-model=\"formData.${javaField}\"\n        type=\"date\"\n        value-format=\"x\"\n        placeholder=\"选择${comment}\"\n      />\n    </el-form-item>\n      #elseif($column.htmlType == \"textarea\")## 文本框\n    <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n      <el-input v-model=\"formData.${javaField}\" type=\"textarea\" placeholder=\"请输入${comment}\" />\n    </el-form-item>\n      #end\n  #end\n#end\n  </el-form>\n#end\n</template>\n<script setup lang=\"ts\">\nimport { getIntDictOptions, getStrDictOptions, getBoolDictOptions, DICT_TYPE } from '@/utils/dict'\nimport { ${simpleClassName}Api } from '@/api/${table.moduleName}/${table.businessName}'\n\nconst props = defineProps<{\n  ${subJoinColumn.javaField}: undefined // ${subJoinColumn.columnComment}（主表的关联字段）\n}>()\nconst formLoading = ref(false) // 表单的加载中\nconst formData = ref([])\nconst formRules = reactive({\n#foreach ($column in $subColumns)\n    #if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键\n        #set($comment=$column.columnComment)\n  $column.javaField: [{ required: true, message: '${comment}不能为空', trigger: #if($column.htmlType == 'select')'change'#else'blur'#end }],\n    #end\n#end\n})\nconst formRef = ref() // 表单 Ref\n\n/** 监听主表的关联字段的变化，加载对应的子表数据 */\nwatch(\n  () => props.${subJoinColumn.javaField},\n  async (val) => {\n    // 1. 重置表单\n#if ( $subTable.subJoinMany )\n    formData.value = []\n#else\n    formData.value = {\n    #foreach ($column in $subColumns)\n      #if ($column.createOperation || $column.updateOperation)\n        #if ($column.htmlType == \"checkbox\")\n      $column.javaField: [],\n        #else\n      $column.javaField: undefined,\n        #end\n      #end\n    #end\n    }\n#end\n    // 2. val 非空，则加载数据\n    if (!val) {\n      return;\n    }\n    try {\n      formLoading.value = true\n#if ( $subTable.subJoinMany )\n      formData.value = await ${simpleClassName}Api.get${subSimpleClassName}ListBy${SubJoinColumnName}(val)\n#else\n      const data = await ${simpleClassName}Api.get${subSimpleClassName}By${SubJoinColumnName}(val)\n      if (!data) {\n        return\n      }\n      formData.value = data\n#end\n    } finally {\n      formLoading.value = false\n    }\n  },\n  { immediate: true }\n)\n#if ( $subTable.subJoinMany )\n\n/** 新增按钮操作 */\nconst handleAdd = () => {\n  const row = {\n#foreach ($column in $subColumns)\n    #if ($column.createOperation || $column.updateOperation)\n      #if ($column.htmlType == \"checkbox\")\n    $column.javaField: [],\n      #else\n    $column.javaField: undefined,\n      #end\n  #end\n#end\n  }\n  row.${subJoinColumn.javaField} = props.${subJoinColumn.javaField}\n  formData.value.push(row)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = (index) => {\n  formData.value.splice(index, 1)\n}\n#end\n\n/** 表单校验 */\nconst validate = () => {\n  return formRef.value.validate()\n}\n\n/** 表单值 */\nconst getData = () => {\n  return formData.value\n}\n\ndefineExpose({ validate, getData })\n</script>"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/vue3/views/components/list_sub_erp.vue.vm",
    "content": "#set ($subTable = $subTables.get($subIndex))##当前表\n#set ($subColumns = $subColumnsList.get($subIndex))##当前字段数组\n#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段\n#set ($subSimpleClassName = $subSimpleClassNames.get($subIndex))\n#set ($subJoinColumn = $subJoinColumns.get($subIndex))##当前 join 字段\n#set ($SubJoinColumnName = $subJoinColumn.javaField.substring(0,1).toUpperCase() + ${subJoinColumn.javaField.substring(1)})##首字母大写\n<template>\n  <!-- 列表 -->\n  <ContentWrap>\n#if ($table.templateType == 11)\n    <el-button\n      type=\"primary\"\n      plain\n      @click=\"openForm('create')\"\n      v-hasPermi=\"['${permissionPrefix}:create']\"\n    >\n      <Icon icon=\"ep:plus\" class=\"mr-5px\" /> 新增\n    </el-button>\n#end\n    <el-table v-loading=\"loading\" :data=\"list\" :stripe=\"true\" :show-overflow-tooltip=\"true\">\n      #foreach($column in $subColumns)\n      #if ($column.listOperationResult)\n        #set ($dictType=$column.dictType)\n        #set ($javaField = $column.javaField)\n        #set ($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})\n        #set ($comment=$column.columnComment)\n        #if ( $column.id == $subJoinColumn.id) ## 特殊：忽略主子表的 join 字段，不用填写\n        #elseif ($column.javaType == \"LocalDateTime\")## 时间类型\n      <el-table-column\n        label=\"${comment}\"\n        align=\"center\"\n        prop=\"${javaField}\"\n        :formatter=\"dateFormatter\"\n        width=\"180px\"\n      />\n        #elseif($column.dictType && \"\" != $column.dictType)## 数据字典\n      <el-table-column label=\"${comment}\" align=\"center\" prop=\"${javaField}\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.$dictType.toUpperCase()\" :value=\"scope.row.${column.javaField}\" />\n        </template>\n      </el-table-column>\n        #else\n      <el-table-column label=\"${comment}\" align=\"center\" prop=\"${javaField}\" />\n        #end\n      #end\n    #end\n    #if ($table.templateType == 11)\n      <el-table-column label=\"操作\" align=\"center\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n            v-hasPermi=\"['${permissionPrefix}:update']\"\n          >\n            编辑\n          </el-button>\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n            v-hasPermi=\"['${permissionPrefix}:delete']\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    #end\n    </el-table>\n    #if ($table.templateType == 11)\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n    #end\n  </ContentWrap>\n#if ($table.templateType == 11)\n    <!-- 表单弹窗：添加/修改 -->\n    <${subSimpleClassName}Form ref=\"formRef\" @success=\"getList\" />\n#end\n</template>\n<script setup lang=\"ts\">\nimport { getIntDictOptions, getStrDictOptions, getBoolDictOptions, DICT_TYPE } from '@/utils/dict'\nimport { dateFormatter } from '@/utils/formatTime'\nimport { ${simpleClassName}Api } from '@/api/${table.moduleName}/${table.businessName}'\n#if ($table.templateType == 11)\nimport ${subSimpleClassName}Form from './${subSimpleClassName}Form.vue'\n#end\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst props = defineProps<{\n  ${subJoinColumn.javaField}?: number // ${subJoinColumn.columnComment}（主表的关联字段）\n}>()\nconst loading = ref(false) // 列表的加载中\nconst list = ref([]) // 列表的数据\n#if ($table.templateType == 11)\nconst total = ref(0) // 列表的总页数\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  ${subJoinColumn.javaField}: undefined as unknown\n})\n\n/** 监听主表的关联字段的变化，加载对应的子表数据 */\nwatch(\n  () => props.${subJoinColumn.javaField},\n  (val: number) => {\n    if (!val) {\n      return\n    }\n    queryParams.${subJoinColumn.javaField} = val\n    handleQuery()\n  },\n    { immediate: true, deep: true }\n)\n#end\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n#if ($table.templateType == 11)\n    const data = await ${simpleClassName}Api.get${subSimpleClassName}Page(queryParams)\n    list.value = data.list\n    total.value = data.total\n#else\n  #if ( $subTable.subJoinMany )\n    list.value = await ${simpleClassName}Api.get${subSimpleClassName}ListBy${SubJoinColumnName}(props.${subJoinColumn.javaField})\n  #else\n    const data = await ${simpleClassName}Api.get${subSimpleClassName}By${SubJoinColumnName}(props.${subJoinColumn.javaField})\n    if (!data) {\n      return\n    }\n    list.value.push(data)\n  #end\n#end\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n#if ($table.templateType == 11)\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  if (!props.${subJoinColumn.javaField}) {\n    message.error('请选择一个${table.classComment}')\n    return\n  }\n  formRef.value.open(type, id, props.${subJoinColumn.javaField})\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await ${simpleClassName}Api.delete${subSimpleClassName}(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n#end\n#if ($table.templateType != 11)\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n#end\n</script>"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/vue3/views/components/list_sub_inner.vue.vm",
    "content": "## 子表的 erp 和 inner 使用相似的 list 列表，差异主要两点：\n## 1）inner 使用 list 不分页，erp 使用 page 分页\n## 2）erp 支持单个子表的新增、修改、删除，inner 不支持\n#parse(\"codegen/vue3/views/components/list_sub_erp.vue.vm\")"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/vue3/views/form.vue.vm",
    "content": "<template>\n  <Dialog :title=\"dialogTitle\" v-model=\"dialogVisible\">\n    <el-form\n      ref=\"formRef\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"100px\"\n      v-loading=\"formLoading\"\n    >\n#foreach($column in $columns)\n    #if ($column.createOperation || $column.updateOperation)\n        #set ($dictType = $column.dictType)\n        #set ($javaField = $column.javaField)\n        #set ($javaType = $column.javaType)\n        #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})\n        #set ($comment = $column.columnComment)\n        #set ($dictMethod = \"getDictOptions\")## 计算使用哪个 dict 字典方法\n        #if ($javaType == \"Integer\" || $javaType == \"Long\" || $javaType == \"Byte\" || $javaType == \"Short\")\n            #set ($dictMethod = \"getIntDictOptions\")\n        #elseif ($javaType == \"String\")\n            #set ($dictMethod = \"getStrDictOptions\")\n        #elseif ($javaType == \"Boolean\")\n            #set ($dictMethod = \"getBoolDictOptions\")\n        #end\n        #if ( $table.templateType == 2 && $column.id == $treeParentColumn.id )\n      <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n        <el-tree-select\n          v-model=\"formData.${javaField}\"\n          :data=\"${classNameVar}Tree\"\n          #if ($treeNameColumn.javaField == \"name\")\n          :props=\"defaultProps\"\n          #else\n          :props=\"{...defaultProps, label: '$treeNameColumn.javaField'}\"\n          #end\n          check-strictly\n          default-expand-all\n          placeholder=\"请选择${comment}\"\n        />\n      </el-form-item>\n        #elseif ($column.htmlType == \"input\" && !$column.primaryKey)## 忽略主键，不用在表单里\n      <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n        <el-input v-model=\"formData.${javaField}\" placeholder=\"请输入${comment}\" />\n      </el-form-item>\n        #elseif($column.htmlType == \"imageUpload\")## 图片上传\n      <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n        <UploadImg v-model=\"formData.${javaField}\" />\n      </el-form-item>\n        #elseif($column.htmlType == \"fileUpload\")## 文件上传\n      <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n        <UploadFile v-model=\"formData.${javaField}\" />\n      </el-form-item>\n        #elseif($column.htmlType == \"editor\")## 文本编辑器\n      <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n        <Editor v-model=\"formData.${javaField}\" height=\"150px\" />\n      </el-form-item>\n        #elseif($column.htmlType == \"select\")## 下拉框\n      <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n        <el-select v-model=\"formData.${javaField}\" placeholder=\"请选择${comment}\">\n                #if (\"\" != $dictType)## 有数据字典\n          <el-option\n            v-for=\"dict in $dictMethod(DICT_TYPE.$dictType.toUpperCase())\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n                #else##没数据字典\n          <el-option label=\"请选择字典生成\" value=\"\" />\n                #end\n        </el-select>\n      </el-form-item>\n        #elseif($column.htmlType == \"checkbox\")## 多选框\n      <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n        <el-checkbox-group v-model=\"formData.${javaField}\">\n                #if (\"\" != $dictType)## 有数据字典\n          <el-checkbox\n            v-for=\"dict in $dictMethod(DICT_TYPE.$dictType.toUpperCase())\"\n            :key=\"dict.value\"\n            :label=\"dict.value\"\n          >\n            {{ dict.label }}\n          </el-checkbox>\n                #else##没数据字典\n          <el-checkbox>请选择字典生成</el-checkbox>\n                #end\n        </el-checkbox-group>\n      </el-form-item>\n        #elseif($column.htmlType == \"radio\")## 单选框\n      <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n        <el-radio-group v-model=\"formData.${javaField}\">\n                #if (\"\" != $dictType)## 有数据字典\n          <el-radio\n            v-for=\"dict in $dictMethod(DICT_TYPE.$dictType.toUpperCase())\"\n            :key=\"dict.value\"\n            :label=\"dict.value\"\n          >\n            {{ dict.label }}\n          </el-radio>\n                #else##没数据字典\n          <el-radio label=\"1\">请选择字典生成</el-radio>\n                #end\n        </el-radio-group>\n      </el-form-item>\n        #elseif($column.htmlType == \"datetime\")## 时间框\n      <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n        <el-date-picker\n          v-model=\"formData.${javaField}\"\n          type=\"date\"\n          value-format=\"x\"\n          placeholder=\"选择${comment}\"\n        />\n      </el-form-item>\n        #elseif($column.htmlType == \"textarea\")## 文本框\n      <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n        <el-input v-model=\"formData.${javaField}\" type=\"textarea\" placeholder=\"请输入${comment}\" />\n      </el-form-item>\n        #end\n    #end\n#end\n    </el-form>\n## 特殊：主子表专属逻辑\n#if ( $table.templateType == 10 || $table.templateType == 12 )\n    <!-- 子表的表单 -->\n    <el-tabs v-model=\"subTabsName\">\n    #foreach ($subTable in $subTables)\n      #set ($index = $foreach.count - 1)\n      #set ($subClassNameVar = $subClassNameVars.get($index))\n      #set ($subSimpleClassName = $subSimpleClassNames.get($index))\n      #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))\n      <el-tab-pane label=\"${subTable.classComment}\" name=\"$subClassNameVar\">\n        <${subSimpleClassName}Form ref=\"${subClassNameVar}FormRef\" :${subJoinColumn_strikeCase}=\"formData.id\" />\n      </el-tab-pane>\n    #end\n    </el-tabs>\n#end\n    <template #footer>\n      <el-button @click=\"submitForm\" type=\"primary\" :disabled=\"formLoading\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script setup lang=\"ts\">\nimport { getIntDictOptions, getStrDictOptions, getBoolDictOptions, DICT_TYPE } from '@/utils/dict'\nimport { ${simpleClassName}Api, ${simpleClassName}VO } from '@/api/${table.moduleName}/${table.businessName}'\n## 特殊：树表专属逻辑\n#if ( $table.templateType == 2 )\nimport { defaultProps, handleTree } from '@/utils/tree'\n#end\n## 特殊：主子表专属逻辑\n#if ( $table.templateType == 10 || $table.templateType == 12 )\n#foreach ($subSimpleClassName in $subSimpleClassNames)\nimport ${subSimpleClassName}Form from './components/${subSimpleClassName}Form.vue'\n#end\n#end\n\n/** ${table.classComment} 表单 */\ndefineOptions({ name: '${simpleClassName}Form' })\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n#foreach ($column in $columns)\n    #if ($column.createOperation || $column.updateOperation)\n      #if ($column.htmlType == \"checkbox\")\n  $column.javaField: [],\n      #else\n  $column.javaField: undefined,\n      #end\n    #end\n#end\n})\nconst formRules = reactive({\n#foreach ($column in $columns)\n    #if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键\n        #set($comment=$column.columnComment)\n  $column.javaField: [{ required: true, message: '${comment}不能为空', trigger: #if($column.htmlType == 'select')'change'#else'blur'#end }],\n    #end\n#end\n})\nconst formRef = ref() // 表单 Ref\n## 特殊：树表专属逻辑\n#if ( $table.templateType == 2 )\nconst ${classNameVar}Tree = ref() // 树形结构\n#end\n## 特殊：主子表专属逻辑\n#if ( $table.templateType == 10 || $table.templateType == 12 )\n#if ( $subTables && $subTables.size() > 0 )\n\n/** 子表的表单 */\nconst subTabsName = ref('$subClassNameVars.get(0)')\n#foreach ($subClassNameVar in $subClassNameVars)\nconst ${subClassNameVar}FormRef = ref()\n#end\n#end\n#end\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await ${simpleClassName}Api.get${simpleClassName}(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n## 特殊：树表专属逻辑\n#if ( $table.templateType == 2 )\n  await get${simpleClassName}Tree()\n#end\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  await formRef.value.validate()\n## 特殊：主子表专属逻辑\n#if ( $table.templateType == 10 || $table.templateType == 12 )\n#if ( $subTables && $subTables.size() > 0 )\n  // 校验子表单\n  #foreach ($subTable in $subTables)\n  #set ($index = $foreach.count - 1)\n  #set ($subClassNameVar = $subClassNameVars.get($index))\n  try {\n    await ${subClassNameVar}FormRef.value.validate()\n  } catch (e) {\n    subTabsName.value = '${subClassNameVar}'\n    return\n  }\n  #end\n#end\n#end\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as unknown as ${simpleClassName}VO\n## 特殊：主子表专属逻辑\n#if ( $table.templateType == 10 || $table.templateType == 12 )\n#if ( $subTables && $subTables.size() > 0 )\n    // 拼接子表的数据\n  #foreach ($subTable in $subTables)\n  #set ($index = $foreach.count - 1)\n  #set ($subClassNameVar = $subClassNameVars.get($index))\n    data.${subClassNameVar}#if ( $subTable.subJoinMany)s#end = ${subClassNameVar}FormRef.value.getData()\n  #end\n#end\n#end\n    if (formType.value === 'create') {\n      await ${simpleClassName}Api.create${simpleClassName}(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await ${simpleClassName}Api.update${simpleClassName}(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n#foreach ($column in $columns)\n  #if ($column.createOperation || $column.updateOperation)\n      #if ($column.htmlType == \"checkbox\")\n    $column.javaField: [],\n      #else\n    $column.javaField: undefined,\n      #end\n  #end\n#end\n  }\n  formRef.value?.resetFields()\n}\n## 特殊：树表专属逻辑\n#if ( $table.templateType == 2 )\n\n/** 获得${table.classComment}树 */\nconst get${simpleClassName}Tree = async () => {\n  ${classNameVar}Tree.value = []\n  const data = await ${simpleClassName}Api.get${simpleClassName}List()\n  const root: Tree = { id: 0, name: '顶级${table.classComment}', children: [] }\n  root.children = handleTree(data, 'id', '${treeParentColumn.javaField}')\n  ${classNameVar}Tree.value.push(root)\n}\n#end\n</script>"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/vue3/views/index.vue.vm",
    "content": "<template>\n  <ContentWrap>\n    <!-- 搜索工作栏 -->\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n    #foreach($column in $columns)\n        #if ($column.listOperation)\n            #set ($dictType = $column.dictType)\n            #set ($javaField = $column.javaField)\n            #set ($javaType = $column.javaType)\n            #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})\n            #set ($comment = $column.columnComment)\n            #set ($dictMethod = \"getDictOptions\")## 计算使用哪个 dict 字典方法\n            #if ($javaType == \"Integer\" || $javaType == \"Long\" || $javaType == \"Byte\" || $javaType == \"Short\")\n                #set ($dictMethod = \"getIntDictOptions\")\n            #elseif ($javaType == \"String\")\n                #set ($dictMethod = \"getStrDictOptions\")\n            #elseif ($javaType == \"Boolean\")\n                #set ($dictMethod = \"getBoolDictOptions\")\n            #end\n            #if ($column.htmlType == \"input\")\n      <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n        <el-input\n          v-model=\"queryParams.${javaField}\"\n          placeholder=\"请输入${comment}\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n            #elseif ($column.htmlType == \"select\" || $column.htmlType == \"radio\")\n      <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n        <el-select\n          v-model=\"queryParams.${javaField}\"\n          placeholder=\"请选择${comment}\"\n          clearable\n          class=\"!w-240px\"\n        >\n                #if (\"\" != $dictType)## 设置了 dictType 数据字典的情况\n          <el-option\n            v-for=\"dict in $dictMethod(DICT_TYPE.$dictType.toUpperCase())\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n                #else## 未设置 dictType 数据字典的情况\n          <el-option label=\"请选择字典生成\" value=\"\" />\n                #end\n        </el-select>\n      </el-form-item>\n    #elseif($column.htmlType == \"datetime\")\n      #if ($column.listOperationCondition != \"BETWEEN\")## 非范围\n      <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n        <el-date-picker\n          v-model=\"queryParams.${javaField}\"\n          value-format=\"YYYY-MM-DD\"\n          type=\"date\"\n          placeholder=\"选择${comment}\"\n          clearable\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      #else## 范围\n      <el-form-item label=\"${comment}\" prop=\"${javaField}\">\n        <el-date-picker\n          v-model=\"queryParams.${javaField}\"\n          value-format=\"YYYY-MM-DD HH:mm:ss\"\n          type=\"daterange\"\n          start-placeholder=\"开始日期\"\n          end-placeholder=\"结束日期\"\n          :default-time=\"[new Date('1 00:00:00'), new Date('1 23:59:59')]\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      #end\n    #end\n    #end\n    #end\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n        <el-button\n          type=\"primary\"\n          plain\n          @click=\"openForm('create')\"\n          v-hasPermi=\"['${permissionPrefix}:create']\"\n        >\n          <Icon icon=\"ep:plus\" class=\"mr-5px\" /> 新增\n        </el-button>\n        <el-button\n          type=\"success\"\n          plain\n          @click=\"handleExport\"\n          :loading=\"exportLoading\"\n          v-hasPermi=\"['${permissionPrefix}:export']\"\n        >\n          <Icon icon=\"ep:download\" class=\"mr-5px\" /> 导出\n        </el-button>\n## 特殊：树表专属逻辑\n#if ( $table.templateType == 2 )\n        <el-button type=\"danger\" plain @click=\"toggleExpandAll\">\n          <Icon icon=\"ep:sort\" class=\"mr-5px\" /> 展开/折叠\n        </el-button>\n#end\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n## 特殊：主子表专属逻辑\n#if ( $table.templateType == 11 && $subTables && $subTables.size() > 0 )\n    <el-table\n      v-loading=\"loading\"\n      :data=\"list\"\n      :stripe=\"true\"\n      :show-overflow-tooltip=\"true\"\n      highlight-current-row\n      @current-change=\"handleCurrentChange\"\n    >\n## 特殊：树表专属逻辑\n#elseif ( $table.templateType == 2 )\n    <el-table\n      v-loading=\"loading\"\n      :data=\"list\"\n      :stripe=\"true\"\n      :show-overflow-tooltip=\"true\"\n      row-key=\"id\"\n      :default-expand-all=\"isExpandAll\"\n      v-if=\"refreshTable\"\n    >\n#else\n    <el-table v-loading=\"loading\" :data=\"list\" :stripe=\"true\" :show-overflow-tooltip=\"true\">\n#end\n## 特殊：主子表专属逻辑\n#if ( $table.templateType == 12 && $subTables && $subTables.size() > 0 )\n      <!-- 子表的列表 -->\n      <el-table-column type=\"expand\">\n        <template #default=\"scope\">\n          <el-tabs model-value=\"$subClassNameVars.get(0)\">\n            #foreach ($subTable in $subTables)\n              #set ($index = $foreach.count - 1)\n              #set ($subClassNameVar = $subClassNameVars.get($index))\n              #set ($subSimpleClassName = $subSimpleClassNames.get($index))\n              #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))\n            <el-tab-pane label=\"${subTable.classComment}\" name=\"$subClassNameVar\">\n              <${subSimpleClassName}List :${subJoinColumn_strikeCase}=\"scope.row.id\" />\n            </el-tab-pane>\n            #end\n          </el-tabs>\n        </template>\n      </el-table-column>\n#end\n      #foreach($column in $columns)\n      #if ($column.listOperationResult)\n        #set ($dictType=$column.dictType)\n        #set ($javaField = $column.javaField)\n        #set ($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})\n        #set ($comment=$column.columnComment)\n        #if ($column.javaType == \"LocalDateTime\")## 时间类型\n      <el-table-column\n        label=\"${comment}\"\n        align=\"center\"\n        prop=\"${javaField}\"\n        :formatter=\"dateFormatter\"\n        width=\"180px\"\n      />\n        #elseif($column.dictType && \"\" != $column.dictType)## 数据字典\n      <el-table-column label=\"${comment}\" align=\"center\" prop=\"${javaField}\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.$dictType.toUpperCase()\" :value=\"scope.row.${column.javaField}\" />\n        </template>\n      </el-table-column>\n        #else\n      <el-table-column label=\"${comment}\" align=\"center\" prop=\"${javaField}\" />\n        #end\n      #end\n    #end\n      <el-table-column label=\"操作\" align=\"center\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n            v-hasPermi=\"['${permissionPrefix}:update']\"\n          >\n            编辑\n          </el-button>\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n            v-hasPermi=\"['${permissionPrefix}:delete']\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <${simpleClassName}Form ref=\"formRef\" @success=\"getList\" />\n## 特殊：主子表专属逻辑\n#if ( $table.templateType == 11 && $subTables && $subTables.size() > 0 )\n  <!-- 子表的列表 -->\n  <ContentWrap>\n    <el-tabs model-value=\"$subClassNameVars.get(0)\">\n      #foreach ($subTable in $subTables)\n        #set ($index = $foreach.count - 1)\n        #set ($subClassNameVar = $subClassNameVars.get($index))\n        #set ($subSimpleClassName = $subSimpleClassNames.get($index))\n        #set ($subJoinColumn_strikeCase = $subJoinColumn_strikeCases.get($index))\n      <el-tab-pane label=\"${subTable.classComment}\" name=\"$subClassNameVar\">\n        <${subSimpleClassName}List :${subJoinColumn_strikeCase}=\"currentRow.id\" />\n      </el-tab-pane>\n      #end\n    </el-tabs>\n  </ContentWrap>\n#end\n</template>\n\n<script setup lang=\"ts\">\nimport { getIntDictOptions, getStrDictOptions, getBoolDictOptions, DICT_TYPE } from '@/utils/dict'\nimport { dateFormatter } from '@/utils/formatTime'\n## 特殊：树表专属逻辑\n#if ( $table.templateType == 2 )\nimport { handleTree } from '@/utils/tree'\n#end\nimport download from '@/utils/download'\nimport { ${simpleClassName}Api, ${simpleClassName}VO } from '@/api/${table.moduleName}/${table.businessName}'\nimport ${simpleClassName}Form from './${simpleClassName}Form.vue'\n## 特殊：主子表专属逻辑\n#if ( $table.templateType != 10 )\n#foreach ($subSimpleClassName in $subSimpleClassNames)\nimport ${subSimpleClassName}List from './components/${subSimpleClassName}List.vue'\n#end\n#end\n\n/** ${table.classComment} 列表 */\ndefineOptions({ name: '${table.className}' })\n\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst list = ref<${simpleClassName}VO[]>([]) // 列表的数据\n## 特殊：树表专属逻辑（树不需要分页接口）\n#if ( $table.templateType != 2 )\nconst total = ref(0) // 列表的总页数\n#end\nconst queryParams = reactive({\n## 特殊：树表专属逻辑（树不需要分页接口）\n#if ( $table.templateType != 2 )\n  pageNo: 1,\n  pageSize: 10,\n#end\n  #foreach ($column in $columns)\n    #if ($column.listOperation)\n      #if ($column.listOperationCondition != 'BETWEEN')\n  $column.javaField: undefined,\n  #end\n      #if ($column.htmlType == \"datetime\" || $column.listOperationCondition == \"BETWEEN\")\n  $column.javaField: [],\n      #end\n    #end\n  #end\n})\nconst queryFormRef = ref() // 搜索的表单\nconst exportLoading = ref(false) // 导出的加载中\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n## 特殊：树表专属逻辑（树不需要分页接口）\n  #if ( $table.templateType == 2 )\n    const data = await ${simpleClassName}Api.get${simpleClassName}List(queryParams)\n    list.value = handleTree(data, 'id', '${treeParentColumn.javaField}')\n  #else\n    const data = await ${simpleClassName}Api.get${simpleClassName}Page(queryParams)\n    list.value = data.list\n    total.value = data.total\n  #end\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await ${simpleClassName}Api.delete${simpleClassName}(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 导出按钮操作 */\nconst handleExport = async () => {\n  try {\n    // 导出的二次确认\n    await message.exportConfirm()\n    // 发起导出\n    exportLoading.value = true\n    const data = await ${simpleClassName}Api.export${simpleClassName}(queryParams)\n    download.excel(data, '${table.classComment}.xls')\n  } catch {\n  } finally {\n    exportLoading.value = false\n  }\n}\n## 特殊：主子表专属逻辑\n#if ( $table.templateType == 11 )\n\n/** 选中行操作 */\nconst currentRow = ref({}) // 选中行\nconst handleCurrentChange = (row) => {\n  currentRow.value = row\n}\n#end\n## 特殊：树表专属逻辑\n#if ( $table.templateType == 2 )\n\n/** 展开/折叠操作 */\nconst isExpandAll = ref(true) // 是否展开，默认全部展开\nconst refreshTable = ref(true) // 重新渲染表格状态\nconst toggleExpandAll = async () => {\n  refreshTable.value = false\n  isExpandAll.value = !isExpandAll.value\n  await nextTick()\n  refreshTable.value = true\n}\n#end\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/vue3_schema/api/api.ts.vm",
    "content": "import request from '@/config/axios'\n#set ($baseURL = \"/${table.moduleName}/${simpleClassName_strikeCase}\")\n\nexport interface ${simpleClassName}VO {\n    #foreach ($column in $columns)\n        #if ($column.createOperation || $column.updateOperation)\n            #if(${column.javaType.toLowerCase()} == \"long\" || ${column.javaType.toLowerCase()} == \"integer\" || ${column.javaType.toLowerCase()} == \"double\" || ${column.javaType.toLowerCase()} == \"bigdecimal\")\n                    ${column.javaField}: number\n            #elseif(${column.javaType.toLowerCase()} == \"date\" || ${column.javaType.toLowerCase()} == \"localdatetime\")\n                    ${column.javaField}: Date\n            #else\n                    ${column.javaField}: ${column.javaType.toLowerCase()}\n            #end\n        #end\n    #end\n}\n\n// 查询${table.classComment}列表\nexport const get${simpleClassName}Page = async (params) => {\n  return await request.get({ url: '${baseURL}/page', params })\n}\n\n// 查询${table.classComment}详情\nexport const get${simpleClassName} = async (id: number) => {\n  return await request.get({ url: '${baseURL}/get?id=' + id })\n}\n\n// 新增${table.classComment}\nexport const create${simpleClassName} = async (data: ${simpleClassName}VO) => {\n  return await request.post({ url: '${baseURL}/create', data })\n}\n\n// 修改${table.classComment}\nexport const update${simpleClassName} = async (data: ${simpleClassName}VO) => {\n  return await request.put({ url: '${baseURL}/update', data })\n}\n\n// 删除${table.classComment}\nexport const delete${simpleClassName} = async (id: number) => {\n  return await request.delete({ url: '${baseURL}/delete?id=' + id })\n}\n\n// 导出${table.classComment} Excel\nexport const export${simpleClassName}Api = async (params) => {\n  return await request.download({ url: '${baseURL}/export-excel', params })\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/vue3_schema/views/data.ts.vm",
    "content": "import type { CrudSchema } from '@/hooks/web/useCrudSchemas'\nimport { dateFormatter } from '@/utils/formatTime'\n\n// 表单校验\nexport const rules = reactive({\n#foreach ($column in $columns)\n#if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键\n#set($comment=$column.columnComment)\n  $column.javaField: [required],\n#end\n#end\n})\n\n// CrudSchema https://www.yixiang.co/vue3/crud-schema/\nconst crudSchemas = reactive<CrudSchema[]>([\n#foreach($column in $columns)\n#if ($column.listOperation || $column.listOperationResult || $column.createOperation || $column.updateOperation)\n#set ($dictType = $column.dictType)\n#set ($javaField = $column.javaField)\n#set ($javaType = $column.javaType)\n  {\n    label: '${column.columnComment}',\n    field: '${column.javaField}',\n## ========= 字典部分 =========\n    #if (\"\" != $dictType)## 有数据字典\n    dictType: DICT_TYPE.$dictType.toUpperCase(),\n        #if ($javaType == \"Integer\" || $javaType == \"Long\" || $javaType == \"Byte\" || $javaType == \"Short\")\n    dictClass: 'number',\n        #elseif ($javaType == \"String\")\n    dictClass: 'string',\n        #elseif ($javaType == \"Boolean\")\n    dictClass: 'boolean',\n        #end\n    #end\n## ========= Table 表格部分 =========\n    #if (!$column.listOperationResult)\n    isTable: false,\n    #else\n      #if ($column.htmlType == \"datetime\")\n    formatter: dateFormatter,\n      #end\n    #end\n## ========= Search 表格部分 =========\n    #if ($column.listOperation)\n    isSearch: true,\n        #if ($column.htmlType == \"datetime\")\n    search: {\n      component: 'DatePicker',\n      componentProps: {\n        valueFormat: 'YYYY-MM-DD HH:mm:ss',\n        type: 'daterange',\n        defaultTime: [new Date('1 00:00:00'), new Date('1 23:59:59')]\n      }\n    },\n        #end\n    #end\n## ========= Form 表单部分 =========\n    #if ((!$column.createOperation && !$column.updateOperation) || $column.primaryKey)\n    isForm: false,\n    #else\n        #if($column.htmlType == \"imageUpload\")## 图片上传\n    form: {\n      component: 'UploadImg'\n    },\n        #elseif($column.htmlType == \"fileUpload\")## 文件上传\n    form: {\n      component: 'UploadFile'\n    },\n        #elseif($column.htmlType == \"editor\")## 文本编辑器\n    form: {\n      component: 'Editor',\n      componentProps: {\n        valueHtml: '',\n        height: 200\n      }\n    },\n        #elseif($column.htmlType == \"select\")## 下拉框\n    form: {\n      component: 'SelectV2'\n    },\n        #elseif($column.htmlType == \"checkbox\")## 多选框\n    form: {\n      component: 'Checkbox'\n    },\n        #elseif($column.htmlType == \"radio\")## 单选框\n    form: {\n      component: 'Radio'\n    },\n        #elseif($column.htmlType == \"datetime\")## 时间框\n    form: {\n      component: 'DatePicker',\n      componentProps: {\n        type: 'datetime',\n        valueFormat: 'x'\n      }\n    },\n        #elseif($column.htmlType == \"textarea\")## 文本框\n    form: {\n      component: 'Input',\n      componentProps: {\n        type: 'textarea',\n        rows: 4\n      },\n      colProps: {\n        span: 24\n      }\n    },\n        #elseif(${javaType.toLowerCase()} == \"long\" || ${javaType.toLowerCase()} == \"integer\")## 文本框\n    form: {\n      component: 'InputNumber',\n      value: 0\n    },\n        #end\n    #end\n  },\n#end\n#end\n  {\n    label: '操作',\n    field: 'action',\n    isForm: false\n  }\n])\nexport const { allSchemas } = useCrudSchemas(crudSchemas)\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/vue3_schema/views/form.vue.vm",
    "content": "<template>\n  <Dialog :title=\"dialogTitle\" v-model=\"dialogVisible\">\n    <Form ref=\"formRef\" :schema=\"allSchemas.formSchema\" :rules=\"rules\" v-loading=\"formLoading\" />\n    <template #footer>\n      <el-button @click=\"submitForm\" type=\"primary\" :disabled=\"formLoading\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script setup lang=\"ts\">\nimport * as ${simpleClassName}Api from '@/api/${table.moduleName}/${table.businessName}'\nimport { rules, allSchemas } from './${classNameVar}.data'\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      const data = await ${simpleClassName}Api.get${simpleClassName}(id)\n      formRef.value.setValues(data)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.getElFormRef().validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formRef.value.formModel as ${simpleClassName}Api.${simpleClassName}VO\n    if (formType.value === 'create') {\n      await ${simpleClassName}Api.create${simpleClassName}(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await ${simpleClassName}Api.update${simpleClassName}(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/vue3_schema/views/index.vue.vm",
    "content": "<template>\n  <!-- 搜索工作栏 -->\n  <ContentWrap>\n    <Search :schema=\"allSchemas.searchSchema\" @search=\"setSearchParams\" @reset=\"setSearchParams\">\n      <!-- 新增等操作按钮 -->\n      <template #actionMore>\n        <el-button\n          type=\"primary\"\n          plain\n          @click=\"openForm('create')\"\n          v-hasPermi=\"['${permissionPrefix}:create']\"\n        >\n          <Icon icon=\"ep:plus\" class=\"mr-5px\" /> 新增\n        </el-button>\n      </template>\n    </Search>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <Table\n      :columns=\"allSchemas.tableColumns\"\n      :data=\"tableObject.tableList\"\n      :loading=\"tableObject.loading\"\n      :pagination=\"{\n        total: tableObject.total\n      }\"\n      v-model:pageSize=\"tableObject.pageSize\"\n      v-model:currentPage=\"tableObject.currentPage\"\n    >\n      <template #action=\"{ row }\">\n        <el-button\n          link\n          type=\"primary\"\n          @click=\"openForm('update', row.id)\"\n          v-hasPermi=\"['${permissionPrefix}:update']\"\n        >\n          编辑\n        </el-button>\n        <el-button\n          link\n          type=\"danger\"\n          v-hasPermi=\"['${permissionPrefix}:delete']\"\n          @click=\"handleDelete(row.id)\"\n        >\n          删除\n        </el-button>\n      </template>\n    </Table>\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <${simpleClassName}Form ref=\"formRef\" @success=\"getList\" />\n</template>\n<script setup lang=\"ts\" name=\"${table.className}\">\nimport { allSchemas } from './${classNameVar}.data'\nimport * as ${simpleClassName}Api from '@/api/${table.moduleName}/${table.businessName}'\nimport ${simpleClassName}Form from './${simpleClassName}Form.vue'\n\n// tableObject：表格的属性对象，可获得分页大小、条数等属性\n// tableMethods：表格的操作对象，可进行获得分页、删除记录等操作\n// 详细可见：https://www.yixiang.co/vue3/crud-schema/\nconst { tableObject, tableMethods } = useTable({\n  getListApi: ${simpleClassName}Api.get${simpleClassName}Page, // 分页接口\n  delListApi: ${simpleClassName}Api.delete${simpleClassName} // 删除接口\n})\n// 获得表格的各种操作\nconst { getList, setSearchParams } = tableMethods\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = (id: number) => {\n  tableMethods.delList(id, false)\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/vue3_vben/api/api.ts.vm",
    "content": "import { defHttp } from '@/utils/http/axios'\n#set ($baseURL = \"/${table.moduleName}/${simpleClassName_strikeCase}\")\n\n// 查询${table.classComment}列表\nexport function get${simpleClassName}Page(params) {\n  return defHttp.get({ url: '${baseURL}/page', params })\n}\n\n// 查询${table.classComment}详情\nexport function get${simpleClassName}(id: number) {\n  return defHttp.get({ url: `${baseURL}/get?id=${id}` })\n}\n\n// 新增${table.classComment}\nexport function create${simpleClassName}(data) {\n  return defHttp.post({ url: '${baseURL}/create', data })\n}\n\n// 修改${table.classComment}\nexport function update${simpleClassName}(data) {\n  return defHttp.put({ url: '${baseURL}/update', data })\n}\n\n// 删除${table.classComment}\nexport function delete${simpleClassName}(id: number) {\n  return defHttp.delete({ url: `${baseURL}/delete?id=${id}` })\n}\n\n// 导出${table.classComment} Excel\nexport function export${simpleClassName}(params) {\n  return defHttp.download({ url: '${baseURL}/export-excel', params }, '${table.classComment}.xls')\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/vue3_vben/views/data.ts.vm",
    "content": "import type {BasicColumn, FormSchema} from '@/components/Table'\nimport {useRender} from '@/components/Table'\nimport {DICT_TYPE, getDictOptions} from '@/utils/dict'\n\nexport const columns: BasicColumn[] = [\n#foreach($column in $columns)\n#if ($column.listOperationResult)\n  #set ($dictType=$column.dictType)\n  #set ($javaField = $column.javaField)\n  #set ($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})\n  #set ($comment=$column.columnComment)\n#if ($column.javaType == \"LocalDateTime\")## 时间类型\n  {\n    title: '${comment}',\n    dataIndex: '${javaField}',\n    width: 180,\n    customRender: ({ text }) => {\n      return useRender.renderDate(text)\n    },\n  },\n#elseif(\"\" != $column.dictType)## 数据字典\n  {\n    title: '${comment}',\n    dataIndex: '${javaField}',\n    width: 180,\n    customRender: ({ text }) => {\n      return useRender.renderDict(text, DICT_TYPE.$dictType.toUpperCase())\n    },\n  },\n#else\n  {\n    title: '${comment}',\n    dataIndex: '${javaField}',\n    width: 160,\n  },\n#end\n#end\n#end\n]\n\nexport const searchFormSchema: FormSchema[] = [\n#foreach($column in $columns)\n#if ($column.listOperation)\n  #set ($dictType=$column.dictType)\n  #set ($javaField = $column.javaField)\n  #set ($AttrName=$column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})\n  #set ($comment=$column.columnComment)\n  {\n    label: '${comment}',\n    field: '${javaField}',\n  #if ($column.htmlType == \"input\")\n    component: 'Input',\n  #elseif ($column.htmlType == \"select\")\n    component: 'Select',\n    componentProps: {\n      #if (\"\" != $dictType)## 设置了 dictType 数据字典的情况\n        options: getDictOptions(DICT_TYPE.$dictType.toUpperCase()),\n      #else## 未设置 dictType 数据字典的情况\n        options: [],\n      #end\n    },\n  #elseif ($column.htmlType == \"radio\")\n    component: 'Radio',\n    componentProps: {\n      #if (\"\" != $dictType)## 设置了 dictType 数据字典的情况\n        options: getDictOptions(DICT_TYPE.$dictType.toUpperCase()),\n      #else## 未设置 dictType 数据字典的情况\n        options: [],\n      #end\n    },\n  #elseif($column.htmlType == \"datetime\")\n    component: 'RangePicker',\n    #end\n    colProps: { span: 8 },\n  },\n#end\n#end\n]\n\nexport const createFormSchema: FormSchema[] = [\n  {\n    label: '编号',\n    field: 'id',\n    show: false,\n    component: 'Input',\n  },\n#foreach($column in $columns)\n#if ($column.createOperation)\n  #set ($dictType = $column.dictType)\n  #set ($javaField = $column.javaField)\n  #set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})\n  #set ($comment = $column.columnComment)\n#if (!$column.primaryKey)## 忽略主键，不用在表单里\n  {\n    label: '${comment}',\n    field: '${javaField}',\n  #if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键\n    required: true,\n    #end\n  #if ($column.htmlType == \"input\")\n    component: 'Input',\n  #elseif($column.htmlType == \"imageUpload\")## 图片上传\n    component: 'FileUpload',\n    componentProps: {\n      fileType: 'image',\n      maxCount: 1,\n    },\n  #elseif($column.htmlType == \"fileUpload\")## 文件上传\n    component: 'FileUpload',\n    componentProps: {\n      fileType: 'file',\n      maxCount: 1,\n    },\n  #elseif($column.htmlType == \"editor\")## 文本编辑器\n    component: 'Editor',\n  #elseif($column.htmlType == \"select\")## 下拉框\n    component: 'Select',\n    componentProps: {\n      #if (\"\" != $dictType)## 有数据字典\n        options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), 'number'),\n      #else##没数据字典\n        options:[],\n      #end\n    },\n  #elseif($column.htmlType == \"checkbox\")## 多选框\n    component: 'Checkbox',\n    componentProps: {\n      #if (\"\" != $dictType)## 有数据字典\n        options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), 'number'),\n      #else##没数据字典\n        options:[],\n      #end\n    },\n  #elseif($column.htmlType == \"radio\")## 单选框\n    component: 'RadioButtonGroup',\n    componentProps: {\n      #if (\"\" != $dictType)## 有数据字典\n        options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), 'number'),\n      #else##没数据字典\n        options:[],\n      #end\n    },\n  #elseif($column.htmlType == \"datetime\")## 时间框\n    component: 'DatePicker',\n    componentProps: {\n      showTime: true,\n      format: 'YYYY-MM-DD HH:mm:ss',\n      valueFormat: 'x',\n    },\n  #elseif($column.htmlType == \"textarea\")## 文本域\n    component: 'InputTextArea',\n  #end\n  },\n#end\n#end\n#end\n]\n\nexport const updateFormSchema: FormSchema[] = [\n  {\n    label: '编号',\n    field: 'id',\n    show: false,\n    component: 'Input',\n  },\n#foreach($column in $columns)\n#if ($column.updateOperation)\n#set ($dictType = $column.dictType)\n#set ($javaField = $column.javaField)\n#set ($AttrName = $column.javaField.substring(0,1).toUpperCase() + ${column.javaField.substring(1)})\n#set ($comment = $column.columnComment)\n  #if (!$column.primaryKey)## 忽略主键，不用在表单里\n  {\n    label: '${comment}',\n    field: '${javaField}',\n    #if (($column.createOperation || $column.updateOperation) && !$column.nullable && !${column.primaryKey})## 创建或者更新操作 && 要求非空 && 非主键\n    required: true,\n    #end\n    #if ($column.htmlType == \"input\")\n    component: 'Input',\n    #elseif($column.htmlType == \"imageUpload\")## 图片上传\n    component: 'FileUpload',\n    componentProps: {\n      fileType: 'image',\n      maxCount: 1,\n    },\n    #elseif($column.htmlType == \"fileUpload\")## 文件上传\n    component: 'FileUpload',\n    componentProps: {\n      fileType: 'file',\n      maxCount: 1,\n    },\n    #elseif($column.htmlType == \"editor\")## 文本编辑器\n    component: 'Editor',\n    #elseif($column.htmlType == \"select\")## 下拉框\n    component: 'Select',\n    componentProps: {\n      #if (\"\" != $dictType)## 有数据字典\n      options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), 'number'),\n      #else##没数据字典\n      options:[],\n      #end\n    },\n    #elseif($column.htmlType == \"checkbox\")## 多选框\n    component: 'Checkbox',\n    componentProps: {\n      #if (\"\" != $dictType)## 有数据字典\n      options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), 'number'),\n      #else##没数据字典\n      options:[],\n      #end\n    },\n    #elseif($column.htmlType == \"radio\")## 单选框\n    component: 'RadioButtonGroup',\n    componentProps: {\n      #if (\"\" != $dictType)## 有数据字典\n      options: getDictOptions(DICT_TYPE.$dictType.toUpperCase(), 'number'),\n      #else##没数据字典\n      options:[],\n      #end\n    },\n    #elseif($column.htmlType == \"datetime\")## 时间框\n    component: 'DatePicker',\n    componentProps: {\n      showTime: true,\n      format: 'YYYY-MM-DD HH:mm:ss',\n      valueFormat: 'x',\n    },\n    #elseif($column.htmlType == \"textarea\")## 文本域\n    component: 'InputTextArea',\n    #end\n  },\n  #end\n#end\n#end\n]"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/vue3_vben/views/form.vue.vm",
    "content": "<script lang=\"ts\" setup>\nimport { ref, unref } from 'vue'\nimport { createFormSchema, updateFormSchema } from './${classNameVar}.data'\nimport { useI18n } from '@/hooks/web/useI18n'\nimport { useMessage } from '@/hooks/web/useMessage'\nimport { BasicForm, useForm } from '@/components/Form'\nimport { BasicModal, useModalInner } from '@/components/Modal'\nimport { create${simpleClassName}, get${simpleClassName}, update${simpleClassName} } from '@/api/${table.moduleName}/${table.businessName}'\n\ndefineOptions({ name: '${table.className}Modal' })\n\nconst emit = defineEmits(['success', 'register'])\n\nconst { t } = useI18n()\nconst { createMessage } = useMessage()\nconst isUpdate = ref(true)\n\nconst [registerForm, { setFieldsValue, resetFields, resetSchema, validate }] = useForm({\n  labelWidth: 120,\n  baseColProps: { span: 24 },\n  schemas: createFormSchema,\n  showActionButtonGroup: false,\n  actionColOptions: { span: 23 },\n})\n\nconst [registerModal, { setModalProps, closeModal }] = useModalInner(async (data) => {\n  resetFields()\n  setModalProps({ confirmLoading: false })\n  isUpdate.value = !!data?.isUpdate\n  if (unref(isUpdate)) {\n    resetSchema(updateFormSchema)\n    const res = await get${simpleClassName}(data.record.id)\n    setFieldsValue({ ...res })\n  }\n})\n\nasync function handleSubmit() {\n  try {\n    const values = await validate()\n    setModalProps({ confirmLoading: true })\n    if (unref(isUpdate))\n      await update${simpleClassName}(values)\n    else\n      await create${simpleClassName}(values)\n\n    closeModal()\n    emit('success')\n    createMessage.success(t('common.saveSuccessText'))\n  } finally {\n    setModalProps({ confirmLoading: false })\n  }\n}\n</script>\n<template>\n  <BasicModal v-bind=\"$attrs\" :title=\"isUpdate ? t('action.edit') : t('action.create')\" @register=\"registerModal\" @ok=\"handleSubmit\">\n    <BasicForm @register=\"registerForm\" />\n  </BasicModal>\n</template>"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-infra/yshop-module-infra-biz/src/main/resources/codegen/vue3_vben/views/index.vue.vm",
    "content": "<script lang=\"ts\" setup>\nimport ${simpleClassName}Modal from './${simpleClassName}Modal.vue'\nimport { columns, searchFormSchema } from './${classNameVar}.data'\nimport { useI18n } from '@/hooks/web/useI18n'\nimport { useMessage } from '@/hooks/web/useMessage'\nimport { useModal } from '@/components/Modal'\nimport { useTable } from '@/components/Table'\nimport { delete${simpleClassName}, export${simpleClassName}, get${simpleClassName}Page } from '@/api/${table.moduleName}/${table.businessName}'\n\ndefineOptions({ name: '${table.className}' })\n\nconst { t } = useI18n()\nconst { createConfirm, createMessage } = useMessage()\nconst [registerModal, { openModal }] = useModal()\n\nconst [registerTable, { getForm, reload }] = useTable({\n  title: '${table.classComment}列表',\n  api: get${simpleClassName}Page,\n    columns,\n    formConfig: { labelWidth: 120, schemas: searchFormSchema },\n    useSearchForm: true,\n    showTableSetting: true,\n    actionColumn: {\n      width: 140,\n      title: t('common.action'),\n      dataIndex: 'action',\n      fixed: 'right',\n    },\n})\n\nfunction handleCreate() {\n  openModal(true, { isUpdate: false })\n}\n\nfunction handleEdit(record: Recordable) {\n  openModal(true, { record, isUpdate: true })\n}\n\nasync function handleExport() {\n  createConfirm({\n    title: t('common.exportTitle'),\n    iconType: 'warning',\n    content: t('common.exportMessage'),\n    async onOk() {\n      await export${simpleClassName}(getForm().getFieldsValue())\n      createMessage.success(t('common.exportSuccessText'))\n    },\n  })\n}\n\nasync function handleDelete(record: Recordable) {\n  await delete${simpleClassName}(record.id)\n  createMessage.success(t('common.delSuccessText'))\n  reload()\n}\n</script>\n<template>\n  <div>\n    <BasicTable @register=\"registerTable\">\n      <template #toolbar>\n        <a-button type=\"primary\" v-auth=\"['${permissionPrefix}:create']\" :preIcon=\"IconEnum.ADD\" @click=\"handleCreate\">\n          {{ t('action.create') }}\n        </a-button>\n        <a-button v-auth=\"['${permissionPrefix}:export']\" :preIcon=\"IconEnum.EXPORT\" @click=\"handleExport\">\n          {{ t('action.export') }}\n        </a-button>\n      </template>\n      <template #bodyCell=\"{ column, record }\">\n        <template v-if=\"column.key === 'action'\">\n          <TableAction\n            :actions=\"[\n              { icon: IconEnum.EDIT, label: t('action.edit'), auth: '${permissionPrefix}:update', onClick: handleEdit.bind(null, record) },\n              {\n                icon: IconEnum.DELETE,\n                danger: true,\n                label: t('action.delete'),\n                auth: '${permissionPrefix}:delete',\n                popConfirm: {\n                  title: t('common.delMessage'),\n                  placement: 'left',\n                  confirm: handleDelete.bind(null, record),\n                },\n              },\n            ]\"\n          />\n        </template>\n      </template>\n    </BasicTable>\n    <${simpleClassName}Modal @register=\"registerModal\" @success=\"reload()\" />\n  </div>\n</template>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>yshop</artifactId>\n        <groupId>co.yixiang.boot</groupId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>yshop-module-mall</artifactId>\n    <packaging>pom</packaging>\n\n    <name>${project.artifactId}</name>\n\n    <description>\n        商城大模块，由 product 商品、promotion 营销、trade 交易等组成\n    </description>\n    <modules>\n        <module>yshop-module-product-api</module>\n        <module>yshop-module-product-biz</module>\n        <module>yshop-module-shop-api</module>\n        <module>yshop-module-shop-biz</module>\n        <module>yshop-module-order-api</module>\n        <module>yshop-module-order-biz</module>\n        <module>yshop-module-store-api</module>\n        <module>yshop-module-store-biz</module>\n    </modules>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-api/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <parent>\n        <groupId>co.yixiang.boot</groupId>\n        <artifactId>yshop-module-mall</artifactId>\n        <version>${revision}</version>\n    </parent>\n\n    <artifactId>yshop-module-order-api</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>\n        order 模块 API，暴露给其它模块调用\n    </description>\n\n    <dependencies>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-common</artifactId>\n        </dependency>\n\n        <!-- 参数校验 -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-validation</artifactId>\n            <optional>true</optional>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-api/src/main/java/co/yixiang/yshop/module/order/enums/AdminAfterOrderStatusEnum.java",
    "content": "/**\n * Copyright (C) 2018-2022\n * All rights reserved, Designed By www.yixiang.co\n\n */\npackage co.yixiang.yshop.module.order.enums;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\nimport java.util.stream.Stream;\n\n/**\n * @author hupeng\n * 后台订单相关枚举\n */\n@Getter\n@AllArgsConstructor\npublic enum AdminAfterOrderStatusEnum {\n\n\tSTATUS_1(1,\"售后中\"),\n\tSTATUS_2(2,\"已完成\");\n\n\n\n\n\tprivate Integer value;\n\tprivate String desc;\n\n\tpublic static AdminAfterOrderStatusEnum toType(int value) {\n\t\treturn Stream.of(AdminAfterOrderStatusEnum.values())\n\t\t\t\t.filter(p -> p.value == value)\n\t\t\t\t.findAny()\n\t\t\t\t.orElse(null);\n\t}\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-api/src/main/java/co/yixiang/yshop/module/order/enums/AdminOrderStatusEnum.java",
    "content": "/**\n * Copyright (C) 2018-2022\n * All rights reserved, Designed By www.yixiang.co\n\n */\npackage co.yixiang.yshop.module.order.enums;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\nimport java.util.stream.Stream;\n\n/**\n * @author hupeng\n * 后台订单相关枚举\n */\n@Getter\n@AllArgsConstructor\npublic enum AdminOrderStatusEnum {\n\n\tSTATUS_0(0,\"未支付\"),\n\tSTATUS_1(1,\"未发货\"),\n\tSTATUS_2(2,\"待收货\"),\n\tSTATUS_3(3,\"待评价\"),\n\tSTATUS_4(4,\"交易完成\"),\n\tSTATUS_5(5,\"已退款\"),\n\tSTATUS_6(6,\"已删除\");\n\n\n\n\n\tprivate Integer value;\n\tprivate String desc;\n\n\tpublic static AdminOrderStatusEnum toType(int value) {\n\t\treturn Stream.of(AdminOrderStatusEnum.values())\n\t\t\t\t.filter(p -> p.value == value)\n\t\t\t\t.findAny()\n\t\t\t\t.orElse(null);\n\t}\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-api/src/main/java/co/yixiang/yshop/module/order/enums/AfterChangeTypeEnum.java",
    "content": "package co.yixiang.yshop.module.order.enums;\r\n\r\n\r\nimport lombok.AllArgsConstructor;\r\nimport lombok.Getter;\r\n\r\n/**\r\n * @author hupeng\r\n * 售后状态枚举\r\n */\r\n@Getter\r\n@AllArgsConstructor\r\npublic enum AfterChangeTypeEnum {\r\n    STATE_0(0,\"售后订单生成\"),\r\n    STATE_1(1,\"后台审核成功\"),\r\n    STATE_2(2,\"用户发货\"),\r\n    STATE_3(3,\"打款\"),\r\n    STATE_4(4,\"审核失败\"),\r\n    STATE_5(5,\"用户撤销\");\r\n\r\n    private Integer value;\r\n    private String desc;\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-api/src/main/java/co/yixiang/yshop/module/order/enums/AfterSalesStatusEnum.java",
    "content": "/**\n * Copyright (C) 2018-2022\n * All rights reserved, Designed By www.yixiang.co\n\n */\npackage co.yixiang.yshop.module.order.enums;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\nimport java.util.stream.Stream;\n\n/**\n * 售后状态枚举\n *\n * @author hupeng\n * @date 2023/6/23\n */\n@Getter\n@AllArgsConstructor\npublic enum AfterSalesStatusEnum {\n\n\tSTATUS_0(0,\"已提交等待平台审核\"),\n\tSTATUS_1(1,\"平台已审核,等待用户发货/退款\"),\n\tSTATUS_2(2,\"用户已发货\"),\n\tSTATUS_3(3,\"已完成\");\n\n\tprivate Integer value;\n\tprivate String desc;\n\n\tpublic static AfterSalesStatusEnum toType(int value) {\n\t\treturn Stream.of(AfterSalesStatusEnum.values())\n\t\t\t\t.filter(p -> p.value == value)\n\t\t\t\t.findAny()\n\t\t\t\t.orElse(null);\n\t}\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-api/src/main/java/co/yixiang/yshop/module/order/enums/AfterStatusEnum.java",
    "content": "package co.yixiang.yshop.module.order.enums;\r\n\r\n\r\nimport lombok.AllArgsConstructor;\r\nimport lombok.Getter;\r\n\r\n/**\r\n * @author hupeng\r\n * 售后状态枚举\r\n */\r\n@Getter\r\n@AllArgsConstructor\r\npublic enum AfterStatusEnum {\r\n    STATE_0(0,\"正常\"),\r\n    STATE_1(1,\"用户取消\"),\r\n    STATE_2(2,\"商家拒绝\");\r\n\r\n    private Integer value;\r\n    private String desc;\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-api/src/main/java/co/yixiang/yshop/module/order/enums/AfterTypeEnum.java",
    "content": "package co.yixiang.yshop.module.order.enums;\r\n\r\n\r\nimport lombok.AllArgsConstructor;\r\nimport lombok.Getter;\r\n\r\n/**\r\n * @author hupeng\r\n * 售后类型枚举\r\n */\r\n@Getter\r\n@AllArgsConstructor\r\npublic enum AfterTypeEnum {\r\n    TYPE_1(1,\"同意\"),\r\n    TYPE_2(2,\"拒绝\"),\r\n    SERVICE_TYPE_0(0,\"仅退款\"),\r\n    SERVICE_TYPE_1(1,\"退货退款\");\r\n\r\n    private Integer value;\r\n    private String desc;\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-api/src/main/java/co/yixiang/yshop/module/order/enums/AppFromEnum.java",
    "content": "/**\n * Copyright (C) 2018-2022\n * All rights reserved, Designed By www.yixiang.co\n\n */\npackage co.yixiang.yshop.module.order.enums;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * @author hupeng\n * 应用来源相关枚举\n */\n@Getter\n@AllArgsConstructor\npublic enum AppFromEnum {\n\n\tWEIXIN_H5(\"weixinh5\",\"weixinh5\"),\n\tH5(\"h5\",\"H5\"),\n\tWECHAT(\"wechat\",\"公众号\"),\n\tAPP(\"app\",\"APP\"),\n\tPC(\"pc\",\"PC\"),\n\tROUNTINE(\"routine\",\"小程序\"),\n\tUNIAPPH5(\"uniappH5\",\"uniappH5\");\n\n\n\tprivate String value;\n\tprivate String desc;\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-api/src/main/java/co/yixiang/yshop/module/order/enums/ErrorCodeConstants.java",
    "content": "package co.yixiang.yshop.module.order.enums;\n\nimport co.yixiang.yshop.framework.common.exception.ErrorCode;\n\npublic interface ErrorCodeConstants {\n    // ========== 订单1008007000==========\n    ErrorCode STORE_ORDER_NOT_EXISTS = new ErrorCode(1008007000, \"订单不存在\");\n    ErrorCode STORE_ORDER_CART_INFO_NOT_EXISTS = new ErrorCode(1008007001, \"订单购物详情不存在\");\n    ErrorCode STORE_ORDER_STATUS_NOT_EXISTS = new ErrorCode(1008007002, \"订单操作记录不存在\");\n    ErrorCode STORE_AFTER_SALES_NOT_EXISTS = new ErrorCode(1008007003, \"售后记录不存在\");\n    ErrorCode STORE_AFTER_SALES_ITEM_NOT_EXISTS = new ErrorCode(1008007004, \"售后子不存在\");\n    ErrorCode STORE_AFTER_SALES_STATUS_NOT_EXISTS = new ErrorCode(1008007005, \"售后订单操作详情不存在\");\n    ErrorCode INVALID_PRODUCT = new ErrorCode(1008007005, \"有失效的商品请重新提交\");\n    ErrorCode VALID_PRODUCT_EMPTY = new ErrorCode(1008007005, \"请提交购买的商品\");\n    ErrorCode PARAM_ERROR = new ErrorCode(1008007006, \"参数错误\");\n    ErrorCode ORDER_EXPIRED = new ErrorCode(1008007007, \"订单已过期,请刷新当前页面\");\n    ErrorCode SELECT_ADDRESS = new ErrorCode(1008007008, \"请选择收货地址\");\n    ErrorCode ORDER_GEN_FAIL = new ErrorCode(1008007009, \"订单生成失败\");\n    ErrorCode ORDER_PAY_FINISH = new ErrorCode(1008007010, \"该订单已支付\");\n    ErrorCode PAY_YUE_NOT = new ErrorCode(1008007011, \"余额不足\");\n    ErrorCode ORDER_STATUS_ERROR = new ErrorCode(1008007012, \"订单状态错误\");\n    ErrorCode ORDER_STATUS_FINISH = new ErrorCode(1008007023, \"订单已经完成，不要重复操作\");\n    ErrorCode COMMENT_PRODUCT_NOT_EXISTS = new ErrorCode(1008007013, \"评价产品不存在\");\n    ErrorCode COMMENT_PRODUCT_IN_EXISTS = new ErrorCode(1008007014, \"该产品已评价\");\n    ErrorCode ORDER_NOT_DELETE = new ErrorCode(1008007018, \"该订单无法删除\");\n    ErrorCode ORDER_STATUS_NOT_EXPRESS_ = new ErrorCode(1008007015, \"当前状态不能添加物流信息\");\n    ErrorCode ORDER_REFUND_NOT = new ErrorCode(1008007017, \"订单状态不能售后\");\n    ErrorCode ORDER_NOT_REVOKE = new ErrorCode(1008007016, \"订单不能撤销\");\n    ErrorCode ORDER_NOT_CANCEL = new ErrorCode(1008007018, \"订单不能取消\");\n    ErrorCode ORDER_ADDRESS_REQUERED = new ErrorCode(1008007019, \"请输入商家收货人信息\");\n    ErrorCode ORDER_REFUNDING = new ErrorCode(1008007020, \"正在申请退款中\");\n    ErrorCode ORDER_REFUNDED = new ErrorCode(1008007021, \"订单已退款\");\n    ErrorCode ORDER_PRICE_ERROR = new ErrorCode(1008007022, \"退款金额不正确\");\n    // ========== 订单电子面单记录 ==========\n    ErrorCode STORE_ORDER_ELECTRONICS_NOT_EXISTS = new ErrorCode(1008010000, \"订单电子面单记录不存在\");\n\n\n    ErrorCode STORE_ORDER_DESK_NOT = new ErrorCode(1008007023, \"当前桌号不存在\");\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-api/src/main/java/co/yixiang/yshop/module/order/enums/OrderLogEnum.java",
    "content": "package co.yixiang.yshop.module.order.enums;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\nimport java.util.stream.Stream;\n\n/**\n * @author hupeng\n * 订单操作相关枚举\n */\n@Getter\n@AllArgsConstructor\npublic enum OrderLogEnum {\n\n\tORDER_TAKE_DESK(\"desk\",\"扫码点餐\"),\n\tORDER_TAKE_OUT(\"takeout\",\"外卖\"),\n\tORDER_TAKE_IN(\"takein\",\"自取\"),\n\tPINK_ORDER_FAIL_1(\"ORDER_EXIST\",\"订单生成失败，你已经参加该团了，请先支付订单\"),\n\tPINK_ORDER_FAIL_2(\"ORDER_EXIST\",\"订单生成失败，你已经在该团内不能再参加了\"),\n\tREFUND_ORDER_SUCCESS(\"refund_price_success\",\"退款成功\"),\n\tORDER_EDIT(\"order_edit\",\"订单改价\"),\n\tREMOVE_ORDER(\"remove_order\",\"删除订单\"),\n\tEVAL_ORDER(\"order_eval\",\"用户评价\"),\n\tREFUND_ORDER_APPLY(\"apply_refund\",\"用户申请退款\"),\n\tTAKE_ORDER_DELIVERY(\"user_take_delivery\",\"用户已收货\"),\n\tPAY_ORDER_FAIL(\"PAY_DEFICIENCY\",\"余额不足\"),\n\tPAY_ORDER_SUCCESS(\"pay_success\",\"用户付款成功\"),\n\tCREATE_ORDER_SUCCESS(\"SUCCESS\",\"订单创建成功\"),\n\tCREATE_ORDER(\"yshop_create_order\",\"订单生成\"),\n\tNONE_ORDER(\"NONE\",\"订单OK\"),\n\tDELIVERY_GOODS(\"delivery_goods\", \"订单发货\"),\n\tOFFLINE_PAY(\"offline_pay\", \"线下支付\"),\n\tEXTEND_ORDER(\"EXTEND_ORDER\",\"订单已生成\");\n\n\n\tprivate String value;\n\tprivate String desc;\n\n\n\n\tpublic static OrderLogEnum toType(String value) {\n\t\treturn Stream.of(OrderLogEnum.values())\n\t\t\t\t.filter(p -> p.value.equals(value))\n\t\t\t\t.findAny()\n\t\t\t\t.orElse(null);\n\t}\n\n\tpublic static String getDesc(String value) {\n\t\treturn toType(value) == null ? null : toType(value).desc;\n\t}\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-api/src/main/java/co/yixiang/yshop/module/order/enums/OrderStatusEnum.java",
    "content": "/**\n * Copyright (C) 2018-2022\n * All rights reserved, Designed By www.yixiang.co\n\n */\npackage co.yixiang.yshop.module.order.enums;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\nimport java.util.stream.Stream;\n\n/**\n * @author hupeng\n * 订单相关枚举\n */\n@Getter\n@AllArgsConstructor\npublic enum OrderStatusEnum {\n\n\tSTATUS__1(-1,\"全部订单\"),\n\tSTATUS_0(0,\"未支付\"),\n\tSTATUS_1(1,\"待发货\"),\n\tSTATUS_2(2,\"待收货\"),\n\tSTATUS_3(3,\"待评价\"),\n\tSTATUS_4(4,\"已完成\"),\n\tSTATUS_MINUS_1(-1,\"退款中\"),\n\tSTATUS_MINUS_2(-2,\"已退款\"),\n\tSTATUS_MINUS_3(-3,\"退款\");\n\n\n\n\tprivate Integer value;\n\tprivate String desc;\n\n\tpublic static OrderStatusEnum toType(int value) {\n\t\treturn Stream.of(OrderStatusEnum.values())\n\t\t\t\t.filter(p -> p.value == value)\n\t\t\t\t.findAny()\n\t\t\t\t.orElse(null);\n\t}\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-api/src/main/java/co/yixiang/yshop/module/order/enums/PayTypeEnum.java",
    "content": "/**\n * Copyright (C) 2018-2022\n * All rights reserved, Designed By www.yixiang.co\n\n */\npackage co.yixiang.yshop.module.order.enums;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\nimport java.util.stream.Stream;\n\n/**\n * @author hupeng\n * 支付相关枚举\n */\n@Getter\n@AllArgsConstructor\npublic enum PayTypeEnum {\n\n\tCASH(\"cash\",\"现金支付\"),\n\tALI(\"alipay\",\"支付宝支付\"),\n\tWEIXIN(\"weixin\",\"微信支付\"),\n\tYUE(\"yue\",\"余额支付\"),\n\tINTEGRAL(\"integral\",\"积分兑换\");\n\n\n\tprivate String value;\n\tprivate String desc;\n\n\tpublic static PayTypeEnum toType(String value) {\n\t\treturn Stream.of(PayTypeEnum.values())\n\t\t\t\t.filter(p -> p.value.equals(value))\n\t\t\t\t.findAny()\n\t\t\t\t.orElse(null);\n\t}\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-api/src/main/java/co/yixiang/yshop/module/order/enums/ShippingTempEnum.java",
    "content": "package co.yixiang.yshop.module.order.enums;\r\n\r\n\r\nimport lombok.AllArgsConstructor;\r\nimport lombok.Getter;\r\n\r\n/**\r\n * @author hupeng\r\n * 运费模板类型枚举\r\n */\r\n@Getter\r\n@AllArgsConstructor\r\npublic enum ShippingTempEnum {\r\n    TYPE_1(1,\"按件数\"),\r\n    TYPE_2(2,\"按重量\"),\r\n    TYPE_3(3,\"按体积\");\r\n\r\n    private Integer value;\r\n    private String desc;\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-api/src/main/java/co/yixiang/yshop/module/order/enums/UpdateOrderEnum.java",
    "content": "/**\n * Copyright (C) 2018-2022\n * All rights reserved, Designed By www.yixiang.co\n\n */\npackage co.yixiang.yshop.module.order.enums;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * @author hupeng\n * 应用来源相关枚举\n */\n@Getter\n@AllArgsConstructor\npublic enum UpdateOrderEnum {\n\n\tUPDATE_ORDER(\"updateOrder\",\"修改订单\"),\n\tORDER_SEND(\"orderSend\",\"订单发货\"),\n\tRMARK(\"remark\",\"备注\"),\n\tSEND_INFO(\"sendInfo\",\"配送信息\");\n\n\n\tprivate String value;\n\tprivate String desc;\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>co.yixiang.boot</groupId>\n        <artifactId>yshop-module-mall</artifactId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>yshop-module-order-biz</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>\n        order 模块，主要实现商品购物车相关功能\n    </description>\n\n    <dependencies>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-order-api</artifactId>\n            <version>${revision}</version>\n        </dependency>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-pay-biz</artifactId>\n            <version>${revision}</version>\n        </dependency>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-message-biz</artifactId>\n            <version>${revision}</version>\n        </dependency>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-product-biz</artifactId>\n            <version>${revision}</version>\n        </dependency>\n\n\n        <!-- 业务组件 -->\n<!--        <dependency>-->\n<!--            <groupId>co.yixiang.boot</groupId>-->\n<!--            <artifactId>yshop-spring-boot-starter-biz-operatelog</artifactId>-->\n<!--        </dependency>-->\n\n        <!-- Web 相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-web</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-security</artifactId>\n        </dependency>\n\n        <!-- DB 相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-mybatis</artifactId>\n        </dependency>\n\n        <!-- Test 测试相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-test</artifactId>\n        </dependency>\n\n        <!-- 工具类相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-excel</artifactId>\n        </dependency>\n\n\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/controller/admin/storeorder/StoreOrderController.java",
    "content": "package co.yixiang.yshop.module.order.controller.admin.storeorder;\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.excel.core.util.ExcelUtils;\nimport co.yixiang.yshop.module.order.controller.admin.storeorder.vo.*;\nimport co.yixiang.yshop.module.order.convert.storeorder.StoreOrderConvert;\nimport co.yixiang.yshop.module.order.dal.dataobject.storeorder.StoreOrderDO;\nimport co.yixiang.yshop.module.order.dal.dataobject.storeorderstatus.StoreOrderStatusDO;\nimport co.yixiang.yshop.module.order.dal.redis.order.AsyncCountRedisDAO;\nimport co.yixiang.yshop.module.order.service.storeorder.AsyncStoreOrderService;\nimport co.yixiang.yshop.module.order.service.storeorder.StoreOrderService;\nimport co.yixiang.yshop.module.order.service.storeorder.dto.OrderTimeDataDto;\nimport co.yixiang.yshop.module.order.service.storeorderstatus.StoreOrderStatusService;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.annotation.Resource;\nimport jakarta.servlet.http.HttpServletResponse;\nimport jakarta.validation.Valid;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.io.IOException;\nimport java.util.Collection;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 订单\")\n@RestController\n@RequestMapping(\"/order/store-order\")\n@Validated\npublic class StoreOrderController {\n\n    @Resource\n    private StoreOrderService storeOrderService;\n    @Resource\n    private StoreOrderStatusService storeOrderStatusService;\n    @Resource\n    private AsyncCountRedisDAO asyncCountRedisDAO;\n    @Resource\n    private AsyncStoreOrderService asyncStoreOrderService;\n\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建订单\")\n    @PreAuthorize(\"@ss.hasPermission('order:store-order:create')\")\n    public CommonResult<Long> createStoreOrder(@Valid @RequestBody StoreOrderCreateReqVO createReqVO) {\n        return success(storeOrderService.createStoreOrder(createReqVO));\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"更新订单\")\n    @PreAuthorize(\"@ss.hasPermission('order:store-order:update')\")\n    public CommonResult<Boolean> updateStoreOrder(@Valid @RequestBody StoreOrderUpdateReqVO updateReqVO) {\n        storeOrderService.updateStoreOrder(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除订单\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('order:store-order:delete')\")\n    public CommonResult<Boolean> deleteStoreOrder(@RequestParam(\"id\") Long id) {\n        storeOrderService.deleteStoreOrder(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/pay\")\n    @Operation(summary = \"订单线下支付\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('order:store-order:delete')\")\n    public CommonResult<Boolean> payStoreOrder(@RequestParam(\"id\") Long id) {\n        storeOrderService.payStoreOrder(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/take\")\n    @Operation(summary = \"确认收货\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('order:store-order:delete')\")\n    public CommonResult<Boolean> takeStoreOrder(@RequestParam(\"id\") Long id) {\n        storeOrderService.takeStoreOrder(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得订单\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('order:store-order:query')\")\n    public CommonResult<StoreOrderRespVO> getStoreOrder(@RequestParam(\"id\") Long id) {\n        return success(storeOrderService.getStoreOrder(id));\n    }\n\n    @GetMapping(\"/list\")\n    @Operation(summary = \"获得订单列表\")\n    @Parameter(name = \"ids\", description = \"编号列表\", required = true, example = \"1024,2048\")\n    @PreAuthorize(\"@ss.hasPermission('order:store-order:query')\")\n    public CommonResult<List<StoreOrderRespVO>> getStoreOrderList(@RequestParam(\"ids\") Collection<Long> ids) {\n        List<StoreOrderDO> list = storeOrderService.getStoreOrderList(ids);\n        return success(StoreOrderConvert.INSTANCE.convertList(list));\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得订单分页\")\n    @PreAuthorize(\"@ss.hasPermission('order:store-order:query')\")\n    public CommonResult<PageResult<StoreOrderRespVO>> getStoreOrderPage(@Valid StoreOrderPageReqVO pageVO) {\n        return success(storeOrderService.getStoreOrderPage(pageVO));\n    }\n\n    @GetMapping(\"/record-list\")\n    @Operation(summary = \"获得订单记录列表\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('order:store-order:query')\")\n    public CommonResult<List<StoreOrderStatusDO>> getStoreOrderRecordList(@RequestParam(\"id\") Long id) {\n        List<StoreOrderStatusDO> list = storeOrderStatusService.list(new LambdaQueryWrapper<StoreOrderStatusDO>()\n                .eq(StoreOrderStatusDO::getOid,id));\n        return success(list);\n    }\n\n    @GetMapping(\"/export-excel\")\n    @Operation(summary = \"导出订单 Excel\")\n    @PreAuthorize(\"@ss.hasPermission('order:store-order:export')\")\n    public void exportStoreOrderExcel(@Valid StoreOrderExportReqVO exportReqVO,\n              HttpServletResponse response) throws IOException {\n        List<StoreOrderDO> list = storeOrderService.getStoreOrderList(exportReqVO);\n        // 导出 Excel\n        List<StoreOrderExcelVO> datas = StoreOrderConvert.INSTANCE.convertList02(list);\n        ExcelUtils.write(response, \"订单.xls\", \"数据\", StoreOrderExcelVO.class, datas);\n    }\n\n\n\n    @GetMapping(\"/count\")\n    @Operation(summary = \"获得订单统计\")\n    public CommonResult<OrderTimeDataDto> getStoreOrderCount() {\n        asyncStoreOrderService.getOrderTimeData();\n        return success(asyncCountRedisDAO.get());\n    }\n\n\n    @Operation(summary = \"退款\")\n    @PostMapping(value = \"/refund\")\n    @PreAuthorize(\"@ss.hasPermission('order:store-order:update')\")\n    public CommonResult<Boolean> refund(@Validated @RequestBody StoreOrderRefundVO updateReqVO) {\n        storeOrderService.orderRefund(updateReqVO.getId(),updateReqVO.getPayPrice(), 0, null);\n        return success(true);\n    }\n\n    @Operation(summary = \"订单通知\")\n    @GetMapping(value = \"/notice\")\n    public CommonResult<Long> refund() {\n        return success(storeOrderService.orderNotice());\n    }\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/controller/admin/storeorder/vo/ShoperOrderTimeDataVo.java",
    "content": "package co.yixiang.yshop.module.order.controller.admin.storeorder.vo;\r\n\r\nimport lombok.Data;\r\n\r\nimport java.io.Serializable;\r\n\r\n/**\r\n * @ClassName OrderTimeDataDTO\r\n * @Author hupeng <610796224@qq.com>\r\n * @Date 2023/7/26\r\n **/\r\n@Data\r\npublic class ShoperOrderTimeDataVo implements Serializable {\r\n\r\n    /**今日成交额*/\r\n    private Double todayPrice;\r\n\r\n    /**今日订单数*/\r\n    private Long todayCount;\r\n\r\n    /**昨日成交额*/\r\n    private Double proPrice;\r\n\r\n    /**昨日订单数*/\r\n    private Long proCount;\r\n\r\n    /**本月成交额*/\r\n    private Double monthPrice;\r\n\r\n    /**本月订单数*/\r\n    private Long monthCount;\r\n\r\n    /**上周订单数*/\r\n    private Long lastWeekCount;\r\n\r\n    /**上周成交额*/\r\n    private Double lastWeekPrice;\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/controller/admin/storeorder/vo/StoreOrderBaseVO.java",
    "content": "package co.yixiang.yshop.module.order.controller.admin.storeorder.vo;\n\nimport co.yixiang.yshop.framework.desensitize.core.slider.annotation.MobileDesensitize;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport jakarta.validation.constraints.NotNull;\nimport java.math.BigDecimal;\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n/**\n* 订单 Base VO，提供给添加、修改、详细的子 VO 使用\n* 如果子 VO 存在差异的字段，请不要添加到这里，影响 Swagger 文档生成\n*/\n@Data\npublic class StoreOrderBaseVO {\n\n    @Schema(description = \"订单号\", required = true, example = \"20527\")\n    @NotNull(message = \"订单号不能为空\")\n    private String orderId;\n\n    @Schema(description = \"额外订单号\", example = \"12452\")\n    private String extendOrderId;\n\n    @Schema(description = \"用户id\", required = true, example = \"8323\")\n    @NotNull(message = \"用户id不能为空\")\n    private Long uid;\n\n    @Schema(description = \"用户姓名\", required = true, example = \"张三\")\n    //@NotNull(message = \"用户姓名不能为空\")\n    private String realName;\n\n    //@MobileDesensitize\n    @Schema(description = \"用户电话\", required = true)\n    //@NotNull(message = \"用户电话不能为空\")\n    private String userPhone;\n\n    @Schema(description = \"详细地址\", required = true)\n   // @NotNull(message = \"详细地址不能为空\")\n    private String userAddress;\n\n    @Schema(description = \"购物车id\", required = true, example = \"23301\")\n    //@NotNull(message = \"购物车id不能为空\")\n    private String cartId;\n\n    @Schema(description = \"运费金额\", required = true, example = \"637\")\n    //@NotNull(message = \"运费金额不能为空\")\n    private BigDecimal freightPrice;\n\n    @Schema(description = \"订单商品总数\", required = true)\n    //@NotNull(message = \"订单商品总数不能为空\")\n    private Integer totalNum;\n\n    @Schema(description = \"订单总价\", required = true, example = \"31659\")\n    //@NotNull(message = \"订单总价不能为空\")\n    private BigDecimal totalPrice;\n\n    @Schema(description = \"邮费\", required = true)\n   // @NotNull(message = \"邮费不能为空\")\n    private BigDecimal totalPostage;\n\n    @Schema(description = \"实际支付金额\", required = true, example = \"19682\")\n   // @NotNull(message = \"实际支付金额不能为空\")\n    private BigDecimal payPrice;\n\n    @Schema(description = \"支付邮费\", required = true)\n    //@NotNull(message = \"支付邮费不能为空\")\n    private BigDecimal payPostage;\n\n    @Schema(description = \"抵扣金额\", required = true, example = \"16463\")\n    //@NotNull(message = \"抵扣金额不能为空\")\n    private BigDecimal deductionPrice;\n\n    @Schema(description = \"优惠券id\", required = true, example = \"3299\")\n    //@NotNull(message = \"优惠券id不能为空\")\n    private Integer couponId;\n\n    @Schema(description = \"优惠券金额\", required = true, example = \"22157\")\n    //@NotNull(message = \"优惠券金额不能为空\")\n    private BigDecimal couponPrice;\n\n    @Schema(description = \"支付状态\", required = true, example = \"11728\")\n    //@NotNull(message = \"支付状态不能为空\")\n    private Integer paid;\n\n    @Schema(description = \"支付时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime payTime;\n\n    @Schema(description = \"支付方式\", required = true, example = \"2\")\n    //@NotNull(message = \"支付方式不能为空\")\n    private String payType;\n\n    @Schema(description = \"订单状态（-1 : 申请退款 -2 : 退货成功 0：待发货；1：待收货；2：已收货；3：已完成；-1：已退款）\", required = true, example = \"1\")\n    //@NotNull(message = \"订单状态（-1 : 申请退款 -2 : 退货成功 0：待发货；1：待收货；2：已收货；3：已完成；-1：已退款）不能为空\")\n    private Integer status;\n\n    @Schema(description = \"0 未退款 1 申请中 2 已退款\", required = true, example = \"2\")\n    //@NotNull(message = \"0 未退款 1 申请中 2 已退款不能为空\")\n    private Integer refundStatus;\n\n    @Schema(description = \"退款图片\")\n    private String refundReasonWapImg;\n\n    @Schema(description = \"退款用户说明\")\n    private String refundReasonWapExplain;\n\n    @Schema(description = \"退款时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime refundReasonTime;\n\n    @Schema(description = \"前台退款原因\")\n    private String refundReasonWap;\n\n    @Schema(description = \"不退款的理由\", example = \"不喜欢\")\n    private String refundReason;\n\n    @Schema(description = \"退款金额\", required = true, example = \"7547\")\n    //@NotNull(message = \"退款金额不能为空\")\n    private BigDecimal refundPrice;\n\n    @Schema(description = \"快递公司编号\")\n    private String deliverySn;\n\n    @Schema(description = \"快递名称/送货人姓名\", example = \"张三\")\n    private String deliveryName;\n\n    @Schema(description = \"发货类型\", example = \"1\")\n    private String deliveryType;\n\n    @Schema(description = \"快递单号/手机号\", example = \"24798\")\n    private String deliveryId;\n\n    @Schema(description = \"消费赚取积分\", required = true)\n    //@NotNull(message = \"消费赚取积分不能为空\")\n    private BigDecimal gainIntegral;\n\n    @Schema(description = \"使用积分\", required = true)\n    //@NotNull(message = \"使用积分不能为空\")\n    private BigDecimal useIntegral;\n\n    @Schema(description = \"实际支付积分\", required = true)\n    //@NotNull(message = \"实际支付积分不能为空\")\n    private BigDecimal payIntegral;\n\n    @Schema(description = \"给用户退了多少积分\")\n    private BigDecimal backIntegral;\n\n    @Schema(description = \"备注\", required = true)\n    //@NotNull(message = \"备注不能为空\")\n    private String mark;\n\n    @Schema(description = \"唯一id(md5加密)类似id\", required = true)\n    //@NotNull(message = \"唯一id(md5加密)类似id不能为空\")\n    private String unique;\n\n    @Schema(description = \"管理员备注\", example = \"随便\")\n    private String remark;\n\n    @Schema(description = \"商户ID\", required = true, example = \"8499\")\n    //@NotNull(message = \"商户ID不能为空\")\n    private Integer merId;\n\n    @Schema(description = \"拼团产品id0一般产品\", example = \"3865\")\n    private Long combinationId;\n\n    @Schema(description = \"拼团id 0没有拼团\", required = true, example = \"8463\")\n    //@NotNull(message = \"拼团id 0没有拼团不能为空\")\n    private Long pinkId;\n\n    @Schema(description = \"成本价\", required = true)\n    //@NotNull(message = \"成本价不能为空\")\n    private BigDecimal cost;\n\n    @Schema(description = \"秒杀产品ID\", required = true, example = \"21525\")\n    //@NotNull(message = \"秒杀产品ID不能为空\")\n    private Long seckillId;\n\n    @Schema(description = \"砍价id\", example = \"5132\")\n    private Integer bargainId;\n\n    @Schema(description = \"核销码\", required = true)\n    //@NotNull(message = \"核销码不能为空\")\n    private String verifyCode;\n\n    @Schema(description = \"门店id\", required = true, example = \"12064\")\n    //@NotNull(message = \"门店id不能为空\")\n    private Integer storeId;\n\n    @Schema(description = \"配送方式 1=快递 ，2=门店自提\", required = true, example = \"2\")\n    //@NotNull(message = \"配送方式 1=快递 ，2=门店自提不能为空\")\n    private Integer shippingType;\n\n    @Schema(description = \"支付渠道(0微信公众号1微信小程序)\")\n    private Integer isChannel;\n\n    @Schema(description = \"系统删除\")\n    private Integer isSystemDel;\n\n    @Schema(description = \"订单类型\")\n    private String orderType;\n\n    /**\n     * 取餐👌\n     */\n    private Long numberId;\n\n    /**\n     * 门店ID\n     */\n    private Long shopId;\n\n    /**\n     * 门店名称\n     */\n    private String shopName;\n\n    private LocalDateTime getTime;\n\n    /**\n     * 桌面ID\n     */\n    private Long deskId;\n\n\n    /**\n     * 桌号\n     */\n    private String deskNumber;\n\n    /**\n     * 就餐人数\n     */\n    private Integer deskPeople;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/controller/admin/storeorder/vo/StoreOrderCreateReqVO.java",
    "content": "package co.yixiang.yshop.module.order.controller.admin.storeorder.vo;\n\nimport lombok.*;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.*;\n\n@Schema(description = \"管理后台 - 订单创建 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class StoreOrderCreateReqVO extends StoreOrderBaseVO {\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/controller/admin/storeorder/vo/StoreOrderExcelVO.java",
    "content": "package co.yixiang.yshop.module.order.controller.admin.storeorder.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.util.*;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\n\nimport com.alibaba.excel.annotation.ExcelProperty;\n\n/**\n * 订单 Excel VO\n *\n * @author yshop\n */\n@Data\npublic class StoreOrderExcelVO {\n\n    @ExcelProperty(\"订单ID\")\n    private Long id;\n\n    @ExcelProperty(\"订单号\")\n    private String orderId;\n\n    @ExcelProperty(\"额外订单号\")\n    private String extendOrderId;\n\n    @ExcelProperty(\"用户id\")\n    private Long uid;\n\n    @ExcelProperty(\"用户姓名\")\n    private String realName;\n\n    @ExcelProperty(\"用户电话\")\n    private String userPhone;\n\n    @ExcelProperty(\"详细地址\")\n    private String userAddress;\n\n    @ExcelProperty(\"购物车id\")\n    private String cartId;\n\n    @ExcelProperty(\"运费金额\")\n    private BigDecimal freightPrice;\n\n    @ExcelProperty(\"订单商品总数\")\n    private Integer totalNum;\n\n    @ExcelProperty(\"订单总价\")\n    private BigDecimal totalPrice;\n\n    @ExcelProperty(\"邮费\")\n    private BigDecimal totalPostage;\n\n    @ExcelProperty(\"实际支付金额\")\n    private BigDecimal payPrice;\n\n    @ExcelProperty(\"支付邮费\")\n    private BigDecimal payPostage;\n\n    @ExcelProperty(\"抵扣金额\")\n    private BigDecimal deductionPrice;\n\n    @ExcelProperty(\"优惠券id\")\n    private Integer couponId;\n\n    @ExcelProperty(\"优惠券金额\")\n    private BigDecimal couponPrice;\n\n    @ExcelProperty(\"支付状态\")\n    private Integer paid;\n\n    @ExcelProperty(\"支付时间\")\n    private LocalDateTime payTime;\n\n    @ExcelProperty(\"支付方式\")\n    private String payType;\n\n    @ExcelProperty(\"订单状态（-1 : 申请退款 -2 : 退货成功 0：待发货；1：待收货；2：已收货；3：已完成；-1：已退款）\")\n    private Integer status;\n\n    @ExcelProperty(\"0 未退款 1 申请中 2 已退款\")\n    private Integer refundStatus;\n\n    @ExcelProperty(\"退款图片\")\n    private String refundReasonWapImg;\n\n    @ExcelProperty(\"退款用户说明\")\n    private String refundReasonWapExplain;\n\n    @ExcelProperty(\"退款时间\")\n    private LocalDateTime refundReasonTime;\n\n    @ExcelProperty(\"前台退款原因\")\n    private String refundReasonWap;\n\n    @ExcelProperty(\"不退款的理由\")\n    private String refundReason;\n\n    @ExcelProperty(\"退款金额\")\n    private BigDecimal refundPrice;\n\n    @ExcelProperty(\"快递公司编号\")\n    private String deliverySn;\n\n    @ExcelProperty(\"快递名称/送货人姓名\")\n    private String deliveryName;\n\n    @ExcelProperty(\"发货类型\")\n    private String deliveryType;\n\n    @ExcelProperty(\"快递单号/手机号\")\n    private String deliveryId;\n\n    @ExcelProperty(\"消费赚取积分\")\n    private BigDecimal gainIntegral;\n\n    @ExcelProperty(\"使用积分\")\n    private BigDecimal useIntegral;\n\n    @ExcelProperty(\"实际支付积分\")\n    private BigDecimal payIntegral;\n\n    @ExcelProperty(\"给用户退了多少积分\")\n    private BigDecimal backIntegral;\n\n    @ExcelProperty(\"备注\")\n    private String mark;\n\n    @ExcelProperty(\"唯一id(md5加密)类似id\")\n    private String unique;\n\n    @ExcelProperty(\"管理员备注\")\n    private String remark;\n\n    @ExcelProperty(\"商户ID\")\n    private Integer merId;\n\n    @ExcelProperty(\"拼团产品id0一般产品\")\n    private Long combinationId;\n\n    @ExcelProperty(\"拼团id 0没有拼团\")\n    private Long pinkId;\n\n    @ExcelProperty(\"成本价\")\n    private BigDecimal cost;\n\n    @ExcelProperty(\"秒杀产品ID\")\n    private Long seckillId;\n\n    @ExcelProperty(\"砍价id\")\n    private Integer bargainId;\n\n    @ExcelProperty(\"核销码\")\n    private String verifyCode;\n\n    @ExcelProperty(\"门店id\")\n    private Integer storeId;\n\n    @ExcelProperty(\"配送方式 1=快递 ，2=门店自提\")\n    private Integer shippingType;\n\n    @ExcelProperty(\"支付渠道(0微信公众号1微信小程序)\")\n    private Integer isChannel;\n\n    @ExcelProperty(\"系统删除\")\n    private Integer isSystemDel;\n\n    @ExcelProperty(\"添加时间\")\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/controller/admin/storeorder/vo/StoreOrderExportReqVO.java",
    "content": "package co.yixiang.yshop.module.order.controller.admin.storeorder.vo;\n\nimport lombok.*;\n\nimport java.math.BigDecimal;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport java.time.LocalDateTime;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 订单 Excel 导出 Request VO，参数和 StoreOrderPageReqVO 是一致的\")\n@Data\npublic class StoreOrderExportReqVO {\n\n    @Schema(description = \"订单号\", example = \"20527\")\n    private String orderId;\n\n    @Schema(description = \"额外订单号\", example = \"12452\")\n    private String extendOrderId;\n\n    @Schema(description = \"用户id\", example = \"8323\")\n    private Long uid;\n\n    @Schema(description = \"用户姓名\", example = \"张三\")\n    private String realName;\n\n    @Schema(description = \"用户电话\")\n    private String userPhone;\n\n    @Schema(description = \"详细地址\")\n    private String userAddress;\n\n    @Schema(description = \"购物车id\", example = \"23301\")\n    private String cartId;\n\n    @Schema(description = \"运费金额\", example = \"637\")\n    private BigDecimal freightPrice;\n\n    @Schema(description = \"订单商品总数\")\n    private Integer totalNum;\n\n    @Schema(description = \"订单总价\", example = \"31659\")\n    private BigDecimal totalPrice;\n\n    @Schema(description = \"邮费\")\n    private BigDecimal totalPostage;\n\n    @Schema(description = \"实际支付金额\", example = \"19682\")\n    private BigDecimal payPrice;\n\n    @Schema(description = \"支付邮费\")\n    private BigDecimal payPostage;\n\n    @Schema(description = \"抵扣金额\", example = \"16463\")\n    private BigDecimal deductionPrice;\n\n    @Schema(description = \"优惠券id\", example = \"3299\")\n    private Integer couponId;\n\n    @Schema(description = \"优惠券金额\", example = \"22157\")\n    private BigDecimal couponPrice;\n\n    @Schema(description = \"支付状态\", example = \"11728\")\n    private Integer paid;\n\n    @Schema(description = \"支付时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] payTime;\n\n    @Schema(description = \"支付方式\", example = \"2\")\n    private String payType;\n\n    @Schema(description = \"订单状态（-1 : 申请退款 -2 : 退货成功 0：待发货；1：待收货；2：已收货；3：已完成；-1：已退款）\", example = \"1\")\n    private Integer status;\n\n    @Schema(description = \"0 未退款 1 申请中 2 已退款\", example = \"2\")\n    private Integer refundStatus;\n\n    @Schema(description = \"退款图片\")\n    private String refundReasonWapImg;\n\n    @Schema(description = \"退款用户说明\")\n    private String refundReasonWapExplain;\n\n    @Schema(description = \"退款时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] refundReasonTime;\n\n    @Schema(description = \"前台退款原因\")\n    private String refundReasonWap;\n\n    @Schema(description = \"不退款的理由\", example = \"不喜欢\")\n    private String refundReason;\n\n    @Schema(description = \"退款金额\", example = \"7547\")\n    private BigDecimal refundPrice;\n\n    @Schema(description = \"快递公司编号\")\n    private String deliverySn;\n\n    @Schema(description = \"快递名称/送货人姓名\", example = \"张三\")\n    private String deliveryName;\n\n    @Schema(description = \"发货类型\", example = \"1\")\n    private String deliveryType;\n\n    @Schema(description = \"快递单号/手机号\", example = \"24798\")\n    private String deliveryId;\n\n    @Schema(description = \"消费赚取积分\")\n    private BigDecimal gainIntegral;\n\n    @Schema(description = \"使用积分\")\n    private BigDecimal useIntegral;\n\n    @Schema(description = \"实际支付积分\")\n    private BigDecimal payIntegral;\n\n    @Schema(description = \"给用户退了多少积分\")\n    private BigDecimal backIntegral;\n\n    @Schema(description = \"备注\")\n    private String mark;\n\n    @Schema(description = \"唯一id(md5加密)类似id\")\n    private String unique;\n\n    @Schema(description = \"管理员备注\", example = \"随便\")\n    private String remark;\n\n    @Schema(description = \"商户ID\", example = \"8499\")\n    private Integer merId;\n\n    @Schema(description = \"拼团产品id0一般产品\", example = \"3865\")\n    private Long combinationId;\n\n    @Schema(description = \"拼团id 0没有拼团\", example = \"8463\")\n    private Long pinkId;\n\n    @Schema(description = \"成本价\")\n    private BigDecimal cost;\n\n    @Schema(description = \"秒杀产品ID\", example = \"21525\")\n    private Long seckillId;\n\n    @Schema(description = \"砍价id\", example = \"5132\")\n    private Integer bargainId;\n\n    @Schema(description = \"核销码\")\n    private String verifyCode;\n\n    @Schema(description = \"门店id\", example = \"12064\")\n    private Integer storeId;\n\n    @Schema(description = \"配送方式 1=快递 ，2=门店自提\", example = \"2\")\n    private Integer shippingType;\n\n    @Schema(description = \"支付渠道(0微信公众号1微信小程序)\")\n    private Integer isChannel;\n\n    @Schema(description = \"系统删除\")\n    private Integer isSystemDel;\n\n    @Schema(description = \"添加时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/controller/admin/storeorder/vo/StoreOrderPageReqVO.java",
    "content": "package co.yixiang.yshop.module.order.controller.admin.storeorder.vo;\n\nimport lombok.*;\n\nimport java.math.BigDecimal;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport org.springframework.format.annotation.DateTimeFormat;\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 订单分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class StoreOrderPageReqVO extends PageParam {\n\n    @Schema(description = \"取餐号\", example = \"20527\")\n    private String numberId;\n\n    @Schema(description = \"订单号\", example = \"20527\")\n    private String orderId;\n\n    @Schema(description = \"用户姓名\", example = \"张三\")\n    private String realName;\n\n    @Schema(description = \"用户电话\")\n    private String userPhone;\n\n    @Schema(description = \"添加时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n    @Schema(description = \"订单状态搜索值\")\n    private Integer orderStatus;\n\n    @Schema(description = \"支付状态搜索值\")\n    private String payStatus;\n\n    @Schema(description = \"工作台订单还是普通订单\")\n    private String type;\n\n    @Schema(description = \"店铺ID\", example = \"20527\")\n    private String ShopId;\n\n    @Schema(description = \"订单类型\", example = \"desk\")\n    private String orderType;\n\n    @Schema(description = \"桌面id\")\n    private Integer deskId;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/controller/admin/storeorder/vo/StoreOrderRefundVO.java",
    "content": "package co.yixiang.yshop.module.order.controller.admin.storeorder.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\n\nimport jakarta.validation.constraints.NotNull;\nimport java.math.BigDecimal;\n\n@Schema(description = \"管理后台 - 订单退款 Request VO\")\n@Data\n@ToString(callSuper = true)\npublic class StoreOrderRefundVO {\n\n    @Schema(description = \"订单ID\", required = true, example = \"31716\")\n    @NotNull(message = \"订单ID不能为空\")\n    private Long id;\n\n    @Schema(description = \"退款金额\", required = true, example = \"31716\")\n    @NotNull(message = \"退款金额不能为空\")\n    private BigDecimal payPrice;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/controller/admin/storeorder/vo/StoreOrderRespVO.java",
    "content": "package co.yixiang.yshop.module.order.controller.admin.storeorder.vo;\n\nimport co.yixiang.yshop.module.member.controller.admin.user.vo.UserRespVO;\nimport co.yixiang.yshop.module.order.dal.dataobject.storeordercartinfo.StoreOrderCartInfoDO;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.time.LocalDateTime;\nimport java.util.List;\n\n@Schema(description = \"管理后台 - 订单 Response VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class StoreOrderRespVO extends StoreOrderBaseVO {\n\n    @Schema(description = \"订单ID\", required = true, example = \"31716\")\n    private Long id;\n\n    @Schema(description = \"添加时间\", required = true)\n    private LocalDateTime createTime;\n\n    @Schema(description = \"用户信息\", required = true)\n    private UserRespVO userRespVO;\n\n    @Schema(description = \"商品信息\", required = true)\n    private List<StoreOrderCartInfoDO> storeOrderCartInfoDOList;\n\n    @Schema(description = \"订单状态\", required = true)\n    private String StatusStr;\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/controller/admin/storeorder/vo/StoreOrderUpdateReqVO.java",
    "content": "package co.yixiang.yshop.module.order.controller.admin.storeorder.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.util.*;\nimport jakarta.validation.constraints.*;\n\n@Schema(description = \"管理后台 - 订单更新 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class StoreOrderUpdateReqVO extends StoreOrderBaseVO {\n\n    @Schema(description = \"订单ID\", required = true, example = \"31716\")\n    @NotNull(message = \"订单ID不能为空\")\n    private Long id;\n\n    /**\n     *     updateOrder: '修改订单',\n     *     orderSend: '订单发货',\n     *     remark: '备注',\n     *     sendInfo: '配送信息',\n     */\n    @Schema(description = \"更新类型\", required = true, example = \"31716\")\n    private String updateType;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/controller/app/order/AppOrderController.java",
    "content": "/**\n * Copyright (C) 2018-2022\n * All rights reserved, Designed By www.yixiang.co\n * 注意：\n * 本软件为www.yixiang.co开发研制，未经购买不得使用\n * 购买后可获得全部源代码（禁止转卖、分享、上传到码云、github等开源平台）\n * 一经发现盗用、分享等行为，将追究法律责任，后果自负\n */\npackage co.yixiang.yshop.module.order.controller.app.order;\n\nimport cn.hutool.core.util.ObjectUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.enums.OrderInfoEnum;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.security.core.annotations.PreAuthenticated;\nimport co.yixiang.yshop.module.member.controller.app.user.vo.AppUserOrderCountVo;\nimport co.yixiang.yshop.module.order.controller.app.order.param.*;\nimport co.yixiang.yshop.module.order.controller.app.order.vo.AppStoreOrderQueryVo;\nimport co.yixiang.yshop.module.order.dal.dataobject.storeorder.StoreOrderDO;\nimport co.yixiang.yshop.module.order.dal.redis.order.AsyncOrderRedisDAO;\nimport co.yixiang.yshop.module.order.service.storeorder.AppStoreOrderService;\nimport co.yixiang.yshop.module.pay.http.HttpRequestNoticeNewParams;\nimport co.yixiang.yshop.module.store.controller.app.storeshop.vo.AppStoreShopVO;\nimport co.yixiang.yshop.module.store.convert.storeshop.StoreShopConvert;\nimport co.yixiang.yshop.module.store.dal.dataobject.storeshop.StoreShopDO;\nimport co.yixiang.yshop.module.store.service.storeshop.AppStoreShopService;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport com.egzosn.pay.spring.boot.core.PayServiceManager;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.Parameters;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.validation.Valid;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.time.LocalDateTime;\nimport java.util.List;\nimport java.util.Map;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\nimport static co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;\nimport static co.yixiang.yshop.module.order.enums.ErrorCodeConstants.PARAM_ERROR;\nimport static co.yixiang.yshop.module.order.enums.ErrorCodeConstants.STORE_ORDER_NOT_EXISTS;\n\n/**\n * <p>\n * 订单控制器\n * </p>\n *\n * @author hupeng\n * @since 2023-6-23\n */\n@Slf4j\n@RestController\n@RequiredArgsConstructor(onConstructor = @__(@Autowired))\n@Tag(name = \"用户 APP - 订单模块\")\n@RequestMapping(\"/order\")\npublic class AppOrderController {\n\n    private final AppStoreOrderService appStoreOrderService;\n    private final AsyncOrderRedisDAO asyncOrderRedisDAO;\n    private final PayServiceManager manager;\n    private final AppStoreShopService appStoreShopService;\n;\n\n\n\n    /**\n     * 订单创建\n     */\n    @PreAuthenticated\n    @PostMapping(\"/create\")\n    @Operation(summary = \"订单创建\")\n    public CommonResult<Map<String, Object>> create(@RequestBody @Valid AppOrderParam param) {\n        Long uid = getLoginUserId();\n        return success(appStoreOrderService.createOrder(uid, param));\n    }\n\n\n    /**\n     * 订单支付\n     */\n    @PreAuthenticated\n    @PostMapping(value = \"/pay\")\n    @Operation(summary = \"订单支付\")\n    public CommonResult<Map<String, Object>> pay(@RequestBody @Valid AppPayParam param) {\n        Long uid = getLoginUserId();\n        return success(appStoreOrderService.pay(uid,param));\n    }\n\n    /**\n     * 支付回调地址\n     *\n     * @param request   请求\n     * @param detailsId 列表id\n     * @return 支付是否成功\n     */\n    @RequestMapping(value = \"/notify/payBack{detailsId}.json\")\n    public String payBack(HttpServletRequest request, @PathVariable String detailsId)  {\n        return manager.payBack(detailsId, new HttpRequestNoticeNewParams(request));\n    }\n\n\n    /**\n     * 订单列表\n     */\n    @PreAuthenticated\n    @GetMapping(\"/list\")\n    @Operation(summary = \"订单列表\")\n    @Parameters({\n            @Parameter(name = \"type\", description = \"商品状态,-1全部 默认为0未支付 1待发货 2待收货 3待评价 4已完成 5退款中 6已退款 7退款\",\n                    required = true, example = \"1\"),\n            @Parameter(name = \"page\", description = \"页码,默认为1\",\n                    required = true, example = \"1\"),\n            @Parameter(name = \"limit\", description = \"页大小,默认为10\",\n                    required = true, example = \"10      \")\n    })\n    public CommonResult<List<AppStoreOrderQueryVo>> orderList(@RequestParam(value = \"type\", defaultValue = \"0\") int type,\n                                       @RequestParam(value = \"page\", defaultValue = \"1\") int page,\n                                       @RequestParam(value = \"limit\", defaultValue = \"10\") int limit) {\n        Long uid = getLoginUserId();\n        return success(appStoreOrderService.orderList(uid, type, page, limit));\n    }\n\n\n    /**\n     * 订单详情\n     */\n    @PreAuthenticated\n    @GetMapping(\"/detail/{key}\")\n    @Operation(summary = \"订单详情\")\n    @Parameter(name = \"key\", description = \"唯一的uni值或者订单号\", required = true, example = \"10      \")\n    public CommonResult<AppStoreOrderQueryVo> detail(@PathVariable String key) {\n        Long uid = getLoginUserId();\n        if (StrUtil.isEmpty(key)) {\n            throw exception(PARAM_ERROR);\n        }\n        AppStoreOrderQueryVo storeOrder = appStoreOrderService.getOrderInfo(key, uid);\n        if (ObjectUtil.isNull(storeOrder)) {\n            throw exception(STORE_ORDER_NOT_EXISTS);\n        }\n        return success(appStoreOrderService.handleOrder(storeOrder));\n    }\n\n\n    /**\n     * 订单收货\n     */\n    @PreAuthenticated\n    @PostMapping(\"/take\")\n    @Operation(summary = \"订单收货\")\n    public CommonResult<Boolean> orderTake(@RequestBody @Validated AppDoOrderParam param) {\n        Long uid = getLoginUserId();\n        appStoreOrderService.takeOrder(param.getUni(), uid);\n        return success(true);\n    }\n\n    /**\n     * 订单退款审核\n     */\n    @PostMapping(\"/refund\")\n    @Operation(summary = \"订单退款审核\")\n    public CommonResult<Boolean> refundVerify(@RequestBody AppRefundParam param) {\n        Long uid = getLoginUserId();\n        appStoreOrderService.orderApplyRefund(param.getRefundReasonWapExplain(),\n                param.getRefundReasonWapImg(),\n                param.getText(),\n                param.getUni(), uid);\n        return success(true);\n    }\n\n\n\n\n    /**\n     * 订单删除\n     */\n    @PreAuthenticated\n    @PostMapping(\"/del\")\n    @Operation(summary = \"订单删除\")\n    public CommonResult<Boolean> orderDel(@Validated @RequestBody AppDoOrderParam param) {\n        Long uid = getLoginUserId();\n        appStoreOrderService.removeOrder(param.getUni(), uid);\n        return success(true);\n    }\n\n\n\n\n    /**\n     * 订单取消   未支付的订单回退积分,回退优惠券,回退库存\n     */\n    @PreAuthenticated\n    @PostMapping(\"/cancel\")\n    @Operation(summary = \"订单取消\")\n    public CommonResult<Boolean> cancelOrder(@Validated @RequestBody AppHandleOrderParam param) {\n        Long uid = getLoginUserId();\n        appStoreOrderService.cancelOrder(param.getId(), uid);\n        return success(true);\n    }\n\n    /**\n     * 个人中心订单统计\n     */\n    @PreAuthenticated\n    @PostMapping(\"/user_count\")\n    @Operation(summary = \"个人中心订单统计\")\n    public CommonResult<AppUserOrderCountVo> countOrder() {\n        Long uid = getLoginUserId();\n        AppUserOrderCountVo appUserOrderCountVo = asyncOrderRedisDAO.get(uid);\n        return success(appUserOrderCountVo);\n\n    }\n\n\n    @PreAuthenticated\n    @GetMapping(\"/getShop\")\n    @Operation(summary = \"获取店铺\")\n    public CommonResult<AppStoreShopVO> getShop(@RequestParam(\"shopId\") Integer shopId) {\n        StoreShopDO storeShopDO = appStoreShopService.getById(shopId);\n\n        AppStoreShopVO appStoreShopVO =  StoreShopConvert.INSTANCE.convert02(storeShopDO);\n        appStoreShopVO.setIsEmpty(true);\n\n        return success(appStoreShopVO);\n    }\n\n\n\n\n\n\n\n}\n\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/controller/app/order/param/AppComputeOrderParam.java",
    "content": "package co.yixiang.yshop.module.order.controller.app.order.param;\r\n\r\nimport io.swagger.v3.oas.annotations.media.Schema;\r\nimport lombok.*;\r\n\r\n/**\r\n * @ClassName ComputeOrderParam\r\n * @Author hupeng <610796224@qq.com>\r\n * @Date 2023/6/19\r\n **/\r\n@Getter\r\n@Setter\r\n@ToString\r\n@Builder\r\n@NoArgsConstructor\r\n@AllArgsConstructor\r\npublic class AppComputeOrderParam {\r\n\r\n    @Schema(description = \"地址ID\", required = true)\r\n    private String addressId;\r\n\r\n    @Schema(description = \"优惠券ID\", required = true)\r\n    private String couponId;\r\n\r\n    @Schema(description = \"支付方式\", required = true)\r\n    private String payType;\r\n\r\n    @Schema(description = \"使用积分 1-表示使用\", required = true)\r\n    private String useIntegral;\r\n\r\n    @Schema(description = \"配送方式 1=快递 ，2=门店自提\", required = true)\r\n    private String shippingType;\r\n\r\n    @Schema(description = \"拼团ID\", required = true)\r\n    private String pinkId;\r\n\r\n    @Schema(description = \"拼团产品ID\", required = true)\r\n    private String combinationId;\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/controller/app/order/param/AppConfirmOrderParam.java",
    "content": "package co.yixiang.yshop.module.order.controller.app.order.param;\r\n\r\nimport io.swagger.v3.oas.annotations.media.Schema;\r\nimport lombok.Getter;\r\nimport lombok.Setter;\r\n\r\nimport jakarta.validation.constraints.NotBlank;\r\n\r\n/**\r\n * @ClassName 确认订单ConfirmOrderDTO\r\n * @Author hupeng <610796224@qq.com>\r\n * @Date 2023/6/18\r\n **/\r\n@Getter\r\n@Setter\r\npublic class AppConfirmOrderParam {\r\n\r\n    @NotBlank(message = \"请提交购买的商品\")\r\n    @Schema(description = \"购物车ID\", required = true)\r\n    private String cartId;\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/controller/app/order/param/AppDoOrderParam.java",
    "content": "package co.yixiang.yshop.module.order.controller.app.order.param;\r\n\r\nimport io.swagger.v3.oas.annotations.media.Schema;\r\nimport lombok.Getter;\r\nimport lombok.Setter;\r\n\r\nimport jakarta.validation.constraints.NotBlank;\r\n\r\n/**\r\n * @ClassName HandleOrderParam\r\n * @Author hupeng <610796224@qq.com>\r\n * @Date 2023/6/21\r\n **/\r\n@Getter\r\n@Setter\r\npublic class AppDoOrderParam {\r\n    @NotBlank(message = \"参数有误\")\r\n    @Schema(description = \"订单ID\", required = true)\r\n    private String uni;\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/controller/app/order/param/AppExpressParam.java",
    "content": "package co.yixiang.yshop.module.order.controller.app.order.param;\r\n\r\nimport io.swagger.v3.oas.annotations.media.Schema;\r\nimport lombok.Data;\r\n\r\nimport java.io.Serializable;\r\n\r\n/**\r\n * @ClassName ExpressParam\r\n * @Author hupeng <610796224@qq.com>\r\n * @Date 2023/7/26\r\n **/\r\n@Data\r\npublic class AppExpressParam implements Serializable {\r\n\r\n    @Schema(description = \"订单编号\", required = true)\r\n    private String orderCode;\r\n\r\n    @Schema(description = \"快递公司编码\", required = true)\r\n    private String shipperCode;\r\n\r\n    @Schema(description = \"物流单号\", required = true)\r\n    private String logisticCode;\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/controller/app/order/param/AppHandleOrderParam.java",
    "content": "package co.yixiang.yshop.module.order.controller.app.order.param;\r\n\r\nimport io.swagger.v3.oas.annotations.media.Schema;\r\nimport lombok.Getter;\r\nimport lombok.Setter;\r\n\r\nimport jakarta.validation.constraints.NotBlank;\r\n\r\n/**\r\n * @ClassName HandleOrderParam\r\n * @Author hupeng <610796224@qq.com>\r\n * @Date 2023/6/23\r\n **/\r\n@Getter\r\n@Setter\r\npublic class AppHandleOrderParam {\r\n\r\n    @NotBlank(message = \"参数有误\")\r\n    @Schema(description = \"订单ID\", required = true)\r\n    private String id;\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/controller/app/order/param/AppOrderParam.java",
    "content": "package co.yixiang.yshop.module.order.controller.app.order.param;\r\n\r\nimport io.swagger.v3.oas.annotations.media.Schema;\r\nimport lombok.Data;\r\n\r\nimport jakarta.validation.constraints.Size;\r\nimport java.io.Serializable;\r\nimport java.util.List;\r\n\r\n/**\r\n * @ClassName OrderParam\r\n * @Author hupeng <610796224@qq.com>\r\n * @Date 2023/8/22\r\n **/\r\n@Data\r\npublic class AppOrderParam implements Serializable {\r\n\r\n    @Schema(description = \"地址ID\", required = true)\r\n    private String addressId;\r\n\r\n    @Schema(description = \"门店ID\", required = true)\r\n    private String shopId;\r\n\r\n    @Schema(description = \"手机号\", required = true)\r\n    private String mobile;\r\n\r\n    @Schema(description = \"优惠券ID\", required = true)\r\n    private String couponId;\r\n\r\n    @Schema(description = \"购买类型:takein=自取,takeout=外卖,desk=扫码点餐\", required = true)\r\n    private String orderType;\r\n\r\n    @Size(max = 200,message = \"长度超过了限制\")\r\n    @Schema(description = \"备注\", required = true)\r\n    private String remark;\r\n\r\n    @Schema(description = \"取餐时间\", required = true)\r\n    private Integer gettime;\r\n\r\n    @Schema(description = \"秒杀产品ID\", required = true)\r\n    private List<String> productId;\r\n\r\n    @Schema(description = \"规格\", required = true)\r\n    private List<String> spec;\r\n\r\n    @Schema(description = \"数量\", required = true)\r\n    private List<String>  number;\r\n\r\n    @Schema(description = \"支付类型\", required = true)\r\n    private String payType;\r\n\r\n    @Schema(description = \"桌面ID\", required = true)\r\n    private Long deskId;\r\n\r\n    @Schema(description = \"桌号\", required = true)\r\n    private String deskNumber;\r\n\r\n    @Schema(description = \"就餐人数\", required = true)\r\n    private Integer deskPeople;\r\n\r\n    @Schema(description = \"订单号\", required = true)\r\n    private String orderId;\r\n\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/controller/app/order/param/AppPayParam.java",
    "content": "package co.yixiang.yshop.module.order.controller.app.order.param;\r\n\r\nimport io.swagger.v3.oas.annotations.media.Schema;\r\nimport lombok.Data;\r\n\r\nimport jakarta.validation.constraints.NotBlank;\r\nimport java.io.Serializable;\r\n\r\n/**\r\n * @ClassName PayDto\r\n * @Author hupeng <610796224@qq.com>\r\n * @Date 2013/6/20\r\n **/\r\n@Data\r\npublic class AppPayParam implements Serializable {\r\n\r\n    @Schema(description = \"来源\", required = true)\r\n    private String from;\r\n\r\n    @NotBlank(message = \"选择支付类型 PayTypeEnum类型(alipay weixin yue)\")\r\n    @Schema(description = \"支付类型\", required = true)\r\n    private String paytype;\r\n\r\n    @NotBlank(message = \"参数错误\")\r\n    @Schema(description = \"订单ID\", required = true)\r\n    private String uni;\r\n\r\n//    @Schema(description = \"服务商id 当不是余额支付必填1-阿里支付 3-微信支付 这里当编号与数据库id对应\", required = true)\r\n//    private String detailsId;\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/controller/app/order/param/AppProductReplyParam.java",
    "content": "package co.yixiang.yshop.module.order.controller.app.order.param;\r\n\r\nimport io.swagger.v3.oas.annotations.media.Schema;\r\nimport lombok.Getter;\r\nimport lombok.Setter;\r\n\r\nimport jakarta.validation.constraints.NotBlank;\r\nimport jakarta.validation.constraints.Size;\r\n\r\n/**\r\n * @ClassName ProductReplyParam\r\n * @Author hupeng <610796224@qq.com>\r\n * @Date 2023/6/20\r\n **/\r\n@Getter\r\n@Setter\r\npublic class AppProductReplyParam {\r\n\r\n    @NotBlank(message = \"评论不能为空\")\r\n    @Size(min = 1, max = 200,message = \"长度超过了限制\")\r\n    @Schema(description = \"商品评论内容\", required = true)\r\n    private String comment;\r\n\r\n    @Schema(description = \"商品评论图片地址\", required = true)\r\n    private String pics;\r\n\r\n    @NotBlank(message = \"请为商品评分\")\r\n    @Schema(description = \"商品评分\", required = true)\r\n    private String productScore;\r\n\r\n    @NotBlank(message = \"请为商品评分\")\r\n    @Schema(description = \"服务评分\", required = true)\r\n    private String serviceScore;\r\n\r\n    @NotBlank(message = \"参数有误\")\r\n    @Schema(description = \"参数有误\", required = true)\r\n    private String unique;\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/controller/app/order/param/AppRefundParam.java",
    "content": "package co.yixiang.yshop.module.order.controller.app.order.param;\r\n\r\nimport io.swagger.v3.oas.annotations.media.Schema;\r\nimport lombok.Data;\r\n\r\nimport jakarta.validation.constraints.NotBlank;\r\nimport java.io.Serializable;\r\n\r\n/**\r\n * @ClassName RefundParam\r\n * @Author hupeng <610796224@qq.com>\r\n * @Date 2023/6/23\r\n **/\r\n@Data\r\npublic class AppRefundParam implements Serializable {\r\n\r\n    @Schema(description = \"退款备注\", required = true)\r\n    private String refundReasonWapExplain;\r\n\r\n    @Schema(description = \"退款图片\", required = true)\r\n    private String refundReasonWapImg;\r\n\r\n    @NotBlank(message = \"请填写退款原因\")\r\n    @Schema(description = \"退款原因\", required = true)\r\n    private String text;\r\n\r\n    @NotBlank(message = \"参数错误\")\r\n    @Schema(description = \"订单号\", required = true)\r\n    private String uni;\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/controller/app/order/vo/AppComputeVo.java",
    "content": "package co.yixiang.yshop.module.order.controller.app.order.vo;\r\n\r\n\r\nimport co.yixiang.yshop.framework.common.serializer.BigDecimalSerializer;\r\nimport com.fasterxml.jackson.databind.annotation.JsonSerialize;\r\nimport lombok.AllArgsConstructor;\r\nimport lombok.Builder;\r\nimport lombok.Data;\r\nimport lombok.NoArgsConstructor;\r\n\r\nimport java.io.Serializable;\r\nimport java.math.BigDecimal;\r\n\r\n/**\r\n * @ClassName ComputeVo\r\n * @Author hupeng <610796224@qq.com>\r\n * @Date 2019/10/27\r\n **/\r\n@Data\r\n@Builder\r\n@NoArgsConstructor\r\n@AllArgsConstructor\r\npublic class AppComputeVo implements Serializable {\r\n\r\n    @JsonSerialize(using = BigDecimalSerializer.class)\r\n    private BigDecimal couponPrice;\r\n\r\n    @JsonSerialize(using = BigDecimalSerializer.class)\r\n    private BigDecimal deductionPrice;\r\n\r\n    @JsonSerialize(using = BigDecimalSerializer.class)\r\n    private BigDecimal payPostage;\r\n\r\n    @JsonSerialize(using = BigDecimalSerializer.class)\r\n    private BigDecimal payPrice;\r\n\r\n    @JsonSerialize(using = BigDecimalSerializer.class)\r\n    private BigDecimal totalPrice;\r\n\r\n    private Double usedIntegral; //使用了多少积分\r\n\r\n    @JsonSerialize(using = BigDecimalSerializer.class)\r\n    private BigDecimal payIntegral;\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/controller/app/order/vo/AppConfirmOrderVo.java",
    "content": "package co.yixiang.yshop.module.order.controller.app.order.vo;\r\n\r\n\r\nimport co.yixiang.yshop.module.member.controller.app.user.vo.AppUserQueryVo;\r\nimport co.yixiang.yshop.module.member.dal.dataobject.useraddress.UserAddressDO;\r\nimport co.yixiang.yshop.module.order.service.storeorder.dto.PriceGroupDto;\r\nimport co.yixiang.yshop.module.product.controller.app.cart.vo.AppStoreCartQueryVo;\r\nimport io.swagger.v3.oas.annotations.media.Schema;\r\nimport lombok.AllArgsConstructor;\r\nimport lombok.Builder;\r\nimport lombok.Data;\r\nimport lombok.NoArgsConstructor;\r\n\r\nimport java.io.Serializable;\r\nimport java.util.List;\r\n\r\n/**\r\n * @ClassName ConfirmOrderVo\r\n * @Author hupeng <610796224@qq.com>\r\n * @Date 2023/6/18\r\n **/\r\n@Data\r\n@NoArgsConstructor\r\n@AllArgsConstructor\r\n@Builder\r\n@Schema(description = \"用户 APP - 订单确认参数参数\")\r\npublic class AppConfirmOrderVo implements Serializable {\r\n    //地址信息\r\n    private UserAddressDO addressInfo;\r\n\r\n    //砍价id\r\n    private Integer bargainId;\r\n\r\n\r\n    private Integer combinationId;\r\n\r\n    //优惠券减\r\n    private Boolean deduction;\r\n\r\n    private Boolean enableIntegral;\r\n\r\n    private Double enableIntegralNum;\r\n\r\n    //积分抵扣\r\n    private Integer integralRatio;\r\n\r\n    private String orderKey;\r\n\r\n    private PriceGroupDto priceGroup;\r\n\r\n    private Integer seckillId;\r\n\r\n    //店铺自提\r\n    private Integer storeSelfMention;\r\n\r\n\r\n   // private StoreCouponUserVo usableCoupon;\r\n\r\n    private AppUserQueryVo userInfo;\r\n\r\n\r\n\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/controller/app/order/vo/AppStoreOrderQueryVo.java",
    "content": "package co.yixiang.yshop.module.order.controller.app.order.vo;\n\n\nimport co.yixiang.yshop.module.order.dal.dataobject.storeordercartinfo.StoreOrderCartInfoDO;\nimport co.yixiang.yshop.module.order.service.storeorder.dto.StatusDto;\nimport co.yixiang.yshop.module.product.controller.app.cart.vo.AppStoreCartQueryVo;\nimport co.yixiang.yshop.module.store.controller.app.storeshop.vo.AppStoreShopVO;\nimport com.fasterxml.jackson.annotation.JsonFormat;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.io.Serializable;\nimport java.math.BigDecimal;\nimport java.time.LocalDateTime;\nimport java.util.Date;\nimport java.util.List;\n\n/**\n * <p>\n * 订单表 查询结果对象\n * </p>\n *\n * @author hupeng\n * @date 2023-6-19\n */\n@Data\n@Schema(description = \"用户 APP - 订单表查询参数\")\npublic class AppStoreOrderQueryVo implements Serializable {\n    private static final long serialVersionUID = 1L;\n\n    @Schema(description = \"订单ID\", required = true)\n    private Long id;\n\n    @Schema(description = \"订单号\", required = true)\n    private String orderId;\n\n    private String extendOrderId;\n\n    @Schema(description = \"用户id\", required = true)\n    private Long uid;\n\n    @Schema(description = \"用户姓名\", required = true)\n    private String realName;\n\n    @Schema(description = \"用户电话\", required = true)\n    private String userPhone;\n\n    @Schema(description = \"详细地址\", required = true)\n    private String userAddress;\n\n    @Schema(description = \"购物车id\", required = true)\n    private String cartId;\n\n    @Schema(description = \"购物车信息\", required = true)\n    private List<StoreOrderCartInfoDO> cartInfo;\n\n    @Schema(description = \"订单信息合集\", required = true)\n    private StatusDto statusDto;\n\n    @Schema(description = \"运费金额\", required = true)\n    private BigDecimal freightPrice;\n\n    @Schema(description = \"订单商品总数\", required = true)\n    private Integer totalNum;\n\n    @Schema(description = \"订单总价\", required = true)\n    private BigDecimal totalPrice;\n\n    @Schema(description = \"邮费\", required = true)\n    private BigDecimal totalPostage;\n\n    @Schema(description = \"实际支付金额\", required = true)\n    private BigDecimal payPrice;\n\n    @Schema(description = \"实际支付积分\", required = true)\n    private BigDecimal payIntegral;\n\n    @Schema(description = \"支付邮费\", required = true)\n    private BigDecimal payPostage;\n\n    @Schema(description = \"抵扣金额\", required = true)\n    private BigDecimal deductionPrice;\n\n    @Schema(description = \"优惠券id\", required = true)\n    private Integer couponId;\n\n    @Schema(description = \"优惠券金额\", required = true)\n    private BigDecimal couponPrice;\n\n    @Schema(description = \"支付状态\", required = true)\n    private Integer paid;\n\n    @Schema(description = \"支付时间\", required = true)\n    private LocalDateTime payTime;\n\n    @Schema(description = \"支付方式\", required = true)\n    private String payType;\n\n    @JsonFormat(pattern = \"yyyy-MM-dd HH:mm:ss\",timezone=\"GMT+8\")\n    @Schema(description = \"创建时间\", required = true)\n    private LocalDateTime createTime;\n\n    @Schema(description = \"订单状态（-1 : 申请退款 -2 : 退货成功 0：待发货；1：待收货；2：已收货；3：待评价；-1：已退款\", required = true)\n    private Integer status;\n\n    @Schema(description = \"0 未退款 1 申请中 2 已退款\", required = true)\n    private Integer refundStatus;\n\n    @Schema(description = \"退款图片\", required = true)\n    private String refundReasonWapImg;\n\n    @Schema(description = \"退款用户说明\", required = true)\n    private String refundReasonWapExplain;\n\n    @Schema(description = \"退款时间\", required = true)\n    private LocalDateTime refundReasonTime;\n\n    @Schema(description = \"前台退款原因\", required = true)\n    private String refundReasonWap;\n\n    @Schema(description = \"不退款的理由\", required = true)\n    private String refundReason;\n\n    @Schema(description = \"退款金额\", required = true)\n    private BigDecimal refundPrice;\n\n    @Schema(description = \"快递名称/送货人姓名\", required = true)\n    private String deliveryName;\n\n    @Schema(description = \"快递公司编号\", required = true)\n    private String deliverySn;\n\n    @Schema(description = \"发货类型\", required = true)\n    private String deliveryType;\n\n    @Schema(description = \"快递单号/手机号\", required = true)\n    private String deliveryId;\n\n    @Schema(description = \"发货时间\", required = true)\n    private LocalDateTime deliveryTime;\n\n    @Schema(description = \"消费赚取积分\", required = true)\n    private BigDecimal gainIntegral;\n\n    @Schema(description = \"使用积分\", required = true)\n    private BigDecimal useIntegral;\n\n    @Schema(description = \"给用户退了多少积分\", required = true)\n    private BigDecimal backIntegral;\n\n    @Schema(description = \"备注\", required = true)\n    private String mark;\n\n    @Schema(description = \"确认订单返回的key\", required = true)\n    private String unique;\n\n    @Schema(description = \"管理员备注\", required = true)\n    private String remark;\n\n    @Schema(description = \"拼团产品id0一般产品\", required = true)\n    private Long combinationId;\n\n    @Schema(description = \"拼团id 0没有拼团\", required = true)\n    private Long pinkId;\n\n    @Schema(description = \"成本价\", required = true)\n    private BigDecimal cost;\n\n    @Schema(description = \"秒杀产品ID\", required = true)\n    private Long seckillId;\n\n\n    @Schema(description = \"配送方式 1=快递 ，2=门店自提\\\"\", required = true)\n    private Integer shippingType;\n\n    @Schema(description = \"取餐时间\", required = true)\n    private LocalDateTime getTime;\n\n    @Schema(description = \"取餐号\", required = true)\n    private Integer numberId;\n\n    @Schema(description = \"购买类型:takein=自取,takeout=外卖\", required = true)\n    private String orderType;\n\n    @Schema(description = \"门店\", required = true)\n    private AppStoreShopVO shop;\n\n    @Schema(description = \"门店id\", required = true)\n    private Long shopId;\n\n    @Schema(description = \"前面等等的制作数量\", required = true)\n    private Long preNum;\n\n    @Schema(description = \"店铺名称\", required = true)\n    private String shopName;\n\n    @Schema(description = \"桌面ID\", required = true)\n    private Long deskId;\n\n    @Schema(description = \"桌号\", required = true)\n    private String deskNumber;\n\n    @Schema(description = \"就餐人数\", required = true)\n    private Integer deskPeople;\n\n\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/convert/storeorder/StoreOrderConvert.java",
    "content": "package co.yixiang.yshop.module.order.convert.storeorder;\n\nimport java.util.*;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\nimport co.yixiang.yshop.module.order.controller.app.order.vo.AppStoreOrderQueryVo;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.factory.Mappers;\nimport co.yixiang.yshop.module.order.controller.admin.storeorder.vo.*;\nimport co.yixiang.yshop.module.order.dal.dataobject.storeorder.StoreOrderDO;\n\n/**\n * 订单 Convert\n *\n * @author yshop\n */\n@Mapper\npublic interface StoreOrderConvert {\n\n    StoreOrderConvert INSTANCE = Mappers.getMapper(StoreOrderConvert.class);\n\n    StoreOrderDO convert(StoreOrderCreateReqVO bean);\n\n    StoreOrderDO convert(StoreOrderUpdateReqVO bean);\n\n    StoreOrderRespVO convert(StoreOrderDO bean);\n\n    AppStoreOrderQueryVo convert1(StoreOrderDO bean);\n\n    List<StoreOrderRespVO> convertList(List<StoreOrderDO> list);\n\n    List<AppStoreOrderQueryVo> convertList01(List<StoreOrderDO> list);\n\n    PageResult<StoreOrderRespVO> convertPage(PageResult<StoreOrderDO> page);\n\n    List<StoreOrderExcelVO> convertList02(List<StoreOrderDO> list);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/convert/storeordercartinfo/StoreOrderCartInfoConvert.java",
    "content": "package co.yixiang.yshop.module.order.convert.storeordercartinfo;\n\nimport org.mapstruct.Mapper;\nimport org.mapstruct.factory.Mappers;\n\n/**\n * 订单购物详情 Convert\n *\n * @author yshop\n */\n@Mapper\npublic interface StoreOrderCartInfoConvert {\n\n    StoreOrderCartInfoConvert INSTANCE = Mappers.getMapper(StoreOrderCartInfoConvert.class);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/convert/storeorderstatus/StoreOrderStatusConvert.java",
    "content": "package co.yixiang.yshop.module.order.convert.storeorderstatus;\n\nimport org.mapstruct.Mapper;\nimport org.mapstruct.factory.Mappers;\n\n/**\n * 订单操作记录 Convert\n *\n * @author yshop\n */\n@Mapper\npublic interface StoreOrderStatusConvert {\n\n    StoreOrderStatusConvert INSTANCE = Mappers.getMapper(StoreOrderStatusConvert.class);\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/dal/dataobject/ordernumber/OrderNumberDO.java",
    "content": "package co.yixiang.yshop.module.order.dal.dataobject.ordernumber;\n\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.*;\n\nimport java.math.BigDecimal;\nimport java.time.LocalDateTime;\n\n/**\n * 订单 DO\n *\n * @author yshop\n */\n@TableName(\"yshop_order_number\")\n@KeySequence(\"yshop_order_number_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class OrderNumberDO  {\n\n    /**\n     * ID\n     */\n    private Long id;\n    /**\n     * 订单号\n     */\n    private String orderId;\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/dal/dataobject/storeorder/StoreOrderDO.java",
    "content": "package co.yixiang.yshop.module.order.dal.dataobject.storeorder;\n\nimport lombok.*;\nimport java.util.*;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\nimport com.baomidou.mybatisplus.annotation.*;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\n\n/**\n * 订单 DO\n *\n * @author yshop\n */\n@TableName(\"yshop_store_order\")\n@KeySequence(\"yshop_store_order_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class StoreOrderDO extends BaseDO {\n\n    /**\n     * 订单ID\n     */\n    @TableId\n    private Long id;\n    /**\n     * 订单号\n     */\n    private String orderId;\n    /**\n     * 额外订单号\n     */\n    private String extendOrderId;\n    /**\n     * 用户id\n     */\n    private Long uid;\n    /**\n     * 用户姓名\n     */\n    private String realName;\n    /**\n     * 用户电话\n     */\n    private String userPhone;\n    /**\n     * 详细地址\n     */\n    private String userAddress;\n    /**\n     * 购物车id\n     */\n    private String cartId;\n    /**\n     * 运费金额\n     */\n    private BigDecimal freightPrice;\n    /**\n     * 订单商品总数\n     */\n    private Integer totalNum;\n    /**\n     * 订单总价\n     */\n    private BigDecimal totalPrice;\n    /**\n     * 邮费\n     */\n    private BigDecimal totalPostage;\n    /**\n     * 实际支付金额\n     */\n    private BigDecimal payPrice;\n    /**\n     * 支付邮费\n     */\n    private BigDecimal payPostage;\n    /**\n     * 抵扣金额\n     */\n    private BigDecimal deductionPrice;\n    /**\n     * 优惠券id\n     */\n    private Integer couponId;\n    /**\n     * 优惠券金额\n     */\n    private BigDecimal couponPrice;\n    /**\n     * 支付状态\n     */\n    private Integer paid;\n    /**\n     * 支付时间\n     */\n    private LocalDateTime payTime;\n    /**\n     * 支付方式\n     */\n    private String payType;\n\n    /**\n     * 订单类型 购买类型:takein=自取,takeout=外卖\n     */\n    private String orderType;\n    /**\n     * 订单状态（-1 : 申请退款 -2 : 退货成功 0：待发货；1：待收货；2：已收货；3：已完成；-1：已退款）\n     */\n    private Integer status;\n    /**\n     * 0 未退款 1 申请中 2 已退款\n     */\n    private Integer refundStatus;\n    /**\n     * 退款图片\n     */\n    private String refundReasonWapImg;\n    /**\n     * 退款用户说明\n     */\n    private String refundReasonWapExplain;\n    /**\n     * 退款时间\n     */\n    private LocalDateTime refundReasonTime;\n    /**\n     * 前台退款原因\n     */\n    private String refundReasonWap;\n    /**\n     * 不退款的理由\n     */\n    private String refundReason;\n    /**\n     * 退款金额\n     */\n    private BigDecimal refundPrice;\n    /**\n     * 快递公司编号\n     */\n    private String deliverySn;\n    /**\n     * 快递名称/送货人姓名\n     */\n    private String deliveryName;\n    /**\n     * 发货类型\n     */\n    private String deliveryType;\n    /**\n     * 快递单号/手机号\n     */\n    private String deliveryId;\n\n    /**\n     * 发货时间\n     */\n    private LocalDateTime deliveryTime;\n    /**\n     * 消费赚取积分\n     */\n    private BigDecimal gainIntegral;\n    /**\n     * 使用积分\n     */\n    private BigDecimal useIntegral;\n    /**\n     * 实际支付积分\n     */\n    private BigDecimal payIntegral;\n    /**\n     * 给用户退了多少积分\n     */\n    private BigDecimal backIntegral;\n    /**\n     * 备注\n     */\n    private String mark;\n    /**\n     * 唯一id(md5加密)类似id\n     */\n    @TableField(value = \"`unique`\")\n    private String unique;\n    /**\n     * 管理员备注\n     */\n    private String remark;\n    /**\n     * 商户ID\n     */\n    private Integer merId;\n    /**\n     * 拼团产品id0一般产品\n     */\n    private Long combinationId;\n    /**\n     * 拼团id 0没有拼团\n     */\n    private Long pinkId;\n    /**\n     * 成本价\n     */\n    private BigDecimal cost;\n    /**\n     * 秒杀产品ID\n     */\n    private Long seckillId;\n    /**\n     * 砍价id\n     */\n    private Integer bargainId;\n    /**\n     * 核销码\n     */\n    private String verifyCode;\n    /**\n     * 门店id\n     */\n    private Integer storeId;\n    /**\n     * 配送方式 1=快递 ，2=门店自提\n     */\n    private Integer shippingType;\n    /**\n     * 支付渠道(0微信公众号1微信小程序)\n     */\n    private Integer isChannel;\n    /**\n     * 系统删除\n     */\n    private Integer isSystemDel;\n\n    /**\n     * 门店ID\n     */\n    private Long shopId;\n\n    /**\n     * 门店名称\n     */\n    private String shopName;\n\n    /**\n     * 取餐时间\n     */\n    private LocalDateTime getTime;\n\n    /**\n     * 取餐👌\n     */\n    private Long numberId;\n\n    private String outTradeNo;\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/dal/dataobject/storeordercartinfo/StoreOrderCartInfoDO.java",
    "content": "package co.yixiang.yshop.module.order.dal.dataobject.storeordercartinfo;\n\nimport lombok.*;\n\nimport java.math.BigDecimal;\nimport java.util.*;\nimport com.baomidou.mybatisplus.annotation.*;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\n\n/**\n * 订单购物详情 DO\n *\n * @author yshop\n */\n@TableName(\"yshop_store_order_cart_info\")\n@KeySequence(\"yshop_store_order_cart_info_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class StoreOrderCartInfoDO  {\n\n    /**\n     * id\n     */\n    @TableId\n    private Long id;\n    /**\n     * 订单id\n     */\n    private Long oid;\n    /**\n     * 订单号\n     */\n    private String orderId;\n    /**\n     * 购物车id\n     */\n    private Long cartId;\n    /**\n     * 商品ID\n     */\n    private Long productId;\n    /**\n     * 购买东西的详细信息\n     */\n    private String cartInfo;\n    /**\n     * 唯一id\n     */\n    @TableField(value = \"`unique`\")\n    private String unique;\n    /**\n     * 是否能售后0不能1能\n     */\n    private Integer isAfterSales;\n\n    /**\n     * 商品名称\n     */\n    private String title;\n\n    /**\n     * 商品图片\n     */\n    private String image;\n\n    /**\n     * 数量\n     */\n    private Integer number;\n\n    /**\n     * 规格\n     */\n    private String spec;\n\n    /**\n     * 价格\n     */\n    private BigDecimal price;\n\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/dal/dataobject/storeorderstatus/StoreOrderStatusDO.java",
    "content": "package co.yixiang.yshop.module.order.dal.dataobject.storeorderstatus;\n\nimport lombok.*;\nimport java.util.*;\nimport java.time.LocalDateTime;\nimport com.baomidou.mybatisplus.annotation.*;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\n\n/**\n * 订单操作记录 DO\n *\n * @author yshop\n */\n@TableName(\"yshop_store_order_status\")\n@KeySequence(\"yshop_store_order_status_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class StoreOrderStatusDO {\n\n    /**\n     * id\n     */\n    @TableId\n    private Long id;\n    /**\n     * 订单id\n     */\n    private Long oid;\n    /**\n     * 操作类型\n     */\n    private String changeType;\n    /**\n     * 操作备注\n     */\n    private String changeMessage;\n    /**\n     * 操作时间\n     */\n    private LocalDateTime changeTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/dal/mysql/ordernumber/OrderNumberMapper.java",
    "content": "package co.yixiang.yshop.module.order.dal.mysql.ordernumber;\n\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.module.order.dal.dataobject.ordernumber.OrderNumberDO;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * 订单 Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface OrderNumberMapper extends BaseMapperX<OrderNumberDO> {\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/dal/mysql/storeorder/StoreOrderMapper.java",
    "content": "package co.yixiang.yshop.module.order.dal.mysql.storeorder;\n\nimport java.util.*;\n\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.enums.OrderInfoEnum;\nimport co.yixiang.yshop.framework.common.enums.ShopCommonEnum;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils;\nimport co.yixiang.yshop.module.order.dal.dataobject.storeorder.StoreOrderDO;\nimport co.yixiang.yshop.module.order.enums.AdminOrderStatusEnum;\nimport co.yixiang.yshop.module.order.enums.OrderLogEnum;\nimport co.yixiang.yshop.framework.common.enums.OrderTypeEnum;\nimport com.baomidou.mybatisplus.core.conditions.Wrapper;\nimport com.baomidou.mybatisplus.core.toolkit.Constants;\nimport org.apache.ibatis.annotations.Mapper;\nimport co.yixiang.yshop.module.order.controller.admin.storeorder.vo.*;\nimport org.apache.ibatis.annotations.Param;\nimport org.apache.ibatis.annotations.Select;\n\n/**\n * 订单 Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface StoreOrderMapper extends BaseMapperX<StoreOrderDO> {\n\n    default PageResult<StoreOrderDO> selectPage(StoreOrderPageReqVO reqVO) {\n        LambdaQueryWrapperX<StoreOrderDO> wrapper = new LambdaQueryWrapperX();\n        Long shopId = SecurityFrameworkUtils.getLoginUser().getShopId();\n        if(shopId > 0) {\n            wrapper.eq(StoreOrderDO::getShopId,shopId);\n        }\n        wrapper.eqIfPresent(StoreOrderDO::getOrderId, reqVO.getOrderId())\n                .eqIfPresent(StoreOrderDO::getNumberId, reqVO.getNumberId())\n                .eqIfPresent(StoreOrderDO::getShopId, reqVO.getShopId())\n                .likeIfPresent(StoreOrderDO::getRealName, reqVO.getRealName())\n                .eqIfPresent(StoreOrderDO::getUserPhone, reqVO.getUserPhone())\n                .eqIfPresent(StoreOrderDO::getOrderType,reqVO.getOrderType())\n                .betweenIfPresent(StoreOrderDO::getCreateTime, reqVO.getCreateTime());\n                //.orderByDesc(StoreOrderDO::getId);\n        if( OrderTypeEnum.TYPE_WORK.getValue().equals(reqVO.getType())){\n            wrapper.ne(StoreOrderDO::getIsSystemDel, ShopCommonEnum.DELETE_1.getValue()).orderByAsc(StoreOrderDO::getCreateTime);\n        }else{\n            wrapper.orderByDesc(StoreOrderDO::getCreateTime);\n        }\n        if (reqVO.getOrderStatus() != null) {\n            switch (AdminOrderStatusEnum.toType(reqVO.getOrderStatus())) {\n                //未支付\n                case STATUS_0:\n                    wrapper.ne(StoreOrderDO::getIsSystemDel, ShopCommonEnum.DELETE_1.getValue())\n                            .eq(StoreOrderDO::getPaid, OrderInfoEnum.PAY_STATUS_0.getValue())\n                            .eq(StoreOrderDO::getRefundStatus, OrderInfoEnum.REFUND_STATUS_0.getValue())\n                            .eq(StoreOrderDO::getStatus, OrderInfoEnum.STATUS_0.getValue());\n                    break;\n                //待发货\n                case STATUS_1:\n                    wrapper.ne(StoreOrderDO::getIsSystemDel, ShopCommonEnum.DELETE_1.getValue())\n                            .eq(StoreOrderDO::getRefundStatus, OrderInfoEnum.REFUND_STATUS_0.getValue())\n                            .eq(StoreOrderDO::getStatus, OrderInfoEnum.STATUS_0.getValue());\n                    if( OrderTypeEnum.TYPE_WORK.getValue().equals(reqVO.getType())){\n                        wrapper.and(i->i.eq(StoreOrderDO::getPaid, OrderInfoEnum.PAY_STATUS_1.getValue())\n                                .or(j->j.eq(StoreOrderDO::getOrderType, OrderLogEnum.ORDER_TAKE_DESK.getValue())\n                                .eq(StoreOrderDO::getPaid, OrderInfoEnum.PAY_STATUS_0.getValue())));\n                    }else {\n                        wrapper.eq(StoreOrderDO::getPaid, OrderInfoEnum.PAY_STATUS_1.getValue());\n                    }\n                    break;\n                //待收货\n                case STATUS_2:\n                    wrapper.ne(StoreOrderDO::getIsSystemDel, ShopCommonEnum.DELETE_1.getValue())\n                            .eq(StoreOrderDO::getPaid, OrderInfoEnum.PAY_STATUS_1.getValue())\n                            .eq(StoreOrderDO::getRefundStatus, OrderInfoEnum.REFUND_STATUS_0.getValue())\n                            .eq(StoreOrderDO::getStatus, OrderInfoEnum.STATUS_1.getValue());\n                    break;\n                //待评价\n                case STATUS_3:\n                    wrapper.ne(StoreOrderDO::getIsSystemDel, ShopCommonEnum.DELETE_1.getValue())\n                            .eq(StoreOrderDO::getPaid, OrderInfoEnum.PAY_STATUS_1.getValue())\n                            .eq(StoreOrderDO::getRefundStatus, OrderInfoEnum.REFUND_STATUS_0.getValue())\n                            .eq(StoreOrderDO::getStatus, OrderInfoEnum.STATUS_2.getValue());\n                    break;\n                //已完成\n                case STATUS_4:\n                    wrapper.ne(StoreOrderDO::getIsSystemDel, ShopCommonEnum.DELETE_1.getValue())\n                            .eq(StoreOrderDO::getPaid, OrderInfoEnum.PAY_STATUS_1.getValue())\n                            .eq(StoreOrderDO::getRefundStatus, OrderInfoEnum.REFUND_STATUS_0.getValue())\n                            .eq(StoreOrderDO::getStatus, OrderInfoEnum.STATUS_3.getValue());\n                    break;\n                //退款单\n                case STATUS_5:\n                    String[] strs = {\"1\", \"2\"};\n                    wrapper.ne(StoreOrderDO::getIsSystemDel, ShopCommonEnum.DELETE_1.getValue())\n                            .in(StoreOrderDO::getRefundStatus, strs);\n                    break;\n                //已删除\n                case STATUS_6:\n                    wrapper.eq(StoreOrderDO::getIsSystemDel, ShopCommonEnum.DELETE_1.getValue());\n                    break;\n                default:\n            }\n        }\n        if (StrUtil.isNotEmpty(reqVO.getPayStatus())) {\n            wrapper.eq(StoreOrderDO::getPayType,reqVO.getPayStatus());\n        }\n\n        return selectPage(reqVO, wrapper);\n    }\n\n    default List<StoreOrderDO> selectList(StoreOrderExportReqVO reqVO) {\n        return selectList(new LambdaQueryWrapperX<StoreOrderDO>()\n                .eqIfPresent(StoreOrderDO::getOrderId, reqVO.getOrderId())\n                .likeIfPresent(StoreOrderDO::getRealName, reqVO.getRealName())\n                .eqIfPresent(StoreOrderDO::getUserPhone, reqVO.getUserPhone())\n                .eqIfPresent(StoreOrderDO::getUserAddress, reqVO.getUserAddress())\n                .betweenIfPresent(StoreOrderDO::getCreateTime, reqVO.getCreateTime())\n                .orderByDesc(StoreOrderDO::getId));\n    }\n\n\n    @Select(\"select IFNULL(sum(pay_price),0) from yshop_store_order \" +\n            \"where paid=1 and deleted=0 and refund_status=0 and uid=#{uid}\")\n    double sumPrice(@Param(\"uid\") Long uid);\n\n    @Select(\"SELECT IFNULL(sum(pay_price),0) \" +\n            \" FROM yshop_store_order ${ew.customSqlSegment}\")\n    Double todayPrice(@Param(Constants.WRAPPER) Wrapper<StoreOrderDO> wrapper);\n\n    @Select( \"select IFNULL(sum(pay_price),0)  from yshop_store_order \" +\n            \"where refund_status=0 and deleted=0 and paid=1\")\n    Double sumTotalPrice();\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/dal/mysql/storeordercartinfo/StoreOrderCartInfoMapper.java",
    "content": "package co.yixiang.yshop.module.order.dal.mysql.storeordercartinfo;\n\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.module.order.dal.dataobject.storeordercartinfo.StoreOrderCartInfoDO;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * 订单购物详情 Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface StoreOrderCartInfoMapper extends BaseMapperX<StoreOrderCartInfoDO> {\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/dal/mysql/storeorderstatus/StoreOrderStatusMapper.java",
    "content": "package co.yixiang.yshop.module.order.dal.mysql.storeorderstatus;\n\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.module.order.dal.dataobject.storeorderstatus.StoreOrderStatusDO;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * 订单操作记录 Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface StoreOrderStatusMapper extends BaseMapperX<StoreOrderStatusDO> {\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/dal/redis/RedisKeyConstants.java",
    "content": "package co.yixiang.yshop.module.order.dal.redis;\n\nimport co.yixiang.yshop.framework.redis.core.RedisKeyDefine;\nimport co.yixiang.yshop.module.order.service.storeorder.dto.CacheDto;\n\nimport static co.yixiang.yshop.framework.redis.core.RedisKeyDefine.KeyTypeEnum.STRING;\n\n/**\n * System Redis Key 枚举类\n *\n * @author yshop\n */\npublic interface RedisKeyConstants {\n\n\n    RedisKeyDefine YSHOP_ORDER_CACHE_KEY = new RedisKeyDefine(\"确认订单数据缓存\",\n            \"yshop_order_cache:%s\", // 参数为访问uid+key\n            STRING, CacheDto.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);\n\n    RedisKeyDefine YSHOP_ORDER_SALE_STATUS_KEY = new RedisKeyDefine(\"售后订单数据缓存\",\n            \"yshop_after_order_cache:%s\", // 参数为访问uid+key\n            STRING, String.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);\n\n    RedisKeyDefine YSHOP_ORDER_COUNT_CACHE_KEY = new RedisKeyDefine(\"统计订单数据缓存\",\n            \"yshop_order_count_cache:%s\", // 参数为访问uid\n            STRING, CacheDto.class, RedisKeyDefine.TimeoutTypeEnum.FOREVER);\n\n    RedisKeyDefine YSHOP_ADMIN_ORDER_COUNT_CACHE_KEY = new RedisKeyDefine(\"后台统计订单数据缓存\",\n            \"yshop_admin_order_count_cache:\", // 参数为访问uid\n            STRING, CacheDto.class, RedisKeyDefine.TimeoutTypeEnum.FOREVER);\n\n    RedisKeyDefine YSHOP_WEB_PRINT_MECHINE_KEY = new RedisKeyDefine(\"打印机token\",\n            \"yshop_web_print_machine_cache:%s\", // 参数为访问shopid\n            STRING, String.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/dal/redis/ofterorder/AfterOrderRedisDAO.java",
    "content": "package co.yixiang.yshop.module.order.dal.redis.ofterorder;\n\nimport co.yixiang.yshop.module.order.service.storeorder.dto.CacheDto;\nimport org.springframework.data.redis.core.StringRedisTemplate;\nimport org.springframework.stereotype.Repository;\n\nimport jakarta.annotation.Resource;\n\nimport static co.yixiang.yshop.module.order.dal.redis.RedisKeyConstants.YSHOP_ORDER_SALE_STATUS_KEY;\n\n/**\n * {@link CacheDto} 的 RedisDAO\n *\n * @author yshop\n */\n@Repository\npublic class AfterOrderRedisDAO {\n\n    @Resource\n    private StringRedisTemplate stringRedisTemplate;\n\n    public String get(String key,Long uid) {\n        String redisKey = formatKey(key+uid);\n        return stringRedisTemplate.opsForValue().get(redisKey);\n    }\n\n    public void set(Long uid,String key,String o) {\n        String redisKey = formatKey(key + uid);\n\n\n        stringRedisTemplate.opsForValue().set(redisKey, o);\n    }\n\n    public void delete(String key,Long uid) {\n        String redisKey = formatKey(key+uid);\n        stringRedisTemplate.delete(redisKey);\n    }\n\n\n\n    private static String formatKey(String key) {\n        return String.format(YSHOP_ORDER_SALE_STATUS_KEY.getKeyTemplate(), key);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/dal/redis/order/AsyncCountRedisDAO.java",
    "content": "package co.yixiang.yshop.module.order.dal.redis.order;\n\nimport co.yixiang.yshop.framework.common.util.json.JsonUtils;\nimport co.yixiang.yshop.framework.tenant.core.context.TenantContextHolder;\nimport co.yixiang.yshop.module.member.controller.app.user.vo.AppUserOrderCountVo;\nimport co.yixiang.yshop.module.order.controller.admin.storeorder.vo.ShoperOrderTimeDataVo;\nimport co.yixiang.yshop.module.order.service.storeorder.dto.OrderTimeDataDto;\nimport org.springframework.data.redis.core.StringRedisTemplate;\nimport org.springframework.stereotype.Repository;\n\nimport jakarta.annotation.Resource;\n\nimport static co.yixiang.yshop.module.order.dal.redis.RedisKeyConstants.YSHOP_ADMIN_ORDER_COUNT_CACHE_KEY;\n\n/**\n * {@link AppUserOrderCountVo} 的 RedisDAO\n *\n * @author yshop\n */\n@Repository\npublic class AsyncCountRedisDAO {\n\n    @Resource\n    private StringRedisTemplate stringRedisTemplate;\n\n    public OrderTimeDataDto get() {\n        String redisKey = formatKey();\n        return JsonUtils.parseObject(stringRedisTemplate.opsForValue().get(redisKey), OrderTimeDataDto.class);\n    }\n\n    public void set(OrderTimeDataDto orderTimeDataDto) {\n        String redisKey = formatKey();\n        stringRedisTemplate.opsForValue().set(redisKey, JsonUtils.toJsonString(orderTimeDataDto));\n    }\n\n    public void delete(Long uid) {\n        String redisKey = YSHOP_ADMIN_ORDER_COUNT_CACHE_KEY.getKeyTemplate();\n        stringRedisTemplate.delete(redisKey);\n    }\n\n\n    private static String formatKey() {\n        return String.format(YSHOP_ADMIN_ORDER_COUNT_CACHE_KEY.getKeyTemplate());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/dal/redis/order/AsyncOrderRedisDAO.java",
    "content": "package co.yixiang.yshop.module.order.dal.redis.order;\n\nimport cn.hutool.core.util.IdUtil;\nimport co.yixiang.yshop.framework.common.constant.ShopConstants;\nimport co.yixiang.yshop.framework.common.util.json.JsonUtils;\nimport co.yixiang.yshop.module.member.controller.app.user.vo.AppUserOrderCountVo;\nimport co.yixiang.yshop.module.order.service.storeorder.dto.CacheDto;\nimport org.springframework.data.redis.core.StringRedisTemplate;\nimport org.springframework.stereotype.Repository;\n\nimport jakarta.annotation.Resource;\nimport java.util.concurrent.TimeUnit;\n\nimport static co.yixiang.yshop.module.order.dal.redis.RedisKeyConstants.YSHOP_ORDER_CACHE_KEY;\nimport static co.yixiang.yshop.module.order.dal.redis.RedisKeyConstants.YSHOP_ORDER_COUNT_CACHE_KEY;\n\n/**\n * {@link AppUserOrderCountVo} 的 RedisDAO\n *\n * @author yshop\n */\n@Repository\npublic class AsyncOrderRedisDAO {\n\n    @Resource\n    private StringRedisTemplate stringRedisTemplate;\n\n    public AppUserOrderCountVo get(Long uid) {\n        String redisKey = formatKey(\"\"+uid);\n        return JsonUtils.parseObject(stringRedisTemplate.opsForValue().get(redisKey), AppUserOrderCountVo.class);\n    }\n\n    public void set(AppUserOrderCountVo appUserOrderCountVo, Long uid) {\n        String redisKey = formatKey(\"\" + uid);\n        stringRedisTemplate.opsForValue().set(redisKey, JsonUtils.toJsonString(appUserOrderCountVo));\n    }\n\n    public void delete(Long uid) {\n        String redisKey = formatKey(\"\"+uid);\n        stringRedisTemplate.delete(redisKey);\n    }\n\n\n    private static String formatKey(String key) {\n        return String.format(YSHOP_ORDER_COUNT_CACHE_KEY.getKeyTemplate(), key);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/dal/redis/order/OrderRedisDAO.java",
    "content": "package co.yixiang.yshop.module.order.dal.redis.order;\n\nimport cn.hutool.core.util.IdUtil;\nimport co.yixiang.yshop.framework.common.constant.ShopConstants;\nimport co.yixiang.yshop.framework.common.util.json.JsonUtils;\nimport co.yixiang.yshop.module.order.service.storeorder.dto.CacheDto;\nimport org.springframework.data.redis.core.StringRedisTemplate;\nimport org.springframework.stereotype.Repository;\n\nimport jakarta.annotation.Resource;\nimport java.util.concurrent.TimeUnit;\n\nimport static co.yixiang.yshop.module.order.dal.redis.RedisKeyConstants.YSHOP_ORDER_CACHE_KEY;\n\n/**\n * {@link CacheDto} 的 RedisDAO\n *\n * @author yshop\n */\n@Repository\npublic class OrderRedisDAO {\n\n    @Resource\n    private StringRedisTemplate stringRedisTemplate;\n\n    public CacheDto get(String key,Long uid) {\n        String redisKey = formatKey(key+uid);\n        return JsonUtils.parseObject(stringRedisTemplate.opsForValue().get(redisKey), CacheDto.class);\n    }\n\n    public String set(CacheDto cacheDto,Long uid) {\n        String key = IdUtil.simpleUUID();\n        String redisKey = formatKey(key + uid);\n        long time =  ShopConstants.YSHOP_ORDER_CACHE_TIME;\n        stringRedisTemplate.opsForValue().set(redisKey, JsonUtils.toJsonString(cacheDto), time, TimeUnit.SECONDS);\n        return key;\n    }\n\n    public void delete(String key,Long uid) {\n        String redisKey = formatKey(key+uid);\n        stringRedisTemplate.delete(redisKey);\n    }\n\n\n\n    private static String formatKey(String key) {\n        return String.format(YSHOP_ORDER_CACHE_KEY.getKeyTemplate(), key);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/dal/redis/order/PrintMechinRedisDAO.java",
    "content": "package co.yixiang.yshop.module.order.dal.redis.order;\n\n\nimport org.springframework.data.redis.core.StringRedisTemplate;\nimport org.springframework.stereotype.Repository;\n\nimport jakarta.annotation.Resource;\nimport java.util.concurrent.TimeUnit;\n\nimport static co.yixiang.yshop.module.order.dal.redis.RedisKeyConstants.YSHOP_WEB_PRINT_MECHINE_KEY;\nimport static co.yixiang.yshop.module.store.dal.redis.RedisKeyConstants.YSHOP_WEB_PRINT_TOKEN_KEY;\n\n\n/**\n\n *\n * @author yshop\n */\n@Repository\npublic class PrintMechinRedisDAO {\n\n    @Resource\n    private StringRedisTemplate stringRedisTemplate;\n\n    public String get(Long shopId) {\n        String redisKey = formatKey(shopId);\n        return stringRedisTemplate.opsForValue().get(redisKey);\n    }\n\n    public void set(Long shopId,String o) {\n        String redisKey = formatKey(shopId);\n        stringRedisTemplate.opsForValue().set(redisKey, o);\n    }\n\n    public void delete(Long shopId) {\n        String redisKey = formatKey(shopId);\n        stringRedisTemplate.delete(redisKey);\n    }\n\n\n\n    private static String formatKey(Long shopId) {\n        return String.format(YSHOP_WEB_PRINT_MECHINE_KEY.getKeyTemplate(), shopId);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/handle/OrderAutoConfirmListener.java",
    "content": "package co.yixiang.yshop.module.order.handle;\n\nimport cn.hutool.core.util.ObjectUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.constant.ShopConstants;\nimport co.yixiang.yshop.framework.tenant.core.util.TenantUtils;\nimport co.yixiang.yshop.module.message.redismq.DelayedQueueListener;\nimport co.yixiang.yshop.module.message.redismq.msg.OrderMsg;\nimport co.yixiang.yshop.module.order.service.storeorder.AppStoreOrderService;\nimport jakarta.annotation.Resource;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Component;\n\n/**\n * 自动收货订单监听\n */\n@Component\n@Slf4j\npublic class OrderAutoConfirmListener implements DelayedQueueListener<OrderMsg> {\n    @Resource\n    private AppStoreOrderService appStoreOrderService;\n    @Override\n    public String delayedQueueKey() {\n        return ShopConstants.REDIS_ORDER_OUTTIME_UNCONFIRM;\n    }\n\n    @Override\n    public void consume(OrderMsg message) throws Exception {\n        if(ObjectUtil.isNotNull(message) && StrUtil.isNotEmpty(message.getOrderId())) {\n            TenantUtils.executeIgnore(() -> {\n                appStoreOrderService.takeOrder(message.getOrderId(),null);\n                log.info(\"订单编号:{}自动确认收货成功\",message.getOrderId());\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/handle/OrderUnPayListener.java",
    "content": "package co.yixiang.yshop.module.order.handle;\n\nimport cn.hutool.core.util.ObjectUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.constant.ShopConstants;\nimport co.yixiang.yshop.framework.tenant.core.util.TenantUtils;\nimport co.yixiang.yshop.module.message.redismq.DelayedQueueListener;\nimport co.yixiang.yshop.module.message.redismq.msg.OrderMsg;\nimport co.yixiang.yshop.module.order.service.storeorder.AppStoreOrderService;\nimport jakarta.annotation.Resource;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Component;\n\n/**\n * 未支付订单监听\n */\n@Component\n@Slf4j\npublic class OrderUnPayListener implements DelayedQueueListener<OrderMsg> {\n    @Resource\n    private AppStoreOrderService appStoreOrderService;\n    @Override\n    public String delayedQueueKey() {\n        return ShopConstants.REDIS_ORDER_OUTTIME_UNPAY_QUEUE;\n    }\n\n    @Override\n    public void consume(OrderMsg message) throws Exception {\n        if(ObjectUtil.isNotNull(message) && StrUtil.isNotEmpty(message.getOrderId())) {\n            TenantUtils.executeIgnore(() -> {\n                appStoreOrderService.cancelOrder(message.getOrderId(),null);\n                log.info(\"订单编号:{}自动取消订单成功\",message.getOrderId());\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/handle/RedisDelayHandle.java",
    "content": "package co.yixiang.yshop.module.order.handle;\n\nimport cn.hutool.core.thread.ExecutorBuilder;\nimport cn.hutool.core.thread.ThreadFactoryBuilder;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.constant.ShopConstants;\nimport co.yixiang.yshop.module.order.service.storeorder.AppStoreOrderService;\nimport lombok.SneakyThrows;\nimport lombok.extern.slf4j.Slf4j;\nimport org.redisson.api.RBlockingDeque;\nimport org.redisson.api.RedissonClient;\nimport org.springframework.stereotype.Component;\n\nimport jakarta.annotation.PostConstruct;\nimport jakarta.annotation.Resource;\nimport java.util.concurrent.ExecutorService;\nimport java.util.concurrent.ThreadFactory;\n\n/**\n * 延时队列消费 已经被废弃\n * @author hupeng\n * @date 2023.7.27\n */\n//@Component\n@Slf4j\n@Deprecated\npublic class RedisDelayHandle {\n    @Resource\n    private RedissonClient redissonClient;\n    @Resource\n    private AppStoreOrderService appStoreOrderService;\n\n    @PostConstruct\n    public void startJobTimer() {\n        ThreadFactory threadFactory = new ThreadFactoryBuilder().setNamePrefix(\"delay-job-service\").build();\n        log.info(\"========延时队列开始=========\");\n        ExecutorService executorService = ExecutorBuilder.create()\n                .setCorePoolSize(2)\n                .setMaxPoolSize(10)\n                .setKeepAliveTime(0)\n                .setThreadFactory(threadFactory)\n                .build();\n\n        executorService.execute(new ExecutorTaskUnPay());\n        executorService.execute(new ExecutorTaskUnConfirm());\n    }\n\n    class ExecutorTaskUnPay implements Runnable {\n        @SneakyThrows\n        @Override\n        public void run() {\n            RBlockingDeque<String> blockingDeque = redissonClient\n                    .getBlockingDeque(ShopConstants.REDIS_ORDER_OUTTIME_UNPAY_QUEUE);\n            while (true) {\n                try {\n                    log.info(\"======延迟队列监听未支付订单======\");\n                    String orderId = blockingDeque.take();\n                    log.info(\"延迟队列监听未支付订单订单编号：{}\", orderId);\n                    if(StrUtil.isNotEmpty(orderId)) {\n                        appStoreOrderService.cancelOrder(orderId,null);\n                    }\n                } catch (Exception e) {\n                    log.error(\"Redission延迟队列监测异常中断,忽略此消息：{}\", e.getMessage());\n                    return;\n                }\n\n            }\n\n        }\n    }\n\n\n    class ExecutorTaskUnConfirm implements Runnable {\n        @SneakyThrows\n        @Override\n        public void run() {\n            RBlockingDeque<String> blockingDeque = redissonClient\n                    .getBlockingDeque(ShopConstants.REDIS_ORDER_OUTTIME_UNCONFIRM);\n            while (true) {\n                try {\n                    log.info(\"======延迟队列监听收货超时订单======\");\n                    String orderId = blockingDeque.take();\n                    log.info(\"延迟队列监听收货超时订单订单编号：{}\", orderId);\n                    if(StrUtil.isNotEmpty(orderId)) {\n                        appStoreOrderService.takeOrder(orderId,null);\n                    }\n                } catch (Exception e) {\n                    log.error(\"Redission延迟队列监测异常中断,忽略此消息：{}\", e.getMessage());\n                    return;\n                }\n\n            }\n\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/mq/consumer/PayNoticeConsumer.java",
    "content": "package co.yixiang.yshop.module.order.mq.consumer;\n\nimport co.yixiang.yshop.framework.mq.redis.core.stream.AbstractRedisStreamMessageListener;\nimport co.yixiang.yshop.module.order.service.storeorder.AppStoreOrderService;\nimport co.yixiang.yshop.module.pay.mq.message.PayNoticeMessage;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Component;\n\nimport jakarta.annotation.Resource;\n\n/**\n * 消息队列处理支付消息\n */\n@Component\n@Slf4j\npublic class PayNoticeConsumer extends AbstractRedisStreamMessageListener<PayNoticeMessage> {\n\n    @Resource\n    private AppStoreOrderService appStoreOrderService;\n\n    @Override\n    public void onMessage(PayNoticeMessage message) {\n        log.info(\"[onMessage][支付消息内容({})]\", message);\n        appStoreOrderService.paySuccess(message.getOrderId(),message.getPayType());\n\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorder/AppStoreOrderService.java",
    "content": "package co.yixiang.yshop.module.order.service.storeorder;\n\nimport co.yixiang.yshop.module.order.controller.app.order.param.AppComputeOrderParam;\nimport co.yixiang.yshop.module.order.controller.app.order.param.AppOrderParam;\nimport co.yixiang.yshop.module.order.controller.app.order.param.AppPayParam;\nimport co.yixiang.yshop.module.order.controller.app.order.vo.AppConfirmOrderVo;\nimport co.yixiang.yshop.module.order.controller.app.order.vo.AppStoreOrderQueryVo;\nimport co.yixiang.yshop.module.order.dal.dataobject.storeorder.StoreOrderDO;\nimport com.baomidou.mybatisplus.extension.service.IService;\n\nimport java.math.BigDecimal;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * 订单 Service 接口\n *\n * @author yshop\n */\npublic interface AppStoreOrderService extends IService<StoreOrderDO> {\n\n\n\n    /**\n     * 订单信息\n     * @param unique 唯一值或者单号\n     * @param uid 用户id\n     * @return YxStoreOrderQueryVo\n     */\n    AppStoreOrderQueryVo getOrderInfo(String unique, Long uid);\n\n\n    /**\n     * 创建订单\n     * @param uid 用户uid\n     * @param param param\n     * @return map\n     */\n    Map<String, Object> createOrder(Long uid, AppOrderParam param);\n\n\n    /**\n     * 第三方支付\n     * @param param 订单\n     * @param uid 用户id\n     */\n    Map<String, Object> pay(Long uid, AppPayParam param);\n\n\n    /**\n     * 余额支付\n     * @param orderId 订单号\n     * @param uid 用户id\n     */\n    void yuePay(String orderId,Long uid);\n\n    /**\n     * 支付成功后操作\n     * @param orderId 订单号\n     * @param payType 支付方式\n     */\n    void paySuccess(String orderId,String payType);\n\n\n    /**\n     * 订单列表\n     * @param uid 用户id\n     * @param type OrderStatusEnum\n     * @param page page\n     * @param limit limit\n     * @return list\n     */\n    List<AppStoreOrderQueryVo> orderList(Long uid, int type, int page, int limit);\n\n    /**\n     * 处理订单返回的状态\n     * @param order order\n     * @return YxStoreOrderQueryVo\n     */\n    AppStoreOrderQueryVo handleOrder(AppStoreOrderQueryVo order);\n\n    /**\n     * 订单确认收货\n     * @param orderId 单号\n     * @param uid uid\n     */\n    void takeOrder(String orderId,Long uid);\n\n    /**\n     * 申请退款\n     * @param explain 退款备注\n     * @param Img 图片\n     * @param text 理由\n     * @param orderId 订单号\n     * @param uid uid\n     */\n    void orderApplyRefund(String explain,String Img,String text,String orderId, Long uid);\n\n    /**\n     * 删除订单\n     * @param orderId 单号\n     * @param uid uid\n     */\n    void removeOrder(String orderId,Long uid);\n\n    /**\n     * 未付款取消订单\n     * @param orderId 订单号\n     * @param uid 用户id\n     */\n    void cancelOrder(String orderId,Long uid);\n\n\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorder/AppStoreOrderServiceImpl.java",
    "content": "package co.yixiang.yshop.module.order.service.storeorder;\n\nimport cn.hutool.core.util.IdUtil;\nimport cn.hutool.core.util.NumberUtil;\nimport cn.hutool.core.util.ObjectUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.constant.ShopConstants;\nimport co.yixiang.yshop.framework.common.enums.OrderInfoEnum;\nimport co.yixiang.yshop.framework.common.enums.PayIdEnum;\nimport co.yixiang.yshop.framework.common.enums.ShopCommonEnum;\nimport co.yixiang.yshop.framework.common.exception.ErrorCode;\nimport co.yixiang.yshop.framework.tenant.core.aop.TenantIgnore;\nimport co.yixiang.yshop.framework.tenant.core.context.TenantContextHolder;\nimport co.yixiang.yshop.module.coupon.dal.dataobject.couponuser.CouponUserDO;\nimport co.yixiang.yshop.module.coupon.service.couponuser.AppCouponUserService;\nimport co.yixiang.yshop.module.member.controller.app.user.vo.AppUserQueryVo;\nimport co.yixiang.yshop.module.member.dal.dataobject.user.MemberUserDO;\nimport co.yixiang.yshop.module.member.dal.dataobject.useraddress.UserAddressDO;\nimport co.yixiang.yshop.module.member.dal.dataobject.userbill.UserBillDO;\nimport co.yixiang.yshop.module.member.enums.BillDetailEnum;\nimport co.yixiang.yshop.module.member.service.user.MemberUserService;\nimport co.yixiang.yshop.module.member.service.useraddress.AppUserAddressService;\nimport co.yixiang.yshop.module.member.service.userbill.UserBillService;\nimport co.yixiang.yshop.module.message.enums.WechatTempateEnum;\nimport co.yixiang.yshop.module.message.mq.producer.WeixinNoticeProducer;\nimport co.yixiang.yshop.module.message.redismq.msg.OrderMsg;\nimport co.yixiang.yshop.module.order.controller.app.order.param.AppOrderParam;\nimport co.yixiang.yshop.module.order.controller.app.order.param.AppPayParam;\nimport co.yixiang.yshop.module.order.controller.app.order.vo.AppStoreOrderQueryVo;\nimport co.yixiang.yshop.module.order.convert.storeorder.StoreOrderConvert;\nimport co.yixiang.yshop.module.order.dal.dataobject.ordernumber.OrderNumberDO;\nimport co.yixiang.yshop.module.order.dal.dataobject.storeorder.StoreOrderDO;\nimport co.yixiang.yshop.module.order.dal.dataobject.storeordercartinfo.StoreOrderCartInfoDO;\nimport co.yixiang.yshop.module.order.dal.mysql.ordernumber.OrderNumberMapper;\nimport co.yixiang.yshop.module.order.dal.mysql.storeorder.StoreOrderMapper;\nimport co.yixiang.yshop.module.order.enums.AppFromEnum;\nimport co.yixiang.yshop.module.order.enums.OrderLogEnum;\nimport co.yixiang.yshop.module.order.enums.OrderStatusEnum;\nimport co.yixiang.yshop.module.order.enums.PayTypeEnum;\nimport co.yixiang.yshop.module.order.service.storeorder.dto.StatusDto;\nimport co.yixiang.yshop.module.order.service.storeordercartinfo.StoreOrderCartInfoService;\nimport co.yixiang.yshop.module.order.service.storeorderstatus.StoreOrderStatusService;\nimport co.yixiang.yshop.module.pay.dal.dataobject.merchantdetails.MerchantDetailsDO;\nimport co.yixiang.yshop.module.pay.service.merchantdetails.MerchantDetailsService;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproduct.StoreProductDO;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproductattrvalue.StoreProductAttrValueDO;\nimport co.yixiang.yshop.module.product.service.storeproduct.AppStoreProductService;\nimport co.yixiang.yshop.module.product.service.storeproductattrvalue.StoreProductAttrValueService;\nimport co.yixiang.yshop.module.store.convert.storeshop.StoreShopConvert;\nimport co.yixiang.yshop.module.store.dal.dataobject.storeshop.StoreShopDO;\nimport co.yixiang.yshop.module.store.service.storeshop.AppStoreShopService;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.egzosn.pay.spring.boot.core.PayServiceManager;\nimport com.egzosn.pay.spring.boot.core.bean.MerchantPayOrder;\nimport jakarta.annotation.Resource;\nimport lombok.extern.slf4j.Slf4j;\nimport org.redisson.api.RBlockingDeque;\nimport org.redisson.api.RDelayedQueue;\nimport org.redisson.api.RLock;\nimport org.redisson.api.RedissonClient;\nimport org.redisson.client.codec.StringCodec;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Propagation;\nimport org.springframework.transaction.annotation.Transactional;\nimport org.springframework.validation.annotation.Validated;\n\nimport java.io.UnsupportedEncodingException;\nimport java.math.BigDecimal;\nimport java.net.URLEncoder;\nimport java.time.LocalDateTime;\nimport java.util.*;\nimport java.util.concurrent.TimeUnit;\nimport java.util.stream.Collectors;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.member.enums.ErrorCodeConstants.COUPON_NOT_CONDITION;\nimport static co.yixiang.yshop.module.member.enums.ErrorCodeConstants.USER_ADDRESS_NOT_EXISTS;\nimport static co.yixiang.yshop.module.order.enums.ErrorCodeConstants.*;\n\n/**\n * 订单 Service 实现类\n *\n * @author yshop\n */\n@Slf4j\n@Service\n@Validated\npublic class AppStoreOrderServiceImpl extends ServiceImpl<StoreOrderMapper,StoreOrderDO> implements AppStoreOrderService {\n\n    @Resource\n    private StoreOrderMapper storeOrderMapper;\n\n    @Resource\n    private AppUserAddressService appUserAddressService;\n    @Resource\n    private MemberUserService userService;\n    @Resource\n    private AppStoreProductService appStoreProductService;\n    @Resource\n    private StoreOrderCartInfoService storeOrderCartInfoService;\n    @Resource\n    private StoreOrderStatusService storeOrderStatusService;\n    @Resource\n    private UserBillService billService;\n    @Resource\n    private PayServiceManager manager;\n    @Resource\n    private WeixinNoticeProducer weixinNoticeProducer;\n    @Resource\n    private RedissonClient redissonClient;\n    @Resource\n    private StoreProductAttrValueService storeProductAttrValueService;\n    @Resource\n    private AppStoreShopService appStoreShopService;\n    @Resource\n    private OrderNumberMapper orderNumberMapper;\n    @Resource\n    private AppCouponUserService appCouponUserService;\n    @Resource\n    private AsyncStoreOrderService asyncStoreOrderService;\n    @Resource\n    private MerchantDetailsService merchantDetailsService;\n\n    private static final String LOCK_KEY = \"cart:check:stock:lock\";\n    private static final String STOCK_LOCK_KEY = \"cart:do:stock:lock\";\n\n\n\n\n    /**\n     * 订单信息\n     *\n     * @param unique 唯一值或者单号\n     * @param uid    用户id\n     * @return YxStoreOrderQueryVo\n     */\n    @Override\n    public AppStoreOrderQueryVo getOrderInfo(String unique, Long uid) {\n        LambdaQueryWrapper<StoreOrderDO> wrapper = new LambdaQueryWrapper<>();\n        wrapper.and(\n                i -> i.eq(StoreOrderDO::getOrderId, unique).or().eq(StoreOrderDO::getUnique, unique).or()\n                        .eq(StoreOrderDO::getExtendOrderId, unique));\n        if (uid != null) {\n            wrapper.eq(StoreOrderDO::getUid, uid);\n        }\n\n        AppStoreOrderQueryVo appStoreOrderQueryVo = StoreOrderConvert.INSTANCE.convert1(storeOrderMapper.selectOne(wrapper));\n        return appStoreOrderQueryVo;\n    }\n\n\n    /**\n     * 创建订单\n     *\n     * @param uid 用户uid\n     * @param param    param\n     * @return YxStoreOrder\n     */\n    @Override\n    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)\n    public Map<String, Object> createOrder(Long uid,  AppOrderParam param) {\n        if(OrderLogEnum.ORDER_TAKE_DESK.getValue().equals(param.getOrderType())\n                && StrUtil.isBlank(param.getDeskNumber())){\n            throw exception(STORE_ORDER_DESK_NOT);\n        }\n        //转换参数\n        List<String> productIds = param.getProductId();\n        List<String> numbers = param.getNumber();\n        List<String> specs = param.getSpec();\n\n\n        Integer totalNum = 0;\n        List<String> cartIds = new ArrayList<>();\n\n        StoreShopDO storeShopDO = appStoreShopService.getById(param.getShopId());\n\n        BigDecimal sumPrice = BigDecimal.ZERO;\n        BigDecimal couponPrice = BigDecimal.ZERO;\n        BigDecimal postagePrice = storeShopDO.getDeliveryPrice();\n        BigDecimal deductionPrice = BigDecimal.ZERO;\n\n        //对库存检查加锁\n        RLock lock = redissonClient.getLock(LOCK_KEY);\n        if (lock.tryLock()){\n            try {\n                for (int i = 0;i < productIds.size();i++){\n                    String newSku = StrUtil.replace(specs.get(i),\"|\",\",\");\n                    appStoreProductService.checkProductStock(uid, Long.valueOf(productIds.get(i)),\n                            Integer.valueOf(numbers.get(i)), newSku);\n                    totalNum += Integer.valueOf(numbers.get(i));\n\n                    StoreProductAttrValueDO storeProductAttrValue = storeProductAttrValueService\n                            .getOne(Wrappers.<StoreProductAttrValueDO>lambdaQuery()\n                                    .eq(StoreProductAttrValueDO::getSku, newSku)\n                                    .eq(StoreProductAttrValueDO::getProductId, productIds.get(i)));\n\n                    sumPrice = NumberUtil.add(sumPrice, NumberUtil.mul(numbers.get(i),\n                            storeProductAttrValue.getPrice().toString()));\n                }\n\n            }catch (Exception ex) {\n                log.error(\"[checkProductStock][执行异常]\", ex);\n                throw exception(new ErrorCode(999999,ex.getMessage()));\n            } finally {\n                lock.unlock();\n            }\n        }\n\n        //计算优惠券价格\n        if(StrUtil.isNotBlank(param.getCouponId())){\n            CouponUserDO couponUserDO = appCouponUserService.getById(param.getCouponId());\n            if(couponUserDO != null){\n                if(couponUserDO.getLeast().compareTo(sumPrice) > 0) {\n                    throw exception(COUPON_NOT_CONDITION);\n                }\n                couponPrice = couponUserDO.getValue();\n\n                //使用了优惠券扣优惠券\n                couponUserDO.setStatus(ShopCommonEnum.IS_STATUS_1.getValue());\n                appCouponUserService.updateById(couponUserDO);\n\n            }\n        }\n\n\n        BigDecimal payPrice = BigDecimal.ZERO;\n        //计算最终支付价格\n        if(OrderLogEnum.ORDER_TAKE_OUT.getValue().equals(param.getOrderType())){\n            payPrice = NumberUtil.sub(NumberUtil.add(sumPrice,postagePrice),couponPrice,deductionPrice);\n        }else{\n            payPrice = NumberUtil.sub(sumPrice,couponPrice,deductionPrice);\n        }\n\n\n\n        //计算奖励积分\n        BigDecimal gainIntegral = this.getGainIntegral(productIds);\n\n        StoreOrderDO storeOrder = new StoreOrderDO();\n        String orderSn = \"\";\n        //todo 桌面点餐功能 商业版本才有 官网地址：https://www.yixiang.co\n        if(OrderLogEnum.ORDER_TAKE_DESK.getValue().equals(param.getOrderType())\n                && StrUtil.isNotBlank(param.getOrderId())){\n\n        }else{\n            //生成分布式唯一值\n            orderSn = IdUtil.getSnowflake(0, 0).nextIdStr();\n\n            //添加取餐表\n            OrderNumberDO orderNumberDO = OrderNumberDO.builder().orderId(orderSn).build();\n            orderNumberMapper.insert(orderNumberDO);\n\n            //组合数据\n            LocalDateTime localDateTime = LocalDateTime.now();\n            storeOrder.setGetTime(localDateTime.plusMinutes(param.getGettime()));\n            storeOrder.setNumberId(orderNumberDO.getId());\n            storeOrder.setShopId(storeShopDO.getId());\n            storeOrder.setShopName(storeShopDO.getName());\n            storeOrder.setUid(uid);\n            storeOrder.setOrderId(orderSn);\n            //处理如果是外卖 地址\n            if(OrderLogEnum.ORDER_TAKE_OUT.getValue().equals(param.getOrderType())){\n                if (StrUtil.isEmpty(param.getAddressId())) {\n                    throw exception(SELECT_ADDRESS);\n                }\n                UserAddressDO userAddress = appUserAddressService.getById(param.getAddressId());\n                if (ObjectUtil.isNull(userAddress)) {\n                    throw exception(USER_ADDRESS_NOT_EXISTS);\n                }\n                storeOrder.setRealName(userAddress.getRealName());\n                storeOrder.setUserPhone(userAddress.getPhone());\n                storeOrder.setUserAddress(userAddress.getAddress() + \" \" + userAddress.getDetail());\n            }\n            storeOrder.setCartId(StrUtil.join(\",\", cartIds));\n            storeOrder.setTotalNum(totalNum);\n            storeOrder.setTotalPrice(sumPrice);\n            storeOrder.setTotalPostage(storeShopDO.getDeliveryPrice());\n\n            storeOrder.setCouponId(StrUtil.isBlank(param.getCouponId()) ? 0 : Integer.valueOf(param.getCouponId()));\n            storeOrder.setCouponPrice(couponPrice);\n            storeOrder.setPayPrice(payPrice);\n            storeOrder.setPayPostage(storeShopDO.getDeliveryPrice());\n            storeOrder.setDeductionPrice(deductionPrice);\n            storeOrder.setPaid(OrderInfoEnum.PAY_STATUS_0.getValue());\n            storeOrder.setPayType(param.getPayType());\n            storeOrder.setUseIntegral(BigDecimal.ZERO);\n            storeOrder.setBackIntegral(BigDecimal.ZERO);\n            storeOrder.setGainIntegral(gainIntegral);\n            storeOrder.setMark(param.getRemark());\n            storeOrder.setCost(BigDecimal.ZERO);\n            //storeOrder.setUnique(key);\n            storeOrder.setShippingType(OrderInfoEnum.SHIPPIING_TYPE_1.getValue());\n            storeOrder.setOrderType(param.getOrderType());\n\n\n            boolean res = this.save(storeOrder);\n            if (!res) {\n                throw exception(ORDER_GEN_FAIL);\n            }\n        }\n\n\n        // 减库存加销量\n        this.deStockIncSale(productIds,numbers,specs);\n\n\n        //保存购物车商品信息，异步执行\n        storeOrderCartInfoService.saveCartInfo(storeOrder.getId(), storeOrder.getOrderId(),productIds,numbers,specs);\n\n        ////todo 桌面点餐功能 商业版本才有 官网地址：https://www.yixiang.co异步更新桌面信息\n\n\n\n        //增加状态\n        storeOrderStatusService.create(uid,storeOrder.getId(), OrderLogEnum.CREATE_ORDER.getValue(),\n                OrderLogEnum.CREATE_ORDER.getDesc());\n\n        //堂食点餐不需要\n        if(!OrderLogEnum.ORDER_TAKE_DESK.getValue().equals(param.getOrderType())) {\n            //加入延时队列，30分钟自动取消\n            try {\n                RBlockingDeque<Object> blockingDeque = redissonClient.getBlockingDeque(ShopConstants.REDIS_ORDER_OUTTIME_UNPAY_QUEUE );\n                RDelayedQueue<Object> delayedQueue = redissonClient.getDelayedQueue(blockingDeque);\n                delayedQueue.offer(OrderMsg.builder().orderId(orderSn).build(), ShopConstants.ORDER_OUTTIME_UNPAY, TimeUnit.MINUTES);\n                String s = TimeUnit.SECONDS.toSeconds(ShopConstants.ORDER_OUTTIME_UNPAY) + \"分钟\";\n                log.info(\"添加延时队列成功 ，延迟时间：\" + s + \"订单编号：\" + orderSn);\n            } catch (Exception e) {\n                log.error(\"添加延时队列失败：{}\",e.getMessage());\n            }\n        }\n\n        Map<String,Object> map = new HashMap<>();\n        map.put(\"orderId\",orderSn);\n        return map;\n    }\n\n    /**\n     * 第三方支付\n     * @param uid  用户id\n     * @param param 订单参数\n     * @return\n     */\n    @Override\n    public Map<String, Object> pay(Long uid, AppPayParam param) {\n        AppStoreOrderQueryVo orderInfo = getOrderInfo(param.getUni(), uid);\n        UserBillDO userBillDO =  billService.getOne(new LambdaQueryWrapper<UserBillDO>().eq(UserBillDO::getUid,uid)\n                .eq(UserBillDO::getExtendField,param.getUni()));\n\n        if (ObjectUtil.isNull(orderInfo) && ObjectUtil.isNull(userBillDO)) {\n            throw exception(STORE_ORDER_NOT_EXISTS);\n        }\n        if(ObjectUtil.isNotNull(orderInfo) && orderInfo.getPaid().equals(OrderInfoEnum.PAY_STATUS_1.getValue())) {\n            throw exception(ORDER_PAY_FINISH);\n        }\n        if(ObjectUtil.isNotNull(userBillDO) && userBillDO.getStatus().equals(OrderInfoEnum.PAY_STATUS_1.getValue())) {\n            throw exception(ORDER_PAY_FINISH);\n        }\n\n        MemberUserDO memberUserDO = userService.getUser(uid);\n        Map<String, Object> map = new LinkedHashMap<>();\n        BigDecimal price = BigDecimal.ZERO;\n        String msg = \"\";\n        String detailsId = \"\";\n        if(orderInfo != null) {\n            price = orderInfo.getPayPrice();\n            msg = \"商品购买\";\n        }else if(userBillDO != null){\n            //todo 充值商业版本才有 官网购买：https://www.yixiang.co\n        }\n        switch (PayTypeEnum.toType(param.getPaytype())){\n            case WEIXIN:\n                if(AppFromEnum.H5.getValue().equals(param.getFrom())){\n                    detailsId = PayIdEnum.WX_WECHAT.getValue();\n                    //todo 如果启用微信H5支付充值 需要另外增加一个配置用于同步跳转页面 比如下面的增加了一个id=5的配置,微信支付公众号与H%配置是一样 基本\n//                    if(orderInfo != null) {\n//                        detailsId = \"4\";\n//                    }else{\n//                        detailsId = \"5\";\n//                    }\n                    MerchantPayOrder payOrder = new MerchantPayOrder(detailsId, \"MWEB\", msg,\n                            msg, price, param.getUni());\n\n                    Map<String, Object> payOrderInfo = manager.getOrderInfo(payOrder);\n                    MerchantDetailsDO merchantDetailsDO = merchantDetailsService.getMerchantDetails(detailsId);\n                    String url = merchantDetailsDO.getReturnUrl();\n\n                    String newUrl = \"\";\n                    try {\n                        newUrl =  String.format(\"%s%s\", payOrderInfo.get(\"mweb_url\"), \"&redirect_url=\" + URLEncoder.encode(url,\"UTF-8\"));\n                    } catch (UnsupportedEncodingException e) {\n                        log.error(e.getMessage());\n                    }\n                    map.put(\"data\",newUrl);\n                    map.put(\"trade_type\",\"MWEB\");\n                } else if(AppFromEnum.WECHAT.getValue().equals(param.getFrom())){//微信公众号\n//                    MerchantPayOrder payOrder = new MerchantPayOrder(\"4\", \"JSAPI\", msg,\n//                            msg, price, param.getUni());\n                    MerchantPayOrder payOrder = new MerchantPayOrder(PayIdEnum.WX_WECHAT.getValue(), \"JSAPI\", msg,\n                            msg, price, param.getUni());\n                    payOrder.setOpenid(memberUserDO.getOpenid());\n                    map.put(\"data\",manager.getOrderInfo(payOrder));\n                    map.put(\"trade_type\",\"W-JSAPI\");\n                } else {//微信小程序\n                    MerchantPayOrder payOrder = new MerchantPayOrder(PayIdEnum.WX_MINIAPP.getValue(), \"JSAPI\", msg,\n                            msg, price, param.getUni());\n                    payOrder.setOpenid(memberUserDO.getRoutineOpenid());\n                    map.put(\"data\",manager.getOrderInfo(payOrder));\n                    map.put(\"trade_type\",\"JSAPI\");\n\n                }\n                break;\n            case YUE:\n                this.yuePay(param.getUni(), uid);\n                map.put(\"status\",\"ok\");\n                break;\n            case ALI:\n                //h5支付\n                detailsId = PayIdEnum.ALI_H5.getValue();\n                //todo 如果启用支付宝H5支付充值 需要另外增加一个配置用于同步跳转页面 比如下面的增加了一个id=6的配置\n//                if(orderInfo != null) {\n//                    detailsId = \"1\";\n//                }else{\n//                    detailsId = \"6\";\n//                }\n                MerchantPayOrder payOrder = new MerchantPayOrder(detailsId, \"WAP\", msg,\n                        msg, price, param.getUni());\n                map.put(\"data\",manager.toPay(payOrder));\n\n            default:\n        }\n        return map;\n    }\n\n    /**\n     * 余额支付\n     *\n     * @param orderId 订单号\n     * @param uid     用户id\n     */\n    @Override\n    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)\n    public void yuePay(String orderId, Long uid) {\n        AppStoreOrderQueryVo orderInfo = getOrderInfo(orderId, uid);\n        if (ObjectUtil.isNull(orderInfo)) {\n            throw exception(STORE_ORDER_NOT_EXISTS);\n        }\n\n        if (OrderInfoEnum.PAY_STATUS_1.getValue().equals(orderInfo.getPaid())) {\n            throw exception(ORDER_PAY_FINISH);\n        }\n\n        AppUserQueryVo userInfo = userService.getAppUser(uid);\n\n        if (userInfo.getNowMoney().compareTo(orderInfo.getPayPrice()) < 0) {\n            throw exception(PAY_YUE_NOT);\n        }\n\n        userService.decPrice(uid, orderInfo.getPayPrice());\n\n        //支付成功后处理\n        this.paySuccess(orderInfo.getOrderId(), PayTypeEnum.YUE.getValue());\n    }\n\n\n    /**\n     * 支付成功后操作\n     *\n     * @param orderId 订单号\n     * @param payType 支付方式\n     */\n    @Override\n    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)\n    @TenantIgnore\n    public void paySuccess(String orderId, String payType) {\n        //处理充值与会员卡订单\n        UserBillDO userBillDO =  billService.getOne(new LambdaQueryWrapper<UserBillDO>()\n                .eq(UserBillDO::getExtendField,orderId));\n        if(userBillDO != null) {\n            userBillDO.setStatus(ShopCommonEnum.IS_STATUS_1.getValue());\n            billService.updateById(userBillDO);\n        if(BillDetailEnum.TYPE_1.getValue().equals(userBillDO.getType())){\n                //充值\n                userService.incMoney(userBillDO.getUid(), userBillDO.getNumber());\n            }\n\n            return;\n        }\n\n        log.info(\"orderId:[{}]\",orderId);\n        AppStoreOrderQueryVo orderInfo = getOrderInfo(orderId, null);\n        log.info(\"orderInfo:[{}]\",orderInfo);\n        if(orderInfo == null){\n            return;\n        }\n\n        //更新订单状态\n        LambdaQueryWrapper<StoreOrderDO> wrapper = new LambdaQueryWrapper<>();\n        wrapper.eq(StoreOrderDO::getOrderId, orderId);\n        StoreOrderDO storeOrder = new StoreOrderDO();\n        storeOrder.setPaid(OrderInfoEnum.PAY_STATUS_1.getValue());\n        storeOrder.setPayType(payType);\n        storeOrder.setPayTime(LocalDateTime.now());\n\n        this.update(storeOrder, wrapper);\n\n        //增加用户购买次数\n        userService.incPayCount(orderInfo.getUid());\n        //增加状态\n        storeOrderStatusService.create(orderInfo.getUid(),orderInfo.getId(), OrderLogEnum.PAY_ORDER_SUCCESS.getValue(),\n                OrderLogEnum.PAY_ORDER_SUCCESS.getDesc());\n\n\n        MemberUserDO userInfo = userService.getUser(orderInfo.getUid());\n        //增加流水\n        String payTypeMsg = PayTypeEnum.WEIXIN.getDesc();\n        if (PayTypeEnum.YUE.getValue().equals(payType)) {\n            payTypeMsg = PayTypeEnum.YUE.getDesc();\n        }else if (PayTypeEnum.ALI.getValue().equals(payType)) {\n            payTypeMsg = PayTypeEnum.ALI.getDesc();\n        }else if(PayTypeEnum.CASH.getValue().equals(payType)){\n            payTypeMsg = PayTypeEnum.CASH.getDesc();\n        }\n        billService.expend(userInfo.getId(), \"购买商品\",\n                BillDetailEnum.CATEGORY_1.getValue(),\n                BillDetailEnum.TYPE_3.getValue(),\n                orderInfo.getPayPrice().doubleValue(), userInfo.getNowMoney().doubleValue(),\n                payTypeMsg + orderInfo.getPayPrice() + \"元购买商品\");\n\n\n\n        //发送消息队列进行推送消息,堂食不需要\n        if(!OrderLogEnum.ORDER_TAKE_DESK.getValue().equals(orderInfo.getOrderType()) &&\n                userInfo.getLoginType().equals(AppFromEnum.ROUNTINE.getValue())){\n            List<StoreOrderCartInfoDO> storeOrderCartInfoDOList = storeOrderCartInfoService\n                    .list(new LambdaQueryWrapper<StoreOrderCartInfoDO>()\n                    .eq(StoreOrderCartInfoDO::getOid,orderInfo.getId()));\n            List<String> names = storeOrderCartInfoDOList.stream().map(StoreOrderCartInfoDO::getTitle)\n                    .collect(Collectors.toList());\n            String productName = StrUtil.join(\",\",names);\n            weixinNoticeProducer.sendNoticeMessage(orderInfo.getUid(),WechatTempateEnum.PAY_SUCCESS.getValue(),\n                    WechatTempateEnum.SUBSCRIBE.getValue(),orderInfo.getOrderId(),\n                    \"\",\"\",\"\",\"\",orderInfo.getId(),orderInfo.getNumberId(),\n                    productName,orderInfo.getShopName());\n        }\n\n\n    }\n\n    /**\n     * 减库存增加销量\n     *\n     * @param productIds 商品id\n     * @param numbers 商品数量\n     * @param specs 商品规格\n     */\n    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)\n    public void deStockIncSale(List<String> productIds,List<String> numbers,List<String> specs) {\n\n\n        log.info(\"========减库存增加销量start=========\");\n        //对库存加锁\n        RLock lock = redissonClient.getLock(STOCK_LOCK_KEY);\n        if (lock.tryLock()) {\n            try {\n                for (int i = 0;i < productIds.size();i++){\n                    String newSku = StrUtil.replace(specs.get(i),\"|\",\",\");\n                    appStoreProductService.decProductStock(Integer.valueOf(numbers.get(i)),\n                            Long.valueOf(productIds.get(i)),\n                            newSku, 0L, \"\");\n                }\n            }catch (Exception ex) {\n                log.error(\"[deStockIncSale][执行异常]\", ex);\n                throw exception(new ErrorCode(999999,ex.getMessage()));\n            } finally {\n                lock.unlock();\n            }\n        }\n    }\n\n\n    /**\n     * 订单列表\n     *\n     * @param uid   用户id\n     * @param type  OrderStatusEnum\n     * @param page  page\n     * @param limit limit\n     * @return list\n     */\n    @Override\n    public List<AppStoreOrderQueryVo> orderList(Long uid, int type, int page, int limit) {\n        LambdaQueryWrapper<StoreOrderDO> wrapper = new LambdaQueryWrapper<>();\n        if (uid != null) {\n            wrapper.eq(StoreOrderDO::getUid, uid);\n        }\n        wrapper.eq(StoreOrderDO::getIsSystemDel,ShopCommonEnum.DELETE_0.getValue()).orderByDesc(StoreOrderDO::getId);\n       // wrapper.eq(StoreOrderDO::getPaid, OrderInfoEnum.PAY_STATUS_1.getValue())\n                //.eq(StoreOrderDO::getRefundStatus, OrderInfoEnum.REFUND_STATUS_0.getValue())\n                //.eq(StoreOrderDO::getStatus, OrderInfoEnum.STATUS_0.getValue())\n               // .orderByDesc(StoreOrderDO::getId);\n        switch (OrderStatusEnum.toType(type)) {\n            case STATUS__1:\n                break;\n            //未支付\n            case STATUS_0:\n                wrapper.eq(StoreOrderDO::getPaid, OrderInfoEnum.PAY_STATUS_0.getValue())\n                        .eq(StoreOrderDO::getRefundStatus, OrderInfoEnum.REFUND_STATUS_0.getValue())\n                        .eq(StoreOrderDO::getStatus, OrderInfoEnum.STATUS_0.getValue());\n                break;\n            //已经支付\n            case STATUS_1:\n                wrapper.eq(StoreOrderDO::getPaid, OrderInfoEnum.PAY_STATUS_1.getValue())\n                        .eq(StoreOrderDO::getRefundStatus, OrderInfoEnum.REFUND_STATUS_0.getValue())\n                        .eq(StoreOrderDO::getStatus, OrderInfoEnum.STATUS_0.getValue());\n                break;\n            //待收货\n            case STATUS_2:\n                wrapper.eq(StoreOrderDO::getPaid, OrderInfoEnum.PAY_STATUS_1.getValue())\n                        .eq(StoreOrderDO::getRefundStatus, OrderInfoEnum.REFUND_STATUS_0.getValue())\n                        .eq(StoreOrderDO::getStatus, OrderInfoEnum.STATUS_1.getValue());\n                break;\n//            //待评价\n//            case STATUS_3:\n//                wrapper.eq(StoreOrderDO::getPaid, OrderInfoEnum.PAY_STATUS_1.getValue())\n//                        .eq(StoreOrderDO::getRefundStatus, OrderInfoEnum.REFUND_STATUS_0.getValue())\n//                        .eq(StoreOrderDO::getStatus, OrderInfoEnum.STATUS_2.getValue());\n//                break;\n            //已完成\n            case STATUS_4:\n                wrapper.eq(StoreOrderDO::getPaid, OrderInfoEnum.PAY_STATUS_1.getValue())\n                        .eq(StoreOrderDO::getRefundStatus, OrderInfoEnum.REFUND_STATUS_0.getValue())\n                        .ge(StoreOrderDO::getStatus, OrderInfoEnum.STATUS_2.getValue());\n                break;\n            //退款\n            case STATUS_MINUS_3:\n                String[] strs = {\"1\", \"2\"};\n                wrapper.in(StoreOrderDO::getRefundStatus, Arrays.asList(strs));\n                break;\n            default:\n        }\n\n        Page<StoreOrderDO> pageModel = new Page<>(page, limit);\n        IPage<StoreOrderDO> pageList = storeOrderMapper.selectPage(pageModel, wrapper);\n        List<AppStoreOrderQueryVo> list = StoreOrderConvert.INSTANCE.convertList01(pageList.getRecords());\n\n        return list.stream().map(this::handleOrder).collect(Collectors.toList());\n\n    }\n\n\n    /**\n     * 处理订单返回的状态\n     *\n     * @param order order\n     * @return YxStoreOrderQueryVo\n     */\n    @Override\n    public AppStoreOrderQueryVo handleOrder(AppStoreOrderQueryVo order) {\n        LambdaQueryWrapper<StoreOrderCartInfoDO> wrapper = new LambdaQueryWrapper<>();\n        wrapper.eq(StoreOrderCartInfoDO::getOid, order.getId());\n        List<StoreOrderCartInfoDO> cartInfos = storeOrderCartInfoService.list(wrapper);\n\n        order.setCartInfo(cartInfos);\n\n        StoreShopDO storeShopDO = appStoreShopService.getById(order.getShopId());\n        order.setShop(StoreShopConvert.INSTANCE.convert02(storeShopDO));\n\n        long count = storeOrderMapper.selectCount(new LambdaQueryWrapper<StoreOrderDO>()\n                .eq(StoreOrderDO::getShopId,order.getShopId())\n                .lt(StoreOrderDO::getCreateTime,order.getCreateTime())\n                .eq(StoreOrderDO::getPaid, OrderInfoEnum.PAY_STATUS_1.getValue())\n                .eq(StoreOrderDO::getRefundStatus, OrderInfoEnum.REFUND_STATUS_0.getValue())\n                .eq(StoreOrderDO::getStatus, OrderInfoEnum.STATUS_0.getValue()));\n        order.setPreNum(count);\n\n\n        StatusDto statusDTO = new StatusDto();\n        if (OrderStatusEnum.STATUS_0.getValue().equals(order.getPaid())) {\n            //计算未支付到自动取消订 时间\n            int offset = Integer.valueOf(String.valueOf(ShopConstants.ORDER_OUTTIME_UNPAY));\n            //Date time = DateUtil.offsetMinute(order.getCreateTime()., offset);\n            statusDTO.setYClass(\"nobuy\");\n            //statusDTO.setMsg(StrUtil.format(\"请在{}前完成支付\", DateUtil.formatDateTime(time)));\n            statusDTO.setType(\"0\");\n            statusDTO.setTitle(\"未支付\");\n        } else if (OrderInfoEnum.REFUND_STATUS_1.getValue().equals(order.getRefundStatus())) {\n            statusDTO.setYClass(\"state-sqtk\");\n            statusDTO.setMsg(\"商家审核中,请耐心等待\");\n            statusDTO.setType(\"-1\");\n            statusDTO.setTitle(\"申请退款中\");\n        } else if (OrderInfoEnum.REFUND_STATUS_2.getValue().equals(order.getRefundStatus())) {\n            statusDTO.setYClass(\"state-sqtk\");\n            statusDTO.setMsg(\"已为您退款,感谢您的支持\");\n            statusDTO.setType(\"-2\");\n            statusDTO.setTitle(\"已退款\");\n        } else if (OrderInfoEnum.STATUS_0.getValue().equals(order.getStatus())) {\n            // 拼团 todo\n            if (order.getPinkId() > 0) {\n            } else {\n                statusDTO.setYClass(\"state-nfh\");\n                statusDTO.setMsg(\"商家未发货,请耐心等待\");\n                statusDTO.setType(\"1\");\n                statusDTO.setTitle(\"制作中\");\n            }\n\n        } else if (OrderInfoEnum.STATUS_1.getValue().equals(order.getStatus())) {\n            if(OrderLogEnum.ORDER_TAKE_OUT.getValue().equals(order.getOrderType())){\n                statusDTO.setTitle(\"配送中\");\n            }else{\n                statusDTO.setTitle(\"待取餐\");\n            }\n            statusDTO.setYClass(\"state-ysh\");\n            statusDTO.setMsg(\"服务商已发货\");\n            statusDTO.setType(\"2\");\n\n        } else if (OrderInfoEnum.STATUS_2.getValue().equals(order.getStatus())) {\n            if(OrderLogEnum.ORDER_TAKE_OUT.getValue().equals(order.getOrderType())){\n                statusDTO.setTitle(\"已收货\");\n            }else{\n                statusDTO.setTitle(\"已取餐\");\n            }\n            statusDTO.setYClass(\"state-ypj\");\n            statusDTO.setMsg(\"已收货,快去评价一下吧\");\n            statusDTO.setType(\"3\");\n        } else if (OrderInfoEnum.STATUS_3.getValue().equals(order.getStatus())) {\n            statusDTO.setYClass(\"state-ytk\");\n            statusDTO.setMsg(\"交易完成,感谢您的支持\");\n            statusDTO.setType(\"4\");\n            statusDTO.setTitle(\"交易完成\");\n        }\n\n        if (PayTypeEnum.WEIXIN.getValue().equals(order.getPayType())) {\n            statusDTO.setPayType(\"微信支付\");\n        } else if (PayTypeEnum.YUE.getValue().equals(order.getPayType())) {\n            statusDTO.setPayType(\"余额支付\");\n        } else if (PayTypeEnum.ALI.getValue().equals(order.getPayType())) {\n            statusDTO.setPayType(\"支付宝支付\");\n        }else {\n            statusDTO.setPayType(\"积分支付\");\n        }\n\n        order.setStatusDto(statusDTO);\n\n\n        return order;\n    }\n\n\n    /**\n     * 订单确认收货\n     *\n     * @param orderId 单号\n     * @param uid     uid\n     */\n    @Override\n    public void takeOrder(String orderId, Long uid) {\n        AppStoreOrderQueryVo order = this.getOrderInfo(orderId, uid);\n        if (ObjectUtil.isNull(order)) {\n            throw exception(STORE_ORDER_NOT_EXISTS);\n        }\n\n        if (OrderInfoEnum.PAY_STATUS_0.getValue().equals(order.getPaid())) {\n            throw exception(ORDER_STATUS_ERROR);\n        }\n\n        if (OrderInfoEnum.STATUS_3.getValue().equals(order.getStatus())){\n            throw exception(ORDER_STATUS_FINISH);\n        }\n        order = handleOrder(order);\n//        if (order.getOrderType().equals(OrderLogEnum.ORDER_TAKE_OUT.getValue())\n//                && !OrderStatusEnum.STATUS_2.getValue().toString().equals(order.get_status().get_type())) {\n//            throw exception(ORDER_STATUS_ERROR);\n//        }\n\n\n\n        StoreOrderDO storeOrder = new StoreOrderDO();\n        storeOrder.setStatus(OrderInfoEnum.STATUS_3.getValue());\n        storeOrder.setId(order.getId());\n        this.updateById(storeOrder);\n\n        //增加状态\n        storeOrderStatusService.create(order.getUid(),order.getId(), OrderLogEnum.TAKE_ORDER_DELIVERY.getValue(), OrderLogEnum.TAKE_ORDER_DELIVERY.getDesc());\n\n        //奖励积分\n        this.gainUserIntegral(order);\n\n\n        //分销计算 todo\n\n    }\n\n    /**\n     * 申请退款\n     *\n     * @param explain 退款备注\n     * @param Img     图片\n     * @param text    理由\n     * @param orderId 订单号\n     * @param uid     uid\n     */\n    @Override\n    public void orderApplyRefund(String explain, String Img, String text, String orderId, Long uid) {\n        AppStoreOrderQueryVo order = this.getOrderInfo(orderId, uid);\n        if (ObjectUtil.isNull(order)) {\n            throw exception(STORE_ORDER_NOT_EXISTS);\n        }\n\n        if (OrderInfoEnum.REFUND_STATUS_2.getValue().equals(order.getRefundStatus())) {\n            throw exception(ORDER_REFUNDED);\n        }\n        if (OrderInfoEnum.REFUND_STATUS_1.getValue().equals(order.getRefundStatus())) {\n            throw exception(ORDER_REFUNDING);\n        }\n\n\n        StoreOrderDO storeOrder = new StoreOrderDO();\n        storeOrder.setRefundStatus(OrderInfoEnum.REFUND_STATUS_1.getValue());\n        storeOrder.setRefundReasonTime(LocalDateTime.now());\n        storeOrder.setRefundReasonWapExplain(explain);\n        storeOrder.setRefundReasonWapImg(Img);\n        storeOrder.setRefundReasonWap(text);\n        storeOrder.setId(order.getId());\n        this.updateById(storeOrder);\n\n        //增加状态\n        storeOrderStatusService.create(order.getUid(),order.getId(),\n                OrderLogEnum.REFUND_ORDER_APPLY.getValue(),\n                \"用户申请退款，原因：\" + text);\n\n        //todo 消息推送\n\n    }\n\n\n\n    /**\n     * 删除订单\n     *\n     * @param orderId 单号\n     * @param uid     uid\n     */\n    @Override\n    public void removeOrder(String orderId, Long uid) {\n        AppStoreOrderQueryVo order = this.getOrderInfo(orderId,  uid);\n        if (order == null) {\n            throw exception(STORE_ORDER_NOT_EXISTS);\n        }\n        order = handleOrder(order);\n        if (!OrderInfoEnum.STATUS_3.getValue().equals(order.getStatus())) {\n            throw exception(ORDER_NOT_DELETE);\n        }\n\n        this.removeById(order.getId());\n\n        //增加状态\n        storeOrderStatusService.create(uid,order.getId(),\n                OrderLogEnum.REMOVE_ORDER.getValue(),\n                OrderLogEnum.REMOVE_ORDER.getDesc());\n    }\n\n    /**\n     * 未付款取消订单\n     *\n     * @param orderId 订单号\n     * @param uid     用户id\n     */\n    @Override\n    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)\n    public void cancelOrder(String orderId, Long uid) {\n        log.info(\"订单取消：({})\",orderId);\n        AppStoreOrderQueryVo order = this.getOrderInfo(orderId, uid);\n        if (ObjectUtil.isNull(order)) {\n            throw exception(STORE_ORDER_NOT_EXISTS);\n        }\n        if (order.getPaid() != 0) {\n            throw exception(ORDER_NOT_CANCEL);\n        }\n\n\n        this.regressionStock(order);\n\n        this.regressionCoupon(order, 0);\n\n        storeOrderMapper.deleteById(order.getId());\n    }\n\n\n    /**\n     * 退回积分\n     *\n     * @param order 订单\n     */\n    private void regressionIntegral(AppStoreOrderQueryVo order, Integer type) {\n        if (OrderInfoEnum.PAY_STATUS_1.getValue().equals(order.getPaid())\n                || OrderStatusEnum.STATUS_MINUS_2.getValue().equals(order.getStatus())) {\n            return;\n        }\n\n        if (order.getPayIntegral().compareTo(BigDecimal.ZERO) > 0) {\n            order.setUseIntegral(order.getPayIntegral());\n        }\n        if (order.getUseIntegral().compareTo(BigDecimal.ZERO) <= 0) {\n            return;\n        }\n\n        if (!OrderStatusEnum.STATUS_MINUS_2.getValue().equals(order.getStatus())\n                && !OrderInfoEnum.REFUND_STATUS_2.getValue().equals(order.getRefundStatus())\n                && order.getBackIntegral().compareTo(BigDecimal.ZERO) > 0) {\n            return;\n        }\n\n        MemberUserDO yxUser = userService.getById(order.getUid());\n\n        //增加积分\n        BigDecimal newIntegral = NumberUtil.add(order.getUseIntegral(), yxUser.getIntegral());\n        yxUser.setIntegral(newIntegral);\n        userService.updateById(yxUser);\n\n        //增加流水\n        billService.income(yxUser.getId(), \"积分回退\", BillDetailEnum.CATEGORY_2.getValue(),\n                BillDetailEnum.TYPE_8.getValue(),\n                order.getUseIntegral().doubleValue(),\n                newIntegral.doubleValue(),\n                \"购买商品失败,回退积分\" + order.getUseIntegral(), order.getId().toString());\n\n        //更新回退积分\n        StoreOrderDO storeOrder = new StoreOrderDO();\n        storeOrder.setBackIntegral(order.getUseIntegral());\n        storeOrder.setId(order.getId());\n        this.updateById(storeOrder);\n    }\n\n    /**\n     * 退回优惠券\n     *\n     * @param order 订单 todo\n     */\n    private void regressionCoupon(AppStoreOrderQueryVo order, Integer type) {\n        if (OrderInfoEnum.PAY_STATUS_1.getValue().equals(order.getPaid())\n                || OrderStatusEnum.STATUS_MINUS_2.getValue().equals(order.getStatus())) {\n            return;\n        }\n\n        if (order.getCouponId() != null && order.getCouponId() > 0) {\n\n            CouponUserDO couponUser = appCouponUserService\n                    .getOne(Wrappers.<CouponUserDO>lambdaQuery()\n                            .eq(CouponUserDO::getId, order.getCouponId())\n                            .eq(CouponUserDO::getStatus, ShopCommonEnum.IS_STATUS_1.getValue())\n                            .eq(CouponUserDO::getUserId, order.getUid()));\n\n            if (ObjectUtil.isNotNull(couponUser)) {\n                couponUser.setStatus(ShopCommonEnum.IS_STATUS_0.getValue());\n                appCouponUserService.updateById(couponUser);\n            }\n        }\n    }\n\n    /**\n     * 退回库存\n     *\n     * @param order 订单\n     */\n    private void regressionStock(AppStoreOrderQueryVo order) {\n        if (OrderInfoEnum.PAY_STATUS_1.getValue().equals(order.getPaid())\n                || OrderStatusEnum.STATUS_MINUS_2.getValue().equals(order.getStatus())) {\n            return;\n        }\n\n        LambdaQueryWrapper<StoreOrderCartInfoDO> wrapper = new LambdaQueryWrapper<>();\n        wrapper.eq(StoreOrderCartInfoDO::getOid, order.getId());\n\n        List<StoreOrderCartInfoDO> cartInfoList = storeOrderCartInfoService.list(wrapper);\n        for (StoreOrderCartInfoDO cartInfo : cartInfoList) {\n            String newSku = StrUtil.replace(cartInfo.getSpec(),\"|\",\",\");\n            appStoreProductService.incProductStock(cartInfo.getNumber(), cartInfo.getProductId()\n                    ,newSku, 0L, null);\n        }\n    }\n    \n\n    /**\n     * 奖励积分\n     *\n     * @param order 订单\n     */\n    private void gainUserIntegral(AppStoreOrderQueryVo order) {\n        if (order.getGainIntegral().compareTo(BigDecimal.ZERO) > 0) {\n            MemberUserDO user = userService.getUser(order.getUid());\n\n            BigDecimal newIntegral = NumberUtil.add(user.getIntegral(), order.getGainIntegral());\n            user.setIntegral(newIntegral);\n            user.setId(order.getUid());\n            userService.updateById(user);\n\n            //增加流水\n            billService.income(user.getId(), \"购买商品赠送积分\", BillDetailEnum.CATEGORY_2.getValue(),\n                    BillDetailEnum.TYPE_9.getValue(),\n                    order.getGainIntegral().doubleValue(),\n                    newIntegral.doubleValue(),\n                    \"购买商品赠送\" + order.getGainIntegral() + \"积分\", order.getId().toString());\n        }\n    }\n\n\n\n    /**\n     * 计算奖励的积分\n     *\n     * @param productIds\n     * @return double\n     */\n    private BigDecimal getGainIntegral(List<String> productIds) {\n        BigDecimal gainIntegral = BigDecimal.ZERO;\n        for (int i = 0;i < productIds.size();i++){\n            StoreProductDO storeProductDO = appStoreProductService.getById(productIds.get(i));\n            if(storeProductDO.getGiveIntegral().intValue() == 0){\n                continue;\n            }\n            gainIntegral = NumberUtil.add(gainIntegral, storeProductDO.getGiveIntegral());\n        }\n        return gainIntegral;\n    }\n\n\n\n\n\n\n\n\n\n\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorder/AsynStoreOrderServiceImpl.java",
    "content": "package co.yixiang.yshop.module.order.service.storeorder;\n\nimport cn.hutool.core.bean.BeanUtil;\nimport cn.hutool.core.date.DatePattern;\nimport cn.hutool.core.date.DateUtil;\nimport cn.hutool.core.date.LocalDateTimeUtil;\nimport cn.hutool.core.util.NumberUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.enums.OrderInfoEnum;\nimport co.yixiang.yshop.module.member.controller.app.user.vo.AppUserOrderCountVo;\nimport co.yixiang.yshop.module.member.service.user.MemberUserService;\nimport co.yixiang.yshop.module.order.controller.admin.storeorder.vo.ShoperOrderTimeDataVo;\nimport co.yixiang.yshop.module.order.controller.app.order.vo.AppStoreOrderQueryVo;\nimport co.yixiang.yshop.module.order.dal.dataobject.storeorder.StoreOrderDO;\nimport co.yixiang.yshop.module.order.dal.dataobject.storeordercartinfo.StoreOrderCartInfoDO;\nimport co.yixiang.yshop.module.order.dal.mysql.storeorder.StoreOrderMapper;\nimport co.yixiang.yshop.module.order.dal.mysql.storeordercartinfo.StoreOrderCartInfoMapper;\nimport co.yixiang.yshop.module.order.dal.redis.order.AsyncCountRedisDAO;\nimport co.yixiang.yshop.module.order.dal.redis.order.AsyncOrderRedisDAO;\nimport co.yixiang.yshop.module.order.dal.redis.order.PrintMechinRedisDAO;\nimport co.yixiang.yshop.module.order.enums.OrderLogEnum;\nimport co.yixiang.yshop.module.order.service.storeorder.dto.OrderTimeDataDto;\nimport co.yixiang.yshop.module.product.service.storeproduct.StoreProductService;\nimport co.yixiang.yshop.module.store.dal.dataobject.storeshop.StoreShopDO;\nimport co.yixiang.yshop.module.store.dal.mysql.storeshop.StoreShopMapper;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.scheduling.annotation.Async;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\nimport java.math.BigDecimal;\nimport java.util.Date;\nimport java.util.List;\n\n/**\n * 异步订单 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\n@Slf4j\npublic class AsynStoreOrderServiceImpl implements AsyncStoreOrderService {\n\n    @Resource\n    private StoreOrderMapper storeOrderMapper;\n    @Resource\n    private AsyncOrderRedisDAO asyncOrderRedisDAO;\n    @Resource\n    private AsyncCountRedisDAO asyncCountRedisDAO;\n    @Resource\n    private MemberUserService userService;\n    @Resource\n    private StoreProductService productService;\n    @Resource\n    private PrintMechinRedisDAO printMechinRedisDAO;\n    @Resource\n    private StoreShopMapper storeShopMapper;\n    @Resource\n    private StoreOrderCartInfoMapper storeOrderCartInfoMapper;\n\n    /**\n     * 获取某个用户的订单统计数据\n     *\n     * @param uid uid>0 取用户 否则取所有\n     * @return\n     */\n    @Override\n    @Async\n    public void orderData(Long uid) {\n        log.info(\"========获取某个用户的订单统计数据=========\");\n        //订单支付没有退款 数量\n        LambdaQueryWrapper<StoreOrderDO> wrapperOne = new LambdaQueryWrapper<>();\n        if (uid != null) {\n            wrapperOne.eq(StoreOrderDO::getUid, uid);\n        }\n        wrapperOne.eq(StoreOrderDO::getRefundStatus, OrderInfoEnum.REFUND_STATUS_0.getValue())\n                .eq(StoreOrderDO::getPaid, OrderInfoEnum.PAY_STATUS_1.getValue());\n        Long orderCount = storeOrderMapper.selectCount(wrapperOne);\n\n        //订单支付没有退款 支付总金额\n        double sumPrice = storeOrderMapper.sumPrice(uid);\n\n        //订单待支付 数量\n        LambdaQueryWrapper<StoreOrderDO> wrapperTwo = new LambdaQueryWrapper<>();\n        if (uid != null) {\n            wrapperTwo.eq(StoreOrderDO::getUid, uid);\n        }\n        wrapperTwo.eq(StoreOrderDO::getPaid, OrderInfoEnum.PAY_STATUS_0.getValue())\n                .eq(StoreOrderDO::getRefundStatus, OrderInfoEnum.REFUND_STATUS_0.getValue())\n                .eq(StoreOrderDO::getStatus, OrderInfoEnum.STATUS_0.getValue());\n        Long unpaidCount = storeOrderMapper.selectCount(wrapperTwo);\n\n        //订单待发货 数量\n        LambdaQueryWrapper<StoreOrderDO> wrapperThree = new LambdaQueryWrapper<>();\n        if (uid != null) {\n            wrapperThree.eq(StoreOrderDO::getUid, uid);\n        }\n        wrapperThree.eq(StoreOrderDO::getPaid, OrderInfoEnum.PAY_STATUS_1.getValue())\n                .eq(StoreOrderDO::getRefundStatus, OrderInfoEnum.REFUND_STATUS_0.getValue())\n                .eq(StoreOrderDO::getStatus, OrderInfoEnum.STATUS_0.getValue());\n        Long unshippedCount = storeOrderMapper.selectCount(wrapperThree);\n\n        //订单待收货 数量\n        LambdaQueryWrapper<StoreOrderDO> wrapperFour = new LambdaQueryWrapper<>();\n        if (uid != null) {\n            wrapperFour.eq(StoreOrderDO::getUid, uid);\n        }\n        wrapperFour.eq(StoreOrderDO::getPaid, OrderInfoEnum.PAY_STATUS_1.getValue())\n                .eq(StoreOrderDO::getRefundStatus, OrderInfoEnum.REFUND_STATUS_0.getValue())\n                .eq(StoreOrderDO::getStatus, OrderInfoEnum.STATUS_1.getValue());\n        Long receivedCount = storeOrderMapper.selectCount(wrapperFour);\n\n        //订单待评价 数量\n        LambdaQueryWrapper<StoreOrderDO> wrapperFive = new LambdaQueryWrapper<>();\n        if (uid != null) {\n            wrapperFive.eq(StoreOrderDO::getUid, uid);\n        }\n        wrapperFive.eq(StoreOrderDO::getPaid, OrderInfoEnum.PAY_STATUS_1.getValue())\n                .eq(StoreOrderDO::getRefundStatus, OrderInfoEnum.REFUND_STATUS_0.getValue())\n                .eq(StoreOrderDO::getStatus, OrderInfoEnum.STATUS_2.getValue());\n        Long evaluatedCount = storeOrderMapper.selectCount(wrapperFive);\n\n        //订单已完成 数量\n        LambdaQueryWrapper<StoreOrderDO> wrapperSix = new LambdaQueryWrapper<>();\n        if (uid != null) {\n            wrapperSix.eq(StoreOrderDO::getUid, uid);\n        }\n        wrapperSix.eq(StoreOrderDO::getPaid, OrderInfoEnum.PAY_STATUS_1.getValue())\n                .eq(StoreOrderDO::getRefundStatus, OrderInfoEnum.REFUND_STATUS_0.getValue())\n                .eq(StoreOrderDO::getStatus, OrderInfoEnum.STATUS_3.getValue());\n        Long completeCount = storeOrderMapper.selectCount(wrapperSix);\n\n        //售后退款\n        Long salesCount = 0L;\n\n         AppUserOrderCountVo appUserOrderCountVo = AppUserOrderCountVo.builder()\n                .orderCount(orderCount)\n                .sumPrice(sumPrice)\n                .unpaidCount(unpaidCount)\n                .unshippedCount(unshippedCount)\n                .receivedCount(receivedCount)\n                .evaluatedCount(evaluatedCount)\n                .completeCount(completeCount)\n                .refundCount(salesCount)\n                .build();\n\n         //存redis\n        asyncOrderRedisDAO.set(appUserOrderCountVo,uid);\n\n        this.getOrderTimeData();\n    }\n\n\n    /**\n     * 首页订单/用户等统计\n     *\n     * @return OrderTimeDataDto\n     */\n    @Async\n    @Override\n    public void getOrderTimeData() {\n        OrderTimeDataDto orderTimeDataDto = new OrderTimeDataDto();\n\n        ShoperOrderTimeDataVo shoperOrderTimeData = this.getShoperOrderTimeData();\n\n        BeanUtil.copyProperties(shoperOrderTimeData, orderTimeDataDto);\n\n\n        orderTimeDataDto.setUserCount(userService.count());\n        orderTimeDataDto.setOrderCount(storeOrderMapper.selectCount());\n        orderTimeDataDto.setPriceCount(storeOrderMapper.sumTotalPrice());\n        orderTimeDataDto.setGoodsCount(productService.count());\n\n        asyncCountRedisDAO.set(orderTimeDataDto);\n    }\n\n    /**\n     * 异步后台统计\n     */\n    public ShoperOrderTimeDataVo getShoperOrderTimeData() {\n\n        Date today = DateUtil.beginOfDay(new Date());\n        Date yesterday = DateUtil.beginOfDay(DateUtil.yesterday());\n        Date nowMonth = DateUtil.beginOfMonth(new Date());\n        Date lastWeek = DateUtil.beginOfDay(DateUtil.lastWeek());\n\n        ShoperOrderTimeDataVo orderTimeDataVo = new ShoperOrderTimeDataVo();\n\n        //今日成交额\n        LambdaQueryWrapper<StoreOrderDO> wrapperOne = new LambdaQueryWrapper<>();\n        wrapperOne\n                .ge(StoreOrderDO::getPayTime, today)\n                .eq(StoreOrderDO::getPaid, OrderInfoEnum.PAY_STATUS_1.getValue())\n                .eq(StoreOrderDO::getRefundStatus, OrderInfoEnum.REFUND_STATUS_0.getValue());\n        orderTimeDataVo.setTodayPrice(storeOrderMapper.todayPrice(wrapperOne));\n        //今日订单数\n        orderTimeDataVo.setTodayCount(storeOrderMapper.selectCount(wrapperOne));\n\n        //昨日成交额\n        LambdaQueryWrapper<StoreOrderDO> wrapperTwo = new LambdaQueryWrapper<>();\n        wrapperTwo\n                .lt(StoreOrderDO::getPayTime, today)\n                .ge(StoreOrderDO::getPayTime, yesterday)\n                .eq(StoreOrderDO::getPaid, OrderInfoEnum.PAY_STATUS_1.getValue())\n                .eq(StoreOrderDO::getRefundStatus, OrderInfoEnum.REFUND_STATUS_0.getValue());\n        orderTimeDataVo.setProPrice(storeOrderMapper.todayPrice(wrapperTwo));\n        //昨日订单数\n        orderTimeDataVo.setProCount(storeOrderMapper.selectCount(wrapperTwo));\n\n        //本月成交额\n        LambdaQueryWrapper<StoreOrderDO> wrapperThree = new LambdaQueryWrapper<>();\n        wrapperThree\n                .ge(StoreOrderDO::getPayTime, nowMonth)\n                .eq(StoreOrderDO::getPaid, OrderInfoEnum.PAY_STATUS_1.getValue())\n                .eq(StoreOrderDO::getRefundStatus, OrderInfoEnum.REFUND_STATUS_0.getValue());\n        orderTimeDataVo.setMonthPrice(storeOrderMapper.todayPrice(wrapperThree));\n        //本月订单数\n        orderTimeDataVo.setMonthCount(storeOrderMapper.selectCount(wrapperThree));\n\n        //上周成交额\n        LambdaQueryWrapper<StoreOrderDO> wrapperLastWeek = new LambdaQueryWrapper<>();\n        wrapperLastWeek\n                .lt(StoreOrderDO::getPayTime, today)\n                .ge(StoreOrderDO::getPayTime, lastWeek)\n                .eq(StoreOrderDO::getPaid, OrderInfoEnum.PAY_STATUS_1.getValue())\n                .eq(StoreOrderDO::getRefundStatus, OrderInfoEnum.REFUND_STATUS_0.getValue());\n        orderTimeDataVo.setLastWeekPrice(storeOrderMapper.todayPrice(wrapperLastWeek));\n        //上周订单数\n        orderTimeDataVo.setLastWeekCount(storeOrderMapper.selectCount(wrapperLastWeek));\n\n\n        return orderTimeDataVo;\n\n    }\n\n\n\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorder/AsyncStoreOrderService.java",
    "content": "package co.yixiang.yshop.module.order.service.storeorder;\n\nimport co.yixiang.yshop.module.member.controller.app.user.vo.AppUserOrderCountVo;\nimport co.yixiang.yshop.module.order.controller.admin.storeorder.vo.ShoperOrderTimeDataVo;\nimport co.yixiang.yshop.module.order.controller.app.order.vo.AppStoreOrderQueryVo;\nimport co.yixiang.yshop.module.order.dal.dataobject.storeorder.StoreOrderDO;\nimport co.yixiang.yshop.module.order.service.storeorder.dto.OrderTimeDataDto;\n\n/**\n * 异步订单 Service 接口\n *\n * @author yshop\n */\npublic interface AsyncStoreOrderService {\n\n    /**\n     * 计算某个用户的订单统计数据\n     * @param uid uid>0 取用户 否则取所有\n     * @return UserOrderCountVo\n     */\n    void orderData(Long uid);\n\n\n\n    /**\n     * 异步后台数据统计\n     */\n    void getOrderTimeData();\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorder/StoreOrderService.java",
    "content": "package co.yixiang.yshop.module.order.service.storeorder;\n\nimport java.math.BigDecimal;\nimport java.util.*;\nimport jakarta.validation.*;\nimport co.yixiang.yshop.module.order.controller.admin.storeorder.vo.*;\nimport co.yixiang.yshop.module.order.dal.dataobject.storeorder.StoreOrderDO;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\n/**\n * 订单 Service 接口\n *\n * @author yshop\n */\npublic interface StoreOrderService {\n\n    /**\n     * 创建订单\n     *\n     * @param createReqVO 创建信息\n     * @return 编号\n     */\n    Long createStoreOrder(@Valid StoreOrderCreateReqVO createReqVO);\n\n    /**\n     * 更新订单\n     *\n     * @param updateReqVO 更新信息\n     */\n    void updateStoreOrder(@Valid StoreOrderUpdateReqVO updateReqVO);\n\n    /**\n     * 删除订单\n     *\n     * @param id 编号\n     */\n    void deleteStoreOrder(Long id);\n\n    /**\n     * 订单线下支付\n     *\n     * @param id 编号\n     */\n    void payStoreOrder(Long id);\n\n    /**\n     * 确认收货\n     *\n     * @param id 编号\n     */\n    void takeStoreOrder(Long id);\n\n    /**\n     * 获得订单\n     *\n     * @param id 编号\n     * @return 订单\n     */\n    StoreOrderRespVO getStoreOrder(Long id);\n\n    /**\n     * 获得订单列表\n     *\n     * @param ids 编号\n     * @return 订单列表\n     */\n    List<StoreOrderDO> getStoreOrderList(Collection<Long> ids);\n\n    /**\n     * 获得订单分页\n     *\n     * @param pageReqVO 分页查询\n     * @return 订单分页\n     */\n    PageResult<StoreOrderRespVO> getStoreOrderPage(StoreOrderPageReqVO pageReqVO);\n\n    /**\n     * 获得订单列表, 用于 Excel 导出\n     *\n     * @param exportReqVO 查询条件\n     * @return 订单列表\n     */\n    List<StoreOrderDO> getStoreOrderList(StoreOrderExportReqVO exportReqVO);\n\n    /**\n     * 确认订单退款\n     *\n     * @param id 单号\n     * @param price   金额\n     * @param type    ShopCommonEnum\n     * @param salesId 售后id\n     */\n    void orderRefund(Long id, BigDecimal price, Integer type, Long salesId);\n\n    /**\n     * 订单30s内通知\n     * @return\n     */\n    Long orderNotice();\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorder/StoreOrderServiceImpl.java",
    "content": "package co.yixiang.yshop.module.order.service.storeorder;\n\nimport cn.hutool.core.util.IdUtil;\nimport cn.hutool.core.util.ObjectUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.constant.ShopConstants;\nimport co.yixiang.yshop.framework.common.enums.OrderInfoEnum;\nimport co.yixiang.yshop.framework.common.enums.PayIdEnum;\nimport co.yixiang.yshop.framework.common.enums.ShopCommonEnum;\nimport co.yixiang.yshop.framework.common.exception.ErrorCode;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils;\nimport co.yixiang.yshop.framework.tenant.core.context.TenantContextHolder;\nimport co.yixiang.yshop.module.member.controller.admin.user.vo.UserRespVO;\nimport co.yixiang.yshop.module.member.convert.user.UserConvert;\nimport co.yixiang.yshop.module.member.dal.dataobject.user.MemberUserDO;\nimport co.yixiang.yshop.module.member.dal.mysql.user.MemberUserMapper;\nimport co.yixiang.yshop.module.member.enums.BillDetailEnum;\nimport co.yixiang.yshop.module.member.service.user.MemberUserService;\nimport co.yixiang.yshop.module.member.service.userbill.UserBillService;\nimport co.yixiang.yshop.module.message.mq.producer.WeixinNoticeProducer;\nimport co.yixiang.yshop.module.message.redismq.msg.OrderMsg;\nimport co.yixiang.yshop.module.order.controller.admin.storeorder.vo.*;\nimport co.yixiang.yshop.module.order.convert.storeorder.StoreOrderConvert;\nimport co.yixiang.yshop.module.order.dal.dataobject.storeorder.StoreOrderDO;\nimport co.yixiang.yshop.module.order.dal.dataobject.storeordercartinfo.StoreOrderCartInfoDO;\nimport co.yixiang.yshop.module.order.dal.mysql.storeorder.StoreOrderMapper;\nimport co.yixiang.yshop.module.order.dal.mysql.storeordercartinfo.StoreOrderCartInfoMapper;\nimport co.yixiang.yshop.module.order.enums.OrderLogEnum;\nimport co.yixiang.yshop.module.order.enums.PayTypeEnum;\nimport co.yixiang.yshop.module.order.enums.UpdateOrderEnum;\nimport co.yixiang.yshop.module.order.service.storeorderstatus.StoreOrderStatusService;\nimport co.yixiang.yshop.module.product.service.storeproduct.AppStoreProductService;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport com.egzosn.pay.common.bean.RefundOrder;\nimport com.egzosn.pay.common.bean.RefundResult;\nimport com.egzosn.pay.spring.boot.core.PayServiceManager;\nimport jakarta.annotation.Resource;\nimport lombok.extern.slf4j.Slf4j;\nimport org.redisson.api.RBlockingDeque;\nimport org.redisson.api.RDelayedQueue;\nimport org.redisson.api.RedissonClient;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Propagation;\nimport org.springframework.transaction.annotation.Transactional;\nimport org.springframework.validation.annotation.Validated;\n\nimport java.math.BigDecimal;\nimport java.time.LocalDateTime;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.TimeUnit;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.member.enums.ErrorCodeConstants.USER_NOT_EXISTS;\nimport static co.yixiang.yshop.module.order.enums.ErrorCodeConstants.*;\n\n/**\n * 订单 Service 实现类\n *\n * @author yshop\n */\n@Slf4j\n@Service\n@Validated\npublic class StoreOrderServiceImpl implements StoreOrderService {\n\n    @Resource\n    private StoreOrderMapper storeOrderMapper;\n    @Resource\n    private MemberUserMapper memberUserMapper;\n    @Resource\n    private StoreOrderCartInfoMapper storeOrderCartInfoMapper;\n    @Resource\n    private StoreOrderStatusService storeOrderStatusService;\n    @Resource\n    private AppStoreOrderService appStoreOrderService;\n    @Resource\n    private MemberUserService userService;\n    @Resource\n    private WeixinNoticeProducer weixinNoticeProducer;\n    @Resource\n    private RedissonClient redissonClient;\n    @Resource\n    private UserBillService billService;\n    @Resource\n    private AppStoreProductService appStoreProductService;\n    @Resource\n    private PayServiceManager manager;\n\n    @Resource\n    private AsyncStoreOrderService asyncStoreOrderService;\n\n\n    @Value(\"${yshop.demo}\")\n    private Boolean isDemo;\n\n\n    @Override\n    public Long createStoreOrder(StoreOrderCreateReqVO createReqVO) {\n        // 插入\n        StoreOrderDO storeOrder = StoreOrderConvert.INSTANCE.convert(createReqVO);\n        storeOrderMapper.insert(storeOrder);\n        // 返回\n        return storeOrder.getId();\n    }\n\n    @Override\n    public void updateStoreOrder(StoreOrderUpdateReqVO updateReqVO) {\n        // 校验存在\n        // 更新\n        StoreOrderDO updateObj = StoreOrderConvert.INSTANCE.convert(updateReqVO);\n        StoreOrderDO updateObj2 = storeOrderMapper.selectById(updateReqVO.getId());\n        if (updateObj2 == null) {\n            throw exception(STORE_ORDER_NOT_EXISTS);\n        }\n\n        //发货自取模式 直接收货\n        if(UpdateOrderEnum.ORDER_SEND.getValue().equals(updateReqVO.getUpdateType())\n                && updateObj.getOrderType().equals(OrderLogEnum.ORDER_TAKE_IN.getValue())){\n           this.takeStoreOrder(updateObj.getId());\n            //todo 异步打印小票 商业版本才有 官网购买：https://www.yixiang.co\n\n           return;\n        }\n        //发货-外卖模式\n        if(UpdateOrderEnum.ORDER_SEND.getValue().equals(updateReqVO.getUpdateType())\n                && updateObj.getOrderType().equals(OrderLogEnum.ORDER_TAKE_OUT.getValue())){\n            updateObj.setStatus(OrderInfoEnum.STATUS_1.getValue());\n            //todo 异步打印小票 商业版本才有 官网购买：https://www.yixiang.co\n\n        }\n        //堂食模式\n        if(UpdateOrderEnum.ORDER_SEND.getValue().equals(updateReqVO.getUpdateType())\n                && updateObj.getOrderType().equals(OrderLogEnum.ORDER_TAKE_DESK.getValue())){\n\n            //todo 异步打印小票 商业版本才有 官网购买：https://www.yixiang.co\n\n        }\n        storeOrderMapper.updateById(updateObj);\n\n        if(UpdateOrderEnum.ORDER_SEND.getValue().equals(updateReqVO.getUpdateType())){\n            //增加状态\n            storeOrderStatusService.create(updateObj.getUid(),updateObj.getId(), OrderLogEnum.DELIVERY_GOODS.getValue(),\n                    \"已发货 快递公司：\" + updateObj.getDeliveryName() + \"快递单号：\" + updateObj.getDeliveryId());\n\n            MemberUserDO userInfo = userService.getUser(updateObj.getUid());\n            //发送消息队列进行推送消息\n//            if(userInfo.getLoginType().equals(AppFromEnum.ROUNTINE.getValue())){\n//                weixinNoticeProducer.sendNoticeMessage(updateObj.getUid(), WechatTempateEnum.DELIVERY_SUCCESS.getValue(),\n//                        WechatTempateEnum.SUBSCRIBE.getValue(),updateObj.getOrderId(),\n//                        updateObj.getPayPrice().toString(),\"\",updateObj.getDeliveryName(),\n//                        updateObj.getDeliveryId());\n//            }else if(userInfo.getLoginType().equals(AppFromEnum.WECHAT.getValue())){\n//                weixinNoticeProducer.sendNoticeMessage(updateObj.getUid(),WechatTempateEnum.PAY_SUCCESS.getValue(),\n//                        WechatTempateEnum.TEMPLATES.getValue(),updateObj.getOrderId(),\n//                        updateObj.getPayPrice().toString(),\"\",updateObj.getDeliveryName(),\n//                        updateObj.getDeliveryId());\n//            }\n\n            //延时队列 1小时候自动收货\n            try {\n                RBlockingDeque<Object> blockingDeque = redissonClient.getBlockingDeque(ShopConstants.REDIS_ORDER_OUTTIME_UNCONFIRM);\n                RDelayedQueue<Object> delayedQueue = redissonClient.getDelayedQueue(blockingDeque);\n                delayedQueue.offer(OrderMsg.builder().orderId(updateObj.getOrderId()).build(), ShopConstants.ORDER_OUTTIME_UNCONFIRM, TimeUnit.MINUTES);\n                String s = TimeUnit.SECONDS.toSeconds(ShopConstants.ORDER_OUTTIME_UNCONFIRM) + \"分钟\";\n                log.info(\"添加延时队列成功 ，延迟时间：\" + s);\n            } catch (Exception e) {\n                log.error(e.getMessage());\n            }\n\n        }\n\n    }\n\n    @Override\n    public void deleteStoreOrder(Long id) {\n        // 校验存在\n        validateStoreOrderExists(id);\n        // 删除\n        StoreOrderDO storeOrderDO = StoreOrderDO.builder()\n                .isSystemDel(ShopCommonEnum.DELETE_1.getValue())\n                .id(id)\n                .build();\n        storeOrderMapper.updateById(storeOrderDO);\n    }\n\n    @Override\n    public void payStoreOrder(Long id) {\n        // 校验存在\n        validateStoreOrderExists(id);\n        StoreOrderDO storeOrderDO = storeOrderMapper.selectById(id);\n        //下线支付\n        appStoreOrderService.paySuccess(storeOrderDO.getOrderId(),PayTypeEnum.CASH.getValue());\n    }\n\n    @Override\n    public void takeStoreOrder(Long id) {\n        StoreOrderDO storeOrderDO = storeOrderMapper.selectById(id);\n        appStoreOrderService.takeOrder(storeOrderDO.getOrderId(),storeOrderDO.getUid());\n    }\n\n\n    private void validateStoreOrderExists(Long id) {\n        if (storeOrderMapper.selectById(id) == null) {\n            throw exception(STORE_ORDER_NOT_EXISTS);\n        }\n    }\n\n    @Override\n    public StoreOrderRespVO getStoreOrder(Long id) {\n        StoreOrderDO storeOrderDO = storeOrderMapper.selectById(id);\n        StoreOrderRespVO storeOrderRespVO = StoreOrderConvert.INSTANCE.convert(storeOrderDO);\n        MemberUserDO memberUserDO = memberUserMapper.selectById(storeOrderRespVO.getUid());\n        UserRespVO  userRespVO = UserConvert.INSTANCE.convert4(memberUserDO);\n        storeOrderRespVO.setUserRespVO(userRespVO);\n\n        LambdaQueryWrapper<StoreOrderCartInfoDO> wrapper = new LambdaQueryWrapper<>();\n        wrapper.eq(StoreOrderCartInfoDO::getOid,storeOrderDO.getId());\n        List<StoreOrderCartInfoDO> storeOrderCartInfoDOList = storeOrderCartInfoMapper.selectList(wrapper);\n        storeOrderRespVO.setStoreOrderCartInfoDOList(storeOrderCartInfoDOList);\n\n        storeOrderRespVO.setStatusStr(this.handleOrderStatus(storeOrderRespVO.getPaid()\n                ,storeOrderRespVO.getStatus(),storeOrderRespVO.getRefundStatus(),storeOrderRespVO.getIsSystemDel()));\n        return storeOrderRespVO;\n    }\n\n    @Override\n    public List<StoreOrderDO> getStoreOrderList(Collection<Long> ids) {\n        return storeOrderMapper.selectBatchIds(ids);\n    }\n\n    /**\n     * 订单查询\n     * @param pageReqVO 分页查询\n     * @return\n     */\n    @Override\n    public PageResult<StoreOrderRespVO> getStoreOrderPage(StoreOrderPageReqVO pageReqVO) {\n        PageResult<StoreOrderDO> pageResult =  storeOrderMapper.selectPage(pageReqVO);\n        PageResult<StoreOrderRespVO> storeOrderRespVO =  StoreOrderConvert.INSTANCE.convertPage(pageResult);\n        for (StoreOrderRespVO storeOrderRespVO1 : storeOrderRespVO.getList()) {\n            LambdaQueryWrapper<StoreOrderCartInfoDO> wrapper = new LambdaQueryWrapper<>();\n            wrapper.eq(StoreOrderCartInfoDO::getOid,storeOrderRespVO1.getId());\n            List<StoreOrderCartInfoDO> storeOrderCartInfoDOList = storeOrderCartInfoMapper.selectList(wrapper);\n            MemberUserDO memberUserDO = memberUserMapper.selectById(storeOrderRespVO1.getUid());\n            UserRespVO  userRespVO = UserConvert.INSTANCE.convert4(memberUserDO);\n            storeOrderRespVO1.setStoreOrderCartInfoDOList(storeOrderCartInfoDOList);\n            storeOrderRespVO1.setUserRespVO(userRespVO);\n            storeOrderRespVO1.setStatusStr(this.handleOrderStatus(storeOrderRespVO1.getPaid()\n                    ,storeOrderRespVO1.getStatus(),storeOrderRespVO1.getRefundStatus(),storeOrderRespVO1.getIsSystemDel()));\n        }\n        return storeOrderRespVO;\n    }\n\n    @Override\n    public List<StoreOrderDO> getStoreOrderList(StoreOrderExportReqVO exportReqVO) {\n        return storeOrderMapper.selectList(exportReqVO);\n    }\n\n    /**\n     * 确认订单退款\n     *\n     * @param id 单号\n     * @param price   金额\n     * @param type    ShopCommonEnum\n     * @param salesId 售后id\n     */\n    @Override\n    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)\n    public void orderRefund(Long id, BigDecimal price, Integer type, Long salesId) {\n\n        StoreOrderDO storeOrderDO = storeOrderMapper.selectById(id);\n        if (storeOrderDO == null) {\n            throw exception(STORE_ORDER_NOT_EXISTS);\n        }\n\n        MemberUserDO userQueryVo = userService.getById(storeOrderDO.getUid());\n        if (ObjectUtil.isNull(userQueryVo)) {\n            throw exception(USER_NOT_EXISTS);\n        }\n\n        if (OrderInfoEnum.REFUND_STATUS_2.getValue().equals(storeOrderDO.getRefundStatus())) {\n            throw exception(ORDER_REFUNDED);\n        }\n\n        if (storeOrderDO.getPayPrice().compareTo(price) < 0) {\n            throw exception(ORDER_PRICE_ERROR);\n        }\n\n        storeOrderDO.setRefundStatus(OrderInfoEnum.REFUND_STATUS_2.getValue());\n        storeOrderDO.setRefundPrice(price);\n\n\n        //生成分布式唯一值用于退款订单\n        String orderSn = IdUtil.getSnowflake(0, 0).nextIdStr();\n        BigDecimal balance = userQueryVo.getNowMoney();\n        //根据支付类型不同退款不同\n        if (PayTypeEnum.YUE.getValue().equals(storeOrderDO.getPayType())) {\n            //退款到余额\n            userService.incMoney(storeOrderDO.getUid(), price);\n            balance = balance.add(price);\n\n        } else if (PayTypeEnum.WEIXIN.getValue().equals(storeOrderDO.getPayType())) {\n            if(isDemo){\n                throw exception(new ErrorCode(888888,\"演示模式没有配置证书无法微信退款的哦！\"));\n            }\n            log.error(\"{},{},{},{}\",orderSn,storeOrderDO.getOrderId(),price,storeOrderDO.getPayPrice());\n            RefundOrder refundOrder = new RefundOrder(orderSn,\"\",storeOrderDO.getOrderId(),price,storeOrderDO.getPayPrice());\n            //查询微信退款\n            if(StrUtil.isNotEmpty(storeOrderDO.getOutTradeNo())){\n                RefundOrder refundQueryOrder = new RefundOrder();\n                refundQueryOrder.setRefundNo(storeOrderDO.getOutTradeNo());\n                Map<String, Object> objectMap =  manager.refundQuery(PayIdEnum.WX_MINIAPP.getValue(), refundQueryOrder);\n                if(objectMap.get(\"'return_code'\").toString().equals(\"SUCCESS\")){\n                    storeOrderMapper.updateById(storeOrderDO);\n                    return;\n                }\n            }\n\n            RefundResult refundResult = manager.refund(PayIdEnum.WX_MINIAPP.getValue(), refundOrder);\n            if(refundResult.getCode().equals(\"FAIL\")){\n                log.error(\"支付退款错误：{}\",refundResult.getMsg());\n                throw exception(new ErrorCode(999999,refundResult.getMsg()));\n            }else {\n                if(refundResult.getResultCode().equals(\"FAIL\")){\n                    log.error(\"支付退款错误：{}\",refundResult.getResultMsg());\n                    throw exception(new ErrorCode(999998,refundResult.getResultMsg()));\n                }\n            }\n            storeOrderDO.setOutTradeNo(orderSn);\n        }else if (PayTypeEnum.ALI.getValue().equals(storeOrderDO.getPayType())){\n            throw exception(new ErrorCode(999997,\"支付宝暂时不支持退款\"));\n        }\n        storeOrderMapper.updateById(storeOrderDO);\n        //增加流水\n        billService.income(storeOrderDO.getUid(), \"商品退款\", BillDetailEnum.CATEGORY_1.getValue(),\n                BillDetailEnum.TYPE_5.getValue(),\n                price.doubleValue(),\n                balance.doubleValue(),\n                \"订单退款到余额\" + price + \"元\", storeOrderDO.getId().toString(),orderSn);\n        storeOrderStatusService.create(storeOrderDO.getUid(), storeOrderDO.getId(),\n                OrderLogEnum.REFUND_ORDER_SUCCESS.getValue(), \"退款给用户：\" + price + \"元\");\n\n        //退库存\n        this.regressionStock(storeOrderDO,0);\n\n    }\n\n    /**\n     * 订单30s内通知\n     * @return\n     */\n    @Override\n    public Long orderNotice() {\n        LambdaQueryWrapper<StoreOrderDO> wrapper = new LambdaQueryWrapper();\n        Long shopId = SecurityFrameworkUtils.getLoginUser().getShopId();\n        if(shopId > 0) {\n            wrapper.eq(StoreOrderDO::getShopId,shopId);\n        }\n        wrapper.eq(StoreOrderDO::getPaid, OrderInfoEnum.PAY_STATUS_1.getValue());\n        LocalDateTime nowTime = LocalDateTime.now();\n        wrapper.ge(StoreOrderDO::getCreateTime,nowTime.minusSeconds(30));\n\n        return  storeOrderMapper.selectCount(wrapper);\n\n    }\n\n    /**\n     * 退回库存\n     *\n     * @param order 订单\n     */\n    private void regressionStock(StoreOrderDO order, Integer type) {\n\n        LambdaQueryWrapper<StoreOrderCartInfoDO> wrapper = new LambdaQueryWrapper<>();\n        wrapper.in(StoreOrderCartInfoDO::getOid, order.getId());\n\n        List<StoreOrderCartInfoDO> cartInfoList = storeOrderCartInfoMapper.selectList(wrapper);\n        for (StoreOrderCartInfoDO cartInfo : cartInfoList) {\n            appStoreProductService.incProductStock(cartInfo.getNumber(), cartInfo.getProductId(), cartInfo.getSpec(),\n                    0L, null);\n        }\n    }\n\n\n    /**\n     * 处理订单状态\n     * @param payStatus\n     * @param status\n     * @param refundStatus\n     * @param del\n     * @return\n     */\n    private String handleOrderStatus(Integer payStatus,Integer status,Integer refundStatus,Integer del) {\n        String statusName = \"\";\n        if (del == 1){\n            statusName = \"已删除\";\n        }else if (payStatus == 0 && status == 0) {\n            statusName = \"未支付\";\n        } else if (payStatus == 1 && status == 0 && refundStatus == 0) {\n            statusName = \"未发货\";\n        }  else if (payStatus == 1 && status == 1 && refundStatus == 0) {\n            statusName = \"待收货\";\n        }  else if (payStatus == 1 && status == 2 && refundStatus == 0) {\n            statusName = \"待评价\";\n        } else if (payStatus == 1 && status == 3 && refundStatus == 0) {\n            statusName = \"已完成\";\n        } else if (payStatus == 1 && refundStatus == 2) {\n            statusName = \"已退款\";\n        }\n        else if (payStatus == 1 && refundStatus == 1) {\n            statusName = \"退款中\";\n        }\n        return statusName;\n    }\n\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorder/dto/CacheDto.java",
    "content": "package co.yixiang.yshop.module.order.service.storeorder.dto;\r\n\r\nimport lombok.Data;\r\n\r\nimport java.io.Serializable;\r\n\r\n/**\r\n * @ClassName CacheDto\r\n * @Author hupeng <610796224@qq.com>\r\n * @Date 2023/6/19\r\n **/\r\n@Data\r\npublic class CacheDto implements Serializable {\r\n    private PriceGroupDto priceGroup;\r\n    private OtherDto other;\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorder/dto/ChartDataDto.java",
    "content": "/**\n * Copyright (C) 2018-2022\n * All rights reserved, Designed By www.yixiang.co\n\n */\npackage co.yixiang.yshop.module.order.service.storeorder.dto;\n\nimport lombok.Data;\n\n/**\n * @ClassName ChartDataDTO\n * @Author hupeng <610796224@qq.com>\n * @Date 2019/11/25\n **/\n@Data\npublic class ChartDataDto {\n\n   // @Value(\"#{target.adminCount}\")\n   private  Double num;\n   private  String time;\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorder/dto/CountDto.java",
    "content": "/**\r\n * Copyright (C) 2018-2022\r\n * All rights reserved, Designed By www.yixiang.co\r\n\r\n */\r\npackage co.yixiang.yshop.module.order.service.storeorder.dto;\r\n\r\nimport lombok.Data;\r\n\r\n@Data\r\npublic class CountDto {\r\n\r\n    private String catename;\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorder/dto/OrderCountDto.java",
    "content": "/**\r\n * Copyright (C) 2018-2022\r\n * All rights reserved, Designed By www.yixiang.co\r\n\r\n */\r\npackage co.yixiang.yshop.module.order.service.storeorder.dto;\r\n\r\n\r\nimport lombok.Data;\r\n\r\nimport java.util.List;\r\n\r\n@Data\r\npublic class OrderCountDto {\r\n\r\n    private List<String> column;\r\n\r\n    private List<OrderCountData> orderCountDatas;\r\n\r\n    @Data\r\n    public static class OrderCountData{\r\n        private String name;\r\n\r\n        private Integer value;\r\n    }\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorder/dto/OrderExtendDto.java",
    "content": "package co.yixiang.yshop.module.order.service.storeorder.dto;\r\n\r\nimport io.swagger.v3.oas.annotations.media.Schema;\r\nimport lombok.AllArgsConstructor;\r\nimport lombok.Builder;\r\nimport lombok.Data;\r\nimport lombok.NoArgsConstructor;\r\n\r\nimport java.io.Serializable;\r\nimport java.util.Map;\r\n\r\n/**\r\n * @ClassName OrderExtendDto\r\n * @Author hupeng <610796224@qq.com>\r\n * @Date 2023/6/19\r\n **/\r\n@Data\r\n@AllArgsConstructor\r\n@NoArgsConstructor\r\n@Builder\r\npublic class OrderExtendDto implements Serializable {\r\n\r\n    @Schema(description = \"唯一的key\", required = true)\r\n    private String key;\r\n\r\n    @Schema(description = \"订单ID\", required = true)\r\n    private String orderId;\r\n\r\n    @Schema(description = \"微信相关配置\", required = true)\r\n    private Map<String,String> jsConfig;\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorder/dto/OrderTimeDataDto.java",
    "content": "/**\n * Copyright (C) 2018-2022\n * All rights reserved, Designed By www.yixiang.co\n\n */\npackage co.yixiang.yshop.module.order.service.storeorder.dto;\n\nimport lombok.Data;\n\nimport java.io.Serializable;\n\n/**\n * @ClassName OrderTimeDataDTO\n * @Author hupeng <610796224@qq.com>\n * @Date 2019/11/25\n **/\n@Data\npublic class OrderTimeDataDto implements Serializable {\n    private Double todayPrice;  //今日成交额\n    private Integer todayCount; //今日订单数\n    private Double proPrice;  //昨日成交额\n    private Integer proCount;//昨日订单数\n    private Double monthPrice;//本月成交额\n    private Integer monthCount;//本月订单数\n\n    private Integer lastWeekCount;//上周\n    private Double lastWeekPrice; //上周\n\n    private Long userCount;\n    private Long orderCount;\n    private Double priceCount;\n    private Long goodsCount;\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorder/dto/OtherDto.java",
    "content": "package co.yixiang.yshop.module.order.service.storeorder.dto;\r\n\r\nimport lombok.Data;\r\n\r\nimport java.io.Serializable;\r\n\r\n/**\r\n * @ClassName OtherDto\r\n * @Author hupeng <610796224@qq.com>\r\n * @Date 2019/10/27\r\n **/\r\n@Data\r\npublic class OtherDto implements Serializable {\r\n    //线下包邮\r\n    private String offlinePostage;\r\n    //积分抵扣\r\n    private String integralRatio;\r\n\r\n    //最大\r\n    private String integralMax;\r\n\r\n    //满多少\r\n    private String integralFull;\r\n\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorder/dto/PriceGroupDto.java",
    "content": "package co.yixiang.yshop.module.order.service.storeorder.dto;\r\n\r\nimport com.fasterxml.jackson.databind.annotation.JsonSerialize;\r\nimport lombok.Data;\r\n\r\nimport java.math.BigDecimal;\r\n\r\n/**\r\n * @ClassName PriceGroup\r\n * @Author hupeng <610796224@qq.com>\r\n * @Date 2023/6/18\r\n **/\r\n@Data\r\npublic class PriceGroupDto {\r\n\r\n    private BigDecimal costPrice;\r\n\r\n    private BigDecimal storeFreePostage;\r\n\r\n    private BigDecimal storePostage;\r\n\r\n    private BigDecimal totalPrice;\r\n\r\n    private BigDecimal vipPrice;\r\n\r\n    private BigDecimal payIntegral;\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorder/dto/ProductAttrDto.java",
    "content": "package co.yixiang.yshop.module.order.service.storeorder.dto;\r\n\r\nimport lombok.Data;\r\n\r\nimport java.io.Serializable;\r\n\r\n/**\r\n * @ClassName ProductAttrDto\r\n * @Author hupeng <610796224@qq.com>\r\n * @Date 2023/6/27\r\n **/\r\n@Data\r\npublic class ProductAttrDto implements Serializable {\r\n\r\n    private Long productId;\r\n\r\n    private String sku;\r\n\r\n    private Double price;\r\n\r\n    private String image;\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorder/dto/ProductDto.java",
    "content": "package co.yixiang.yshop.module.order.service.storeorder.dto;\r\n\r\nimport lombok.Data;\r\n\r\nimport java.io.Serializable;\r\n\r\n/**\r\n * @ClassName ProductVo\r\n * @Author hupeng <610796224@qq.com>\r\n * @Date 20123/6/27\r\n **/\r\n@Data\r\npublic class ProductDto implements Serializable {\r\n\r\n    private String image;\r\n\r\n    private Double price;\r\n\r\n    private String storeName;\r\n\r\n    private ProductAttrDto attrInfo;\r\n\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorder/dto/StatusDto.java",
    "content": "package co.yixiang.yshop.module.order.service.storeorder.dto;\r\n\r\nimport lombok.Data;\r\n\r\nimport java.io.Serializable;\r\n\r\n/**\r\n * @ClassName StatusDto\r\n * @Author hupeng <610796224@qq.com>\r\n * @Date 2024/1/19\r\n **/\r\n@Data\r\npublic class StatusDto implements Serializable {\r\n    private String yClass;\r\n    private String msg;\r\n    private String payType;\r\n    private String title;\r\n    private String type;\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorder/dto/StoreOrderCartInfoDto.java",
    "content": "/**\n * Copyright (C) 2018-2022\n * All rights reserved, Designed By www.yixiang.co\n\n */\npackage co.yixiang.yshop.module.order.service.storeorder.dto;\n\nimport lombok.Data;\n\nimport java.util.Map;\n\n/**\n * @ClassName StoreOrderCartInfo\n * @Author hupeng <610796224@qq.com>\n * @Date 2019/10/14\n **/\n\n\n@Data\npublic class StoreOrderCartInfoDto {\n\n\n    private Integer id;\n\n\n    private Integer oid;\n\n\n    private Integer cartId;\n\n\n    private String cartInfo;\n\n\n    private String unique;\n\n    private Map<String,Object> cartInfoMap;\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorder/dto/TemplateDto.java",
    "content": "package co.yixiang.yshop.module.order.service.storeorder.dto;\r\n\r\nimport lombok.*;\r\n\r\nimport java.math.BigDecimal;\r\n\r\n/**\r\n * @ClassName TemplateDTO\r\n * @Author hupeng <610796224@qq.com>\r\n * @Date 2024/2/17\r\n **/\r\n@Getter\r\n@Setter\r\n@Builder\r\n@AllArgsConstructor\r\n@NoArgsConstructor\r\npublic class TemplateDto {\r\n    private Double number;\r\n    private BigDecimal price;\r\n    private Double first;\r\n    private BigDecimal firstPrice;\r\n    private Double yContinue;\r\n    private BigDecimal continuePrice;\r\n    private Integer tempId;\r\n    private Integer cityId;\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorder/dto/YxExpressDto.java",
    "content": "/**\n * Copyright (C) 2018-2022\n * All rights reserved, Designed By www.yixiang.co\n\n */\npackage co.yixiang.yshop.module.order.service.storeorder.dto;\n\nimport lombok.Data;\n\nimport java.io.Serializable;\n\n/**\n* @author hupeng\n* @date 2020-05-12\n*/\n@Data\npublic class YxExpressDto implements Serializable {\n\n    /** 快递公司id */\n    private Integer id;\n\n    /** 快递公司简称 */\n    private String code;\n\n    /** 快递公司全称 */\n    private String name;\n\n    /** 排序 */\n    private Integer sort;\n\n    /** 是否显示 */\n    private Integer isShow;\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorder/dto/YxOrderNowOrderStatusDto.java",
    "content": "package co.yixiang.yshop.module.order.service.storeorder.dto;\n\nimport com.fasterxml.jackson.annotation.JsonFormat;\nimport lombok.Data;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\n/**\n * @author ：LionCity\n * @date ：Created in 2020-05-29 11:16\n * @description：\n * @modified By：\n * @version:\n */\n@Data\npublic class YxOrderNowOrderStatusDto implements Serializable {\n    @JsonFormat(pattern = \"yyyy-MM-dd HH:mm:ss\",timezone=\"GMT+8\")\n    private Date cacheKeyCreateOrder;\n    @JsonFormat(pattern = \"yyyy-MM-dd HH:mm:ss\",timezone=\"GMT+8\")\n    private Date paySuccess;\n    @JsonFormat(pattern = \"yyyy-MM-dd HH:mm:ss\",timezone=\"GMT+8\")\n    private Date deliveryGoods;\n    @JsonFormat(pattern = \"yyyy-MM-dd HH:mm:ss\",timezone=\"GMT+8\")\n    private Date orderVerific;\n    @JsonFormat(pattern = \"yyyy-MM-dd HH:mm:ss\",timezone=\"GMT+8\")\n    private Date userTakeDelivery;\n    @JsonFormat(pattern = \"yyyy-MM-dd HH:mm:ss\",timezone=\"GMT+8\")\n    private Date checkOrderOver;\n    @JsonFormat(pattern = \"yyyy-MM-dd HH:mm:ss\",timezone=\"GMT+8\")\n    private Date applyRefund;\n    @JsonFormat(pattern = \"yyyy-MM-dd HH:mm:ss\",timezone=\"GMT+8\")\n    private Date refundOrderSuccess;\n    private int size;\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorder/dto/YxStoreOrderCartInfoDto.java",
    "content": "/**\n * Copyright (C) 2018-2022\n * All rights reserved, Designed By www.yixiang.co\n\n */\npackage co.yixiang.yshop.module.order.service.storeorder.dto;\n\nimport lombok.Data;\n\nimport java.io.Serializable;\n\n/**\n* @author hupeng\n* @date 2020-05-12\n*/\n@Data\npublic class YxStoreOrderCartInfoDto implements Serializable {\n\n    private Integer id;\n\n    /** 订单id */\n    private Integer oid;\n\n    /**\n     * 订单号\n     */\n    private String orderId;\n\n    /** 购物车id */\n    private Integer cartId;\n\n    /** 商品ID */\n    private Integer productId;\n\n    /** 购买东西的详细信息 */\n    private String cartInfo;\n\n    /** 唯一id */\n    private String unique;\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorder/dto/YxStoreOrderStatusDto.java",
    "content": "/**\n * Copyright (C) 2018-2022\n * All rights reserved, Designed By www.yixiang.co\n\n */\npackage co.yixiang.yshop.module.order.service.storeorder.dto;\n\nimport com.fasterxml.jackson.annotation.JsonFormat;\nimport lombok.Data;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\n/**\n* @author hupeng\n* @date 2020-05-12\n*/\n@Data\npublic class YxStoreOrderStatusDto implements Serializable {\n\n    private Integer id;\n\n    /** 订单id */\n    private Integer oid;\n\n    /** 操作类型 */\n    private String changeType;\n\n    /** 操作备注 */\n    private String changeMessage;\n\n    /** 操作时间 */\n    @DateTimeFormat(pattern=\"yyyy-MM-dd HH:mm:ss\")\n    @JsonFormat(pattern = \"yyyy-MM-dd HH:mm:ss\",timezone=\"GMT+8\")\n    private Date changeTime;\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeordercartinfo/StoreOrderCartInfoService.java",
    "content": "package co.yixiang.yshop.module.order.service.storeordercartinfo;\n\nimport co.yixiang.yshop.module.order.dal.dataobject.storeorder.StoreOrderDO;\nimport co.yixiang.yshop.module.order.dal.dataobject.storeordercartinfo.StoreOrderCartInfoDO;\nimport co.yixiang.yshop.module.product.controller.app.cart.vo.AppStoreCartQueryVo;\nimport com.baomidou.mybatisplus.extension.service.IService;\n\nimport java.util.List;\n\n/**\n * 订单购物详情 Service 接口\n *\n * @author yshop\n */\npublic interface StoreOrderCartInfoService extends IService<StoreOrderCartInfoDO> {\n\n    /**\n     * 添加购物车商品信息\n     * @param oid 订单id\n     * @param orderId 订单号\n     * @param productIds 商品id\n     * @param numbers 商品数量\n     * @param specs 商品规格\n     */\n    void saveCartInfo(Long oid, String orderId,List<String> productIds,List<String> numbers,List<String> specs);\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeordercartinfo/StoreOrderCartInfoServiceImpl.java",
    "content": "package co.yixiang.yshop.module.order.service.storeordercartinfo;\n\nimport cn.hutool.core.util.IdUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.module.order.dal.dataobject.storeordercartinfo.StoreOrderCartInfoDO;\nimport co.yixiang.yshop.module.order.dal.mysql.storeordercartinfo.StoreOrderCartInfoMapper;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproduct.StoreProductDO;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproductattrvalue.StoreProductAttrValueDO;\nimport co.yixiang.yshop.module.product.service.storeproduct.AppStoreProductService;\nimport co.yixiang.yshop.module.product.service.storeproductattrvalue.StoreProductAttrValueService;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport jakarta.annotation.Resource;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.scheduling.annotation.Async;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * 订单购物详情 Service 实现类\n *\n * @author yshop\n */\n@Slf4j\n@Service\n@Validated\npublic class StoreOrderCartInfoServiceImpl extends ServiceImpl<StoreOrderCartInfoMapper, StoreOrderCartInfoDO> implements StoreOrderCartInfoService {\n\n    @Resource\n    private AppStoreProductService appStoreProductService;\n    @Resource\n    private StoreProductAttrValueService storeProductAttrValueService;\n\n\n    /**\n     * 添加购物车商品信息\n     * @param oid 订单id\n     * @param orderId\n     * @param productIds 商品id\n     * @param numbers 商品数量\n     * @param specs 商品规格\n     */\n    @Async\n    @Override\n    public void saveCartInfo(Long oid, String orderId, List<String> productIds,List<String> numbers,List<String> specs) {\n        log.info(\"==========添加购物车商品信息start===========\");\n\n        List<StoreOrderCartInfoDO> list = new ArrayList<>();\n        for (int i = 0;i < productIds.size();i++){\n            String newSku = StrUtil.replace(specs.get(i),\"|\",\",\");\n            StoreProductDO storeProductDO = appStoreProductService.getById(productIds.get(i));\n\n            StoreProductAttrValueDO storeProductAttrValue = storeProductAttrValueService\n                    .getOne(Wrappers.<StoreProductAttrValueDO>lambdaQuery()\n                            .eq(StoreProductAttrValueDO::getSku, newSku)\n                            .eq(StoreProductAttrValueDO::getProductId, productIds.get(i)));\n            StoreOrderCartInfoDO info = new StoreOrderCartInfoDO();\n            info.setOid(oid);\n            info.setOrderId(orderId);\n            info.setCartId(0L);\n            info.setProductId(Long.valueOf(productIds.get(i)));\n            info.setCartInfo(\"\");\n            info.setUnique(IdUtil.simpleUUID());\n            info.setIsAfterSales(1);\n            info.setTitle(storeProductDO.getStoreName());\n            info.setImage(storeProductDO.getImage());\n            info.setNumber(Integer.valueOf(numbers.get(i)));\n            info.setSpec(specs.get(i));\n            info.setPrice(storeProductAttrValue.getPrice());\n            list.add(info);\n\n        }\n\n        this.saveBatch(list);\n    }\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorderstatus/StoreOrderStatusService.java",
    "content": "package co.yixiang.yshop.module.order.service.storeorderstatus;\n\nimport co.yixiang.yshop.module.order.dal.dataobject.storeorderstatus.StoreOrderStatusDO;\nimport com.baomidou.mybatisplus.extension.service.IService;\n\n/**\n * 订单操作记录 Service 接口\n *\n * @author yshop\n */\npublic interface StoreOrderStatusService extends IService<StoreOrderStatusDO> {\n    /**\n     * 添加订单操作记录\n     * @param oid 订单id\n     * @param changetype 操作状态\n     * @param changeMessage 操作内容\n     */\n    void create(Long uid,Long oid,String changetype,String changeMessage);\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/java/co/yixiang/yshop/module/order/service/storeorderstatus/StoreOrderStatusServiceImpl.java",
    "content": "package co.yixiang.yshop.module.order.service.storeorderstatus;\n\nimport co.yixiang.yshop.module.order.dal.dataobject.storeorderstatus.StoreOrderStatusDO;\nimport co.yixiang.yshop.module.order.dal.mysql.storeorderstatus.StoreOrderStatusMapper;\nimport co.yixiang.yshop.module.order.service.storeorder.AsyncStoreOrderService;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Propagation;\nimport org.springframework.transaction.annotation.Transactional;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\n\n/**\n * 订单操作记录 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class StoreOrderStatusServiceImpl extends ServiceImpl<StoreOrderStatusMapper, StoreOrderStatusDO> implements StoreOrderStatusService {\n\n    @Resource\n    private StoreOrderStatusMapper storeOrderStatusMapper;\n    @Resource\n    private AsyncStoreOrderService asyncStoreOrderService;\n    /**\n     * 添加订单操作记录\n     * @param oid 订单id\n     * @param changetype 操作状态\n     * @param changeMessage 操作内容\n     */\n    @Override\n    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)\n    public void create(Long uid,Long oid, String changetype, String changeMessage) {\n        StoreOrderStatusDO storeOrderStatus = new StoreOrderStatusDO();\n        storeOrderStatus.setOid(oid);\n        storeOrderStatus.setChangeType(changetype);\n        storeOrderStatus.setChangeMessage(changeMessage);\n        this.baseMapper.insert(storeOrderStatus);\n\n        //异步统计\n        asyncStoreOrderService.orderData(uid);\n\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/resources/mapper/express/ExpressMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"co.yixiang.yshop.module.order.dal.mysql.express.ExpressMapper\">\n\n    <!--\n        一般情况下，尽可能使用 Mapper 进行 CRUD 增删改查即可。\n        无法满足的场景，例如说多表关联查询，才使用 XML 编写 SQL。\n        代码生成器暂时只生成 Mapper XML 文件本身，更多推荐 MybatisX 快速开发插件来生成查询。\n        文档可见：https://www.yixiang.co/MyBatis/x-plugins/\n     -->\n\n</mapper>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/resources/mapper/storeorder/StoreOrderMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"co.yixiang.yshop.module.order.dal.mysql.storeorder.StoreOrderMapper\">\n\n    <!--\n        一般情况下，尽可能使用 Mapper 进行 CRUD 增删改查即可。\n        无法满足的场景，例如说多表关联查询，才使用 XML 编写 SQL。\n        代码生成器暂时只生成 Mapper XML 文件本身，更多推荐 MybatisX 快速开发插件来生成查询。\n        文档可见：https://www.yixiang.co/MyBatis/x-plugins/\n     -->\n\n</mapper>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/resources/mapper/storeordercartinfo/StoreOrderCartInfoMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"co.yixiang.yshop.module.order.dal.mysql.storeordercartinfo.StoreOrderCartInfoMapper\">\n\n    <!--\n        一般情况下，尽可能使用 Mapper 进行 CRUD 增删改查即可。\n        无法满足的场景，例如说多表关联查询，才使用 XML 编写 SQL。\n        代码生成器暂时只生成 Mapper XML 文件本身，更多推荐 MybatisX 快速开发插件来生成查询。\n        文档可见：https://www.yixiang.co/MyBatis/x-plugins/\n     -->\n\n</mapper>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-order-biz/src/main/resources/mapper/storeorderstatus/StoreOrderStatusMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"co.yixiang.yshop.module.order.dal.mysql.storeorderstatus.StoreOrderStatusMapper\">\n\n\n</mapper>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-api/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <parent>\n        <groupId>co.yixiang.boot</groupId>\n        <artifactId>yshop-module-mall</artifactId>\n        <version>${revision}</version>\n    </parent>\n\n    <artifactId>yshop-module-product-api</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>\n        product 模块 API，暴露给其它模块调用\n    </description>\n\n    <dependencies>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-common</artifactId>\n        </dependency>\n\n        <!-- 参数校验 -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-validation</artifactId>\n            <optional>true</optional>\n        </dependency>\n        <dependency>\n            <groupId>io.swagger.core.v3</groupId>\n            <artifactId>swagger-annotations</artifactId>\n            <version>2.2.8</version>\n            <scope>compile</scope>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-api/src/main/java/co/yixiang/yshop/module/product/api/package-info.java",
    "content": "/**\n * 占位\n */\npackage co.yixiang.yshop.module.product.api;"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-api/src/main/java/co/yixiang/yshop/module/product/api/product/ProductApi.java",
    "content": "package co.yixiang.yshop.module.product.api.product;\n\nimport java.util.List;\n\npublic interface ProductApi {\n//    /**\n//     * 检测商品/秒杀/砍价/拼团库存\n//     * @param uid  用户ID\n//     * @param productId  产品ID\n//     * @param cartNum 购买数量\n//     * @param productAttrUnique  商品属性Unique\n//     */\n//    void checkProductStock(Long uid, Long productId, Integer cartNum, String productAttrUnique);\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-api/src/main/java/co/yixiang/yshop/module/product/api/property/ProductPropertyValueApi.java",
    "content": "package co.yixiang.yshop.module.product.api.property;\n\nimport co.yixiang.yshop.module.product.api.property.dto.ProductPropertyValueDetailRespDTO;\n\nimport java.util.Collection;\nimport java.util.List;\n\n/**\n * 商品属性值 API 接口\n *\n * @author yshop\n */\npublic interface ProductPropertyValueApi {\n\n    /**\n     * 根据编号数组，获得属性值列表\n     *\n     * @param ids 编号数组\n     * @return 属性值明细列表\n     */\n    List<ProductPropertyValueDetailRespDTO> getPropertyValueDetailList(Collection<Long> ids);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-api/src/main/java/co/yixiang/yshop/module/product/api/property/dto/ProductPropertyValueDetailRespDTO.java",
    "content": "package co.yixiang.yshop.module.product.api.property.dto;\n\nimport lombok.Data;\n\n/**\n * 商品属性项的明细 Response DTO\n *\n * @author yshop\n */\n@Data\npublic class ProductPropertyValueDetailRespDTO {\n\n    /**\n     * 属性的编号\n     */\n    private Long propertyId;\n\n    /**\n     * 属性的名称\n     */\n    private String propertyName;\n\n    /**\n     * 属性值的编号\n     */\n    private Long valueId;\n\n    /**\n     * 属性值的名称\n     */\n    private String valueName;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-api/src/main/java/co/yixiang/yshop/module/product/enums/ErrorCodeConstants.java",
    "content": "package co.yixiang.yshop.module.product.enums;\n\nimport co.yixiang.yshop.framework.common.exception.ErrorCode;\n\n/**\n * Product 错误码枚举类\n *\n * product 系统，使用 1-008-000-000 段\n */\npublic interface ErrorCodeConstants {\n\n    ErrorCode PARAM_ERROR = new ErrorCode(1008001000, \"参数非法\");\n    // ========== 商品分类相关 1008001000 ============\n    ErrorCode CATEGORY_NOT_EXISTS = new ErrorCode(1008001000, \"商品分类不存在\");\n    ErrorCode CATEGORY_PARENT_NOT_EXISTS = new ErrorCode(1008001001, \"父分类不存在\");\n    ErrorCode CATEGORY_PARENT_NOT_FIRST_LEVEL = new ErrorCode(1008001002, \"分类最多只有二级哦\");\n    ErrorCode CATEGORY_EXISTS_CHILDREN = new ErrorCode(1008001003, \"存在子分类，无法删除\");\n    ErrorCode CATEGORY_DISABLED = new ErrorCode(1008001004, \"商品分类({})已禁用，无法使用\");\n\n    // ========== 商品品牌相关编号 1008002000 ==========\n    ErrorCode BRAND_NOT_EXISTS = new ErrorCode(1008002000, \"品牌不存在\");\n    ErrorCode BRAND_DISABLED = new ErrorCode(1008002001, \"品牌不存在\");\n    ErrorCode BRAND_NAME_EXISTS = new ErrorCode(1008002002, \"品牌名称已存在\");\n\n    // ========== 商品 1008003000 ==========\n    ErrorCode STORE_PRODUCT_NOT_EXISTS = new ErrorCode(1008003000, \"商品不存在\");\n    ErrorCode STORE_PRODUCT_ATTR_NOT_EXISTS = new ErrorCode(1008003001, \"商品属性不存在\");\n    ErrorCode STORE_PRODUCT_ATTR_RESULT_NOT_EXISTS = new ErrorCode(1008003002, \"商品属性详情不存在\");\n    ErrorCode STORE_PRODUCT_ATTR_VALUE_NOT_EXISTS = new ErrorCode(1008003003, \"商品属性值不存在\");\n    ErrorCode STORE_PRODUCT_RULE_NOT_EXISTS = new ErrorCode(1008003004, \"商品规则值(规格)不存在\");\n    ErrorCode STORE_PRODUCT_RULE_NEED = new ErrorCode(1008003005, \"请至少添加一个规格值哦\");\n    ErrorCode STORE_PRODUCT_RULE_RE = new ErrorCode(1008003006, \"规格值里包含'-',请重新添加\");\n    ErrorCode STORE_PRODUCT_STOCK_ERROR = new ErrorCode(1008003007, \"库存不能低于0\");\n    ErrorCode STORE_PRODUCT_SLIDER_ERROR = new ErrorCode(1008003008, \"请上传轮播图\");\n    ErrorCode STORE_PRODUCT_ATTR_NEED = new ErrorCode(1008003009, \"请设置至少一个属性\");\n\n    // ========== 运费模板 1008004000 ==========\n    ErrorCode SHIPPING_TEMPLATES_NOT_EXISTS = new ErrorCode(1008004000, \"运费模板不存在\");\n    ErrorCode SHIPPING_TEMPLATES_FREE_NOT_EXISTS = new ErrorCode(1008004001, \"请添加包邮区域\");\n    ErrorCode SHIPPING_TEMPLATES_REGION_NOT_EXISTS = new ErrorCode(1008004002, \"请添加区域\");\n    ErrorCode SHIPPING_TEMPLATES_FREE_NEED = new ErrorCode(1008004000, \"请指定包邮地区\");\n\n\n    // ========== 评论 1008005000 ==========\n    ErrorCode STORE_PRODUCT_REPLY_NOT_EXISTS = new ErrorCode(1008005000, \"评论不存在\");\n    ErrorCode STORE_PRODUCT_RELATION_NOT_EXISTS = new ErrorCode(1008005001, \"商品点赞和收藏不存在\");\n    ErrorCode STORE_PRODUCT_RELATION_EXISTS = new ErrorCode(1008005002, \"已经收藏过\");\n    ErrorCode PRODUCT_STOCK_LESS = new ErrorCode(1008005003, \"商品库存不足\");\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-api/src/main/java/co/yixiang/yshop/module/product/enums/ProductConstants.java",
    "content": "package co.yixiang.yshop.module.product.enums;\n\nimport co.yixiang.yshop.framework.common.exception.ErrorCode;\n\n/**\n * Product 错误码枚举类\n *\n * product 系统，使用 1-008-000-000 段\n */\npublic interface ProductConstants {\n\n   String NO_COMMENT_CONTENT = \"此用户没有填写评价\";\n   String FREE_POSTAGE = \"全国包邮\";\n   String POSTAGE_TEMP_NOT = \"运费模板不存在\";\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-api/src/main/java/co/yixiang/yshop/module/product/enums/comment/ProductCommentAuditStatusEnum.java",
    "content": "package co.yixiang.yshop.module.product.enums.comment;\n\nimport co.yixiang.yshop.framework.common.core.IntArrayValuable;\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\nimport java.util.Arrays;\n\n/**\n * 商品评论的审批状态枚举\n *\n * @author yshop\n */\n@Getter\n@AllArgsConstructor\npublic enum ProductCommentAuditStatusEnum implements IntArrayValuable {\n\n    NONE(1, \"待审核\"),\n    APPROVE(2, \"审批通过\"),\n    REJECT(2, \"审批不通过\"),;\n\n    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(ProductCommentAuditStatusEnum::getStatus).toArray();\n\n    /**\n     * 审批状态\n     */\n    private final Integer status;\n    /**\n     * 状态名\n     */\n    private final String name;\n\n    @Override\n    public int[] array() {\n        return ARRAYS;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-api/src/main/java/co/yixiang/yshop/module/product/enums/delivery/DeliveryTypeEnum.java",
    "content": "package co.yixiang.yshop.module.product.enums.delivery;\n\nimport co.yixiang.yshop.framework.common.core.IntArrayValuable;\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\nimport java.util.Arrays;\n\n/**\n * 配送方式枚举\n *\n * @author yshop\n */\n@Getter\n@AllArgsConstructor\npublic enum DeliveryTypeEnum implements IntArrayValuable {\n\n    // TODO yshop：英文单词，需要再想下；\n    EXPRESS(1, \"快递发货\"),\n    USER(2, \"用户自提\"),;\n\n    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(DeliveryTypeEnum::getMode).toArray();\n\n    /**\n     * 配送方式\n     */\n    private final Integer mode;\n    /**\n     * 状态名\n     */\n    private final String name;\n\n    @Override\n    public int[] array() {\n        return ARRAYS;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-api/src/main/java/co/yixiang/yshop/module/product/enums/group/ProductGroupStyleEnum.java",
    "content": "package co.yixiang.yshop.module.product.enums.group;\n\nimport co.yixiang.yshop.framework.common.core.IntArrayValuable;\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\nimport java.util.Arrays;\n\n/**\n * 商品分组的样式枚举\n *\n * @author yshop\n */\n@Getter\n@AllArgsConstructor\npublic enum ProductGroupStyleEnum implements IntArrayValuable {\n\n    ONE(1, \"每列一个\"),\n    TWO(2, \"每列两个\"),\n    THREE(2, \"每列三个\"),;\n\n    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(ProductGroupStyleEnum::getStyle).toArray();\n\n    /**\n     * 列表样式\n     */\n    private final Integer style;\n    /**\n     * 状态名\n     */\n    private final String name;\n\n    @Override\n    public int[] array() {\n        return ARRAYS;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-api/src/main/java/co/yixiang/yshop/module/product/enums/product/DefaultEnum.java",
    "content": "package co.yixiang.yshop.module.product.enums.product;\r\n\r\nimport lombok.AllArgsConstructor;\r\nimport lombok.Getter;\r\n\r\n/**\r\n * @author hupeng\r\n * 产品相关规格类型枚举\r\n */\r\n@Getter\r\n@AllArgsConstructor\r\npublic enum DefaultEnum {\r\n    DEFAULT_0(0,\"默认值0\"),\r\n    DEFAULT_1(1,\"默认值1\");\r\n\r\n    private Integer value;\r\n    private String desc;\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-api/src/main/java/co/yixiang/yshop/module/product/enums/product/ProductEnum.java",
    "content": "/**\n * Copyright (C) 2018-2022\n * All rights reserved, Designed By www.yixiang.co\n\n */\npackage co.yixiang.yshop.module.product.enums.product;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\nimport java.util.stream.Stream;\n\n/**\n * @author hupeng\n * 产品相关枚举\n */\n@Getter\n@AllArgsConstructor\npublic enum ProductEnum {\n\n\tTYPE_1(1,\"精品推荐\"),\n\tTYPE_2(2,\"热门榜单\"),\n\tTYPE_3(3,\"首发新品\"),\n\tTYPE_4(4,\"猜你喜欢\");\n\n\n\tprivate Integer value;\n\tprivate String desc;\n\n\tpublic static ProductEnum toType(int value) {\n\t\treturn Stream.of(ProductEnum.values())\n\t\t\t\t.filter(p -> p.value == value)\n\t\t\t\t.findAny()\n\t\t\t\t.orElse(null);\n\t}\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-api/src/main/java/co/yixiang/yshop/module/product/enums/product/ProductTypeEnum.java",
    "content": "package co.yixiang.yshop.module.product.enums.product;\n\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * @author hupeng\n * 产品类型枚举\n */\n\n@Getter\n@AllArgsConstructor\npublic enum ProductTypeEnum {\n\n    PINK(\"pink\",\"拼团\"),\n\n    SECKILL(\"seckill\",\"秒杀\"),\n\n    COMBINATION(\"combination\",\"拼团产品\");\n\n    private String value;\n    private String desc;\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-api/src/main/java/co/yixiang/yshop/module/product/enums/product/RelationCateEnum.java",
    "content": "package co.yixiang.yshop.module.product.enums.product;\r\n\r\nimport lombok.AllArgsConstructor;\r\nimport lombok.Getter;\r\n\r\n/**\r\n * @author hupeng\r\n * 产品收藏类型枚举\r\n */\r\n@Getter\r\n@AllArgsConstructor\r\npublic enum RelationCateEnum {\r\n    COMMON(\"common\",\"普通商品\"),\r\n    SECKILL(\"seckill\",\"秒杀商品\"),\r\n    BARGAIN(\"bargain\",\"砍价商品\"),\r\n    PINK(\"pink\",\"拼团商品\");\r\n\r\n    private String value;\r\n    private String desc;\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-api/src/main/java/co/yixiang/yshop/module/product/enums/product/RelationEnum.java",
    "content": "package co.yixiang.yshop.module.product.enums.product;\r\n\r\nimport lombok.AllArgsConstructor;\r\nimport lombok.Getter;\r\n\r\n/**\r\n * @author hupeng\r\n * 产品收藏类型枚举\r\n */\r\n@Getter\r\n@AllArgsConstructor\r\npublic enum RelationEnum {\r\n    COLLECT(\"collect\",\"收藏\"),\r\n    FOOT(\"foot\",\"足迹\");\r\n\r\n    private String value;\r\n    private String desc;\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-api/src/main/java/co/yixiang/yshop/module/product/enums/product/ScoreEnum.java",
    "content": "package co.yixiang.yshop.module.product.enums.product;\r\n\r\nimport lombok.AllArgsConstructor;\r\nimport lombok.Getter;\r\n\r\n/**\r\n * @author hupeng\r\n * 产品相关规格类型枚举\r\n */\r\n@Getter\r\n@AllArgsConstructor\r\npublic enum ScoreEnum {\r\n    DEFAULT_0(0,\"0分数\"),\r\n    DEFAULT_1(1,\"1分数\"),\r\n    DEFAULT_2(2,\"2分数\"),\r\n    DEFAULT_3(3,\"3分数\"),\r\n    DEFAULT_4(4,\"4分数\"),\r\n    DEFAULT_5(5,\"5分数\");\r\n\r\n    private Integer value;\r\n    private String desc;\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-api/src/main/java/co/yixiang/yshop/module/product/enums/product/SpecTypeEnum.java",
    "content": "package co.yixiang.yshop.module.product.enums.product;\r\n\r\nimport lombok.AllArgsConstructor;\r\nimport lombok.Getter;\r\n\r\n/**\r\n * @author hupeng\r\n * 产品相关规格类型枚举\r\n */\r\n@Getter\r\n@AllArgsConstructor\r\npublic enum SpecTypeEnum {\r\n    TYPE_0(0,\"单规格\"),\r\n    TYPE_1(1,\"多规格\");\r\n\r\n    private Integer value;\r\n    private String desc;\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-api/src/main/java/co/yixiang/yshop/module/product/enums/spu/ProductSpuSpecTypeEnum.java",
    "content": "package co.yixiang.yshop.module.product.enums.spu;\n\nimport co.yixiang.yshop.framework.common.core.IntArrayValuable;\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\nimport java.util.Arrays;\n\n/**\n * 商品 SPU 规格类型\n *\n * @author yshop\n */\n@Getter\n@AllArgsConstructor\npublic enum ProductSpuSpecTypeEnum implements IntArrayValuable {\n\n    RECYCLE(1, \"统一规格\"),\n    DISABLE(2, \"多规格\");\n\n    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(ProductSpuSpecTypeEnum::getType).toArray();\n\n    /**\n     * 规格类型\n     */\n    private final Integer type;\n    /**\n     * 规格名称\n     */\n    private final String name;\n\n    @Override\n    public int[] array() {\n        return ARRAYS;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-api/src/main/java/co/yixiang/yshop/module/product/enums/spu/ProductSpuStatusEnum.java",
    "content": "package co.yixiang.yshop.module.product.enums.spu;\n\nimport co.yixiang.yshop.framework.common.core.IntArrayValuable;\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\nimport java.util.Arrays;\n\n/**\n * 商品 SPU 状态\n *\n * @author yshop\n */\n@Getter\n@AllArgsConstructor\npublic enum ProductSpuStatusEnum implements IntArrayValuable {\n\n    RECYCLE(-1, \"回收站\"),\n    DISABLE(0, \"下架\"),\n    ENABLE(1, \"上架\"),;\n\n    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(ProductSpuStatusEnum::getStatus).toArray();\n\n    /**\n     * 状态\n     */\n    private final Integer status;\n    /**\n     * 状态名\n     */\n    private final String name;\n\n    @Override\n    public int[] array() {\n        return ARRAYS;\n    }\n\n    /**\n     * 判断是否处于【上架】状态\n     *\n     * @param status 状态\n     * @return 是否处于【上架】状态\n     */\n    public static boolean isEnable(Integer status) {\n        return ENABLE.getStatus().equals(status);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>co.yixiang.boot</groupId>\n        <artifactId>yshop-module-mall</artifactId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>yshop-module-product-biz</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>\n        product 模块，主要实现商品相关功能\n        例如：品牌、商品分类、spu、sku等功能。\n    </description>\n\n    <dependencies>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-product-api</artifactId>\n            <version>${revision}</version>\n        </dependency>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-store-biz</artifactId>\n            <version>${revision}</version>\n        </dependency>\n\n        <!-- 业务组件 -->\n<!--        <dependency>-->\n<!--            <groupId>co.yixiang.boot</groupId>-->\n<!--            <artifactId>yshop-spring-boot-starter-biz-operatelog</artifactId>-->\n<!--        </dependency>-->\n\n        <!-- Web 相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-web</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-security</artifactId>\n        </dependency>\n\n        <!-- DB 相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-mybatis</artifactId>\n        </dependency>\n\n        <!-- Test 测试相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-test</artifactId>\n        </dependency>\n\n        <!-- 工具类相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-excel</artifactId>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/api/package-info.java",
    "content": "package co.yixiang.yshop.module.product.api;\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/api/product/ProductApiImpl.java",
    "content": "package co.yixiang.yshop.module.product.api.product;\n\nimport co.yixiang.yshop.framework.common.enums.ShopCommonEnum;\nimport co.yixiang.yshop.framework.common.exception.ErrorCode;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproduct.StoreProductDO;\nimport co.yixiang.yshop.module.product.enums.product.ProductTypeEnum;\nimport co.yixiang.yshop.module.product.service.storeproduct.AppStoreProductService;\nimport org.springframework.stereotype.Service;\n\nimport jakarta.annotation.Resource;\nimport java.util.Date;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.product.enums.ErrorCodeConstants.STORE_PRODUCT_NOT_EXISTS;\n\n@Service\npublic class ProductApiImpl implements ProductApi {\n//\n//    @Resource\n//    private AppStoreProductService appStoreProductService;\n//    /**\n//     * 检测商品库存\n//     *\n//     * @param uid               用户ID\n//     * @param productId         产品ID\n//     * @param cartNum           购买数量\n//     * @param productAttrUnique 商品属性Unique\n//     * @param combinationId     拼团产品ID\n//     */\n//   @Override\n//   public void checkProductStock(Long uid, Long productId, Integer cartNum, String productAttrUnique) {\n//        StoreProductDO product = appStoreProductService\n//                .lambdaQuery().eq(StoreProductDO::getId, productId)\n//                .eq(StoreProductDO::getIsShow, ShopCommonEnum.SHOW_1.getValue())\n//                .one();\n//        if (product == null) {\n//            throw exception(STORE_PRODUCT_NOT_EXISTS);\n//        }\n//\n//        int stock = appStoreProductService.getProductStock(productId, productAttrUnique, ProductTypeEnum.PINK.getValue());\n//        if (stock < cartNum) {\n//            throw exception(new ErrorCode(1008003010, product.getStoreName() + \"库存不足\" + cartNum));\n//        }\n//\n//    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/controller/admin/category/ProductCategoryController.java",
    "content": "package co.yixiang.yshop.module.product.controller.admin.category;\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils;\nimport co.yixiang.yshop.module.product.controller.admin.category.vo.ProductCategoryCreateReqVO;\nimport co.yixiang.yshop.module.product.controller.admin.category.vo.ProductCategoryListReqVO;\nimport co.yixiang.yshop.module.product.controller.admin.category.vo.ProductCategoryRespVO;\nimport co.yixiang.yshop.module.product.controller.admin.category.vo.ProductCategoryUpdateReqVO;\nimport co.yixiang.yshop.module.product.convert.category.ProductCategoryConvert;\nimport co.yixiang.yshop.module.product.dal.dataobject.category.ProductCategoryDO;\nimport co.yixiang.yshop.module.product.service.category.ProductCategoryService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Valid;\nimport java.util.Comparator;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 商品分类\")\n@RestController\n@RequestMapping(\"/product/category\")\n@Validated\npublic class ProductCategoryController {\n\n    @Resource\n    private ProductCategoryService categoryService;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建商品分类\")\n    @PreAuthorize(\"@ss.hasPermission('product:category:create')\")\n    public CommonResult<Long> createCategory(@Valid @RequestBody ProductCategoryCreateReqVO createReqVO) {\n        return success(categoryService.createCategory(createReqVO));\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"更新商品分类\")\n    @PreAuthorize(\"@ss.hasPermission('product:category:update')\")\n    public CommonResult<Boolean> updateCategory(@Valid @RequestBody ProductCategoryUpdateReqVO updateReqVO) {\n        categoryService.updateCategory(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除商品分类\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('product:category:delete')\")\n    public CommonResult<Boolean> deleteCategory(@RequestParam(\"id\") Long id) {\n        categoryService.deleteCategory(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得商品分类\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('product:category:query')\")\n    public CommonResult<ProductCategoryRespVO> getCategory(@RequestParam(\"id\") Long id) {\n        ProductCategoryDO category = categoryService.getCategory(id);\n        return success(ProductCategoryConvert.INSTANCE.convert(category));\n    }\n\n    @GetMapping(\"/list\")\n    @Operation(summary = \"获得商品分类列表\")\n    @PreAuthorize(\"@ss.hasPermission('product:category:query')\")\n    public CommonResult<List<ProductCategoryRespVO>> getCategoryList(@Valid ProductCategoryListReqVO treeListReqVO) {\n        List<ProductCategoryDO> list = categoryService.getEnableCategoryList(treeListReqVO);\n        list.sort(Comparator.comparing(ProductCategoryDO::getSort));\n        return success(ProductCategoryConvert.INSTANCE.convertList(list));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/controller/admin/category/vo/ProductCategoryBaseVO.java",
    "content": "package co.yixiang.yshop.module.product.controller.admin.category.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotBlank;\nimport jakarta.validation.constraints.NotNull;\n\n/**\n* 商品分类 Base VO，提供给添加、修改、详细的子 VO 使用\n* 如果子 VO 存在差异的字段，请不要添加到这里，影响 Swagger 文档生成\n*/\n@Data\npublic class ProductCategoryBaseVO {\n\n    /**\n     * 店铺id\n     */\n    private Integer shopId;\n\n    /**\n     * 店铺名称\n     */\n    private String shopName;\n\n    @Schema(description = \"父分类编号\", required = true, example = \"1\")\n    //@NotNull(message = \"父分类编号不能为空\")\n    private Long parentId;\n\n    @Schema(description = \"分类名称\", required = true, example = \"办公文具\")\n    @NotBlank(message = \"分类名称不能为空\")\n    private String name;\n\n    @Schema(description = \"分类图片\", required = true)\n    @NotBlank(message = \"分类图片不能为空\")\n    private String picUrl;\n\n    @Schema(description = \"分类排序\", required = true, example = \"1\")\n    private Integer sort;\n\n    @Schema(description = \"分类描述\", required = true, example = \"描述\")\n    private String description;\n\n    @Schema(description = \"开启状态\", required = true, example = \"0\")\n    @NotNull(message = \"开启状态不能为空\")\n    private Integer status;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/controller/admin/category/vo/ProductCategoryCreateReqVO.java",
    "content": "package co.yixiang.yshop.module.product.controller.admin.category.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\n\n@Schema(description = \"管理后台 - 商品分类创建 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class ProductCategoryCreateReqVO extends ProductCategoryBaseVO {\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/controller/admin/category/vo/ProductCategoryListReqVO.java",
    "content": "package co.yixiang.yshop.module.product.controller.admin.category.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\n@Schema(description = \"管理后台 - 商品分类列表查询 Request VO\")\n@Data\npublic class ProductCategoryListReqVO {\n\n    @Schema(description = \"分类名称\", example = \"办公文具\")\n    private String name;\n\n    @Schema(description = \"店铺名称\", example = \"办公文具\")\n    private String shopName;\n\n    private Integer shopId;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/controller/admin/category/vo/ProductCategoryRespVO.java",
    "content": "package co.yixiang.yshop.module.product.controller.admin.category.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\n\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 商品分类 Response VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class ProductCategoryRespVO extends ProductCategoryBaseVO {\n\n    @Schema(description = \"分类编号\", required = true, example = \"2\")\n    private Long id;\n\n    @Schema(description = \"创建时间\", required = true)\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/controller/admin/category/vo/ProductCategoryUpdateReqVO.java",
    "content": "package co.yixiang.yshop.module.product.controller.admin.category.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\n\nimport jakarta.validation.constraints.NotNull;\n\n@Schema(description = \"管理后台 - 商品分类更新 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class ProductCategoryUpdateReqVO extends ProductCategoryBaseVO {\n\n    @Schema(description = \"分类编号\", required = true, example = \"2\")\n    @NotNull(message = \"分类编号不能为空\")\n    private Long id;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/controller/admin/storeproduct/StoreProductController.java",
    "content": "package co.yixiang.yshop.module.product.controller.admin.storeproduct;\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.excel.core.util.ExcelUtils;\nimport co.yixiang.yshop.module.product.controller.admin.storeproduct.vo.*;\nimport co.yixiang.yshop.module.product.convert.storeproduct.StoreProductConvert;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproduct.StoreProductDO;\nimport co.yixiang.yshop.module.product.service.storeproduct.StoreProductService;\nimport co.yixiang.yshop.module.product.service.storeproduct.dto.StoreProductDto;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.servlet.http.HttpServletResponse;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Valid;\nimport java.io.IOException;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Map;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 商品\")\n@RestController\n@RequestMapping(\"/product/store-product\")\n@Validated\npublic class StoreProductController {\n\n    @Resource\n    private StoreProductService storeProductService;\n\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建商品\")\n    @PreAuthorize(\"@ss.hasPermission('shop:store-product:create')\")\n    public CommonResult<Boolean> createStoreProduct(@Validated @RequestBody StoreProductDto storeProductDto) {\n        storeProductService.insertAndEditYxStoreProduct(storeProductDto);\n        return success(true);\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"更新商品\")\n    @PreAuthorize(\"@ss.hasPermission('shop:store-product:update')\")\n    public CommonResult<Boolean> updateStoreProduct(@Valid @RequestBody StoreProductUpdateReqVO updateReqVO) {\n        storeProductService.updateStoreProduct(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除商品\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('shop:store-product:delete')\")\n    public CommonResult<Boolean> deleteStoreProduct(@RequestParam(\"id\") Long id) {\n        storeProductService.deleteStoreProduct(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得商品\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('shop:store-product:query')\")\n    public CommonResult<StoreProductRespVO> getStoreProduct(@RequestParam(\"id\") Long id) {\n        StoreProductDO storeProduct = storeProductService.getStoreProduct(id);\n        return success(StoreProductConvert.INSTANCE.convert(storeProduct));\n    }\n\n    @GetMapping(\"/list\")\n    @Operation(summary = \"获得商品列表\")\n    @Parameter(name = \"ids\", description = \"编号列表\", required = true, example = \"1024,2048\")\n    @PreAuthorize(\"@ss.hasPermission('shop:store-product:query')\")\n    public CommonResult<List<StoreProductRespVO>> getStoreProductList(@RequestParam(\"ids\") Collection<Long> ids) {\n        List<StoreProductDO> list = storeProductService.getStoreProductList(ids);\n        return success(StoreProductConvert.INSTANCE.convertList(list));\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得商品分页\")\n    @PreAuthorize(\"@ss.hasPermission('shop:store-product:query')\")\n    public CommonResult<PageResult<StoreProductRespVO>> getStoreProductPage(@Valid StoreProductPageReqVO pageVO) {\n        PageResult<StoreProductDO> pageResult = storeProductService.getStoreProductPage(pageVO);\n        return success(StoreProductConvert.INSTANCE.convertPage(pageResult));\n    }\n\n    @GetMapping(\"/export-excel\")\n    @Operation(summary = \"导出商品 Excel\")\n    @PreAuthorize(\"@ss.hasPermission('shop:store-product:export')\")\n    public void exportStoreProductExcel(@Valid StoreProductExportReqVO exportReqVO,\n              HttpServletResponse response) throws IOException {\n        List<StoreProductDO> list = storeProductService.getStoreProductList(exportReqVO);\n        // 导出 Excel\n        List<StoreProductExcelVO> datas = StoreProductConvert.INSTANCE.convertList02(list);\n        ExcelUtils.write(response, \"商品.xls\", \"数据\", StoreProductExcelVO.class, datas);\n    }\n\n    @Operation(summary = \"获取商品信息\")\n    @GetMapping(value = \"/info/{id}\")\n    public CommonResult<Map<String,Object>> info(@PathVariable Long id){\n        return success(storeProductService.getProductInfo(id));\n    }\n\n\n    @Operation(summary = \"生成属性\")\n    @PostMapping(value = \"/isFormatAttr/{id}\")\n    public CommonResult<Map<String,Object>> isFormatAttr(@PathVariable Long id,@RequestBody String jsonStr){\n        return success(storeProductService.getFormatAttr(id,jsonStr,false));\n    }\n\n    @Operation(summary = \"商品上架/下架\")\n    @GetMapping(value = \"/sale\")\n    public CommonResult<Boolean> onSale(@RequestParam(\"id\") Long id,@RequestParam(\"type\") int status){\n        storeProductService.onSale(id,status);\n        return success(true);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/controller/admin/storeproduct/vo/StoreProductBaseVO.java",
    "content": "package co.yixiang.yshop.module.product.controller.admin.storeproduct.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotNull;\nimport java.math.BigDecimal;\n\n/**\n* 商品 Base VO，提供给添加、修改、详细的子 VO 使用\n* 如果子 VO 存在差异的字段，请不要添加到这里，影响 Swagger 文档生成\n*/\n@Data\npublic class StoreProductBaseVO {\n\n    @Schema(description = \"商品图片\", required = true)\n    @NotNull(message = \"商品图片不能为空\")\n    private String image;\n\n    private String shopName;\n\n    private Integer shopId;\n\n    @Schema(description = \"商品名称\", required = true, example = \"张三\")\n    @NotNull(message = \"商品名称不能为空\")\n    private String storeName;\n\n    @Schema(description = \"商品价格\", required = true, example = \"18735\")\n    @NotNull(message = \"商品价格不能为空\")\n    private BigDecimal price;\n\n    @Schema(description = \"单位名\", example = \"李四\")\n    private String unitName;\n\n    @Schema(description = \"销量\")\n    private Integer sales;\n\n    @Schema(description = \"库存\")\n    private Integer stock;\n\n    @Schema(description = \"上架状态\")\n    private Integer isShow;\n\n    @Schema(description = \"是否包邮\")\n    private Integer isPostage;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/controller/admin/storeproduct/vo/StoreProductCreateReqVO.java",
    "content": "package co.yixiang.yshop.module.product.controller.admin.storeproduct.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\n\nimport jakarta.validation.constraints.NotNull;\nimport java.math.BigDecimal;\n\n@Schema(description = \"管理后台 - 商品创建 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class StoreProductCreateReqVO extends StoreProductBaseVO {\n\n    @Schema(description = \"轮播图\", required = true)\n    @NotNull(message = \"轮播图不能为空\")\n    private String sliderImage;\n\n    @Schema(description = \"商品简介\", required = true)\n    @NotNull(message = \"商品简介不能为空\")\n    private String storeInfo;\n\n    @Schema(description = \"关键字\", required = true)\n    @NotNull(message = \"关键字不能为空\")\n    private String keyword;\n\n    @Schema(description = \"产品条码（一维码）\")\n    private String barCode;\n\n    @Schema(description = \"分类id\", required = true, example = \"4928\")\n    @NotNull(message = \"分类id不能为空\")\n    private String cateId;\n\n    @Schema(description = \"会员价格\", example = \"18248\")\n    private BigDecimal vipPrice;\n\n    @Schema(description = \"市场价\", example = \"14818\")\n    private BigDecimal otPrice;\n\n    @Schema(description = \"邮费\")\n    private BigDecimal postage;\n\n    @Schema(description = \"排序\")\n    private Short sort;\n\n    @Schema(description = \"状态（0：未上架，1：上架）\")\n    private Integer isShow;\n\n    @Schema(description = \"是否热卖\")\n    private Boolean isHot;\n\n    @Schema(description = \"是否优惠\")\n    private Boolean isBenefit;\n\n    @Schema(description = \"是否精品\")\n    private Boolean isBest;\n\n    @Schema(description = \"是否新品\")\n    private Integer isNew;\n\n    @Schema(description = \"产品描述\", example = \"你说的对\")\n    private String description;\n\n    @Schema(description = \"商户是否代理 0不可代理1可代理\")\n    private Byte merUse;\n\n    @Schema(description = \"获得积分\")\n    private BigDecimal giveIntegral;\n\n    @Schema(description = \"成本价\")\n    private BigDecimal cost;\n\n    @Schema(description = \"秒杀状态 0 未开启 1已开启\")\n    private Byte isSeckill;\n\n    @Schema(description = \"砍价状态 0未开启 1开启\")\n    private Byte isBargain;\n\n    @Schema(description = \"是否优品推荐\")\n    private Boolean isGood;\n\n    @Schema(description = \"虚拟销量\")\n    private Integer ficti;\n\n    @Schema(description = \"浏览量\")\n    private Integer browse;\n\n    @Schema(description = \"产品二维码地址(用户小程序海报)\", required = true)\n    private String codePath;\n\n    @Schema(description = \"是否单独分佣\")\n    private Boolean isSub;\n\n    @Schema(description = \"运费模板ID\", example = \"18065\")\n    private Integer tempId;\n\n    @Schema(description = \"规格 0单 1多\", example = \"1\")\n    private Integer specType;\n\n    @Schema(description = \"是开启积分兑换\")\n    private Byte isIntegral;\n\n    @Schema(description = \"需要多少积分兑换 只在开启积分兑换时生效\")\n    private Integer integral;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/controller/admin/storeproduct/vo/StoreProductExcelVO.java",
    "content": "package co.yixiang.yshop.module.product.controller.admin.storeproduct.vo;\n\nimport com.alibaba.excel.annotation.ExcelProperty;\nimport lombok.Data;\n\nimport java.math.BigDecimal;\nimport java.time.LocalDateTime;\n\n/**\n * 商品 Excel VO\n *\n * @author yshop\n */\n@Data\npublic class StoreProductExcelVO {\n\n    @ExcelProperty(\"商品id\")\n    private Long id;\n\n    @ExcelProperty(\"商品图片\")\n    private String image;\n\n    @ExcelProperty(\"商品名称\")\n    private String storeName;\n\n    @ExcelProperty(\"商品价格\")\n    private BigDecimal price;\n\n    @ExcelProperty(\"单位名\")\n    private String unitName;\n\n    @ExcelProperty(\"销量\")\n    private Integer sales;\n\n    @ExcelProperty(\"库存\")\n    private Integer stock;\n\n    @ExcelProperty(\"添加时间\")\n    private LocalDateTime createTime;\n\n    @ExcelProperty(\"是否包邮\")\n    private Integer isPostage;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/controller/admin/storeproduct/vo/StoreProductExportReqVO.java",
    "content": "package co.yixiang.yshop.module.product.controller.admin.storeproduct.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\n@Schema(description = \"管理后台 - 商品 Excel 导出 Request VO，参数和 StoreProductPageReqVO 是一致的\")\n@Data\npublic class StoreProductExportReqVO {\n\n    @Schema(description = \"商品名称\", example = \"张三\")\n    private String storeName;\n\n    @Schema(description = \"是否包邮\")\n    private Byte isPostage;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/controller/admin/storeproduct/vo/StoreProductPageReqVO.java",
    "content": "package co.yixiang.yshop.module.product.controller.admin.storeproduct.vo;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\n\nimport java.util.List;\n\n@Schema(description = \"管理后台 - 商品分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class StoreProductPageReqVO extends PageParam {\n\n    @Schema(description = \"商品名称\", example = \"张三\")\n    private String storeName;\n\n    private String shopName;\n\n    @Schema(description = \"是否包邮\")\n    private Byte isPostage;\n\n    @Schema(description = \"上下架\", example = \"1\")\n    private String isShow;\n\n    @Schema(description = \"库存售罄\", example = \"0\")\n    private String stock;\n\n    @Schema(description = \"库存售罄\", example = \"0\")\n    private String cateId;\n\n    private List<Long> catIds;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/controller/admin/storeproduct/vo/StoreProductRespVO.java",
    "content": "package co.yixiang.yshop.module.product.controller.admin.storeproduct.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 商品 Response VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class StoreProductRespVO extends StoreProductBaseVO {\n\n    @Schema(description = \"商品id\", required = true, example = \"1175\")\n    private Long id;\n\n    @Schema(description = \"添加时间\")\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/controller/admin/storeproduct/vo/StoreProductUpdateReqVO.java",
    "content": "package co.yixiang.yshop.module.product.controller.admin.storeproduct.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\n\nimport jakarta.validation.constraints.NotNull;\nimport java.math.BigDecimal;\n\n@Schema(description = \"管理后台 - 商品更新 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class StoreProductUpdateReqVO extends StoreProductBaseVO {\n\n    @Schema(description = \"商品id\", required = true, example = \"1175\")\n    @NotNull(message = \"商品id不能为空\")\n    private Long id;\n\n    @Schema(description = \"轮播图\", required = true)\n    @NotNull(message = \"轮播图不能为空\")\n    private String sliderImage;\n\n    @Schema(description = \"商品简介\", required = true)\n    @NotNull(message = \"商品简介不能为空\")\n    private String storeInfo;\n\n    @Schema(description = \"关键字\", required = true)\n    @NotNull(message = \"关键字不能为空\")\n    private String keyword;\n\n    @Schema(description = \"产品条码（一维码）\")\n    private String barCode;\n\n    @Schema(description = \"分类id\", required = true, example = \"4928\")\n    @NotNull(message = \"分类id不能为空\")\n    private String cateId;\n\n    @Schema(description = \"会员价格\", example = \"18248\")\n    private BigDecimal vipPrice;\n\n    @Schema(description = \"市场价\", example = \"14818\")\n    private BigDecimal otPrice;\n\n    @Schema(description = \"邮费\")\n    private BigDecimal postage;\n\n    @Schema(description = \"排序\")\n    private Short sort;\n\n    @Schema(description = \"状态（0：未上架，1：上架）\")\n    private Integer isShow;\n\n    @Schema(description = \"是否热卖\")\n    private Boolean isHot;\n\n    @Schema(description = \"是否优惠\")\n    private Boolean isBenefit;\n\n    @Schema(description = \"是否精品\")\n    private Boolean isBest;\n\n    @Schema(description = \"是否新品\")\n    private Integer isNew;\n\n    @Schema(description = \"产品描述\", example = \"你说的对\")\n    private String description;\n\n    @Schema(description = \"商户是否代理 0不可代理1可代理\")\n    private Byte merUse;\n\n    @Schema(description = \"获得积分\")\n    private BigDecimal giveIntegral;\n\n    @Schema(description = \"成本价\")\n    private BigDecimal cost;\n\n    @Schema(description = \"秒杀状态 0 未开启 1已开启\")\n    private Byte isSeckill;\n\n    @Schema(description = \"砍价状态 0未开启 1开启\")\n    private Byte isBargain;\n\n    @Schema(description = \"是否优品推荐\")\n    private Boolean isGood;\n\n    @Schema(description = \"虚拟销量\")\n    private Integer ficti;\n\n    @Schema(description = \"浏览量\")\n    private Integer browse;\n\n    @Schema(description = \"产品二维码地址(用户小程序海报)\", required = true)\n    @NotNull(message = \"产品二维码地址(用户小程序海报)不能为空\")\n    private String codePath;\n\n    @Schema(description = \"是否单独分佣\")\n    private Boolean isSub;\n\n    @Schema(description = \"运费模板ID\", example = \"18065\")\n    private Integer tempId;\n\n    @Schema(description = \"规格 0单 1多\", example = \"1\")\n    private Integer specType;\n\n    @Schema(description = \"是开启积分兑换\")\n    private Byte isIntegral;\n\n    @Schema(description = \"需要多少积分兑换 只在开启积分兑换时生效\")\n    private Integer integral;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/controller/admin/storeproductreply/StoreProductReplyController.java",
    "content": "package co.yixiang.yshop.module.product.controller.admin.storeproductreply;\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.product.controller.admin.storeproductreply.vo.StoreProductReplyPageReqVO;\nimport co.yixiang.yshop.module.product.controller.admin.storeproductreply.vo.StoreProductReplyRespVO;\nimport co.yixiang.yshop.module.product.controller.admin.storeproductreply.vo.StoreProductReplyUpdateReqVO;\nimport co.yixiang.yshop.module.product.controller.app.product.vo.AppStoreProductReplyQueryVo;\nimport co.yixiang.yshop.module.product.convert.storeproductreply.StoreProductReplyConvert;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproductreply.StoreProductReplyDO;\nimport co.yixiang.yshop.module.product.service.storeproductreply.StoreProductReplyService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Valid;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 评论\")\n@RestController\n@RequestMapping(\"/product/store-product-reply\")\n@Validated\npublic class StoreProductReplyController {\n\n    @Resource\n    private StoreProductReplyService storeProductReplyService;\n\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"更新评论\")\n    @PreAuthorize(\"@ss.hasPermission('product:store-product-reply:update')\")\n    public CommonResult<Boolean> updateStoreProductReply(@Valid @RequestBody StoreProductReplyUpdateReqVO updateReqVO) {\n        storeProductReplyService.updateStoreProductReply(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除评论\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('product:store-product-reply:delete')\")\n    public CommonResult<Boolean> deleteStoreProductReply(@RequestParam(\"id\") Long id) {\n        storeProductReplyService.deleteStoreProductReply(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得评论\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('product:store-product-reply:query')\")\n    public CommonResult<StoreProductReplyRespVO> getStoreProductReply(@RequestParam(\"id\") Long id) {\n        StoreProductReplyDO storeProductReply = storeProductReplyService.getStoreProductReply(id);\n        return success(StoreProductReplyConvert.INSTANCE.convert(storeProductReply));\n    }\n\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得评论分页\")\n    @PreAuthorize(\"@ss.hasPermission('product:store-product-reply:query')\")\n    public CommonResult<PageResult<AppStoreProductReplyQueryVo>> getStoreProductReplyPage(@Valid StoreProductReplyPageReqVO pageVO) {\n        PageResult<AppStoreProductReplyQueryVo> pageResult = storeProductReplyService.getStoreProductReplyPage(pageVO);\n        return success(pageResult);\n    }\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/controller/admin/storeproductreply/vo/StoreProductReplyBaseVO.java",
    "content": "package co.yixiang.yshop.module.product.controller.admin.storeproductreply.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.util.*;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\nimport jakarta.validation.constraints.*;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n/**\n* 评论 Base VO，提供给添加、修改、详细的子 VO 使用\n* 如果子 VO 存在差异的字段，请不要添加到这里，影响 Swagger 文档生成\n*/\n@Data\npublic class StoreProductReplyBaseVO {\n\n    @Schema(description = \"用户ID\", required = true, example = \"12573\")\n    @NotNull(message = \"用户ID不能为空\")\n    private Long uid;\n\n    @Schema(description = \"订单ID\", required = true, example = \"5646\")\n    @NotNull(message = \"订单ID不能为空\")\n    private Long oid;\n\n    @Schema(description = \"唯一id\", required = true)\n    @NotNull(message = \"唯一id不能为空\")\n    private String unique;\n\n    @Schema(description = \"产品id\", required = true, example = \"23509\")\n    @NotNull(message = \"产品id不能为空\")\n    private Long productId;\n\n    @Schema(description = \"某种商品类型(普通商品、秒杀商品）\", required = true, example = \"2\")\n    @NotNull(message = \"某种商品类型(普通商品、秒杀商品）不能为空\")\n    private String replyType;\n\n    @Schema(description = \"商品分数\", required = true)\n    @NotNull(message = \"商品分数不能为空\")\n    private Integer productScore;\n\n    @Schema(description = \"服务分数\", required = true)\n    @NotNull(message = \"服务分数不能为空\")\n    private Integer serviceScore;\n\n    @Schema(description = \"评论内容\", required = true)\n    @NotNull(message = \"评论内容不能为空\")\n    private String comment;\n\n    @Schema(description = \"评论图片\", required = true)\n    @NotNull(message = \"评论图片不能为空\")\n    private String pics;\n\n    @Schema(description = \"管理员回复内容\")\n    private String merchantReplyContent;\n\n    @Schema(description = \"管理员回复时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime merchantReplyTime;\n\n    @Schema(description = \"0未回复1已回复\", required = true)\n    @NotNull(message = \"0未回复1已回复不能为空\")\n    private Integer isReply;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/controller/admin/storeproductreply/vo/StoreProductReplyPageReqVO.java",
    "content": "package co.yixiang.yshop.module.product.controller.admin.storeproductreply.vo;\n\nimport lombok.*;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport org.springframework.format.annotation.DateTimeFormat;\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 评论分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class StoreProductReplyPageReqVO extends PageParam {\n\n    @Schema(description = \"用户昵称\", example = \"12573\")\n    private String nickname;\n\n    @Schema(description = \"用户ID\", example = \"12573\")\n    private Long uid;\n\n    @Schema(description = \"订单ID\", example = \"5646\")\n    private Long oid;\n\n    @Schema(description = \"唯一id\")\n    private String unique;\n\n    @Schema(description = \"产品id\", example = \"23509\")\n    private Long productId;\n\n    @Schema(description = \"某种商品类型(普通商品、秒杀商品）\", example = \"2\")\n    private String replyType;\n\n    @Schema(description = \"商品分数\")\n    private Boolean productScore;\n\n    @Schema(description = \"服务分数\")\n    private Boolean serviceScore;\n\n    @Schema(description = \"评论内容\")\n    private String comment;\n\n    @Schema(description = \"评论图片\")\n    private String pics;\n\n    @Schema(description = \"评论时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n    @Schema(description = \"管理员回复内容\")\n    private String merchantReplyContent;\n\n    @Schema(description = \"管理员回复时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] merchantReplyTime;\n\n    @Schema(description = \"0未回复1已回复\")\n    private Boolean isReply;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/controller/admin/storeproductreply/vo/StoreProductReplyRespVO.java",
    "content": "package co.yixiang.yshop.module.product.controller.admin.storeproductreply.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 评论 Response VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class StoreProductReplyRespVO extends StoreProductReplyBaseVO {\n\n    @Schema(description = \"评论ID\", required = true, example = \"7419\")\n    private Long id;\n\n    @Schema(description = \"评论时间\", required = true)\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/controller/admin/storeproductreply/vo/StoreProductReplyUpdateReqVO.java",
    "content": "package co.yixiang.yshop.module.product.controller.admin.storeproductreply.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.util.*;\nimport jakarta.validation.constraints.*;\n\n@Schema(description = \"管理后台 - 评论更新 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class StoreProductReplyUpdateReqVO extends StoreProductReplyBaseVO {\n\n    @Schema(description = \"评论ID\", required = true, example = \"7419\")\n    @NotNull(message = \"评论ID不能为空\")\n    private Long id;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/controller/admin/storeproductrule/StoreProductRuleController.java",
    "content": "package co.yixiang.yshop.module.product.controller.admin.storeproductrule;\n\nimport jakarta.servlet.http.HttpServletResponse;\nimport org.springframework.web.bind.annotation.*;\nimport jakarta.annotation.Resource;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.Operation;\n\nimport jakarta.validation.*;\nimport java.util.*;\nimport java.io.IOException;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\nimport co.yixiang.yshop.framework.excel.core.util.ExcelUtils;\n\nimport co.yixiang.yshop.module.product.controller.admin.storeproductrule.vo.*;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproductrule.StoreProductRuleDO;\nimport co.yixiang.yshop.module.product.convert.storeproductrule.StoreProductRuleConvert;\nimport co.yixiang.yshop.module.product.service.storeproductrule.StoreProductRuleService;\n\n@Tag(name = \"管理后台 - 商品规则值(规格)\")\n@RestController\n@RequestMapping(\"/product/store-product-rule\")\n@Validated\npublic class StoreProductRuleController {\n\n    @Resource\n    private StoreProductRuleService storeProductRuleService;\n\n\n    @PostMapping(\"/save/{id}\")\n    @Operation(summary = \"创建与更新商品规则值(规格)\")\n    @PreAuthorize(\"@ss.hasPermission('shop:store-product-rule:create')\")\n    public CommonResult<Integer> createStoreProductRule(@Valid @RequestBody StoreProductRuleCreateReqVO createReqVO,@PathVariable Integer id) {\n        if(id != null && id > 0){\n            StoreProductRuleUpdateReqVO updateReqVO = new StoreProductRuleUpdateReqVO();\n            updateReqVO.setId(id);\n            updateReqVO.setRuleName(createReqVO.getRuleName());\n            updateReqVO.setRuleValue(createReqVO.getRuleValue());\n            storeProductRuleService.updateStoreProductRule(updateReqVO);\n            return success(1);\n        }else{\n           return success(storeProductRuleService.createStoreProductRule(createReqVO));\n        }\n    }\n\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除商品规则值(规格)\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('shop:store-product-rule:delete')\")\n    public CommonResult<Boolean> deleteStoreProductRule(@RequestParam(\"id\") Integer id) {\n        storeProductRuleService.deleteStoreProductRule(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得商品规则值(规格)\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('shop:store-product-rule:query')\")\n    public CommonResult<StoreProductRuleRespVO> getStoreProductRule(@RequestParam(\"id\") Integer id) {\n        StoreProductRuleDO storeProductRule = storeProductRuleService.getStoreProductRule(id);\n        return success(StoreProductRuleConvert.INSTANCE.convert(storeProductRule));\n    }\n\n    @GetMapping(\"/list\")\n    @Operation(summary = \"获得商品规则值(规格)列表\")\n    @Parameter(name = \"ids\", description = \"编号列表\", required = true, example = \"1024,2048\")\n    @PreAuthorize(\"@ss.hasPermission('shop:store-product-rule:query')\")\n    public CommonResult<List<StoreProductRuleRespVO>> getStoreProductRuleList(@RequestParam(\"ids\") Collection<Integer> ids) {\n        List<StoreProductRuleDO> list = storeProductRuleService.getStoreProductRuleList(ids);\n        return success(StoreProductRuleConvert.INSTANCE.convertList(list));\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得商品规则值(规格)分页\")\n    @PreAuthorize(\"@ss.hasPermission('shop:store-product-rule:query')\")\n    public CommonResult<PageResult<StoreProductRuleRespVO>> getStoreProductRulePage(@Valid StoreProductRulePageReqVO pageVO) {\n        PageResult<StoreProductRuleDO> pageResult = storeProductRuleService.getStoreProductRulePage(pageVO);\n        System.out.println(\"aa:\"+pageResult);\n        return success(StoreProductRuleConvert.INSTANCE.convertPage(pageResult));\n    }\n\n    @GetMapping(\"/export-excel\")\n    @Operation(summary = \"导出商品规则值(规格) Excel\")\n    @PreAuthorize(\"@ss.hasPermission('shop:store-product-rule:export')\")\n    public void exportStoreProductRuleExcel(@Valid StoreProductRuleExportReqVO exportReqVO,\n              HttpServletResponse response) throws IOException {\n        List<StoreProductRuleDO> list = storeProductRuleService.getStoreProductRuleList(exportReqVO);\n        // 导出 Excel\n        List<StoreProductRuleExcelVO> datas = StoreProductRuleConvert.INSTANCE.convertList02(list);\n        ExcelUtils.write(response, \"商品规则值(规格).xls\", \"数据\", StoreProductRuleExcelVO.class, datas);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/controller/admin/storeproductrule/vo/StoreProductRuleBaseVO.java",
    "content": "package co.yixiang.yshop.module.product.controller.admin.storeproductrule.vo;\n\n\nimport com.alibaba.fastjson.JSONArray;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\n\nimport jakarta.validation.constraints.*;\n\n\n/**\n* 商品规则值(规格) Base VO，提供给添加、修改、详细的子 VO 使用\n* 如果子 VO 存在差异的字段，请不要添加到这里，影响 Swagger 文档生成\n*/\n@Data\npublic class StoreProductRuleBaseVO {\n\n    @Schema(description = \"id\", required = false, example = \"有值表示是更新没值是添加\")\n    private Integer id;\n\n    @Schema(description = \"规格名称\", required = true, example = \"赵六\")\n    @NotNull(message = \"规格名称不能为空\")\n    private String ruleName;\n\n    @Schema(description = \"规格值\", required = true)\n    @NotNull(message = \"规格值不能为空\")\n    private JSONArray ruleValue;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/controller/admin/storeproductrule/vo/StoreProductRuleCreateReqVO.java",
    "content": "package co.yixiang.yshop.module.product.controller.admin.storeproductrule.vo;\n\nimport lombok.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\n\n@Schema(description = \"管理后台 - 商品规则值(规格)创建 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class StoreProductRuleCreateReqVO extends StoreProductRuleBaseVO {\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/controller/admin/storeproductrule/vo/StoreProductRuleExcelVO.java",
    "content": "package co.yixiang.yshop.module.product.controller.admin.storeproductrule.vo;\n\nimport com.alibaba.fastjson.JSONArray;\nimport lombok.*;\n\nimport java.time.LocalDateTime;\n\nimport com.alibaba.excel.annotation.ExcelProperty;\n\n/**\n * 商品规则值(规格) Excel VO\n *\n * @author yshop\n */\n@Data\npublic class StoreProductRuleExcelVO {\n\n    @ExcelProperty(\"id\")\n    private Integer id;\n\n    @ExcelProperty(\"规格名称\")\n    private String ruleName;\n\n    @ExcelProperty(\"规格值\")\n    private JSONArray ruleValue;\n\n    @ExcelProperty(\"创建时间\")\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/controller/admin/storeproductrule/vo/StoreProductRuleExportReqVO.java",
    "content": "package co.yixiang.yshop.module.product.controller.admin.storeproductrule.vo;\n\nimport lombok.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\n\n@Schema(description = \"管理后台 - 商品规则值(规格) Excel 导出 Request VO，参数和 StoreProductRulePageReqVO 是一致的\")\n@Data\npublic class StoreProductRuleExportReqVO {\n\n    @Schema(description = \"规格名称\", example = \"赵六\")\n    private String ruleName;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/controller/admin/storeproductrule/vo/StoreProductRulePageReqVO.java",
    "content": "package co.yixiang.yshop.module.product.controller.admin.storeproductrule.vo;\n\nimport lombok.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\n\n@Schema(description = \"管理后台 - 商品规则值(规格)分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class StoreProductRulePageReqVO extends PageParam {\n\n    @Schema(description = \"规格名称\", example = \"赵六\")\n    private String ruleName;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/controller/admin/storeproductrule/vo/StoreProductRuleRespVO.java",
    "content": "package co.yixiang.yshop.module.product.controller.admin.storeproductrule.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 商品规则值(规格) Response VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class StoreProductRuleRespVO extends StoreProductRuleBaseVO {\n\n    @Schema(description = \"id\", required = true, example = \"2067\")\n    private Integer id;\n\n    @Schema(description = \"创建时间\")\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/controller/admin/storeproductrule/vo/StoreProductRuleUpdateReqVO.java",
    "content": "package co.yixiang.yshop.module.product.controller.admin.storeproductrule.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\n\nimport jakarta.validation.constraints.*;\n\n@Schema(description = \"管理后台 - 商品规则值(规格)更新 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class StoreProductRuleUpdateReqVO extends StoreProductRuleBaseVO {\n\n    @Schema(description = \"id\", required = true, example = \"2067\")\n    @NotNull(message = \"id不能为空\")\n    private Integer id;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/controller/app/cart/vo/AppStoreCartQueryVo.java",
    "content": "package co.yixiang.yshop.module.product.controller.app.cart.vo;\n\nimport co.yixiang.yshop.module.product.controller.app.product.vo.AppStoreProductRespVo;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.io.Serializable;\nimport java.math.BigDecimal;\n\n/**\n * <p>\n * 购物车表 查询结果对象\n * </p>\n *\n * @author hupeng\n * @date 2023-6-13\n */\n@Data\n@Schema(description = \"用户 APP - 购物车数据vo\")\npublic class AppStoreCartQueryVo implements Serializable {\n    private static final long serialVersionUID = 1L;\n\n    @Schema(description = \"购物车表ID\", required = true)\n    private Long id;\n\n    @Schema(description = \"用户ID\", required = true)\n    private Long uid;\n\n    @Schema(description = \"类型\", required = true)\n    private String type;\n\n    @Schema(description = \"商品ID\", required = true)\n    private Long productId;\n\n    @Schema(description = \"商品属性\", required = true)\n    private String productAttrUnique;\n\n    @Schema(description = \"商品数量\", required = true)\n    private Integer cartNum;\n\n\n    @Schema(description = \"拼团id\", required = true)\n    private Long combinationId;\n\n    @Schema(description = \"秒杀产品ID\", required = true)\n    private Long seckillId;\n\n    @Schema(description = \"砍价id\", required = true)\n    private Long bargainId;\n\n    @Schema(description = \"商品信息\", required = true)\n    private AppStoreProductRespVo productInfo;\n\n    @Schema(description = \"成本价\", required = true)\n    private BigDecimal costPrice;\n\n    @Schema(description = \"真实价格\", required = true)\n    private BigDecimal truePrice;\n\n    @Schema(description = \"真实库存\", required = true)\n    private Integer trueStock;\n\n    @Schema(description = \"vip真实价格\", required = true)\n    private BigDecimal vipTruePrice;\n\n    @Schema(description = \"唯一id\", required = true)\n    private String unique;\n\n    @Schema(description = \"是否评价 大于0表示已经评价\", required = true)\n    private Long isReply;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/controller/app/category/AppCategoryController.java",
    "content": "package co.yixiang.yshop.module.product.controller.app.category;\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.module.product.controller.app.category.vo.AppCategoryRespVO;\nimport co.yixiang.yshop.module.product.convert.category.ProductCategoryConvert;\nimport co.yixiang.yshop.module.product.dal.dataobject.category.ProductCategoryDO;\nimport co.yixiang.yshop.module.product.service.category.ProductCategoryService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport jakarta.annotation.Resource;\nimport java.util.Comparator;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"用户 APP - 商品分类\")\n@RestController\n@RequestMapping(\"/product/category\")\n@Validated\npublic class AppCategoryController {\n\n    @Resource\n    private ProductCategoryService categoryService;\n\n//    @GetMapping(\"/list\")\n//    @Operation(summary = \"获得商品分类列表\")\n//    public CommonResult<List<AppCategoryRespVO>> getProductCategoryList() {\n//        List<ProductCategoryDO> list = categoryService.getEnableCategoryList();\n//        list.sort(Comparator.comparing(ProductCategoryDO::getSort));\n//        return success(ProductCategoryConvert.INSTANCE.convertList03(list));\n//    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/controller/app/category/vo/AppCategoryRespVO.java",
    "content": "package co.yixiang.yshop.module.product.controller.app.category.vo;\n\nimport co.yixiang.yshop.module.product.controller.app.product.vo.AppStoreProductRespVo;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotBlank;\nimport jakarta.validation.constraints.NotNull;\nimport java.util.List;\n\n@Data\n@Schema(description = \"用户 APP - 商品分类 Response VO\")\npublic class AppCategoryRespVO {\n\n    @Schema(description = \"分类编号\", required = true, example = \"2\")\n    private Long id;\n\n    @Schema(description = \"父分类编号\", required = true, example = \"1\")\n    @NotNull(message = \"父分类编号不能为空\")\n    private Long parentId;\n\n    @Schema(description = \"分类名称\", required = true, example = \"办公文具\")\n    @NotBlank(message = \"分类名称不能为空\")\n    private String name;\n\n    @Schema(description = \"分类图片\", required = true)\n    @NotBlank(message = \"分类图片不能为空\")\n    private String picUrl;\n\n    @Schema(description = \"商品列表\", required = true)\n    private List<AppStoreProductRespVo> goodsList;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/controller/app/product/AppStoreProductController.java",
    "content": "/**\n * Copyright (C) 2018-2022\n * All rights reserved, Designed By www.yixiang.co\n * 注意：\n * 本软件为www.yixiang.co开发研制，未经购买不得使用\n * 购买后可获得全部源代码（禁止转卖、分享、上传到码云、github等开源平台）\n * 一经发现盗用、分享等行为，将追究法律责任，后果自负\n */\npackage co.yixiang.yshop.module.product.controller.app.product;\n\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.module.product.controller.app.category.vo.AppCategoryRespVO;\nimport co.yixiang.yshop.module.product.controller.app.product.param.AppStoreProductQueryParam;\nimport co.yixiang.yshop.module.product.controller.app.product.vo.AppStoreProductRespVo;\nimport co.yixiang.yshop.module.product.service.storeproduct.AppStoreProductService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n/**\n * <p>\n * 商品控制器\n * </p>\n *\n * @author hupeng\n * @since 2023-8-16\n */\n@Slf4j\n@RestController\n@Tag(name = \"用户 APP - 商品\")\n@RequiredArgsConstructor(onConstructor = @__(@Autowired))\n@RequestMapping(\"/product\")\npublic class AppStoreProductController {\n\n    private final AppStoreProductService storeProductService;\n\n\n    /**\n     * 获取产品列表\n     */\n    @GetMapping(\"/products\")\n    @Operation(summary = \"商品列表\")\n    public CommonResult<List<AppCategoryRespVO>> goodsList(AppStoreProductQueryParam productQueryParam){\n        return success(storeProductService.getGoodsList(productQueryParam));\n    }\n\n    /**\n     * 获取产品详情\n     */\n    @GetMapping(\"/detail/{id}\")\n    @Operation(summary = \"获取产品详情\")\n    public CommonResult<AppStoreProductRespVo> goodsDetail(@PathVariable Long id){\n        return success(storeProductService.getStoreProductById(id));\n    }\n\n\n\n\n\n\n\n\n\n\n\n}\n\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/controller/app/product/param/AppStoreProductQueryParam.java",
    "content": "package co.yixiang.yshop.module.product.controller.app.product.param;\n\nimport co.yixiang.yshop.framework.common.params.QueryParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\n/**\n * <p>\n * 商品表 查询参数对象\n * </p>\n *\n * @author hupeng\n * @date 2023-6-12\n */\n@Data\n@EqualsAndHashCode(callSuper = true)\n@Schema(description = \"用户 APP - 商品表查询参数\")\npublic class AppStoreProductQueryParam extends QueryParam {\n    private static final long serialVersionUID = 1L;\n\n    @Schema(description = \"类别\", required = true)\n    private String type;\n\n    @Schema(description = \"分类ID\", required = true)\n    private String sid;\n\n    @Schema(description = \"是否新品,不为空的字符串即可\", required = true)\n    private String news;\n\n    @Schema(description = \"是否积分兑换商品\", required = true)\n    private Integer isIntegral;\n\n    @Schema(description = \"关键字\", required = true)\n    private String keyword;\n\n    @Schema(description = \"门店ID\", required = true)\n    private Integer shopId;\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/controller/app/product/vo/AppIndexVo.java",
    "content": "package co.yixiang.yshop.module.product.controller.app.product.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\nimport java.util.List;\n\n@Data\n@Builder\n@AllArgsConstructor\n@NoArgsConstructor\n@Schema(description = \"用户 APP - 首页数据\")\npublic class AppIndexVo {\n\n\n\n    @Schema(description = \"首发新品\", required = true)\n    private List<AppStoreProductRespVo> firstList;\n\n    @Schema(description = \"热门榜单\", required = true)\n    private List<AppStoreProductRespVo> likeInfo;\n\n    @Schema(description = \"地图key\", required = true)\n    private String mapKey;\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/controller/app/product/vo/AppProductVo.java",
    "content": "package co.yixiang.yshop.module.product.controller.app.product.vo;\n\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproductattrvalue.StoreProductAttrValueDO;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.util.ArrayList;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * <p>\n * 商品dto\n * </p>\n *\n * @author hupeng\n * @date 2023-6-12\n */\n@Data\n@Schema(description = \"用户 APP - 商品详情vo\")\npublic class AppProductVo {\n\n    @Schema(description = \"商品信息列表\", required = true)\n    private List<AppStoreProductRespVo> goodList = new ArrayList();\n\n    @Schema(description = \"价格\", required = true)\n    private String priceName = \"\";\n\n    @Schema(description = \"产品规格\", required = true)\n    private List<AppStoreProductAttrQueryVo> productAttr = new ArrayList();\n\n    @Schema(description = \"属性集合\", required = true)\n    private Map<String, StoreProductAttrValueDO>  productValue = new LinkedHashMap<>();\n\n    @Schema(description = \"评论信息\", required = true)\n    private AppStoreProductReplyQueryVo reply;\n\n    @Schema(description = \"回复渠道\", required = true)\n    private String replyChance;\n\n    @Schema(description = \"回复数\", required = true)\n    private Long replyCount;\n\n\n    @Schema(description = \"商品信息\", required = true)\n    private AppStoreProductRespVo storeInfo;\n\n    @Schema(description = \"腾讯地图key\", required = true)\n    private String mapKey;\n\n\n    @Schema(description = \"用户ID\", required = true)\n    private Integer uid = 0;\n\n    @Schema(description = \"模版名称\", required = true)\n    private String tempName;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/controller/app/product/vo/AppReplyCountVo.java",
    "content": "package co.yixiang.yshop.module.product.controller.app.product.vo;\r\n\r\nimport io.swagger.v3.oas.annotations.media.Schema;\r\nimport lombok.*;\r\n\r\nimport java.io.Serializable;\r\n\r\n/**\r\n * @ClassName ReplyCount\r\n * @Author hupeng <610796224@qq.com>\r\n * @Date 2023/7/9\r\n **/\r\n@Getter\r\n@Setter\r\n@AllArgsConstructor\r\n@NoArgsConstructor\r\n@Builder\r\npublic class AppReplyCountVo implements Serializable {\r\n\r\n    @Schema(description = \"总的评论数\", required = true)\r\n    private Long sumCount;\r\n\r\n    @Schema(description = \"好评数\", required = true)\r\n    private Long goodCount;\r\n\r\n    @Schema(description = \"中评数\", required = true)\r\n    private Long inCount;\r\n\r\n    @Schema(description = \"差评数\", required = true)\r\n    private Long poorCount;\r\n\r\n    @Schema(description = \"好评率\", required = true)\r\n    private String replyChance;\r\n\r\n    @Schema(description = \"好评星星数\", required = true)\r\n    private String replySstar;\r\n\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/controller/app/product/vo/AppStoreProductAttrQueryVo.java",
    "content": "package co.yixiang.yshop.module.product.controller.app.product.vo;\n\nimport co.yixiang.yshop.module.product.service.storeproduct.dto.AttrValueDto;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.io.Serializable;\nimport java.util.List;\n\n/**\n * <p>\n * 商品属性表 查询结果对象\n * </p>\n *\n * @author hupeng\n * @date 2023-6-12\n */\n@Data\n@Schema(description = \"用户 APP - 商品属性表vo\")\npublic class AppStoreProductAttrQueryVo implements Serializable {\n    private static final long serialVersionUID = 1L;\n\n    @Schema(description = \"ID\", required = true)\n    private Long id;\n\n    @Schema(description = \"商品ID\", required = true)\n    private Long productId;\n\n    @Schema(description = \"属性名\", required = true)\n    private String attrName;\n\n    @Schema(description = \"属性值\", required = true)\n    private String attrValues;\n\n    @Schema(description = \"属性值集合\", required = true)\n    private List<AttrValueDto> attrValue;\n\n    @Schema(description = \"属性列表\", required = true)\n    private List<String> attrValueArr;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/controller/app/product/vo/AppStoreProductReplyQueryVo.java",
    "content": "package co.yixiang.yshop.module.product.controller.app.product.vo;\n\nimport com.fasterxml.jackson.annotation.JsonFormat;\nimport com.fasterxml.jackson.annotation.JsonIgnore;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.io.Serializable;\nimport java.util.Date;\n\n/**\n * <p>\n * 评论表 查询结果对象\n * </p>\n *\n * @author hupeng\n * @date 2023-6-12\n */\n@Data\n@Schema(description = \"用户 APP - 评论表vo\")\npublic class AppStoreProductReplyQueryVo implements Serializable {\n    private static final long serialVersionUID = 1L;\n\n    @Schema(description = \"评论ID\", required = true)\n    private Long id;\n\n    @Schema(description = \"产品id\", required = true)\n    private Long productId;\n\n    @Schema(description = \"某种商品类型(普通商品、秒杀商品）\", required = true)\n    private String replyType;\n\n    @Schema(description = \"商品分数\", required = true)\n    private Integer productScore;\n\n    @Schema(description = \"服务分数\", required = true)\n    private Integer serviceScore;\n\n    @Schema(description = \"评论内容\", required = true)\n    private String comment;\n\n    @Schema(description = \"评论图片\", required = true)\n    private String[] pics;\n\n    @Schema(description = \"评论图片,字符串\", required = true)\n    private String pictures;\n\n    @Schema(description = \"管理员回复内容\", required = true)\n    private String merchantReplyContent;\n\n    @Schema(description = \"管理员回复时间\", required = true)\n    private Date merchantReplyTime;\n\n    @JsonFormat(pattern = \"yyyy-MM-dd HH:mm:ss\",timezone=\"GMT+8\")\n    @Schema(description = \"发布时间\", required = true)\n    private Date createTime;\n\n    @Schema(description = \"评价星星数\", required = true)\n    private String star;\n\n    @Schema(description = \"用户昵称\", required = true)\n    private String nickname;\n\n    @Schema(description = \"用户头像\", required = true)\n    private String avatar;\n\n    @Schema(description = \"商品sku\", required = true)\n    private String sku;\n\n    @JsonIgnore\n    private String cartInfo;\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/controller/app/product/vo/AppStoreProductRespVo.java",
    "content": "package co.yixiang.yshop.module.product.controller.app.product.vo;\n\n\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproductattrvalue.StoreProductAttrValueDO;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.io.Serializable;\nimport java.math.BigDecimal;\nimport java.util.ArrayList;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * <p>\n * 商品表 查询结果对象\n * </p>\n *\n * @author hupeng\n * @date 2023-6-12\n */\n@Data\n@Schema(description = \"商品 APP - 获取商品列表查询结果数\")\npublic class AppStoreProductRespVo implements Serializable {\n    private static final long serialVersionUID = 1L;\n\n    @Schema(description = \"id\", required = true)\n    private Long id;\n\n    @Schema(description = \"分类id\", required = true)\n    private String cateId;\n\n    @Schema(description = \"商品图片\", required = true)\n    private String image;\n\n\n    @Schema(description = \"是否收藏\", required = true)\n    private Boolean userCollect = false;\n\n    @Schema(description = \"是否喜欢\", required = true)\n    private Boolean userLike = false;\n\n    @Schema(description = \"轮播图，多个用,分割\", required = true)\n    private String sliderImage;\n\n    @Schema(description = \"商品属性信息\", required = true)\n    private StoreProductAttrValueDO attrInfo;\n\n    @Schema(description = \"商品名称\", required = true)\n    private String storeName;\n\n    @Schema(description = \"商品简介\", required = true)\n    private String storeInfo;\n\n    @Schema(description = \"关键字\", required = true)\n    private String keyword;\n\n    @Schema(description = \"商品价格\", required = true)\n    private BigDecimal price;\n\n    @Schema(description = \"会员价格\", required = true)\n    private BigDecimal vipPrice;\n\n    @Schema(description = \"市场价\", required = true)\n    private BigDecimal otPrice;\n\n    @Schema(description = \"邮费\", required = true)\n    private BigDecimal postage;\n\n    @Schema(description = \"单位名\", required = true)\n    private String unitName;\n\n    @Schema(description = \"销量\", required = true)\n    private Integer sales;\n\n    @Schema(description = \"库存\", required = true)\n    private Integer stock;\n\n    @Schema(description = \"产品描述\", required = true)\n    private String description;\n\n    @Schema(description = \"获得积分\", required = true)\n    private BigDecimal giveIntegral;\n\n    @Schema(description = \"需要多少积分\", required = true)\n    private Integer integral;\n\n    @Schema(description = \"状态（0：未上架，1：上架）\", required = true)\n    private Integer isShow;\n\n    @Schema(description = \"运费模板id\", required = true)\n    private Integer tempId;\n\n    @Schema(description = \"产品规格\", required = true)\n    private List<AppStoreProductAttrQueryVo> productAttr = new ArrayList();\n\n    @Schema(description = \"属性集合\", required = true)\n    private Map<String, StoreProductAttrValueDO> productValue = new LinkedHashMap<>();\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/convert/category/ProductCategoryConvert.java",
    "content": "package co.yixiang.yshop.module.product.convert.category;\n\nimport co.yixiang.yshop.module.product.controller.admin.category.vo.ProductCategoryCreateReqVO;\nimport co.yixiang.yshop.module.product.controller.admin.category.vo.ProductCategoryRespVO;\nimport co.yixiang.yshop.module.product.controller.admin.category.vo.ProductCategoryUpdateReqVO;\nimport co.yixiang.yshop.module.product.controller.app.category.vo.AppCategoryRespVO;\nimport co.yixiang.yshop.module.product.dal.dataobject.category.ProductCategoryDO;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.factory.Mappers;\n\nimport java.util.List;\n\n/**\n * 商品分类 Convert\n *\n * @author yshop\n */\n@Mapper\npublic interface ProductCategoryConvert {\n\n    ProductCategoryConvert INSTANCE = Mappers.getMapper(ProductCategoryConvert.class);\n\n    ProductCategoryDO convert(ProductCategoryCreateReqVO bean);\n\n    ProductCategoryDO convert(ProductCategoryUpdateReqVO bean);\n\n    ProductCategoryRespVO convert(ProductCategoryDO bean);\n\n    List<ProductCategoryRespVO> convertList(List<ProductCategoryDO> list);\n\n    List<AppCategoryRespVO> convertList03(List<ProductCategoryDO> list);\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/convert/storeproduct/StoreProductConvert.java",
    "content": "package co.yixiang.yshop.module.product.convert.storeproduct;\n\nimport java.util.*;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\nimport co.yixiang.yshop.module.product.controller.app.product.vo.AppStoreProductRespVo;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.factory.Mappers;\nimport co.yixiang.yshop.module.product.controller.admin.storeproduct.vo.*;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproduct.StoreProductDO;\n\n/**\n * 商品 Convert\n *\n * @author yshop\n */\n@Mapper\npublic interface StoreProductConvert {\n\n    StoreProductConvert INSTANCE = Mappers.getMapper(StoreProductConvert.class);\n\n    StoreProductDO convert(StoreProductCreateReqVO bean);\n\n    StoreProductDO convert(StoreProductUpdateReqVO bean);\n\n    StoreProductRespVO convert(StoreProductDO bean);\n\n    AppStoreProductRespVo convert01(StoreProductDO bean);\n\n    List<StoreProductRespVO> convertList(List<StoreProductDO> list);\n\n    PageResult<StoreProductRespVO> convertPage(PageResult<StoreProductDO> page);\n\n    List<StoreProductExcelVO> convertList02(List<StoreProductDO> list);\n\n    List<AppStoreProductRespVo> convertList03(List<StoreProductDO> list);\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/convert/storeproductattr/StoreProductAttrConvert.java",
    "content": "package co.yixiang.yshop.module.product.convert.storeproductattr;\n\nimport co.yixiang.yshop.module.product.controller.admin.storeproduct.vo.StoreProductRespVO;\nimport co.yixiang.yshop.module.product.controller.app.product.vo.AppStoreProductAttrQueryVo;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproduct.StoreProductDO;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproductattr.StoreProductAttrDO;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.factory.Mappers;\n\n/**\n * 商品属性 Convert\n *\n * @author yshop\n */\n@Mapper\npublic interface StoreProductAttrConvert {\n\n    StoreProductAttrConvert INSTANCE = Mappers.getMapper(StoreProductAttrConvert.class);\n\n    AppStoreProductAttrQueryVo convert(StoreProductAttrDO bean);\n    \n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/convert/storeproductattrresult/StoreProductAttrResultConvert.java",
    "content": "package co.yixiang.yshop.module.product.convert.storeproductattrresult;\n\nimport org.mapstruct.Mapper;\nimport org.mapstruct.factory.Mappers;\n\n/**\n * 商品属性详情 Convert\n *\n * @author yshop\n */\n@Mapper\npublic interface StoreProductAttrResultConvert {\n\n    StoreProductAttrResultConvert INSTANCE = Mappers.getMapper(StoreProductAttrResultConvert.class);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/convert/storeproductattrvalue/StoreProductAttrValueConvert.java",
    "content": "package co.yixiang.yshop.module.product.convert.storeproductattrvalue;\n\nimport org.mapstruct.Mapper;\nimport org.mapstruct.factory.Mappers;\n\n/**\n * 商品属性值 Convert\n *\n * @author yshop\n */\n@Mapper\npublic interface StoreProductAttrValueConvert {\n\n    StoreProductAttrValueConvert INSTANCE = Mappers.getMapper(StoreProductAttrValueConvert.class);\n    \n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/convert/storeproductreply/StoreProductReplyConvert.java",
    "content": "package co.yixiang.yshop.module.product.convert.storeproductreply;\n\nimport java.util.*;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\nimport org.mapstruct.Mapper;\nimport org.mapstruct.factory.Mappers;\nimport co.yixiang.yshop.module.product.controller.admin.storeproductreply.vo.*;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproductreply.StoreProductReplyDO;\n\n/**\n * 评论 Convert\n *\n * @author yshop\n */\n@Mapper\npublic interface StoreProductReplyConvert {\n\n    StoreProductReplyConvert INSTANCE = Mappers.getMapper(StoreProductReplyConvert.class);\n\n\n    StoreProductReplyDO convert(StoreProductReplyUpdateReqVO bean);\n\n    StoreProductReplyRespVO convert(StoreProductReplyDO bean);\n\n    List<StoreProductReplyRespVO> convertList(List<StoreProductReplyDO> list);\n\n    PageResult<StoreProductReplyRespVO> convertPage(PageResult<StoreProductReplyDO> page);\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/convert/storeproductrule/StoreProductRuleConvert.java",
    "content": "package co.yixiang.yshop.module.product.convert.storeproductrule;\n\nimport java.util.*;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\nimport org.mapstruct.Mapper;\nimport org.mapstruct.factory.Mappers;\nimport co.yixiang.yshop.module.product.controller.admin.storeproductrule.vo.*;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproductrule.StoreProductRuleDO;\n\n/**\n * 商品规则值(规格) Convert\n *\n * @author yshop\n */\n@Mapper\npublic interface StoreProductRuleConvert {\n\n    StoreProductRuleConvert INSTANCE = Mappers.getMapper(StoreProductRuleConvert.class);\n\n    StoreProductRuleDO convert(StoreProductRuleCreateReqVO bean);\n\n    StoreProductRuleDO convert(StoreProductRuleUpdateReqVO bean);\n\n    StoreProductRuleRespVO convert(StoreProductRuleDO bean);\n\n    List<StoreProductRuleRespVO> convertList(List<StoreProductRuleDO> list);\n\n    PageResult<StoreProductRuleRespVO> convertPage(PageResult<StoreProductRuleDO> page);\n\n    List<StoreProductRuleExcelVO> convertList02(List<StoreProductRuleDO> list);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/dal/dataobject/category/ProductCategoryDO.java",
    "content": "package co.yixiang.yshop.module.product.dal.dataobject.category;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.*;\n\n/**\n * 商品分类 DO\n *\n * 商品分类一共两类：\n * 1）一级分类：{@link #parentId} 等于 0\n * 2）二级 + 三级分类：{@link #parentId} 不等于 0\n *\n * @author yshop\n */\n@TableName(\"yshop_store_product_category\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class ProductCategoryDO extends BaseDO {\n\n    /**\n     * 父分类编号 - 根分类\n     */\n    public static final Long PARENT_ID_NULL = 0L;\n\n    /**\n     * 分类编号\n     */\n    @TableId\n    private Long id;\n\n    /**\n     * 店铺id\n     */\n    private Integer shopId;\n\n    /**\n     * 店铺名称\n     */\n    private String shopName;\n    /**\n     * 父分类编号\n     */\n    private Long parentId;\n    /**\n     * 分类名称\n     */\n    private String name;\n    /**\n     * 分类图片\n     *\n     * 一级分类：推荐 200 x 100 分辨率\n     * 二级 + 三级分类：推荐 100 x 100 分辨率\n     */\n    private String picUrl;\n    /**\n     * 分类排序\n     */\n    private Integer sort;\n    /**\n     * 分类描述\n     */\n    private String description;\n    /**\n     * 开启状态\n     *\n     * 枚举 {@link CommonStatusEnum}\n     */\n    private Integer status;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/dal/dataobject/storeproduct/StoreProductDO.java",
    "content": "package co.yixiang.yshop.module.product.dal.dataobject.storeproduct;\n\nimport lombok.*;\nimport java.util.*;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport com.baomidou.mybatisplus.annotation.*;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\n\n/**\n * 商品 DO\n *\n * @author yshop\n */\n@TableName(\"yshop_store_product\")\n@KeySequence(\"yshop_store_product_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class StoreProductDO extends BaseDO {\n\n    /**\n     * 商品id\n     */\n    @TableId\n    private Long id;\n\n    /**\n     * 店铺id\n     */\n    private Integer shopId;\n\n    /**\n     * 店铺名称\n     */\n    private String shopName;\n    /**\n     * 商品图片\n     */\n    private String image;\n    /**\n     * 轮播图\n     */\n    private String sliderImage;\n    /**\n     * 商品名称\n     */\n    private String storeName;\n    /**\n     * 商品简介\n     */\n    private String storeInfo;\n    /**\n     * 关键字\n     */\n    private String keyword;\n    /**\n     * 产品条码（一维码）\n     */\n    private String barCode;\n    /**\n     * 分类id\n     */\n    private String cateId;\n\n    //品牌id\n    private Long brandId;\n    /**\n     * 商品价格\n     */\n    private BigDecimal price;\n    /**\n     * 会员价格\n     */\n    private BigDecimal vipPrice;\n    /**\n     * 市场价\n     */\n    private BigDecimal otPrice;\n    /**\n     * 邮费\n     */\n    private BigDecimal postage;\n    /**\n     * 单位名\n     */\n    private String unitName;\n    /**\n     * 排序\n     */\n    private Short sort;\n    /**\n     * 销量\n     */\n    private Integer sales;\n    /**\n     * 库存\n     */\n    private Integer stock;\n    /**\n     * 状态（0：未上架，1：上架）\n     */\n    private Integer isShow;\n    /**\n     * 是否热卖\n     */\n    private Boolean isHot;\n    /**\n     * 是否优惠\n     */\n    private Boolean isBenefit;\n    /**\n     * 是否精品\n     */\n    private Boolean isBest;\n    /**\n     * 是否新品\n     */\n    private Integer isNew;\n    /**\n     * 产品描述\n     */\n    private String description;\n    /**\n     * 是否包邮\n     */\n    private Integer isPostage;\n    /**\n     * 商户是否代理 0不可代理1可代理\n     */\n    private Byte merUse;\n    /**\n     * 获得积分\n     */\n    private BigDecimal giveIntegral;\n    /**\n     * 成本价\n     */\n    private BigDecimal cost;\n    /**\n     * 秒杀状态 0 未开启 1已开启\n     */\n    private Byte isSeckill;\n    /**\n     * 砍价状态 0未开启 1开启\n     */\n    private Byte isBargain;\n    /**\n     * 是否优品推荐\n     */\n    private Boolean isGood;\n    /**\n     * 虚拟销量\n     */\n    private Integer ficti;\n    /**\n     * 浏览量\n     */\n    private Integer browse;\n    /**\n     * 产品二维码地址(用户小程序海报)\n     */\n    private String codePath;\n    /**\n     * 是否单独分佣\n     */\n    private Boolean isSub;\n    /**\n     * 运费模板ID\n     */\n    private Integer tempId;\n    /**\n     * 规格 0单 1多\n     */\n    private Integer specType;\n    /**\n     * 是开启积分兑换\n     */\n    private Byte isIntegral;\n    /**\n     * 需要多少积分兑换 只在开启积分兑换时生效\n     */\n    private Integer integral;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/dal/dataobject/storeproductattr/StoreProductAttrDO.java",
    "content": "package co.yixiang.yshop.module.product.dal.dataobject.storeproductattr;\n\nimport lombok.*;\nimport java.util.*;\nimport com.baomidou.mybatisplus.annotation.*;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\n\n/**\n * 商品属性 DO\n *\n * @author yshop\n */\n@TableName(\"yshop_store_product_attr\")\n@KeySequence(\"yshop_store_product_attr_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class StoreProductAttrDO{\n\n    /**\n     * id\n     */\n    @TableId\n    private Long id;\n    /**\n     * 商品ID\n     */\n    private Long productId;\n    /**\n     * 属性名\n     */\n    private String attrName;\n    /**\n     * 属性值\n     */\n    private String attrValues;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/dal/dataobject/storeproductattrresult/StoreProductAttrResultDO.java",
    "content": "package co.yixiang.yshop.module.product.dal.dataobject.storeproductattrresult;\n\nimport lombok.*;\nimport java.util.*;\nimport java.time.LocalDateTime;\nimport com.baomidou.mybatisplus.annotation.*;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\n\n/**\n * 商品属性详情 DO\n *\n * @author yshop\n */\n@TableName(\"yshop_store_product_attr_result\")\n@KeySequence(\"yshop_store_product_attr_result_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class StoreProductAttrResultDO  {\n\n    /**\n     * id\n     */\n    @TableId\n    private Long id;\n    /**\n     * 商品ID\n     */\n    private Long productId;\n    /**\n     * 商品属性参数\n     */\n    private String result;\n    /**\n     * 上次修改时间\n     */\n    private Date changeTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/dal/dataobject/storeproductattrvalue/StoreProductAttrValueDO.java",
    "content": "package co.yixiang.yshop.module.product.dal.dataobject.storeproductattrvalue;\n\nimport lombok.*;\nimport java.util.*;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport com.baomidou.mybatisplus.annotation.*;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\n\n/**\n * 商品属性值 DO\n *\n * @author yshop\n */\n@TableName(\"yshop_store_product_attr_value\")\n@KeySequence(\"yshop_store_product_attr_value_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class StoreProductAttrValueDO {\n\n    /**\n     * id\n     */\n    @TableId\n    private Long id;\n    /**\n     * 商品ID\n     */\n    private Long productId;\n    /**\n     * 商品属性索引值 (attr_value|attr_value[|....])\n     */\n    private String sku;\n    /**\n     * 属性对应的库存\n     */\n    private Integer stock;\n    /**\n     * 销量\n     */\n    private Integer sales;\n    /**\n     * 属性金额\n     */\n    private BigDecimal price;\n    /**\n     * 图片\n     */\n    private String image;\n    /**\n     * 唯一值\n     */\n    @TableField(value = \"`unique`\")\n    private String unique;\n    /**\n     * 成本价\n     */\n    private BigDecimal cost;\n    /**\n     * 商品条码\n     */\n    private String barCode;\n    /**\n     * 原价\n     */\n    private BigDecimal otPrice;\n    /**\n     * 重量\n     */\n    private BigDecimal weight;\n    /**\n     * 体积\n     */\n    private BigDecimal volume;\n    /**\n     * 一级返佣\n     */\n    private BigDecimal brokerage;\n    /**\n     * 二级返佣\n     */\n    private BigDecimal brokerageTwo;\n    /**\n     * 拼团价\n     */\n    private BigDecimal pinkPrice;\n    /**\n     * 拼团库存\n     */\n    private Integer pinkStock;\n    /**\n     * 秒杀价\n     */\n    private BigDecimal seckillPrice;\n    /**\n     * 秒杀库存\n     */\n    private Integer seckillStock;\n    /**\n     * 需要多少积分兑换\n     */\n    private Integer integral;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/dal/dataobject/storeproductreply/StoreProductReplyDO.java",
    "content": "package co.yixiang.yshop.module.product.dal.dataobject.storeproductreply;\n\nimport lombok.*;\nimport java.util.*;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\nimport com.baomidou.mybatisplus.annotation.*;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\n\n/**\n * 评论 DO\n *\n * @author yshop\n */\n@TableName(\"yshop_store_product_reply\")\n@KeySequence(\"yshop_store_product_reply_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class StoreProductReplyDO extends BaseDO {\n\n    /**\n     * 评论ID\n     */\n    @TableId\n    private Long id;\n    /**\n     * 用户ID\n     */\n    private Long uid;\n    /**\n     * 订单ID\n     */\n    private Long oid;\n    /**\n     * 唯一id\n     */\n    @TableField(value = \"`unique`\")\n    private String unique;\n    /**\n     * 产品id\n     */\n    private Long productId;\n    /**\n     * 某种商品类型(普通商品、秒杀商品）\n     */\n    private String replyType;\n    /**\n     * 商品分数\n     */\n    private Integer productScore;\n    /**\n     * 服务分数\n     */\n    private Integer serviceScore;\n    /**\n     * 评论内容\n     */\n    private String comment;\n    /**\n     * 评论图片\n     */\n    private String pics;\n    /**\n     * 管理员回复内容\n     */\n    private String merchantReplyContent;\n    /**\n     * 管理员回复时间\n     */\n    private LocalDateTime merchantReplyTime;\n    /**\n     * 0未回复1已回复\n     */\n    private Integer isReply;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/dal/dataobject/storeproductrule/StoreProductRuleDO.java",
    "content": "package co.yixiang.yshop.module.product.dal.dataobject.storeproductrule;\n\nimport com.alibaba.fastjson.JSONArray;\nimport com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler;\nimport lombok.*;\nimport com.baomidou.mybatisplus.annotation.*;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\n\n/**\n * 商品规则值(规格) DO\n *\n * @author yshop\n */\n@TableName(value = \"yshop_store_product_rule\",autoResultMap = true)\n@KeySequence(\"yshop_store_product_rule_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class StoreProductRuleDO extends BaseDO {\n\n    /**\n     * id\n     */\n    @TableId\n    private Integer id;\n    /**\n     * 规格名称\n     */\n    private String ruleName;\n    /**\n     * 规格值\n     */\n    @TableField(typeHandler = FastjsonTypeHandler.class)\n    private JSONArray ruleValue;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/dal/mysql/category/ProductCategoryMapper.java",
    "content": "package co.yixiang.yshop.module.product.dal.mysql.category;\n\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils;\nimport co.yixiang.yshop.module.product.controller.admin.category.vo.ProductCategoryListReqVO;\nimport co.yixiang.yshop.module.product.dal.dataobject.category.ProductCategoryDO;\nimport org.apache.ibatis.annotations.Mapper;\n\nimport java.util.List;\n\n/**\n * 商品分类 Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface ProductCategoryMapper extends BaseMapperX<ProductCategoryDO> {\n\n    default List<ProductCategoryDO> selectList(ProductCategoryListReqVO listReqVO) {\n        Long shopId = SecurityFrameworkUtils.getLoginUser().getShopId();\n        if(shopId == 0) {\n            listReqVO.setShopId(null);\n        }else {\n            listReqVO.setShopId(shopId.intValue());\n        }\n        return selectList(new LambdaQueryWrapperX<ProductCategoryDO>()\n                .likeIfPresent(ProductCategoryDO::getName, listReqVO.getName())\n                .likeIfPresent(ProductCategoryDO::getShopName, listReqVO.getShopName())\n                .eqIfPresent(ProductCategoryDO::getShopId,listReqVO.getShopId())\n                .orderByDesc(ProductCategoryDO::getId));\n    }\n\n    default Long selectCountByParentId(Long parentId) {\n        return selectCount(ProductCategoryDO::getParentId, parentId);\n    }\n\n    default List<ProductCategoryDO> selectListByStatus(Integer status,Integer shopId) {\n        return selectList(new LambdaQueryWrapperX<ProductCategoryDO>()\n                .eqIfPresent(ProductCategoryDO::getStatus, status)\n                .eqIfPresent(ProductCategoryDO::getShopId, shopId)\n                .orderByDesc(ProductCategoryDO::getId));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/dal/mysql/storeproduct/StoreProductMapper.java",
    "content": "package co.yixiang.yshop.module.product.dal.mysql.storeproduct;\n\nimport java.util.*;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.convert.Convert;\nimport co.yixiang.yshop.framework.common.enums.ShopCommonEnum;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproduct.StoreProductDO;\nimport co.yixiang.yshop.module.product.enums.product.DefaultEnum;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport org.apache.ibatis.annotations.Mapper;\nimport co.yixiang.yshop.module.product.controller.admin.storeproduct.vo.*;\nimport org.apache.ibatis.annotations.Param;\nimport org.apache.ibatis.annotations.Update;\n\n/**\n * 商品 Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface StoreProductMapper extends BaseMapperX<StoreProductDO> {\n\n    default PageResult<StoreProductDO> selectPage(StoreProductPageReqVO reqVO) {\n        LambdaQueryWrapperX<StoreProductDO> wrapper = new LambdaQueryWrapperX<>();\n\n        Long shopId = SecurityFrameworkUtils.getLoginUser().getShopId();\n        if(shopId > 0) {\n            wrapper.eq(StoreProductDO::getShopId,shopId);\n        }\n\n        wrapper.likeIfPresent(StoreProductDO::getStoreName, reqVO.getStoreName())\n                .likeIfPresent(StoreProductDO::getShopName, reqVO.getShopName())\n                .eqIfPresent(StoreProductDO::getIsPostage, reqVO.getIsPostage())\n                .eqIfPresent(StoreProductDO::getCateId,reqVO.getCateId())\n                .orderByDesc(StoreProductDO::getId);\n\n        wrapper.eq(StoreProductDO::getIsShow,Convert.toInt(reqVO.getIsShow()));\n        if(DefaultEnum.DEFAULT_0.getValue().equals(Convert.toInt(reqVO.getStock()))){\n            wrapper.eq(StoreProductDO::getStock,DefaultEnum.DEFAULT_0.getValue());\n        }\n//\n//        if(CollUtil.isNotEmpty(reqVO.getCatIds())){\n//            wrapper.in(StoreProductDO::getCateId,reqVO.getCatIds());\n//        }\n\n        return selectPage(reqVO, wrapper);\n\n    }\n\n    default List<StoreProductDO> selectList(StoreProductExportReqVO reqVO) {\n        return selectList(new LambdaQueryWrapperX<StoreProductDO>()\n                .likeIfPresent(StoreProductDO::getStoreName, reqVO.getStoreName())\n                .eqIfPresent(StoreProductDO::getIsPostage, reqVO.getIsPostage())\n                .orderByDesc(StoreProductDO::getId));\n    }\n\n    @Update(\"update yshop_store_product set is_show = #{status} where id = #{id}\")\n    void updateOnsale(@Param(\"status\") Integer status, @Param(\"id\") Long id);\n\n\n    /**\n     * 正常商品库存 加库存 减销量\n     * @param num\n     * @param productId\n     * @return\n     */\n    @Update(\"update yshop_store_product set stock=stock+#{num}, sales=sales-#{num}\" +\n            \" where id=#{productId}\")\n    int incStockDecSales(@Param(\"num\") Integer num,@Param(\"productId\") Long productId);\n\n    /**\n     * 正常商品库存 减库存 加销量\n     * @param num\n     * @param productId\n     * @return\n     */\n    @Update(\"update yshop_store_product set stock=stock-#{num}, sales=sales+#{num}\" +\n            \" where id=#{productId} and stock >= #{num}\")\n    int decStockIncSales(@Param(\"num\") Integer num,@Param(\"productId\") Long productId);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/dal/mysql/storeproductattr/StoreProductAttrMapper.java",
    "content": "package co.yixiang.yshop.module.product.dal.mysql.storeproductattr;\n\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproductattr.StoreProductAttrDO;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * 商品属性 Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface StoreProductAttrMapper extends BaseMapperX<StoreProductAttrDO> {\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/dal/mysql/storeproductattrresult/StoreProductAttrResultMapper.java",
    "content": "package co.yixiang.yshop.module.product.dal.mysql.storeproductattrresult;\n\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproductattrresult.StoreProductAttrResultDO;\nimport org.apache.ibatis.annotations.Mapper;\n\n/**\n * 商品属性详情 Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface StoreProductAttrResultMapper extends BaseMapperX<StoreProductAttrResultDO> {\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/dal/mysql/storeproductattrvalue/StoreProductAttrValueMapper.java",
    "content": "package co.yixiang.yshop.module.product.dal.mysql.storeproductattrvalue;\n\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproductattrvalue.StoreProductAttrValueDO;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.apache.ibatis.annotations.Param;\nimport org.apache.ibatis.annotations.Update;\n\n/**\n * 商品属性值 Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface StoreProductAttrValueMapper extends BaseMapperX<StoreProductAttrValueDO> {\n\n    /**\n     * 普通商品 减库存 加销量\n     * @param num\n     * @param productId\n     * @param unique\n     * @return\n     */\n    @Update(\"update yshop_store_product_attr_value set stock=stock-#{num}, sales=sales+#{num}\" +\n            \" where product_id=#{productId} and `sku`=#{unique} and stock >= #{num}\")\n    int decStockIncSales(@Param(\"num\") Integer num, @Param(\"productId\") Long productId,\n                         @Param(\"unique\")  String unique);\n\n    /**\n     * 正常商品 加库存 减销量\n     * @param num\n     * @param productId\n     * @param unique\n     * @return\n     */\n    @Update(\"update yshop_store_product_attr_value set stock=stock+#{num}, sales=sales-#{num}\" +\n            \" where product_id=#{productId} and `sku`=#{unique}\")\n    int incStockDecSales(@Param(\"num\") Integer num,@Param(\"productId\") Long productId,\n                         @Param(\"unique\")  String unique);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/dal/mysql/storeproductreply/StoreProductReplyMapper.java",
    "content": "package co.yixiang.yshop.module.product.dal.mysql.storeproductreply;\n\nimport java.util.*;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.module.product.controller.app.product.vo.AppStoreProductReplyQueryVo;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproductreply.StoreProductReplyDO;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport org.apache.ibatis.annotations.Mapper;\nimport co.yixiang.yshop.module.product.controller.admin.storeproductreply.vo.*;\nimport org.apache.ibatis.annotations.Param;\nimport org.apache.ibatis.annotations.Select;\n\n/**\n * 评论 Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface StoreProductReplyMapper extends BaseMapperX<StoreProductReplyDO> {\n\n    default PageResult<StoreProductReplyDO> selectPage(StoreProductReplyPageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<StoreProductReplyDO>()\n                .eqIfPresent(StoreProductReplyDO::getUid, reqVO.getUid())\n                .eqIfPresent(StoreProductReplyDO::getOid, reqVO.getOid())\n                .eqIfPresent(StoreProductReplyDO::getUnique, reqVO.getUnique())\n                .eqIfPresent(StoreProductReplyDO::getProductId, reqVO.getProductId())\n                .eqIfPresent(StoreProductReplyDO::getReplyType, reqVO.getReplyType())\n                .eqIfPresent(StoreProductReplyDO::getProductScore, reqVO.getProductScore())\n                .eqIfPresent(StoreProductReplyDO::getServiceScore, reqVO.getServiceScore())\n                .eqIfPresent(StoreProductReplyDO::getComment, reqVO.getComment())\n                .eqIfPresent(StoreProductReplyDO::getPics, reqVO.getPics())\n                .betweenIfPresent(StoreProductReplyDO::getCreateTime, reqVO.getCreateTime())\n                .eqIfPresent(StoreProductReplyDO::getMerchantReplyContent, reqVO.getMerchantReplyContent())\n                .betweenIfPresent(StoreProductReplyDO::getMerchantReplyTime, reqVO.getMerchantReplyTime())\n                .eqIfPresent(StoreProductReplyDO::getIsReply, reqVO.getIsReply())\n                .orderByDesc(StoreProductReplyDO::getId));\n    }\n\n\n\n    @Select(\"select A.product_score as productScore,A.service_score as serviceScore,\" +\n            \"A.comment,A.merchant_reply_content as merchantReplyContent,\" +\n            \"A.merchant_reply_time as merchantReplyTime,A.pics as pictures,A.create_Time as createTime,\" +\n            \"B.nickname,B.avatar,C.cart_info as cartInfo\" +\n            \" from yshop_store_product_reply A left join yshop_user B \" +\n            \"on A.uid = B.id left join yshop_store_order_cart_info C on A.`unique` = C.`unique`\" +\n            \" where A.product_id=#{productId} and A.deleted=0 \" +\n            \"order by A.create_Time DESC limit 1\")\n    AppStoreProductReplyQueryVo getReply(long productId);\n\n    @Select(\"<script>select A.product_score as productScore,A.service_score as serviceScore,\" +\n            \"A.comment,A.merchant_reply_content as merchantReplyContent,\" +\n            \"A.merchant_reply_time as merchantReplyTime,A.pics as pictures,A.create_Time as createTime,\" +\n            \"B.nickname,B.avatar,C.cart_info as cartInfo\" +\n            \" from yshop_store_product_reply A left join yshop_user B \" +\n            \"on A.uid = B.id left join yshop_store_order_cart_info C on A.`unique` = C.`unique`\" +\n            \" where A.product_id=#{productId} and A.deleted=0 \" +\n            \"<if test='type == 1'>and A.product_score = 5</if>\" +\n            \"<if test='type == 2'>and A.product_score &lt; 5 and A.product_score &gt; 2</if>\" +\n            \"<if test='type == 3'>and A.product_score &lt; 2</if>\"+\n            \" order by A.create_Time DESC</script>\")\n    List<AppStoreProductReplyQueryVo> selectReplyList(Page page, @Param(\"productId\") long productId, @Param(\"type\") int type);\n\n    @Select(\"<script>select A.id, A.product_score as productScore,A.service_score as serviceScore,\" +\n            \"A.comment,A.merchant_reply_content as merchantReplyContent,\" +\n            \"A.merchant_reply_time as merchantReplyTime,A.pics as pictures,A.create_Time as createTime,\" +\n            \"B.nickname,B.avatar,C.cart_info as cartInfo\" +\n            \" from yshop_store_product_reply A left join yshop_user B \" +\n            \"on A.uid = B.id left join yshop_store_order_cart_info C on A.`unique` = C.`unique`\" +\n            \" where A.deleted=0 \" +\n            \"<if test =\\\"nickname !=''\\\">and B.nickname = #{nickname}</if>\" +\n            \" order by A.create_Time DESC</script>\")\n    List<AppStoreProductReplyQueryVo> allReplyList(Page page, @Param(\"nickname\") String nickname);\n\n    @Select(\"<script>select count(*)\" +\n            \" from yshop_store_product_reply A left join yshop_user B \" +\n            \"on A.uid = B.id left join yshop_store_order_cart_info C on A.`unique` = C.`unique`\" +\n            \" where A.deleted=0 \" +\n            \"<if test =\\\"nickname !=''\\\">and B.nickname = #{nickname}</if>\" +\n            \" order by A.create_Time DESC</script>\")\n   Long allReplyListCount(@Param(\"nickname\") String nickname);\n\n  // <if test =\"nickname !=''\">\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/dal/mysql/storeproductrule/StoreProductRuleMapper.java",
    "content": "package co.yixiang.yshop.module.product.dal.mysql.storeproductrule;\n\nimport java.util.*;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproductrule.StoreProductRuleDO;\nimport org.apache.ibatis.annotations.Mapper;\nimport co.yixiang.yshop.module.product.controller.admin.storeproductrule.vo.*;\nimport org.apache.ibatis.annotations.Param;\nimport org.apache.ibatis.annotations.Update;\n\n/**\n * 商品规则值(规格) Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface StoreProductRuleMapper extends BaseMapperX<StoreProductRuleDO> {\n\n    default PageResult<StoreProductRuleDO> selectPage(StoreProductRulePageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<StoreProductRuleDO>()\n                .likeIfPresent(StoreProductRuleDO::getRuleName, reqVO.getRuleName())\n                .orderByDesc(StoreProductRuleDO::getId));\n    }\n\n    default List<StoreProductRuleDO> selectList(StoreProductRuleExportReqVO reqVO) {\n        return selectList(new LambdaQueryWrapperX<StoreProductRuleDO>()\n                .likeIfPresent(StoreProductRuleDO::getRuleName, reqVO.getRuleName())\n                .orderByDesc(StoreProductRuleDO::getId));\n    }\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/framework/package-info.java",
    "content": "/**\n * 属于 product 模块的 framework 封装\n *\n * @author yshop\n */\npackage co.yixiang.yshop.module.product.framework;\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/framework/web/config/ProductWebConfiguration.java",
    "content": "package co.yixiang.yshop.module.product.framework.web.config;\n\nimport co.yixiang.yshop.framework.swagger.config.YshopSwaggerAutoConfiguration;\nimport org.springdoc.core.models.GroupedOpenApi;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n/**\n * product 模块的 web 组件的 Configuration\n *\n * @author yshop\n */\n@Configuration(proxyBeanMethods = false)\npublic class ProductWebConfiguration {\n\n    /**\n     * product 模块的 API 分组\n     */\n    @Bean\n    public GroupedOpenApi productGroupedOpenApi() {\n        return YshopSwaggerAutoConfiguration.buildGroupedOpenApi(\"product\");\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/framework/web/package-info.java",
    "content": "/**\n * product 模块的 web 配置\n */\npackage co.yixiang.yshop.module.product.framework.web;\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/package-info.java",
    "content": "/**\n * trade 模块，主要实现交易相关功能\n * 例如：订单、退款、购物车等功能。\n *\n * 1. Controller URL：以 /product/ 开头，避免和其它 Module 冲突\n * 2. DataObject 表名：以 product_ 开头，方便在数据库中区分\n */\npackage co.yixiang.yshop.module.product;\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/service/category/ProductCategoryService.java",
    "content": "package co.yixiang.yshop.module.product.service.category;\n\nimport co.yixiang.yshop.module.product.controller.admin.category.vo.ProductCategoryCreateReqVO;\nimport co.yixiang.yshop.module.product.controller.admin.category.vo.ProductCategoryListReqVO;\nimport co.yixiang.yshop.module.product.controller.admin.category.vo.ProductCategoryUpdateReqVO;\nimport co.yixiang.yshop.module.product.dal.dataobject.category.ProductCategoryDO;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproduct.StoreProductDO;\nimport com.baomidou.mybatisplus.extension.service.IService;\n\nimport jakarta.validation.Valid;\nimport java.util.List;\n\n/**\n * 商品分类 Service 接口\n *\n * @author yshop\n */\npublic interface ProductCategoryService extends IService<ProductCategoryDO> {\n\n    /**\n     * 创建商品分类\n     *\n     * @param createReqVO 创建信息\n     * @return 编号\n     */\n    Long createCategory(@Valid ProductCategoryCreateReqVO createReqVO);\n\n    /**\n     * 更新商品分类\n     *\n     * @param updateReqVO 更新信息\n     */\n    void updateCategory(@Valid ProductCategoryUpdateReqVO updateReqVO);\n\n    /**\n     * 删除商品分类\n     *\n     * @param id 编号\n     */\n    void deleteCategory(Long id);\n\n    /**\n     * 获得商品分类\n     *\n     * @param id 编号\n     * @return 商品分类\n     */\n    ProductCategoryDO getCategory(Long id);\n\n    /**\n     * 校验商品分类\n     *\n     * @param id 分类编号\n     */\n    void validateCategory(Long id);\n\n    /**\n     * 获得商品分类的层级\n     *\n     * @param id 编号\n     * @return 商品分类的层级\n     */\n    Integer getCategoryLevel(Long id);\n\n    /**\n     * 获得商品分类列表\n     *\n     * @param listReqVO 查询条件\n     * @return 商品分类列表\n     */\n    List<ProductCategoryDO> getEnableCategoryList(ProductCategoryListReqVO listReqVO);\n\n    /**\n     * 获得开启状态的商品分类列表\n     *\n     * @return 商品分类列表\n     */\n    List<ProductCategoryDO> getEnableCategoryList(Integer shopId);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/service/category/ProductCategoryServiceImpl.java",
    "content": "package co.yixiang.yshop.module.product.service.category;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.module.product.controller.admin.category.vo.ProductCategoryCreateReqVO;\nimport co.yixiang.yshop.module.product.controller.admin.category.vo.ProductCategoryListReqVO;\nimport co.yixiang.yshop.module.product.controller.admin.category.vo.ProductCategoryUpdateReqVO;\nimport co.yixiang.yshop.module.product.convert.category.ProductCategoryConvert;\nimport co.yixiang.yshop.module.product.dal.dataobject.category.ProductCategoryDO;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproduct.StoreProductDO;\nimport co.yixiang.yshop.module.product.dal.mysql.category.ProductCategoryMapper;\nimport co.yixiang.yshop.module.product.dal.mysql.storeproduct.StoreProductMapper;\nimport co.yixiang.yshop.module.store.dal.dataobject.storeshop.StoreShopDO;\nimport co.yixiang.yshop.module.store.dal.mysql.storeshop.StoreShopMapper;\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\nimport java.util.List;\nimport java.util.Objects;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.product.enums.ErrorCodeConstants.*;\n\n/**\n * 商品分类 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class ProductCategoryServiceImpl extends ServiceImpl<ProductCategoryMapper, ProductCategoryDO> implements ProductCategoryService {\n\n    @Resource\n    private ProductCategoryMapper productCategoryMapper;\n    @Resource\n    private StoreShopMapper storeShopMapper;\n\n    @Override\n    public Long createCategory(ProductCategoryCreateReqVO createReqVO) {\n        // 校验父分类存在\n        //validateParentProductCategory(createReqVO.getParentId());\n\n        // 插入\n        ProductCategoryDO category = ProductCategoryConvert.INSTANCE.convert(createReqVO);\n        StoreShopDO storeShopDO = storeShopMapper.selectById(createReqVO.getShopId());\n        category.setShopName(storeShopDO.getName());\n        productCategoryMapper.insert(category);\n        // 返回\n        return category.getId();\n    }\n\n    @Override\n    public void updateCategory(ProductCategoryUpdateReqVO updateReqVO) {\n        // 校验分类是否存在\n        validateProductCategoryExists(updateReqVO.getId());\n        // 校验父分类存在\n       //validateParentProductCategory(updateReqVO.getParentId());\n\n        // 更新\n        ProductCategoryDO updateObj = ProductCategoryConvert.INSTANCE.convert(updateReqVO);\n        StoreShopDO storeShopDO = storeShopMapper.selectById(updateObj.getShopId());\n        updateObj.setShopName(storeShopDO.getName());\n        productCategoryMapper.updateById(updateObj);\n    }\n\n    @Override\n    public void deleteCategory(Long id) {\n        // 校验分类是否存在\n        validateProductCategoryExists(id);\n        // 校验是否还有子分类\n        if (productCategoryMapper.selectCountByParentId(id) > 0) {\n            throw exception(CATEGORY_EXISTS_CHILDREN);\n        }\n        // TODO yshop 补充只有不存在商品才可以删除\n        // 删除\n        productCategoryMapper.deleteById(id);\n    }\n\n    private void validateParentProductCategory(Long id) {\n        // 如果是根分类，无需验证\n        if (Objects.equals(id, ProductCategoryDO.PARENT_ID_NULL)) {\n            return;\n        }\n        // 父分类不存在\n        ProductCategoryDO category = productCategoryMapper.selectById(id);\n        if (category == null) {\n            throw exception(CATEGORY_PARENT_NOT_EXISTS);\n        }\n\n        ProductCategoryDO storeCategory = productCategoryMapper.selectById(id);\n\n        // 最多二级\n        if (!Objects.equals(storeCategory.getParentId(), ProductCategoryDO.PARENT_ID_NULL)) {\n            throw exception(CATEGORY_PARENT_NOT_FIRST_LEVEL);\n        }\n    }\n\n    private void validateProductCategoryExists(Long id) {\n        ProductCategoryDO category = productCategoryMapper.selectById(id);\n        if (category == null) {\n            throw exception(CATEGORY_NOT_EXISTS);\n        }\n    }\n\n    @Override\n    public ProductCategoryDO getCategory(Long id) {\n        return productCategoryMapper.selectById(id);\n    }\n\n    @Override\n    public void validateCategory(Long id) {\n        ProductCategoryDO category = productCategoryMapper.selectById(id);\n        if (category == null) {\n            throw exception(CATEGORY_NOT_EXISTS);\n        }\n        if (Objects.equals(category.getStatus(), CommonStatusEnum.DISABLE.getStatus())) {\n            throw exception(CATEGORY_DISABLED, category.getName());\n        }\n    }\n\n    @Override\n    public Integer getCategoryLevel(Long id) {\n        if (Objects.equals(id, ProductCategoryDO.PARENT_ID_NULL)) {\n            return 0;\n        }\n        int level = 1;\n        for (int i = 0; i < 100; i++) {\n            ProductCategoryDO category = productCategoryMapper.selectById(id);\n            // 如果没有父节点，break 结束\n            if (category == null\n                    || Objects.equals(category.getParentId(), ProductCategoryDO.PARENT_ID_NULL)) {\n                break;\n            }\n            // 继续递归父节点\n            level++;\n            id = category.getParentId();\n        }\n        return level;\n    }\n\n    @Override\n    public List<ProductCategoryDO> getEnableCategoryList(ProductCategoryListReqVO listReqVO) {\n        return productCategoryMapper.selectList(listReqVO);\n    }\n\n    @Override\n    public List<ProductCategoryDO> getEnableCategoryList(Integer shopId) {\n        return productCategoryMapper.selectListByStatus(CommonStatusEnum.ENABLE.getStatus(),shopId);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/service/storeproduct/AppStoreProductService.java",
    "content": "package co.yixiang.yshop.module.product.service.storeproduct;\n\nimport co.yixiang.yshop.module.product.controller.app.category.vo.AppCategoryRespVO;\nimport co.yixiang.yshop.module.product.controller.app.product.param.AppStoreProductQueryParam;\nimport co.yixiang.yshop.module.product.controller.app.product.vo.AppProductVo;\nimport co.yixiang.yshop.module.product.controller.app.product.vo.AppStoreProductRespVo;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproduct.StoreProductDO;\nimport com.baomidou.mybatisplus.extension.service.IService;\n\nimport java.util.List;\n\n/**\n * 商品 AppService 接口\n *\n * @author yshop\n */\npublic interface AppStoreProductService extends IService<StoreProductDO> {\n\n    /**\n     * 商品列表\n     * @param page 页码\n     * @param limit 条数\n     * @param order ProductEnum\n     * @return List\n     */\n    List<AppStoreProductRespVo> getList(int page, int limit, int order);\n\n    /**\n     * 商品列表\n     * @param productQueryParam AppStoreProductQueryParam\n     * @return list\n     */\n    List<AppCategoryRespVO> getGoodsList(AppStoreProductQueryParam productQueryParam);\n\n\n    /**\n     * 返回普通商品库存\n     * @param productId 商品id\n     * @param unique sku唯一值\n     * @return int\n     */\n    int getProductStock(Long productId, String unique,String type);\n\n    /**\n     * 获取单个商品\n     * @param id 商品id\n     * @return YxStoreProductQueryVo\n     */\n    AppStoreProductRespVo getStoreProductById(Long id);\n\n    /**\n     * 减少库存与增加销量\n     * @param num 数量\n     * @param productId 商品id\n     * @param unique sku\n     */\n    void decProductStock(int num, Long productId, String unique,Long activityId,String type);\n\n    /**\n     * 增加库存 减少销量\n     * @param num 数量\n     * @param productId 商品id\n     * @param unique sku唯一值\n     */\n    void incProductStock(Integer num, Long productId, String unique,Long activityId, String type);\n\n    /**\n     * 检测商品/秒杀/砍价/拼团库存\n     * @param uid  用户ID\n     * @param productId  产品ID\n     * @param cartNum 购买数量\n     * @param productAttrUnique  商品属性Unique\n     */\n    void checkProductStock(Long uid, Long productId, Integer cartNum, String productAttrUnique);\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/service/storeproduct/AppStoreProductServiceImpl.java",
    "content": "package co.yixiang.yshop.module.product.service.storeproduct;\n\nimport cn.hutool.core.collection.ListUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.enums.ShopCommonEnum;\nimport co.yixiang.yshop.framework.common.exception.ErrorCode;\nimport co.yixiang.yshop.module.product.controller.app.category.vo.AppCategoryRespVO;\nimport co.yixiang.yshop.module.product.controller.app.product.param.AppStoreProductQueryParam;\nimport co.yixiang.yshop.module.product.controller.app.product.vo.AppStoreProductAttrQueryVo;\nimport co.yixiang.yshop.module.product.controller.app.product.vo.AppStoreProductRespVo;\nimport co.yixiang.yshop.module.product.convert.category.ProductCategoryConvert;\nimport co.yixiang.yshop.module.product.convert.storeproduct.StoreProductConvert;\nimport co.yixiang.yshop.module.product.dal.dataobject.category.ProductCategoryDO;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproduct.StoreProductDO;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproductattrvalue.StoreProductAttrValueDO;\nimport co.yixiang.yshop.module.product.dal.mysql.storeproduct.StoreProductMapper;\nimport co.yixiang.yshop.module.product.dal.mysql.storeproductattrvalue.StoreProductAttrValueMapper;\nimport co.yixiang.yshop.module.product.enums.product.ProductEnum;\nimport co.yixiang.yshop.module.product.enums.product.ProductTypeEnum;\nimport co.yixiang.yshop.module.product.service.category.ProductCategoryService;\nimport co.yixiang.yshop.module.product.service.storeproductattr.AppStoreProductAttrService;\nimport co.yixiang.yshop.module.product.service.storeproductattrvalue.StoreProductAttrValueService;\nimport co.yixiang.yshop.module.product.service.storeproductreply.AppStoreProductReplyService;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Propagation;\nimport org.springframework.transaction.annotation.Transactional;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.Map;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.product.enums.ErrorCodeConstants.PRODUCT_STOCK_LESS;\nimport static co.yixiang.yshop.module.product.enums.ErrorCodeConstants.STORE_PRODUCT_NOT_EXISTS;\n\n/**\n * 商品 AppService 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class AppStoreProductServiceImpl extends ServiceImpl<StoreProductMapper,StoreProductDO> implements AppStoreProductService {\n\n    @Resource\n    private AppStoreProductAttrService appStoreProductAttrService;\n    @Resource\n    private AppStoreProductReplyService appStoreProductReplyService;\n    @Resource\n    private StoreProductAttrValueService storeProductAttrValueService;\n    @Resource\n    private StoreProductAttrValueMapper storeProductAttrValueMapper;\n    @Resource\n    private ProductCategoryService categoryService;\n\n    /**\n     * 商品列表\n     *\n     * @param page  页码\n     * @param limit 条数\n     * @param order ProductEnum\n     * @return List\n     */\n    @Override\n    public List<AppStoreProductRespVo> getList(int page, int limit, int order) {\n        LambdaQueryWrapper<StoreProductDO> wrapper = new LambdaQueryWrapper<>();\n        wrapper.eq(StoreProductDO::getIsShow, ShopCommonEnum.SHOW_1.getValue())\n                //.eq(YxStoreProduct::getIsDel,ShopCommonEnum.DELETE_0.getValue())\n                .orderByDesc(StoreProductDO::getSort);\n        wrapper.eq(StoreProductDO::getIsIntegral,0);\n        // order\n        switch (ProductEnum.toType(order)) {\n            //精品推荐\n            case TYPE_1:\n                wrapper.eq(StoreProductDO::getIsBest,\n                        ShopCommonEnum.IS_STATUS_1.getValue());\n                break;\n            //首发新品\n            case TYPE_3:\n                wrapper.eq(StoreProductDO::getIsNew,\n                        ShopCommonEnum.IS_STATUS_1.getValue());\n                break;\n            // 猜你喜欢\n            case TYPE_4:\n                wrapper.eq(StoreProductDO::getIsBenefit,\n                        ShopCommonEnum.IS_STATUS_1.getValue());\n                break;\n            // 热门榜单\n            case TYPE_2:\n                wrapper.eq(StoreProductDO::getIsHot,\n                        ShopCommonEnum.IS_STATUS_1.getValue());\n                break;\n            default:\n        }\n        Page<StoreProductDO> pageModel = new Page<>(page, limit);\n\n        IPage<StoreProductDO> pageList = this.baseMapper.selectPage(pageModel, wrapper);\n\n        return StoreProductConvert.INSTANCE.convertList03(pageList.getRecords());\n    }\n\n\n    /**\n     * 商品列表\n     *\n     * @param productQueryParam AppStoreProductQueryParam\n     * @return list\n     */\n    @Override\n    public List<AppCategoryRespVO> getGoodsList(AppStoreProductQueryParam productQueryParam) {\n\n        List<ProductCategoryDO> list = categoryService.getEnableCategoryList(productQueryParam.getShopId());\n        list.sort(Comparator.comparing(ProductCategoryDO::getSort));\n        List<AppCategoryRespVO> appCategoryRespVOS =  ProductCategoryConvert.INSTANCE.convertList03(list);\n\n        for (AppCategoryRespVO appCategoryRespVO : appCategoryRespVOS) {\n            LambdaQueryWrapper<StoreProductDO> wrapper = new LambdaQueryWrapper<>();\n            wrapper.eq(StoreProductDO::getIsShow, ShopCommonEnum.SHOW_1.getValue())\n                    .eq(StoreProductDO::getCateId,appCategoryRespVO.getId())\n                    .eq(StoreProductDO::getShopId,productQueryParam.getShopId());\n            List<StoreProductDO> storeProductDOList = this.baseMapper.selectList(wrapper);\n            List<AppStoreProductRespVo> appStoreProductRespVoList = ListUtil.list(false);\n            for (StoreProductDO storeProductDO : storeProductDOList) {\n                Map<String, Object> returnMap = appStoreProductAttrService.getProductAttrDetail(storeProductDO.getId());\n                AppStoreProductRespVo storeProductQueryVo = StoreProductConvert.INSTANCE.convert01(storeProductDO);\n\n                storeProductQueryVo.setProductAttr((List<AppStoreProductAttrQueryVo>) returnMap.get(\"productAttr\"));\n                storeProductQueryVo.setProductValue((Map<String, StoreProductAttrValueDO>) returnMap.get(\"productValue\"));\n\n                appStoreProductRespVoList.add(storeProductQueryVo);\n            }\n//            List<AppStoreProductRespVo> appStoreProductRespVoList = StoreProductConvert.INSTANCE\n//                    .convertList03(this.baseMapper.selectList(wrapper));\n\n            appCategoryRespVO.setGoodsList(appStoreProductRespVoList);\n        }\n\n        return appCategoryRespVOS;\n\n    }\n\n\n\n    /**\n     * 返回普通商品库存\n     *\n     * @param productId 商品id\n     * @param unique    sku唯一值\n     * @return int\n     */\n    @Override\n    public int getProductStock(Long productId, String unique, String type) {\n        StoreProductAttrValueDO storeProductAttrValue = storeProductAttrValueService\n                .getOne(Wrappers.<StoreProductAttrValueDO>lambdaQuery()\n                        .eq(StoreProductAttrValueDO::getSku, unique)\n                        .eq(StoreProductAttrValueDO::getProductId, productId));\n\n        if (storeProductAttrValue == null) {\n            return 0;\n        }\n        if (ProductTypeEnum.PINK.getValue().equals(type)) {\n            return storeProductAttrValue.getPinkStock();\n        } else if (ProductTypeEnum.SECKILL.getValue().equals(type)) {\n            return storeProductAttrValue.getSeckillStock();\n        }\n        return storeProductAttrValue.getStock();\n\n    }\n\n    /**\n     * 获取单个商品\n     *\n     * @param id 商品id\n     * @return YxStoreProductQueryVo\n     */\n    @Override\n    public AppStoreProductRespVo getStoreProductById(Long id) {\n        AppStoreProductRespVo storeProductRespVo = StoreProductConvert.INSTANCE.convert01(this.baseMapper.selectById(id));\n        Map<String, Object> returnMap = appStoreProductAttrService.getProductAttrDetail(id);\n        storeProductRespVo.setProductAttr((List<AppStoreProductAttrQueryVo>) returnMap.get(\"productAttr\"));\n        storeProductRespVo.setProductValue((Map<String, StoreProductAttrValueDO>) returnMap.get(\"productValue\"));\n        return  storeProductRespVo;\n    }\n\n\n    /**\n     * 减少库存与增加销量\n     *\n     * @param num       数量\n     * @param productId 商品id\n     * @param unique    sku\n     */\n    @Override\n    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)\n    public void decProductStock(int num, Long productId, String unique, Long activityId, String type) {\n\n\n        int res = 0;\n        res =  storeProductAttrValueMapper.decStockIncSales(num,productId,unique);\n\n        if(res == 0) {\n            throw exception(PRODUCT_STOCK_LESS);\n        }\n\n        int product = this.baseMapper.decStockIncSales(num, productId);\n        if (product == 0) {\n            throw exception(PRODUCT_STOCK_LESS);\n        }\n\n\n    }\n\n\n    /**\n     * 增加库存 减少销量\n     *\n     * @param num       数量\n     * @param productId 商品id\n     * @param unique    sku唯一值\n     */\n    @Override\n    public void incProductStock(Integer num, Long productId, String unique, Long activityId, String type) {\n        //处理属性sku\n        if (StrUtil.isNotEmpty(unique)) {\n            storeProductAttrValueMapper.incStockDecSales(num, productId, unique);\n        }\n        //更新商品\n        this.baseMapper.incStockDecSales(num, productId);\n    }\n\n    /**\n     * 检测商品库存 库存加锁\n     *\n     * @param uid               用户ID\n     * @param productId         产品ID\n     * @param cartNum           购买数量\n     * @param productAttrUnique 商品属性Unique\n     */\n    @Override\n    public void checkProductStock(Long uid, Long productId, Integer cartNum, String productAttrUnique) {\n        StoreProductDO product = this\n                .lambdaQuery().eq(StoreProductDO::getId, productId)\n                .eq(StoreProductDO::getIsShow, ShopCommonEnum.SHOW_1.getValue())\n                .one();\n        if (product == null) {\n            throw exception(STORE_PRODUCT_NOT_EXISTS);\n        }\n\n        int stock = this.getProductStock(productId, productAttrUnique, \"\");\n        if (stock < cartNum) {\n            throw exception(new ErrorCode(1008003010, product.getStoreName() + \"库存不足\" + cartNum));\n        }\n\n    }\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/service/storeproduct/StoreProductService.java",
    "content": "package co.yixiang.yshop.module.product.service.storeproduct;\n\nimport java.util.*;\nimport jakarta.validation.*;\nimport co.yixiang.yshop.module.product.controller.admin.storeproduct.vo.*;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproduct.StoreProductDO;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproductattrvalue.StoreProductAttrValueDO;\nimport co.yixiang.yshop.module.product.service.storeproduct.dto.StoreProductDto;\nimport com.baomidou.mybatisplus.extension.service.IService;\n\n/**\n * 商品 Service 接口\n *\n * @author yshop\n */\npublic interface StoreProductService extends IService<StoreProductDO> {\n\n    /**\n     * 创建商品\n     *\n     * @param createReqVO 创建信息\n     * @return 编号\n     */\n    Long createStoreProduct(@Valid StoreProductCreateReqVO createReqVO);\n\n    /**\n     * 更新商品\n     *\n     * @param updateReqVO 更新信息\n     */\n    void updateStoreProduct(@Valid StoreProductUpdateReqVO updateReqVO);\n\n    /**\n     * 删除商品\n     *\n     * @param id 编号\n     */\n    void deleteStoreProduct(Long id);\n\n    /**\n     * 获得商品\n     *\n     * @param id 编号\n     * @return 商品\n     */\n    StoreProductDO getStoreProduct(Long id);\n\n    /**\n     * 获得商品列表\n     *\n     * @param ids 编号\n     * @return 商品列表\n     */\n    List<StoreProductDO> getStoreProductList(Collection<Long> ids);\n\n    /**\n     * 获得商品分页\n     *\n     * @param pageReqVO 分页查询\n     * @return 商品分页\n     */\n    PageResult<StoreProductDO> getStoreProductPage(StoreProductPageReqVO pageReqVO);\n\n    /**\n     * 获得商品列表, 用于 Excel 导出\n     *\n     * @param exportReqVO 查询条件\n     * @return 商品列表\n     */\n    List<StoreProductDO> getStoreProductList(StoreProductExportReqVO exportReqVO);\n\n    /**\n     * 获取生成的属性\n     * @param id 商品id\n     * @param jsonStr jsonStr\n     * @return map\n     */\n    Map<String,Object> getFormatAttr(Long id, String jsonStr,boolean isActivity);\n\n    /**\n     * 新增/保存商品\n     * @param storeProductDto 商品\n     */\n    void insertAndEditYxStoreProduct(StoreProductDto storeProductDto);\n\n    Map<String,Object> getProductInfo(Long id);\n\n    /**\n     * 商品上架下架\n     * @param id 商品id\n     * @param status  ShopCommonEnum\n     */\n    void onSale(Long id,Integer status);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/service/storeproduct/StoreProductServiceImpl.java",
    "content": "package co.yixiang.yshop.module.product.service.storeproduct;\n\nimport cn.hutool.core.bean.BeanUtil;\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.collection.ListUtil;\nimport cn.hutool.core.convert.Convert;\nimport cn.hutool.core.util.ArrayUtil;\nimport cn.hutool.core.util.ObjectUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.enums.ShopCommonEnum;\nimport co.yixiang.yshop.framework.common.util.string.StrUtils;\nimport co.yixiang.yshop.module.product.convert.storeproductrule.StoreProductRuleConvert;\nimport co.yixiang.yshop.module.product.dal.dataobject.category.ProductCategoryDO;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproductattrresult.StoreProductAttrResultDO;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproductattrvalue.StoreProductAttrValueDO;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproductrule.StoreProductRuleDO;\nimport co.yixiang.yshop.module.product.enums.product.SpecTypeEnum;\nimport co.yixiang.yshop.module.product.service.category.ProductCategoryService;\nimport co.yixiang.yshop.module.product.service.storeproduct.dto.*;\nimport co.yixiang.yshop.module.product.service.storeproductattr.StoreProductAttrService;\nimport co.yixiang.yshop.module.product.service.storeproductattrresult.StoreProductAttrResultService;\nimport co.yixiang.yshop.module.product.service.storeproductattrvalue.StoreProductAttrValueService;\nimport co.yixiang.yshop.module.product.service.storeproductrule.StoreProductRuleService;\nimport co.yixiang.yshop.module.store.dal.dataobject.storeshop.StoreShopDO;\nimport co.yixiang.yshop.module.store.dal.mysql.storeshop.StoreShopMapper;\nimport com.alibaba.fastjson.JSON;\nimport com.alibaba.fastjson.JSONObject;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport org.springframework.beans.BeanUtils;\nimport org.springframework.stereotype.Service;\nimport jakarta.annotation.Resource;\n\nimport org.springframework.transaction.annotation.Transactional;\nimport org.springframework.validation.annotation.Validated;\n\nimport java.math.BigDecimal;\nimport java.util.*;\nimport java.util.stream.Collectors;\n\nimport co.yixiang.yshop.module.product.controller.admin.storeproduct.vo.*;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproduct.StoreProductDO;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\nimport co.yixiang.yshop.module.product.convert.storeproduct.StoreProductConvert;\nimport co.yixiang.yshop.module.product.dal.mysql.storeproduct.StoreProductMapper;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.product.enums.ErrorCodeConstants.*;\n\n/**\n * 商品 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class StoreProductServiceImpl extends ServiceImpl<StoreProductMapper,StoreProductDO> implements StoreProductService {\n\n    @Resource\n    private StoreProductMapper storeProductMapper;\n    @Resource\n    private StoreProductAttrValueService storeProductAttrValueService;\n    @Resource\n    private StoreProductAttrService storeProductAttrService;\n    @Resource\n    private StoreProductRuleService storeProductRuleService;\n    @Resource\n    private StoreProductAttrResultService storeProductAttrResultService;\n    @Resource\n    private ProductCategoryService productCategoryService;\n    @Resource\n    private StoreShopMapper storeShopMapper;\n\n    @Override\n    public Long createStoreProduct(StoreProductCreateReqVO createReqVO) {\n        // 插入\n        StoreProductDO storeProduct = StoreProductConvert.INSTANCE.convert(createReqVO);\n        storeProductMapper.insert(storeProduct);\n        // 返回\n        return storeProduct.getId();\n    }\n\n    @Override\n    public void updateStoreProduct(StoreProductUpdateReqVO updateReqVO) {\n        // 校验存在\n        validateStoreProductExists(updateReqVO.getId());\n        // 更新\n        StoreProductDO updateObj = StoreProductConvert.INSTANCE.convert(updateReqVO);\n        storeProductMapper.updateById(updateObj);\n    }\n\n    @Override\n    public void deleteStoreProduct(Long id) {\n        // 校验存在\n        validateStoreProductExists(id);\n        // 删除\n        storeProductMapper.deleteById(id);\n    }\n\n    private void validateStoreProductExists(Long id) {\n        if (storeProductMapper.selectById(id) == null) {\n            throw exception(STORE_PRODUCT_NOT_EXISTS);\n        }\n    }\n\n    @Override\n    public StoreProductDO getStoreProduct(Long id) {\n        return storeProductMapper.selectById(id);\n    }\n\n    @Override\n    public List<StoreProductDO> getStoreProductList(Collection<Long> ids) {\n        return storeProductMapper.selectBatchIds(ids);\n    }\n\n    @Override\n    public PageResult<StoreProductDO> getStoreProductPage(StoreProductPageReqVO pageReqVO) {\n//        if(StrUtil.isNotEmpty(pageReqVO.getCateId())) {\n//            ProductCategoryDO productCategoryDO = productCategoryService.getCategory(Convert.toLong(pageReqVO.getCateId()));\n//            if(productCategoryDO != null) {\n//                List<Long> catIds = new ArrayList<>();\n//                if (Objects.equals(productCategoryDO.getParentId(), ProductCategoryDO.PARENT_ID_NULL)) {\n//                    catIds = productCategoryService.list((Wrappers.<ProductCategoryDO>lambdaQuery()\n//                            .eq(ProductCategoryDO::getParentId, productCategoryDO.getId())))\n//                            .stream().map(ProductCategoryDO::getId).collect(Collectors.toList());\n//                } else {\n//                    catIds.add(Convert.toLong(pageReqVO.getCateId()));\n//                }\n//                pageReqVO.setCatIds(catIds);\n//            }\n      //  }\n        return storeProductMapper.selectPage(pageReqVO);\n    }\n\n    @Override\n    public List<StoreProductDO> getStoreProductList(StoreProductExportReqVO exportReqVO) {\n        return storeProductMapper.selectList(exportReqVO);\n    }\n\n    /**\n     * 获取生成的属性\n     *\n     * @param id      商品id\n     * @param jsonStr jsonStr\n     * @return map\n     */\n    @Override\n    public Map<String, Object> getFormatAttr(Long id, String jsonStr, boolean isActivity) {\n        JSONObject jsonObject = JSON.parseObject(jsonStr);\n        Map<String, Object> resultMap = new LinkedHashMap<>(3);\n\n        if (jsonObject == null || jsonObject.get(\"attrs\") == null || jsonObject.getJSONArray(\"attrs\").isEmpty()) {\n            resultMap.put(\"attr\", new ArrayList<>());\n            resultMap.put(\"value\", new ArrayList<>());\n            resultMap.put(\"header\", new ArrayList<>());\n            return resultMap;\n        }\n\n\n        List<FromatDetailDto> fromatDetailDTOList = JSON.parseArray(jsonObject.get(\"attrs\").toString(),\n                FromatDetailDto.class);\n\n        //fromatDetailDTOList\n        DetailDto detailDto = this.attrFormat(fromatDetailDTOList);\n\n        List<Map<String, Object>> headerMapList = null;\n        List<Map<String, Object>> valueMapList = new ArrayList<>();\n        String align = \"center\";\n        Map<String, Object> headerMap = new LinkedHashMap<>();\n        for (Map<String, Map<String, String>> map : detailDto.getRes()) {\n            Map<String, String> detail = map.get(\"detail\");\n            String[] detailArr = detail.values().toArray(new String[]{});\n            //Arrays.sort(detailArr);\n\n           // String sku = String.join(\",\", detailArr);\n            String sku = String.join(\",\", StrUtils.compareTo(CollUtil.toList(detailArr)));\n\n            Map<String, Object> valueMap = new LinkedHashMap<>();\n\n            List<String> detailKeys =\n                    detail.entrySet()\n                            .stream()\n                            .map(Map.Entry::getKey)\n                            .collect(Collectors.toList());\n\n            int i = 0;\n            headerMapList = new ArrayList<>();\n            for (String title : detailKeys) {\n                headerMap.put(\"title\", title);\n                headerMap.put(\"minWidth\", \"130\");\n                headerMap.put(\"align\", align);\n                headerMap.put(\"key\", \"value\" + (i + 1));\n                headerMap.put(\"slot\", \"value\" + (i + 1));\n                headerMapList.add(ObjectUtil.clone(headerMap));\n                i++;\n            }\n\n            String[] detailValues = detail.values().toArray(new String[]{});\n            for (int j = 0; j < detailValues.length; j++) {\n                String key = \"value\" + (j + 1);\n                valueMap.put(key, detailValues[j]);\n            }\n//            /** 拼团属性对应的金额 */\n//            private BigDecimal pinkPrice;\n//\n//            /** 秒杀属性对应的金额 */\n//            private BigDecimal seckillPrice;\n//            /** 拼团库存属性对应的库存 */\n//            private Integer pinkStock;\n//\n//            private Integer seckillStock;\n            valueMap.put(\"detail\", detail);\n            valueMap.put(\"sku\", \"\");\n            valueMap.put(\"pic\", \"\");\n            valueMap.put(\"price\", 0);\n            valueMap.put(\"cost\", 0);\n            valueMap.put(\"ot_price\", 0);\n            valueMap.put(\"stock\", 0);\n            valueMap.put(\"bar_code\", \"\");\n            valueMap.put(\"weight\", 0);\n            valueMap.put(\"volume\", 0);\n            valueMap.put(\"brokerage\", 0);\n            valueMap.put(\"brokerage_two\", 0);\n            valueMap.put(\"pink_price\", 0);\n            valueMap.put(\"seckill_price\", 0);\n            valueMap.put(\"pink_stock\", 0);\n            valueMap.put(\"seckill_stock\", 0);\n            valueMap.put(\"integral\", 0);\n            if (id > 0) {\n                StoreProductAttrValueDO storeProductAttrValue = storeProductAttrValueService\n                        .getOne(Wrappers.<StoreProductAttrValueDO>lambdaQuery()\n                                .eq(StoreProductAttrValueDO::getProductId, id)\n                                .eq(StoreProductAttrValueDO::getSku, sku));\n                if (storeProductAttrValue != null) {\n                    valueMap.put(\"sku\",storeProductAttrValue.getSku());\n                    valueMap.put(\"pic\", storeProductAttrValue.getImage());\n                    valueMap.put(\"price\", storeProductAttrValue.getPrice());\n                    valueMap.put(\"cost\", storeProductAttrValue.getCost());\n                    valueMap.put(\"ot_price\", storeProductAttrValue.getOtPrice());\n                    valueMap.put(\"stock\", storeProductAttrValue.getStock());\n                    valueMap.put(\"bar_code\", storeProductAttrValue.getBarCode());\n                    valueMap.put(\"weight\", storeProductAttrValue.getWeight());\n                    valueMap.put(\"volume\", storeProductAttrValue.getVolume());\n                    valueMap.put(\"brokerage\", storeProductAttrValue.getBrokerage());\n                    valueMap.put(\"brokerage_two\", storeProductAttrValue.getBrokerageTwo());\n                    valueMap.put(\"pink_price\", storeProductAttrValue.getPinkPrice());\n                    valueMap.put(\"seckill_price\", storeProductAttrValue.getSeckillPrice());\n                    valueMap.put(\"pink_stock\", storeProductAttrValue.getPinkStock());\n                    valueMap.put(\"seckill_stock\", storeProductAttrValue.getSeckillStock());\n                    valueMap.put(\"integral\", storeProductAttrValue.getIntegral());\n                }\n            }\n\n            valueMapList.add(ObjectUtil.clone(valueMap));\n\n        }\n\n        this.addMap(headerMap, headerMapList, align, isActivity);\n\n\n        resultMap.put(\"attr\", fromatDetailDTOList);\n        resultMap.put(\"value\", valueMapList);\n        resultMap.put(\"header\", headerMapList);\n\n        return resultMap;\n    }\n\n    /**\n     * 增加表头\n     *\n     * @param headerMap     headerMap\n     * @param headerMapList headerMapList\n     * @param align         align\n     */\n    private void addMap(Map<String, Object> headerMap, List<Map<String, Object>> headerMapList, String align, boolean isActivity) {\n        headerMap.put(\"title\", \"图片\");\n        headerMap.put(\"slot\", \"pic\");\n        headerMap.put(\"align\", align);\n        headerMap.put(\"minWidth\", 80);\n        headerMapList.add(ObjectUtil.clone(headerMap));\n\n        headerMap.put(\"title\", \"售价\");\n        headerMap.put(\"slot\", \"price\");\n        headerMap.put(\"align\", align);\n        headerMap.put(\"minWidth\", 120);\n        headerMapList.add(ObjectUtil.clone(headerMap));\n\n        headerMap.put(\"title\", \"成本价\");\n        headerMap.put(\"slot\", \"cost\");\n        headerMap.put(\"align\", align);\n        headerMap.put(\"minWidth\", 140);\n        headerMapList.add(ObjectUtil.clone(headerMap));\n\n        headerMap.put(\"title\", \"原价\");\n        headerMap.put(\"slot\", \"ot_price\");\n        headerMap.put(\"align\", align);\n        headerMap.put(\"minWidth\", 140);\n        headerMapList.add(ObjectUtil.clone(headerMap));\n\n        headerMap.put(\"title\", \"库存\");\n        headerMap.put(\"slot\", \"stock\");\n        headerMap.put(\"align\", align);\n        headerMap.put(\"minWidth\", 140);\n        headerMapList.add(ObjectUtil.clone(headerMap));\n\n        headerMap.put(\"title\", \"产品编号\");\n        headerMap.put(\"slot\", \"bar_code\");\n        headerMap.put(\"align\", align);\n        headerMap.put(\"minWidth\", 140);\n        headerMapList.add(ObjectUtil.clone(headerMap));\n\n        headerMap.put(\"title\", \"重量(KG)\");\n        headerMap.put(\"slot\", \"weight\");\n        headerMap.put(\"align\", align);\n        headerMap.put(\"minWidth\", 140);\n        headerMapList.add(ObjectUtil.clone(headerMap));\n\n        headerMap.put(\"title\", \"体积(m³)\");\n        headerMap.put(\"slot\", \"volume\");\n        headerMap.put(\"align\", align);\n        headerMap.put(\"minWidth\", 140);\n        headerMapList.add(ObjectUtil.clone(headerMap));\n\n\n        if (isActivity) {\n            headerMap.put(\"title\", \"拼团价\");\n            headerMap.put(\"slot\", \"pink_price\");\n            headerMap.put(\"align\", align);\n            headerMap.put(\"minWidth\", 140);\n            headerMapList.add(ObjectUtil.clone(headerMap));\n\n            headerMap.put(\"title\", \"拼团活动库存\");\n            headerMap.put(\"slot\", \"pink_stock\");\n            headerMap.put(\"align\", align);\n            headerMap.put(\"minWidth\", 140);\n            headerMapList.add(ObjectUtil.clone(headerMap));\n\n            headerMap.put(\"title\", \"秒杀价\");\n            headerMap.put(\"slot\", \"seckill_price\");\n            headerMap.put(\"align\", align);\n            headerMap.put(\"minWidth\", 140);\n            headerMapList.add(ObjectUtil.clone(headerMap));\n\n            headerMap.put(\"title\", \"秒杀活动库存\");\n            headerMap.put(\"slot\", \"seckill_stock\");\n            headerMap.put(\"align\", align);\n            headerMap.put(\"minWidth\", 140);\n            headerMapList.add(ObjectUtil.clone(headerMap));\n        }\n\n        headerMap.put(\"title\", \"操作\");\n        headerMap.put(\"slot\", \"action\");\n        headerMap.put(\"align\", align);\n        headerMap.put(\"minWidth\", 70);\n        headerMapList.add(ObjectUtil.clone(headerMap));\n    }\n\n\n\n    /**\n     * 组合规则属性算法\n     *\n     * @param fromatDetailDTOList\n     * @return DetailDto\n     */\n    private DetailDto attrFormat(List<FromatDetailDto> fromatDetailDTOList) {\n\n        List<String> data = new ArrayList<>();\n        List<Map<String, Map<String, String>>> res = new ArrayList<>();\n\n        fromatDetailDTOList.stream()\n                .map(FromatDetailDto::getDetail)\n                .forEach(i -> {\n                    if (i == null || i.isEmpty()) {\n                        throw exception(STORE_PRODUCT_RULE_NEED);\n                    }\n                    String str = ArrayUtil.join(i.toArray(), \",\");\n                    if (str.contains(\"-\")) {\n                        throw exception(STORE_PRODUCT_RULE_RE);\n                    }\n                });\n\n        if (fromatDetailDTOList.size() > 1) {\n            for (int i = 0; i < fromatDetailDTOList.size() - 1; i++) {\n                if (i == 0) {\n                    data = fromatDetailDTOList.get(i).getDetail();\n                }\n                List<String> tmp = new LinkedList<>();\n                for (String v : data) {\n                    for (String g : fromatDetailDTOList.get(i + 1).getDetail()) {\n                        String rep2 = \"\";\n                        if (i == 0) {\n                            rep2 = fromatDetailDTOList.get(i).getValue() + \"_\" + v + \"-\"\n                                    + fromatDetailDTOList.get(i + 1).getValue() + \"_\" + g;\n                        } else {\n                            rep2 = v + \"-\"\n                                    + fromatDetailDTOList.get(i + 1).getValue() + \"_\" + g;\n                        }\n\n                        tmp.add(rep2);\n\n                        if (i == fromatDetailDTOList.size() - 2) {\n                            Map<String, Map<String, String>> rep4 = new LinkedHashMap<>();\n                            Map<String, String> reptemp = new LinkedHashMap<>();\n                            for (String h : Arrays.asList(rep2.split(\"-\"))) {\n                                List<String> rep3 = Arrays.asList(h.split(\"_\"));\n                                if (rep3.size() > 1) {\n                                    reptemp.put(rep3.get(0), rep3.get(1));\n                                } else {\n                                    reptemp.put(rep3.get(0), \"\");\n                                }\n                            }\n                            rep4.put(\"detail\", reptemp);\n\n                            res.add(rep4);\n                        }\n                    }\n\n                }\n\n                if (!tmp.isEmpty()) {\n                    data = tmp;\n                }\n            }\n        } else {\n            List<String> dataArr = new ArrayList<>();\n            for (FromatDetailDto fromatDetailDTO : fromatDetailDTOList) {\n                for (String str : fromatDetailDTO.getDetail()) {\n                    Map<String, Map<String, String>> map2 = new LinkedHashMap<>();\n                    dataArr.add(fromatDetailDTO.getValue() + \"_\" + str);\n                    Map<String, String> map1 = new LinkedHashMap<>();\n                    map1.put(fromatDetailDTO.getValue(), str);\n                    map2.put(\"detail\", map1);\n                    res.add(map2);\n                }\n            }\n            String s = StrUtil.join(\"-\", dataArr);\n            data.add(s);\n        }\n\n        DetailDto detailDto = new DetailDto();\n        detailDto.setData(data);\n        detailDto.setRes(res);\n\n        return detailDto;\n    }\n\n    /**\n     * 新增/保存商品\n     *\n     * @param storeProductDto 商品\n     */\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void insertAndEditYxStoreProduct(StoreProductDto storeProductDto) {\n        //storeProductDto.setDescription(RegexUtil.converProductDescription(storeProductDto.getDescription()));\n        ProductResultDto resultDTO = this.computedProduct(storeProductDto.getAttrs());\n\n        //添加商品\n        StoreProductDO yxStoreProduct = new StoreProductDO();\n        BeanUtil.copyProperties(storeProductDto, yxStoreProduct, \"sliderImage\");\n        if (storeProductDto.getSliderImage().isEmpty()) {\n            throw exception(STORE_PRODUCT_SLIDER_ERROR);\n        }\n        StoreShopDO storeShopDO = storeShopMapper.selectById(storeProductDto.getShopId());\n        yxStoreProduct.setShopName(storeShopDO.getName());\n        yxStoreProduct.setPrice(BigDecimal.valueOf(resultDTO.getMinPrice()));\n        yxStoreProduct.setOtPrice(BigDecimal.valueOf(resultDTO.getMinOtPrice()));\n        yxStoreProduct.setCost(BigDecimal.valueOf(resultDTO.getMinCost()));\n        yxStoreProduct.setIntegral(resultDTO.getMinIntegral());\n        yxStoreProduct.setStock(resultDTO.getStock());\n        yxStoreProduct.setSliderImage(String.join(\",\", storeProductDto.getSliderImage()));\n\n\n        this.saveOrUpdate(yxStoreProduct);\n\n        //属性处理\n        //处理单sKu\n        if (SpecTypeEnum.TYPE_0.getValue().equals(storeProductDto.getSpecType())) {\n            FromatDetailDto fromatDetailDto = FromatDetailDto.builder()\n                    .value(\"规格\")\n                    .detailValue(\"\")\n                    .attrHidden(\"\")\n                    .detail(ListUtil.toList(\"默认\"))\n                    .build();\n            List<ProductFormatDto> attrs = storeProductDto.getAttrs();\n            ProductFormatDto productFormatDto = attrs.get(0);\n            productFormatDto.setValue1(\"规格\");\n            Map<String, String> map = new HashMap<>();\n            map.put(\"规格\", \"默认\");\n            productFormatDto.setDetail(map);\n            storeProductAttrService.insertYxStoreProductAttr(ListUtil.toList(fromatDetailDto),\n                    ListUtil.toList(productFormatDto), yxStoreProduct.getId());\n        } else {\n            storeProductAttrService.insertYxStoreProductAttr(storeProductDto.getItems(),\n                    storeProductDto.getAttrs(), yxStoreProduct.getId());\n        }\n\n\n    }\n\n    @Override\n    public Map<String, Object> getProductInfo(Long id) {\n        Map<String,Object> map = new LinkedHashMap<>(3);\n\n        ArrayUtil.newArray(String.class, 3);\n        //运费模板\n        //todo\n        //shippingTemplatesService.list()\n\n\n\n        //商品规格\n        List<StoreProductRuleDO> list = storeProductRuleService.getStoreProductRuleList(CollUtil.newArrayList());\n        map.put(\"ruleList\", StoreProductRuleConvert.INSTANCE.convertList(list));\n        if (id == 0 ) {\n            return map;\n        }\n\n        //处理商品详情\n        StoreProductDO storeProduct = storeProductMapper.selectById(id);\n        ProductDto productDto = new ProductDto();\n        BeanUtil.copyProperties(storeProduct,productDto,\"sliderImage\");\n        productDto.setSliderImage(Arrays.asList(storeProduct.getSliderImage().split(\",\")));\n        StoreProductAttrResultDO storeProductAttrResult = storeProductAttrResultService\n                .getOne(Wrappers.<StoreProductAttrResultDO>lambdaQuery()\n                        .eq(StoreProductAttrResultDO::getProductId,id).last(\"limit 1\"));\n        JSONObject result = JSON.parseObject(storeProductAttrResult.getResult());\n        List<StoreProductAttrValueDO> attrValues = storeProductAttrValueService.list(new LambdaQueryWrapper<StoreProductAttrValueDO>().eq(StoreProductAttrValueDO::getProductId, id));\n        List<ProductFormatDto> productFormatDtos =attrValues.stream().map(i ->{\n            ProductFormatDto productFormatDto = new ProductFormatDto();\n            BeanUtils.copyProperties(i,productFormatDto);\n            productFormatDto.setPic(i.getImage());\n            return productFormatDto;\n        }).collect(Collectors.toList());\n        if(SpecTypeEnum.TYPE_1.getValue().equals(storeProduct.getSpecType())){\n            productDto.setAttr(new ProductFormatDto());\n            productDto.setAttrs(productFormatDtos);\n            productDto.setItems(result.getObject(\"attr\",ArrayList.class));\n        }else{\n           // this.productFromat(productDto, result);\n            this.productFromatNew(productDto, attrValues.get(0));\n        }\n\n        map.put(\"productInfo\",productDto);\n\n        return map;\n\n    }\n\n    /**\n     * 商品上架下架\n     *\n     * @param id     商品id\n     * @param status ShopCommonEnum\n     */\n    @Override\n    public void onSale(Long id, Integer status) {\n        if (ShopCommonEnum.SHOW_1.getValue().equals(status)) {\n            status = ShopCommonEnum.SHOW_0.getValue();\n        } else {\n            status = ShopCommonEnum.SHOW_1.getValue();\n        }\n        storeProductMapper.updateOnsale(status, id);\n    }\n\n\n    /**\n     * 获取商品属性\n     * @param productDto\n     * @param result\n     */\n    private void productFromatNew(ProductDto productDto, StoreProductAttrValueDO result) {\n        //Map<String,Object> mapAttr = (Map<String,Object>) result.getObject(\"value\",ArrayList.class).get(0);\n        ProductFormatDto productFormatDto = ProductFormatDto.builder()\n                .pic(result.getImage())\n                .price(result.getPrice().doubleValue())\n                .cost(result.getCost().doubleValue())\n                .otPrice(result.getOtPrice().doubleValue())\n                .stock(result.getStock())\n                .barCode(result.getBarCode())\n                .weight(result.getWeight().doubleValue())\n                .volume(result.getVolume().doubleValue())\n                .value1(\"规格\")\n                .integral(result.getIntegral())\n                .brokerage(result.getBrokerage().doubleValue())\n                .brokerageTwo(result.getBrokerageTwo().doubleValue())\n                .pinkPrice(result.getPinkPrice().doubleValue())\n                .pinkStock(result.getPinkStock())\n                .seckillPrice(result.getSeckillPrice().doubleValue())\n                .seckillStock(result.getSeckillStock())\n                .build();\n        productDto.setAttr(productFormatDto);\n    }\n\n\n    /**\n     * 获取商品属性 已经废弃\n     * @param productDto\n     * @param result\n     */\n    @Deprecated\n    private void productFromat(ProductDto productDto, JSONObject result) {\n        Map<String,Object> mapAttr = (Map<String,Object>) result.getObject(\"value\",ArrayList.class).get(0);\n        ProductFormatDto productFormatDto = ProductFormatDto.builder()\n                .pic(mapAttr.get(\"pic\").toString())\n                .price(Double.valueOf(mapAttr.get(\"price\").toString()))\n                .cost(Double.valueOf(mapAttr.get(\"cost\").toString()))\n                .otPrice(Double.valueOf(mapAttr.get(\"otPrice\").toString()))\n                .stock(Integer.valueOf(mapAttr.get(\"stock\").toString()))\n                .barCode(mapAttr.get(\"barCode\").toString())\n                .weight(Double.valueOf(mapAttr.get(\"weight\").toString()))\n                .volume(Double.valueOf(mapAttr.get(\"volume\").toString()))\n                .value1(mapAttr.get(\"value1\").toString())\n                .integral(mapAttr.get(\"integral\") !=null ? Integer.valueOf(mapAttr.get(\"integral\").toString()) : 0)\n                .brokerage(Double.valueOf(mapAttr.get(\"brokerage\").toString()))\n                .brokerageTwo(Double.valueOf(mapAttr.get(\"brokerageTwo\").toString()))\n                .pinkPrice(Double.valueOf(mapAttr.get(\"pinkPrice\").toString()))\n                .pinkStock(Integer.valueOf(mapAttr.get(\"pinkStock\").toString()))\n                .seckillPrice(Double.valueOf(mapAttr.get(\"seckillPrice\").toString()))\n                .seckillStock(Integer.valueOf(mapAttr.get(\"seckillStock\").toString()))\n                .build();\n        productDto.setAttr(productFormatDto);\n    }\n\n\n    /**\n     * 计算产品数据\n     *\n     * @param attrs attrs\n     * @return ProductResultDto\n     */\n    private ProductResultDto computedProduct(List<ProductFormatDto> attrs) {\n        //取最小价格\n        Double minPrice = attrs\n                .stream()\n                .map(ProductFormatDto::getPrice)\n                .min(Comparator.naturalOrder())\n                .orElse(0d);\n\n        //取最小积分\n        Integer minIntegral = attrs\n                .stream()\n                .map(ProductFormatDto::getIntegral)\n                .min(Comparator.naturalOrder())\n                .orElse(0);\n\n        Double minOtPrice = attrs\n                .stream()\n                .map(ProductFormatDto::getOtPrice)\n                .min(Comparator.naturalOrder())\n                .orElse(0d);\n\n        Double minCost = attrs\n                .stream()\n                .map(ProductFormatDto::getCost)\n                .min(Comparator.naturalOrder())\n                .orElse(0d);\n        //计算库存\n        Integer stock = attrs\n                .stream()\n                .map(ProductFormatDto::getStock)\n                .reduce(Integer::sum)\n                .orElse(0);\n\n        if (stock <= 0) {\n            throw exception(STORE_PRODUCT_STOCK_ERROR);\n        }\n\n        return ProductResultDto.builder()\n                .minPrice(minPrice)\n                .minOtPrice(minOtPrice)\n                .minCost(minCost)\n                .stock(stock)\n                .minIntegral(minIntegral)\n                .build();\n    }\n\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/service/storeproduct/dto/AttrValueDto.java",
    "content": "package co.yixiang.yshop.module.product.service.storeproduct.dto;\r\n\r\nimport lombok.Data;\r\n\r\n/**\r\n * @ClassName AttrValueDTO\r\n * @Author hupeng <610796224@qq.com>\r\n * @Date 2019/10/23\r\n **/\r\n@Data\r\npublic class AttrValueDto {\r\n\r\n    private String attr;\r\n\r\n    private Boolean check = false;\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/service/storeproduct/dto/DetailDto.java",
    "content": "/**\n * Copyright (C) 2018-2022\n * All rights reserved, Designed By www.yixiang.co\n\n */\npackage co.yixiang.yshop.module.product.service.storeproduct.dto;\n\nimport lombok.Data;\n\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * @ClassName DetailDTO\n * @Author hupeng <610796224@qq.com>\n * @Date 2019/10/12\n **/\n@Data\npublic class DetailDto {\n    private List<String> data;\n\n    //private List<Map<String,List<Map<String,String>>>> res;\n\n    private List<Map<String,Map<String,String>>> res;\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/service/storeproduct/dto/FromatDetailDto.java",
    "content": "/**\n * Copyright (C) 2018-2022\n * All rights reserved, Designed By www.yixiang.co\n\n */\npackage co.yixiang.yshop.module.product.service.storeproduct.dto;\n\nimport lombok.*;\n\nimport java.util.List;\n\n/**\n * @ClassName FromatDetailDTO\n * @Author hupeng <610796224@qq.com>\n * @Date 2019/10/12\n **/\n\n@Getter\n@Setter\n@NoArgsConstructor\n@AllArgsConstructor\n@Builder\npublic class FromatDetailDto {\n    private  String attrHidden;\n\n    private  String detailValue;\n\n    private List<String> detail;\n\n    private String value;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/service/storeproduct/dto/ProductDto.java",
    "content": "package co.yixiang.yshop.module.product.service.storeproduct.dto;\r\n\r\n\r\nimport com.fasterxml.jackson.annotation.JsonProperty;\r\nimport lombok.AllArgsConstructor;\r\nimport lombok.Getter;\r\nimport lombok.NoArgsConstructor;\r\nimport lombok.Setter;\r\n\r\nimport java.util.List;\r\n\r\n/**\r\n * 商品对象VO\r\n *\r\n * @author hupeng\r\n * @date 2020-04-25\r\n */\r\n@NoArgsConstructor\r\n@AllArgsConstructor\r\n@Getter\r\n@Setter\r\npublic class ProductDto\r\n{\r\n\r\n    /** 商品id */\r\n    private Long id;\r\n\r\n\r\n    /** 商品图片 */\r\n    private String image;\r\n\r\n    private String shopName;\r\n\r\n    private Integer shopId;\r\n\r\n    /** 轮播图 */\r\n    @JsonProperty(\"slider_image\")\r\n    private List<String> sliderImage;\r\n\r\n    /** 商品名称 */\r\n    @JsonProperty(\"store_name\")\r\n    private String storeName;\r\n\r\n    /** 商品简介 */\r\n    @JsonProperty(\"store_info\")\r\n    private String storeInfo;\r\n\r\n    /** 关键字 */\r\n    private String keyword;\r\n\r\n    /** 商品条码（一维码） */\r\n    @JsonProperty(\"bar_code\")\r\n    private String barCode;\r\n\r\n    /** 分类id */\r\n    @JsonProperty(\"cate_id\")\r\n    private String cateId;\r\n\r\n    /** 商品价格 */\r\n    private Double price;\r\n\r\n\r\n    /** 市场价 */\r\n    @JsonProperty(\"ot_price\")\r\n    private Double otPrice;\r\n\r\n    /** 邮费 */\r\n    private Double postage;\r\n\r\n    /** 单位名 */\r\n    @JsonProperty(\"unit_name\")\r\n    private String unitName;\r\n\r\n    /** 排序 */\r\n    private Long sort;\r\n\r\n    /** 销量 */\r\n    private Long sales;\r\n\r\n    /** 库存 */\r\n    private Long stock;\r\n\r\n    /** 状态（0：未上架，1：上架） */\r\n    @JsonProperty(\"is_show\")\r\n    private Integer isShow;\r\n\r\n    /** 是否热卖 */\r\n    @JsonProperty(\"is_hot\")\r\n    private Integer isHot;\r\n\r\n    /** 是否优惠 */\r\n    @JsonProperty(\"is_benefit\")\r\n    private Integer isBenefit;\r\n\r\n    /** 是否精品 */\r\n    @JsonProperty(\"is_best\")\r\n    private Integer isBest;\r\n\r\n    /** 是否新品 */\r\n    @JsonProperty(\"is_new\")\r\n    private Integer isNew;\r\n\r\n    /** 商品描述 */\r\n    private String description;\r\n\r\n\r\n    /** 是否包邮 */\r\n    @JsonProperty(\"is_postage\")\r\n    private Integer isPostage;\r\n\r\n\r\n    /** 获得积分 */\r\n    @JsonProperty(\"give_integral\")\r\n    private Double giveIntegral;\r\n\r\n    /** 成本价 */\r\n    private Double cost;\r\n\r\n\r\n    /** 是否优品推荐 */\r\n    @JsonProperty(\"is_good\")\r\n    private Integer isGood;\r\n\r\n    /** 是否单独分佣 */\r\n    @JsonProperty(\"is_sub\")\r\n    private Integer isSub;\r\n\r\n    /** 是否开启啊积分兑换 */\r\n    @JsonProperty(\"is_integral\")\r\n    private Integer isIntegral;\r\n\r\n    /** 虚拟销量 */\r\n    private Long ficti;\r\n\r\n\r\n    /** 运费模板ID */\r\n    @JsonProperty(\"temp_id\")\r\n    private Long tempId;\r\n\r\n    /** 规格 0单 1多 */\r\n    @JsonProperty(\"spec_type\")\r\n    private Integer specType;\r\n\r\n    private ProductFormatDto attr;\r\n\r\n    private List<FromatDetailDto> items;\r\n\r\n    private List<ProductFormatDto> attrs;\r\n\r\n\r\n\r\n\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/service/storeproduct/dto/ProductFormatDto.java",
    "content": "/**\n * Copyright (C) 2018-2022\n * All rights reserved, Designed By www.yixiang.co\n\n */\npackage co.yixiang.yshop.module.product.service.storeproduct.dto;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport lombok.*;\n\nimport java.util.Map;\n\n/**\n * @ClassName ProductFormatDTO\n * @Author hupeng <610796224@qq.com>\n * @Date 2019/10/12\n **/\n\n@Builder\n@AllArgsConstructor\n@NoArgsConstructor\n@Getter\n@Setter\npublic class ProductFormatDto {\n\n    private String sku = \"\";\n\n    @JsonProperty(\"bar_code\")\n    private String barCode = \"\";\n\n    private Double brokerage = 0d;\n\n    @JsonProperty(\"brokerage_two\")\n    private Double brokerageTwo = 0d;\n\n    private Double price = 0d;\n\n    @JsonProperty(\"ot_price\")\n    private Double otPrice = 0d;\n\n    private Double cost = 0d;\n\n    private Integer stock = 0;\n\n    private Integer integral = 0;\n\n    private String pic = \"\";\n\n    private String value1 = \"\";\n\n    private String value2 = \"\";\n\n    private Double volume = 0d;\n\n    private Double weight = 0d;\n    @JsonProperty(\"pink_price\")\n    private Double pinkPrice = 0d;\n    @JsonProperty(\"pink_stock\")\n    private Integer pinkStock = 0;\n    @JsonProperty(\"seckill_price\")\n    private Double seckillPrice = 0d;\n    @JsonProperty(\"seckill_stock\")\n    private Integer seckillStock = 0;\n\n    private Map<String, String> detail;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/service/storeproduct/dto/ProductResultDto.java",
    "content": "package co.yixiang.yshop.module.product.service.storeproduct.dto;\r\n\r\nimport lombok.Builder;\r\nimport lombok.Getter;\r\nimport lombok.Setter;\r\n\r\n/**\r\n * @ClassName 产品结果DTO\r\n * @Author hupeng <610796224@qq.com>\r\n * @Date 2020/4/24\r\n **/\r\n@Getter\r\n@Setter\r\n@Builder\r\npublic class ProductResultDto {\r\n    private Double minPrice;\r\n\r\n    private Double minOtPrice;\r\n\r\n    private Double minCost;\r\n\r\n    private Integer stock;\r\n\r\n    private Integer minIntegral;\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/service/storeproduct/dto/StoreProductDto.java",
    "content": "package co.yixiang.yshop.module.product.service.storeproduct.dto;\r\n\r\nimport com.fasterxml.jackson.annotation.JsonProperty;\r\nimport lombok.Getter;\r\nimport lombok.Setter;\r\nimport lombok.ToString;\r\n\r\nimport jakarta.validation.constraints.NotBlank;\r\nimport jakarta.validation.constraints.NotNull;\r\nimport java.util.List;\r\n\r\n\r\n/**\r\n * 商品对象DTO\r\n *\r\n * @author hupeng\r\n * @date 2020-04-23\r\n */\r\n@Getter\r\n@Setter\r\n@ToString\r\npublic class StoreProductDto\r\n{\r\n\r\n    /** 商品id */\r\n    private Long id;\r\n\r\n    /**\r\n     * 店铺id\r\n     */\r\n    private Integer shopId;\r\n\r\n    /**\r\n     * 店铺名称\r\n     */\r\n    private String shopName;\r\n\r\n    /** 商品图片 */\r\n    @NotBlank(message = \"商品图片必传\")\r\n    private String image;\r\n\r\n    /** 轮播图 */\r\n    @NotNull(message = \"轮播图不为空\")\r\n    @JsonProperty(\"slider_image\")\r\n    private List<String> sliderImage;\r\n\r\n    /** 商品名称 */\r\n    @NotBlank(message = \"商品名称不能为空\")\r\n    @JsonProperty(\"store_name\")\r\n    private String storeName;\r\n\r\n    /** 商品简介 */\r\n    @JsonProperty(\"store_info\")\r\n    private String storeInfo;\r\n\r\n    /** 关键字 */\r\n    //@NotBlank(message = \"关键字不能为空\")\r\n    private String keyword;\r\n\r\n    /** 商品条码（一维码） */\r\n    @JsonProperty(\"bar_code\")\r\n    private String barCode;\r\n\r\n    /** 分类id */\r\n    @NotNull(message = \"分类id不能为空\")\r\n    @JsonProperty(\"cate_id\")\r\n    private String cateId;\r\n\r\n    /** 商品价格 */\r\n    private Double price;\r\n\r\n    /** 市场价 */\r\n    private Double otPrice;\r\n\r\n    /** 邮费 */\r\n    private Double postage;\r\n\r\n    /** 单位名 */\r\n    @JsonProperty(\"unit_name\")\r\n    private String unitName;\r\n\r\n    /** 排序 */\r\n    private Long sort;\r\n\r\n    /** 销量 */\r\n    private Long sales;\r\n\r\n    /** 库存 */\r\n    private Long stock;\r\n\r\n    /** 状态（0：未上架，1：上架） */\r\n    @JsonProperty(\"is_show\")\r\n    private Integer isShow;\r\n\r\n    /** 是否热卖 */\r\n    @JsonProperty(\"is_hot\")\r\n    private Integer isHot;\r\n\r\n    /** 是否优惠 */\r\n    @JsonProperty(\"is_benefit\")\r\n    private Integer isBenefit;\r\n\r\n    /** 是否精品 */\r\n    @JsonProperty(\"is_best\")\r\n    private Integer isBest;\r\n\r\n    /** 是否新品 */\r\n    @JsonProperty(\"is_new\")\r\n    private Integer isNew;\r\n\r\n    /** 商品描述 */\r\n    @NotBlank(message = \"商品详情不能为空\")\r\n    private String description;\r\n\r\n\r\n    /** 是否包邮 */\r\n    @JsonProperty(\"is_postage\")\r\n    private Integer isPostage;\r\n\r\n    /** 获得积分 */\r\n    @JsonProperty(\"give_integral\")\r\n    private Double giveIntegral;\r\n\r\n    /** 成本价 */\r\n    private Double cost;\r\n\r\n\r\n    /** 是否优品推荐 */\r\n    @JsonProperty(\"is_good\")\r\n    private Integer isGood;\r\n\r\n    /** 是否单独分佣 */\r\n    @JsonProperty(\"is_sub\")\r\n    private Integer isSub;\r\n\r\n    /** 是否开启啊积分兑换 */\r\n    @JsonProperty(\"is_integral\")\r\n    private Integer isIntegral;\r\n\r\n    /** 虚拟销量 */\r\n    private Long ficti;\r\n\r\n    /** 运费模板ID */\r\n    @JsonProperty(\"temp_id\")\r\n    private Long tempId;\r\n\r\n    /** 规格 0单 1多 */\r\n    @JsonProperty(\"spec_type\")\r\n    private Integer specType;\r\n\r\n    //属性项目\r\n    private List<FromatDetailDto> items;\r\n\r\n    //sku结果集\r\n    private List<ProductFormatDto> attrs;\r\n\r\n\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/service/storeproduct/dto/YxStoreProductDto.java",
    "content": "/**\n * Copyright (C) 2018-2022\n * All rights reserved, Designed By www.yixiang.co\n\n */\npackage co.yixiang.yshop.module.product.service.storeproduct.dto;\n\nimport lombok.Data;\n\nimport java.io.Serializable;\nimport java.math.BigDecimal;\n\n/**\n* @author hupeng\n* @date 2020-05-12\n*/\n@Data\npublic class YxStoreProductDto implements Serializable {\n\n    /** 商品id */\n    private Integer id;\n\n    /** 商户Id(0为总后台管理员创建,不为0的时候是商户后台创建) */\n    private Integer merId;\n\n    /** 商品图片 */\n    private String image;\n\n    /** 轮播图 */\n    private String sliderImage;\n\n    /** 商品名称 */\n    private String storeName;\n\n    /** 商品简介 */\n    private String storeInfo;\n\n    /** 关键字 */\n    private String keyword;\n\n    /** 产品条码（一维码） */\n    private String barCode;\n\n    /** 分类id */\n    private String cateId;\n\n    /** 商品价格 */\n    private BigDecimal price;\n\n    /** 会员价格 */\n    private BigDecimal vipPrice;\n\n    /** 市场价 */\n    private BigDecimal otPrice;\n\n    /** 邮费 */\n    private BigDecimal postage;\n\n    /** 单位名 */\n    private String unitName;\n\n    /** 排序 */\n    private Integer sort;\n\n    /** 销量 */\n    private Integer sales;\n\n    /** 库存 */\n    private Integer stock;\n\n    /** 状态（0：未上架，1：上架） */\n    private Integer isShow;\n\n    /** 是否热卖 */\n    private Integer isHot;\n\n    /** 是否优惠 */\n    private Integer isBenefit;\n\n    /** 是否精品 */\n    private Integer isBest;\n\n    /** 是否新品 */\n    private Integer isNew;\n\n    /** 产品描述 */\n    private String description;\n\n    /** 添加时间 */\n    private Integer addTime;\n\n    /** 是否包邮 */\n    private Integer isPostage;\n\n    /** 是否删除 */\n    private Integer isDel;\n\n    /** 商户是否代理 0不可代理1可代理 */\n    private Integer merUse;\n\n    /** 获得积分 */\n    private BigDecimal giveIntegral;\n\n    /** 成本价 */\n    private BigDecimal cost;\n\n    /** 秒杀状态 0 未开启 1已开启 */\n    private Integer isSeckill;\n\n    /** 砍价状态 0未开启 1开启 */\n    private Integer isBargain;\n\n    /** 是否优品推荐 */\n    private Integer isGood;\n\n    /** 虚拟销量 */\n    private Integer ficti;\n\n    /** 浏览量 */\n    private Integer browse;\n\n    /** 产品二维码地址(用户小程序海报) */\n    private String codePath;\n\n    /** 淘宝京东1688类型 */\n    private String soureLink;\n\n    private Integer isIntegral;\n\n   // private YxStoreCategorySmallDto storeCategory;\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/service/storeproduct/dto/YxStoreProductRelationDto.java",
    "content": "///**\n// * Copyright (C) 2018-2022\n// * All rights reserved, Designed By www.yixiang.co\n// * 注意：\n// * 本软件为www.yixiang.co开发研制，未经购买不得使用\n// * 购买后可获得全部源代码（禁止转卖、分享、上传到码云、github等开源平台）\n// * 一经发现盗用、分享等行为，将追究法律责任，后果自负\n// */\n//package co.yixiang.yshop.module.product.service.storeproduct.dto;\n//\n//import co.yixiang.modules.product.domain.YxStoreProduct;\n//import com.fasterxml.jackson.annotation.JsonFormat;\n//import lombok.Data;\n//import org.springframework.format.annotation.DateTimeFormat;\n//\n//import java.io.Serializable;\n//import java.sql.Timestamp;\n//\n///**\n// * @author hupeng\n// * @date 2020-09-03\n// */\n//@Data\n//public class YxStoreProductRelationDto implements Serializable {\n//\n//    private Long id;\n//\n//    /** 用户ID */\n//    private Long uid;\n//\n//    private String userName;\n//\n//    /** 商品ID */\n//    private Long productId;\n//\n//    private YxStoreProduct product;\n//\n//    /** 类型(收藏(collect）、点赞(like)) */\n//    private String type;\n//\n//    /** 某种类型的商品(普通商品、秒杀商品) */\n//    private String category;\n//\n//    /** 添加时间 */\n//    @JsonFormat(pattern = \"yyyy-MM-dd HH:mm:ss\")\n//    @DateTimeFormat(pattern = \"yyyy-MM-dd HH:mm:ss\")\n//    private Timestamp createTime;\n//    @JsonFormat(pattern = \"yyyy-MM-dd HH:mm:ss\")\n//    @DateTimeFormat(pattern = \"yyyy-MM-dd HH:mm:ss\")\n//    private Timestamp updateTime;\n//\n//    private Integer isDel;\n//}\n//\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/service/storeproduct/dto/YxStoreProductReplyDto.java",
    "content": "///**\n// * Copyright (C) 2018-2022\n// * All rights reserved, Designed By www.yixiang.co\n//\n// */\n//package co.yixiang.yshop.module.product.service.storeproduct.dto;\n//\n//import co.yixiang.modules.user.service.dto.YxUserSmallDto;\n//import lombok.Data;\n//\n//import java.io.Serializable;\n//import java.util.Date;\n//\n///**\n//* @author hupeng\n//* @date 2020-05-12\n//*/\n//@Data\n//public class YxStoreProductReplyDto implements Serializable {\n//\n//    // 评论ID\n//    private Long id;\n//\n//    // 用户ID\n//    private Long uid;\n//\n//    private YxUserSmallDto user;\n//\n//    // 订单ID\n//    private Long oid;\n//\n//    // 唯一id\n//    private String unique;\n//\n//    // 产品id\n//    private Long productId;\n//\n//    private YxStoreProductSmallDto storeProduct;\n//\n//\n//    // 某种商品类型(普通商品、秒杀商品）\n//    private String replyType;\n//\n//    // 商品分数\n//    private Integer productScore;\n//\n//    // 服务分数\n//    private Integer serviceScore;\n//\n//    // 评论内容\n//    private String comment;\n//\n//    // 评论图片\n//    private String pics;\n//\n//    // 评论时间\n//    private Date createTime;\n//\n//    // 管理员回复内容\n//    private String merchantReplyContent;\n//\n//    // 管理员回复时间\n//    private Date merchantReplyTime;\n//\n//    // 0未回复1已回复\n//    private Integer isReply;\n//}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/service/storeproduct/dto/YxStoreProductRuleDto.java",
    "content": "/**\n* Copyright (C) 2018-2022\n* All rights reserved, Designed By www.yixiang.co\n* 注意：\n* 本软件为www.yixiang.co开发研制，未经购买不得使用\n* 购买后可获得全部源代码（禁止转卖、分享、上传到码云、github等开源平台）\n* 一经发现盗用、分享等行为，将追究法律责任，后果自负\n*/\npackage co.yixiang.yshop.module.product.service.storeproduct.dto;\n\nimport com.alibaba.fastjson.JSONArray;\nimport lombok.Data;\n\nimport java.io.Serializable;\nimport java.sql.Timestamp;\n\n/**\n* @author hupeng\n* @date 2020-06-28\n*/\n@Data\npublic class YxStoreProductRuleDto implements Serializable {\n\n    private Integer id;\n\n    /** 规格名称 */\n    private String ruleName;\n\n    /** 规格值 */\n    private JSONArray ruleValue;\n\n    private Timestamp createTime;\n\n    private Timestamp updateTime;\n\n    private Integer isDel;\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/service/storeproduct/dto/YxStoreProductSmallDto.java",
    "content": "/**\n * Copyright (C) 2018-2022\n * All rights reserved, Designed By www.yixiang.co\n\n */\npackage co.yixiang.yshop.module.product.service.storeproduct.dto;\n\nimport lombok.Data;\n\nimport java.io.Serializable;\n\n\n/**\n* @author hupeng\n* @date 2019-10-04\n*/\n@Data\npublic class YxStoreProductSmallDto implements Serializable {\n\n    // 商品id\n    private Integer id;\n\n    // 商品图片\n    private String image;\n\n\n    // 商品名称\n    private String storeName;\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/service/storeproductattr/AppStoreProductAttrService.java",
    "content": "package co.yixiang.yshop.module.product.service.storeproductattr;\n\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproductattr.StoreProductAttrDO;\nimport co.yixiang.yshop.module.product.service.storeproduct.dto.FromatDetailDto;\nimport co.yixiang.yshop.module.product.service.storeproduct.dto.ProductFormatDto;\nimport com.baomidou.mybatisplus.extension.service.IService;\n\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * 商品属性 Service 接口\n *\n * @author yshop\n */\npublic interface AppStoreProductAttrService extends IService<StoreProductAttrDO>  {\n\n    /**\n     * 获取商品sku属性\n     * @param productId 商品id\n     * @return map\n     */\n    Map<String, Object> getProductAttrDetail(long productId);\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/service/storeproductattr/AppStoreProductAttrServiceImpl.java",
    "content": "package co.yixiang.yshop.module.product.service.storeproductattr;\n\nimport co.yixiang.yshop.module.product.controller.app.product.vo.AppStoreProductAttrQueryVo;\nimport co.yixiang.yshop.module.product.convert.storeproductattr.StoreProductAttrConvert;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproductattr.StoreProductAttrDO;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproductattrvalue.StoreProductAttrValueDO;\nimport co.yixiang.yshop.module.product.dal.mysql.storeproductattr.StoreProductAttrMapper;\nimport co.yixiang.yshop.module.product.service.storeproduct.dto.AttrValueDto;\nimport co.yixiang.yshop.module.product.service.storeproductattrvalue.StoreProductAttrValueService;\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\nimport java.util.*;\nimport java.util.stream.Collectors;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\n\n/**\n * 商品属性 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class AppStoreProductAttrServiceImpl extends ServiceImpl<StoreProductAttrMapper,StoreProductAttrDO> implements AppStoreProductAttrService {\n\n    @Resource\n    private StoreProductAttrValueService storeProductAttrValueService;\n\n    /**\n     * 获取商品sku属性\n     * @param productId 商品id\n     * @return map\n     */\n    @Override\n    public Map<String, Object> getProductAttrDetail(long productId) {\n        List<StoreProductAttrDO>  storeProductAttrs = this.baseMapper\n                .selectList(Wrappers.<StoreProductAttrDO>lambdaQuery()\n                        .eq(StoreProductAttrDO::getProductId,productId)\n                        .orderByAsc(StoreProductAttrDO::getAttrValues));\n\n        List<StoreProductAttrValueDO>  productAttrValues = storeProductAttrValueService\n                .list(Wrappers.<StoreProductAttrValueDO>lambdaQuery()\n                        .eq(StoreProductAttrValueDO::getProductId,productId));\n\n\n        Map<String, StoreProductAttrValueDO> map = productAttrValues.stream()\n                .collect(Collectors.toMap(StoreProductAttrValueDO::getSku, p -> p));\n\n        List<AppStoreProductAttrQueryVo> yxStoreProductAttrQueryVoList = new ArrayList<>();\n\n        for (StoreProductAttrDO attr : storeProductAttrs) {\n            List<String> stringList = Arrays.asList(attr.getAttrValues().split(\",\"));\n            List<AttrValueDto> attrValueDTOS = new ArrayList<>();\n            for (String str : stringList) {\n                AttrValueDto attrValueDTO = new AttrValueDto();\n                attrValueDTO.setAttr(str);\n                attrValueDTOS.add(attrValueDTO);\n            }\n\n            AppStoreProductAttrQueryVo attrQueryVo = StoreProductAttrConvert.INSTANCE.convert(attr);\n            attrQueryVo.setAttrValue(attrValueDTOS);\n            attrQueryVo.setAttrValueArr(stringList);\n\n            yxStoreProductAttrQueryVoList.add(attrQueryVo);\n        }\n\n        Map<String, Object> returnMap = new LinkedHashMap<>(2);\n        returnMap.put(\"productAttr\",yxStoreProductAttrQueryVoList);\n        returnMap.put(\"productValue\",map);\n\n        return returnMap;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/service/storeproductattr/StoreProductAttrService.java",
    "content": "package co.yixiang.yshop.module.product.service.storeproductattr;\n\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproductattr.StoreProductAttrDO;\nimport co.yixiang.yshop.module.product.service.storeproduct.dto.FromatDetailDto;\nimport co.yixiang.yshop.module.product.service.storeproduct.dto.ProductFormatDto;\nimport com.baomidou.mybatisplus.extension.service.IService;\n\nimport java.util.Collection;\nimport java.util.List;\n\n/**\n * 商品属性 Service 接口\n *\n * @author yshop\n */\npublic interface StoreProductAttrService extends IService<StoreProductAttrDO>  {\n\n    /**\n     * 删除商品属性\n     *\n     * @param id 编号\n     */\n    void deleteStoreProductAttr(Long id);\n\n    /**\n     * 获得商品属性\n     *\n     * @param id 编号\n     * @return 商品属性\n     */\n    StoreProductAttrDO getStoreProductAttr(Long id);\n\n    /**\n     * 获得商品属性列表\n     *\n     * @param ids 编号\n     * @return 商品属性列表\n     */\n    List<StoreProductAttrDO> getStoreProductAttrList(Collection<Long> ids);\n\n\n    /**\n     * 新增商品属性\n     * @param items attr\n     * @param attrs value\n     * @param productId 商品id\n     */\n    void insertYxStoreProductAttr(List<FromatDetailDto> items, List<ProductFormatDto> attrs,\n                                  Long productId);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/service/storeproductattr/StoreProductAttrServiceImpl.java",
    "content": "package co.yixiang.yshop.module.product.service.storeproductattr;\n\nimport cn.hutool.core.util.IdUtil;\nimport cn.hutool.core.util.ObjectUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.util.string.StrUtils;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproductattr.StoreProductAttrDO;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproductattrvalue.StoreProductAttrValueDO;\nimport co.yixiang.yshop.module.product.dal.mysql.storeproductattr.StoreProductAttrMapper;\nimport co.yixiang.yshop.module.product.dal.mysql.storeproductattrvalue.StoreProductAttrValueMapper;\nimport co.yixiang.yshop.module.product.service.storeproduct.dto.FromatDetailDto;\nimport co.yixiang.yshop.module.product.service.storeproduct.dto.ProductFormatDto;\nimport co.yixiang.yshop.module.product.service.storeproductattrresult.StoreProductAttrResultService;\nimport co.yixiang.yshop.module.product.service.storeproductattrvalue.StoreProductAttrValueService;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\nimport java.math.BigDecimal;\nimport java.util.*;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.product.enums.ErrorCodeConstants.*;\n\n/**\n * 商品属性 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class StoreProductAttrServiceImpl extends ServiceImpl<StoreProductAttrMapper,StoreProductAttrDO> implements StoreProductAttrService {\n\n    @Resource\n    private StoreProductAttrMapper storeProductAttrMapper;\n    @Resource\n    private StoreProductAttrValueService storeProductAttrValueService;\n    @Resource\n    private StoreProductAttrValueMapper storeProductAttrValueMapper;\n    @Resource\n    private StoreProductAttrResultService storeProductAttrResultService;\n\n\n    @Override\n    public void deleteStoreProductAttr(Long id) {\n        // 校验存在\n        validateStoreProductAttrExists(id);\n        // 删除\n        storeProductAttrMapper.deleteById(id);\n    }\n\n    private void validateStoreProductAttrExists(Long id) {\n        if (storeProductAttrMapper.selectById(id) == null) {\n            throw exception(STORE_PRODUCT_ATTR_NOT_EXISTS);\n        }\n    }\n\n    @Override\n    public StoreProductAttrDO getStoreProductAttr(Long id) {\n        return storeProductAttrMapper.selectById(id);\n    }\n\n    @Override\n    public List<StoreProductAttrDO> getStoreProductAttrList(Collection<Long> ids) {\n        return storeProductAttrMapper.selectBatchIds(ids);\n    }\n\n\n    /**\n     * 新增商品属性\n     * @param items attr\n     * @param attrs value\n     * @param productId 商品id\n     */\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void insertYxStoreProductAttr(List<FromatDetailDto> items, List<ProductFormatDto> attrs,\n                                         Long productId)\n    {\n        List<StoreProductAttrDO> attrGroup = new ArrayList<>();\n        for (FromatDetailDto fromatDetailDto : items) {\n            StoreProductAttrDO  storeProductAttr = StoreProductAttrDO.builder()\n                    .productId(productId)\n                    .attrName(fromatDetailDto.getValue())\n                    .attrValues(StrUtil.join(\",\",fromatDetailDto.getDetail()))\n                    .build();\n\n            attrGroup.add(storeProductAttr);\n        }\n\n        /*int count = storeProductAttrValueService.count(Wrappers.<YxStoreProductAttrValue>lambdaQuery().eq(YxStoreProductAttrValue::getProductId, productId));\n        if (count > 0 ) {\n            throw new BadRequestException(\"该产品已被添加到其他活动,禁止操作!\");\n        }*/\n\n        List<StoreProductAttrValueDO> valueGroup = new ArrayList<>();\n        for (ProductFormatDto productFormatDto : attrs) {\n\n//            if(productFormatDto.getPinkStock()>productFormatDto.getStock() || productFormatDto.getSeckillStock()>productFormatDto.getStock()){\n//                throw new BadRequestException(\"活动商品库存不能大于原有商品库存\");\n//            }\n            List<String> stringList = new ArrayList<>(productFormatDto.getDetail().values());\n            stringList =  StrUtils.compareTo(stringList);\n            StoreProductAttrValueDO oldAttrValue = storeProductAttrValueService.getOne(new LambdaQueryWrapper<StoreProductAttrValueDO>()\n                    .eq(StoreProductAttrValueDO::getSku, productFormatDto.getSku())\n                    .eq(StoreProductAttrValueDO::getProductId, productId));\n\n            String unique = IdUtil.simpleUUID();\n            if (Objects.nonNull(oldAttrValue)) {\n                unique = oldAttrValue.getUnique();\n            }\n\n            StoreProductAttrValueDO yxStoreProductAttrValue = StoreProductAttrValueDO.builder()\n                    .id(Objects.isNull(oldAttrValue) ? null : oldAttrValue.getId())\n                    .productId(productId)\n                    .sku(StrUtil.join(\",\",stringList))\n                    .price(BigDecimal.valueOf(productFormatDto.getPrice()))\n                    .cost(BigDecimal.valueOf(productFormatDto.getCost()))\n                    .otPrice(BigDecimal.valueOf(productFormatDto.getOtPrice()))\n                    .unique(unique)\n                    .image(productFormatDto.getPic())\n                    .barCode(productFormatDto.getBarCode())\n                    .weight(BigDecimal.valueOf(productFormatDto.getWeight()))\n                    .volume(BigDecimal.valueOf(productFormatDto.getVolume()))\n                    .brokerage(BigDecimal.valueOf(productFormatDto.getBrokerage()))\n                    .brokerageTwo(BigDecimal.valueOf(productFormatDto.getBrokerageTwo()))\n                    .stock(productFormatDto.getStock())\n                    .integral(productFormatDto.getIntegral())\n                    .pinkPrice(BigDecimal.valueOf(productFormatDto.getPinkPrice()==null?0:productFormatDto.getPinkPrice()))\n                    .seckillPrice(BigDecimal.valueOf(productFormatDto.getSeckillPrice()==null?0:productFormatDto.getSeckillPrice()))\n                    .pinkStock(productFormatDto.getPinkStock()==null?0:productFormatDto.getPinkStock())\n                    .seckillStock(productFormatDto.getSeckillStock()==null?0:productFormatDto.getSeckillStock())\n                    .build();\n\n            valueGroup.add(yxStoreProductAttrValue);\n        }\n\n        if(attrGroup.isEmpty() || valueGroup.isEmpty()){\n            throw exception(STORE_PRODUCT_ATTR_NEED);\n        }\n\n        //清理属性\n        this.clearProductAttr(productId);\n\n        //批量添加\n        this.saveBatch(attrGroup);\n        storeProductAttrValueService.saveBatch(valueGroup);\n\n        Map<String,Object> map = new LinkedHashMap<>();\n        map.put(\"attr\",items);\n        map.put(\"value\",attrs);\n\n        storeProductAttrResultService.insertYxStoreProductAttrResult(map,productId);\n    }\n\n    /**\n     * 删除YxStoreProductAttrValue表的属性\n     * @param productId 商品id\n     */\n    private void clearProductAttr(Long productId) {\n        if(ObjectUtil.isNull(productId)) {\n            throw exception(STORE_PRODUCT_NOT_EXISTS);\n        }\n\n        storeProductAttrMapper.delete(Wrappers.<StoreProductAttrDO>lambdaQuery()\n                .eq(StoreProductAttrDO::getProductId,productId));\n        storeProductAttrValueMapper.delete(Wrappers.<StoreProductAttrValueDO>lambdaQuery()\n                .eq(StoreProductAttrValueDO::getProductId,productId));\n\n    }\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/service/storeproductattrresult/StoreProductAttrResultService.java",
    "content": "package co.yixiang.yshop.module.product.service.storeproductattrresult;\n\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproductattrresult.StoreProductAttrResultDO;\nimport com.baomidou.mybatisplus.extension.service.IService;\n\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * 商品属性详情 Service 接口\n *\n * @author yshop\n */\npublic interface StoreProductAttrResultService extends IService<StoreProductAttrResultDO> {\n\n    /**\n     * 删除商品属性详情\n     *\n     * @param id 编号\n     */\n    void deleteStoreProductAttrResult(Long id);\n\n    /**\n     * 获得商品属性详情\n     *\n     * @param id 编号\n     * @return 商品属性详情\n     */\n    StoreProductAttrResultDO getStoreProductAttrResult(Long id);\n\n    /**\n     * 获得商品属性详情列表\n     *\n     * @param ids 编号\n     * @return 商品属性详情列表\n     */\n    List<StoreProductAttrResultDO> getStoreProductAttrResultList(Collection<Long> ids);\n\n\n    /**\n     * 新增商品属性详情\n     * @param map map\n     * @param productId 商品id\n     */\n    void insertYxStoreProductAttrResult(Map<String, Object> map, Long productId);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/service/storeproductattrresult/StoreProductAttrResultServiceImpl.java",
    "content": "package co.yixiang.yshop.module.product.service.storeproductattrresult;\n\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproductattrresult.StoreProductAttrResultDO;\nimport co.yixiang.yshop.module.product.dal.mysql.storeproductattrresult.StoreProductAttrResultMapper;\nimport com.alibaba.fastjson.JSON;\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\nimport java.util.Collection;\nimport java.util.Date;\nimport java.util.List;\nimport java.util.Map;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.product.enums.ErrorCodeConstants.STORE_PRODUCT_ATTR_RESULT_NOT_EXISTS;\n\n/**\n * 商品属性详情 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class StoreProductAttrResultServiceImpl extends ServiceImpl<StoreProductAttrResultMapper, StoreProductAttrResultDO> implements StoreProductAttrResultService {\n\n    @Resource\n    private StoreProductAttrResultMapper storeProductAttrResultMapper;\n\n\n    @Override\n    public void deleteStoreProductAttrResult(Long id) {\n        // 校验存在\n        validateStoreProductAttrResultExists(id);\n        // 删除\n        storeProductAttrResultMapper.deleteById(id);\n    }\n\n    private void validateStoreProductAttrResultExists(Long id) {\n        if (storeProductAttrResultMapper.selectById(id) == null) {\n            throw exception(STORE_PRODUCT_ATTR_RESULT_NOT_EXISTS);\n        }\n    }\n\n    @Override\n    public StoreProductAttrResultDO getStoreProductAttrResult(Long id) {\n        return storeProductAttrResultMapper.selectById(id);\n    }\n\n    @Override\n    public List<StoreProductAttrResultDO> getStoreProductAttrResultList(Collection<Long> ids) {\n        return storeProductAttrResultMapper.selectBatchIds(ids);\n    }\n\n\n    /**\n     * 新增商品属性详情\n     * @param map map\n     * @param productId 商品id\n     */\n    @Override\n    public void insertYxStoreProductAttrResult(Map<String, Object> map, Long productId)\n    {\n        StoreProductAttrResultDO yxStoreProductAttrResult = new StoreProductAttrResultDO();\n        yxStoreProductAttrResult.setProductId(productId);\n        yxStoreProductAttrResult.setResult(JSON.toJSONString(map));\n        yxStoreProductAttrResult.setChangeTime(new Date());\n\n        long count = this.count(Wrappers.<StoreProductAttrResultDO>lambdaQuery()\n                .eq(StoreProductAttrResultDO::getProductId,productId));\n        if(count > 0) {\n            this.remove(Wrappers.<StoreProductAttrResultDO>lambdaQuery()\n                    .eq(StoreProductAttrResultDO::getProductId,productId));\n        }\n\n        this.save(yxStoreProductAttrResult);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/service/storeproductattrvalue/StoreProductAttrValueService.java",
    "content": "package co.yixiang.yshop.module.product.service.storeproductattrvalue;\n\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproductattrvalue.StoreProductAttrValueDO;\nimport com.baomidou.mybatisplus.extension.service.IService;\n\nimport java.util.Collection;\nimport java.util.List;\n\n/**\n * 商品属性值 Service 接口\n *\n * @author yshop\n */\npublic interface StoreProductAttrValueService extends IService<StoreProductAttrValueDO> {\n\n\n    /**\n     * 删除商品属性值\n     *\n     * @param id 编号\n     */\n    void deleteStoreProductAttrValue(Long id);\n\n    /**\n     * 获得商品属性值\n     *\n     * @param id 编号\n     * @return 商品属性值\n     */\n    StoreProductAttrValueDO getStoreProductAttrValue(Long id);\n\n    /**\n     * 获得商品属性值列表\n     *\n     * @param ids 编号\n     * @return 商品属性值列表\n     */\n    List<StoreProductAttrValueDO> getStoreProductAttrValueList(Collection<Long> ids);\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/service/storeproductattrvalue/StoreProductAttrValueServiceImpl.java",
    "content": "package co.yixiang.yshop.module.product.service.storeproductattrvalue;\n\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproductattrvalue.StoreProductAttrValueDO;\nimport co.yixiang.yshop.module.product.dal.mysql.storeproductattrvalue.StoreProductAttrValueMapper;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\nimport java.util.Collection;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.product.enums.ErrorCodeConstants.STORE_PRODUCT_ATTR_VALUE_NOT_EXISTS;\n\n/**\n * 商品属性值 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class StoreProductAttrValueServiceImpl  extends ServiceImpl<StoreProductAttrValueMapper,StoreProductAttrValueDO> implements StoreProductAttrValueService {\n\n    @Resource\n    private StoreProductAttrValueMapper storeProductAttrValueMapper;\n\n\n    @Override\n    public void deleteStoreProductAttrValue(Long id) {\n        // 校验存在\n        validateStoreProductAttrValueExists(id);\n        // 删除\n        storeProductAttrValueMapper.deleteById(id);\n    }\n\n    private void validateStoreProductAttrValueExists(Long id) {\n        if (storeProductAttrValueMapper.selectById(id) == null) {\n            throw exception(STORE_PRODUCT_ATTR_VALUE_NOT_EXISTS);\n        }\n    }\n\n    @Override\n    public StoreProductAttrValueDO getStoreProductAttrValue(Long id) {\n        return storeProductAttrValueMapper.selectById(id);\n    }\n\n    @Override\n    public List<StoreProductAttrValueDO> getStoreProductAttrValueList(Collection<Long> ids) {\n        return storeProductAttrValueMapper.selectBatchIds(ids);\n    }\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/service/storeproductreply/AppStoreProductReplyService.java",
    "content": "package co.yixiang.yshop.module.product.service.storeproductreply;\n\nimport co.yixiang.yshop.module.product.controller.app.product.vo.AppReplyCountVo;\nimport co.yixiang.yshop.module.product.controller.app.product.vo.AppStoreProductReplyQueryVo;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproductreply.StoreProductReplyDO;\nimport com.baomidou.mybatisplus.extension.service.IService;\n\nimport java.util.List;\n\n/**\n * 评论 Service 接口\n *\n * @author yshop\n */\npublic interface AppStoreProductReplyService extends IService<StoreProductReplyDO> {\n\n    /**\n     * 获取评价数量\n     * @param productId\n     * @return\n     */\n    Long productReplyCount(long productId);\n\n    /**\n     * 获取单条评价\n     * @param productId 商品di\n     * @return YxStoreProductReplyQueryVo\n     */\n    AppStoreProductReplyQueryVo getReply(long productId);\n\n    /**\n     * 好评比例\n     * @param productId\n     * @return\n     */\n    String replyPer(long productId);\n\n    /**\n     * 返回当前商品评价数量\n     * @param unique\n     * @return\n     */\n    Long replyCount(String unique);\n\n    /**\n     * 获取评价列表\n     * @param productId 商品id\n     * @param type 0-全部 1-好评 2-中评 3-差评\n     * @param page page\n     * @param limit limit\n     * @return list\n     */\n    List<AppStoreProductReplyQueryVo> getReplyList(long productId, int type, int page, int limit);\n\n    /**\n     * 评价数据\n     * @param productId 商品id\n     * @return ReplyCountVO\n     */\n    AppReplyCountVo getReplyCount(long productId);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/service/storeproductreply/AppStoreProductReplyServiceImpl.java",
    "content": "package co.yixiang.yshop.module.product.service.storeproductreply;\n\nimport cn.hutool.core.util.NumberUtil;\nimport cn.hutool.core.util.ObjectUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.constant.ShopConstants;\nimport co.yixiang.yshop.module.product.controller.app.cart.vo.AppStoreCartQueryVo;\nimport co.yixiang.yshop.module.product.controller.app.product.vo.AppReplyCountVo;\nimport co.yixiang.yshop.module.product.controller.app.product.vo.AppStoreProductReplyQueryVo;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproductreply.StoreProductReplyDO;\nimport co.yixiang.yshop.module.product.dal.mysql.storeproductreply.StoreProductReplyMapper;\nimport co.yixiang.yshop.module.product.enums.ProductConstants;\nimport co.yixiang.yshop.module.product.enums.product.ScoreEnum;\nimport com.alibaba.fastjson.JSONObject;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport org.springframework.beans.BeanUtils;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\nimport java.math.BigDecimal;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\n/**\n * 评论 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class AppStoreProductReplyServiceImpl extends ServiceImpl<StoreProductReplyMapper, StoreProductReplyDO> implements AppStoreProductReplyService {\n\n    @Resource\n    private StoreProductReplyMapper storeProductReplyMapper;\n\n    /**\n     * 获取评价数量\n     * @param productId\n     * @return\n     */\n    @Override\n    public Long productReplyCount(long productId) {\n\n        return this.baseMapper.selectCount(Wrappers.<StoreProductReplyDO>lambdaQuery()\n                .eq(StoreProductReplyDO::getProductId,productId));\n\n    }\n\n    /**\n     * 获取单条评价\n     * @param productId 商品di\n     * @return YxStoreProductReplyQueryVo\n     */\n    @Override\n    public AppStoreProductReplyQueryVo getReply(long productId) {\n        AppStoreProductReplyQueryVo vo = this.baseMapper.getReply(productId);\n        if(ObjectUtil.isNotNull(vo)){\n            return handleReply(vo);\n        }\n        return null;\n    }\n\n    /**\n     * 好评比例\n     * @param productId 商品id\n     * @return %\n     */\n    @Override\n    public String replyPer(long productId) {\n        LambdaQueryWrapper<StoreProductReplyDO> wrapper = new LambdaQueryWrapper<>();\n        wrapper.eq(StoreProductReplyDO::getProductId,productId)\n                .eq(StoreProductReplyDO::getProductScore, ScoreEnum.DEFAULT_5.getValue());\n        Long productScoreCount = this.baseMapper.selectCount(wrapper);\n        Long count = productReplyCount(productId);\n        if(count > 0){\n            return \"\"+NumberUtil.round(NumberUtil.mul(NumberUtil.div(productScoreCount,count),100),2);\n        }\n\n        return ShopConstants.YSHOP_ZERO;\n    }\n\n\n    /**\n     * 返回当前商品评价数量\n     * @param unique\n     * @return\n     */\n    @Override\n    public Long replyCount(String unique) {\n        LambdaQueryWrapper<StoreProductReplyDO> wrapper = new LambdaQueryWrapper<>();\n        wrapper.eq(StoreProductReplyDO::getUnique,unique);\n        return this.baseMapper.selectCount(wrapper);\n    }\n\n    /**\n     * 获取评价列表\n     * @param productId 商品id\n     * @param type 0-全部 1-好评 2-中评 3-差评\n     * @param page page\n     * @param limit limit\n     * @return list\n     */\n    @Override\n    public List<AppStoreProductReplyQueryVo> getReplyList(long productId, int type, int page, int limit) {\n        List<AppStoreProductReplyQueryVo> newList = new ArrayList<>();\n        Page<StoreProductReplyDO> pageModel = new Page<>(page, limit);\n        List<AppStoreProductReplyQueryVo> list = this.baseMapper\n                .selectReplyList(pageModel,productId,type);\n        List<AppStoreProductReplyQueryVo> list1 = list.stream().map(i ->{\n            AppStoreProductReplyQueryVo vo = new AppStoreProductReplyQueryVo();\n            BeanUtils.copyProperties(i,vo);\n            if(i.getPictures().contains(\",\")){\n                vo.setPics(i.getPictures().split(\",\"));\n            }\n            return vo;\n        }).collect(Collectors.toList());\n        for (AppStoreProductReplyQueryVo queryVo : list1) {\n            newList.add(handleReply(queryVo));\n        }\n        return newList;\n    }\n\n\n    /**\n     * 评价数据\n     * @param productId 商品id\n     * @return ReplyCountVO\n     */\n    @Override\n    public AppReplyCountVo getReplyCount(long productId) {\n        Long sumCount = productReplyCount(productId);\n\n        if(sumCount == 0) {\n            return new AppReplyCountVo();\n        }\n\n        //好评\n        Long goodCount = this.baseMapper.selectCount(Wrappers.<StoreProductReplyDO>lambdaQuery()\n                .eq(StoreProductReplyDO::getProductId,productId)\n                .eq(StoreProductReplyDO::getProductScore,ScoreEnum.DEFAULT_5.getValue()));\n\n        //中评\n        Long inCount = this.baseMapper.selectCount(Wrappers.<StoreProductReplyDO>lambdaQuery()\n                .eq(StoreProductReplyDO::getProductId,productId)\n                .lt(StoreProductReplyDO::getProductScore,ScoreEnum.DEFAULT_5.getValue())\n                .gt(StoreProductReplyDO::getProductScore,ScoreEnum.DEFAULT_2.getValue()));\n\n        //差评\n        Long poorCount = this.baseMapper.selectCount(Wrappers.<StoreProductReplyDO>lambdaQuery()\n                .eq(StoreProductReplyDO::getProductId,productId)\n                .lt(StoreProductReplyDO::getProductScore,ScoreEnum.DEFAULT_2.getValue()));\n\n        //好评率\n        String replyChance = \"\"+NumberUtil.round(NumberUtil.mul(NumberUtil.div(goodCount,sumCount),100),2);\n        String replyStar = \"\"+NumberUtil.round(NumberUtil.mul(NumberUtil.div(goodCount,sumCount),5),2);\n\n        return AppReplyCountVo.builder()\n                .sumCount(sumCount)\n                .goodCount(goodCount)\n                .inCount(inCount)\n                .poorCount(poorCount)\n                .replyChance(replyChance)\n                .replySstar(replyStar)\n                .build();\n\n    }\n\n    /**\n     * 处理评价\n     * @param replyQueryVo replyQueryVo\n     * @return YxStoreProductReplyQueryVo\n     */\n    private AppStoreProductReplyQueryVo handleReply(AppStoreProductReplyQueryVo replyQueryVo) {\n        AppStoreCartQueryVo cartInfo = JSONObject.parseObject(replyQueryVo.getCartInfo()\n                ,AppStoreCartQueryVo.class);\n        if(ObjectUtil.isNotNull(cartInfo)){\n            if(ObjectUtil.isNotNull(cartInfo.getProductInfo())){\n                if(ObjectUtil.isNotNull(cartInfo.getProductInfo().getAttrInfo())){\n                    replyQueryVo.setSku(cartInfo.getProductInfo().getAttrInfo().getSku());\n                }\n            }\n        }\n\n        BigDecimal star = NumberUtil.add(replyQueryVo.getProductScore(),\n                replyQueryVo.getServiceScore());\n\n        star = NumberUtil.div(star,2);\n\n        replyQueryVo.setStar(String.valueOf(star.intValue()));\n\n        if(StrUtil.isEmpty(replyQueryVo.getComment())){\n            replyQueryVo.setComment(ProductConstants.NO_COMMENT_CONTENT);\n        }\n\n        return replyQueryVo;\n    }\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/service/storeproductreply/StoreProductReplyService.java",
    "content": "package co.yixiang.yshop.module.product.service.storeproductreply;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.product.controller.admin.storeproductreply.vo.StoreProductReplyPageReqVO;\nimport co.yixiang.yshop.module.product.controller.admin.storeproductreply.vo.StoreProductReplyUpdateReqVO;\nimport co.yixiang.yshop.module.product.controller.app.product.vo.AppStoreProductReplyQueryVo;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproductreply.StoreProductReplyDO;\n\nimport jakarta.validation.Valid;\n\n/**\n * 评论 Service 接口\n *\n * @author yshop\n */\npublic interface StoreProductReplyService {\n\n\n    /**\n     * 更新评论\n     *\n     * @param updateReqVO 更新信息\n     */\n    void updateStoreProductReply(@Valid StoreProductReplyUpdateReqVO updateReqVO);\n\n    /**\n     * 删除评论\n     *\n     * @param id 编号\n     */\n    void deleteStoreProductReply(Long id);\n\n    /**\n     * 获得评论\n     *\n     * @param id 编号\n     * @return 评论\n     */\n    StoreProductReplyDO getStoreProductReply(Long id);\n\n\n    /**\n     * 获得评论分页\n     *\n     * @param pageReqVO 分页查询\n     * @return 评论分页\n     */\n    PageResult<AppStoreProductReplyQueryVo> getStoreProductReplyPage(StoreProductReplyPageReqVO pageReqVO);\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/service/storeproductreply/StoreProductReplyServiceImpl.java",
    "content": "package co.yixiang.yshop.module.product.service.storeproductreply;\n\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.product.controller.app.product.vo.AppStoreProductReplyQueryVo;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproduct.StoreProductDO;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport org.springframework.stereotype.Service;\nimport jakarta.annotation.Resource;\nimport org.springframework.validation.annotation.Validated;\n\nimport java.util.*;\nimport co.yixiang.yshop.module.product.controller.admin.storeproductreply.vo.*;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproductreply.StoreProductReplyDO;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\nimport co.yixiang.yshop.module.product.convert.storeproductreply.StoreProductReplyConvert;\nimport co.yixiang.yshop.module.product.dal.mysql.storeproductreply.StoreProductReplyMapper;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.product.enums.ErrorCodeConstants.*;\n\n/**\n * 评论 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class StoreProductReplyServiceImpl implements StoreProductReplyService {\n\n    @Resource\n    private StoreProductReplyMapper storeProductReplyMapper;\n\n\n    @Override\n    public void updateStoreProductReply(StoreProductReplyUpdateReqVO updateReqVO) {\n        // 校验存在\n        validateStoreProductReplyExists(updateReqVO.getId());\n        // 更新\n        StoreProductReplyDO updateObj = StoreProductReplyConvert.INSTANCE.convert(updateReqVO);\n        storeProductReplyMapper.updateById(updateObj);\n    }\n\n    @Override\n    public void deleteStoreProductReply(Long id) {\n        // 校验存在\n        validateStoreProductReplyExists(id);\n        // 删除\n        storeProductReplyMapper.deleteById(id);\n    }\n\n    private void validateStoreProductReplyExists(Long id) {\n        if (storeProductReplyMapper.selectById(id) == null) {\n            throw exception(STORE_PRODUCT_REPLY_NOT_EXISTS);\n        }\n    }\n\n    @Override\n    public StoreProductReplyDO getStoreProductReply(Long id) {\n        return storeProductReplyMapper.selectById(id);\n    }\n\n    @Override\n    public PageResult<AppStoreProductReplyQueryVo> getStoreProductReplyPage(StoreProductReplyPageReqVO pageReqVO) {\n                Page<StoreProductReplyDO> pageModel = new Page<>(pageReqVO.getPageNo(), pageReqVO.getPageSize());\n        List<AppStoreProductReplyQueryVo> list = storeProductReplyMapper\n                .allReplyList(pageModel,pageReqVO.getNickname());\n        return new PageResult<>(list, storeProductReplyMapper.allReplyListCount(pageReqVO.getNickname()));\n    }\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/service/storeproductrule/StoreProductRuleService.java",
    "content": "package co.yixiang.yshop.module.product.service.storeproductrule;\n\nimport java.util.*;\nimport jakarta.validation.*;\nimport co.yixiang.yshop.module.product.controller.admin.storeproductrule.vo.*;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproductrule.StoreProductRuleDO;\n\n/**\n * 商品规则值(规格) Service 接口\n *\n * @author yshop\n */\npublic interface StoreProductRuleService {\n\n    /**\n     * 创建商品规则值(规格)\n     *\n     * @param createReqVO 创建信息\n     * @return 编号\n     */\n    Integer createStoreProductRule(@Valid StoreProductRuleCreateReqVO createReqVO);\n\n    /**\n     * 更新商品规则值(规格)\n     *\n     * @param updateReqVO 更新信息\n     */\n    void updateStoreProductRule(@Valid StoreProductRuleUpdateReqVO updateReqVO);\n\n    /**\n     * 删除商品规则值(规格)\n     *\n     * @param id 编号\n     */\n    void deleteStoreProductRule(Integer id);\n\n    /**\n     * 获得商品规则值(规格)\n     *\n     * @param id 编号\n     * @return 商品规则值(规格)\n     */\n    StoreProductRuleDO getStoreProductRule(Integer id);\n\n    /**\n     * 获得商品规则值(规格)列表\n     *\n     * @param ids 编号\n     * @return 商品规则值(规格)列表\n     */\n    List<StoreProductRuleDO> getStoreProductRuleList(Collection<Integer> ids);\n\n    /**\n     * 获得商品规则值(规格)分页\n     *\n     * @param pageReqVO 分页查询\n     * @return 商品规则值(规格)分页\n     */\n    PageResult<StoreProductRuleDO> getStoreProductRulePage(StoreProductRulePageReqVO pageReqVO);\n\n    /**\n     * 获得商品规则值(规格)列表, 用于 Excel 导出\n     *\n     * @param exportReqVO 查询条件\n     * @return 商品规则值(规格)列表\n     */\n    List<StoreProductRuleDO> getStoreProductRuleList(StoreProductRuleExportReqVO exportReqVO);\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/java/co/yixiang/yshop/module/product/service/storeproductrule/StoreProductRuleServiceImpl.java",
    "content": "package co.yixiang.yshop.module.product.service.storeproductrule;\n\nimport co.yixiang.yshop.module.product.convert.storeproductrule.StoreProductRuleConvert;\nimport org.springframework.stereotype.Service;\nimport jakarta.annotation.Resource;\nimport org.springframework.validation.annotation.Validated;\n\nimport java.util.*;\n\nimport co.yixiang.yshop.module.product.controller.admin.storeproductrule.vo.*;\nimport co.yixiang.yshop.module.product.dal.dataobject.storeproductrule.StoreProductRuleDO;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.product.dal.mysql.storeproductrule.StoreProductRuleMapper;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.product.enums.ErrorCodeConstants.STORE_PRODUCT_RULE_NOT_EXISTS;\n\n\n/**\n * 商品规则值(规格) Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class StoreProductRuleServiceImpl implements StoreProductRuleService {\n\n    @Resource\n    private StoreProductRuleMapper storeProductRuleMapper;\n\n    @Override\n    public Integer createStoreProductRule(StoreProductRuleCreateReqVO createReqVO) {\n        // 插入\n        StoreProductRuleDO storeProductRule = StoreProductRuleConvert.INSTANCE.convert(createReqVO);\n        storeProductRuleMapper.insert(storeProductRule);\n        // 返回\n        return storeProductRule.getId();\n    }\n\n    @Override\n    public void updateStoreProductRule(StoreProductRuleUpdateReqVO updateReqVO) {\n        // 校验存在\n        validateStoreProductRuleExists(updateReqVO.getId());\n        // 更新\n        StoreProductRuleDO updateObj = StoreProductRuleConvert.INSTANCE.convert(updateReqVO);\n        storeProductRuleMapper.updateById(updateObj);\n    }\n\n    @Override\n    public void deleteStoreProductRule(Integer id) {\n        // 校验存在\n        validateStoreProductRuleExists(id);\n        // 删除\n        storeProductRuleMapper.deleteById(id);\n    }\n\n    private void validateStoreProductRuleExists(Integer id) {\n        if (storeProductRuleMapper.selectById(id) == null) {\n            throw exception(STORE_PRODUCT_RULE_NOT_EXISTS);\n        }\n    }\n\n    @Override\n    public StoreProductRuleDO getStoreProductRule(Integer id) {\n        return storeProductRuleMapper.selectById(id);\n    }\n\n    @Override\n    public List<StoreProductRuleDO> getStoreProductRuleList(Collection<Integer> ids) {\n        if (ids.isEmpty()) {\n            return storeProductRuleMapper.selectList();\n        }\n        return storeProductRuleMapper.selectBatchIds(ids);\n    }\n\n    @Override\n    public PageResult<StoreProductRuleDO> getStoreProductRulePage(StoreProductRulePageReqVO pageReqVO) {\n        return storeProductRuleMapper.selectPage(pageReqVO);\n    }\n\n    @Override\n    public List<StoreProductRuleDO> getStoreProductRuleList(StoreProductRuleExportReqVO exportReqVO) {\n        return storeProductRuleMapper.selectList(exportReqVO);\n    }\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/resources/mapper/shippingtemplates/ShippingTemplatesMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"co.yixiang.yshop.module.product.dal.mysql.shippingtemplates.ShippingTemplatesMapper\">\n\n    <!--\n        一般情况下，尽可能使用 Mapper 进行 CRUD 增删改查即可。\n        无法满足的场景，例如说多表关联查询，才使用 XML 编写 SQL。\n        代码生成器暂时只生成 Mapper XML 文件本身，更多推荐 MybatisX 快速开发插件来生成查询。\n        文档可见：https://www.yixiang.co/MyBatis/x-plugins/\n     -->\n\n</mapper>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/resources/mapper/shippingtemplatesfree/ShippingTemplatesFreeMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"co.yixiang.yshop.module.product.dal.mysql.shippingtemplatesfree.ShippingTemplatesFreeMapper\">\n\n    <!--\n        一般情况下，尽可能使用 Mapper 进行 CRUD 增删改查即可。\n        无法满足的场景，例如说多表关联查询，才使用 XML 编写 SQL。\n        代码生成器暂时只生成 Mapper XML 文件本身，更多推荐 MybatisX 快速开发插件来生成查询。\n        文档可见：https://www.yixiang.co/MyBatis/x-plugins/\n     -->\n\n</mapper>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/resources/mapper/shippingtemplatesregion/ShippingTemplatesRegionMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"co.yixiang.yshop.module.product.dal.mysql.shippingtemplatesregion.ShippingTemplatesRegionMapper\">\n\n    <!--\n        一般情况下，尽可能使用 Mapper 进行 CRUD 增删改查即可。\n        无法满足的场景，例如说多表关联查询，才使用 XML 编写 SQL。\n        代码生成器暂时只生成 Mapper XML 文件本身，更多推荐 MybatisX 快速开发插件来生成查询。\n        文档可见：https://www.yixiang.co/MyBatis/x-plugins/\n     -->\n\n</mapper>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/resources/mapper/storeproduct/StoreProductMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"co.yixiang.yshop.module.shop.dal.mysql.storeproduct.StoreProductMapper\">\n\n    <!--\n        一般情况下，尽可能使用 Mapper 进行 CRUD 增删改查即可。\n        无法满足的场景，例如说多表关联查询，才使用 XML 编写 SQL。\n        代码生成器暂时只生成 Mapper XML 文件本身，更多推荐 MybatisX 快速开发插件来生成查询。\n        文档可见：https://www.yixiang.co/MyBatis/x-plugins/\n     -->\n\n</mapper>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/resources/mapper/storeproductattr/StoreProductAttrMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"co.yixiang.yshop.module.product.dal.mysql.storeproductattr.StoreProductAttrMapper\">\n\n    <!--\n        一般情况下，尽可能使用 Mapper 进行 CRUD 增删改查即可。\n        无法满足的场景，例如说多表关联查询，才使用 XML 编写 SQL。\n        代码生成器暂时只生成 Mapper XML 文件本身，更多推荐 MybatisX 快速开发插件来生成查询。\n        文档可见：https://www.yixiang.co/MyBatis/x-plugins/\n     -->\n\n</mapper>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/resources/mapper/storeproductattrresult/StoreProductAttrResultMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"co.yixiang.yshop.module.product.dal.mysql.storeproductattrresult.StoreProductAttrResultMapper\">\n\n    <!--\n        一般情况下，尽可能使用 Mapper 进行 CRUD 增删改查即可。\n        无法满足的场景，例如说多表关联查询，才使用 XML 编写 SQL。\n        代码生成器暂时只生成 Mapper XML 文件本身，更多推荐 MybatisX 快速开发插件来生成查询。\n        文档可见：https://www.yixiang.co/MyBatis/x-plugins/\n     -->\n\n</mapper>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/resources/mapper/storeproductattrvalue/StoreProductAttrValueMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"co.yixiang.yshop.module.product.dal.mysql.storeproductattrvalue.StoreProductAttrValueMapper\">\n\n    <!--\n        一般情况下，尽可能使用 Mapper 进行 CRUD 增删改查即可。\n        无法满足的场景，例如说多表关联查询，才使用 XML 编写 SQL。\n        代码生成器暂时只生成 Mapper XML 文件本身，更多推荐 MybatisX 快速开发插件来生成查询。\n        文档可见：https://www.yixiang.co/MyBatis/x-plugins/\n     -->\n\n</mapper>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/resources/mapper/storeproductrelation/StoreProductRelationMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"co.yixiang.yshop.module.product.dal.mysql.storeproductrelation.StoreProductRelationMapper\">\n\n    <!--\n        一般情况下，尽可能使用 Mapper 进行 CRUD 增删改查即可。\n        无法满足的场景，例如说多表关联查询，才使用 XML 编写 SQL。\n        代码生成器暂时只生成 Mapper XML 文件本身，更多推荐 MybatisX 快速开发插件来生成查询。\n        文档可见：https://www.yixiang.co/MyBatis/x-plugins/\n     -->\n\n</mapper>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/resources/mapper/storeproductreply/StoreProductReplyMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"co.yixiang.yshop.module.product.dal.mysql.storeproductreply.StoreProductReplyMapper\">\n\n    <!--\n        一般情况下，尽可能使用 Mapper 进行 CRUD 增删改查即可。\n        无法满足的场景，例如说多表关联查询，才使用 XML 编写 SQL。\n        代码生成器暂时只生成 Mapper XML 文件本身，更多推荐 MybatisX 快速开发插件来生成查询。\n        文档可见：https://www.yixiang.co/MyBatis/x-plugins/\n     -->\n\n</mapper>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-product-biz/src/main/resources/mapper/storeproductrule/StoreProductRuleMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"co.yixiang.yshop.module.product.dal.mysql.storeproductrule.StoreProductRuleMapper\">\n\n    <!--\n        一般情况下，尽可能使用 Mapper 进行 CRUD 增删改查即可。\n        无法满足的场景，例如说多表关联查询，才使用 XML 编写 SQL。\n        代码生成器暂时只生成 Mapper XML 文件本身，更多推荐 MybatisX 快速开发插件来生成查询。\n        文档可见：https://www.yixiang.co/MyBatis/x-plugins/\n     -->\n\n</mapper>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-api/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <parent>\n        <groupId>co.yixiang.boot</groupId>\n        <artifactId>yshop-module-mall</artifactId>\n        <version>${revision}</version>\n    </parent>\n\n    <artifactId>yshop-module-shop-api</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>\n        shop 模块 API，暴露给其它模块调用\n    </description>\n\n    <dependencies>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-common</artifactId>\n        </dependency>\n\n        <!-- 参数校验 -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-validation</artifactId>\n            <optional>true</optional>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-api/src/main/java/co/yixiang/yshop/module/shop/enums/ErrorCodeConstants.java",
    "content": "package co.yixiang.yshop.module.shop.enums;\n\nimport co.yixiang.yshop.framework.common.exception.ErrorCode;\n\n/**\n * Product 错误码枚举类\n *\n * product 系统，使用 1-008-000-000 段\n */\npublic interface ErrorCodeConstants {\n\n    // ========== 素材相关 1009001000 ============\n    ErrorCode MATERIAL_NOT_EXISTS = new ErrorCode(1009001000, \"素材库不存在\");\n    ErrorCode MATERIAL_GROUP_NOT_EXISTS = new ErrorCode(1009001001, \"素材分组不存在\");\n    ErrorCode ADS_NOT_EXISTS = new ErrorCode(1008017000, \"广告图管理不存在\");\n    ErrorCode SERVICE_NOT_EXISTS = new ErrorCode(1008017001, \"我的服务不存在\");\n    ErrorCode RECHARGE_NOT_EXISTS = new ErrorCode(1008017002, \"充值金额不存在\");\n\n    // ========== 商品规则值(规格)  ==========\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>co.yixiang.boot</groupId>\n        <artifactId>yshop-module-mall</artifactId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>yshop-module-shop-biz</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>\n        shop 模块，主要实现商品相关功能\n    </description>\n\n    <dependencies>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-shop-api</artifactId>\n            <version>${revision}</version>\n        </dependency>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-store-biz</artifactId>\n            <version>${revision}</version>\n        </dependency>\n\n        <!-- 业务组件 -->\n<!--        <dependency>-->\n<!--            <groupId>co.yixiang.boot</groupId>-->\n<!--            <artifactId>yshop-spring-boot-starter-biz-operatelog</artifactId>-->\n<!--        </dependency>-->\n\n        <!-- Web 相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-web</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-security</artifactId>\n        </dependency>\n\n        <!-- DB 相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-mybatis</artifactId>\n        </dependency>\n\n        <!-- Test 测试相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-test</artifactId>\n        </dependency>\n\n        <!-- 工具类相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-excel</artifactId>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/controller/admin/material/MaterialController.java",
    "content": "package co.yixiang.yshop.module.shop.controller.admin.material;\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.shop.controller.admin.material.vo.MaterialCreateReqVO;\nimport co.yixiang.yshop.module.shop.controller.admin.material.vo.MaterialPageReqVO;\nimport co.yixiang.yshop.module.shop.controller.admin.material.vo.MaterialRespVO;\nimport co.yixiang.yshop.module.shop.controller.admin.material.vo.MaterialUpdateReqVO;\nimport co.yixiang.yshop.module.shop.convert.material.MaterialConvert;\nimport co.yixiang.yshop.module.shop.dal.dataobject.material.MaterialDO;\nimport co.yixiang.yshop.module.shop.service.material.MaterialService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Valid;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.Collection;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 素材库\")\n@RestController\n@RequestMapping(\"/shop/material\")\n@Validated\npublic class MaterialController {\n\n    @Resource\n    private MaterialService materialService;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建素材库\")\n    //@PreAuthorize(\"@ss.hasPermission('shop:material:create')\")\n    public CommonResult<Long> createMaterial(@Valid @RequestBody MaterialCreateReqVO createReqVO) {\n        return success(materialService.createMaterial(createReqVO));\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"更新素材库\")\n    //@PreAuthorize(\"@ss.hasPermission('shop:material:update')\")\n    public CommonResult<Boolean> updateMaterial(@Valid @RequestBody MaterialUpdateReqVO updateReqVO) {\n        materialService.updateMaterial(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除素材库\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    //@PreAuthorize(\"@ss.hasPermission('shop:material:delete')\")\n    public CommonResult<Boolean> deleteMaterial(@RequestParam(\"id\") Long id) {\n        materialService.deleteMaterial(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得素材库\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    //@PreAuthorize(\"@ss.hasPermission('shop:material:query')\")\n    public CommonResult<MaterialRespVO> getMaterial(@RequestParam(\"id\") Long id) {\n        MaterialDO material = materialService.getMaterial(id);\n        return success(MaterialConvert.INSTANCE.convert(material));\n    }\n\n    @GetMapping(\"/list\")\n    @Operation(summary = \"获得素材库列表\")\n    @Parameter(name = \"ids\", description = \"编号列表\", required = true, example = \"1024,2048\")\n    //@PreAuthorize(\"@ss.hasPermission('shop:material:query')\")\n    public CommonResult<List<MaterialRespVO>> getMaterialList(@RequestParam(\"ids\") Collection<Long> ids) {\n        List<MaterialDO> list = materialService.getMaterialList(ids);\n        return success(MaterialConvert.INSTANCE.convertList(list));\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得素材库分页\")\n    //@PreAuthorize(\"@ss.hasPermission('shop:material:query')\")\n    public CommonResult<PageResult<MaterialRespVO>> getMaterialPage(@Valid MaterialPageReqVO pageVO) {\n        PageResult<MaterialDO> pageResult = materialService.getMaterialPage(pageVO);\n        return success(MaterialConvert.INSTANCE.convertPage(pageResult));\n    }\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/controller/admin/material/vo/MaterialBaseVO.java",
    "content": "package co.yixiang.yshop.module.shop.controller.admin.material.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.util.*;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\nimport jakarta.validation.constraints.*;\n\n/**\n* 素材库 Base VO，提供给添加、修改、详细的子 VO 使用\n* 如果子 VO 存在差异的字段，请不要添加到这里，影响 Swagger 文档生成\n*/\n@Data\npublic class MaterialBaseVO {\n\n    @Schema(description = \"类型1、图片；2、视频\", required = true, example = \"2\")\n    @NotNull(message = \"类型1、图片；2、视频不能为空\")\n    private String type;\n\n    @Schema(description = \"分组ID\", example = \"21579\")\n    private String groupId;\n\n    @Schema(description = \"素材名\", required = true, example = \"yshop\")\n    @NotNull(message = \"素材名不能为空\")\n    private String name;\n\n    @Schema(description = \"素材链接\", example = \"https://www.yixiang.co\")\n    private String url;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/controller/admin/material/vo/MaterialCreateReqVO.java",
    "content": "package co.yixiang.yshop.module.shop.controller.admin.material.vo;\n\nimport lombok.*;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.*;\n\n@Schema(description = \"管理后台 - 素材库创建 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class MaterialCreateReqVO extends MaterialBaseVO {\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/controller/admin/material/vo/MaterialExcelVO.java",
    "content": "package co.yixiang.yshop.module.shop.controller.admin.material.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.util.*;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\n\nimport com.alibaba.excel.annotation.ExcelProperty;\n\n/**\n * 素材库 Excel VO\n *\n * @author yshop\n */\n@Data\npublic class MaterialExcelVO {\n\n    @ExcelProperty(\"编号\")\n    private Long id;\n\n    @ExcelProperty(\"创建时间\")\n    private LocalDateTime createTime;\n\n    @ExcelProperty(\"类型1、图片；2、视频\")\n    private String type;\n\n    @ExcelProperty(\"分组ID\")\n    private String groupId;\n\n    @ExcelProperty(\"素材名\")\n    private String name;\n\n    @ExcelProperty(\"素材链接\")\n    private String url;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/controller/admin/material/vo/MaterialExportReqVO.java",
    "content": "package co.yixiang.yshop.module.shop.controller.admin.material.vo;\n\nimport lombok.*;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport java.time.LocalDateTime;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 素材库 Excel 导出 Request VO，参数和 MaterialPageReqVO 是一致的\")\n@Data\npublic class MaterialExportReqVO {\n\n    @Schema(description = \"创建时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n    @Schema(description = \"类型1、图片；2、视频\", example = \"2\")\n    private String type;\n\n    @Schema(description = \"分组ID\", example = \"21579\")\n    private String groupId;\n\n    @Schema(description = \"素材名\", example = \"yshop\")\n    private String name;\n\n    @Schema(description = \"素材链接\", example = \"https://www.yixiang.co\")\n    private String url;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/controller/admin/material/vo/MaterialPageReqVO.java",
    "content": "package co.yixiang.yshop.module.shop.controller.admin.material.vo;\n\nimport lombok.*;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport org.springframework.format.annotation.DateTimeFormat;\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 素材库分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class MaterialPageReqVO extends PageParam {\n\n    @Schema(description = \"创建时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n    @Schema(description = \"类型1、图片；2、视频\", example = \"2\")\n    private String type;\n\n    @Schema(description = \"分组ID\", example = \"21579\")\n    private String groupId;\n\n    @Schema(description = \"素材名\", example = \"yshop\")\n    private String name;\n\n    @Schema(description = \"素材链接\", example = \"https://www.yixiang.co\")\n    private String url;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/controller/admin/material/vo/MaterialRespVO.java",
    "content": "package co.yixiang.yshop.module.shop.controller.admin.material.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 素材库 Response VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class MaterialRespVO extends MaterialBaseVO {\n\n    @Schema(description = \"编号\", required = true, example = \"9920\")\n    private Long id;\n\n    @Schema(description = \"创建时间\", required = true)\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/controller/admin/material/vo/MaterialUpdateReqVO.java",
    "content": "package co.yixiang.yshop.module.shop.controller.admin.material.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.util.*;\nimport jakarta.validation.constraints.*;\n\n@Schema(description = \"管理后台 - 素材库更新 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class MaterialUpdateReqVO extends MaterialBaseVO {\n\n    @Schema(description = \"编号\", required = true, example = \"9920\")\n    @NotNull(message = \"编号不能为空\")\n    private Long id;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/controller/admin/materialgroup/MaterialGroupController.java",
    "content": "package co.yixiang.yshop.module.shop.controller.admin.materialgroup;\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.shop.controller.admin.materialgroup.vo.*;\nimport co.yixiang.yshop.module.shop.convert.materialgroup.MaterialGroupConvert;\nimport co.yixiang.yshop.module.shop.dal.dataobject.materialgroup.MaterialGroupDO;\nimport co.yixiang.yshop.module.shop.service.materialgroup.MaterialGroupService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Valid;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 素材分组\")\n@RestController\n@RequestMapping(\"/shop/material-group\")\n@Validated\npublic class MaterialGroupController {\n\n    @Resource\n    private MaterialGroupService materialGroupService;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建素材分组\")\n    //@PreAuthorize(\"@ss.hasPermission('shop:material-group:create')\")\n    public CommonResult<Long> createMaterialGroup(@Valid @RequestBody MaterialGroupCreateReqVO createReqVO) {\n        return success(materialGroupService.createMaterialGroup(createReqVO));\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"更新素材分组\")\n    //@PreAuthorize(\"@ss.hasPermission('shop:material-group:update')\")\n    public CommonResult<Boolean> updateMaterialGroup(@Valid @RequestBody MaterialGroupUpdateReqVO updateReqVO) {\n        materialGroupService.updateMaterialGroup(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除素材分组\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    //@PreAuthorize(\"@ss.hasPermission('shop:material-group:delete')\")\n    public CommonResult<Boolean> deleteMaterialGroup(@RequestParam(\"id\") Long id) {\n        materialGroupService.deleteMaterialGroup(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得素材分组\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    //@PreAuthorize(\"@ss.hasPermission('shop:material-group:query')\")\n    public CommonResult<MaterialGroupRespVO> getMaterialGroup(@RequestParam(\"id\") Long id) {\n        MaterialGroupDO materialGroup = materialGroupService.getMaterialGroup(id);\n        return success(MaterialGroupConvert.INSTANCE.convert(materialGroup));\n    }\n\n    @GetMapping(\"/list\")\n    @Operation(summary = \"获得素材分组列表\")\n    //@Parameter(name = \"ids\", description = \"编号列表\", required = true, example = \"1024,2048\")\n    //@PreAuthorize(\"@ss.hasPermission('shop:material-group:query')\")\n    public CommonResult<List<MaterialGroupRespVO>> getMaterialGroupList() {\n        MaterialGroupExportReqVO exportReqVO = new MaterialGroupExportReqVO();\n        List<MaterialGroupDO> list = materialGroupService.getMaterialGroupList(exportReqVO);\n        return success(MaterialGroupConvert.INSTANCE.convertList(list));\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得素材分组分页\")\n    //@PreAuthorize(\"@ss.hasPermission('shop:material-group:query')\")\n    public CommonResult<PageResult<MaterialGroupRespVO>> getMaterialGroupPage(@Valid MaterialGroupPageReqVO pageVO) {\n        PageResult<MaterialGroupDO> pageResult = materialGroupService.getMaterialGroupPage(pageVO);\n        return success(MaterialGroupConvert.INSTANCE.convertPage(pageResult));\n    }\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/controller/admin/materialgroup/vo/MaterialGroupBaseVO.java",
    "content": "package co.yixiang.yshop.module.shop.controller.admin.materialgroup.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.util.*;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\nimport jakarta.validation.constraints.*;\n\n/**\n* 素材分组 Base VO，提供给添加、修改、详细的子 VO 使用\n* 如果子 VO 存在差异的字段，请不要添加到这里，影响 Swagger 文档生成\n*/\n@Data\npublic class MaterialGroupBaseVO {\n\n    @Schema(description = \"分组名\", required = true, example = \"赵六\")\n    @NotNull(message = \"分组名不能为空\")\n    private String name;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/controller/admin/materialgroup/vo/MaterialGroupCreateReqVO.java",
    "content": "package co.yixiang.yshop.module.shop.controller.admin.materialgroup.vo;\n\nimport lombok.*;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.*;\n\n@Schema(description = \"管理后台 - 素材分组创建 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class MaterialGroupCreateReqVO extends MaterialGroupBaseVO {\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/controller/admin/materialgroup/vo/MaterialGroupExcelVO.java",
    "content": "package co.yixiang.yshop.module.shop.controller.admin.materialgroup.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.util.*;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\n\nimport com.alibaba.excel.annotation.ExcelProperty;\n\n/**\n * 素材分组 Excel VO\n *\n * @author yshop\n */\n@Data\npublic class MaterialGroupExcelVO {\n\n    @ExcelProperty(\"编号\")\n    private Long id;\n\n    @ExcelProperty(\"创建时间\")\n    private LocalDateTime createTime;\n\n    @ExcelProperty(\"分组名\")\n    private String name;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/controller/admin/materialgroup/vo/MaterialGroupExportReqVO.java",
    "content": "package co.yixiang.yshop.module.shop.controller.admin.materialgroup.vo;\n\nimport lombok.*;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport java.time.LocalDateTime;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 素材分组 Excel 导出 Request VO，参数和 MaterialGroupPageReqVO 是一致的\")\n@Data\npublic class MaterialGroupExportReqVO {\n\n    @Schema(description = \"创建时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n    @Schema(description = \"分组名\", example = \"赵六\")\n    private String name;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/controller/admin/materialgroup/vo/MaterialGroupPageReqVO.java",
    "content": "package co.yixiang.yshop.module.shop.controller.admin.materialgroup.vo;\n\nimport lombok.*;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport org.springframework.format.annotation.DateTimeFormat;\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 素材分组分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class MaterialGroupPageReqVO extends PageParam {\n\n    @Schema(description = \"创建时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n    @Schema(description = \"分组名\", example = \"赵六\")\n    private String name;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/controller/admin/materialgroup/vo/MaterialGroupRespVO.java",
    "content": "package co.yixiang.yshop.module.shop.controller.admin.materialgroup.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 素材分组 Response VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class MaterialGroupRespVO extends MaterialGroupBaseVO {\n\n    @Schema(description = \"编号\", required = true, example = \"1995\")\n    private Long id;\n\n    @Schema(description = \"创建时间\", required = true)\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/controller/admin/materialgroup/vo/MaterialGroupUpdateReqVO.java",
    "content": "package co.yixiang.yshop.module.shop.controller.admin.materialgroup.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.util.*;\nimport jakarta.validation.constraints.*;\n\n@Schema(description = \"管理后台 - 素材分组更新 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class MaterialGroupUpdateReqVO extends MaterialGroupBaseVO {\n\n    @Schema(description = \"编号\", required = true, example = \"1995\")\n    @NotNull(message = \"编号不能为空\")\n    private Long id;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/controller/admin/service/ServiceController.java",
    "content": "package co.yixiang.yshop.module.shop.controller.admin.service;\n\nimport jakarta.servlet.http.HttpServletResponse;\nimport org.springframework.web.bind.annotation.*;\nimport jakarta.annotation.Resource;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.Operation;\n\nimport jakarta.validation.constraints.*;\nimport jakarta.validation.*;\nimport java.util.*;\nimport java.io.IOException;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\nimport co.yixiang.yshop.framework.excel.core.util.ExcelUtils;\n\nimport co.yixiang.yshop.module.shop.controller.admin.service.vo.*;\nimport co.yixiang.yshop.module.shop.dal.dataobject.service.ServiceDO;\nimport co.yixiang.yshop.module.shop.convert.service.ServiceConvert;\nimport co.yixiang.yshop.module.shop.service.service.ServiceService;\n\n@Tag(name = \"管理后台 - 我的服务\")\n@RestController\n@RequestMapping(\"/shop/service\")\n@Validated\npublic class ServiceController {\n\n    @Resource\n    private ServiceService serviceService;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建我的服务\")\n    @PreAuthorize(\"@ss.hasPermission('shop:service:create')\")\n    public CommonResult<Long> createService(@Valid @RequestBody ServiceCreateReqVO createReqVO) {\n        return success(serviceService.createService(createReqVO));\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"更新我的服务\")\n    @PreAuthorize(\"@ss.hasPermission('shop:service:update')\")\n    public CommonResult<Boolean> updateService(@Valid @RequestBody ServiceUpdateReqVO updateReqVO) {\n        serviceService.updateService(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除我的服务\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('shop:service:delete')\")\n    public CommonResult<Boolean> deleteService(@RequestParam(\"id\") Long id) {\n        serviceService.deleteService(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得我的服务\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('shop:service:query')\")\n    public CommonResult<ServiceRespVO> getService(@RequestParam(\"id\") Long id) {\n        ServiceDO service = serviceService.getService(id);\n        return success(ServiceConvert.INSTANCE.convert(service));\n    }\n\n    @GetMapping(\"/list\")\n    @Operation(summary = \"获得我的服务列表\")\n    @Parameter(name = \"ids\", description = \"编号列表\", required = true, example = \"1024,2048\")\n    @PreAuthorize(\"@ss.hasPermission('shop:service:query')\")\n    public CommonResult<List<ServiceRespVO>> getServiceList(@RequestParam(\"ids\") Collection<Long> ids) {\n        List<ServiceDO> list = serviceService.getServiceList(ids);\n        return success(ServiceConvert.INSTANCE.convertList(list));\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得我的服务分页\")\n    @PreAuthorize(\"@ss.hasPermission('shop:service:query')\")\n    public CommonResult<PageResult<ServiceRespVO>> getServicePage(@Valid ServicePageReqVO pageVO) {\n        PageResult<ServiceDO> pageResult = serviceService.getServicePage(pageVO);\n        return success(ServiceConvert.INSTANCE.convertPage(pageResult));\n    }\n\n    @GetMapping(\"/export-excel\")\n    @Operation(summary = \"导出我的服务 Excel\")\n    @PreAuthorize(\"@ss.hasPermission('shop:service:export')\")\n    public void exportServiceExcel(@Valid ServiceExportReqVO exportReqVO,\n              HttpServletResponse response) throws IOException {\n        List<ServiceDO> list = serviceService.getServiceList(exportReqVO);\n        // 导出 Excel\n        List<ServiceExcelVO> datas = ServiceConvert.INSTANCE.convertList02(list);\n        ExcelUtils.write(response, \"我的服务.xls\", \"数据\", ServiceExcelVO.class, datas);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/controller/admin/service/vo/ServiceBaseVO.java",
    "content": "package co.yixiang.yshop.module.shop.controller.admin.service.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.util.*;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\nimport jakarta.validation.constraints.*;\n\n/**\n* 我的服务 Base VO，提供给添加、修改、详细的子 VO 使用\n* 如果子 VO 存在差异的字段，请不要添加到这里，影响 Swagger 文档生成\n*/\n@Data\npublic class ServiceBaseVO {\n\n    @Schema(description = \"标题\", required = true, example = \"张三\")\n    @NotNull(message = \"标题不能为空\")\n    private String name;\n\n    @Schema(description = \"图标\", required = true)\n    @NotNull(message = \"图标不能为空\")\n    private String image;\n\n    @Schema(description = \"类型:pages=页面,miniprogram=跳转小程序,menu=菜单,content=内容,call=电话\", required = true, example = \"1\")\n    @NotNull(message = \"类型不能为空\")\n    private String type;\n\n    @Schema(description = \"详情\", required = true)\n    private String content;\n\n    @Schema(description = \"父级id\", required = true, example = \"25371\")\n    private Integer pid;\n\n    @Schema(description = \"小程序app_id\", required = true, example = \"20728\")\n    private String appId;\n\n    @Schema(description = \"页面路径\", required = true)\n    private String pages;\n\n    @Schema(description = \"电话\", required = true)\n    private String phone;\n\n    @Schema(description = \"权重\", required = true)\n    private Integer weigh;\n\n    @Schema(description = \"状态:0=下架,1=上架\", required = true, example = \"2\")\n    private Integer status;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/controller/admin/service/vo/ServiceCreateReqVO.java",
    "content": "package co.yixiang.yshop.module.shop.controller.admin.service.vo;\n\nimport lombok.*;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.*;\n\n@Schema(description = \"管理后台 - 我的服务创建 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class ServiceCreateReqVO extends ServiceBaseVO {\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/controller/admin/service/vo/ServiceExcelVO.java",
    "content": "package co.yixiang.yshop.module.shop.controller.admin.service.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.util.*;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\n\nimport com.alibaba.excel.annotation.ExcelProperty;\n\n/**\n * 我的服务 Excel VO\n *\n * @author yshop\n */\n@Data\npublic class ServiceExcelVO {\n\n    @ExcelProperty(\"id\")\n    private Long id;\n\n    @ExcelProperty(\"标题\")\n    private String name;\n\n    @ExcelProperty(\"图标\")\n    private String image;\n\n    @ExcelProperty(\"类型:pages=页面,miniprogram=跳转小程序,menu=菜单,content=内容,call=电话\")\n    private String type;\n\n    @ExcelProperty(\"详情\")\n    private String content;\n\n    @ExcelProperty(\"父级id\")\n    private Integer pid;\n\n    @ExcelProperty(\"小程序app_id\")\n    private String appId;\n\n    @ExcelProperty(\"页面路径\")\n    private String pages;\n\n    @ExcelProperty(\"电话\")\n    private String phone;\n\n    @ExcelProperty(\"权重\")\n    private Integer weigh;\n\n    @ExcelProperty(\"状态:0=下架,1=上架\")\n    private Integer status;\n\n    @ExcelProperty(\"添加时间\")\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/controller/admin/service/vo/ServiceExportReqVO.java",
    "content": "package co.yixiang.yshop.module.shop.controller.admin.service.vo;\n\nimport lombok.*;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport java.time.LocalDateTime;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 我的服务 Excel 导出 Request VO，参数和 ServicePageReqVO 是一致的\")\n@Data\npublic class ServiceExportReqVO {\n\n    @Schema(description = \"标题\", example = \"张三\")\n    private String name;\n\n    @Schema(description = \"图标\")\n    private String image;\n\n    @Schema(description = \"类型:pages=页面,miniprogram=跳转小程序,menu=菜单,content=内容,call=电话\", example = \"1\")\n    private String type;\n\n    @Schema(description = \"详情\")\n    private String content;\n\n    @Schema(description = \"父级id\", example = \"25371\")\n    private Integer pid;\n\n    @Schema(description = \"小程序app_id\", example = \"20728\")\n    private String appId;\n\n    @Schema(description = \"页面路径\")\n    private String pages;\n\n    @Schema(description = \"电话\")\n    private String phone;\n\n    @Schema(description = \"权重\")\n    private Integer weigh;\n\n    @Schema(description = \"状态:0=下架,1=上架\", example = \"2\")\n    private Boolean status;\n\n    @Schema(description = \"添加时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/controller/admin/service/vo/ServicePageReqVO.java",
    "content": "package co.yixiang.yshop.module.shop.controller.admin.service.vo;\n\nimport lombok.*;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport org.springframework.format.annotation.DateTimeFormat;\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 我的服务分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class ServicePageReqVO extends PageParam {\n\n    @Schema(description = \"标题\", example = \"张三\")\n    private String name;\n\n    @Schema(description = \"图标\")\n    private String image;\n\n    @Schema(description = \"类型:pages=页面,miniprogram=跳转小程序,menu=菜单,content=内容,call=电话\", example = \"1\")\n    private String type;\n\n    @Schema(description = \"详情\")\n    private String content;\n\n    @Schema(description = \"父级id\", example = \"25371\")\n    private Integer pid;\n\n    @Schema(description = \"小程序app_id\", example = \"20728\")\n    private String appId;\n\n    @Schema(description = \"页面路径\")\n    private String pages;\n\n    @Schema(description = \"电话\")\n    private String phone;\n\n    @Schema(description = \"权重\")\n    private Integer weigh;\n\n    @Schema(description = \"状态:0=下架,1=上架\", example = \"2\")\n    private Boolean status;\n\n    @Schema(description = \"添加时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/controller/admin/service/vo/ServiceRespVO.java",
    "content": "package co.yixiang.yshop.module.shop.controller.admin.service.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 我的服务 Response VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class ServiceRespVO extends ServiceBaseVO {\n\n    @Schema(description = \"id\", required = true, example = \"6335\")\n    private Long id;\n\n    @Schema(description = \"添加时间\")\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/controller/admin/service/vo/ServiceUpdateReqVO.java",
    "content": "package co.yixiang.yshop.module.shop.controller.admin.service.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.util.*;\nimport jakarta.validation.constraints.*;\n\n@Schema(description = \"管理后台 - 我的服务更新 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class ServiceUpdateReqVO extends ServiceBaseVO {\n\n    @Schema(description = \"id\", required = true, example = \"6335\")\n    @NotNull(message = \"id不能为空\")\n    private Long id;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/controller/admin/shopads/ShopAdsController.java",
    "content": "package co.yixiang.yshop.module.shop.controller.admin.shopads;\n\nimport jakarta.servlet.http.HttpServletResponse;\nimport org.springframework.web.bind.annotation.*;\nimport jakarta.annotation.Resource;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.Operation;\n\nimport jakarta.validation.constraints.*;\nimport jakarta.validation.*;\nimport java.util.*;\nimport java.io.IOException;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\nimport co.yixiang.yshop.framework.excel.core.util.ExcelUtils;\n\n\nimport co.yixiang.yshop.module.shop.controller.admin.shopads.vo.*;\nimport co.yixiang.yshop.module.shop.dal.dataobject.shopads.ShopAdsDO;\nimport co.yixiang.yshop.module.shop.convert.shopads.ShopAdsConvert;\nimport co.yixiang.yshop.module.shop.service.shopads.ShopAdsService;\n\n@Tag(name = \"管理后台 - 广告图管理\")\n@RestController\n@RequestMapping(\"/shop/ads\")\n@Validated\npublic class ShopAdsController {\n\n    @Resource\n    private ShopAdsService adsService;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建广告图管理\")\n    @PreAuthorize(\"@ss.hasPermission('shop:ads:create')\")\n    public CommonResult<Long> createAds(@Valid @RequestBody ShopAdsCreateReqVO createReqVO) {\n        return success(adsService.createAds(createReqVO));\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"更新广告图管理\")\n    @PreAuthorize(\"@ss.hasPermission('shop:ads:update')\")\n    public CommonResult<Boolean> updateAds(@Valid @RequestBody ShopAdsUpdateReqVO updateReqVO) {\n        adsService.updateAds(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除广告图管理\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('shop:ads:delete')\")\n    public CommonResult<Boolean> deleteAds(@RequestParam(\"id\") Long id) {\n        adsService.deleteAds(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得广告图管理\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('shop:ads:query')\")\n    public CommonResult<ShopAdsRespVO> getAds(@RequestParam(\"id\") Long id) {\n        ShopAdsDO ads = adsService.getAds(id);\n        return success(ShopAdsConvert.INSTANCE.convert(ads));\n    }\n\n    @GetMapping(\"/list\")\n    @Operation(summary = \"获得广告图管理列表\")\n    @Parameter(name = \"ids\", description = \"编号列表\", required = true, example = \"1024,2048\")\n    @PreAuthorize(\"@ss.hasPermission('shop:ads:query')\")\n    public CommonResult<List<ShopAdsRespVO>> getAdsList(@RequestParam(\"ids\") Collection<Long> ids) {\n        List<ShopAdsDO> list = adsService.getAdsList(ids);\n        return success(ShopAdsConvert.INSTANCE.convertList(list));\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得广告图管理分页\")\n    @PreAuthorize(\"@ss.hasPermission('shop:ads:query')\")\n    public CommonResult<PageResult<ShopAdsRespVO>> getAdsPage(@Valid ShopAdsPageReqVO pageVO) {\n        PageResult<ShopAdsDO> pageResult = adsService.getAdsPage(pageVO);\n        return success(ShopAdsConvert.INSTANCE.convertPage(pageResult));\n    }\n\n    @GetMapping(\"/export-excel\")\n    @Operation(summary = \"导出广告图管理 Excel\")\n    @PreAuthorize(\"@ss.hasPermission('shop:ads:export')\")\n    public void exportAdsExcel(@Valid ShopAdsExportReqVO exportReqVO,\n              HttpServletResponse response) throws IOException {\n        List<ShopAdsDO> list = adsService.getAdsList(exportReqVO);\n        // 导出 Excel\n        List<ShopAdsExcelVO> datas = ShopAdsConvert.INSTANCE.convertList02(list);\n        ExcelUtils.write(response, \"广告图管理.xls\", \"数据\", ShopAdsExcelVO.class, datas);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/controller/admin/shopads/vo/ShopAdsBaseVO.java",
    "content": "package co.yixiang.yshop.module.shop.controller.admin.shopads.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.util.*;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\nimport jakarta.validation.constraints.*;\n\n/**\n* 广告图管理 Base VO，提供给添加、修改、详细的子 VO 使用\n* 如果子 VO 存在差异的字段，请不要添加到这里，影响 Swagger 文档生成\n*/\n@Data\npublic class ShopAdsBaseVO {\n\n    @Schema(description = \"图片\")\n    private String image;\n\n    @Schema(description = \"是否显现\", required = true)\n    @NotNull(message = \"是否显现不能为空\")\n    private Integer isSwitch;\n\n    @Schema(description = \"权重\", required = true)\n    @NotNull(message = \"权重不能为空\")\n    private Integer weigh;\n\n    @Schema(description = \"所支持的店铺id用','区分，0代表全部\", required = true, example = \"2901\")\n    @NotNull(message = \"所支持的店铺id用','区分，0代表全部不能为空\")\n    private String shopId;\n\n    private String shopName;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/controller/admin/shopads/vo/ShopAdsCreateReqVO.java",
    "content": "package co.yixiang.yshop.module.shop.controller.admin.shopads.vo;\n\nimport lombok.*;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.*;\n\n@Schema(description = \"管理后台 - 广告图管理创建 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class ShopAdsCreateReqVO extends ShopAdsBaseVO {\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/controller/admin/shopads/vo/ShopAdsExcelVO.java",
    "content": "package co.yixiang.yshop.module.shop.controller.admin.shopads.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.util.*;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\n\nimport com.alibaba.excel.annotation.ExcelProperty;\n\n/**\n * 广告图管理 Excel VO\n *\n * @author yshop\n */\n@Data\npublic class ShopAdsExcelVO {\n\n    @ExcelProperty(\"id\")\n    private Long id;\n\n    @ExcelProperty(\"图片\")\n    private String image;\n\n    @ExcelProperty(\"是否显现\")\n    private Integer isSwitch;\n\n    @ExcelProperty(\"权重\")\n    private Integer weigh;\n\n    @ExcelProperty(\"所支持的店铺id用','区分，0代表全部\")\n    private String shopId;\n\n    @ExcelProperty(\"添加时间\")\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/controller/admin/shopads/vo/ShopAdsExportReqVO.java",
    "content": "package co.yixiang.yshop.module.shop.controller.admin.shopads.vo;\n\nimport lombok.*;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport java.time.LocalDateTime;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 广告图管理 Excel 导出 Request VO，参数和 ShopAdsPageReqVO 是一致的\")\n@Data\npublic class ShopAdsExportReqVO {\n\n    @Schema(description = \"图片\")\n    private String image;\n\n    @Schema(description = \"是否显现\")\n    private Integer isSwitch;\n\n    @Schema(description = \"权重\")\n    private Integer weigh;\n\n    @Schema(description = \"所支持的店铺id用','区分，0代表全部\", example = \"2901\")\n    private String shopId;\n\n    @Schema(description = \"添加时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/controller/admin/shopads/vo/ShopAdsPageReqVO.java",
    "content": "package co.yixiang.yshop.module.shop.controller.admin.shopads.vo;\n\nimport lombok.*;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport org.springframework.format.annotation.DateTimeFormat;\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 广告图管理分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class ShopAdsPageReqVO extends PageParam {\n\n    @Schema(description = \"图片\")\n    private String image;\n\n    @Schema(description = \"是否显现\")\n    private Integer isSwitch;\n\n    @Schema(description = \"权重\")\n    private Integer weigh;\n\n    @Schema(description = \"所支持的店铺id用','区分，0代表全部\", example = \"2901\")\n    private String shopName;\n\n    @Schema(description = \"添加时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/controller/admin/shopads/vo/ShopAdsRespVO.java",
    "content": "package co.yixiang.yshop.module.shop.controller.admin.shopads.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 广告图管理 Response VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class ShopAdsRespVO extends ShopAdsBaseVO {\n\n    @Schema(description = \"id\", required = true, example = \"24753\")\n    private Long id;\n\n    @Schema(description = \"添加时间\")\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/controller/admin/shopads/vo/ShopAdsUpdateReqVO.java",
    "content": "package co.yixiang.yshop.module.shop.controller.admin.shopads.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.util.*;\nimport jakarta.validation.constraints.*;\n\n@Schema(description = \"管理后台 - 广告图管理更新 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class ShopAdsUpdateReqVO extends ShopAdsBaseVO {\n\n    @Schema(description = \"id\", required = true, example = \"24753\")\n    @NotNull(message = \"id不能为空\")\n    private Long id;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/controller/app/ad/AppAdController.java",
    "content": "/**\n * Copyright (C) 2018-2022\n * All rights reserved, Designed By www.yixiang.co\n * 注意：\n * 本软件为www.yixiang.co开发研制，未经购买不得使用\n * 购买后可获得全部源代码（禁止转卖、分享、上传到码云、github等开源平台）\n * 一经发现盗用、分享等行为，将追究法律责任，后果自负\n */\npackage co.yixiang.yshop.module.shop.controller.app.ad;\n\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.security.core.annotations.PreAuthenticated;\nimport co.yixiang.yshop.module.shop.controller.app.ad.vo.AppShopAdsVO;\nimport co.yixiang.yshop.module.shop.convert.shopads.ShopAdsConvert;\nimport co.yixiang.yshop.module.shop.dal.dataobject.shopads.ShopAdsDO;\nimport co.yixiang.yshop.module.shop.service.shopads.AppShopAdsService;\nimport com.baomidou.mybatisplus.core.conditions.Wrapper;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n/**\n * <p>\n * 首页广告控制器\n * </p>\n *\n * @author hupeng\n * @since 2023-8-10\n */\n@Slf4j\n@RestController\n@Tag(name = \"用户 APP - 广告\")\n@RequiredArgsConstructor(onConstructor = @__(@Autowired))\n@RequestMapping(\"/ad\")\npublic class AppAdController {\n\n    private final AppShopAdsService appShopAdsService;\n\n    @Value(\"${yshop.info.isActive}\")\n    private Boolean isActive;\n\n    @GetMapping(\"/list\")\n    @Operation(summary = \"查询广告列表\")\n    @Parameter(name = \"shop_id\", description = \"店铺id\", required = true, example = \"10      \")\n    public CommonResult<Map<String,Object>> getList(@RequestParam(\"shop_id\") Long shopId) {\n//        List<ShopAdsDO> appShopAdsVOS = appShopAdsService.list(new LambdaQueryWrapper<ShopAdsDO>()\n//                .eq(ShopAdsDO::getId,0).or().apply(shopId > 0,\n//                        \"FIND_IN_SET ('\" + shopId + \"',shop_id)\"));\n        List<ShopAdsDO> appShopAdsVOS = appShopAdsService.list(new LambdaQueryWrapper<ShopAdsDO>()\n                .eq(ShopAdsDO::getShopId,0).or().eq(ShopAdsDO::getShopId,shopId));\n        Map<String,Object> map = new HashMap<>();\n        map.put(\"list\",ShopAdsConvert.INSTANCE.convertList03(appShopAdsVOS));\n        map.put(\"isActive\",isActive);\n        return success(map);\n    }\n\n\n\n}\n\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/controller/app/ad/vo/AppShopAdsVO.java",
    "content": "package co.yixiang.yshop.module.shop.controller.app.ad.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotNull;\n\n/**\n* 广告图VO\n*/\n@Data\npublic class AppShopAdsVO {\n\n    @Schema(description = \"id\", required = true, example = \"24753\")\n    private Long id;\n\n    @Schema(description = \"图片\")\n    private String image;\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/controller/app/service/AppServiceController.java",
    "content": "/**\n * Copyright (C) 2018-2022\n * All rights reserved, Designed By www.yixiang.co\n * 注意：\n * 本软件为www.yixiang.co开发研制，未经购买不得使用\n * 购买后可获得全部源代码（禁止转卖、分享、上传到码云、github等开源平台）\n * 一经发现盗用、分享等行为，将追究法律责任，后果自负\n */\npackage co.yixiang.yshop.module.shop.controller.app.service;\n\n\nimport co.yixiang.yshop.framework.common.enums.ShopCommonEnum;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.module.shop.controller.app.service.vo.AppServiceVO;\nimport co.yixiang.yshop.module.shop.convert.service.ServiceConvert;\nimport co.yixiang.yshop.module.shop.dal.dataobject.service.ServiceDO;\nimport co.yixiang.yshop.module.shop.service.service.AppServiceService;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n/**\n * <p>\n * 首页广告控制器\n * </p>\n *\n * @author hupeng\n * @since 2023-8-11\n */\n@Slf4j\n@RestController\n@Tag(name = \"用户 APP - 服务菜单\")\n@RequiredArgsConstructor(onConstructor = @__(@Autowired))\n@RequestMapping(\"/service\")\npublic class AppServiceController {\n\n    private final AppServiceService appServiceService;\n\n\n    @GetMapping(\"/list\")\n    @Operation(summary = \"服务菜单列表\")\n    public CommonResult<List<AppServiceVO>> getList() {\n        List<ServiceDO> appShopAdsVOS = appServiceService.list(new LambdaQueryWrapper<ServiceDO>()\n                .eq(ServiceDO::getStatus, ShopCommonEnum.IS_STATUS_1.getValue()).orderByDesc(ServiceDO::getWeigh));\n        return success(ServiceConvert.INSTANCE.convertList03(appShopAdsVOS));\n    }\n\n    @GetMapping(\"/content\")\n    @Operation(summary = \"服务菜单列表\")\n    public CommonResult<AppServiceVO> getContent(@RequestParam(\"id\") Integer id) {\n        ServiceDO appShopAdsVO = appServiceService.getById(id);\n        return success(ServiceConvert.INSTANCE.convert03(appShopAdsVO));\n    }\n\n\n\n}\n\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/controller/app/service/vo/AppServiceVO.java",
    "content": "package co.yixiang.yshop.module.shop.controller.app.service.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\n\n/**\n* 我的服务\n*/\n@Data\npublic class AppServiceVO {\n\n    @Schema(description = \"id\", required = true, example = \"6335\")\n    private Long id;\n\n    @Schema(description = \"标题\", required = true, example = \"张三\")\n    private String name;\n\n    @Schema(description = \"图标\", required = true)\n    private String image;\n\n    @Schema(description = \"类型:pages=页面,miniprogram=跳转小程序,menu=菜单,content=内容,call=电话\", required = true, example = \"1\")\n    private String type;\n\n    @Schema(description = \"详情\", required = true)\n    private String content;\n\n    @Schema(description = \"父级id\", required = true, example = \"25371\")\n    private Integer pid;\n\n    @Schema(description = \"小程序app_id\", required = true, example = \"20728\")\n    private String appId;\n\n    @Schema(description = \"页面路径\", required = true)\n    private String pages;\n\n    @Schema(description = \"电话\", required = true)\n    private String phone;\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/convert/material/MaterialConvert.java",
    "content": "package co.yixiang.yshop.module.shop.convert.material;\n\nimport java.util.*;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\nimport org.mapstruct.Mapper;\nimport org.mapstruct.factory.Mappers;\nimport co.yixiang.yshop.module.shop.controller.admin.material.vo.*;\nimport co.yixiang.yshop.module.shop.dal.dataobject.material.MaterialDO;\n\n/**\n * 素材库 Convert\n *\n * @author yshop\n */\n@Mapper\npublic interface MaterialConvert {\n\n    MaterialConvert INSTANCE = Mappers.getMapper(MaterialConvert.class);\n\n    MaterialDO convert(MaterialCreateReqVO bean);\n\n    MaterialDO convert(MaterialUpdateReqVO bean);\n\n    MaterialRespVO convert(MaterialDO bean);\n\n    List<MaterialRespVO> convertList(List<MaterialDO> list);\n\n    PageResult<MaterialRespVO> convertPage(PageResult<MaterialDO> page);\n\n    List<MaterialExcelVO> convertList02(List<MaterialDO> list);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/convert/materialgroup/MaterialGroupConvert.java",
    "content": "package co.yixiang.yshop.module.shop.convert.materialgroup;\n\nimport java.util.*;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\nimport org.mapstruct.Mapper;\nimport org.mapstruct.factory.Mappers;\nimport co.yixiang.yshop.module.shop.controller.admin.materialgroup.vo.*;\nimport co.yixiang.yshop.module.shop.dal.dataobject.materialgroup.MaterialGroupDO;\n\n/**\n * 素材分组 Convert\n *\n * @author yshop\n */\n@Mapper\npublic interface MaterialGroupConvert {\n\n    MaterialGroupConvert INSTANCE = Mappers.getMapper(MaterialGroupConvert.class);\n\n    MaterialGroupDO convert(MaterialGroupCreateReqVO bean);\n\n    MaterialGroupDO convert(MaterialGroupUpdateReqVO bean);\n\n    MaterialGroupRespVO convert(MaterialGroupDO bean);\n\n    List<MaterialGroupRespVO> convertList(List<MaterialGroupDO> list);\n\n    PageResult<MaterialGroupRespVO> convertPage(PageResult<MaterialGroupDO> page);\n\n    List<MaterialGroupExcelVO> convertList02(List<MaterialGroupDO> list);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/convert/service/ServiceConvert.java",
    "content": "package co.yixiang.yshop.module.shop.convert.service;\n\nimport java.util.*;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\nimport co.yixiang.yshop.module.shop.controller.app.service.vo.AppServiceVO;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.factory.Mappers;\nimport co.yixiang.yshop.module.shop.controller.admin.service.vo.*;\nimport co.yixiang.yshop.module.shop.dal.dataobject.service.ServiceDO;\n\n/**\n * 我的服务 Convert\n *\n * @author yshop\n */\n@Mapper\npublic interface ServiceConvert {\n\n    ServiceConvert INSTANCE = Mappers.getMapper(ServiceConvert.class);\n\n    ServiceDO convert(ServiceCreateReqVO bean);\n\n    ServiceDO convert(ServiceUpdateReqVO bean);\n\n    ServiceRespVO convert(ServiceDO bean);\n\n    List<ServiceRespVO> convertList(List<ServiceDO> list);\n\n    List<AppServiceVO> convertList03(List<ServiceDO> list);\n\n    AppServiceVO convert03(ServiceDO bean);\n\n    PageResult<ServiceRespVO> convertPage(PageResult<ServiceDO> page);\n\n    List<ServiceExcelVO> convertList02(List<ServiceDO> list);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/convert/shopads/ShopAdsConvert.java",
    "content": "package co.yixiang.yshop.module.shop.convert.shopads;\n\nimport java.util.*;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\nimport co.yixiang.yshop.module.shop.controller.app.ad.vo.AppShopAdsVO;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.factory.Mappers;\nimport co.yixiang.yshop.module.shop.controller.admin.shopads.vo.*;\nimport co.yixiang.yshop.module.shop.dal.dataobject.shopads.ShopAdsDO;\n\n/**\n * 广告图管理 Convert\n *\n * @author yshop\n */\n@Mapper\npublic interface ShopAdsConvert {\n\n    ShopAdsConvert INSTANCE = Mappers.getMapper(ShopAdsConvert.class);\n\n    ShopAdsDO convert(ShopAdsCreateReqVO bean);\n\n    ShopAdsDO convert(ShopAdsUpdateReqVO bean);\n\n    ShopAdsRespVO convert(ShopAdsDO bean);\n\n    List<ShopAdsRespVO> convertList(List<ShopAdsDO> list);\n\n    List<AppShopAdsVO> convertList03(List<ShopAdsDO> list);\n\n    PageResult<ShopAdsRespVO> convertPage(PageResult<ShopAdsDO> page);\n\n    List<ShopAdsExcelVO> convertList02(List<ShopAdsDO> list);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/dal/dataobject/material/MaterialDO.java",
    "content": "package co.yixiang.yshop.module.shop.dal.dataobject.material;\n\nimport lombok.*;\nimport java.util.*;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\nimport com.baomidou.mybatisplus.annotation.*;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\n\n/**\n * 素材库 DO\n *\n * @author yshop\n */\n@TableName(\"yshop_material\")\n@KeySequence(\"yshop_material_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class MaterialDO extends BaseDO {\n\n    /**\n     * 编号\n     */\n    @TableId\n    private Long id;\n    /**\n     * 类型1、图片；2、视频\n     */\n    private String type;\n    /**\n     * 分组ID\n     */\n    private String groupId;\n    /**\n     * 素材名\n     */\n    private String name;\n    /**\n     * 素材链接\n     */\n    private String url;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/dal/dataobject/materialgroup/MaterialGroupDO.java",
    "content": "package co.yixiang.yshop.module.shop.dal.dataobject.materialgroup;\n\nimport lombok.*;\nimport java.util.*;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\nimport com.baomidou.mybatisplus.annotation.*;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\n\n/**\n * 素材分组 DO\n *\n * @author yshop\n */\n@TableName(\"yshop_material_group\")\n@KeySequence(\"yshop_material_group_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class MaterialGroupDO extends BaseDO {\n\n    /**\n     * 编号\n     */\n    @TableId\n    private Long id;\n    /**\n     * 分组名\n     */\n    private String name;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/dal/dataobject/service/ServiceDO.java",
    "content": "package co.yixiang.yshop.module.shop.dal.dataobject.service;\n\nimport lombok.*;\nimport java.util.*;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\nimport com.baomidou.mybatisplus.annotation.*;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\n\n/**\n * 我的服务 DO\n *\n * @author yshop\n */\n@TableName(\"yshop_service\")\n@KeySequence(\"yshop_service_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class ServiceDO extends BaseDO {\n\n    /**\n     * id\n     */\n    @TableId\n    private Long id;\n    /**\n     * 标题\n     */\n    private String name;\n    /**\n     * 图标\n     */\n    private String image;\n    /**\n     * 类型:pages=页面,miniprogram=跳转小程序,menu=菜单,content=内容,call=电话\n     */\n    private String type;\n    /**\n     * 详情\n     */\n    private String content;\n    /**\n     * 父级id\n     */\n    private Integer pid;\n    /**\n     * 小程序app_id\n     */\n    private String appId;\n    /**\n     * 页面路径\n     */\n    private String pages;\n    /**\n     * 电话\n     */\n    private String phone;\n    /**\n     * 权重\n     */\n    private Integer weigh;\n    /**\n     * 状态:0=下架,1=上架\n     */\n    private Integer status;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/dal/dataobject/shopads/ShopAdsDO.java",
    "content": "package co.yixiang.yshop.module.shop.dal.dataobject.shopads;\n\nimport co.yixiang.yshop.framework.mybatis.core.type.StringListTypeHandler;\nimport lombok.*;\nimport java.util.*;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\nimport com.baomidou.mybatisplus.annotation.*;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\n\n/**\n * 广告图管理 DO\n *\n * @author yshop\n */\n@TableName(value = \"yshop_shop_ads\")\n@KeySequence(\"yshop_shop_ads_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class ShopAdsDO extends BaseDO {\n\n    /**\n     * id\n     */\n    @TableId\n    private Long id;\n    /**\n     * 图片\n     */\n    private String image;\n    /**\n     * 是否显现\n     */\n    private Integer isSwitch;\n    /**\n     * 权重\n     */\n    private Integer weigh;\n    /**\n     * 所支持的店铺id用','区分，0代表全部\n     */\n    private String shopId;\n\n    private String shopName;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/dal/mysql/material/MaterialMapper.java",
    "content": "package co.yixiang.yshop.module.shop.dal.mysql.material;\n\nimport java.util.*;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.module.shop.dal.dataobject.material.MaterialDO;\nimport org.apache.ibatis.annotations.Mapper;\nimport co.yixiang.yshop.module.shop.controller.admin.material.vo.*;\n\n/**\n * 素材库 Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface MaterialMapper extends BaseMapperX<MaterialDO> {\n\n    default PageResult<MaterialDO> selectPage(MaterialPageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<MaterialDO>()\n                .betweenIfPresent(MaterialDO::getCreateTime, reqVO.getCreateTime())\n                .eqIfPresent(MaterialDO::getType, reqVO.getType())\n                .eqIfPresent(MaterialDO::getGroupId, reqVO.getGroupId())\n                .likeIfPresent(MaterialDO::getName, reqVO.getName())\n                .eqIfPresent(MaterialDO::getUrl, reqVO.getUrl())\n                .orderByDesc(MaterialDO::getId));\n    }\n\n    default List<MaterialDO> selectList(MaterialExportReqVO reqVO) {\n        return selectList(new LambdaQueryWrapperX<MaterialDO>()\n                .betweenIfPresent(MaterialDO::getCreateTime, reqVO.getCreateTime())\n                .eqIfPresent(MaterialDO::getType, reqVO.getType())\n                .eqIfPresent(MaterialDO::getGroupId, reqVO.getGroupId())\n                .likeIfPresent(MaterialDO::getName, reqVO.getName())\n                .eqIfPresent(MaterialDO::getUrl, reqVO.getUrl())\n                .orderByDesc(MaterialDO::getId));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/dal/mysql/materialgroup/MaterialGroupMapper.java",
    "content": "package co.yixiang.yshop.module.shop.dal.mysql.materialgroup;\n\nimport java.util.*;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.module.shop.dal.dataobject.materialgroup.MaterialGroupDO;\nimport org.apache.ibatis.annotations.Mapper;\nimport co.yixiang.yshop.module.shop.controller.admin.materialgroup.vo.*;\n\n/**\n * 素材分组 Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface MaterialGroupMapper extends BaseMapperX<MaterialGroupDO> {\n\n    default PageResult<MaterialGroupDO> selectPage(MaterialGroupPageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<MaterialGroupDO>()\n                .betweenIfPresent(MaterialGroupDO::getCreateTime, reqVO.getCreateTime())\n                .likeIfPresent(MaterialGroupDO::getName, reqVO.getName())\n                .orderByDesc(MaterialGroupDO::getId));\n    }\n\n    default List<MaterialGroupDO> selectList(MaterialGroupExportReqVO reqVO) {\n        return selectList(new LambdaQueryWrapperX<MaterialGroupDO>()\n                .betweenIfPresent(MaterialGroupDO::getCreateTime, reqVO.getCreateTime())\n                .likeIfPresent(MaterialGroupDO::getName, reqVO.getName())\n                .orderByDesc(MaterialGroupDO::getId));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/dal/mysql/service/ServiceMapper.java",
    "content": "package co.yixiang.yshop.module.shop.dal.mysql.service;\n\nimport java.util.*;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.module.shop.dal.dataobject.service.ServiceDO;\nimport org.apache.ibatis.annotations.Mapper;\nimport co.yixiang.yshop.module.shop.controller.admin.service.vo.*;\n\n/**\n * 我的服务 Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface ServiceMapper extends BaseMapperX<ServiceDO> {\n\n    default PageResult<ServiceDO> selectPage(ServicePageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<ServiceDO>()\n                .likeIfPresent(ServiceDO::getName, reqVO.getName())\n                .eqIfPresent(ServiceDO::getImage, reqVO.getImage())\n                .eqIfPresent(ServiceDO::getType, reqVO.getType())\n                .eqIfPresent(ServiceDO::getContent, reqVO.getContent())\n                .eqIfPresent(ServiceDO::getPid, reqVO.getPid())\n                .eqIfPresent(ServiceDO::getAppId, reqVO.getAppId())\n                .eqIfPresent(ServiceDO::getPages, reqVO.getPages())\n                .eqIfPresent(ServiceDO::getPhone, reqVO.getPhone())\n                .eqIfPresent(ServiceDO::getWeigh, reqVO.getWeigh())\n                .eqIfPresent(ServiceDO::getStatus, reqVO.getStatus())\n                .betweenIfPresent(ServiceDO::getCreateTime, reqVO.getCreateTime())\n                .orderByDesc(ServiceDO::getId));\n    }\n\n    default List<ServiceDO> selectList(ServiceExportReqVO reqVO) {\n        return selectList(new LambdaQueryWrapperX<ServiceDO>()\n                .likeIfPresent(ServiceDO::getName, reqVO.getName())\n                .eqIfPresent(ServiceDO::getImage, reqVO.getImage())\n                .eqIfPresent(ServiceDO::getType, reqVO.getType())\n                .eqIfPresent(ServiceDO::getContent, reqVO.getContent())\n                .eqIfPresent(ServiceDO::getPid, reqVO.getPid())\n                .eqIfPresent(ServiceDO::getAppId, reqVO.getAppId())\n                .eqIfPresent(ServiceDO::getPages, reqVO.getPages())\n                .eqIfPresent(ServiceDO::getPhone, reqVO.getPhone())\n                .eqIfPresent(ServiceDO::getWeigh, reqVO.getWeigh())\n                .eqIfPresent(ServiceDO::getStatus, reqVO.getStatus())\n                .betweenIfPresent(ServiceDO::getCreateTime, reqVO.getCreateTime())\n                .orderByDesc(ServiceDO::getId));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/dal/mysql/shopads/ShopAdsMapper.java",
    "content": "package co.yixiang.yshop.module.shop.dal.mysql.shopads;\n\nimport java.util.*;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.module.shop.dal.dataobject.shopads.ShopAdsDO;\nimport org.apache.ibatis.annotations.Mapper;\nimport co.yixiang.yshop.module.shop.controller.admin.shopads.vo.*;\n\n/**\n * 广告图管理 Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface ShopAdsMapper extends BaseMapperX<ShopAdsDO> {\n\n    default PageResult<ShopAdsDO> selectPage(ShopAdsPageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<ShopAdsDO>()\n                .eqIfPresent(ShopAdsDO::getImage, reqVO.getImage())\n                .eqIfPresent(ShopAdsDO::getIsSwitch, reqVO.getIsSwitch())\n                .eqIfPresent(ShopAdsDO::getWeigh, reqVO.getWeigh())\n                .likeIfPresent(ShopAdsDO::getShopName, reqVO.getShopName())\n                .betweenIfPresent(ShopAdsDO::getCreateTime, reqVO.getCreateTime())\n                .orderByDesc(ShopAdsDO::getId));\n    }\n\n    default List<ShopAdsDO> selectList(ShopAdsExportReqVO reqVO) {\n        return selectList(new LambdaQueryWrapperX<ShopAdsDO>()\n                .eqIfPresent(ShopAdsDO::getImage, reqVO.getImage())\n                .eqIfPresent(ShopAdsDO::getIsSwitch, reqVO.getIsSwitch())\n                .eqIfPresent(ShopAdsDO::getWeigh, reqVO.getWeigh())\n                .eqIfPresent(ShopAdsDO::getShopId, reqVO.getShopId())\n                .betweenIfPresent(ShopAdsDO::getCreateTime, reqVO.getCreateTime())\n                .orderByDesc(ShopAdsDO::getId));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/framework/package-info.java",
    "content": "/**\n * 属于 promotion 模块的 framework 封装\n *\n * @author yshop\n */\npackage co.yixiang.yshop.module.shop.framework;\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/framework/web/config/ShopWebConfiguration.java",
    "content": "package co.yixiang.yshop.module.shop.framework.web.config;\n\nimport co.yixiang.yshop.framework.swagger.config.YshopSwaggerAutoConfiguration;\nimport org.springdoc.core.models.GroupedOpenApi;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n/**\n * shop 模块的 web 组件的 Configuration\n *\n * @author yshop\n */\n@Configuration(proxyBeanMethods = false)\npublic class ShopWebConfiguration {\n\n    /**\n     * promotion 模块的 API 分组\n     */\n    @Bean\n    public GroupedOpenApi shopGroupedOpenApi() {\n        return YshopSwaggerAutoConfiguration.buildGroupedOpenApi(\"shop\");\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/framework/web/package-info.java",
    "content": "/**\n * promotion 模块的 web 配置\n */\npackage co.yixiang.yshop.module.shop.framework.web;\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/service/material/MaterialService.java",
    "content": "package co.yixiang.yshop.module.shop.service.material;\n\nimport java.util.*;\nimport jakarta.validation.*;\nimport co.yixiang.yshop.module.shop.controller.admin.material.vo.*;\nimport co.yixiang.yshop.module.shop.dal.dataobject.material.MaterialDO;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\n/**\n * 素材库 Service 接口\n *\n * @author yshop\n */\npublic interface MaterialService {\n\n    /**\n     * 创建素材库\n     *\n     * @param createReqVO 创建信息\n     * @return 编号\n     */\n    Long createMaterial(@Valid MaterialCreateReqVO createReqVO);\n\n    /**\n     * 更新素材库\n     *\n     * @param updateReqVO 更新信息\n     */\n    void updateMaterial(@Valid MaterialUpdateReqVO updateReqVO);\n\n    /**\n     * 删除素材库\n     *\n     * @param id 编号\n     */\n    void deleteMaterial(Long id);\n\n    /**\n     * 获得素材库\n     *\n     * @param id 编号\n     * @return 素材库\n     */\n    MaterialDO getMaterial(Long id);\n\n    /**\n     * 获得素材库列表\n     *\n     * @param ids 编号\n     * @return 素材库列表\n     */\n    List<MaterialDO> getMaterialList(Collection<Long> ids);\n\n    /**\n     * 获得素材库分页\n     *\n     * @param pageReqVO 分页查询\n     * @return 素材库分页\n     */\n    PageResult<MaterialDO> getMaterialPage(MaterialPageReqVO pageReqVO);\n\n    /**\n     * 获得素材库列表, 用于 Excel 导出\n     *\n     * @param exportReqVO 查询条件\n     * @return 素材库列表\n     */\n    List<MaterialDO> getMaterialList(MaterialExportReqVO exportReqVO);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/service/material/MaterialServiceImpl.java",
    "content": "package co.yixiang.yshop.module.shop.service.material;\n\nimport org.springframework.stereotype.Service;\nimport jakarta.annotation.Resource;\nimport org.springframework.validation.annotation.Validated;\n\nimport java.util.*;\nimport co.yixiang.yshop.module.shop.controller.admin.material.vo.*;\nimport co.yixiang.yshop.module.shop.dal.dataobject.material.MaterialDO;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\nimport co.yixiang.yshop.module.shop.convert.material.MaterialConvert;\nimport co.yixiang.yshop.module.shop.dal.mysql.material.MaterialMapper;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.shop.enums.ErrorCodeConstants.*;\n\n/**\n * 素材库 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class MaterialServiceImpl implements MaterialService {\n\n    @Resource\n    private MaterialMapper materialMapper;\n\n    @Override\n    public Long createMaterial(MaterialCreateReqVO createReqVO) {\n        // 插入\n        MaterialDO material = MaterialConvert.INSTANCE.convert(createReqVO);\n        materialMapper.insert(material);\n        // 返回\n        return material.getId();\n    }\n\n    @Override\n    public void updateMaterial(MaterialUpdateReqVO updateReqVO) {\n        // 校验存在\n        validateMaterialExists(updateReqVO.getId());\n        // 更新\n        MaterialDO updateObj = MaterialConvert.INSTANCE.convert(updateReqVO);\n        materialMapper.updateById(updateObj);\n    }\n\n    @Override\n    public void deleteMaterial(Long id) {\n        // 校验存在\n        validateMaterialExists(id);\n        // 删除\n        materialMapper.deleteById(id);\n    }\n\n    private void validateMaterialExists(Long id) {\n        if (materialMapper.selectById(id) == null) {\n            throw exception(MATERIAL_NOT_EXISTS);\n        }\n    }\n\n    @Override\n    public MaterialDO getMaterial(Long id) {\n        return materialMapper.selectById(id);\n    }\n\n    @Override\n    public List<MaterialDO> getMaterialList(Collection<Long> ids) {\n        return materialMapper.selectBatchIds(ids);\n    }\n\n    @Override\n    public PageResult<MaterialDO> getMaterialPage(MaterialPageReqVO pageReqVO) {\n        return materialMapper.selectPage(pageReqVO);\n    }\n\n    @Override\n    public List<MaterialDO> getMaterialList(MaterialExportReqVO exportReqVO) {\n        return materialMapper.selectList(exportReqVO);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/service/materialgroup/MaterialGroupService.java",
    "content": "package co.yixiang.yshop.module.shop.service.materialgroup;\n\nimport java.util.*;\nimport jakarta.validation.*;\nimport co.yixiang.yshop.module.shop.controller.admin.materialgroup.vo.*;\nimport co.yixiang.yshop.module.shop.dal.dataobject.materialgroup.MaterialGroupDO;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\n/**\n * 素材分组 Service 接口\n *\n * @author yshop\n */\npublic interface MaterialGroupService {\n\n    /**\n     * 创建素材分组\n     *\n     * @param createReqVO 创建信息\n     * @return 编号\n     */\n    Long createMaterialGroup(@Valid MaterialGroupCreateReqVO createReqVO);\n\n    /**\n     * 更新素材分组\n     *\n     * @param updateReqVO 更新信息\n     */\n    void updateMaterialGroup(@Valid MaterialGroupUpdateReqVO updateReqVO);\n\n    /**\n     * 删除素材分组\n     *\n     * @param id 编号\n     */\n    void deleteMaterialGroup(Long id);\n\n    /**\n     * 获得素材分组\n     *\n     * @param id 编号\n     * @return 素材分组\n     */\n    MaterialGroupDO getMaterialGroup(Long id);\n\n    /**\n     * 获得素材分组列表\n     *\n     * @param ids 编号\n     * @return 素材分组列表\n     */\n    List<MaterialGroupDO> getMaterialGroupList(Collection<Long> ids);\n\n    /**\n     * 获得素材分组分页\n     *\n     * @param pageReqVO 分页查询\n     * @return 素材分组分页\n     */\n    PageResult<MaterialGroupDO> getMaterialGroupPage(MaterialGroupPageReqVO pageReqVO);\n\n    /**\n     * 获得素材分组列表, 用于 Excel 导出\n     *\n     * @param exportReqVO 查询条件\n     * @return 素材分组列表\n     */\n    List<MaterialGroupDO> getMaterialGroupList(MaterialGroupExportReqVO exportReqVO);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/service/materialgroup/MaterialGroupServiceImpl.java",
    "content": "package co.yixiang.yshop.module.shop.service.materialgroup;\n\nimport org.springframework.stereotype.Service;\nimport jakarta.annotation.Resource;\nimport org.springframework.validation.annotation.Validated;\n\nimport java.util.*;\nimport co.yixiang.yshop.module.shop.controller.admin.materialgroup.vo.*;\nimport co.yixiang.yshop.module.shop.dal.dataobject.materialgroup.MaterialGroupDO;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\nimport co.yixiang.yshop.module.shop.convert.materialgroup.MaterialGroupConvert;\nimport co.yixiang.yshop.module.shop.dal.mysql.materialgroup.MaterialGroupMapper;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.shop.enums.ErrorCodeConstants.*;\n\n/**\n * 素材分组 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class MaterialGroupServiceImpl implements MaterialGroupService {\n\n    @Resource\n    private MaterialGroupMapper materialGroupMapper;\n\n    @Override\n    public Long createMaterialGroup(MaterialGroupCreateReqVO createReqVO) {\n        // 插入\n        MaterialGroupDO materialGroup = MaterialGroupConvert.INSTANCE.convert(createReqVO);\n        materialGroupMapper.insert(materialGroup);\n        // 返回\n        return materialGroup.getId();\n    }\n\n    @Override\n    public void updateMaterialGroup(MaterialGroupUpdateReqVO updateReqVO) {\n        // 校验存在\n        validateMaterialGroupExists(updateReqVO.getId());\n        // 更新\n        MaterialGroupDO updateObj = MaterialGroupConvert.INSTANCE.convert(updateReqVO);\n        materialGroupMapper.updateById(updateObj);\n    }\n\n    @Override\n    public void deleteMaterialGroup(Long id) {\n        // 校验存在\n        validateMaterialGroupExists(id);\n        // 删除\n        materialGroupMapper.deleteById(id);\n    }\n\n    private void validateMaterialGroupExists(Long id) {\n        if (materialGroupMapper.selectById(id) == null) {\n            throw exception(MATERIAL_GROUP_NOT_EXISTS);\n        }\n    }\n\n    @Override\n    public MaterialGroupDO getMaterialGroup(Long id) {\n        return materialGroupMapper.selectById(id);\n    }\n\n    @Override\n    public List<MaterialGroupDO> getMaterialGroupList(Collection<Long> ids) {\n        return materialGroupMapper.selectBatchIds(ids);\n    }\n\n    @Override\n    public PageResult<MaterialGroupDO> getMaterialGroupPage(MaterialGroupPageReqVO pageReqVO) {\n        return materialGroupMapper.selectPage(pageReqVO);\n    }\n\n    @Override\n    public List<MaterialGroupDO> getMaterialGroupList(MaterialGroupExportReqVO exportReqVO) {\n        return materialGroupMapper.selectList(exportReqVO);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/service/service/AppServiceService.java",
    "content": "package co.yixiang.yshop.module.shop.service.service;\n\nimport co.yixiang.yshop.module.shop.dal.dataobject.service.ServiceDO;\nimport com.baomidou.mybatisplus.extension.service.IService;\n\n/**\n * 我的服务 Service 接口\n *\n * @author yshop\n */\npublic interface AppServiceService extends IService<ServiceDO> {\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/service/service/AppServiceServiceImpl.java",
    "content": "package co.yixiang.yshop.module.shop.service.service;\n\nimport co.yixiang.yshop.module.shop.dal.dataobject.service.ServiceDO;\nimport co.yixiang.yshop.module.shop.dal.mysql.service.ServiceMapper;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\n\n/**\n * 我的服务 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class AppServiceServiceImpl extends ServiceImpl<ServiceMapper, ServiceDO> implements AppServiceService {\n\n    @Resource\n    private ServiceMapper serviceMapper;\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/service/service/ServiceService.java",
    "content": "package co.yixiang.yshop.module.shop.service.service;\n\nimport java.util.*;\nimport jakarta.validation.*;\nimport co.yixiang.yshop.module.shop.controller.admin.service.vo.*;\nimport co.yixiang.yshop.module.shop.dal.dataobject.service.ServiceDO;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\n/**\n * 我的服务 Service 接口\n *\n * @author yshop\n */\npublic interface ServiceService {\n\n    /**\n     * 创建我的服务\n     *\n     * @param createReqVO 创建信息\n     * @return 编号\n     */\n    Long createService(@Valid ServiceCreateReqVO createReqVO);\n\n    /**\n     * 更新我的服务\n     *\n     * @param updateReqVO 更新信息\n     */\n    void updateService(@Valid ServiceUpdateReqVO updateReqVO);\n\n    /**\n     * 删除我的服务\n     *\n     * @param id 编号\n     */\n    void deleteService(Long id);\n\n    /**\n     * 获得我的服务\n     *\n     * @param id 编号\n     * @return 我的服务\n     */\n    ServiceDO getService(Long id);\n\n    /**\n     * 获得我的服务列表\n     *\n     * @param ids 编号\n     * @return 我的服务列表\n     */\n    List<ServiceDO> getServiceList(Collection<Long> ids);\n\n    /**\n     * 获得我的服务分页\n     *\n     * @param pageReqVO 分页查询\n     * @return 我的服务分页\n     */\n    PageResult<ServiceDO> getServicePage(ServicePageReqVO pageReqVO);\n\n    /**\n     * 获得我的服务列表, 用于 Excel 导出\n     *\n     * @param exportReqVO 查询条件\n     * @return 我的服务列表\n     */\n    List<ServiceDO> getServiceList(ServiceExportReqVO exportReqVO);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/service/service/ServiceServiceImpl.java",
    "content": "package co.yixiang.yshop.module.shop.service.service;\n\nimport org.springframework.stereotype.Service;\nimport jakarta.annotation.Resource;\nimport org.springframework.validation.annotation.Validated;\n\nimport java.util.*;\nimport co.yixiang.yshop.module.shop.controller.admin.service.vo.*;\nimport co.yixiang.yshop.module.shop.dal.dataobject.service.ServiceDO;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\nimport co.yixiang.yshop.module.shop.convert.service.ServiceConvert;\nimport co.yixiang.yshop.module.shop.dal.mysql.service.ServiceMapper;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.shop.enums.ErrorCodeConstants.*;\n\n/**\n * 我的服务 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class ServiceServiceImpl implements ServiceService {\n\n    @Resource\n    private ServiceMapper serviceMapper;\n\n    @Override\n    public Long createService(ServiceCreateReqVO createReqVO) {\n        // 插入\n        ServiceDO service = ServiceConvert.INSTANCE.convert(createReqVO);\n        serviceMapper.insert(service);\n        // 返回\n        return service.getId();\n    }\n\n    @Override\n    public void updateService(ServiceUpdateReqVO updateReqVO) {\n        // 校验存在\n        validateServiceExists(updateReqVO.getId());\n        // 更新\n        ServiceDO updateObj = ServiceConvert.INSTANCE.convert(updateReqVO);\n        serviceMapper.updateById(updateObj);\n    }\n\n    @Override\n    public void deleteService(Long id) {\n        // 校验存在\n        validateServiceExists(id);\n        // 删除\n        serviceMapper.deleteById(id);\n    }\n\n    private void validateServiceExists(Long id) {\n        if (serviceMapper.selectById(id) == null) {\n            throw exception(SERVICE_NOT_EXISTS);\n        }\n    }\n\n    @Override\n    public ServiceDO getService(Long id) {\n        return serviceMapper.selectById(id);\n    }\n\n    @Override\n    public List<ServiceDO> getServiceList(Collection<Long> ids) {\n        return serviceMapper.selectBatchIds(ids);\n    }\n\n    @Override\n    public PageResult<ServiceDO> getServicePage(ServicePageReqVO pageReqVO) {\n        return serviceMapper.selectPage(pageReqVO);\n    }\n\n    @Override\n    public List<ServiceDO> getServiceList(ServiceExportReqVO exportReqVO) {\n        return serviceMapper.selectList(exportReqVO);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/service/shopads/AppShopAdsService.java",
    "content": "package co.yixiang.yshop.module.shop.service.shopads;\n\nimport co.yixiang.yshop.module.shop.dal.dataobject.shopads.ShopAdsDO;\nimport com.baomidou.mybatisplus.extension.service.IService;\n\n/**\n * 广告图管理 Service 接口\n *\n * @author yshop\n */\npublic interface AppShopAdsService extends IService<ShopAdsDO> {\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/service/shopads/AppShopAdsServiceImpl.java",
    "content": "package co.yixiang.yshop.module.shop.service.shopads;\n\nimport co.yixiang.yshop.module.shop.dal.dataobject.shopads.ShopAdsDO;\nimport co.yixiang.yshop.module.shop.dal.mysql.shopads.ShopAdsMapper;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\n/**\n * 商品 AppShopAds 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class AppShopAdsServiceImpl extends ServiceImpl<ShopAdsMapper, ShopAdsDO> implements AppShopAdsService {\n\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/service/shopads/ShopAdsService.java",
    "content": "package co.yixiang.yshop.module.shop.service.shopads;\n\nimport java.util.*;\nimport jakarta.validation.*;\nimport co.yixiang.yshop.module.shop.controller.admin.shopads.vo.*;\nimport co.yixiang.yshop.module.shop.dal.dataobject.shopads.ShopAdsDO;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\n/**\n * 广告图管理 Service 接口\n *\n * @author yshop\n */\npublic interface ShopAdsService {\n\n    /**\n     * 创建广告图管理\n     *\n     * @param createReqVO 创建信息\n     * @return 编号\n     */\n    Long createAds(@Valid ShopAdsCreateReqVO createReqVO);\n\n    /**\n     * 更新广告图管理\n     *\n     * @param updateReqVO 更新信息\n     */\n    void updateAds(@Valid ShopAdsUpdateReqVO updateReqVO);\n\n    /**\n     * 删除广告图管理\n     *\n     * @param id 编号\n     */\n    void deleteAds(Long id);\n\n    /**\n     * 获得广告图管理\n     *\n     * @param id 编号\n     * @return 广告图管理\n     */\n    ShopAdsDO getAds(Long id);\n\n    /**\n     * 获得广告图管理列表\n     *\n     * @param ids 编号\n     * @return 广告图管理列表\n     */\n    List<ShopAdsDO> getAdsList(Collection<Long> ids);\n\n    /**\n     * 获得广告图管理分页\n     *\n     * @param pageReqVO 分页查询\n     * @return 广告图管理分页\n     */\n    PageResult<ShopAdsDO> getAdsPage(ShopAdsPageReqVO pageReqVO);\n\n    /**\n     * 获得广告图管理列表, 用于 Excel 导出\n     *\n     * @param exportReqVO 查询条件\n     * @return 广告图管理列表\n     */\n    List<ShopAdsDO> getAdsList(ShopAdsExportReqVO exportReqVO);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/java/co/yixiang/yshop/module/shop/service/shopads/ShopAdsServiceImpl.java",
    "content": "package co.yixiang.yshop.module.shop.service.shopads;\n\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.constant.ShopConstants;\nimport co.yixiang.yshop.module.store.dal.dataobject.storeshop.StoreShopDO;\nimport co.yixiang.yshop.module.store.dal.mysql.storeshop.StoreShopMapper;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport org.springframework.stereotype.Service;\nimport jakarta.annotation.Resource;\nimport org.springframework.validation.annotation.Validated;\n\nimport java.util.*;\nimport java.util.stream.Collectors;\n\nimport co.yixiang.yshop.module.shop.controller.admin.shopads.vo.*;\nimport co.yixiang.yshop.module.shop.dal.dataobject.shopads.ShopAdsDO;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\nimport co.yixiang.yshop.module.shop.convert.shopads.ShopAdsConvert;\nimport co.yixiang.yshop.module.shop.dal.mysql.shopads.ShopAdsMapper;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.shop.enums.ErrorCodeConstants.*;\n\n/**\n * 广告图管理 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class ShopAdsServiceImpl implements ShopAdsService {\n\n    @Resource\n    private ShopAdsMapper adsMapper;\n    @Resource\n    private StoreShopMapper storeShopMapper;\n\n    @Override\n    public Long createAds(ShopAdsCreateReqVO createReqVO) {\n\n        // 插入\n        ShopAdsDO ads = ShopAdsConvert.INSTANCE.convert(createReqVO);\n        StoreShopDO storeShopDO = storeShopMapper.selectById(createReqVO.getShopId());\n        if(storeShopDO == null){\n            ads.setShopName(\"全部\");\n        }else{\n            ads.setShopName(storeShopDO.getName());\n        }\n        adsMapper.insert(ads);\n        // 返回\n        return ads.getId();\n    }\n\n    @Override\n    public void updateAds(ShopAdsUpdateReqVO updateReqVO) {\n        // 校验存在\n        validateAdsExists(updateReqVO.getId());\n        ShopAdsDO updateObj = ShopAdsConvert.INSTANCE.convert(updateReqVO);\n        StoreShopDO storeShopDO= storeShopMapper.selectById(updateReqVO.getShopId());\n        if(storeShopDO == null){\n            updateObj.setShopName(\"全部\");\n        }else{\n            updateObj.setShopName(storeShopDO.getName());\n        }\n        adsMapper.updateById(updateObj);\n    }\n\n    @Override\n    public void deleteAds(Long id) {\n        // 校验存在\n        validateAdsExists(id);\n        // 删除\n        adsMapper.deleteById(id);\n    }\n\n    private void validateAdsExists(Long id) {\n        if (adsMapper.selectById(id) == null) {\n            throw exception(ADS_NOT_EXISTS);\n        }\n    }\n\n    @Override\n    public ShopAdsDO getAds(Long id) {\n        return adsMapper.selectById(id);\n    }\n\n    @Override\n    public List<ShopAdsDO> getAdsList(Collection<Long> ids) {\n        return adsMapper.selectBatchIds(ids);\n    }\n\n    @Override\n    public PageResult<ShopAdsDO> getAdsPage(ShopAdsPageReqVO pageReqVO) {\n        return adsMapper.selectPage(pageReqVO);\n    }\n\n    @Override\n    public List<ShopAdsDO> getAdsList(ShopAdsExportReqVO exportReqVO) {\n        return adsMapper.selectList(exportReqVO);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/resources/mapper/material/MaterialMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"co.yixiang.yshop.module.shop.dal.mysql.material.MaterialMapper\">\n\n    <!--\n        一般情况下，尽可能使用 Mapper 进行 CRUD 增删改查即可。\n        无法满足的场景，例如说多表关联查询，才使用 XML 编写 SQL。\n        代码生成器暂时只生成 Mapper XML 文件本身，更多推荐 MybatisX 快速开发插件来生成查询。\n        文档可见：https://www.yixiang.co/MyBatis/x-plugins/\n     -->\n\n</mapper>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/main/resources/mapper/materialgroup/MaterialGroupMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"co.yixiang.yshop.module.shop.dal.mysql.materialgroup.MaterialGroupMapper\">\n\n    <!--\n        一般情况下，尽可能使用 Mapper 进行 CRUD 增删改查即可。\n        无法满足的场景，例如说多表关联查询，才使用 XML 编写 SQL。\n        代码生成器暂时只生成 Mapper XML 文件本身，更多推荐 MybatisX 快速开发插件来生成查询。\n        文档可见：https://www.yixiang.co/MyBatis/x-plugins/\n     -->\n\n</mapper>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-shop-biz/src/test/java/co/yixiang/yshop/module/shop/service/material/MaterialServiceImplTest.java",
    "content": "package co.yixiang.yshop.module.shop.service.material;\n\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.mock.mockito.MockBean;\n\nimport jakarta.annotation.Resource;\n\nimport co.yixiang.yshop.framework.test.core.ut.BaseDbUnitTest;\n\nimport co.yixiang.yshop.module.shop.controller.admin.material.vo.*;\nimport co.yixiang.yshop.module.shop.dal.dataobject.material.MaterialDO;\nimport co.yixiang.yshop.module.shop.dal.mysql.material.MaterialMapper;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\nimport jakarta.annotation.Resource;\nimport org.springframework.context.annotation.Import;\nimport java.util.*;\nimport java.time.LocalDateTime;\n\nimport static cn.hutool.core.util.RandomUtil.*;\nimport static co.yixiang.yshop.module.shop.enums.ErrorCodeConstants.*;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.*;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.*;\nimport static co.yixiang.yshop.framework.common.util.date.LocalDateTimeUtils.*;\nimport static co.yixiang.yshop.framework.common.util.object.ObjectUtils.*;\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.*;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.*;\n\n/**\n* {@link MaterialServiceImpl} 的单元测试类\n*\n* @author yshop\n*/\n@Import(MaterialServiceImpl.class)\npublic class MaterialServiceImplTest extends BaseDbUnitTest {\n\n    @Resource\n    private MaterialServiceImpl materialService;\n\n    @Resource\n    private MaterialMapper materialMapper;\n\n    @Test\n    public void testCreateMaterial_success() {\n        // 准备参数\n        MaterialCreateReqVO reqVO = randomPojo(MaterialCreateReqVO.class);\n\n        // 调用\n        Long materialId = materialService.createMaterial(reqVO);\n        // 断言\n        assertNotNull(materialId);\n        // 校验记录的属性是否正确\n        MaterialDO material = materialMapper.selectById(materialId);\n        assertPojoEquals(reqVO, material);\n    }\n\n    @Test\n    public void testUpdateMaterial_success() {\n        // mock 数据\n        MaterialDO dbMaterial = randomPojo(MaterialDO.class);\n        materialMapper.insert(dbMaterial);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        MaterialUpdateReqVO reqVO = randomPojo(MaterialUpdateReqVO.class, o -> {\n            o.setId(dbMaterial.getId()); // 设置更新的 ID\n        });\n\n        // 调用\n        materialService.updateMaterial(reqVO);\n        // 校验是否更新正确\n        MaterialDO material = materialMapper.selectById(reqVO.getId()); // 获取最新的\n        assertPojoEquals(reqVO, material);\n    }\n\n    @Test\n    public void testUpdateMaterial_notExists() {\n        // 准备参数\n        MaterialUpdateReqVO reqVO = randomPojo(MaterialUpdateReqVO.class);\n\n        // 调用, 并断言异常\n        assertServiceException(() -> materialService.updateMaterial(reqVO), MATERIAL_NOT_EXISTS);\n    }\n\n    @Test\n    public void testDeleteMaterial_success() {\n        // mock 数据\n        MaterialDO dbMaterial = randomPojo(MaterialDO.class);\n        materialMapper.insert(dbMaterial);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        Long id = dbMaterial.getId();\n\n        // 调用\n        materialService.deleteMaterial(id);\n       // 校验数据不存在了\n       assertNull(materialMapper.selectById(id));\n    }\n\n    @Test\n    public void testDeleteMaterial_notExists() {\n        // 准备参数\n        Long id = randomLongId();\n\n        // 调用, 并断言异常\n        assertServiceException(() -> materialService.deleteMaterial(id), MATERIAL_NOT_EXISTS);\n    }\n\n    @Test\n    @Disabled  // TODO 请修改 null 为需要的值，然后删除 @Disabled 注解\n    public void testGetMaterialPage() {\n       // mock 数据\n       MaterialDO dbMaterial = randomPojo(MaterialDO.class, o -> { // 等会查询到\n           o.setCreateTime(null);\n           o.setType(null);\n           o.setGroupId(null);\n           o.setName(null);\n           o.setUrl(null);\n       });\n       materialMapper.insert(dbMaterial);\n       // 测试 createTime 不匹配\n       materialMapper.insert(cloneIgnoreId(dbMaterial, o -> o.setCreateTime(null)));\n       // 测试 type 不匹配\n       materialMapper.insert(cloneIgnoreId(dbMaterial, o -> o.setType(null)));\n       // 测试 groupId 不匹配\n       materialMapper.insert(cloneIgnoreId(dbMaterial, o -> o.setGroupId(null)));\n       // 测试 name 不匹配\n       materialMapper.insert(cloneIgnoreId(dbMaterial, o -> o.setName(null)));\n       // 测试 url 不匹配\n       materialMapper.insert(cloneIgnoreId(dbMaterial, o -> o.setUrl(null)));\n       // 准备参数\n       MaterialPageReqVO reqVO = new MaterialPageReqVO();\n       reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));\n       reqVO.setType(null);\n       reqVO.setGroupId(null);\n       reqVO.setName(null);\n       reqVO.setUrl(null);\n\n       // 调用\n       PageResult<MaterialDO> pageResult = materialService.getMaterialPage(reqVO);\n       // 断言\n       assertEquals(1, pageResult.getTotal());\n       assertEquals(1, pageResult.getList().size());\n       assertPojoEquals(dbMaterial, pageResult.getList().get(0));\n    }\n\n    @Test\n    @Disabled  // TODO 请修改 null 为需要的值，然后删除 @Disabled 注解\n    public void testGetMaterialList() {\n       // mock 数据\n       MaterialDO dbMaterial = randomPojo(MaterialDO.class, o -> { // 等会查询到\n           o.setCreateTime(null);\n           o.setType(null);\n           o.setGroupId(null);\n           o.setName(null);\n           o.setUrl(null);\n       });\n       materialMapper.insert(dbMaterial);\n       // 测试 createTime 不匹配\n       materialMapper.insert(cloneIgnoreId(dbMaterial, o -> o.setCreateTime(null)));\n       // 测试 type 不匹配\n       materialMapper.insert(cloneIgnoreId(dbMaterial, o -> o.setType(null)));\n       // 测试 groupId 不匹配\n       materialMapper.insert(cloneIgnoreId(dbMaterial, o -> o.setGroupId(null)));\n       // 测试 name 不匹配\n       materialMapper.insert(cloneIgnoreId(dbMaterial, o -> o.setName(null)));\n       // 测试 url 不匹配\n       materialMapper.insert(cloneIgnoreId(dbMaterial, o -> o.setUrl(null)));\n       // 准备参数\n       MaterialExportReqVO reqVO = new MaterialExportReqVO();\n       reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 28));\n       reqVO.setType(null);\n       reqVO.setGroupId(null);\n       reqVO.setName(null);\n       reqVO.setUrl(null);\n\n       // 调用\n       List<MaterialDO> list = materialService.getMaterialList(reqVO);\n       // 断言\n       assertEquals(1, list.size());\n       assertPojoEquals(dbMaterial, list.get(0));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-store-api/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <parent>\n        <groupId>co.yixiang.boot</groupId>\n        <artifactId>yshop-module-mall</artifactId>\n        <version>${revision}</version>\n    </parent>\n\n    <artifactId>yshop-module-store-api</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>\n        store 模块 API，暴露给其它模块调用\n    </description>\n\n    <dependencies>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-common</artifactId>\n        </dependency>\n\n        <!-- 参数校验 -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-validation</artifactId>\n            <optional>true</optional>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-store-api/src/main/java/co/yixiang/yshop/module/store/enums/ErrorCodeConstants.java",
    "content": "package co.yixiang.yshop.module.store.enums;\n\nimport co.yixiang.yshop.framework.common.exception.ErrorCode;\n\npublic interface ErrorCodeConstants {\n    ErrorCode SHOP_NOT_EXISTS = new ErrorCode(1008016000, \"门店管理不存在\");\n    ErrorCode WEB_PRINT_NOT_EXISTS = new ErrorCode(1008016001, \"易联云打印机不存在\");\n    ErrorCode STORE_REVENUE_NOT_EXISTS = new ErrorCode(1008016002, \"店铺收支明细不存在\");\n    ErrorCode WITHDRAWAL_NOT_EXISTS = new ErrorCode(1008016003, \"提现管理不存在\");\n    ErrorCode USER_BANK_NOT_EXISTS = new ErrorCode(1008016004, \"提现账户不存在\");\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-store-api/src/main/java/co/yixiang/yshop/module/store/enums/WithdrawalStatusEnum.java",
    "content": "package co.yixiang.yshop.module.store.enums;\r\n\r\n\r\nimport lombok.AllArgsConstructor;\r\nimport lombok.Getter;\r\n\r\n/**\r\n * @author hupeng\r\n * 类型枚举\r\n */\r\n@Getter\r\n@AllArgsConstructor\r\npublic enum WithdrawalStatusEnum {\r\n    STATUS_0(0,\"未审核\"),\r\n    STATUS_1(1,\"待到账\"),\r\n    STATUS_2(2,\"审核拒绝\"),\r\n    STATUS_3(3,\"已到账\");\r\n\r\n    private Integer value;\r\n    private String desc;\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-store-biz/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>co.yixiang.boot</groupId>\n        <artifactId>yshop-module-mall</artifactId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>yshop-module-store-biz</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>\n        门店 模块，主要实现商品购物车相关功能\n    </description>\n\n    <dependencies>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-store-api</artifactId>\n            <version>${revision}</version>\n        </dependency>\n\n\n        <!-- 业务组件 -->\n<!--        <dependency>-->\n<!--            <groupId>co.yixiang.boot</groupId>-->\n<!--            <artifactId>yshop-spring-boot-starter-biz-operatelog</artifactId>-->\n<!--        </dependency>-->\n\n        <!-- Web 相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-web</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-security</artifactId>\n        </dependency>\n\n        <!-- DB 相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-mybatis</artifactId>\n        </dependency>\n\n        <!-- Test 测试相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-test</artifactId>\n        </dependency>\n\n        <!-- 工具类相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-excel</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-redis</artifactId>\n        </dependency>\n\n        <!-- Job 定时任务相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-job</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-biz-tenant</artifactId>\n        </dependency>\n\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-store-biz/src/main/java/co/yixiang/yshop/module/store/controller/admin/storeshop/StoreShopController.java",
    "content": "package co.yixiang.yshop.module.store.controller.admin.storeshop;\n\nimport jakarta.servlet.http.HttpServletResponse;\nimport org.springframework.web.bind.annotation.*;\nimport jakarta.annotation.Resource;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.Operation;\n\nimport jakarta.validation.constraints.*;\nimport jakarta.validation.*;\nimport java.util.*;\nimport java.io.IOException;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\nimport co.yixiang.yshop.framework.excel.core.util.ExcelUtils;\n\nimport co.yixiang.yshop.module.store.controller.admin.storeshop.vo.*;\nimport co.yixiang.yshop.module.store.dal.dataobject.storeshop.StoreShopDO;\nimport co.yixiang.yshop.module.store.convert.storeshop.StoreShopConvert;\nimport co.yixiang.yshop.module.store.service.storeshop.StoreShopService;\n\n@Tag(name = \"管理后台 - 门店管理\")\n@RestController\n@RequestMapping(\"/store/shop\")\n@Validated\npublic class StoreShopController {\n\n    @Resource\n    private StoreShopService shopService;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建门店管理\")\n    @PreAuthorize(\"@ss.hasPermission('store:shop:create')\")\n    public CommonResult<Long> createShop(@Valid @RequestBody StoreShopCreateReqVO createReqVO) {\n        return success(shopService.createShop(createReqVO));\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"更新门店管理\")\n    @PreAuthorize(\"@ss.hasPermission('store:shop:update')\")\n    public CommonResult<Boolean> updateShop(@Valid @RequestBody StoreShopUpdateReqVO updateReqVO) {\n        shopService.updateShop(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除门店管理\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('store:shop:delete')\")\n    public CommonResult<Boolean> deleteShop(@RequestParam(\"id\") Long id) {\n        shopService.deleteShop(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得门店管理\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('store:shop:query')\")\n    public CommonResult<StoreShopRespVO> getShop(@RequestParam(\"id\") Long id) {\n        StoreShopDO shop = shopService.getShop(id);\n        return success(StoreShopConvert.INSTANCE.convert(shop));\n    }\n\n    @GetMapping(\"/list\")\n    @Operation(summary = \"获得门店管理列表\")\n    @PreAuthorize(\"@ss.hasPermission('store:shop:query')\")\n    public CommonResult<List<StoreShopRespVO>> getShopList() {\n        List<StoreShopDO> list = shopService.getShopList();\n        return success(StoreShopConvert.INSTANCE.convertList(list));\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得门店管理分页\")\n    @PreAuthorize(\"@ss.hasPermission('store:shop:query')\")\n    public CommonResult<PageResult<StoreShopRespVO>> getShopPage(@Valid StoreShopPageReqVO pageVO) {\n        PageResult<StoreShopDO> pageResult = shopService.getShopPage(pageVO);\n        return success(StoreShopConvert.INSTANCE.convertPage(pageResult));\n    }\n\n    @GetMapping(\"/export-excel\")\n    @Operation(summary = \"导出门店管理 Excel\")\n    @PreAuthorize(\"@ss.hasPermission('store:shop:export')\")\n    public void exportShopExcel(@Valid StoreShopExportReqVO exportReqVO,\n              HttpServletResponse response) throws IOException {\n        List<StoreShopDO> list = shopService.getShopList(exportReqVO);\n        // 导出 Excel\n        List<StoreShopExcelVO> datas = StoreShopConvert.INSTANCE.convertList02(list);\n        ExcelUtils.write(response, \"门店管理.xls\", \"数据\", StoreShopExcelVO.class, datas);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-store-biz/src/main/java/co/yixiang/yshop/module/store/controller/admin/storeshop/vo/StoreShopBaseVO.java",
    "content": "package co.yixiang.yshop.module.store.controller.admin.storeshop.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.util.*;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\nimport jakarta.validation.constraints.*;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n/**\n* 门店管理 Base VO，提供给添加、修改、详细的子 VO 使用\n* 如果子 VO 存在差异的字段，请不要添加到这里，影响 Swagger 文档生成\n*/\n@Data\npublic class StoreShopBaseVO {\n\n    @Schema(description = \"店铺名称\", required = true, example = \"李四\")\n    @NotNull(message = \"店铺名称不能为空\")\n    private String name;\n\n    @Schema(description = \"店铺电话\", required = true)\n    @NotNull(message = \"店铺电话不能为空\")\n    private String mobile;\n\n    @Schema(description = \"图片\", required = true)\n    @NotNull(message = \"图片不能为空\")\n    private String image;\n\n    @Schema(description = \"多张图片\", required = true)\n    @NotNull(message = \"多张图片不能为空\")\n    private List<String> images;\n\n    @Schema(description = \"详细地址\", required = true)\n    @NotNull(message = \"详细地址不能为空\")\n    private String address;\n\n    @Schema(description = \"地图定位地址\", required = true)\n    @NotNull(message = \"地图定位地址不能为空\")\n    private String addressMap;\n\n    @Schema(description = \"经度\", required = true)\n    @NotNull(message = \"经度不能为空\")\n    private String lng;\n\n    @Schema(description = \"纬度\", required = true)\n    @NotNull(message = \"纬度不能为空\")\n    private String lat;\n\n    @Schema(description = \"外卖配送距离,单位为千米。0表示不送外卖\", required = true)\n    @NotNull(message = \"外卖配送距离,单位为千米。0表示不送外卖不能为空\")\n    private Integer distance;\n\n    @Schema(description = \"起送价钱\", required = true, example = \"15157\")\n    private BigDecimal minPrice;\n\n    @Schema(description = \"配送价格\", required = true, example = \"11771\")\n    private BigDecimal deliveryPrice;\n\n    @Schema(description = \"公告\", required = true)\n    @NotNull(message = \"公告不能为空\")\n    private String notice;\n\n    @Schema(description = \"是否营业:0=否,1=是\", required = true, example = \"2\")\n    @NotNull(message = \"是否营业:0=否,1=是不能为空\")\n    private Integer status;\n\n    @Schema(description = \"管理员id\", required = true, example = \"22251\")\n    @NotNull(message = \"管理员id不能为空\")\n    private List<String> adminId;\n\n    @Schema(description = \"打印机id\", required = true, example = \"5596\")\n    //@NotNull(message = \"打印机id不能为空\")\n    private String uniprintId;\n\n    @Schema(description = \"营业开始时间\", required = true)\n    @NotNull(message = \"营业开始时间不能为空\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private Date startTime;\n\n    @Schema(description = \"营业结束时间\", required = true)\n    @NotNull(message = \"营业结束时间不能为空\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private Date endTime;\n\n    @Schema(description = \"余额\", required = true, example = \"5596\")\n    private BigDecimal balance;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-store-biz/src/main/java/co/yixiang/yshop/module/store/controller/admin/storeshop/vo/StoreShopCreateReqVO.java",
    "content": "package co.yixiang.yshop.module.store.controller.admin.storeshop.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\n\n@Schema(description = \"管理后台 - 门店管理创建 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class StoreShopCreateReqVO extends StoreShopBaseVO {\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-store-biz/src/main/java/co/yixiang/yshop/module/store/controller/admin/storeshop/vo/StoreShopExcelVO.java",
    "content": "package co.yixiang.yshop.module.store.controller.admin.storeshop.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.util.*;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\n\nimport com.alibaba.excel.annotation.ExcelProperty;\n\n/**\n * 门店管理 Excel VO\n *\n * @author yshop\n */\n@Data\npublic class StoreShopExcelVO {\n\n    @ExcelProperty(\"id\")\n    private Long id;\n\n    @ExcelProperty(\"店铺名称\")\n    private String name;\n\n    @ExcelProperty(\"店铺电话\")\n    private String mobile;\n\n    @ExcelProperty(\"图片\")\n    private String image;\n\n    @ExcelProperty(\"多张图片\")\n    private List<String> images;\n\n    @ExcelProperty(\"详细地址\")\n    private String address;\n\n    @ExcelProperty(\"地图定位地址\")\n    private String addressMap;\n\n    @ExcelProperty(\"经度\")\n    private String lng;\n\n    @ExcelProperty(\"纬度\")\n    private String lat;\n\n    @ExcelProperty(\"外卖配送距离,单位为千米。0表示不送外卖\")\n    private Integer distance;\n\n    @ExcelProperty(\"起送价钱\")\n    private BigDecimal minPrice;\n\n    @ExcelProperty(\"配送价格\")\n    private BigDecimal deliveryPrice;\n\n    @ExcelProperty(\"公告\")\n    private String notice;\n\n    @ExcelProperty(\"是否营业:0=否,1=是\")\n    private Integer status;\n\n    @ExcelProperty(\"管理员id\")\n    private List<String> adminId;\n\n    @ExcelProperty(\"打印机id\")\n    private String uniprintId;\n\n    @ExcelProperty(\"添加时间\")\n    private LocalDateTime createTime;\n\n    @ExcelProperty(\"营业开始时间\")\n    private Date startTime;\n\n    @ExcelProperty(\"营业结束时间\")\n    private Date endTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-store-biz/src/main/java/co/yixiang/yshop/module/store/controller/admin/storeshop/vo/StoreShopExportReqVO.java",
    "content": "package co.yixiang.yshop.module.store.controller.admin.storeshop.vo;\n\nimport lombok.*;\n\nimport java.math.BigDecimal;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport java.time.LocalDateTime;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 门店管理 Excel 导出 Request VO，参数和 StoreShopPageReqVO 是一致的\")\n@Data\npublic class StoreShopExportReqVO {\n\n    @Schema(description = \"店铺名称\", example = \"李四\")\n    private String name;\n\n    @Schema(description = \"店铺电话\")\n    private String mobile;\n\n    @Schema(description = \"图片\")\n    private String image;\n\n    @Schema(description = \"多张图片\")\n    private String images;\n\n    @Schema(description = \"详细地址\")\n    private String address;\n\n    @Schema(description = \"地图定位地址\")\n    private String addressMap;\n\n    @Schema(description = \"经度\")\n    private String lng;\n\n    @Schema(description = \"纬度\")\n    private String lat;\n\n    @Schema(description = \"外卖配送距离,单位为千米。0表示不送外卖\")\n    private Integer distance;\n\n    @Schema(description = \"起送价钱\", example = \"15157\")\n    private BigDecimal minPrice;\n\n    @Schema(description = \"配送价格\", example = \"11771\")\n    private BigDecimal deliveryPrice;\n\n    @Schema(description = \"公告\")\n    private String notice;\n\n    @Schema(description = \"是否营业:0=否,1=是\", example = \"2\")\n    private Boolean status;\n\n    @Schema(description = \"管理员id\", example = \"22251\")\n    private String adminId;\n\n    @Schema(description = \"打印机id\", example = \"5596\")\n    private String uniprintId;\n\n    @Schema(description = \"添加时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n    @Schema(description = \"营业开始时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] startTime;\n\n    @Schema(description = \"营业结束时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] endTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-store-biz/src/main/java/co/yixiang/yshop/module/store/controller/admin/storeshop/vo/StoreShopPageReqVO.java",
    "content": "package co.yixiang.yshop.module.store.controller.admin.storeshop.vo;\n\nimport lombok.*;\n\nimport java.math.BigDecimal;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport org.springframework.format.annotation.DateTimeFormat;\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 门店管理分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class StoreShopPageReqVO extends PageParam {\n\n    @Schema(description = \"店铺名称\", example = \"李四\")\n    private String name;\n\n    @Schema(description = \"店铺电话\")\n    private String mobile;\n\n    @Schema(description = \"图片\")\n    private String image;\n\n    @Schema(description = \"多张图片\")\n    private String images;\n\n    @Schema(description = \"详细地址\")\n    private String address;\n\n    @Schema(description = \"地图定位地址\")\n    private String addressMap;\n\n    @Schema(description = \"经度\")\n    private String lng;\n\n    @Schema(description = \"纬度\")\n    private String lat;\n\n    @Schema(description = \"外卖配送距离,单位为千米。0表示不送外卖\")\n    private Integer distance;\n\n    @Schema(description = \"起送价钱\", example = \"15157\")\n    private BigDecimal minPrice;\n\n    @Schema(description = \"配送价格\", example = \"11771\")\n    private BigDecimal deliveryPrice;\n\n    @Schema(description = \"公告\")\n    private String notice;\n\n    @Schema(description = \"是否营业:0=否,1=是\", example = \"2\")\n    private Integer status;\n\n    @Schema(description = \"管理员id\", example = \"22251\")\n    private String adminId;\n\n    @Schema(description = \"打印机id\", example = \"5596\")\n    private String uniprintId;\n\n    @Schema(description = \"添加时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n    @Schema(description = \"营业开始时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] startTime;\n\n    @Schema(description = \"营业结束时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] endTime;\n\n    private Long shopId;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-store-biz/src/main/java/co/yixiang/yshop/module/store/controller/admin/storeshop/vo/StoreShopRespVO.java",
    "content": "package co.yixiang.yshop.module.store.controller.admin.storeshop.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 门店管理 Response VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class StoreShopRespVO extends StoreShopBaseVO {\n\n    @Schema(description = \"id\", required = true, example = \"25450\")\n    private Long id;\n\n    @Schema(description = \"添加时间\")\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-store-biz/src/main/java/co/yixiang/yshop/module/store/controller/admin/storeshop/vo/StoreShopUpdateReqVO.java",
    "content": "package co.yixiang.yshop.module.store.controller.admin.storeshop.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.util.*;\nimport jakarta.validation.constraints.*;\n\n@Schema(description = \"管理后台 - 门店管理更新 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class StoreShopUpdateReqVO extends StoreShopBaseVO {\n\n    @Schema(description = \"id\", required = true, example = \"25450\")\n    @NotNull(message = \"id不能为空\")\n    private Long id;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-store-biz/src/main/java/co/yixiang/yshop/module/store/controller/app/storeshop/AppStoreController.java",
    "content": "/**\n * Copyright (C) 2018-2022\n * All rights reserved, Designed By www.yixiang.co\n * 注意：\n * 本软件为www.yixiang.co开发研制，未经购买不得使用\n * 购买后可获得全部源代码（禁止转卖、分享、上传到码云、github等开源平台）\n * 一经发现盗用、分享等行为，将追究法律责任，后果自负\n */\npackage co.yixiang.yshop.module.store.controller.app.storeshop;\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.module.store.controller.app.storeshop.vo.AppStoreShopVO;\nimport co.yixiang.yshop.module.store.convert.storeshop.StoreShopConvert;\nimport co.yixiang.yshop.module.store.dal.dataobject.storeshop.StoreShopDO;\nimport co.yixiang.yshop.module.store.service.storeshop.AppStoreShopService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n/**\n * <p>\n * 门店控制器\n * </p>\n *\n * @author hupeng\n * @since 2023-8-14\n */\n@Slf4j\n@RestController\n@Tag(name = \"用户 APP - 门店\")\n@RequiredArgsConstructor(onConstructor = @__(@Autowired))\n@RequestMapping(\"/store\")\npublic class AppStoreController {\n\n    private final AppStoreShopService appStoreShopService;\n\n\n    @GetMapping(\"/nearby\")\n    @Operation(summary = \"获取最近的店铺\")\n    public CommonResult<AppStoreShopVO> getNearby(@RequestParam(\"lng\") double lon,\n                                                      @RequestParam(\"lat\") double lat,\n                                                      @RequestParam(\"kw\") String name,\n                                                      @RequestParam(\"shop_id\") Integer shopId) {\n        List<AppStoreShopVO> list = appStoreShopService.getStoreList(lon,lat,name,shopId);\n        if(list != null ){\n            return success(list.get(0));\n        }\n        return success(new AppStoreShopVO());\n    }\n\n    @GetMapping(\"/list\")\n    @Operation(summary = \"服务菜单列表\")\n    public CommonResult<List<AppStoreShopVO>> getList(@RequestParam(\"lng\") double lon,\n                                                      @RequestParam(\"lat\") double lat,\n                                                      @RequestParam(\"kw\") String name,\n                                                      @RequestParam(\"shop_id\") Integer shopId) {\n\n        return success(appStoreShopService.getStoreList(lon,lat,name,shopId));\n    }\n\n    @GetMapping(\"/getShop\")\n    @Operation(summary = \"获取最近的店铺\")\n    public CommonResult<AppStoreShopVO> getShop(@RequestParam(\"shop_id\") Integer shopId) {\n        StoreShopDO storeShopDO = appStoreShopService.getById(shopId);\n        return success(StoreShopConvert.INSTANCE.convert02(storeShopDO));\n    }\n\n\n\n}\n\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-store-biz/src/main/java/co/yixiang/yshop/module/store/controller/app/storeshop/vo/AppStoreShopVO.java",
    "content": "package co.yixiang.yshop.module.store.controller.app.storeshop.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport java.math.BigDecimal;\nimport java.util.Date;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n/**\n* 门店管理VO\n*/\n@Data\npublic class AppStoreShopVO {\n\n    private Long id;\n\n    @Schema(description = \"店铺名称\", required = true, example = \"李四\")\n    private String name;\n\n    @Schema(description = \"店铺电话\", required = true)\n    private String mobile;\n\n    @Schema(description = \"图片\", required = true)\n    private String image;\n\n    @Schema(description = \"多张图片\", required = true)\n    private List<String> images;\n\n    @Schema(description = \"详细地址\", required = true)\n    private String address;\n\n    @Schema(description = \"地图定位地址\", required = true)\n    private String addressMap;\n\n    @Schema(description = \"经度\", required = true)\n    private String lng;\n\n    @Schema(description = \"纬度\", required = true)\n    private String lat;\n\n    @Schema(description = \"外卖配送距离,单位为千米。0表示不送外卖\", required = true)\n    private Integer distance;\n\n    @Schema(description = \"计算出来的距离\", required = true)\n    private Long dis;\n\n    @Schema(description = \"起送价钱\", required = true, example = \"15157\")\n    private BigDecimal minPrice;\n\n    @Schema(description = \"配送价格\", required = true, example = \"11771\")\n    private BigDecimal deliveryPrice;\n\n    @Schema(description = \"公告\", required = true)\n    private String notice;\n\n\n    @Schema(description = \"营业开始时间\", required = true)\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private Date startTime;\n\n    @Schema(description = \"营业结束时间\", required = true)\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private Date endTime;\n\n    @Schema(description = \"桌面是否空闲\", required = true)\n    private Boolean isEmpty;\n\n    @Schema(description = \"桌面订单\", required = true)\n    private String deskOrderId;\n\n    @Schema(description = \"是否营业:0=否,1=是\", required = true)\n    private Integer status;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-store-biz/src/main/java/co/yixiang/yshop/module/store/convert/storeshop/StoreShopConvert.java",
    "content": "package co.yixiang.yshop.module.store.convert.storeshop;\n\nimport java.util.*;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\nimport co.yixiang.yshop.module.store.controller.app.storeshop.vo.AppStoreShopVO;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.factory.Mappers;\nimport co.yixiang.yshop.module.store.controller.admin.storeshop.vo.*;\nimport co.yixiang.yshop.module.store.dal.dataobject.storeshop.StoreShopDO;\n\n/**\n * 门店管理 Convert\n *\n * @author yshop\n */\n@Mapper\npublic interface StoreShopConvert {\n\n    StoreShopConvert INSTANCE = Mappers.getMapper(StoreShopConvert.class);\n\n    StoreShopDO convert(StoreShopCreateReqVO bean);\n\n    StoreShopDO convert(StoreShopUpdateReqVO bean);\n\n    StoreShopRespVO convert(StoreShopDO bean);\n\n    AppStoreShopVO convert02(StoreShopDO bean);\n\n    List<StoreShopRespVO> convertList(List<StoreShopDO> list);\n\n    PageResult<StoreShopRespVO> convertPage(PageResult<StoreShopDO> page);\n\n    List<StoreShopExcelVO> convertList02(List<StoreShopDO> list);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-store-biz/src/main/java/co/yixiang/yshop/module/store/dal/dataobject/storeshop/StoreShopDO.java",
    "content": "package co.yixiang.yshop.module.store.dal.dataobject.storeshop;\n\nimport co.yixiang.yshop.framework.mybatis.core.type.StringListTypeHandler;\nimport lombok.*;\nimport java.util.*;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\nimport com.baomidou.mybatisplus.annotation.*;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\n\n/**\n * 门店管理 DO\n *\n * @author yshop\n */\n@TableName(value = \"yshop_store_shop\",autoResultMap = true)\n@KeySequence(\"yshop_store_shop_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class StoreShopDO extends BaseDO {\n\n    /**\n     * id\n     */\n    @TableId\n    private Long id;\n    /**\n     * 店铺名称\n     */\n    private String name;\n    /**\n     * 店铺电话\n     */\n    private String mobile;\n    /**\n     * 图片\n     */\n    private String image;\n    /**\n     * 多张图片\n     */\n    @TableField(typeHandler = StringListTypeHandler.class)\n    private List<String> images;\n    /**\n     * 详细地址\n     */\n    private String address;\n    /**\n     * 地图定位地址\n     */\n    private String addressMap;\n    /**\n     * 经度\n     */\n    private String lng;\n    /**\n     * 纬度\n     */\n    private String lat;\n    /**\n     * 外卖配送距离,单位为千米。0表示不送外卖\n     */\n    private Integer distance;\n    /**\n     * 起送价钱\n     */\n    private BigDecimal minPrice;\n    /**\n     * 配送价格\n     */\n    private BigDecimal deliveryPrice;\n    /**\n     * 公告\n     */\n    private String notice;\n    /**\n     * 是否营业:0=否,1=是\n     */\n    private Integer status;\n    /**\n     * 管理员id\n     */\n    @TableField(typeHandler = StringListTypeHandler.class)\n    private List<String> adminId;\n    /**\n     * 打印机id\n     */\n    private String uniprintId;\n    /**\n     * 营业开始时间\n     */\n    private Date startTime;\n    /**\n     * 营业结束时间\n     */\n    private Date endTime;\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-store-biz/src/main/java/co/yixiang/yshop/module/store/dal/mysql/storeshop/StoreShopMapper.java",
    "content": "package co.yixiang.yshop.module.store.dal.mysql.storeshop;\n\nimport java.math.BigDecimal;\nimport java.util.*;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils;\nimport co.yixiang.yshop.module.store.controller.app.storeshop.vo.AppStoreShopVO;\nimport co.yixiang.yshop.module.store.dal.dataobject.storeshop.StoreShopDO;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport org.apache.ibatis.annotations.Mapper;\nimport co.yixiang.yshop.module.store.controller.admin.storeshop.vo.*;\nimport org.apache.ibatis.annotations.Param;\nimport org.apache.ibatis.annotations.Select;\nimport org.apache.ibatis.annotations.Update;\n\n/**\n * 门店管理 Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface StoreShopMapper extends BaseMapperX<StoreShopDO> {\n\n    default PageResult<StoreShopDO> selectPage(StoreShopPageReqVO reqVO) {\n        Long shopId = SecurityFrameworkUtils.getLoginUser().getShopId();\n        System.out.println(\"shopId2:\"+shopId);\n        if(shopId == 0) {\n            reqVO.setShopId(null);\n        }else {\n            reqVO.setShopId(shopId);\n        }\n        return selectPage(reqVO, new LambdaQueryWrapperX<StoreShopDO>()\n                .likeIfPresent(StoreShopDO::getName, reqVO.getName())\n                .eqIfPresent(StoreShopDO::getMobile, reqVO.getMobile())\n                .eqIfPresent(StoreShopDO::getId, reqVO.getShopId())\n                .eqIfPresent(StoreShopDO::getDistance, reqVO.getDistance())\n                .eqIfPresent(StoreShopDO::getMinPrice, reqVO.getMinPrice())\n                .eqIfPresent(StoreShopDO::getDeliveryPrice, reqVO.getDeliveryPrice())\n                .eqIfPresent(StoreShopDO::getNotice, reqVO.getNotice())\n                .eqIfPresent(StoreShopDO::getStatus, reqVO.getStatus())\n                .betweenIfPresent(StoreShopDO::getCreateTime, reqVO.getCreateTime())\n                .betweenIfPresent(StoreShopDO::getStartTime, reqVO.getStartTime())\n                .betweenIfPresent(StoreShopDO::getEndTime, reqVO.getEndTime())\n                .orderByDesc(StoreShopDO::getId));\n    }\n\n    default List<StoreShopDO> selectList(StoreShopExportReqVO reqVO) {\n        return selectList(new LambdaQueryWrapperX<StoreShopDO>()\n                .likeIfPresent(StoreShopDO::getName, reqVO.getName())\n                .eqIfPresent(StoreShopDO::getMobile, reqVO.getMobile())\n                .eqIfPresent(StoreShopDO::getImage, reqVO.getImage())\n                .eqIfPresent(StoreShopDO::getImages, reqVO.getImages())\n                .eqIfPresent(StoreShopDO::getAddress, reqVO.getAddress())\n                .eqIfPresent(StoreShopDO::getAddressMap, reqVO.getAddressMap())\n                .eqIfPresent(StoreShopDO::getLng, reqVO.getLng())\n                .eqIfPresent(StoreShopDO::getLat, reqVO.getLat())\n                .eqIfPresent(StoreShopDO::getDistance, reqVO.getDistance())\n                .eqIfPresent(StoreShopDO::getMinPrice, reqVO.getMinPrice())\n                .eqIfPresent(StoreShopDO::getDeliveryPrice, reqVO.getDeliveryPrice())\n                .eqIfPresent(StoreShopDO::getNotice, reqVO.getNotice())\n                .eqIfPresent(StoreShopDO::getStatus, reqVO.getStatus())\n                .eqIfPresent(StoreShopDO::getAdminId, reqVO.getAdminId())\n                .eqIfPresent(StoreShopDO::getUniprintId, reqVO.getUniprintId())\n                .betweenIfPresent(StoreShopDO::getCreateTime, reqVO.getCreateTime())\n                .betweenIfPresent(StoreShopDO::getStartTime, reqVO.getStartTime())\n                .betweenIfPresent(StoreShopDO::getEndTime, reqVO.getEndTime())\n                .orderByDesc(StoreShopDO::getId));\n    }\n\n    @Select(\"<script>SELECT*,ROUND(6378.138 * 2 * ASIN(SQRT(POW(SIN((#{lat} * PI() / 180 - lat * PI() / 180\" +\n            \"    ) / 2),2) + COS(40.0497810000 * PI() / 180) * COS(lat * PI() / 180) * POW(\" +\n            \"    SIN((#{lon} * PI() / 180 - lng * PI() / 180) / 2),2))) * 1000) AS dis\" +\n            \"    FROM yshop_store_shop WHERE deleted=0 \" +\n            \"<if test =\\\"name !=''\\\">and name = #{name}</if>\" +\n            \"<if test =\\\"shopId > 0\\\">and id = #{shopId}</if>\" +\n            \" ORDER BY dis ASC</script>\"\n    )\n    List<AppStoreShopVO> getStoreList(@Param(\"lon\") double lon, @Param(\"lat\") double lat,\n                                      @Param(\"name\") String name,@Param(\"shopId\") Integer shopId);\n\n\n    @Update(\"update yshop_store_shop set balance=balance+#{price}\" +\n            \" where id=#{shopId}\")\n    int incMoney(@Param(\"shopId\") Long shopId,@Param(\"price\") BigDecimal price);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-store-biz/src/main/java/co/yixiang/yshop/module/store/dal/redis/PrintTokenRedisDAO.java",
    "content": "package co.yixiang.yshop.module.store.dal.redis;\n\n\nimport org.springframework.data.redis.core.StringRedisTemplate;\nimport org.springframework.stereotype.Repository;\n\nimport jakarta.annotation.Resource;\n\nimport java.util.concurrent.TimeUnit;\n\nimport static co.yixiang.yshop.module.store.dal.redis.RedisKeyConstants.YSHOP_WEB_PRINT_TOKEN_KEY;\n\n\n/**\n\n *\n * @author yshop\n */\n@Repository\npublic class PrintTokenRedisDAO {\n\n    @Resource\n    private StringRedisTemplate stringRedisTemplate;\n\n    public String get() {\n        String redisKey = YSHOP_WEB_PRINT_TOKEN_KEY.getKeyTemplate();\n        return stringRedisTemplate.opsForValue().get(redisKey);\n    }\n\n    public void set(String o) {\n        String redisKey = YSHOP_WEB_PRINT_TOKEN_KEY.getKeyTemplate();\n\n\n        stringRedisTemplate.opsForValue().set(redisKey, o,2, TimeUnit.HOURS);\n    }\n\n    public void delete() {\n        String redisKey = YSHOP_WEB_PRINT_TOKEN_KEY.getKeyTemplate();\n        stringRedisTemplate.delete(redisKey);\n    }\n\n\n//\n//    private static String formatKey) {\n//        return String.format(YSHOP_ORDER_SALE_STATUS_KEY.getKeyTemplate(), key);\n//    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-store-biz/src/main/java/co/yixiang/yshop/module/store/dal/redis/RedisKeyConstants.java",
    "content": "package co.yixiang.yshop.module.store.dal.redis;\n\nimport co.yixiang.yshop.framework.redis.core.RedisKeyDefine;\n\nimport static co.yixiang.yshop.framework.redis.core.RedisKeyDefine.KeyTypeEnum.STRING;\n\n/**\n * System Redis Key 枚举类\n *\n * @author yshop\n */\npublic interface RedisKeyConstants {\n\n//\n//    RedisKeyDefine YSHOP_ORDER_CACHE_KEY = new RedisKeyDefine(\"确认订单数据缓存\",\n//            \"yshop_order_cache:%s\", // 参数为访问uid+key\n//            STRING, CacheDto.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);\n\n    RedisKeyDefine YSHOP_WEB_PRINT_TOKEN_KEY = new RedisKeyDefine(\"打印机token\",\n            \"yshop_web_print_token_cache:\", // 参数为访问uid+key\n            STRING, String.class, RedisKeyDefine.TimeoutTypeEnum.DYNAMIC);\n\n//    RedisKeyDefine YSHOP_ORDER_COUNT_CACHE_KEY = new RedisKeyDefine(\"统计订单数据缓存\",\n//            \"yshop_order_count_cache:%s\", // 参数为访问uid\n//            STRING, CacheDto.class, RedisKeyDefine.TimeoutTypeEnum.FOREVER);\n//\n//    RedisKeyDefine YSHOP_ADMIN_ORDER_COUNT_CACHE_KEY = new RedisKeyDefine(\"后台统计订单数据缓存\",\n//            \"yshop_admin_order_count_cache:\", // 参数为访问uid\n//            STRING, CacheDto.class, RedisKeyDefine.TimeoutTypeEnum.FOREVER);\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-store-biz/src/main/java/co/yixiang/yshop/module/store/service/storeshop/AppStoreShopService.java",
    "content": "package co.yixiang.yshop.module.store.service.storeshop;\n\nimport co.yixiang.yshop.module.store.controller.app.storeshop.vo.AppStoreShopVO;\nimport co.yixiang.yshop.module.store.dal.dataobject.storeshop.StoreShopDO;\nimport com.baomidou.mybatisplus.extension.service.IService;\n\nimport java.util.List;\n\n/**\n * 门店管理 Service 接口\n *\n * @author yshop\n */\npublic interface AppStoreShopService extends IService<StoreShopDO> {\n\n    /**\n     * 获取门店\n     * @param lon 经度\n     * @param lat 纬度\n     * @param name 店铺名称\n     * @return\n     */\n    List<AppStoreShopVO> getStoreList(double lon,double lat,String name,Integer shopId);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-store-biz/src/main/java/co/yixiang/yshop/module/store/service/storeshop/AppStoreShopServiceImpl.java",
    "content": "package co.yixiang.yshop.module.store.service.storeshop;\n\nimport co.yixiang.yshop.module.store.controller.app.storeshop.vo.AppStoreShopVO;\nimport co.yixiang.yshop.module.store.dal.dataobject.storeshop.StoreShopDO;\nimport co.yixiang.yshop.module.store.dal.mysql.storeshop.StoreShopMapper;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\nimport java.util.List;\n\n/**\n * 门店管理 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class AppStoreShopServiceImpl extends ServiceImpl<StoreShopMapper, StoreShopDO> implements AppStoreShopService {\n\n    @Resource\n    private StoreShopMapper shopMapper;\n\n\n    /**\n     * 获取门店\n     * @param lon 经度\n     * @param lat 纬度\n     * @param name 店铺名称\n     * @return\n     */\n    @Override\n    public List<AppStoreShopVO> getStoreList(double lon, double lat,String name,Integer shopId) {\n        return shopMapper.getStoreList(lon,lat,name,shopId);\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-store-biz/src/main/java/co/yixiang/yshop/module/store/service/storeshop/StoreShopService.java",
    "content": "package co.yixiang.yshop.module.store.service.storeshop;\n\nimport java.util.*;\nimport jakarta.validation.*;\nimport co.yixiang.yshop.module.store.controller.admin.storeshop.vo.*;\nimport co.yixiang.yshop.module.store.dal.dataobject.storeshop.StoreShopDO;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\n/**\n * 门店管理 Service 接口\n *\n * @author yshop\n */\npublic interface StoreShopService {\n\n    /**\n     * 创建门店管理\n     *\n     * @param createReqVO 创建信息\n     * @return 编号\n     */\n    Long createShop(@Valid StoreShopCreateReqVO createReqVO);\n\n    /**\n     * 更新门店管理\n     *\n     * @param updateReqVO 更新信息\n     */\n    void updateShop(@Valid StoreShopUpdateReqVO updateReqVO);\n\n    /**\n     * 删除门店管理\n     *\n     * @param id 编号\n     */\n    void deleteShop(Long id);\n\n    /**\n     * 获得门店管理\n     *\n     * @param id 编号\n     * @return 门店管理\n     */\n    StoreShopDO getShop(Long id);\n\n    /**\n     * 获得门店管理列表\n     *\n     * @return 门店管理列表\n     */\n    List<StoreShopDO> getShopList();\n\n    /**\n     * 获得门店管理分页\n     *\n     * @param pageReqVO 分页查询\n     * @return 门店管理分页\n     */\n    PageResult<StoreShopDO> getShopPage(StoreShopPageReqVO pageReqVO);\n\n    /**\n     * 获得门店管理列表, 用于 Excel 导出\n     *\n     * @param exportReqVO 查询条件\n     * @return 门店管理列表\n     */\n    List<StoreShopDO> getShopList(StoreShopExportReqVO exportReqVO);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-store-biz/src/main/java/co/yixiang/yshop/module/store/service/storeshop/StoreShopServiceImpl.java",
    "content": "package co.yixiang.yshop.module.store.service.storeshop;\n\nimport cn.hutool.core.bean.BeanUtil;\nimport cn.hutool.core.date.DateUtil;\nimport co.yixiang.yshop.framework.common.exception.ErrorCode;\nimport co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport org.springframework.stereotype.Service;\nimport jakarta.annotation.Resource;\nimport org.springframework.validation.annotation.Validated;\n\nimport java.util.*;\nimport co.yixiang.yshop.module.store.controller.admin.storeshop.vo.*;\nimport co.yixiang.yshop.module.store.dal.dataobject.storeshop.StoreShopDO;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\nimport co.yixiang.yshop.module.store.convert.storeshop.StoreShopConvert;\nimport co.yixiang.yshop.module.store.dal.mysql.storeshop.StoreShopMapper;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.store.enums.ErrorCodeConstants.*;\n\n/**\n * 门店管理 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class StoreShopServiceImpl implements StoreShopService {\n\n    @Resource\n    private StoreShopMapper shopMapper;\n\n    @Override\n    public Long createShop(StoreShopCreateReqVO createReqVO) {\n        //管理员只能绑定一个门店\n        createReqVO.getAdminId().forEach(val -> {\n           Long count = shopMapper.selectCount(new LambdaQueryWrapper<StoreShopDO>()\n                    .apply(\n                        \"FIND_IN_SET ('\" + val + \"',admin_id)\"));\n           if(count > 0){\n               throw exception(new ErrorCode(1000,\"管理员ID：\"+val+\"已经绑定过其他门店，不能再次绑定\"));\n           }\n        });\n        // 插入\n        StoreShopDO shop = StoreShopConvert.INSTANCE.convert(createReqVO);\n        Integer status = doShopStatus(shop.getStartTime(),shop.getEndTime());\n        shop.setStatus(status);\n        shopMapper.insert(shop);\n        // 返回\n        return shop.getId();\n    }\n\n    @Override\n    public void updateShop(StoreShopUpdateReqVO updateReqVO) {\n\n        //管理员只能绑定一个门店\n        updateReqVO.getAdminId().forEach(val -> {\n            Long count = shopMapper.selectCount(new LambdaQueryWrapper<StoreShopDO>()\n                    .ne(StoreShopDO::getId,updateReqVO.getId())\n                    .apply(\n                            \"FIND_IN_SET ('\" + val + \"',admin_id)\"));\n            if(count > 0){\n                throw exception(new ErrorCode(1000,\"管理员ID：\"+val+\"已经绑定过其他门店，不能再次绑定\"));\n            }\n        });\n        // 校验存在\n        validateShopExists(updateReqVO.getId());\n        // 更新\n        StoreShopDO updateObj = StoreShopConvert.INSTANCE.convert(updateReqVO);\n        Integer status = doShopStatus(updateObj.getStartTime(),updateObj.getEndTime());\n        updateObj.setStatus(status);\n        shopMapper.updateById(updateObj);\n    }\n\n\n    /**\n     * 处理营业时间\n     * @param startTime\n     * @param endTime\n     * @return\n     */\n    private Integer doShopStatus(Date startTime,Date endTime){\n        Date now = new Date();\n        Integer sH = DateUtil.hour(startTime,true);\n        Integer sM = DateUtil.minute(startTime);\n        Integer eH = DateUtil.hour(endTime,true);\n        Integer eM = DateUtil.minute(endTime);\n        Integer nH = DateUtil.hour(now,true);\n        Integer nM = DateUtil.minute(now);\n        if(nH < sH){\n            return 0;\n        }else if(eH < nH){\n            return 0;\n        }else if(nH == sH && sM > nM){\n            return 0;\n        }else if(eH == nH && eM < nM){\n            return 0;\n        } else{\n            return 1;\n        }\n    }\n\n    @Override\n    public void deleteShop(Long id) {\n        // 校验存在\n        validateShopExists(id);\n        // 删除\n        shopMapper.deleteById(id);\n    }\n\n    private void validateShopExists(Long id) {\n        if (shopMapper.selectById(id) == null) {\n            throw exception(SHOP_NOT_EXISTS);\n        }\n    }\n\n    @Override\n    public StoreShopDO getShop(Long id) {\n        return shopMapper.selectById(id);\n    }\n\n    @Override\n    public List<StoreShopDO> getShopList() {\n        Long shopId = SecurityFrameworkUtils.getLoginUser().getShopId();\n        LambdaQueryWrapper<StoreShopDO> wrapper = new LambdaQueryWrapper<>();\n        if(shopId == 0) {\n            wrapper.ge(StoreShopDO::getId,shopId);\n        }else {\n            wrapper.eq(StoreShopDO::getId,shopId);\n        }\n        return shopMapper.selectList(wrapper);\n    }\n\n    @Override\n    public PageResult<StoreShopDO> getShopPage(StoreShopPageReqVO pageReqVO) {\n        return shopMapper.selectPage(pageReqVO);\n    }\n\n    @Override\n    public List<StoreShopDO> getShopList(StoreShopExportReqVO exportReqVO) {\n        return shopMapper.selectList(exportReqVO);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mall/yshop-module-store-biz/src/main/resources/mapper/storeshop/StoreShopMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"co.yixiang.yshop.module.store.dal.mysql.storeshop.StoreShopMapper\">\n\n    <!--\n        一般情况下，尽可能使用 Mapper 进行 CRUD 增删改查即可。\n        无法满足的场景，例如说多表关联查询，才使用 XML 编写 SQL。\n        代码生成器暂时只生成 Mapper XML 文件本身，更多推荐 MybatisX 快速开发插件来生成查询。\n        文档可见：https://www.yixiang.co/MyBatis/x-plugins/\n     -->\n\n</mapper>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>yshop</artifactId>\n        <groupId>co.yixiang.boot</groupId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>yshop-module-marketing</artifactId>\n    <packaging>pom</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>\n        营销模块 优惠券  会员卡 分销 拼团\n    </description>\n\n    <modules>\n        <module>yshop-module-coupon-api</module>\n        <module>yshop-module-coupon-biz</module>\n    </modules>\n\n\n</project>"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/yshop-module-coupon-api/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <modelVersion>4.0.0</modelVersion>\n    <parent>\n        <groupId>co.yixiang.boot</groupId>\n        <artifactId>yshop-module-marketing</artifactId>\n        <version>${revision}</version>\n    </parent>\n\n    <artifactId>yshop-module-coupon-api</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>\n        coupon模块 API，暴露给其它模块调用\n    </description>\n\n    <dependencies>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-common</artifactId>\n        </dependency>\n\n        <!-- 参数校验 -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-validation</artifactId>\n            <optional>true</optional>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/yshop-module-coupon-api/src/main/java/co/yixiang/yshop/module/coupon/enums/CouponStatusEnum.java",
    "content": "/**\n * Copyright (C) 2018-2022\n * All rights reserved, Designed By www.yixiang.co\n\n */\npackage co.yixiang.yshop.module.coupon.enums;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\nimport java.util.stream.Stream;\n\n/**\n * @author hupeng\n * 优惠券相关枚举\n */\n@Getter\n@AllArgsConstructor\npublic enum CouponStatusEnum {\n\tSTATUS_4(4,\"所有\"),\n\tSTATUS_0(0,\"未使用\"),\n\tSTATUS_1(1,\"已使用\"),\n\tSTATUS_2(2,\"已失效\");\n\n\n\n\tprivate Integer value;\n\tprivate String desc;\n\n\tpublic static CouponStatusEnum toType(int value) {\n\t\treturn Stream.of(CouponStatusEnum.values())\n\t\t\t\t.filter(p -> p.value == value)\n\t\t\t\t.findAny()\n\t\t\t\t.orElse(null);\n\t}\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/yshop-module-coupon-api/src/main/java/co/yixiang/yshop/module/coupon/enums/ErrorCodeConstants.java",
    "content": "package co.yixiang.yshop.module.coupon.enums;\nimport co.yixiang.yshop.framework.common.exception.ErrorCode;\n\npublic interface ErrorCodeConstants {\n    ErrorCode COUPON_NOT_EXISTS = new ErrorCode(1008018000, \"优惠券不存在\");\n    ErrorCode COUPON_USER_NOT_EXISTS = new ErrorCode(1008018001, \"用户领的优惠券不存在\");\n    ErrorCode COUPON_RECEIVED = new ErrorCode(1008018001, \"优惠券已经领取过\");\n    ErrorCode COUPON_RECEIVE_ZERO = new ErrorCode(1008018001, \"优惠券已经被领完\");\n}\n\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/yshop-module-coupon-biz/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>co.yixiang.boot</groupId>\n        <artifactId>yshop-module-marketing</artifactId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>yshop-module-coupon-biz</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>\n        优惠券 模块，主要实现商品购物车相关功能\n    </description>\n\n    <dependencies>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-coupon-api</artifactId>\n            <version>${revision}</version>\n        </dependency>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-store-biz</artifactId>\n            <version>${revision}</version>\n        </dependency>\n\n        <!-- Job 定时任务相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-job</artifactId>\n        </dependency>\n\n        <!-- 业务组件 -->\n<!--        <dependency>-->\n<!--            <groupId>co.yixiang.boot</groupId>-->\n<!--            <artifactId>yshop-spring-boot-starter-biz-operatelog</artifactId>-->\n<!--        </dependency>-->\n\n        <!-- Web 相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-web</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-security</artifactId>\n        </dependency>\n\n        <!-- DB 相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-mybatis</artifactId>\n        </dependency>\n\n        <!-- Test 测试相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-test</artifactId>\n        </dependency>\n\n        <!-- 工具类相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-excel</artifactId>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/yshop-module-coupon-biz/src/main/java/co/yixiang/yshop/module/coupon/controller/admin/coupon/CouponController.java",
    "content": "package co.yixiang.yshop.module.coupon.controller.admin.coupon;\n\nimport jakarta.servlet.http.HttpServletResponse;\nimport org.springframework.web.bind.annotation.*;\nimport jakarta.annotation.Resource;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.Operation;\n\nimport jakarta.validation.constraints.*;\nimport jakarta.validation.*;\nimport java.util.*;\nimport java.io.IOException;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\nimport co.yixiang.yshop.framework.excel.core.util.ExcelUtils;\n\nimport co.yixiang.yshop.module.coupon.controller.admin.coupon.vo.*;\nimport co.yixiang.yshop.module.coupon.dal.dataobject.coupon.CouponDO;\nimport co.yixiang.yshop.module.coupon.convert.coupon.CouponConvert;\nimport co.yixiang.yshop.module.coupon.service.coupon.CouponService;\n\n@Tag(name = \"管理后台 - 优惠券\")\n@RestController\n@RequestMapping(\"/coupon/\")\n@Validated\npublic class CouponController {\n\n    @Resource\n    private CouponService Service;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建优惠券\")\n    @PreAuthorize(\"@ss.hasPermission('coupon::create')\")\n    public CommonResult<Long> create(@Valid @RequestBody CouponCreateReqVO createReqVO) {\n        return success(Service.create(createReqVO));\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"更新优惠券\")\n    @PreAuthorize(\"@ss.hasPermission('coupon::update')\")\n    public CommonResult<Boolean> update(@Valid @RequestBody CouponUpdateReqVO updateReqVO) {\n        Service.update(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除优惠券\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('coupon::delete')\")\n    public CommonResult<Boolean> delete(@RequestParam(\"id\") Long id) {\n        Service.delete(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得优惠券\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('coupon::query')\")\n    public CommonResult<CouponRespVO> get(@RequestParam(\"id\") Long id) {\n        CouponDO couponDO = Service.get(id);\n        return success(CouponConvert.INSTANCE.convert(couponDO));\n    }\n\n    @GetMapping(\"/list\")\n    @Operation(summary = \"获得优惠券列表\")\n    @Parameter(name = \"ids\", description = \"编号列表\", required = true, example = \"1024,2048\")\n    @PreAuthorize(\"@ss.hasPermission('coupon::query')\")\n    public CommonResult<List<CouponRespVO>> getList() {\n        List<CouponDO> list = Service.getList();\n        return success(CouponConvert.INSTANCE.convertList(list));\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得优惠券分页\")\n    @PreAuthorize(\"@ss.hasPermission('coupon::query')\")\n    public CommonResult<PageResult<CouponRespVO>> getPage(@Valid CouponPageReqVO pageVO) {\n        PageResult<CouponDO> pageResult = Service.getPage(pageVO);\n        return success(CouponConvert.INSTANCE.convertPage(pageResult));\n    }\n\n    @GetMapping(\"/export-excel\")\n    @Operation(summary = \"导出优惠券 Excel\")\n    @PreAuthorize(\"@ss.hasPermission('coupon::export')\")\n    public void exportExcel(@Valid CouponExportReqVO exportReqVO,\n              HttpServletResponse response) throws IOException {\n        List<CouponDO> list = Service.getList(exportReqVO);\n        // 导出 Excel\n        List<CouponExcelVO> datas = CouponConvert.INSTANCE.convertList02(list);\n        ExcelUtils.write(response, \"优惠券.xls\", \"数据\", CouponExcelVO.class, datas);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/yshop-module-coupon-biz/src/main/java/co/yixiang/yshop/module/coupon/controller/admin/coupon/vo/CouponBaseVO.java",
    "content": "package co.yixiang.yshop.module.coupon.controller.admin.coupon.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.util.*;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\nimport jakarta.validation.constraints.*;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n/**\n* 优惠券 Base VO，提供给添加、修改、详细的子 VO 使用\n* 如果子 VO 存在差异的字段，请不要添加到这里，影响 Swagger 文档生成\n*/\n@Data\npublic class CouponBaseVO {\n\n    @Schema(description = \"店铺id,0表示通用\", required = true, example = \"8915\")\n    private String shopId;\n\n    private String shopName;\n\n    @Schema(description = \"优惠券名称\", required = true)\n    @NotNull(message = \"优惠券名称不能为空\")\n    private String title;\n\n    @Schema(description = \"是否上架\", required = true)\n    @NotNull(message = \"是否上架不能为空\")\n    private Integer isSwitch;\n\n    @Schema(description = \"消费多少可用\", required = true)\n    @NotNull(message = \"消费多少可用不能为空\")\n    private BigDecimal least;\n\n    @Schema(description = \"优惠券金额\", required = true)\n    @NotNull(message = \"优惠券金额不能为空\")\n    private BigDecimal value;\n\n    @Schema(description = \"开始时间\", required = true)\n    @NotNull(message = \"开始时间不能为空\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime startTime;\n\n    @Schema(description = \"结束时间\", required = true)\n    @NotNull(message = \"结束时间不能为空\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime endTime;\n\n    @Schema(description = \"权重\", required = true)\n    private Integer weigh;\n\n    @Schema(description = \"可用类型:0=通用,1=自取,2=外卖\", required = true, example = \"2\")\n    @NotNull(message = \"可用类型不能为空\")\n    private Integer type;\n\n    @Schema(description = \"兑换码\")\n    private String exchangeCode;\n\n    @Schema(description = \"已领取\", required = true)\n    private Integer receive;\n\n    @Schema(description = \"发行数量\", required = true)\n    @NotNull(message = \"发行数量不能为空\")\n    private Integer distribute;\n\n    @Schema(description = \"所需积分\", required = true)\n    private Integer score;\n\n    @Schema(description = \"使用说明\", required = true)\n    private String instructions;\n\n    @Schema(description = \"图片\", required = true)\n    private String image;\n\n    @Schema(description = \"限领数量\")\n    @NotNull(message = \"限领数量不能为空\")\n    private Integer limit;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/yshop-module-coupon-biz/src/main/java/co/yixiang/yshop/module/coupon/controller/admin/coupon/vo/CouponCreateReqVO.java",
    "content": "package co.yixiang.yshop.module.coupon.controller.admin.coupon.vo;\n\nimport lombok.*;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.*;\n\n@Schema(description = \"管理后台 - 优惠券创建 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class CouponCreateReqVO extends CouponBaseVO {\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/yshop-module-coupon-biz/src/main/java/co/yixiang/yshop/module/coupon/controller/admin/coupon/vo/CouponExcelVO.java",
    "content": "package co.yixiang.yshop.module.coupon.controller.admin.coupon.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.util.*;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\n\nimport com.alibaba.excel.annotation.ExcelProperty;\n\n/**\n * 优惠券 Excel VO\n *\n * @author yshop\n */\n@Data\npublic class CouponExcelVO {\n\n    @ExcelProperty(\"id\")\n    private Long id;\n\n    @ExcelProperty(\"店铺id,0表示通用\")\n    private String shopId;\n\n    @ExcelProperty(\"店铺名称逗号隔开\")\n    private String shopName;\n\n    @ExcelProperty(\"优惠券名称\")\n    private String title;\n\n    @ExcelProperty(\"是否上架\")\n    private Integer isSwitch;\n\n    @ExcelProperty(\"消费多少可用\")\n    private BigDecimal least;\n\n    @ExcelProperty(\"优惠券金额\")\n    private BigDecimal value;\n\n    @ExcelProperty(\"开始时间\")\n    private LocalDateTime startTime;\n\n    @ExcelProperty(\"结束时间\")\n    private LocalDateTime endTime;\n\n    @ExcelProperty(\"创建时间\")\n    private LocalDateTime createTime;\n\n    @ExcelProperty(\"权重\")\n    private Integer weigh;\n\n    @ExcelProperty(\"可用类型:0=通用,1=自取,2=外卖\")\n    private Integer type;\n\n    @ExcelProperty(\"兑换码\")\n    private String exchangeCode;\n\n    @ExcelProperty(\"已领取\")\n    private Integer receive;\n\n    @ExcelProperty(\"发行数量\")\n    private Integer distribute;\n\n    @ExcelProperty(\"所需积分\")\n    private Integer score;\n\n    @ExcelProperty(\"使用说明\")\n    private String instructions;\n\n    @ExcelProperty(\"图片\")\n    private String image;\n\n    @ExcelProperty(\"限领数量\")\n    private Integer limit;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/yshop-module-coupon-biz/src/main/java/co/yixiang/yshop/module/coupon/controller/admin/coupon/vo/CouponExportReqVO.java",
    "content": "package co.yixiang.yshop.module.coupon.controller.admin.coupon.vo;\n\nimport lombok.*;\n\nimport java.math.BigDecimal;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport java.time.LocalDateTime;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 优惠券 Excel 导出 Request VO，参数和 CouponPageReqVO 是一致的\")\n@Data\npublic class CouponExportReqVO {\n\n    @Schema(description = \"店铺id,0表示通用\", example = \"8915\")\n    private String shopId;\n\n    @Schema(description = \"店铺名称逗号隔开\", example = \"王五\")\n    private String shopName;\n\n    @Schema(description = \"优惠券名称\")\n    private String title;\n\n    @Schema(description = \"是否上架\")\n    private Integer isSwitch;\n\n    @Schema(description = \"消费多少可用\")\n    private BigDecimal least;\n\n    @Schema(description = \"优惠券金额\")\n    private BigDecimal value;\n\n    @Schema(description = \"开始时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] startTime;\n\n    @Schema(description = \"结束时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] endTime;\n\n    @Schema(description = \"创建时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n    @Schema(description = \"权重\")\n    private Integer weigh;\n\n    @Schema(description = \"可用类型:0=通用,1=自取,2=外卖\", example = \"2\")\n    private Integer type;\n\n    @Schema(description = \"兑换码\")\n    private String exchangeCode;\n\n    @Schema(description = \"已领取\")\n    private Integer receive;\n\n    @Schema(description = \"发行数量\")\n    private Integer distribute;\n\n    @Schema(description = \"所需积分\")\n    private Integer score;\n\n    @Schema(description = \"使用说明\")\n    private String instructions;\n\n    @Schema(description = \"图片\")\n    private String image;\n\n    @Schema(description = \"限领数量\")\n    private Integer limit;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/yshop-module-coupon-biz/src/main/java/co/yixiang/yshop/module/coupon/controller/admin/coupon/vo/CouponPageReqVO.java",
    "content": "package co.yixiang.yshop.module.coupon.controller.admin.coupon.vo;\n\nimport lombok.*;\n\nimport java.math.BigDecimal;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport org.springframework.format.annotation.DateTimeFormat;\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 优惠券分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class CouponPageReqVO extends PageParam {\n\n    @Schema(description = \"店铺id,0表示通用\", example = \"8915\")\n    private String shopId;\n\n    @Schema(description = \"店铺名称逗号隔开\", example = \"王五\")\n    private String shopName;\n\n    @Schema(description = \"优惠券名称\")\n    private String title;\n\n    @Schema(description = \"是否上架\")\n    private Integer isSwitch;\n\n    @Schema(description = \"消费多少可用\")\n    private BigDecimal least;\n\n    @Schema(description = \"优惠券金额\")\n    private BigDecimal value;\n\n    @Schema(description = \"开始时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] startTime;\n\n    @Schema(description = \"结束时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] endTime;\n\n    @Schema(description = \"创建时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n    @Schema(description = \"权重\")\n    private Integer weigh;\n\n    @Schema(description = \"可用类型:0=通用,1=自取,2=外卖\", example = \"2\")\n    private Integer type;\n\n    @Schema(description = \"兑换码\")\n    private String exchangeCode;\n\n    @Schema(description = \"已领取\")\n    private Integer receive;\n\n    @Schema(description = \"发行数量\")\n    private Integer distribute;\n\n    @Schema(description = \"所需积分\")\n    private Integer score;\n\n    @Schema(description = \"使用说明\")\n    private String instructions;\n\n    @Schema(description = \"图片\")\n    private String image;\n\n    @Schema(description = \"限领数量\")\n    private Integer limit;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/yshop-module-coupon-biz/src/main/java/co/yixiang/yshop/module/coupon/controller/admin/coupon/vo/CouponRespVO.java",
    "content": "package co.yixiang.yshop.module.coupon.controller.admin.coupon.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 优惠券 Response VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class CouponRespVO extends CouponBaseVO {\n\n    @Schema(description = \"id\", required = true, example = \"1582\")\n    private Long id;\n\n    @Schema(description = \"创建时间\", required = true)\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/yshop-module-coupon-biz/src/main/java/co/yixiang/yshop/module/coupon/controller/admin/coupon/vo/CouponUpdateReqVO.java",
    "content": "package co.yixiang.yshop.module.coupon.controller.admin.coupon.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.util.*;\nimport jakarta.validation.constraints.*;\n\n@Schema(description = \"管理后台 - 优惠券更新 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class CouponUpdateReqVO extends CouponBaseVO {\n\n    @Schema(description = \"id\", required = true, example = \"1582\")\n    @NotNull(message = \"id不能为空\")\n    private Long id;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/yshop-module-coupon-biz/src/main/java/co/yixiang/yshop/module/coupon/controller/admin/couponuser/CouponUserController.java",
    "content": "package co.yixiang.yshop.module.coupon.controller.admin.couponuser;\n\nimport jakarta.servlet.http.HttpServletResponse;\nimport org.springframework.web.bind.annotation.*;\nimport jakarta.annotation.Resource;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.Operation;\n\nimport jakarta.validation.constraints.*;\nimport jakarta.validation.*;\nimport java.util.*;\nimport java.io.IOException;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\nimport co.yixiang.yshop.framework.excel.core.util.ExcelUtils;\n\nimport co.yixiang.yshop.module.coupon.controller.admin.couponuser.vo.*;\nimport co.yixiang.yshop.module.coupon.dal.dataobject.couponuser.CouponUserDO;\nimport co.yixiang.yshop.module.coupon.convert.couponuser.CouponUserConvert;\nimport co.yixiang.yshop.module.coupon.service.couponuser.CouponUserService;\n\n@Tag(name = \"管理后台 - 用户领的优惠券\")\n@RestController\n@RequestMapping(\"/coupon/user\")\n@Validated\npublic class CouponUserController {\n\n    @Resource\n    private CouponUserService userService;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建用户领的优惠券\")\n    @PreAuthorize(\"@ss.hasPermission('coupon:user:create')\")\n    public CommonResult<Integer> createUser(@Valid @RequestBody CouponUserCreateReqVO createReqVO) {\n        return success(userService.createUser(createReqVO));\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"更新用户领的优惠券\")\n    @PreAuthorize(\"@ss.hasPermission('coupon:user:update')\")\n    public CommonResult<Boolean> updateUser(@Valid @RequestBody CouponUserUpdateReqVO updateReqVO) {\n        userService.updateUser(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除用户领的优惠券\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('coupon:user:delete')\")\n    public CommonResult<Boolean> deleteUser(@RequestParam(\"id\") Integer id) {\n        userService.deleteUser(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得用户领的优惠券\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('coupon:user:query')\")\n    public CommonResult<CouponUserRespVO> getUser(@RequestParam(\"id\") Integer id) {\n        CouponUserDO user = userService.getUser(id);\n        return success(CouponUserConvert.INSTANCE.convert(user));\n    }\n\n    @GetMapping(\"/list\")\n    @Operation(summary = \"获得用户领的优惠券列表\")\n    @Parameter(name = \"id\", description = \"编号列表\", required = true, example = \"1024\")\n   // @PreAuthorize(\"@ss.hasPermission('coupon:user:query')\")\n    public CommonResult<List<CouponUserRespVO>> getUserList(@RequestParam(\"couponId\") Integer couponId) {\n        List<CouponUserDO> list = userService.getUserList(couponId);\n        return success(CouponUserConvert.INSTANCE.convertList(list));\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得用户领的优惠券分页\")\n    //@PreAuthorize(\"@ss.hasPermission('coupon:user:query')\")\n    public CommonResult<PageResult<CouponUserRespVO>> getUserPage(@Valid CouponUserPageReqVO pageVO) {\n        PageResult<CouponUserDO> pageResult = userService.getUserPage(pageVO);\n        return success(CouponUserConvert.INSTANCE.convertPage(pageResult));\n    }\n\n    @GetMapping(\"/export-excel\")\n    @Operation(summary = \"导出用户领的优惠券 Excel\")\n    @PreAuthorize(\"@ss.hasPermission('coupon:user:export')\")\n    public void exportUserExcel(@Valid CouponUserExportReqVO exportReqVO,\n              HttpServletResponse response) throws IOException {\n        List<CouponUserDO> list = userService.getUserList(exportReqVO);\n        // 导出 Excel\n        List<CouponUserExcelVO> datas = CouponUserConvert.INSTANCE.convertList02(list);\n        ExcelUtils.write(response, \"用户领的优惠券.xls\", \"数据\", CouponUserExcelVO.class, datas);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/yshop-module-coupon-biz/src/main/java/co/yixiang/yshop/module/coupon/controller/admin/couponuser/vo/CouponUserBaseVO.java",
    "content": "package co.yixiang.yshop.module.coupon.controller.admin.couponuser.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\n\nimport java.time.LocalDateTime;\nimport java.util.*;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport jakarta.validation.constraints.*;\n\n/**\n* 用户领的优惠券 Base VO，提供给添加、修改、详细的子 VO 使用\n* 如果子 VO 存在差异的字段，请不要添加到这里，影响 Swagger 文档生成\n*/\n@Data\npublic class CouponUserBaseVO {\n\n    @Schema(description = \"店铺id,0表示通用\", required = true, example = \"32582\")\n    @NotNull(message = \"店铺id,0表示通用不能为空\")\n    private String shopId;\n\n    @Schema(description = \"店铺名称逗号隔开\", example = \"yshop\")\n    private String shopName;\n\n    @Schema(description = \"优惠券名称\", required = true)\n    @NotNull(message = \"优惠券名称不能为空\")\n    private String title;\n\n    @Schema(description = \"消费多少可用\", required = true)\n    @NotNull(message = \"消费多少可用不能为空\")\n    private BigDecimal least;\n\n    @Schema(description = \"优惠券金额\", required = true)\n    @NotNull(message = \"优惠券金额不能为空\")\n    private BigDecimal value;\n\n    @Schema(description = \"开始时间\", required = true)\n    @NotNull(message = \"开始时间不能为空\")\n    private LocalDateTime startTime;\n\n    @Schema(description = \"结束时间\", required = true)\n    @NotNull(message = \"结束时间不能为空\")\n    private LocalDateTime endTime;\n\n    @Schema(description = \"创建时间\", required = true)\n    @NotNull(message = \"创建时间不能为空\")\n    private LocalDateTime createTime;\n\n    @Schema(description = \"更新时间\", required = true)\n    @NotNull(message = \"更新时间不能为空\")\n    private LocalDateTime updateTime;\n\n    @Schema(description = \"可用类型:0=通用,1=自取,2=外卖\", required = true, example = \"2\")\n    @NotNull(message = \"可用类型:0=通用,1=自取,2=外卖不能为空\")\n    private Integer type;\n\n    @Schema(description = \"消耗积分\", required = true)\n    @NotNull(message = \"消耗积分不能为空\")\n    private Integer score;\n\n    @Schema(description = \"使用说明\", required = true)\n    @NotNull(message = \"使用说明不能为空\")\n    private String instructions;\n\n    @Schema(description = \"图片\", required = true)\n    @NotNull(message = \"图片不能为空\")\n    private String image;\n\n    @Schema(description = \"用户id\", required = true, example = \"20961\")\n    @NotNull(message = \"用户id不能为空\")\n    private Integer userId;\n\n    @Schema(description = \"已使用:0=否,1=是\", required = true, example = \"1\")\n    @NotNull(message = \"已使用:0=否,1=是不能为空\")\n    private Integer status;\n\n    @Schema(description = \"优惠券id\", required = true, example = \"23870\")\n    @NotNull(message = \"优惠券id不能为空\")\n    private Integer couponId;\n\n    @Schema(description = \"兑换码\")\n    private String exchangeCode;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/yshop-module-coupon-biz/src/main/java/co/yixiang/yshop/module/coupon/controller/admin/couponuser/vo/CouponUserCreateReqVO.java",
    "content": "package co.yixiang.yshop.module.coupon.controller.admin.couponuser.vo;\n\nimport lombok.*;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.*;\n\n@Schema(description = \"管理后台 - 用户领的优惠券创建 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class CouponUserCreateReqVO extends CouponUserBaseVO {\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/yshop-module-coupon-biz/src/main/java/co/yixiang/yshop/module/coupon/controller/admin/couponuser/vo/CouponUserExcelVO.java",
    "content": "package co.yixiang.yshop.module.coupon.controller.admin.couponuser.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.util.*;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\n\nimport com.alibaba.excel.annotation.ExcelProperty;\n\n/**\n * 用户领的优惠券 Excel VO\n *\n * @author yshop\n */\n@Data\npublic class CouponUserExcelVO {\n\n    @ExcelProperty(\"id\")\n    private Integer id;\n\n    @ExcelProperty(\"店铺id,0表示通用\")\n    private String shopId;\n\n    @ExcelProperty(\"店铺名称逗号隔开\")\n    private String shopName;\n\n    @ExcelProperty(\"优惠券名称\")\n    private String title;\n\n    @ExcelProperty(\"消费多少可用\")\n    private BigDecimal least;\n\n    @ExcelProperty(\"优惠券金额\")\n    private BigDecimal value;\n\n    @ExcelProperty(\"开始时间\")\n    private Integer starttime;\n\n    @ExcelProperty(\"结束时间\")\n    private Integer endtime;\n\n    @ExcelProperty(\"创建时间\")\n    private Integer createtime;\n\n    @ExcelProperty(\"更新时间\")\n    private Integer updatetime;\n\n    @ExcelProperty(\"可用类型:0=通用,1=自取,2=外卖\")\n    private Integer type;\n\n    @ExcelProperty(\"消耗积分\")\n    private Integer score;\n\n    @ExcelProperty(\"使用说明\")\n    private String instructions;\n\n    @ExcelProperty(\"图片\")\n    private String image;\n\n    @ExcelProperty(\"用户id\")\n    private Integer userId;\n\n    @ExcelProperty(\"已使用:0=否,1=是\")\n    private Integer status;\n\n    @ExcelProperty(\"优惠券id\")\n    private Integer couponId;\n\n    @ExcelProperty(\"兑换码\")\n    private String exchangeCode;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/yshop-module-coupon-biz/src/main/java/co/yixiang/yshop/module/coupon/controller/admin/couponuser/vo/CouponUserExportReqVO.java",
    "content": "package co.yixiang.yshop.module.coupon.controller.admin.couponuser.vo;\n\nimport lombok.*;\n\nimport java.math.BigDecimal;\nimport java.time.LocalDateTime;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport jakarta.validation.constraints.NotNull;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 用户领的优惠券 Excel 导出 Request VO，参数和 CouponUserPageReqVO 是一致的\")\n@Data\npublic class CouponUserExportReqVO {\n\n    @Schema(description = \"店铺id,0表示通用\", example = \"32582\")\n    private String shopId;\n\n    @Schema(description = \"店铺名称逗号隔开\", example = \"yshop\")\n    private String shopName;\n\n    @Schema(description = \"优惠券名称\")\n    private String title;\n\n    @Schema(description = \"消费多少可用\")\n    private BigDecimal least;\n\n    @Schema(description = \"优惠券金额\")\n    private BigDecimal value;\n\n    @Schema(description = \"开始时间\", required = true)\n    @NotNull(message = \"开始时间不能为空\")\n    private LocalDateTime startTime;\n\n    @Schema(description = \"结束时间\", required = true)\n    @NotNull(message = \"结束时间不能为空\")\n    private LocalDateTime endTime;\n\n    @Schema(description = \"创建时间\", required = true)\n    @NotNull(message = \"创建时间不能为空\")\n    private LocalDateTime createTime;\n\n    @Schema(description = \"更新时间\", required = true)\n    @NotNull(message = \"更新时间不能为空\")\n    private LocalDateTime updateTime;\n\n    @Schema(description = \"可用类型:0=通用,1=自取,2=外卖\", example = \"2\")\n    private Boolean type;\n\n    @Schema(description = \"消耗积分\")\n    private Integer score;\n\n    @Schema(description = \"使用说明\")\n    private String instructions;\n\n    @Schema(description = \"图片\")\n    private String image;\n\n    @Schema(description = \"用户id\", example = \"20961\")\n    private Integer userId;\n\n    @Schema(description = \"已使用:0=否,1=是\", example = \"1\")\n    private Boolean status;\n\n    @Schema(description = \"优惠券id\", example = \"23870\")\n    private Integer couponId;\n\n    @Schema(description = \"兑换码\")\n    private String exchangeCode;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/yshop-module-coupon-biz/src/main/java/co/yixiang/yshop/module/coupon/controller/admin/couponuser/vo/CouponUserPageReqVO.java",
    "content": "package co.yixiang.yshop.module.coupon.controller.admin.couponuser.vo;\n\nimport lombok.*;\n\nimport java.math.BigDecimal;\nimport java.time.LocalDateTime;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport jakarta.validation.constraints.NotNull;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 用户领的优惠券分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class CouponUserPageReqVO extends PageParam {\n\n    @Schema(description = \"店铺id,0表示通用\", example = \"32582\")\n    private String shopId;\n\n    @Schema(description = \"店铺名称逗号隔开\", example = \"yshop\")\n    private String shopName;\n\n    @Schema(description = \"优惠券名称\")\n    private String title;\n\n    @Schema(description = \"消费多少可用\")\n    private BigDecimal least;\n\n    @Schema(description = \"优惠券金额\")\n    private BigDecimal value;\n\n    @Schema(description = \"开始时间\", required = true)\n    @NotNull(message = \"开始时间不能为空\")\n    private LocalDateTime startTime;\n\n    @Schema(description = \"结束时间\", required = true)\n    @NotNull(message = \"结束时间不能为空\")\n    private LocalDateTime endTime;\n\n    @Schema(description = \"创建时间\", required = true)\n    @NotNull(message = \"创建时间不能为空\")\n    private LocalDateTime createTime;\n\n    @Schema(description = \"更新时间\", required = true)\n    @NotNull(message = \"更新时间不能为空\")\n    private LocalDateTime updateTime;\n\n    @Schema(description = \"可用类型:0=通用,1=自取,2=外卖\", example = \"2\")\n    private Boolean type;\n\n    @Schema(description = \"消耗积分\")\n    private Integer score;\n\n    @Schema(description = \"使用说明\")\n    private String instructions;\n\n    @Schema(description = \"图片\")\n    private String image;\n\n    @Schema(description = \"用户id\", example = \"20961\")\n    private Integer userId;\n\n    @Schema(description = \"已使用:0=否,1=是\", example = \"1\")\n    private Boolean status;\n\n    @Schema(description = \"优惠券id\", example = \"23870\")\n    private Integer couponId;\n\n    @Schema(description = \"兑换码\")\n    private String exchangeCode;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/yshop-module-coupon-biz/src/main/java/co/yixiang/yshop/module/coupon/controller/admin/couponuser/vo/CouponUserRespVO.java",
    "content": "package co.yixiang.yshop.module.coupon.controller.admin.couponuser.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\n\n@Schema(description = \"管理后台 - 用户领的优惠券 Response VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class CouponUserRespVO extends CouponUserBaseVO {\n\n    @Schema(description = \"id\", required = true, example = \"5159\")\n    private Integer id;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/yshop-module-coupon-biz/src/main/java/co/yixiang/yshop/module/coupon/controller/admin/couponuser/vo/CouponUserUpdateReqVO.java",
    "content": "package co.yixiang.yshop.module.coupon.controller.admin.couponuser.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.util.*;\nimport jakarta.validation.constraints.*;\n\n@Schema(description = \"管理后台 - 用户领的优惠券更新 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class CouponUserUpdateReqVO extends CouponUserBaseVO {\n\n    @Schema(description = \"id\", required = true, example = \"5159\")\n    @NotNull(message = \"id不能为空\")\n    private Integer id;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/yshop-module-coupon-biz/src/main/java/co/yixiang/yshop/module/coupon/controller/app/coupon/AppCouponController.java",
    "content": "/**\n * Copyright (C) 2018-2022\n * All rights reserved, Designed By www.yixiang.co\n * 注意：\n * 本软件为www.yixiang.co开发研制，未经购买不得使用\n * 购买后可获得全部源代码（禁止转卖、分享、上传到码云、github等开源平台）\n * 一经发现盗用、分享等行为，将追究法律责任，后果自负\n */\npackage co.yixiang.yshop.module.coupon.controller.app.coupon;\n\nimport co.yixiang.yshop.framework.common.enums.ShopCommonEnum;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.security.core.annotations.PreAuthenticated;\nimport co.yixiang.yshop.module.coupon.controller.app.coupon.vo.AppCouponVO;\nimport co.yixiang.yshop.module.coupon.controller.app.coupon.vo.AppMyCouponVO;\nimport co.yixiang.yshop.module.coupon.controller.app.coupon.vo.AppReceVO;\nimport co.yixiang.yshop.module.coupon.dal.dataobject.couponuser.CouponUserDO;\nimport co.yixiang.yshop.module.coupon.service.coupon.AppCouponService;\nimport co.yixiang.yshop.module.coupon.service.couponuser.AppCouponUserService;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.Parameters;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.time.LocalDateTime;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\nimport static co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;\n\n/**\n * <p>\n * 优惠券控制器\n * </p>\n *\n * @author hupeng\n * @since 2023-8-20\n */\n@Slf4j\n@RestController\n@Tag(name = \"用户 APP - 优惠券\")\n@RequiredArgsConstructor(onConstructor = @__(@Autowired))\n@RequestMapping(\"/coupon\")\npublic class AppCouponController {\n\n    private final AppCouponUserService appCouponUserService;\n    private final AppCouponService appCouponService;\n\n\n    @PreAuthenticated\n    @GetMapping(\"/count\")\n    @Operation(summary = \"获取可用优惠券数量\")\n    public CommonResult<Long> getCount(@RequestParam(\"shop_id\") Integer shopId,\n                                                      @RequestParam(\"type\") Integer type) {\n        Long uid = getLoginUserId();\n        LocalDateTime nowTime = LocalDateTime.now();\n        Long count = appCouponUserService.count(new LambdaQueryWrapper<CouponUserDO>()\n                .eq(CouponUserDO::getUserId,uid)\n                .eq(CouponUserDO::getShopId,shopId)\n                .lt(CouponUserDO::getStartTime,nowTime)\n                .gt(CouponUserDO::getEndTime,nowTime)\n                .and(i->i.eq(CouponUserDO::getType,type).or().eq(CouponUserDO::getType,0))\n                .eq(CouponUserDO::getStatus, ShopCommonEnum.IS_STATUS_0));\n\n        return success(count);\n    }\n\n    /**\n     * 获取我的优惠券\n     */\n    @PreAuthenticated\n    @GetMapping(\"/my\")\n    @Parameters({\n            @Parameter(name = \"shopId\", description = \"店铺ID\",\n                     example = \"1\"),\n            @Parameter(name = \"type\", description = \"0-未使用 1-已使用 2-已过期\",\n                    example = \"1\"),\n            @Parameter(name = \"page\", description = \"页码,默认为1\",\n                    required = true, example = \"1\"),\n            @Parameter(name = \"pagesize\", description = \"页大小,默认为10\",\n                    required = true, example = \"10      \")\n    })\n    @Operation(summary = \"获取我的优惠券\")\n    public CommonResult<List<AppMyCouponVO>> myList(@RequestParam(value = \"shopId\",required = false)  Long shopId,\n                                                    @RequestParam(value = \"type\",defaultValue = \"0\") int type,\n                                                    @RequestParam(value = \"page\",defaultValue = \"1\") int page,\n                                                    @RequestParam(value = \"pagesize\",defaultValue = \"10\") int pagesize){\n        Long uid = getLoginUserId();\n        return success(appCouponUserService.getList(uid,shopId,type,page,pagesize));\n    }\n\n    /**\n     * 获取未被领取优惠券\n     */\n    @PreAuthenticated\n    @GetMapping(\"/not\")\n    @Parameters({\n            @Parameter(name = \"shopId\", description = \"店铺ID\",\n                     example = \"1\"),\n            @Parameter(name = \"page\", description = \"页码,默认为1\",\n                    required = true, example = \"1\"),\n            @Parameter(name = \"pagesize\", description = \"页大小,默认为10\",\n                    required = true, example = \"10      \")\n    })\n    @Operation(summary = \"获取未被领取优惠券\")\n    public CommonResult<List<AppCouponVO>> myNotList(@RequestParam(value = \"id\",required = false)  Long shopId,\n                                                     @RequestParam(value = \"page\",defaultValue = \"1\") int page,\n                                                     @RequestParam(value = \"pagesize\",defaultValue = \"10\") int pagesize){\n        Long uid = getLoginUserId();\n        return success(appCouponService.getNotList(uid,shopId,page,pagesize));\n    }\n\n    /**\n     * 领取优惠券\n     */\n    @PreAuthenticated\n    @PostMapping(\"/receive\")\n    @Parameters({\n            @Parameter(name = \"id\", description = \"优惠券ID\",\n                    example = \"1\"),\n            @Parameter(name = \"code\", description = \"优惠券兑换码\",\n                    example = \"1\")\n    })\n    @Operation(summary = \"获取未被领取优惠券\")\n    public CommonResult<Boolean> receive(@RequestBody AppReceVO appReceVO){\n        Long uid = getLoginUserId();\n        appCouponService.receive(uid,appReceVO.getId(),appReceVO.getCode());\n        return success(true);\n    }\n\n\n}\n\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/yshop-module-coupon-biz/src/main/java/co/yixiang/yshop/module/coupon/controller/app/coupon/vo/AppCouponVO.java",
    "content": "package co.yixiang.yshop.module.coupon.controller.app.coupon.vo;\n\nimport lombok.*;\n\nimport java.math.BigDecimal;\nimport java.time.LocalDateTime;\n\n/**\n * 优惠券 vo\n *\n * @author yshop\n */\n@Data\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class AppCouponVO {\n\n    /**\n     * id\n     */\n    private Long id;\n    /**\n     * 店铺id,0表示通用\n     */\n    private String shopId;\n    /**\n     * 店铺名称逗号隔开\n     */\n    private String shopName;\n    /**\n     * 优惠券名称\n     */\n    private String title;\n    /**\n     * 是否上架\n     */\n    private Integer isSwitch;\n    /**\n     * 消费多少可用\n     */\n    private BigDecimal least;\n    /**\n     * 优惠券金额\n     */\n    private BigDecimal value;\n    /**\n     * 开始时间\n     */\n    private LocalDateTime startTime;\n    /**\n     * 结束时间\n     */\n    private LocalDateTime endTime;\n    /**\n     * 权重\n     */\n    private Integer weigh;\n    /**\n     * 可用类型:0=通用,1=自取,2=外卖\n     */\n    private Integer type;\n    /**\n     * 兑换码\n     */\n    private String exchangeCode;\n    /**\n     * 已领取\n     */\n    private Integer receive;\n    /**\n     * 发行数量\n     */\n    private Integer distribute;\n    /**\n     * 所需积分\n     */\n    private Integer score;\n    /**\n     * 使用说明\n     */\n    private String instructions;\n    /**\n     * 图片\n     */\n    private String image;\n    /**\n     * 限领数量\n     */\n    private Integer limit;\n\n    private LocalDateTime createTime;\n\n    /**\n     * 是否已领取\n     */\n    private Integer isReceive;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/yshop-module-coupon-biz/src/main/java/co/yixiang/yshop/module/coupon/controller/app/coupon/vo/AppMyCouponVO.java",
    "content": "package co.yixiang.yshop.module.coupon.controller.app.coupon.vo;\n\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.*;\n\nimport java.math.BigDecimal;\nimport java.time.LocalDateTime;\n\n/**\n * 用户领的优惠券vo\n *\n * @author yshop\n */\n@Data\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class AppMyCouponVO {\n\n    /**\n     * id\n     */\n    private Integer id;\n    /**\n     * 店铺id,0表示通用\n     */\n    private String shopId;\n    /**\n     * 店铺名称逗号隔开\n     */\n    private String shopName;\n    /**\n     * 优惠券名称\n     */\n    private String title;\n    /**\n     * 消费多少可用\n     */\n    private BigDecimal least;\n    /**\n     * 优惠券金额\n     */\n    private BigDecimal value;\n    /**\n     * 开始时间\n     */\n    private LocalDateTime startTime;\n    /**\n     * 结束时间\n     */\n    private LocalDateTime endTime;\n\n    /**\n     * 可用类型:0=通用,1=自取,2=外卖\n     */\n    private Integer type;\n    /**\n     * 消耗积分\n     */\n    private Integer score;\n    /**\n     * 使用说明\n     */\n    private String instructions;\n    /**\n     * 图片\n     */\n    private String image;\n    /**\n     * 用户id\n     */\n    private Integer userId;\n    /**\n     * 已使用:0=否,1=是\n     */\n    private Integer status;\n    /**\n     * 优惠券id\n     */\n    private Integer couponId;\n    /**\n     * 兑换码\n     */\n    private String exchangeCode;\n\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/yshop-module-coupon-biz/src/main/java/co/yixiang/yshop/module/coupon/controller/app/coupon/vo/AppReceVO.java",
    "content": "package co.yixiang.yshop.module.coupon.controller.app.coupon.vo;\n\nimport lombok.*;\n\nimport java.math.BigDecimal;\nimport java.time.LocalDateTime;\n\n/**\n * 领取优惠券 vo\n *\n * @author yshop\n */\n@Data\npublic class AppReceVO {\n\n    /**\n     * id\n     */\n    private Long id;\n    /**\n     * 优惠券兑换码\n     */\n    private String code;\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/yshop-module-coupon-biz/src/main/java/co/yixiang/yshop/module/coupon/convert/coupon/CouponConvert.java",
    "content": "package co.yixiang.yshop.module.coupon.convert.coupon;\n\nimport java.util.*;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\nimport co.yixiang.yshop.module.coupon.controller.app.coupon.vo.AppCouponVO;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.factory.Mappers;\nimport co.yixiang.yshop.module.coupon.controller.admin.coupon.vo.*;\nimport co.yixiang.yshop.module.coupon.dal.dataobject.coupon.CouponDO;\n\n/**\n * 优惠券 Convert\n *\n * @author yshop\n */\n@Mapper\npublic interface CouponConvert {\n\n    CouponConvert INSTANCE = Mappers.getMapper(CouponConvert.class);\n\n    CouponDO convert(CouponCreateReqVO bean);\n\n    CouponDO convert(CouponUpdateReqVO bean);\n\n    CouponRespVO convert(CouponDO bean);\n\n    AppCouponVO convert01(CouponDO bean);\n\n    List<CouponRespVO> convertList(List<CouponDO> list);\n\n    List<AppCouponVO> convertList03(List<CouponDO> list);\n\n    PageResult<CouponRespVO> convertPage(PageResult<CouponDO> page);\n\n    List<CouponExcelVO> convertList02(List<CouponDO> list);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/yshop-module-coupon-biz/src/main/java/co/yixiang/yshop/module/coupon/convert/couponuser/CouponUserConvert.java",
    "content": "package co.yixiang.yshop.module.coupon.convert.couponuser;\n\nimport java.util.*;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\nimport co.yixiang.yshop.module.coupon.controller.app.coupon.vo.AppMyCouponVO;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.factory.Mappers;\nimport co.yixiang.yshop.module.coupon.controller.admin.couponuser.vo.*;\nimport co.yixiang.yshop.module.coupon.dal.dataobject.couponuser.CouponUserDO;\n\n/**\n * 用户领的优惠券 Convert\n *\n * @author yshop\n */\n@Mapper\npublic interface CouponUserConvert {\n\n    CouponUserConvert INSTANCE = Mappers.getMapper(CouponUserConvert.class);\n\n    CouponUserDO convert(CouponUserCreateReqVO bean);\n\n    CouponUserDO convert(CouponUserUpdateReqVO bean);\n\n    CouponUserRespVO convert(CouponUserDO bean);\n\n    List<AppMyCouponVO> convertList03(List<CouponUserDO> list);\n\n    List<CouponUserRespVO> convertList(List<CouponUserDO> list);\n\n    PageResult<CouponUserRespVO> convertPage(PageResult<CouponUserDO> page);\n\n    List<CouponUserExcelVO> convertList02(List<CouponUserDO> list);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/yshop-module-coupon-biz/src/main/java/co/yixiang/yshop/module/coupon/dal/dataobject/coupon/CouponDO.java",
    "content": "package co.yixiang.yshop.module.coupon.dal.dataobject.coupon;\n\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.*;\n\nimport java.math.BigDecimal;\nimport java.time.LocalDateTime;\n\n/**\n * 优惠券 DO\n *\n * @author yshop\n */\n@TableName(value = \"yshop_coupon\")\n@KeySequence(\"yshop_coupon_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class CouponDO extends BaseDO {\n\n    /**\n     * id\n     */\n    @TableId\n    private Long id;\n    /**\n     * 店铺id,0表示通用\n     */\n    private String shopId;\n    /**\n     * 店铺名称逗号隔开\n     */\n    private String shopName;\n    /**\n     * 优惠券名称\n     */\n    private String title;\n    /**\n     * 是否上架\n     */\n    private Integer isSwitch;\n    /**\n     * 消费多少可用\n     */\n    private BigDecimal least;\n    /**\n     * 优惠券金额\n     */\n    private BigDecimal value;\n    /**\n     * 开始时间\n     */\n    private LocalDateTime startTime;\n    /**\n     * 结束时间\n     */\n    private LocalDateTime endTime;\n    /**\n     * 权重\n     */\n    private Integer weigh;\n    /**\n     * 可用类型:0=通用,1=自取,2=外卖\n     */\n    private Integer type;\n    /**\n     * 兑换码\n     */\n    private String exchangeCode;\n    /**\n     * 已领取\n     */\n    private Integer receive;\n    /**\n     * 发行数量\n     */\n    private Integer distribute;\n    /**\n     * 所需积分\n     */\n    private Integer score;\n    /**\n     * 使用说明\n     */\n    private String instructions;\n    /**\n     * 图片\n     */\n    private String image;\n    /**\n     * 限领数量\n     */\n    @TableField(value = \"`limit`\")\n    private Integer limit;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/yshop-module-coupon-biz/src/main/java/co/yixiang/yshop/module/coupon/dal/dataobject/couponuser/CouponUserDO.java",
    "content": "package co.yixiang.yshop.module.coupon.dal.dataobject.couponuser;\n\nimport lombok.*;\n\nimport java.time.LocalDateTime;\nimport java.util.*;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport com.baomidou.mybatisplus.annotation.*;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\n\n/**\n * 用户领的优惠券 DO\n *\n * @author yshop\n */\n@TableName(\"yshop_coupon_user\")\n@KeySequence(\"yshop_coupon_user_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class CouponUserDO extends BaseDO {\n\n    /**\n     * id\n     */\n    @TableId\n    private Integer id;\n    /**\n     * 店铺id,0表示通用\n     */\n    private String shopId;\n    /**\n     * 店铺名称逗号隔开\n     */\n    private String shopName;\n    /**\n     * 优惠券名称\n     */\n    private String title;\n    /**\n     * 消费多少可用\n     */\n    private BigDecimal least;\n    /**\n     * 优惠券金额\n     */\n    private BigDecimal value;\n    /**\n     * 开始时间\n     */\n    private LocalDateTime startTime;\n    /**\n     * 结束时间\n     */\n    private LocalDateTime endTime;\n\n    /**\n     * 可用类型:0=通用,1=自取,2=外卖\n     */\n    private Integer type;\n    /**\n     * 消耗积分\n     */\n    private Integer score;\n    /**\n     * 使用说明\n     */\n    private String instructions;\n    /**\n     * 图片\n     */\n    private String image;\n    /**\n     * 用户id\n     */\n    private Integer userId;\n    /**\n     * 已使用:0=否,1=是\n     */\n    private Integer status;\n    /**\n     * 优惠券id\n     */\n    private Integer couponId;\n    /**\n     * 兑换码\n     */\n    private String exchangeCode;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/yshop-module-coupon-biz/src/main/java/co/yixiang/yshop/module/coupon/dal/mysql/coupon/CouponMapper.java",
    "content": "package co.yixiang.yshop.module.coupon.dal.mysql.coupon;\n\nimport java.util.*;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils;\nimport co.yixiang.yshop.module.coupon.dal.dataobject.coupon.CouponDO;\nimport org.apache.ibatis.annotations.Mapper;\nimport co.yixiang.yshop.module.coupon.controller.admin.coupon.vo.*;\n\n/**\n * 优惠券 Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface CouponMapper extends BaseMapperX<CouponDO> {\n\n    default PageResult<CouponDO> selectPage(CouponPageReqVO reqVO) {\n        Long shopId = SecurityFrameworkUtils.getLoginUser().getShopId();\n        if(shopId == 0) {\n            reqVO.setShopId(null);\n        }else {\n            reqVO.setShopId(shopId.toString());\n        }\n        return selectPage(reqVO, new LambdaQueryWrapperX<CouponDO>()\n                .eqIfPresent(CouponDO::getShopId, reqVO.getShopId())\n                .likeIfPresent(CouponDO::getShopName, reqVO.getShopName())\n                .eqIfPresent(CouponDO::getTitle, reqVO.getTitle())\n                .orderByDesc(CouponDO::getId));\n    }\n\n    default List<CouponDO> selectList(CouponExportReqVO reqVO) {\n        return selectList(new LambdaQueryWrapperX<CouponDO>()\n                .eqIfPresent(CouponDO::getShopId, reqVO.getShopId())\n                .likeIfPresent(CouponDO::getShopName, reqVO.getShopName())\n                .eqIfPresent(CouponDO::getTitle, reqVO.getTitle())\n                .eqIfPresent(CouponDO::getIsSwitch, reqVO.getIsSwitch())\n                .eqIfPresent(CouponDO::getLeast, reqVO.getLeast())\n                .eqIfPresent(CouponDO::getValue, reqVO.getValue())\n                .betweenIfPresent(CouponDO::getStartTime, reqVO.getStartTime())\n                .betweenIfPresent(CouponDO::getCreateTime, reqVO.getCreateTime())\n                .eqIfPresent(CouponDO::getWeigh, reqVO.getWeigh())\n                .eqIfPresent(CouponDO::getType, reqVO.getType())\n                .eqIfPresent(CouponDO::getExchangeCode, reqVO.getExchangeCode())\n                .eqIfPresent(CouponDO::getReceive, reqVO.getReceive())\n                .eqIfPresent(CouponDO::getDistribute, reqVO.getDistribute())\n                .eqIfPresent(CouponDO::getScore, reqVO.getScore())\n                .eqIfPresent(CouponDO::getInstructions, reqVO.getInstructions())\n                .eqIfPresent(CouponDO::getImage, reqVO.getImage())\n                .eqIfPresent(CouponDO::getLimit, reqVO.getLimit())\n                .orderByDesc(CouponDO::getId));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/yshop-module-coupon-biz/src/main/java/co/yixiang/yshop/module/coupon/dal/mysql/couponuser/CouponUserMapper.java",
    "content": "package co.yixiang.yshop.module.coupon.dal.mysql.couponuser;\n\nimport java.util.*;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.module.coupon.dal.dataobject.couponuser.CouponUserDO;\nimport org.apache.ibatis.annotations.Mapper;\nimport co.yixiang.yshop.module.coupon.controller.admin.couponuser.vo.*;\n\n/**\n * 用户领的优惠券 Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface CouponUserMapper extends BaseMapperX<CouponUserDO> {\n\n    default PageResult<CouponUserDO> selectPage(CouponUserPageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<CouponUserDO>()\n                .eqIfPresent(CouponUserDO::getShopId, reqVO.getShopId())\n                .likeIfPresent(CouponUserDO::getShopName, reqVO.getShopName())\n                .eqIfPresent(CouponUserDO::getTitle, reqVO.getTitle())\n                .eqIfPresent(CouponUserDO::getLeast, reqVO.getLeast())\n                .eqIfPresent(CouponUserDO::getValue, reqVO.getValue())\n                .eqIfPresent(CouponUserDO::getType, reqVO.getType())\n                .eqIfPresent(CouponUserDO::getScore, reqVO.getScore())\n                .eqIfPresent(CouponUserDO::getInstructions, reqVO.getInstructions())\n                .eqIfPresent(CouponUserDO::getImage, reqVO.getImage())\n                .eqIfPresent(CouponUserDO::getUserId, reqVO.getUserId())\n                .eqIfPresent(CouponUserDO::getStatus, reqVO.getStatus())\n                .eqIfPresent(CouponUserDO::getCouponId, reqVO.getCouponId())\n                .eqIfPresent(CouponUserDO::getExchangeCode, reqVO.getExchangeCode())\n                .orderByDesc(CouponUserDO::getId));\n    }\n\n    default List<CouponUserDO> selectList(CouponUserExportReqVO reqVO) {\n        return selectList(new LambdaQueryWrapperX<CouponUserDO>()\n                .eqIfPresent(CouponUserDO::getShopId, reqVO.getShopId())\n                .likeIfPresent(CouponUserDO::getShopName, reqVO.getShopName())\n                .eqIfPresent(CouponUserDO::getTitle, reqVO.getTitle())\n                .eqIfPresent(CouponUserDO::getLeast, reqVO.getLeast())\n                .eqIfPresent(CouponUserDO::getValue, reqVO.getValue())\n                .eqIfPresent(CouponUserDO::getType, reqVO.getType())\n                .eqIfPresent(CouponUserDO::getScore, reqVO.getScore())\n                .eqIfPresent(CouponUserDO::getInstructions, reqVO.getInstructions())\n                .eqIfPresent(CouponUserDO::getImage, reqVO.getImage())\n                .eqIfPresent(CouponUserDO::getUserId, reqVO.getUserId())\n                .eqIfPresent(CouponUserDO::getStatus, reqVO.getStatus())\n                .eqIfPresent(CouponUserDO::getCouponId, reqVO.getCouponId())\n                .eqIfPresent(CouponUserDO::getExchangeCode, reqVO.getExchangeCode())\n                .orderByDesc(CouponUserDO::getId));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/yshop-module-coupon-biz/src/main/java/co/yixiang/yshop/module/coupon/service/coupon/AppCouponService.java",
    "content": "package co.yixiang.yshop.module.coupon.service.coupon;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.coupon.controller.admin.coupon.vo.CouponCreateReqVO;\nimport co.yixiang.yshop.module.coupon.controller.admin.coupon.vo.CouponExportReqVO;\nimport co.yixiang.yshop.module.coupon.controller.admin.coupon.vo.CouponPageReqVO;\nimport co.yixiang.yshop.module.coupon.controller.admin.coupon.vo.CouponUpdateReqVO;\nimport co.yixiang.yshop.module.coupon.controller.app.coupon.vo.AppCouponVO;\nimport co.yixiang.yshop.module.coupon.controller.app.coupon.vo.AppMyCouponVO;\nimport co.yixiang.yshop.module.coupon.dal.dataobject.coupon.CouponDO;\nimport com.baomidou.mybatisplus.extension.service.IService;\n\nimport jakarta.validation.Valid;\nimport java.util.Collection;\nimport java.util.List;\n\n/**\n * 优惠券 Service 接口\n *\n * @author yshop\n */\npublic interface AppCouponService extends IService<CouponDO> {\n\n    /**\n     * 获取未被领取优惠券\n     * @param shopId 店铺id\n     * @param page\n     * @param pagesize\n     * @return\n     */\n    List<AppCouponVO> getNotList(Long uid, Long shopId, int page, int pagesize);\n\n    /**\n     * 领取优惠券\n     * @param uid 用户ID\n     * @param id  优惠券ID\n     * @param code 兑换码\n     */\n    void receive(Long uid,Long id,String code);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/yshop-module-coupon-biz/src/main/java/co/yixiang/yshop/module/coupon/service/coupon/AppCouponServiceImpl.java",
    "content": "package co.yixiang.yshop.module.coupon.service.coupon;\n\nimport cn.hutool.core.bean.BeanUtil;\nimport cn.hutool.core.util.ObjectUtil;\nimport co.yixiang.yshop.framework.common.enums.ShopCommonEnum;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.ObjectUtils;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.coupon.controller.admin.coupon.vo.CouponCreateReqVO;\nimport co.yixiang.yshop.module.coupon.controller.admin.coupon.vo.CouponExportReqVO;\nimport co.yixiang.yshop.module.coupon.controller.admin.coupon.vo.CouponPageReqVO;\nimport co.yixiang.yshop.module.coupon.controller.admin.coupon.vo.CouponUpdateReqVO;\nimport co.yixiang.yshop.module.coupon.controller.app.coupon.vo.AppCouponVO;\nimport co.yixiang.yshop.module.coupon.controller.app.coupon.vo.AppMyCouponVO;\nimport co.yixiang.yshop.module.coupon.convert.coupon.CouponConvert;\nimport co.yixiang.yshop.module.coupon.convert.couponuser.CouponUserConvert;\nimport co.yixiang.yshop.module.coupon.dal.dataobject.coupon.CouponDO;\nimport co.yixiang.yshop.module.coupon.dal.dataobject.couponuser.CouponUserDO;\nimport co.yixiang.yshop.module.coupon.dal.mysql.coupon.CouponMapper;\nimport co.yixiang.yshop.module.coupon.dal.mysql.couponuser.CouponUserMapper;\nimport co.yixiang.yshop.module.coupon.service.couponuser.AppCouponUserService;\nimport co.yixiang.yshop.module.store.dal.dataobject.storeshop.StoreShopDO;\nimport co.yixiang.yshop.module.store.dal.mysql.storeshop.StoreShopMapper;\nimport co.yixiang.yshop.module.system.api.logger.dto.OperateLogCreateReqDTO;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Propagation;\nimport org.springframework.transaction.annotation.Transactional;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\nimport java.time.LocalDateTime;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.coupon.enums.ErrorCodeConstants.*;\n\n/**\n * 优惠券 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class AppCouponServiceImpl extends ServiceImpl<CouponMapper, CouponDO> implements AppCouponService {\n\n    @Resource\n    private CouponMapper couponMapper;\n    @Resource\n    private AppCouponUserService appCouponUserService;\n\n\n    /**\n     * 获取未被领取优惠券\n     * @param shopId 店铺id\n     * @param page\n     * @param pagesize\n     * @return\n     */\n    @Override\n    public List<AppCouponVO> getNotList(Long uid, Long shopId, int page, int pagesize) {\n        LocalDateTime nowTime = LocalDateTime.now();\n        Page<CouponDO> pageModel = new Page<>(page, pagesize);\n        LambdaQueryWrapperX<CouponDO> wrapper = new LambdaQueryWrapperX<>();\n        wrapper.eqIfPresent(CouponDO::getShopId, shopId)\n                .gt(CouponDO::getEndTime,nowTime)\n                .orderByDesc(CouponDO::getWeigh);\n        IPage<CouponDO> pageList = this.baseMapper.selectPage(pageModel, wrapper);\n        List<AppCouponVO> list = new ArrayList<>();\n        for (CouponDO couponDO : pageList.getRecords()) {\n            AppCouponVO appCouponVO = CouponConvert.INSTANCE.convert01(couponDO);\n            long count = appCouponUserService.count(new LambdaQueryWrapper<CouponUserDO>()\n                    .eq(CouponUserDO::getUserId,uid).eq(CouponUserDO::getCouponId,couponDO.getId()));\n            if(count > 0){\n                appCouponVO.setIsReceive(ShopCommonEnum.DEFAULT_1.getValue());\n            }else {\n                appCouponVO.setIsReceive(ShopCommonEnum.DEFAULT_0.getValue());\n            }\n            list.add(appCouponVO);\n        }\n        return list;\n    }\n\n    /**\n     * 领取优惠券\n     * @param uid 用户ID\n     * @param id  优惠券ID\n     * @param code 兑换码\n     */\n    @Override\n    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)\n    public void receive(Long uid, Long id, String code) {\n        CouponDO couponDO = null;\n        if(id != null){\n            couponDO = this.baseMapper.selectById(id);\n        }else {\n            couponDO = couponMapper.selectOne(CouponDO::getExchangeCode,code);\n        }\n        if(couponDO == null){\n            throw exception(COUPON_NOT_EXISTS);\n        }\n        if(couponDO.getReceive() >= couponDO.getDistribute()){\n            throw exception(COUPON_RECEIVE_ZERO);\n        }\n        Long couponCount = appCouponUserService.count(new LambdaQueryWrapper<CouponUserDO>()\n                .eq(CouponUserDO::getUserId,uid).eq(CouponUserDO::getCouponId,couponDO.getId()));\n        if(couponCount > 0){\n            throw exception(COUPON_RECEIVED);\n        }\n\n\n        CouponUserDO couponUserDO = BeanUtil.copyProperties(couponDO,CouponUserDO.class,\"id\");\n\n        couponUserDO.setUserId(uid.intValue());\n        couponUserDO.setCouponId(couponDO.getId().intValue());\n        couponUserDO.setEndTime(couponDO.getEndTime());\n        couponUserDO.setExchangeCode(code);\n        appCouponUserService.save(couponUserDO);\n\n        int newRecive = couponDO.getReceive() + 1;\n        couponDO.setReceive(newRecive);\n        couponMapper.updateById(couponDO);\n\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/yshop-module-coupon-biz/src/main/java/co/yixiang/yshop/module/coupon/service/coupon/CouponService.java",
    "content": "package co.yixiang.yshop.module.coupon.service.coupon;\n\nimport java.util.*;\nimport jakarta.validation.*;\nimport co.yixiang.yshop.module.coupon.controller.admin.coupon.vo.*;\nimport co.yixiang.yshop.module.coupon.dal.dataobject.coupon.CouponDO;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\n/**\n * 优惠券 Service 接口\n *\n * @author yshop\n */\npublic interface CouponService {\n\n    /**\n     * 创建优惠券\n     *\n     * @param createReqVO 创建信息\n     * @return 编号\n     */\n    Long create(@Valid CouponCreateReqVO createReqVO);\n\n    /**\n     * 更新优惠券\n     *\n     * @param updateReqVO 更新信息\n     */\n    void update(@Valid CouponUpdateReqVO updateReqVO);\n\n    /**\n     * 删除优惠券\n     *\n     * @param id 编号\n     */\n    void delete(Long id);\n\n    /**\n     * 获得优惠券\n     *\n     * @param id 编号\n     * @return 优惠券\n     */\n    CouponDO get(Long id);\n\n    /**\n     * 获得优惠券列表全店铺通用\n     *\n     * @return 优惠券列表\n     */\n    List<CouponDO> getList();\n\n    /**\n     * 获得优惠券分页\n     *\n     * @param pageReqVO 分页查询\n     * @return 优惠券分页\n     */\n    PageResult<CouponDO> getPage(CouponPageReqVO pageReqVO);\n\n    /**\n     * 获得优惠券列表, 用于 Excel 导出\n     *\n     * @param exportReqVO 查询条件\n     * @return 优惠券列表\n     */\n    List<CouponDO> getList(CouponExportReqVO exportReqVO);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/yshop-module-coupon-biz/src/main/java/co/yixiang/yshop/module/coupon/service/coupon/CouponServiceImpl.java",
    "content": "package co.yixiang.yshop.module.coupon.service.coupon;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.coupon.controller.admin.coupon.vo.CouponCreateReqVO;\nimport co.yixiang.yshop.module.coupon.controller.admin.coupon.vo.CouponExportReqVO;\nimport co.yixiang.yshop.module.coupon.controller.admin.coupon.vo.CouponPageReqVO;\nimport co.yixiang.yshop.module.coupon.controller.admin.coupon.vo.CouponUpdateReqVO;\nimport co.yixiang.yshop.module.coupon.convert.coupon.CouponConvert;\nimport co.yixiang.yshop.module.coupon.dal.dataobject.coupon.CouponDO;\nimport co.yixiang.yshop.module.coupon.dal.mysql.coupon.CouponMapper;\nimport co.yixiang.yshop.module.store.dal.dataobject.storeshop.StoreShopDO;\nimport co.yixiang.yshop.module.store.dal.mysql.storeshop.StoreShopMapper;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport com.baomidou.mybatisplus.extension.conditions.query.LambdaQueryChainWrapper;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\nimport java.util.Collection;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.coupon.enums.ErrorCodeConstants.COUPON_NOT_EXISTS;\n\n/**\n * 优惠券 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class CouponServiceImpl implements CouponService {\n\n    @Resource\n    private CouponMapper Mapper;\n    @Resource\n    private StoreShopMapper storeShopMapper;\n\n    @Override\n    public Long create(CouponCreateReqVO createReqVO) {\n        // 插入\n        CouponDO  couponDO = CouponConvert.INSTANCE.convert(createReqVO);\n        StoreShopDO storeShopDO = storeShopMapper.selectById(createReqVO.getShopId());\n        couponDO.setShopName(storeShopDO.getName());\n        Mapper.insert(couponDO);\n        // 返回\n        return couponDO.getId();\n    }\n\n    @Override\n    public void update(CouponUpdateReqVO updateReqVO) {\n        // 校验存在\n        validateExists(updateReqVO.getId());\n        // 更新\n        CouponDO updateObj = CouponConvert.INSTANCE.convert(updateReqVO);\n        StoreShopDO storeShopDO = storeShopMapper.selectById(updateReqVO.getShopId());\n        updateObj.setShopName(storeShopDO.getName());\n        Mapper.updateById(updateObj);\n    }\n\n    @Override\n    public void delete(Long id) {\n        // 校验存在\n        validateExists(id);\n        // 删除\n        Mapper.deleteById(id);\n    }\n\n    private void validateExists(Long id) {\n        if (Mapper.selectById(id) == null) {\n            throw exception(COUPON_NOT_EXISTS);\n        }\n    }\n\n    @Override\n    public CouponDO get(Long id) {\n        return Mapper.selectById(id);\n    }\n\n    @Override\n    public List<CouponDO> getList() {\n        return Mapper.selectList(new LambdaQueryWrapper<CouponDO>().eq(CouponDO::getShopId,0));\n    }\n\n    @Override\n    public PageResult<CouponDO> getPage(CouponPageReqVO pageReqVO) {\n        return Mapper.selectPage(pageReqVO);\n    }\n\n    @Override\n    public List<CouponDO> getList(CouponExportReqVO exportReqVO) {\n        return Mapper.selectList(exportReqVO);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/yshop-module-coupon-biz/src/main/java/co/yixiang/yshop/module/coupon/service/couponuser/AppCouponUserService.java",
    "content": "package co.yixiang.yshop.module.coupon.service.couponuser;\n\nimport co.yixiang.yshop.module.coupon.controller.app.coupon.vo.AppMyCouponVO;\nimport co.yixiang.yshop.module.coupon.dal.dataobject.couponuser.CouponUserDO;\nimport com.baomidou.mybatisplus.extension.service.IService;\n\nimport java.util.List;\n\n/**\n * 用户领的优惠券 Service 接口\n *\n * @author yshop\n */\npublic interface AppCouponUserService extends IService<CouponUserDO> {\n\n    /**\n     * 获取我的优惠券列表\n     * @param shopId 店铺id\n     * @param type\n     * @param page\n     * @param pagesize\n     * @return\n     */\n    List<AppMyCouponVO> getList(Long uid, Long shopId,int type, int page, int pagesize);\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/yshop-module-coupon-biz/src/main/java/co/yixiang/yshop/module/coupon/service/couponuser/AppCouponUserServiceImpl.java",
    "content": "package co.yixiang.yshop.module.coupon.service.couponuser;\n\nimport co.yixiang.yshop.framework.common.enums.ShopCommonEnum;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.coupon.controller.app.coupon.vo.AppMyCouponVO;\nimport co.yixiang.yshop.module.coupon.convert.couponuser.CouponUserConvert;\nimport co.yixiang.yshop.module.coupon.dal.dataobject.couponuser.CouponUserDO;\nimport co.yixiang.yshop.module.coupon.dal.mysql.couponuser.CouponUserMapper;\nimport co.yixiang.yshop.module.coupon.enums.CouponStatusEnum;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\nimport java.time.LocalDateTime;\nimport java.util.List;\n\n/**\n * 用户领的优惠券 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class AppCouponUserServiceImpl extends ServiceImpl<CouponUserMapper, CouponUserDO> implements AppCouponUserService {\n\n    @Resource\n    private CouponUserMapper userMapper;\n\n\n\n    /**\n     * 获取我的优惠券列表\n     * @param shopId 店铺id\n     * @param page\n     * @param pagesize\n     * @return\n     */\n    @Override\n    public List<AppMyCouponVO> getList(Long uid, Long shopId, int type, int page, int pagesize) {\n        LocalDateTime nowTime = LocalDateTime.now();\n        Page<CouponUserDO> pageModel = new Page<>(page, pagesize);\n        LambdaQueryWrapperX<CouponUserDO> wrapper = new LambdaQueryWrapperX<>();\n        switch (CouponStatusEnum.toType(type)) {\n            case STATUS_0:\n                wrapper.eq(CouponUserDO::getStatus,CouponStatusEnum.STATUS_0.getValue())\n                        .lt(CouponUserDO::getStartTime,nowTime)\n                        .gt(CouponUserDO::getEndTime,nowTime);\n                break;\n            case STATUS_1:\n                wrapper.eq(CouponUserDO::getStatus,CouponStatusEnum.STATUS_1.getValue())\n                        .lt(CouponUserDO::getStartTime,nowTime)\n                        .gt(CouponUserDO::getEndTime,nowTime);\n                break;\n            case STATUS_2:\n                wrapper.lt(CouponUserDO::getEndTime,nowTime);\n                break;\n            default:\n                log.warn(\"为了遵循阿里巴巴规范\");\n        }\n        wrapper.eqIfPresent(CouponUserDO::getShopId, shopId)\n                .eq(CouponUserDO::getUserId,uid);\n        IPage<CouponUserDO> pageList = this.baseMapper.selectPage(pageModel, wrapper);\n        return CouponUserConvert.INSTANCE.convertList03(pageList.getRecords());\n    }\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/yshop-module-coupon-biz/src/main/java/co/yixiang/yshop/module/coupon/service/couponuser/CouponUserService.java",
    "content": "package co.yixiang.yshop.module.coupon.service.couponuser;\n\nimport java.util.*;\nimport jakarta.validation.*;\nimport co.yixiang.yshop.module.coupon.controller.admin.couponuser.vo.*;\nimport co.yixiang.yshop.module.coupon.dal.dataobject.couponuser.CouponUserDO;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\n/**\n * 用户领的优惠券 Service 接口\n *\n * @author yshop\n */\npublic interface CouponUserService {\n\n    /**\n     * 创建用户领的优惠券\n     *\n     * @param createReqVO 创建信息\n     * @return 编号\n     */\n    Integer createUser(@Valid CouponUserCreateReqVO createReqVO);\n\n    /**\n     * 更新用户领的优惠券\n     *\n     * @param updateReqVO 更新信息\n     */\n    void updateUser(@Valid CouponUserUpdateReqVO updateReqVO);\n\n    /**\n     * 删除用户领的优惠券\n     *\n     * @param id 编号\n     */\n    void deleteUser(Integer id);\n\n    /**\n     * 获得用户领的优惠券\n     *\n     * @param id 编号\n     * @return 用户领的优惠券\n     */\n    CouponUserDO getUser(Integer id);\n\n    /**\n     * 获得用户领的优惠券列表\n     *\n     * @param id 编号\n     * @return 用户领的优惠券列表\n     */\n    List<CouponUserDO> getUserList(Integer id);\n\n    /**\n     * 获得用户领的优惠券分页\n     *\n     * @param pageReqVO 分页查询\n     * @return 用户领的优惠券分页\n     */\n    PageResult<CouponUserDO> getUserPage(CouponUserPageReqVO pageReqVO);\n\n    /**\n     * 获得用户领的优惠券列表, 用于 Excel 导出\n     *\n     * @param exportReqVO 查询条件\n     * @return 用户领的优惠券列表\n     */\n    List<CouponUserDO> getUserList(CouponUserExportReqVO exportReqVO);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/yshop-module-coupon-biz/src/main/java/co/yixiang/yshop/module/coupon/service/couponuser/CouponUserServiceImpl.java",
    "content": "package co.yixiang.yshop.module.coupon.service.couponuser;\n\nimport org.springframework.stereotype.Service;\nimport jakarta.annotation.Resource;\nimport org.springframework.validation.annotation.Validated;\n\nimport java.util.*;\nimport co.yixiang.yshop.module.coupon.controller.admin.couponuser.vo.*;\nimport co.yixiang.yshop.module.coupon.dal.dataobject.couponuser.CouponUserDO;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\nimport co.yixiang.yshop.module.coupon.convert.couponuser.CouponUserConvert;\nimport co.yixiang.yshop.module.coupon.dal.mysql.couponuser.CouponUserMapper;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.coupon.enums.ErrorCodeConstants.*;\n\n/**\n * 用户领的优惠券 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class CouponUserServiceImpl implements CouponUserService {\n\n    @Resource\n    private CouponUserMapper userMapper;\n\n    @Override\n    public Integer createUser(CouponUserCreateReqVO createReqVO) {\n        // 插入\n        CouponUserDO user = CouponUserConvert.INSTANCE.convert(createReqVO);\n        userMapper.insert(user);\n        // 返回\n        return user.getId();\n    }\n\n    @Override\n    public void updateUser(CouponUserUpdateReqVO updateReqVO) {\n        // 校验存在\n        validateUserExists(updateReqVO.getId());\n        // 更新\n        CouponUserDO updateObj = CouponUserConvert.INSTANCE.convert(updateReqVO);\n        userMapper.updateById(updateObj);\n    }\n\n    @Override\n    public void deleteUser(Integer id) {\n        // 校验存在\n        validateUserExists(id);\n        // 删除\n        userMapper.deleteById(id);\n    }\n\n    private void validateUserExists(Integer id) {\n        if (userMapper.selectById(id) == null) {\n            throw exception(COUPON_USER_NOT_EXISTS);\n        }\n    }\n\n    @Override\n    public CouponUserDO getUser(Integer id) {\n        return userMapper.selectById(id);\n    }\n\n    @Override\n    public List<CouponUserDO> getUserList(Integer id) {\n        CouponUserExportReqVO exportReqVO = new CouponUserExportReqVO();\n        exportReqVO.setCouponId(id);\n        return userMapper.selectList(exportReqVO);\n    }\n\n    @Override\n    public PageResult<CouponUserDO> getUserPage(CouponUserPageReqVO pageReqVO) {\n        return userMapper.selectPage(pageReqVO);\n    }\n\n    @Override\n    public List<CouponUserDO> getUserList(CouponUserExportReqVO exportReqVO) {\n        return userMapper.selectList(exportReqVO);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-marketing/yshop-module-coupon-biz/src/main/resources/mapper/coupon/CouponMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"co.yixiang.yshop.module.coupon.dal.mysql.coupon.CouponMapper\">\n\n    <!--\n        一般情况下，尽可能使用 Mapper 进行 CRUD 增删改查即可。\n        无法满足的场景，例如说多表关联查询，才使用 XML 编写 SQL。\n        代码生成器暂时只生成 Mapper XML 文件本身，更多推荐 MybatisX 快速开发插件来生成查询。\n        文档可见：https://www.yixiang.co/MyBatis/x-plugins/\n     -->\n\n</mapper>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>co.yixiang.boot</groupId>\n        <artifactId>yshop</artifactId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <modules>\n        <module>yshop-module-member-api</module>\n        <module>yshop-module-member-biz</module>\n    </modules>\n    <artifactId>yshop-module-member</artifactId>\n    <packaging>pom</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>\n        member 模块，我们放会员业务。\n        例如说：会员中心等等\n    </description>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-api/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>co.yixiang.boot</groupId>\n        <artifactId>yshop-module-member</artifactId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>yshop-module-member-api</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>\n        member 模块 API，暴露给其它模块调用\n    </description>\n\n    <dependencies>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-common</artifactId>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-api/src/main/java/co/yixiang/yshop/module/member/api/package-info.java",
    "content": "/**\n * member API 包，定义暴露给其它模块的 API\n */\npackage co.yixiang.yshop.module.member.api;\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-api/src/main/java/co/yixiang/yshop/module/member/api/user/MemberUserApi.java",
    "content": "package co.yixiang.yshop.module.member.api.user;\n\nimport co.yixiang.yshop.module.member.api.user.dto.MemberUserRespDTO;\nimport co.yixiang.yshop.module.member.api.user.dto.WechatUserDto;\n\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Map;\n\nimport static co.yixiang.yshop.framework.common.util.collection.CollectionUtils.convertMap;\n\n/**\n * 会员用户的 API 接口\n *\n * @author yshop\n */\npublic interface MemberUserApi {\n\n    void saveWechatMember(WechatUserDto wechatUserDto);\n\n    /**\n     * 获得会员用户信息\n     *\n     * @param id 用户编号\n     * @return 用户信息\n     */\n    MemberUserRespDTO getUser(Long id);\n\n    /**\n     * 获得会员用户信息们\n     *\n     * @param ids 用户编号的数组\n     * @return 用户信息们\n     */\n    List<MemberUserRespDTO> getUsers(Collection<Long> ids);\n\n    /**\n     * 获得会员用户 Map\n     *\n     * @param ids 用户编号的数组\n     * @return 会员用户 Map\n     */\n    default Map<Long, MemberUserRespDTO> getUserMap(Collection<Long> ids) {\n        return convertMap(getUsers(ids), MemberUserRespDTO::getId);\n    }\n\n    /**\n     * 基于用户昵称，模糊匹配用户列表\n     *\n     * @param nickname 用户昵称，模糊匹配\n     * @return 用户信息的列表\n     */\n    List<MemberUserRespDTO> getUserListByNickname(String nickname);\n\n    /**\n     * 基于手机号，精准匹配用户\n     *\n     * @param mobile 手机号\n     * @return 用户信息\n     */\n    MemberUserRespDTO getUserByMobile(String mobile);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-api/src/main/java/co/yixiang/yshop/module/member/api/user/dto/MemberUserRespDTO.java",
    "content": "package co.yixiang.yshop.module.member.api.user.dto;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport lombok.Data;\n\n/**\n * 用户信息 Response DTO\n *\n * @author yshop\n */\n@Data\npublic class MemberUserRespDTO {\n\n    /**\n     * 用户ID\n     */\n    private Long id;\n    /**\n     * 用户昵称\n     */\n    private String nickname;\n    /**\n     * 帐号状态\n     *\n     * 枚举 {@link CommonStatusEnum}\n     */\n    private Integer status;\n\n    /**\n     * 手机\n     */\n    private String mobile;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-api/src/main/java/co/yixiang/yshop/module/member/api/user/dto/WechatUserDto.java",
    "content": "package co.yixiang.yshop.module.member.api.user.dto;\r\n\r\nimport lombok.*;\r\n\r\n/**\r\n * @ClassName WechatUserDTO\r\n * @Author hupeng <610796224@qq.com>\r\n * @Date 2023/7/18\r\n **/\r\n@Getter\r\n@Setter\r\n@Builder\r\n@AllArgsConstructor\r\n@NoArgsConstructor\r\npublic class WechatUserDto {\r\n\r\n    private String openid;\r\n\r\n    private String unionId;\r\n\r\n    private String routineOpenid;\r\n\r\n    private String nickname;\r\n\r\n    private String headimgurl;\r\n\r\n    private String sex;\r\n\r\n    private String city;\r\n\r\n    private String language;\r\n\r\n    private String province;\r\n\r\n    private String country;\r\n\r\n    private Boolean subscribe;\r\n\r\n    private Long subscribeTime;\r\n\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-api/src/main/java/co/yixiang/yshop/module/member/enums/BillDetailEnum.java",
    "content": "/**\n * Copyright (C) 2018-2022\n * All rights reserved, Designed By www.yixiang.co\n\n */\npackage co.yixiang.yshop.module.member.enums;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * @author hupeng\n * 账单明细相关枚举\n */\n@Getter\n@AllArgsConstructor\npublic enum BillDetailEnum {\n\n\tTYPE_1(\"recharge\",\"充值\"),\n\tTYPE_2(\"brokerage\",\"返佣\"),\n\tTYPE_3(\"pay_product\",\"消费\"),\n\tTYPE_4(\"extract\",\"提现\"),\n\tTYPE_5(\"pay_product_refund\",\"退款\"),\n\tTYPE_6(\"system_add\",\"系统添加\"),\n\tTYPE_7(\"system_sub\",\"系统减少\"),\n\tTYPE_8(\"deduction\",\"减去\"),\n\tTYPE_9(\"gain\",\"奖励\"),\n\tTYPE_10(\"sign\",\"签到\"),\n\n\n\tCATEGORY_1(\"now_money\",\"金额\"),\n\tCATEGORY_2(\"integral\",\"积分\");\n\n\n\n\tprivate String value;\n\tprivate String desc;\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-api/src/main/java/co/yixiang/yshop/module/member/enums/BillEnum.java",
    "content": "/**\n * Copyright (C) 2018-2022\n * All rights reserved, Designed By www.yixiang.co\n\n */\npackage co.yixiang.yshop.module.member.enums;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\nimport java.util.stream.Stream;\n\n/**\n * @author hupeng\n * 账单相关枚举\n */\n@Getter\n@AllArgsConstructor\npublic enum BillEnum {\n\n\tPM_0(0,\"支出\"),\n\tPM_1(1,\"获得\"),\n\n\tSTATUS_0(0,\"默认\"),\n\tSTATUS_1(1,\"有效\"),\n\tSTATUS_2(2,\"无效\");\n\n\n\n\n\n\tprivate Integer value;\n\tprivate String desc;\n\n\tpublic static BillEnum toType(int value) {\n\t\treturn Stream.of(BillEnum.values())\n\t\t\t\t.filter(p -> p.value == value)\n\t\t\t\t.findAny()\n\t\t\t\t.orElse(null);\n\t}\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-api/src/main/java/co/yixiang/yshop/module/member/enums/ErrorCodeConstants.java",
    "content": "package co.yixiang.yshop.module.member.enums;\n\nimport co.yixiang.yshop.framework.common.exception.ErrorCode;\n\n/**\n * Member 错误码枚举类\n *\n * member 系统，使用 1-004-000-000 段\n */\npublic interface ErrorCodeConstants {\n\n    // ========== 用户相关  1004001000============\n    ErrorCode USER_NOT_EXISTS = new ErrorCode(1004001000, \"用户不存在\");\n    ErrorCode USER_PASSWORD_FAILED = new ErrorCode(1004001001, \"密码校验失败\");\n\n    // ========== AUTH 模块 1004003000 ==========\n    ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1004003000, \"登录失败，账号密码不正确\");\n    ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1004003001, \"登录失败，账号被禁用\");\n    ErrorCode AUTH_TOKEN_EXPIRED = new ErrorCode(1004003004, \"Token 已经过期\");\n    ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1004003005, \"未绑定账号，需要进行绑定\");\n    ErrorCode AUTH_WEIXIN_MINI_APP_PHONE_CODE_ERROR = new ErrorCode(1004003006, \"获得手机号失败\");\n\n    // ========== 用户收件地址 1004004000 ==========\n    ErrorCode USER_ADDRESS_NOT_EXISTS = new ErrorCode(1004004000, \"用户收件地址不存在\");\n    ErrorCode USER_ADDRESS_PARAM_NOT_EXISTS = new ErrorCode(1004004001, \"用户收件地址参数错误\");\n    ErrorCode USER_BILL_NOT_EXISTS = new ErrorCode(1004004001, \"用户账单不存在\");\n    ErrorCode MINI_AUTH_LOGIN_BAD = new ErrorCode(1004004002, \"登录失败，请联系管理员\");\n    ErrorCode COUPON_NOT_CONDITION = new ErrorCode(1004004003, \"此优惠券不满足使用提交\");\n    ErrorCode MINI_AUTH_LOGIN_BAD2 = new ErrorCode(1004004002, \"登录失败，请返回首页刷新重新登录\");\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-api/src/main/java/co/yixiang/yshop/module/member/enums/LoginTypeEnum.java",
    "content": "/**\n * Copyright (C) 2018-2022\n * All rights reserved, Designed By www.yixiang.co\n\n */\npackage co.yixiang.yshop.module.member.enums;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * @author hupeng\n * 账单明细相关枚举\n */\n@Getter\n@AllArgsConstructor\npublic enum LoginTypeEnum {\n\tWEIXIN_H5(\"weixinh5\",\"weixinh5\"),\n\tH5(\"h5\",\"H5\"),\n\tWECHAT(\"wechat\",\"公众号\"),\n\tAPP(\"app\",\"APP\"),\n\tPC(\"pc\",\"PC\"),\n\tROUNTINE(\"routine\",\"小程序\"),\n\tUNIAPPH5(\"uniappH5\",\"uniappH5\");\n\n//\n//\tWXAPP(\"wxapp\",\"微信小程序\"),\n//\tALIAPP(\"aliapp\",\"支付宝小程序\"),\n//\tWECHAT(\"wechat\",\"微信公众号\"),\n//\tH5(\"h5\",\"h5\"),\n//\tPC(\"pc\",\"pc\"),\n//\tAPP(\"app\",\"APP\");\n\n\n\n\n\tprivate String value;\n\tprivate String desc;\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>co.yixiang.boot</groupId>\n        <artifactId>yshop-module-member</artifactId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>yshop-module-member-biz</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>\n        member 模块，我们放会员业务。\n        例如说：会员中心等等\n    </description>\n\n    <dependencies>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-member-api</artifactId>\n            <version>${revision}</version>\n        </dependency>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-system-api</artifactId>\n            <version>${revision}</version>\n        </dependency>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-infra-api</artifactId>\n            <version>${revision}</version>\n        </dependency>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-coupon-biz</artifactId>\n            <version>${revision}</version>\n        </dependency>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-shop-biz</artifactId>\n            <version>${revision}</version>\n        </dependency>\n\n        <!-- 业务组件 -->\n        <dependency>\n            <groupId>com.github.binarywang</groupId>\n            <artifactId>wx-java-mp-spring-boot-starter</artifactId> <!-- 微信登录（公众号） -->\n        </dependency>\n        <dependency>\n            <groupId>com.github.binarywang</groupId>\n            <artifactId>wx-java-miniapp-spring-boot-starter</artifactId>  <!-- 微信登录（小程序） -->\n        </dependency>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-biz-tenant</artifactId>\n        </dependency>\n\n        <!-- Web 相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-security</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-validation</artifactId>\n        </dependency>\n\n        <!-- DB 相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-mybatis</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-redis</artifactId>\n        </dependency>\n        <!-- 消息队列相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-mq</artifactId>\n        </dependency>\n\n        <!-- Test 测试相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-biz-ip</artifactId>\n        </dependency>\n\n\n        <!-- 工具类相关 -->\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/api/package-info.java",
    "content": "package co.yixiang.yshop.module.member.api;\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/api/user/MemberUserApiImpl.java",
    "content": "package co.yixiang.yshop.module.member.api.user;\n\nimport cn.hutool.core.util.IdUtil;\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.enums.ShopCommonEnum;\nimport co.yixiang.yshop.module.member.api.user.dto.MemberUserRespDTO;\nimport co.yixiang.yshop.module.member.api.user.dto.WechatUserDto;\nimport co.yixiang.yshop.module.member.convert.user.UserConvert;\nimport co.yixiang.yshop.module.member.dal.dataobject.user.MemberUserDO;\nimport co.yixiang.yshop.module.member.service.user.MemberUserService;\nimport jakarta.annotation.Resource;\nimport org.springframework.security.crypto.password.PasswordEncoder;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport java.util.Collection;\nimport java.util.List;\n\n/**\n * 会员用户的 API 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class MemberUserApiImpl implements MemberUserApi {\n\n    @Resource\n    private MemberUserService userService;\n    @Resource\n    private PasswordEncoder passwordEncoder;\n\n    @Override\n    public void saveWechatMember(WechatUserDto wechatUserDto) {\n        MemberUserDO user = new MemberUserDO();\n        user.setNickname(wechatUserDto.getNickname());\n        user.setAvatar(wechatUserDto.getHeadimgurl());\n        // 生成密码\n        String password = IdUtil.fastSimpleUUID();\n        user.setPassword(encodePassword(password));\n        user.setUsername(wechatUserDto.getOpenid());\n        user.setLoginType(\"wechat\");\n        user.setWxProfile(wechatUserDto);\n\n        userService.save(user);\n    }\n\n    @Override\n    public MemberUserRespDTO getUser(Long id) {\n        MemberUserDO user = userService.getUser(id);\n        return UserConvert.INSTANCE.convert2(user);\n    }\n\n    @Override\n    public List<MemberUserRespDTO> getUsers(Collection<Long> ids) {\n        return UserConvert.INSTANCE.convertList2(userService.getUserList(ids));\n    }\n\n    @Override\n    public List<MemberUserRespDTO> getUserListByNickname(String nickname) {\n        return UserConvert.INSTANCE.convertList2(userService.getUserListByNickname(nickname));\n    }\n\n    @Override\n    public MemberUserRespDTO getUserByMobile(String mobile) {\n        return UserConvert.INSTANCE.convert2(userService.getUserByMobile(mobile));\n    }\n\n    /**\n     * 对密码进行加密\n     *\n     * @param password 密码\n     * @return 加密后的密码\n     */\n    private String encodePassword(String password) {\n        return passwordEncoder.encode(password);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/admin/user/MemberUserController.java",
    "content": "package co.yixiang.yshop.module.member.controller.admin.user;\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.member.controller.admin.user.vo.*;\nimport co.yixiang.yshop.module.member.convert.user.UserConvert;\nimport co.yixiang.yshop.module.member.dal.dataobject.user.MemberUserDO;\nimport co.yixiang.yshop.module.member.service.user.UserService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Valid;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.Collection;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 用户\")\n@RestController\n@RequestMapping(\"/member/user\")\n@Validated\npublic class MemberUserController {\n\n    @Resource\n    private UserService userService;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建用户\")\n    @PreAuthorize(\"@ss.hasPermission('member:user:create')\")\n    public CommonResult<Long> createUser(@Valid @RequestBody UserCreateReqVO createReqVO) {\n        return success(userService.createUser(createReqVO));\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"更新用户\")\n    @PreAuthorize(\"@ss.hasPermission('member:user:update')\")\n    public CommonResult<Boolean> updateUser(@Valid @RequestBody UserUpdateReqVO updateReqVO) {\n        userService.updateUser(updateReqVO);\n        return success(true);\n    }\n\n    @PutMapping(\"/updateMony\")\n    @Operation(summary = \"更新用户余额与积分\")\n    @PreAuthorize(\"@ss.hasPermission('member:user:update')\")\n    public CommonResult<Boolean> updateMony(@Valid @RequestBody UserUpdateMoneyReqVO updateReqVO) {\n        userService.updateMony(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除用户\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('member:user:delete')\")\n    public CommonResult<Boolean> deleteUser(@RequestParam(\"id\") Long id) {\n        userService.deleteUser(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得用户\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('member:user:query')\")\n    public CommonResult<UserRespVO> getUser(@RequestParam(\"id\") Long id) {\n        MemberUserDO user = userService.getUser(id);\n        return success(UserConvert.INSTANCE.convert(user,true));\n    }\n\n    @GetMapping(\"/list\")\n    @Operation(summary = \"获得用户列表\")\n    @Parameter(name = \"ids\", description = \"编号列表\", required = true, example = \"1024,2048\")\n    @PreAuthorize(\"@ss.hasPermission('member:user:query')\")\n    public CommonResult<List<UserRespVO>> getUserList(@RequestParam(\"ids\") Collection<Long> ids) {\n        List<MemberUserDO> list = userService.getUserList(ids);\n        return success(UserConvert.INSTANCE.convertList(list));\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得用户分页\")\n    @PreAuthorize(\"@ss.hasPermission('member:user:query')\")\n    public CommonResult<PageResult<UserRespVO>> getUserPage(@Valid UserPageReqVO pageVO) {\n        PageResult<MemberUserDO> pageResult = userService.getUserPage(pageVO);\n        return success(UserConvert.INSTANCE.convertPage(pageResult));\n    }\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/admin/user/vo/UserBaseVO.java",
    "content": "package co.yixiang.yshop.module.member.controller.admin.user.vo;\n\nimport co.yixiang.yshop.module.member.api.user.dto.WechatUserDto;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.math.BigDecimal;\n\n\n/**\n* 用户 Base VO，提供给添加、修改、详细的子 VO 使用\n* 如果子 VO 存在差异的字段，请不要添加到这里，影响 Swagger 文档生成\n*/\n@Data\npublic class UserBaseVO {\n\n    @Schema(description = \"用户账户(跟accout一样)\", example = \"王五\")\n    private String username;\n\n    @Schema(description = \"真实姓名\", example = \"王五\")\n    private String realName;\n\n    @Schema(description = \"用户昵称\", example = \"李四\")\n    private String nickname;\n\n    @Schema(description = \"用户头像\")\n    private String avatar;\n\n    //@MobileDesensitize\n    @Schema(description = \"手机号码\")\n    private String mobile;\n\n    @Schema(description = \"添加ip\")\n    private String addIp;\n\n    @Schema(description = \"用户余额\", required = true)\n    //@NotNull(message = \"用户余额不能为空\")\n    private BigDecimal nowMoney;\n\n    @Schema(description = \"佣金金额\", required = true, example = \"14395\")\n    //@NotNull(message = \"佣金金额不能为空\")\n    private BigDecimal brokeragePrice;\n\n    @Schema(description = \"用户剩余积分\", required = true)\n   // @NotNull(message = \"用户剩余积分不能为空\")\n    private BigDecimal integral;\n\n    @Schema(description = \"1为正常，0为禁止\", required = true, example = \"2\")\n   // @NotNull(message = \"1为正常，0为禁止不能为空\")\n    private Integer status;\n\n    @Schema(description = \"是否为推广员\", required = true)\n   // @NotNull(message = \"是否为推广员不能为空\")\n    private Integer isPromoter;\n\n    @Schema(description = \"用户购买次数\", example = \"16061\")\n    private Integer payCount;\n\n    @Schema(description = \"下级人数\", example = \"4960\")\n    private Integer spreadCount;\n\n    @Schema(description = \"详细地址\", required = true)\n   // @NotNull(message = \"详细地址不能为空\")\n    private String addres;\n\n    @Schema(description = \"管理员编号 \", example = \"29490\")\n    private Integer adminid;\n\n    @Schema(description = \"用户登陆类型，h5,wechat,routine\", required = true, example = \"2\")\n   // @NotNull(message = \"用户登陆类型，h5,wechat,routine不能为空\")\n    private String loginType;\n\n    @Schema(description = \"微信用户json信息\")\n    private WechatUserDto wxProfile;\n\n    @Schema(description = \"生日\")\n    private String birthday;\n\n    /**\n     * 最后一次登录ip\n     */\n    private String loginIp;\n\n    /**\n     * 最后一次登录ip\n     */\n    private String lastIp;\n\n    /**\n     * 等级\n     */\n    private Integer level;\n\n    /**\n     * 推广元id\n     */\n    private Long spreadUid;\n\n    /**\n     * 身份证号码\n     */\n    private String cardId;\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/admin/user/vo/UserCreateReqVO.java",
    "content": "package co.yixiang.yshop.module.member.controller.admin.user.vo;\n\nimport jakarta.validation.constraints.NotNull;\nimport lombok.*;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport org.springframework.format.annotation.DateTimeFormat;\nimport java.time.LocalDateTime;\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 用户创建 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class UserCreateReqVO extends UserBaseVO {\n\n    @Schema(description = \"用户密码（跟pwd）\")\n    private String password;\n\n    @Schema(description = \"生日\")\n    private String birthday;\n\n    @Schema(description = \"身份证号码\", example = \"29961\")\n    private String cardId;\n\n    @Schema(description = \"用户备注\")\n    private String mark;\n\n    @Schema(description = \"合伙人id\", example = \"4234\")\n    private Integer partnerId;\n\n    @Schema(description = \"用户分组id\", example = \"12625\")\n    private Integer groupId;\n\n    @Schema(description = \"最后一次登录ip\")\n    private String lastIp;\n\n    @Schema(description = \"连续签到天数\", required = true)\n    @NotNull(message = \"连续签到天数不能为空\")\n    private Integer signNum;\n\n    @Schema(description = \"等级\", required = true)\n    @NotNull(message = \"等级不能为空\")\n    private Integer level;\n\n    @Schema(description = \"推广元id\", example = \"5747\")\n    private Long spreadUid;\n\n    @Schema(description = \"推广员关联时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime spreadTime;\n\n    @Schema(description = \"用户类型\", required = true, example = \"1\")\n    @NotNull(message = \"用户类型不能为空\")\n    private String userType;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/admin/user/vo/UserExportReqVO.java",
    "content": "package co.yixiang.yshop.module.member.controller.admin.user.vo;\n\nimport lombok.*;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport java.time.LocalDateTime;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 用户 Excel 导出 Request VO，参数和 UserPageReqVO 是一致的\")\n@Data\npublic class UserExportReqVO {\n\n    @Schema(description = \"用户账户(跟accout一样)\", example = \"王五\")\n    private String username;\n\n    @Schema(description = \"真实姓名\", example = \"王五\")\n    private String realName;\n\n    @Schema(description = \"用户昵称\", example = \"李四\")\n    private String nickname;\n\n    @Schema(description = \"手机号码\")\n    private String phone;\n\n    @Schema(description = \"添加时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/admin/user/vo/UserPageReqVO.java",
    "content": "package co.yixiang.yshop.module.member.controller.admin.user.vo;\n\nimport lombok.*;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport org.springframework.format.annotation.DateTimeFormat;\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 用户分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class UserPageReqVO extends PageParam {\n\n    @Schema(description = \"用户账户(跟accout一样)\", example = \"王五\")\n    private String username;\n\n    @Schema(description = \"真实姓名\", example = \"王五\")\n    private String realName;\n\n    @Schema(description = \"用户昵称\", example = \"李四\")\n    private String nickname;\n\n    @Schema(description = \"手机号码\")\n    private String mobile;\n\n    private String phone;\n\n    @Schema(description = \"添加时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n    @Schema(description = \"登录类型\")\n    private String loginType;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/admin/user/vo/UserRespVO.java",
    "content": "package co.yixiang.yshop.module.member.controller.admin.user.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 用户 Response VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class UserRespVO extends UserBaseVO {\n\n    @Schema(description = \"用户id\", required = true, example = \"16370\")\n    private Long id;\n\n    @Schema(description = \"添加时间\")\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/admin/user/vo/UserUpdateMoneyReqVO.java",
    "content": "package co.yixiang.yshop.module.member.controller.admin.user.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotNull;\nimport lombok.Data;\nimport lombok.ToString;\n\n@Schema(description = \"管理后台 - 用户更新 Request VO\")\n@Data\n@ToString(callSuper = true)\npublic class UserUpdateMoneyReqVO {\n\n    @Schema(description = \"用户id\", required = true, example = \"16370\")\n    @NotNull(message = \"用户id不能为空\")\n    private Long id;\n\n    @Schema(description = \"修改金额类型\")\n    private Integer ptype;\n\n    @Schema(description = \"金额\")\n    @NotNull(message = \"金额不能为空\")\n    private String money;\n\n    @Schema(description = \"修改积分类型\")\n    private Integer itype;\n\n    @Schema(description = \"积分\")\n    @NotNull(message = \"积分不能为空\")\n    private String integral;\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/admin/user/vo/UserUpdateReqVO.java",
    "content": "package co.yixiang.yshop.module.member.controller.admin.user.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotNull;\nimport lombok.*;\nimport org.springframework.format.annotation.DateTimeFormat;\nimport java.time.LocalDateTime;\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 用户更新 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class UserUpdateReqVO extends UserBaseVO {\n\n    @Schema(description = \"用户id\", required = true, example = \"16370\")\n    @NotNull(message = \"用户id不能为空\")\n    private Long id;\n\n    @Schema(description = \"用户密码（跟pwd）\")\n    private String password;\n\n    @Schema(description = \"生日\")\n    private String birthday;\n\n    @Schema(description = \"身份证号码\", example = \"29961\")\n    private String cardId;\n\n    @Schema(description = \"用户备注\")\n    private String mark;\n\n    @Schema(description = \"合伙人id\", example = \"4234\")\n    private Integer partnerId;\n\n    @Schema(description = \"用户分组id\", example = \"12625\")\n    private Integer groupId;\n\n    @Schema(description = \"最后一次登录ip\")\n    private String lastIp;\n\n    @Schema(description = \"连续签到天数\", required = true)\n    //@NotNull(message = \"连续签到天数不能为空\")\n    private Integer signNum;\n\n    @Schema(description = \"等级\", required = true)\n    //@NotNull(message = \"等级不能为空\")\n    private Integer level;\n\n    @Schema(description = \"推广元id\", example = \"5747\")\n    private Long spreadUid;\n\n    @Schema(description = \"推广员关联时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime spreadTime;\n\n    @Schema(description = \"用户类型\", required = true, example = \"1\")\n   // @NotNull(message = \"用户类型不能为空\")\n    private String userType;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/admin/useraddress/UserAddressController.java",
    "content": "package co.yixiang.yshop.module.member.controller.admin.useraddress;\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.member.controller.admin.useraddress.vo.UserAddressCreateReqVO;\nimport co.yixiang.yshop.module.member.controller.admin.useraddress.vo.UserAddressPageReqVO;\nimport co.yixiang.yshop.module.member.controller.admin.useraddress.vo.UserAddressRespVO;\nimport co.yixiang.yshop.module.member.controller.admin.useraddress.vo.UserAddressUpdateReqVO;\nimport co.yixiang.yshop.module.member.convert.useraddress.UserAddressConvert;\nimport co.yixiang.yshop.module.member.dal.dataobject.useraddress.UserAddressDO;\nimport co.yixiang.yshop.module.member.service.useraddress.UserAddressService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Valid;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.Collection;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 用户地址\")\n@RestController\n@RequestMapping(\"/member/user-address\")\n@Validated\npublic class UserAddressController {\n\n    @Resource\n    private UserAddressService userAddressService;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建用户地址\")\n    @PreAuthorize(\"@ss.hasPermission('member:user-address:create')\")\n    public CommonResult<Long> createUserAddress(@Valid @RequestBody UserAddressCreateReqVO createReqVO) {\n        return success(userAddressService.createUserAddress(createReqVO));\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"更新用户地址\")\n    @PreAuthorize(\"@ss.hasPermission('member:user-address:update')\")\n    public CommonResult<Boolean> updateUserAddress(@Valid @RequestBody UserAddressUpdateReqVO updateReqVO) {\n        userAddressService.updateUserAddress(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除用户地址\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('member:user-address:delete')\")\n    public CommonResult<Boolean> deleteUserAddress(@RequestParam(\"id\") Long id) {\n        userAddressService.deleteUserAddress(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得用户地址\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('member:user-address:query')\")\n    public CommonResult<UserAddressRespVO> getUserAddress(@RequestParam(\"id\") Long id) {\n        UserAddressDO userAddress = userAddressService.getUserAddress(id);\n        return success(UserAddressConvert.INSTANCE.convert(userAddress));\n    }\n\n    @GetMapping(\"/list\")\n    @Operation(summary = \"获得用户地址列表\")\n    @Parameter(name = \"ids\", description = \"编号列表\", required = true, example = \"1024,2048\")\n    @PreAuthorize(\"@ss.hasPermission('member:user-address:query')\")\n    public CommonResult<List<UserAddressRespVO>> getUserAddressList(@RequestParam(\"ids\") Collection<Long> ids) {\n        List<UserAddressDO> list = userAddressService.getUserAddressList(ids);\n        return success(UserAddressConvert.INSTANCE.convertList(list));\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得用户地址分页\")\n    @PreAuthorize(\"@ss.hasPermission('member:user-address:query')\")\n    public CommonResult<PageResult<UserAddressRespVO>> getUserAddressPage(@Valid UserAddressPageReqVO pageVO) {\n        PageResult<UserAddressDO> pageResult = userAddressService.getUserAddressPage(pageVO);\n        return success(UserAddressConvert.INSTANCE.convertPage(pageResult));\n    }\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/admin/useraddress/vo/UserAddressBaseVO.java",
    "content": "package co.yixiang.yshop.module.member.controller.admin.useraddress.vo;\n\nimport co.yixiang.yshop.framework.desensitize.core.slider.annotation.MobileDesensitize;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotNull;\nimport lombok.*;\nimport java.util.*;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\n\n\n/**\n* 用户地址 Base VO，提供给添加、修改、详细的子 VO 使用\n* 如果子 VO 存在差异的字段，请不要添加到这里，影响 Swagger 文档生成\n*/\n@Data\npublic class UserAddressBaseVO {\n\n    @Schema(description = \"用户id\", required = true, example = \"25124\")\n    @NotNull(message = \"用户id不能为空\")\n    private Long uid;\n\n    @Schema(description = \"收货人姓名\", required = true, example = \"李四\")\n    @NotNull(message = \"收货人姓名不能为空\")\n    private String realName;\n\n    @MobileDesensitize\n    @Schema(description = \"收货人电话\", required = true)\n    @NotNull(message = \"收货人电话不能为空\")\n    private String phone;\n\n    @Schema(description = \"收货人所在省\", required = true)\n    @NotNull(message = \"收货人所在省不能为空\")\n    private String province;\n\n    @Schema(description = \"收货人所在市\", required = true)\n    @NotNull(message = \"收货人所在市不能为空\")\n    private String city;\n\n    @Schema(description = \"城市id\", example = \"15595\")\n    private Integer cityId;\n\n    @Schema(description = \"收货人所在区\", required = true)\n    @NotNull(message = \"收货人所在区不能为空\")\n    private String district;\n\n    @Schema(description = \"收货人详细地址\", required = true)\n    @NotNull(message = \"收货人详细地址不能为空\")\n    private String detail;\n\n    @Schema(description = \"邮编\", required = true)\n    @NotNull(message = \"邮编不能为空\")\n    private String postCode;\n\n    @Schema(description = \"经度\", required = true)\n    @NotNull(message = \"经度不能为空\")\n    private String longitude;\n\n    @Schema(description = \"纬度\", required = true)\n    @NotNull(message = \"纬度不能为空\")\n    private String latitude;\n\n    @Schema(description = \"是否默认\", required = true)\n    @NotNull(message = \"是否默认不能为空\")\n    private Integer isDefault;\n\n    private String address;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/admin/useraddress/vo/UserAddressCreateReqVO.java",
    "content": "package co.yixiang.yshop.module.member.controller.admin.useraddress.vo;\n\nimport lombok.*;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\n\n@Schema(description = \"管理后台 - 用户地址创建 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class UserAddressCreateReqVO extends UserAddressBaseVO {\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/admin/useraddress/vo/UserAddressExportReqVO.java",
    "content": "package co.yixiang.yshop.module.member.controller.admin.useraddress.vo;\n\nimport lombok.*;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport java.time.LocalDateTime;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 用户地址 Excel 导出 Request VO，参数和 UserAddressPageReqVO 是一致的\")\n@Data\npublic class UserAddressExportReqVO {\n\n    @Schema(description = \"用户id\", example = \"25124\")\n    private Long uid;\n\n    @Schema(description = \"收货人姓名\", example = \"李四\")\n    private String realName;\n\n    @Schema(description = \"收货人电话\")\n    private String phone;\n\n    @Schema(description = \"收货人所在省\")\n    private String province;\n\n    @Schema(description = \"收货人所在市\")\n    private String city;\n\n    @Schema(description = \"城市id\", example = \"15595\")\n    private Integer cityId;\n\n    @Schema(description = \"收货人所在区\")\n    private String district;\n\n    @Schema(description = \"收货人详细地址\")\n    private String detail;\n\n    @Schema(description = \"邮编\")\n    private String postCode;\n\n    @Schema(description = \"经度\")\n    private String longitude;\n\n    @Schema(description = \"纬度\")\n    private String latitude;\n\n    @Schema(description = \"是否默认\")\n    private Byte isDefault;\n\n    @Schema(description = \"添加时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/admin/useraddress/vo/UserAddressPageReqVO.java",
    "content": "package co.yixiang.yshop.module.member.controller.admin.useraddress.vo;\n\nimport lombok.*;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport org.springframework.format.annotation.DateTimeFormat;\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 用户地址分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class UserAddressPageReqVO extends PageParam {\n\n    @Schema(description = \"用户id\", example = \"25124\")\n    private Long uid;\n\n    @Schema(description = \"收货人姓名\", example = \"李四\")\n    private String realName;\n\n    @Schema(description = \"收货人电话\")\n    private String phone;\n\n    @Schema(description = \"收货人所在省\")\n    private String province;\n\n    @Schema(description = \"收货人所在市\")\n    private String city;\n\n    @Schema(description = \"城市id\", example = \"15595\")\n    private Integer cityId;\n\n    @Schema(description = \"收货人所在区\")\n    private String district;\n\n    @Schema(description = \"收货人详细地址\")\n    private String detail;\n\n    @Schema(description = \"邮编\")\n    private String postCode;\n\n    @Schema(description = \"经度\")\n    private String longitude;\n\n    @Schema(description = \"纬度\")\n    private String latitude;\n\n    @Schema(description = \"是否默认\")\n    private Byte isDefault;\n\n    @Schema(description = \"添加时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/admin/useraddress/vo/UserAddressRespVO.java",
    "content": "package co.yixiang.yshop.module.member.controller.admin.useraddress.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 用户地址 Response VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class UserAddressRespVO extends UserAddressBaseVO {\n\n    @Schema(description = \"用户地址id\", required = true, example = \"24169\")\n    private Long id;\n\n    @Schema(description = \"添加时间\", required = true)\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/admin/useraddress/vo/UserAddressUpdateReqVO.java",
    "content": "package co.yixiang.yshop.module.member.controller.admin.useraddress.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotNull;\nimport lombok.*;\nimport java.util.*;\n\n\n@Schema(description = \"管理后台 - 用户地址更新 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class UserAddressUpdateReqVO extends UserAddressBaseVO {\n\n    @Schema(description = \"用户地址id\", required = true, example = \"24169\")\n    @NotNull(message = \"用户地址id不能为空\")\n    private Long id;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/admin/userbill/UserBillController.java",
    "content": "package co.yixiang.yshop.module.member.controller.admin.userbill;\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.member.controller.admin.userbill.vo.UserBillPageReqVO;\nimport co.yixiang.yshop.module.member.controller.admin.userbill.vo.UserBillRespVO;\nimport co.yixiang.yshop.module.member.convert.userbill.UserBillConvert;\nimport co.yixiang.yshop.module.member.dal.dataobject.userbill.UserBillDO;\nimport co.yixiang.yshop.module.member.service.userbill.UserBillService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Valid;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 用户账单\")\n@RestController\n@RequestMapping(\"/member/user-bill\")\n@Validated\npublic class UserBillController {\n\n    @Resource\n    private UserBillService userBillService;\n\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得用户账单分页\")\n    @PreAuthorize(\"@ss.hasPermission('member:user-bill:query')\")\n    public CommonResult<PageResult<UserBillRespVO>> getUserBillPage(@Valid UserBillPageReqVO pageVO) {\n        PageResult<UserBillDO> pageResult = userBillService.getUserBillPage(pageVO);\n        return success(UserBillConvert.INSTANCE.convertPage(pageResult));\n    }\n\n\n\n\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/admin/userbill/vo/UserBillBaseVO.java",
    "content": "package co.yixiang.yshop.module.member.controller.admin.userbill.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotNull;\nimport lombok.*;\nimport java.util.*;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\n\n\n/**\n* 用户账单 Base VO，提供给添加、修改、详细的子 VO 使用\n* 如果子 VO 存在差异的字段，请不要添加到这里，影响 Swagger 文档生成\n*/\n@Data\npublic class UserBillBaseVO {\n\n    @Schema(description = \"用户uid\", required = true, example = \"9419\")\n    @NotNull(message = \"用户uid不能为空\")\n    private Long uid;\n\n    @Schema(description = \"关联id\", required = true, example = \"18439\")\n    @NotNull(message = \"关联id不能为空\")\n    private String linkId;\n\n    @Schema(description = \"0 = 支出 1 = 获得\", required = true)\n    @NotNull(message = \"0 = 支出 1 = 获得不能为空\")\n    private Byte pm;\n\n    @Schema(description = \"账单标题\", required = true)\n    @NotNull(message = \"账单标题不能为空\")\n    private String title;\n\n    @Schema(description = \"明细种类\", required = true)\n    @NotNull(message = \"明细种类不能为空\")\n    private String category;\n\n    @Schema(description = \"明细类型\", required = true, example = \"2\")\n    @NotNull(message = \"明细类型不能为空\")\n    private String type;\n\n    @Schema(description = \"明细数字\", required = true)\n    @NotNull(message = \"明细数字不能为空\")\n    private BigDecimal number;\n\n    @Schema(description = \"剩余\", required = true)\n    @NotNull(message = \"剩余不能为空\")\n    private BigDecimal balance;\n\n    @Schema(description = \"备注\", required = true)\n    @NotNull(message = \"备注不能为空\")\n    private String mark;\n\n    @Schema(description = \"0 = 带确定 1 = 有效 -1 = 无效\", required = true, example = \"1\")\n    @NotNull(message = \"0 = 带确定 1 = 有效 -1 = 无效不能为空\")\n    private Integer status;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/admin/userbill/vo/UserBillCreateReqVO.java",
    "content": "package co.yixiang.yshop.module.member.controller.admin.userbill.vo;\n\nimport lombok.*;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\n\n@Schema(description = \"管理后台 - 用户账单创建 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class UserBillCreateReqVO extends UserBillBaseVO {\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/admin/userbill/vo/UserBillExportReqVO.java",
    "content": "package co.yixiang.yshop.module.member.controller.admin.userbill.vo;\n\nimport lombok.*;\n\nimport java.math.BigDecimal;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport java.time.LocalDateTime;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 用户账单 Excel 导出 Request VO，参数和 UserBillPageReqVO 是一致的\")\n@Data\npublic class UserBillExportReqVO {\n\n    @Schema(description = \"用户uid\", example = \"9419\")\n    private Long uid;\n\n    @Schema(description = \"关联id\", example = \"18439\")\n    private String linkId;\n\n    @Schema(description = \"0 = 支出 1 = 获得\")\n    private Byte pm;\n\n    @Schema(description = \"账单标题\")\n    private String title;\n\n    @Schema(description = \"明细种类\")\n    private String category;\n\n    @Schema(description = \"明细类型\", example = \"2\")\n    private String type;\n\n    @Schema(description = \"明细数字\")\n    private BigDecimal number;\n\n    @Schema(description = \"剩余\")\n    private BigDecimal balance;\n\n    @Schema(description = \"备注\")\n    private String mark;\n\n    @Schema(description = \"添加时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n    @Schema(description = \"0 = 带确定 1 = 有效 -1 = 无效\", example = \"1\")\n    private Boolean status;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/admin/userbill/vo/UserBillPageReqVO.java",
    "content": "package co.yixiang.yshop.module.member.controller.admin.userbill.vo;\n\nimport lombok.*;\n\nimport java.math.BigDecimal;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport org.springframework.format.annotation.DateTimeFormat;\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 用户账单分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class UserBillPageReqVO extends PageParam {\n\n    @Schema(description = \"用户uid\", example = \"9419\")\n    private Long uid;\n\n    @Schema(description = \"关联id\", example = \"18439\")\n    private String linkId;\n\n    @Schema(description = \"0 = 支出 1 = 获得\")\n    private Byte pm;\n\n    @Schema(description = \"账单标题\")\n    private String title;\n\n    @Schema(description = \"明细种类\")\n    private String category;\n\n    @Schema(description = \"明细类型\", example = \"2\")\n    private String type;\n\n    @Schema(description = \"明细数字\")\n    private BigDecimal number;\n\n    @Schema(description = \"剩余\")\n    private BigDecimal balance;\n\n    @Schema(description = \"备注\")\n    private String mark;\n\n    @Schema(description = \"添加时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n    @Schema(description = \"0 = 带确定 1 = 有效 -1 = 无效\", example = \"1\")\n    private Boolean status;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/admin/userbill/vo/UserBillRespVO.java",
    "content": "package co.yixiang.yshop.module.member.controller.admin.userbill.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 用户账单 Response VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class UserBillRespVO extends UserBillBaseVO {\n\n    @Schema(description = \"用户账单id\", required = true, example = \"22559\")\n    private Long id;\n\n    @Schema(description = \"添加时间\", required = true)\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/admin/userbill/vo/UserBillUpdateReqVO.java",
    "content": "package co.yixiang.yshop.module.member.controller.admin.userbill.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotNull;\nimport lombok.*;\n\n\n@Schema(description = \"管理后台 - 用户账单更新 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class UserBillUpdateReqVO extends UserBillBaseVO {\n\n    @Schema(description = \"用户账单id\", required = true, example = \"22559\")\n    @NotNull(message = \"用户账单id不能为空\")\n    private Long id;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/app/address/AppUserAddressController.java",
    "content": "/**\n * Copyright (C) 2018-2022\n * All rights reserved, Designed By www.yixiang.co\n * 注意：\n * 本软件为www.yixiang.co开发研制，未经购买不得使用\n * 购买后可获得全部源代码（禁止转卖、分享、上传到码云、github等开源平台）\n * 一经发现盗用、分享等行为，将追究法律责任，后果自负\n */\npackage co.yixiang.yshop.module.member.controller.app.address;\n\nimport cn.hutool.core.lang.Assert;\nimport cn.hutool.core.util.NumberUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.util.yshop.LocationUtils;\nimport co.yixiang.yshop.framework.ip.core.Area;\nimport co.yixiang.yshop.framework.ip.core.utils.AreaUtils;\nimport co.yixiang.yshop.framework.security.core.annotations.PreAuthenticated;\nimport co.yixiang.yshop.module.member.controller.app.address.param.AppAddressParam;\nimport co.yixiang.yshop.module.member.controller.app.address.vo.AppUserAddressLocationVo;\nimport co.yixiang.yshop.module.member.controller.app.address.vo.AppUserAddressQueryVo;\nimport co.yixiang.yshop.module.member.controller.app.address.vo.AreaNodeRespVO;\nimport co.yixiang.yshop.module.member.convert.useraddress.AreaConvert;\nimport co.yixiang.yshop.module.member.service.useraddress.AppUserAddressService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.validation.Valid;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\nimport static co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;\nimport static co.yixiang.yshop.module.member.enums.ErrorCodeConstants.USER_ADDRESS_PARAM_NOT_EXISTS;\n\n/**\n * <p>\n * 用户地前端控制器\n * </p>\n *\n * @author hupeng\n * @since 2023-6-28\n */\n@Slf4j\n@RestController\n@RequiredArgsConstructor(onConstructor = @__(@Autowired))\n@Tag(name = \"用户 APP - 用户地址\")\n@RequestMapping(\"/address\")\npublic class AppUserAddressController {\n\n    private final AppUserAddressService appUserAddressService;\n\n\n    @GetMapping(\"/city_list\")\n    @Operation(summary = \"城市列表\")\n    public CommonResult<List<AreaNodeRespVO>> getTest() {\n        Area area = AreaUtils.getArea(Area.ID_CHINA);\n        Assert.notNull(area, \"获取不到中国\");\n        return success(AreaConvert.INSTANCE.convertList(area.getChildren()));\n    }\n\n    /**\n    * 添加或修改地址\n    */\n    @PreAuthenticated\n    @PostMapping(\"/addAndEdit\")\n    @Operation(summary = \"添加或修改地址\")\n    public CommonResult<Long> addYxUserAddress(@RequestBody @Valid AppAddressParam param){\n        Long uid = getLoginUserId();\n        Long id = appUserAddressService.addAndEdit(uid,param);\n        return success(id);\n    }\n\n    /**\n     * 设置默认地址\n     */\n    @PreAuthenticated\n    @PostMapping(\"/default/set/{id}\")\n    @Parameter(name = \"id\", description = \"地址id\", required = true)\n    @Operation(summary = \"设置默认地址\")\n    public CommonResult<Boolean> setDefault(@PathVariable String id){\n        Long uid = getLoginUserId();\n        appUserAddressService.setDefault(uid,id);\n        return success(true);\n    }\n\n\n\n    /**\n    * 删除用户地址\n    */\n    @PreAuthenticated\n    @PostMapping(\"/del/{id}\")\n    @Operation(summary = \"删除用户地址\")\n    public CommonResult<Boolean> deleteYxUserAddress(@PathVariable String id){\n        if(StrUtil.isBlank(id) || !NumberUtil.isNumber(id)){\n            throw exception(USER_ADDRESS_PARAM_NOT_EXISTS);\n        }\n        appUserAddressService.removeById(id);\n        return success(true);\n    }\n\n\n    /**\n     * 用户地址列表\n     */\n    @PreAuthenticated\n    @GetMapping(\"/list\")\n    @Operation(summary = \"用户地址列表\")\n    public CommonResult<List<AppUserAddressQueryVo>> getYxUserAddressPageList(@RequestParam(value = \"page\",defaultValue = \"1\") int page,\n                                                                           @RequestParam(value = \"limit\",defaultValue = \"10\") int limit){\n        Long uid = getLoginUserId();\n        return success(appUserAddressService.getList(uid,page,limit));\n    }\n\n    /**\n     * 用户地址列表\n     */\n    @PostMapping(\"/getDistanceFromLocation\")\n    @Operation(summary = \"用户地址列表\")\n    public CommonResult<Double> getDistanceFromLocation( @RequestBody AppUserAddressLocationVo addressLocation){\n        return success(LocationUtils.getDistance(addressLocation.getLat(),addressLocation.getLng(),\n                addressLocation.getLat2(),addressLocation.getLng2()));\n    }\n\n\n\n\n\n}\n\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/app/address/param/AddressDetailParam.java",
    "content": "package co.yixiang.yshop.module.member.controller.app.address.param;\r\n\r\nimport io.swagger.v3.oas.annotations.media.Schema;\r\nimport lombok.Data;\r\n\r\nimport java.io.Serializable;\r\n\r\n/**\r\n * @ClassName AddressDetailParam\r\n * @Author hupeng <610796224@qq.com>\r\n * @Date 2023/6/28\r\n **/\r\n@Data\r\npublic class AddressDetailParam implements Serializable {\r\n\r\n    @Schema(description = \"城市ID\", required = true)\r\n    private Integer cityId;\r\n\r\n    @Schema(description = \"城市\", required = true)\r\n    private String city;\r\n\r\n    @Schema(description = \"地区\", required = true)\r\n    private String district;\r\n\r\n    @Schema(description = \"省份\", required = true)\r\n    private String province;\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/app/address/param/AppAddressParam.java",
    "content": "package co.yixiang.yshop.module.member.controller.app.address.param;\r\n\r\nimport com.fasterxml.jackson.annotation.JsonIgnoreProperties;\r\nimport io.swagger.v3.oas.annotations.media.Schema;\r\nimport jakarta.validation.constraints.NotBlank;\r\nimport jakarta.validation.constraints.Size;\r\nimport lombok.Data;\r\n\r\nimport java.io.Serializable;\r\n\r\n/**\r\n * @ClassName AddressParam\r\n * @Author hupeng <610796224@qq.com>\r\n * @Date 2023/6/28\r\n **/\r\n@Data\r\n@JsonIgnoreProperties(ignoreUnknown = true)\r\npublic class AppAddressParam implements Serializable {\r\n\r\n    @Schema(description = \"地址ID\", required = true)\r\n    private String id;\r\n\r\n    @NotBlank\r\n    @Size(min = 1, max = 30,message = \"长度超过了限制\")\r\n    @Schema(description = \"收货地址真实名字\", required = true)\r\n    private String realName;\r\n\r\n    @Schema(description = \"收货地址邮编\", required = true)\r\n    private String postCode;\r\n\r\n    @Schema(description = \"是否默认收货地址 1是 0否\", required = true)\r\n    private Integer isDefault;\r\n\r\n    @NotBlank\r\n    @Size(min = 1, max = 60,message = \"长度超过了限制\")\r\n    @Schema(description = \"收货详细地址\", required = true)\r\n    private String detail;\r\n\r\n    @NotBlank\r\n    @Schema(description = \"收货手机号码\", required = true)\r\n    private String phone;\r\n\r\n    @Schema(description = \"收货地址详情\", required = true)\r\n    private String address;\r\n\r\n    @Schema(description = \"经度\", required = true)\r\n    private String longitude;\r\n\r\n    @Schema(description = \"纬度\", required = true)\r\n    private String latitude;\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/app/address/vo/AppUserAddressLocationVo.java",
    "content": "package co.yixiang.yshop.module.member.controller.app.address.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.io.Serializable;\n\n/**\n * <p>\n * 用户地址表 查询结果对象\n * </p>\n *\n * @author hupeng\n * @date 2023-6-28\n */\n@Data\n@Schema(description = \"用户地址表查询参数\")\npublic class AppUserAddressLocationVo implements Serializable {\n    private static final long serialVersionUID = 1L;\n\n\n    private double lat;\n\n\n    private double lng;\n\n\n    private double lat2;\n\n\n    private double lng2;\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/app/address/vo/AppUserAddressQueryVo.java",
    "content": "package co.yixiang.yshop.module.member.controller.app.address.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.io.Serializable;\n\n/**\n * <p>\n * 用户地址表 查询结果对象\n * </p>\n *\n * @author hupeng\n * @date 2023-6-28\n */\n@Data\n@Schema(description = \"用户地址表查询参数\")\npublic class AppUserAddressQueryVo implements Serializable {\n    private static final long serialVersionUID = 1L;\n\n    @Schema(description = \"用户地址id\", required = true, example = \"24169\")\n    private Long id;\n\n    @Schema(description = \"用户id\", required = true, example = \"24169\")\n    private Long uid;\n\n    @Schema(description = \"收货人姓名\", required = true, example = \"24169\")\n    private String realName;\n\n    @Schema(description = \"收货人电话\", required = true, example = \"24169\")\n    private String phone;\n\n    @Schema(description = \"收货人所在省\", required = true, example = \"24169\")\n    private String address;\n\n    @Schema(description = \"收货人详细地址\", required = true, example = \"24169\")\n    private String detail;\n\n    @Schema(description = \"经度\", required = true, example = \"24169\")\n    private String longitude;\n\n    @Schema(description = \"纬度\", required = true, example = \"24169\")\n    private String latitude;\n\n    @Schema(description = \"是否默认\", required = true, example = \"24169\")\n    private Integer isDefault;\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/app/address/vo/AreaNodeRespVO.java",
    "content": "package co.yixiang.yshop.module.member.controller.app.address.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.util.List;\n\n@Schema(description = \"管理后台 - 地区节点 Response VO\")\n@Data\npublic class AreaNodeRespVO {\n\n    @Schema(description = \"编号\", required = true, example = \"110000\")\n    private Integer id;\n\n    @Schema(description = \"名字\", required = true, example = \"北京\")\n    private String name;\n\n    /**\n     * 子节点\n     */\n    private List<AreaNodeRespVO> children;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/app/auth/AppAuthController.java",
    "content": "package co.yixiang.yshop.module.member.controller.app.auth;\n\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.security.config.SecurityProperties;\nimport co.yixiang.yshop.framework.security.core.annotations.PreAuthenticated;\nimport co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils;\nimport co.yixiang.yshop.module.member.controller.app.auth.vo.*;\nimport co.yixiang.yshop.module.member.service.auth.MemberAuthService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.Parameters;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.annotation.Resource;\nimport jakarta.annotation.security.PermitAll;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.validation.Valid;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\nimport static co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;\n\n@Tag(name = \"用户 APP - 认证\")\n@RestController\n@RequestMapping(\"/member/auth\")\n@Validated\n@Slf4j\npublic class AppAuthController {\n\n    @Resource\n    private MemberAuthService authService;\n\n    @Resource\n    private SecurityProperties securityProperties;\n\n    @Value(\"${yshop.info.isActive}\")\n    private Boolean isActive;\n\n\n    @PostMapping(\"/login\")\n    @Operation(summary = \"使用手机 + 密码登录\")\n    public CommonResult<AppAuthLoginRespVO> login(@RequestBody @Valid AppAuthLoginReqVO reqVO) {\n        return success(authService.login(reqVO));\n    }\n\n    @PostMapping(\"/logout\")\n    @PermitAll\n    @Operation(summary = \"登出系统\")\n    public CommonResult<Boolean> logout(HttpServletRequest request) {\n        String token = SecurityFrameworkUtils.obtainAuthorization(request, securityProperties.getTokenHeader(),securityProperties.getTokenParameter());\n        if (StrUtil.isNotBlank(token)) {\n            authService.logout(token);\n        }\n        return success(true);\n    }\n\n    @PostMapping(\"/refresh-token\")\n    @Operation(summary = \"刷新令牌\")\n    @Parameter(name = \"refreshToken\", description = \"刷新令牌\", required = true)\n    public CommonResult<AppAuthLoginRespVO> refreshToken(@RequestParam(\"refreshToken\") String refreshToken) {\n        return success(authService.refreshToken(refreshToken));\n    }\n\n    // ========== 短信登录相关 ==========\n\n    @PostMapping(\"/sms-login\")\n    @Operation(summary = \"使用手机 + 验证码登录\")\n    public CommonResult<AppAuthLoginRespVO> smsLogin(@RequestBody @Valid AppAuthSmsLoginReqVO reqVO) {\n        return success(authService.smsLogin(reqVO));\n    }\n\n    @PostMapping(\"/send-sms-code\")\n    @Operation(summary = \"发送手机验证码\")\n    public CommonResult<Boolean> sendSmsCode(@RequestBody @Valid AppAuthSmsSendReqVO reqVO) {\n        authService.sendSmsCode(getLoginUserId(), reqVO);\n        return success(true);\n    }\n\n//    @PostMapping(\"/reset-password\")\n//    @Operation(summary = \"重置密码\", description = \"用户忘记密码时使用\")\n//    @PreAuthenticated\n//    public CommonResult<Boolean> resetPassword(@RequestBody @Valid AppAuthResetPasswordReqVO reqVO) {\n//        authService.resetPassword(reqVO);\n//        return success(true);\n//    }\n\n    @PostMapping(\"/update-password\")\n    @Operation(summary = \"修改用户密码\", description = \"用户修改密码时使用\")\n    @PreAuthenticated\n    public CommonResult<Boolean> updatePassword(@RequestBody @Valid AppAuthUpdatePasswordReqVO reqVO) {\n        authService.updatePassword(getLoginUserId(), reqVO);\n        return success(true);\n    }\n\n    // ========== 社交登录相关 ==========\n\n//    @GetMapping(\"/social-auth-redirect\")\n//    @Operation(summary = \"社交授权的跳转\")\n//    @Parameters({\n//            @Parameter(name = \"type\", description = \"社交类型\", required = true),\n//            @Parameter(name = \"redirectUri\", description = \"回调路径\")\n//    })\n//    public CommonResult<String> socialAuthRedirect(@RequestParam(\"type\") Integer type,\n//                                                   @RequestParam(\"redirectUri\") String redirectUri) {\n//        return CommonResult.success(authService.getSocialAuthorizeUrl(type, redirectUri));\n//    }\n//\n//    @PostMapping(\"/social-login\")\n//    @Operation(summary = \"社交快捷登录，使用 code 授权码\", description = \"适合未登录的用户，但是社交账号已绑定用户\")\n//    public CommonResult<AppAuthLoginRespVO> socialLogin(@RequestBody @Valid AppAuthSocialLoginReqVO reqVO) {\n//        return success(authService.socialLogin(reqVO));\n//    }\n\n    @PostMapping(\"/weixin-mini-app-login\")\n    @Operation(summary = \"微信小程序的一键登录\")\n    public CommonResult<AppAuthLoginRespVO> weixinMiniAppLogin(@RequestBody @Valid AppAuthWeixinMiniAppLoginReqVO reqVO) {\n        return success(authService.weixinMiniAppLogin(reqVO));\n    }\n\n    @PostMapping(\"/auth-session\")\n    @Operation(summary = \"微信小程序登录\")\n    public CommonResult<AppAuthLoginRespVO> weixinMiniAppLogin2(@RequestBody @Valid AppWeixinMiniLoginVO loginVO) {\n        AppAuthLoginRespVO appAuthLoginRespVO = authService.weixinMiniAppLogin2(loginVO);\n        appAuthLoginRespVO.setIsActive(isActive);\n        return success(appAuthLoginRespVO);\n    }\n\n    @PostMapping(\"/auth-miniapp-login\")\n    @Operation(summary = \"微信小程序登录\")\n    public CommonResult<AppAuthLoginRespVO> weixinMiniAppLogin3(@RequestBody @Valid AppWxMiniLoginVO appWxMiniLoginVO) {\n        return success(authService.weixinMiniAppLogin3(appWxMiniLoginVO.getEncryptedData(),appWxMiniLoginVO.getIv()\n                ,appWxMiniLoginVO.getOpenid()));\n    }\n\n    @GetMapping(\"/auth-wechat-login\")\n    @Operation(summary = \"社交授权的跳转\")\n    @Parameters({\n            @Parameter(name = \"code\", description = \"code码\", required = true)\n    })\n    public CommonResult<AppAuthLoginRespVO> wechatAuth(@RequestParam(\"code\") String code) {\n        System.out.println(\"code:\"+code);\n        return CommonResult.success(authService.wechatAuth(code));\n    }\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/app/auth/vo/AppAuthCheckCodeReqVO.java",
    "content": "package co.yixiang.yshop.module.member.controller.app.auth.vo;\n\nimport co.yixiang.yshop.framework.common.validation.InEnum;\nimport co.yixiang.yshop.framework.common.validation.Mobile;\nimport co.yixiang.yshop.module.system.enums.sms.SmsSceneEnum;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotBlank;\nimport jakarta.validation.constraints.NotNull;\nimport jakarta.validation.constraints.Pattern;\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport org.hibernate.validator.constraints.Length;\n\n\n// TODO yshop：code review 相关逻辑\n@Schema(description = \"用户 APP - 校验验证码 Request VO\")\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\n@Builder\npublic class AppAuthCheckCodeReqVO {\n\n    @Schema(description = \"手机号\", example = \"15601691234\")\n    @NotBlank(message = \"手机号不能为空\")\n    @Mobile\n    private String mobile;\n\n    @Schema(description = \"手机验证码\", required = true, example = \"1024\")\n    @NotBlank(message = \"手机验证码不能为空\")\n    @Length(min = 4, max = 6, message = \"手机验证码长度为 4-6 位\")\n    @Pattern(regexp = \"^[0-9]+$\", message = \"手机验证码必须都是数字\")\n    private String code;\n\n    @Schema(description = \"发送场景,对应 SmsSceneEnum 枚举\", example = \"1\")\n    @NotNull(message = \"发送场景不能为空\")\n    @InEnum(SmsSceneEnum.class)\n    private Integer scene;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/app/auth/vo/AppAuthLoginReqVO.java",
    "content": "package co.yixiang.yshop.module.member.controller.app.auth.vo;\n\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.validation.InEnum;\nimport co.yixiang.yshop.framework.common.validation.Mobile;\nimport co.yixiang.yshop.module.system.enums.social.SocialTypeEnum;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.AssertTrue;\nimport jakarta.validation.constraints.NotEmpty;\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport org.hibernate.validator.constraints.Length;\n\n\n@Schema(description = \"用户 APP - 手机 + 密码登录 Request VO,如果登录并绑定社交用户，需要传递 social 开头的参数\")\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\n@Builder\npublic class AppAuthLoginReqVO {\n\n    @Schema(description = \"手机号\", required = true, example = \"15601691300\")\n    @NotEmpty(message = \"手机号不能为空\")\n    @Mobile\n    private String mobile;\n\n    @Schema(description = \"密码\", required = true, example = \"buzhidao\")\n    @NotEmpty(message = \"密码不能为空\")\n    @Length(min = 4, max = 16, message = \"密码长度为 4-16 位\")\n    private String password;\n\n    // ========== 绑定社交登录时，需要传递如下参数 ==========\n\n    @Schema(description = \"社交平台的类型,参见 SysUserSocialTypeEnum 枚举值\", required = true, example = \"10\")\n    @InEnum(SocialTypeEnum.class)\n    private Integer socialType;\n\n    @Schema(description = \"授权码\", required = true, example = \"1024\")\n    private String socialCode;\n\n    @Schema(description = \"state\", required = true, example = \"9b2ffbc1-7425-4155-9894-9d5c08541d62\")\n    private String socialState;\n\n    @AssertTrue(message = \"授权码不能为空\")\n    public boolean isSocialCodeValid() {\n        return socialType == null || StrUtil.isNotEmpty(socialCode);\n    }\n\n    @AssertTrue(message = \"授权 state 不能为空\")\n    public boolean isSocialState() {\n        return socialType == null || StrUtil.isNotEmpty(socialState);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/app/auth/vo/AppAuthLoginRespVO.java",
    "content": "package co.yixiang.yshop.module.member.controller.app.auth.vo;\n\nimport co.yixiang.yshop.module.member.controller.app.user.vo.AppUserQueryVo;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\nimport java.time.LocalDateTime;\n\n@Schema(description = \"用户 APP - 登录 Response VO\")\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\n@Builder\npublic class AppAuthLoginRespVO {\n\n    @Schema(description = \"用户编号\", required = true, example = \"1024\")\n    private Long userId;\n\n    @Schema(description = \"访问令牌\", required = true, example = \"happy\")\n    private String accessToken;\n\n    @Schema(description = \"刷新令牌\", required = true, example = \"nice\")\n    private String refreshToken;\n\n    @Schema(description = \"过期时间\", required = true)\n    private LocalDateTime expiresTime;\n\n    private String openId;\n\n    private AppUserQueryVo userInfo;\n\n    private Boolean isActive;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/app/auth/vo/AppAuthResetPasswordReqVO.java",
    "content": "package co.yixiang.yshop.module.member.controller.app.auth.vo;\n\nimport co.yixiang.yshop.framework.common.validation.Mobile;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotBlank;\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.Pattern;\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport org.hibernate.validator.constraints.Length;\n\n\n// TODO yshop：code review 相关逻辑\n@Schema(description = \"用户 APP - 重置密码 Request VO\")\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\n@Builder\npublic class AppAuthResetPasswordReqVO {\n\n    @Schema(description = \"新密码\", required = true, example = \"buzhidao\")\n    @NotEmpty(message = \"新密码不能为空\")\n    @Length(min = 4, max = 16, message = \"密码长度为 4-16 位\")\n    private String password;\n\n    @Schema(description = \"手机验证码\", required = true, example = \"1024\")\n    @NotEmpty(message = \"手机验证码不能为空\")\n    @Length(min = 4, max = 6, message = \"手机验证码长度为 4-6 位\")\n    @Pattern(regexp = \"^[0-9]+$\", message = \"手机验证码必须都是数字\")\n    private String code;\n\n    @Schema(description = \"手机号\",required = true,example = \"15878962356\")\n    @NotBlank(message = \"手机号不能为空\")\n    @Mobile\n    private String mobile;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/app/auth/vo/AppAuthSmsLoginReqVO.java",
    "content": "package co.yixiang.yshop.module.member.controller.app.auth.vo;\n\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.validation.InEnum;\nimport co.yixiang.yshop.framework.common.validation.Mobile;\nimport co.yixiang.yshop.module.system.enums.social.SocialTypeEnum;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.AssertTrue;\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.Pattern;\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport org.hibernate.validator.constraints.Length;\n\n\n@Schema(description = \"用户 APP - 手机 + 验证码登录 Request VO,如果登录并绑定社交用户，需要传递 social 开头的参数\")\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\n@Builder\npublic class AppAuthSmsLoginReqVO {\n\n    @Schema(description = \"手机号\", required = true, example = \"15601691300\")\n    @NotEmpty(message = \"手机号不能为空\")\n    @Mobile\n    private String mobile;\n\n    @Schema(description = \"手机验证码\", required = true, example = \"1024\")\n    @NotEmpty(message = \"手机验证码不能为空\")\n    @Length(min = 4, max = 6, message = \"手机验证码长度为 4-6 位\")\n    @Pattern(regexp = \"^[0-9]+$\", message = \"手机验证码必须都是数字\")\n    private String code;\n\n    @Schema(description = \"设备来源\", required = true, example = \"h5\")\n    private String from;\n\n    private String openid;\n\n    // ========== 绑定社交登录时，需要传递如下参数 ==========\n\n    @Schema(description = \"社交平台的类型,参见 SysUserSocialTypeEnum 枚举值\", required = true, example = \"10\")\n    @InEnum(SocialTypeEnum.class)\n    private Integer socialType;\n\n    @Schema(description = \"授权码\", required = true, example = \"1024\")\n    private String socialCode;\n\n    @Schema(description = \"state\", required = true, example = \"9b2ffbc1-7425-4155-9894-9d5c08541d62\")\n    private String socialState;\n\n    @AssertTrue(message = \"授权码不能为空\")\n    public boolean isSocialCodeValid() {\n        return socialType == null || StrUtil.isNotEmpty(socialCode);\n    }\n\n    @AssertTrue(message = \"授权 state 不能为空\")\n    public boolean isSocialState() {\n        return socialType == null || StrUtil.isNotEmpty(socialState);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/app/auth/vo/AppAuthSmsSendReqVO.java",
    "content": "package co.yixiang.yshop.module.member.controller.app.auth.vo;\n\nimport co.yixiang.yshop.framework.common.validation.InEnum;\nimport co.yixiang.yshop.framework.common.validation.Mobile;\nimport co.yixiang.yshop.module.system.enums.sms.SmsSceneEnum;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotNull;\nimport lombok.Data;\nimport lombok.experimental.Accessors;\n\n\n@Schema(description = \"用户 APP - 发送手机验证码 Request VO\")\n@Data\n@Accessors(chain = true)\npublic class AppAuthSmsSendReqVO {\n\n    @Schema(description = \"手机号\", example = \"15601691234\")\n    @Mobile\n    private String mobile;\n\n    @Schema(description = \"发送场景,对应 SmsSceneEnum 枚举\", example = \"1\")\n    @NotNull(message = \"发送场景不能为空\")\n    @InEnum(SmsSceneEnum.class)\n    private Integer scene;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/app/auth/vo/AppAuthSocialLoginReqVO.java",
    "content": "package co.yixiang.yshop.module.member.controller.app.auth.vo;\n\nimport co.yixiang.yshop.framework.common.validation.InEnum;\nimport co.yixiang.yshop.module.system.enums.social.SocialTypeEnum;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\n\n@Schema(description = \"用户 APP - 社交快捷登录 Request VO，使用 code 授权码\")\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\n@Builder\npublic class AppAuthSocialLoginReqVO {\n\n    @Schema(description = \"社交平台的类型,参见 SysUserSocialTypeEnum 枚举值\", required = true, example = \"10\")\n    @InEnum(SocialTypeEnum.class)\n    @NotNull(message = \"社交平台的类型不能为空\")\n    private Integer type;\n\n    @Schema(description = \"授权码\", required = true, example = \"1024\")\n    @NotEmpty(message = \"授权码不能为空\")\n    private String code;\n\n    @Schema(description = \"state\", required = true, example = \"9b2ffbc1-7425-4155-9894-9d5c08541d62\")\n    @NotEmpty(message = \"state 不能为空\")\n    private String state;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/app/auth/vo/AppAuthUpdatePasswordReqVO.java",
    "content": "package co.yixiang.yshop.module.member.controller.app.auth.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotBlank;\nimport jakarta.validation.constraints.NotEmpty;\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport org.hibernate.validator.constraints.Length;\n\n\n// TODO yshop：code review 相关逻辑\n@Schema(description = \"用户 APP - 修改密码 Request VO\")\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\n@Builder\npublic class AppAuthUpdatePasswordReqVO {\n\n    @Schema(description = \"用户旧密码\", required = true, example = \"123456\")\n    @NotBlank(message = \"旧密码不能为空\")\n    @Length(min = 4, max = 16, message = \"密码长度为 4-16 位\")\n    private String oldPassword;\n\n    @Schema(description = \"新密码\", required = true, example = \"buzhidao\")\n    @NotEmpty(message = \"新密码不能为空\")\n    @Length(min = 4, max = 16, message = \"密码长度为 4-16 位\")\n    private String password;\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/app/auth/vo/AppAuthWeixinMiniAppLoginReqVO.java",
    "content": "package co.yixiang.yshop.module.member.controller.app.auth.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotEmpty;\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\n\n@Schema(description = \"用户 APP - 微信小程序手机登录 Request VO\")\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\n@Builder\npublic class AppAuthWeixinMiniAppLoginReqVO {\n\n    @Schema(description = \"手机 code,小程序通过 wx.getPhoneNumber 方法获得\", required = true, example = \"hello\")\n    @NotEmpty(message = \"手机 code 不能为空\")\n    private String phoneCode;\n\n    @Schema(description = \"登录 code,小程序通过 wx.login 方法获得\", required = true, example = \"word\")\n    @NotEmpty(message = \"登录 code 不能为空\")\n    private String loginCode;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/app/auth/vo/AppWeixinMiniLoginVO.java",
    "content": "package co.yixiang.yshop.module.member.controller.app.auth.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotEmpty;\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\n\n@Schema(description = \"用户 APP - 微信小程序手机登录 VO\")\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\n@Builder\npublic class AppWeixinMiniLoginVO {\n\n\n    @Schema(description = \"登录 code,小程序通过 wx.login 方法获得\", required = true, example = \"word\")\n    @NotEmpty(message = \"登录 code 不能为空\")\n    private String code;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/app/auth/vo/AppWxMiniLoginVO.java",
    "content": "package co.yixiang.yshop.module.member.controller.app.auth.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotEmpty;\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\n\n@Schema(description = \"用户 APP - 微信小程序手机登录 VO\")\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\n@Builder\npublic class AppWxMiniLoginVO {\n\n\n    @Schema(description = \"登录encryptedData\", required = true, example = \"word\")\n    @NotEmpty(message = \"登录encryptedData不能为空\")\n    private String encryptedData;\n\n    @Schema(description = \"登录iv\", required = true, example = \"word\")\n    @NotEmpty(message = \"登录iv不能为空\")\n    private String iv;\n\n    @Schema(description = \"登录openid\", required = true, example = \"word\")\n    @NotEmpty(message = \"登录openid不能为空\")\n    private String openid;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/app/social/AppSocialUserController.java",
    "content": "package co.yixiang.yshop.module.member.controller.app.social;\n\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.module.member.controller.app.social.vo.AppSocialUserBindReqVO;\nimport co.yixiang.yshop.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO;\nimport co.yixiang.yshop.module.member.convert.social.SocialUserConvert;\nimport co.yixiang.yshop.module.system.api.social.SocialUserApi;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport io.swagger.v3.oas.annotations.Operation;\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Valid;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\n\nimport static co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;\n\n@Tag(name = \"用户 App - 社交用户\")\n@RestController\n@RequestMapping(\"/system/social-user\")\n@Validated\npublic class AppSocialUserController {\n\n    @Resource\n    private SocialUserApi socialUserApi;\n\n    @PostMapping(\"/bind\")\n    @Operation(summary = \"社交绑定，使用 code 授权码\")\n    public CommonResult<Boolean> socialBind(@RequestBody @Valid AppSocialUserBindReqVO reqVO) {\n        socialUserApi.bindSocialUser(SocialUserConvert.INSTANCE.convert(getLoginUserId(), UserTypeEnum.MEMBER.getValue(), reqVO));\n        return CommonResult.success(true);\n    }\n\n    @DeleteMapping(\"/unbind\")\n    @Operation(summary = \"取消社交绑定\")\n    public CommonResult<Boolean> socialUnbind(@RequestBody AppSocialUserUnbindReqVO reqVO) {\n        socialUserApi.unbindSocialUser(SocialUserConvert.INSTANCE.convert(getLoginUserId(), UserTypeEnum.MEMBER.getValue(), reqVO));\n        return CommonResult.success(true);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/app/social/vo/AppSocialUserBindReqVO.java",
    "content": "package co.yixiang.yshop.module.member.controller.app.social.vo;\n\nimport co.yixiang.yshop.framework.common.validation.InEnum;\nimport co.yixiang.yshop.module.system.enums.social.SocialTypeEnum;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\n\n@Schema(description = \"用户 APP - 社交绑定 Request VO，使用 code 授权码\")\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\n@Builder\npublic class AppSocialUserBindReqVO {\n\n    @Schema(description = \"社交平台的类型,参见 SysUserSocialTypeEnum 枚举值\", required = true, example = \"10\")\n    @InEnum(SocialTypeEnum.class)\n    @NotNull(message = \"社交平台的类型不能为空\")\n    private Integer type;\n\n    @Schema(description = \"授权码\", required = true, example = \"1024\")\n    @NotEmpty(message = \"授权码不能为空\")\n    private String code;\n\n    @Schema(description = \"state\", required = true, example = \"9b2ffbc1-7425-4155-9894-9d5c08541d62\")\n    @NotEmpty(message = \"state 不能为空\")\n    private String state;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/app/social/vo/AppSocialUserUnbindReqVO.java",
    "content": "package co.yixiang.yshop.module.member.controller.app.social.vo;\n\nimport co.yixiang.yshop.framework.common.validation.InEnum;\nimport co.yixiang.yshop.module.system.enums.social.SocialTypeEnum;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\n\n@Schema(description = \"用户 APP - 取消社交绑定 Request VO\")\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\n@Builder\npublic class AppSocialUserUnbindReqVO {\n\n    @Schema(description = \"社交平台的类型,参见 SysUserSocialTypeEnum 枚举值\", required = true, example = \"10\")\n    @InEnum(SocialTypeEnum.class)\n    @NotNull(message = \"社交平台的类型不能为空\")\n    private Integer type;\n\n    @Schema(description = \"社交用户的 openid\", required = true, example = \"IPRmJ0wvBptiPIlGEZiPewGwiEiE\")\n    @NotEmpty(message = \"社交用户的 openid 不能为空\")\n    private String openid;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/app/user/AppUserController.java",
    "content": "package co.yixiang.yshop.module.member.controller.app.user;\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.security.core.annotations.PreAuthenticated;\nimport co.yixiang.yshop.module.member.controller.app.user.vo.*;\nimport co.yixiang.yshop.module.member.convert.user.UserConvert;\nimport co.yixiang.yshop.module.member.dal.dataobject.user.MemberUserDO;\nimport co.yixiang.yshop.module.member.service.user.MemberUserService;\nimport co.yixiang.yshop.module.member.service.userbill.UserBillService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.Parameters;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Valid;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\nimport org.springframework.web.multipart.MultipartFile;\n\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\nimport static co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;\nimport static co.yixiang.yshop.module.infra.enums.ErrorCodeConstants.FILE_IS_EMPTY;\n\n@Tag(name = \"用户 APP - 用户个人中心\")\n@RestController\n@RequestMapping(\"/member/user\")\n@Validated\n@Slf4j\npublic class AppUserController {\n\n    @Resource\n    private MemberUserService userService;\n    @Resource\n    private UserBillService userBillService;\n\n    @PostMapping(\"/update-nickname\")\n    @Operation(summary = \"修改用户昵称或者生日\")\n    @Parameters({\n            @Parameter(name = \"nickname\", description = \"用户昵称\",\n                    required = true, example = \"wang\"),\n            @Parameter(name = \"birthday\", description = \"生日\",\n                    required = true, example = \"2023-11-12\"),\n            @Parameter(name = \"gender\", description = \"性别\",\n                    required = true, example = \"o\"),\n            @Parameter(name = \"avatar\", description = \"用户头像\",\n                    required = true, example = \"\"),\n            @Parameter(name = \"mobile\", description = \"手机\",\n                    required = true, example = \"\")\n    })\n    @PreAuthenticated\n    public CommonResult<Boolean> updateUserNickname(@RequestBody @Valid AppUserNickVO appUserNickVO) {\n        userService.updateUserNickname(getLoginUserId(), appUserNickVO.getNickname(),appUserNickVO.getBirthday(),\n                appUserNickVO.getGender(),appUserNickVO.getAvatar(),appUserNickVO.getMobile());\n        return success(true);\n    }\n\n    @PostMapping(\"/update-avatar\")\n    @Operation(summary = \"修改用户头像\")\n    @PreAuthenticated\n    public CommonResult<String> updateUserAvatar(@RequestParam(\"avatarFile\") MultipartFile file) throws Exception {\n        if (file.isEmpty()) {\n            throw exception(FILE_IS_EMPTY);\n        }\n        String avatar = userService.updateUserAvatar(getLoginUserId(), file.getInputStream());\n        return success(avatar);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得基本信息\")\n    @PreAuthenticated\n    public CommonResult<AppUserInfoRespVO> getUserInfo() {\n        MemberUserDO user = userService.getUser(getLoginUserId());\n        return success(UserConvert.INSTANCE.convert(user));\n    }\n\n    @PostMapping(\"/update-mobile\")\n    @Operation(summary = \"修改用户手机\")\n    @PreAuthenticated\n    public CommonResult<Boolean> updateMobile(@RequestBody @Valid AppUserUpdateMobileReqVO reqVO) {\n        userService.updateUserMobile(getLoginUserId(), reqVO);\n        return success(true);\n    }\n\n    @GetMapping(\"/get-info\")\n    @Operation(summary = \"获得基本信息\")\n    @PreAuthenticated\n    public CommonResult<AppUserQueryVo> getUserBaseInfo() {\n\n        return success(userService.getAppUser(getLoginUserId()));\n    }\n\n    @GetMapping(\"/getBill\")\n    @Operation(summary = \"获取用户账单\")\n    @PreAuthenticated\n    @Parameters({\n            @Parameter(name = \"cate\", description = \"状态,0余额 1-积分\",\n                    required = true, example = \"1\"),\n            @Parameter(name = \"type\", description = \"状态,0全部  1消费 2充值 3退款\",\n                    required = true, example = \"1\"),\n            @Parameter(name = \"page\", description = \"页码,默认为1\",\n                    required = true, example = \"1\"),\n            @Parameter(name = \"pagesize\", description = \"页大小,默认为10\",\n                    required = true, example = \"10      \")\n    })\n    public CommonResult<List<AppUserBillVO>> getUserBill(@RequestParam(value = \"cate\", defaultValue = \"0\") int cate,\n                                                         @RequestParam(value = \"type\", defaultValue = \"0\") int type,\n                                                         @RequestParam(value = \"page\", defaultValue = \"1\") int page,\n                                                         @RequestParam(value = \"pagesize\", defaultValue = \"10\") int pagesize) {\n        return success(userBillService.getBillList(getLoginUserId(),cate,type,page,pagesize));\n    }\n\n\n\n\n}\n\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/app/user/vo/AppUserBillVO.java",
    "content": "package co.yixiang.yshop.module.member.controller.app.user.vo;\n\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport lombok.*;\n\nimport java.math.BigDecimal;\nimport java.time.LocalDateTime;\n\n/**\n * 用户账单 vo\n *\n * @author yshop\n */\n@Data\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class AppUserBillVO {\n\n    /**\n     * 用户账单id\n     */\n    private Long id;\n    /**\n     * 用户uid\n     */\n    private Long uid;\n    /**\n     * 关联id\n     */\n    private String linkId;\n    /**\n     * 0 = 支出 1 = 获得\n     */\n    private Integer pm;\n    /**\n     * 账单标题\n     */\n    private String title;\n    /**\n     * 明细种类\n     */\n    private String category;\n    /**\n     * 明细类型\n     */\n    private String type;\n    /**\n     * 明细数字\n     */\n    private BigDecimal number;\n    /**\n     * 剩余\n     */\n    private BigDecimal balance;\n    /**\n     * 备注\n     */\n    private String mark;\n    /**\n     * 0 = 带确定 1 = 有效 -1 = 无效\n     */\n    private Integer status;\n\n    private LocalDateTime createTime;\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/app/user/vo/AppUserInfoRespVO.java",
    "content": "package co.yixiang.yshop.module.member.controller.app.user.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\n@Schema(description = \"用户 APP - 用户个人信息 Response VO\")\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\npublic class AppUserInfoRespVO {\n\n    @Schema(description = \"用户id\", required = true, example = \"yshop\")\n    private Long id;\n\n    @Schema(description = \"用户昵称\", required = true, example = \"yshop\")\n    private String nickname;\n\n    @Schema(description = \"用户头像\", required = true, example = \"/infra/file/get/35a12e57-4297-4faa-bf7d-7ed2f211c952\")\n    private String avatar;\n\n    @Schema(description = \"用户手机号\", required = true, example = \"15601691300\")\n    private String mobile;\n\n    @Schema(description = \"生日\", required = true, example = \"2023-10-11\")\n    private String birthday;\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/app/user/vo/AppUserNickVO.java",
    "content": "package co.yixiang.yshop.module.member.controller.app.user.vo;\n\nimport jakarta.validation.constraints.NotBlank;\nimport lombok.*;\n\nimport java.math.BigDecimal;\nimport java.time.LocalDateTime;\n\n/**\n * 用户信息 vo\n *\n * @author yshop\n */\n@Data\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class AppUserNickVO {\n\n    /**\n     * 用户nickname\n     */\n    @NotBlank(message = \"用户昵称不能为空\")\n    private String nickname;\n    /**\n     * 用户birthday\n     */\n    @NotBlank(message = \"生日不能为空\")\n    private String birthday;\n    /**\n     * avatar\n     */\n    @NotBlank(message = \"用户头像不能为空\")\n    private String avatar;\n    /**\n     * gender\n     */\n    private Integer gender;\n    /**\n     * mobile\n     */\n    private String mobile;\n\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/app/user/vo/AppUserOrderCountVo.java",
    "content": "package co.yixiang.yshop.module.member.controller.app.user.vo;\r\n\r\nimport io.swagger.v3.oas.annotations.media.Schema;\r\nimport lombok.*;\r\n\r\nimport java.io.Serializable;\r\n\r\n/**\r\n * @ClassName OrderCountDTO\r\n * @Author hupeng <610796224@qq.com>\r\n * @Date 2023/6/18\r\n **/\r\n@Getter\r\n@Setter\r\n@NoArgsConstructor\r\n@AllArgsConstructor\r\n@Builder\r\n@Schema(description = \"用户 APP - 用户 OrderCountDTO\")\r\npublic class AppUserOrderCountVo implements Serializable {\r\n\r\n    /**订单支付没有退款 数量*/\r\n    @Schema(description = \"订单支付没有退款数量\", required = true)\r\n    private Long orderCount;\r\n\r\n    /**订单支付没有退款 支付总金额*/\r\n    @Schema(description = \"订单支付没有退款支付总金额\", required = true)\r\n    private Double sumPrice;\r\n\r\n    /**订单待支付 数量*/\r\n    @Schema(description = \"订单待支付数量\", required = true)\r\n    private Long unpaidCount;\r\n\r\n    /**订单待发货数量*/\r\n    @Schema(description = \"订单待发货数量\", required = true)\r\n    private Long unshippedCount;\r\n\r\n    /**订单待收货数量*/\r\n    @Schema(description = \"订单待收货数量\", required = true)\r\n    private Long receivedCount;\r\n\r\n    /**订单待评价数量*/\r\n    @Schema(description = \"订单待评价数量\", required = true)\r\n    private Long evaluatedCount;\r\n\r\n    /**订单已完成数量*/\r\n    @Schema(description = \"订单已完成数量\", required = true)\r\n    private Long completeCount;\r\n\r\n    /**订单退款数量*/\r\n    @Schema(description = \"订单退款数量\", required = true)\r\n    private Long refundCount;\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/app/user/vo/AppUserQueryVo.java",
    "content": "package co.yixiang.yshop.module.member.controller.app.user.vo;\n\n\nimport com.fasterxml.jackson.annotation.JsonFormat;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport java.io.Serializable;\nimport java.math.BigDecimal;\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n/**\n * <p>\n * 用户表 查询结果对象\n * </p>\n *\n * @author hupeng\n * @date 2023-6-18\n */\n@Data\n@Schema(description = \"用户 APP - 用户表查 Response VO\")\npublic class AppUserQueryVo implements Serializable {\n    private static final long serialVersionUID = 1L;\n\n    @Schema(description = \"用户id\", required = true)\n    private Long id;\n\n    @Schema(description = \"用户账户(跟accout一样)\\\"\", required = true)\n    private String username;\n\n    @Schema(description = \"用户账号\", required = true)\n    private String account;\n\n    @Schema(description = \"优惠券数量\", required = true)\n    private Long couponCount = 0L;\n\n    @Schema(description = \"订单详情数据\", required = true)\n    private AppUserOrderCountVo orderStatusNum;\n\n\n    private Integer statu;\n\n    @Schema(description = \"总的签到天数\", required = true)\n    private Long sumSignDay;\n\n    @Schema(description = \"当天是否签到\", required = true)\n    private Integer isDaySign;\n\n    @Schema(description = \"昨天是否签到\", required = true)\n    private Integer isYesterDaySign;\n\n    @Schema(description = \"核销权限\", required = true)\n    private Integer checkStatus;\n\n    @Schema(description = \"用户昵称\", required = true)\n    private String nickname;\n\n    @Schema(description = \"用户头像\", required = true)\n    private String avatar;\n\n    @Schema(description = \"手机号码\", required = true)\n    private String mobile;\n\n    @Schema(description = \"用户余额\", required = true)\n    private BigDecimal nowMoney;\n\n    @Schema(description = \"用户花费的金额\", required = true)\n    private BigDecimal sumMoney;\n\n    @Schema(description = \"佣金金额\", required = true)\n    private BigDecimal brokeragePrice;\n\n    @Schema(description = \"用户剩余积分\", required = true)\n    private BigDecimal integral;\n\n    @Schema(description = \"连续签到天数\", required = true)\n    private Integer signNum;\n\n    @Schema(description = \"推广元id\", required = true)\n    private Long spreadUid;\n\n    @Schema(description = \"用户类型\", required = true)\n    private String userType;\n\n    @Schema(description = \"是否为推广员\", required = true)\n    private Integer isPromoter;\n\n    @Schema(description = \"用户购买次数\", required = true)\n    private Integer payCount;\n\n    @Schema(description = \"下级人数\", required = true)\n    private Integer spreadCount;\n\n    @Schema(description = \"详细地址\", required = true)\n    private String addres;\n\n    private Integer gender;\n\n    @JsonFormat(pattern = \"yyyy-MM-dd HH:mm:ss\")\n    private LocalDateTime createTime;\n\n    private String birthday;\n\n\n    @Schema(description = \"用户登陆类型，h5,wechat,routine\", required = true)\n    private String loginType;\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/app/user/vo/AppUserRechargeVO.java",
    "content": "package co.yixiang.yshop.module.member.controller.app.user.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotBlank;\nimport lombok.*;\n\n\n\n/**\n * 用户充值 vo\n *\n * @author yshop\n */\n@Data\npublic class AppUserRechargeVO {\n\n    @Schema(description = \"充值ID\", required = true)\n    @NotBlank(message = \"请选择要充值的面值\")\n    private String rechargeId;\n\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/app/user/vo/AppUserUpdateMobileReqVO.java",
    "content": "package co.yixiang.yshop.module.member.controller.app.user.vo;\n\nimport co.yixiang.yshop.framework.common.validation.Mobile;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotBlank;\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.Pattern;\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport org.hibernate.validator.constraints.Length;\n\n\n@Schema(description = \"用户 APP - 修改手机 Request VO\")\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\n@Builder\npublic class AppUserUpdateMobileReqVO {\n\n    @Schema(description = \"手机验证码\", required = true, example = \"1024\")\n    @NotEmpty(message = \"手机验证码不能为空\")\n    @Length(min = 4, max = 6, message = \"手机验证码长度为 4-6 位\")\n    @Pattern(regexp = \"^[0-9]+$\", message = \"手机验证码必须都是数字\")\n    private String code;\n\n    @Schema(description = \"手机号\",required = true,example = \"15823654487\")\n    @NotBlank(message = \"手机号不能为空\")\n    @Length(min = 8, max = 11, message = \"手机号码长度为 8-11 位\")\n    @Mobile\n    private String mobile;\n\n    @Schema(description = \"原手机验证码\", required = true, example = \"1024\")\n    @NotEmpty(message = \"原手机验证码不能为空\")\n    @Length(min = 4, max = 6, message = \"手机验证码长度为 4-6 位\")\n    @Pattern(regexp = \"^[0-9]+$\", message = \"手机验证码必须都是数字\")\n    private String oldCode;\n\n    // TODO @yshop：oldMobile 应该不用传递\n\n    @Schema(description = \"原手机号\",required = true,example = \"15823654487\")\n    @NotBlank(message = \"手机号不能为空\")\n    @Length(min = 8, max = 11, message = \"手机号码长度为 8-11 位\")\n    @Mobile\n    private String oldMobile;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/controller/app/weixin/AppWxMpController.java",
    "content": "package co.yixiang.yshop.module.member.controller.app.weixin;\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport io.swagger.v3.oas.annotations.Operation;\nimport jakarta.annotation.Resource;\nimport lombok.extern.slf4j.Slf4j;\nimport me.chanjar.weixin.common.bean.WxJsapiSignature;\nimport me.chanjar.weixin.common.error.WxErrorException;\nimport me.chanjar.weixin.mp.api.WxMpService;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"微信公众号\")\n@RestController\n@RequestMapping(\"/member/wx-mp\")\n@Validated\n@Slf4j\npublic class AppWxMpController {\n\n    @Resource\n    private WxMpService mpService;\n\n    @GetMapping(\"/create-jsapi-signature\")\n    @Operation(summary = \"创建微信 JS SDK 初始化所需的签名\",\n        description = \"参考 https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html 文档\")\n    public CommonResult<WxJsapiSignature> createJsapiSignature(@RequestParam(\"url\") String url) throws WxErrorException {\n        return success(mpService.createJsapiSignature(url));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/convert/auth/AuthConvert.java",
    "content": "package co.yixiang.yshop.module.member.convert.auth;\n\nimport co.yixiang.yshop.module.member.controller.app.auth.vo.*;\nimport co.yixiang.yshop.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO;\nimport co.yixiang.yshop.module.system.api.oauth2.dto.OAuth2AccessTokenRespDTO;\nimport co.yixiang.yshop.module.system.api.sms.dto.code.SmsCodeSendReqDTO;\nimport co.yixiang.yshop.module.system.api.sms.dto.code.SmsCodeUseReqDTO;\nimport co.yixiang.yshop.module.system.api.social.dto.SocialUserBindReqDTO;\nimport co.yixiang.yshop.module.system.api.social.dto.SocialUserUnbindReqDTO;\nimport co.yixiang.yshop.module.system.enums.sms.SmsSceneEnum;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.factory.Mappers;\n\n@Mapper\npublic interface AuthConvert {\n\n    AuthConvert INSTANCE = Mappers.getMapper(AuthConvert.class);\n\n    SocialUserBindReqDTO convert(Long userId, Integer userType, AppAuthSocialLoginReqVO reqVO);\n    SocialUserUnbindReqDTO convert(Long userId, Integer userType, AppSocialUserUnbindReqVO reqVO);\n\n    SmsCodeSendReqDTO convert(AppAuthSmsSendReqVO reqVO);\n    SmsCodeUseReqDTO convert(AppAuthResetPasswordReqVO reqVO, SmsSceneEnum scene, String usedIp);\n    SmsCodeUseReqDTO convert(AppAuthSmsLoginReqVO reqVO, Integer scene, String usedIp);\n\n    AppAuthLoginRespVO convert(OAuth2AccessTokenRespDTO bean);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/convert/package-info.java",
    "content": "/**\n * 提供 POJO 类的实体转换\n *\n * 目前使用 MapStruct 框架\n */\npackage co.yixiang.yshop.module.member.convert;\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/convert/social/SocialUserConvert.java",
    "content": "package co.yixiang.yshop.module.member.convert.social;\n\nimport co.yixiang.yshop.module.member.controller.app.social.vo.AppSocialUserBindReqVO;\nimport co.yixiang.yshop.module.member.controller.app.social.vo.AppSocialUserUnbindReqVO;\nimport co.yixiang.yshop.module.system.api.social.dto.SocialUserBindReqDTO;\nimport co.yixiang.yshop.module.system.api.social.dto.SocialUserUnbindReqDTO;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.factory.Mappers;\n\n@Mapper\npublic interface SocialUserConvert {\n\n    SocialUserConvert INSTANCE = Mappers.getMapper(SocialUserConvert.class);\n\n    SocialUserBindReqDTO convert(Long userId, Integer userType, AppSocialUserBindReqVO reqVO);\n\n    SocialUserUnbindReqDTO convert(Long userId, Integer userType, AppSocialUserUnbindReqVO reqVO);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/convert/user/UserConvert.java",
    "content": "package co.yixiang.yshop.module.member.convert.user;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.member.api.user.dto.MemberUserRespDTO;\nimport co.yixiang.yshop.module.member.controller.admin.user.vo.UserCreateReqVO;\nimport co.yixiang.yshop.module.member.controller.admin.user.vo.UserRespVO;\nimport co.yixiang.yshop.module.member.controller.admin.user.vo.UserUpdateReqVO;\nimport co.yixiang.yshop.module.member.controller.app.user.vo.AppUserInfoRespVO;\nimport co.yixiang.yshop.module.member.controller.app.user.vo.AppUserQueryVo;\nimport co.yixiang.yshop.module.member.dal.dataobject.user.MemberUserDO;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.factory.Mappers;\n\nimport java.util.List;\n\n@Mapper\npublic interface UserConvert {\n\n    UserConvert INSTANCE = Mappers.getMapper(UserConvert.class);\n\n    AppUserInfoRespVO convert(MemberUserDO bean);\n\n    MemberUserRespDTO convert2(MemberUserDO bean);\n\n    AppUserQueryVo convert3(MemberUserDO bean);\n\n    UserRespVO convert4(MemberUserDO bean);\n\n    List<MemberUserRespDTO> convertList2(List<MemberUserDO> list);\n\n    MemberUserDO convert(UserCreateReqVO bean);\n\n    MemberUserDO convert(UserUpdateReqVO bean);\n\n   UserRespVO convert(MemberUserDO bean,Boolean bool);\n\n    List<UserRespVO> convertList(List<MemberUserDO> list);\n\n    PageResult<UserRespVO> convertPage(PageResult<MemberUserDO> page);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/convert/useraddress/AreaConvert.java",
    "content": "package co.yixiang.yshop.module.member.convert.useraddress;\n\nimport co.yixiang.yshop.framework.ip.core.Area;\nimport co.yixiang.yshop.module.member.controller.app.address.vo.AreaNodeRespVO;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.factory.Mappers;\n\nimport java.util.List;\n\n@Mapper\npublic interface AreaConvert {\n\n    AreaConvert INSTANCE = Mappers.getMapper(AreaConvert.class);\n\n    List<AreaNodeRespVO> convertList(List<Area> list);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/convert/useraddress/UserAddressConvert.java",
    "content": "package co.yixiang.yshop.module.member.convert.useraddress;\n\nimport java.util.*;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\nimport co.yixiang.yshop.module.member.controller.app.address.vo.AppUserAddressQueryVo;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.factory.Mappers;\nimport co.yixiang.yshop.module.member.controller.admin.useraddress.vo.*;\nimport co.yixiang.yshop.module.member.dal.dataobject.useraddress.UserAddressDO;\n\n/**\n * 用户地址 Convert\n *\n * @author yshop\n */\n@Mapper\npublic interface UserAddressConvert {\n\n    UserAddressConvert INSTANCE = Mappers.getMapper(UserAddressConvert.class);\n\n    UserAddressDO convert(UserAddressCreateReqVO bean);\n\n    UserAddressDO convert(UserAddressUpdateReqVO bean);\n\n    UserAddressRespVO convert(UserAddressDO bean);\n\n    List<UserAddressRespVO> convertList(List<UserAddressDO> list);\n\n    List<AppUserAddressQueryVo> convertList02(List<UserAddressDO> list);\n\n    PageResult<UserAddressRespVO> convertPage(PageResult<UserAddressDO> page);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/convert/userbill/UserBillConvert.java",
    "content": "package co.yixiang.yshop.module.member.convert.userbill;\n\nimport java.util.*;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\nimport co.yixiang.yshop.module.member.controller.app.user.vo.AppUserBillVO;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.factory.Mappers;\nimport co.yixiang.yshop.module.member.controller.admin.userbill.vo.*;\nimport co.yixiang.yshop.module.member.dal.dataobject.userbill.UserBillDO;\n\n/**\n * 用户账单 Convert\n *\n * @author yshop\n */\n@Mapper\npublic interface UserBillConvert {\n\n    UserBillConvert INSTANCE = Mappers.getMapper(UserBillConvert.class);\n\n    UserBillDO convert(UserBillCreateReqVO bean);\n\n    UserBillDO convert(UserBillUpdateReqVO bean);\n\n    UserBillRespVO convert(UserBillDO bean);\n\n    List<UserBillRespVO> convertList(List<UserBillDO> list);\n\n    List<AppUserBillVO> convertList02(List<UserBillDO> list);\n\n    PageResult<UserBillRespVO> convertPage(PageResult<UserBillDO> page);\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/dal/dataobject/user/MemberUserDO.java",
    "content": "package co.yixiang.yshop.module.member.dal.dataobject.user;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.tenant.core.db.TenantBaseDO;\n\nimport co.yixiang.yshop.module.member.api.user.dto.WechatUserDto;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler;\nimport lombok.*;\nimport org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;\n\nimport java.math.BigDecimal;\nimport java.time.LocalDateTime;\n\n/**\n * 会员用户 DO\n *\n * uk_mobile 索引：基于 {@link #mobile} 字段\n *\n * @author yshop\n */\n@TableName(value = \"yshop_user\", autoResultMap = true)\n@KeySequence(\"member_user_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class MemberUserDO extends TenantBaseDO {\n\n    /**\n     * 用户ID\n     */\n    @TableId\n    private Long id;\n    /**\n     * 用户昵称\n     */\n    private String nickname;\n    /**\n     * 用户头像\n     */\n    private String avatar;\n    /**\n     * 帐号状态\n     *\n     * 枚举 {@link CommonStatusEnum}\n     */\n    private Integer status;\n\n    /**\n     * 手机\n     */\n    private String mobile;\n    /**\n     * 加密后的密码\n     *\n     * 因为目前使用 {@link BCryptPasswordEncoder} 加密器，所以无需自己处理 salt 盐\n     */\n    private String password;\n    /**\n     * 注册 IP\n     */\n    private String registerIp;\n    /**\n     * 最后登录IP\n     */\n    private String loginIp;\n    /**\n     * 最后登录时间\n     */\n    private LocalDateTime loginDate;\n\n    /**\n     * 用户账户(跟accout一样)\n     */\n    private String username;\n\n    /**\n     * 真实姓名\n     */\n    private String realName;\n    /**\n     * 生日\n     */\n    private String birthday;\n    /**\n     * 身份证号码\n     */\n    private String cardId;\n    /**\n     * 用户备注\n     */\n    private String mark;\n    /**\n     * 合伙人id\n     */\n    private Integer partnerId;\n    /**\n     * 用户分组id\n     */\n    private Integer groupId;\n\n    /**\n     * 添加ip\n     */\n    private String addIp;\n    /**\n     * 最后一次登录ip\n     */\n    private String lastIp;\n    /**\n     * 用户余额\n     */\n    private BigDecimal nowMoney;\n    /**\n     * 佣金金额\n     */\n    private BigDecimal brokeragePrice;\n    /**\n     * 用户剩余积分\n     */\n    private BigDecimal integral;\n    /**\n     * 连续签到天数\n     */\n    private Integer signNum;\n\n    /**\n     * 等级\n     */\n    private Integer level;\n    /**\n     * 推广元id\n     */\n    private Long spreadUid;\n    /**\n     * 推广员关联时间\n     */\n    private LocalDateTime spreadTime;\n    /**\n     * 用户类型\n     */\n    private String userType;\n    /**\n     * 是否为推广员\n     */\n    private Integer isPromoter;\n    /**\n     * 用户购买次数\n     */\n    private Integer payCount;\n    /**\n     * 下级人数\n     */\n    private Integer spreadCount;\n    /**\n     * 详细地址\n     */\n    private String addres;\n    /**\n     * 管理员编号\n     */\n    private Integer adminid;\n    /**\n     * 用户登陆类型，h5,wechat,routine\n     */\n    private String loginType;\n\n    //公众号openid\n    private String openid;\n\n    //小程序openid\n    private String routineOpenid;\n    //性别\n    private Integer gender;\n\n\n    /** 微信用户json信息 */\n    @TableField(typeHandler = FastjsonTypeHandler.class)\n    @Deprecated\n    private  WechatUserDto wxProfile;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/dal/dataobject/useraddress/UserAddressDO.java",
    "content": "package co.yixiang.yshop.module.member.dal.dataobject.useraddress;\n\nimport lombok.*;\nimport java.util.*;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\nimport com.baomidou.mybatisplus.annotation.*;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\n\n/**\n * 用户地址 DO\n *\n * @author yshop\n */\n@TableName(\"yshop_user_address\")\n@KeySequence(\"yshop_user_address_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class UserAddressDO extends BaseDO {\n\n    /**\n     * 用户地址id\n     */\n    @TableId\n    private Long id;\n    /**\n     * 用户id\n     */\n    private Long uid;\n    /**\n     * 收货人姓名\n     */\n    private String realName;\n    /**\n     * 收货人电话\n     */\n    private String phone;\n    /**\n     * 收货人所在省\n     */\n    private String province;\n    /**\n     * 收货人所在市\n     */\n    private String city;\n    /**\n     * 城市id\n     */\n    private Integer cityId;\n    /**\n     * 收货人所在区\n     */\n    private String district;\n    /**\n     * 收货人详细地址\n     */\n    private String detail;\n    /**\n     * 邮编\n     */\n    private String postCode;\n    /**\n     * 经度\n     */\n    private String longitude;\n    /**\n     * 纬度\n     */\n    private String latitude;\n    /**\n     * 是否默认\n     */\n    private Integer isDefault;\n\n    /**\n     * 地址\n     */\n    private String address;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/dal/dataobject/userbill/UserBillDO.java",
    "content": "package co.yixiang.yshop.module.member.dal.dataobject.userbill;\n\nimport lombok.*;\nimport java.util.*;\nimport java.math.BigDecimal;\nimport java.math.BigDecimal;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\nimport com.baomidou.mybatisplus.annotation.*;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\n\n/**\n * 用户账单 DO\n *\n * @author yshop\n */\n@TableName(\"yshop_user_bill\")\n@KeySequence(\"yshop_user_bill_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class UserBillDO extends BaseDO {\n\n    /**\n     * 用户账单id\n     */\n    @TableId\n    private Long id;\n    /**\n     * 用户uid\n     */\n    private Long uid;\n    /**\n     * 关联id\n     */\n    private String linkId;\n    /**\n     * 0 = 支出 1 = 获得\n     */\n    private Integer pm;\n    /**\n     * 账单标题\n     */\n    private String title;\n    /**\n     * 明细种类\n     */\n    private String category;\n    /**\n     * 明细类型\n     */\n    private String type;\n    /**\n     * 明细数字\n     */\n    private BigDecimal number;\n    /**\n     * 剩余\n     */\n    private BigDecimal balance;\n    /**\n     * 备注\n     */\n    private String mark;\n    /**\n     * 0 = 带确定 1 = 有效 -1 = 无效\n     */\n    private Integer status;\n\n    @TableField(exist = false)\n    private BigDecimal sumAll;\n\n    private String extendField;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/dal/mysql/user/MemberUserMapper.java",
    "content": "package co.yixiang.yshop.module.member.dal.mysql.user;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.member.controller.admin.user.vo.UserExportReqVO;\nimport co.yixiang.yshop.module.member.controller.admin.user.vo.UserPageReqVO;\nimport co.yixiang.yshop.module.member.dal.dataobject.user.MemberUserDO;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.apache.ibatis.annotations.Param;\nimport org.apache.ibatis.annotations.Update;\n\nimport java.math.BigDecimal;\nimport java.util.List;\n\n/**\n * 会员 User Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface MemberUserMapper extends BaseMapperX<MemberUserDO> {\n\n    default MemberUserDO selectByMobile(String mobile) {\n        return selectOne(MemberUserDO::getMobile, mobile);\n    }\n\n    default List<MemberUserDO> selectListByNicknameLike(String nickname) {\n        return selectList(new LambdaQueryWrapperX<MemberUserDO>()\n                .likeIfPresent(MemberUserDO::getNickname, nickname));\n    }\n\n    default PageResult<MemberUserDO> selectPage(UserPageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<MemberUserDO>()\n                .likeIfPresent(MemberUserDO::getLoginType, reqVO.getLoginType())\n                .likeIfPresent(MemberUserDO::getUsername, reqVO.getUsername())\n                .likeIfPresent(MemberUserDO::getRealName, reqVO.getRealName())\n                .likeIfPresent(MemberUserDO::getNickname, reqVO.getNickname())\n                .likeIfPresent(MemberUserDO::getMobile, reqVO.getPhone())\n                .betweenIfPresent(MemberUserDO::getCreateTime, reqVO.getCreateTime())\n                .orderByDesc(MemberUserDO::getId));\n    }\n\n    default List<MemberUserDO> selectList(UserExportReqVO reqVO) {\n        return selectList(new LambdaQueryWrapperX<MemberUserDO>()\n                .likeIfPresent(MemberUserDO::getUsername, reqVO.getUsername())\n                .likeIfPresent(MemberUserDO::getRealName, reqVO.getRealName())\n                .likeIfPresent(MemberUserDO::getNickname, reqVO.getNickname())\n                .eqIfPresent(MemberUserDO::getMobile, reqVO.getPhone())\n                .betweenIfPresent(MemberUserDO::getCreateTime, reqVO.getCreateTime())\n                .orderByDesc(MemberUserDO::getId));\n    }\n\n    @Update(\"update yshop_user set now_money=now_money-#{payPrice}\" +\n            \" where id=#{uid}\")\n    int decPrice(@Param(\"payPrice\") BigDecimal payPrice, @Param(\"uid\") Long uid);\n\n    @Update(\"update yshop_user set integral=integral-#{score}\" +\n            \" where id=#{uid}\")\n    int decScore(@Param(\"score\") Integer score, @Param(\"uid\") Long uid);\n\n    @Update(\"update yshop_user set pay_count=pay_count+1\" +\n            \" where id=#{uid}\")\n    int incPayCount(@Param(\"uid\") Long uid);\n\n    @Update(\"update yshop_user set now_money=now_money+#{price}\" +\n            \" where id=#{uid}\")\n    int incMoney(@Param(\"uid\") Long uid,@Param(\"price\") BigDecimal price);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/dal/mysql/useraddress/UserAddressMapper.java",
    "content": "package co.yixiang.yshop.module.member.dal.mysql.useraddress;\n\nimport java.util.*;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.module.member.dal.dataobject.useraddress.UserAddressDO;\nimport org.apache.ibatis.annotations.Mapper;\nimport co.yixiang.yshop.module.member.controller.admin.useraddress.vo.*;\n\n/**\n * 用户地址 Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface UserAddressMapper extends BaseMapperX<UserAddressDO> {\n\n    default PageResult<UserAddressDO> selectPage(UserAddressPageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<UserAddressDO>()\n                .eqIfPresent(UserAddressDO::getUid, reqVO.getUid())\n                .likeIfPresent(UserAddressDO::getRealName, reqVO.getRealName())\n                .eqIfPresent(UserAddressDO::getPhone, reqVO.getPhone())\n                .eqIfPresent(UserAddressDO::getProvince, reqVO.getProvince())\n                .eqIfPresent(UserAddressDO::getCity, reqVO.getCity())\n                .eqIfPresent(UserAddressDO::getCityId, reqVO.getCityId())\n                .eqIfPresent(UserAddressDO::getDistrict, reqVO.getDistrict())\n                .eqIfPresent(UserAddressDO::getDetail, reqVO.getDetail())\n                .eqIfPresent(UserAddressDO::getPostCode, reqVO.getPostCode())\n                .eqIfPresent(UserAddressDO::getLongitude, reqVO.getLongitude())\n                .eqIfPresent(UserAddressDO::getLatitude, reqVO.getLatitude())\n                .eqIfPresent(UserAddressDO::getIsDefault, reqVO.getIsDefault())\n                .betweenIfPresent(UserAddressDO::getCreateTime, reqVO.getCreateTime())\n                .orderByDesc(UserAddressDO::getId));\n    }\n\n    default List<UserAddressDO> selectList(UserAddressExportReqVO reqVO) {\n        return selectList(new LambdaQueryWrapperX<UserAddressDO>()\n                .eqIfPresent(UserAddressDO::getUid, reqVO.getUid())\n                .likeIfPresent(UserAddressDO::getRealName, reqVO.getRealName())\n                .eqIfPresent(UserAddressDO::getPhone, reqVO.getPhone())\n                .eqIfPresent(UserAddressDO::getProvince, reqVO.getProvince())\n                .eqIfPresent(UserAddressDO::getCity, reqVO.getCity())\n                .eqIfPresent(UserAddressDO::getCityId, reqVO.getCityId())\n                .eqIfPresent(UserAddressDO::getDistrict, reqVO.getDistrict())\n                .eqIfPresent(UserAddressDO::getDetail, reqVO.getDetail())\n                .eqIfPresent(UserAddressDO::getPostCode, reqVO.getPostCode())\n                .eqIfPresent(UserAddressDO::getLongitude, reqVO.getLongitude())\n                .eqIfPresent(UserAddressDO::getLatitude, reqVO.getLatitude())\n                .eqIfPresent(UserAddressDO::getIsDefault, reqVO.getIsDefault())\n                .betweenIfPresent(UserAddressDO::getCreateTime, reqVO.getCreateTime())\n                .orderByDesc(UserAddressDO::getId));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/dal/mysql/userbill/UserBillMapper.java",
    "content": "package co.yixiang.yshop.module.member.dal.mysql.userbill;\n\nimport java.util.*;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.module.member.dal.dataobject.userbill.UserBillDO;\nimport org.apache.ibatis.annotations.Mapper;\nimport co.yixiang.yshop.module.member.controller.admin.userbill.vo.*;\n\n/**\n * 用户账单 Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface UserBillMapper extends BaseMapperX<UserBillDO> {\n\n    default PageResult<UserBillDO> selectPage(UserBillPageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<UserBillDO>()\n                .eqIfPresent(UserBillDO::getUid, reqVO.getUid())\n                .eqIfPresent(UserBillDO::getLinkId, reqVO.getLinkId())\n                .eqIfPresent(UserBillDO::getPm, reqVO.getPm())\n                .eqIfPresent(UserBillDO::getTitle, reqVO.getTitle())\n                .eqIfPresent(UserBillDO::getCategory, reqVO.getCategory())\n                .eqIfPresent(UserBillDO::getType, reqVO.getType())\n                .eqIfPresent(UserBillDO::getNumber, reqVO.getNumber())\n                .eqIfPresent(UserBillDO::getBalance, reqVO.getBalance())\n                .eqIfPresent(UserBillDO::getMark, reqVO.getMark())\n                .betweenIfPresent(UserBillDO::getCreateTime, reqVO.getCreateTime())\n                .eqIfPresent(UserBillDO::getStatus, reqVO.getStatus())\n                .orderByDesc(UserBillDO::getId));\n    }\n\n    default List<UserBillDO> selectList(UserBillExportReqVO reqVO) {\n        return selectList(new LambdaQueryWrapperX<UserBillDO>()\n                .eqIfPresent(UserBillDO::getUid, reqVO.getUid())\n                .eqIfPresent(UserBillDO::getLinkId, reqVO.getLinkId())\n                .eqIfPresent(UserBillDO::getPm, reqVO.getPm())\n                .eqIfPresent(UserBillDO::getTitle, reqVO.getTitle())\n                .eqIfPresent(UserBillDO::getCategory, reqVO.getCategory())\n                .eqIfPresent(UserBillDO::getType, reqVO.getType())\n                .eqIfPresent(UserBillDO::getNumber, reqVO.getNumber())\n                .eqIfPresent(UserBillDO::getBalance, reqVO.getBalance())\n                .eqIfPresent(UserBillDO::getMark, reqVO.getMark())\n                .betweenIfPresent(UserBillDO::getCreateTime, reqVO.getCreateTime())\n                .eqIfPresent(UserBillDO::getStatus, reqVO.getStatus())\n                .orderByDesc(UserBillDO::getId));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/dal/redis/RedisKeyConstants.java",
    "content": "package co.yixiang.yshop.module.member.dal.redis;\n\nimport co.yixiang.yshop.framework.redis.core.RedisKeyDefine;\n\nimport static co.yixiang.yshop.framework.redis.core.RedisKeyDefine.KeyTypeEnum.STRING;\n\n/**\n * System Redis Key 枚举类\n *\n * @author yshop\n */\npublic interface RedisKeyConstants {\n\n\n\n\n    RedisKeyDefine YSHOP_MINI_LOGIN_CACHE_KEY = new RedisKeyDefine(\"小程序登录session\",\n            \"yshop_mini_login_cache:%s\", // 参数为访问uid+key\n            STRING, String.class, RedisKeyDefine.TimeoutTypeEnum.FOREVER);\n\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/dal/redis/order/MiniRedisDAO.java",
    "content": "package co.yixiang.yshop.module.member.dal.redis.order;\n\nimport co.yixiang.yshop.framework.common.util.json.JsonUtils;\nimport jakarta.annotation.Resource;\nimport org.springframework.data.redis.core.StringRedisTemplate;\nimport org.springframework.stereotype.Repository;\n\n\nimport static co.yixiang.yshop.module.member.dal.redis.RedisKeyConstants.YSHOP_MINI_LOGIN_CACHE_KEY;\n\n\n/**\n *\n * @author yshop\n */\n@Repository\npublic class MiniRedisDAO {\n\n    @Resource\n    private StringRedisTemplate stringRedisTemplate;\n\n    public String get(String key) {\n        String redisKey = formatKey(key);\n        return stringRedisTemplate.opsForValue().get(redisKey);\n    }\n\n    public String set(String str,String key) {\n        String redisKey = formatKey(key);\n        stringRedisTemplate.opsForValue().set(redisKey, str);\n        return key;\n    }\n\n    public void delete(String key) {\n        String redisKey = formatKey(key);\n        stringRedisTemplate.delete(redisKey);\n    }\n\n\n\n    private static String formatKey(String key) {\n        return String.format(YSHOP_MINI_LOGIN_CACHE_KEY.getKeyTemplate(), key);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/framework/package-info.java",
    "content": "/**\n * 属于 member 模块的 framework 封装\n *\n * @author yshop\n */\npackage co.yixiang.yshop.module.member.framework;\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/framework/web/config/MemberWebConfiguration.java",
    "content": "package co.yixiang.yshop.module.member.framework.web.config;\n\nimport co.yixiang.yshop.framework.swagger.config.YshopSwaggerAutoConfiguration;\nimport org.springdoc.core.models.GroupedOpenApi;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n/**\n * member 模块的 web 组件的 Configuration\n *\n * @author yshop\n */\n@Configuration(proxyBeanMethods = false)\npublic class MemberWebConfiguration {\n\n    /**\n     * member 模块的 API 分组\n     */\n    @Bean\n    public GroupedOpenApi memberGroupedOpenApi() {\n        return YshopSwaggerAutoConfiguration.buildGroupedOpenApi(\"member\");\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/framework/web/package-info.java",
    "content": "/**\n * member 模块的 web 配置\n */\npackage co.yixiang.yshop.module.member.framework.web;\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/service/auth/MemberAuthService.java",
    "content": "package co.yixiang.yshop.module.member.service.auth;\n\nimport co.yixiang.yshop.module.member.controller.app.auth.vo.*;\nimport jakarta.validation.Valid;\nimport org.springframework.web.bind.annotation.RequestParam;\n\n\n/**\n * 会员的认证 Service 接口\n *\n * 提供用户的账号密码登录、token 的校验等认证相关的功能\n *\n * @author yshop\n */\npublic interface MemberAuthService {\n\n    /**\n     * 手机 + 密码登录\n     *\n     * @param reqVO 登录信息\n     * @return 登录结果\n     */\n    AppAuthLoginRespVO login(@Valid AppAuthLoginReqVO reqVO);\n\n    /**\n     * 基于 token 退出登录\n     *\n     * @param token token\n     */\n    void logout(String token);\n\n    /**\n     * 手机 + 验证码登陆\n     *\n     * @param reqVO 登陆信息\n     * @return 登录结果\n     */\n    AppAuthLoginRespVO smsLogin(@Valid AppAuthSmsLoginReqVO reqVO);\n\n    /**\n     * 社交登录，使用 code 授权码\n     *\n     * @param reqVO 登录信息\n     * @return 登录结果\n     */\n    //AppAuthLoginRespVO socialLogin(@Valid AppAuthSocialLoginReqVO reqVO);\n\n    /**\n     * 微信小程序的一键登录\n     *\n     * @param reqVO 登录信息\n     * @return 登录结果\n     */\n    AppAuthLoginRespVO weixinMiniAppLogin(AppAuthWeixinMiniAppLoginReqVO reqVO);\n\n    /**\n     * 微信小程序的一键登录\n     *\n     * @param loginVO 登录信息\n     * @return 登录结果\n     */\n    AppAuthLoginRespVO weixinMiniAppLogin2(AppWeixinMiniLoginVO loginVO);\n\n    /**\n     * 微信小程序的一键登录\n     *\n     * @param encryptedData encryptedData\n     * @param encryptedData encryptedData\n     * @param encryptedData encryptedData\n     * @return 登录结果\n     */\n    AppAuthLoginRespVO weixinMiniAppLogin3(String encryptedData, String iv, String openid);\n\n    /**\n     * 微信公众号的一键登录\n     *\n     * @param code code\n     * @return 登录结果\n     */\n    AppAuthLoginRespVO wechatAuth(String code);\n\n\n    /**\n     * 获得社交认证 URL\n     *\n     * @param type 社交平台类型\n     * @param redirectUri 跳转地址\n     * @return 认证 URL\n     */\n    //String getSocialAuthorizeUrl(Integer type, String redirectUri);\n\n    /**\n     * 修改用户密码\n     * @param userId 用户id\n     * @param userReqVO 用户请求实体类\n     */\n    void updatePassword(Long userId, AppAuthUpdatePasswordReqVO userReqVO);\n\n    /**\n     * 忘记密码\n     * @param userReqVO 用户请求实体类\n     */\n    //void resetPassword(AppAuthResetPasswordReqVO userReqVO);\n\n    /**\n     * 给用户发送短信验证码\n     *\n     * @param userId 用户编号\n     * @param reqVO 发送信息\n     */\n    void sendSmsCode(Long userId, AppAuthSmsSendReqVO reqVO);\n\n    /**\n     * 刷新访问令牌\n     *\n     * @param refreshToken 刷新令牌\n     * @return 登录结果\n     */\n    AppAuthLoginRespVO refreshToken(String refreshToken);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/service/auth/MemberAuthServiceImpl.java",
    "content": "package co.yixiang.yshop.module.member.service.auth;\n\nimport cn.binarywang.wx.miniapp.api.WxMaService;\nimport cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;\nimport cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;\nimport cn.hutool.core.lang.Assert;\nimport cn.hutool.core.util.ObjectUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.common.util.monitor.TracerUtils;\nimport co.yixiang.yshop.framework.common.util.servlet.ServletUtils;\nimport co.yixiang.yshop.module.member.controller.app.auth.vo.*;\nimport co.yixiang.yshop.module.member.convert.auth.AuthConvert;\nimport co.yixiang.yshop.module.member.convert.user.UserConvert;\nimport co.yixiang.yshop.module.member.dal.dataobject.user.MemberUserDO;\nimport co.yixiang.yshop.module.member.dal.mysql.user.MemberUserMapper;\nimport co.yixiang.yshop.module.member.dal.redis.order.MiniRedisDAO;\nimport co.yixiang.yshop.module.member.enums.LoginTypeEnum;\nimport co.yixiang.yshop.module.member.service.user.MemberUserService;\nimport co.yixiang.yshop.module.system.api.logger.LoginLogApi;\nimport co.yixiang.yshop.module.system.api.logger.dto.LoginLogCreateReqDTO;\nimport co.yixiang.yshop.module.system.api.oauth2.OAuth2TokenApi;\nimport co.yixiang.yshop.module.system.api.oauth2.dto.OAuth2AccessTokenCreateReqDTO;\nimport co.yixiang.yshop.module.system.api.oauth2.dto.OAuth2AccessTokenRespDTO;\nimport co.yixiang.yshop.module.system.api.sms.SmsCodeApi;\nimport co.yixiang.yshop.module.system.api.social.SocialUserApi;\nimport co.yixiang.yshop.module.system.api.social.dto.SocialUserBindReqDTO;\nimport co.yixiang.yshop.module.system.enums.logger.LoginLogTypeEnum;\nimport co.yixiang.yshop.module.system.enums.logger.LoginResultEnum;\nimport co.yixiang.yshop.module.system.enums.oauth2.OAuth2ClientConstants;\nimport co.yixiang.yshop.module.system.enums.sms.SmsSceneEnum;\nimport co.yixiang.yshop.module.system.enums.social.SocialTypeEnum;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport com.google.common.annotations.VisibleForTesting;\nimport jakarta.annotation.Resource;\nimport lombok.extern.slf4j.Slf4j;\nimport me.chanjar.weixin.common.bean.WxOAuth2UserInfo;\nimport me.chanjar.weixin.common.bean.oauth2.WxOAuth2AccessToken;\nimport me.chanjar.weixin.common.error.WxErrorException;\nimport me.chanjar.weixin.mp.api.WxMpService;\nimport org.springframework.security.crypto.password.PasswordEncoder;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport java.util.Objects;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.framework.common.util.servlet.ServletUtils.getClientIP;\nimport static co.yixiang.yshop.module.member.enums.ErrorCodeConstants.*;\n\n/**\n * 会员的认证 Service 接口\n *\n * @author yshop\n */\n@Service\n@Slf4j\npublic class MemberAuthServiceImpl implements MemberAuthService {\n\n    @Resource\n    private MemberUserService userService;\n    @Resource\n    private SmsCodeApi smsCodeApi;\n    @Resource\n    private LoginLogApi loginLogApi;\n    @Resource\n    private SocialUserApi socialUserApi;\n    @Resource\n    private OAuth2TokenApi oauth2TokenApi;\n\n    @Resource\n    private WxMaService wxMaService;\n    @Resource\n    private WxMpService mpService;\n\n    @Resource\n    private PasswordEncoder passwordEncoder;\n    @Resource\n    private MemberUserMapper userMapper;\n    @Resource\n    private MiniRedisDAO miniRedisDAO;\n\n    @Override\n    public AppAuthLoginRespVO login(AppAuthLoginReqVO reqVO) {\n        // 使用手机 + 密码，进行登录。\n        MemberUserDO user = login0(reqVO.getMobile(), reqVO.getPassword());\n\n        // 如果 socialType 非空，说明需要绑定社交用户\n        if (reqVO.getSocialType() != null) {\n            socialUserApi.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(),\n                    reqVO.getSocialType(), reqVO.getSocialCode(), reqVO.getSocialState()));\n        }\n\n        // 创建 Token 令牌，记录登录日志\n        return createTokenAfterLoginSuccess(user, reqVO.getMobile(), LoginLogTypeEnum.LOGIN_MOBILE);\n    }\n\n    @Override\n    @Transactional\n    public AppAuthLoginRespVO smsLogin(AppAuthSmsLoginReqVO reqVO) {\n        // 校验验证码\n        String userIp = getClientIP();\n        smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsSceneEnum.MEMBER_LOGIN.getScene(), userIp));\n        MemberUserDO user = null;\n\n        if(StrUtil.isNotBlank(reqVO.getOpenid())){\n            LambdaQueryWrapper<MemberUserDO> lambdaQueryWrapper =  new LambdaQueryWrapper<>();\n            //根据openid查用户是否存在\n            if(LoginTypeEnum.ROUNTINE.getValue().equals(reqVO.getFrom())){\n                lambdaQueryWrapper.eq(MemberUserDO::getRoutineOpenid,reqVO.getOpenid());\n            }else {\n                lambdaQueryWrapper.eq(MemberUserDO::getOpenid,reqVO.getOpenid());\n            }\n\n            user = userMapper.selectOne(lambdaQueryWrapper);\n        }\n\n        if(user == null){\n            // 获得获得注册用户\n            user = userService.createUserIfAbsent(reqVO.getMobile(), userIp,reqVO.getFrom());\n            Assert.notNull(user, \"获取用户失败，结果为空\");\n\n            if(StrUtil.isNotBlank(reqVO.getOpenid())){\n                if(LoginTypeEnum.ROUNTINE.getValue().equals(reqVO.getFrom())){\n                    user.setRoutineOpenid(reqVO.getOpenid());\n                }else {\n                    user.setOpenid(reqVO.getOpenid());\n                }\n\n                userService.updateById(user);\n            }\n        }else {\n            if(StrUtil.isBlank(user.getMobile())){\n                user.setMobile(reqVO.getMobile());\n                userService.updateById(user);\n            }\n        }\n\n        // 如果 socialType 非空，说明需要绑定社交用户\n        if (reqVO.getSocialType() != null) {\n            socialUserApi.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(),\n                    reqVO.getSocialType(), reqVO.getSocialCode(), reqVO.getSocialState()));\n        }\n\n        // 创建 Token 令牌，记录登录日志\n        AppAuthLoginRespVO appAuthLoginRespVO = createTokenAfterLoginSuccess(user, reqVO.getMobile(),\n                LoginLogTypeEnum.LOGIN_SMS);\n\n        appAuthLoginRespVO.setUserInfo(UserConvert.INSTANCE.convert3(user));\n        return appAuthLoginRespVO;\n        // 创建 Token 令牌，记录登录日志\n       // return createTokenAfterLoginSuccess(user, reqVO.getMobile(), LoginLogTypeEnum.LOGIN_SMS);\n    }\n\n   // @Override\n//    public AppAuthLoginRespVO socialLogin(AppAuthSocialLoginReqVO reqVO) {\n//        // 使用 code 授权码，进行登录。然后，获得到绑定的用户编号\n//        Long userId = socialUserApi.getBindUserId(UserTypeEnum.MEMBER.getValue(), reqVO.getType(),\n//                reqVO.getCode(), reqVO.getState());\n//        if (userId == null) {\n//            throw exception(AUTH_THIRD_LOGIN_NOT_BIND);\n//        }\n//\n//        // 自动登录\n//        MemberUserDO user = userService.getUser(userId);\n//        if (user == null) {\n//            throw exception(USER_NOT_EXISTS);\n//        }\n//\n//        // 创建 Token 令牌，记录登录日志\n//        return createTokenAfterLoginSuccess(user, user.getMobile(), LoginLogTypeEnum.LOGIN_SOCIAL);\n//    }\n\n    @Override\n    public AppAuthLoginRespVO weixinMiniAppLogin(AppAuthWeixinMiniAppLoginReqVO reqVO) {\n        // 获得对应的手机号信息\n        WxMaPhoneNumberInfo phoneNumberInfo;\n        try {\n            phoneNumberInfo = wxMaService.getUserService().getNewPhoneNoInfo(reqVO.getPhoneCode());\n        } catch (Exception exception) {\n            throw exception(AUTH_WEIXIN_MINI_APP_PHONE_CODE_ERROR);\n        }\n        // 获得获得注册用户\n        MemberUserDO user = userService.createUserIfAbsent(phoneNumberInfo.getPurePhoneNumber(), getClientIP(),\n                LoginTypeEnum.ROUNTINE.getValue());\n        Assert.notNull(user, \"获取用户失败，结果为空\");\n\n        // 绑定社交用户\n        socialUserApi.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(),\n                SocialTypeEnum.WECHAT_MINI_APP.getType(), reqVO.getLoginCode(), \"\"));\n\n        // 创建 Token 令牌，记录登录日志\n        return createTokenAfterLoginSuccess(user, user.getMobile(), LoginLogTypeEnum.LOGIN_SOCIAL);\n    }\n\n\n    /**\n     * 微信小程序的一键登录\n     *\n     * @param loginVO 登录信息\n     * @return 登录结果\n     */\n    @Override\n    public AppAuthLoginRespVO weixinMiniAppLogin2(AppWeixinMiniLoginVO loginVO){\n        try {\n            WxMaJscode2SessionResult session = wxMaService.getUserService().getSessionInfo(loginVO.getCode());\n            log.info(session.getOpenid());\n            miniRedisDAO.set(session.getSessionKey(),session.getOpenid());\n            //根据openid查用户是否存在\n            MemberUserDO memberUserDO = userMapper.selectOne(new LambdaQueryWrapper<MemberUserDO>()\n                    .eq(MemberUserDO::getRoutineOpenid,session.getOpenid()));\n            AppAuthLoginRespVO appAuthLoginRespVO = new AppAuthLoginRespVO();\n\n            if(memberUserDO != null) {\n                appAuthLoginRespVO = createTokenAfterLoginSuccess(memberUserDO, memberUserDO.getMobile(),\n                        LoginLogTypeEnum.LOGIN_SOCIAL);\n                appAuthLoginRespVO.setUserInfo(UserConvert.INSTANCE.convert3(memberUserDO));\n                //return appAuthLoginRespVO;\n            }\n            appAuthLoginRespVO.setOpenId(session.getOpenid());\n            return appAuthLoginRespVO;\n\n        } catch (WxErrorException e) {\n            log.error(e.getMessage());\n            throw exception(MINI_AUTH_LOGIN_BAD);\n        }\n    }\n\n\n    /**\n     * 微信小程序的一键登录\n     *\n     * @param encryptedData encryptedData\n     * @param encryptedData encryptedData\n     * @param encryptedData encryptedData\n     * @return 登录结果\n     */\n    @Override\n    public AppAuthLoginRespVO weixinMiniAppLogin3(String encryptedData, String iv, String openid){\n        // 解密用户信息\n        String sessionKey = miniRedisDAO.get(openid);\n        if(StrUtil.isBlank(sessionKey)) {\n            throw exception(MINI_AUTH_LOGIN_BAD2);\n        }\n\n        // 解密\n        WxMaPhoneNumberInfo phoneNoInfo = wxMaService.getUserService().getPhoneNoInfo(sessionKey, encryptedData, iv);\n\n        // 用户已经存在\n        MemberUserDO memberUserDO = userMapper.selectByMobile(phoneNoInfo.getPhoneNumber());\n        if (memberUserDO == null) {\n            // 获得获得注册用户\n            memberUserDO = userService.createUserIfAbsent(phoneNoInfo.getPhoneNumber(), getClientIP(),\n                    LoginTypeEnum.ROUNTINE.getValue());\n\n            memberUserDO.setRoutineOpenid(openid);\n\n            memberUserDO.setNickname(\"yshop用户_\" + memberUserDO.getId());\n            userMapper.updateById(memberUserDO);\n\n        }\n        if(StrUtil.isBlank(memberUserDO.getRoutineOpenid())){\n            memberUserDO.setRoutineOpenid(openid);\n            userMapper.updateById(memberUserDO);\n        }\n\n        // 创建 Token 令牌，记录登录日志\n        AppAuthLoginRespVO appAuthLoginRespVO = createTokenAfterLoginSuccess(memberUserDO, memberUserDO.getMobile(),\n                LoginLogTypeEnum.LOGIN_SOCIAL);\n        appAuthLoginRespVO.setOpenId(openid);\n        appAuthLoginRespVO.setUserInfo(UserConvert.INSTANCE.convert3(memberUserDO));\n        return appAuthLoginRespVO;\n    }\n\n    /**\n     * 微信公众号登录\n     * @param code code\n     * @return\n     */\n    @Override\n    public AppAuthLoginRespVO wechatAuth(String code) {\n        WxOAuth2AccessToken wxMpOAuth2AccessToken = null;\n        MemberUserDO memberUserDO = null;\n        try {\n            wxMpOAuth2AccessToken = mpService.getOAuth2Service().getAccessToken(code);\n            WxOAuth2UserInfo wxMpUser = mpService.getOAuth2Service().getUserInfo(wxMpOAuth2AccessToken, null);\n            String openid = wxMpUser.getOpenid();\n            //根据openid查用户是否存在\n            memberUserDO = userMapper.selectOne(new LambdaQueryWrapper<MemberUserDO>()\n                    .eq(MemberUserDO::getOpenid,openid));\n            if (memberUserDO == null) {\n                // 获得获得注册用户\n                memberUserDO = userService.createUserIfAbsent(\"\", getClientIP(),\n                        LoginTypeEnum.WECHAT.getValue());\n                //过滤掉表情\n                String nickname = wxMpUser.getNickname();\n                memberUserDO.setOpenid(openid);\n                memberUserDO.setNickname(nickname);\n                memberUserDO.setAvatar(wxMpUser.getHeadImgUrl());\n                memberUserDO.setUsername(openid);\n                userMapper.updateById(memberUserDO);\n            }\n        }catch (WxErrorException e) {\n            log.error(e.getMessage());\n            throw exception(MINI_AUTH_LOGIN_BAD);\n        }\n\n        // 创建 Token 令牌，记录登录日志\n        AppAuthLoginRespVO appAuthLoginRespVO = createTokenAfterLoginSuccess(memberUserDO, memberUserDO.getNickname(),\n                LoginLogTypeEnum.LOGIN_SOCIAL);\n        appAuthLoginRespVO.setOpenId(memberUserDO.getOpenid());\n        appAuthLoginRespVO.setUserInfo(UserConvert.INSTANCE.convert3(memberUserDO));\n        return appAuthLoginRespVO;\n    }\n\n    private AppAuthLoginRespVO createTokenAfterLoginSuccess(MemberUserDO user, String mobile, LoginLogTypeEnum logType) {\n        // 插入登陆日志\n        createLoginLog(user.getId(), mobile, logType, LoginResultEnum.SUCCESS);\n        // 创建 Token 令牌\n        OAuth2AccessTokenRespDTO accessTokenRespDTO = oauth2TokenApi.createAccessToken(new OAuth2AccessTokenCreateReqDTO()\n                .setUserId(user.getId()).setUserType(getUserType().getValue())\n                .setClientId(OAuth2ClientConstants.CLIENT_ID_DEFAULT));\n        // 构建返回结果\n        return AuthConvert.INSTANCE.convert(accessTokenRespDTO);\n    }\n\n//    @Override\n//    public String getSocialAuthorizeUrl(Integer type, String redirectUri) {\n//        return socialUserApi.getAuthorizeUrl(type, redirectUri);\n//    }\n\n    private MemberUserDO login0(String mobile, String password) {\n        final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_MOBILE;\n        // 校验账号是否存在\n        MemberUserDO user = userService.getUserByMobile(mobile);\n        if (user == null) {\n            createLoginLog(null, mobile, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);\n            throw exception(AUTH_LOGIN_BAD_CREDENTIALS);\n        }\n        if (!userService.isPasswordMatch(password, user.getPassword())) {\n            createLoginLog(user.getId(), mobile, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);\n            throw exception(AUTH_LOGIN_BAD_CREDENTIALS);\n        }\n        // 校验是否禁用\n        if (ObjectUtil.notEqual(user.getStatus(), CommonStatusEnum.ENABLE.getStatus())) {\n            createLoginLog(user.getId(), mobile, logTypeEnum, LoginResultEnum.USER_DISABLED);\n            throw exception(AUTH_LOGIN_USER_DISABLED);\n        }\n        return user;\n    }\n\n    private void createLoginLog(Long userId, String mobile, LoginLogTypeEnum logType, LoginResultEnum loginResult) {\n        // 插入登录日志\n        LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();\n        reqDTO.setLogType(logType.getType());\n        reqDTO.setTraceId(TracerUtils.getTraceId());\n        reqDTO.setUserId(userId);\n        reqDTO.setUserType(getUserType().getValue());\n        reqDTO.setUsername(mobile);\n        reqDTO.setUserAgent(ServletUtils.getUserAgent());\n        reqDTO.setUserIp(getClientIP());\n        reqDTO.setResult(loginResult.getResult());\n        loginLogApi.createLoginLog(reqDTO);\n        // 更新最后登录时间\n        if (userId != null && Objects.equals(LoginResultEnum.SUCCESS.getResult(), loginResult.getResult())) {\n            userService.updateUserLogin(userId, getClientIP());\n        }\n    }\n\n    @Override\n    public void logout(String token) {\n        // 删除访问令牌\n        OAuth2AccessTokenRespDTO accessTokenRespDTO = oauth2TokenApi.removeAccessToken(token);\n        if (accessTokenRespDTO == null) {\n            return;\n        }\n        // 删除成功，则记录登出日志\n        createLogoutLog(accessTokenRespDTO.getUserId());\n    }\n\n    @Override\n    public void updatePassword(Long userId, AppAuthUpdatePasswordReqVO reqVO) {\n        // 检验旧密码\n        MemberUserDO userDO = checkOldPassword(userId, reqVO.getOldPassword());\n\n        // 更新用户密码\n        // TODO yshop：需要重构到用户模块\n        userMapper.updateById(MemberUserDO.builder().id(userDO.getId())\n                .password(passwordEncoder.encode(reqVO.getPassword())).build());\n    }\n\n//    @Override\n//    public void resetPassword(AppAuthResetPasswordReqVO reqVO) {\n//        // 检验用户是否存在\n//        MemberUserDO userDO = checkUserIfExists(reqVO.getMobile());\n//\n//        // 使用验证码\n//        smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsSceneEnum.MEMBER_FORGET_PASSWORD,\n//                getClientIP()));\n//\n//        // 更新密码\n//        userMapper.updateById(MemberUserDO.builder().id(userDO.getId())\n//                .password(passwordEncoder.encode(reqVO.getPassword())).build());\n//    }\n\n    @Override\n    public void sendSmsCode(Long userId, AppAuthSmsSendReqVO reqVO) {\n        // TODO 要根据不同的场景，校验是否有用户\n        smsCodeApi.sendSmsCode(AuthConvert.INSTANCE.convert(reqVO).setCreateIp(getClientIP()));\n    }\n\n    @Override\n    public AppAuthLoginRespVO refreshToken(String refreshToken) {\n        OAuth2AccessTokenRespDTO accessTokenDO = oauth2TokenApi.refreshAccessToken(refreshToken, OAuth2ClientConstants.CLIENT_ID_DEFAULT);\n        return AuthConvert.INSTANCE.convert(accessTokenDO);\n    }\n\n    /**\n     * 校验旧密码\n     *\n     * @param id          用户 id\n     * @param oldPassword 旧密码\n     * @return MemberUserDO 用户实体\n     */\n    @VisibleForTesting\n    public MemberUserDO checkOldPassword(Long id, String oldPassword) {\n        MemberUserDO user = userMapper.selectById(id);\n        if (user == null) {\n            throw exception(USER_NOT_EXISTS);\n        }\n        // 参数：未加密密码，编码后的密码\n        if (!passwordEncoder.matches(oldPassword,user.getPassword())) {\n            throw exception(USER_PASSWORD_FAILED);\n        }\n        return user;\n    }\n\n    public MemberUserDO checkUserIfExists(String mobile) {\n        MemberUserDO user = userMapper.selectByMobile(mobile);\n        if (user == null) {\n            throw exception(USER_NOT_EXISTS);\n        }\n        return user;\n    }\n\n    private void createLogoutLog(Long userId) {\n        LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();\n        reqDTO.setLogType(LoginLogTypeEnum.LOGOUT_SELF.getType());\n        reqDTO.setTraceId(TracerUtils.getTraceId());\n        reqDTO.setUserId(userId);\n        reqDTO.setUserType(getUserType().getValue());\n        reqDTO.setUsername(getMobile(userId));\n        reqDTO.setUserAgent(ServletUtils.getUserAgent());\n        reqDTO.setUserIp(getClientIP());\n        reqDTO.setResult(LoginResultEnum.SUCCESS.getResult());\n        loginLogApi.createLoginLog(reqDTO);\n    }\n\n    private String getMobile(Long userId) {\n        if (userId == null) {\n            return null;\n        }\n        MemberUserDO user = userService.getUser(userId);\n        return user != null ? user.getMobile() : null;\n    }\n\n    private UserTypeEnum getUserType() {\n        return UserTypeEnum.MEMBER;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/service/user/MemberUserService.java",
    "content": "package co.yixiang.yshop.module.member.service.user;\n\nimport co.yixiang.yshop.framework.common.validation.Mobile;\nimport co.yixiang.yshop.module.member.controller.app.user.vo.AppUserQueryVo;\nimport co.yixiang.yshop.module.member.controller.app.user.vo.AppUserUpdateMobileReqVO;\nimport co.yixiang.yshop.module.member.dal.dataobject.user.MemberUserDO;\nimport com.baomidou.mybatisplus.extension.service.IService;\nimport org.springframework.web.bind.annotation.RequestParam;\n\nimport java.io.InputStream;\nimport java.math.BigDecimal;\nimport java.util.Collection;\nimport java.util.List;\n\n/**\n * 会员用户 Service 接口\n *\n * @author yshop\n */\npublic interface MemberUserService extends IService<MemberUserDO> {\n\n\n    /**\n     * 通过手机查询用户\n     *\n     * @param mobile 手机\n     * @return 用户对象\n     */\n    MemberUserDO getUserByMobile(String mobile);\n\n    /**\n     * 基于用户昵称，模糊匹配用户列表\n     *\n     * @param nickname 用户昵称，模糊匹配\n     * @return 用户信息的列表\n     */\n    List<MemberUserDO> getUserListByNickname(String nickname);\n\n    /**\n     * 基于手机号创建用户。\n     * 如果用户已经存在，则直接进行返回\n     *\n     * @param mobile 手机号\n     * @param registerIp 注册 IP\n     * @return 用户对象\n     */\n    MemberUserDO createUserIfAbsent(@Mobile String mobile, String registerIp,String from);\n\n    /**\n     * 更新用户的最后登陆信息\n     *\n     * @param id 用户编号\n     * @param loginIp 登陆 IP\n     */\n    void updateUserLogin(Long id, String loginIp);\n\n    /**\n     * 通过用户 ID 查询用户\n     *\n     * @param id 用户ID\n     * @return 用户对象信息\n     */\n    MemberUserDO getUser(Long id);\n\n    /**\n     * 通过用户 ID 查询用户\n     *\n     * @param id 用户ID\n     * @return 用户对象信息\n     */\n    AppUserQueryVo getAppUser(Long id);\n\n\n    /**\n     * 减去用户余额\n     * @param uid uid\n     * @param payPrice 金额\n     */\n    void decPrice(Long uid, BigDecimal payPrice);\n\n    /**\n     * 减去用户积分\n     * @param uid uid\n     * @param score 积分\n     */\n    void decScore(Long uid, Integer score);\n\n\n    /**\n     * 增加购买次数\n     * @param uid uid\n     */\n    void incPayCount(Long uid);\n\n    /**\n     * 通过用户 ID 查询用户们\n     *\n     * @param ids 用户 ID\n     * @return 用户对象信息数组\n     */\n    List<MemberUserDO> getUserList(Collection<Long> ids);\n\n    /**\n     * 修改用户昵称\n     * @param userId 用户id\n     * @param nickname 用户新昵称\n     */\n    void updateUserNickname(Long userId, String nickname,String birthday, Integer gender,\n                            String avatar,\n                            String mobile);\n\n    /**\n     * 修改用户头像\n     * @param userId 用户id\n     * @param inputStream 头像文件\n     * @return 头像url\n     */\n    String updateUserAvatar(Long userId, InputStream inputStream) throws Exception;\n\n    /**\n     * 修改手机\n     * @param userId 用户id\n     * @param reqVO 请求实体\n     */\n    void updateUserMobile(Long userId, AppUserUpdateMobileReqVO reqVO);\n\n    /**\n     * 判断密码是否匹配\n     *\n     * @param rawPassword 未加密的密码\n     * @param encodedPassword 加密后的密码\n     * @return 是否匹配\n     */\n    boolean isPasswordMatch(String rawPassword, String encodedPassword);\n\n    /**\n     * 更新用户余额\n     * @param uid y用户id\n     * @param price 金额\n     */\n    void incMoney(Long uid,BigDecimal price);\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/service/user/MemberUserServiceImpl.java",
    "content": "package co.yixiang.yshop.module.member.service.user;\n\nimport cn.hutool.core.io.IoUtil;\nimport cn.hutool.core.util.IdUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.enums.ShopCommonEnum;\nimport co.yixiang.yshop.module.coupon.dal.dataobject.couponuser.CouponUserDO;\nimport co.yixiang.yshop.module.coupon.service.couponuser.AppCouponUserService;\nimport co.yixiang.yshop.module.infra.api.file.FileApi;\nimport co.yixiang.yshop.module.member.controller.app.user.vo.AppUserQueryVo;\nimport co.yixiang.yshop.module.member.controller.app.user.vo.AppUserUpdateMobileReqVO;\nimport co.yixiang.yshop.module.member.convert.user.UserConvert;\nimport co.yixiang.yshop.module.member.dal.dataobject.user.MemberUserDO;\nimport co.yixiang.yshop.module.member.dal.dataobject.userbill.UserBillDO;\nimport co.yixiang.yshop.module.member.dal.mysql.user.MemberUserMapper;\nimport co.yixiang.yshop.module.member.dal.mysql.userbill.UserBillMapper;\nimport co.yixiang.yshop.module.member.enums.BillDetailEnum;\nimport co.yixiang.yshop.module.member.service.userbill.UserBillService;\nimport co.yixiang.yshop.module.system.api.sms.SmsCodeApi;\nimport co.yixiang.yshop.module.system.api.sms.dto.code.SmsCodeUseReqDTO;\nimport co.yixiang.yshop.module.system.enums.sms.SmsSceneEnum;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport com.google.common.annotations.VisibleForTesting;\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Valid;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.security.crypto.password.PasswordEncoder;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Propagation;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport java.io.InputStream;\nimport java.math.BigDecimal;\nimport java.time.LocalDateTime;\nimport java.util.Collection;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.framework.common.util.servlet.ServletUtils.getClientIP;\nimport static co.yixiang.yshop.module.member.enums.ErrorCodeConstants.USER_NOT_EXISTS;\n\n/**\n * 会员 User Service 实现类\n *\n * @author yshop\n */\n@Service\n@Valid\n@Slf4j\npublic class MemberUserServiceImpl extends ServiceImpl<MemberUserMapper,MemberUserDO> implements MemberUserService {\n\n    @Resource\n    private MemberUserMapper memberUserMapper;\n\n    @Resource\n    private FileApi fileApi;\n    @Resource\n    private SmsCodeApi smsCodeApi;\n\n    @Resource\n    private PasswordEncoder passwordEncoder;\n    @Resource\n    private AppCouponUserService appCouponUserService;\n    @Resource\n    private UserBillMapper userBillMapper;\n    @Resource\n    private UserBillService billService;\n\n\n\n\n    @Override\n    public MemberUserDO getUserByMobile(String mobile) {\n        return memberUserMapper.selectByMobile(mobile);\n    }\n\n    @Override\n    public List<MemberUserDO> getUserListByNickname(String nickname) {\n        return memberUserMapper.selectListByNicknameLike(nickname);\n    }\n\n    @Override\n    public MemberUserDO createUserIfAbsent(String mobile, String registerIp,String from) {\n        // 用户已经存在\n        if(StrUtil.isNotEmpty(mobile)){\n            MemberUserDO user = memberUserMapper.selectByMobile(mobile);\n            if (user != null) {\n                return user;\n            }\n        }\n\n        // 用户不存在，则进行创建\n        return this.createUser(mobile, registerIp,from);\n    }\n\n    private MemberUserDO createUser(String mobile, String registerIp,String from) {\n        // 生成密码\n        String password = IdUtil.fastSimpleUUID();\n        // 插入用户\n        MemberUserDO user = new MemberUserDO();\n        user.setNickname(mobile);\n        user.setUsername(mobile);\n        user.setMobile(mobile);\n        user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启\n        user.setPassword(encodePassword(password)); // 加密密码\n        user.setRegisterIp(registerIp);\n        user.setLoginType(from);\n        memberUserMapper.insert(user);\n        return user;\n    }\n\n    @Override\n    public void updateUserLogin(Long id, String loginIp) {\n        memberUserMapper.updateById(new MemberUserDO().setId(id)\n                .setLoginIp(loginIp).setLoginDate(LocalDateTime.now()));\n    }\n\n    @Override\n    public MemberUserDO getUser(Long id) {\n        return memberUserMapper.selectById(id);\n    }\n\n    @Override\n    public AppUserQueryVo getAppUser(Long id){\n        AppUserQueryVo appUserQueryVo = UserConvert.INSTANCE.convert3(memberUserMapper.selectById(id));\n        Long count = appCouponUserService.count(new LambdaQueryWrapper<CouponUserDO>()\n                .eq(CouponUserDO::getUserId,id)\n                .eq(CouponUserDO::getStatus, ShopCommonEnum.IS_STATUS_1));\n        appUserQueryVo.setCouponCount(count);\n        QueryWrapper<UserBillDO> wrapper = new QueryWrapper<>();\n        wrapper.select(\"SUM(number) as sumAll\")\n                .eq(\"category\", BillDetailEnum.CATEGORY_1.getValue())\n                .eq(\"type\",BillDetailEnum.TYPE_3.getValue()).eq(\"uid\",id);\n\n        UserBillDO userBillDO =  userBillMapper.selectOne(wrapper);\n        if(userBillDO == null){\n            appUserQueryVo.setSumMoney(BigDecimal.ZERO);\n        }else{\n            appUserQueryVo.setSumMoney(userBillDO.getSumAll());\n        }\n\n\n        return appUserQueryVo;\n    }\n    /**\n     * 减去用户余额\n     * @param uid uid\n     * @param payPrice 金额\n     */\n    @Override\n    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)\n    public void decPrice(Long uid, BigDecimal payPrice) {\n        memberUserMapper.decPrice(payPrice,uid);\n    }\n\n    /**\n     * 减去用户积分\n     * @param uid uid\n     * @param score 积分\n     */\n    @Override\n    public void decScore(Long uid, Integer score) {\n        memberUserMapper.decScore(score,uid);\n    }\n\n    /**\n     * 增加购买次数\n     * @param uid uid\n     */\n    @Override\n    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)\n    public void incPayCount(Long uid) {\n        memberUserMapper.incPayCount(uid);\n    }\n\n\n    @Override\n    public List<MemberUserDO> getUserList(Collection<Long> ids) {\n        return memberUserMapper.selectBatchIds(ids);\n    }\n\n    @Override\n    public void updateUserNickname(Long userId, String nickname,String birthday, Integer gender,\n                                   String avatar,\n                                   String mobile) {\n        MemberUserDO user = this.checkUserExists(userId);\n        // 仅当新昵称不等于旧昵称时进行修改\n//        if (nickname.equals(user.getNickname())){\n//            return;\n//        }\n        MemberUserDO userDO = new MemberUserDO();\n        userDO.setId(user.getId());\n        userDO.setNickname(nickname);\n        userDO.setBirthday(birthday);\n        userDO.setGender(gender);\n        userDO.setAvatar(avatar);\n        userDO.setMobile(mobile);\n        memberUserMapper.updateById(userDO);\n    }\n\n    @Override\n    public String updateUserAvatar(Long userId, InputStream avatarFile) throws Exception {\n        this.checkUserExists(userId);\n        // 创建文件\n        String avatar = fileApi.createFile(IoUtil.readBytes(avatarFile));\n        // 更新头像路径\n        memberUserMapper.updateById(MemberUserDO.builder().id(userId).avatar(avatar).build());\n        return avatar;\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void updateUserMobile(Long userId, AppUserUpdateMobileReqVO reqVO) {\n        // 检测用户是否存在\n        checkUserExists(userId);\n        // TODO yshop：oldMobile 应该不用传递\n\n        // 校验旧手机和旧验证码\n        smsCodeApi.useSmsCode(new SmsCodeUseReqDTO().setMobile(reqVO.getOldMobile()).setCode(reqVO.getOldCode())\n                .setScene(SmsSceneEnum.MEMBER_UPDATE_MOBILE.getScene()).setUsedIp(getClientIP()));\n        // 使用新验证码\n        smsCodeApi.useSmsCode(new SmsCodeUseReqDTO().setMobile(reqVO.getMobile()).setCode(reqVO.getCode())\n                .setScene(SmsSceneEnum.MEMBER_UPDATE_MOBILE.getScene()).setUsedIp(getClientIP()));\n\n        // 更新用户手机\n        memberUserMapper.updateById(MemberUserDO.builder().id(userId).mobile(reqVO.getMobile()).build());\n    }\n\n    @Override\n    public boolean isPasswordMatch(String rawPassword, String encodedPassword) {\n        return passwordEncoder.matches(rawPassword, encodedPassword);\n    }\n\n    /**\n     * 更新用户余额\n     * @param uid y用户id\n     * @param price 金额\n     */\n    @Override\n    public void incMoney(Long uid, BigDecimal price) {\n        if(price!=null&&price.doubleValue()>0){\n            memberUserMapper.incMoney(uid,price);\n        }\n    }\n\n\n\n\n    /**\n     * 对密码进行加密\n     *\n     * @param password 密码\n     * @return 加密后的密码\n     */\n    private String encodePassword(String password) {\n        return passwordEncoder.encode(password);\n    }\n\n    @VisibleForTesting\n    public MemberUserDO checkUserExists(Long id) {\n        if (id == null) {\n            return null;\n        }\n        MemberUserDO user = memberUserMapper.selectById(id);\n        if (user == null) {\n            throw exception(USER_NOT_EXISTS);\n        }\n        return user;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/service/user/UserService.java",
    "content": "package co.yixiang.yshop.module.member.service.user;\n\nimport java.util.*;\nimport co.yixiang.yshop.module.member.controller.admin.user.vo.*;\nimport co.yixiang.yshop.module.member.dal.dataobject.user.MemberUserDO;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport jakarta.validation.Valid;\n\n/**\n * 用户 Service 接口\n *\n * @author yshop\n */\npublic interface UserService {\n\n    /**\n     * 创建用户\n     *\n     * @param createReqVO 创建信息\n     * @return 编号\n     */\n    Long createUser(@Valid UserCreateReqVO createReqVO);\n\n    /**\n     * 更新用户\n     *\n     * @param updateReqVO 更新信息\n     */\n    void updateUser(@Valid UserUpdateReqVO updateReqVO);\n\n    /**\n     * 更新金额与积分\n     *\n     * @param updateReqVO 更新信息\n     */\n    void updateMony(@Valid UserUpdateMoneyReqVO updateReqVO);\n\n    /**\n     * 删除用户\n     *\n     * @param id 编号\n     */\n    void deleteUser(Long id);\n\n    /**\n     * 获得用户\n     *\n     * @param id 编号\n     * @return 用户\n     */\n    MemberUserDO getUser(Long id);\n\n    /**\n     * 获得用户列表\n     *\n     * @param ids 编号\n     * @return 用户列表\n     */\n    List<MemberUserDO> getUserList(Collection<Long> ids);\n\n    /**\n     * 获得用户分页\n     *\n     * @param pageReqVO 分页查询\n     * @return 用户分页\n     */\n    PageResult<MemberUserDO> getUserPage(UserPageReqVO pageReqVO);\n\n    /**\n     * 获得用户列表, 用于 Excel 导出\n     *\n     * @param exportReqVO 查询条件\n     * @return 用户列表\n     */\n    List<MemberUserDO> getUserList(UserExportReqVO exportReqVO);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/service/user/UserServiceImpl.java",
    "content": "package co.yixiang.yshop.module.member.service.user;\n\nimport cn.hutool.core.util.NumberUtil;\nimport co.yixiang.yshop.framework.common.enums.ShopCommonEnum;\nimport co.yixiang.yshop.module.member.enums.BillDetailEnum;\nimport co.yixiang.yshop.module.member.service.userbill.UserBillService;\nimport jakarta.annotation.Resource;\nimport org.springframework.stereotype.Service;\n\nimport org.springframework.validation.annotation.Validated;\n\nimport java.math.BigDecimal;\nimport java.util.*;\nimport co.yixiang.yshop.module.member.controller.admin.user.vo.*;\nimport co.yixiang.yshop.module.member.dal.dataobject.user.MemberUserDO;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\nimport co.yixiang.yshop.module.member.convert.user.UserConvert;\nimport co.yixiang.yshop.module.member.dal.mysql.user.MemberUserMapper;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.member.enums.ErrorCodeConstants.*;\n\n/**\n * 用户 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class UserServiceImpl implements UserService {\n\n    @Resource\n    private MemberUserMapper userMapper;\n    @Resource\n    private UserBillService userBillService;\n\n    @Override\n    public Long createUser(UserCreateReqVO createReqVO) {\n        // 插入\n        MemberUserDO user = UserConvert.INSTANCE.convert(createReqVO);\n        userMapper.insert(user);\n        // 返回\n        return user.getId();\n    }\n\n    @Override\n    public void updateUser(UserUpdateReqVO updateReqVO) {\n        // 校验存在\n        validateUserExists(updateReqVO.getId());\n        // 更新\n        MemberUserDO updateObj = UserConvert.INSTANCE.convert(updateReqVO);\n        userMapper.updateById(updateObj);\n    }\n\n    /**\n     * 更新金额与积分\n     *\n     * @param updateReqVO 更新信息\n     */\n    @Override\n    public void updateMony(UserUpdateMoneyReqVO updateReqVO){\n        MemberUserDO memberUserDO = userMapper.selectById(updateReqVO.getId());\n        double newMoney = 0d;\n        String mark = \"\";\n        if(ShopCommonEnum.ADD_1.getValue().equals(updateReqVO.getPtype())){\n            mark = \"系统增加了\"+updateReqVO.getMoney()+\"余额\";\n            newMoney = NumberUtil.add(memberUserDO.getNowMoney(),new BigDecimal(updateReqVO.getMoney())).doubleValue();\n            userBillService.income(memberUserDO.getId(),\"系统增加余额\", BillDetailEnum.CATEGORY_1.getValue(),\n                    BillDetailEnum.TYPE_6.getValue(),Double.valueOf(updateReqVO.getMoney()),newMoney, mark,\"\");\n        }else{\n            mark = \"系统扣除了\"+updateReqVO.getMoney()+\"余额\";\n            newMoney = NumberUtil.sub(memberUserDO.getNowMoney(),new BigDecimal(updateReqVO.getMoney())).doubleValue();\n            if(newMoney < 0) {\n                newMoney = 0d;\n            }\n            userBillService.expend(memberUserDO.getId(), \"系统减少余额\",\n                    BillDetailEnum.CATEGORY_1.getValue(),\n                    BillDetailEnum.TYPE_7.getValue(),\n                    Double.valueOf(updateReqVO.getMoney()), newMoney, mark);\n        }\n        double newIntegral = 0d;\n        if(ShopCommonEnum.ADD_1.getValue().equals(updateReqVO.getItype())){\n            mark = \"系统增加了\"+updateReqVO.getIntegral()+\"积分\";\n            newIntegral = NumberUtil.add(memberUserDO.getIntegral(),new BigDecimal(updateReqVO.getIntegral())).doubleValue();\n            userBillService.income(memberUserDO.getId(),\"系统增加积分\", BillDetailEnum.CATEGORY_2.getValue(),\n                    BillDetailEnum.TYPE_6.getValue(),Double.valueOf(updateReqVO.getIntegral()),newIntegral, mark,\"\");\n        }else{\n            mark = \"系统扣除了\"+updateReqVO.getIntegral()+\"积分\";\n            newIntegral = NumberUtil.sub(memberUserDO.getIntegral(),new BigDecimal(updateReqVO.getIntegral())).doubleValue();\n            if(newIntegral < 0) {\n                newIntegral = 0d;\n            }\n            userBillService.expend(memberUserDO.getId(), \"系统减少积分\",\n                    BillDetailEnum.CATEGORY_2.getValue(),\n                    BillDetailEnum.TYPE_7.getValue(),\n                    Double.valueOf(updateReqVO.getIntegral()), newIntegral, mark);\n        }\n        memberUserDO.setIntegral(BigDecimal.valueOf(newIntegral));\n        memberUserDO.setNowMoney(BigDecimal.valueOf(newMoney));\n        userMapper.updateById(memberUserDO);\n\n    }\n\n\n    @Override\n    public void deleteUser(Long id) {\n        // 校验存在\n        validateUserExists(id);\n        // 删除\n        userMapper.deleteById(id);\n    }\n\n    private void validateUserExists(Long id) {\n        if (userMapper.selectById(id) == null) {\n            throw exception(USER_NOT_EXISTS);\n        }\n    }\n\n    @Override\n    public MemberUserDO getUser(Long id) {\n        return userMapper.selectById(id);\n    }\n\n    @Override\n    public List<MemberUserDO> getUserList(Collection<Long> ids) {\n        return userMapper.selectBatchIds(ids);\n    }\n\n    @Override\n    public PageResult<MemberUserDO> getUserPage(UserPageReqVO pageReqVO) {\n        return userMapper.selectPage(pageReqVO);\n    }\n\n    @Override\n    public List<MemberUserDO> getUserList(UserExportReqVO exportReqVO) {\n        return userMapper.selectList(exportReqVO);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/service/user/dto/WechatUserDto.java",
    "content": "//package co.yixiang.yshop.module.member.service.user.dto;\r\n//\r\n//import lombok.*;\r\n//\r\n///**\r\n// * @ClassName WechatUserDTO\r\n// * @Author hupeng <610796224@qq.com>\r\n// * @Date 2023/7/18\r\n// **/\r\n//@Getter\r\n//@Setter\r\n//@Builder\r\n//@AllArgsConstructor\r\n//@NoArgsConstructor\r\n//public class WechatUserDto {\r\n//\r\n//    private String openid;\r\n//\r\n//    private String unionId;\r\n//\r\n//    private String routineOpenid;\r\n//\r\n//    private String nickname;\r\n//\r\n//    private String headimgurl;\r\n//\r\n//    private Integer sex;\r\n//\r\n//    private String city;\r\n//\r\n//    private String language;\r\n//\r\n//    private String province;\r\n//\r\n//    private String country;\r\n//\r\n//    private Boolean subscribe;\r\n//\r\n//    private Long subscribeTime;\r\n//\r\n//}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/service/useraddress/AppUserAddressService.java",
    "content": "package co.yixiang.yshop.module.member.service.useraddress;\n\nimport co.yixiang.yshop.module.member.controller.app.address.param.AppAddressParam;\nimport co.yixiang.yshop.module.member.controller.app.address.vo.AppUserAddressQueryVo;\nimport co.yixiang.yshop.module.member.dal.dataobject.useraddress.UserAddressDO;\nimport com.baomidou.mybatisplus.extension.service.IService;\n\nimport java.util.List;\n\n/**\n * 用户地址 Service 接口\n *\n * @author yshop\n */\npublic interface AppUserAddressService extends IService<UserAddressDO> {\n\n    /**\n     * 添加或者修改地址\n     * @param uid uid\n     * @param param AddressParam\n     */\n    Long addAndEdit(Long uid, AppAddressParam param);\n\n\n    /**\n     * 设置默认地址\n     * @param uid uid\n     * @param addressId 地址id\n     */\n    void setDefault(Long uid,String addressId);\n\n    /**\n     * 获取用户地址\n     * @param uid uid\n     * @param page page\n     * @param limit limit\n     * @return List\n     */\n    List<AppUserAddressQueryVo> getList(Long uid, int page, int limit);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/service/useraddress/AppUserAddressServiceImpl.java",
    "content": "package co.yixiang.yshop.module.member.service.useraddress;\n\nimport cn.hutool.core.util.NumberUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.enums.ShopCommonEnum;\nimport co.yixiang.yshop.module.member.controller.app.address.param.AppAddressParam;\nimport co.yixiang.yshop.module.member.controller.app.address.vo.AppUserAddressQueryVo;\nimport co.yixiang.yshop.module.member.convert.useraddress.UserAddressConvert;\nimport co.yixiang.yshop.module.member.dal.dataobject.useraddress.UserAddressDO;\nimport co.yixiang.yshop.module.member.dal.mysql.useraddress.UserAddressMapper;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport jakarta.annotation.Resource;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\n\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.member.enums.ErrorCodeConstants.USER_ADDRESS_NOT_EXISTS;\nimport static co.yixiang.yshop.module.member.enums.ErrorCodeConstants.USER_ADDRESS_PARAM_NOT_EXISTS;\n\n/**\n * 用户地址 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class AppUserAddressServiceImpl extends ServiceImpl<UserAddressMapper,UserAddressDO> implements AppUserAddressService {\n\n    @Resource\n    private UserAddressMapper userAddressMapper;\n\n    /**\n     * 添加或者修改地址\n     * @param uid uid\n     * @param param AddressParam\n     * @return id 地址id\n     */\n    @Override\n    public Long addAndEdit(Long uid, AppAddressParam param){\n        if(ShopCommonEnum.ENABLE_1.getValue().equals(param.getIsDefault())){\n            userAddressMapper.update(UserAddressDO.builder().isDefault(ShopCommonEnum.DEFAULT_0.getValue()).build(),\n                    new LambdaQueryWrapper<UserAddressDO>().eq(UserAddressDO::getUid,uid));\n        }\n        UserAddressDO userAddress = UserAddressDO.builder()\n                .address(param.getAddress())\n                .detail(param.getDetail())\n                .uid(uid)\n                .isDefault(param.getIsDefault())\n                .phone(param.getPhone())\n                .postCode(param.getPostCode())\n                .realName(param.getRealName())\n                .latitude(param.getLatitude())\n                .longitude(param.getLongitude())\n                .build();\n        if(StrUtil.isBlank(param.getId())){\n            this.save(userAddress);\n        }else{\n            userAddress.setId(Long.valueOf(param.getId()));\n            this.updateById(userAddress);\n        }\n\n        return userAddress.getId();\n    }\n\n    /**\n     * 设置默认地址\n     * @param uid uid\n     * @param addressId 地址id\n     */\n    @Override\n    public void setDefault(Long uid,String addressId){\n        if(StrUtil.isBlank(addressId) || !NumberUtil.isNumber(addressId)){\n            throw exception(USER_ADDRESS_PARAM_NOT_EXISTS);\n        }\n        UserAddressDO address = new UserAddressDO();\n        address.setIsDefault(ShopCommonEnum.DEFAULT_0.getValue());\n        userAddressMapper.update(address,\n                new LambdaQueryWrapper<UserAddressDO>().eq(UserAddressDO::getUid,uid));\n\n        UserAddressDO userAddress = new UserAddressDO();\n        userAddress.setIsDefault(ShopCommonEnum.DEFAULT_1.getValue());\n        userAddress.setId(Long.valueOf(addressId));\n        userAddressMapper.updateById(userAddress);\n    }\n\n\n    /**\n     * 获取用户地址\n     * @param uid uid\n     * @param page page\n     * @param limit limit\n     * @return List\n     */\n    @Override\n    public List<AppUserAddressQueryVo> getList(Long uid, int page, int limit){\n        Page<UserAddressDO> pageModel = new Page<>(page, limit);\n        IPage<UserAddressDO> pageList = this.lambdaQuery().eq(UserAddressDO::getUid,uid).page(pageModel);\n        return UserAddressConvert.INSTANCE.convertList02(pageList.getRecords());\n    }\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/service/useraddress/UserAddressService.java",
    "content": "package co.yixiang.yshop.module.member.service.useraddress;\n\nimport java.util.*;\nimport co.yixiang.yshop.module.member.controller.admin.useraddress.vo.*;\nimport co.yixiang.yshop.module.member.dal.dataobject.useraddress.UserAddressDO;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport jakarta.validation.Valid;\n\n/**\n * 用户地址 Service 接口\n *\n * @author yshop\n */\npublic interface UserAddressService {\n\n    /**\n     * 创建用户地址\n     *\n     * @param createReqVO 创建信息\n     * @return 编号\n     */\n    Long createUserAddress(@Valid UserAddressCreateReqVO createReqVO);\n\n    /**\n     * 更新用户地址\n     *\n     * @param updateReqVO 更新信息\n     */\n    void updateUserAddress(@Valid UserAddressUpdateReqVO updateReqVO);\n\n    /**\n     * 删除用户地址\n     *\n     * @param id 编号\n     */\n    void deleteUserAddress(Long id);\n\n    /**\n     * 获得用户地址\n     *\n     * @param id 编号\n     * @return 用户地址\n     */\n    UserAddressDO getUserAddress(Long id);\n\n    /**\n     * 获得用户地址列表\n     *\n     * @param ids 编号\n     * @return 用户地址列表\n     */\n    List<UserAddressDO> getUserAddressList(Collection<Long> ids);\n\n    /**\n     * 获得用户地址分页\n     *\n     * @param pageReqVO 分页查询\n     * @return 用户地址分页\n     */\n    PageResult<UserAddressDO> getUserAddressPage(UserAddressPageReqVO pageReqVO);\n\n    /**\n     * 获得用户地址列表, 用于 Excel 导出\n     *\n     * @param exportReqVO 查询条件\n     * @return 用户地址列表\n     */\n    List<UserAddressDO> getUserAddressList(UserAddressExportReqVO exportReqVO);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/service/useraddress/UserAddressServiceImpl.java",
    "content": "package co.yixiang.yshop.module.member.service.useraddress;\n\nimport jakarta.annotation.Resource;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport java.util.*;\nimport co.yixiang.yshop.module.member.controller.admin.useraddress.vo.*;\nimport co.yixiang.yshop.module.member.dal.dataobject.useraddress.UserAddressDO;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\nimport co.yixiang.yshop.module.member.convert.useraddress.UserAddressConvert;\nimport co.yixiang.yshop.module.member.dal.mysql.useraddress.UserAddressMapper;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.member.enums.ErrorCodeConstants.*;\n\n/**\n * 用户地址 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class UserAddressServiceImpl implements UserAddressService {\n\n    @Resource\n    private UserAddressMapper userAddressMapper;\n\n    @Override\n    public Long createUserAddress(UserAddressCreateReqVO createReqVO) {\n        // 插入\n        UserAddressDO userAddress = UserAddressConvert.INSTANCE.convert(createReqVO);\n        userAddressMapper.insert(userAddress);\n        // 返回\n        return userAddress.getId();\n    }\n\n    @Override\n    public void updateUserAddress(UserAddressUpdateReqVO updateReqVO) {\n        // 校验存在\n        validateUserAddressExists(updateReqVO.getId());\n        // 更新\n        UserAddressDO updateObj = UserAddressConvert.INSTANCE.convert(updateReqVO);\n        userAddressMapper.updateById(updateObj);\n    }\n\n    @Override\n    public void deleteUserAddress(Long id) {\n        // 校验存在\n        validateUserAddressExists(id);\n        // 删除\n        userAddressMapper.deleteById(id);\n    }\n\n    private void validateUserAddressExists(Long id) {\n        if (userAddressMapper.selectById(id) == null) {\n            throw exception(USER_ADDRESS_NOT_EXISTS);\n        }\n    }\n\n    @Override\n    public UserAddressDO getUserAddress(Long id) {\n        return userAddressMapper.selectById(id);\n    }\n\n    @Override\n    public List<UserAddressDO> getUserAddressList(Collection<Long> ids) {\n        return userAddressMapper.selectBatchIds(ids);\n    }\n\n    @Override\n    public PageResult<UserAddressDO> getUserAddressPage(UserAddressPageReqVO pageReqVO) {\n        return userAddressMapper.selectPage(pageReqVO);\n    }\n\n    @Override\n    public List<UserAddressDO> getUserAddressList(UserAddressExportReqVO exportReqVO) {\n        return userAddressMapper.selectList(exportReqVO);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/service/userbill/UserBillService.java",
    "content": "package co.yixiang.yshop.module.member.service.userbill;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.member.controller.admin.userbill.vo.UserBillPageReqVO;\nimport co.yixiang.yshop.module.member.controller.app.user.vo.AppUserBillVO;\nimport co.yixiang.yshop.module.member.dal.dataobject.userbill.UserBillDO;\nimport com.baomidou.mybatisplus.extension.service.IService;\n\nimport java.util.List;\n\n/**\n * 用户账单 Service 接口\n *\n * @author yshop\n */\npublic interface UserBillService extends IService<UserBillDO> {\n\n\n    /**\n     * 获得用户账单分页\n     *\n     * @param pageReqVO 分页查询\n     * @return 用户账单分页\n     */\n    PageResult<UserBillDO> getUserBillPage(UserBillPageReqVO pageReqVO);\n\n    /**\n     * 增加支出流水\n     * @param uid uid\n     * @param title 账单标题\n     * @param category 明细种类\n     * @param type 明细类型\n     * @param number 明细数字\n     * @param balance 剩余\n     * @param mark 备注\n     */\n    void expend(Long uid,String title,String category,String type,double number,double balance,String mark);\n\n    /**\n     * 增加收入/支入流水\n     * @param uid uid\n     * @param title 账单标题\n     * @param category 明细种类\n     * @param type 明细类型\n     * @param number 明细数字\n     * @param balance 剩余\n     * @param mark 备注\n     * @param linkid 关联id\n     */\n    void income(Long uid,String title,String category,String type,double number,\n                double balance,String mark,String linkid);\n\n    void income(Long uid,String title,String category,String type,double number,\n                double balance,String mark,String linkid,String extendField);\n\n    /**\n     * 获取用户账单\n     * @param uid\n     * @param cate 0余额 1-积分\n     * @param type 状态,0全部  1消费 2充值 3退款\n     * @param page\n     * @param limit\n     * @return\n     */\n    List<AppUserBillVO> getBillList(Long uid, int cate, int type,int page, int limit);\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/main/java/co/yixiang/yshop/module/member/service/userbill/UserBillServiceImpl.java",
    "content": "package co.yixiang.yshop.module.member.service.userbill;\n\nimport co.yixiang.yshop.framework.common.enums.ShopCommonEnum;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.member.controller.admin.userbill.vo.UserBillPageReqVO;\nimport co.yixiang.yshop.module.member.controller.app.user.vo.AppUserBillVO;\nimport co.yixiang.yshop.module.member.convert.userbill.UserBillConvert;\nimport co.yixiang.yshop.module.member.dal.dataobject.userbill.UserBillDO;\nimport co.yixiang.yshop.module.member.dal.mysql.userbill.UserBillMapper;\nimport co.yixiang.yshop.module.member.enums.BillDetailEnum;\nimport co.yixiang.yshop.module.member.enums.BillEnum;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport jakarta.annotation.Resource;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport java.math.BigDecimal;\nimport java.util.List;\n\n/**\n * 用户账单 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class UserBillServiceImpl extends ServiceImpl<UserBillMapper, UserBillDO> implements UserBillService {\n\n    @Resource\n    private UserBillMapper userBillMapper;\n\n    @Override\n    public PageResult<UserBillDO> getUserBillPage(UserBillPageReqVO pageReqVO) {\n        return userBillMapper.selectPage(pageReqVO);\n    }\n\n\n    /**\n     * 增加支出流水\n     * @param uid uid\n     * @param title 账单标题\n     * @param category 明细种类\n     * @param type 明细类型\n     * @param number 明细数字\n     * @param balance 剩余\n     * @param mark 备注\n     */\n    @Override\n    public void expend(Long uid,String title,String category,String type,double number,double balance,String mark){\n        UserBillDO userBill = UserBillDO.builder()\n                .uid(uid)\n                .title(title)\n                .category(category)\n                .type(type)\n                .number(BigDecimal.valueOf(number))\n                .balance(BigDecimal.valueOf(balance))\n                .mark(mark)\n                .pm(BillEnum.PM_0.getValue())\n                .build();\n\n        userBillMapper.insert(userBill);\n    }\n\n    /**\n     * 增加收入/支入流水\n     * @param uid uid\n     * @param title 账单标题\n     * @param category 明细种类\n     * @param type 明细类型\n     * @param number 明细数字\n     * @param balance 剩余\n     * @param mark 备注\n     * @param linkid 关联id\n     */\n    @Override\n    public void income(Long uid,String title,String category,String type,double number,\n                       double balance,String mark,String linkid){\n        UserBillDO userBill = UserBillDO.builder()\n                .uid(uid)\n                .title(title)\n                .category(category)\n                .type(type)\n                .number(BigDecimal.valueOf(number))\n                .balance(BigDecimal.valueOf(balance))\n                .mark(mark)\n                .pm(BillEnum.PM_1.getValue())\n                .linkId(linkid)\n                .build();\n\n        userBillMapper.insert(userBill);\n    }\n\n    @Override\n    public void income(Long uid, String title, String category, String type, double number, double balance,\n                       String mark, String linkid, String extendField) {\n\n        UserBillDO userBill = UserBillDO.builder()\n                .uid(uid)\n                .title(title)\n                .category(category)\n                .type(type)\n                .number(BigDecimal.valueOf(number))\n                .balance(BigDecimal.valueOf(balance))\n                .mark(mark)\n                .pm(BillEnum.PM_1.getValue())\n                .linkId(linkid)\n                .extendField(extendField)\n                .status(ShopCommonEnum.IS_STATUS_1.getValue())\n                .build();\n\n        userBillMapper.insert(userBill);\n\n    }\n\n    /**\n     * 获取用户账单\n     * @param uid\n     * @param cate 0余额 1-积分\n     * @param type 状态,0全部  1消费 2充值 3退款\n     * @param page\n     * @param limit\n     * @return\n     */\n    @Override\n    public List<AppUserBillVO> getBillList(Long uid,int cate, int type, int page, int limit) {\n        LambdaQueryWrapper<UserBillDO> wrapper = new LambdaQueryWrapper<>();\n        if(cate == 1){\n            wrapper.eq(UserBillDO::getCategory,BillDetailEnum.CATEGORY_2.getValue());\n        }else{\n            wrapper.eq(UserBillDO::getCategory,BillDetailEnum.CATEGORY_1.getValue());\n        }\n        wrapper.eq(UserBillDO::getUid, uid)\n                .eq(UserBillDO::getStatus,ShopCommonEnum.IS_STATUS_1.getValue())\n                //.eq(UserBillDO::getCategory,BillDetailEnum.CATEGORY_1.getValue())\n                .orderByDesc(UserBillDO::getId);\n        switch (type){\n            case 1:\n                wrapper.eq(UserBillDO::getType, BillDetailEnum.TYPE_3.getValue());\n                break;\n            case 2:\n                wrapper.eq(UserBillDO::getType, BillDetailEnum.TYPE_1.getValue());\n                break;\n            case 3:\n                wrapper.eq(UserBillDO::getType, BillDetailEnum.TYPE_5.getValue());\n                break;\n            default:\n\n        }\n        Page<UserBillDO> pageModel = new Page<>(page, limit);\n        IPage<UserBillDO> pageList = userBillMapper.selectPage(pageModel, wrapper);\n        return UserBillConvert.INSTANCE.convertList02(pageList.getRecords());\n    }\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/test/java/co/yixiang/yshop/module/member/service/auth/MemberAuthServiceTest.java",
    "content": "package co.yixiang.yshop.module.member.service.auth;\n\nimport cn.binarywang.wx.miniapp.api.WxMaService;\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.util.collection.ArrayUtils;\nimport co.yixiang.yshop.framework.redis.config.YshopRedisAutoConfiguration;\nimport co.yixiang.yshop.framework.test.core.ut.BaseDbAndRedisUnitTest;\nimport co.yixiang.yshop.module.member.controller.app.auth.vo.AppAuthResetPasswordReqVO;\nimport co.yixiang.yshop.module.member.controller.app.auth.vo.AppAuthUpdatePasswordReqVO;\nimport co.yixiang.yshop.module.member.dal.dataobject.user.MemberUserDO;\nimport co.yixiang.yshop.module.member.dal.mysql.user.MemberUserMapper;\nimport co.yixiang.yshop.module.member.service.user.MemberUserService;\nimport co.yixiang.yshop.module.system.api.oauth2.OAuth2TokenApi;\nimport co.yixiang.yshop.module.system.api.logger.LoginLogApi;\nimport co.yixiang.yshop.module.system.api.sms.SmsCodeApi;\nimport co.yixiang.yshop.module.system.api.social.SocialUserApi;\nimport jakarta.annotation.Resource;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.mock.mockito.MockBean;\nimport org.springframework.context.annotation.Import;\nimport org.springframework.security.crypto.password.PasswordEncoder;\n\nimport java.util.function.Consumer;\n\nimport static cn.hutool.core.util.RandomUtil.randomEle;\nimport static cn.hutool.core.util.RandomUtil.randomNumbers;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.randomPojo;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.randomString;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.when;\n\n// TODO @yshop：单测的 review，等逻辑都达成一致后\n/**\n * {@link MemberAuthService} 的单元测试类\n *\n * @author 宋天\n */\n@Import({MemberAuthServiceImpl.class, YshopRedisAutoConfiguration.class})\npublic class MemberAuthServiceTest extends BaseDbAndRedisUnitTest {\n\n    // TODO @yshop：登录相关的单测，待补全\n\n    @Resource\n    private MemberAuthServiceImpl authService;\n\n    @MockBean\n    private MemberUserService userService;\n    @MockBean\n    private SmsCodeApi smsCodeApi;\n    @MockBean\n    private LoginLogApi loginLogApi;\n    @MockBean\n    private OAuth2TokenApi oauth2TokenApi;\n    @MockBean\n    private SocialUserApi socialUserApi;\n    @MockBean\n    private WxMaService wxMaService;\n    @MockBean\n    private PasswordEncoder passwordEncoder;\n\n    @Resource\n    private MemberUserMapper memberUserMapper;\n\n    @Test\n    public void testUpdatePassword_success(){\n        // 准备参数\n        MemberUserDO userDO = randomUserDO();\n        memberUserMapper.insert(userDO);\n\n        // 新密码\n        String newPassword = randomString();\n\n        // 请求实体\n        AppAuthUpdatePasswordReqVO reqVO = AppAuthUpdatePasswordReqVO.builder()\n                .oldPassword(userDO.getPassword())\n                .password(newPassword)\n                .build();\n\n        // 测试桩\n        // 这两个相等是为了返回ture这个结果\n        when(passwordEncoder.matches(reqVO.getOldPassword(),reqVO.getOldPassword())).thenReturn(true);\n        when(passwordEncoder.encode(newPassword)).thenReturn(newPassword);\n\n        // 更新用户密码\n        authService.updatePassword(userDO.getId(), reqVO);\n        assertEquals(memberUserMapper.selectById(userDO.getId()).getPassword(),newPassword);\n    }\n\n    @Test\n    public void testResetPassword_success(){\n        // 准备参数\n        MemberUserDO userDO = randomUserDO();\n        memberUserMapper.insert(userDO);\n\n        // 随机密码\n        String password = randomNumbers(11);\n        // 随机验证码\n        String code = randomNumbers(4);\n\n        // mock\n        when(passwordEncoder.encode(password)).thenReturn(password);\n\n        // 更新用户密码\n        AppAuthResetPasswordReqVO reqVO = new AppAuthResetPasswordReqVO();\n        reqVO.setMobile(userDO.getMobile());\n        reqVO.setPassword(password);\n        reqVO.setCode(code);\n\n       // authService.resetPassword(reqVO);\n        assertEquals(memberUserMapper.selectById(userDO.getId()).getPassword(),password);\n    }\n\n\n    // ========== 随机对象 ==========\n\n    @SafeVarargs\n    private static MemberUserDO randomUserDO(Consumer<MemberUserDO>... consumers) {\n        Consumer<MemberUserDO> consumer = (o) -> {\n            o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围\n            o.setPassword(randomString());\n        };\n        return randomPojo(MemberUserDO.class, ArrayUtils.append(consumer, consumers));\n    }\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/test/resources/application-unit-test.yaml",
    "content": "spring:\n  main:\n    lazy-initialization: true # 开启懒加载，加快速度\n    banner-mode: off # 单元测试，禁用 Banner\n\n--- #################### 数据库相关配置 ####################\n\nspring:\n  # 数据源配置项\n  datasource:\n    name: yshop-pro\n    url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=value; # MODE 使用 MySQL 模式；DATABASE_TO_UPPER 配置表和字段使用小写\n    driver-class-name: org.h2.Driver\n    username: sa\n    password:\n    druid:\n      async-init: true # 单元测试，异步初始化 Druid 连接池，提升启动速度\n      initial-size: 1 # 单元测试，配置为 1，提升启动速度\n  sql:\n    init:\n      schema-locations: classpath:/sql/create_tables.sql\n\n  # Redis 配置。Redisson 默认的配置足够使用，一般不需要进行调优\n  redis:\n    host: 127.0.0.1 # 地址\n    port: 16379 # 端口（单元测试，使用 16379 端口）\n    database: 0 # 数据库索引\n\nmybatis:\n  lazy-initialization: true # 单元测试，设置 MyBatis Mapper 延迟加载，加速每个单元测试\n\n--- #################### 定时任务相关配置 ####################\n\n--- #################### 配置中心相关配置 ####################\n\n--- #################### 服务保障相关配置 ####################\n\n# Lock4j 配置项（单元测试，禁用 Lock4j）\n\n# Resilience4j 配置项\n\n--- #################### 监控相关配置 ####################\n\n--- #################### yshop相关配置 ####################\n\n# yshop配置项，设置当前项目所有自定义的配置\nyshop:\n  info:\n    base-package: co.yixiang.yshop.module\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/test/resources/logback.xml",
    "content": "<configuration>\n    <!-- 引用 Spring Boot 的 logback 基础配置 -->\n    <include resource=\"org/springframework/boot/logging/logback/defaults.xml\" />\n</configuration>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/test/resources/sql/clean.sql",
    "content": "DELETE FROM \"member_user\";\nDELETE FROM \"member_address\";\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-member/yshop-module-member-biz/src/test/resources/sql/create_tables.sql",
    "content": "CREATE TABLE IF NOT EXISTS \"member_user\"  (\n    \"id\" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY COMMENT '编号',\n    \"nickname\" varchar(30)  NOT NULL DEFAULT '' COMMENT '用户昵称',\n    \"avatar\" varchar(255)  NOT NULL DEFAULT '' COMMENT '头像',\n    \"status\" tinyint NOT NULL COMMENT '状态',\n    \"mobile\" varchar(11)  NOT NULL COMMENT '手机号',\n    \"password\" varchar(100)  NOT NULL DEFAULT '' COMMENT '密码',\n    \"register_ip\" varchar(32)  NOT NULL COMMENT '注册 IP',\n    \"login_ip\" varchar(50) NULL DEFAULT '' COMMENT '最后登录IP',\n    \"login_date\" datetime NULL DEFAULT NULL COMMENT '最后登录时间',\n    \"creator\" varchar(64)  NULL DEFAULT '' COMMENT '创建者',\n    \"create_time\" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',\n    \"updater\" varchar(64)  NULL DEFAULT '' COMMENT '更新者',\n    \"update_time\" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',\n    \"deleted\" bit(1) NOT NULL DEFAULT '0' COMMENT '是否删除',\n    \"tenant_id\" bigint not null default  '0',\n    PRIMARY KEY (\"id\")\n) COMMENT '会员表';\n\nCREATE TABLE IF NOT EXISTS \"member_address\" (\n    \"id\" bigint(20) NOT NULL GENERATED BY DEFAULT AS IDENTITY,\n    \"user_id\" bigint(20) NOT NULL,\n    \"name\" varchar(10) NOT NULL,\n    \"mobile\" varchar(20) NOT NULL,\n    \"area_id\" bigint(20) NOT NULL,\n    \"post_code\" varchar(16) NOT NULL,\n    \"detail_address\" varchar(250) NOT NULL,\n    \"defaulted\" bit NOT NULL,\n    \"create_time\" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    \"creator\" varchar(64) DEFAULT '',\n    \"update_time\" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n    \"deleted\" bit NOT NULL DEFAULT FALSE,\n    \"updater\" varchar(64) DEFAULT '',\n    PRIMARY KEY (\"id\")\n) COMMENT '用户收件地址';\n\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-message/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>co.yixiang.boot</groupId>\n        <artifactId>yshop</artifactId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>yshop-module-message</artifactId>\n    <packaging>pom</packaging>\n    <modules>\n        <module>yshop-module-message-api</module>\n        <module>yshop-module-message-biz</module>\n    </modules>\n\n    <name>${project.artifactId}</name>\n    <description>\n        message 模块\n    </description>\n\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-message/yshop-module-message-api/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>yshop-module-message</artifactId>\n        <groupId>co.yixiang.boot</groupId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>yshop-module-message-api</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>\n        message 模块 API，暴露给其它模块调用\n    </description>\n\n    <dependencies>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-common</artifactId>\n        </dependency>\n\n        <!-- 参数校验 -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-validation</artifactId>\n            <optional>true</optional>\n        </dependency>\n\n        <!-- DB 相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-mybatis</artifactId>\n        </dependency>\n\n\n\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-message/yshop-module-message-api/src/main/java/co/yixiang/yshop/module/message/enums/ErrorCodeConstants.java",
    "content": "package co.yixiang.yshop.module.message.enums;\n\nimport co.yixiang.yshop.framework.common.exception.ErrorCode;\n\npublic interface ErrorCodeConstants {\n    ErrorCode WECHAT_TEMPLATE_NOT_EXISTS = new ErrorCode(1008011000, \"微信模板不存在\");\n}\n\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-message/yshop-module-message-api/src/main/java/co/yixiang/yshop/module/message/enums/WechatTempateEnum.java",
    "content": "/**\n * Copyright (C) 2018-2022\n * All rights reserved, Designed By www.yixiang.co\n * 注意：\n * 本软件为www.yixiang.co开发研制，未经购买不得使用\n * 购买后可获得全部源代码（禁止转卖、分享、上传到码云、github等开源平台）\n * 一经发现盗用、分享等行为，将追究法律责任，后果自负\n */\npackage co.yixiang.yshop.module.message.enums;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * @author hupeng\n * 微信公众号模板枚举\n */\n@Getter\n@AllArgsConstructor\npublic enum WechatTempateEnum {\n    PAY_SUCCESS(\"pay_success\",\"支付成功通知\"),\n    DELIVERY_SUCCESS(\"delivery_success\",\"发货成功通知\"),\n    REFUND_SUCCESS(\"refund_success\",\"退款成功通知\"),\n    RECHARGE_SUCCESS(\"recharge_success\",\"充值成功通知\"),\n    TEMPLATES(\"template\",\"公众号模板消息\"),\n    SUBSCRIBE(\"subscribe\",\"小程序订阅消息\");\n\n    private String value; //模板编号\n    private String desc; //模板id\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-message/yshop-module-message-biz/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>yshop-module-message</artifactId>\n        <groupId>co.yixiang.boot</groupId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>yshop-module-message-biz</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>\n        message 模块，模板消息 websocket 推送消息等等\n    </description>\n\n    <dependencies>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-message-api</artifactId>\n            <version>${revision}</version>\n        </dependency>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-member-biz</artifactId>\n            <version>${revision}</version>\n        </dependency>\n\n        <!-- 业务组件 -->\n<!--        <dependency>-->\n<!--            <groupId>co.yixiang.boot</groupId>-->\n<!--            <artifactId>yshop-spring-boot-starter-biz-operatelog</artifactId>-->\n<!--        </dependency>-->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-biz-tenant</artifactId>\n        </dependency>\n\n        <!-- Web 相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-security</artifactId>\n        </dependency>\n\n\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-redis</artifactId>\n        </dependency>\n\n\n        <!-- 消息队列相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-mq</artifactId>\n        </dependency>\n\n        <!-- Test 测试相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n\n        <!-- 工具类相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-excel</artifactId>\n        </dependency>\n\n\n\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-message/yshop-module-message-biz/src/main/java/co/yixiang/yshop/module/message/controller/admin/wechattemplate/WechatTemplateController.java",
    "content": "package co.yixiang.yshop.module.message.controller.admin.wechattemplate;\n\nimport jakarta.servlet.http.HttpServletResponse;\nimport org.springframework.web.bind.annotation.*;\nimport jakarta.annotation.Resource;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.Operation;\n\nimport jakarta.validation.constraints.*;\nimport jakarta.validation.*;\nimport java.util.*;\nimport java.io.IOException;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\nimport co.yixiang.yshop.framework.excel.core.util.ExcelUtils;\n\nimport co.yixiang.yshop.module.message.controller.admin.wechattemplate.vo.*;\nimport co.yixiang.yshop.module.message.dal.dataobject.wechattemplate.WechatTemplateDO;\nimport co.yixiang.yshop.module.message.convert.wechattemplate.WechatTemplateConvert;\nimport co.yixiang.yshop.module.message.service.wechattemplate.WechatTemplateService;\n\n@Tag(name = \"管理后台 - 微信模板\")\n@RestController\n@RequestMapping(\"/message/wechat-template\")\n@Validated\npublic class WechatTemplateController {\n\n    @Resource\n    private WechatTemplateService wechatTemplateService;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建微信模板\")\n    @PreAuthorize(\"@ss.hasPermission('message:wechat-template:create')\")\n    public CommonResult<Integer> createWechatTemplate(@Valid @RequestBody WechatTemplateCreateReqVO createReqVO) {\n        return success(wechatTemplateService.createWechatTemplate(createReqVO));\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"更新微信模板\")\n    @PreAuthorize(\"@ss.hasPermission('message:wechat-template:update')\")\n    public CommonResult<Boolean> updateWechatTemplate(@Valid @RequestBody WechatTemplateUpdateReqVO updateReqVO) {\n        wechatTemplateService.updateWechatTemplate(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除微信模板\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('message:wechat-template:delete')\")\n    public CommonResult<Boolean> deleteWechatTemplate(@RequestParam(\"id\") Integer id) {\n        wechatTemplateService.deleteWechatTemplate(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得微信模板\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('message:wechat-template:query')\")\n    public CommonResult<WechatTemplateRespVO> getWechatTemplate(@RequestParam(\"id\") Integer id) {\n        WechatTemplateDO wechatTemplate = wechatTemplateService.getWechatTemplate(id);\n        return success(WechatTemplateConvert.INSTANCE.convert(wechatTemplate));\n    }\n\n    @GetMapping(\"/list\")\n    @Operation(summary = \"获得微信模板列表\")\n    @Parameter(name = \"ids\", description = \"编号列表\", required = true, example = \"1024,2048\")\n    @PreAuthorize(\"@ss.hasPermission('message:wechat-template:query')\")\n    public CommonResult<List<WechatTemplateRespVO>> getWechatTemplateList(@RequestParam(\"ids\") Collection<Integer> ids) {\n        List<WechatTemplateDO> list = wechatTemplateService.getWechatTemplateList(ids);\n        return success(WechatTemplateConvert.INSTANCE.convertList(list));\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得微信模板分页\")\n    @PreAuthorize(\"@ss.hasPermission('message:wechat-template:query')\")\n    public CommonResult<PageResult<WechatTemplateRespVO>> getWechatTemplatePage(@Valid WechatTemplatePageReqVO pageVO) {\n        PageResult<WechatTemplateDO> pageResult = wechatTemplateService.getWechatTemplatePage(pageVO);\n        return success(WechatTemplateConvert.INSTANCE.convertPage(pageResult));\n    }\n\n    @GetMapping(\"/export-excel\")\n    @Operation(summary = \"导出微信模板 Excel\")\n    @PreAuthorize(\"@ss.hasPermission('message:wechat-template:export')\")\n    public void exportWechatTemplateExcel(@Valid WechatTemplateExportReqVO exportReqVO,\n              HttpServletResponse response) throws IOException {\n        List<WechatTemplateDO> list = wechatTemplateService.getWechatTemplateList(exportReqVO);\n        // 导出 Excel\n        List<WechatTemplateExcelVO> datas = WechatTemplateConvert.INSTANCE.convertList02(list);\n        ExcelUtils.write(response, \"微信模板.xls\", \"数据\", WechatTemplateExcelVO.class, datas);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-message/yshop-module-message-biz/src/main/java/co/yixiang/yshop/module/message/controller/admin/wechattemplate/vo/WechatTemplateBaseVO.java",
    "content": "package co.yixiang.yshop.module.message.controller.admin.wechattemplate.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.util.*;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\nimport jakarta.validation.constraints.*;\n\n/**\n* 微信模板 Base VO，提供给添加、修改、详细的子 VO 使用\n* 如果子 VO 存在差异的字段，请不要添加到这里，影响 Swagger 文档生成\n*/\n@Data\npublic class WechatTemplateBaseVO {\n\n    @Schema(description = \"模板编号\", required = true)\n    @NotNull(message = \"模板编号不能为空\")\n    private String tempkey;\n\n    @Schema(description = \"模板名\", required = true, example = \"张三\")\n    @NotNull(message = \"模板名不能为空\")\n    private String name;\n\n    @Schema(description = \"回复内容\", required = true)\n    private String content;\n\n    @Schema(description = \"模板ID\", example = \"15656\")\n    private String tempid;\n\n    @Schema(description = \"状态\", required = true, example = \"1\")\n    @NotNull(message = \"状态不能为空\")\n    private Byte status;\n\n    @Schema(description = \"类型：template:模板消息 subscribe:订阅消息\", example = \"1\")\n    private String type;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-message/yshop-module-message-biz/src/main/java/co/yixiang/yshop/module/message/controller/admin/wechattemplate/vo/WechatTemplateCreateReqVO.java",
    "content": "package co.yixiang.yshop.module.message.controller.admin.wechattemplate.vo;\n\nimport lombok.*;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.*;\n\n@Schema(description = \"管理后台 - 微信模板创建 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class WechatTemplateCreateReqVO extends WechatTemplateBaseVO {\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-message/yshop-module-message-biz/src/main/java/co/yixiang/yshop/module/message/controller/admin/wechattemplate/vo/WechatTemplateExcelVO.java",
    "content": "package co.yixiang.yshop.module.message.controller.admin.wechattemplate.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.util.*;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\n\nimport com.alibaba.excel.annotation.ExcelProperty;\n\n/**\n * 微信模板 Excel VO\n *\n * @author yshop\n */\n@Data\npublic class WechatTemplateExcelVO {\n\n    @ExcelProperty(\"模板id\")\n    private Integer id;\n\n    @ExcelProperty(\"模板编号\")\n    private String tempkey;\n\n    @ExcelProperty(\"模板名\")\n    private String name;\n\n    @ExcelProperty(\"回复内容\")\n    private String content;\n\n    @ExcelProperty(\"模板ID\")\n    private String tempid;\n\n    @ExcelProperty(\"添加时间\")\n    private LocalDateTime createTime;\n\n    @ExcelProperty(\"状态\")\n    private Byte status;\n\n    @ExcelProperty(\"类型：template:模板消息 subscribe:订阅消息\")\n    private String type;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-message/yshop-module-message-biz/src/main/java/co/yixiang/yshop/module/message/controller/admin/wechattemplate/vo/WechatTemplateExportReqVO.java",
    "content": "package co.yixiang.yshop.module.message.controller.admin.wechattemplate.vo;\n\nimport lombok.*;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport java.time.LocalDateTime;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 微信模板 Excel 导出 Request VO，参数和 WechatTemplatePageReqVO 是一致的\")\n@Data\npublic class WechatTemplateExportReqVO {\n\n    @Schema(description = \"模板编号\")\n    private String tempkey;\n\n    @Schema(description = \"模板名\", example = \"张三\")\n    private String name;\n\n    @Schema(description = \"回复内容\")\n    private String content;\n\n    @Schema(description = \"模板ID\", example = \"15656\")\n    private String tempid;\n\n    @Schema(description = \"添加时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n    @Schema(description = \"状态\", example = \"1\")\n    private Byte status;\n\n    @Schema(description = \"类型：template:模板消息 subscribe:订阅消息\", example = \"1\")\n    private String type;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-message/yshop-module-message-biz/src/main/java/co/yixiang/yshop/module/message/controller/admin/wechattemplate/vo/WechatTemplatePageReqVO.java",
    "content": "package co.yixiang.yshop.module.message.controller.admin.wechattemplate.vo;\n\nimport lombok.*;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport org.springframework.format.annotation.DateTimeFormat;\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 微信模板分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class WechatTemplatePageReqVO extends PageParam {\n\n    @Schema(description = \"模板编号\")\n    private String tempkey;\n\n    @Schema(description = \"模板名\", example = \"张三\")\n    private String name;\n\n    @Schema(description = \"回复内容\")\n    private String content;\n\n    @Schema(description = \"模板ID\", example = \"15656\")\n    private String tempid;\n\n    @Schema(description = \"添加时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n    @Schema(description = \"状态\", example = \"1\")\n    private Byte status;\n\n    @Schema(description = \"类型：template:模板消息 subscribe:订阅消息\", example = \"1\")\n    private String type;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-message/yshop-module-message-biz/src/main/java/co/yixiang/yshop/module/message/controller/admin/wechattemplate/vo/WechatTemplateRespVO.java",
    "content": "package co.yixiang.yshop.module.message.controller.admin.wechattemplate.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 微信模板 Response VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class WechatTemplateRespVO extends WechatTemplateBaseVO {\n\n    @Schema(description = \"模板id\", required = true, example = \"8445\")\n    private Integer id;\n\n    @Schema(description = \"添加时间\", required = true)\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-message/yshop-module-message-biz/src/main/java/co/yixiang/yshop/module/message/controller/admin/wechattemplate/vo/WechatTemplateUpdateReqVO.java",
    "content": "package co.yixiang.yshop.module.message.controller.admin.wechattemplate.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.util.*;\nimport jakarta.validation.constraints.*;\n\n@Schema(description = \"管理后台 - 微信模板更新 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class WechatTemplateUpdateReqVO extends WechatTemplateBaseVO {\n\n    @Schema(description = \"模板id\", required = true, example = \"8445\")\n    @NotNull(message = \"模板id不能为空\")\n    private Integer id;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-message/yshop-module-message-biz/src/main/java/co/yixiang/yshop/module/message/convert/wechattemplate/WechatTemplateConvert.java",
    "content": "package co.yixiang.yshop.module.message.convert.wechattemplate;\n\nimport java.util.*;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\nimport org.mapstruct.Mapper;\nimport org.mapstruct.factory.Mappers;\nimport co.yixiang.yshop.module.message.controller.admin.wechattemplate.vo.*;\nimport co.yixiang.yshop.module.message.dal.dataobject.wechattemplate.WechatTemplateDO;\n\n/**\n * 微信模板 Convert\n *\n * @author yshop\n */\n@Mapper\npublic interface WechatTemplateConvert {\n\n    WechatTemplateConvert INSTANCE = Mappers.getMapper(WechatTemplateConvert.class);\n\n    WechatTemplateDO convert(WechatTemplateCreateReqVO bean);\n\n    WechatTemplateDO convert(WechatTemplateUpdateReqVO bean);\n\n    WechatTemplateRespVO convert(WechatTemplateDO bean);\n\n    List<WechatTemplateRespVO> convertList(List<WechatTemplateDO> list);\n\n    PageResult<WechatTemplateRespVO> convertPage(PageResult<WechatTemplateDO> page);\n\n    List<WechatTemplateExcelVO> convertList02(List<WechatTemplateDO> list);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-message/yshop-module-message-biz/src/main/java/co/yixiang/yshop/module/message/dal/dataobject/wechattemplate/WechatTemplateDO.java",
    "content": "package co.yixiang.yshop.module.message.dal.dataobject.wechattemplate;\n\nimport lombok.*;\nimport java.util.*;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\nimport com.baomidou.mybatisplus.annotation.*;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\n\n/**\n * 微信模板 DO\n *\n * @author yshop\n */\n@TableName(\"yshop_wechat_template\")\n@KeySequence(\"yshop_wechat_template_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class WechatTemplateDO extends BaseDO {\n\n    /**\n     * 模板id\n     */\n    @TableId\n    private Integer id;\n    /**\n     * 模板编号\n     */\n    private String tempkey;\n    /**\n     * 模板名\n     */\n    private String name;\n    /**\n     * 回复内容\n     */\n    private String content;\n    /**\n     * 模板ID\n     */\n    private String tempid;\n    /**\n     * 状态\n     */\n    private Byte status;\n    /**\n     * 类型：template:模板消息 subscribe:订阅消息\n     */\n    private String type;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-message/yshop-module-message-biz/src/main/java/co/yixiang/yshop/module/message/dal/mysql/wechattemplate/WechatTemplateMapper.java",
    "content": "package co.yixiang.yshop.module.message.dal.mysql.wechattemplate;\n\nimport java.util.*;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.module.message.dal.dataobject.wechattemplate.WechatTemplateDO;\nimport org.apache.ibatis.annotations.Mapper;\nimport co.yixiang.yshop.module.message.controller.admin.wechattemplate.vo.*;\n\n/**\n * 微信模板 Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface WechatTemplateMapper extends BaseMapperX<WechatTemplateDO> {\n\n    default PageResult<WechatTemplateDO> selectPage(WechatTemplatePageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<WechatTemplateDO>()\n                .eqIfPresent(WechatTemplateDO::getTempkey, reqVO.getTempkey())\n                .likeIfPresent(WechatTemplateDO::getName, reqVO.getName())\n                .eqIfPresent(WechatTemplateDO::getContent, reqVO.getContent())\n                .eqIfPresent(WechatTemplateDO::getTempid, reqVO.getTempid())\n                .betweenIfPresent(WechatTemplateDO::getCreateTime, reqVO.getCreateTime())\n                .eqIfPresent(WechatTemplateDO::getStatus, reqVO.getStatus())\n                .eqIfPresent(WechatTemplateDO::getType, reqVO.getType())\n                .orderByDesc(WechatTemplateDO::getId));\n    }\n\n    default List<WechatTemplateDO> selectList(WechatTemplateExportReqVO reqVO) {\n        return selectList(new LambdaQueryWrapperX<WechatTemplateDO>()\n                .eqIfPresent(WechatTemplateDO::getTempkey, reqVO.getTempkey())\n                .likeIfPresent(WechatTemplateDO::getName, reqVO.getName())\n                .eqIfPresent(WechatTemplateDO::getContent, reqVO.getContent())\n                .eqIfPresent(WechatTemplateDO::getTempid, reqVO.getTempid())\n                .betweenIfPresent(WechatTemplateDO::getCreateTime, reqVO.getCreateTime())\n                .eqIfPresent(WechatTemplateDO::getStatus, reqVO.getStatus())\n                .eqIfPresent(WechatTemplateDO::getType, reqVO.getType())\n                .orderByDesc(WechatTemplateDO::getId));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-message/yshop-module-message-biz/src/main/java/co/yixiang/yshop/module/message/mq/consumer/WeixinNoticeConsumer.java",
    "content": "package co.yixiang.yshop.module.message.mq.consumer;\n\n\nimport co.yixiang.yshop.framework.mq.redis.core.stream.AbstractRedisStreamMessageListener;\nimport co.yixiang.yshop.module.message.enums.WechatTempateEnum;\nimport co.yixiang.yshop.module.message.mq.message.WeixinNoticeMessage;\nimport co.yixiang.yshop.module.message.supply.WeiXinSubscribeService;\nimport co.yixiang.yshop.module.message.supply.WeixinTemplateService;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Component;\n\nimport jakarta.annotation.Resource;\n\n\n/**\n * 消息队列处理消息推送\n */\n@Component\n@Slf4j\npublic class WeixinNoticeConsumer extends AbstractRedisStreamMessageListener<WeixinNoticeMessage> {\n\n    @Resource\n    private WeiXinSubscribeService weiXinSubscribeService;\n    @Resource\n    private WeixinTemplateService weixinTemplateService;\n\n\n\n    @Override\n    public void onMessage(WeixinNoticeMessage message) {\n        log.info(\"[onMessage][消息内容({})]\", message);\n        //公众号\n        if(WechatTempateEnum.TEMPLATES.getValue().equals(message.getType())) {\n            if(WechatTempateEnum.PAY_SUCCESS.getValue().equals(message.getTempkey())){\n                weixinTemplateService.paySuccessNotice(message.getOrderId(),message.getPrice(),message.getUid());\n            }else if(WechatTempateEnum.DELIVERY_SUCCESS.getValue().equals(message.getTempkey())){\n                weixinTemplateService.deliverySuccessNotice(message.getOrderId(),message.getDeliveryName(),\n                        message.getDeliveryId(),message.getUid());\n            } else if(WechatTempateEnum.REFUND_SUCCESS.getValue().equals(message.getTempkey())){\n                weixinTemplateService.refundSuccessNotice(\"您的订单退款申请被通过，钱款将很快还至您的支付账户。\",\n                        message.getOrderId(),message.getDeliveryName(),message.getUid(),message.getTime());\n            }\n\n        }else if(WechatTempateEnum.SUBSCRIBE.getValue().equals(message.getType())){\n            //小程序\n            if(WechatTempateEnum.PAY_SUCCESS.getValue().equals(message.getTempkey())){\n                weiXinSubscribeService.paySuccessNotice(message.getNumberId().toString(),message.getProductName()\n                        ,message.getShopName(),message.getUid(),message.getId(),message.getOrderId());\n            }else if(WechatTempateEnum.DELIVERY_SUCCESS.getValue().equals(message.getTempkey())){\n                weiXinSubscribeService.deliverySuccessNotice(message.getOrderId(),message.getDeliveryName(),\n                        message.getDeliveryId(),message.getUid());\n            } else if(WechatTempateEnum.REFUND_SUCCESS.getValue().equals(message.getTempkey())){\n                weiXinSubscribeService.refundSuccessNotice(message.getOrderId(),message.getDeliveryName(),\n                        message.getUid(),message.getTime());\n            }\n        }\n\n\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-message/yshop-module-message-biz/src/main/java/co/yixiang/yshop/module/message/mq/message/WeixinNoticeMessage.java",
    "content": "package co.yixiang.yshop.module.message.mq.message;\n\n\nimport co.yixiang.yshop.framework.mq.redis.core.stream.AbstractRedisStreamMessage;\nimport lombok.Data;\n\n@Data\npublic class WeixinNoticeMessage extends AbstractRedisStreamMessage {\n\n    /**\n     * 模板编号\n     */\n    private String tempkey;\n\n    //消息类型\n    private String type;\n\n    //订单好\n    private String orderId;\n\n    //价格\n    private String price;\n\n    //用户\n    private Long uid;\n\n    //时间\n    private String time;\n\n    // 快递公司\n    private String deliveryName;\n\n    // 快递单号\n    private String deliveryId;\n\n    //订单ID\n    private Long id;\n\n    //取餐号\n    private Integer numberId;\n\n    //产品名称\n    private String productName;\n\n    //门店名称\n    private String shopName;\n\n\n\n    @Override\n    public String getStreamKey() {\n        return \"weixin.msg.notice\";\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-message/yshop-module-message-biz/src/main/java/co/yixiang/yshop/module/message/mq/producer/WeixinNoticeProducer.java",
    "content": "package co.yixiang.yshop.module.message.mq.producer;\n\nimport co.yixiang.yshop.framework.mq.redis.core.RedisMQTemplate;\nimport co.yixiang.yshop.module.message.mq.message.WeixinNoticeMessage;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Component;\n\nimport jakarta.annotation.Resource;\n\n@Slf4j\n@Component\npublic class WeixinNoticeProducer {\n    @Resource\n    private RedisMQTemplate redisMQTemplate;\n\n    /**\n     * 发送消息\n     * @param tempkey 订单编号\n     * @param type 类型\n     */\n    public void sendNoticeMessage( Long uid,String tempkey,String type,String orderId,String price,\n                                     String time,String deliveryName,String deliveryId,\n                                    Long id,Integer numberId,String productName,String shopName) {\n        WeixinNoticeMessage weixinNoticeMessage = new WeixinNoticeMessage()\n                .setTempkey(tempkey).setType(type).setOrderId(orderId).setUid(uid)\n                .setPrice(price).setDeliveryId(deliveryId).setTime(time).setDeliveryName(deliveryName)\n                .setId(id).setNumberId(numberId).setProductName(productName).setShopName(shopName);\n        redisMQTemplate.send(weixinNoticeMessage);\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-message/yshop-module-message-biz/src/main/java/co/yixiang/yshop/module/message/redismq/DelayedQueueListener.java",
    "content": "package co.yixiang.yshop.module.message.redismq;\n\n/**\n * 延时队列接口\n * @author hupeng\n * @date 2024.6.25\n */\npublic interface DelayedQueueListener<T> {\n    /**\n     * 是否启用\n     *\n     * @return boolean\n     */\n    default boolean isEnable() {\n        return true;\n    }\n\n    /**\n     * 队列键\n     *\n     * @return String\n     */\n    String delayedQueueKey();\n\n    /**\n     * 消费\n     *\n     * @param message Object\n     * @throws Exception Exception\n     */\n    void consume(T message) throws Exception;\n\n    /**\n     * 发生异常时最终处理\n     */\n    default void whenExceptionFinally() {\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-message/yshop-module-message-biz/src/main/java/co/yixiang/yshop/module/message/redismq/DelayedQueueListenerConfigurer.java",
    "content": "package co.yixiang.yshop.module.message.redismq;\n\nimport com.google.common.util.concurrent.ThreadFactoryBuilder;\nimport org.redisson.api.RedissonClient;\nimport org.springframework.beans.factory.DisposableBean;\nimport org.springframework.beans.factory.InitializingBean;\nimport org.springframework.stereotype.Component;\nimport org.springframework.util.Assert;\n\nimport java.util.List;\nimport java.util.concurrent.LinkedBlockingQueue;\nimport java.util.concurrent.ThreadFactory;\nimport java.util.concurrent.ThreadPoolExecutor;\nimport java.util.concurrent.TimeUnit;\n\n/**\n * 配置\n * @author hupeng\n * @date 2024.6.25\n */\n@Component\npublic class DelayedQueueListenerConfigurer implements InitializingBean, DisposableBean {\n    private ThreadPoolExecutor delayedThreadPoolExecutor;\n\n    private final List<DelayedQueueListener<?>> delayedQueueListenerList;\n\n    private final RedissonClient redissonClient;\n\n    public DelayedQueueListenerConfigurer(List<DelayedQueueListener<?>> delayedQueueListenerList, RedissonClient redissonClient) {\n        this.delayedQueueListenerList = delayedQueueListenerList;\n        this.redissonClient = redissonClient;\n    }\n\n    @Override\n    public void destroy() throws Exception {\n        if (delayedThreadPoolExecutor != null) {\n            delayedThreadPoolExecutor.shutdownNow();\n        }\n    }\n\n    @Override\n    public void afterPropertiesSet() throws Exception {\n        Assert.notEmpty(delayedQueueListenerList, \"delayedQueueListenerList must not be empty!\");\n\n        ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat(\"delayed-queue-pool-%d\").build();\n        int numberOfJob = delayedQueueListenerList.size();\n        delayedThreadPoolExecutor = new ThreadPoolExecutor(\n                numberOfJob,\n                numberOfJob,\n                0L,\n                TimeUnit.MILLISECONDS,\n                new LinkedBlockingQueue<>(numberOfJob),\n                namedThreadFactory\n        );\n        delayedQueueListenerList.forEach(delayedQueueListener -> delayedThreadPoolExecutor.execute(new DelayedQueuePollTask<>(redissonClient, delayedQueueListener)));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-message/yshop-module-message-biz/src/main/java/co/yixiang/yshop/module/message/redismq/DelayedQueuePollTask.java",
    "content": "package co.yixiang.yshop.module.message.redismq;\n\nimport lombok.extern.slf4j.Slf4j;\nimport org.redisson.api.RBlockingDeque;\nimport org.redisson.api.RedissonClient;\n\n/**\n * 延迟队列轮询任务类\n * @author hupeng\n * @date 2024.6.25\n */\n@Slf4j\npublic class DelayedQueuePollTask<T> implements Runnable{\n    private final RedissonClient redissonClient;\n\n    private final DelayedQueueListener<T> delayedQueueListener;\n\n    public DelayedQueuePollTask(RedissonClient redissonClient, DelayedQueueListener<T> delayedQueueListener) {\n        this.redissonClient = redissonClient;\n        this.delayedQueueListener = delayedQueueListener;\n    }\n\n    @Override\n    public void run() {\n        String threadName = \"delayed-queue-listener-\" + delayedQueueListener.getClass().getSimpleName();\n        Thread.currentThread().setName(threadName);\n        if (!delayedQueueListener.isEnable()) {\n            return;\n        }\n        RBlockingDeque<T> blockingDeque = redissonClient.getBlockingDeque(delayedQueueListener.delayedQueueKey());\n        // 解决消息丢失问题，发送subscribe命令订阅redis队列\n        redissonClient.getDelayedQueue(blockingDeque);\n        while (!Thread.currentThread().isInterrupted()) {\n            try {\n                T message = blockingDeque.take();\n                delayedQueueListener.consume(message);\n            } catch (InterruptedException e) {\n                Thread.currentThread().interrupt();\n            } catch (Exception e) {\n                log.error(e.getMessage(), e);\n            } finally {\n                delayedQueueListener.whenExceptionFinally();\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-message/yshop-module-message-biz/src/main/java/co/yixiang/yshop/module/message/redismq/msg/OrderMsg.java",
    "content": "package co.yixiang.yshop.module.message.redismq.msg;\n\nimport lombok.Builder;\nimport lombok.Data;\n\nimport java.io.Serializable;\n\n@Data\n@Builder\npublic class OrderMsg implements Serializable {\n    private String orderId;\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-message/yshop-module-message-biz/src/main/java/co/yixiang/yshop/module/message/service/wechattemplate/WechatTemplateService.java",
    "content": "package co.yixiang.yshop.module.message.service.wechattemplate;\n\nimport java.util.*;\nimport jakarta.validation.*;\n\nimport co.yixiang.yshop.module.member.dal.dataobject.user.MemberUserDO;\nimport co.yixiang.yshop.module.message.controller.admin.wechattemplate.vo.*;\nimport co.yixiang.yshop.module.message.dal.dataobject.wechattemplate.WechatTemplateDO;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport com.baomidou.mybatisplus.extension.service.IService;\n\n/**\n * 微信模板 Service 接口\n *\n * @author yshop\n */\npublic interface WechatTemplateService extends IService<WechatTemplateDO> {\n\n    /**\n     * 创建微信模板\n     *\n     * @param createReqVO 创建信息\n     * @return 编号\n     */\n    Integer createWechatTemplate(@Valid WechatTemplateCreateReqVO createReqVO);\n\n    /**\n     * 更新微信模板\n     *\n     * @param updateReqVO 更新信息\n     */\n    void updateWechatTemplate(@Valid WechatTemplateUpdateReqVO updateReqVO);\n\n    /**\n     * 删除微信模板\n     *\n     * @param id 编号\n     */\n    void deleteWechatTemplate(Integer id);\n\n    /**\n     * 获得微信模板\n     *\n     * @param id 编号\n     * @return 微信模板\n     */\n    WechatTemplateDO getWechatTemplate(Integer id);\n\n    /**\n     * 获得微信模板列表\n     *\n     * @param ids 编号\n     * @return 微信模板列表\n     */\n    List<WechatTemplateDO> getWechatTemplateList(Collection<Integer> ids);\n\n    /**\n     * 获得微信模板分页\n     *\n     * @param pageReqVO 分页查询\n     * @return 微信模板分页\n     */\n    PageResult<WechatTemplateDO> getWechatTemplatePage(WechatTemplatePageReqVO pageReqVO);\n\n    /**\n     * 获得微信模板列表, 用于 Excel 导出\n     *\n     * @param exportReqVO 查询条件\n     * @return 微信模板列表\n     */\n    List<WechatTemplateDO> getWechatTemplateList(WechatTemplateExportReqVO exportReqVO);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-message/yshop-module-message-biz/src/main/java/co/yixiang/yshop/module/message/service/wechattemplate/WechatTemplateServiceImpl.java",
    "content": "package co.yixiang.yshop.module.message.service.wechattemplate;\n\nimport co.yixiang.yshop.module.member.dal.dataobject.user.MemberUserDO;\nimport co.yixiang.yshop.module.member.dal.mysql.user.MemberUserMapper;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport org.springframework.stereotype.Service;\nimport jakarta.annotation.Resource;\nimport org.springframework.validation.annotation.Validated;\n\nimport java.util.*;\nimport co.yixiang.yshop.module.message.controller.admin.wechattemplate.vo.*;\nimport co.yixiang.yshop.module.message.dal.dataobject.wechattemplate.WechatTemplateDO;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\nimport co.yixiang.yshop.module.message.convert.wechattemplate.WechatTemplateConvert;\nimport co.yixiang.yshop.module.message.dal.mysql.wechattemplate.WechatTemplateMapper;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.message.enums.ErrorCodeConstants.*;\n\n/**\n * 微信模板 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class WechatTemplateServiceImpl extends ServiceImpl<WechatTemplateMapper, WechatTemplateDO> implements WechatTemplateService {\n\n    @Resource\n    private WechatTemplateMapper wechatTemplateMapper;\n\n    @Override\n    public Integer createWechatTemplate(WechatTemplateCreateReqVO createReqVO) {\n        // 插入\n        WechatTemplateDO wechatTemplate = WechatTemplateConvert.INSTANCE.convert(createReqVO);\n        wechatTemplateMapper.insert(wechatTemplate);\n        // 返回\n        return wechatTemplate.getId();\n    }\n\n    @Override\n    public void updateWechatTemplate(WechatTemplateUpdateReqVO updateReqVO) {\n        // 校验存在\n        validateWechatTemplateExists(updateReqVO.getId());\n        // 更新\n        WechatTemplateDO updateObj = WechatTemplateConvert.INSTANCE.convert(updateReqVO);\n        wechatTemplateMapper.updateById(updateObj);\n    }\n\n    @Override\n    public void deleteWechatTemplate(Integer id) {\n        // 校验存在\n        validateWechatTemplateExists(id);\n        // 删除\n        wechatTemplateMapper.deleteById(id);\n    }\n\n    private void validateWechatTemplateExists(Integer id) {\n        if (wechatTemplateMapper.selectById(id) == null) {\n            throw exception(WECHAT_TEMPLATE_NOT_EXISTS);\n        }\n    }\n\n    @Override\n    public WechatTemplateDO getWechatTemplate(Integer id) {\n        return wechatTemplateMapper.selectById(id);\n    }\n\n    @Override\n    public List<WechatTemplateDO> getWechatTemplateList(Collection<Integer> ids) {\n        return wechatTemplateMapper.selectBatchIds(ids);\n    }\n\n    @Override\n    public PageResult<WechatTemplateDO> getWechatTemplatePage(WechatTemplatePageReqVO pageReqVO) {\n        return wechatTemplateMapper.selectPage(pageReqVO);\n    }\n\n    @Override\n    public List<WechatTemplateDO> getWechatTemplateList(WechatTemplateExportReqVO exportReqVO) {\n        return wechatTemplateMapper.selectList(exportReqVO);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-message/yshop-module-message-biz/src/main/java/co/yixiang/yshop/module/message/supply/WeiXinSubscribeService.java",
    "content": "package co.yixiang.yshop.module.message.supply;\r\n\r\nimport cn.binarywang.wx.miniapp.api.WxMaService;\r\nimport cn.binarywang.wx.miniapp.bean.WxMaSubscribeMessage;\r\nimport cn.hutool.core.util.ObjUtil;\r\nimport cn.hutool.core.util.StrUtil;\r\nimport co.yixiang.yshop.framework.common.constant.ShopConstants;\r\nimport co.yixiang.yshop.framework.common.enums.ShopCommonEnum;\r\nimport co.yixiang.yshop.framework.common.exception.ErrorCode;\r\nimport co.yixiang.yshop.framework.tenant.core.aop.TenantIgnore;\r\nimport co.yixiang.yshop.framework.tenant.core.util.TenantUtils;\r\nimport co.yixiang.yshop.module.member.api.user.dto.WechatUserDto;\r\nimport co.yixiang.yshop.module.member.dal.dataobject.user.MemberUserDO;\r\nimport co.yixiang.yshop.module.member.service.user.MemberUserService;\r\nimport co.yixiang.yshop.module.message.dal.dataobject.wechattemplate.WechatTemplateDO;\r\nimport co.yixiang.yshop.module.message.enums.WechatTempateEnum;\r\nimport co.yixiang.yshop.module.message.service.wechattemplate.WechatTemplateService;\r\nimport me.chanjar.weixin.common.error.WxErrorException;\r\nimport org.springframework.stereotype.Service;\r\n\r\nimport jakarta.annotation.Resource;\r\nimport java.text.SimpleDateFormat;\r\nimport java.util.Date;\r\nimport java.util.HashMap;\r\nimport java.util.Map;\r\n\r\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\r\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.USER_USERNAME_EXISTS;\r\n\r\n;\r\n\r\n/**\r\n * 小程序订阅消息通知\r\n */\r\n@Service\r\npublic class WeiXinSubscribeService {\r\n\r\n    @Resource\r\n    private MemberUserService userService;\r\n    @Resource\r\n    private WechatTemplateService wechatTemplateService;\r\n    @Resource\r\n    private WxMaService wxMaService;\r\n\r\n\r\n    /**\r\n     * 充值成功通知\r\n     * @param time 时间\r\n     * @param price 金额\r\n     * @param uid uid\r\n     */\r\n    public void rechargeSuccessNotice(String time,String price,Long uid){\r\n        String openid = this.getUserOpenid(uid);\r\n\r\n        if(StrUtil.isBlank(openid)) {\r\n            return;\r\n        }\r\n\r\n        Map<String,String> map = new HashMap<>();\r\n        map.put(\"first\",\"您的账户金币发生变动，详情如下：\");\r\n        map.put(\"keyword1\",\"充值\");\r\n        map.put(\"keyword2\",time);\r\n        map.put(\"keyword3\",price);\r\n        map.put(\"remark\", ShopConstants.YSHOP_WECHAT_PUSH_REMARK);\r\n        String tempId = this.getTempId(WechatTempateEnum.RECHARGE_SUCCESS.getValue());\r\n        if(StrUtil.isNotBlank(tempId)) {\r\n            this.sendSubscribeMsg( openid, tempId, \"/user/account\",map);\r\n        }\r\n    }\r\n\r\n\r\n    /**\r\n     * 支付成功通知\r\n     * @param numberId 取餐号\r\n     * @param productName 商品名称\r\n     * @param shopName 门店名称\r\n     * @param uid uid\r\n     * @param id 订单ID\r\n     */\r\n    public void paySuccessNotice(String numberId,String productName,String shopName,Long uid,Long id,String orderId){\r\n        TenantUtils.executeIgnore(() -> {\r\n            String openid = this.getUserOpenid(uid);\r\n            if(StrUtil.isBlank(openid)) {\r\n                return;\r\n            }\r\n            SimpleDateFormat simpleDateFormat = new SimpleDateFormat(\"yyyy年MM月dd日 HH:mm:ss\");\r\n            Map<String,String> map = new HashMap<>();\r\n            //取餐号\r\n            map.put(\"character_string1\",numberId);\r\n            //商品名\r\n            map.put(\"thing2\", productName);\r\n            map.put(\"time3\",simpleDateFormat.format(new Date()));\r\n            //下单门店\r\n            map.put(\"thing4\",shopName);\r\n            map.put(\"thing5\",\"意向点餐系统为您服务！\");\r\n            String tempId = this.getTempId(WechatTempateEnum.PAY_SUCCESS.getValue());\r\n            if(StrUtil.isNotBlank(tempId)) {\r\n                this.sendSubscribeMsg( openid,tempId, \"/pages/components/pages/orders/detail?id=\"+orderId,map);\r\n            }\r\n        });\r\n\r\n    }\r\n\r\n    /**\r\n     * 退款成功通知\r\n     * @param orderId 订单号\r\n     * @param price 金额\r\n     * @param uid uid\r\n     * @param time 时间\r\n     */\r\n    public void refundSuccessNotice(String orderId,String price,Long uid,String time){\r\n\r\n        TenantUtils.executeIgnore(() -> {\r\n            String openid = this.getUserOpenid(uid);\r\n\r\n            if(StrUtil.isBlank(openid)) {\r\n                return;\r\n            }\r\n\r\n            Map<String,String> map = new HashMap<>();\r\n            map.put(\"first\",\"您的订单退款申请被通过，钱款将很快还至您的支付账户。\");\r\n            //订单号\r\n            map.put(\"keyword1\",orderId);\r\n            map.put(\"keyword2\",price);\r\n            map.put(\"keyword3\", time);\r\n            map.put(\"remark\",ShopConstants.YSHOP_WECHAT_PUSH_REMARK);\r\n            String tempId = this.getTempId(WechatTempateEnum.REFUND_SUCCESS.getValue());\r\n            if(StrUtil.isNotBlank(tempId)) {\r\n                this.sendSubscribeMsg( openid,tempId, \"/order/detail/\"+orderId,map);\r\n            }\r\n        });\r\n\r\n    }\r\n\r\n    /**\r\n     * 发货成功通知\r\n     * @param orderId 单号\r\n     * @param deliveryName 快递公司\r\n     * @param deliveryId 快递单号\r\n     * @param uid uid\r\n     */\r\n    public void deliverySuccessNotice(String orderId,String deliveryName,\r\n                                      String deliveryId,Long uid){\r\n\r\n        TenantUtils.executeIgnore(() -> {\r\n            String openid = this.getUserOpenid(uid);\r\n\r\n            if(StrUtil.isEmpty(openid)) {\r\n                return;\r\n            }\r\n\r\n            Map<String,String> map = new HashMap<>();\r\n            map.put(\"first\",\"亲，宝贝已经启程了，好想快点来到你身边。\");\r\n            map.put(\"keyword2\",deliveryName);\r\n            map.put(\"keyword1\",orderId);\r\n            map.put(\"keyword3\",deliveryId);\r\n            map.put(\"remark\",ShopConstants.YSHOP_WECHAT_PUSH_REMARK);\r\n            String tempId = this.getTempId(WechatTempateEnum.DELIVERY_SUCCESS.getValue());\r\n            if(StrUtil.isNotBlank(tempId)) {\r\n                this.sendSubscribeMsg( openid,tempId, \"/order/detail/\"+orderId,map);\r\n            }\r\n        });\r\n\r\n    }\r\n\r\n\r\n    /**\r\n     * 构建小程序一次性订阅消息\r\n     * @param openId 单号\r\n     * @param templateId 模板id\r\n     * @param page 跳转页面\r\n     * @param map map内容\r\n     * @return String\r\n     */\r\n    private void sendSubscribeMsg(String openId, String templateId, String page, Map<String,String> map){\r\n        WxMaSubscribeMessage wxMaSubscribeMessage = WxMaSubscribeMessage.builder()\r\n                .toUser(openId)\r\n                .templateId(templateId)\r\n                .page(page)\r\n                .build();\r\n        map.forEach( (k,v)-> { wxMaSubscribeMessage.addData(new WxMaSubscribeMessage.MsgData(k, v));} );\r\n       // WxMaService wxMaService = WxMaConfiguration.getWxMaService();\r\n        try {\r\n            wxMaService.getMsgService().sendSubscribeMsg(wxMaSubscribeMessage);\r\n        } catch (WxErrorException e) {\r\n            e.printStackTrace();\r\n        }\r\n    }\r\n\r\n    /**\r\n     * 获取模板消息id\r\n     * @param key 模板key\r\n     * @return string\r\n     */\r\n    @TenantIgnore\r\n    private String getTempId(String key){\r\n        WechatTemplateDO yxWechatTemplate = wechatTemplateService.lambdaQuery()\r\n                .eq(WechatTemplateDO::getType,\"subscribe\")\r\n                .eq(WechatTemplateDO::getTempkey,key)\r\n                .one();\r\n        if (yxWechatTemplate == null) {\r\n            throw exception(new ErrorCode(9999999,\"请后台配置key:\" + key + \"订阅消息id\"));\r\n        }\r\n        if(ShopCommonEnum.IS_STATUS_0.getValue().equals(yxWechatTemplate.getStatus())){\r\n            return \"\";\r\n        }\r\n        return yxWechatTemplate.getTempid();\r\n    }\r\n\r\n\r\n    /**\r\n     * 获取openid\r\n     * @param uid uid\r\n     * @return String\r\n     */\r\n    @TenantIgnore\r\n    private String getUserOpenid(Long uid){\r\n        MemberUserDO yxUser = userService.getById(uid);\r\n        if(yxUser == null) {\r\n            return \"\";\r\n        }\r\n        if(StrUtil.isBlank(yxUser.getRoutineOpenid())) {\r\n            return \"\";\r\n        }\r\n        return yxUser.getRoutineOpenid();\r\n\r\n    }\r\n}\r\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-message/yshop-module-message-biz/src/main/java/co/yixiang/yshop/module/message/supply/WeixinTemplateService.java",
    "content": "/**\n * Copyright (C) 2018-2022\n * All rights reserved, Designed By www.yixiang.co\n * 注意：\n * 本软件为www.yixiang.co开发研制，未经购买不得使用\n * 购买后可获得全部源代码（禁止转卖、分享、上传到码云、github等开源平台）\n * 一经发现盗用、分享等行为，将追究法律责任，后果自负\n */\npackage co.yixiang.yshop.module.message.supply;\n\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.constant.ShopConstants;\nimport co.yixiang.yshop.framework.common.enums.ShopCommonEnum;\nimport co.yixiang.yshop.framework.common.exception.ErrorCode;\nimport co.yixiang.yshop.module.member.api.user.dto.WechatUserDto;\nimport co.yixiang.yshop.module.member.dal.dataobject.user.MemberUserDO;\nimport co.yixiang.yshop.module.member.service.user.MemberUserService;\nimport co.yixiang.yshop.module.message.dal.dataobject.wechattemplate.WechatTemplateDO;\nimport co.yixiang.yshop.module.message.enums.WechatTempateEnum;\nimport co.yixiang.yshop.module.message.service.wechattemplate.WechatTemplateService;\nimport lombok.extern.slf4j.Slf4j;\nimport me.chanjar.weixin.common.error.WxErrorException;\nimport me.chanjar.weixin.mp.api.WxMpService;\nimport me.chanjar.weixin.mp.bean.template.WxMpTemplateData;\nimport me.chanjar.weixin.mp.bean.template.WxMpTemplateMessage;\nimport org.springframework.stereotype.Service;\n\nimport jakarta.annotation.Resource;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\n\n/**\n * @ClassName 微信公众号模板通知\n * @Author hupeng <610796224@qq.com>\n * @Date 2020/6/27\n **/\n@Slf4j\n@Service\npublic class WeixinTemplateService {\n\n\n    @Resource\n    private MemberUserService userService;\n    @Resource\n    private WechatTemplateService wechatTemplateService;\n    @Resource\n    private WxMpService wxMpService;\n\n\n\n\n    /**\n     * 充值成功通知\n     * @param time 时间\n     * @param price 金额\n     * @param uid uid\n     */\n    public void rechargeSuccessNotice(String time,String price,Long uid){\n        String openid = this.getUserOpenid(uid);\n\n        if(StrUtil.isBlank(openid)) {\n            return;\n        }\n\n        Map<String,String> map = new HashMap<>();\n        map.put(\"first\",\"您的账户金币发生变动，详情如下：\");\n        map.put(\"keyword1\",\"充值\");\n        map.put(\"keyword2\",time);\n        map.put(\"keyword3\",price);\n        map.put(\"remark\", ShopConstants.YSHOP_WECHAT_PUSH_REMARK);\n        String tempId = this.getTempId(WechatTempateEnum.RECHARGE_SUCCESS.getValue());\n        if(StrUtil.isNotBlank(tempId)) {\n            this.sendWxMpTemplateMessage( openid, tempId, \"https://www.yixiang.co\",map);\n        }\n    }\n\n\n    /**\n     * 支付成功通知\n     * @param orderId 订单号\n     * @param price 金额\n     * @param uid uid\n     */\n    public void paySuccessNotice(String orderId,String price,Long uid){\n\n        String openid = this.getUserOpenid(uid);\n\n        if(StrUtil.isBlank(openid)) {\n            return;\n        }\n\n        Map<String,String> map = new HashMap<>();\n        map.put(\"first\",\"您的订单已支付成功，我们会尽快为您发货。\");\n        //订单号\n        map.put(\"keyword1\",orderId);\n        map.put(\"keyword2\",price);\n        map.put(\"remark\",ShopConstants.YSHOP_WECHAT_PUSH_REMARK);\n        String tempId = this.getTempId(WechatTempateEnum.PAY_SUCCESS.getValue());\n        if(StrUtil.isNotBlank(tempId)) {\n            this.sendWxMpTemplateMessage( openid,tempId, \"https://www.yixiang.co\",map);\n        }\n    }\n\n\n\n\n    /**\n     * 退款成功通知\n     * @param orderId 订单号\n     * @param price 金额\n     * @param uid uid\n     * @param time 时间\n     */\n    public void refundSuccessNotice(String title,String orderId,String price,Long uid,String time){\n\n        String openid = this.getUserOpenid(uid);\n\n        if(StrUtil.isBlank(openid)) {\n            return;\n        }\n\n        Map<String,String> map = new HashMap<>();\n        map.put(\"first\",title);\n        //订单号\n        map.put(\"keyword1\",orderId);\n        map.put(\"keyword2\",price);\n        map.put(\"keyword3\", time);\n        map.put(\"remark\",ShopConstants.YSHOP_WECHAT_PUSH_REMARK);\n        String tempId = this.getTempId(WechatTempateEnum.REFUND_SUCCESS.getValue());\n        if(StrUtil.isNotBlank(tempId)) {\n            this.sendWxMpTemplateMessage( openid,tempId, \"https://www.yixiang.co\",map);\n        }\n    }\n\n\n    /**\n     * 发货成功通知\n     * @param orderId 单号\n     * @param deliveryName 快递公司\n     * @param deliveryId 快递单号\n     * @param uid uid\n     */\n    public void deliverySuccessNotice(String orderId,String deliveryName,\n                                      String deliveryId,Long uid){\n\n        String openid = this.getUserOpenid(uid);\n\n        if(StrUtil.isEmpty(openid)) {\n            return;\n        }\n\n        Map<String,String> map = new HashMap<>();\n        map.put(\"first\",\"亲，宝贝已经启程了，好想快点来到你身边。\");\n        map.put(\"keyword2\",deliveryName);\n        map.put(\"keyword1\",orderId);\n        map.put(\"keyword3\",deliveryId);\n        map.put(\"remark\",ShopConstants.YSHOP_WECHAT_PUSH_REMARK);\n        String tempId = this.getTempId(WechatTempateEnum.DELIVERY_SUCCESS.getValue());\n        if(StrUtil.isNotBlank(tempId)) {\n            this.sendWxMpTemplateMessage( openid,tempId, \"https://www.yixiang.co\",map);\n        }\n    }\n\n\n    /**\n     * 构建微信模板通知\n     * @param openId 单号\n     * @param templateId 模板id\n     * @param url 跳转url\n     * @param map map内容\n     * @return String\n     */\n    private String sendWxMpTemplateMessage(String openId, String templateId, String url, Map<String,String> map){\n        WxMpTemplateMessage templateMessage = WxMpTemplateMessage.builder()\n                .toUser(openId)\n                .templateId(templateId)\n                .url(url)\n                .build();\n        map.forEach( (k,v)-> { templateMessage.addData(new WxMpTemplateData(k, v, \"#000000\"));} );\n        String msgId = null;\n        try {\n            msgId =   wxMpService.getTemplateMsgService().sendTemplateMsg(templateMessage);\n        } catch (WxErrorException e) {\n            e.printStackTrace();\n        }\n        return msgId;\n    }\n\n\n\n\n\n    /**\n     * 获取模板消息id\n     * @param key 模板key\n     * @return string\n     */\n    private String getTempId(String key){\n        WechatTemplateDO yxWechatTemplate = wechatTemplateService.lambdaQuery()\n                .eq(WechatTemplateDO::getType,\"template\")\n                .eq(WechatTemplateDO::getTempkey,key)\n                .one();\n        if (yxWechatTemplate == null) {\n            throw exception(new ErrorCode(9999999,\"请后台配置key:\" + key + \"订阅消息id\"));\n        }\n\n        if(ShopCommonEnum.IS_STATUS_0.getValue().equals(yxWechatTemplate.getStatus())){\n            return \"\";\n        }\n\n        return yxWechatTemplate.getTempid();\n    }\n\n\n\n    /**\n     * 获取openid\n     * @param uid uid\n     * @return String\n     */\n    private String getUserOpenid(Long uid){\n        MemberUserDO yxUser = userService.getById(uid);\n        if(yxUser == null) {\n            return \"\";\n        }\n\n        WechatUserDto wechatUserDto = yxUser.getWxProfile();\n        if(wechatUserDto == null) {\n            return \"\";\n        }\n        if(StrUtil.isBlank(wechatUserDto.getOpenid())) {\n            return \"\";\n        }\n        return wechatUserDto.getOpenid();\n\n    }\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-message/yshop-module-message-biz/src/main/resources/mapper/wechattemplate/WechatTemplateMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"co.yixiang.yshop.module.message.dal.mysql.wechattemplate.WechatTemplateMapper\">\n\n    <!--\n        一般情况下，尽可能使用 Mapper 进行 CRUD 增删改查即可。\n        无法满足的场景，例如说多表关联查询，才使用 XML 编写 SQL。\n        代码生成器暂时只生成 Mapper XML 文件本身，更多推荐 MybatisX 快速开发插件来生成查询。\n        文档可见：https://www.yixiang.co/MyBatis/x-plugins/\n     -->\n\n</mapper>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>yshop</artifactId>\n        <groupId>co.yixiang.boot</groupId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>yshop-module-mp</artifactId>\n    <packaging>pom</packaging>\n\n    <description>\n        wechat 模块，主要实现微信平台的相关业务。\n        例如：微信公众号、企业微信 SCRM 等\n    </description>\n    <modules>\n        <module>yshop-module-mp-api</module>\n        <module>yshop-module-mp-biz</module>\n    </modules>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-api/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>yshop-module-mp</artifactId>\n        <groupId>co.yixiang.boot</groupId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>yshop-module-mp-api</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>\n        mp 模块 API，暴露给其它模块调用\n    </description>\n\n    <dependencies>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-common</artifactId>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-api/src/main/java/co/yixiang/yshop/module/mp/enums/ErrorCodeConstants.java",
    "content": "package co.yixiang.yshop.module.mp.enums;\n\nimport co.yixiang.yshop.framework.common.exception.ErrorCode;\n\n/**\n * Mp 错误码枚举类\n *\n * mp 系统，使用 1-006-000-000 段\n */\npublic interface ErrorCodeConstants {\n\n    // ========== 公众号账号 1-006-000-000 ============\n    ErrorCode ACCOUNT_NOT_EXISTS = new ErrorCode(1_006_000_000, \"公众号账号不存在\");\n    ErrorCode ACCOUNT_GENERATE_QR_CODE_FAIL = new ErrorCode(1_006_000_001, \"生成公众号二维码失败，原因：{}\");\n    ErrorCode ACCOUNT_CLEAR_QUOTA_FAIL = new ErrorCode(1_006_000_002, \"清空公众号的 API 配额失败，原因：{}\");\n\n    // ========== 公众号统计 1-006-001-000 ============\n    ErrorCode STATISTICS_GET_USER_SUMMARY_FAIL = new ErrorCode(1_006_001_000, \"获取粉丝增减数据失败，原因：{}\");\n    ErrorCode STATISTICS_GET_USER_CUMULATE_FAIL = new ErrorCode(1_006_001_001, \"获得粉丝累计数据失败，原因：{}\");\n    ErrorCode STATISTICS_GET_UPSTREAM_MESSAGE_FAIL = new ErrorCode(1_006_001_002, \"获得消息发送概况数据失败，原因：{}\");\n    ErrorCode STATISTICS_GET_INTERFACE_SUMMARY_FAIL = new ErrorCode(1_006_001_003, \"获得接口分析数据失败，原因：{}\");\n\n    // ========== 公众号标签 1-006-002-000 ============\n    ErrorCode TAG_NOT_EXISTS = new ErrorCode(1_006_002_000, \"标签不存在\");\n    ErrorCode TAG_CREATE_FAIL = new ErrorCode(1_006_002_001, \"创建标签失败，原因：{}\");\n    ErrorCode TAG_UPDATE_FAIL = new ErrorCode(1_006_002_002, \"更新标签失败，原因：{}\");\n    ErrorCode TAG_DELETE_FAIL = new ErrorCode(1_006_002_003, \"删除标签失败，原因：{}\");\n    ErrorCode TAG_GET_FAIL = new ErrorCode(1_006_002_004, \"获得标签失败，原因：{}\");\n\n    // ========== 公众号粉丝 1-006-003-000 ============\n    ErrorCode USER_NOT_EXISTS = new ErrorCode(1_006_003_000, \"粉丝不存在\");\n    ErrorCode USER_UPDATE_TAG_FAIL = new ErrorCode(1_006_003_001, \"更新粉丝标签失败，原因：{}\");\n\n    // ========== 公众号素材 1-006-004-000 ============\n    ErrorCode MATERIAL_NOT_EXISTS = new ErrorCode(1_006_004_000, \"素材不存在\");\n    ErrorCode MATERIAL_UPLOAD_FAIL = new ErrorCode(1_006_004_001, \"上传素材失败，原因：{}\");\n    ErrorCode MATERIAL_IMAGE_UPLOAD_FAIL = new ErrorCode(1_006_004_002, \"上传图片失败，原因：{}\");\n    ErrorCode MATERIAL_DELETE_FAIL = new ErrorCode(1_006_004_003, \"删除素材失败，原因：{}\");\n\n    // ========== 公众号消息 1-006-005-000 ============\n    ErrorCode MESSAGE_SEND_FAIL = new ErrorCode(1_006_005_000, \"发送消息失败，原因：{}\");\n\n    // ========== 公众号发布能力 1-006-006-000 ============\n    ErrorCode FREE_PUBLISH_LIST_FAIL = new ErrorCode(1_006_006_000, \"获得已成功发布列表失败，原因：{}\");\n    ErrorCode FREE_PUBLISH_SUBMIT_FAIL = new ErrorCode(1_006_006_001, \"提交发布失败，原因：{}\");\n    ErrorCode FREE_PUBLISH_DELETE_FAIL = new ErrorCode(1_006_006_002, \"删除发布失败，原因：{}\");\n\n    // ========== 公众号草稿 1-006-007-000 ============\n    ErrorCode DRAFT_LIST_FAIL = new ErrorCode(1_006_007_000, \"获得草稿列表失败，原因：{}\");\n    ErrorCode DRAFT_CREATE_FAIL = new ErrorCode(1_006_007_001, \"创建草稿失败，原因：{}\");\n    ErrorCode DRAFT_UPDATE_FAIL = new ErrorCode(1_006_007_002, \"更新草稿失败，原因：{}\");\n    ErrorCode DRAFT_DELETE_FAIL = new ErrorCode(1_006_007_003, \"删除草稿失败，原因：{}\");\n\n    // ========== 公众号菜单 1-006-008-000 ============\n    ErrorCode MENU_SAVE_FAIL = new ErrorCode(1_006_008_000, \"创建菜单失败，原因：{}\");\n    ErrorCode MENU_DELETE_FAIL = new ErrorCode(1_006_008_001, \"删除菜单失败，原因：{}\");\n\n    // ========== 公众号自动回复 1-006-009-000 ============\n    ErrorCode AUTO_REPLY_NOT_EXISTS = new ErrorCode(1_006_009_000, \"自动回复不存在\");\n    ErrorCode AUTO_REPLY_ADD_SUBSCRIBE_FAIL_EXISTS = new ErrorCode(1_006_009_001, \"操作失败，原因：已存在关注时的回复\");\n    ErrorCode AUTO_REPLY_ADD_MESSAGE_FAIL_EXISTS = new ErrorCode(1_006_009_002, \"操作失败，原因：已存在该消息类型的回复\");\n    ErrorCode AUTO_REPLY_ADD_KEYWORD_FAIL_EXISTS = new ErrorCode(1_006_009_003, \"操作失败，原因：已关在该关键字的回复\");\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-api/src/main/java/co/yixiang/yshop/module/mp/enums/MpAccountEnum.java",
    "content": "package co.yixiang.yshop.module.mp.enums;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * 微信账户枚举\n * @author yshop\n */\n@Getter\n@AllArgsConstructor\npublic enum MpAccountEnum {\n    IS_MAIN_0(0, \" 不是主账户\"),\n    IS_MAIN_1(1, \"是主账户\"),\n    IS_MINI_0(0, \"不是小程序\"),\n    IS_MINI_1(1, \"是小程序\");\n\n    /**\n     * 匹配\n     */\n    private final Integer value;\n    /**\n     * 匹配的名字\n     */\n    private final String name;\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-api/src/main/java/co/yixiang/yshop/module/mp/enums/message/MpAutoReplyMatchEnum.java",
    "content": "package co.yixiang.yshop.module.mp.enums.message;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * 公众号消息自动回复的匹配模式\n *\n * @author yshop\n */\n@Getter\n@AllArgsConstructor\npublic enum MpAutoReplyMatchEnum {\n\n    ALL(1, \"完全匹配\"),\n    LIKE(2, \"半匹配\"),\n    ;\n\n    /**\n     * 匹配\n     */\n    private final Integer match;\n    /**\n     * 匹配的名字\n     */\n    private final String name;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-api/src/main/java/co/yixiang/yshop/module/mp/enums/message/MpAutoReplyTypeEnum.java",
    "content": "package co.yixiang.yshop.module.mp.enums.message;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * 公众号消息自动回复的类型\n *\n * @author yshop\n */\n@Getter\n@AllArgsConstructor\npublic enum MpAutoReplyTypeEnum {\n\n    SUBSCRIBE(1, \"关注时回复\"),\n    MESSAGE(2, \"收到消息回复\"),\n    KEYWORD(3, \"关键词回复\"),\n    ;\n\n    /**\n     * 来源\n     */\n    private final Integer type;\n    /**\n     * 类型的名字\n     */\n    private final String name;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-api/src/main/java/co/yixiang/yshop/module/mp/enums/message/MpMessageSendFromEnum.java",
    "content": "package co.yixiang.yshop.module.mp.enums.message;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * 微信公众号消息的发送来源\n *\n * @author yshop\n */\n@Getter\n@AllArgsConstructor\npublic enum MpMessageSendFromEnum {\n\n    USER_TO_MP(1, \"粉丝发送给公众号\"),\n    MP_TO_USER(2, \"公众号发给粉丝\"),\n    ;\n\n    /**\n     * 来源\n     */\n    private final Integer from;\n    /**\n     * 来源的名字\n     */\n    private final String name;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>yshop-module-mp</artifactId>\n        <groupId>co.yixiang.boot</groupId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>yshop-module-mp-biz</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>\n        mp 模块，我们放微信微信公众号。\n        例如说：提供微信公众号的账号、菜单、粉丝、标签、消息、自动回复、素材、模板通知、运营数据等功能\n    </description>\n\n    <dependencies>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-mp-api</artifactId>\n            <version>${revision}</version>\n        </dependency>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-system-api</artifactId>\n            <version>${revision}</version>\n        </dependency>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-infra-api</artifactId>\n            <version>${revision}</version>\n        </dependency>\n\n        <!-- 业务组件 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-biz-tenant</artifactId>\n        </dependency>\n\n        <!-- Web 相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-security</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-validation</artifactId>\n        </dependency>\n\n        <!-- DB 相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-mybatis</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-redis</artifactId>\n        </dependency>\n\n        <!-- 消息队列相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-mq</artifactId>\n        </dependency>\n\n        <!-- Test 测试相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n\n        <!-- 工具类相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-excel</artifactId>\n        </dependency>\n\n        <!-- 三方云服务相关 -->\n        <dependency>\n            <groupId>com.github.binarywang</groupId>\n            <artifactId>wx-java-mp-spring-boot-starter</artifactId> <!-- 微信登录（公众号） -->\n        </dependency>\n        <dependency>\n            <groupId>com.github.binarywang</groupId>\n            <artifactId>wx-java-miniapp-spring-boot-starter</artifactId>  <!-- 微信登录（小程序） -->\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/account/MaAccountController.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.account;\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.mp.controller.admin.account.vo.MpAccountCreateReqVO;\nimport co.yixiang.yshop.module.mp.controller.admin.account.vo.MpAccountPageReqVO;\nimport co.yixiang.yshop.module.mp.controller.admin.account.vo.MpAccountRespVO;\nimport co.yixiang.yshop.module.mp.controller.admin.account.vo.MpAccountUpdateReqVO;\nimport co.yixiang.yshop.module.mp.convert.account.MpAccountConvert;\nimport co.yixiang.yshop.module.mp.dal.dataobject.account.MpAccountDO;\nimport co.yixiang.yshop.module.mp.enums.MpAccountEnum;\nimport co.yixiang.yshop.module.mp.service.account.MpAccountService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Valid;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 小程序账号\")\n@RestController\n@RequestMapping(\"/ma/account\")\n@Validated\npublic class MaAccountController {\n\n    @Resource\n    private MpAccountService mpAccountService;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建小程序账号\")\n    @PreAuthorize(\"@ss.hasPermission('ma:account:create')\")\n    public CommonResult<Long> createAccount(@Valid @RequestBody MpAccountCreateReqVO createReqVO) {\n        createReqVO.setIsMiapp(MpAccountEnum.IS_MINI_1.getValue());\n        return success(mpAccountService.createAccount(createReqVO));\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"更新小程序账号\")\n    @PreAuthorize(\"@ss.hasPermission('ma:account:update')\")\n    public CommonResult<Boolean> updateAccount(@Valid @RequestBody MpAccountUpdateReqVO updateReqVO) {\n        mpAccountService.updateAccount(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除小程序账号\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('ma:account:delete')\")\n    public CommonResult<Boolean> deleteAccount(@RequestParam(\"id\") Long id) {\n        mpAccountService.deleteAccount(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得小程序账号\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('ma:account:query')\")\n    public CommonResult<MpAccountRespVO> getAccount(@RequestParam(\"id\") Long id) {\n        MpAccountDO wxAccount = mpAccountService.getAccount(id);\n        return success(MpAccountConvert.INSTANCE.convert(wxAccount));\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得小程序账号分页\")\n    @PreAuthorize(\"@ss.hasPermission('ma:account:query')\")\n    public CommonResult<PageResult<MpAccountRespVO>> getAccountPage(@Valid MpAccountPageReqVO pageVO) {\n        pageVO.setIsMini(MpAccountEnum.IS_MAIN_1.getValue());\n        PageResult<MpAccountDO> pageResult = mpAccountService.getAccountPage(pageVO);\n        return success(MpAccountConvert.INSTANCE.convertPage(pageResult));\n    }\n\n\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/account/MpAccountController.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.account;\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.mp.controller.admin.account.vo.*;\nimport co.yixiang.yshop.module.mp.convert.account.MpAccountConvert;\nimport co.yixiang.yshop.module.mp.dal.dataobject.account.MpAccountDO;\nimport co.yixiang.yshop.module.mp.enums.MpAccountEnum;\nimport co.yixiang.yshop.module.mp.service.account.MpAccountService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Valid;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 公众号账号\")\n@RestController\n@RequestMapping(\"/mp/account\")\n@Validated\npublic class MpAccountController {\n\n    @Resource\n    private MpAccountService mpAccountService;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建公众号账号\")\n    @PreAuthorize(\"@ss.hasPermission('mp:account:create')\")\n    public CommonResult<Long> createAccount(@Valid @RequestBody MpAccountCreateReqVO createReqVO) {\n        return success(mpAccountService.createAccount(createReqVO));\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"更新公众号账号\")\n    @PreAuthorize(\"@ss.hasPermission('mp:account:update')\")\n    public CommonResult<Boolean> updateAccount(@Valid @RequestBody MpAccountUpdateReqVO updateReqVO) {\n        mpAccountService.updateAccount(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除公众号账号\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('mp:account:delete')\")\n    public CommonResult<Boolean> deleteAccount(@RequestParam(\"id\") Long id) {\n        mpAccountService.deleteAccount(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得公众号账号\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('mp:account:query')\")\n    public CommonResult<MpAccountRespVO> getAccount(@RequestParam(\"id\") Long id) {\n        MpAccountDO wxAccount = mpAccountService.getAccount(id);\n        return success(MpAccountConvert.INSTANCE.convert(wxAccount));\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得公众号账号分页\")\n    @PreAuthorize(\"@ss.hasPermission('mp:account:query')\")\n    public CommonResult<PageResult<MpAccountRespVO>> getAccountPage(@Valid MpAccountPageReqVO pageVO) {\n        pageVO.setIsMini(MpAccountEnum.IS_MAIN_0.getValue());\n        PageResult<MpAccountDO> pageResult = mpAccountService.getAccountPage(pageVO);\n        return success(MpAccountConvert.INSTANCE.convertPage(pageResult));\n    }\n\n    @GetMapping(\"/list-all-simple\")\n    @Operation(summary = \"获取公众号账号精简信息列表\")\n    @PreAuthorize(\"@ss.hasPermission('mp:account:query')\")\n    public CommonResult<List<MpAccountSimpleRespVO>> getSimpleAccounts() {\n        List<MpAccountDO> list = mpAccountService.getAccountList();\n        return success(MpAccountConvert.INSTANCE.convertList02(list));\n    }\n\n    @PutMapping(\"/generate-qr-code\")\n    @Operation(summary = \"生成公众号二维码\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('mp:account:qr-code')\")\n    public CommonResult<Boolean> generateAccountQrCode(@RequestParam(\"id\") Long id) {\n        mpAccountService.generateAccountQrCode(id);\n        return success(true);\n    }\n\n    @PutMapping(\"/clear-quota\")\n    @Operation(summary = \"清空公众号 API 配额\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('mp:account:clear-quota')\")\n    public CommonResult<Boolean> clearAccountQuota(@RequestParam(\"id\") Long id) {\n        mpAccountService.clearAccountQuota(id);\n        return success(true);\n    }\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/account/vo/MpAccountBaseVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.account.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotEmpty;\n\n/**\n * 公众号账号 Base VO，提供给添加、修改、详细的子 VO 使用\n * 如果子 VO 存在差异的字段，请不要添加到这里，影响 Swagger 文档生成\n *\n * @author fengdan\n */\n@Data\npublic class MpAccountBaseVO {\n\n    @Schema(description = \"公众号名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    @NotEmpty(message = \"名称不能为空\")\n    private String name;\n\n    @Schema(description = \"公众号微信号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshopyuanma\")\n    //@NotEmpty(message = \"公众号微信号不能为空\")\n    private String account;\n\n    @Schema(description = \"公众号 appId\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"wx5b23ba7a5589ecbb\")\n    @NotEmpty(message = \"appId 不能为空\")\n    private String appId;\n\n    @Schema(description = \"公众号密钥\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"3a7b3b20c537e52e74afd395eb85f61f\")\n    @NotEmpty(message = \"密钥不能为空\")\n    private String appSecret;\n\n    @Schema(description = \"公众号 token\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"kangdayuzhen\")\n    //@NotEmpty(message = \"公众号 token 不能为空\")\n    private String token;\n\n    @Schema(description = \"加密密钥\", example = \"gjN+Ksei\")\n    private String aesKey;\n\n    @Schema(description = \"备注\", example = \"请关注yshop，学习技术\")\n    private String remark;\n\n    /**\n     * 设置主账户\n     */\n    private Integer isMain;\n\n    private Integer isMiapp;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/account/vo/MpAccountCreateReqVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.account.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\n\n@Schema(description = \"管理后台 - 公众号账号创建 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class MpAccountCreateReqVO extends MpAccountBaseVO {\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/account/vo/MpAccountPageReqVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.account.vo;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\n\n@Schema(description = \"管理后台 - 公众号账号分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class MpAccountPageReqVO extends PageParam {\n\n    @Schema(name = \"公众号名称\", description = \"模糊匹配\")\n    private String name;\n\n    @Schema(name = \"公众号账号\", description = \"模糊匹配\")\n    private String account;\n\n    @Schema(name = \"公众号 appid\", description = \"模糊匹配\")\n    private String appId;\n\n    @Schema(name = \"是否是小程序\", description = \"\")\n    private Integer isMini;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/account/vo/MpAccountRespVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.account.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\n\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 公众号账号 Response VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class MpAccountRespVO extends MpAccountBaseVO {\n\n    @Schema(description = \"编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"二维码图片URL\", example = \"https://www.yixiang.co/1024.png\")\n    private String qrCodeUrl;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/account/vo/MpAccountSimpleRespVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.account.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\n@Schema(description = \"管理后台 - 公众号账号精简信息 Response VO\")\n@Data\npublic class MpAccountSimpleRespVO {\n\n    @Schema(description = \"编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"公众号名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    private String name;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/account/vo/MpAccountUpdateReqVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.account.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\n\nimport jakarta.validation.constraints.NotNull;\n\n@Schema(description = \"管理后台 - 公众号账号更新 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class MpAccountUpdateReqVO extends MpAccountBaseVO {\n\n    @Schema(description = \"编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @NotNull(message = \"编号不能为空\")\n    private Long id;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/material/MpMaterialController.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.material;\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.mp.controller.admin.material.vo.*;\nimport co.yixiang.yshop.module.mp.convert.material.MpMaterialConvert;\nimport co.yixiang.yshop.module.mp.dal.dataobject.material.MpMaterialDO;\nimport co.yixiang.yshop.module.mp.service.material.MpMaterialService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Valid;\nimport java.io.IOException;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 公众号素材\")\n@RestController\n@RequestMapping(\"/mp/material\")\n@Validated\npublic class MpMaterialController {\n\n    @Resource\n    private MpMaterialService mpMaterialService;\n\n    @Operation(summary = \"上传临时素材\")\n    @PostMapping(\"/upload-temporary\")\n    @PreAuthorize(\"@ss.hasPermission('mp:material:upload-temporary')\")\n    public CommonResult<MpMaterialUploadRespVO> uploadTemporaryMaterial(\n            @Valid MpMaterialUploadTemporaryReqVO reqVO) throws IOException {\n        MpMaterialDO material = mpMaterialService.uploadTemporaryMaterial(reqVO);\n        return success(MpMaterialConvert.INSTANCE.convert(material));\n    }\n\n    @Operation(summary = \"上传永久素材\")\n    @PostMapping(\"/upload-permanent\")\n    @PreAuthorize(\"@ss.hasPermission('mp:material:upload-permanent')\")\n    public CommonResult<MpMaterialUploadRespVO> uploadPermanentMaterial(\n            @Valid MpMaterialUploadPermanentReqVO reqVO) throws IOException {\n        MpMaterialDO material = mpMaterialService.uploadPermanentMaterial(reqVO);\n        return success(MpMaterialConvert.INSTANCE.convert(material));\n    }\n\n    @Operation(summary = \"删除素材\")\n    @DeleteMapping(\"/delete-permanent\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('mp:material:delete')\")\n    public CommonResult<Boolean> deleteMaterial(@RequestParam(\"id\") Long id) {\n        mpMaterialService.deleteMaterial(id);\n        return success(true);\n    }\n\n    @Operation(summary = \"上传图文内容中的图片\")\n    @PostMapping(\"/upload-news-image\")\n    @PreAuthorize(\"@ss.hasPermission('mp:material:upload-news-image')\")\n    public CommonResult<String> uploadNewsImage(@Valid MpMaterialUploadNewsImageReqVO reqVO)\n            throws IOException {\n        return success(mpMaterialService.uploadNewsImage(reqVO));\n    }\n\n    @Operation(summary = \"获得素材分页\")\n    @GetMapping(\"/page\")\n    @PreAuthorize(\"@ss.hasPermission('mp:material:query')\")\n    public CommonResult<PageResult<MpMaterialRespVO>> getMaterialPage(@Valid MpMaterialPageReqVO pageReqVO) {\n        PageResult<MpMaterialDO> pageResult = mpMaterialService.getMaterialPage(pageReqVO);\n        return success(MpMaterialConvert.INSTANCE.convertPage(pageResult));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/material/vo/MpMaterialPageReqVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.material.vo;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\n\nimport jakarta.validation.constraints.NotNull;\n\n@Schema(description = \"管理后台 - 公众号素材的分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class MpMaterialPageReqVO extends PageParam {\n\n    @Schema(description = \"公众号账号的编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"2048\")\n    @NotNull(message = \"公众号账号的编号不能为空\")\n    private Long accountId;\n\n    @Schema(description = \"是否永久\", example = \"true\")\n    private Boolean permanent;\n\n    @Schema(description = \"文件类型 参见 WxConsts.MediaFileType 枚举\", example = \"image\")\n    private String type;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/material/vo/MpMaterialRespVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.material.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 公众号素材 Response VO\")\n@Data\npublic class MpMaterialRespVO {\n\n    @Schema(description = \"主键\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"公众号账号的编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    private Long accountId;\n    @Schema(description = \"公众号账号的 appId\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"wx1234567890\")\n    private String appId;\n\n    @Schema(description = \"素材的 media_id\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"123\")\n    private String mediaId;\n\n    @Schema(description = \"文件类型 参见 WxConsts.MediaFileType 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"image\")\n    private String type;\n\n    @Schema(description = \"是否永久 true - 永久；false - 临时\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"true\")\n    private Boolean permanent;\n\n    @Schema(description = \"素材的 URL\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"https://www.yixiang.co/1.png\")\n    private String url;\n\n\n    @Schema(description = \"名字\", example = \"yshop.png\")\n    private String name;\n\n    @Schema(description = \"公众号文件 URL 只有【永久素材】使用\", example = \"https://mmbiz.qpic.cn/xxx.mp3\")\n    private String mpUrl;\n\n    @Schema(description = \"视频素材的标题 只有【永久素材】使用\", example = \"我是标题\")\n    private String title;\n    @Schema(description = \"视频素材的描述 只有【永久素材】使用\", example = \"我是介绍\")\n    private String introduction;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/material/vo/MpMaterialUploadNewsImageReqVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.material.vo;\n\nimport com.fasterxml.jackson.annotation.JsonIgnore;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport org.springframework.web.multipart.MultipartFile;\n\nimport jakarta.validation.constraints.NotNull;\n\n@Schema(description = \"管理后台 - 公众号素材上传图文内容中的图片 Request VO\")\n@Data\npublic class MpMaterialUploadNewsImageReqVO {\n\n    @Schema(description = \"公众号账号的编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"2048\")\n    @NotNull(message = \"公众号账号的编号不能为空\")\n    private Long accountId;\n\n    @Schema(description = \"文件附件\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @NotNull(message = \"文件不能为空\")\n    @JsonIgnore // 避免被操作日志，进行序列化，导致报错\n    private MultipartFile file;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/material/vo/MpMaterialUploadPermanentReqVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.material.vo;\n\nimport cn.hutool.core.util.ObjectUtil;\nimport com.fasterxml.jackson.annotation.JsonIgnore;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport me.chanjar.weixin.common.api.WxConsts;\nimport org.springframework.web.multipart.MultipartFile;\n\nimport jakarta.validation.constraints.AssertTrue;\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\n\n@Schema(description = \"管理后台 - 公众号素材上传永久 Request VO\")\n@Data\npublic class MpMaterialUploadPermanentReqVO {\n\n    @Schema(description = \"公众号账号的编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"2048\")\n    @NotNull(message = \"公众号账号的编号不能为空\")\n    private Long accountId;\n\n    @Schema(description = \"文件类型 参见 WxConsts.MediaFileType 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"image\")\n    @NotEmpty(message = \"文件类型不能为空\")\n    private String type;\n\n    @Schema(description = \"文件附件\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @NotNull(message = \"文件不能为空\")\n    @JsonIgnore // 避免被操作日志，进行序列化，导致报错\n    private MultipartFile file;\n\n    @Schema(description = \"名字 如果 name 为空，则使用 file 文件名\", example = \"wechat.mp\")\n    private String name;\n\n    @Schema(description = \"视频素材的标题 文件类型为 video 时，必填\", example = \"视频素材的标题\")\n    private String title;\n    @Schema(description = \"视频素材的描述 文件类型为 video 时，必填\", example = \"视频素材的描述\")\n    private String introduction;\n\n    @AssertTrue(message = \"标题不能为空\")\n    public boolean isTitleValid() {\n        // 生成场景为管理后台时，必须设置上级菜单，不然生成的菜单 SQL 是无父级菜单的\n        return ObjectUtil.notEqual(type, WxConsts.MediaFileType.VIDEO)\n                || title != null;\n    }\n\n    @AssertTrue(message = \"描述不能为空\")\n    public boolean isIntroductionValid() {\n        // 生成场景为管理后台时，必须设置上级菜单，不然生成的菜单 SQL 是无父级菜单的\n        return ObjectUtil.notEqual(type, WxConsts.MediaFileType.VIDEO)\n                || introduction != null;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/material/vo/MpMaterialUploadRespVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.material.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\n@Schema(description = \"管理后台 - 公众号素材上传结果 Response VO\")\n@Data\npublic class MpMaterialUploadRespVO {\n\n    @Schema(description = \"素材的 media_id\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"123\")\n    private String mediaId;\n\n    @Schema(description = \"素材的 URL\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"https://www.yixiang.co/1.png\")\n    private String url;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/material/vo/MpMaterialUploadTemporaryReqVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.material.vo;\n\nimport com.fasterxml.jackson.annotation.JsonIgnore;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport org.springframework.web.multipart.MultipartFile;\n\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\n\n@Schema(description = \"管理后台 - 公众号素材上传临时 Request VO\")\n@Data\npublic class MpMaterialUploadTemporaryReqVO {\n\n    @Schema(description = \"公众号账号的编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"2048\")\n    @NotNull(message = \"公众号账号的编号不能为空\")\n    private Long accountId;\n\n    @Schema(description = \"文件类型 参见 WxConsts.MediaFileType 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"image\")\n    @NotEmpty(message = \"文件类型不能为空\")\n    private String type;\n\n    @Schema(description = \"文件附件\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @NotNull(message = \"文件不能为空\")\n    @JsonIgnore // 避免被操作日志，进行序列化，导致报错\n    private MultipartFile file;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/menu/MpMenuController.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.menu;\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.module.mp.controller.admin.menu.vo.MpMenuRespVO;\nimport co.yixiang.yshop.module.mp.controller.admin.menu.vo.MpMenuSaveReqVO;\nimport co.yixiang.yshop.module.mp.convert.menu.MpMenuConvert;\nimport co.yixiang.yshop.module.mp.dal.dataobject.menu.MpMenuDO;\nimport co.yixiang.yshop.module.mp.service.menu.MpMenuService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Valid;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 公众号菜单\")\n@RestController\n@RequestMapping(\"/mp/menu\")\n@Validated\npublic class MpMenuController {\n\n    @Resource\n    private MpMenuService mpMenuService;\n\n    @PostMapping(\"/save\")\n    @Operation(summary = \"保存公众号菜单\")\n    @PreAuthorize(\"@ss.hasPermission('mp:menu:save')\")\n    public CommonResult<Boolean> saveMenu(@Valid @RequestBody MpMenuSaveReqVO createReqVO) {\n        mpMenuService.saveMenu(createReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除公众号菜单\")\n    @Parameter(name = \"accountId\", description = \"公众号账号的编号\", required = true, example = \"10\")\n    @PreAuthorize(\"@ss.hasPermission('mp:menu:delete')\")\n    public CommonResult<Boolean> deleteMenu(@RequestParam(\"accountId\") Long accountId) {\n        mpMenuService.deleteMenuByAccountId(accountId);\n        return success(true);\n    }\n\n    @GetMapping(\"/list\")\n    @Operation(summary = \"获得公众号菜单列表\")\n    @Parameter(name = \"accountId\", description = \"公众号账号的编号\", required = true, example = \"10\")\n    @PreAuthorize(\"@ss.hasPermission('mp:menu:query')\")\n    public CommonResult<List<MpMenuRespVO>> getMenuList(@RequestParam(\"accountId\") Long accountId) {\n        List<MpMenuDO> list = mpMenuService.getMenuListByAccountId(accountId);\n        return success(MpMenuConvert.INSTANCE.convertList(list));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/menu/vo/MpMenuBaseVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.menu.vo;\n\nimport co.yixiang.yshop.module.mp.dal.dataobject.message.MpMessageDO;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport me.chanjar.weixin.common.api.WxConsts;\nimport org.hibernate.validator.constraints.URL;\n\nimport jakarta.validation.Valid;\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\nimport java.util.List;\n\nimport static co.yixiang.yshop.module.mp.framework.mp.core.util.MpUtils.*;\n\n/**\n * 公众号菜单 Base VO，提供给添加、修改、详细的子 VO 使用\n * 如果子 VO 存在差异的字段，请不要添加到这里，影响 Swagger 文档生成\n */\n@Data\npublic class MpMenuBaseVO {\n\n    /**\n     * 菜单名称\n     */\n    private String name;\n    /**\n     * 菜单标识\n     *\n     * 支持多 DB 类型时，无法直接使用 key + @TableField(\"menuKey\") 来实现转换，原因是 \"menuKey\" AS key 而存在报错\n     */\n    private String menuKey;\n    /**\n     * 父菜单编号\n     */\n    private Long parentId;\n\n    // ========== 按钮操作 ==========\n\n    /**\n     * 按钮类型\n     *\n     * 枚举 {@link WxConsts.MenuButtonType}\n     */\n    private String type;\n\n    @Schema(description = \"网页链接\", example = \"https://www.yixiang.co/\")\n    @NotEmpty(message = \"网页链接不能为空\", groups = {ViewButtonGroup.class, MiniProgramButtonGroup.class})\n    @URL(message = \"网页链接必须是 URL 格式\")\n    private String url;\n\n    @Schema(description = \"小程序的 appId\", example = \"wx1234567890\")\n    @NotEmpty(message = \"小程序的 appId 不能为空\", groups = MiniProgramButtonGroup.class)\n    private String miniProgramAppId;\n\n    @Schema(description = \"小程序的页面路径\", example = \"pages/index/index\")\n    @NotEmpty(message = \"小程序的页面路径不能为空\", groups = MiniProgramButtonGroup.class)\n    private String miniProgramPagePath;\n\n    @Schema(description =\"跳转图文的媒体编号\", example = \"jCQk93AIIgp8ixClWcW_NXXqBKInNWNmq2XnPeDZl7IMVqWiNeL4FfELtggRXd83\")\n    @NotEmpty(message = \"跳转图文的媒体编号不能为空\", groups = ViewLimitedButtonGroup.class)\n    private String articleId;\n\n    // ========== 消息内容 ==========\n\n    @Schema(description = \"回复的消息类型 枚举 TEXT、IMAGE、VOICE、VIDEO、NEWS、MUSIC\", example = \"text\")\n    @NotEmpty(message = \"回复的消息类型不能为空\", groups = {ClickButtonGroup.class, ScanCodeWaitMsgButtonGroup.class})\n    private String replyMessageType;\n\n    @Schema(description = \"回复的消息内容\", example = \"欢迎关注\")\n    @NotEmpty(message = \"回复的消息内容不能为空\", groups = TextMessageGroup.class)\n    private String replyContent;\n\n    @Schema(description = \"回复的媒体 id\", example = \"123456\")\n    @NotEmpty(message = \"回复的消息 mediaId 不能为空\",\n            groups = {ImageMessageGroup.class, VoiceMessageGroup.class, VideoMessageGroup.class})\n    private String replyMediaId;\n    @Schema(description = \"回复的媒体 URL\", example = \"https://www.yixiang.co/xxx.jpg\")\n    @NotEmpty(message = \"回复的消息 mediaId 不能为空\",\n            groups = {ImageMessageGroup.class, VoiceMessageGroup.class, VideoMessageGroup.class})\n    private String replyMediaUrl;\n\n    @Schema(description = \"缩略图的媒体 id\", example = \"123456\")\n    @NotEmpty(message = \"回复的消息 thumbMediaId 不能为空\", groups = {MusicMessageGroup.class})\n    private String replyThumbMediaId;\n    @Schema(description = \"缩略图的媒体 URL\",example = \"https://www.yixiang.co/xxx.jpg\")\n    @NotEmpty(message = \"回复的消息 thumbMedia 地址不能为空\", groups = {MusicMessageGroup.class})\n    private String replyThumbMediaUrl;\n\n    @Schema(description = \"回复的标题\", example = \"视频标题\")\n    @NotEmpty(message = \"回复的消息标题不能为空\", groups = VideoMessageGroup.class)\n    private String replyTitle;\n    @Schema(description = \"回复的描述\", example = \"视频描述\")\n    @NotEmpty(message = \"消息描述不能为空\", groups = VideoMessageGroup.class)\n    private String replyDescription;\n\n    /**\n     * 回复的图文消息数组\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS\n     */\n    @NotNull(message = \"回复的图文消息不能为空\", groups = {NewsMessageGroup.class, ViewLimitedButtonGroup.class})\n    @Valid\n    private List<MpMessageDO.Article> replyArticles;\n\n    @Schema(description = \"回复的音乐链接\", example = \"https://www.yixiang.co/xxx.mp3\")\n    @NotEmpty(message = \"回复的音乐链接不能为空\", groups = MusicMessageGroup.class)\n    @URL(message = \"回复的高质量音乐链接格式不正确\", groups = MusicMessageGroup.class)\n    private String replyMusicUrl;\n    @Schema(description = \"高质量音乐链接\", example = \"https://www.yixiang.co/xxx.mp3\")\n    @NotEmpty(message = \"回复的高质量音乐链接不能为空\", groups = MusicMessageGroup.class)\n    @URL(message = \"回复的高质量音乐链接格式不正确\", groups = MusicMessageGroup.class)\n    private String replyHqMusicUrl;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/menu/vo/MpMenuRespVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.menu.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\n\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 公众号菜单 Response VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class MpMenuRespVO extends MpMenuBaseVO {\n\n    @Schema(description = \"主键\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"公众号账号的编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"2048\")\n    private Long accountId;\n\n    @Schema(description = \"公众号 appId\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"wx1234567890ox\")\n    private String appId;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/menu/vo/MpMenuSaveReqVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.menu.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.Valid;\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\nimport java.util.List;\n\n@Schema(description = \"管理后台 - 公众号菜单保存 Request VO\")\n@Data\npublic class MpMenuSaveReqVO {\n\n    @Schema(description = \"公众号账号的编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"2048\")\n    @NotNull(message = \"公众号账号的编号不能为空\")\n    private Long accountId;\n\n    @NotEmpty(message = \"菜单不能为空\")\n    @Valid\n    private List<Menu> menus;\n\n    @Schema(description = \"管理后台 - 公众号菜单保存时的每个菜单\")\n    @Data\n    public static class Menu extends MpMenuBaseVO {\n\n        /**\n         * 子菜单数组\n         */\n        private List<Menu> children;\n\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/message/MpAutoReplyController.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.message;\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.mp.controller.admin.message.vo.autoreply.MpAutoReplyCreateReqVO;\nimport co.yixiang.yshop.module.mp.controller.admin.message.vo.autoreply.MpAutoReplyRespVO;\nimport co.yixiang.yshop.module.mp.controller.admin.message.vo.autoreply.MpAutoReplyUpdateReqVO;\nimport co.yixiang.yshop.module.mp.controller.admin.message.vo.message.MpMessagePageReqVO;\nimport co.yixiang.yshop.module.mp.convert.message.MpAutoReplyConvert;\nimport co.yixiang.yshop.module.mp.dal.dataobject.message.MpAutoReplyDO;\nimport co.yixiang.yshop.module.mp.service.message.MpAutoReplyService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Valid;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 公众号自动回复\")\n@RestController\n@RequestMapping(\"/mp/auto-reply\")\n@Validated\npublic class MpAutoReplyController {\n\n    @Resource\n    private MpAutoReplyService mpAutoReplyService;\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得公众号自动回复分页\")\n    @PreAuthorize(\"@ss.hasPermission('mp:auto-reply:query')\")\n    public CommonResult<PageResult<MpAutoReplyRespVO>> getAutoReplyPage(@Valid MpMessagePageReqVO pageVO) {\n        PageResult<MpAutoReplyDO> pageResult = mpAutoReplyService.getAutoReplyPage(pageVO);\n        return success(MpAutoReplyConvert.INSTANCE.convertPage(pageResult));\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得公众号自动回复\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('mp:auto-reply:query')\")\n    public CommonResult<MpAutoReplyRespVO> getAutoReply(@RequestParam(\"id\") Long id) {\n        MpAutoReplyDO autoReply = mpAutoReplyService.getAutoReply(id);\n        return success(MpAutoReplyConvert.INSTANCE.convert(autoReply));\n    }\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建公众号自动回复\")\n    @PreAuthorize(\"@ss.hasPermission('mp:auto-reply:create')\")\n    public CommonResult<Long> createAutoReply(@Valid @RequestBody MpAutoReplyCreateReqVO createReqVO) {\n        return success(mpAutoReplyService.createAutoReply(createReqVO));\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"更新公众号自动回复\")\n    @PreAuthorize(\"@ss.hasPermission('mp:auto-reply:update')\")\n    public CommonResult<Boolean> updateAutoReply(@Valid @RequestBody MpAutoReplyUpdateReqVO updateReqVO) {\n        mpAutoReplyService.updateAutoReply(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除公众号自动回复\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('mp:auto-reply:delete')\")\n    public CommonResult<Boolean> deleteAutoReply(@RequestParam(\"id\") Long id) {\n        mpAutoReplyService.deleteAutoReply(id);\n        return success(true);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/message/MpMessageController.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.message;\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.mp.controller.admin.message.vo.message.MpMessagePageReqVO;\nimport co.yixiang.yshop.module.mp.controller.admin.message.vo.message.MpMessageRespVO;\nimport co.yixiang.yshop.module.mp.controller.admin.message.vo.message.MpMessageSendReqVO;\nimport co.yixiang.yshop.module.mp.convert.message.MpMessageConvert;\nimport co.yixiang.yshop.module.mp.dal.dataobject.message.MpMessageDO;\nimport co.yixiang.yshop.module.mp.service.message.MpMessageService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Valid;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 公众号消息\")\n@RestController\n@RequestMapping(\"/mp/message\")\n@Validated\npublic class MpMessageController {\n\n    @Resource\n    private MpMessageService mpMessageService;\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得公众号消息分页\")\n    @PreAuthorize(\"@ss.hasPermission('mp:message:query')\")\n    public CommonResult<PageResult<MpMessageRespVO>> getMessagePage(@Valid MpMessagePageReqVO pageVO) {\n        PageResult<MpMessageDO> pageResult = mpMessageService.getMessagePage(pageVO);\n        return success(MpMessageConvert.INSTANCE.convertPage(pageResult));\n    }\n\n    @PostMapping(\"/send\")\n    @Operation(summary = \"给粉丝发送消息\")\n    @PreAuthorize(\"@ss.hasPermission('mp:message:send')\")\n    public CommonResult<MpMessageRespVO> sendMessage(@Valid @RequestBody MpMessageSendReqVO reqVO) {\n        MpMessageDO message = mpMessageService.sendKefuMessage(reqVO);\n        return success(MpMessageConvert.INSTANCE.convert(message));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyBaseVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.message.vo.autoreply;\n\nimport cn.hutool.core.util.ObjectUtil;\nimport co.yixiang.yshop.module.mp.dal.dataobject.message.MpMessageDO;\nimport co.yixiang.yshop.module.mp.enums.message.MpAutoReplyTypeEnum;\nimport co.yixiang.yshop.module.mp.framework.mp.core.util.MpUtils.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport me.chanjar.weixin.common.api.WxConsts;\nimport org.hibernate.validator.constraints.URL;\n\nimport jakarta.validation.Valid;\nimport jakarta.validation.constraints.AssertTrue;\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\nimport java.util.List;\n\n/**\n * 公众号自动回复  Base VO，提供给添加、修改、详细的子 VO 使用\n * 如果子 VO 存在差异的字段，请不要添加到这里，影响 Swagger 文档生成\n */\n@Data\npublic class MpAutoReplyBaseVO {\n\n    @Schema(description = \"回复类型 参见 MpAutoReplyTypeEnum 枚举\", example = \"1\")\n    @NotNull(message = \"回复类型不能为空\")\n    private Integer type;\n\n    // ==================== 请求消息 ====================\n\n    @Schema(description = \"请求的关键字 当 type 为 MpAutoReplyTypeEnum#KEYWORD 时，必填\", example = \"关键字\")\n    private String requestKeyword;\n    @Schema(description = \"请求的匹配方式 当 type 为 MpAutoReplyTypeEnum#KEYWORD 时，必填\", example = \"1\")\n    private Integer requestMatch;\n\n    @Schema(description = \"请求的消息类型 当 type 为 MpAutoReplyTypeEnum#MESSAGE 时，必填\", example = \"text\")\n    private String requestMessageType;\n\n    // ==================== 响应消息 ====================\n\n    @Schema(description = \"回复的消息类型 枚举 TEXT、IMAGE、VOICE、VIDEO、NEWS、MUSIC\", example = \"text\")\n    @NotEmpty(message = \"回复的消息类型不能为空\")\n    private String responseMessageType;\n\n    @Schema(description = \"回复的消息内容\", example = \"欢迎关注\")\n    @NotEmpty(message = \"回复的消息内容不能为空\", groups = TextMessageGroup.class)\n    private String responseContent;\n\n    @Schema(description = \"回复的媒体 id\", example = \"123456\")\n    @NotEmpty(message = \"回复的消息 mediaId 不能为空\",\n            groups = {ImageMessageGroup.class, VoiceMessageGroup.class, VideoMessageGroup.class})\n    private String responseMediaId;\n    @Schema(description = \"回复的媒体 URL\", example = \"https://www.yixiang.co/xxx.jpg\")\n    @NotEmpty(message = \"回复的消息 mediaId 不能为空\",\n            groups = {ImageMessageGroup.class, VoiceMessageGroup.class, VideoMessageGroup.class})\n    private String responseMediaUrl;\n\n    @Schema(description = \"缩略图的媒体 id\", example = \"123456\")\n    @NotEmpty(message = \"回复的消息 thumbMediaId 不能为空\", groups = {MusicMessageGroup.class})\n    private String responseThumbMediaId;\n    @Schema(description = \"缩略图的媒体 URL\",example = \"https://www.yixiang.co/xxx.jpg\")\n    @NotEmpty(message = \"回复的消息 thumbMedia 地址不能为空\", groups = {MusicMessageGroup.class})\n    private String responseThumbMediaUrl;\n\n    @Schema(description = \"回复的标题\", example = \"视频标题\")\n    @NotEmpty(message = \"回复的消息标题不能为空\", groups = VideoMessageGroup.class)\n    private String responseTitle;\n    @Schema(description = \"回复的描述\", example = \"视频描述\")\n    @NotEmpty(message = \"消息描述不能为空\", groups = VideoMessageGroup.class)\n    private String responseDescription;\n\n    /**\n     * 回复的图文消息\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS\n     */\n    @NotNull(message = \"回复的图文消息不能为空\", groups = {NewsMessageGroup.class, ViewLimitedButtonGroup.class})\n    @Valid\n    private List<MpMessageDO.Article> responseArticles;\n\n    @Schema(description = \"回复的音乐链接\", example = \"https://www.yixiang.co/xxx.mp3\")\n    @NotEmpty(message = \"回复的音乐链接不能为空\", groups = MusicMessageGroup.class)\n    @URL(message = \"回复的高质量音乐链接格式不正确\", groups = MusicMessageGroup.class)\n    private String responseMusicUrl;\n    @Schema(description = \"高质量音乐链接\", example = \"https://www.yixiang.co/xxx.mp3\")\n    @NotEmpty(message = \"回复的高质量音乐链接不能为空\", groups = MusicMessageGroup.class)\n    @URL(message = \"回复的高质量音乐链接格式不正确\", groups = MusicMessageGroup.class)\n    private String responseHqMusicUrl;\n\n    @AssertTrue(message = \"请求的关键字不能为空\")\n    public boolean isRequestKeywordValid() {\n        return ObjectUtil.notEqual(type, MpAutoReplyTypeEnum.KEYWORD)\n                || requestKeyword != null;\n    }\n\n    @AssertTrue(message = \"请求的关键字的匹配不能为空\")\n    public boolean isRequestMatchValid() {\n        return ObjectUtil.notEqual(type, MpAutoReplyTypeEnum.KEYWORD)\n                || requestMatch != null;\n    }\n\n    @AssertTrue(message = \"请求的消息类型不能为空\")\n    public boolean isRequestMessageTypeValid() {\n        return ObjectUtil.notEqual(type, MpAutoReplyTypeEnum.MESSAGE)\n                || requestMessageType != null;\n    }\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyCreateReqVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.message.vo.autoreply;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\n\nimport jakarta.validation.constraints.NotNull;\n\n@Schema(description = \"管理后台 - 公众号自动回复的创建 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class MpAutoReplyCreateReqVO extends MpAutoReplyBaseVO {\n\n    @Schema(description = \"公众号账号的编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @NotNull(message = \"公众号账号的编号不能为空\")\n    private Long accountId;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyPageReqVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.message.vo.autoreply;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\n\nimport jakarta.validation.constraints.NotNull;\n\n@Schema(description = \"管理后台 - 公众号自动回复的分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class MpAutoReplyPageReqVO extends PageParam {\n\n    @Schema(description = \"公众号账号的编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @NotNull(message = \"公众号账号的编号不能为空\")\n    private Long accountId;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyRespVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.message.vo.autoreply;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\n\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 公众号自动回复 Response VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class MpAutoReplyRespVO extends MpAutoReplyBaseVO {\n\n    @Schema(description = \"主键\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"公众号账号的编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Long accountId;\n    @Schema(description = \"公众号 appId\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"wx1234567890\")\n    private String appId;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/message/vo/autoreply/MpAutoReplyUpdateReqVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.message.vo.autoreply;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\n\nimport jakarta.validation.constraints.NotNull;\n\n@Schema(description = \"管理后台 - 公众号自动回复的更新 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class MpAutoReplyUpdateReqVO extends MpAutoReplyBaseVO {\n\n    @Schema(description = \"主键\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @NotNull(message = \"主键不能为空\")\n    private Long id;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/message/vo/message/MpMessagePageReqVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.message.vo.message;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport jakarta.validation.constraints.NotNull;\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 公众号消息分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class MpMessagePageReqVO extends PageParam {\n\n    @Schema(description = \"公众号账号的编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @NotNull(message = \"公众号账号的编号不能为空\")\n    private Long accountId;\n\n    @Schema(description = \"消息类型 参见 WxConsts.XmlMsgType 枚举\", example = \"text\")\n    private String type;\n\n    @Schema(description = \"公众号粉丝标识\", example = \"o6_bmjrPTlm6_2sgVt7hMZOPfL2M\")\n    private String openid;\n\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    @Schema(description = \"创建时间\")\n    private LocalDateTime[] createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/message/vo/message/MpMessageRespVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.message.vo.message;\n\nimport co.yixiang.yshop.module.mp.dal.dataobject.message.MpMessageDO;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport me.chanjar.weixin.common.api.WxConsts;\n\nimport java.time.LocalDateTime;\nimport java.util.Date;\nimport java.util.List;\n\n@Schema(description = \"管理后台 - 公众号消息 Response VO\")\n@Data\npublic class MpMessageRespVO {\n\n    @Schema(description = \"主键\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Integer id;\n\n    @Schema(description = \"微信公众号消息 id\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"23953173569869169\")\n    private Long msgId;\n\n    @Schema(description = \"公众号账号的编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    private Long accountId;\n    @Schema(description = \"公众号账号的 appid\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"wx1234567890\")\n    private String appId;\n\n    @Schema(description = \"公众号粉丝编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"2048\")\n    private Long userId;\n    @Schema(description = \"公众号粉丝标志\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"o6_bmjrPTlm6_2sgVt7hMZOPfL2M\")\n    private String openid;\n\n    @Schema(description = \"消息类型 参见 WxConsts.XmlMsgType 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"text\")\n    private String type;\n    @Schema(description = \"消息来源 参见 MpMessageSendFromEnum 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    private Integer sendFrom;\n\n    // ========= 普通消息内容 https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_standard_messages.html\n\n    @Schema(description = \"消息内容 消息类型为 text 时，才有值\", example = \"你好呀\")\n    private String content;\n\n    @Schema(description = \"媒体素材的编号 消息类型为 image、voice、video 时，才有值\", example = \"1234567890\")\n    private String mediaId;\n    @Schema(description = \"媒体文件的 URL 消息类型为 image、voice、video 时，才有值\", example = \"https://www.yixiang.co/xxx.png\")\n    private String mediaUrl;\n\n    @Schema(description = \"语音识别后文本 消息类型为 voice 时，才有值\", example = \"语音识别后文本\")\n    private String recognition;\n    @Schema(description = \"语音格式 消息类型为 voice 时，才有值\", example = \"amr\")\n    private String format;\n\n    @Schema(description = \"标题 消息类型为 video、music、link 时，才有值\", example = \"我是标题\")\n    private String title;\n\n    @Schema(description = \"描述 消息类型为 video、music 时，才有值\", example = \"我是描述\")\n    private String description;\n\n    @Schema(description = \"缩略图的媒体 id 消息类型为 video、music 时，才有值\", example = \"1234567890\")\n    private String thumbMediaId;\n    @Schema(description = \"缩略图的媒体 URL 消息类型为 video、music 时，才有值\", example = \"https://www.yixiang.co/xxx.png\")\n    private String thumbMediaUrl;\n\n    @Schema(description = \"点击图文消息跳转链接 消息类型为 link 时，才有值\", example = \"https://www.yixiang.co\")\n    private String url;\n\n    @Schema(description = \"地理位置维度 消息类型为 location 时，才有值\", example = \"23.137466\")\n    private Double locationX;\n\n    @Schema(description = \"地理位置经度 消息类型为 location 时，才有值\", example = \"113.352425\")\n    private Double locationY;\n\n    @Schema(description = \"地图缩放大小 消息类型为 location 时，才有值\", example = \"13\")\n    private Double scale;\n\n    @Schema(description = \"详细地址 消息类型为 location 时，才有值\", example = \"杨浦区黄兴路 221-4 号临\")\n    private String label;\n\n    /**\n     * 图文消息数组\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS\n     */\n    @TableField(typeHandler = MpMessageDO.ArticleTypeHandler.class)\n    private List<MpMessageDO.Article> articles;\n\n    @Schema(description = \"音乐链接 消息类型为 music 时，才有值\", example = \"https://www.yixiang.co/xxx.mp3\")\n    private String musicUrl;\n    @Schema(description = \"高质量音乐链接 消息类型为 music 时，才有值\", example = \"https://www.yixiang.co/xxx.mp3\")\n    private String hqMusicUrl;\n\n    // ========= 事件推送 https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html\n\n    @Schema(description = \"事件类型 参见 WxConsts.EventType 枚举\", example = \"subscribe\")\n    private String event;\n    @Schema(description = \"事件 Key 参见 WxConsts.EventType 枚举\", example = \"qrscene_123456\")\n    private String eventKey;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/message/vo/message/MpMessageSendReqVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.message.vo.message;\n\nimport co.yixiang.yshop.module.mp.dal.dataobject.message.MpMessageDO;\nimport co.yixiang.yshop.module.mp.framework.mp.core.util.MpUtils.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.Valid;\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\nimport java.util.List;\n\n@Schema(description = \"管理后台 - 公众号消息发送 Request VO\")\n@Data\npublic class MpMessageSendReqVO {\n\n    @Schema(description = \"公众号粉丝的编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @NotNull(message = \"公众号粉丝的编号不能为空\")\n    private Long userId;\n\n    // ========== 消息内容 ==========\n\n    @Schema(description = \"消息类型 TEXT/IMAGE/VOICE/VIDEO/NEWS\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"text\")\n    @NotEmpty(message = \"消息类型不能为空\")\n    public String type;\n\n    @Schema(description = \"消息内容\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"你好呀\")\n    @NotEmpty(message = \"消息内容不能为空\", groups = TextMessageGroup.class)\n    private String content;\n\n    @Schema(description = \"媒体 ID\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"qqc_2Fot30Jse-HDoZmo5RrUDijz2nGUkP\")\n    @NotEmpty(message = \"消息内容不能为空\", groups = {ImageMessageGroup.class, VoiceMessageGroup.class, VideoMessageGroup.class})\n    private String mediaId;\n\n    @Schema(description = \"标题\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"没有标题\")\n    @NotEmpty(message = \"消息内容不能为空\", groups = VideoMessageGroup.class)\n    private String title;\n\n    @Schema(description = \"描述\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"你猜\")\n    @NotEmpty(message = \"消息描述不能为空\", groups = VideoMessageGroup.class)\n    private String description;\n\n    @Schema(description = \"缩略图的媒体 id\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"qqc_2Fot30Jse-HDoZmo5RrUDijz2nGUkP\")\n    @NotEmpty(message = \"缩略图的媒体 id 不能为空\", groups = MusicMessageGroup.class)\n    private String thumbMediaId;\n\n    @Schema(description = \"图文消息\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @Valid\n    @NotNull(message = \"图文消息不能为空\", groups = NewsMessageGroup.class)\n    private List<MpMessageDO.Article> articles;\n\n    @Schema(description = \"音乐链接 消息类型为 MUSIC 时\", example = \"https://www.yixiang.co/music.mp3\")\n    private String musicUrl;\n\n    @Schema(description = \"高质量音乐链接 消息类型为 MUSIC 时\", example = \"https://www.yixiang.co/music.mp3\")\n    private String hqMusicUrl;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/news/MpDraftController.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.news;\n\nimport cn.hutool.core.collection.CollUtil;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.collection.CollectionUtils;\nimport co.yixiang.yshop.framework.common.util.object.PageUtils;\nimport co.yixiang.yshop.module.mp.controller.admin.news.vo.MpDraftPageReqVO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.material.MpMaterialDO;\nimport co.yixiang.yshop.module.mp.framework.mp.core.MpServiceFactory;\nimport co.yixiang.yshop.module.mp.service.material.MpMaterialService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.Parameters;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport me.chanjar.weixin.common.error.WxErrorException;\nimport me.chanjar.weixin.mp.api.WxMpService;\nimport me.chanjar.weixin.mp.bean.draft.*;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport jakarta.annotation.Resource;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\nimport static co.yixiang.yshop.framework.common.util.collection.MapUtils.findAndThen;\nimport static co.yixiang.yshop.module.mp.enums.ErrorCodeConstants.*;\n\n@Tag(name = \"管理后台 - 公众号草稿\")\n@RestController\n@RequestMapping(\"/mp/draft\")\n@Validated\npublic class MpDraftController {\n\n    @Resource\n    private MpServiceFactory mpServiceFactory;\n\n    @Resource\n    private MpMaterialService mpMaterialService;\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得草稿分页\")\n    @PreAuthorize(\"@ss.hasPermission('mp:draft:query')\")\n    public CommonResult<PageResult<WxMpDraftItem>> getDraftPage(MpDraftPageReqVO reqVO) {\n        // 从公众号查询草稿箱\n        WxMpService mpService = mpServiceFactory.getRequiredMpService(reqVO.getAccountId());\n        WxMpDraftList draftList;\n        try {\n            draftList = mpService.getDraftService().listDraft(PageUtils.getStart(reqVO), reqVO.getPageSize());\n        } catch (WxErrorException e) {\n            throw exception(DRAFT_LIST_FAIL, e.getError().getErrorMsg());\n        }\n        // 查询对应的图片地址。目的：解决公众号的图片链接无法在我们后台展示\n        setDraftThumbUrl(draftList.getItems());\n\n        // 返回分页\n        return success(new PageResult<>(draftList.getItems(), draftList.getTotalCount().longValue()));\n    }\n\n    private void setDraftThumbUrl(List<WxMpDraftItem> items) {\n        // 1.1 获得 mediaId 数组\n        Set<String> mediaIds = new HashSet<>();\n        items.forEach(item -> item.getContent().getNewsItem().forEach(newsItem -> mediaIds.add(newsItem.getThumbMediaId())));\n        if (CollUtil.isEmpty(mediaIds)) {\n            return;\n        }\n        // 1.2 批量查询对应的 Media 素材\n        Map<String, MpMaterialDO> materials = CollectionUtils.convertMap(mpMaterialService.getMaterialListByMediaId(mediaIds),\n                MpMaterialDO::getMediaId);\n\n        // 2. 设置回 WxMpDraftItem 记录\n        items.forEach(item -> item.getContent().getNewsItem().forEach(newsItem ->\n                findAndThen(materials, newsItem.getThumbMediaId(), material -> newsItem.setThumbUrl(material.getUrl()))));\n    }\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建草稿\")\n    @Parameter(name = \"accountId\", description = \"公众号账号的编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('mp:draft:create')\")\n    public CommonResult<String> deleteDraft(@RequestParam(\"accountId\") Long accountId,\n                                            @RequestBody WxMpAddDraft draft) {\n        WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId);\n        try {\n            String mediaId = mpService.getDraftService().addDraft(draft);\n            return success(mediaId);\n        } catch (WxErrorException e) {\n            throw exception(DRAFT_CREATE_FAIL, e.getError().getErrorMsg());\n        }\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"更新草稿\")\n    @Parameters({\n            @Parameter(name = \"accountId\", description = \"公众号账号的编号\", required = true, example = \"1024\"),\n            @Parameter(name = \"mediaId\", description = \"草稿素材的编号\", required = true, example = \"xxx\")\n    })\n    @PreAuthorize(\"@ss.hasPermission('mp:draft:update')\")\n    public CommonResult<Boolean> deleteDraft(@RequestParam(\"accountId\") Long accountId,\n                                             @RequestParam(\"mediaId\") String mediaId,\n                                             @RequestBody List<WxMpDraftArticles> articles) {\n        WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId);\n        try {\n            for (int i = 0; i < articles.size(); i++) {\n                WxMpDraftArticles article = articles.get(i);\n                mpService.getDraftService().updateDraft(new WxMpUpdateDraft(mediaId, i, article));\n            }\n            return success(true);\n        } catch (WxErrorException e) {\n            throw exception(DRAFT_UPDATE_FAIL, e.getError().getErrorMsg());\n        }\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除草稿\")\n    @Parameters({\n            @Parameter(name = \"accountId\", description = \"公众号账号的编号\", required = true, example = \"1024\"),\n            @Parameter(name = \"mediaId\", description = \"草稿素材的编号\", required = true, example = \"xxx\")\n    })\n    @PreAuthorize(\"@ss.hasPermission('mp:draft:delete')\")\n    public CommonResult<Boolean> deleteDraft(@RequestParam(\"accountId\") Long accountId,\n                                             @RequestParam(\"mediaId\") String mediaId) {\n        WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId);\n        try {\n            mpService.getDraftService().delDraft(mediaId);\n            return success(true);\n        } catch (WxErrorException e) {\n            throw exception(DRAFT_DELETE_FAIL, e.getError().getErrorMsg());\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/news/MpFreePublishController.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.news;\n\nimport cn.hutool.core.collection.CollUtil;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.collection.CollectionUtils;\nimport co.yixiang.yshop.framework.common.util.object.PageUtils;\nimport co.yixiang.yshop.module.mp.controller.admin.news.vo.MpFreePublishPageReqVO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.material.MpMaterialDO;\nimport co.yixiang.yshop.module.mp.framework.mp.core.MpServiceFactory;\nimport co.yixiang.yshop.module.mp.service.material.MpMaterialService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.Parameters;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport me.chanjar.weixin.common.error.WxErrorException;\nimport me.chanjar.weixin.mp.api.WxMpService;\nimport me.chanjar.weixin.mp.bean.freepublish.WxMpFreePublishItem;\nimport me.chanjar.weixin.mp.bean.freepublish.WxMpFreePublishList;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport jakarta.annotation.Resource;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\nimport static co.yixiang.yshop.framework.common.util.collection.MapUtils.findAndThen;\nimport static co.yixiang.yshop.module.mp.enums.ErrorCodeConstants.*;\n\n@Tag(name = \"管理后台 - 公众号发布能力\")\n@RestController\n@RequestMapping(\"/mp/free-publish\")\n@Validated\npublic class MpFreePublishController {\n\n    @Resource\n    private MpServiceFactory mpServiceFactory;\n\n    @Resource\n    private MpMaterialService mpMaterialService;\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得已发布的图文分页\")\n    @PreAuthorize(\"@ss.hasPermission('mp:free-publish:query')\")\n    public CommonResult<PageResult<WxMpFreePublishItem>> getFreePublishPage(MpFreePublishPageReqVO reqVO) {\n        // 从公众号查询已发布的图文列表\n        WxMpService mpService = mpServiceFactory.getRequiredMpService(reqVO.getAccountId());\n        WxMpFreePublishList publicationRecords;\n        try {\n            publicationRecords = mpService.getFreePublishService().getPublicationRecords(\n                    PageUtils.getStart(reqVO), reqVO.getPageSize());\n        } catch (WxErrorException e) {\n            throw exception(FREE_PUBLISH_LIST_FAIL, e.getError().getErrorMsg());\n        }\n        // 查询对应的图片地址。目的：解决公众号的图片链接无法在我们后台展示\n        setFreePublishThumbUrl(publicationRecords.getItems());\n\n        // 返回分页\n        return success(new PageResult<>(publicationRecords.getItems(), publicationRecords.getTotalCount().longValue()));\n    }\n\n    private void setFreePublishThumbUrl(List<WxMpFreePublishItem> items) {\n        // 1.1 获得 mediaId 数组\n        Set<String> mediaIds = new HashSet<>();\n        items.forEach(item -> item.getContent().getNewsItem().forEach(newsItem -> mediaIds.add(newsItem.getThumbMediaId())));\n        if (CollUtil.isEmpty(mediaIds)) {\n            return;\n        }\n        // 1.2 批量查询对应的 Media 素材\n        Map<String, MpMaterialDO> materials = CollectionUtils.convertMap(mpMaterialService.getMaterialListByMediaId(mediaIds),\n                MpMaterialDO::getMediaId);\n\n        // 2. 设置回 WxMpFreePublishItem 记录\n        items.forEach(item -> item.getContent().getNewsItem().forEach(newsItem ->\n                findAndThen(materials, newsItem.getThumbMediaId(), material -> newsItem.setThumbUrl(material.getUrl()))));\n    }\n\n    @PostMapping(\"/submit\")\n    @Operation(summary = \"发布草稿\")\n    @Parameters({\n            @Parameter(name = \"accountId\", description = \"公众号账号的编号\", required = true, example = \"1024\"),\n            @Parameter(name = \"mediaId\", description = \"要发布的草稿的 media_id\", required = true, example = \"2048\")\n    })\n    @PreAuthorize(\"@ss.hasPermission('mp:free-publish:submit')\")\n    public CommonResult<String> submitFreePublish(@RequestParam(\"accountId\") Long accountId,\n                                                  @RequestParam(\"mediaId\") String mediaId) {\n        WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId);\n        try {\n            String publishId = mpService.getFreePublishService().submit(mediaId);\n            return success(publishId);\n        } catch (WxErrorException e) {\n            throw exception(FREE_PUBLISH_SUBMIT_FAIL, e.getError().getErrorMsg());\n        }\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除草稿\")\n    @Parameters({\n            @Parameter(name = \"accountId\", description = \"公众号账号的编号\", required = true, example = \"1024\"),\n            @Parameter(name = \"articleId\", description = \"发布记录的编号\", required = true, example = \"2048\")\n    })\n    @PreAuthorize(\"@ss.hasPermission('mp:free-publish:delete')\")\n    public CommonResult<Boolean> deleteFreePublish(@RequestParam(\"accountId\") Long accountId,\n                                                   @RequestParam(\"articleId\") String articleId) {\n        WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId);\n        try {\n            mpService.getFreePublishService().deletePushAllArticle(articleId);\n            return success(true);\n        } catch (WxErrorException e) {\n            throw exception(FREE_PUBLISH_DELETE_FAIL, e.getError().getErrorMsg());\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/news/vo/MpDraftPageReqVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.news.vo;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\n\nimport jakarta.validation.constraints.NotNull;\n\n@Schema(description = \"管理后台 - 公众号草稿的分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class MpDraftPageReqVO extends PageParam {\n\n    @Schema(description = \"公众号账号的编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @NotNull(message = \"公众号账号的编号不能为空\")\n    private Long accountId;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/news/vo/MpFreePublishPageReqVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.news.vo;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\n\nimport jakarta.validation.constraints.NotNull;\n\n@Schema(description = \"管理后台 - 公众号已发布列表的分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class MpFreePublishPageReqVO extends PageParam {\n\n    @Schema(description = \"公众号账号的编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @NotNull(message = \"公众号账号的编号不能为空\")\n    private Long accountId;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/open/MpOpenController.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.open;\n\nimport cn.hutool.core.lang.Assert;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.tenant.core.util.TenantUtils;\nimport co.yixiang.yshop.module.mp.controller.admin.open.vo.MpOpenCheckSignatureReqVO;\nimport co.yixiang.yshop.module.mp.controller.admin.open.vo.MpOpenHandleMessageReqVO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.account.MpAccountDO;\nimport co.yixiang.yshop.module.mp.framework.mp.core.MpServiceFactory;\nimport co.yixiang.yshop.module.mp.framework.mp.core.context.MpContextHolder;\nimport co.yixiang.yshop.module.mp.service.account.MpAccountService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport lombok.extern.slf4j.Slf4j;\nimport me.chanjar.weixin.mp.api.WxMpMessageRouter;\nimport me.chanjar.weixin.mp.api.WxMpService;\nimport me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;\nimport me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport jakarta.annotation.Resource;\nimport java.util.Objects;\n\n@Tag(name = \"管理后台 - 公众号回调\")\n@RestController\n@RequestMapping(\"/mp/open\")\n@Validated\n@Slf4j\npublic class MpOpenController {\n\n    @Resource\n    private MpServiceFactory mpServiceFactory;\n\n    @Resource\n    private MpAccountService mpAccountService;\n\n    /**\n     * 接收微信公众号的校验签名\n     *\n     * 对应 <a href=\"https://developers.weixin.qq.com/doc/offiaccount/Basic_Information/Access_Overview.html\">文档</a>\n     */\n    @Operation(summary = \"校验签名\") // 参见\n    @GetMapping(value = \"/{appId}\", produces = \"text/plain;charset=utf-8\")\n    public String checkSignature(@PathVariable(\"appId\") String appId,\n                                 MpOpenCheckSignatureReqVO reqVO) {\n        log.info(\"[checkSignature][appId({}) 接收到来自微信服务器的认证消息({})]\", appId, reqVO);\n        // 校验请求签名\n        WxMpService wxMpService = mpServiceFactory.getRequiredMpService(appId);\n        // 校验通过\n        if (wxMpService.checkSignature(reqVO.getTimestamp(), reqVO.getNonce(), reqVO.getSignature())) {\n            return reqVO.getEchostr();\n        }\n        // 校验不通过\n        return \"非法请求\";\n    }\n\n    /**\n     * 接收微信公众号的消息推送\n     *\n     * <a href=\"https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_standard_messages.html\">文档</a>\n     */\n    @Operation(summary = \"处理消息\")\n    @PostMapping(value = \"/{appId}\", produces = \"application/xml; charset=UTF-8\")\n    public String handleMessage(@PathVariable(\"appId\") String appId,\n                                @RequestBody String content,\n                                MpOpenHandleMessageReqVO reqVO) {\n        log.info(\"[handleMessage][appId({}) 推送消息，参数({}) 内容({})]\", appId, reqVO, content);\n\n        // 处理 appId + 多租户的上下文\n        MpAccountDO account = mpAccountService.getAccountFromCache(appId);\n        Assert.notNull(account, \"公众号 appId({}) 不存在\", appId);\n        try {\n            MpContextHolder.setAppId(appId);\n            return TenantUtils.execute(account.getTenantId(),\n                    () -> handleMessage0(appId, content, reqVO));\n        } finally {\n            MpContextHolder.clear();\n        }\n    }\n\n    private String handleMessage0(String appId, String content, MpOpenHandleMessageReqVO reqVO) {\n        // 校验请求签名\n        WxMpService mppService = mpServiceFactory.getRequiredMpService(appId);\n        Assert.isTrue(mppService.checkSignature(reqVO.getTimestamp(), reqVO.getNonce(), reqVO.getSignature()),\n                \"非法请求\");\n\n        // 第一步，解析消息\n        WxMpXmlMessage inMessage = null;\n        if (StrUtil.isBlank(reqVO.getEncrypt_type())) { // 明文模式\n            inMessage = WxMpXmlMessage.fromXml(content);\n        } else if (Objects.equals(reqVO.getEncrypt_type(), MpOpenHandleMessageReqVO.ENCRYPT_TYPE_AES)) { // AES 加密模式\n            inMessage = WxMpXmlMessage.fromEncryptedXml(content, mppService.getWxMpConfigStorage(),\n                    reqVO.getTimestamp(), reqVO.getNonce(), reqVO.getMsg_signature());\n        }\n        Assert.notNull(inMessage, \"消息解析失败，原因：消息为空\");\n\n        // 第二步，处理消息\n        WxMpMessageRouter mpMessageRouter = mpServiceFactory.getRequiredMpMessageRouter(appId);\n        WxMpXmlOutMessage outMessage = mpMessageRouter.route(inMessage);\n        if (outMessage == null) {\n            return \"\";\n        }\n\n        // 第三步，返回消息\n        if (StrUtil.isBlank(reqVO.getEncrypt_type())) { // 明文模式\n            return outMessage.toXml();\n        } else if (Objects.equals(reqVO.getEncrypt_type(), MpOpenHandleMessageReqVO.ENCRYPT_TYPE_AES)) { // AES 加密模式\n            return outMessage.toEncryptedXml(mppService.getWxMpConfigStorage());\n        }\n        return \"\";\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/open/vo/MpOpenCheckSignatureReqVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.open.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotEmpty;\n\n@Schema(description = \"管理后台 - 公众号校验签名 Request VO\")\n@Data\npublic class MpOpenCheckSignatureReqVO {\n\n    @Schema(description = \"微信加密签名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"490eb57f448b87bd5f20ccef58aa4de46aa1908e\")\n    @NotEmpty(message = \"微信加密签名不能为空\")\n    private String signature;\n\n    @Schema(description = \"时间戳\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1672587863\")\n    @NotEmpty(message = \"时间戳不能为空\")\n    private String timestamp;\n\n    @Schema(description = \"随机数\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1827365808\")\n    @NotEmpty(message = \"随机数不能为空\")\n    private String nonce;\n\n    @Schema(description = \"随机字符串\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"2721154047828672511\")\n    @NotEmpty(message = \"随机字符串不能为空\")\n    @SuppressWarnings(\"SpellCheckingInspection\")\n    private String echostr;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/open/vo/MpOpenHandleMessageReqVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.open.vo;\n\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotEmpty;\n\n@Schema(description = \"管理后台 - 公众号处理消息 Request VO\")\n@Data\npublic class MpOpenHandleMessageReqVO {\n\n    public static final String ENCRYPT_TYPE_AES = \"aes\";\n\n    @Schema(description = \"微信加密签名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"490eb57f448b87bd5f20ccef58aa4de46aa1908e\")\n    @NotEmpty(message = \"微信加密签名不能为空\")\n    private String signature;\n\n    @Schema(description = \"时间戳\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1672587863\")\n    @NotEmpty(message = \"时间戳不能为空\")\n    private String timestamp;\n\n    @Schema(description = \"随机数\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1827365808\")\n    @NotEmpty(message = \"随机数不能为空\")\n    private String nonce;\n\n    @Schema(description = \"粉丝 openid\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"oz-Jdtyn-WGm4C4I5Z-nvBMO_ZfY\")\n    @NotEmpty(message = \"粉丝 openid 不能为空\")\n    private String openid;\n\n    @Schema(description = \"消息加密类型\", example = \"aes\")\n    private String encrypt_type;\n\n    @Schema(description = \"微信签名\", example = \"QW5kcm9pZCBUaGUgQmFzZTY0IGlzIGEgZ2VuZXJhdGVkIHN0cmluZw==\")\n    private String msg_signature;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/statistics/MpStatisticsController.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.statistics;\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.module.mp.controller.admin.statistics.vo.*;\nimport co.yixiang.yshop.module.mp.convert.statistics.MpStatisticsConvert;\nimport co.yixiang.yshop.module.mp.service.statistics.MpStatisticsService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport me.chanjar.weixin.mp.bean.datacube.WxDataCubeInterfaceResult;\nimport me.chanjar.weixin.mp.bean.datacube.WxDataCubeMsgResult;\nimport me.chanjar.weixin.mp.bean.datacube.WxDataCubeUserCumulate;\nimport me.chanjar.weixin.mp.bean.datacube.WxDataCubeUserSummary;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport jakarta.annotation.Resource;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 公众号统计\")\n@RestController\n@RequestMapping(\"/mp/statistics\")\n@Validated\npublic class MpStatisticsController {\n\n    @Resource\n    private MpStatisticsService mpStatisticsService;\n\n    @GetMapping(\"/user-summary\")\n    @Operation(summary = \"获得粉丝增减数据\")\n    @PreAuthorize(\"@ss.hasPermission('mp:statistics:query')\")\n    public CommonResult<List<MpStatisticsUserSummaryRespVO>> getUserSummary(MpStatisticsGetReqVO getReqVO) {\n        List<WxDataCubeUserSummary> list = mpStatisticsService.getUserSummary(\n                getReqVO.getAccountId(), getReqVO.getDate());\n        return success(MpStatisticsConvert.INSTANCE.convertList01(list));\n    }\n\n    @GetMapping(\"/user-cumulate\")\n    @Operation(summary = \"获得粉丝累计数据\")\n    @PreAuthorize(\"@ss.hasPermission('mp:statistics:query')\")\n    public CommonResult<List<MpStatisticsUserCumulateRespVO>> getUserCumulate(MpStatisticsGetReqVO getReqVO) {\n        List<WxDataCubeUserCumulate> list = mpStatisticsService.getUserCumulate(\n                getReqVO.getAccountId(), getReqVO.getDate());\n        return success(MpStatisticsConvert.INSTANCE.convertList02(list));\n    }\n\n    @GetMapping(\"/upstream-message\")\n    @Operation(summary = \"获取消息发送概况数据\")\n    @PreAuthorize(\"@ss.hasPermission('mp:statistics:query')\")\n    public CommonResult<List<MpStatisticsUpstreamMessageRespVO>> getUpstreamMessage(MpStatisticsGetReqVO getReqVO) {\n        List<WxDataCubeMsgResult> list = mpStatisticsService.getUpstreamMessage(\n                getReqVO.getAccountId(), getReqVO.getDate());\n        return success(MpStatisticsConvert.INSTANCE.convertList03(list));\n    }\n\n    @GetMapping(\"/interface-summary\")\n    @Operation(summary = \"获取消息发送概况数据\")\n    @PreAuthorize(\"@ss.hasPermission('mp:statistics:query')\")\n    public CommonResult<List<MpStatisticsInterfaceSummaryRespVO>> getInterfaceSummary(MpStatisticsGetReqVO getReqVO) {\n        List<WxDataCubeInterfaceResult> list = mpStatisticsService.getInterfaceSummary(\n                getReqVO.getAccountId(), getReqVO.getDate());\n        return success(MpStatisticsConvert.INSTANCE.convertList04(list));\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/statistics/vo/MpStatisticsGetReqVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.statistics.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotNull;\nimport lombok.Data;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 获得统计数据 Request VO\")\n@Data\npublic class MpStatisticsGetReqVO {\n\n    @Schema(description = \"公众号账号的编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @NotNull(message = \"公众号账号的编号不能为空\")\n    private Long accountId;\n\n    @Schema(description = \"查询时间范围\", example = \"[2022-07-01 00:00:00, 2022-07-01 23:59:59]\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    @NotNull(message = \"查询时间范围不能为空\")\n    private LocalDateTime[] date;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/statistics/vo/MpStatisticsInterfaceSummaryRespVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.statistics.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 某一天的接口分析数据 Response VO\")\n@Data\npublic class MpStatisticsInterfaceSummaryRespVO {\n\n    @Schema(description = \"日期\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private LocalDateTime refDate;\n\n    @Schema(description = \"通过服务器配置地址获得消息后，被动回复粉丝消息的次数\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"10\")\n    private Integer callbackCount;\n\n    @Schema(description = \"上述动作的失败次数\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"20\")\n    private Integer failCount;\n\n    @Schema(description = \"总耗时，除以 callback_count 即为平均耗时\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"30\")\n    private Integer totalTimeCost;\n\n    @Schema(description = \"最大耗时\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"40\")\n    private Integer maxTimeCost;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/statistics/vo/MpStatisticsUpstreamMessageRespVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.statistics.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 某一天的粉丝增减数据 Response VO\")\n@Data\npublic class MpStatisticsUpstreamMessageRespVO {\n\n    @Schema(description = \"日期\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private LocalDateTime refDate;\n\n    @Schema(description = \"上行发送了（向公众号发送了）消息的粉丝数\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"10\")\n    private Integer messageUser;\n\n    @Schema(description = \"上行发送了消息的消息总数\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"20\")\n    private Integer messageCount;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/statistics/vo/MpStatisticsUserCumulateRespVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.statistics.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 某一天的消息发送概况数据 Response VO\")\n@Data\npublic class MpStatisticsUserCumulateRespVO {\n\n    @Schema(description = \"日期\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private LocalDateTime refDate;\n\n    @Schema(description = \"累计粉丝量\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"10\")\n    private Integer cumulateUser;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/statistics/vo/MpStatisticsUserSummaryRespVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.statistics.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 某一天的粉丝增减数据 Response VO\")\n@Data\npublic class MpStatisticsUserSummaryRespVO {\n\n    @Schema(description = \"日期\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private LocalDateTime refDate;\n\n    @Schema(description = \"粉丝来源\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"0\")\n    private Integer userSource;\n\n    @Schema(description = \"新关注的粉丝数量\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"10\")\n    private Integer newUser;\n\n    @Schema(description = \"取消关注的粉丝数量\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"20\")\n    private Integer cancelUser;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/tag/MpTagController.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.tag;\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.mp.controller.admin.tag.vo.*;\nimport co.yixiang.yshop.module.mp.convert.tag.MpTagConvert;\nimport co.yixiang.yshop.module.mp.dal.dataobject.tag.MpTagDO;\nimport co.yixiang.yshop.module.mp.service.tag.MpTagService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Valid;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 公众号标签\")\n@RestController\n@RequestMapping(\"/mp/tag\")\n@Validated\npublic class MpTagController {\n\n    @Resource\n    private MpTagService mpTagService;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建公众号标签\")\n    @PreAuthorize(\"@ss.hasPermission('mp:tag:create')\")\n    public CommonResult<Long> createTag(@Valid @RequestBody MpTagCreateReqVO createReqVO) {\n        return success(mpTagService.createTag(createReqVO));\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"更新公众号标签\")\n    @PreAuthorize(\"@ss.hasPermission('mp:tag:update')\")\n    public CommonResult<Boolean> updateTag(@Valid @RequestBody MpTagUpdateReqVO updateReqVO) {\n        mpTagService.updateTag(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除公众号标签\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('mp:tag:delete')\")\n    public CommonResult<Boolean> deleteTag(@RequestParam(\"id\") Long id) {\n        mpTagService.deleteTag(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获取公众号标签详情\")\n    @PreAuthorize(\"@ss.hasPermission('mp:tag:query')\")\n    public CommonResult<MpTagRespVO> get(@RequestParam(\"id\") Long id) {\n        MpTagDO mpTagDO = mpTagService.get(id);\n        return success(MpTagConvert.INSTANCE.convert(mpTagDO));\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获取公众号标签分页\")\n    @PreAuthorize(\"@ss.hasPermission('mp:tag:query')\")\n    public CommonResult<PageResult<MpTagRespVO>> getTagPage(MpTagPageReqVO pageReqVO) {\n        PageResult<MpTagDO> pageResult = mpTagService.getTagPage(pageReqVO);\n        return success(MpTagConvert.INSTANCE.convertPage(pageResult));\n    }\n\n    @GetMapping(\"/list-all-simple\")\n    @Operation(summary = \"获取公众号账号精简信息列表\")\n    @PreAuthorize(\"@ss.hasPermission('mp:account:query')\")\n    public CommonResult<List<MpTagSimpleRespVO>> getSimpleTags() {\n        List<MpTagDO> list = mpTagService.getTagList();\n        return success(MpTagConvert.INSTANCE.convertList02(list));\n    }\n\n    @PostMapping(\"/sync\")\n    @Operation(summary = \"同步公众号标签\")\n    @Parameter(name = \"accountId\", description = \"公众号账号的编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('mp:tag:sync')\")\n    public CommonResult<Boolean> syncTag(@RequestParam(\"accountId\") Long accountId) {\n        mpTagService.syncTag(accountId);\n        return success(true);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/tag/vo/MpTagBaseVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.tag.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotEmpty;\n\n/**\n * 公众号标签 Base VO，提供给添加、修改、详细的子 VO 使用\n * 如果子 VO 存在差异的字段，请不要添加到这里，影响 Swagger 文档生成\n *\n * @author fengdan\n */\n@Data\npublic class MpTagBaseVO {\n\n    @Schema(description = \"标签名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"土豆\")\n    @NotEmpty(message = \"标签名不能为空\")\n    private String name;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/tag/vo/MpTagCreateReqVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.tag.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\n\nimport jakarta.validation.constraints.NotNull;\n\n@Schema(description = \"管理后台 - 公众号标签创建 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class MpTagCreateReqVO extends MpTagBaseVO {\n\n    @Schema(description = \"公众号账号的编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"2048\")\n    @NotNull(message = \"公众号账号的编号不能为空\")\n    private Long accountId;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/tag/vo/MpTagPageReqVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.tag.vo;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\n\nimport jakarta.validation.constraints.NotEmpty;\n\n@Schema(description = \"管理后台 - 公众号标签分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class MpTagPageReqVO extends PageParam {\n\n    @Schema(description = \"公众号账号的编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"2048\")\n    @NotEmpty(message = \"公众号账号的编号不能为空\")\n    private Long accountId;\n\n    @Schema(description = \"标签名，模糊匹配\", example = \"哈哈\")\n    private String name;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/tag/vo/MpTagRespVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.tag.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\n\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 公众号标签 Response VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class MpTagRespVO extends MpTagBaseVO {\n\n    @Schema(description = \"编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"此标签下粉丝数量\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"0\")\n    private Integer count;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/tag/vo/MpTagSimpleRespVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.tag.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\n@Schema(description = \"管理后台 - 公众号标签精简信息 Response VO\")\n@Data\npublic class MpTagSimpleRespVO {\n\n    @Schema(description = \"编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"公众号的标签编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"2048\")\n    private Long tagId;\n\n    @Schema(description = \"标签名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"快乐\")\n    private String name;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/tag/vo/MpTagUpdateReqVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.tag.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\n\nimport jakarta.validation.constraints.NotNull;\n\n@Schema(description = \"管理后台 - 公众号标签更新 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class MpTagUpdateReqVO extends MpTagBaseVO {\n\n    @Schema(description = \"编号\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @NotNull(message = \"编号不能为空\")\n    private Long id;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/user/MpUserController.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.user;\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.mp.controller.admin.user.vo.MpUserPageReqVO;\nimport co.yixiang.yshop.module.mp.controller.admin.user.vo.MpUserRespVO;\nimport co.yixiang.yshop.module.mp.controller.admin.user.vo.MpUserUpdateReqVO;\nimport co.yixiang.yshop.module.mp.convert.user.MpUserConvert;\nimport co.yixiang.yshop.module.mp.dal.dataobject.user.MpUserDO;\nimport co.yixiang.yshop.module.mp.service.user.MpUserService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Valid;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 公众号粉丝\")\n@RestController\n@RequestMapping(\"/mp/user\")\n@Validated\npublic class MpUserController {\n\n    @Resource\n    private MpUserService mpUserService;\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得公众号粉丝分页\")\n    @PreAuthorize(\"@ss.hasPermission('mp:user:query')\")\n    public CommonResult<PageResult<MpUserRespVO>> getUserPage(@Valid MpUserPageReqVO pageVO) {\n        PageResult<MpUserDO> pageResult = mpUserService.getUserPage(pageVO);\n        return success(MpUserConvert.INSTANCE.convertPage(pageResult));\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得公众号粉丝\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('mp:user:query')\")\n    public CommonResult<MpUserRespVO> getUser(@RequestParam(\"id\") Long id) {\n        return success(MpUserConvert.INSTANCE.convert(mpUserService.getUser(id)));\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"更新公众号粉丝\")\n    @PreAuthorize(\"@ss.hasPermission('mp:user:update')\")\n    public CommonResult<Boolean> updateUser(@Valid @RequestBody MpUserUpdateReqVO updateReqVO) {\n        mpUserService.updateUser(updateReqVO);\n        return success(true);\n    }\n\n    @PostMapping(\"/sync\")\n    @Operation(summary = \"同步公众号粉丝\")\n    @Parameter(name = \"accountId\", description = \"公众号账号的编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('mp:user:sync')\")\n    public CommonResult<Boolean> syncUser(@RequestParam(\"accountId\") Long accountId) {\n        mpUserService.syncUser(accountId);\n        return success(true);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/user/vo/MpUserPageReqVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.user.vo;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\n\nimport jakarta.validation.constraints.NotNull;\n\n@Schema(description = \"管理后台 - 公众号粉丝分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class MpUserPageReqVO extends PageParam {\n\n    @Schema(description = \"公众号账号的编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"2048\")\n    @NotNull(message = \"公众号账号的编号不能为空\")\n    private Long accountId;\n\n    @Schema(description = \"公众号粉丝标识，模糊匹配\", example = \"o6_bmjrPTlm6_2sgVt7hMZOPfL2M\")\n    private String openid;\n\n    @Schema(description = \"微信生态唯一标识，模糊匹配\", example = \"o6_bmjrPTlm6_2sgVt7hMZOPfL2M\")\n    private String unionId;\n\n    @Schema(description = \"公众号粉丝昵称，模糊匹配\", example = \"yshop\")\n    private String nickname;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/user/vo/MpUserRespVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.user.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\nimport java.util.List;\n\n@Schema(description = \"管理后台 - 公众号粉丝 Response VO\")\n@Data\npublic class MpUserRespVO  {\n\n    @Schema(description = \"编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"公众号粉丝标识\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"o6_bmjrPTlm6_2sgVt7hMZOPfL2M\")\n    private String openid;\n\n    @Schema(description = \"微信生态唯一标识\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"o6_bmjrPTlm6_2sgVt7hMZOPfL2M\")\n    private String unionId;\n\n    @Schema(description = \"关注状态 参见 CommonStatusEnum 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    private Integer subscribeStatus;\n    @Schema(description = \"关注时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private LocalDateTime subscribeTime;\n    @Schema(description = \"取消关注时间\")\n    private LocalDateTime unsubscribeTime;\n\n    @Schema(description = \"昵称\", example = \"yshop\")\n    private String nickname;\n    @Schema(description = \"头像地址\", example = \"https://www.yixiang.co/1.png\")\n    private String headImageUrl;\n    @Schema(description = \"语言\", example = \"zh_CN\")\n    private String language;\n    @Schema(description = \"国家\", example = \"中国\")\n    private String country;\n    @Schema(description = \"省份\", example = \"广东省\")\n    private String province;\n    @Schema(description = \"城市\", example = \"广州市\")\n    private String city;\n    @Schema(description = \"备注\", example = \"你是一个芋头嘛\")\n    private String remark;\n\n    @Schema(description = \"标签编号数组\", example = \"1,2,3\")\n    private List<Long> tagIds;\n\n    @Schema(description = \"公众号账号的编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    private Long accountId;\n    @Schema(description = \"公众号账号的 appId\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"wx1234567890\")\n    private String appId;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/controller/admin/user/vo/MpUserUpdateReqVO.java",
    "content": "package co.yixiang.yshop.module.mp.controller.admin.user.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotNull;\nimport java.util.List;\n\n@Schema(description = \"管理后台 - 公众号粉丝更新 Request VO\")\n@Data\npublic class MpUserUpdateReqVO {\n\n    @Schema(description = \"编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @NotNull(message = \"编号不能为空\")\n    private Long id;\n\n    @Schema(description = \"昵称\", example = \"yshop\")\n    private String nickname;\n\n    @Schema(description = \"备注\", example = \"你是一个芋头嘛\")\n    private String remark;\n\n    @Schema(description = \"标签编号数组\", example = \"1,2,3\")\n    private List<Long> tagIds;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/convert/account/MpAccountConvert.java",
    "content": "package co.yixiang.yshop.module.mp.convert.account;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.mp.controller.admin.account.vo.MpAccountCreateReqVO;\nimport co.yixiang.yshop.module.mp.controller.admin.account.vo.MpAccountRespVO;\nimport co.yixiang.yshop.module.mp.controller.admin.account.vo.MpAccountSimpleRespVO;\nimport co.yixiang.yshop.module.mp.controller.admin.account.vo.MpAccountUpdateReqVO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.account.MpAccountDO;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.factory.Mappers;\n\nimport java.util.List;\n\n@Mapper\npublic interface MpAccountConvert {\n\n    MpAccountConvert INSTANCE = Mappers.getMapper(MpAccountConvert.class);\n\n    MpAccountDO convert(MpAccountCreateReqVO bean);\n\n    MpAccountDO convert(MpAccountUpdateReqVO bean);\n\n    MpAccountRespVO convert(MpAccountDO bean);\n\n    List<MpAccountRespVO> convertList(List<MpAccountDO> list);\n\n    PageResult<MpAccountRespVO> convertPage(PageResult<MpAccountDO> page);\n\n    List<MpAccountSimpleRespVO> convertList02(List<MpAccountDO> list);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/convert/material/MpMaterialConvert.java",
    "content": "package co.yixiang.yshop.module.mp.convert.material;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.mp.controller.admin.material.vo.MpMaterialRespVO;\nimport co.yixiang.yshop.module.mp.controller.admin.material.vo.MpMaterialUploadRespVO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.account.MpAccountDO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.material.MpMaterialDO;\nimport me.chanjar.weixin.mp.bean.material.WxMpMaterial;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.Mapping;\nimport org.mapstruct.Mappings;\nimport org.mapstruct.factory.Mappers;\n\nimport java.io.File;\n\n@Mapper\npublic interface MpMaterialConvert {\n\n    MpMaterialConvert INSTANCE = Mappers.getMapper(MpMaterialConvert.class);\n\n    @Mappings({\n            @Mapping(target = \"id\", ignore = true),\n            @Mapping(source = \"account.id\", target = \"accountId\"),\n            @Mapping(source = \"account.appId\", target = \"appId\"),\n            @Mapping(source = \"name\", target = \"name\")\n    })\n    MpMaterialDO convert(String mediaId, String type, String url, MpAccountDO account,\n                         String name);\n\n    @Mappings({\n            @Mapping(target = \"id\", ignore = true),\n            @Mapping(source = \"account.id\", target = \"accountId\"),\n            @Mapping(source = \"account.appId\", target = \"appId\"),\n            @Mapping(source = \"name\", target = \"name\")\n    })\n    MpMaterialDO convert(String mediaId, String type, String url, MpAccountDO account,\n                         String name, String title, String introduction, String mpUrl);\n\n    MpMaterialUploadRespVO convert(MpMaterialDO bean);\n\n    default WxMpMaterial convert(String name, File file, String title, String introduction) {\n        return new WxMpMaterial(name, file, title, introduction);\n    }\n\n    PageResult<MpMaterialRespVO> convertPage(PageResult<MpMaterialDO> page);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/convert/menu/MpMenuConvert.java",
    "content": "package co.yixiang.yshop.module.mp.convert.menu;\n\nimport co.yixiang.yshop.module.mp.controller.admin.menu.vo.MpMenuRespVO;\nimport co.yixiang.yshop.module.mp.controller.admin.menu.vo.MpMenuSaveReqVO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.menu.MpMenuDO;\nimport co.yixiang.yshop.module.mp.service.message.bo.MpMessageSendOutReqBO;\nimport me.chanjar.weixin.common.bean.menu.WxMenuButton;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.Mapping;\nimport org.mapstruct.Mappings;\nimport org.mapstruct.factory.Mappers;\n\nimport java.util.List;\n\n@Mapper\npublic interface MpMenuConvert {\n\n    MpMenuConvert INSTANCE = Mappers.getMapper(MpMenuConvert.class);\n\n    MpMenuRespVO convert(MpMenuDO bean);\n\n    List<MpMenuRespVO> convertList(List<MpMenuDO> list);\n\n    @Mappings({\n            @Mapping(source = \"menu.appId\", target = \"appId\"),\n            @Mapping(source = \"menu.replyMessageType\", target = \"type\"),\n            @Mapping(source = \"menu.replyContent\", target = \"content\"),\n            @Mapping(source = \"menu.replyMediaId\", target = \"mediaId\"),\n            @Mapping(source = \"menu.replyThumbMediaId\", target = \"thumbMediaId\"),\n            @Mapping(source = \"menu.replyTitle\", target = \"title\"),\n            @Mapping(source = \"menu.replyDescription\", target = \"description\"),\n            @Mapping(source = \"menu.replyArticles\", target = \"articles\"),\n            @Mapping(source = \"menu.replyMusicUrl\", target = \"musicUrl\"),\n            @Mapping(source = \"menu.replyHqMusicUrl\", target = \"hqMusicUrl\"),\n    })\n    MpMessageSendOutReqBO convert(String openid, MpMenuDO menu);\n\n    List<WxMenuButton> convert(List<MpMenuSaveReqVO.Menu> list);\n\n    @Mappings({\n            @Mapping(source = \"menuKey\", target = \"key\"),\n            @Mapping(source = \"children\", target = \"subButtons\"),\n            @Mapping(source = \"miniProgramAppId\", target = \"appId\"),\n            @Mapping(source = \"miniProgramPagePath\", target = \"pagePath\"),\n    })\n    WxMenuButton convert(MpMenuSaveReqVO.Menu bean);\n\n    MpMenuDO convert02(MpMenuSaveReqVO.Menu menu);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/convert/message/MpAutoReplyConvert.java",
    "content": "package co.yixiang.yshop.module.mp.convert.message;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.mp.controller.admin.message.vo.autoreply.MpAutoReplyCreateReqVO;\nimport co.yixiang.yshop.module.mp.controller.admin.message.vo.autoreply.MpAutoReplyRespVO;\nimport co.yixiang.yshop.module.mp.controller.admin.message.vo.autoreply.MpAutoReplyUpdateReqVO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.message.MpAutoReplyDO;\nimport co.yixiang.yshop.module.mp.service.message.bo.MpMessageSendOutReqBO;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.Mapping;\nimport org.mapstruct.Mappings;\nimport org.mapstruct.factory.Mappers;\n\n@Mapper\npublic interface MpAutoReplyConvert {\n\n    MpAutoReplyConvert INSTANCE = Mappers.getMapper(MpAutoReplyConvert.class);\n\n    @Mappings({\n            @Mapping(source = \"reply.appId\", target = \"appId\"),\n            @Mapping(source = \"reply.responseMessageType\", target = \"type\"),\n            @Mapping(source = \"reply.responseContent\", target = \"content\"),\n            @Mapping(source = \"reply.responseMediaId\", target = \"mediaId\"),\n            @Mapping(source = \"reply.responseTitle\", target = \"title\"),\n            @Mapping(source = \"reply.responseDescription\", target = \"description\"),\n            @Mapping(source = \"reply.responseArticles\", target = \"articles\"),\n    })\n    MpMessageSendOutReqBO convert(String openid, MpAutoReplyDO reply);\n\n    PageResult<MpAutoReplyRespVO> convertPage(PageResult<MpAutoReplyDO> page);\n\n    MpAutoReplyRespVO convert(MpAutoReplyDO bean);\n\n    MpAutoReplyDO convert(MpAutoReplyCreateReqVO bean);\n\n    MpAutoReplyDO convert(MpAutoReplyUpdateReqVO bean);\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/convert/message/MpMessageConvert.java",
    "content": "package co.yixiang.yshop.module.mp.convert.message;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.mp.controller.admin.message.vo.message.MpMessageRespVO;\nimport co.yixiang.yshop.module.mp.controller.admin.message.vo.message.MpMessageSendReqVO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.account.MpAccountDO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.message.MpMessageDO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.user.MpUserDO;\nimport co.yixiang.yshop.module.mp.service.message.bo.MpMessageSendOutReqBO;\nimport me.chanjar.weixin.common.api.WxConsts;\nimport me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage;\nimport me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;\nimport me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;\nimport me.chanjar.weixin.mp.bean.message.WxMpXmlOutNewsMessage;\nimport me.chanjar.weixin.mp.builder.outxml.BaseBuilder;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.Mapping;\nimport org.mapstruct.Mappings;\nimport org.mapstruct.factory.Mappers;\n\nimport java.util.List;\n\n@Mapper\npublic interface MpMessageConvert {\n\n    MpMessageConvert INSTANCE = Mappers.getMapper(MpMessageConvert.class);\n\n    MpMessageRespVO convert(MpMessageDO bean);\n\n    List<MpMessageRespVO> convertList(List<MpMessageDO> list);\n\n    PageResult<MpMessageRespVO> convertPage(PageResult<MpMessageDO> page);\n\n    default MpMessageDO convert(WxMpXmlMessage wxMessage, MpAccountDO account, MpUserDO user) {\n        MpMessageDO message = convert(wxMessage);\n        if (account != null) {\n            message.setAccountId(account.getId()).setAppId(account.getAppId());\n        }\n        if (user != null) {\n            message.setUserId(user.getId()).setOpenid(user.getOpenid());\n        }\n        return message;\n    }\n    @Mappings(value = {\n            @Mapping(source = \"msgType\", target = \"type\"),\n            @Mapping(target = \"createTime\", ignore = true),\n    })\n    MpMessageDO convert(WxMpXmlMessage bean);\n\n    default MpMessageDO convert(MpMessageSendOutReqBO sendReqBO, MpAccountDO account, MpUserDO user) {\n        // 构建消息\n        MpMessageDO message = new MpMessageDO();\n        message.setType(sendReqBO.getType());\n        switch (sendReqBO.getType()) {\n            case WxConsts.XmlMsgType.TEXT: // 1. 文本\n                message.setContent(sendReqBO.getContent());\n                break;\n            case WxConsts.XmlMsgType.IMAGE: // 2. 图片\n            case WxConsts.XmlMsgType.VOICE: // 3. 语音\n                message.setMediaId(sendReqBO.getMediaId());\n                break;\n            case WxConsts.XmlMsgType.VIDEO: // 4. 视频\n                message.setMediaId(sendReqBO.getMediaId())\n                        .setTitle(sendReqBO.getTitle()).setDescription(sendReqBO.getDescription());\n                break;\n            case WxConsts.XmlMsgType.NEWS: // 5. 图文\n                message.setArticles(sendReqBO.getArticles());\n            case WxConsts.XmlMsgType.MUSIC: // 6. 音乐\n                message.setTitle(sendReqBO.getTitle()).setDescription(sendReqBO.getDescription())\n                        .setMusicUrl(sendReqBO.getMusicUrl()).setHqMusicUrl(sendReqBO.getHqMusicUrl())\n                        .setThumbMediaId(sendReqBO.getThumbMediaId());\n                break;\n            default:\n                throw new IllegalArgumentException(\"不支持的消息类型：\" + message.getType());\n        }\n\n        // 其它字段\n        if (account != null) {\n            message.setAccountId(account.getId()).setAppId(account.getAppId());\n        }\n        if (user != null) {\n            message.setUserId(user.getId()).setOpenid(user.getOpenid());\n        }\n        return message;\n    }\n\n    default WxMpXmlOutMessage convert02(MpMessageDO message, MpAccountDO account) {\n        BaseBuilder<?, ? extends WxMpXmlOutMessage> builder;\n        // 个性化字段\n        switch (message.getType()) {\n            case WxConsts.XmlMsgType.TEXT:\n                builder = WxMpXmlOutMessage.TEXT().content(message.getContent());\n                break;\n            case WxConsts.XmlMsgType.IMAGE:\n                builder = WxMpXmlOutMessage.IMAGE().mediaId(message.getMediaId());\n                break;\n            case WxConsts.XmlMsgType.VOICE:\n                builder = WxMpXmlOutMessage.VOICE().mediaId(message.getMediaId());\n                break;\n            case WxConsts.XmlMsgType.VIDEO:\n                builder = WxMpXmlOutMessage.VIDEO().mediaId(message.getMediaId())\n                        .title(message.getTitle()).description(message.getDescription());\n                break;\n            case WxConsts.XmlMsgType.NEWS:\n                builder = WxMpXmlOutMessage.NEWS().articles(convertList02(message.getArticles()));\n                break;\n            case WxConsts.XmlMsgType.MUSIC:\n                builder = WxMpXmlOutMessage.MUSIC().title(message.getTitle()).description(message.getDescription())\n                        .musicUrl(message.getMusicUrl()).hqMusicUrl(message.getHqMusicUrl())\n                        .thumbMediaId(message.getThumbMediaId());\n                break;\n            default:\n                throw new IllegalArgumentException(\"不支持的消息类型：\" + message.getType());\n        }\n        // 通用字段\n        builder.fromUser(account.getAccount());\n        builder.toUser(message.getOpenid());\n        return builder.build();\n    }\n    List<WxMpXmlOutNewsMessage.Item> convertList02(List<MpMessageDO.Article> list);\n\n    default WxMpKefuMessage convert(MpMessageSendReqVO sendReqVO, MpUserDO user) {\n        me.chanjar.weixin.mp.builder.kefu.BaseBuilder<?> builder;\n        // 个性化字段\n        switch (sendReqVO.getType()) {\n            case WxConsts.KefuMsgType.TEXT:\n                builder = WxMpKefuMessage.TEXT().content(sendReqVO.getContent());\n                break;\n            case WxConsts.KefuMsgType.IMAGE:\n                builder = WxMpKefuMessage.IMAGE().mediaId(sendReqVO.getMediaId());\n                break;\n            case WxConsts.KefuMsgType.VOICE:\n                builder = WxMpKefuMessage.VOICE().mediaId(sendReqVO.getMediaId());\n                break;\n            case WxConsts.KefuMsgType.VIDEO:\n                builder = WxMpKefuMessage.VIDEO().mediaId(sendReqVO.getMediaId())\n                        .title(sendReqVO.getTitle()).description(sendReqVO.getDescription());\n                break;\n            case WxConsts.KefuMsgType.NEWS:\n                builder = WxMpKefuMessage.NEWS().articles(convertList03(sendReqVO.getArticles()));\n                break;\n            case WxConsts.KefuMsgType.MUSIC:\n                builder = WxMpKefuMessage.MUSIC().title(sendReqVO.getTitle()).description(sendReqVO.getDescription())\n                        .thumbMediaId(sendReqVO.getThumbMediaId())\n                        .musicUrl(sendReqVO.getMusicUrl()).hqMusicUrl(sendReqVO.getHqMusicUrl());\n                break;\n            default:\n                throw new IllegalArgumentException(\"不支持的消息类型：\" + sendReqVO.getType());\n        }\n        // 通用字段\n        builder.toUser(user.getOpenid());\n        return builder.build();\n    }\n    List<WxMpKefuMessage.WxArticle> convertList03(List<MpMessageDO.Article> list);\n\n    default MpMessageDO convert(WxMpKefuMessage wxMessage, MpAccountDO account, MpUserDO user) {\n        MpMessageDO message = convert(wxMessage);\n        if (account != null) {\n            message.setAccountId(account.getId()).setAppId(account.getAppId());\n        }\n        if (user != null) {\n            message.setUserId(user.getId()).setOpenid(user.getOpenid());\n        }\n        return message;\n    }\n    @Mappings(value = {\n            @Mapping(source = \"msgType\", target = \"type\"),\n            @Mapping(target = \"createTime\", ignore = true),\n    })\n    MpMessageDO convert(WxMpKefuMessage bean);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/convert/statistics/MpStatisticsConvert.java",
    "content": "package co.yixiang.yshop.module.mp.convert.statistics;\n\nimport co.yixiang.yshop.module.mp.controller.admin.statistics.vo.MpStatisticsInterfaceSummaryRespVO;\nimport co.yixiang.yshop.module.mp.controller.admin.statistics.vo.MpStatisticsUpstreamMessageRespVO;\nimport co.yixiang.yshop.module.mp.controller.admin.statistics.vo.MpStatisticsUserCumulateRespVO;\nimport co.yixiang.yshop.module.mp.controller.admin.statistics.vo.MpStatisticsUserSummaryRespVO;\nimport me.chanjar.weixin.mp.bean.datacube.WxDataCubeInterfaceResult;\nimport me.chanjar.weixin.mp.bean.datacube.WxDataCubeMsgResult;\nimport me.chanjar.weixin.mp.bean.datacube.WxDataCubeUserCumulate;\nimport me.chanjar.weixin.mp.bean.datacube.WxDataCubeUserSummary;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.Mapping;\nimport org.mapstruct.Mappings;\nimport org.mapstruct.Named;\nimport org.mapstruct.factory.Mappers;\n\nimport java.time.LocalDate;\nimport java.time.LocalDateTime;\nimport java.time.format.DateTimeFormatter;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;\n\n@Mapper\npublic interface MpStatisticsConvert {\n\n    MpStatisticsConvert INSTANCE = Mappers.getMapper(MpStatisticsConvert.class);\n\n    List<MpStatisticsUserSummaryRespVO> convertList01(List<WxDataCubeUserSummary> list);\n\n    List<MpStatisticsUserCumulateRespVO> convertList02(List<WxDataCubeUserCumulate> list);\n\n    List<MpStatisticsUpstreamMessageRespVO> convertList03(List<WxDataCubeMsgResult> list);\n\n    @Mappings({\n            @Mapping(target = \"refDate\", expression = \"java(dateFormat0(bean.getRefDate()))\"),\n            @Mapping(source = \"msgUser\", target = \"messageUser\"),\n            @Mapping(source = \"msgCount\", target = \"messageCount\"),\n    })\n    MpStatisticsUpstreamMessageRespVO convert(WxDataCubeMsgResult bean);\n\n    List<MpStatisticsInterfaceSummaryRespVO> convertList04(List<WxDataCubeInterfaceResult> list);\n\n    @Mapping(target = \"refDate\", expression = \"java(dateFormat0(bean.getRefDate()))\")\n    MpStatisticsInterfaceSummaryRespVO convert(WxDataCubeInterfaceResult bean);\n\n    @Named(\"dateFormat0\")\n    default LocalDateTime dateFormat0(String date) {\n        return LocalDate.parse(date, DateTimeFormatter.ofPattern(FORMAT_YEAR_MONTH_DAY)).atStartOfDay();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/convert/tag/MpTagConvert.java",
    "content": "package co.yixiang.yshop.module.mp.convert.tag;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.mp.controller.admin.tag.vo.MpTagRespVO;\nimport co.yixiang.yshop.module.mp.controller.admin.tag.vo.MpTagSimpleRespVO;\nimport co.yixiang.yshop.module.mp.controller.admin.tag.vo.MpTagUpdateReqVO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.account.MpAccountDO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.tag.MpTagDO;\nimport me.chanjar.weixin.mp.bean.tag.WxUserTag;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.Mapping;\nimport org.mapstruct.Mappings;\nimport org.mapstruct.factory.Mappers;\n\nimport java.util.List;\n\n@Mapper\npublic interface MpTagConvert {\n\n    MpTagConvert INSTANCE = Mappers.getMapper(MpTagConvert.class);\n\n    WxUserTag convert(MpTagUpdateReqVO bean);\n\n    MpTagRespVO convert(WxUserTag bean);\n\n    List<MpTagRespVO> convertList(List<WxUserTag> list);\n\n    PageResult<MpTagRespVO> convertPage(PageResult<MpTagDO> page);\n\n    @Mappings({\n            @Mapping(target = \"id\", ignore = true),\n            @Mapping(source = \"tag.id\", target = \"tagId\"),\n            @Mapping(source = \"tag.name\", target = \"name\"),\n            @Mapping(source = \"tag.count\", target = \"count\"),\n            @Mapping(source = \"account.id\", target = \"accountId\"),\n            @Mapping(source = \"account.appId\", target = \"appId\"),\n    })\n    MpTagDO convert(WxUserTag tag, MpAccountDO account);\n\n    MpTagRespVO convert(MpTagDO mpTagDO);\n\n    List<MpTagSimpleRespVO> convertList02(List<MpTagDO> list);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/convert/user/MpUserConvert.java",
    "content": "package co.yixiang.yshop.module.mp.convert.user;\n\nimport cn.hutool.core.date.LocalDateTimeUtil;\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.collection.CollectionUtils;\nimport co.yixiang.yshop.module.mp.controller.admin.user.vo.MpUserRespVO;\nimport co.yixiang.yshop.module.mp.controller.admin.user.vo.MpUserUpdateReqVO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.account.MpAccountDO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.user.MpUserDO;\nimport me.chanjar.weixin.mp.bean.result.WxMpUser;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.Mapping;\nimport org.mapstruct.Mappings;\nimport org.mapstruct.factory.Mappers;\n\nimport java.util.List;\n\n@Mapper\npublic interface MpUserConvert {\n\n    MpUserConvert INSTANCE = Mappers.getMapper(MpUserConvert.class);\n\n    MpUserRespVO convert(MpUserDO bean);\n\n    List<MpUserRespVO> convertList(List<MpUserDO> list);\n\n    PageResult<MpUserRespVO> convertPage(PageResult<MpUserDO> page);\n\n    @Mappings(value = {\n            @Mapping(source = \"openId\", target = \"openid\"),\n            @Mapping(source = \"unionId\", target = \"unionId\"),\n            @Mapping(source = \"headImgUrl\", target = \"headImageUrl\"),\n            @Mapping(target = \"subscribeTime\", ignore = true), // 单独转换\n    })\n    MpUserDO convert(WxMpUser wxMpUser);\n\n    default MpUserDO convert(MpAccountDO account, WxMpUser wxMpUser) {\n        MpUserDO user = convert(wxMpUser);\n        user.setSubscribeStatus(wxMpUser.getSubscribe() ? CommonStatusEnum.ENABLE.getStatus()\n                : CommonStatusEnum.DISABLE.getStatus());\n        user.setSubscribeTime(LocalDateTimeUtil.of(wxMpUser.getSubscribeTime() * 1000L));\n        if (account != null) {\n            user.setAccountId(account.getId());\n            user.setAppId(account.getAppId());\n        }\n        return user;\n    }\n\n    default List<MpUserDO> convertList(MpAccountDO account, List<WxMpUser> wxUsers) {\n        return CollectionUtils.convertList(wxUsers, wxUser -> convert(account, wxUser));\n    }\n\n    MpUserDO convert(MpUserUpdateReqVO bean);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/dal/dataobject/account/MpAccountDO.java",
    "content": "package co.yixiang.yshop.module.mp.dal.dataobject.account;\n\nimport co.yixiang.yshop.framework.tenant.core.db.TenantBaseDO;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.*;\n\n/**\n * 公众号账号 DO\n *\n * @author yshop\n */\n@TableName(\"mp_account\")\n@KeySequence(\"mp_account_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class MpAccountDO extends TenantBaseDO {\n\n    /**\n     * 编号\n     */\n    @TableId\n    private Long id;\n    /**\n     * 公众号名称\n     */\n    private String name;\n    /**\n     * 公众号账号\n     */\n    private String account;\n    /**\n     * 公众号 appid\n     */\n    private String appId;\n    /**\n     * 公众号密钥\n     */\n    private String appSecret;\n    /**\n     * 公众号token\n     */\n    private String token;\n    /**\n     * 消息加解密密钥\n     */\n    private String aesKey;\n    /**\n     * 二维码图片 URL\n     */\n    private String qrCodeUrl;\n    /**\n     * 备注\n     */\n    private String remark;\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/dal/dataobject/material/MpMaterialDO.java",
    "content": "package co.yixiang.yshop.module.mp.dal.dataobject.material;\n\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.account.MpAccountDO;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.*;\nimport me.chanjar.weixin.common.api.WxConsts;\n\n/**\n * 公众号素材 DO\n *\n * 1. <a href=\"https://developers.weixin.qq.com/doc/offiaccount/Asset_Management/New_temporary_materials.html\">临时素材</a>\n * 2. <a href=\"https://developers.weixin.qq.com/doc/offiaccount/Asset_Management/Adding_Permanent_Assets.html\">永久素材</a>\n *\n * @author yshop\n */\n@TableName(\"mp_material\")\n@KeySequence(\"mp_material_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class MpMaterialDO extends BaseDO {\n\n    /**\n     * 主键\n     */\n    @TableId\n    private Long id;\n    /**\n     * 公众号账号的编号\n     *\n     * 关联 {@link MpAccountDO#getId()}\n     */\n    private Long accountId;\n    /**\n     * 公众号 appId\n     *\n     * 冗余 {@link MpAccountDO#getAppId()}\n     */\n    private String appId;\n\n    /**\n     * 公众号素材 id\n     */\n    private String mediaId;\n    /**\n     * 文件类型\n     *\n     * 枚举 {@link WxConsts.MediaFileType}\n     */\n    private String type;\n    /**\n     * 是否永久\n     *\n     * true - 永久素材\n     * false - 临时素材\n     */\n    private Boolean permanent;\n    /**\n     * 文件服务器的 URL\n     */\n    private String url;\n\n    /**\n     * 名字\n     *\n     * 永久素材：非空\n     * 临时素材：可能为空。\n     *      1. 为空的情况：粉丝主动发送的图片、语音等\n     *      2. 非空的情况：主动发送给粉丝的图片、语音等\n     */\n    private String name;\n\n    /**\n     * 公众号文件 URL\n     *\n     * 只有【永久素材】使用\n     */\n    private String mpUrl;\n\n    /**\n     * 视频素材的标题\n     *\n     * 只有【永久素材】使用\n     */\n    private String title;\n    /**\n     * 视频素材的描述\n     *\n     * 只有【永久素材】使用\n     */\n    private String introduction;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/dal/dataobject/menu/MpMenuDO.java",
    "content": "package co.yixiang.yshop.module.mp.dal.dataobject.menu;\n\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.account.MpAccountDO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.message.MpMessageDO;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\nimport me.chanjar.weixin.common.api.WxConsts;\nimport me.chanjar.weixin.common.api.WxConsts.MenuButtonType;\n\nimport java.util.List;\n\n/**\n * 公众号菜单 DO\n *\n * @author yshop\n */\n@TableName(value = \"mp_menu\", autoResultMap = true)\n@KeySequence(\"mp_menu_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class MpMenuDO extends BaseDO {\n\n    /**\n     * 编号 - 顶级菜单\n     */\n    public static final Long ID_ROOT = 0L;\n\n    /**\n     * 编号\n     */\n    @TableId\n    private Long id;\n    /**\n     * 公众号账号的编号\n     *\n     * 关联 {@link MpAccountDO#getId()}\n     */\n    private Long accountId;\n    /**\n     * 公众号 appId\n     *\n     * 冗余 {@link MpAccountDO#getAppId()}\n     */\n    private String appId;\n\n    /**\n     * 菜单名称\n     */\n    private String name;\n    /**\n     * 菜单标识\n     *\n     * 支持多 DB 类型时，无法直接使用 key + @TableField(\"menuKey\") 来实现转换，原因是 \"menuKey\" AS key 而存在报错\n     */\n    private String menuKey;\n    /**\n     * 父菜单编号\n     */\n    private Long parentId;\n\n    // ========== 按钮操作 ==========\n\n    /**\n     * 按钮类型\n     *\n     * 枚举 {@link MenuButtonType}\n     */\n    private String type;\n\n    /**\n     * 网页链接\n     *\n     * 粉丝点击菜单可打开链接，不超过 1024 字节\n     *\n     * 类型为 {@link WxConsts.XmlMsgType} 的 VIEW、MINIPROGRAM\n     */\n    private String url;\n\n    /**\n     * 小程序的 appId\n     *\n     * 类型为 {@link MenuButtonType} 的 MINIPROGRAM\n     */\n    private String miniProgramAppId;\n    /**\n     * 小程序的页面路径\n     *\n     * 类型为 {@link MenuButtonType} 的 MINIPROGRAM\n     */\n    private String miniProgramPagePath;\n\n    /**\n     * 跳转图文的媒体编号\n     */\n    private String articleId;\n\n    // ========== 消息内容 ==========\n\n    /**\n     * 消息类型\n     *\n     * 当 {@link #type} 为 CLICK、SCANCODE_WAITMSG\n     *\n     * 枚举 {@link WxConsts.XmlMsgType} 中的 TEXT、IMAGE、VOICE、VIDEO、NEWS、MUSIC\n     */\n    private String replyMessageType;\n\n    /**\n     * 回复的消息内容\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 TEXT\n     */\n    private String replyContent;\n\n    /**\n     * 回复的媒体 id\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO\n     */\n    private String replyMediaId;\n    /**\n     * 回复的媒体 URL\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO\n     */\n    private String replyMediaUrl;\n\n    /**\n     * 回复的标题\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO\n     */\n    private String replyTitle;\n    /**\n     * 回复的描述\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO\n     */\n    private String replyDescription;\n\n    /**\n     * 回复的缩略图的媒体 id，通过素材管理中的接口上传多媒体文件，得到的 id\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC、VIDEO\n     */\n    private String replyThumbMediaId;\n    /**\n     * 回复的缩略图的媒体 URL\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC、VIDEO\n     */\n    private String replyThumbMediaUrl;\n\n    /**\n     * 回复的图文消息数组\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS\n     */\n    @TableField(typeHandler = MpMessageDO.ArticleTypeHandler.class)\n    private List<MpMessageDO.Article> replyArticles;\n\n    /**\n     * 回复的音乐链接\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC\n     */\n    private String replyMusicUrl;\n    /**\n     * 回复的高质量音乐链接\n     *\n     * WIFI 环境优先使用该链接播放音乐\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC\n     */\n    private String replyHqMusicUrl;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/dal/dataobject/message/MpAutoReplyDO.java",
    "content": "package co.yixiang.yshop.module.mp.dal.dataobject.message;\n\nimport co.yixiang.yshop.framework.common.util.collection.SetUtils;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.account.MpAccountDO;\nimport co.yixiang.yshop.module.mp.enums.message.MpAutoReplyMatchEnum;\nimport co.yixiang.yshop.module.mp.enums.message.MpAutoReplyTypeEnum;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\nimport me.chanjar.weixin.common.api.WxConsts;\nimport me.chanjar.weixin.common.api.WxConsts.XmlMsgType;\n\nimport java.util.List;\nimport java.util.Set;\n\n/**\n * 公众号消息自动回复 DO\n *\n * @author yshop\n */\n@TableName(value = \"mp_auto_reply\", autoResultMap = true)\n@KeySequence(\"mp_auto_reply_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class MpAutoReplyDO extends BaseDO {\n\n    public static Set<String> REQUEST_MESSAGE_TYPE = SetUtils.asSet(WxConsts.XmlMsgType.TEXT, WxConsts.XmlMsgType.IMAGE,\n            WxConsts.XmlMsgType.VOICE, WxConsts.XmlMsgType.VIDEO, WxConsts.XmlMsgType.SHORTVIDEO,\n            WxConsts.XmlMsgType.LOCATION, WxConsts.XmlMsgType.LINK);\n\n    /**\n     * 主键\n     */\n    @TableId\n    private Long id;\n    /**\n     * 公众号账号的编号\n     *\n     * 关联 {@link MpAccountDO#getId()}\n     */\n    private Long accountId;\n    /**\n     * 公众号 appId\n     *\n     * 冗余 {@link MpAccountDO#getAppId()}\n     */\n    private String appId;\n\n    /**\n     * 回复类型\n     *\n     * 枚举 {@link MpAutoReplyTypeEnum}\n     */\n    private Integer type;\n\n    // ==================== 请求消息 ====================\n\n    /**\n     * 请求的关键字\n     *\n     * 当 {@link #type} 为 {@link MpAutoReplyTypeEnum#KEYWORD}\n     */\n    private String requestKeyword;\n    /**\n     * 请求的关键字的匹配\n     *\n     * 当 {@link #type} 为 {@link MpAutoReplyTypeEnum#KEYWORD}\n     *\n     * 枚举 {@link MpAutoReplyMatchEnum}\n     */\n    private Integer requestMatch;\n\n    /**\n     * 请求的消息类型\n     *\n     * 当 {@link #type} 为 {@link MpAutoReplyTypeEnum#MESSAGE}\n     *\n     * 枚举 {@link XmlMsgType} 中的 {@link #REQUEST_MESSAGE_TYPE}\n     */\n    private String requestMessageType;\n\n    // ==================== 响应消息 ====================\n\n    /**\n     * 回复的消息类型\n     *\n     * 枚举 {@link XmlMsgType} 中的 TEXT、IMAGE、VOICE、VIDEO、NEWS\n     */\n    private String responseMessageType;\n\n    /**\n     * 回复的消息内容\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 TEXT\n     */\n    private String responseContent;\n\n    /**\n     * 回复的媒体 id\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO\n     */\n    private String responseMediaId;\n    /**\n     * 回复的媒体 URL\n     */\n    private String responseMediaUrl;\n\n    /**\n     * 回复的标题\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO\n     */\n    private String responseTitle;\n    /**\n     * 回复的描述\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO\n     */\n    private String responseDescription;\n\n    /**\n     * 回复的缩略图的媒体 id，通过素材管理中的接口上传多媒体文件，得到的 id\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC、VIDEO\n     */\n    private String responseThumbMediaId;\n    /**\n     * 回复的缩略图的媒体 URL\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC、VIDEO\n     */\n    private String responseThumbMediaUrl;\n\n    /**\n     * 回复的图文消息\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS\n     */\n    @TableField(typeHandler = MpMessageDO.ArticleTypeHandler.class)\n    private List<MpMessageDO.Article> responseArticles;\n\n    /**\n     * 回复的音乐链接\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC\n     */\n    private String responseMusicUrl;\n    /**\n     * 回复的高质量音乐链接\n     *\n     * WIFI 环境优先使用该链接播放音乐\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC\n     */\n    private String responseHqMusicUrl;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/dal/dataobject/message/MpMessageDO.java",
    "content": "package co.yixiang.yshop.module.mp.dal.dataobject.message;\n\nimport co.yixiang.yshop.framework.common.util.json.JsonUtils;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.account.MpAccountDO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.user.MpUserDO;\nimport co.yixiang.yshop.module.mp.enums.message.MpMessageSendFromEnum;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport com.baomidou.mybatisplus.extension.handlers.AbstractJsonTypeHandler;\nimport lombok.*;\nimport me.chanjar.weixin.common.api.WxConsts;\nimport me.chanjar.weixin.mp.builder.kefu.NewsBuilder;\n\nimport jakarta.validation.constraints.NotEmpty;\nimport java.io.Serializable;\nimport java.util.List;\n\n/**\n * 公众号消息 DO\n *\n * @author yshop\n */\n@TableName(value = \"mp_message\", autoResultMap = true)\n@KeySequence(\"mp_message_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class MpMessageDO extends BaseDO {\n\n    /**\n     * 主键\n     */\n    @TableId\n    private Long id;\n    /**\n     * 微信公众号消息 id\n     */\n    private Long msgId;\n    /**\n     * 公众号账号的 ID\n     *\n     * 关联 {@link MpAccountDO#getId()}\n     */\n    private Long accountId;\n    /**\n     * 公众号 appid\n     *\n     * 冗余 {@link MpAccountDO#getAppId()}\n     */\n    private String appId;\n    /**\n     * 公众号粉丝的编号\n     *\n     * 关联 {@link MpUserDO#getId()}\n     */\n    private Long userId;\n    /**\n     * 公众号粉丝标志\n     *\n     * 冗余 {@link MpUserDO#getOpenid()}\n     */\n    private String openid;\n\n    /**\n     * 消息类型\n     *\n     * 枚举 {@link WxConsts.XmlMsgType}\n     */\n    private String type;\n    /**\n     * 消息来源\n     *\n     * 枚举 {@link MpMessageSendFromEnum}\n     */\n    private Integer sendFrom;\n\n    // ========= 普通消息内容 https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_standard_messages.html\n\n    /**\n     * 消息内容\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 TEXT\n     */\n    private String content;\n\n    /**\n     * 媒体文件的编号\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO\n     */\n    private String mediaId;\n    /**\n     * 媒体文件的 URL\n     */\n    private String mediaUrl;\n    /**\n     * 语音识别后文本\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 VOICE\n     */\n    private String recognition;\n    /**\n     * 语音格式，如 amr，speex 等\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 VOICE\n     */\n    private String format;\n    /**\n     * 标题\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO、MUSIC、LINK\n     */\n    private String title;\n    /**\n     * 描述\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO、MUSIC\n     */\n    private String description;\n\n    /**\n     * 缩略图的媒体 id，通过素材管理中的接口上传多媒体文件，得到的 id\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC、VIDEO\n     */\n    private String thumbMediaId;\n    /**\n     * 缩略图的媒体 URL\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC、VIDEO\n     */\n    private String thumbMediaUrl;\n\n    /**\n     * 点击图文消息跳转链接\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 LINK\n     */\n    private String url;\n\n    /**\n     * 地理位置维度\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 LOCATION\n     */\n    private Double locationX;\n    /**\n     * 地理位置经度\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 LOCATION\n     */\n    private Double locationY;\n    /**\n     * 地图缩放大小\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 LOCATION\n     */\n    private Double scale;\n    /**\n     * 详细地址\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 LOCATION\n     *\n     * 例如说杨浦区黄兴路 221-4 号临\n     */\n    private String label;\n\n    /**\n     * 图文消息数组\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS\n     */\n    @TableField(typeHandler = ArticleTypeHandler.class)\n    private List<Article> articles;\n\n    /**\n     * 音乐链接\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC\n     */\n    private String musicUrl;\n    /**\n     * 高质量音乐链接\n     *\n     * WIFI 环境优先使用该链接播放音乐\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC\n     */\n    private String hqMusicUrl;\n\n    // ========= 事件推送 https://developers.weixin.qq.com/doc/offiaccount/Message_Management/Receiving_event_pushes.html\n\n    /**\n     * 事件类型\n     *\n     * 枚举 {@link WxConsts.EventType}\n     */\n    private String event;\n    /**\n     * 事件 Key\n     *\n     * 1. {@link WxConsts.EventType} 的 SCAN：qrscene_ 为前缀，后面为二维码的参数值\n     * 2. {@link WxConsts.EventType} 的 CLICK：与自定义菜单接口中 KEY 值对应\n     */\n    private String eventKey;\n\n    /**\n     * 文章\n     */\n    @Data\n    public static class Article implements Serializable {\n\n        /**\n         * 图文消息标题\n         */\n        @NotEmpty(message = \"图文消息标题不能为空\", groups = NewsBuilder.class)\n        private String title;\n        /**\n         * 图文消息描述\n         */\n        @NotEmpty(message = \"图文消息描述不能为空\", groups = NewsBuilder.class)\n        private String description;\n        /**\n         * 图片链接\n         *\n         * 支持 JPG、PNG 格式，较好的效果为大图 360*200，小图 200*200\n         */\n        @NotEmpty(message = \"图片链接不能为空\", groups = NewsBuilder.class)\n        private String picUrl;\n        /**\n         * 点击图文消息跳转链接\n         */\n        @NotEmpty(message = \"点击图文消息跳转链接不能为空\", groups = NewsBuilder.class)\n        private String url;\n\n    }\n\n    // TODO @yshop：可以找一些新的思路\n    public static class ArticleTypeHandler extends AbstractJsonTypeHandler<List<Article>> {\n\n        @Override\n        protected List<Article> parse(String json) {\n            return JsonUtils.parseArray(json, Article.class);\n        }\n\n        @Override\n        protected String toJson(List<Article> obj) {\n            return JsonUtils.toJsonString(obj);\n        }\n\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/dal/dataobject/tag/MpTagDO.java",
    "content": "package co.yixiang.yshop.module.mp.dal.dataobject.tag;\n\nimport co.yixiang.yshop.module.mp.dal.dataobject.account.MpAccountDO;\nimport lombok.*;\n\nimport com.baomidou.mybatisplus.annotation.*;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport me.chanjar.weixin.mp.bean.tag.WxUserTag;\n\n/**\n * 公众号标签 DO\n *\n * @author yshop\n */\n@TableName(\"mp_tag\")\n@KeySequence(\"mp_tag_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class MpTagDO extends BaseDO {\n\n    /**\n     * 主键\n     */\n    @TableId(type = IdType.INPUT)\n    private Long id;\n    /**\n     * 公众号标签 id\n     */\n    private Long tagId;\n    /**\n     * 标签名\n     */\n    private String name;\n    /**\n     * 此标签下粉丝数\n     *\n     * 冗余：{@link WxUserTag#getCount()} 字段，需要管理员点击【同步】后，更新该字段\n     */\n    private Integer count;\n\n    /**\n     * 公众号账号的编号\n     *\n     * 关联 {@link MpAccountDO#getId()}\n     */\n    private Long accountId;\n    /**\n     * 公众号 appId\n     *\n     * 冗余 {@link MpAccountDO#getAppId()}\n     */\n    private String appId;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/dal/dataobject/user/MpUserDO.java",
    "content": "package co.yixiang.yshop.module.mp.dal.dataobject.user;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport co.yixiang.yshop.framework.mybatis.core.type.LongListTypeHandler;\nimport co.yixiang.yshop.module.mp.dal.dataobject.account.MpAccountDO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.tag.MpTagDO;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.*;\n\nimport java.time.LocalDateTime;\nimport java.util.List;\n\n/**\n * 微信公众号粉丝 DO\n *\n * @author yshop\n */\n@TableName(value = \"mp_user\", autoResultMap = true)\n@KeySequence(\"mp_user_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class MpUserDO extends BaseDO {\n\n    /**\n     * 编号\n     */\n    @TableId\n    private Long id;\n    /**\n     * 粉丝标识\n     */\n    private String openid;\n    /**\n     * 微信生态唯一标识\n     */\n    private String unionId;\n    /**\n     * 关注状态\n     *\n     * 枚举 {@link CommonStatusEnum}\n     * 1. 开启 - 已关注\n     * 2. 禁用 - 取消关注\n     */\n    private Integer subscribeStatus;\n    /**\n     * 关注时间\n     */\n    private LocalDateTime subscribeTime;\n    /**\n     * 取消关注时间\n     */\n    private LocalDateTime unsubscribeTime;\n    /**\n     * 昵称\n     *\n     * 注意，2021-12-27 公众号接口不再返回头像和昵称，只能通过微信公众号的网页登录获取\n     */\n    private String nickname;\n    /**\n     * 头像地址\n     *\n     * 注意，2021-12-27 公众号接口不再返回头像和昵称，只能通过微信公众号的网页登录获取\n     */\n    private String headImageUrl;\n    /**\n     * 语言\n     */\n    private String language;\n    /**\n     * 国家\n     */\n    private String country;\n    /**\n     * 省份\n     */\n    private String province;\n    /**\n     * 城市\n     */\n    private String city;\n    /**\n     * 备注\n     */\n    private String remark;\n    /**\n     * 标签编号数组\n     *\n     * 注意，对应的是 {@link MpTagDO#getTagId()} 字段\n     */\n    @TableField(typeHandler = LongListTypeHandler.class)\n    private List<Long> tagIds;\n\n    /**\n     * 公众号账号的编号\n     *\n     * 关联 {@link MpAccountDO#getId()}\n     */\n    private Long accountId;\n    /**\n     * 公众号 appId\n     *\n     * 冗余 {@link MpAccountDO#getAppId()}\n     */\n    private String appId;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/dal/mysql/account/MpAccountMapper.java",
    "content": "package co.yixiang.yshop.module.mp.dal.mysql.account;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.mp.controller.admin.account.vo.MpAccountPageReqVO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.account.MpAccountDO;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.apache.ibatis.annotations.Select;\n\nimport java.time.LocalDateTime;\n\n@Mapper\npublic interface MpAccountMapper extends BaseMapperX<MpAccountDO> {\n\n    default PageResult<MpAccountDO> selectPage(MpAccountPageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<MpAccountDO>()\n                .likeIfPresent(MpAccountDO::getName, reqVO.getName())\n                .likeIfPresent(MpAccountDO::getAccount, reqVO.getAccount())\n                .likeIfPresent(MpAccountDO::getAppId, reqVO.getAppId())\n                .orderByDesc(MpAccountDO::getId));\n    }\n\n    default MpAccountDO selectByAppId(String appId) {\n        return selectOne(MpAccountDO::getAppId, appId);\n    }\n\n    @Select(\"SELECT COUNT(*) FROM mp_account WHERE update_time > #{maxUpdateTime}\")\n    Long selectCountByUpdateTimeGt(LocalDateTime maxUpdateTime);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/dal/mysql/material/MpMaterialMapper.java",
    "content": "package co.yixiang.yshop.module.mp.dal.mysql.material;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.mp.controller.admin.material.vo.MpMaterialPageReqVO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.material.MpMaterialDO;\nimport org.apache.ibatis.annotations.Mapper;\n\nimport java.util.Collection;\nimport java.util.List;\n\n@Mapper\npublic interface MpMaterialMapper extends BaseMapperX<MpMaterialDO> {\n\n    default MpMaterialDO selectByAccountIdAndMediaId(Long accountId, String mediaId) {\n        return selectOne(MpMaterialDO::getAccountId, accountId,\n                MpMaterialDO::getMediaId, mediaId);\n    }\n\n    default PageResult<MpMaterialDO> selectPage(MpMaterialPageReqVO pageReqVO) {\n        return selectPage(pageReqVO, new LambdaQueryWrapperX<MpMaterialDO>()\n                .eq(MpMaterialDO::getAccountId, pageReqVO.getAccountId())\n                .eqIfPresent(MpMaterialDO::getPermanent, pageReqVO.getPermanent())\n                .eqIfPresent(MpMaterialDO::getType, pageReqVO.getType())\n                .orderByDesc(MpMaterialDO::getId));\n    }\n\n    default List<MpMaterialDO> selectListByMediaId(Collection<String> mediaIds) {\n        return selectList(MpMaterialDO::getMediaId, mediaIds);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/dal/mysql/menu/MpMenuMapper.java",
    "content": "package co.yixiang.yshop.module.mp.dal.mysql.menu;\n\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.mp.dal.dataobject.menu.MpMenuDO;\nimport org.apache.ibatis.annotations.Mapper;\n\nimport java.util.List;\n\n@Mapper\npublic interface MpMenuMapper extends BaseMapperX<MpMenuDO> {\n\n    default MpMenuDO selectByAppIdAndMenuKey(String appId, String menuKey) {\n        return selectOne(MpMenuDO::getAppId, appId,\n                MpMenuDO::getMenuKey, menuKey);\n    }\n\n    default List<MpMenuDO> selectListByAccountId(Long accountId) {\n        return selectList(MpMenuDO::getAccountId, accountId);\n    }\n\n    default void deleteByAccountId(Long accountId) {\n        delete(new LambdaQueryWrapperX<MpMenuDO>().eq(MpMenuDO::getAccountId, accountId));\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/dal/mysql/message/MpAutoReplyMapper.java",
    "content": "package co.yixiang.yshop.module.mp.dal.mysql.message;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.mp.controller.admin.message.vo.message.MpMessagePageReqVO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.message.MpAutoReplyDO;\nimport co.yixiang.yshop.module.mp.enums.message.MpAutoReplyMatchEnum;\nimport co.yixiang.yshop.module.mp.enums.message.MpAutoReplyTypeEnum;\nimport org.apache.ibatis.annotations.Mapper;\n\nimport java.util.List;\n\n@Mapper\npublic interface MpAutoReplyMapper extends BaseMapperX<MpAutoReplyDO> {\n\n    default PageResult<MpAutoReplyDO> selectPage(MpMessagePageReqVO pageVO) {\n        return selectPage(pageVO, new LambdaQueryWrapperX<MpAutoReplyDO>()\n                .eq(MpAutoReplyDO::getAccountId, pageVO.getAccountId())\n                .eqIfPresent(MpAutoReplyDO::getType, pageVO.getType()));\n    }\n\n    default List<MpAutoReplyDO> selectListByAppIdAndKeywordAll(String appId, String requestKeyword) {\n        return selectList(new LambdaQueryWrapperX<MpAutoReplyDO>()\n                .eq(MpAutoReplyDO::getAppId, appId)\n                .eq(MpAutoReplyDO::getType, MpAutoReplyTypeEnum.KEYWORD.getType())\n                .eq(MpAutoReplyDO::getRequestMatch, MpAutoReplyMatchEnum.ALL.getMatch())\n                .eq(MpAutoReplyDO::getRequestKeyword, requestKeyword));\n    }\n\n    default List<MpAutoReplyDO> selectListByAppIdAndKeywordLike(String appId, String requestKeyword) {\n        return selectList(new LambdaQueryWrapperX<MpAutoReplyDO>()\n                .eq(MpAutoReplyDO::getAppId, appId)\n                .eq(MpAutoReplyDO::getType, MpAutoReplyTypeEnum.KEYWORD.getType())\n                .eq(MpAutoReplyDO::getRequestMatch, MpAutoReplyMatchEnum.LIKE.getMatch())\n                .like(MpAutoReplyDO::getRequestKeyword, requestKeyword));\n    }\n\n    default List<MpAutoReplyDO> selectListByAppIdAndMessage(String appId, String requestMessageType) {\n        return selectList(new LambdaQueryWrapperX<MpAutoReplyDO>()\n                .eq(MpAutoReplyDO::getAppId, appId)\n                .eq(MpAutoReplyDO::getType, MpAutoReplyTypeEnum.MESSAGE.getType())\n                .eq(MpAutoReplyDO::getRequestMessageType, requestMessageType));\n    }\n\n    default List<MpAutoReplyDO> selectListByAppIdAndSubscribe(String appId) {\n        return selectList(new LambdaQueryWrapperX<MpAutoReplyDO>()\n                .eq(MpAutoReplyDO::getAppId, appId)\n                .eq(MpAutoReplyDO::getType, MpAutoReplyTypeEnum.SUBSCRIBE.getType()));\n    }\n\n    default MpAutoReplyDO selectByAccountIdAndSubscribe(Long accountId) {\n        return selectOne(MpAutoReplyDO::getAccountId, accountId,\n                MpAutoReplyDO::getType, MpAutoReplyTypeEnum.SUBSCRIBE.getType());\n    }\n\n    default MpAutoReplyDO selectByAccountIdAndMessage(Long accountId, String requestMessageType) {\n        return selectOne(new LambdaQueryWrapperX<MpAutoReplyDO>()\n                .eq(MpAutoReplyDO::getAccountId, accountId)\n                .eq(MpAutoReplyDO::getType, MpAutoReplyTypeEnum.MESSAGE.getType())\n                .eq(MpAutoReplyDO::getRequestMessageType, requestMessageType));\n    }\n\n    default MpAutoReplyDO selectByAccountIdAndKeyword(Long accountId, String requestKeyword) {\n        return selectOne(new LambdaQueryWrapperX<MpAutoReplyDO>()\n                .eq(MpAutoReplyDO::getAccountId, accountId)\n                .eq(MpAutoReplyDO::getType, MpAutoReplyTypeEnum.KEYWORD.getType())\n                .eq(MpAutoReplyDO::getRequestKeyword, requestKeyword));\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/dal/mysql/message/MpMessageMapper.java",
    "content": "package co.yixiang.yshop.module.mp.dal.mysql.message;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.mp.controller.admin.message.vo.message.MpMessagePageReqVO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.message.MpMessageDO;\nimport org.apache.ibatis.annotations.Mapper;\n\n@Mapper\npublic interface MpMessageMapper extends BaseMapperX<MpMessageDO> {\n\n    default PageResult<MpMessageDO> selectPage(MpMessagePageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<MpMessageDO>()\n                .eqIfPresent(MpMessageDO::getAccountId, reqVO.getAccountId())\n                .eqIfPresent(MpMessageDO::getType, reqVO.getType())\n                .eqIfPresent(MpMessageDO::getOpenid, reqVO.getOpenid())\n                .betweenIfPresent(MpMessageDO::getCreateTime, reqVO.getCreateTime())\n                .orderByDesc(MpMessageDO::getId));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/dal/mysql/tag/MpTagMapper.java",
    "content": "package co.yixiang.yshop.module.mp.dal.mysql.tag;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.mp.controller.admin.tag.vo.MpTagPageReqVO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.tag.MpTagDO;\nimport org.apache.ibatis.annotations.Mapper;\n\nimport java.util.List;\n\n@Mapper\npublic interface MpTagMapper extends BaseMapperX<MpTagDO> {\n\n    default PageResult<MpTagDO> selectPage(MpTagPageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<MpTagDO>()\n                .eqIfPresent(MpTagDO::getAccountId, reqVO.getAccountId())\n                .likeIfPresent(MpTagDO::getName, reqVO.getName())\n                .orderByDesc(MpTagDO::getId));\n    }\n\n    default List<MpTagDO> selectListByAccountId(Long accountId) {\n        return selectList(MpTagDO::getAccountId, accountId);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/dal/mysql/user/MpUserMapper.java",
    "content": "package co.yixiang.yshop.module.mp.dal.mysql.user;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.mp.controller.admin.user.vo.MpUserPageReqVO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.user.MpUserDO;\nimport org.apache.ibatis.annotations.Mapper;\n\nimport java.util.List;\n\n@Mapper\npublic interface MpUserMapper extends BaseMapperX<MpUserDO> {\n\n    default PageResult<MpUserDO> selectPage(MpUserPageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<MpUserDO>()\n                .likeIfPresent(MpUserDO::getOpenid, reqVO.getOpenid())\n                .likeIfPresent(MpUserDO::getNickname, reqVO.getNickname())\n                .eqIfPresent(MpUserDO::getAccountId, reqVO.getAccountId())\n                .orderByDesc(MpUserDO::getId));\n    }\n\n    default MpUserDO selectByAppIdAndOpenid(String appId, String openid) {\n        return selectOne(MpUserDO::getAppId, appId,\n                MpUserDO::getOpenid, openid);\n    }\n\n    default List<MpUserDO> selectListByAppIdAndOpenid(String appId, List<String> openids) {\n        return selectList(new LambdaQueryWrapperX<MpUserDO>()\n                .eq(MpUserDO::getAppId, appId)\n                .in(MpUserDO::getOpenid, openids));\n\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/framework/mp/config/MpConfiguration.java",
    "content": "package co.yixiang.yshop.module.mp.framework.mp.config;\n\nimport co.yixiang.yshop.module.mp.framework.mp.core.DefaultMpServiceFactory;\nimport co.yixiang.yshop.module.mp.framework.mp.core.MpServiceFactory;\nimport co.yixiang.yshop.module.mp.service.handler.menu.MenuHandler;\nimport co.yixiang.yshop.module.mp.service.handler.message.MessageReceiveHandler;\nimport co.yixiang.yshop.module.mp.service.handler.message.MessageAutoReplyHandler;\nimport co.yixiang.yshop.module.mp.service.handler.other.KfSessionHandler;\nimport co.yixiang.yshop.module.mp.service.handler.other.NullHandler;\nimport co.yixiang.yshop.module.mp.service.handler.other.ScanHandler;\nimport co.yixiang.yshop.module.mp.service.handler.other.StoreCheckNotifyHandler;\nimport co.yixiang.yshop.module.mp.service.handler.user.LocationHandler;\nimport co.yixiang.yshop.module.mp.service.handler.user.SubscribeHandler;\nimport co.yixiang.yshop.module.mp.service.handler.user.UnsubscribeHandler;\nimport com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties;\nimport me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.data.redis.core.StringRedisTemplate;\n\n/**\n * 微信公众号的配置类\n *\n * @author yshop\n */\n@Configuration\npublic class MpConfiguration {\n\n    @Bean\n    @SuppressWarnings(\"SpringJavaInjectionPointsAutowiringInspection\")\n    public RedisTemplateWxRedisOps redisTemplateWxRedisOps(StringRedisTemplate stringRedisTemplate) {\n        return new RedisTemplateWxRedisOps(stringRedisTemplate);\n    }\n\n    @Bean\n    @SuppressWarnings(\"SpringJavaInjectionPointsAutowiringInspection\")\n    public MpServiceFactory mpServiceFactory(RedisTemplateWxRedisOps redisTemplateWxRedisOps,\n                                             WxMpProperties wxMpProperties,\n                                             MessageReceiveHandler messageReceiveHandler,\n                                             KfSessionHandler kfSessionHandler,\n                                             StoreCheckNotifyHandler storeCheckNotifyHandler,\n                                             MenuHandler menuHandler,\n                                             NullHandler nullHandler,\n                                             SubscribeHandler subscribeHandler,\n                                             UnsubscribeHandler unsubscribeHandler,\n                                             LocationHandler locationHandler,\n                                             ScanHandler scanHandler,\n                                             MessageAutoReplyHandler messageAutoReplyHandler) {\n        return new DefaultMpServiceFactory(redisTemplateWxRedisOps, wxMpProperties,\n                messageReceiveHandler, kfSessionHandler, storeCheckNotifyHandler, menuHandler,\n                nullHandler, subscribeHandler, unsubscribeHandler, locationHandler, scanHandler, messageAutoReplyHandler);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/framework/mp/core/DefaultMpServiceFactory.java",
    "content": "package co.yixiang.yshop.module.mp.framework.mp.core;\n\nimport co.yixiang.yshop.module.mp.dal.dataobject.account.MpAccountDO;\nimport co.yixiang.yshop.module.mp.service.handler.menu.MenuHandler;\nimport co.yixiang.yshop.module.mp.service.handler.message.MessageReceiveHandler;\nimport co.yixiang.yshop.module.mp.service.handler.message.MessageAutoReplyHandler;\nimport co.yixiang.yshop.module.mp.service.handler.other.KfSessionHandler;\nimport co.yixiang.yshop.module.mp.service.handler.other.NullHandler;\nimport co.yixiang.yshop.module.mp.service.handler.other.ScanHandler;\nimport co.yixiang.yshop.module.mp.service.handler.other.StoreCheckNotifyHandler;\nimport co.yixiang.yshop.module.mp.service.handler.user.LocationHandler;\nimport co.yixiang.yshop.module.mp.service.handler.user.SubscribeHandler;\nimport co.yixiang.yshop.module.mp.service.handler.user.UnsubscribeHandler;\nimport com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties;\nimport com.google.common.collect.Maps;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport me.chanjar.weixin.common.api.WxConsts;\nimport me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps;\nimport me.chanjar.weixin.mp.api.WxMpMessageRouter;\nimport me.chanjar.weixin.mp.api.WxMpService;\nimport me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;\nimport me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl;\nimport me.chanjar.weixin.mp.constant.WxMpEventConstants;\n\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * 默认的 {@link MpServiceFactory} 实现类\n *\n * @author yshop\n */\n@Slf4j\n@RequiredArgsConstructor\npublic class DefaultMpServiceFactory implements MpServiceFactory {\n\n    /**\n     * 微信 appId 与 WxMpService 的映射\n     */\n    private volatile Map<String, WxMpService> appId2MpServices;\n    /**\n     * 公众号账号 id 与 WxMpService 的映射\n     */\n    private volatile Map<Long, WxMpService> id2MpServices;\n    /**\n     * 微信 appId 与 WxMpMessageRouter 的映射\n     */\n    private volatile Map<String, WxMpMessageRouter> mpMessageRouters;\n\n    private final RedisTemplateWxRedisOps redisTemplateWxRedisOps;\n    private final WxMpProperties mpProperties;\n\n    // ========== 各种 Handler ==========\n\n    private final MessageReceiveHandler messageReceiveHandler;\n    private final KfSessionHandler kfSessionHandler;\n    private final StoreCheckNotifyHandler storeCheckNotifyHandler;\n    private final MenuHandler menuHandler;\n    private final NullHandler nullHandler;\n    private final SubscribeHandler subscribeHandler;\n    private final UnsubscribeHandler unsubscribeHandler;\n    private final LocationHandler locationHandler;\n    private final ScanHandler scanHandler;\n    private final MessageAutoReplyHandler messageAutoReplyHandler;\n\n    @Override\n    public void init(List<MpAccountDO> list) {\n        Map<String, WxMpService> appId2MpServices = Maps.newHashMap();\n        Map<Long, WxMpService> id2MpServices = Maps.newHashMap();\n        Map<String, WxMpMessageRouter> mpMessageRouters = Maps.newHashMap();\n        // 处理 list\n        list.forEach(account -> {\n            // 构建 WxMpService 对象\n            WxMpService mpService = buildMpService(account);\n            appId2MpServices.put(account.getAppId(), mpService);\n            id2MpServices.put(account.getId(), mpService);\n            // 构建 WxMpMessageRouter 对象\n            WxMpMessageRouter mpMessageRouter = buildMpMessageRouter(mpService);\n            mpMessageRouters.put(account.getAppId(), mpMessageRouter);\n        });\n\n        // 设置到缓存\n        this.appId2MpServices = appId2MpServices;\n        this.id2MpServices = id2MpServices;\n        this.mpMessageRouters = mpMessageRouters;\n    }\n\n    @Override\n    public WxMpService getMpService(Long id) {\n        return id2MpServices.get(id);\n    }\n\n    @Override\n    public WxMpService getMpService(String appId) {\n        return appId2MpServices.get(appId);\n    }\n\n    @Override\n    public WxMpMessageRouter getMpMessageRouter(String appId) {\n        return mpMessageRouters.get(appId);\n    }\n\n    private WxMpService buildMpService(MpAccountDO account) {\n        // 第一步，创建 WxMpRedisConfigImpl 对象\n        WxMpRedisConfigImpl configStorage = new WxMpRedisConfigImpl(\n                redisTemplateWxRedisOps, mpProperties.getConfigStorage().getKeyPrefix());\n        configStorage.setAppId(account.getAppId());\n        configStorage.setSecret(account.getAppSecret());\n        configStorage.setToken(account.getToken());\n        configStorage.setAesKey(account.getAesKey());\n\n        // 第二步，创建 WxMpService 对象\n        WxMpService service = new WxMpServiceImpl();\n        service.setWxMpConfigStorage(configStorage);\n        return service;\n    }\n\n    private WxMpMessageRouter buildMpMessageRouter(WxMpService mpService) {\n        WxMpMessageRouter router = new WxMpMessageRouter(mpService);\n        // 记录所有事件的日志（异步执行）\n        router.rule().handler(messageReceiveHandler).next();\n\n        // 接收客服会话管理事件\n        router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)\n                .event(WxMpEventConstants.CustomerService.KF_CREATE_SESSION)\n                .handler(kfSessionHandler).end();\n        router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)\n                .event(WxMpEventConstants.CustomerService.KF_CLOSE_SESSION)\n                .handler(kfSessionHandler)\n                .end();\n        router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)\n                .event(WxMpEventConstants.CustomerService.KF_SWITCH_SESSION)\n                .handler(kfSessionHandler).end();\n\n        // 门店审核事件\n        router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)\n                .event(WxMpEventConstants.POI_CHECK_NOTIFY)\n                .handler(storeCheckNotifyHandler).end();\n\n        // 自定义菜单事件\n        router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)\n                .event(WxConsts.MenuButtonType.CLICK).handler(menuHandler).end();\n\n        // 点击菜单连接事件\n        router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)\n                .event(WxConsts.MenuButtonType.VIEW).handler(nullHandler).end();\n\n        // 关注事件\n        router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)\n                .event(WxConsts.EventType.SUBSCRIBE).handler(subscribeHandler)\n                .end();\n\n        // 取消关注事件\n        router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)\n                .event(WxConsts.EventType.UNSUBSCRIBE)\n                .handler(unsubscribeHandler).end();\n\n        // 上报地理位置事件\n        router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)\n                .event(WxConsts.EventType.LOCATION).handler(locationHandler)\n                .end();\n\n        // 接收地理位置消息\n        router.rule().async(false).msgType(WxConsts.XmlMsgType.LOCATION)\n                .handler(locationHandler).end();\n\n        // 扫码事件\n        router.rule().async(false).msgType(WxConsts.XmlMsgType.EVENT)\n                .event(WxConsts.EventType.SCAN).handler(scanHandler).end();\n\n        // 默认\n        router.rule().async(false).handler(messageAutoReplyHandler).end();\n        return router;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/framework/mp/core/MpServiceFactory.java",
    "content": "package co.yixiang.yshop.module.mp.framework.mp.core;\n\nimport cn.hutool.core.lang.Assert;\nimport co.yixiang.yshop.module.mp.dal.dataobject.account.MpAccountDO;\nimport me.chanjar.weixin.mp.api.WxMpMessageRouter;\nimport me.chanjar.weixin.mp.api.WxMpService;\n\nimport java.util.List;\n\n/**\n * {@link WxMpService} 工厂接口\n *\n * @author yshop\n */\npublic interface MpServiceFactory {\n\n    /**\n     * 基于微信公众号的账号，初始化对应的 WxMpService 与 WxMpMessageRouter 实例\n     *\n     * @param list 公众号的账号列表\n     */\n    void init(List<MpAccountDO> list);\n\n    /**\n     * 获得 id 对应的 WxMpService 实例\n     *\n     * @param id 微信公众号的编号\n     * @return WxMpService 实例\n     */\n    WxMpService getMpService(Long id);\n\n    default WxMpService getRequiredMpService(Long id) {\n        WxMpService wxMpService = getMpService(id);\n        Assert.notNull(wxMpService, \"找到对应 id({}) 的 WxMpService，请核实！\", id);\n        return wxMpService;\n    }\n\n    /**\n     * 获得 appId 对应的 WxMpService 实例\n     *\n     * @param appId 微信公众号 appId\n     * @return WxMpService 实例\n     */\n    WxMpService getMpService(String appId);\n\n    default WxMpService getRequiredMpService(String appId) {\n        WxMpService wxMpService = getMpService(appId);\n        Assert.notNull(wxMpService, \"找到对应 appId({}) 的 WxMpService，请核实！\", appId);\n        return wxMpService;\n    }\n\n    /**\n     * 获得 appId 对应的 WxMpMessageRouter 实例\n     *\n     * @param appId 微信公众号 appId\n     * @return WxMpMessageRouter 实例\n     */\n    WxMpMessageRouter getMpMessageRouter(String appId);\n\n    default WxMpMessageRouter getRequiredMpMessageRouter(String appId) {\n        WxMpMessageRouter wxMpMessageRouter = getMpMessageRouter(appId);\n        Assert.notNull(wxMpMessageRouter, \"找到对应 appId({}) 的 WxMpMessageRouter，请核实！\", appId);\n        return wxMpMessageRouter;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/framework/mp/core/context/MpContextHolder.java",
    "content": "/*\n *    Copyright (c) 2018-2025, lengleng All rights reserved.\n *\n * Redistribution and use in source and binary forms, with or without\n * modification, are permitted provided that the following conditions are met:\n *\n * Redistributions of source code must retain the above copyright notice,\n * this list of conditions and the following disclaimer.\n * Redistributions in binary form must reproduce the above copyright\n * notice, this list of conditions and the following disclaimer in the\n * documentation and/or other materials provided with the distribution.\n * Neither the name of the pig4cloud.com developer nor the names of its\n * contributors may be used to endorse or promote products derived from\n * this software without specific prior written permission.\n * Author: lengleng (wangiegie@gmail.com)\n */\n\npackage co.yixiang.yshop.module.mp.framework.mp.core.context;\n\nimport co.yixiang.yshop.module.mp.controller.admin.open.vo.MpOpenHandleMessageReqVO;\nimport com.alibaba.ttl.TransmittableThreadLocal;\nimport lombok.experimental.UtilityClass;\nimport me.chanjar.weixin.mp.api.WxMpMessageHandler;\n\n/**\n * 微信上下文 Context\n *\n * 目的：解决微信多公众号的问题，在 {@link WxMpMessageHandler} 实现类中，可以通过 {@link #getAppId()} 获取到当前的 appId\n *\n * @see co.yixiang.yshop.module.mp.controller.admin.open.MpOpenController#handleMessage(String, String, MpOpenHandleMessageReqVO)\n *\n * @author yshop\n */\npublic class MpContextHolder {\n\n    /**\n     * 微信公众号的 appId 上下文\n     */\n\tprivate static final ThreadLocal<String> APPID = new TransmittableThreadLocal<>();\n\n\tpublic static void setAppId(String appId) {\n\t\tAPPID.set(appId);\n\t}\n\n\tpublic static String getAppId() {\n\t\treturn APPID.get();\n\t}\n\n\tpublic static void clear() {\n\t\tAPPID.remove();\n\t}\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/framework/mp/core/util/MpUtils.java",
    "content": "package co.yixiang.yshop.module.mp.framework.mp.core.util;\n\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.util.validation.ValidationUtils;\nimport lombok.extern.slf4j.Slf4j;\nimport me.chanjar.weixin.common.api.WxConsts;\n\nimport jakarta.validation.Validator;\n\n/**\n * 公众号工具类\n *\n * @author yshop\n */\n@Slf4j\npublic class MpUtils {\n\n    /**\n     * 校验消息的格式是否符合要求\n     *\n     * @param type 类型\n     * @param message 消息\n     */\n    public static void validateMessage(Validator validator, String type, Object message) {\n        // 获得对应的校验 group\n        Class<?> group;\n        switch (type) {\n            case WxConsts.XmlMsgType.TEXT:\n                group = TextMessageGroup.class;\n                break;\n            case WxConsts.XmlMsgType.IMAGE:\n                group = ImageMessageGroup.class;\n                break;\n            case WxConsts.XmlMsgType.VOICE:\n                group = VoiceMessageGroup.class;\n                break;\n            case WxConsts.XmlMsgType.VIDEO:\n                group = VideoMessageGroup.class;\n                break;\n            case WxConsts.XmlMsgType.NEWS:\n                group = NewsMessageGroup.class;\n                break;\n            case WxConsts.XmlMsgType.MUSIC:\n                group = MusicMessageGroup.class;\n                break;\n            default:\n                log.error(\"[validateMessage][未知的消息类型({})]\", message);\n                throw new IllegalArgumentException(\"不支持的消息类型：\" + type);\n        }\n        // 执行校验\n        ValidationUtils.validate(validator, message, group);\n    }\n\n    public static void validateButton(Validator validator, String type, String messageType, Object button) {\n        if (StrUtil.isBlank(type)) {\n            return;\n        }\n        // 获得对应的校验 group\n        Class<?> group;\n        switch (type) {\n            case WxConsts.MenuButtonType.CLICK:\n                group = ClickButtonGroup.class;\n                validateMessage(validator, messageType, button); // 需要额外校验回复的消息格式\n                break;\n            case WxConsts.MenuButtonType.VIEW:\n                group = ViewButtonGroup.class;\n                break;\n            case WxConsts.MenuButtonType.MINIPROGRAM:\n                group = MiniProgramButtonGroup.class;\n                break;\n            case WxConsts.MenuButtonType.SCANCODE_WAITMSG:\n                group = ScanCodeWaitMsgButtonGroup.class;\n                validateMessage(validator, messageType, button); // 需要额外校验回复的消息格式\n                break;\n            case \"article_\" + WxConsts.MenuButtonType.VIEW_LIMITED:\n                group = ViewLimitedButtonGroup.class;\n                break;\n            case WxConsts.MenuButtonType.SCANCODE_PUSH: // 不用校验，直接 return 即可\n            case WxConsts.MenuButtonType.PIC_SYSPHOTO:\n            case WxConsts.MenuButtonType.PIC_PHOTO_OR_ALBUM:\n            case WxConsts.MenuButtonType.PIC_WEIXIN:\n            case WxConsts.MenuButtonType.LOCATION_SELECT:\n                return;\n            default:\n                log.error(\"[validateButton][未知的按钮({})]\", button);\n                throw new IllegalArgumentException(\"不支持的按钮类型：\" + type);\n        }\n        // 执行校验\n        ValidationUtils.validate(validator, button, group);\n    }\n\n    /**\n     * 根据消息类型，获得对应的媒体文件类型\n     *\n     * 注意，不会返回 WxConsts.MediaFileType.THUMB，因为该类型会有明确标注\n     *\n     * @param messageType 消息类型 {@link  WxConsts.XmlMsgType}\n     * @return 媒体文件类型 {@link WxConsts.MediaFileType}\n     */\n    public static String getMediaFileType(String messageType) {\n        switch (messageType) {\n            case WxConsts.XmlMsgType.IMAGE:\n                return WxConsts.MediaFileType.IMAGE;\n            case WxConsts.XmlMsgType.VOICE:\n                return WxConsts.MediaFileType.VOICE;\n            case WxConsts.XmlMsgType.VIDEO:\n                return WxConsts.MediaFileType.VIDEO;\n            default:\n                return WxConsts.MediaFileType.FILE;\n        }\n    }\n\n    /**\n     * Text 类型的消息，参数校验 Group\n     */\n    public interface TextMessageGroup {}\n\n    /**\n     * Image 类型的消息，参数校验 Group\n     */\n    public interface ImageMessageGroup {}\n\n    /**\n     * Voice 类型的消息，参数校验 Group\n     */\n    public interface VoiceMessageGroup {}\n\n    /**\n     * Video 类型的消息，参数校验 Group\n     */\n    public interface VideoMessageGroup {}\n\n    /**\n     * News 类型的消息，参数校验 Group\n     */\n    public interface NewsMessageGroup {}\n\n    /**\n     * Music 类型的消息，参数校验 Group\n     */\n    public interface MusicMessageGroup {}\n\n    /**\n     * Click 类型的按钮，参数校验 Group\n     */\n    public interface ClickButtonGroup {}\n\n    /**\n     * View 类型的按钮，参数校验 Group\n     */\n    public interface ViewButtonGroup {}\n\n    /**\n     * MiniProgram 类型的按钮，参数校验 Group\n     */\n    public interface MiniProgramButtonGroup {}\n\n    /**\n     * SCANCODE_WAITMSG 类型的按钮，参数校验 Group\n     */\n    public interface ScanCodeWaitMsgButtonGroup {}\n\n    /**\n     * VIEW_LIMITED 类型的按钮，参数校验 Group\n     */\n    public interface ViewLimitedButtonGroup {}\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/framework/web/config/MpWebConfiguration.java",
    "content": "package co.yixiang.yshop.module.mp.framework.web.config;\n\nimport co.yixiang.yshop.framework.swagger.config.YshopSwaggerAutoConfiguration;\nimport org.springdoc.core.models.GroupedOpenApi;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n/**\n * mp 模块的 web 组件的 Configuration\n *\n * @author yshop\n */\n@Configuration(proxyBeanMethods = false)\npublic class MpWebConfiguration {\n\n    /**\n     * mp 模块的 API 分组\n     */\n    @Bean\n    public GroupedOpenApi mpGroupedOpenApi() {\n        return YshopSwaggerAutoConfiguration.buildGroupedOpenApi(\"mp\");\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/service/account/MpAccountService.java",
    "content": "package co.yixiang.yshop.module.mp.service.account;\n\nimport cn.binarywang.wx.miniapp.api.WxMaService;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.mp.controller.admin.account.vo.MpAccountCreateReqVO;\nimport co.yixiang.yshop.module.mp.controller.admin.account.vo.MpAccountPageReqVO;\nimport co.yixiang.yshop.module.mp.controller.admin.account.vo.MpAccountUpdateReqVO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.account.MpAccountDO;\n\nimport jakarta.validation.Valid;\nimport me.chanjar.weixin.mp.api.WxMpService;\n\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.mp.enums.ErrorCodeConstants.ACCOUNT_NOT_EXISTS;\n\n/**\n * 公众号账号 Service 接口\n *\n * @author yshop\n */\npublic interface MpAccountService {\n\n    /**\n     * 初始化缓存\n     */\n    void initLocalCache();\n\n    /**\n     * 创建公众号账号\n     *\n     * @param createReqVO 创建信息\n     * @return 编号\n     */\n    Long createAccount(@Valid MpAccountCreateReqVO createReqVO);\n\n    /**\n     * 更新公众号账号\n     *\n     * @param updateReqVO 更新信息\n     */\n    void updateAccount(@Valid MpAccountUpdateReqVO updateReqVO);\n\n    /**\n     * 删除公众号账号\n     *\n     * @param id 编号\n     */\n    void deleteAccount(Long id);\n\n    /**\n     * 获得公众号账号\n     *\n     * @param id 编号\n     * @return 公众号账号\n     */\n    MpAccountDO getAccount(Long id);\n\n    /**\n     * 获得公众号账号。若不存在，则抛出业务异常\n     *\n     * @param id 编号\n     * @return 公众号账号\n     */\n    default MpAccountDO getRequiredAccount(Long id) {\n        MpAccountDO account = getAccount(id);\n        if (account == null) {\n            throw exception(ACCOUNT_NOT_EXISTS);\n        }\n        return account;\n    }\n\n    /**\n     * 从缓存中，获得公众号账号\n     *\n     * @param appId 微信公众号 appId\n     * @return 公众号账号\n     */\n    MpAccountDO getAccountFromCache(String appId);\n\n    /**\n     * 获得公众号账号分页\n     *\n     * @param pageReqVO 分页查询\n     * @return 公众号账号分页\n     */\n    PageResult<MpAccountDO> getAccountPage(MpAccountPageReqVO pageReqVO);\n\n    /**\n     * 获得公众号账号列表\n     *\n     * @return 公众号账号列表\n     */\n    List<MpAccountDO> getAccountList();\n\n    /**\n     * 生成公众号账号的二维码\n     *\n     * @param id 编号\n     */\n    void generateAccountQrCode(Long id);\n\n    /**\n     * 清空公众号账号的 API 配额\n     *\n     * 参考文档：<a href=\"https://developers.weixin.qq.com/doc/offiaccount/Message_Management/API_Call_Limits.html\">接口调用频次限制说明</a>\n     *\n     * @param id 编号\n     */\n    void clearAccountQuota(Long id);\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/service/account/MpAccountServiceImpl.java",
    "content": "package co.yixiang.yshop.module.mp.service.account;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.util.ObjUtil;\nimport co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.tenant.core.util.TenantUtils;\nimport co.yixiang.yshop.module.mp.controller.admin.account.vo.MpAccountCreateReqVO;\nimport co.yixiang.yshop.module.mp.controller.admin.account.vo.MpAccountPageReqVO;\nimport co.yixiang.yshop.module.mp.controller.admin.account.vo.MpAccountUpdateReqVO;\nimport co.yixiang.yshop.module.mp.convert.account.MpAccountConvert;\nimport co.yixiang.yshop.module.mp.dal.dataobject.account.MpAccountDO;\nimport co.yixiang.yshop.module.mp.dal.mysql.account.MpAccountMapper;\nimport co.yixiang.yshop.module.mp.enums.ErrorCodeConstants;\nimport co.yixiang.yshop.module.mp.framework.mp.core.MpServiceFactory;\nimport com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties;\nimport com.google.common.annotations.VisibleForTesting;\nimport jakarta.annotation.PostConstruct;\nimport jakarta.annotation.Resource;\nimport lombok.Getter;\nimport lombok.extern.slf4j.Slf4j;\nimport me.chanjar.weixin.common.error.WxErrorException;\nimport me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps;\nimport me.chanjar.weixin.mp.api.WxMpService;\nimport me.chanjar.weixin.mp.bean.result.WxMpQrCodeTicket;\nimport org.springframework.context.annotation.Lazy;\nimport org.springframework.scheduling.annotation.Scheduled;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport java.time.LocalDateTime;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.concurrent.TimeUnit;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.framework.common.util.collection.CollectionUtils.convertMap;\nimport static co.yixiang.yshop.framework.common.util.collection.CollectionUtils.getMaxValue;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.USER_USERNAME_EXISTS;\n\n/**\n * 公众号账号 Service 实现类\n *\n * @author fengdan\n */\n@Slf4j\n@Service\n@Validated\npublic class MpAccountServiceImpl implements MpAccountService {\n\n    /**\n     * 账号缓存\n     * key：账号编号 {@link MpAccountDO#getAppId()}\n     *\n     * 这里声明 volatile 修饰的原因是，每次刷新时，直接修改指向\n     */\n    @Getter\n    private volatile Map<String, MpAccountDO> accountCache;\n\n    @Resource\n    private MpAccountMapper mpAccountMapper;\n\n    @Resource\n    @Lazy // 延迟加载，解决循环依赖的问题\n    private MpServiceFactory mpServiceFactory;\n\n    @Resource\n    private RedisTemplateWxRedisOps redisTemplateWxRedisOps;\n    @Resource\n    private WxMpProperties mpProperties;\n\n    @Override\n    @PostConstruct\n    public void initLocalCache() {\n        // 注意：忽略自动多租户，因为要全局初始化缓存\n        TenantUtils.executeIgnore(() -> {\n            // 第一步：查询数据\n            List<MpAccountDO> accounts = Collections.emptyList();\n            try {\n                accounts = mpAccountMapper.selectList();\n            } catch (Throwable ex) {\n                if (!ex.getMessage().contains(\"doesn't exist\")) {\n                    throw ex;\n                }\n                log.error(\"[微信公众号 yshop-module-mp - 表结构未导入][参考 https://www.yixiang.co/mp/build/ 开启]\");\n            }\n            log.info(\"[initLocalCacheIfUpdate][缓存公众号账号，数量为:{}]\", accounts.size());\n\n            // 第二步：构建缓存。创建或更新支付 Client\n            mpServiceFactory.init(accounts);\n            accountCache = convertMap(accounts, MpAccountDO::getAppId);\n        });\n    }\n\n    /**\n     * 通过定时任务轮询，刷新缓存\n     *\n     * 目的：多节点部署时，通过轮询”通知“所有节点，进行刷新\n     */\n    @Scheduled(initialDelay = 60, fixedRate = 60, timeUnit = TimeUnit.SECONDS)\n    public void refreshLocalCache() {\n        // 注意：忽略自动多租户，因为要全局初始化缓存\n        TenantUtils.executeIgnore(() -> {\n            // 情况一：如果缓存里没有数据，则直接刷新缓存\n            if (CollUtil.isEmpty(accountCache)) {\n                initLocalCache();\n                return;\n            }\n\n            // 情况二，如果缓存里数据，则通过 updateTime 判断是否有数据变更，有变更则刷新缓存\n            LocalDateTime maxTime = getMaxValue(accountCache.values(), MpAccountDO::getUpdateTime);\n            if (mpAccountMapper.selectCountByUpdateTimeGt(maxTime) > 0) {\n                initLocalCache();\n            }\n        });\n    }\n\n    @Override\n    public Long createAccount(MpAccountCreateReqVO createReqVO) {\n        // 校验 appId 唯一\n        validateAppIdUnique(null, createReqVO.getAppId());\n\n        // 插入\n        MpAccountDO account = MpAccountConvert.INSTANCE.convert(createReqVO);\n        mpAccountMapper.insert(account);\n\n        // 刷新缓存\n        initLocalCache();\n        return account.getId();\n    }\n\n    @Override\n    public void updateAccount(MpAccountUpdateReqVO updateReqVO) {\n        // 校验存在\n        validateAccountExists(updateReqVO.getId());\n        // 校验 appId 唯一\n        validateAppIdUnique(updateReqVO.getId(), updateReqVO.getAppId());\n\n        // 更新\n        MpAccountDO updateObj = MpAccountConvert.INSTANCE.convert(updateReqVO);\n        mpAccountMapper.updateById(updateObj);\n\n        // 刷新缓存\n        initLocalCache();\n    }\n\n    @Override\n    public void deleteAccount(Long id) {\n        // 校验存在\n        validateAccountExists(id);\n        // 删除\n        mpAccountMapper.deleteById(id);\n\n        // 刷新缓存\n        initLocalCache();\n    }\n\n    private MpAccountDO validateAccountExists(Long id) {\n        MpAccountDO account = mpAccountMapper.selectById(id);\n        if (account == null) {\n            throw ServiceExceptionUtil.exception(ErrorCodeConstants.ACCOUNT_NOT_EXISTS);\n        }\n        return account;\n    }\n\n    @VisibleForTesting\n    public void validateAppIdUnique(Long id, String appId) {\n        // 多个租户，appId 是不能重复，否则公众号回调会无法识别\n        TenantUtils.executeIgnore(() -> {\n            MpAccountDO account = mpAccountMapper.selectByAppId(appId);\n            if (account == null) {\n                return;\n            }\n            // 存在 account 记录的情况下\n            if (id == null // 新增时，说明重复\n                    || ObjUtil.notEqual(id, account.getId())) { // 更新时，如果 id 不一致，说明重复\n                throw exception(USER_USERNAME_EXISTS);\n            }\n        });\n    }\n\n    @Override\n    public MpAccountDO getAccount(Long id) {\n        return mpAccountMapper.selectById(id);\n    }\n\n    @Override\n    public MpAccountDO getAccountFromCache(String appId) {\n        return accountCache.get(appId);\n    }\n\n    @Override\n    public PageResult<MpAccountDO> getAccountPage(MpAccountPageReqVO pageReqVO) {\n        return mpAccountMapper.selectPage(pageReqVO);\n    }\n\n    @Override\n    public List<MpAccountDO> getAccountList() {\n        return mpAccountMapper.selectList();\n    }\n\n    @Override\n    public void generateAccountQrCode(Long id) {\n        // 校验存在\n        MpAccountDO account = validateAccountExists(id);\n\n        // 生成二维码\n        WxMpService mpService = mpServiceFactory.getRequiredMpService(account.getAppId());\n        String qrCodeUrl;\n        try {\n            WxMpQrCodeTicket qrCodeTicket = mpService.getQrcodeService().qrCodeCreateLastTicket(\"default\");\n            qrCodeUrl = mpService.getQrcodeService().qrCodePictureUrl(qrCodeTicket.getTicket());\n        } catch (WxErrorException e) {\n            throw exception(ErrorCodeConstants.ACCOUNT_GENERATE_QR_CODE_FAIL, e.getError().getErrorMsg());\n        }\n\n        // 保存二维码\n        mpAccountMapper.updateById(new MpAccountDO().setId(id).setQrCodeUrl(qrCodeUrl));\n    }\n\n    @Override\n    public void clearAccountQuota(Long id) {\n        // 校验存在\n        MpAccountDO account = validateAccountExists(id);\n\n        // 生成二维码\n        WxMpService mpService = mpServiceFactory.getRequiredMpService(account.getAppId());\n        try {\n            mpService.clearQuota(account.getAppId());\n        } catch (WxErrorException e) {\n            throw exception(ErrorCodeConstants.ACCOUNT_CLEAR_QUOTA_FAIL, e.getError().getErrorMsg());\n        }\n    }\n\n\n\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/service/handler/menu/MenuHandler.java",
    "content": "package co.yixiang.yshop.module.mp.service.handler.menu;\n\nimport co.yixiang.yshop.module.mp.framework.mp.core.context.MpContextHolder;\nimport co.yixiang.yshop.module.mp.service.menu.MpMenuService;\nimport me.chanjar.weixin.common.session.WxSessionManager;\nimport me.chanjar.weixin.mp.api.WxMpMenuService;\nimport me.chanjar.weixin.mp.api.WxMpMessageHandler;\nimport me.chanjar.weixin.mp.api.WxMpService;\nimport me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;\nimport me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;\nimport org.springframework.stereotype.Component;\n\nimport jakarta.annotation.Resource;\nimport java.util.Map;\n\nimport static me.chanjar.weixin.common.api.WxConsts.MenuButtonType;\n\n/**\n * 自定义菜单的事件处理器\n *\n * 逻辑：粉丝点击菜单时，触发对应的回复\n *\n * @author yshop\n */\n@Component\npublic class MenuHandler implements WxMpMessageHandler {\n\n    @Resource\n    private MpMenuService mpMenuService;\n\n    @Override\n    public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context,\n                                    WxMpService weixinService, WxSessionManager sessionManager) {\n        return mpMenuService.reply(MpContextHolder.getAppId(), wxMessage.getEventKey(), wxMessage.getFromUser());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/service/handler/message/MessageAutoReplyHandler.java",
    "content": "package co.yixiang.yshop.module.mp.service.handler.message;\n\nimport co.yixiang.yshop.module.mp.dal.dataobject.message.MpAutoReplyDO;\nimport co.yixiang.yshop.module.mp.framework.mp.core.context.MpContextHolder;\nimport co.yixiang.yshop.module.mp.service.message.MpAutoReplyService;\nimport lombok.extern.slf4j.Slf4j;\nimport me.chanjar.weixin.common.session.WxSessionManager;\nimport me.chanjar.weixin.mp.api.WxMpMessageHandler;\nimport me.chanjar.weixin.mp.api.WxMpService;\nimport me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;\nimport me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;\nimport org.springframework.stereotype.Component;\n\nimport jakarta.annotation.Resource;\nimport java.util.Map;\n\n/**\n * 自动回复消息的事件处理器\n *\n * @author yshop\n */\n@Component\n@Slf4j\npublic class MessageAutoReplyHandler implements WxMpMessageHandler {\n\n    @Resource\n    private MpAutoReplyService mpAutoReplyService;\n\n    @Override\n    public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context,\n                                    WxMpService weixinService, WxSessionManager sessionManager) {\n        // 只处理指定类型的消息\n        if (!MpAutoReplyDO.REQUEST_MESSAGE_TYPE.contains(wxMessage.getMsgType())) {\n            return null;\n        }\n\n        // 自动回复\n        return mpAutoReplyService.replyForMessage(MpContextHolder.getAppId(), wxMessage);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/service/handler/message/MessageReceiveHandler.java",
    "content": "package co.yixiang.yshop.module.mp.service.handler.message;\n\nimport co.yixiang.yshop.module.mp.framework.mp.core.context.MpContextHolder;\nimport co.yixiang.yshop.module.mp.service.message.MpMessageService;\nimport lombok.extern.slf4j.Slf4j;\nimport me.chanjar.weixin.common.session.WxSessionManager;\nimport me.chanjar.weixin.mp.api.WxMpMessageHandler;\nimport me.chanjar.weixin.mp.api.WxMpService;\nimport me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;\nimport me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;\nimport org.springframework.stereotype.Component;\n\nimport jakarta.annotation.Resource;\nimport java.util.Map;\n\n/**\n * 保存微信消息的事件处理器\n *\n * @author yshop\n */\n@Component\n@Slf4j\npublic class MessageReceiveHandler implements WxMpMessageHandler {\n\n    @Resource\n    private MpMessageService mpMessageService;\n\n    @Override\n    public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context,\n                                    WxMpService wxMpService, WxSessionManager sessionManager) {\n        log.info(\"[handle][接收到请求消息，内容：{}]\", wxMessage);\n        mpMessageService.receiveMessage(MpContextHolder.getAppId(), wxMessage);\n        return null;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/service/handler/other/KfSessionHandler.java",
    "content": "package co.yixiang.yshop.module.mp.service.handler.other;\n\nimport me.chanjar.weixin.common.session.WxSessionManager;\nimport me.chanjar.weixin.mp.api.WxMpMessageHandler;\nimport me.chanjar.weixin.mp.api.WxMpService;\nimport me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;\nimport me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;\nimport org.springframework.stereotype.Component;\n\nimport java.util.Map;\n\n/**\n * 接收客服会话管理的事件处理器\n *\n * @author yshop\n */\n@Component\npublic class KfSessionHandler implements WxMpMessageHandler {\n\n    @Override\n    public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context,\n                                    WxMpService wxMpService, WxSessionManager sessionManager) {\n        throw new UnsupportedOperationException(\"未实现该处理，请自行重写\");\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/service/handler/other/NullHandler.java",
    "content": "package co.yixiang.yshop.module.mp.service.handler.other;\n\nimport me.chanjar.weixin.common.session.WxSessionManager;\nimport me.chanjar.weixin.mp.api.WxMpMessageHandler;\nimport me.chanjar.weixin.mp.api.WxMpService;\nimport me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;\nimport me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;\nimport org.springframework.stereotype.Component;\n\nimport java.util.Map;\n\n/**\n * 点击菜单连接的事件处理器\n */\n@Component\npublic class NullHandler implements WxMpMessageHandler {\n\n    @Override\n    public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context,\n                                    WxMpService wxMpService, WxSessionManager sessionManager) {\n        throw new UnsupportedOperationException(\"未实现该处理，请自行重写\");\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/service/handler/other/ScanHandler.java",
    "content": "package co.yixiang.yshop.module.mp.service.handler.other;\n\nimport me.chanjar.weixin.common.error.WxErrorException;\nimport me.chanjar.weixin.common.session.WxSessionManager;\nimport me.chanjar.weixin.mp.api.WxMpMessageHandler;\nimport me.chanjar.weixin.mp.api.WxMpService;\nimport me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;\nimport me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;\nimport org.springframework.stereotype.Component;\n\nimport java.util.Map;\n\n/**\n * 扫码的事件处理器\n */\n@Component\npublic class ScanHandler implements WxMpMessageHandler {\n\n    @Override\n    public WxMpXmlOutMessage handle(WxMpXmlMessage wxMpXmlMessage, Map<String, Object> context,\n                                    WxMpService wxMpService, WxSessionManager wxSessionManager) throws WxErrorException {\n        throw new UnsupportedOperationException(\"未实现该处理，请自行重写\");\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/service/handler/other/StoreCheckNotifyHandler.java",
    "content": "package co.yixiang.yshop.module.mp.service.handler.other;\n\nimport me.chanjar.weixin.common.session.WxSessionManager;\nimport me.chanjar.weixin.mp.api.WxMpMessageHandler;\nimport me.chanjar.weixin.mp.api.WxMpService;\nimport me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;\nimport me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;\nimport org.springframework.stereotype.Component;\n\nimport java.util.Map;\n\n/**\n * 门店审核事件的事件处理器\n */\n@Component\npublic class StoreCheckNotifyHandler implements WxMpMessageHandler {\n\n    @Override\n    public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context,\n                                    WxMpService wxMpService, WxSessionManager sessionManager) {\n        throw new UnsupportedOperationException(\"未实现该处理，请自行重写\");\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/service/handler/other/package-info.java",
    "content": "/**\n * 本包内的 handler 都是一些不重要的，所以放在 other 其它里\n */\npackage co.yixiang.yshop.module.mp.service.handler.other;\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/service/handler/user/LocationHandler.java",
    "content": "package co.yixiang.yshop.module.mp.service.handler.user;\n\nimport cn.hutool.core.util.ObjectUtil;\nimport co.yixiang.yshop.framework.common.util.object.ObjectUtils;\nimport co.yixiang.yshop.module.mp.framework.mp.core.context.MpContextHolder;\nimport co.yixiang.yshop.module.mp.service.message.MpAutoReplyService;\nimport lombok.extern.slf4j.Slf4j;\nimport me.chanjar.weixin.common.api.WxConsts;\nimport me.chanjar.weixin.common.session.WxSessionManager;\nimport me.chanjar.weixin.mp.api.WxMpMessageHandler;\nimport me.chanjar.weixin.mp.api.WxMpService;\nimport me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;\nimport me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;\nimport org.springframework.stereotype.Component;\n\nimport jakarta.annotation.Resource;\nimport java.util.Map;\n\n/**\n * 上报地理位置的事件处理器\n *\n * 触发操作：打开微信公众号 -> 点击 + 号 -> 选择「语音」\n *\n * 逻辑：粉丝上传地理位置时，也可以触发自动回复\n *\n * @author yshop\n */\n@Component\n@Slf4j\npublic class LocationHandler implements WxMpMessageHandler {\n\n    @Resource\n    private MpAutoReplyService mpAutoReplyService;\n\n    @Override\n    public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context,\n                                    WxMpService wxMpService, WxSessionManager sessionManager) {\n        // 防御性编程：必须是 LOCATION 消息\n        if (ObjectUtil.notEqual(wxMessage.getMsgType(), WxConsts.XmlMsgType.LOCATION)) {\n            return null;\n        }\n        log.info(\"[handle][上报地理位置，纬度({})、经度({})、精度({})\", wxMessage.getLatitude(),\n                wxMessage.getLongitude(), wxMessage.getPrecision());\n\n        // 自动回复\n        return mpAutoReplyService.replyForMessage(MpContextHolder.getAppId(), wxMessage);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/service/handler/user/SubscribeHandler.java",
    "content": "package co.yixiang.yshop.module.mp.service.handler.user;\n\nimport co.yixiang.yshop.module.mp.framework.mp.core.context.MpContextHolder;\nimport co.yixiang.yshop.module.mp.service.message.MpAutoReplyService;\nimport co.yixiang.yshop.module.mp.service.user.MpUserService;\nimport lombok.extern.slf4j.Slf4j;\nimport me.chanjar.weixin.common.error.WxErrorException;\nimport me.chanjar.weixin.common.session.WxSessionManager;\nimport me.chanjar.weixin.mp.api.WxMpMessageHandler;\nimport me.chanjar.weixin.mp.api.WxMpService;\nimport me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;\nimport me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;\nimport me.chanjar.weixin.mp.bean.result.WxMpUser;\nimport org.springframework.stereotype.Component;\n\nimport jakarta.annotation.Resource;\nimport java.util.Map;\n\n/**\n * 关注的事件处理器\n *\n * @author yshop\n */\n@Component\n@Slf4j\npublic class SubscribeHandler implements WxMpMessageHandler {\n\n    @Resource\n    private MpUserService mpUserService;\n    @Resource\n    private MpAutoReplyService mpAutoReplyService;\n\n    @Override\n    public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage, Map<String, Object> context,\n                                    WxMpService weixinService, WxSessionManager sessionManager) throws WxErrorException {\n        // 第一步，从公众号平台，获取粉丝信息\n        log.info(\"[handle][粉丝({}) 关注]\", wxMessage.getFromUser());\n        WxMpUser wxMpUser = null;\n        try {\n            wxMpUser = weixinService.getUserService().userInfo(wxMessage.getFromUser());\n        } catch (WxErrorException e) {\n            log.error(\"[handle][粉丝({})] 获取粉丝信息失败！\", wxMessage.getFromUser(), e);\n        }\n\n        // 第二步，保存粉丝信息\n        mpUserService.saveUser(MpContextHolder.getAppId(), wxMpUser);\n\n        // 第三步，回复关注的欢迎语\n        return mpAutoReplyService.replyForSubscribe(MpContextHolder.getAppId(), wxMessage);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/service/handler/user/UnsubscribeHandler.java",
    "content": "package co.yixiang.yshop.module.mp.service.handler.user;\n\nimport co.yixiang.yshop.module.mp.framework.mp.core.context.MpContextHolder;\nimport co.yixiang.yshop.module.mp.service.user.MpUserService;\nimport lombok.extern.slf4j.Slf4j;\nimport me.chanjar.weixin.common.session.WxSessionManager;\nimport me.chanjar.weixin.mp.api.WxMpMessageHandler;\nimport me.chanjar.weixin.mp.api.WxMpService;\nimport me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;\nimport me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;\nimport org.springframework.context.annotation.Lazy;\nimport org.springframework.stereotype.Component;\n\nimport jakarta.annotation.Resource;\nimport java.util.Map;\n\n/**\n * 取消关注的事件处理器\n *\n * @author yshop\n */\n@Component\n@Slf4j\npublic class UnsubscribeHandler implements WxMpMessageHandler {\n\n    @Resource\n    @Lazy // 延迟加载，解决循环依赖的问题\n    private MpUserService mpUserService;\n\n    @Override\n    public WxMpXmlOutMessage handle(WxMpXmlMessage wxMessage,\n                                    Map<String, Object> context, WxMpService wxMpService,\n                                    WxSessionManager sessionManager) {\n        log.info(\"[handle][粉丝({}) 取消关注]\", wxMessage.getFromUser());\n        mpUserService.updateUserUnsubscribe(MpContextHolder.getAppId(), wxMessage.getFromUser());\n        return null;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/service/material/MpMaterialService.java",
    "content": "package co.yixiang.yshop.module.mp.service.material;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.mp.controller.admin.material.vo.MpMaterialPageReqVO;\nimport co.yixiang.yshop.module.mp.controller.admin.material.vo.MpMaterialUploadNewsImageReqVO;\nimport co.yixiang.yshop.module.mp.controller.admin.material.vo.MpMaterialUploadPermanentReqVO;\nimport co.yixiang.yshop.module.mp.controller.admin.material.vo.MpMaterialUploadTemporaryReqVO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.material.MpMaterialDO;\nimport me.chanjar.weixin.common.api.WxConsts;\n\nimport jakarta.validation.Valid;\nimport java.io.IOException;\nimport java.util.Collection;\nimport java.util.List;\n\n/**\n * 公众号素材 Service 接口\n *\n * @author yshop\n */\npublic interface MpMaterialService {\n\n    /**\n     * 获得素材的 URL\n     *\n     * 该 URL 来自我们自己的文件服务器存储的 URL，不是公众号存储的 URL\n     *\n     * @param accountId 公众号账号编号\n     * @param mediaId 公众号素材 id\n     * @param type 文件类型 {@link WxConsts.MediaFileType}\n     * @return 素材的 URL\n     */\n    String downloadMaterialUrl(Long accountId, String mediaId, String type);\n\n    /**\n     * 上传临时素材\n     *\n     * @param reqVO 请求\n     * @return 素材\n     * @throws IOException 文件操作发生异常\n     */\n    MpMaterialDO uploadTemporaryMaterial(@Valid MpMaterialUploadTemporaryReqVO reqVO) throws IOException;\n\n    /**\n     * 上传永久素材\n     *\n     * @param reqVO 请求\n     * @return 素材\n     * @throws IOException 文件操作发生异常\n     */\n    MpMaterialDO uploadPermanentMaterial(@Valid MpMaterialUploadPermanentReqVO reqVO) throws IOException;\n\n    /**\n     * 上传图文内容中的图片\n     *\n     * @param reqVO 上传请求\n     * @return 图片地址\n     */\n    String uploadNewsImage(MpMaterialUploadNewsImageReqVO reqVO) throws IOException;\n\n    /**\n     * 获得素材分页\n     *\n     * @param pageReqVO 分页请求\n     * @return 素材分页\n     */\n    PageResult<MpMaterialDO> getMaterialPage(MpMaterialPageReqVO pageReqVO);\n\n    /**\n     * 获得素材列表\n     *\n     * @param mediaIds 素材 mediaId 列表\n     * @return 素材列表\n     */\n    List<MpMaterialDO> getMaterialListByMediaId(Collection<String> mediaIds);\n\n    /**\n     * 删除素材\n     *\n     * @param id 编号\n     */\n    void deleteMaterial(Long id);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/service/material/MpMaterialServiceImpl.java",
    "content": "package co.yixiang.yshop.module.mp.service.material;\n\nimport cn.hutool.core.io.FileTypeUtil;\nimport cn.hutool.core.io.FileUtil;\nimport cn.hutool.core.util.ObjUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.infra.api.file.FileApi;\nimport co.yixiang.yshop.module.mp.controller.admin.material.vo.MpMaterialPageReqVO;\nimport co.yixiang.yshop.module.mp.controller.admin.material.vo.MpMaterialUploadNewsImageReqVO;\nimport co.yixiang.yshop.module.mp.controller.admin.material.vo.MpMaterialUploadPermanentReqVO;\nimport co.yixiang.yshop.module.mp.controller.admin.material.vo.MpMaterialUploadTemporaryReqVO;\nimport co.yixiang.yshop.module.mp.convert.material.MpMaterialConvert;\nimport co.yixiang.yshop.module.mp.dal.dataobject.account.MpAccountDO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.material.MpMaterialDO;\nimport co.yixiang.yshop.module.mp.dal.mysql.material.MpMaterialMapper;\nimport co.yixiang.yshop.module.mp.framework.mp.core.MpServiceFactory;\nimport co.yixiang.yshop.module.mp.service.account.MpAccountService;\nimport lombok.extern.slf4j.Slf4j;\nimport me.chanjar.weixin.common.bean.result.WxMediaUploadResult;\nimport me.chanjar.weixin.common.error.WxErrorException;\nimport me.chanjar.weixin.mp.api.WxMpService;\nimport me.chanjar.weixin.mp.bean.material.WxMpMaterialUploadResult;\nimport org.springframework.context.annotation.Lazy;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.Collection;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.mp.enums.ErrorCodeConstants.*;\n\n/**\n * 公众号素材 Service 接口\n *\n * @author yshop\n */\n@Service\n@Validated\n@Slf4j\npublic class MpMaterialServiceImpl implements MpMaterialService {\n\n    @Resource\n    private MpMaterialMapper mpMaterialMapper;\n\n    @Resource\n    private FileApi fileApi;\n\n    @Resource\n    @Lazy // 延迟加载，解决循环依赖的问题\n    private MpAccountService mpAccountService;\n\n    @Resource\n    @Lazy // 延迟加载，解决循环依赖的问题\n    private MpServiceFactory mpServiceFactory;\n\n    @Override\n    public String downloadMaterialUrl(Long accountId, String mediaId, String type) {\n        // 第一步，直接从数据库查询。如果已经下载，直接返回\n        MpMaterialDO material = mpMaterialMapper.selectByAccountIdAndMediaId(accountId, mediaId);\n        if (material != null) {\n            return material.getUrl();\n        }\n\n        // 第二步，尝试从临时素材中下载\n        String url = downloadMedia(accountId, mediaId);\n        if (url == null) {\n            return null;\n        }\n        MpAccountDO account = mpAccountService.getRequiredAccount(accountId);\n        material = MpMaterialConvert.INSTANCE.convert(mediaId, type, url, account, null)\n                .setPermanent(false);\n        mpMaterialMapper.insert(material);\n\n        // 不考虑下载永久素材，因为上传的时候已经保存\n        return url;\n    }\n\n    @Override\n    public MpMaterialDO uploadTemporaryMaterial(MpMaterialUploadTemporaryReqVO reqVO) throws IOException {\n        WxMpService mpService = mpServiceFactory.getRequiredMpService(reqVO.getAccountId());\n        // 第一步，上传到公众号\n        File file = null;\n        WxMediaUploadResult result;\n        String mediaId;\n        String url;\n        try {\n            // 写入到临时文件\n            file = FileUtil.newFile(FileUtil.getTmpDirPath() + reqVO.getFile().getOriginalFilename());\n            reqVO.getFile().transferTo(file);\n            // 上传到公众号\n            result = mpService.getMaterialService().mediaUpload(reqVO.getType(), file);\n            // 上传到文件服务\n            mediaId = ObjUtil.defaultIfNull(result.getMediaId(), result.getThumbMediaId());\n            url = uploadFile(mediaId, file);\n        } catch (WxErrorException e) {\n            throw exception(MATERIAL_UPLOAD_FAIL, e.getError().getErrorMsg());\n        } finally {\n            FileUtil.del(file);\n        }\n\n        // 第二步，存储到数据库\n        MpAccountDO account = mpAccountService.getRequiredAccount(reqVO.getAccountId());\n        MpMaterialDO material = MpMaterialConvert.INSTANCE.convert(mediaId, reqVO.getType(), url, account,\n                        reqVO.getFile().getName()).setPermanent(false);\n        mpMaterialMapper.insert(material);\n        return material;\n    }\n\n    @Override\n    public MpMaterialDO uploadPermanentMaterial(MpMaterialUploadPermanentReqVO reqVO) throws IOException {\n        WxMpService mpService = mpServiceFactory.getRequiredMpService(reqVO.getAccountId());\n        // 第一步，上传到公众号\n        String name = StrUtil.blankToDefault(reqVO.getName(), reqVO.getFile().getName());\n        File file = null;\n        WxMpMaterialUploadResult result;\n        String mediaId;\n        String url;\n        try {\n            // 写入到临时文件\n            file = FileUtil.newFile(FileUtil.getTmpDirPath() + reqVO.getFile().getOriginalFilename());\n            reqVO.getFile().transferTo(file);\n            // 上传到公众号\n            result = mpService.getMaterialService().materialFileUpload(reqVO.getType(),\n                    MpMaterialConvert.INSTANCE.convert(name, file, reqVO.getTitle(), reqVO.getIntroduction()));\n            // 上传到文件服务\n            mediaId = ObjUtil.defaultIfNull(result.getMediaId(), result.getMediaId());\n            url = uploadFile(mediaId, file);\n        } catch (WxErrorException e) {\n            throw exception(MATERIAL_UPLOAD_FAIL, e.getError().getErrorMsg());\n        } finally {\n            FileUtil.del(file);\n        }\n\n        // 第二步，存储到数据库\n        MpAccountDO account = mpAccountService.getRequiredAccount(reqVO.getAccountId());\n        MpMaterialDO material = MpMaterialConvert.INSTANCE.convert(mediaId, reqVO.getType(), url, account,\n                        name, reqVO.getTitle(), reqVO.getIntroduction(), result.getUrl()).setPermanent(true);\n        mpMaterialMapper.insert(material);\n        return material;\n    }\n\n    @Override\n    public String uploadNewsImage(MpMaterialUploadNewsImageReqVO reqVO) throws IOException {\n        WxMpService mpService = mpServiceFactory.getRequiredMpService(reqVO.getAccountId());\n        File file = null;\n        try {\n            // 写入到临时文件\n            file = FileUtil.newFile(FileUtil.getTmpDirPath() + reqVO.getFile().getOriginalFilename());\n            reqVO.getFile().transferTo(file);\n            // 上传到公众号\n            return mpService.getMaterialService().mediaImgUpload(file).getUrl();\n        } catch (WxErrorException e) {\n            throw exception(MATERIAL_IMAGE_UPLOAD_FAIL, e.getError().getErrorMsg());\n        } finally {\n            FileUtil.del(file);\n        }\n    }\n\n    @Override\n    public PageResult<MpMaterialDO> getMaterialPage(MpMaterialPageReqVO pageReqVO) {\n        return mpMaterialMapper.selectPage(pageReqVO);\n    }\n\n    @Override\n    public List<MpMaterialDO> getMaterialListByMediaId(Collection<String> mediaIds) {\n        return mpMaterialMapper.selectListByMediaId(mediaIds);\n    }\n\n    @Override\n    public void deleteMaterial(Long id) {\n        MpMaterialDO material = mpMaterialMapper.selectById(id);\n        if (material == null) {\n            throw exception(MATERIAL_NOT_EXISTS);\n        }\n\n        // 第一步，从公众号删除\n        if (material.getPermanent()) {\n            WxMpService mpService = mpServiceFactory.getRequiredMpService(material.getAppId());\n            try {\n                mpService.getMaterialService().materialDelete(material.getMediaId());\n            } catch (WxErrorException e) {\n                throw exception(MATERIAL_DELETE_FAIL, e.getError().getErrorMsg());\n            }\n        }\n\n        // 第二步，从数据库中删除\n        mpMaterialMapper.deleteById(id);\n    }\n\n    /**\n     * 下载微信媒体文件的内容，并上传到文件服务\n     *\n     * 为什么要下载？媒体文件在微信后台保存时间为 3 天，即 3 天后 media_id 失效。\n     *\n     * @param accountId 公众号账号的编号\n     * @param mediaId 媒体文件编号\n     * @return 上传后的 URL\n     */\n    public String downloadMedia(Long accountId, String mediaId) {\n        WxMpService mpService = mpServiceFactory.getMpService(accountId);\n        for (int i = 0; i < 3; i++) {\n            try {\n                // 第一步，从公众号下载媒体文件\n                File file = mpService.getMaterialService().mediaDownload(mediaId);\n                // 第二步，上传到文件服务\n                return uploadFile(mediaId, file);\n            } catch (WxErrorException e) {\n                log.error(\"[mediaDownload][media({}) 第 ({}) 次下载失败]\", mediaId, i);\n            }\n        }\n        return null;\n    }\n\n    private String uploadFile(String mediaId, File file) {\n        String path = mediaId + \".\" + FileTypeUtil.getType(file);\n        return fileApi.createFile(path, FileUtil.readBytes(file));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/service/menu/MpMenuService.java",
    "content": "package co.yixiang.yshop.module.mp.service.menu;\n\nimport co.yixiang.yshop.module.mp.controller.admin.menu.vo.MpMenuSaveReqVO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.menu.MpMenuDO;\nimport me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;\n\nimport jakarta.validation.Valid;\nimport java.util.List;\n\n/**\n * 公众号菜单 Service 接口\n *\n * @author yshop\n */\npublic interface MpMenuService {\n\n    /**\n     * 保存公众号菜单\n     *\n     * @param createReqVO 创建信息\n     */\n    void saveMenu(@Valid MpMenuSaveReqVO createReqVO);\n\n    /**\n     * 删除公众号菜单\n     *\n     * @param accountId 公众号账号的编号\n     */\n    void deleteMenuByAccountId(Long accountId);\n\n    /**\n     * 粉丝点击菜单按钮时，回复对应的消息\n     *\n     * @param appId 公众号 AppId\n     * @param key 菜单按钮的标识\n     * @param openid 粉丝的 openid\n     * @return 消息\n     */\n    WxMpXmlOutMessage reply(String appId, String key, String openid);\n\n    /**\n     * 获得公众号菜单列表\n     *\n     * @param accountId 公众号账号的编号\n     * @return 公众号菜单列表\n     */\n    List<MpMenuDO> getMenuListByAccountId(Long accountId);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/service/menu/MpMenuServiceImpl.java",
    "content": "package co.yixiang.yshop.module.mp.service.menu;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.module.mp.controller.admin.menu.vo.MpMenuSaveReqVO;\nimport co.yixiang.yshop.module.mp.convert.menu.MpMenuConvert;\nimport co.yixiang.yshop.module.mp.dal.dataobject.account.MpAccountDO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.menu.MpMenuDO;\nimport co.yixiang.yshop.module.mp.dal.mysql.menu.MpMenuMapper;\nimport co.yixiang.yshop.module.mp.framework.mp.core.MpServiceFactory;\nimport co.yixiang.yshop.module.mp.framework.mp.core.util.MpUtils;\nimport co.yixiang.yshop.module.mp.service.account.MpAccountService;\nimport co.yixiang.yshop.module.mp.service.message.MpMessageService;\nimport co.yixiang.yshop.module.mp.service.message.bo.MpMessageSendOutReqBO;\nimport lombok.extern.slf4j.Slf4j;\nimport me.chanjar.weixin.common.bean.menu.WxMenu;\nimport me.chanjar.weixin.common.error.WxErrorException;\nimport me.chanjar.weixin.mp.api.WxMpService;\nimport me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;\nimport org.springframework.context.annotation.Lazy;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Validator;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.mp.enums.ErrorCodeConstants.MENU_DELETE_FAIL;\nimport static co.yixiang.yshop.module.mp.enums.ErrorCodeConstants.MENU_SAVE_FAIL;\n\n/**\n * 公众号菜单 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\n@Slf4j\npublic class MpMenuServiceImpl implements MpMenuService {\n\n    @Resource\n    private MpMessageService mpMessageService;\n    @Resource\n    @Lazy // 延迟加载，避免循环引用报错\n    private MpAccountService mpAccountService;\n\n    @Resource\n    @Lazy // 延迟加载，避免循环引用报错\n    private MpServiceFactory mpServiceFactory;\n\n    @Resource\n    private Validator validator;\n\n    @Resource\n    private MpMenuMapper mpMenuMapper;\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void saveMenu(MpMenuSaveReqVO createReqVO) {\n        MpAccountDO account = mpAccountService.getRequiredAccount(createReqVO.getAccountId());\n        WxMpService mpService = mpServiceFactory.getRequiredMpService(createReqVO.getAccountId());\n\n        // 参数校验\n        createReqVO.getMenus().forEach(this::validateMenu);\n\n        // 第一步，同步公众号\n        WxMenu wxMenu = new WxMenu();\n        wxMenu.setButtons(MpMenuConvert.INSTANCE.convert(createReqVO.getMenus()));\n        try {\n            mpService.getMenuService().menuCreate(wxMenu);\n        } catch (WxErrorException e) {\n            throw exception(MENU_SAVE_FAIL, e.getError().getErrorMsg());\n        }\n\n        // 第二步，存储到数据库\n        mpMenuMapper.deleteByAccountId(createReqVO.getAccountId());\n        createReqVO.getMenus().forEach(menu -> {\n            // 先保存顶级菜单\n            MpMenuDO menuDO = createMenu(menu, null, account);\n            // 再保存子菜单\n            if (CollUtil.isEmpty(menu.getChildren())) {\n                return;\n            }\n            menu.getChildren().forEach(childMenu -> createMenu(childMenu, menuDO, account));\n        });\n    }\n\n    /**\n     * 校验菜单的格式是否正确\n     *\n     * @param menu 菜单\n     */\n    private void validateMenu(MpMenuSaveReqVO.Menu menu) {\n        MpUtils.validateButton(validator, menu.getType(), menu.getReplyMessageType(), menu);\n        // 子菜单\n        if (CollUtil.isEmpty(menu.getChildren())) {\n            return;\n        }\n        menu.getChildren().forEach(this::validateMenu);\n    }\n\n    /**\n     * 创建菜单，并存储到数据库\n     *\n     * @param wxMenu 菜单信息\n     * @param parentMenu 父菜单\n     * @param account 公众号账号\n     * @return 创建后的菜单\n     */\n    private MpMenuDO createMenu(MpMenuSaveReqVO.Menu wxMenu, MpMenuDO parentMenu, MpAccountDO account) {\n        // 创建菜单\n        MpMenuDO menu = CollUtil.isNotEmpty(wxMenu.getChildren())\n                ? new MpMenuDO().setName(wxMenu.getName())\n                : MpMenuConvert.INSTANCE.convert02(wxMenu);\n        // 设置菜单的公众号账号信息\n        if (account != null) {\n            menu.setAccountId(account.getId()).setAppId(account.getAppId());\n        }\n        // 设置父编号\n        if (parentMenu != null) {\n            menu.setParentId(parentMenu.getId());\n        } else {\n            menu.setParentId(MpMenuDO.ID_ROOT);\n        }\n\n        // 插入到数据库\n        mpMenuMapper.insert(menu);\n        return menu;\n    }\n\n    @Override\n    public void deleteMenuByAccountId(Long accountId) {\n        WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId);\n        // 第一步，同步公众号\n        try {\n            mpService.getMenuService().menuDelete();\n        } catch (WxErrorException e) {\n            throw exception(MENU_DELETE_FAIL, e.getError().getErrorMsg());\n        }\n\n        // 第二步，存储到数据库\n        mpMenuMapper.deleteByAccountId(accountId);\n    }\n\n    @Override\n    public WxMpXmlOutMessage reply(String appId, String key, String openid) {\n        // 第一步，获得菜单\n        MpMenuDO menu = mpMenuMapper.selectByAppIdAndMenuKey(appId, key);\n        if (menu == null) {\n            log.error(\"[reply][appId({}) key({}) 找不到对应的菜单]\", appId, key);\n            return null;\n        }\n        // 按钮必须要有消息类型，不然后续无法回复消息\n        if (StrUtil.isEmpty(menu.getReplyMessageType())) {\n            log.error(\"[reply][menu({}) 不存在对应的消息类型]\", menu);\n            return null;\n        }\n\n        // 第二步，回复消息\n        MpMessageSendOutReqBO sendReqBO = MpMenuConvert.INSTANCE.convert(openid, menu);\n        return mpMessageService.sendOutMessage(sendReqBO);\n    }\n\n    @Override\n    public List<MpMenuDO> getMenuListByAccountId(Long accountId) {\n        return mpMenuMapper.selectListByAccountId(accountId);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/service/message/MpAutoReplyService.java",
    "content": "package co.yixiang.yshop.module.mp.service.message;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.mp.controller.admin.message.vo.autoreply.MpAutoReplyCreateReqVO;\nimport co.yixiang.yshop.module.mp.controller.admin.message.vo.autoreply.MpAutoReplyUpdateReqVO;\nimport co.yixiang.yshop.module.mp.controller.admin.message.vo.message.MpMessagePageReqVO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.message.MpAutoReplyDO;\nimport me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;\nimport me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;\n\n/**\n * 公众号的自动回复 Service 接口\n *\n * @author yshop\n */\npublic interface MpAutoReplyService {\n\n    /**\n     * 获得公众号自动回复分页\n     *\n     * @param pageVO 分页请求\n     * @return 自动回复分页结果\n     */\n    PageResult<MpAutoReplyDO> getAutoReplyPage(MpMessagePageReqVO pageVO);\n\n    /**\n     * 获得公众号自动回复\n     *\n     * @param id 编号\n     * @return 自动回复\n     */\n    MpAutoReplyDO getAutoReply(Long id);\n\n\n    /**\n     * 创建公众号自动回复\n     *\n     * @param createReqVO 创建请求\n     * @return 自动回复的编号\n     */\n    Long createAutoReply(MpAutoReplyCreateReqVO createReqVO);\n\n    /**\n     * 更新公众号自动回复\n     *\n     * @param updateReqVO 更新请求\n     */\n    void updateAutoReply(MpAutoReplyUpdateReqVO updateReqVO);\n\n    /**\n     * 删除公众号自动回复\n     *\n     * @param id 自动回复的编号\n     */\n    void deleteAutoReply(Long id);\n\n    /**\n     * 当收到消息时，自动回复\n     *\n     * @param appId 微信公众号 appId\n     * @param wxMessage 消息\n     * @return 回复的消息\n     */\n    WxMpXmlOutMessage replyForMessage(String appId, WxMpXmlMessage wxMessage);\n\n    /**\n     * 当粉丝关注时，自动回复\n     *\n     * @param appId 微信公众号 appId\n     * @param wxMessage 消息\n     * @return 回复的消息\n     */\n    WxMpXmlOutMessage replyForSubscribe(String appId, WxMpXmlMessage wxMessage);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/service/message/MpAutoReplyServiceImpl.java",
    "content": "package co.yixiang.yshop.module.mp.service.message;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.lang.Assert;\nimport cn.hutool.core.util.ObjUtil;\nimport co.yixiang.yshop.framework.common.exception.ErrorCode;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.mp.controller.admin.message.vo.autoreply.MpAutoReplyCreateReqVO;\nimport co.yixiang.yshop.module.mp.controller.admin.message.vo.autoreply.MpAutoReplyUpdateReqVO;\nimport co.yixiang.yshop.module.mp.controller.admin.message.vo.message.MpMessagePageReqVO;\nimport co.yixiang.yshop.module.mp.convert.message.MpAutoReplyConvert;\nimport co.yixiang.yshop.module.mp.dal.dataobject.account.MpAccountDO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.message.MpAutoReplyDO;\nimport co.yixiang.yshop.module.mp.dal.mysql.message.MpAutoReplyMapper;\nimport co.yixiang.yshop.module.mp.enums.message.MpAutoReplyTypeEnum;\nimport co.yixiang.yshop.module.mp.framework.mp.core.util.MpUtils;\nimport co.yixiang.yshop.module.mp.service.account.MpAccountService;\nimport co.yixiang.yshop.module.mp.service.message.bo.MpMessageSendOutReqBO;\nimport me.chanjar.weixin.common.api.WxConsts;\nimport me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;\nimport me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;\nimport org.springframework.context.annotation.Lazy;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Validator;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.mp.enums.ErrorCodeConstants.*;\n\n/**\n * 公众号的自动回复 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class MpAutoReplyServiceImpl implements MpAutoReplyService {\n\n    @Resource\n    private MpMessageService mpMessageService;\n    @Resource\n    @Lazy // 延迟加载，避免循环依赖\n    private MpAccountService mpAccountService;\n\n    @Resource\n    private Validator validator;\n\n    @Resource\n    private MpAutoReplyMapper mpAutoReplyMapper;\n\n    @Override\n    public PageResult<MpAutoReplyDO> getAutoReplyPage(MpMessagePageReqVO pageVO) {\n        return mpAutoReplyMapper.selectPage(pageVO);\n    }\n\n    @Override\n    public MpAutoReplyDO getAutoReply(Long id) {\n        return mpAutoReplyMapper.selectById(id);\n    }\n\n    @Override\n    public Long createAutoReply(MpAutoReplyCreateReqVO createReqVO) {\n        // 第一步，校验数据\n        if (createReqVO.getResponseMessageType() != null) {\n            MpUtils.validateMessage(validator, createReqVO.getResponseMessageType(), createReqVO);\n        }\n        validateAutoReplyConflict(null, createReqVO.getAccountId(), createReqVO.getType(),\n                createReqVO.getRequestKeyword(), createReqVO.getRequestMessageType());\n\n        // 第二步，插入数据\n        MpAccountDO account = mpAccountService.getRequiredAccount(createReqVO.getAccountId());\n        MpAutoReplyDO autoReply = MpAutoReplyConvert.INSTANCE.convert(createReqVO)\n                .setAppId(account.getAppId());\n        mpAutoReplyMapper.insert(autoReply);\n        return autoReply.getId();\n    }\n\n    @Override\n    public void updateAutoReply(MpAutoReplyUpdateReqVO updateReqVO) {\n        // 第一步，校验数据\n        if (updateReqVO.getResponseMessageType() != null) {\n            MpUtils.validateMessage(validator, updateReqVO.getResponseMessageType(), updateReqVO);\n        }\n        MpAutoReplyDO autoReply = validateAutoReplyExists(updateReqVO.getId());\n        validateAutoReplyConflict(updateReqVO.getId(), autoReply.getAccountId(), updateReqVO.getType(),\n                updateReqVO.getRequestKeyword(), updateReqVO.getRequestMessageType());\n\n        // 第二步，更新数据\n        MpAutoReplyDO updateObj = MpAutoReplyConvert.INSTANCE.convert(updateReqVO)\n                .setAccountId(null).setAppId(null); // 避免前端传递，更新着两个字段\n        mpAutoReplyMapper.updateById(updateObj);\n    }\n\n    /**\n     * 校验自动回复是否冲突\n     *\n     * 不同的 type，会有不同的逻辑：\n     * 1. type = SUBSCRIBE 时，不允许有其他的自动回复\n     * 2. type = MESSAGE 时，校验 requestMessageType 已经存在自动回复\n     * 3. type = KEYWORD 时，校验 keyword 已经存在自动回复\n     *\n     * @param id 自动回复编号\n     * @param accountId 公众号账号的编号\n     * @param type 类型\n     * @param requestKeyword 请求关键词\n     * @param requestMessageType 请求消息类型\n     */\n    private void validateAutoReplyConflict(Long id, Long accountId, Integer type,\n                                           String requestKeyword, String requestMessageType) {\n        // 获得已经存在的自动回复\n        MpAutoReplyDO autoReply = null;\n        ErrorCode errorCode = null;\n        if (MpAutoReplyTypeEnum.SUBSCRIBE.getType().equals(type)) {\n            autoReply = mpAutoReplyMapper.selectByAccountIdAndSubscribe(accountId);\n            errorCode = AUTO_REPLY_ADD_SUBSCRIBE_FAIL_EXISTS;\n        } else if (MpAutoReplyTypeEnum.MESSAGE.getType().equals(type)) {\n            autoReply = mpAutoReplyMapper.selectByAccountIdAndMessage(accountId, requestMessageType);\n            errorCode = AUTO_REPLY_ADD_MESSAGE_FAIL_EXISTS;\n        } else if (MpAutoReplyTypeEnum.KEYWORD.getType().equals(type)) {\n            autoReply = mpAutoReplyMapper.selectByAccountIdAndKeyword(accountId, requestKeyword);\n            errorCode = AUTO_REPLY_ADD_KEYWORD_FAIL_EXISTS;\n        }\n        if (autoReply == null) {\n            return;\n        }\n\n        // 存在冲突，抛出业务异常\n        if (id == null // 情况一，新增（id == null），存在记录，说明冲突\n            || ObjUtil.notEqual(id, autoReply.getId())) { // 情况二，修改（id != null），id 不匹配，说明冲突\n            throw exception(errorCode);\n        }\n    }\n\n    @Override\n    public void deleteAutoReply(Long id) {\n        // 校验粉丝存在\n        validateAutoReplyExists(id);\n\n        // 删除自动回复\n        mpAutoReplyMapper.deleteById(id);\n    }\n\n    private MpAutoReplyDO validateAutoReplyExists(Long id) {\n        MpAutoReplyDO autoReply = mpAutoReplyMapper.selectById(id);\n        if (autoReply == null) {\n            throw exception(AUTO_REPLY_NOT_EXISTS);\n        }\n        return autoReply;\n    }\n\n    @Override\n    public WxMpXmlOutMessage replyForMessage(String appId, WxMpXmlMessage wxMessage) {\n        // 第一步，匹配自动回复\n        List<MpAutoReplyDO> replies = null;\n        // 1.1 关键字\n        if (wxMessage.getMsgType().equals(WxConsts.XmlMsgType.TEXT)) {\n            // 完全匹配\n            replies = mpAutoReplyMapper.selectListByAppIdAndKeywordAll(appId, wxMessage.getContent());\n            if (CollUtil.isEmpty(replies)) {\n                // 模糊匹配\n                replies = mpAutoReplyMapper.selectListByAppIdAndKeywordLike(appId, wxMessage.getContent());\n            }\n        }\n        // 1.2 消息类型\n        if (CollUtil.isEmpty(replies)) {\n            replies = mpAutoReplyMapper.selectListByAppIdAndMessage(appId, wxMessage.getMsgType());\n        }\n        if (CollUtil.isEmpty(replies)) {\n            return null;\n        }\n        MpAutoReplyDO reply = CollUtil.getFirst(replies);\n\n        // 第二步，基于自动回复，创建消息\n        MpMessageSendOutReqBO sendReqBO = MpAutoReplyConvert.INSTANCE.convert(wxMessage.getFromUser(), reply);\n        return mpMessageService.sendOutMessage(sendReqBO);\n    }\n\n    @Override\n    public WxMpXmlOutMessage replyForSubscribe(String appId, WxMpXmlMessage wxMessage) {\n        // 第一步，匹配自动回复\n        List<MpAutoReplyDO> replies = mpAutoReplyMapper.selectListByAppIdAndSubscribe(appId);\n        MpAutoReplyDO reply = CollUtil.isNotEmpty(replies) ? CollUtil.getFirst(replies)\n                : buildDefaultSubscribeAutoReply(appId); // 如果不存在，提供一个默认末班\n\n        // 第二步，基于自动回复，创建消息\n        MpMessageSendOutReqBO sendReqBO = MpAutoReplyConvert.INSTANCE.convert(wxMessage.getFromUser(), reply);\n        return mpMessageService.sendOutMessage(sendReqBO);\n    }\n\n    private MpAutoReplyDO buildDefaultSubscribeAutoReply(String appId) {\n        MpAccountDO account = mpAccountService.getAccountFromCache(appId);\n        Assert.notNull(account, \"公众号账号({}) 不存在\", appId);\n        // 构建默认的【关注】自动回复\n        return new MpAutoReplyDO().setAppId(appId).setAccountId(account.getId())\n                .setType(MpAutoReplyTypeEnum.SUBSCRIBE.getType())\n                .setResponseMessageType(WxConsts.XmlMsgType.TEXT).setResponseContent(\"感谢关注\");\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/service/message/MpMessageService.java",
    "content": "package co.yixiang.yshop.module.mp.service.message;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.mp.controller.admin.message.vo.message.MpMessagePageReqVO;\nimport co.yixiang.yshop.module.mp.controller.admin.message.vo.message.MpMessageSendReqVO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.message.MpMessageDO;\nimport co.yixiang.yshop.module.mp.service.message.bo.MpMessageSendOutReqBO;\nimport me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;\nimport me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;\n\nimport jakarta.validation.Valid;\n\n/**\n * 公众号消息 Service 接口\n *\n * @author yshop\n */\npublic interface MpMessageService {\n\n    /**\n     * 获得公众号消息分页\n     *\n     * @param pageReqVO 分页查询\n     * @return 公众号消息分页\n     */\n    PageResult<MpMessageDO> getMessagePage(MpMessagePageReqVO pageReqVO);\n\n    /**\n     * 从公众号，接收到粉丝消息\n     *\n     * @param appId 微信公众号 appId\n     * @param wxMessage 消息\n     */\n    void receiveMessage(String appId, WxMpXmlMessage wxMessage);\n\n    /**\n     * 使用公众号，给粉丝回复消息\n     *\n     * 例如说：自动回复、客服消息、菜单回复消息等场景\n     *\n     * 注意，该方法只是返回 WxMpXmlOutMessage 对象，不会真的发送消息\n     *\n     * @param sendReqBO 消息内容\n     * @return 微信回复消息 XML\n     */\n    WxMpXmlOutMessage sendOutMessage(@Valid MpMessageSendOutReqBO sendReqBO);\n\n    /**\n     * 使用公众号，给粉丝发送【客服】消息\n     *\n     * 注意，该方法会真实发送消息\n     *\n     * @param sendReqVO 消息内容\n     * @return 消息\n     */\n    MpMessageDO sendKefuMessage(MpMessageSendReqVO sendReqVO);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/service/message/MpMessageServiceImpl.java",
    "content": "package co.yixiang.yshop.module.mp.service.message;\n\nimport cn.hutool.core.lang.Assert;\nimport cn.hutool.core.util.ObjUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.mp.controller.admin.message.vo.message.MpMessagePageReqVO;\nimport co.yixiang.yshop.module.mp.controller.admin.message.vo.message.MpMessageSendReqVO;\nimport co.yixiang.yshop.module.mp.convert.message.MpMessageConvert;\nimport co.yixiang.yshop.module.mp.dal.dataobject.account.MpAccountDO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.message.MpMessageDO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.user.MpUserDO;\nimport co.yixiang.yshop.module.mp.dal.mysql.message.MpMessageMapper;\nimport co.yixiang.yshop.module.mp.enums.message.MpMessageSendFromEnum;\nimport co.yixiang.yshop.module.mp.framework.mp.core.MpServiceFactory;\nimport co.yixiang.yshop.module.mp.framework.mp.core.util.MpUtils;\nimport co.yixiang.yshop.module.mp.service.account.MpAccountService;\nimport co.yixiang.yshop.module.mp.service.material.MpMaterialService;\nimport co.yixiang.yshop.module.mp.service.message.bo.MpMessageSendOutReqBO;\nimport co.yixiang.yshop.module.mp.service.user.MpUserService;\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Validator;\nimport lombok.extern.slf4j.Slf4j;\nimport me.chanjar.weixin.common.api.WxConsts;\nimport me.chanjar.weixin.common.error.WxErrorException;\nimport me.chanjar.weixin.mp.api.WxMpService;\nimport me.chanjar.weixin.mp.bean.kefu.WxMpKefuMessage;\nimport me.chanjar.weixin.mp.bean.message.WxMpXmlMessage;\nimport me.chanjar.weixin.mp.bean.message.WxMpXmlOutMessage;\nimport org.springframework.context.annotation.Lazy;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.mp.enums.ErrorCodeConstants.MESSAGE_SEND_FAIL;\n\n/**\n * 粉丝消息 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\n@Slf4j\npublic class MpMessageServiceImpl implements MpMessageService {\n\n    @Resource\n    @Lazy // 延迟加载，避免循环依赖\n    private MpAccountService mpAccountService;\n    @Resource\n    private MpUserService mpUserService;\n    @Resource\n    private MpMaterialService mpMaterialService;\n\n    @Resource\n    private MpMessageMapper mpMessageMapper;\n\n    @Resource\n    @Lazy // 延迟加载，解决循环依赖的问题\n    private MpServiceFactory mpServiceFactory;\n\n    @Resource\n    private Validator validator;\n\n    @Override\n    public PageResult<MpMessageDO> getMessagePage(MpMessagePageReqVO pageReqVO) {\n        return mpMessageMapper.selectPage(pageReqVO);\n    }\n\n    @Override\n    public void receiveMessage(String appId, WxMpXmlMessage wxMessage) {\n        // 获得关联信息\n        MpAccountDO account = mpAccountService.getAccountFromCache(appId);\n        Assert.notNull(account, \"公众号账号({}) 不存在\", appId);\n\n        // 订阅事件不记录，因为此时公众号粉丝表中还没有此粉丝的数据\n        // TODO @yshop：这个修复，后续看看还有啥问题\n        if (ObjUtil.equal(wxMessage.getEvent(), WxConsts.EventType.SUBSCRIBE)) {\n            return;\n        }\n\n        MpUserDO user = mpUserService.getUser(appId, wxMessage.getFromUser());\n        Assert.notNull(user, \"公众号粉丝({}/{}) 不存在\", appId, wxMessage.getFromUser());\n\n        // 记录消息\n        MpMessageDO message = MpMessageConvert.INSTANCE.convert(wxMessage, account, user)\n                .setSendFrom(MpMessageSendFromEnum.USER_TO_MP.getFrom());\n        downloadMessageMedia(message);\n        mpMessageMapper.insert(message);\n    }\n\n    @Override\n    public WxMpXmlOutMessage sendOutMessage(MpMessageSendOutReqBO sendReqBO) {\n        // 校验消息格式\n        MpUtils.validateMessage(validator, sendReqBO.getType(), sendReqBO);\n\n        // 获得关联信息\n        MpAccountDO account = mpAccountService.getAccountFromCache(sendReqBO.getAppId());\n        Assert.notNull(account, \"公众号账号({}) 不存在\", sendReqBO.getAppId());\n        MpUserDO user = mpUserService.getUser(sendReqBO.getAppId(), sendReqBO.getOpenid());\n        Assert.notNull(user, \"公众号粉丝({}/{}) 不存在\", sendReqBO.getAppId(), sendReqBO.getOpenid());\n\n        // 记录消息\n        MpMessageDO message = MpMessageConvert.INSTANCE.convert(sendReqBO, account, user).\n                setSendFrom(MpMessageSendFromEnum.MP_TO_USER.getFrom());\n        downloadMessageMedia(message);\n        mpMessageMapper.insert(message);\n\n        // 转换返回 WxMpXmlOutMessage 对象\n        return MpMessageConvert.INSTANCE.convert02(message, account);\n    }\n\n    @Override\n    public MpMessageDO sendKefuMessage(MpMessageSendReqVO sendReqVO) {\n        // 校验消息格式\n        MpUtils.validateMessage(validator, sendReqVO.getType(), sendReqVO);\n\n        // 获得关联信息\n        MpUserDO user = mpUserService.getRequiredUser(sendReqVO.getUserId());\n        MpAccountDO account = mpAccountService.getRequiredAccount(user.getAccountId());\n\n        // 发送客服消息\n        WxMpKefuMessage wxMessage = MpMessageConvert.INSTANCE.convert(sendReqVO, user);\n        WxMpService mpService = mpServiceFactory.getRequiredMpService(user.getAppId());\n        try {\n            mpService.getKefuService().sendKefuMessageWithResponse(wxMessage);\n        } catch (WxErrorException e) {\n            throw exception(MESSAGE_SEND_FAIL, e.getError().getErrorMsg());\n        }\n\n        // 记录消息\n        MpMessageDO message = MpMessageConvert.INSTANCE.convert(wxMessage, account, user)\n                .setSendFrom(MpMessageSendFromEnum.MP_TO_USER.getFrom());\n        downloadMessageMedia(message);\n        mpMessageMapper.insert(message);\n        return message;\n    }\n\n    /**\n     * 下载消息使用到的媒体文件，并上传到文件服务\n     *\n     * @param message 消息\n     */\n    private void downloadMessageMedia(MpMessageDO message) {\n        if (StrUtil.isNotEmpty(message.getMediaId())) {\n            message.setMediaUrl(mpMaterialService.downloadMaterialUrl(message.getAccountId(),\n                    message.getMediaId(), MpUtils.getMediaFileType(message.getType())));\n        }\n        if (StrUtil.isNotEmpty(message.getThumbMediaId())) {\n            message.setThumbMediaUrl(mpMaterialService.downloadMaterialUrl(message.getAccountId(),\n                    message.getThumbMediaId(), WxConsts.MediaFileType.THUMB));\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/service/message/bo/MpMessageSendOutReqBO.java",
    "content": "package co.yixiang.yshop.module.mp.service.message.bo;\n\nimport co.yixiang.yshop.module.mp.dal.dataobject.message.MpMessageDO;\nimport co.yixiang.yshop.module.mp.framework.mp.core.util.MpUtils.*;\nimport lombok.Data;\nimport me.chanjar.weixin.common.api.WxConsts;\nimport org.hibernate.validator.constraints.URL;\n\nimport jakarta.validation.Valid;\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\nimport java.util.List;\n\n/**\n * 公众号消息发送 Request BO\n *\n * 为什么要有该 BO 呢？在自动回复、客服消息、菜单回复消息等场景，都涉及到 MP 给粉丝发送消息，所以使用该 BO 统一承接\n *\n * @author yshop\n */\n@Data\npublic class MpMessageSendOutReqBO {\n\n    /**\n     * 公众号 appId\n     */\n    @NotEmpty(message = \"公众号 appId 不能为空\")\n    private String appId;\n    /**\n     * 公众号粉丝 openid\n     */\n    @NotEmpty(message = \"公众号粉丝 openid 不能为空\")\n    private String openid;\n\n    // ========== 消息内容 ==========\n    /**\n     * 消息类型\n     *\n     * 枚举 {@link WxConsts.XmlMsgType} 中的 TEXT、IMAGE、VOICE、VIDEO、NEWS、MUSIC\n     */\n    @NotEmpty(message = \"消息类型不能为空\")\n    public String type;\n\n    /**\n     * 消息内容\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 TEXT\n     */\n    @NotEmpty(message = \"消息内容不能为空\", groups = TextMessageGroup.class)\n    private String content;\n\n    /**\n     * 媒体 id\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 IMAGE、VOICE、VIDEO\n     */\n    @NotEmpty(message = \"消息 mediaId 不能为空\", groups = {ImageMessageGroup.class, VoiceMessageGroup.class, VideoMessageGroup.class})\n    private String mediaId;\n\n    /**\n     * 缩略图的媒体 id\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO、MUSIC\n     */\n    @NotEmpty(message = \"消息 thumbMediaId 不能为空\", groups = {MusicMessageGroup.class})\n    private String thumbMediaId;\n\n    /**\n     * 标题\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO\n     */\n    @NotEmpty(message = \"消息标题不能为空\", groups = VideoMessageGroup.class)\n    private String title;\n    /**\n     * 描述\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 VIDEO\n     */\n    @NotEmpty(message = \"消息描述不能为空\", groups = VideoMessageGroup.class)\n    private String description;\n\n    /**\n     * 图文消息\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 NEWS\n     */\n    @Valid\n    @NotNull(message = \"图文消息不能为空\", groups = NewsMessageGroup.class)\n    private List<MpMessageDO.Article> articles;\n\n    /**\n     * 音乐链接\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC\n     */\n    @NotEmpty(message = \"音乐链接不能为空\", groups = MusicMessageGroup.class)\n    @URL(message = \"高质量音乐链接格式不正确\", groups = MusicMessageGroup.class)\n    private String musicUrl;\n\n    /**\n     * 高质量音乐链接\n     *\n     * 消息类型为 {@link WxConsts.XmlMsgType} 的 MUSIC\n     */\n    @NotEmpty(message = \"高质量音乐链接不能为空\", groups = MusicMessageGroup.class)\n    @URL(message = \"高质量音乐链接格式不正确\", groups = MusicMessageGroup.class)\n    private String hqMusicUrl;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/service/statistics/MpStatisticsService.java",
    "content": "package co.yixiang.yshop.module.mp.service.statistics;\n\nimport me.chanjar.weixin.mp.bean.datacube.WxDataCubeInterfaceResult;\nimport me.chanjar.weixin.mp.bean.datacube.WxDataCubeMsgResult;\nimport me.chanjar.weixin.mp.bean.datacube.WxDataCubeUserCumulate;\nimport me.chanjar.weixin.mp.bean.datacube.WxDataCubeUserSummary;\n\nimport java.time.LocalDateTime;\nimport java.util.List;\n\n/**\n * 公众号统计 Service 接口\n *\n * @author yshop\n */\npublic interface MpStatisticsService {\n\n    /**\n     * 获取粉丝增减数据\n     *\n     * @param accountId 公众号账号编号\n     * @param date 时间区间\n     * @return 粉丝增减数据\n     */\n    List<WxDataCubeUserSummary> getUserSummary(Long accountId, LocalDateTime[] date);\n\n    /**\n     * 获取粉丝累计数据\n     *\n     * @param accountId 公众号账号编号\n     * @param date 时间区间\n     * @return 粉丝累计数据\n     */\n    List<WxDataCubeUserCumulate> getUserCumulate(Long accountId, LocalDateTime[] date);\n\n    /**\n     * 获取消息发送概况数据\n     *\n     * @param accountId 公众号账号编号\n     * @param date 时间区间\n     * @return 消息发送概况数据\n     */\n    List<WxDataCubeMsgResult> getUpstreamMessage(Long accountId, LocalDateTime[] date);\n\n    /**\n     * 获取接口分析数据\n     *\n     * @param accountId 公众号账号编号\n     * @param date 时间区间\n     * @return 接口分析数据\n     */\n    List<WxDataCubeInterfaceResult> getInterfaceSummary(Long accountId, LocalDateTime[] date);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/service/statistics/MpStatisticsServiceImpl.java",
    "content": "package co.yixiang.yshop.module.mp.service.statistics;\n\nimport cn.hutool.core.date.DateUtil;\nimport co.yixiang.yshop.module.mp.framework.mp.core.MpServiceFactory;\nimport me.chanjar.weixin.common.error.WxErrorException;\nimport me.chanjar.weixin.mp.api.WxMpService;\nimport me.chanjar.weixin.mp.bean.datacube.WxDataCubeInterfaceResult;\nimport me.chanjar.weixin.mp.bean.datacube.WxDataCubeMsgResult;\nimport me.chanjar.weixin.mp.bean.datacube.WxDataCubeUserCumulate;\nimport me.chanjar.weixin.mp.bean.datacube.WxDataCubeUserSummary;\nimport org.springframework.context.annotation.Lazy;\nimport org.springframework.stereotype.Service;\n\nimport jakarta.annotation.Resource;\nimport java.time.LocalDateTime;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.mp.enums.ErrorCodeConstants.*;\n\n/**\n * 公众号统计 Service 实现类\n *\n * @author yshop\n */\n@Service\npublic class MpStatisticsServiceImpl implements MpStatisticsService {\n\n    @Resource\n    @Lazy // 延迟加载，解决循环依赖的问题\n    private MpServiceFactory mpServiceFactory;\n\n    @Override\n    public List<WxDataCubeUserSummary> getUserSummary(Long accountId, LocalDateTime[] date) {\n        WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId);\n        try {\n            return mpService.getDataCubeService().getUserSummary(\n                    DateUtil.date(date[0]), DateUtil.date(date[1]));\n        } catch (WxErrorException e) {\n            throw exception(STATISTICS_GET_USER_SUMMARY_FAIL, e.getError().getErrorMsg());\n        }\n    }\n\n    @Override\n    public List<WxDataCubeUserCumulate> getUserCumulate(Long accountId, LocalDateTime[] date) {\n        WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId);\n        try {\n            return mpService.getDataCubeService().getUserCumulate(\n                    DateUtil.date(date[0]), DateUtil.date(date[1]));\n        } catch (WxErrorException e) {\n            throw exception(STATISTICS_GET_USER_CUMULATE_FAIL, e.getError().getErrorMsg());\n        }\n    }\n\n    @Override\n    public List<WxDataCubeMsgResult> getUpstreamMessage(Long accountId, LocalDateTime[] date) {\n        WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId);\n        try {\n            return mpService.getDataCubeService().getUpstreamMsg(\n                    DateUtil.date(date[0]), DateUtil.date(date[1]));\n        } catch (WxErrorException e) {\n            throw exception(STATISTICS_GET_UPSTREAM_MESSAGE_FAIL, e.getError().getErrorMsg());\n        }\n    }\n\n    @Override\n    public List<WxDataCubeInterfaceResult> getInterfaceSummary(Long accountId, LocalDateTime[] date) {\n        WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId);\n        try {\n            return mpService.getDataCubeService().getInterfaceSummary(\n                    DateUtil.date(date[0]), DateUtil.date(date[1]));\n        } catch (WxErrorException e) {\n            throw exception(STATISTICS_GET_INTERFACE_SUMMARY_FAIL, e.getError().getErrorMsg());\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/service/tag/MpTagService.java",
    "content": "package co.yixiang.yshop.module.mp.service.tag;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.mp.controller.admin.tag.vo.MpTagCreateReqVO;\nimport co.yixiang.yshop.module.mp.controller.admin.tag.vo.MpTagPageReqVO;\nimport co.yixiang.yshop.module.mp.controller.admin.tag.vo.MpTagUpdateReqVO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.tag.MpTagDO;\n\nimport jakarta.validation.Valid;\nimport java.util.List;\n\n/**\n * 公众号标签 Service 接口\n *\n * @author fengdan\n */\npublic interface MpTagService {\n\n    /**\n     * 创建公众号标签\n     *\n     * @param createReqVO 创建标签信息\n     * @return 标签编号\n     */\n    Long createTag(@Valid MpTagCreateReqVO createReqVO);\n\n    /**\n     * 更新公众号标签\n     *\n     * @param updateReqVO 更新标签信息\n     */\n    void updateTag(@Valid MpTagUpdateReqVO updateReqVO);\n\n    /**\n     * 删除公众号标签\n     *\n     * @param id    编号\n     */\n    void deleteTag(Long id);\n\n    /**\n     * 获得公众号标签分页\n     *\n     * @param pageReqVO 分页查询\n     * @return 公众号标签分页\n     */\n    PageResult<MpTagDO> getTagPage(MpTagPageReqVO pageReqVO);\n\n    /**\n     * 获得公众号标签详情\n     * @param id id查询\n     * @return 公众号标签详情\n     */\n    MpTagDO get(Long id);\n\n    List<MpTagDO> getTagList();\n\n    /**\n     * 同步公众号标签\n     *\n     * @param accountId 公众号账号的编号\n     */\n    void syncTag(Long accountId);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/service/tag/MpTagServiceImpl.java",
    "content": "package co.yixiang.yshop.module.mp.service.tag;\n\nimport cn.hutool.core.collection.CollUtil;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.mp.controller.admin.tag.vo.MpTagCreateReqVO;\nimport co.yixiang.yshop.module.mp.controller.admin.tag.vo.MpTagPageReqVO;\nimport co.yixiang.yshop.module.mp.controller.admin.tag.vo.MpTagUpdateReqVO;\nimport co.yixiang.yshop.module.mp.convert.tag.MpTagConvert;\nimport co.yixiang.yshop.module.mp.dal.dataobject.account.MpAccountDO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.tag.MpTagDO;\nimport co.yixiang.yshop.module.mp.dal.mysql.tag.MpTagMapper;\nimport co.yixiang.yshop.module.mp.framework.mp.core.MpServiceFactory;\nimport co.yixiang.yshop.module.mp.service.account.MpAccountService;\nimport lombok.extern.slf4j.Slf4j;\nimport me.chanjar.weixin.common.error.WxErrorException;\nimport me.chanjar.weixin.mp.api.WxMpService;\nimport me.chanjar.weixin.mp.bean.tag.WxUserTag;\nimport org.springframework.context.annotation.Lazy;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\nimport java.util.List;\nimport java.util.Map;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.framework.common.util.collection.CollectionUtils.convertList;\nimport static co.yixiang.yshop.framework.common.util.collection.CollectionUtils.convertMap;\nimport static co.yixiang.yshop.module.mp.enums.ErrorCodeConstants.*;\n\n/**\n * 公众号标签 Service 实现类\n *\n * @author fengdan\n */\n@Slf4j\n@Service\n@Validated\npublic class MpTagServiceImpl implements MpTagService {\n\n    @Resource\n    private MpTagMapper mpTagMapper;\n\n    @Resource\n    private MpAccountService mpAccountService;\n\n    @Resource\n    @Lazy // 延迟加载，为了解决延迟加载\n    private MpServiceFactory mpServiceFactory;\n\n    @Override\n    public Long createTag(MpTagCreateReqVO createReqVO) {\n        // 获得公众号账号\n        MpAccountDO account = mpAccountService.getRequiredAccount(createReqVO.getAccountId());\n\n        // 第一步，新增标签到公众号平台。标签名的唯一，交给公众号平台\n        WxMpService mpService = mpServiceFactory.getRequiredMpService(createReqVO.getAccountId());\n        WxUserTag wxTag;\n        try {\n            wxTag = mpService.getUserTagService().tagCreate(createReqVO.getName());\n        } catch (WxErrorException e) {\n            throw exception(TAG_CREATE_FAIL, e.getError().getErrorMsg());\n        }\n\n        // 第二步，新增标签到数据库\n        MpTagDO tag = MpTagConvert.INSTANCE.convert(wxTag, account);\n        mpTagMapper.insert(tag);\n        return tag.getId();\n    }\n\n    @Override\n    public void updateTag(MpTagUpdateReqVO updateReqVO) {\n        // 校验标签存在\n        MpTagDO tag = validateTagExists(updateReqVO.getId());\n\n        // 第一步，更新标签到公众号平台。标签名的唯一，交给公众号平台\n        WxMpService mpService = mpServiceFactory.getRequiredMpService(tag.getAccountId());\n        try {\n            mpService.getUserTagService().tagUpdate(tag.getTagId(), updateReqVO.getName());\n        } catch (WxErrorException e) {\n            throw exception(TAG_UPDATE_FAIL, e.getError().getErrorMsg());\n        }\n\n        // 第二步，更新标签到数据库\n        mpTagMapper.updateById(new MpTagDO().setId(tag.getId()).setName(updateReqVO.getName()));\n    }\n\n    @Override\n    public void deleteTag(Long id) {\n        // 校验标签存在\n        MpTagDO tag = validateTagExists(id);\n\n        // 第一步，删除标签到公众号平台。\n        WxMpService mpService = mpServiceFactory.getRequiredMpService(tag.getAccountId());\n        try {\n            mpService.getUserTagService().tagDelete(tag.getTagId());\n        } catch (WxErrorException e) {\n            throw exception(TAG_DELETE_FAIL, e.getError().getErrorMsg());\n        }\n\n        // 第二步，删除标签到数据库\n        mpTagMapper.deleteById(tag.getId());\n    }\n\n    private MpTagDO validateTagExists(Long id) {\n        MpTagDO tag = mpTagMapper.selectById(id);\n        if (tag == null) {\n            throw exception(TAG_NOT_EXISTS);\n        }\n        return tag;\n    }\n\n    @Override\n    public PageResult<MpTagDO> getTagPage(MpTagPageReqVO pageReqVO) {\n        return mpTagMapper.selectPage(pageReqVO);\n    }\n\n    @Override\n    public MpTagDO get(Long id) {\n        return mpTagMapper.selectById(id);\n    }\n\n    @Override\n    public List<MpTagDO> getTagList() {\n        return mpTagMapper.selectList();\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public void syncTag(Long accountId) {\n        MpAccountDO account = mpAccountService.getRequiredAccount(accountId);\n\n        // 第一步，从公众号平台获取最新的标签列表\n        WxMpService mpService = mpServiceFactory.getRequiredMpService(accountId);\n        List<WxUserTag> wxTags;\n        try {\n            wxTags = mpService.getUserTagService().tagGet();\n        } catch (WxErrorException e) {\n            throw exception(TAG_GET_FAIL, e.getError().getErrorMsg());\n        }\n\n        // 第二步，合并更新回自己的数据库；由于标签只有 100 个，所以直接 for 循环操作\n        Map<Long, MpTagDO> tagMap = convertMap(mpTagMapper.selectListByAccountId(accountId),\n                MpTagDO::getTagId);\n        wxTags.forEach(wxTag -> {\n            MpTagDO tag = tagMap.remove(wxTag.getId());\n            // 情况一，不存在，新增\n            if (tag == null) {\n                tag = MpTagConvert.INSTANCE.convert(wxTag, account);\n                mpTagMapper.insert(tag);\n                return;\n            }\n            // 情况二，存在，则更新\n            mpTagMapper.updateById(new MpTagDO().setId(tag.getId())\n                    .setName(wxTag.getName()).setCount(wxTag.getCount()));\n        });\n        // 情况三，部分标签已经不存在了，删除\n        if (CollUtil.isNotEmpty(tagMap)) {\n            mpTagMapper.deleteBatchIds(convertList(tagMap.values(), MpTagDO::getId));\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/service/user/MpUserService.java",
    "content": "package co.yixiang.yshop.module.mp.service.user;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.mp.controller.admin.user.vo.MpUserPageReqVO;\nimport co.yixiang.yshop.module.mp.controller.admin.user.vo.MpUserUpdateReqVO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.user.MpUserDO;\nimport me.chanjar.weixin.mp.bean.result.WxMpUser;\n\nimport java.util.Collection;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.mp.enums.ErrorCodeConstants.USER_NOT_EXISTS;\n\n/**\n * 公众号粉丝 Service 接口\n *\n * @author yshop\n */\npublic interface MpUserService {\n\n    /**\n     * 获得公众号粉丝\n     *\n     * @param id 编号\n     * @return 公众号粉丝\n     */\n    MpUserDO getUser(Long id);\n\n    /**\n     * 使用 appId + openId，获得公众号粉丝\n     *\n     * @param appId 公众号 appId\n     * @param openId 公众号 openId\n     * @return 公众号粉丝\n     */\n    MpUserDO getUser(String appId, String openId);\n\n    /**\n     * 获得公众号粉丝\n     *\n     * @param id 编号\n     * @return 公众号粉丝\n     */\n    default MpUserDO getRequiredUser(Long id) {\n        MpUserDO user = getUser(id);\n        if (user == null) {\n            throw exception(USER_NOT_EXISTS);\n        }\n        return user;\n    }\n\n    /**\n     * 获得公众号粉丝列表\n     *\n     * @param ids 编号\n     * @return 公众号粉丝列表\n     */\n    List<MpUserDO> getUserList(Collection<Long> ids);\n\n    /**\n     * 获得公众号粉丝分页\n     *\n     * @param pageReqVO 分页查询\n     * @return 公众号粉丝分页\n     */\n    PageResult<MpUserDO> getUserPage(MpUserPageReqVO pageReqVO);\n\n    /**\n     * 保存公众号粉丝\n     *\n     * 新增或更新，根据是否存在数据库中\n     *\n     * @param appId 公众号 appId\n     * @param wxMpUser 公众号粉丝的信息\n     * @return 公众号粉丝\n     */\n    MpUserDO saveUser(String appId, WxMpUser wxMpUser);\n\n    /**\n     * 同步一个公众号粉丝\n     *\n     * @param accountId 公众号账号的编号\n     */\n    void syncUser(Long accountId);\n\n    /**\n     * 更新公众号粉丝，取消关注\n     *\n     * @param appId 公众号 appId\n     * @param openId 公众号粉丝的 openid\n     */\n    void updateUserUnsubscribe(String appId, String openId);\n\n    /**\n     * 更新公众号粉丝\n     *\n     * @param updateReqVO 更新信息\n     */\n    void updateUser(MpUserUpdateReqVO updateReqVO);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-mp/yshop-module-mp-biz/src/main/java/co/yixiang/yshop/module/mp/service/user/MpUserServiceImpl.java",
    "content": "package co.yixiang.yshop.module.mp.service.user;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.collection.CollectionUtils;\nimport co.yixiang.yshop.module.mp.controller.admin.user.vo.MpUserPageReqVO;\nimport co.yixiang.yshop.module.mp.controller.admin.user.vo.MpUserUpdateReqVO;\nimport co.yixiang.yshop.module.mp.convert.user.MpUserConvert;\nimport co.yixiang.yshop.module.mp.dal.dataobject.account.MpAccountDO;\nimport co.yixiang.yshop.module.mp.dal.dataobject.user.MpUserDO;\nimport co.yixiang.yshop.module.mp.dal.mysql.user.MpUserMapper;\nimport co.yixiang.yshop.module.mp.framework.mp.core.MpServiceFactory;\nimport co.yixiang.yshop.module.mp.service.account.MpAccountService;\nimport lombok.extern.slf4j.Slf4j;\nimport me.chanjar.weixin.common.error.WxErrorException;\nimport me.chanjar.weixin.mp.api.WxMpService;\nimport me.chanjar.weixin.mp.bean.result.WxMpUser;\nimport me.chanjar.weixin.mp.bean.result.WxMpUserList;\nimport org.springframework.context.annotation.Lazy;\nimport org.springframework.scheduling.annotation.Async;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\nimport java.time.LocalDateTime;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Map;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.mp.enums.ErrorCodeConstants.USER_NOT_EXISTS;\nimport static co.yixiang.yshop.module.mp.enums.ErrorCodeConstants.USER_UPDATE_TAG_FAIL;\n\n/**\n * 微信公众号粉丝 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\n@Slf4j\npublic class MpUserServiceImpl implements MpUserService {\n\n    @Resource\n    @Lazy // 延迟加载，解决循环依赖的问题\n    private MpAccountService mpAccountService;\n\n    @Resource\n    @Lazy // 延迟加载，解决循环依赖的问题\n    private MpServiceFactory mpServiceFactory;\n\n    @Resource\n    private MpUserMapper mpUserMapper;\n\n    @Override\n    public MpUserDO getUser(Long id) {\n        return mpUserMapper.selectById(id);\n    }\n\n    @Override\n    public MpUserDO getUser(String appId, String openId) {\n        return mpUserMapper.selectByAppIdAndOpenid(appId, openId);\n    }\n\n    @Override\n    public List<MpUserDO> getUserList(Collection<Long> ids) {\n        return mpUserMapper.selectBatchIds(ids);\n    }\n\n    @Override\n    public PageResult<MpUserDO> getUserPage(MpUserPageReqVO pageReqVO) {\n        return mpUserMapper.selectPage(pageReqVO);\n    }\n\n    @Override\n    public MpUserDO saveUser(String appId, WxMpUser wxMpUser) {\n        // 构建保存的 MpUserDO 对象\n        MpAccountDO account = mpAccountService.getAccountFromCache(appId);\n        MpUserDO user = MpUserConvert.INSTANCE.convert(account, wxMpUser);\n\n        // 根据情况，插入或更新\n        MpUserDO dbUser = mpUserMapper.selectByAppIdAndOpenid(appId, wxMpUser.getOpenId());\n        if (dbUser == null) {\n            mpUserMapper.insert(user);\n        } else {\n            user.setId(dbUser.getId());\n            mpUserMapper.updateById(user);\n        }\n        return user;\n    }\n\n    @Override\n    @Async\n    public void syncUser(Long accountId) {\n        MpAccountDO account = mpAccountService.getRequiredAccount(accountId);\n        // for 循环，避免递归出意外问题，导致死循环\n        String nextOpenid = null;\n        for (int i = 0; i < Short.MAX_VALUE; i++) {\n            log.info(\"[syncUser][第({}) 次加载公众号粉丝列表，nextOpenid({})]\", i, nextOpenid);\n            try {\n                nextOpenid = syncUser0(account, nextOpenid);\n            } catch (WxErrorException e) {\n                log.error(\"[syncUser][第({}) 次同步粉丝异常]\", i, e);\n                break;\n            }\n            // 如果 nextOpenid 为空，表示已经同步完毕\n            if (StrUtil.isEmpty(nextOpenid)) {\n                break;\n            }\n        }\n    }\n\n    private String syncUser0(MpAccountDO account, String nextOpenid) throws WxErrorException {\n        // 第一步，从公众号流式加载粉丝\n        WxMpService mpService = mpServiceFactory.getRequiredMpService(account.getId());\n        WxMpUserList wxUserList = mpService.getUserService().userList(nextOpenid);\n        if (CollUtil.isEmpty(wxUserList.getOpenids())) {\n            return null;\n        }\n\n        // 第二步，分批加载粉丝信息\n        List<List<String>> openidsList = CollUtil.split(wxUserList.getOpenids(), 100);\n        for (List<String> openids : openidsList) {\n            log.info(\"[syncUser][批量加载粉丝信息，openids({})]\", openids);\n            List<WxMpUser> wxUsers = mpService.getUserService().userInfoList(openids);\n            batchSaveUser(account, wxUsers);\n        }\n\n        // 返回下一次的 nextOpenId\n        return wxUserList.getNextOpenid();\n    }\n\n    private void batchSaveUser(MpAccountDO account, List<WxMpUser> wxUsers) {\n        if (CollUtil.isEmpty(wxUsers)) {\n            return;\n        }\n        // 1. 获得数据库已保存的粉丝列表\n        List<MpUserDO> dbUsers = mpUserMapper.selectListByAppIdAndOpenid(account.getAppId(),\n                CollectionUtils.convertList(wxUsers, WxMpUser::getOpenId));\n        Map<String, MpUserDO> openId2Users = CollectionUtils.convertMap(dbUsers, MpUserDO::getOpenid);\n\n        // 2.1 根据情况，插入或更新\n        List<MpUserDO> users = MpUserConvert.INSTANCE.convertList(account, wxUsers);\n        List<MpUserDO> newUsers = new ArrayList<>();\n        for (MpUserDO user : users) {\n            MpUserDO dbUser = openId2Users.get(user.getOpenid());\n            if (dbUser == null) { // 新增：稍后批量插入\n                newUsers.add(user);\n            } else { // 更新：直接执行更新\n                user.setId(dbUser.getId());\n                mpUserMapper.updateById(user);\n            }\n        }\n        // 2.2 批量插入\n        if (CollUtil.isNotEmpty(newUsers)) {\n            mpUserMapper.insertBatch(newUsers);\n        }\n    }\n\n    @Override\n    public void updateUserUnsubscribe(String appId, String openid) {\n        MpUserDO dbUser = mpUserMapper.selectByAppIdAndOpenid(appId, openid);\n        if (dbUser == null) {\n            log.error(\"[updateUserUnsubscribe][微信公众号粉丝 appId({}) openid({}) 不存在]\", appId, openid);\n            return;\n        }\n        mpUserMapper.updateById(new MpUserDO().setId(dbUser.getId()).setSubscribeStatus(CommonStatusEnum.DISABLE.getStatus())\n                .setUnsubscribeTime(LocalDateTime.now()));\n    }\n\n    @Override\n    public void updateUser(MpUserUpdateReqVO updateReqVO) {\n        // 校验存在\n        MpUserDO user = validateUserExists(updateReqVO.getId());\n\n        // 第一步，更新标签到公众号\n        updateUserTag(user.getAppId(), user.getOpenid(), updateReqVO.getTagIds());\n\n        // 第二步，更新基本信息到数据库\n        MpUserDO updateObj = MpUserConvert.INSTANCE.convert(updateReqVO).setId(user.getId());\n        mpUserMapper.updateById(updateObj);\n    }\n\n    private MpUserDO validateUserExists(Long id) {\n        MpUserDO user = mpUserMapper.selectById(id);\n        if (user == null) {\n            throw exception(USER_NOT_EXISTS);\n        }\n        return user;\n    }\n\n    private void updateUserTag(String appId, String openid, List<Long> tagIds) {\n        WxMpService mpService = mpServiceFactory.getRequiredMpService(appId);\n        try {\n            // 第一步，先取消原来的标签\n            List<Long> oldTagIds = mpService.getUserTagService().userTagList(openid);\n            for (Long tagId : oldTagIds) {\n                mpService.getUserTagService().batchUntagging(tagId, new String[]{openid});\n            }\n            // 第二步，再设置新的标签\n            if (CollUtil.isEmpty(tagIds)) {\n                return;\n            }\n            for (Long tagId: tagIds) {\n                mpService.getUserTagService().batchTagging(tagId, new String[]{openid});\n            }\n        } catch (WxErrorException e) {\n            throw exception(USER_UPDATE_TAG_FAIL, e.getError().getErrorMsg());\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-pay/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>co.yixiang.boot</groupId>\n        <artifactId>yshop</artifactId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <modules>\n        <module>yshop-module-pay-api</module>\n        <module>yshop-module-pay-biz</module>\n    </modules>\n    <artifactId>yshop-module-pay</artifactId>\n    <packaging>pom</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>\n        pay 模块\n    </description>\n\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-pay/yshop-module-pay-api/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>yshop-module-pay</artifactId>\n        <groupId>co.yixiang.boot</groupId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>yshop-module-pay-api</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>\n        pay 模块 API，暴露给其它模块调用\n    </description>\n\n    <dependencies>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-common</artifactId>\n        </dependency>\n\n        <!-- 参数校验 -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-validation</artifactId>\n            <optional>true</optional>\n        </dependency>\n\n        <!-- DB 相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-mybatis</artifactId>\n        </dependency>\n\n        <!-- 消息队列相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-mq</artifactId>\n        </dependency>\n\n\n        <!-- 支付相关 -->\n        <dependency>\n            <groupId>com.egzosn</groupId>\n            <artifactId>pay-java-wx</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.egzosn</groupId>\n            <artifactId>pay-java-ali</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.egzosn</groupId>\n            <artifactId>pay-spring-boot-starter</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.egzosn</groupId>\n            <artifactId>pay-java-web-support</artifactId>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-pay/yshop-module-pay-api/src/main/java/co/yixiang/yshop/module/pay/config/MerchantPayServiceConfigurer.java",
    "content": "package co.yixiang.yshop.module.pay.config;\n\nimport co.yixiang.yshop.module.pay.config.handlers.AliPayMessageHandler;\nimport co.yixiang.yshop.module.pay.config.handlers.WxPayMessageHandler;\nimport com.egzosn.pay.spring.boot.core.PayServiceConfigurer;\nimport com.egzosn.pay.spring.boot.core.configurers.MerchantDetailsServiceConfigurer;\nimport com.egzosn.pay.spring.boot.core.configurers.PayMessageConfigurer;\nimport com.egzosn.pay.spring.boot.core.merchant.PaymentPlatform;\nimport com.egzosn.pay.spring.boot.core.provider.merchant.platform.AliPaymentPlatform;\nimport com.egzosn.pay.spring.boot.core.provider.merchant.platform.PaymentPlatforms;\nimport com.egzosn.pay.spring.boot.core.provider.merchant.platform.WxPaymentPlatform;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.jdbc.core.JdbcTemplate;\n\nimport jakarta.annotation.Resource;\n\n\n/**\n * 支付服务配置\n *\n * @author hupeng\n * @date 2023/7/15\n */\n@Configuration\npublic class MerchantPayServiceConfigurer implements PayServiceConfigurer {\n\n    @Resource\n    private JdbcTemplate jdbcTemplate;\n    @Resource\n    private AliPayMessageHandler aliPayMessageHandler;\n    @Resource\n    private WxPayMessageHandler wxPayMessageHandler;\n\n    /**\n     * 商户配置\n     *\n     * @param merchants 商户配置\n     */\n    @Override\n    public void configure(MerchantDetailsServiceConfigurer merchants)  {\n        merchants.jdbc()\n                //是否开启缓存，默认不开启,这里开启缓存\n                .cache(false)\n                .template(jdbcTemplate);\n\n    }\n    /**\n     * 商户配置\n     *\n     * @param configurer 支付消息配置\n     */\n    @Override\n    public void configure(PayMessageConfigurer configurer) {\n        PaymentPlatform aliPaymentPlatform = PaymentPlatforms.getPaymentPlatform(AliPaymentPlatform.PLATFORM_NAME);\n        PaymentPlatform wxPaymentPlatform = PaymentPlatforms.getPaymentPlatform(WxPaymentPlatform.PLATFORM_NAME);\n        configurer.addHandler(aliPaymentPlatform, aliPayMessageHandler);\n        configurer.addHandler(wxPaymentPlatform, wxPayMessageHandler);\n        //configurer.addInterceptor(wxPaymentPlatform, spring.getBean(AliPayMessageInterceptor.class));\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-pay/yshop-module-pay-api/src/main/java/co/yixiang/yshop/module/pay/config/PayAutoConfiguration.java",
    "content": "package co.yixiang.yshop.module.pay.config;\n\nimport org.springframework.boot.autoconfigure.EnableAutoConfiguration;\nimport org.springframework.context.annotation.Configuration;\n\n//@Configuration\n//@EnableAutoConfiguration\npublic class PayAutoConfiguration\n{\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-pay/yshop-module-pay-api/src/main/java/co/yixiang/yshop/module/pay/config/handlers/AliPayMessageHandler.java",
    "content": "package co.yixiang.yshop.module.pay.config.handlers;\n\nimport co.yixiang.yshop.module.pay.mq.producer.PayNoticeProducer;\nimport com.egzosn.pay.ali.api.AliPayService;\nimport com.egzosn.pay.ali.bean.AliPayMessage;\nimport com.egzosn.pay.common.api.PayMessageHandler;\nimport com.egzosn.pay.common.bean.PayOutMessage;\nimport com.egzosn.pay.common.exception.PayErrorException;\nimport org.springframework.stereotype.Component;\n\nimport jakarta.annotation.Resource;\nimport java.util.Map;\n\n/**\n * 支付宝支付回调处理器\n * @author hupeng\n * @date 2023/7/15\n */\n@Component\npublic class AliPayMessageHandler implements PayMessageHandler<AliPayMessage, AliPayService> {\n\n    @Resource\n    private PayNoticeProducer payNoticeProducer;\n    /**\n     * 处理支付回调消息的处理器接口\n     *\n     * @param payMessage 支付消息\n     * @param context    上下文，如果handler或interceptor之间有信息要传递，可以用这个\n     * @param payService 支付服务\n     * @return xml, text格式的消息，如果在异步规则里处理的话，可以返回null\n     * @throws PayErrorException 支付错误异常\n     */\n    @Override\n    public PayOutMessage handle(AliPayMessage payMessage, Map<String, Object> context, AliPayService payService) throws PayErrorException {\n\n        Map<String, Object> message = payMessage.getPayMessage();\n        //交易状态\n        String trade_status = (String) message.get(\"trade_status\");\n\n        //交易完成\n        if (\"TRADE_SUCCESS\".equals(trade_status) || \"TRADE_FINISHED\".equals(trade_status)) {\n\n            String orderId = (String) payMessage.getPayMessage().get(\"out_trade_no\");\n            //消息队列处理\n            payNoticeProducer.sendPayNoticeMessage(orderId,\"alipay\");\n\n            return payService.getPayOutMessage(\"success\", \"成功\");\n\n        }\n\n        return payService.getPayOutMessage(\"fail\", \"失败\");\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-pay/yshop-module-pay-api/src/main/java/co/yixiang/yshop/module/pay/config/handlers/WxPayMessageHandler.java",
    "content": "package co.yixiang.yshop.module.pay.config.handlers;\n\nimport co.yixiang.yshop.module.pay.mq.producer.PayNoticeProducer;\nimport com.egzosn.pay.common.api.PayMessageHandler;\nimport com.egzosn.pay.common.api.PayService;\nimport com.egzosn.pay.common.bean.PayOutMessage;\nimport com.egzosn.pay.common.exception.PayErrorException;\nimport com.egzosn.pay.wx.bean.WxPayMessage;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.stereotype.Component;\n\nimport jakarta.annotation.Resource;\nimport java.util.Map;\n\n/**\n * 微信支付回调处理器\n * @author hupeng\n * @date 2023/7/15\n */\n@Component\n@Slf4j\npublic class WxPayMessageHandler implements PayMessageHandler<WxPayMessage, PayService> {\n\n    @Resource\n    private PayNoticeProducer payNoticeProducer;\n\n    @Override\n    public PayOutMessage handle(WxPayMessage payMessage, Map<String, Object> context, PayService payService) throws PayErrorException {\n\n        log.info(\"======pay notice ========\");\n\n        //交易状态\n        if (\"SUCCESS\".equals(payMessage.getPayMessage().get(\"result_code\"))){\n            String orderId = (String)payMessage.getPayMessage().get(\"out_trade_no\");\n            //消息队列处理\n            payNoticeProducer.sendPayNoticeMessage(orderId,\"weixin\");\n            return  payService.getPayOutMessage(\"SUCCESS\", \"OK\");\n        }\n\n        return  payService.getPayOutMessage(\"FAIL\", \"失败\");\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-pay/yshop-module-pay-api/src/main/java/co/yixiang/yshop/module/pay/config/interceptor/AliPayMessageInterceptor.java",
    "content": "\npackage co.yixiang.yshop.module.pay.config.interceptor;\n\nimport com.egzosn.pay.ali.api.AliPayService;\nimport com.egzosn.pay.ali.bean.AliPayMessage;\nimport com.egzosn.pay.common.api.PayMessageHandler;\nimport com.egzosn.pay.common.api.PayMessageInterceptor;\nimport com.egzosn.pay.common.exception.PayErrorException;\n\nimport java.util.Map;\n\n/**\n * 支付宝回调信息拦截器\n * @author hupeng\n * @date 2023/7/15\n */\n//使用 自己开启\n//@Component\npublic class AliPayMessageInterceptor implements PayMessageInterceptor<AliPayMessage, AliPayService> {\n\n    /**\n     * 拦截支付消息\n     *\n     * @param payMessage     支付回调消息\n     * @param context        上下文，如果handler或interceptor之间有信息要传递，可以用这个\n     * @param payService 支付服务\n     * @return true代表OK，false代表不OK并直接中断对应的支付处理器\n     * @see PayMessageHandler 支付处理器\n     * @throws PayErrorException PayErrorException*\n     */\n    @Override\n    public boolean intercept(AliPayMessage payMessage, Map<String, Object> context, AliPayService payService) throws PayErrorException {\n\n        //这里进行拦截器处理，自行实现\n        String outTradeNo = payMessage.getOutTradeNo();\n        // todo\n\n\n\n        return true;\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-pay/yshop-module-pay-api/src/main/java/co/yixiang/yshop/module/pay/config/interceptor/WxPayMessageInterceptor.java",
    "content": "\npackage co.yixiang.yshop.module.pay.config.interceptor;\n\nimport com.egzosn.pay.ali.api.AliPayService;\nimport com.egzosn.pay.ali.bean.AliPayMessage;\nimport com.egzosn.pay.common.api.PayMessageHandler;\nimport com.egzosn.pay.common.api.PayMessageInterceptor;\nimport com.egzosn.pay.common.exception.PayErrorException;\nimport com.egzosn.pay.wx.api.WxPayService;\nimport com.egzosn.pay.wx.bean.WxPayMessage;\nimport org.springframework.stereotype.Component;\n\nimport java.util.Map;\n\n/**\n * 支付宝回调信息拦截器\n * @author hupeng\n * @date 2023/7/15\n */\n//使用 自己开启\n//@Component\npublic class WxPayMessageInterceptor implements PayMessageInterceptor<WxPayMessage, WxPayService> {\n\n\n//    /**\n//     * 拦截支付消息\n//     *\n//     * @param payMessage     支付回调消息\n//     * @param context        上下文，如果handler或interceptor之间有信息要传递，可以用这个\n//     * @param payService 支付服务\n//     * @return true代表OK，false代表不OK并直接中断对应的支付处理器\n//     * @see PayMessageHandler 支付处理器\n//     * @throws PayErrorException PayErrorException*\n//     */\n//    @Override\n//    public boolean intercept(AliPayMessage payMessage, Map<String, Object> context, AliPayService payService) throws PayErrorException {\n//\n//        //这里进行拦截器处理，自行实现\n//        String outTradeNo = payMessage.getOutTradeNo();\n//        // todo\n//\n//\n//\n//        return true;\n//    }\n\n    @Override\n    public boolean intercept(WxPayMessage wxPayMessage, Map<String, Object> map, WxPayService wxPayService) throws PayErrorException {\n//        wxPayService.\n        return false;\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-pay/yshop-module-pay-api/src/main/java/co/yixiang/yshop/module/pay/enums/ErrorCodeConstants.java",
    "content": "package co.yixiang.yshop.module.pay.enums;\n\nimport co.yixiang.yshop.framework.common.exception.ErrorCode;\n\npublic interface ErrorCodeConstants {\n    ErrorCode MERCHANT_DETAILS_NOT_EXISTS = new ErrorCode(1008009000, \"支付服务商配置不存在\");\n}\n\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-pay/yshop-module-pay-api/src/main/java/co/yixiang/yshop/module/pay/mq/message/PayNoticeMessage.java",
    "content": "package co.yixiang.yshop.module.pay.mq.message;\n\nimport co.yixiang.yshop.framework.mq.redis.core.stream.AbstractRedisStreamMessage;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotNull;\n\n@Data\npublic class PayNoticeMessage extends AbstractRedisStreamMessage {\n\n    /**\n     * 订单编号\n     */\n    @NotNull(message = \"订单编号编号不能为空\")\n    private String orderId;\n\n    //支付类型\n    private String payType;\n\n    @Override\n    public String getStreamKey() {\n        return \"order.pay.notice\";\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-pay/yshop-module-pay-api/src/main/java/co/yixiang/yshop/module/pay/mq/producer/PayNoticeProducer.java",
    "content": "package co.yixiang.yshop.module.pay.mq.producer;\n\nimport co.yixiang.yshop.framework.mq.redis.core.RedisMQTemplate;\nimport co.yixiang.yshop.module.pay.mq.message.PayNoticeMessage;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Component;\n\nimport jakarta.annotation.Resource;\n\n@Slf4j\n@Component\npublic class PayNoticeProducer {\n    @Resource\n    private RedisMQTemplate redisMQTemplate;\n\n    /**\n     * 发送消息\n     * @param orderId 订单编号\n     */\n    public void sendPayNoticeMessage(String orderId,String payType) {\n        PayNoticeMessage payNoticeMessage = new PayNoticeMessage().setOrderId(orderId).setPayType(payType);\n        redisMQTemplate.send(payNoticeMessage);\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-pay/yshop-module-pay-biz/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>yshop-module-pay</artifactId>\n        <groupId>co.yixiang.boot</groupId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>yshop-module-pay-biz</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>\n        pay 模块，我们放支付业务，提供业务的支付能力。\n        例如说：商户、应用、支付、退款等等\n    </description>\n\n    <dependencies>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-pay-api</artifactId>\n            <version>${revision}</version>\n        </dependency>\n\n        <!-- 业务组件 -->\n<!--        <dependency>-->\n<!--            <groupId>co.yixiang.boot</groupId>-->\n<!--            <artifactId>yshop-spring-boot-starter-biz-operatelog</artifactId>-->\n<!--        </dependency>-->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-biz-tenant</artifactId>\n        </dependency>\n\n        <!-- Web 相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-security</artifactId>\n        </dependency>\n\n<!--        <dependency>-->\n<!--            <groupId>co.yixiang.boot</groupId>-->\n<!--            <artifactId>yshop-spring-boot-starter-desensitize</artifactId>-->\n<!--        </dependency>-->\n\n\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-redis</artifactId>\n        </dependency>\n\n\n        <!-- 消息队列相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-mq</artifactId>\n        </dependency>\n\n        <!-- Test 测试相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n\n        <!-- 工具类相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-excel</artifactId>\n        </dependency>\n\n\n\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-pay/yshop-module-pay-biz/src/main/java/co/yixiang/yshop/module/pay/controller/admin/merchantdetails/MerchantDetailsController.java",
    "content": "package co.yixiang.yshop.module.pay.controller.admin.merchantdetails;\n\nimport jakarta.servlet.http.HttpServletResponse;\nimport org.springframework.web.bind.annotation.*;\nimport jakarta.annotation.Resource;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.Operation;\n\nimport jakarta.validation.*;\nimport java.util.*;\nimport java.io.IOException;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\nimport co.yixiang.yshop.framework.excel.core.util.ExcelUtils;\n\nimport co.yixiang.yshop.module.pay.controller.admin.merchantdetails.vo.*;\nimport co.yixiang.yshop.module.pay.dal.dataobject.merchantdetails.MerchantDetailsDO;\nimport co.yixiang.yshop.module.pay.convert.merchantdetails.MerchantDetailsConvert;\nimport co.yixiang.yshop.module.pay.service.merchantdetails.MerchantDetailsService;\n\n@Tag(name = \"管理后台 - 支付服务商配置\")\n@RestController\n@RequestMapping(\"/pay/merchant-details\")\n@Validated\npublic class MerchantDetailsController {\n\n    @Resource\n    private MerchantDetailsService merchantDetailsService;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建支付服务商配置\")\n    @PreAuthorize(\"@ss.hasPermission('pay:merchant-details:create')\")\n    public CommonResult<String> createMerchantDetails(@Valid @RequestBody MerchantDetailsCreateReqVO createReqVO) {\n        return success(merchantDetailsService.createMerchantDetails(createReqVO));\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"更新支付服务商配置\")\n    @PreAuthorize(\"@ss.hasPermission('pay:merchant-details:update')\")\n    public CommonResult<Boolean> updateMerchantDetails(@Valid @RequestBody MerchantDetailsUpdateReqVO updateReqVO) {\n        merchantDetailsService.updateMerchantDetails(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除支付服务商配置\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('pay:merchant-details:delete')\")\n    public CommonResult<Boolean> deleteMerchantDetails(@RequestParam(\"id\") String id) {\n        merchantDetailsService.deleteMerchantDetails(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得支付服务商配置\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('pay:merchant-details:query')\")\n    public CommonResult<MerchantDetailsRespVO> getMerchantDetails(@RequestParam(\"id\") String id) {\n        MerchantDetailsDO merchantDetails = merchantDetailsService.getMerchantDetails(id);\n        return success(MerchantDetailsConvert.INSTANCE.convert(merchantDetails));\n    }\n\n    @GetMapping(\"/list\")\n    @Operation(summary = \"获得支付服务商配置列表\")\n    @Parameter(name = \"ids\", description = \"编号列表\", required = true, example = \"1024,2048\")\n    @PreAuthorize(\"@ss.hasPermission('pay:merchant-details:query')\")\n    public CommonResult<List<MerchantDetailsRespVO>> getMerchantDetailsList(@RequestParam(\"ids\") Collection<String> ids) {\n        List<MerchantDetailsDO> list = merchantDetailsService.getMerchantDetailsList(ids);\n        return success(MerchantDetailsConvert.INSTANCE.convertList(list));\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得支付服务商配置分页\")\n    @PreAuthorize(\"@ss.hasPermission('pay:merchant-details:query')\")\n    public CommonResult<PageResult<MerchantDetailsRespVO>> getMerchantDetailsPage(@Valid MerchantDetailsPageReqVO pageVO) {\n        PageResult<MerchantDetailsDO> pageResult = merchantDetailsService.getMerchantDetailsPage(pageVO);\n        return success(MerchantDetailsConvert.INSTANCE.convertPage(pageResult));\n    }\n\n    @GetMapping(\"/export-excel\")\n    @Operation(summary = \"导出支付服务商配置 Excel\")\n    @PreAuthorize(\"@ss.hasPermission('pay:merchant-details:export')\")\n    public void exportMerchantDetailsExcel(@Valid MerchantDetailsExportReqVO exportReqVO,\n              HttpServletResponse response) throws IOException {\n        List<MerchantDetailsDO> list = merchantDetailsService.getMerchantDetailsList(exportReqVO);\n        // 导出 Excel\n        List<MerchantDetailsExcelVO> datas = MerchantDetailsConvert.INSTANCE.convertList02(list);\n        ExcelUtils.write(response, \"支付服务商配置.xls\", \"数据\", MerchantDetailsExcelVO.class, datas);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-pay/yshop-module-pay-biz/src/main/java/co/yixiang/yshop/module/pay/controller/admin/merchantdetails/vo/MerchantDetailsBaseVO.java",
    "content": "package co.yixiang.yshop.module.pay.controller.admin.merchantdetails.vo;\n\nimport co.yixiang.yshop.framework.desensitize.core.slider.annotation.SliderDesensitize;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\n\nimport jakarta.validation.constraints.*;\n\n/**\n* 支付服务商配置 Base VO，提供给添加、修改、详细的子 VO 使用\n* 如果子 VO 存在差异的字段，请不要添加到这里，影响 Swagger 文档生成\n*/\n@Data\npublic class MerchantDetailsBaseVO {\n\n    private String detailsId;\n\n    @Schema(description = \"支付类型(支付渠道) 详情查看com.egzosn.pay.spring.boot.core.merchant.PaymentPlatform对应子类，aliPay 支付宝， wxPay微信..等等\", required = true, example = \"2\")\n    @NotNull(message = \"支付类型(支付渠道)等不能为空\")\n    private String payType;\n\n    //@SliderDesensitize(prefixKeep=3 ,suffixKeep=4)\n    @Schema(description = \"应用id\", example = \"1718\")\n    private String appid;\n\n    @Schema(description = \"商户id，商户号，合作伙伴id等等\", example = \"21574\")\n    //@SliderDesensitize(prefixKeep=2 ,suffixKeep=2)\n    private String mchId;\n\n    @Schema(description = \"当前面私钥公钥为证书类型的时候，这里必填，可选值:PATH,STR,INPUT_STREAM,CLASS_PATH,URL\", example = \"1\")\n    private String certStoreType;\n\n    @Schema(description = \"私钥或私钥证书\")\n    //@SliderDesensitize(prefixKeep=4 ,suffixKeep=4)\n    private String keyPrivate;\n\n    @Schema(description = \"公钥或公钥证书\")\n    private String keyPublic;\n\n    @Schema(description = \"key证书,附加证书使用，如SSL证书，或者银联根级证书方面\")\n    private String keyCert;\n\n    @Schema(description = \"私钥证书或key证书的密码\")\n    private String keyCertPwd;\n\n    @Schema(description = \"异步回调\", example = \"https://www.yixiang.co\")\n    private String notifyUrl;\n\n    @Schema(description = \"同步回调地址，大部分用于付款成功后页面转跳\", example = \"https://www.yixiang.co\")\n    private String returnUrl;\n\n    @Schema(description = \"签名方式,目前已实现多种签名方式详情查看com.egzosn.pay.common.util.sign.encrypt。MD5,RSA等等\", required = true, example = \"1\")\n    @NotNull(message = \"签名方式不能为空\")\n    private String signType;\n\n    @Schema(description = \"收款账号，暂时只有支付宝部分使用，可根据开发者自行使用\")\n    private String seller;\n\n    @Schema(description = \"子appid\", example = \"13761\")\n    private String subAppId;\n\n    @Schema(description = \"子商户id\", example = \"10127\")\n    private String subMchId;\n\n    @Schema(description = \"编码类型，大部分为utf-8\", required = true)\n    //@NotNull(message = \"编码类型，大部分为utf-8不能为空\")\n    private String inputCharset;\n\n    @Schema(description = \"是否为测试环境: 0 否，1 测试环境\", required = true)\n    @NotNull(message = \"是否为测试环境: 0 否，1 测试环境不能为空\")\n    private Integer isTest;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-pay/yshop-module-pay-biz/src/main/java/co/yixiang/yshop/module/pay/controller/admin/merchantdetails/vo/MerchantDetailsCreateReqVO.java",
    "content": "package co.yixiang.yshop.module.pay.controller.admin.merchantdetails.vo;\n\nimport lombok.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\n\n@Schema(description = \"管理后台 - 支付服务商配置创建 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class MerchantDetailsCreateReqVO extends MerchantDetailsBaseVO {\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-pay/yshop-module-pay-biz/src/main/java/co/yixiang/yshop/module/pay/controller/admin/merchantdetails/vo/MerchantDetailsExcelVO.java",
    "content": "package co.yixiang.yshop.module.pay.controller.admin.merchantdetails.vo;\n\nimport lombok.*;\n\nimport com.alibaba.excel.annotation.ExcelProperty;\n\n/**\n * 支付服务商配置 Excel VO\n *\n * @author yshop\n */\n@Data\npublic class MerchantDetailsExcelVO {\n\n    @ExcelProperty(\"列表id\")\n    private String detailsId;\n\n    @ExcelProperty(\"支付类型(支付渠道) 详情查看com.egzosn.pay.spring.boot.core.merchant.PaymentPlatform对应子类，aliPay 支付宝， wxPay微信..等等\")\n    private String payType;\n\n    @ExcelProperty(\"应用id\")\n    private String appid;\n\n    @ExcelProperty(\"商户id，商户号，合作伙伴id等等\")\n    private String mchId;\n\n    @ExcelProperty(\"当前面私钥公钥为证书类型的时候，这里必填，可选值:PATH,STR,INPUT_STREAM,CLASS_PATH,URL\")\n    private String certStoreType;\n\n    @ExcelProperty(\"私钥或私钥证书\")\n    private String keyPrivate;\n\n    @ExcelProperty(\"公钥或公钥证书\")\n    private String keyPublic;\n\n    @ExcelProperty(\"key证书,附加证书使用，如SSL证书，或者银联根级证书方面\")\n    private String keyCert;\n\n    @ExcelProperty(\"私钥证书或key证书的密码\")\n    private String keyCertPwd;\n\n    @ExcelProperty(\"异步回调\")\n    private String notifyUrl;\n\n    @ExcelProperty(\"同步回调地址，大部分用于付款成功后页面转跳\")\n    private String returnUrl;\n\n    @ExcelProperty(\"签名方式,目前已实现多种签名方式详情查看com.egzosn.pay.common.util.sign.encrypt。MD5,RSA等等\")\n    private String signType;\n\n    @ExcelProperty(\"收款账号，暂时只有支付宝部分使用，可根据开发者自行使用\")\n    private String seller;\n\n    @ExcelProperty(\"子appid\")\n    private String subAppId;\n\n    @ExcelProperty(\"子商户id\")\n    private String subMchId;\n\n    @ExcelProperty(\"编码类型，大部分为utf-8\")\n    private String inputCharset;\n\n    @ExcelProperty(\"是否为测试环境: 0 否，1 测试环境\")\n    private Integer isTest;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-pay/yshop-module-pay-biz/src/main/java/co/yixiang/yshop/module/pay/controller/admin/merchantdetails/vo/MerchantDetailsExportReqVO.java",
    "content": "package co.yixiang.yshop.module.pay.controller.admin.merchantdetails.vo;\n\nimport lombok.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\n\n@Schema(description = \"管理后台 - 支付服务商配置 Excel 导出 Request VO，参数和 MerchantDetailsPageReqVO 是一致的\")\n@Data\npublic class MerchantDetailsExportReqVO {\n\n    @Schema(description = \"支付类型(支付渠道) 详情查看com.egzosn.pay.spring.boot.core.merchant.PaymentPlatform对应子类，aliPay 支付宝， wxPay微信..等等\", example = \"2\")\n    private String payType;\n\n    @Schema(description = \"应用id\", example = \"1718\")\n    private String appid;\n\n    @Schema(description = \"商户id，商户号，合作伙伴id等等\", example = \"21574\")\n    private String mchId;\n\n    @Schema(description = \"当前面私钥公钥为证书类型的时候，这里必填，可选值:PATH,STR,INPUT_STREAM,CLASS_PATH,URL\", example = \"1\")\n    private String certStoreType;\n\n    @Schema(description = \"私钥或私钥证书\")\n    private String keyPrivate;\n\n    @Schema(description = \"公钥或公钥证书\")\n    private String keyPublic;\n\n    @Schema(description = \"key证书,附加证书使用，如SSL证书，或者银联根级证书方面\")\n    private String keyCert;\n\n    @Schema(description = \"私钥证书或key证书的密码\")\n    private String keyCertPwd;\n\n    @Schema(description = \"异步回调\", example = \"https://www.yixiang.co\")\n    private String notifyUrl;\n\n    @Schema(description = \"同步回调地址，大部分用于付款成功后页面转跳\", example = \"https://www.yixiang.co\")\n    private String returnUrl;\n\n    @Schema(description = \"签名方式,目前已实现多种签名方式详情查看com.egzosn.pay.common.util.sign.encrypt。MD5,RSA等等\", example = \"1\")\n    private String signType;\n\n    @Schema(description = \"收款账号，暂时只有支付宝部分使用，可根据开发者自行使用\")\n    private String seller;\n\n    @Schema(description = \"子appid\", example = \"13761\")\n    private String subAppId;\n\n    @Schema(description = \"子商户id\", example = \"10127\")\n    private String subMchId;\n\n    @Schema(description = \"编码类型，大部分为utf-8\")\n    private String inputCharset;\n\n    @Schema(description = \"是否为测试环境: 0 否，1 测试环境\")\n    private Boolean isTest;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-pay/yshop-module-pay-biz/src/main/java/co/yixiang/yshop/module/pay/controller/admin/merchantdetails/vo/MerchantDetailsPageReqVO.java",
    "content": "package co.yixiang.yshop.module.pay.controller.admin.merchantdetails.vo;\n\nimport lombok.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\n\n@Schema(description = \"管理后台 - 支付服务商配置分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class MerchantDetailsPageReqVO extends PageParam {\n\n    @Schema(description = \"支付类型(支付渠道) 详情查看com.egzosn.pay.spring.boot.core.merchant.PaymentPlatform对应子类，aliPay 支付宝， wxPay微信..等等\", example = \"2\")\n    private String payType;\n\n    @Schema(description = \"应用id\", example = \"1718\")\n    private String appid;\n\n    @Schema(description = \"商户id，商户号，合作伙伴id等等\", example = \"21574\")\n    private String mchId;\n\n    @Schema(description = \"当前面私钥公钥为证书类型的时候，这里必填，可选值:PATH,STR,INPUT_STREAM,CLASS_PATH,URL\", example = \"1\")\n    private String certStoreType;\n\n    @Schema(description = \"私钥或私钥证书\")\n    private String keyPrivate;\n\n    @Schema(description = \"公钥或公钥证书\")\n    private String keyPublic;\n\n    @Schema(description = \"key证书,附加证书使用，如SSL证书，或者银联根级证书方面\")\n    private String keyCert;\n\n    @Schema(description = \"私钥证书或key证书的密码\")\n    private String keyCertPwd;\n\n    @Schema(description = \"异步回调\", example = \"https://www.yixiang.co\")\n    private String notifyUrl;\n\n    @Schema(description = \"同步回调地址，大部分用于付款成功后页面转跳\", example = \"https://www.yixiang.co\")\n    private String returnUrl;\n\n    @Schema(description = \"签名方式,目前已实现多种签名方式详情查看com.egzosn.pay.common.util.sign.encrypt。MD5,RSA等等\", example = \"1\")\n    private String signType;\n\n    @Schema(description = \"收款账号，暂时只有支付宝部分使用，可根据开发者自行使用\")\n    private String seller;\n\n    @Schema(description = \"子appid\", example = \"13761\")\n    private String subAppId;\n\n    @Schema(description = \"子商户id\", example = \"10127\")\n    private String subMchId;\n\n    @Schema(description = \"编码类型，大部分为utf-8\")\n    private String inputCharset;\n\n    @Schema(description = \"是否为测试环境: 0 否，1 测试环境\")\n    private Boolean isTest;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-pay/yshop-module-pay-biz/src/main/java/co/yixiang/yshop/module/pay/controller/admin/merchantdetails/vo/MerchantDetailsRespVO.java",
    "content": "package co.yixiang.yshop.module.pay.controller.admin.merchantdetails.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\n\n@Schema(description = \"管理后台 - 支付服务商配置 Response VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class MerchantDetailsRespVO extends MerchantDetailsBaseVO {\n\n    @Schema(description = \"列表id\", required = true, example = \"17552\")\n    private String detailsId;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-pay/yshop-module-pay-biz/src/main/java/co/yixiang/yshop/module/pay/controller/admin/merchantdetails/vo/MerchantDetailsUpdateReqVO.java",
    "content": "package co.yixiang.yshop.module.pay.controller.admin.merchantdetails.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\n\nimport jakarta.validation.constraints.*;\n\n@Schema(description = \"管理后台 - 支付服务商配置更新 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class MerchantDetailsUpdateReqVO extends MerchantDetailsBaseVO {\n\n    @Schema(description = \"列表id\", required = true, example = \"17552\")\n    @NotNull(message = \"列表id不能为空\")\n    private String detailsId;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-pay/yshop-module-pay-biz/src/main/java/co/yixiang/yshop/module/pay/convert/merchantdetails/MerchantDetailsConvert.java",
    "content": "package co.yixiang.yshop.module.pay.convert.merchantdetails;\n\nimport java.util.*;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\nimport org.mapstruct.Mapper;\nimport org.mapstruct.factory.Mappers;\nimport co.yixiang.yshop.module.pay.controller.admin.merchantdetails.vo.*;\nimport co.yixiang.yshop.module.pay.dal.dataobject.merchantdetails.MerchantDetailsDO;\n\n/**\n * 支付服务商配置 Convert\n *\n * @author yshop\n */\n@Mapper\npublic interface MerchantDetailsConvert {\n\n    MerchantDetailsConvert INSTANCE = Mappers.getMapper(MerchantDetailsConvert.class);\n\n    MerchantDetailsDO convert(MerchantDetailsCreateReqVO bean);\n\n    MerchantDetailsDO convert(MerchantDetailsUpdateReqVO bean);\n\n    MerchantDetailsRespVO convert(MerchantDetailsDO bean);\n\n    List<MerchantDetailsRespVO> convertList(List<MerchantDetailsDO> list);\n\n    PageResult<MerchantDetailsRespVO> convertPage(PageResult<MerchantDetailsDO> page);\n\n    List<MerchantDetailsExcelVO> convertList02(List<MerchantDetailsDO> list);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-pay/yshop-module-pay-biz/src/main/java/co/yixiang/yshop/module/pay/dal/dataobject/merchantdetails/MerchantDetailsDO.java",
    "content": "package co.yixiang.yshop.module.pay.dal.dataobject.merchantdetails;\n\nimport lombok.*;\nimport com.baomidou.mybatisplus.annotation.*;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\n\n/**\n * 支付服务商配置 DO\n *\n * @author yshop\n */\n@TableName(\"merchant_details\")\n@KeySequence(\"merchant_details_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class MerchantDetailsDO extends BaseDO {\n\n    /**\n     * 列表id\n     */\n    @TableId(type = IdType.INPUT)\n    private String detailsId;\n    /**\n     * 支付类型(支付渠道) 详情查看com.egzosn.pay.spring.boot.core.merchant.PaymentPlatform对应子类，aliPay 支付宝， wxPay微信..等等\n     */\n    private String payType;\n    /**\n     * 应用id\n     */\n    private String appid;\n    /**\n     * 商户id，商户号，合作伙伴id等等\n     */\n    private String mchId;\n    /**\n     * 当前面私钥公钥为证书类型的时候，这里必填，可选值:PATH,STR,INPUT_STREAM,CLASS_PATH,URL\n     */\n    private String certStoreType;\n    /**\n     * 私钥或私钥证书\n     */\n    private String keyPrivate;\n    /**\n     * 公钥或公钥证书\n     */\n    private String keyPublic;\n    /**\n     * key证书,附加证书使用，如SSL证书，或者银联根级证书方面\n     */\n    private String keyCert;\n    /**\n     * 私钥证书或key证书的密码\n     */\n    private String keyCertPwd;\n    /**\n     * 异步回调\n     */\n    private String notifyUrl;\n    /**\n     * 同步回调地址，大部分用于付款成功后页面转跳\n     */\n    private String returnUrl;\n    /**\n     * 签名方式,目前已实现多种签名方式详情查看com.egzosn.pay.common.util.sign.encrypt。MD5,RSA等等\n     */\n    private String signType;\n    /**\n     * 收款账号，暂时只有支付宝部分使用，可根据开发者自行使用\n     */\n    private String seller;\n    /**\n     * 子appid\n     */\n    private String subAppId;\n    /**\n     * 子商户id\n     */\n    private String subMchId;\n    /**\n     * 编码类型，大部分为utf-8\n     */\n    private String inputCharset;\n    /**\n     * 是否为测试环境: 0 否，1 测试环境\n     */\n    private Integer isTest;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-pay/yshop-module-pay-biz/src/main/java/co/yixiang/yshop/module/pay/dal/mysql/merchantdetails/MerchantDetailsMapper.java",
    "content": "package co.yixiang.yshop.module.pay.dal.mysql.merchantdetails;\n\nimport java.util.*;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.module.pay.dal.dataobject.merchantdetails.MerchantDetailsDO;\nimport org.apache.ibatis.annotations.Mapper;\nimport co.yixiang.yshop.module.pay.controller.admin.merchantdetails.vo.*;\n\n/**\n * 支付服务商配置 Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface MerchantDetailsMapper extends BaseMapperX<MerchantDetailsDO> {\n\n    default PageResult<MerchantDetailsDO> selectPage(MerchantDetailsPageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<MerchantDetailsDO>()\n                .eqIfPresent(MerchantDetailsDO::getPayType, reqVO.getPayType())\n                .eqIfPresent(MerchantDetailsDO::getAppid, reqVO.getAppid())\n                .eqIfPresent(MerchantDetailsDO::getMchId, reqVO.getMchId())\n                .eqIfPresent(MerchantDetailsDO::getCertStoreType, reqVO.getCertStoreType())\n                .eqIfPresent(MerchantDetailsDO::getKeyPrivate, reqVO.getKeyPrivate())\n                .eqIfPresent(MerchantDetailsDO::getKeyPublic, reqVO.getKeyPublic())\n                .eqIfPresent(MerchantDetailsDO::getKeyCert, reqVO.getKeyCert())\n                .eqIfPresent(MerchantDetailsDO::getKeyCertPwd, reqVO.getKeyCertPwd())\n                .eqIfPresent(MerchantDetailsDO::getNotifyUrl, reqVO.getNotifyUrl())\n                .eqIfPresent(MerchantDetailsDO::getReturnUrl, reqVO.getReturnUrl())\n                .eqIfPresent(MerchantDetailsDO::getSignType, reqVO.getSignType())\n                .eqIfPresent(MerchantDetailsDO::getSeller, reqVO.getSeller())\n                .eqIfPresent(MerchantDetailsDO::getSubAppId, reqVO.getSubAppId())\n                .eqIfPresent(MerchantDetailsDO::getSubMchId, reqVO.getSubMchId())\n                .eqIfPresent(MerchantDetailsDO::getInputCharset, reqVO.getInputCharset())\n                .eqIfPresent(MerchantDetailsDO::getIsTest, reqVO.getIsTest())\n                .orderByDesc(MerchantDetailsDO::getDetailsId));\n    }\n\n    default List<MerchantDetailsDO> selectList(MerchantDetailsExportReqVO reqVO) {\n        return selectList(new LambdaQueryWrapperX<MerchantDetailsDO>()\n                .eqIfPresent(MerchantDetailsDO::getPayType, reqVO.getPayType())\n                .eqIfPresent(MerchantDetailsDO::getAppid, reqVO.getAppid())\n                .eqIfPresent(MerchantDetailsDO::getMchId, reqVO.getMchId())\n                .eqIfPresent(MerchantDetailsDO::getCertStoreType, reqVO.getCertStoreType())\n                .eqIfPresent(MerchantDetailsDO::getKeyPrivate, reqVO.getKeyPrivate())\n                .eqIfPresent(MerchantDetailsDO::getKeyPublic, reqVO.getKeyPublic())\n                .eqIfPresent(MerchantDetailsDO::getKeyCert, reqVO.getKeyCert())\n                .eqIfPresent(MerchantDetailsDO::getKeyCertPwd, reqVO.getKeyCertPwd())\n                .eqIfPresent(MerchantDetailsDO::getNotifyUrl, reqVO.getNotifyUrl())\n                .eqIfPresent(MerchantDetailsDO::getReturnUrl, reqVO.getReturnUrl())\n                .eqIfPresent(MerchantDetailsDO::getSignType, reqVO.getSignType())\n                .eqIfPresent(MerchantDetailsDO::getSeller, reqVO.getSeller())\n                .eqIfPresent(MerchantDetailsDO::getSubAppId, reqVO.getSubAppId())\n                .eqIfPresent(MerchantDetailsDO::getSubMchId, reqVO.getSubMchId())\n                .eqIfPresent(MerchantDetailsDO::getInputCharset, reqVO.getInputCharset())\n                .eqIfPresent(MerchantDetailsDO::getIsTest, reqVO.getIsTest())\n                .orderByDesc(MerchantDetailsDO::getDetailsId));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-pay/yshop-module-pay-biz/src/main/java/co/yixiang/yshop/module/pay/http/HttpRequestNoticeNewParams.java",
    "content": "package co.yixiang.yshop.module.pay.http;\n\nimport com.egzosn.pay.common.bean.NoticeRequest;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.Enumeration;\nimport java.util.Map;\nimport jakarta.servlet.http.HttpServletRequest;\n\npublic class HttpRequestNoticeNewParams implements NoticeRequest {\n    private final HttpServletRequest httpServletRequest;\n\n    public HttpRequestNoticeNewParams(HttpServletRequest httpServletRequest) {\n        this.httpServletRequest = httpServletRequest;\n    }\n\n    /**\n     * 根据请求头名称获取请求头信息\n     *\n     * @param name 名称\n     * @return 请求头值\n     */\n    @Override\n    public String getHeader(String name) {\n        return httpServletRequest.getHeader(name);\n    }\n\n    /**\n     * 根据请求头名称获取请求头信息\n     *\n     * @param name 名称\n     * @return 请求头值\n     */\n    @Override\n    public Enumeration<String> getHeaders(String name) {\n        return httpServletRequest.getHeaders(name);\n    }\n\n    /**\n     * 获取所有的请求头名称\n     *\n     * @return 请求头名称\n     */\n    @Override\n    public Enumeration<String> getHeaderNames() {\n        return httpServletRequest.getHeaderNames();\n    }\n\n    /**\n     * 输入流\n     *\n     * @return 输入流\n     * @throws IOException IOException\n     */\n    @Override\n    public InputStream getInputStream() throws IOException {\n        return httpServletRequest.getInputStream();\n    }\n\n    /**\n     * 获取所有的请求参数\n     *\n     * @return 请求参数\n     */\n    @Override\n    public Map<String, String[]> getParameterMap() {\n        return httpServletRequest.getParameterMap();\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-pay/yshop-module-pay-biz/src/main/java/co/yixiang/yshop/module/pay/service/merchantdetails/MerchantDetailsService.java",
    "content": "package co.yixiang.yshop.module.pay.service.merchantdetails;\n\nimport java.util.*;\nimport jakarta.validation.*;\nimport co.yixiang.yshop.module.pay.controller.admin.merchantdetails.vo.*;\nimport co.yixiang.yshop.module.pay.dal.dataobject.merchantdetails.MerchantDetailsDO;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\n/**\n * 支付服务商配置 Service 接口\n *\n * @author yshop\n */\npublic interface MerchantDetailsService {\n\n    /**\n     * 创建支付服务商配置\n     *\n     * @param createReqVO 创建信息\n     * @return 编号\n     */\n    String createMerchantDetails(@Valid MerchantDetailsCreateReqVO createReqVO);\n\n    /**\n     * 更新支付服务商配置\n     *\n     * @param updateReqVO 更新信息\n     */\n    void updateMerchantDetails(@Valid MerchantDetailsUpdateReqVO updateReqVO);\n\n    /**\n     * 删除支付服务商配置\n     *\n     * @param id 编号\n     */\n    void deleteMerchantDetails(String id);\n\n    /**\n     * 获得支付服务商配置\n     *\n     * @param id 编号\n     * @return 支付服务商配置\n     */\n    MerchantDetailsDO getMerchantDetails(String id);\n\n    /**\n     * 获得支付服务商配置列表\n     *\n     * @param ids 编号\n     * @return 支付服务商配置列表\n     */\n    List<MerchantDetailsDO> getMerchantDetailsList(Collection<String> ids);\n\n    /**\n     * 获得支付服务商配置分页\n     *\n     * @param pageReqVO 分页查询\n     * @return 支付服务商配置分页\n     */\n    PageResult<MerchantDetailsDO> getMerchantDetailsPage(MerchantDetailsPageReqVO pageReqVO);\n\n    /**\n     * 获得支付服务商配置列表, 用于 Excel 导出\n     *\n     * @param exportReqVO 查询条件\n     * @return 支付服务商配置列表\n     */\n    List<MerchantDetailsDO> getMerchantDetailsList(MerchantDetailsExportReqVO exportReqVO);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-pay/yshop-module-pay-biz/src/main/java/co/yixiang/yshop/module/pay/service/merchantdetails/MerchantDetailsServiceImpl.java",
    "content": "package co.yixiang.yshop.module.pay.service.merchantdetails;\n\nimport org.springframework.stereotype.Service;\nimport jakarta.annotation.Resource;\nimport org.springframework.validation.annotation.Validated;\n\nimport java.util.*;\nimport co.yixiang.yshop.module.pay.controller.admin.merchantdetails.vo.*;\nimport co.yixiang.yshop.module.pay.dal.dataobject.merchantdetails.MerchantDetailsDO;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\nimport co.yixiang.yshop.module.pay.convert.merchantdetails.MerchantDetailsConvert;\nimport co.yixiang.yshop.module.pay.dal.mysql.merchantdetails.MerchantDetailsMapper;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.pay.enums.ErrorCodeConstants.*;\n\n/**\n * 支付服务商配置 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class MerchantDetailsServiceImpl implements MerchantDetailsService {\n\n    @Resource\n    private MerchantDetailsMapper merchantDetailsMapper;\n\n    @Override\n    public String createMerchantDetails(MerchantDetailsCreateReqVO createReqVO) {\n        // 插入\n        MerchantDetailsDO merchantDetails = MerchantDetailsConvert.INSTANCE.convert(createReqVO);\n        merchantDetailsMapper.insert(merchantDetails);\n        // 返回\n        return merchantDetails.getDetailsId();\n    }\n\n    @Override\n    public void updateMerchantDetails(MerchantDetailsUpdateReqVO updateReqVO) {\n        // 校验存在\n        validateMerchantDetailsExists(updateReqVO.getDetailsId());\n        // 更新\n        MerchantDetailsDO updateObj = MerchantDetailsConvert.INSTANCE.convert(updateReqVO);\n        merchantDetailsMapper.updateById(updateObj);\n    }\n\n    @Override\n    public void deleteMerchantDetails(String id) {\n        // 校验存在\n        validateMerchantDetailsExists(id);\n        // 删除\n        merchantDetailsMapper.deleteById(id);\n    }\n\n    private void validateMerchantDetailsExists(String id) {\n        if (merchantDetailsMapper.selectById(id) == null) {\n            throw exception(MERCHANT_DETAILS_NOT_EXISTS);\n        }\n    }\n\n    @Override\n    public MerchantDetailsDO getMerchantDetails(String id) {\n        return merchantDetailsMapper.selectById(id);\n    }\n\n    @Override\n    public List<MerchantDetailsDO> getMerchantDetailsList(Collection<String> ids) {\n        return merchantDetailsMapper.selectBatchIds(ids);\n    }\n\n    @Override\n    public PageResult<MerchantDetailsDO> getMerchantDetailsPage(MerchantDetailsPageReqVO pageReqVO) {\n        return merchantDetailsMapper.selectPage(pageReqVO);\n    }\n\n    @Override\n    public List<MerchantDetailsDO> getMerchantDetailsList(MerchantDetailsExportReqVO exportReqVO) {\n        return merchantDetailsMapper.selectList(exportReqVO);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-pay/yshop-module-pay-biz/src/main/resources/mapper/merchantdetails/MerchantDetailsMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"co.yixiang.yshop.module.pay.dal.mysql.merchantdetails.MerchantDetailsMapper\">\n\n    <!--\n        一般情况下，尽可能使用 Mapper 进行 CRUD 增删改查即可。\n        无法满足的场景，例如说多表关联查询，才使用 XML 编写 SQL。\n        代码生成器暂时只生成 Mapper XML 文件本身，更多推荐 MybatisX 快速开发插件来生成查询。\n        文档可见：https://www.yixiang.co/MyBatis/x-plugins/\n     -->\n\n</mapper>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>co.yixiang.boot</groupId>\n        <artifactId>yshop</artifactId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>yshop-module-score</artifactId>\n    <packaging>pom</packaging>\n    <modules>\n        <module>yshop-module-score-api</module>\n        <module>yshop-module-score-biz</module>\n    </modules>\n\n    <name>${project.artifactId}</name>\n    <description>\n        score 模块\n    </description>\n\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-api/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>yshop-module-score</artifactId>\n        <groupId>co.yixiang.boot</groupId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>yshop-module-score-api</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>\n        score 模块 API，暴露给其它模块调用\n    </description>\n\n    <dependencies>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-common</artifactId>\n        </dependency>\n\n        <!-- 参数校验 -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-validation</artifactId>\n            <optional>true</optional>\n        </dependency>\n\n        <!-- DB 相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-mybatis</artifactId>\n        </dependency>\n\n        <!-- 消息队列相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-mq</artifactId>\n        </dependency>\n\n\n        <!-- 支付相关 -->\n        <dependency>\n            <groupId>com.egzosn</groupId>\n            <artifactId>pay-java-wx</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.egzosn</groupId>\n            <artifactId>pay-java-ali</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.egzosn</groupId>\n            <artifactId>pay-spring-boot-starter</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>com.egzosn</groupId>\n            <artifactId>pay-java-web-support</artifactId>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-api/src/main/java/co/yixiang/yshop/module/score/enums/ErrorCodeConstants.java",
    "content": "package co.yixiang.yshop.module.score.enums;\n\nimport co.yixiang.yshop.framework.common.exception.ErrorCode;\npublic interface ErrorCodeConstants {\n    ErrorCode ORDER_NOT_EXISTS = new ErrorCode(1008018000, \"积分商城订单不存在\");\n    ErrorCode PRODUCT_NOT_EXISTS = new ErrorCode(1008018001, \"积分产品不存在\");\n    ErrorCode SCORE_NOT = new ErrorCode(1008018002, \"积分不足\");\n    ErrorCode PRODUCT_NOT_STOCK = new ErrorCode(1008018002, \"库存不足\");\n}\n\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-api/src/main/java/co/yixiang/yshop/module/score/enums/OrderStatusEnum.java",
    "content": "package co.yixiang.yshop.module.score.enums;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\nimport java.util.stream.Stream;\n\n/**\n * @author hupeng\n * 订单相关枚举\n */\n@Getter\n@AllArgsConstructor\npublic enum OrderStatusEnum {\n\n    STATUS__1(-1,\"全部订单\"),\n    STATUS_0(0,\"待发货\"),\n    STATUS_1(1,\"待收货\"),\n    STATUS_2(2,\"已完成\"),\n    STATUS_6(6,\"已删除\");\n\n\n\n    private Integer value;\n    private String desc;\n\n    public static OrderStatusEnum toType(int value) {\n        return Stream.of(OrderStatusEnum.values())\n                .filter(p -> p.value == value)\n                .findAny()\n                .orElse(null);\n    }\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-biz/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <artifactId>yshop-module-score</artifactId>\n        <groupId>co.yixiang.boot</groupId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>yshop-module-score-biz</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>\n        score 模块\n    </description>\n\n    <dependencies>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-score-api</artifactId>\n            <version>${revision}</version>\n        </dependency>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-member-biz</artifactId>\n            <version>${revision}</version>\n        </dependency>\n\n\n        <!-- 业务组件 -->\n<!--        <dependency>-->\n<!--            <groupId>co.yixiang.boot</groupId>-->\n<!--            <artifactId>yshop-spring-boot-starter-biz-operatelog</artifactId>-->\n<!--        </dependency>-->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-biz-tenant</artifactId>\n        </dependency>\n\n        <!-- Web 相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-security</artifactId>\n        </dependency>\n\n\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-redis</artifactId>\n        </dependency>\n\n\n        <!-- 消息队列相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-mq</artifactId>\n        </dependency>\n\n        <!-- Test 测试相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n\n        <!-- 工具类相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-excel</artifactId>\n        </dependency>\n\n\n\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-biz/src/main/java/co/yixiang/yshop/module/score/controller/admin/scoreorder/ScoreOrderController.java",
    "content": "package co.yixiang.yshop.module.score.controller.admin.scoreorder;\n\nimport jakarta.servlet.http.HttpServletResponse;\nimport org.springframework.web.bind.annotation.*;\nimport jakarta.annotation.Resource;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.Operation;\n\nimport jakarta.validation.constraints.*;\nimport jakarta.validation.*;\nimport java.util.*;\nimport java.io.IOException;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\nimport co.yixiang.yshop.framework.excel.core.util.ExcelUtils;\n\nimport co.yixiang.yshop.module.score.controller.admin.scoreorder.vo.*;\nimport co.yixiang.yshop.module.score.dal.dataobject.scoreorder.ScoreOrderDO;\nimport co.yixiang.yshop.module.score.convert.scoreorder.ScoreOrderConvert;\nimport co.yixiang.yshop.module.score.service.scoreorder.ScoreOrderService;\n\n@Tag(name = \"管理后台 - 积分商城订单\")\n@RestController\n@RequestMapping(\"/score/order\")\n@Validated\npublic class ScoreOrderController {\n\n    @Resource\n    private ScoreOrderService orderService;\n\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"更新积分商城订单\")\n    @PreAuthorize(\"@ss.hasPermission('score:order:update')\")\n    public CommonResult<Boolean> updateOrder(@Valid @RequestBody ScoreOrderUpdateReqVO updateReqVO) {\n        orderService.updateOrder(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除积分商城订单\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('score:order:delete')\")\n    public CommonResult<Boolean> deleteOrder(@RequestParam(\"id\") Long id) {\n        orderService.deleteOrder(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得积分商城订单\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('score:order:query')\")\n    public CommonResult<ScoreOrderRespVO> getOrder(@RequestParam(\"id\") Long id) {\n        return success(orderService.getOrder(id));\n    }\n\n    @GetMapping(\"/list\")\n    @Operation(summary = \"获得积分商城订单列表\")\n    @Parameter(name = \"ids\", description = \"编号列表\", required = true, example = \"1024,2048\")\n    @PreAuthorize(\"@ss.hasPermission('score:order:query')\")\n    public CommonResult<List<ScoreOrderRespVO>> getOrderList(@RequestParam(\"ids\") Collection<Long> ids) {\n        List<ScoreOrderDO> list = orderService.getOrderList(ids);\n        return success(ScoreOrderConvert.INSTANCE.convertList(list));\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得积分商城订单分页\")\n    @PreAuthorize(\"@ss.hasPermission('score:order:query')\")\n    public CommonResult<PageResult<ScoreOrderRespVO>> getOrderPage(@Valid ScoreOrderPageReqVO pageVO) {\n        return success(orderService.getOrderPage(pageVO));\n    }\n\n    @GetMapping(\"/export-excel\")\n    @Operation(summary = \"导出积分商城订单 Excel\")\n    @PreAuthorize(\"@ss.hasPermission('score:order:export')\")\n    public void exportOrderExcel(@Valid ScoreOrderExportReqVO exportReqVO,\n              HttpServletResponse response) throws IOException {\n        List<ScoreOrderDO> list = orderService.getOrderList(exportReqVO);\n        // 导出 Excel\n        List<ScoreOrderExcelVO> datas = ScoreOrderConvert.INSTANCE.convertList02(list);\n        ExcelUtils.write(response, \"积分商城订单.xls\", \"数据\", ScoreOrderExcelVO.class, datas);\n    }\n\n    @GetMapping(\"/take\")\n    @Operation(summary = \"收货\")\n    @PreAuthorize(\"@ss.hasPermission('score:order:update')\")\n    public CommonResult<Boolean> take(@RequestParam(\"id\") Long id) {\n        orderService.takeOrder(id);\n        return success(true);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-biz/src/main/java/co/yixiang/yshop/module/score/controller/admin/scoreorder/vo/ScoreOrderBaseVO.java",
    "content": "package co.yixiang.yshop.module.score.controller.admin.scoreorder.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.util.*;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\nimport jakarta.validation.constraints.*;\n\n/**\n* 积分商城订单 Base VO，提供给添加、修改、详细的子 VO 使用\n* 如果子 VO 存在差异的字段，请不要添加到这里，影响 Swagger 文档生成\n*/\n@Data\npublic class ScoreOrderBaseVO {\n\n    @Schema(description = \"用户id\", required = true, example = \"28397\")\n    @NotNull(message = \"用户id不能为空\")\n    private Long uid;\n\n    @Schema(description = \"订单编号\", required = true, example = \"28397\")\n    private String orderId;\n\n    @Schema(description = \"商品id\", required = true, example = \"31639\")\n    @NotNull(message = \"商品id不能为空\")\n    private Long productId;\n\n    @Schema(description = \"数量\", required = true)\n    @NotNull(message = \"数量不能为空\")\n    private Integer number;\n\n    @Schema(description = \"单个商品积分\", required = true)\n    @NotNull(message = \"单个商品积分不能为空\")\n    private Integer score;\n\n    @Schema(description = \"总消耗积分\", required = true)\n    @NotNull(message = \"总消耗积分不能为空\")\n    private Integer totalScore;\n\n    @Schema(description = \"下单ip\")\n    private String ip;\n\n    @Schema(description = \"快递公司编码\", required = true)\n    private String expressSn;\n\n    @Schema(description = \"快递编号\", required = true)\n    @NotNull(message = \"快递编号不能为空\")\n    private String expressNumber;\n\n    @Schema(description = \"快递公司\", required = true)\n    @NotNull(message = \"快递公司不能为空\")\n    private String expressCompany;\n\n    @Schema(description = \"收货名称\", example = \"王五\")\n    private String customerName;\n\n    @Schema(description = \"收货电话\")\n    private String customerPhone;\n\n    @Schema(description = \"收货地址\")\n    private String customerAddress;\n\n    @Schema(description = \"订单状态:0=取消订单,1=正常啊\", required = true, example = \"1\")\n    @NotNull(message = \"订单状态:0=取消订单,1=正常啊不能为空\")\n    private Boolean status;\n\n    @Schema(description = \"已支付:0=否\", required = true, example = \"12822\")\n    @NotNull(message = \"已支付:0=否不能为空\")\n    private Integer havePaid;\n\n    @Schema(description = \"已发货:0=否\", required = true)\n    @NotNull(message = \"已发货:0=否不能为空\")\n    private Integer haveDelivered;\n\n    @Schema(description = \"已收货:0=否\", required = true)\n    @NotNull(message = \"已收货:0=否不能为空\")\n    private Integer haveReceived;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-biz/src/main/java/co/yixiang/yshop/module/score/controller/admin/scoreorder/vo/ScoreOrderCreateReqVO.java",
    "content": "package co.yixiang.yshop.module.score.controller.admin.scoreorder.vo;\n\nimport lombok.*;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.*;\n\n@Schema(description = \"管理后台 - 积分商城订单创建 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class ScoreOrderCreateReqVO extends ScoreOrderBaseVO {\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-biz/src/main/java/co/yixiang/yshop/module/score/controller/admin/scoreorder/vo/ScoreOrderExcelVO.java",
    "content": "package co.yixiang.yshop.module.score.controller.admin.scoreorder.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.util.*;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\n\nimport com.alibaba.excel.annotation.ExcelProperty;\n\n/**\n * 积分商城订单 Excel VO\n *\n * @author yshop\n */\n@Data\npublic class ScoreOrderExcelVO {\n\n    @ExcelProperty(\"id\")\n    private Long id;\n\n    @ExcelProperty(\"用户id\")\n    private Integer userId;\n\n    @ExcelProperty(\"商品id\")\n    private Integer productId;\n\n    @ExcelProperty(\"数量\")\n    private Integer number;\n\n    @ExcelProperty(\"单个商品积分\")\n    private Integer score;\n\n    @ExcelProperty(\"总消耗积分\")\n    private Integer totalScore;\n\n    @ExcelProperty(\"下单ip\")\n    private String ip;\n\n    @ExcelProperty(\"快递编号\")\n    private String expressNumber;\n\n    @ExcelProperty(\"快递公司\")\n    private String expressCompany;\n\n    @ExcelProperty(\"收货名称\")\n    private String customerName;\n\n    @ExcelProperty(\"收货电话\")\n    private String customerPhone;\n\n    @ExcelProperty(\"收货地址\")\n    private String customerAddress;\n\n    @ExcelProperty(\"订单状态:0=取消订单,1=正常啊\")\n    private Boolean status;\n\n    @ExcelProperty(\"已支付:0=否\")\n    private Integer havePaid;\n\n    @ExcelProperty(\"已发货:0=否\")\n    private Integer haveDelivered;\n\n    @ExcelProperty(\"已收货:0=否\")\n    private Integer haveReceived;\n\n    @ExcelProperty(\"添加时间\")\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-biz/src/main/java/co/yixiang/yshop/module/score/controller/admin/scoreorder/vo/ScoreOrderExportReqVO.java",
    "content": "package co.yixiang.yshop.module.score.controller.admin.scoreorder.vo;\n\nimport lombok.*;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport java.time.LocalDateTime;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 积分商城订单 Excel 导出 Request VO，参数和 ScoreOrderPageReqVO 是一致的\")\n@Data\npublic class ScoreOrderExportReqVO {\n\n    @Schema(description = \"用户id\", example = \"28397\")\n    private Integer userId;\n\n    @Schema(description = \"商品id\", example = \"31639\")\n    private Integer productId;\n\n    @Schema(description = \"数量\")\n    private Integer number;\n\n    @Schema(description = \"单个商品积分\")\n    private Integer score;\n\n    @Schema(description = \"总消耗积分\")\n    private Integer totalScore;\n\n    @Schema(description = \"下单ip\")\n    private String ip;\n\n    @Schema(description = \"快递编号\")\n    private String expressNumber;\n\n    @Schema(description = \"快递公司\")\n    private String expressCompany;\n\n    @Schema(description = \"收货名称\", example = \"王五\")\n    private String customerName;\n\n    @Schema(description = \"收货电话\")\n    private String customerPhone;\n\n    @Schema(description = \"收货地址\")\n    private String customerAddress;\n\n    @Schema(description = \"订单状态:0=取消订单,1=正常啊\", example = \"1\")\n    private Boolean status;\n\n    @Schema(description = \"已支付:0=否\", example = \"12822\")\n    private Integer havePaid;\n\n    @Schema(description = \"已发货:0=否\")\n    private Integer haveDelivered;\n\n    @Schema(description = \"已收货:0=否\")\n    private Integer haveReceived;\n\n    @Schema(description = \"添加时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-biz/src/main/java/co/yixiang/yshop/module/score/controller/admin/scoreorder/vo/ScoreOrderPageReqVO.java",
    "content": "package co.yixiang.yshop.module.score.controller.admin.scoreorder.vo;\n\nimport lombok.*;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport org.springframework.format.annotation.DateTimeFormat;\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 积分商城订单分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class ScoreOrderPageReqVO extends PageParam {\n\n    @Schema(description = \"用户id\", example = \"28397\")\n    private Long uid;\n\n    @Schema(description = \"订单编号\", required = true, example = \"28397\")\n    private String orderId;\n\n    @Schema(description = \"商品id\", example = \"31639\")\n    private Long productId;\n\n    @Schema(description = \"数量\")\n    private Integer number;\n\n    @Schema(description = \"单个商品积分\")\n    private Integer score;\n\n    @Schema(description = \"总消耗积分\")\n    private Integer totalScore;\n\n    @Schema(description = \"下单ip\")\n    private String ip;\n\n    @Schema(description = \"快递编号\")\n    private String expressNumber;\n\n    @Schema(description = \"快递公司\")\n    private String expressCompany;\n\n    @Schema(description = \"收货名称\", example = \"王五\")\n    private String customerName;\n\n    @Schema(description = \"收货电话\")\n    private String customerPhone;\n\n    @Schema(description = \"收货地址\")\n    private String customerAddress;\n\n    @Schema(description = \"订单状态:0=取消订单,1=正常啊\", example = \"1\")\n    private Boolean status;\n\n    @Schema(description = \"已支付:0=否\", example = \"12822\")\n    private Integer havePaid;\n\n    @Schema(description = \"已发货:0=否\")\n    private Integer haveDelivered;\n\n    @Schema(description = \"已收货:0=否\")\n    private Integer haveReceived;\n\n    @Schema(description = \"添加时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n    // \"状态,-1全部 默认为0待发货 1待收货  2已完成\"\n    private Integer type;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-biz/src/main/java/co/yixiang/yshop/module/score/controller/admin/scoreorder/vo/ScoreOrderRespVO.java",
    "content": "package co.yixiang.yshop.module.score.controller.admin.scoreorder.vo;\n\nimport co.yixiang.yshop.module.member.controller.admin.user.vo.UserRespVO;\nimport co.yixiang.yshop.module.score.controller.admin.scoreproduct.vo.ScoreProductRespVO;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 积分商城订单 Response VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class ScoreOrderRespVO extends ScoreOrderBaseVO {\n\n    @Schema(description = \"id\", required = true, example = \"19735\")\n    private Long id;\n\n    @Schema(description = \"添加时间\")\n    private LocalDateTime createTime;\n\n    @Schema(description = \"商品明细\")\n    private ScoreProductRespVO scoreProductRespVO;\n\n    @Schema(description = \"用户信息\", required = true)\n    private UserRespVO userRespVO;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-biz/src/main/java/co/yixiang/yshop/module/score/controller/admin/scoreorder/vo/ScoreOrderUpdateReqVO.java",
    "content": "package co.yixiang.yshop.module.score.controller.admin.scoreorder.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.util.*;\nimport jakarta.validation.constraints.*;\n\n@Schema(description = \"管理后台 - 积分商城订单更新 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class ScoreOrderUpdateReqVO extends ScoreOrderBaseVO {\n\n    @Schema(description = \"id\", required = true, example = \"19735\")\n    @NotNull(message = \"id不能为空\")\n    private Long id;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-biz/src/main/java/co/yixiang/yshop/module/score/controller/admin/scoreproduct/ScoreProductController.java",
    "content": "package co.yixiang.yshop.module.score.controller.admin.scoreproduct;\n\nimport jakarta.servlet.http.HttpServletResponse;\nimport org.springframework.web.bind.annotation.*;\nimport jakarta.annotation.Resource;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.Operation;\n\nimport jakarta.validation.constraints.*;\nimport jakarta.validation.*;\nimport java.util.*;\nimport java.io.IOException;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\nimport co.yixiang.yshop.framework.excel.core.util.ExcelUtils;\n\nimport co.yixiang.yshop.module.score.controller.admin.scoreproduct.vo.*;\nimport co.yixiang.yshop.module.score.dal.dataobject.scoreproduct.ScoreProductDO;\nimport co.yixiang.yshop.module.score.convert.scoreproduct.ScoreProductConvert;\nimport co.yixiang.yshop.module.score.service.scoreproduct.ScoreProductService;\n\n@Tag(name = \"管理后台 - 积分产品\")\n@RestController\n@RequestMapping(\"/score/product\")\n@Validated\npublic class ScoreProductController {\n\n    @Resource\n    private ScoreProductService productService;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建积分产品\")\n    @PreAuthorize(\"@ss.hasPermission('score:product:create')\")\n    public CommonResult<Long> createProduct(@Valid @RequestBody ScoreProductCreateReqVO createReqVO) {\n        return success(productService.createProduct(createReqVO));\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"更新积分产品\")\n    @PreAuthorize(\"@ss.hasPermission('score:product:update')\")\n    public CommonResult<Boolean> updateProduct(@Valid @RequestBody ScoreProductUpdateReqVO updateReqVO) {\n        productService.updateProduct(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除积分产品\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('score:product:delete')\")\n    public CommonResult<Boolean> deleteProduct(@RequestParam(\"id\") Long id) {\n        productService.deleteProduct(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得积分产品\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('score:product:query')\")\n    public CommonResult<ScoreProductRespVO> getProduct(@RequestParam(\"id\") Long id) {\n        ScoreProductDO product = productService.getProduct(id);\n        return success(ScoreProductConvert.INSTANCE.convert(product));\n    }\n\n    @GetMapping(\"/list\")\n    @Operation(summary = \"获得积分产品列表\")\n    @Parameter(name = \"ids\", description = \"编号列表\", required = true, example = \"1024,2048\")\n    @PreAuthorize(\"@ss.hasPermission('score:product:query')\")\n    public CommonResult<List<ScoreProductRespVO>> getProductList(@RequestParam(\"ids\") Collection<Long> ids) {\n        List<ScoreProductDO> list = productService.getProductList(ids);\n        return success(ScoreProductConvert.INSTANCE.convertList(list));\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得积分产品分页\")\n    @PreAuthorize(\"@ss.hasPermission('score:product:query')\")\n    public CommonResult<PageResult<ScoreProductRespVO>> getProductPage(@Valid ScoreProductPageReqVO pageVO) {\n        PageResult<ScoreProductDO> pageResult = productService.getProductPage(pageVO);\n        return success(ScoreProductConvert.INSTANCE.convertPage(pageResult));\n    }\n\n    @GetMapping(\"/export-excel\")\n    @Operation(summary = \"导出积分产品 Excel\")\n    @PreAuthorize(\"@ss.hasPermission('score:product:export')\")\n    public void exportProductExcel(@Valid ScoreProductExportReqVO exportReqVO,\n              HttpServletResponse response) throws IOException {\n        List<ScoreProductDO> list = productService.getProductList(exportReqVO);\n        // 导出 Excel\n        List<ScoreProductExcelVO> datas = ScoreProductConvert.INSTANCE.convertList02(list);\n        ExcelUtils.write(response, \"积分产品.xls\", \"数据\", ScoreProductExcelVO.class, datas);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-biz/src/main/java/co/yixiang/yshop/module/score/controller/admin/scoreproduct/vo/ScoreProductBaseVO.java",
    "content": "package co.yixiang.yshop.module.score.controller.admin.scoreproduct.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.util.*;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\nimport jakarta.validation.constraints.*;\n\n/**\n* 积分产品 Base VO，提供给添加、修改、详细的子 VO 使用\n* 如果子 VO 存在差异的字段，请不要添加到这里，影响 Swagger 文档生成\n*/\n@Data\npublic class ScoreProductBaseVO {\n\n    @Schema(description = \"产品标题\", required = true)\n    @NotNull(message = \"产品标题不能为空\")\n    private String title;\n\n    @Schema(description = \"主图\", required = true)\n    @NotNull(message = \"主图不能为空\")\n    private String image;\n\n    @Schema(description = \"组图\", required = true)\n    @NotNull(message = \"组图不能为空\")\n    private List<String> images;\n\n    @Schema(description = \"详情\", required = true)\n    @NotNull(message = \"详情不能为空\")\n    private String desc;\n\n    @Schema(description = \"消耗积分\", required = true)\n    @NotNull(message = \"消耗积分不能为空\")\n    private Integer score;\n\n    @Schema(description = \"权重\", required = true)\n    private Integer weigh;\n\n    @Schema(description = \"库存\", required = true)\n    @NotNull(message = \"库存不能为空\")\n    private Integer stock;\n\n    @Schema(description = \"销售量\", required = true)\n    private Integer sales;\n\n    @Schema(description = \"是否上架:0=否,1=是\", required = true)\n    private Integer isSwitch;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-biz/src/main/java/co/yixiang/yshop/module/score/controller/admin/scoreproduct/vo/ScoreProductCreateReqVO.java",
    "content": "package co.yixiang.yshop.module.score.controller.admin.scoreproduct.vo;\n\nimport lombok.*;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.*;\n\n@Schema(description = \"管理后台 - 积分产品创建 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class ScoreProductCreateReqVO extends ScoreProductBaseVO {\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-biz/src/main/java/co/yixiang/yshop/module/score/controller/admin/scoreproduct/vo/ScoreProductExcelVO.java",
    "content": "package co.yixiang.yshop.module.score.controller.admin.scoreproduct.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.util.*;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\n\nimport com.alibaba.excel.annotation.ExcelProperty;\n\n/**\n * 积分产品 Excel VO\n *\n * @author yshop\n */\n@Data\npublic class ScoreProductExcelVO {\n\n    @ExcelProperty(\"id\")\n    private Long id;\n\n    @ExcelProperty(\"产品标题\")\n    private String title;\n\n    @ExcelProperty(\"主图\")\n    private String image;\n\n    @ExcelProperty(\"组图\")\n    private List<String> images;\n\n    @ExcelProperty(\"详情\")\n    private String desc;\n\n    @ExcelProperty(\"消耗积分\")\n    private Integer score;\n\n    @ExcelProperty(\"权重\")\n    private Integer weigh;\n\n    @ExcelProperty(\"库存\")\n    private Integer stock;\n\n    @ExcelProperty(\"销售量\")\n    private Integer sales;\n\n    @ExcelProperty(\"是否上架:0=否,1=是\")\n    private Integer isSwitch;\n\n    @ExcelProperty(\"添加时间\")\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-biz/src/main/java/co/yixiang/yshop/module/score/controller/admin/scoreproduct/vo/ScoreProductExportReqVO.java",
    "content": "package co.yixiang.yshop.module.score.controller.admin.scoreproduct.vo;\n\nimport lombok.*;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport java.time.LocalDateTime;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 积分产品 Excel 导出 Request VO，参数和 ScoreProductPageReqVO 是一致的\")\n@Data\npublic class ScoreProductExportReqVO {\n\n    @Schema(description = \"产品标题\")\n    private String title;\n\n    @Schema(description = \"主图\")\n    private String image;\n\n    @Schema(description = \"组图\")\n    private String images;\n\n    @Schema(description = \"详情\")\n    private String desc;\n\n    @Schema(description = \"消耗积分\")\n    private Integer score;\n\n    @Schema(description = \"权重\")\n    private Integer weigh;\n\n    @Schema(description = \"库存\")\n    private Integer stock;\n\n    @Schema(description = \"销售量\")\n    private Integer sales;\n\n    @Schema(description = \"是否上架:0=否,1=是\")\n    private Integer isSwitch;\n\n    @Schema(description = \"添加时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-biz/src/main/java/co/yixiang/yshop/module/score/controller/admin/scoreproduct/vo/ScoreProductPageReqVO.java",
    "content": "package co.yixiang.yshop.module.score.controller.admin.scoreproduct.vo;\n\nimport lombok.*;\nimport java.util.*;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport org.springframework.format.annotation.DateTimeFormat;\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 积分产品分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class ScoreProductPageReqVO extends PageParam {\n\n    @Schema(description = \"产品标题\")\n    private String title;\n\n    @Schema(description = \"主图\")\n    private String image;\n\n    @Schema(description = \"组图\")\n    private List<String> images;\n\n    @Schema(description = \"详情\")\n    private String desc;\n\n    @Schema(description = \"消耗积分\")\n    private Integer score;\n\n    @Schema(description = \"权重\")\n    private Integer weigh;\n\n    @Schema(description = \"库存\")\n    private Integer stock;\n\n    @Schema(description = \"销售量\")\n    private Integer sales;\n\n    @Schema(description = \"是否上架:0=否,1=是\")\n    private Integer isSwitch;\n\n    @Schema(description = \"添加时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-biz/src/main/java/co/yixiang/yshop/module/score/controller/admin/scoreproduct/vo/ScoreProductRespVO.java",
    "content": "package co.yixiang.yshop.module.score.controller.admin.scoreproduct.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 积分产品 Response VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class ScoreProductRespVO extends ScoreProductBaseVO {\n\n    @Schema(description = \"id\", required = true, example = \"8624\")\n    private Long id;\n\n    @Schema(description = \"添加时间\")\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-biz/src/main/java/co/yixiang/yshop/module/score/controller/admin/scoreproduct/vo/ScoreProductUpdateReqVO.java",
    "content": "package co.yixiang.yshop.module.score.controller.admin.scoreproduct.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport java.util.*;\nimport jakarta.validation.constraints.*;\n\n@Schema(description = \"管理后台 - 积分产品更新 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class ScoreProductUpdateReqVO extends ScoreProductBaseVO {\n\n    @Schema(description = \"id\", required = true, example = \"8624\")\n    @NotNull(message = \"id不能为空\")\n    private Long id;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-biz/src/main/java/co/yixiang/yshop/module/score/controller/app/order/AppScoreOrderController.java",
    "content": "package co.yixiang.yshop.module.score.controller.app.order;\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.security.core.annotations.PreAuthenticated;\nimport co.yixiang.yshop.module.score.controller.app.order.param.AppScoreOrderParam;\nimport co.yixiang.yshop.module.score.controller.app.order.vo.AppScoreOrderVO;\nimport co.yixiang.yshop.module.score.controller.app.product.param.AppScoreProductQueryParam;\nimport co.yixiang.yshop.module.score.controller.app.product.vo.AppScoreProductVO;\nimport co.yixiang.yshop.module.score.service.scoreorder.AppScoreOrderService;\nimport co.yixiang.yshop.module.score.service.scoreproduct.AppScoreProductService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.Parameters;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.web.bind.annotation.*;\n\nimport jakarta.validation.Valid;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\nimport static co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;\n\n@Tag(name = \"用户 APP - 积分订单\")\n@RestController\n@RequestMapping(\"/score-order\")\n@RequiredArgsConstructor(onConstructor = @__(@Autowired))\npublic class AppScoreOrderController {\n\n    private final AppScoreOrderService appScoreOrderService;\n\n    /**\n     * 提交积分订单\n     */\n    @PreAuthenticated\n    @PostMapping(\"/submit\")\n    @Operation(summary = \"提交积分订单\")\n    public CommonResult<Boolean> submit(@RequestBody @Valid AppScoreOrderParam appScoreOrderParam){\n        Long uid = getLoginUserId();\n        appScoreOrderService.submit(uid,appScoreOrderParam);\n        return success(true);\n    }\n\n    /**\n     * 订单列表\n     */\n    @PreAuthenticated\n    @GetMapping(\"/list\")\n    @Operation(summary = \"订单列表\")\n    @Parameters({\n            @Parameter(name = \"type\", description = \"状态,-1全部 默认为0待发货 1待收货  2已完成\",\n                    required = true, example = \"1\"),\n            @Parameter(name = \"page\", description = \"页码,默认为1\",\n                    required = true, example = \"1\"),\n            @Parameter(name = \"limit\", description = \"页大小,默认为10\",\n                    required = true, example = \"10      \")\n    })\n    public CommonResult<List<AppScoreOrderVO>> orderList(@RequestParam(value = \"type\", defaultValue = \"0\") int type,\n                                                         @RequestParam(value = \"page\", defaultValue = \"1\") int page,\n                                                         @RequestParam(value = \"limit\", defaultValue = \"10\") int limit) {\n        Long uid = getLoginUserId();\n        return success(appScoreOrderService.orderList(uid, type, page, limit));\n    }\n\n    /**\n     * 订单详情\n     */\n    @PreAuthenticated\n    @GetMapping(\"/detail\")\n    @Operation(summary = \"订单详情\")\n    @Parameters({\n            @Parameter(name = \"id\", description = \"id\",\n                    required = true, example = \"1\")\n    })\n    public CommonResult<AppScoreOrderVO> orderList(@RequestParam(value = \"id\", defaultValue = \"0\") Long id) {\n        Long uid = getLoginUserId();\n        return success(appScoreOrderService.orderDetail(uid, id));\n    }\n\n\n    /**\n     * 收货\n     */\n    @PreAuthenticated\n    @GetMapping(\"/take\")\n    @Operation(summary = \"收货\")\n    public CommonResult<Boolean> take(@RequestParam(value = \"id\", defaultValue = \"0\") Long id){\n        Long uid = getLoginUserId();\n        appScoreOrderService.take(uid,id);\n        return success(true);\n    }\n\n\n\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-biz/src/main/java/co/yixiang/yshop/module/score/controller/app/order/param/AppScoreOrderParam.java",
    "content": "package co.yixiang.yshop.module.score.controller.app.order.param;\n\nimport co.yixiang.yshop.framework.common.params.QueryParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\nimport jakarta.validation.constraints.NotBlank;\n\n/**\n * <p>\n * 积分订单参数对象\n * </p>\n *\n * @author hupeng\n * @date 2023-11-30\n */\n@Data\n@Schema(description = \"用户 APP - 积分订单参数对象\")\npublic class AppScoreOrderParam {\n\n    @Schema(description = \"积分商品ID\", required = true)\n    @NotBlank(message = \"参数有误\")\n    private String productId;\n\n    @Schema(description = \"地址ID\", required = true)\n    @NotBlank(message = \"请选择地址\")\n    private String addressId;\n\n    @Schema(description = \"数量\", required = true)\n    private String num;\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-biz/src/main/java/co/yixiang/yshop/module/score/controller/app/order/vo/AppScoreOrderVO.java",
    "content": "package co.yixiang.yshop.module.score.controller.app.order.vo;\n\nimport co.yixiang.yshop.module.score.controller.app.product.vo.AppScoreProductVO;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotNull;\nimport java.time.LocalDateTime;\n\n/**\n* 积分商城订单 AppScoreOrderVO VO，提供给添加、修改、详细的子 VO 使用\n*/\n@Data\npublic class AppScoreOrderVO {\n\n    private Long id;\n\n    @Schema(description = \"用户id\", required = true, example = \"28397\")\n    private Long uid;\n\n    @Schema(description = \"订单编号\", required = true, example = \"28397\")\n    private String orderId;\n\n    @Schema(description = \"商品id\", required = true, example = \"31639\")\n    private Long productId;\n\n    @Schema(description = \"数量\", required = true)\n    private Integer number;\n\n    @Schema(description = \"单个商品积分\", required = true)\n    private Integer score;\n\n    @Schema(description = \"总消耗积分\", required = true)\n    @NotNull(message = \"总消耗积分不能为空\")\n    private Integer totalScore;\n\n    @Schema(description = \"下单ip\")\n    private String ip;\n\n    @Schema(description = \"快递公司编号\", required = true)\n    private String expressSn;\n\n    @Schema(description = \"快递编号\", required = true)\n    private String expressNumber;\n\n    @Schema(description = \"快递公司\", required = true)\n    private String expressCompany;\n\n    @Schema(description = \"收货名称\", example = \"王五\")\n    private String customerName;\n\n    @Schema(description = \"收货电话\")\n    private String customerPhone;\n\n    @Schema(description = \"收货地址\")\n    private String customerAddress;\n\n    @Schema(description = \"订单状态:0=取消订单,1=正常啊\", required = true, example = \"1\")\n    private Boolean status;\n\n    @Schema(description = \"已支付:0=否\", required = true, example = \"12822\")\n    private Integer havePaid;\n\n    @Schema(description = \"已发货:0=否\", required = true)\n    private Integer haveDelivered;\n\n    @Schema(description = \"已收货:0=否\", required = true)\n    private Integer haveReceived;\n\n    @Schema(description = \"订单状态字符串\", required = true)\n    private String statusText;\n\n    private LocalDateTime createTime;\n\n    private AppScoreProductVO product;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-biz/src/main/java/co/yixiang/yshop/module/score/controller/app/product/AppScoreProductController.java",
    "content": "package co.yixiang.yshop.module.score.controller.app.product;\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.module.score.controller.app.product.param.AppScoreProductQueryParam;\nimport co.yixiang.yshop.module.score.controller.app.product.vo.AppScoreProductVO;\nimport co.yixiang.yshop.module.score.service.scoreproduct.AppScoreProductService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport lombok.RequiredArgsConstructor;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"用户 APP - 积分商品\")\n@RestController\n@RequestMapping(\"/score-product\")\n@RequiredArgsConstructor(onConstructor = @__(@Autowired))\npublic class AppScoreProductController {\n\n    private final AppScoreProductService appScoreProductService;\n\n    /**\n     * 获取积分商品列表\n     */\n    @GetMapping(\"/list\")\n    @Operation(summary = \"获取积分商品列表\")\n    public CommonResult<List<AppScoreProductVO>> goodsList(AppScoreProductQueryParam productQueryParam){\n        return success(appScoreProductService.getList(productQueryParam.getPage(),productQueryParam.getLimit()));\n    }\n\n    /**\n     * 获取积分商品详情\n     */\n    @GetMapping(\"/detail\")\n    @Operation(summary = \"获取积分商品详情\")\n    public CommonResult<AppScoreProductVO> goodsDetail(@RequestParam(value = \"id\", defaultValue = \"0\") Long id){\n        return success(appScoreProductService.getDetail(id));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-biz/src/main/java/co/yixiang/yshop/module/score/controller/app/product/param/AppScoreProductQueryParam.java",
    "content": "package co.yixiang.yshop.module.score.controller.app.product.param;\n\nimport co.yixiang.yshop.framework.common.params.QueryParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\n/**\n * <p>\n * 商品查询参数对象\n * </p>\n *\n * @author hupeng\n * @date 2023-11-26\n */\n@Data\n@EqualsAndHashCode(callSuper = true)\n@Schema(description = \"用户 APP - 积分商品表查询参数\")\npublic class AppScoreProductQueryParam extends QueryParam {\n    private static final long serialVersionUID = 1L;\n    @Schema(description = \"关键字\", required = true)\n    private String keyword;\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-biz/src/main/java/co/yixiang/yshop/module/score/controller/app/product/vo/AppScoreProductVO.java",
    "content": "package co.yixiang.yshop.module.score.controller.app.product.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotNull;\nimport java.util.List;\n\n/**\n* 积分产品 AppScoreProductVO VO\n* 如果子 VO 存在差异的字段，请不要添加到这里，影响 Swagger 文档生成\n*/\n@Data\npublic class AppScoreProductVO {\n\n    @Schema(description = \"id\", required = true, example = \"8624\")\n    private Long id;\n\n    @Schema(description = \"产品标题\", required = true)\n    private String title;\n\n    @Schema(description = \"主图\", required = true)\n    private String image;\n\n    @Schema(description = \"组图\", required = true)\n    private List<String> images;\n\n    @Schema(description = \"详情\", required = true)\n    private String desc;\n\n    @Schema(description = \"消耗积分\", required = true)\n    private Integer score;\n\n\n    @Schema(description = \"库存\", required = true)\n    private Integer stock;\n\n    @Schema(description = \"销售量\", required = true)\n    private Integer sales;\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-biz/src/main/java/co/yixiang/yshop/module/score/convert/scoreorder/ScoreOrderConvert.java",
    "content": "package co.yixiang.yshop.module.score.convert.scoreorder;\n\nimport java.util.*;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\nimport co.yixiang.yshop.module.score.controller.app.order.vo.AppScoreOrderVO;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.factory.Mappers;\nimport co.yixiang.yshop.module.score.controller.admin.scoreorder.vo.*;\nimport co.yixiang.yshop.module.score.dal.dataobject.scoreorder.ScoreOrderDO;\n\n/**\n * 积分商城订单 Convert\n *\n * @author yshop\n */\n@Mapper\npublic interface ScoreOrderConvert {\n\n    ScoreOrderConvert INSTANCE = Mappers.getMapper(ScoreOrderConvert.class);\n\n    ScoreOrderDO convert(ScoreOrderCreateReqVO bean);\n\n    ScoreOrderDO convert(ScoreOrderUpdateReqVO bean);\n\n    ScoreOrderRespVO convert(ScoreOrderDO bean);\n\n    AppScoreOrderVO convert01(ScoreOrderDO bean);\n\n    List<ScoreOrderRespVO> convertList(List<ScoreOrderDO> list);\n\n    List<AppScoreOrderVO> convertList01(List<ScoreOrderDO> list);\n\n    PageResult<ScoreOrderRespVO> convertPage(PageResult<ScoreOrderDO> page);\n\n    List<ScoreOrderExcelVO> convertList02(List<ScoreOrderDO> list);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-biz/src/main/java/co/yixiang/yshop/module/score/convert/scoreproduct/ScoreProductConvert.java",
    "content": "package co.yixiang.yshop.module.score.convert.scoreproduct;\n\nimport java.util.*;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\nimport co.yixiang.yshop.module.score.controller.app.product.vo.AppScoreProductVO;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.factory.Mappers;\nimport co.yixiang.yshop.module.score.controller.admin.scoreproduct.vo.*;\nimport co.yixiang.yshop.module.score.dal.dataobject.scoreproduct.ScoreProductDO;\n\n/**\n * 积分产品 Convert\n *\n * @author yshop\n */\n@Mapper\npublic interface ScoreProductConvert {\n\n    ScoreProductConvert INSTANCE = Mappers.getMapper(ScoreProductConvert.class);\n\n    ScoreProductDO convert(ScoreProductCreateReqVO bean);\n\n    ScoreProductDO convert(ScoreProductUpdateReqVO bean);\n\n    ScoreProductRespVO convert(ScoreProductDO bean);\n\n    AppScoreProductVO convert01(ScoreProductDO bean);\n\n    List<ScoreProductRespVO> convertList(List<ScoreProductDO> list);\n\n    List<AppScoreProductVO> convertList01(List<ScoreProductDO> list);\n\n    PageResult<ScoreProductRespVO> convertPage(PageResult<ScoreProductDO> page);\n\n    List<ScoreProductExcelVO> convertList02(List<ScoreProductDO> list);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-biz/src/main/java/co/yixiang/yshop/module/score/dal/dataobject/scoreorder/ScoreOrderDO.java",
    "content": "package co.yixiang.yshop.module.score.dal.dataobject.scoreorder;\n\nimport lombok.*;\nimport java.util.*;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\nimport com.baomidou.mybatisplus.annotation.*;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\n\n/**\n * 积分商城订单 DO\n *\n * @author yshop\n */\n@TableName(\"yshop_score_order\")\n@KeySequence(\"yshop_score_order_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class ScoreOrderDO extends BaseDO {\n\n    /**\n     * id\n     */\n    @TableId\n    private Long id;\n    /**\n     * 用户id\n     */\n    private Long uid;\n\n    /**\n     * 订单编号\n     */\n    private String orderId;\n    /**\n     * 商品id\n     */\n    private Long productId;\n    /**\n     * 数量\n     */\n    private Integer number;\n    /**\n     * 单个商品积分\n     */\n    private Integer score;\n    /**\n     * 总消耗积分\n     */\n    private Integer totalScore;\n    /**\n     * 下单ip\n     */\n    private String ip;\n\n    /**\n     * 快递公司编码\n     */\n    private String expressSn;\n    /**\n     * 快递编号\n     */\n    private String expressNumber;\n    /**\n     * 快递公司\n     */\n    private String expressCompany;\n    /**\n     * 收货名称\n     */\n    private String customerName;\n    /**\n     * 收货电话\n     */\n    private String customerPhone;\n    /**\n     * 收货地址\n     */\n    private String customerAddress;\n    /**\n     * 订单状态:0=取消订单,1=正常啊\n     */\n    private Boolean status;\n    /**\n     * 已支付:0=否\n     */\n    private Integer havePaid;\n    /**\n     * 已发货:0=否\n     */\n    private Integer haveDelivered;\n    /**\n     * 已收货:0=否\n     */\n    private Integer haveReceived;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-biz/src/main/java/co/yixiang/yshop/module/score/dal/dataobject/scoreproduct/ScoreProductDO.java",
    "content": "package co.yixiang.yshop.module.score.dal.dataobject.scoreproduct;\n\nimport co.yixiang.yshop.framework.mybatis.core.type.StringListTypeHandler;\nimport lombok.*;\nimport java.util.*;\nimport java.time.LocalDateTime;\nimport java.time.LocalDateTime;\nimport com.baomidou.mybatisplus.annotation.*;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\n\n/**\n * 积分产品 DO\n *\n * @author yshop\n */\n@TableName(value = \"yshop_score_product\",autoResultMap = true)\n@KeySequence(\"yshop_score_product_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class ScoreProductDO extends BaseDO {\n\n    /**\n     * id\n     */\n    @TableId\n    private Long id;\n    /**\n     * 产品标题\n     */\n    private String title;\n    /**\n     * 主图\n     */\n    private String image;\n    /**\n     * 组图\n     */\n    @TableField(typeHandler = StringListTypeHandler.class)\n    private List<String> images;\n    /**\n     * 详情\n     */\n    @TableField(value = \"`desc`\")\n    private String desc;\n    /**\n     * 消耗积分\n     */\n    private Integer score;\n    /**\n     * 权重\n     */\n    private Integer weigh;\n    /**\n     * 库存\n     */\n    private Integer stock;\n    /**\n     * 销售量\n     */\n    private Integer sales;\n    /**\n     * 是否上架:0=否,1=是\n     */\n    private Integer isSwitch;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-biz/src/main/java/co/yixiang/yshop/module/score/dal/mysql/scoreorder/ScoreOrderMapper.java",
    "content": "package co.yixiang.yshop.module.score.dal.mysql.scoreorder;\n\nimport java.util.*;\n\nimport co.yixiang.yshop.framework.common.enums.OrderInfoEnum;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.module.score.dal.dataobject.scoreorder.ScoreOrderDO;\nimport co.yixiang.yshop.module.score.enums.OrderStatusEnum;\nimport org.apache.ibatis.annotations.Mapper;\nimport co.yixiang.yshop.module.score.controller.admin.scoreorder.vo.*;\nimport org.apache.ibatis.annotations.Param;\nimport org.apache.ibatis.annotations.Update;\n\n/**\n * 积分商城订单 Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface ScoreOrderMapper extends BaseMapperX<ScoreOrderDO> {\n\n    default PageResult<ScoreOrderDO> selectPage(ScoreOrderPageReqVO reqVO) {\n        LambdaQueryWrapperX<ScoreOrderDO> wrapper = new LambdaQueryWrapperX();\n        wrapper.eqIfPresent(ScoreOrderDO::getOrderId, reqVO.getOrderId())\n                .likeIfPresent(ScoreOrderDO::getCustomerName, reqVO.getCustomerName())\n                .eqIfPresent(ScoreOrderDO::getCustomerPhone, reqVO.getCustomerPhone())\n                .orderByDesc(ScoreOrderDO::getId);\n        switch (OrderStatusEnum.toType(reqVO.getType())) {\n            case STATUS__1:\n                break;\n            //待发货\n            case STATUS_0:\n                wrapper.eq(ScoreOrderDO::getHavePaid, OrderInfoEnum.PAY_STATUS_1.getValue())\n                        .eq(ScoreOrderDO::getHaveDelivered, OrderInfoEnum.STATUS_0.getValue())\n                        .eq(ScoreOrderDO::getHaveReceived, OrderInfoEnum.STATUS_0.getValue());\n                break;\n            //待收货\n            case STATUS_1:\n                wrapper.eq(ScoreOrderDO::getHavePaid, OrderInfoEnum.PAY_STATUS_1.getValue())\n                        .eq(ScoreOrderDO::getHaveDelivered, OrderInfoEnum.STATUS_1.getValue())\n                        .eq(ScoreOrderDO::getHaveReceived, OrderInfoEnum.STATUS_0.getValue());\n                break;\n            //已完成\n            case STATUS_2:\n                wrapper.eq(ScoreOrderDO::getHavePaid, OrderInfoEnum.PAY_STATUS_1.getValue())\n                        .eq(ScoreOrderDO::getHaveDelivered, OrderInfoEnum.STATUS_1.getValue())\n                        .eq(ScoreOrderDO::getHaveReceived, OrderInfoEnum.STATUS_1.getValue());\n                break;\n            default:\n        }\n        return selectPage(reqVO, wrapper);\n    }\n\n    default List<ScoreOrderDO> selectList(ScoreOrderExportReqVO reqVO) {\n        return selectList(new LambdaQueryWrapperX<ScoreOrderDO>()\n                .eqIfPresent(ScoreOrderDO::getProductId, reqVO.getProductId())\n                .eqIfPresent(ScoreOrderDO::getNumber, reqVO.getNumber())\n                .eqIfPresent(ScoreOrderDO::getScore, reqVO.getScore())\n                .eqIfPresent(ScoreOrderDO::getTotalScore, reqVO.getTotalScore())\n                .eqIfPresent(ScoreOrderDO::getIp, reqVO.getIp())\n                .eqIfPresent(ScoreOrderDO::getExpressNumber, reqVO.getExpressNumber())\n                .eqIfPresent(ScoreOrderDO::getExpressCompany, reqVO.getExpressCompany())\n                .likeIfPresent(ScoreOrderDO::getCustomerName, reqVO.getCustomerName())\n                .eqIfPresent(ScoreOrderDO::getCustomerPhone, reqVO.getCustomerPhone())\n                .eqIfPresent(ScoreOrderDO::getCustomerAddress, reqVO.getCustomerAddress())\n                .eqIfPresent(ScoreOrderDO::getStatus, reqVO.getStatus())\n                .eqIfPresent(ScoreOrderDO::getHavePaid, reqVO.getHavePaid())\n                .eqIfPresent(ScoreOrderDO::getHaveDelivered, reqVO.getHaveDelivered())\n                .eqIfPresent(ScoreOrderDO::getHaveReceived, reqVO.getHaveReceived())\n                .betweenIfPresent(ScoreOrderDO::getCreateTime, reqVO.getCreateTime())\n                .orderByDesc(ScoreOrderDO::getId));\n    }\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-biz/src/main/java/co/yixiang/yshop/module/score/dal/mysql/scoreproduct/ScoreProductMapper.java",
    "content": "package co.yixiang.yshop.module.score.dal.mysql.scoreproduct;\n\nimport java.util.*;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.module.score.dal.dataobject.scoreproduct.ScoreProductDO;\nimport org.apache.ibatis.annotations.Mapper;\nimport co.yixiang.yshop.module.score.controller.admin.scoreproduct.vo.*;\nimport org.apache.ibatis.annotations.Param;\nimport org.apache.ibatis.annotations.Update;\n\n/**\n * 积分产品 Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface ScoreProductMapper extends BaseMapperX<ScoreProductDO> {\n\n    default PageResult<ScoreProductDO> selectPage(ScoreProductPageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<ScoreProductDO>()\n                .eqIfPresent(ScoreProductDO::getTitle, reqVO.getTitle())\n                .eqIfPresent(ScoreProductDO::getImage, reqVO.getImage())\n                .eqIfPresent(ScoreProductDO::getImages, reqVO.getImages())\n                .eqIfPresent(ScoreProductDO::getDesc, reqVO.getDesc())\n                .eqIfPresent(ScoreProductDO::getScore, reqVO.getScore())\n                .eqIfPresent(ScoreProductDO::getWeigh, reqVO.getWeigh())\n                .eqIfPresent(ScoreProductDO::getStock, reqVO.getStock())\n                .eqIfPresent(ScoreProductDO::getSales, reqVO.getSales())\n                .betweenIfPresent(ScoreProductDO::getCreateTime, reqVO.getCreateTime())\n                .orderByDesc(ScoreProductDO::getId));\n    }\n\n    default List<ScoreProductDO> selectList(ScoreProductExportReqVO reqVO) {\n        return selectList(new LambdaQueryWrapperX<ScoreProductDO>()\n                .eqIfPresent(ScoreProductDO::getTitle, reqVO.getTitle())\n                .eqIfPresent(ScoreProductDO::getImage, reqVO.getImage())\n                .eqIfPresent(ScoreProductDO::getImages, reqVO.getImages())\n                .eqIfPresent(ScoreProductDO::getDesc, reqVO.getDesc())\n                .eqIfPresent(ScoreProductDO::getScore, reqVO.getScore())\n                .eqIfPresent(ScoreProductDO::getWeigh, reqVO.getWeigh())\n                .eqIfPresent(ScoreProductDO::getStock, reqVO.getStock())\n                .eqIfPresent(ScoreProductDO::getSales, reqVO.getSales())\n                .betweenIfPresent(ScoreProductDO::getCreateTime, reqVO.getCreateTime())\n                .orderByDesc(ScoreProductDO::getId));\n    }\n\n\n    /**\n     * 普通商品 减库存 加销量\n     * @param num\n     * @param productId\n     * @return\n     */\n    @Update(\"update yshop_score_product set stock=stock-#{num}, sales=sales+#{num}\" +\n            \" where id=#{productId} and stock >= #{num}\")\n    int decStockIncSales(@Param(\"num\") Integer num, @Param(\"productId\") Long productId);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-biz/src/main/java/co/yixiang/yshop/module/score/service/scoreorder/AppScoreOrderService.java",
    "content": "package co.yixiang.yshop.module.score.service.scoreorder;\n\nimport co.yixiang.yshop.module.score.controller.app.order.param.AppScoreOrderParam;\nimport co.yixiang.yshop.module.score.controller.app.order.vo.AppScoreOrderVO;\nimport co.yixiang.yshop.module.score.dal.dataobject.scoreorder.ScoreOrderDO;\nimport com.baomidou.mybatisplus.extension.service.IService;\n\nimport java.util.List;\n\n/**\n * 积分商城订单 Service 接口\n *\n * @author yshop\n */\npublic interface AppScoreOrderService extends IService<ScoreOrderDO> {\n\n    /**\n     * 提交\n     * @param uid\n     * @param appScoreOrderParam\n     */\n    void submit(Long uid,AppScoreOrderParam appScoreOrderParam);\n\n    /**\n     * 订单列表\n     * @param uid 用户id\n     * @param type\n     * @param page page\n     * @param limit limit\n     * @return list\n     */\n    List<AppScoreOrderVO> orderList(Long uid, int type, int page, int limit);\n\n    /**\n     * 订单详情\n     * @param uid 用户id\n     * @param id\n     */\n    AppScoreOrderVO orderDetail(Long uid, Long id);\n\n    /**\n     * 收货\n     * @param uid\n     * @param id\n     */\n    void take(Long uid, Long id);\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-biz/src/main/java/co/yixiang/yshop/module/score/service/scoreorder/AppScoreOrderServiceImpl.java",
    "content": "package co.yixiang.yshop.module.score.service.scoreorder;\n\nimport cn.hutool.core.util.IdUtil;\nimport cn.hutool.core.util.NumberUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.constant.ShopConstants;\nimport co.yixiang.yshop.framework.common.enums.OrderInfoEnum;\nimport co.yixiang.yshop.framework.common.enums.ShopCommonEnum;\nimport co.yixiang.yshop.framework.common.exception.ErrorCode;\nimport co.yixiang.yshop.framework.common.util.servlet.ServletUtils;\nimport co.yixiang.yshop.framework.ip.core.utils.IPUtils;\nimport co.yixiang.yshop.module.member.dal.dataobject.user.MemberUserDO;\nimport co.yixiang.yshop.module.member.dal.dataobject.useraddress.UserAddressDO;\nimport co.yixiang.yshop.module.member.enums.BillDetailEnum;\nimport co.yixiang.yshop.module.member.service.user.MemberUserService;\nimport co.yixiang.yshop.module.member.service.useraddress.AppUserAddressService;\nimport co.yixiang.yshop.module.member.service.userbill.UserBillService;\nimport co.yixiang.yshop.module.score.controller.app.order.param.AppScoreOrderParam;\nimport co.yixiang.yshop.module.score.controller.app.order.vo.AppScoreOrderVO;\nimport co.yixiang.yshop.module.score.convert.scoreorder.ScoreOrderConvert;\nimport co.yixiang.yshop.module.score.dal.dataobject.scoreorder.ScoreOrderDO;\nimport co.yixiang.yshop.module.score.dal.dataobject.scoreproduct.ScoreProductDO;\nimport co.yixiang.yshop.module.score.dal.mysql.scoreorder.ScoreOrderMapper;\nimport co.yixiang.yshop.module.score.dal.mysql.scoreproduct.ScoreProductMapper;\nimport co.yixiang.yshop.module.score.enums.OrderStatusEnum;\nimport co.yixiang.yshop.module.score.service.scoreproduct.AppScoreProductService;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport org.redisson.api.RLock;\nimport org.redisson.api.RedissonClient;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Propagation;\nimport org.springframework.transaction.annotation.Transactional;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\n\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.stream.Collectors;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.member.enums.ErrorCodeConstants.USER_ADDRESS_NOT_EXISTS;\nimport static co.yixiang.yshop.module.score.enums.ErrorCodeConstants.*;\n\n/**\n * 积分商城订单 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class AppScoreOrderServiceImpl extends ServiceImpl<ScoreOrderMapper, ScoreOrderDO> implements AppScoreOrderService {\n\n    @Resource\n    private ScoreOrderMapper orderMapper;\n    @Resource\n    private ScoreProductMapper scoreProductMapper;\n    @Resource\n    private AppScoreProductService appScoreProductService;\n    @Resource\n    private MemberUserService userService;\n    @Resource\n    private RedissonClient redissonClient;\n    @Resource\n    private UserBillService billService;\n    @Resource\n    private  AppUserAddressService appUserAddressService;\n\n    private static final String STOCK_LOCK_KEY = \"score:order:stock:lock\";\n\n\n    /**\n     * 提交\n     * @param uid\n     * @param appScoreOrderParam\n     */\n    @Override\n    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)\n    public void submit(Long uid,AppScoreOrderParam appScoreOrderParam) {\n        ScoreProductDO scoreProductDO = appScoreProductService.getById(appScoreOrderParam.getProductId());\n        if(scoreProductDO == null){\n            throw exception(PRODUCT_NOT_EXISTS);\n        }\n       UserAddressDO userAddressDO =  appUserAddressService.getById(appScoreOrderParam.getAddressId());\n        if(userAddressDO == null){\n            throw exception(USER_ADDRESS_NOT_EXISTS);\n        }\n        MemberUserDO memberUserDO = userService.getById(uid);\n        if(scoreProductDO.getStock() <= 0){\n            throw exception(PRODUCT_NOT_STOCK);\n        }\n        if(NumberUtil.compare(memberUserDO.getIntegral().intValue(),scoreProductDO.getScore()) < 0){\n            throw exception(SCORE_NOT);\n        }\n\n        //生成分布式唯一值\n        String orderSn = IdUtil.getSnowflake(0, 0).nextIdStr();\n        ScoreOrderDO scoreOrderDO = ScoreOrderDO.builder()\n                .havePaid(ShopCommonEnum.IS_STATUS_1.getValue())\n                .customerAddress(userAddressDO.getAddress() + userAddressDO.getDetail())\n                .customerName(userAddressDO.getRealName())\n                .customerPhone(userAddressDO.getPhone())\n                .ip(ServletUtils.getClientIP())\n                .orderId(orderSn)\n                .productId(scoreProductDO.getId())\n                .number(Integer.valueOf(appScoreOrderParam.getNum()))\n                .score(scoreProductDO.getScore())\n                .totalScore(scoreProductDO.getScore())\n                .uid(uid)\n                .build();\n        //保存\n        this.save(scoreOrderDO);\n\n        //减去积分\n        userService.decScore(uid,scoreOrderDO.getScore());\n\n        this.deStockIncSale(scoreProductDO.getId(),Integer.valueOf(appScoreOrderParam.getNum()));\n\n        //增加流水\n        billService.expend(uid, \"积分兑换\",\n                BillDetailEnum.CATEGORY_2.getValue(),\n                BillDetailEnum.TYPE_3.getValue(),\n                scoreProductDO.getScore().doubleValue(), memberUserDO.getIntegral().doubleValue(),\n                scoreProductDO.getScore() + \"积分兑换商品\");\n\n    }\n\n\n    /**\n     * 订单列表\n     *\n     * @param uid   用户id\n     * @param type  OrderStatusEnum\n     * @param page  page\n     * @param limit limit\n     * @return list\n     */\n    @Override\n    public List<AppScoreOrderVO> orderList(Long uid, int type, int page, int limit) {\n        LambdaQueryWrapper<ScoreOrderDO> wrapper = new LambdaQueryWrapper<>();\n        wrapper.eq(ScoreOrderDO::getUid, uid);\n        wrapper.orderByDesc(ScoreOrderDO::getId);\n        switch (OrderStatusEnum.toType(type)) {\n            case STATUS__1:\n                break;\n            //待发货\n            case STATUS_0:\n                wrapper.eq(ScoreOrderDO::getHavePaid, OrderInfoEnum.PAY_STATUS_1.getValue())\n                        .eq(ScoreOrderDO::getHaveDelivered, OrderInfoEnum.STATUS_0.getValue())\n                        .eq(ScoreOrderDO::getHaveReceived, OrderInfoEnum.STATUS_0.getValue());\n                break;\n            //待收货\n            case STATUS_1:\n                wrapper.eq(ScoreOrderDO::getHavePaid, OrderInfoEnum.PAY_STATUS_1.getValue())\n                        .eq(ScoreOrderDO::getHaveDelivered, OrderInfoEnum.STATUS_1.getValue())\n                        .eq(ScoreOrderDO::getHaveReceived, OrderInfoEnum.STATUS_0.getValue());\n                break;\n            //已完成\n            case STATUS_2:\n                wrapper.eq(ScoreOrderDO::getHavePaid, OrderInfoEnum.PAY_STATUS_1.getValue())\n                        //.eq(ScoreOrderDO::getHaveDelivered, OrderInfoEnum.STATUS_1.getValue())\n                        .eq(ScoreOrderDO::getHaveReceived, OrderInfoEnum.STATUS_1.getValue());\n                break;\n            default:\n        }\n\n        Page<ScoreOrderDO> pageModel = new Page<>(page, limit);\n        IPage<ScoreOrderDO> pageList = orderMapper.selectPage(pageModel, wrapper);\n        List<AppScoreOrderVO> list = ScoreOrderConvert.INSTANCE.convertList01(pageList.getRecords());\n\n        return list.stream().map(this::handleOrder).collect(Collectors.toList());\n    }\n\n    /**\n     * 订单详情\n     * @param uid 用户id\n     * @param id\n     */\n    @Override\n    public AppScoreOrderVO orderDetail(Long uid, Long id) {\n        LambdaQueryWrapper<ScoreOrderDO> wrapper = new LambdaQueryWrapper<>();\n        wrapper.eq(ScoreOrderDO::getUid, uid).eq(ScoreOrderDO::getId,id);\n        ScoreOrderDO scoreOrderDO = this.getOne(wrapper);\n\n        AppScoreOrderVO appScoreOrderVO = ScoreOrderConvert.INSTANCE.convert01(scoreOrderDO);\n        return this.handleOrder(appScoreOrderVO);\n    }\n\n    /**\n     * 收货\n     * @param uid\n     * @param id\n     */\n    @Override\n    public void take(Long uid, Long id) {\n        LambdaQueryWrapper<ScoreOrderDO> wrapper = new LambdaQueryWrapper<>();\n        wrapper.eq(ScoreOrderDO::getUid, uid).eq(ScoreOrderDO::getId,id);\n        ScoreOrderDO scoreOrderDO = this.getOne(wrapper);\n        if(scoreOrderDO == null){\n            throw exception(ORDER_NOT_EXISTS);\n        }\n        scoreOrderDO.setHaveReceived(ShopCommonEnum.DEFAULT_1.getValue());\n        this.updateById(scoreOrderDO);\n    }\n\n    /**\n     * 处理订单\n     * @param order\n     * @return\n     */\n    private AppScoreOrderVO handleOrder(AppScoreOrderVO order) {\n        order.setProduct(appScoreProductService.getDetail(order.getProductId()));\n        if (OrderInfoEnum.PAY_STATUS_1.getValue().equals(order.getHavePaid())\n                && OrderInfoEnum.STATUS_0.getValue().equals(order.getHaveDelivered())\n                && OrderInfoEnum.STATUS_0.getValue().equals(order.getHaveReceived())) {\n            order.setStatusText(\"待发货\");\n        } else if (OrderInfoEnum.PAY_STATUS_1.getValue().equals(order.getHavePaid())\n                && OrderInfoEnum.STATUS_1.getValue().equals(order.getHaveDelivered())\n                && OrderInfoEnum.STATUS_0.getValue().equals(order.getHaveReceived())) {\n            order.setStatusText(\"待收货\");\n        } else if (OrderInfoEnum.PAY_STATUS_1.getValue().equals(order.getHavePaid())\n                && OrderInfoEnum.STATUS_1.getValue().equals(order.getHaveReceived())) {\n            order.setStatusText(\"已完成\");\n        }\n        return order;\n    }\n\n\n\n    /**\n     * 减库存增加销量\n     *\n     * @param productId 商品id\n     * @param number 商品数量\n     */\n    private void deStockIncSale(Long productId, Integer number) {\n\n        //对库存加锁\n        RLock lock = redissonClient.getLock(STOCK_LOCK_KEY);\n        if (lock.tryLock()) {\n            try {\n                scoreProductMapper.decStockIncSales(number,productId);\n            }catch (Exception ex) {\n                log.error(\"[deStockIncSale][执行异常]\", ex);\n                throw exception(new ErrorCode(999999,ex.getMessage()));\n            } finally {\n                lock.unlock();\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-biz/src/main/java/co/yixiang/yshop/module/score/service/scoreorder/ScoreOrderService.java",
    "content": "package co.yixiang.yshop.module.score.service.scoreorder;\n\nimport java.util.*;\nimport jakarta.validation.*;\nimport co.yixiang.yshop.module.score.controller.admin.scoreorder.vo.*;\nimport co.yixiang.yshop.module.score.dal.dataobject.scoreorder.ScoreOrderDO;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\n/**\n * 积分商城订单 Service 接口\n *\n * @author yshop\n */\npublic interface ScoreOrderService {\n\n    /**\n     * 创建积分商城订单\n     *\n     * @param createReqVO 创建信息\n     * @return 编号\n     */\n    Long createOrder(@Valid ScoreOrderCreateReqVO createReqVO);\n\n    /**\n     * 更新积分商城订单\n     *\n     * @param updateReqVO 更新信息\n     */\n    void updateOrder(@Valid ScoreOrderUpdateReqVO updateReqVO);\n\n    /**\n     * 删除积分商城订单\n     *\n     * @param id 编号\n     */\n    void deleteOrder(Long id);\n\n    /**\n     * 收货\n     *\n     * @param id 编号\n     */\n    void takeOrder(Long id);\n\n    /**\n     * 获得积分商城订单\n     *\n     * @param id 编号\n     * @return 积分商城订单\n     */\n    ScoreOrderRespVO getOrder(Long id);\n\n    /**\n     * 获得积分商城订单列表\n     *\n     * @param ids 编号\n     * @return 积分商城订单列表\n     */\n    List<ScoreOrderDO> getOrderList(Collection<Long> ids);\n\n    /**\n     * 获得积分商城订单分页\n     *\n     * @param pageReqVO 分页查询\n     * @return 积分商城订单分页\n     */\n    PageResult<ScoreOrderRespVO> getOrderPage(ScoreOrderPageReqVO pageReqVO);\n\n    /**\n     * 获得积分商城订单列表, 用于 Excel 导出\n     *\n     * @param exportReqVO 查询条件\n     * @return 积分商城订单列表\n     */\n    List<ScoreOrderDO> getOrderList(ScoreOrderExportReqVO exportReqVO);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-biz/src/main/java/co/yixiang/yshop/module/score/service/scoreorder/ScoreOrderServiceImpl.java",
    "content": "package co.yixiang.yshop.module.score.service.scoreorder;\n\nimport co.yixiang.yshop.framework.common.enums.ShopCommonEnum;\nimport co.yixiang.yshop.module.member.controller.admin.user.vo.UserRespVO;\nimport co.yixiang.yshop.module.member.convert.user.UserConvert;\nimport co.yixiang.yshop.module.member.dal.dataobject.user.MemberUserDO;\nimport co.yixiang.yshop.module.member.dal.mysql.user.MemberUserMapper;\nimport co.yixiang.yshop.module.score.convert.scoreproduct.ScoreProductConvert;\nimport co.yixiang.yshop.module.score.dal.dataobject.scoreproduct.ScoreProductDO;\nimport co.yixiang.yshop.module.score.dal.mysql.scoreproduct.ScoreProductMapper;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport org.springframework.stereotype.Service;\nimport jakarta.annotation.Resource;\nimport org.springframework.validation.annotation.Validated;\n\nimport java.util.*;\nimport co.yixiang.yshop.module.score.controller.admin.scoreorder.vo.*;\nimport co.yixiang.yshop.module.score.dal.dataobject.scoreorder.ScoreOrderDO;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\nimport co.yixiang.yshop.module.score.convert.scoreorder.ScoreOrderConvert;\nimport co.yixiang.yshop.module.score.dal.mysql.scoreorder.ScoreOrderMapper;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.score.enums.ErrorCodeConstants.*;\n\n/**\n * 积分商城订单 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class ScoreOrderServiceImpl implements ScoreOrderService {\n\n    @Resource\n    private ScoreOrderMapper orderMapper;\n    @Resource\n    private ScoreProductMapper scoreProductMapper;\n    @Resource\n    private MemberUserMapper memberUserMapper;\n\n    @Override\n    public Long createOrder(ScoreOrderCreateReqVO createReqVO) {\n        // 插入\n        ScoreOrderDO order = ScoreOrderConvert.INSTANCE.convert(createReqVO);\n        orderMapper.insert(order);\n        // 返回\n        return order.getId();\n    }\n\n    @Override\n    public void updateOrder(ScoreOrderUpdateReqVO updateReqVO) {\n        // 校验存在\n        validateOrderExists(updateReqVO.getId());\n        // 更新\n        ScoreOrderDO updateObj = ScoreOrderConvert.INSTANCE.convert(updateReqVO);\n        updateObj.setHaveDelivered(ShopCommonEnum.DEFAULT_1.getValue());\n        orderMapper.updateById(updateObj);\n    }\n\n    @Override\n    public void deleteOrder(Long id) {\n        // 校验存在\n        validateOrderExists(id);\n        // 删除\n        orderMapper.deleteById(id);\n    }\n\n    @Override\n    public void takeOrder(Long id) {\n        // 校验存在\n        ScoreOrderDO scoreOrderDO = orderMapper.selectById(id);\n        if (scoreOrderDO == null) {\n            throw exception(ORDER_NOT_EXISTS);\n        }\n        scoreOrderDO.setHaveReceived(ShopCommonEnum.DEFAULT_1.getValue());\n        orderMapper.updateById(scoreOrderDO);\n\n    }\n\n    private void validateOrderExists(Long id) {\n        if (orderMapper.selectById(id) == null) {\n            throw exception(ORDER_NOT_EXISTS);\n        }\n    }\n\n    @Override\n    public ScoreOrderRespVO getOrder(Long id) {\n        ScoreOrderDO scoreOrderDO = orderMapper.selectById(id);\n        ScoreOrderRespVO scoreOrderRespVO = ScoreOrderConvert.INSTANCE.convert(scoreOrderDO);\n        ScoreProductDO scoreProductDO = scoreProductMapper.selectById(scoreOrderRespVO.getProductId());\n        scoreOrderRespVO.setScoreProductRespVO(ScoreProductConvert.INSTANCE.convert(scoreProductDO));\n        MemberUserDO memberUserDO = memberUserMapper.selectById(scoreOrderRespVO.getUid());\n        UserRespVO  userRespVO = UserConvert.INSTANCE.convert4(memberUserDO);\n        scoreOrderRespVO.setUserRespVO(userRespVO);\n        return scoreOrderRespVO;\n    }\n\n    @Override\n    public List<ScoreOrderDO> getOrderList(Collection<Long> ids) {\n        return orderMapper.selectBatchIds(ids);\n    }\n\n    @Override\n    public PageResult<ScoreOrderRespVO> getOrderPage(ScoreOrderPageReqVO pageReqVO) {\n        PageResult<ScoreOrderDO> pageResult = orderMapper.selectPage(pageReqVO);\n        PageResult<ScoreOrderRespVO> scoreOrderRespVOPageResult = ScoreOrderConvert.INSTANCE.convertPage(pageResult);\n        for (ScoreOrderRespVO scoreOrderRespVO : scoreOrderRespVOPageResult.getList()) {\n            ScoreProductDO scoreProductDO = scoreProductMapper.selectById(scoreOrderRespVO.getProductId());\n            scoreOrderRespVO.setScoreProductRespVO(ScoreProductConvert.INSTANCE.convert(scoreProductDO));\n            MemberUserDO memberUserDO = memberUserMapper.selectById(scoreOrderRespVO.getUid());\n            UserRespVO  userRespVO = UserConvert.INSTANCE.convert4(memberUserDO);\n            scoreOrderRespVO.setUserRespVO(userRespVO);\n        }\n        return scoreOrderRespVOPageResult;\n    }\n\n    @Override\n    public List<ScoreOrderDO> getOrderList(ScoreOrderExportReqVO exportReqVO) {\n        return orderMapper.selectList(exportReqVO);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-biz/src/main/java/co/yixiang/yshop/module/score/service/scoreproduct/AppScoreProductService.java",
    "content": "package co.yixiang.yshop.module.score.service.scoreproduct;\n\nimport co.yixiang.yshop.module.score.controller.app.product.vo.AppScoreProductVO;\nimport co.yixiang.yshop.module.score.dal.dataobject.scoreproduct.ScoreProductDO;\nimport com.baomidou.mybatisplus.extension.service.IService;\n\nimport java.util.List;\n\n/**\n * 积分产品 Service 接口\n *\n * @author yshop\n */\npublic interface AppScoreProductService  extends IService<ScoreProductDO> {\n\n    /**\n     * 列表\n     * @param page page\n     * @param limit limit\n     * @return list\n     */\n    List<AppScoreProductVO> getList(int page, int limit);\n\n    /**\n     * 详情\n     * @param id 产品ID\n     * @return list\n     */\n    AppScoreProductVO getDetail(Long id);\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-biz/src/main/java/co/yixiang/yshop/module/score/service/scoreproduct/AppScoreProductServiceImpl.java",
    "content": "package co.yixiang.yshop.module.score.service.scoreproduct;\n\nimport co.yixiang.yshop.module.score.controller.app.product.vo.AppScoreProductVO;\nimport co.yixiang.yshop.module.score.convert.scoreproduct.ScoreProductConvert;\nimport co.yixiang.yshop.module.score.dal.dataobject.scoreproduct.ScoreProductDO;\nimport co.yixiang.yshop.module.score.dal.mysql.scoreproduct.ScoreProductMapper;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport com.baomidou.mybatisplus.core.metadata.IPage;\nimport com.baomidou.mybatisplus.extension.plugins.pagination.Page;\nimport com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\nimport java.util.List;\n\n/**\n * 积分产品 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class AppScoreProductServiceImpl  extends ServiceImpl<ScoreProductMapper,ScoreProductDO> implements AppScoreProductService {\n\n    @Resource\n    private ScoreProductMapper productMapper;\n\n\n    /**\n     * 订单列表\n     * @param page page\n     * @param limit limit\n     * @return list\n     */\n    @Override\n    public List<AppScoreProductVO> getList(int page, int limit) {\n        LambdaQueryWrapper<ScoreProductDO> wrapper = new LambdaQueryWrapper<>();\n        wrapper.orderByDesc(ScoreProductDO::getWeigh);\n\n\n        Page<ScoreProductDO> pageModel = new Page<>(page, limit);\n        IPage<ScoreProductDO> pageList = productMapper.selectPage(pageModel, wrapper);\n        List<AppScoreProductVO> list = ScoreProductConvert.INSTANCE.convertList01(pageList.getRecords());\n\n        return list;\n    }\n\n\n    /**\n     * 详情\n     * @param id 产品ID\n     * @return list\n     */\n    @Override\n    public AppScoreProductVO getDetail(Long id) {\n        ScoreProductDO scoreProductDO = this.getById(id);\n        return ScoreProductConvert.INSTANCE.convert01(scoreProductDO);\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-biz/src/main/java/co/yixiang/yshop/module/score/service/scoreproduct/ScoreProductService.java",
    "content": "package co.yixiang.yshop.module.score.service.scoreproduct;\n\nimport java.util.*;\nimport jakarta.validation.*;\nimport co.yixiang.yshop.module.score.controller.admin.scoreproduct.vo.*;\nimport co.yixiang.yshop.module.score.dal.dataobject.scoreproduct.ScoreProductDO;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\n/**\n * 积分产品 Service 接口\n *\n * @author yshop\n */\npublic interface ScoreProductService {\n\n    /**\n     * 创建积分产品\n     *\n     * @param createReqVO 创建信息\n     * @return 编号\n     */\n    Long createProduct(@Valid ScoreProductCreateReqVO createReqVO);\n\n    /**\n     * 更新积分产品\n     *\n     * @param updateReqVO 更新信息\n     */\n    void updateProduct(@Valid ScoreProductUpdateReqVO updateReqVO);\n\n    /**\n     * 删除积分产品\n     *\n     * @param id 编号\n     */\n    void deleteProduct(Long id);\n\n    /**\n     * 获得积分产品\n     *\n     * @param id 编号\n     * @return 积分产品\n     */\n    ScoreProductDO getProduct(Long id);\n\n    /**\n     * 获得积分产品列表\n     *\n     * @param ids 编号\n     * @return 积分产品列表\n     */\n    List<ScoreProductDO> getProductList(Collection<Long> ids);\n\n    /**\n     * 获得积分产品分页\n     *\n     * @param pageReqVO 分页查询\n     * @return 积分产品分页\n     */\n    PageResult<ScoreProductDO> getProductPage(ScoreProductPageReqVO pageReqVO);\n\n    /**\n     * 获得积分产品列表, 用于 Excel 导出\n     *\n     * @param exportReqVO 查询条件\n     * @return 积分产品列表\n     */\n    List<ScoreProductDO> getProductList(ScoreProductExportReqVO exportReqVO);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-biz/src/main/java/co/yixiang/yshop/module/score/service/scoreproduct/ScoreProductServiceImpl.java",
    "content": "package co.yixiang.yshop.module.score.service.scoreproduct;\n\nimport org.springframework.stereotype.Service;\nimport jakarta.annotation.Resource;\nimport org.springframework.validation.annotation.Validated;\n\nimport java.util.*;\nimport co.yixiang.yshop.module.score.controller.admin.scoreproduct.vo.*;\nimport co.yixiang.yshop.module.score.dal.dataobject.scoreproduct.ScoreProductDO;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\n\nimport co.yixiang.yshop.module.score.convert.scoreproduct.ScoreProductConvert;\nimport co.yixiang.yshop.module.score.dal.mysql.scoreproduct.ScoreProductMapper;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.score.enums.ErrorCodeConstants.*;\n\n/**\n * 积分产品 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class ScoreProductServiceImpl implements ScoreProductService {\n\n    @Resource\n    private ScoreProductMapper productMapper;\n\n    @Override\n    public Long createProduct(ScoreProductCreateReqVO createReqVO) {\n        // 插入\n        ScoreProductDO product = ScoreProductConvert.INSTANCE.convert(createReqVO);\n        productMapper.insert(product);\n        // 返回\n        return product.getId();\n    }\n\n    @Override\n    public void updateProduct(ScoreProductUpdateReqVO updateReqVO) {\n        // 校验存在\n        validateProductExists(updateReqVO.getId());\n        // 更新\n        ScoreProductDO updateObj = ScoreProductConvert.INSTANCE.convert(updateReqVO);\n        productMapper.updateById(updateObj);\n    }\n\n    @Override\n    public void deleteProduct(Long id) {\n        // 校验存在\n        validateProductExists(id);\n        // 删除\n        productMapper.deleteById(id);\n    }\n\n    private void validateProductExists(Long id) {\n        if (productMapper.selectById(id) == null) {\n            throw exception(PRODUCT_NOT_EXISTS);\n        }\n    }\n\n    @Override\n    public ScoreProductDO getProduct(Long id) {\n        return productMapper.selectById(id);\n    }\n\n    @Override\n    public List<ScoreProductDO> getProductList(Collection<Long> ids) {\n        return productMapper.selectBatchIds(ids);\n    }\n\n    @Override\n    public PageResult<ScoreProductDO> getProductPage(ScoreProductPageReqVO pageReqVO) {\n        return productMapper.selectPage(pageReqVO);\n    }\n\n    @Override\n    public List<ScoreProductDO> getProductList(ScoreProductExportReqVO exportReqVO) {\n        return productMapper.selectList(exportReqVO);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-score/yshop-module-score-biz/src/main/resources/mapper/scoreorder/ScoreOrderMapper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE mapper PUBLIC \"-//mybatis.org//DTD Mapper 3.0//EN\" \"http://mybatis.org/dtd/mybatis-3-mapper.dtd\">\n<mapper namespace=\"co.yixiang.yshop.module.score.dal.mysql.scoreorder.ScoreOrderMapper\">\n\n    <!--\n        一般情况下，尽可能使用 Mapper 进行 CRUD 增删改查即可。\n        无法满足的场景，例如说多表关联查询，才使用 XML 编写 SQL。\n        代码生成器暂时只生成 Mapper XML 文件本身，更多推荐 MybatisX 快速开发插件来生成查询。\n        文档可见：https://www.yixiang.co/MyBatis/x-plugins/\n     -->\n\n</mapper>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>co.yixiang.boot</groupId>\n        <artifactId>yshop</artifactId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <modules>\n        <module>yshop-module-system-api</module>\n        <module>yshop-module-system-biz</module>\n    </modules>\n    <artifactId>yshop-module-system</artifactId>\n    <packaging>pom</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>\n        system 模块下，我们放通用业务，支撑上层的核心业务。\n        例如说：用户、部门、权限、数据字典等等\n    </description>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>co.yixiang.boot</groupId>\n        <artifactId>yshop-module-system</artifactId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>yshop-module-system-api</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>\n        system 模块 API，暴露给其它模块调用\n    </description>\n\n    <dependencies>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-common</artifactId>\n        </dependency>\n\n        <!-- 参数校验 -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-validation</artifactId>\n            <optional>true</optional>\n        </dependency>\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/dept/DeptApi.java",
    "content": "package co.yixiang.yshop.module.system.api.dept;\n\nimport co.yixiang.yshop.framework.common.util.collection.CollectionUtils;\nimport co.yixiang.yshop.module.system.api.dept.dto.DeptRespDTO;\n\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * 部门 API 接口\n *\n * @author yshop\n */\npublic interface DeptApi {\n\n    /**\n     * 获得部门信息\n     *\n     * @param id 部门编号\n     * @return 部门信息\n     */\n    DeptRespDTO getDept(Long id);\n\n    /**\n     * 获得部门信息数组\n     *\n     * @param ids 部门编号数组\n     * @return 部门信息数组\n     */\n    List<DeptRespDTO> getDeptList(Collection<Long> ids);\n\n    /**\n     * 校验部门们是否有效。如下情况，视为无效：\n     * 1. 部门编号不存在\n     * 2. 部门被禁用\n     *\n     * @param ids 角色编号数组\n     */\n    void validateDeptList(Collection<Long> ids);\n\n    /**\n     * 获得指定编号的部门 Map\n     *\n     * @param ids 部门编号数组\n     * @return 部门 Map\n     */\n    default Map<Long, DeptRespDTO> getDeptMap(Collection<Long> ids) {\n        List<DeptRespDTO> list = getDeptList(ids);\n        return CollectionUtils.convertMap(list, DeptRespDTO::getId);\n    }\n\n    /**\n     * 获得指定部门的所有子部门\n     *\n     * @param id 部门编号\n     * @return 子部门列表\n     */\n    List<DeptRespDTO> getChildDeptList(Long id);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/dept/PostApi.java",
    "content": "package co.yixiang.yshop.module.system.api.dept;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.map.MapUtil;\nimport co.yixiang.yshop.framework.common.util.collection.CollectionUtils;\nimport co.yixiang.yshop.module.system.api.dept.dto.PostRespDTO;\n\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * 岗位 API 接口\n *\n * @author yshop\n */\npublic interface PostApi {\n\n    /**\n     * 校验岗位们是否有效。如下情况，视为无效：\n     * 1. 岗位编号不存在\n     * 2. 岗位被禁用\n     *\n     * @param ids 岗位编号数组\n     */\n    void validPostList(Collection<Long> ids);\n\n    List<PostRespDTO> getPostList(Collection<Long> ids);\n\n    default Map<Long, PostRespDTO> getPostMap(Collection<Long> ids) {\n        if (CollUtil.isEmpty(ids)) {\n            return MapUtil.empty();\n        }\n\n        List<PostRespDTO> list = getPostList(ids);\n        return CollectionUtils.convertMap(list, PostRespDTO::getId);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/dept/dto/DeptRespDTO.java",
    "content": "package co.yixiang.yshop.module.system.api.dept.dto;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport lombok.Data;\n\n/**\n * 部门 Response DTO\n *\n * @author yshop\n */\n@Data\npublic class DeptRespDTO {\n\n    /**\n     * 部门编号\n     */\n    private Long id;\n    /**\n     * 部门名称\n     */\n    private String name;\n    /**\n     * 父部门编号\n     */\n    private Long parentId;\n    /**\n     * 负责人的用户编号\n     */\n    private Long leaderUserId;\n    /**\n     * 部门状态\n     *\n     * 枚举 {@link CommonStatusEnum}\n     */\n    private Integer status;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/dept/dto/PostRespDTO.java",
    "content": "package co.yixiang.yshop.module.system.api.dept.dto;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport lombok.Data;\n\n/**\n * 岗位 Response DTO\n *\n * @author yshop\n */\n@Data\npublic class PostRespDTO {\n\n    /**\n     * 岗位序号\n     */\n    private Long id;\n    /**\n     * 岗位名称\n     */\n    private String name;\n    /**\n     * 岗位编码\n     */\n    private String code;\n    /**\n     * 岗位排序\n     */\n    private Integer sort;\n    /**\n     * 状态\n     *\n     * 枚举 {@link CommonStatusEnum}\n     */\n    private Integer status;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/dict/DictDataApi.java",
    "content": "package co.yixiang.yshop.module.system.api.dict;\n\nimport cn.hutool.core.util.ObjUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.module.system.api.dict.dto.DictDataRespDTO;\n\nimport java.util.Collection;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.util.collection.CollectionUtils.convertList;\n\n/**\n * 字典数据 API 接口\n *\n * @author yshop\n */\npublic interface DictDataApi {\n\n    /**\n     * 校验字典数据们是否有效。如下情况，视为无效：\n     * 1. 字典数据不存在\n     * 2. 字典数据被禁用\n     *\n     * @param dictType 字典类型\n     * @param values   字典数据值的数组\n     */\n    void validateDictDataList(String dictType, Collection<String> values);\n\n    /**\n     * 获得指定的字典数据，从缓存中\n     *\n     * @param type  字典类型\n     * @param value 字典数据值\n     * @return 字典数据\n     */\n    DictDataRespDTO getDictData(String type, String value);\n\n    /**\n     * 获得指定的字典标签，从缓存中\n     *\n     * @param type  字典类型\n     * @param value 字典数据值\n     * @return 字典标签\n     */\n    default String getDictDataLabel(String type, Integer value) {\n        DictDataRespDTO dictData = getDictData(type, String.valueOf(value));\n        if (ObjUtil.isNull(dictData)) {\n            return StrUtil.EMPTY;\n        }\n        return dictData.getLabel();\n    }\n\n    /**\n     * 解析获得指定的字典数据，从缓存中\n     *\n     * @param type  字典类型\n     * @param label 字典数据标签\n     * @return 字典数据\n     */\n    DictDataRespDTO parseDictData(String type, String label);\n\n    /**\n     * 获得指定字典类型的字典数据列表\n     *\n     * @param dictType 字典类型\n     * @return 字典数据列表\n     */\n    List<DictDataRespDTO> getDictDataList(String dictType);\n\n    /**\n     * 获得字典数据标签列表\n     *\n     * @param dictType 字典类型\n     * @return 字典数据标签列表\n     */\n    default List<String> getDictDataLabelList(String dictType) {\n        List<DictDataRespDTO> list = getDictDataList(dictType);\n        return convertList(list, DictDataRespDTO::getLabel);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/dict/dto/DictDataRespDTO.java",
    "content": "package co.yixiang.yshop.module.system.api.dict.dto;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport lombok.Data;\n\n/**\n * 字典数据 Response DTO\n *\n * @author yshop\n */\n@Data\npublic class DictDataRespDTO {\n\n    /**\n     * 字典标签\n     */\n    private String label;\n    /**\n     * 字典值\n     */\n    private String value;\n    /**\n     * 字典类型\n     */\n    private String dictType;\n    /**\n     * 状态\n     *\n     * 枚举 {@link CommonStatusEnum}\n     */\n    private Integer status;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/logger/LoginLogApi.java",
    "content": "package co.yixiang.yshop.module.system.api.logger;\n\nimport co.yixiang.yshop.module.system.api.logger.dto.LoginLogCreateReqDTO;\n\nimport jakarta.validation.Valid;\n\n/**\n * 登录日志的 API 接口\n *\n * @author yshop\n */\npublic interface LoginLogApi {\n\n    /**\n     * 创建登录日志\n     *\n     * @param reqDTO 日志信息\n     */\n    void createLoginLog(@Valid LoginLogCreateReqDTO reqDTO);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/logger/OperateLogApi.java",
    "content": "package co.yixiang.yshop.module.system.api.logger;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.system.api.logger.dto.OperateLogCreateReqDTO;\nimport co.yixiang.yshop.module.system.api.logger.dto.OperateLogPageReqDTO;\nimport co.yixiang.yshop.module.system.api.logger.dto.OperateLogRespDTO;\nimport jakarta.validation.Valid;\n\n/**\n * 操作日志 API 接口\n *\n * @author yshop\n */\npublic interface OperateLogApi {\n\n    /**\n     * 创建操作日志\n     *\n     * @param createReqDTO 请求\n     */\n    void createOperateLog(@Valid OperateLogCreateReqDTO createReqDTO);\n\n    /**\n     * 获取指定模块的指定数据的操作日志分页\n     *\n     * @param pageReqVO 请求\n     * @return 操作日志分页\n     */\n    PageResult<OperateLogRespDTO> getOperateLogPage(OperateLogPageReqDTO pageReqVO);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/logger/dto/LoginLogCreateReqDTO.java",
    "content": "package co.yixiang.yshop.module.system.api.logger.dto;\n\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotBlank;\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\nimport jakarta.validation.constraints.Size;\n\n/**\n * 登录日志创建 Request DTO\n *\n * @author yshop\n */\n@Data\npublic class LoginLogCreateReqDTO {\n\n    /**\n     * 日志类型\n     */\n    @NotNull(message = \"日志类型不能为空\")\n    private Integer logType;\n    /**\n     * 链路追踪编号\n     */\n    private String traceId;\n\n    /**\n     * 用户编号\n     */\n    private Long userId;\n    /**\n     * 用户类型\n     */\n    @NotNull(message = \"用户类型不能为空\")\n    private Integer userType;\n    /**\n     * 用户账号\n     *\n     * 不再强制校验 username 非空，因为 Member 社交登录时，此时暂时没有 username(mobile）！\n     */\n    private String username;\n\n    /**\n     * 登录结果\n     */\n    @NotNull(message = \"登录结果不能为空\")\n    private Integer result;\n\n    /**\n     * 用户 IP\n     */\n    @NotEmpty(message = \"用户 IP 不能为空\")\n    private String userIp;\n    /**\n     * 浏览器 UserAgent\n     *\n     * 允许空，原因：Job 过期登出时，是无法传递 UserAgent 的\n     */\n    private String userAgent;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/logger/dto/OperateLogCreateReqDTO.java",
    "content": "package co.yixiang.yshop.module.system.api.logger.dto;\n\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\nimport lombok.Data;\n\n/**\n * 系统操作日志 Create Request DTO\n *\n * @author HUIHUI\n */\n@Data\npublic class OperateLogCreateReqDTO {\n\n    /**\n     * 链路追踪编号\n     *\n     * 一般来说，通过链路追踪编号，可以将访问日志，错误日志，链路追踪日志，logger 打印日志等，结合在一起，从而进行排错。\n     */\n    private String traceId;\n    /**\n     * 用户编号\n     *\n     * 关联 MemberUserDO 的 id 属性，或者 AdminUserDO 的 id 属性\n     */\n    @NotNull(message = \"用户编号不能为空\")\n    private Long userId;\n    /**\n     * 用户类型\n     *\n     * 关联 {@link  UserTypeEnum}\n     */\n    @NotNull(message = \"用户类型不能为空\")\n    private Integer userType;\n    /**\n     * 操作模块类型\n     */\n    @NotEmpty(message = \"操作模块类型不能为空\")\n    private String type;\n    /**\n     * 操作名\n     */\n    @NotEmpty(message = \"操作名不能为空\")\n    private String subType;\n    /**\n     * 操作模块业务编号\n     */\n    @NotNull(message = \"操作模块业务编号不能为空\")\n    private Long bizId;\n    /**\n     * 操作内容，记录整个操作的明细\n     * 例如说，修改编号为 1 的用户信息，将性别从男改成女，将姓名从yshop改成源码。\n     */\n    @NotEmpty(message = \"操作内容不能为空\")\n    private String action;\n    /**\n     * 拓展字段，有些复杂的业务，需要记录一些字段 ( JSON 格式 )\n     * 例如说，记录订单编号，{ orderId: \"1\"}\n     */\n    private String extra;\n\n    /**\n     * 请求方法名\n     */\n    @NotEmpty(message = \"请求方法名不能为空\")\n    private String requestMethod;\n    /**\n     * 请求地址\n     */\n    @NotEmpty(message = \"请求地址不能为空\")\n    private String requestUrl;\n    /**\n     * 用户 IP\n     */\n    @NotEmpty(message = \"用户 IP 不能为空\")\n    private String userIp;\n    /**\n     * 浏览器 UA\n     */\n    @NotEmpty(message = \"浏览器 UA 不能为空\")\n    private String userAgent;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/logger/dto/OperateLogPageReqDTO.java",
    "content": "package co.yixiang.yshop.module.system.api.logger.dto;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport lombok.Data;\n\n/**\n * 操作日志分页 Request DTO\n *\n * @author HUIHUI\n */\n@Data\npublic class OperateLogPageReqDTO extends PageParam {\n\n    /**\n     * 模块类型\n     */\n    private String type;\n    /**\n     * 模块数据编号\n     */\n    private Long bizId;\n\n    /**\n     * 用户编号\n     */\n    private Long userId;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/logger/dto/OperateLogRespDTO.java",
    "content": "package co.yixiang.yshop.module.system.api.logger.dto;\n\nimport com.fhs.core.trans.anno.Trans;\nimport com.fhs.core.trans.constant.TransType;\nimport com.fhs.core.trans.vo.VO;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\n\n/**\n * 系统操作日志 Resp DTO\n *\n * @author HUIHUI\n */\n@Data\npublic class OperateLogRespDTO implements VO {\n\n    /**\n     * 日志编号\n     */\n    private Long id;\n    /**\n     * 链路追踪编号\n     */\n    private String traceId;\n    /**\n     * 用户编号\n     */\n    @Trans(type = TransType.RPC, targetClassName = \"co.yixiang.yshop.module.system.dal.dataobject.user.AdminUserDO\",\n            fields = \"nickname\", ref = \"userName\")\n    private Long userId;\n    /**\n     * 用户名称\n     */\n    private String userName;\n    /**\n     * 用户类型\n     */\n    private Integer userType;\n    /**\n     * 操作模块类型\n     */\n    private String type;\n    /**\n     * 操作名\n     */\n    private String subType;\n    /**\n     * 操作模块业务编号\n     */\n    private Long bizId;\n    /**\n     * 操作内容\n     */\n    private String action;\n    /**\n     * 拓展字段\n     */\n    private String extra;\n\n    /**\n     * 请求方法名\n     */\n    private String requestMethod;\n    /**\n     * 请求地址\n     */\n    private String requestUrl;\n    /**\n     * 用户 IP\n     */\n    private String userIp;\n    /**\n     * 浏览器 UA\n     */\n    private String userAgent;\n\n    /**\n     * 创建时间\n     */\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/mail/MailSendApi.java",
    "content": "package co.yixiang.yshop.module.system.api.mail;\n\nimport co.yixiang.yshop.module.system.api.mail.dto.MailSendSingleToUserReqDTO;\n\nimport jakarta.validation.Valid;\n\n/**\n * 邮箱发送 API 接口\n *\n * @author yshop\n */\npublic interface MailSendApi {\n\n    /**\n     * 发送单条邮箱给 Admin 用户\n     *\n     * 在 mail 为空时，使用 userId 加载对应 Admin 的邮箱\n     *\n     * @param reqDTO 发送请求\n     * @return 发送日志编号\n     */\n    Long sendSingleMailToAdmin(@Valid MailSendSingleToUserReqDTO reqDTO);\n\n    /**\n     * 发送单条邮箱给 Member 用户\n     *\n     * 在 mail 为空时，使用 userId 加载对应 Member 的邮箱\n     *\n     * @param reqDTO 发送请求\n     * @return 发送日志编号\n     */\n    Long sendSingleMailToMember(@Valid MailSendSingleToUserReqDTO reqDTO);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/mail/dto/MailSendSingleToUserReqDTO.java",
    "content": "package co.yixiang.yshop.module.system.api.mail.dto;\n\nimport lombok.Data;\n\nimport jakarta.validation.constraints.Email;\nimport jakarta.validation.constraints.NotNull;\nimport java.util.Map;\n\n/**\n * 邮件发送 Request DTO\n *\n * @author wangjingqi\n */\n@Data\npublic class MailSendSingleToUserReqDTO {\n\n    /**\n     * 用户编号\n     */\n    private Long userId;\n    /**\n     * 邮箱\n     */\n    @Email\n    private String mail;\n\n    /**\n     * 邮件模板编号\n     */\n    @NotNull(message = \"邮件模板编号不能为空\")\n    private String templateCode;\n    /**\n     * 邮件模板参数\n     */\n    private Map<String, Object> templateParams;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/notify/NotifyMessageSendApi.java",
    "content": "package co.yixiang.yshop.module.system.api.notify;\n\nimport co.yixiang.yshop.module.system.api.notify.dto.NotifySendSingleToUserReqDTO;\n\nimport jakarta.validation.Valid;\n\n/**\n * 站内信发送 API 接口\n *\n * @author xrcoder\n */\npublic interface NotifyMessageSendApi {\n\n    /**\n     * 发送单条站内信给 Admin 用户\n     *\n     * @param reqDTO 发送请求\n     * @return 发送消息 ID\n     */\n    Long sendSingleMessageToAdmin(@Valid NotifySendSingleToUserReqDTO reqDTO);\n\n    /**\n     * 发送单条站内信给 Member 用户\n     *\n     * @param reqDTO 发送请求\n     * @return 发送消息 ID\n     */\n    Long sendSingleMessageToMember(@Valid NotifySendSingleToUserReqDTO reqDTO);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/notify/dto/NotifySendSingleToUserReqDTO.java",
    "content": "package co.yixiang.yshop.module.system.api.notify.dto;\n\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\nimport java.util.Map;\n\n/**\n * 站内信发送给 Admin 或者 Member 用户\n *\n * @author xrcoder\n */\n@Data\npublic class NotifySendSingleToUserReqDTO {\n\n    /**\n     * 用户编号\n     */\n    @NotNull(message = \"用户编号不能为空\")\n    private Long userId;\n\n    /**\n     * 站内信模板编号\n     */\n    @NotEmpty(message = \"站内信模板编号不能为空\")\n    private String templateCode;\n\n    /**\n     * 站内信模板参数\n     */\n    private Map<String, Object> templateParams;\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/notify/dto/NotifyTemplateReqDTO.java",
    "content": "package co.yixiang.yshop.module.system.api.notify.dto;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.validation.InEnum;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\n\n@Data\npublic class NotifyTemplateReqDTO {\n\n    @NotEmpty(message = \"模版名称不能为空\")\n    private String name;\n\n    @NotNull(message = \"模版编码不能为空\")\n    private String code;\n\n    @NotNull(message = \"模版类型不能为空\")\n    private Integer type;\n\n    @NotEmpty(message = \"发送人名称不能为空\")\n    private String nickname;\n\n    @NotEmpty(message = \"模版内容不能为空\")\n    private String content;\n\n    @NotNull(message = \"状态不能为空\")\n    @InEnum(value = CommonStatusEnum.class, message = \"状态必须是 {value}\")\n    private Integer status;\n\n    private String remark;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/oauth2/OAuth2TokenApi.java",
    "content": "package co.yixiang.yshop.module.system.api.oauth2;\n\nimport co.yixiang.yshop.module.system.api.oauth2.dto.OAuth2AccessTokenCheckRespDTO;\nimport co.yixiang.yshop.module.system.api.oauth2.dto.OAuth2AccessTokenCreateReqDTO;\nimport co.yixiang.yshop.module.system.api.oauth2.dto.OAuth2AccessTokenRespDTO;\n\nimport jakarta.validation.Valid;\n\n/**\n * OAuth2.0 Token API 接口\n *\n * @author yshop\n */\npublic interface OAuth2TokenApi {\n\n    /**\n     * 创建访问令牌\n     *\n     * @param reqDTO 访问令牌的创建信息\n     * @return 访问令牌的信息\n     */\n    OAuth2AccessTokenRespDTO createAccessToken(@Valid OAuth2AccessTokenCreateReqDTO reqDTO);\n\n    /**\n     * 校验访问令牌\n     *\n     * @param accessToken 访问令牌\n     * @return 访问令牌的信息\n     */\n    OAuth2AccessTokenCheckRespDTO checkAccessToken(String accessToken);\n\n    /**\n     * 移除访问令牌\n     *\n     * @param accessToken 访问令牌\n     * @return 访问令牌的信息\n     */\n    OAuth2AccessTokenRespDTO removeAccessToken(String accessToken);\n\n    /**\n     * 刷新访问令牌\n     *\n     * @param refreshToken 刷新令牌\n     * @param clientId 客户端编号\n     * @return 访问令牌的信息\n     */\n    OAuth2AccessTokenRespDTO refreshAccessToken(String refreshToken, String clientId);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/oauth2/dto/OAuth2AccessTokenCheckRespDTO.java",
    "content": "package co.yixiang.yshop.module.system.api.oauth2.dto;\n\nimport lombok.Data;\n\nimport java.io.Serializable;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * OAuth2.0 访问令牌的校验 Response DTO\n *\n * @author yshop\n */\n@Data\npublic class OAuth2AccessTokenCheckRespDTO implements Serializable {\n\n    /**\n     * 用户编号\n     */\n    private Long userId;\n    /**\n     * 用户类型\n     */\n    private Integer userType;\n    /**\n     * 用户信息\n     */\n    private Map<String, String> userInfo;\n    /**\n     * 租户编号\n     */\n    private Long tenantId;\n    /**\n     * 授权范围的数组\n     */\n    private List<String> scopes;\n\n    private Long shopId;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/oauth2/dto/OAuth2AccessTokenCreateReqDTO.java",
    "content": "package co.yixiang.yshop.module.system.api.oauth2.dto;\n\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.common.validation.InEnum;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotNull;\nimport java.io.Serializable;\nimport java.util.List;\n\n/**\n * OAuth2.0 访问令牌创建 Request DTO\n *\n * @author yshop\n */\n@Data\npublic class OAuth2AccessTokenCreateReqDTO implements Serializable {\n\n    /**\n     * 用户编号\n     */\n    @NotNull(message = \"用户编号不能为空\")\n    private Long userId;\n    /**\n     * 用户类型\n     */\n    @NotNull(message = \"用户类型不能为空\")\n    @InEnum(value = UserTypeEnum.class, message = \"用户类型必须是 {value}\")\n    private Integer userType;\n    /**\n     * 客户端编号\n     */\n    @NotNull(message = \"客户端编号不能为空\")\n    private String clientId;\n    /**\n     * 授权范围\n     */\n    private List<String> scopes;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/oauth2/dto/OAuth2AccessTokenRespDTO.java",
    "content": "package co.yixiang.yshop.module.system.api.oauth2.dto;\n\nimport lombok.Data;\nimport lombok.experimental.Accessors;\n\nimport java.io.Serializable;\nimport java.time.LocalDateTime;\n\n/**\n * OAuth2.0 访问令牌的信息 Response DTO\n *\n * @author yshop\n */\n@Data\n@Accessors(chain = true)\npublic class OAuth2AccessTokenRespDTO implements Serializable {\n\n    /**\n     * 访问令牌\n     */\n    private String accessToken;\n    /**\n     * 刷新令牌\n     */\n    private String refreshToken;\n    /**\n     * 用户编号\n     */\n    private Long userId;\n    /**\n     * 用户类型\n     */\n    private Integer userType;\n    /**\n     * 过期时间\n     */\n    private LocalDateTime expiresTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/permission/PermissionApi.java",
    "content": "package co.yixiang.yshop.module.system.api.permission;\n\nimport co.yixiang.yshop.module.system.api.permission.dto.DeptDataPermissionRespDTO;\n\nimport java.util.Collection;\nimport java.util.Set;\n\n/**\n * 权限 API 接口\n *\n * @author yshop\n */\npublic interface PermissionApi {\n\n    /**\n     * 获得拥有多个角色的用户编号集合\n     *\n     * @param roleIds 角色编号集合\n     * @return 用户编号集合\n     */\n    Set<Long> getUserRoleIdListByRoleIds(Collection<Long> roleIds);\n\n    /**\n     * 判断是否有权限，任一一个即可\n     *\n     * @param userId 用户编号\n     * @param permissions 权限\n     * @return 是否\n     */\n    boolean hasAnyPermissions(Long userId, String... permissions);\n\n    /**\n     * 判断是否有角色，任一一个即可\n     *\n     * @param userId 用户编号\n     * @param roles 角色数组\n     * @return 是否\n     */\n    boolean hasAnyRoles(Long userId, String... roles);\n\n    /**\n     * 获得登陆用户的部门数据权限\n     *\n     * @param userId 用户编号\n     * @return 部门数据权限\n     */\n    DeptDataPermissionRespDTO getDeptDataPermission(Long userId);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/permission/RoleApi.java",
    "content": "package co.yixiang.yshop.module.system.api.permission;\n\nimport java.util.Collection;\n\n/**\n * 角色 API 接口\n *\n * @author yshop\n */\npublic interface RoleApi {\n\n    /**\n     * 校验角色们是否有效。如下情况，视为无效：\n     * 1. 角色编号不存在\n     * 2. 角色被禁用\n     *\n     * @param ids 角色编号数组\n     */\n    void validRoleList(Collection<Long> ids);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/permission/dto/DeptDataPermissionRespDTO.java",
    "content": "package co.yixiang.yshop.module.system.api.permission.dto;\n\nimport lombok.Data;\n\nimport java.util.HashSet;\nimport java.util.Set;\n\n/**\n * 部门的数据权限 Response DTO\n *\n * @author yshop\n */\n@Data\npublic class DeptDataPermissionRespDTO {\n\n    /**\n     * 是否可查看全部数据\n     */\n    private Boolean all;\n    /**\n     * 是否可查看自己的数据\n     */\n    private Boolean self;\n    /**\n     * 可查看的部门编号数组\n     */\n    private Set<Long> deptIds;\n\n    public DeptDataPermissionRespDTO() {\n        this.all = false;\n        this.self = false;\n        this.deptIds = new HashSet<>();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/sms/SmsCodeApi.java",
    "content": "package co.yixiang.yshop.module.system.api.sms;\n\nimport co.yixiang.yshop.framework.common.exception.ServiceException;\nimport co.yixiang.yshop.module.system.api.sms.dto.code.SmsCodeValidateReqDTO;\nimport co.yixiang.yshop.module.system.api.sms.dto.code.SmsCodeSendReqDTO;\nimport co.yixiang.yshop.module.system.api.sms.dto.code.SmsCodeUseReqDTO;\n\nimport jakarta.validation.Valid;\n\n/**\n * 短信验证码 API 接口\n *\n * @author yshop\n */\npublic interface SmsCodeApi {\n\n    /**\n     * 创建短信验证码，并进行发送\n     *\n     * @param reqDTO 发送请求\n     */\n    void sendSmsCode(@Valid SmsCodeSendReqDTO reqDTO);\n\n    /**\n     * 验证短信验证码，并进行使用\n     * 如果正确，则将验证码标记成已使用\n     * 如果错误，则抛出 {@link ServiceException} 异常\n     *\n     * @param reqDTO 使用请求\n     */\n    void useSmsCode(@Valid SmsCodeUseReqDTO reqDTO);\n\n    /**\n     * 检查验证码是否有效\n     *\n     * @param reqDTO 校验请求\n     */\n    void validateSmsCode(@Valid SmsCodeValidateReqDTO reqDTO);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/sms/SmsSendApi.java",
    "content": "package co.yixiang.yshop.module.system.api.sms;\n\nimport co.yixiang.yshop.module.system.api.sms.dto.send.SmsSendSingleToUserReqDTO;\n\nimport jakarta.validation.Valid;\n\n/**\n * 短信发送 API 接口\n *\n * @author yshop\n */\npublic interface SmsSendApi {\n\n    /**\n     * 发送单条短信给 Admin 用户\n     *\n     * 在 mobile 为空时，使用 userId 加载对应 Admin 的手机号\n     *\n     * @param reqDTO 发送请求\n     * @return 发送日志编号\n     */\n    Long sendSingleSmsToAdmin(@Valid SmsSendSingleToUserReqDTO reqDTO);\n\n    /**\n     * 发送单条短信给 Member 用户\n     *\n     * 在 mobile 为空时，使用 userId 加载对应 Member 的手机号\n     *\n     * @param reqDTO 发送请求\n     * @return 发送日志编号\n     */\n    Long sendSingleSmsToMember(@Valid SmsSendSingleToUserReqDTO reqDTO);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/sms/dto/code/SmsCodeSendReqDTO.java",
    "content": "package co.yixiang.yshop.module.system.api.sms.dto.code;\n\nimport co.yixiang.yshop.framework.common.validation.InEnum;\nimport co.yixiang.yshop.framework.common.validation.Mobile;\nimport co.yixiang.yshop.module.system.enums.sms.SmsSceneEnum;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\n\n/**\n * 短信验证码的发送 Request DTO\n *\n * @author yshop\n */\n@Data\npublic class SmsCodeSendReqDTO {\n\n    /**\n     * 手机号\n     */\n    @Mobile\n    @NotEmpty(message = \"手机号不能为空\")\n    private String mobile;\n    /**\n     * 发送场景\n     */\n    @NotNull(message = \"发送场景不能为空\")\n    @InEnum(SmsSceneEnum.class)\n    private Integer scene;\n    /**\n     * 发送 IP\n     */\n    @NotEmpty(message = \"发送 IP 不能为空\")\n    private String createIp;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/sms/dto/code/SmsCodeUseReqDTO.java",
    "content": "package co.yixiang.yshop.module.system.api.sms.dto.code;\n\nimport co.yixiang.yshop.framework.common.validation.InEnum;\nimport co.yixiang.yshop.framework.common.validation.Mobile;\nimport co.yixiang.yshop.module.system.enums.sms.SmsSceneEnum;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\n\n/**\n * 短信验证码的使用 Request DTO\n *\n * @author yshop\n */\n@Data\npublic class SmsCodeUseReqDTO {\n\n    /**\n     * 手机号\n     */\n    @Mobile\n    @NotEmpty(message = \"手机号不能为空\")\n    private String mobile;\n    /**\n     * 发送场景\n     */\n    @NotNull(message = \"发送场景不能为空\")\n    @InEnum(SmsSceneEnum.class)\n    private Integer scene;\n    /**\n     * 验证码\n     */\n    @NotEmpty(message = \"验证码\")\n    private String code;\n    /**\n     * 使用 IP\n     */\n    @NotEmpty(message = \"使用 IP 不能为空\")\n    private String usedIp;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/sms/dto/code/SmsCodeValidateReqDTO.java",
    "content": "package co.yixiang.yshop.module.system.api.sms.dto.code;\n\nimport co.yixiang.yshop.framework.common.validation.InEnum;\nimport co.yixiang.yshop.framework.common.validation.Mobile;\nimport co.yixiang.yshop.module.system.enums.sms.SmsSceneEnum;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\n\n/**\n * 短信验证码的校验 Request DTO\n *\n * @author yshop\n */\n@Data\npublic class SmsCodeValidateReqDTO {\n\n    /**\n     * 手机号\n     */\n    @Mobile\n    @NotEmpty(message = \"手机号不能为空\")\n    private String mobile;\n    /**\n     * 发送场景\n     */\n    @NotNull(message = \"发送场景不能为空\")\n    @InEnum(SmsSceneEnum.class)\n    private Integer scene;\n    /**\n     * 验证码\n     */\n    @NotEmpty(message = \"验证码\")\n    private String code;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/sms/dto/send/SmsSendSingleToUserReqDTO.java",
    "content": "package co.yixiang.yshop.module.system.api.sms.dto.send;\n\nimport co.yixiang.yshop.framework.common.validation.Mobile;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotEmpty;\nimport java.util.Map;\n\n/**\n * 短信发送给 Admin 或者 Member 用户\n *\n * @author yshop\n */\n@Data\npublic class SmsSendSingleToUserReqDTO {\n\n    /**\n     * 用户编号\n     */\n    private Long userId;\n    /**\n     * 手机号\n     */\n    @Mobile\n    private String mobile;\n    /**\n     * 短信模板编号\n     */\n    @NotEmpty(message = \"短信模板编号不能为空\")\n    private String templateCode;\n    /**\n     * 短信模板参数\n     */\n    private Map<String, Object> templateParams;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/social/SocialClientApi.java",
    "content": "package co.yixiang.yshop.module.system.api.social;\n\nimport co.yixiang.yshop.module.system.api.social.dto.SocialWxJsapiSignatureRespDTO;\nimport co.yixiang.yshop.module.system.api.social.dto.SocialWxPhoneNumberInfoRespDTO;\nimport co.yixiang.yshop.module.system.enums.social.SocialTypeEnum;\n\n/**\n * 社交应用的 API 接口\n *\n * @author yshop\n */\npublic interface SocialClientApi {\n\n    /**\n     * 获得社交平台的授权 URL\n     *\n     * @param socialType 社交平台的类型 {@link SocialTypeEnum}\n     * @param userType 用户类型\n     * @param redirectUri 重定向 URL\n     * @return 社交平台的授权 URL\n     */\n    String getAuthorizeUrl(Integer socialType, Integer userType, String redirectUri);\n\n    /**\n     * 创建微信公众号 JS SDK 初始化所需的签名\n     *\n     * @param userType 用户类型\n     * @param url 访问的 URL 地址\n     * @return 签名\n     */\n    SocialWxJsapiSignatureRespDTO createWxMpJsapiSignature(Integer userType, String url);\n\n    /**\n     * 获得微信小程序的手机信息\n     *\n     * @param userType 用户类型\n     * @param phoneCode 手机授权码\n     * @return 手机信息\n     */\n    SocialWxPhoneNumberInfoRespDTO getWxMaPhoneNumberInfo(Integer userType, String phoneCode);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/social/SocialUserApi.java",
    "content": "package co.yixiang.yshop.module.system.api.social;\n\nimport co.yixiang.yshop.framework.common.exception.ServiceException;\nimport co.yixiang.yshop.module.system.api.social.dto.SocialUserBindReqDTO;\nimport co.yixiang.yshop.module.system.api.social.dto.SocialUserRespDTO;\nimport co.yixiang.yshop.module.system.api.social.dto.SocialUserUnbindReqDTO;\n\nimport jakarta.validation.Valid;\n\n/**\n * 社交用户的 API 接口\n *\n * @author yshop\n */\npublic interface SocialUserApi {\n\n    /**\n     * 绑定社交用户\n     *\n     * @param reqDTO 绑定信息\n     * @return 社交用户 openid\n     */\n    String bindSocialUser(@Valid SocialUserBindReqDTO reqDTO);\n\n    /**\n     * 取消绑定社交用户\n     *\n     * @param reqDTO 解绑\n     */\n    void unbindSocialUser(@Valid SocialUserUnbindReqDTO reqDTO);\n\n    /**\n     * 获得社交用户，基于 userId\n     *\n     * @param userType 用户类型\n     * @param userId 用户编号\n     * @param socialType 社交平台的类型\n     * @return 社交用户\n     */\n    SocialUserRespDTO getSocialUserByUserId(Integer userType, Long userId, Integer socialType);\n\n    /**\n     * 获得社交用户\n     *\n     * 在认证信息不正确的情况下，也会抛出 {@link ServiceException} 业务异常\n     *\n     * @param userType 用户类型\n     * @param socialType 社交平台的类型\n     * @param code 授权码\n     * @param state state\n     * @return 社交用户\n     */\n    SocialUserRespDTO getSocialUserByCode(Integer userType, Integer socialType, String code, String state);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/social/dto/SocialUserBindReqDTO.java",
    "content": "package co.yixiang.yshop.module.system.api.social.dto;\n\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.common.validation.InEnum;\nimport co.yixiang.yshop.module.system.enums.social.SocialTypeEnum;\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\n\n/**\n * 取消绑定社交用户 Request DTO\n *\n * @author yshop\n */\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\npublic class SocialUserBindReqDTO {\n\n    /**\n     * 用户编号\n     */\n    @NotNull(message = \"用户编号不能为空\")\n    private Long userId;\n    /**\n     * 用户类型\n     */\n    @InEnum(UserTypeEnum.class)\n    @NotNull(message = \"用户类型不能为空\")\n    private Integer userType;\n\n    /**\n     * 社交平台的类型\n     */\n    @InEnum(SocialTypeEnum.class)\n    @NotNull(message = \"社交平台的类型不能为空\")\n    private Integer socialType;\n    /**\n     * 授权码\n     */\n    @NotEmpty(message = \"授权码不能为空\")\n    private String code;\n    /**\n     * state\n     */\n    @NotNull(message = \"state 不能为空\")\n    private String state;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/social/dto/SocialUserRespDTO.java",
    "content": "package co.yixiang.yshop.module.system.api.social.dto;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\n/**\n * 社交用户 Response DTO\n *\n * @author yshop\n */\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\npublic class SocialUserRespDTO {\n\n    /**\n     * 社交用户的 openid\n     */\n    private String openid;\n    /**\n     * 社交用户的昵称\n     */\n    private String nickname;\n    /**\n     * 社交用户的头像\n     */\n    private String avatar;\n\n    /**\n     * 关联的用户编号\n     */\n    private Long userId;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/social/dto/SocialUserUnbindReqDTO.java",
    "content": "package co.yixiang.yshop.module.system.api.social.dto;\n\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.common.validation.InEnum;\nimport co.yixiang.yshop.module.system.enums.social.SocialTypeEnum;\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\nimport lombok.NoArgsConstructor;\n\n/**\n * 社交绑定 Request DTO，使用 code 授权码\n *\n * @author yshop\n */\n@Data\n@AllArgsConstructor\n@NoArgsConstructor\npublic class SocialUserUnbindReqDTO {\n\n    /**\n     * 用户编号\n     */\n    @NotNull(message = \"用户编号不能为空\")\n    private Long userId;\n    /**\n     * 用户类型\n     */\n    @InEnum(UserTypeEnum.class)\n    @NotNull(message = \"用户类型不能为空\")\n    private Integer userType;\n\n    /**\n     * 社交平台的类型\n     */\n    @InEnum(SocialTypeEnum.class)\n    @NotNull(message = \"社交平台的类型不能为空\")\n    private Integer socialType;\n\n    /**\n     * 社交平台的 openid\n     */\n    @NotEmpty(message = \"社交平台的 openid 不能为空\")\n    private String openid;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/social/dto/SocialWxJsapiSignatureRespDTO.java",
    "content": "package co.yixiang.yshop.module.system.api.social.dto;\n\nimport lombok.Data;\n\n/**\n * 微信公众号 JSAPI 签名 Response DTO\n *\n * @author yshop\n */\n@Data\npublic class SocialWxJsapiSignatureRespDTO {\n\n    /**\n     * 微信公众号的 appId\n     */\n    private String appId;\n    /**\n     * 匿名串\n     */\n    private String nonceStr;\n    /**\n     * 时间戳\n     */\n    private Long timestamp;\n    /**\n     * URL\n     */\n    private String url;\n    /**\n     * 签名\n     */\n    private String signature;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/social/dto/SocialWxPhoneNumberInfoRespDTO.java",
    "content": "package co.yixiang.yshop.module.system.api.social.dto;\n\nimport lombok.Data;\n\n/**\n * 微信小程序的手机信息 Response DTO\n *\n * @author yshop\n */\n@Data\npublic class SocialWxPhoneNumberInfoRespDTO {\n\n    /**\n     * 用户绑定的手机号（国外手机号会有区号）\n     */\n    private String phoneNumber;\n\n    /**\n     * 没有区号的手机号\n     */\n    private String purePhoneNumber;\n    /**\n     * 区号\n     */\n    private String countryCode;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/tenant/TenantApi.java",
    "content": "package co.yixiang.yshop.module.system.api.tenant;\n\nimport java.util.List;\n\n/**\n * 多租户的 API 接口\n *\n * @author yshop\n */\npublic interface TenantApi {\n\n    /**\n     * 获得所有租户\n     *\n     * @return 租户编号数组\n     */\n    List<Long> getTenantIdList();\n\n    /**\n     * 校验租户是否合法\n     *\n     * @param id 租户编号\n     */\n    void validateTenant(Long id);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/user/AdminUserApi.java",
    "content": "package co.yixiang.yshop.module.system.api.user;\n\nimport co.yixiang.yshop.framework.common.util.collection.CollectionUtils;\nimport co.yixiang.yshop.module.system.api.user.dto.AdminUserRespDTO;\n\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Admin 用户 API 接口\n *\n * @author yshop\n */\npublic interface AdminUserApi {\n\n    /**\n     * 通过用户 ID 查询用户\n     *\n     * @param id 用户ID\n     * @return 用户对象信息\n     */\n    AdminUserRespDTO getUser(Long id);\n\n    /**\n     * 通过用户 ID 查询用户下属\n     *\n     * @param id 用户编号\n     * @return 用户下属用户列表\n     */\n    List<AdminUserRespDTO> getUserListBySubordinate(Long id);\n\n    /**\n     * 通过用户 ID 查询用户们\n     *\n     * @param ids 用户 ID 们\n     * @return 用户对象信息\n     */\n    List<AdminUserRespDTO> getUserList(Collection<Long> ids);\n\n    /**\n     * 获得指定部门的用户数组\n     *\n     * @param deptIds 部门数组\n     * @return 用户数组\n     */\n    List<AdminUserRespDTO> getUserListByDeptIds(Collection<Long> deptIds);\n\n    /**\n     * 获得指定岗位的用户数组\n     *\n     * @param postIds 岗位数组\n     * @return 用户数组\n     */\n    List<AdminUserRespDTO> getUserListByPostIds(Collection<Long> postIds);\n\n    /**\n     * 获得用户 Map\n     *\n     * @param ids 用户编号数组\n     * @return 用户 Map\n     */\n    default Map<Long, AdminUserRespDTO> getUserMap(Collection<Long> ids) {\n        List<AdminUserRespDTO> users = getUserList(ids);\n        return CollectionUtils.convertMap(users, AdminUserRespDTO::getId);\n    }\n\n    /**\n     * 校验用户是否有效。如下情况，视为无效：\n     * 1. 用户编号不存在\n     * 2. 用户被禁用\n     *\n     * @param id 用户编号\n     */\n    default void validateUser(Long id) {\n        validateUserList(Collections.singleton(id));\n    }\n\n    /**\n     * 校验用户们是否有效。如下情况，视为无效：\n     * 1. 用户编号不存在\n     * 2. 用户被禁用\n     *\n     * @param ids 用户编号数组\n     */\n    void validateUserList(Collection<Long> ids);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/api/user/dto/AdminUserRespDTO.java",
    "content": "package co.yixiang.yshop.module.system.api.user.dto;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport lombok.Data;\n\nimport java.util.Set;\n\n/**\n * Admin 用户 Response DTO\n *\n * @author yshop\n */\n@Data\npublic class AdminUserRespDTO {\n\n    /**\n     * 用户ID\n     */\n    private Long id;\n    /**\n     * 用户昵称\n     */\n    private String nickname;\n    /**\n     * 帐号状态\n     *\n     * 枚举 {@link CommonStatusEnum}\n     */\n    private Integer status;\n\n    /**\n     * 部门ID\n     */\n    private Long deptId;\n    /**\n     * 岗位编号数组\n     */\n    private Set<Long> postIds;\n    /**\n     * 手机号码\n     */\n    private String mobile;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/enums/DictTypeConstants.java",
    "content": "package co.yixiang.yshop.module.system.enums;\n\n/**\n * System 字典类型的枚举类\n *\n * @author yshop\n */\npublic interface DictTypeConstants {\n\n    String USER_TYPE = \"user_type\"; // 用户类型\n    String COMMON_STATUS = \"common_status\"; // 系统状态\n\n    // ========== SYSTEM 模块 ==========\n\n    String USER_SEX = \"system_user_sex\"; // 用户性别\n\n    String LOGIN_TYPE = \"system_login_type\"; // 登录日志的类型\n    String LOGIN_RESULT = \"system_login_result\"; // 登录结果\n\n    String ERROR_CODE_TYPE = \"system_error_code_type\"; // 错误码的类型枚举\n\n    String SMS_CHANNEL_CODE = \"system_sms_channel_code\"; // 短信渠道编码\n    String SMS_TEMPLATE_TYPE = \"system_sms_template_type\"; // 短信模板类型\n    String SMS_SEND_STATUS = \"system_sms_send_status\"; // 短信发送状态\n    String SMS_RECEIVE_STATUS = \"system_sms_receive_status\"; // 短信接收状态\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/enums/ErrorCodeConstants.java",
    "content": "package co.yixiang.yshop.module.system.enums;\n\nimport co.yixiang.yshop.framework.common.exception.ErrorCode;\n\n/**\n * System 错误码枚举类\n *\n * system 系统，使用 1-002-000-000 段\n */\npublic interface ErrorCodeConstants {\n\n    // ========== AUTH 模块 1-002-000-000 ==========\n    ErrorCode AUTH_LOGIN_BAD_CREDENTIALS = new ErrorCode(1_002_000_000, \"登录失败，账号密码不正确\");\n    ErrorCode AUTH_LOGIN_USER_DISABLED = new ErrorCode(1_002_000_001, \"登录失败，账号被禁用\");\n    ErrorCode AUTH_LOGIN_CAPTCHA_CODE_ERROR = new ErrorCode(1_002_000_004, \"验证码不正确，原因：{}\");\n    ErrorCode AUTH_THIRD_LOGIN_NOT_BIND = new ErrorCode(1_002_000_005, \"未绑定账号，需要进行绑定\");\n    ErrorCode AUTH_TOKEN_EXPIRED = new ErrorCode(1_002_000_006, \"Token 已经过期\");\n    ErrorCode AUTH_MOBILE_NOT_EXISTS = new ErrorCode(1_002_000_007, \"手机号不存在\");\n\n    // ========== 菜单模块 1-002-001-000 ==========\n    ErrorCode MENU_NAME_DUPLICATE = new ErrorCode(1_002_001_000, \"已经存在该名字的菜单\");\n    ErrorCode MENU_PARENT_NOT_EXISTS = new ErrorCode(1_002_001_001, \"父菜单不存在\");\n    ErrorCode MENU_PARENT_ERROR = new ErrorCode(1_002_001_002, \"不能设置自己为父菜单\");\n    ErrorCode MENU_NOT_EXISTS = new ErrorCode(1_002_001_003, \"菜单不存在\");\n    ErrorCode MENU_EXISTS_CHILDREN = new ErrorCode(1_002_001_004, \"存在子菜单，无法删除\");\n    ErrorCode MENU_PARENT_NOT_DIR_OR_MENU = new ErrorCode(1_002_001_005, \"父菜单的类型必须是目录或者菜单\");\n\n    // ========== 角色模块 1-002-002-000 ==========\n    ErrorCode ROLE_NOT_EXISTS = new ErrorCode(1_002_002_000, \"角色不存在\");\n    ErrorCode ROLE_NAME_DUPLICATE = new ErrorCode(1_002_002_001, \"已经存在名为【{}】的角色\");\n    ErrorCode ROLE_CODE_DUPLICATE = new ErrorCode(1_002_002_002, \"已经存在编码为【{}】的角色\");\n    ErrorCode ROLE_CAN_NOT_UPDATE_SYSTEM_TYPE_ROLE = new ErrorCode(1_002_002_003, \"不能操作类型为系统内置的角色\");\n    ErrorCode ROLE_IS_DISABLE = new ErrorCode(1_002_002_004, \"名字为【{}】的角色已被禁用\");\n    ErrorCode ROLE_ADMIN_CODE_ERROR = new ErrorCode(1_002_002_005, \"编码【{}】不能使用\");\n\n    // ========== 用户模块 1-002-003-000 ==========\n    ErrorCode USER_USERNAME_EXISTS = new ErrorCode(1_002_003_000, \"用户账号已经存在\");\n    ErrorCode USER_MOBILE_EXISTS = new ErrorCode(1_002_003_001, \"手机号已经存在\");\n    ErrorCode USER_EMAIL_EXISTS = new ErrorCode(1_002_003_002, \"邮箱已经存在\");\n    ErrorCode USER_NOT_EXISTS = new ErrorCode(1_002_003_003, \"用户不存在\");\n    ErrorCode USER_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_002_003_004, \"导入用户数据不能为空！\");\n    ErrorCode USER_PASSWORD_FAILED = new ErrorCode(1_002_003_005, \"用户密码校验失败\");\n    ErrorCode USER_IS_DISABLE = new ErrorCode(1_002_003_006, \"名字为【{}】的用户已被禁用\");\n    ErrorCode USER_COUNT_MAX = new ErrorCode(1_002_003_008, \"创建用户失败，原因：超过租户最大租户配额({})！\");\n\n    // ========== 部门模块 1-002-004-000 ==========\n    ErrorCode DEPT_NAME_DUPLICATE = new ErrorCode(1_002_004_000, \"已经存在该名字的部门\");\n    ErrorCode DEPT_PARENT_NOT_EXITS = new ErrorCode(1_002_004_001,\"父级部门不存在\");\n    ErrorCode DEPT_NOT_FOUND = new ErrorCode(1_002_004_002, \"当前部门不存在\");\n    ErrorCode DEPT_EXITS_CHILDREN = new ErrorCode(1_002_004_003, \"存在子部门，无法删除\");\n    ErrorCode DEPT_PARENT_ERROR = new ErrorCode(1_002_004_004, \"不能设置自己为父部门\");\n    ErrorCode DEPT_EXISTS_USER = new ErrorCode(1_002_004_005, \"部门中存在员工，无法删除\");\n    ErrorCode DEPT_NOT_ENABLE = new ErrorCode(1_002_004_006, \"部门({})不处于开启状态，不允许选择\");\n    ErrorCode DEPT_PARENT_IS_CHILD = new ErrorCode(1_002_004_007, \"不能设置自己的子部门为父部门\");\n\n    // ========== 岗位模块 1-002-005-000 ==========\n    ErrorCode POST_NOT_FOUND = new ErrorCode(1_002_005_000, \"当前岗位不存在\");\n    ErrorCode POST_NOT_ENABLE = new ErrorCode(1_002_005_001, \"岗位({}) 不处于开启状态，不允许选择\");\n    ErrorCode POST_NAME_DUPLICATE = new ErrorCode(1_002_005_002, \"已经存在该名字的岗位\");\n    ErrorCode POST_CODE_DUPLICATE = new ErrorCode(1_002_005_003, \"已经存在该标识的岗位\");\n\n    // ========== 字典类型 1-002-006-000 ==========\n    ErrorCode DICT_TYPE_NOT_EXISTS = new ErrorCode(1_002_006_001, \"当前字典类型不存在\");\n    ErrorCode DICT_TYPE_NOT_ENABLE = new ErrorCode(1_002_006_002, \"字典类型不处于开启状态，不允许选择\");\n    ErrorCode DICT_TYPE_NAME_DUPLICATE = new ErrorCode(1_002_006_003, \"已经存在该名字的字典类型\");\n    ErrorCode DICT_TYPE_TYPE_DUPLICATE = new ErrorCode(1_002_006_004, \"已经存在该类型的字典类型\");\n    ErrorCode DICT_TYPE_HAS_CHILDREN = new ErrorCode(1_002_006_005, \"无法删除，该字典类型还有字典数据\");\n\n    // ========== 字典数据 1-002-007-000 ==========\n    ErrorCode DICT_DATA_NOT_EXISTS = new ErrorCode(1_002_007_001, \"当前字典数据不存在\");\n    ErrorCode DICT_DATA_NOT_ENABLE = new ErrorCode(1_002_007_002, \"字典数据({})不处于开启状态，不允许选择\");\n    ErrorCode DICT_DATA_VALUE_DUPLICATE = new ErrorCode(1_002_007_003, \"已经存在该值的字典数据\");\n\n    // ========== 通知公告 1-002-008-000 ==========\n    ErrorCode NOTICE_NOT_FOUND = new ErrorCode(1_002_008_001, \"当前通知公告不存在\");\n\n    // ========== 短信渠道 1-002-011-000 ==========\n    ErrorCode SMS_CHANNEL_NOT_EXISTS = new ErrorCode(1_002_011_000, \"短信渠道不存在\");\n    ErrorCode SMS_CHANNEL_DISABLE = new ErrorCode(1_002_011_001, \"短信渠道不处于开启状态，不允许选择\");\n    ErrorCode SMS_CHANNEL_HAS_CHILDREN = new ErrorCode(1_002_011_002, \"无法删除，该短信渠道还有短信模板\");\n\n    // ========== 短信模板 1-002-012-000 ==========\n    ErrorCode SMS_TEMPLATE_NOT_EXISTS = new ErrorCode(1_002_012_000, \"短信模板不存在\");\n    ErrorCode SMS_TEMPLATE_CODE_DUPLICATE = new ErrorCode(1_002_012_001, \"已经存在编码为【{}】的短信模板\");\n    ErrorCode SMS_TEMPLATE_API_ERROR = new ErrorCode(1_002_012_002, \"短信 API 模板调用失败，原因是：{}\");\n    ErrorCode SMS_TEMPLATE_API_AUDIT_CHECKING = new ErrorCode(1_002_012_003, \"短信 API 模版无法使用，原因：审批中\");\n    ErrorCode SMS_TEMPLATE_API_AUDIT_FAIL = new ErrorCode(1_002_012_004, \"短信 API 模版无法使用，原因：审批不通过，{}\");\n    ErrorCode SMS_TEMPLATE_API_NOT_FOUND = new ErrorCode(1_002_012_005, \"短信 API 模版无法使用，原因：模版不存在\");\n\n    // ========== 短信发送 1-002-013-000 ==========\n    ErrorCode SMS_SEND_MOBILE_NOT_EXISTS = new ErrorCode(1_002_013_000, \"手机号不存在\");\n    ErrorCode SMS_SEND_MOBILE_TEMPLATE_PARAM_MISS = new ErrorCode(1_002_013_001, \"模板参数({})缺失\");\n    ErrorCode SMS_SEND_TEMPLATE_NOT_EXISTS = new ErrorCode(1_002_013_002, \"短信模板不存在\");\n\n    // ========== 短信验证码 1-002-014-000 ==========\n    ErrorCode SMS_CODE_NOT_FOUND = new ErrorCode(1_002_014_000, \"验证码不存在\");\n    ErrorCode SMS_CODE_EXPIRED = new ErrorCode(1_002_014_001, \"验证码已过期\");\n    ErrorCode SMS_CODE_USED = new ErrorCode(1_002_014_002, \"验证码已使用\");\n    ErrorCode SMS_CODE_NOT_CORRECT = new ErrorCode(1_002_014_003, \"验证码不正确\");\n    ErrorCode SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY = new ErrorCode(1_002_014_004, \"超过每日短信发送数量\");\n    ErrorCode SMS_CODE_SEND_TOO_FAST = new ErrorCode(1_002_014_005, \"短信发送过于频繁\");\n    ErrorCode SMS_CODE_IS_EXISTS = new ErrorCode(1_002_014_006, \"手机号已被使用\");\n    ErrorCode SMS_CODE_IS_UNUSED = new ErrorCode(1_002_014_007, \"验证码未被使用\");\n\n    // ========== 租户信息 1-002-015-000 ==========\n    ErrorCode TENANT_NOT_EXISTS = new ErrorCode(1_002_015_000, \"租户不存在\");\n    ErrorCode TENANT_DISABLE = new ErrorCode(1_002_015_001, \"名字为【{}】的租户已被禁用\");\n    ErrorCode TENANT_EXPIRE = new ErrorCode(1_002_015_002, \"名字为【{}】的租户已过期\");\n    ErrorCode TENANT_CAN_NOT_UPDATE_SYSTEM = new ErrorCode(1_002_015_003, \"系统租户不能进行修改、删除等操作！\");\n    ErrorCode TENANT_NAME_DUPLICATE = new ErrorCode(1_002_015_004, \"名字为【{}】的租户已存在\");\n    ErrorCode TENANT_WEBSITE_DUPLICATE = new ErrorCode(1_002_015_005, \"域名为【{}】的租户已存在\");\n\n    // ========== 租户套餐 1-002-016-000 ==========\n    ErrorCode TENANT_PACKAGE_NOT_EXISTS = new ErrorCode(1_002_016_000, \"租户套餐不存在\");\n    ErrorCode TENANT_PACKAGE_USED = new ErrorCode(1_002_016_001, \"租户正在使用该套餐，请给租户重新设置套餐后再尝试删除\");\n    ErrorCode TENANT_PACKAGE_DISABLE = new ErrorCode(1_002_016_002, \"名字为【{}】的租户套餐已被禁用\");\n\n    // ========== 社交用户 1-002-018-000 ==========\n    ErrorCode SOCIAL_USER_AUTH_FAILURE = new ErrorCode(1_002_018_000, \"社交授权失败，原因是：{}\");\n    ErrorCode SOCIAL_USER_NOT_FOUND = new ErrorCode(1_002_018_001, \"社交授权失败，找不到对应的用户\");\n\n    ErrorCode SOCIAL_CLIENT_WEIXIN_MINI_APP_PHONE_CODE_ERROR = new ErrorCode(1_002_018_200, \"获得手机号失败\");\n    ErrorCode SOCIAL_CLIENT_NOT_EXISTS = new ErrorCode(1_002_018_201, \"社交客户端不存在\");\n    ErrorCode SOCIAL_CLIENT_UNIQUE = new ErrorCode(1_002_018_202, \"社交客户端已存在配置\");\n\n    // ========== OAuth2 客户端 1-002-020-000 =========\n    ErrorCode OAUTH2_CLIENT_NOT_EXISTS = new ErrorCode(1_002_020_000, \"OAuth2 客户端不存在\");\n    ErrorCode OAUTH2_CLIENT_EXISTS = new ErrorCode(1_002_020_001, \"OAuth2 客户端编号已存在\");\n    ErrorCode OAUTH2_CLIENT_DISABLE = new ErrorCode(1_002_020_002, \"OAuth2 客户端已禁用\");\n    ErrorCode OAUTH2_CLIENT_AUTHORIZED_GRANT_TYPE_NOT_EXISTS = new ErrorCode(1_002_020_003, \"不支持该授权类型\");\n    ErrorCode OAUTH2_CLIENT_SCOPE_OVER = new ErrorCode(1_002_020_004, \"授权范围过大\");\n    ErrorCode OAUTH2_CLIENT_REDIRECT_URI_NOT_MATCH = new ErrorCode(1_002_020_005, \"无效 redirect_uri: {}\");\n    ErrorCode OAUTH2_CLIENT_CLIENT_SECRET_ERROR = new ErrorCode(1_002_020_006, \"无效 client_secret: {}\");\n\n    // ========== OAuth2 授权 1-002-021-000 =========\n    ErrorCode OAUTH2_GRANT_CLIENT_ID_MISMATCH = new ErrorCode(1_002_021_000, \"client_id 不匹配\");\n    ErrorCode OAUTH2_GRANT_REDIRECT_URI_MISMATCH = new ErrorCode(1_002_021_001, \"redirect_uri 不匹配\");\n    ErrorCode OAUTH2_GRANT_STATE_MISMATCH = new ErrorCode(1_002_021_002, \"state 不匹配\");\n    ErrorCode OAUTH2_GRANT_CODE_NOT_EXISTS = new ErrorCode(1_002_021_003, \"code 不存在\");\n\n    // ========== OAuth2 授权 1-002-022-000 =========\n    ErrorCode OAUTH2_CODE_NOT_EXISTS = new ErrorCode(1_002_022_000, \"code 不存在\");\n    ErrorCode OAUTH2_CODE_EXPIRE = new ErrorCode(1_002_022_001, \"code 已过期\");\n\n    // ========== 邮箱账号 1-002-023-000 ==========\n    ErrorCode MAIL_ACCOUNT_NOT_EXISTS = new ErrorCode(1_002_023_000, \"邮箱账号不存在\");\n    ErrorCode MAIL_ACCOUNT_RELATE_TEMPLATE_EXISTS = new ErrorCode(1_002_023_001, \"无法删除，该邮箱账号还有邮件模板\");\n\n    // ========== 邮件模版 1-002-024-000 ==========\n    ErrorCode MAIL_TEMPLATE_NOT_EXISTS = new ErrorCode(1_002_024_000, \"邮件模版不存在\");\n    ErrorCode MAIL_TEMPLATE_CODE_EXISTS = new ErrorCode(1_002_024_001, \"邮件模版 code({}) 已存在\");\n\n    // ========== 邮件发送 1-002-025-000 ==========\n    ErrorCode MAIL_SEND_TEMPLATE_PARAM_MISS = new ErrorCode(1_002_025_000, \"模板参数({})缺失\");\n    ErrorCode MAIL_SEND_MAIL_NOT_EXISTS = new ErrorCode(1_002_025_001, \"邮箱不存在\");\n\n    // ========== 站内信模版 1-002-026-000 ==========\n    ErrorCode NOTIFY_TEMPLATE_NOT_EXISTS = new ErrorCode(1_002_026_000, \"站内信模版不存在\");\n    ErrorCode NOTIFY_TEMPLATE_CODE_DUPLICATE = new ErrorCode(1_002_026_001, \"已经存在编码为【{}】的站内信模板\");\n\n    // ========== 站内信模版 1-002-027-000 ==========\n\n    // ========== 站内信发送 1-002-028-000 ==========\n    ErrorCode NOTIFY_SEND_TEMPLATE_PARAM_MISS = new ErrorCode(1_002_028_000, \"模板参数({})缺失\");\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/enums/LogRecordConstants.java",
    "content": "package co.yixiang.yshop.module.system.enums;\n\n/**\n * System 操作日志枚举\n * 目的：统一管理，也减少 Service 里各种“复杂”字符串\n *\n * @author yshop\n */\npublic interface LogRecordConstants {\n\n    // ======================= SYSTEM_USER 用户 =======================\n\n    String SYSTEM_USER_TYPE = \"SYSTEM 用户\";\n    String SYSTEM_USER_CREATE_SUB_TYPE = \"创建用户\";\n    String SYSTEM_USER_CREATE_SUCCESS = \"创建了用户【{{#user.nickname}}】\";\n    String SYSTEM_USER_UPDATE_SUB_TYPE = \"更新用户\";\n    String SYSTEM_USER_UPDATE_SUCCESS = \"更新了用户【{{#user.nickname}}】: {_DIFF{#updateReqVO}}\";\n    String SYSTEM_USER_DELETE_SUB_TYPE = \"删除用户\";\n    String SYSTEM_USER_DELETE_SUCCESS = \"删除了用户【{{#user.nickname}}】\";\n    String SYSTEM_USER_UPDATE_PASSWORD_SUB_TYPE = \"重置用户密码\";\n    String SYSTEM_USER_UPDATE_PASSWORD_SUCCESS = \"将用户【{{#user.nickname}}】的密码从【{{#user.password}}】重置为【{{#newPassword}}】\";\n\n    // ======================= SYSTEM_ROLE 角色 =======================\n\n    String SYSTEM_ROLE_TYPE = \"SYSTEM 角色\";\n    String SYSTEM_ROLE_CREATE_SUB_TYPE = \"创建角色\";\n    String SYSTEM_ROLE_CREATE_SUCCESS = \"创建了角色【{{#role.name}}】\";\n    String SYSTEM_ROLE_UPDATE_SUB_TYPE = \"更新角色\";\n    String SYSTEM_ROLE_UPDATE_SUCCESS = \"更新了角色【{{#role.name}}】: {_DIFF{#updateReqVO}}\";\n    String SYSTEM_ROLE_DELETE_SUB_TYPE = \"删除角色\";\n    String SYSTEM_ROLE_DELETE_SUCCESS = \"删除了角色【{{#role.name}}】\";\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/enums/common/SexEnum.java",
    "content": "package co.yixiang.yshop.module.system.enums.common;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * 性别的枚举值\n *\n * @author yshop\n */\n@Getter\n@AllArgsConstructor\npublic enum SexEnum {\n    /** 男 */\n    MALE(1),\n    /** 女 */\n    FEMALE(2),\n    /* 未知 */\n    UNKNOWN(0);\n\n    /**\n     * 性别\n     */\n    private final Integer sex;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/enums/logger/LoginLogTypeEnum.java",
    "content": "package co.yixiang.yshop.module.system.enums.logger;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * 登录日志的类型枚举\n */\n@Getter\n@AllArgsConstructor\npublic enum LoginLogTypeEnum {\n\n    LOGIN_USERNAME(100), // 使用账号登录\n    LOGIN_SOCIAL(101), // 使用社交登录\n    LOGIN_MOBILE(103), // 使用手机登陆\n    LOGIN_SMS(104), // 使用短信登陆\n\n    LOGOUT_SELF(200),  // 自己主动登出\n    LOGOUT_DELETE(202), // 强制退出\n    ;\n\n    /**\n     * 日志类型\n     */\n    private final Integer type;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/enums/logger/LoginResultEnum.java",
    "content": "package co.yixiang.yshop.module.system.enums.logger;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * 登录结果的枚举类\n */\n@Getter\n@AllArgsConstructor\npublic enum LoginResultEnum {\n\n    SUCCESS(0), // 成功\n    BAD_CREDENTIALS(10), // 账号或密码不正确\n    USER_DISABLED(20), // 用户被禁用\n    CAPTCHA_NOT_FOUND(30), // 图片验证码不存在\n    CAPTCHA_CODE_ERROR(31), // 图片验证码不正确\n\n    ;\n\n    /**\n     * 结果\n     */\n    private final Integer result;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/enums/mail/MailSendStatusEnum.java",
    "content": "package co.yixiang.yshop.module.system.enums.mail;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * 邮件的发送状态枚举\n *\n * @author wangjingyi\n * @since 2022/4/10 13:39\n */\n@Getter\n@AllArgsConstructor\npublic enum MailSendStatusEnum {\n\n    INIT(0), // 初始化\n    SUCCESS(10), // 发送成功\n    FAILURE(20), // 发送失败\n    IGNORE(30), // 忽略，即不发送\n    ;\n\n    private final int status;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/enums/notice/NoticeTypeEnum.java",
    "content": "package co.yixiang.yshop.module.system.enums.notice;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * 通知类型\n *\n * @author yshop\n */\n@Getter\n@AllArgsConstructor\npublic enum NoticeTypeEnum {\n\n    NOTICE(1),\n    ANNOUNCEMENT(2);\n\n    /**\n     * 类型\n     */\n    private final Integer type;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/enums/notify/NotifyTemplateTypeEnum.java",
    "content": "package co.yixiang.yshop.module.system.enums.notify;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * 通知模板类型枚举\n *\n * @author HUIHUI\n */\n@Getter\n@AllArgsConstructor\npublic enum NotifyTemplateTypeEnum {\n\n    /**\n     * 系统消息\n     */\n    SYSTEM_MESSAGE(2),\n    /**\n     * 通知消息\n     */\n    NOTIFICATION_MESSAGE(1);\n\n    private final Integer type;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/enums/oauth2/OAuth2ClientConstants.java",
    "content": "package co.yixiang.yshop.module.system.enums.oauth2;\n\n/**\n * OAuth2.0 客户端的通用枚举\n *\n * @author yshop\n */\npublic interface OAuth2ClientConstants {\n\n    String CLIENT_ID_DEFAULT = \"default\";\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/enums/oauth2/OAuth2GrantTypeEnum.java",
    "content": "package co.yixiang.yshop.module.system.enums.oauth2;\n\nimport cn.hutool.core.util.ArrayUtil;\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * OAuth2 授权类型（模式）的枚举\n *\n * @author yshop\n */\n@AllArgsConstructor\n@Getter\npublic enum OAuth2GrantTypeEnum {\n\n    PASSWORD(\"password\"), // 密码模式\n    AUTHORIZATION_CODE(\"authorization_code\"), // 授权码模式\n    IMPLICIT(\"implicit\"), // 简化模式\n    CLIENT_CREDENTIALS(\"client_credentials\"), // 客户端模式\n    REFRESH_TOKEN(\"refresh_token\"), // 刷新模式\n    ;\n\n    private final String grantType;\n\n    public static OAuth2GrantTypeEnum getByGranType(String grantType) {\n        return ArrayUtil.firstMatch(o -> o.getGrantType().equals(grantType), values());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/enums/permission/DataScopeEnum.java",
    "content": "package co.yixiang.yshop.module.system.enums.permission;\n\nimport co.yixiang.yshop.framework.common.core.IntArrayValuable;\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\nimport java.util.Arrays;\n\n/**\n * 数据范围枚举类\n *\n * 用于实现数据级别的权限\n *\n * @author yshop\n */\n@Getter\n@AllArgsConstructor\npublic enum DataScopeEnum implements IntArrayValuable {\n\n    ALL(1), // 全部数据权限\n\n    DEPT_CUSTOM(2), // 指定部门数据权限\n    DEPT_ONLY(3), // 部门数据权限\n    DEPT_AND_CHILD(4), // 部门及以下数据权限\n\n    SELF(5); // 仅本人数据权限\n\n    /**\n     * 范围\n     */\n    private final Integer scope;\n\n    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(DataScopeEnum::getScope).toArray();\n\n    @Override\n    public int[] array() {\n        return ARRAYS;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/enums/permission/MenuTypeEnum.java",
    "content": "package co.yixiang.yshop.module.system.enums.permission;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * 菜单类型枚举类\n *\n * @author yshop\n */\n@Getter\n@AllArgsConstructor\npublic enum MenuTypeEnum {\n\n    DIR(1), // 目录\n    MENU(2), // 菜单\n    BUTTON(3) // 按钮\n    ;\n\n    /**\n     * 类型\n     */\n    private final Integer type;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/enums/permission/RoleCodeEnum.java",
    "content": "package co.yixiang.yshop.module.system.enums.permission;\n\nimport co.yixiang.yshop.framework.common.util.object.ObjectUtils;\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * 角色标识枚举\n */\n@Getter\n@AllArgsConstructor\npublic enum RoleCodeEnum {\n\n    SUPER_ADMIN(\"super_admin\", \"超级管理员\"),\n    TENANT_ADMIN(\"tenant_admin\", \"租户管理员\"),\n    CRM_ADMIN(\"crm_admin\", \"CRM 管理员\"); // CRM 系统专用\n    ;\n\n    /**\n     * 角色编码\n     */\n    private final String code;\n    /**\n     * 名字\n     */\n    private final String name;\n\n    public static boolean isSuperAdmin(String code) {\n        return ObjectUtils.equalsAny(code, SUPER_ADMIN.getCode());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/enums/permission/RoleTypeEnum.java",
    "content": "package co.yixiang.yshop.module.system.enums.permission;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n@Getter\n@AllArgsConstructor\npublic enum RoleTypeEnum {\n\n    /**\n     * 内置角色\n     */\n    SYSTEM(1),\n    /**\n     * 自定义角色\n     */\n    CUSTOM(2);\n\n    private final Integer type;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/enums/sms/SmsReceiveStatusEnum.java",
    "content": "package co.yixiang.yshop.module.system.enums.sms;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * 短信的接收状态枚举\n *\n * @author yshop\n * @date 2021/2/1 13:39\n */\n@Getter\n@AllArgsConstructor\npublic enum SmsReceiveStatusEnum {\n\n    INIT(0), // 初始化\n    SUCCESS(10), // 接收成功\n    FAILURE(20), // 接收失败\n    ;\n\n    private final int status;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/enums/sms/SmsSceneEnum.java",
    "content": "package co.yixiang.yshop.module.system.enums.sms;\n\nimport cn.hutool.core.util.ArrayUtil;\nimport co.yixiang.yshop.framework.common.core.IntArrayValuable;\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\nimport java.util.Arrays;\n\n/**\n * 用户短信验证码发送场景的枚举\n *\n * @author yshop\n */\n@Getter\n@AllArgsConstructor\npublic enum SmsSceneEnum implements IntArrayValuable {\n\n    MEMBER_LOGIN(1, \"user-sms-login\", \"会员用户 - 手机号登陆\"),\n    MEMBER_UPDATE_MOBILE(2, \"user-update-mobile\", \"会员用户 - 修改手机\"),\n    MEMBER_UPDATE_PASSWORD(3, \"user-update-password\", \"会员用户 - 修改密码\"),\n    MEMBER_RESET_PASSWORD(4, \"user-reset-password\", \"会员用户 - 忘记密码\"),\n\n    ADMIN_MEMBER_LOGIN(21, \"admin-sms-login\", \"后台用户 - 手机号登录\");\n\n    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(SmsSceneEnum::getScene).toArray();\n\n    /**\n     * 验证场景的编号\n     */\n    private final Integer scene;\n    /**\n     * 模版编码\n     */\n    private final String templateCode;\n    /**\n     * 描述\n     */\n    private final String description;\n\n    @Override\n    public int[] array() {\n        return ARRAYS;\n    }\n\n    public static SmsSceneEnum getCodeByScene(Integer scene) {\n        return ArrayUtil.firstMatch(sceneEnum -> sceneEnum.getScene().equals(scene),\n                values());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/enums/sms/SmsSendStatusEnum.java",
    "content": "package co.yixiang.yshop.module.system.enums.sms;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * 短信的发送状态枚举\n *\n * @author zzf\n * @date 2021/2/1 13:39\n */\n@Getter\n@AllArgsConstructor\npublic enum SmsSendStatusEnum {\n\n    INIT(0), // 初始化\n    SUCCESS(10), // 发送成功\n    FAILURE(20), // 发送失败\n    IGNORE(30), // 忽略，即不发送\n    ;\n\n    private final int status;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/enums/sms/SmsTemplateTypeEnum.java",
    "content": "package co.yixiang.yshop.module.system.enums.sms;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * 短信的模板类型枚举\n *\n * @author yshop\n */\n@Getter\n@AllArgsConstructor\npublic enum SmsTemplateTypeEnum {\n\n    VERIFICATION_CODE(1), // 验证码\n    NOTICE(2), // 通知\n    PROMOTION(3), // 营销\n    ;\n\n    /**\n     * 类型\n     */\n    private final int type;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-api/src/main/java/co/yixiang/yshop/module/system/enums/social/SocialTypeEnum.java",
    "content": "package co.yixiang.yshop.module.system.enums.social;\n\nimport cn.hutool.core.util.ArrayUtil;\nimport co.yixiang.yshop.framework.common.core.IntArrayValuable;\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\nimport java.util.Arrays;\n\n/**\n * 社交平台的类型枚举\n *\n * @author yshop\n */\n@Getter\n@AllArgsConstructor\npublic enum SocialTypeEnum implements IntArrayValuable {\n\n    /**\n     * Gitee\n     *\n     * @see <a href=\"https://gitee.com/api/v5/oauth_doc#/\">接入文档</a>\n     */\n    GITEE(10, \"GITEE\"),\n    /**\n     * 钉钉\n     *\n     * @see <a href=\"https://developers.dingtalk.com/document/app/obtain-identity-credentials\">接入文档</a>\n     */\n    DINGTALK(20, \"DINGTALK\"),\n\n    /**\n     * 企业微信\n     *\n     * @see <a href=\"https://xkcoding.com/2019/08/06/use-justauth-integration-wechat-enterprise.html\">接入文档</a>\n     */\n    WECHAT_ENTERPRISE(30, \"WECHAT_ENTERPRISE\"),\n    /**\n     * 微信公众平台 - 移动端 H5\n     *\n     * @see <a href=\"https://www.cnblogs.com/juewuzhe/p/11905461.html\">接入文档</a>\n     */\n    WECHAT_MP(31, \"WECHAT_MP\"),\n    /**\n     * 微信开放平台 - 网站应用 PC 端扫码授权登录\n     *\n     * @see <a href=\"https://justauth.wiki/guide/oauth/wechat_open/#_2-申请开发者资质认证\">接入文档</a>\n     */\n    WECHAT_OPEN(32, \"WECHAT_OPEN\"),\n    /**\n     * 微信小程序\n     *\n     * @see <a href=\"https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/login.html\">接入文档</a>\n     */\n    WECHAT_MINI_APP(34, \"WECHAT_MINI_APP\"),\n    ;\n\n    public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(SocialTypeEnum::getType).toArray();\n\n    /**\n     * 类型\n     */\n    private final Integer type;\n    /**\n     * 类型的标识\n     */\n    private final String source;\n\n    @Override\n    public int[] array() {\n        return ARRAYS;\n    }\n\n    public static SocialTypeEnum valueOfType(Integer type) {\n        return ArrayUtil.firstMatch(o -> o.getType().equals(type), values());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>co.yixiang.boot</groupId>\n        <artifactId>yshop-module-system</artifactId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n    <artifactId>yshop-module-system-biz</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>\n        system 模块下，我们放通用业务，支撑上层的核心业务。\n        例如说：用户、部门、权限、数据字典等等\n    </description>\n\n    <dependencies>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-system-api</artifactId>\n            <version>${revision}</version>\n        </dependency>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-infra-api</artifactId>\n            <version>${revision}</version>\n        </dependency>\n\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-store-biz</artifactId>\n            <version>${revision}</version>\n        </dependency>\n\n        <!-- 业务组件 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-biz-data-permission</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-biz-tenant</artifactId>\n        </dependency>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-biz-ip</artifactId>\n        </dependency>\n\n        <!-- Web 相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-security</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-validation</artifactId>\n        </dependency>\n\n        <!-- DB 相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-mybatis</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-redis</artifactId>\n        </dependency>\n\n        <!-- Job 定时任务相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-job</artifactId>\n        </dependency>\n\n        <!-- 消息队列相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-mq</artifactId>\n        </dependency>\n\n        <!-- Test 测试相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-test</artifactId>\n            <scope>test</scope>\n        </dependency>\n\n        <!-- 工具类相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-excel</artifactId>\n        </dependency>\n\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-starter-mail</artifactId>\n        </dependency>\n\n        <!-- 三方云服务相关 -->\n        <dependency>\n            <groupId>com.xingyuv</groupId>\n            <artifactId>spring-boot-starter-justauth</artifactId> <!-- 社交登陆（例如说，个人微信、企业微信等等） -->\n        </dependency>\n\n        <dependency>\n            <groupId>com.github.binarywang</groupId>\n            <artifactId>wx-java-mp-spring-boot-starter</artifactId> <!-- 微信登录（公众号） -->\n        </dependency>\n        <dependency>\n            <groupId>com.github.binarywang</groupId>\n            <artifactId>wx-java-miniapp-spring-boot-starter</artifactId>  <!-- 微信登录（小程序） -->\n        </dependency>\n\n        <dependency>\n            <groupId>com.aliyun</groupId>\n            <artifactId>aliyun-java-sdk-core</artifactId> <!-- 短信（阿里云） -->\n        </dependency>\n        <dependency>\n            <groupId>com.aliyun</groupId>\n            <artifactId>aliyun-java-sdk-dysmsapi</artifactId> <!-- 短信（阿里云） -->\n        </dependency>\n        <dependency>\n            <groupId>com.tencentcloudapi</groupId>\n            <artifactId>tencentcloud-sdk-java-sms</artifactId> <!-- 短信（腾讯云） -->\n        </dependency>\n\n        <dependency>\n            <groupId>com.xingyuv</groupId>\n            <artifactId>spring-boot-starter-captcha-plus</artifactId> <!-- 验证码，一般用于登录使用 -->\n        </dependency>\n\n        <dependency>\n            <groupId>org.dromara.hutool</groupId>\n            <artifactId>hutool-extra</artifactId> <!-- 邮件 -->\n        </dependency>\n\n    </dependencies>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/api/dept/DeptApiImpl.java",
    "content": "package co.yixiang.yshop.module.system.api.dept;\n\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.api.dept.dto.DeptRespDTO;\nimport co.yixiang.yshop.module.system.dal.dataobject.dept.DeptDO;\nimport co.yixiang.yshop.module.system.service.dept.DeptService;\nimport org.springframework.context.annotation.Lazy;\nimport org.springframework.stereotype.Service;\n\nimport jakarta.annotation.Resource;\nimport java.util.Collection;\nimport java.util.List;\n\n/**\n * 部门 API 实现类\n *\n * @author yshop\n */\n@Service\npublic class DeptApiImpl implements DeptApi {\n\n    @Resource\n    private DeptService deptService;\n\n    @Override\n    public DeptRespDTO getDept(Long id) {\n        DeptDO dept = deptService.getDept(id);\n        return BeanUtils.toBean(dept, DeptRespDTO.class);\n    }\n\n    @Override\n    public List<DeptRespDTO> getDeptList(Collection<Long> ids) {\n        List<DeptDO> depts = deptService.getDeptList(ids);\n        return BeanUtils.toBean(depts, DeptRespDTO.class);\n    }\n\n    @Override\n    public void validateDeptList(Collection<Long> ids) {\n        deptService.validateDeptList(ids);\n    }\n\n    @Override\n    public List<DeptRespDTO> getChildDeptList(Long id) {\n        List<DeptDO> childDeptList = deptService.getChildDeptList(id);\n        return BeanUtils.toBean(childDeptList, DeptRespDTO.class);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/api/dept/PostApiImpl.java",
    "content": "package co.yixiang.yshop.module.system.api.dept;\n\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.api.dept.dto.PostRespDTO;\nimport co.yixiang.yshop.module.system.dal.dataobject.dept.PostDO;\nimport co.yixiang.yshop.module.system.service.dept.PostService;\nimport org.springframework.stereotype.Service;\n\nimport jakarta.annotation.Resource;\nimport java.util.Collection;\nimport java.util.List;\n\n/**\n * 岗位 API 实现类\n *\n * @author yshop\n */\n@Service\npublic class PostApiImpl implements PostApi {\n\n    @Resource\n    private PostService postService;\n\n    @Override\n    public void validPostList(Collection<Long> ids) {\n        postService.validatePostList(ids);\n    }\n\n    @Override\n    public List<PostRespDTO> getPostList(Collection<Long> ids) {\n        List<PostDO> list = postService.getPostList(ids);\n        return BeanUtils.toBean(list, PostRespDTO.class);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/api/dict/DictDataApiImpl.java",
    "content": "package co.yixiang.yshop.module.system.api.dict;\n\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.api.dict.dto.DictDataRespDTO;\nimport co.yixiang.yshop.module.system.dal.dataobject.dict.DictDataDO;\nimport co.yixiang.yshop.module.system.service.dict.DictDataService;\nimport jakarta.annotation.Resource;\nimport org.springframework.stereotype.Service;\n\nimport java.util.Collection;\nimport java.util.List;\n\n/**\n * 字典数据 API 实现类\n *\n * @author yshop\n */\n@Service\npublic class DictDataApiImpl implements DictDataApi {\n\n    @Resource\n    private DictDataService dictDataService;\n\n    @Override\n    public void validateDictDataList(String dictType, Collection<String> values) {\n        dictDataService.validateDictDataList(dictType, values);\n    }\n\n    @Override\n    public DictDataRespDTO getDictData(String dictType, String value) {\n        DictDataDO dictData = dictDataService.getDictData(dictType, value);\n        return BeanUtils.toBean(dictData, DictDataRespDTO.class);\n    }\n\n    @Override\n    public DictDataRespDTO parseDictData(String dictType, String label) {\n        DictDataDO dictData = dictDataService.parseDictData(dictType, label);\n        return BeanUtils.toBean(dictData, DictDataRespDTO.class);\n    }\n\n    @Override\n    public List<DictDataRespDTO> getDictDataList(String dictType) {\n        List<DictDataDO> list = dictDataService.getDictDataListByDictType(dictType);\n        return BeanUtils.toBean(list, DictDataRespDTO.class);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/api/logger/LoginLogApiImpl.java",
    "content": "package co.yixiang.yshop.module.system.api.logger;\n\nimport co.yixiang.yshop.module.system.api.logger.dto.LoginLogCreateReqDTO;\nimport co.yixiang.yshop.module.system.service.logger.LoginLogService;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\n\n/**\n * 登录日志的 API 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class LoginLogApiImpl implements LoginLogApi {\n\n    @Resource\n    private LoginLogService loginLogService;\n\n    @Override\n    public void createLoginLog(LoginLogCreateReqDTO reqDTO) {\n        loginLogService.createLoginLog(reqDTO);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/api/logger/OperateLogApiImpl.java",
    "content": "package co.yixiang.yshop.module.system.api.logger;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.api.logger.dto.OperateLogCreateReqDTO;\nimport co.yixiang.yshop.module.system.api.logger.dto.OperateLogPageReqDTO;\nimport co.yixiang.yshop.module.system.api.logger.dto.OperateLogRespDTO;\nimport co.yixiang.yshop.module.system.dal.dataobject.logger.OperateLogDO;\nimport co.yixiang.yshop.module.system.service.logger.OperateLogService;\nimport com.fhs.core.trans.anno.TransMethodResult;\nimport jakarta.annotation.Resource;\nimport org.springframework.scheduling.annotation.Async;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\n/**\n * 操作日志 API 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class OperateLogApiImpl implements OperateLogApi {\n\n    @Resource\n    private OperateLogService operateLogService;\n\n    @Override\n    @Async\n    public void createOperateLog(OperateLogCreateReqDTO createReqDTO) {\n        operateLogService.createOperateLog(createReqDTO);\n    }\n\n    @Override\n    @TransMethodResult\n    public PageResult<OperateLogRespDTO> getOperateLogPage(OperateLogPageReqDTO pageReqVO) {\n        PageResult<OperateLogDO> operateLogPage = operateLogService.getOperateLogPage(pageReqVO);\n        return BeanUtils.toBean(operateLogPage, OperateLogRespDTO.class);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/api/mail/MailSendApiImpl.java",
    "content": "package co.yixiang.yshop.module.system.api.mail;\n\nimport co.yixiang.yshop.module.system.api.mail.dto.MailSendSingleToUserReqDTO;\nimport co.yixiang.yshop.module.system.service.mail.MailSendService;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\n\n/**\n * 邮件发送 API 实现类\n *\n * @author wangjingyi\n */\n@Service\n@Validated\npublic class MailSendApiImpl implements MailSendApi {\n\n    @Resource\n    private MailSendService mailSendService;\n\n    @Override\n    public Long sendSingleMailToAdmin(MailSendSingleToUserReqDTO reqDTO) {\n        return mailSendService.sendSingleMailToAdmin(reqDTO.getMail(), reqDTO.getUserId(),\n                reqDTO.getTemplateCode(), reqDTO.getTemplateParams());\n    }\n\n    @Override\n    public Long sendSingleMailToMember(MailSendSingleToUserReqDTO reqDTO) {\n        return mailSendService.sendSingleMailToMember(reqDTO.getMail(), reqDTO.getUserId(),\n                reqDTO.getTemplateCode(), reqDTO.getTemplateParams());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/api/notify/NotifyMessageSendApiImpl.java",
    "content": "package co.yixiang.yshop.module.system.api.notify;\n\nimport co.yixiang.yshop.module.system.api.notify.dto.NotifySendSingleToUserReqDTO;\nimport co.yixiang.yshop.module.system.service.notify.NotifySendService;\nimport org.springframework.stereotype.Service;\n\nimport jakarta.annotation.Resource;\n\n/**\n * 站内信发送 API 实现类\n *\n * @author xrcoder\n */\n@Service\npublic class NotifyMessageSendApiImpl implements NotifyMessageSendApi {\n\n    @Resource\n    private NotifySendService notifySendService;\n\n    @Override\n    public Long sendSingleMessageToAdmin(NotifySendSingleToUserReqDTO reqDTO) {\n        return notifySendService.sendSingleNotifyToAdmin(reqDTO.getUserId(),\n                reqDTO.getTemplateCode(), reqDTO.getTemplateParams());\n    }\n\n    @Override\n    public Long sendSingleMessageToMember(NotifySendSingleToUserReqDTO reqDTO) {\n        return notifySendService.sendSingleNotifyToMember(reqDTO.getUserId(),\n                reqDTO.getTemplateCode(), reqDTO.getTemplateParams());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/api/oauth2/OAuth2TokenApiImpl.java",
    "content": "package co.yixiang.yshop.module.system.api.oauth2;\n\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.api.oauth2.dto.OAuth2AccessTokenCheckRespDTO;\nimport co.yixiang.yshop.module.system.api.oauth2.dto.OAuth2AccessTokenCreateReqDTO;\nimport co.yixiang.yshop.module.system.api.oauth2.dto.OAuth2AccessTokenRespDTO;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;\nimport co.yixiang.yshop.module.system.service.oauth2.OAuth2TokenService;\nimport org.springframework.stereotype.Service;\n\nimport jakarta.annotation.Resource;\n\n/**\n * OAuth2.0 Token API 实现类\n *\n * @author yshop\n */\n@Service\npublic class OAuth2TokenApiImpl implements OAuth2TokenApi {\n\n    @Resource\n    private OAuth2TokenService oauth2TokenService;\n\n    @Override\n    public OAuth2AccessTokenRespDTO createAccessToken(OAuth2AccessTokenCreateReqDTO reqDTO) {\n        OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.createAccessToken(\n                reqDTO.getUserId(), reqDTO.getUserType(), reqDTO.getClientId(), reqDTO.getScopes());\n        return BeanUtils.toBean(accessTokenDO, OAuth2AccessTokenRespDTO.class);\n    }\n\n    @Override\n    public OAuth2AccessTokenCheckRespDTO checkAccessToken(String accessToken) {\n        OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.checkAccessToken(accessToken);\n        return BeanUtils.toBean(accessTokenDO, OAuth2AccessTokenCheckRespDTO.class);\n    }\n\n    @Override\n    public OAuth2AccessTokenRespDTO removeAccessToken(String accessToken) {\n        OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.removeAccessToken(accessToken);\n        return BeanUtils.toBean(accessTokenDO, OAuth2AccessTokenRespDTO.class);\n    }\n\n    @Override\n    public OAuth2AccessTokenRespDTO refreshAccessToken(String refreshToken, String clientId) {\n        OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.refreshAccessToken(refreshToken, clientId);\n        return BeanUtils.toBean(accessTokenDO, OAuth2AccessTokenRespDTO.class);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/api/permission/PermissionApiImpl.java",
    "content": "package co.yixiang.yshop.module.system.api.permission;\n\nimport co.yixiang.yshop.module.system.api.permission.dto.DeptDataPermissionRespDTO;\nimport co.yixiang.yshop.module.system.service.permission.PermissionService;\nimport org.springframework.stereotype.Service;\n\nimport jakarta.annotation.Resource;\nimport java.util.Collection;\nimport java.util.Set;\n\n/**\n * 权限 API 实现类\n *\n * @author yshop\n */\n@Service\npublic class PermissionApiImpl implements PermissionApi {\n\n    @Resource\n    private PermissionService permissionService;\n\n    @Override\n    public Set<Long> getUserRoleIdListByRoleIds(Collection<Long> roleIds) {\n        return permissionService.getUserRoleIdListByRoleId(roleIds);\n    }\n\n    @Override\n    public boolean hasAnyPermissions(Long userId, String... permissions) {\n        return permissionService.hasAnyPermissions(userId, permissions);\n    }\n\n    @Override\n    public boolean hasAnyRoles(Long userId, String... roles) {\n        return permissionService.hasAnyRoles(userId, roles);\n    }\n\n    @Override\n    public DeptDataPermissionRespDTO getDeptDataPermission(Long userId) {\n        return permissionService.getDeptDataPermission(userId);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/api/permission/RoleApiImpl.java",
    "content": "package co.yixiang.yshop.module.system.api.permission;\n\nimport co.yixiang.yshop.module.system.service.permission.RoleService;\nimport org.springframework.stereotype.Service;\n\nimport jakarta.annotation.Resource;\nimport java.util.Collection;\n\n/**\n * 角色 API 实现类\n *\n * @author yshop\n */\n@Service\npublic class RoleApiImpl implements RoleApi {\n\n    @Resource\n    private RoleService roleService;\n\n    @Override\n    public void validRoleList(Collection<Long> ids) {\n        roleService.validateRoleList(ids);\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/api/sms/SmsCodeApiImpl.java",
    "content": "package co.yixiang.yshop.module.system.api.sms;\n\nimport co.yixiang.yshop.module.system.api.sms.dto.code.SmsCodeValidateReqDTO;\nimport co.yixiang.yshop.module.system.api.sms.dto.code.SmsCodeSendReqDTO;\nimport co.yixiang.yshop.module.system.api.sms.dto.code.SmsCodeUseReqDTO;\nimport co.yixiang.yshop.module.system.service.sms.SmsCodeService;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\n\n/**\n * 短信验证码 API 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class SmsCodeApiImpl implements SmsCodeApi {\n\n    @Resource\n    private SmsCodeService smsCodeService;\n\n    @Override\n    public void sendSmsCode(SmsCodeSendReqDTO reqDTO) {\n        smsCodeService.sendSmsCode(reqDTO);\n    }\n\n    @Override\n    public void useSmsCode(SmsCodeUseReqDTO reqDTO) {\n        smsCodeService.useSmsCode(reqDTO);\n    }\n\n    @Override\n    public void validateSmsCode(SmsCodeValidateReqDTO reqDTO) {\n        smsCodeService.validateSmsCode(reqDTO);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/api/sms/SmsSendApiImpl.java",
    "content": "package co.yixiang.yshop.module.system.api.sms;\n\nimport co.yixiang.yshop.module.system.api.sms.dto.send.SmsSendSingleToUserReqDTO;\nimport co.yixiang.yshop.module.system.service.sms.SmsSendService;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\n\n/**\n * 短信发送 API 接口\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class SmsSendApiImpl implements SmsSendApi {\n\n    @Resource\n    private SmsSendService smsSendService;\n\n    @Override\n    public Long sendSingleSmsToAdmin(SmsSendSingleToUserReqDTO reqDTO) {\n        return smsSendService.sendSingleSmsToAdmin(reqDTO.getMobile(), reqDTO.getUserId(),\n                reqDTO.getTemplateCode(), reqDTO.getTemplateParams());\n    }\n\n    @Override\n    public Long sendSingleSmsToMember(SmsSendSingleToUserReqDTO reqDTO) {\n        return smsSendService.sendSingleSmsToMember(reqDTO.getMobile(), reqDTO.getUserId(),\n                reqDTO.getTemplateCode(), reqDTO.getTemplateParams());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/api/social/SocialClientApiImpl.java",
    "content": "package co.yixiang.yshop.module.system.api.social;\n\nimport cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.api.social.dto.SocialWxJsapiSignatureRespDTO;\nimport co.yixiang.yshop.module.system.api.social.dto.SocialWxPhoneNumberInfoRespDTO;\nimport co.yixiang.yshop.module.system.service.social.SocialClientService;\nimport me.chanjar.weixin.common.bean.WxJsapiSignature;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\n\n/**\n * 社交应用的 API 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class SocialClientApiImpl implements SocialClientApi {\n\n    @Resource\n    private SocialClientService socialClientService;\n\n    @Override\n    public String getAuthorizeUrl(Integer socialType, Integer userType, String redirectUri) {\n        return socialClientService.getAuthorizeUrl(socialType, userType, redirectUri);\n    }\n\n    @Override\n    public SocialWxJsapiSignatureRespDTO createWxMpJsapiSignature(Integer userType, String url) {\n        WxJsapiSignature signature = socialClientService.createWxMpJsapiSignature(userType, url);\n        return BeanUtils.toBean(signature, SocialWxJsapiSignatureRespDTO.class);\n    }\n\n    @Override\n    public SocialWxPhoneNumberInfoRespDTO getWxMaPhoneNumberInfo(Integer userType, String phoneCode) {\n        WxMaPhoneNumberInfo info = socialClientService.getWxMaPhoneNumberInfo(userType, phoneCode);\n        return BeanUtils.toBean(info, SocialWxPhoneNumberInfoRespDTO.class);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/api/social/SocialUserApiImpl.java",
    "content": "package co.yixiang.yshop.module.system.api.social;\n\nimport co.yixiang.yshop.module.system.api.social.dto.SocialUserBindReqDTO;\nimport co.yixiang.yshop.module.system.api.social.dto.SocialUserRespDTO;\nimport co.yixiang.yshop.module.system.api.social.dto.SocialUserUnbindReqDTO;\nimport co.yixiang.yshop.module.system.service.social.SocialUserService;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\n\n/**\n * 社交用户的 API 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class SocialUserApiImpl implements SocialUserApi {\n\n    @Resource\n    private SocialUserService socialUserService;\n\n    @Override\n    public String bindSocialUser(SocialUserBindReqDTO reqDTO) {\n        return socialUserService.bindSocialUser(reqDTO);\n    }\n\n    @Override\n    public void unbindSocialUser(SocialUserUnbindReqDTO reqDTO) {\n        socialUserService.unbindSocialUser(reqDTO.getUserId(), reqDTO.getUserType(),\n                reqDTO.getSocialType(), reqDTO.getOpenid());\n    }\n\n    @Override\n    public SocialUserRespDTO getSocialUserByUserId(Integer userType, Long userId, Integer socialType) {\n        return socialUserService.getSocialUserByUserId(userType, userId, socialType);\n    }\n\n    @Override\n    public SocialUserRespDTO getSocialUserByCode(Integer userType, Integer socialType, String code, String state) {\n       return socialUserService.getSocialUserByCode(userType, socialType, code, state);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/api/tenant/TenantApiImpl.java",
    "content": "package co.yixiang.yshop.module.system.api.tenant;\n\nimport co.yixiang.yshop.module.system.service.tenant.TenantService;\nimport org.springframework.stereotype.Service;\n\nimport jakarta.annotation.Resource;\nimport java.util.List;\n\n/**\n * 多租户的 API 实现类\n *\n * @author yshop\n */\n@Service\npublic class TenantApiImpl implements TenantApi {\n\n    @Resource\n    private TenantService tenantService;\n\n    @Override\n    public List<Long> getTenantIdList() {\n        return tenantService.getTenantIdList();\n    }\n\n    @Override\n    public void validateTenant(Long id) {\n        tenantService.validTenant(id);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/api/user/AdminUserApiImpl.java",
    "content": "package co.yixiang.yshop.module.system.api.user;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.util.ObjUtil;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.api.user.dto.AdminUserRespDTO;\nimport co.yixiang.yshop.module.system.dal.dataobject.dept.DeptDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.user.AdminUserDO;\nimport co.yixiang.yshop.module.system.service.dept.DeptService;\nimport co.yixiang.yshop.module.system.service.user.AdminUserService;\nimport jakarta.annotation.Resource;\nimport org.springframework.stereotype.Service;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.util.collection.CollectionUtils.convertSet;\n\n/**\n * Admin 用户 API 实现类\n *\n * @author yshop\n */\n@Service\npublic class AdminUserApiImpl implements AdminUserApi {\n\n    @Resource\n    private AdminUserService userService;\n    @Resource\n    private DeptService deptService;\n\n    @Override\n    public AdminUserRespDTO getUser(Long id) {\n        AdminUserDO user = userService.getUser(id);\n        return BeanUtils.toBean(user, AdminUserRespDTO.class);\n    }\n\n    @Override\n    public List<AdminUserRespDTO> getUserListBySubordinate(Long id) {\n        // 1.1 获取用户负责的部门\n        AdminUserDO user = userService.getUser(id);\n        if (user == null) {\n            return Collections.emptyList();\n        }\n        ArrayList<Long> deptIds = new ArrayList<>();\n        DeptDO dept = deptService.getDept(user.getDeptId());\n        if (dept == null) {\n            return Collections.emptyList();\n        }\n        if (ObjUtil.notEqual(dept.getLeaderUserId(), id)) { // 校验为负责人\n            return Collections.emptyList();\n        }\n        deptIds.add(dept.getId());\n        // 1.2 获取所有子部门\n        List<DeptDO> childDeptList = deptService.getChildDeptList(dept.getId());\n        if (CollUtil.isNotEmpty(childDeptList)) {\n            deptIds.addAll(convertSet(childDeptList, DeptDO::getId));\n        }\n\n        // 2. 获取部门对应的用户信息\n        List<AdminUserDO> users = userService.getUserListByDeptIds(deptIds);\n        users.removeIf(item -> ObjUtil.equal(item.getId(), id)); // 排除自己\n        return BeanUtils.toBean(users, AdminUserRespDTO.class);\n    }\n\n    @Override\n    public List<AdminUserRespDTO> getUserList(Collection<Long> ids) {\n        List<AdminUserDO> users = userService.getUserList(ids);\n        return BeanUtils.toBean(users, AdminUserRespDTO.class);\n    }\n\n    @Override\n    public List<AdminUserRespDTO> getUserListByDeptIds(Collection<Long> deptIds) {\n        List<AdminUserDO> users = userService.getUserListByDeptIds(deptIds);\n        return BeanUtils.toBean(users, AdminUserRespDTO.class);\n    }\n\n    @Override\n    public List<AdminUserRespDTO> getUserListByPostIds(Collection<Long> postIds) {\n        List<AdminUserDO> users = userService.getUserListByPostIds(postIds);\n        return BeanUtils.toBean(users, AdminUserRespDTO.class);\n    }\n\n    @Override\n    public void validateUserList(Collection<Long> ids) {\n        userService.validateUserList(ids);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/auth/AuthController.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.auth;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.security.config.SecurityProperties;\nimport co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils;\nimport co.yixiang.yshop.module.system.controller.admin.auth.vo.*;\nimport co.yixiang.yshop.module.system.convert.auth.AuthConvert;\nimport co.yixiang.yshop.module.system.dal.dataobject.permission.MenuDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.permission.RoleDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.user.AdminUserDO;\nimport co.yixiang.yshop.module.system.enums.logger.LoginLogTypeEnum;\nimport co.yixiang.yshop.module.system.service.auth.AdminAuthService;\nimport co.yixiang.yshop.module.system.service.permission.MenuService;\nimport co.yixiang.yshop.module.system.service.permission.PermissionService;\nimport co.yixiang.yshop.module.system.service.permission.RoleService;\nimport co.yixiang.yshop.module.system.service.social.SocialClientService;\nimport co.yixiang.yshop.module.system.service.user.AdminUserService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.Parameters;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport jakarta.annotation.Resource;\nimport jakarta.annotation.security.PermitAll;\nimport jakarta.servlet.http.HttpServletRequest;\nimport jakarta.validation.Valid;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Set;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\nimport static co.yixiang.yshop.framework.common.util.collection.CollectionUtils.convertSet;\nimport static co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;\n\n@Tag(name = \"管理后台 - 认证\")\n@RestController\n@RequestMapping(\"/system/auth\")\n@Validated\n@Slf4j\npublic class AuthController {\n\n    @Resource\n    private AdminAuthService authService;\n    @Resource\n    private AdminUserService userService;\n    @Resource\n    private RoleService roleService;\n    @Resource\n    private MenuService menuService;\n    @Resource\n    private PermissionService permissionService;\n    @Resource\n    private SocialClientService socialClientService;\n\n    @Resource\n    private SecurityProperties securityProperties;\n\n    @PostMapping(\"/login\")\n    @PermitAll\n    @Operation(summary = \"使用账号密码登录\")\n    public CommonResult<AuthLoginRespVO> login(@RequestBody @Valid AuthLoginReqVO reqVO) {\n        return success(authService.login(reqVO));\n    }\n\n    @PostMapping(\"/logout\")\n    @PermitAll\n    @Operation(summary = \"登出系统\")\n    public CommonResult<Boolean> logout(HttpServletRequest request) {\n        String token = SecurityFrameworkUtils.obtainAuthorization(request,\n                securityProperties.getTokenHeader(), securityProperties.getTokenParameter());\n        if (StrUtil.isNotBlank(token)) {\n            authService.logout(token, LoginLogTypeEnum.LOGOUT_SELF.getType());\n        }\n        return success(true);\n    }\n\n    @PostMapping(\"/refresh-token\")\n    @PermitAll\n    @Operation(summary = \"刷新令牌\")\n    @Parameter(name = \"refreshToken\", description = \"刷新令牌\", required = true)\n    public CommonResult<AuthLoginRespVO> refreshToken(@RequestParam(\"refreshToken\") String refreshToken) {\n        return success(authService.refreshToken(refreshToken));\n    }\n\n    @GetMapping(\"/get-permission-info\")\n    @Operation(summary = \"获取登录用户的权限信息\")\n    public CommonResult<AuthPermissionInfoRespVO> getPermissionInfo() {\n        // 1.1 获得用户信息\n        AdminUserDO user = userService.getUser(getLoginUserId());\n        if (user == null) {\n            return success(null);\n        }\n\n        // 1.2 获得角色列表\n        Set<Long> roleIds = permissionService.getUserRoleIdListByUserId(getLoginUserId());\n        if (CollUtil.isEmpty(roleIds)) {\n            return success(AuthConvert.INSTANCE.convert(user, Collections.emptyList(), Collections.emptyList()));\n        }\n        List<RoleDO> roles = roleService.getRoleList(roleIds);\n        roles.removeIf(role -> !CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus())); // 移除禁用的角色\n\n        // 1.3 获得菜单列表\n        Set<Long> menuIds = permissionService.getRoleMenuListByRoleId(convertSet(roles, RoleDO::getId));\n        List<MenuDO> menuList = menuService.getMenuList(menuIds);\n        menuList.removeIf(menu -> !CommonStatusEnum.ENABLE.getStatus().equals(menu.getStatus())); // 移除禁用的菜单\n\n        // 2. 拼接结果返回\n        return success(AuthConvert.INSTANCE.convert(user, roles, menuList));\n    }\n\n    // ========== 短信登录相关 ==========\n\n    @PostMapping(\"/sms-login\")\n    @PermitAll\n    @Operation(summary = \"使用短信验证码登录\")\n    public CommonResult<AuthLoginRespVO> smsLogin(@RequestBody @Valid AuthSmsLoginReqVO reqVO) {\n        return success(authService.smsLogin(reqVO));\n    }\n\n    @PostMapping(\"/send-sms-code\")\n    @PermitAll\n    @Operation(summary = \"发送手机验证码\")\n    public CommonResult<Boolean> sendLoginSmsCode(@RequestBody @Valid AuthSmsSendReqVO reqVO) {\n        authService.sendSmsCode(reqVO);\n        return success(true);\n    }\n\n    // ========== 社交登录相关 ==========\n\n    @GetMapping(\"/social-auth-redirect\")\n    @PermitAll\n    @Operation(summary = \"社交授权的跳转\")\n    @Parameters({\n            @Parameter(name = \"type\", description = \"社交类型\", required = true),\n            @Parameter(name = \"redirectUri\", description = \"回调路径\")\n    })\n    public CommonResult<String> socialLogin(@RequestParam(\"type\") Integer type,\n                                            @RequestParam(\"redirectUri\") String redirectUri) {\n        return success(socialClientService.getAuthorizeUrl(\n                type, UserTypeEnum.ADMIN.getValue(), redirectUri));\n    }\n\n    @PostMapping(\"/social-login\")\n    @PermitAll\n    @Operation(summary = \"社交快捷登录，使用 code 授权码\", description = \"适合未登录的用户，但是社交账号已绑定用户\")\n    public CommonResult<AuthLoginRespVO> socialQuickLogin(@RequestBody @Valid AuthSocialLoginReqVO reqVO) {\n        return success(authService.socialLogin(reqVO));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/auth/vo/AuthLoginReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.auth.vo;\n\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.validation.InEnum;\nimport co.yixiang.yshop.module.system.enums.social.SocialTypeEnum;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport org.hibernate.validator.constraints.Length;\n\nimport jakarta.validation.constraints.AssertTrue;\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.Pattern;\n\n@Schema(description = \"管理后台 - 账号密码登录 Request VO，如果登录并绑定社交用户，需要传递 social 开头的参数\")\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\n@Builder\npublic class AuthLoginReqVO {\n\n    @Schema(description = \"账号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshopyuanma\")\n    @NotEmpty(message = \"登录账号不能为空\")\n    @Length(min = 4, max = 16, message = \"账号长度为 4-16 位\")\n    @Pattern(regexp = \"^[A-Za-z0-9]+$\", message = \"账号格式为数字以及字母\")\n    private String username;\n\n    @Schema(description = \"密码\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"buzhidao\")\n    @NotEmpty(message = \"密码不能为空\")\n    @Length(min = 4, max = 16, message = \"密码长度为 4-16 位\")\n    private String password;\n\n    // ========== 图片验证码相关 ==========\n\n    @Schema(description = \"验证码，验证码开启时，需要传递\", requiredMode = Schema.RequiredMode.REQUIRED,\n            example = \"PfcH6mgr8tpXuMWFjvW6YVaqrswIuwmWI5dsVZSg7sGpWtDCUbHuDEXl3cFB1+VvCC/rAkSwK8Fad52FSuncVg==\")\n    @NotEmpty(message = \"验证码不能为空\", groups = CodeEnableGroup.class)\n    private String captchaVerification;\n\n    // ========== 绑定社交登录时，需要传递如下参数 ==========\n\n    @Schema(description = \"社交平台的类型，参见 SocialTypeEnum 枚举值\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"10\")\n    @InEnum(SocialTypeEnum.class)\n    private Integer socialType;\n\n    @Schema(description = \"授权码\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private String socialCode;\n\n    @Schema(description = \"state\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"9b2ffbc1-7425-4155-9894-9d5c08541d62\")\n    private String socialState;\n\n    /**\n     * 开启验证码的 Group\n     */\n    public interface CodeEnableGroup {}\n\n    @AssertTrue(message = \"授权码不能为空\")\n    public boolean isSocialCodeValid() {\n        return socialType == null || StrUtil.isNotEmpty(socialCode);\n    }\n\n    @AssertTrue(message = \"授权 state 不能为空\")\n    public boolean isSocialState() {\n        return socialType == null || StrUtil.isNotEmpty(socialState);\n    }\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/auth/vo/AuthLoginRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.auth.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 登录 Response VO\")\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\n@Builder\npublic class AuthLoginRespVO {\n\n    @Schema(description = \"用户编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Long userId;\n\n    @Schema(description = \"访问令牌\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"happy\")\n    private String accessToken;\n\n    @Schema(description = \"刷新令牌\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"nice\")\n    private String refreshToken;\n\n    @Schema(description = \"过期时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private LocalDateTime expiresTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/auth/vo/AuthMenuRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.auth.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\nimport java.util.List;\n\n@Schema(description = \"管理后台 - 登录用户的菜单信息 Response VO\")\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\n@Builder\npublic class AuthMenuRespVO {\n\n    @Schema(description = \"菜单名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    private Long id;\n\n    @Schema(description = \"父菜单 ID\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Long parentId;\n\n    @Schema(description = \"菜单名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    private String name;\n\n    @Schema(description = \"路由地址,仅菜单类型为菜单或者目录时，才需要传\", example = \"post\")\n    private String path;\n\n    @Schema(description = \"组件路径,仅菜单类型为菜单时，才需要传\", example = \"system/post/index\")\n    private String component;\n\n    @Schema(description = \"组件名\", example = \"SystemUser\")\n    private String componentName;\n\n    @Schema(description = \"菜单图标,仅菜单类型为菜单或者目录时，才需要传\", example = \"/menu/list\")\n    private String icon;\n\n    @Schema(description = \"是否可见\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"false\")\n    private Boolean visible;\n\n    @Schema(description = \"是否缓存\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"false\")\n    private Boolean keepAlive;\n\n    @Schema(description = \"是否总是显示\", example = \"false\")\n    private Boolean alwaysShow;\n\n    /**\n     * 子路由\n     */\n    private List<AuthMenuRespVO> children;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/auth/vo/AuthPermissionInfoRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.auth.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\nimport java.util.List;\nimport java.util.Set;\n\n@Schema(description = \"管理后台 - 登录用户的权限信息 Response VO，额外包括用户信息和角色列表\")\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\n@Builder\npublic class AuthPermissionInfoRespVO {\n\n    @Schema(description = \"用户信息\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private UserVO user;\n\n    @Schema(description = \"角色标识数组\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private Set<String> roles;\n\n    @Schema(description = \"操作权限数组\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private Set<String> permissions;\n\n    @Schema(description = \"菜单树\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private List<MenuVO> menus;\n\n    @Schema(description = \"用户信息 VO\")\n    @Data\n    @NoArgsConstructor\n    @AllArgsConstructor\n    @Builder\n    public static class UserVO {\n\n        @Schema(description = \"用户编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n        private Long id;\n\n        @Schema(description = \"用户昵称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n        private String nickname;\n\n        @Schema(description = \"用户头像\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"https://www.yixiang.co/xx.jpg\")\n        private String avatar;\n\n        @Schema(description = \"部门编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"2048\")\n        private Long deptId;\n\n        private Long shopId;\n\n    }\n\n    @Schema(description = \"管理后台 - 登录用户的菜单信息 Response VO\")\n    @Data\n    @NoArgsConstructor\n    @AllArgsConstructor\n    @Builder\n    public static class MenuVO {\n\n        @Schema(description = \"菜单名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n        private Long id;\n\n        @Schema(description = \"父菜单 ID\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n        private Long parentId;\n\n        @Schema(description = \"菜单名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n        private String name;\n\n        @Schema(description = \"路由地址,仅菜单类型为菜单或者目录时，才需要传\", example = \"post\")\n        private String path;\n\n        @Schema(description = \"组件路径,仅菜单类型为菜单时，才需要传\", example = \"system/post/index\")\n        private String component;\n\n        @Schema(description = \"组件名\", example = \"SystemUser\")\n        private String componentName;\n\n        @Schema(description = \"菜单图标,仅菜单类型为菜单或者目录时，才需要传\", example = \"/menu/list\")\n        private String icon;\n\n        @Schema(description = \"是否可见\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"false\")\n        private Boolean visible;\n\n        @Schema(description = \"是否缓存\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"false\")\n        private Boolean keepAlive;\n\n        @Schema(description = \"是否总是显示\", example = \"false\")\n        private Boolean alwaysShow;\n\n        /**\n         * 子路由\n         */\n        private List<MenuVO> children;\n\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/auth/vo/AuthSmsLoginReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.auth.vo;\n\nimport co.yixiang.yshop.framework.common.validation.Mobile;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\nimport jakarta.validation.constraints.NotEmpty;\n\n@Schema(description = \"管理后台 - 短信验证码的登录 Request VO\")\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\n@Builder\npublic class AuthSmsLoginReqVO {\n\n    @Schema(description = \"手机号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshopyuanma\")\n    @NotEmpty(message = \"手机号不能为空\")\n    @Mobile\n    private String mobile;\n\n    @Schema(description = \"短信验证码\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @NotEmpty(message = \"验证码不能为空\")\n    private String code;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/auth/vo/AuthSmsSendReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.auth.vo;\n\nimport co.yixiang.yshop.framework.common.validation.InEnum;\nimport co.yixiang.yshop.framework.common.validation.Mobile;\nimport co.yixiang.yshop.module.system.enums.sms.SmsSceneEnum;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\n\n@Schema(description = \"管理后台 - 发送手机验证码 Request VO\")\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\n@Builder\npublic class AuthSmsSendReqVO {\n\n    @Schema(description = \"手机号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshopyuanma\")\n    @NotEmpty(message = \"手机号不能为空\")\n    @Mobile\n    private String mobile;\n\n    @Schema(description = \"短信场景\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @NotNull(message = \"发送场景不能为空\")\n    @InEnum(SmsSceneEnum.class)\n    private Integer scene;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/auth/vo/AuthSocialLoginReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.auth.vo;\n\nimport co.yixiang.yshop.framework.common.validation.InEnum;\nimport co.yixiang.yshop.module.system.enums.social.SocialTypeEnum;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\n\n@Schema(description = \"管理后台 - 社交绑定登录 Request VO，使用 code 授权码 + 账号密码\")\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\n@Builder\npublic class AuthSocialLoginReqVO {\n\n    @Schema(description = \"社交平台的类型，参见 UserSocialTypeEnum 枚举值\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"10\")\n    @InEnum(SocialTypeEnum.class)\n    @NotNull(message = \"社交平台的类型不能为空\")\n    private Integer type;\n\n    @Schema(description = \"授权码\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @NotEmpty(message = \"授权码不能为空\")\n    private String code;\n\n    @Schema(description = \"state\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"9b2ffbc1-7425-4155-9894-9d5c08541d62\")\n    @NotEmpty(message = \"state 不能为空\")\n    private String state;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/captcha/CaptchaController.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.captcha;\n\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.util.servlet.ServletUtils;\nimport com.xingyuv.captcha.model.common.ResponseModel;\nimport com.xingyuv.captcha.model.vo.CaptchaVO;\nimport com.xingyuv.captcha.service.CaptchaService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport org.springframework.web.bind.annotation.PostMapping;\nimport org.springframework.web.bind.annotation.RequestBody;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport jakarta.annotation.Resource;\nimport jakarta.annotation.security.PermitAll;\nimport jakarta.servlet.http.HttpServletRequest;\n\n@Tag(name = \"管理后台 - 验证码\")\n@RestController(\"adminCaptchaController\")\n@RequestMapping(\"/system/captcha\")\npublic class CaptchaController {\n\n    @Resource\n    private CaptchaService captchaService;\n\n    @PostMapping({\"/get\"})\n    @Operation(summary = \"获得验证码\")\n    @PermitAll\n    public ResponseModel get(@RequestBody CaptchaVO data, HttpServletRequest request) {\n        assert request.getRemoteHost() != null;\n        data.setBrowserInfo(getRemoteId(request));\n        return captchaService.get(data);\n    }\n\n    @PostMapping(\"/check\")\n    @Operation(summary = \"校验验证码\")\n    @PermitAll\n    public ResponseModel check(@RequestBody CaptchaVO data, HttpServletRequest request) {\n        data.setBrowserInfo(getRemoteId(request));\n        return captchaService.check(data);\n    }\n\n    public static String getRemoteId(HttpServletRequest request) {\n        String ip = ServletUtils.getClientIP(request);\n        String ua = request.getHeader(\"user-agent\");\n        if (StrUtil.isNotBlank(ip)) {\n            return ip + ua;\n        }\n        return request.getRemoteAddr() + ua;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/dept/DeptController.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.dept;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.controller.admin.dept.vo.dept.DeptListReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.dept.vo.dept.DeptRespVO;\nimport co.yixiang.yshop.module.system.controller.admin.dept.vo.dept.DeptSaveReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.dept.vo.dept.DeptSimpleRespVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.dept.DeptDO;\nimport co.yixiang.yshop.module.system.service.dept.DeptService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Valid;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 部门\")\n@RestController\n@RequestMapping(\"/system/dept\")\n@Validated\npublic class DeptController {\n\n    @Resource\n    private DeptService deptService;\n\n    @PostMapping(\"create\")\n    @Operation(summary = \"创建部门\")\n    @PreAuthorize(\"@ss.hasPermission('system:dept:create')\")\n    public CommonResult<Long> createDept(@Valid @RequestBody DeptSaveReqVO createReqVO) {\n        Long deptId = deptService.createDept(createReqVO);\n        return success(deptId);\n    }\n\n    @PutMapping(\"update\")\n    @Operation(summary = \"更新部门\")\n    @PreAuthorize(\"@ss.hasPermission('system:dept:update')\")\n    public CommonResult<Boolean> updateDept(@Valid @RequestBody DeptSaveReqVO updateReqVO) {\n        deptService.updateDept(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"delete\")\n    @Operation(summary = \"删除部门\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('system:dept:delete')\")\n    public CommonResult<Boolean> deleteDept(@RequestParam(\"id\") Long id) {\n        deptService.deleteDept(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/list\")\n    @Operation(summary = \"获取部门列表\")\n    @PreAuthorize(\"@ss.hasPermission('system:dept:query')\")\n    public CommonResult<List<DeptRespVO>> getDeptList(DeptListReqVO reqVO) {\n        List<DeptDO> list = deptService.getDeptList(reqVO);\n        return success(BeanUtils.toBean(list, DeptRespVO.class));\n    }\n\n    @GetMapping(value = {\"/list-all-simple\", \"/simple-list\"})\n    @Operation(summary = \"获取部门精简信息列表\", description = \"只包含被开启的部门，主要用于前端的下拉选项\")\n    public CommonResult<List<DeptSimpleRespVO>> getSimpleDeptList() {\n        List<DeptDO> list = deptService.getDeptList(\n                new DeptListReqVO().setStatus(CommonStatusEnum.ENABLE.getStatus()));\n        return success(BeanUtils.toBean(list, DeptSimpleRespVO.class));\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得部门信息\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('system:dept:query')\")\n    public CommonResult<DeptRespVO> getDept(@RequestParam(\"id\") Long id) {\n        DeptDO dept = deptService.getDept(id);\n        return success(BeanUtils.toBean(dept, DeptRespVO.class));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/dept/PostController.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.dept;\n\nimport co.yixiang.yshop.framework.apilog.core.annotation.ApiAccessLog;\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.framework.excel.core.util.ExcelUtils;\nimport co.yixiang.yshop.module.system.controller.admin.dept.vo.post.PostPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.dept.vo.post.PostRespVO;\nimport co.yixiang.yshop.module.system.controller.admin.dept.vo.post.PostSaveReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.dept.vo.post.PostSimpleRespVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.dept.PostDO;\nimport co.yixiang.yshop.module.system.service.dept.PostService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.annotation.Resource;\nimport jakarta.servlet.http.HttpServletResponse;\nimport jakarta.validation.Valid;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.io.IOException;\nimport java.util.Collections;\nimport java.util.Comparator;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.apilog.core.enums.OperateTypeEnum.EXPORT;\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 岗位\")\n@RestController\n@RequestMapping(\"/system/post\")\n@Validated\npublic class PostController {\n\n    @Resource\n    private PostService postService;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建岗位\")\n    @PreAuthorize(\"@ss.hasPermission('system:post:create')\")\n    public CommonResult<Long> createPost(@Valid @RequestBody PostSaveReqVO createReqVO) {\n        Long postId = postService.createPost(createReqVO);\n        return success(postId);\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"修改岗位\")\n    @PreAuthorize(\"@ss.hasPermission('system:post:update')\")\n    public CommonResult<Boolean> updatePost(@Valid @RequestBody PostSaveReqVO updateReqVO) {\n        postService.updatePost(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除岗位\")\n    @PreAuthorize(\"@ss.hasPermission('system:post:delete')\")\n    public CommonResult<Boolean> deletePost(@RequestParam(\"id\") Long id) {\n        postService.deletePost(id);\n        return success(true);\n    }\n\n    @GetMapping(value = \"/get\")\n    @Operation(summary = \"获得岗位信息\")\n    @Parameter(name = \"id\", description = \"岗位编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('system:post:query')\")\n    public CommonResult<PostRespVO> getPost(@RequestParam(\"id\") Long id) {\n        PostDO post = postService.getPost(id);\n        return success(BeanUtils.toBean(post, PostRespVO.class));\n    }\n\n    @GetMapping(value = {\"/list-all-simple\", \"simple-list\"})\n    @Operation(summary = \"获取岗位全列表\", description = \"只包含被开启的岗位，主要用于前端的下拉选项\")\n    public CommonResult<List<PostSimpleRespVO>> getSimplePostList() {\n        // 获得岗位列表，只要开启状态的\n        List<PostDO> list = postService.getPostList(null, Collections.singleton(CommonStatusEnum.ENABLE.getStatus()));\n        // 排序后，返回给前端\n        list.sort(Comparator.comparing(PostDO::getSort));\n        return success(BeanUtils.toBean(list, PostSimpleRespVO.class));\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得岗位分页列表\")\n    @PreAuthorize(\"@ss.hasPermission('system:post:query')\")\n    public CommonResult<PageResult<PostRespVO>> getPostPage(@Validated PostPageReqVO pageReqVO) {\n        PageResult<PostDO> pageResult = postService.getPostPage(pageReqVO);\n        return success(BeanUtils.toBean(pageResult, PostRespVO.class));\n    }\n\n    @GetMapping(\"/export\")\n    @Operation(summary = \"岗位管理\")\n    @PreAuthorize(\"@ss.hasPermission('system:post:export')\")\n    @ApiAccessLog(operateType = EXPORT)\n    public void export(HttpServletResponse response, @Validated PostPageReqVO reqVO) throws IOException {\n        reqVO.setPageSize(PageParam.PAGE_SIZE_NONE);\n        List<PostDO> list = postService.getPostPage(reqVO).getList();\n        // 输出\n        ExcelUtils.write(response, \"岗位数据.xls\", \"岗位列表\", PostRespVO.class,\n                BeanUtils.toBean(list, PostRespVO.class));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/dept/vo/dept/DeptListReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.dept.vo.dept;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\n@Schema(description = \"管理后台 - 部门列表 Request VO\")\n@Data\npublic class DeptListReqVO {\n\n    @Schema(description = \"部门名称，模糊匹配\", example = \"yshop\")\n    private String name;\n\n    @Schema(description = \"展示状态，参见 CommonStatusEnum 枚举类\", example = \"1\")\n    private Integer status;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/dept/vo/dept/DeptRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.dept.vo.dept;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 部门信息 Response VO\")\n@Data\npublic class DeptRespVO {\n\n    @Schema(description = \"部门编号\", example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"部门名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    private String name;\n\n    @Schema(description = \"父部门 ID\", example = \"1024\")\n    private Long parentId;\n\n    @Schema(description = \"显示顺序\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Integer sort;\n\n    @Schema(description = \"负责人的用户编号\", example = \"2048\")\n    private Long leaderUserId;\n\n    @Schema(description = \"联系电话\", example = \"15601691000\")\n    private String phone;\n\n    @Schema(description = \"邮箱\", example = \"yshop@yixiang.co\")\n    private String email;\n\n    @Schema(description = \"状态,见 CommonStatusEnum 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    private Integer status;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"时间戳格式\")\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/dept/vo/dept/DeptSaveReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.dept.vo.dept;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.validation.InEnum;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.Email;\nimport jakarta.validation.constraints.NotBlank;\nimport jakarta.validation.constraints.NotNull;\nimport jakarta.validation.constraints.Size;\n\n@Schema(description = \"管理后台 - 部门创建/修改 Request VO\")\n@Data\npublic class DeptSaveReqVO {\n\n    @Schema(description = \"部门编号\", example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"部门名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    @NotBlank(message = \"部门名称不能为空\")\n    @Size(max = 30, message = \"部门名称长度不能超过 30 个字符\")\n    private String name;\n\n    @Schema(description = \"父部门 ID\", example = \"1024\")\n    private Long parentId;\n\n    @Schema(description = \"显示顺序\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @NotNull(message = \"显示顺序不能为空\")\n    private Integer sort;\n\n    @Schema(description = \"负责人的用户编号\", example = \"2048\")\n    private Long leaderUserId;\n\n    @Schema(description = \"联系电话\", example = \"15601691000\")\n    @Size(max = 11, message = \"联系电话长度不能超过11个字符\")\n    private String phone;\n\n    @Schema(description = \"邮箱\", example = \"yshop@yixiang.co\")\n    @Email(message = \"邮箱格式不正确\")\n    @Size(max = 50, message = \"邮箱长度不能超过 50 个字符\")\n    private String email;\n\n    @Schema(description = \"状态,见 CommonStatusEnum 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @NotNull(message = \"状态不能为空\")\n    @InEnum(value = CommonStatusEnum.class, message = \"修改状态必须是 {value}\")\n    private Integer status;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/dept/vo/dept/DeptSimpleRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.dept.vo.dept;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\n@Schema(description = \"管理后台 - 部门精简信息 Response VO\")\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\npublic class DeptSimpleRespVO {\n\n    @Schema(description = \"部门编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"部门名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    private String name;\n\n    @Schema(description = \"父部门 ID\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Long parentId;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/dept/vo/post/PostPageReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.dept.vo.post;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\n@Schema(description = \"管理后台 - 岗位分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\npublic class PostPageReqVO extends PageParam {\n\n    @Schema(description = \"岗位编码，模糊匹配\", example = \"yshop\")\n    private String code;\n\n    @Schema(description = \"岗位名称，模糊匹配\", example = \"yshop\")\n    private String name;\n\n    @Schema(description = \"展示状态，参见 CommonStatusEnum 枚举类\", example = \"1\")\n    private Integer status;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/dept/vo/post/PostRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.dept.vo.post;\n\nimport co.yixiang.yshop.framework.excel.core.annotations.DictFormat;\nimport co.yixiang.yshop.framework.excel.core.convert.DictConvert;\nimport co.yixiang.yshop.module.system.enums.DictTypeConstants;\nimport com.alibaba.excel.annotation.ExcelIgnoreUnannotated;\nimport com.alibaba.excel.annotation.ExcelProperty;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 岗位信息 Response VO\")\n@Data\n@ExcelIgnoreUnannotated\npublic class PostRespVO {\n\n    @Schema(description = \"岗位序号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @ExcelProperty(\"岗位序号\")\n    private Long id;\n\n    @Schema(description = \"岗位名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"小土豆\")\n    @ExcelProperty(\"岗位名称\")\n    private String name;\n\n    @Schema(description = \"岗位编码\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    @ExcelProperty(\"岗位编码\")\n    private String code;\n\n    @Schema(description = \"显示顺序\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @ExcelProperty(\"岗位排序\")\n    private Integer sort;\n\n    @Schema(description = \"状态，参见 CommonStatusEnum 枚举类\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @ExcelProperty(value = \"状态\", converter = DictConvert.class)\n    @DictFormat(DictTypeConstants.COMMON_STATUS)\n    private Integer status;\n\n    @Schema(description = \"备注\", example = \"快乐的备注\")\n    private String remark;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/dept/vo/post/PostSaveReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.dept.vo.post;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.validation.InEnum;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotBlank;\nimport jakarta.validation.constraints.NotNull;\nimport jakarta.validation.constraints.Size;\n\n@Schema(description = \"管理后台 - 岗位创建/修改 Request VO\")\n@Data\npublic class PostSaveReqVO {\n\n    @Schema(description = \"岗位编号\", example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"岗位名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"小土豆\")\n    @NotBlank(message = \"岗位名称不能为空\")\n    @Size(max = 50, message = \"岗位名称长度不能超过 50 个字符\")\n    private String name;\n\n    @Schema(description = \"岗位编码\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    @NotBlank(message = \"岗位编码不能为空\")\n    @Size(max = 64, message = \"岗位编码长度不能超过64个字符\")\n    private String code;\n\n    @Schema(description = \"显示顺序\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @NotNull(message = \"显示顺序不能为空\")\n    private Integer sort;\n\n    @Schema(description = \"状态\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @InEnum(CommonStatusEnum.class)\n    private Integer status;\n\n    @Schema(description = \"备注\", example = \"快乐的备注\")\n    private String remark;\n\n}"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/dept/vo/post/PostSimpleRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.dept.vo.post;\n\nimport com.alibaba.excel.annotation.ExcelProperty;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\n@Schema(description = \"管理后台 - 岗位信息的精简 Response VO\")\n@Data\npublic class PostSimpleRespVO {\n\n    @Schema(description = \"岗位序号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @ExcelProperty(\"岗位序号\")\n    private Long id;\n\n    @Schema(description = \"岗位名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"小土豆\")\n    @ExcelProperty(\"岗位名称\")\n    private String name;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/dict/DictDataController.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.dict;\n\nimport co.yixiang.yshop.framework.apilog.core.annotation.ApiAccessLog;\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.framework.excel.core.util.ExcelUtils;\nimport co.yixiang.yshop.module.system.controller.admin.dict.vo.data.DictDataPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.dict.vo.data.DictDataRespVO;\nimport co.yixiang.yshop.module.system.controller.admin.dict.vo.data.DictDataSaveReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.dict.vo.data.DictDataSimpleRespVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.dict.DictDataDO;\nimport co.yixiang.yshop.module.system.service.dict.DictDataService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.annotation.Resource;\nimport jakarta.servlet.http.HttpServletResponse;\nimport jakarta.validation.Valid;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.io.IOException;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.apilog.core.enums.OperateTypeEnum.EXPORT;\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 字典数据\")\n@RestController\n@RequestMapping(\"/system/dict-data\")\n@Validated\npublic class DictDataController {\n\n    @Resource\n    private DictDataService dictDataService;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"新增字典数据\")\n    @PreAuthorize(\"@ss.hasPermission('system:dict:create')\")\n    public CommonResult<Long> createDictData(@Valid @RequestBody DictDataSaveReqVO createReqVO) {\n        Long dictDataId = dictDataService.createDictData(createReqVO);\n        return success(dictDataId);\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"修改字典数据\")\n    @PreAuthorize(\"@ss.hasPermission('system:dict:update')\")\n    public CommonResult<Boolean> updateDictData(@Valid @RequestBody DictDataSaveReqVO updateReqVO) {\n        dictDataService.updateDictData(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除字典数据\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('system:dict:delete')\")\n    public CommonResult<Boolean> deleteDictData(Long id) {\n        dictDataService.deleteDictData(id);\n        return success(true);\n    }\n\n    @GetMapping(value = {\"/list-all-simple\", \"simple-list\"})\n    @Operation(summary = \"获得全部字典数据列表\", description = \"一般用于管理后台缓存字典数据在本地\")\n    // 无需添加权限认证，因为前端全局都需要\n    public CommonResult<List<DictDataSimpleRespVO>> getSimpleDictDataList() {\n        List<DictDataDO> list = dictDataService.getDictDataList(\n                CommonStatusEnum.ENABLE.getStatus(), null);\n        return success(BeanUtils.toBean(list, DictDataSimpleRespVO.class));\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"/获得字典类型的分页列表\")\n    @PreAuthorize(\"@ss.hasPermission('system:dict:query')\")\n    public CommonResult<PageResult<DictDataRespVO>> getDictTypePage(@Valid DictDataPageReqVO pageReqVO) {\n        PageResult<DictDataDO> pageResult = dictDataService.getDictDataPage(pageReqVO);\n        return success(BeanUtils.toBean(pageResult, DictDataRespVO.class));\n    }\n\n    @GetMapping(value = \"/get\")\n    @Operation(summary = \"/查询字典数据详细\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('system:dict:query')\")\n    public CommonResult<DictDataRespVO> getDictData(@RequestParam(\"id\") Long id) {\n        DictDataDO dictData = dictDataService.getDictData(id);\n        return success(BeanUtils.toBean(dictData, DictDataRespVO.class));\n    }\n\n    @GetMapping(\"/export\")\n    @Operation(summary = \"导出字典数据\")\n    @PreAuthorize(\"@ss.hasPermission('system:dict:export')\")\n    @ApiAccessLog(operateType = EXPORT)\n    public void export(HttpServletResponse response, @Valid DictDataPageReqVO exportReqVO) throws IOException {\n        exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);\n        List<DictDataDO> list = dictDataService.getDictDataPage(exportReqVO).getList();\n        // 输出\n        ExcelUtils.write(response, \"字典数据.xls\", \"数据\", DictDataRespVO.class,\n                BeanUtils.toBean(list, DictDataRespVO.class));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/dict/DictTypeController.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.dict;\n\nimport co.yixiang.yshop.framework.apilog.core.annotation.ApiAccessLog;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.framework.excel.core.util.ExcelUtils;\nimport co.yixiang.yshop.module.system.controller.admin.dict.vo.type.DictTypePageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.dict.vo.type.DictTypeRespVO;\nimport co.yixiang.yshop.module.system.controller.admin.dict.vo.type.DictTypeSaveReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.dict.vo.type.DictTypeSimpleRespVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.dict.DictTypeDO;\nimport co.yixiang.yshop.module.system.service.dict.DictTypeService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.annotation.Resource;\nimport jakarta.servlet.http.HttpServletResponse;\nimport jakarta.validation.Valid;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.io.IOException;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.apilog.core.enums.OperateTypeEnum.EXPORT;\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 字典类型\")\n@RestController\n@RequestMapping(\"/system/dict-type\")\n@Validated\npublic class DictTypeController {\n\n    @Resource\n    private DictTypeService dictTypeService;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建字典类型\")\n    @PreAuthorize(\"@ss.hasPermission('system:dict:create')\")\n    public CommonResult<Long> createDictType(@Valid @RequestBody DictTypeSaveReqVO createReqVO) {\n        Long dictTypeId = dictTypeService.createDictType(createReqVO);\n        return success(dictTypeId);\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"修改字典类型\")\n    @PreAuthorize(\"@ss.hasPermission('system:dict:update')\")\n    public CommonResult<Boolean> updateDictType(@Valid @RequestBody DictTypeSaveReqVO updateReqVO) {\n        dictTypeService.updateDictType(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除字典类型\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('system:dict:delete')\")\n    public CommonResult<Boolean> deleteDictType(Long id) {\n        dictTypeService.deleteDictType(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得字典类型的分页列表\")\n    @PreAuthorize(\"@ss.hasPermission('system:dict:query')\")\n    public CommonResult<PageResult<DictTypeRespVO>> pageDictTypes(@Valid DictTypePageReqVO pageReqVO) {\n        PageResult<DictTypeDO> pageResult = dictTypeService.getDictTypePage(pageReqVO);\n        return success(BeanUtils.toBean(pageResult, DictTypeRespVO.class));\n    }\n\n    @Operation(summary = \"/查询字典类型详细\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @GetMapping(value = \"/get\")\n    @PreAuthorize(\"@ss.hasPermission('system:dict:query')\")\n    public CommonResult<DictTypeRespVO> getDictType(@RequestParam(\"id\") Long id) {\n        DictTypeDO dictType = dictTypeService.getDictType(id);\n        return success(BeanUtils.toBean(dictType, DictTypeRespVO.class));\n    }\n\n    @GetMapping(value = {\"/list-all-simple\", \"simple-list\"})\n    @Operation(summary = \"获得全部字典类型列表\", description = \"包括开启 + 禁用的字典类型，主要用于前端的下拉选项\")\n    // 无需添加权限认证，因为前端全局都需要\n    public CommonResult<List<DictTypeSimpleRespVO>> getSimpleDictTypeList() {\n        List<DictTypeDO> list = dictTypeService.getDictTypeList();\n        return success(BeanUtils.toBean(list, DictTypeSimpleRespVO.class));\n    }\n\n    @Operation(summary = \"导出数据类型\")\n    @GetMapping(\"/export\")\n    @PreAuthorize(\"@ss.hasPermission('system:dict:query')\")\n    @ApiAccessLog(operateType = EXPORT)\n    public void export(HttpServletResponse response, @Valid DictTypePageReqVO exportReqVO) throws IOException {\n        exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);\n        List<DictTypeDO> list = dictTypeService.getDictTypePage(exportReqVO).getList();\n        // 导出\n        ExcelUtils.write(response, \"字典类型.xls\", \"数据\", DictTypeRespVO.class,\n                BeanUtils.toBean(list, DictTypeRespVO.class));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/dict/vo/data/DictDataPageReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.dict.vo.data;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport co.yixiang.yshop.framework.common.validation.InEnum;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\nimport jakarta.validation.constraints.Size;\n\n@Schema(description = \"管理后台 - 字典类型分页列表 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\npublic class DictDataPageReqVO extends PageParam {\n\n    @Schema(description = \"字典标签\", example = \"yshop\")\n    @Size(max = 100, message = \"字典标签长度不能超过100个字符\")\n    private String label;\n\n    @Schema(description = \"字典类型，模糊匹配\", example = \"sys_common_sex\")\n    @Size(max = 100, message = \"字典类型类型长度不能超过100个字符\")\n    private String dictType;\n\n    @Schema(description = \"展示状态，参见 CommonStatusEnum 枚举类\", example = \"1\")\n    @InEnum(value = CommonStatusEnum.class, message = \"修改状态必须是 {value}\")\n    private Integer status;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/dict/vo/data/DictDataRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.dict.vo.data;\n\nimport co.yixiang.yshop.framework.excel.core.annotations.DictFormat;\nimport co.yixiang.yshop.framework.excel.core.convert.DictConvert;\nimport co.yixiang.yshop.module.system.enums.DictTypeConstants;\nimport com.alibaba.excel.annotation.ExcelIgnoreUnannotated;\nimport com.alibaba.excel.annotation.ExcelProperty;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 字典数据信息 Response VO\")\n@Data\n@ExcelIgnoreUnannotated\npublic class DictDataRespVO {\n\n    @Schema(description = \"字典数据编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @ExcelProperty(\"字典编码\")\n    private Long id;\n\n    @Schema(description = \"显示顺序\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @ExcelProperty(\"字典排序\")\n    private Integer sort;\n\n    @Schema(description = \"字典标签\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    @ExcelProperty(\"字典标签\")\n    private String label;\n\n    @Schema(description = \"字典值\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yixiang.co\")\n    @ExcelProperty(\"字典键值\")\n    private String value;\n\n    @Schema(description = \"字典类型\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"sys_common_sex\")\n    @ExcelProperty(\"字典类型\")\n    private String dictType;\n\n    @Schema(description = \"状态,见 CommonStatusEnum 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @ExcelProperty(value = \"状态\", converter = DictConvert.class)\n    @DictFormat(DictTypeConstants.COMMON_STATUS)\n    private Integer status;\n\n    @Schema(description = \"颜色类型,default、primary、success、info、warning、danger\", example = \"default\")\n    private String colorType;\n\n    @Schema(description = \"css 样式\", example = \"btn-visible\")\n    private String cssClass;\n\n    @Schema(description = \"备注\", example = \"我是一个角色\")\n    private String remark;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"时间戳格式\")\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/dict/vo/data/DictDataSaveReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.dict.vo.data;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.validation.InEnum;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotBlank;\nimport jakarta.validation.constraints.NotNull;\nimport jakarta.validation.constraints.Size;\n\n@Schema(description = \"管理后台 - 字典数据创建/修改 Request VO\")\n@Data\npublic class DictDataSaveReqVO {\n\n    @Schema(description = \"字典数据编号\", example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"显示顺序\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @NotNull(message = \"显示顺序不能为空\")\n    private Integer sort;\n\n    @Schema(description = \"字典标签\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    @NotBlank(message = \"字典标签不能为空\")\n    @Size(max = 100, message = \"字典标签长度不能超过100个字符\")\n    private String label;\n\n    @Schema(description = \"字典值\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yixiang.co\")\n    @NotBlank(message = \"字典键值不能为空\")\n    @Size(max = 100, message = \"字典键值长度不能超过100个字符\")\n    private String value;\n\n    @Schema(description = \"字典类型\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"sys_common_sex\")\n    @NotBlank(message = \"字典类型不能为空\")\n    @Size(max = 100, message = \"字典类型长度不能超过100个字符\")\n    private String dictType;\n\n    @Schema(description = \"状态,见 CommonStatusEnum 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @NotNull(message = \"状态不能为空\")\n    @InEnum(value = CommonStatusEnum.class, message = \"修改状态必须是 {value}\")\n    private Integer status;\n\n    @Schema(description = \"颜色类型,default、primary、success、info、warning、danger\", example = \"default\")\n    private String colorType;\n\n    @Schema(description = \"css 样式\", example = \"btn-visible\")\n    private String cssClass;\n\n    @Schema(description = \"备注\", example = \"我是一个角色\")\n    private String remark;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/dict/vo/data/DictDataSimpleRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.dict.vo.data;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\n@Schema(description = \"管理后台 - 数据字典精简 Response VO\")\n@Data\npublic class DictDataSimpleRespVO {\n\n    @Schema(description = \"字典类型\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"gender\")\n    private String dictType;\n\n    @Schema(description = \"字典键值\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    private String value;\n\n    @Schema(description = \"字典标签\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"男\")\n    private String label;\n\n    @Schema(description = \"颜色类型，default、primary、success、info、warning、danger\", example = \"default\")\n    private String colorType;\n\n    @Schema(description = \"css 样式\", example = \"btn-visible\")\n    private String cssClass;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/dict/vo/type/DictTypePageReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.dict.vo.type;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport jakarta.validation.constraints.Size;\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 字典类型分页列表 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\npublic class DictTypePageReqVO extends PageParam {\n\n    @Schema(description = \"字典类型名称，模糊匹配\", example = \"yshop\")\n    private String name;\n\n    @Schema(description = \"字典类型，模糊匹配\", example = \"sys_common_sex\")\n    @Size(max = 100, message = \"字典类型类型长度不能超过100个字符\")\n    private String type;\n\n    @Schema(description = \"展示状态，参见 CommonStatusEnum 枚举类\", example = \"1\")\n    private Integer status;\n\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    @Schema(description = \"创建时间\")\n    private LocalDateTime[] createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/dict/vo/type/DictTypeRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.dict.vo.type;\n\nimport co.yixiang.yshop.framework.excel.core.annotations.DictFormat;\nimport co.yixiang.yshop.framework.excel.core.convert.DictConvert;\nimport co.yixiang.yshop.module.system.enums.DictTypeConstants;\nimport com.alibaba.excel.annotation.ExcelIgnoreUnannotated;\nimport com.alibaba.excel.annotation.ExcelProperty;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 字典类型信息 Response VO\")\n@Data\n@ExcelIgnoreUnannotated\npublic class DictTypeRespVO {\n\n    @Schema(description = \"字典类型编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @ExcelProperty(\"字典主键\")\n    private Long id;\n\n    @Schema(description = \"字典名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"性别\")\n    @ExcelProperty(\"字典名称\")\n    private String name;\n\n    @Schema(description = \"字典类型\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"sys_common_sex\")\n    @ExcelProperty(\"字典类型\")\n    private String type;\n\n    @Schema(description = \"状态，参见 CommonStatusEnum 枚举类\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @ExcelProperty(value = \"状态\", converter = DictConvert.class)\n    @DictFormat(DictTypeConstants.COMMON_STATUS)\n    private Integer status;\n\n    @Schema(description = \"备注\", example = \"快乐的备注\")\n    private String remark;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"时间戳格式\")\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/dict/vo/type/DictTypeSaveReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.dict.vo.type;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotBlank;\nimport jakarta.validation.constraints.NotNull;\nimport jakarta.validation.constraints.Size;\n\n@Schema(description = \"管理后台 - 字典类型创建/修改 Request VO\")\n@Data\npublic class DictTypeSaveReqVO {\n\n    @Schema(description = \"字典类型编号\", example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"字典名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"性别\")\n    @NotBlank(message = \"字典名称不能为空\")\n    @Size(max = 100, message = \"字典类型名称长度不能超过100个字符\")\n    private String name;\n\n    @Schema(description = \"字典类型\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"sys_common_sex\")\n    @NotNull(message = \"字典类型不能为空\")\n    @Size(max = 100, message = \"字典类型类型长度不能超过 100 个字符\")\n    private String type;\n\n    @Schema(description = \"状态，参见 CommonStatusEnum 枚举类\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @NotNull(message = \"状态不能为空\")\n    private Integer status;\n\n    @Schema(description = \"备注\", example = \"快乐的备注\")\n    private String remark;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/dict/vo/type/DictTypeSimpleRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.dict.vo.type;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\n@Schema(description = \"管理后台 - 字典类型精简信息 Response VO\")\n@Data\npublic class DictTypeSimpleRespVO {\n\n    @Schema(description = \"字典类型编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"字典类型名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    private String name;\n\n    @Schema(description = \"字典类型\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"sys_common_sex\")\n    private String type;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/ip/AreaController.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.ip;\n\nimport cn.hutool.core.lang.Assert;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.framework.ip.core.Area;\nimport co.yixiang.yshop.framework.ip.core.utils.AreaUtils;\nimport co.yixiang.yshop.framework.ip.core.utils.IPUtils;\nimport co.yixiang.yshop.module.system.controller.admin.ip.vo.AreaNodeRespVO;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 地区\")\n@RestController\n@RequestMapping(\"/system/area\")\n@Validated\npublic class AreaController {\n\n    @GetMapping(\"/tree\")\n    @Operation(summary = \"获得地区树\")\n    public CommonResult<List<AreaNodeRespVO>> getAreaTree() {\n        Area area = AreaUtils.getArea(Area.ID_CHINA);\n        Assert.notNull(area, \"获取不到中国\");\n        return success(BeanUtils.toBean(area.getChildren(), AreaNodeRespVO.class));\n    }\n\n    @GetMapping(\"/get-by-ip\")\n    @Operation(summary = \"获得 IP 对应的地区名\")\n    @Parameter(name = \"ip\", description = \"IP\", required = true)\n    public CommonResult<String> getAreaByIp(@RequestParam(\"ip\") String ip) {\n        // 获得城市\n        Area area = IPUtils.getArea(ip);\n        if (area == null) {\n            return success(\"未知\");\n        }\n        // 格式化返回\n        return success(AreaUtils.format(area.getId()));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/ip/vo/AreaNodeRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.ip.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.util.List;\n\n@Schema(description = \"管理后台 - 地区节点 Response VO\")\n@Data\npublic class AreaNodeRespVO {\n\n    @Schema(description = \"编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"110000\")\n    private Integer id;\n\n    @Schema(description = \"名字\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"北京\")\n    private String name;\n\n    /**\n     * 子节点\n     */\n    private List<AreaNodeRespVO> children;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/logger/LoginLogController.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.logger;\n\nimport co.yixiang.yshop.framework.apilog.core.annotation.ApiAccessLog;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.framework.excel.core.util.ExcelUtils;\nimport co.yixiang.yshop.module.system.controller.admin.logger.vo.loginlog.LoginLogPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.logger.vo.loginlog.LoginLogRespVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.logger.LoginLogDO;\nimport co.yixiang.yshop.module.system.service.logger.LoginLogService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.annotation.Resource;\nimport jakarta.servlet.http.HttpServletResponse;\nimport jakarta.validation.Valid;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport java.io.IOException;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.apilog.core.enums.OperateTypeEnum.EXPORT;\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 登录日志\")\n@RestController\n@RequestMapping(\"/system/login-log\")\n@Validated\npublic class LoginLogController {\n\n    @Resource\n    private LoginLogService loginLogService;\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得登录日志分页列表\")\n    @PreAuthorize(\"@ss.hasPermission('system:login-log:query')\")\n    public CommonResult<PageResult<LoginLogRespVO>> getLoginLogPage(@Valid LoginLogPageReqVO pageReqVO) {\n        PageResult<LoginLogDO> pageResult = loginLogService.getLoginLogPage(pageReqVO);\n        return success(BeanUtils.toBean(pageResult, LoginLogRespVO.class));\n    }\n\n    @GetMapping(\"/export\")\n    @Operation(summary = \"导出登录日志 Excel\")\n    @PreAuthorize(\"@ss.hasPermission('system:login-log:export')\")\n    @ApiAccessLog(operateType = EXPORT)\n    public void exportLoginLog(HttpServletResponse response, @Valid LoginLogPageReqVO exportReqVO) throws IOException {\n        exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);\n        List<LoginLogDO> list = loginLogService.getLoginLogPage(exportReqVO).getList();\n        // 输出\n        ExcelUtils.write(response, \"登录日志.xls\", \"数据列表\", LoginLogRespVO.class,\n                BeanUtils.toBean(list, LoginLogRespVO.class));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/logger/OperateLogController.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.logger;\n\nimport co.yixiang.yshop.framework.apilog.core.annotation.ApiAccessLog;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.framework.excel.core.util.ExcelUtils;\nimport co.yixiang.yshop.framework.translate.core.TranslateUtils;\nimport co.yixiang.yshop.module.system.controller.admin.logger.vo.operatelog.OperateLogPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.logger.vo.operatelog.OperateLogRespVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.logger.OperateLogDO;\nimport co.yixiang.yshop.module.system.service.logger.OperateLogService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.annotation.Resource;\nimport jakarta.servlet.http.HttpServletResponse;\nimport jakarta.validation.Valid;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport java.io.IOException;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.apilog.core.enums.OperateTypeEnum.EXPORT;\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 操作日志\")\n@RestController\n@RequestMapping(\"/system/operate-log\")\n@Validated\npublic class OperateLogController {\n\n    @Resource\n    private OperateLogService operateLogService;\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"查看操作日志分页列表\")\n    @PreAuthorize(\"@ss.hasPermission('system:operate-log:query')\")\n    public CommonResult<PageResult<OperateLogRespVO>> pageOperateLog(@Valid OperateLogPageReqVO pageReqVO) {\n        PageResult<OperateLogDO> pageResult = operateLogService.getOperateLogPage(pageReqVO);\n        return success(BeanUtils.toBean(pageResult, OperateLogRespVO.class));\n    }\n\n    @Operation(summary = \"导出操作日志\")\n    @GetMapping(\"/export\")\n    @PreAuthorize(\"@ss.hasPermission('system:operate-log:export')\")\n    @ApiAccessLog(operateType = EXPORT)\n    public void exportOperateLog(HttpServletResponse response, @Valid OperateLogPageReqVO exportReqVO) throws IOException {\n        exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);\n        List<OperateLogDO> list = operateLogService.getOperateLogPage(exportReqVO).getList();\n        ExcelUtils.write(response, \"操作日志.xls\", \"数据列表\", OperateLogRespVO.class,\n                TranslateUtils.translate(BeanUtils.toBean(list, OperateLogRespVO.class)));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/logger/vo/loginlog/LoginLogPageReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.logger.vo.loginlog;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 登录日志分页列表 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\npublic class LoginLogPageReqVO extends PageParam {\n\n    @Schema(description = \"用户 IP，模拟匹配\", example = \"127.0.0.1\")\n    private String userIp;\n\n    @Schema(description = \"用户账号，模拟匹配\", example = \"yshop\")\n    private String username;\n\n    @Schema(description = \"操作状态\", example = \"true\")\n    private Boolean status;\n\n    @Schema(description = \"登录时间\", example = \"[2022-07-01 00:00:00,2022-07-01 23:59:59]\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/logger/vo/loginlog/LoginLogRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.logger.vo.loginlog;\n\nimport co.yixiang.yshop.framework.excel.core.annotations.DictFormat;\nimport co.yixiang.yshop.framework.excel.core.convert.DictConvert;\nimport co.yixiang.yshop.module.system.enums.DictTypeConstants;\nimport com.alibaba.excel.annotation.ExcelIgnoreUnannotated;\nimport com.alibaba.excel.annotation.ExcelProperty;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 登录日志 Response VO\")\n@Data\n@ExcelIgnoreUnannotated\npublic class LoginLogRespVO {\n\n    @Schema(description = \"日志编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @ExcelProperty(\"日志主键\")\n    private Long id;\n\n    @Schema(description = \"日志类型，参见 LoginLogTypeEnum 枚举类\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @ExcelProperty(value = \"日志类型\", converter = DictConvert.class)\n    @DictFormat(DictTypeConstants.LOGIN_TYPE)\n    private Integer logType;\n\n    @Schema(description = \"用户编号\", example = \"666\")\n    private Long userId;\n\n    @Schema(description = \"用户类型，参见 UserTypeEnum 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"2\")\n    private Integer userType;\n\n    @Schema(description = \"链路追踪编号\", example = \"89aca178-a370-411c-ae02-3f0d672be4ab\")\n    private String traceId;\n\n    @Schema(description = \"用户账号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    @ExcelProperty(\"用户账号\")\n    private String username;\n\n    @Schema(description = \"登录结果，参见 LoginResultEnum 枚举类\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @ExcelProperty(value = \"登录结果\", converter = DictConvert.class)\n    @DictFormat(DictTypeConstants.LOGIN_RESULT)\n    private Integer result;\n\n    @Schema(description = \"用户 IP\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"127.0.0.1\")\n    @ExcelProperty(\"登录 IP\")\n    private String userIp;\n\n    @Schema(description = \"浏览器 UserAgent\", example = \"Mozilla/5.0\")\n    @ExcelProperty(\"浏览器 UA\")\n    private String userAgent;\n\n    @Schema(description = \"登录时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @ExcelProperty(\"登录时间\")\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/logger/vo/operatelog/OperateLogPageReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.logger.vo.operatelog;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 操作日志分页列表 Request VO\")\n@Data\npublic class OperateLogPageReqVO extends PageParam {\n\n    @Schema(description = \"用户编号\", example = \"yshop\")\n    private Long userId;\n\n    @Schema(description = \"操作模块业务编号\", example = \"1\")\n    private Long bizId;\n\n    @Schema(description = \"操作模块，模拟匹配\", example = \"订单\")\n    private String type;\n\n    @Schema(description = \"操作名，模拟匹配\", example = \"创建订单\")\n    private String subType;\n\n    @Schema(description = \"操作明细，模拟匹配\", example = \"修改编号为 1 的用户信息\")\n    private String action;\n\n    @Schema(description = \"开始时间\", example = \"[2022-07-01 00:00:00,2022-07-01 23:59:59]\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/logger/vo/operatelog/OperateLogRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.logger.vo.operatelog;\n\nimport co.yixiang.yshop.module.system.dal.dataobject.user.AdminUserDO;\nimport com.alibaba.excel.annotation.ExcelIgnoreUnannotated;\nimport com.alibaba.excel.annotation.ExcelProperty;\nimport com.fhs.core.trans.anno.Trans;\nimport com.fhs.core.trans.constant.TransType;\nimport com.fhs.core.trans.vo.VO;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.NotEmpty;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 操作日志 Response VO\")\n@Data\n@ExcelIgnoreUnannotated\npublic class OperateLogRespVO implements VO {\n\n    @Schema(description = \"日志编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @ExcelProperty(\"日志编号\")\n    private Long id;\n\n    @Schema(description = \"链路追踪编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"89aca178-a370-411c-ae02-3f0d672be4ab\")\n    private String traceId;\n\n    @Schema(description = \"用户编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @Trans(type = TransType.SIMPLE, target = AdminUserDO.class, fields = \"nickname\", ref = \"userName\")\n    private Long userId;\n    @Schema(description = \"用户昵称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    @ExcelProperty(\"操作人\")\n    private String userName;\n\n    @Schema(description = \"操作模块类型\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"订单\")\n    @ExcelProperty(\"操作模块类型\")\n    private String type;\n\n    @Schema(description = \"操作名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"创建订单\")\n    @ExcelProperty(\"操作名\")\n    private String subType;\n\n    @Schema(description = \"操作模块业务编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @ExcelProperty(\"操作模块业务编号\")\n    private Long bizId;\n\n    @Schema(description = \"操作明细\", example = \"修改编号为 1 的用户信息，将性别从男改成女，将姓名从yshop改成源码。\")\n    private String action;\n\n    @Schema(description = \"拓展字段\", example = \"{'orderId': 1}\")\n    private String extra;\n\n    @Schema(description = \"请求方法名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"GET\")\n    @NotEmpty(message = \"请求方法名不能为空\")\n    private String requestMethod;\n\n    @Schema(description = \"请求地址\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"/xxx/yyy\")\n    private String requestUrl;\n\n    @Schema(description = \"用户 IP\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"127.0.0.1\")\n    private String userIp;\n\n    @Schema(description = \"浏览器 UserAgent\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"Mozilla/5.0\")\n    private String userAgent;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/mail/MailAccountController.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.mail;\n\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.controller.admin.mail.vo.account.MailAccountPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.mail.vo.account.MailAccountRespVO;\nimport co.yixiang.yshop.module.system.controller.admin.mail.vo.account.MailAccountSaveReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.mail.vo.account.MailAccountSimpleRespVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.mail.MailAccountDO;\nimport co.yixiang.yshop.module.system.service.mail.MailAccountService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.web.bind.annotation.*;\n\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Valid;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 邮箱账号\")\n@RestController\n@RequestMapping(\"/system/mail-account\")\npublic class MailAccountController {\n\n    @Resource\n    private MailAccountService mailAccountService;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建邮箱账号\")\n    @PreAuthorize(\"@ss.hasPermission('system:mail-account:create')\")\n    public CommonResult<Long> createMailAccount(@Valid @RequestBody MailAccountSaveReqVO createReqVO) {\n        return success(mailAccountService.createMailAccount(createReqVO));\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"修改邮箱账号\")\n    @PreAuthorize(\"@ss.hasPermission('system:mail-account:update')\")\n    public CommonResult<Boolean> updateMailAccount(@Valid @RequestBody MailAccountSaveReqVO updateReqVO) {\n        mailAccountService.updateMailAccount(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除邮箱账号\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('system:mail-account:delete')\")\n    public CommonResult<Boolean> deleteMailAccount(@RequestParam Long id) {\n        mailAccountService.deleteMailAccount(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得邮箱账号\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('system:mail-account:query')\")\n    public CommonResult<MailAccountRespVO> getMailAccount(@RequestParam(\"id\") Long id) {\n        MailAccountDO account = mailAccountService.getMailAccount(id);\n        return success(BeanUtils.toBean(account, MailAccountRespVO.class));\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得邮箱账号分页\")\n    @PreAuthorize(\"@ss.hasPermission('system:mail-account:query')\")\n    public CommonResult<PageResult<MailAccountRespVO>> getMailAccountPage(@Valid MailAccountPageReqVO pageReqVO) {\n        PageResult<MailAccountDO> pageResult = mailAccountService.getMailAccountPage(pageReqVO);\n        return success(BeanUtils.toBean(pageResult, MailAccountRespVO.class));\n    }\n\n    @GetMapping({\"/list-all-simple\", \"simple-list\"})\n    @Operation(summary = \"获得邮箱账号精简列表\")\n    public CommonResult<List<MailAccountSimpleRespVO>> getSimpleMailAccountList() {\n        List<MailAccountDO> list = mailAccountService.getMailAccountList();\n        return success(BeanUtils.toBean(list, MailAccountSimpleRespVO.class));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/mail/MailLogController.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.mail;\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.controller.admin.mail.vo.log.MailLogPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.mail.vo.log.MailLogRespVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.mail.MailLogDO;\nimport co.yixiang.yshop.module.system.service.mail.MailLogService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Valid;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 邮件日志\")\n@RestController\n@RequestMapping(\"/system/mail-log\")\npublic class MailLogController {\n\n    @Resource\n    private MailLogService mailLogService;\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得邮箱日志分页\")\n    @PreAuthorize(\"@ss.hasPermission('system:mail-log:query')\")\n    public CommonResult<PageResult<MailLogRespVO>> getMailLogPage(@Valid MailLogPageReqVO pageVO) {\n        PageResult<MailLogDO> pageResult = mailLogService.getMailLogPage(pageVO);\n        return success(BeanUtils.toBean(pageResult, MailLogRespVO.class));\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得邮箱日志\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('system:mail-log:query')\")\n    public CommonResult<MailLogRespVO> getMailTemplate(@RequestParam(\"id\") Long id) {\n        MailLogDO log = mailLogService.getMailLog(id);\n        return success(BeanUtils.toBean(log, MailLogRespVO.class));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/mail/MailTemplateController.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.mail;\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.controller.admin.mail.vo.template.*;\nimport co.yixiang.yshop.module.system.dal.dataobject.mail.MailTemplateDO;\nimport co.yixiang.yshop.module.system.service.mail.MailSendService;\nimport co.yixiang.yshop.module.system.service.mail.MailTemplateService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.web.bind.annotation.*;\n\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Valid;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\nimport static co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;\n\n@Tag(name = \"管理后台 - 邮件模版\")\n@RestController\n@RequestMapping(\"/system/mail-template\")\npublic class MailTemplateController {\n\n    @Resource\n    private MailTemplateService mailTempleService;\n    @Resource\n    private MailSendService mailSendService;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建邮件模版\")\n    @PreAuthorize(\"@ss.hasPermission('system:mail-template:create')\")\n    public CommonResult<Long> createMailTemplate(@Valid @RequestBody MailTemplateSaveReqVO createReqVO){\n        return success(mailTempleService.createMailTemplate(createReqVO));\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"修改邮件模版\")\n    @PreAuthorize(\"@ss.hasPermission('system:mail-template:update')\")\n    public CommonResult<Boolean> updateMailTemplate(@Valid @RequestBody MailTemplateSaveReqVO updateReqVO){\n        mailTempleService.updateMailTemplate(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除邮件模版\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('system:mail-template:delete')\")\n    public CommonResult<Boolean> deleteMailTemplate(@RequestParam(\"id\") Long id) {\n        mailTempleService.deleteMailTemplate(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得邮件模版\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('system:mail-template:query')\")\n    public CommonResult<MailTemplateRespVO> getMailTemplate(@RequestParam(\"id\") Long id) {\n        MailTemplateDO template = mailTempleService.getMailTemplate(id);\n        return success(BeanUtils.toBean(template, MailTemplateRespVO.class));\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得邮件模版分页\")\n    @PreAuthorize(\"@ss.hasPermission('system:mail-template:query')\")\n    public CommonResult<PageResult<MailTemplateRespVO>> getMailTemplatePage(@Valid MailTemplatePageReqVO pageReqVO) {\n        PageResult<MailTemplateDO> pageResult = mailTempleService.getMailTemplatePage(pageReqVO);\n        return success(BeanUtils.toBean(pageResult, MailTemplateRespVO.class));\n    }\n\n    @GetMapping({\"/list-all-simple\", \"simple-list\"})\n    @Operation(summary = \"获得邮件模版精简列表\")\n    public CommonResult<List<MailTemplateSimpleRespVO>> getSimpleTemplateList() {\n        List<MailTemplateDO> list = mailTempleService.getMailTemplateList();\n        return success(BeanUtils.toBean(list, MailTemplateSimpleRespVO.class));\n    }\n\n    @PostMapping(\"/send-mail\")\n    @Operation(summary = \"发送短信\")\n    @PreAuthorize(\"@ss.hasPermission('system:mail-template:send-mail')\")\n    public CommonResult<Long> sendMail(@Valid @RequestBody MailTemplateSendReqVO sendReqVO) {\n        return success(mailSendService.sendSingleMailToAdmin(sendReqVO.getMail(), getLoginUserId(),\n                sendReqVO.getTemplateCode(), sendReqVO.getTemplateParams()));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/mail/vo/account/MailAccountPageReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.mail.vo.account;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\n\n@Schema(description = \"管理后台 - 邮箱账号分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class MailAccountPageReqVO extends PageParam {\n\n    @Schema(description = \"邮箱\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshopyuanma@123.com\")\n    private String mail;\n\n    @Schema(description = \"用户名\" , requiredMode = Schema.RequiredMode.REQUIRED , example = \"yshop\")\n    private String username;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/mail/vo/account/MailAccountRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.mail.vo.account;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 邮箱账号 Response VO\")\n@Data\npublic class MailAccountRespVO {\n\n    @Schema(description = \"编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"邮箱\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshopyuanma@123.com\")\n    private String mail;\n\n    @Schema(description = \"用户名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    private String username;\n\n    @Schema(description = \"密码\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"123456\")\n    private String password;\n\n    @Schema(description = \"SMTP 服务器域名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"www.yixiang.co\")\n    private String host;\n\n    @Schema(description = \"SMTP 服务器端口\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"80\")\n    private Integer port;\n\n    @Schema(description = \"是否开启 ssl\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"true\")\n    private Boolean sslEnable;\n\n    @Schema(description = \"是否开启 starttls\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"true\")\n    private Boolean starttlsEnable;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/mail/vo/account/MailAccountSaveReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.mail.vo.account;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.Email;\nimport jakarta.validation.constraints.NotNull;\n\n@Schema(description = \"管理后台 - 邮箱账号创建/修改 Request VO\")\n@Data\npublic class MailAccountSaveReqVO {\n\n    @Schema(description = \"编号\", example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"邮箱\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshopyuanma@123.com\")\n    @NotNull(message = \"邮箱不能为空\")\n    @Email(message = \"必须是 Email 格式\")\n    private String mail;\n\n    @Schema(description = \"用户名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    @NotNull(message = \"用户名不能为空\")\n    private String username;\n\n    @Schema(description = \"密码\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"123456\")\n    @NotNull(message = \"密码必填\")\n    private String password;\n\n    @Schema(description = \"SMTP 服务器域名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"www.yixiang.co\")\n    @NotNull(message = \"SMTP 服务器域名不能为空\")\n    private String host;\n\n    @Schema(description = \"SMTP 服务器端口\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"80\")\n    @NotNull(message = \"SMTP 服务器端口不能为空\")\n    private Integer port;\n\n    @Schema(description = \"是否开启 ssl\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"true\")\n    @NotNull(message = \"是否开启 ssl 必填\")\n    private Boolean sslEnable;\n\n    @Schema(description = \"是否开启 starttls\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"true\")\n    @NotNull(message = \"是否开启 starttls 必填\")\n    private Boolean starttlsEnable;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/mail/vo/account/MailAccountSimpleRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.mail.vo.account;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\n@Schema(description = \"管理后台 - 邮箱账号的精简 Response VO\")\n@Data\npublic class MailAccountSimpleRespVO {\n\n    @Schema(description = \"邮箱编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"邮箱\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"768541388@qq.com\")\n    private String mail;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/mail/vo/log/MailLogPageReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.mail.vo.log;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 邮箱日志分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class MailLogPageReqVO extends PageParam {\n\n    @Schema(description = \"用户编号\", example = \"30883\")\n    private Long userId;\n\n    @Schema(description = \"用户类型，参见 UserTypeEnum 枚举\", example = \"2\")\n    private Integer userType;\n\n    @Schema(description = \"接收邮箱地址，模糊匹配\", example = \"76854@qq.com\")\n    private String toMail;\n\n    @Schema(description = \"邮箱账号编号\", example = \"18107\")\n    private Long accountId;\n\n    @Schema(description = \"模板编号\", example = \"5678\")\n    private Long templateId;\n\n    @Schema(description = \"发送状态，参见 MailSendStatusEnum 枚举\", example = \"1\")\n    private Integer sendStatus;\n\n    @Schema(description = \"发送时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] sendTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/mail/vo/log/MailLogRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.mail.vo.log;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport java.time.LocalDateTime;\nimport java.util.Map;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 邮件日志 Response VO\")\n@Data\npublic class MailLogRespVO {\n\n    @Schema(description = \"编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"31020\")\n    private Long id;\n\n    @Schema(description = \"用户编号\", example = \"30883\")\n    private Long userId;\n\n    @Schema(description = \"用户类型，参见 UserTypeEnum 枚举\", example = \"2\")\n    private Byte userType;\n\n    @Schema(description = \"接收邮箱地址\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"76854@qq.com\")\n    private String toMail;\n\n    @Schema(description = \"邮箱账号编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"18107\")\n    private Long accountId;\n\n    @Schema(description = \"发送邮箱地址\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"85757@qq.com\")\n    private String fromMail;\n\n    @Schema(description = \"模板编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"5678\")\n    private Long templateId;\n\n    @Schema(description = \"模板编码\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"test_01\")\n    private String templateCode;\n\n    @Schema(description = \"模版发送人名称\", example = \"李四\")\n    private String templateNickname;\n\n    @Schema(description = \"邮件标题\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"测试标题\")\n    private String templateTitle;\n\n    @Schema(description = \"邮件内容\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"测试内容\")\n    private String templateContent;\n\n    @Schema(description = \"邮件参数\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private Map<String, Object> templateParams;\n\n    @Schema(description = \"发送状态，参见 MailSendStatusEnum 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    private Byte sendStatus;\n\n    @Schema(description = \"发送时间\")\n    private LocalDateTime sendTime;\n\n    @Schema(description = \"发送返回的消息 ID\", example = \"28568\")\n    private String sendMessageId;\n\n    @Schema(description = \"发送异常\")\n    private String sendException;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/mail/vo/template/MailTemplatePageReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.mail.vo.template;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 邮件模版分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class MailTemplatePageReqVO extends PageParam {\n\n    @Schema(description = \"状态，参见 CommonStatusEnum 枚举\", example = \"1\")\n    private Integer status;\n\n    @Schema(description = \"标识，模糊匹配\", example = \"code_1024\")\n    private String code;\n\n    @Schema(description = \"名称，模糊匹配\", example = \"芋头\")\n    private String name;\n\n    @Schema(description = \"账号编号\", example = \"2048\")\n    private Long accountId;\n\n    @Schema(description = \"创建时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/mail/vo/template/MailTemplateRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.mail.vo.template;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\nimport java.util.List;\n\n@Schema(description = \"管理后台 - 邮件末班 Response VO\")\n@Data\npublic class MailTemplateRespVO {\n\n    @Schema(description = \"编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"模版名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"测试名字\")\n    private String name;\n\n    @Schema(description = \"模版编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"test\")\n    private String code;\n\n    @Schema(description = \"发送的邮箱账号编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    private Long accountId;\n\n    @Schema(description = \"发送人名称\", example = \"芋头\")\n    private String nickname;\n\n    @Schema(description = \"标题\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"注册成功\")\n    private String title;\n\n    @Schema(description = \"内容\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"你好，注册成功啦\")\n    private String content;\n\n    @Schema(description = \"参数数组\", example = \"name,code\")\n    private List<String> params;\n\n    @Schema(description = \"状态，参见 CommonStatusEnum 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    private Integer status;\n\n    @Schema(description = \"备注\", example = \"奥特曼\")\n    private String remark;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/mail/vo/template/MailTemplateSaveReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.mail.vo.template;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\n\n@Schema(description = \"管理后台 - 邮件模版创建/修改 Request VO\")\n@Data\npublic class MailTemplateSaveReqVO {\n\n    @Schema(description = \"编号\", example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"模版名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"测试名字\")\n    @NotNull(message = \"名称不能为空\")\n    private String name;\n\n    @Schema(description = \"模版编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"test\")\n    @NotNull(message = \"模版编号不能为空\")\n    private String code;\n\n    @Schema(description = \"发送的邮箱账号编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @NotNull(message = \"发送的邮箱账号编号不能为空\")\n    private Long accountId;\n\n    @Schema(description = \"发送人名称\", example = \"芋头\")\n    private String nickname;\n\n    @Schema(description = \"标题\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"注册成功\")\n    @NotEmpty(message = \"标题不能为空\")\n    private String title;\n\n    @Schema(description = \"内容\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"你好，注册成功啦\")\n    @NotEmpty(message = \"内容不能为空\")\n    private String content;\n\n    @Schema(description = \"状态，参见 CommonStatusEnum 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @NotNull(message = \"状态不能为空\")\n    private Integer status;\n\n    @Schema(description = \"备注\", example = \"奥特曼\")\n    private String remark;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/mail/vo/template/MailTemplateSendReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.mail.vo.template;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\nimport java.util.Map;\n\n@Schema(description = \"管理后台 - 邮件发送 Req VO\")\n@Data\npublic class MailTemplateSendReqVO {\n\n    @Schema(description = \"接收邮箱\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"7685413@qq.com\")\n    @NotEmpty(message = \"接收邮箱不能为空\")\n    private String mail;\n\n    @Schema(description = \"模板编码\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"test_01\")\n    @NotNull(message = \"模板编码不能为空\")\n    private String templateCode;\n\n    @Schema(description = \"模板参数\")\n    private Map<String, Object> templateParams;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/mail/vo/template/MailTemplateSimpleRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.mail.vo.template;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\n@Schema(description = \"管理后台 - 邮件模版的精简 Response VO\")\n@Data\npublic class MailTemplateSimpleRespVO {\n\n    @Schema(description = \"模版编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"模版名字\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"哒哒哒\")\n    private String name;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/notice/NoticeController.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.notice;\n\nimport cn.hutool.core.lang.Assert;\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.infra.api.websocket.WebSocketSenderApi;\nimport co.yixiang.yshop.module.system.controller.admin.notice.vo.NoticePageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.notice.vo.NoticeRespVO;\nimport co.yixiang.yshop.module.system.controller.admin.notice.vo.NoticeSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.notice.NoticeDO;\nimport co.yixiang.yshop.module.system.service.notice.NoticeService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Valid;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 通知公告\")\n@RestController\n@RequestMapping(\"/system/notice\")\n@Validated\npublic class NoticeController {\n\n    @Resource\n    private NoticeService noticeService;\n\n    @Resource\n    private WebSocketSenderApi webSocketSenderApi;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建通知公告\")\n    @PreAuthorize(\"@ss.hasPermission('system:notice:create')\")\n    public CommonResult<Long> createNotice(@Valid @RequestBody NoticeSaveReqVO createReqVO) {\n        Long noticeId = noticeService.createNotice(createReqVO);\n        return success(noticeId);\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"修改通知公告\")\n    @PreAuthorize(\"@ss.hasPermission('system:notice:update')\")\n    public CommonResult<Boolean> updateNotice(@Valid @RequestBody NoticeSaveReqVO updateReqVO) {\n        noticeService.updateNotice(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除通知公告\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('system:notice:delete')\")\n    public CommonResult<Boolean> deleteNotice(@RequestParam(\"id\") Long id) {\n        noticeService.deleteNotice(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获取通知公告列表\")\n    @PreAuthorize(\"@ss.hasPermission('system:notice:query')\")\n    public CommonResult<PageResult<NoticeRespVO>> getNoticePage(@Validated NoticePageReqVO pageReqVO) {\n        PageResult<NoticeDO> pageResult = noticeService.getNoticePage(pageReqVO);\n        return success(BeanUtils.toBean(pageResult, NoticeRespVO.class));\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得通知公告\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('system:notice:query')\")\n    public CommonResult<NoticeRespVO> getNotice(@RequestParam(\"id\") Long id) {\n        NoticeDO notice = noticeService.getNotice(id);\n        return success(BeanUtils.toBean(notice, NoticeRespVO.class));\n    }\n\n    @PostMapping(\"/push\")\n    @Operation(summary = \"推送通知公告\", description = \"只发送给 websocket 连接在线的用户\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('system:notice:update')\")\n    public CommonResult<Boolean> push(@RequestParam(\"id\") Long id) {\n        NoticeDO notice = noticeService.getNotice(id);\n        Assert.notNull(notice, \"公告不能为空\");\n        // 通过 websocket 推送给在线的用户\n        webSocketSenderApi.sendObject(UserTypeEnum.ADMIN.getValue(), \"notice-push\", notice);\n        return success(true);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/notice/vo/NoticePageReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.notice.vo;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\n@Schema(description = \"管理后台 - 通知公告分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\npublic class NoticePageReqVO extends PageParam {\n\n    @Schema(description = \"通知公告名称，模糊匹配\", example = \"yshop\")\n    private String title;\n\n    @Schema(description = \"展示状态，参见 CommonStatusEnum 枚举类\", example = \"1\")\n    private Integer status;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/notice/vo/NoticeRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.notice.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 通知公告信息 Response VO\")\n@Data\npublic class NoticeRespVO {\n\n    @Schema(description = \"通知公告序号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"公告标题\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"小博主\")\n    private String title;\n\n    private String picUrl;\n\n    @Schema(description = \"公告类型\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"小博主\")\n    private Integer type;\n\n    @Schema(description = \"公告内容\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"半生编码\")\n    private String content;\n\n    @Schema(description = \"状态，参见 CommonStatusEnum 枚举类\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    private Integer status;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"时间戳格式\")\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/notice/vo/NoticeSaveReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.notice.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotBlank;\nimport jakarta.validation.constraints.NotNull;\nimport jakarta.validation.constraints.Size;\n\n@Schema(description = \"管理后台 - 通知公告创建/修改 Request VO\")\n@Data\npublic class NoticeSaveReqVO {\n\n    @Schema(description = \"岗位公告编号\", example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"公告标题\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"小博主\")\n    @NotBlank(message = \"公告标题不能为空\")\n    @Size(max = 50, message = \"公告标题不能超过50个字符\")\n    private String title;\n\n\n    @Schema(description = \"公告类型\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"小博主\")\n    @NotNull(message = \"公告类型不能为空\")\n    private Integer type;\n\n    @Schema(description = \"公告内容\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"半生编码\")\n    private String content;\n\n    @Schema(description = \"状态，参见 CommonStatusEnum 枚举类\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    private Integer status;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/notify/NotifyMessageController.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.notify;\n\nimport co.yixiang.yshop.framework.apilog.core.annotation.ApiAccessLog;\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.controller.admin.notify.vo.message.NotifyMessageMyPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.notify.vo.message.NotifyMessagePageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.notify.vo.message.NotifyMessageRespVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.notify.NotifyMessageDO;\nimport co.yixiang.yshop.module.system.service.notify.NotifyMessageService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Valid;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\nimport static co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;\n\n@Tag(name = \"管理后台 - 我的站内信\")\n@RestController\n@RequestMapping(\"/system/notify-message\")\n@Validated\npublic class NotifyMessageController {\n\n    @Resource\n    private NotifyMessageService notifyMessageService;\n\n    // ========== 管理所有的站内信 ==========\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得站内信\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('system:notify-message:query')\")\n    public CommonResult<NotifyMessageRespVO> getNotifyMessage(@RequestParam(\"id\") Long id) {\n        NotifyMessageDO message = notifyMessageService.getNotifyMessage(id);\n        return success(BeanUtils.toBean(message, NotifyMessageRespVO.class));\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得站内信分页\")\n    @PreAuthorize(\"@ss.hasPermission('system:notify-message:query')\")\n    public CommonResult<PageResult<NotifyMessageRespVO>> getNotifyMessagePage(@Valid NotifyMessagePageReqVO pageVO) {\n        PageResult<NotifyMessageDO> pageResult = notifyMessageService.getNotifyMessagePage(pageVO);\n        return success(BeanUtils.toBean(pageResult, NotifyMessageRespVO.class));\n    }\n\n    // ========== 查看自己的站内信 ==========\n\n    @GetMapping(\"/my-page\")\n    @Operation(summary = \"获得我的站内信分页\")\n    public CommonResult<PageResult<NotifyMessageRespVO>> getMyMyNotifyMessagePage(@Valid NotifyMessageMyPageReqVO pageVO) {\n        PageResult<NotifyMessageDO> pageResult = notifyMessageService.getMyMyNotifyMessagePage(pageVO,\n                getLoginUserId(), UserTypeEnum.ADMIN.getValue());\n        return success(BeanUtils.toBean(pageResult, NotifyMessageRespVO.class));\n    }\n\n    @PutMapping(\"/update-read\")\n    @Operation(summary = \"标记站内信为已读\")\n    @Parameter(name = \"ids\", description = \"编号列表\", required = true, example = \"1024,2048\")\n    public CommonResult<Boolean> updateNotifyMessageRead(@RequestParam(\"ids\") List<Long> ids) {\n        notifyMessageService.updateNotifyMessageRead(ids, getLoginUserId(), UserTypeEnum.ADMIN.getValue());\n        return success(Boolean.TRUE);\n    }\n\n    @PutMapping(\"/update-all-read\")\n    @Operation(summary = \"标记所有站内信为已读\")\n    public CommonResult<Boolean> updateAllNotifyMessageRead() {\n        notifyMessageService.updateAllNotifyMessageRead(getLoginUserId(), UserTypeEnum.ADMIN.getValue());\n        return success(Boolean.TRUE);\n    }\n\n    @GetMapping(\"/get-unread-list\")\n    @Operation(summary = \"获取当前用户的最新站内信列表，默认 10 条\")\n    @Parameter(name = \"size\", description = \"10\")\n    public CommonResult<List<NotifyMessageRespVO>> getUnreadNotifyMessageList(\n            @RequestParam(name = \"size\", defaultValue = \"10\") Integer size) {\n        List<NotifyMessageDO> list = notifyMessageService.getUnreadNotifyMessageList(\n                getLoginUserId(), UserTypeEnum.ADMIN.getValue(), size);\n        return success(BeanUtils.toBean(list, NotifyMessageRespVO.class));\n    }\n\n    @GetMapping(\"/get-unread-count\")\n    @Operation(summary = \"获得当前用户的未读站内信数量\")\n    @ApiAccessLog(enable = false) // 由于前端会不断轮询该接口，记录日志没有意义\n    public CommonResult<Long> getUnreadNotifyMessageCount() {\n        return success(notifyMessageService.getUnreadNotifyMessageCount(\n                getLoginUserId(), UserTypeEnum.ADMIN.getValue()));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/notify/NotifyTemplateController.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.notify;\n\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.controller.admin.notify.vo.template.*;\nimport co.yixiang.yshop.module.system.dal.dataobject.notify.NotifyTemplateDO;\nimport co.yixiang.yshop.module.system.service.notify.NotifySendService;\nimport co.yixiang.yshop.module.system.service.notify.NotifyTemplateService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Valid;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 站内信模版\")\n@RestController\n@RequestMapping(\"/system/notify-template\")\n@Validated\npublic class NotifyTemplateController {\n\n    @Resource\n    private NotifyTemplateService notifyTemplateService;\n\n    @Resource\n    private NotifySendService notifySendService;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建站内信模版\")\n    @PreAuthorize(\"@ss.hasPermission('system:notify-template:create')\")\n    public CommonResult<Long> createNotifyTemplate(@Valid @RequestBody NotifyTemplateSaveReqVO createReqVO) {\n        return success(notifyTemplateService.createNotifyTemplate(createReqVO));\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"更新站内信模版\")\n    @PreAuthorize(\"@ss.hasPermission('system:notify-template:update')\")\n    public CommonResult<Boolean> updateNotifyTemplate(@Valid @RequestBody NotifyTemplateSaveReqVO updateReqVO) {\n        notifyTemplateService.updateNotifyTemplate(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除站内信模版\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('system:notify-template:delete')\")\n    public CommonResult<Boolean> deleteNotifyTemplate(@RequestParam(\"id\") Long id) {\n        notifyTemplateService.deleteNotifyTemplate(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得站内信模版\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('system:notify-template:query')\")\n    public CommonResult<NotifyTemplateRespVO> getNotifyTemplate(@RequestParam(\"id\") Long id) {\n        NotifyTemplateDO template = notifyTemplateService.getNotifyTemplate(id);\n        return success(BeanUtils.toBean(template, NotifyTemplateRespVO.class));\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得站内信模版分页\")\n    @PreAuthorize(\"@ss.hasPermission('system:notify-template:query')\")\n    public CommonResult<PageResult<NotifyTemplateRespVO>> getNotifyTemplatePage(@Valid NotifyTemplatePageReqVO pageVO) {\n        PageResult<NotifyTemplateDO> pageResult = notifyTemplateService.getNotifyTemplatePage(pageVO);\n        return success(BeanUtils.toBean(pageResult, NotifyTemplateRespVO.class));\n    }\n\n    @PostMapping(\"/send-notify\")\n    @Operation(summary = \"发送站内信\")\n    @PreAuthorize(\"@ss.hasPermission('system:notify-template:send-notify')\")\n    public CommonResult<Long> sendNotify(@Valid @RequestBody NotifyTemplateSendReqVO sendReqVO) {\n        if (UserTypeEnum.MEMBER.getValue().equals(sendReqVO.getUserType())) {\n            return success(notifySendService.sendSingleNotifyToMember(sendReqVO.getUserId(),\n                    sendReqVO.getTemplateCode(), sendReqVO.getTemplateParams()));\n        } else {\n            return success(notifySendService.sendSingleNotifyToAdmin(sendReqVO.getUserId(),\n                    sendReqVO.getTemplateCode(), sendReqVO.getTemplateParams()));\n        }\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/notify/vo/message/NotifyMessageMyPageReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.notify.vo.message;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 站内信分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class NotifyMessageMyPageReqVO extends PageParam {\n\n    @Schema(description = \"是否已读\", example = \"true\")\n    private Boolean readStatus;\n\n    @Schema(description = \"创建时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/notify/vo/message/NotifyMessagePageReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.notify.vo.message;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 站内信分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class NotifyMessagePageReqVO extends PageParam {\n\n    @Schema(description = \"用户编号\", example = \"25025\")\n    private Long userId;\n\n    @Schema(description = \"用户类型\", example = \"1\")\n    private Integer userType;\n\n    @Schema(description = \"模板编码\", example = \"test_01\")\n    private String templateCode;\n\n    @Schema(description = \"模版类型\", example = \"2\")\n    private Integer templateType;\n\n    @Schema(description = \"创建时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/notify/vo/message/NotifyMessageRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.notify.vo.message;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\nimport java.util.Map;\n\n@Schema(description = \"管理后台 - 站内信 Response VO\")\n@Data\npublic class NotifyMessageRespVO {\n\n    @Schema(description = \"ID\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"用户编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"25025\")\n    private Long userId;\n\n    @Schema(description = \"用户类型，参见 UserTypeEnum 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    private Byte userType;\n\n    @Schema(description = \"模版编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"13013\")\n    private Long templateId;\n\n    @Schema(description = \"模板编码\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"test_01\")\n    private String templateCode;\n\n    @Schema(description = \"模版发送人名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    private String templateNickname;\n\n    @Schema(description = \"模版内容\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"测试内容\")\n    private String templateContent;\n\n    @Schema(description = \"模版类型\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"2\")\n    private Integer templateType;\n\n    @Schema(description = \"模版参数\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private Map<String, Object> templateParams;\n\n    @Schema(description = \"是否已读\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"true\")\n    private Boolean readStatus;\n\n    @Schema(description = \"阅读时间\")\n    private LocalDateTime readTime;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/notify/vo/template/NotifyTemplatePageReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.notify.vo.template;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 站内信模版分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class NotifyTemplatePageReqVO extends PageParam {\n\n    @Schema(description = \"模版编码\", example = \"test_01\")\n    private String code;\n\n    @Schema(description = \"模版名称\", example = \"我是名称\")\n    private String name;\n\n    @Schema(description = \"状态，参见 CommonStatusEnum 枚举类\", example = \"1\")\n    private Integer status;\n\n    @Schema(description = \"创建时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/notify/vo/template/NotifyTemplateRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.notify.vo.template;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\nimport java.util.List;\n\n@Schema(description = \"管理后台 - 站内信模版 Response VO\")\n@Data\npublic class NotifyTemplateRespVO {\n\n    @Schema(description = \"ID\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"模版名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"测试模版\")\n    private String name;\n\n    @Schema(description = \"模版编码\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"SEND_TEST\")\n    private String code;\n\n    @Schema(description = \"模版类型，对应 system_notify_template_type 字典\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    private Integer type;\n\n    @Schema(description = \"发送人名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"土豆\")\n    private String nickname;\n\n    @Schema(description = \"模版内容\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"我是模版内容\")\n    private String content;\n\n    @Schema(description = \"参数数组\", example = \"name,code\")\n    private List<String> params;\n\n    @Schema(description = \"状态，参见 CommonStatusEnum 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    private Integer status;\n\n    @Schema(description = \"备注\", example = \"我是备注\")\n    private String remark;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/notify/vo/template/NotifyTemplateSaveReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.notify.vo.template;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.validation.InEnum;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\n\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\n\n@Schema(description = \"管理后台 - 站内信模版创建/修改 Request VO\")\n@Data\npublic class NotifyTemplateSaveReqVO {\n\n    @Schema(description = \"ID\", example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"模版名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"测试模版\")\n    @NotEmpty(message = \"模版名称不能为空\")\n    private String name;\n\n    @Schema(description = \"模版编码\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"SEND_TEST\")\n    @NotNull(message = \"模版编码不能为空\")\n    private String code;\n\n    @Schema(description = \"模版类型，对应 system_notify_template_type 字典\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @NotNull(message = \"模版类型不能为空\")\n    private Integer type;\n\n    @Schema(description = \"发送人名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"土豆\")\n    @NotEmpty(message = \"发送人名称不能为空\")\n    private String nickname;\n\n    @Schema(description = \"模版内容\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"我是模版内容\")\n    @NotEmpty(message = \"模版内容不能为空\")\n    private String content;\n\n    @Schema(description = \"状态，参见 CommonStatusEnum 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @NotNull(message = \"状态不能为空\")\n    @InEnum(value = CommonStatusEnum.class, message = \"状态必须是 {value}\")\n    private Integer status;\n\n    @Schema(description = \"备注\", example = \"我是备注\")\n    private String remark;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/notify/vo/template/NotifyTemplateSendReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.notify.vo.template;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\nimport java.util.Map;\n\n@Schema(description = \"管理后台 - 站内信模板的发送 Request VO\")\n@Data\npublic class NotifyTemplateSendReqVO {\n\n    @Schema(description = \"用户id\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"01\")\n    @NotNull(message = \"用户id不能为空\")\n    private Long userId;\n\n    @Schema(description = \"用户类型\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @NotNull(message = \"用户类型不能为空\")\n    private Integer userType;\n\n    @Schema(description = \"模板编码\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"01\")\n    @NotEmpty(message = \"模板编码不能为空\")\n    private String templateCode;\n\n    @Schema(description = \"模板参数\")\n    private Map<String, Object> templateParams;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/oauth2/OAuth2ClientController.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.oauth2;\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.controller.admin.oauth2.vo.client.OAuth2ClientPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.oauth2.vo.client.OAuth2ClientRespVO;\nimport co.yixiang.yshop.module.system.controller.admin.oauth2.vo.client.OAuth2ClientSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2ClientDO;\nimport co.yixiang.yshop.module.system.service.oauth2.OAuth2ClientService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Valid;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - OAuth2 客户端\")\n@RestController\n@RequestMapping(\"/system/oauth2-client\")\n@Validated\npublic class OAuth2ClientController {\n\n    @Resource\n    private OAuth2ClientService oAuth2ClientService;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建 OAuth2 客户端\")\n    @PreAuthorize(\"@ss.hasPermission('system:oauth2-client:create')\")\n    public CommonResult<Long> createOAuth2Client(@Valid @RequestBody OAuth2ClientSaveReqVO createReqVO) {\n        return success(oAuth2ClientService.createOAuth2Client(createReqVO));\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"更新 OAuth2 客户端\")\n    @PreAuthorize(\"@ss.hasPermission('system:oauth2-client:update')\")\n    public CommonResult<Boolean> updateOAuth2Client(@Valid @RequestBody OAuth2ClientSaveReqVO updateReqVO) {\n        oAuth2ClientService.updateOAuth2Client(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除 OAuth2 客户端\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('system:oauth2-client:delete')\")\n    public CommonResult<Boolean> deleteOAuth2Client(@RequestParam(\"id\") Long id) {\n        oAuth2ClientService.deleteOAuth2Client(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得 OAuth2 客户端\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('system:oauth2-client:query')\")\n    public CommonResult<OAuth2ClientRespVO> getOAuth2Client(@RequestParam(\"id\") Long id) {\n        OAuth2ClientDO client = oAuth2ClientService.getOAuth2Client(id);\n        return success(BeanUtils.toBean(client, OAuth2ClientRespVO.class));\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得 OAuth2 客户端分页\")\n    @PreAuthorize(\"@ss.hasPermission('system:oauth2-client:query')\")\n    public CommonResult<PageResult<OAuth2ClientRespVO>> getOAuth2ClientPage(@Valid OAuth2ClientPageReqVO pageVO) {\n        PageResult<OAuth2ClientDO> pageResult = oAuth2ClientService.getOAuth2ClientPage(pageVO);\n        return success(BeanUtils.toBean(pageResult, OAuth2ClientRespVO.class));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/oauth2/OAuth2OpenController.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.oauth2;\n\nimport cn.hutool.core.lang.Assert;\nimport cn.hutool.core.util.ArrayUtil;\nimport cn.hutool.core.util.ObjectUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.util.http.HttpUtils;\nimport co.yixiang.yshop.framework.common.util.json.JsonUtils;\nimport co.yixiang.yshop.module.system.controller.admin.oauth2.vo.open.OAuth2OpenAccessTokenRespVO;\nimport co.yixiang.yshop.module.system.controller.admin.oauth2.vo.open.OAuth2OpenAuthorizeInfoRespVO;\nimport co.yixiang.yshop.module.system.controller.admin.oauth2.vo.open.OAuth2OpenCheckTokenRespVO;\nimport co.yixiang.yshop.module.system.convert.oauth2.OAuth2OpenConvert;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2ApproveDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2ClientDO;\nimport co.yixiang.yshop.module.system.enums.oauth2.OAuth2GrantTypeEnum;\nimport co.yixiang.yshop.module.system.service.oauth2.OAuth2ApproveService;\nimport co.yixiang.yshop.module.system.service.oauth2.OAuth2ClientService;\nimport co.yixiang.yshop.module.system.service.oauth2.OAuth2GrantService;\nimport co.yixiang.yshop.module.system.service.oauth2.OAuth2TokenService;\nimport co.yixiang.yshop.module.system.util.oauth2.OAuth2Utils;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.Parameters;\nimport io.swagger.v3.oas.annotations.Operation;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport jakarta.annotation.Resource;\nimport jakarta.annotation.security.PermitAll;\nimport jakarta.servlet.http.HttpServletRequest;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\n\nimport static co.yixiang.yshop.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception0;\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\nimport static co.yixiang.yshop.framework.common.util.collection.CollectionUtils.convertList;\nimport static co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;\n\n/**\n * 提供给外部应用调用为主\n *\n * 一般来说，管理后台的 /system-api/* 是不直接提供给外部应用使用，主要是外部应用能够访问的数据与接口是有限的，而管理后台的 RBAC 无法很好的控制。\n * 参考大量的开放平台，都是独立的一套 OpenAPI，对应到【本系统】就是在 Controller 下新建 open 包，实现 /open-api/* 接口，然后通过 scope 进行控制。\n * 另外，一个公司如果有多个管理后台，它们 client_id 产生的 access token 相互之间是无法互通的，即无法访问它们系统的 API 接口，直到两个 client_id 产生信任授权。\n *\n * 考虑到【本系统】暂时不想做的过于复杂，默认只有获取到 access token 之后，可以访问【本系统】管理后台的 /system-api/* 所有接口，除非手动添加 scope 控制。\n * scope 的使用示例，可见 {@link OAuth2UserController} 类\n *\n * @author yshop\n */\n@Tag(name = \"管理后台 - OAuth2.0 授权\")\n@RestController\n@RequestMapping(\"/system/oauth2\")\n@Validated\n@Slf4j\npublic class OAuth2OpenController {\n\n    @Resource\n    private OAuth2GrantService oauth2GrantService;\n    @Resource\n    private OAuth2ClientService oauth2ClientService;\n    @Resource\n    private OAuth2ApproveService oauth2ApproveService;\n    @Resource\n    private OAuth2TokenService oauth2TokenService;\n\n    /**\n     * 对应 Spring Security OAuth 的 TokenEndpoint 类的 postAccessToken 方法\n     *\n     * 授权码 authorization_code 模式时：code + redirectUri + state 参数\n     * 密码 password 模式时：username + password + scope 参数\n     * 刷新 refresh_token 模式时：refreshToken 参数\n     * 客户端 client_credentials 模式：scope 参数\n     * 简化 implicit 模式时：不支持\n     *\n     * 注意，默认需要传递 client_id + client_secret 参数\n     */\n    @PostMapping(\"/token\")\n    @PermitAll\n    @Operation(summary = \"获得访问令牌\", description = \"适合 code 授权码模式，或者 implicit 简化模式；在 sso.vue 单点登录界面被【获取】调用\")\n    @Parameters({\n            @Parameter(name = \"grant_type\", required = true, description = \"授权类型\", example = \"code\"),\n            @Parameter(name = \"code\", description = \"授权范围\", example = \"userinfo.read\"),\n            @Parameter(name = \"redirect_uri\", description = \"重定向 URI\", example = \"https://www.yixiang.co\"),\n            @Parameter(name = \"state\", description = \"状态\", example = \"1\"),\n            @Parameter(name = \"username\", example = \"tudou\"),\n            @Parameter(name = \"password\", example = \"cai\"), // 多个使用空格分隔\n            @Parameter(name = \"scope\", example = \"user_info\"),\n            @Parameter(name = \"refresh_token\", example = \"123424233\"),\n    })\n    public CommonResult<OAuth2OpenAccessTokenRespVO> postAccessToken(HttpServletRequest request,\n                                                                     @RequestParam(\"grant_type\") String grantType,\n                                                                     @RequestParam(value = \"code\", required = false) String code, // 授权码模式\n                                                                     @RequestParam(value = \"redirect_uri\", required = false) String redirectUri, // 授权码模式\n                                                                     @RequestParam(value = \"state\", required = false) String state, // 授权码模式\n                                                                     @RequestParam(value = \"username\", required = false) String username, // 密码模式\n                                                                     @RequestParam(value = \"password\", required = false) String password, // 密码模式\n                                                                     @RequestParam(value = \"scope\", required = false) String scope, // 密码模式\n                                                                     @RequestParam(value = \"refresh_token\", required = false) String refreshToken) { // 刷新模式\n        List<String> scopes = OAuth2Utils.buildScopes(scope);\n        // 1.1 校验授权类型\n        OAuth2GrantTypeEnum grantTypeEnum = OAuth2GrantTypeEnum.getByGranType(grantType);\n        if (grantTypeEnum == null) {\n            throw exception0(BAD_REQUEST.getCode(), StrUtil.format(\"未知授权类型({})\", grantType));\n        }\n        if (grantTypeEnum == OAuth2GrantTypeEnum.IMPLICIT) {\n            throw exception0(BAD_REQUEST.getCode(), \"Token 接口不支持 implicit 授权模式\");\n        }\n\n        // 1.2 校验客户端\n        String[] clientIdAndSecret = obtainBasicAuthorization(request);\n        OAuth2ClientDO client = oauth2ClientService.validOAuthClientFromCache(clientIdAndSecret[0], clientIdAndSecret[1],\n                grantType, scopes, redirectUri);\n\n        // 2. 根据授权模式，获取访问令牌\n        OAuth2AccessTokenDO accessTokenDO;\n        switch (grantTypeEnum) {\n            case AUTHORIZATION_CODE:\n                accessTokenDO = oauth2GrantService.grantAuthorizationCodeForAccessToken(client.getClientId(), code, redirectUri, state);\n                break;\n            case PASSWORD:\n                accessTokenDO = oauth2GrantService.grantPassword(username, password, client.getClientId(), scopes);\n                break;\n            case CLIENT_CREDENTIALS:\n                accessTokenDO = oauth2GrantService.grantClientCredentials(client.getClientId(), scopes);\n                break;\n            case REFRESH_TOKEN:\n                accessTokenDO = oauth2GrantService.grantRefreshToken(refreshToken, client.getClientId());\n                break;\n            default:\n                throw new IllegalArgumentException(\"未知授权类型：\" + grantType);\n        }\n        Assert.notNull(accessTokenDO, \"访问令牌不能为空\"); // 防御性检查\n        return success(OAuth2OpenConvert.INSTANCE.convert(accessTokenDO));\n    }\n\n    @DeleteMapping(\"/token\")\n    @PermitAll\n    @Operation(summary = \"删除访问令牌\")\n    @Parameter(name = \"token\", required = true, description = \"访问令牌\", example = \"biu\")\n    public CommonResult<Boolean> revokeToken(HttpServletRequest request,\n                                             @RequestParam(\"token\") String token) {\n        // 校验客户端\n        String[] clientIdAndSecret = obtainBasicAuthorization(request);\n        OAuth2ClientDO client = oauth2ClientService.validOAuthClientFromCache(clientIdAndSecret[0], clientIdAndSecret[1],\n                null, null, null);\n\n        // 删除访问令牌\n        return success(oauth2GrantService.revokeToken(client.getClientId(), token));\n    }\n\n    /**\n     * 对应 Spring Security OAuth 的 CheckTokenEndpoint 类的 checkToken 方法\n     */\n    @PostMapping(\"/check-token\")\n    @PermitAll\n    @Operation(summary = \"校验访问令牌\")\n    @Parameter(name = \"token\", required = true, description = \"访问令牌\", example = \"biu\")\n    public CommonResult<OAuth2OpenCheckTokenRespVO> checkToken(HttpServletRequest request,\n                                                               @RequestParam(\"token\") String token) {\n        // 校验客户端\n        String[] clientIdAndSecret = obtainBasicAuthorization(request);\n        oauth2ClientService.validOAuthClientFromCache(clientIdAndSecret[0], clientIdAndSecret[1],\n                null, null, null);\n\n        // 校验令牌\n        OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.checkAccessToken(token);\n        Assert.notNull(accessTokenDO, \"访问令牌不能为空\"); // 防御性检查\n        return success(OAuth2OpenConvert.INSTANCE.convert2(accessTokenDO));\n    }\n\n    /**\n     * 对应 Spring Security OAuth 的 AuthorizationEndpoint 类的 authorize 方法\n     */\n    @GetMapping(\"/authorize\")\n    @Operation(summary = \"获得授权信息\", description = \"适合 code 授权码模式，或者 implicit 简化模式；在 sso.vue 单点登录界面被【获取】调用\")\n    @Parameter(name = \"clientId\", required = true, description = \"客户端编号\", example = \"tudou\")\n    public CommonResult<OAuth2OpenAuthorizeInfoRespVO> authorize(@RequestParam(\"clientId\") String clientId) {\n        // 0. 校验用户已经登录。通过 Spring Security 实现\n\n        // 1. 获得 Client 客户端的信息\n        OAuth2ClientDO client = oauth2ClientService.validOAuthClientFromCache(clientId);\n        // 2. 获得用户已经授权的信息\n        List<OAuth2ApproveDO> approves = oauth2ApproveService.getApproveList(getLoginUserId(), getUserType(), clientId);\n        // 拼接返回\n        return success(OAuth2OpenConvert.INSTANCE.convert(client, approves));\n    }\n\n    /**\n     * 对应 Spring Security OAuth 的 AuthorizationEndpoint 类的 approveOrDeny 方法\n     *\n     * 场景一：【自动授权 autoApprove = true】\n     *      刚进入 sso.vue 界面，调用该接口，用户历史已经给该应用做过对应的授权，或者 OAuth2Client 支持该 scope 的自动授权\n     * 场景二：【手动授权 autoApprove = false】\n     *      在 sso.vue 界面，用户选择好 scope 授权范围，调用该接口，进行授权。此时，approved 为 true 或者 false\n     *\n     * 因为前后端分离，Axios 无法很好的处理 302 重定向，所以和 Spring Security OAuth 略有不同，返回结果是重定向的 URL，剩余交给前端处理\n     */\n    @PostMapping(\"/authorize\")\n    @Operation(summary = \"申请授权\", description = \"适合 code 授权码模式，或者 implicit 简化模式；在 sso.vue 单点登录界面被【提交】调用\")\n    @Parameters({\n            @Parameter(name = \"response_type\", required = true, description = \"响应类型\", example = \"code\"),\n            @Parameter(name = \"client_id\", required = true, description = \"客户端编号\", example = \"tudou\"),\n            @Parameter(name = \"scope\", description = \"授权范围\", example = \"userinfo.read\"), // 使用 Map<String, Boolean> 格式，Spring MVC 暂时不支持这么接收参数\n            @Parameter(name = \"redirect_uri\", required = true, description = \"重定向 URI\", example = \"https://www.yixiang.co\"),\n            @Parameter(name = \"auto_approve\", required = true, description = \"用户是否接受\", example = \"true\"),\n            @Parameter(name = \"state\", example = \"1\")\n    })\n    public CommonResult<String> approveOrDeny(@RequestParam(\"response_type\") String responseType,\n                                              @RequestParam(\"client_id\") String clientId,\n                                              @RequestParam(value = \"scope\", required = false) String scope,\n                                              @RequestParam(\"redirect_uri\") String redirectUri,\n                                              @RequestParam(value = \"auto_approve\") Boolean autoApprove,\n                                              @RequestParam(value = \"state\", required = false) String state) {\n        @SuppressWarnings(\"unchecked\")\n        Map<String, Boolean> scopes = JsonUtils.parseObject(scope, Map.class);\n        scopes = ObjectUtil.defaultIfNull(scopes, Collections.emptyMap());\n        // 0. 校验用户已经登录。通过 Spring Security 实现\n\n        // 1.1 校验 responseType 是否满足 code 或者 token 值\n        OAuth2GrantTypeEnum grantTypeEnum = getGrantTypeEnum(responseType);\n        // 1.2 校验 redirectUri 重定向域名是否合法 + 校验 scope 是否在 Client 授权范围内\n        OAuth2ClientDO client = oauth2ClientService.validOAuthClientFromCache(clientId, null,\n                grantTypeEnum.getGrantType(), scopes.keySet(), redirectUri);\n\n        // 2.1 假设 approved 为 null，说明是场景一\n        if (Boolean.TRUE.equals(autoApprove)) {\n            // 如果无法自动授权通过，则返回空 url，前端不进行跳转\n            if (!oauth2ApproveService.checkForPreApproval(getLoginUserId(), getUserType(), clientId, scopes.keySet())) {\n                return success(null);\n            }\n        } else { // 2.2 假设 approved 非 null，说明是场景二\n            // 如果计算后不通过，则跳转一个错误链接\n            if (!oauth2ApproveService.updateAfterApproval(getLoginUserId(), getUserType(), clientId, scopes)) {\n                return success(OAuth2Utils.buildUnsuccessfulRedirect(redirectUri, responseType, state,\n                        \"access_denied\", \"User denied access\"));\n            }\n        }\n\n        // 3.1 如果是 code 授权码模式，则发放 code 授权码，并重定向\n        List<String> approveScopes = convertList(scopes.entrySet(), Map.Entry::getKey, Map.Entry::getValue);\n        if (grantTypeEnum == OAuth2GrantTypeEnum.AUTHORIZATION_CODE) {\n            return success(getAuthorizationCodeRedirect(getLoginUserId(), client, approveScopes, redirectUri, state));\n        }\n        // 3.2 如果是 token 则是 implicit 简化模式，则发送 accessToken 访问令牌，并重定向\n        return success(getImplicitGrantRedirect(getLoginUserId(), client, approveScopes, redirectUri, state));\n    }\n\n    private static OAuth2GrantTypeEnum getGrantTypeEnum(String responseType) {\n        if (StrUtil.equals(responseType, \"code\")) {\n            return OAuth2GrantTypeEnum.AUTHORIZATION_CODE;\n        }\n        if (StrUtil.equalsAny(responseType, \"token\")) {\n            return OAuth2GrantTypeEnum.IMPLICIT;\n        }\n        throw exception0(BAD_REQUEST.getCode(), \"response_type 参数值只允许 code 和 token\");\n    }\n\n    private String getImplicitGrantRedirect(Long userId, OAuth2ClientDO client,\n                                            List<String> scopes, String redirectUri, String state) {\n        // 1. 创建 access token 访问令牌\n        OAuth2AccessTokenDO accessTokenDO = oauth2GrantService.grantImplicit(userId, getUserType(), client.getClientId(), scopes);\n        Assert.notNull(accessTokenDO, \"访问令牌不能为空\"); // 防御性检查\n        // 2. 拼接重定向的 URL\n        // noinspection unchecked\n        return OAuth2Utils.buildImplicitRedirectUri(redirectUri, accessTokenDO.getAccessToken(), state, accessTokenDO.getExpiresTime(),\n                scopes, JsonUtils.parseObject(client.getAdditionalInformation(), Map.class));\n    }\n\n    private String getAuthorizationCodeRedirect(Long userId, OAuth2ClientDO client,\n                                                List<String> scopes, String redirectUri, String state) {\n        // 1. 创建 code 授权码\n        String authorizationCode = oauth2GrantService.grantAuthorizationCodeForCode(userId, getUserType(), client.getClientId(), scopes,\n                redirectUri, state);\n        // 2. 拼接重定向的 URL\n        return OAuth2Utils.buildAuthorizationCodeRedirectUri(redirectUri, authorizationCode, state);\n    }\n\n    private Integer getUserType() {\n        return UserTypeEnum.ADMIN.getValue();\n    }\n\n    private String[] obtainBasicAuthorization(HttpServletRequest request) {\n        String[] clientIdAndSecret = HttpUtils.obtainBasicAuthorization(request);\n        if (ArrayUtil.isEmpty(clientIdAndSecret) || clientIdAndSecret.length != 2) {\n            throw exception0(BAD_REQUEST.getCode(), \"client_id 或 client_secret 未正确传递\");\n        }\n        return clientIdAndSecret;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/oauth2/OAuth2TokenController.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.oauth2;\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.controller.admin.oauth2.vo.token.OAuth2AccessTokenPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.oauth2.vo.token.OAuth2AccessTokenRespVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;\nimport co.yixiang.yshop.module.system.enums.logger.LoginLogTypeEnum;\nimport co.yixiang.yshop.module.system.service.auth.AdminAuthService;\nimport co.yixiang.yshop.module.system.service.oauth2.OAuth2TokenService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.web.bind.annotation.*;\n\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Valid;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - OAuth2.0 令牌\")\n@RestController\n@RequestMapping(\"/system/oauth2-token\")\npublic class OAuth2TokenController {\n\n    @Resource\n    private OAuth2TokenService oauth2TokenService;\n    @Resource\n    private AdminAuthService authService;\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得访问令牌分页\", description = \"只返回有效期内的\")\n    @PreAuthorize(\"@ss.hasPermission('system:oauth2-token:page')\")\n    public CommonResult<PageResult<OAuth2AccessTokenRespVO>> getAccessTokenPage(@Valid OAuth2AccessTokenPageReqVO reqVO) {\n        PageResult<OAuth2AccessTokenDO> pageResult = oauth2TokenService.getAccessTokenPage(reqVO);\n        return success(BeanUtils.toBean(pageResult, OAuth2AccessTokenRespVO.class));\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除访问令牌\")\n    @Parameter(name = \"accessToken\", description = \"访问令牌\", required = true, example = \"tudou\")\n    @PreAuthorize(\"@ss.hasPermission('system:oauth2-token:delete')\")\n    public CommonResult<Boolean> deleteAccessToken(@RequestParam(\"accessToken\") String accessToken) {\n        authService.logout(accessToken, LoginLogTypeEnum.LOGOUT_DELETE.getType());\n        return success(true);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/oauth2/OAuth2UserController.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.oauth2;\n\nimport cn.hutool.core.collection.CollUtil;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.controller.admin.oauth2.vo.user.OAuth2UserInfoRespVO;\nimport co.yixiang.yshop.module.system.controller.admin.oauth2.vo.user.OAuth2UserUpdateReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.user.vo.profile.UserProfileUpdateReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.dept.DeptDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.dept.PostDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.user.AdminUserDO;\nimport co.yixiang.yshop.module.system.service.dept.DeptService;\nimport co.yixiang.yshop.module.system.service.dept.PostService;\nimport co.yixiang.yshop.module.system.service.user.AdminUserService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Valid;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\nimport static co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;\n\n/**\n * 提供给外部应用调用为主\n *\n * 1. 在 getUserInfo 方法上，添加 @PreAuthorize(\"@ss.hasScope('user.read')\") 注解，声明需要满足 scope = user.read\n * 2. 在 updateUserInfo 方法上，添加 @PreAuthorize(\"@ss.hasScope('user.write')\") 注解，声明需要满足 scope = user.write\n *\n * @author yshop\n */\n@Tag(name = \"管理后台 - OAuth2.0 用户\")\n@RestController\n@RequestMapping(\"/system/oauth2/user\")\n@Validated\n@Slf4j\npublic class OAuth2UserController {\n\n    @Resource\n    private AdminUserService userService;\n    @Resource\n    private DeptService deptService;\n    @Resource\n    private PostService postService;\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得用户基本信息\")\n    @PreAuthorize(\"@ss.hasScope('user.read')\") //\n    public CommonResult<OAuth2UserInfoRespVO> getUserInfo() {\n        // 获得用户基本信息\n        AdminUserDO user = userService.getUser(getLoginUserId());\n        OAuth2UserInfoRespVO resp = BeanUtils.toBean(user, OAuth2UserInfoRespVO.class);\n        // 获得部门信息\n        if (user.getDeptId() != null) {\n            DeptDO dept = deptService.getDept(user.getDeptId());\n            resp.setDept(BeanUtils.toBean(dept, OAuth2UserInfoRespVO.Dept.class));\n        }\n        // 获得岗位信息\n        if (CollUtil.isNotEmpty(user.getPostIds())) {\n            List<PostDO> posts = postService.getPostList(user.getPostIds());\n            resp.setPosts(BeanUtils.toBean(posts, OAuth2UserInfoRespVO.Post.class));\n        }\n        return success(resp);\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"更新用户基本信息\")\n    @PreAuthorize(\"@ss.hasScope('user.write')\")\n    public CommonResult<Boolean> updateUserInfo(@Valid @RequestBody OAuth2UserUpdateReqVO reqVO) {\n        // 这里将 UserProfileUpdateReqVO =》UserProfileUpdateReqVO 对象，实现接口的复用。\n        // 主要是，AdminUserService 没有自己的 BO 对象，所以复用只能这么做\n        userService.updateUserProfile(getLoginUserId(), BeanUtils.toBean(reqVO, UserProfileUpdateReqVO.class));\n        return success(true);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/oauth2/vo/client/OAuth2ClientPageReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.oauth2.vo.client;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\n\n@Schema(description = \"管理后台 - OAuth2 客户端分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class OAuth2ClientPageReqVO extends PageParam {\n\n    @Schema(description = \"应用名，模糊匹配\", example = \"土豆\")\n    private String name;\n\n    @Schema(description = \"状态，参见 CommonStatusEnum 枚举\", example = \"1\")\n    private Integer status;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/oauth2/vo/client/OAuth2ClientRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.oauth2.vo.client;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\nimport java.util.List;\n\n@Schema(description = \"管理后台 - OAuth2 客户端 Response VO\")\n@Data\npublic class OAuth2ClientRespVO {\n\n    @Schema(description = \"编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"客户端编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"tudou\")\n    private String clientId;\n\n    @Schema(description = \"客户端密钥\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"fan\")\n    private String secret;\n\n    @Schema(description = \"应用名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"土豆\")\n    private String name;\n\n    @Schema(description = \"应用图标\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"https://www.yixiang.co/xx.png\")\n    private String logo;\n\n    @Schema(description = \"应用描述\", example = \"我是一个应用\")\n    private String description;\n\n    @Schema(description = \"状态，参见 CommonStatusEnum 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    private Integer status;\n\n    @Schema(description = \"访问令牌的有效期\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"8640\")\n    private Integer accessTokenValiditySeconds;\n\n    @Schema(description = \"刷新令牌的有效期\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"8640000\")\n    private Integer refreshTokenValiditySeconds;\n\n    @Schema(description = \"可重定向的 URI 地址\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"https://www.yixiang.co\")\n    private List<String> redirectUris;\n\n    @Schema(description = \"授权类型，参见 OAuth2GrantTypeEnum 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"password\")\n    private List<String> authorizedGrantTypes;\n\n    @Schema(description = \"授权范围\", example = \"user_info\")\n    private List<String> scopes;\n\n    @Schema(description = \"自动通过的授权范围\", example = \"user_info\")\n    private List<String> autoApproveScopes;\n\n    @Schema(description = \"权限\", example = \"system:user:query\")\n    private List<String> authorities;\n\n    @Schema(description = \"资源\", example = \"1024\")\n    private List<String> resourceIds;\n\n    @Schema(description = \"附加信息\", example = \"{yshop: true}\")\n    private String additionalInformation;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/oauth2/vo/client/OAuth2ClientSaveReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.oauth2.vo.client;\n\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.util.json.JsonUtils;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.*;\nimport org.hibernate.validator.constraints.URL;\n\nimport jakarta.validation.constraints.AssertTrue;\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\nimport java.util.List;\n\n@Schema(description = \"管理后台 - OAuth2 客户端创建/修改 Request VO\")\n@Data\npublic class OAuth2ClientSaveReqVO {\n\n    @Schema(description = \"编号\", example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"客户端编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"tudou\")\n    @NotNull(message = \"客户端编号不能为空\")\n    private String clientId;\n\n    @Schema(description = \"客户端密钥\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"fan\")\n    @NotNull(message = \"客户端密钥不能为空\")\n    private String secret;\n\n    @Schema(description = \"应用名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"土豆\")\n    @NotNull(message = \"应用名不能为空\")\n    private String name;\n\n    @Schema(description = \"应用图标\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"https://www.yixiang.co/xx.png\")\n    @NotNull(message = \"应用图标不能为空\")\n    @URL(message = \"应用图标的地址不正确\")\n    private String logo;\n\n    @Schema(description = \"应用描述\", example = \"我是一个应用\")\n    private String description;\n\n    @Schema(description = \"状态，参见 CommonStatusEnum 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @NotNull(message = \"状态不能为空\")\n    private Integer status;\n\n    @Schema(description = \"访问令牌的有效期\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"8640\")\n    @NotNull(message = \"访问令牌的有效期不能为空\")\n    private Integer accessTokenValiditySeconds;\n\n    @Schema(description = \"刷新令牌的有效期\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"8640000\")\n    @NotNull(message = \"刷新令牌的有效期不能为空\")\n    private Integer refreshTokenValiditySeconds;\n\n    @Schema(description = \"可重定向的 URI 地址\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"https://www.yixiang.co\")\n    @NotNull(message = \"可重定向的 URI 地址不能为空\")\n    private List<@NotEmpty(message = \"重定向的 URI 不能为空\") @URL(message = \"重定向的 URI 格式不正确\") String> redirectUris;\n\n    @Schema(description = \"授权类型，参见 OAuth2GrantTypeEnum 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"password\")\n    @NotNull(message = \"授权类型不能为空\")\n    private List<String> authorizedGrantTypes;\n\n    @Schema(description = \"授权范围\", example = \"user_info\")\n    private List<String> scopes;\n\n    @Schema(description = \"自动通过的授权范围\", example = \"user_info\")\n    private List<String> autoApproveScopes;\n\n    @Schema(description = \"权限\", example = \"system:user:query\")\n    private List<String> authorities;\n\n    @Schema(description = \"资源\", example = \"1024\")\n    private List<String> resourceIds;\n\n    @Schema(description = \"附加信息\", example = \"{yshop: true}\")\n    private String additionalInformation;\n\n    @AssertTrue(message = \"附加信息必须是 JSON 格式\")\n    public boolean isAdditionalInformationJson() {\n        return StrUtil.isEmpty(additionalInformation) || JsonUtils.isJson(additionalInformation);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/oauth2/vo/open/OAuth2OpenAccessTokenRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.oauth2.vo.open;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\n@Schema(description = \"管理后台 - 【开放接口】访问令牌 Response VO\")\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\npublic class OAuth2OpenAccessTokenRespVO {\n\n    @Schema(description = \"访问令牌\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"tudou\")\n    @JsonProperty(\"access_token\")\n    private String accessToken;\n\n    @Schema(description = \"刷新令牌\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"nice\")\n    @JsonProperty(\"refresh_token\")\n    private String refreshToken;\n\n    @Schema(description = \"令牌类型\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"bearer\")\n    @JsonProperty(\"token_type\")\n    private String tokenType;\n\n    @Schema(description = \"过期时间,单位：秒\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"42430\")\n    @JsonProperty(\"expires_in\")\n    private Long expiresIn;\n\n    @Schema(description = \"授权范围,如果多个授权范围，使用空格分隔\", example = \"user_info\")\n    private String scope;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/oauth2/vo/open/OAuth2OpenAuthorizeInfoRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.oauth2.vo.open;\n\nimport co.yixiang.yshop.framework.common.core.KeyValue;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\nimport java.util.List;\n\n@Schema(description = \"管理后台 - 授权页的信息 Response VO\")\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\npublic class OAuth2OpenAuthorizeInfoRespVO {\n\n    /**\n     * 客户端\n     */\n    private Client client;\n\n    @Schema(description = \"scope 的选中信息,使用 List 保证有序性，Key 是 scope，Value 为是否选中\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private List<KeyValue<String, Boolean>> scopes;\n\n    @Data\n    @NoArgsConstructor\n    @AllArgsConstructor\n    public static class Client {\n\n        @Schema(description = \"应用名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"土豆\")\n        private String name;\n\n        @Schema(description = \"应用图标\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"https://www.yixiang.co/xx.png\")\n        private String logo;\n\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/oauth2/vo/open/OAuth2OpenCheckTokenRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.oauth2.vo.open;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\nimport java.util.List;\n\n@Schema(description = \"管理后台 - 【开放接口】校验令牌 Response VO\")\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\npublic class OAuth2OpenCheckTokenRespVO {\n\n    @Schema(description = \"用户编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"666\")\n    @JsonProperty(\"user_id\")\n    private Long userId;\n    @Schema(description = \"用户类型，参见 UserTypeEnum 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"2\")\n    @JsonProperty(\"user_type\")\n    private Integer userType;\n    @Schema(description = \"租户编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @JsonProperty(\"tenant_id\")\n    private Long tenantId;\n\n    @Schema(description = \"客户端编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"car\")\n    @JsonProperty(\"client_id\")\n    private String clientId;\n    @Schema(description = \"授权范围\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"user_info\")\n    private List<String> scopes;\n\n    @Schema(description = \"访问令牌\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"tudou\")\n    @JsonProperty(\"access_token\")\n    private String accessToken;\n\n    @Schema(description = \"过期时间，时间戳 / 1000，即单位：秒\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1593092157\")\n    private Long exp;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/oauth2/vo/token/OAuth2AccessTokenPageReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.oauth2.vo.token;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\n@Schema(description = \"管理后台 - 访问令牌分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\npublic class OAuth2AccessTokenPageReqVO extends PageParam {\n\n    @Schema(description = \"用户编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"666\")\n    private Long userId;\n\n    @Schema(description = \"用户类型，参见 UserTypeEnum 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"2\")\n    private Integer userType;\n\n    @Schema(description = \"客户端编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"2\")\n    private String clientId;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/oauth2/vo/token/OAuth2AccessTokenRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.oauth2.vo.token;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 访问令牌 Response VO\")\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\npublic class OAuth2AccessTokenRespVO {\n\n    @Schema(description = \"编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"访问令牌\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"tudou\")\n    private String accessToken;\n\n    @Schema(description = \"刷新令牌\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"nice\")\n    private String refreshToken;\n\n    @Schema(description = \"用户编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"666\")\n    private Long userId;\n\n    @Schema(description = \"用户类型，参见 UserTypeEnum 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"2\")\n    private Integer userType;\n\n    @Schema(description = \"客户端编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"2\")\n    private String clientId;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private LocalDateTime createTime;\n\n    @Schema(description = \"过期时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private LocalDateTime expiresTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/oauth2/vo/user/OAuth2UserInfoRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.oauth2.vo.user;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\nimport java.util.List;\n\n@Schema(description = \"管理后台 - OAuth2 获得用户基本信息 Response VO\")\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\npublic class OAuth2UserInfoRespVO {\n\n    @Schema(description = \"用户编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    private Long id;\n\n    @Schema(description = \"用户账号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    private String username;\n\n    @Schema(description = \"用户昵称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    private String nickname;\n\n    @Schema(description = \"用户邮箱\", example = \"yshop@yixiang.co\")\n    private String email;\n    @Schema(description = \"手机号码\", example = \"15601691300\")\n    private String mobile;\n\n    @Schema(description = \"用户性别，参见 SexEnum 枚举类\", example = \"1\")\n    private Integer sex;\n\n    @Schema(description = \"用户头像\", example = \"https://www.yixiang.co/xxx.png\")\n    private String avatar;\n\n    /**\n     * 所在部门\n     */\n    private Dept dept;\n\n    /**\n     * 所属岗位数组\n     */\n    private List<Post> posts;\n\n    @Schema(description = \"部门\")\n    @Data\n    public static class Dept {\n\n        @Schema(description = \"部门编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n        private Long id;\n\n        @Schema(description = \"部门名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"研发部\")\n        private String name;\n\n    }\n\n    @Schema(description = \"岗位\")\n    @Data\n    public static class Post {\n\n        @Schema(description = \"岗位编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n        private Long id;\n\n        @Schema(description = \"岗位名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"开发\")\n        private String name;\n\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/oauth2/vo/user/OAuth2UserUpdateReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.oauth2.vo.user;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport org.hibernate.validator.constraints.Length;\n\nimport jakarta.validation.constraints.Email;\nimport jakarta.validation.constraints.Size;\n\n@Schema(description = \"管理后台 - OAuth2 更新用户基本信息 Request VO\")\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\npublic class OAuth2UserUpdateReqVO {\n\n    @Schema(description = \"用户昵称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    @Size(max = 30, message = \"用户昵称长度不能超过 30 个字符\")\n    private String nickname;\n\n    @Schema(description = \"用户邮箱\", example = \"yshop@yixiang.co\")\n    @Email(message = \"邮箱格式不正确\")\n    @Size(max = 50, message = \"邮箱长度不能超过 50 个字符\")\n    private String email;\n\n    @Schema(description = \"手机号码\", example = \"15601691300\")\n    @Length(min = 11, max = 11, message = \"手机号长度必须 11 位\")\n    private String mobile;\n\n    @Schema(description = \"用户性别，参见 SexEnum 枚举类\", example = \"1\")\n    private Integer sex;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/permission/MenuController.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.permission;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.controller.admin.permission.vo.menu.MenuListReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.permission.vo.menu.MenuRespVO;\nimport co.yixiang.yshop.module.system.controller.admin.permission.vo.menu.MenuSaveVO;\nimport co.yixiang.yshop.module.system.controller.admin.permission.vo.menu.MenuSimpleRespVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.permission.MenuDO;\nimport co.yixiang.yshop.module.system.service.permission.MenuService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Valid;\nimport java.util.Comparator;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 菜单\")\n@RestController\n@RequestMapping(\"/system/menu\")\n@Validated\npublic class MenuController {\n\n    @Resource\n    private MenuService menuService;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建菜单\")\n    @PreAuthorize(\"@ss.hasPermission('system:menu:create')\")\n    public CommonResult<Long> createMenu(@Valid @RequestBody MenuSaveVO createReqVO) {\n        Long menuId = menuService.createMenu(createReqVO);\n        return success(menuId);\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"修改菜单\")\n    @PreAuthorize(\"@ss.hasPermission('system:menu:update')\")\n    public CommonResult<Boolean> updateMenu(@Valid @RequestBody MenuSaveVO updateReqVO) {\n        menuService.updateMenu(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除菜单\")\n    @Parameter(name = \"id\", description = \"菜单编号\", required= true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('system:menu:delete')\")\n    public CommonResult<Boolean> deleteMenu(@RequestParam(\"id\") Long id) {\n        menuService.deleteMenu(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/list\")\n    @Operation(summary = \"获取菜单列表\", description = \"用于【菜单管理】界面\")\n    @PreAuthorize(\"@ss.hasPermission('system:menu:query')\")\n    public CommonResult<List<MenuRespVO>> getMenuList(MenuListReqVO reqVO) {\n        List<MenuDO> list = menuService.getMenuList(reqVO);\n        list.sort(Comparator.comparing(MenuDO::getSort));\n        return success(BeanUtils.toBean(list, MenuRespVO.class));\n    }\n\n    @GetMapping({\"/list-all-simple\", \"simple-list\"})\n    @Operation(summary = \"获取菜单精简信息列表\", description = \"只包含被开启的菜单，用于【角色分配菜单】功能的选项。\" +\n            \"在多租户的场景下，会只返回租户所在套餐有的菜单\")\n    public CommonResult<List<MenuSimpleRespVO>> getSimpleMenuList() {\n        List<MenuDO> list = menuService.getMenuListByTenant(\n                new MenuListReqVO().setStatus(CommonStatusEnum.ENABLE.getStatus()));\n        list.sort(Comparator.comparing(MenuDO::getSort));\n        return success(BeanUtils.toBean(list, MenuSimpleRespVO.class));\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获取菜单信息\")\n    @PreAuthorize(\"@ss.hasPermission('system:menu:query')\")\n    public CommonResult<MenuRespVO> getMenu(Long id) {\n        MenuDO menu = menuService.getMenu(id);\n        return success(BeanUtils.toBean(menu, MenuRespVO.class));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/permission/PermissionController.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.permission;\n\nimport cn.hutool.core.collection.CollUtil;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.module.system.controller.admin.permission.vo.permission.PermissionAssignRoleDataScopeReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.permission.vo.permission.PermissionAssignRoleMenuReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.permission.vo.permission.PermissionAssignUserRoleReqVO;\nimport co.yixiang.yshop.module.system.service.permission.PermissionService;\nimport co.yixiang.yshop.module.system.service.tenant.TenantService;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.Operation;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Valid;\nimport java.util.Set;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n/**\n * 权限 Controller，提供赋予用户、角色的权限的 API 接口\n *\n * @author yshop\n */\n@Tag(name = \"管理后台 - 权限\")\n@RestController\n@RequestMapping(\"/system/permission\")\npublic class PermissionController {\n\n    @Resource\n    private PermissionService permissionService;\n    @Resource\n    private TenantService tenantService;\n\n    @Operation(summary = \"获得角色拥有的菜单编号\")\n    @Parameter(name = \"roleId\", description = \"角色编号\", required = true)\n    @GetMapping(\"/list-role-menus\")\n    @PreAuthorize(\"@ss.hasPermission('system:permission:assign-role-menu')\")\n    public CommonResult<Set<Long>> getRoleMenuList(Long roleId) {\n        return success(permissionService.getRoleMenuListByRoleId(roleId));\n    }\n\n    @PostMapping(\"/assign-role-menu\")\n    @Operation(summary = \"赋予角色菜单\")\n    @PreAuthorize(\"@ss.hasPermission('system:permission:assign-role-menu')\")\n    public CommonResult<Boolean> assignRoleMenu(@Validated @RequestBody PermissionAssignRoleMenuReqVO reqVO) {\n        // 开启多租户的情况下，需要过滤掉未开通的菜单\n        tenantService.handleTenantMenu(menuIds -> reqVO.getMenuIds().removeIf(menuId -> !CollUtil.contains(menuIds, menuId)));\n\n        // 执行菜单的分配\n        permissionService.assignRoleMenu(reqVO.getRoleId(), reqVO.getMenuIds());\n        return success(true);\n    }\n\n    @PostMapping(\"/assign-role-data-scope\")\n    @Operation(summary = \"赋予角色数据权限\")\n    @PreAuthorize(\"@ss.hasPermission('system:permission:assign-role-data-scope')\")\n    public CommonResult<Boolean> assignRoleDataScope(@Valid @RequestBody PermissionAssignRoleDataScopeReqVO reqVO) {\n        permissionService.assignRoleDataScope(reqVO.getRoleId(), reqVO.getDataScope(), reqVO.getDataScopeDeptIds());\n        return success(true);\n    }\n\n    @Operation(summary = \"获得管理员拥有的角色编号列表\")\n    @Parameter(name = \"userId\", description = \"用户编号\", required = true)\n    @GetMapping(\"/list-user-roles\")\n    @PreAuthorize(\"@ss.hasPermission('system:permission:assign-user-role')\")\n    public CommonResult<Set<Long>> listAdminRoles(@RequestParam(\"userId\") Long userId) {\n        return success(permissionService.getUserRoleIdListByUserId(userId));\n    }\n\n    @Operation(summary = \"赋予用户角色\")\n    @PostMapping(\"/assign-user-role\")\n    @PreAuthorize(\"@ss.hasPermission('system:permission:assign-user-role')\")\n    public CommonResult<Boolean> assignUserRole(@Validated @RequestBody PermissionAssignUserRoleReqVO reqVO) {\n        permissionService.assignUserRole(reqVO.getUserId(), reqVO.getRoleIds());\n        return success(true);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/permission/RoleController.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.permission;\n\nimport co.yixiang.yshop.framework.apilog.core.annotation.ApiAccessLog;\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.framework.excel.core.util.ExcelUtils;\nimport co.yixiang.yshop.module.system.controller.admin.permission.vo.role.*;\nimport co.yixiang.yshop.module.system.dal.dataobject.permission.RoleDO;\nimport co.yixiang.yshop.module.system.service.permission.RoleService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.annotation.Resource;\nimport jakarta.servlet.http.HttpServletResponse;\nimport jakarta.validation.Valid;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.io.IOException;\nimport java.util.Comparator;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.apilog.core.enums.OperateTypeEnum.EXPORT;\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\nimport static java.util.Collections.singleton;\n\n@Tag(name = \"管理后台 - 角色\")\n@RestController\n@RequestMapping(\"/system/role\")\n@Validated\npublic class RoleController {\n\n    @Resource\n    private RoleService roleService;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建角色\")\n    @PreAuthorize(\"@ss.hasPermission('system:role:create')\")\n    public CommonResult<Long> createRole(@Valid @RequestBody RoleSaveReqVO createReqVO) {\n        return success(roleService.createRole(createReqVO, null));\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"修改角色\")\n    @PreAuthorize(\"@ss.hasPermission('system:role:update')\")\n    public CommonResult<Boolean> updateRole(@Valid @RequestBody RoleSaveReqVO updateReqVO) {\n        roleService.updateRole(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除角色\")\n    @Parameter(name = \"id\", description = \"角色编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('system:role:delete')\")\n    public CommonResult<Boolean> deleteRole(@RequestParam(\"id\") Long id) {\n        roleService.deleteRole(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得角色信息\")\n    @PreAuthorize(\"@ss.hasPermission('system:role:query')\")\n    public CommonResult<RoleRespVO> getRole(@RequestParam(\"id\") Long id) {\n        RoleDO role = roleService.getRole(id);\n        return success(BeanUtils.toBean(role, RoleRespVO.class));\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得角色分页\")\n    @PreAuthorize(\"@ss.hasPermission('system:role:query')\")\n    public CommonResult<PageResult<RoleRespVO>> getRolePage(RolePageReqVO pageReqVO) {\n        PageResult<RoleDO> pageResult = roleService.getRolePage(pageReqVO);\n        return success(BeanUtils.toBean(pageResult, RoleRespVO.class));\n    }\n\n    @GetMapping({\"/list-all-simple\", \"/simple-list\"})\n    @Operation(summary = \"获取角色精简信息列表\", description = \"只包含被开启的角色，主要用于前端的下拉选项\")\n    public CommonResult<List<RoleRespVO>> getSimpleRoleList() {\n        List<RoleDO> list = roleService.getRoleListByStatus(singleton(CommonStatusEnum.ENABLE.getStatus()));\n        list.sort(Comparator.comparing(RoleDO::getSort));\n        return success(BeanUtils.toBean(list, RoleRespVO.class));\n    }\n\n    @GetMapping(\"/export-excel\")\n    @Operation(summary = \"导出角色 Excel\")\n    @ApiAccessLog(operateType = EXPORT)\n    @PreAuthorize(\"@ss.hasPermission('system:role:export')\")\n    public void export(HttpServletResponse response, @Validated RolePageReqVO exportReqVO) throws IOException {\n        exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);\n        List<RoleDO> list = roleService.getRolePage(exportReqVO).getList();\n        // 输出\n        ExcelUtils.write(response, \"角色数据.xls\", \"数据\", RoleRespVO.class,\n                BeanUtils.toBean(list, RoleRespVO.class));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/permission/vo/menu/MenuListReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.permission.vo.menu;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\n@Schema(description = \"管理后台 - 菜单列表 Request VO\")\n@Data\npublic class MenuListReqVO {\n\n    @Schema(description = \"菜单名称，模糊匹配\", example = \"yshop\")\n    private String name;\n\n    @Schema(description = \"展示状态，参见 CommonStatusEnum 枚举类\", example = \"1\")\n    private Integer status;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/permission/vo/menu/MenuRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.permission.vo.menu;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotBlank;\nimport jakarta.validation.constraints.NotNull;\nimport jakarta.validation.constraints.Size;\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 菜单信息 Response VO\")\n@Data\npublic class MenuRespVO {\n\n    @Schema(description = \"菜单编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"菜单名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    @NotBlank(message = \"菜单名称不能为空\")\n    @Size(max = 50, message = \"菜单名称长度不能超过50个字符\")\n    private String name;\n\n    @Schema(description = \"权限标识,仅菜单类型为按钮时，才需要传递\", example = \"sys:menu:add\")\n    @Size(max = 100)\n    private String permission;\n\n    @Schema(description = \"类型，参见 MenuTypeEnum 枚举类\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @NotNull(message = \"菜单类型不能为空\")\n    private Integer type;\n\n    @Schema(description = \"显示顺序\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @NotNull(message = \"显示顺序不能为空\")\n    private Integer sort;\n\n    @Schema(description = \"父菜单 ID\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @NotNull(message = \"父菜单 ID 不能为空\")\n    private Long parentId;\n\n    @Schema(description = \"路由地址,仅菜单类型为菜单或者目录时，才需要传\", example = \"post\")\n    @Size(max = 200, message = \"路由地址不能超过200个字符\")\n    private String path;\n\n    @Schema(description = \"菜单图标,仅菜单类型为菜单或者目录时，才需要传\", example = \"/menu/list\")\n    private String icon;\n\n    @Schema(description = \"组件路径,仅菜单类型为菜单时，才需要传\", example = \"system/post/index\")\n    @Size(max = 200, message = \"组件路径不能超过255个字符\")\n    private String component;\n\n    @Schema(description = \"组件名\", example = \"SystemUser\")\n    private String componentName;\n\n    @Schema(description = \"状态,见 CommonStatusEnum 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @NotNull(message = \"状态不能为空\")\n    private Integer status;\n\n    @Schema(description = \"是否可见\", example = \"false\")\n    private Boolean visible;\n\n    @Schema(description = \"是否缓存\", example = \"false\")\n    private Boolean keepAlive;\n\n    @Schema(description = \"是否总是显示\", example = \"false\")\n    private Boolean alwaysShow;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"时间戳格式\")\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/permission/vo/menu/MenuSaveVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.permission.vo.menu;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotBlank;\nimport jakarta.validation.constraints.NotNull;\nimport jakarta.validation.constraints.Size;\n\n@Schema(description = \"管理后台 - 菜单创建/修改 Request VO\")\n@Data\npublic class MenuSaveVO {\n\n    @Schema(description = \"菜单编号\", example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"菜单名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    @NotBlank(message = \"菜单名称不能为空\")\n    @Size(max = 50, message = \"菜单名称长度不能超过50个字符\")\n    private String name;\n\n    @Schema(description = \"权限标识,仅菜单类型为按钮时，才需要传递\", example = \"sys:menu:add\")\n    @Size(max = 100)\n    private String permission;\n\n    @Schema(description = \"类型，参见 MenuTypeEnum 枚举类\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @NotNull(message = \"菜单类型不能为空\")\n    private Integer type;\n\n    @Schema(description = \"显示顺序\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @NotNull(message = \"显示顺序不能为空\")\n    private Integer sort;\n\n    @Schema(description = \"父菜单 ID\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @NotNull(message = \"父菜单 ID 不能为空\")\n    private Long parentId;\n\n    @Schema(description = \"路由地址,仅菜单类型为菜单或者目录时，才需要传\", example = \"post\")\n    @Size(max = 200, message = \"路由地址不能超过200个字符\")\n    private String path;\n\n    @Schema(description = \"菜单图标,仅菜单类型为菜单或者目录时，才需要传\", example = \"/menu/list\")\n    private String icon;\n\n    @Schema(description = \"组件路径,仅菜单类型为菜单时，才需要传\", example = \"system/post/index\")\n    @Size(max = 200, message = \"组件路径不能超过255个字符\")\n    private String component;\n\n    @Schema(description = \"组件名\", example = \"SystemUser\")\n    private String componentName;\n\n    @Schema(description = \"状态,见 CommonStatusEnum 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @NotNull(message = \"状态不能为空\")\n    private Integer status;\n\n    @Schema(description = \"是否可见\", example = \"false\")\n    private Boolean visible;\n\n    @Schema(description = \"是否缓存\", example = \"false\")\n    private Boolean keepAlive;\n\n    @Schema(description = \"是否总是显示\", example = \"false\")\n    private Boolean alwaysShow;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/permission/vo/menu/MenuSimpleRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.permission.vo.menu;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\n@Schema(description = \"管理后台 - 菜单精简信息 Response VO\")\n@Data\npublic class MenuSimpleRespVO {\n\n    @Schema(description = \"菜单编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"菜单名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    private String name;\n\n    @Schema(description = \"父菜单 ID\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Long parentId;\n\n    @Schema(description = \"类型，参见 MenuTypeEnum 枚举类\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    private Integer type;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/permission/vo/permission/PermissionAssignRoleDataScopeReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.permission.vo.permission;\n\nimport co.yixiang.yshop.framework.common.validation.InEnum;\nimport co.yixiang.yshop.module.system.enums.permission.DataScopeEnum;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotNull;\nimport java.util.Collections;\nimport java.util.Set;\n\n@Schema(description = \"管理后台 - 赋予角色数据权限 Request VO\")\n@Data\npublic class PermissionAssignRoleDataScopeReqVO {\n\n    @Schema(description = \"角色编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @NotNull(message = \"角色编号不能为空\")\n    private Long roleId;\n\n    @Schema(description = \"数据范围，参见 DataScopeEnum 枚举类\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @NotNull(message = \"数据范围不能为空\")\n    @InEnum(value = DataScopeEnum.class, message = \"数据范围必须是 {value}\")\n    private Integer dataScope;\n\n    @Schema(description = \"部门编号列表，只有范围类型为 DEPT_CUSTOM 时，该字段才需要\", example = \"1,3,5\")\n    private Set<Long> dataScopeDeptIds = Collections.emptySet(); // 兜底\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/permission/vo/permission/PermissionAssignRoleMenuReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.permission.vo.permission;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotNull;\nimport java.util.Collections;\nimport java.util.Set;\n\n@Schema(description = \"管理后台 - 赋予角色菜单 Request VO\")\n@Data\npublic class PermissionAssignRoleMenuReqVO {\n\n    @Schema(description = \"角色编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @NotNull(message = \"角色编号不能为空\")\n    private Long roleId;\n\n    @Schema(description = \"菜单编号列表\", example = \"1,3,5\")\n    private Set<Long> menuIds = Collections.emptySet(); // 兜底\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/permission/vo/permission/PermissionAssignUserRoleReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.permission.vo.permission;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotNull;\nimport java.util.Collections;\nimport java.util.Set;\n\n@Schema(description = \"管理后台 - 赋予用户角色 Request VO\")\n@Data\npublic class PermissionAssignUserRoleReqVO {\n\n    @Schema(description = \"用户编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @NotNull(message = \"用户编号不能为空\")\n    private Long userId;\n\n    @Schema(description = \"角色编号列表\", example = \"1,3,5\")\n    private Set<Long> roleIds = Collections.emptySet(); // 兜底\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/permission/vo/role/RolePageReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.permission.vo.role;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 角色分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\npublic class RolePageReqVO extends PageParam {\n\n    @Schema(description = \"角色名称，模糊匹配\", example = \"yshop\")\n    private String name;\n\n    @Schema(description = \"角色标识，模糊匹配\", example = \"yshop\")\n    private String code;\n\n    @Schema(description = \"展示状态，参见 CommonStatusEnum 枚举类\", example = \"1\")\n    private Integer status;\n\n    @Schema(description = \"创建时间\", example = \"[2022-07-01 00:00:00,2022-07-01 23:59:59]\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/permission/vo/role/RoleRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.permission.vo.role;\n\nimport co.yixiang.yshop.framework.excel.core.annotations.DictFormat;\nimport co.yixiang.yshop.framework.excel.core.convert.DictConvert;\nimport co.yixiang.yshop.module.system.enums.DictTypeConstants;\nimport com.alibaba.excel.annotation.ExcelIgnoreUnannotated;\nimport com.alibaba.excel.annotation.ExcelProperty;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotBlank;\nimport java.time.LocalDateTime;\nimport java.util.Set;\n\n@Schema(description = \"管理后台 - 角色信息 Response VO\")\n@Data\n@ExcelIgnoreUnannotated\npublic class RoleRespVO {\n\n    @Schema(description = \"角色编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @ExcelProperty(\"角色序号\")\n    private Long id;\n\n    @Schema(description = \"角色名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"管理员\")\n    @ExcelProperty(\"角色名称\")\n    private String name;\n\n    @Schema(description = \"角色标志\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"admin\")\n    @NotBlank(message = \"角色标志不能为空\")\n    @ExcelProperty(\"角色标志\")\n    private String code;\n\n    @Schema(description = \"显示顺序\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @ExcelProperty(\"角色排序\")\n    private Integer sort;\n\n    @Schema(description = \"状态，参见 CommonStatusEnum 枚举类\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @ExcelProperty(value = \"角色状态\", converter = DictConvert.class)\n    @DictFormat(DictTypeConstants.COMMON_STATUS)\n    private Integer status;\n\n    @Schema(description = \"角色类型，参见 RoleTypeEnum 枚举类\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    private Integer type;\n\n    @Schema(description = \"备注\", example = \"我是一个角色\")\n    private String remark;\n\n    @Schema(description = \"数据范围，参见 DataScopeEnum 枚举类\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @ExcelProperty(\"数据范围\")\n    private Integer dataScope;\n\n    @Schema(description = \"数据范围(指定部门数组)\", example = \"1\")\n    private Set<Long> dataScopeDeptIds;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"时间戳格式\")\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/permission/vo/role/RoleSaveReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.permission.vo.role;\n\nimport com.mzt.logapi.starter.annotation.DiffLogField;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotBlank;\nimport jakarta.validation.constraints.NotNull;\nimport jakarta.validation.constraints.Size;\n\n@Schema(description = \"管理后台 - 角色创建/更新 Request VO\")\n@Data\npublic class RoleSaveReqVO {\n\n    @Schema(description = \"角色编号\", example = \"1\")\n    private Long id;\n\n    @Schema(description = \"角色名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"管理员\")\n    @NotBlank(message = \"角色名称不能为空\")\n    @Size(max = 30, message = \"角色名称长度不能超过 30 个字符\")\n    @DiffLogField(name = \"角色名称\")\n    private String name;\n\n    @NotBlank(message = \"角色标志不能为空\")\n    @Size(max = 100, message = \"角色标志长度不能超过 100 个字符\")\n    @Schema(description = \"角色编码\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"ADMIN\")\n    @DiffLogField(name = \"角色标志\")\n    private String code;\n\n    @Schema(description = \"显示顺序\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @NotNull(message = \"显示顺序不能为空\")\n    @DiffLogField(name = \"显示顺序\")\n    private Integer sort;\n\n    @Schema(description = \"备注\", example = \"我是一个角色\")\n    @DiffLogField(name = \"备注\")\n    private String remark;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/permission/vo/role/RoleSimpleRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.permission.vo.role;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\n@Schema(description = \"管理后台 - 角色精简信息 Response VO\")\n@Data\npublic class RoleSimpleRespVO {\n\n    @Schema(description = \"角色编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"角色名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    private String name;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/sms/SmsCallbackController.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.sms;\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.util.servlet.ServletUtils;\nimport co.yixiang.yshop.module.system.framework.sms.core.enums.SmsChannelEnum;\nimport co.yixiang.yshop.module.system.service.sms.SmsSendService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport org.springframework.web.bind.annotation.PostMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport jakarta.annotation.Resource;\nimport jakarta.annotation.security.PermitAll;\nimport jakarta.servlet.http.HttpServletRequest;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 短信回调\")\n@RestController\n@RequestMapping(\"/system/sms/callback\")\npublic class SmsCallbackController {\n\n    @Resource\n    private SmsSendService smsSendService;\n\n    @PostMapping(\"/aliyun\")\n    @PermitAll\n    @Operation(summary = \"阿里云短信的回调\", description = \"参见 https://help.aliyun.com/zh/sms/developer-reference/configure-delivery-receipts-1 文档\")\n    public CommonResult<Boolean> receiveAliyunSmsStatus(HttpServletRequest request) throws Throwable {\n        String text = ServletUtils.getBody(request);\n        smsSendService.receiveSmsStatus(SmsChannelEnum.ALIYUN.getCode(), text);\n        return success(true);\n    }\n\n    @PostMapping(\"/tencent\")\n    @PermitAll\n    @Operation(summary = \"腾讯云短信的回调\", description = \"参见 https://cloud.tencent.com/document/product/382/59178 文档\")\n    public CommonResult<Boolean> receiveTencentSmsStatus(HttpServletRequest request) throws Throwable {\n        String text = ServletUtils.getBody(request);\n        smsSendService.receiveSmsStatus(SmsChannelEnum.TENCENT.getCode(), text);\n        return success(true);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/sms/SmsChannelController.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.sms;\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.sms.vo.channel.SmsChannelRespVO;\nimport co.yixiang.yshop.module.system.controller.admin.sms.vo.channel.SmsChannelSaveReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.sms.vo.channel.SmsChannelSimpleRespVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.sms.SmsChannelDO;\nimport co.yixiang.yshop.module.system.service.sms.SmsChannelService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.web.bind.annotation.*;\n\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Valid;\nimport java.util.Comparator;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 短信渠道\")\n@RestController\n@RequestMapping(\"system/sms-channel\")\npublic class SmsChannelController {\n\n    @Resource\n    private SmsChannelService smsChannelService;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建短信渠道\")\n    @PreAuthorize(\"@ss.hasPermission('system:sms-channel:create')\")\n    public CommonResult<Long> createSmsChannel(@Valid @RequestBody SmsChannelSaveReqVO createReqVO) {\n        return success(smsChannelService.createSmsChannel(createReqVO));\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"更新短信渠道\")\n    @PreAuthorize(\"@ss.hasPermission('system:sms-channel:update')\")\n    public CommonResult<Boolean> updateSmsChannel(@Valid @RequestBody SmsChannelSaveReqVO updateReqVO) {\n        smsChannelService.updateSmsChannel(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除短信渠道\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('system:sms-channel:delete')\")\n    public CommonResult<Boolean> deleteSmsChannel(@RequestParam(\"id\") Long id) {\n        smsChannelService.deleteSmsChannel(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得短信渠道\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('system:sms-channel:query')\")\n    public CommonResult<SmsChannelRespVO> getSmsChannel(@RequestParam(\"id\") Long id) {\n        SmsChannelDO channel = smsChannelService.getSmsChannel(id);\n        return success(BeanUtils.toBean(channel, SmsChannelRespVO.class));\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得短信渠道分页\")\n    @PreAuthorize(\"@ss.hasPermission('system:sms-channel:query')\")\n    public CommonResult<PageResult<SmsChannelRespVO>> getSmsChannelPage(@Valid SmsChannelPageReqVO pageVO) {\n        PageResult<SmsChannelDO> pageResult = smsChannelService.getSmsChannelPage(pageVO);\n        return success(BeanUtils.toBean(pageResult, SmsChannelRespVO.class));\n    }\n\n    @GetMapping({\"/list-all-simple\", \"/simple-list\"})\n    @Operation(summary = \"获得短信渠道精简列表\", description = \"包含被禁用的短信渠道\")\n    public CommonResult<List<SmsChannelSimpleRespVO>> getSimpleSmsChannelList() {\n        List<SmsChannelDO> list = smsChannelService.getSmsChannelList();\n        list.sort(Comparator.comparing(SmsChannelDO::getId));\n        return success(BeanUtils.toBean(list, SmsChannelSimpleRespVO.class));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/sms/SmsLogController.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.sms;\n\nimport co.yixiang.yshop.framework.apilog.core.annotation.ApiAccessLog;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.framework.excel.core.util.ExcelUtils;\nimport co.yixiang.yshop.module.system.controller.admin.sms.vo.log.SmsLogPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.sms.vo.log.SmsLogRespVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.sms.SmsLogDO;\nimport co.yixiang.yshop.module.system.service.sms.SmsLogService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.annotation.Resource;\nimport jakarta.servlet.http.HttpServletResponse;\nimport jakarta.validation.Valid;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport java.io.IOException;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.apilog.core.enums.OperateTypeEnum.EXPORT;\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 短信日志\")\n@RestController\n@RequestMapping(\"/system/sms-log\")\n@Validated\npublic class SmsLogController {\n\n    @Resource\n    private SmsLogService smsLogService;\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得短信日志分页\")\n    @PreAuthorize(\"@ss.hasPermission('system:sms-log:query')\")\n    public CommonResult<PageResult<SmsLogRespVO>> getSmsLogPage(@Valid SmsLogPageReqVO pageReqVO) {\n        PageResult<SmsLogDO> pageResult = smsLogService.getSmsLogPage(pageReqVO);\n        return success(BeanUtils.toBean(pageResult, SmsLogRespVO.class));\n    }\n\n    @GetMapping(\"/export-excel\")\n    @Operation(summary = \"导出短信日志 Excel\")\n    @PreAuthorize(\"@ss.hasPermission('system:sms-log:export')\")\n    @ApiAccessLog(operateType = EXPORT)\n    public void exportSmsLogExcel(@Valid SmsLogPageReqVO exportReqVO,\n                                  HttpServletResponse response) throws IOException {\n        exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);\n        List<SmsLogDO> list = smsLogService.getSmsLogPage(exportReqVO).getList();\n        // 导出 Excel\n        ExcelUtils.write(response, \"短信日志.xls\", \"数据\", SmsLogRespVO.class,\n                BeanUtils.toBean(list, SmsLogRespVO.class));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/sms/SmsTemplateController.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.sms;\n\nimport co.yixiang.yshop.framework.apilog.core.annotation.ApiAccessLog;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.controller.admin.sms.vo.template.*;\nimport co.yixiang.yshop.module.system.dal.dataobject.sms.SmsTemplateDO;\nimport co.yixiang.yshop.module.system.service.sms.SmsTemplateService;\nimport co.yixiang.yshop.module.system.service.sms.SmsSendService;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.excel.core.util.ExcelUtils;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.Operation;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.web.bind.annotation.*;\n\nimport jakarta.annotation.Resource;\nimport jakarta.servlet.http.HttpServletResponse;\nimport jakarta.validation.Valid;\nimport java.io.IOException;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.apilog.core.enums.OperateTypeEnum.EXPORT;\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 短信模板\")\n@RestController\n@RequestMapping(\"/system/sms-template\")\npublic class SmsTemplateController {\n\n    @Resource\n    private SmsTemplateService smsTemplateService;\n    @Resource\n    private SmsSendService smsSendService;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建短信模板\")\n    @PreAuthorize(\"@ss.hasPermission('system:sms-template:create')\")\n    public CommonResult<Long> createSmsTemplate(@Valid @RequestBody SmsTemplateSaveReqVO createReqVO) {\n        return success(smsTemplateService.createSmsTemplate(createReqVO));\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"更新短信模板\")\n    @PreAuthorize(\"@ss.hasPermission('system:sms-template:update')\")\n    public CommonResult<Boolean> updateSmsTemplate(@Valid @RequestBody SmsTemplateSaveReqVO updateReqVO) {\n        smsTemplateService.updateSmsTemplate(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除短信模板\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('system:sms-template:delete')\")\n    public CommonResult<Boolean> deleteSmsTemplate(@RequestParam(\"id\") Long id) {\n        smsTemplateService.deleteSmsTemplate(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得短信模板\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('system:sms-template:query')\")\n    public CommonResult<SmsTemplateRespVO> getSmsTemplate(@RequestParam(\"id\") Long id) {\n        SmsTemplateDO template = smsTemplateService.getSmsTemplate(id);\n        return success(BeanUtils.toBean(template, SmsTemplateRespVO.class));\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得短信模板分页\")\n    @PreAuthorize(\"@ss.hasPermission('system:sms-template:query')\")\n    public CommonResult<PageResult<SmsTemplateRespVO>> getSmsTemplatePage(@Valid SmsTemplatePageReqVO pageVO) {\n        PageResult<SmsTemplateDO> pageResult = smsTemplateService.getSmsTemplatePage(pageVO);\n        return success(BeanUtils.toBean(pageResult, SmsTemplateRespVO.class));\n    }\n\n    @GetMapping(\"/export-excel\")\n    @Operation(summary = \"导出短信模板 Excel\")\n    @PreAuthorize(\"@ss.hasPermission('system:sms-template:export')\")\n    @ApiAccessLog(operateType = EXPORT)\n    public void exportSmsTemplateExcel(@Valid SmsTemplatePageReqVO exportReqVO,\n                                       HttpServletResponse response) throws IOException {\n        exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);\n        List<SmsTemplateDO> list = smsTemplateService.getSmsTemplatePage(exportReqVO).getList();\n        // 导出 Excel\n        ExcelUtils.write(response, \"短信模板.xls\", \"数据\", SmsTemplateRespVO.class,\n                BeanUtils.toBean(list, SmsTemplateRespVO.class));\n    }\n\n    @PostMapping(\"/send-sms\")\n    @Operation(summary = \"发送短信\")\n    @PreAuthorize(\"@ss.hasPermission('system:sms-template:send-sms')\")\n    public CommonResult<Long> sendSms(@Valid @RequestBody SmsTemplateSendReqVO sendReqVO) {\n        return success(smsSendService.sendSingleSmsToAdmin(sendReqVO.getMobile(), null,\n                sendReqVO.getTemplateCode(), sendReqVO.getTemplateParams()));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/sms/vo/channel/SmsChannelPageReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.sms.vo.channel;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 短信渠道分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class SmsChannelPageReqVO extends PageParam {\n\n    @Schema(description = \"任务状态\", example = \"1\")\n    private Integer status;\n\n    @Schema(description = \"短信签名，模糊匹配\", example = \"yshop\")\n    private String signature;\n\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    @Schema(description = \"创建时间\")\n    private LocalDateTime[] createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/sms/vo/channel/SmsChannelRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.sms.vo.channel;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport org.hibernate.validator.constraints.URL;\n\nimport jakarta.validation.constraints.NotNull;\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 短信渠道 Response VO\")\n@Data\npublic class SmsChannelRespVO {\n\n    @Schema(description = \"编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"短信签名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    @NotNull(message = \"短信签名不能为空\")\n    private String signature;\n\n    @Schema(description = \"渠道编码，参见 SmsChannelEnum 枚举类\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"YUN_PIAN\")\n    private String code;\n\n    @Schema(description = \"启用状态\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @NotNull(message = \"启用状态不能为空\")\n    private Integer status;\n\n    @Schema(description = \"备注\", example = \"好吃！\")\n    private String remark;\n\n    @Schema(description = \"短信 API 的账号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    @NotNull(message = \"短信 API 的账号不能为空\")\n    private String apiKey;\n\n    @Schema(description = \"短信 API 的密钥\", example = \"yuanma\")\n    private String apiSecret;\n\n    @Schema(description = \"短信发送回调 URL\", example = \"https://www.yixiang.co\")\n    @URL(message = \"回调 URL 格式不正确\")\n    private String callbackUrl;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/sms/vo/channel/SmsChannelSaveReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.sms.vo.channel;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport org.hibernate.validator.constraints.URL;\n\nimport jakarta.validation.constraints.NotNull;\n\n@Schema(description = \"管理后台 - 短信渠道创建/修改 Request VO\")\n@Data\npublic class SmsChannelSaveReqVO {\n\n    @Schema(description = \"编号\", example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"短信签名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    @NotNull(message = \"短信签名不能为空\")\n    private String signature;\n\n    @Schema(description = \"渠道编码，参见 SmsChannelEnum 枚举类\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"YUN_PIAN\")\n    @NotNull(message = \"渠道编码不能为空\")\n    private String code;\n\n    @Schema(description = \"启用状态\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @NotNull(message = \"启用状态不能为空\")\n    private Integer status;\n\n    @Schema(description = \"备注\", example = \"好吃！\")\n    private String remark;\n\n    @Schema(description = \"短信 API 的账号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    @NotNull(message = \"短信 API 的账号不能为空\")\n    private String apiKey;\n\n    @Schema(description = \"短信 API 的密钥\", example = \"yuanma\")\n    private String apiSecret;\n\n    @Schema(description = \"短信发送回调 URL\", example = \"http://www.yixiang.co\")\n    @URL(message = \"回调 URL 格式不正确\")\n    private String callbackUrl;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/sms/vo/channel/SmsChannelSimpleRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.sms.vo.channel;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\n@Schema(description = \"管理后台 - 短信渠道精简 Response VO\")\n@Data\npublic class SmsChannelSimpleRespVO {\n\n    @Schema(description = \"编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"短信签名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    private String signature;\n\n    @Schema(description = \"渠道编码，参见 SmsChannelEnum 枚举类\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"YUN_PIAN\")\n    private String code;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/sms/vo/log/SmsLogPageReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.sms.vo.log;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 短信日志分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class SmsLogPageReqVO extends PageParam {\n\n    @Schema(description = \"短信渠道编号\", example = \"10\")\n    private Long channelId;\n\n    @Schema(description = \"模板编号\", example = \"20\")\n    private Long templateId;\n\n    @Schema(description = \"手机号\", example = \"15601691300\")\n    private String mobile;\n\n    @Schema(description = \"发送状态，参见 SmsSendStatusEnum 枚举类\", example = \"1\")\n    private Integer sendStatus;\n\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    @Schema(description = \"发送时间\")\n    private LocalDateTime[] sendTime;\n\n    @Schema(description = \"接收状态，参见 SmsReceiveStatusEnum 枚举类\", example = \"0\")\n    private Integer receiveStatus;\n\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    @Schema(description = \"接收时间\")\n    private LocalDateTime[] receiveTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/sms/vo/log/SmsLogRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.sms.vo.log;\n\nimport co.yixiang.yshop.framework.excel.core.annotations.DictFormat;\nimport co.yixiang.yshop.framework.excel.core.convert.DictConvert;\nimport co.yixiang.yshop.framework.excel.core.convert.JsonConvert;\nimport co.yixiang.yshop.module.system.enums.DictTypeConstants;\nimport com.alibaba.excel.annotation.ExcelIgnoreUnannotated;\nimport com.alibaba.excel.annotation.ExcelProperty;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\nimport java.util.Map;\n\n@Schema(description = \"管理后台 - 短信日志 Response VO\")\n@Data\n@ExcelIgnoreUnannotated\npublic class SmsLogRespVO {\n\n    @Schema(description = \"编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @ExcelProperty(\"编号\")\n    private Long id;\n\n    @Schema(description = \"短信渠道编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"10\")\n    @ExcelProperty(\"短信渠道编号\")\n    private Long channelId;\n\n    @Schema(description = \"短信渠道编码\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"ALIYUN\")\n    @ExcelProperty(\"短信渠道编码\")\n    private String channelCode;\n\n    @Schema(description = \"模板编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"20\")\n    @ExcelProperty(\"模板编号\")\n    private Long templateId;\n\n    @Schema(description = \"模板编码\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"test-01\")\n    @ExcelProperty(\"模板编码\")\n    private String templateCode;\n\n    @Schema(description = \"短信类型\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @ExcelProperty(value = \"短信类型\", converter = DictConvert.class)\n    @DictFormat(DictTypeConstants.SMS_TEMPLATE_TYPE)\n    private Integer templateType;\n\n    @Schema(description = \"短信内容\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"你好，你的验证码是 1024\")\n    @ExcelProperty(\"短信内容\")\n    private String templateContent;\n\n    @Schema(description = \"短信参数\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"name,code\")\n    @ExcelProperty(value = \"短信参数\", converter = JsonConvert.class)\n    private Map<String, Object> templateParams;\n\n    @Schema(description = \"短信 API 的模板编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"SMS_207945135\")\n    @ExcelProperty(\"短信 API 的模板编号\")\n    private String apiTemplateId;\n\n    @Schema(description = \"手机号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"15601691300\")\n    @ExcelProperty(\"手机号\")\n    private String mobile;\n\n    @Schema(description = \"用户编号\", example = \"10\")\n    @ExcelProperty(\"用户编号\")\n    private Long userId;\n\n    @Schema(description = \"用户类型\", example = \"1\")\n    @ExcelProperty(value = \"用户类型\", converter = DictConvert.class)\n    @DictFormat(DictTypeConstants.USER_TYPE)\n    private Integer userType;\n\n    @Schema(description = \"发送状态\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @ExcelProperty(value = \"发送状态\", converter = DictConvert.class)\n    @DictFormat(DictTypeConstants.SMS_SEND_STATUS)\n    private Integer sendStatus;\n\n    @Schema(description = \"发送时间\")\n    @ExcelProperty(\"发送时间\")\n    private LocalDateTime sendTime;\n\n    @Schema(description = \"短信 API 发送结果的编码\", example = \"SUCCESS\")\n    @ExcelProperty(\"短信 API 发送结果的编码\")\n    private String apiSendCode;\n\n    @Schema(description = \"短信 API 发送失败的提示\", example = \"成功\")\n    @ExcelProperty(\"短信 API 发送失败的提示\")\n    private String apiSendMsg;\n\n    @Schema(description = \"短信 API 发送返回的唯一请求 ID\", example = \"3837C6D3-B96F-428C-BBB2-86135D4B5B99\")\n    @ExcelProperty(\"短信 API 发送返回的唯一请求 ID\")\n    private String apiRequestId;\n\n    @Schema(description = \"短信 API 发送返回的序号\", example = \"62923244790\")\n    @ExcelProperty(\"短信 API 发送返回的序号\")\n    private String apiSerialNo;\n\n    @Schema(description = \"接收状态\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"0\")\n    @ExcelProperty(value = \"接收状态\", converter = DictConvert.class)\n    @DictFormat(DictTypeConstants.SMS_RECEIVE_STATUS)\n    private Integer receiveStatus;\n\n    @Schema(description = \"接收时间\")\n    @ExcelProperty(\"接收时间\")\n    private LocalDateTime receiveTime;\n\n    @Schema(description = \"API 接收结果的编码\", example = \"DELIVRD\")\n    @ExcelProperty(\"API 接收结果的编码\")\n    private String apiReceiveCode;\n\n    @Schema(description = \"API 接收结果的说明\", example = \"用户接收成功\")\n    @ExcelProperty(\"API 接收结果的说明\")\n    private String apiReceiveMsg;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @ExcelProperty(\"创建时间\")\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/sms/vo/template/SmsTemplatePageReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.sms.vo.template;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 短信模板分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class SmsTemplatePageReqVO extends PageParam {\n\n    @Schema(description = \"短信签名\", example = \"1\")\n    private Integer type;\n\n    @Schema(description = \"开启状态\", example = \"1\")\n    private Integer status;\n\n    @Schema(description = \"模板编码，模糊匹配\", example = \"test_01\")\n    private String code;\n\n    @Schema(description = \"模板内容，模糊匹配\", example = \"你好，{name}。你长的太{like}啦！\")\n    private String content;\n\n    @Schema(description = \"短信 API 的模板编号，模糊匹配\", example = \"4383920\")\n    private String apiTemplateId;\n\n    @Schema(description = \"短信渠道编号\", example = \"10\")\n    private Long channelId;\n\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    @Schema(description = \"创建时间\")\n    private LocalDateTime[] createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/sms/vo/template/SmsTemplateRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.sms.vo.template;\n\nimport co.yixiang.yshop.framework.excel.core.annotations.DictFormat;\nimport co.yixiang.yshop.framework.excel.core.convert.DictConvert;\nimport co.yixiang.yshop.module.system.enums.DictTypeConstants;\nimport com.alibaba.excel.annotation.ExcelIgnoreUnannotated;\nimport com.alibaba.excel.annotation.ExcelProperty;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\nimport java.util.List;\n\n@Schema(description = \"管理后台 - 短信模板 Response VO\")\n@Data\n@ExcelIgnoreUnannotated\npublic class SmsTemplateRespVO {\n\n    @Schema(description = \"编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @ExcelProperty(\"编号\")\n    private Long id;\n\n    @Schema(description = \"短信类型，参见 SmsTemplateTypeEnum 枚举类\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @ExcelProperty(value = \"短信签名\", converter = DictConvert.class)\n    @DictFormat(DictTypeConstants.SMS_TEMPLATE_TYPE)\n    private Integer type;\n\n    @Schema(description = \"开启状态，参见 CommonStatusEnum 枚举类\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @ExcelProperty(value = \"开启状态\", converter = DictConvert.class)\n    @DictFormat(DictTypeConstants.COMMON_STATUS)\n    private Integer status;\n\n    @Schema(description = \"模板编码\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"test_01\")\n    @ExcelProperty(\"模板编码\")\n    private String code;\n\n    @Schema(description = \"模板名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    @ExcelProperty(\"模板名称\")\n    private String name;\n\n    @Schema(description = \"模板内容\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"你好，{name}。你长的太{like}啦！\")\n    @ExcelProperty(\"模板内容\")\n    private String content;\n\n    @Schema(description = \"参数数组\", example = \"name,code\")\n    private List<String> params;\n\n    @Schema(description = \"备注\", example = \"哈哈哈\")\n    @ExcelProperty(\"备注\")\n    private String remark;\n\n    @Schema(description = \"短信 API 的模板编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"4383920\")\n    @ExcelProperty(\"短信 API 的模板编号\")\n    private String apiTemplateId;\n\n    @Schema(description = \"短信渠道编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"10\")\n    @ExcelProperty(\"短信渠道编号\")\n    private Long channelId;\n\n    @Schema(description = \"短信渠道编码\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"ALIYUN\")\n    @ExcelProperty(value = \"短信渠道编码\", converter = DictConvert.class)\n    @DictFormat(DictTypeConstants.SMS_CHANNEL_CODE)\n    private String channelCode;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @ExcelProperty(\"创建时间\")\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/sms/vo/template/SmsTemplateSaveReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.sms.vo.template;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotNull;\n\n@Schema(description = \"管理后台 - 短信模板创建/修改 Request VO\")\n@Data\npublic class SmsTemplateSaveReqVO {\n\n    @Schema(description = \"编号\", example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"短信类型，参见 SmsTemplateTypeEnum 枚举类\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @NotNull(message = \"短信类型不能为空\")\n    private Integer type;\n\n    @Schema(description = \"开启状态，参见 CommonStatusEnum 枚举类\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @NotNull(message = \"开启状态不能为空\")\n    private Integer status;\n\n    @Schema(description = \"模板编码\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"test_01\")\n    @NotNull(message = \"模板编码不能为空\")\n    private String code;\n\n    @Schema(description = \"模板名称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    @NotNull(message = \"模板名称不能为空\")\n    private String name;\n\n    @Schema(description = \"模板内容\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"你好，{name}。你长的太{like}啦！\")\n    @NotNull(message = \"模板内容不能为空\")\n    private String content;\n\n    @Schema(description = \"备注\", example = \"哈哈哈\")\n    private String remark;\n\n    @Schema(description = \"短信 API 的模板编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"4383920\")\n    @NotNull(message = \"短信 API 的模板编号不能为空\")\n    private String apiTemplateId;\n\n    @Schema(description = \"短信渠道编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"10\")\n    @NotNull(message = \"短信渠道编号不能为空\")\n    private Long channelId;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/sms/vo/template/SmsTemplateSendReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.sms.vo.template;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotNull;\nimport java.util.Map;\n\n@Schema(description = \"管理后台 - 短信模板的发送 Request VO\")\n@Data\npublic class SmsTemplateSendReqVO {\n\n    @Schema(description = \"手机号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"15601691300\")\n    @NotNull(message = \"手机号不能为空\")\n    private String mobile;\n\n    @Schema(description = \"模板编码\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"test_01\")\n    @NotNull(message = \"模板编码不能为空\")\n    private String templateCode;\n\n    @Schema(description = \"模板参数\")\n    private Map<String, Object> templateParams;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/socail/SocialClientController.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.socail;\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.controller.admin.socail.vo.client.SocialClientPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.socail.vo.client.SocialClientRespVO;\nimport co.yixiang.yshop.module.system.controller.admin.socail.vo.client.SocialClientSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.social.SocialClientDO;\nimport co.yixiang.yshop.module.system.service.social.SocialClientService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Valid;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 社交客户端\")\n@RestController\n@RequestMapping(\"/system/social-client\")\n@Validated\npublic class SocialClientController {\n\n    @Resource\n    private SocialClientService socialClientService;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建社交客户端\")\n    @PreAuthorize(\"@ss.hasPermission('system:social-client:create')\")\n    public CommonResult<Long> createSocialClient(@Valid @RequestBody SocialClientSaveReqVO createReqVO) {\n        return success(socialClientService.createSocialClient(createReqVO));\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"更新社交客户端\")\n    @PreAuthorize(\"@ss.hasPermission('system:social-client:update')\")\n    public CommonResult<Boolean> updateSocialClient(@Valid @RequestBody SocialClientSaveReqVO updateReqVO) {\n        socialClientService.updateSocialClient(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除社交客户端\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('system:social-client:delete')\")\n    public CommonResult<Boolean> deleteSocialClient(@RequestParam(\"id\") Long id) {\n        socialClientService.deleteSocialClient(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得社交客户端\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('system:social-client:query')\")\n    public CommonResult<SocialClientRespVO> getSocialClient(@RequestParam(\"id\") Long id) {\n        SocialClientDO client = socialClientService.getSocialClient(id);\n        return success(BeanUtils.toBean(client, SocialClientRespVO.class));\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得社交客户端分页\")\n    @PreAuthorize(\"@ss.hasPermission('system:social-client:query')\")\n    public CommonResult<PageResult<SocialClientRespVO>> getSocialClientPage(@Valid SocialClientPageReqVO pageVO) {\n        PageResult<SocialClientDO> pageResult = socialClientService.getSocialClientPage(pageVO);\n        return success(BeanUtils.toBean(pageResult, SocialClientRespVO.class));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/socail/SocialUserController.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.socail;\n\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.controller.admin.socail.vo.user.SocialUserBindReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.socail.vo.user.SocialUserUnbindReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.socail.vo.user.SocialUserPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.socail.vo.user.SocialUserRespVO;\nimport co.yixiang.yshop.module.system.convert.social.SocialUserConvert;\nimport co.yixiang.yshop.module.system.dal.dataobject.social.SocialUserDO;\nimport co.yixiang.yshop.module.system.service.social.SocialUserService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Valid;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\nimport static co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;\n\n@Tag(name = \"管理后台 - 社交用户\")\n@RestController\n@RequestMapping(\"/system/social-user\")\n@Validated\npublic class SocialUserController {\n\n    @Resource\n    private SocialUserService socialUserService;\n\n    @PostMapping(\"/bind\")\n    @Operation(summary = \"社交绑定，使用 code 授权码\")\n    public CommonResult<Boolean> socialBind(@RequestBody @Valid SocialUserBindReqVO reqVO) {\n        socialUserService.bindSocialUser(SocialUserConvert.INSTANCE.convert(\n                getLoginUserId(), UserTypeEnum.ADMIN.getValue(), reqVO));\n        return CommonResult.success(true);\n    }\n\n    @DeleteMapping(\"/unbind\")\n    @Operation(summary = \"取消社交绑定\")\n    public CommonResult<Boolean> socialUnbind(@RequestBody SocialUserUnbindReqVO reqVO) {\n        socialUserService.unbindSocialUser(getLoginUserId(), UserTypeEnum.ADMIN.getValue(), reqVO.getType(), reqVO.getOpenid());\n        return CommonResult.success(true);\n    }\n\n    // ==================== 社交用户 CRUD ====================\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得社交用户\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('system:social-user:query')\")\n    public CommonResult<SocialUserRespVO> getSocialUser(@RequestParam(\"id\") Long id) {\n        SocialUserDO socialUser = socialUserService.getSocialUser(id);\n        return success(BeanUtils.toBean(socialUser, SocialUserRespVO.class));\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得社交用户分页\")\n    @PreAuthorize(\"@ss.hasPermission('system:social-user:query')\")\n    public CommonResult<PageResult<SocialUserRespVO>> getSocialUserPage(@Valid SocialUserPageReqVO pageVO) {\n        PageResult<SocialUserDO> pageResult = socialUserService.getSocialUserPage(pageVO);\n        return success(BeanUtils.toBean(pageResult, SocialUserRespVO.class));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/socail/vo/client/SocialClientPageReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.socail.vo.client;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\n\n@Schema(description = \"管理后台 - 社交客户端分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class SocialClientPageReqVO extends PageParam {\n\n    @Schema(description = \"应用名\", example = \"yshop商城\")\n    private String name;\n\n    @Schema(description = \"社交平台的类型\", example = \"31\")\n    private Integer socialType;\n\n    @Schema(description = \"用户类型\", example = \"2\")\n    private Integer userType;\n\n    @Schema(description = \"客户端编号\", example = \"145442115\")\n    private String clientId;\n\n    @Schema(description = \"状态\", example = \"1\")\n    private Integer status;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/socail/vo/client/SocialClientRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.socail.vo.client;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 社交客户端 Response VO\")\n@Data\npublic class SocialClientRespVO {\n\n    @Schema(description = \"编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"27162\")\n    private Long id;\n\n    @Schema(description = \"应用名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop商城\")\n    private String name;\n\n    @Schema(description = \"社交平台的类型\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"31\")\n    private Integer socialType;\n\n    @Schema(description = \"用户类型\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"2\")\n    private Integer userType;\n\n    @Schema(description = \"客户端编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"wwd411c69a39ad2e54\")\n    private String clientId;\n\n    @Schema(description = \"客户端密钥\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"peter\")\n    private String clientSecret;\n\n    @Schema(description = \"授权方的网页应用编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"2000045\")\n    private String agentId;\n\n    @Schema(description = \"状态\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    private Integer status;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/socail/vo/client/SocialClientSaveReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.socail.vo.client;\n\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.common.validation.InEnum;\nimport co.yixiang.yshop.module.system.enums.social.SocialTypeEnum;\nimport com.fasterxml.jackson.annotation.JsonIgnore;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.AssertTrue;\nimport jakarta.validation.constraints.NotNull;\nimport java.util.Objects;\n\n@Schema(description = \"管理后台 - 社交客户端创建/修改 Request VO\")\n@Data\npublic class SocialClientSaveReqVO {\n\n    @Schema(description = \"编号\", example = \"27162\")\n    private Long id;\n\n    @Schema(description = \"应用名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop商城\")\n    @NotNull(message = \"应用名不能为空\")\n    private String name;\n\n    @Schema(description = \"社交平台的类型\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"31\")\n    @NotNull(message = \"社交平台的类型不能为空\")\n    @InEnum(SocialTypeEnum.class)\n    private Integer socialType;\n\n    @Schema(description = \"用户类型\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"2\")\n    @NotNull(message = \"用户类型不能为空\")\n    @InEnum(UserTypeEnum.class)\n    private Integer userType;\n\n    @Schema(description = \"客户端编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"wwd411c69a39ad2e54\")\n    @NotNull(message = \"客户端编号不能为空\")\n    private String clientId;\n\n    @Schema(description = \"客户端密钥\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"peter\")\n    @NotNull(message = \"客户端密钥不能为空\")\n    private String clientSecret;\n\n    @Schema(description = \"授权方的网页应用编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"2000045\")\n    private String agentId;\n\n    @Schema(description = \"状态\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @NotNull(message = \"状态不能为空\")\n    @InEnum(CommonStatusEnum.class)\n    private Integer status;\n\n    @AssertTrue(message = \"agentId 不能为空\")\n    @JsonIgnore\n    public boolean isAgentIdValid() {\n        // 如果是企业微信，必须填写 agentId 属性\n        return !Objects.equals(socialType, SocialTypeEnum.WECHAT_ENTERPRISE.getType())\n                || !StrUtil.isEmpty(agentId);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/socail/vo/user/SocialUserBindReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.socail.vo.user;\n\nimport co.yixiang.yshop.module.system.enums.social.SocialTypeEnum;\nimport co.yixiang.yshop.framework.common.validation.InEnum;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\n\n@Schema(description = \"管理后台 - 社交绑定 Request VO，使用 code 授权码\")\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\n@Builder\npublic class SocialUserBindReqVO {\n\n    @Schema(description = \"社交平台的类型，参见 UserSocialTypeEnum 枚举值\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"10\")\n    @InEnum(SocialTypeEnum.class)\n    @NotNull(message = \"社交平台的类型不能为空\")\n    private Integer type;\n\n    @Schema(description = \"授权码\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @NotEmpty(message = \"授权码不能为空\")\n    private String code;\n\n    @Schema(description = \"state\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"9b2ffbc1-7425-4155-9894-9d5c08541d62\")\n    @NotEmpty(message = \"state 不能为空\")\n    private String state;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/socail/vo/user/SocialUserPageReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.socail.vo.user;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 社交用户分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class SocialUserPageReqVO extends PageParam {\n\n    @Schema(description = \"社交平台的类型\", example = \"30\")\n    private Integer type;\n\n    @Schema(description = \"用户昵称\", example = \"李四\")\n    private String nickname;\n\n    @Schema(description = \"社交 openid\", example = \"oz-Jdt0kd_jdhUxJHQdBJMlOFN7w\")\n    private String openid;\n\n    @Schema(description = \"创建时间\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/socail/vo/user/SocialUserRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.socail.vo.user;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 社交用户 Response VO\")\n@Data\npublic class SocialUserRespVO {\n\n    @Schema(description = \"主键(自增策略)\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"14569\")\n    private Long id;\n\n    @Schema(description = \"社交平台的类型\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"30\")\n    private Integer type;\n\n    @Schema(description = \"社交 openid\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"666\")\n    private String openid;\n\n    @Schema(description = \"社交 token\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"666\")\n    private String token;\n\n    @Schema(description = \"原始 Token 数据，一般是 JSON 格式\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"{}\")\n    private String rawTokenInfo;\n\n    @Schema(description = \"用户昵称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    private String nickname;\n\n    @Schema(description = \"用户头像\", example = \"https://www.yixiang.co/xxx.png\")\n    private String avatar;\n\n    @Schema(description = \"原始用户数据，一般是 JSON 格式\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"{}\")\n    private String rawUserInfo;\n\n    @Schema(description = \"最后一次的认证 code\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"666666\")\n    private String code;\n\n    @Schema(description = \"最后一次的认证 state\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"123456\")\n    private String state;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private LocalDateTime createTime;\n\n    @Schema(description = \"更新时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private LocalDateTime updateTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/socail/vo/user/SocialUserUnbindReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.socail.vo.user;\n\nimport co.yixiang.yshop.framework.common.validation.InEnum;\nimport co.yixiang.yshop.module.system.enums.social.SocialTypeEnum;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\n\n@Schema(description = \"管理后台 - 取消社交绑定 Request VO\")\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\n@Builder\npublic class SocialUserUnbindReqVO {\n\n    @Schema(description = \"社交平台的类型，参见 UserSocialTypeEnum 枚举值\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"10\")\n    @InEnum(SocialTypeEnum.class)\n    @NotNull(message = \"社交平台的类型不能为空\")\n    private Integer type;\n\n    @Schema(description = \"社交用户的 openid\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"IPRmJ0wvBptiPIlGEZiPewGwiEiE\")\n    @NotEmpty(message = \"社交用户的 openid 不能为空\")\n    private String openid;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/tenant/TenantController.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.tenant;\n\nimport co.yixiang.yshop.framework.apilog.core.annotation.ApiAccessLog;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.framework.excel.core.util.ExcelUtils;\nimport co.yixiang.yshop.module.system.controller.admin.tenant.vo.tenant.TenantPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.tenant.vo.tenant.TenantRespVO;\nimport co.yixiang.yshop.module.system.controller.admin.tenant.vo.tenant.TenantSaveReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.tenant.vo.tenant.TenantSimpleRespVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.tenant.TenantDO;\nimport co.yixiang.yshop.module.system.service.tenant.TenantService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.annotation.Resource;\nimport jakarta.annotation.security.PermitAll;\nimport jakarta.servlet.http.HttpServletResponse;\nimport jakarta.validation.Valid;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.web.bind.annotation.*;\n\nimport java.io.IOException;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.apilog.core.enums.OperateTypeEnum.EXPORT;\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 租户\")\n@RestController\n@RequestMapping(\"/system/tenant\")\npublic class TenantController {\n\n    @Resource\n    private TenantService tenantService;\n\n    @GetMapping(\"/get-id-by-name\")\n    @PermitAll\n    @Operation(summary = \"使用租户名，获得租户编号\", description = \"登录界面，根据用户的租户名，获得租户编号\")\n    @Parameter(name = \"name\", description = \"租户名\", required = true, example = \"1024\")\n    public CommonResult<Long> getTenantIdByName(@RequestParam(\"name\") String name) {\n        TenantDO tenant = tenantService.getTenantByName(name);\n        return success(tenant != null ? tenant.getId() : null);\n    }\n\n    @GetMapping(\"/get-by-website\")\n    @PermitAll\n    @Operation(summary = \"使用域名，获得租户信息\", description = \"登录界面，根据用户的域名，获得租户信息\")\n    @Parameter(name = \"website\", description = \"域名\", required = true, example = \"www.yixiang.co\")\n    public CommonResult<TenantSimpleRespVO> getTenantByWebsite(@RequestParam(\"website\") String website) {\n        TenantDO tenant = tenantService.getTenantByWebsite(website);\n        return success(BeanUtils.toBean(tenant, TenantSimpleRespVO.class));\n    }\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建租户\")\n    @PreAuthorize(\"@ss.hasPermission('system:tenant:create')\")\n    public CommonResult<Long> createTenant(@Valid @RequestBody TenantSaveReqVO createReqVO) {\n        return success(tenantService.createTenant(createReqVO));\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"更新租户\")\n    @PreAuthorize(\"@ss.hasPermission('system:tenant:update')\")\n    public CommonResult<Boolean> updateTenant(@Valid @RequestBody TenantSaveReqVO updateReqVO) {\n        tenantService.updateTenant(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除租户\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('system:tenant:delete')\")\n    public CommonResult<Boolean> deleteTenant(@RequestParam(\"id\") Long id) {\n        tenantService.deleteTenant(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得租户\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('system:tenant:query')\")\n    public CommonResult<TenantRespVO> getTenant(@RequestParam(\"id\") Long id) {\n        TenantDO tenant = tenantService.getTenant(id);\n        return success(BeanUtils.toBean(tenant, TenantRespVO.class));\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得租户分页\")\n    @PreAuthorize(\"@ss.hasPermission('system:tenant:query')\")\n    public CommonResult<PageResult<TenantRespVO>> getTenantPage(@Valid TenantPageReqVO pageVO) {\n        PageResult<TenantDO> pageResult = tenantService.getTenantPage(pageVO);\n        return success(BeanUtils.toBean(pageResult, TenantRespVO.class));\n    }\n\n    @GetMapping(\"/export-excel\")\n    @Operation(summary = \"导出租户 Excel\")\n    @PreAuthorize(\"@ss.hasPermission('system:tenant:export')\")\n    @ApiAccessLog(operateType = EXPORT)\n    public void exportTenantExcel(@Valid TenantPageReqVO exportReqVO,\n                                  HttpServletResponse response) throws IOException {\n        exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);\n        List<TenantDO> list = tenantService.getTenantPage(exportReqVO).getList();\n        // 导出 Excel\n        ExcelUtils.write(response, \"租户.xls\", \"数据\", TenantRespVO.class,\n                BeanUtils.toBean(list, TenantRespVO.class));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/tenant/TenantPackageController.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.tenant;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.controller.admin.tenant.vo.packages.*;\nimport co.yixiang.yshop.module.system.dal.dataobject.tenant.TenantPackageDO;\nimport co.yixiang.yshop.module.system.service.tenant.TenantPackageService;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.Operation;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\n\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Valid;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"管理后台 - 租户套餐\")\n@RestController\n@RequestMapping(\"/system/tenant-package\")\n@Validated\npublic class TenantPackageController {\n\n    @Resource\n    private TenantPackageService tenantPackageService;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"创建租户套餐\")\n    @PreAuthorize(\"@ss.hasPermission('system:tenant-package:create')\")\n    public CommonResult<Long> createTenantPackage(@Valid @RequestBody TenantPackageSaveReqVO createReqVO) {\n        return success(tenantPackageService.createTenantPackage(createReqVO));\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"更新租户套餐\")\n    @PreAuthorize(\"@ss.hasPermission('system:tenant-package:update')\")\n    public CommonResult<Boolean> updateTenantPackage(@Valid @RequestBody TenantPackageSaveReqVO updateReqVO) {\n        tenantPackageService.updateTenantPackage(updateReqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除租户套餐\")\n    @Parameter(name = \"id\", description = \"编号\", required = true)\n    @PreAuthorize(\"@ss.hasPermission('system:tenant-package:delete')\")\n    public CommonResult<Boolean> deleteTenantPackage(@RequestParam(\"id\") Long id) {\n        tenantPackageService.deleteTenantPackage(id);\n        return success(true);\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得租户套餐\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('system:tenant-package:query')\")\n    public CommonResult<TenantPackageRespVO> getTenantPackage(@RequestParam(\"id\") Long id) {\n        TenantPackageDO tenantPackage = tenantPackageService.getTenantPackage(id);\n        return success(BeanUtils.toBean(tenantPackage, TenantPackageRespVO.class));\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得租户套餐分页\")\n    @PreAuthorize(\"@ss.hasPermission('system:tenant-package:query')\")\n    public CommonResult<PageResult<TenantPackageRespVO>> getTenantPackagePage(@Valid TenantPackagePageReqVO pageVO) {\n        PageResult<TenantPackageDO> pageResult = tenantPackageService.getTenantPackagePage(pageVO);\n        return success(BeanUtils.toBean(pageResult, TenantPackageRespVO.class));\n    }\n\n    @GetMapping({\"/get-simple-list\", \"simple-list\"})\n    @Operation(summary = \"获取租户套餐精简信息列表\", description = \"只包含被开启的租户套餐，主要用于前端的下拉选项\")\n    public CommonResult<List<TenantPackageSimpleRespVO>> getTenantPackageList() {\n        List<TenantPackageDO> list = tenantPackageService.getTenantPackageListByStatus(CommonStatusEnum.ENABLE.getStatus());\n        return success(BeanUtils.toBean(list, TenantPackageSimpleRespVO.class));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/tenant/vo/packages/TenantPackagePageReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.tenant.vo.packages;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 租户套餐分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class TenantPackagePageReqVO extends PageParam {\n\n    @Schema(description = \"套餐名\", example = \"VIP\")\n    private String name;\n\n    @Schema(description = \"状态\", example = \"1\")\n    private Integer status;\n\n    @Schema(description = \"备注\", example = \"好\")\n    private String remark;\n\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    @Schema(description = \"创建时间\")\n    private LocalDateTime[] createTime;\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/tenant/vo/packages/TenantPackageRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.tenant.vo.packages;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\nimport java.util.Set;\n\n@Schema(description = \"管理后台 - 租户套餐 Response VO\")\n@Data\npublic class TenantPackageRespVO {\n\n    @Schema(description = \"套餐编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"套餐名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"VIP\")\n    private String name;\n\n    @Schema(description = \"状态，参见 CommonStatusEnum 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    private Integer status;\n\n    @Schema(description = \"备注\", example = \"好\")\n    private String remark;\n\n    @Schema(description = \"关联的菜单编号\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private Set<Long> menuIds;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/tenant/vo/packages/TenantPackageSaveReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.tenant.vo.packages;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.validation.InEnum;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\nimport java.util.Set;\n\n@Schema(description = \"管理后台 - 租户套餐创建/修改 Request VO\")\n@Data\npublic class TenantPackageSaveReqVO {\n\n    @Schema(description = \"套餐编号\", example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"套餐名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"VIP\")\n    @NotEmpty(message = \"套餐名不能为空\")\n    private String name;\n\n    @Schema(description = \"状态，参见 CommonStatusEnum 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @NotNull(message = \"状态不能为空\")\n    @InEnum(value = CommonStatusEnum.class, message = \"状态必须是 {value}\")\n    private Integer status;\n\n    @Schema(description = \"备注\", example = \"好\")\n    private String remark;\n\n    @Schema(description = \"关联的菜单编号\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @NotNull(message = \"关联的菜单编号不能为空\")\n    private Set<Long> menuIds;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/tenant/vo/packages/TenantPackageSimpleRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.tenant.vo.packages;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotNull;\n\n@Schema(description = \"管理后台 - 租户套餐精简 Response VO\")\n@Data\npublic class TenantPackageSimpleRespVO {\n\n    @Schema(description = \"套餐编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @NotNull(message = \"套餐编号不能为空\")\n    private Long id;\n\n    @Schema(description = \"套餐名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"VIP\")\n    @NotNull(message = \"套餐名不能为空\")\n    private String name;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/tenant/vo/tenant/TenantPageReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.tenant.vo.tenant;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 租户分页 Request VO\")\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class TenantPageReqVO extends PageParam {\n\n    @Schema(description = \"租户名\", example = \"yshop\")\n    private String name;\n\n    @Schema(description = \"联系人\", example = \"yshop\")\n    private String contactName;\n\n    @Schema(description = \"联系手机\", example = \"15601691300\")\n    private String contactMobile;\n\n    @Schema(description = \"租户状态（0正常 1停用）\", example = \"1\")\n    private Integer status;\n\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    @Schema(description = \"创建时间\")\n    private LocalDateTime[] createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/tenant/vo/tenant/TenantRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.tenant.vo.tenant;\n\nimport co.yixiang.yshop.framework.excel.core.annotations.DictFormat;\nimport co.yixiang.yshop.framework.excel.core.convert.DictConvert;\nimport co.yixiang.yshop.module.system.enums.DictTypeConstants;\nimport com.alibaba.excel.annotation.ExcelIgnoreUnannotated;\nimport com.alibaba.excel.annotation.ExcelProperty;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 租户 Response VO\")\n@Data\n@ExcelIgnoreUnannotated\npublic class TenantRespVO {\n\n    @Schema(description = \"租户编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @ExcelProperty(\"租户编号\")\n    private Long id;\n\n    @Schema(description = \"租户名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    @ExcelProperty(\"租户名\")\n    private String name;\n\n    @Schema(description = \"联系人\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    @ExcelProperty(\"联系人\")\n    private String contactName;\n\n    @Schema(description = \"联系手机\", example = \"15601691300\")\n    @ExcelProperty(\"联系手机\")\n    private String contactMobile;\n\n    @Schema(description = \"租户状态\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @ExcelProperty(value = \"状态\", converter = DictConvert.class)\n    @DictFormat(DictTypeConstants.COMMON_STATUS)\n    private Integer status;\n\n    @Schema(description = \"绑定域名\", example = \"https://www.yixiang.co\")\n    private String website;\n\n    @Schema(description = \"租户套餐编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Long packageId;\n\n    @Schema(description = \"过期时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private LocalDateTime expireTime;\n\n    @Schema(description = \"账号数量\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Integer accountCount;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @ExcelProperty(\"创建时间\")\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/tenant/vo/tenant/TenantSaveReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.tenant.vo.tenant;\n\nimport cn.hutool.core.util.ObjectUtil;\nimport com.fasterxml.jackson.annotation.JsonIgnore;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport org.hibernate.validator.constraints.Length;\n\nimport jakarta.validation.constraints.AssertTrue;\nimport jakarta.validation.constraints.NotNull;\nimport jakarta.validation.constraints.Pattern;\nimport jakarta.validation.constraints.Size;\nimport java.time.LocalDateTime;\n\n@Schema(description = \"管理后台 - 租户创建/修改 Request VO\")\n@Data\npublic class TenantSaveReqVO {\n\n    @Schema(description = \"租户编号\", example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"租户名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    @NotNull(message = \"租户名不能为空\")\n    private String name;\n\n    @Schema(description = \"联系人\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    @NotNull(message = \"联系人不能为空\")\n    private String contactName;\n\n    @Schema(description = \"联系手机\", example = \"15601691300\")\n    private String contactMobile;\n\n    @Schema(description = \"租户状态\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @NotNull(message = \"租户状态\")\n    private Integer status;\n\n    @Schema(description = \"绑定域名\", example = \"https://www.yixiang.co\")\n    private String website;\n\n    @Schema(description = \"租户套餐编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @NotNull(message = \"租户套餐编号不能为空\")\n    private Long packageId;\n\n    @Schema(description = \"过期时间\", requiredMode = Schema.RequiredMode.REQUIRED)\n    @NotNull(message = \"过期时间不能为空\")\n    private LocalDateTime expireTime;\n\n    @Schema(description = \"账号数量\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @NotNull(message = \"账号数量不能为空\")\n    private Integer accountCount;\n\n    // ========== 仅【创建】时，需要传递的字段 ==========\n\n    @Schema(description = \"用户账号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    @Pattern(regexp = \"^[a-zA-Z0-9]{4,30}$\", message = \"用户账号由 数字、字母 组成\")\n    @Size(min = 4, max = 30, message = \"用户账号长度为 4-30 个字符\")\n    private String username;\n\n    @Schema(description = \"密码\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"123456\")\n    @Length(min = 4, max = 16, message = \"密码长度为 4-16 位\")\n    private String password;\n\n    @AssertTrue(message = \"用户账号、密码不能为空\")\n    @JsonIgnore\n    public boolean isUsernameValid() {\n        return id != null // 修改时，不需要传递\n                || (ObjectUtil.isAllNotEmpty(username, password)); // 新增时，必须都传递 username、password\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/tenant/vo/tenant/TenantSimpleRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.tenant.vo.tenant;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\n@Schema(description = \"管理后台 - 租户精简 Response VO\")\n@Data\npublic class TenantSimpleRespVO {\n\n    @Schema(description = \"租户编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"租户名\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    private String name;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/user/UserController.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.user;\n\nimport cn.hutool.core.collection.CollUtil;\nimport co.yixiang.yshop.framework.apilog.core.annotation.ApiAccessLog;\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.excel.core.util.ExcelUtils;\nimport co.yixiang.yshop.module.system.controller.admin.user.vo.user.*;\nimport co.yixiang.yshop.module.system.convert.user.UserConvert;\nimport co.yixiang.yshop.module.system.dal.dataobject.dept.DeptDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.user.AdminUserDO;\nimport co.yixiang.yshop.module.system.enums.common.SexEnum;\nimport co.yixiang.yshop.module.system.service.dept.DeptService;\nimport co.yixiang.yshop.module.system.service.user.AdminUserService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.Parameters;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport jakarta.annotation.Resource;\nimport jakarta.servlet.http.HttpServletResponse;\nimport jakarta.validation.Valid;\nimport org.springframework.security.access.prepost.PreAuthorize;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\nimport org.springframework.web.multipart.MultipartFile;\n\nimport java.io.IOException;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Map;\n\nimport static co.yixiang.yshop.framework.apilog.core.enums.OperateTypeEnum.EXPORT;\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\nimport static co.yixiang.yshop.framework.common.util.collection.CollectionUtils.convertList;\n\n@Tag(name = \"管理后台 - 用户\")\n@RestController\n@RequestMapping(\"/system/user\")\n@Validated\npublic class UserController {\n\n    @Resource\n    private AdminUserService userService;\n    @Resource\n    private DeptService deptService;\n\n    @PostMapping(\"/create\")\n    @Operation(summary = \"新增用户\")\n    @PreAuthorize(\"@ss.hasPermission('system:user:create')\")\n    public CommonResult<Long> createUser(@Valid @RequestBody UserSaveReqVO reqVO) {\n        Long id = userService.createUser(reqVO);\n        return success(id);\n    }\n\n    @PutMapping(\"update\")\n    @Operation(summary = \"修改用户\")\n    @PreAuthorize(\"@ss.hasPermission('system:user:update')\")\n    public CommonResult<Boolean> updateUser(@Valid @RequestBody UserSaveReqVO reqVO) {\n        userService.updateUser(reqVO);\n        return success(true);\n    }\n\n    @DeleteMapping(\"/delete\")\n    @Operation(summary = \"删除用户\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('system:user:delete')\")\n    public CommonResult<Boolean> deleteUser(@RequestParam(\"id\") Long id) {\n        userService.deleteUser(id);\n        return success(true);\n    }\n\n    @PutMapping(\"/update-password\")\n    @Operation(summary = \"重置用户密码\")\n    @PreAuthorize(\"@ss.hasPermission('system:user:update-password')\")\n    public CommonResult<Boolean> updateUserPassword(@Valid @RequestBody UserUpdatePasswordReqVO reqVO) {\n        userService.updateUserPassword(reqVO.getId(), reqVO.getPassword());\n        return success(true);\n    }\n\n    @PutMapping(\"/update-status\")\n    @Operation(summary = \"修改用户状态\")\n    @PreAuthorize(\"@ss.hasPermission('system:user:update')\")\n    public CommonResult<Boolean> updateUserStatus(@Valid @RequestBody UserUpdateStatusReqVO reqVO) {\n        userService.updateUserStatus(reqVO.getId(), reqVO.getStatus());\n        return success(true);\n    }\n\n    @GetMapping(\"/page\")\n    @Operation(summary = \"获得用户分页列表\")\n    @PreAuthorize(\"@ss.hasPermission('system:user:list')\")\n    public CommonResult<PageResult<UserRespVO>> getUserPage(@Valid UserPageReqVO pageReqVO) {\n        // 获得用户分页列表\n        PageResult<AdminUserDO> pageResult = userService.getUserPage(pageReqVO);\n        if (CollUtil.isEmpty(pageResult.getList())) {\n            return success(new PageResult<>(pageResult.getTotal()));\n        }\n        // 拼接数据\n        Map<Long, DeptDO> deptMap = deptService.getDeptMap(\n                convertList(pageResult.getList(), AdminUserDO::getDeptId));\n        return success(new PageResult<>(UserConvert.INSTANCE.convertList(pageResult.getList(), deptMap),\n                pageResult.getTotal()));\n    }\n\n    @GetMapping({\"/list-all-simple\", \"/simple-list\"})\n    @Operation(summary = \"获取用户精简信息列表\", description = \"只包含被开启的用户，主要用于前端的下拉选项\")\n    public CommonResult<List<UserSimpleRespVO>> getSimpleUserList() {\n        List<AdminUserDO> list = userService.getUserListByStatus(CommonStatusEnum.ENABLE.getStatus());\n        // 拼接数据\n        Map<Long, DeptDO> deptMap = deptService.getDeptMap(\n                convertList(list, AdminUserDO::getDeptId));\n        return success(UserConvert.INSTANCE.convertSimpleList(list, deptMap));\n    }\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得用户详情\")\n    @Parameter(name = \"id\", description = \"编号\", required = true, example = \"1024\")\n    @PreAuthorize(\"@ss.hasPermission('system:user:query')\")\n    public CommonResult<UserRespVO> getUser(@RequestParam(\"id\") Long id) {\n        AdminUserDO user = userService.getUser(id);\n        if (user == null) {\n            return success(null);\n        }\n        // 拼接数据\n        DeptDO dept = deptService.getDept(user.getDeptId());\n        return success(UserConvert.INSTANCE.convert(user, dept));\n    }\n\n    @GetMapping(\"/export\")\n    @Operation(summary = \"导出用户\")\n    @PreAuthorize(\"@ss.hasPermission('system:user:export')\")\n    @ApiAccessLog(operateType = EXPORT)\n    public void exportUserList(@Validated UserPageReqVO exportReqVO,\n                               HttpServletResponse response) throws IOException {\n        exportReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);\n        List<AdminUserDO> list = userService.getUserPage(exportReqVO).getList();\n        // 输出 Excel\n        Map<Long, DeptDO> deptMap = deptService.getDeptMap(\n                convertList(list, AdminUserDO::getDeptId));\n        ExcelUtils.write(response, \"用户数据.xls\", \"数据\", UserRespVO.class,\n                UserConvert.INSTANCE.convertList(list, deptMap));\n    }\n\n    @GetMapping(\"/get-import-template\")\n    @Operation(summary = \"获得导入用户模板\")\n    public void importTemplate(HttpServletResponse response) throws IOException {\n        // 手动创建导出 demo\n        List<UserImportExcelVO> list = Arrays.asList(\n                UserImportExcelVO.builder().username(\"yshop\").deptId(1L).email(\"yshop@yixiang.co\").mobile(\"15601691300\")\n                        .nickname(\"yshop\").status(CommonStatusEnum.ENABLE.getStatus()).sex(SexEnum.MALE.getSex()).build(),\n                UserImportExcelVO.builder().username(\"yuanma\").deptId(2L).email(\"yuanma@yixiang.co\").mobile(\"15601701300\")\n                        .nickname(\"源码\").status(CommonStatusEnum.DISABLE.getStatus()).sex(SexEnum.FEMALE.getSex()).build()\n        );\n        // 输出\n        ExcelUtils.write(response, \"用户导入模板.xls\", \"用户列表\", UserImportExcelVO.class, list);\n    }\n\n    @PostMapping(\"/import\")\n    @Operation(summary = \"导入用户\")\n    @Parameters({\n            @Parameter(name = \"file\", description = \"Excel 文件\", required = true),\n            @Parameter(name = \"updateSupport\", description = \"是否支持更新，默认为 false\", example = \"true\")\n    })\n    @PreAuthorize(\"@ss.hasPermission('system:user:import')\")\n    public CommonResult<UserImportRespVO> importExcel(@RequestParam(\"file\") MultipartFile file,\n                                                      @RequestParam(value = \"updateSupport\", required = false, defaultValue = \"false\") Boolean updateSupport) throws Exception {\n        List<UserImportExcelVO> list = ExcelUtils.read(file, UserImportExcelVO.class);\n        return success(userService.importUserList(list, updateSupport));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/user/UserProfileController.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.user;\n\nimport cn.hutool.core.collection.CollUtil;\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.datapermission.core.annotation.DataPermission;\nimport co.yixiang.yshop.module.system.controller.admin.user.vo.profile.UserProfileRespVO;\nimport co.yixiang.yshop.module.system.controller.admin.user.vo.profile.UserProfileUpdatePasswordReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.user.vo.profile.UserProfileUpdateReqVO;\nimport co.yixiang.yshop.module.system.convert.user.UserConvert;\nimport co.yixiang.yshop.module.system.dal.dataobject.dept.DeptDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.dept.PostDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.permission.RoleDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.social.SocialUserDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.user.AdminUserDO;\nimport co.yixiang.yshop.module.system.service.dept.DeptService;\nimport co.yixiang.yshop.module.system.service.dept.PostService;\nimport co.yixiang.yshop.module.system.service.permission.PermissionService;\nimport co.yixiang.yshop.module.system.service.permission.RoleService;\nimport co.yixiang.yshop.module.system.service.social.SocialUserService;\nimport co.yixiang.yshop.module.system.service.user.AdminUserService;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport io.swagger.v3.oas.annotations.Operation;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.*;\nimport org.springframework.web.multipart.MultipartFile;\n\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Valid;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\nimport static co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;\nimport static co.yixiang.yshop.module.infra.enums.ErrorCodeConstants.FILE_IS_EMPTY;\n\n@Tag(name = \"管理后台 - 用户个人中心\")\n@RestController\n@RequestMapping(\"/system/user/profile\")\n@Validated\n@Slf4j\npublic class UserProfileController {\n\n    @Resource\n    private AdminUserService userService;\n    @Resource\n    private DeptService deptService;\n    @Resource\n    private PostService postService;\n    @Resource\n    private PermissionService permissionService;\n    @Resource\n    private RoleService roleService;\n    @Resource\n    private SocialUserService socialService;\n\n    @GetMapping(\"/get\")\n    @Operation(summary = \"获得登录用户信息\")\n    @DataPermission(enable = false) // 关闭数据权限，避免只查看自己时，查询不到部门。\n    public CommonResult<UserProfileRespVO> getUserProfile() {\n        // 获得用户基本信息\n        AdminUserDO user = userService.getUser(getLoginUserId());\n        // 获得用户角色\n        List<RoleDO> userRoles = roleService.getRoleListFromCache(permissionService.getUserRoleIdListByUserId(user.getId()));\n        // 获得部门信息\n        DeptDO dept = user.getDeptId() != null ? deptService.getDept(user.getDeptId()) : null;\n        // 获得岗位信息\n        List<PostDO> posts = CollUtil.isNotEmpty(user.getPostIds()) ? postService.getPostList(user.getPostIds()) : null;\n        // 获得社交用户信息\n        List<SocialUserDO> socialUsers = socialService.getSocialUserList(user.getId(), UserTypeEnum.ADMIN.getValue());\n        return success(UserConvert.INSTANCE.convert(user, userRoles, dept, posts, socialUsers));\n    }\n\n    @PutMapping(\"/update\")\n    @Operation(summary = \"修改用户个人信息\")\n    public CommonResult<Boolean> updateUserProfile(@Valid @RequestBody UserProfileUpdateReqVO reqVO) {\n        userService.updateUserProfile(getLoginUserId(), reqVO);\n        return success(true);\n    }\n\n    @PutMapping(\"/update-password\")\n    @Operation(summary = \"修改用户个人密码\")\n    public CommonResult<Boolean> updateUserProfilePassword(@Valid @RequestBody UserProfileUpdatePasswordReqVO reqVO) {\n        userService.updateUserPassword(getLoginUserId(), reqVO);\n        return success(true);\n    }\n\n    @RequestMapping(value = \"/update-avatar\",\n            method = {RequestMethod.POST, RequestMethod.PUT}) // 解决 uni-app 不支持 Put 上传文件的问题\n    @Operation(summary = \"上传用户个人头像\")\n    public CommonResult<String> updateUserAvatar(@RequestParam(\"avatarFile\") MultipartFile file) throws Exception {\n        if (file.isEmpty()) {\n            throw exception(FILE_IS_EMPTY);\n        }\n        String avatar = userService.updateUserAvatar(getLoginUserId(), file.getInputStream());\n        return success(avatar);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/user/vo/profile/UserProfileRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.user.vo.profile;\n\nimport co.yixiang.yshop.module.system.controller.admin.dept.vo.dept.DeptSimpleRespVO;\nimport co.yixiang.yshop.module.system.controller.admin.dept.vo.post.PostSimpleRespVO;\nimport co.yixiang.yshop.module.system.controller.admin.permission.vo.role.RoleSimpleRespVO;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\nimport java.util.List;\n\n@Data\n@Schema(description = \"管理后台 - 用户个人中心信息 Response VO\")\npublic class UserProfileRespVO {\n\n    @Schema(description = \"用户编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    private Long id;\n\n    @Schema(description = \"用户账号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    private String username;\n\n    @Schema(description = \"用户昵称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    private String nickname;\n\n    @Schema(description = \"用户邮箱\", example = \"yshop@yixiang.co\")\n    private String email;\n\n    @Schema(description = \"手机号码\", example = \"15601691300\")\n    private String mobile;\n\n    @Schema(description = \"用户性别，参见 SexEnum 枚举类\", example = \"1\")\n    private Integer sex;\n\n    @Schema(description = \"用户头像\", example = \"https://www.yixiang.co/xxx.png\")\n    private String avatar;\n\n    @Schema(description = \"最后登录 IP\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"192.168.1.1\")\n    private String loginIp;\n\n    @Schema(description = \"最后登录时间\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"时间戳格式\")\n    private LocalDateTime loginDate;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"时间戳格式\")\n    private LocalDateTime createTime;\n\n    /**\n     * 所属角色\n     */\n    private List<RoleSimpleRespVO> roles;\n    /**\n     * 所在部门\n     */\n    private DeptSimpleRespVO dept;\n    /**\n     * 所属岗位数组\n     */\n    private List<PostSimpleRespVO> posts;\n    /**\n     * 社交用户数组\n     */\n    private List<SocialUser> socialUsers;\n\n    @Schema(description = \"社交用户\")\n    @Data\n    public static class SocialUser {\n\n        @Schema(description = \"社交平台的类型，参见 SocialTypeEnum 枚举类\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"10\")\n        private Integer type;\n\n        @Schema(description = \"社交用户的 openid\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"IPRmJ0wvBptiPIlGEZiPewGwiEiE\")\n        private String openid;\n\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/user/vo/profile/UserProfileUpdatePasswordReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.user.vo.profile;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport org.hibernate.validator.constraints.Length;\n\nimport jakarta.validation.constraints.NotEmpty;\n\n@Schema(description = \"管理后台 - 用户个人中心更新密码 Request VO\")\n@Data\npublic class UserProfileUpdatePasswordReqVO {\n\n    @Schema(description = \"旧密码\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"123456\")\n    @NotEmpty(message = \"旧密码不能为空\")\n    @Length(min = 4, max = 16, message = \"密码长度为 4-16 位\")\n    private String oldPassword;\n\n    @Schema(description = \"新密码\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"654321\")\n    @NotEmpty(message = \"新密码不能为空\")\n    @Length(min = 4, max = 16, message = \"密码长度为 4-16 位\")\n    private String newPassword;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/user/vo/profile/UserProfileUpdateReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.user.vo.profile;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport org.hibernate.validator.constraints.Length;\n\nimport jakarta.validation.constraints.Email;\nimport jakarta.validation.constraints.Size;\n\n\n@Schema(description = \"管理后台 - 用户个人信息更新 Request VO\")\n@Data\npublic class UserProfileUpdateReqVO {\n\n    @Schema(description = \"用户昵称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    @Size(max = 30, message = \"用户昵称长度不能超过 30 个字符\")\n    private String nickname;\n\n    @Schema(description = \"用户邮箱\", example = \"yshop@yixiang.co\")\n    @Email(message = \"邮箱格式不正确\")\n    @Size(max = 50, message = \"邮箱长度不能超过 50 个字符\")\n    private String email;\n\n    @Schema(description = \"手机号码\", example = \"15601691300\")\n    @Length(min = 11, max = 11, message = \"手机号长度必须 11 位\")\n    private String mobile;\n\n    @Schema(description = \"用户性别，参见 SexEnum 枚举类\", example = \"1\")\n    private Integer sex;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/user/vo/user/UserImportExcelVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.user.vo.user;\n\nimport co.yixiang.yshop.framework.excel.core.annotations.DictFormat;\nimport co.yixiang.yshop.framework.excel.core.convert.DictConvert;\nimport co.yixiang.yshop.module.system.enums.DictTypeConstants;\nimport com.alibaba.excel.annotation.ExcelProperty;\nimport lombok.AllArgsConstructor;\nimport lombok.Builder;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\nimport lombok.experimental.Accessors;\n\n/**\n * 用户 Excel 导入 VO\n */\n@Data\n@Builder\n@AllArgsConstructor\n@NoArgsConstructor\n@Accessors(chain = false) // 设置 chain = false，避免用户导入有问题\npublic class UserImportExcelVO {\n\n    @ExcelProperty(\"登录名称\")\n    private String username;\n\n    @ExcelProperty(\"用户名称\")\n    private String nickname;\n\n    @ExcelProperty(\"部门编号\")\n    private Long deptId;\n\n    @ExcelProperty(\"用户邮箱\")\n    private String email;\n\n    @ExcelProperty(\"手机号码\")\n    private String mobile;\n\n    @ExcelProperty(value = \"用户性别\", converter = DictConvert.class)\n    @DictFormat(DictTypeConstants.USER_SEX)\n    private Integer sex;\n\n    @ExcelProperty(value = \"账号状态\", converter = DictConvert.class)\n    @DictFormat(DictTypeConstants.COMMON_STATUS)\n    private Integer status;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/user/vo/user/UserImportRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.user.vo.user;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Builder;\nimport lombok.Data;\n\nimport java.util.List;\nimport java.util.Map;\n\n@Schema(description = \"管理后台 - 用户导入 Response VO\")\n@Data\n@Builder\npublic class UserImportRespVO {\n\n    @Schema(description = \"创建成功的用户名数组\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private List<String> createUsernames;\n\n    @Schema(description = \"更新成功的用户名数组\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private List<String> updateUsernames;\n\n    @Schema(description = \"导入失败的用户集合，key 为用户名，value 为失败原因\", requiredMode = Schema.RequiredMode.REQUIRED)\n    private Map<String, String> failureUsernames;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/user/vo/user/UserPageReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.user.vo.user;\n\nimport co.yixiang.yshop.framework.common.pojo.PageParam;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.NoArgsConstructor;\nimport org.springframework.format.annotation.DateTimeFormat;\n\nimport java.time.LocalDateTime;\n\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\n\n@Schema(description = \"管理后台 - 用户分页 Request VO\")\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\n@EqualsAndHashCode(callSuper = true)\npublic class UserPageReqVO extends PageParam {\n\n    @Schema(description = \"用户账号，模糊匹配\", example = \"yshop\")\n    private String username;\n\n    @Schema(description = \"手机号码，模糊匹配\", example = \"yshop\")\n    private String mobile;\n\n    @Schema(description = \"展示状态，参见 CommonStatusEnum 枚举类\", example = \"1\")\n    private Integer status;\n\n    @Schema(description = \"创建时间\", example = \"[2022-07-01 00:00:00, 2022-07-01 23:59:59]\")\n    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)\n    private LocalDateTime[] createTime;\n\n    @Schema(description = \"部门编号，同时筛选子部门\", example = \"1024\")\n    private Long deptId;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/user/vo/user/UserRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.user.vo.user;\n\nimport co.yixiang.yshop.framework.excel.core.annotations.DictFormat;\nimport co.yixiang.yshop.framework.excel.core.convert.DictConvert;\nimport co.yixiang.yshop.module.system.enums.DictTypeConstants;\nimport com.alibaba.excel.annotation.ExcelIgnoreUnannotated;\nimport com.alibaba.excel.annotation.ExcelProperty;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\nimport java.util.Set;\n\n@Schema(description = \"管理后台 - 用户信息 Response VO\")\n@Data\n@ExcelIgnoreUnannotated\npublic class UserRespVO{\n\n    @Schema(description = \"用户编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @ExcelProperty(\"用户编号\")\n    private Long id;\n\n    @Schema(description = \"用户账号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    @ExcelProperty(\"用户名称\")\n    private String username;\n\n    @Schema(description = \"用户昵称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    @ExcelProperty(\"用户昵称\")\n    private String nickname;\n\n    @Schema(description = \"备注\", example = \"我是一个用户\")\n    private String remark;\n\n    @Schema(description = \"部门ID\", example = \"我是一个用户\")\n    private Long deptId;\n    @Schema(description = \"部门名称\", example = \"IT 部\")\n    @ExcelProperty(\"部门名称\")\n    private String deptName;\n\n    @Schema(description = \"岗位编号数组\", example = \"1\")\n    private Set<Long> postIds;\n\n    @Schema(description = \"用户邮箱\", example = \"yshop@yixiang.co\")\n    @ExcelProperty(\"用户邮箱\")\n    private String email;\n\n    @Schema(description = \"手机号码\", example = \"15601691300\")\n    @ExcelProperty(\"手机号码\")\n    private String mobile;\n\n    @Schema(description = \"用户性别，参见 SexEnum 枚举类\", example = \"1\")\n    @ExcelProperty(value = \"用户性别\", converter = DictConvert.class)\n    @DictFormat(DictTypeConstants.USER_SEX)\n    private Integer sex;\n\n    @Schema(description = \"用户头像\", example = \"https://www.yixiang.co/xxx.png\")\n    private String avatar;\n\n    @Schema(description = \"状态，参见 CommonStatusEnum 枚举类\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @ExcelProperty(value = \"帐号状态\", converter = DictConvert.class)\n    @DictFormat(DictTypeConstants.COMMON_STATUS)\n    private Integer status;\n\n    @Schema(description = \"最后登录 IP\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"192.168.1.1\")\n    @ExcelProperty(\"最后登录IP\")\n    private String loginIp;\n\n    @Schema(description = \"最后登录时间\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"时间戳格式\")\n    @ExcelProperty(\"最后登录时间\")\n    private LocalDateTime loginDate;\n\n    @Schema(description = \"创建时间\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"时间戳格式\")\n    private LocalDateTime createTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/user/vo/user/UserSaveReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.user.vo.user;\n\nimport cn.hutool.core.util.ObjectUtil;\nimport co.yixiang.yshop.framework.common.validation.Mobile;\nimport co.yixiang.yshop.module.system.framework.operatelog.core.DeptParseFunction;\nimport co.yixiang.yshop.module.system.framework.operatelog.core.PostParseFunction;\nimport co.yixiang.yshop.module.system.framework.operatelog.core.SexParseFunction;\nimport com.fasterxml.jackson.annotation.JsonIgnore;\nimport com.mzt.logapi.starter.annotation.DiffLogField;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport jakarta.validation.constraints.*;\nimport lombok.Data;\nimport org.hibernate.validator.constraints.Length;\n\nimport java.util.Set;\n\n@Schema(description = \"管理后台 - 用户创建/修改 Request VO\")\n@Data\npublic class UserSaveReqVO {\n\n    @Schema(description = \"用户编号\", example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"用户账号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    @NotBlank(message = \"用户账号不能为空\")\n    @Pattern(regexp = \"^[a-zA-Z0-9]{4,30}$\", message = \"用户账号由 数字、字母 组成\")\n    @Size(min = 4, max = 30, message = \"用户账号长度为 4-30 个字符\")\n    @DiffLogField(name = \"用户账号\")\n    private String username;\n\n    @Schema(description = \"用户昵称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    @Size(max = 30, message = \"用户昵称长度不能超过30个字符\")\n    @DiffLogField(name = \"用户昵称\")\n    private String nickname;\n\n    @Schema(description = \"备注\", example = \"我是一个用户\")\n    @DiffLogField(name = \"备注\")\n    private String remark;\n\n    @Schema(description = \"部门编号\", example = \"我是一个用户\")\n    @DiffLogField(name = \"部门\", function = DeptParseFunction.NAME)\n    private Long deptId;\n\n    @Schema(description = \"岗位编号数组\", example = \"1\")\n    @DiffLogField(name = \"岗位\", function = PostParseFunction.NAME)\n    private Set<Long> postIds;\n\n    @Schema(description = \"用户邮箱\", example = \"yshop@yixiang.co\")\n    @Email(message = \"邮箱格式不正确\")\n    @Size(max = 50, message = \"邮箱长度不能超过 50 个字符\")\n    @DiffLogField(name = \"用户邮箱\")\n    private String email;\n\n    @Schema(description = \"手机号码\", example = \"15601691300\")\n    @Mobile\n    @DiffLogField(name = \"手机号码\")\n    private String mobile;\n\n    @Schema(description = \"用户性别，参见 SexEnum 枚举类\", example = \"1\")\n    @DiffLogField(name = \"用户性别\", function = SexParseFunction.NAME)\n    private Integer sex;\n\n    @Schema(description = \"用户头像\", example = \"https://www.yixiang.co/xxx.png\")\n    @DiffLogField(name = \"用户头像\")\n    private String avatar;\n\n    // ========== 仅【创建】时，需要传递的字段 ==========\n\n    @Schema(description = \"密码\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"123456\")\n    @Length(min = 4, max = 16, message = \"密码长度为 4-16 位\")\n    private String password;\n\n    @AssertTrue(message = \"密码不能为空\")\n    @JsonIgnore\n    public boolean isPasswordValid() {\n        return id != null // 修改时，不需要传递\n                || (ObjectUtil.isAllNotEmpty(password)); // 新增时，必须都传递 password\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/user/vo/user/UserSimpleRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.user.vo.user;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\n@Schema(description = \"管理后台 - 用户精简信息 Response VO\")\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\npublic class UserSimpleRespVO {\n\n    @Schema(description = \"用户编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"用户昵称\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    private String nickname;\n\n    @Schema(description = \"部门ID\", example = \"我是一个用户\")\n    private Long deptId;\n    @Schema(description = \"部门名称\", example = \"IT 部\")\n    private String deptName;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/user/vo/user/UserUpdatePasswordReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.user.vo.user;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport org.hibernate.validator.constraints.Length;\n\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\n\n@Schema(description = \"管理后台 - 用户更新密码 Request VO\")\n@Data\npublic class UserUpdatePasswordReqVO {\n\n    @Schema(description = \"用户编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @NotNull(message = \"用户编号不能为空\")\n    private Long id;\n\n    @Schema(description = \"密码\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"123456\")\n    @NotEmpty(message = \"密码不能为空\")\n    @Length(min = 4, max = 16, message = \"密码长度为 4-16 位\")\n    private String password;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/admin/user/vo/user/UserUpdateStatusReqVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.user.vo.user;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.validation.InEnum;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotNull;\n\n@Schema(description = \"管理后台 - 用户更新状态 Request VO\")\n@Data\npublic class UserUpdateStatusReqVO {\n\n    @Schema(description = \"用户编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    @NotNull(message = \"角色编号不能为空\")\n    private Long id;\n\n    @Schema(description = \"状态，见 CommonStatusEnum 枚举\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1\")\n    @NotNull(message = \"状态不能为空\")\n    @InEnum(value = CommonStatusEnum.class, message = \"修改状态必须是 {value}\")\n    private Integer status;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/app/dict/AppDictDataController.java",
    "content": "package co.yixiang.yshop.module.system.controller.app.dict;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.controller.app.dict.vo.AppDictDataRespVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.dict.DictDataDO;\nimport co.yixiang.yshop.module.system.service.dict.DictDataService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.Parameter;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport jakarta.annotation.Resource;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"用户 App - 字典数据\")\n@RestController\n@RequestMapping(\"/system/dict-data\")\n@Validated\npublic class AppDictDataController {\n\n    @Resource\n    private DictDataService dictDataService;\n\n    @GetMapping(\"/type\")\n    @Operation(summary = \"根据字典类型查询字典数据信息\")\n    @Parameter(name = \"type\", description = \"字典类型\", required = true, example = \"common_status\")\n    public CommonResult<List<AppDictDataRespVO>> getDictDataListByType(@RequestParam(\"type\") String type) {\n        List<DictDataDO> list = dictDataService.getDictDataList(\n                CommonStatusEnum.ENABLE.getStatus(), type);\n        return success(BeanUtils.toBean(list, AppDictDataRespVO.class));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/app/dict/vo/AppDictDataRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.app.dict.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.AllArgsConstructor;\nimport lombok.Data;\nimport lombok.NoArgsConstructor;\n\n@Schema(description = \"用户 App - 字典数据信息 Response VO\")\n@Data\n@NoArgsConstructor\n@AllArgsConstructor\npublic class AppDictDataRespVO {\n\n    @Schema(description = \"字典数据编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"1024\")\n    private Long id;\n\n    @Schema(description = \"字典标签\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yshop\")\n    private String label;\n\n    @Schema(description = \"字典值\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"yixiang.co\")\n    private String value;\n\n    @Schema(description = \"字典类型\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"sys_common_sex\")\n    private String dictType;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/app/ip/AppAreaController.java",
    "content": "package co.yixiang.yshop.module.system.controller.app.ip;\n\nimport cn.hutool.core.lang.Assert;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.framework.ip.core.Area;\nimport co.yixiang.yshop.framework.ip.core.utils.AreaUtils;\nimport co.yixiang.yshop.module.system.controller.app.ip.vo.AppAreaNodeRespVO;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport org.springframework.validation.annotation.Validated;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n@Tag(name = \"用户 App - 地区\")\n@RestController\n@RequestMapping(\"/system/area\")\n@Validated\npublic class AppAreaController {\n\n    @GetMapping(\"/tree\")\n    @Operation(summary = \"获得地区树\")\n    public CommonResult<List<AppAreaNodeRespVO>> getAreaTree() {\n        Area area = AreaUtils.getArea(Area.ID_CHINA);\n        Assert.notNull(area, \"获取不到中国\");\n        return success(BeanUtils.toBean(area.getChildren(), AppAreaNodeRespVO.class));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/app/ip/vo/AppAreaNodeRespVO.java",
    "content": "package co.yixiang.yshop.module.system.controller.app.ip.vo;\n\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\n\nimport java.util.List;\n\n@Schema(description = \"用户 App - 地区节点 Response VO\")\n@Data\npublic class AppAreaNodeRespVO {\n\n    @Schema(description = \"编号\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"110000\")\n    private Integer id;\n\n    @Schema(description = \"名字\", requiredMode = Schema.RequiredMode.REQUIRED, example = \"北京\")\n    private String name;\n\n    /**\n     * 子节点\n     */\n    private List<AppAreaNodeRespVO> children;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/controller/app/notice/AppNoticeController.java",
    "content": "/**\n * Copyright (C) 2018-2022\n * All rights reserved, Designed By www.yixiang.co\n * 注意：\n * 本软件为www.yixiang.co开发研制，未经购买不得使用\n * 购买后可获得全部源代码（禁止转卖、分享、上传到码云、github等开源平台）\n * 一经发现盗用、分享等行为，将追究法律责任，后果自负\n */\npackage co.yixiang.yshop.module.system.controller.app.notice;\n\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.controller.admin.notice.vo.NoticePageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.notice.vo.NoticeRespVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.notice.NoticeDO;\nimport co.yixiang.yshop.module.system.service.notice.NoticeService;\nimport io.swagger.v3.oas.annotations.Operation;\nimport io.swagger.v3.oas.annotations.tags.Tag;\nimport lombok.RequiredArgsConstructor;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.web.bind.annotation.GetMapping;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RestController;\n\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.pojo.CommonResult.success;\n\n/**\n * <p>\n * 新闻控制器\n * </p>\n *\n * @author hupeng\n * @since 2024-5-6\n */\n@Slf4j\n@RestController\n@Tag(name = \"用户 APP - 新闻\")\n@RequiredArgsConstructor(onConstructor = @__(@Autowired))\n@RequestMapping(\"/news\")\npublic class AppNoticeController {\n\n    private final NoticeService noticeService;\n\n\n    @GetMapping(\"/list\")\n    @Operation(summary = \"列表\")\n    public CommonResult<List<NoticeRespVO>> getList() {\n        NoticePageReqVO noticePageReqVO = new NoticePageReqVO();\n        noticePageReqVO.setPageSize(2);\n        List<NoticeDO> noticeDOS = noticeService.getNoticePage(noticePageReqVO).getList();\n        return success(BeanUtils.toBean(noticeDOS, NoticeRespVO.class));\n    }\n\n    @GetMapping(\"/detail/{id}\")\n    @Operation(summary = \"详情\")\n    public CommonResult<NoticeRespVO> getDetail(@PathVariable Long id) {\n        return success(BeanUtils.toBean(noticeService.getNotice(id), NoticeRespVO.class));\n    }\n\n\n\n\n\n}\n\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/convert/auth/AuthConvert.java",
    "content": "package co.yixiang.yshop.module.system.convert.auth;\n\nimport cn.hutool.core.collection.CollUtil;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils;\nimport co.yixiang.yshop.module.system.api.sms.dto.code.SmsCodeSendReqDTO;\nimport co.yixiang.yshop.module.system.api.sms.dto.code.SmsCodeUseReqDTO;\nimport co.yixiang.yshop.module.system.api.social.dto.SocialUserBindReqDTO;\nimport co.yixiang.yshop.module.system.controller.admin.auth.vo.*;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.permission.MenuDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.permission.RoleDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.user.AdminUserDO;\nimport co.yixiang.yshop.module.system.enums.permission.MenuTypeEnum;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.factory.Mappers;\nimport org.slf4j.LoggerFactory;\n\nimport java.util.*;\n\nimport static co.yixiang.yshop.framework.common.util.collection.CollectionUtils.convertSet;\nimport static co.yixiang.yshop.framework.common.util.collection.CollectionUtils.filterList;\nimport static co.yixiang.yshop.module.system.dal.dataobject.permission.MenuDO.ID_ROOT;\n\n@Mapper\npublic interface AuthConvert {\n\n    AuthConvert INSTANCE = Mappers.getMapper(AuthConvert.class);\n\n    AuthLoginRespVO convert(OAuth2AccessTokenDO bean);\n\n    default AuthPermissionInfoRespVO convert(AdminUserDO user, List<RoleDO> roleList, List<MenuDO> menuList) {\n        Long shopId = SecurityFrameworkUtils.getLoginUser().getShopId();\n        return AuthPermissionInfoRespVO.builder()\n                .user(BeanUtils.toBean(user, AuthPermissionInfoRespVO.UserVO.class).setShopId(shopId))\n                .roles(convertSet(roleList, RoleDO::getCode))\n                // 权限标识信息\n                .permissions(convertSet(menuList, MenuDO::getPermission))\n                // 菜单树\n                .menus(buildMenuTree(menuList))\n                .build();\n    }\n\n    AuthPermissionInfoRespVO.MenuVO convertTreeNode(MenuDO menu);\n\n    /**\n     * 将菜单列表，构建成菜单树\n     *\n     * @param menuList 菜单列表\n     * @return 菜单树\n     */\n    default List<AuthPermissionInfoRespVO.MenuVO> buildMenuTree(List<MenuDO> menuList) {\n        if (CollUtil.isEmpty(menuList)) {\n            return Collections.emptyList();\n        }\n        // 移除按钮\n        menuList.removeIf(menu -> menu.getType().equals(MenuTypeEnum.BUTTON.getType()));\n        // 排序，保证菜单的有序性\n        menuList.sort(Comparator.comparing(MenuDO::getSort));\n\n        // 构建菜单树\n        // 使用 LinkedHashMap 的原因，是为了排序 。实际也可以用 Stream API ，就是太丑了。\n        Map<Long, AuthPermissionInfoRespVO.MenuVO> treeNodeMap = new LinkedHashMap<>();\n        menuList.forEach(menu -> treeNodeMap.put(menu.getId(), AuthConvert.INSTANCE.convertTreeNode(menu)));\n        // 处理父子关系\n        treeNodeMap.values().stream().filter(node -> !node.getParentId().equals(ID_ROOT)).forEach(childNode -> {\n            // 获得父节点\n            AuthPermissionInfoRespVO.MenuVO parentNode = treeNodeMap.get(childNode.getParentId());\n            if (parentNode == null) {\n                LoggerFactory.getLogger(getClass()).error(\"[buildRouterTree][resource({}) 找不到父资源({})]\",\n                        childNode.getId(), childNode.getParentId());\n                return;\n            }\n            // 将自己添加到父节点中\n            if (parentNode.getChildren() == null) {\n                parentNode.setChildren(new ArrayList<>());\n            }\n            parentNode.getChildren().add(childNode);\n        });\n        // 获得到所有的根节点\n        return filterList(treeNodeMap.values(), node -> ID_ROOT.equals(node.getParentId()));\n    }\n\n    SocialUserBindReqDTO convert(Long userId, Integer userType, AuthSocialLoginReqVO reqVO);\n\n    SmsCodeSendReqDTO convert(AuthSmsSendReqVO reqVO);\n\n    SmsCodeUseReqDTO convert(AuthSmsLoginReqVO reqVO, Integer scene, String usedIp);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/convert/oauth2/OAuth2OpenConvert.java",
    "content": "package co.yixiang.yshop.module.system.convert.oauth2;\n\nimport cn.hutool.core.date.LocalDateTimeUtil;\nimport co.yixiang.yshop.framework.common.core.KeyValue;\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.common.util.collection.CollectionUtils;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils;\nimport co.yixiang.yshop.module.system.controller.admin.oauth2.vo.open.OAuth2OpenAccessTokenRespVO;\nimport co.yixiang.yshop.module.system.controller.admin.oauth2.vo.open.OAuth2OpenAuthorizeInfoRespVO;\nimport co.yixiang.yshop.module.system.controller.admin.oauth2.vo.open.OAuth2OpenCheckTokenRespVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2ApproveDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2ClientDO;\nimport co.yixiang.yshop.module.system.util.oauth2.OAuth2Utils;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.factory.Mappers;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.Map;\n\n@Mapper\npublic interface OAuth2OpenConvert {\n\n    OAuth2OpenConvert INSTANCE = Mappers.getMapper(OAuth2OpenConvert.class);\n\n    default OAuth2OpenAccessTokenRespVO convert(OAuth2AccessTokenDO bean) {\n        OAuth2OpenAccessTokenRespVO respVO = BeanUtils.toBean(bean, OAuth2OpenAccessTokenRespVO.class);\n        respVO.setTokenType(SecurityFrameworkUtils.AUTHORIZATION_BEARER.toLowerCase());\n        respVO.setExpiresIn(OAuth2Utils.getExpiresIn(bean.getExpiresTime()));\n        respVO.setScope(OAuth2Utils.buildScopeStr(bean.getScopes()));\n        return respVO;\n    }\n\n    default OAuth2OpenCheckTokenRespVO convert2(OAuth2AccessTokenDO bean) {\n        OAuth2OpenCheckTokenRespVO respVO = BeanUtils.toBean(bean, OAuth2OpenCheckTokenRespVO.class);\n        respVO.setExp(LocalDateTimeUtil.toEpochMilli(bean.getExpiresTime()) / 1000L);\n        respVO.setUserType(UserTypeEnum.ADMIN.getValue());\n        return respVO;\n    }\n\n    default OAuth2OpenAuthorizeInfoRespVO convert(OAuth2ClientDO client, List<OAuth2ApproveDO> approves) {\n        // 构建 scopes\n        List<KeyValue<String, Boolean>> scopes = new ArrayList<>(client.getScopes().size());\n        Map<String, OAuth2ApproveDO> approveMap = CollectionUtils.convertMap(approves, OAuth2ApproveDO::getScope);\n        client.getScopes().forEach(scope -> {\n            OAuth2ApproveDO approve = approveMap.get(scope);\n            scopes.add(new KeyValue<>(scope, approve != null ? approve.getApproved() : false));\n        });\n        // 拼接返回\n        return new OAuth2OpenAuthorizeInfoRespVO(\n                new OAuth2OpenAuthorizeInfoRespVO.Client(client.getName(), client.getLogo()), scopes);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/convert/social/SocialUserConvert.java",
    "content": "package co.yixiang.yshop.module.system.convert.social;\n\nimport co.yixiang.yshop.module.system.api.social.dto.SocialUserBindReqDTO;\nimport co.yixiang.yshop.module.system.controller.admin.socail.vo.user.SocialUserBindReqVO;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.Mapping;\nimport org.mapstruct.factory.Mappers;\n\n@Mapper\npublic interface SocialUserConvert {\n\n    SocialUserConvert INSTANCE = Mappers.getMapper(SocialUserConvert.class);\n\n    @Mapping(source = \"reqVO.type\", target = \"socialType\")\n    SocialUserBindReqDTO convert(Long userId, Integer userType, SocialUserBindReqVO reqVO);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/convert/tenant/TenantConvert.java",
    "content": "package co.yixiang.yshop.module.system.convert.tenant;\n\nimport co.yixiang.yshop.module.system.controller.admin.tenant.vo.tenant.TenantSaveReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.user.vo.user.UserSaveReqVO;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.factory.Mappers;\n\n/**\n * 租户 Convert\n *\n * @author yshop\n */\n@Mapper\npublic interface TenantConvert {\n\n    TenantConvert INSTANCE = Mappers.getMapper(TenantConvert.class);\n\n    default UserSaveReqVO convert02(TenantSaveReqVO bean) {\n        UserSaveReqVO reqVO = new UserSaveReqVO();\n        reqVO.setUsername(bean.getUsername());\n        reqVO.setPassword(bean.getPassword());\n        reqVO.setNickname(bean.getContactName()).setMobile(bean.getContactMobile());\n        return reqVO;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/convert/user/UserConvert.java",
    "content": "package co.yixiang.yshop.module.system.convert.user;\n\nimport co.yixiang.yshop.framework.common.util.collection.CollectionUtils;\nimport co.yixiang.yshop.framework.common.util.collection.MapUtils;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.controller.admin.dept.vo.dept.DeptSimpleRespVO;\nimport co.yixiang.yshop.module.system.controller.admin.dept.vo.post.PostSimpleRespVO;\nimport co.yixiang.yshop.module.system.controller.admin.permission.vo.role.RoleSimpleRespVO;\nimport co.yixiang.yshop.module.system.controller.admin.user.vo.profile.UserProfileRespVO;\nimport co.yixiang.yshop.module.system.controller.admin.user.vo.user.UserRespVO;\nimport co.yixiang.yshop.module.system.controller.admin.user.vo.user.UserSimpleRespVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.dept.DeptDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.dept.PostDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.permission.RoleDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.social.SocialUserDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.user.AdminUserDO;\nimport org.mapstruct.Mapper;\nimport org.mapstruct.factory.Mappers;\n\nimport java.util.List;\nimport java.util.Map;\n\n@Mapper\npublic interface UserConvert {\n\n    UserConvert INSTANCE = Mappers.getMapper(UserConvert.class);\n\n    default List<UserRespVO> convertList(List<AdminUserDO> list, Map<Long, DeptDO> deptMap) {\n        return CollectionUtils.convertList(list, user -> convert(user, deptMap.get(user.getDeptId())));\n    }\n\n    default UserRespVO convert(AdminUserDO user, DeptDO dept) {\n        UserRespVO userVO = BeanUtils.toBean(user, UserRespVO.class);\n        if (dept != null) {\n            userVO.setDeptName(dept.getName());\n        }\n        return userVO;\n    }\n\n    default List<UserSimpleRespVO> convertSimpleList(List<AdminUserDO> list, Map<Long, DeptDO> deptMap) {\n        return CollectionUtils.convertList(list, user -> {\n            UserSimpleRespVO userVO = BeanUtils.toBean(user, UserSimpleRespVO.class);\n            MapUtils.findAndThen(deptMap, user.getDeptId(), dept -> userVO.setDeptName(dept.getName()));\n            return userVO;\n        });\n    }\n\n    default UserProfileRespVO convert(AdminUserDO user, List<RoleDO> userRoles,\n                                      DeptDO dept, List<PostDO> posts, List<SocialUserDO> socialUsers) {\n        UserProfileRespVO userVO = BeanUtils.toBean(user, UserProfileRespVO.class);\n        userVO.setRoles(BeanUtils.toBean(userRoles, RoleSimpleRespVO.class));\n        userVO.setDept(BeanUtils.toBean(dept, DeptSimpleRespVO.class));\n        userVO.setPosts(BeanUtils.toBean(posts, PostSimpleRespVO.class));\n        userVO.setSocialUsers(BeanUtils.toBean(socialUsers, UserProfileRespVO.SocialUser.class));\n        return userVO;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/dataobject/dept/DeptDO.java",
    "content": "package co.yixiang.yshop.module.system.dal.dataobject.dept;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.tenant.core.db.TenantBaseDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.user.AdminUserDO;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\n/**\n * 部门表\n *\n * @author ruoyi\n * @author yshop\n */\n@TableName(\"system_dept\")\n@KeySequence(\"system_dept_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\npublic class DeptDO extends TenantBaseDO {\n\n    public static final Long PARENT_ID_ROOT = 0L;\n\n    /**\n     * 部门ID\n     */\n    @TableId\n    private Long id;\n    /**\n     * 部门名称\n     */\n    private String name;\n    /**\n     * 父部门ID\n     *\n     * 关联 {@link #id}\n     */\n    private Long parentId;\n    /**\n     * 显示顺序\n     */\n    private Integer sort;\n    /**\n     * 负责人\n     *\n     * 关联 {@link AdminUserDO#getId()}\n     */\n    private Long leaderUserId;\n    /**\n     * 联系电话\n     */\n    private String phone;\n    /**\n     * 邮箱\n     */\n    private String email;\n    /**\n     * 部门状态\n     *\n     * 枚举 {@link CommonStatusEnum}\n     */\n    private Integer status;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/dataobject/dept/PostDO.java",
    "content": "package co.yixiang.yshop.module.system.dal.dataobject.dept;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\n/**\n * 岗位表\n *\n * @author ruoyi\n */\n@TableName(\"system_post\")\n@KeySequence(\"system_post_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\npublic class PostDO extends BaseDO {\n\n    /**\n     * 岗位序号\n     */\n    @TableId\n    private Long id;\n    /**\n     * 岗位名称\n     */\n    private String name;\n    /**\n     * 岗位编码\n     */\n    private String code;\n    /**\n     * 岗位排序\n     */\n    private Integer sort;\n    /**\n     * 状态\n     *\n     * 枚举 {@link CommonStatusEnum}\n     */\n    private Integer status;\n    /**\n     * 备注\n     */\n    private String remark;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/dataobject/dept/UserPostDO.java",
    "content": "package co.yixiang.yshop.module.system.dal.dataobject.dept;\n\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.user.AdminUserDO;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\n/**\n * 用户和岗位关联\n *\n * @author ruoyi\n */\n@TableName(\"system_user_post\")\n@KeySequence(\"system_user_post_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\npublic class UserPostDO extends BaseDO {\n\n    /**\n     * 自增主键\n     */\n    @TableId\n    private Long id;\n    /**\n     * 用户 ID\n     *\n     * 关联 {@link AdminUserDO#getId()}\n     */\n    private Long userId;\n    /**\n     * 角色 ID\n     *\n     * 关联 {@link PostDO#getId()}\n     */\n    private Long postId;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/dataobject/dict/DictDataDO.java",
    "content": "package co.yixiang.yshop.module.system.dal.dataobject.dict;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport com.baomidou.mybatisplus.annotation.*;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\n/**\n * 字典数据表\n *\n * @author ruoyi\n */\n@TableName(\"system_dict_data\")\n@KeySequence(\"system_dict_data_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\npublic class DictDataDO extends BaseDO {\n\n    /**\n     * 字典数据编号\n     */\n    @TableId\n    private Long id;\n    /**\n     * 字典排序\n     */\n    private Integer sort;\n    /**\n     * 字典标签\n     */\n    private String label;\n    /**\n     * 字典值\n     */\n    private String value;\n    /**\n     * 字典类型\n     *\n     * 冗余 {@link DictDataDO#getDictType()}\n     */\n    private String dictType;\n    /**\n     * 状态\n     *\n     * 枚举 {@link CommonStatusEnum}\n     */\n    private Integer status;\n    /**\n     * 颜色类型\n     *\n     * 对应到 element-ui 为 default、primary、success、info、warning、danger\n     */\n    private String colorType;\n    /**\n     * css 样式\n     */\n    @TableField(updateStrategy = FieldStrategy.ALWAYS)\n    private String cssClass;\n    /**\n     * 备注\n     */\n    private String remark;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/dataobject/dict/DictTypeDO.java",
    "content": "package co.yixiang.yshop.module.system.dal.dataobject.dict;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.*;\n\nimport java.time.LocalDateTime;\n\n/**\n * 字典类型表\n *\n * @author ruoyi\n */\n@TableName(\"system_dict_type\")\n@KeySequence(\"system_dict_type_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class DictTypeDO extends BaseDO {\n\n    /**\n     * 字典主键\n     */\n    @TableId\n    private Long id;\n    /**\n     * 字典名称\n     */\n    private String name;\n    /**\n     * 字典类型\n     */\n    private String type;\n    /**\n     * 状态\n     *\n     * 枚举 {@link CommonStatusEnum}\n     */\n    private Integer status;\n    /**\n     * 备注\n     */\n    private String remark;\n\n    /**\n     * 删除时间\n     */\n    private LocalDateTime deletedTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/dataobject/logger/LoginLogDO.java",
    "content": "package co.yixiang.yshop.module.system.dal.dataobject.logger;\n\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport co.yixiang.yshop.module.system.enums.logger.LoginLogTypeEnum;\nimport co.yixiang.yshop.module.system.enums.logger.LoginResultEnum;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\n\n/**\n * 登录日志表\n *\n * 注意，包括登录和登出两种行为\n *\n * @author yshop\n */\n@TableName(\"system_login_log\")\n@KeySequence(\"system_login_log_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class LoginLogDO extends BaseDO {\n\n    /**\n     * 日志主键\n     */\n    private Long id;\n    /**\n     * 日志类型\n     *\n     * 枚举 {@link LoginLogTypeEnum}\n     */\n    private Integer logType;\n    /**\n     * 链路追踪编号\n     */\n    private String traceId;\n    /**\n     * 用户编号\n     */\n    private Long userId;\n    /**\n     * 用户类型\n     *\n     * 枚举 {@link UserTypeEnum}\n     */\n    private Integer userType;\n    /**\n     * 用户账号\n     *\n     * 冗余，因为账号可以变更\n     */\n    private String username;\n    /**\n     * 登录结果\n     *\n     * 枚举 {@link LoginResultEnum}\n     */\n    private Integer result;\n    /**\n     * 用户 IP\n     */\n    private String userIp;\n    /**\n     * 浏览器 UA\n     */\n    private String userAgent;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/dataobject/logger/OperateLogDO.java",
    "content": "package co.yixiang.yshop.module.system.dal.dataobject.logger;\n\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.Data;\n\n/**\n * 操作日志表\n *\n * @author yshop\n */\n@TableName(value = \"system_operate_log\", autoResultMap = true)\n@KeySequence(\"system_operate_log_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\npublic class OperateLogDO extends BaseDO {\n\n    /**\n     * 日志主键\n     */\n    @TableId\n    private Long id;\n    /**\n     * 链路追踪编号\n     *\n     * 一般来说，通过链路追踪编号，可以将访问日志，错误日志，链路追踪日志，logger 打印日志等，结合在一起，从而进行排错。\n     */\n    private String traceId;\n    /**\n     * 用户编号\n     *\n     * 关联 MemberUserDO 的 id 属性，或者 AdminUserDO 的 id 属性\n     */\n    private Long userId;\n    /**\n     * 用户类型\n     *\n     * 关联 {@link  UserTypeEnum}\n     */\n    private Integer userType;\n    /**\n     * 操作模块类型\n     */\n    private String type;\n    /**\n     * 操作名\n     */\n    private String subType;\n    /**\n     * 操作模块业务编号\n     */\n    private Long bizId;\n    /**\n     * 日志内容，记录整个操作的明细\n     *\n     * 例如说，修改编号为 1 的用户信息，将性别从男改成女，将姓名从yshop改成源码。\n     */\n    private String action;\n    /**\n     * 拓展字段，有些复杂的业务，需要记录一些字段 ( JSON 格式 )\n     *\n     * 例如说，记录订单编号，{ orderId: \"1\"}\n     */\n    private String extra;\n\n    /**\n     * 请求方法名\n     */\n    private String requestMethod;\n    /**\n     * 请求地址\n     */\n    private String requestUrl;\n    /**\n     * 用户 IP\n     */\n    private String userIp;\n    /**\n     * 浏览器 UA\n     */\n    private String userAgent;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/dataobject/mail/MailAccountDO.java",
    "content": "package co.yixiang.yshop.module.system.dal.dataobject.mail;\n\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\n/**\n * 邮箱账号 DO\n *\n * 用途：配置发送邮箱的账号\n *\n * @author wangjingyi\n * @since 2022-03-21\n */\n@TableName(value = \"system_mail_account\", autoResultMap = true)\n@Data\n@EqualsAndHashCode(callSuper = true)\npublic class MailAccountDO extends BaseDO {\n\n    /**\n     * 主键\n     */\n    @TableId\n    private Long id;\n    /**\n     * 邮箱\n     */\n    private String mail;\n\n    /**\n     * 用户名\n     */\n    private String username;\n    /**\n     * 密码\n     */\n    private String password;\n    /**\n     * SMTP 服务器域名\n     */\n    private String host;\n    /**\n     * SMTP 服务器端口\n     */\n    private Integer port;\n    /**\n     * 是否开启 SSL\n     */\n    private Boolean sslEnable;\n    /**\n     * 是否开启 STARTTLS\n     */\n    private Boolean starttlsEnable;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/dataobject/mail/MailLogDO.java",
    "content": "package co.yixiang.yshop.module.system.dal.dataobject.mail;\n\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport co.yixiang.yshop.module.system.enums.mail.MailSendStatusEnum;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;\nimport lombok.*;\n\nimport java.io.Serializable;\nimport java.time.LocalDateTime;\nimport java.util.Map;\n\n/**\n * 邮箱日志 DO\n * 记录每一次邮件的发送\n *\n * @author wangjingyi\n * @since 2022-03-21\n */\n@TableName(value = \"system_mail_log\", autoResultMap = true)\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@AllArgsConstructor\n@NoArgsConstructor\npublic class MailLogDO extends BaseDO implements Serializable {\n\n    /**\n     * 日志编号，自增\n     */\n    private Long id;\n\n    /**\n     * 用户编码\n     */\n    private Long userId;\n    /**\n     * 用户类型\n     *\n     * 枚举 {@link UserTypeEnum}\n     */\n    private Integer userType;\n    /**\n     * 接收邮箱地址\n     */\n    private String toMail;\n\n    /**\n     * 邮箱账号编号\n     *\n     * 关联 {@link MailAccountDO#getId()}\n     */\n    private Long accountId;\n    /**\n     * 发送邮箱地址\n     *\n     * 冗余 {@link MailAccountDO#getMail()}\n     */\n    private String fromMail;\n\n    // ========= 模板相关字段 =========\n    /**\n     * 模版编号\n     *\n     * 关联 {@link MailTemplateDO#getId()}\n     */\n    private Long templateId;\n    /**\n     * 模版编码\n     *\n     * 冗余 {@link MailTemplateDO#getCode()}\n     */\n    private String templateCode;\n    /**\n     * 模版发送人名称\n     *\n     * 冗余 {@link MailTemplateDO#getNickname()}\n     */\n    private String templateNickname;\n    /**\n     * 模版标题\n     */\n    private String templateTitle;\n    /**\n     * 模版内容\n     *\n     * 基于 {@link MailTemplateDO#getContent()} 格式化后的内容\n     */\n    private String templateContent;\n    /**\n     * 模版参数\n     *\n     * 基于 {@link MailTemplateDO#getParams()} 输入后的参数\n     */\n    @TableField(typeHandler = JacksonTypeHandler.class)\n    private Map<String, Object> templateParams;\n\n    // ========= 发送相关字段 =========\n    /**\n     * 发送状态\n     *\n     * 枚举 {@link MailSendStatusEnum}\n     */\n    private Integer sendStatus;\n    /**\n     * 发送时间\n     */\n    private LocalDateTime sendTime;\n    /**\n     * 发送返回的消息 ID\n     */\n    private String sendMessageId;\n    /**\n     * 发送异常\n     */\n    private String sendException;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/dataobject/mail/MailTemplateDO.java",
    "content": "package co.yixiang.yshop.module.system.dal.dataobject.mail;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\nimport java.util.List;\n\n/**\n * 邮件模版 DO\n *\n * @author wangjingyi\n * @since 2022-03-21\n */\n@TableName(value = \"system_mail_template\", autoResultMap = true)\n@Data\n@EqualsAndHashCode(callSuper = true)\npublic class MailTemplateDO extends BaseDO {\n\n    /**\n     * 主键\n     */\n    private Long id;\n    /**\n     * 模版名称\n     */\n    private String name;\n    /**\n     * 模版编号\n     */\n    private String code;\n    /**\n     * 发送的邮箱账号编号\n     *\n     * 关联 {@link MailAccountDO#getId()}\n     */\n    private Long accountId;\n\n    /**\n     * 发送人名称\n     */\n    private String nickname;\n    /**\n     * 标题\n     */\n    private String title;\n    /**\n     * 内容\n     */\n    private String content;\n    /**\n     * 参数数组(自动根据内容生成)\n     */\n    @TableField(typeHandler = JacksonTypeHandler.class)\n    private List<String> params;\n    /**\n     * 状态\n     *\n     * 枚举 {@link CommonStatusEnum}\n     */\n    private Integer status;\n    /**\n     * 备注\n     */\n    private String remark;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/dataobject/notice/NoticeDO.java",
    "content": "package co.yixiang.yshop.module.system.dal.dataobject.notice;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport co.yixiang.yshop.module.system.enums.notice.NoticeTypeEnum;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport io.swagger.v3.oas.annotations.media.Schema;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\n/**\n * 通知公告表\n *\n * @author ruoyi\n */\n@TableName(\"system_notice\")\n@KeySequence(\"system_notice_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\npublic class NoticeDO extends BaseDO {\n\n    /**\n     * 公告ID\n     */\n    private Long id;\n    /**\n     * 公告标题\n     */\n    private String title;\n\n    /**\n     * 公告类型\n     *\n     * 枚举 {@link NoticeTypeEnum}\n     */\n    private Integer type;\n    /**\n     * 公告内容\n     */\n    private String content;\n    /**\n     * 公告状态\n     *\n     * 枚举 {@link CommonStatusEnum}\n     */\n    private Integer status;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/dataobject/notify/NotifyMessageDO.java",
    "content": "package co.yixiang.yshop.module.system.dal.dataobject.notify;\n\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.mail.MailTemplateDO;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;\nimport lombok.*;\n\nimport java.time.LocalDateTime;\nimport java.util.Date;\nimport java.util.Map;\n\n/**\n * 站内信 DO\n *\n * @author xrcoder\n */\n@TableName(value = \"system_notify_message\", autoResultMap = true)\n@KeySequence(\"system_notify_message_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class NotifyMessageDO extends BaseDO {\n\n    /**\n     * 站内信编号，自增\n     */\n    @TableId\n    private Long id;\n    /**\n     * 用户编号\n     *\n     * 关联 MemberUserDO 的 id 字段、或者 AdminUserDO 的 id 字段\n     */\n    private Long userId;\n    /**\n     * 用户类型\n     *\n     * 枚举 {@link UserTypeEnum}\n     */\n    private Integer userType;\n\n    // ========= 模板相关字段 =========\n\n    /**\n     * 模版编号\n     *\n     * 关联 {@link NotifyTemplateDO#getId()}\n     */\n    private Long templateId;\n    /**\n     * 模版编码\n     *\n     * 关联 {@link NotifyTemplateDO#getCode()}\n     */\n    private String templateCode;\n    /**\n     * 模版类型\n     *\n     * 冗余 {@link NotifyTemplateDO#getType()}\n     */\n    private Integer templateType;\n    /**\n     * 模版发送人名称\n     *\n     * 冗余 {@link NotifyTemplateDO#getNickname()}\n     */\n    private String templateNickname;\n    /**\n     * 模版内容\n     *\n     * 基于 {@link NotifyTemplateDO#getContent()} 格式化后的内容\n     */\n    private String templateContent;\n    /**\n     * 模版参数\n     *\n     * 基于 {@link NotifyTemplateDO#getParams()} 输入后的参数\n     */\n    @TableField(typeHandler = JacksonTypeHandler.class)\n    private Map<String, Object> templateParams;\n\n    // ========= 读取相关字段 =========\n\n    /**\n     * 是否已读\n     */\n    private Boolean readStatus;\n    /**\n     * 阅读时间\n     */\n    private LocalDateTime readTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/dataobject/notify/NotifyTemplateDO.java",
    "content": "package co.yixiang.yshop.module.system.dal.dataobject.notify;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;\nimport lombok.*;\n\nimport java.util.List;\n\n/**\n * 站内信模版 DO\n *\n * @author xrcoder\n */\n@TableName(value = \"system_notify_template\", autoResultMap = true)\n@KeySequence(\"system_notify_template_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class NotifyTemplateDO extends BaseDO {\n\n    /**\n     * ID\n     */\n    @TableId\n    private Long id;\n    /**\n     * 模版名称\n     */\n    private String name;\n    /**\n     * 模版编码\n     */\n    private String code;\n    /**\n     * 模版类型\n     *\n     * 对应 system_notify_template_type 字典\n     */\n    private Integer type;\n    /**\n     * 发送人名称\n     */\n    private String nickname;\n    /**\n     * 模版内容\n     */\n    private String content;\n    /**\n     * 参数数组\n     */\n    @TableField(typeHandler = JacksonTypeHandler.class)\n    private List<String> params;\n    /**\n     * 状态\n     *\n     * 枚举 {@link CommonStatusEnum}\n     */\n    private Integer status;\n    /**\n     * 备注\n     */\n    private String remark;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/dataobject/oauth2/OAuth2AccessTokenDO.java",
    "content": "package co.yixiang.yshop.module.system.dal.dataobject.oauth2;\n\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.tenant.core.db.TenantBaseDO;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\nimport java.time.LocalDateTime;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * OAuth2 访问令牌 DO\n *\n * 如下字段，暂时未使用，暂时不支持：\n * user_name、authentication（用户信息）\n *\n * @author yshop\n */\n@TableName(value = \"system_oauth2_access_token\", autoResultMap = true)\n@KeySequence(\"system_oauth2_access_token_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\npublic class OAuth2AccessTokenDO extends TenantBaseDO {\n\n    /**\n     * 编号，数据库递增\n     */\n    @TableId\n    private Long id;\n    /**\n     * 访问令牌\n     */\n    private String accessToken;\n    /**\n     * 刷新令牌\n     */\n    private String refreshToken;\n    /**\n     * 用户编号\n     */\n    private Long userId;\n    /**\n     * 用户类型\n     *\n     * 枚举 {@link UserTypeEnum}\n     */\n    private Integer userType;\n    /**\n     * 用户信息\n     */\n    @TableField(typeHandler = JacksonTypeHandler.class)\n    private Map<String, String> userInfo;\n    /**\n     * 客户端编号\n     *\n     * 关联 {@link OAuth2ClientDO#getId()}\n     */\n    private String clientId;\n    /**\n     * 授权范围\n     */\n    @TableField(typeHandler = JacksonTypeHandler.class)\n    private List<String> scopes;\n    /**\n     * 过期时间\n     */\n    private LocalDateTime expiresTime;\n\n    /**\n     * 门店id\n     */\n    private Long shopId;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/dataobject/oauth2/OAuth2ApproveDO.java",
    "content": "package co.yixiang.yshop.module.system.dal.dataobject.oauth2;\n\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\nimport java.time.LocalDateTime;\n\n/**\n * OAuth2 批准 DO\n *\n * 用户在 sso.vue 界面时，记录接受的 scope 列表\n *\n * @author yshop\n */\n@TableName(value = \"system_oauth2_approve\", autoResultMap = true)\n@KeySequence(\"system_oauth2_approve_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\npublic class OAuth2ApproveDO extends BaseDO {\n\n    /**\n     * 编号，数据库自增\n     */\n    @TableId\n    private Long id;\n    /**\n     * 用户编号\n     */\n    private Long userId;\n    /**\n     * 用户类型\n     *\n     * 枚举 {@link UserTypeEnum}\n     */\n    private Integer userType;\n    /**\n     * 客户端编号\n     *\n     * 关联 {@link OAuth2ClientDO#getId()}\n     */\n    private String clientId;\n    /**\n     * 授权范围\n     */\n    private String scope;\n    /**\n     * 是否接受\n     *\n     * true - 接受\n     * false - 拒绝\n     */\n    private Boolean approved;\n    /**\n     * 过期时间\n     */\n    private LocalDateTime expiresTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/dataobject/oauth2/OAuth2ClientDO.java",
    "content": "package co.yixiang.yshop.module.system.dal.dataobject.oauth2;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport co.yixiang.yshop.module.system.enums.oauth2.OAuth2GrantTypeEnum;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\nimport java.util.List;\n\n/**\n * OAuth2 客户端 DO\n *\n * @author yshop\n */\n@TableName(value = \"system_oauth2_client\", autoResultMap = true)\n@KeySequence(\"system_oauth2_client_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\npublic class OAuth2ClientDO extends BaseDO {\n\n    /**\n     * 编号，数据库自增\n     *\n     * 由于 SQL Server 在存储 String 主键有点问题，所以暂时使用 Long 类型\n     */\n    @TableId\n    private Long id;\n    /**\n     * 客户端编号\n     */\n    private String clientId;\n    /**\n     * 客户端密钥\n     */\n    private String secret;\n    /**\n     * 应用名\n     */\n    private String name;\n    /**\n     * 应用图标\n     */\n    private String logo;\n    /**\n     * 应用描述\n     */\n    private String description;\n    /**\n     * 状态\n     *\n     * 枚举 {@link CommonStatusEnum}\n     */\n    private Integer status;\n    /**\n     * 访问令牌的有效期\n     */\n    private Integer accessTokenValiditySeconds;\n    /**\n     * 刷新令牌的有效期\n     */\n    private Integer refreshTokenValiditySeconds;\n    /**\n     * 可重定向的 URI 地址\n     */\n    @TableField(typeHandler = JacksonTypeHandler.class)\n    private List<String> redirectUris;\n    /**\n     * 授权类型（模式）\n     *\n     * 枚举 {@link OAuth2GrantTypeEnum}\n     */\n    @TableField(typeHandler = JacksonTypeHandler.class)\n    private List<String> authorizedGrantTypes;\n    /**\n     * 授权范围\n     */\n    @TableField(typeHandler = JacksonTypeHandler.class)\n    private List<String> scopes;\n    /**\n     * 自动授权的 Scope\n     *\n     * code 授权时，如果 scope 在这个范围内，则自动通过\n     */\n    @TableField(typeHandler = JacksonTypeHandler.class)\n    private List<String> autoApproveScopes;\n    /**\n     * 权限\n     */\n    @TableField(typeHandler = JacksonTypeHandler.class)\n    private List<String> authorities;\n    /**\n     * 资源\n     */\n    @TableField(typeHandler = JacksonTypeHandler.class)\n    private List<String> resourceIds;\n    /**\n     * 附加信息，JSON 格式\n     */\n    private String additionalInformation;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/dataobject/oauth2/OAuth2CodeDO.java",
    "content": "package co.yixiang.yshop.module.system.dal.dataobject.oauth2;\n\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\nimport java.time.LocalDateTime;\nimport java.util.List;\n\n/**\n * OAuth2 授权码 DO\n *\n * @author yshop\n */\n@TableName(value = \"system_oauth2_code\", autoResultMap = true)\n@KeySequence(\"system_oauth2_code_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\npublic class OAuth2CodeDO extends BaseDO {\n\n    /**\n     * 编号，数据库递增\n     */\n    private Long id;\n    /**\n     * 授权码\n     */\n    private String code;\n    /**\n     * 用户编号\n     */\n    private Long userId;\n    /**\n     * 用户类型\n     *\n     * 枚举 {@link UserTypeEnum}\n     */\n    private Integer userType;\n    /**\n     * 客户端编号\n     *\n     * 关联 {@link OAuth2ClientDO#getClientId()}\n     */\n    private String clientId;\n    /**\n     * 授权范围\n     */\n    @TableField(typeHandler = JacksonTypeHandler.class)\n    private List<String> scopes;\n    /**\n     * 重定向地址\n     */\n    private String redirectUri;\n    /**\n     * 状态\n     */\n    private String state;\n    /**\n     * 过期时间\n     */\n    private LocalDateTime expiresTime;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/dataobject/oauth2/OAuth2RefreshTokenDO.java",
    "content": "package co.yixiang.yshop.module.system.dal.dataobject.oauth2;\n\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.experimental.Accessors;\n\nimport java.time.LocalDateTime;\nimport java.util.List;\n\n/**\n * OAuth2 刷新令牌\n *\n * @author yshop\n */\n@TableName(value = \"system_oauth2_refresh_token\", autoResultMap = true)\n// 由于 Oracle 的 SEQ 的名字长度有限制，所以就先用 system_oauth2_access_token_seq 吧，反正也没啥问题\n@KeySequence(\"system_oauth2_access_token_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@Accessors(chain = true)\npublic class OAuth2RefreshTokenDO extends BaseDO {\n\n    /**\n     * 编号，数据库字典\n     */\n    private Long id;\n    /**\n     * 刷新令牌\n     */\n    private String refreshToken;\n    /**\n     * 用户编号\n     */\n    private Long userId;\n    /**\n     * 用户类型\n     *\n     * 枚举 {@link UserTypeEnum}\n     */\n    private Integer userType;\n    /**\n     * 客户端编号\n     *\n     * 关联 {@link OAuth2ClientDO#getId()}\n     */\n    private String clientId;\n    /**\n     * 授权范围\n     */\n    @TableField(typeHandler = JacksonTypeHandler.class)\n    private List<String> scopes;\n    /**\n     * 过期时间\n     */\n    private LocalDateTime expiresTime;\n\n    private Long shopId;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/dataobject/permission/MenuDO.java",
    "content": "package co.yixiang.yshop.module.system.dal.dataobject.permission;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport co.yixiang.yshop.module.system.enums.permission.MenuTypeEnum;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\n/**\n * 菜单 DO\n *\n * @author ruoyi\n */\n@TableName(\"system_menu\")\n@KeySequence(\"system_menu_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\npublic class MenuDO extends BaseDO {\n\n    /**\n     * 菜单编号 - 根节点\n     */\n    public static final Long ID_ROOT = 0L;\n\n    /**\n     * 菜单编号\n     */\n    @TableId\n    private Long id;\n    /**\n     * 菜单名称\n     */\n    private String name;\n    /**\n     * 权限标识\n     *\n     * 一般格式为：${系统}:${模块}:${操作}\n     * 例如说：system:admin:add，即 system 服务的添加管理员。\n     *\n     * 当我们把该 MenuDO 赋予给角色后，意味着该角色有该资源：\n     * - 对于后端，配合 @PreAuthorize 注解，配置 API 接口需要该权限，从而对 API 接口进行权限控制。\n     * - 对于前端，配合前端标签，配置按钮是否展示，避免用户没有该权限时，结果可以看到该操作。\n     */\n    private String permission;\n    /**\n     * 菜单类型\n     *\n     * 枚举 {@link MenuTypeEnum}\n     */\n    private Integer type;\n    /**\n     * 显示顺序\n     */\n    private Integer sort;\n    /**\n     * 父菜单ID\n     */\n    private Long parentId;\n    /**\n     * 路由地址\n     *\n     * 如果 path 为 http(s) 时，则它是外链\n     */\n    private String path;\n    /**\n     * 菜单图标\n     */\n    private String icon;\n    /**\n     * 组件路径\n     */\n    private String component;\n    /**\n     * 组件名\n     */\n    private String componentName;\n    /**\n     * 状态\n     *\n     * 枚举 {@link CommonStatusEnum}\n     */\n    private Integer status;\n    /**\n     * 是否可见\n     *\n     * 只有菜单、目录使用\n     * 当设置为 true 时，该菜单不会展示在侧边栏，但是路由还是存在。例如说，一些独立的编辑页面 /edit/1024 等等\n     */\n    private Boolean visible;\n    /**\n     * 是否缓存\n     *\n     * 只有菜单、目录使用，否使用 Vue 路由的 keep-alive 特性\n     * 注意：如果开启缓存，则必须填写 {@link #componentName} 属性，否则无法缓存\n     */\n    private Boolean keepAlive;\n    /**\n     * 是否总是显示\n     *\n     * 如果为 false 时，当该菜单只有一个子菜单时，不展示自己，直接展示子菜单\n     */\n    private Boolean alwaysShow;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/dataobject/permission/RoleDO.java",
    "content": "package co.yixiang.yshop.module.system.dal.dataobject.permission;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.mybatis.core.type.JsonLongSetTypeHandler;\nimport co.yixiang.yshop.module.system.enums.permission.DataScopeEnum;\nimport co.yixiang.yshop.framework.tenant.core.db.TenantBaseDO;\nimport co.yixiang.yshop.module.system.enums.permission.RoleTypeEnum;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\nimport java.util.Set;\n\n/**\n * 角色 DO\n *\n * @author ruoyi\n */\n@TableName(value = \"system_role\", autoResultMap = true)\n@KeySequence(\"system_role_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\npublic class RoleDO extends TenantBaseDO {\n\n    /**\n     * 角色ID\n     */\n    @TableId\n    private Long id;\n    /**\n     * 角色名称\n     */\n    private String name;\n    /**\n     * 角色标识\n     *\n     * 枚举\n     */\n    private String code;\n    /**\n     * 角色排序\n     */\n    private Integer sort;\n    /**\n     * 角色状态\n     *\n     * 枚举 {@link CommonStatusEnum}\n     */\n    private Integer status;\n    /**\n     * 角色类型\n     *\n     * 枚举 {@link RoleTypeEnum}\n     */\n    private Integer type;\n    /**\n     * 备注\n     */\n    private String remark;\n\n    /**\n     * 数据范围\n     *\n     * 枚举 {@link DataScopeEnum}\n     */\n    private Integer dataScope;\n    /**\n     * 数据范围(指定部门数组)\n     *\n     * 适用于 {@link #dataScope} 的值为 {@link DataScopeEnum#DEPT_CUSTOM} 时\n     */\n    @TableField(typeHandler = JsonLongSetTypeHandler.class)\n    private Set<Long> dataScopeDeptIds;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/dataobject/permission/RoleMenuDO.java",
    "content": "package co.yixiang.yshop.module.system.dal.dataobject.permission;\n\nimport co.yixiang.yshop.framework.tenant.core.db.TenantBaseDO;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\n/**\n * 角色和菜单关联\n *\n * @author ruoyi\n */\n@TableName(\"system_role_menu\")\n@KeySequence(\"system_role_menu_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\npublic class RoleMenuDO extends TenantBaseDO {\n\n    /**\n     * 自增主键\n     */\n    @TableId\n    private Long id;\n    /**\n     * 角色ID\n     */\n    private Long roleId;\n    /**\n     * 菜单ID\n     */\n    private Long menuId;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/dataobject/permission/UserRoleDO.java",
    "content": "package co.yixiang.yshop.module.system.dal.dataobject.permission;\n\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\n\n/**\n * 用户和角色关联\n *\n * @author ruoyi\n */\n@TableName(\"system_user_role\")\n@KeySequence(\"system_user_role_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\npublic class UserRoleDO extends BaseDO {\n\n    /**\n     * 自增主键\n     */\n    @TableId\n    private Long id;\n    /**\n     * 用户 ID\n     */\n    private Long userId;\n    /**\n     * 角色 ID\n     */\n    private Long roleId;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/dataobject/sms/SmsChannelDO.java",
    "content": "package co.yixiang.yshop.module.system.dal.dataobject.sms;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport co.yixiang.yshop.module.system.framework.sms.core.enums.SmsChannelEnum;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\n\n/**\n * 短信渠道 DO\n *\n * @author zzf\n * @since 2021-01-25\n */\n@TableName(value = \"system_sms_channel\", autoResultMap = true)\n@KeySequence(\"system_sms_channel_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class SmsChannelDO extends BaseDO {\n\n    /**\n     * 渠道编号\n     */\n    private Long id;\n    /**\n     * 短信签名\n     */\n    private String signature;\n    /**\n     * 渠道编码\n     *\n     * 枚举 {@link SmsChannelEnum}\n     */\n    private String code;\n    /**\n     * 启用状态\n     *\n     * 枚举 {@link CommonStatusEnum}\n     */\n    private Integer status;\n    /**\n     * 备注\n     */\n    private String remark;\n    /**\n     * 短信 API 的账号\n     */\n    private String apiKey;\n    /**\n     * 短信 API 的密钥\n     */\n    private String apiSecret;\n    /**\n     * 短信发送回调 URL\n     */\n    private String callbackUrl;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/dataobject/sms/SmsCodeDO.java",
    "content": "package co.yixiang.yshop.module.system.dal.dataobject.sms;\n\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.*;\n\nimport java.time.LocalDateTime;\n\n/**\n * 手机验证码 DO\n *\n * idx_mobile 索引：基于 {@link #mobile} 字段\n *\n * @author yshop\n */\n@TableName(\"system_sms_code\")\n@KeySequence(\"system_sms_code_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class SmsCodeDO extends BaseDO {\n\n    /**\n     * 编号\n     */\n    private Long id;\n    /**\n     * 手机号\n     */\n    private String mobile;\n    /**\n     * 验证码\n     */\n    private String code;\n    /**\n     * 发送场景\n     *\n     * 枚举 {@link SmsCodeDO}\n     */\n    private Integer scene;\n    /**\n     * 创建 IP\n     */\n    private String createIp;\n    /**\n     * 今日发送的第几条\n     */\n    private Integer todayIndex;\n    /**\n     * 是否使用\n     */\n    private Boolean used;\n    /**\n     * 使用时间\n     */\n    private LocalDateTime usedTime;\n    /**\n     * 使用 IP\n     */\n    private String usedIp;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/dataobject/sms/SmsLogDO.java",
    "content": "package co.yixiang.yshop.module.system.dal.dataobject.sms;\n\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport co.yixiang.yshop.module.system.enums.sms.SmsReceiveStatusEnum;\nimport co.yixiang.yshop.module.system.enums.sms.SmsSendStatusEnum;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;\nimport lombok.*;\n\nimport java.time.LocalDateTime;\nimport java.util.Map;\n\n/**\n * 短信日志 DO\n *\n * @author zzf\n * @since 2021-01-25\n */\n@TableName(value = \"system_sms_log\", autoResultMap = true)\n@KeySequence(\"system_sms_log_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@AllArgsConstructor\n@NoArgsConstructor\npublic class SmsLogDO extends BaseDO {\n\n    /**\n     * 自增编号\n     */\n    private Long id;\n\n    // ========= 渠道相关字段 =========\n\n    /**\n     * 短信渠道编号\n     *\n     * 关联 {@link SmsChannelDO#getId()}\n     */\n    private Long channelId;\n    /**\n     * 短信渠道编码\n     *\n     * 冗余 {@link SmsChannelDO#getCode()}\n     */\n    private String channelCode;\n\n    // ========= 模板相关字段 =========\n\n    /**\n     * 模板编号\n     *\n     * 关联 {@link SmsTemplateDO#getId()}\n     */\n    private Long templateId;\n    /**\n     * 模板编码\n     *\n     * 冗余 {@link SmsTemplateDO#getCode()}\n     */\n    private String templateCode;\n    /**\n     * 短信类型\n     *\n     * 冗余 {@link SmsTemplateDO#getType()}\n     */\n    private Integer templateType;\n    /**\n     * 基于 {@link SmsTemplateDO#getContent()} 格式化后的内容\n     */\n    private String templateContent;\n    /**\n     * 基于 {@link SmsTemplateDO#getParams()} 输入后的参数\n     */\n    @TableField(typeHandler = JacksonTypeHandler.class)\n    private Map<String, Object> templateParams;\n    /**\n     * 短信 API 的模板编号\n     *\n     * 冗余 {@link SmsTemplateDO#getApiTemplateId()}\n     */\n    private String apiTemplateId;\n\n    // ========= 手机相关字段 =========\n\n    /**\n     * 手机号\n     */\n    private String mobile;\n    /**\n     * 用户编号\n     */\n    private Long userId;\n    /**\n     * 用户类型\n     *\n     * 枚举 {@link UserTypeEnum}\n     */\n    private Integer userType;\n\n    // ========= 发送相关字段 =========\n\n    /**\n     * 发送状态\n     *\n     * 枚举 {@link SmsSendStatusEnum}\n     */\n    private Integer sendStatus;\n    /**\n     * 发送时间\n     */\n    private LocalDateTime sendTime;\n    /**\n     * 短信 API 发送结果的编码\n     *\n     * 由于第三方的错误码可能是字符串，所以使用 String 类型\n     */\n    private String apiSendCode;\n    /**\n     * 短信 API 发送失败的提示\n     */\n    private String apiSendMsg;\n    /**\n     * 短信 API 发送返回的唯一请求 ID\n     *\n     * 用于和短信 API 进行定位于排错\n     */\n    private String apiRequestId;\n    /**\n     * 短信 API 发送返回的序号\n     *\n     * 用于和短信 API 平台的发送记录关联\n     */\n    private String apiSerialNo;\n\n    // ========= 接收相关字段 =========\n\n    /**\n     * 接收状态\n     *\n     * 枚举 {@link SmsReceiveStatusEnum}\n     */\n    private Integer receiveStatus;\n    /**\n     * 接收时间\n     */\n    private LocalDateTime receiveTime;\n    /**\n     * 短信 API 接收结果的编码\n     */\n    private String apiReceiveCode;\n    /**\n     * 短信 API 接收结果的提示\n     */\n    private String apiReceiveMsg;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/dataobject/sms/SmsTemplateDO.java",
    "content": "package co.yixiang.yshop.module.system.dal.dataobject.sms;\n\nimport co.yixiang.yshop.module.system.enums.sms.SmsTemplateTypeEnum;\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport com.baomidou.mybatisplus.extension.handlers.JacksonTypeHandler;\nimport lombok.Data;\nimport lombok.EqualsAndHashCode;\nimport lombok.ToString;\n\nimport java.util.List;\n\n/**\n * 短信模板 DO\n *\n * @author zzf\n * @since 2021-01-25\n */\n@TableName(value = \"system_sms_template\", autoResultMap = true)\n@KeySequence(\"system_sms_template_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\npublic class SmsTemplateDO extends BaseDO {\n\n    /**\n     * 自增编号\n     */\n    private Long id;\n\n    // ========= 模板相关字段 =========\n\n    /**\n     * 短信类型\n     *\n     * 枚举 {@link SmsTemplateTypeEnum}\n     */\n    private Integer type;\n    /**\n     * 启用状态\n     *\n     * 枚举 {@link CommonStatusEnum}\n     */\n    private Integer status;\n    /**\n     * 模板编码，保证唯一\n     */\n    private String code;\n    /**\n     * 模板名称\n     */\n    private String name;\n    /**\n     * 模板内容\n     *\n     * 内容的参数，使用 {} 包括，例如说 {name}\n     */\n    private String content;\n    /**\n     * 参数数组(自动根据内容生成)\n     */\n    @TableField(typeHandler = JacksonTypeHandler.class)\n    private List<String> params;\n    /**\n     * 备注\n     */\n    private String remark;\n    /**\n     * 短信 API 的模板编号\n     */\n    private String apiTemplateId;\n\n    // ========= 渠道相关字段 =========\n\n    /**\n     * 短信渠道编号\n     *\n     * 关联 {@link SmsChannelDO#getId()}\n     */\n    private Long channelId;\n    /**\n     * 短信渠道编码\n     *\n     * 冗余 {@link SmsChannelDO#getCode()}\n     */\n    private String channelCode;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/dataobject/social/SocialClientDO.java",
    "content": "package co.yixiang.yshop.module.system.dal.dataobject.social;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.tenant.core.db.TenantBaseDO;\nimport co.yixiang.yshop.module.system.enums.social.SocialTypeEnum;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport com.xingyuv.jushauth.config.AuthConfig;\nimport lombok.*;\n\n/**\n * 社交客户端 DO\n *\n * 对应 {@link AuthConfig} 配置，满足不同租户，有自己的客户端配置，实现社交（三方）登录\n *\n * @author yshop\n */\n@TableName(value = \"system_social_client\", autoResultMap = true)\n@KeySequence(\"system_social_client_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class SocialClientDO extends TenantBaseDO {\n\n    /**\n     * 编号，自增\n     */\n    @TableId\n    private Long id;\n    /**\n     * 应用名\n     */\n    private String name;\n    /**\n     * 社交类型\n     *\n     * 枚举 {@link SocialTypeEnum}\n     */\n    private Integer socialType;\n    /**\n     * 用户类型\n     *\n     * 目的：不同用户类型，对应不同的小程序，需要自己的配置\n     *\n     * 枚举 {@link UserTypeEnum}\n     */\n    private Integer userType;\n    /**\n     * 状态\n     *\n     * 枚举 {@link CommonStatusEnum}\n     */\n    private Integer status;\n\n    /**\n     * 客户端 id\n     */\n    private String clientId;\n    /**\n     * 客户端 Secret\n     */\n    private String clientSecret;\n\n    /**\n     * 代理编号\n     *\n     * 目前只有部分“社交类型”在使用：\n     * 1. 企业微信：对应授权方的网页应用 ID\n     */\n    private String agentId;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/dataobject/social/SocialUserBindDO.java",
    "content": "package co.yixiang.yshop.module.system.dal.dataobject.social;\n\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.*;\n\n/**\n * 社交用户的绑定\n * 即 {@link SocialUserDO} 与 UserDO 的关联表\n *\n * @author yshop\n */\n@TableName(value = \"system_social_user_bind\", autoResultMap = true)\n@KeySequence(\"system_social_user_bind_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class SocialUserBindDO extends BaseDO {\n\n    /**\n     * 编号\n     */\n    @TableId\n    private Long id;\n    /**\n     * 关联的用户编号\n     *\n     * 关联 UserDO 的编号\n     */\n    private Long userId;\n    /**\n     * 用户类型\n     *\n     * 枚举 {@link UserTypeEnum}\n     */\n    private Integer userType;\n\n    /**\n     * 社交平台的用户编号\n     *\n     * 关联 {@link SocialUserDO#getId()}\n     */\n    private Long socialUserId;\n    /**\n     * 社交平台的类型\n     *\n     * 冗余 {@link SocialUserDO#getType()}\n     */\n    private Integer socialType;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/dataobject/social/SocialUserDO.java",
    "content": "package co.yixiang.yshop.module.system.dal.dataobject.social;\n\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport co.yixiang.yshop.module.system.enums.social.SocialTypeEnum;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.*;\n\n/**\n * 社交（三方）用户\n *\n * @author weir\n */\n@TableName(value = \"system_social_user\", autoResultMap = true)\n@KeySequence(\"system_social_user_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class SocialUserDO extends BaseDO {\n\n    /**\n     * 自增主键\n     */\n    @TableId\n    private Long id;\n    /**\n     * 社交平台的类型\n     *\n     * 枚举 {@link SocialTypeEnum}\n     */\n    private Integer type;\n\n    /**\n     * 社交 openid\n     */\n    private String openid;\n    /**\n     * 社交 token\n     */\n    private String token;\n    /**\n     * 原始 Token 数据，一般是 JSON 格式\n     */\n    private String rawTokenInfo;\n\n    /**\n     * 用户昵称\n     */\n    private String nickname;\n    /**\n     * 用户头像\n     */\n    private String avatar;\n    /**\n     * 原始用户数据，一般是 JSON 格式\n     */\n    private String rawUserInfo;\n\n    /**\n     * 最后一次的认证 code\n     */\n    private String code;\n    /**\n     * 最后一次的认证 state\n     */\n    private String state;\n\n}\n\n\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/dataobject/tenant/TenantDO.java",
    "content": "package co.yixiang.yshop.module.system.dal.dataobject.tenant;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.user.AdminUserDO;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.*;\n\nimport java.time.LocalDateTime;\n\n/**\n * 租户 DO\n *\n * @author yshop\n */\n@TableName(value = \"system_tenant\", autoResultMap = true)\n@KeySequence(\"system_tenant_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@AllArgsConstructor\n@NoArgsConstructor\npublic class TenantDO extends BaseDO {\n\n    /**\n     * 套餐编号 - 系统\n     */\n    public static final Long PACKAGE_ID_SYSTEM = 0L;\n\n    /**\n     * 租户编号，自增\n     */\n    private Long id;\n    /**\n     * 租户名，唯一\n     */\n    private String name;\n    /**\n     * 联系人的用户编号\n     *\n     * 关联 {@link AdminUserDO#getId()}\n     */\n    private Long contactUserId;\n    /**\n     * 联系人\n     */\n    private String contactName;\n    /**\n     * 联系手机\n     */\n    private String contactMobile;\n    /**\n     * 租户状态\n     *\n     * 枚举 {@link CommonStatusEnum}\n     */\n    private Integer status;\n    /**\n     * 绑定域名\n     */\n    @TableField(value = \"domain\")\n    private String website;\n    /**\n     * 租户套餐编号\n     *\n     * 关联 {@link TenantPackageDO#getId()}\n     * 特殊逻辑：系统内置租户，不使用套餐，暂时使用 {@link #PACKAGE_ID_SYSTEM} 标识\n     */\n    private Long packageId;\n    /**\n     * 过期时间\n     */\n    private LocalDateTime expireTime;\n    /**\n     * 账号数量\n     */\n    private Integer accountCount;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/dataobject/tenant/TenantPackageDO.java",
    "content": "package co.yixiang.yshop.module.system.dal.dataobject.tenant;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport co.yixiang.yshop.framework.mybatis.core.type.JsonLongSetTypeHandler;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.*;\n\nimport java.util.Set;\n\n/**\n * 租户套餐 DO\n *\n * @author yshop\n */\n@TableName(value = \"system_tenant_package\", autoResultMap = true)\n@KeySequence(\"system_tenant_package_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@ToString(callSuper = true)\n@Builder\n@AllArgsConstructor\n@NoArgsConstructor\npublic class TenantPackageDO extends BaseDO {\n\n    /**\n     * 套餐编号，自增\n     */\n    private Long id;\n    /**\n     * 套餐名，唯一\n     */\n    private String name;\n    /**\n     * 租户套餐状态\n     *\n     * 枚举 {@link CommonStatusEnum}\n     */\n    private Integer status;\n    /**\n     * 备注\n     */\n    private String remark;\n    /**\n     * 关联的菜单编号\n     */\n    @TableField(typeHandler = JsonLongSetTypeHandler.class)\n    private Set<Long> menuIds;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/dataobject/user/AdminUserDO.java",
    "content": "package co.yixiang.yshop.module.system.dal.dataobject.user;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.mybatis.core.type.JsonLongSetTypeHandler;\nimport co.yixiang.yshop.framework.tenant.core.db.TenantBaseDO;\nimport co.yixiang.yshop.module.system.enums.common.SexEnum;\nimport com.baomidou.mybatisplus.annotation.KeySequence;\nimport com.baomidou.mybatisplus.annotation.TableField;\nimport com.baomidou.mybatisplus.annotation.TableId;\nimport com.baomidou.mybatisplus.annotation.TableName;\nimport lombok.*;\nimport org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;\n\nimport java.time.LocalDateTime;\nimport java.util.Set;\n\n/**\n * 管理后台的用户 DO\n *\n * @author yshop\n */\n@TableName(value = \"system_users\", autoResultMap = true) // 由于 SQL Server 的 system_user 是关键字，所以使用 system_users\n@KeySequence(\"system_users_seq\") // 用于 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库的主键自增。如果是 MySQL 等数据库，可不写。\n@Data\n@EqualsAndHashCode(callSuper = true)\n@Builder\n@NoArgsConstructor\n@AllArgsConstructor\npublic class AdminUserDO extends TenantBaseDO {\n\n    /**\n     * 用户ID\n     */\n    @TableId\n    private Long id;\n    /**\n     * 用户账号\n     */\n    private String username;\n    /**\n     * 加密后的密码\n     *\n     * 因为目前使用 {@link BCryptPasswordEncoder} 加密器，所以无需自己处理 salt 盐\n     */\n    private String password;\n    /**\n     * 用户昵称\n     */\n    private String nickname;\n    /**\n     * 备注\n     */\n    private String remark;\n    /**\n     * 部门 ID\n     */\n    private Long deptId;\n    /**\n     * 岗位编号数组\n     */\n    @TableField(typeHandler = JsonLongSetTypeHandler.class)\n    private Set<Long> postIds;\n    /**\n     * 用户邮箱\n     */\n    private String email;\n    /**\n     * 手机号码\n     */\n    private String mobile;\n    /**\n     * 用户性别\n     *\n     * 枚举类 {@link SexEnum}\n     */\n    private Integer sex;\n    /**\n     * 用户头像\n     */\n    private String avatar;\n    /**\n     * 帐号状态\n     *\n     * 枚举 {@link CommonStatusEnum}\n     */\n    private Integer status;\n    /**\n     * 最后登录IP\n     */\n    private String loginIp;\n    /**\n     * 最后登录时间\n     */\n    private LocalDateTime loginDate;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/mysql/dept/DeptMapper.java",
    "content": "package co.yixiang.yshop.module.system.dal.mysql.dept;\n\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.system.controller.admin.dept.vo.dept.DeptListReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.dept.DeptDO;\nimport org.apache.ibatis.annotations.Mapper;\n\nimport java.util.Collection;\nimport java.util.List;\n\n@Mapper\npublic interface DeptMapper extends BaseMapperX<DeptDO> {\n\n    default List<DeptDO> selectList(DeptListReqVO reqVO) {\n        return selectList(new LambdaQueryWrapperX<DeptDO>()\n                .likeIfPresent(DeptDO::getName, reqVO.getName())\n                .eqIfPresent(DeptDO::getStatus, reqVO.getStatus()));\n    }\n\n    default DeptDO selectByParentIdAndName(Long parentId, String name) {\n        return selectOne(DeptDO::getParentId, parentId, DeptDO::getName, name);\n    }\n\n    default Long selectCountByParentId(Long parentId) {\n        return selectCount(DeptDO::getParentId, parentId);\n    }\n\n    default List<DeptDO> selectListByParentId(Collection<Long> parentIds) {\n        return selectList(DeptDO::getParentId, parentIds);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/mysql/dept/PostMapper.java",
    "content": "package co.yixiang.yshop.module.system.dal.mysql.dept;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.system.controller.admin.dept.vo.post.PostPageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.dept.PostDO;\nimport org.apache.ibatis.annotations.Mapper;\n\nimport java.util.Collection;\nimport java.util.List;\n\n@Mapper\npublic interface PostMapper extends BaseMapperX<PostDO> {\n\n    default List<PostDO> selectList(Collection<Long> ids, Collection<Integer> statuses) {\n        return selectList(new LambdaQueryWrapperX<PostDO>()\n                .inIfPresent(PostDO::getId, ids)\n                .inIfPresent(PostDO::getStatus, statuses));\n    }\n\n    default PageResult<PostDO> selectPage(PostPageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<PostDO>()\n                .likeIfPresent(PostDO::getCode, reqVO.getCode())\n                .likeIfPresent(PostDO::getName, reqVO.getName())\n                .eqIfPresent(PostDO::getStatus, reqVO.getStatus())\n                .orderByDesc(PostDO::getId));\n    }\n\n    default PostDO selectByName(String name) {\n        return selectOne(PostDO::getName, name);\n    }\n\n    default PostDO selectByCode(String code) {\n        return selectOne(PostDO::getCode, code);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/mysql/dept/UserPostMapper.java",
    "content": "package co.yixiang.yshop.module.system.dal.mysql.dept;\n\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.system.dal.dataobject.dept.UserPostDO;\nimport com.baomidou.mybatisplus.core.toolkit.Wrappers;\nimport org.apache.ibatis.annotations.Mapper;\n\nimport java.util.Collection;\nimport java.util.List;\n\n@Mapper\npublic interface UserPostMapper extends BaseMapperX<UserPostDO> {\n\n    default List<UserPostDO> selectListByUserId(Long userId) {\n        return selectList(UserPostDO::getUserId, userId);\n    }\n\n    default void deleteByUserIdAndPostId(Long userId, Collection<Long> postIds) {\n        delete(new LambdaQueryWrapperX<UserPostDO>()\n                .eq(UserPostDO::getUserId, userId)\n                .in(UserPostDO::getPostId, postIds));\n    }\n\n    default List<UserPostDO> selectListByPostIds(Collection<Long> postIds) {\n        return selectList(UserPostDO::getPostId, postIds);\n    }\n\n    default void deleteByUserId(Long userId) {\n        delete(Wrappers.lambdaUpdate(UserPostDO.class).eq(UserPostDO::getUserId, userId));\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/mysql/dict/DictDataMapper.java",
    "content": "package co.yixiang.yshop.module.system.dal.mysql.dict;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.system.controller.admin.dict.vo.data.DictDataPageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.dict.DictDataDO;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport org.apache.ibatis.annotations.Mapper;\n\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.List;\n\n@Mapper\npublic interface DictDataMapper extends BaseMapperX<DictDataDO> {\n\n    default DictDataDO selectByDictTypeAndValue(String dictType, String value) {\n        return selectOne(DictDataDO::getDictType, dictType, DictDataDO::getValue, value);\n    }\n\n    default DictDataDO selectByDictTypeAndLabel(String dictType, String label) {\n        return selectOne(DictDataDO::getDictType, dictType, DictDataDO::getLabel, label);\n    }\n\n    default List<DictDataDO> selectByDictTypeAndValues(String dictType, Collection<String> values) {\n        return selectList(new LambdaQueryWrapper<DictDataDO>().eq(DictDataDO::getDictType, dictType)\n                .in(DictDataDO::getValue, values));\n    }\n\n    default long selectCountByDictType(String dictType) {\n        return selectCount(DictDataDO::getDictType, dictType);\n    }\n\n    default PageResult<DictDataDO> selectPage(DictDataPageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<DictDataDO>()\n                .likeIfPresent(DictDataDO::getLabel, reqVO.getLabel())\n                .eqIfPresent(DictDataDO::getDictType, reqVO.getDictType())\n                .eqIfPresent(DictDataDO::getStatus, reqVO.getStatus())\n                .orderByDesc(Arrays.asList(DictDataDO::getDictType, DictDataDO::getSort)));\n    }\n\n    default List<DictDataDO> selectListByStatusAndDictType(Integer status, String dictType) {\n        return selectList(new LambdaQueryWrapperX<DictDataDO>()\n                .eqIfPresent(DictDataDO::getStatus, status)\n                .eqIfPresent(DictDataDO::getDictType, dictType));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/mysql/dict/DictTypeMapper.java",
    "content": "package co.yixiang.yshop.module.system.dal.mysql.dict;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.system.controller.admin.dict.vo.type.DictTypePageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.dict.DictTypeDO;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.apache.ibatis.annotations.Param;\nimport org.apache.ibatis.annotations.Update;\n\nimport java.time.LocalDateTime;\n\n@Mapper\npublic interface DictTypeMapper extends BaseMapperX<DictTypeDO> {\n\n    default PageResult<DictTypeDO> selectPage(DictTypePageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<DictTypeDO>()\n                .likeIfPresent(DictTypeDO::getName, reqVO.getName())\n                .likeIfPresent(DictTypeDO::getType, reqVO.getType())\n                .eqIfPresent(DictTypeDO::getStatus, reqVO.getStatus())\n                .betweenIfPresent(DictTypeDO::getCreateTime, reqVO.getCreateTime())\n                .orderByDesc(DictTypeDO::getId));\n    }\n\n    default DictTypeDO selectByType(String type) {\n        return selectOne(DictTypeDO::getType, type);\n    }\n\n    default DictTypeDO selectByName(String name) {\n        return selectOne(DictTypeDO::getName, name);\n    }\n\n    @Update(\"UPDATE system_dict_type SET deleted = 1, deleted_time = #{deletedTime} WHERE id = #{id}\")\n    void updateToDelete(@Param(\"id\") Long id, @Param(\"deletedTime\") LocalDateTime deletedTime);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/mysql/logger/LoginLogMapper.java",
    "content": "package co.yixiang.yshop.module.system.dal.mysql.logger;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.system.controller.admin.logger.vo.loginlog.LoginLogPageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.logger.LoginLogDO;\nimport co.yixiang.yshop.module.system.enums.logger.LoginResultEnum;\nimport org.apache.ibatis.annotations.Mapper;\n\n@Mapper\npublic interface LoginLogMapper extends BaseMapperX<LoginLogDO> {\n\n    default PageResult<LoginLogDO> selectPage(LoginLogPageReqVO reqVO) {\n        LambdaQueryWrapperX<LoginLogDO> query = new LambdaQueryWrapperX<LoginLogDO>()\n                .likeIfPresent(LoginLogDO::getUserIp, reqVO.getUserIp())\n                .likeIfPresent(LoginLogDO::getUsername, reqVO.getUsername())\n                .betweenIfPresent(LoginLogDO::getCreateTime, reqVO.getCreateTime());\n        if (Boolean.TRUE.equals(reqVO.getStatus())) {\n            query.eq(LoginLogDO::getResult, LoginResultEnum.SUCCESS.getResult());\n        } else if (Boolean.FALSE.equals(reqVO.getStatus())) {\n            query.gt(LoginLogDO::getResult, LoginResultEnum.SUCCESS.getResult());\n        }\n        query.orderByDesc(LoginLogDO::getId); // 降序\n        return selectPage(reqVO, query);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/mysql/logger/OperateLogMapper.java",
    "content": "package co.yixiang.yshop.module.system.dal.mysql.logger;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.system.api.logger.dto.OperateLogPageReqDTO;\nimport co.yixiang.yshop.module.system.controller.admin.logger.vo.operatelog.OperateLogPageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.logger.OperateLogDO;\nimport org.apache.ibatis.annotations.Mapper;\n\n@Mapper\npublic interface OperateLogMapper extends BaseMapperX<OperateLogDO> {\n\n    default PageResult<OperateLogDO> selectPage(OperateLogPageReqVO pageReqDTO) {\n        return selectPage(pageReqDTO, new LambdaQueryWrapperX<OperateLogDO>()\n                .eqIfPresent(OperateLogDO::getUserId, pageReqDTO.getUserId())\n                .eqIfPresent(OperateLogDO::getBizId, pageReqDTO.getBizId())\n                .likeIfPresent(OperateLogDO::getType, pageReqDTO.getType())\n                .likeIfPresent(OperateLogDO::getSubType, pageReqDTO.getSubType())\n                .likeIfPresent(OperateLogDO::getAction, pageReqDTO.getAction())\n                .betweenIfPresent(OperateLogDO::getCreateTime, pageReqDTO.getCreateTime())\n                .orderByDesc(OperateLogDO::getId));\n    }\n\n    default PageResult<OperateLogDO> selectPage(OperateLogPageReqDTO pageReqDTO) {\n        return selectPage(pageReqDTO, new LambdaQueryWrapperX<OperateLogDO>()\n                .eqIfPresent(OperateLogDO::getType, pageReqDTO.getType())\n                .eqIfPresent(OperateLogDO::getBizId, pageReqDTO.getBizId())\n                .eqIfPresent(OperateLogDO::getUserId, pageReqDTO.getUserId())\n                .orderByDesc(OperateLogDO::getId));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/mysql/mail/MailAccountMapper.java",
    "content": "package co.yixiang.yshop.module.system.dal.mysql.mail;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.QueryWrapperX;\nimport co.yixiang.yshop.module.system.controller.admin.mail.vo.account.MailAccountPageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.mail.MailAccountDO;\nimport org.apache.ibatis.annotations.Mapper;\n\n@Mapper\npublic interface MailAccountMapper extends BaseMapperX<MailAccountDO> {\n\n    default PageResult<MailAccountDO> selectPage(MailAccountPageReqVO pageReqVO) {\n        return selectPage(pageReqVO, new LambdaQueryWrapperX<MailAccountDO>()\n                .likeIfPresent(MailAccountDO::getMail, pageReqVO.getMail())\n                .likeIfPresent(MailAccountDO::getUsername , pageReqVO.getUsername()));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/mysql/mail/MailLogMapper.java",
    "content": "package co.yixiang.yshop.module.system.dal.mysql.mail;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.system.controller.admin.mail.vo.log.MailLogPageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.mail.MailLogDO;\nimport org.apache.ibatis.annotations.Mapper;\n\n@Mapper\npublic interface MailLogMapper extends BaseMapperX<MailLogDO> {\n\n    default PageResult<MailLogDO> selectPage(MailLogPageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<MailLogDO>()\n                .eqIfPresent(MailLogDO::getUserId, reqVO.getUserId())\n                .eqIfPresent(MailLogDO::getUserType, reqVO.getUserType())\n                .likeIfPresent(MailLogDO::getToMail, reqVO.getToMail())\n                .eqIfPresent(MailLogDO::getAccountId, reqVO.getAccountId())\n                .eqIfPresent(MailLogDO::getTemplateId, reqVO.getTemplateId())\n                .eqIfPresent(MailLogDO::getSendStatus, reqVO.getSendStatus())\n                .betweenIfPresent(MailLogDO::getSendTime, reqVO.getSendTime())\n                .orderByDesc(MailLogDO::getId));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/mysql/mail/MailTemplateMapper.java",
    "content": "package co.yixiang.yshop.module.system.dal.mysql.mail;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.QueryWrapperX;\nimport co.yixiang.yshop.module.system.controller.admin.mail.vo.template.MailTemplatePageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.mail.MailTemplateDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.sms.SmsTemplateDO;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.apache.ibatis.annotations.Select;\n\nimport java.util.Date;\n\n@Mapper\npublic interface MailTemplateMapper extends BaseMapperX<MailTemplateDO> {\n\n    default PageResult<MailTemplateDO> selectPage(MailTemplatePageReqVO pageReqVO){\n        return selectPage(pageReqVO , new LambdaQueryWrapperX<MailTemplateDO>()\n                .eqIfPresent(MailTemplateDO::getStatus, pageReqVO.getStatus())\n                .likeIfPresent(MailTemplateDO::getCode, pageReqVO.getCode())\n                .likeIfPresent(MailTemplateDO::getName, pageReqVO.getName())\n                .eqIfPresent(MailTemplateDO::getAccountId, pageReqVO.getAccountId())\n                .betweenIfPresent(MailTemplateDO::getCreateTime, pageReqVO.getCreateTime()));\n    }\n\n    default Long selectCountByAccountId(Long accountId) {\n        return selectCount(MailTemplateDO::getAccountId, accountId);\n    }\n\n    default MailTemplateDO selectByCode(String code) {\n        return selectOne(MailTemplateDO::getCode, code);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/mysql/notice/NoticeMapper.java",
    "content": "package co.yixiang.yshop.module.system.dal.mysql.notice;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.system.controller.admin.notice.vo.NoticePageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.notice.NoticeDO;\nimport org.apache.ibatis.annotations.Mapper;\n\n@Mapper\npublic interface NoticeMapper extends BaseMapperX<NoticeDO> {\n\n    default PageResult<NoticeDO> selectPage(NoticePageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<NoticeDO>()\n                .likeIfPresent(NoticeDO::getTitle, reqVO.getTitle())\n                .eqIfPresent(NoticeDO::getStatus, reqVO.getStatus())\n                .orderByDesc(NoticeDO::getId));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/mysql/notify/NotifyMessageMapper.java",
    "content": "package co.yixiang.yshop.module.system.dal.mysql.notify;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.QueryWrapperX;\nimport co.yixiang.yshop.module.system.controller.admin.notify.vo.message.NotifyMessageMyPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.notify.vo.message.NotifyMessagePageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.notify.NotifyMessageDO;\nimport org.apache.ibatis.annotations.Mapper;\n\nimport java.time.LocalDateTime;\nimport java.util.Collection;\nimport java.util.List;\n\n@Mapper\npublic interface NotifyMessageMapper extends BaseMapperX<NotifyMessageDO> {\n\n    default PageResult<NotifyMessageDO> selectPage(NotifyMessagePageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<NotifyMessageDO>()\n                .eqIfPresent(NotifyMessageDO::getUserId, reqVO.getUserId())\n                .eqIfPresent(NotifyMessageDO::getUserType, reqVO.getUserType())\n                .likeIfPresent(NotifyMessageDO::getTemplateCode, reqVO.getTemplateCode())\n                .eqIfPresent(NotifyMessageDO::getTemplateType, reqVO.getTemplateType())\n                .betweenIfPresent(NotifyMessageDO::getCreateTime, reqVO.getCreateTime())\n                .orderByDesc(NotifyMessageDO::getId));\n    }\n\n    default PageResult<NotifyMessageDO> selectPage(NotifyMessageMyPageReqVO reqVO, Long userId, Integer userType) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<NotifyMessageDO>()\n                .eqIfPresent(NotifyMessageDO::getReadStatus, reqVO.getReadStatus())\n                .betweenIfPresent(NotifyMessageDO::getCreateTime, reqVO.getCreateTime())\n                .eq(NotifyMessageDO::getUserId, userId)\n                .eq(NotifyMessageDO::getUserType, userType)\n                .orderByDesc(NotifyMessageDO::getId));\n    }\n\n    default int updateListRead(Collection<Long> ids, Long userId, Integer userType) {\n        return update(new NotifyMessageDO().setReadStatus(true).setReadTime(LocalDateTime.now()),\n                new LambdaQueryWrapperX<NotifyMessageDO>()\n                        .in(NotifyMessageDO::getId, ids)\n                        .eq(NotifyMessageDO::getUserId, userId)\n                        .eq(NotifyMessageDO::getUserType, userType)\n                        .eq(NotifyMessageDO::getReadStatus, false));\n    }\n\n    default int updateListRead(Long userId, Integer userType) {\n        return update(new NotifyMessageDO().setReadStatus(true).setReadTime(LocalDateTime.now()),\n                new LambdaQueryWrapperX<NotifyMessageDO>()\n                        .eq(NotifyMessageDO::getUserId, userId)\n                        .eq(NotifyMessageDO::getUserType, userType)\n                        .eq(NotifyMessageDO::getReadStatus, false));\n    }\n\n    default List<NotifyMessageDO> selectUnreadListByUserIdAndUserType(Long userId, Integer userType, Integer size) {\n        return selectList(new QueryWrapperX<NotifyMessageDO>() // 由于要使用 limitN 语句，所以只能用 QueryWrapperX\n                .eq(\"user_id\", userId)\n                .eq(\"user_type\", userType)\n                .eq(\"read_status\", false)\n                .orderByDesc(\"id\").limitN(size));\n    }\n\n    default Long selectUnreadCountByUserIdAndUserType(Long userId, Integer userType) {\n        return selectCount(new LambdaQueryWrapperX<NotifyMessageDO>()\n                .eq(NotifyMessageDO::getReadStatus, false)\n                .eq(NotifyMessageDO::getUserId, userId)\n                .eq(NotifyMessageDO::getUserType, userType));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/mysql/notify/NotifyTemplateMapper.java",
    "content": "package co.yixiang.yshop.module.system.dal.mysql.notify;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.system.controller.admin.notify.vo.template.NotifyTemplatePageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.notify.NotifyTemplateDO;\nimport org.apache.ibatis.annotations.Mapper;\n\n@Mapper\npublic interface NotifyTemplateMapper extends BaseMapperX<NotifyTemplateDO> {\n\n    default NotifyTemplateDO selectByCode(String code) {\n        return selectOne(NotifyTemplateDO::getCode, code);\n    }\n\n    default PageResult<NotifyTemplateDO> selectPage(NotifyTemplatePageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<NotifyTemplateDO>()\n                .likeIfPresent(NotifyTemplateDO::getCode, reqVO.getCode())\n                .likeIfPresent(NotifyTemplateDO::getName, reqVO.getName())\n                .eqIfPresent(NotifyTemplateDO::getStatus, reqVO.getStatus())\n                .betweenIfPresent(NotifyTemplateDO::getCreateTime, reqVO.getCreateTime())\n                .orderByDesc(NotifyTemplateDO::getId));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/mysql/oauth2/OAuth2AccessTokenMapper.java",
    "content": "package co.yixiang.yshop.module.system.dal.mysql.oauth2;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.framework.tenant.core.aop.TenantIgnore;\nimport co.yixiang.yshop.module.system.controller.admin.oauth2.vo.token.OAuth2AccessTokenPageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;\nimport org.apache.ibatis.annotations.Mapper;\n\nimport java.time.LocalDateTime;\nimport java.util.List;\n\n@Mapper\npublic interface OAuth2AccessTokenMapper extends BaseMapperX<OAuth2AccessTokenDO> {\n\n    @TenantIgnore // 获取 token 的时候，需要忽略租户编号。原因是：一些场景下，可能不会传递 tenant-id 请求头，例如说文件上传、积木报表等等\n    default OAuth2AccessTokenDO selectByAccessToken(String accessToken) {\n        return selectOne(OAuth2AccessTokenDO::getAccessToken, accessToken);\n    }\n\n    default List<OAuth2AccessTokenDO> selectListByRefreshToken(String refreshToken) {\n        return selectList(OAuth2AccessTokenDO::getRefreshToken, refreshToken);\n    }\n\n    default PageResult<OAuth2AccessTokenDO> selectPage(OAuth2AccessTokenPageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<OAuth2AccessTokenDO>()\n                .eqIfPresent(OAuth2AccessTokenDO::getUserId, reqVO.getUserId())\n                .eqIfPresent(OAuth2AccessTokenDO::getUserType, reqVO.getUserType())\n                .likeIfPresent(OAuth2AccessTokenDO::getClientId, reqVO.getClientId())\n                .gt(OAuth2AccessTokenDO::getExpiresTime, LocalDateTime.now())\n                .orderByDesc(OAuth2AccessTokenDO::getId));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/mysql/oauth2/OAuth2ApproveMapper.java",
    "content": "package co.yixiang.yshop.module.system.dal.mysql.oauth2;\n\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2ApproveDO;\nimport org.apache.ibatis.annotations.Mapper;\n\nimport java.util.List;\n\n@Mapper\npublic interface OAuth2ApproveMapper extends BaseMapperX<OAuth2ApproveDO> {\n\n    default int update(OAuth2ApproveDO updateObj) {\n        return update(updateObj, new LambdaQueryWrapperX<OAuth2ApproveDO>()\n                .eq(OAuth2ApproveDO::getUserId, updateObj.getUserId())\n                .eq(OAuth2ApproveDO::getUserType, updateObj.getUserType())\n                .eq(OAuth2ApproveDO::getClientId, updateObj.getClientId())\n                .eq(OAuth2ApproveDO::getScope, updateObj.getScope()));\n    }\n\n    default List<OAuth2ApproveDO> selectListByUserIdAndUserTypeAndClientId(Long userId, Integer userType, String clientId) {\n        return selectList(new LambdaQueryWrapperX<OAuth2ApproveDO>()\n                .eq(OAuth2ApproveDO::getUserId, userId)\n                .eq(OAuth2ApproveDO::getUserType, userType)\n                .eq(OAuth2ApproveDO::getClientId, clientId));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/mysql/oauth2/OAuth2ClientMapper.java",
    "content": "package co.yixiang.yshop.module.system.dal.mysql.oauth2;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.system.controller.admin.oauth2.vo.client.OAuth2ClientPageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2ClientDO;\nimport org.apache.ibatis.annotations.Mapper;\n\n\n/**\n * OAuth2 客户端 Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface OAuth2ClientMapper extends BaseMapperX<OAuth2ClientDO> {\n\n    default PageResult<OAuth2ClientDO> selectPage(OAuth2ClientPageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<OAuth2ClientDO>()\n                .likeIfPresent(OAuth2ClientDO::getName, reqVO.getName())\n                .eqIfPresent(OAuth2ClientDO::getStatus, reqVO.getStatus())\n                .orderByDesc(OAuth2ClientDO::getId));\n    }\n\n    default OAuth2ClientDO selectByClientId(String clientId) {\n        return selectOne(OAuth2ClientDO::getClientId, clientId);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/mysql/oauth2/OAuth2CodeMapper.java",
    "content": "package co.yixiang.yshop.module.system.dal.mysql.oauth2;\n\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2CodeDO;\nimport org.apache.ibatis.annotations.Mapper;\n\n@Mapper\npublic interface OAuth2CodeMapper extends BaseMapperX<OAuth2CodeDO> {\n\n    default OAuth2CodeDO selectByCode(String code) {\n        return selectOne(OAuth2CodeDO::getCode, code);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/mysql/oauth2/OAuth2RefreshTokenMapper.java",
    "content": "package co.yixiang.yshop.module.system.dal.mysql.oauth2;\n\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2RefreshTokenDO;\nimport org.apache.ibatis.annotations.Mapper;\n\n@Mapper\npublic interface OAuth2RefreshTokenMapper extends BaseMapperX<OAuth2RefreshTokenDO> {\n\n    default int deleteByRefreshToken(String refreshToken) {\n        return delete(new LambdaQueryWrapperX<OAuth2RefreshTokenDO>()\n                .eq(OAuth2RefreshTokenDO::getRefreshToken, refreshToken));\n    }\n\n    default OAuth2RefreshTokenDO selectByRefreshToken(String refreshToken) {\n        return selectOne(OAuth2RefreshTokenDO::getRefreshToken, refreshToken);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/mysql/permission/MenuMapper.java",
    "content": "package co.yixiang.yshop.module.system.dal.mysql.permission;\n\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.system.controller.admin.permission.vo.menu.MenuListReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.permission.MenuDO;\nimport org.apache.ibatis.annotations.Mapper;\n\nimport java.util.List;\n\n@Mapper\npublic interface MenuMapper extends BaseMapperX<MenuDO> {\n\n    default MenuDO selectByParentIdAndName(Long parentId, String name) {\n        return selectOne(MenuDO::getParentId, parentId, MenuDO::getName, name);\n    }\n\n    default Long selectCountByParentId(Long parentId) {\n        return selectCount(MenuDO::getParentId, parentId);\n    }\n\n    default List<MenuDO> selectList(MenuListReqVO reqVO) {\n        return selectList(new LambdaQueryWrapperX<MenuDO>()\n                .likeIfPresent(MenuDO::getName, reqVO.getName())\n                .eqIfPresent(MenuDO::getStatus, reqVO.getStatus()));\n    }\n\n    default List<MenuDO> selectListByPermission(String permission) {\n        return selectList(MenuDO::getPermission, permission);\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/mysql/permission/RoleMapper.java",
    "content": "package co.yixiang.yshop.module.system.dal.mysql.permission;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.dataobject.BaseDO;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.system.controller.admin.permission.vo.role.RolePageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.permission.RoleDO;\nimport org.apache.ibatis.annotations.Mapper;\nimport org.springframework.lang.Nullable;\n\nimport java.util.Collection;\nimport java.util.List;\n\n@Mapper\npublic interface RoleMapper extends BaseMapperX<RoleDO> {\n\n    default PageResult<RoleDO> selectPage(RolePageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<RoleDO>()\n                .likeIfPresent(RoleDO::getName, reqVO.getName())\n                .likeIfPresent(RoleDO::getCode, reqVO.getCode())\n                .eqIfPresent(RoleDO::getStatus, reqVO.getStatus())\n                .betweenIfPresent(BaseDO::getCreateTime, reqVO.getCreateTime())\n                .orderByAsc(RoleDO::getSort));\n    }\n\n    default RoleDO selectByName(String name) {\n        return selectOne(RoleDO::getName, name);\n    }\n\n    default RoleDO selectByCode(String code) {\n        return selectOne(RoleDO::getCode, code);\n    }\n\n    default List<RoleDO> selectListByStatus(@Nullable Collection<Integer> statuses) {\n        return selectList(RoleDO::getStatus, statuses);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/mysql/permission/RoleMenuMapper.java",
    "content": "package co.yixiang.yshop.module.system.dal.mysql.permission;\n\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.module.system.dal.dataobject.permission.RoleMenuDO;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport org.apache.ibatis.annotations.Mapper;\n\nimport java.util.Collection;\nimport java.util.List;\n\n@Mapper\npublic interface RoleMenuMapper extends BaseMapperX<RoleMenuDO> {\n\n    default List<RoleMenuDO> selectListByRoleId(Long roleId) {\n        return selectList(RoleMenuDO::getRoleId, roleId);\n    }\n\n    default List<RoleMenuDO> selectListByRoleId(Collection<Long> roleIds) {\n        return selectList(RoleMenuDO::getRoleId, roleIds);\n    }\n\n    default List<RoleMenuDO> selectListByMenuId(Long menuId) {\n        return selectList(RoleMenuDO::getMenuId, menuId);\n    }\n\n    default void deleteListByRoleIdAndMenuIds(Long roleId, Collection<Long> menuIds) {\n        delete(new LambdaQueryWrapper<RoleMenuDO>()\n                .eq(RoleMenuDO::getRoleId, roleId)\n                .in(RoleMenuDO::getMenuId, menuIds));\n    }\n\n    default void deleteListByMenuId(Long menuId) {\n        delete(new LambdaQueryWrapper<RoleMenuDO>().eq(RoleMenuDO::getMenuId, menuId));\n    }\n\n    default void deleteListByRoleId(Long roleId) {\n        delete(new LambdaQueryWrapper<RoleMenuDO>().eq(RoleMenuDO::getRoleId, roleId));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/mysql/permission/UserRoleMapper.java",
    "content": "package co.yixiang.yshop.module.system.dal.mysql.permission;\n\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.module.system.dal.dataobject.permission.UserRoleDO;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport org.apache.ibatis.annotations.Mapper;\n\nimport java.util.Collection;\nimport java.util.List;\n\n@Mapper\npublic interface UserRoleMapper extends BaseMapperX<UserRoleDO> {\n\n    default List<UserRoleDO> selectListByUserId(Long userId) {\n        return selectList(UserRoleDO::getUserId, userId);\n    }\n\n    default void deleteListByUserIdAndRoleIdIds(Long userId, Collection<Long> roleIds) {\n        delete(new LambdaQueryWrapper<UserRoleDO>()\n                .eq(UserRoleDO::getUserId, userId)\n                .in(UserRoleDO::getRoleId, roleIds));\n    }\n\n    default void deleteListByUserId(Long userId) {\n        delete(new LambdaQueryWrapper<UserRoleDO>().eq(UserRoleDO::getUserId, userId));\n    }\n\n    default void deleteListByRoleId(Long roleId) {\n        delete(new LambdaQueryWrapper<UserRoleDO>().eq(UserRoleDO::getRoleId, roleId));\n    }\n\n    default List<UserRoleDO> selectListByRoleIds(Collection<Long> roleIds) {\n        return selectList(UserRoleDO::getRoleId, roleIds);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/mysql/sms/SmsChannelMapper.java",
    "content": "package co.yixiang.yshop.module.system.dal.mysql.sms;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.sms.SmsChannelDO;\nimport org.apache.ibatis.annotations.Mapper;\n\n@Mapper\npublic interface SmsChannelMapper extends BaseMapperX<SmsChannelDO> {\n\n    default PageResult<SmsChannelDO> selectPage(SmsChannelPageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<SmsChannelDO>()\n                .likeIfPresent(SmsChannelDO::getSignature, reqVO.getSignature())\n                .eqIfPresent(SmsChannelDO::getStatus, reqVO.getStatus())\n                .betweenIfPresent(SmsChannelDO::getCreateTime, reqVO.getCreateTime())\n                .orderByDesc(SmsChannelDO::getId));\n    }\n\n    default SmsChannelDO selectByCode(String code) {\n        return selectOne(SmsChannelDO::getCode, code);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/mysql/sms/SmsCodeMapper.java",
    "content": "package co.yixiang.yshop.module.system.dal.mysql.sms;\n\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.QueryWrapperX;\nimport co.yixiang.yshop.module.system.dal.dataobject.sms.SmsCodeDO;\nimport org.apache.ibatis.annotations.Mapper;\n\n@Mapper\npublic interface SmsCodeMapper extends BaseMapperX<SmsCodeDO> {\n\n    /**\n     * 获得手机号的最后一个手机验证码\n     *\n     * @param mobile 手机号\n     * @param scene 发送场景，选填\n     * @param code 验证码 选填\n     * @return 手机验证码\n     */\n    default SmsCodeDO selectLastByMobile(String mobile, String code, Integer scene) {\n        return selectOne(new QueryWrapperX<SmsCodeDO>()\n                .eq(\"mobile\", mobile)\n                .eqIfPresent(\"scene\", scene)\n                .eqIfPresent(\"code\", code)\n                .orderByDesc(\"id\")\n                .limitN(1));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/mysql/sms/SmsLogMapper.java",
    "content": "package co.yixiang.yshop.module.system.dal.mysql.sms;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.system.controller.admin.sms.vo.log.SmsLogPageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.sms.SmsLogDO;\nimport org.apache.ibatis.annotations.Mapper;\n\n@Mapper\npublic interface SmsLogMapper extends BaseMapperX<SmsLogDO> {\n\n    default PageResult<SmsLogDO> selectPage(SmsLogPageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<SmsLogDO>()\n                .eqIfPresent(SmsLogDO::getChannelId, reqVO.getChannelId())\n                .eqIfPresent(SmsLogDO::getTemplateId, reqVO.getTemplateId())\n                .likeIfPresent(SmsLogDO::getMobile, reqVO.getMobile())\n                .eqIfPresent(SmsLogDO::getSendStatus, reqVO.getSendStatus())\n                .betweenIfPresent(SmsLogDO::getSendTime, reqVO.getSendTime())\n                .eqIfPresent(SmsLogDO::getReceiveStatus, reqVO.getReceiveStatus())\n                .betweenIfPresent(SmsLogDO::getReceiveTime, reqVO.getReceiveTime())\n                .orderByDesc(SmsLogDO::getId));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/mysql/sms/SmsTemplateMapper.java",
    "content": "package co.yixiang.yshop.module.system.dal.mysql.sms;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.system.controller.admin.sms.vo.template.SmsTemplatePageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.sms.SmsTemplateDO;\nimport org.apache.ibatis.annotations.Mapper;\n\n@Mapper\npublic interface SmsTemplateMapper extends BaseMapperX<SmsTemplateDO> {\n\n    default SmsTemplateDO selectByCode(String code) {\n        return selectOne(SmsTemplateDO::getCode, code);\n    }\n\n    default PageResult<SmsTemplateDO> selectPage(SmsTemplatePageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<SmsTemplateDO>()\n                .eqIfPresent(SmsTemplateDO::getType, reqVO.getType())\n                .eqIfPresent(SmsTemplateDO::getStatus, reqVO.getStatus())\n                .likeIfPresent(SmsTemplateDO::getCode, reqVO.getCode())\n                .likeIfPresent(SmsTemplateDO::getContent, reqVO.getContent())\n                .likeIfPresent(SmsTemplateDO::getApiTemplateId, reqVO.getApiTemplateId())\n                .eqIfPresent(SmsTemplateDO::getChannelId, reqVO.getChannelId())\n                .betweenIfPresent(SmsTemplateDO::getCreateTime, reqVO.getCreateTime())\n                .orderByDesc(SmsTemplateDO::getId));\n    }\n\n    default Long selectCountByChannelId(Long channelId) {\n        return selectCount(SmsTemplateDO::getChannelId, channelId);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/mysql/social/SocialClientMapper.java",
    "content": "package co.yixiang.yshop.module.system.dal.mysql.social;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.system.controller.admin.socail.vo.client.SocialClientPageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.social.SocialClientDO;\nimport org.apache.ibatis.annotations.Mapper;\n\n@Mapper\npublic interface SocialClientMapper extends BaseMapperX<SocialClientDO> {\n\n    default SocialClientDO selectBySocialTypeAndUserType(Integer socialType, Integer userType) {\n        return selectOne(SocialClientDO::getSocialType, socialType,\n                SocialClientDO::getUserType, userType);\n    }\n\n    default PageResult<SocialClientDO> selectPage(SocialClientPageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<SocialClientDO>()\n                .likeIfPresent(SocialClientDO::getName, reqVO.getName())\n                .eqIfPresent(SocialClientDO::getSocialType, reqVO.getSocialType())\n                .eqIfPresent(SocialClientDO::getUserType, reqVO.getUserType())\n                .likeIfPresent(SocialClientDO::getClientId, reqVO.getClientId())\n                .eqIfPresent(SocialClientDO::getStatus, reqVO.getStatus())\n                .orderByDesc(SocialClientDO::getId));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/mysql/social/SocialUserBindMapper.java",
    "content": "package co.yixiang.yshop.module.system.dal.mysql.social;\n\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.system.dal.dataobject.social.SocialUserBindDO;\nimport org.apache.ibatis.annotations.Mapper;\n\nimport java.util.List;\n\n@Mapper\npublic interface SocialUserBindMapper extends BaseMapperX<SocialUserBindDO> {\n\n    default void deleteByUserTypeAndUserIdAndSocialType(Integer userType, Long userId, Integer socialType) {\n        delete(new LambdaQueryWrapperX<SocialUserBindDO>()\n                .eq(SocialUserBindDO::getUserType, userType)\n                .eq(SocialUserBindDO::getUserId, userId)\n                .eq(SocialUserBindDO::getSocialType, socialType));\n    }\n\n    default void deleteByUserTypeAndSocialUserId(Integer userType, Long socialUserId) {\n        delete(new LambdaQueryWrapperX<SocialUserBindDO>()\n                .eq(SocialUserBindDO::getUserType, userType)\n                .eq(SocialUserBindDO::getSocialUserId, socialUserId));\n    }\n\n    default SocialUserBindDO selectByUserTypeAndSocialUserId(Integer userType, Long socialUserId) {\n        return selectOne(SocialUserBindDO::getUserType, userType,\n                SocialUserBindDO::getSocialUserId, socialUserId);\n    }\n\n    default List<SocialUserBindDO> selectListByUserIdAndUserType(Long userId, Integer userType) {\n        return selectList(new LambdaQueryWrapperX<SocialUserBindDO>()\n                .eq(SocialUserBindDO::getUserId, userId)\n                .eq(SocialUserBindDO::getUserType, userType));\n    }\n\n    default SocialUserBindDO selectByUserIdAndUserTypeAndSocialType(Long userId, Integer userType, Integer socialType) {\n        return selectOne(new LambdaQueryWrapperX<SocialUserBindDO>()\n                .eq(SocialUserBindDO::getUserId, userId)\n                .eq(SocialUserBindDO::getUserType, userType)\n                .eq(SocialUserBindDO::getSocialType, socialType));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/mysql/social/SocialUserMapper.java",
    "content": "package co.yixiang.yshop.module.system.dal.mysql.social;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.system.controller.admin.socail.vo.user.SocialUserPageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.social.SocialUserDO;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport org.apache.ibatis.annotations.Mapper;\n\n@Mapper\npublic interface SocialUserMapper extends BaseMapperX<SocialUserDO> {\n\n    default SocialUserDO selectByTypeAndCodeAnState(Integer type, String code, String state) {\n        return selectOne(new LambdaQueryWrapper<SocialUserDO>()\n                .eq(SocialUserDO::getType, type)\n                .eq(SocialUserDO::getCode, code)\n                .eq(SocialUserDO::getState, state));\n    }\n\n    default SocialUserDO selectByTypeAndOpenid(Integer type, String openid) {\n        return selectOne(new LambdaQueryWrapper<SocialUserDO>()\n                .eq(SocialUserDO::getType, type)\n                .eq(SocialUserDO::getOpenid, openid));\n    }\n\n    default PageResult<SocialUserDO> selectPage(SocialUserPageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<SocialUserDO>()\n                .eqIfPresent(SocialUserDO::getType, reqVO.getType())\n                .likeIfPresent(SocialUserDO::getNickname, reqVO.getNickname())\n                .likeIfPresent(SocialUserDO::getOpenid, reqVO.getOpenid())\n                .betweenIfPresent(SocialUserDO::getCreateTime, reqVO.getCreateTime())\n                .orderByDesc(SocialUserDO::getId));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/mysql/tenant/TenantMapper.java",
    "content": "package co.yixiang.yshop.module.system.dal.mysql.tenant;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.system.controller.admin.tenant.vo.tenant.TenantPageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.tenant.TenantDO;\nimport org.apache.ibatis.annotations.Mapper;\n\nimport java.util.List;\n\n/**\n * 租户 Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface TenantMapper extends BaseMapperX<TenantDO> {\n\n    default PageResult<TenantDO> selectPage(TenantPageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<TenantDO>()\n                .likeIfPresent(TenantDO::getName, reqVO.getName())\n                .likeIfPresent(TenantDO::getContactName, reqVO.getContactName())\n                .likeIfPresent(TenantDO::getContactMobile, reqVO.getContactMobile())\n                .eqIfPresent(TenantDO::getStatus, reqVO.getStatus())\n                .betweenIfPresent(TenantDO::getCreateTime, reqVO.getCreateTime())\n                .orderByDesc(TenantDO::getId));\n    }\n\n    default TenantDO selectByName(String name) {\n        return selectOne(TenantDO::getName, name);\n    }\n\n    default TenantDO selectByWebsite(String website) {\n        return selectOne(TenantDO::getWebsite, website);\n    }\n\n    default Long selectCountByPackageId(Long packageId) {\n        return selectCount(TenantDO::getPackageId, packageId);\n    }\n\n    default List<TenantDO> selectListByPackageId(Long packageId) {\n        return selectList(TenantDO::getPackageId, packageId);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/mysql/tenant/TenantPackageMapper.java",
    "content": "package co.yixiang.yshop.module.system.dal.mysql.tenant;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.system.controller.admin.tenant.vo.packages.TenantPackagePageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.tenant.TenantPackageDO;\nimport org.apache.ibatis.annotations.Mapper;\n\nimport java.util.List;\n\n/**\n * 租户套餐 Mapper\n *\n * @author yshop\n */\n@Mapper\npublic interface TenantPackageMapper extends BaseMapperX<TenantPackageDO> {\n\n    default PageResult<TenantPackageDO> selectPage(TenantPackagePageReqVO reqVO) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<TenantPackageDO>()\n                .likeIfPresent(TenantPackageDO::getName, reqVO.getName())\n                .eqIfPresent(TenantPackageDO::getStatus, reqVO.getStatus())\n                .likeIfPresent(TenantPackageDO::getRemark, reqVO.getRemark())\n                .betweenIfPresent(TenantPackageDO::getCreateTime, reqVO.getCreateTime())\n                .orderByDesc(TenantPackageDO::getId));\n    }\n\n    default List<TenantPackageDO> selectListByStatus(Integer status) {\n        return selectList(TenantPackageDO::getStatus, status);\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/mysql/user/AdminUserMapper.java",
    "content": "package co.yixiang.yshop.module.system.dal.mysql.user;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.mapper.BaseMapperX;\nimport co.yixiang.yshop.framework.mybatis.core.query.LambdaQueryWrapperX;\nimport co.yixiang.yshop.module.system.controller.admin.user.vo.user.UserPageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.user.AdminUserDO;\nimport org.apache.ibatis.annotations.Mapper;\n\nimport java.util.Collection;\nimport java.util.List;\n\n@Mapper\npublic interface AdminUserMapper extends BaseMapperX<AdminUserDO> {\n\n    default AdminUserDO selectByUsername(String username) {\n        return selectOne(AdminUserDO::getUsername, username);\n    }\n\n    default AdminUserDO selectByEmail(String email) {\n        return selectOne(AdminUserDO::getEmail, email);\n    }\n\n    default AdminUserDO selectByMobile(String mobile) {\n        return selectOne(AdminUserDO::getMobile, mobile);\n    }\n\n    default PageResult<AdminUserDO> selectPage(UserPageReqVO reqVO, Collection<Long> deptIds) {\n        return selectPage(reqVO, new LambdaQueryWrapperX<AdminUserDO>()\n                .likeIfPresent(AdminUserDO::getUsername, reqVO.getUsername())\n                .likeIfPresent(AdminUserDO::getMobile, reqVO.getMobile())\n                .eqIfPresent(AdminUserDO::getStatus, reqVO.getStatus())\n                .betweenIfPresent(AdminUserDO::getCreateTime, reqVO.getCreateTime())\n                .inIfPresent(AdminUserDO::getDeptId, deptIds)\n                .orderByDesc(AdminUserDO::getId));\n    }\n\n    default List<AdminUserDO> selectListByNickname(String nickname) {\n        return selectList(new LambdaQueryWrapperX<AdminUserDO>().like(AdminUserDO::getNickname, nickname));\n    }\n\n    default List<AdminUserDO> selectListByStatus(Integer status) {\n        return selectList(AdminUserDO::getStatus, status);\n    }\n\n    default List<AdminUserDO> selectListByDeptIds(Collection<Long> deptIds) {\n        return selectList(AdminUserDO::getDeptId, deptIds);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/redis/RedisKeyConstants.java",
    "content": "package co.yixiang.yshop.module.system.dal.redis;\n\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;\n\n/**\n * System Redis Key 枚举类\n *\n * @author yshop\n */\npublic interface RedisKeyConstants {\n\n    /**\n     * 指定部门的所有子部门编号数组的缓存\n     * <p>\n     * KEY 格式：dept_children_ids:{id}\n     * VALUE 数据类型：String 子部门编号集合\n     */\n    String DEPT_CHILDREN_ID_LIST = \"dept_children_ids\";\n\n    /**\n     * 角色的缓存\n     * <p>\n     * KEY 格式：role:{id}\n     * VALUE 数据类型：String 角色信息\n     */\n    String ROLE = \"role\";\n\n    /**\n     * 用户拥有的角色编号的缓存\n     * <p>\n     * KEY 格式：user_role_ids:{userId}\n     * VALUE 数据类型：String 角色编号集合\n     */\n    String USER_ROLE_ID_LIST = \"user_role_ids\";\n\n    /**\n     * 拥有指定菜单的角色编号的缓存\n     * <p>\n     * KEY 格式：user_role_ids:{menuId}\n     * VALUE 数据类型：String 角色编号集合\n     */\n    String MENU_ROLE_ID_LIST = \"menu_role_ids\";\n\n    /**\n     * 拥有权限对应的菜单编号数组的缓存\n     * <p>\n     * KEY 格式：permission_menu_ids:{permission}\n     * VALUE 数据类型：String 菜单编号数组\n     */\n    String PERMISSION_MENU_ID_LIST = \"permission_menu_ids\";\n\n    /**\n     * OAuth2 客户端的缓存\n     * <p>\n     * KEY 格式：oauth_client:{id}\n     * VALUE 数据类型：String 客户端信息\n     */\n    String OAUTH_CLIENT = \"oauth_client\";\n\n    /**\n     * 访问令牌的缓存\n     * <p>\n     * KEY 格式：oauth2_access_token:{token}\n     * VALUE 数据类型：String 访问令牌信息 {@link OAuth2AccessTokenDO}\n     * <p>\n     * 由于动态过期时间，使用 RedisTemplate 操作\n     */\n    String OAUTH2_ACCESS_TOKEN = \"oauth2_access_token:%s\";\n\n    /**\n     * 站内信模版的缓存\n     * <p>\n     * KEY 格式：notify_template:{code}\n     * VALUE 数据格式：String 模版信息\n     */\n    String NOTIFY_TEMPLATE = \"notify_template\";\n\n    /**\n     * 邮件账号的缓存\n     * <p>\n     * KEY 格式：mail_account:{id}\n     * VALUE 数据格式：String 账号信息\n     */\n    String MAIL_ACCOUNT = \"mail_account\";\n\n    /**\n     * 邮件模版的缓存\n     * <p>\n     * KEY 格式：mail_template:{code}\n     * VALUE 数据格式：String 模版信息\n     */\n    String MAIL_TEMPLATE = \"mail_template\";\n\n    /**\n     * 短信模版的缓存\n     * <p>\n     * KEY 格式：sms_template:{id}\n     * VALUE 数据格式：String 模版信息\n     */\n    String SMS_TEMPLATE = \"sms_template\";\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/dal/redis/oauth2/OAuth2AccessTokenRedisDAO.java",
    "content": "package co.yixiang.yshop.module.system.dal.redis.oauth2;\n\nimport cn.hutool.core.date.LocalDateTimeUtil;\nimport co.yixiang.yshop.framework.common.util.collection.CollectionUtils;\nimport co.yixiang.yshop.framework.common.util.json.JsonUtils;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;\nimport org.springframework.data.redis.core.StringRedisTemplate;\nimport org.springframework.stereotype.Repository;\n\nimport jakarta.annotation.Resource;\nimport java.time.LocalDateTime;\nimport java.time.temporal.ChronoUnit;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.concurrent.TimeUnit;\n\nimport static co.yixiang.yshop.module.system.dal.redis.RedisKeyConstants.OAUTH2_ACCESS_TOKEN;\n\n/**\n * {@link OAuth2AccessTokenDO} 的 RedisDAO\n *\n * @author yshop\n */\n@Repository\npublic class OAuth2AccessTokenRedisDAO {\n\n    @Resource\n    private StringRedisTemplate stringRedisTemplate;\n\n    public OAuth2AccessTokenDO get(String accessToken) {\n        String redisKey = formatKey(accessToken);\n        return JsonUtils.parseObject(stringRedisTemplate.opsForValue().get(redisKey), OAuth2AccessTokenDO.class);\n    }\n\n    public void set(OAuth2AccessTokenDO accessTokenDO) {\n        String redisKey = formatKey(accessTokenDO.getAccessToken());\n        // 清理多余字段，避免缓存\n        accessTokenDO.setUpdater(null).setUpdateTime(null).setCreateTime(null).setCreator(null).setDeleted(null);\n        long time = LocalDateTimeUtil.between(LocalDateTime.now(), accessTokenDO.getExpiresTime(), ChronoUnit.SECONDS);\n        if (time > 0) {\n            stringRedisTemplate.opsForValue().set(redisKey, JsonUtils.toJsonString(accessTokenDO), time, TimeUnit.SECONDS);\n        }\n    }\n\n    public void delete(String accessToken) {\n        String redisKey = formatKey(accessToken);\n        stringRedisTemplate.delete(redisKey);\n    }\n\n    public void deleteList(Collection<String> accessTokens) {\n        List<String> redisKeys = CollectionUtils.convertList(accessTokens, OAuth2AccessTokenRedisDAO::formatKey);\n        stringRedisTemplate.delete(redisKeys);\n    }\n\n    private static String formatKey(String accessToken) {\n        return String.format(OAUTH2_ACCESS_TOKEN, accessToken);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/framework/captcha/config/YshopCaptchaConfiguration.java",
    "content": "package co.yixiang.yshop.module.system.framework.captcha.config;\n\nimport co.yixiang.yshop.module.system.framework.captcha.core.RedisCaptchaServiceImpl;\nimport com.xingyuv.captcha.properties.AjCaptchaProperties;\nimport com.xingyuv.captcha.service.CaptchaCacheService;\nimport com.xingyuv.captcha.service.impl.CaptchaServiceFactory;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\nimport org.springframework.data.redis.core.StringRedisTemplate;\n\n/**\n * 验证码的配置类\n *\n * @author yshop\n */\n@Configuration(proxyBeanMethods = false)\npublic class YshopCaptchaConfiguration {\n\n    @Bean\n    public CaptchaCacheService captchaCacheService(AjCaptchaProperties config,\n                                                   StringRedisTemplate stringRedisTemplate) {\n        CaptchaCacheService captchaCacheService = CaptchaServiceFactory.getCache(config.getCacheType().name());\n        if (captchaCacheService instanceof RedisCaptchaServiceImpl) {\n            ((RedisCaptchaServiceImpl) captchaCacheService).setStringRedisTemplate(stringRedisTemplate);\n        }\n        return captchaCacheService;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/framework/captcha/core/RedisCaptchaServiceImpl.java",
    "content": "package co.yixiang.yshop.module.system.framework.captcha.core;\n\nimport com.xingyuv.captcha.service.CaptchaCacheService;\nimport lombok.Setter;\nimport org.springframework.data.redis.core.StringRedisTemplate;\n\nimport java.util.concurrent.TimeUnit;\n\n/**\n * 基于 Redis 实现验证码的存储\n *\n * @author 星语\n */\n@Setter\npublic class RedisCaptchaServiceImpl implements CaptchaCacheService {\n\n    private StringRedisTemplate stringRedisTemplate;\n\n    @Override\n    public String type() {\n        return \"redis\";\n    }\n\n    @Override\n    public void set(String key, String value, long expiresInSeconds) {\n        stringRedisTemplate.opsForValue().set(key, value, expiresInSeconds, TimeUnit.SECONDS);\n    }\n\n    @Override\n    public boolean exists(String key) {\n        return Boolean.TRUE.equals(stringRedisTemplate.hasKey(key));\n    }\n\n    @Override\n    public void delete(String key) {\n        stringRedisTemplate.delete(key);\n    }\n\n    @Override\n    public String get(String key) {\n        return stringRedisTemplate.opsForValue().get(key);\n    }\n\n    @Override\n    public Long increment(String key, long val) {\n        return stringRedisTemplate.opsForValue().increment(key,val);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/framework/datapermission/config/DataPermissionConfiguration.java",
    "content": "package co.yixiang.yshop.module.system.framework.datapermission.config;\n\nimport co.yixiang.yshop.module.system.dal.dataobject.dept.DeptDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.user.AdminUserDO;\nimport co.yixiang.yshop.framework.datapermission.core.rule.dept.DeptDataPermissionRuleCustomizer;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n/**\n * system 模块的数据权限 Configuration\n *\n * @author yshop\n */\n@Configuration(proxyBeanMethods = false)\npublic class DataPermissionConfiguration {\n\n    @Bean\n    public DeptDataPermissionRuleCustomizer sysDeptDataPermissionRuleCustomizer() {\n        return rule -> {\n            // dept\n            rule.addDeptColumn(AdminUserDO.class);\n            rule.addDeptColumn(DeptDO.class, \"id\");\n            // user\n            rule.addUserColumn(AdminUserDO.class, \"id\");\n        };\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/framework/operatelog/core/AdminUserParseFunction.java",
    "content": "package co.yixiang.yshop.module.system.framework.operatelog.core;\n\nimport cn.hutool.core.convert.Convert;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.module.system.dal.dataobject.user.AdminUserDO;\nimport co.yixiang.yshop.module.system.service.user.AdminUserService;\nimport com.mzt.logapi.service.IParseFunction;\nimport jakarta.annotation.Resource;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Component;\n\n/**\n * 管理员名字的 {@link IParseFunction} 实现类\n *\n * @author HUIHUI\n */\n@Slf4j\n@Component\npublic class AdminUserParseFunction implements IParseFunction {\n\n    public static final String NAME = \"getAdminUserById\";\n\n    @Resource\n    private AdminUserService adminUserService;\n\n    @Override\n    public String functionName() {\n        return NAME;\n    }\n\n    @Override\n    public String apply(Object value) {\n        if (StrUtil.isEmptyIfStr(value)) {\n            return \"\";\n        }\n\n        // 获取用户信息\n        AdminUserDO user = adminUserService.getUser(Convert.toLong(value));\n        if (user == null) {\n            log.warn(\"[apply][获取用户{{}}为空\", value);\n            return \"\";\n        }\n        // 返回格式 yshop(13888888888)\n        String nickname = user.getNickname();\n        if (StrUtil.isEmpty(user.getMobile())) {\n            return nickname;\n        }\n        return StrUtil.format(\"{}({})\", nickname, user.getMobile());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/framework/operatelog/core/AreaParseFunction.java",
    "content": "package co.yixiang.yshop.module.system.framework.operatelog.core;\n\nimport cn.hutool.core.convert.Convert;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.ip.core.utils.AreaUtils;\nimport com.mzt.logapi.service.IParseFunction;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Component;\n\n/**\n * 地名的 {@link IParseFunction} 实现类\n *\n * @author HUIHUI\n */\n@Slf4j\n@Component\npublic class AreaParseFunction implements IParseFunction {\n\n    public static final String NAME = \"getArea\";\n\n    @Override\n    public boolean executeBefore() {\n        return true; // 先转换值后对比\n    }\n\n    @Override\n    public String functionName() {\n        return NAME;\n    }\n\n    @Override\n    public String apply(Object value) {\n        if (StrUtil.isEmptyIfStr(value)) {\n            return \"\";\n        }\n        return AreaUtils.format(Convert.toInt(value));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/framework/operatelog/core/BooleanParseFunction.java",
    "content": "package co.yixiang.yshop.module.system.framework.operatelog.core;\n\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.dict.core.DictFrameworkUtils;\nimport co.yixiang.yshop.module.infra.enums.DictTypeConstants;\nimport com.mzt.logapi.service.IParseFunction;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Component;\n\n/**\n * 是否类型的 {@link IParseFunction} 实现类\n *\n * @author HUIHUI\n */\n@Component\n@Slf4j\npublic class BooleanParseFunction implements IParseFunction {\n\n    public static final String NAME = \"getBoolean\";\n\n    @Override\n    public boolean executeBefore() {\n        return true; // 先转换值后对比\n    }\n\n    @Override\n    public String functionName() {\n        return NAME;\n    }\n\n    @Override\n    public String apply(Object value) {\n        if (StrUtil.isEmptyIfStr(value)) {\n            return \"\";\n        }\n        return DictFrameworkUtils.getDictDataLabel(DictTypeConstants.BOOLEAN_STRING, value.toString());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/framework/operatelog/core/DeptParseFunction.java",
    "content": "package co.yixiang.yshop.module.system.framework.operatelog.core;\n\nimport cn.hutool.core.convert.Convert;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.module.system.dal.dataobject.dept.DeptDO;\nimport co.yixiang.yshop.module.system.service.dept.DeptService;\nimport com.mzt.logapi.service.IParseFunction;\nimport jakarta.annotation.Resource;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Component;\n\n/**\n * 部门名字的 {@link IParseFunction} 实现类\n *\n * @author HUIHUI\n */\n@Slf4j\n@Component\npublic class DeptParseFunction implements IParseFunction {\n\n    public static final String NAME = \"getDeptById\";\n\n    @Resource\n    private DeptService deptService;\n\n    @Override\n    public String functionName() {\n        return NAME;\n    }\n\n    @Override\n    public String apply(Object value) {\n        if (StrUtil.isEmptyIfStr(value)) {\n            return \"\";\n        }\n\n        // 获取部门信息\n        DeptDO dept = deptService.getDept(Convert.toLong(value));\n        if (dept == null) {\n            log.warn(\"[apply][获取部门{{}}为空\", value);\n            return \"\";\n        }\n        return dept.getName();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/framework/operatelog/core/PostParseFunction.java",
    "content": "package co.yixiang.yshop.module.system.framework.operatelog.core;\n\nimport cn.hutool.core.convert.Convert;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.module.system.dal.dataobject.dept.PostDO;\nimport co.yixiang.yshop.module.system.service.dept.PostService;\nimport com.mzt.logapi.service.IParseFunction;\nimport jakarta.annotation.Resource;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Component;\n\n/**\n * 岗位名字的 {@link IParseFunction} 实现类\n *\n * @author HUIHUI\n */\n@Slf4j\n@Component\npublic class PostParseFunction implements IParseFunction {\n\n    public static final String NAME = \"getPostById\";\n\n    @Resource\n    private PostService postService;\n\n    @Override\n    public String functionName() {\n        return NAME;\n    }\n\n    @Override\n    public String apply(Object value) {\n        if (StrUtil.isEmptyIfStr(value)) {\n            return \"\";\n        }\n\n        // 获取岗位信息\n        PostDO post = postService.getPost(Convert.toLong(value));\n        if (post == null) {\n            log.warn(\"[apply][获取岗位{{}}为空\", value);\n            return \"\";\n        }\n        return post.getName();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/framework/operatelog/core/SexParseFunction.java",
    "content": "package co.yixiang.yshop.module.system.framework.operatelog.core;\n\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.dict.core.DictFrameworkUtils;\nimport co.yixiang.yshop.module.system.enums.DictTypeConstants;\nimport com.mzt.logapi.service.IParseFunction;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Component;\n\n/**\n * 行业的 {@link IParseFunction} 实现类\n *\n * @author HUIHUI\n */\n@Component\n@Slf4j\npublic class SexParseFunction implements IParseFunction {\n\n    public static final String NAME = \"getSex\";\n\n    @Override\n    public boolean executeBefore() {\n        return true; // 先转换值后对比\n    }\n\n    @Override\n    public String functionName() {\n        return NAME;\n    }\n\n    @Override\n    public String apply(Object value) {\n        if (StrUtil.isEmptyIfStr(value)) {\n            return \"\";\n        }\n        return DictFrameworkUtils.getDictDataLabel(DictTypeConstants.USER_SEX, value.toString());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/framework/sms/config/SmsCodeProperties.java",
    "content": "package co.yixiang.yshop.module.system.framework.sms.config;\n\nimport lombok.Data;\nimport org.springframework.boot.context.properties.ConfigurationProperties;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.validation.constraints.NotNull;\nimport java.time.Duration;\n\n@ConfigurationProperties(prefix = \"yshop.sms-code\")\n@Validated\n@Data\npublic class SmsCodeProperties {\n\n    /**\n     * 过期时间\n     */\n    @NotNull(message = \"过期时间不能为空\")\n    private Duration expireTimes;\n    /**\n     * 短信发送频率\n     */\n    @NotNull(message = \"短信发送频率不能为空\")\n    private Duration sendFrequency;\n    /**\n     * 每日发送最大数量\n     */\n    @NotNull(message = \"每日发送最大数量不能为空\")\n    private Integer sendMaximumQuantityPerDay;\n    /**\n     * 验证码最小值\n     */\n    @NotNull(message = \"验证码最小值不能为空\")\n    private Integer beginCode;\n    /**\n     * 验证码最大值\n     */\n    @NotNull(message = \"验证码最大值不能为空\")\n    private Integer endCode;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/framework/sms/config/SmsConfiguration.java",
    "content": "package co.yixiang.yshop.module.system.framework.sms.config;\n\nimport co.yixiang.yshop.module.system.framework.sms.core.client.SmsClientFactory;\nimport co.yixiang.yshop.module.system.framework.sms.core.client.impl.SmsClientFactoryImpl;\nimport org.springframework.boot.context.properties.EnableConfigurationProperties;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n/**\n * 短信配置类，包括短信客户端、短信验证码两部分\n *\n * @author yshop\n */\n@Configuration(proxyBeanMethods = false)\n@EnableConfigurationProperties(SmsCodeProperties.class)\npublic class SmsConfiguration {\n\n    @Bean\n    public SmsClientFactory smsClientFactory() {\n        return new SmsClientFactoryImpl();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/framework/sms/core/client/SmsClient.java",
    "content": "package co.yixiang.yshop.module.system.framework.sms.core.client;\n\nimport co.yixiang.yshop.framework.common.core.KeyValue;\nimport co.yixiang.yshop.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;\nimport co.yixiang.yshop.module.system.framework.sms.core.client.dto.SmsSendRespDTO;\nimport co.yixiang.yshop.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO;\n\nimport java.util.List;\n\n/**\n * 短信客户端，用于对接各短信平台的 SDK，实现短信发送等功能\n *\n * @author zzf\n * @since 2021/1/25 14:14\n */\npublic interface SmsClient {\n\n    /**\n     * 获得渠道编号\n     *\n     * @return 渠道编号\n     */\n    Long getId();\n\n    /**\n     * 发送消息\n     *\n     * @param logId 日志编号\n     * @param mobile 手机号\n     * @param apiTemplateId 短信 API 的模板编号\n     * @param templateParams 短信模板参数。通过 List 数组，保证参数的顺序\n     * @return 短信发送结果\n     */\n    SmsSendRespDTO sendSms(Long logId, String mobile, String apiTemplateId,\n                           List<KeyValue<String, Object>> templateParams) throws Throwable;\n\n    /**\n     * 解析接收短信的接收结果\n     *\n     * @param text 结果\n     * @return 结果内容\n     * @throws Throwable 当解析 text 发生异常时，则会抛出异常\n     */\n    List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) throws Throwable;\n\n    /**\n     * 查询指定的短信模板\n     *\n     * @param apiTemplateId 短信 API 的模板编号\n     * @return 短信模板\n     */\n    SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/framework/sms/core/client/SmsClientFactory.java",
    "content": "package co.yixiang.yshop.module.system.framework.sms.core.client;\n\nimport co.yixiang.yshop.module.system.framework.sms.core.property.SmsChannelProperties;\n\n/**\n * 短信客户端的工厂接口\n *\n * @author zzf\n * @since 2021/1/28 14:01\n */\npublic interface SmsClientFactory {\n\n    /**\n     * 获得短信 Client\n     *\n     * @param channelId 渠道编号\n     * @return 短信 Client\n     */\n    SmsClient getSmsClient(Long channelId);\n\n    /**\n     * 获得短信 Client\n     *\n     * @param channelCode 渠道编码\n     * @return 短信 Client\n     */\n    SmsClient getSmsClient(String channelCode);\n\n    /**\n     * 创建短信 Client\n     *\n     * @param properties 配置对象\n     */\n    void createOrUpdateSmsClient(SmsChannelProperties properties);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/framework/sms/core/client/dto/SmsReceiveRespDTO.java",
    "content": "package co.yixiang.yshop.module.system.framework.sms.core.client.dto;\n\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\n\n/**\n * 消息接收 Response DTO\n *\n * @author yshop\n */\n@Data\npublic class SmsReceiveRespDTO {\n\n    /**\n     * 是否接收成功\n     */\n    private Boolean success;\n    /**\n     * API 接收结果的编码\n     */\n    private String errorCode;\n    /**\n     * API 接收结果的说明\n     */\n    private String errorMsg;\n\n    /**\n     * 手机号\n     */\n    private String mobile;\n    /**\n     * 用户接收时间\n     */\n    private LocalDateTime receiveTime;\n\n    /**\n     * 短信 API 发送返回的序号\n     */\n    private String serialNo;\n    /**\n     * 短信日志编号\n     *\n     * 对应 SysSmsLogDO 的编号\n     */\n    private Long logId;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/framework/sms/core/client/dto/SmsSendRespDTO.java",
    "content": "package co.yixiang.yshop.module.system.framework.sms.core.client.dto;\n\nimport lombok.Data;\n\n/**\n * 短信发送 Response DTO\n *\n * @author yshop\n */\n@Data\npublic class SmsSendRespDTO {\n\n    /**\n     * 是否成功\n     */\n    private Boolean success;\n\n    /**\n     * API 请求编号\n     */\n    private String apiRequestId;\n\n    // ==================== 成功时字段 ====================\n\n    /**\n     * 短信 API 发送返回的序号\n     */\n    private String serialNo;\n\n    // ==================== 失败时字段 ====================\n\n    /**\n     * API 返回错误码\n     *\n     * 由于第三方的错误码可能是字符串，所以使用 String 类型\n     */\n    private String apiCode;\n    /**\n     * API 返回提示\n     */\n    private String apiMsg;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/framework/sms/core/client/dto/SmsTemplateRespDTO.java",
    "content": "package co.yixiang.yshop.module.system.framework.sms.core.client.dto;\n\nimport co.yixiang.yshop.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;\nimport lombok.Data;\n\n/**\n * 短信模板 Response DTO\n *\n * @author yshop\n */\n@Data\npublic class SmsTemplateRespDTO {\n\n    /**\n     * 模板编号\n     */\n    private String id;\n    /**\n     * 短信内容\n     */\n    private String content;\n    /**\n     * 审核状态\n     *\n     * 枚举 {@link SmsTemplateAuditStatusEnum}\n     */\n    private Integer auditStatus;\n    /**\n     * 审核未通过的理由\n     */\n    private String auditReason;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/framework/sms/core/client/impl/AbstractSmsClient.java",
    "content": "package co.yixiang.yshop.module.system.framework.sms.core.client.impl;\n\nimport co.yixiang.yshop.module.system.framework.sms.core.client.SmsClient;\nimport co.yixiang.yshop.module.system.framework.sms.core.property.SmsChannelProperties;\nimport lombok.extern.slf4j.Slf4j;\n\n/**\n * 短信客户端的抽象类，提供模板方法，减少子类的冗余代码\n *\n * @author zzf\n * @since 2021/2/1 9:28\n */\n@Slf4j\npublic abstract class AbstractSmsClient implements SmsClient {\n\n    /**\n     * 短信渠道配置\n     */\n    protected volatile SmsChannelProperties properties;\n\n    public AbstractSmsClient(SmsChannelProperties properties) {\n        this.properties = properties;\n    }\n\n    /**\n     * 初始化\n     */\n    public final void init() {\n        doInit();\n        log.debug(\"[init][配置({}) 初始化完成]\", properties);\n    }\n\n    /**\n     * 自定义初始化\n     */\n    protected abstract void doInit();\n\n    public final void refresh(SmsChannelProperties properties) {\n        // 判断是否更新\n        if (properties.equals(this.properties)) {\n            return;\n        }\n        log.info(\"[refresh][配置({})发生变化，重新初始化]\", properties);\n        this.properties = properties;\n        // 初始化\n        this.init();\n    }\n\n    @Override\n    public Long getId() {\n        return properties.getId();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/framework/sms/core/client/impl/AliyunSmsClient.java",
    "content": "package co.yixiang.yshop.module.system.framework.sms.core.client.impl;\n\nimport cn.hutool.core.lang.Assert;\nimport co.yixiang.yshop.framework.common.core.KeyValue;\nimport co.yixiang.yshop.framework.common.util.collection.MapUtils;\nimport co.yixiang.yshop.framework.common.util.json.JsonUtils;\nimport co.yixiang.yshop.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;\nimport co.yixiang.yshop.module.system.framework.sms.core.client.dto.SmsSendRespDTO;\nimport co.yixiang.yshop.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO;\nimport co.yixiang.yshop.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;\nimport co.yixiang.yshop.module.system.framework.sms.core.property.SmsChannelProperties;\nimport com.aliyuncs.DefaultAcsClient;\nimport com.aliyuncs.IAcsClient;\nimport com.aliyuncs.dysmsapi.model.v20170525.QuerySmsTemplateRequest;\nimport com.aliyuncs.dysmsapi.model.v20170525.QuerySmsTemplateResponse;\nimport com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;\nimport com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;\nimport com.aliyuncs.profile.DefaultProfile;\nimport com.aliyuncs.profile.IClientProfile;\nimport com.fasterxml.jackson.annotation.JsonFormat;\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport com.google.common.annotations.VisibleForTesting;\nimport lombok.Data;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.time.LocalDateTime;\nimport java.util.List;\nimport java.util.Objects;\n\nimport static co.yixiang.yshop.framework.common.util.collection.CollectionUtils.convertList;\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT;\n\n/**\n * 阿里短信客户端的实现类\n *\n * @author zzf\n * @since 2021/1/25 14:17\n */\n@Slf4j\npublic class AliyunSmsClient extends AbstractSmsClient {\n\n    /**\n     * 调用成功 code\n     */\n    public static final String API_CODE_SUCCESS = \"OK\";\n\n    /**\n     * REGION, 使用杭州\n     */\n    private static final String ENDPOINT = \"cn-hangzhou\";\n\n    /**\n     * 阿里云客户端\n     */\n    private volatile IAcsClient client;\n\n    public AliyunSmsClient(SmsChannelProperties properties) {\n        super(properties);\n        Assert.notEmpty(properties.getApiKey(), \"apiKey 不能为空\");\n        Assert.notEmpty(properties.getApiSecret(), \"apiSecret 不能为空\");\n    }\n\n    @Override\n    protected void doInit() {\n        IClientProfile profile = DefaultProfile.getProfile(ENDPOINT, properties.getApiKey(), properties.getApiSecret());\n        client = new DefaultAcsClient(profile);\n    }\n\n    @Override\n    public SmsSendRespDTO sendSms(Long sendLogId, String mobile, String apiTemplateId,\n                                  List<KeyValue<String, Object>> templateParams) throws Throwable {\n        // 构建请求\n        SendSmsRequest request = new SendSmsRequest();\n        request.setPhoneNumbers(mobile);\n        request.setSignName(properties.getSignature());\n        request.setTemplateCode(apiTemplateId);\n        request.setTemplateParam(JsonUtils.toJsonString(MapUtils.convertMap(templateParams)));\n        request.setOutId(String.valueOf(sendLogId));\n        // 执行请求\n        SendSmsResponse response = client.getAcsResponse(request);\n        return new SmsSendRespDTO().setSuccess(Objects.equals(response.getCode(), API_CODE_SUCCESS)).setSerialNo(response.getBizId())\n                .setApiRequestId(response.getRequestId()).setApiCode(response.getCode()).setApiMsg(response.getMessage());\n    }\n\n    @Override\n    public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) {\n        List<SmsReceiveStatus> statuses = JsonUtils.parseArray(text, SmsReceiveStatus.class);\n        return convertList(statuses, status -> new SmsReceiveRespDTO().setSuccess(status.getSuccess())\n                .setErrorCode(status.getErrCode()).setErrorMsg(status.getErrMsg())\n                .setMobile(status.getPhoneNumber()).setReceiveTime(status.getReportTime())\n                .setSerialNo(status.getBizId()).setLogId(Long.valueOf(status.getOutId())));\n    }\n\n    @Override\n    public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable {\n        // 构建请求\n        QuerySmsTemplateRequest request = new QuerySmsTemplateRequest();\n        request.setTemplateCode(apiTemplateId);\n        // 执行请求\n        QuerySmsTemplateResponse response = client.getAcsResponse(request);\n        if (response.getTemplateStatus() == null) {\n            return null;\n        }\n        return new SmsTemplateRespDTO().setId(response.getTemplateCode()).setContent(response.getTemplateContent())\n                .setAuditStatus(convertSmsTemplateAuditStatus(response.getTemplateStatus())).setAuditReason(response.getReason());\n    }\n\n    @VisibleForTesting\n    Integer convertSmsTemplateAuditStatus(Integer templateStatus) {\n        switch (templateStatus) {\n            case 0: return SmsTemplateAuditStatusEnum.CHECKING.getStatus();\n            case 1: return SmsTemplateAuditStatusEnum.SUCCESS.getStatus();\n            case 2: return SmsTemplateAuditStatusEnum.FAIL.getStatus();\n            default: throw new IllegalArgumentException(String.format(\"未知审核状态(%d)\", templateStatus));\n        }\n    }\n\n    /**\n     * 短信接收状态\n     *\n     * 参见 <a href=\"https://help.aliyun.com/document_detail/101867.html\">文档</a>\n     *\n     * @author yshop\n     */\n    @Data\n    public static class SmsReceiveStatus {\n\n        /**\n         * 手机号\n         */\n        @JsonProperty(\"phone_number\")\n        private String phoneNumber;\n        /**\n         * 发送时间\n         */\n        @JsonProperty(\"send_time\")\n        @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)\n        private LocalDateTime sendTime;\n        /**\n         * 状态报告时间\n         */\n        @JsonProperty(\"report_time\")\n        @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)\n        private LocalDateTime reportTime;\n        /**\n         * 是否接收成功\n         */\n        private Boolean success;\n        /**\n         * 状态报告说明\n         */\n        @JsonProperty(\"err_msg\")\n        private String errMsg;\n        /**\n         * 状态报告编码\n         */\n        @JsonProperty(\"err_code\")\n        private String errCode;\n        /**\n         * 发送序列号\n         */\n        @JsonProperty(\"biz_id\")\n        private String bizId;\n        /**\n         * 用户序列号\n         *\n         * 这里我们传递的是 SysSmsLogDO 的日志编号\n         */\n        @JsonProperty(\"out_id\")\n        private String outId;\n        /**\n         * 短信长度，例如说 1、2、3\n         *\n         * 140 字节算一条短信，短信长度超过 140 字节时会拆分成多条短信发送\n         */\n        @JsonProperty(\"sms_size\")\n        private Integer smsSize;\n\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/framework/sms/core/client/impl/DebugDingTalkSmsClient.java",
    "content": "package co.yixiang.yshop.module.system.framework.sms.core.client.impl;\n\nimport cn.hutool.core.codec.Base64;\nimport cn.hutool.core.lang.Assert;\nimport cn.hutool.core.map.MapUtil;\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.crypto.digest.DigestUtil;\nimport cn.hutool.crypto.digest.HmacAlgorithm;\nimport cn.hutool.http.HttpUtil;\nimport co.yixiang.yshop.framework.common.core.KeyValue;\nimport co.yixiang.yshop.framework.common.util.collection.MapUtils;\nimport co.yixiang.yshop.framework.common.util.json.JsonUtils;\nimport co.yixiang.yshop.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;\nimport co.yixiang.yshop.module.system.framework.sms.core.client.dto.SmsSendRespDTO;\nimport co.yixiang.yshop.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO;\nimport co.yixiang.yshop.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;\nimport co.yixiang.yshop.module.system.framework.sms.core.property.SmsChannelProperties;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\n\n/**\n * 基于钉钉 WebHook 实现的调试的短信客户端实现类\n *\n * 考虑到省钱，我们使用钉钉 WebHook 模拟发送短信，方便调试。\n *\n * @author yshop\n */\npublic class DebugDingTalkSmsClient extends AbstractSmsClient {\n\n    public DebugDingTalkSmsClient(SmsChannelProperties properties) {\n        super(properties);\n        Assert.notEmpty(properties.getApiKey(), \"apiKey 不能为空\");\n        Assert.notEmpty(properties.getApiSecret(), \"apiSecret 不能为空\");\n    }\n\n    @Override\n    protected void doInit() {\n    }\n\n    @Override\n    public SmsSendRespDTO sendSms(Long sendLogId, String mobile,\n                                  String apiTemplateId, List<KeyValue<String, Object>> templateParams) throws Throwable {\n        // 构建请求\n        String url = buildUrl(\"robot/send\");\n        Map<String, Object> params = new HashMap<>();\n        params.put(\"msgtype\", \"text\");\n        String content = String.format(\"【模拟短信】\\n手机号：%s\\n短信日志编号：%d\\n模板参数：%s\",\n                mobile, sendLogId, MapUtils.convertMap(templateParams));\n        params.put(\"text\", MapUtil.builder().put(\"content\", content).build());\n        // 执行请求\n        String responseText = HttpUtil.post(url, JsonUtils.toJsonString(params));\n        // 解析结果\n        Map<?, ?> responseObj = JsonUtils.parseObject(responseText, Map.class);\n        String errorCode = MapUtil.getStr(responseObj, \"errcode\");\n        return new SmsSendRespDTO().setSuccess(Objects.equals(errorCode, \"0\")).setSerialNo(StrUtil.uuid())\n                .setApiCode(errorCode).setApiMsg(MapUtil.getStr(responseObj, \"errorMsg\"));\n    }\n\n    /**\n     * 构建请求地址\n     *\n     * 参见 <a href=\"https://developers.dingtalk.com/document/app/custom-robot-access/title-nfv-794-g71\">文档</a>\n     *\n     * @param path 请求路径\n     * @return 请求地址\n     */\n    @SuppressWarnings(\"SameParameterValue\")\n    private String buildUrl(String path) {\n        // 生成 timestamp\n        long timestamp = System.currentTimeMillis();\n        // 生成 sign\n        String secret = properties.getApiSecret();\n        String stringToSign = timestamp + \"\\n\" + secret;\n        byte[] signData = DigestUtil.hmac(HmacAlgorithm.HmacSHA256, StrUtil.bytes(secret)).digest(stringToSign);\n        String sign = Base64.encode(signData);\n        // 构建最终 URL\n        return String.format(\"https://oapi.dingtalk.com/%s?access_token=%s&timestamp=%d&sign=%s\",\n                path, properties.getApiKey(), timestamp, sign);\n    }\n\n    @Override\n    public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) {\n        throw new UnsupportedOperationException(\"模拟短信客户端，暂时无需解析回调\");\n    }\n\n    @Override\n    public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) {\n        return new SmsTemplateRespDTO().setId(apiTemplateId).setContent(\"\")\n                .setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus()).setAuditReason(\"\");\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/framework/sms/core/client/impl/SmsClientFactoryImpl.java",
    "content": "package co.yixiang.yshop.module.system.framework.sms.core.client.impl;\n\nimport co.yixiang.yshop.module.system.framework.sms.core.client.SmsClient;\nimport co.yixiang.yshop.module.system.framework.sms.core.client.SmsClientFactory;\nimport co.yixiang.yshop.module.system.framework.sms.core.enums.SmsChannelEnum;\nimport co.yixiang.yshop.module.system.framework.sms.core.property.SmsChannelProperties;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.util.Assert;\nimport org.springframework.validation.annotation.Validated;\n\nimport java.util.Arrays;\nimport java.util.concurrent.ConcurrentHashMap;\nimport java.util.concurrent.ConcurrentMap;\n\n/**\n * 短信客户端工厂接口\n *\n * @author zzf\n */\n@Validated\n@Slf4j\npublic class SmsClientFactoryImpl implements SmsClientFactory {\n\n    /**\n     * 短信客户端 Map\n     * key：渠道编号，使用 {@link SmsChannelProperties#getId()}\n     */\n    private final ConcurrentMap<Long, AbstractSmsClient> channelIdClients = new ConcurrentHashMap<>();\n\n    /**\n     * 短信客户端 Map\n     * key：渠道编码，使用 {@link SmsChannelProperties#getCode()} ()}\n     *\n     * 注意，一些场景下，需要获得某个渠道类型的客户端，所以需要使用它。\n     * 例如说，解析短信接收结果，是相对通用的，不需要使用某个渠道编号的 {@link #channelIdClients}\n     */\n    private final ConcurrentMap<String, AbstractSmsClient> channelCodeClients = new ConcurrentHashMap<>();\n\n    public SmsClientFactoryImpl() {\n        // 初始化 channelCodeClients 集合\n        Arrays.stream(SmsChannelEnum.values()).forEach(channel -> {\n            // 创建一个空的 SmsChannelProperties 对象\n            SmsChannelProperties properties = new SmsChannelProperties().setCode(channel.getCode())\n                    .setApiKey(\"default default\").setApiSecret(\"default\");\n            // 创建 Sms 客户端\n            AbstractSmsClient smsClient = createSmsClient(properties);\n            channelCodeClients.put(channel.getCode(), smsClient);\n        });\n    }\n\n    @Override\n    public SmsClient getSmsClient(Long channelId) {\n        return channelIdClients.get(channelId);\n    }\n\n    @Override\n    public SmsClient getSmsClient(String channelCode) {\n        return channelCodeClients.get(channelCode);\n    }\n\n    @Override\n    public void createOrUpdateSmsClient(SmsChannelProperties properties) {\n        AbstractSmsClient client = channelIdClients.get(properties.getId());\n        if (client == null) {\n            client = this.createSmsClient(properties);\n            client.init();\n            channelIdClients.put(client.getId(), client);\n        } else {\n            client.refresh(properties);\n        }\n    }\n\n    private AbstractSmsClient createSmsClient(SmsChannelProperties properties) {\n        SmsChannelEnum channelEnum = SmsChannelEnum.getByCode(properties.getCode());\n        Assert.notNull(channelEnum, String.format(\"渠道类型(%s) 为空\", channelEnum));\n        // 创建客户端\n        switch (channelEnum) {\n            case ALIYUN: return new AliyunSmsClient(properties);\n            case DEBUG_DING_TALK: return new DebugDingTalkSmsClient(properties);\n            case TENCENT: return new TencentSmsClient(properties);\n        }\n        // 创建失败，错误日志 + 抛出异常\n        log.error(\"[createSmsClient][配置({}) 找不到合适的客户端实现]\", properties);\n        throw new IllegalArgumentException(String.format(\"配置(%s) 找不到合适的客户端实现\", properties));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/framework/sms/core/client/impl/TencentSmsClient.java",
    "content": "package co.yixiang.yshop.module.system.framework.sms.core.client.impl;\n\nimport cn.hutool.core.lang.Assert;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.core.KeyValue;\nimport co.yixiang.yshop.framework.common.util.collection.ArrayUtils;\nimport co.yixiang.yshop.framework.common.util.json.JsonUtils;\nimport co.yixiang.yshop.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;\nimport co.yixiang.yshop.module.system.framework.sms.core.client.dto.SmsSendRespDTO;\nimport co.yixiang.yshop.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO;\nimport co.yixiang.yshop.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;\nimport co.yixiang.yshop.module.system.framework.sms.core.property.SmsChannelProperties;\nimport com.fasterxml.jackson.annotation.JsonFormat;\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.tencentcloudapi.common.Credential;\nimport com.tencentcloudapi.sms.v20210111.SmsClient;\nimport com.tencentcloudapi.sms.v20210111.models.*;\nimport lombok.Data;\n\nimport java.time.LocalDateTime;\nimport java.util.List;\nimport java.util.Objects;\n\nimport static co.yixiang.yshop.framework.common.util.collection.CollectionUtils.convertList;\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.TIME_ZONE_DEFAULT;\n\n/**\n * 腾讯云短信功能实现\n *\n * 参见 <a href=\"https://cloud.tencent.com/document/product/382/52077\">文档</a>\n *\n * @author shiwp\n */\npublic class TencentSmsClient extends AbstractSmsClient {\n\n    /**\n     * 调用成功 code\n     */\n    public static final String API_CODE_SUCCESS = \"Ok\";\n\n    /**\n     * REGION，使用南京\n     */\n    private static final String ENDPOINT = \"ap-nanjing\";\n\n    /**\n     * 是否国际/港澳台短信：\n     *\n     * 0：表示国内短信。\n     * 1：表示国际/港澳台短信。\n     */\n    private static final long INTERNATIONAL_CHINA = 0L;\n\n    private SmsClient client;\n\n    public TencentSmsClient(SmsChannelProperties properties) {\n        super(properties);\n        Assert.notEmpty(properties.getApiSecret(), \"apiSecret 不能为空\");\n        validateSdkAppId(properties);\n    }\n\n    @Override\n    protected void doInit() {\n        // 实例化一个认证对象，入参需要传入腾讯云账户密钥对 secretId，secretKey\n        Credential credential = new Credential(getApiKey(), properties.getApiSecret());\n        client = new SmsClient(credential, ENDPOINT);\n    }\n\n    /**\n     * 参数校验腾讯云的 SDK AppId\n     *\n     * 原因是：腾讯云发放短信的时候，需要额外的参数 sdkAppId\n     *\n     * 解决方案：考虑到不破坏原有的 apiKey + apiSecret 的结构，所以将 secretId 拼接到 apiKey 字段中，格式为 \"secretId sdkAppId\"。\n     *\n     * @param properties 配置\n     */\n    private static void validateSdkAppId(SmsChannelProperties properties) {\n        String combineKey = properties.getApiKey();\n        Assert.notEmpty(combineKey, \"apiKey 不能为空\");\n        String[] keys = combineKey.trim().split(\" \");\n        Assert.isTrue(keys.length == 2, \"腾讯云短信 apiKey 配置格式错误，请配置 为[secretId sdkAppId]\");\n    }\n\n    private String getSdkAppId() {\n        return StrUtil.subAfter(properties.getApiKey(), \" \", true);\n    }\n\n    private String getApiKey() {\n        return StrUtil.subBefore(properties.getApiKey(), \" \", true);\n    }\n\n    @Override\n    public SmsSendRespDTO sendSms(Long sendLogId, String mobile,\n                                  String apiTemplateId, List<KeyValue<String, Object>> templateParams) throws Throwable {\n        // 构建请求\n        SendSmsRequest request = new SendSmsRequest();\n        request.setSmsSdkAppId(getSdkAppId());\n        request.setPhoneNumberSet(new String[]{mobile});\n        request.setSignName(properties.getSignature());\n        request.setTemplateId(apiTemplateId);\n        request.setTemplateParamSet(ArrayUtils.toArray(templateParams, e -> String.valueOf(e.getValue())));\n        request.setSessionContext(JsonUtils.toJsonString(new SessionContext().setLogId(sendLogId)));\n        // 执行请求\n        SendSmsResponse response = client.SendSms(request);\n        SendStatus status = response.getSendStatusSet()[0];\n        return new SmsSendRespDTO().setSuccess(Objects.equals(status.getCode(), API_CODE_SUCCESS)).setSerialNo(status.getSerialNo())\n                .setApiRequestId(response.getRequestId()).setApiCode(status.getCode()).setApiMsg(status.getMessage());\n    }\n\n    @Override\n    public List<SmsReceiveRespDTO> parseSmsReceiveStatus(String text) {\n        List<SmsReceiveStatus> callback = JsonUtils.parseArray(text, SmsReceiveStatus.class);\n        return convertList(callback, status -> new SmsReceiveRespDTO()\n                .setSuccess(SmsReceiveStatus.SUCCESS_CODE.equalsIgnoreCase(status.getStatus()))\n                .setErrorCode(status.getErrCode()).setErrorMsg(status.getDescription())\n                .setMobile(status.getMobile()).setReceiveTime(status.getReceiveTime())\n                .setSerialNo(status.getSerialNo()).setLogId(status.getSessionContext().getLogId()));\n    }\n\n    @Override\n    public SmsTemplateRespDTO getSmsTemplate(String apiTemplateId) throws Throwable {\n        // 构建请求\n        DescribeSmsTemplateListRequest request = new DescribeSmsTemplateListRequest();\n        request.setTemplateIdSet(new Long[]{Long.parseLong(apiTemplateId)});\n        request.setInternational(INTERNATIONAL_CHINA);\n        // 执行请求\n        DescribeSmsTemplateListResponse response = client.DescribeSmsTemplateList(request);\n        DescribeTemplateListStatus status = response.getDescribeTemplateStatusSet()[0];\n        if (status == null || status.getStatusCode() == null) {\n            return null;\n        }\n        return new SmsTemplateRespDTO().setId(status.getTemplateId().toString()).setContent(status.getTemplateContent())\n                .setAuditStatus(convertSmsTemplateAuditStatus(status.getStatusCode().intValue())).setAuditReason(status.getReviewReply());\n    }\n\n    @VisibleForTesting\n    Integer convertSmsTemplateAuditStatus(int templateStatus) {\n        switch (templateStatus) {\n            case 1: return SmsTemplateAuditStatusEnum.CHECKING.getStatus();\n            case 0: return SmsTemplateAuditStatusEnum.SUCCESS.getStatus();\n            case -1: return SmsTemplateAuditStatusEnum.FAIL.getStatus();\n            default: throw new IllegalArgumentException(String.format(\"未知审核状态(%d)\", templateStatus));\n        }\n    }\n\n    @Data\n    private static class SmsReceiveStatus {\n\n        /**\n         * 短信接受成功 code\n         */\n        public static final String SUCCESS_CODE = \"SUCCESS\";\n\n        /**\n         * 用户实际接收到短信的时间\n         */\n        @JsonProperty(\"user_receive_time\")\n        @JsonFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND, timezone = TIME_ZONE_DEFAULT)\n        private LocalDateTime receiveTime;\n\n        /**\n         * 国家（或地区）码\n         */\n        @JsonProperty(\"nationcode\")\n        private String nationCode;\n\n        /**\n         * 手机号码\n         */\n        private String mobile;\n\n        /**\n         * 实际是否收到短信接收状态，SUCCESS（成功）、FAIL（失败）\n         */\n        @JsonProperty(\"report_status\")\n        private String status;\n\n        /**\n         * 用户接收短信状态码错误信息\n         */\n        @JsonProperty(\"errmsg\")\n        private String errCode;\n\n        /**\n         * 用户接收短信状态描述\n         */\n        @JsonProperty(\"description\")\n        private String description;\n\n        /**\n         * 本次发送标识 ID（与发送接口返回的SerialNo对应）\n         */\n        @JsonProperty(\"sid\")\n        private String serialNo;\n\n        /**\n         * 用户的 session 内容（与发送接口的请求参数 SessionContext 一致）\n         */\n        @JsonProperty(\"ext\")\n        private SessionContext sessionContext;\n\n    }\n\n    @VisibleForTesting\n    @Data\n    static class SessionContext {\n\n        /**\n         * 发送短信记录id\n         */\n        private Long logId;\n\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/framework/sms/core/enums/SmsChannelEnum.java",
    "content": "package co.yixiang.yshop.module.system.framework.sms.core.enums;\n\nimport cn.hutool.core.util.ArrayUtil;\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * 短信渠道枚举\n *\n * @author zzf\n * @since 2021/1/25 10:56\n */\n@Getter\n@AllArgsConstructor\npublic enum SmsChannelEnum {\n\n    DEBUG_DING_TALK(\"DEBUG_DING_TALK\", \"调试(钉钉)\"),\n    ALIYUN(\"ALIYUN\", \"阿里云\"),\n    TENCENT(\"TENCENT\", \"腾讯云\"),\n//    HUA_WEI(\"HUA_WEI\", \"华为云\"),\n    ;\n\n    /**\n     * 编码\n     */\n    private final String code;\n    /**\n     * 名字\n     */\n    private final String name;\n\n    public static SmsChannelEnum getByCode(String code) {\n        return ArrayUtil.firstMatch(o -> o.getCode().equals(code), values());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/framework/sms/core/enums/SmsTemplateAuditStatusEnum.java",
    "content": "package co.yixiang.yshop.module.system.framework.sms.core.enums;\n\nimport lombok.AllArgsConstructor;\nimport lombok.Getter;\n\n/**\n * 短信模板的审核状态枚举\n *\n * @author yshop\n */\n@AllArgsConstructor\n@Getter\npublic enum SmsTemplateAuditStatusEnum {\n\n    CHECKING(1),\n    SUCCESS(2),\n    FAIL(3);\n\n    private final Integer status;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/framework/sms/core/property/SmsChannelProperties.java",
    "content": "package co.yixiang.yshop.module.system.framework.sms.core.property;\n\nimport co.yixiang.yshop.module.system.framework.sms.core.enums.SmsChannelEnum;\nimport lombok.Data;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\n\n/**\n * 短信渠道配置类\n *\n * @author zzf\n * @since 2021/1/25 17:01\n */\n@Data\n@Validated\npublic class SmsChannelProperties {\n\n    /**\n     * 渠道编号\n     */\n    @NotNull(message = \"短信渠道 ID 不能为空\")\n    private Long id;\n    /**\n     * 短信签名\n     */\n    @NotEmpty(message = \"短信签名不能为空\")\n    private String signature;\n    /**\n     * 渠道编码\n     *\n     * 枚举 {@link SmsChannelEnum}\n     */\n    @NotEmpty(message = \"渠道编码不能为空\")\n    private String code;\n    /**\n     * 短信 API 的账号\n     */\n    @NotEmpty(message = \"短信 API 的账号不能为空\")\n    private String apiKey;\n    /**\n     * 短信 API 的密钥\n     */\n    @NotEmpty(message = \"短信 API 的密钥不能为空\")\n    private String apiSecret;\n    /**\n     * 短信发送回调 URL\n     */\n    private String callbackUrl;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/framework/web/config/SystemWebConfiguration.java",
    "content": "package co.yixiang.yshop.module.system.framework.web.config;\n\nimport co.yixiang.yshop.framework.swagger.config.YshopSwaggerAutoConfiguration;\nimport org.springdoc.core.models.GroupedOpenApi;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.context.annotation.Configuration;\n\n/**\n * system 模块的 web 组件的 Configuration\n *\n * @author yshop\n */\n@Configuration(proxyBeanMethods = false)\npublic class SystemWebConfiguration {\n\n    /**\n     * system 模块的 API 分组\n     */\n    @Bean\n    public GroupedOpenApi systemGroupedOpenApi() {\n        return YshopSwaggerAutoConfiguration.buildGroupedOpenApi(\"system\");\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/job/DemoJob.java",
    "content": "package co.yixiang.yshop.module.system.job;\n\nimport co.yixiang.yshop.framework.quartz.core.handler.JobHandler;\nimport co.yixiang.yshop.framework.tenant.core.context.TenantContextHolder;\nimport co.yixiang.yshop.framework.tenant.core.job.TenantJob;\nimport co.yixiang.yshop.module.system.dal.dataobject.user.AdminUserDO;\nimport co.yixiang.yshop.module.system.dal.mysql.user.AdminUserMapper;\nimport org.springframework.stereotype.Component;\n\nimport jakarta.annotation.Resource;\nimport java.util.List;\n\n@Component\npublic class DemoJob implements JobHandler {\n\n    @Resource\n    private AdminUserMapper adminUserMapper;\n\n    @Override\n    @TenantJob // 标记多租户\n    public String execute(String param) {\n        System.out.println(\"当前租户：\" + TenantContextHolder.getTenantId());\n        List<AdminUserDO> users = adminUserMapper.selectList();\n        return \"用户数量：\" + users.size();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/mq/consumer/mail/MailSendConsumer.java",
    "content": "package co.yixiang.yshop.module.system.mq.consumer.mail;\n\nimport co.yixiang.yshop.module.system.mq.message.mail.MailSendMessage;\nimport co.yixiang.yshop.module.system.service.mail.MailSendService;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.context.event.EventListener;\nimport org.springframework.scheduling.annotation.Async;\nimport org.springframework.stereotype.Component;\n\nimport jakarta.annotation.Resource;\n\n/**\n * 针对 {@link MailSendMessage} 的消费者\n *\n * @author yshop\n */\n@Component\n@Slf4j\npublic class MailSendConsumer {\n\n    @Resource\n    private MailSendService mailSendService;\n\n    @EventListener\n    @Async // Spring Event 默认在 Producer 发送的线程，通过 @Async 实现异步\n    public void onMessage(MailSendMessage message) {\n        log.info(\"[onMessage][消息内容({})]\", message);\n        mailSendService.doSendMail(message);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/mq/consumer/sms/SmsSendConsumer.java",
    "content": "package co.yixiang.yshop.module.system.mq.consumer.sms;\n\nimport co.yixiang.yshop.module.system.mq.message.sms.SmsSendMessage;\nimport co.yixiang.yshop.module.system.service.sms.SmsSendService;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.context.event.EventListener;\nimport org.springframework.scheduling.annotation.Async;\nimport org.springframework.stereotype.Component;\n\nimport jakarta.annotation.Resource;\n\n/**\n * 针对 {@link SmsSendMessage} 的消费者\n *\n * @author zzf\n */\n@Component\n@Slf4j\npublic class SmsSendConsumer {\n\n    @Resource\n    private SmsSendService smsSendService;\n\n    @EventListener\n    @Async // Spring Event 默认在 Producer 发送的线程，通过 @Async 实现异步\n    public void onMessage(SmsSendMessage message) {\n        log.info(\"[onMessage][消息内容({})]\", message);\n        smsSendService.doSendSms(message);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/mq/message/mail/MailSendMessage.java",
    "content": "package co.yixiang.yshop.module.system.mq.message.mail;\n\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotEmpty;\nimport jakarta.validation.constraints.NotNull;\n\n/**\n * 邮箱发送消息\n *\n * @author yshop\n */\n@Data\npublic class MailSendMessage {\n\n    /**\n     * 邮件日志编号\n     */\n    @NotNull(message = \"邮件日志编号不能为空\")\n    private Long logId;\n    /**\n     * 接收邮件地址\n     */\n    @NotNull(message = \"接收邮件地址不能为空\")\n    private String mail;\n    /**\n     * 邮件账号编号\n     */\n    @NotNull(message = \"邮件账号编号不能为空\")\n    private Long accountId;\n\n    /**\n     * 邮件发件人\n     */\n    private String nickname;\n    /**\n     * 邮件标题\n     */\n    @NotEmpty(message = \"邮件标题不能为空\")\n    private String title;\n    /**\n     * 邮件内容\n     */\n    @NotEmpty(message = \"邮件内容不能为空\")\n    private String content;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/mq/message/sms/SmsSendMessage.java",
    "content": "package co.yixiang.yshop.module.system.mq.message.sms;\n\nimport co.yixiang.yshop.framework.common.core.KeyValue;\nimport lombok.Data;\n\nimport jakarta.validation.constraints.NotNull;\nimport java.util.List;\n\n/**\n * 短信发送消息\n *\n * @author yshop\n */\n@Data\npublic class SmsSendMessage {\n\n    /**\n     * 短信日志编号\n     */\n    @NotNull(message = \"短信日志编号不能为空\")\n    private Long logId;\n    /**\n     * 手机号\n     */\n    @NotNull(message = \"手机号不能为空\")\n    private String mobile;\n    /**\n     * 短信渠道编号\n     */\n    @NotNull(message = \"短信渠道编号不能为空\")\n    private Long channelId;\n    /**\n     * 短信 API 的模板编号\n     */\n    @NotNull(message = \"短信 API 的模板编号不能为空\")\n    private String apiTemplateId;\n    /**\n     * 短信模板参数\n     */\n    private List<KeyValue<String, Object>> templateParams;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/mq/producer/mail/MailProducer.java",
    "content": "package co.yixiang.yshop.module.system.mq.producer.mail;\n\nimport co.yixiang.yshop.module.system.mq.message.mail.MailSendMessage;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.stereotype.Component;\n\nimport jakarta.annotation.Resource;\n\n/**\n * Mail 邮件相关消息的 Producer\n *\n * @author wangjingyi\n * @since 2021/4/19 13:33\n */\n@Slf4j\n@Component\npublic class MailProducer {\n\n    @Resource\n    private ApplicationContext applicationContext;\n\n    /**\n     * 发送 {@link MailSendMessage} 消息\n     *\n     * @param sendLogId 发送日志编码\n     * @param mail 接收邮件地址\n     * @param accountId 邮件账号编号\n     * @param nickname 邮件发件人\n     * @param title 邮件标题\n     * @param content 邮件内容\n     */\n    public void sendMailSendMessage(Long sendLogId, String mail, Long accountId,\n                                    String nickname, String title, String content) {\n        MailSendMessage message = new MailSendMessage()\n                .setLogId(sendLogId).setMail(mail).setAccountId(accountId)\n                .setNickname(nickname).setTitle(title).setContent(content);\n        applicationContext.publishEvent(message);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/mq/producer/sms/SmsProducer.java",
    "content": "package co.yixiang.yshop.module.system.mq.producer.sms;\n\nimport co.yixiang.yshop.framework.common.core.KeyValue;\nimport co.yixiang.yshop.module.system.mq.message.sms.SmsSendMessage;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.context.ApplicationContext;\nimport org.springframework.stereotype.Component;\n\nimport jakarta.annotation.Resource;\nimport java.util.List;\n\n/**\n * Sms 短信相关消息的 Producer\n *\n * @author zzf\n * @since 2021/3/9 16:35\n */\n@Slf4j\n@Component\npublic class SmsProducer {\n\n    @Resource\n    private ApplicationContext applicationContext;\n\n    /**\n     * 发送 {@link SmsSendMessage} 消息\n     *\n     * @param logId 短信日志编号\n     * @param mobile 手机号\n     * @param channelId 渠道编号\n     * @param apiTemplateId 短信模板编号\n     * @param templateParams 短信模板参数\n     */\n    public void sendSmsSendMessage(Long logId, String mobile,\n                                   Long channelId, String apiTemplateId, List<KeyValue<String, Object>> templateParams) {\n        SmsSendMessage message = new SmsSendMessage().setLogId(logId).setMobile(mobile);\n        message.setChannelId(channelId).setApiTemplateId(apiTemplateId).setTemplateParams(templateParams);\n        applicationContext.publishEvent(message);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/auth/AdminAuthService.java",
    "content": "package co.yixiang.yshop.module.system.service.auth;\n\nimport co.yixiang.yshop.module.system.controller.admin.auth.vo.*;\nimport co.yixiang.yshop.module.system.dal.dataobject.user.AdminUserDO;\n\nimport jakarta.validation.Valid;\n\n/**\n * 管理后台的认证 Service 接口\n *\n * 提供用户的登录、登出的能力\n *\n * @author yshop\n */\npublic interface AdminAuthService {\n\n    /**\n     * 验证账号 + 密码。如果通过，则返回用户\n     *\n     * @param username 账号\n     * @param password 密码\n     * @return 用户\n     */\n    AdminUserDO authenticate(String username, String password);\n\n    /**\n     * 账号登录\n     *\n     * @param reqVO 登录信息\n     * @return 登录结果\n     */\n    AuthLoginRespVO login(@Valid AuthLoginReqVO reqVO);\n\n    /**\n     * 基于 token 退出登录\n     *\n     * @param token token\n     * @param logType 登出类型\n     */\n    void logout(String token, Integer logType);\n\n    /**\n     * 短信验证码发送\n     *\n     * @param reqVO 发送请求\n     */\n    void sendSmsCode(AuthSmsSendReqVO reqVO);\n\n    /**\n     * 短信登录\n     *\n     * @param reqVO 登录信息\n     * @return 登录结果\n     */\n    AuthLoginRespVO smsLogin(AuthSmsLoginReqVO reqVO) ;\n\n    /**\n     * 社交快捷登录，使用 code 授权码\n     *\n     * @param reqVO 登录信息\n     * @return 登录结果\n     */\n    AuthLoginRespVO socialLogin(@Valid AuthSocialLoginReqVO reqVO);\n\n    /**\n     * 刷新访问令牌\n     *\n     * @param refreshToken 刷新令牌\n     * @return 登录结果\n     */\n    AuthLoginRespVO refreshToken(String refreshToken);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/auth/AdminAuthServiceImpl.java",
    "content": "package co.yixiang.yshop.module.system.service.auth;\n\nimport cn.hutool.core.util.ObjectUtil;\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.common.util.monitor.TracerUtils;\nimport co.yixiang.yshop.framework.common.util.servlet.ServletUtils;\nimport co.yixiang.yshop.framework.common.util.validation.ValidationUtils;\nimport co.yixiang.yshop.module.system.api.logger.dto.LoginLogCreateReqDTO;\nimport co.yixiang.yshop.module.system.api.sms.SmsCodeApi;\nimport co.yixiang.yshop.module.system.api.social.dto.SocialUserBindReqDTO;\nimport co.yixiang.yshop.module.system.api.social.dto.SocialUserRespDTO;\nimport co.yixiang.yshop.module.system.controller.admin.auth.vo.*;\nimport co.yixiang.yshop.module.system.convert.auth.AuthConvert;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.user.AdminUserDO;\nimport co.yixiang.yshop.module.system.enums.logger.LoginLogTypeEnum;\nimport co.yixiang.yshop.module.system.enums.logger.LoginResultEnum;\nimport co.yixiang.yshop.module.system.enums.oauth2.OAuth2ClientConstants;\nimport co.yixiang.yshop.module.system.enums.sms.SmsSceneEnum;\nimport co.yixiang.yshop.module.system.service.logger.LoginLogService;\nimport co.yixiang.yshop.module.system.service.member.MemberService;\nimport co.yixiang.yshop.module.system.service.oauth2.OAuth2TokenService;\nimport co.yixiang.yshop.module.system.service.social.SocialUserService;\nimport co.yixiang.yshop.module.system.service.user.AdminUserService;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.xingyuv.captcha.model.common.ResponseModel;\nimport com.xingyuv.captcha.model.vo.CaptchaVO;\nimport com.xingyuv.captcha.service.CaptchaService;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Service;\n\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Validator;\nimport java.util.Objects;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.framework.common.util.servlet.ServletUtils.getClientIP;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.*;\n\n/**\n * Auth Service 实现类\n *\n * @author yshop\n */\n@Service\n@Slf4j\npublic class AdminAuthServiceImpl implements AdminAuthService {\n\n    @Resource\n    private AdminUserService userService;\n    @Resource\n    private LoginLogService loginLogService;\n    @Resource\n    private OAuth2TokenService oauth2TokenService;\n    @Resource\n    private SocialUserService socialUserService;\n    @Resource\n    private MemberService memberService;\n    @Resource\n    private Validator validator;\n    @Resource\n    private CaptchaService captchaService;\n    @Resource\n    private SmsCodeApi smsCodeApi;\n\n    /**\n     * 验证码的开关，默认为 true\n     */\n    @Value(\"${yshop.captcha.enable:true}\")\n    private Boolean captchaEnable;\n\n    @Override\n    public AdminUserDO authenticate(String username, String password) {\n        final LoginLogTypeEnum logTypeEnum = LoginLogTypeEnum.LOGIN_USERNAME;\n        // 校验账号是否存在\n        AdminUserDO user = userService.getUserByUsername(username);\n        if (user == null) {\n            createLoginLog(null, username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);\n            throw exception(AUTH_LOGIN_BAD_CREDENTIALS);\n        }\n        if (!userService.isPasswordMatch(password, user.getPassword())) {\n            createLoginLog(user.getId(), username, logTypeEnum, LoginResultEnum.BAD_CREDENTIALS);\n            throw exception(AUTH_LOGIN_BAD_CREDENTIALS);\n        }\n        // 校验是否禁用\n        if (CommonStatusEnum.isDisable(user.getStatus())) {\n            createLoginLog(user.getId(), username, logTypeEnum, LoginResultEnum.USER_DISABLED);\n            throw exception(AUTH_LOGIN_USER_DISABLED);\n        }\n        return user;\n    }\n\n    @Override\n    public AuthLoginRespVO login(AuthLoginReqVO reqVO) {\n        // 校验验证码\n        validateCaptcha(reqVO);\n\n        // 使用账号密码，进行登录\n        AdminUserDO user = authenticate(reqVO.getUsername(), reqVO.getPassword());\n\n        // 如果 socialType 非空，说明需要绑定社交用户\n        if (reqVO.getSocialType() != null) {\n            socialUserService.bindSocialUser(new SocialUserBindReqDTO(user.getId(), getUserType().getValue(),\n                    reqVO.getSocialType(), reqVO.getSocialCode(), reqVO.getSocialState()));\n        }\n        // 创建 Token 令牌，记录登录日志\n        return createTokenAfterLoginSuccess(user.getId(), reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME);\n    }\n\n    @Override\n    public void sendSmsCode(AuthSmsSendReqVO reqVO) {\n        // 登录场景，验证是否存在\n        if (userService.getUserByMobile(reqVO.getMobile()) == null) {\n            throw exception(AUTH_MOBILE_NOT_EXISTS);\n        }\n        // 发送验证码\n        smsCodeApi.sendSmsCode(AuthConvert.INSTANCE.convert(reqVO).setCreateIp(getClientIP()));\n    }\n\n    @Override\n    public AuthLoginRespVO smsLogin(AuthSmsLoginReqVO reqVO) {\n        // 校验验证码\n        smsCodeApi.useSmsCode(AuthConvert.INSTANCE.convert(reqVO, SmsSceneEnum.ADMIN_MEMBER_LOGIN.getScene(), getClientIP()));\n\n        // 获得用户信息\n        AdminUserDO user = userService.getUserByMobile(reqVO.getMobile());\n        if (user == null) {\n            throw exception(USER_NOT_EXISTS);\n        }\n\n        // 创建 Token 令牌，记录登录日志\n        return createTokenAfterLoginSuccess(user.getId(), reqVO.getMobile(), LoginLogTypeEnum.LOGIN_MOBILE);\n    }\n\n    private void createLoginLog(Long userId, String username,\n                                LoginLogTypeEnum logTypeEnum, LoginResultEnum loginResult) {\n        // 插入登录日志\n        LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();\n        reqDTO.setLogType(logTypeEnum.getType());\n        reqDTO.setTraceId(TracerUtils.getTraceId());\n        reqDTO.setUserId(userId);\n        reqDTO.setUserType(getUserType().getValue());\n        reqDTO.setUsername(username);\n        reqDTO.setUserAgent(ServletUtils.getUserAgent());\n        reqDTO.setUserIp(ServletUtils.getClientIP());\n        reqDTO.setResult(loginResult.getResult());\n        loginLogService.createLoginLog(reqDTO);\n        // 更新最后登录时间\n        if (userId != null && Objects.equals(LoginResultEnum.SUCCESS.getResult(), loginResult.getResult())) {\n            userService.updateUserLogin(userId, ServletUtils.getClientIP());\n        }\n    }\n\n    @Override\n    public AuthLoginRespVO socialLogin(AuthSocialLoginReqVO reqVO) {\n        // 使用 code 授权码，进行登录。然后，获得到绑定的用户编号\n        SocialUserRespDTO socialUser = socialUserService.getSocialUserByCode(UserTypeEnum.ADMIN.getValue(), reqVO.getType(),\n                reqVO.getCode(), reqVO.getState());\n        if (socialUser == null || socialUser.getUserId() == null) {\n            throw exception(AUTH_THIRD_LOGIN_NOT_BIND);\n        }\n\n        // 获得用户\n        AdminUserDO user = userService.getUser(socialUser.getUserId());\n        if (user == null) {\n            throw exception(USER_NOT_EXISTS);\n        }\n\n        // 创建 Token 令牌，记录登录日志\n        return createTokenAfterLoginSuccess(user.getId(), user.getUsername(), LoginLogTypeEnum.LOGIN_SOCIAL);\n    }\n\n    @VisibleForTesting\n    void validateCaptcha(AuthLoginReqVO reqVO) {\n        // 如果验证码关闭，则不进行校验\n        if (!captchaEnable) {\n            return;\n        }\n        // 校验验证码\n        ValidationUtils.validate(validator, reqVO, AuthLoginReqVO.CodeEnableGroup.class);\n        CaptchaVO captchaVO = new CaptchaVO();\n        captchaVO.setCaptchaVerification(reqVO.getCaptchaVerification());\n        ResponseModel response = captchaService.verification(captchaVO);\n        // 验证不通过\n        if (!response.isSuccess()) {\n            // 创建登录失败日志（验证码不正确)\n            createLoginLog(null, reqVO.getUsername(), LoginLogTypeEnum.LOGIN_USERNAME, LoginResultEnum.CAPTCHA_CODE_ERROR);\n            throw exception(AUTH_LOGIN_CAPTCHA_CODE_ERROR, response.getRepMsg());\n        }\n    }\n\n    private AuthLoginRespVO createTokenAfterLoginSuccess(Long userId, String username, LoginLogTypeEnum logType) {\n        // 插入登陆日志\n        createLoginLog(userId, username, logType, LoginResultEnum.SUCCESS);\n        // 创建访问令牌\n        OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.createAccessToken(userId, getUserType().getValue(),\n                OAuth2ClientConstants.CLIENT_ID_DEFAULT, null);\n        // 构建返回结果\n        return AuthConvert.INSTANCE.convert(accessTokenDO);\n    }\n\n    @Override\n    public AuthLoginRespVO refreshToken(String refreshToken) {\n        OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.refreshAccessToken(refreshToken, OAuth2ClientConstants.CLIENT_ID_DEFAULT);\n        return AuthConvert.INSTANCE.convert(accessTokenDO);\n    }\n\n    @Override\n    public void logout(String token, Integer logType) {\n        // 删除访问令牌\n        OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.removeAccessToken(token);\n        if (accessTokenDO == null) {\n            return;\n        }\n        // 删除成功，则记录登出日志\n        createLogoutLog(accessTokenDO.getUserId(), accessTokenDO.getUserType(), logType);\n    }\n\n    private void createLogoutLog(Long userId, Integer userType, Integer logType) {\n        LoginLogCreateReqDTO reqDTO = new LoginLogCreateReqDTO();\n        reqDTO.setLogType(logType);\n        reqDTO.setTraceId(TracerUtils.getTraceId());\n        reqDTO.setUserId(userId);\n        reqDTO.setUserType(userType);\n        if (ObjectUtil.equal(getUserType().getValue(), userType)) {\n            reqDTO.setUsername(getUsername(userId));\n        } else {\n            reqDTO.setUsername(memberService.getMemberUserMobile(userId));\n        }\n        reqDTO.setUserAgent(ServletUtils.getUserAgent());\n        reqDTO.setUserIp(ServletUtils.getClientIP());\n        reqDTO.setResult(LoginResultEnum.SUCCESS.getResult());\n        loginLogService.createLoginLog(reqDTO);\n    }\n\n    private String getUsername(Long userId) {\n        if (userId == null) {\n            return null;\n        }\n        AdminUserDO user = userService.getUser(userId);\n        return user != null ? user.getUsername() : null;\n    }\n\n    private UserTypeEnum getUserType() {\n        return UserTypeEnum.ADMIN;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/dept/DeptService.java",
    "content": "package co.yixiang.yshop.module.system.service.dept;\n\nimport co.yixiang.yshop.framework.common.util.collection.CollectionUtils;\nimport co.yixiang.yshop.module.system.controller.admin.dept.vo.dept.DeptListReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.dept.vo.dept.DeptSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.dept.DeptDO;\n\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\n\n/**\n * 部门 Service 接口\n *\n * @author yshop\n */\npublic interface DeptService {\n\n    /**\n     * 创建部门\n     *\n     * @param createReqVO 部门信息\n     * @return 部门编号\n     */\n    Long createDept(DeptSaveReqVO createReqVO);\n\n    /**\n     * 更新部门\n     *\n     * @param updateReqVO 部门信息\n     */\n    void updateDept(DeptSaveReqVO updateReqVO);\n\n    /**\n     * 删除部门\n     *\n     * @param id 部门编号\n     */\n    void deleteDept(Long id);\n\n    /**\n     * 获得部门信息\n     *\n     * @param id 部门编号\n     * @return 部门信息\n     */\n    DeptDO getDept(Long id);\n\n    /**\n     * 获得部门信息数组\n     *\n     * @param ids 部门编号数组\n     * @return 部门信息数组\n     */\n    List<DeptDO> getDeptList(Collection<Long> ids);\n\n    /**\n     * 筛选部门列表\n     *\n     * @param reqVO 筛选条件请求 VO\n     * @return 部门列表\n     */\n    List<DeptDO> getDeptList(DeptListReqVO reqVO);\n\n    /**\n     * 获得指定编号的部门 Map\n     *\n     * @param ids 部门编号数组\n     * @return 部门 Map\n     */\n    default Map<Long, DeptDO> getDeptMap(Collection<Long> ids) {\n        List<DeptDO> list = getDeptList(ids);\n        return CollectionUtils.convertMap(list, DeptDO::getId);\n    }\n\n    /**\n     * 获得指定部门的所有子部门\n     *\n     * @param id 部门编号\n     * @return 子部门列表\n     */\n    List<DeptDO> getChildDeptList(Long id);\n\n    /**\n     * 获得所有子部门，从缓存中\n     *\n     * @param id 父部门编号\n     * @return 子部门列表\n     */\n    Set<Long> getChildDeptIdListFromCache(Long id);\n\n    /**\n     * 校验部门们是否有效。如下情况，视为无效：\n     * 1. 部门编号不存在\n     * 2. 部门被禁用\n     *\n     * @param ids 角色编号数组\n     */\n    void validateDeptList(Collection<Long> ids);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/dept/DeptServiceImpl.java",
    "content": "package co.yixiang.yshop.module.system.service.dept;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.util.ObjectUtil;\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.framework.datapermission.core.annotation.DataPermission;\nimport co.yixiang.yshop.module.system.controller.admin.dept.vo.dept.DeptListReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.dept.vo.dept.DeptSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.dept.DeptDO;\nimport co.yixiang.yshop.module.system.dal.mysql.dept.DeptMapper;\nimport co.yixiang.yshop.module.system.dal.redis.RedisKeyConstants;\nimport com.google.common.annotations.VisibleForTesting;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.cache.annotation.CacheEvict;\nimport org.springframework.cache.annotation.Cacheable;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\nimport java.util.*;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.framework.common.util.collection.CollectionUtils.convertSet;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.*;\n\n/**\n * 部门 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\n@Slf4j\npublic class DeptServiceImpl implements DeptService {\n\n    @Resource\n    private DeptMapper deptMapper;\n\n    @Override\n    @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST,\n            allEntries = true) // allEntries 清空所有缓存，因为操作一个部门，涉及到多个缓存\n    public Long createDept(DeptSaveReqVO createReqVO) {\n        if (createReqVO.getParentId() == null) {\n            createReqVO.setParentId(DeptDO.PARENT_ID_ROOT);\n        }\n        // 校验父部门的有效性\n        validateParentDept(null, createReqVO.getParentId());\n        // 校验部门名的唯一性\n        validateDeptNameUnique(null, createReqVO.getParentId(), createReqVO.getName());\n\n        // 插入部门\n        DeptDO dept = BeanUtils.toBean(createReqVO, DeptDO.class);\n        deptMapper.insert(dept);\n        return dept.getId();\n    }\n\n    @Override\n    @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST,\n            allEntries = true) // allEntries 清空所有缓存，因为操作一个部门，涉及到多个缓存\n    public void updateDept(DeptSaveReqVO updateReqVO) {\n        if (updateReqVO.getParentId() == null) {\n            updateReqVO.setParentId(DeptDO.PARENT_ID_ROOT);\n        }\n        // 校验自己存在\n        validateDeptExists(updateReqVO.getId());\n        // 校验父部门的有效性\n        validateParentDept(updateReqVO.getId(), updateReqVO.getParentId());\n        // 校验部门名的唯一性\n        validateDeptNameUnique(updateReqVO.getId(), updateReqVO.getParentId(), updateReqVO.getName());\n\n        // 更新部门\n        DeptDO updateObj = BeanUtils.toBean(updateReqVO, DeptDO.class);\n        deptMapper.updateById(updateObj);\n    }\n\n    @Override\n    @CacheEvict(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST,\n            allEntries = true) // allEntries 清空所有缓存，因为操作一个部门，涉及到多个缓存\n    public void deleteDept(Long id) {\n        // 校验是否存在\n        validateDeptExists(id);\n        // 校验是否有子部门\n        if (deptMapper.selectCountByParentId(id) > 0) {\n            throw exception(DEPT_EXITS_CHILDREN);\n        }\n        // 删除部门\n        deptMapper.deleteById(id);\n    }\n\n    @VisibleForTesting\n    void validateDeptExists(Long id) {\n        if (id == null) {\n            return;\n        }\n        DeptDO dept = deptMapper.selectById(id);\n        if (dept == null) {\n            throw exception(DEPT_NOT_FOUND);\n        }\n    }\n\n    @VisibleForTesting\n    void validateParentDept(Long id, Long parentId) {\n        if (parentId == null || DeptDO.PARENT_ID_ROOT.equals(parentId)) {\n            return;\n        }\n        // 1. 不能设置自己为父部门\n        if (Objects.equals(id, parentId)) {\n            throw exception(DEPT_PARENT_ERROR);\n        }\n        // 2. 父部门不存在\n        DeptDO parentDept = deptMapper.selectById(parentId);\n        if (parentDept == null) {\n            throw exception(DEPT_PARENT_NOT_EXITS);\n        }\n        // 3. 递归校验父部门，如果父部门是自己的子部门，则报错，避免形成环路\n        if (id == null) { // id 为空，说明新增，不需要考虑环路\n            return;\n        }\n        for (int i = 0; i < Short.MAX_VALUE; i++) {\n            // 3.1 校验环路\n            parentId = parentDept.getParentId();\n            if (Objects.equals(id, parentId)) {\n                throw exception(DEPT_PARENT_IS_CHILD);\n            }\n            // 3.2 继续递归下一级父部门\n            if (parentId == null || DeptDO.PARENT_ID_ROOT.equals(parentId)) {\n                break;\n            }\n            parentDept = deptMapper.selectById(parentId);\n            if (parentDept == null) {\n                break;\n            }\n        }\n    }\n\n    @VisibleForTesting\n    void validateDeptNameUnique(Long id, Long parentId, String name) {\n        DeptDO dept = deptMapper.selectByParentIdAndName(parentId, name);\n        if (dept == null) {\n            return;\n        }\n        // 如果 id 为空，说明不用比较是否为相同 id 的部门\n        if (id == null) {\n            throw exception(DEPT_NAME_DUPLICATE);\n        }\n        if (ObjectUtil.notEqual(dept.getId(), id)) {\n            throw exception(DEPT_NAME_DUPLICATE);\n        }\n    }\n\n    @Override\n    public DeptDO getDept(Long id) {\n        return deptMapper.selectById(id);\n    }\n\n    @Override\n    public List<DeptDO> getDeptList(Collection<Long> ids) {\n        if (CollUtil.isEmpty(ids)) {\n            return Collections.emptyList();\n        }\n        return deptMapper.selectBatchIds(ids);\n    }\n\n    @Override\n    public List<DeptDO> getDeptList(DeptListReqVO reqVO) {\n        List<DeptDO> list = deptMapper.selectList(reqVO);\n        list.sort(Comparator.comparing(DeptDO::getSort));\n        return list;\n    }\n\n    @Override\n    public List<DeptDO> getChildDeptList(Long id) {\n        List<DeptDO> children = new LinkedList<>();\n        // 遍历每一层\n        Collection<Long> parentIds = Collections.singleton(id);\n        for (int i = 0; i < Short.MAX_VALUE; i++) { // 使用 Short.MAX_VALUE 避免 bug 场景下，存在死循环\n            // 查询当前层，所有的子部门\n            List<DeptDO> depts = deptMapper.selectListByParentId(parentIds);\n            // 1. 如果没有子部门，则结束遍历\n            if (CollUtil.isEmpty(depts)) {\n                break;\n            }\n            // 2. 如果有子部门，继续遍历\n            children.addAll(depts);\n            parentIds = convertSet(depts, DeptDO::getId);\n        }\n        return children;\n    }\n\n    @Override\n    @DataPermission(enable = false) // 禁用数据权限，避免建立不正确的缓存\n    @Cacheable(cacheNames = RedisKeyConstants.DEPT_CHILDREN_ID_LIST, key = \"#id\")\n    public Set<Long> getChildDeptIdListFromCache(Long id) {\n        List<DeptDO> children = getChildDeptList(id);\n        return convertSet(children, DeptDO::getId);\n    }\n\n    @Override\n    public void validateDeptList(Collection<Long> ids) {\n        if (CollUtil.isEmpty(ids)) {\n            return;\n        }\n        // 获得科室信息\n        Map<Long, DeptDO> deptMap = getDeptMap(ids);\n        // 校验\n        ids.forEach(id -> {\n            DeptDO dept = deptMap.get(id);\n            if (dept == null) {\n                throw exception(DEPT_NOT_FOUND);\n            }\n            if (!CommonStatusEnum.ENABLE.getStatus().equals(dept.getStatus())) {\n                throw exception(DEPT_NOT_ENABLE, dept.getName());\n            }\n        });\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/dept/PostService.java",
    "content": "package co.yixiang.yshop.module.system.service.dept;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.system.controller.admin.dept.vo.post.PostPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.dept.vo.post.PostSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.dept.PostDO;\nimport org.springframework.lang.Nullable;\n\nimport java.util.Collection;\nimport java.util.List;\n\n/**\n * 岗位 Service 接口\n *\n * @author yshop\n */\npublic interface PostService {\n\n    /**\n     * 创建岗位\n     *\n     * @param createReqVO 岗位信息\n     * @return 岗位编号\n     */\n    Long createPost(PostSaveReqVO createReqVO);\n\n    /**\n     * 更新岗位\n     *\n     * @param updateReqVO 岗位信息\n     */\n    void updatePost(PostSaveReqVO updateReqVO);\n\n    /**\n     * 删除岗位信息\n     *\n     * @param id 岗位编号\n     */\n    void deletePost(Long id);\n\n    /**\n     * 获得岗位列表\n     *\n     * @param ids 岗位编号数组\n     * @return 部门列表\n     */\n    List<PostDO> getPostList(@Nullable Collection<Long> ids);\n\n    /**\n     * 获得符合条件的岗位列表\n     *\n     * @param ids 岗位编号数组。如果为空，不进行筛选\n     * @param statuses 状态数组。如果为空，不进行筛选\n     * @return 部门列表\n     */\n    List<PostDO> getPostList(@Nullable Collection<Long> ids,\n                             @Nullable Collection<Integer> statuses);\n\n    /**\n     * 获得岗位分页列表\n     *\n     * @param reqVO 分页条件\n     * @return 部门分页列表\n     */\n    PageResult<PostDO> getPostPage(PostPageReqVO reqVO);\n\n    /**\n     * 获得岗位信息\n     *\n     * @param id 岗位编号\n     * @return 岗位信息\n     */\n    PostDO getPost(Long id);\n\n    /**\n     * 校验岗位们是否有效。如下情况，视为无效：\n     * 1. 岗位编号不存在\n     * 2. 岗位被禁用\n     *\n     * @param ids 岗位编号数组\n     */\n    void validatePostList(Collection<Long> ids);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/dept/PostServiceImpl.java",
    "content": "package co.yixiang.yshop.module.system.service.dept;\n\nimport cn.hutool.core.collection.CollUtil;\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.controller.admin.dept.vo.post.PostPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.dept.vo.post.PostSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.dept.PostDO;\nimport co.yixiang.yshop.module.system.dal.mysql.dept.PostMapper;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.framework.common.util.collection.CollectionUtils.convertMap;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.*;\n\n/**\n * 岗位 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class PostServiceImpl implements PostService {\n\n    @Resource\n    private PostMapper postMapper;\n\n    @Override\n    public Long createPost(PostSaveReqVO createReqVO) {\n        // 校验正确性\n        validatePostForCreateOrUpdate(null, createReqVO.getName(), createReqVO.getCode());\n\n        // 插入岗位\n        PostDO post = BeanUtils.toBean(createReqVO, PostDO.class);\n        postMapper.insert(post);\n        return post.getId();\n    }\n\n    @Override\n    public void updatePost(PostSaveReqVO updateReqVO) {\n        // 校验正确性\n        validatePostForCreateOrUpdate(updateReqVO.getId(), updateReqVO.getName(), updateReqVO.getCode());\n\n        // 更新岗位\n        PostDO updateObj = BeanUtils.toBean(updateReqVO, PostDO.class);\n        postMapper.updateById(updateObj);\n    }\n\n    @Override\n    public void deletePost(Long id) {\n        // 校验是否存在\n        validatePostExists(id);\n        // 删除部门\n        postMapper.deleteById(id);\n    }\n\n    private void validatePostForCreateOrUpdate(Long id, String name, String code) {\n        // 校验自己存在\n        validatePostExists(id);\n        // 校验岗位名的唯一性\n        validatePostNameUnique(id, name);\n        // 校验岗位编码的唯一性\n        validatePostCodeUnique(id, code);\n    }\n\n    private void validatePostNameUnique(Long id, String name) {\n        PostDO post = postMapper.selectByName(name);\n        if (post == null) {\n            return;\n        }\n        // 如果 id 为空，说明不用比较是否为相同 id 的岗位\n        if (id == null) {\n            throw exception(POST_NAME_DUPLICATE);\n        }\n        if (!post.getId().equals(id)) {\n            throw exception(POST_NAME_DUPLICATE);\n        }\n    }\n\n    private void validatePostCodeUnique(Long id, String code) {\n        PostDO post = postMapper.selectByCode(code);\n        if (post == null) {\n            return;\n        }\n        // 如果 id 为空，说明不用比较是否为相同 id 的岗位\n        if (id == null) {\n            throw exception(POST_CODE_DUPLICATE);\n        }\n        if (!post.getId().equals(id)) {\n            throw exception(POST_CODE_DUPLICATE);\n        }\n    }\n\n    private void validatePostExists(Long id) {\n        if (id == null) {\n            return;\n        }\n        if (postMapper.selectById(id) == null) {\n            throw exception(POST_NOT_FOUND);\n        }\n    }\n\n    @Override\n    public List<PostDO> getPostList(Collection<Long> ids) {\n        if (CollUtil.isEmpty(ids)) {\n            return Collections.emptyList();\n        }\n        return postMapper.selectBatchIds(ids);\n    }\n\n    @Override\n    public List<PostDO> getPostList(Collection<Long> ids, Collection<Integer> statuses) {\n        return postMapper.selectList(ids, statuses);\n    }\n\n    @Override\n    public PageResult<PostDO> getPostPage(PostPageReqVO reqVO) {\n        return postMapper.selectPage(reqVO);\n    }\n\n    @Override\n    public PostDO getPost(Long id) {\n        return postMapper.selectById(id);\n    }\n\n    @Override\n    public void validatePostList(Collection<Long> ids) {\n        if (CollUtil.isEmpty(ids)) {\n            return;\n        }\n        // 获得岗位信息\n        List<PostDO> posts = postMapper.selectBatchIds(ids);\n        Map<Long, PostDO> postMap = convertMap(posts, PostDO::getId);\n        // 校验\n        ids.forEach(id -> {\n            PostDO post = postMap.get(id);\n            if (post == null) {\n                throw exception(POST_NOT_FOUND);\n            }\n            if (!CommonStatusEnum.ENABLE.getStatus().equals(post.getStatus())) {\n                throw exception(POST_NOT_ENABLE, post.getName());\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/dict/DictDataService.java",
    "content": "package co.yixiang.yshop.module.system.service.dict;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.system.controller.admin.dict.vo.data.DictDataPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.dict.vo.data.DictDataSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.dict.DictDataDO;\nimport org.springframework.lang.Nullable;\n\nimport java.util.Collection;\nimport java.util.List;\n\n/**\n * 字典数据 Service 接口\n *\n * @author ruoyi\n */\npublic interface DictDataService {\n\n    /**\n     * 创建字典数据\n     *\n     * @param createReqVO 字典数据信息\n     * @return 字典数据编号\n     */\n    Long createDictData(DictDataSaveReqVO createReqVO);\n\n    /**\n     * 更新字典数据\n     *\n     * @param updateReqVO 字典数据信息\n     */\n    void updateDictData(DictDataSaveReqVO updateReqVO);\n\n    /**\n     * 删除字典数据\n     *\n     * @param id 字典数据编号\n     */\n    void deleteDictData(Long id);\n\n    /**\n     * 获得字典数据列表\n     *\n     * @param status   状态\n     * @param dictType 字典类型\n     * @return 字典数据全列表\n     */\n    List<DictDataDO> getDictDataList(@Nullable Integer status, @Nullable String dictType);\n\n    /**\n     * 获得字典数据分页列表\n     *\n     * @param pageReqVO 分页请求\n     * @return 字典数据分页列表\n     */\n    PageResult<DictDataDO> getDictDataPage(DictDataPageReqVO pageReqVO);\n\n    /**\n     * 获得字典数据详情\n     *\n     * @param id 字典数据编号\n     * @return 字典数据\n     */\n    DictDataDO getDictData(Long id);\n\n    /**\n     * 获得指定字典类型的数据数量\n     *\n     * @param dictType 字典类型\n     * @return 数据数量\n     */\n    long getDictDataCountByDictType(String dictType);\n\n    /**\n     * 校验字典数据们是否有效。如下情况，视为无效：\n     * 1. 字典数据不存在\n     * 2. 字典数据被禁用\n     *\n     * @param dictType 字典类型\n     * @param values   字典数据值的数组\n     */\n    void validateDictDataList(String dictType, Collection<String> values);\n\n    /**\n     * 获得指定的字典数据\n     *\n     * @param dictType 字典类型\n     * @param value    字典数据值\n     * @return 字典数据\n     */\n    DictDataDO getDictData(String dictType, String value);\n\n    /**\n     * 解析获得指定的字典数据，从缓存中\n     *\n     * @param dictType 字典类型\n     * @param label    字典数据标签\n     * @return 字典数据\n     */\n    DictDataDO parseDictData(String dictType, String label);\n\n    /**\n     * 获得指定数据类型的字典数据列表\n     *\n     * @param dictType 字典类型\n     * @return 字典数据列表\n     */\n    List<DictDataDO> getDictDataListByDictType(String dictType);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/dict/DictDataServiceImpl.java",
    "content": "package co.yixiang.yshop.module.system.service.dict;\n\nimport cn.hutool.core.collection.CollUtil;\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.collection.CollectionUtils;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.controller.admin.dict.vo.data.DictDataPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.dict.vo.data.DictDataSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.dict.DictDataDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.dict.DictTypeDO;\nimport co.yixiang.yshop.module.system.dal.mysql.dict.DictDataMapper;\nimport com.google.common.annotations.VisibleForTesting;\nimport jakarta.annotation.Resource;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Service;\n\nimport java.util.Collection;\nimport java.util.Comparator;\nimport java.util.List;\nimport java.util.Map;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.*;\n\n/**\n * 字典数据 Service 实现类\n *\n * @author ruoyi\n */\n@Service\n@Slf4j\npublic class DictDataServiceImpl implements DictDataService {\n\n    /**\n     * 排序 dictType > sort\n     */\n    private static final Comparator<DictDataDO> COMPARATOR_TYPE_AND_SORT = Comparator\n            .comparing(DictDataDO::getDictType)\n            .thenComparingInt(DictDataDO::getSort);\n\n    @Resource\n    private DictTypeService dictTypeService;\n\n    @Resource\n    private DictDataMapper dictDataMapper;\n\n    @Override\n    public List<DictDataDO> getDictDataList(Integer status, String dictType) {\n        List<DictDataDO> list = dictDataMapper.selectListByStatusAndDictType(status, dictType);\n        list.sort(COMPARATOR_TYPE_AND_SORT);\n        return list;\n    }\n\n    @Override\n    public PageResult<DictDataDO> getDictDataPage(DictDataPageReqVO pageReqVO) {\n        return dictDataMapper.selectPage(pageReqVO);\n    }\n\n    @Override\n    public DictDataDO getDictData(Long id) {\n        return dictDataMapper.selectById(id);\n    }\n\n    @Override\n    public Long createDictData(DictDataSaveReqVO createReqVO) {\n        // 校验字典类型有效\n        validateDictTypeExists(createReqVO.getDictType());\n        // 校验字典数据的值的唯一性\n        validateDictDataValueUnique(null, createReqVO.getDictType(), createReqVO.getValue());\n\n        // 插入字典类型\n        DictDataDO dictData = BeanUtils.toBean(createReqVO, DictDataDO.class);\n        dictDataMapper.insert(dictData);\n        return dictData.getId();\n    }\n\n    @Override\n    public void updateDictData(DictDataSaveReqVO updateReqVO) {\n        // 校验自己存在\n        validateDictDataExists(updateReqVO.getId());\n        // 校验字典类型有效\n        validateDictTypeExists(updateReqVO.getDictType());\n        // 校验字典数据的值的唯一性\n        validateDictDataValueUnique(updateReqVO.getId(), updateReqVO.getDictType(), updateReqVO.getValue());\n\n        // 更新字典类型\n        DictDataDO updateObj = BeanUtils.toBean(updateReqVO, DictDataDO.class);\n        dictDataMapper.updateById(updateObj);\n    }\n\n    @Override\n    public void deleteDictData(Long id) {\n        // 校验是否存在\n        validateDictDataExists(id);\n\n        // 删除字典数据\n        dictDataMapper.deleteById(id);\n    }\n\n    @Override\n    public long getDictDataCountByDictType(String dictType) {\n        return dictDataMapper.selectCountByDictType(dictType);\n    }\n\n    @VisibleForTesting\n    public void validateDictDataValueUnique(Long id, String dictType, String value) {\n        DictDataDO dictData = dictDataMapper.selectByDictTypeAndValue(dictType, value);\n        if (dictData == null) {\n            return;\n        }\n        // 如果 id 为空，说明不用比较是否为相同 id 的字典数据\n        if (id == null) {\n            throw exception(DICT_DATA_VALUE_DUPLICATE);\n        }\n        if (!dictData.getId().equals(id)) {\n            throw exception(DICT_DATA_VALUE_DUPLICATE);\n        }\n    }\n\n    @VisibleForTesting\n    public void validateDictDataExists(Long id) {\n        if (id == null) {\n            return;\n        }\n        DictDataDO dictData = dictDataMapper.selectById(id);\n        if (dictData == null) {\n            throw exception(DICT_DATA_NOT_EXISTS);\n        }\n    }\n\n    @VisibleForTesting\n    public void validateDictTypeExists(String type) {\n        DictTypeDO dictType = dictTypeService.getDictType(type);\n        if (dictType == null) {\n            throw exception(DICT_TYPE_NOT_EXISTS);\n        }\n        if (!CommonStatusEnum.ENABLE.getStatus().equals(dictType.getStatus())) {\n            throw exception(DICT_TYPE_NOT_ENABLE);\n        }\n    }\n\n    @Override\n    public void validateDictDataList(String dictType, Collection<String> values) {\n        if (CollUtil.isEmpty(values)) {\n            return;\n        }\n        Map<String, DictDataDO> dictDataMap = CollectionUtils.convertMap(\n                dictDataMapper.selectByDictTypeAndValues(dictType, values), DictDataDO::getValue);\n        // 校验\n        values.forEach(value -> {\n            DictDataDO dictData = dictDataMap.get(value);\n            if (dictData == null) {\n                throw exception(DICT_DATA_NOT_EXISTS);\n            }\n            if (!CommonStatusEnum.ENABLE.getStatus().equals(dictData.getStatus())) {\n                throw exception(DICT_DATA_NOT_ENABLE, dictData.getLabel());\n            }\n        });\n    }\n\n    @Override\n    public DictDataDO getDictData(String dictType, String value) {\n        return dictDataMapper.selectByDictTypeAndValue(dictType, value);\n    }\n\n    @Override\n    public DictDataDO parseDictData(String dictType, String label) {\n        return dictDataMapper.selectByDictTypeAndLabel(dictType, label);\n    }\n\n    @Override\n    public List<DictDataDO> getDictDataListByDictType(String dictType) {\n        List<DictDataDO> list = dictDataMapper.selectList(DictDataDO::getDictType, dictType);\n        list.sort(Comparator.comparing(DictDataDO::getSort));\n        return list;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/dict/DictTypeService.java",
    "content": "package co.yixiang.yshop.module.system.service.dict;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.system.controller.admin.dict.vo.type.DictTypePageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.dict.vo.type.DictTypeSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.dict.DictTypeDO;\n\nimport java.util.List;\n\n/**\n * 字典类型 Service 接口\n *\n * @author yshop\n */\npublic interface DictTypeService {\n\n    /**\n     * 创建字典类型\n     *\n     * @param createReqVO 字典类型信息\n     * @return 字典类型编号\n     */\n    Long createDictType(DictTypeSaveReqVO createReqVO);\n\n    /**\n     * 更新字典类型\n     *\n     * @param updateReqVO 字典类型信息\n     */\n    void updateDictType(DictTypeSaveReqVO updateReqVO);\n\n    /**\n     * 删除字典类型\n     *\n     * @param id 字典类型编号\n     */\n    void deleteDictType(Long id);\n\n    /**\n     * 获得字典类型分页列表\n     *\n     * @param pageReqVO 分页请求\n     * @return 字典类型分页列表\n     */\n    PageResult<DictTypeDO> getDictTypePage(DictTypePageReqVO pageReqVO);\n\n    /**\n     * 获得字典类型详情\n     *\n     * @param id 字典类型编号\n     * @return 字典类型\n     */\n    DictTypeDO getDictType(Long id);\n\n    /**\n     * 获得字典类型详情\n     *\n     * @param type 字典类型\n     * @return 字典类型详情\n     */\n    DictTypeDO getDictType(String type);\n\n    /**\n     * 获得全部字典类型列表\n     *\n     * @return 字典类型列表\n     */\n    List<DictTypeDO> getDictTypeList();\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/dict/DictTypeServiceImpl.java",
    "content": "package co.yixiang.yshop.module.system.service.dict;\n\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.date.LocalDateTimeUtils;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.controller.admin.dict.vo.type.DictTypePageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.dict.vo.type.DictTypeSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.dict.DictTypeDO;\nimport co.yixiang.yshop.module.system.dal.mysql.dict.DictTypeMapper;\nimport com.google.common.annotations.VisibleForTesting;\nimport org.springframework.stereotype.Service;\n\nimport jakarta.annotation.Resource;\nimport java.time.LocalDateTime;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.*;\n\n/**\n * 字典类型 Service 实现类\n *\n * @author yshop\n */\n@Service\npublic class DictTypeServiceImpl implements DictTypeService {\n\n    @Resource\n    private DictDataService dictDataService;\n\n    @Resource\n    private DictTypeMapper dictTypeMapper;\n\n    @Override\n    public PageResult<DictTypeDO> getDictTypePage(DictTypePageReqVO pageReqVO) {\n        return dictTypeMapper.selectPage(pageReqVO);\n    }\n\n    @Override\n    public DictTypeDO getDictType(Long id) {\n        return dictTypeMapper.selectById(id);\n    }\n\n    @Override\n    public DictTypeDO getDictType(String type) {\n        return dictTypeMapper.selectByType(type);\n    }\n\n    @Override\n    public Long createDictType(DictTypeSaveReqVO createReqVO) {\n        // 校验字典类型的名字的唯一性\n        validateDictTypeNameUnique(null, createReqVO.getName());\n        // 校验字典类型的类型的唯一性\n        validateDictTypeUnique(null, createReqVO.getType());\n\n        // 插入字典类型\n        DictTypeDO dictType = BeanUtils.toBean(createReqVO, DictTypeDO.class);\n        dictType.setDeletedTime(LocalDateTimeUtils.EMPTY); // 唯一索引，避免 null 值\n        dictTypeMapper.insert(dictType);\n        return dictType.getId();\n    }\n\n    @Override\n    public void updateDictType(DictTypeSaveReqVO updateReqVO) {\n        // 校验自己存在\n        validateDictTypeExists(updateReqVO.getId());\n        // 校验字典类型的名字的唯一性\n        validateDictTypeNameUnique(updateReqVO.getId(), updateReqVO.getName());\n        // 校验字典类型的类型的唯一性\n        validateDictTypeUnique(updateReqVO.getId(), updateReqVO.getType());\n\n        // 更新字典类型\n        DictTypeDO updateObj = BeanUtils.toBean(updateReqVO, DictTypeDO.class);\n        dictTypeMapper.updateById(updateObj);\n    }\n\n    @Override\n    public void deleteDictType(Long id) {\n        // 校验是否存在\n        DictTypeDO dictType = validateDictTypeExists(id);\n        // 校验是否有字典数据\n        if (dictDataService.getDictDataCountByDictType(dictType.getType()) > 0) {\n            throw exception(DICT_TYPE_HAS_CHILDREN);\n        }\n        // 删除字典类型\n        dictTypeMapper.updateToDelete(id, LocalDateTime.now());\n    }\n\n    @Override\n    public List<DictTypeDO> getDictTypeList() {\n        return dictTypeMapper.selectList();\n    }\n\n    @VisibleForTesting\n    void validateDictTypeNameUnique(Long id, String name) {\n        DictTypeDO dictType = dictTypeMapper.selectByName(name);\n        if (dictType == null) {\n            return;\n        }\n        // 如果 id 为空，说明不用比较是否为相同 id 的字典类型\n        if (id == null) {\n            throw exception(DICT_TYPE_NAME_DUPLICATE);\n        }\n        if (!dictType.getId().equals(id)) {\n            throw exception(DICT_TYPE_NAME_DUPLICATE);\n        }\n    }\n\n    @VisibleForTesting\n    void validateDictTypeUnique(Long id, String type) {\n        if (StrUtil.isEmpty(type)) {\n            return;\n        }\n        DictTypeDO dictType = dictTypeMapper.selectByType(type);\n        if (dictType == null) {\n            return;\n        }\n        // 如果 id 为空，说明不用比较是否为相同 id 的字典类型\n        if (id == null) {\n            throw exception(DICT_TYPE_TYPE_DUPLICATE);\n        }\n        if (!dictType.getId().equals(id)) {\n            throw exception(DICT_TYPE_TYPE_DUPLICATE);\n        }\n    }\n\n    @VisibleForTesting\n    DictTypeDO validateDictTypeExists(Long id) {\n        if (id == null) {\n            return null;\n        }\n        DictTypeDO dictType = dictTypeMapper.selectById(id);\n        if (dictType == null) {\n            throw exception(DICT_TYPE_NOT_EXISTS);\n        }\n        return dictType;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/logger/LoginLogService.java",
    "content": "package co.yixiang.yshop.module.system.service.logger;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.system.api.logger.dto.LoginLogCreateReqDTO;\nimport co.yixiang.yshop.module.system.controller.admin.logger.vo.loginlog.LoginLogPageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.logger.LoginLogDO;\n\nimport jakarta.validation.Valid;\n\n/**\n * 登录日志 Service 接口\n */\npublic interface LoginLogService {\n\n    /**\n     * 获得登录日志分页\n     *\n     * @param pageReqVO 分页条件\n     * @return 登录日志分页\n     */\n    PageResult<LoginLogDO> getLoginLogPage(LoginLogPageReqVO pageReqVO);\n\n    /**\n     * 创建登录日志\n     *\n     * @param reqDTO 日志信息\n     */\n    void createLoginLog(@Valid LoginLogCreateReqDTO reqDTO);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/logger/LoginLogServiceImpl.java",
    "content": "package co.yixiang.yshop.module.system.service.logger;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.api.logger.dto.LoginLogCreateReqDTO;\nimport co.yixiang.yshop.module.system.controller.admin.logger.vo.loginlog.LoginLogPageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.logger.LoginLogDO;\nimport co.yixiang.yshop.module.system.dal.mysql.logger.LoginLogMapper;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\n\n/**\n * 登录日志 Service 实现\n */\n@Service\n@Validated\npublic class LoginLogServiceImpl implements LoginLogService {\n\n    @Resource\n    private LoginLogMapper loginLogMapper;\n\n    @Override\n    public PageResult<LoginLogDO> getLoginLogPage(LoginLogPageReqVO pageReqVO) {\n        return loginLogMapper.selectPage(pageReqVO);\n    }\n\n    @Override\n    public void createLoginLog(LoginLogCreateReqDTO reqDTO) {\n        LoginLogDO loginLog = BeanUtils.toBean(reqDTO, LoginLogDO.class);\n        loginLogMapper.insert(loginLog);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/logger/OperateLogService.java",
    "content": "package co.yixiang.yshop.module.system.service.logger;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.system.api.logger.dto.OperateLogCreateReqDTO;\nimport co.yixiang.yshop.module.system.api.logger.dto.OperateLogPageReqDTO;\nimport co.yixiang.yshop.module.system.controller.admin.logger.vo.operatelog.OperateLogPageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.logger.OperateLogDO;\n\n/**\n * 操作日志 Service 接口\n *\n * @author yshop\n */\npublic interface OperateLogService {\n\n    /**\n     * 记录操作日志\n     *\n     * @param createReqDTO 创建请求\n     */\n    void createOperateLog(OperateLogCreateReqDTO createReqDTO);\n\n    /**\n     * 获得操作日志分页列表\n     *\n     * @param pageReqVO 分页条件\n     * @return 操作日志分页列表\n     */\n    PageResult<OperateLogDO> getOperateLogPage(OperateLogPageReqVO pageReqVO);\n\n    /**\n     * 获得操作日志分页列表\n     *\n     * @param pageReqVO 分页条件\n     * @return 操作日志分页列表\n     */\n    PageResult<OperateLogDO> getOperateLogPage(OperateLogPageReqDTO pageReqVO);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/logger/OperateLogServiceImpl.java",
    "content": "package co.yixiang.yshop.module.system.service.logger;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.api.logger.dto.OperateLogCreateReqDTO;\nimport co.yixiang.yshop.module.system.api.logger.dto.OperateLogPageReqDTO;\nimport co.yixiang.yshop.module.system.controller.admin.logger.vo.operatelog.OperateLogPageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.logger.OperateLogDO;\nimport co.yixiang.yshop.module.system.dal.mysql.logger.OperateLogMapper;\nimport jakarta.annotation.Resource;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\n/**\n * 操作日志 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\n@Slf4j\npublic class OperateLogServiceImpl implements OperateLogService {\n\n    @Resource\n    private OperateLogMapper operateLogMapper;\n\n    @Override\n    public void createOperateLog(OperateLogCreateReqDTO createReqDTO) {\n        OperateLogDO log = BeanUtils.toBean(createReqDTO, OperateLogDO.class);\n        operateLogMapper.insert(log);\n    }\n\n    @Override\n    public PageResult<OperateLogDO> getOperateLogPage(OperateLogPageReqVO pageReqVO) {\n        return operateLogMapper.selectPage(pageReqVO);\n    }\n\n    @Override\n    public PageResult<OperateLogDO> getOperateLogPage(OperateLogPageReqDTO pageReqDTO) {\n        return operateLogMapper.selectPage(pageReqDTO);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/mail/MailAccountService.java",
    "content": "package co.yixiang.yshop.module.system.service.mail;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.system.controller.admin.mail.vo.account.MailAccountPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.mail.vo.account.MailAccountSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.mail.MailAccountDO;\n\nimport jakarta.validation.Valid;\nimport java.util.List;\n\n/**\n * 邮箱账号 Service 接口\n *\n * @author wangjingyi\n * @since 2022-03-21\n */\npublic interface MailAccountService {\n\n    /**\n     * 创建邮箱账号\n     *\n     * @param createReqVO 邮箱账号信息\n     * @return 编号\n     */\n    Long createMailAccount(@Valid MailAccountSaveReqVO createReqVO);\n\n    /**\n     * 修改邮箱账号\n     *\n     * @param updateReqVO 邮箱账号信息\n     */\n    void updateMailAccount(@Valid MailAccountSaveReqVO updateReqVO);\n\n    /**\n     * 删除邮箱账号\n     *\n     * @param id 编号\n     */\n    void deleteMailAccount(Long id);\n\n    /**\n     * 获取邮箱账号信息\n     *\n     * @param id 编号\n     * @return 邮箱账号信息\n     */\n    MailAccountDO getMailAccount(Long id);\n\n    /**\n     * 从缓存中获取邮箱账号\n     *\n     * @param id 编号\n     * @return 邮箱账号\n     */\n    MailAccountDO getMailAccountFromCache(Long id);\n\n    /**\n     * 获取邮箱账号分页信息\n     *\n     * @param pageReqVO 邮箱账号分页参数\n     * @return 邮箱账号分页信息\n     */\n    PageResult<MailAccountDO> getMailAccountPage(MailAccountPageReqVO pageReqVO);\n\n    /**\n     * 获取邮箱数组信息\n     *\n     * @return 邮箱账号信息数组\n     */\n    List<MailAccountDO> getMailAccountList();\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/mail/MailAccountServiceImpl.java",
    "content": "package co.yixiang.yshop.module.system.service.mail;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.controller.admin.mail.vo.account.MailAccountPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.mail.vo.account.MailAccountSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.mail.MailAccountDO;\nimport co.yixiang.yshop.module.system.dal.mysql.mail.MailAccountMapper;\nimport co.yixiang.yshop.module.system.dal.redis.RedisKeyConstants;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.cache.annotation.CacheEvict;\nimport org.springframework.cache.annotation.Cacheable;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.MAIL_ACCOUNT_NOT_EXISTS;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.MAIL_ACCOUNT_RELATE_TEMPLATE_EXISTS;\n\n/**\n * 邮箱账号 Service 实现类\n *\n * @author wangjingyi\n * @since 2022-03-21\n */\n@Service\n@Validated\n@Slf4j\npublic class MailAccountServiceImpl implements MailAccountService {\n\n    @Resource\n    private MailAccountMapper mailAccountMapper;\n\n    @Resource\n    private MailTemplateService mailTemplateService;\n\n    @Override\n    public Long createMailAccount(MailAccountSaveReqVO createReqVO) {\n        MailAccountDO account = BeanUtils.toBean(createReqVO, MailAccountDO.class);\n        mailAccountMapper.insert(account);\n        return account.getId();\n    }\n\n    @Override\n    @CacheEvict(value = RedisKeyConstants.MAIL_ACCOUNT, key = \"#updateReqVO.id\")\n    public void updateMailAccount(MailAccountSaveReqVO updateReqVO) {\n        // 校验是否存在\n        validateMailAccountExists(updateReqVO.getId());\n\n        // 更新\n        MailAccountDO updateObj = BeanUtils.toBean(updateReqVO, MailAccountDO.class);\n        mailAccountMapper.updateById(updateObj);\n    }\n\n    @Override\n    @CacheEvict(value = RedisKeyConstants.MAIL_ACCOUNT, key = \"#id\")\n    public void deleteMailAccount(Long id) {\n        // 校验是否存在账号\n        validateMailAccountExists(id);\n        // 校验是否存在关联模版\n        if (mailTemplateService.getMailTemplateCountByAccountId(id) > 0) {\n            throw exception(MAIL_ACCOUNT_RELATE_TEMPLATE_EXISTS);\n        }\n\n        // 删除\n        mailAccountMapper.deleteById(id);\n    }\n\n    private void validateMailAccountExists(Long id) {\n        if (mailAccountMapper.selectById(id) == null) {\n            throw exception(MAIL_ACCOUNT_NOT_EXISTS);\n        }\n    }\n\n    @Override\n    public MailAccountDO getMailAccount(Long id) {\n        return mailAccountMapper.selectById(id);\n    }\n\n    @Override\n    @Cacheable(value = RedisKeyConstants.MAIL_ACCOUNT, key = \"#id\", unless = \"#result == null\")\n    public MailAccountDO getMailAccountFromCache(Long id) {\n        return getMailAccount(id);\n    }\n\n    @Override\n    public PageResult<MailAccountDO> getMailAccountPage(MailAccountPageReqVO pageReqVO) {\n        return mailAccountMapper.selectPage(pageReqVO);\n    }\n\n    @Override\n    public List<MailAccountDO> getMailAccountList() {\n        return mailAccountMapper.selectList();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/mail/MailLogService.java",
    "content": "package co.yixiang.yshop.module.system.service.mail;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.system.controller.admin.mail.vo.log.MailLogPageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.mail.MailAccountDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.mail.MailLogDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.mail.MailTemplateDO;\n\nimport java.util.Map;\n\n/**\n * 邮件日志 Service 接口\n *\n * @author wangjingyi\n * @since 2022-03-21\n */\npublic interface MailLogService {\n\n    /**\n     * 邮件日志分页\n     *\n     * @param pageVO 分页参数\n     * @return 分页结果\n     */\n    PageResult<MailLogDO> getMailLogPage(MailLogPageReqVO pageVO);\n\n    /**\n     * 获得指定编号的邮件日志\n     *\n     * @param id 日志编号\n     * @return 邮件日志\n     */\n    MailLogDO getMailLog(Long id);\n\n    /**\n     * 创建邮件日志\n     *\n     * @param userId 用户编码\n     * @param userType 用户类型\n     * @param toMail 收件人邮件\n     * @param account 邮件账号信息\n     * @param template      模版信息\n     * @param templateContent 模版内容\n     * @param templateParams 模版参数\n     * @param isSend        是否发送成功\n     * @return 日志编号\n     */\n    Long createMailLog(Long userId, Integer userType, String toMail,\n                       MailAccountDO account, MailTemplateDO template ,\n                       String templateContent, Map<String, Object> templateParams, Boolean isSend);\n\n    /**\n     * 更新邮件发送结果\n     *\n     * @param logId  日志编号\n     * @param messageId 发送后的消息编号\n     * @param exception 发送异常\n     */\n    void updateMailSendResult(Long logId, String messageId, Exception exception);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/mail/MailLogServiceImpl.java",
    "content": "package co.yixiang.yshop.module.system.service.mail;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.system.controller.admin.mail.vo.log.MailLogPageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.mail.MailAccountDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.mail.MailLogDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.mail.MailTemplateDO;\nimport co.yixiang.yshop.module.system.dal.mysql.mail.MailLogMapper;\nimport co.yixiang.yshop.module.system.enums.mail.MailSendStatusEnum;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\nimport java.time.LocalDateTime;\nimport java.util.Map;\nimport java.util.Objects;\n\nimport static cn.hutool.core.exceptions.ExceptionUtil.getRootCauseMessage;\n\n/**\n * 邮件日志 Service 实现类\n *\n * @author wangjingyi\n * @since 2022-03-21\n */\n@Service\n@Validated\npublic class MailLogServiceImpl implements MailLogService {\n\n    @Resource\n    private MailLogMapper mailLogMapper;\n\n    @Override\n    public PageResult<MailLogDO> getMailLogPage(MailLogPageReqVO pageVO) {\n        return mailLogMapper.selectPage(pageVO);\n    }\n\n    @Override\n    public MailLogDO getMailLog(Long id) {\n        return mailLogMapper.selectById(id);\n    }\n\n    @Override\n    public Long createMailLog(Long userId, Integer userType, String toMail,\n                              MailAccountDO account, MailTemplateDO template,\n                              String templateContent, Map<String, Object> templateParams, Boolean isSend) {\n        MailLogDO.MailLogDOBuilder logDOBuilder = MailLogDO.builder();\n        // 根据是否要发送，设置状态\n        logDOBuilder.sendStatus(Objects.equals(isSend, true) ? MailSendStatusEnum.INIT.getStatus()\n                : MailSendStatusEnum.IGNORE.getStatus())\n                // 用户信息\n                .userId(userId).userType(userType).toMail(toMail)\n                .accountId(account.getId()).fromMail(account.getMail())\n                // 模板相关字段\n                .templateId(template.getId()).templateCode(template.getCode()).templateNickname(template.getNickname())\n                .templateTitle(template.getTitle()).templateContent(templateContent).templateParams(templateParams);\n\n        // 插入数据库\n        MailLogDO logDO = logDOBuilder.build();\n        mailLogMapper.insert(logDO);\n        return logDO.getId();\n    }\n\n    @Override\n    public void updateMailSendResult(Long logId, String messageId, Exception exception) {\n        // 1. 成功\n        if (exception == null) {\n            mailLogMapper.updateById(new MailLogDO().setId(logId).setSendTime(LocalDateTime.now())\n                    .setSendStatus(MailSendStatusEnum.SUCCESS.getStatus()).setSendMessageId(messageId));\n            return;\n        }\n        // 2. 失败\n        mailLogMapper.updateById(new MailLogDO().setId(logId).setSendTime(LocalDateTime.now())\n                .setSendStatus(MailSendStatusEnum.FAILURE.getStatus()).setSendException(getRootCauseMessage(exception)));\n\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/mail/MailSendService.java",
    "content": "package co.yixiang.yshop.module.system.service.mail;\n\nimport co.yixiang.yshop.module.system.mq.message.mail.MailSendMessage;\n\nimport java.util.Map;\n\n/**\n * 邮件发送 Service 接口\n *\n * @author wangjingyi\n * @since 2022-03-21\n */\npublic interface MailSendService {\n\n    /**\n     * 发送单条邮件给管理后台的用户\n     *\n     * @param mail 邮箱\n     * @param userId 用户编码\n     * @param templateCode 邮件模版编码\n     * @param templateParams 邮件模版参数\n     * @return 发送日志编号\n     */\n    Long sendSingleMailToAdmin(String mail, Long userId,\n                               String templateCode, Map<String, Object> templateParams);\n\n    /**\n     * 发送单条邮件给用户 APP 的用户\n     *\n     * @param mail 邮箱\n     * @param userId 用户编码\n     * @param templateCode 邮件模版编码\n     * @param templateParams 邮件模版参数\n     * @return 发送日志编号\n     */\n    Long sendSingleMailToMember(String mail, Long userId,\n                                String templateCode, Map<String, Object> templateParams);\n\n    /**\n     * 发送单条邮件给用户\n     *\n     * @param mail 邮箱\n     * @param userId 用户编码\n     * @param userType 用户类型\n     * @param templateCode 邮件模版编码\n     * @param templateParams 邮件模版参数\n     * @return 发送日志编号\n     */\n    Long sendSingleMail(String mail, Long userId, Integer userType,\n                        String templateCode, Map<String, Object> templateParams);\n\n    /**\n     * 执行真正的邮件发送\n     * 注意，该方法仅仅提供给 MQ Consumer 使用\n     *\n     * @param message 邮件\n     */\n    void doSendMail(MailSendMessage message);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/mail/MailSendServiceImpl.java",
    "content": "package co.yixiang.yshop.module.system.service.mail;\n\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.module.system.dal.dataobject.mail.MailAccountDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.mail.MailTemplateDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.user.AdminUserDO;\nimport co.yixiang.yshop.module.system.mq.message.mail.MailSendMessage;\nimport co.yixiang.yshop.module.system.mq.producer.mail.MailProducer;\nimport co.yixiang.yshop.module.system.service.member.MemberService;\nimport co.yixiang.yshop.module.system.service.user.AdminUserService;\nimport com.google.common.annotations.VisibleForTesting;\nimport jakarta.annotation.Resource;\nimport lombok.extern.slf4j.Slf4j;\nimport org.dromara.hutool.extra.mail.*;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport java.util.Map;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.*;\n\n/**\n * 邮箱发送 Service 实现类\n *\n * @author wangjingyi\n * @since 2022-03-21\n */\n@Service\n@Validated\n@Slf4j\npublic class MailSendServiceImpl implements MailSendService {\n\n    @Resource\n    private AdminUserService adminUserService;\n    @Resource\n    private MemberService memberService;\n\n    @Resource\n    private MailAccountService mailAccountService;\n    @Resource\n    private MailTemplateService mailTemplateService;\n\n    @Resource\n    private MailLogService mailLogService;\n    @Resource\n    private MailProducer mailProducer;\n\n    @Override\n    public Long sendSingleMailToAdmin(String mail, Long userId,\n                                      String templateCode, Map<String, Object> templateParams) {\n        // 如果 mail 为空，则加载用户编号对应的邮箱\n        if (StrUtil.isEmpty(mail)) {\n            AdminUserDO user = adminUserService.getUser(userId);\n            if (user != null) {\n                mail = user.getEmail();\n            }\n        }\n        // 执行发送\n        return sendSingleMail(mail, userId, UserTypeEnum.ADMIN.getValue(), templateCode, templateParams);\n    }\n\n    @Override\n    public Long sendSingleMailToMember(String mail, Long userId,\n                                       String templateCode, Map<String, Object> templateParams) {\n        // 如果 mail 为空，则加载用户编号对应的邮箱\n        if (StrUtil.isEmpty(mail)) {\n            mail = memberService.getMemberUserEmail(userId);\n        }\n        // 执行发送\n        return sendSingleMail(mail, userId, UserTypeEnum.MEMBER.getValue(), templateCode, templateParams);\n    }\n\n    @Override\n    public Long sendSingleMail(String mail, Long userId, Integer userType,\n                               String templateCode, Map<String, Object> templateParams) {\n        // 校验邮箱模版是否合法\n        MailTemplateDO template = validateMailTemplate(templateCode);\n        // 校验邮箱账号是否合法\n        MailAccountDO account = validateMailAccount(template.getAccountId());\n\n        // 校验邮箱是否存在\n        mail = validateMail(mail);\n        validateTemplateParams(template, templateParams);\n\n        // 创建发送日志。如果模板被禁用，则不发送短信，只记录日志\n        Boolean isSend = CommonStatusEnum.ENABLE.getStatus().equals(template.getStatus());\n        String title = mailTemplateService.formatMailTemplateContent(template.getTitle(), templateParams);\n        String content = mailTemplateService.formatMailTemplateContent(template.getContent(), templateParams);\n        Long sendLogId = mailLogService.createMailLog(userId, userType, mail,\n                account, template, content, templateParams, isSend);\n        // 发送 MQ 消息，异步执行发送短信\n        if (isSend) {\n            mailProducer.sendMailSendMessage(sendLogId, mail, account.getId(),\n                    template.getNickname(), title, content);\n        }\n        return sendLogId;\n    }\n\n    @Override\n    public void doSendMail(MailSendMessage message) {\n        // 1. 创建发送账号\n        MailAccountDO account = validateMailAccount(message.getAccountId());\n        MailAccount mailAccount  = buildMailAccount(account, message.getNickname());\n        // 2. 发送邮件\n        try {\n            String messageId = MailUtil.send(mailAccount, message.getMail(),\n                    message.getTitle(), message.getContent(), true);\n            // 3. 更新结果（成功）\n            mailLogService.updateMailSendResult(message.getLogId(), messageId, null);\n        } catch (Exception e) {\n            // 3. 更新结果（异常）\n            mailLogService.updateMailSendResult(message.getLogId(), null, e);\n        }\n    }\n\n    private MailAccount buildMailAccount(MailAccountDO account, String nickname) {\n        String from = StrUtil.isNotEmpty(nickname) ? nickname + \" <\" + account.getMail() + \">\" : account.getMail();\n        return new MailAccount().setFrom(from).setAuth(true)\n                .setUser(account.getUsername()).setPass(account.getPassword().toCharArray())\n                .setHost(account.getHost()).setPort(account.getPort())\n                .setSslEnable(account.getSslEnable()).setStarttlsEnable(account.getStarttlsEnable());\n    }\n\n    @VisibleForTesting\n    MailTemplateDO validateMailTemplate(String templateCode) {\n        // 获得邮件模板。考虑到效率，从缓存中获取\n        MailTemplateDO template = mailTemplateService.getMailTemplateByCodeFromCache(templateCode);\n        // 邮件模板不存在\n        if (template == null) {\n            throw exception(MAIL_TEMPLATE_NOT_EXISTS);\n        }\n        return template;\n    }\n\n    @VisibleForTesting\n    MailAccountDO validateMailAccount(Long accountId) {\n        // 获得邮箱账号。考虑到效率，从缓存中获取\n        MailAccountDO account = mailAccountService.getMailAccountFromCache(accountId);\n        // 邮箱账号不存在\n        if (account == null) {\n            throw exception(MAIL_ACCOUNT_NOT_EXISTS);\n        }\n        return account;\n    }\n\n    @VisibleForTesting\n    String validateMail(String mail) {\n        if (StrUtil.isEmpty(mail)) {\n            throw exception(MAIL_SEND_MAIL_NOT_EXISTS);\n        }\n        return mail;\n    }\n\n    /**\n     * 校验邮件参数是否确实\n     *\n     * @param template 邮箱模板\n     * @param templateParams 参数列表\n     */\n    @VisibleForTesting\n    void validateTemplateParams(MailTemplateDO template, Map<String, Object> templateParams) {\n        template.getParams().forEach(key -> {\n            Object value = templateParams.get(key);\n            if (value == null) {\n                throw exception(MAIL_SEND_TEMPLATE_PARAM_MISS, key);\n            }\n        });\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/mail/MailTemplateService.java",
    "content": "package co.yixiang.yshop.module.system.service.mail;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.system.controller.admin.mail.vo.template.MailTemplatePageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.mail.vo.template.MailTemplateSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.mail.MailTemplateDO;\n\nimport jakarta.validation.Valid;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * 邮件模版 Service 接口\n *\n * @author wangjingyi\n * @since 2022-03-21\n */\npublic interface MailTemplateService {\n\n    /**\n     * 邮件模版创建\n     *\n     * @param createReqVO 邮件信息\n     * @return 编号\n     */\n    Long createMailTemplate(@Valid MailTemplateSaveReqVO createReqVO);\n\n    /**\n     * 邮件模版修改\n     *\n     * @param updateReqVO 邮件信息\n     */\n    void updateMailTemplate(@Valid MailTemplateSaveReqVO updateReqVO);\n\n    /**\n     * 邮件模版删除\n     *\n     * @param id 编号\n     */\n    void deleteMailTemplate(Long id);\n\n    /**\n     * 获取邮件模版\n     *\n     * @param id 编号\n     * @return 邮件模版\n     */\n    MailTemplateDO getMailTemplate(Long id);\n\n    /**\n     * 获取邮件模版分页\n     *\n     * @param pageReqVO 模版信息\n     * @return 邮件模版分页信息\n     */\n    PageResult<MailTemplateDO> getMailTemplatePage(MailTemplatePageReqVO pageReqVO);\n\n    /**\n     * 获取邮件模板数组\n     *\n     * @return 模版数组\n     */\n    List<MailTemplateDO> getMailTemplateList();\n\n    /**\n     * 从缓存中获取邮件模版\n     *\n     * @param code 模板编码\n     * @return 邮件模板\n     */\n    MailTemplateDO getMailTemplateByCodeFromCache(String code);\n\n    /**\n     * 邮件模版内容合成\n     *\n     * @param content 邮件模版\n     * @param params 合成参数\n     * @return 格式化后的内容\n     */\n    String formatMailTemplateContent(String content, Map<String, Object> params);\n\n    /**\n     * 获得指定邮件账号下的邮件模板数量\n     *\n     * @param accountId 账号编号\n     * @return 数量\n     */\n    long getMailTemplateCountByAccountId(Long accountId);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/mail/MailTemplateServiceImpl.java",
    "content": "package co.yixiang.yshop.module.system.service.mail;\n\nimport cn.hutool.core.util.ObjUtil;\nimport cn.hutool.core.util.ReUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.controller.admin.mail.vo.template.MailTemplatePageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.mail.vo.template.MailTemplateSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.mail.MailTemplateDO;\nimport co.yixiang.yshop.module.system.dal.mysql.mail.MailTemplateMapper;\nimport co.yixiang.yshop.module.system.dal.redis.RedisKeyConstants;\nimport com.google.common.annotations.VisibleForTesting;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.cache.annotation.CacheEvict;\nimport org.springframework.cache.annotation.Cacheable;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\nimport jakarta.validation.Valid;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.regex.Pattern;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.MAIL_TEMPLATE_CODE_EXISTS;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.MAIL_TEMPLATE_NOT_EXISTS;\n\n/**\n * 邮箱模版 Service 实现类\n *\n * @author wangjingyi\n * @since 2022-03-21\n */\n@Service\n@Validated\n@Slf4j\npublic class MailTemplateServiceImpl implements MailTemplateService {\n\n    /**\n     * 正则表达式，匹配 {} 中的变量\n     */\n    private static final Pattern PATTERN_PARAMS = Pattern.compile(\"\\\\{(.*?)}\");\n\n    @Resource\n    private MailTemplateMapper mailTemplateMapper;\n\n    @Override\n    public Long createMailTemplate(MailTemplateSaveReqVO createReqVO) {\n        // 校验 code 是否唯一\n        validateCodeUnique(null, createReqVO.getCode());\n\n        // 插入\n        MailTemplateDO template = BeanUtils.toBean(createReqVO, MailTemplateDO.class)\n                .setParams(parseTemplateContentParams(createReqVO.getContent()));\n        mailTemplateMapper.insert(template);\n        return template.getId();\n    }\n\n    @Override\n    @CacheEvict(cacheNames = RedisKeyConstants.MAIL_TEMPLATE,\n            allEntries = true) // allEntries 清空所有缓存，因为可能修改到 code 字段，不好清理\n    public void updateMailTemplate(@Valid MailTemplateSaveReqVO updateReqVO) {\n        // 校验是否存在\n        validateMailTemplateExists(updateReqVO.getId());\n        // 校验 code 是否唯一\n        validateCodeUnique(updateReqVO.getId(),updateReqVO.getCode());\n\n        // 更新\n        MailTemplateDO updateObj = BeanUtils.toBean(updateReqVO, MailTemplateDO.class)\n                .setParams(parseTemplateContentParams(updateReqVO.getContent()));\n        mailTemplateMapper.updateById(updateObj);\n    }\n\n    @VisibleForTesting\n    void validateCodeUnique(Long id, String code) {\n        MailTemplateDO template = mailTemplateMapper.selectByCode(code);\n        if (template == null) {\n            return;\n        }\n        // 存在 template 记录的情况下\n        if (id == null // 新增时，说明重复\n                || ObjUtil.notEqual(id, template.getId())) { // 更新时，如果 id 不一致，说明重复\n            throw exception(MAIL_TEMPLATE_CODE_EXISTS);\n        }\n    }\n\n    @Override\n    @CacheEvict(cacheNames = RedisKeyConstants.MAIL_TEMPLATE,\n            allEntries = true) // allEntries 清空所有缓存，因为 id 不是直接的缓存 code，不好清理\n    public void deleteMailTemplate(Long id) {\n        // 校验是否存在\n        validateMailTemplateExists(id);\n\n        // 删除\n        mailTemplateMapper.deleteById(id);\n    }\n\n    private void validateMailTemplateExists(Long id) {\n        if (mailTemplateMapper.selectById(id) == null) {\n            throw exception(MAIL_TEMPLATE_NOT_EXISTS);\n        }\n    }\n\n    @Override\n    public MailTemplateDO getMailTemplate(Long id) {return mailTemplateMapper.selectById(id);}\n\n    @Override\n    @Cacheable(value = RedisKeyConstants.MAIL_TEMPLATE, key = \"#code\", unless = \"#result == null\")\n    public MailTemplateDO getMailTemplateByCodeFromCache(String code) {\n        return mailTemplateMapper.selectByCode(code);\n    }\n\n    @Override\n    public PageResult<MailTemplateDO> getMailTemplatePage(MailTemplatePageReqVO pageReqVO) {\n        return mailTemplateMapper.selectPage(pageReqVO);\n    }\n\n    @Override\n    public List<MailTemplateDO> getMailTemplateList() {return mailTemplateMapper.selectList();}\n\n    @Override\n    public String formatMailTemplateContent(String content, Map<String, Object> params) {\n        return StrUtil.format(content, params);\n    }\n\n    @VisibleForTesting\n    public List<String> parseTemplateContentParams(String content) {\n        return ReUtil.findAllGroup1(PATTERN_PARAMS, content);\n    }\n\n    @Override\n    public long getMailTemplateCountByAccountId(Long accountId) {\n        return mailTemplateMapper.selectCountByAccountId(accountId);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/member/MemberService.java",
    "content": "package co.yixiang.yshop.module.system.service.member;\n\n/**\n * Member Service 接口\n *\n * @author yshop\n */\npublic interface MemberService {\n\n    /**\n     * 获得会员用户的手机号码\n     *\n     * @param id 会员用户编号\n     * @return 手机号码\n     */\n    String getMemberUserMobile(Long id);\n\n    /**\n     * 获得会员用户的邮箱\n     *\n     * @param id 会员用户编号\n     * @return 邮箱\n     */\n    String getMemberUserEmail(Long id);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/member/MemberServiceImpl.java",
    "content": "package co.yixiang.yshop.module.system.service.member;\n\nimport cn.hutool.core.util.ClassUtil;\nimport cn.hutool.core.util.ReflectUtil;\nimport cn.hutool.extra.spring.SpringUtil;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.stereotype.Service;\n\n/**\n * Member Service 实现类\n *\n * @author yshop\n */\n@Service\npublic class MemberServiceImpl implements MemberService {\n\n    @Value(\"${yshop.info.base-package}\")\n    private String basePackage;\n\n    private volatile Object memberUserApi;\n\n    @Override\n    public String getMemberUserMobile(Long id) {\n        Object user = getMemberUser(id);\n        if (user == null) {\n            return null;\n        }\n        return ReflectUtil.invoke(user, \"getMobile\");\n    }\n\n    @Override\n    public String getMemberUserEmail(Long id) {\n        Object user = getMemberUser(id);\n        if (user == null) {\n            return null;\n        }\n        return ReflectUtil.invoke(user, \"getEmail\");\n    }\n\n    private Object getMemberUser(Long id) {\n        if (id == null) {\n            return null;\n        }\n        return ReflectUtil.invoke(getMemberUserApi(), \"getUser\", id);\n    }\n\n    private Object getMemberUserApi() {\n        if (memberUserApi == null) {\n            memberUserApi = SpringUtil.getBean(ClassUtil.loadClass(String.format(\"%s.module.member.api.user.MemberUserApi\", basePackage)));\n        }\n        return memberUserApi;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/notice/NoticeService.java",
    "content": "package co.yixiang.yshop.module.system.service.notice;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.system.controller.admin.notice.vo.NoticePageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.notice.vo.NoticeSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.notice.NoticeDO;\n\n/**\n * 通知公告 Service 接口\n */\npublic interface NoticeService {\n\n    /**\n     * 创建通知公告\n     *\n     * @param createReqVO 通知公告\n     * @return 编号\n     */\n    Long createNotice(NoticeSaveReqVO createReqVO);\n\n    /**\n     * 更新通知公告\n     *\n     * @param reqVO 通知公告\n     */\n    void updateNotice(NoticeSaveReqVO reqVO);\n\n    /**\n     * 删除通知公告\n     *\n     * @param id 编号\n     */\n    void deleteNotice(Long id);\n\n    /**\n     * 获得通知公告分页列表\n     *\n     * @param reqVO 分页条件\n     * @return 部门分页列表\n     */\n    PageResult<NoticeDO> getNoticePage(NoticePageReqVO reqVO);\n\n    /**\n     * 获得通知公告\n     *\n     * @param id 编号\n     * @return 通知公告\n     */\n    NoticeDO getNotice(Long id);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/notice/NoticeServiceImpl.java",
    "content": "package co.yixiang.yshop.module.system.service.notice;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.controller.admin.notice.vo.NoticePageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.notice.vo.NoticeSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.notice.NoticeDO;\nimport co.yixiang.yshop.module.system.dal.mysql.notice.NoticeMapper;\nimport com.google.common.annotations.VisibleForTesting;\nimport org.springframework.stereotype.Service;\n\nimport jakarta.annotation.Resource;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.NOTICE_NOT_FOUND;\n\n/**\n * 通知公告 Service 实现类\n *\n * @author yshop\n */\n@Service\npublic class NoticeServiceImpl implements NoticeService {\n\n    @Resource\n    private NoticeMapper noticeMapper;\n\n    @Override\n    public Long createNotice(NoticeSaveReqVO createReqVO) {\n        NoticeDO notice = BeanUtils.toBean(createReqVO, NoticeDO.class);\n        noticeMapper.insert(notice);\n        return notice.getId();\n    }\n\n    @Override\n    public void updateNotice(NoticeSaveReqVO updateReqVO) {\n        // 校验是否存在\n        validateNoticeExists(updateReqVO.getId());\n        // 更新通知公告\n        NoticeDO updateObj = BeanUtils.toBean(updateReqVO, NoticeDO.class);\n        noticeMapper.updateById(updateObj);\n    }\n\n    @Override\n    public void deleteNotice(Long id) {\n        // 校验是否存在\n        validateNoticeExists(id);\n        // 删除通知公告\n        noticeMapper.deleteById(id);\n    }\n\n    @Override\n    public PageResult<NoticeDO> getNoticePage(NoticePageReqVO reqVO) {\n        return noticeMapper.selectPage(reqVO);\n    }\n\n    @Override\n    public NoticeDO getNotice(Long id) {\n        return noticeMapper.selectById(id);\n    }\n\n    @VisibleForTesting\n    public void validateNoticeExists(Long id) {\n        if (id == null) {\n            return;\n        }\n        NoticeDO notice = noticeMapper.selectById(id);\n        if (notice == null) {\n            throw exception(NOTICE_NOT_FOUND);\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/notify/NotifyMessageService.java",
    "content": "package co.yixiang.yshop.module.system.service.notify;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.system.controller.admin.notify.vo.message.NotifyMessageMyPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.notify.vo.message.NotifyMessagePageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.notify.NotifyMessageDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.notify.NotifyTemplateDO;\n\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * 站内信 Service 接口\n *\n * @author xrcoder\n */\npublic interface NotifyMessageService {\n\n    /**\n     * 创建站内信\n     *\n     * @param userId 用户编号\n     * @param userType 用户类型\n     * @param template 模版信息\n     * @param templateContent 模版内容\n     * @param templateParams 模版参数\n     * @return 站内信编号\n     */\n    Long createNotifyMessage(Long userId, Integer userType,\n                             NotifyTemplateDO template, String templateContent, Map<String, Object> templateParams);\n\n    /**\n     * 获得站内信分页\n     *\n     * @param pageReqVO 分页查询\n     * @return 站内信分页\n     */\n    PageResult<NotifyMessageDO> getNotifyMessagePage(NotifyMessagePageReqVO pageReqVO);\n\n    /**\n     * 获得【我的】站内信分页\n     *\n     * @param pageReqVO 分页查询\n     * @param userId 用户编号\n     * @param userType 用户类型\n     * @return 站内信分页\n     */\n    PageResult<NotifyMessageDO> getMyMyNotifyMessagePage(NotifyMessageMyPageReqVO pageReqVO, Long userId, Integer userType);\n\n    /**\n     * 获得站内信\n     *\n     * @param id 编号\n     * @return 站内信\n     */\n    NotifyMessageDO getNotifyMessage(Long id);\n\n    /**\n     * 获得【我的】未读站内信列表\n     *\n     * @param userId   用户编号\n     * @param userType 用户类型\n     * @param size     数量\n     * @return 站内信列表\n     */\n    List<NotifyMessageDO> getUnreadNotifyMessageList(Long userId, Integer userType, Integer size);\n\n    /**\n     * 统计用户未读站内信条数\n     *\n     * @param userId   用户编号\n     * @param userType 用户类型\n     * @return 返回未读站内信条数\n     */\n    Long getUnreadNotifyMessageCount(Long userId, Integer userType);\n\n    /**\n     * 标记站内信为已读\n     *\n     * @param ids    站内信编号集合\n     * @param userId 用户编号\n     * @param userType 用户类型\n     * @return 更新到的条数\n     */\n    int updateNotifyMessageRead(Collection<Long> ids, Long userId, Integer userType);\n\n    /**\n     * 标记所有站内信为已读\n     *\n     * @param userId   用户编号\n     * @param userType 用户类型\n     * @return 更新到的条数\n     */\n    int updateAllNotifyMessageRead(Long userId, Integer userType);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/notify/NotifyMessageServiceImpl.java",
    "content": "package co.yixiang.yshop.module.system.service.notify;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.system.controller.admin.notify.vo.message.NotifyMessageMyPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.notify.vo.message.NotifyMessagePageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.notify.NotifyMessageDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.notify.NotifyTemplateDO;\nimport co.yixiang.yshop.module.system.dal.mysql.notify.NotifyMessageMapper;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * 站内信 Service 实现类\n *\n * @author xrcoder\n */\n@Service\n@Validated\npublic class NotifyMessageServiceImpl implements NotifyMessageService {\n\n    @Resource\n    private NotifyMessageMapper notifyMessageMapper;\n\n    @Override\n    public Long createNotifyMessage(Long userId, Integer userType,\n                                    NotifyTemplateDO template, String templateContent, Map<String, Object> templateParams) {\n        NotifyMessageDO message = new NotifyMessageDO().setUserId(userId).setUserType(userType)\n                .setTemplateId(template.getId()).setTemplateCode(template.getCode())\n                .setTemplateType(template.getType()).setTemplateNickname(template.getNickname())\n                .setTemplateContent(templateContent).setTemplateParams(templateParams).setReadStatus(false);\n        notifyMessageMapper.insert(message);\n        return message.getId();\n    }\n\n    @Override\n    public PageResult<NotifyMessageDO> getNotifyMessagePage(NotifyMessagePageReqVO pageReqVO) {\n        return notifyMessageMapper.selectPage(pageReqVO);\n    }\n\n    @Override\n    public PageResult<NotifyMessageDO> getMyMyNotifyMessagePage(NotifyMessageMyPageReqVO pageReqVO, Long userId, Integer userType) {\n        return notifyMessageMapper.selectPage(pageReqVO, userId, userType);\n    }\n\n    @Override\n    public NotifyMessageDO getNotifyMessage(Long id) {\n        return notifyMessageMapper.selectById(id);\n    }\n\n    @Override\n    public List<NotifyMessageDO> getUnreadNotifyMessageList(Long userId, Integer userType, Integer size) {\n        return notifyMessageMapper.selectUnreadListByUserIdAndUserType(userId, userType, size);\n    }\n\n    @Override\n    public Long getUnreadNotifyMessageCount(Long userId, Integer userType) {\n        return notifyMessageMapper.selectUnreadCountByUserIdAndUserType(userId, userType);\n    }\n\n    @Override\n    public int updateNotifyMessageRead(Collection<Long> ids, Long userId, Integer userType) {\n        return notifyMessageMapper.updateListRead(ids, userId, userType);\n    }\n\n    @Override\n    public int updateAllNotifyMessageRead(Long userId, Integer userType) {\n        return notifyMessageMapper.updateListRead(userId, userType);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/notify/NotifySendService.java",
    "content": "package co.yixiang.yshop.module.system.service.notify;\n\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * 站内信发送 Service 接口\n *\n * @author xrcoder\n */\npublic interface NotifySendService {\n\n    /**\n     * 发送单条站内信给管理后台的用户\n     *\n     * 在 mobile 为空时，使用 userId 加载对应管理员的手机号\n     *\n     * @param userId 用户编号\n     * @param templateCode 短信模板编号\n     * @param templateParams 短信模板参数\n     * @return 发送日志编号\n     */\n    Long sendSingleNotifyToAdmin(Long userId,\n                                 String templateCode, Map<String, Object> templateParams);\n    /**\n     * 发送单条站内信给用户 APP 的用户\n     *\n     * 在 mobile 为空时，使用 userId 加载对应会员的手机号\n     *\n     * @param userId 用户编号\n     * @param templateCode 站内信模板编号\n     * @param templateParams 站内信模板参数\n     * @return 发送日志编号\n     */\n    Long sendSingleNotifyToMember(Long userId,\n                                  String templateCode, Map<String, Object> templateParams);\n\n    /**\n     * 发送单条站内信给用户\n     *\n     * @param userId 用户编号\n     * @param userType 用户类型\n     * @param templateCode 站内信模板编号\n     * @param templateParams 站内信模板参数\n     * @return 发送日志编号\n     */\n    Long sendSingleNotify( Long userId, Integer userType,\n                           String templateCode, Map<String, Object> templateParams);\n\n    default void sendBatchNotify(List<String> mobiles, List<Long> userIds, Integer userType,\n                                 String templateCode, Map<String, Object> templateParams) {\n        throw new UnsupportedOperationException(\"暂时不支持该操作，感兴趣可以实现该功能哟！\");\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/notify/NotifySendServiceImpl.java",
    "content": "package co.yixiang.yshop.module.system.service.notify;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.module.system.dal.dataobject.notify.NotifyTemplateDO;\nimport com.google.common.annotations.VisibleForTesting;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\nimport java.util.Map;\nimport java.util.Objects;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.*;\n\n/**\n * 站内信发送 Service 实现类\n *\n * @author xrcoder\n */\n@Service\n@Validated\n@Slf4j\npublic class NotifySendServiceImpl implements NotifySendService {\n\n    @Resource\n    private NotifyTemplateService notifyTemplateService;\n\n    @Resource\n    private NotifyMessageService notifyMessageService;\n\n    @Override\n    public Long sendSingleNotifyToAdmin(Long userId, String templateCode, Map<String, Object> templateParams) {\n        return sendSingleNotify(userId, UserTypeEnum.ADMIN.getValue(), templateCode, templateParams);\n    }\n\n    @Override\n    public Long sendSingleNotifyToMember(Long userId, String templateCode, Map<String, Object> templateParams) {\n        return sendSingleNotify(userId, UserTypeEnum.MEMBER.getValue(), templateCode, templateParams);\n    }\n\n    @Override\n    public Long sendSingleNotify(Long userId, Integer userType, String templateCode, Map<String, Object> templateParams) {\n        // 校验模版\n        NotifyTemplateDO template = validateNotifyTemplate(templateCode);\n        if (Objects.equals(template.getStatus(), CommonStatusEnum.DISABLE.getStatus())) {\n            log.info(\"[sendSingleNotify][模版({})已经关闭，无法给用户({}/{})发送]\", templateCode, userId, userType);\n            return null;\n        }\n        // 校验参数\n        validateTemplateParams(template, templateParams);\n\n        // 发送站内信\n        String content = notifyTemplateService.formatNotifyTemplateContent(template.getContent(), templateParams);\n        return notifyMessageService.createNotifyMessage(userId, userType, template, content, templateParams);\n    }\n\n    @VisibleForTesting\n    public NotifyTemplateDO validateNotifyTemplate(String templateCode) {\n        // 获得站内信模板。考虑到效率，从缓存中获取\n        NotifyTemplateDO template = notifyTemplateService.getNotifyTemplateByCodeFromCache(templateCode);\n        // 站内信模板不存在\n        if (template == null) {\n            throw exception(NOTICE_NOT_FOUND);\n        }\n        return template;\n    }\n\n    /**\n     * 校验站内信模版参数是否确实\n     *\n     * @param template 邮箱模板\n     * @param templateParams 参数列表\n     */\n    @VisibleForTesting\n    public void validateTemplateParams(NotifyTemplateDO template, Map<String, Object> templateParams) {\n        template.getParams().forEach(key -> {\n            Object value = templateParams.get(key);\n            if (value == null) {\n                throw exception(NOTIFY_SEND_TEMPLATE_PARAM_MISS, key);\n            }\n        });\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/notify/NotifyTemplateService.java",
    "content": "package co.yixiang.yshop.module.system.service.notify;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.system.controller.admin.notify.vo.template.NotifyTemplatePageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.notify.vo.template.NotifyTemplateSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.notify.NotifyTemplateDO;\n\nimport jakarta.validation.Valid;\nimport java.util.Map;\n\n/**\n * 站内信模版 Service 接口\n *\n * @author xrcoder\n */\npublic interface NotifyTemplateService {\n\n    /**\n     * 创建站内信模版\n     *\n     * @param createReqVO 创建信息\n     * @return 编号\n     */\n    Long createNotifyTemplate(@Valid NotifyTemplateSaveReqVO createReqVO);\n\n    /**\n     * 更新站内信模版\n     *\n     * @param updateReqVO 更新信息\n     */\n    void updateNotifyTemplate(@Valid NotifyTemplateSaveReqVO updateReqVO);\n\n    /**\n     * 删除站内信模版\n     *\n     * @param id 编号\n     */\n    void deleteNotifyTemplate(Long id);\n\n    /**\n     * 获得站内信模版\n     *\n     * @param id 编号\n     * @return 站内信模版\n     */\n    NotifyTemplateDO getNotifyTemplate(Long id);\n\n    /**\n     * 获得站内信模板，从缓存中\n     *\n     * @param code 模板编码\n     * @return 站内信模板\n     */\n    NotifyTemplateDO getNotifyTemplateByCodeFromCache(String code);\n\n    /**\n     * 获得站内信模版分页\n     *\n     * @param pageReqVO 分页查询\n     * @return 站内信模版分页\n     */\n    PageResult<NotifyTemplateDO> getNotifyTemplatePage(NotifyTemplatePageReqVO pageReqVO);\n\n    /**\n     * 格式化站内信内容\n     *\n     * @param content 站内信模板的内容\n     * @param params 站内信内容的参数\n     * @return 格式化后的内容\n     */\n    String formatNotifyTemplateContent(String content, Map<String, Object> params);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/notify/NotifyTemplateServiceImpl.java",
    "content": "package co.yixiang.yshop.module.system.service.notify;\n\nimport cn.hutool.core.util.ReUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.controller.admin.notify.vo.template.NotifyTemplatePageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.notify.vo.template.NotifyTemplateSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.notify.NotifyTemplateDO;\nimport co.yixiang.yshop.module.system.dal.mysql.notify.NotifyTemplateMapper;\nimport co.yixiang.yshop.module.system.dal.redis.RedisKeyConstants;\nimport com.google.common.annotations.VisibleForTesting;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.cache.annotation.CacheEvict;\nimport org.springframework.cache.annotation.Cacheable;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.regex.Pattern;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.NOTIFY_TEMPLATE_CODE_DUPLICATE;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.NOTIFY_TEMPLATE_NOT_EXISTS;\n\n/**\n * 站内信模版 Service 实现类\n *\n * @author xrcoder\n */\n@Service\n@Validated\n@Slf4j\npublic class NotifyTemplateServiceImpl implements NotifyTemplateService {\n\n    /**\n     * 正则表达式，匹配 {} 中的变量\n     */\n    private static final Pattern PATTERN_PARAMS = Pattern.compile(\"\\\\{(.*?)}\");\n\n    @Resource\n    private NotifyTemplateMapper notifyTemplateMapper;\n\n    @Override\n    public Long createNotifyTemplate(NotifyTemplateSaveReqVO createReqVO) {\n        // 校验站内信编码是否重复\n        validateNotifyTemplateCodeDuplicate(null, createReqVO.getCode());\n\n        // 插入\n        NotifyTemplateDO notifyTemplate = BeanUtils.toBean(createReqVO, NotifyTemplateDO.class);\n        notifyTemplate.setParams(parseTemplateContentParams(notifyTemplate.getContent()));\n        notifyTemplateMapper.insert(notifyTemplate);\n        return notifyTemplate.getId();\n    }\n\n    @Override\n    @CacheEvict(cacheNames = RedisKeyConstants.NOTIFY_TEMPLATE,\n            allEntries = true) // allEntries 清空所有缓存，因为可能修改到 code 字段，不好清理\n    public void updateNotifyTemplate(NotifyTemplateSaveReqVO updateReqVO) {\n        // 校验存在\n        validateNotifyTemplateExists(updateReqVO.getId());\n        // 校验站内信编码是否重复\n        validateNotifyTemplateCodeDuplicate(updateReqVO.getId(), updateReqVO.getCode());\n\n        // 更新\n        NotifyTemplateDO updateObj = BeanUtils.toBean(updateReqVO, NotifyTemplateDO.class);\n        updateObj.setParams(parseTemplateContentParams(updateObj.getContent()));\n        notifyTemplateMapper.updateById(updateObj);\n    }\n\n    @VisibleForTesting\n    public List<String> parseTemplateContentParams(String content) {\n        return ReUtil.findAllGroup1(PATTERN_PARAMS, content);\n    }\n\n    @Override\n    @CacheEvict(cacheNames = RedisKeyConstants.NOTIFY_TEMPLATE,\n            allEntries = true) // allEntries 清空所有缓存，因为 id 不是直接的缓存 code，不好清理\n    public void deleteNotifyTemplate(Long id) {\n        // 校验存在\n        validateNotifyTemplateExists(id);\n        // 删除\n        notifyTemplateMapper.deleteById(id);\n    }\n\n    private void validateNotifyTemplateExists(Long id) {\n        if (notifyTemplateMapper.selectById(id) == null) {\n            throw exception(NOTIFY_TEMPLATE_NOT_EXISTS);\n        }\n    }\n\n    @Override\n    public NotifyTemplateDO getNotifyTemplate(Long id) {\n        return notifyTemplateMapper.selectById(id);\n    }\n\n    @Override\n    @Cacheable(cacheNames = RedisKeyConstants.NOTIFY_TEMPLATE, key = \"#code\",\n            unless = \"#result == null\")\n    public NotifyTemplateDO getNotifyTemplateByCodeFromCache(String code) {\n        return notifyTemplateMapper.selectByCode(code);\n    }\n\n    @Override\n    public PageResult<NotifyTemplateDO> getNotifyTemplatePage(NotifyTemplatePageReqVO pageReqVO) {\n        return notifyTemplateMapper.selectPage(pageReqVO);\n    }\n\n    @VisibleForTesting\n    void validateNotifyTemplateCodeDuplicate(Long id, String code) {\n        NotifyTemplateDO template = notifyTemplateMapper.selectByCode(code);\n        if (template == null) {\n            return;\n        }\n        // 如果 id 为空，说明不用比较是否为相同 id 的字典类型\n        if (id == null) {\n            throw exception(NOTIFY_TEMPLATE_CODE_DUPLICATE, code);\n        }\n        if (!template.getId().equals(id)) {\n            throw exception(NOTIFY_TEMPLATE_CODE_DUPLICATE, code);\n        }\n    }\n\n    /**\n     * 格式化站内信内容\n     *\n     * @param content 站内信模板的内容\n     * @param params  站内信内容的参数\n     * @return 格式化后的内容\n     */\n    @Override\n    public String formatNotifyTemplateContent(String content, Map<String, Object> params) {\n        return StrUtil.format(content, params);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/oauth2/OAuth2ApproveService.java",
    "content": "package co.yixiang.yshop.module.system.service.oauth2;\n\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2ApproveDO;\n\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * OAuth2 批准 Service 接口\n *\n * 从功能上，和 Spring Security OAuth 的 ApprovalStoreUserApprovalHandler 的功能，记录用户针对指定客户端的授权，减少手动确定。\n *\n * @author yshop\n */\npublic interface OAuth2ApproveService {\n\n    /**\n     * 获得指定用户，针对指定客户端的指定授权，是否通过\n     *\n     * 参考 ApprovalStoreUserApprovalHandler 的 checkForPreApproval 方法\n     *\n     * @param userId 用户编号\n     * @param userType 用户类型\n     * @param clientId 客户端编号\n     * @param requestedScopes 授权范围\n     * @return 是否授权通过\n     */\n    boolean checkForPreApproval(Long userId, Integer userType, String clientId, Collection<String> requestedScopes);\n\n    /**\n     * 在用户发起批准时，基于 scopes 的选项，计算最终是否通过\n     *\n     * @param userId 用户编号\n     * @param userType 用户类型\n     * @param clientId 客户端编号\n     * @param requestedScopes 授权范围\n     * @return 是否授权通过\n     */\n    boolean updateAfterApproval(Long userId, Integer userType, String clientId, Map<String, Boolean> requestedScopes);\n\n    /**\n     * 获得用户的批准列表，排除已过期的\n     *\n     * @param userId 用户编号\n     * @param userType 用户类型\n     * @param clientId 客户端编号\n     * @return 是否授权通过\n     */\n    List<OAuth2ApproveDO> getApproveList(Long userId, Integer userType, String clientId);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/oauth2/OAuth2ApproveServiceImpl.java",
    "content": "package co.yixiang.yshop.module.system.service.oauth2;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.lang.Assert;\nimport co.yixiang.yshop.framework.common.util.date.DateUtils;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2ApproveDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2ClientDO;\nimport co.yixiang.yshop.module.system.dal.mysql.oauth2.OAuth2ApproveMapper;\nimport com.google.common.annotations.VisibleForTesting;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\nimport java.time.LocalDateTime;\nimport java.util.*;\n\nimport static co.yixiang.yshop.framework.common.util.collection.CollectionUtils.convertSet;\n\n/**\n * OAuth2 批准 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class OAuth2ApproveServiceImpl implements OAuth2ApproveService {\n\n    /**\n     * 批准的过期时间，默认 30 天\n     */\n    private static final Integer TIMEOUT = 30 * 24 * 60 * 60; // 单位：秒\n\n    @Resource\n    private OAuth2ClientService oauth2ClientService;\n\n    @Resource\n    private OAuth2ApproveMapper oauth2ApproveMapper;\n\n    @Override\n    @Transactional\n    public boolean checkForPreApproval(Long userId, Integer userType, String clientId, Collection<String> requestedScopes) {\n        // 第一步，基于 Client 的自动授权计算，如果 scopes 都在自动授权中，则返回 true 通过\n        OAuth2ClientDO clientDO = oauth2ClientService.validOAuthClientFromCache(clientId);\n        Assert.notNull(clientDO, \"客户端不能为空\"); // 防御性编程\n        if (CollUtil.containsAll(clientDO.getAutoApproveScopes(), requestedScopes)) {\n            // gh-877 - if all scopes are auto approved, approvals still need to be added to the approval store.\n            LocalDateTime expireTime = LocalDateTime.now().plusSeconds(TIMEOUT);\n            for (String scope : requestedScopes) {\n                saveApprove(userId, userType, clientId, scope, true, expireTime);\n            }\n            return true;\n        }\n\n        // 第二步，算上用户已经批准的授权。如果 scopes 都包含，则返回 true\n        List<OAuth2ApproveDO> approveDOs = getApproveList(userId, userType, clientId);\n        Set<String> scopes = convertSet(approveDOs, OAuth2ApproveDO::getScope,\n                OAuth2ApproveDO::getApproved); // 只保留未过期的 + 同意的\n        return CollUtil.containsAll(scopes, requestedScopes);\n    }\n\n    @Override\n    @Transactional\n    public boolean updateAfterApproval(Long userId, Integer userType, String clientId, Map<String, Boolean> requestedScopes) {\n        // 如果 requestedScopes 为空，说明没有要求，则返回 true 通过\n        if (CollUtil.isEmpty(requestedScopes)) {\n            return true;\n        }\n\n        // 更新批准的信息\n        boolean success = false; // 需要至少有一个同意\n        LocalDateTime expireTime = LocalDateTime.now().plusSeconds(TIMEOUT);\n        for (Map.Entry<String, Boolean> entry : requestedScopes.entrySet()) {\n            if (entry.getValue()) {\n                success = true;\n            }\n            saveApprove(userId, userType, clientId, entry.getKey(), entry.getValue(), expireTime);\n        }\n        return success;\n    }\n\n    @Override\n    public List<OAuth2ApproveDO> getApproveList(Long userId, Integer userType, String clientId) {\n        List<OAuth2ApproveDO> approveDOs = oauth2ApproveMapper.selectListByUserIdAndUserTypeAndClientId(\n                userId, userType, clientId);\n        approveDOs.removeIf(o -> DateUtils.isExpired(o.getExpiresTime()));\n        return approveDOs;\n    }\n\n    @VisibleForTesting\n    void saveApprove(Long userId, Integer userType, String clientId,\n                     String scope, Boolean approved, LocalDateTime expireTime) {\n        // 先更新\n        OAuth2ApproveDO approveDO = new OAuth2ApproveDO().setUserId(userId).setUserType(userType)\n                .setClientId(clientId).setScope(scope).setApproved(approved).setExpiresTime(expireTime);\n        if (oauth2ApproveMapper.update(approveDO) == 1) {\n            return;\n        }\n        // 失败，则说明不存在，进行更新\n        oauth2ApproveMapper.insert(approveDO);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/oauth2/OAuth2ClientService.java",
    "content": "package co.yixiang.yshop.module.system.service.oauth2;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.system.controller.admin.oauth2.vo.client.OAuth2ClientPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.oauth2.vo.client.OAuth2ClientSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2ClientDO;\n\nimport jakarta.validation.Valid;\nimport java.util.Collection;\n\n/**\n * OAuth2.0 Client Service 接口\n *\n * 从功能上，和 JdbcClientDetailsService 的功能，提供客户端的操作\n *\n * @author yshop\n */\npublic interface OAuth2ClientService {\n\n    /**\n     * 创建 OAuth2 客户端\n     *\n     * @param createReqVO 创建信息\n     * @return 编号\n     */\n    Long createOAuth2Client(@Valid OAuth2ClientSaveReqVO createReqVO);\n\n    /**\n     * 更新 OAuth2 客户端\n     *\n     * @param updateReqVO 更新信息\n     */\n    void updateOAuth2Client(@Valid OAuth2ClientSaveReqVO updateReqVO);\n\n    /**\n     * 删除 OAuth2 客户端\n     *\n     * @param id 编号\n     */\n    void deleteOAuth2Client(Long id);\n\n    /**\n     * 获得 OAuth2 客户端\n     *\n     * @param id 编号\n     * @return OAuth2 客户端\n     */\n    OAuth2ClientDO getOAuth2Client(Long id);\n\n    /**\n     * 获得 OAuth2 客户端，从缓存中\n     *\n     * @param clientId 客户端编号\n     * @return OAuth2 客户端\n     */\n    OAuth2ClientDO getOAuth2ClientFromCache(String clientId);\n\n    /**\n     * 获得 OAuth2 客户端分页\n     *\n     * @param pageReqVO 分页查询\n     * @return OAuth2 客户端分页\n     */\n    PageResult<OAuth2ClientDO> getOAuth2ClientPage(OAuth2ClientPageReqVO pageReqVO);\n\n    /**\n     * 从缓存中，校验客户端是否合法\n     *\n     * @return 客户端\n     */\n    default OAuth2ClientDO validOAuthClientFromCache(String clientId) {\n        return validOAuthClientFromCache(clientId, null, null, null, null);\n    }\n\n    /**\n     * 从缓存中，校验客户端是否合法\n     *\n     * 非空时，进行校验\n     *\n     * @param clientId 客户端编号\n     * @param clientSecret 客户端密钥\n     * @param authorizedGrantType 授权方式\n     * @param scopes 授权范围\n     * @param redirectUri 重定向地址\n     * @return 客户端\n     */\n    OAuth2ClientDO validOAuthClientFromCache(String clientId, String clientSecret, String authorizedGrantType,\n                                             Collection<String> scopes, String redirectUri);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/oauth2/OAuth2ClientServiceImpl.java",
    "content": "package co.yixiang.yshop.module.system.service.oauth2;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.util.ObjectUtil;\nimport cn.hutool.core.util.StrUtil;\nimport cn.hutool.extra.spring.SpringUtil;\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.framework.common.util.string.StrUtils;\nimport co.yixiang.yshop.module.system.controller.admin.oauth2.vo.client.OAuth2ClientPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.oauth2.vo.client.OAuth2ClientSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2ClientDO;\nimport co.yixiang.yshop.module.system.dal.mysql.oauth2.OAuth2ClientMapper;\nimport co.yixiang.yshop.module.system.dal.redis.RedisKeyConstants;\nimport com.google.common.annotations.VisibleForTesting;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.cache.annotation.CacheEvict;\nimport org.springframework.cache.annotation.Cacheable;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\nimport java.util.Collection;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.*;\n\n/**\n * OAuth2.0 Client Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\n@Slf4j\npublic class OAuth2ClientServiceImpl implements OAuth2ClientService {\n\n    @Resource\n    private OAuth2ClientMapper oauth2ClientMapper;\n\n    @Override\n    public Long createOAuth2Client(OAuth2ClientSaveReqVO createReqVO) {\n        validateClientIdExists(null, createReqVO.getClientId());\n        // 插入\n        OAuth2ClientDO client = BeanUtils.toBean(createReqVO, OAuth2ClientDO.class);\n        oauth2ClientMapper.insert(client);\n        return client.getId();\n    }\n\n    @Override\n    @CacheEvict(cacheNames = RedisKeyConstants.OAUTH_CLIENT,\n            allEntries = true) // allEntries 清空所有缓存，因为可能修改到 clientId 字段，不好清理\n    public void updateOAuth2Client(OAuth2ClientSaveReqVO updateReqVO) {\n        // 校验存在\n        validateOAuth2ClientExists(updateReqVO.getId());\n        // 校验 Client 未被占用\n        validateClientIdExists(updateReqVO.getId(), updateReqVO.getClientId());\n\n        // 更新\n        OAuth2ClientDO updateObj = BeanUtils.toBean(updateReqVO, OAuth2ClientDO.class);\n        oauth2ClientMapper.updateById(updateObj);\n    }\n\n    @Override\n    @CacheEvict(cacheNames = RedisKeyConstants.OAUTH_CLIENT,\n            allEntries = true) // allEntries 清空所有缓存，因为 id 不是直接的缓存 key，不好清理\n    public void deleteOAuth2Client(Long id) {\n        // 校验存在\n        validateOAuth2ClientExists(id);\n        // 删除\n        oauth2ClientMapper.deleteById(id);\n    }\n\n    private void validateOAuth2ClientExists(Long id) {\n        if (oauth2ClientMapper.selectById(id) == null) {\n            throw exception(OAUTH2_CLIENT_NOT_EXISTS);\n        }\n    }\n\n    @VisibleForTesting\n    void validateClientIdExists(Long id, String clientId) {\n        OAuth2ClientDO client = oauth2ClientMapper.selectByClientId(clientId);\n        if (client == null) {\n            return;\n        }\n        // 如果 id 为空，说明不用比较是否为相同 id 的客户端\n        if (id == null) {\n            throw exception(OAUTH2_CLIENT_EXISTS);\n        }\n        if (!client.getId().equals(id)) {\n            throw exception(OAUTH2_CLIENT_EXISTS);\n        }\n    }\n\n    @Override\n    public OAuth2ClientDO getOAuth2Client(Long id) {\n        return oauth2ClientMapper.selectById(id);\n    }\n\n    @Override\n    @Cacheable(cacheNames = RedisKeyConstants.OAUTH_CLIENT, key = \"#clientId\",\n            unless = \"#result == null\")\n    public OAuth2ClientDO getOAuth2ClientFromCache(String clientId) {\n        return oauth2ClientMapper.selectByClientId(clientId);\n    }\n\n    @Override\n    public PageResult<OAuth2ClientDO> getOAuth2ClientPage(OAuth2ClientPageReqVO pageReqVO) {\n        return oauth2ClientMapper.selectPage(pageReqVO);\n    }\n\n    @Override\n    public OAuth2ClientDO validOAuthClientFromCache(String clientId, String clientSecret, String authorizedGrantType,\n                                                    Collection<String> scopes, String redirectUri) {\n        // 校验客户端存在、且开启\n        OAuth2ClientDO client = getSelf().getOAuth2ClientFromCache(clientId);\n        if (client == null) {\n            throw exception(OAUTH2_CLIENT_NOT_EXISTS);\n        }\n        if (CommonStatusEnum.isDisable(client.getStatus())) {\n            throw exception(OAUTH2_CLIENT_DISABLE);\n        }\n\n        // 校验客户端密钥\n        if (StrUtil.isNotEmpty(clientSecret) && ObjectUtil.notEqual(client.getSecret(), clientSecret)) {\n            throw exception(OAUTH2_CLIENT_CLIENT_SECRET_ERROR);\n        }\n        // 校验授权方式\n        if (StrUtil.isNotEmpty(authorizedGrantType) && !CollUtil.contains(client.getAuthorizedGrantTypes(), authorizedGrantType)) {\n            throw exception(OAUTH2_CLIENT_AUTHORIZED_GRANT_TYPE_NOT_EXISTS);\n        }\n        // 校验授权范围\n        if (CollUtil.isNotEmpty(scopes) && !CollUtil.containsAll(client.getScopes(), scopes)) {\n            throw exception(OAUTH2_CLIENT_SCOPE_OVER);\n        }\n        // 校验回调地址\n        if (StrUtil.isNotEmpty(redirectUri) && !StrUtils.startWithAny(redirectUri, client.getRedirectUris())) {\n            throw exception(OAUTH2_CLIENT_REDIRECT_URI_NOT_MATCH, redirectUri);\n        }\n        return client;\n    }\n\n    /**\n     * 获得自身的代理对象，解决 AOP 生效问题\n     *\n     * @return 自己\n     */\n    private OAuth2ClientServiceImpl getSelf() {\n        return SpringUtil.getBean(getClass());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/oauth2/OAuth2CodeService.java",
    "content": "package co.yixiang.yshop.module.system.service.oauth2;\n\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2CodeDO;\n\nimport java.util.List;\n\n/**\n * OAuth2.0 授权码 Service 接口\n *\n * 从功能上，和 Spring Security OAuth 的 JdbcAuthorizationCodeServices 的功能，提供授权码的操作\n *\n * @author yshop\n */\npublic interface OAuth2CodeService {\n\n    /**\n     * 创建授权码\n     *\n     * 参考 JdbcAuthorizationCodeServices 的 createAuthorizationCode 方法\n     *\n     * @param userId 用户编号\n     * @param userType 用户类型\n     * @param clientId 客户端编号\n     * @param scopes 授权范围\n     * @param redirectUri 重定向 URI\n     * @param state 状态\n     * @return 授权码的信息\n     */\n    OAuth2CodeDO createAuthorizationCode(Long userId, Integer userType, String clientId,\n                                         List<String> scopes, String redirectUri, String state);\n\n    /**\n     * 使用授权码\n     *\n     * @param code 授权码\n     */\n    OAuth2CodeDO consumeAuthorizationCode(String code);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/oauth2/OAuth2CodeServiceImpl.java",
    "content": "package co.yixiang.yshop.module.system.service.oauth2;\n\nimport cn.hutool.core.util.IdUtil;\nimport co.yixiang.yshop.framework.common.util.date.DateUtils;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2CodeDO;\nimport co.yixiang.yshop.module.system.dal.mysql.oauth2.OAuth2CodeMapper;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\nimport java.time.LocalDateTime;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.OAUTH2_CODE_EXPIRE;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.OAUTH2_CODE_NOT_EXISTS;\n\n/**\n * OAuth2.0 授权码 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class OAuth2CodeServiceImpl implements OAuth2CodeService {\n\n    /**\n     * 授权码的过期时间，默认 5 分钟\n     */\n    private static final Integer TIMEOUT = 5 * 60;\n\n    @Resource\n    private OAuth2CodeMapper oauth2CodeMapper;\n\n    @Override\n    public OAuth2CodeDO createAuthorizationCode(Long userId, Integer userType, String clientId,\n                                                List<String> scopes, String redirectUri, String state) {\n        OAuth2CodeDO codeDO = new OAuth2CodeDO().setCode(generateCode())\n                .setUserId(userId).setUserType(userType)\n                .setClientId(clientId).setScopes(scopes)\n                .setExpiresTime(LocalDateTime.now().plusSeconds(TIMEOUT))\n                .setRedirectUri(redirectUri).setState(state);\n        oauth2CodeMapper.insert(codeDO);\n        return codeDO;\n    }\n\n    @Override\n    public OAuth2CodeDO consumeAuthorizationCode(String code) {\n        OAuth2CodeDO codeDO = oauth2CodeMapper.selectByCode(code);\n        if (codeDO == null) {\n            throw exception(OAUTH2_CODE_NOT_EXISTS);\n        }\n        if (DateUtils.isExpired(codeDO.getExpiresTime())) {\n            throw exception(OAUTH2_CODE_EXPIRE);\n        }\n        oauth2CodeMapper.deleteById(codeDO.getId());\n        return codeDO;\n    }\n\n    private static String generateCode() {\n        return IdUtil.fastSimpleUUID();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/oauth2/OAuth2GrantService.java",
    "content": "package co.yixiang.yshop.module.system.service.oauth2;\n\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;\n\nimport java.util.List;\n\n/**\n * OAuth2 授予 Service 接口\n *\n * 从功能上，和 Spring Security OAuth 的 TokenGranter 的功能，提供访问令牌、刷新令牌的操作\n *\n * 将自身的 AdminUser 用户，授权给第三方应用，采用 OAuth2.0 的协议。\n *\n * 问题：为什么自身也作为一个第三方应用，也走这套流程呢？\n * 回复：当然可以这么做，采用 password 模式。考虑到大多数开发者使用不到这个特性，OAuth2.0 毕竟有一定学习成本，所以暂时没有采取这种方式。\n *\n * @author yshop\n */\npublic interface OAuth2GrantService {\n\n    /**\n     * 简化模式\n     *\n     * 对应 Spring Security OAuth2 的 ImplicitTokenGranter 功能\n     *\n     * @param userId 用户编号\n     * @param userType 用户类型\n     * @param clientId 客户端编号\n     * @param scopes 授权范围\n     * @return 访问令牌\n     */\n    OAuth2AccessTokenDO grantImplicit(Long userId, Integer userType,\n                                      String clientId, List<String> scopes);\n\n    /**\n     * 授权码模式，第一阶段，获得 code 授权码\n     *\n     * 对应 Spring Security OAuth2 的 AuthorizationEndpoint 的 generateCode 方法\n     *\n     * @param userId 用户编号\n     * @param userType 用户类型\n     * @param clientId 客户端编号\n     * @param scopes 授权范围\n     * @param redirectUri 重定向 URI\n     * @param state 状态\n     * @return 授权码\n     */\n    String grantAuthorizationCodeForCode(Long userId, Integer userType,\n                                         String clientId, List<String> scopes,\n                                         String redirectUri, String state);\n\n    /**\n     * 授权码模式，第二阶段，获得 accessToken 访问令牌\n     *\n     * 对应 Spring Security OAuth2 的 AuthorizationCodeTokenGranter 功能\n     *\n     * @param clientId 客户端编号\n     * @param code 授权码\n     * @param redirectUri 重定向 URI\n     * @param state 状态\n     * @return 访问令牌\n     */\n    OAuth2AccessTokenDO grantAuthorizationCodeForAccessToken(String clientId, String code,\n                                                             String redirectUri, String state);\n\n    /**\n     * 密码模式\n     *\n     * 对应 Spring Security OAuth2 的 ResourceOwnerPasswordTokenGranter 功能\n     *\n     * @param username 账号\n     * @param password 密码\n     * @param clientId 客户端编号\n     * @param scopes 授权范围\n     * @return 访问令牌\n     */\n    OAuth2AccessTokenDO grantPassword(String username, String password,\n                                      String clientId, List<String> scopes);\n\n    /**\n     * 刷新模式\n     *\n     * 对应 Spring Security OAuth2 的 ResourceOwnerPasswordTokenGranter 功能\n     *\n     * @param refreshToken 刷新令牌\n     * @param clientId 客户端编号\n     * @return 访问令牌\n     */\n    OAuth2AccessTokenDO grantRefreshToken(String refreshToken, String clientId);\n\n    /**\n     * 客户端模式\n     *\n     * 对应 Spring Security OAuth2 的 ClientCredentialsTokenGranter 功能\n     *\n     * @param clientId 客户端编号\n     * @param scopes 授权范围\n     * @return 访问令牌\n     */\n    OAuth2AccessTokenDO grantClientCredentials(String clientId, List<String> scopes);\n\n    /**\n     * 移除访问令牌\n     *\n     * 对应 Spring Security OAuth2 的 ConsumerTokenServices 的 revokeToken 方法\n     *\n     * @param accessToken 访问令牌\n     * @param clientId 客户端编号\n     * @return 是否移除到\n     */\n    boolean revokeToken(String clientId, String accessToken);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/oauth2/OAuth2GrantServiceImpl.java",
    "content": "package co.yixiang.yshop.module.system.service.oauth2;\n\nimport cn.hutool.core.lang.Assert;\nimport cn.hutool.core.util.ObjectUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2CodeDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.user.AdminUserDO;\nimport co.yixiang.yshop.module.system.enums.ErrorCodeConstants;\nimport co.yixiang.yshop.module.system.service.auth.AdminAuthService;\nimport org.springframework.stereotype.Service;\n\nimport jakarta.annotation.Resource;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\n\n/**\n * OAuth2 授予 Service 实现类\n *\n * @author yshop\n */\n@Service\npublic class OAuth2GrantServiceImpl implements OAuth2GrantService {\n\n    @Resource\n    private OAuth2TokenService oauth2TokenService;\n    @Resource\n    private OAuth2CodeService oauth2CodeService;\n    @Resource\n    private AdminAuthService adminAuthService;\n\n    @Override\n    public OAuth2AccessTokenDO grantImplicit(Long userId, Integer userType,\n                                             String clientId, List<String> scopes) {\n        return oauth2TokenService.createAccessToken(userId, userType, clientId, scopes);\n    }\n\n    @Override\n    public String grantAuthorizationCodeForCode(Long userId, Integer userType,\n                                                String clientId, List<String> scopes,\n                                                String redirectUri, String state) {\n        return oauth2CodeService.createAuthorizationCode(userId, userType, clientId, scopes,\n                redirectUri, state).getCode();\n    }\n\n    @Override\n    public OAuth2AccessTokenDO grantAuthorizationCodeForAccessToken(String clientId, String code,\n                                                                    String redirectUri, String state) {\n        OAuth2CodeDO codeDO = oauth2CodeService.consumeAuthorizationCode(code);\n        Assert.notNull(codeDO, \"授权码不能为空\"); // 防御性编程\n        // 校验 clientId 是否匹配\n        if (!StrUtil.equals(clientId, codeDO.getClientId())) {\n            throw exception(ErrorCodeConstants.OAUTH2_GRANT_CLIENT_ID_MISMATCH);\n        }\n        // 校验 redirectUri 是否匹配\n        if (!StrUtil.equals(redirectUri, codeDO.getRedirectUri())) {\n            throw exception(ErrorCodeConstants.OAUTH2_GRANT_REDIRECT_URI_MISMATCH);\n        }\n        // 校验 state 是否匹配\n        state = StrUtil.nullToDefault(state, \"\"); // 数据库 state 为 null 时，会设置为 \"\" 空串\n        if (!StrUtil.equals(state, codeDO.getState())) {\n            throw exception(ErrorCodeConstants.OAUTH2_GRANT_STATE_MISMATCH);\n        }\n\n        // 创建访问令牌\n        return oauth2TokenService.createAccessToken(codeDO.getUserId(), codeDO.getUserType(),\n                codeDO.getClientId(), codeDO.getScopes());\n    }\n\n    @Override\n    public OAuth2AccessTokenDO grantPassword(String username, String password, String clientId, List<String> scopes) {\n        // 使用账号 + 密码进行登录\n        AdminUserDO user = adminAuthService.authenticate(username, password);\n        Assert.notNull(user, \"用户不能为空！\"); // 防御性编程\n\n        // 创建访问令牌\n        return oauth2TokenService.createAccessToken(user.getId(), UserTypeEnum.ADMIN.getValue(), clientId, scopes);\n    }\n\n    @Override\n    public OAuth2AccessTokenDO grantRefreshToken(String refreshToken, String clientId) {\n        return oauth2TokenService.refreshAccessToken(refreshToken, clientId);\n    }\n\n    @Override\n    public OAuth2AccessTokenDO grantClientCredentials(String clientId, List<String> scopes) {\n        // TODO yshop：项目中使用 OAuth2 解决的是三方应用的授权，内部的 SSO 等问题，所以暂时不考虑 client_credentials 这个场景\n        throw new UnsupportedOperationException(\"暂时不支持 client_credentials 授权模式\");\n    }\n\n    @Override\n    public boolean revokeToken(String clientId, String accessToken) {\n        // 先查询，保证 clientId 时匹配的\n        OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.getAccessToken(accessToken);\n        if (accessTokenDO == null || ObjectUtil.notEqual(clientId, accessTokenDO.getClientId())) {\n            return false;\n        }\n        // 再删除\n        return oauth2TokenService.removeAccessToken(accessToken) != null;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/oauth2/OAuth2TokenService.java",
    "content": "package co.yixiang.yshop.module.system.service.oauth2;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.system.controller.admin.oauth2.vo.token.OAuth2AccessTokenPageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;\n\nimport java.util.List;\n\n/**\n * OAuth2.0 Token Service 接口\n *\n * 从功能上，和 Spring Security OAuth 的 DefaultTokenServices + JdbcTokenStore 的功能，提供访问令牌、刷新令牌的操作\n *\n * @author yshop\n */\npublic interface OAuth2TokenService {\n\n    /**\n     * 创建访问令牌\n     * 注意：该流程中，会包含创建刷新令牌的创建\n     *\n     * 参考 DefaultTokenServices 的 createAccessToken 方法\n     *\n     * @param userId 用户编号\n     * @param userType 用户类型\n     * @param clientId 客户端编号\n     * @param scopes 授权范围\n     * @return 访问令牌的信息\n     */\n    OAuth2AccessTokenDO createAccessToken(Long userId, Integer userType, String clientId, List<String> scopes);\n\n    /**\n     * 刷新访问令牌\n     *\n     * 参考 DefaultTokenServices 的 refreshAccessToken 方法\n     *\n     * @param refreshToken 刷新令牌\n     * @param clientId 客户端编号\n     * @return 访问令牌的信息\n     */\n    OAuth2AccessTokenDO refreshAccessToken(String refreshToken, String clientId);\n\n    /**\n     * 获得访问令牌\n     *\n     * 参考 DefaultTokenServices 的 getAccessToken 方法\n     *\n     * @param accessToken 访问令牌\n     * @return 访问令牌的信息\n     */\n    OAuth2AccessTokenDO getAccessToken(String accessToken);\n\n    /**\n     * 校验访问令牌\n     *\n     * @param accessToken 访问令牌\n     * @return 访问令牌的信息\n     */\n    OAuth2AccessTokenDO checkAccessToken(String accessToken);\n\n    /**\n     * 移除访问令牌\n     * 注意：该流程中，会移除相关的刷新令牌\n     *\n     * 参考 DefaultTokenServices 的 revokeToken 方法\n     *\n     * @param accessToken 刷新令牌\n     * @return 访问令牌的信息\n     */\n    OAuth2AccessTokenDO removeAccessToken(String accessToken);\n\n    /**\n     * 获得访问令牌分页\n     *\n     * @param reqVO 请求\n     * @return 访问令牌分页\n     */\n    PageResult<OAuth2AccessTokenDO> getAccessTokenPage(OAuth2AccessTokenPageReqVO reqVO);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/oauth2/OAuth2TokenServiceImpl.java",
    "content": "package co.yixiang.yshop.module.system.service.oauth2;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.map.MapUtil;\nimport cn.hutool.core.util.IdUtil;\nimport cn.hutool.core.util.ObjectUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.common.exception.enums.GlobalErrorCodeConstants;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.date.DateUtils;\nimport co.yixiang.yshop.framework.security.core.LoginUser;\nimport co.yixiang.yshop.framework.tenant.core.context.TenantContextHolder;\nimport co.yixiang.yshop.module.store.dal.dataobject.storeshop.StoreShopDO;\nimport co.yixiang.yshop.module.store.dal.mysql.storeshop.StoreShopMapper;\nimport co.yixiang.yshop.module.system.controller.admin.oauth2.vo.token.OAuth2AccessTokenPageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2ClientDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2RefreshTokenDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.user.AdminUserDO;\nimport co.yixiang.yshop.module.system.dal.mysql.oauth2.OAuth2AccessTokenMapper;\nimport co.yixiang.yshop.module.system.dal.mysql.oauth2.OAuth2RefreshTokenMapper;\nimport co.yixiang.yshop.module.system.dal.redis.oauth2.OAuth2AccessTokenRedisDAO;\nimport co.yixiang.yshop.module.system.service.user.AdminUserService;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport jakarta.annotation.Resource;\nimport org.springframework.context.annotation.Lazy;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport java.time.LocalDateTime;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Map;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception0;\nimport static co.yixiang.yshop.framework.common.util.collection.CollectionUtils.convertSet;\n\n/**\n * OAuth2.0 Token Service 实现类\n *\n * @author yshop\n */\n@Service\npublic class OAuth2TokenServiceImpl implements OAuth2TokenService {\n\n    @Resource\n    private OAuth2AccessTokenMapper oauth2AccessTokenMapper;\n    @Resource\n    private OAuth2RefreshTokenMapper oauth2RefreshTokenMapper;\n\n    @Resource\n    private OAuth2AccessTokenRedisDAO oauth2AccessTokenRedisDAO;\n\n    @Resource\n    private OAuth2ClientService oauth2ClientService;\n    @Resource\n    @Lazy // 懒加载，避免循环依赖\n    private AdminUserService adminUserService;\n    @Resource\n    private StoreShopMapper storeShopMapper;\n\n    @Override\n    @Transactional\n    public OAuth2AccessTokenDO createAccessToken(Long userId, Integer userType, String clientId, List<String> scopes) {\n        OAuth2ClientDO clientDO = oauth2ClientService.validOAuthClientFromCache(clientId);\n        // 创建刷新令牌\n        OAuth2RefreshTokenDO refreshTokenDO = createOAuth2RefreshToken(userId, userType, clientDO, scopes);\n        // 创建访问令牌\n        return createOAuth2AccessToken(refreshTokenDO, clientDO);\n    }\n\n    @Override\n    public OAuth2AccessTokenDO refreshAccessToken(String refreshToken, String clientId) {\n        // 查询访问令牌\n        OAuth2RefreshTokenDO refreshTokenDO = oauth2RefreshTokenMapper.selectByRefreshToken(refreshToken);\n        if (refreshTokenDO == null) {\n            throw exception0(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), \"无效的刷新令牌\");\n        }\n\n        // 校验 Client 匹配\n        OAuth2ClientDO clientDO = oauth2ClientService.validOAuthClientFromCache(clientId);\n        if (ObjectUtil.notEqual(clientId, refreshTokenDO.getClientId())) {\n            throw exception0(GlobalErrorCodeConstants.BAD_REQUEST.getCode(), \"刷新令牌的客户端编号不正确\");\n        }\n\n        // 移除相关的访问令牌\n        List<OAuth2AccessTokenDO> accessTokenDOs = oauth2AccessTokenMapper.selectListByRefreshToken(refreshToken);\n        if (CollUtil.isNotEmpty(accessTokenDOs)) {\n            oauth2AccessTokenMapper.deleteBatchIds(convertSet(accessTokenDOs, OAuth2AccessTokenDO::getId));\n            oauth2AccessTokenRedisDAO.deleteList(convertSet(accessTokenDOs, OAuth2AccessTokenDO::getAccessToken));\n        }\n\n        // 已过期的情况下，删除刷新令牌\n        if (DateUtils.isExpired(refreshTokenDO.getExpiresTime())) {\n            oauth2RefreshTokenMapper.deleteById(refreshTokenDO.getId());\n            throw exception0(GlobalErrorCodeConstants.UNAUTHORIZED.getCode(), \"刷新令牌已过期\");\n        }\n\n        // 创建访问令牌\n        return createOAuth2AccessToken(refreshTokenDO, clientDO);\n    }\n\n    @Override\n    public OAuth2AccessTokenDO getAccessToken(String accessToken) {\n        // 优先从 Redis 中获取\n        OAuth2AccessTokenDO accessTokenDO = oauth2AccessTokenRedisDAO.get(accessToken);\n        if (accessTokenDO != null) {\n            return accessTokenDO;\n        }\n\n        // 获取不到，从 MySQL 中获取\n        accessTokenDO = oauth2AccessTokenMapper.selectByAccessToken(accessToken);\n        // 如果在 MySQL 存在，则往 Redis 中写入\n        if (accessTokenDO != null && !DateUtils.isExpired(accessTokenDO.getExpiresTime())) {\n            oauth2AccessTokenRedisDAO.set(accessTokenDO);\n        }\n        return accessTokenDO;\n    }\n\n    @Override\n    public OAuth2AccessTokenDO checkAccessToken(String accessToken) {\n        OAuth2AccessTokenDO accessTokenDO = getAccessToken(accessToken);\n        if (accessTokenDO == null) {\n            throw exception0(GlobalErrorCodeConstants.UNAUTHORIZED.getCode(), \"访问令牌不存在\");\n        }\n        if (DateUtils.isExpired(accessTokenDO.getExpiresTime())) {\n            throw exception0(GlobalErrorCodeConstants.UNAUTHORIZED.getCode(), \"访问令牌已过期\");\n        }\n        return accessTokenDO;\n    }\n\n    @Override\n    public OAuth2AccessTokenDO removeAccessToken(String accessToken) {\n        // 删除访问令牌\n        OAuth2AccessTokenDO accessTokenDO = oauth2AccessTokenMapper.selectByAccessToken(accessToken);\n        if (accessTokenDO == null) {\n            return null;\n        }\n        oauth2AccessTokenMapper.deleteById(accessTokenDO.getId());\n        oauth2AccessTokenRedisDAO.delete(accessToken);\n        // 删除刷新令牌\n        oauth2RefreshTokenMapper.deleteByRefreshToken(accessTokenDO.getRefreshToken());\n        return accessTokenDO;\n    }\n\n    @Override\n    public PageResult<OAuth2AccessTokenDO> getAccessTokenPage(OAuth2AccessTokenPageReqVO reqVO) {\n        return oauth2AccessTokenMapper.selectPage(reqVO);\n    }\n\n    private OAuth2AccessTokenDO createOAuth2AccessToken(OAuth2RefreshTokenDO refreshTokenDO, OAuth2ClientDO clientDO) {\n        OAuth2AccessTokenDO accessTokenDO = new OAuth2AccessTokenDO().setAccessToken(generateAccessToken())\n                .setShopId(refreshTokenDO.getShopId())\n                .setUserId(refreshTokenDO.getUserId()).setUserType(refreshTokenDO.getUserType())\n                .setUserInfo(buildUserInfo(refreshTokenDO.getUserId(), refreshTokenDO.getUserType()))\n                .setClientId(clientDO.getClientId()).setScopes(refreshTokenDO.getScopes())\n                .setRefreshToken(refreshTokenDO.getRefreshToken())\n                .setExpiresTime(LocalDateTime.now().plusSeconds(clientDO.getAccessTokenValiditySeconds()));\n        accessTokenDO.setTenantId(TenantContextHolder.getTenantId()); // 手动设置租户编号，避免缓存到 Redis 的时候，无对应的租户编号\n        oauth2AccessTokenMapper.insert(accessTokenDO);\n        // 记录到 Redis 中\n        oauth2AccessTokenRedisDAO.set(accessTokenDO);\n        return accessTokenDO;\n    }\n\n    private OAuth2RefreshTokenDO createOAuth2RefreshToken(Long userId, Integer userType, OAuth2ClientDO clientDO, List<String> scopes) {\n        Long shopId = getShopId(userId);\n        OAuth2RefreshTokenDO refreshToken = new OAuth2RefreshTokenDO().setRefreshToken(generateRefreshToken())\n                .setShopId(shopId)\n                .setUserId(userId).setUserType(userType)\n                .setClientId(clientDO.getClientId()).setScopes(scopes)\n                .setExpiresTime(LocalDateTime.now().plusSeconds(clientDO.getRefreshTokenValiditySeconds()));\n        oauth2RefreshTokenMapper.insert(refreshToken);\n        return refreshToken;\n    }\n\n    private Long getShopId(Long userId) {\n        StoreShopDO storeShopDO = storeShopMapper.selectOne(new LambdaQueryWrapper<StoreShopDO>()\n                .apply(userId > 0, \"FIND_IN_SET ('\" + userId + \"',admin_id)\"));\n        if (storeShopDO == null) {\n            return 0L;\n        }\n        return storeShopDO.getId();\n\n    }\n\n        /**\n         * 加载用户信息，方便 {@link co.yixiang.yshop.framework.security.core.LoginUser} 获取到昵称、部门等信息\n         *\n         * @param userId 用户编号\n         * @param userType 用户类型\n         * @return 用户信息\n         */\n    private Map<String, String> buildUserInfo(Long userId, Integer userType) {\n        if (userType.equals(UserTypeEnum.ADMIN.getValue())) {\n            AdminUserDO user = adminUserService.getUser(userId);\n            return MapUtil.builder(LoginUser.INFO_KEY_NICKNAME, user.getNickname())\n                    .put(LoginUser.INFO_KEY_DEPT_ID, StrUtil.toStringOrNull(user.getDeptId())).build();\n        } else if (userType.equals(UserTypeEnum.MEMBER.getValue())) {\n            // 注意：目前 Member 暂时不读取，可以按需实现\n            return Collections.emptyMap();\n        }\n        return null;\n    }\n\n    private static String generateAccessToken() {\n        return IdUtil.fastSimpleUUID();\n    }\n\n    private static String generateRefreshToken() {\n        return IdUtil.fastSimpleUUID();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/permission/MenuService.java",
    "content": "package co.yixiang.yshop.module.system.service.permission;\n\nimport co.yixiang.yshop.module.system.controller.admin.permission.vo.menu.MenuSaveVO;\nimport co.yixiang.yshop.module.system.controller.admin.permission.vo.menu.MenuListReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.permission.MenuDO;\n\nimport java.util.Collection;\nimport java.util.List;\n\n/**\n * 菜单 Service 接口\n *\n * @author yshop\n */\npublic interface MenuService {\n\n    /**\n     * 创建菜单\n     *\n     * @param createReqVO 菜单信息\n     * @return 创建出来的菜单编号\n     */\n    Long createMenu(MenuSaveVO createReqVO);\n\n    /**\n     * 更新菜单\n     *\n     * @param updateReqVO 菜单信息\n     */\n    void updateMenu(MenuSaveVO updateReqVO);\n\n    /**\n     * 删除菜单\n     *\n     * @param id 菜单编号\n     */\n    void deleteMenu(Long id);\n\n    /**\n     * 获得所有菜单列表\n     *\n     * @return 菜单列表\n     */\n    List<MenuDO> getMenuList();\n\n    /**\n     * 基于租户，筛选菜单列表\n     * 注意，如果是系统租户，返回的还是全菜单\n     *\n     * @param reqVO 筛选条件请求 VO\n     * @return 菜单列表\n     */\n    List<MenuDO> getMenuListByTenant(MenuListReqVO reqVO);\n\n    /**\n     * 筛选菜单列表\n     *\n     * @param reqVO 筛选条件请求 VO\n     * @return 菜单列表\n     */\n    List<MenuDO> getMenuList(MenuListReqVO reqVO);\n\n    /**\n     * 获得权限对应的菜单编号数组\n     *\n     * @param permission 权限标识\n     * @return 数组\n     */\n    List<Long> getMenuIdListByPermissionFromCache(String permission);\n\n    /**\n     * 获得菜单\n     *\n     * @param id 菜单编号\n     * @return 菜单\n     */\n    MenuDO getMenu(Long id);\n\n    /**\n     * 获得菜单数组\n     *\n     * @param ids 菜单编号数组\n     * @return 菜单数组\n     */\n    List<MenuDO> getMenuList(Collection<Long> ids);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/permission/MenuServiceImpl.java",
    "content": "package co.yixiang.yshop.module.system.service.permission;\n\nimport cn.hutool.core.collection.CollUtil;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.controller.admin.permission.vo.menu.MenuListReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.permission.vo.menu.MenuSaveVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.permission.MenuDO;\nimport co.yixiang.yshop.module.system.dal.mysql.permission.MenuMapper;\nimport co.yixiang.yshop.module.system.dal.redis.RedisKeyConstants;\nimport co.yixiang.yshop.module.system.enums.permission.MenuTypeEnum;\nimport co.yixiang.yshop.module.system.service.tenant.TenantService;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.collect.Lists;\nimport jakarta.annotation.Resource;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.cache.annotation.CacheEvict;\nimport org.springframework.cache.annotation.Cacheable;\nimport org.springframework.context.annotation.Lazy;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport java.util.Collection;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.framework.common.util.collection.CollectionUtils.convertList;\nimport static co.yixiang.yshop.module.system.dal.dataobject.permission.MenuDO.ID_ROOT;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.*;\n\n/**\n * 菜单 Service 实现\n *\n * @author yshop\n */\n@Service\n@Slf4j\npublic class MenuServiceImpl implements MenuService {\n\n    @Resource\n    private MenuMapper menuMapper;\n    @Resource\n    private PermissionService permissionService;\n    @Resource\n    @Lazy // 延迟，避免循环依赖报错\n    private TenantService tenantService;\n\n    @Override\n    @CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST, key = \"#createReqVO.permission\",\n            condition = \"#createReqVO.permission != null\")\n    public Long createMenu(MenuSaveVO createReqVO) {\n        // 校验父菜单存在\n        validateParentMenu(createReqVO.getParentId(), null);\n        // 校验菜单（自己）\n        validateMenu(createReqVO.getParentId(), createReqVO.getName(), null);\n\n        // 插入数据库\n        MenuDO menu = BeanUtils.toBean(createReqVO, MenuDO.class);\n        initMenuProperty(menu);\n        menuMapper.insert(menu);\n        // 返回\n        return menu.getId();\n    }\n\n    @Override\n    @CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST,\n            allEntries = true) // allEntries 清空所有缓存，因为 permission 如果变更，涉及到新老两个 permission。直接清理，简单有效\n    public void updateMenu(MenuSaveVO updateReqVO) {\n        // 校验更新的菜单是否存在\n        if (menuMapper.selectById(updateReqVO.getId()) == null) {\n            throw exception(MENU_NOT_EXISTS);\n        }\n        // 校验父菜单存在\n        validateParentMenu(updateReqVO.getParentId(), updateReqVO.getId());\n        // 校验菜单（自己）\n        validateMenu(updateReqVO.getParentId(), updateReqVO.getName(), updateReqVO.getId());\n\n        // 更新到数据库\n        MenuDO updateObj = BeanUtils.toBean(updateReqVO, MenuDO.class);\n        initMenuProperty(updateObj);\n        menuMapper.updateById(updateObj);\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    @CacheEvict(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST,\n            allEntries = true) // allEntries 清空所有缓存，因为此时不知道 id 对应的 permission 是多少。直接清理，简单有效\n    public void deleteMenu(Long id) {\n        // 校验是否还有子菜单\n        if (menuMapper.selectCountByParentId(id) > 0) {\n            throw exception(MENU_EXISTS_CHILDREN);\n        }\n        // 校验删除的菜单是否存在\n        if (menuMapper.selectById(id) == null) {\n            throw exception(MENU_NOT_EXISTS);\n        }\n        // 标记删除\n        menuMapper.deleteById(id);\n        // 删除授予给角色的权限\n        permissionService.processMenuDeleted(id);\n    }\n\n    @Override\n    public List<MenuDO> getMenuList() {\n        return menuMapper.selectList();\n    }\n\n    @Override\n    public List<MenuDO> getMenuListByTenant(MenuListReqVO reqVO) {\n        List<MenuDO> menus = getMenuList(reqVO);\n        // 开启多租户的情况下，需要过滤掉未开通的菜单\n        tenantService.handleTenantMenu(menuIds -> menus.removeIf(menu -> !CollUtil.contains(menuIds, menu.getId())));\n        return menus;\n    }\n\n    @Override\n    public List<MenuDO> getMenuList(MenuListReqVO reqVO) {\n        return menuMapper.selectList(reqVO);\n    }\n\n    @Override\n    @Cacheable(value = RedisKeyConstants.PERMISSION_MENU_ID_LIST, key = \"#permission\")\n    public List<Long> getMenuIdListByPermissionFromCache(String permission) {\n        List<MenuDO> menus = menuMapper.selectListByPermission(permission);\n        return convertList(menus, MenuDO::getId);\n    }\n\n    @Override\n    public MenuDO getMenu(Long id) {\n        return menuMapper.selectById(id);\n    }\n\n    @Override\n    public List<MenuDO> getMenuList(Collection<Long> ids) {\n        // 当 ids 为空时，返回一个空的实例对象\n        if (CollUtil.isEmpty(ids)) {\n            return Lists.newArrayList();\n        }\n        return menuMapper.selectBatchIds(ids);\n    }\n\n    /**\n     * 校验父菜单是否合法\n     * <p>\n     * 1. 不能设置自己为父菜单\n     * 2. 父菜单不存在\n     * 3. 父菜单必须是 {@link MenuTypeEnum#MENU} 菜单类型\n     *\n     * @param parentId 父菜单编号\n     * @param childId  当前菜单编号\n     */\n    @VisibleForTesting\n    void validateParentMenu(Long parentId, Long childId) {\n        if (parentId == null || ID_ROOT.equals(parentId)) {\n            return;\n        }\n        // 不能设置自己为父菜单\n        if (parentId.equals(childId)) {\n            throw exception(MENU_PARENT_ERROR);\n        }\n        MenuDO menu = menuMapper.selectById(parentId);\n        // 父菜单不存在\n        if (menu == null) {\n            throw exception(MENU_PARENT_NOT_EXISTS);\n        }\n        // 父菜单必须是目录或者菜单类型\n        if (!MenuTypeEnum.DIR.getType().equals(menu.getType())\n                && !MenuTypeEnum.MENU.getType().equals(menu.getType())) {\n            throw exception(MENU_PARENT_NOT_DIR_OR_MENU);\n        }\n    }\n\n    /**\n     * 校验菜单是否合法\n     * <p>\n     * 1. 校验相同父菜单编号下，是否存在相同的菜单名\n     *\n     * @param name     菜单名字\n     * @param parentId 父菜单编号\n     * @param id       菜单编号\n     */\n    @VisibleForTesting\n    void validateMenu(Long parentId, String name, Long id) {\n        MenuDO menu = menuMapper.selectByParentIdAndName(parentId, name);\n        if (menu == null) {\n            return;\n        }\n        // 如果 id 为空，说明不用比较是否为相同 id 的菜单\n        if (id == null) {\n            throw exception(MENU_NAME_DUPLICATE);\n        }\n        if (!menu.getId().equals(id)) {\n            throw exception(MENU_NAME_DUPLICATE);\n        }\n    }\n\n    /**\n     * 初始化菜单的通用属性。\n     * <p>\n     * 例如说，只有目录或者菜单类型的菜单，才设置 icon\n     *\n     * @param menu 菜单\n     */\n    private void initMenuProperty(MenuDO menu) {\n        // 菜单为按钮类型时，无需 component、icon、path 属性，进行置空\n        if (MenuTypeEnum.BUTTON.getType().equals(menu.getType())) {\n            menu.setComponent(\"\");\n            menu.setComponentName(\"\");\n            menu.setIcon(\"\");\n            menu.setPath(\"\");\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/permission/PermissionService.java",
    "content": "package co.yixiang.yshop.module.system.service.permission;\n\nimport co.yixiang.yshop.module.system.api.permission.dto.DeptDataPermissionRespDTO;\n\nimport java.util.Collection;\nimport java.util.Set;\n\nimport static java.util.Collections.singleton;\n\n/**\n * 权限 Service 接口\n * <p>\n * 提供用户-角色、角色-菜单、角色-部门的关联权限处理\n *\n * @author yshop\n */\npublic interface PermissionService {\n\n    /**\n     * 判断是否有权限，任一一个即可\n     *\n     * @param userId      用户编号\n     * @param permissions 权限\n     * @return 是否\n     */\n    boolean hasAnyPermissions(Long userId, String... permissions);\n\n    /**\n     * 判断是否有角色，任一一个即可\n     *\n     * @param roles 角色数组\n     * @return 是否\n     */\n    boolean hasAnyRoles(Long userId, String... roles);\n\n    // ========== 角色-菜单的相关方法  ==========\n\n    /**\n     * 设置角色菜单\n     *\n     * @param roleId  角色编号\n     * @param menuIds 菜单编号集合\n     */\n    void assignRoleMenu(Long roleId, Set<Long> menuIds);\n\n    /**\n     * 处理角色删除时，删除关联授权数据\n     *\n     * @param roleId 角色编号\n     */\n    void processRoleDeleted(Long roleId);\n\n    /**\n     * 处理菜单删除时，删除关联授权数据\n     *\n     * @param menuId 菜单编号\n     */\n    void processMenuDeleted(Long menuId);\n\n    /**\n     * 获得角色拥有的菜单编号集合\n     *\n     * @param roleId 角色编号\n     * @return 菜单编号集合\n     */\n    default Set<Long> getRoleMenuListByRoleId(Long roleId) {\n        return getRoleMenuListByRoleId(singleton(roleId));\n    }\n\n    /**\n     * 获得角色们拥有的菜单编号集合\n     *\n     * @param roleIds 角色编号数组\n     * @return 菜单编号集合\n     */\n    Set<Long> getRoleMenuListByRoleId(Collection<Long> roleIds);\n\n    /**\n     * 获得拥有指定菜单的角色编号数组，从缓存中获取\n     *\n     * @param menuId 菜单编号\n     * @return 角色编号数组\n     */\n    Set<Long> getMenuRoleIdListByMenuIdFromCache(Long menuId);\n\n    // ========== 用户-角色的相关方法  ==========\n\n    /**\n     * 设置用户角色\n     *\n     * @param userId  角色编号\n     * @param roleIds 角色编号集合\n     */\n    void assignUserRole(Long userId, Set<Long> roleIds);\n\n    /**\n     * 处理用户删除时，删除关联授权数据\n     *\n     * @param userId 用户编号\n     */\n    void processUserDeleted(Long userId);\n\n    /**\n     * 获得拥有多个角色的用户编号集合\n     *\n     * @param roleIds 角色编号集合\n     * @return 用户编号集合\n     */\n    Set<Long> getUserRoleIdListByRoleId(Collection<Long> roleIds);\n\n    /**\n     * 获得用户拥有的角色编号集合\n     *\n     * @param userId 用户编号\n     * @return 角色编号集合\n     */\n    Set<Long> getUserRoleIdListByUserId(Long userId);\n\n    /**\n     * 获得用户拥有的角色编号集合，从缓存中获取\n     *\n     * @param userId 用户编号\n     * @return 角色编号集合\n     */\n    Set<Long> getUserRoleIdListByUserIdFromCache(Long userId);\n\n    // ========== 用户-部门的相关方法  ==========\n\n    /**\n     * 设置角色的数据权限\n     *\n     * @param roleId           角色编号\n     * @param dataScope        数据范围\n     * @param dataScopeDeptIds 部门编号数组\n     */\n    void assignRoleDataScope(Long roleId, Integer dataScope, Set<Long> dataScopeDeptIds);\n\n    /**\n     * 获得登陆用户的部门数据权限\n     *\n     * @param userId 用户编号\n     * @return 部门数据权限\n     */\n    DeptDataPermissionRespDTO getDeptDataPermission(Long userId);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/permission/PermissionServiceImpl.java",
    "content": "package co.yixiang.yshop.module.system.service.permission;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.collection.CollectionUtil;\nimport cn.hutool.core.util.ArrayUtil;\nimport cn.hutool.extra.spring.SpringUtil;\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.util.collection.CollectionUtils;\nimport co.yixiang.yshop.framework.datapermission.core.annotation.DataPermission;\nimport co.yixiang.yshop.module.system.api.permission.dto.DeptDataPermissionRespDTO;\nimport co.yixiang.yshop.module.system.dal.dataobject.permission.MenuDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.permission.RoleDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.permission.RoleMenuDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.permission.UserRoleDO;\nimport co.yixiang.yshop.module.system.dal.mysql.permission.RoleMenuMapper;\nimport co.yixiang.yshop.module.system.dal.mysql.permission.UserRoleMapper;\nimport co.yixiang.yshop.module.system.dal.redis.RedisKeyConstants;\nimport co.yixiang.yshop.module.system.enums.permission.DataScopeEnum;\nimport co.yixiang.yshop.module.system.service.dept.DeptService;\nimport co.yixiang.yshop.module.system.service.user.AdminUserService;\nimport com.baomidou.dynamic.datasource.annotation.DSTransactional;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.base.Suppliers;\nimport com.google.common.collect.Sets;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.cache.annotation.CacheEvict;\nimport org.springframework.cache.annotation.Cacheable;\nimport org.springframework.cache.annotation.Caching;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport jakarta.annotation.Resource;\nimport java.util.*;\nimport java.util.function.Supplier;\n\nimport static co.yixiang.yshop.framework.common.util.collection.CollectionUtils.convertSet;\nimport static co.yixiang.yshop.framework.common.util.json.JsonUtils.toJsonString;\n\n/**\n * 权限 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Slf4j\npublic class PermissionServiceImpl implements PermissionService {\n\n    @Resource\n    private RoleMenuMapper roleMenuMapper;\n    @Resource\n    private UserRoleMapper userRoleMapper;\n\n    @Resource\n    private RoleService roleService;\n    @Resource\n    private MenuService menuService;\n    @Resource\n    private DeptService deptService;\n    @Resource\n    private AdminUserService userService;\n\n    @Override\n    public boolean hasAnyPermissions(Long userId, String... permissions) {\n        // 如果为空，说明已经有权限\n        if (ArrayUtil.isEmpty(permissions)) {\n            return true;\n        }\n\n        // 获得当前登录的角色。如果为空，说明没有权限\n        List<RoleDO> roles = getEnableUserRoleListByUserIdFromCache(userId);\n        if (CollUtil.isEmpty(roles)) {\n            return false;\n        }\n\n        // 情况一：遍历判断每个权限，如果有一满足，说明有权限\n        for (String permission : permissions) {\n            if (hasAnyPermission(roles, permission)) {\n                return true;\n            }\n        }\n\n        // 情况二：如果是超管，也说明有权限\n        return roleService.hasAnySuperAdmin(convertSet(roles, RoleDO::getId));\n    }\n\n    /**\n     * 判断指定角色，是否拥有该 permission 权限\n     *\n     * @param roles 指定角色数组\n     * @param permission 权限标识\n     * @return 是否拥有\n     */\n    private boolean hasAnyPermission(List<RoleDO> roles, String permission) {\n        List<Long> menuIds = menuService.getMenuIdListByPermissionFromCache(permission);\n        // 采用严格模式，如果权限找不到对应的 Menu 的话，也认为没有权限\n        if (CollUtil.isEmpty(menuIds)) {\n            return false;\n        }\n\n        // 判断是否有权限\n        Set<Long> roleIds = convertSet(roles, RoleDO::getId);\n        for (Long menuId : menuIds) {\n            // 获得拥有该菜单的角色编号集合\n            Set<Long> menuRoleIds = getSelf().getMenuRoleIdListByMenuIdFromCache(menuId);\n            // 如果有交集，说明有权限\n            if (CollUtil.containsAny(menuRoleIds, roleIds)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    @Override\n    public boolean hasAnyRoles(Long userId, String... roles) {\n        // 如果为空，说明已经有权限\n        if (ArrayUtil.isEmpty(roles)) {\n            return true;\n        }\n\n        // 获得当前登录的角色。如果为空，说明没有权限\n        List<RoleDO> roleList = getEnableUserRoleListByUserIdFromCache(userId);\n        if (CollUtil.isEmpty(roleList)) {\n            return false;\n        }\n\n        // 判断是否有角色\n        Set<String> userRoles = convertSet(roleList, RoleDO::getCode);\n        return CollUtil.containsAny(userRoles, Sets.newHashSet(roles));\n    }\n\n    // ========== 角色-菜单的相关方法  ==========\n\n    @Override\n    @DSTransactional // 多数据源，使用 @DSTransactional 保证本地事务，以及数据源的切换\n    @CacheEvict(value = RedisKeyConstants.MENU_ROLE_ID_LIST,\n            allEntries = true) // allEntries 清空所有缓存，主要一次更新涉及到的 menuIds 较多，反倒批量会更快\n    public void assignRoleMenu(Long roleId, Set<Long> menuIds) {\n        // 获得角色拥有菜单编号\n        Set<Long> dbMenuIds = convertSet(roleMenuMapper.selectListByRoleId(roleId), RoleMenuDO::getMenuId);\n        // 计算新增和删除的菜单编号\n        Set<Long> menuIdList = CollUtil.emptyIfNull(menuIds);\n        Collection<Long> createMenuIds = CollUtil.subtract(menuIdList, dbMenuIds);\n        Collection<Long> deleteMenuIds = CollUtil.subtract(dbMenuIds, menuIdList);\n        // 执行新增和删除。对于已经授权的菜单，不用做任何处理\n        if (CollUtil.isNotEmpty(createMenuIds)) {\n            roleMenuMapper.insertBatch(CollectionUtils.convertList(createMenuIds, menuId -> {\n                RoleMenuDO entity = new RoleMenuDO();\n                entity.setRoleId(roleId);\n                entity.setMenuId(menuId);\n                return entity;\n            }));\n        }\n        if (CollUtil.isNotEmpty(deleteMenuIds)) {\n            roleMenuMapper.deleteListByRoleIdAndMenuIds(roleId, deleteMenuIds);\n        }\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    @Caching(evict = {\n            @CacheEvict(value = RedisKeyConstants.MENU_ROLE_ID_LIST,\n                    allEntries = true), // allEntries 清空所有缓存，此处无法方便获得 roleId 对应的 menu 缓存们\n            @CacheEvict(value = RedisKeyConstants.USER_ROLE_ID_LIST,\n                    allEntries = true) // allEntries 清空所有缓存，此处无法方便获得 roleId 对应的 user 缓存们\n    })\n    public void processRoleDeleted(Long roleId) {\n        // 标记删除 UserRole\n        userRoleMapper.deleteListByRoleId(roleId);\n        // 标记删除 RoleMenu\n        roleMenuMapper.deleteListByRoleId(roleId);\n    }\n\n    @Override\n    @CacheEvict(value = RedisKeyConstants.MENU_ROLE_ID_LIST, key = \"#menuId\")\n    public void processMenuDeleted(Long menuId) {\n        roleMenuMapper.deleteListByMenuId(menuId);\n    }\n\n    @Override\n    public Set<Long> getRoleMenuListByRoleId(Collection<Long> roleIds) {\n        if (CollUtil.isEmpty(roleIds)) {\n            return Collections.emptySet();\n        }\n\n        // 如果是管理员的情况下，获取全部菜单编号\n        if (roleService.hasAnySuperAdmin(roleIds)) {\n            return convertSet(menuService.getMenuList(), MenuDO::getId);\n        }\n        // 如果是非管理员的情况下，获得拥有的菜单编号\n        return convertSet(roleMenuMapper.selectListByRoleId(roleIds), RoleMenuDO::getMenuId);\n    }\n\n    @Override\n    @Cacheable(value = RedisKeyConstants.MENU_ROLE_ID_LIST, key = \"#menuId\")\n    public Set<Long> getMenuRoleIdListByMenuIdFromCache(Long menuId) {\n        return convertSet(roleMenuMapper.selectListByMenuId(menuId), RoleMenuDO::getRoleId);\n    }\n\n    // ========== 用户-角色的相关方法  ==========\n\n    @Override\n    @DSTransactional // 多数据源，使用 @DSTransactional 保证本地事务，以及数据源的切换\n    @CacheEvict(value = RedisKeyConstants.USER_ROLE_ID_LIST, key = \"#userId\")\n    public void assignUserRole(Long userId, Set<Long> roleIds) {\n        // 获得角色拥有角色编号\n        Set<Long> dbRoleIds = convertSet(userRoleMapper.selectListByUserId(userId),\n                UserRoleDO::getRoleId);\n        // 计算新增和删除的角色编号\n        Set<Long> roleIdList = CollUtil.emptyIfNull(roleIds);\n        Collection<Long> createRoleIds = CollUtil.subtract(roleIdList, dbRoleIds);\n        Collection<Long> deleteMenuIds = CollUtil.subtract(dbRoleIds, roleIdList);\n        // 执行新增和删除。对于已经授权的角色，不用做任何处理\n        if (!CollectionUtil.isEmpty(createRoleIds)) {\n            userRoleMapper.insertBatch(CollectionUtils.convertList(createRoleIds, roleId -> {\n                UserRoleDO entity = new UserRoleDO();\n                entity.setUserId(userId);\n                entity.setRoleId(roleId);\n                return entity;\n            }));\n        }\n        if (!CollectionUtil.isEmpty(deleteMenuIds)) {\n            userRoleMapper.deleteListByUserIdAndRoleIdIds(userId, deleteMenuIds);\n        }\n    }\n\n    @Override\n    @CacheEvict(value = RedisKeyConstants.USER_ROLE_ID_LIST, key = \"#userId\")\n    public void processUserDeleted(Long userId) {\n        userRoleMapper.deleteListByUserId(userId);\n    }\n\n    @Override\n    public Set<Long> getUserRoleIdListByUserId(Long userId) {\n        return convertSet(userRoleMapper.selectListByUserId(userId), UserRoleDO::getRoleId);\n    }\n\n    @Override\n    @Cacheable(value = RedisKeyConstants.USER_ROLE_ID_LIST, key = \"#userId\")\n    public Set<Long> getUserRoleIdListByUserIdFromCache(Long userId) {\n        return getUserRoleIdListByUserId(userId);\n    }\n\n    @Override\n    public Set<Long> getUserRoleIdListByRoleId(Collection<Long> roleIds) {\n        return convertSet(userRoleMapper.selectListByRoleIds(roleIds), UserRoleDO::getUserId);\n    }\n\n    /**\n     * 获得用户拥有的角色，并且这些角色是开启状态的\n     *\n     * @param userId 用户编号\n     * @return 用户拥有的角色\n     */\n    @VisibleForTesting\n    List<RoleDO> getEnableUserRoleListByUserIdFromCache(Long userId) {\n        // 获得用户拥有的角色编号\n        Set<Long> roleIds = getSelf().getUserRoleIdListByUserIdFromCache(userId);\n        // 获得角色数组，并移除被禁用的\n        List<RoleDO> roles = roleService.getRoleListFromCache(roleIds);\n        roles.removeIf(role -> !CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus()));\n        return roles;\n    }\n\n    // ========== 用户-部门的相关方法  ==========\n\n    @Override\n    public void assignRoleDataScope(Long roleId, Integer dataScope, Set<Long> dataScopeDeptIds) {\n        roleService.updateRoleDataScope(roleId, dataScope, dataScopeDeptIds);\n    }\n\n    @Override\n    @DataPermission(enable = false) // 关闭数据权限，不然就会出现递归获取数据权限的问题\n    public DeptDataPermissionRespDTO getDeptDataPermission(Long userId) {\n        // 获得用户的角色\n        List<RoleDO> roles = getEnableUserRoleListByUserIdFromCache(userId);\n\n        // 如果角色为空，则只能查看自己\n        DeptDataPermissionRespDTO result = new DeptDataPermissionRespDTO();\n        if (CollUtil.isEmpty(roles)) {\n            result.setSelf(true);\n            return result;\n        }\n\n        // 获得用户的部门编号的缓存，通过 Guava 的 Suppliers 惰性求值，即有且仅有第一次发起 DB 的查询\n        Supplier<Long> userDeptId = Suppliers.memoize(() -> userService.getUser(userId).getDeptId());\n        // 遍历每个角色，计算\n        for (RoleDO role : roles) {\n            // 为空时，跳过\n            if (role.getDataScope() == null) {\n                continue;\n            }\n            // 情况一，ALL\n            if (Objects.equals(role.getDataScope(), DataScopeEnum.ALL.getScope())) {\n                result.setAll(true);\n                continue;\n            }\n            // 情况二，DEPT_CUSTOM\n            if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_CUSTOM.getScope())) {\n                CollUtil.addAll(result.getDeptIds(), role.getDataScopeDeptIds());\n                // 自定义可见部门时，保证可以看到自己所在的部门。否则，一些场景下可能会有问题。\n                // 例如说，登录时，基于 t_user 的 username 查询会可能被 dept_id 过滤掉\n                CollUtil.addAll(result.getDeptIds(), userDeptId.get());\n                continue;\n            }\n            // 情况三，DEPT_ONLY\n            if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_ONLY.getScope())) {\n                CollectionUtils.addIfNotNull(result.getDeptIds(), userDeptId.get());\n                continue;\n            }\n            // 情况四，DEPT_DEPT_AND_CHILD\n            if (Objects.equals(role.getDataScope(), DataScopeEnum.DEPT_AND_CHILD.getScope())) {\n                CollUtil.addAll(result.getDeptIds(), deptService.getChildDeptIdListFromCache(userDeptId.get()));\n                // 添加本身部门编号\n                CollUtil.addAll(result.getDeptIds(), userDeptId.get());\n                continue;\n            }\n            // 情况五，SELF\n            if (Objects.equals(role.getDataScope(), DataScopeEnum.SELF.getScope())) {\n                result.setSelf(true);\n                continue;\n            }\n            // 未知情况，error log 即可\n            log.error(\"[getDeptDataPermission][LoginUser({}) role({}) 无法处理]\", userId, toJsonString(result));\n        }\n        return result;\n    }\n\n    /**\n     * 获得自身的代理对象，解决 AOP 生效问题\n     *\n     * @return 自己\n     */\n    private PermissionServiceImpl getSelf() {\n        return SpringUtil.getBean(getClass());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/permission/RoleService.java",
    "content": "package co.yixiang.yshop.module.system.service.permission;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.system.controller.admin.permission.vo.role.RolePageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.permission.vo.role.RoleSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.permission.RoleDO;\n\nimport jakarta.validation.Valid;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Set;\n\n/**\n * 角色 Service 接口\n *\n * @author yshop\n */\npublic interface RoleService {\n\n    /**\n     * 创建角色\n     *\n     * @param createReqVO 创建角色信息\n     * @param type 角色类型\n     * @return 角色编号\n     */\n    Long createRole(@Valid RoleSaveReqVO createReqVO, Integer type);\n\n    /**\n     * 更新角色\n     *\n     * @param updateReqVO 更新角色信息\n     */\n    void updateRole(@Valid RoleSaveReqVO updateReqVO);\n\n    /**\n     * 删除角色\n     *\n     * @param id 角色编号\n     */\n    void deleteRole(Long id);\n\n    /**\n     * 设置角色的数据权限\n     *\n     * @param id 角色编号\n     * @param dataScope 数据范围\n     * @param dataScopeDeptIds 部门编号数组\n     */\n    void updateRoleDataScope(Long id, Integer dataScope, Set<Long> dataScopeDeptIds);\n\n    /**\n     * 获得角色\n     *\n     * @param id 角色编号\n     * @return 角色\n     */\n    RoleDO getRole(Long id);\n\n    /**\n     * 获得角色，从缓存中\n     *\n     * @param id 角色编号\n     * @return 角色\n     */\n    RoleDO getRoleFromCache(Long id);\n\n    /**\n     * 获得角色列表\n     *\n     * @param ids 角色编号数组\n     * @return 角色列表\n     */\n    List<RoleDO> getRoleList(Collection<Long> ids);\n\n    /**\n     * 获得角色数组，从缓存中\n     *\n     * @param ids 角色编号数组\n     * @return 角色数组\n     */\n    List<RoleDO> getRoleListFromCache(Collection<Long> ids);\n\n    /**\n     * 获得角色列表\n     *\n     * @param statuses 筛选的状态\n     * @return 角色列表\n     */\n    List<RoleDO> getRoleListByStatus(Collection<Integer> statuses);\n\n    /**\n     * 获得所有角色列表\n     *\n     * @return 角色列表\n     */\n    List<RoleDO> getRoleList();\n\n    /**\n     * 获得角色分页\n     *\n     * @param reqVO 角色分页查询\n     * @return 角色分页结果\n     */\n    PageResult<RoleDO> getRolePage(RolePageReqVO reqVO);\n\n    /**\n     * 判断角色编号数组中，是否有管理员\n     *\n     * @param ids 角色编号数组\n     * @return 是否有管理员\n     */\n    boolean hasAnySuperAdmin(Collection<Long> ids);\n\n    /**\n     * 校验角色们是否有效。如下情况，视为无效：\n     * 1. 角色编号不存在\n     * 2. 角色被禁用\n     *\n     * @param ids 角色编号数组\n     */\n    void validateRoleList(Collection<Long> ids);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/permission/RoleServiceImpl.java",
    "content": "package co.yixiang.yshop.module.system.service.permission;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.collection.CollectionUtil;\nimport cn.hutool.core.util.ObjectUtil;\nimport cn.hutool.extra.spring.SpringUtil;\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.collection.CollectionUtils;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.controller.admin.permission.vo.role.RolePageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.permission.vo.role.RoleSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.permission.RoleDO;\nimport co.yixiang.yshop.module.system.dal.mysql.permission.RoleMapper;\nimport co.yixiang.yshop.module.system.dal.redis.RedisKeyConstants;\nimport co.yixiang.yshop.module.system.enums.permission.DataScopeEnum;\nimport co.yixiang.yshop.module.system.enums.permission.RoleCodeEnum;\nimport co.yixiang.yshop.module.system.enums.permission.RoleTypeEnum;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.mzt.logapi.context.LogRecordContext;\nimport com.mzt.logapi.service.impl.DiffParseFunction;\nimport com.mzt.logapi.starter.annotation.LogRecord;\nimport jakarta.annotation.Resource;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.cache.annotation.CacheEvict;\nimport org.springframework.cache.annotation.Cacheable;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport org.springframework.util.StringUtils;\n\nimport java.util.*;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.framework.common.util.collection.CollectionUtils.convertMap;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.*;\nimport static co.yixiang.yshop.module.system.enums.LogRecordConstants.*;\n\n/**\n * 角色 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Slf4j\npublic class RoleServiceImpl implements RoleService {\n\n    @Resource\n    private PermissionService permissionService;\n\n    @Resource\n    private RoleMapper roleMapper;\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    @LogRecord(type = SYSTEM_ROLE_TYPE, subType = SYSTEM_ROLE_CREATE_SUB_TYPE, bizNo = \"{{#role.id}}\",\n            success = SYSTEM_ROLE_CREATE_SUCCESS)\n    public Long createRole(RoleSaveReqVO createReqVO, Integer type) {\n        // 1. 校验角色\n        validateRoleDuplicate(createReqVO.getName(), createReqVO.getCode(), null);\n\n        // 2. 插入到数据库\n        RoleDO role = BeanUtils.toBean(createReqVO, RoleDO.class)\n                .setType(ObjectUtil.defaultIfNull(type, RoleTypeEnum.CUSTOM.getType()))\n                .setStatus(CommonStatusEnum.ENABLE.getStatus())\n                .setDataScope(DataScopeEnum.ALL.getScope()); // 默认可查看所有数据。原因是，可能一些项目不需要项目权限\n        roleMapper.insert(role);\n\n        // 3. 记录操作日志上下文\n        LogRecordContext.putVariable(\"role\", role);\n        return role.getId();\n    }\n\n    @Override\n    @CacheEvict(value = RedisKeyConstants.ROLE, key = \"#updateReqVO.id\")\n    @LogRecord(type = SYSTEM_ROLE_TYPE, subType = SYSTEM_ROLE_UPDATE_SUB_TYPE, bizNo = \"{{#updateReqVO.id}}\",\n            success = SYSTEM_ROLE_UPDATE_SUCCESS)\n    public void updateRole(RoleSaveReqVO updateReqVO) {\n        // 1.1 校验是否可以更新\n        RoleDO role = validateRoleForUpdate(updateReqVO.getId());\n        // 1.2 校验角色的唯一字段是否重复\n        validateRoleDuplicate(updateReqVO.getName(), updateReqVO.getCode(), updateReqVO.getId());\n\n        // 2. 更新到数据库\n        RoleDO updateObj = BeanUtils.toBean(updateReqVO, RoleDO.class);\n        roleMapper.updateById(updateObj);\n\n        // 3. 记录操作日志上下文\n        LogRecordContext.putVariable(\"role\", role);\n    }\n\n    @Override\n    @CacheEvict(value = RedisKeyConstants.ROLE, key = \"#id\")\n    public void updateRoleDataScope(Long id, Integer dataScope, Set<Long> dataScopeDeptIds) {\n        // 校验是否可以更新\n        validateRoleForUpdate(id);\n\n        // 更新数据范围\n        RoleDO updateObject = new RoleDO();\n        updateObject.setId(id);\n        updateObject.setDataScope(dataScope);\n        updateObject.setDataScopeDeptIds(dataScopeDeptIds);\n        roleMapper.updateById(updateObject);\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    @CacheEvict(value = RedisKeyConstants.ROLE, key = \"#id\")\n    @LogRecord(type = SYSTEM_ROLE_TYPE, subType = SYSTEM_ROLE_DELETE_SUB_TYPE, bizNo = \"{{#id}}\",\n            success = SYSTEM_ROLE_DELETE_SUCCESS)\n    public void deleteRole(Long id) {\n        // 1. 校验是否可以更新\n        RoleDO role = validateRoleForUpdate(id);\n\n        // 2.1 标记删除\n        roleMapper.deleteById(id);\n        // 2.2 删除相关数据\n        permissionService.processRoleDeleted(id);\n\n        // 3. 记录操作日志上下文\n        LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(role, RoleSaveReqVO.class));\n        LogRecordContext.putVariable(\"role\", role);\n    }\n\n    /**\n     * 校验角色的唯一字段是否重复\n     *\n     * 1. 是否存在相同名字的角色\n     * 2. 是否存在相同编码的角色\n     *\n     * @param name 角色名字\n     * @param code 角色额编码\n     * @param id 角色编号\n     */\n    @VisibleForTesting\n    void validateRoleDuplicate(String name, String code, Long id) {\n        // 0. 超级管理员，不允许创建\n        if (RoleCodeEnum.isSuperAdmin(code)) {\n            throw exception(ROLE_ADMIN_CODE_ERROR, code);\n        }\n        // 1. 该 name 名字被其它角色所使用\n        RoleDO role = roleMapper.selectByName(name);\n        if (role != null && !role.getId().equals(id)) {\n            throw exception(ROLE_NAME_DUPLICATE, name);\n        }\n        // 2. 是否存在相同编码的角色\n        if (!StringUtils.hasText(code)) {\n            return;\n        }\n        // 该 code 编码被其它角色所使用\n        role = roleMapper.selectByCode(code);\n        if (role != null && !role.getId().equals(id)) {\n            throw exception(ROLE_CODE_DUPLICATE, code);\n        }\n    }\n\n    /**\n     * 校验角色是否可以被更新\n     *\n     * @param id 角色编号\n     */\n    @VisibleForTesting\n    RoleDO validateRoleForUpdate(Long id) {\n        RoleDO role = roleMapper.selectById(id);\n        if (role == null) {\n            throw exception(ROLE_NOT_EXISTS);\n        }\n        // 内置角色，不允许删除\n        if (RoleTypeEnum.SYSTEM.getType().equals(role.getType())) {\n            throw exception(ROLE_CAN_NOT_UPDATE_SYSTEM_TYPE_ROLE);\n        }\n        return role;\n    }\n\n    @Override\n    public RoleDO getRole(Long id) {\n        return roleMapper.selectById(id);\n    }\n\n    @Override\n    @Cacheable(value = RedisKeyConstants.ROLE, key = \"#id\",\n            unless = \"#result == null\")\n    public RoleDO getRoleFromCache(Long id) {\n        return roleMapper.selectById(id);\n    }\n\n\n    @Override\n    public List<RoleDO> getRoleListByStatus(Collection<Integer> statuses) {\n        return roleMapper.selectListByStatus(statuses);\n    }\n\n    @Override\n    public List<RoleDO> getRoleList() {\n        return roleMapper.selectList();\n    }\n\n    @Override\n    public List<RoleDO> getRoleList(Collection<Long> ids) {\n        if (CollectionUtil.isEmpty(ids)) {\n            return Collections.emptyList();\n        }\n        return roleMapper.selectBatchIds(ids);\n    }\n\n    @Override\n    public List<RoleDO> getRoleListFromCache(Collection<Long> ids) {\n        if (CollectionUtil.isEmpty(ids)) {\n            return Collections.emptyList();\n        }\n        // 这里采用 for 循环从缓存中获取，主要考虑 Spring CacheManager 无法批量操作的问题\n        RoleServiceImpl self = getSelf();\n        return CollectionUtils.convertList(ids, self::getRoleFromCache);\n    }\n\n    @Override\n    public PageResult<RoleDO> getRolePage(RolePageReqVO reqVO) {\n        return roleMapper.selectPage(reqVO);\n    }\n\n    @Override\n    public boolean hasAnySuperAdmin(Collection<Long> ids) {\n        if (CollectionUtil.isEmpty(ids)) {\n            return false;\n        }\n        RoleServiceImpl self = getSelf();\n        return ids.stream().anyMatch(id -> {\n            RoleDO role = self.getRoleFromCache(id);\n            return role != null && RoleCodeEnum.isSuperAdmin(role.getCode());\n        });\n    }\n\n    @Override\n    public void validateRoleList(Collection<Long> ids) {\n        if (CollUtil.isEmpty(ids)) {\n            return;\n        }\n        // 获得角色信息\n        List<RoleDO> roles = roleMapper.selectBatchIds(ids);\n        Map<Long, RoleDO> roleMap = convertMap(roles, RoleDO::getId);\n        // 校验\n        ids.forEach(id -> {\n            RoleDO role = roleMap.get(id);\n            if (role == null) {\n                throw exception(ROLE_NOT_EXISTS);\n            }\n            if (!CommonStatusEnum.ENABLE.getStatus().equals(role.getStatus())) {\n                throw exception(ROLE_IS_DISABLE, role.getName());\n            }\n        });\n    }\n\n    /**\n     * 获得自身的代理对象，解决 AOP 生效问题\n     *\n     * @return 自己\n     */\n    private RoleServiceImpl getSelf() {\n        return SpringUtil.getBean(getClass());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/sms/SmsChannelService.java",
    "content": "package co.yixiang.yshop.module.system.service.sms;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.system.framework.sms.core.client.SmsClient;\nimport co.yixiang.yshop.module.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.sms.vo.channel.SmsChannelSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.sms.SmsChannelDO;\n\nimport jakarta.validation.Valid;\nimport java.util.List;\n\n/**\n * 短信渠道 Service 接口\n *\n * @author zzf\n * @since 2021/1/25 9:24\n */\npublic interface SmsChannelService {\n\n    /**\n     * 创建短信渠道\n     *\n     * @param createReqVO 创建信息\n     * @return 编号\n     */\n    Long createSmsChannel(@Valid SmsChannelSaveReqVO createReqVO);\n\n    /**\n     * 更新短信渠道\n     *\n     * @param updateReqVO 更新信息\n     */\n    void updateSmsChannel(@Valid SmsChannelSaveReqVO updateReqVO);\n\n    /**\n     * 删除短信渠道\n     *\n     * @param id 编号\n     */\n    void deleteSmsChannel(Long id);\n\n    /**\n     * 获得短信渠道\n     *\n     * @param id 编号\n     * @return 短信渠道\n     */\n    SmsChannelDO getSmsChannel(Long id);\n\n    /**\n     * 获得所有短信渠道列表\n     *\n     * @return 短信渠道列表\n     */\n    List<SmsChannelDO> getSmsChannelList();\n\n    /**\n     * 获得短信渠道分页\n     *\n     * @param pageReqVO 分页查询\n     * @return 短信渠道分页\n     */\n    PageResult<SmsChannelDO> getSmsChannelPage(SmsChannelPageReqVO pageReqVO);\n\n    /**\n     * 获得短信客户端\n     *\n     * @param id 编号\n     * @return 短信客户端\n     */\n    SmsClient getSmsClient(Long id);\n\n    /**\n     * 获得短信客户端\n     *\n     * @param code 编码\n     * @return 短信客户端\n     */\n    SmsClient getSmsClient(String code);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/sms/SmsChannelServiceImpl.java",
    "content": "package co.yixiang.yshop.module.system.service.sms;\n\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.framework.sms.core.client.SmsClient;\nimport co.yixiang.yshop.module.system.framework.sms.core.client.SmsClientFactory;\nimport co.yixiang.yshop.module.system.framework.sms.core.property.SmsChannelProperties;\nimport co.yixiang.yshop.module.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.sms.vo.channel.SmsChannelSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.sms.SmsChannelDO;\nimport co.yixiang.yshop.module.system.dal.mysql.sms.SmsChannelMapper;\nimport com.google.common.cache.CacheLoader;\nimport com.google.common.cache.LoadingCache;\nimport lombok.Getter;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Service;\n\nimport jakarta.annotation.Resource;\nimport java.time.Duration;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.framework.common.util.cache.CacheUtils.buildAsyncReloadingCache;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_HAS_CHILDREN;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_NOT_EXISTS;\n\n/**\n * 短信渠道 Service 实现类\n *\n * @author zzf\n */\n@Service\n@Slf4j\npublic class SmsChannelServiceImpl implements SmsChannelService {\n\n    /**\n     * {@link SmsClient} 缓存，通过它异步刷新 smsClientFactory\n     */\n    @Getter\n    private final LoadingCache<Long, SmsClient> idClientCache = buildAsyncReloadingCache(Duration.ofSeconds(10L),\n            new CacheLoader<Long, SmsClient>() {\n\n                @Override\n                public SmsClient load(Long id) {\n                    // 查询，然后尝试刷新\n                    SmsChannelDO channel = smsChannelMapper.selectById(id);\n                    if (channel != null) {\n                        SmsChannelProperties properties = BeanUtils.toBean(channel, SmsChannelProperties.class);\n                        smsClientFactory.createOrUpdateSmsClient(properties);\n                    }\n                    return smsClientFactory.getSmsClient(id);\n                }\n\n            });\n\n    /**\n     * {@link SmsClient} 缓存，通过它异步刷新 smsClientFactory\n     */\n    @Getter\n    private final LoadingCache<String, SmsClient> codeClientCache = buildAsyncReloadingCache(Duration.ofSeconds(60L),\n            new CacheLoader<String, SmsClient>() {\n\n                @Override\n                public SmsClient load(String code) {\n                    // 查询，然后尝试刷新\n                    SmsChannelDO channel = smsChannelMapper.selectByCode(code);\n                    if (channel != null) {\n                        SmsChannelProperties properties = BeanUtils.toBean(channel, SmsChannelProperties.class);\n                        smsClientFactory.createOrUpdateSmsClient(properties);\n                    }\n                    return smsClientFactory.getSmsClient(code);\n                }\n\n            });\n\n    @Resource\n    private SmsClientFactory smsClientFactory;\n\n    @Resource\n    private SmsChannelMapper smsChannelMapper;\n\n    @Resource\n    private SmsTemplateService smsTemplateService;\n\n    @Override\n    public Long createSmsChannel(SmsChannelSaveReqVO createReqVO) {\n        SmsChannelDO channel = BeanUtils.toBean(createReqVO, SmsChannelDO.class);\n        smsChannelMapper.insert(channel);\n        return channel.getId();\n    }\n\n    @Override\n    public void updateSmsChannel(SmsChannelSaveReqVO updateReqVO) {\n        // 校验存在\n        SmsChannelDO channel = validateSmsChannelExists(updateReqVO.getId());\n        // 更新\n        SmsChannelDO updateObj = BeanUtils.toBean(updateReqVO, SmsChannelDO.class);\n        smsChannelMapper.updateById(updateObj);\n\n        // 清空缓存\n        clearCache(updateReqVO.getId(), channel.getCode());\n    }\n\n    @Override\n    public void deleteSmsChannel(Long id) {\n        // 校验存在\n        SmsChannelDO channel = validateSmsChannelExists(id);\n        // 校验是否有在使用该账号的模版\n        if (smsTemplateService.getSmsTemplateCountByChannelId(id) > 0) {\n            throw exception(SMS_CHANNEL_HAS_CHILDREN);\n        }\n        // 删除\n        smsChannelMapper.deleteById(id);\n\n        // 清空缓存\n        clearCache(id, channel.getCode());\n    }\n\n    /**\n     * 清空指定渠道编号的缓存\n     *\n     * @param id 渠道编号\n     * @param code 渠道编码\n     */\n    private void clearCache(Long id, String code) {\n        idClientCache.invalidate(id);\n        if (StrUtil.isNotEmpty(code)) {\n            codeClientCache.invalidate(code);\n        }\n    }\n\n    private SmsChannelDO validateSmsChannelExists(Long id) {\n        SmsChannelDO channel = smsChannelMapper.selectById(id);\n        if (channel == null) {\n            throw exception(SMS_CHANNEL_NOT_EXISTS);\n        }\n        return channel;\n    }\n\n    @Override\n    public SmsChannelDO getSmsChannel(Long id) {\n        return smsChannelMapper.selectById(id);\n    }\n\n    @Override\n    public List<SmsChannelDO> getSmsChannelList() {\n        return smsChannelMapper.selectList();\n    }\n\n    @Override\n    public PageResult<SmsChannelDO> getSmsChannelPage(SmsChannelPageReqVO pageReqVO) {\n        return smsChannelMapper.selectPage(pageReqVO);\n    }\n\n    @Override\n    public SmsClient getSmsClient(Long id) {\n        return idClientCache.getUnchecked(id);\n    }\n\n    @Override\n    public SmsClient getSmsClient(String code) {\n        return codeClientCache.getUnchecked(code);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/sms/SmsCodeService.java",
    "content": "package co.yixiang.yshop.module.system.service.sms;\n\nimport co.yixiang.yshop.framework.common.exception.ServiceException;\nimport co.yixiang.yshop.module.system.api.sms.dto.code.SmsCodeValidateReqDTO;\nimport co.yixiang.yshop.module.system.api.sms.dto.code.SmsCodeSendReqDTO;\nimport co.yixiang.yshop.module.system.api.sms.dto.code.SmsCodeUseReqDTO;\n\nimport jakarta.validation.Valid;\n\n/**\n * 短信验证码 Service 接口\n *\n * @author yshop\n */\npublic interface SmsCodeService {\n\n    /**\n     * 创建短信验证码，并进行发送\n     *\n     * @param reqDTO 发送请求\n     */\n    void sendSmsCode(@Valid SmsCodeSendReqDTO reqDTO);\n\n    /**\n     * 验证短信验证码，并进行使用\n     * 如果正确，则将验证码标记成已使用\n     * 如果错误，则抛出 {@link ServiceException} 异常\n     *\n     * @param reqDTO 使用请求\n     */\n    void useSmsCode(@Valid SmsCodeUseReqDTO reqDTO);\n\n    /**\n     * 检查验证码是否有效\n     *\n     * @param reqDTO 校验请求\n     */\n    void validateSmsCode(@Valid SmsCodeValidateReqDTO reqDTO);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/sms/SmsCodeServiceImpl.java",
    "content": "package co.yixiang.yshop.module.system.service.sms;\n\nimport cn.hutool.core.date.LocalDateTimeUtil;\nimport cn.hutool.core.lang.Assert;\nimport cn.hutool.core.map.MapUtil;\nimport co.yixiang.yshop.module.system.api.sms.dto.code.SmsCodeSendReqDTO;\nimport co.yixiang.yshop.module.system.api.sms.dto.code.SmsCodeUseReqDTO;\nimport co.yixiang.yshop.module.system.api.sms.dto.code.SmsCodeValidateReqDTO;\nimport co.yixiang.yshop.module.system.dal.dataobject.sms.SmsCodeDO;\nimport co.yixiang.yshop.module.system.dal.mysql.sms.SmsCodeMapper;\nimport co.yixiang.yshop.module.system.enums.sms.SmsSceneEnum;\nimport co.yixiang.yshop.module.system.framework.sms.config.SmsCodeProperties;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\nimport java.time.LocalDateTime;\n\nimport static cn.hutool.core.util.RandomUtil.randomInt;\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.framework.common.util.date.DateUtils.isToday;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.*;\n\n/**\n * 短信验证码 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class SmsCodeServiceImpl implements SmsCodeService {\n\n    @Resource\n    private SmsCodeProperties smsCodeProperties;\n\n    @Resource\n    private SmsCodeMapper smsCodeMapper;\n\n    @Resource\n    private SmsSendService smsSendService;\n\n    @Override\n    public void sendSmsCode(SmsCodeSendReqDTO reqDTO) {\n        SmsSceneEnum sceneEnum = SmsSceneEnum.getCodeByScene(reqDTO.getScene());\n        Assert.notNull(sceneEnum, \"验证码场景({}) 查找不到配置\", reqDTO.getScene());\n        // 创建验证码\n        String code = createSmsCode(reqDTO.getMobile(), reqDTO.getScene(), reqDTO.getCreateIp());\n        // 发送验证码\n        smsSendService.sendSingleSms(reqDTO.getMobile(), null, null,\n                sceneEnum.getTemplateCode(), MapUtil.of(\"code\", code));\n    }\n\n    private String createSmsCode(String mobile, Integer scene, String ip) {\n        // 校验是否可以发送验证码，不用筛选场景\n        SmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile, null, null);\n        if (lastSmsCode != null) {\n            if (LocalDateTimeUtil.between(lastSmsCode.getCreateTime(), LocalDateTime.now()).toMillis()\n                    < smsCodeProperties.getSendFrequency().toMillis()) { // 发送过于频繁\n                throw exception(SMS_CODE_SEND_TOO_FAST);\n            }\n            if (isToday(lastSmsCode.getCreateTime()) && // 必须是今天，才能计算超过当天的上限\n                    lastSmsCode.getTodayIndex() >= smsCodeProperties.getSendMaximumQuantityPerDay()) { // 超过当天发送的上限。\n                throw exception(SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY);\n            }\n            // TODO yshop：提升，每个 IP 每天可发送数量\n            // TODO yshop：提升，每个 IP 每小时可发送数量\n        }\n\n        // 创建验证码记录\n        String code = String.format(\"%0\" + smsCodeProperties.getEndCode().toString().length() + \"d\",\n                randomInt(smsCodeProperties.getBeginCode(), smsCodeProperties.getEndCode() + 1));\n        SmsCodeDO newSmsCode = SmsCodeDO.builder().mobile(mobile).code(code).scene(scene)\n                .todayIndex(lastSmsCode != null && isToday(lastSmsCode.getCreateTime()) ? lastSmsCode.getTodayIndex() + 1 : 1)\n                .createIp(ip).used(false).build();\n        smsCodeMapper.insert(newSmsCode);\n        return code;\n    }\n\n    @Override\n    public void useSmsCode(SmsCodeUseReqDTO reqDTO) {\n        // 检测验证码是否有效\n        SmsCodeDO lastSmsCode = validateSmsCode0(reqDTO.getMobile(), reqDTO.getCode(), reqDTO.getScene());\n        // 使用验证码\n        smsCodeMapper.updateById(SmsCodeDO.builder().id(lastSmsCode.getId())\n                .used(true).usedTime(LocalDateTime.now()).usedIp(reqDTO.getUsedIp()).build());\n    }\n\n    @Override\n    public void validateSmsCode(SmsCodeValidateReqDTO reqDTO) {\n        validateSmsCode0(reqDTO.getMobile(), reqDTO.getCode(), reqDTO.getScene());\n    }\n\n    private SmsCodeDO validateSmsCode0(String mobile, String code, Integer scene) {\n        // 校验验证码\n        SmsCodeDO lastSmsCode = smsCodeMapper.selectLastByMobile(mobile, code, scene);\n        // 若验证码不存在，抛出异常\n        if (lastSmsCode == null) {\n            throw exception(SMS_CODE_NOT_FOUND);\n        }\n        // 超过时间\n        if (LocalDateTimeUtil.between(lastSmsCode.getCreateTime(), LocalDateTime.now()).toMillis()\n                >= smsCodeProperties.getExpireTimes().toMillis()) { // 验证码已过期\n            throw exception(SMS_CODE_EXPIRED);\n        }\n        // 判断验证码是否已被使用\n        if (Boolean.TRUE.equals(lastSmsCode.getUsed())) {\n            throw exception(SMS_CODE_USED);\n        }\n        return lastSmsCode;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/sms/SmsLogService.java",
    "content": "package co.yixiang.yshop.module.system.service.sms;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.system.controller.admin.sms.vo.log.SmsLogPageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.sms.SmsLogDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.sms.SmsTemplateDO;\n\nimport java.time.LocalDateTime;\nimport java.util.Map;\n\n/**\n * 短信日志 Service 接口\n *\n * @author zzf\n * @date 13:48 2021/3/2\n */\npublic interface SmsLogService {\n\n    /**\n     * 创建短信日志\n     *\n     * @param mobile 手机号\n     * @param userId 用户编号\n     * @param userType 用户类型\n     * @param isSend 是否发送\n     * @param template 短信模板\n     * @param templateContent 短信内容\n     * @param templateParams 短信参数\n     * @return 发送日志编号\n     */\n    Long createSmsLog(String mobile, Long userId, Integer userType, Boolean isSend,\n                      SmsTemplateDO template, String templateContent, Map<String, Object> templateParams);\n\n    /**\n     * 更新日志的发送结果\n     *\n     * @param id 日志编号\n     * @param success 发送是否成功\n     * @param apiSendCode 短信 API 发送结果的编码\n     * @param apiSendMsg 短信 API 发送失败的提示\n     * @param apiRequestId 短信 API 发送返回的唯一请求 ID\n     * @param apiSerialNo 短信 API 发送返回的序号\n     */\n    void updateSmsSendResult(Long id, Boolean success,\n                             String apiSendCode, String apiSendMsg,\n                             String apiRequestId, String apiSerialNo);\n\n    /**\n     * 更新日志的接收结果\n     *\n     * @param id 日志编号\n     * @param success 是否接收成功\n     * @param receiveTime 用户接收时间\n     * @param apiReceiveCode API 接收结果的编码\n     * @param apiReceiveMsg API 接收结果的说明\n     */\n    void updateSmsReceiveResult(Long id, Boolean success,\n                                LocalDateTime receiveTime, String apiReceiveCode, String apiReceiveMsg);\n\n    /**\n     * 获得短信日志分页\n     *\n     * @param pageReqVO 分页查询\n     * @return 短信日志分页\n     */\n    PageResult<SmsLogDO> getSmsLogPage(SmsLogPageReqVO pageReqVO);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/sms/SmsLogServiceImpl.java",
    "content": "package co.yixiang.yshop.module.system.service.sms;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.system.controller.admin.sms.vo.log.SmsLogPageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.sms.SmsLogDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.sms.SmsTemplateDO;\nimport co.yixiang.yshop.module.system.dal.mysql.sms.SmsLogMapper;\nimport co.yixiang.yshop.module.system.enums.sms.SmsReceiveStatusEnum;\nimport co.yixiang.yshop.module.system.enums.sms.SmsSendStatusEnum;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Service;\n\nimport jakarta.annotation.Resource;\nimport java.time.LocalDateTime;\nimport java.util.Map;\nimport java.util.Objects;\n\n/**\n * 短信日志 Service 实现类\n *\n * @author zzf\n */\n@Slf4j\n@Service\npublic class SmsLogServiceImpl implements SmsLogService {\n\n    @Resource\n    private SmsLogMapper smsLogMapper;\n\n    @Override\n    public Long createSmsLog(String mobile, Long userId, Integer userType, Boolean isSend,\n                             SmsTemplateDO template, String templateContent, Map<String, Object> templateParams) {\n        SmsLogDO.SmsLogDOBuilder logBuilder = SmsLogDO.builder();\n        // 根据是否要发送，设置状态\n        logBuilder.sendStatus(Objects.equals(isSend, true) ? SmsSendStatusEnum.INIT.getStatus()\n                : SmsSendStatusEnum.IGNORE.getStatus());\n        // 设置手机相关字段\n        logBuilder.mobile(mobile).userId(userId).userType(userType);\n        // 设置模板相关字段\n        logBuilder.templateId(template.getId()).templateCode(template.getCode()).templateType(template.getType());\n        logBuilder.templateContent(templateContent).templateParams(templateParams)\n                .apiTemplateId(template.getApiTemplateId());\n        // 设置渠道相关字段\n        logBuilder.channelId(template.getChannelId()).channelCode(template.getChannelCode());\n        // 设置接收相关字段\n        logBuilder.receiveStatus(SmsReceiveStatusEnum.INIT.getStatus());\n\n        // 插入数据库\n        SmsLogDO logDO = logBuilder.build();\n        smsLogMapper.insert(logDO);\n        return logDO.getId();\n    }\n\n    @Override\n    public void updateSmsSendResult(Long id, Boolean success,\n                                    String apiSendCode, String apiSendMsg,\n                                    String apiRequestId, String apiSerialNo) {\n        SmsSendStatusEnum sendStatus = success ? SmsSendStatusEnum.SUCCESS : SmsSendStatusEnum.FAILURE;\n        smsLogMapper.updateById(SmsLogDO.builder().id(id)\n                .sendStatus(sendStatus.getStatus()).sendTime(LocalDateTime.now())\n                .apiSendCode(apiSendCode).apiSendMsg(apiSendMsg)\n                .apiRequestId(apiRequestId).apiSerialNo(apiSerialNo).build());\n    }\n\n    @Override\n    public void updateSmsReceiveResult(Long id, Boolean success, LocalDateTime receiveTime,\n                                       String apiReceiveCode, String apiReceiveMsg) {\n        SmsReceiveStatusEnum receiveStatus = Objects.equals(success, true) ?\n                SmsReceiveStatusEnum.SUCCESS : SmsReceiveStatusEnum.FAILURE;\n        smsLogMapper.updateById(SmsLogDO.builder().id(id).receiveStatus(receiveStatus.getStatus())\n                .receiveTime(receiveTime).apiReceiveCode(apiReceiveCode).apiReceiveMsg(apiReceiveMsg).build());\n    }\n\n    @Override\n    public PageResult<SmsLogDO> getSmsLogPage(SmsLogPageReqVO pageReqVO) {\n        return smsLogMapper.selectPage(pageReqVO);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/sms/SmsSendService.java",
    "content": "package co.yixiang.yshop.module.system.service.sms;\n\nimport co.yixiang.yshop.module.system.mq.message.sms.SmsSendMessage;\n\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * 短信发送 Service 接口\n *\n * @author yshop\n */\npublic interface SmsSendService {\n\n    /**\n     * 发送单条短信给管理后台的用户\n     *\n     * 在 mobile 为空时，使用 userId 加载对应管理员的手机号\n     *\n     * @param mobile 手机号\n     * @param userId 用户编号\n     * @param templateCode 短信模板编号\n     * @param templateParams 短信模板参数\n     * @return 发送日志编号\n     */\n    Long sendSingleSmsToAdmin(String mobile, Long userId,\n                              String templateCode, Map<String, Object> templateParams);\n\n    /**\n     * 发送单条短信给用户 APP 的用户\n     *\n     * 在 mobile 为空时，使用 userId 加载对应会员的手机号\n     *\n     * @param mobile 手机号\n     * @param userId 用户编号\n     * @param templateCode 短信模板编号\n     * @param templateParams 短信模板参数\n     * @return 发送日志编号\n     */\n    Long sendSingleSmsToMember(String mobile, Long userId,\n                               String templateCode, Map<String, Object> templateParams);\n\n    /**\n     * 发送单条短信给用户\n     *\n     * @param mobile 手机号\n     * @param userId 用户编号\n     * @param userType 用户类型\n     * @param templateCode 短信模板编号\n     * @param templateParams 短信模板参数\n     * @return 发送日志编号\n     */\n    Long sendSingleSms(String mobile, Long userId, Integer userType,\n                       String templateCode, Map<String, Object> templateParams);\n\n    default void sendBatchSms(List<String> mobiles, List<Long> userIds, Integer userType,\n                              String templateCode, Map<String, Object> templateParams) {\n        throw new UnsupportedOperationException(\"暂时不支持该操作，感兴趣可以实现该功能哟！\");\n    }\n\n    /**\n     * 执行真正的短信发送\n     * 注意，该方法仅仅提供给 MQ Consumer 使用\n     *\n     * @param message 短信\n     */\n    void doSendSms(SmsSendMessage message);\n\n    /**\n     * 接收短信的接收结果\n     *\n     * @param channelCode 渠道编码\n     * @param text 结果内容\n     * @throws Throwable 处理失败时，抛出异常\n     */\n    void receiveSmsStatus(String channelCode, String text) throws Throwable;\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/sms/SmsSendServiceImpl.java",
    "content": "package co.yixiang.yshop.module.system.service.sms;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.exceptions.ExceptionUtil;\nimport cn.hutool.core.lang.Assert;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.core.KeyValue;\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.datapermission.core.annotation.DataPermission;\nimport co.yixiang.yshop.module.system.framework.sms.core.client.SmsClient;\nimport co.yixiang.yshop.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;\nimport co.yixiang.yshop.module.system.framework.sms.core.client.dto.SmsSendRespDTO;\nimport co.yixiang.yshop.module.system.dal.dataobject.sms.SmsChannelDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.sms.SmsTemplateDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.user.AdminUserDO;\nimport co.yixiang.yshop.module.system.mq.message.sms.SmsSendMessage;\nimport co.yixiang.yshop.module.system.mq.producer.sms.SmsProducer;\nimport co.yixiang.yshop.module.system.service.member.MemberService;\nimport co.yixiang.yshop.module.system.service.user.AdminUserService;\nimport com.google.common.annotations.VisibleForTesting;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Service;\n\nimport jakarta.annotation.Resource;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.stream.Collectors;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.*;\n\n/**\n * 短信发送 Service 发送的实现\n *\n * @author yshop\n */\n@Service\n@Slf4j\npublic class SmsSendServiceImpl implements SmsSendService {\n\n    @Resource\n    private AdminUserService adminUserService;\n    @Resource\n    private MemberService memberService;\n    @Resource\n    private SmsChannelService smsChannelService;\n    @Resource\n    private SmsTemplateService smsTemplateService;\n    @Resource\n    private SmsLogService smsLogService;\n\n    @Resource\n    private SmsProducer smsProducer;\n\n    @Override\n    @DataPermission(enable = false) // 发送短信时，无需考虑数据权限\n    public Long sendSingleSmsToAdmin(String mobile, Long userId, String templateCode, Map<String, Object> templateParams) {\n        // 如果 mobile 为空，则加载用户编号对应的手机号\n        if (StrUtil.isEmpty(mobile)) {\n            AdminUserDO user = adminUserService.getUser(userId);\n            if (user != null) {\n                mobile = user.getMobile();\n            }\n        }\n        // 执行发送\n        return sendSingleSms(mobile, userId, UserTypeEnum.ADMIN.getValue(), templateCode, templateParams);\n    }\n\n    @Override\n    public Long sendSingleSmsToMember(String mobile, Long userId, String templateCode, Map<String, Object> templateParams) {\n        // 如果 mobile 为空，则加载用户编号对应的手机号\n        if (StrUtil.isEmpty(mobile)) {\n            mobile = memberService.getMemberUserMobile(userId);\n        }\n        // 执行发送\n        return sendSingleSms(mobile, userId, UserTypeEnum.MEMBER.getValue(), templateCode, templateParams);\n    }\n\n    @Override\n    public Long sendSingleSms(String mobile, Long userId, Integer userType,\n                              String templateCode, Map<String, Object> templateParams) {\n        // 校验短信模板是否合法\n        SmsTemplateDO template = validateSmsTemplate(templateCode);\n        // 校验短信渠道是否合法\n        SmsChannelDO smsChannel = validateSmsChannel(template.getChannelId());\n\n        // 校验手机号码是否存在\n        mobile = validateMobile(mobile);\n        // 构建有序的模板参数。为什么放在这个位置，是提前保证模板参数的正确性，而不是到了插入发送日志\n        List<KeyValue<String, Object>> newTemplateParams = buildTemplateParams(template, templateParams);\n\n        // 创建发送日志。如果模板被禁用，则不发送短信，只记录日志\n        Boolean isSend = CommonStatusEnum.ENABLE.getStatus().equals(template.getStatus())\n                && CommonStatusEnum.ENABLE.getStatus().equals(smsChannel.getStatus());\n        String content = smsTemplateService.formatSmsTemplateContent(template.getContent(), templateParams);\n        Long sendLogId = smsLogService.createSmsLog(mobile, userId, userType, isSend, template, content, templateParams);\n\n        // 发送 MQ 消息，异步执行发送短信\n        if (isSend) {\n            smsProducer.sendSmsSendMessage(sendLogId, mobile, template.getChannelId(),\n                    template.getApiTemplateId(), newTemplateParams);\n        }\n        return sendLogId;\n    }\n\n    @VisibleForTesting\n    SmsChannelDO validateSmsChannel(Long channelId) {\n        // 获得短信模板。考虑到效率，从缓存中获取\n        SmsChannelDO channelDO = smsChannelService.getSmsChannel(channelId);\n        // 短信模板不存在\n        if (channelDO == null) {\n            throw exception(SMS_CHANNEL_NOT_EXISTS);\n        }\n        return channelDO;\n    }\n\n    @VisibleForTesting\n    SmsTemplateDO validateSmsTemplate(String templateCode) {\n        // 获得短信模板。考虑到效率，从缓存中获取\n        SmsTemplateDO template = smsTemplateService.getSmsTemplateByCodeFromCache(templateCode);\n        // 短信模板不存在\n        if (template == null) {\n            throw exception(SMS_SEND_TEMPLATE_NOT_EXISTS);\n        }\n        return template;\n    }\n\n    /**\n     * 将参数模板，处理成有序的 KeyValue 数组\n     * <p>\n     * 原因是，部分短信平台并不是使用 key 作为参数，而是数组下标，例如说 <a href=\"https://cloud.tencent.com/document/product/382/39023\">腾讯云</a>\n     *\n     * @param template       短信模板\n     * @param templateParams 原始参数\n     * @return 处理后的参数\n     */\n    @VisibleForTesting\n    List<KeyValue<String, Object>> buildTemplateParams(SmsTemplateDO template, Map<String, Object> templateParams) {\n        return template.getParams().stream().map(key -> {\n            Object value = templateParams.get(key);\n            if (value == null) {\n                throw exception(SMS_SEND_MOBILE_TEMPLATE_PARAM_MISS, key);\n            }\n            return new KeyValue<>(key, value);\n        }).collect(Collectors.toList());\n    }\n\n    @VisibleForTesting\n    public String validateMobile(String mobile) {\n        if (StrUtil.isEmpty(mobile)) {\n            throw exception(SMS_SEND_MOBILE_NOT_EXISTS);\n        }\n        return mobile;\n    }\n\n    @Override\n    public void doSendSms(SmsSendMessage message) {\n        // 获得渠道对应的 SmsClient 客户端\n        SmsClient smsClient = smsChannelService.getSmsClient(message.getChannelId());\n        Assert.notNull(smsClient, \"短信客户端({}) 不存在\", message.getChannelId());\n        // 发送短信\n        try {\n            SmsSendRespDTO sendResponse = smsClient.sendSms(message.getLogId(), message.getMobile(),\n                    message.getApiTemplateId(), message.getTemplateParams());\n            smsLogService.updateSmsSendResult(message.getLogId(), sendResponse.getSuccess(),\n                    sendResponse.getApiCode(), sendResponse.getApiMsg(),\n                    sendResponse.getApiRequestId(), sendResponse.getSerialNo());\n        } catch (Throwable ex) {\n            log.error(\"[doSendSms][发送短信异常，日志编号({})]\", message.getLogId(), ex);\n            smsLogService.updateSmsSendResult(message.getLogId(), false,\n                    \"EXCEPTION\", ExceptionUtil.getRootCauseMessage(ex), null, null);\n        }\n    }\n\n    @Override\n    public void receiveSmsStatus(String channelCode, String text) throws Throwable {\n        // 获得渠道对应的 SmsClient 客户端\n        SmsClient smsClient = smsChannelService.getSmsClient(channelCode);\n        Assert.notNull(smsClient, \"短信客户端({}) 不存在\", channelCode);\n        // 解析内容\n        List<SmsReceiveRespDTO> receiveResults = smsClient.parseSmsReceiveStatus(text);\n        if (CollUtil.isEmpty(receiveResults)) {\n            return;\n        }\n        // 更新短信日志的接收结果. 因为量一般不大，所以先使用 for 循环更新\n        receiveResults.forEach(result -> smsLogService.updateSmsReceiveResult(result.getLogId(),\n                result.getSuccess(), result.getReceiveTime(), result.getErrorCode(), result.getErrorMsg()));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/sms/SmsTemplateService.java",
    "content": "package co.yixiang.yshop.module.system.service.sms;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.system.controller.admin.sms.vo.template.SmsTemplatePageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.sms.vo.template.SmsTemplateSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.sms.SmsTemplateDO;\n\nimport jakarta.validation.Valid;\nimport java.util.Map;\n\n/**\n * 短信模板 Service 接口\n *\n * @author zzf\n * @since 2021/1/25 9:24\n */\npublic interface SmsTemplateService {\n\n    /**\n     * 创建短信模板\n     *\n     * @param createReqVO 创建信息\n     * @return 编号\n     */\n    Long createSmsTemplate(@Valid SmsTemplateSaveReqVO createReqVO);\n\n    /**\n     * 更新短信模板\n     *\n     * @param updateReqVO 更新信息\n     */\n    void updateSmsTemplate(@Valid SmsTemplateSaveReqVO updateReqVO);\n\n    /**\n     * 删除短信模板\n     *\n     * @param id 编号\n     */\n    void deleteSmsTemplate(Long id);\n\n    /**\n     * 获得短信模板\n     *\n     * @param id 编号\n     * @return 短信模板\n     */\n    SmsTemplateDO getSmsTemplate(Long id);\n\n    /**\n     * 获得短信模板，从缓存中\n     *\n     * @param code 模板编码\n     * @return 短信模板\n     */\n    SmsTemplateDO getSmsTemplateByCodeFromCache(String code);\n\n    /**\n     * 获得短信模板分页\n     *\n     * @param pageReqVO 分页查询\n     * @return 短信模板分页\n     */\n    PageResult<SmsTemplateDO> getSmsTemplatePage(SmsTemplatePageReqVO pageReqVO);\n\n    /**\n     * 获得指定短信渠道下的短信模板数量\n     *\n     * @param channelId 短信渠道编号\n     * @return 数量\n     */\n    Long getSmsTemplateCountByChannelId(Long channelId);\n\n    /**\n     * 格式化短信内容\n     *\n     * @param content 短信模板的内容\n     * @param params 内容的参数\n     * @return 格式化后的内容\n     */\n    String formatSmsTemplateContent(String content, Map<String, Object> params);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/sms/SmsTemplateServiceImpl.java",
    "content": "package co.yixiang.yshop.module.system.service.sms;\n\nimport cn.hutool.core.exceptions.ExceptionUtil;\nimport cn.hutool.core.lang.Assert;\nimport cn.hutool.core.util.ReUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.framework.sms.core.client.SmsClient;\nimport co.yixiang.yshop.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO;\nimport co.yixiang.yshop.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;\nimport co.yixiang.yshop.module.system.controller.admin.sms.vo.template.SmsTemplatePageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.sms.vo.template.SmsTemplateSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.sms.SmsChannelDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.sms.SmsTemplateDO;\nimport co.yixiang.yshop.module.system.dal.mysql.sms.SmsTemplateMapper;\nimport co.yixiang.yshop.module.system.dal.redis.RedisKeyConstants;\nimport com.google.common.annotations.VisibleForTesting;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.cache.annotation.CacheEvict;\nimport org.springframework.cache.annotation.Cacheable;\nimport org.springframework.stereotype.Service;\n\nimport jakarta.annotation.Resource;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Objects;\nimport java.util.regex.Pattern;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.*;\n\n/**\n * 短信模板 Service 实现类\n *\n * @author zzf\n * @since 2021/1/25 9:25\n */\n@Service\n@Slf4j\npublic class SmsTemplateServiceImpl implements SmsTemplateService {\n\n    /**\n     * 正则表达式，匹配 {} 中的变量\n     */\n    private static final Pattern PATTERN_PARAMS = Pattern.compile(\"\\\\{(.*?)}\");\n\n    @Resource\n    private SmsTemplateMapper smsTemplateMapper;\n\n    @Resource\n    private SmsChannelService smsChannelService;\n\n    @Override\n    public Long createSmsTemplate(SmsTemplateSaveReqVO createReqVO) {\n        // 校验短信渠道\n        SmsChannelDO channelDO = validateSmsChannel(createReqVO.getChannelId());\n        // 校验短信编码是否重复\n        validateSmsTemplateCodeDuplicate(null, createReqVO.getCode());\n        // 校验短信模板\n        validateApiTemplate(createReqVO.getChannelId(), createReqVO.getApiTemplateId());\n\n        // 插入\n        SmsTemplateDO template = BeanUtils.toBean(createReqVO, SmsTemplateDO.class);\n        template.setParams(parseTemplateContentParams(template.getContent()));\n        template.setChannelCode(channelDO.getCode());\n        smsTemplateMapper.insert(template);\n        // 返回\n        return template.getId();\n    }\n\n    @Override\n    @CacheEvict(cacheNames = RedisKeyConstants.SMS_TEMPLATE,\n            allEntries = true) // allEntries 清空所有缓存，因为可能修改到 code 字段，不好清理\n    public void updateSmsTemplate(SmsTemplateSaveReqVO updateReqVO) {\n        // 校验存在\n        validateSmsTemplateExists(updateReqVO.getId());\n        // 校验短信渠道\n        SmsChannelDO channelDO = validateSmsChannel(updateReqVO.getChannelId());\n        // 校验短信编码是否重复\n        validateSmsTemplateCodeDuplicate(updateReqVO.getId(), updateReqVO.getCode());\n        // 校验短信模板\n        validateApiTemplate(updateReqVO.getChannelId(), updateReqVO.getApiTemplateId());\n\n        // 更新\n        SmsTemplateDO updateObj = BeanUtils.toBean(updateReqVO, SmsTemplateDO.class);\n        updateObj.setParams(parseTemplateContentParams(updateObj.getContent()));\n        updateObj.setChannelCode(channelDO.getCode());\n        smsTemplateMapper.updateById(updateObj);\n    }\n\n    @Override\n    @CacheEvict(cacheNames = RedisKeyConstants.SMS_TEMPLATE,\n            allEntries = true) // allEntries 清空所有缓存，因为 id 不是直接的缓存 code，不好清理\n    public void deleteSmsTemplate(Long id) {\n        // 校验存在\n        validateSmsTemplateExists(id);\n        // 更新\n        smsTemplateMapper.deleteById(id);\n    }\n\n    private void validateSmsTemplateExists(Long id) {\n        if (smsTemplateMapper.selectById(id) == null) {\n            throw exception(SMS_TEMPLATE_NOT_EXISTS);\n        }\n    }\n\n    @Override\n    public SmsTemplateDO getSmsTemplate(Long id) {\n        return smsTemplateMapper.selectById(id);\n    }\n\n    @Override\n    @Cacheable(cacheNames = RedisKeyConstants.SMS_TEMPLATE, key = \"#code\",\n            unless = \"#result == null\")\n    public SmsTemplateDO getSmsTemplateByCodeFromCache(String code) {\n        return smsTemplateMapper.selectByCode(code);\n    }\n\n    @Override\n    public PageResult<SmsTemplateDO> getSmsTemplatePage(SmsTemplatePageReqVO pageReqVO) {\n        return smsTemplateMapper.selectPage(pageReqVO);\n    }\n\n    @Override\n    public Long getSmsTemplateCountByChannelId(Long channelId) {\n        return smsTemplateMapper.selectCountByChannelId(channelId);\n    }\n\n    @VisibleForTesting\n    public SmsChannelDO validateSmsChannel(Long channelId) {\n        SmsChannelDO channelDO = smsChannelService.getSmsChannel(channelId);\n        if (channelDO == null) {\n            throw exception(SMS_CHANNEL_NOT_EXISTS);\n        }\n        if (CommonStatusEnum.isDisable(channelDO.getStatus())) {\n            throw exception(SMS_CHANNEL_DISABLE);\n        }\n        return channelDO;\n    }\n\n    @VisibleForTesting\n    public void validateSmsTemplateCodeDuplicate(Long id, String code) {\n        SmsTemplateDO template = smsTemplateMapper.selectByCode(code);\n        if (template == null) {\n            return;\n        }\n        // 如果 id 为空，说明不用比较是否为相同 id 的字典类型\n        if (id == null) {\n            throw exception(SMS_TEMPLATE_CODE_DUPLICATE, code);\n        }\n        if (!template.getId().equals(id)) {\n            throw exception(SMS_TEMPLATE_CODE_DUPLICATE, code);\n        }\n    }\n\n    /**\n     * 校验 API 短信平台的模板是否有效\n     *\n     * @param channelId 渠道编号\n     * @param apiTemplateId API 模板编号\n     */\n    @VisibleForTesting\n    void validateApiTemplate(Long channelId, String apiTemplateId) {\n        // 获得短信模板\n        SmsClient smsClient = smsChannelService.getSmsClient(channelId);\n        Assert.notNull(smsClient, String.format(\"短信客户端(%d) 不存在\", channelId));\n        SmsTemplateRespDTO template;\n        try {\n            template = smsClient.getSmsTemplate(apiTemplateId);\n        } catch (Throwable ex) {\n            throw exception(SMS_TEMPLATE_API_ERROR, ExceptionUtil.getRootCauseMessage(ex));\n        }\n        // 校验短信模版\n        if (template == null) {\n            throw exception(SMS_TEMPLATE_API_NOT_FOUND);\n        }\n        if (Objects.equals(template.getAuditStatus(), SmsTemplateAuditStatusEnum.CHECKING.getStatus())) {\n            throw exception(SMS_TEMPLATE_API_AUDIT_CHECKING);\n        }\n        if (Objects.equals(template.getAuditStatus(), SmsTemplateAuditStatusEnum.FAIL.getStatus())) {\n            throw exception(SMS_TEMPLATE_API_AUDIT_FAIL, template.getAuditReason());\n        }\n        Assert.equals(template.getAuditStatus(), SmsTemplateAuditStatusEnum.SUCCESS.getStatus(),\n                String.format(\"短信模板(%s) 审核状态(%d) 不正确\", apiTemplateId, template.getAuditStatus()));\n    }\n\n    @Override\n    public String formatSmsTemplateContent(String content, Map<String, Object> params) {\n        return StrUtil.format(content, params);\n    }\n\n    @VisibleForTesting\n    List<String> parseTemplateContentParams(String content) {\n        return ReUtil.findAllGroup1(PATTERN_PARAMS, content);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/social/SocialClientService.java",
    "content": "package co.yixiang.yshop.module.system.service.social;\n\nimport cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.system.controller.admin.socail.vo.client.SocialClientPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.socail.vo.client.SocialClientSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.social.SocialClientDO;\nimport co.yixiang.yshop.module.system.enums.social.SocialTypeEnum;\nimport com.xingyuv.jushauth.model.AuthUser;\nimport me.chanjar.weixin.common.bean.WxJsapiSignature;\n\nimport jakarta.validation.Valid;\n\n/**\n * 社交应用 Service 接口\n *\n * @author yshop\n */\npublic interface SocialClientService {\n\n    /**\n     * 获得社交平台的授权 URL\n     *\n     * @param socialType 社交平台的类型 {@link SocialTypeEnum}\n     * @param userType 用户类型\n     * @param redirectUri 重定向 URL\n     * @return 社交平台的授权 URL\n     */\n    String getAuthorizeUrl(Integer socialType, Integer userType, String redirectUri);\n\n    /**\n     * 请求社交平台，获得授权的用户\n     *\n     * @param socialType 社交平台的类型\n     * @param userType 用户类型\n     * @param code 授权码\n     * @param state 授权 state\n     * @return 授权的用户\n     */\n    AuthUser getAuthUser(Integer socialType, Integer userType, String code, String state);\n\n    // =================== 微信公众号独有 ===================\n\n    /**\n     * 创建微信公众号的 JS SDK 初始化所需的签名\n     *\n     * @param userType 用户类型\n     * @param url 访问的 URL 地址\n     * @return 签名\n     */\n    WxJsapiSignature createWxMpJsapiSignature(Integer userType, String url);\n\n    // =================== 微信小程序独有 ===================\n\n    /**\n     * 获得微信小程序的手机信息\n     *\n     * @param userType 用户类型\n     * @param phoneCode 手机授权码\n     * @return 手机信息\n     */\n    WxMaPhoneNumberInfo getWxMaPhoneNumberInfo(Integer userType, String phoneCode);\n\n    // =================== 客户端管理 ===================\n\n    /**\n     * 创建社交客户端\n     *\n     * @param createReqVO 创建信息\n     * @return 编号\n     */\n    Long createSocialClient(@Valid SocialClientSaveReqVO createReqVO);\n\n    /**\n     * 更新社交客户端\n     *\n     * @param updateReqVO 更新信息\n     */\n    void updateSocialClient(@Valid SocialClientSaveReqVO updateReqVO);\n\n    /**\n     * 删除社交客户端\n     *\n     * @param id 编号\n     */\n    void deleteSocialClient(Long id);\n\n    /**\n     * 获得社交客户端\n     *\n     * @param id 编号\n     * @return 社交客户端\n     */\n    SocialClientDO getSocialClient(Long id);\n\n    /**\n     * 获得社交客户端分页\n     *\n     * @param pageReqVO 分页查询\n     * @return 社交客户端分页\n     */\n    PageResult<SocialClientDO> getSocialClientPage(SocialClientPageReqVO pageReqVO);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/social/SocialClientServiceImpl.java",
    "content": "package co.yixiang.yshop.module.system.service.social;\n\nimport cn.binarywang.wx.miniapp.api.WxMaService;\nimport cn.binarywang.wx.miniapp.api.impl.WxMaServiceImpl;\nimport cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;\nimport cn.binarywang.wx.miniapp.config.impl.WxMaRedisBetterConfigImpl;\nimport cn.hutool.core.bean.BeanUtil;\nimport cn.hutool.core.lang.Assert;\nimport cn.hutool.core.util.ObjUtil;\nimport cn.hutool.core.util.ReflectUtil;\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.cache.CacheUtils;\nimport co.yixiang.yshop.framework.common.util.http.HttpUtils;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.controller.admin.socail.vo.client.SocialClientPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.socail.vo.client.SocialClientSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.social.SocialClientDO;\nimport co.yixiang.yshop.module.system.dal.mysql.social.SocialClientMapper;\nimport co.yixiang.yshop.module.system.enums.social.SocialTypeEnum;\nimport com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaProperties;\nimport com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.google.common.cache.CacheLoader;\nimport com.google.common.cache.LoadingCache;\nimport com.xingyuv.jushauth.config.AuthConfig;\nimport com.xingyuv.jushauth.model.AuthCallback;\nimport com.xingyuv.jushauth.model.AuthResponse;\nimport com.xingyuv.jushauth.model.AuthUser;\nimport com.xingyuv.jushauth.request.AuthRequest;\nimport com.xingyuv.jushauth.utils.AuthStateUtils;\nimport com.xingyuv.justauth.AuthRequestFactory;\nimport jakarta.annotation.Resource;\nimport lombok.SneakyThrows;\nimport lombok.extern.slf4j.Slf4j;\nimport me.chanjar.weixin.common.bean.WxJsapiSignature;\nimport me.chanjar.weixin.common.error.WxErrorException;\nimport me.chanjar.weixin.common.redis.RedisTemplateWxRedisOps;\nimport me.chanjar.weixin.mp.api.WxMpService;\nimport me.chanjar.weixin.mp.api.impl.WxMpServiceImpl;\nimport me.chanjar.weixin.mp.config.impl.WxMpRedisConfigImpl;\nimport org.springframework.data.redis.core.StringRedisTemplate;\nimport org.springframework.stereotype.Service;\n\nimport java.time.Duration;\nimport java.util.Objects;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.framework.common.util.json.JsonUtils.toJsonString;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.*;\n\n/**\n * 社交应用 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Slf4j\npublic class SocialClientServiceImpl implements SocialClientService {\n\n    @Resource\n    private AuthRequestFactory authRequestFactory;\n\n    @Resource\n    private WxMpService wxMpService;\n    @Resource\n    private WxMpProperties wxMpProperties;\n    @Resource\n    private StringRedisTemplate stringRedisTemplate; // WxMpService 需要使用到，所以在 Service 注入了它\n    /**\n     * 缓存 WxMpService 对象\n     *\n     * key：使用微信公众号的 appId + secret 拼接，即 {@link SocialClientDO} 的 clientId 和 clientSecret 属性。\n     * 为什么 key 使用这种格式？因为 {@link SocialClientDO} 在管理后台可以变更，通过这个 key 存储它的单例。\n     *\n     * 为什么要做 WxMpService 缓存？因为 WxMpService 构建成本比较大，所以尽量保证它是单例。\n     */\n    private final LoadingCache<String, WxMpService> wxMpServiceCache = CacheUtils.buildAsyncReloadingCache(\n            Duration.ofSeconds(10L),\n            new CacheLoader<String, WxMpService>() {\n\n                @Override\n                public WxMpService load(String key) {\n                    String[] keys = key.split(\":\");\n                    return buildWxMpService(keys[0], keys[1]);\n                }\n\n            });\n\n    @Resource\n    private WxMaService wxMaService;\n    @Resource\n    private WxMaProperties wxMaProperties;\n    /**\n     * 缓存 WxMaService 对象\n     *\n     * 说明同 {@link #wxMpServiceCache} 变量\n     */\n    private final LoadingCache<String, WxMaService> wxMaServiceCache = CacheUtils.buildAsyncReloadingCache(\n            Duration.ofSeconds(10L),\n            new CacheLoader<String, WxMaService>() {\n\n                @Override\n                public WxMaService load(String key) {\n                    String[] keys = key.split(\":\");\n                    return buildWxMaService(keys[0], keys[1]);\n                }\n\n            });\n\n    @Resource\n    private SocialClientMapper socialClientMapper;\n\n    @Override\n    public String getAuthorizeUrl(Integer socialType, Integer userType, String redirectUri) {\n        // 获得对应的 AuthRequest 实现\n        AuthRequest authRequest = buildAuthRequest(socialType, userType);\n        // 生成跳转地址\n        String authorizeUri = authRequest.authorize(AuthStateUtils.createState());\n        return HttpUtils.replaceUrlQuery(authorizeUri, \"redirect_uri\", redirectUri);\n    }\n\n    @Override\n    public AuthUser getAuthUser(Integer socialType, Integer userType, String code, String state) {\n        // 构建请求\n        AuthRequest authRequest = buildAuthRequest(socialType, userType);\n        AuthCallback authCallback = AuthCallback.builder().code(code).state(state).build();\n        // 执行请求\n        AuthResponse<?> authResponse = authRequest.login(authCallback);\n        log.info(\"[getAuthUser][请求社交平台 type({}) request({}) response({})]\", socialType,\n                toJsonString(authCallback), toJsonString(authResponse));\n        if (!authResponse.ok()) {\n            throw exception(SOCIAL_USER_AUTH_FAILURE, authResponse.getMsg());\n        }\n        return (AuthUser) authResponse.getData();\n    }\n\n    /**\n     * 构建 AuthRequest 对象，支持多租户配置\n     *\n     * @param socialType 社交类型\n     * @param userType 用户类型\n     * @return AuthRequest 对象\n     */\n    @VisibleForTesting\n    AuthRequest buildAuthRequest(Integer socialType, Integer userType) {\n        // 1. 先查找默认的配置项，从 application-*.yaml 中读取\n        AuthRequest request = authRequestFactory.get(SocialTypeEnum.valueOfType(socialType).getSource());\n        Assert.notNull(request, String.format(\"社交平台(%d) 不存在\", socialType));\n        // 2. 查询 DB 的配置项，如果存在则进行覆盖\n        SocialClientDO client = socialClientMapper.selectBySocialTypeAndUserType(socialType, userType);\n        if (client != null && Objects.equals(client.getStatus(), CommonStatusEnum.ENABLE.getStatus())) {\n            // 2.1 构造新的 AuthConfig 对象\n            AuthConfig authConfig = (AuthConfig) ReflectUtil.getFieldValue(request, \"config\");\n            AuthConfig newAuthConfig = ReflectUtil.newInstance(authConfig.getClass());\n            BeanUtil.copyProperties(authConfig, newAuthConfig);\n            // 2.2 修改对应的 clientId + clientSecret 密钥\n            newAuthConfig.setClientId(client.getClientId());\n            newAuthConfig.setClientSecret(client.getClientSecret());\n            if (client.getAgentId() != null) { // 如果有 agentId 则修改 agentId\n                newAuthConfig.setAgentId(client.getAgentId());\n            }\n            // 2.3 设置会 request 里，进行后续使用\n            ReflectUtil.setFieldValue(request, \"config\", newAuthConfig);\n        }\n        return request;\n    }\n\n    // =================== 微信公众号独有 ===================\n\n    @Override\n    @SneakyThrows\n    public WxJsapiSignature createWxMpJsapiSignature(Integer userType, String url) {\n        WxMpService service = getWxMpService(userType);\n        return service.createJsapiSignature(url);\n    }\n\n    /**\n     * 获得 clientId + clientSecret 对应的 WxMpService 对象\n     *\n     * @param userType 用户类型\n     * @return WxMpService 对象\n     */\n    @VisibleForTesting\n    WxMpService getWxMpService(Integer userType) {\n        // 第一步，查询 DB 的配置项，获得对应的 WxMpService 对象\n        SocialClientDO client = socialClientMapper.selectBySocialTypeAndUserType(\n                SocialTypeEnum.WECHAT_MP.getType(), userType);\n        if (client != null && Objects.equals(client.getStatus(), CommonStatusEnum.ENABLE.getStatus())) {\n            return wxMpServiceCache.getUnchecked(client.getClientId() + \":\" + client.getClientSecret());\n        }\n        // 第二步，不存在 DB 配置项，则使用 application-*.yaml 对应的 WxMpService 对象\n        return wxMpService;\n    }\n\n    /**\n     * 创建 clientId + clientSecret 对应的 WxMpService 对象\n     *\n     * @param clientId 微信公众号 appId\n     * @param clientSecret 微信公众号 secret\n     * @return WxMpService 对象\n     */\n    public WxMpService buildWxMpService(String clientId, String clientSecret) {\n        // 第一步，创建 WxMpRedisConfigImpl 对象\n        WxMpRedisConfigImpl configStorage = new WxMpRedisConfigImpl(\n                new RedisTemplateWxRedisOps(stringRedisTemplate),\n                wxMpProperties.getConfigStorage().getKeyPrefix());\n        configStorage.setAppId(clientId);\n        configStorage.setSecret(clientSecret);\n\n        // 第二步，创建 WxMpService 对象\n        WxMpService service = new WxMpServiceImpl();\n        service.setWxMpConfigStorage(configStorage);\n        return service;\n    }\n\n    // =================== 微信小程序独有 ===================\n\n    @Override\n    public WxMaPhoneNumberInfo getWxMaPhoneNumberInfo(Integer userType, String phoneCode) {\n        WxMaService service = getWxMaService(userType);\n        try {\n            return service.getUserService().getPhoneNoInfo(phoneCode);\n        } catch (WxErrorException e) {\n            log.error(\"[getPhoneNoInfo][userType({}) phoneCode({}) 获得手机号失败]\", userType, phoneCode, e);\n            throw exception(SOCIAL_CLIENT_WEIXIN_MINI_APP_PHONE_CODE_ERROR);\n        }\n    }\n\n    /**\n     * 获得 clientId + clientSecret 对应的 WxMpService 对象\n     *\n     * @param userType 用户类型\n     * @return WxMpService 对象\n     */\n    @VisibleForTesting\n    WxMaService getWxMaService(Integer userType) {\n        // 第一步，查询 DB 的配置项，获得对应的 WxMaService 对象\n        SocialClientDO client = socialClientMapper.selectBySocialTypeAndUserType(\n                SocialTypeEnum.WECHAT_MINI_APP.getType(), userType);\n        if (client != null && Objects.equals(client.getStatus(), CommonStatusEnum.ENABLE.getStatus())) {\n            return wxMaServiceCache.getUnchecked(client.getClientId() + \":\" + client.getClientSecret());\n        }\n        // 第二步，不存在 DB 配置项，则使用 application-*.yaml 对应的 WxMaService 对象\n        return wxMaService;\n    }\n\n    /**\n     * 创建 clientId + clientSecret 对应的 WxMaService 对象\n     *\n     * @param clientId 微信小程序 appId\n     * @param clientSecret 微信小程序 secret\n     * @return WxMaService 对象\n     */\n    private WxMaService buildWxMaService(String clientId, String clientSecret) {\n        // 第一步，创建 WxMaRedisBetterConfigImpl 对象\n        WxMaRedisBetterConfigImpl configStorage = new WxMaRedisBetterConfigImpl(\n                new RedisTemplateWxRedisOps(stringRedisTemplate),\n                wxMaProperties.getConfigStorage().getKeyPrefix());\n        configStorage.setAppid(clientId);\n        configStorage.setSecret(clientSecret);\n\n        // 第二步，创建 WxMpService 对象\n        WxMaService service = new WxMaServiceImpl();\n        service.setWxMaConfig(configStorage);\n        return service;\n    }\n\n    // =================== 客户端管理 ===================\n\n    @Override\n    public Long createSocialClient(SocialClientSaveReqVO createReqVO) {\n        // 校验重复\n        validateSocialClientUnique(null, createReqVO.getUserType(), createReqVO.getSocialType());\n\n        // 插入\n        SocialClientDO client = BeanUtils.toBean(createReqVO, SocialClientDO.class);\n        socialClientMapper.insert(client);\n        return client.getId();\n    }\n\n    @Override\n    public void updateSocialClient(SocialClientSaveReqVO updateReqVO) {\n        // 校验存在\n        validateSocialClientExists(updateReqVO.getId());\n        // 校验重复\n        validateSocialClientUnique(updateReqVO.getId(), updateReqVO.getUserType(), updateReqVO.getSocialType());\n\n        // 更新\n        SocialClientDO updateObj = BeanUtils.toBean(updateReqVO, SocialClientDO.class);\n        socialClientMapper.updateById(updateObj);\n    }\n\n    @Override\n    public void deleteSocialClient(Long id) {\n        // 校验存在\n        validateSocialClientExists(id);\n        // 删除\n        socialClientMapper.deleteById(id);\n    }\n\n    private void validateSocialClientExists(Long id) {\n        if (socialClientMapper.selectById(id) == null) {\n            throw exception(SOCIAL_CLIENT_NOT_EXISTS);\n        }\n    }\n\n    /**\n     * 校验社交应用是否重复，需要保证 userType + socialType 唯一\n     *\n     * 原因是，不同端（userType）选择某个社交登录（socialType）时，需要通过 {@link #buildAuthRequest(Integer, Integer)} 构建对应的请求\n     *\n     * @param id 编号\n     * @param userType 用户类型\n     * @param socialType 社交类型\n     */\n    private void validateSocialClientUnique(Long id, Integer userType, Integer socialType) {\n        SocialClientDO client = socialClientMapper.selectBySocialTypeAndUserType(\n                socialType, userType);\n        if (client == null) {\n            return;\n        }\n        if (id == null // 新增时，说明重复\n                || ObjUtil.notEqual(id, client.getId())) { // 更新时，如果 id 不一致，说明重复\n            throw exception(SOCIAL_CLIENT_UNIQUE);\n        }\n    }\n\n    @Override\n    public SocialClientDO getSocialClient(Long id) {\n        return socialClientMapper.selectById(id);\n    }\n\n    @Override\n    public PageResult<SocialClientDO> getSocialClientPage(SocialClientPageReqVO pageReqVO) {\n        return socialClientMapper.selectPage(pageReqVO);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/social/SocialUserService.java",
    "content": "package co.yixiang.yshop.module.system.service.social;\n\nimport co.yixiang.yshop.framework.common.exception.ServiceException;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.system.api.social.dto.SocialUserBindReqDTO;\nimport co.yixiang.yshop.module.system.api.social.dto.SocialUserRespDTO;\nimport co.yixiang.yshop.module.system.controller.admin.socail.vo.user.SocialUserPageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.social.SocialUserDO;\nimport co.yixiang.yshop.module.system.enums.social.SocialTypeEnum;\n\nimport jakarta.validation.Valid;\nimport java.util.List;\n\n/**\n * 社交用户 Service 接口，例如说社交平台的授权登录\n *\n * @author yshop\n */\npublic interface SocialUserService {\n\n    /**\n     * 获得指定用户的社交用户列表\n     *\n     * @param userId   用户编号\n     * @param userType 用户类型\n     * @return 社交用户列表\n     */\n    List<SocialUserDO> getSocialUserList(Long userId, Integer userType);\n\n    /**\n     * 绑定社交用户\n     *\n     * @param reqDTO 绑定信息\n     * @return 社交用户 openid\n     */\n    String bindSocialUser(@Valid SocialUserBindReqDTO reqDTO);\n\n    /**\n     * 取消绑定社交用户\n     *\n     * @param userId 用户编号\n     * @param userType 全局用户类型\n     * @param socialType 社交平台的类型 {@link SocialTypeEnum}\n     * @param openid 社交平台的 openid\n     */\n    void unbindSocialUser(Long userId, Integer userType, Integer socialType, String openid);\n\n    /**\n     * 获得社交用户，基于 userId\n     *\n     * @param userType 用户类型\n     * @param userId 用户编号\n     * @param socialType 社交平台的类型\n     * @return 社交用户\n     */\n    SocialUserRespDTO getSocialUserByUserId(Integer userType, Long userId, Integer socialType);\n\n    /**\n     * 获得社交用户\n     *\n     * 在认证信息不正确的情况下，也会抛出 {@link ServiceException} 业务异常\n     *\n     * @param userType 用户类型\n     * @param socialType 社交平台的类型\n     * @param code 授权码\n     * @param state state\n     * @return 社交用户\n     */\n    SocialUserRespDTO getSocialUserByCode(Integer userType, Integer socialType, String code, String state);\n\n    // ==================== 社交用户 CRUD ====================\n\n    /**\n     * 获得社交用户\n     *\n     * @param id 编号\n     * @return 社交用户\n     */\n    SocialUserDO getSocialUser(Long id);\n\n    /**\n     * 获得社交用户分页\n     *\n     * @param pageReqVO 分页查询\n     * @return 社交用户分页\n     */\n    PageResult<SocialUserDO> getSocialUserPage(SocialUserPageReqVO pageReqVO);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/social/SocialUserServiceImpl.java",
    "content": "package co.yixiang.yshop.module.system.service.social;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.lang.Assert;\nimport co.yixiang.yshop.framework.common.exception.ServiceException;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.system.api.social.dto.SocialUserBindReqDTO;\nimport co.yixiang.yshop.module.system.api.social.dto.SocialUserRespDTO;\nimport co.yixiang.yshop.module.system.controller.admin.socail.vo.user.SocialUserPageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.social.SocialUserBindDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.social.SocialUserDO;\nimport co.yixiang.yshop.module.system.dal.mysql.social.SocialUserBindMapper;\nimport co.yixiang.yshop.module.system.dal.mysql.social.SocialUserMapper;\nimport co.yixiang.yshop.module.system.enums.social.SocialTypeEnum;\nimport com.xingyuv.jushauth.model.AuthUser;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\nimport jakarta.validation.constraints.NotNull;\nimport java.util.Collections;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.framework.common.util.collection.CollectionUtils.convertSet;\nimport static co.yixiang.yshop.framework.common.util.json.JsonUtils.toJsonString;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.SOCIAL_USER_NOT_FOUND;\n\n/**\n * 社交用户 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\n@Slf4j\npublic class SocialUserServiceImpl implements SocialUserService {\n\n    @Resource\n    private SocialUserBindMapper socialUserBindMapper;\n    @Resource\n    private SocialUserMapper socialUserMapper;\n\n    @Resource\n    private SocialClientService socialClientService;\n\n    @Override\n    public List<SocialUserDO> getSocialUserList(Long userId, Integer userType) {\n        // 获得绑定\n        List<SocialUserBindDO> socialUserBinds = socialUserBindMapper.selectListByUserIdAndUserType(userId, userType);\n        if (CollUtil.isEmpty(socialUserBinds)) {\n            return Collections.emptyList();\n        }\n        // 获得社交用户\n        return socialUserMapper.selectBatchIds(convertSet(socialUserBinds, SocialUserBindDO::getSocialUserId));\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    public String bindSocialUser(SocialUserBindReqDTO reqDTO) {\n        // 获得社交用户\n        SocialUserDO socialUser = authSocialUser(reqDTO.getSocialType(), reqDTO.getUserType(),\n                reqDTO.getCode(), reqDTO.getState());\n        Assert.notNull(socialUser, \"社交用户不能为空\");\n\n        // 社交用户可能之前绑定过别的用户，需要进行解绑\n        socialUserBindMapper.deleteByUserTypeAndSocialUserId(reqDTO.getUserType(), socialUser.getId());\n\n        // 用户可能之前已经绑定过该社交类型，需要进行解绑\n        socialUserBindMapper.deleteByUserTypeAndUserIdAndSocialType(reqDTO.getUserType(), reqDTO.getUserId(),\n                socialUser.getType());\n\n        // 绑定当前登录的社交用户\n        SocialUserBindDO socialUserBind = SocialUserBindDO.builder()\n                .userId(reqDTO.getUserId()).userType(reqDTO.getUserType())\n                .socialUserId(socialUser.getId()).socialType(socialUser.getType()).build();\n        socialUserBindMapper.insert(socialUserBind);\n        return socialUser.getOpenid();\n    }\n\n    @Override\n    public void unbindSocialUser(Long userId, Integer userType, Integer socialType, String openid) {\n        // 获得 openid 对应的 SocialUserDO 社交用户\n        SocialUserDO socialUser = socialUserMapper.selectByTypeAndOpenid(socialType, openid);\n        if (socialUser == null) {\n            throw exception(SOCIAL_USER_NOT_FOUND);\n        }\n\n        // 获得对应的社交绑定关系\n        socialUserBindMapper.deleteByUserTypeAndUserIdAndSocialType(userType, userId, socialUser.getType());\n    }\n\n    @Override\n    public SocialUserRespDTO getSocialUserByUserId(Integer userType, Long userId, Integer socialType) {\n        // 获得绑定用户\n        SocialUserBindDO socialUserBind = socialUserBindMapper.selectByUserIdAndUserTypeAndSocialType(userId, userType, socialType);\n        if (socialUserBind == null) {\n            return null;\n        }\n        // 获得社交用户\n        SocialUserDO socialUser = socialUserMapper.selectById(socialUserBind.getSocialUserId());\n        Assert.notNull(socialUser, \"社交用户不能为空\");\n        return new SocialUserRespDTO(socialUser.getOpenid(), socialUser.getNickname(), socialUser.getAvatar(),\n                socialUserBind.getUserId());\n    }\n\n    @Override\n    public SocialUserRespDTO getSocialUserByCode(Integer userType, Integer socialType, String code, String state) {\n        // 获得社交用户\n        SocialUserDO socialUser = authSocialUser(socialType, userType, code, state);\n        Assert.notNull(socialUser, \"社交用户不能为空\");\n\n        // 获得绑定用户\n        SocialUserBindDO socialUserBind = socialUserBindMapper.selectByUserTypeAndSocialUserId(userType,\n                socialUser.getId());\n        return new SocialUserRespDTO(socialUser.getOpenid(), socialUser.getNickname(), socialUser.getAvatar(),\n                socialUserBind != null ? socialUserBind.getUserId() : null);\n    }\n\n    /**\n     * 授权获得对应的社交用户\n     * 如果授权失败，则会抛出 {@link ServiceException} 异常\n     *\n     * @param socialType 社交平台的类型 {@link SocialTypeEnum}\n     * @param userType 用户类型\n     * @param code     授权码\n     * @param state    state\n     * @return 授权用户\n     */\n    @NotNull\n    public SocialUserDO authSocialUser(Integer socialType, Integer userType, String code, String state) {\n        // 优先从 DB 中获取，因为 code 有且可以使用一次。\n        // 在社交登录时，当未绑定 User 时，需要绑定登录，此时需要 code 使用两次\n        SocialUserDO socialUser = socialUserMapper.selectByTypeAndCodeAnState(socialType, code, state);\n        if (socialUser != null) {\n            return socialUser;\n        }\n\n        // 请求获取\n        AuthUser authUser = socialClientService.getAuthUser(socialType, userType, code, state);\n        Assert.notNull(authUser, \"三方用户不能为空\");\n\n        // 保存到 DB 中\n        socialUser = socialUserMapper.selectByTypeAndOpenid(socialType, authUser.getUuid());\n        if (socialUser == null) {\n            socialUser = new SocialUserDO();\n        }\n        socialUser.setType(socialType).setCode(code).setState(state) // 需要保存 code + state 字段，保证后续可查询\n                .setOpenid(authUser.getUuid()).setToken(authUser.getToken().getAccessToken()).setRawTokenInfo((toJsonString(authUser.getToken())))\n                .setNickname(authUser.getNickname()).setAvatar(authUser.getAvatar()).setRawUserInfo(toJsonString(authUser.getRawUserInfo()));\n        if (socialUser.getId() == null) {\n            socialUserMapper.insert(socialUser);\n        } else {\n            socialUserMapper.updateById(socialUser);\n        }\n        return socialUser;\n    }\n\n    // ==================== 社交用户 CRUD ====================\n\n    @Override\n    public SocialUserDO getSocialUser(Long id) {\n        return socialUserMapper.selectById(id);\n    }\n\n    @Override\n    public PageResult<SocialUserDO> getSocialUserPage(SocialUserPageReqVO pageReqVO) {\n        return socialUserMapper.selectPage(pageReqVO);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/tenant/TenantPackageService.java",
    "content": "package co.yixiang.yshop.module.system.service.tenant;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.system.controller.admin.tenant.vo.packages.TenantPackagePageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.tenant.vo.packages.TenantPackageSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.tenant.TenantPackageDO;\n\nimport jakarta.validation.Valid;\nimport java.util.List;\n\n/**\n * 租户套餐 Service 接口\n *\n * @author yshop\n */\npublic interface TenantPackageService {\n\n    /**\n     * 创建租户套餐\n     *\n     * @param createReqVO 创建信息\n     * @return 编号\n     */\n    Long createTenantPackage(@Valid TenantPackageSaveReqVO createReqVO);\n\n    /**\n     * 更新租户套餐\n     *\n     * @param updateReqVO 更新信息\n     */\n    void updateTenantPackage(@Valid TenantPackageSaveReqVO updateReqVO);\n\n    /**\n     * 删除租户套餐\n     *\n     * @param id 编号\n     */\n    void deleteTenantPackage(Long id);\n\n    /**\n     * 获得租户套餐\n     *\n     * @param id 编号\n     * @return 租户套餐\n     */\n    TenantPackageDO getTenantPackage(Long id);\n\n    /**\n     * 获得租户套餐分页\n     *\n     * @param pageReqVO 分页查询\n     * @return 租户套餐分页\n     */\n    PageResult<TenantPackageDO> getTenantPackagePage(TenantPackagePageReqVO pageReqVO);\n\n    /**\n     * 校验租户套餐\n     *\n     * @param id 编号\n     * @return 租户套餐\n     */\n    TenantPackageDO validTenantPackage(Long id);\n\n    /**\n     * 获得指定状态的租户套餐列表\n     *\n     * @param status 状态\n     * @return 租户套餐\n     */\n    List<TenantPackageDO> getTenantPackageListByStatus(Integer status);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/tenant/TenantPackageServiceImpl.java",
    "content": "package co.yixiang.yshop.module.system.service.tenant;\n\nimport cn.hutool.core.collection.CollUtil;\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.controller.admin.tenant.vo.packages.TenantPackagePageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.tenant.vo.packages.TenantPackageSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.tenant.TenantDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.tenant.TenantPackageDO;\nimport co.yixiang.yshop.module.system.dal.mysql.tenant.TenantPackageMapper;\nimport com.baomidou.dynamic.datasource.annotation.DSTransactional;\nimport org.springframework.context.annotation.Lazy;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.*;\n\n/**\n * 租户套餐 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\npublic class TenantPackageServiceImpl implements TenantPackageService {\n\n    @Resource\n    private TenantPackageMapper tenantPackageMapper;\n\n    @Resource\n    @Lazy // 避免循环依赖的报错\n    private TenantService tenantService;\n\n    @Override\n    public Long createTenantPackage(TenantPackageSaveReqVO createReqVO) {\n        // 插入\n        TenantPackageDO tenantPackage = BeanUtils.toBean(createReqVO, TenantPackageDO.class);\n        tenantPackageMapper.insert(tenantPackage);\n        // 返回\n        return tenantPackage.getId();\n    }\n\n    @Override\n    @DSTransactional // 多数据源，使用 @DSTransactional 保证本地事务，以及数据源的切换\n    public void updateTenantPackage(TenantPackageSaveReqVO updateReqVO) {\n        // 校验存在\n        TenantPackageDO tenantPackage = validateTenantPackageExists(updateReqVO.getId());\n        // 更新\n        TenantPackageDO updateObj = BeanUtils.toBean(updateReqVO, TenantPackageDO.class);\n        tenantPackageMapper.updateById(updateObj);\n        // 如果菜单发生变化，则修改每个租户的菜单\n        if (!CollUtil.isEqualList(tenantPackage.getMenuIds(), updateReqVO.getMenuIds())) {\n            List<TenantDO> tenants = tenantService.getTenantListByPackageId(tenantPackage.getId());\n            tenants.forEach(tenant -> tenantService.updateTenantRoleMenu(tenant.getId(), updateReqVO.getMenuIds()));\n        }\n    }\n\n    @Override\n    public void deleteTenantPackage(Long id) {\n        // 校验存在\n        validateTenantPackageExists(id);\n        // 校验正在使用\n        validateTenantUsed(id);\n        // 删除\n        tenantPackageMapper.deleteById(id);\n    }\n\n    private TenantPackageDO validateTenantPackageExists(Long id) {\n        TenantPackageDO tenantPackage = tenantPackageMapper.selectById(id);\n        if (tenantPackage == null) {\n            throw exception(TENANT_PACKAGE_NOT_EXISTS);\n        }\n        return tenantPackage;\n    }\n\n    private void validateTenantUsed(Long id) {\n        if (tenantService.getTenantCountByPackageId(id) > 0) {\n            throw exception(TENANT_PACKAGE_USED);\n        }\n    }\n\n    @Override\n    public TenantPackageDO getTenantPackage(Long id) {\n        return tenantPackageMapper.selectById(id);\n    }\n\n    @Override\n    public PageResult<TenantPackageDO> getTenantPackagePage(TenantPackagePageReqVO pageReqVO) {\n        return tenantPackageMapper.selectPage(pageReqVO);\n    }\n\n    @Override\n    public TenantPackageDO validTenantPackage(Long id) {\n        TenantPackageDO tenantPackage = tenantPackageMapper.selectById(id);\n        if (tenantPackage == null) {\n            throw exception(TENANT_PACKAGE_NOT_EXISTS);\n        }\n        if (tenantPackage.getStatus().equals(CommonStatusEnum.DISABLE.getStatus())) {\n            throw exception(TENANT_PACKAGE_DISABLE, tenantPackage.getName());\n        }\n        return tenantPackage;\n    }\n\n    @Override\n    public List<TenantPackageDO> getTenantPackageListByStatus(Integer status) {\n        return tenantPackageMapper.selectListByStatus(status);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/tenant/TenantService.java",
    "content": "package co.yixiang.yshop.module.system.service.tenant;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.tenant.core.context.TenantContextHolder;\nimport co.yixiang.yshop.module.system.controller.admin.tenant.vo.tenant.TenantPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.tenant.vo.tenant.TenantSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.tenant.TenantDO;\nimport co.yixiang.yshop.module.system.service.tenant.handler.TenantInfoHandler;\nimport co.yixiang.yshop.module.system.service.tenant.handler.TenantMenuHandler;\n\nimport jakarta.validation.Valid;\nimport java.util.List;\nimport java.util.Set;\n\n/**\n * 租户 Service 接口\n *\n * @author yshop\n */\npublic interface TenantService {\n\n    /**\n     * 创建租户\n     *\n     * @param createReqVO 创建信息\n     * @return 编号\n     */\n    Long createTenant(@Valid TenantSaveReqVO createReqVO);\n\n    /**\n     * 更新租户\n     *\n     * @param updateReqVO 更新信息\n     */\n    void updateTenant(@Valid TenantSaveReqVO updateReqVO);\n\n    /**\n     * 更新租户的角色菜单\n     *\n     * @param tenantId 租户编号\n     * @param menuIds 菜单编号数组\n     */\n    void updateTenantRoleMenu(Long tenantId, Set<Long> menuIds);\n\n    /**\n     * 删除租户\n     *\n     * @param id 编号\n     */\n    void deleteTenant(Long id);\n\n    /**\n     * 获得租户\n     *\n     * @param id 编号\n     * @return 租户\n     */\n    TenantDO getTenant(Long id);\n\n    /**\n     * 获得租户分页\n     *\n     * @param pageReqVO 分页查询\n     * @return 租户分页\n     */\n    PageResult<TenantDO> getTenantPage(TenantPageReqVO pageReqVO);\n\n    /**\n     * 获得名字对应的租户\n     *\n     * @param name 租户名\n     * @return 租户\n     */\n    TenantDO getTenantByName(String name);\n\n    /**\n     * 获得域名对应的租户\n     *\n     * @param website 域名\n     * @return 租户\n     */\n    TenantDO getTenantByWebsite(String website);\n\n    /**\n     * 获得使用指定套餐的租户数量\n     *\n     * @param packageId 租户套餐编号\n     * @return 租户数量\n     */\n    Long getTenantCountByPackageId(Long packageId);\n\n    /**\n     * 获得使用指定套餐的租户数组\n     *\n     * @param packageId 租户套餐编号\n     * @return 租户数组\n     */\n    List<TenantDO> getTenantListByPackageId(Long packageId);\n\n    /**\n     * 进行租户的信息处理逻辑\n     * 其中，租户编号从 {@link TenantContextHolder} 上下文中获取\n     *\n     * @param handler 处理器\n     */\n    void handleTenantInfo(TenantInfoHandler handler);\n\n    /**\n     * 进行租户的菜单处理逻辑\n     * 其中，租户编号从 {@link TenantContextHolder} 上下文中获取\n     *\n     * @param handler 处理器\n     */\n    void handleTenantMenu(TenantMenuHandler handler);\n\n    /**\n     * 获得所有租户\n     *\n     * @return 租户编号数组\n     */\n    List<Long> getTenantIdList();\n\n    /**\n     * 校验租户是否合法\n     *\n     * @param id 租户编号\n     */\n    void validTenant(Long id);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/tenant/TenantServiceImpl.java",
    "content": "package co.yixiang.yshop.module.system.service.tenant;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.lang.Assert;\nimport cn.hutool.core.util.ObjectUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.collection.CollectionUtils;\nimport co.yixiang.yshop.framework.common.util.date.DateUtils;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.framework.tenant.config.TenantProperties;\nimport co.yixiang.yshop.framework.tenant.core.context.TenantContextHolder;\nimport co.yixiang.yshop.framework.tenant.core.util.TenantUtils;\nimport co.yixiang.yshop.module.system.controller.admin.permission.vo.role.RoleSaveReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.tenant.vo.tenant.TenantPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.tenant.vo.tenant.TenantSaveReqVO;\nimport co.yixiang.yshop.module.system.convert.tenant.TenantConvert;\nimport co.yixiang.yshop.module.system.dal.dataobject.permission.MenuDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.permission.RoleDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.tenant.TenantDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.tenant.TenantPackageDO;\nimport co.yixiang.yshop.module.system.dal.mysql.tenant.TenantMapper;\nimport co.yixiang.yshop.module.system.enums.permission.RoleCodeEnum;\nimport co.yixiang.yshop.module.system.enums.permission.RoleTypeEnum;\nimport co.yixiang.yshop.module.system.service.permission.MenuService;\nimport co.yixiang.yshop.module.system.service.permission.PermissionService;\nimport co.yixiang.yshop.module.system.service.permission.RoleService;\nimport co.yixiang.yshop.module.system.service.tenant.handler.TenantInfoHandler;\nimport co.yixiang.yshop.module.system.service.tenant.handler.TenantMenuHandler;\nimport co.yixiang.yshop.module.system.service.user.AdminUserService;\nimport com.baomidou.dynamic.datasource.annotation.DSTransactional;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.context.annotation.Lazy;\nimport org.springframework.stereotype.Service;\nimport org.springframework.validation.annotation.Validated;\n\nimport jakarta.annotation.Resource;\nimport java.util.List;\nimport java.util.Objects;\nimport java.util.Set;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.*;\nimport static java.util.Collections.singleton;\n\n/**\n * 租户 Service 实现类\n *\n * @author yshop\n */\n@Service\n@Validated\n@Slf4j\npublic class TenantServiceImpl implements TenantService {\n\n    @SuppressWarnings(\"SpringJavaAutowiredFieldsWarningInspection\")\n    @Autowired(required = false) // 由于 yshop.tenant.enable 配置项，可以关闭多租户的功能，所以这里只能不强制注入\n    private TenantProperties tenantProperties;\n\n    @Resource\n    private TenantMapper tenantMapper;\n\n    @Resource\n    private TenantPackageService tenantPackageService;\n    @Resource\n    @Lazy // 延迟，避免循环依赖报错\n    private AdminUserService userService;\n    @Resource\n    private RoleService roleService;\n    @Resource\n    private MenuService menuService;\n    @Resource\n    private PermissionService permissionService;\n\n    @Override\n    public List<Long> getTenantIdList() {\n        List<TenantDO> tenants = tenantMapper.selectList();\n        return CollectionUtils.convertList(tenants, TenantDO::getId);\n    }\n\n    @Override\n    public void validTenant(Long id) {\n        TenantDO tenant = getTenant(id);\n        if (tenant == null) {\n            throw exception(TENANT_NOT_EXISTS);\n        }\n        if (tenant.getStatus().equals(CommonStatusEnum.DISABLE.getStatus())) {\n            throw exception(TENANT_DISABLE, tenant.getName());\n        }\n        if (DateUtils.isExpired(tenant.getExpireTime())) {\n            throw exception(TENANT_EXPIRE, tenant.getName());\n        }\n    }\n\n    @Override\n    @DSTransactional // 多数据源，使用 @DSTransactional 保证本地事务，以及数据源的切换\n    public Long createTenant(TenantSaveReqVO createReqVO) {\n        // 校验租户名称是否重复\n        validTenantNameDuplicate(createReqVO.getName(), null);\n        // 校验租户域名是否重复\n        validTenantWebsiteDuplicate(createReqVO.getWebsite(), null);\n        // 校验套餐被禁用\n        TenantPackageDO tenantPackage = tenantPackageService.validTenantPackage(createReqVO.getPackageId());\n\n        // 创建租户\n        TenantDO tenant = BeanUtils.toBean(createReqVO, TenantDO.class);\n        tenantMapper.insert(tenant);\n        // 创建租户的管理员\n        TenantUtils.execute(tenant.getId(), () -> {\n            // 创建角色\n            Long roleId = createRole(tenantPackage);\n            // 创建用户，并分配角色\n            Long userId = createUser(roleId, createReqVO);\n            // 修改租户的管理员\n            tenantMapper.updateById(new TenantDO().setId(tenant.getId()).setContactUserId(userId));\n        });\n        return tenant.getId();\n    }\n\n    private Long createUser(Long roleId, TenantSaveReqVO createReqVO) {\n        // 创建用户\n        Long userId = userService.createUser(TenantConvert.INSTANCE.convert02(createReqVO));\n        // 分配角色\n        permissionService.assignUserRole(userId, singleton(roleId));\n        return userId;\n    }\n\n    private Long createRole(TenantPackageDO tenantPackage) {\n        // 创建角色\n        RoleSaveReqVO reqVO = new RoleSaveReqVO();\n        reqVO.setName(RoleCodeEnum.TENANT_ADMIN.getName()).setCode(RoleCodeEnum.TENANT_ADMIN.getCode())\n                .setSort(0).setRemark(\"系统自动生成\");\n        Long roleId = roleService.createRole(reqVO, RoleTypeEnum.SYSTEM.getType());\n        // 分配权限\n        permissionService.assignRoleMenu(roleId, tenantPackage.getMenuIds());\n        return roleId;\n    }\n\n    @Override\n    @DSTransactional // 多数据源，使用 @DSTransactional 保证本地事务，以及数据源的切换\n    public void updateTenant(TenantSaveReqVO updateReqVO) {\n        // 校验存在\n        TenantDO tenant = validateUpdateTenant(updateReqVO.getId());\n        // 校验租户名称是否重复\n        validTenantNameDuplicate(updateReqVO.getName(), updateReqVO.getId());\n        // 校验租户域名是否重复\n        validTenantWebsiteDuplicate(updateReqVO.getWebsite(), updateReqVO.getId());\n        // 校验套餐被禁用\n        TenantPackageDO tenantPackage = tenantPackageService.validTenantPackage(updateReqVO.getPackageId());\n\n        // 更新租户\n        TenantDO updateObj = BeanUtils.toBean(updateReqVO, TenantDO.class);\n        tenantMapper.updateById(updateObj);\n        // 如果套餐发生变化，则修改其角色的权限\n        if (ObjectUtil.notEqual(tenant.getPackageId(), updateReqVO.getPackageId())) {\n            updateTenantRoleMenu(tenant.getId(), tenantPackage.getMenuIds());\n        }\n    }\n\n    private void validTenantNameDuplicate(String name, Long id) {\n        TenantDO tenant = tenantMapper.selectByName(name);\n        if (tenant == null) {\n            return;\n        }\n        // 如果 id 为空，说明不用比较是否为相同名字的租户\n        if (id == null) {\n            throw exception(TENANT_NAME_DUPLICATE, name);\n        }\n        if (!tenant.getId().equals(id)) {\n            throw exception(TENANT_NAME_DUPLICATE, name);\n        }\n    }\n\n    private void validTenantWebsiteDuplicate(String website, Long id) {\n        if (StrUtil.isEmpty(website)) {\n            return;\n        }\n        TenantDO tenant = tenantMapper.selectByWebsite(website);\n        if (tenant == null) {\n            return;\n        }\n        // 如果 id 为空，说明不用比较是否为相同名字的租户\n        if (id == null) {\n            throw exception(TENANT_WEBSITE_DUPLICATE, website);\n        }\n        if (!tenant.getId().equals(id)) {\n            throw exception(TENANT_WEBSITE_DUPLICATE, website);\n        }\n    }\n\n    @Override\n    @DSTransactional\n    public void updateTenantRoleMenu(Long tenantId, Set<Long> menuIds) {\n        TenantUtils.execute(tenantId, () -> {\n            // 获得所有角色\n            List<RoleDO> roles = roleService.getRoleList();\n            roles.forEach(role -> Assert.isTrue(tenantId.equals(role.getTenantId()), \"角色({}/{}) 租户不匹配\",\n                    role.getId(), role.getTenantId(), tenantId)); // 兜底校验\n            // 重新分配每个角色的权限\n            roles.forEach(role -> {\n                // 如果是租户管理员，重新分配其权限为租户套餐的权限\n                if (Objects.equals(role.getCode(), RoleCodeEnum.TENANT_ADMIN.getCode())) {\n                    permissionService.assignRoleMenu(role.getId(), menuIds);\n                    log.info(\"[updateTenantRoleMenu][租户管理员({}/{}) 的权限修改为({})]\", role.getId(), role.getTenantId(), menuIds);\n                    return;\n                }\n                // 如果是其他角色，则去掉超过套餐的权限\n                Set<Long> roleMenuIds = permissionService.getRoleMenuListByRoleId(role.getId());\n                roleMenuIds = CollUtil.intersectionDistinct(roleMenuIds, menuIds);\n                permissionService.assignRoleMenu(role.getId(), roleMenuIds);\n                log.info(\"[updateTenantRoleMenu][角色({}/{}) 的权限修改为({})]\", role.getId(), role.getTenantId(), roleMenuIds);\n            });\n        });\n    }\n\n    @Override\n    public void deleteTenant(Long id) {\n        // 校验存在\n        validateUpdateTenant(id);\n        // 删除\n        tenantMapper.deleteById(id);\n    }\n\n    private TenantDO validateUpdateTenant(Long id) {\n        TenantDO tenant = tenantMapper.selectById(id);\n        if (tenant == null) {\n            throw exception(TENANT_NOT_EXISTS);\n        }\n        // 内置租户，不允许删除\n        if (isSystemTenant(tenant)) {\n            throw exception(TENANT_CAN_NOT_UPDATE_SYSTEM);\n        }\n        return tenant;\n    }\n\n    @Override\n    public TenantDO getTenant(Long id) {\n        return tenantMapper.selectById(id);\n    }\n\n    @Override\n    public PageResult<TenantDO> getTenantPage(TenantPageReqVO pageReqVO) {\n        return tenantMapper.selectPage(pageReqVO);\n    }\n\n    @Override\n    public TenantDO getTenantByName(String name) {\n        return tenantMapper.selectByName(name);\n    }\n\n    @Override\n    public TenantDO getTenantByWebsite(String website) {\n        return tenantMapper.selectByWebsite(website);\n    }\n\n    @Override\n    public Long getTenantCountByPackageId(Long packageId) {\n        return tenantMapper.selectCountByPackageId(packageId);\n    }\n\n    @Override\n    public List<TenantDO> getTenantListByPackageId(Long packageId) {\n        return tenantMapper.selectListByPackageId(packageId);\n    }\n\n    @Override\n    public void handleTenantInfo(TenantInfoHandler handler) {\n        // 如果禁用，则不执行逻辑\n        if (isTenantDisable()) {\n            return;\n        }\n        // 获得租户\n        TenantDO tenant = getTenant(TenantContextHolder.getRequiredTenantId());\n        // 执行处理器\n        handler.handle(tenant);\n    }\n\n    @Override\n    public void handleTenantMenu(TenantMenuHandler handler) {\n        // 如果禁用，则不执行逻辑\n        if (isTenantDisable()) {\n            return;\n        }\n        // 获得租户，然后获得菜单\n        TenantDO tenant = getTenant(TenantContextHolder.getRequiredTenantId());\n        Set<Long> menuIds;\n        if (isSystemTenant(tenant)) { // 系统租户，菜单是全量的\n            menuIds = CollectionUtils.convertSet(menuService.getMenuList(), MenuDO::getId);\n        } else {\n            menuIds = tenantPackageService.getTenantPackage(tenant.getPackageId()).getMenuIds();\n        }\n        // 执行处理器\n        handler.handle(menuIds);\n    }\n\n    private static boolean isSystemTenant(TenantDO tenant) {\n        return Objects.equals(tenant.getPackageId(), TenantDO.PACKAGE_ID_SYSTEM);\n    }\n\n    private boolean isTenantDisable() {\n        return tenantProperties == null || Boolean.FALSE.equals(tenantProperties.getEnable());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/tenant/handler/TenantInfoHandler.java",
    "content": "package co.yixiang.yshop.module.system.service.tenant.handler;\n\nimport co.yixiang.yshop.module.system.dal.dataobject.tenant.TenantDO;\n\n/**\n * 租户信息处理\n * 目的：尽量减少租户逻辑耦合到系统中\n *\n * @author yshop\n */\npublic interface TenantInfoHandler {\n\n    /**\n     * 基于传入的租户信息，进行相关逻辑的执行\n     * 例如说，创建用户时，超过最大账户配额\n     *\n     * @param tenant 租户信息\n     */\n    void handle(TenantDO tenant);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/tenant/handler/TenantMenuHandler.java",
    "content": "package co.yixiang.yshop.module.system.service.tenant.handler;\n\nimport java.util.Set;\n\n/**\n * 租户菜单处理\n * 目的：尽量减少租户逻辑耦合到系统中\n *\n * @author yshop\n */\npublic interface TenantMenuHandler {\n\n    /**\n     * 基于传入的租户菜单【全】列表，进行相关逻辑的执行\n     * 例如说，返回可分配菜单的时候，可以移除多余的\n     *\n     * @param menuIds 菜单列表\n     */\n    void handle(Set<Long> menuIds);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/user/AdminUserService.java",
    "content": "package co.yixiang.yshop.module.system.service.user;\n\nimport cn.hutool.core.collection.CollUtil;\nimport co.yixiang.yshop.framework.common.util.collection.CollectionUtils;\nimport co.yixiang.yshop.module.system.controller.admin.user.vo.profile.UserProfileUpdatePasswordReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.user.vo.profile.UserProfileUpdateReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.user.vo.user.*;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.module.system.dal.dataobject.user.AdminUserDO;\n\nimport jakarta.validation.Valid;\nimport java.io.InputStream;\nimport java.util.*;\n\n/**\n * 后台用户 Service 接口\n *\n * @author yshop\n */\npublic interface AdminUserService {\n\n    /**\n     * 创建用户\n     *\n     * @param createReqVO 用户信息\n     * @return 用户编号\n     */\n    Long createUser(@Valid UserSaveReqVO createReqVO);\n\n    /**\n     * 修改用户\n     *\n     * @param updateReqVO 用户信息\n     */\n    void updateUser(@Valid UserSaveReqVO updateReqVO);\n\n    /**\n     * 更新用户的最后登陆信息\n     *\n     * @param id 用户编号\n     * @param loginIp 登陆 IP\n     */\n    void updateUserLogin(Long id, String loginIp);\n\n    /**\n     * 修改用户个人信息\n     *\n     * @param id 用户编号\n     * @param reqVO 用户个人信息\n     */\n    void updateUserProfile(Long id, @Valid UserProfileUpdateReqVO reqVO);\n\n    /**\n     * 修改用户个人密码\n     *\n     * @param id 用户编号\n     * @param reqVO 更新用户个人密码\n     */\n    void updateUserPassword(Long id, @Valid UserProfileUpdatePasswordReqVO reqVO);\n\n    /**\n     * 更新用户头像\n     *\n     * @param id         用户 id\n     * @param avatarFile 头像文件\n     */\n    String updateUserAvatar(Long id, InputStream avatarFile) throws Exception;\n\n    /**\n     * 修改密码\n     *\n     * @param id       用户编号\n     * @param password 密码\n     */\n    void updateUserPassword(Long id, String password);\n\n    /**\n     * 修改状态\n     *\n     * @param id     用户编号\n     * @param status 状态\n     */\n    void updateUserStatus(Long id, Integer status);\n\n    /**\n     * 删除用户\n     *\n     * @param id 用户编号\n     */\n    void deleteUser(Long id);\n\n    /**\n     * 通过用户名查询用户\n     *\n     * @param username 用户名\n     * @return 用户对象信息\n     */\n    AdminUserDO getUserByUsername(String username);\n\n    /**\n     * 通过手机号获取用户\n     *\n     * @param mobile 手机号\n     * @return 用户对象信息\n     */\n    AdminUserDO getUserByMobile(String mobile);\n\n    /**\n     * 获得用户分页列表\n     *\n     * @param reqVO 分页条件\n     * @return 分页列表\n     */\n    PageResult<AdminUserDO> getUserPage(UserPageReqVO reqVO);\n\n    /**\n     * 通过用户 ID 查询用户\n     *\n     * @param id 用户ID\n     * @return 用户对象信息\n     */\n    AdminUserDO getUser(Long id);\n\n    /**\n     * 获得指定部门的用户数组\n     *\n     * @param deptIds 部门数组\n     * @return 用户数组\n     */\n    List<AdminUserDO> getUserListByDeptIds(Collection<Long> deptIds);\n\n    /**\n     * 获得指定岗位的用户数组\n     *\n     * @param postIds 岗位数组\n     * @return 用户数组\n     */\n    List<AdminUserDO> getUserListByPostIds(Collection<Long> postIds);\n\n    /**\n     * 获得用户列表\n     *\n     * @param ids 用户编号数组\n     * @return 用户列表\n     */\n    List<AdminUserDO> getUserList(Collection<Long> ids);\n\n    /**\n     * 校验用户们是否有效。如下情况，视为无效：\n     * 1. 用户编号不存在\n     * 2. 用户被禁用\n     *\n     * @param ids 用户编号数组\n     */\n    void validateUserList(Collection<Long> ids);\n\n    /**\n     * 获得用户 Map\n     *\n     * @param ids 用户编号数组\n     * @return 用户 Map\n     */\n    default Map<Long, AdminUserDO> getUserMap(Collection<Long> ids) {\n        if (CollUtil.isEmpty(ids)) {\n            return new HashMap<>();\n        }\n        return CollectionUtils.convertMap(getUserList(ids), AdminUserDO::getId);\n    }\n\n    /**\n     * 获得用户列表，基于昵称模糊匹配\n     *\n     * @param nickname 昵称\n     * @return 用户列表\n     */\n    List<AdminUserDO> getUserListByNickname(String nickname);\n\n    /**\n     * 批量导入用户\n     *\n     * @param importUsers     导入用户列表\n     * @param isUpdateSupport 是否支持更新\n     * @return 导入结果\n     */\n    UserImportRespVO importUserList(List<UserImportExcelVO> importUsers, boolean isUpdateSupport);\n\n    /**\n     * 获得指定状态的用户们\n     *\n     * @param status 状态\n     * @return 用户们\n     */\n    List<AdminUserDO> getUserListByStatus(Integer status);\n\n    /**\n     * 判断密码是否匹配\n     *\n     * @param rawPassword 未加密的密码\n     * @param encodedPassword 加密后的密码\n     * @return 是否匹配\n     */\n    boolean isPasswordMatch(String rawPassword, String encodedPassword);\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/service/user/AdminUserServiceImpl.java",
    "content": "package co.yixiang.yshop.module.system.service.user;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.collection.CollectionUtil;\nimport cn.hutool.core.io.IoUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.exception.ErrorCode;\nimport co.yixiang.yshop.framework.common.exception.ServiceException;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.collection.CollectionUtils;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.framework.datapermission.core.util.DataPermissionUtils;\nimport co.yixiang.yshop.module.infra.api.file.FileApi;\nimport co.yixiang.yshop.module.store.dal.dataobject.storeshop.StoreShopDO;\nimport co.yixiang.yshop.module.store.dal.mysql.storeshop.StoreShopMapper;\nimport co.yixiang.yshop.module.system.controller.admin.user.vo.profile.UserProfileUpdatePasswordReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.user.vo.profile.UserProfileUpdateReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.user.vo.user.UserImportExcelVO;\nimport co.yixiang.yshop.module.system.controller.admin.user.vo.user.UserImportRespVO;\nimport co.yixiang.yshop.module.system.controller.admin.user.vo.user.UserPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.user.vo.user.UserSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.dept.DeptDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.dept.UserPostDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.user.AdminUserDO;\nimport co.yixiang.yshop.module.system.dal.mysql.dept.UserPostMapper;\nimport co.yixiang.yshop.module.system.dal.mysql.user.AdminUserMapper;\nimport co.yixiang.yshop.module.system.service.dept.DeptService;\nimport co.yixiang.yshop.module.system.service.dept.PostService;\nimport co.yixiang.yshop.module.system.service.permission.PermissionService;\nimport co.yixiang.yshop.module.system.service.tenant.TenantService;\nimport com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;\nimport com.google.common.annotations.VisibleForTesting;\nimport com.mzt.logapi.context.LogRecordContext;\nimport com.mzt.logapi.service.impl.DiffParseFunction;\nimport com.mzt.logapi.starter.annotation.LogRecord;\nimport jakarta.annotation.Resource;\nimport lombok.extern.slf4j.Slf4j;\nimport org.springframework.beans.factory.annotation.Value;\nimport org.springframework.context.annotation.Lazy;\nimport org.springframework.security.crypto.password.PasswordEncoder;\nimport org.springframework.stereotype.Service;\nimport org.springframework.transaction.annotation.Transactional;\n\nimport java.io.InputStream;\nimport java.time.LocalDateTime;\nimport java.util.*;\n\nimport static co.yixiang.yshop.framework.common.exception.util.ServiceExceptionUtil.exception;\nimport static co.yixiang.yshop.framework.common.util.collection.CollectionUtils.convertList;\nimport static co.yixiang.yshop.framework.common.util.collection.CollectionUtils.convertSet;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.*;\nimport static co.yixiang.yshop.module.system.enums.LogRecordConstants.*;\n\n/**\n * 后台用户 Service 实现类\n *\n * @author yshop\n */\n@Service(\"adminUserService\")\n@Slf4j\npublic class AdminUserServiceImpl implements AdminUserService {\n\n    @Value(\"${sys.user.init-password:yshopyuanma}\")\n    private String userInitPassword;\n\n    @Resource\n    private AdminUserMapper userMapper;\n\n    @Resource\n    private DeptService deptService;\n    @Resource\n    private PostService postService;\n    @Resource\n    private PermissionService permissionService;\n    @Resource\n    private PasswordEncoder passwordEncoder;\n    @Resource\n    @Lazy // 延迟，避免循环依赖报错\n    private TenantService tenantService;\n\n    @Resource\n    private UserPostMapper userPostMapper;\n\n    @Resource\n    private FileApi fileApi;\n    @Resource\n    private StoreShopMapper storeShopMapper;\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    @LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_CREATE_SUB_TYPE, bizNo = \"{{#user.id}}\",\n            success = SYSTEM_USER_CREATE_SUCCESS)\n    public Long createUser(UserSaveReqVO createReqVO) {\n        // 1.1 校验账户配合\n        tenantService.handleTenantInfo(tenant -> {\n            long count = userMapper.selectCount();\n            if (count >= tenant.getAccountCount()) {\n                throw exception(USER_COUNT_MAX, tenant.getAccountCount());\n            }\n        });\n        // 1.2 校验正确性\n        validateUserForCreateOrUpdate(null, createReqVO.getUsername(),\n                createReqVO.getMobile(), createReqVO.getEmail(), createReqVO.getDeptId(), createReqVO.getPostIds());\n        // 2.1 插入用户\n        AdminUserDO user = BeanUtils.toBean(createReqVO, AdminUserDO.class);\n        user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启\n        user.setPassword(encodePassword(createReqVO.getPassword())); // 加密密码\n        userMapper.insert(user);\n        // 2.2 插入关联岗位\n        if (CollectionUtil.isNotEmpty(user.getPostIds())) {\n            userPostMapper.insertBatch(convertList(user.getPostIds(),\n                    postId -> new UserPostDO().setUserId(user.getId()).setPostId(postId)));\n        }\n\n        // 3. 记录操作日志上下文\n        LogRecordContext.putVariable(\"user\", user);\n        return user.getId();\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    @LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_UPDATE_SUB_TYPE, bizNo = \"{{#updateReqVO.id}}\",\n            success = SYSTEM_USER_UPDATE_SUCCESS)\n    public void updateUser(UserSaveReqVO updateReqVO) {\n        updateReqVO.setPassword(null); // 特殊：此处不更新密码\n        // 1. 校验正确性\n        AdminUserDO oldUser = validateUserForCreateOrUpdate(updateReqVO.getId(), updateReqVO.getUsername(),\n                updateReqVO.getMobile(), updateReqVO.getEmail(), updateReqVO.getDeptId(), updateReqVO.getPostIds());\n\n        // 2.1 更新用户\n        AdminUserDO updateObj = BeanUtils.toBean(updateReqVO, AdminUserDO.class);\n        userMapper.updateById(updateObj);\n        // 2.2 更新岗位\n        updateUserPost(updateReqVO, updateObj);\n\n        // 3. 记录操作日志上下文\n        LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldUser, UserSaveReqVO.class));\n        LogRecordContext.putVariable(\"user\", oldUser);\n    }\n\n    private void updateUserPost(UserSaveReqVO reqVO, AdminUserDO updateObj) {\n        Long userId = reqVO.getId();\n        Set<Long> dbPostIds = convertSet(userPostMapper.selectListByUserId(userId), UserPostDO::getPostId);\n        // 计算新增和删除的岗位编号\n        Set<Long> postIds = CollUtil.emptyIfNull(updateObj.getPostIds());\n        Collection<Long> createPostIds = CollUtil.subtract(postIds, dbPostIds);\n        Collection<Long> deletePostIds = CollUtil.subtract(dbPostIds, postIds);\n        // 执行新增和删除。对于已经授权的岗位，不用做任何处理\n        if (!CollectionUtil.isEmpty(createPostIds)) {\n            userPostMapper.insertBatch(convertList(createPostIds,\n                    postId -> new UserPostDO().setUserId(userId).setPostId(postId)));\n        }\n        if (!CollectionUtil.isEmpty(deletePostIds)) {\n            userPostMapper.deleteByUserIdAndPostId(userId, deletePostIds);\n        }\n    }\n\n    @Override\n    public void updateUserLogin(Long id, String loginIp) {\n        userMapper.updateById(new AdminUserDO().setId(id).setLoginIp(loginIp).setLoginDate(LocalDateTime.now()));\n    }\n\n    @Override\n    public void updateUserProfile(Long id, UserProfileUpdateReqVO reqVO) {\n        // 校验正确性\n        validateUserExists(id);\n        validateEmailUnique(id, reqVO.getEmail());\n        validateMobileUnique(id, reqVO.getMobile());\n        // 执行更新\n        userMapper.updateById(BeanUtils.toBean(reqVO, AdminUserDO.class).setId(id));\n    }\n\n    @Override\n    public void updateUserPassword(Long id, UserProfileUpdatePasswordReqVO reqVO) {\n        // 校验旧密码密码\n        validateOldPassword(id, reqVO.getOldPassword());\n        // 执行更新\n        AdminUserDO updateObj = new AdminUserDO().setId(id);\n        updateObj.setPassword(encodePassword(reqVO.getNewPassword())); // 加密密码\n        userMapper.updateById(updateObj);\n    }\n\n    @Override\n    public String updateUserAvatar(Long id, InputStream avatarFile) {\n        validateUserExists(id);\n        // 存储文件\n        String avatar = fileApi.createFile(IoUtil.readBytes(avatarFile));\n        // 更新路径\n        AdminUserDO sysUserDO = new AdminUserDO();\n        sysUserDO.setId(id);\n        sysUserDO.setAvatar(avatar);\n        userMapper.updateById(sysUserDO);\n        return avatar;\n    }\n\n    @Override\n    @LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_UPDATE_PASSWORD_SUB_TYPE, bizNo = \"{{#id}}\",\n            success = SYSTEM_USER_UPDATE_PASSWORD_SUCCESS)\n    public void updateUserPassword(Long id, String password) {\n        // 1. 校验用户存在\n        AdminUserDO user = validateUserExists(id);\n\n        // 2. 更新密码\n        AdminUserDO updateObj = new AdminUserDO();\n        updateObj.setId(id);\n        updateObj.setPassword(encodePassword(password)); // 加密密码\n        userMapper.updateById(updateObj);\n\n        // 3. 记录操作日志上下文\n        LogRecordContext.putVariable(\"user\", user);\n        LogRecordContext.putVariable(\"newPassword\", updateObj.getPassword());\n    }\n\n    @Override\n    public void updateUserStatus(Long id, Integer status) {\n        // 校验用户存在\n        validateUserExists(id);\n        // 更新状态\n        AdminUserDO updateObj = new AdminUserDO();\n        updateObj.setId(id);\n        updateObj.setStatus(status);\n        userMapper.updateById(updateObj);\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class)\n    @LogRecord(type = SYSTEM_USER_TYPE, subType = SYSTEM_USER_DELETE_SUB_TYPE, bizNo = \"{{#id}}\",\n            success = SYSTEM_USER_DELETE_SUCCESS)\n    public void deleteUser(Long id) {\n        // 1. 校验用户存在\n        AdminUserDO user = validateUserExists(id);\n\n        getShop(id);\n\n        // 2.1 删除用户\n        userMapper.deleteById(id);\n        // 2.2 删除用户关联数据\n        permissionService.processUserDeleted(id);\n        // 2.2 删除用户岗位\n        userPostMapper.deleteByUserId(id);\n\n        // 3. 记录操作日志上下文\n        LogRecordContext.putVariable(\"user\", user);\n    }\n\n    private void getShop(Long userId) {\n        StoreShopDO storeShopDO = storeShopMapper.selectOne(new LambdaQueryWrapper<StoreShopDO>()\n                .apply(userId > 0, \"FIND_IN_SET ('\" + userId + \"',admin_id)\"));\n        if(storeShopDO != null){\n            throw exception(new ErrorCode(20241013,\"当前门店下：\" + storeShopDO.getName() + \"绑定等有管理员不可以删除\"));\n        }\n\n    }\n\n    @Override\n    public AdminUserDO getUserByUsername(String username) {\n        return userMapper.selectByUsername(username);\n    }\n\n    @Override\n    public AdminUserDO getUserByMobile(String mobile) {\n        return userMapper.selectByMobile(mobile);\n    }\n\n    @Override\n    public PageResult<AdminUserDO> getUserPage(UserPageReqVO reqVO) {\n        return userMapper.selectPage(reqVO, getDeptCondition(reqVO.getDeptId()));\n    }\n\n    @Override\n    public AdminUserDO getUser(Long id) {\n        return userMapper.selectById(id);\n    }\n\n    @Override\n    public List<AdminUserDO> getUserListByDeptIds(Collection<Long> deptIds) {\n        if (CollUtil.isEmpty(deptIds)) {\n            return Collections.emptyList();\n        }\n        return userMapper.selectListByDeptIds(deptIds);\n    }\n\n    @Override\n    public List<AdminUserDO> getUserListByPostIds(Collection<Long> postIds) {\n        if (CollUtil.isEmpty(postIds)) {\n            return Collections.emptyList();\n        }\n        Set<Long> userIds = convertSet(userPostMapper.selectListByPostIds(postIds), UserPostDO::getUserId);\n        if (CollUtil.isEmpty(userIds)) {\n            return Collections.emptyList();\n        }\n        return userMapper.selectBatchIds(userIds);\n    }\n\n    @Override\n    public List<AdminUserDO> getUserList(Collection<Long> ids) {\n        if (CollUtil.isEmpty(ids)) {\n            return Collections.emptyList();\n        }\n        return userMapper.selectBatchIds(ids);\n    }\n\n    @Override\n    public void validateUserList(Collection<Long> ids) {\n        if (CollUtil.isEmpty(ids)) {\n            return;\n        }\n        // 获得岗位信息\n        List<AdminUserDO> users = userMapper.selectBatchIds(ids);\n        Map<Long, AdminUserDO> userMap = CollectionUtils.convertMap(users, AdminUserDO::getId);\n        // 校验\n        ids.forEach(id -> {\n            AdminUserDO user = userMap.get(id);\n            if (user == null) {\n                throw exception(USER_NOT_EXISTS);\n            }\n            if (!CommonStatusEnum.ENABLE.getStatus().equals(user.getStatus())) {\n                throw exception(USER_IS_DISABLE, user.getNickname());\n            }\n        });\n    }\n\n    @Override\n    public List<AdminUserDO> getUserListByNickname(String nickname) {\n        return userMapper.selectListByNickname(nickname);\n    }\n\n    /**\n     * 获得部门条件：查询指定部门的子部门编号们，包括自身\n     * @param deptId 部门编号\n     * @return 部门编号集合\n     */\n    private Set<Long> getDeptCondition(Long deptId) {\n        if (deptId == null) {\n            return Collections.emptySet();\n        }\n        Set<Long> deptIds = convertSet(deptService.getChildDeptList(deptId), DeptDO::getId);\n        deptIds.add(deptId); // 包括自身\n        return deptIds;\n    }\n\n    private AdminUserDO validateUserForCreateOrUpdate(Long id, String username, String mobile, String email,\n                                               Long deptId, Set<Long> postIds) {\n        // 关闭数据权限，避免因为没有数据权限，查询不到数据，进而导致唯一校验不正确\n        return DataPermissionUtils.executeIgnore(() -> {\n            // 校验用户存在\n            AdminUserDO user = validateUserExists(id);\n            // 校验用户名唯一\n            validateUsernameUnique(id, username);\n            // 校验手机号唯一\n            validateMobileUnique(id, mobile);\n            // 校验邮箱唯一\n            validateEmailUnique(id, email);\n            // 校验部门处于开启状态\n            deptService.validateDeptList(CollectionUtils.singleton(deptId));\n            // 校验岗位处于开启状态\n            postService.validatePostList(postIds);\n            return user;\n        });\n    }\n\n    @VisibleForTesting\n    AdminUserDO validateUserExists(Long id) {\n        if (id == null) {\n            return null;\n        }\n        AdminUserDO user = userMapper.selectById(id);\n        if (user == null) {\n            throw exception(USER_NOT_EXISTS);\n        }\n        return user;\n    }\n\n    @VisibleForTesting\n    void validateUsernameUnique(Long id, String username) {\n        if (StrUtil.isBlank(username)) {\n            return;\n        }\n        AdminUserDO user = userMapper.selectByUsername(username);\n        if (user == null) {\n            return;\n        }\n        // 如果 id 为空，说明不用比较是否为相同 id 的用户\n        if (id == null) {\n            throw exception(USER_USERNAME_EXISTS);\n        }\n        if (!user.getId().equals(id)) {\n            throw exception(USER_USERNAME_EXISTS);\n        }\n    }\n\n    @VisibleForTesting\n    void validateEmailUnique(Long id, String email) {\n        if (StrUtil.isBlank(email)) {\n            return;\n        }\n        AdminUserDO user = userMapper.selectByEmail(email);\n        if (user == null) {\n            return;\n        }\n        // 如果 id 为空，说明不用比较是否为相同 id 的用户\n        if (id == null) {\n            throw exception(USER_EMAIL_EXISTS);\n        }\n        if (!user.getId().equals(id)) {\n            throw exception(USER_EMAIL_EXISTS);\n        }\n    }\n\n    @VisibleForTesting\n    void validateMobileUnique(Long id, String mobile) {\n        if (StrUtil.isBlank(mobile)) {\n            return;\n        }\n        AdminUserDO user = userMapper.selectByMobile(mobile);\n        if (user == null) {\n            return;\n        }\n        // 如果 id 为空，说明不用比较是否为相同 id 的用户\n        if (id == null) {\n            throw exception(USER_MOBILE_EXISTS);\n        }\n        if (!user.getId().equals(id)) {\n            throw exception(USER_MOBILE_EXISTS);\n        }\n    }\n\n    /**\n     * 校验旧密码\n     * @param id          用户 id\n     * @param oldPassword 旧密码\n     */\n    @VisibleForTesting\n    void validateOldPassword(Long id, String oldPassword) {\n        AdminUserDO user = userMapper.selectById(id);\n        if (user == null) {\n            throw exception(USER_NOT_EXISTS);\n        }\n        if (!isPasswordMatch(oldPassword, user.getPassword())) {\n            throw exception(USER_PASSWORD_FAILED);\n        }\n    }\n\n    @Override\n    @Transactional(rollbackFor = Exception.class) // 添加事务，异常则回滚所有导入\n    public UserImportRespVO importUserList(List<UserImportExcelVO> importUsers, boolean isUpdateSupport) {\n        if (CollUtil.isEmpty(importUsers)) {\n            throw exception(USER_IMPORT_LIST_IS_EMPTY);\n        }\n        UserImportRespVO respVO = UserImportRespVO.builder().createUsernames(new ArrayList<>())\n                .updateUsernames(new ArrayList<>()).failureUsernames(new LinkedHashMap<>()).build();\n        importUsers.forEach(importUser -> {\n            // 校验，判断是否有不符合的原因\n            try {\n                validateUserForCreateOrUpdate(null, null, importUser.getMobile(), importUser.getEmail(),\n                        importUser.getDeptId(), null);\n            } catch (ServiceException ex) {\n                respVO.getFailureUsernames().put(importUser.getUsername(), ex.getMessage());\n                return;\n            }\n            // 判断如果不存在，在进行插入\n            AdminUserDO existUser = userMapper.selectByUsername(importUser.getUsername());\n            if (existUser == null) {\n                userMapper.insert(BeanUtils.toBean(importUser, AdminUserDO.class)\n                        .setPassword(encodePassword(userInitPassword)).setPostIds(new HashSet<>())); // 设置默认密码及空岗位编号数组\n                respVO.getCreateUsernames().add(importUser.getUsername());\n                return;\n            }\n            // 如果存在，判断是否允许更新\n            if (!isUpdateSupport) {\n                respVO.getFailureUsernames().put(importUser.getUsername(), USER_USERNAME_EXISTS.getMsg());\n                return;\n            }\n            AdminUserDO updateUser = BeanUtils.toBean(importUser, AdminUserDO.class);\n            updateUser.setId(existUser.getId());\n            userMapper.updateById(updateUser);\n            respVO.getUpdateUsernames().add(importUser.getUsername());\n        });\n        return respVO;\n    }\n\n    @Override\n    public List<AdminUserDO> getUserListByStatus(Integer status) {\n        return userMapper.selectListByStatus(status);\n    }\n\n    @Override\n    public boolean isPasswordMatch(String rawPassword, String encodedPassword) {\n        return passwordEncoder.matches(rawPassword, encodedPassword);\n    }\n\n    /**\n     * 对密码进行加密\n     *\n     * @param password 密码\n     * @return 加密后的密码\n     */\n    private String encodePassword(String password) {\n        return passwordEncoder.encode(password);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/java/co/yixiang/yshop/module/system/util/oauth2/OAuth2Utils.java",
    "content": "package co.yixiang.yshop.module.system.util.oauth2;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.core.date.LocalDateTimeUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.util.http.HttpUtils;\nimport co.yixiang.yshop.framework.security.core.util.SecurityFrameworkUtils;\n\nimport java.time.LocalDateTime;\nimport java.time.temporal.ChronoUnit;\nimport java.util.*;\n\n/**\n * OAuth2 相关的工具类\n *\n * @author yshop\n */\npublic class OAuth2Utils {\n\n    /**\n     * 构建授权码模式下，重定向的 URI\n     *\n     * copy from Spring Security OAuth2 的 AuthorizationEndpoint 类的 getSuccessfulRedirect 方法\n     *\n     * @param redirectUri 重定向 URI\n     * @param authorizationCode 授权码\n     * @param state 状态\n     * @return 授权码模式下的重定向 URI\n     */\n    public static String buildAuthorizationCodeRedirectUri(String redirectUri, String authorizationCode, String state) {\n        Map<String, String> query = new LinkedHashMap<>();\n        query.put(\"code\", authorizationCode);\n        if (state != null) {\n            query.put(\"state\", state);\n        }\n        return HttpUtils.append(redirectUri, query, null, false);\n    }\n\n    /**\n     * 构建简化模式下，重定向的 URI\n     *\n     * copy from Spring Security OAuth2 的 AuthorizationEndpoint 类的 appendAccessToken 方法\n     *\n     * @param redirectUri 重定向 URI\n     * @param accessToken 访问令牌\n     * @param state 状态\n     * @param expireTime 过期时间\n     * @param scopes 授权范围\n     * @param additionalInformation 附加信息\n     * @return 简化授权模式下的重定向 URI\n     */\n    public static String buildImplicitRedirectUri(String redirectUri, String accessToken, String state, LocalDateTime expireTime,\n                                                  Collection<String> scopes, Map<String, Object> additionalInformation) {\n        Map<String, Object> vars = new LinkedHashMap<String, Object>();\n        Map<String, String> keys = new HashMap<String, String>();\n        vars.put(\"access_token\", accessToken);\n        vars.put(\"token_type\", SecurityFrameworkUtils.AUTHORIZATION_BEARER.toLowerCase());\n        if (state != null) {\n            vars.put(\"state\", state);\n        }\n        if (expireTime != null) {\n            vars.put(\"expires_in\", getExpiresIn(expireTime));\n        }\n        if (CollUtil.isNotEmpty(scopes)) {\n            vars.put(\"scope\", buildScopeStr(scopes));\n        }\n        if (CollUtil.isNotEmpty(additionalInformation)) {\n            for (String key : additionalInformation.keySet()) {\n                Object value = additionalInformation.get(key);\n                if (value != null) {\n                    keys.put(\"extra_\" + key, key);\n                    vars.put(\"extra_\" + key, value);\n                }\n            }\n        }\n        // Do not include the refresh token (even if there is one)\n        return HttpUtils.append(redirectUri, vars, keys, true);\n    }\n\n    public static String buildUnsuccessfulRedirect(String redirectUri, String responseType, String state,\n                                                   String error, String description) {\n        Map<String, String> query = new LinkedHashMap<String, String>();\n        query.put(\"error\", error);\n        query.put(\"error_description\", description);\n        if (state != null) {\n            query.put(\"state\", state);\n        }\n        return HttpUtils.append(redirectUri, query, null, !responseType.contains(\"code\"));\n    }\n\n    public static long getExpiresIn(LocalDateTime expireTime) {\n        return LocalDateTimeUtil.between(LocalDateTime.now(), expireTime, ChronoUnit.SECONDS);\n    }\n\n    public static String buildScopeStr(Collection<String> scopes) {\n        return CollUtil.join(scopes, \" \");\n    }\n\n    public static List<String> buildScopes(String scope) {\n        return StrUtil.split(scope, ' ');\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/main/resources/META-INF/services/com.xingyuv.captcha.service.CaptchaCacheService",
    "content": "co.yixiang.yshop.module.system.framework.captcha.core.RedisCaptchaServiceImpl\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/java/co/yixiang/yshop/module/system/controller/admin/oauth2/OAuth2OpenControllerTest.java",
    "content": "package co.yixiang.yshop.module.system.controller.admin.oauth2;\n\nimport cn.hutool.core.collection.ListUtil;\nimport cn.hutool.core.date.LocalDateTimeUtil;\nimport cn.hutool.core.map.MapUtil;\nimport co.yixiang.yshop.framework.common.core.KeyValue;\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.common.exception.ErrorCode;\nimport co.yixiang.yshop.framework.common.pojo.CommonResult;\nimport co.yixiang.yshop.framework.common.util.collection.SetUtils;\nimport co.yixiang.yshop.framework.common.util.object.ObjectUtils;\nimport co.yixiang.yshop.framework.test.core.ut.BaseMockitoUnitTest;\nimport co.yixiang.yshop.module.system.controller.admin.oauth2.vo.open.OAuth2OpenAccessTokenRespVO;\nimport co.yixiang.yshop.module.system.controller.admin.oauth2.vo.open.OAuth2OpenAuthorizeInfoRespVO;\nimport co.yixiang.yshop.module.system.controller.admin.oauth2.vo.open.OAuth2OpenCheckTokenRespVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2ApproveDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2ClientDO;\nimport co.yixiang.yshop.module.system.enums.oauth2.OAuth2GrantTypeEnum;\nimport co.yixiang.yshop.module.system.service.oauth2.OAuth2ApproveService;\nimport co.yixiang.yshop.module.system.service.oauth2.OAuth2ClientService;\nimport co.yixiang.yshop.module.system.service.oauth2.OAuth2GrantService;\nimport co.yixiang.yshop.module.system.service.oauth2.OAuth2TokenService;\nimport org.assertj.core.util.Lists;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\n\nimport jakarta.servlet.http.HttpServletRequest;\nimport java.time.LocalDateTime;\nimport java.time.temporal.ChronoUnit;\nimport java.util.ArrayList;\nimport java.util.LinkedHashMap;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.util.collection.SetUtils.asSet;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertPojoEquals;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertServiceException;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.randomPojo;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.randomString;\nimport static java.util.Arrays.asList;\nimport static org.hamcrest.CoreMatchers.anyOf;\nimport static org.hamcrest.CoreMatchers.is;\nimport static org.hamcrest.MatcherAssert.assertThat;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.ArgumentMatchers.isNull;\nimport static org.mockito.Mockito.mock;\nimport static org.mockito.Mockito.when;\n\n/**\n * {@link OAuth2OpenController} 的单元测试\n *\n * @author yshop\n */\npublic class OAuth2OpenControllerTest extends BaseMockitoUnitTest {\n\n    @InjectMocks\n    private OAuth2OpenController oauth2OpenController;\n\n    @Mock\n    private OAuth2GrantService oauth2GrantService;\n    @Mock\n    private OAuth2ClientService oauth2ClientService;\n    @Mock\n    private OAuth2ApproveService oauth2ApproveService;\n    @Mock\n    private OAuth2TokenService oauth2TokenService;\n\n    @Test\n    public void testPostAccessToken_authorizationCode() {\n        // 准备参数\n        String granType = OAuth2GrantTypeEnum.AUTHORIZATION_CODE.getGrantType();\n        String code = randomString();\n        String redirectUri = randomString();\n        String state = randomString();\n        HttpServletRequest request = mockRequest(\"test_client_id\", \"test_client_secret\");\n        // mock 方法（client）\n        OAuth2ClientDO client = randomPojo(OAuth2ClientDO.class).setClientId(\"test_client_id\");\n        when(oauth2ClientService.validOAuthClientFromCache(eq(\"test_client_id\"), eq(\"test_client_secret\"), eq(granType), eq(new ArrayList<>()), eq(redirectUri))).thenReturn(client);\n\n        // mock 方法（访问令牌）\n        OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class)\n                .setExpiresTime(LocalDateTimeUtil.offset(LocalDateTime.now(), 30000L, ChronoUnit.MILLIS));\n        when(oauth2GrantService.grantAuthorizationCodeForAccessToken(eq(\"test_client_id\"),\n                eq(code), eq(redirectUri), eq(state))).thenReturn(accessTokenDO);\n\n        // 调用\n        CommonResult<OAuth2OpenAccessTokenRespVO> result = oauth2OpenController.postAccessToken(request, granType,\n                code, redirectUri, state, null, null, null, null);\n        // 断言\n        assertEquals(0, result.getCode());\n        assertPojoEquals(accessTokenDO, result.getData());\n        assertTrue(ObjectUtils.equalsAny(result.getData().getExpiresIn(), 29L, 30L));  // 执行过程会过去几毫秒\n    }\n\n    @Test\n    public void testPostAccessToken_password() {\n        // 准备参数\n        String granType = OAuth2GrantTypeEnum.PASSWORD.getGrantType();\n        String username = randomString();\n        String password = randomString();\n        String scope = \"write read\";\n        HttpServletRequest request = mockRequest(\"test_client_id\", \"test_client_secret\");\n        // mock 方法（client）\n        OAuth2ClientDO client = randomPojo(OAuth2ClientDO.class).setClientId(\"test_client_id\");\n        when(oauth2ClientService.validOAuthClientFromCache(eq(\"test_client_id\"), eq(\"test_client_secret\"),\n                eq(granType), eq(Lists.newArrayList(\"write\", \"read\")), isNull())).thenReturn(client);\n\n        // mock 方法（访问令牌）\n        OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class)\n                .setExpiresTime(LocalDateTimeUtil.offset(LocalDateTime.now(), 30000L, ChronoUnit.MILLIS));\n        when(oauth2GrantService.grantPassword(eq(username), eq(password), eq(\"test_client_id\"),\n                eq(Lists.newArrayList(\"write\", \"read\")))).thenReturn(accessTokenDO);\n\n        // 调用\n        CommonResult<OAuth2OpenAccessTokenRespVO> result = oauth2OpenController.postAccessToken(request, granType,\n                null, null, null, username, password, scope, null);\n        // 断言\n        assertEquals(0, result.getCode());\n        assertPojoEquals(accessTokenDO, result.getData());\n        assertTrue(ObjectUtils.equalsAny(result.getData().getExpiresIn(), 29L, 30L));  // 执行过程会过去几毫秒\n    }\n\n    @Test\n    public void testPostAccessToken_refreshToken() {\n        // 准备参数\n        String granType = OAuth2GrantTypeEnum.REFRESH_TOKEN.getGrantType();\n        String refreshToken = randomString();\n        String password = randomString();\n        HttpServletRequest request = mockRequest(\"test_client_id\", \"test_client_secret\");\n        // mock 方法（client）\n        OAuth2ClientDO client = randomPojo(OAuth2ClientDO.class).setClientId(\"test_client_id\");\n        when(oauth2ClientService.validOAuthClientFromCache(eq(\"test_client_id\"), eq(\"test_client_secret\"),\n                eq(granType), eq(Lists.newArrayList()), isNull())).thenReturn(client);\n\n        // mock 方法（访问令牌）\n        OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class)\n                .setExpiresTime(LocalDateTimeUtil.offset(LocalDateTime.now(), 30000L, ChronoUnit.MILLIS));\n        when(oauth2GrantService.grantRefreshToken(eq(refreshToken), eq(\"test_client_id\"))).thenReturn(accessTokenDO);\n\n        // 调用\n        CommonResult<OAuth2OpenAccessTokenRespVO> result = oauth2OpenController.postAccessToken(request, granType,\n                null, null, null, null, password, null, refreshToken);\n        // 断言\n        assertEquals(0, result.getCode());\n        assertPojoEquals(accessTokenDO, result.getData());\n        assertTrue(ObjectUtils.equalsAny(result.getData().getExpiresIn(), 29L, 30L));  // 执行过程会过去几毫秒\n    }\n\n    @Test\n    public void testPostAccessToken_implicit() {\n        // 调用，并断言\n        assertServiceException(() -> oauth2OpenController.postAccessToken(null,\n                        OAuth2GrantTypeEnum.IMPLICIT.getGrantType(), null, null, null,\n                        null, null, null, null),\n                new ErrorCode(400, \"Token 接口不支持 implicit 授权模式\"));\n    }\n\n    @Test\n    public void testRevokeToken() {\n        // 准备参数\n        HttpServletRequest request = mockRequest(\"demo_client_id\", \"demo_client_secret\");\n        String token = randomString();\n        // mock 方法（client）\n        OAuth2ClientDO client = randomPojo(OAuth2ClientDO.class).setClientId(\"demo_client_id\");\n        when(oauth2ClientService.validOAuthClientFromCache(eq(\"demo_client_id\"),\n                eq(\"demo_client_secret\"), isNull(), isNull(), isNull())).thenReturn(client);\n        // mock 方法（移除）\n        when(oauth2GrantService.revokeToken(eq(\"demo_client_id\"), eq(token))).thenReturn(true);\n\n        // 调用\n        CommonResult<Boolean> result = oauth2OpenController.revokeToken(request, token);\n        // 断言\n        assertEquals(0, result.getCode());\n        assertTrue(result.getData());\n    }\n\n    @Test\n    public void testCheckToken() {\n        // 准备参数\n        HttpServletRequest request = mockRequest(\"demo_client_id\", \"demo_client_secret\");\n        String token = randomString();\n        // mock 方法\n        OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class).setUserType(UserTypeEnum.ADMIN.getValue()).setExpiresTime(LocalDateTimeUtil.of(1653485731195L));\n        when(oauth2TokenService.checkAccessToken(eq(token))).thenReturn(accessTokenDO);\n\n        // 调用\n        CommonResult<OAuth2OpenCheckTokenRespVO> result = oauth2OpenController.checkToken(request, token);\n        // 断言\n        assertEquals(0, result.getCode());\n        assertPojoEquals(accessTokenDO, result.getData());\n        assertEquals(1653485731L, result.getData().getExp()); // 执行过程会过去几毫秒\n    }\n\n    @Test\n    public void testAuthorize() {\n        // 准备参数\n        String clientId = randomString();\n        // mock 方法（client）\n        OAuth2ClientDO client = randomPojo(OAuth2ClientDO.class).setClientId(\"demo_client_id\").setScopes(ListUtil.toList(\"read\", \"write\", \"all\"));\n        when(oauth2ClientService.validOAuthClientFromCache(eq(clientId))).thenReturn(client);\n        // mock 方法（approve）\n        List<OAuth2ApproveDO> approves = asList(\n                randomPojo(OAuth2ApproveDO.class).setScope(\"read\").setApproved(true),\n                randomPojo(OAuth2ApproveDO.class).setScope(\"write\").setApproved(false));\n        when(oauth2ApproveService.getApproveList(isNull(), eq(UserTypeEnum.ADMIN.getValue()), eq(clientId))).thenReturn(approves);\n\n        // 调用\n        CommonResult<OAuth2OpenAuthorizeInfoRespVO> result = oauth2OpenController.authorize(clientId);\n        // 断言\n        assertEquals(0, result.getCode());\n        assertPojoEquals(client, result.getData().getClient());\n        assertEquals(new KeyValue<>(\"read\", true), result.getData().getScopes().get(0));\n        assertEquals(new KeyValue<>(\"write\", false), result.getData().getScopes().get(1));\n        assertEquals(new KeyValue<>(\"all\", false), result.getData().getScopes().get(2));\n    }\n\n    @Test\n    public void testApproveOrDeny_grantTypeError() {\n        // 调用，并断言\n        assertServiceException(() -> oauth2OpenController.approveOrDeny(randomString(), null,\n                        null, null, null, null),\n                new ErrorCode(400, \"response_type 参数值只允许 code 和 token\"));\n    }\n\n    @Test // autoApprove = true，但是不通过\n    public void testApproveOrDeny_autoApproveNo() {\n        // 准备参数\n        String responseType = \"code\";\n        String clientId = randomString();\n        String scope = \"{\\\"read\\\": true, \\\"write\\\": false}\";\n        String redirectUri = randomString();\n        String state = randomString();\n        // mock 方法\n        OAuth2ClientDO client = randomPojo(OAuth2ClientDO.class);\n        when(oauth2ClientService.validOAuthClientFromCache(eq(clientId), isNull(), eq(\"authorization_code\"),\n                eq(asSet(\"read\", \"write\")), eq(redirectUri))).thenReturn(client);\n\n        // 调用\n        CommonResult<String> result = oauth2OpenController.approveOrDeny(responseType, clientId,\n                scope, redirectUri, true, state);\n        // 断言\n        assertEquals(0, result.getCode());\n        assertNull(result.getData());\n    }\n\n    @Test // autoApprove = false，但是不通过\n    public void testApproveOrDeny_ApproveNo() {\n        // 准备参数\n        String responseType = \"token\";\n        String clientId = randomString();\n        String scope = \"{\\\"read\\\": true, \\\"write\\\": false}\";\n        String redirectUri = \"https://www.yixiang.co\";\n        String state = \"test\";\n        // mock 方法\n        OAuth2ClientDO client = randomPojo(OAuth2ClientDO.class);\n        when(oauth2ClientService.validOAuthClientFromCache(eq(clientId), isNull(), eq(\"implicit\"),\n                eq(asSet(\"read\", \"write\")), eq(redirectUri))).thenReturn(client);\n\n        // 调用\n        CommonResult<String> result = oauth2OpenController.approveOrDeny(responseType, clientId,\n                scope, redirectUri, false, state);\n        // 断言\n        assertEquals(0, result.getCode());\n        assertEquals(\"https://www.yixiang.co#error=access_denied&error_description=User%20denied%20access&state=test\", result.getData());\n    }\n\n    @Test // autoApprove = true，通过 + token\n    public void testApproveOrDeny_autoApproveWithToken() {\n        // 准备参数\n        String responseType = \"token\";\n        String clientId = randomString();\n        String scope = \"{\\\"read\\\": true, \\\"write\\\": false}\";\n        String redirectUri = \"https://www.yixiang.co\";\n        String state = \"test\";\n        // mock 方法（client)\n        OAuth2ClientDO client = randomPojo(OAuth2ClientDO.class).setClientId(clientId).setAdditionalInformation(null);\n        when(oauth2ClientService.validOAuthClientFromCache(eq(clientId), isNull(), eq(\"implicit\"),\n                eq(asSet(\"read\", \"write\")), eq(redirectUri))).thenReturn(client);\n        // mock 方法（场景一）\n        when(oauth2ApproveService.checkForPreApproval(isNull(), eq(UserTypeEnum.ADMIN.getValue()),\n                eq(clientId), eq(SetUtils.asSet(\"read\", \"write\")))).thenReturn(true);\n        // mock 方法（访问令牌）\n        OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class)\n                .setAccessToken(\"test_access_token\").setExpiresTime(LocalDateTimeUtil.offset(LocalDateTime.now(), 30010L, ChronoUnit.MILLIS));\n        when(oauth2GrantService.grantImplicit(isNull(), eq(UserTypeEnum.ADMIN.getValue()),\n                eq(clientId), eq(ListUtil.toList(\"read\")))).thenReturn(accessTokenDO);\n\n        // 调用\n        CommonResult<String> result = oauth2OpenController.approveOrDeny(responseType, clientId,\n                scope, redirectUri, true, state);\n        // 断言\n        assertEquals(0, result.getCode());\n        assertThat(result.getData(), anyOf( // 29 和 30 都有一定概率，主要是时间计算\n                is(\"https://www.yixiang.co#access_token=test_access_token&token_type=bearer&state=test&expires_in=29&scope=read\"),\n                is(\"https://www.yixiang.co#access_token=test_access_token&token_type=bearer&state=test&expires_in=30&scope=read\")\n        ));\n    }\n\n    @Test // autoApprove = false，通过 + code\n    public void testApproveOrDeny_approveWithCode() {\n        // 准备参数\n        String responseType = \"code\";\n        String clientId = randomString();\n        String scope = \"{\\\"read\\\": true, \\\"write\\\": false}\";\n        String redirectUri = \"https://www.yixiang.co\";\n        String state = \"test\";\n        // mock 方法（client)\n        OAuth2ClientDO client = randomPojo(OAuth2ClientDO.class).setClientId(clientId).setAdditionalInformation(null);\n        when(oauth2ClientService.validOAuthClientFromCache(eq(clientId), isNull(), eq(\"authorization_code\"),\n                eq(asSet(\"read\", \"write\")), eq(redirectUri))).thenReturn(client);\n        // mock 方法（场景二）\n        when(oauth2ApproveService.updateAfterApproval(isNull(), eq(UserTypeEnum.ADMIN.getValue()), eq(clientId),\n                eq(MapUtil.builder(new LinkedHashMap<String, Boolean>()).put(\"read\", true).put(\"write\", false).build())))\n                .thenReturn(true);\n        // mock 方法（访问令牌）\n        String authorizationCode = \"test_code\";\n        when(oauth2GrantService.grantAuthorizationCodeForCode(isNull(), eq(UserTypeEnum.ADMIN.getValue()),\n                eq(clientId), eq(ListUtil.toList(\"read\")), eq(redirectUri), eq(state))).thenReturn(authorizationCode);\n\n        // 调用\n        CommonResult<String> result = oauth2OpenController.approveOrDeny(responseType, clientId,\n                scope, redirectUri, false, state);\n        // 断言\n        assertEquals(0, result.getCode());\n        assertEquals(\"https://www.yixiang.co?code=test_code&state=test\", result.getData());\n    }\n\n    private HttpServletRequest mockRequest(String clientId, String secret) {\n        HttpServletRequest request = mock(HttpServletRequest.class);\n        when(request.getParameter(eq(\"client_id\"))).thenReturn(clientId);\n        when(request.getParameter(eq(\"client_secret\"))).thenReturn(secret);\n        return request;\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/java/co/yixiang/yshop/module/system/framework/sms/core/client/impl/AliyunSmsClientTest.java",
    "content": "package co.yixiang.yshop.module.system.framework.sms.core.client.impl;\n\nimport cn.hutool.core.util.ReflectUtil;\nimport co.yixiang.yshop.framework.common.core.KeyValue;\nimport co.yixiang.yshop.framework.common.util.collection.MapUtils;\nimport co.yixiang.yshop.framework.test.core.ut.BaseMockitoUnitTest;\nimport co.yixiang.yshop.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;\nimport co.yixiang.yshop.module.system.framework.sms.core.client.dto.SmsSendRespDTO;\nimport co.yixiang.yshop.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO;\nimport co.yixiang.yshop.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;\nimport co.yixiang.yshop.module.system.framework.sms.core.property.SmsChannelProperties;\nimport com.aliyuncs.IAcsClient;\nimport com.aliyuncs.dysmsapi.model.v20170525.QuerySmsTemplateRequest;\nimport com.aliyuncs.dysmsapi.model.v20170525.QuerySmsTemplateResponse;\nimport com.aliyuncs.dysmsapi.model.v20170525.SendSmsRequest;\nimport com.aliyuncs.dysmsapi.model.v20170525.SendSmsResponse;\nimport com.google.common.collect.Lists;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.ArgumentMatcher;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\n\nimport java.time.LocalDateTime;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.util.json.JsonUtils.toJsonString;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.*;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.ArgumentMatchers.argThat;\nimport static org.mockito.Mockito.when;\n\n/**\n * {@link AliyunSmsClient} 的单元测试\n *\n * @author yshop\n */\npublic class AliyunSmsClientTest extends BaseMockitoUnitTest {\n\n    private final SmsChannelProperties properties = new SmsChannelProperties()\n            .setApiKey(randomString()) // 随机一个 apiKey，避免构建报错\n            .setApiSecret(randomString()) // 随机一个 apiSecret，避免构建报错\n            .setSignature(\"yshop\");\n\n    @InjectMocks\n    private final AliyunSmsClient smsClient = new AliyunSmsClient(properties);\n\n    @Mock\n    private IAcsClient client;\n\n    @Test\n    public void testDoInit() {\n        // 准备参数\n        // mock 方法\n\n        // 调用\n        smsClient.doInit();\n        // 断言\n        assertNotSame(client, ReflectUtil.getFieldValue(smsClient, \"acsClient\"));\n    }\n\n    @Test\n    public void tesSendSms_success() throws Throwable {\n        // 准备参数\n        Long sendLogId = randomLongId();\n        String mobile = randomString();\n        String apiTemplateId = randomString();\n        List<KeyValue<String, Object>> templateParams = Lists.newArrayList(\n                new KeyValue<>(\"code\", 1234), new KeyValue<>(\"op\", \"login\"));\n        // mock 方法\n        SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> o.setCode(\"OK\"));\n        when(client.getAcsResponse(argThat((ArgumentMatcher<SendSmsRequest>) acsRequest -> {\n            assertEquals(mobile, acsRequest.getPhoneNumbers());\n            assertEquals(properties.getSignature(), acsRequest.getSignName());\n            assertEquals(apiTemplateId, acsRequest.getTemplateCode());\n            assertEquals(toJsonString(MapUtils.convertMap(templateParams)), acsRequest.getTemplateParam());\n            assertEquals(sendLogId.toString(), acsRequest.getOutId());\n            return true;\n        }))).thenReturn(response);\n\n        // 调用\n        SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile,\n                apiTemplateId, templateParams);\n        // 断言\n        assertTrue(result.getSuccess());\n        assertEquals(response.getRequestId(), result.getApiRequestId());\n        assertEquals(response.getCode(), result.getApiCode());\n        assertEquals(response.getMessage(), result.getApiMsg());\n        assertEquals(response.getBizId(), result.getSerialNo());\n    }\n\n    @Test\n    public void tesSendSms_fail() throws Throwable {\n        // 准备参数\n        Long sendLogId = randomLongId();\n        String mobile = randomString();\n        String apiTemplateId = randomString();\n        List<KeyValue<String, Object>> templateParams = Lists.newArrayList(\n                new KeyValue<>(\"code\", 1234), new KeyValue<>(\"op\", \"login\"));\n        // mock 方法\n        SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> o.setCode(\"ERROR\"));\n        when(client.getAcsResponse(argThat((ArgumentMatcher<SendSmsRequest>) acsRequest -> {\n            assertEquals(mobile, acsRequest.getPhoneNumbers());\n            assertEquals(properties.getSignature(), acsRequest.getSignName());\n            assertEquals(apiTemplateId, acsRequest.getTemplateCode());\n            assertEquals(toJsonString(MapUtils.convertMap(templateParams)), acsRequest.getTemplateParam());\n            assertEquals(sendLogId.toString(), acsRequest.getOutId());\n            return true;\n        }))).thenReturn(response);\n\n        // 调用\n        SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams);\n        // 断言\n        assertFalse(result.getSuccess());\n        assertEquals(response.getRequestId(), result.getApiRequestId());\n        assertEquals(response.getCode(), result.getApiCode());\n        assertEquals(response.getMessage(), result.getApiMsg());\n        assertEquals(response.getBizId(), result.getSerialNo());\n    }\n\n    @Test\n    public void testParseSmsReceiveStatus() {\n        // 准备参数\n        String text = \"[\\n\" +\n                \"  {\\n\" +\n                \"    \\\"phone_number\\\" : \\\"13900000001\\\",\\n\" +\n                \"    \\\"send_time\\\" : \\\"2017-01-01 11:12:13\\\",\\n\" +\n                \"    \\\"report_time\\\" : \\\"2017-02-02 22:23:24\\\",\\n\" +\n                \"    \\\"success\\\" : true,\\n\" +\n                \"    \\\"err_code\\\" : \\\"DELIVERED\\\",\\n\" +\n                \"    \\\"err_msg\\\" : \\\"用户接收成功\\\",\\n\" +\n                \"    \\\"sms_size\\\" : \\\"1\\\",\\n\" +\n                \"    \\\"biz_id\\\" : \\\"12345\\\",\\n\" +\n                \"    \\\"out_id\\\" : \\\"67890\\\"\\n\" +\n                \"  }\\n\" +\n                \"]\";\n        // mock 方法\n\n        // 调用\n        List<SmsReceiveRespDTO> statuses = smsClient.parseSmsReceiveStatus(text);\n        // 断言\n        assertEquals(1, statuses.size());\n        assertTrue(statuses.get(0).getSuccess());\n        assertEquals(\"DELIVERED\", statuses.get(0).getErrorCode());\n        assertEquals(\"用户接收成功\", statuses.get(0).getErrorMsg());\n        assertEquals(\"13900000001\", statuses.get(0).getMobile());\n        assertEquals(LocalDateTime.of(2017, 2, 2, 22, 23, 24),\n                statuses.get(0).getReceiveTime());\n        assertEquals(\"12345\", statuses.get(0).getSerialNo());\n        assertEquals(67890L, statuses.get(0).getLogId());\n    }\n\n    @Test\n    public void testGetSmsTemplate() throws Throwable {\n        // 准备参数\n        String apiTemplateId = randomString();\n        // mock 方法\n        QuerySmsTemplateResponse response = randomPojo(QuerySmsTemplateResponse.class, o -> {\n            o.setCode(\"OK\");\n            o.setTemplateStatus(1); // 设置模板通过\n        });\n        when(client.getAcsResponse(argThat((ArgumentMatcher<QuerySmsTemplateRequest>) acsRequest -> {\n            assertEquals(apiTemplateId, acsRequest.getTemplateCode());\n            return true;\n        }))).thenReturn(response);\n\n        // 调用\n        SmsTemplateRespDTO result = smsClient.getSmsTemplate(apiTemplateId);\n        // 断言\n        assertEquals(response.getTemplateCode(), result.getId());\n        assertEquals(response.getTemplateContent(), result.getContent());\n        assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getAuditStatus());\n        assertEquals(response.getReason(), result.getAuditReason());\n    }\n\n    @Test\n    public void testConvertSmsTemplateAuditStatus() {\n        assertEquals(SmsTemplateAuditStatusEnum.CHECKING.getStatus(),\n                smsClient.convertSmsTemplateAuditStatus(0));\n        assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(),\n                smsClient.convertSmsTemplateAuditStatus(1));\n        assertEquals(SmsTemplateAuditStatusEnum.FAIL.getStatus(),\n                smsClient.convertSmsTemplateAuditStatus(2));\n        assertThrows(IllegalArgumentException.class, () -> smsClient.convertSmsTemplateAuditStatus(3),\n                \"未知审核状态(3)\");\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/java/co/yixiang/yshop/module/system/framework/sms/core/client/impl/TencentSmsClientTest.java",
    "content": "package co.yixiang.yshop.module.system.framework.sms.core.client.impl;\n\nimport cn.hutool.core.util.ReflectUtil;\nimport co.yixiang.yshop.framework.common.core.KeyValue;\nimport co.yixiang.yshop.framework.common.util.collection.ArrayUtils;\nimport co.yixiang.yshop.framework.common.util.collection.MapUtils;\nimport co.yixiang.yshop.framework.common.util.json.JsonUtils;\nimport co.yixiang.yshop.framework.test.core.ut.BaseMockitoUnitTest;\nimport co.yixiang.yshop.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;\nimport co.yixiang.yshop.module.system.framework.sms.core.client.dto.SmsSendRespDTO;\nimport co.yixiang.yshop.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO;\nimport co.yixiang.yshop.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;\nimport co.yixiang.yshop.module.system.framework.sms.core.property.SmsChannelProperties;\nimport com.google.common.collect.Lists;\nimport com.tencentcloudapi.sms.v20210111.SmsClient;\nimport com.tencentcloudapi.sms.v20210111.models.DescribeSmsTemplateListResponse;\nimport com.tencentcloudapi.sms.v20210111.models.DescribeTemplateListStatus;\nimport com.tencentcloudapi.sms.v20210111.models.SendSmsResponse;\nimport com.tencentcloudapi.sms.v20210111.models.SendStatus;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\n\nimport java.time.LocalDateTime;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.util.json.JsonUtils.toJsonString;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.*;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.ArgumentMatchers.argThat;\nimport static org.mockito.Mockito.when;\n\n/**\n * {@link TencentSmsClient} 的单元测试\n *\n * @author shiwp\n */\npublic class TencentSmsClientTest extends BaseMockitoUnitTest {\n\n    private final SmsChannelProperties properties = new SmsChannelProperties()\n            .setApiKey(randomString() + \" \" + randomString()) // 随机一个 apiKey，避免构建报错\n            .setApiSecret(randomString()) // 随机一个 apiSecret，避免构建报错\n            .setSignature(\"yshop\");\n\n    @InjectMocks\n    private TencentSmsClient smsClient = new TencentSmsClient(properties);\n\n    @Mock\n    private SmsClient client;\n\n    @Test\n    public void testDoInit() {\n        // 准备参数\n        // mock 方法\n\n        // 调用\n        smsClient.doInit();\n        // 断言\n        assertNotSame(client, ReflectUtil.getFieldValue(smsClient, \"client\"));\n    }\n\n    @Test\n    public void testRefresh() {\n        // 准备参数\n        SmsChannelProperties p = new SmsChannelProperties()\n                .setApiKey(randomString() + \" \" + randomString()) // 随机一个 apiKey，避免构建报错\n                .setApiSecret(randomString()) // 随机一个 apiSecret，避免构建报错\n                .setSignature(\"yshop\");\n        // 调用\n        smsClient.refresh(p);\n        // 断言\n        assertNotSame(client, ReflectUtil.getFieldValue(smsClient, \"client\"));\n    }\n\n    @Test\n    public void testDoSendSms_success() throws Throwable {\n        // 准备参数\n        Long sendLogId = randomLongId();\n        String mobile = randomString();\n        String apiTemplateId = randomString();\n        List<KeyValue<String, Object>> templateParams = Lists.newArrayList(\n                new KeyValue<>(\"1\", 1234), new KeyValue<>(\"2\", \"login\"));\n        String requestId = randomString();\n        String serialNo = randomString();\n        // mock 方法\n        SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> {\n            o.setRequestId(requestId);\n            SendStatus[] sendStatuses = new SendStatus[1];\n            o.setSendStatusSet(sendStatuses);\n            SendStatus sendStatus = new SendStatus();\n            sendStatuses[0] = sendStatus;\n            sendStatus.setCode(TencentSmsClient.API_CODE_SUCCESS);\n            sendStatus.setMessage(\"send success\");\n            sendStatus.setSerialNo(serialNo);\n        });\n        when(client.SendSms(argThat(request -> {\n            assertEquals(mobile, request.getPhoneNumberSet()[0]);\n            assertEquals(properties.getSignature(), request.getSignName());\n            assertEquals(apiTemplateId, request.getTemplateId());\n            assertEquals(toJsonString(ArrayUtils.toArray(new ArrayList<>(MapUtils.convertMap(templateParams).values()), String::valueOf)),\n                    toJsonString(request.getTemplateParamSet()));\n            assertEquals(sendLogId, ReflectUtil.getFieldValue(JsonUtils.parseObject(request.getSessionContext(), TencentSmsClient.SessionContext.class), \"logId\"));\n            return true;\n        }))).thenReturn(response);\n\n        // 调用\n        SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams);\n        // 断言\n        assertTrue(result.getSuccess());\n        assertEquals(response.getRequestId(), result.getApiRequestId());\n        assertEquals(response.getSendStatusSet()[0].getCode(), result.getApiCode());\n        assertEquals(response.getSendStatusSet()[0].getMessage(), result.getApiMsg());\n        assertEquals(response.getSendStatusSet()[0].getSerialNo(), result.getSerialNo());\n    }\n\n    @Test\n    public void testDoSendSms_fail() throws Throwable {\n        // 准备参数\n        Long sendLogId = randomLongId();\n        String mobile = randomString();\n        String apiTemplateId = randomString();\n        List<KeyValue<String, Object>> templateParams = Lists.newArrayList(\n                new KeyValue<>(\"1\", 1234), new KeyValue<>(\"2\", \"login\"));\n        String requestId = randomString();\n        String serialNo = randomString();\n        // mock 方法\n        SendSmsResponse response = randomPojo(SendSmsResponse.class, o -> {\n            o.setRequestId(requestId);\n            SendStatus[] sendStatuses = new SendStatus[1];\n            o.setSendStatusSet(sendStatuses);\n            SendStatus sendStatus = new SendStatus();\n            sendStatuses[0] = sendStatus;\n            sendStatus.setCode(\"ERROR\");\n            sendStatus.setMessage(\"send success\");\n            sendStatus.setSerialNo(serialNo);\n        });\n        when(client.SendSms(argThat(request -> {\n            assertEquals(mobile, request.getPhoneNumberSet()[0]);\n            assertEquals(properties.getSignature(), request.getSignName());\n            assertEquals(apiTemplateId, request.getTemplateId());\n            assertEquals(toJsonString(ArrayUtils.toArray(new ArrayList<>(MapUtils.convertMap(templateParams).values()), String::valueOf)),\n                    toJsonString(request.getTemplateParamSet()));\n            assertEquals(sendLogId, ReflectUtil.getFieldValue(JsonUtils.parseObject(request.getSessionContext(), TencentSmsClient.SessionContext.class), \"logId\"));\n            return true;\n        }))).thenReturn(response);\n\n        // 调用\n        SmsSendRespDTO result = smsClient.sendSms(sendLogId, mobile, apiTemplateId, templateParams);\n        // 断言\n        assertFalse(result.getSuccess());\n        assertEquals(response.getRequestId(), result.getApiRequestId());\n        assertEquals(response.getSendStatusSet()[0].getCode(), result.getApiCode());\n        assertEquals(response.getSendStatusSet()[0].getMessage(), result.getApiMsg());\n        assertEquals(response.getSendStatusSet()[0].getSerialNo(), result.getSerialNo());\n    }\n\n    @Test\n    public void testParseSmsReceiveStatus() {\n        // 准备参数\n        String text = \"[\\n\" +\n                \"    {\\n\" +\n                \"        \\\"user_receive_time\\\": \\\"2015-10-17 08:03:04\\\",\\n\" +\n                \"        \\\"nationcode\\\": \\\"86\\\",\\n\" +\n                \"        \\\"mobile\\\": \\\"13900000001\\\",\\n\" +\n                \"        \\\"report_status\\\": \\\"SUCCESS\\\",\\n\" +\n                \"        \\\"errmsg\\\": \\\"DELIVRD\\\",\\n\" +\n                \"        \\\"description\\\": \\\"用户短信送达成功\\\",\\n\" +\n                \"        \\\"sid\\\": \\\"12345\\\",\\n\" +\n                \"        \\\"ext\\\": {\\\"logId\\\":\\\"67890\\\"}\\n\" +\n                \"    }\\n\" +\n                \"]\";\n        // mock 方法\n\n        // 调用\n        List<SmsReceiveRespDTO> statuses = smsClient.parseSmsReceiveStatus(text);\n        // 断言\n        assertEquals(1, statuses.size());\n        assertTrue(statuses.get(0).getSuccess());\n        assertEquals(\"DELIVRD\", statuses.get(0).getErrorCode());\n        assertEquals(\"用户短信送达成功\", statuses.get(0).getErrorMsg());\n        assertEquals(\"13900000001\", statuses.get(0).getMobile());\n        assertEquals(LocalDateTime.of(2015, 10, 17, 8, 3, 4), statuses.get(0).getReceiveTime());\n        assertEquals(\"12345\", statuses.get(0).getSerialNo());\n        assertEquals(67890L, statuses.get(0).getLogId());\n    }\n\n    @Test\n    public void testGetSmsTemplate() throws Throwable {\n        // 准备参数\n        Long apiTemplateId = randomLongId();\n        String requestId = randomString();\n\n        // mock 方法\n        DescribeSmsTemplateListResponse response = randomPojo(DescribeSmsTemplateListResponse.class, o -> {\n            DescribeTemplateListStatus[] describeTemplateListStatuses = new DescribeTemplateListStatus[1];\n            DescribeTemplateListStatus templateStatus = new DescribeTemplateListStatus();\n            templateStatus.setTemplateId(apiTemplateId);\n            templateStatus.setStatusCode(0L);// 设置模板通过\n            describeTemplateListStatuses[0] = templateStatus;\n            o.setDescribeTemplateStatusSet(describeTemplateListStatuses);\n            o.setRequestId(requestId);\n        });\n        when(client.DescribeSmsTemplateList(argThat(request -> {\n            assertEquals(apiTemplateId, request.getTemplateIdSet()[0]);\n            return true;\n        }))).thenReturn(response);\n\n        // 调用\n        SmsTemplateRespDTO result = smsClient.getSmsTemplate(apiTemplateId.toString());\n        // 断言\n        assertEquals(response.getDescribeTemplateStatusSet()[0].getTemplateId().toString(), result.getId());\n        assertEquals(response.getDescribeTemplateStatusSet()[0].getTemplateContent(), result.getContent());\n        assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(), result.getAuditStatus());\n        assertEquals(response.getDescribeTemplateStatusSet()[0].getReviewReply(), result.getAuditReason());\n    }\n\n    @Test\n    public void testConvertSmsTemplateAuditStatus() {\n        assertEquals(SmsTemplateAuditStatusEnum.SUCCESS.getStatus(),\n                smsClient.convertSmsTemplateAuditStatus(0));\n        assertEquals(SmsTemplateAuditStatusEnum.CHECKING.getStatus(),\n                smsClient.convertSmsTemplateAuditStatus(1));\n        assertEquals(SmsTemplateAuditStatusEnum.FAIL.getStatus(),\n                smsClient.convertSmsTemplateAuditStatus(-1));\n        assertThrows(IllegalArgumentException.class, () -> smsClient.convertSmsTemplateAuditStatus(3),\n                \"未知审核状态(3)\");\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/java/co/yixiang/yshop/module/system/service/auth/AdminAuthServiceImplTest.java",
    "content": "package co.yixiang.yshop.module.system.service.auth;\n\nimport cn.hutool.core.util.ReflectUtil;\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.test.core.ut.BaseDbUnitTest;\nimport co.yixiang.yshop.module.system.api.sms.SmsCodeApi;\nimport co.yixiang.yshop.module.system.api.social.dto.SocialUserBindReqDTO;\nimport co.yixiang.yshop.module.system.api.social.dto.SocialUserRespDTO;\nimport co.yixiang.yshop.module.system.controller.admin.auth.vo.*;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.user.AdminUserDO;\nimport co.yixiang.yshop.module.system.enums.logger.LoginLogTypeEnum;\nimport co.yixiang.yshop.module.system.enums.logger.LoginResultEnum;\nimport co.yixiang.yshop.module.system.enums.sms.SmsSceneEnum;\nimport co.yixiang.yshop.module.system.enums.social.SocialTypeEnum;\nimport co.yixiang.yshop.module.system.service.logger.LoginLogService;\nimport co.yixiang.yshop.module.system.service.member.MemberService;\nimport co.yixiang.yshop.module.system.service.oauth2.OAuth2TokenService;\nimport co.yixiang.yshop.module.system.service.social.SocialUserService;\nimport co.yixiang.yshop.module.system.service.user.AdminUserService;\nimport com.xingyuv.captcha.model.common.ResponseModel;\nimport com.xingyuv.captcha.service.CaptchaService;\nimport jakarta.annotation.Resource;\nimport jakarta.validation.ConstraintViolationException;\nimport jakarta.validation.Validation;\nimport jakarta.validation.Validator;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.mock.mockito.MockBean;\nimport org.springframework.context.annotation.Import;\n\nimport static cn.hutool.core.util.RandomUtil.randomEle;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertPojoEquals;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertServiceException;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.randomPojo;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.randomString;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.*;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertThrows;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.*;\n\n@Import(AdminAuthServiceImpl.class)\npublic class AdminAuthServiceImplTest extends BaseDbUnitTest {\n\n    @Resource\n    private AdminAuthServiceImpl authService;\n\n    @MockBean\n    private AdminUserService userService;\n    @MockBean\n    private CaptchaService captchaService;\n    @MockBean\n    private LoginLogService loginLogService;\n    @MockBean\n    private SocialUserService socialUserService;\n    @MockBean\n    private SmsCodeApi smsCodeApi;\n    @MockBean\n    private OAuth2TokenService oauth2TokenService;\n    @MockBean\n    private MemberService memberService;\n    @MockBean\n    private Validator validator;\n\n    @BeforeEach\n    public void setUp() {\n        ReflectUtil.setFieldValue(authService, \"captchaEnable\", true);\n        // 注入一个 Validator 对象\n        ReflectUtil.setFieldValue(authService, \"validator\",\n                Validation.buildDefaultValidatorFactory().getValidator());\n    }\n\n    @Test\n    public void testAuthenticate_success() {\n        // 准备参数\n        String username = randomString();\n        String password = randomString();\n        // mock user 数据\n        AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setUsername(username)\n                .setPassword(password).setStatus(CommonStatusEnum.ENABLE.getStatus()));\n        when(userService.getUserByUsername(eq(username))).thenReturn(user);\n        // mock password 匹配\n        when(userService.isPasswordMatch(eq(password), eq(user.getPassword()))).thenReturn(true);\n\n        // 调用\n        AdminUserDO loginUser = authService.authenticate(username, password);\n        // 校验\n        assertPojoEquals(user, loginUser);\n    }\n\n    @Test\n    public void testAuthenticate_userNotFound() {\n        // 准备参数\n        String username = randomString();\n        String password = randomString();\n\n        // 调用, 并断言异常\n        assertServiceException(() -> authService.authenticate(username, password),\n                AUTH_LOGIN_BAD_CREDENTIALS);\n        verify(loginLogService).createLoginLog(\n                argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())\n                        && o.getResult().equals(LoginResultEnum.BAD_CREDENTIALS.getResult())\n                        && o.getUserId() == null)\n        );\n    }\n\n    @Test\n    public void testAuthenticate_badCredentials() {\n        // 准备参数\n        String username = randomString();\n        String password = randomString();\n        // mock user 数据\n        AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setUsername(username)\n                .setPassword(password).setStatus(CommonStatusEnum.ENABLE.getStatus()));\n        when(userService.getUserByUsername(eq(username))).thenReturn(user);\n\n        // 调用, 并断言异常\n        assertServiceException(() -> authService.authenticate(username, password),\n                AUTH_LOGIN_BAD_CREDENTIALS);\n        verify(loginLogService).createLoginLog(\n                argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())\n                        && o.getResult().equals(LoginResultEnum.BAD_CREDENTIALS.getResult())\n                        && o.getUserId().equals(user.getId()))\n        );\n    }\n\n    @Test\n    public void testAuthenticate_userDisabled() {\n        // 准备参数\n        String username = randomString();\n        String password = randomString();\n        // mock user 数据\n        AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setUsername(username)\n                .setPassword(password).setStatus(CommonStatusEnum.DISABLE.getStatus()));\n        when(userService.getUserByUsername(eq(username))).thenReturn(user);\n        // mock password 匹配\n        when(userService.isPasswordMatch(eq(password), eq(user.getPassword()))).thenReturn(true);\n\n        // 调用, 并断言异常\n        assertServiceException(() -> authService.authenticate(username, password),\n                AUTH_LOGIN_USER_DISABLED);\n        verify(loginLogService).createLoginLog(\n                argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())\n                        && o.getResult().equals(LoginResultEnum.USER_DISABLED.getResult())\n                        && o.getUserId().equals(user.getId()))\n        );\n    }\n\n    @Test\n    public void testLogin_success() {\n        // 准备参数\n        AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class, o ->\n                o.setUsername(\"test_username\").setPassword(\"test_password\")\n                        .setSocialType(randomEle(SocialTypeEnum.values()).getType()));\n\n        // mock 验证码正确\n        ReflectUtil.setFieldValue(authService, \"captchaEnable\", false);\n        // mock user 数据\n        AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setId(1L).setUsername(\"test_username\")\n                .setPassword(\"test_password\").setStatus(CommonStatusEnum.ENABLE.getStatus()));\n        when(userService.getUserByUsername(eq(\"test_username\"))).thenReturn(user);\n        // mock password 匹配\n        when(userService.isPasswordMatch(eq(\"test_password\"), eq(user.getPassword()))).thenReturn(true);\n        // mock 缓存登录用户到 Redis\n        OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class, o -> o.setUserId(1L)\n                .setUserType(UserTypeEnum.ADMIN.getValue()));\n        when(oauth2TokenService.createAccessToken(eq(1L), eq(UserTypeEnum.ADMIN.getValue()), eq(\"default\"), isNull()))\n                .thenReturn(accessTokenDO);\n\n        // 调用，并校验\n        AuthLoginRespVO loginRespVO = authService.login(reqVO);\n        assertPojoEquals(accessTokenDO, loginRespVO);\n        // 校验调用参数\n        verify(loginLogService).createLoginLog(\n                argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())\n                        && o.getResult().equals(LoginResultEnum.SUCCESS.getResult())\n                        && o.getUserId().equals(user.getId()))\n        );\n        verify(socialUserService).bindSocialUser(eq(new SocialUserBindReqDTO(\n                user.getId(), UserTypeEnum.ADMIN.getValue(),\n                reqVO.getSocialType(), reqVO.getSocialCode(), reqVO.getSocialState())));\n    }\n\n    @Test\n    public void testSendSmsCode() {\n        // 准备参数\n        String mobile = randomString();\n        Integer scene = randomEle(SmsSceneEnum.values()).getScene();\n        AuthSmsSendReqVO reqVO = new AuthSmsSendReqVO(mobile, scene);\n        // mock 方法（用户信息）\n        AdminUserDO user = randomPojo(AdminUserDO.class);\n        when(userService.getUserByMobile(eq(mobile))).thenReturn(user);\n\n        // 调用\n        authService.sendSmsCode(reqVO);\n        // 断言\n        verify(smsCodeApi).sendSmsCode(argThat(sendReqDTO -> {\n            assertEquals(mobile, sendReqDTO.getMobile());\n            assertEquals(scene, sendReqDTO.getScene());\n            return true;\n        }));\n    }\n\n    @Test\n    public void testSmsLogin_success() {\n        // 准备参数\n        String mobile = randomString();\n        String code = randomString();\n        AuthSmsLoginReqVO reqVO = new AuthSmsLoginReqVO(mobile, code);\n        // mock 方法（用户信息）\n        AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setId(1L));\n        when(userService.getUserByMobile(eq(mobile))).thenReturn(user);\n        // mock 缓存登录用户到 Redis\n        OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class, o -> o.setUserId(1L)\n                .setUserType(UserTypeEnum.ADMIN.getValue()));\n        when(oauth2TokenService.createAccessToken(eq(1L), eq(UserTypeEnum.ADMIN.getValue()), eq(\"default\"), isNull()))\n                .thenReturn(accessTokenDO);\n\n        // 调用，并断言\n        AuthLoginRespVO loginRespVO = authService.smsLogin(reqVO);\n        assertPojoEquals(accessTokenDO, loginRespVO);\n        // 断言调用\n        verify(loginLogService).createLoginLog(\n                argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_MOBILE.getType())\n                        && o.getResult().equals(LoginResultEnum.SUCCESS.getResult())\n                        && o.getUserId().equals(user.getId()))\n        );\n    }\n\n    @Test\n    public void testSocialLogin_success() {\n        // 准备参数\n        AuthSocialLoginReqVO reqVO = randomPojo(AuthSocialLoginReqVO.class);\n        // mock 方法（绑定的用户编号）\n        Long userId = 1L;\n        when(socialUserService.getSocialUserByCode(eq(UserTypeEnum.ADMIN.getValue()), eq(reqVO.getType()),\n                eq(reqVO.getCode()), eq(reqVO.getState()))).thenReturn(new SocialUserRespDTO(randomString(), randomString(), randomString(), userId));\n        // mock（用户）\n        AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setId(userId));\n        when(userService.getUser(eq(userId))).thenReturn(user);\n        // mock 缓存登录用户到 Redis\n        OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class, o -> o.setUserId(1L)\n                .setUserType(UserTypeEnum.ADMIN.getValue()));\n        when(oauth2TokenService.createAccessToken(eq(1L), eq(UserTypeEnum.ADMIN.getValue()), eq(\"default\"), isNull()))\n                .thenReturn(accessTokenDO);\n\n        // 调用，并断言\n        AuthLoginRespVO loginRespVO = authService.socialLogin(reqVO);\n        assertPojoEquals(accessTokenDO, loginRespVO);\n        // 断言调用\n        verify(loginLogService).createLoginLog(\n                argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_SOCIAL.getType())\n                        && o.getResult().equals(LoginResultEnum.SUCCESS.getResult())\n                        && o.getUserId().equals(user.getId()))\n        );\n    }\n\n    @Test\n    public void testValidateCaptcha_successWithEnable() {\n        // 准备参数\n        AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class);\n\n        // mock 验证码打开\n        ReflectUtil.setFieldValue(authService, \"captchaEnable\", true);\n        // mock 验证通过\n        when(captchaService.verification(argThat(captchaVO -> {\n            assertEquals(reqVO.getCaptchaVerification(), captchaVO.getCaptchaVerification());\n            return true;\n        }))).thenReturn(ResponseModel.success());\n\n        // 调用，无需断言\n        authService.validateCaptcha(reqVO);\n    }\n\n    @Test\n    public void testValidateCaptcha_successWithDisable() {\n        // 准备参数\n        AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class);\n\n        // mock 验证码关闭\n        ReflectUtil.setFieldValue(authService, \"captchaEnable\", false);\n\n        // 调用，无需断言\n        authService.validateCaptcha(reqVO);\n    }\n\n    @Test\n    public void testValidateCaptcha_constraintViolationException() {\n        // 准备参数\n        AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class).setCaptchaVerification(null);\n\n        // mock 验证码打开\n        ReflectUtil.setFieldValue(authService, \"captchaEnable\", true);\n\n        // 调用，并断言异常\n        assertThrows(ConstraintViolationException.class, () -> authService.validateCaptcha(reqVO),\n                \"验证码不能为空\");\n    }\n\n\n    @Test\n    public void testCaptcha_fail() {\n        // 准备参数\n        AuthLoginReqVO reqVO = randomPojo(AuthLoginReqVO.class);\n\n        // mock 验证码打开\n        ReflectUtil.setFieldValue(authService, \"captchaEnable\", true);\n        // mock 验证通过\n        when(captchaService.verification(argThat(captchaVO -> {\n            assertEquals(reqVO.getCaptchaVerification(), captchaVO.getCaptchaVerification());\n            return true;\n        }))).thenReturn(ResponseModel.errorMsg(\"就是不对\"));\n\n        // 调用, 并断言异常\n        assertServiceException(() -> authService.validateCaptcha(reqVO), AUTH_LOGIN_CAPTCHA_CODE_ERROR, \"就是不对\");\n        // 校验调用参数\n        verify(loginLogService).createLoginLog(\n                argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGIN_USERNAME.getType())\n                        && o.getResult().equals(LoginResultEnum.CAPTCHA_CODE_ERROR.getResult()))\n        );\n    }\n\n    @Test\n    public void testRefreshToken() {\n        // 准备参数\n        String refreshToken = randomString();\n        // mock 方法\n        OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class);\n        when(oauth2TokenService.refreshAccessToken(eq(refreshToken), eq(\"default\")))\n                .thenReturn(accessTokenDO);\n\n        // 调用\n        AuthLoginRespVO loginRespVO = authService.refreshToken(refreshToken);\n        // 断言\n        assertPojoEquals(accessTokenDO, loginRespVO);\n    }\n\n    @Test\n    public void testLogout_success() {\n        // 准备参数\n        String token = randomString();\n        // mock\n        OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class, o -> o.setUserId(1L)\n                .setUserType(UserTypeEnum.ADMIN.getValue()));\n        when(oauth2TokenService.removeAccessToken(eq(token))).thenReturn(accessTokenDO);\n\n        // 调用\n        authService.logout(token, LoginLogTypeEnum.LOGOUT_SELF.getType());\n        // 校验调用参数\n        verify(loginLogService).createLoginLog(\n                argThat(o -> o.getLogType().equals(LoginLogTypeEnum.LOGOUT_SELF.getType())\n                        && o.getResult().equals(LoginResultEnum.SUCCESS.getResult()))\n        );\n        // 调用，并校验\n\n    }\n\n    @Test\n    public void testLogout_fail() {\n        // 准备参数\n        String token = randomString();\n\n        // 调用\n        authService.logout(token, LoginLogTypeEnum.LOGOUT_SELF.getType());\n        // 校验调用参数\n        verify(loginLogService, never()).createLoginLog(any());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/java/co/yixiang/yshop/module/system/service/dept/DeptServiceImplTest.java",
    "content": "package co.yixiang.yshop.module.system.service.dept;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.util.object.ObjectUtils;\nimport co.yixiang.yshop.framework.test.core.ut.BaseDbUnitTest;\nimport co.yixiang.yshop.module.system.controller.admin.dept.vo.dept.DeptListReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.dept.vo.dept.DeptSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.dept.DeptDO;\nimport co.yixiang.yshop.module.system.dal.mysql.dept.DeptMapper;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.context.annotation.Import;\n\nimport jakarta.annotation.Resource;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.Set;\n\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertPojoEquals;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertServiceException;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.*;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.*;\nimport static java.util.Collections.singletonList;\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * {@link DeptServiceImpl} 的单元测试类\n *\n * @author niudehua\n */\n@Import(DeptServiceImpl.class)\npublic class DeptServiceImplTest extends BaseDbUnitTest {\n\n    @Resource\n    private DeptServiceImpl deptService;\n    @Resource\n    private DeptMapper deptMapper;\n\n    @Test\n    public void testCreateDept() {\n        // 准备参数\n        DeptSaveReqVO reqVO = randomPojo(DeptSaveReqVO.class, o -> {\n            o.setId(null); // 防止 id 被设置\n            o.setParentId(DeptDO.PARENT_ID_ROOT);\n            o.setStatus(randomCommonStatus());\n        });\n\n        // 调用\n        Long deptId = deptService.createDept(reqVO);\n        // 断言\n        assertNotNull(deptId);\n        // 校验记录的属性是否正确\n        DeptDO deptDO = deptMapper.selectById(deptId);\n        assertPojoEquals(reqVO, deptDO, \"id\");\n    }\n\n    @Test\n    public void testUpdateDept() {\n        // mock 数据\n        DeptDO dbDeptDO = randomPojo(DeptDO.class, o -> o.setStatus(randomCommonStatus()));\n        deptMapper.insert(dbDeptDO);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        DeptSaveReqVO reqVO = randomPojo(DeptSaveReqVO.class, o -> {\n            // 设置更新的 ID\n            o.setParentId(DeptDO.PARENT_ID_ROOT);\n            o.setId(dbDeptDO.getId());\n            o.setStatus(randomCommonStatus());\n        });\n\n        // 调用\n        deptService.updateDept(reqVO);\n        // 校验是否更新正确\n        DeptDO deptDO = deptMapper.selectById(reqVO.getId()); // 获取最新的\n        assertPojoEquals(reqVO, deptDO);\n    }\n\n    @Test\n    public void testDeleteDept_success() {\n        // mock 数据\n        DeptDO dbDeptDO = randomPojo(DeptDO.class);\n        deptMapper.insert(dbDeptDO);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        Long id = dbDeptDO.getId();\n\n        // 调用\n        deptService.deleteDept(id);\n        // 校验数据不存在了\n        assertNull(deptMapper.selectById(id));\n    }\n\n    @Test\n    public void testDeleteDept_exitsChildren() {\n        // mock 数据\n        DeptDO parentDept = randomPojo(DeptDO.class);\n        deptMapper.insert(parentDept);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        DeptDO childrenDeptDO = randomPojo(DeptDO.class, o -> {\n            o.setParentId(parentDept.getId());\n            o.setStatus(randomCommonStatus());\n        });\n        // 插入子部门\n        deptMapper.insert(childrenDeptDO);\n\n        // 调用, 并断言异常\n        assertServiceException(() -> deptService.deleteDept(parentDept.getId()), DEPT_EXITS_CHILDREN);\n    }\n\n    @Test\n    public void testValidateDeptExists_notFound() {\n        // 准备参数\n        Long id = randomLongId();\n\n        // 调用, 并断言异常\n        assertServiceException(() -> deptService.validateDeptExists(id), DEPT_NOT_FOUND);\n    }\n\n    @Test\n    public void testValidateParentDept_parentError() {\n        // 准备参数\n        Long id = randomLongId();\n\n        // 调用, 并断言异常\n        assertServiceException(() -> deptService.validateParentDept(id, id),\n                DEPT_PARENT_ERROR);\n    }\n\n    @Test\n    public void testValidateParentDept_parentIsChild() {\n        // mock 数据（父节点）\n        DeptDO parentDept = randomPojo(DeptDO.class);\n        deptMapper.insert(parentDept);\n        // mock 数据（子节点）\n        DeptDO childDept = randomPojo(DeptDO.class, o -> {\n            o.setParentId(parentDept.getId());\n        });\n        deptMapper.insert(childDept);\n\n        // 准备参数\n        Long id = parentDept.getId();\n        Long parentId = childDept.getId();\n\n        // 调用, 并断言异常\n        assertServiceException(() -> deptService.validateParentDept(id, parentId), DEPT_PARENT_IS_CHILD);\n    }\n\n    @Test\n    public void testValidateNameUnique_duplicate() {\n        // mock 数据\n        DeptDO deptDO = randomPojo(DeptDO.class);\n        deptMapper.insert(deptDO);\n\n        // 准备参数\n        Long id = randomLongId();\n        Long parentId = deptDO.getParentId();\n        String name = deptDO.getName();\n\n        // 调用, 并断言异常\n        assertServiceException(() -> deptService.validateDeptNameUnique(id, parentId, name),\n                DEPT_NAME_DUPLICATE);\n    }\n\n    @Test\n    public void testGetDept() {\n        // mock 数据\n        DeptDO deptDO = randomPojo(DeptDO.class);\n        deptMapper.insert(deptDO);\n        // 准备参数\n        Long id = deptDO.getId();\n\n        // 调用\n        DeptDO dbDept = deptService.getDept(id);\n        // 断言\n        assertEquals(deptDO, dbDept);\n    }\n\n    @Test\n    public void testGetDeptList_ids() {\n        // mock 数据\n        DeptDO deptDO01 = randomPojo(DeptDO.class);\n        deptMapper.insert(deptDO01);\n        DeptDO deptDO02 = randomPojo(DeptDO.class);\n        deptMapper.insert(deptDO02);\n        // 准备参数\n        List<Long> ids = Arrays.asList(deptDO01.getId(), deptDO02.getId());\n\n        // 调用\n        List<DeptDO> deptDOList = deptService.getDeptList(ids);\n        // 断言\n        assertEquals(2, deptDOList.size());\n        assertEquals(deptDO01, deptDOList.get(0));\n        assertEquals(deptDO02, deptDOList.get(1));\n    }\n\n    @Test\n    public void testGetDeptList_reqVO() {\n        // mock 数据\n        DeptDO dept = randomPojo(DeptDO.class, o -> { // 等会查询到\n            o.setName(\"开发部\");\n            o.setStatus(CommonStatusEnum.ENABLE.getStatus());\n        });\n        deptMapper.insert(dept);\n        // 测试 name 不匹配\n        deptMapper.insert(ObjectUtils.cloneIgnoreId(dept, o -> o.setName(\"发\")));\n        // 测试 status 不匹配\n        deptMapper.insert(ObjectUtils.cloneIgnoreId(dept, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));\n        // 准备参数\n        DeptListReqVO reqVO = new DeptListReqVO();\n        reqVO.setName(\"开\");\n        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());\n\n        // 调用\n        List<DeptDO> sysDeptDOS = deptService.getDeptList(reqVO);\n        // 断言\n        assertEquals(1, sysDeptDOS.size());\n        assertPojoEquals(dept, sysDeptDOS.get(0));\n    }\n\n    @Test\n    public void testGetChildDeptList() {\n        // mock 数据（1 级别子节点）\n        DeptDO dept1 = randomPojo(DeptDO.class, o -> o.setName(\"1\"));\n        deptMapper.insert(dept1);\n        DeptDO dept2 = randomPojo(DeptDO.class, o -> o.setName(\"2\"));\n        deptMapper.insert(dept2);\n        // mock 数据（2 级子节点）\n        DeptDO dept1a = randomPojo(DeptDO.class, o -> o.setName(\"1-a\").setParentId(dept1.getId()));\n        deptMapper.insert(dept1a);\n        DeptDO dept2a = randomPojo(DeptDO.class, o -> o.setName(\"2-a\").setParentId(dept2.getId()));\n        deptMapper.insert(dept2a);\n        // 准备参数\n        Long id = dept1.getParentId();\n\n        // 调用\n        List<DeptDO> result = deptService.getChildDeptList(id);\n        // 断言\n        assertEquals(result.size(), 2);\n        assertPojoEquals(dept1, result.get(0));\n        assertPojoEquals(dept1a, result.get(1));\n    }\n\n    @Test\n    public void testGetChildDeptListFromCache() {\n        // mock 数据（1 级别子节点）\n        DeptDO dept1 = randomPojo(DeptDO.class, o -> o.setName(\"1\"));\n        deptMapper.insert(dept1);\n        DeptDO dept2 = randomPojo(DeptDO.class, o -> o.setName(\"2\"));\n        deptMapper.insert(dept2);\n        // mock 数据（2 级子节点）\n        DeptDO dept1a = randomPojo(DeptDO.class, o -> o.setName(\"1-a\").setParentId(dept1.getId()));\n        deptMapper.insert(dept1a);\n        DeptDO dept2a = randomPojo(DeptDO.class, o -> o.setName(\"2-a\").setParentId(dept2.getId()));\n        deptMapper.insert(dept2a);\n        // 准备参数\n        Long id = dept1.getParentId();\n\n        // 调用\n        Set<Long> result = deptService.getChildDeptIdListFromCache(id);\n        // 断言\n        assertEquals(result.size(), 2);\n        assertTrue(result.contains(dept1.getId()));\n        assertTrue(result.contains(dept1a.getId()));\n    }\n\n    @Test\n    public void testValidateDeptList_success() {\n        // mock 数据\n        DeptDO deptDO = randomPojo(DeptDO.class).setStatus(CommonStatusEnum.ENABLE.getStatus());\n        deptMapper.insert(deptDO);\n        // 准备参数\n        List<Long> ids = singletonList(deptDO.getId());\n\n        // 调用，无需断言\n        deptService.validateDeptList(ids);\n    }\n\n    @Test\n    public void testValidateDeptList_notFound() {\n        // 准备参数\n        List<Long> ids = singletonList(randomLongId());\n\n        // 调用, 并断言异常\n        assertServiceException(() -> deptService.validateDeptList(ids), DEPT_NOT_FOUND);\n    }\n\n    @Test\n    public void testValidateDeptList_notEnable() {\n        // mock 数据\n        DeptDO deptDO = randomPojo(DeptDO.class).setStatus(CommonStatusEnum.DISABLE.getStatus());\n        deptMapper.insert(deptDO);\n        // 准备参数\n        List<Long> ids = singletonList(deptDO.getId());\n\n        // 调用, 并断言异常\n        assertServiceException(() -> deptService.validateDeptList(ids), DEPT_NOT_ENABLE, deptDO.getName());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/java/co/yixiang/yshop/module/system/service/dept/PostServiceImplTest.java",
    "content": "package co.yixiang.yshop.module.system.service.dept;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.collection.ArrayUtils;\nimport co.yixiang.yshop.framework.test.core.ut.BaseDbUnitTest;\nimport co.yixiang.yshop.module.system.controller.admin.dept.vo.post.PostPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.dept.vo.post.PostSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.dept.PostDO;\nimport co.yixiang.yshop.module.system.dal.mysql.dept.PostMapper;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.context.annotation.Import;\n\nimport jakarta.annotation.Resource;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.function.Consumer;\n\nimport static cn.hutool.core.util.RandomUtil.randomEle;\nimport static co.yixiang.yshop.framework.common.util.object.ObjectUtils.cloneIgnoreId;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertPojoEquals;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertServiceException;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.*;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.*;\nimport static java.util.Collections.singletonList;\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * {@link PostServiceImpl} 的单元测试类\n *\n * @author niudehua\n */\n@Import(PostServiceImpl.class)\npublic class PostServiceImplTest extends BaseDbUnitTest {\n\n    @Resource\n    private PostServiceImpl postService;\n\n    @Resource\n    private PostMapper postMapper;\n\n    @Test\n    public void testCreatePost_success() {\n        // 准备参数\n        PostSaveReqVO reqVO = randomPojo(PostSaveReqVO.class,\n                o -> o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()))\n                .setId(null); // 防止 id 被设置\n        // 调用\n        Long postId = postService.createPost(reqVO);\n\n        // 断言\n        assertNotNull(postId);\n        // 校验记录的属性是否正确\n        PostDO post = postMapper.selectById(postId);\n        assertPojoEquals(reqVO, post, \"id\");\n    }\n\n    @Test\n    public void testUpdatePost_success() {\n        // mock 数据\n        PostDO postDO = randomPostDO();\n        postMapper.insert(postDO);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        PostSaveReqVO reqVO = randomPojo(PostSaveReqVO.class, o -> {\n            // 设置更新的 ID\n            o.setId(postDO.getId());\n            o.setStatus(randomEle(CommonStatusEnum.values()).getStatus());\n        });\n\n        // 调用\n        postService.updatePost(reqVO);\n        // 校验是否更新正确\n        PostDO post = postMapper.selectById(reqVO.getId());\n        assertPojoEquals(reqVO, post);\n    }\n\n    @Test\n    public void testDeletePost_success() {\n        // mock 数据\n        PostDO postDO = randomPostDO();\n        postMapper.insert(postDO);\n        // 准备参数\n        Long id = postDO.getId();\n\n        // 调用\n        postService.deletePost(id);\n        assertNull(postMapper.selectById(id));\n    }\n\n    @Test\n    public void testValidatePost_notFoundForDelete() {\n        // 准备参数\n        Long id = randomLongId();\n\n        // 调用, 并断言异常\n        assertServiceException(() -> postService.deletePost(id), POST_NOT_FOUND);\n    }\n\n    @Test\n    public void testValidatePost_nameDuplicateForCreate() {\n        // mock 数据\n        PostDO postDO = randomPostDO();\n        postMapper.insert(postDO);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        PostSaveReqVO reqVO = randomPojo(PostSaveReqVO.class,\n            // 模拟 name 重复\n            o -> o.setName(postDO.getName()));\n        assertServiceException(() -> postService.createPost(reqVO), POST_NAME_DUPLICATE);\n    }\n\n    @Test\n    public void testValidatePost_codeDuplicateForUpdate() {\n        // mock 数据\n        PostDO postDO = randomPostDO();\n        postMapper.insert(postDO);\n        // mock 数据：稍后模拟重复它的 code\n        PostDO codePostDO = randomPostDO();\n        postMapper.insert(codePostDO);\n        // 准备参数\n        PostSaveReqVO reqVO = randomPojo(PostSaveReqVO.class, o -> {\n            // 设置更新的 ID\n            o.setId(postDO.getId());\n            // 模拟 code 重复\n            o.setCode(codePostDO.getCode());\n        });\n\n        // 调用, 并断言异常\n        assertServiceException(() -> postService.updatePost(reqVO), POST_CODE_DUPLICATE);\n    }\n\n    @Test\n    public void testGetPostPage() {\n        // mock 数据\n        PostDO postDO = randomPojo(PostDO.class, o -> {\n            o.setName(\"码仔\");\n            o.setStatus(CommonStatusEnum.ENABLE.getStatus());\n        });\n        postMapper.insert(postDO);\n        // 测试 name 不匹配\n        postMapper.insert(cloneIgnoreId(postDO, o -> o.setName(\"程序员\")));\n        // 测试 status 不匹配\n        postMapper.insert(cloneIgnoreId(postDO, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));\n        // 准备参数\n        PostPageReqVO reqVO = new PostPageReqVO();\n        reqVO.setName(\"码\");\n        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());\n\n        // 调用\n        PageResult<PostDO> pageResult = postService.getPostPage(reqVO);\n        // 断言\n        assertEquals(1, pageResult.getTotal());\n        assertEquals(1, pageResult.getList().size());\n        assertPojoEquals(postDO, pageResult.getList().get(0));\n    }\n\n    @Test\n    public void testGetPostList() {\n        // mock 数据\n        PostDO postDO01 = randomPojo(PostDO.class);\n        postMapper.insert(postDO01);\n        // 测试 id 不匹配\n        PostDO postDO02 = randomPojo(PostDO.class);\n        postMapper.insert(postDO02);\n        // 准备参数\n        List<Long> ids = singletonList(postDO01.getId());\n\n        // 调用\n        List<PostDO> list = postService.getPostList(ids);\n        // 断言\n        assertEquals(1, list.size());\n        assertPojoEquals(postDO01, list.get(0));\n    }\n\n    @Test\n    public void testGetPostList_idsAndStatus() {\n        // mock 数据\n        PostDO postDO01 = randomPojo(PostDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));\n        postMapper.insert(postDO01);\n        // 测试 status 不匹配\n        PostDO postDO02 = randomPojo(PostDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()));\n        postMapper.insert(postDO02);\n        // 准备参数\n        List<Long> ids = Arrays.asList(postDO01.getId(), postDO02.getId());\n\n        // 调用\n        List<PostDO> list = postService.getPostList(ids, singletonList(CommonStatusEnum.ENABLE.getStatus()));\n        // 断言\n        assertEquals(1, list.size());\n        assertPojoEquals(postDO01, list.get(0));\n    }\n\n    @Test\n    public void testGetPost() {\n        // mock 数据\n        PostDO dbPostDO = randomPostDO();\n        postMapper.insert(dbPostDO);\n        // 准备参数\n        Long id = dbPostDO.getId();\n        // 调用\n        PostDO post = postService.getPost(id);\n        // 断言\n        assertNotNull(post);\n        assertPojoEquals(dbPostDO, post);\n    }\n\n    @Test\n    public void testValidatePostList_success() {\n        // mock 数据\n        PostDO postDO = randomPostDO().setStatus(CommonStatusEnum.ENABLE.getStatus());\n        postMapper.insert(postDO);\n        // 准备参数\n        List<Long> ids = singletonList(postDO.getId());\n\n        // 调用，无需断言\n        postService.validatePostList(ids);\n    }\n\n    @Test\n    public void testValidatePostList_notFound() {\n        // 准备参数\n        List<Long> ids = singletonList(randomLongId());\n\n        // 调用, 并断言异常\n        assertServiceException(() -> postService.validatePostList(ids), POST_NOT_FOUND);\n    }\n\n    @Test\n    public void testValidatePostList_notEnable() {\n        // mock 数据\n        PostDO postDO = randomPostDO().setStatus(CommonStatusEnum.DISABLE.getStatus());\n        postMapper.insert(postDO);\n        // 准备参数\n        List<Long> ids = singletonList(postDO.getId());\n\n        // 调用, 并断言异常\n        assertServiceException(() -> postService.validatePostList(ids), POST_NOT_ENABLE,\n                postDO.getName());\n    }\n\n    @SafeVarargs\n    private static PostDO randomPostDO(Consumer<PostDO>... consumers) {\n        Consumer<PostDO> consumer = (o) -> {\n            o.setStatus(randomCommonStatus()); // 保证 status 的范围\n        };\n        return randomPojo(PostDO.class, ArrayUtils.append(consumer, consumers));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/java/co/yixiang/yshop/module/system/service/dict/DictDataServiceImplTest.java",
    "content": "package co.yixiang.yshop.module.system.service.dict;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.collection.ArrayUtils;\nimport co.yixiang.yshop.framework.test.core.ut.BaseDbUnitTest;\nimport co.yixiang.yshop.module.system.controller.admin.dict.vo.data.DictDataPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.dict.vo.data.DictDataSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.dict.DictDataDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.dict.DictTypeDO;\nimport co.yixiang.yshop.module.system.dal.mysql.dict.DictDataMapper;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.mock.mockito.MockBean;\nimport org.springframework.context.annotation.Import;\n\nimport jakarta.annotation.Resource;\nimport java.util.List;\nimport java.util.function.Consumer;\n\nimport static co.yixiang.yshop.framework.common.util.object.ObjectUtils.cloneIgnoreId;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertPojoEquals;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertServiceException;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.*;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.*;\nimport static java.util.Collections.singletonList;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.when;\n\n@Import(DictDataServiceImpl.class)\npublic class DictDataServiceImplTest extends BaseDbUnitTest {\n\n    @Resource\n    private DictDataServiceImpl dictDataService;\n\n    @Resource\n    private DictDataMapper dictDataMapper;\n    @MockBean\n    private DictTypeService dictTypeService;\n\n    @Test\n    public void testGetDictDataList() {\n        // mock 数据\n        DictDataDO dictDataDO01 = randomDictDataDO().setDictType(\"yshop\").setSort(2)\n                .setStatus(CommonStatusEnum.ENABLE.getStatus());\n        dictDataMapper.insert(dictDataDO01);\n        DictDataDO dictDataDO02 = randomDictDataDO().setDictType(\"yshop\").setSort(1)\n                .setStatus(CommonStatusEnum.ENABLE.getStatus());\n        dictDataMapper.insert(dictDataDO02);\n        DictDataDO dictDataDO03 = randomDictDataDO().setDictType(\"yshop\").setSort(3)\n                .setStatus(CommonStatusEnum.DISABLE.getStatus());\n        dictDataMapper.insert(dictDataDO03);\n        DictDataDO dictDataDO04 = randomDictDataDO().setDictType(\"yshop2\").setSort(3)\n                .setStatus(CommonStatusEnum.DISABLE.getStatus());\n        dictDataMapper.insert(dictDataDO04);\n        // 准备参数\n        Integer status = CommonStatusEnum.ENABLE.getStatus();\n        String dictType = \"yshop\";\n\n        // 调用\n        List<DictDataDO> dictDataDOList = dictDataService.getDictDataList(status, dictType);\n        // 断言\n        assertEquals(2, dictDataDOList.size());\n        assertPojoEquals(dictDataDO02, dictDataDOList.get(0));\n        assertPojoEquals(dictDataDO01, dictDataDOList.get(1));\n    }\n\n    @Test\n    public void testGetDictDataPage() {\n        // mock 数据\n        DictDataDO dbDictData = randomPojo(DictDataDO.class, o -> { // 等会查询到\n            o.setLabel(\"yshop\");\n            o.setDictType(\"yshop\");\n            o.setStatus(CommonStatusEnum.ENABLE.getStatus());\n        });\n        dictDataMapper.insert(dbDictData);\n        // 测试 label 不匹配\n        dictDataMapper.insert(cloneIgnoreId(dbDictData, o -> o.setLabel(\"艿\")));\n        // 测试 dictType 不匹配\n        dictDataMapper.insert(cloneIgnoreId(dbDictData, o -> o.setDictType(\"nai\")));\n        // 测试 status 不匹配\n        dictDataMapper.insert(cloneIgnoreId(dbDictData, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));\n        // 准备参数\n        DictDataPageReqVO reqVO = new DictDataPageReqVO();\n        reqVO.setLabel(\"芋\");\n        reqVO.setDictType(\"yshop\");\n        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());\n\n        // 调用\n        PageResult<DictDataDO> pageResult = dictDataService.getDictDataPage(reqVO);\n        // 断言\n        assertEquals(1, pageResult.getTotal());\n        assertEquals(1, pageResult.getList().size());\n        assertPojoEquals(dbDictData, pageResult.getList().get(0));\n    }\n\n    @Test\n    public void testGetDictData() {\n        // mock 数据\n        DictDataDO dbDictData = randomDictDataDO();\n        dictDataMapper.insert(dbDictData);\n        // 准备参数\n        Long id = dbDictData.getId();\n\n        // 调用\n        DictDataDO dictData = dictDataService.getDictData(id);\n        // 断言\n        assertPojoEquals(dbDictData, dictData);\n    }\n\n    @Test\n    public void testCreateDictData_success() {\n        // 准备参数\n        DictDataSaveReqVO reqVO = randomPojo(DictDataSaveReqVO.class,\n                o -> o.setStatus(randomCommonStatus()))\n                .setId(null); // 防止 id 被赋值\n        // mock 方法\n        when(dictTypeService.getDictType(eq(reqVO.getDictType()))).thenReturn(randomDictTypeDO(reqVO.getDictType()));\n\n        // 调用\n        Long dictDataId = dictDataService.createDictData(reqVO);\n        // 断言\n        assertNotNull(dictDataId);\n        // 校验记录的属性是否正确\n        DictDataDO dictData = dictDataMapper.selectById(dictDataId);\n        assertPojoEquals(reqVO, dictData, \"id\");\n    }\n\n    @Test\n    public void testUpdateDictData_success() {\n        // mock 数据\n        DictDataDO dbDictData = randomDictDataDO();\n        dictDataMapper.insert(dbDictData);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        DictDataSaveReqVO reqVO = randomPojo(DictDataSaveReqVO.class, o -> {\n            o.setId(dbDictData.getId()); // 设置更新的 ID\n            o.setStatus(randomCommonStatus());\n        });\n        // mock 方法，字典类型\n        when(dictTypeService.getDictType(eq(reqVO.getDictType()))).thenReturn(randomDictTypeDO(reqVO.getDictType()));\n\n        // 调用\n        dictDataService.updateDictData(reqVO);\n        // 校验是否更新正确\n        DictDataDO dictData = dictDataMapper.selectById(reqVO.getId()); // 获取最新的\n        assertPojoEquals(reqVO, dictData);\n    }\n\n    @Test\n    public void testDeleteDictData_success() {\n        // mock 数据\n        DictDataDO dbDictData = randomDictDataDO();\n        dictDataMapper.insert(dbDictData);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        Long id = dbDictData.getId();\n\n        // 调用\n        dictDataService.deleteDictData(id);\n        // 校验数据不存在了\n        assertNull(dictDataMapper.selectById(id));\n    }\n\n    @Test\n    public void testValidateDictDataExists_success() {\n        // mock 数据\n        DictDataDO dbDictData = randomDictDataDO();\n        dictDataMapper.insert(dbDictData);// @Sql: 先插入出一条存在的数据\n\n        // 调用成功\n        dictDataService.validateDictDataExists(dbDictData.getId());\n    }\n\n    @Test\n    public void testValidateDictDataExists_notExists() {\n        assertServiceException(() -> dictDataService.validateDictDataExists(randomLongId()), DICT_DATA_NOT_EXISTS);\n    }\n\n    @Test\n    public void testValidateDictTypeExists_success() {\n        // mock 方法，数据类型被禁用\n        String type = randomString();\n        when(dictTypeService.getDictType(eq(type))).thenReturn(randomDictTypeDO(type));\n\n        // 调用, 成功\n        dictDataService.validateDictTypeExists(type);\n    }\n\n    @Test\n    public void testValidateDictTypeExists_notExists() {\n        assertServiceException(() -> dictDataService.validateDictTypeExists(randomString()), DICT_TYPE_NOT_EXISTS);\n    }\n\n    @Test\n    public void testValidateDictTypeExists_notEnable() {\n        // mock 方法，数据类型被禁用\n        String dictType = randomString();\n        when(dictTypeService.getDictType(eq(dictType))).thenReturn(\n                randomPojo(DictTypeDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));\n\n        // 调用, 并断言异常\n        assertServiceException(() -> dictDataService.validateDictTypeExists(dictType), DICT_TYPE_NOT_ENABLE);\n    }\n\n    @Test\n    public void testValidateDictDataValueUnique_success() {\n        // 调用，成功\n        dictDataService.validateDictDataValueUnique(randomLongId(), randomString(), randomString());\n    }\n\n    @Test\n    public void testValidateDictDataValueUnique_valueDuplicateForCreate() {\n        // 准备参数\n        String dictType = randomString();\n        String value = randomString();\n        // mock 数据\n        dictDataMapper.insert(randomDictDataDO(o -> {\n            o.setDictType(dictType);\n            o.setValue(value);\n        }));\n\n        // 调用，校验异常\n        assertServiceException(() -> dictDataService.validateDictDataValueUnique(null, dictType, value),\n                DICT_DATA_VALUE_DUPLICATE);\n    }\n\n    @Test\n    public void testValidateDictDataValueUnique_valueDuplicateForUpdate() {\n        // 准备参数\n        Long id = randomLongId();\n        String dictType = randomString();\n        String value = randomString();\n        // mock 数据\n        dictDataMapper.insert(randomDictDataDO(o -> {\n            o.setDictType(dictType);\n            o.setValue(value);\n        }));\n\n        // 调用，校验异常\n        assertServiceException(() -> dictDataService.validateDictDataValueUnique(id, dictType, value),\n                DICT_DATA_VALUE_DUPLICATE);\n    }\n\n    @Test\n    public void testGetDictDataCountByDictType() {\n        // mock 数据\n        dictDataMapper.insert(randomDictDataDO(o -> o.setDictType(\"yshop\")));\n        dictDataMapper.insert(randomDictDataDO(o -> o.setDictType(\"tudou\")));\n        dictDataMapper.insert(randomDictDataDO(o -> o.setDictType(\"yshop\")));\n        // 准备参数\n        String dictType = \"yshop\";\n\n        // 调用\n        long count = dictDataService.getDictDataCountByDictType(dictType);\n        // 校验\n        assertEquals(2L, count);\n    }\n\n    @Test\n    public void testValidateDictDataList_success() {\n        // mock 数据\n        DictDataDO dictDataDO = randomDictDataDO().setStatus(CommonStatusEnum.ENABLE.getStatus());\n        dictDataMapper.insert(dictDataDO);\n        // 准备参数\n        String dictType = dictDataDO.getDictType();\n        List<String> values = singletonList(dictDataDO.getValue());\n\n        // 调用，无需断言\n        dictDataService.validateDictDataList(dictType, values);\n    }\n\n    @Test\n    public void testValidateDictDataList_notFound() {\n        // 准备参数\n        String dictType = randomString();\n        List<String> values = singletonList(randomString());\n\n        // 调用, 并断言异常\n        assertServiceException(() -> dictDataService.validateDictDataList(dictType, values), DICT_DATA_NOT_EXISTS);\n    }\n\n    @Test\n    public void testValidateDictDataList_notEnable() {\n        // mock 数据\n        DictDataDO dictDataDO = randomDictDataDO().setStatus(CommonStatusEnum.DISABLE.getStatus());\n        dictDataMapper.insert(dictDataDO);\n        // 准备参数\n        String dictType = dictDataDO.getDictType();\n        List<String> values = singletonList(dictDataDO.getValue());\n\n        // 调用, 并断言异常\n        assertServiceException(() -> dictDataService.validateDictDataList(dictType, values),\n                DICT_DATA_NOT_ENABLE, dictDataDO.getLabel());\n    }\n\n    @Test\n    public void testGetDictData_dictType() {\n        // mock 数据\n        DictDataDO dictDataDO = randomDictDataDO().setDictType(\"yshop\").setValue(\"1\");\n        dictDataMapper.insert(dictDataDO);\n        DictDataDO dictDataDO02 = randomDictDataDO().setDictType(\"yshop\").setValue(\"2\");\n        dictDataMapper.insert(dictDataDO02);\n        // 准备参数\n        String dictType = \"yshop\";\n        String value = \"1\";\n\n        // 调用\n        DictDataDO dbDictData = dictDataService.getDictData(dictType, value);\n        // 断言\n        assertEquals(dictDataDO, dbDictData);\n    }\n\n    @Test\n    public void testParseDictData() {\n        // mock 数据\n        DictDataDO dictDataDO = randomDictDataDO().setDictType(\"yshop\").setLabel(\"1\");\n        dictDataMapper.insert(dictDataDO);\n        DictDataDO dictDataDO02 = randomDictDataDO().setDictType(\"yshop\").setLabel(\"2\");\n        dictDataMapper.insert(dictDataDO02);\n        // 准备参数\n        String dictType = \"yshop\";\n        String label = \"1\";\n\n        // 调用\n        DictDataDO dbDictData = dictDataService.parseDictData(dictType, label);\n        // 断言\n        assertEquals(dictDataDO, dbDictData);\n    }\n\n    // ========== 随机对象 ==========\n\n    @SafeVarargs\n    private static DictDataDO randomDictDataDO(Consumer<DictDataDO>... consumers) {\n        Consumer<DictDataDO> consumer = (o) -> {\n            o.setStatus(randomCommonStatus()); // 保证 status 的范围\n        };\n        return randomPojo(DictDataDO.class, ArrayUtils.append(consumer, consumers));\n    }\n\n    /**\n     * 生成一个有效的字典类型\n     *\n     * @param type 字典类型\n     * @return DictTypeDO 对象\n     */\n    private static DictTypeDO randomDictTypeDO(String type) {\n        return randomPojo(DictTypeDO.class, o -> {\n            o.setType(type);\n            o.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 保证 status 是开启\n        });\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/java/co/yixiang/yshop/module/system/service/dict/DictTypeServiceImplTest.java",
    "content": "package co.yixiang.yshop.module.system.service.dict;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.collection.ArrayUtils;\nimport co.yixiang.yshop.framework.test.core.ut.BaseDbUnitTest;\nimport co.yixiang.yshop.module.system.controller.admin.dict.vo.type.DictTypePageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.dict.vo.type.DictTypeSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.dict.DictTypeDO;\nimport co.yixiang.yshop.module.system.dal.mysql.dict.DictTypeMapper;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.mock.mockito.MockBean;\nimport org.springframework.context.annotation.Import;\n\nimport jakarta.annotation.Resource;\nimport java.util.List;\nimport java.util.function.Consumer;\n\nimport static cn.hutool.core.util.RandomUtil.randomEle;\nimport static co.yixiang.yshop.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;\nimport static co.yixiang.yshop.framework.common.util.date.LocalDateTimeUtils.buildTime;\nimport static co.yixiang.yshop.framework.common.util.object.ObjectUtils.cloneIgnoreId;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertPojoEquals;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertServiceException;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.*;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.*;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.when;\n\n@Import(DictTypeServiceImpl.class)\npublic class DictTypeServiceImplTest extends BaseDbUnitTest {\n\n    @Resource\n    private DictTypeServiceImpl dictTypeService;\n\n    @Resource\n    private DictTypeMapper dictTypeMapper;\n    @MockBean\n    private DictDataService dictDataService;\n\n    @Test\n    public void testGetDictTypePage() {\n       // mock 数据\n       DictTypeDO dbDictType = randomPojo(DictTypeDO.class, o -> { // 等会查询到\n           o.setName(\"yshop\");\n           o.setType(\"yshop\");\n           o.setStatus(CommonStatusEnum.ENABLE.getStatus());\n           o.setCreateTime(buildTime(2021, 1, 15));\n       });\n       dictTypeMapper.insert(dbDictType);\n       // 测试 name 不匹配\n       dictTypeMapper.insert(cloneIgnoreId(dbDictType, o -> o.setName(\"tudou\")));\n       // 测试 type 不匹配\n       dictTypeMapper.insert(cloneIgnoreId(dbDictType, o -> o.setType(\"土豆\")));\n       // 测试 status 不匹配\n       dictTypeMapper.insert(cloneIgnoreId(dbDictType, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));\n       // 测试 createTime 不匹配\n       dictTypeMapper.insert(cloneIgnoreId(dbDictType, o -> o.setCreateTime(buildTime(2021, 1, 1))));\n       // 准备参数\n       DictTypePageReqVO reqVO = new DictTypePageReqVO();\n       reqVO.setName(\"nai\");\n       reqVO.setType(\"艿\");\n       reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());\n       reqVO.setCreateTime(buildBetweenTime(2021, 1, 10, 2021, 1, 20));\n\n       // 调用\n       PageResult<DictTypeDO> pageResult = dictTypeService.getDictTypePage(reqVO);\n       // 断言\n       assertEquals(1, pageResult.getTotal());\n       assertEquals(1, pageResult.getList().size());\n       assertPojoEquals(dbDictType, pageResult.getList().get(0));\n    }\n\n    @Test\n    public void testGetDictType_id() {\n        // mock 数据\n        DictTypeDO dbDictType = randomDictTypeDO();\n        dictTypeMapper.insert(dbDictType);\n        // 准备参数\n        Long id = dbDictType.getId();\n\n        // 调用\n        DictTypeDO dictType = dictTypeService.getDictType(id);\n        // 断言\n        assertNotNull(dictType);\n        assertPojoEquals(dbDictType, dictType);\n    }\n\n    @Test\n    public void testGetDictType_type() {\n        // mock 数据\n        DictTypeDO dbDictType = randomDictTypeDO();\n        dictTypeMapper.insert(dbDictType);\n        // 准备参数\n        String type = dbDictType.getType();\n\n        // 调用\n        DictTypeDO dictType = dictTypeService.getDictType(type);\n        // 断言\n        assertNotNull(dictType);\n        assertPojoEquals(dbDictType, dictType);\n    }\n\n    @Test\n    public void testCreateDictType_success() {\n        // 准备参数\n        DictTypeSaveReqVO reqVO = randomPojo(DictTypeSaveReqVO.class,\n                o -> o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()))\n                .setId(null); // 避免 id 被赋值\n\n        // 调用\n        Long dictTypeId = dictTypeService.createDictType(reqVO);\n        // 断言\n        assertNotNull(dictTypeId);\n        // 校验记录的属性是否正确\n        DictTypeDO dictType = dictTypeMapper.selectById(dictTypeId);\n        assertPojoEquals(reqVO, dictType, \"id\");\n    }\n\n    @Test\n    public void testUpdateDictType_success() {\n        // mock 数据\n        DictTypeDO dbDictType = randomDictTypeDO();\n        dictTypeMapper.insert(dbDictType);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        DictTypeSaveReqVO reqVO = randomPojo(DictTypeSaveReqVO.class, o -> {\n            o.setId(dbDictType.getId()); // 设置更新的 ID\n            o.setStatus(randomEle(CommonStatusEnum.values()).getStatus());\n        });\n\n        // 调用\n        dictTypeService.updateDictType(reqVO);\n        // 校验是否更新正确\n        DictTypeDO dictType = dictTypeMapper.selectById(reqVO.getId()); // 获取最新的\n        assertPojoEquals(reqVO, dictType);\n    }\n\n    @Test\n    public void testDeleteDictType_success() {\n        // mock 数据\n        DictTypeDO dbDictType = randomDictTypeDO();\n        dictTypeMapper.insert(dbDictType);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        Long id = dbDictType.getId();\n\n        // 调用\n        dictTypeService.deleteDictType(id);\n        // 校验数据不存在了\n        assertNull(dictTypeMapper.selectById(id));\n    }\n\n    @Test\n    public void testDeleteDictType_hasChildren() {\n        // mock 数据\n        DictTypeDO dbDictType = randomDictTypeDO();\n        dictTypeMapper.insert(dbDictType);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        Long id = dbDictType.getId();\n        // mock 方法\n        when(dictDataService.getDictDataCountByDictType(eq(dbDictType.getType()))).thenReturn(1L);\n\n        // 调用, 并断言异常\n        assertServiceException(() -> dictTypeService.deleteDictType(id), DICT_TYPE_HAS_CHILDREN);\n    }\n\n    @Test\n    public void testGetDictTypeList() {\n        // 准备参数\n        DictTypeDO dictTypeDO01 = randomDictTypeDO();\n        dictTypeMapper.insert(dictTypeDO01);\n        DictTypeDO dictTypeDO02 = randomDictTypeDO();\n        dictTypeMapper.insert(dictTypeDO02);\n        // mock 方法\n\n        // 调用\n        List<DictTypeDO> dictTypeDOList = dictTypeService.getDictTypeList();\n        // 断言\n        assertEquals(2, dictTypeDOList.size());\n        assertPojoEquals(dictTypeDO01, dictTypeDOList.get(0));\n        assertPojoEquals(dictTypeDO02, dictTypeDOList.get(1));\n    }\n\n    @Test\n    public void testValidateDictDataExists_success() {\n        // mock 数据\n        DictTypeDO dbDictType = randomDictTypeDO();\n        dictTypeMapper.insert(dbDictType);// @Sql: 先插入出一条存在的数据\n\n        // 调用成功\n        dictTypeService.validateDictTypeExists(dbDictType.getId());\n    }\n\n    @Test\n    public void testValidateDictDataExists_notExists() {\n        assertServiceException(() -> dictTypeService.validateDictTypeExists(randomLongId()), DICT_TYPE_NOT_EXISTS);\n    }\n\n    @Test\n    public void testValidateDictTypeUnique_success() {\n        // 调用，成功\n        dictTypeService.validateDictTypeUnique(randomLongId(), randomString());\n    }\n\n    @Test\n    public void testValidateDictTypeUnique_valueDuplicateForCreate() {\n        // 准备参数\n        String type = randomString();\n        // mock 数据\n        dictTypeMapper.insert(randomDictTypeDO(o -> o.setType(type)));\n\n        // 调用，校验异常\n        assertServiceException(() -> dictTypeService.validateDictTypeUnique(null, type),\n                DICT_TYPE_TYPE_DUPLICATE);\n    }\n\n    @Test\n    public void testValidateDictTypeUnique_valueDuplicateForUpdate() {\n        // 准备参数\n        Long id = randomLongId();\n        String type = randomString();\n        // mock 数据\n        dictTypeMapper.insert(randomDictTypeDO(o -> o.setType(type)));\n\n        // 调用，校验异常\n        assertServiceException(() -> dictTypeService.validateDictTypeUnique(id, type),\n                DICT_TYPE_TYPE_DUPLICATE);\n    }\n\n    @Test\n    public void testValidateDictTypNameUnique_success() {\n        // 调用，成功\n        dictTypeService.validateDictTypeNameUnique(randomLongId(), randomString());\n    }\n\n    @Test\n    public void testValidateDictTypeNameUnique_nameDuplicateForCreate() {\n        // 准备参数\n        String name = randomString();\n        // mock 数据\n        dictTypeMapper.insert(randomDictTypeDO(o -> o.setName(name)));\n\n        // 调用，校验异常\n        assertServiceException(() -> dictTypeService.validateDictTypeNameUnique(null, name),\n                DICT_TYPE_NAME_DUPLICATE);\n    }\n\n    @Test\n    public void testValidateDictTypeNameUnique_nameDuplicateForUpdate() {\n        // 准备参数\n        Long id = randomLongId();\n        String name = randomString();\n        // mock 数据\n        dictTypeMapper.insert(randomDictTypeDO(o -> o.setName(name)));\n\n        // 调用，校验异常\n        assertServiceException(() -> dictTypeService.validateDictTypeNameUnique(id, name),\n                DICT_TYPE_NAME_DUPLICATE);\n    }\n\n    // ========== 随机对象 ==========\n\n    @SafeVarargs\n    private static DictTypeDO randomDictTypeDO(Consumer<DictTypeDO>... consumers) {\n        Consumer<DictTypeDO> consumer = (o) -> {\n            o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围\n        };\n        return randomPojo(DictTypeDO.class, ArrayUtils.append(consumer, consumers));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/java/co/yixiang/yshop/module/system/service/logger/LoginLogServiceImplTest.java",
    "content": "package co.yixiang.yshop.module.system.service.logger;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.test.core.ut.BaseDbUnitTest;\nimport co.yixiang.yshop.module.system.api.logger.dto.LoginLogCreateReqDTO;\nimport co.yixiang.yshop.module.system.controller.admin.logger.vo.loginlog.LoginLogPageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.logger.LoginLogDO;\nimport co.yixiang.yshop.module.system.dal.mysql.logger.LoginLogMapper;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.context.annotation.Import;\n\nimport jakarta.annotation.Resource;\n\nimport static co.yixiang.yshop.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;\nimport static co.yixiang.yshop.framework.common.util.date.LocalDateTimeUtils.buildTime;\nimport static co.yixiang.yshop.framework.common.util.object.ObjectUtils.cloneIgnoreId;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertPojoEquals;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.randomPojo;\nimport static co.yixiang.yshop.module.system.enums.logger.LoginResultEnum.CAPTCHA_CODE_ERROR;\nimport static co.yixiang.yshop.module.system.enums.logger.LoginResultEnum.SUCCESS;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n@Import(LoginLogServiceImpl.class)\npublic class LoginLogServiceImplTest extends BaseDbUnitTest {\n\n    @Resource\n    private LoginLogServiceImpl loginLogService;\n\n    @Resource\n    private LoginLogMapper loginLogMapper;\n\n    @Test\n    public void testGetLoginLogPage() {\n        // mock 数据\n        LoginLogDO loginLogDO = randomPojo(LoginLogDO.class, o -> {\n            o.setUserIp(\"192.168.199.16\");\n            o.setUsername(\"wang\");\n            o.setResult(SUCCESS.getResult());\n            o.setCreateTime(buildTime(2021, 3, 6));\n        });\n        loginLogMapper.insert(loginLogDO);\n        // 测试 status 不匹配\n        loginLogMapper.insert(cloneIgnoreId(loginLogDO, o -> o.setResult(CAPTCHA_CODE_ERROR.getResult())));\n        // 测试 ip 不匹配\n        loginLogMapper.insert(cloneIgnoreId(loginLogDO, o -> o.setUserIp(\"192.168.128.18\")));\n        // 测试 username 不匹配\n        loginLogMapper.insert(cloneIgnoreId(loginLogDO, o -> o.setUsername(\"yshop\")));\n        // 测试 createTime 不匹配\n        loginLogMapper.insert(cloneIgnoreId(loginLogDO, o -> o.setCreateTime(buildTime(2021, 2, 6))));\n        // 构造调用参数\n        LoginLogPageReqVO reqVO = new LoginLogPageReqVO();\n        reqVO.setUsername(\"wang\");\n        reqVO.setUserIp(\"192.168.199\");\n        reqVO.setStatus(true);\n        reqVO.setCreateTime(buildBetweenTime(2021, 3, 5, 2021, 3, 7));\n\n        // 调用\n        PageResult<LoginLogDO> pageResult = loginLogService.getLoginLogPage(reqVO);\n        // 断言，只查到了一条符合条件的\n        assertEquals(1, pageResult.getTotal());\n        assertEquals(1, pageResult.getList().size());\n        assertPojoEquals(loginLogDO, pageResult.getList().get(0));\n    }\n\n    @Test\n    public void testCreateLoginLog() {\n        LoginLogCreateReqDTO reqDTO = randomPojo(LoginLogCreateReqDTO.class);\n\n        // 调用\n        loginLogService.createLoginLog(reqDTO);\n        // 断言\n        LoginLogDO loginLogDO = loginLogMapper.selectOne(null);\n        assertPojoEquals(reqDTO, loginLogDO);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/java/co/yixiang/yshop/module/system/service/logger/OperateLogServiceImplTest.java",
    "content": "package co.yixiang.yshop.module.system.service.logger;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.test.core.ut.BaseDbUnitTest;\nimport co.yixiang.yshop.framework.test.core.util.RandomUtils;\nimport co.yixiang.yshop.module.system.api.logger.dto.OperateLogCreateReqDTO;\nimport co.yixiang.yshop.module.system.api.logger.dto.OperateLogPageReqDTO;\nimport co.yixiang.yshop.module.system.controller.admin.logger.vo.operatelog.OperateLogPageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.logger.OperateLogDO;\nimport co.yixiang.yshop.module.system.dal.mysql.logger.OperateLogMapper;\nimport jakarta.annotation.Resource;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.context.annotation.Import;\n\nimport static co.yixiang.yshop.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;\nimport static co.yixiang.yshop.framework.common.util.date.LocalDateTimeUtils.buildTime;\nimport static co.yixiang.yshop.framework.common.util.object.ObjectUtils.cloneIgnoreId;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertPojoEquals;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\n\n@Import({OperateLogServiceImpl.class})\npublic class OperateLogServiceImplTest extends BaseDbUnitTest {\n\n    @Resource\n    private OperateLogService operateLogServiceImpl;\n\n    @Resource\n    private OperateLogMapper operateLogMapper;\n\n    @Test\n    public void testCreateOperateLog() {\n        OperateLogCreateReqDTO reqVO = RandomUtils.randomPojo(OperateLogCreateReqDTO.class);\n\n        // 调研\n        operateLogServiceImpl.createOperateLog(reqVO);\n        // 断言\n        OperateLogDO operateLogDO = operateLogMapper.selectOne(null);\n        assertPojoEquals(reqVO, operateLogDO);\n    }\n\n    @Test\n    public void testGetOperateLogPage_vo() {\n        // 构造操作日志\n        OperateLogDO operateLogDO = RandomUtils.randomPojo(OperateLogDO.class, o -> {\n            o.setUserId(2048L);\n            o.setBizId(999L);\n            o.setType(\"订单\");\n            o.setSubType(\"创建订单\");\n            o.setAction(\"修改编号为 1 的用户信息\");\n            o.setCreateTime(buildTime(2021, 3, 6));\n        });\n        operateLogMapper.insert(operateLogDO);\n        // 测试 userId 不匹配\n        operateLogMapper.insert(cloneIgnoreId(operateLogDO, o -> o.setUserId(1024L)));\n        // 测试 bizId 不匹配\n        operateLogMapper.insert(cloneIgnoreId(operateLogDO, o -> o.setBizId(888L)));\n        // 测试 type 不匹配\n        operateLogMapper.insert(cloneIgnoreId(operateLogDO, o -> o.setType(\"退款\")));\n        // 测试 subType 不匹配\n        operateLogMapper.insert(cloneIgnoreId(operateLogDO, o -> o.setSubType(\"创建退款\")));\n        // 测试 action 不匹配\n        operateLogMapper.insert(cloneIgnoreId(operateLogDO, o -> o.setAction(\"修改编号为 1 退款信息\")));\n        // 测试 createTime 不匹配\n        operateLogMapper.insert(cloneIgnoreId(operateLogDO, o -> o.setCreateTime(buildTime(2021, 2, 6))));\n\n        // 构造调用参数\n        OperateLogPageReqVO reqVO = new OperateLogPageReqVO();\n        reqVO.setUserId(2048L);\n        reqVO.setBizId(999L);\n        reqVO.setType(\"订\");\n        reqVO.setSubType(\"订单\");\n        reqVO.setAction(\"用户信息\");\n        reqVO.setCreateTime(buildBetweenTime(2021, 3, 5, 2021, 3, 7));\n\n        // 调用\n        PageResult<OperateLogDO> pageResult = operateLogServiceImpl.getOperateLogPage(reqVO);\n        // 断言，只查到了一条符合条件的\n        assertEquals(1, pageResult.getTotal());\n        assertEquals(1, pageResult.getList().size());\n        assertPojoEquals(operateLogDO, pageResult.getList().get(0));\n    }\n\n    @Test\n    public void testGetOperateLogPage_dto() {\n        // 构造操作日志\n        OperateLogDO operateLogDO = RandomUtils.randomPojo(OperateLogDO.class, o -> {\n            o.setUserId(2048L);\n            o.setBizId(999L);\n            o.setType(\"订单\");\n        });\n        operateLogMapper.insert(operateLogDO);\n        // 测试 userId 不匹配\n        operateLogMapper.insert(cloneIgnoreId(operateLogDO, o -> o.setUserId(1024L)));\n        // 测试 bizId 不匹配\n        operateLogMapper.insert(cloneIgnoreId(operateLogDO, o -> o.setBizId(888L)));\n        // 测试 type 不匹配\n        operateLogMapper.insert(cloneIgnoreId(operateLogDO, o -> o.setType(\"退款\")));\n\n        // 构造调用参数\n        OperateLogPageReqDTO reqDTO = new OperateLogPageReqDTO();\n        reqDTO.setUserId(2048L);\n        reqDTO.setBizId(999L);\n        reqDTO.setType(\"订单\");\n\n        // 调用\n        PageResult<OperateLogDO> pageResult = operateLogServiceImpl.getOperateLogPage(reqDTO);\n        // 断言，只查到了一条符合条件的\n        assertEquals(1, pageResult.getTotal());\n        assertEquals(1, pageResult.getList().size());\n        assertPojoEquals(operateLogDO, pageResult.getList().get(0));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/java/co/yixiang/yshop/module/system/service/mail/MailAccountServiceImplTest.java",
    "content": "package co.yixiang.yshop.module.system.service.mail;\n\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.test.core.ut.BaseDbUnitTest;\nimport co.yixiang.yshop.module.system.controller.admin.mail.vo.account.MailAccountPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.mail.vo.account.MailAccountSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.mail.MailAccountDO;\nimport co.yixiang.yshop.module.system.dal.mysql.mail.MailAccountMapper;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.mock.mockito.MockBean;\nimport org.springframework.context.annotation.Import;\n\nimport jakarta.annotation.Resource;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.util.object.ObjectUtils.cloneIgnoreId;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertPojoEquals;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertServiceException;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.*;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.MAIL_ACCOUNT_NOT_EXISTS;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.when;\n\n/**\n * {@link MailAccountServiceImpl} 的单元测试类\n *\n * @author yshop\n */\n@Import(MailAccountServiceImpl.class)\npublic class MailAccountServiceImplTest extends BaseDbUnitTest {\n\n    @Resource\n    private MailAccountServiceImpl mailAccountService;\n\n    @Resource\n    private MailAccountMapper mailAccountMapper;\n\n    @MockBean\n    private MailTemplateService mailTemplateService;\n\n    @Test\n    public void testCreateMailAccount_success() {\n        // 准备参数\n        MailAccountSaveReqVO reqVO = randomPojo(MailAccountSaveReqVO.class, o -> o.setMail(randomEmail()))\n                .setId(null); // 防止 id 被赋值\n\n        // 调用\n        Long mailAccountId = mailAccountService.createMailAccount(reqVO);\n        // 断言\n        assertNotNull(mailAccountId);\n        // 校验记录的属性是否正确\n        MailAccountDO mailAccount = mailAccountMapper.selectById(mailAccountId);\n        assertPojoEquals(reqVO, mailAccount, \"id\");\n    }\n\n    @Test\n    public void testUpdateMailAccount_success() {\n        // mock 数据\n        MailAccountDO dbMailAccount = randomPojo(MailAccountDO.class);\n        mailAccountMapper.insert(dbMailAccount);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        MailAccountSaveReqVO reqVO = randomPojo(MailAccountSaveReqVO.class, o -> {\n            o.setId(dbMailAccount.getId()); // 设置更新的 ID\n            o.setMail(randomEmail());\n        });\n\n        // 调用\n        mailAccountService.updateMailAccount(reqVO);\n        // 校验是否更新正确\n        MailAccountDO mailAccount = mailAccountMapper.selectById(reqVO.getId()); // 获取最新的\n        assertPojoEquals(reqVO, mailAccount);\n    }\n\n    @Test\n    public void testUpdateMailAccount_notExists() {\n        // 准备参数\n        MailAccountSaveReqVO reqVO = randomPojo(MailAccountSaveReqVO.class);\n\n        // 调用, 并断言异常\n        assertServiceException(() -> mailAccountService.updateMailAccount(reqVO), MAIL_ACCOUNT_NOT_EXISTS);\n    }\n\n    @Test\n    public void testDeleteMailAccount_success() {\n        // mock 数据\n        MailAccountDO dbMailAccount = randomPojo(MailAccountDO.class);\n        mailAccountMapper.insert(dbMailAccount);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        Long id = dbMailAccount.getId();\n        // mock 方法（无关联模版）\n        when(mailTemplateService.getMailTemplateCountByAccountId(eq(id))).thenReturn(0L);\n\n        // 调用\n        mailAccountService.deleteMailAccount(id);\n        // 校验数据不存在了\n        assertNull(mailAccountMapper.selectById(id));\n    }\n\n    @Test\n    public void testGetMailAccountFromCache() {\n        // mock 数据\n        MailAccountDO dbMailAccount = randomPojo(MailAccountDO.class);\n        mailAccountMapper.insert(dbMailAccount);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        Long id = dbMailAccount.getId();\n\n        // 调用\n        MailAccountDO mailAccount = mailAccountService.getMailAccountFromCache(id);\n        // 断言\n        assertPojoEquals(dbMailAccount, mailAccount);\n    }\n\n    @Test\n    public void testDeleteMailAccount_notExists() {\n        // 准备参数\n        Long id = randomLongId();\n\n        // 调用, 并断言异常\n        assertServiceException(() -> mailAccountService.deleteMailAccount(id), MAIL_ACCOUNT_NOT_EXISTS);\n    }\n\n    @Test\n    public void testGetMailAccountPage() {\n        // mock 数据\n        MailAccountDO dbMailAccount = randomPojo(MailAccountDO.class, o -> { // 等会查询到\n            o.setMail(\"768@qq.com\");\n            o.setUsername(\"yshop\");\n        });\n        mailAccountMapper.insert(dbMailAccount);\n        // 测试 mail 不匹配\n        mailAccountMapper.insert(cloneIgnoreId(dbMailAccount, o -> o.setMail(\"788@qq.com\")));\n        // 测试 username 不匹配\n        mailAccountMapper.insert(cloneIgnoreId(dbMailAccount, o -> o.setUsername(\"tudou\")));\n        // 准备参数\n        MailAccountPageReqVO reqVO = new MailAccountPageReqVO();\n        reqVO.setMail(\"768\");\n        reqVO.setUsername(\"yu\");\n\n        // 调用\n        PageResult<MailAccountDO> pageResult = mailAccountService.getMailAccountPage(reqVO);\n        // 断言\n        assertEquals(1, pageResult.getTotal());\n        assertEquals(1, pageResult.getList().size());\n        assertPojoEquals(dbMailAccount, pageResult.getList().get(0));\n    }\n\n    @Test\n    public void testGetMailAccount() {\n        // mock 数据\n        MailAccountDO dbMailAccount = randomPojo(MailAccountDO.class);\n        mailAccountMapper.insert(dbMailAccount);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        Long id = dbMailAccount.getId();\n\n        // 调用\n        MailAccountDO mailAccount = mailAccountService.getMailAccount(id);\n        // 断言\n        assertPojoEquals(dbMailAccount, mailAccount);\n    }\n\n    @Test\n    public void testGetMailAccountList() {\n        // mock 数据\n        MailAccountDO dbMailAccount01 = randomPojo(MailAccountDO.class);\n        mailAccountMapper.insert(dbMailAccount01);\n        MailAccountDO dbMailAccount02 = randomPojo(MailAccountDO.class);\n        mailAccountMapper.insert(dbMailAccount02);\n        // 准备参数\n\n        // 调用\n        List<MailAccountDO> list = mailAccountService.getMailAccountList();\n        // 断言\n        assertEquals(2, list.size());\n        assertPojoEquals(dbMailAccount01, list.get(0));\n        assertPojoEquals(dbMailAccount02, list.get(1));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/java/co/yixiang/yshop/module/system/service/mail/MailLogServiceImplTest.java",
    "content": "package co.yixiang.yshop.module.system.service.mail;\n\nimport cn.hutool.core.map.MapUtil;\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.test.core.ut.BaseDbUnitTest;\nimport co.yixiang.yshop.module.system.controller.admin.mail.vo.log.MailLogPageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.mail.MailAccountDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.mail.MailLogDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.mail.MailTemplateDO;\nimport co.yixiang.yshop.module.system.dal.mysql.mail.MailLogMapper;\nimport co.yixiang.yshop.module.system.enums.mail.MailSendStatusEnum;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.context.annotation.Import;\n\nimport jakarta.annotation.Resource;\nimport java.util.Map;\n\nimport static cn.hutool.core.util.RandomUtil.randomEle;\nimport static co.yixiang.yshop.framework.common.util.date.LocalDateTimeUtils.buildTime;\nimport static co.yixiang.yshop.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;\nimport static co.yixiang.yshop.framework.common.util.object.ObjectUtils.cloneIgnoreId;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertPojoEquals;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.*;\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * {@link MailLogServiceImpl} 的单元测试类\n *\n * @author yshop\n */\n@Import(MailLogServiceImpl.class)\npublic class MailLogServiceImplTest extends BaseDbUnitTest {\n\n    @Resource\n    private MailLogServiceImpl mailLogService;\n\n    @Resource\n    private MailLogMapper mailLogMapper;\n\n    @Test\n    public void testCreateMailLog() {\n        // 准备参数\n        Long userId = randomLongId();\n        Integer userType = randomEle(UserTypeEnum.values()).getValue();\n        String toMail = randomEmail();\n        MailAccountDO account = randomPojo(MailAccountDO.class);\n        MailTemplateDO template = randomPojo(MailTemplateDO.class);\n        String templateContent = randomString();\n        Map<String, Object> templateParams = randomTemplateParams();\n        Boolean isSend = true;\n        // mock 方法\n\n        // 调用\n        Long logId = mailLogService.createMailLog(userId, userType, toMail, account, template, templateContent, templateParams, isSend);\n        // 断言\n        MailLogDO log = mailLogMapper.selectById(logId);\n        assertNotNull(log);\n        assertEquals(MailSendStatusEnum.INIT.getStatus(), log.getSendStatus());\n        assertEquals(userId, log.getUserId());\n        assertEquals(userType, log.getUserType());\n        assertEquals(toMail, log.getToMail());\n        assertEquals(account.getId(), log.getAccountId());\n        assertEquals(account.getMail(), log.getFromMail());\n        assertEquals(template.getId(), log.getTemplateId());\n        assertEquals(template.getCode(), log.getTemplateCode());\n        assertEquals(template.getNickname(), log.getTemplateNickname());\n        assertEquals(template.getTitle(), log.getTemplateTitle());\n        assertEquals(templateContent, log.getTemplateContent());\n        assertEquals(templateParams, log.getTemplateParams());\n    }\n\n    @Test\n    public void testUpdateMailSendResult_success() {\n        // mock 数据\n        MailLogDO log = randomPojo(MailLogDO.class, o -> {\n            o.setSendStatus(MailSendStatusEnum.INIT.getStatus());\n            o.setSendTime(null).setSendMessageId(null).setSendException(null)\n                    .setTemplateParams(randomTemplateParams());\n        });\n        mailLogMapper.insert(log);\n        // 准备参数\n        Long logId = log.getId();\n        String messageId = randomString();\n\n        // 调用\n        mailLogService.updateMailSendResult(logId, messageId, null);\n        // 断言\n        MailLogDO dbLog = mailLogMapper.selectById(logId);\n        assertEquals(MailSendStatusEnum.SUCCESS.getStatus(), dbLog.getSendStatus());\n        assertNotNull(dbLog.getSendTime());\n        assertEquals(messageId, dbLog.getSendMessageId());\n        assertNull(dbLog.getSendException());\n    }\n\n    @Test\n    public void testUpdateMailSendResult_exception() {\n        // mock 数据\n        MailLogDO log = randomPojo(MailLogDO.class, o -> {\n            o.setSendStatus(MailSendStatusEnum.INIT.getStatus());\n            o.setSendTime(null).setSendMessageId(null).setSendException(null)\n                    .setTemplateParams(randomTemplateParams());\n        });\n        mailLogMapper.insert(log);\n        // 准备参数\n        Long logId = log.getId();\n        Exception exception = new NullPointerException(\"测试异常\");\n\n        // 调用\n        mailLogService.updateMailSendResult(logId, null, exception);\n        // 断言\n        MailLogDO dbLog = mailLogMapper.selectById(logId);\n        assertEquals(MailSendStatusEnum.FAILURE.getStatus(), dbLog.getSendStatus());\n        assertNotNull(dbLog.getSendTime());\n        assertNull(dbLog.getSendMessageId());\n        assertEquals(\"NullPointerException: 测试异常\", dbLog.getSendException());\n    }\n\n    @Test\n    public void testGetMailLog() {\n        // mock 数据\n        MailLogDO dbMailLog = randomPojo(MailLogDO.class, o -> o.setTemplateParams(randomTemplateParams()));\n        mailLogMapper.insert(dbMailLog);\n        // 准备参数\n        Long id = dbMailLog.getId();\n\n        // 调用\n        MailLogDO mailLog = mailLogService.getMailLog(id);\n        // 断言\n        assertPojoEquals(dbMailLog, mailLog);\n    }\n\n    @Test\n    public void testGetMailLogPage() {\n       // mock 数据\n       MailLogDO dbMailLog = randomPojo(MailLogDO.class, o -> { // 等会查询到\n           o.setUserId(1L);\n           o.setUserType(UserTypeEnum.ADMIN.getValue());\n           o.setToMail(\"768@qq.com\");\n           o.setAccountId(10L);\n           o.setTemplateId(100L);\n           o.setSendStatus(MailSendStatusEnum.INIT.getStatus());\n           o.setSendTime(buildTime(2023, 2, 10));\n           o.setTemplateParams(randomTemplateParams());\n       });\n       mailLogMapper.insert(dbMailLog);\n       // 测试 userId 不匹配\n       mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setUserId(2L)));\n       // 测试 userType 不匹配\n       mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setUserType(UserTypeEnum.MEMBER.getValue())));\n       // 测试 toMail 不匹配\n       mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setToMail(\"788@.qq.com\")));\n       // 测试 accountId 不匹配\n       mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setAccountId(11L)));\n       // 测试 templateId 不匹配\n       mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setTemplateId(101L)));\n       // 测试 sendStatus 不匹配\n       mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setSendStatus(MailSendStatusEnum.SUCCESS.getStatus())));\n       // 测试 sendTime 不匹配\n       mailLogMapper.insert(cloneIgnoreId(dbMailLog, o -> o.setSendTime(buildTime(2023, 3, 10))));\n       // 准备参数\n       MailLogPageReqVO reqVO = new MailLogPageReqVO();\n       reqVO.setUserId(1L);\n       reqVO.setUserType(UserTypeEnum.ADMIN.getValue());\n       reqVO.setToMail(\"768\");\n       reqVO.setAccountId(10L);\n       reqVO.setTemplateId(100L);\n       reqVO.setSendStatus(MailSendStatusEnum.INIT.getStatus());\n       reqVO.setSendTime((buildBetweenTime(2023, 2, 1, 2023, 2, 15)));\n\n       // 调用\n       PageResult<MailLogDO> pageResult = mailLogService.getMailLogPage(reqVO);\n       // 断言\n       assertEquals(1, pageResult.getTotal());\n       assertEquals(1, pageResult.getList().size());\n       assertPojoEquals(dbMailLog, pageResult.getList().get(0));\n    }\n\n    private static Map<String, Object> randomTemplateParams() {\n        return MapUtil.<String, Object>builder().put(randomString(), randomString())\n                .put(randomString(), randomString()).build();\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/java/co/yixiang/yshop/module/system/service/mail/MailSendServiceImplTest.java",
    "content": "package co.yixiang.yshop.module.system.service.mail;\n\nimport cn.hutool.core.map.MapUtil;\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.test.core.ut.BaseMockitoUnitTest;\nimport co.yixiang.yshop.framework.test.core.util.RandomUtils;\nimport co.yixiang.yshop.module.system.dal.dataobject.mail.MailAccountDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.mail.MailTemplateDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.user.AdminUserDO;\nimport co.yixiang.yshop.module.system.mq.message.mail.MailSendMessage;\nimport co.yixiang.yshop.module.system.mq.producer.mail.MailProducer;\nimport co.yixiang.yshop.module.system.service.member.MemberService;\nimport co.yixiang.yshop.module.system.service.user.AdminUserService;\nimport org.assertj.core.util.Lists;\nimport org.dromara.hutool.extra.mail.*;\nimport org.junit.jupiter.api.Disabled;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\nimport org.mockito.MockedStatic;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static cn.hutool.core.util.RandomUtil.randomEle;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertServiceException;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.*;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.*;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.*;\n\npublic class MailSendServiceImplTest extends BaseMockitoUnitTest {\n\n    @InjectMocks\n    private MailSendServiceImpl mailSendService;\n\n    @Mock\n    private AdminUserService adminUserService;\n    @Mock\n    private MemberService memberService;\n    @Mock\n    private MailAccountService mailAccountService;\n    @Mock\n    private MailTemplateService mailTemplateService;\n    @Mock\n    private MailLogService mailLogService;\n    @Mock\n    private MailProducer mailProducer;\n\n    /**\n     * 用于快速测试你的邮箱账号是否正常\n     */\n    @Test\n    @Disabled\n    public void testDemo() {\n        MailAccount mailAccount = new MailAccount()\n//                .setFrom(\"奥特曼 <ydym_test@163.com>\")\n                .setFrom(\"ydym_test@163.com\") // 邮箱地址\n                .setHost(\"smtp.163.com\").setPort(465).setSslEnable(true) // SMTP 服务器\n                .setAuth(true).setUser(\"ydym_test@163.com\").setPass(\"WBZTEINMIFVRYSOE\".toCharArray()); // 登录账号密码\n        String messageId = MailUtil.send(mailAccount, \"7685413@qq.com\", \"主题\", \"内容\", false);\n        System.out.println(\"发送结果：\" + messageId);\n    }\n\n    @Test\n    public void testSendSingleMailToAdmin() {\n        // 准备参数\n        Long userId = randomLongId();\n        String templateCode = RandomUtils.randomString();\n        Map<String, Object> templateParams = MapUtil.<String, Object>builder().put(\"code\", \"1234\")\n                .put(\"op\", \"login\").build();\n        // mock adminUserService 的方法\n        AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setMobile(\"15601691300\"));\n        when(adminUserService.getUser(eq(userId))).thenReturn(user);\n\n        // mock MailTemplateService 的方法\n        MailTemplateDO template = randomPojo(MailTemplateDO.class, o -> {\n            o.setStatus(CommonStatusEnum.ENABLE.getStatus());\n            o.setContent(\"验证码为{code}, 操作为{op}\");\n            o.setParams(Lists.newArrayList(\"code\", \"op\"));\n        });\n        when(mailTemplateService.getMailTemplateByCodeFromCache(eq(templateCode))).thenReturn(template);\n        String title = RandomUtils.randomString();\n        when(mailTemplateService.formatMailTemplateContent(eq(template.getTitle()), eq(templateParams)))\n                .thenReturn(title);\n        String content = RandomUtils.randomString();\n        when(mailTemplateService.formatMailTemplateContent(eq(template.getContent()), eq(templateParams)))\n                .thenReturn(content);\n        // mock MailAccountService 的方法\n        MailAccountDO account = randomPojo(MailAccountDO.class);\n        when(mailAccountService.getMailAccountFromCache(eq(template.getAccountId()))).thenReturn(account);\n        // mock MailLogService 的方法\n        Long mailLogId = randomLongId();\n        when(mailLogService.createMailLog(eq(userId), eq(UserTypeEnum.ADMIN.getValue()), eq(user.getEmail()),\n                eq(account), eq(template), eq(content), eq(templateParams), eq(true))).thenReturn(mailLogId);\n\n        // 调用\n        Long resultMailLogId = mailSendService.sendSingleMailToAdmin(null, userId, templateCode, templateParams);\n        // 断言\n        assertEquals(mailLogId, resultMailLogId);\n        // 断言调用\n        verify(mailProducer).sendMailSendMessage(eq(mailLogId), eq(user.getEmail()),\n                eq(account.getId()), eq(template.getNickname()), eq(title), eq(content));\n    }\n\n    @Test\n    public void testSendSingleMailToMember() {\n        // 准备参数\n        Long userId = randomLongId();\n        String templateCode = RandomUtils.randomString();\n        Map<String, Object> templateParams = MapUtil.<String, Object>builder().put(\"code\", \"1234\")\n                .put(\"op\", \"login\").build();\n        // mock memberService 的方法\n        String mail = randomEmail();\n        when(memberService.getMemberUserEmail(eq(userId))).thenReturn(mail);\n\n        // mock MailTemplateService 的方法\n        MailTemplateDO template = randomPojo(MailTemplateDO.class, o -> {\n            o.setStatus(CommonStatusEnum.ENABLE.getStatus());\n            o.setContent(\"验证码为{code}, 操作为{op}\");\n            o.setParams(Lists.newArrayList(\"code\", \"op\"));\n        });\n        when(mailTemplateService.getMailTemplateByCodeFromCache(eq(templateCode))).thenReturn(template);\n        String title = RandomUtils.randomString();\n        when(mailTemplateService.formatMailTemplateContent(eq(template.getTitle()), eq(templateParams)))\n                .thenReturn(title);\n        String content = RandomUtils.randomString();\n        when(mailTemplateService.formatMailTemplateContent(eq(template.getContent()), eq(templateParams)))\n                .thenReturn(content);\n        // mock MailAccountService 的方法\n        MailAccountDO account = randomPojo(MailAccountDO.class);\n        when(mailAccountService.getMailAccountFromCache(eq(template.getAccountId()))).thenReturn(account);\n        // mock MailLogService 的方法\n        Long mailLogId = randomLongId();\n        when(mailLogService.createMailLog(eq(userId), eq(UserTypeEnum.MEMBER.getValue()), eq(mail),\n                eq(account), eq(template), eq(content), eq(templateParams), eq(true))).thenReturn(mailLogId);\n\n        // 调用\n        Long resultMailLogId = mailSendService.sendSingleMailToMember(null, userId, templateCode, templateParams);\n        // 断言\n        assertEquals(mailLogId, resultMailLogId);\n        // 断言调用\n        verify(mailProducer).sendMailSendMessage(eq(mailLogId), eq(mail),\n                eq(account.getId()), eq(template.getNickname()), eq(title), eq(content));\n    }\n\n    /**\n     * 发送成功，当短信模板开启时\n     */\n    @Test\n    public void testSendSingleMail_successWhenMailTemplateEnable() {\n        // 准备参数\n        String mail = randomEmail();\n        Long userId = randomLongId();\n        Integer userType = randomEle(UserTypeEnum.values()).getValue();\n        String templateCode = RandomUtils.randomString();\n        Map<String, Object> templateParams = MapUtil.<String, Object>builder().put(\"code\", \"1234\")\n                .put(\"op\", \"login\").build();\n        // mock MailTemplateService 的方法\n        MailTemplateDO template = randomPojo(MailTemplateDO.class, o -> {\n            o.setStatus(CommonStatusEnum.ENABLE.getStatus());\n            o.setContent(\"验证码为{code}, 操作为{op}\");\n            o.setParams(Lists.newArrayList(\"code\", \"op\"));\n        });\n        when(mailTemplateService.getMailTemplateByCodeFromCache(eq(templateCode))).thenReturn(template);\n        String title = RandomUtils.randomString();\n        when(mailTemplateService.formatMailTemplateContent(eq(template.getTitle()), eq(templateParams)))\n                .thenReturn(title);\n        String content = RandomUtils.randomString();\n        when(mailTemplateService.formatMailTemplateContent(eq(template.getContent()), eq(templateParams)))\n                .thenReturn(content);\n        // mock MailAccountService 的方法\n        MailAccountDO account = randomPojo(MailAccountDO.class);\n        when(mailAccountService.getMailAccountFromCache(eq(template.getAccountId()))).thenReturn(account);\n        // mock MailLogService 的方法\n        Long mailLogId = randomLongId();\n        when(mailLogService.createMailLog(eq(userId), eq(userType), eq(mail),\n                eq(account), eq(template), eq(content), eq(templateParams), eq(true))).thenReturn(mailLogId);\n\n        // 调用\n        Long resultMailLogId = mailSendService.sendSingleMail(mail, userId, userType, templateCode, templateParams);\n        // 断言\n        assertEquals(mailLogId, resultMailLogId);\n        // 断言调用\n        verify(mailProducer).sendMailSendMessage(eq(mailLogId), eq(mail),\n                eq(account.getId()), eq(template.getNickname()), eq(title), eq(content));\n    }\n\n    /**\n     * 发送成功，当短信模板关闭时\n     */\n    @Test\n    public void testSendSingleMail_successWhenSmsTemplateDisable() {\n        // 准备参数\n        String mail = randomEmail();\n        Long userId = randomLongId();\n        Integer userType = randomEle(UserTypeEnum.values()).getValue();\n        String templateCode = RandomUtils.randomString();\n        Map<String, Object> templateParams = MapUtil.<String, Object>builder().put(\"code\", \"1234\")\n                .put(\"op\", \"login\").build();\n        // mock MailTemplateService 的方法\n        MailTemplateDO template = randomPojo(MailTemplateDO.class, o -> {\n            o.setStatus(CommonStatusEnum.DISABLE.getStatus());\n            o.setContent(\"验证码为{code}, 操作为{op}\");\n            o.setParams(Lists.newArrayList(\"code\", \"op\"));\n        });\n        when(mailTemplateService.getMailTemplateByCodeFromCache(eq(templateCode))).thenReturn(template);\n        String title = RandomUtils.randomString();\n        when(mailTemplateService.formatMailTemplateContent(eq(template.getTitle()), eq(templateParams)))\n                .thenReturn(title);\n        String content = RandomUtils.randomString();\n        when(mailTemplateService.formatMailTemplateContent(eq(template.getContent()), eq(templateParams)))\n                .thenReturn(content);\n        // mock MailAccountService 的方法\n        MailAccountDO account = randomPojo(MailAccountDO.class);\n        when(mailAccountService.getMailAccountFromCache(eq(template.getAccountId()))).thenReturn(account);\n        // mock MailLogService 的方法\n        Long mailLogId = randomLongId();\n        when(mailLogService.createMailLog(eq(userId), eq(userType), eq(mail),\n                eq(account), eq(template), eq(content), eq(templateParams), eq(false))).thenReturn(mailLogId);\n\n        // 调用\n        Long resultMailLogId = mailSendService.sendSingleMail(mail, userId, userType, templateCode, templateParams);\n        // 断言\n        assertEquals(mailLogId, resultMailLogId);\n        // 断言调用\n        verify(mailProducer, times(0)).sendMailSendMessage(anyLong(), anyString(),\n                anyLong(), anyString(), anyString(), anyString());\n    }\n\n    @Test\n    public void testValidateMailTemplateValid_notExists() {\n        // 准备参数\n        String templateCode = RandomUtils.randomString();\n        // mock 方法\n\n        // 调用，并断言异常\n        assertServiceException(() -> mailSendService.validateMailTemplate(templateCode),\n                MAIL_TEMPLATE_NOT_EXISTS);\n    }\n\n    @Test\n    public void testValidateTemplateParams_paramMiss() {\n        // 准备参数\n        MailTemplateDO template = randomPojo(MailTemplateDO.class,\n                o -> o.setParams(Lists.newArrayList(\"code\")));\n        Map<String, Object> templateParams = new HashMap<>();\n        // mock 方法\n\n        // 调用，并断言异常\n        assertServiceException(() -> mailSendService.validateTemplateParams(template, templateParams),\n                MAIL_SEND_TEMPLATE_PARAM_MISS, \"code\");\n    }\n\n    @Test\n    public void testValidateMail_notExists() {\n        // 准备参数\n        // mock 方法\n\n        // 调用，并断言异常\n        assertServiceException(() -> mailSendService.validateMail(null),\n                MAIL_SEND_MAIL_NOT_EXISTS);\n    }\n\n    @Test\n    public void testDoSendMail_success() {\n        try (final MockedStatic<MailUtil> mailUtilMock = mockStatic(MailUtil.class)) {\n            // 准备参数\n            MailSendMessage message = randomPojo(MailSendMessage.class, o -> o.setNickname(\"yshop\"));\n            // mock 方法（获得邮箱账号）\n            MailAccountDO account = randomPojo(MailAccountDO.class, o -> o.setMail(\"7685@qq.com\"));\n            when(mailAccountService.getMailAccountFromCache(eq(message.getAccountId())))\n                    .thenReturn(account);\n\n            // mock 方法（发送邮件）\n            String messageId = randomString();\n            mailUtilMock.when(() -> MailUtil.send(\n                    argThat(mailAccount -> {\n                        assertEquals(\"yshop <7685@qq.com>\", mailAccount.getFrom());\n                        assertTrue(mailAccount.isAuth());\n                        assertEquals(account.getUsername(), mailAccount.getUser());\n                        assertArrayEquals(account.getPassword().toCharArray(), mailAccount.getPass());\n                        assertEquals(account.getHost(), mailAccount.getHost());\n                        assertEquals(account.getPort(), mailAccount.getPort());\n                        assertEquals(account.getSslEnable(), mailAccount.isSslEnable());\n                        return true;\n                    }), eq(message.getMail()), eq(message.getTitle()), eq(message.getContent()), eq(true)))\n                    .thenReturn(messageId);\n\n            // 调用\n            mailSendService.doSendMail(message);\n            // 断言\n            verify(mailLogService).updateMailSendResult(eq(message.getLogId()), eq(messageId), isNull());\n        }\n    }\n\n    @Test\n    public void testDoSendMail_exception() {\n        try (MockedStatic<MailUtil> mailUtilMock = mockStatic(MailUtil.class)) {\n            // 准备参数\n            MailSendMessage message = randomPojo(MailSendMessage.class, o -> o.setNickname(\"yshop\"));\n            // mock 方法（获得邮箱账号）\n            MailAccountDO account = randomPojo(MailAccountDO.class, o -> o.setMail(\"7685@qq.com\"));\n            when(mailAccountService.getMailAccountFromCache(eq(message.getAccountId())))\n                    .thenReturn(account);\n\n            // mock 方法（发送邮件）\n            Exception e = new NullPointerException(\"啦啦啦\");\n            mailUtilMock.when(() -> MailUtil.send(argThat(mailAccount -> {\n                assertEquals(\"yshop <7685@qq.com>\", mailAccount.getFrom());\n                assertTrue(mailAccount.isAuth());\n                assertEquals(account.getUsername(), mailAccount.getUser());\n                assertArrayEquals(account.getPassword().toCharArray(), mailAccount.getPass());\n                assertEquals(account.getHost(), mailAccount.getHost());\n                assertEquals(account.getPort(), mailAccount.getPort());\n                assertEquals(account.getSslEnable(), mailAccount.isSslEnable());\n                return true;\n            }), eq(message.getMail()), eq(message.getTitle()), eq(message.getContent()), eq(true))).thenThrow(e);\n\n            // 调用\n            mailSendService.doSendMail(message);\n            // 断言\n            verify(mailLogService).updateMailSendResult(eq(message.getLogId()), isNull(), same(e));\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/java/co/yixiang/yshop/module/system/service/mail/MailTemplateServiceImplTest.java",
    "content": "package co.yixiang.yshop.module.system.service.mail;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.test.core.ut.BaseDbUnitTest;\nimport co.yixiang.yshop.module.system.controller.admin.mail.vo.template.MailTemplateSaveReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.mail.vo.template.MailTemplatePageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.mail.MailTemplateDO;\nimport co.yixiang.yshop.module.system.dal.mysql.mail.MailTemplateMapper;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.context.annotation.Import;\n\nimport jakarta.annotation.Resource;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport static co.yixiang.yshop.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;\nimport static co.yixiang.yshop.framework.common.util.date.LocalDateTimeUtils.buildTime;\nimport static co.yixiang.yshop.framework.common.util.object.ObjectUtils.cloneIgnoreId;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertPojoEquals;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertServiceException;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.randomLongId;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.randomPojo;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.MAIL_TEMPLATE_NOT_EXISTS;\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * {@link MailTemplateServiceImpl} 的单元测试类\n *\n * @author yshop\n */\n@Import(MailTemplateServiceImpl.class)\npublic class MailTemplateServiceImplTest extends BaseDbUnitTest {\n\n    @Resource\n    private MailTemplateServiceImpl mailTemplateService;\n\n    @Resource\n    private MailTemplateMapper mailTemplateMapper;\n\n    @Test\n    public void testCreateMailTemplate_success() {\n        // 准备参数\n        MailTemplateSaveReqVO reqVO = randomPojo(MailTemplateSaveReqVO.class)\n                .setId(null); // 防止 id 被赋值\n\n        // 调用\n        Long mailTemplateId = mailTemplateService.createMailTemplate(reqVO);\n        // 断言\n        assertNotNull(mailTemplateId);\n        // 校验记录的属性是否正确\n        MailTemplateDO mailTemplate = mailTemplateMapper.selectById(mailTemplateId);\n        assertPojoEquals(reqVO, mailTemplate, \"id\");\n    }\n\n    @Test\n    public void testUpdateMailTemplate_success() {\n        // mock 数据\n        MailTemplateDO dbMailTemplate = randomPojo(MailTemplateDO.class);\n        mailTemplateMapper.insert(dbMailTemplate);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        MailTemplateSaveReqVO reqVO = randomPojo(MailTemplateSaveReqVO.class, o -> {\n            o.setId(dbMailTemplate.getId()); // 设置更新的 ID\n        });\n\n        // 调用\n        mailTemplateService.updateMailTemplate(reqVO);\n        // 校验是否更新正确\n        MailTemplateDO mailTemplate = mailTemplateMapper.selectById(reqVO.getId()); // 获取最新的\n        assertPojoEquals(reqVO, mailTemplate);\n    }\n\n    @Test\n    public void testUpdateMailTemplate_notExists() {\n        // 准备参数\n        MailTemplateSaveReqVO reqVO = randomPojo(MailTemplateSaveReqVO.class);\n\n        // 调用, 并断言异常\n        assertServiceException(() -> mailTemplateService.updateMailTemplate(reqVO), MAIL_TEMPLATE_NOT_EXISTS);\n    }\n\n    @Test\n    public void testDeleteMailTemplate_success() {\n        // mock 数据\n        MailTemplateDO dbMailTemplate = randomPojo(MailTemplateDO.class);\n        mailTemplateMapper.insert(dbMailTemplate);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        Long id = dbMailTemplate.getId();\n\n        // 调用\n        mailTemplateService.deleteMailTemplate(id);\n        // 校验数据不存在了\n        assertNull(mailTemplateMapper.selectById(id));\n    }\n\n    @Test\n    public void testDeleteMailTemplate_notExists() {\n        // 准备参数\n        Long id = randomLongId();\n\n        // 调用, 并断言异常\n        assertServiceException(() -> mailTemplateService.deleteMailTemplate(id), MAIL_TEMPLATE_NOT_EXISTS);\n    }\n\n    @Test\n    public void testGetMailTemplatePage() {\n        // mock 数据\n        MailTemplateDO dbMailTemplate = randomPojo(MailTemplateDO.class, o -> { // 等会查询到\n            o.setName(\"源码\");\n            o.setCode(\"test_01\");\n            o.setAccountId(1L);\n            o.setStatus(CommonStatusEnum.ENABLE.getStatus());\n            o.setCreateTime(buildTime(2023, 2, 3));\n        });\n        mailTemplateMapper.insert(dbMailTemplate);\n        // 测试 name 不匹配\n        mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setName(\"yshop\")));\n        // 测试 code 不匹配\n        mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setCode(\"test_02\")));\n        // 测试 accountId 不匹配\n        mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setAccountId(2L)));\n        // 测试 status 不匹配\n        mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));\n        // 测试 createTime 不匹配\n        mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setCreateTime(buildTime(2023, 1, 5))));\n        // 准备参数\n        MailTemplatePageReqVO reqVO = new MailTemplatePageReqVO();\n        reqVO.setName(\"源\");\n        reqVO.setCode(\"est_01\");\n        reqVO.setAccountId(1L);\n        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());\n        reqVO.setCreateTime(buildBetweenTime(2023, 2, 1, 2023, 2, 5));\n\n        // 调用\n        PageResult<MailTemplateDO> pageResult = mailTemplateService.getMailTemplatePage(reqVO);\n        // 断言\n        assertEquals(1, pageResult.getTotal());\n        assertEquals(1, pageResult.getList().size());\n        assertPojoEquals(dbMailTemplate, pageResult.getList().get(0));\n    }\n\n    @Test\n    public void testGetMailTemplateList() {\n        // mock 数据\n        MailTemplateDO dbMailTemplate01 = randomPojo(MailTemplateDO.class);\n        mailTemplateMapper.insert(dbMailTemplate01);\n        MailTemplateDO dbMailTemplate02 = randomPojo(MailTemplateDO.class);\n        mailTemplateMapper.insert(dbMailTemplate02);\n\n        // 调用\n        List<MailTemplateDO> list = mailTemplateService.getMailTemplateList();\n        // 断言\n        assertEquals(2, list.size());\n        assertEquals(dbMailTemplate01, list.get(0));\n        assertEquals(dbMailTemplate02, list.get(1));\n    }\n\n    @Test\n    public void testGetMailTemplate() {\n        // mock 数据\n        MailTemplateDO dbMailTemplate = randomPojo(MailTemplateDO.class);\n        mailTemplateMapper.insert(dbMailTemplate);\n        // 准备参数\n        Long id = dbMailTemplate.getId();\n\n        // 调用\n        MailTemplateDO mailTemplate = mailTemplateService.getMailTemplate(id);\n        // 断言\n        assertPojoEquals(dbMailTemplate, mailTemplate);\n    }\n\n    @Test\n    public void testGetMailTemplateByCodeFromCache() {\n        // mock 数据\n        MailTemplateDO dbMailTemplate = randomPojo(MailTemplateDO.class);\n        mailTemplateMapper.insert(dbMailTemplate);\n        // 准备参数\n        String code = dbMailTemplate.getCode();\n\n        // 调用\n        MailTemplateDO mailTemplate = mailTemplateService.getMailTemplateByCodeFromCache(code);\n        // 断言\n        assertPojoEquals(dbMailTemplate, mailTemplate);\n    }\n\n    @Test\n    public void testFormatMailTemplateContent() {\n        // 准备参数\n        Map<String, Object> params = new HashMap<>();\n        params.put(\"name\", \"小红\");\n        params.put(\"what\", \"饭\");\n\n        // 调用，并断言\n        assertEquals(\"小红，你好，饭吃了吗？\",\n                mailTemplateService.formatMailTemplateContent(\"{name}，你好，{what}吃了吗？\", params));\n    }\n\n    @Test\n    public void testCountByAccountId() {\n        // mock 数据\n        MailTemplateDO dbMailTemplate = randomPojo(MailTemplateDO.class);\n        mailTemplateMapper.insert(dbMailTemplate);\n        // 测试 accountId 不匹配\n        mailTemplateMapper.insert(cloneIgnoreId(dbMailTemplate, o -> o.setAccountId(2L)));\n        // 准备参数\n        Long accountId = dbMailTemplate.getAccountId();\n\n        // 调用\n        long count = mailTemplateService.getMailTemplateCountByAccountId(accountId);\n        // 断言\n        assertEquals(1, count);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/java/co/yixiang/yshop/module/system/service/notice/NoticeServiceImplTest.java",
    "content": "package co.yixiang.yshop.module.system.service.notice;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.test.core.ut.BaseDbUnitTest;\nimport co.yixiang.yshop.module.system.controller.admin.notice.vo.NoticePageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.notice.vo.NoticeSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.notice.NoticeDO;\nimport co.yixiang.yshop.module.system.dal.mysql.notice.NoticeMapper;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.context.annotation.Import;\n\nimport jakarta.annotation.Resource;\n\nimport static co.yixiang.yshop.framework.common.util.object.ObjectUtils.cloneIgnoreId;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertPojoEquals;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertServiceException;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.randomLongId;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.randomPojo;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.NOTICE_NOT_FOUND;\nimport static org.junit.jupiter.api.Assertions.*;\n\n@Import(NoticeServiceImpl.class)\nclass NoticeServiceImplTest extends BaseDbUnitTest {\n\n    @Resource\n    private NoticeServiceImpl noticeService;\n\n    @Resource\n    private NoticeMapper noticeMapper;\n\n    @Test\n    public void testGetNoticePage_success() {\n        // 插入前置数据\n        NoticeDO dbNotice = randomPojo(NoticeDO.class, o -> {\n            o.setTitle(\"尼古拉斯赵四来啦！\");\n            o.setStatus(CommonStatusEnum.ENABLE.getStatus());\n        });\n        noticeMapper.insert(dbNotice);\n        // 测试 title 不匹配\n        noticeMapper.insert(cloneIgnoreId(dbNotice, o -> o.setTitle(\"尼古拉斯凯奇也来啦！\")));\n        // 测试 status 不匹配\n        noticeMapper.insert(cloneIgnoreId(dbNotice, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));\n        // 准备参数\n        NoticePageReqVO reqVO = new NoticePageReqVO();\n        reqVO.setTitle(\"尼古拉斯赵四来啦！\");\n        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());\n\n        // 调用\n        PageResult<NoticeDO> pageResult = noticeService.getNoticePage(reqVO);\n        // 验证查询结果经过筛选\n        assertEquals(1, pageResult.getTotal());\n        assertEquals(1, pageResult.getList().size());\n        assertPojoEquals(dbNotice, pageResult.getList().get(0));\n    }\n\n    @Test\n    public void testGetNotice_success() {\n        // 插入前置数据\n        NoticeDO dbNotice = randomPojo(NoticeDO.class);\n        noticeMapper.insert(dbNotice);\n\n        // 查询\n        NoticeDO notice = noticeService.getNotice(dbNotice.getId());\n\n        // 验证插入与读取对象是否一致\n        assertNotNull(notice);\n        assertPojoEquals(dbNotice, notice);\n    }\n\n    @Test\n    public void testCreateNotice_success() {\n        // 准备参数\n        NoticeSaveReqVO reqVO = randomPojo(NoticeSaveReqVO.class)\n                .setId(null); // 避免 id 被赋值\n\n        // 调用\n        Long noticeId = noticeService.createNotice(reqVO);\n        // 校验插入属性是否正确\n        assertNotNull(noticeId);\n        NoticeDO notice = noticeMapper.selectById(noticeId);\n        assertPojoEquals(reqVO, notice, \"id\");\n    }\n\n    @Test\n    public void testUpdateNotice_success() {\n        // 插入前置数据\n        NoticeDO dbNoticeDO = randomPojo(NoticeDO.class);\n        noticeMapper.insert(dbNoticeDO);\n\n        // 准备更新参数\n        NoticeSaveReqVO reqVO = randomPojo(NoticeSaveReqVO.class, o -> o.setId(dbNoticeDO.getId()));\n\n        // 更新\n        noticeService.updateNotice(reqVO);\n        // 检验是否更新成功\n        NoticeDO notice = noticeMapper.selectById(reqVO.getId());\n        assertPojoEquals(reqVO, notice);\n    }\n\n    @Test\n    public void testDeleteNotice_success() {\n        // 插入前置数据\n        NoticeDO dbNotice = randomPojo(NoticeDO.class);\n        noticeMapper.insert(dbNotice);\n\n        // 删除\n        noticeService.deleteNotice(dbNotice.getId());\n\n        // 检查是否删除成功\n        assertNull(noticeMapper.selectById(dbNotice.getId()));\n    }\n\n    @Test\n    public void testValidateNoticeExists_success() {\n        // 插入前置数据\n        NoticeDO dbNotice = randomPojo(NoticeDO.class);\n        noticeMapper.insert(dbNotice);\n\n        // 成功调用\n        noticeService.validateNoticeExists(dbNotice.getId());\n    }\n\n    @Test\n    public void testValidateNoticeExists_noExists() {\n        assertServiceException(() ->\n                noticeService.validateNoticeExists(randomLongId()), NOTICE_NOT_FOUND);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/java/co/yixiang/yshop/module/system/service/notify/NotifyMessageServiceImplTest.java",
    "content": "package co.yixiang.yshop.module.system.service.notify;\n\nimport cn.hutool.core.map.MapUtil;\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.mybatis.core.enums.SqlConstants;\nimport co.yixiang.yshop.framework.test.core.ut.BaseDbUnitTest;\nimport co.yixiang.yshop.module.system.controller.admin.notify.vo.message.NotifyMessageMyPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.notify.vo.message.NotifyMessagePageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.notify.NotifyMessageDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.notify.NotifyTemplateDO;\nimport co.yixiang.yshop.module.system.dal.mysql.notify.NotifyMessageMapper;\nimport com.baomidou.mybatisplus.annotation.DbType;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.context.annotation.Import;\n\nimport jakarta.annotation.Resource;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Map;\n\nimport static cn.hutool.core.util.RandomUtil.randomEle;\nimport static co.yixiang.yshop.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;\nimport static co.yixiang.yshop.framework.common.util.date.LocalDateTimeUtils.buildTime;\nimport static co.yixiang.yshop.framework.common.util.object.ObjectUtils.cloneIgnoreId;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertPojoEquals;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.*;\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n* {@link NotifyMessageServiceImpl} 的单元测试类\n*\n* @author yshop\n*/\n@Import(NotifyMessageServiceImpl.class)\npublic class NotifyMessageServiceImplTest extends BaseDbUnitTest {\n\n    @Resource\n    private NotifyMessageServiceImpl notifyMessageService;\n\n    @Resource\n    private NotifyMessageMapper notifyMessageMapper;\n\n    @Test\n    public void testCreateNotifyMessage_success() {\n        // 准备参数\n        Long userId = randomLongId();\n        Integer userType = randomEle(UserTypeEnum.values()).getValue();\n        NotifyTemplateDO template = randomPojo(NotifyTemplateDO.class);\n        String templateContent = randomString();\n        Map<String, Object> templateParams = randomTemplateParams();\n        // mock 方法\n\n        // 调用\n        Long messageId = notifyMessageService.createNotifyMessage(userId, userType,\n                template, templateContent, templateParams);\n        // 断言\n        NotifyMessageDO message = notifyMessageMapper.selectById(messageId);\n        assertNotNull(message);\n        assertEquals(userId, message.getUserId());\n        assertEquals(userType, message.getUserType());\n        assertEquals(template.getId(), message.getTemplateId());\n        assertEquals(template.getCode(), message.getTemplateCode());\n        assertEquals(template.getType(), message.getTemplateType());\n        assertEquals(template.getNickname(), message.getTemplateNickname());\n        assertEquals(templateContent, message.getTemplateContent());\n        assertEquals(templateParams, message.getTemplateParams());\n        assertEquals(false, message.getReadStatus());\n        assertNull(message.getReadTime());\n    }\n\n    @Test\n    public void testGetNotifyMessagePage() {\n       // mock 数据\n       NotifyMessageDO dbNotifyMessage = randomPojo(NotifyMessageDO.class, o -> { // 等会查询到\n           o.setUserId(1L);\n           o.setUserType(UserTypeEnum.ADMIN.getValue());\n           o.setTemplateCode(\"test_01\");\n           o.setTemplateType(10);\n           o.setCreateTime(buildTime(2022, 1, 2));\n           o.setTemplateParams(randomTemplateParams());\n       });\n       notifyMessageMapper.insert(dbNotifyMessage);\n       // 测试 userId 不匹配\n       notifyMessageMapper.insert(cloneIgnoreId(dbNotifyMessage, o -> o.setUserId(2L)));\n       // 测试 userType 不匹配\n       notifyMessageMapper.insert(cloneIgnoreId(dbNotifyMessage, o -> o.setUserType(UserTypeEnum.MEMBER.getValue())));\n       // 测试 templateCode 不匹配\n       notifyMessageMapper.insert(cloneIgnoreId(dbNotifyMessage, o -> o.setTemplateCode(\"test_11\")));\n       // 测试 templateType 不匹配\n       notifyMessageMapper.insert(cloneIgnoreId(dbNotifyMessage, o -> o.setTemplateType(20)));\n       // 测试 createTime 不匹配\n       notifyMessageMapper.insert(cloneIgnoreId(dbNotifyMessage, o -> o.setCreateTime(buildTime(2022, 2, 1))));\n       // 准备参数\n       NotifyMessagePageReqVO reqVO = new NotifyMessagePageReqVO();\n       reqVO.setUserId(1L);\n       reqVO.setUserType(UserTypeEnum.ADMIN.getValue());\n       reqVO.setTemplateCode(\"est_01\");\n       reqVO.setTemplateType(10);\n       reqVO.setCreateTime(buildBetweenTime(2022, 1, 1, 2022, 1, 10));\n\n       // 调用\n       PageResult<NotifyMessageDO> pageResult = notifyMessageService.getNotifyMessagePage(reqVO);\n       // 断言\n       assertEquals(1, pageResult.getTotal());\n       assertEquals(1, pageResult.getList().size());\n       assertPojoEquals(dbNotifyMessage, pageResult.getList().get(0));\n    }\n\n    @Test\n    public void testGetNotifyMessage() {\n        // mock 数据\n        NotifyMessageDO dbNotifyMessage = randomPojo(NotifyMessageDO.class,\n                o -> o.setTemplateParams(randomTemplateParams()));\n        notifyMessageMapper.insert(dbNotifyMessage);\n        // 准备参数\n        Long id = dbNotifyMessage.getId();\n\n        // 调用\n        NotifyMessageDO notifyMessage = notifyMessageService.getNotifyMessage(id);\n        assertPojoEquals(dbNotifyMessage, notifyMessage);\n    }\n\n    @Test\n    public void testGetMyNotifyMessagePage() {\n        // mock 数据\n        NotifyMessageDO dbNotifyMessage = randomPojo(NotifyMessageDO.class, o -> { // 等会查询到\n            o.setUserId(1L);\n            o.setUserType(UserTypeEnum.ADMIN.getValue());\n            o.setReadStatus(true);\n            o.setCreateTime(buildTime(2022, 1, 2));\n            o.setTemplateParams(randomTemplateParams());\n        });\n        notifyMessageMapper.insert(dbNotifyMessage);\n        // 测试 userId 不匹配\n        notifyMessageMapper.insert(cloneIgnoreId(dbNotifyMessage, o -> o.setUserId(2L)));\n        // 测试 userType 不匹配\n        notifyMessageMapper.insert(cloneIgnoreId(dbNotifyMessage, o -> o.setUserType(UserTypeEnum.MEMBER.getValue())));\n        // 测试 readStatus 不匹配\n        notifyMessageMapper.insert(cloneIgnoreId(dbNotifyMessage, o -> o.setReadStatus(false)));\n        // 测试 createTime 不匹配\n        notifyMessageMapper.insert(cloneIgnoreId(dbNotifyMessage, o -> o.setCreateTime(buildTime(2022, 2, 1))));\n        // 准备参数\n        Long userId = 1L;\n        Integer userType = UserTypeEnum.ADMIN.getValue();\n        NotifyMessageMyPageReqVO reqVO = new NotifyMessageMyPageReqVO();\n        reqVO.setReadStatus(true);\n        reqVO.setCreateTime(buildBetweenTime(2022, 1, 1, 2022, 1, 10));\n\n        // 调用\n        PageResult<NotifyMessageDO> pageResult = notifyMessageService.getMyMyNotifyMessagePage(reqVO, userId, userType);\n        // 断言\n        assertEquals(1, pageResult.getTotal());\n        assertEquals(1, pageResult.getList().size());\n        assertPojoEquals(dbNotifyMessage, pageResult.getList().get(0));\n    }\n\n    @Test\n    public void testGetUnreadNotifyMessageList() {\n        SqlConstants.init(DbType.MYSQL);\n        // mock 数据\n        NotifyMessageDO dbNotifyMessage = randomPojo(NotifyMessageDO.class, o -> { // 等会查询到\n            o.setUserId(1L);\n            o.setUserType(UserTypeEnum.ADMIN.getValue());\n            o.setReadStatus(false);\n            o.setTemplateParams(randomTemplateParams());\n        });\n        notifyMessageMapper.insert(dbNotifyMessage);\n        // 测试 userId 不匹配\n        notifyMessageMapper.insert(cloneIgnoreId(dbNotifyMessage, o -> o.setUserId(2L)));\n        // 测试 userType 不匹配\n        notifyMessageMapper.insert(cloneIgnoreId(dbNotifyMessage, o -> o.setUserType(UserTypeEnum.MEMBER.getValue())));\n        // 测试 readStatus 不匹配\n        notifyMessageMapper.insert(cloneIgnoreId(dbNotifyMessage, o -> o.setReadStatus(true)));\n        // 准备参数\n        Long userId = 1L;\n        Integer userType = UserTypeEnum.ADMIN.getValue();\n        Integer size = 10;\n\n        // 调用\n        List<NotifyMessageDO> list = notifyMessageService.getUnreadNotifyMessageList(userId, userType, size);\n        // 断言\n        assertEquals(1, list.size());\n        assertPojoEquals(dbNotifyMessage, list.get(0));\n    }\n\n    @Test\n    public void testGetUnreadNotifyMessageCount() {\n        SqlConstants.init(DbType.MYSQL);\n        // mock 数据\n        NotifyMessageDO dbNotifyMessage = randomPojo(NotifyMessageDO.class, o -> { // 等会查询到\n            o.setUserId(1L);\n            o.setUserType(UserTypeEnum.ADMIN.getValue());\n            o.setReadStatus(false);\n            o.setTemplateParams(randomTemplateParams());\n        });\n        notifyMessageMapper.insert(dbNotifyMessage);\n        // 测试 userId 不匹配\n        notifyMessageMapper.insert(cloneIgnoreId(dbNotifyMessage, o -> o.setUserId(2L)));\n        // 测试 userType 不匹配\n        notifyMessageMapper.insert(cloneIgnoreId(dbNotifyMessage, o -> o.setUserType(UserTypeEnum.MEMBER.getValue())));\n        // 测试 readStatus 不匹配\n        notifyMessageMapper.insert(cloneIgnoreId(dbNotifyMessage, o -> o.setReadStatus(true)));\n        // 准备参数\n        Long userId = 1L;\n        Integer userType = UserTypeEnum.ADMIN.getValue();\n\n        // 调用，并断言\n        assertEquals(1, notifyMessageService.getUnreadNotifyMessageCount(userId, userType));\n    }\n\n    @Test\n    public void testUpdateNotifyMessageRead() {\n        // mock 数据\n        NotifyMessageDO dbNotifyMessage = randomPojo(NotifyMessageDO.class, o -> { // 等会查询到\n            o.setUserId(1L);\n            o.setUserType(UserTypeEnum.ADMIN.getValue());\n            o.setReadStatus(false);\n            o.setReadTime(null);\n            o.setTemplateParams(randomTemplateParams());\n        });\n        notifyMessageMapper.insert(dbNotifyMessage);\n        // 测试 userId 不匹配\n        notifyMessageMapper.insert(cloneIgnoreId(dbNotifyMessage, o -> o.setUserId(2L)));\n        // 测试 userType 不匹配\n        notifyMessageMapper.insert(cloneIgnoreId(dbNotifyMessage, o -> o.setUserType(UserTypeEnum.MEMBER.getValue())));\n        // 测试 readStatus 不匹配\n        notifyMessageMapper.insert(cloneIgnoreId(dbNotifyMessage, o -> o.setReadStatus(true)));\n        // 准备参数\n        Collection<Long> ids = Arrays.asList(dbNotifyMessage.getId(), dbNotifyMessage.getId() + 1,\n                dbNotifyMessage.getId() + 2, dbNotifyMessage.getId() + 3);\n        Long userId = 1L;\n        Integer userType = UserTypeEnum.ADMIN.getValue();\n\n        // 调用\n        int updateCount = notifyMessageService.updateNotifyMessageRead(ids, userId, userType);\n        // 断言\n        assertEquals(1, updateCount);\n        NotifyMessageDO notifyMessage = notifyMessageMapper.selectById(dbNotifyMessage.getId());\n        assertTrue(notifyMessage.getReadStatus());\n        assertNotNull(notifyMessage.getReadTime());\n    }\n\n    @Test\n    public void testUpdateAllNotifyMessageRead() {\n        // mock 数据\n        NotifyMessageDO dbNotifyMessage = randomPojo(NotifyMessageDO.class, o -> { // 等会查询到\n            o.setUserId(1L);\n            o.setUserType(UserTypeEnum.ADMIN.getValue());\n            o.setReadStatus(false);\n            o.setReadTime(null);\n            o.setTemplateParams(randomTemplateParams());\n        });\n        notifyMessageMapper.insert(dbNotifyMessage);\n        // 测试 userId 不匹配\n        notifyMessageMapper.insert(cloneIgnoreId(dbNotifyMessage, o -> o.setUserId(2L)));\n        // 测试 userType 不匹配\n        notifyMessageMapper.insert(cloneIgnoreId(dbNotifyMessage, o -> o.setUserType(UserTypeEnum.MEMBER.getValue())));\n        // 测试 readStatus 不匹配\n        notifyMessageMapper.insert(cloneIgnoreId(dbNotifyMessage, o -> o.setReadStatus(true)));\n        // 准备参数\n        Long userId = 1L;\n        Integer userType = UserTypeEnum.ADMIN.getValue();\n\n        // 调用\n        int updateCount = notifyMessageService.updateAllNotifyMessageRead(userId, userType);\n        // 断言\n        assertEquals(1, updateCount);\n        NotifyMessageDO notifyMessage = notifyMessageMapper.selectById(dbNotifyMessage.getId());\n        assertTrue(notifyMessage.getReadStatus());\n        assertNotNull(notifyMessage.getReadTime());\n    }\n\n    private static Map<String, Object> randomTemplateParams() {\n        return MapUtil.<String, Object>builder().put(randomString(), randomString())\n                .put(randomString(), randomString()).build();\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/java/co/yixiang/yshop/module/system/service/notify/NotifySendServiceImplTest.java",
    "content": "package co.yixiang.yshop.module.system.service.notify;\n\nimport cn.hutool.core.map.MapUtil;\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.test.core.ut.BaseMockitoUnitTest;\nimport co.yixiang.yshop.module.system.dal.dataobject.notify.NotifyTemplateDO;\nimport org.assertj.core.util.Lists;\nimport org.junit.jupiter.api.Assertions;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\n\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static cn.hutool.core.util.RandomUtil.randomEle;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertServiceException;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.*;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.NOTICE_NOT_FOUND;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.NOTIFY_SEND_TEMPLATE_PARAM_MISS;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.*;\n\nclass NotifySendServiceImplTest extends BaseMockitoUnitTest {\n\n    @InjectMocks\n    private NotifySendServiceImpl notifySendService;\n\n    @Mock\n    private NotifyTemplateService notifyTemplateService;\n    @Mock\n    private NotifyMessageService notifyMessageService;\n\n    @Test\n    public void testSendSingleNotifyToAdmin() {\n        // 准备参数\n        Long userId = randomLongId();\n        String templateCode = randomString();\n        Map<String, Object> templateParams = MapUtil.<String, Object>builder().put(\"code\", \"1234\")\n                .put(\"op\", \"login\").build();\n        // mock NotifyTemplateService 的方法\n        NotifyTemplateDO template = randomPojo(NotifyTemplateDO.class, o -> {\n            o.setStatus(CommonStatusEnum.ENABLE.getStatus());\n            o.setContent(\"验证码为{code}, 操作为{op}\");\n            o.setParams(Lists.newArrayList(\"code\", \"op\"));\n        });\n        when(notifyTemplateService.getNotifyTemplateByCodeFromCache(eq(templateCode))).thenReturn(template);\n        String content = randomString();\n        when(notifyTemplateService.formatNotifyTemplateContent(eq(template.getContent()), eq(templateParams)))\n                .thenReturn(content);\n        // mock NotifyMessageService 的方法\n        Long messageId = randomLongId();\n        when(notifyMessageService.createNotifyMessage(eq(userId), eq(UserTypeEnum.ADMIN.getValue()),\n                eq(template), eq(content), eq(templateParams))).thenReturn(messageId);\n\n        // 调用\n        Long resultMessageId = notifySendService.sendSingleNotifyToAdmin(userId, templateCode, templateParams);\n        // 断言\n        assertEquals(messageId, resultMessageId);\n    }\n\n    @Test\n    public void testSendSingleNotifyToMember() {\n        // 准备参数\n        Long userId = randomLongId();\n        String templateCode = randomString();\n        Map<String, Object> templateParams = MapUtil.<String, Object>builder().put(\"code\", \"1234\")\n                .put(\"op\", \"login\").build();\n        // mock NotifyTemplateService 的方法\n        NotifyTemplateDO template = randomPojo(NotifyTemplateDO.class, o -> {\n            o.setStatus(CommonStatusEnum.ENABLE.getStatus());\n            o.setContent(\"验证码为{code}, 操作为{op}\");\n            o.setParams(Lists.newArrayList(\"code\", \"op\"));\n        });\n        when(notifyTemplateService.getNotifyTemplateByCodeFromCache(eq(templateCode))).thenReturn(template);\n        String content = randomString();\n        when(notifyTemplateService.formatNotifyTemplateContent(eq(template.getContent()), eq(templateParams)))\n                .thenReturn(content);\n        // mock NotifyMessageService 的方法\n        Long messageId = randomLongId();\n        when(notifyMessageService.createNotifyMessage(eq(userId), eq(UserTypeEnum.MEMBER.getValue()),\n                eq(template), eq(content), eq(templateParams))).thenReturn(messageId);\n\n        // 调用\n        Long resultMessageId = notifySendService.sendSingleNotifyToMember(userId, templateCode, templateParams);\n        // 断言\n        assertEquals(messageId, resultMessageId);\n    }\n\n    /**\n     * 发送成功，当短信模板开启时\n     */\n    @Test\n    public void testSendSingleNotify_successWhenMailTemplateEnable() {\n        // 准备参数\n        Long userId = randomLongId();\n        Integer userType = randomEle(UserTypeEnum.values()).getValue();\n        String templateCode = randomString();\n        Map<String, Object> templateParams = MapUtil.<String, Object>builder().put(\"code\", \"1234\")\n                .put(\"op\", \"login\").build();\n        // mock NotifyTemplateService 的方法\n        NotifyTemplateDO template = randomPojo(NotifyTemplateDO.class, o -> {\n            o.setStatus(CommonStatusEnum.ENABLE.getStatus());\n            o.setContent(\"验证码为{code}, 操作为{op}\");\n            o.setParams(Lists.newArrayList(\"code\", \"op\"));\n        });\n        when(notifyTemplateService.getNotifyTemplateByCodeFromCache(eq(templateCode))).thenReturn(template);\n        String content = randomString();\n        when(notifyTemplateService.formatNotifyTemplateContent(eq(template.getContent()), eq(templateParams)))\n                .thenReturn(content);\n        // mock NotifyMessageService 的方法\n        Long messageId = randomLongId();\n        when(notifyMessageService.createNotifyMessage(eq(userId), eq(userType),\n                eq(template), eq(content), eq(templateParams))).thenReturn(messageId);\n\n        // 调用\n        Long resultMessageId = notifySendService.sendSingleNotify(userId, userType, templateCode, templateParams);\n        // 断言\n        assertEquals(messageId, resultMessageId);\n    }\n\n    /**\n     * 发送成功，当短信模板关闭时\n     */\n    @Test\n    public void testSendSingleMail_successWhenSmsTemplateDisable() {\n        // 准备参数\n        Long userId = randomLongId();\n        Integer userType = randomEle(UserTypeEnum.values()).getValue();\n        String templateCode = randomString();\n        Map<String, Object> templateParams = MapUtil.<String, Object>builder().put(\"code\", \"1234\")\n                .put(\"op\", \"login\").build();\n        // mock NotifyTemplateService 的方法\n        NotifyTemplateDO template = randomPojo(NotifyTemplateDO.class, o -> {\n            o.setStatus(CommonStatusEnum.DISABLE.getStatus());\n            o.setContent(\"验证码为{code}, 操作为{op}\");\n            o.setParams(Lists.newArrayList(\"code\", \"op\"));\n        });\n        when(notifyTemplateService.getNotifyTemplateByCodeFromCache(eq(templateCode))).thenReturn(template);\n\n        // 调用\n        Long resultMessageId = notifySendService.sendSingleNotify(userId, userType, templateCode, templateParams);\n        // 断言\n        assertNull(resultMessageId);\n        verify(notifyTemplateService, never()).formatNotifyTemplateContent(anyString(), anyMap());\n        verify(notifyMessageService, never()).createNotifyMessage(anyLong(), anyInt(), any(), anyString(), anyMap());\n    }\n\n    @Test\n    public void testCheckMailTemplateValid_notExists() {\n        // 准备参数\n        String templateCode = randomString();\n        // mock 方法\n\n        // 调用，并断言异常\n        assertServiceException(() -> notifySendService.validateNotifyTemplate(templateCode),\n                NOTICE_NOT_FOUND);\n    }\n\n    @Test\n    public void testCheckTemplateParams_paramMiss() {\n        // 准备参数\n        NotifyTemplateDO template = randomPojo(NotifyTemplateDO.class,\n                o -> o.setParams(Lists.newArrayList(\"code\")));\n        Map<String, Object> templateParams = new HashMap<>();\n        // mock 方法\n\n        // 调用，并断言异常\n        assertServiceException(() -> notifySendService.validateTemplateParams(template, templateParams),\n                NOTIFY_SEND_TEMPLATE_PARAM_MISS, \"code\");\n    }\n\n    @Test\n    public void testSendBatchNotify() {\n        // 准备参数\n        // mock 方法\n\n        // 调用\n        UnsupportedOperationException exception = Assertions.assertThrows(\n                UnsupportedOperationException.class,\n                () -> notifySendService.sendBatchNotify(null, null, null, null, null)\n        );\n        // 断言\n        assertEquals(\"暂时不支持该操作，感兴趣可以实现该功能哟！\", exception.getMessage());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/java/co/yixiang/yshop/module/system/service/notify/NotifyTemplateServiceImplTest.java",
    "content": "package co.yixiang.yshop.module.system.service.notify;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.test.core.ut.BaseDbUnitTest;\nimport co.yixiang.yshop.module.system.controller.admin.notify.vo.template.NotifyTemplatePageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.notify.vo.template.NotifyTemplateSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.notify.NotifyTemplateDO;\nimport co.yixiang.yshop.module.system.dal.mysql.notify.NotifyTemplateMapper;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.context.annotation.Import;\n\nimport jakarta.annotation.Resource;\nimport java.util.HashMap;\nimport java.util.Map;\n\nimport static co.yixiang.yshop.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;\nimport static co.yixiang.yshop.framework.common.util.date.LocalDateTimeUtils.buildTime;\nimport static co.yixiang.yshop.framework.common.util.object.ObjectUtils.cloneIgnoreId;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertPojoEquals;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertServiceException;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.*;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.NOTIFY_TEMPLATE_NOT_EXISTS;\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * {@link NotifyTemplateServiceImpl} 的单元测试类\n *\n * @author yshop\n */\n@Import(NotifyTemplateServiceImpl.class)\npublic class NotifyTemplateServiceImplTest extends BaseDbUnitTest {\n\n    @Resource\n    private NotifyTemplateServiceImpl notifyTemplateService;\n\n    @Resource\n    private NotifyTemplateMapper notifyTemplateMapper;\n\n    @Test\n    public void testCreateNotifyTemplate_success() {\n        // 准备参数\n        NotifyTemplateSaveReqVO reqVO = randomPojo(NotifyTemplateSaveReqVO.class,\n                o -> o.setStatus(randomCommonStatus()))\n                .setId(null); // 防止 id 被赋值\n\n        // 调用\n        Long notifyTemplateId = notifyTemplateService.createNotifyTemplate(reqVO);\n        // 断言\n        assertNotNull(notifyTemplateId);\n        // 校验记录的属性是否正确\n        NotifyTemplateDO notifyTemplate = notifyTemplateMapper.selectById(notifyTemplateId);\n        assertPojoEquals(reqVO, notifyTemplate, \"id\");\n    }\n\n    @Test\n    public void testUpdateNotifyTemplate_success() {\n        // mock 数据\n        NotifyTemplateDO dbNotifyTemplate = randomPojo(NotifyTemplateDO.class);\n        notifyTemplateMapper.insert(dbNotifyTemplate);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        NotifyTemplateSaveReqVO reqVO = randomPojo(NotifyTemplateSaveReqVO.class, o -> {\n            o.setId(dbNotifyTemplate.getId()); // 设置更新的 ID\n            o.setStatus(randomCommonStatus());\n        });\n\n        // 调用\n        notifyTemplateService.updateNotifyTemplate(reqVO);\n        // 校验是否更新正确\n        NotifyTemplateDO notifyTemplate = notifyTemplateMapper.selectById(reqVO.getId()); // 获取最新的\n        assertPojoEquals(reqVO, notifyTemplate);\n    }\n\n    @Test\n    public void testUpdateNotifyTemplate_notExists() {\n        // 准备参数\n        NotifyTemplateSaveReqVO reqVO = randomPojo(NotifyTemplateSaveReqVO.class);\n\n        // 调用, 并断言异常\n        assertServiceException(() -> notifyTemplateService.updateNotifyTemplate(reqVO), NOTIFY_TEMPLATE_NOT_EXISTS);\n    }\n\n    @Test\n    public void testDeleteNotifyTemplate_success() {\n        // mock 数据\n        NotifyTemplateDO dbNotifyTemplate = randomPojo(NotifyTemplateDO.class);\n        notifyTemplateMapper.insert(dbNotifyTemplate);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        Long id = dbNotifyTemplate.getId();\n\n        // 调用\n        notifyTemplateService.deleteNotifyTemplate(id);\n        // 校验数据不存在了\n        assertNull(notifyTemplateMapper.selectById(id));\n    }\n\n    @Test\n    public void testDeleteNotifyTemplate_notExists() {\n        // 准备参数\n        Long id = randomLongId();\n\n        // 调用, 并断言异常\n        assertServiceException(() -> notifyTemplateService.deleteNotifyTemplate(id), NOTIFY_TEMPLATE_NOT_EXISTS);\n    }\n\n    @Test\n    public void testGetNotifyTemplatePage() {\n        // mock 数据\n        NotifyTemplateDO dbNotifyTemplate = randomPojo(NotifyTemplateDO.class, o -> { // 等会查询到\n            o.setName(\"芋头\");\n            o.setCode(\"test_01\");\n            o.setStatus(CommonStatusEnum.ENABLE.getStatus());\n            o.setCreateTime(buildTime(2022, 2, 3));\n        });\n        notifyTemplateMapper.insert(dbNotifyTemplate);\n        // 测试 name 不匹配\n        notifyTemplateMapper.insert(cloneIgnoreId(dbNotifyTemplate, o -> o.setName(\"投\")));\n        // 测试 code 不匹配\n        notifyTemplateMapper.insert(cloneIgnoreId(dbNotifyTemplate, o -> o.setCode(\"test_02\")));\n        // 测试 status 不匹配\n        notifyTemplateMapper.insert(cloneIgnoreId(dbNotifyTemplate, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));\n        // 测试 createTime 不匹配\n        notifyTemplateMapper.insert(cloneIgnoreId(dbNotifyTemplate, o -> o.setCreateTime(buildTime(2022, 1, 5))));\n        // 准备参数\n        NotifyTemplatePageReqVO reqVO = new NotifyTemplatePageReqVO();\n        reqVO.setName(\"芋\");\n        reqVO.setCode(\"est_01\");\n        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());\n        reqVO.setCreateTime(buildBetweenTime(2022, 2, 1, 2022, 2, 5));\n\n        // 调用\n        PageResult<NotifyTemplateDO> pageResult = notifyTemplateService.getNotifyTemplatePage(reqVO);\n        // 断言\n        assertEquals(1, pageResult.getTotal());\n        assertEquals(1, pageResult.getList().size());\n        assertPojoEquals(dbNotifyTemplate, pageResult.getList().get(0));\n    }\n\n    @Test\n    public void testGetNotifyTemplate() {\n        // mock 数据\n        NotifyTemplateDO dbNotifyTemplate = randomPojo(NotifyTemplateDO.class);\n        notifyTemplateMapper.insert(dbNotifyTemplate);\n        // 准备参数\n        Long id = dbNotifyTemplate.getId();\n\n        // 调用\n        NotifyTemplateDO notifyTemplate = notifyTemplateService.getNotifyTemplate(id);\n        // 断言\n        assertPojoEquals(dbNotifyTemplate, notifyTemplate);\n    }\n\n    @Test\n    public void testGetNotifyTemplateByCodeFromCache() {\n        // mock 数据\n        NotifyTemplateDO dbNotifyTemplate = randomPojo(NotifyTemplateDO.class);\n        notifyTemplateMapper.insert(dbNotifyTemplate);\n        // 准备参数\n        String code = dbNotifyTemplate.getCode();\n\n        // 调用\n        NotifyTemplateDO notifyTemplate = notifyTemplateService.getNotifyTemplateByCodeFromCache(code);\n        // 断言\n        assertPojoEquals(dbNotifyTemplate, notifyTemplate);\n    }\n\n    @Test\n    public void testFormatNotifyTemplateContent() {\n        // 准备参数\n        Map<String, Object> params = new HashMap<>();\n        params.put(\"name\", \"小红\");\n        params.put(\"what\", \"饭\");\n\n        // 调用，并断言\n        assertEquals(\"小红，你好，饭吃了吗？\",\n                notifyTemplateService.formatNotifyTemplateContent(\"{name}，你好，{what}吃了吗？\", params));\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/java/co/yixiang/yshop/module/system/service/oauth2/OAuth2ApproveServiceImplTest.java",
    "content": "package co.yixiang.yshop.module.system.service.oauth2;\n\nimport cn.hutool.core.date.LocalDateTimeUtil;\nimport cn.hutool.core.util.ObjectUtil;\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.common.util.date.DateUtils;\nimport co.yixiang.yshop.framework.test.core.ut.BaseDbUnitTest;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2ApproveDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2ClientDO;\nimport co.yixiang.yshop.module.system.dal.mysql.oauth2.OAuth2ApproveMapper;\nimport org.assertj.core.util.Lists;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.mock.mockito.MockBean;\nimport org.springframework.context.annotation.Import;\n\nimport jakarta.annotation.Resource;\nimport java.time.LocalDateTime;\nimport java.time.ZoneId;\nimport java.time.temporal.ChronoUnit;\nimport java.util.*;\n\nimport static cn.hutool.core.util.RandomUtil.*;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertPojoEquals;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.randomString;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.*;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.when;\n\n/**\n * {@link OAuth2ApproveServiceImpl} 的单元测试类\n *\n * @author yshop\n */\n@Import(OAuth2ApproveServiceImpl.class)\npublic class OAuth2ApproveServiceImplTest extends BaseDbUnitTest {\n\n    @Resource\n    private OAuth2ApproveServiceImpl oauth2ApproveService;\n\n    @Resource\n    private OAuth2ApproveMapper oauth2ApproveMapper;\n\n    @MockBean\n    private OAuth2ClientService oauth2ClientService;\n\n    @Test\n    public void checkForPreApproval_clientAutoApprove() {\n        // 准备参数\n        Long userId = randomLongId();\n        Integer userType = randomEle(UserTypeEnum.values()).getValue();\n        String clientId = randomString();\n        List<String> requestedScopes = Lists.newArrayList(\"read\");\n        // mock 方法\n        when(oauth2ClientService.validOAuthClientFromCache(eq(clientId)))\n                .thenReturn(randomPojo(OAuth2ClientDO.class).setAutoApproveScopes(requestedScopes));\n\n        // 调用\n        boolean success = oauth2ApproveService.checkForPreApproval(userId, userType,\n                clientId, requestedScopes);\n        // 断言\n        assertTrue(success);\n        List<OAuth2ApproveDO> result = oauth2ApproveMapper.selectList();\n        assertEquals(1, result.size());\n        assertEquals(userId, result.get(0).getUserId());\n        assertEquals(userType, result.get(0).getUserType());\n        assertEquals(clientId, result.get(0).getClientId());\n        assertEquals(\"read\", result.get(0).getScope());\n        assertTrue(result.get(0).getApproved());\n        assertFalse(DateUtils.isExpired(result.get(0).getExpiresTime()));\n    }\n\n    @Test\n    public void checkForPreApproval_approve() {\n        // 准备参数\n        Long userId = randomLongId();\n        Integer userType = randomEle(UserTypeEnum.values()).getValue();\n        String clientId = randomString();\n        List<String> requestedScopes = Lists.newArrayList(\"read\");\n        // mock 方法\n        when(oauth2ClientService.validOAuthClientFromCache(eq(clientId)))\n                .thenReturn(randomPojo(OAuth2ClientDO.class).setAutoApproveScopes(null));\n        // mock 数据\n        OAuth2ApproveDO approve = randomPojo(OAuth2ApproveDO.class).setUserId(userId)\n                .setUserType(userType).setClientId(clientId).setScope(\"read\")\n                .setExpiresTime(LocalDateTimeUtil.offset(LocalDateTime.now(), 1L, ChronoUnit.DAYS)).setApproved(true); // 同意\n        oauth2ApproveMapper.insert(approve);\n\n        // 调用\n        boolean success = oauth2ApproveService.checkForPreApproval(userId, userType,\n                clientId, requestedScopes);\n        // 断言\n        assertTrue(success);\n    }\n\n    @Test\n    public void checkForPreApproval_reject() {\n        // 准备参数\n        Long userId = randomLongId();\n        Integer userType = randomEle(UserTypeEnum.values()).getValue();\n        String clientId = randomString();\n        List<String> requestedScopes = Lists.newArrayList(\"read\");\n        // mock 方法\n        when(oauth2ClientService.validOAuthClientFromCache(eq(clientId)))\n                .thenReturn(randomPojo(OAuth2ClientDO.class).setAutoApproveScopes(null));\n        // mock 数据\n        OAuth2ApproveDO approve = randomPojo(OAuth2ApproveDO.class).setUserId(userId)\n                .setUserType(userType).setClientId(clientId).setScope(\"read\")\n                .setExpiresTime(LocalDateTimeUtil.offset(LocalDateTime.now(), 1L, ChronoUnit.DAYS)).setApproved(false); // 拒绝\n        oauth2ApproveMapper.insert(approve);\n\n        // 调用\n        boolean success = oauth2ApproveService.checkForPreApproval(userId, userType,\n                clientId, requestedScopes);\n        // 断言\n        assertFalse(success);\n    }\n\n    @Test\n    public void testUpdateAfterApproval_none() {\n        // 准备参数\n        Long userId = randomLongId();\n        Integer userType = randomEle(UserTypeEnum.values()).getValue();\n        String clientId = randomString();\n\n        // 调用\n        boolean success = oauth2ApproveService.updateAfterApproval(userId, userType, clientId,\n                null);\n        // 断言\n        assertTrue(success);\n        List<OAuth2ApproveDO> result = oauth2ApproveMapper.selectList();\n        assertEquals(0, result.size());\n    }\n\n    @Test\n    public void testUpdateAfterApproval_approved() {\n        // 准备参数\n        Long userId = randomLongId();\n        Integer userType = randomEle(UserTypeEnum.values()).getValue();\n        String clientId = randomString();\n        Map<String, Boolean> requestedScopes = new LinkedHashMap<>(); // 有序，方便判断\n        requestedScopes.put(\"read\", true);\n        requestedScopes.put(\"write\", false);\n        // mock 方法\n\n        // 调用\n        boolean success = oauth2ApproveService.updateAfterApproval(userId, userType, clientId,\n                requestedScopes);\n        // 断言\n        assertTrue(success);\n        List<OAuth2ApproveDO> result = oauth2ApproveMapper.selectList();\n        assertEquals(2, result.size());\n        // read\n        assertEquals(userId, result.get(0).getUserId());\n        assertEquals(userType, result.get(0).getUserType());\n        assertEquals(clientId, result.get(0).getClientId());\n        assertEquals(\"read\", result.get(0).getScope());\n        assertTrue(result.get(0).getApproved());\n        assertFalse(DateUtils.isExpired(result.get(0).getExpiresTime()));\n        // write\n        assertEquals(userId, result.get(1).getUserId());\n        assertEquals(userType, result.get(1).getUserType());\n        assertEquals(clientId, result.get(1).getClientId());\n        assertEquals(\"write\", result.get(1).getScope());\n        assertFalse(result.get(1).getApproved());\n        assertFalse(DateUtils.isExpired(result.get(1).getExpiresTime()));\n    }\n\n    @Test\n    public void testUpdateAfterApproval_reject() {\n        // 准备参数\n        Long userId = randomLongId();\n        Integer userType = randomEle(UserTypeEnum.values()).getValue();\n        String clientId = randomString();\n        Map<String, Boolean> requestedScopes = new LinkedHashMap<>();\n        requestedScopes.put(\"write\", false);\n        // mock 方法\n\n        // 调用\n        boolean success = oauth2ApproveService.updateAfterApproval(userId, userType, clientId,\n                requestedScopes);\n        // 断言\n        assertFalse(success);\n        List<OAuth2ApproveDO> result = oauth2ApproveMapper.selectList();\n        assertEquals(1, result.size());\n        // write\n        assertEquals(userId, result.get(0).getUserId());\n        assertEquals(userType, result.get(0).getUserType());\n        assertEquals(clientId, result.get(0).getClientId());\n        assertEquals(\"write\", result.get(0).getScope());\n        assertFalse(result.get(0).getApproved());\n        assertFalse(DateUtils.isExpired(result.get(0).getExpiresTime()));\n    }\n\n    @Test\n    public void testGetApproveList() {\n        // 准备参数\n        Long userId = 10L;\n        Integer userType = UserTypeEnum.ADMIN.getValue();\n        String clientId = randomString();\n        // mock 数据\n        OAuth2ApproveDO approve = randomPojo(OAuth2ApproveDO.class).setUserId(userId)\n                .setUserType(userType).setClientId(clientId).setExpiresTime(LocalDateTimeUtil.offset(LocalDateTime.now(), 1L, ChronoUnit.DAYS));\n        oauth2ApproveMapper.insert(approve); // 未过期\n        oauth2ApproveMapper.insert(ObjectUtil.clone(approve).setId(null)\n                .setExpiresTime(LocalDateTimeUtil.offset(LocalDateTime.now(), -1L, ChronoUnit.DAYS))); // 已过期\n\n        // 调用\n        List<OAuth2ApproveDO> result = oauth2ApproveService.getApproveList(userId, userType, clientId);\n        // 断言\n        assertEquals(1, result.size());\n        assertPojoEquals(approve, result.get(0));\n    }\n\n    @Test\n    public void testSaveApprove_insert() {\n        // 准备参数\n        Long userId = randomLongId();\n        Integer userType = randomEle(UserTypeEnum.values()).getValue();\n        String clientId = randomString();\n        String scope = randomString();\n        Boolean approved = randomBoolean();\n        LocalDateTime expireTime = LocalDateTime.ofInstant(randomDay(1, 30).toInstant(), ZoneId.systemDefault());\n        // mock 方法\n\n        // 调用\n        oauth2ApproveService.saveApprove(userId, userType, clientId,\n                scope, approved, expireTime);\n        // 断言\n        List<OAuth2ApproveDO> result = oauth2ApproveMapper.selectList();\n        assertEquals(1, result.size());\n        assertEquals(userId, result.get(0).getUserId());\n        assertEquals(userType, result.get(0).getUserType());\n        assertEquals(clientId, result.get(0).getClientId());\n        assertEquals(scope, result.get(0).getScope());\n        assertEquals(approved, result.get(0).getApproved());\n        assertEquals(expireTime, result.get(0).getExpiresTime());\n    }\n\n    @Test\n    public void testSaveApprove_update() {\n        // mock 数据\n        OAuth2ApproveDO approve = randomPojo(OAuth2ApproveDO.class);\n        oauth2ApproveMapper.insert(approve);\n        // 准备参数\n        Long userId = approve.getUserId();\n        Integer userType = approve.getUserType();\n        String clientId = approve.getClientId();\n        String scope = approve.getScope();\n        Boolean approved = randomBoolean();\n        LocalDateTime expireTime = LocalDateTime.ofInstant(randomDay(1, 30).toInstant(), ZoneId.systemDefault());\n        // mock 方法\n\n        // 调用\n        oauth2ApproveService.saveApprove(userId, userType, clientId,\n                scope, approved, expireTime);\n        // 断言\n        List<OAuth2ApproveDO> result = oauth2ApproveMapper.selectList();\n        assertEquals(1, result.size());\n        assertEquals(approve.getId(), result.get(0).getId());\n        assertEquals(userId, result.get(0).getUserId());\n        assertEquals(userType, result.get(0).getUserType());\n        assertEquals(clientId, result.get(0).getClientId());\n        assertEquals(scope, result.get(0).getScope());\n        assertEquals(approved, result.get(0).getApproved());\n        assertEquals(expireTime, result.get(0).getExpiresTime());\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/java/co/yixiang/yshop/module/system/service/oauth2/OAuth2ClientServiceImplTest.java",
    "content": "package co.yixiang.yshop.module.system.service.oauth2;\n\nimport cn.hutool.extra.spring.SpringUtil;\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.test.core.ut.BaseDbUnitTest;\nimport co.yixiang.yshop.module.system.controller.admin.oauth2.vo.client.OAuth2ClientPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.oauth2.vo.client.OAuth2ClientSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2ClientDO;\nimport co.yixiang.yshop.module.system.dal.mysql.oauth2.OAuth2ClientMapper;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.MockedStatic;\nimport org.springframework.context.annotation.Import;\n\nimport jakarta.annotation.Resource;\nimport java.util.Collections;\n\nimport static co.yixiang.yshop.framework.common.util.object.ObjectUtils.cloneIgnoreId;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertPojoEquals;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertServiceException;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.*;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.*;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mockStatic;\n\n/**\n * {@link OAuth2ClientServiceImpl} 的单元测试类\n *\n * @author yshop\n */\n@Import(OAuth2ClientServiceImpl.class)\npublic class OAuth2ClientServiceImplTest extends BaseDbUnitTest {\n\n    @Resource\n    private OAuth2ClientServiceImpl oauth2ClientService;\n\n    @Resource\n    private OAuth2ClientMapper oauth2ClientMapper;\n\n    @Test\n    public void testCreateOAuth2Client_success() {\n        // 准备参数\n        OAuth2ClientSaveReqVO reqVO = randomPojo(OAuth2ClientSaveReqVO.class,\n                o -> o.setLogo(randomString()))\n                .setId(null); // 防止 id 被赋值\n\n        // 调用\n        Long oauth2ClientId = oauth2ClientService.createOAuth2Client(reqVO);\n        // 断言\n        assertNotNull(oauth2ClientId);\n        // 校验记录的属性是否正确\n        OAuth2ClientDO oAuth2Client = oauth2ClientMapper.selectById(oauth2ClientId);\n        assertPojoEquals(reqVO, oAuth2Client, \"id\");\n    }\n\n    @Test\n    public void testUpdateOAuth2Client_success() {\n        // mock 数据\n        OAuth2ClientDO dbOAuth2Client = randomPojo(OAuth2ClientDO.class);\n        oauth2ClientMapper.insert(dbOAuth2Client);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        OAuth2ClientSaveReqVO reqVO = randomPojo(OAuth2ClientSaveReqVO.class, o -> {\n            o.setId(dbOAuth2Client.getId()); // 设置更新的 ID\n            o.setLogo(randomString());\n        });\n\n        // 调用\n        oauth2ClientService.updateOAuth2Client(reqVO);\n        // 校验是否更新正确\n        OAuth2ClientDO oAuth2Client = oauth2ClientMapper.selectById(reqVO.getId()); // 获取最新的\n        assertPojoEquals(reqVO, oAuth2Client);\n    }\n\n    @Test\n    public void testUpdateOAuth2Client_notExists() {\n        // 准备参数\n        OAuth2ClientSaveReqVO reqVO = randomPojo(OAuth2ClientSaveReqVO.class);\n\n        // 调用, 并断言异常\n        assertServiceException(() -> oauth2ClientService.updateOAuth2Client(reqVO), OAUTH2_CLIENT_NOT_EXISTS);\n    }\n\n    @Test\n    public void testDeleteOAuth2Client_success() {\n        // mock 数据\n        OAuth2ClientDO dbOAuth2Client = randomPojo(OAuth2ClientDO.class);\n        oauth2ClientMapper.insert(dbOAuth2Client);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        Long id = dbOAuth2Client.getId();\n\n        // 调用\n        oauth2ClientService.deleteOAuth2Client(id);\n        // 校验数据不存在了\n        assertNull(oauth2ClientMapper.selectById(id));\n    }\n\n    @Test\n    public void testDeleteOAuth2Client_notExists() {\n        // 准备参数\n        Long id = randomLongId();\n\n        // 调用, 并断言异常\n        assertServiceException(() -> oauth2ClientService.deleteOAuth2Client(id), OAUTH2_CLIENT_NOT_EXISTS);\n    }\n\n    @Test\n    public void testValidateClientIdExists_withId() {\n        // mock 数据\n        OAuth2ClientDO client = randomPojo(OAuth2ClientDO.class).setClientId(\"tudou\");\n        oauth2ClientMapper.insert(client);\n        // 准备参数\n        Long id = randomLongId();\n        String clientId = \"tudou\";\n\n        // 调用，不会报错\n        assertServiceException(() -> oauth2ClientService.validateClientIdExists(id, clientId), OAUTH2_CLIENT_EXISTS);\n    }\n\n    @Test\n    public void testValidateClientIdExists_noId() {\n        // mock 数据\n        OAuth2ClientDO client = randomPojo(OAuth2ClientDO.class).setClientId(\"tudou\");\n        oauth2ClientMapper.insert(client);\n        // 准备参数\n        String clientId = \"tudou\";\n\n        // 调用，不会报错\n        assertServiceException(() -> oauth2ClientService.validateClientIdExists(null, clientId), OAUTH2_CLIENT_EXISTS);\n    }\n\n    @Test\n    public void testGetOAuth2Client() {\n        // mock 数据\n        OAuth2ClientDO clientDO = randomPojo(OAuth2ClientDO.class);\n        oauth2ClientMapper.insert(clientDO);\n        // 准备参数\n        Long id = clientDO.getId();\n\n        // 调用，并断言\n        OAuth2ClientDO dbClientDO = oauth2ClientService.getOAuth2Client(id);\n        assertPojoEquals(clientDO, dbClientDO);\n    }\n\n    @Test\n    public void testGetOAuth2ClientFromCache() {\n        // mock 数据\n        OAuth2ClientDO clientDO = randomPojo(OAuth2ClientDO.class);\n        oauth2ClientMapper.insert(clientDO);\n        // 准备参数\n        String clientId = clientDO.getClientId();\n\n        // 调用，并断言\n        OAuth2ClientDO dbClientDO = oauth2ClientService.getOAuth2ClientFromCache(clientId);\n        assertPojoEquals(clientDO, dbClientDO);\n    }\n\n    @Test\n    public void testGetOAuth2ClientPage() {\n        // mock 数据\n        OAuth2ClientDO dbOAuth2Client = randomPojo(OAuth2ClientDO.class, o -> { // 等会查询到\n            o.setName(\"潜龙\");\n            o.setStatus(CommonStatusEnum.ENABLE.getStatus());\n        });\n        oauth2ClientMapper.insert(dbOAuth2Client);\n        // 测试 name 不匹配\n        oauth2ClientMapper.insert(cloneIgnoreId(dbOAuth2Client, o -> o.setName(\"凤凰\")));\n        // 测试 status 不匹配\n        oauth2ClientMapper.insert(cloneIgnoreId(dbOAuth2Client, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));\n        // 准备参数\n        OAuth2ClientPageReqVO reqVO = new OAuth2ClientPageReqVO();\n        reqVO.setName(\"龙\");\n        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());\n\n        // 调用\n        PageResult<OAuth2ClientDO> pageResult = oauth2ClientService.getOAuth2ClientPage(reqVO);\n        // 断言\n        assertEquals(1, pageResult.getTotal());\n        assertEquals(1, pageResult.getList().size());\n        assertPojoEquals(dbOAuth2Client, pageResult.getList().get(0));\n    }\n\n    @Test\n    public void testValidOAuthClientFromCache() {\n        try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {\n            springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(OAuth2ClientServiceImpl.class)))\n                    .thenReturn(oauth2ClientService);\n\n            // mock 方法\n            OAuth2ClientDO client = randomPojo(OAuth2ClientDO.class).setClientId(\"default\")\n                    .setStatus(CommonStatusEnum.ENABLE.getStatus());\n            oauth2ClientMapper.insert(client);\n            OAuth2ClientDO client02 = randomPojo(OAuth2ClientDO.class).setClientId(\"disable\")\n                    .setStatus(CommonStatusEnum.DISABLE.getStatus());\n            oauth2ClientMapper.insert(client02);\n\n            // 调用，并断言\n            assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache(randomString(),\n                    null, null, null, null), OAUTH2_CLIENT_NOT_EXISTS);\n            assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache(\"disable\",\n                    null, null, null, null), OAUTH2_CLIENT_DISABLE);\n            assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache(\"default\",\n                    randomString(), null, null, null), OAUTH2_CLIENT_CLIENT_SECRET_ERROR);\n            assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache(\"default\",\n                    null, randomString(), null, null), OAUTH2_CLIENT_AUTHORIZED_GRANT_TYPE_NOT_EXISTS);\n            assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache(\"default\",\n                    null, null, Collections.singleton(randomString()), null), OAUTH2_CLIENT_SCOPE_OVER);\n            assertServiceException(() -> oauth2ClientService.validOAuthClientFromCache(\"default\",\n                    null, null, null, \"test\"), OAUTH2_CLIENT_REDIRECT_URI_NOT_MATCH, \"test\");\n            // 成功调用（1：参数完整）\n            OAuth2ClientDO result = oauth2ClientService.validOAuthClientFromCache(client.getClientId(), client.getSecret(),\n                    client.getAuthorizedGrantTypes().get(0), client.getScopes(), client.getRedirectUris().get(0));\n            assertPojoEquals(client, result);\n            // 成功调用（2：只有 clientId 参数）\n            result = oauth2ClientService.validOAuthClientFromCache(client.getClientId());\n            assertPojoEquals(client, result);\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/java/co/yixiang/yshop/module/system/service/oauth2/OAuth2CodeServiceImplTest.java",
    "content": "package co.yixiang.yshop.module.system.service.oauth2;\n\nimport cn.hutool.core.util.RandomUtil;\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.common.util.date.DateUtils;\nimport co.yixiang.yshop.framework.test.core.ut.BaseDbUnitTest;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2CodeDO;\nimport co.yixiang.yshop.module.system.dal.mysql.oauth2.OAuth2CodeMapper;\nimport org.assertj.core.util.Lists;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.context.annotation.Import;\n\nimport jakarta.annotation.Resource;\nimport java.time.LocalDateTime;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertPojoEquals;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertServiceException;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.*;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.OAUTH2_CODE_EXPIRE;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.OAUTH2_CODE_NOT_EXISTS;\nimport static org.junit.jupiter.api.Assertions.*;\n\n/**\n * {@link OAuth2CodeServiceImpl} 的单元测试类\n *\n * @author yshop\n */\n@Import(OAuth2CodeServiceImpl.class)\nclass OAuth2CodeServiceImplTest extends BaseDbUnitTest {\n\n    @Resource\n    private OAuth2CodeServiceImpl oauth2CodeService;\n\n    @Resource\n    private OAuth2CodeMapper oauth2CodeMapper;\n\n    @Test\n    public void testCreateAuthorizationCode() {\n        // 准备参数\n        Long userId = randomLongId();\n        Integer userType = RandomUtil.randomEle(UserTypeEnum.values()).getValue();\n        String clientId = randomString();\n        List<String> scopes = Lists.newArrayList(\"read\", \"write\");\n        String redirectUri = randomString();\n        String state = randomString();\n\n        // 调用\n        OAuth2CodeDO codeDO = oauth2CodeService.createAuthorizationCode(userId, userType, clientId,\n                scopes, redirectUri, state);\n        // 断言\n        OAuth2CodeDO dbCodeDO = oauth2CodeMapper.selectByCode(codeDO.getCode());\n        assertPojoEquals(codeDO, dbCodeDO, \"createTime\", \"updateTime\", \"deleted\");\n        assertEquals(userId, codeDO.getUserId());\n        assertEquals(userType, codeDO.getUserType());\n        assertEquals(clientId, codeDO.getClientId());\n        assertEquals(scopes, codeDO.getScopes());\n        assertEquals(redirectUri, codeDO.getRedirectUri());\n        assertEquals(state, codeDO.getState());\n        assertFalse(DateUtils.isExpired(codeDO.getExpiresTime()));\n    }\n\n    @Test\n    public void testConsumeAuthorizationCode_null() {\n        // 调用，并断言\n        assertServiceException(() -> oauth2CodeService.consumeAuthorizationCode(randomString()),\n                OAUTH2_CODE_NOT_EXISTS);\n    }\n\n    @Test\n    public void testConsumeAuthorizationCode_expired() {\n        // 准备参数\n        String code = \"test_code\";\n        // mock 数据\n        OAuth2CodeDO codeDO = randomPojo(OAuth2CodeDO.class).setCode(code)\n                .setExpiresTime(LocalDateTime.now().minusDays(1));\n        oauth2CodeMapper.insert(codeDO);\n\n        // 调用，并断言\n        assertServiceException(() -> oauth2CodeService.consumeAuthorizationCode(code),\n                OAUTH2_CODE_EXPIRE);\n    }\n\n    @Test\n    public void testConsumeAuthorizationCode_success() {\n        // 准备参数\n        String code = \"test_code\";\n        // mock 数据\n        OAuth2CodeDO codeDO = randomPojo(OAuth2CodeDO.class).setCode(code)\n                .setExpiresTime(LocalDateTime.now().plusDays(1));\n        oauth2CodeMapper.insert(codeDO);\n\n        // 调用\n        OAuth2CodeDO result = oauth2CodeService.consumeAuthorizationCode(code);\n        assertPojoEquals(codeDO, result);\n        assertNull(oauth2CodeMapper.selectByCode(code));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/java/co/yixiang/yshop/module/system/service/oauth2/OAuth2GrantServiceImplTest.java",
    "content": "package co.yixiang.yshop.module.system.service.oauth2;\n\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.test.core.ut.BaseMockitoUnitTest;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2CodeDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.user.AdminUserDO;\nimport co.yixiang.yshop.module.system.service.auth.AdminAuthService;\nimport com.google.common.collect.Lists;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\n\nimport java.util.List;\n\nimport static cn.hutool.core.util.RandomUtil.randomEle;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertPojoEquals;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.*;\nimport static java.util.Collections.emptyList;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.when;\n\n/**\n * {@link OAuth2GrantServiceImpl} 的单元测试\n *\n * @author yshop\n */\npublic class OAuth2GrantServiceImplTest extends BaseMockitoUnitTest {\n\n    @InjectMocks\n    private OAuth2GrantServiceImpl oauth2GrantService;\n\n    @Mock\n    private OAuth2TokenService oauth2TokenService;\n    @Mock\n    private OAuth2CodeService oauth2CodeService;\n    @Mock\n    private AdminAuthService adminAuthService;\n\n    @Test\n    public void testGrantImplicit() {\n        // 准备参数\n        Long userId = randomLongId();\n        Integer userType = randomEle(UserTypeEnum.values()).getValue();\n        String clientId = randomString();\n        List<String> scopes = Lists.newArrayList(\"read\", \"write\");\n        // mock 方法\n        OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class);\n        when(oauth2TokenService.createAccessToken(eq(userId), eq(userType),\n                eq(clientId), eq(scopes))).thenReturn(accessTokenDO);\n\n        // 调用，并断言\n        assertPojoEquals(accessTokenDO, oauth2GrantService.grantImplicit(\n                userId, userType, clientId, scopes));\n    }\n\n    @Test\n    public void testGrantAuthorizationCodeForCode() {\n        // 准备参数\n        Long userId = randomLongId();\n        Integer userType = randomEle(UserTypeEnum.values()).getValue();\n        String clientId = randomString();\n        List<String> scopes = Lists.newArrayList(\"read\", \"write\");\n        String redirectUri = randomString();\n        String state = randomString();\n        // mock 方法\n        OAuth2CodeDO codeDO = randomPojo(OAuth2CodeDO.class);\n        when(oauth2CodeService.createAuthorizationCode(eq(userId), eq(userType),\n                eq(clientId), eq(scopes), eq(redirectUri), eq(state))).thenReturn(codeDO);\n\n        // 调用，并断言\n        assertEquals(codeDO.getCode(), oauth2GrantService.grantAuthorizationCodeForCode(userId, userType,\n                clientId, scopes, redirectUri, state));\n    }\n\n    @Test\n    public void testGrantAuthorizationCodeForAccessToken() {\n        // 准备参数\n        String clientId = randomString();\n        String code = randomString();\n        List<String> scopes = Lists.newArrayList(\"read\", \"write\");\n        String redirectUri = randomString();\n        String state = randomString();\n        // mock 方法（code）\n        OAuth2CodeDO codeDO = randomPojo(OAuth2CodeDO.class, o -> {\n            o.setClientId(clientId);\n            o.setRedirectUri(redirectUri);\n            o.setState(state);\n            o.setScopes(scopes);\n        });\n        when(oauth2CodeService.consumeAuthorizationCode(eq(code))).thenReturn(codeDO);\n        // mock 方法（创建令牌）\n        OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class);\n        when(oauth2TokenService.createAccessToken(eq(codeDO.getUserId()), eq(codeDO.getUserType()),\n                eq(codeDO.getClientId()), eq(codeDO.getScopes()))).thenReturn(accessTokenDO);\n\n        // 调用，并断言\n        assertPojoEquals(accessTokenDO, oauth2GrantService.grantAuthorizationCodeForAccessToken(\n                clientId, code, redirectUri, state));\n    }\n\n    @Test\n    public void testGrantPassword() {\n        // 准备参数\n        String username = randomString();\n        String password = randomString();\n        String clientId = randomString();\n        List<String> scopes = Lists.newArrayList(\"read\", \"write\");\n        // mock 方法(认证)\n        AdminUserDO user = randomPojo(AdminUserDO.class);\n        when(adminAuthService.authenticate(eq(username), eq(password))).thenReturn(user);\n        // mock 方法（访问令牌）\n        OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class);\n        when(oauth2TokenService.createAccessToken(eq(user.getId()), eq(UserTypeEnum.ADMIN.getValue()),\n                eq(clientId), eq(scopes))).thenReturn(accessTokenDO);\n\n        // 调用，并断言\n        assertPojoEquals(accessTokenDO, oauth2GrantService.grantPassword(\n                username, password, clientId, scopes));\n    }\n\n    @Test\n    public void testGrantRefreshToken() {\n        // 准备参数\n        String refreshToken = randomString();\n        String clientId = randomString();\n        // mock 方法\n        OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class);\n        when(oauth2TokenService.refreshAccessToken(eq(refreshToken), eq(clientId)))\n                .thenReturn(accessTokenDO);\n\n        // 调用，并断言\n        assertPojoEquals(accessTokenDO, oauth2GrantService.grantRefreshToken(\n                refreshToken, clientId));\n    }\n\n    @Test\n    public void testGrantClientCredentials() {\n        assertThrows(UnsupportedOperationException.class,\n                () -> oauth2GrantService.grantClientCredentials(randomString(), emptyList()),\n                \"暂时不支持 client_credentials 授权模式\");\n    }\n\n    @Test\n    public void testRevokeToken_clientIdError() {\n        // 准备参数\n        String clientId = randomString();\n        String accessToken = randomString();\n        // mock 方法\n        OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class);\n        when(oauth2TokenService.getAccessToken(eq(accessToken))).thenReturn(accessTokenDO);\n\n        // 调用，并断言\n        assertFalse(oauth2GrantService.revokeToken(clientId, accessToken));\n    }\n\n    @Test\n    public void testRevokeToken_success() {\n        // 准备参数\n        String clientId = randomString();\n        String accessToken = randomString();\n        // mock 方法（访问令牌）\n        OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class).setClientId(clientId);\n        when(oauth2TokenService.getAccessToken(eq(accessToken))).thenReturn(accessTokenDO);\n        // mock 方法（移除）\n        when(oauth2TokenService.removeAccessToken(eq(accessToken))).thenReturn(accessTokenDO);\n\n        // 调用，并断言\n        assertTrue(oauth2GrantService.revokeToken(clientId, accessToken));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/java/co/yixiang/yshop/module/system/service/oauth2/OAuth2TokenServiceImplTest.java",
    "content": "package co.yixiang.yshop.module.system.service.oauth2;\n\nimport cn.hutool.core.date.LocalDateTimeUtil;\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.common.exception.ErrorCode;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.date.DateUtils;\nimport co.yixiang.yshop.framework.tenant.core.context.TenantContextHolder;\nimport co.yixiang.yshop.framework.test.core.ut.BaseDbAndRedisUnitTest;\nimport co.yixiang.yshop.module.system.controller.admin.oauth2.vo.token.OAuth2AccessTokenPageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2AccessTokenDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2ClientDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.oauth2.OAuth2RefreshTokenDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.user.AdminUserDO;\nimport co.yixiang.yshop.module.system.dal.mysql.oauth2.OAuth2AccessTokenMapper;\nimport co.yixiang.yshop.module.system.dal.mysql.oauth2.OAuth2RefreshTokenMapper;\nimport co.yixiang.yshop.module.system.dal.redis.oauth2.OAuth2AccessTokenRedisDAO;\nimport co.yixiang.yshop.module.system.service.user.AdminUserService;\nimport jakarta.annotation.Resource;\nimport org.assertj.core.util.Lists;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.mock.mockito.MockBean;\nimport org.springframework.context.annotation.Import;\n\nimport java.time.LocalDateTime;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.util.object.ObjectUtils.cloneIgnoreId;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertPojoEquals;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertServiceException;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.*;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.when;\n\n/**\n * {@link OAuth2TokenServiceImpl} 的单元测试类\n *\n * @author yshop\n */\n@Import({OAuth2TokenServiceImpl.class, OAuth2AccessTokenRedisDAO.class})\npublic class OAuth2TokenServiceImplTest extends BaseDbAndRedisUnitTest {\n\n    @Resource\n    private OAuth2TokenServiceImpl oauth2TokenService;\n\n    @Resource\n    private OAuth2AccessTokenMapper oauth2AccessTokenMapper;\n    @Resource\n    private OAuth2RefreshTokenMapper oauth2RefreshTokenMapper;\n\n    @Resource\n    private OAuth2AccessTokenRedisDAO oauth2AccessTokenRedisDAO;\n\n    @MockBean\n    private OAuth2ClientService oauth2ClientService;\n    @MockBean\n    private AdminUserService adminUserService;\n\n    @Test\n    public void testCreateAccessToken() {\n        TenantContextHolder.setTenantId(0L);\n        // 准备参数\n        Long userId = randomLongId();\n        Integer userType = UserTypeEnum.ADMIN.getValue();\n        String clientId = randomString();\n        List<String> scopes = Lists.newArrayList(\"read\", \"write\");\n        // mock 方法\n        OAuth2ClientDO clientDO = randomPojo(OAuth2ClientDO.class).setClientId(clientId)\n                .setAccessTokenValiditySeconds(30).setRefreshTokenValiditySeconds(60);\n        when(oauth2ClientService.validOAuthClientFromCache(eq(clientId))).thenReturn(clientDO);\n        // mock 数据（用户）\n        AdminUserDO user = randomPojo(AdminUserDO.class);\n        when(adminUserService.getUser(userId)).thenReturn(user);\n\n        // 调用\n        OAuth2AccessTokenDO accessTokenDO = oauth2TokenService.createAccessToken(userId, userType, clientId, scopes);\n        // 断言访问令牌\n        OAuth2AccessTokenDO dbAccessTokenDO = oauth2AccessTokenMapper.selectByAccessToken(accessTokenDO.getAccessToken());\n        assertPojoEquals(accessTokenDO, dbAccessTokenDO, \"createTime\", \"updateTime\", \"deleted\");\n        assertEquals(userId, accessTokenDO.getUserId());\n        assertEquals(userType, accessTokenDO.getUserType());\n        assertEquals(2, accessTokenDO.getUserInfo().size());\n        assertEquals(user.getNickname(), accessTokenDO.getUserInfo().get(\"nickname\"));\n        assertEquals(user.getDeptId().toString(), accessTokenDO.getUserInfo().get(\"deptId\"));\n        assertEquals(clientId, accessTokenDO.getClientId());\n        assertEquals(scopes, accessTokenDO.getScopes());\n        assertFalse(DateUtils.isExpired(accessTokenDO.getExpiresTime()));\n        // 断言访问令牌的缓存\n        OAuth2AccessTokenDO redisAccessTokenDO = oauth2AccessTokenRedisDAO.get(accessTokenDO.getAccessToken());\n        assertPojoEquals(accessTokenDO, redisAccessTokenDO, \"createTime\", \"updateTime\", \"deleted\");\n        // 断言刷新令牌\n        OAuth2RefreshTokenDO refreshTokenDO = oauth2RefreshTokenMapper.selectList().get(0);\n        assertPojoEquals(accessTokenDO, refreshTokenDO, \"id\", \"expiresTime\", \"createTime\", \"updateTime\", \"deleted\");\n        assertFalse(DateUtils.isExpired(refreshTokenDO.getExpiresTime()));\n    }\n\n    @Test\n    public void testRefreshAccessToken_null() {\n        // 准备参数\n        String refreshToken = randomString();\n        String clientId = randomString();\n        // mock 方法\n\n        // 调用，并断言\n        assertServiceException(() -> oauth2TokenService.refreshAccessToken(refreshToken, clientId),\n                new ErrorCode(400, \"无效的刷新令牌\"));\n    }\n\n    @Test\n    public void testRefreshAccessToken_clientIdError() {\n        // 准备参数\n        String refreshToken = randomString();\n        String clientId = randomString();\n        // mock 方法\n        OAuth2ClientDO clientDO = randomPojo(OAuth2ClientDO.class).setClientId(clientId);\n        when(oauth2ClientService.validOAuthClientFromCache(eq(clientId))).thenReturn(clientDO);\n        // mock 数据（访问令牌）\n        OAuth2RefreshTokenDO refreshTokenDO = randomPojo(OAuth2RefreshTokenDO.class)\n                .setRefreshToken(refreshToken).setClientId(\"error\");\n        oauth2RefreshTokenMapper.insert(refreshTokenDO);\n\n        // 调用，并断言\n        assertServiceException(() -> oauth2TokenService.refreshAccessToken(refreshToken, clientId),\n                new ErrorCode(400, \"刷新令牌的客户端编号不正确\"));\n    }\n\n    @Test\n    public void testRefreshAccessToken_expired() {\n        // 准备参数\n        String refreshToken = randomString();\n        String clientId = randomString();\n        // mock 方法\n        OAuth2ClientDO clientDO = randomPojo(OAuth2ClientDO.class).setClientId(clientId);\n        when(oauth2ClientService.validOAuthClientFromCache(eq(clientId))).thenReturn(clientDO);\n        // mock 数据（访问令牌）\n        OAuth2RefreshTokenDO refreshTokenDO = randomPojo(OAuth2RefreshTokenDO.class)\n                .setRefreshToken(refreshToken).setClientId(clientId)\n                .setExpiresTime(LocalDateTime.now().minusDays(1));\n        oauth2RefreshTokenMapper.insert(refreshTokenDO);\n\n        // 调用，并断言\n        assertServiceException(() -> oauth2TokenService.refreshAccessToken(refreshToken, clientId),\n                new ErrorCode(401, \"刷新令牌已过期\"));\n        assertEquals(0, oauth2RefreshTokenMapper.selectCount());\n    }\n\n    @Test\n    public void testRefreshAccessToken_success() {\n        TenantContextHolder.setTenantId(0L);\n        // 准备参数\n        String refreshToken = randomString();\n        String clientId = randomString();\n        // mock 方法\n        OAuth2ClientDO clientDO = randomPojo(OAuth2ClientDO.class).setClientId(clientId)\n                .setAccessTokenValiditySeconds(30);\n        when(oauth2ClientService.validOAuthClientFromCache(eq(clientId))).thenReturn(clientDO);\n        // mock 数据（访问令牌）\n        OAuth2RefreshTokenDO refreshTokenDO = randomPojo(OAuth2RefreshTokenDO.class)\n                .setRefreshToken(refreshToken).setClientId(clientId)\n                .setExpiresTime(LocalDateTime.now().plusDays(1))\n                .setUserType(UserTypeEnum.ADMIN.getValue());\n        oauth2RefreshTokenMapper.insert(refreshTokenDO);\n        // mock 数据（访问令牌）\n        OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class).setRefreshToken(refreshToken)\n                .setUserType(refreshTokenDO.getUserType());\n        oauth2AccessTokenMapper.insert(accessTokenDO);\n        oauth2AccessTokenRedisDAO.set(accessTokenDO);\n        // mock 数据（用户）\n        AdminUserDO user = randomPojo(AdminUserDO.class);\n        when(adminUserService.getUser(refreshTokenDO.getUserId())).thenReturn(user);\n\n        // 调用\n        OAuth2AccessTokenDO newAccessTokenDO = oauth2TokenService.refreshAccessToken(refreshToken, clientId);\n        // 断言，老的访问令牌被删除\n        assertNull(oauth2AccessTokenMapper.selectByAccessToken(accessTokenDO.getAccessToken()));\n        assertNull(oauth2AccessTokenRedisDAO.get(accessTokenDO.getAccessToken()));\n        // 断言，新的访问令牌\n        OAuth2AccessTokenDO dbAccessTokenDO = oauth2AccessTokenMapper.selectByAccessToken(newAccessTokenDO.getAccessToken());\n        assertPojoEquals(newAccessTokenDO, dbAccessTokenDO, \"createTime\", \"updateTime\", \"deleted\");\n        assertPojoEquals(newAccessTokenDO, refreshTokenDO, \"id\", \"expiresTime\", \"createTime\", \"updateTime\", \"deleted\",\n                \"creator\", \"updater\");\n        assertFalse(DateUtils.isExpired(newAccessTokenDO.getExpiresTime()));\n        // 断言，新的访问令牌的缓存\n        OAuth2AccessTokenDO redisAccessTokenDO = oauth2AccessTokenRedisDAO.get(newAccessTokenDO.getAccessToken());\n        assertPojoEquals(newAccessTokenDO, redisAccessTokenDO, \"createTime\", \"updateTime\", \"deleted\");\n    }\n\n    @Test\n    public void testGetAccessToken() {\n        // mock 数据（访问令牌）\n        OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class)\n                .setExpiresTime(LocalDateTime.now().plusDays(1));\n        oauth2AccessTokenMapper.insert(accessTokenDO);\n        // 准备参数\n        String accessToken = accessTokenDO.getAccessToken();\n\n        // 调用\n        OAuth2AccessTokenDO result = oauth2TokenService.getAccessToken(accessToken);\n        // 断言\n        assertPojoEquals(accessTokenDO, result, \"createTime\", \"updateTime\", \"deleted\",\n                \"creator\", \"updater\");\n        assertPojoEquals(accessTokenDO, oauth2AccessTokenRedisDAO.get(accessToken), \"createTime\", \"updateTime\", \"deleted\",\n                \"creator\", \"updater\");\n    }\n\n    @Test\n    public void testCheckAccessToken_null() {\n        // 调研，并断言\n        assertServiceException(() -> oauth2TokenService.checkAccessToken(randomString()),\n                new ErrorCode(401, \"访问令牌不存在\"));\n    }\n\n    @Test\n    public void testCheckAccessToken_expired() {\n        // mock 数据（访问令牌）\n        OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class)\n                .setExpiresTime(LocalDateTime.now().minusDays(1));\n        oauth2AccessTokenMapper.insert(accessTokenDO);\n        // 准备参数\n        String accessToken = accessTokenDO.getAccessToken();\n\n        // 调研，并断言\n        assertServiceException(() -> oauth2TokenService.checkAccessToken(accessToken),\n                new ErrorCode(401, \"访问令牌已过期\"));\n    }\n\n    @Test\n    public void testCheckAccessToken_success() {\n        // mock 数据（访问令牌）\n        OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class)\n                .setExpiresTime(LocalDateTime.now().plusDays(1));\n        oauth2AccessTokenMapper.insert(accessTokenDO);\n        // 准备参数\n        String accessToken = accessTokenDO.getAccessToken();\n\n        // 调研，并断言\n        OAuth2AccessTokenDO result = oauth2TokenService.getAccessToken(accessToken);\n        // 断言\n        assertPojoEquals(accessTokenDO, result, \"createTime\", \"updateTime\", \"deleted\",\n                \"creator\", \"updater\");\n    }\n\n    @Test\n    public void testRemoveAccessToken_null() {\n        // 调用，并断言\n        assertNull(oauth2TokenService.removeAccessToken(randomString()));\n    }\n\n    @Test\n    public void testRemoveAccessToken_success() {\n        // mock 数据（访问令牌）\n        OAuth2AccessTokenDO accessTokenDO = randomPojo(OAuth2AccessTokenDO.class)\n                .setExpiresTime(LocalDateTime.now().plusDays(1));\n        oauth2AccessTokenMapper.insert(accessTokenDO);\n        // mock 数据（刷新令牌）\n        OAuth2RefreshTokenDO refreshTokenDO = randomPojo(OAuth2RefreshTokenDO.class)\n                .setRefreshToken(accessTokenDO.getRefreshToken());\n        oauth2RefreshTokenMapper.insert(refreshTokenDO);\n        // 调用\n        OAuth2AccessTokenDO result = oauth2TokenService.removeAccessToken(accessTokenDO.getAccessToken());\n        assertPojoEquals(accessTokenDO, result, \"createTime\", \"updateTime\", \"deleted\",\n                \"creator\", \"updater\");\n        // 断言数据\n        assertNull(oauth2AccessTokenMapper.selectByAccessToken(accessTokenDO.getAccessToken()));\n        assertNull(oauth2RefreshTokenMapper.selectByRefreshToken(accessTokenDO.getRefreshToken()));\n        assertNull(oauth2AccessTokenRedisDAO.get(accessTokenDO.getAccessToken()));\n    }\n\n\n    @Test\n    public void testGetAccessTokenPage() {\n        // mock 数据\n        OAuth2AccessTokenDO dbAccessToken = randomPojo(OAuth2AccessTokenDO.class, o -> { // 等会查询到\n            o.setUserId(10L);\n            o.setUserType(1);\n            o.setClientId(\"test_client\");\n            o.setExpiresTime(LocalDateTime.now().plusDays(1));\n        });\n        oauth2AccessTokenMapper.insert(dbAccessToken);\n        // 测试 userId 不匹配\n        oauth2AccessTokenMapper.insert(cloneIgnoreId(dbAccessToken, o -> o.setUserId(20L)));\n        // 测试 userType 不匹配\n        oauth2AccessTokenMapper.insert(cloneIgnoreId(dbAccessToken, o -> o.setUserType(2)));\n        // 测试 userType 不匹配\n        oauth2AccessTokenMapper.insert(cloneIgnoreId(dbAccessToken, o -> o.setClientId(\"it_client\")));\n        // 测试 expireTime 不匹配\n        oauth2AccessTokenMapper.insert(cloneIgnoreId(dbAccessToken, o -> o.setExpiresTime(LocalDateTimeUtil.now())));\n        // 准备参数\n        OAuth2AccessTokenPageReqVO reqVO = new OAuth2AccessTokenPageReqVO();\n        reqVO.setUserId(10L);\n        reqVO.setUserType(1);\n        reqVO.setClientId(\"test\");\n\n        // 调用\n        PageResult<OAuth2AccessTokenDO> pageResult = oauth2TokenService.getAccessTokenPage(reqVO);\n        // 断言\n        assertEquals(1, pageResult.getTotal());\n        assertEquals(1, pageResult.getList().size());\n        assertPojoEquals(dbAccessToken, pageResult.getList().get(0));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/java/co/yixiang/yshop/module/system/service/permission/MenuServiceImplTest.java",
    "content": "package co.yixiang.yshop.module.system.service.permission;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.test.core.ut.BaseDbUnitTest;\nimport co.yixiang.yshop.module.system.controller.admin.permission.vo.menu.MenuListReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.permission.vo.menu.MenuSaveVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.permission.MenuDO;\nimport co.yixiang.yshop.module.system.dal.mysql.permission.MenuMapper;\nimport co.yixiang.yshop.module.system.enums.permission.MenuTypeEnum;\nimport co.yixiang.yshop.module.system.service.tenant.TenantService;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.mock.mockito.MockBean;\nimport org.springframework.context.annotation.Import;\n\nimport jakarta.annotation.Resource;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.List;\nimport java.util.Set;\n\nimport static co.yixiang.yshop.framework.common.util.collection.SetUtils.asSet;\nimport static co.yixiang.yshop.framework.common.util.object.ObjectUtils.cloneIgnoreId;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertPojoEquals;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertServiceException;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.*;\nimport static co.yixiang.yshop.module.system.dal.dataobject.permission.MenuDO.ID_ROOT;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.*;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNull;\nimport static org.mockito.ArgumentMatchers.argThat;\nimport static org.mockito.Mockito.doNothing;\nimport static org.mockito.Mockito.verify;\n\n@Import(MenuServiceImpl.class)\npublic class MenuServiceImplTest extends BaseDbUnitTest {\n\n    @Resource\n    private MenuServiceImpl menuService;\n\n    @Resource\n    private MenuMapper menuMapper;\n\n    @MockBean\n    private PermissionService permissionService;\n    @MockBean\n    private TenantService tenantService;\n\n    @Test\n    public void testCreateMenu_success() {\n        // mock 数据（构造父菜单）\n        MenuDO menuDO = buildMenuDO(MenuTypeEnum.MENU,\n                \"parent\", 0L);\n        menuMapper.insert(menuDO);\n        Long parentId = menuDO.getId();\n        // 准备参数\n        MenuSaveVO reqVO = randomPojo(MenuSaveVO.class, o -> {\n            o.setParentId(parentId);\n            o.setName(\"testSonName\");\n            o.setType(MenuTypeEnum.MENU.getType());\n        }).setId(null); // 防止 id 被赋值\n        Long menuId = menuService.createMenu(reqVO);\n\n        // 校验记录的属性是否正确\n        MenuDO dbMenu = menuMapper.selectById(menuId);\n        assertPojoEquals(reqVO, dbMenu, \"id\");\n    }\n\n    @Test\n    public void testUpdateMenu_success() {\n        // mock 数据（构造父子菜单）\n        MenuDO sonMenuDO = createParentAndSonMenu();\n        Long sonId = sonMenuDO.getId();\n        // 准备参数\n        MenuSaveVO reqVO = randomPojo(MenuSaveVO.class, o -> {\n            o.setId(sonId);\n            o.setName(\"testSonName\"); // 修改名字\n            o.setParentId(sonMenuDO.getParentId());\n            o.setType(MenuTypeEnum.MENU.getType());\n        });\n\n        // 调用\n        menuService.updateMenu(reqVO);\n        // 校验记录的属性是否正确\n        MenuDO dbMenu = menuMapper.selectById(sonId);\n        assertPojoEquals(reqVO, dbMenu);\n    }\n\n    @Test\n    public void testUpdateMenu_sonIdNotExist() {\n        // 准备参数\n        MenuSaveVO reqVO = randomPojo(MenuSaveVO.class);\n        // 调用，并断言异常\n        assertServiceException(() -> menuService.updateMenu(reqVO), MENU_NOT_EXISTS);\n    }\n\n    @Test\n    public void testDeleteMenu_success() {\n        // mock 数据\n        MenuDO menuDO = randomPojo(MenuDO.class);\n        menuMapper.insert(menuDO);\n        // 准备参数\n        Long id = menuDO.getId();\n\n        // 调用\n        menuService.deleteMenu(id);\n        // 断言\n        MenuDO dbMenuDO = menuMapper.selectById(id);\n        assertNull(dbMenuDO);\n        verify(permissionService).processMenuDeleted(id);\n    }\n\n    @Test\n    public void testDeleteMenu_menuNotExist() {\n        assertServiceException(() -> menuService.deleteMenu(randomLongId()),\n                MENU_NOT_EXISTS);\n    }\n\n    @Test\n    public void testDeleteMenu_existChildren() {\n        // mock 数据（构造父子菜单）\n        MenuDO sonMenu = createParentAndSonMenu();\n        // 准备参数\n        Long parentId = sonMenu.getParentId();\n\n        // 调用并断言异常\n        assertServiceException(() -> menuService.deleteMenu(parentId), MENU_EXISTS_CHILDREN);\n    }\n\n    @Test\n    public void testGetMenuList_all() {\n        // mock 数据\n        MenuDO menu100 = randomPojo(MenuDO.class);\n        menuMapper.insert(menu100);\n        MenuDO menu101 = randomPojo(MenuDO.class);\n        menuMapper.insert(menu101);\n        // 准备参数\n\n        // 调用\n        List<MenuDO> list = menuService.getMenuList();\n        // 断言\n        assertEquals(2, list.size());\n        assertPojoEquals(menu100, list.get(0));\n        assertPojoEquals(menu101, list.get(1));\n    }\n\n    @Test\n    public void testGetMenuList() {\n        // mock 数据\n        MenuDO menuDO = randomPojo(MenuDO.class, o -> o.setName(\"yshop\").setStatus(CommonStatusEnum.ENABLE.getStatus()));\n        menuMapper.insert(menuDO);\n        // 测试 status 不匹配\n        menuMapper.insert(cloneIgnoreId(menuDO, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));\n        // 测试 name 不匹配\n        menuMapper.insert(cloneIgnoreId(menuDO, o -> o.setName(\"艿\")));\n        // 准备参数\n        MenuListReqVO reqVO = new MenuListReqVO().setName(\"芋\").setStatus(CommonStatusEnum.ENABLE.getStatus());\n\n        // 调用\n        List<MenuDO> result = menuService.getMenuList(reqVO);\n        // 断言\n        assertEquals(1, result.size());\n        assertPojoEquals(menuDO, result.get(0));\n    }\n\n    @Test\n    public void testGetMenuListByTenant() {\n        // mock 数据\n        MenuDO menu100 = randomPojo(MenuDO.class, o -> o.setId(100L).setStatus(CommonStatusEnum.ENABLE.getStatus()));\n        menuMapper.insert(menu100);\n        MenuDO menu101 = randomPojo(MenuDO.class, o -> o.setId(101L).setStatus(CommonStatusEnum.DISABLE.getStatus()));\n        menuMapper.insert(menu101);\n        MenuDO menu102 = randomPojo(MenuDO.class, o -> o.setId(102L).setStatus(CommonStatusEnum.ENABLE.getStatus()));\n        menuMapper.insert(menu102);\n        // mock 过滤菜单\n        Set<Long> menuIds = asSet(100L, 101L);\n        doNothing().when(tenantService).handleTenantMenu(argThat(handler -> {\n            handler.handle(menuIds);\n            return true;\n        }));\n        // 准备参数\n        MenuListReqVO reqVO = new MenuListReqVO().setStatus(CommonStatusEnum.ENABLE.getStatus());\n\n        // 调用\n        List<MenuDO> result = menuService.getMenuListByTenant(reqVO);\n        // 断言\n        assertEquals(1, result.size());\n        assertPojoEquals(menu100, result.get(0));\n    }\n\n    @Test\n    public void testGetMenuIdListByPermissionFromCache() {\n        // mock 数据\n        MenuDO menu100 = randomPojo(MenuDO.class);\n        menuMapper.insert(menu100);\n        MenuDO menu101 = randomPojo(MenuDO.class);\n        menuMapper.insert(menu101);\n        // 准备参数\n        String permission = menu100.getPermission();\n\n        // 调用\n        List<Long> ids = menuService.getMenuIdListByPermissionFromCache(permission);\n        // 断言\n        assertEquals(1, ids.size());\n        assertEquals(menu100.getId(), ids.get(0));\n    }\n\n    @Test\n    public void testGetMenuList_ids() {\n        // mock 数据\n        MenuDO menu100 = randomPojo(MenuDO.class);\n        menuMapper.insert(menu100);\n        MenuDO menu101 = randomPojo(MenuDO.class);\n        menuMapper.insert(menu101);\n        // 准备参数\n        Collection<Long> ids = Collections.singleton(menu100.getId());\n\n        // 调用\n        List<MenuDO> list = menuService.getMenuList(ids);\n        // 断言\n        assertEquals(1, list.size());\n        assertPojoEquals(menu100, list.get(0));\n    }\n\n    @Test\n    public void testGetMenu() {\n        // mock 数据\n        MenuDO menu = randomPojo(MenuDO.class);\n        menuMapper.insert(menu);\n        // 准备参数\n        Long id = menu.getId();\n\n        // 调用\n        MenuDO dbMenu = menuService.getMenu(id);\n        // 断言\n        assertPojoEquals(menu, dbMenu);\n    }\n\n    @Test\n    public void testValidateParentMenu_success() {\n        // mock 数据\n        MenuDO menuDO = buildMenuDO(MenuTypeEnum.MENU, \"parent\", 0L);\n        menuMapper.insert(menuDO);\n        // 准备参数\n        Long parentId = menuDO.getId();\n\n        // 调用，无需断言\n        menuService.validateParentMenu(parentId, null);\n    }\n\n    @Test\n    public void testValidateParentMenu_canNotSetSelfToBeParent() {\n        // 调用，并断言异常\n        assertServiceException(() -> menuService.validateParentMenu(1L, 1L),\n                MENU_PARENT_ERROR);\n    }\n\n    @Test\n    public void testValidateParentMenu_parentNotExist() {\n        // 调用，并断言异常\n        assertServiceException(() -> menuService.validateParentMenu(randomLongId(), null),\n                MENU_PARENT_NOT_EXISTS);\n    }\n\n    @Test\n    public void testValidateParentMenu_parentTypeError() {\n        // mock 数据\n        MenuDO menuDO = buildMenuDO(MenuTypeEnum.BUTTON, \"parent\", 0L);\n        menuMapper.insert(menuDO);\n        // 准备参数\n        Long parentId = menuDO.getId();\n\n        // 调用，并断言异常\n        assertServiceException(() -> menuService.validateParentMenu(parentId, null),\n                MENU_PARENT_NOT_DIR_OR_MENU);\n    }\n\n    @Test\n    public void testValidateMenu_success() {\n        // mock 父子菜单\n        MenuDO sonMenu = createParentAndSonMenu();\n        // 准备参数\n        Long parentId = sonMenu.getParentId();\n        Long otherSonMenuId = randomLongId();\n        String otherSonMenuName = randomString();\n\n        // 调用，无需断言\n        menuService.validateMenu(parentId, otherSonMenuName, otherSonMenuId);\n    }\n\n    @Test\n    public void testValidateMenu_sonMenuNameDuplicate() {\n        // mock 父子菜单\n        MenuDO sonMenu = createParentAndSonMenu();\n        // 准备参数\n        Long parentId = sonMenu.getParentId();\n        Long otherSonMenuId = randomLongId();\n        String otherSonMenuName = sonMenu.getName(); //相同名称\n\n        // 调用，并断言异常\n        assertServiceException(() -> menuService.validateMenu(parentId, otherSonMenuName, otherSonMenuId),\n                MENU_NAME_DUPLICATE);\n    }\n\n    // ====================== 初始化方法 ======================\n\n    /**\n     * 插入父子菜单，返回子菜单\n     *\n     * @return 子菜单\n     */\n    private MenuDO createParentAndSonMenu() {\n        // 构造父子菜单\n        MenuDO parentMenuDO = buildMenuDO(MenuTypeEnum.MENU, \"parent\", ID_ROOT);\n        menuMapper.insert(parentMenuDO);\n        // 构建子菜单\n        MenuDO sonMenuDO = buildMenuDO(MenuTypeEnum.MENU, \"testSonName\",\n                parentMenuDO.getParentId());\n        menuMapper.insert(sonMenuDO);\n        return sonMenuDO;\n    }\n\n    private MenuDO buildMenuDO(MenuTypeEnum type, String name, Long parentId) {\n        return buildMenuDO(type, name, parentId, randomCommonStatus());\n    }\n\n    private MenuDO buildMenuDO(MenuTypeEnum type, String name, Long parentId, Integer status) {\n        return randomPojo(MenuDO.class, o -> o.setId(null).setName(name).setParentId(parentId)\n                .setType(type.getType()).setStatus(status));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/java/co/yixiang/yshop/module/system/service/permission/PermissionServiceTest.java",
    "content": "package co.yixiang.yshop.module.system.service.permission;\n\nimport cn.hutool.core.collection.CollUtil;\nimport cn.hutool.extra.spring.SpringUtil;\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.test.core.ut.BaseDbUnitTest;\nimport co.yixiang.yshop.module.system.api.permission.dto.DeptDataPermissionRespDTO;\nimport co.yixiang.yshop.module.system.dal.dataobject.dept.DeptDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.permission.MenuDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.permission.RoleDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.permission.RoleMenuDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.permission.UserRoleDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.user.AdminUserDO;\nimport co.yixiang.yshop.module.system.dal.mysql.permission.RoleMenuMapper;\nimport co.yixiang.yshop.module.system.dal.mysql.permission.UserRoleMapper;\nimport co.yixiang.yshop.module.system.enums.permission.DataScopeEnum;\nimport co.yixiang.yshop.module.system.service.dept.DeptService;\nimport co.yixiang.yshop.module.system.service.user.AdminUserService;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.MockedStatic;\nimport org.springframework.boot.test.mock.mockito.MockBean;\nimport org.springframework.context.annotation.Import;\n\nimport jakarta.annotation.Resource;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Set;\n\nimport static cn.hutool.core.collection.ListUtil.toList;\nimport static co.yixiang.yshop.framework.common.util.collection.SetUtils.asSet;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertPojoEquals;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.randomLongId;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.randomPojo;\nimport static java.util.Collections.singleton;\nimport static java.util.Collections.singletonList;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.*;\n\n@Import({PermissionServiceImpl.class})\npublic class PermissionServiceTest extends BaseDbUnitTest {\n\n    @Resource\n    private PermissionServiceImpl permissionService;\n\n    @Resource\n    private RoleMenuMapper roleMenuMapper;\n    @Resource\n    private UserRoleMapper userRoleMapper;\n\n    @MockBean\n    private RoleService roleService;\n    @MockBean\n    private MenuService menuService;\n    @MockBean\n    private DeptService deptService;\n    @MockBean\n    private AdminUserService userService;\n\n    @Test\n    public void testHasAnyPermissions_superAdmin() {\n        try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {\n            springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))\n                    .thenReturn(permissionService);\n\n            // 准备参数\n            Long userId = 1L;\n            String[] roles = new String[]{\"system:user:query\", \"system:user:create\"};\n            // mock 用户登录的角色\n            userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(100L));\n            RoleDO role = randomPojo(RoleDO.class, o -> o.setId(100L)\n                    .setStatus(CommonStatusEnum.ENABLE.getStatus()));\n            when(roleService.getRoleListFromCache(eq(singleton(100L)))).thenReturn(toList(role));\n            // mock 其它方法\n            when(roleService.hasAnySuperAdmin(eq(asSet(100L)))).thenReturn(true);\n\n            // 调用，并断言\n            assertTrue(permissionService.hasAnyPermissions(userId, roles));\n        }\n    }\n\n    @Test\n    public void testHasAnyPermissions_normal() {\n        try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {\n            springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))\n                    .thenReturn(permissionService);\n\n            // 准备参数\n            Long userId = 1L;\n            String[] roles = new String[]{\"system:user:query\", \"system:user:create\"};\n            // mock 用户登录的角色\n            userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(100L));\n            RoleDO role = randomPojo(RoleDO.class, o -> o.setId(100L)\n                    .setStatus(CommonStatusEnum.ENABLE.getStatus()));\n            when(roleService.getRoleListFromCache(eq(singleton(100L)))).thenReturn(toList(role));\n            // mock 菜单\n            Long menuId = 1000L;\n            when(menuService.getMenuIdListByPermissionFromCache(\n                    eq(\"system:user:create\"))).thenReturn(singletonList(menuId));\n            roleMenuMapper.insert(randomPojo(RoleMenuDO.class).setRoleId(100L).setMenuId(1000L));\n\n            // 调用，并断言\n            assertTrue(permissionService.hasAnyPermissions(userId, roles));\n        }\n    }\n\n    @Test\n    public void testHasAnyRoles() {\n        try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {\n            springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))\n                    .thenReturn(permissionService);\n\n            // 准备参数\n            Long userId = 1L;\n            String[] roles = new String[]{\"yshop\", \"tudou\"};\n            // mock 用户与角色的缓存\n            userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(100L));\n            RoleDO role = randomPojo(RoleDO.class, o -> o.setId(100L).setCode(\"tudou\")\n                    .setStatus(CommonStatusEnum.ENABLE.getStatus()));\n            when(roleService.getRoleListFromCache(eq(singleton(100L)))).thenReturn(toList(role));\n\n            // 调用，并断言\n            assertTrue(permissionService.hasAnyRoles(userId, roles));\n        }\n    }\n\n    // ========== 角色-菜单的相关方法  ==========\n\n    @Test\n    public void testAssignRoleMenu() {\n        // 准备参数\n        Long roleId = 1L;\n        Set<Long> menuIds = asSet(200L, 300L);\n        // mock 数据\n        RoleMenuDO roleMenu01 = randomPojo(RoleMenuDO.class).setRoleId(1L).setMenuId(100L);\n        roleMenuMapper.insert(roleMenu01);\n        RoleMenuDO roleMenu02 = randomPojo(RoleMenuDO.class).setRoleId(1L).setMenuId(200L);\n        roleMenuMapper.insert(roleMenu02);\n\n        // 调用\n        permissionService.assignRoleMenu(roleId, menuIds);\n        // 断言\n        List<RoleMenuDO> roleMenuList = roleMenuMapper.selectList();\n        assertEquals(2, roleMenuList.size());\n        assertEquals(1L, roleMenuList.get(0).getRoleId());\n        assertEquals(200L, roleMenuList.get(0).getMenuId());\n        assertEquals(1L, roleMenuList.get(1).getRoleId());\n        assertEquals(300L, roleMenuList.get(1).getMenuId());\n    }\n\n    @Test\n    public void testProcessRoleDeleted() {\n        // 准备参数\n        Long roleId = randomLongId();\n        // mock 数据 UserRole\n        UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setRoleId(roleId)); // 被删除\n        userRoleMapper.insert(userRoleDO01);\n        UserRoleDO userRoleDO02 = randomPojo(UserRoleDO.class); // 不被删除\n        userRoleMapper.insert(userRoleDO02);\n        // mock 数据 RoleMenu\n        RoleMenuDO roleMenuDO01 = randomPojo(RoleMenuDO.class, o -> o.setRoleId(roleId)); // 被删除\n        roleMenuMapper.insert(roleMenuDO01);\n        RoleMenuDO roleMenuDO02 = randomPojo(RoleMenuDO.class); // 不被删除\n        roleMenuMapper.insert(roleMenuDO02);\n\n        // 调用\n        permissionService.processRoleDeleted(roleId);\n        // 断言数据 RoleMenuDO\n        List<RoleMenuDO> dbRoleMenus = roleMenuMapper.selectList();\n        assertEquals(1, dbRoleMenus.size());\n        assertPojoEquals(dbRoleMenus.get(0), roleMenuDO02);\n        // 断言数据 UserRoleDO\n        List<UserRoleDO> dbUserRoles = userRoleMapper.selectList();\n        assertEquals(1, dbUserRoles.size());\n        assertPojoEquals(dbUserRoles.get(0), userRoleDO02);\n    }\n\n    @Test\n    public void testProcessMenuDeleted() {\n        // 准备参数\n        Long menuId = randomLongId();\n        // mock 数据\n        RoleMenuDO roleMenuDO01 = randomPojo(RoleMenuDO.class, o -> o.setMenuId(menuId)); // 被删除\n        roleMenuMapper.insert(roleMenuDO01);\n        RoleMenuDO roleMenuDO02 = randomPojo(RoleMenuDO.class); // 不被删除\n        roleMenuMapper.insert(roleMenuDO02);\n\n        // 调用\n        permissionService.processMenuDeleted(menuId);\n        // 断言数据\n        List<RoleMenuDO> dbRoleMenus = roleMenuMapper.selectList();\n        assertEquals(1, dbRoleMenus.size());\n        assertPojoEquals(dbRoleMenus.get(0), roleMenuDO02);\n    }\n\n    @Test\n    public void testGetRoleMenuIds_superAdmin() {\n        // 准备参数\n        Long roleId = 100L;\n        // mock 方法\n        when(roleService.hasAnySuperAdmin(eq(singleton(100L)))).thenReturn(true);\n        List<MenuDO> menuList = singletonList(randomPojo(MenuDO.class).setId(1L));\n        when(menuService.getMenuList()).thenReturn(menuList);\n\n        // 调用\n        Set<Long> menuIds = permissionService.getRoleMenuListByRoleId(roleId);\n        // 断言\n        assertEquals(singleton(1L), menuIds);\n    }\n\n    @Test\n    public void testGetRoleMenuIds_normal() {\n        // 准备参数\n        Long roleId = 100L;\n        // mock 数据\n        RoleMenuDO roleMenu01 = randomPojo(RoleMenuDO.class).setRoleId(100L).setMenuId(1L);\n        roleMenuMapper.insert(roleMenu01);\n        RoleMenuDO roleMenu02 = randomPojo(RoleMenuDO.class).setRoleId(100L).setMenuId(2L);\n        roleMenuMapper.insert(roleMenu02);\n\n        // 调用\n        Set<Long> menuIds = permissionService.getRoleMenuListByRoleId(roleId);\n        // 断言\n        assertEquals(asSet(1L, 2L), menuIds);\n    }\n\n    @Test\n    public void testGetMenuRoleIdListByMenuIdFromCache() {\n        // 准备参数\n        Long menuId = 1L;\n        // mock 数据\n        RoleMenuDO roleMenu01 = randomPojo(RoleMenuDO.class).setRoleId(100L).setMenuId(1L);\n        roleMenuMapper.insert(roleMenu01);\n        RoleMenuDO roleMenu02 = randomPojo(RoleMenuDO.class).setRoleId(200L).setMenuId(1L);\n        roleMenuMapper.insert(roleMenu02);\n\n        // 调用\n        Set<Long> roleIds = permissionService.getMenuRoleIdListByMenuIdFromCache(menuId);\n        // 断言\n        assertEquals(asSet(100L, 200L), roleIds);\n    }\n\n    // ========== 用户-角色的相关方法  ==========\n\n    @Test\n    public void testAssignUserRole() {\n        // 准备参数\n        Long userId = 1L;\n        Set<Long> roleIds = asSet(200L, 300L);\n        // mock 数据\n        UserRoleDO userRole01 = randomPojo(UserRoleDO.class).setUserId(1L).setRoleId(100L);\n        userRoleMapper.insert(userRole01);\n        UserRoleDO userRole02 = randomPojo(UserRoleDO.class).setUserId(1L).setRoleId(200L);\n        userRoleMapper.insert(userRole02);\n\n        // 调用\n        permissionService.assignUserRole(userId, roleIds);\n        // 断言\n        List<UserRoleDO> userRoleDOList = userRoleMapper.selectList();\n        assertEquals(2, userRoleDOList.size());\n        assertEquals(1L, userRoleDOList.get(0).getUserId());\n        assertEquals(200L, userRoleDOList.get(0).getRoleId());\n        assertEquals(1L, userRoleDOList.get(1).getUserId());\n        assertEquals(300L, userRoleDOList.get(1).getRoleId());\n    }\n\n    @Test\n    public void testProcessUserDeleted() {\n        // 准备参数\n        Long userId = randomLongId();\n        // mock 数据\n        UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setUserId(userId)); // 被删除\n        userRoleMapper.insert(userRoleDO01);\n        UserRoleDO userRoleDO02 = randomPojo(UserRoleDO.class); // 不被删除\n        userRoleMapper.insert(userRoleDO02);\n\n        // 调用\n        permissionService.processUserDeleted(userId);\n        // 断言数据\n        List<UserRoleDO> dbUserRoles = userRoleMapper.selectList();\n        assertEquals(1, dbUserRoles.size());\n        assertPojoEquals(dbUserRoles.get(0), userRoleDO02);\n    }\n\n    @Test\n    public void testGetUserRoleIdListByUserId() {\n        // 准备参数\n        Long userId = 1L;\n        // mock 数据\n        UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(10L));\n        userRoleMapper.insert(userRoleDO01);\n        UserRoleDO roleMenuDO02 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(20L));\n        userRoleMapper.insert(roleMenuDO02);\n\n        // 调用\n        Set<Long> result = permissionService.getUserRoleIdListByUserId(userId);\n        // 断言\n        assertEquals(asSet(10L, 20L), result);\n    }\n\n    @Test\n    public void testGetUserRoleIdListByUserIdFromCache() {\n        // 准备参数\n        Long userId = 1L;\n        // mock 数据\n        UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(10L));\n        userRoleMapper.insert(userRoleDO01);\n        UserRoleDO roleMenuDO02 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(20L));\n        userRoleMapper.insert(roleMenuDO02);\n\n        // 调用\n        Set<Long> result = permissionService.getUserRoleIdListByUserIdFromCache(userId);\n        // 断言\n        assertEquals(asSet(10L, 20L), result);\n    }\n\n    @Test\n    public void testGetUserRoleIdsFromCache() {\n        // 准备参数\n        Long userId = 1L;\n        // mock 数据\n        UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(10L));\n        userRoleMapper.insert(userRoleDO01);\n        UserRoleDO roleMenuDO02 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(20L));\n        userRoleMapper.insert(roleMenuDO02);\n\n        // 调用\n        Set<Long> result = permissionService.getUserRoleIdListByUserIdFromCache(userId);\n        // 断言\n        assertEquals(asSet(10L, 20L), result);\n    }\n\n    @Test\n    public void testGetUserRoleIdListByRoleId() {\n        // 准备参数\n        Collection<Long> roleIds = asSet(10L, 20L);\n        // mock 数据\n        UserRoleDO userRoleDO01 = randomPojo(UserRoleDO.class, o -> o.setUserId(1L).setRoleId(10L));\n        userRoleMapper.insert(userRoleDO01);\n        UserRoleDO roleMenuDO02 = randomPojo(UserRoleDO.class, o -> o.setUserId(2L).setRoleId(20L));\n        userRoleMapper.insert(roleMenuDO02);\n\n        // 调用\n        Set<Long> result = permissionService.getUserRoleIdListByRoleId(roleIds);\n        // 断言\n        assertEquals(asSet(1L, 2L), result);\n    }\n\n    @Test\n    public void testGetEnableUserRoleListByUserIdFromCache() {\n        try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {\n            springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))\n                    .thenReturn(permissionService);\n\n            // 准备参数\n            Long userId = 1L;\n            // mock 用户登录的角色\n            userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(100L));\n            userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(200L));\n            RoleDO role01 = randomPojo(RoleDO.class, o -> o.setId(100L)\n                    .setStatus(CommonStatusEnum.ENABLE.getStatus()));\n            RoleDO role02 = randomPojo(RoleDO.class, o -> o.setId(200L)\n                    .setStatus(CommonStatusEnum.DISABLE.getStatus()));\n            when(roleService.getRoleListFromCache(eq(asSet(100L, 200L))))\n                    .thenReturn(toList(role01, role02));\n\n            // 调用\n            List<RoleDO> result = permissionService.getEnableUserRoleListByUserIdFromCache(userId);\n            // 断言\n            assertEquals(1, result.size());\n            assertPojoEquals(role01, result.get(0));\n        }\n    }\n\n    // ========== 用户-部门的相关方法  ==========\n\n    @Test\n    public void testAssignRoleDataScope() {\n        // 准备参数\n        Long roleId = 1L;\n        Integer dataScope = 2;\n        Set<Long> dataScopeDeptIds = asSet(10L, 20L);\n\n        // 调用\n        permissionService.assignRoleDataScope(roleId, dataScope, dataScopeDeptIds);\n        // 断言\n        verify(roleService).updateRoleDataScope(eq(roleId), eq(dataScope), eq(dataScopeDeptIds));\n    }\n\n    @Test\n    public void testGetDeptDataPermission_All() {\n        try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {\n            springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))\n                    .thenReturn(permissionService);\n\n            // 准备参数\n            Long userId = 1L;\n            // mock 用户的角色编号\n            userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(2L));\n            // mock 获得用户的角色\n            RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.ALL.getScope())\n                    .setStatus(CommonStatusEnum.ENABLE.getStatus()));\n            when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(toList(roleDO));\n\n            // 调用\n            DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);\n            // 断言\n            assertTrue(result.getAll());\n            assertFalse(result.getSelf());\n            assertTrue(CollUtil.isEmpty(result.getDeptIds()));\n        }\n    }\n\n    @Test\n    public void testGetDeptDataPermission_DeptCustom() {\n        try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {\n            springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))\n                    .thenReturn(permissionService);\n\n            // 准备参数\n            Long userId = 1L;\n            // mock 用户的角色编号\n            userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(2L));\n            // mock 获得用户的角色\n            RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_CUSTOM.getScope())\n                    .setStatus(CommonStatusEnum.ENABLE.getStatus()));\n            when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(toList(roleDO));\n            // mock 部门的返回\n            when(userService.getUser(eq(1L))).thenReturn(new AdminUserDO().setDeptId(3L),\n                    null, null); // 最后返回 null 的目的，看看会不会重复调用\n\n            // 调用\n            DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);\n            // 断言\n            assertFalse(result.getAll());\n            assertFalse(result.getSelf());\n            assertEquals(roleDO.getDataScopeDeptIds().size() + 1, result.getDeptIds().size());\n            assertTrue(CollUtil.containsAll(result.getDeptIds(), roleDO.getDataScopeDeptIds()));\n            assertTrue(CollUtil.contains(result.getDeptIds(), 3L));\n        }\n    }\n\n    @Test\n    public void testGetDeptDataPermission_DeptOnly() {\n        try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {\n            springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))\n                    .thenReturn(permissionService);\n\n            // 准备参数\n            Long userId = 1L;\n            // mock 用户的角色编号\n            userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(2L));\n            // mock 获得用户的角色\n            RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_ONLY.getScope())\n                    .setStatus(CommonStatusEnum.ENABLE.getStatus()));\n            when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(toList(roleDO));\n            // mock 部门的返回\n            when(userService.getUser(eq(1L))).thenReturn(new AdminUserDO().setDeptId(3L),\n                    null, null); // 最后返回 null 的目的，看看会不会重复调用\n\n            // 调用\n            DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);\n            // 断言\n            assertFalse(result.getAll());\n            assertFalse(result.getSelf());\n            assertEquals(1, result.getDeptIds().size());\n            assertTrue(CollUtil.contains(result.getDeptIds(), 3L));\n        }\n    }\n\n    @Test\n    public void testGetDeptDataPermission_DeptAndChild() {\n        try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {\n            springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))\n                    .thenReturn(permissionService);\n\n            // 准备参数\n            Long userId = 1L;\n            // mock 用户的角色编号\n            userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(2L));\n            // mock 获得用户的角色\n            RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.DEPT_AND_CHILD.getScope())\n                    .setStatus(CommonStatusEnum.ENABLE.getStatus()));\n            when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(toList(roleDO));\n            // mock 部门的返回\n            when(userService.getUser(eq(1L))).thenReturn(new AdminUserDO().setDeptId(3L),\n                    null, null); // 最后返回 null 的目的，看看会不会重复调用\n            // mock 方法（部门)\n            DeptDO deptDO = randomPojo(DeptDO.class);\n            when(deptService.getChildDeptIdListFromCache(eq(3L))).thenReturn(singleton(deptDO.getId()));\n\n            // 调用\n            DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);\n            // 断言\n            assertFalse(result.getAll());\n            assertFalse(result.getSelf());\n            assertEquals(2, result.getDeptIds().size());\n            assertTrue(CollUtil.contains(result.getDeptIds(), deptDO.getId()));\n            assertTrue(CollUtil.contains(result.getDeptIds(), 3L));\n        }\n    }\n\n    @Test\n    public void testGetDeptDataPermission_Self() {\n        try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {\n            springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(PermissionServiceImpl.class)))\n                    .thenReturn(permissionService);\n\n            // 准备参数\n            Long userId = 1L;\n            // mock 用户的角色编号\n            userRoleMapper.insert(randomPojo(UserRoleDO.class).setUserId(userId).setRoleId(2L));\n            // mock 获得用户的角色\n            RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setDataScope(DataScopeEnum.SELF.getScope())\n                    .setStatus(CommonStatusEnum.ENABLE.getStatus()));\n            when(roleService.getRoleListFromCache(eq(singleton(2L)))).thenReturn(toList(roleDO));\n\n            // 调用\n            DeptDataPermissionRespDTO result = permissionService.getDeptDataPermission(userId);\n            // 断言\n            assertFalse(result.getAll());\n            assertTrue(result.getSelf());\n            assertTrue(CollUtil.isEmpty(result.getDeptIds()));\n        }\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/java/co/yixiang/yshop/module/system/service/permission/RoleServiceImplTest.java",
    "content": "package co.yixiang.yshop.module.system.service.permission;\n\nimport cn.hutool.extra.spring.SpringUtil;\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.test.core.ut.BaseDbUnitTest;\nimport co.yixiang.yshop.module.system.controller.admin.permission.vo.role.RolePageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.permission.vo.role.RoleSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.permission.RoleDO;\nimport co.yixiang.yshop.module.system.dal.mysql.permission.RoleMapper;\nimport co.yixiang.yshop.module.system.enums.permission.DataScopeEnum;\nimport co.yixiang.yshop.module.system.enums.permission.RoleTypeEnum;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.MockedStatic;\nimport org.springframework.boot.test.mock.mockito.MockBean;\nimport org.springframework.context.annotation.Import;\n\nimport jakarta.annotation.Resource;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Set;\n\nimport static cn.hutool.core.util.RandomUtil.randomEle;\nimport static co.yixiang.yshop.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;\nimport static co.yixiang.yshop.framework.common.util.date.LocalDateTimeUtils.buildTime;\nimport static co.yixiang.yshop.framework.common.util.object.ObjectUtils.cloneIgnoreId;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertPojoEquals;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertServiceException;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.*;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.*;\nimport static java.util.Collections.singleton;\nimport static java.util.Collections.singletonList;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.mockStatic;\nimport static org.mockito.Mockito.verify;\n\n@Import(RoleServiceImpl.class)\npublic class RoleServiceImplTest extends BaseDbUnitTest {\n\n    @Resource\n    private RoleServiceImpl roleService;\n\n    @Resource\n    private RoleMapper roleMapper;\n\n    @MockBean\n    private PermissionService permissionService;\n\n    @Test\n    public void testCreateRole() {\n        // 准备参数\n        RoleSaveReqVO reqVO = randomPojo(RoleSaveReqVO.class)\n                .setId(null); // 防止 id 被赋值\n\n        // 调用\n        Long roleId = roleService.createRole(reqVO, null);\n        // 断言\n        RoleDO roleDO = roleMapper.selectById(roleId);\n        assertPojoEquals(reqVO, roleDO, \"id\");\n        assertEquals(RoleTypeEnum.CUSTOM.getType(), roleDO.getType());\n        assertEquals(CommonStatusEnum.ENABLE.getStatus(), roleDO.getStatus());\n        assertEquals(DataScopeEnum.ALL.getScope(), roleDO.getDataScope());\n    }\n\n    @Test\n    public void testUpdateRole() {\n        // mock 数据\n        RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setType(RoleTypeEnum.CUSTOM.getType()));\n        roleMapper.insert(roleDO);\n        // 准备参数\n        Long id = roleDO.getId();\n        RoleSaveReqVO reqVO = randomPojo(RoleSaveReqVO.class, o -> o.setId(id));\n\n        // 调用\n        roleService.updateRole(reqVO);\n        // 断言\n        RoleDO newRoleDO = roleMapper.selectById(id);\n        assertPojoEquals(reqVO, newRoleDO);\n    }\n\n    @Test\n    public void testUpdateRoleDataScope() {\n        // mock 数据\n        RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setType(RoleTypeEnum.CUSTOM.getType()));\n        roleMapper.insert(roleDO);\n        // 准备参数\n        Long id = roleDO.getId();\n        Integer dataScope = randomEle(DataScopeEnum.values()).getScope();\n        Set<Long> dataScopeRoleIds = randomSet(Long.class);\n\n        // 调用\n        roleService.updateRoleDataScope(id, dataScope, dataScopeRoleIds);\n        // 断言\n        RoleDO dbRoleDO = roleMapper.selectById(id);\n        assertEquals(dataScope, dbRoleDO.getDataScope());\n        assertEquals(dataScopeRoleIds, dbRoleDO.getDataScopeDeptIds());\n    }\n\n    @Test\n    public void testDeleteRole() {\n        // mock 数据\n        RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setType(RoleTypeEnum.CUSTOM.getType()));\n        roleMapper.insert(roleDO);\n        // 参数准备\n        Long id = roleDO.getId();\n\n        // 调用\n        roleService.deleteRole(id);\n        // 断言\n        assertNull(roleMapper.selectById(id));\n        // verify 删除相关数据\n        verify(permissionService).processRoleDeleted(id);\n    }\n\n    @Test\n    public void testValidateRoleDuplicate_success() {\n        // 调用，不会抛异常\n        roleService.validateRoleDuplicate(randomString(), randomString(), null);\n    }\n\n    @Test\n    public void testValidateRoleDuplicate_nameDuplicate() {\n        // mock 数据\n        RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setName(\"role_name\"));\n        roleMapper.insert(roleDO);\n        // 准备参数\n        String name = \"role_name\";\n\n        // 调用，并断言异常\n        assertServiceException(() -> roleService.validateRoleDuplicate(name, randomString(), null),\n                ROLE_NAME_DUPLICATE, name);\n    }\n\n    @Test\n    public void testValidateRoleDuplicate_codeDuplicate() {\n        // mock 数据\n        RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setCode(\"code\"));\n        roleMapper.insert(roleDO);\n        // 准备参数\n        String code = \"code\";\n\n        // 调用，并断言异常\n        assertServiceException(() -> roleService.validateRoleDuplicate(randomString(), code, null),\n                ROLE_CODE_DUPLICATE, code);\n    }\n\n    @Test\n    public void testValidateUpdateRole_success() {\n        RoleDO roleDO = randomPojo(RoleDO.class);\n        roleMapper.insert(roleDO);\n        // 准备参数\n        Long id = roleDO.getId();\n\n        // 调用，无异常\n        roleService.validateRoleForUpdate(id);\n    }\n\n    @Test\n    public void testValidateUpdateRole_roleIdNotExist() {\n        assertServiceException(() -> roleService.validateRoleForUpdate(randomLongId()), ROLE_NOT_EXISTS);\n    }\n\n    @Test\n    public void testValidateUpdateRole_systemRoleCanNotBeUpdate() {\n        RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setType(RoleTypeEnum.SYSTEM.getType()));\n        roleMapper.insert(roleDO);\n        // 准备参数\n        Long id = roleDO.getId();\n\n        assertServiceException(() -> roleService.validateRoleForUpdate(id),\n                ROLE_CAN_NOT_UPDATE_SYSTEM_TYPE_ROLE);\n    }\n\n    @Test\n    public void testGetRole() {\n        // mock 数据\n        RoleDO roleDO = randomPojo(RoleDO.class);\n        roleMapper.insert(roleDO);\n        // 参数准备\n        Long id = roleDO.getId();\n\n        // 调用\n        RoleDO dbRoleDO = roleService.getRole(id);\n        // 断言\n        assertPojoEquals(roleDO, dbRoleDO);\n    }\n\n    @Test\n    public void testGetRoleFromCache() {\n        // mock 数据（缓存）\n        RoleDO roleDO = randomPojo(RoleDO.class);\n        roleMapper.insert(roleDO);\n        // 参数准备\n        Long id = roleDO.getId();\n\n        // 调用\n        RoleDO dbRoleDO = roleService.getRoleFromCache(id);\n        // 断言\n        assertPojoEquals(roleDO, dbRoleDO);\n    }\n\n    @Test\n    public void testGetRoleListByStatus() {\n        // mock 数据\n        RoleDO dbRole01 = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));\n        roleMapper.insert(dbRole01);\n        RoleDO dbRole02 = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()));\n        roleMapper.insert(dbRole02);\n\n        // 调用\n        List<RoleDO> list = roleService.getRoleListByStatus(\n                singleton(CommonStatusEnum.ENABLE.getStatus()));\n        // 断言\n        assertEquals(1, list.size());\n        assertPojoEquals(dbRole01, list.get(0));\n    }\n\n    @Test\n    public void testGetRoleList() {\n        // mock 数据\n        RoleDO dbRole01 = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));\n        roleMapper.insert(dbRole01);\n        RoleDO dbRole02 = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()));\n        roleMapper.insert(dbRole02);\n\n        // 调用\n        List<RoleDO> list = roleService.getRoleList();\n        // 断言\n        assertEquals(2, list.size());\n        assertPojoEquals(dbRole01, list.get(0));\n        assertPojoEquals(dbRole02, list.get(1));\n    }\n\n    @Test\n    public void testGetRoleList_ids() {\n        // mock 数据\n        RoleDO dbRole01 = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));\n        roleMapper.insert(dbRole01);\n        RoleDO dbRole02 = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()));\n        roleMapper.insert(dbRole02);\n        // 准备参数\n        Collection<Long> ids = singleton(dbRole01.getId());\n\n        // 调用\n        List<RoleDO> list = roleService.getRoleList(ids);\n        // 断言\n        assertEquals(1, list.size());\n        assertPojoEquals(dbRole01, list.get(0));\n    }\n\n    @Test\n    public void testGetRoleListFromCache() {\n        try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {\n            springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(RoleServiceImpl.class)))\n                    .thenReturn(roleService);\n\n            // mock 数据\n            RoleDO dbRole = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));\n            roleMapper.insert(dbRole);\n            // 测试 id 不匹配\n            roleMapper.insert(cloneIgnoreId(dbRole, o -> {}));\n            // 准备参数\n            Collection<Long> ids = singleton(dbRole.getId());\n\n            // 调用\n            List<RoleDO> list = roleService.getRoleListFromCache(ids);\n            // 断言\n            assertEquals(1, list.size());\n            assertPojoEquals(dbRole, list.get(0));\n        }\n    }\n\n    @Test\n    public void testGetRolePage() {\n        // mock 数据\n        RoleDO dbRole = randomPojo(RoleDO.class, o -> { // 等会查询到\n            o.setName(\"土豆\");\n            o.setCode(\"tudou\");\n            o.setStatus(CommonStatusEnum.ENABLE.getStatus());\n            o.setCreateTime(buildTime(2022, 2, 8));\n        });\n        roleMapper.insert(dbRole);\n        // 测试 name 不匹配\n        roleMapper.insert(cloneIgnoreId(dbRole, o -> o.setName(\"红薯\")));\n        // 测试 code 不匹配\n        roleMapper.insert(cloneIgnoreId(dbRole, o -> o.setCode(\"hong\")));\n        // 测试 createTime 不匹配\n        roleMapper.insert(cloneIgnoreId(dbRole, o -> o.setCreateTime(buildTime(2022, 2, 16))));\n        // 准备参数\n        RolePageReqVO reqVO = new RolePageReqVO();\n        reqVO.setName(\"土豆\");\n        reqVO.setCode(\"tu\");\n        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());\n        reqVO.setCreateTime(buildBetweenTime(2022, 2, 1, 2022, 2, 12));\n\n        // 调用\n        PageResult<RoleDO> pageResult = roleService.getRolePage(reqVO);\n        // 断言\n        assertEquals(1, pageResult.getTotal());\n        assertEquals(1, pageResult.getList().size());\n        assertPojoEquals(dbRole, pageResult.getList().get(0));\n    }\n\n    @Test\n    public void testHasAnySuperAdmin_true() {\n        try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {\n            springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(RoleServiceImpl.class)))\n                    .thenReturn(roleService);\n\n            // mock 数据\n            RoleDO dbRole = randomPojo(RoleDO.class).setCode(\"super_admin\");\n            roleMapper.insert(dbRole);\n            // 准备参数\n            Long id = dbRole.getId();\n\n            // 调用，并调用\n            assertTrue(roleService.hasAnySuperAdmin(singletonList(id)));\n        }\n    }\n\n    @Test\n    public void testHasAnySuperAdmin_false() {\n        try (MockedStatic<SpringUtil> springUtilMockedStatic = mockStatic(SpringUtil.class)) {\n            springUtilMockedStatic.when(() -> SpringUtil.getBean(eq(RoleServiceImpl.class)))\n                    .thenReturn(roleService);\n\n            // mock 数据\n            RoleDO dbRole = randomPojo(RoleDO.class).setCode(\"tenant_admin\");\n            roleMapper.insert(dbRole);\n            // 准备参数\n            Long id = dbRole.getId();\n\n            // 调用，并调用\n            assertFalse(roleService.hasAnySuperAdmin(singletonList(id)));\n        }\n    }\n\n    @Test\n    public void testValidateRoleList_success() {\n        // mock 数据\n        RoleDO roleDO = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));\n        roleMapper.insert(roleDO);\n        // 准备参数\n        List<Long> ids = singletonList(roleDO.getId());\n\n        // 调用，无需断言\n        roleService.validateRoleList(ids);\n    }\n\n    @Test\n    public void testValidateRoleList_notFound() {\n        // 准备参数\n        List<Long> ids = singletonList(randomLongId());\n\n        // 调用, 并断言异常\n        assertServiceException(() -> roleService.validateRoleList(ids), ROLE_NOT_EXISTS);\n    }\n\n    @Test\n    public void testValidateRoleList_notEnable() {\n        // mock 数据\n        RoleDO RoleDO = randomPojo(RoleDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()));\n        roleMapper.insert(RoleDO);\n        // 准备参数\n        List<Long> ids = singletonList(RoleDO.getId());\n\n        // 调用, 并断言异常\n        assertServiceException(() -> roleService.validateRoleList(ids), ROLE_IS_DISABLE, RoleDO.getName());\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/java/co/yixiang/yshop/module/system/service/sms/SmsChannelServiceTest.java",
    "content": "package co.yixiang.yshop.module.system.service.sms;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.object.BeanUtils;\nimport co.yixiang.yshop.module.system.framework.sms.core.client.SmsClient;\nimport co.yixiang.yshop.module.system.framework.sms.core.client.SmsClientFactory;\nimport co.yixiang.yshop.module.system.framework.sms.core.property.SmsChannelProperties;\nimport co.yixiang.yshop.framework.test.core.ut.BaseDbUnitTest;\nimport co.yixiang.yshop.module.system.controller.admin.sms.vo.channel.SmsChannelPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.sms.vo.channel.SmsChannelSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.sms.SmsChannelDO;\nimport co.yixiang.yshop.module.system.dal.mysql.sms.SmsChannelMapper;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.mock.mockito.MockBean;\nimport org.springframework.context.annotation.Import;\n\nimport jakarta.annotation.Resource;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;\nimport static co.yixiang.yshop.framework.common.util.date.LocalDateTimeUtils.buildTime;\nimport static co.yixiang.yshop.framework.common.util.object.ObjectUtils.cloneIgnoreId;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertPojoEquals;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertServiceException;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.*;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_HAS_CHILDREN;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.SMS_CHANNEL_NOT_EXISTS;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.*;\n\n@Import(SmsChannelServiceImpl.class)\npublic class SmsChannelServiceTest extends BaseDbUnitTest {\n\n    @Resource\n    private SmsChannelServiceImpl smsChannelService;\n\n    @Resource\n    private SmsChannelMapper smsChannelMapper;\n\n    @MockBean\n    private SmsClientFactory smsClientFactory;\n    @MockBean\n    private SmsTemplateService smsTemplateService;\n\n    @Test\n    public void testCreateSmsChannel_success() {\n        // 准备参数\n        SmsChannelSaveReqVO reqVO = randomPojo(SmsChannelSaveReqVO.class, o -> o.setStatus(randomCommonStatus()))\n                .setId(null); // 防止 id 被赋值\n\n        // 调用\n        Long smsChannelId = smsChannelService.createSmsChannel(reqVO);\n        // 断言\n        assertNotNull(smsChannelId);\n        // 校验记录的属性是否正确\n        SmsChannelDO smsChannel = smsChannelMapper.selectById(smsChannelId);\n        assertPojoEquals(reqVO, smsChannel, \"id\");\n        // 断言 cache\n        assertNull(smsChannelService.getIdClientCache().getIfPresent(smsChannel.getId()));\n        assertNull(smsChannelService.getCodeClientCache().getIfPresent(smsChannel.getCode()));\n    }\n\n    @Test\n    public void testUpdateSmsChannel_success() {\n        // mock 数据\n        SmsChannelDO dbSmsChannel = randomPojo(SmsChannelDO.class);\n        smsChannelMapper.insert(dbSmsChannel);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        SmsChannelSaveReqVO reqVO = randomPojo(SmsChannelSaveReqVO.class, o -> {\n            o.setId(dbSmsChannel.getId()); // 设置更新的 ID\n            o.setStatus(randomCommonStatus());\n            o.setCallbackUrl(randomString());\n        });\n\n        // 调用\n        smsChannelService.updateSmsChannel(reqVO);\n        // 校验是否更新正确\n        SmsChannelDO smsChannel = smsChannelMapper.selectById(reqVO.getId()); // 获取最新的\n        assertPojoEquals(reqVO, smsChannel);\n        // 断言 cache\n        assertNull(smsChannelService.getIdClientCache().getIfPresent(smsChannel.getId()));\n        assertNull(smsChannelService.getCodeClientCache().getIfPresent(smsChannel.getCode()));\n    }\n\n    @Test\n    public void testUpdateSmsChannel_notExists() {\n        // 准备参数\n        SmsChannelSaveReqVO reqVO = randomPojo(SmsChannelSaveReqVO.class);\n\n        // 调用, 并断言异常\n        assertServiceException(() -> smsChannelService.updateSmsChannel(reqVO), SMS_CHANNEL_NOT_EXISTS);\n    }\n\n    @Test\n    public void testDeleteSmsChannel_success() {\n        // mock 数据\n        SmsChannelDO dbSmsChannel = randomPojo(SmsChannelDO.class);\n        smsChannelMapper.insert(dbSmsChannel);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        Long id = dbSmsChannel.getId();\n\n        // 调用\n        smsChannelService.deleteSmsChannel(id);\n        // 校验数据不存在了\n        assertNull(smsChannelMapper.selectById(id));\n        // 断言 cache\n        assertNull(smsChannelService.getIdClientCache().getIfPresent(dbSmsChannel.getId()));\n        assertNull(smsChannelService.getCodeClientCache().getIfPresent(dbSmsChannel.getCode()));\n    }\n\n    @Test\n    public void testDeleteSmsChannel_notExists() {\n        // 准备参数\n        Long id = randomLongId();\n\n        // 调用, 并断言异常\n        assertServiceException(() -> smsChannelService.deleteSmsChannel(id), SMS_CHANNEL_NOT_EXISTS);\n    }\n\n    @Test\n    public void testDeleteSmsChannel_hasChildren() {\n        // mock 数据\n        SmsChannelDO dbSmsChannel = randomPojo(SmsChannelDO.class);\n        smsChannelMapper.insert(dbSmsChannel);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        Long id = dbSmsChannel.getId();\n        // mock 方法\n        when(smsTemplateService.getSmsTemplateCountByChannelId(eq(id))).thenReturn(10L);\n\n        // 调用, 并断言异常\n        assertServiceException(() -> smsChannelService.deleteSmsChannel(id), SMS_CHANNEL_HAS_CHILDREN);\n    }\n\n    @Test\n    public void testGetSmsChannel() {\n        // mock 数据\n        SmsChannelDO dbSmsChannel = randomPojo(SmsChannelDO.class);\n        smsChannelMapper.insert(dbSmsChannel); // @Sql: 先插入出一条存在的数据\n        // 准备参数\n        Long id = dbSmsChannel.getId();\n\n        // 调用，并断言\n        assertPojoEquals(dbSmsChannel, smsChannelService.getSmsChannel(id));\n    }\n\n    @Test\n    public void testGetSmsChannelList() {\n        // mock 数据\n        SmsChannelDO dbSmsChannel01 = randomPojo(SmsChannelDO.class);\n        smsChannelMapper.insert(dbSmsChannel01);\n        SmsChannelDO dbSmsChannel02 = randomPojo(SmsChannelDO.class);\n        smsChannelMapper.insert(dbSmsChannel02);\n        // 准备参数\n\n        // 调用\n        List<SmsChannelDO> list = smsChannelService.getSmsChannelList();\n        // 断言\n        assertEquals(2, list.size());\n        assertPojoEquals(dbSmsChannel01, list.get(0));\n        assertPojoEquals(dbSmsChannel02, list.get(1));\n    }\n\n    @Test\n    public void testGetSmsChannelPage() {\n       // mock 数据\n       SmsChannelDO dbSmsChannel = randomPojo(SmsChannelDO.class, o -> { // 等会查询到\n           o.setSignature(\"yshop\");\n           o.setStatus(CommonStatusEnum.ENABLE.getStatus());\n           o.setCreateTime(buildTime(2020, 12, 12));\n       });\n       smsChannelMapper.insert(dbSmsChannel);\n       // 测试 signature 不匹配\n       smsChannelMapper.insert(cloneIgnoreId(dbSmsChannel, o -> o.setSignature(\"源码\")));\n       // 测试 status 不匹配\n       smsChannelMapper.insert(cloneIgnoreId(dbSmsChannel, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));\n       // 测试 createTime 不匹配\n       smsChannelMapper.insert(cloneIgnoreId(dbSmsChannel, o -> o.setCreateTime(buildTime(2020, 11, 11))));\n       // 准备参数\n       SmsChannelPageReqVO reqVO = new SmsChannelPageReqVO();\n       reqVO.setSignature(\"yshop\");\n       reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());\n       reqVO.setCreateTime(buildBetweenTime(2020, 12, 1, 2020, 12, 24));\n\n       // 调用\n       PageResult<SmsChannelDO> pageResult = smsChannelService.getSmsChannelPage(reqVO);\n       // 断言\n       assertEquals(1, pageResult.getTotal());\n       assertEquals(1, pageResult.getList().size());\n       assertPojoEquals(dbSmsChannel, pageResult.getList().get(0));\n    }\n\n    @Test\n    public void testGetSmsClient_id() {\n        // mock 数据\n        SmsChannelDO channel = randomPojo(SmsChannelDO.class);\n        smsChannelMapper.insert(channel);\n        // mock 参数\n        Long id = channel.getId();\n        // mock 方法\n        SmsClient mockClient = mock(SmsClient.class);\n        when(smsClientFactory.getSmsClient(eq(id))).thenReturn(mockClient);\n\n        // 调用\n        SmsClient client = smsChannelService.getSmsClient(id);\n        // 断言\n        assertSame(client, mockClient);\n        verify(smsClientFactory).createOrUpdateSmsClient(argThat(arg -> {\n            SmsChannelProperties properties = BeanUtils.toBean(channel, SmsChannelProperties.class);\n            return properties.equals(arg);\n        }));\n    }\n\n    @Test\n    public void testGetSmsClient_code() {\n        // mock 数据\n        SmsChannelDO channel = randomPojo(SmsChannelDO.class);\n        smsChannelMapper.insert(channel);\n        // mock 参数\n        String code = channel.getCode();\n        // mock 方法\n        SmsClient mockClient = mock(SmsClient.class);\n        when(smsClientFactory.getSmsClient(eq(code))).thenReturn(mockClient);\n\n        // 调用\n        SmsClient client = smsChannelService.getSmsClient(code);\n        // 断言\n        assertSame(client, mockClient);\n        verify(smsClientFactory).createOrUpdateSmsClient(argThat(arg -> {\n            SmsChannelProperties properties = BeanUtils.toBean(channel, SmsChannelProperties.class);\n            return properties.equals(arg);\n        }));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/java/co/yixiang/yshop/module/system/service/sms/SmsCodeServiceImplTest.java",
    "content": "package co.yixiang.yshop.module.system.service.sms;\n\nimport cn.hutool.core.map.MapUtil;\nimport co.yixiang.yshop.framework.mybatis.core.enums.SqlConstants;\nimport co.yixiang.yshop.framework.test.core.ut.BaseDbUnitTest;\nimport co.yixiang.yshop.module.system.api.sms.dto.code.SmsCodeSendReqDTO;\nimport co.yixiang.yshop.module.system.api.sms.dto.code.SmsCodeUseReqDTO;\nimport co.yixiang.yshop.module.system.api.sms.dto.code.SmsCodeValidateReqDTO;\nimport co.yixiang.yshop.module.system.dal.dataobject.sms.SmsCodeDO;\nimport co.yixiang.yshop.module.system.dal.mysql.sms.SmsCodeMapper;\nimport co.yixiang.yshop.module.system.enums.sms.SmsSceneEnum;\nimport co.yixiang.yshop.module.system.framework.sms.config.SmsCodeProperties;\nimport com.baomidou.mybatisplus.annotation.DbType;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.mock.mockito.MockBean;\nimport org.springframework.context.annotation.Import;\n\nimport jakarta.annotation.Resource;\nimport java.time.Duration;\nimport java.time.LocalDateTime;\n\nimport static cn.hutool.core.util.RandomUtil.randomEle;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertPojoEquals;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertServiceException;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.randomPojo;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.*;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.ArgumentMatchers.isNull;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\n@Import(SmsCodeServiceImpl.class)\npublic class SmsCodeServiceImplTest extends BaseDbUnitTest {\n\n    @Resource\n    private SmsCodeServiceImpl smsCodeService;\n\n    @Resource\n    private SmsCodeMapper smsCodeMapper;\n\n    @MockBean\n    private SmsCodeProperties smsCodeProperties;\n    @MockBean\n    private SmsSendService smsSendService;\n\n    @BeforeEach\n    public void setUp() {\n        when(smsCodeProperties.getExpireTimes()).thenReturn(Duration.ofMinutes(5));\n        when(smsCodeProperties.getSendFrequency()).thenReturn(Duration.ofMinutes(1));\n        when(smsCodeProperties.getSendMaximumQuantityPerDay()).thenReturn(10);\n        when(smsCodeProperties.getBeginCode()).thenReturn(9999);\n        when(smsCodeProperties.getEndCode()).thenReturn(9999);\n    }\n\n    @Test\n    public void sendSmsCode_success() {\n        // 准备参数\n        SmsCodeSendReqDTO reqDTO = randomPojo(SmsCodeSendReqDTO.class, o -> {\n            o.setMobile(\"15601691300\");\n            o.setScene(SmsSceneEnum.MEMBER_LOGIN.getScene());\n        });\n        // mock 方法\n        SqlConstants.init(DbType.MYSQL);\n\n        // 调用\n        smsCodeService.sendSmsCode(reqDTO);\n        // 断言 code 验证码\n        SmsCodeDO smsCodeDO = smsCodeMapper.selectOne(null);\n        assertPojoEquals(reqDTO, smsCodeDO);\n        assertEquals(\"9999\", smsCodeDO.getCode());\n        assertEquals(1, smsCodeDO.getTodayIndex());\n        assertFalse(smsCodeDO.getUsed());\n        // 断言调用\n        verify(smsSendService).sendSingleSms(eq(reqDTO.getMobile()), isNull(), isNull(),\n                eq(\"user-sms-login\"), eq(MapUtil.of(\"code\", \"9999\")));\n    }\n\n    @Test\n    public void sendSmsCode_tooFast() {\n        // mock 数据\n        SmsCodeDO smsCodeDO = randomPojo(SmsCodeDO.class,\n                o -> o.setMobile(\"15601691300\").setTodayIndex(1));\n        smsCodeMapper.insert(smsCodeDO);\n        // 准备参数\n        SmsCodeSendReqDTO reqDTO = randomPojo(SmsCodeSendReqDTO.class, o -> {\n            o.setMobile(\"15601691300\");\n            o.setScene(SmsSceneEnum.MEMBER_LOGIN.getScene());\n        });\n        // mock 方法\n        SqlConstants.init(DbType.MYSQL);\n\n        // 调用，并断言异常\n        assertServiceException(() -> smsCodeService.sendSmsCode(reqDTO),\n                SMS_CODE_SEND_TOO_FAST);\n    }\n\n    @Test\n    public void sendSmsCode_exceedDay() {\n        // mock 数据\n        SmsCodeDO smsCodeDO = randomPojo(SmsCodeDO.class,\n                o -> o.setMobile(\"15601691300\").setTodayIndex(10).setCreateTime(LocalDateTime.now()));\n        smsCodeMapper.insert(smsCodeDO);\n        // 准备参数\n        SmsCodeSendReqDTO reqDTO = randomPojo(SmsCodeSendReqDTO.class, o -> {\n            o.setMobile(\"15601691300\");\n            o.setScene(SmsSceneEnum.MEMBER_LOGIN.getScene());\n        });\n        // mock 方法\n        SqlConstants.init(DbType.MYSQL);\n        when(smsCodeProperties.getSendFrequency()).thenReturn(Duration.ofMillis(0));\n\n        // 调用，并断言异常\n        assertServiceException(() -> smsCodeService.sendSmsCode(reqDTO),\n                SMS_CODE_EXCEED_SEND_MAXIMUM_QUANTITY_PER_DAY);\n    }\n\n    @Test\n    public void testUseSmsCode_success() {\n        // 准备参数\n        SmsCodeUseReqDTO reqDTO = randomPojo(SmsCodeUseReqDTO.class, o -> {\n            o.setMobile(\"15601691300\");\n            o.setScene(randomEle(SmsSceneEnum.values()).getScene());\n        });\n        // mock 数据\n        SqlConstants.init(DbType.MYSQL);\n        smsCodeMapper.insert(randomPojo(SmsCodeDO.class, o -> {\n            o.setMobile(reqDTO.getMobile()).setScene(reqDTO.getScene())\n                    .setCode(reqDTO.getCode()).setUsed(false);\n        }));\n\n        // 调用\n        smsCodeService.useSmsCode(reqDTO);\n        // 断言\n        SmsCodeDO smsCodeDO = smsCodeMapper.selectOne(null);\n        assertTrue(smsCodeDO.getUsed());\n        assertNotNull(smsCodeDO.getUsedTime());\n        assertEquals(reqDTO.getUsedIp(), smsCodeDO.getUsedIp());\n    }\n\n    @Test\n    public void validateSmsCode_success() {\n        // 准备参数\n        SmsCodeValidateReqDTO reqDTO = randomPojo(SmsCodeValidateReqDTO.class, o -> {\n            o.setMobile(\"15601691300\");\n            o.setScene(randomEle(SmsSceneEnum.values()).getScene());\n        });\n        // mock 数据\n        SqlConstants.init(DbType.MYSQL);\n        smsCodeMapper.insert(randomPojo(SmsCodeDO.class, o -> o.setMobile(reqDTO.getMobile())\n                .setScene(reqDTO.getScene()).setCode(reqDTO.getCode()).setUsed(false)));\n\n        // 调用\n        smsCodeService.validateSmsCode(reqDTO);\n    }\n\n    @Test\n    public void validateSmsCode_notFound() {\n        // 准备参数\n        SmsCodeValidateReqDTO reqDTO = randomPojo(SmsCodeValidateReqDTO.class, o -> {\n            o.setMobile(\"15601691300\");\n            o.setScene(randomEle(SmsSceneEnum.values()).getScene());\n        });\n        // mock 数据\n        SqlConstants.init(DbType.MYSQL);\n\n        // 调用，并断言异常\n        assertServiceException(() -> smsCodeService.validateSmsCode(reqDTO),\n                SMS_CODE_NOT_FOUND);\n    }\n\n    @Test\n    public void validateSmsCode_expired() {\n        // 准备参数\n        SmsCodeValidateReqDTO reqDTO = randomPojo(SmsCodeValidateReqDTO.class, o -> {\n            o.setMobile(\"15601691300\");\n            o.setScene(randomEle(SmsSceneEnum.values()).getScene());\n        });\n        // mock 数据\n        SqlConstants.init(DbType.MYSQL);\n        smsCodeMapper.insert(randomPojo(SmsCodeDO.class, o -> o.setMobile(reqDTO.getMobile())\n                .setScene(reqDTO.getScene()).setCode(reqDTO.getCode()).setUsed(false)\n                .setCreateTime(LocalDateTime.now().minusMinutes(6))));\n\n        // 调用，并断言异常\n        assertServiceException(() -> smsCodeService.validateSmsCode(reqDTO),\n                SMS_CODE_EXPIRED);\n    }\n\n    @Test\n    public void validateSmsCode_used() {\n        // 准备参数\n        SmsCodeValidateReqDTO reqDTO = randomPojo(SmsCodeValidateReqDTO.class, o -> {\n            o.setMobile(\"15601691300\");\n            o.setScene(randomEle(SmsSceneEnum.values()).getScene());\n        });\n        // mock 数据\n        SqlConstants.init(DbType.MYSQL);\n        smsCodeMapper.insert(randomPojo(SmsCodeDO.class, o -> o.setMobile(reqDTO.getMobile())\n                .setScene(reqDTO.getScene()).setCode(reqDTO.getCode()).setUsed(true)\n                .setCreateTime(LocalDateTime.now())));\n\n        // 调用，并断言异常\n        assertServiceException(() -> smsCodeService.validateSmsCode(reqDTO),\n                SMS_CODE_USED);\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/java/co/yixiang/yshop/module/system/service/sms/SmsLogServiceImplTest.java",
    "content": "package co.yixiang.yshop.module.system.service.sms;\n\nimport cn.hutool.core.map.MapUtil;\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.collection.ArrayUtils;\nimport co.yixiang.yshop.framework.test.core.ut.BaseDbUnitTest;\nimport co.yixiang.yshop.module.system.controller.admin.sms.vo.log.SmsLogPageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.sms.SmsLogDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.sms.SmsTemplateDO;\nimport co.yixiang.yshop.module.system.dal.mysql.sms.SmsLogMapper;\nimport co.yixiang.yshop.module.system.enums.sms.SmsReceiveStatusEnum;\nimport co.yixiang.yshop.module.system.enums.sms.SmsSendStatusEnum;\nimport co.yixiang.yshop.module.system.enums.sms.SmsTemplateTypeEnum;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.context.annotation.Import;\n\nimport jakarta.annotation.Resource;\nimport java.time.LocalDateTime;\nimport java.util.Map;\nimport java.util.function.Consumer;\n\nimport static cn.hutool.core.util.RandomUtil.randomBoolean;\nimport static cn.hutool.core.util.RandomUtil.randomEle;\nimport static co.yixiang.yshop.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;\nimport static co.yixiang.yshop.framework.common.util.date.LocalDateTimeUtils.buildTime;\nimport static co.yixiang.yshop.framework.common.util.object.ObjectUtils.cloneIgnoreId;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertPojoEquals;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.*;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.junit.jupiter.api.Assertions.assertNotNull;\n\n@Import(SmsLogServiceImpl.class)\npublic class SmsLogServiceImplTest extends BaseDbUnitTest {\n\n    @Resource\n    private SmsLogServiceImpl smsLogService;\n\n    @Resource\n    private SmsLogMapper smsLogMapper;\n\n    @Test\n    public void testGetSmsLogPage() {\n       // mock 数据\n       SmsLogDO dbSmsLog = randomSmsLogDO(o -> { // 等会查询到\n           o.setChannelId(1L);\n           o.setTemplateId(10L);\n           o.setMobile(\"15601691300\");\n           o.setSendStatus(SmsSendStatusEnum.INIT.getStatus());\n           o.setSendTime(buildTime(2020, 11, 11));\n           o.setReceiveStatus(SmsReceiveStatusEnum.INIT.getStatus());\n           o.setReceiveTime(buildTime(2021, 11, 11));\n       });\n       smsLogMapper.insert(dbSmsLog);\n       // 测试 channelId 不匹配\n       smsLogMapper.insert(cloneIgnoreId(dbSmsLog, o -> o.setChannelId(2L)));\n       // 测试 templateId 不匹配\n       smsLogMapper.insert(cloneIgnoreId(dbSmsLog, o -> o.setTemplateId(20L)));\n       // 测试 mobile 不匹配\n       smsLogMapper.insert(cloneIgnoreId(dbSmsLog, o -> o.setMobile(\"18818260999\")));\n       // 测试 sendStatus 不匹配\n       smsLogMapper.insert(cloneIgnoreId(dbSmsLog, o -> o.setSendStatus(SmsSendStatusEnum.IGNORE.getStatus())));\n       // 测试 sendTime 不匹配\n       smsLogMapper.insert(cloneIgnoreId(dbSmsLog, o -> o.setSendTime(buildTime(2020, 12, 12))));\n       // 测试 receiveStatus 不匹配\n       smsLogMapper.insert(cloneIgnoreId(dbSmsLog, o -> o.setReceiveStatus(SmsReceiveStatusEnum.SUCCESS.getStatus())));\n       // 测试 receiveTime 不匹配\n       smsLogMapper.insert(cloneIgnoreId(dbSmsLog, o -> o.setReceiveTime(buildTime(2021, 12, 12))));\n       // 准备参数\n       SmsLogPageReqVO reqVO = new SmsLogPageReqVO();\n       reqVO.setChannelId(1L);\n       reqVO.setTemplateId(10L);\n       reqVO.setMobile(\"156\");\n       reqVO.setSendStatus(SmsSendStatusEnum.INIT.getStatus());\n       reqVO.setSendTime(buildBetweenTime(2020, 11, 1, 2020, 11, 30));\n       reqVO.setReceiveStatus(SmsReceiveStatusEnum.INIT.getStatus());\n       reqVO.setReceiveTime(buildBetweenTime(2021, 11, 1, 2021, 11, 30));\n\n       // 调用\n       PageResult<SmsLogDO> pageResult = smsLogService.getSmsLogPage(reqVO);\n       // 断言\n       assertEquals(1, pageResult.getTotal());\n       assertEquals(1, pageResult.getList().size());\n       assertPojoEquals(dbSmsLog, pageResult.getList().get(0));\n    }\n\n    @Test\n    public void testCreateSmsLog() {\n        // 准备参数\n        String mobile = randomString();\n        Long userId = randomLongId();\n        Integer userType = randomEle(UserTypeEnum.values()).getValue();\n        Boolean isSend = randomBoolean();\n        SmsTemplateDO templateDO = randomPojo(SmsTemplateDO.class,\n                o -> o.setType(randomEle(SmsTemplateTypeEnum.values()).getType()));\n        String templateContent = randomString();\n        Map<String, Object> templateParams = randomTemplateParams();\n        // mock 方法\n\n        // 调用\n        Long logId = smsLogService.createSmsLog(mobile, userId, userType, isSend,\n                templateDO, templateContent, templateParams);\n        // 断言\n        SmsLogDO logDO = smsLogMapper.selectById(logId);\n        assertEquals(isSend ? SmsSendStatusEnum.INIT.getStatus() : SmsSendStatusEnum.IGNORE.getStatus(),\n                logDO.getSendStatus());\n        assertEquals(mobile, logDO.getMobile());\n        assertEquals(userType, logDO.getUserType());\n        assertEquals(userId, logDO.getUserId());\n        assertEquals(templateDO.getId(), logDO.getTemplateId());\n        assertEquals(templateDO.getCode(), logDO.getTemplateCode());\n        assertEquals(templateDO.getType(), logDO.getTemplateType());\n        assertEquals(templateDO.getChannelId(), logDO.getChannelId());\n        assertEquals(templateDO.getChannelCode(), logDO.getChannelCode());\n        assertEquals(templateContent, logDO.getTemplateContent());\n        assertEquals(templateParams, logDO.getTemplateParams());\n        assertEquals(SmsReceiveStatusEnum.INIT.getStatus(), logDO.getReceiveStatus());\n    }\n\n    @Test\n    public void testUpdateSmsSendResult() {\n        // mock 数据\n        SmsLogDO dbSmsLog = randomSmsLogDO(\n                o -> o.setSendStatus(SmsSendStatusEnum.IGNORE.getStatus()));\n        smsLogMapper.insert(dbSmsLog);\n        // 准备参数\n        Long id = dbSmsLog.getId();\n        Boolean success = randomBoolean();\n        String apiSendCode = randomString();\n        String apiSendMsg = randomString();\n        String apiRequestId = randomString();\n        String apiSerialNo = randomString();\n\n        // 调用\n        smsLogService.updateSmsSendResult(id, success,\n                apiSendCode, apiSendMsg, apiRequestId, apiSerialNo);\n        // 断言\n        dbSmsLog = smsLogMapper.selectById(id);\n        assertEquals(success ? SmsSendStatusEnum.SUCCESS.getStatus() : SmsSendStatusEnum.FAILURE.getStatus(),\n                dbSmsLog.getSendStatus());\n        assertNotNull(dbSmsLog.getSendTime());\n        assertEquals(apiSendCode, dbSmsLog.getApiSendCode());\n        assertEquals(apiSendMsg, dbSmsLog.getApiSendMsg());\n        assertEquals(apiRequestId, dbSmsLog.getApiRequestId());\n        assertEquals(apiSerialNo, dbSmsLog.getApiSerialNo());\n    }\n\n    @Test\n    public void testUpdateSmsReceiveResult() {\n        // mock 数据\n        SmsLogDO dbSmsLog = randomSmsLogDO(\n                o -> o.setReceiveStatus(SmsReceiveStatusEnum.INIT.getStatus()));\n        smsLogMapper.insert(dbSmsLog);\n        // 准备参数\n        Long id = dbSmsLog.getId();\n        Boolean success = randomBoolean();\n        LocalDateTime receiveTime = randomLocalDateTime();\n        String apiReceiveCode = randomString();\n        String apiReceiveMsg = randomString();\n\n        // 调用\n        smsLogService.updateSmsReceiveResult(id, success, receiveTime, apiReceiveCode, apiReceiveMsg);\n        // 断言\n        dbSmsLog = smsLogMapper.selectById(id);\n        assertEquals(success ? SmsReceiveStatusEnum.SUCCESS.getStatus()\n                : SmsReceiveStatusEnum.FAILURE.getStatus(), dbSmsLog.getReceiveStatus());\n        assertEquals(receiveTime, dbSmsLog.getReceiveTime());\n        assertEquals(apiReceiveCode, dbSmsLog.getApiReceiveCode());\n        assertEquals(apiReceiveMsg, dbSmsLog.getApiReceiveMsg());\n    }\n\n    // ========== 随机对象 ==========\n\n    @SafeVarargs\n    private static SmsLogDO randomSmsLogDO(Consumer<SmsLogDO>... consumers) {\n        Consumer<SmsLogDO> consumer = (o) -> {\n            o.setTemplateParams(randomTemplateParams());\n            o.setTemplateType(randomEle(SmsTemplateTypeEnum.values()).getType()); // 保证 templateType 的范围\n            o.setUserType(randomEle(UserTypeEnum.values()).getValue()); // 保证 userType 的范围\n            o.setSendStatus(randomEle(SmsSendStatusEnum.values()).getStatus()); // 保证 sendStatus 的范围\n            o.setReceiveStatus(randomEle(SmsReceiveStatusEnum.values()).getStatus()); // 保证 receiveStatus 的范围\n        };\n        return randomPojo(SmsLogDO.class, ArrayUtils.append(consumer, consumers));\n    }\n\n    private static Map<String, Object> randomTemplateParams() {\n        return MapUtil.<String, Object>builder().put(randomString(), randomString())\n                .put(randomString(), randomString()).build();\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/java/co/yixiang/yshop/module/system/service/sms/SmsSendServiceImplTest.java",
    "content": "package co.yixiang.yshop.module.system.service.sms;\n\nimport cn.hutool.core.map.MapUtil;\nimport co.yixiang.yshop.framework.common.core.KeyValue;\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.module.system.framework.sms.core.client.SmsClient;\nimport co.yixiang.yshop.module.system.framework.sms.core.client.dto.SmsReceiveRespDTO;\nimport co.yixiang.yshop.module.system.framework.sms.core.client.dto.SmsSendRespDTO;\nimport co.yixiang.yshop.framework.test.core.ut.BaseMockitoUnitTest;\nimport co.yixiang.yshop.module.system.dal.dataobject.sms.SmsChannelDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.sms.SmsTemplateDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.user.AdminUserDO;\nimport co.yixiang.yshop.module.system.mq.message.sms.SmsSendMessage;\nimport co.yixiang.yshop.module.system.mq.producer.sms.SmsProducer;\nimport co.yixiang.yshop.module.system.service.member.MemberService;\nimport co.yixiang.yshop.module.system.service.user.AdminUserService;\nimport org.assertj.core.util.Lists;\nimport org.junit.jupiter.api.Assertions;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.InjectMocks;\nimport org.mockito.Mock;\n\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport static cn.hutool.core.util.RandomUtil.randomEle;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertServiceException;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.*;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.*;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.*;\n\npublic class SmsSendServiceImplTest extends BaseMockitoUnitTest {\n\n    @InjectMocks\n    private SmsSendServiceImpl smsSendService;\n\n    @Mock\n    private AdminUserService adminUserService;\n    @Mock\n    private MemberService memberService;\n    @Mock\n    private SmsChannelService smsChannelService;\n    @Mock\n    private SmsTemplateService smsTemplateService;\n    @Mock\n    private SmsLogService smsLogService;\n    @Mock\n    private SmsProducer smsProducer;\n\n    @Test\n    public void testSendSingleSmsToAdmin() {\n        // 准备参数\n        Long userId = randomLongId();\n        String templateCode = randomString();\n        Map<String, Object> templateParams = MapUtil.<String, Object>builder().put(\"code\", \"1234\")\n                .put(\"op\", \"login\").build();\n        // mock adminUserService 的方法\n        AdminUserDO user = randomPojo(AdminUserDO.class, o -> o.setMobile(\"15601691300\"));\n        when(adminUserService.getUser(eq(userId))).thenReturn(user);\n\n        // mock SmsTemplateService 的方法\n        SmsTemplateDO template = randomPojo(SmsTemplateDO.class, o -> {\n            o.setStatus(CommonStatusEnum.ENABLE.getStatus());\n            o.setContent(\"验证码为{code}, 操作为{op}\");\n            o.setParams(Lists.newArrayList(\"code\", \"op\"));\n        });\n        when(smsTemplateService.getSmsTemplateByCodeFromCache(eq(templateCode))).thenReturn(template);\n        String content = randomString();\n        when(smsTemplateService.formatSmsTemplateContent(eq(template.getContent()), eq(templateParams)))\n                .thenReturn(content);\n        // mock SmsChannelService 的方法\n        SmsChannelDO smsChannel = randomPojo(SmsChannelDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));\n        when(smsChannelService.getSmsChannel(eq(template.getChannelId()))).thenReturn(smsChannel);\n        // mock SmsLogService 的方法\n        Long smsLogId = randomLongId();\n        when(smsLogService.createSmsLog(eq(user.getMobile()), eq(userId), eq(UserTypeEnum.ADMIN.getValue()), eq(Boolean.TRUE), eq(template),\n                eq(content), eq(templateParams))).thenReturn(smsLogId);\n\n        // 调用\n        Long resultSmsLogId = smsSendService.sendSingleSmsToAdmin(null, userId, templateCode, templateParams);\n        // 断言\n        assertEquals(smsLogId, resultSmsLogId);\n        // 断言调用\n        verify(smsProducer).sendSmsSendMessage(eq(smsLogId), eq(user.getMobile()),\n                eq(template.getChannelId()), eq(template.getApiTemplateId()),\n                eq(Lists.newArrayList(new KeyValue<>(\"code\", \"1234\"), new KeyValue<>(\"op\", \"login\"))));\n    }\n\n    @Test\n    public void testSendSingleSmsToUser() {\n        // 准备参数\n        Long userId = randomLongId();\n        String templateCode = randomString();\n        Map<String, Object> templateParams = MapUtil.<String, Object>builder().put(\"code\", \"1234\")\n                .put(\"op\", \"login\").build();\n        // mock memberService 的方法\n        String mobile = \"15601691300\";\n        when(memberService.getMemberUserMobile(eq(userId))).thenReturn(mobile);\n\n        // mock SmsTemplateService 的方法\n        SmsTemplateDO template = randomPojo(SmsTemplateDO.class, o -> {\n            o.setStatus(CommonStatusEnum.ENABLE.getStatus());\n            o.setContent(\"验证码为{code}, 操作为{op}\");\n            o.setParams(Lists.newArrayList(\"code\", \"op\"));\n        });\n        when(smsTemplateService.getSmsTemplateByCodeFromCache(eq(templateCode))).thenReturn(template);\n        String content = randomString();\n        when(smsTemplateService.formatSmsTemplateContent(eq(template.getContent()), eq(templateParams)))\n                .thenReturn(content);\n        // mock SmsChannelService 的方法\n        SmsChannelDO smsChannel = randomPojo(SmsChannelDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));\n        when(smsChannelService.getSmsChannel(eq(template.getChannelId()))).thenReturn(smsChannel);\n        // mock SmsLogService 的方法\n        Long smsLogId = randomLongId();\n        when(smsLogService.createSmsLog(eq(mobile), eq(userId), eq(UserTypeEnum.MEMBER.getValue()), eq(Boolean.TRUE), eq(template),\n                eq(content), eq(templateParams))).thenReturn(smsLogId);\n\n        // 调用\n        Long resultSmsLogId = smsSendService.sendSingleSmsToMember(null, userId, templateCode, templateParams);\n        // 断言\n        assertEquals(smsLogId, resultSmsLogId);\n        // 断言调用\n        verify(smsProducer).sendSmsSendMessage(eq(smsLogId), eq(mobile),\n                eq(template.getChannelId()), eq(template.getApiTemplateId()),\n                eq(Lists.newArrayList(new KeyValue<>(\"code\", \"1234\"), new KeyValue<>(\"op\", \"login\"))));\n    }\n\n    /**\n     * 发送成功，当短信模板开启时\n     */\n    @Test\n    public void testSendSingleSms_successWhenSmsTemplateEnable() {\n        // 准备参数\n        String mobile = randomString();\n        Long userId = randomLongId();\n        Integer userType = randomEle(UserTypeEnum.values()).getValue();\n        String templateCode = randomString();\n        Map<String, Object> templateParams = MapUtil.<String, Object>builder().put(\"code\", \"1234\")\n                .put(\"op\", \"login\").build();\n        // mock SmsTemplateService 的方法\n        SmsTemplateDO template = randomPojo(SmsTemplateDO.class, o -> {\n            o.setStatus(CommonStatusEnum.ENABLE.getStatus());\n            o.setContent(\"验证码为{code}, 操作为{op}\");\n            o.setParams(Lists.newArrayList(\"code\", \"op\"));\n        });\n        when(smsTemplateService.getSmsTemplateByCodeFromCache(eq(templateCode))).thenReturn(template);\n        String content = randomString();\n        when(smsTemplateService.formatSmsTemplateContent(eq(template.getContent()), eq(templateParams)))\n                .thenReturn(content);\n        // mock SmsChannelService 的方法\n        SmsChannelDO smsChannel = randomPojo(SmsChannelDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));\n        when(smsChannelService.getSmsChannel(eq(template.getChannelId()))).thenReturn(smsChannel);\n        // mock SmsLogService 的方法\n        Long smsLogId = randomLongId();\n        when(smsLogService.createSmsLog(eq(mobile), eq(userId), eq(userType), eq(Boolean.TRUE), eq(template),\n                eq(content), eq(templateParams))).thenReturn(smsLogId);\n\n        // 调用\n        Long resultSmsLogId = smsSendService.sendSingleSms(mobile, userId, userType, templateCode, templateParams);\n        // 断言\n        assertEquals(smsLogId, resultSmsLogId);\n        // 断言调用\n        verify(smsProducer).sendSmsSendMessage(eq(smsLogId), eq(mobile),\n                eq(template.getChannelId()), eq(template.getApiTemplateId()),\n                eq(Lists.newArrayList(new KeyValue<>(\"code\", \"1234\"), new KeyValue<>(\"op\", \"login\"))));\n    }\n\n    /**\n     * 发送成功，当短信模板关闭时\n     */\n    @Test\n    public void testSendSingleSms_successWhenSmsTemplateDisable() {\n        // 准备参数\n        String mobile = randomString();\n        Long userId = randomLongId();\n        Integer userType = randomEle(UserTypeEnum.values()).getValue();\n        String templateCode = randomString();\n        Map<String, Object> templateParams = MapUtil.<String, Object>builder().put(\"code\", \"1234\")\n                .put(\"op\", \"login\").build();\n        // mock SmsTemplateService 的方法\n        SmsTemplateDO template = randomPojo(SmsTemplateDO.class, o -> {\n            o.setStatus(CommonStatusEnum.DISABLE.getStatus());\n            o.setContent(\"验证码为{code}, 操作为{op}\");\n            o.setParams(Lists.newArrayList(\"code\", \"op\"));\n        });\n        when(smsTemplateService.getSmsTemplateByCodeFromCache(eq(templateCode))).thenReturn(template);\n        String content = randomString();\n        when(smsTemplateService.formatSmsTemplateContent(eq(template.getContent()), eq(templateParams)))\n                .thenReturn(content);\n        // mock SmsChannelService 的方法\n        SmsChannelDO smsChannel = randomPojo(SmsChannelDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));\n        when(smsChannelService.getSmsChannel(eq(template.getChannelId()))).thenReturn(smsChannel);\n        // mock SmsLogService 的方法\n        Long smsLogId = randomLongId();\n        when(smsLogService.createSmsLog(eq(mobile), eq(userId), eq(userType), eq(Boolean.FALSE), eq(template),\n                eq(content), eq(templateParams))).thenReturn(smsLogId);\n\n        // 调用\n        Long resultSmsLogId = smsSendService.sendSingleSms(mobile, userId, userType, templateCode, templateParams);\n        // 断言\n        assertEquals(smsLogId, resultSmsLogId);\n        // 断言调用\n        verify(smsProducer, times(0)).sendSmsSendMessage(anyLong(), anyString(),\n                anyLong(), any(), anyList());\n    }\n\n    @Test\n    public void testCheckSmsTemplateValid_notExists() {\n        // 准备参数\n        String templateCode = randomString();\n        // mock 方法\n\n        // 调用，并断言异常\n        assertServiceException(() -> smsSendService.validateSmsTemplate(templateCode),\n                SMS_SEND_TEMPLATE_NOT_EXISTS);\n    }\n\n    @Test\n    public void testBuildTemplateParams_paramMiss() {\n        // 准备参数\n        SmsTemplateDO template = randomPojo(SmsTemplateDO.class,\n                o -> o.setParams(Lists.newArrayList(\"code\")));\n        Map<String, Object> templateParams = new HashMap<>();\n        // mock 方法\n\n        // 调用，并断言异常\n        assertServiceException(() -> smsSendService.buildTemplateParams(template, templateParams),\n                SMS_SEND_MOBILE_TEMPLATE_PARAM_MISS, \"code\");\n    }\n\n    @Test\n    public void testCheckMobile_notExists() {\n        // 准备参数\n        // mock 方法\n\n        // 调用，并断言异常\n        assertServiceException(() -> smsSendService.validateMobile(null),\n                SMS_SEND_MOBILE_NOT_EXISTS);\n    }\n\n    @Test\n    public void testSendBatchNotify() {\n        // 准备参数\n        // mock 方法\n\n        // 调用\n        UnsupportedOperationException exception = Assertions.assertThrows(\n                UnsupportedOperationException.class,\n                () -> smsSendService.sendBatchSms(null, null, null, null, null)\n        );\n        // 断言\n        assertEquals(\"暂时不支持该操作，感兴趣可以实现该功能哟！\", exception.getMessage());\n    }\n\n    @Test\n    @SuppressWarnings(\"unchecked\")\n    public void testDoSendSms() throws Throwable {\n        // 准备参数\n        SmsSendMessage message = randomPojo(SmsSendMessage.class);\n        // mock SmsClientFactory 的方法\n        SmsClient smsClient = spy(SmsClient.class);\n        when(smsChannelService.getSmsClient(eq(message.getChannelId()))).thenReturn(smsClient);\n        // mock SmsClient 的方法\n        SmsSendRespDTO sendResult = randomPojo(SmsSendRespDTO.class);\n        when(smsClient.sendSms(eq(message.getLogId()), eq(message.getMobile()), eq(message.getApiTemplateId()),\n                eq(message.getTemplateParams()))).thenReturn(sendResult);\n\n        // 调用\n        smsSendService.doSendSms(message);\n        // 断言\n        verify(smsLogService).updateSmsSendResult(eq(message.getLogId()),\n                eq(sendResult.getSuccess()), eq(sendResult.getApiCode()),\n                eq(sendResult.getApiMsg()), eq(sendResult.getApiRequestId()), eq(sendResult.getSerialNo()));\n    }\n\n    @Test\n    public void testReceiveSmsStatus() throws Throwable {\n        // 准备参数\n        String channelCode = randomString();\n        String text = randomString();\n        // mock SmsClientFactory 的方法\n        SmsClient smsClient = spy(SmsClient.class);\n        when(smsChannelService.getSmsClient(eq(channelCode))).thenReturn(smsClient);\n        // mock SmsClient 的方法\n        List<SmsReceiveRespDTO> receiveResults = randomPojoList(SmsReceiveRespDTO.class);\n\n        // 调用\n        smsSendService.receiveSmsStatus(channelCode, text);\n        // 断言\n        receiveResults.forEach(result -> smsLogService.updateSmsReceiveResult(eq(result.getLogId()), eq(result.getSuccess()),\n                eq(result.getReceiveTime()), eq(result.getErrorCode()), eq(result.getErrorCode())));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/java/co/yixiang/yshop/module/system/service/sms/SmsTemplateServiceImplTest.java",
    "content": "package co.yixiang.yshop.module.system.service.sms;\n\nimport cn.hutool.core.map.MapUtil;\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.collection.ArrayUtils;\nimport co.yixiang.yshop.framework.common.util.object.ObjectUtils;\nimport co.yixiang.yshop.module.system.framework.sms.core.client.SmsClient;\nimport co.yixiang.yshop.module.system.framework.sms.core.client.dto.SmsTemplateRespDTO;\nimport co.yixiang.yshop.module.system.framework.sms.core.enums.SmsTemplateAuditStatusEnum;\nimport co.yixiang.yshop.framework.test.core.ut.BaseDbUnitTest;\nimport co.yixiang.yshop.module.system.controller.admin.sms.vo.template.SmsTemplatePageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.sms.vo.template.SmsTemplateSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.sms.SmsChannelDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.sms.SmsTemplateDO;\nimport co.yixiang.yshop.module.system.dal.mysql.sms.SmsTemplateMapper;\nimport co.yixiang.yshop.module.system.enums.sms.SmsTemplateTypeEnum;\nimport com.google.common.collect.Lists;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.mock.mockito.MockBean;\nimport org.springframework.context.annotation.Import;\n\nimport jakarta.annotation.Resource;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.function.Consumer;\n\nimport static cn.hutool.core.util.RandomUtil.randomEle;\nimport static co.yixiang.yshop.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;\nimport static co.yixiang.yshop.framework.common.util.date.LocalDateTimeUtils.buildTime;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertPojoEquals;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertServiceException;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.*;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.*;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.when;\n\n@Import(SmsTemplateServiceImpl.class)\npublic class SmsTemplateServiceImplTest extends BaseDbUnitTest {\n\n    @Resource\n    private SmsTemplateServiceImpl smsTemplateService;\n\n    @Resource\n    private SmsTemplateMapper smsTemplateMapper;\n\n    @MockBean\n    private SmsChannelService smsChannelService;\n    @MockBean\n    private SmsClient smsClient;\n\n    @Test\n    public void testFormatSmsTemplateContent() {\n        // 准备参数\n        String content = \"正在进行登录操作{operation}，您的验证码是{code}\";\n        Map<String, Object> params = MapUtil.<String, Object>builder(\"operation\", \"登录\")\n                .put(\"code\", \"1234\").build();\n\n        // 调用\n        String result = smsTemplateService.formatSmsTemplateContent(content, params);\n        // 断言\n        assertEquals(\"正在进行登录操作登录，您的验证码是1234\", result);\n    }\n\n    @Test\n    public void testParseTemplateContentParams() {\n        // 准备参数\n        String content = \"正在进行登录操作{operation}，您的验证码是{code}\";\n        // mock 方法\n\n        // 调用\n        List<String> params = smsTemplateService.parseTemplateContentParams(content);\n        // 断言\n        assertEquals(Lists.newArrayList(\"operation\", \"code\"), params);\n    }\n\n    @Test\n    @SuppressWarnings(\"unchecked\")\n    public void testCreateSmsTemplate_success() throws Throwable {\n        // 准备参数\n        SmsTemplateSaveReqVO reqVO = randomPojo(SmsTemplateSaveReqVO.class, o -> {\n            o.setContent(\"正在进行登录操作{operation}，您的验证码是{code}\");\n            o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围\n            o.setType(randomEle(SmsTemplateTypeEnum.values()).getType()); // 保证 type 的 范围\n        }).setId(null); // 防止 id 被赋值\n        // mock Channel 的方法\n        SmsChannelDO channelDO = randomPojo(SmsChannelDO.class, o -> {\n            o.setId(reqVO.getChannelId());\n            o.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 保证 status 开启，创建必须处于这个状态\n        });\n        when(smsChannelService.getSmsChannel(eq(channelDO.getId()))).thenReturn(channelDO);\n        // mock 获得 API 短信模板成功\n        when(smsChannelService.getSmsClient(eq(reqVO.getChannelId()))).thenReturn(smsClient);\n        when(smsClient.getSmsTemplate(eq(reqVO.getApiTemplateId()))).thenReturn(\n                randomPojo(SmsTemplateRespDTO.class, o -> o.setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus())));\n\n        // 调用\n        Long smsTemplateId = smsTemplateService.createSmsTemplate(reqVO);\n        // 断言\n        assertNotNull(smsTemplateId);\n        // 校验记录的属性是否正确\n        SmsTemplateDO smsTemplate = smsTemplateMapper.selectById(smsTemplateId);\n        assertPojoEquals(reqVO, smsTemplate, \"id\");\n        assertEquals(Lists.newArrayList(\"operation\", \"code\"), smsTemplate.getParams());\n        assertEquals(channelDO.getCode(), smsTemplate.getChannelCode());\n    }\n\n    @Test\n    @SuppressWarnings(\"unchecked\")\n    public void testUpdateSmsTemplate_success() throws Throwable {\n        // mock 数据\n        SmsTemplateDO dbSmsTemplate = randomSmsTemplateDO();\n        smsTemplateMapper.insert(dbSmsTemplate);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        SmsTemplateSaveReqVO reqVO = randomPojo(SmsTemplateSaveReqVO.class, o -> {\n            o.setId(dbSmsTemplate.getId()); // 设置更新的 ID\n            o.setContent(\"正在进行登录操作{operation}，您的验证码是{code}\");\n            o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围\n            o.setType(randomEle(SmsTemplateTypeEnum.values()).getType()); // 保证 type 的 范围\n        });\n        // mock 方法\n        SmsChannelDO channelDO = randomPojo(SmsChannelDO.class, o -> {\n            o.setId(reqVO.getChannelId());\n            o.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 保证 status 开启，创建必须处于这个状态\n        });\n        when(smsChannelService.getSmsChannel(eq(channelDO.getId()))).thenReturn(channelDO);\n        // mock 获得 API 短信模板成功\n        when(smsChannelService.getSmsClient(eq(reqVO.getChannelId()))).thenReturn(smsClient);\n        when(smsClient.getSmsTemplate(eq(reqVO.getApiTemplateId()))).thenReturn(\n                randomPojo(SmsTemplateRespDTO.class, o -> o.setAuditStatus(SmsTemplateAuditStatusEnum.SUCCESS.getStatus())));\n\n        // 调用\n        smsTemplateService.updateSmsTemplate(reqVO);\n        // 校验是否更新正确\n        SmsTemplateDO smsTemplate = smsTemplateMapper.selectById(reqVO.getId()); // 获取最新的\n        assertPojoEquals(reqVO, smsTemplate);\n        assertEquals(Lists.newArrayList(\"operation\", \"code\"), smsTemplate.getParams());\n        assertEquals(channelDO.getCode(), smsTemplate.getChannelCode());\n    }\n\n    @Test\n    public void testUpdateSmsTemplate_notExists() {\n        // 准备参数\n        SmsTemplateSaveReqVO reqVO = randomPojo(SmsTemplateSaveReqVO.class);\n\n        // 调用, 并断言异常\n        assertServiceException(() -> smsTemplateService.updateSmsTemplate(reqVO), SMS_TEMPLATE_NOT_EXISTS);\n    }\n\n    @Test\n    public void testDeleteSmsTemplate_success() {\n        // mock 数据\n        SmsTemplateDO dbSmsTemplate = randomSmsTemplateDO();\n        smsTemplateMapper.insert(dbSmsTemplate);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        Long id = dbSmsTemplate.getId();\n\n        // 调用\n        smsTemplateService.deleteSmsTemplate(id);\n        // 校验数据不存在了\n        assertNull(smsTemplateMapper.selectById(id));\n    }\n\n    @Test\n    public void testDeleteSmsTemplate_notExists() {\n        // 准备参数\n        Long id = randomLongId();\n\n        // 调用, 并断言异常\n        assertServiceException(() -> smsTemplateService.deleteSmsTemplate(id), SMS_TEMPLATE_NOT_EXISTS);\n    }\n\n    @Test\n    public void testGetSmsTemplate() {\n        // mock 数据\n        SmsTemplateDO dbSmsTemplate = randomSmsTemplateDO();\n        smsTemplateMapper.insert(dbSmsTemplate);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        Long id = dbSmsTemplate.getId();\n\n        // 调用\n        SmsTemplateDO smsTemplate = smsTemplateService.getSmsTemplate(id);\n        // 校验\n        assertPojoEquals(dbSmsTemplate, smsTemplate);\n    }\n\n    @Test\n    public void testGetSmsTemplateByCodeFromCache() {\n        // mock 数据\n        SmsTemplateDO dbSmsTemplate = randomSmsTemplateDO();\n        smsTemplateMapper.insert(dbSmsTemplate);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        String code = dbSmsTemplate.getCode();\n\n        // 调用\n        SmsTemplateDO smsTemplate = smsTemplateService.getSmsTemplateByCodeFromCache(code);\n        // 校验\n        assertPojoEquals(dbSmsTemplate, smsTemplate);\n    }\n\n    @Test\n    public void testGetSmsTemplatePage() {\n        // mock 数据\n        SmsTemplateDO dbSmsTemplate = randomPojo(SmsTemplateDO.class, o -> { // 等会查询到\n            o.setType(SmsTemplateTypeEnum.PROMOTION.getType());\n            o.setStatus(CommonStatusEnum.ENABLE.getStatus());\n            o.setCode(\"tudou\");\n            o.setContent(\"yshop\");\n            o.setApiTemplateId(\"yshop\");\n            o.setChannelId(1L);\n            o.setCreateTime(buildTime(2021, 11, 11));\n        });\n        smsTemplateMapper.insert(dbSmsTemplate);\n        // 测试 type 不匹配\n        smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setType(SmsTemplateTypeEnum.VERIFICATION_CODE.getType())));\n        // 测试 status 不匹配\n        smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));\n        // 测试 code 不匹配\n        smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setCode(\"yuanma\")));\n        // 测试 content 不匹配\n        smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setContent(\"源码\")));\n        // 测试 apiTemplateId 不匹配\n        smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setApiTemplateId(\"nai\")));\n        // 测试 channelId 不匹配\n        smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setChannelId(2L)));\n        // 测试 createTime 不匹配\n        smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setCreateTime(buildTime(2021, 12, 12))));\n        // 准备参数\n        SmsTemplatePageReqVO reqVO = new SmsTemplatePageReqVO();\n        reqVO.setType(SmsTemplateTypeEnum.PROMOTION.getType());\n        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());\n        reqVO.setCode(\"tu\");\n        reqVO.setContent(\"yshop\");\n        reqVO.setApiTemplateId(\"yu\");\n        reqVO.setChannelId(1L);\n        reqVO.setCreateTime(buildBetweenTime(2021, 11, 1, 2021, 12, 1));\n\n        // 调用\n        PageResult<SmsTemplateDO> pageResult = smsTemplateService.getSmsTemplatePage(reqVO);\n        // 断言\n        assertEquals(1, pageResult.getTotal());\n        assertEquals(1, pageResult.getList().size());\n        assertPojoEquals(dbSmsTemplate, pageResult.getList().get(0));\n    }\n\n    @Test\n    public void testGetSmsTemplateCountByChannelId() {\n        // mock 数据\n        SmsTemplateDO dbSmsTemplate = randomPojo(SmsTemplateDO.class, o -> o.setChannelId(1L));\n        smsTemplateMapper.insert(dbSmsTemplate);\n        // 测试 channelId 不匹配\n        smsTemplateMapper.insert(ObjectUtils.cloneIgnoreId(dbSmsTemplate, o -> o.setChannelId(2L)));\n        // 准备参数\n        Long channelId = 1L;\n\n        // 调用\n        Long count = smsTemplateService.getSmsTemplateCountByChannelId(channelId);\n        // 断言\n        assertEquals(1, count);\n    }\n\n    @Test\n    public void testValidateSmsChannel_success() {\n        // 准备参数\n        Long channelId = randomLongId();\n        // mock 方法\n        SmsChannelDO channelDO = randomPojo(SmsChannelDO.class, o -> {\n            o.setId(channelId);\n            o.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 保证 status 开启，创建必须处于这个状态\n        });\n        when(smsChannelService.getSmsChannel(eq(channelId))).thenReturn(channelDO);\n\n        // 调用\n        SmsChannelDO returnChannelDO = smsTemplateService.validateSmsChannel(channelId);\n        // 断言\n        assertPojoEquals(returnChannelDO, channelDO);\n    }\n\n    @Test\n    public void testValidateSmsChannel_notExists() {\n        // 准备参数\n        Long channelId = randomLongId();\n\n        // 调用，校验异常\n        assertServiceException(() -> smsTemplateService.validateSmsChannel(channelId),\n                SMS_CHANNEL_NOT_EXISTS);\n    }\n\n    @Test\n    public void testValidateSmsChannel_disable() {\n        // 准备参数\n        Long channelId = randomLongId();\n        // mock 方法\n        SmsChannelDO channelDO = randomPojo(SmsChannelDO.class, o -> {\n            o.setId(channelId);\n            o.setStatus(CommonStatusEnum.DISABLE.getStatus()); // 保证 status 禁用，触发失败\n        });\n        when(smsChannelService.getSmsChannel(eq(channelId))).thenReturn(channelDO);\n\n        // 调用，校验异常\n        assertServiceException(() -> smsTemplateService.validateSmsChannel(channelId),\n                SMS_CHANNEL_DISABLE);\n    }\n\n    @Test\n    public void testValidateDictDataValueUnique_success() {\n        // 调用，成功\n        smsTemplateService.validateSmsTemplateCodeDuplicate(randomLongId(), randomString());\n    }\n\n    @Test\n    public void testValidateSmsTemplateCodeDuplicate_valueDuplicateForCreate() {\n        // 准备参数\n        String code = randomString();\n        // mock 数据\n        smsTemplateMapper.insert(randomSmsTemplateDO(o -> o.setCode(code)));\n\n        // 调用，校验异常\n        assertServiceException(() -> smsTemplateService.validateSmsTemplateCodeDuplicate(null, code),\n                SMS_TEMPLATE_CODE_DUPLICATE, code);\n    }\n\n    @Test\n    public void testValidateDictDataValueUnique_valueDuplicateForUpdate() {\n        // 准备参数\n        Long id = randomLongId();\n        String code = randomString();\n        // mock 数据\n        smsTemplateMapper.insert(randomSmsTemplateDO(o -> o.setCode(code)));\n\n        // 调用，校验异常\n        assertServiceException(() -> smsTemplateService.validateSmsTemplateCodeDuplicate(id, code),\n                SMS_TEMPLATE_CODE_DUPLICATE, code);\n    }\n\n    // ========== 随机对象 ==========\n\n    @SafeVarargs\n    private static SmsTemplateDO randomSmsTemplateDO(Consumer<SmsTemplateDO>... consumers) {\n        Consumer<SmsTemplateDO> consumer = (o) -> {\n            o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围\n            o.setType(randomEle(SmsTemplateTypeEnum.values()).getType()); // 保证 type 的 范围\n        };\n        return randomPojo(SmsTemplateDO.class, ArrayUtils.append(consumer, consumers));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/java/co/yixiang/yshop/module/system/service/social/SocialClientServiceImplTest.java",
    "content": "package co.yixiang.yshop.module.system.service.social;\n\nimport cn.binarywang.wx.miniapp.api.WxMaService;\nimport cn.binarywang.wx.miniapp.api.WxMaUserService;\nimport cn.binarywang.wx.miniapp.bean.WxMaPhoneNumberInfo;\nimport cn.hutool.core.util.ReflectUtil;\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.test.core.ut.BaseDbUnitTest;\nimport co.yixiang.yshop.module.system.controller.admin.socail.vo.client.SocialClientPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.socail.vo.client.SocialClientSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.social.SocialClientDO;\nimport co.yixiang.yshop.module.system.dal.mysql.social.SocialClientMapper;\nimport co.yixiang.yshop.module.system.enums.social.SocialTypeEnum;\nimport com.binarywang.spring.starter.wxjava.miniapp.properties.WxMaProperties;\nimport com.binarywang.spring.starter.wxjava.mp.properties.WxMpProperties;\nimport com.xingyuv.jushauth.config.AuthConfig;\nimport com.xingyuv.jushauth.model.AuthResponse;\nimport com.xingyuv.jushauth.model.AuthUser;\nimport com.xingyuv.jushauth.request.AuthDefaultRequest;\nimport com.xingyuv.jushauth.request.AuthRequest;\nimport com.xingyuv.jushauth.utils.AuthStateUtils;\nimport com.xingyuv.justauth.AuthRequestFactory;\nimport me.chanjar.weixin.common.bean.WxJsapiSignature;\nimport me.chanjar.weixin.common.error.WxErrorException;\nimport me.chanjar.weixin.mp.api.WxMpService;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.MockedStatic;\nimport org.springframework.boot.test.mock.mockito.MockBean;\nimport org.springframework.context.annotation.Import;\nimport org.springframework.data.redis.core.StringRedisTemplate;\n\nimport jakarta.annotation.Resource;\n\nimport static cn.hutool.core.util.RandomUtil.randomEle;\nimport static co.yixiang.yshop.framework.common.util.object.ObjectUtils.cloneIgnoreId;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertPojoEquals;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertServiceException;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.*;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.*;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.Mockito.*;\n\n/**\n * {@link SocialClientServiceImpl} 的单元测试类\n *\n * @author yshop\n */\n@Import(SocialClientServiceImpl.class)\npublic class SocialClientServiceImplTest extends BaseDbUnitTest {\n\n    @Resource\n    private SocialClientServiceImpl socialClientService;\n\n    @Resource\n    private SocialClientMapper socialClientMapper;\n\n    @MockBean\n    private AuthRequestFactory authRequestFactory;\n\n    @MockBean\n    private WxMpService wxMpService;\n    @MockBean\n    private WxMpProperties wxMpProperties;\n    @MockBean\n    private StringRedisTemplate stringRedisTemplate;\n    @MockBean\n    private WxMaService wxMaService;\n    @MockBean\n    private WxMaProperties wxMaProperties;\n\n    @Test\n    public void testGetAuthorizeUrl() {\n        try (MockedStatic<AuthStateUtils> authStateUtilsMock = mockStatic(AuthStateUtils.class)) {\n            // 准备参数\n            Integer socialType = SocialTypeEnum.WECHAT_MP.getType();\n            Integer userType = randomPojo(UserTypeEnum.class).getValue();\n            String redirectUri = \"sss\";\n            // mock 获得对应的 AuthRequest 实现\n            AuthRequest authRequest = mock(AuthRequest.class);\n            when(authRequestFactory.get(eq(\"WECHAT_MP\"))).thenReturn(authRequest);\n            // mock 方法\n            authStateUtilsMock.when(AuthStateUtils::createState).thenReturn(\"aoteman\");\n            when(authRequest.authorize(eq(\"aoteman\"))).thenReturn(\"https://www.yixiang.co?redirect_uri=yyy\");\n\n            // 调用\n            String url = socialClientService.getAuthorizeUrl(socialType, userType, redirectUri);\n            // 断言\n            assertEquals(\"https://www.yixiang.co?redirect_uri=sss\", url);\n        }\n    }\n\n    @Test\n    public void testAuthSocialUser_success() {\n        // 准备参数\n        Integer socialType = SocialTypeEnum.WECHAT_MP.getType();\n        Integer userType = randomPojo(UserTypeEnum.class).getValue();\n        String code = randomString();\n        String state = randomString();\n        // mock 方法（AuthRequest）\n        AuthRequest authRequest = mock(AuthRequest.class);\n        when(authRequestFactory.get(eq(\"WECHAT_MP\"))).thenReturn(authRequest);\n        // mock 方法（AuthResponse）\n        AuthUser authUser = randomPojo(AuthUser.class);\n        AuthResponse<?> authResponse = new AuthResponse<>(2000, null, authUser);\n        when(authRequest.login(argThat(authCallback -> {\n            assertEquals(code, authCallback.getCode());\n            assertEquals(state, authCallback.getState());\n            return true;\n        }))).thenReturn(authResponse);\n\n        // 调用\n        AuthUser result = socialClientService.getAuthUser(socialType, userType, code, state);\n        // 断言\n        assertSame(authUser, result);\n    }\n\n    @Test\n    public void testAuthSocialUser_fail() {\n        // 准备参数\n        Integer socialType = SocialTypeEnum.WECHAT_MP.getType();\n        Integer userType = randomPojo(UserTypeEnum.class).getValue();\n        String code = randomString();\n        String state = randomString();\n        // mock 方法（AuthRequest）\n        AuthRequest authRequest = mock(AuthRequest.class);\n        when(authRequestFactory.get(eq(\"WECHAT_MP\"))).thenReturn(authRequest);\n        // mock 方法（AuthResponse）\n        AuthResponse<?> authResponse = new AuthResponse<>(0, \"模拟失败\", null);\n        when(authRequest.login(argThat(authCallback -> {\n            assertEquals(code, authCallback.getCode());\n            assertEquals(state, authCallback.getState());\n            return true;\n        }))).thenReturn(authResponse);\n\n        // 调用并断言\n        assertServiceException(\n                () -> socialClientService.getAuthUser(socialType, userType, code, state),\n                SOCIAL_USER_AUTH_FAILURE, \"模拟失败\");\n    }\n\n    @Test\n    public void testBuildAuthRequest_clientNull() {\n        // 准备参数\n        Integer socialType = SocialTypeEnum.WECHAT_MP.getType();\n        Integer userType = randomPojo(SocialTypeEnum.class).getType();\n        // mock 获得对应的 AuthRequest 实现\n        AuthRequest authRequest = mock(AuthDefaultRequest.class);\n        AuthConfig authConfig = (AuthConfig) ReflectUtil.getFieldValue(authRequest, \"config\");\n        when(authRequestFactory.get(eq(\"WECHAT_MP\"))).thenReturn(authRequest);\n\n        // 调用\n        AuthRequest result = socialClientService.buildAuthRequest(socialType, userType);\n        // 断言\n        assertSame(authRequest, result);\n        assertSame(authConfig, ReflectUtil.getFieldValue(authConfig, \"config\"));\n    }\n\n    @Test\n    public void testBuildAuthRequest_clientDisable() {\n        // 准备参数\n        Integer socialType = SocialTypeEnum.WECHAT_MP.getType();\n        Integer userType = randomPojo(SocialTypeEnum.class).getType();\n        // mock 获得对应的 AuthRequest 实现\n        AuthRequest authRequest = mock(AuthDefaultRequest.class);\n        AuthConfig authConfig = (AuthConfig) ReflectUtil.getFieldValue(authRequest, \"config\");\n        when(authRequestFactory.get(eq(\"WECHAT_MP\"))).thenReturn(authRequest);\n        // mock 数据\n        SocialClientDO client = randomPojo(SocialClientDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())\n                .setUserType(userType).setSocialType(socialType));\n        socialClientMapper.insert(client);\n\n        // 调用\n        AuthRequest result = socialClientService.buildAuthRequest(socialType, userType);\n        // 断言\n        assertSame(authRequest, result);\n        assertSame(authConfig, ReflectUtil.getFieldValue(authConfig, \"config\"));\n    }\n\n    @Test\n    public void testBuildAuthRequest_clientEnable() {\n        // 准备参数\n        Integer socialType = SocialTypeEnum.WECHAT_MP.getType();\n        Integer userType = randomPojo(SocialTypeEnum.class).getType();\n        // mock 获得对应的 AuthRequest 实现\n        AuthConfig authConfig = mock(AuthConfig.class);\n        AuthRequest authRequest = mock(AuthDefaultRequest.class);\n        ReflectUtil.setFieldValue(authRequest, \"config\", authConfig);\n        when(authRequestFactory.get(eq(\"WECHAT_MP\"))).thenReturn(authRequest);\n        // mock 数据\n        SocialClientDO client = randomPojo(SocialClientDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())\n                .setUserType(userType).setSocialType(socialType));\n        socialClientMapper.insert(client);\n\n        // 调用\n        AuthRequest result = socialClientService.buildAuthRequest(socialType, userType);\n        // 断言\n        assertSame(authRequest, result);\n        assertNotSame(authConfig, ReflectUtil.getFieldValue(authRequest, \"config\"));\n    }\n\n    // =================== 微信公众号独有 ===================\n\n    @Test\n    public void testCreateWxMpJsapiSignature() throws WxErrorException {\n        // 准备参数\n        Integer userType = randomPojo(UserTypeEnum.class).getValue();\n        String url = randomString();\n        // mock 方法\n        WxJsapiSignature signature = randomPojo(WxJsapiSignature.class);\n        when(wxMpService.createJsapiSignature(eq(url))).thenReturn(signature);\n\n        // 调用\n        WxJsapiSignature result = socialClientService.createWxMpJsapiSignature(userType, url);\n        // 断言\n        assertSame(signature, result);\n    }\n\n    @Test\n    public void testGetWxMpService_clientNull() {\n        // 准备参数\n        Integer userType = randomPojo(UserTypeEnum.class).getValue();\n        // mock 方法\n\n        // 调用\n        WxMpService result = socialClientService.getWxMpService(userType);\n        // 断言\n        assertSame(wxMpService, result);\n    }\n\n    @Test\n    public void testGetWxMpService_clientDisable() {\n        // 准备参数\n        Integer userType = randomPojo(UserTypeEnum.class).getValue();\n        // mock 数据\n        SocialClientDO client = randomPojo(SocialClientDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())\n                .setUserType(userType).setSocialType(SocialTypeEnum.WECHAT_MP.getType()));\n        socialClientMapper.insert(client);\n\n        // 调用\n        WxMpService result = socialClientService.getWxMpService(userType);\n        // 断言\n        assertSame(wxMpService, result);\n    }\n\n    @Test\n    public void testGetWxMpService_clientEnable() {\n        // 准备参数\n        Integer userType = randomPojo(UserTypeEnum.class).getValue();\n        // mock 数据\n        SocialClientDO client = randomPojo(SocialClientDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())\n                .setUserType(userType).setSocialType(SocialTypeEnum.WECHAT_MP.getType()));\n        socialClientMapper.insert(client);\n        // mock 方法\n        WxMpProperties.ConfigStorage configStorage = mock(WxMpProperties.ConfigStorage.class);\n        when(wxMpProperties.getConfigStorage()).thenReturn(configStorage);\n\n        // 调用\n        WxMpService result = socialClientService.getWxMpService(userType);\n        // 断言\n        assertNotSame(wxMpService, result);\n        assertEquals(client.getClientId(), result.getWxMpConfigStorage().getAppId());\n        assertEquals(client.getClientSecret(), result.getWxMpConfigStorage().getSecret());\n    }\n\n    // =================== 微信小程序独有 ===================\n\n    @Test\n    public void testGetWxMaPhoneNumberInfo_success() throws WxErrorException {\n        // 准备参数\n        Integer userType = randomPojo(UserTypeEnum.class).getValue();\n        String phoneCode = randomString();\n        // mock 方法\n        WxMaUserService userService = mock(WxMaUserService.class);\n        when(wxMaService.getUserService()).thenReturn(userService);\n        WxMaPhoneNumberInfo phoneNumber = randomPojo(WxMaPhoneNumberInfo.class);\n        when(userService.getPhoneNoInfo(eq(phoneCode))).thenReturn(phoneNumber);\n\n        // 调用\n        WxMaPhoneNumberInfo result = socialClientService.getWxMaPhoneNumberInfo(userType, phoneCode);\n        // 断言\n        assertSame(phoneNumber, result);\n    }\n\n    @Test\n    public void testGetWxMaPhoneNumberInfo_exception() throws WxErrorException {\n        // 准备参数\n        Integer userType = randomPojo(UserTypeEnum.class).getValue();\n        String phoneCode = randomString();\n        // mock 方法\n        WxMaUserService userService = mock(WxMaUserService.class);\n        when(wxMaService.getUserService()).thenReturn(userService);\n        WxErrorException wxErrorException = new WxErrorException(new NullPointerException());\n        when(userService.getPhoneNoInfo(eq(phoneCode))).thenThrow(wxErrorException);\n\n        // 调用并断言异常\n        assertServiceException(() -> socialClientService.getWxMaPhoneNumberInfo(userType, phoneCode),\n                SOCIAL_CLIENT_WEIXIN_MINI_APP_PHONE_CODE_ERROR);\n    }\n\n    @Test\n    public void testGetWxMaService_clientNull() {\n        // 准备参数\n        Integer userType = randomPojo(UserTypeEnum.class).getValue();\n        // mock 方法\n\n        // 调用\n        WxMaService result = socialClientService.getWxMaService(userType);\n        // 断言\n        assertSame(wxMaService, result);\n    }\n\n    @Test\n    public void testGetWxMaService_clientDisable() {\n        // 准备参数\n        Integer userType = randomPojo(UserTypeEnum.class).getValue();\n        // mock 数据\n        SocialClientDO client = randomPojo(SocialClientDO.class, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())\n                .setUserType(userType).setSocialType(SocialTypeEnum.WECHAT_MINI_APP.getType()));\n        socialClientMapper.insert(client);\n\n        // 调用\n        WxMaService result = socialClientService.getWxMaService(userType);\n        // 断言\n        assertSame(wxMaService, result);\n    }\n\n    @Test\n    public void testGetWxMaService_clientEnable() {\n        // 准备参数\n        Integer userType = randomPojo(UserTypeEnum.class).getValue();\n        // mock 数据\n        SocialClientDO client = randomPojo(SocialClientDO.class, o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())\n                .setUserType(userType).setSocialType(SocialTypeEnum.WECHAT_MINI_APP.getType()));\n        socialClientMapper.insert(client);\n        // mock 方法\n        WxMaProperties.ConfigStorage configStorage = mock(WxMaProperties.ConfigStorage.class);\n        when(wxMaProperties.getConfigStorage()).thenReturn(configStorage);\n\n        // 调用\n        WxMaService result = socialClientService.getWxMaService(userType);\n        // 断言\n        assertNotSame(wxMaService, result);\n        assertEquals(client.getClientId(), result.getWxMaConfig().getAppid());\n        assertEquals(client.getClientSecret(), result.getWxMaConfig().getSecret());\n    }\n\n    // =================== 客户端管理 ===================\n\n    @Test\n    public void testCreateSocialClient_success() {\n        // 准备参数\n        SocialClientSaveReqVO reqVO = randomPojo(SocialClientSaveReqVO.class,\n                o -> o.setSocialType(randomEle(SocialTypeEnum.values()).getType())\n                        .setUserType(randomEle(UserTypeEnum.values()).getValue())\n                        .setStatus(randomCommonStatus()))\n                .setId(null); // 防止 id 被赋值\n\n        // 调用\n        Long socialClientId = socialClientService.createSocialClient(reqVO);\n        // 断言\n        assertNotNull(socialClientId);\n        // 校验记录的属性是否正确\n        SocialClientDO socialClient = socialClientMapper.selectById(socialClientId);\n        assertPojoEquals(reqVO, socialClient, \"id\");\n    }\n\n    @Test\n    public void testUpdateSocialClient_success() {\n        // mock 数据\n        SocialClientDO dbSocialClient = randomPojo(SocialClientDO.class);\n        socialClientMapper.insert(dbSocialClient);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        SocialClientSaveReqVO reqVO = randomPojo(SocialClientSaveReqVO.class, o -> {\n            o.setId(dbSocialClient.getId()); // 设置更新的 ID\n            o.setSocialType(randomEle(SocialTypeEnum.values()).getType())\n                    .setUserType(randomEle(UserTypeEnum.values()).getValue())\n                    .setStatus(randomCommonStatus());\n        });\n\n        // 调用\n        socialClientService.updateSocialClient(reqVO);\n        // 校验是否更新正确\n        SocialClientDO socialClient = socialClientMapper.selectById(reqVO.getId()); // 获取最新的\n        assertPojoEquals(reqVO, socialClient);\n    }\n\n    @Test\n    public void testUpdateSocialClient_notExists() {\n        // 准备参数\n        SocialClientSaveReqVO reqVO = randomPojo(SocialClientSaveReqVO.class);\n\n        // 调用, 并断言异常\n        assertServiceException(() -> socialClientService.updateSocialClient(reqVO), SOCIAL_CLIENT_NOT_EXISTS);\n    }\n\n    @Test\n    public void testDeleteSocialClient_success() {\n        // mock 数据\n        SocialClientDO dbSocialClient = randomPojo(SocialClientDO.class);\n        socialClientMapper.insert(dbSocialClient);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        Long id = dbSocialClient.getId();\n\n        // 调用\n        socialClientService.deleteSocialClient(id);\n        // 校验数据不存在了\n        assertNull(socialClientMapper.selectById(id));\n    }\n\n    @Test\n    public void testDeleteSocialClient_notExists() {\n        // 准备参数\n        Long id = randomLongId();\n\n        // 调用, 并断言异常\n        assertServiceException(() -> socialClientService.deleteSocialClient(id), SOCIAL_CLIENT_NOT_EXISTS);\n    }\n\n    @Test\n    public void testGetSocialClient() {\n        // mock 数据\n        SocialClientDO dbSocialClient = randomPojo(SocialClientDO.class);\n        socialClientMapper.insert(dbSocialClient);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        Long id = dbSocialClient.getId();\n\n        // 调用\n        SocialClientDO socialClient = socialClientService.getSocialClient(id);\n        // 校验数据正确\n        assertPojoEquals(dbSocialClient, socialClient);\n    }\n\n    @Test\n    public void testGetSocialClientPage() {\n        // mock 数据\n        SocialClientDO dbSocialClient = randomPojo(SocialClientDO.class, o -> { // 等会查询到\n            o.setName(\"芋头\");\n            o.setSocialType(SocialTypeEnum.GITEE.getType());\n            o.setUserType(UserTypeEnum.ADMIN.getValue());\n            o.setClientId(\"yshop\");\n            o.setStatus(CommonStatusEnum.ENABLE.getStatus());\n        });\n        socialClientMapper.insert(dbSocialClient);\n        // 测试 name 不匹配\n        socialClientMapper.insert(cloneIgnoreId(dbSocialClient, o -> o.setName(randomString())));\n        // 测试 socialType 不匹配\n        socialClientMapper.insert(cloneIgnoreId(dbSocialClient, o -> o.setSocialType(SocialTypeEnum.DINGTALK.getType())));\n        // 测试 userType 不匹配\n        socialClientMapper.insert(cloneIgnoreId(dbSocialClient, o -> o.setUserType(UserTypeEnum.MEMBER.getValue())));\n        // 测试 clientId 不匹配\n        socialClientMapper.insert(cloneIgnoreId(dbSocialClient, o -> o.setClientId(\"dao\")));\n        // 测试 status 不匹配\n        socialClientMapper.insert(cloneIgnoreId(dbSocialClient, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));\n        // 准备参数\n        SocialClientPageReqVO reqVO = new SocialClientPageReqVO();\n        reqVO.setName(\"芋\");\n        reqVO.setSocialType(SocialTypeEnum.GITEE.getType());\n        reqVO.setUserType(UserTypeEnum.ADMIN.getValue());\n        reqVO.setClientId(\"yu\");\n        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());\n\n        // 调用\n        PageResult<SocialClientDO> pageResult = socialClientService.getSocialClientPage(reqVO);\n        // 断言\n        assertEquals(1, pageResult.getTotal());\n        assertEquals(1, pageResult.getList().size());\n        assertPojoEquals(dbSocialClient, pageResult.getList().get(0));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/java/co/yixiang/yshop/module/system/service/social/SocialUserServiceImplTest.java",
    "content": "package co.yixiang.yshop.module.system.service.social;\n\nimport co.yixiang.yshop.framework.common.enums.UserTypeEnum;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.test.core.ut.BaseDbUnitTest;\nimport co.yixiang.yshop.module.system.api.social.dto.SocialUserBindReqDTO;\nimport co.yixiang.yshop.module.system.api.social.dto.SocialUserRespDTO;\nimport co.yixiang.yshop.module.system.controller.admin.socail.vo.user.SocialUserPageReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.social.SocialUserBindDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.social.SocialUserDO;\nimport co.yixiang.yshop.module.system.dal.mysql.social.SocialUserBindMapper;\nimport co.yixiang.yshop.module.system.dal.mysql.social.SocialUserMapper;\nimport co.yixiang.yshop.module.system.enums.social.SocialTypeEnum;\nimport com.xingyuv.jushauth.model.AuthUser;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.mock.mockito.MockBean;\nimport org.springframework.context.annotation.Import;\n\nimport jakarta.annotation.Resource;\nimport java.util.List;\n\nimport static cn.hutool.core.util.RandomUtil.randomEle;\nimport static cn.hutool.core.util.RandomUtil.randomLong;\nimport static co.yixiang.yshop.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;\nimport static co.yixiang.yshop.framework.common.util.date.LocalDateTimeUtils.buildTime;\nimport static co.yixiang.yshop.framework.common.util.json.JsonUtils.toJsonString;\nimport static co.yixiang.yshop.framework.common.util.object.ObjectUtils.cloneIgnoreId;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertPojoEquals;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertServiceException;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.randomPojo;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.randomString;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.SOCIAL_USER_NOT_FOUND;\nimport static org.junit.jupiter.api.Assertions.assertEquals;\nimport static org.mockito.Mockito.eq;\nimport static org.mockito.Mockito.when;\n\n/**\n * {@link SocialUserServiceImpl} 的单元测试类\n *\n * @author yshop\n */\n@Import(SocialUserServiceImpl.class)\npublic class SocialUserServiceImplTest extends BaseDbUnitTest {\n\n    @Resource\n    private SocialUserServiceImpl socialUserService;\n\n    @Resource\n    private SocialUserMapper socialUserMapper;\n    @Resource\n    private SocialUserBindMapper socialUserBindMapper;\n\n    @MockBean\n    private SocialClientService socialClientService;\n\n    @Test\n    public void testGetSocialUserList() {\n        Long userId = 1L;\n        Integer userType = UserTypeEnum.ADMIN.getValue();\n        // mock 获得社交用户\n        SocialUserDO socialUser = randomPojo(SocialUserDO.class).setType(SocialTypeEnum.GITEE.getType());\n        socialUserMapper.insert(socialUser); // 可被查到\n        socialUserMapper.insert(randomPojo(SocialUserDO.class)); // 不可被查到\n        // mock 获得绑定\n        socialUserBindMapper.insert(randomPojo(SocialUserBindDO.class) // 可被查询到\n                .setUserId(userId).setUserType(userType).setSocialType(SocialTypeEnum.GITEE.getType())\n                .setSocialUserId(socialUser.getId()));\n        socialUserBindMapper.insert(randomPojo(SocialUserBindDO.class) // 不可被查询到\n                .setUserId(2L).setUserType(userType).setSocialType(SocialTypeEnum.DINGTALK.getType()));\n\n        // 调用\n        List<SocialUserDO> result = socialUserService.getSocialUserList(userId, userType);\n        // 断言\n        assertEquals(1, result.size());\n        assertPojoEquals(socialUser, result.get(0));\n    }\n\n    @Test\n    public void testBindSocialUser() {\n        // 准备参数\n        SocialUserBindReqDTO reqDTO = new SocialUserBindReqDTO()\n                .setUserId(1L).setUserType(UserTypeEnum.ADMIN.getValue())\n                .setSocialType(SocialTypeEnum.GITEE.getType()).setCode(\"test_code\").setState(\"test_state\");\n        // mock 数据：获得社交用户\n        SocialUserDO socialUser = randomPojo(SocialUserDO.class).setType(reqDTO.getSocialType())\n                .setCode(reqDTO.getCode()).setState(reqDTO.getState());\n        socialUserMapper.insert(socialUser);\n        // mock 数据：用户可能之前已经绑定过该社交类型\n        socialUserBindMapper.insert(randomPojo(SocialUserBindDO.class).setUserId(1L).setUserType(UserTypeEnum.ADMIN.getValue())\n                .setSocialType(SocialTypeEnum.GITEE.getType()).setSocialUserId(-1L));\n        // mock 数据：社交用户可能之前绑定过别的用户\n        socialUserBindMapper.insert(randomPojo(SocialUserBindDO.class).setUserType(UserTypeEnum.ADMIN.getValue())\n                .setSocialType(SocialTypeEnum.GITEE.getType()).setSocialUserId(socialUser.getId()));\n\n        // 调用\n        String openid = socialUserService.bindSocialUser(reqDTO);\n        // 断言\n        List<SocialUserBindDO> socialUserBinds = socialUserBindMapper.selectList();\n        assertEquals(1, socialUserBinds.size());\n        assertEquals(socialUser.getOpenid(), openid);\n    }\n\n    @Test\n    public void testUnbindSocialUser_success() {\n        // 准备参数\n        Long userId = 1L;\n        Integer userType = UserTypeEnum.ADMIN.getValue();\n        Integer type = SocialTypeEnum.GITEE.getType();\n        String openid = \"test_openid\";\n        // mock 数据：社交用户\n        SocialUserDO socialUser = randomPojo(SocialUserDO.class).setType(type).setOpenid(openid);\n        socialUserMapper.insert(socialUser);\n        // mock 数据：社交绑定关系\n        SocialUserBindDO socialUserBind = randomPojo(SocialUserBindDO.class).setUserType(userType)\n                .setUserId(userId).setSocialType(type);\n        socialUserBindMapper.insert(socialUserBind);\n\n        // 调用\n        socialUserService.unbindSocialUser(userId, userType, type, openid);\n        // 断言\n        assertEquals(0, socialUserBindMapper.selectCount(null).intValue());\n    }\n\n    @Test\n    public void testUnbindSocialUser_notFound() {\n        // 调用，并断言\n        assertServiceException(\n                () -> socialUserService.unbindSocialUser(randomLong(), UserTypeEnum.ADMIN.getValue(),\n                        SocialTypeEnum.GITEE.getType(), \"test_openid\"),\n                SOCIAL_USER_NOT_FOUND);\n    }\n\n    @Test\n    public void testGetSocialUser() {\n        // 准备参数\n        Integer userType = UserTypeEnum.ADMIN.getValue();\n        Integer type = SocialTypeEnum.GITEE.getType();\n        String code = \"tudou\";\n        String state = \"yuanma\";\n        // mock 社交用户\n        SocialUserDO socialUserDO = randomPojo(SocialUserDO.class).setType(type).setCode(code).setState(state);\n        socialUserMapper.insert(socialUserDO);\n        // mock 社交用户的绑定\n        Long userId = randomLong();\n        SocialUserBindDO socialUserBind = randomPojo(SocialUserBindDO.class).setUserType(userType).setUserId(userId)\n                .setSocialType(type).setSocialUserId(socialUserDO.getId());\n        socialUserBindMapper.insert(socialUserBind);\n\n        // 调用\n        SocialUserRespDTO socialUser = socialUserService.getSocialUserByCode(userType, type, code, state);\n        // 断言\n        assertEquals(userId, socialUser.getUserId());\n        assertEquals(socialUserDO.getOpenid(), socialUser.getOpenid());\n    }\n\n    @Test\n    public void testAuthSocialUser_exists() {\n        // 准备参数\n        Integer socialType = SocialTypeEnum.GITEE.getType();\n        Integer userType = randomEle(SocialTypeEnum.values()).getType();\n        String code = \"tudou\";\n        String state = \"yuanma\";\n        // mock 方法\n        SocialUserDO socialUser = randomPojo(SocialUserDO.class).setType(socialType).setCode(code).setState(state);\n        socialUserMapper.insert(socialUser);\n\n        // 调用\n        SocialUserDO result = socialUserService.authSocialUser(socialType, userType, code, state);\n        // 断言\n        assertPojoEquals(socialUser, result);\n    }\n\n    @Test\n    public void testAuthSocialUser_notNull() {\n        // mock 数据\n        SocialUserDO socialUser = randomPojo(SocialUserDO.class,\n                o -> o.setType(SocialTypeEnum.GITEE.getType()).setCode(\"tudou\").setState(\"yuanma\"));\n        socialUserMapper.insert(socialUser);\n        // 准备参数\n        Integer socialType = SocialTypeEnum.GITEE.getType();\n        Integer userType = randomEle(SocialTypeEnum.values()).getType();\n        String code = \"tudou\";\n        String state = \"yuanma\";\n\n        // 调用\n        SocialUserDO result = socialUserService.authSocialUser(socialType, userType, code, state);\n        // 断言\n        assertPojoEquals(socialUser, result);\n    }\n\n    @Test\n    public void testAuthSocialUser_insert() {\n        // 准备参数\n        Integer socialType = SocialTypeEnum.GITEE.getType();\n        Integer userType = randomEle(SocialTypeEnum.values()).getType();\n        String code = \"tudou\";\n        String state = \"yuanma\";\n        // mock 方法\n        AuthUser authUser = randomPojo(AuthUser.class);\n        when(socialClientService.getAuthUser(eq(socialType), eq(userType), eq(code), eq(state))).thenReturn(authUser);\n\n        // 调用\n        SocialUserDO result = socialUserService.authSocialUser(socialType, userType, code, state);\n        // 断言\n        assertBindSocialUser(socialType, result, authUser);\n        assertEquals(code, result.getCode());\n        assertEquals(state, result.getState());\n    }\n\n    @Test\n    public void testAuthSocialUser_update() {\n        // 准备参数\n        Integer socialType = SocialTypeEnum.GITEE.getType();\n        Integer userType = randomEle(SocialTypeEnum.values()).getType();\n        String code = \"tudou\";\n        String state = \"yuanma\";\n        // mock 数据\n        socialUserMapper.insert(randomPojo(SocialUserDO.class).setType(socialType).setOpenid(\"test_openid\"));\n        // mock 方法\n        AuthUser authUser = randomPojo(AuthUser.class);\n        when(socialClientService.getAuthUser(eq(socialType), eq(userType), eq(code), eq(state))).thenReturn(authUser);\n\n        // 调用\n        SocialUserDO result = socialUserService.authSocialUser(socialType, userType, code, state);\n        // 断言\n        assertBindSocialUser(socialType, result, authUser);\n        assertEquals(code, result.getCode());\n        assertEquals(state, result.getState());\n    }\n\n    private void assertBindSocialUser(Integer type, SocialUserDO socialUser, AuthUser authUser) {\n        assertEquals(authUser.getToken().getAccessToken(), socialUser.getToken());\n        assertEquals(toJsonString(authUser.getToken()), socialUser.getRawTokenInfo());\n        assertEquals(authUser.getNickname(), socialUser.getNickname());\n        assertEquals(authUser.getAvatar(), socialUser.getAvatar());\n        assertEquals(toJsonString(authUser.getRawUserInfo()), socialUser.getRawUserInfo());\n        assertEquals(type, socialUser.getType());\n        assertEquals(authUser.getUuid(), socialUser.getOpenid());\n    }\n\n    @Test\n    public void testGetSocialUser_id() {\n        // mock 数据\n        SocialUserDO socialUserDO = randomPojo(SocialUserDO.class);\n        socialUserMapper.insert(socialUserDO);\n        // 参数准备\n        Long id = socialUserDO.getId();\n\n        // 调用\n        SocialUserDO dbSocialUserDO = socialUserService.getSocialUser(id);\n        // 断言\n        assertPojoEquals(socialUserDO, dbSocialUserDO);\n    }\n\n    @Test\n    public void testGetSocialUserPage() {\n        // mock 数据\n        SocialUserDO dbSocialUser = randomPojo(SocialUserDO.class, o -> { // 等会查询到\n            o.setType(SocialTypeEnum.GITEE.getType());\n            o.setNickname(\"yshop\");\n            o.setOpenid(\"yshopyuanma\");\n            o.setCreateTime(buildTime(2020, 1, 15));\n        });\n        socialUserMapper.insert(dbSocialUser);\n        // 测试 type 不匹配\n        socialUserMapper.insert(cloneIgnoreId(dbSocialUser, o -> o.setType(SocialTypeEnum.DINGTALK.getType())));\n        // 测试 nickname 不匹配\n        socialUserMapper.insert(cloneIgnoreId(dbSocialUser, o -> o.setNickname(randomString())));\n        // 测试 openid 不匹配\n        socialUserMapper.insert(cloneIgnoreId(dbSocialUser, o -> o.setOpenid(\"java\")));\n        // 测试 createTime 不匹配\n        socialUserMapper.insert(cloneIgnoreId(dbSocialUser, o -> o.setCreateTime(buildTime(2020, 1, 21))));\n        // 准备参数\n        SocialUserPageReqVO reqVO = new SocialUserPageReqVO();\n        reqVO.setType(SocialTypeEnum.GITEE.getType());\n        reqVO.setNickname(\"芋\");\n        reqVO.setOpenid(\"yshop\");\n        reqVO.setCreateTime(buildBetweenTime(2020, 1, 10, 2020, 1, 20));\n\n        // 调用\n        PageResult<SocialUserDO> pageResult = socialUserService.getSocialUserPage(reqVO);\n        // 断言\n        assertEquals(1, pageResult.getTotal());\n        assertEquals(1, pageResult.getList().size());\n        assertPojoEquals(dbSocialUser, pageResult.getList().get(0));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/java/co/yixiang/yshop/module/system/service/tenant/TenantPackageServiceImplTest.java",
    "content": "package co.yixiang.yshop.module.system.service.tenant;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.test.core.ut.BaseDbUnitTest;\nimport co.yixiang.yshop.module.system.controller.admin.tenant.vo.packages.TenantPackagePageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.tenant.vo.packages.TenantPackageSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.tenant.TenantDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.tenant.TenantPackageDO;\nimport co.yixiang.yshop.module.system.dal.mysql.tenant.TenantPackageMapper;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.mock.mockito.MockBean;\nimport org.springframework.context.annotation.Import;\n\nimport jakarta.annotation.Resource;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;\nimport static co.yixiang.yshop.framework.common.util.date.LocalDateTimeUtils.buildTime;\nimport static co.yixiang.yshop.framework.common.util.object.ObjectUtils.cloneIgnoreId;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertPojoEquals;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertServiceException;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.*;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.*;\nimport static java.util.Arrays.asList;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.ArgumentMatchers.eq;\nimport static org.mockito.Mockito.verify;\nimport static org.mockito.Mockito.when;\n\n/**\n* {@link TenantPackageServiceImpl} 的单元测试类\n*\n* @author yshop\n*/\n@Import(TenantPackageServiceImpl.class)\npublic class TenantPackageServiceImplTest extends BaseDbUnitTest {\n\n    @Resource\n    private TenantPackageServiceImpl tenantPackageService;\n\n    @Resource\n    private TenantPackageMapper tenantPackageMapper;\n\n    @MockBean\n    private TenantService tenantService;\n\n    @Test\n    public void testCreateTenantPackage_success() {\n        // 准备参数\n        TenantPackageSaveReqVO reqVO = randomPojo(TenantPackageSaveReqVO.class,\n                o -> o.setStatus(randomCommonStatus()))\n                .setId(null); // 防止 id 被赋值\n\n        // 调用\n        Long tenantPackageId = tenantPackageService.createTenantPackage(reqVO);\n        // 断言\n        assertNotNull(tenantPackageId);\n        // 校验记录的属性是否正确\n        TenantPackageDO tenantPackage = tenantPackageMapper.selectById(tenantPackageId);\n        assertPojoEquals(reqVO, tenantPackage, \"id\");\n    }\n\n    @Test\n    public void testUpdateTenantPackage_success() {\n        // mock 数据\n        TenantPackageDO dbTenantPackage = randomPojo(TenantPackageDO.class,\n                o -> o.setStatus(randomCommonStatus()));\n        tenantPackageMapper.insert(dbTenantPackage);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        TenantPackageSaveReqVO reqVO = randomPojo(TenantPackageSaveReqVO.class, o -> {\n            o.setId(dbTenantPackage.getId()); // 设置更新的 ID\n            o.setStatus(randomCommonStatus());\n        });\n        // mock 方法\n        Long tenantId01 = randomLongId();\n        Long tenantId02 = randomLongId();\n        when(tenantService.getTenantListByPackageId(eq(reqVO.getId()))).thenReturn(\n                asList(randomPojo(TenantDO.class, o -> o.setId(tenantId01)),\n                        randomPojo(TenantDO.class, o -> o.setId(tenantId02))));\n\n        // 调用\n        tenantPackageService.updateTenantPackage(reqVO);\n        // 校验是否更新正确\n        TenantPackageDO tenantPackage = tenantPackageMapper.selectById(reqVO.getId()); // 获取最新的\n        assertPojoEquals(reqVO, tenantPackage);\n        // 校验调用租户的菜单\n        verify(tenantService).updateTenantRoleMenu(eq(tenantId01), eq(reqVO.getMenuIds()));\n        verify(tenantService).updateTenantRoleMenu(eq(tenantId02), eq(reqVO.getMenuIds()));\n    }\n\n    @Test\n    public void testUpdateTenantPackage_notExists() {\n        // 准备参数\n        TenantPackageSaveReqVO reqVO = randomPojo(TenantPackageSaveReqVO.class);\n\n        // 调用, 并断言异常\n        assertServiceException(() -> tenantPackageService.updateTenantPackage(reqVO), TENANT_PACKAGE_NOT_EXISTS);\n    }\n\n    @Test\n    public void testDeleteTenantPackage_success() {\n        // mock 数据\n        TenantPackageDO dbTenantPackage = randomPojo(TenantPackageDO.class);\n        tenantPackageMapper.insert(dbTenantPackage);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        Long id = dbTenantPackage.getId();\n        // mock 租户未使用该套餐\n        when(tenantService.getTenantCountByPackageId(eq(id))).thenReturn(0L);\n\n        // 调用\n        tenantPackageService.deleteTenantPackage(id);\n       // 校验数据不存在了\n       assertNull(tenantPackageMapper.selectById(id));\n    }\n\n    @Test\n    public void testDeleteTenantPackage_notExists() {\n        // 准备参数\n        Long id = randomLongId();\n\n        // 调用, 并断言异常\n        assertServiceException(() -> tenantPackageService.deleteTenantPackage(id), TENANT_PACKAGE_NOT_EXISTS);\n    }\n\n    @Test\n    public void testDeleteTenantPackage_used() {\n        // mock 数据\n        TenantPackageDO dbTenantPackage = randomPojo(TenantPackageDO.class);\n        tenantPackageMapper.insert(dbTenantPackage);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        Long id = dbTenantPackage.getId();\n        // mock 租户在使用该套餐\n        when(tenantService.getTenantCountByPackageId(eq(id))).thenReturn(1L);\n\n        // 调用, 并断言异常\n        assertServiceException(() -> tenantPackageService.deleteTenantPackage(id), TENANT_PACKAGE_USED);\n    }\n\n    @Test\n    public void testGetTenantPackagePage() {\n       // mock 数据\n       TenantPackageDO dbTenantPackage = randomPojo(TenantPackageDO.class, o -> { // 等会查询到\n           o.setName(\"yshop\");\n           o.setStatus(CommonStatusEnum.ENABLE.getStatus());\n           o.setRemark(\"源码解析\");\n           o.setCreateTime(buildTime(2022, 10, 10));\n       });\n       tenantPackageMapper.insert(dbTenantPackage);\n       // 测试 name 不匹配\n       tenantPackageMapper.insert(cloneIgnoreId(dbTenantPackage, o -> o.setName(\"源码\")));\n       // 测试 status 不匹配\n       tenantPackageMapper.insert(cloneIgnoreId(dbTenantPackage, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));\n       // 测试 remark 不匹配\n       tenantPackageMapper.insert(cloneIgnoreId(dbTenantPackage, o -> o.setRemark(\"解析\")));\n       // 测试 createTime 不匹配\n       tenantPackageMapper.insert(cloneIgnoreId(dbTenantPackage, o -> o.setCreateTime(buildTime(2022, 11, 11))));\n       // 准备参数\n       TenantPackagePageReqVO reqVO = new TenantPackagePageReqVO();\n       reqVO.setName(\"yshop\");\n       reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());\n       reqVO.setRemark(\"源码\");\n       reqVO.setCreateTime(buildBetweenTime(2022, 10, 9, 2022, 10, 11));\n\n       // 调用\n       PageResult<TenantPackageDO> pageResult = tenantPackageService.getTenantPackagePage(reqVO);\n       // 断言\n       assertEquals(1, pageResult.getTotal());\n       assertEquals(1, pageResult.getList().size());\n       assertPojoEquals(dbTenantPackage, pageResult.getList().get(0));\n    }\n\n    @Test\n    public void testValidTenantPackage_success() {\n        // mock 数据\n        TenantPackageDO dbTenantPackage = randomPojo(TenantPackageDO.class,\n                o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));\n        tenantPackageMapper.insert(dbTenantPackage);// @Sql: 先插入出一条存在的数据\n\n        // 调用\n        TenantPackageDO result = tenantPackageService.validTenantPackage(dbTenantPackage.getId());\n        // 断言\n        assertPojoEquals(dbTenantPackage, result);\n    }\n\n    @Test\n    public void testValidTenantPackage_notExists() {\n        // 准备参数\n        Long id = randomLongId();\n\n        // 调用, 并断言异常\n        assertServiceException(() -> tenantPackageService.validTenantPackage(id), TENANT_PACKAGE_NOT_EXISTS);\n    }\n\n    @Test\n    public void testValidTenantPackage_disable() {\n        // mock 数据\n        TenantPackageDO dbTenantPackage = randomPojo(TenantPackageDO.class,\n                o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()));\n        tenantPackageMapper.insert(dbTenantPackage);// @Sql: 先插入出一条存在的数据\n\n        // 调用, 并断言异常\n        assertServiceException(() -> tenantPackageService.validTenantPackage(dbTenantPackage.getId()),\n                TENANT_PACKAGE_DISABLE, dbTenantPackage.getName());\n    }\n\n    @Test\n    public void testGetTenantPackage() {\n        // mock 数据\n        TenantPackageDO dbTenantPackage = randomPojo(TenantPackageDO.class);\n        tenantPackageMapper.insert(dbTenantPackage);// @Sql: 先插入出一条存在的数据\n\n        // 调用\n        TenantPackageDO result = tenantPackageService.getTenantPackage(dbTenantPackage.getId());\n        // 断言\n        assertPojoEquals(result, dbTenantPackage);\n    }\n\n    @Test\n    public void testGetTenantPackageListByStatus() {\n        // mock 数据\n        TenantPackageDO dbTenantPackage = randomPojo(TenantPackageDO.class,\n                o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus()));\n        tenantPackageMapper.insert(dbTenantPackage);\n        // 测试 status 不匹配\n        tenantPackageMapper.insert(cloneIgnoreId(dbTenantPackage,\n                o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));\n\n        // 调用\n        List<TenantPackageDO> list = tenantPackageService.getTenantPackageListByStatus(\n                CommonStatusEnum.ENABLE.getStatus());\n        assertEquals(1, list.size());\n        assertPojoEquals(dbTenantPackage, list.get(0));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/java/co/yixiang/yshop/module/system/service/tenant/TenantServiceImplTest.java",
    "content": "package co.yixiang.yshop.module.system.service.tenant;\n\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.tenant.config.TenantProperties;\nimport co.yixiang.yshop.framework.tenant.core.context.TenantContextHolder;\nimport co.yixiang.yshop.framework.test.core.ut.BaseDbUnitTest;\nimport co.yixiang.yshop.module.system.controller.admin.tenant.vo.tenant.TenantPageReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.tenant.vo.tenant.TenantSaveReqVO;\nimport co.yixiang.yshop.module.system.dal.dataobject.permission.MenuDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.permission.RoleDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.tenant.TenantDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.tenant.TenantPackageDO;\nimport co.yixiang.yshop.module.system.dal.mysql.tenant.TenantMapper;\nimport co.yixiang.yshop.module.system.enums.permission.RoleCodeEnum;\nimport co.yixiang.yshop.module.system.enums.permission.RoleTypeEnum;\nimport co.yixiang.yshop.module.system.service.permission.MenuService;\nimport co.yixiang.yshop.module.system.service.permission.PermissionService;\nimport co.yixiang.yshop.module.system.service.permission.RoleService;\nimport co.yixiang.yshop.module.system.service.tenant.handler.TenantInfoHandler;\nimport co.yixiang.yshop.module.system.service.tenant.handler.TenantMenuHandler;\nimport co.yixiang.yshop.module.system.service.user.AdminUserService;\nimport org.junit.jupiter.api.BeforeEach;\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.mock.mockito.MockBean;\nimport org.springframework.context.annotation.Import;\n\nimport jakarta.annotation.Resource;\nimport java.time.LocalDateTime;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\n\nimport static co.yixiang.yshop.framework.common.util.collection.SetUtils.asSet;\nimport static co.yixiang.yshop.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;\nimport static co.yixiang.yshop.framework.common.util.date.LocalDateTimeUtils.buildTime;\nimport static co.yixiang.yshop.framework.common.util.object.ObjectUtils.cloneIgnoreId;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertPojoEquals;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertServiceException;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.*;\nimport static co.yixiang.yshop.module.system.dal.dataobject.tenant.TenantDO.PACKAGE_ID_SYSTEM;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.*;\nimport static java.util.Arrays.asList;\nimport static java.util.Collections.singleton;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.ArgumentMatchers.*;\nimport static org.mockito.Mockito.*;\n\n/**\n * {@link TenantServiceImpl} 的单元测试类\n *\n * @author yshop\n */\n@Import(TenantServiceImpl.class)\npublic class TenantServiceImplTest extends BaseDbUnitTest {\n\n    @Resource\n    private TenantServiceImpl tenantService;\n\n    @Resource\n    private TenantMapper tenantMapper;\n\n    @MockBean\n    private TenantProperties tenantProperties;\n    @MockBean\n    private TenantPackageService tenantPackageService;\n    @MockBean\n    private AdminUserService userService;\n    @MockBean\n    private RoleService roleService;\n    @MockBean\n    private MenuService menuService;\n    @MockBean\n    private PermissionService permissionService;\n\n    @BeforeEach\n    public void setUp() {\n        // 清理租户上下文\n        TenantContextHolder.clear();\n    }\n\n    @Test\n    public void testGetTenantIdList() {\n        // mock 数据\n        TenantDO tenant = randomPojo(TenantDO.class, o -> o.setId(1L));\n        tenantMapper.insert(tenant);\n\n        // 调用，并断言业务异常\n        List<Long> result = tenantService.getTenantIdList();\n        assertEquals(Collections.singletonList(1L), result);\n    }\n\n    @Test\n    public void testValidTenant_notExists() {\n        assertServiceException(() -> tenantService.validTenant(randomLongId()), TENANT_NOT_EXISTS);\n    }\n\n    @Test\n    public void testValidTenant_disable() {\n        // mock 数据\n        TenantDO tenant = randomPojo(TenantDO.class, o -> o.setId(1L).setStatus(CommonStatusEnum.DISABLE.getStatus()));\n        tenantMapper.insert(tenant);\n\n        // 调用，并断言业务异常\n        assertServiceException(() -> tenantService.validTenant(1L), TENANT_DISABLE, tenant.getName());\n    }\n\n    @Test\n    public void testValidTenant_expired() {\n        // mock 数据\n        TenantDO tenant = randomPojo(TenantDO.class, o -> o.setId(1L).setStatus(CommonStatusEnum.ENABLE.getStatus())\n                .setExpireTime(buildTime(2020, 2, 2)));\n        tenantMapper.insert(tenant);\n\n        // 调用，并断言业务异常\n        assertServiceException(() -> tenantService.validTenant(1L), TENANT_EXPIRE, tenant.getName());\n    }\n\n    @Test\n    public void testValidTenant_success() {\n        // mock 数据\n        TenantDO tenant = randomPojo(TenantDO.class, o -> o.setId(1L).setStatus(CommonStatusEnum.ENABLE.getStatus())\n                .setExpireTime(LocalDateTime.now().plusDays(1)));\n        tenantMapper.insert(tenant);\n\n        // 调用，并断言业务异常\n        tenantService.validTenant(1L);\n    }\n\n    @Test\n    public void testCreateTenant() {\n        // mock 套餐 100L\n        TenantPackageDO tenantPackage = randomPojo(TenantPackageDO.class, o -> o.setId(100L));\n        when(tenantPackageService.validTenantPackage(eq(100L))).thenReturn(tenantPackage);\n        // mock 角色 200L\n        when(roleService.createRole(argThat(role -> {\n            assertEquals(RoleCodeEnum.TENANT_ADMIN.getName(), role.getName());\n            assertEquals(RoleCodeEnum.TENANT_ADMIN.getCode(), role.getCode());\n            assertEquals(0, role.getSort());\n            assertEquals(\"系统自动生成\", role.getRemark());\n            return true;\n        }), eq(RoleTypeEnum.SYSTEM.getType()))).thenReturn(200L);\n        // mock 用户 300L\n        when(userService.createUser(argThat(user -> {\n            assertEquals(\"yshop\", user.getUsername());\n            assertEquals(\"yuanma\", user.getPassword());\n            assertEquals(\"yshop\", user.getNickname());\n            assertEquals(\"15601691300\", user.getMobile());\n            return true;\n        }))).thenReturn(300L);\n\n        // 准备参数\n        TenantSaveReqVO reqVO = randomPojo(TenantSaveReqVO.class, o -> {\n            o.setContactName(\"yshop\");\n            o.setContactMobile(\"15601691300\");\n            o.setPackageId(100L);\n            o.setStatus(randomCommonStatus());\n            o.setWebsite(\"https://www.yixiang.co\");\n            o.setUsername(\"yshop\");\n            o.setPassword(\"yuanma\");\n        }).setId(null); // 设置为 null，方便后面校验\n\n        // 调用\n        Long tenantId = tenantService.createTenant(reqVO);\n        // 断言\n        assertNotNull(tenantId);\n        // 校验记录的属性是否正确\n        TenantDO tenant = tenantMapper.selectById(tenantId);\n        assertPojoEquals(reqVO, tenant, \"id\");\n        assertEquals(300L, tenant.getContactUserId());\n        // verify 分配权限\n        verify(permissionService).assignRoleMenu(eq(200L), same(tenantPackage.getMenuIds()));\n        // verify 分配角色\n        verify(permissionService).assignUserRole(eq(300L), eq(singleton(200L)));\n    }\n\n    @Test\n    public void testUpdateTenant_success() {\n        // mock 数据\n        TenantDO dbTenant = randomPojo(TenantDO.class, o -> o.setStatus(randomCommonStatus()));\n        tenantMapper.insert(dbTenant);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        TenantSaveReqVO reqVO = randomPojo(TenantSaveReqVO.class, o -> {\n            o.setId(dbTenant.getId()); // 设置更新的 ID\n            o.setStatus(randomCommonStatus());\n            o.setWebsite(randomString());\n        });\n\n        // mock 套餐\n        TenantPackageDO tenantPackage = randomPojo(TenantPackageDO.class,\n                o -> o.setMenuIds(asSet(200L, 201L)));\n        when(tenantPackageService.validTenantPackage(eq(reqVO.getPackageId()))).thenReturn(tenantPackage);\n        // mock 所有角色\n        RoleDO role100 = randomPojo(RoleDO.class, o -> o.setId(100L).setCode(RoleCodeEnum.TENANT_ADMIN.getCode()));\n        role100.setTenantId(dbTenant.getId());\n        RoleDO role101 = randomPojo(RoleDO.class, o -> o.setId(101L));\n        role101.setTenantId(dbTenant.getId());\n        when(roleService.getRoleList()).thenReturn(asList(role100, role101));\n        // mock 每个角色的权限\n        when(permissionService.getRoleMenuListByRoleId(eq(101L))).thenReturn(asSet(201L, 202L));\n\n        // 调用\n        tenantService.updateTenant(reqVO);\n        // 校验是否更新正确\n        TenantDO tenant = tenantMapper.selectById(reqVO.getId()); // 获取最新的\n        assertPojoEquals(reqVO, tenant);\n        // verify 设置角色权限\n        verify(permissionService).assignRoleMenu(eq(100L), eq(asSet(200L, 201L)));\n        verify(permissionService).assignRoleMenu(eq(101L), eq(asSet(201L)));\n    }\n\n    @Test\n    public void testUpdateTenant_notExists() {\n        // 准备参数\n        TenantSaveReqVO reqVO = randomPojo(TenantSaveReqVO.class);\n\n        // 调用, 并断言异常\n        assertServiceException(() -> tenantService.updateTenant(reqVO), TENANT_NOT_EXISTS);\n    }\n\n    @Test\n    public void testUpdateTenant_system() {\n        // mock 数据\n        TenantDO dbTenant = randomPojo(TenantDO.class, o -> o.setPackageId(PACKAGE_ID_SYSTEM));\n        tenantMapper.insert(dbTenant);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        TenantSaveReqVO reqVO = randomPojo(TenantSaveReqVO.class, o -> {\n            o.setId(dbTenant.getId()); // 设置更新的 ID\n        });\n\n        // 调用，校验业务异常\n        assertServiceException(() -> tenantService.updateTenant(reqVO), TENANT_CAN_NOT_UPDATE_SYSTEM);\n    }\n\n    @Test\n    public void testDeleteTenant_success() {\n        // mock 数据\n        TenantDO dbTenant = randomPojo(TenantDO.class,\n                o -> o.setStatus(randomCommonStatus()));\n        tenantMapper.insert(dbTenant);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        Long id = dbTenant.getId();\n\n        // 调用\n        tenantService.deleteTenant(id);\n        // 校验数据不存在了\n        assertNull(tenantMapper.selectById(id));\n    }\n\n    @Test\n    public void testDeleteTenant_notExists() {\n        // 准备参数\n        Long id = randomLongId();\n\n        // 调用, 并断言异常\n        assertServiceException(() -> tenantService.deleteTenant(id), TENANT_NOT_EXISTS);\n    }\n\n    @Test\n    public void testDeleteTenant_system() {\n        // mock 数据\n        TenantDO dbTenant = randomPojo(TenantDO.class, o -> o.setPackageId(PACKAGE_ID_SYSTEM));\n        tenantMapper.insert(dbTenant);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        Long id = dbTenant.getId();\n\n        // 调用, 并断言异常\n        assertServiceException(() -> tenantService.deleteTenant(id), TENANT_CAN_NOT_UPDATE_SYSTEM);\n    }\n\n    @Test\n    public void testGetTenant() {\n        // mock 数据\n        TenantDO dbTenant = randomPojo(TenantDO.class);\n        tenantMapper.insert(dbTenant);// @Sql: 先插入出一条存在的数据\n        // 准备参数\n        Long id = dbTenant.getId();\n\n        // 调用\n        TenantDO result = tenantService.getTenant(id);\n        // 校验存在\n        assertPojoEquals(result, dbTenant);\n    }\n\n    @Test\n    public void testGetTenantPage() {\n        // mock 数据\n        TenantDO dbTenant = randomPojo(TenantDO.class, o -> { // 等会查询到\n            o.setName(\"yshop\");\n            o.setContactName(\"yshop\");\n            o.setContactMobile(\"15601691300\");\n            o.setStatus(CommonStatusEnum.ENABLE.getStatus());\n            o.setCreateTime(buildTime(2020, 12, 12));\n        });\n        tenantMapper.insert(dbTenant);\n        // 测试 name 不匹配\n        tenantMapper.insert(cloneIgnoreId(dbTenant, o -> o.setName(randomString())));\n        // 测试 contactName 不匹配\n        tenantMapper.insert(cloneIgnoreId(dbTenant, o -> o.setContactName(randomString())));\n        // 测试 contactMobile 不匹配\n        tenantMapper.insert(cloneIgnoreId(dbTenant, o -> o.setContactMobile(randomString())));\n        // 测试 status 不匹配\n        tenantMapper.insert(cloneIgnoreId(dbTenant, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));\n        // 测试 createTime 不匹配\n        tenantMapper.insert(cloneIgnoreId(dbTenant, o -> o.setCreateTime(buildTime(2021, 12, 12))));\n        // 准备参数\n        TenantPageReqVO reqVO = new TenantPageReqVO();\n        reqVO.setName(\"yshop\");\n        reqVO.setContactName(\"艿\");\n        reqVO.setContactMobile(\"1560\");\n        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());\n        reqVO.setCreateTime(buildBetweenTime(2020, 12, 1, 2020, 12, 24));\n\n        // 调用\n        PageResult<TenantDO> pageResult = tenantService.getTenantPage(reqVO);\n        // 断言\n        assertEquals(1, pageResult.getTotal());\n        assertEquals(1, pageResult.getList().size());\n        assertPojoEquals(dbTenant, pageResult.getList().get(0));\n    }\n\n    @Test\n    public void testGetTenantByName() {\n        // mock 数据\n        TenantDO dbTenant = randomPojo(TenantDO.class, o -> o.setName(\"yshop\"));\n        tenantMapper.insert(dbTenant);// @Sql: 先插入出一条存在的数据\n\n        // 调用\n        TenantDO result = tenantService.getTenantByName(\"yshop\");\n        // 校验存在\n        assertPojoEquals(result, dbTenant);\n    }\n\n    @Test\n    public void testGetTenantByWebsite() {\n        // mock 数据\n        TenantDO dbTenant = randomPojo(TenantDO.class, o -> o.setWebsite(\"https://www.yixiang.co\"));\n        tenantMapper.insert(dbTenant);// @Sql: 先插入出一条存在的数据\n\n        // 调用\n        TenantDO result = tenantService.getTenantByWebsite(\"https://www.yixiang.co\");\n        // 校验存在\n        assertPojoEquals(result, dbTenant);\n    }\n\n    @Test\n    public void testGetTenantListByPackageId() {\n        // mock 数据\n        TenantDO dbTenant1 = randomPojo(TenantDO.class, o -> o.setPackageId(1L));\n        tenantMapper.insert(dbTenant1);// @Sql: 先插入出一条存在的数据\n        TenantDO dbTenant2 = randomPojo(TenantDO.class, o -> o.setPackageId(2L));\n        tenantMapper.insert(dbTenant2);// @Sql: 先插入出一条存在的数据\n\n        // 调用\n        List<TenantDO> result = tenantService.getTenantListByPackageId(1L);\n        assertEquals(1, result.size());\n        assertPojoEquals(dbTenant1, result.get(0));\n    }\n\n    @Test\n    public void testGetTenantCountByPackageId() {\n        // mock 数据\n        TenantDO dbTenant1 = randomPojo(TenantDO.class, o -> o.setPackageId(1L));\n        tenantMapper.insert(dbTenant1);// @Sql: 先插入出一条存在的数据\n        TenantDO dbTenant2 = randomPojo(TenantDO.class, o -> o.setPackageId(2L));\n        tenantMapper.insert(dbTenant2);// @Sql: 先插入出一条存在的数据\n\n        // 调用\n        Long count = tenantService.getTenantCountByPackageId(1L);\n        assertEquals(1, count);\n    }\n\n    @Test\n    public void testHandleTenantInfo_disable() {\n        // 准备参数\n        TenantInfoHandler handler = mock(TenantInfoHandler.class);\n        // mock 禁用\n        when(tenantProperties.getEnable()).thenReturn(false);\n\n        // 调用\n        tenantService.handleTenantInfo(handler);\n        // 断言\n        verify(handler, never()).handle(any());\n    }\n\n    @Test\n    public void testHandleTenantInfo_success() {\n        // 准备参数\n        TenantInfoHandler handler = mock(TenantInfoHandler.class);\n        // mock 未禁用\n        when(tenantProperties.getEnable()).thenReturn(true);\n        // mock 租户\n        TenantDO dbTenant = randomPojo(TenantDO.class);\n        tenantMapper.insert(dbTenant);// @Sql: 先插入出一条存在的数据\n        TenantContextHolder.setTenantId(dbTenant.getId());\n\n        // 调用\n        tenantService.handleTenantInfo(handler);\n        // 断言\n        verify(handler).handle(argThat(argument -> {\n            assertPojoEquals(dbTenant, argument);\n            return true;\n        }));\n    }\n\n    @Test\n    public void testHandleTenantMenu_disable() {\n        // 准备参数\n        TenantMenuHandler handler = mock(TenantMenuHandler.class);\n        // mock 禁用\n        when(tenantProperties.getEnable()).thenReturn(false);\n\n        // 调用\n        tenantService.handleTenantMenu(handler);\n        // 断言\n        verify(handler, never()).handle(any());\n    }\n\n    @Test // 系统租户的情况\n    public void testHandleTenantMenu_system() {\n        // 准备参数\n        TenantMenuHandler handler = mock(TenantMenuHandler.class);\n        // mock 未禁用\n        when(tenantProperties.getEnable()).thenReturn(true);\n        // mock 租户\n        TenantDO dbTenant = randomPojo(TenantDO.class, o -> o.setPackageId(PACKAGE_ID_SYSTEM));\n        tenantMapper.insert(dbTenant);// @Sql: 先插入出一条存在的数据\n        TenantContextHolder.setTenantId(dbTenant.getId());\n        // mock 菜单\n        when(menuService.getMenuList()).thenReturn(Arrays.asList(randomPojo(MenuDO.class, o -> o.setId(100L)),\n                randomPojo(MenuDO.class, o -> o.setId(101L))));\n\n        // 调用\n        tenantService.handleTenantMenu(handler);\n        // 断言\n        verify(handler).handle(asSet(100L, 101L));\n    }\n\n    @Test // 普通租户的情况\n    public void testHandleTenantMenu_normal() {\n        // 准备参数\n        TenantMenuHandler handler = mock(TenantMenuHandler.class);\n        // mock 未禁用\n        when(tenantProperties.getEnable()).thenReturn(true);\n        // mock 租户\n        TenantDO dbTenant = randomPojo(TenantDO.class, o -> o.setPackageId(200L));\n        tenantMapper.insert(dbTenant);// @Sql: 先插入出一条存在的数据\n        TenantContextHolder.setTenantId(dbTenant.getId());\n        // mock 菜单\n        when(tenantPackageService.getTenantPackage(eq(200L))).thenReturn(randomPojo(TenantPackageDO.class,\n                o -> o.setMenuIds(asSet(100L, 101L))));\n\n        // 调用\n        tenantService.handleTenantMenu(handler);\n        // 断言\n        verify(handler).handle(asSet(100L, 101L));\n    }\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/java/co/yixiang/yshop/module/system/service/user/AdminUserServiceImplTest.java",
    "content": "package co.yixiang.yshop.module.system.service.user;\n\nimport cn.hutool.core.util.RandomUtil;\nimport co.yixiang.yshop.framework.common.enums.CommonStatusEnum;\nimport co.yixiang.yshop.framework.common.exception.ServiceException;\nimport co.yixiang.yshop.framework.common.pojo.PageResult;\nimport co.yixiang.yshop.framework.common.util.collection.ArrayUtils;\nimport co.yixiang.yshop.framework.common.util.collection.CollectionUtils;\nimport co.yixiang.yshop.framework.test.core.ut.BaseDbUnitTest;\nimport co.yixiang.yshop.module.infra.api.file.FileApi;\nimport co.yixiang.yshop.module.system.controller.admin.user.vo.profile.UserProfileUpdatePasswordReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.user.vo.profile.UserProfileUpdateReqVO;\nimport co.yixiang.yshop.module.system.controller.admin.user.vo.user.*;\nimport co.yixiang.yshop.module.system.dal.dataobject.dept.DeptDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.dept.PostDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.dept.UserPostDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.tenant.TenantDO;\nimport co.yixiang.yshop.module.system.dal.dataobject.user.AdminUserDO;\nimport co.yixiang.yshop.module.system.dal.mysql.dept.UserPostMapper;\nimport co.yixiang.yshop.module.system.dal.mysql.user.AdminUserMapper;\nimport co.yixiang.yshop.module.system.enums.common.SexEnum;\nimport co.yixiang.yshop.module.system.service.dept.DeptService;\nimport co.yixiang.yshop.module.system.service.dept.PostService;\nimport co.yixiang.yshop.module.system.service.permission.PermissionService;\nimport co.yixiang.yshop.module.system.service.tenant.TenantService;\nimport org.junit.jupiter.api.Test;\nimport org.mockito.stubbing.Answer;\nimport org.springframework.boot.test.mock.mockito.MockBean;\nimport org.springframework.context.annotation.Import;\nimport org.springframework.security.crypto.password.PasswordEncoder;\n\nimport jakarta.annotation.Resource;\nimport java.io.ByteArrayInputStream;\nimport java.util.Collection;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.function.Consumer;\n\nimport static cn.hutool.core.util.RandomUtil.randomBytes;\nimport static cn.hutool.core.util.RandomUtil.randomEle;\nimport static co.yixiang.yshop.framework.common.util.collection.SetUtils.asSet;\nimport static co.yixiang.yshop.framework.common.util.date.LocalDateTimeUtils.buildBetweenTime;\nimport static co.yixiang.yshop.framework.common.util.date.LocalDateTimeUtils.buildTime;\nimport static co.yixiang.yshop.framework.common.util.object.ObjectUtils.cloneIgnoreId;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertPojoEquals;\nimport static co.yixiang.yshop.framework.test.core.util.AssertUtils.assertServiceException;\nimport static co.yixiang.yshop.framework.test.core.util.RandomUtils.*;\nimport static co.yixiang.yshop.module.system.enums.ErrorCodeConstants.*;\nimport static java.util.Collections.singleton;\nimport static java.util.Collections.singletonList;\nimport static org.assertj.core.util.Lists.newArrayList;\nimport static org.junit.jupiter.api.Assertions.*;\nimport static org.mockito.ArgumentMatchers.*;\nimport static org.mockito.Mockito.*;\n\n@Import(AdminUserServiceImpl.class)\npublic class AdminUserServiceImplTest extends BaseDbUnitTest {\n\n    @Resource\n    private AdminUserServiceImpl userService;\n\n    @Resource\n    private AdminUserMapper userMapper;\n    @Resource\n    private UserPostMapper userPostMapper;\n\n    @MockBean\n    private DeptService deptService;\n    @MockBean\n    private PostService postService;\n    @MockBean\n    private PermissionService permissionService;\n    @MockBean\n    private PasswordEncoder passwordEncoder;\n    @MockBean\n    private TenantService tenantService;\n    @MockBean\n    private FileApi fileApi;\n\n    @Test\n    public void testCreatUser_success() {\n        // 准备参数\n        UserSaveReqVO reqVO = randomPojo(UserSaveReqVO.class, o -> {\n            o.setSex(RandomUtil.randomEle(SexEnum.values()).getSex());\n            o.setMobile(randomString());\n            o.setPostIds(asSet(1L, 2L));\n        }).setId(null); // 避免 id 被赋值\n        // mock 账户额度充足\n        TenantDO tenant = randomPojo(TenantDO.class, o -> o.setAccountCount(1));\n        doNothing().when(tenantService).handleTenantInfo(argThat(handler -> {\n            handler.handle(tenant);\n            return true;\n        }));\n        // mock deptService 的方法\n        DeptDO dept = randomPojo(DeptDO.class, o -> {\n            o.setId(reqVO.getDeptId());\n            o.setStatus(CommonStatusEnum.ENABLE.getStatus());\n        });\n        when(deptService.getDept(eq(dept.getId()))).thenReturn(dept);\n        // mock postService 的方法\n        List<PostDO> posts = CollectionUtils.convertList(reqVO.getPostIds(), postId ->\n                randomPojo(PostDO.class, o -> {\n                    o.setId(postId);\n                    o.setStatus(CommonStatusEnum.ENABLE.getStatus());\n                }));\n        when(postService.getPostList(eq(reqVO.getPostIds()), isNull())).thenReturn(posts);\n        // mock passwordEncoder 的方法\n        when(passwordEncoder.encode(eq(reqVO.getPassword()))).thenReturn(\"yshopyuanma\");\n\n        // 调用\n        Long userId = userService.createUser(reqVO);\n        // 断言\n        AdminUserDO user = userMapper.selectById(userId);\n        assertPojoEquals(reqVO, user, \"password\", \"id\");\n        assertEquals(\"yshopyuanma\", user.getPassword());\n        assertEquals(CommonStatusEnum.ENABLE.getStatus(), user.getStatus());\n        // 断言关联岗位\n        List<UserPostDO> userPosts = userPostMapper.selectListByUserId(user.getId());\n        assertEquals(1L, userPosts.get(0).getPostId());\n        assertEquals(2L, userPosts.get(1).getPostId());\n    }\n\n    @Test\n    public void testCreatUser_max() {\n        // 准备参数\n        UserSaveReqVO reqVO = randomPojo(UserSaveReqVO.class);\n        // mock 账户额度不足\n        TenantDO tenant = randomPojo(TenantDO.class, o -> o.setAccountCount(-1));\n        doNothing().when(tenantService).handleTenantInfo(argThat(handler -> {\n            handler.handle(tenant);\n            return true;\n        }));\n\n        // 调用，并断言异常\n        assertServiceException(() -> userService.createUser(reqVO), USER_COUNT_MAX, -1);\n    }\n\n    @Test\n    public void testUpdateUser_success() {\n        // mock 数据\n        AdminUserDO dbUser = randomAdminUserDO(o -> o.setPostIds(asSet(1L, 2L)));\n        userMapper.insert(dbUser);\n        userPostMapper.insert(new UserPostDO().setUserId(dbUser.getId()).setPostId(1L));\n        userPostMapper.insert(new UserPostDO().setUserId(dbUser.getId()).setPostId(2L));\n        // 准备参数\n        UserSaveReqVO reqVO = randomPojo(UserSaveReqVO.class, o -> {\n            o.setId(dbUser.getId());\n            o.setSex(RandomUtil.randomEle(SexEnum.values()).getSex());\n            o.setMobile(randomString());\n            o.setPostIds(asSet(2L, 3L));\n        });\n        // mock deptService 的方法\n        DeptDO dept = randomPojo(DeptDO.class, o -> {\n            o.setId(reqVO.getDeptId());\n            o.setStatus(CommonStatusEnum.ENABLE.getStatus());\n        });\n        when(deptService.getDept(eq(dept.getId()))).thenReturn(dept);\n        // mock postService 的方法\n        List<PostDO> posts = CollectionUtils.convertList(reqVO.getPostIds(), postId ->\n                randomPojo(PostDO.class, o -> {\n                    o.setId(postId);\n                    o.setStatus(CommonStatusEnum.ENABLE.getStatus());\n                }));\n        when(postService.getPostList(eq(reqVO.getPostIds()), isNull())).thenReturn(posts);\n\n        // 调用\n        userService.updateUser(reqVO);\n        // 断言\n        AdminUserDO user = userMapper.selectById(reqVO.getId());\n        assertPojoEquals(reqVO, user, \"password\");\n        // 断言关联岗位\n        List<UserPostDO> userPosts = userPostMapper.selectListByUserId(user.getId());\n        assertEquals(2L, userPosts.get(0).getPostId());\n        assertEquals(3L, userPosts.get(1).getPostId());\n    }\n\n    @Test\n    public void testUpdateUserLogin() {\n        // mock 数据\n        AdminUserDO user = randomAdminUserDO(o -> o.setLoginDate(null));\n        userMapper.insert(user);\n        // 准备参数\n        Long id = user.getId();\n        String loginIp = randomString();\n\n        // 调用\n        userService.updateUserLogin(id, loginIp);\n        // 断言\n        AdminUserDO dbUser = userMapper.selectById(id);\n        assertEquals(loginIp, dbUser.getLoginIp());\n        assertNotNull(dbUser.getLoginDate());\n    }\n\n    @Test\n    public void testUpdateUserProfile_success() {\n        // mock 数据\n        AdminUserDO dbUser = randomAdminUserDO();\n        userMapper.insert(dbUser);\n        // 准备参数\n        Long userId = dbUser.getId();\n        UserProfileUpdateReqVO reqVO = randomPojo(UserProfileUpdateReqVO.class, o -> {\n            o.setMobile(randomString());\n            o.setSex(RandomUtil.randomEle(SexEnum.values()).getSex());\n        });\n\n        // 调用\n        userService.updateUserProfile(userId, reqVO);\n        // 断言\n        AdminUserDO user = userMapper.selectById(userId);\n        assertPojoEquals(reqVO, user);\n    }\n\n    @Test\n    public void testUpdateUserPassword_success() {\n        // mock 数据\n        AdminUserDO dbUser = randomAdminUserDO(o -> o.setPassword(\"encode:tudou\"));\n        userMapper.insert(dbUser);\n        // 准备参数\n        Long userId = dbUser.getId();\n        UserProfileUpdatePasswordReqVO reqVO = randomPojo(UserProfileUpdatePasswordReqVO.class, o -> {\n            o.setOldPassword(\"tudou\");\n            o.setNewPassword(\"yuanma\");\n        });\n        // mock 方法\n        when(passwordEncoder.encode(anyString())).then(\n                (Answer<String>) invocationOnMock -> \"encode:\" + invocationOnMock.getArgument(0));\n        when(passwordEncoder.matches(eq(reqVO.getOldPassword()), eq(dbUser.getPassword()))).thenReturn(true);\n\n        // 调用\n        userService.updateUserPassword(userId, reqVO);\n        // 断言\n        AdminUserDO user = userMapper.selectById(userId);\n        assertEquals(\"encode:yuanma\", user.getPassword());\n    }\n\n    @Test\n    public void testUpdateUserAvatar_success() throws Exception {\n        // mock 数据\n        AdminUserDO dbUser = randomAdminUserDO();\n        userMapper.insert(dbUser);\n        // 准备参数\n        Long userId = dbUser.getId();\n        byte[] avatarFileBytes = randomBytes(10);\n        ByteArrayInputStream avatarFile = new ByteArrayInputStream(avatarFileBytes);\n        // mock 方法\n        String avatar = randomString();\n        when(fileApi.createFile(eq( avatarFileBytes))).thenReturn(avatar);\n\n        // 调用\n        userService.updateUserAvatar(userId, avatarFile);\n        // 断言\n        AdminUserDO user = userMapper.selectById(userId);\n        assertEquals(avatar, user.getAvatar());\n    }\n\n    @Test\n    public void testUpdateUserPassword02_success() {\n        // mock 数据\n        AdminUserDO dbUser = randomAdminUserDO();\n        userMapper.insert(dbUser);\n        // 准备参数\n        Long userId = dbUser.getId();\n        String password = \"yshop\";\n        // mock 方法\n        when(passwordEncoder.encode(anyString())).then(\n                (Answer<String>) invocationOnMock -> \"encode:\" + invocationOnMock.getArgument(0));\n\n        // 调用\n        userService.updateUserPassword(userId, password);\n        // 断言\n        AdminUserDO user = userMapper.selectById(userId);\n        assertEquals(\"encode:\" + password, user.getPassword());\n    }\n\n    @Test\n    public void testUpdateUserStatus() {\n        // mock 数据\n        AdminUserDO dbUser = randomAdminUserDO();\n        userMapper.insert(dbUser);\n        // 准备参数\n        Long userId = dbUser.getId();\n        Integer status = randomCommonStatus();\n\n        // 调用\n        userService.updateUserStatus(userId, status);\n        // 断言\n        AdminUserDO user = userMapper.selectById(userId);\n        assertEquals(status, user.getStatus());\n    }\n\n    @Test\n    public void testDeleteUser_success(){\n        // mock 数据\n        AdminUserDO dbUser = randomAdminUserDO();\n        userMapper.insert(dbUser);\n        // 准备参数\n        Long userId = dbUser.getId();\n\n        // 调用数据\n        userService.deleteUser(userId);\n        // 校验结果\n        assertNull(userMapper.selectById(userId));\n        // 校验调用次数\n        verify(permissionService, times(1)).processUserDeleted(eq(userId));\n    }\n\n    @Test\n    public void testGetUserByUsername() {\n        // mock 数据\n        AdminUserDO dbUser = randomAdminUserDO();\n        userMapper.insert(dbUser);\n        // 准备参数\n        String username = dbUser.getUsername();\n\n        // 调用\n        AdminUserDO user = userService.getUserByUsername(username);\n        // 断言\n        assertPojoEquals(dbUser, user);\n    }\n\n    @Test\n    public void testGetUserByMobile() {\n        // mock 数据\n        AdminUserDO dbUser = randomAdminUserDO();\n        userMapper.insert(dbUser);\n        // 准备参数\n        String mobile = dbUser.getMobile();\n\n        // 调用\n        AdminUserDO user = userService.getUserByMobile(mobile);\n        // 断言\n        assertPojoEquals(dbUser, user);\n    }\n\n    @Test\n    public void testGetUserPage() {\n        // mock 数据\n        AdminUserDO dbUser = initGetUserPageData();\n        // 准备参数\n        UserPageReqVO reqVO = new UserPageReqVO();\n        reqVO.setUsername(\"tu\");\n        reqVO.setMobile(\"1560\");\n        reqVO.setStatus(CommonStatusEnum.ENABLE.getStatus());\n        reqVO.setCreateTime(buildBetweenTime(2020, 12, 1, 2020, 12, 24));\n        reqVO.setDeptId(1L); // 其中，1L 是 2L 的父部门\n        // mock 方法\n        List<DeptDO> deptList = newArrayList(randomPojo(DeptDO.class, o -> o.setId(2L)));\n        when(deptService.getChildDeptList(eq(reqVO.getDeptId()))).thenReturn(deptList);\n\n        // 调用\n        PageResult<AdminUserDO> pageResult = userService.getUserPage(reqVO);\n        // 断言\n        assertEquals(1, pageResult.getTotal());\n        assertEquals(1, pageResult.getList().size());\n        assertPojoEquals(dbUser, pageResult.getList().get(0));\n    }\n\n    /**\n     * 初始化 getUserPage 方法的测试数据\n     */\n    private AdminUserDO initGetUserPageData() {\n        // mock 数据\n        AdminUserDO dbUser = randomAdminUserDO(o -> { // 等会查询到\n            o.setUsername(\"tudou\");\n            o.setMobile(\"15601691300\");\n            o.setStatus(CommonStatusEnum.ENABLE.getStatus());\n            o.setCreateTime(buildTime(2020, 12, 12));\n            o.setDeptId(2L);\n        });\n        userMapper.insert(dbUser);\n        // 测试 username 不匹配\n        userMapper.insert(cloneIgnoreId(dbUser, o -> o.setUsername(\"dou\")));\n        // 测试 mobile 不匹配\n        userMapper.insert(cloneIgnoreId(dbUser, o -> o.setMobile(\"18818260888\")));\n        // 测试 status 不匹配\n        userMapper.insert(cloneIgnoreId(dbUser, o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus())));\n        // 测试 createTime 不匹配\n        userMapper.insert(cloneIgnoreId(dbUser, o -> o.setCreateTime(buildTime(2020, 11, 11))));\n        // 测试 dept 不匹配\n        userMapper.insert(cloneIgnoreId(dbUser, o -> o.setDeptId(0L)));\n        return dbUser;\n    }\n\n    @Test\n    public void testGetUser() {\n        // mock 数据\n        AdminUserDO dbUser = randomAdminUserDO();\n        userMapper.insert(dbUser);\n        // 准备参数\n        Long userId = dbUser.getId();\n\n        // 调用\n        AdminUserDO user = userService.getUser(userId);\n        // 断言\n        assertPojoEquals(dbUser, user);\n    }\n\n    @Test\n    public void testGetUserListByDeptIds() {\n        // mock 数据\n        AdminUserDO dbUser = randomAdminUserDO(o -> o.setDeptId(1L));\n        userMapper.insert(dbUser);\n        // 测试 deptId 不匹配\n        userMapper.insert(cloneIgnoreId(dbUser, o -> o.setDeptId(2L)));\n        // 准备参数\n        Collection<Long> deptIds = singleton(1L);\n\n        // 调用\n        List<AdminUserDO> list = userService.getUserListByDeptIds(deptIds);\n        // 断言\n        assertEquals(1, list.size());\n        assertEquals(dbUser, list.get(0));\n    }\n\n    /**\n     * 情况一，校验不通过，导致插入失败\n     */\n    @Test\n    public void testImportUserList_01() {\n        // 准备参数\n        UserImportExcelVO importUser = randomPojo(UserImportExcelVO.class, o -> {\n        });\n        // mock 方法，模拟失败\n        doThrow(new ServiceException(DEPT_NOT_FOUND)).when(deptService).validateDeptList(any());\n\n        // 调用\n        UserImportRespVO respVO = userService.importUserList(newArrayList(importUser), true);\n        // 断言\n        assertEquals(0, respVO.getCreateUsernames().size());\n        assertEquals(0, respVO.getUpdateUsernames().size());\n        assertEquals(1, respVO.getFailureUsernames().size());\n        assertEquals(DEPT_NOT_FOUND.getMsg(), respVO.getFailureUsernames().get(importUser.getUsername()));\n    }\n\n    /**\n     * 情况二，不存在，进行插入\n     */\n    @Test\n    public void testImportUserList_02() {\n        // 准备参数\n        UserImportExcelVO importUser = randomPojo(UserImportExcelVO.class, o -> {\n            o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围\n            o.setSex(randomEle(SexEnum.values()).getSex()); // 保证 sex 的范围\n        });\n        // mock deptService 的方法\n        DeptDO dept = randomPojo(DeptDO.class, o -> {\n            o.setId(importUser.getDeptId());\n            o.setStatus(CommonStatusEnum.ENABLE.getStatus());\n        });\n        when(deptService.getDept(eq(dept.getId()))).thenReturn(dept);\n        // mock passwordEncoder 的方法\n        when(passwordEncoder.encode(eq(\"yshopyuanma\"))).thenReturn(\"java\");\n\n        // 调用\n        UserImportRespVO respVO = userService.importUserList(newArrayList(importUser), true);\n        // 断言\n        assertEquals(1, respVO.getCreateUsernames().size());\n        AdminUserDO user = userMapper.selectByUsername(respVO.getCreateUsernames().get(0));\n        assertPojoEquals(importUser, user);\n        assertEquals(\"java\", user.getPassword());\n        assertEquals(0, respVO.getUpdateUsernames().size());\n        assertEquals(0, respVO.getFailureUsernames().size());\n    }\n\n    /**\n     * 情况三，存在，但是不强制更新\n     */\n    @Test\n    public void testImportUserList_03() {\n        // mock 数据\n        AdminUserDO dbUser = randomAdminUserDO();\n        userMapper.insert(dbUser);\n        // 准备参数\n        UserImportExcelVO importUser = randomPojo(UserImportExcelVO.class, o -> {\n            o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围\n            o.setSex(randomEle(SexEnum.values()).getSex()); // 保证 sex 的范围\n            o.setUsername(dbUser.getUsername());\n        });\n        // mock deptService 的方法\n        DeptDO dept = randomPojo(DeptDO.class, o -> {\n            o.setId(importUser.getDeptId());\n            o.setStatus(CommonStatusEnum.ENABLE.getStatus());\n        });\n        when(deptService.getDept(eq(dept.getId()))).thenReturn(dept);\n\n        // 调用\n        UserImportRespVO respVO = userService.importUserList(newArrayList(importUser), false);\n        // 断言\n        assertEquals(0, respVO.getCreateUsernames().size());\n        assertEquals(0, respVO.getUpdateUsernames().size());\n        assertEquals(1, respVO.getFailureUsernames().size());\n        assertEquals(USER_USERNAME_EXISTS.getMsg(), respVO.getFailureUsernames().get(importUser.getUsername()));\n    }\n\n    /**\n     * 情况四，存在，强制更新\n     */\n    @Test\n    public void testImportUserList_04() {\n        // mock 数据\n        AdminUserDO dbUser = randomAdminUserDO();\n        userMapper.insert(dbUser);\n        // 准备参数\n        UserImportExcelVO importUser = randomPojo(UserImportExcelVO.class, o -> {\n            o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围\n            o.setSex(randomEle(SexEnum.values()).getSex()); // 保证 sex 的范围\n            o.setUsername(dbUser.getUsername());\n        });\n        // mock deptService 的方法\n        DeptDO dept = randomPojo(DeptDO.class, o -> {\n            o.setId(importUser.getDeptId());\n            o.setStatus(CommonStatusEnum.ENABLE.getStatus());\n        });\n        when(deptService.getDept(eq(dept.getId()))).thenReturn(dept);\n\n        // 调用\n        UserImportRespVO respVO = userService.importUserList(newArrayList(importUser), true);\n        // 断言\n        assertEquals(0, respVO.getCreateUsernames().size());\n        assertEquals(1, respVO.getUpdateUsernames().size());\n        AdminUserDO user = userMapper.selectByUsername(respVO.getUpdateUsernames().get(0));\n        assertPojoEquals(importUser, user);\n        assertEquals(0, respVO.getFailureUsernames().size());\n    }\n\n    @Test\n    public void testValidateUserExists_notExists() {\n        assertServiceException(() -> userService.validateUserExists(randomLongId()), USER_NOT_EXISTS);\n    }\n\n    @Test\n    public void testValidateUsernameUnique_usernameExistsForCreate() {\n        // 准备参数\n        String username = randomString();\n        // mock 数据\n        userMapper.insert(randomAdminUserDO(o -> o.setUsername(username)));\n\n        // 调用，校验异常\n        assertServiceException(() -> userService.validateUsernameUnique(null, username),\n                USER_USERNAME_EXISTS);\n    }\n\n    @Test\n    public void testValidateUsernameUnique_usernameExistsForUpdate() {\n        // 准备参数\n        Long id = randomLongId();\n        String username = randomString();\n        // mock 数据\n        userMapper.insert(randomAdminUserDO(o -> o.setUsername(username)));\n\n        // 调用，校验异常\n        assertServiceException(() -> userService.validateUsernameUnique(id, username),\n                USER_USERNAME_EXISTS);\n    }\n\n    @Test\n    public void testValidateEmailUnique_emailExistsForCreate() {\n        // 准备参数\n        String email = randomString();\n        // mock 数据\n        userMapper.insert(randomAdminUserDO(o -> o.setEmail(email)));\n\n        // 调用，校验异常\n        assertServiceException(() -> userService.validateEmailUnique(null, email),\n                USER_EMAIL_EXISTS);\n    }\n\n    @Test\n    public void testValidateEmailUnique_emailExistsForUpdate() {\n        // 准备参数\n        Long id = randomLongId();\n        String email = randomString();\n        // mock 数据\n        userMapper.insert(randomAdminUserDO(o -> o.setEmail(email)));\n\n        // 调用，校验异常\n        assertServiceException(() -> userService.validateEmailUnique(id, email),\n                USER_EMAIL_EXISTS);\n    }\n\n    @Test\n    public void testValidateMobileUnique_mobileExistsForCreate() {\n        // 准备参数\n        String mobile = randomString();\n        // mock 数据\n        userMapper.insert(randomAdminUserDO(o -> o.setMobile(mobile)));\n\n        // 调用，校验异常\n        assertServiceException(() -> userService.validateMobileUnique(null, mobile),\n                USER_MOBILE_EXISTS);\n    }\n\n    @Test\n    public void testValidateMobileUnique_mobileExistsForUpdate() {\n        // 准备参数\n        Long id = randomLongId();\n        String mobile = randomString();\n        // mock 数据\n        userMapper.insert(randomAdminUserDO(o -> o.setMobile(mobile)));\n\n        // 调用，校验异常\n        assertServiceException(() -> userService.validateMobileUnique(id, mobile),\n                USER_MOBILE_EXISTS);\n    }\n\n    @Test\n    public void testValidateOldPassword_notExists() {\n        assertServiceException(() -> userService.validateOldPassword(randomLongId(), randomString()),\n                USER_NOT_EXISTS);\n    }\n\n    @Test\n    public void testValidateOldPassword_passwordFailed() {\n        // mock 数据\n        AdminUserDO user = randomAdminUserDO();\n        userMapper.insert(user);\n        // 准备参数\n        Long id = user.getId();\n        String oldPassword = user.getPassword();\n\n        // 调用，校验异常\n        assertServiceException(() -> userService.validateOldPassword(id, oldPassword),\n                USER_PASSWORD_FAILED);\n        // 校验调用\n        verify(passwordEncoder, times(1)).matches(eq(oldPassword), eq(user.getPassword()));\n    }\n\n    @Test\n    public void testUserListByPostIds() {\n        // 准备参数\n        Collection<Long> postIds = asSet(10L, 20L);\n        // mock user1 数据\n        AdminUserDO user1 = randomAdminUserDO(o -> o.setPostIds(asSet(10L, 30L)));\n        userMapper.insert(user1);\n        userPostMapper.insert(new UserPostDO().setUserId(user1.getId()).setPostId(10L));\n        userPostMapper.insert(new UserPostDO().setUserId(user1.getId()).setPostId(30L));\n        // mock user2 数据\n        AdminUserDO user2 = randomAdminUserDO(o -> o.setPostIds(singleton(100L)));\n        userMapper.insert(user2);\n        userPostMapper.insert(new UserPostDO().setUserId(user2.getId()).setPostId(100L));\n\n        // 调用\n        List<AdminUserDO> result = userService.getUserListByPostIds(postIds);\n        // 断言\n        assertEquals(1, result.size());\n        assertEquals(user1, result.get(0));\n    }\n\n    @Test\n    public void testGetUserList() {\n        // mock 数据\n        AdminUserDO user = randomAdminUserDO();\n        userMapper.insert(user);\n        // 测试 id 不匹配\n        userMapper.insert(randomAdminUserDO());\n        // 准备参数\n        Collection<Long> ids = singleton(user.getId());\n\n        // 调用\n        List<AdminUserDO> result = userService.getUserList(ids);\n        // 断言\n        assertEquals(1, result.size());\n        assertEquals(user, result.get(0));\n    }\n\n    @Test\n    public void testGetUserMap() {\n        // mock 数据\n        AdminUserDO user = randomAdminUserDO();\n        userMapper.insert(user);\n        // 测试 id 不匹配\n        userMapper.insert(randomAdminUserDO());\n        // 准备参数\n        Collection<Long> ids = singleton(user.getId());\n\n        // 调用\n        Map<Long, AdminUserDO> result = userService.getUserMap(ids);\n        // 断言\n        assertEquals(1, result.size());\n        assertEquals(user, result.get(user.getId()));\n    }\n\n    @Test\n    public void testGetUserListByNickname() {\n        // mock 数据\n        AdminUserDO user = randomAdminUserDO(o -> o.setNickname(\"芋头\"));\n        userMapper.insert(user);\n        // 测试 nickname 不匹配\n        userMapper.insert(randomAdminUserDO(o -> o.setNickname(\"源码\")));\n        // 准备参数\n        String nickname = \"芋\";\n\n        // 调用\n        List<AdminUserDO> result = userService.getUserListByNickname(nickname);\n        // 断言\n        assertEquals(1, result.size());\n        assertEquals(user, result.get(0));\n    }\n\n    @Test\n    public void testGetUserListByStatus() {\n        // mock 数据\n        AdminUserDO user = randomAdminUserDO(o -> o.setStatus(CommonStatusEnum.DISABLE.getStatus()));\n        userMapper.insert(user);\n        // 测试 status 不匹配\n        userMapper.insert(randomAdminUserDO(o -> o.setStatus(CommonStatusEnum.ENABLE.getStatus())));\n        // 准备参数\n        Integer status = CommonStatusEnum.DISABLE.getStatus();\n\n        // 调用\n        List<AdminUserDO> result = userService.getUserListByStatus(status);\n        // 断言\n        assertEquals(1, result.size());\n        assertEquals(user, result.get(0));\n    }\n\n    @Test\n    public void testValidateUserList_success() {\n        // mock 数据\n        AdminUserDO userDO = randomAdminUserDO().setStatus(CommonStatusEnum.ENABLE.getStatus());\n        userMapper.insert(userDO);\n        // 准备参数\n        List<Long> ids = singletonList(userDO.getId());\n\n        // 调用，无需断言\n        userService.validateUserList(ids);\n    }\n\n    @Test\n    public void testValidateUserList_notFound() {\n        // 准备参数\n        List<Long> ids = singletonList(randomLongId());\n\n        // 调用, 并断言异常\n        assertServiceException(() -> userService.validateUserList(ids), USER_NOT_EXISTS);\n    }\n\n    @Test\n    public void testValidateUserList_notEnable() {\n        // mock 数据\n        AdminUserDO userDO = randomAdminUserDO().setStatus(CommonStatusEnum.DISABLE.getStatus());\n        userMapper.insert(userDO);\n        // 准备参数\n        List<Long> ids = singletonList(userDO.getId());\n\n        // 调用, 并断言异常\n        assertServiceException(() -> userService.validateUserList(ids), USER_IS_DISABLE,\n                userDO.getNickname());\n    }\n\n    // ========== 随机对象 ==========\n\n    @SafeVarargs\n    private static AdminUserDO randomAdminUserDO(Consumer<AdminUserDO>... consumers) {\n        Consumer<AdminUserDO> consumer = (o) -> {\n            o.setStatus(randomEle(CommonStatusEnum.values()).getStatus()); // 保证 status 的范围\n            o.setSex(randomEle(SexEnum.values()).getSex()); // 保证 sex 的范围\n        };\n        return randomPojo(AdminUserDO.class, ArrayUtils.append(consumer, consumers));\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/resources/application-unit-test.yaml",
    "content": "spring:\n  main:\n    lazy-initialization: true # 开启懒加载，加快速度\n    banner-mode: off # 单元测试，禁用 Banner\n\n--- #################### 数据库相关配置 ####################\n\nspring:\n  # 数据源配置项\n  datasource:\n    name: yixiang-drink\n    url: jdbc:h2:mem:testdb;MODE=MYSQL;DATABASE_TO_UPPER=false;NON_KEYWORDS=value; # MODE 使用 MySQL 模式；DATABASE_TO_UPPER 配置表和字段使用小写\n    driver-class-name: org.h2.Driver\n    username: sa\n    password:\n    druid:\n      async-init: true # 单元测试，异步初始化 Druid 连接池，提升启动速度\n      initial-size: 1 # 单元测试，配置为 1，提升启动速度\n  sql:\n    init:\n      schema-locations: classpath:/sql/create_tables.sql\n\n  # Redis 配置。Redisson 默认的配置足够使用，一般不需要进行调优\n  data:\n    redis:\n      host: 127.0.0.1 # 地址\n      port: 16379 # 端口（单元测试，使用 16379 端口）\n      database: 0 # 数据库索引\n\nmybatis:\n  lazy-initialization: true # 单元测试，设置 MyBatis Mapper 延迟加载，加速每个单元测试\n\n--- #################### 定时任务相关配置 ####################\n\n--- #################### 配置中心相关配置 ####################\n\n--- #################### 服务保障相关配置 ####################\n\n# Lock4j 配置项（单元测试，禁用 Lock4j）\n\n--- #################### 监控相关配置 ####################\n\n--- #################### yshop相关配置 ####################\n\n# yshop配置项，设置当前项目所有自定义的配置\nyshop:\n  info:\n    base-package: co.yixiang.yshop.module\n  captcha:\n    timeout: 5m\n    width: 160\n    height: 60\n    enable: true\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/resources/logback.xml",
    "content": "<configuration>\n    <!-- 引用 Spring Boot 的 logback 基础配置 -->\n    <include resource=\"org/springframework/boot/logging/logback/defaults.xml\" />\n</configuration>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/resources/sql/clean.sql",
    "content": "DELETE FROM \"system_dept\";\nDELETE FROM \"system_dict_data\";\nDELETE FROM \"system_role\";\nDELETE FROM \"system_role_menu\";\nDELETE FROM \"system_menu\";\nDELETE FROM \"system_user_role\";\nDELETE FROM \"system_dict_type\";\nDELETE FROM \"system_user_session\";\nDELETE FROM \"system_post\";\nDELETE FROM \"system_user_post\";\nDELETE FROM \"system_notice\";\nDELETE FROM \"system_login_log\";\nDELETE FROM \"system_operate_log\";\nDELETE FROM \"system_users\";\nDELETE FROM \"system_sms_channel\";\nDELETE FROM \"system_sms_template\";\nDELETE FROM \"system_sms_log\";\nDELETE FROM \"system_sms_code\";\nDELETE FROM \"system_social_client\";\nDELETE FROM \"system_social_user\";\nDELETE FROM \"system_social_user_bind\";\nDELETE FROM \"system_tenant\";\nDELETE FROM \"system_tenant_package\";\nDELETE FROM \"system_oauth2_client\";\nDELETE FROM \"system_oauth2_approve\";\nDELETE FROM \"system_oauth2_access_token\";\nDELETE FROM \"system_oauth2_refresh_token\";\nDELETE FROM \"system_oauth2_code\";\nDELETE FROM \"system_mail_account\";\nDELETE FROM \"system_mail_template\";\nDELETE FROM \"system_mail_log\";\nDELETE FROM \"system_notify_template\";\nDELETE FROM \"system_notify_message\";\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-module-system/yshop-module-system-biz/src/test/resources/sql/create_tables.sql",
    "content": "CREATE TABLE IF NOT EXISTS \"system_dept\" (\n    \"id\" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,\n    \"name\" varchar(30) NOT NULL DEFAULT '',\n    \"parent_id\" bigint NOT NULL DEFAULT '0',\n    \"sort\" int NOT NULL DEFAULT '0',\n    \"leader_user_id\" bigint DEFAULT NULL,\n    \"phone\" varchar(11) DEFAULT NULL,\n    \"email\" varchar(50) DEFAULT NULL,\n    \"status\" tinyint NOT NULL,\n    \"creator\" varchar(64) DEFAULT '',\n    \"create_time\" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    \"updater\" varchar(64) DEFAULT '',\n    \"update_time\" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    \"deleted\" bit NOT NULL DEFAULT FALSE,\n    \"tenant_id\" bigint not null default  '0',\n    PRIMARY KEY (\"id\")\n) COMMENT '部门表';\n\nCREATE TABLE IF NOT EXISTS \"system_dict_data\" (\n    \"id\" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,\n    \"sort\" int NOT NULL DEFAULT '0',\n    \"label\" varchar(100) NOT NULL DEFAULT '',\n    \"value\" varchar(100) NOT NULL DEFAULT '',\n    \"dict_type\" varchar(100) NOT NULL DEFAULT '',\n    \"status\" tinyint NOT NULL DEFAULT '0',\n    \"color_type\" varchar(100) NOT NULL DEFAULT '',\n    \"css_class\" varchar(100) NOT NULL DEFAULT '',\n    \"remark\" varchar(500) DEFAULT NULL,\n    \"creator\" varchar(64) DEFAULT '',\n    \"create_time\" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    \"updater\" varchar(64) DEFAULT '',\n    \"update_time\" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    \"deleted\" bit NOT NULL DEFAULT FALSE,\n    PRIMARY KEY (\"id\")\n) COMMENT '字典数据表';\n\nCREATE TABLE IF NOT EXISTS \"system_role\" (\n    \"id\" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,\n    \"name\" varchar(30) NOT NULL,\n    \"code\" varchar(100) NOT NULL,\n    \"sort\" int NOT NULL,\n    \"data_scope\" tinyint NOT NULL DEFAULT '1',\n    \"data_scope_dept_ids\" varchar(500) NOT NULL DEFAULT '',\n    \"status\" tinyint NOT NULL,\n    \"type\" tinyint NOT NULL,\n    \"remark\" varchar(500) DEFAULT NULL,\n    \"creator\" varchar(64) DEFAULT '',\n    \"create_time\" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    \"updater\" varchar(64) DEFAULT '',\n    \"update_time\" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    \"deleted\" bit NOT NULL DEFAULT FALSE,\n    \"tenant_id\" bigint not null default  '0',\n    PRIMARY KEY (\"id\")\n) COMMENT '角色信息表';\n\nCREATE TABLE IF NOT EXISTS \"system_role_menu\" (\n    \"id\" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,\n    \"role_id\" bigint NOT NULL,\n    \"menu_id\" bigint NOT NULL,\n    \"creator\" varchar(64) DEFAULT '',\n    \"create_time\" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    \"updater\" varchar(64) DEFAULT '',\n    \"update_time\" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    \"deleted\" bit NOT NULL DEFAULT FALSE,\n    \"tenant_id\" bigint not null default  '0',\n    PRIMARY KEY (\"id\")\n) COMMENT '角色和菜单关联表';\n\nCREATE TABLE IF NOT EXISTS \"system_menu\" (\n    \"id\" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,\n    \"name\" varchar(50) NOT NULL,\n    \"permission\" varchar(100) NOT NULL DEFAULT '',\n    \"type\" tinyint NOT NULL,\n    \"sort\" int NOT NULL DEFAULT '0',\n    \"parent_id\" bigint NOT NULL DEFAULT '0',\n    \"path\" varchar(200) DEFAULT '',\n    \"icon\" varchar(100) DEFAULT '#',\n    \"component\" varchar(255) DEFAULT NULL,\n    \"component_name\" varchar(255) DEFAULT NULL,\n    \"status\" tinyint NOT NULL DEFAULT '0',\n    \"visible\" bit NOT NULL DEFAULT TRUE,\n    \"keep_alive\" bit NOT NULL DEFAULT TRUE,\n    \"always_show\" bit NOT NULL DEFAULT TRUE,\n    \"creator\" varchar(64) DEFAULT '',\n    \"create_time\" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    \"updater\" varchar(64) DEFAULT '',\n    \"update_time\" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    \"deleted\" bit NOT NULL DEFAULT FALSE,\n    PRIMARY KEY (\"id\")\n) COMMENT '菜单权限表';\n\nCREATE TABLE IF NOT EXISTS \"system_user_role\" (\n     \"id\" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,\n     \"user_id\" bigint NOT NULL,\n     \"role_id\" bigint NOT NULL,\n     \"creator\" varchar(64) DEFAULT '',\n     \"create_time\" timestamp DEFAULT NULL,\n     \"updater\" varchar(64) DEFAULT '',\n     \"update_time\" timestamp DEFAULT NULL,\n     \"deleted\" bit DEFAULT FALSE,\n    \"tenant_id\" bigint not null default  '0',\n    PRIMARY KEY (\"id\")\n) COMMENT '用户和角色关联表';\n\nCREATE TABLE IF NOT EXISTS \"system_dict_type\" (\n    \"id\" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,\n    \"name\" varchar(100) NOT NULL DEFAULT '',\n    \"type\" varchar(100) NOT NULL DEFAULT '',\n    \"status\" tinyint NOT NULL DEFAULT '0',\n    \"remark\" varchar(500) DEFAULT NULL,\n    \"creator\" varchar(64) DEFAULT '',\n    \"create_time\" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    \"updater\" varchar(64) DEFAULT '',\n    \"update_time\" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    \"deleted\" bit NOT NULL DEFAULT FALSE,\n    \"deleted_time\" timestamp NOT NULL,\n    PRIMARY KEY (\"id\")\n) COMMENT '字典类型表';\n\nCREATE TABLE IF NOT EXISTS `system_user_session` (\n    \"id\" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,\n    `token` varchar(32) NOT NULL,\n    `user_id` bigint DEFAULT NULL,\n    \"user_type\" tinyint NOT NULL,\n    `username` varchar(50) NOT NULL DEFAULT '',\n    `user_ip` varchar(50) DEFAULT NULL,\n    `user_agent` varchar(512) DEFAULT NULL,\n    `session_timeout` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    \"creator\" varchar(64) DEFAULT '',\n    \"create_time\" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    `updater` varchar(64) DEFAULT '' ,\n    \"update_time\" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    \"deleted\" bit NOT NULL DEFAULT FALSE,\n    \"tenant_id\" bigint not null default  '0',\n    PRIMARY KEY (`id`)\n) COMMENT '用户在线 Session';\n\nCREATE TABLE IF NOT EXISTS \"system_post\" (\n    \"id\"          bigint      NOT NULL GENERATED BY DEFAULT AS IDENTITY,\n    \"code\"        varchar(64) NOT NULL,\n    \"name\"        varchar(50) NOT NULL,\n    \"sort\"        integer     NOT NULL,\n    \"status\"      tinyint     NOT NULL,\n    \"remark\"      varchar(500)         DEFAULT NULL,\n    \"creator\"     varchar(64)          DEFAULT '',\n    \"create_time\" timestamp   NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    \"updater\"     varchar(64)          DEFAULT '',\n    \"update_time\" timestamp   NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    \"deleted\"     bit         NOT NULL DEFAULT FALSE,\n    \"tenant_id\" bigint not null default  '0',\n    PRIMARY KEY (\"id\")\n) COMMENT '岗位信息表';\n\nCREATE TABLE IF NOT EXISTS `system_user_post`(\n    \"id\"          bigint    NOT NULL GENERATED BY DEFAULT AS IDENTITY,\n    \"user_id\"     bigint             DEFAULT NULL,\n    \"post_id\"     bigint             DEFAULT NULL,\n    \"creator\"     varchar(64)        DEFAULT '',\n    \"create_time\" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    \"updater\"     varchar(64)        DEFAULT '',\n    \"update_time\" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    \"deleted\"     bit       NOT NULL DEFAULT FALSE,\n    \"tenant_id\"   bigint    not null default '0',\n    PRIMARY KEY (`id`)\n) COMMENT ='用户岗位表';\n\nCREATE TABLE IF NOT EXISTS \"system_notice\" (\n\t\"id\" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,\n\t\"title\" varchar(50) NOT NULL COMMENT '公告标题',\n\t\"content\" text NOT NULL COMMENT '公告内容',\n\t\"type\" tinyint NOT NULL COMMENT '公告类型（1通知 2公告）',\n\t\"status\" tinyint NOT NULL DEFAULT '0' COMMENT '公告状态（0正常 1关闭）',\n\t\"creator\" varchar(64) DEFAULT '' COMMENT '创建者',\n\t\"create_time\" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',\n\t\"updater\" varchar(64) DEFAULT '' COMMENT '更新者',\n\t\"update_time\" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',\n\t\"deleted\" bit NOT NULL DEFAULT 0 COMMENT '是否删除',\n    \"tenant_id\" bigint not null default  '0',\n    PRIMARY KEY(\"id\")\n) COMMENT '通知公告表';\n\nCREATE TABLE IF NOT EXISTS `system_login_log` (\n    `id`          bigint(20)   NOT NULL GENERATED BY DEFAULT AS IDENTITY,\n    `log_type`    bigint(4)    NOT NULL,\n    \"user_id\" bigint not null default '0',\n    \"user_type\" tinyint NOT NULL,\n    `trace_id`    varchar(64)  NOT NULL DEFAULT '',\n    `username`    varchar(50)  NOT NULL DEFAULT '',\n    `result`      tinyint(4)   NOT NULL,\n    `user_ip`     varchar(50)  NOT NULL,\n    `user_agent`  varchar(512) NOT NULL,\n    `creator`   varchar(64)           DEFAULT '',\n    `create_time` datetime     NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    `updater`   varchar(64)           DEFAULT '',\n    `update_time` datetime     NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n    `deleted`     bit(1)       NOT NULL DEFAULT '0',\n    PRIMARY KEY (`id`)\n) COMMENT ='系统访问记录';\n\nCREATE TABLE IF NOT EXISTS `system_operate_log` (\n    `id`               bigint(20)    NOT NULL GENERATED BY DEFAULT AS IDENTITY,\n    `trace_id`         varchar(64)   NOT NULL DEFAULT '',\n    `user_id`          bigint(20)    NOT NULL,\n    \"user_type\" tinyint not null default '0',\n    `type`           varchar(50)   NOT NULL,\n    `sub_type`             varchar(50)   NOT NULL,\n    `biz_id`          bigint(20)    NOT NULL,\n    `action`          varchar(2000) NOT NULL DEFAULT '',\n    `extra`             varchar(512)  NOT NULL DEFAULT '',\n    `request_method`   varchar(16)            DEFAULT '',\n    `request_url`      varchar(255)           DEFAULT '',\n    `user_ip`          varchar(50)            DEFAULT NULL,\n    `user_agent`       varchar(200)           DEFAULT NULL,\n    `creator`        varchar(64)            DEFAULT '',\n    `create_time`      datetime      NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    `updater`        varchar(64)            DEFAULT '',\n    `update_time`      datetime      NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n    `deleted`          bit(1)        NOT NULL DEFAULT '0',\n    \"tenant_id\"         bigint not null default  '0',\n    PRIMARY KEY (`id`)\n) COMMENT ='操作日志记录';\n\nCREATE TABLE IF NOT EXISTS \"system_users\" (\n    \"id\" bigint not null GENERATED BY DEFAULT AS IDENTITY,\n    \"username\" varchar(30) not null,\n    \"password\" varchar(100) not null default '',\n    \"nickname\" varchar(30) not null,\n    \"remark\" varchar(500) default null,\n    \"dept_id\" bigint default null,\n    \"post_ids\" varchar(255) default null,\n    \"email\" varchar(50) default '',\n    \"mobile\" varchar(11) default '',\n    \"sex\" tinyint default '0',\n    \"avatar\" varchar(100) default '',\n    \"status\" tinyint not null default '0',\n    \"login_ip\" varchar(50) default '',\n    \"login_date\" timestamp default null,\n    \"creator\" varchar(64) default '',\n    \"create_time\" timestamp not null default current_timestamp,\n    \"updater\" varchar(64) default '',\n    \"update_time\" timestamp not null default current_timestamp,\n    \"deleted\" bit not null default false,\n    \"tenant_id\" bigint not null default  '0',\n    primary key (\"id\")\n) comment '用户信息表';\n\nCREATE TABLE IF NOT EXISTS \"system_sms_channel\" (\n   \"id\" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,\n   \"signature\" varchar(10) NOT NULL,\n   \"code\" varchar(63) NOT NULL,\n   \"status\" tinyint NOT NULL,\n   \"remark\" varchar(255) DEFAULT NULL,\n   \"api_key\" varchar(63) NOT NULL,\n   \"api_secret\" varchar(63) DEFAULT NULL,\n   \"callback_url\" varchar(255) DEFAULT NULL,\n   \"creator\" varchar(64) DEFAULT '',\n   \"create_time\" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n   \"updater\" varchar(64) DEFAULT '',\n   \"update_time\" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n   \"deleted\" bit NOT NULL DEFAULT FALSE,\n   PRIMARY KEY (\"id\")\n) COMMENT '短信渠道';\n\nCREATE TABLE IF NOT EXISTS \"system_sms_template\" (\n    \"id\" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,\n    \"type\" tinyint NOT NULL,\n    \"status\" tinyint NOT NULL,\n    \"code\" varchar(63) NOT NULL,\n    \"name\" varchar(63) NOT NULL,\n    \"content\" varchar(255) NOT NULL,\n    \"params\" varchar(255) NOT NULL,\n    \"remark\" varchar(255) DEFAULT NULL,\n    \"api_template_id\" varchar(63) NOT NULL,\n    \"channel_id\" bigint NOT NULL,\n    \"channel_code\" varchar(63) NOT NULL,\n    \"creator\" varchar(64) DEFAULT '',\n    \"create_time\" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    \"updater\" varchar(64) DEFAULT '',\n    \"update_time\" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    \"deleted\" bit NOT NULL DEFAULT FALSE,\n    PRIMARY KEY (\"id\")\n) COMMENT '短信模板';\n\nCREATE TABLE IF NOT EXISTS \"system_sms_log\" (\n   \"id\" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,\n   \"channel_id\" bigint NOT NULL,\n   \"channel_code\" varchar(63) NOT NULL,\n   \"template_id\" bigint NOT NULL,\n   \"template_code\" varchar(63) NOT NULL,\n   \"template_type\" tinyint NOT NULL,\n   \"template_content\" varchar(255) NOT NULL,\n   \"template_params\" varchar(255) NOT NULL,\n   \"api_template_id\" varchar(63) NOT NULL,\n   \"mobile\" varchar(11) NOT NULL,\n   \"user_id\" bigint DEFAULT '0',\n   \"user_type\" tinyint DEFAULT '0',\n   \"send_status\" tinyint NOT NULL DEFAULT '0',\n   \"send_time\" timestamp DEFAULT NULL,\n   \"send_code\" int DEFAULT NULL,\n   \"send_msg\" varchar(255) DEFAULT NULL,\n   \"api_send_code\" varchar(63) DEFAULT NULL,\n   \"api_send_msg\" varchar(255) DEFAULT NULL,\n   \"api_request_id\" varchar(255) DEFAULT NULL,\n   \"api_serial_no\" varchar(255) DEFAULT NULL,\n   \"receive_status\" tinyint NOT NULL DEFAULT '0',\n   \"receive_time\" timestamp DEFAULT NULL,\n   \"api_receive_code\" varchar(63) DEFAULT NULL,\n   \"api_receive_msg\" varchar(255) DEFAULT NULL,\n   \"creator\" varchar(64) DEFAULT '',\n   \"create_time\" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n   \"updater\" varchar(64) DEFAULT '',\n   \"update_time\" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n   \"deleted\" bit NOT NULL DEFAULT FALSE,\n   PRIMARY KEY (\"id\")\n) COMMENT '短信日志';\n\nCREATE TABLE IF NOT EXISTS \"system_sms_code\" (\n    \"id\" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,\n    \"mobile\" varchar(11) NOT NULL,\n    \"code\" varchar(11) NOT NULL,\n    \"scene\" bigint NOT NULL,\n    \"create_ip\" varchar NOT NULL,\n    \"today_index\" int NOT NULL,\n    \"used\" bit NOT NULL DEFAULT FALSE,\n    \"used_time\" timestamp DEFAULT NULL,\n    \"used_ip\" varchar NULL,\n    \"creator\" varchar(64) DEFAULT '',\n    \"create_time\" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    \"updater\" varchar(64) DEFAULT '',\n    \"update_time\" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    \"deleted\" bit NOT NULL DEFAULT FALSE,\n    PRIMARY KEY (\"id\")\n) COMMENT '短信日志';\n\nCREATE TABLE IF NOT EXISTS \"system_social_client\" (\n  \"id\" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,\n  \"name\" varchar(255) NOT NULL,\n  \"social_type\" int NOT NULL,\n  \"user_type\" int NOT NULL,\n  \"client_id\" varchar(255) NOT NULL,\n  \"client_secret\" varchar(255) NOT NULL,\n  \"agent_id\" varchar(255) NOT NULL,\n  \"status\" int NOT NULL,\n  \"creator\" varchar(64) DEFAULT '',\n  \"create_time\" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n  \"updater\" varchar(64) DEFAULT '',\n  \"update_time\" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n  \"deleted\" bit NOT NULL DEFAULT FALSE,\n  \"tenant_id\" bigint not null default  '0',\n  PRIMARY KEY (\"id\")\n) COMMENT '社交客户端表';\n\nCREATE TABLE IF NOT EXISTS \"system_social_user\" (\n   \"id\" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,\n   \"type\" tinyint NOT NULL,\n   \"openid\" varchar(64) NOT NULL,\n   \"token\" varchar(256) DEFAULT NULL,\n   \"raw_token_info\" varchar(1024) NOT NULL,\n   \"nickname\" varchar(32) NOT NULL,\n   \"avatar\" varchar(255) DEFAULT NULL,\n   \"raw_user_info\" varchar(1024) NOT NULL,\n   \"code\" varchar(64) NOT NULL,\n   \"state\" varchar(64),\n   \"creator\" varchar(64) DEFAULT '',\n   \"create_time\" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n   \"updater\" varchar(64) DEFAULT '',\n   \"update_time\" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n   \"deleted\" bit NOT NULL DEFAULT FALSE,\n   PRIMARY KEY (\"id\")\n) COMMENT '社交用户';\n\nCREATE TABLE IF NOT EXISTS \"system_social_user_bind\" (\n   \"id\" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,\n   \"user_id\" bigint NOT NULL,\n   \"user_type\" tinyint NOT NULL,\n   \"social_type\" tinyint NOT NULL,\n   \"social_user_id\" number NOT NULL,\n   \"creator\" varchar(64) DEFAULT '',\n   \"create_time\" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n   \"updater\" varchar(64) DEFAULT '',\n   \"update_time\" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n   \"deleted\" bit NOT NULL DEFAULT FALSE,\n   PRIMARY KEY (\"id\")\n) COMMENT '社交用户的绑定';\n\nCREATE TABLE IF NOT EXISTS \"system_tenant\" (\n    \"id\" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,\n    \"name\" varchar(63) NOT NULL,\n    \"contact_user_id\" bigint NOT NULL DEFAULT '0',\n    \"contact_name\" varchar(255) NOT NULL,\n    \"contact_mobile\" varchar(255),\n    \"status\" tinyint NOT NULL,\n    \"website\" varchar(63) DEFAULT '',\n    \"package_id\"  bigint NOT NULL,\n    \"expire_time\" timestamp NOT NULL,\n    \"account_count\" int NOT NULL,\n    \"creator\" varchar(64) DEFAULT '',\n    \"create_time\" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    \"updater\" varchar(64) DEFAULT '',\n    \"update_time\" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    \"deleted\" bit NOT NULL DEFAULT FALSE,\n    PRIMARY KEY (\"id\")\n) COMMENT '租户';\n\nCREATE TABLE IF NOT EXISTS \"system_tenant_package\" (\n    \"id\" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,\n    \"name\" varchar(30) NOT NULL,\n    \"status\" tinyint NOT NULL,\n    \"remark\" varchar(256),\n    \"menu_ids\" varchar(2048) NOT NULL,\n    \"creator\" varchar(64) DEFAULT '',\n    \"create_time\" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    \"updater\" varchar(64) DEFAULT '',\n    \"update_time\" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n    \"deleted\" bit NOT NULL DEFAULT FALSE,\n    PRIMARY KEY (\"id\")\n) COMMENT '租户套餐表';\n\nCREATE TABLE IF NOT EXISTS \"system_oauth2_client\" (\n  \"id\" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,\n  \"client_id\" varchar NOT NULL,\n  \"secret\" varchar NOT NULL,\n  \"name\" varchar NOT NULL,\n  \"logo\" varchar NOT NULL,\n  \"description\" varchar,\n  \"status\" int NOT NULL,\n  \"access_token_validity_seconds\" int NOT NULL,\n  \"refresh_token_validity_seconds\" int NOT NULL,\n  \"redirect_uris\" varchar NOT NULL,\n  \"authorized_grant_types\" varchar NOT NULL,\n  \"scopes\" varchar NOT NULL DEFAULT '',\n  \"auto_approve_scopes\" varchar NOT NULL DEFAULT '',\n  \"authorities\" varchar NOT NULL DEFAULT '',\n  \"resource_ids\" varchar NOT NULL DEFAULT '',\n  \"additional_information\" varchar NOT NULL DEFAULT '',\n  \"creator\" varchar DEFAULT '',\n  \"create_time\" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n  \"updater\" varchar DEFAULT '',\n  \"update_time\" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n  \"deleted\" bit NOT NULL DEFAULT FALSE,\n  PRIMARY KEY (\"id\")\n) COMMENT 'OAuth2 客户端表';\n\nCREATE TABLE IF NOT EXISTS \"system_oauth2_approve\" (\n  \"id\" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,\n  \"user_id\" bigint NOT NULL,\n  \"user_type\" tinyint NOT NULL,\n  \"client_id\" varchar NOT NULL,\n  \"scope\" varchar NOT NULL,\n  \"approved\" bit NOT NULL DEFAULT FALSE,\n  \"expires_time\" datetime NOT NULL,\n  \"creator\" varchar DEFAULT '',\n  \"create_time\" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n  \"updater\" varchar DEFAULT '',\n  \"update_time\" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n  \"deleted\" bit NOT NULL DEFAULT FALSE,\n  PRIMARY KEY (\"id\")\n) COMMENT 'OAuth2 批准表';\n\nCREATE TABLE IF NOT EXISTS \"system_oauth2_access_token\" (\n   \"id\" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,\n   \"user_id\" bigint NOT NULL,\n   \"user_type\" tinyint NOT NULL,\n   \"user_info\" varchar NOT NULL,\n   \"access_token\" varchar NOT NULL,\n   \"refresh_token\" varchar NOT NULL,\n   \"client_id\" varchar NOT NULL,\n   \"scopes\" varchar NOT NULL,\n   \"approved\" bit NOT NULL DEFAULT FALSE,\n   \"expires_time\" datetime NOT NULL,\n   \"creator\" varchar DEFAULT '',\n   \"create_time\" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n   \"updater\" varchar DEFAULT '',\n   \"update_time\" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n   \"deleted\" bit NOT NULL DEFAULT FALSE,\n   \"tenant_id\" bigint NOT NULL,\n   PRIMARY KEY (\"id\")\n) COMMENT 'OAuth2 访问令牌';\n\nCREATE TABLE IF NOT EXISTS \"system_oauth2_refresh_token\" (\n    \"id\" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,\n    \"user_id\" bigint NOT NULL,\n    \"user_type\" tinyint NOT NULL,\n    \"refresh_token\" varchar NOT NULL,\n    \"client_id\" varchar NOT NULL,\n    \"scopes\" varchar NOT NULL,\n    \"approved\" bit NOT NULL DEFAULT FALSE,\n    \"expires_time\" datetime NOT NULL,\n    \"creator\" varchar DEFAULT '',\n    \"create_time\" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    \"updater\" varchar DEFAULT '',\n    \"update_time\" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n    \"deleted\" bit NOT NULL DEFAULT FALSE,\n    PRIMARY KEY (\"id\")\n) COMMENT 'OAuth2 刷新令牌';\n\nCREATE TABLE IF NOT EXISTS \"system_oauth2_code\" (\n     \"id\" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,\n     \"user_id\" bigint NOT NULL,\n     \"user_type\" tinyint NOT NULL,\n     \"code\" varchar NOT NULL,\n     \"client_id\" varchar NOT NULL,\n     \"scopes\" varchar NOT NULL,\n     \"expires_time\" datetime NOT NULL,\n     \"redirect_uri\" varchar NOT NULL,\n     \"state\" varchar NOT NULL,\n     \"creator\" varchar DEFAULT '',\n     \"create_time\" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n     \"updater\" varchar DEFAULT '',\n     \"update_time\" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n     \"deleted\" bit NOT NULL DEFAULT FALSE,\n     PRIMARY KEY (\"id\")\n) COMMENT 'OAuth2 刷新令牌';\n\nCREATE TABLE IF NOT EXISTS \"system_mail_account\" (\n    \"id\" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,\n    \"mail\" varchar NOT NULL,\n    \"username\" varchar NOT NULL,\n    \"password\" varchar NOT NULL,\n    \"host\" varchar NOT NULL,\n    \"port\" int NOT NULL,\n    \"ssl_enable\" bit NOT NULL,\n    \"starttls_enable\" bit NOT NULL,\n    \"creator\" varchar DEFAULT '',\n    \"create_time\" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    \"updater\" varchar DEFAULT '',\n    \"update_time\" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n    \"deleted\" bit NOT NULL DEFAULT FALSE,\n    PRIMARY KEY (\"id\")\n) COMMENT '邮箱账号表';\n\nCREATE TABLE IF NOT EXISTS \"system_mail_template\" (\n    \"id\" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,\n    \"name\" varchar NOT NULL,\n    \"code\" varchar NOT NULL,\n    \"account_id\" bigint NOT NULL,\n    \"nickname\" varchar,\n    \"title\" varchar NOT NULL,\n    \"content\" varchar NOT NULL,\n    \"params\" varchar NOT NULL,\n    \"status\" varchar NOT NULL,\n    \"remark\" varchar,\n    \"creator\" varchar DEFAULT '',\n    \"create_time\" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    \"updater\" varchar DEFAULT '',\n    \"update_time\" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n    \"deleted\" bit NOT NULL DEFAULT FALSE,\n    PRIMARY KEY (\"id\")\n) COMMENT '邮件模版表';\n\nCREATE TABLE IF NOT EXISTS \"system_mail_log\" (\n    \"id\" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,\n    \"user_id\" bigint,\n    \"user_type\" varchar,\n    \"to_mail\" varchar NOT NULL,\n    \"account_id\" bigint NOT NULL,\n    \"from_mail\" varchar NOT NULL,\n    \"template_id\" bigint NOT NULL,\n    \"template_code\" varchar NOT NULL,\n    \"template_nickname\" varchar,\n    \"template_title\" varchar NOT NULL,\n    \"template_content\" varchar NOT NULL,\n    \"template_params\" varchar NOT NULL,\n    \"send_status\" varchar NOT NULL,\n    \"send_time\" datetime,\n    \"send_message_id\" varchar,\n    \"send_exception\" varchar,\n    \"creator\" varchar DEFAULT '',\n    \"create_time\" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    \"updater\" varchar DEFAULT '',\n    \"update_time\" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n    \"deleted\" bit NOT NULL DEFAULT FALSE,\n    PRIMARY KEY (\"id\")\n) COMMENT '邮件日志表';\n\n-- 将该建表 SQL 语句，添加到 yshop-module-system-biz 模块的 test/resources/sql/create_tables.sql 文件里\nCREATE TABLE IF NOT EXISTS \"system_notify_template\" (\n    \"id\" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,\n    \"name\" varchar NOT NULL,\n    \"code\" varchar NOT NULL,\n    \"nickname\" varchar NOT NULL,\n    \"content\" varchar NOT NULL,\n    \"type\" varchar NOT NULL,\n    \"params\" varchar,\n    \"status\" varchar NOT NULL,\n    \"remark\" varchar,\n    \"creator\" varchar DEFAULT '',\n    \"create_time\" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    \"updater\" varchar DEFAULT '',\n    \"update_time\" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n    \"deleted\" bit NOT NULL DEFAULT FALSE,\n    PRIMARY KEY (\"id\")\n) COMMENT '站内信模板表';\n\nCREATE TABLE IF NOT EXISTS \"system_notify_message\" (\n    \"id\" bigint NOT NULL GENERATED BY DEFAULT AS IDENTITY,\n    \"user_id\" bigint NOT NULL,\n    \"user_type\" varchar NOT NULL,\n    \"template_id\" bigint NOT NULL,\n    \"template_code\" varchar NOT NULL,\n    \"template_nickname\" varchar NOT NULL,\n    \"template_content\" varchar NOT NULL,\n    \"template_type\" int NOT NULL,\n    \"template_params\" varchar NOT NULL,\n    \"read_status\" bit NOT NULL,\n    \"read_time\" varchar,\n    \"creator\" varchar DEFAULT '',\n    \"create_time\" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,\n    \"updater\" varchar DEFAULT '',\n    \"update_time\" datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n    \"deleted\" bit NOT NULL DEFAULT FALSE,\n    \"tenant_id\" bigint not null default  '0',\n    PRIMARY KEY (\"id\")\n) COMMENT '站内信消息表';\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-server/Dockerfile",
    "content": "## AdoptOpenJDK 停止发布 OpenJDK 二进制，而 Eclipse Temurin 是它的延伸，提供更好的稳定性\n## 感谢复旦核博士的建议！灰子哥，牛皮！\nFROM eclipse-temurin:21-jre\n\n## 创建目录，并使用它作为工作目录\nRUN mkdir -p /yshop-server\nWORKDIR /yshop-server\n## 将后端项目的 Jar 文件，复制到镜像中\nCOPY ./target/yshop-server.jar app.jar\n\n## 设置 TZ 时区\nENV TZ=Asia/Shanghai\n## 设置 JAVA_OPTS 环境变量，可通过 docker run -e \"JAVA_OPTS=\" 进行覆盖\nENV JAVA_OPTS=\"-Xms512m -Xmx512m -Djava.security.egd=file:/dev/./urandom\"\n\n## 应用参数\nENV ARGS=\"\"\n\n## 暴露后端项目的 48080 端口\nEXPOSE 48080\n\n## 启动后端项目\nCMD java ${JAVA_OPTS} -jar app.jar $ARGS\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-server/pom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<project xmlns=\"http://maven.apache.org/POM/4.0.0\"\n         xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\n         xsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd\">\n    <parent>\n        <groupId>co.yixiang.boot</groupId>\n        <artifactId>yshop</artifactId>\n        <version>${revision}</version>\n    </parent>\n    <modelVersion>4.0.0</modelVersion>\n\n    <artifactId>yshop-server</artifactId>\n    <packaging>jar</packaging>\n\n    <name>${project.artifactId}</name>\n    <description>\n        后端 Server 的主项目，通过引入需要 yshop-module-xxx 的依赖，\n        从而实现提供 RESTful API 给前端项目。\n        本质上来说，它就是个空壳（容器）！！\n    </description>\n    <url>https://gitee.com/guchengwuyue/yshop-drink</url>\n\n    <dependencies>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-system-biz</artifactId>\n            <version>${revision}</version>\n        </dependency>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-infra-biz</artifactId>\n            <version>${revision}</version>\n        </dependency>\n\n\n\n        <!-- spring boot 配置所需依赖 -->\n        <dependency>\n            <groupId>org.springframework.boot</groupId>\n            <artifactId>spring-boot-configuration-processor</artifactId>\n            <optional>true</optional>\n        </dependency>\n\n\n        <!-- 支付服务 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-pay-biz</artifactId>\n            <version>${revision}</version>\n        </dependency>\n\n        <!-- 快递服务 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-express-biz</artifactId>\n            <version>${revision}</version>\n        </dependency>\n\n\n        <!-- 消息服务 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-message-biz</artifactId>\n            <version>${revision}</version>\n        </dependency>\n\n        <!-- 微信公众号模块。默认注释，保证编译速度 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-mp-biz</artifactId>\n            <version>${revision}</version>\n        </dependency>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-shop-biz</artifactId>\n            <version>${revision}</version>\n        </dependency>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-order-biz</artifactId>\n            <version>${revision}</version>\n        </dependency>\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-coupon-biz</artifactId>\n            <version>${revision}</version>\n        </dependency>\n\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-module-score-biz</artifactId>\n            <version>${revision}</version>\n        </dependency>\n\n\n        <!-- 服务保障相关 -->\n        <dependency>\n            <groupId>co.yixiang.boot</groupId>\n            <artifactId>yshop-spring-boot-starter-protection</artifactId>\n        </dependency>\n\n    </dependencies>\n\n    <build>\n        <!-- 设置构建的 jar 包名 -->\n        <finalName>${project.artifactId}</finalName>\n        <plugins>\n            <!-- 打包 -->\n            <plugin>\n                <groupId>org.springframework.boot</groupId>\n                <artifactId>spring-boot-maven-plugin</artifactId>\n                <version>${spring.boot.version}</version>\n                <executions>\n                    <execution>\n                        <goals>\n                            <goal>repackage</goal> <!-- 将引入的 jar 打入其中 -->\n                        </goals>\n                    </execution>\n                </executions>\n            </plugin>\n        </plugins>\n    </build>\n\n</project>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-server/src/main/java/co/yixiang/yshop/server/YshopServerApplication.java",
    "content": "package co.yixiang.yshop.server;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\n\n/**\n * 项目的启动类\n * @author yshop\n */\n@SuppressWarnings(\"SpringComponentScan\") // 忽略 IDEA 无法识别 ${yshop.info.base-package}\n@SpringBootApplication(scanBasePackages = {\"${yshop.info.base-package}.server\", \"${yshop.info.base-package}.module\"})\npublic class YshopServerApplication {\n\n    public static void main(String[] args){\n\n        SpringApplication.run(YshopServerApplication.class, args);\n\n\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-server/src/main/java/co/yixiang/yshop/server/controller/DefaultController.java",
    "content": "package co.yixiang.yshop.server.controller;\n\nimport org.springframework.web.bind.annotation.RestController;\n\n/**\n * 默认 Controller，解决部分 module 未开启时的 404 提示。\n * 例如说，/bpm/** 路径，工作流\n *\n * @author yshop\n */\n@RestController\npublic class DefaultController {\n\n\n\n}\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-server/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports",
    "content": "com.egzosn.pay.spring.boot.autoconfigue.PayAutoConfiguration"
  },
  {
    "path": "yshop-drink-boot3/yshop-server/src/main/resources/application-dev.yaml",
    "content": "server:\n  port: 48080\n\n--- #################### 数据库相关配置 ####################\n\nspring:\n  # 数据源配置项\n  datasource:\n    druid: # Druid 【监控】相关的全局配置\n      web-stat-filter:\n        enabled: true\n      stat-view-servlet:\n        enabled: true\n        allow: # 设置白名单，不填则允许所有访问\n        url-pattern: /druid/*\n        login-username: # 控制台管理用户名和密码\n        login-password:\n      filter:\n        stat:\n          enabled: true\n          log-slow-sql: true # 慢 SQL 记录\n          slow-sql-millis: 100\n          merge-sql: true\n        wall:\n          config:\n            multi-statement-allow: true\n    dynamic: # 多数据源配置\n      druid: # Druid 【连接池】相关的全局配置\n        initial-size: 5 # 初始连接数\n        min-idle: 10 # 最小连接池数量\n        max-active: 20 # 最大连接池数量\n        max-wait: 600000 # 配置获取连接等待超时的时间，单位：毫秒\n        time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测，检测需要关闭的空闲连接，单位：毫秒\n        min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间，单位：毫秒\n        max-evictable-idle-time-millis: 900000 # 配置一个连接在池中最大生存的时间，单位：毫秒\n        validation-query: SELECT 1 # 配置检测连接是否有效\n        test-while-idle: true\n        test-on-borrow: false\n        test-on-return: false\n      primary: master\n      datasource:\n        master:\n          url: jdbc:mysql://127.0.0.1:3306/yixiang-drink-open?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例\n          username: root\n          password: root\n        slave: # 模拟从库，可根据自己需要修改 # 模拟从库，可根据自己需要修改\n          lazy: true # 开启懒加载，保证启动速度\n          url: jdbc:mysql://127.0.0.1:3306/yixiang-drink-open?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例\n          username: root\n          password: root\n\n  # Redis 配置。Redisson 默认的配置足够使用，一般不需要进行调优\n  data:\n    redis:\n      host: 400-infra.server.yixiang.co # 地址\n      port: 6379 # 端口\n      database: 1 # 数据库索引\n#    password: 123456 # 密码，建议生产环境开启\n\n--- #################### 定时任务相关配置 ####################\n\n# Quartz 配置项，对应 QuartzProperties 配置类\nspring:\n  quartz:\n    auto-startup: true # 测试环境，需要开启 Job\n    scheduler-name: schedulerName # Scheduler 名字。默认为 schedulerName\n    job-store-type: jdbc # Job 存储器类型。默认为 memory 表示内存，可选 jdbc 使用数据库。\n    wait-for-jobs-to-complete-on-shutdown: true # 应用关闭时，是否等待定时任务执行完成。默认为 false ，建议设置为 true\n    properties: # 添加 Quartz Scheduler 附加属性，更多可以看 http://www.quartz-scheduler.org/documentation/2.4.0-SNAPSHOT/configuration.html 文档\n      org:\n        quartz:\n          # Scheduler 相关配置\n          scheduler:\n            instanceName: schedulerName\n            instanceId: AUTO # 自动生成 instance ID\n          # JobStore 相关配置\n          jobStore:\n            # JobStore 实现类。可见博客：https://blog.csdn.net/weixin_42458219/article/details/122247162\n            class: org.springframework.scheduling.quartz.LocalDataSourceJobStore\n            isClustered: true # 是集群模式\n            clusterCheckinInterval: 15000 # 集群检查频率，单位：毫秒。默认为 15000，即 15 秒\n            misfireThreshold: 60000 # misfire 阀值，单位：毫秒。\n          # 线程池相关配置\n          threadPool:\n            threadCount: 25 # 线程池大小。默认为 10 。\n            threadPriority: 5 # 线程优先级\n            class: org.quartz.simpl.SimpleThreadPool # 线程池类型\n    jdbc: # 使用 JDBC 的 JobStore 的时候，JDBC 的配置\n      initialize-schema: NEVER # 是否自动使用 SQL 初始化 Quartz 表结构。这里设置成 never ，我们手动创建表结构。\n\n--- #################### 消息队列相关 ####################\n\n# rocketmq 配置项，对应 RocketMQProperties 配置类\nrocketmq:\n  name-server: 127.0.0.1:9876 # RocketMQ Namesrv\n\nspring:\n  # RabbitMQ 配置项，对应 RabbitProperties 配置类\n  rabbitmq:\n    host: 127.0.0.1 # RabbitMQ 服务的地址\n    port: 5672 # RabbitMQ 服务的端口\n    username: guest # RabbitMQ 服务的账号\n    password: guest # RabbitMQ 服务的密码\n  # Kafka 配置项，对应 KafkaProperties 配置类\n  kafka:\n    bootstrap-servers: 127.0.0.1:9092 # 指定 Kafka Broker 地址，可以设置多个，以逗号分隔\n\n--- #################### 服务保障相关配置 ####################\n\n# Lock4j 配置项\nlock4j:\n  acquire-timeout: 3000 # 获取分布式锁超时时间，默认为 3000 毫秒\n  expire: 30000 # 分布式锁的超时时间，默认为 30 毫秒\n\n--- #################### 监控相关配置 ####################\n\n# Actuator 监控端点的配置项\nmanagement:\n  endpoints:\n    web:\n      base-path: /actuator # Actuator 提供的 API 接口的根目录。默认为 /actuator\n      exposure:\n        include: '*' # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ，可以开放所有端点。\n\n# Spring Boot Admin 配置项\nspring:\n  boot:\n    admin:\n      # Spring Boot Admin Client 客户端的相关配置\n      client:\n        url: http://127.0.0.1:${server.port}/${spring.boot.admin.context-path} # 设置 Spring Boot Admin Server 地址\n        instance:\n          service-host-type: IP # 注册实例时，优先使用 IP [IP, HOST_NAME, CANONICAL_HOST_NAME]\n      # Spring Boot Admin Server 服务端的相关配置\n      context-path: /admin # 配置 Spring\n\n# 日志文件配置\nlogging:\n  file:\n    name: ${user.home}/logs/${spring.application.name}.log # 日志文件名，全路径\n\n--- #################### 微信公众号相关配置 ####################\nwx: # 参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-starters/wx-java-mp-spring-boot-starter/README.md 文档\n  mp:\n    # 公众号配置(必填)\n    app-id: wxdbdbc123c8c30b45\n    secret: 3312eb2026a006bd0cc39af3021ef9c5\n    # 存储配置，解决 AccessToken 的跨节点的共享\n    config-storage:\n      type: RedisTemplate # 采用 RedisTemplate 操作 Redis，会自动从 Spring 中获取\n      key-prefix: wx # Redis Key 的前缀\n      http-client-type: HttpClient # 采用 HttpClient 请求微信公众号平台\n  miniapp: # 小程序配置（必填），参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-starters/wx-java-miniapp-spring-boot-starter/README.md 文档\n    appid: wx001e2dc50bf532df\n    secret: d22aa6a98472ae0b5ee6dd9b7807520c\n    config-storage:\n      type: RedisTemplate # 采用 RedisTemplate 操作 Redis，会自动从 Spring 中获取\n      key-prefix: wa # Redis Key 的前缀\n      http-client-type: HttpClient # 采用 HttpClient 请求微信公众号平台\n\n--- #################### yshop相关配置 ####################\n\n# yshop配置项，设置当前项目所有自定义的配置\nyshop:\n  xss:\n    enable: false\n    exclude-urls: # 如下两个 url，仅仅是为了演示，去掉配置也没关系\n      - ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求\n      - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求\n  pay:\n    order-notify-url: http://yshop.natapp1.cc/admin-api/pay/notify/order # 支付渠道的【支付】回调地址\n    refund-notify-url: http://yshop.natapp1.cc/admin-api/pay/notify/refund # 支付渠道的【退款】回调地址\n  demo: true # 开启演示模式\n  tencent-lbs-key: TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E # QQ 地图的密钥 https://lbs.qq.com/service/staticV2/staticGuide/staticDoc\n  file-path: /www/wwwroot/drink/file/\n  h5: https://www.yixiang.co/h5\n\njustauth:\n  enabled: true\n  type:\n    DINGTALK: # 钉钉\n      client-id: dingvrnreaje3yqvzhxg\n      client-secret: i8E6iZyDvZj51JIb0tYsYfVQYOks9Cq1lgryEjFRqC79P3iJcrxEwT6Qk2QvLrLI\n      ignore-check-redirect-uri: true\n    WECHAT_ENTERPRISE: # 企业微信\n      client-id: wwd411c69a39ad2e54\n      client-secret: 1wTb7hYxnpT2TUbIeHGXGo7T0odav1ic10mLdyyATOw\n      agent-id: 1000004\n      ignore-check-redirect-uri: true\n    # noinspection SpringBootApplicationYaml\n    WECHAT_MINI_APP: # 微信小程序\n      client-id: ${wx.miniapp.appid}\n      client-secret: ${wx.miniapp.secret}\n      ignore-check-redirect-uri: true\n      ignore-check-state: true # 微信小程序，不会使用到 state，所以不进行校验\n    WECHAT_MP: # 微信公众号\n      client-id: ${wx.mp.app-id}\n      client-secret: ${wx.mp.secret}\n      ignore-check-redirect-uri: true\n  cache:\n    type: REDIS\n    prefix: 'social_auth_state:' # 缓存前缀，目前只对 Redis 缓存生效，默认 JUSTAUTH::STATE::\n    timeout: 24h # 超时时长，目前只对 Redis 缓存生效，默认 3 分钟"
  },
  {
    "path": "yshop-drink-boot3/yshop-server/src/main/resources/application-local.yaml",
    "content": "server:\n  port: 48081\n\n--- #################### 数据库相关配置 ####################\n\nspring:\n  # 数据源配置项\n  autoconfigure:\n    exclude:\n      - org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration # 默认 local 环境，不开启 Quartz 的自动配置\n      - de.codecentric.boot.admin.server.config.AdminServerAutoConfiguration # 禁用 Spring Boot Admin 的 Server 的自动配置\n      - de.codecentric.boot.admin.server.ui.config.AdminServerUiAutoConfiguration # 禁用 Spring Boot Admin 的 Server UI 的自动配置\n      - de.codecentric.boot.admin.client.config.SpringBootAdminClientAutoConfiguration # 禁用 Spring Boot Admin 的 Client 的自动配置\n  datasource:\n    druid: # Druid 【监控】相关的全局配置\n      web-stat-filter:\n        enabled: true\n      stat-view-servlet:\n        enabled: true\n        allow: # 设置白名单，不填则允许所有访问\n        url-pattern: /druid/*\n        login-username: # 控制台管理用户名和密码\n        login-password:\n      filter:\n        stat:\n          enabled: true\n          log-slow-sql: true # 慢 SQL 记录\n          slow-sql-millis: 100\n          merge-sql: true\n        wall:\n          config:\n            multi-statement-allow: true\n    dynamic: # 多数据源配置\n      druid: # Druid 【连接池】相关的全局配置\n        initial-size: 1 # 初始连接数\n        min-idle: 1 # 最小连接池数量\n        max-active: 20 # 最大连接池数量\n        max-wait: 600000 # 配置获取连接等待超时的时间，单位：毫秒\n        time-between-eviction-runs-millis: 60000 # 配置间隔多久才进行一次检测，检测需要关闭的空闲连接，单位：毫秒\n        min-evictable-idle-time-millis: 300000 # 配置一个连接在池中最小生存的时间，单位：毫秒\n        max-evictable-idle-time-millis: 900000 # 配置一个连接在池中最大生存的时间，单位：毫秒\n        validation-query: SELECT 1 FROM DUAL # 配置检测连接是否有效\n        test-while-idle: true\n        test-on-borrow: false\n        test-on-return: false\n      primary: master\n      datasource:\n        master:\n          url: jdbc:mysql://127.0.0.1:3306/yixiang-drink-open?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true # MySQL Connector/J 8.X 连接的示例\n          username: root\n          password: root\n        slave: # 模拟从库，可根据自己需要修改\n          lazy: true # 开启懒加载，保证启动速度\n          url: jdbc:mysql://127.0.0.1:3306/yixiang-drink-open?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true\n          username: root\n          password: root\n\n  # Redis 配置。Redisson 默认的配置足够使用，一般不需要进行调优\n  data:\n    redis:\n      host: 127.0.0.1 # 地址\n      port: 6379 # 端口\n      database: 0 # 数据库索引\n#    password: dev # 密码，建议生产环境开启\n\n--- #################### 定时任务相关配置 ####################\n\n# Quartz 配置项，对应 QuartzProperties 配置类\nspring:\n  quartz:\n    auto-startup: true # 本地开发环境，尽量不要开启 Job\n    scheduler-name: schedulerName # Scheduler 名字。默认为 schedulerName\n    job-store-type: jdbc # Job 存储器类型。默认为 memory 表示内存，可选 jdbc 使用数据库。\n    wait-for-jobs-to-complete-on-shutdown: true # 应用关闭时，是否等待定时任务执行完成。默认为 false ，建议设置为 true\n    properties: # 添加 Quartz Scheduler 附加属性，更多可以看 http://www.quartz-scheduler.org/documentation/2.4.0-SNAPSHOT/configuration.html 文档\n      org:\n        quartz:\n          # Scheduler 相关配置\n          scheduler:\n            instanceName: schedulerName\n            instanceId: AUTO # 自动生成 instance ID\n          # JobStore 相关配置\n          jobStore:\n            # JobStore 实现类。可见博客：https://blog.csdn.net/weixin_42458219/article/details/122247162\n            class: org.springframework.scheduling.quartz.LocalDataSourceJobStore\n            isClustered: true # 是集群模式\n            clusterCheckinInterval: 15000 # 集群检查频率，单位：毫秒。默认为 15000，即 15 秒\n            misfireThreshold: 60000 # misfire 阀值，单位：毫秒。\n          # 线程池相关配置\n          threadPool:\n            threadCount: 25 # 线程池大小。默认为 10 。\n            threadPriority: 5 # 线程优先级\n            class: org.quartz.simpl.SimpleThreadPool # 线程池类型\n    jdbc: # 使用 JDBC 的 JobStore 的时候，JDBC 的配置\n      initialize-schema: NEVER # 是否自动使用 SQL 初始化 Quartz 表结构。这里设置成 never ，我们手动创建表结构。\n\n--- #################### 消息队列相关 ####################\n\n# rocketmq 配置项，对应 RocketMQProperties 配置类\nrocketmq:\n  name-server: 127.0.0.1:9876 # RocketMQ Namesrv\n\nspring:\n  # RabbitMQ 配置项，对应 RabbitProperties 配置类\n  rabbitmq:\n    host: 127.0.0.1 # RabbitMQ 服务的地址\n    port: 5672 # RabbitMQ 服务的端口\n    username: rabbit # RabbitMQ 服务的账号\n    password: rabbit # RabbitMQ 服务的密码\n  # Kafka 配置项，对应 KafkaProperties 配置类\n  kafka:\n    bootstrap-servers: 127.0.0.1:9092 # 指定 Kafka Broker 地址，可以设置多个，以逗号分隔\n\n--- #################### 服务保障相关配置 ####################\n\n# Lock4j 配置项\nlock4j:\n  acquire-timeout: 3000 # 获取分布式锁超时时间，默认为 3000 毫秒\n  expire: 30000 # 分布式锁的超时时间，默认为 30 毫秒\n\n--- #################### 监控相关配置 ####################\n\n# Actuator 监控端点的配置项\nmanagement:\n  endpoints:\n    web:\n      base-path: /actuator # Actuator 提供的 API 接口的根目录。默认为 /actuator\n      exposure:\n        include: '*' # 需要开放的端点。默认值只打开 health 和 info 两个端点。通过设置 * ，可以开放所有端点。\n\n# Spring Boot Admin 配置项\nspring:\n  boot:\n    admin:\n      # Spring Boot Admin Client 客户端的相关配置\n      client:\n        url: http://127.0.0.1:${server.port}/${spring.boot.admin.context-path} # 设置 Spring Boot Admin Server 地址\n        instance:\n          service-host-type: IP # 注册实例时，优先使用 IP [IP, HOST_NAME, CANONICAL_HOST_NAME]\n      # Spring Boot Admin Server 服务端的相关配置\n      context-path: /admin # 配置 Spring\n\n# 日志文件配置\nlogging:\n  file:\n    name: ${user.home}/logs/${spring.application.name}.log # 日志文件名，全路径\n  level:\n    # 配置自己写的 MyBatis Mapper 打印日志\n    co.yixiang.yshop.module.bpm.dal.mysql: debug\n    co.yixiang.yshop.module.infra.dal.mysql: debug\n    co.yixiang.yshop.module.infra.dal.mysql.logger.ApiErrorLogMapper: INFO # 配置 ApiErrorLogMapper 的日志级别为 info，避免和 GlobalExceptionHandler 重复打印\n    co.yixiang.yshop.module.infra.dal.mysql.job.JobLogMapper: INFO # 配置 JobLogMapper 的日志级别为 info\n    co.yixiang.yshop.module.infra.dal.mysql.file.FileConfigMapper: INFO # 配置 FileConfigMapper 的日志级别为 info\n    co.yixiang.yshop.module.pay.dal.mysql: debug\n    co.yixiang.yshop.module.pay.dal.mysql.notify.PayNotifyTaskMapper: INFO # 配置 PayNotifyTaskMapper 的日志级别为 info\n    co.yixiang.yshop.module.system.dal.mysql: debug\n    co.yixiang.yshop.module.system.dal.mysql.sms.SmsChannelMapper: INFO # 配置 SmsChannelMapper 的日志级别为 info\n    co.yixiang.yshop.module.tool.dal.mysql: debug\n    co.yixiang.yshop.module.member.dal.mysql: debug\n    co.yixiang.yshop.module.order.dal.mysql: debug\n    co.yixiang.yshop.module.product.dal.mysql: debug\n    co.yixiang.yshop.module.store.dal.mysql: debug\n    co.yixiang.yshop.module.shop.dal.mysql: debug\n    co.yixiang.yshop.module.coupon.dal.mysql: debug\n    org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # TODO yshop：先禁用，Spring Boot 3.X 存在部分错误的 WARN 提示\n\ndebug: false\n\n--- #################### 微信公众号、小程序相关配置 ####################\nwx:\n  mp: # 公众号配置（必填），参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-starters/wx-java-mp-spring-boot-starter/README.md 文档\n    app-id: wxdbdbc123c8c30b45 # 测试号（自己的）\n    secret: 3312eb2026a006bd0cc39af3021ef9c5\n    # 存储配置，解决 AccessToken 的跨节点的共享\n    config-storage:\n      type: RedisTemplate # 采用 RedisTemplate 操作 Redis，会自动从 Spring 中获取\n      key-prefix: wx # Redis Key 的前缀\n      http-client-type: HttpClient # 采用 HttpClient 请求微信公众号平台\n  miniapp: # 小程序配置（必填），参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-starters/wx-java-miniapp-spring-boot-starter/README.md 文档\n    appid: wx001e2dc50bf532df # wenhualian的接口测试号\n    secret: d22aa6a98472ae0b5ee6dd9b7807520c\n    config-storage:\n      type: RedisTemplate # 采用 RedisTemplate 操作 Redis，会自动从 Spring 中获取\n      key-prefix: wa # Redis Key 的前缀\n      http-client-type: HttpClient # 采用 HttpClient 请求微信公众号平台\n\n--- #################### yshop相关配置 ####################\n\n# yshop配置项，设置当前项目所有自定义的配置\nyshop:\n  captcha:\n    enable: false # 本地环境，暂时关闭图片验证码，方便登录等接口的测试；\n  security:\n    mock-enable: true\n  xss:\n    enable: false\n    exclude-urls: # 如下两个 url，仅仅是为了演示，去掉配置也没关系\n      - ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求\n      - ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求\n  pay:\n    order-notify-url: http://yshop.natapp1.cc/admin-api/pay/notify/order # 支付渠道的【支付】回调地址\n    refund-notify-url: http://yshop.natapp1.cc/admin-api/pay/notify/refund # 支付渠道的【退款】回调地址\n  access-log: # 访问日志的配置项\n    enable: false\n  demo: false # 关闭演示模式\n  tencent-lbs-key: TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E # QQ 地图的密钥 https://lbs.qq.com/service/staticV2/staticGuide/staticDoc\n  file-path: /Users/hupeng/drink/file/\n  h5: http://localhost:80\n\njustauth:\n  enabled: true\n  type:\n    DINGTALK: # 钉钉\n      client-id: dingvrnreaje3yqvzhxg\n      client-secret: i8E6iZyDvZj51JIb0tYsYfVQYOks9Cq1lgryEjFRqC79P3iJcrxEwT6Qk2QvLrLI\n      ignore-check-redirect-uri: true\n    WECHAT_ENTERPRISE: # 企业微信\n      client-id: wwd411c69a39ad2e54\n      client-secret: 1wTb7hYxnpT2TUbIeHGXGo7T0odav1ic10mLdyyATOw\n      agent-id: 1000004\n      ignore-check-redirect-uri: true\n    # noinspection SpringBootApplicationYaml\n    WECHAT_MINI_APP: # 微信小程序\n      client-id: ${wx.miniapp.appid}\n      client-secret: ${wx.miniapp.secret}\n      ignore-check-redirect-uri: true\n      ignore-check-state: true # 微信小程序，不会使用到 state，所以不进行校验\n    WECHAT_MP: # 微信公众号\n      client-id: ${wx.mp.app-id}\n      client-secret: ${wx.mp.secret}\n      ignore-check-redirect-uri: true\n  cache:\n    type: REDIS\n    prefix: 'social_auth_state:' # 缓存前缀，目前只对 Redis 缓存生效，默认 JUSTAUTH::STATE::\n    timeout: 24h # 超时时长，目前只对 Redis 缓存生效，默认 3 分钟\n\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-server/src/main/resources/application.yaml",
    "content": "spring:\n  application:\n    name: yshop-server\n\n  profiles:\n    active: local\n\n  main:\n    allow-circular-references: true # 允许循环依赖，因为项目是三层架构，无法避免这个情况。\n\n  # Servlet 配置\n  servlet:\n    # 文件上传相关配置项\n    multipart:\n      max-file-size: 16MB # 单个文件大小\n      max-request-size: 32MB # 设置总上传的文件大小\n  mvc:\n    pathmatch:\n      matching-strategy: ANT_PATH_MATCHER # 解决 SpringFox 与 SpringBoot 2.6.x 不兼容的问题，参见 SpringFoxHandlerProviderBeanPostProcessor 类\n#    throw-exception-if-no-handler-found: true # 404 错误时抛出异常，方便统一处理\n#    static-path-pattern: /static/** # 静态资源路径; 注意：如果不配置，则 throw-exception-if-no-handler-found 不生效！！！ TODO yshop：不能配置，会导致 swagger 不生效\n\n  # Jackson 配置项\n  jackson:\n    serialization:\n      write-dates-as-timestamps: true # 设置 Date 的格式，使用时间戳\n      write-date-timestamps-as-nanoseconds: false # 设置不使用 nanoseconds 的格式。例如说 1611460870.401，而是直接 1611460870401\n      write-durations-as-timestamps: true # 设置 Duration 的格式，使用时间戳\n      fail-on-empty-beans: false # 允许序列化无属性的 Bean\n\n  # Cache 配置项\n  cache:\n    type: REDIS\n    redis:\n      time-to-live: 1h # 设置过期时间为 1 小时\n\n--- #################### 接口文档配置 ####################\n\nspringdoc:\n  api-docs:\n    enabled: true\n    path: /v3/api-docs\n  swagger-ui:\n    enabled: true\n    path: /swagger-ui\n  default-flat-param-object: true # 参见 https://doc.xiaominfo.com/docs/faq/v4/knife4j-parameterobject-flat-param 文档\n\nknife4j:\n  enable: true\n  setting:\n    language: zh_cn\n\n# 工作流 Flowable 配置\n#flowable:\n#  # 1. false: 默认值，Flowable 启动时，对比数据库表中保存的版本，如果不匹配。将抛出异常\n#  # 2. true: 启动时会对数据库中所有表进行更新操作，如果表存在，不做处理，反之，自动创建表\n#  # 3. create_drop: 启动时自动创建表，关闭时自动删除表\n#  # 4. drop_create: 启动时，删除旧表，再创建新表\n#  database-schema-update: true # 设置为 false，可通过 https://github.com/flowable/flowable-sql 初始化\n#  db-history-used: true # flowable6 默认 true 生成信息表，无需手动设置\n#  check-process-definitions: false # 设置为 false，禁用 /resources/processes 自动部署 BPMN XML 流程\n#  history-level: audit # full：保存历史数据的最高级别，可保存全部流程相关细节，包括流程流转各节点参数\n\n# MyBatis Plus 的配置项\nmybatis-plus:\n  configuration:\n    map-underscore-to-camel-case: true # 虽然默认为 true ，但是还是显示去指定下。\n  global-config:\n    db-config:\n      id-type: NONE # “智能”模式，基于 IdTypeEnvironmentPostProcessor + 数据源的类型，自动适配成 AUTO、INPUT 模式。\n#      id-type: AUTO # 自增 ID，适合 MySQL 等直接自增的数据库\n#      id-type: INPUT # 用户输入 ID，适合 Oracle、PostgreSQL、Kingbase、DB2、H2 数据库\n#      id-type: ASSIGN_ID # 分配 ID，默认使用雪花算法。注意，Oracle、PostgreSQL、Kingbase、DB2、H2 数据库时，需要去除实体类上的 @KeySequence 注解\n      logic-delete-value: 1 # 逻辑已删除值(默认为 1)\n      logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)\n    banner: false # 关闭控制台的 Banner 打印\n  type-aliases-package: ${yshop.info.base-package}.module.*.dal.dataobject\n  encryptor:\n    password: XDV71a+xqStEA3WH # 加解密的秘钥，可使用 https://www.imaegoo.com/2020/aes-key-generator/ 网站生成\n\nmybatis-plus-join:\n  banner: false # 是否打印 mybatis plus join banner，默认true\n  sub-table-logic: true # 全局启用副表逻辑删除，默认true。关闭后关联查询不会加副表逻辑删除\n  ms-cache: true # 拦截器MappedStatement缓存，默认 true\n  table-alias: t # 表别名(默认 t)\n  logic-del-type: on # 副表逻辑删除条件的位置，支持 WHERE、ON，默认 ON\n\n# Spring Data Redis 配置\nspring:\n  data:\n    redis:\n      repositories:\n        enabled: false # 项目未使用到 Spring Data Redis 的 Repository，所以直接禁用，保证启动速度\n\n# VO 转换（数据翻译）相关\neasy-trans:\n  is-enable-global: true # 启用全局翻译（拦截所有 SpringMVC ResponseBody 进行自动翻译 )。如果对于性能要求很高可关闭此配置，或通过 @IgnoreTrans 忽略某个接口\n  is-enable-cloud: false # 禁用 TransType.RPC 微服务模式\n\n--- #################### 验证码相关配置 ####################\n\naj:\n  captcha:\n    jigsaw: classpath:images/jigsaw # 滑动验证，底图路径，不配置将使用默认图片；以 classpath: 开头，取 resource 目录下路径\n    pic-click: classpath:images/pic-click # 滑动验证，底图路径，不配置将使用默认图片；以 classpath: 开头，取 resource 目录下路径\n    cache-type: redis # 缓存 local/redis...\n    cache-number: 1000 # local 缓存的阈值,达到这个值，清除缓存\n    timing-clear: 180 # local定时清除过期缓存(单位秒),设置为0代表不执行\n    type: blockPuzzle # 验证码类型 default两种都实例化。 blockPuzzle 滑块拼图 clickWord 文字点选\n    water-mark: yshop # 右下角水印文字(我的水印)，可使用 https://tool.chinaz.com/tools/unicode.aspx 中文转 Unicode，Linux 可能需要转 unicode\n    interference-options: 0 # 滑动干扰项(0/1/2)\n    req-frequency-limit-enable: false # 接口请求次数一分钟限制是否开启 true|false\n    req-get-lock-limit: 5 # 验证失败 5 次，get接口锁定\n    req-get-lock-seconds: 10 # 验证失败后，锁定时间间隔\n    req-get-minute-limit: 30 # get 接口一分钟内请求数限制\n    req-check-minute-limit: 60 # check 接口一分钟内请求数限制\n    req-verify-minute-limit: 60 # verify 接口一分钟内请求数限制\n\n--- #################### 消息队列相关 ####################\n\n# rocketmq 配置项，对应 RocketMQProperties 配置类\nrocketmq:\n  # Producer 配置项\n  producer:\n    group: ${spring.application.name}_PRODUCER # 生产者分组\n\nspring:\n  # Kafka 配置项，对应 KafkaProperties 配置类\n  kafka:\n    # Kafka Producer 配置项\n    producer:\n      acks: 1 # 0-不应答。1-leader 应答。all-所有 leader 和 follower 应答。\n      retries: 3 # 发送失败时，重试发送的次数\n      value-serializer: org.springframework.kafka.support.serializer.JsonSerializer # 消息的 value 的序列化\n    # Kafka Consumer 配置项\n    consumer:\n      auto-offset-reset: earliest # 设置消费者分组最初的消费进度为 earliest 。可参考博客 https://blog.csdn.net/lishuangzhe7047/article/details/74530417 理解\n      value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer\n      properties:\n        spring.json.trusted.packages: '*'\n    # Kafka Consumer Listener 监听器配置\n    listener:\n      missing-topics-fatal: false # 消费监听接口监听的主题不存在时，默认会报错。所以通过设置为 false ，解决报错\n\n--- #################### yshop相关配置 ####################\n\nyshop:\n  info:\n    version: 3.0.0\n    base-package: co.yixiang.yshop\n    isActive: true\n  web:\n    admin-ui:\n      url: http://dashboard.yshop.yixiang.co # Admin 管理后台 UI 的地址\n  security:\n    permit-all_urls:\n      - /admin-api/mp/open/** # 微信公众号开放平台，微信回调接口，不需要登录\n  websocket:\n    enable: true # websocket的开关\n    path: /infra/ws # 路径\n    sender-type: local # 消息发送的类型，可选值为 local、redis、rocketmq、kafka、rabbitmq\n    sender-rocketmq:\n      topic: ${spring.application.name}-websocket # 消息发送的 RocketMQ Topic\n      consumer-group: ${spring.application.name}-websocket-consumer # 消息发送的 RocketMQ Consumer Group\n    sender-rabbitmq:\n      exchange: ${spring.application.name}-websocket-exchange # 消息发送的 RabbitMQ Exchange\n      queue: ${spring.application.name}-websocket-queue # 消息发送的 RabbitMQ Queue\n    sender-kafka:\n      topic: ${spring.application.name}-websocket # 消息发送的 Kafka Topic\n      consumer-group: ${spring.application.name}-websocket-consumer # 消息发送的 Kafka Consumer Group\n  swagger:\n    title: yshop意象点餐系统\n    description: 提供管理后台、用户 App 的所有功能\n    version: ${yshop.info.version}\n    url: ${yshop.web.admin-ui.url}\n    email: yshop@yixiang.co\n    license: MIT\n    license-url: https://gitee.com/guchengwuyue/yshop-drink\n  captcha:\n    enable: true # 验证码的开关，默认为 true\n  codegen:\n    base-package: ${yshop.info.base-package}\n    db-schemas: ${spring.datasource.dynamic.datasource.master.name}\n    front-type: 10 # 前端模版的类型，参见 CodegenFrontTypeEnum 枚举类\n  tenant: # 多租户相关配置项\n    enable: false\n    ignore-urls:\n      - /admin-api/system/tenant/get-id-by-name # 基于名字获取租户，不许带租户编号\n      - /admin-api/system/tenant/get-by-website # 基于域名获取租户，不许带租户编号\n      - /admin-api/system/captcha/get # 获取图片验证码，和租户无关\n      - /admin-api/system/captcha/check # 校验图片验证码，和租户无关\n      - /admin-api/infra/file/*/get/** # 获取图片，和租户无关\n      - /admin-api/system/sms/callback/* # 短信回调接口，无法带上租户编号\n      - /admin-api/pay/notify/** # 支付回调通知，不携带租户编号\n      - /jmreport/* # 积木报表，无法携带租户编号\n      - /admin-api/mp/open/** # 微信公众号开放平台，微信回调接口，无法携带租户编号\n      - /admin-api/file/qrcode/** # 二维码不需要\n      - /app-api/order/notify/* # 支付回调通知，不携带租户编号\n    ignore-tables:\n      - system_tenant\n      - system_tenant_package\n      - system_dict_data\n      - system_dict_type\n      - system_error_code\n      - system_menu\n      - system_sms_channel\n      - system_sms_template\n      - system_sms_log\n      - system_sensitive_word\n      - system_oauth2_client\n      - system_mail_account\n      - system_mail_template\n      - system_mail_log\n      - system_notify_template\n      - infra_codegen_column\n      - infra_codegen_table\n      - infra_config\n      - infra_file_config\n      - infra_file\n      - infra_file_content\n      - infra_job\n      - infra_job_log\n      - infra_job_log\n      - infra_data_source_config\n      - yshop_store_product_attr\n      - yshop_store_product_attr_result\n      - yshop_store_product_attr_value\n      - yshop_store_order_cart_info\n      - yshop_store_order_status\n      - yshop_order_number\n      - yshop_qrcode\n  sms-code: # 短信验证码相关的配置项\n    expire-times: 10m\n    send-frequency: 1m\n    send-maximum-quantity-per-day: 10\n    begin-code: 9999 # 这里配置 9999 的原因是，测试方便。\n    end-code: 9999 # 这里配置 9999 的原因是，测试方便。\n  trade:\n    order:\n      app-id: 1 # 商户编号\n      pay-expire-time: 2h # 支付的过期时间\n      receive-expire-time: 14d # 收货的过期时间\n      comment-expire-time: 7d # 评论的过期时间\n    express:\n      client: kd_niao\n      kd-niao:\n        api-key: cb022f1e-48f1-4c4a-a723-9001ac9676b8\n        business-id: 1809751\n      kd100:\n        key: pLXUGAwK5305\n        customer: E77DF18BE109F454A5CD319E44BF5177\n\ndebug: false\n\n# 积木报表配置\njeecg:\n  jmreport:\n    saas-mode: tenant"
  },
  {
    "path": "yshop-drink-boot3/yshop-server/src/main/resources/logback-spring.xml",
    "content": "<configuration>\n    <!-- 引用 Spring Boot 的 logback 基础配置 -->\n    <include resource=\"org/springframework/boot/logging/logback/defaults.xml\" />\n    <!-- 变量 yshop.info.base-package，基础业务包 -->\n    <springProperty scope=\"context\" name=\"yshop.info.base-package\" source=\"yshop.info.base-package\"/>\n    <!-- 格式化输出：%d 表示日期，%X{tid} SkWalking 链路追踪编号，%thread 表示线程名，%-5level：级别从左显示 5 个字符宽度，%msg：日志消息，%n是换行符 -->\n    <property name=\"PATTERN_DEFAULT\" value=\"%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}} | %highlight(${LOG_LEVEL_PATTERN:-%5p} ${PID:- }) | %boldYellow(%thread [%tid]) %boldGreen(%-40.40logger{39}) | %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}\"/>\n\n    <!-- 控制台 Appender -->\n    <appender name=\"STDOUT\" class=\"ch.qos.logback.core.ConsoleAppender\">　　　　　\n        <encoder class=\"ch.qos.logback.core.encoder.LayoutWrappingEncoder\">\n            <layout class=\"org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout\">\n                <pattern>${PATTERN_DEFAULT}</pattern>\n            </layout>\n        </encoder>\n    </appender>\n\n    <!-- 文件 Appender -->\n    <!-- 参考 Spring Boot 的 file-appender.xml 编写 -->\n    <appender name=\"FILE\"  class=\"ch.qos.logback.core.rolling.RollingFileAppender\">\n        <encoder class=\"ch.qos.logback.core.encoder.LayoutWrappingEncoder\">\n            <layout class=\"org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout\">\n                <pattern>${PATTERN_DEFAULT}</pattern>\n            </layout>\n        </encoder>\n        <!-- 日志文件名 -->\n        <file>${LOG_FILE}</file>\n        <rollingPolicy class=\"ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy\">\n            <!-- 滚动后的日志文件名 -->\n            <fileNamePattern>${LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN:-${LOG_FILE}.%d{yyyy-MM-dd}.%i.gz}</fileNamePattern>\n            <!-- 启动服务时，是否清理历史日志，一般不建议清理 -->\n            <cleanHistoryOnStart>${LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START:-false}</cleanHistoryOnStart>\n            <!-- 日志文件，到达多少容量，进行滚动 -->\n            <maxFileSize>${LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE:-10MB}</maxFileSize>\n            <!-- 日志文件的总大小，0 表示不限制 -->\n            <totalSizeCap>${LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP:-0}</totalSizeCap>\n            <!-- 日志文件的保留天数 -->\n            <maxHistory>${LOGBACK_ROLLINGPOLICY_MAX_HISTORY:-30}</maxHistory>\n        </rollingPolicy>\n    </appender>\n    <!-- 异步写入日志，提升性能 -->\n    <appender name=\"ASYNC\" class=\"ch.qos.logback.classic.AsyncAppender\">\n        <!-- 不丢失日志。默认的，如果队列的 80% 已满,则会丢弃 TRACT、DEBUG、INFO 级别的日志 -->\n        <discardingThreshold>0</discardingThreshold>\n        <!-- 更改默认的队列的深度，该值会影响性能。默认值为 256 -->\n        <queueSize>256</queueSize>\n        <appender-ref ref=\"FILE\"/>\n    </appender>\n\n    <!-- SkyWalking GRPC 日志收集，实现日志中心。注意：SkyWalking 8.4.0 版本开始支持 -->\n    <appender name=\"GRPC\" class=\"org.apache.skywalking.apm.toolkit.log.logback.v1.x.log.GRPCLogClientAppender\">\n        <encoder class=\"ch.qos.logback.core.encoder.LayoutWrappingEncoder\">\n            <layout class=\"org.apache.skywalking.apm.toolkit.log.logback.v1.x.TraceIdPatternLogbackLayout\">\n                <pattern>${PATTERN_DEFAULT}</pattern>\n            </layout>\n        </encoder>\n    </appender>\n\n    <!-- 本地环境 -->\n    <springProfile name=\"local\">\n        <root level=\"INFO\">\n            <appender-ref ref=\"STDOUT\"/>\n            <appender-ref ref=\"GRPC\"/> <!-- 本地环境下，如果不想接入 SkyWalking 日志服务，可以注释掉本行 -->\n            <appender-ref ref=\"ASYNC\"/>  <!-- 本地环境下，如果不想打印日志，可以注释掉本行 -->\n        </root>\n    </springProfile>\n    <!-- 其它环境 -->\n    <springProfile name=\"dev,test,stage,prod,default\">\n        <root level=\"INFO\">\n            <appender-ref ref=\"STDOUT\"/>\n            <appender-ref ref=\"ASYNC\"/>\n            <appender-ref ref=\"GRPC\"/>\n        </root>\n    </springProfile>\n\n</configuration>\n"
  },
  {
    "path": "yshop-drink-boot3/yshop-server/src/test/java/co/yixiang/yshop/ProjectReactor.java",
    "content": "package co.yixiang.yshop;\n\nimport cn.hutool.core.io.FileTypeUtil;\nimport cn.hutool.core.io.FileUtil;\nimport cn.hutool.core.util.StrUtil;\nimport co.yixiang.yshop.framework.common.util.collection.SetUtils;\nimport lombok.extern.slf4j.Slf4j;\n\nimport java.io.File;\nimport java.nio.charset.StandardCharsets;\nimport java.util.Collection;\nimport java.util.Set;\nimport java.util.regex.Matcher;\nimport java.util.stream.Collectors;\n\nimport static java.io.File.separator;\n\n/**\n * 项目修改器，一键替换 Maven 的 groupId、artifactId，项目的 package 等\n * <p>\n * 通过修改 groupIdNew、artifactIdNew、projectBaseDirNew 三个变量\n *\n * @author yshop\n */\n@Slf4j\npublic class ProjectReactor {\n\n    private static final String GROUP_ID = \"co.yixiang.boot\";\n    private static final String ARTIFACT_ID = \"yshop\";\n    private static final String PACKAGE_NAME = \"co.yixiang.yshop\";\n    private static final String TITLE = \"意象商城管理系统\";\n\n    /**\n     * 白名单文件，不进行重写，避免出问题\n     */\n    private static final Set<String> WHITE_FILE_TYPES = SetUtils.asSet(\"gif\", \"jpg\", \"svg\", \"png\", // 图片\n            \"eot\", \"woff2\", \"ttf\", \"woff\",  // 字体\n            \"xdb\"); // IP 库\n\n    public static void main(String[] args) {\n        long start = System.currentTimeMillis();\n        String projectBaseDir = getProjectBaseDir();\n        log.info(\"[main][原项目路劲改地址 ({})]\", projectBaseDir);\n\n        // ========== 配置，需要你手动修改 ==========\n        String groupIdNew = \"co.yixiang.boot\";\n        String artifactIdNew = \"yshop\";\n        String packageNameNew = \"co.yixiang.yshop\";\n        String titleNew = \"意象商城管理系统\";\n        String projectBaseDirNew = projectBaseDir + \"-new\"; // 一键改名后，“新”项目所在的目录\n        log.info(\"[main][检测新项目目录 ({})是否存在]\", projectBaseDirNew);\n        if (FileUtil.exist(projectBaseDirNew)) {\n            log.error(\"[main][新项目目录检测 ({})已存在，请更改新的目录！程序退出]\", projectBaseDirNew);\n            return;\n        }\n        // 如果新目录中存在 PACKAGE_NAME，ARTIFACT_ID 等关键字，路径会被替换，导致生成的文件不在预期目录\n        if (StrUtil.containsAny(projectBaseDirNew, PACKAGE_NAME, ARTIFACT_ID, StrUtil.upperFirst(ARTIFACT_ID))) {\n            log.error(\"[main][新项目目录 `projectBaseDirNew` 检测 ({}) 存在冲突名称「{}」或者「{}」，请更改新的目录！程序退出]\",\n                    projectBaseDirNew, PACKAGE_NAME, ARTIFACT_ID);\n            return;\n        }\n        log.info(\"[main][完成新项目目录检测，新项目路径地址 ({})]\", projectBaseDirNew);\n        // 获得需要复制的文件\n        log.info(\"[main][开始获得需要重写的文件，预计需要 10-20 秒]\");\n        Collection<File> files = listFiles(projectBaseDir);\n        log.info(\"[main][需要重写的文件数量：{}，预计需要 15-30 秒]\", files.size());\n        // 写入文件\n        files.forEach(file -> {\n            // 如果是白名单的文件类型，不进行重写，直接拷贝\n            String fileType = getFileType(file);\n            if (WHITE_FILE_TYPES.contains(fileType)) {\n                copyFile(file, projectBaseDir, projectBaseDirNew, packageNameNew, artifactIdNew);\n                return;\n            }\n            // 如果非白名单的文件类型，重写内容，在生成文件\n            String content = replaceFileContent(file, groupIdNew, artifactIdNew, packageNameNew, titleNew);\n            writeFile(file, content, projectBaseDir, projectBaseDirNew, packageNameNew, artifactIdNew);\n        });\n        log.info(\"[main][重写完成]共耗时：{} 秒\", (System.currentTimeMillis() - start) / 1000);\n    }\n\n    private static String getProjectBaseDir() {\n        String baseDir = System.getProperty(\"user.dir\");\n        if (StrUtil.isEmpty(baseDir)) {\n            throw new NullPointerException(\"项目基础路径不存在\");\n        }\n        return baseDir;\n    }\n\n    private static Collection<File> listFiles(String projectBaseDir) {\n        Collection<File> files = FileUtil.loopFiles(projectBaseDir);\n        // 移除 IDEA、Git 自身的文件、Node 编译出来的文件\n        files = files.stream()\n                .filter(file -> !file.getPath().contains(separator + \"target\" + separator)\n                        && !file.getPath().contains(separator + \"node_modules\" + separator)\n                        && !file.getPath().contains(separator + \".idea\" + separator)\n                        && !file.getPath().contains(separator + \".git\" + separator)\n                        && !file.getPath().contains(separator + \"dist\" + separator)\n                        && !file.getPath().contains(\".iml\")\n                        && !file.getPath().contains(\".html.gz\"))\n                .collect(Collectors.toList());\n        return files;\n    }\n\n    private static String replaceFileContent(File file, String groupIdNew,\n                                             String artifactIdNew, String packageNameNew,\n                                             String titleNew) {\n        String content = FileUtil.readString(file, StandardCharsets.UTF_8);\n        // 如果是白名单的文件类型，不进行重写\n        String fileType = getFileType(file);\n        if (WHITE_FILE_TYPES.contains(fileType)) {\n            return content;\n        }\n        // 执行文件内容都重写\n        return content.replaceAll(GROUP_ID, groupIdNew)\n                .replaceAll(PACKAGE_NAME, packageNameNew)\n                .replaceAll(ARTIFACT_ID, artifactIdNew) // 必须放在最后替换，因为 ARTIFACT_ID 太短！\n                .replaceAll(StrUtil.upperFirst(ARTIFACT_ID), StrUtil.upperFirst(artifactIdNew))\n                .replaceAll(TITLE, titleNew);\n    }\n\n    private static void writeFile(File file, String fileContent, String projectBaseDir,\n                                  String projectBaseDirNew, String packageNameNew, String artifactIdNew) {\n        String newPath = buildNewFilePath(file, projectBaseDir, projectBaseDirNew, packageNameNew, artifactIdNew);\n        FileUtil.writeUtf8String(fileContent, newPath);\n    }\n\n    private static void copyFile(File file, String projectBaseDir,\n                                 String projectBaseDirNew, String packageNameNew, String artifactIdNew) {\n        String newPath = buildNewFilePath(file, projectBaseDir, projectBaseDirNew, packageNameNew, artifactIdNew);\n        FileUtil.copyFile(file, new File(newPath));\n    }\n\n    private static String buildNewFilePath(File file, String projectBaseDir,\n                                           String projectBaseDirNew, String packageNameNew, String artifactIdNew) {\n        return file.getPath().replace(projectBaseDir, projectBaseDirNew) // 新目录\n                .replace(PACKAGE_NAME.replaceAll(\"\\\\.\", Matcher.quoteReplacement(separator)),\n                        packageNameNew.replaceAll(\"\\\\.\", Matcher.quoteReplacement(separator)))\n                .replace(ARTIFACT_ID, artifactIdNew) //\n                .replaceAll(StrUtil.upperFirst(ARTIFACT_ID), StrUtil.upperFirst(artifactIdNew));\n    }\n\n    private static String getFileType(File file) {\n        return file.length() > 0 ? FileTypeUtil.getType(file) : \"\";\n    }\n\n}\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/.gitignore",
    "content": "# See https://help.github.com/ignore-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n\n# testing\n/coverage\n\n# production\n/build\n\n/unpackage\n\n/dist\n\n# misc\n.DS_Store\n.cache\n\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n.tmp*\n.svn\n.tags\n*.sublime-*\nsftp-config.json\nlogs\n*.log\n.idea*\n.yo-rc.json\n*.swo\n*.swp\n/dist\n/deps\nyarn.lock\ndev-stats.json\n.vscode\n\n.history\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/.hbuilderx/launch.json",
    "content": "{\n    // launch.json 配置了启动调试时相关设置，configurations下节点名称可为 app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/\n    // launchtype项可配置值为local或remote, local代表前端连本地云函数，remote代表前端连云端云函数\n    \"version\" : \"0.0\",\n    \"configurations\" : [\n        {\n            \"app-plus\" : {\n                \"launchtype\" : \"local\"\n            },\n            \"default\" : {\n                \"launchtype\" : \"local\"\n            },\n            \"mp-weixin\" : {\n                \"launchtype\" : \"local\"\n            },\n            \"type\" : \"uniCloud\"\n        },\n        {\n            \"playground\" : \"standard\",\n            \"type\" : \"uni-app:app-ios\"\n        },\n        {\n            \"playground\" : \"custom\",\n            \"type\" : \"uni-app:app-android\"\n        }\n    ]\n}\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/App.vue",
    "content": "<script setup>\nimport { onHide,onLaunch,onShow } from '@dcloudio/uni-app'\nimport { storeToRefs } from 'pinia'\r\nimport { useMainStore } from '@/store/store'\r\nconst main = useMainStore()\nimport { isWeixin,parseQuery } from '@/utils/util'\nimport cookie from '@/utils/cookie'\nimport {\n  userAuthSession,\n  wechatAuth\r\n} from '@/api/auth'\nimport { APP_ID } from '@/config'\n\nonLaunch(() => {\r\n\tconsole.log('App Launch')\r\n})\n\nonShow(() => {\r\n    console.log('App Show')\n   \n\t// 检查用户登录情况\n\t// #ifdef H5\n\tif(isWeixin()){\n\t\toAuth()\n\t\t// H5编译的代码\n\t\t// 判断是否是微信浏览器\n\t}\n\t// #endif\n\t// #ifdef MP-WEIXIN\n\twechatMiniLogin();\n\t// #endif\r\n})\n\nonHide(() => {\r\n   console.log('App Hide')\r\n})\n\n\n\nconst wechatMiniLogin = () => {\n\t//this.$u.toast('登录中');\n\tuni.login({\n\t\tprovider: 'weixin'\n\t}).then(async (res) => {\n\t\tlet data = await userAuthSession({\n\t\t\tcode: res.code\n\t\t});\n\t\tif (data) {\n\t\t\tmain.SET_OPENID(data.openId)\n\t\t\tif (data.hasOwnProperty('userInfo') && data.accessToken && data.accessToken != '') {\n\t\t\t\tmain.SET_MEMBER(data.userInfo);\n\t\t\t\tmain.SET_TOKEN(data.accessToken);\n\t\t\t}\n\t\t}\r\n\t});\n}\n\nconst getAuthUrl = (appId) => {\n\t  // #ifdef H5\n\t  // #endif\n\t  cookie.set('redirect', window.location.href)\n\t  const url = `${location.origin}/h5/#/pages/index/index`\n\t  cookie.set('index_url',url)\n\t  let redirect_uri = encodeURIComponent(url)\n\t\n\t  const state = 'STATE'\n\t  return `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&redirect_uri=${redirect_uri}&response_type=code&scope=snsapi_userinfo&state=STATE#wechat_redirect`\n }\n \nconst oAuth = async() => {\n\t  const WX_AUTH = 'wx_auth'\n\t  return new Promise((resolve, reject) => {\n\t\t     \n\t\tconst accessToken = uni.getStorageSync('accessToken');\n\t\tif (cookie.has('wx_auth') && accessToken && main.isLogin) {\n\t\t  reject()\n\t\t  return\n\t\t}\n\t\tconst { code } = parseQuery()\n\t\tif (!code) {\n\t\t  //公众号appid\n\t\t  const appid = APP_ID;\n\t\t  location.href = getAuthUrl(appid)\n\t\t  return\n\t\t} else {\n\t\t auth(code)\n\t\t}\n\t\tresolve()\n\t  }).catch(error => {\n\t\tconsole.log(error)\n\t  })\n}\n\nconst auth = async(code) => {\n\tconsole.log('获取微信授权:',code)\n\tlet data = await wechatAuth({'code':code})\n\tcookie.set('wx_auth', code)\n\tif (data) {\n\t\tmain.SET_OPENID(data.openId)\n\t\tmain.SET_MEMBER(data.userInfo);\n\t\tmain.SET_TOKEN(data.accessToken);\n\t\t\n\t\n\t}\n\n}\n\n\n\r\n</script>\r\n\r\n<style lang=\"scss\">\n@import '~@/static/style/app.scss';\n//@import 'static/iconfont/iconfont.scss';\r\n//@import url('./static/style/style.less');\n@import 'static/style/yshop.css';\r\n\r\n// /*每个页面公共css */\r\n// page {\r\n//   background-color: #f5f5f5;\r\n// }\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/api/address.js",
    "content": "import api from './api'\n\n// 删除用户地址 \nexport function addressDelete(data) {\n  return api.post(`/address/del/${data.id}`, undefined, { login: true })\n}\n\n// 设置默认地址  \nexport function shopGetDistanceFromLocation(data) {\n  return api.post(`/address/getDistanceFromLocation`, data, { login: true })\n}\n\n// 添加或修改地址\nexport function getAddressAddAndEdit(data) {\n  return api.post(`/address/addAndEdit`, data, { login: true })\n}\n\n// 用户地址列表 l\nexport function addressAll(data) {\n  return api.get(`/address/list`, data, { login: true })\n}\n\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/api/api.js",
    "content": "// #ifdef H5\n// h5端\nimport Fly from 'flyio/dist/npm/fly'\n// #endif\n\n// #ifdef APP-PLUS\n// app端\nimport Fly from 'flyio/dist/npm/wx'\n// #endif\n\n// #ifdef MP-WEIXIN\nimport Fly from 'flyio/dist/npm/wx'\n// #endif\n\nimport { handleLoginFailure } from '@/utils'\nimport { isWeixin } from '@/utils/util'\nimport { VUE_APP_API_URL } from '@/config'\nimport cookie from '@/utils/cookie'\nimport { replace } from '@/utils/router'\n\nconst fly = new Fly()\nfly.config.baseURL = VUE_APP_API_URL\n\nfly.interceptors.response.use(\n  response => {\n    // console.log(response)\n    // 定时刷新access-token\n    return response\n  },\n  error => {\n    if (error.toString() == 'Error: Network Error') {\n      handleLoginFailure()\n      return Promise.reject({ msg: '未登录', toLogin: true })\n    }\n    if (error.status == 401) {\n      handleLoginFailure()\n      return Promise.reject({ msg: '未登录', toLogin: true })\n    }\n    if (error.response.data.status == 5109) {\n      uni.showToast({\n        title: error.response.data.msg,\n        icon: 'none',\n        duration: 2000,\n      })\n    }\n    return Promise.reject(error)\n  }\n)\n\nconst defaultOpt = { login: true }\n\nfunction baseRequest(options) {\n  const token = cookie.get('accessToken')\n   console.log('--> % token % token:\\n', token)\n\n  options.headers = {\n    ...options.headers,\n  }\n\n  // if (options.login === true) {\n  options.headers = {\n    ...options.headers,\n    Authorization: 'Bearer ' + token,\n  }\n  // }\n\n  // 结构请求需要的参数\n  const { url, params, data, login, ...option } = options\n\n  // 发起请求\n  return fly\n    .request(url, params || data, {\n      ...option,\n    })\n    .then(res => {\n     \n      const data = res.data || {}\n\t   //console.log('res.status:',res)\n    // console.log('res.code:',res.code)\n\t // #ifdef H5\n\t if (res.data.code == 1004004002) {\n\t\t if(isWeixin()){\n\t\t\tconst url = cookie.get('index_url')\n\t\t\tconsole.log('redirect_uri:',url)\n\t\t\t//const url = `${location.origin}/h5/#/pages/index/index`\n\t\t\tlocation.href = url\n\t\t\treturn\n\t\t}\n      }\n\t  // #endif\n\t  \n      if (res.status !== 200) {\n        return Promise.reject({ msg: '请求失败', res, data })\n      }\n\n    \n      if (data.code == 401) {\n        uni.hideLoading()\n        handleLoginFailure()\n        uni.showToast({\n          title: data.msg,\n          icon: 'none',\n          duration: 2000,\n        })\n        return Promise.reject({ msg: data.msg, res, data })\n      }\n\n      if (data.code != 0) {\n        uni.showToast({\n          title: data.msg,\n          icon: 'none',\n          duration: 2000,\n        })\n        return Promise.reject({ data, res })\n      }\n\n\n      return Promise.resolve(data.data, res)\n\n      // if ([401, 403].indexOf(data.status) !== -1) {\n      //   handleLoginFailure()\n      //   return Promise.reject({ msg: res.data.msg, res, data, toLogin: true })\n      // } else if (data.status === 200) {\n      //   return Promise.resolve(data, res)\n      // } else if (data.status == 5101) {\n      //   return Promise.reject({ msg: res.data.msg, res, data })\n      // } else {\n      //   return Promise.reject({ msg: res.data.msg, res, data })\n      // }\n    })\n}\n\n/**\n * http 请求基础类\n * 参考文档 https://www.kancloud.cn/yunye/axios/234845\n *\n */\nconst request = ['post', 'put', 'patch'].reduce((request, method) => {\n  /**\n   *\n   * @param url string 接口地址\n   * @param data object get参数\n   * @param options object axios 配置项\n   * @returns {AxiosPromise}\n   */\n  request[method] = (url, data = {}, options = {}) => {\n    console.log(url, data)\n    return baseRequest(Object.assign({ url, data, method }, defaultOpt, options))\n  }\n  return request\n}, {})\n\n;['get', 'delete', 'head'].forEach(method => {\n  /**\n   *\n   * @param url string 接口地址\n   * @param params object get参数\n   * @param options object axios 配置项\n   * @returns {AxiosPromise}\n   */\n  request[method] = (url, params = {}, options = {}) => {\n    return baseRequest(Object.assign({ url, params, method }, defaultOpt, options))\n  }\n})\n\nexport default request\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/api/auth.js",
    "content": "import api from './api'\n\n/**\n * 使用手机 + 验证码登录\n */\nexport function userLogin(data) {\n  return api.post('/member/auth/sms-login', data, { login: false })\n}\n\n/**\n * 使用手机 + 验证码登录  member/auth/send-sms-code\n */\nexport function smsSend(data) {\n  return api.post('/member/auth/send-sms-code', data, { login: false })\n}\n\n/**\n * 小程序 member/auth/auth-miniapp-login         \n */\nexport function userLoginForWechatMini(data) {\n  return api.post('/member/auth/auth-miniapp-login', data, { login: false })\n}\n\n/**\n * userAuthSession   \n */\nexport function userAuthSession(data) {\n  return api.post('/member/auth/auth-session', data, { login: false })\n}\n\n/**\n * wechatAuth   \n */\nexport function wechatAuth(data) {\n  return api.get('/member/auth/auth-wechat-login', data, { login: false })\n}\n\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/api/coupon.js",
    "content": "import api from './api'\n\n/**\n * couponReceive\n */\nexport function couponReceive(data) {\n  return api.post('/coupon/receive', data, { login: false })\n}\n\n/**\n * couponMine\n */\nexport function couponMine(data) {\n  return api.get(`/coupon/my`, data, { login: false })\n}\n\n/**\n * couponIndex let couponCount = (params = {}) => vm.$u.get('/coupon/count', params);\n */\nexport function couponIndexApi(data) {\n  return api.get(`/coupon/not`, data, { login: false })\n}\n\n/**\n * couponCount \n */\nexport function couponCount(data) {\n  return api.get(`/coupon/count`, data, { login: false })\n}\n\n\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/api/goods.js",
    "content": "import api from './api'\n\n/**\n * 获得banner列表 \n */\nexport function shopNearby(data) {\n  return api.get('/store/nearby', data, { login: false })\n}\n/**\n * 获取首页信息\n */\nexport function menuGoods(data) {\n  return api.get('/product/products', data, { login: false })\n}\n\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/api/market.js",
    "content": "import api from './api'\n\n/**\n * shopGetList \n */\nexport function shopGetList(data) {\n  return api.get('/store/list', data, { login: false })\n}\n\n\nexport function menuAds(data) {\n  return api.get('/ad/list', data, { login: false })\n}\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/api/order.js",
    "content": "import api from './api'\n\n/**\n * 订单列表  \n */\nexport function orderTakeFoods(data) {\n  return api.get('/order/list', data, { login: false })\n}\n\n/**\n * 订单创建  \n */\nexport function orderSubmit(data) {\n  return api.post(`/order/create`, data, { login: false })\n}\n\n\n\n/**\n * 订单列表  \n */\nexport function orderGetOrders(data) {\n  return api.get(`/order/list`, data, { login: false })\n}\n\n\n/**\n * 计算详情 \n */\nexport function orderDetail(data) {\n  return api.get(`/order/detail/${data}`, data, { login: false })\n}\n\n\n\n/**\n * 订单收货 \n */\nexport function orderReceive(data) {\n  return api.post(`/order/take`, data, { login: false })\n}\n\n/**\n * 订单退款 \n */\nexport function orderRefund(data) {\n  return api.post(`/order/refund`, data, { login: false })\n}\n\n\n/**\n * 订单支付 \n */\nexport function payUnify(data) {\n  return api.post(`/order/pay`, data, { login: false })\n}\n\n/**\n * getWechatConfig \t\n */\nexport function getWechatConfig() {\n  return api.get(`/member/wx-mp/create-jsapi-signature`, { url: location.href }, { login: false })\n}\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/api/score.js",
    "content": "import api from './api'\n\n/**\n * 详情 \n */\nexport function scoreShopDetail(data) {\n  return api.get('/score-product/detail', data, { login: false })\n}\n/**\n * 列表 \n */\nexport function scoreShopIndex(data) {\n  return api.get('/score-product/list', data, { login: false })\n}\n\n/**\n * 提交\n */\nexport function scoreShopExchange(data) {\n  return api.post('/score-order/submit', data, { login: false })\n}\n\n/**\n * 订单列表 \n */\nexport function scoreShopOrder(data) {\n  return api.get('/score-order/list', data, { login: false })\n}\n\n/**\n * 订单列表 \n */\nexport function scoreShopOrderDetail(data) {\n  return api.get('/score-order/detail', data, { login: false })\n}\n\n/**\n * 确认收货\n */\nexport function scoreShopReceive(data) {\n  return api.get('/score-order/take', data, { login: false })\n}\n\n\n/**\n * 查询物流 \n */\nexport function getLogistic(data) {\n  return api.get('/express/getLogistic', data, { login: false })\n}\n\n\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/api/user.js",
    "content": "import api from './api'\n\n/**\n * 基本信息 \n */\nexport function userGetUserInfo(data) {\n  return api.get('/member/user/get-info', data, { login: true })\n}\n\n/**\n * 获取菜单\n */\nexport function mineService(data) {\n  return api.get('/service/list', data, { login: true })\n}\n\n/**\n * 获取内容 \n */\nexport function mineServiceContent(data) {\n  return api.get('/service/content', data, { login: true })\n}\n\n\n/**\n * save \n */\nexport function userEdit(data) {\n  return api.post('/member/user/update-nickname', data, { login: true })\n}\n\n\n/**\n * balanceGetBillList \n */\nexport function balanceGetBillList(data) {\n  return api.get('/member/user/getBill', data, { login: true })\n}\n\n\n\n\n/**\n * 充值列表 \n */\nexport function balanceGetMoneyList(data) {\n  return api.get('/recharge/getMoneyList', data, { login: true })\n}\n\n/**\n * 充值 \n */\nexport function balanceRecharge(data) {\n  return api.post('/member/user/recharge', data, { login: true })\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/components/blank/blank.vue",
    "content": "<template>\n  <view\n    class=\"blank\"\n    :style=\"{ height: size + 'px' }\"\n  ></view>\n</template>\n\n<script setup>\nimport { ref } from 'vue';\nconst props = defineProps(['size'])\nconst size = ref(props.size)\n</script>\n\n<style lang=\"less\">\n</style>\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/components/card/card.vue",
    "content": "<template>\n  <view\n    :class=\"['card', props.class]\"\n    :style=\"{ width: width ? width + 'rpx' : '100%' }\"\n  >\n    <slot></slot>\n  </view>\n</template>\n\n<script setup>\nimport { ref } from 'vue';\nconst props = defineProps(['class', 'width'])\nconst className = ref(props.class)\nconst width = ref(props.width)\n</script>\n\n<style lang=\"less\">\n.card {\n  width: 100%;\n  background-color: #fff;\n  border-radius: 7.5rpx;\n  box-sizing: border-box;\n  overflow: hidden;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/components/city-select/city-select.vue",
    "content": "<template>\n  <view>\n    <text\n      class=\"uni-input\"\n      @tap=\"open\"\n    >{{ value }}</text>\n    <uv-popup\n      ref=\"popup\"\n      mode=\"bottom\"\n    >\n      <view class=\"cityselect\">\n        <view class=\"cityselect-header\">\n          <view class=\"cityselect-title\">\n            <text>请选择地址</text>\n          </view>\n          <view class=\"cityselect-nav\">\n            <view\n              class=\"item\"\n              v-if=\"provinceActive\"\n              @tap=\"changeNav(0)\"\n            >\n              <text>{{ provinceActive.name }}</text>\n            </view>\n            <view\n              class=\"item\"\n              v-if=\"cityActive\"\n              @tap=\"changeNav(1)\"\n            >\n              <text>{{ cityActive.name }}</text>\n            </view>\n            <view\n              class=\"item\"\n              v-if=\"districtActive\"\n              @tap=\"changeNav(2)\"\n            >\n              <text>{{ districtActive.name }}</text>\n            </view>\n            <view\n              class=\"item active\"\n              v-else\n            >\n              <text>请选择</text>\n            </view>\n          </view>\n        </view>\n        <view class=\"cityselect-content\">\n          <swiper\n            class=\"swiper\"\n            disable-touch=\"true\"\n            touchable=\"false\"\n            :current=\"current\"\n          >\n            <swiper-item>\n              <scroll-view\n                scroll-y\n                class=\"cityScroll\"\n              >\n                <view>\n                  <view\n                    class=\"cityselect-item\"\n                    v-for=\"(item, index) in province\"\n                    :key=\"index\"\n                    @tap=\"selectProvince(item, index)\"\n                  >\n                    <view\n                      class=\"cityselect-item-box\"\n                      :class=\"{ active: isProvinceActive(item) }\"\n                    >\n                      <text>{{ item.name }}</text>\n                    </view>\n                  </view>\n                </view>\n              </scroll-view>\n            </swiper-item>\n            <swiper-item>\n              <scroll-view\n                scroll-y\n                class=\"cityScroll\"\n              >\n                <view>\n                  <view\n                    class=\"cityselect-item\"\n                    v-for=\"(item, index) in city\"\n                    :key=\"index\"\n                    @tap=\"selectCity(item, index)\"\n                  >\n                    <view\n                      class=\"cityselect-item-box\"\n                      :class=\"{ active: isCityActive(item) }\"\n                    >\n                      <text>{{ item.name }}</text>\n                    </view>\n                  </view>\n                </view>\n              </scroll-view>\n            </swiper-item>\n            <swiper-item>\n              <scroll-view\n                scroll-y\n                class=\"cityScroll\"\n              >\n                <view>\n                  <view\n                    class=\"cityselect-item\"\n                    v-for=\"(item, index) in district\"\n                    :key=\"index\"\n                    @tap=\"selectDistrict(item, index)\"\n                  >\n                    <view\n                      class=\"cityselect-item-box\"\n                      :class=\"{ active: isDistrictActive(item) }\"\n                    >\n                      <text>{{ item.name }}</text>\n                    </view>\n                  </view>\n                </view>\n              </scroll-view>\n            </swiper-item>\n          </swiper>\n        </view>\n      </view>\n    </uv-popup>\n  </view>\n</template>\n\n<script setup>\n\nimport { ref, watch,onMounted } from \"vue\"\nconst props = defineProps(['items', 'defaultValue'])\n\nconst emit = defineEmits(['callback'])\n\n\nconst items = ref(props.items || [])\n// const defaultValue = ref(props.defaultValue)\n// console.log(\"--> % defaultValue:\\n\", defaultValue)\n\nconst value = ref(props.value || '请选择')\nconst province = ref(props.items)\nconst provinceActive = ref(null)\nconst city = ref([])\nconst cityActive = ref(null)\nconst district = ref([])\nconst districtActive = ref(null)\nconst current = ref(0)\nconst popup = ref(null)\n\n\nwatch(() => props.items, (next) => {\n  province.value = next\n})\n\nwatch(() => props.defaultValue, (next) => {\n  console.log(\"--> % defaultValue % next:\\n\", next)\n  value.value = `${next.province.name} ${next.city.name} ${next.district.name}`\n  setDefaultValue(items.value, next)\n})\n\n\nonMounted(() => {\n  // setDefaultValue(items, props.defaultValue)\n})\n\nconst isProvinceActive = (item) => {\n  return provinceActive.value && item.value == provinceActive.value.value\n}\n\nconst isCityActive = (item) => {\n  return cityActive.value && item.value == cityActive.value.value\n}\n\nconst isDistrictActive = (item) => {\n  return districtActive.value && item.value == districtActive.value.value\n}\n\nconst setDefaultValue = (items, value) => {\n  if (!items || !value) {\n    return\n  }\n  province.value = items\n  items.map(prov => {\n    console.log(\"--> % setDefaultValue % prov:\\n\", prov)\n    if (prov.name == value.province.name) {\n      city.value = prov.id\n      provinceActive.value = {\n        value: prov.id,\n        name: value.province.name,\n      }\n      prov.children.map(city => {\n        if (city.name == value.city.name) {\n          district.value = city.children\n          cityActive.value = {\n            value: city.id,\n            name: value.city.name,\n          }\n          city.children.map(district => {\n            if (district.name == value.district.name) {\n              districtActive.value = {\n                value: city.id,\n                name: value.district.name,\n              }\n            }\n          })\n        }\n      })\n    }\n  })\n  console.log(provinceActive.value, cityActive.value, districtActive.value)\n}\n\nconst open = () => {\n  province.value = props.items\n  provinceActive.value = null\n  cityActive.value = null\n  districtActive.value = null\n  city.value = []\n  district.value = []\n  current.value = 0\n  popup.value.open()\n\n  setDefaultValue(items.value, props.defaultValue.value)\n}\n\nconst changeNav = (index) => {\n  if (index == 0) {\n    provinceActive.value = null\n  }\n  if (index == 1) {\n    cityActive.value = null\n    districtActive.value = null\n  }\n  if (index == 2) {\n    districtActive.value = null\n  }\n  current.value = index\n}\n\nconst selectProvince = (selectItem, index) => {\n  provinceActive.value = selectItem\n  city.value = selectItem.children\n  current.value = 1\n}\n\nconst selectCity = (selectItem, index) => {\n  cityActive.value = selectItem\n  district.value = selectItem.children\n  current.value = 2\n}\n\nconst selectDistrict = (selectItem, index) => {\n  console.log(\"--> % selectDistrict % selectItem:\\n\", selectItem)\n  districtActive.value = selectItem\n  value.value = `${provinceActive.value?.name} ${cityActive.value?.name} ${districtActive.value?.name}`\n  console.log({\n    province: {\n      id: provinceActive.value?.id,\n      name: provinceActive.value?.name,\n    },\n    city: {\n      id: cityActive.value?.id,\n      name: cityActive.value?.name,\n    },\n    district: {\n      id: districtActive.value?.id,\n      name: districtActive.value?.name,\n    },\n  })\n  emit('callback', {\n    province: {\n      id: provinceActive.value?.id,\n      name: provinceActive.value?.name,\n    },\n    city: {\n      id: cityActive.value?.id,\n      name: cityActive.value?.name,\n    },\n    district: {\n      id: districtActive.value?.id,\n      name: districtActive.value?.name,\n    },\n  })\n  popup.value.close()\n}\n</script>\n\n<style lang=\"less\">\n.cityselect {\n  width: 100%;\n  height: 75%;\n  background-color: #fff;\n  z-index: 1502;\n  position: relative;\n  padding-bottom: 0;\n  padding-bottom: constant(safe-area-inset-bottom);\n  padding-bottom: env(safe-area-inset-bottom);\n\n  .cityScroll {\n    height: 100%;\n  }\n\n  .swiper {\n    height: 800rpx;\n  }\n}\n\n.cityselect-header {\n  width: 100%;\n  z-index: 1;\n}\n\n.cityselect-title {\n  width: 100%;\n  font-size: 30rpx;\n  text-align: center;\n  height: 95rpx;\n  line-height: 95rpx;\n  position: relative;\n\n  &:cityselect-title:after {\n    height: 1px;\n    position: absolute;\n    z-index: 0;\n    bottom: 0;\n    left: 0;\n    content: '';\n    width: 100%;\n    background-image: linear-gradient(0deg, #ececec 50%, transparent 0);\n  }\n}\n\n.cityselect-nav {\n  width: 100%;\n  padding-left: 20rpx;\n  overflow: hidden;\n  display: flex;\n  align-items: center;\n  justify-content: flex-start;\n\n  .item {\n    font-size: 26rpx;\n    color: #222;\n    display: block;\n    height: 80rpx;\n    line-height: 92rpx;\n    padding: 0 16rpx;\n    position: relative;\n    margin-right: 30rpx;\n    white-space: nowrap;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    max-width: 40%;\n\n    &.active {\n      color: #f23030 !important;\n      border-bottom: 1rpx solid #f23030;\n    }\n  }\n}\n\n.cityselect-content {\n  height: 100%;\n  width: 100%;\n}\n\n.cityselect-item {\n  .cityselect-item-box {\n    display: block;\n    padding: 0 40rpx;\n    position: relative;\n    overflow: hidden;\n    display: -webkit-box;\n    -webkit-line-clamp: 2;\n    -webkit-box-orient: vertical;\n    word-break: break-all;\n    text-overflow: ellipsis;\n    line-height: 64rpx;\n    max-height: 65rpx;\n    font-size: 26rpx;\n    color: #333;\n\n    &.active {\n      color: #f23030 !important;\n    }\n\n    &:after {\n      content: '';\n      height: 1rpx;\n      position: absolute;\n      z-index: 0;\n      bottom: 0;\n      left: 0;\n      width: 100%;\n      background-image: linear-gradient(0deg, #ececec 50%, transparent 0);\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/components/container/container.vue",
    "content": "<template>\n  <view :class=\"['container', min ? 'container-min' : '']\">\n    <slot></slot>\n  </view>\n</template>\n\n<script setup>\nimport { ref, } from 'vue';\nconst props = defineProps(['min'])\nconst min = ref(props?.min != undefined)\n</script>\n\n<style lang=\"less\">\n.container {\n  padding: 0 34rpx;\n\n  &-min {\n    padding: 0 10rpx;\n\n  }\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/components/layout/layout.vue",
    "content": "<template>\n  <view class=\"layout\">\n    <slot />\n  </view>\n</template>\n\n<script setup>\nimport { ref, defineProps, reactive } from 'vue';\nconst props = defineProps(['size'])\n</script>\n\n<style lang=\"less\">\n.layout {\n  min-height: 100%;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/components/list-cell/list-cell.vue",
    "content": "<template>\n\t<view\n\t\tclass=\"tui-cell-class tui-list-cell\"\n\t\t:class=\"{ 'tui-cell-arrow': arrow, 'tui-cell-last': last, 'tui-line-left': lineLeft, 'tui-line-right': lineRight, 'tui-radius': radius }\"\n\t\t:hover-class=\"hover ? 'tui-cell-hover' : ''\"\n\t\t:style=\"{ background: bgcolor, fontSize: size + 'rpx', color: color, padding: padding}\"\n\t\t:hover-stay-time=\"150\"\n\t\t@tap=\"handleClick\"\n\t>\n\t\t<slot></slot>\n\t\t<image src=\"/static/images/navigator-1.png\" class=\"arrow\" v-if=\"arrow\"></image>\n\t\t<!-- <view class=\"iconfont iconarrow-right arrow\" v-if=\"arrow\"></view> -->\n\t</view>\n</template>\n\n<script setup>\nimport { ref } from 'vue'\nconst props = defineProps ({\n\t //是否有箭头\n\t arrow: {\n\t \ttype: Boolean,\n\t \tdefault: false\n\t },\n\t //是否有点击效果\n\t hover: {\n\t \ttype: Boolean,\n\t \tdefault: true\n\t },\n\t //left 30rpx\n\t lineLeft:{\n\t \ttype: Boolean,\n\t \tdefault: false\n\t },\n\t //right 30rpx\n\t lineRight:{\n\t \ttype: Boolean,\n\t \tdefault: false\n\t },\n\t padding:{\n\t \ttype:String,\n\t \tdefault:\"26rpx 30rpx\"\n\t },\n\t last: {\n\t \ttype: Boolean,\n\t \tdefault: false //最后一条数据隐藏线条\n\t },\n\t radius:{\n\t \ttype:Boolean,\n\t \tdefault:false\n\t },\n\t bgcolor: {\n\t \ttype: String,\n\t \tdefault: \"#fff\" //背景颜色\n\t },\n\t size: {\n\t \ttype: Number,\n\t \tdefault: 28 //字体大小\n\t },\n\t color: {\n\t \ttype: String,\n\t \tdefault: \"#5A5B5C\" //字体颜色\n\t },\n\t index: {\n\t \ttype: Number,\n\t \tdefault: 0\n\t }\n})\nconst emit = defineEmits(['click'])\nconst handleClick = () => {\n\temit('click', {\n\t\tindex: props.index\n\t});\n}\n\n</script>\n\n<style lang=\"scss\" scoped>\n.tui-list-cell {\n\tposition: relative;\n\twidth: 100%;\n\tbox-sizing: border-box;\n\toverflow: hidden;\n\tdisplay: flex;\n\talign-items: center;\n}\n.tui-radius {\n\tborder-radius: 12rpx;\n\toverflow: hidden;\n}\n\n.tui-cell-hover {\n\tbackground: #f7f7f9 !important;\n}\n\n.tui-list-cell::after {\n\tcontent: '';\n\tposition: absolute;\n\tborder-bottom: 2rpx solid #eee;\n\t-webkit-transform: scaleY(0.8);\n\ttransform: scaleY(0.8);\n\tbottom: 0;\n\tright: 0;\n\tleft: 0;\n}\n.tui-line-left::after {\n\tleft: 30rpx !important;\n}\n\n.tui-line-right::after {\n\tright: 30rpx !important;\n}\n\n.tui-cell-last::after {\n\tborder-bottom: 0 !important;\n}\n\n// .arrow {\n// \tfont-size: 44rpx;\n// \tline-height: 100%;\n// \tcolor: $text-color-grey;\n// \tposition: relative;\n// \tmargin-right: -12rpx;\n// }\n.arrow {\n\twidth: 50rpx;\n\theight: 50rpx;\n\tposition: relative;\n\tmargin-right: -10rpx;\n\tflex-shrink: 0;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/components/logo/logo.vue",
    "content": "<template>\n\t<view>\n\t\t\n\t</view>\n</template>\n\n<script>\n\texport default {\n\t\tname:\"logo\",\n\t\tdata() {\n\t\t\treturn {\n\t\t\t\t\n\t\t\t};\n\t\t}\n\t}\n</script>\n\n<style lang=\"less\">\n\n</style>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/components/modal/modal.vue",
    "content": "<template>\n\t<view @touchmove.stop.prevent>\n\t\t<view class=\"modal-box\" :style=\"{width:props.width,padding:props.padding,borderRadius:props.radius}\" :class=\"[(fadein || props.show)?'modal-normal':'modal-scale',props.show?'modal-show':'']\">\n\t\t\t<view v-if=\"props.custom\">\n\t\t\t\t<slot></slot>\n\t\t\t</view>\n\t\t\t<view v-else>\n\t\t\t\t<view class=\"modal-title\" v-if=\"props.title\">{{props.title}}</view>\n\t\t\t\t<view class=\"modal-content\" :class=\"[props.title?'':'mtop']\" :style=\"{color:props.color,fontSize:props.size+'rpx'}\">\n\t\t\t\t\t<slot></slot>\n\t\t\t\t</view>\n\t\t\t\t<view class=\"modalBtn-box\" :class=\"[props.button.length!=2?'flex-column':'']\">\n\t\t\t\t\t<block v-for=\"(item,index) in props.button\" :key=\"index\">\n\t\t\t\t\t\t<button class=\"modal-btn\" \n\t\t\t\t\t\t\t\t:class=\"[\n\t\t\t\t\t\t\t\t\t\t''+(item.type || 'primary')+(item.plain?'-outline':''),\n\t\t\t\t\t\t\t\t\t\tprops.button.length!=2?'btn-width':'',\n\t\t\t\t\t\t\t\t\t\tprops.button.length>2?'mbtm':'',\n\t\t\t\t\t\t\t\t\t\tprops.shape=='circle'?'circle-btn':'',\n\t\t\t\t\t\t\t\t\t\t'btn-' + (item.size || 'default'),\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t\t\t]\"\n\t\t\t\t\t\t\t\t:hover-class=\"''+(item.plain?'outline':(item.type || 'primary'))+'-hover'\" :data-index=\"index\" @tap=\"handleClick\">{{item.text || \"确定\"}}</button>\n\t\t\t\t\t</block>\n\t\t\t\t</view>\n\t\t\t</view>\n\t\t</view>\n\t\t<view class=\"modal-mask\" :class=\"[props.show?'mask-show':'']\" @tap=\"handleClickCancel\"></view>\n\t</view>\n</template>\n\n<script setup>\nimport { ref } from 'vue'\n//const props = defineProps(['show','custom','width','padding','radius','title','content','color','size','shape','button','maskClosable','fadein'])\nconst props = defineProps ({\n\t show: {type: Boolean,default: false},\n\t custom: {type: Boolean,default: false},\n\t width: {type: String,default: \"84%\"},\n\t padding: {type: String,default: \"40rpx\"},\n\t radius: {type: String,default: \"24rpx\"},\n\t title: {type: String,default: \"\"},\n\t content: {type: String,default: \"\"},\n\t color: {type: String,default: \"#999\"},\n\t size: {type: Number,default: 28},\n\t shape: {type: String,default: 'square'}, //形状 circle, square\n\t button: {type: Array,\n\t\t\t\tdefault: function() {\n\t\t\t\t\treturn [{\n\t\t\t\t\t\ttext: \"取消\",\n\t\t\t\t\t\ttype: \"red\",\n\t\t\t\t\t\tplain: true //是否空心\n\t\t\t\t\t}, {\n\t\t\t\t\t\ttext: \"确定\",\n\t\t\t\t\t\ttype: \"red\",\n\t\t\t\t\t\tplain: false\n\t\t\t\t\t}]\n\t\t\t}},\n\tmaskClosable: {type: Boolean,default: true},\n\tcustom: {type: Boolean,default: false},\n\tfadein: {type: Boolean,default: false} //淡入效果，自定义弹框插入input输入框时传true\n})\n\n// const show = ref(props.show)\n// const maskClosable = ref(props.maskClosable)\nconst emit = defineEmits(['click','cancel','test'])\nconst handleClick = (e) => {\n\tif (!props.show) return;\n\tconst dataset = e.currentTarget.dataset;\n\temit('click', {\n\t\tindex: Number(dataset.index)\n\t});\n}\nconst handleClickCancel = () => {\n\tif (!props.maskClosable) return;\n\temit('cancel');\n}\n\n\n\n\t\n</script>\n\n<style lang=\"scss\">\n\t.modal-box {\n\t\tposition: fixed;\n\t\tleft: 50%;\n\t\ttop: 50%;\n\t\tmargin: auto;\n\t\tbackground: #fff;\n\t\tz-index: 201;\n\t\ttransition: all 0.3s ease-in-out;\n\t\topacity: 0;\n\t\tbox-sizing: border-box;\n\t\tvisibility: hidden;\n\t}\n\n\t.modal-scale {\n\t\ttransform: translate(-50%, -50%) scale(0);\n\t}\n\n\t.modal-normal {\n\t\ttransform: translate(-50%, -50%) scale(1);\n\t}\n\n\t.modal-show {\n\t\topacity: 1;\n\t\tvisibility: visible;\n\t}\n\n\t.modal-mask {\n\t\tposition: fixed;\n\t\ttop: 0;\n\t\tleft: 0;\n\t\tright: 0;\n\t\tbottom: 0;\n\t\tbackground: rgba(0, 0, 0, 0.6);\n\t\tz-index: 98;\n\t\ttransition: all 0.3s ease-in-out;\n\t\topacity: 0;\n\t\tvisibility: hidden;\n\t}\n\n\t.mask-show {\n\t\tvisibility: visible;\n\t\topacity: 1;\n\t}\n\n\t.modal-title {\n\t\ttext-align: center;\n\t\tfont-size: 34rpx;\n\t\tcolor: #333;\n\t\tpadding-top: 20rpx;\n\t\tfont-weight: bold;\n\t}\n\n\t.modal-content {\n\t\tcolor: #999;\n\t\tfont-size: 28rpx;\n\t\tpadding-top: 20rpx;\n\t\tpadding-bottom: 60rpx;\n\t}\n\n\t.mtop {\n\t\tmargin-top: 30rpx;\n\t}\n\n\t.mbtm {\n\t\tmargin-bottom: 30rpx;\n\t}\n\n\t.modalBtn-box {\n\t\twidth: 100%;\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tjustify-content: space-between\n\t}\n\n\t.flex-column {\n\t\tflex-direction: column;\n\t}\n\n\t.modal-btn {\n\t\twidth: 46%;\n\t\theight: 68rpx;\n\t\tline-height: 68rpx;\n\t\tposition: relative;\n\t\tborder-radius: 60rpx;\n\t\tfont-size: 28rpx;\n\t\toverflow: visible;\n\t\tmargin-left: 0;\n\t\tmargin-right: 0;\n\t\t\n\t\t&.btn-default {\n\t\t\tfont-size: 28rpx;\n\t\t}\n\t\t\n\t\t&.btn-lg {\n\t\t\tfont-size: 32rpx;\n\t\t}\n\t\t\n\t\t&.btn-sm {\n\t\t\tfont-size: 24rpx;\n\t\t}\n\t}\n\n\t.modal-btn::after {\n\t\tcontent: \"\";\n\t\tposition: absolute;\n\t\twidth: 200%;\n\t\theight: 200%;\n\t\t-webkit-transform-origin: 0 0;\n\t\ttransform-origin: 0 0;\n\t\t-webkit-transform: scale(0.5, 0.5);\n\t\ttransform: scale(0.5, 0.5);\n\t\tleft: 0;\n\t\ttop: 0;\n\t\tborder-radius: 60rpx;\n\t}\n\n\t.btn-width {\n\t\twidth: 80% !important;\n\t}\n\n\t.primary {\n\t\tbackground: #97AF13;\n\t\tcolor: #fff;\n\t}\n\n\t.primary-hover {\n\t\tbackground: #97AF13;\n\t\tcolor: #e5e5e5;\n\t}\n\n\t.primary-outline {\n\t\tcolor: #97AF13;\n\t\tbackground: none;\n\t}\n\n\t.primary-outline::after {\n\t\tborder: 1px solid #97AF13;\n\t}\n\n\t.danger {\n\t\tbackground: #ed3f14;\n\t\tcolor: #fff;\n\t}\n\n\t.danger-hover {\n\t\tbackground: #d53912;\n\t\tcolor: #e5e5e5;\n\t}\n\n\t.danger-outline {\n\t\tcolor: #ed3f14;\n\t\tbackground: none;\n\t}\n\n\t.danger-outline::after {\n\t\tborder: 1px solid #ed3f14;\n\t}\n\n\t.red {\n\t\tbackground: #e41f19;\n\t\tcolor: #fff;\n\t}\n\n\t.red-hover {\n\t\tbackground: #c51a15;\n\t\tcolor: #e5e5e5;\n\t}\n\n\t.red-outline {\n\t\tcolor: #e41f19;\n\t\tbackground: none;\n\t}\n\n\t.red-outline::after {\n\t\tborder: 1px solid #e41f19;\n\t}\n\n\t.warning {\n\t\tbackground: #ff7900;\n\t\tcolor: #fff;\n\t}\n\n\t.warning-hover {\n\t\tbackground: #e56d00;\n\t\tcolor: #e5e5e5;\n\t}\n\n\t.warning-outline {\n\t\tcolor: #ff7900;\n\t\tbackground: none;\n\t}\n\n\t.warning-outline::after {\n\t\tborder: 1px solid #ff7900;\n\t}\n\n\t.green {\n\t\tbackground: #19be6b;\n\t\tcolor: #fff;\n\t}\n\n\t.green-hover {\n\t\tbackground: #16ab60;\n\t\tcolor: #e5e5e5;\n\t}\n\n\t.green-outline {\n\t\tcolor: #19be6b;\n\t\tbackground: none;\n\t}\n\n\t.green-outline::after {\n\t\tborder: 1px solid #19be6b;\n\t}\n\n\t.white {\n\t\tbackground: #fff;\n\t\tcolor: #333;\n\t}\n\n\t.white-hover {\n\t\tbackground: #f7f7f9;\n\t\tcolor: #666;\n\t}\n\n\t.white-outline {\n\t\tcolor: #333;\n\t\tbackground: none;\n\t}\n\n\t.white-outline::after {\n\t\tborder: 1px solid #333;\n\t}\n\n\t.gray {\n\t\tbackground: #ededed;\n\t\tcolor: #999;\n\t}\n\n\t.gray-hover {\n\t\tbackground: #d5d5d5;\n\t\tcolor: #898989;\n\t}\n\n\t.gray-outline {\n\t\tcolor: #999;\n\t\tbackground: none;\n\t}\n\n\t.gray-outline::after {\n\t\tborder: 1px solid #999;\n\t}\n\n\t.outline-hover {\n\t\topacity: 0.6;\n\t}\n\n\t.circle-btn {\n\t\tborder-radius: 40rpx !important;\n\t}\n\n\t.circle-btn::after {\n\t\tborder-radius: 80rpx !important;\n\t}\n</style>\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/components/space/space.vue",
    "content": "<template>\r\n  <view\r\n    :class=\"classObject\"\r\n    :style=\"[getStyle]\"\r\n  >\r\n    <slot></slot>\r\n  </view>\r\n</template>\r\n\r\n<script setup>\r\nimport { ref, watch, onMounted } from \"vue\"\r\nconst props = defineProps([\r\n  'direction',\r\n  'align',\r\n  'wrap',\r\n  'justify',\r\n  'size',\r\n  'height',\r\n  'padding',\r\n  'flex',\r\n  'border'\r\n])\r\n\r\nconsole.log(\"--> % props:\\n\", props)\r\n\r\n\r\nconst classObject = ref({})\r\n\r\nconst handleClassObject = (props) => {\r\n  console.log(\"--> % handleClassObject % props:\\n\", props.value)\r\n  let className = 'yshop-space'\r\n  let direction = props.direction\r\n  let align = props.align\r\n  let wrap = props.wrap\r\n  let justify = props.justify\r\n  let flex = props.flex\r\n  let border = props.border\r\n  if (border) {\r\n    className += ' yshop-space-border'\r\n  }\r\n  if (direction) {\r\n    className += ` yshop-space-${direction}`\r\n  }\r\n  if (wrap) {\r\n    className += ` yshop-space-${wrap}`\r\n  }\r\n  if (align) {\r\n    className += ` yshop-space-align-${align}`\r\n  }\r\n  if (justify) {\r\n    className += ` yshop-space-justify-${justify}`\r\n  }\r\n  console.log(\"--> % handleClassObject % className:\\n\", className)\r\n\r\n  classObject.value = className\r\n}\r\n\r\nconst getStyle = ({\r\n  size = 6,\r\n  wrap,\r\n  height,\r\n  padding,\r\n  flex\r\n}) => {\r\n  let innerStyle = {}\r\n  // let size = size.value\r\n  // let wrap = wrap.value\r\n  // let height = height.value\r\n  // let padding = padding.value\r\n\r\n  if (height) {\r\n    innerStyle.height = `${height}rpx`\r\n  }\r\n  if (typeof size === 'number') {\r\n    innerStyle.gap = size + 'px'\r\n  }\r\n\r\n\r\n  if (wrap) {\r\n    innerStyle.flexWrap = 'wrap'\r\n  }\r\n\r\n  if (typeof size === 'string') {\r\n    switch (size) {\r\n      case 'small':\r\n        innerStyle.gap = '8rpx'\r\n        break\r\n      case 'middle':\r\n        innerStyle.gap = '16rpx'\r\n        break\r\n      case 'large':\r\n        innerStyle.gap = '24rpx'\r\n        break\r\n    }\r\n  }\r\n\r\n\r\n  if (typeof padding === 'string') {\r\n    innerStyle.padding = `${padding}rpx`\r\n  }\r\n\r\n  if (Object.prototype.toString.call(padding) === '[object Array]') {\r\n    if (typeof padding === 'object') {\r\n      if (padding.length == 1) {\r\n        innerStyle.padding = `${padding[0]}rpx`\r\n      }\r\n      if (padding.length == 2) {\r\n        innerStyle.padding = `${padding[0]}rpx ${padding[1]}rpx`\r\n      }\r\n    }\r\n  }\r\n\r\n  if (Object.prototype.toString.call(size) === '[object Array]') {\r\n    if (typeof size === 'object') {\r\n      if (size.length == 1) {\r\n        innerStyle.gap = `${size[0]}rpx`\r\n      }\r\n      if (size.length == 2) {\r\n        innerStyle.gap = `${size[0]}rpx ${size[1]}rpx`\r\n      }\r\n    }\r\n  }\r\n\r\n\r\n\r\n  if (flex) {\r\n    innerStyle.flex = flex\r\n  }\r\n\r\n  return innerStyle\r\n}\r\n\r\nonMounted(() => {\r\n  handleClassObject(props)\r\n  getStyle(props)\r\n})\r\n\r\n</script>\r\n\r\n<style lang=\"less\">\r\nspace {\r\n  width: 100%;\r\n}\r\n\r\n.yshop-space {\r\n  display: flex;\r\n  width: 100%;\r\n  flex: 1;\r\n\r\n  &-vertical {\r\n    flex-direction: column;\r\n  }\r\n\r\n  &-align-center {\r\n    align-items: center;\r\n  }\r\n\r\n  &-align-start {\r\n    align-items: flex-start;\r\n  }\r\n\r\n  &-align-end {\r\n    align-items: flex-end;\r\n  }\r\n\r\n  &-justify-center {\r\n    justify-content: center;\r\n  }\r\n\r\n  &-justify-between {\r\n    justify-content: space-between;\r\n  }\r\n\r\n  &-justify-around {\r\n    justify-content: space-around;\r\n  }\r\n\r\n  &-align-end {\r\n    align-items: flex-end;\r\n  }\r\n\r\n  &-align-baseline {\r\n    align-items: baseline;\r\n  }\r\n\r\n  &-item:empty {\r\n    display: none;\r\n  }\r\n\r\n  &-border {\r\n    border-bottom: 1rpx solid #f9f9f9;\r\n  }\r\n}\r\n\r\n// #ifdef APP-PLUS\r\n.yshop-space {\r\n  &>view {\r\n    margin-right: 8rpx;\r\n  }\r\n\r\n  &>text {\r\n    margin-right: 8rpx;\r\n  }\r\n\r\n  &>image {\r\n    margin-right: 8rpx;\r\n  }\r\n\r\n  .u-tag-wrapper {\r\n    margin-right: 8rpx;\r\n  }\r\n\r\n  .u-tag {\r\n    margin-right: 8rpx;\r\n  }\r\n\r\n}\r\n\r\n// #endif\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/components/upload-file/upload-file.vue",
    "content": "<template>\n  <uv-upload\n    :fileList=\"list\"\n    name=\"1\"\n    multiple\n    :maxCount=\"10\"\n    @afterRead=\"afterRead\"\n    @delete=\"handleDeletePic\"\n  ></uv-upload>\n</template>\n\n<script setup>\nimport { VUE_APP_UPLOAD_URL } from '@/config';\nimport { ref } from 'vue';\n\nconst props = defineProps(['modelValue'])\nconsole.log(\"--> % modelValue:\\n\", props.modelValue)\nconst emit = defineEmits(['update:modelValue'])\n\nconst list = ref(props.modelValue)\n\nconst handleDeletePic = (event) => {\n  list.value.splice(event.index, 1)\n\n  emit('update:modelValue', list.value)\n}\n\nconst afterRead = async (event) => {\n  // 当设置 multiple 为 true 时, file 为数组格式，否则为对象格式\n  let lists = [].concat(event.file)\n  let fileListLen = list.value.length\n  lists.map((item) => {\n    list.value.push({\n      ...item,\n      status: 'uploading',\n      message: '上传中'\n    })\n  })\n  for (let i = 0; i < lists.length; i++) {\n    const result = await uploadFilePromise(lists[i].url)\n    console.log(\"gxs --> % afterRead % result:\\n\", result)\n    let item = list.value[fileListLen]\n    list.value.splice(fileListLen, 1, Object.assign(item, {\n      status: 'success',\n      message: '',\n      url: result\n    }))\n    fileListLen++\n  }\n  emit('update:modelValue', list.value)\n}\n\nconst uploadFilePromise = (url) => {\n  return new Promise((resolve, reject) => {\n    let a = uni.uploadFile({\n      url: VUE_APP_UPLOAD_URL, // 仅为示例，非真实的接口地址\n      filePath: url,\n      name: 'file',\n      formData: {\n        user: 'test'\n      },\n      success: (res) => {\n        console.log(\"gxs --> % returnnewPromise % res:\\n\", res)\n        setTimeout(() => {\n          resolve(res.data.data)\n        }, 10)\n      }\n    });\n  })\n}\n\n</script>\n\n<style lang=\"less\">\n.activity {\n\n  &-header {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    margin-bottom: 20rpx;\n\n    &-info {\n      display: flex;\n      align-items: flex-end;\n    }\n\n    &-title {\n      line-height: 45rpx;\n      font-size: 32rpx;\n      color: #333333;\n    }\n\n    &-subtitle {\n      margin-left: 10rpx;\n\n      line-height: 33rpx;\n      font-size: 24rpx;\n      color: #EE6D46;\n    }\n\n    &-more {\n      display: flex;\n      align-items: center;\n\n\n      &-info {\n        line-height: 33rpx;\n        font-size: 24rpx;\n        color: #999999;\n      }\n\n      .image {\n        margin-left: 10rpx;\n        display: block;\n        width: 20rpx;\n        height: 20rpx;\n      }\n    }\n\n    &-body {}\n\n  }\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/components/verification/verification.vue",
    "content": "<template>\n  <uv-button\n    round\n    size=\"mini\"\n    block\n    type=\"primary\"\n    @click=\"startCountdown\"\n  >\n    {{ buttonText }}\n  </uv-button>\n</template>\n\n<script setup>\nimport {\n  ref,\n  computed,\n  onUnmounted\n} from 'vue'\nimport {\n  sendSmsCode\n} from '@/api/auth'\n\nconst props = defineProps(['mobile', 'scene'])\n\nconst countingDown = ref(false); // 是否正在倒计时\nconst countdownSeconds = ref(60); // 倒计时的总秒数\nconst timer = ref(null); // 倒计时的总秒数\n\nconst buttonText = computed(() => {\n  return countingDown.value ? `${countdownSeconds.value} 秒` : '发送验证码';\n});\n\nconst startCountdown = () => {\n  if (!countingDown.value) {\n    countingDown.value = true;\n    handleSendSmsCode()\n  }\n};\n\n\nconst handleSendSmsCode = () => {\n  uni.showLoading({\n    title: '发送验证码中'\n  });\n  console.log(\"gxs --> % handleSendSmsCode % props.mobile:\\n\", props.mobile)\n  sendSmsCode({\n    \"mobile\": props.mobile,\n    \"scene\": props.scene\n  }).then(res => {\n    uni.hideLoading();\n    uni.showToast({\n      icon: 'none',\n      title: '验证码已发送',\n      duration: 2000\n    });\n    timer.value = setInterval(() => {\n      countdownSeconds.value--;\n      console.log(\"gxs --> % timer % countdownSeconds.value:\\n\", countdownSeconds.value)\n      if (countdownSeconds.value <= 0) {\n        clearInterval(timer.value);\n        countdownSeconds.value = 60; // 倒计时结束后重置为初始值\n        countingDown.value = false;\n      }\n    }, 1000);\n  }).catch(error => {\n    countingDown.value = false;\n\n  })\n}\n\nonUnmounted(() => {\n  clearInterval(timer.value);\n})\n</script>\n\n<style lang=\"less\">\n.goods {\n  position: relative;\n  padding: 30rpx 0;\n\n  &-card {\n    display: flex;\n    flex-direction: column;\n\n    .goods {\n      &-content {\n        padding: 0 20rpx;\n        display: flex;\n        flex-direction: column;\n      }\n\n      &-info {\n        margin-top: 15rpx;\n      }\n\n      &-thumb {\n        margin-bottom: 15rpx;\n        width: 100%;\n        height: 203rpx;\n\n        &-img {\n          width: 100%;\n          height: 100%;\n          display: block;\n        }\n      }\n\n    }\n  }\n\n  &-header {}\n\n  &-thumb {\n    background: #FAFAFA;\n  }\n\n  &-content {}\n\n  &-storeName {\n    line-height: 40rpx;\n    font-size: 28rpx;\n    font-weight: 500;\n    color: #333333;\n  }\n\n\n\n  &-price {\n    &-row {\n      display: flex;\n      align-items: center;\n\n      .goods-price {}\n    }\n\n    &-primary {\n      line-height: 42rpx;\n      font-size: 30rpx;\n      font-weight: 500;\n      color: #EE6D46;\n    }\n\n    &-default {\n      line-height: 40rpx;\n      font-size: 28rpx;\n      font-weight: 500;\n      color: #333333;\n    }\n\n    &-original {\n      margin-left: 9rpx;\n      line-height: 28rpx;\n      font-size: 20rpx;\n      color: #999999;\n      text-decoration: line-through;\n    }\n\n  }\n\n\n  &-desc {\n    line-height: 33rpx;\n    font-size: 24rpx;\n    color: #999999;\n  }\n\n  &-info {\n    display: flex;\n    align-items: flex-end;\n    justify-content: space-between;\n\n    &-left {}\n\n    &-action {\n      &-btn {}\n\n      &-desc {\n        line-height: 28rpx;\n        font-size: 20rpx;\n        color: #999999;\n      }\n    }\n\n\n  }\n\n  &-image {\n    &-img {}\n  }\n\n  &-list {\n    display: flex;\n    flex-direction: row;\n    padding: 14rpx;\n    box-sizing: border-box;\n    width: 100%;\n\n    .goods {\n      &-thumb {\n        margin-bottom: 0;\n        width: 220rpx;\n        height: 220rpx;\n\n        &-img {\n          width: 100%;\n          height: 100%;\n          display: block;\n        }\n      }\n\n      &-content {\n        padding-right: 40rpx;\n        margin-left: 30rpx;\n        flex: 1;\n        display: flex;\n        flex-direction: column;\n        justify-content: space-between;\n      }\n    }\n\n\n\n    &-model {\n      display: flex;\n\n\n      margin-bottom: 28rpx;\n\n      &-border {\n        display: flex;\n        align-items: center;\n        height: 40rpx;\n        border: 1px solid #CCCCCC;\n        opacity: 1;\n        border-radius: 0rpx;\n        padding: 0 10rpx;\n      }\n\n      &-info {}\n\n      &-label {\n        line-height: 38rpx;\n        font-size: 24rpx;\n        color: #999999;\n        margin-right: 10rpx;\n      }\n\n      &-value {\n        line-height: 38rpx;\n        font-size: 24rpx;\n        color: #333333;\n        margin-right: 10rpx;\n      }\n\n      &-action {\n        width: 11rpx;\n        height: 7rpx;\n      }\n    }\n\n  }\n\n  &-min {\n    .goods {\n      &-thumb {\n        margin-bottom: 0;\n        width: 150rpx;\n        height: 150rpx;\n\n        &-img {\n          width: 100%;\n          height: 100%;\n          display: block;\n        }\n      }\n    }\n  }\n\n  // .goods {\n  //   padding: 16rpx 14rpx;\n\n  //   &-header {\n  //     display: flex;\n  //     align-items: flex-start;\n  //   }\n\n  //   &-thumb {\n\n  //     width: 220rpx;\n  //     height: 220rpx;\n\n  //     &-img {\n  //       width: 100%;\n  //       height: 100%;\n  //       display: block;\n  //     }\n  //   }\n\n  //   &-content {\n  //     margin-top: 24rpx;\n  //     margin-left: 40rpx;\n  //     flex: 1\n  //   }\n\n  //   &-storeName {\n  //     line-height: 40rpx;\n  //     font-size: 28rpx;\n  //     font-weight: 500;\n  //     color: #333333;\n  //     margin-bottom: 35rpx;\n  //   }\n\n  //   &-info {\n  //     display: flex;\n  //     align-items: center;\n  //     justify-content: space-between;\n\n  //     &-left {\n  //       display: flex;\n  //       align-items: flex-end;\n\n  //     }\n\n  //     &-action {\n  //       &-btn {}\n\n  //       &-desc {\n  //         color: #999999;\n  //         font-size: 24rpx;\n  //         line-height: 40rpx;\n  //       }\n  //     }\n\n  //   }\n\n\n  //   &-price {\n  //     &-default {\n  //       line-height: 28rpx;\n  //       font-size: 20rpx;\n  //       color: #999999;\n\n  //     }\n\n  //     &-primary {\n  //       line-height: 42rpx;\n  //       font-size: 30rpx;\n  //       font-weight: 500;\n  //       color: #EE6D46;\n  //       margin-left: 5rpx;\n  //     }\n  //   }\n\n\n\n  //   &-desc {\n  //     color: #999999;\n  //     font-size: 24rpx;\n  //     line-height: 40rpx;\n  //   }\n\n\n  &-model {\n    display: inline-flex;\n    align-items: center;\n    width: auto;\n    height: 40rpx;\n    border: 1px solid #CCCCCC;\n    opacity: 1;\n    border-radius: 0rpx;\n    padding: 0 10rpx;\n\n    margin-bottom: 28rpx;\n\n    &-label {\n      line-height: 38rpx;\n      font-size: 24rpx;\n      color: #999999;\n    }\n\n    &-value {\n      line-height: 38rpx;\n      font-size: 24rpx;\n      color: #333333;\n      margin-right: 10rpx;\n    }\n\n    &-action {\n      width: 11rpx;\n      height: 7rpx;\n    }\n  }\n\n  //   &-model-info {\n  //     display: inline-flex;\n  //     align-items: center;\n  //     width: auto;\n  //     height: 40rpx;\n  //     opacity: 1;\n  //     border-radius: 0rpx;\n  //     margin-bottom: 28rpx;\n\n  //     &-label {\n  //       line-height: 38rpx;\n  //       font-size: 24rpx;\n  //       color: #999999;\n  //     }\n\n  //     &-value {\n  //       line-height: 38rpx;\n  //       font-size: 24rpx;\n  //       color: #333333;\n  //       margin-right: 10rpx;\n  //     }\n\n  //     &-action {\n  //       width: 11rpx;\n  //       height: 7rpx;\n  //     }\n  //   }\n  // }\n}\n\n.buy-progress {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n\n  &-info {\n    flex: 1;\n\n    &-desc {\n      color: #999999;\n      font-size: 24rpx;\n      line-height: 32rpx;\n    }\n  }\n\n  &-action {\n    margin-left: 17rpx;\n  }\n}\n\n.buy-num {\n\n  &-info-desc {\n    color: #999999;\n    font-size: 24rpx;\n    line-height: 32rpx;\n  }\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/config/index.js",
    "content": "export const VUE_APP_API_URL = 'http://localhost:48081/app-api'\n//export const VUE_APP_API_URL = 'https://apidc.yixiang.co/app-api'\nexport const VUE_APP_RESOURCES_URL = 'https://h5.yixiang.co/static'\nexport const VUE_APP_UPLOAD_URL = VUE_APP_API_URL + '/infra/file/upload'\nexport const APP_ID = 'wxdbdbc123c8c30b45'\n\nconst orderListStatus = {}\n\n// -1:申请退款\n// -2:退货成功\n// 0:待发货；\n// 1:待收货；\n// 2:已收货；\n// 3:待评价；\n// -1:已退款\n\nexport const orderStatus = {\n  0: '未支付',\n  1: '待发货',\n  2: '待收货',\n  3: '待评价',\n  4: '已完成',\n  5: '退款中',\n  6: '已退款',\n  7: '退款',\n}\n\nexport const orderReStatus = {\n  0: '等待买家付款',\n  // 1: '等待卖家发货',\n  1: '卖家已发货',\n  2: '等待买家待评价',\n  3: '订单已完成',\n  4: '订单退款中',\n  5: '订单已退款',\n  6: '退款已完成',\n}\n\n// export const orderReStatus = {\n//   0: '等待买家付款',\n//   1: '等待卖家发货',\n//   2: '卖家已发货',\n//   3: '等待买家待评价',\n//   4: '订单已完成',\n//   5: '订单退款中',\n//   6: '订单已退款',\n//   7: '退款已完成',\n// }\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/hooks/index.js",
    "content": "export * from './usePage'\nexport * from './useGlobalProperties'\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/hooks/useGlobalProperties.js",
    "content": "// mouse.js\nimport { ref, onMounted, onUnmounted, getCurrentInstance } from 'vue'\nimport { onReady, onReachBottom } from '@dcloudio/uni-app'\n\nexport const useGlobalProperties = () => {\n  const instance = getCurrentInstance()\n  return instance.appContext.app.config.globalProperties\n}\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/hooks/usePage.js",
    "content": "// mouse.js\nimport { ref, onMounted, onUnmounted } from 'vue'\nimport { onReady, onReachBottom } from '@dcloudio/uni-app'\n\nexport const usePage = getPage => {\n  // 页码,默认为1\n  const page = ref(1)\n\n  // 页大小,默认为10\n  const limit = ref(10)\n\n  // 关键字\n  const keyword = ref('')\n\n  // 类别\n  const type = ref('')\n\n  // 分类ID\n  const sid = ref('')\n\n  // 是否新品,不为空的字符串即可\n  const news = ref('')\n\n  // 是否积分兑换商品\n  const isIntegral = ref('')\n\n  // 到底了\n  const loadend = ref(false)\n\n  // 加载中\n  const loading = ref(false)\n\n  const dataList = ref([])\n\n  const handleGetDataList = async () => {\n    console.log('--> % handleGetDataList % loading:\\n', loading.value)\n    console.log('--> % handleGetDataList % loadend:\\n', loadend.value)\n    if (loading.value || loadend.value) return\n\n    loading.value = true\n    const products = await getPage({\n      page: page.value,\n      limit: limit.value,\n      keyword: keyword.value,\n      type: type.value,\n      sid: sid.value,\n      news: news.value,\n      isIntegral: isIntegral.value,\n    })\n    console.log('--> % handleGetDataList % products:\\n', products)\n    if (products) {\n      if (products.length <= 0) {\n        loadend.value = true\n      }\n      dataList.value = dataList.value.concat(products)\n    }\n    loading.value = false\n  }\n\n  const handleRefresh = () => {\n    loadend.value = false\n    loading.value = false\n    dataList.value = []\n    handleGetDataList()\n  }\n\n  onReady(() => {\n    console.log('onReady')\n    // handleGetDataList()\n  })\n\n  onReachBottom(() => {\n    if (loading.value) return\n    page.value += 1\n  })\n\n  // 通过返回值暴露所管理的状态\n  return {\n    type,\n    dataList,\n    page,\n    limit,\n    keyword,\n    loading,\n    loadend,\n    refresh: handleRefresh,\n  }\n}\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <script>\n      var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||\n        CSS.supports('top: constant(a)'))\n      document.write(\n        '<meta name=\"viewport\" content=\"width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +\n        (coverSupport ? ', viewport-fit=cover' : '') + '\" />')\n    </script>\n    <title></title>\n    <!--preload-links-->\n    <!--app-context-->\n  </head>\n  <body>\n    <div id=\"app\"><!--app-html--></div>\n    <script type=\"module\" src=\"/main.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/jsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"baseUrl\": \"./\",\n    \"paths\": {\n      \"@/*\": [\"./*\"],\n      \"@/api\": [\"./api\"],\n      \"@/utils\": [\"./utils\"]\n    }\n  },\n  \"exclude\": [\"node_modules\", \"dist\"],\n  \"include\": [\"/**/*\"]\n}\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/main.js",
    "content": "/*\n * @Author: Gaoxs\n * @Date: 2023-04-07 15:12:06\n * @LastEditors: Gaoxs\n * @Description:\n */\nimport util from '@/utils'\n\nimport App from './App'\n\nimport { createPinia } from 'pinia'\n\nimport { createSSRApp } from 'vue'\n\nexport function createApp() {\n  const app = createSSRApp(App)\n  app.use(util)\n  app.use(createPinia())\n  return {\n    app,\n  }\n}\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/manifest.json",
    "content": "{\n    \"name\" : \"yshop-miniapp\",\n    \"appid\" : \"__UNI__ADC0FB0\",\n    \"description\" : \"\",\n    \"versionName\" : \"1.0.0\",\n    \"versionCode\" : 1,\n    \"transformPx\" : false,\n    /* 5+App特有相关 */\n    \"app-plus\" : {\n        \"usingComponents\" : true,\n        \"nvueStyleCompiler\" : \"uni-app\",\n        \"compilerVersion\" : 3,\n        \"splashscreen\" : {\n            \"alwaysShowBeforeRender\" : true,\n            \"waiting\" : true,\n            \"autoclose\" : true,\n            \"delay\" : 0\n        },\n        /* 模块配置 */\n        \"modules\" : {\n            \"Payment\" : {},\n            \"OAuth\" : {}\n        },\n        /* 应用发布信息 */\n        \"distribute\" : {\n            //必选，JSON对象，云端打包配置\n            \"android\" : {\n                //可选，JSON对象，Android平台云端打包配置\n                \"packagename\" : \"\", //必填，字符串类型，Android包名\n                \"keystore\" : \"\", //必填，字符串类型，Android签名证书文件路径\n                \"password\" : \"\", //必填，字符串类型，Android签名证书文件的密码\n                \"aliasname\" : \"\", //必填，字符串类型，Android签名证书别名\n                \"schemes\" : \"\", //可选，字符串类型，参考：https://uniapp.dcloud.io/tutorial/app-android-schemes\n                \"abiFilters\" : [\n                    //可选，字符串数组类型，参考：https://uniapp.dcloud.io/tutorial/app-android-abifilters\n                    \"armeabi-v7a\",\n                    \"arm64-v8a\",\n                    \"x86\",\n                    \"x86_64\"\n                ],\n                \"permissions\" : [\n                    \"<uses-feature android:name=\\\"android.hardware.camera\\\"/>\",\n                    \"<uses-feature android:name=\\\"android.hardware.camera.autofocus\\\"/>\",\n                    \"<uses-permission android:name=\\\"android.permission.ACCESS_COARSE_LOCATION\\\"/>\",\n                    \"<uses-permission android:name=\\\"android.permission.ACCESS_FINE_LOCATION\\\"/>\",\n                    \"<uses-permission android:name=\\\"android.permission.ACCESS_NETWORK_STATE\\\"/>\",\n                    \"<uses-permission android:name=\\\"android.permission.ACCESS_WIFI_STATE\\\"/>\",\n                    \"<uses-permission android:name=\\\"android.permission.CAMERA\\\"/>\",\n                    \"<uses-permission android:name=\\\"android.permission.CHANGE_NETWORK_STATE\\\"/>\",\n                    \"<uses-permission android:name=\\\"android.permission.CHANGE_WIFI_STATE\\\"/>\",\n                    \"<uses-permission android:name=\\\"android.permission.FLASHLIGHT\\\"/>\",\n                    \"<uses-permission android:name=\\\"android.permission.GET_ACCOUNTS\\\"/>\",\n                    \"<uses-permission android:name=\\\"android.permission.INTERNET\\\"/>\",\n                    \"<uses-permission android:name=\\\"android.permission.MODIFY_AUDIO_SETTINGS\\\"/>\",\n                    \"<uses-permission android:name=\\\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\\\"/>\",\n                    \"<uses-permission android:name=\\\"android.permission.READ_LOGS\\\"/>\",\n                    \"<uses-permission android:name=\\\"android.permission.READ_PHONE_STATE\\\"/>\",\n                    \"<uses-permission android:name=\\\"android.permission.VIBRATE\\\"/>\",\n                    \"<uses-permission android:name=\\\"android.permission.WAKE_LOCK\\\"/>\",\n                    \"<uses-permission android:name=\\\"android.permission.WRITE_EXTERNAL_STORAGE\\\"/>\",\n                    \"<uses-permission android:name=\\\"android.permission.WRITE_SETTINGS\\\"/>\"\n                ],\n                \"custompermissions\" : false, //可选，Boolean类型，是否自定义Android权限配置\n                \"permissionExternalStorage\" : {\n                    //可选，JSON对象，Android平台应用启动时申请读写手机存储权限策略\n                    \"request\" : \"always\", //必填，字符串类型，申请读写手机存储权限策略，可取值none、once、always\n                    \"prompt\" : \"\" //可选，字符串类型，当request设置为always值用户拒绝时弹出提示框上的内容\n                },\n                \"permissionPhoneState\" : {\n                    //可选，JSON对象，Android平台应用启动时申请读取设备信息权限配置\n                    \"request\" : \"always\", //必填，字符串类型，申请读取设备信息权限策略，可取值none、once、always\n                    \"prompt\" : \"\" //可选，字符串类型，当request设置为always值用户拒绝时弹出提示框上的内容\n                },\n                \"minSdkVersion\" : 21, //可选，数字类型，Android平台最低支持版本，参考：https://uniapp.dcloud.io/tutorial/app-android-minsdkversion\n                \"targetSdkVersion\" : 30, //可选，数字类型，Android平台目标版本，参考：https://uniapp.dcloud.io/tutorial/app-android-targetsdkversion\n                \"packagingOptions\" : [\n                    //可选，字符串数组类型，Android平台云端打包时build.gradle的packagingOptions配置项\n                    \"doNotStrip '*/armeabi-v7a/*.so'\",\n                    \"merge '**/LICENSE.txt'\"\n                ],\n                \"jsEngine\" : \"v8\", //可选，字符串类型，uni-app使用的JS引擎，可取值v8、jsc\n                \"debuggable\" : false, //可选，Boolean类型，是否开启Android调试开关\n                \"locale\" : \"default\", //可选，应用的语言\n                \"forceDarkAllowed\" : false, //可选，Boolean类型，是否强制允许暗黑模式\n                \"resizeableActivity\" : false, //可选，Boolean类型，是否支持分屏调整窗口大小\n                \"hasTaskAffinity\" : false, //可选，Boolean类型，是否设置android：taskAffinity\n                \"buildFeatures\" : {\n                    //（HBuilderX3.5.0+版本支持）可选，JSON对象，Android平台云端打包时build.gradle的buildFeatures配置项  \n                    \"dataBinding\" : false, //可选，Boolean类型，是否设置dataBinding\n                    \"viewBinding\" : false //可选，Boolean类型，是否设置viewBinding\n                }\n            },\n            \"ios\" : {\n                //可选，JSON对象，iOS平台云端打包配置\n                \"appid\" : \"\", //必填，字符串类型，iOS平台Bundle ID\n                \"mobileprovision\" : \"\", //必填，字符串类型，iOS打包使用的profile文件路径\n                \"p12\" : \"\", //必填，字符串类型，iOS打包使用的证书文件路径\n                \"password\" : \"\", //必填，字符串类型，iOS打包使用的证书密码\n                \"devices\" : \"iphone\", //必填，字符串类型，iOS支持的设备类型，可取值iphone、ipad、universal\n                \"urlschemewhitelist\" : \"baidumap\", //可选，字符串类型，应用访问白名单列表，参考：https://uniapp.dcloud.io/tutorial/app-ios-schemewhitelist\n                \"urltypes\" : \"\", //可选，字符串类型，参考：https://uniapp.dcloud.io/tutorial/app-ios-schemes\n                \"UIBackgroundModes\" : \"audio\", //可选，字符串类型，应用后台运行模式，参考：https://uniapp.dcloud.io/tutorial/app-ios-uibackgroundmodes\n                \"frameworks\" : [\n                    //可选，字符串数组类型，依赖的系统库，已废弃，推荐使用uni原生插件扩展使用系统依赖库\n                    \"CoreLocation.framework\"\n                ],\n                \"deploymentTarget\" : \"10.0\", //可选，字符串类型，iOS支持的最低版本\n                \"privacyDescription\" : {\n                    //可选，JSON对象，iOS隐私信息访问的许可描述\n                    \"NSPhotoLibraryUsageDescription\" : \"\", //可选，字符串类型，系统相册读取权限描述\n                    \"NSPhotoLibraryAddUsageDescription\" : \"\", //可选，字符串类型，系统相册写入权限描述\n                    \"NSCameraUsageDescription\" : \"\", //可选，字符串类型，摄像头使用权限描述\n                    \"NSMicrophoneUsageDescription\" : \"\", //可选，字符串类型，麦克风使用权限描述\n                    \"NSLocationWhenInUseUsageDescription\" : \"\", //可选，字符串类型，运行期访问位置权限描述\n                    \"NSLocationAlwaysUsageDescription\" : \"\", //可选，字符串类型，后台运行访问位置权限描述\n                    \"NSLocationAlwaysAndWhenInUseUsageDescription\" : \"\", //可选，字符串类型，运行期后后台访问位置权限描述\n                    \"NSCalendarsUsageDescription\" : \"\", //可选，字符串类型，使用日历权限描述\n                    \"NSContactsUsageDescription\" : \"\", //可选，字符串类型，使用通讯录权限描述\n                    \"NSBluetoothPeripheralUsageDescription\" : \"\", //可选，字符串类型，使用蓝牙权限描述\n                    \"NSBluetoothAlwaysUsageDescription\" : \"\", //可选，字符串类型，后台使用蓝牙权限描述\n                    \"NSSpeechRecognitionUsageDescription\" : \"\", //可选，字符串类型，系统语音识别权限描述\n                    \"NSRemindersUsageDescription\" : \"\", //可选，字符串类型，系统提醒事项权限描述\n                    \"NSMotionUsageDescription\" : \"\", //可选，字符串类型，使用运动与健康权限描述\n                    \"NSHealthUpdateUsageDescription\" : \"\", //可选，字符串类型，使用健康更新权限描述\n                    \"NSHealthShareUsageDescription\" : \"\", //可选，字符串类型，使用健康分享权限描述\n                    \"NSAppleMusicUsageDescription\" : \"\", //可选，字符串类型，使用媒体资料库权限描述\n                    \"NFCReaderUsageDescription\" : \"\", //可选，字符串类型，使用NFC权限描述\n                    \"NSHealthClinicalHealthRecordsShareUsageDescription\" : \"\", //可选，字符串类型，访问临床记录权限描述\n                    \"NSHomeKitUsageDescription\" : \"\", //可选，字符串类型，访问HomeKit权限描述\n                    \"NSSiriUsageDescription\" : \"\", //可选，字符串类型，访问Siri权限描述\n                    \"NSFaceIDUsageDescription\" : \"\", //可选，字符串类型，使用FaceID权限描述\n                    \"NSLocalNetworkUsageDescription\" : \"\", //可选，字符串类型，访问本地网络权限描述\n                    \"NSUserTrackingUsageDescription\" : \"\" //可选，字符串类型，跟踪用户活动权限描述\n                },\n                \"idfa\" : true, //可选，Boolean类型，是否使用广告标识\n                \"capabilities\" : {},\n                //可选，JSON对象，应用的能力配置（Capabilities）\n                \"CFBundleName\" : \"HBuilder\", //可选，字符串类型，CFBundleName名称\n                \"validArchitectures\" : [\n                    //可选，字符串数组类型，编译时支持的CPU指令，可取值arm64、arm64e、armv7、armv7s、x86_64\n                    \"arm64\"\n                ],\n                \"pushRegisterMode\" : \"manual\", //可选，使用“Push(消息推送)”模块时申请系统推送权限模式，manual表示调用push相关API时申请，其它值表示应用启动时自动申请\n                \"privacyRegisterMode\" : \"manual\", //可选，仅iOS有效，设置为manual表示用户同意隐私政策后才获取idfv，设置为其它值表示应用启动时自动获取\n                \"dSYMs\" : false\n            },\n            \"sdkConfigs\" : {\n                //可选，JSON对象，三方SDK相关配置\n                \"geolocation\" : {\n                    //可选，JSON对象，Geolocation(定位)模块三方SDK配置\n                    \"system\" : {\n                        //可选，JSON对象，使用系统定位\n                        \"__platform__\" : [ \"ios\", \"android\" ] //可选，字符串数组类型，支持的平台\n                    },\n                    \"amap\" : {\n                        //可选，JSON对象，使用高德定位SDK配置\n                        \"__platform__\" : [ \"ios\", \"android\" ], //可选，字符串数组类型，支持的平台\n                        \"appkey_ios\" : \"\", //必填，字符串类型，iOS平台高德定位appkey\n                        \"appkey_android\" : \"\" //必填，字符串类型，Android平台高德定位appkey\n                    },\n                    \"baidu\" : {\n                        //可选，JSON对象，使用百度定位SDK配置\n                        \"__platform__\" : [ \"ios\", \"android\" ], //可选，字符串数组类型，支持的平台\n                        \"appkey_ios\" : \"\", //必填，字符串类型，iOS平台百度定位appkey\n                        \"appkey_android\" : \"\" //必填，字符串类型，Android平台百度定位appkey\n                    }\n                },\n                \"maps\" : {\n                    //可选，JSON对象，Maps(地图)模块三方SDK配置\n                    \"amap\" : {\n                        //可选，JSON对象，使用高德地图SDK配置\n                        \"appkey_ios\" : \"\", //必填，字符串类型，iOS平台高德地图appkey\n                        \"appkey_android\" : \"\" //必填，字符串类型，Android平台高德地图appkey\n                    },\n                    \"baidu\" : {\n                        //可选，JSON对象，使用百度地图SDK配置\n                        \"appkey_ios\" : \"\", //必填，字符串类型，iOS平台百度地图appkey\n                        \"appkey_android\" : \"\" //必填，字符串类型，Android平台百度地图appkey\n                    },\n                    \"google\" : {\n                        //可选，JSON对象，使用Google地图SDK配置\n                        \"APIKey_ios\" : \"\", //必填，字符串类型，iOS平台Google地图APIKey\n                        \"APIKey_android\" : \"\" //必填，字符串类型，Android平台Google地图APIKey\n                    }\n                },\n                \"oauth\" : {\n                    //可选，JSON对象，使用苹果登录(Sign in with Apple)SDK配置，无配置参数，仅iOS平台支持\n                    \"weixin\" : {\n                        \"appid\" : \"wx7c84ede33062d1e4\",\n                        \"UniversalLinks\" : \"https://yixiang.co/app/\"\n                    }\n                },\n                \"payment\" : {\n                    \"weixin\" : {\n                        \"__platform__\" : [ \"ios\", \"android\" ],\n                        \"appid\" : \"wx7c84ede33062d1e4\",\n                        \"UniversalLinks\" : \"https://yixiang.co/app/\"\n                    }\n                },\n                //可选，JSON对象，使用google支付SDK配置，无配置参数，仅Android平台支持\n                \"push\" : {\n                    //可选，JSON对象，Push(消息推送)模块三方SDK配置\n                    \"unipush\" : {\n                        //可选，JSON对象，使用UniPush SDK配置，无需手动配置参数，云端打包自动获取配置参数\n                        \"icons\" : {\n                            //可选，JSON对象，推送图标配置\n                            \"push\" : {\n                                //可选，JSON对象，Push图标配置\n                                \"ldpi\" : \"\", //可选，字符串类型，普通屏设备推送图标路径，分辨率要求48x48\n                                \"mdpi\" : \"\", //可选，字符串类型，大屏设备设备推送图标路径，分辨率要求48x48\n                                \"hdpi\" : \"\", //可选，字符串类型，高分屏设备推送图标路径，分辨率要求72x72\n                                \"xdpi\" : \"\", //可选，字符串类型，720P高分屏设备推送图标路径，分辨率要求96x96\n                                \"xxdpi\" : \"\", //可选，字符串类型，1080P高密度屏幕推送图标路径，分辨率要求144x144\n                                \"xxxdpi\" : \"\" //可选，字符串类型，4K屏设备推送图标路径，分辨率要求192x192\n                            },\n                            \"smal\" : {\n                                //可选，JSON对象，Push小图标配置\n                                \"ldpi\" : \"\", //可选，字符串类型，普通屏设备推送小图标路径，分辨率要求18x18\n                                \"mdpi\" : \"\", //可选，字符串类型，大屏设备设备推送小图标路径，分辨率要求24x24\n                                \"hdpi\" : \"\", //可选，字符串类型，高分屏设备推送小图标路径，分辨率要求36x36\n                                \"xdpi\" : \"\", //可选，字符串类型，720P高分屏设备推送小图标路径，分辨率要求48x48\n                                \"xxdpi\" : \"\", //可选，字符串类型，1080P高密度屏幕推送小图标路径，分辨率要求72x72\n                                \"xxxdpi\" : \"\" //可选，字符串类型，4K屏设备推送小图标路径，分辨率要求96x96\n                            }\n                        }\n                    },\n                    \"igexin\" : {\n                        //可选，JSON对象，使用个推推送SDK配置，**已废弃，推荐使用UniPush，UniPush是个推推送VIP版，功能更强大**\n                        \"appid\" : \"\", //必填，字符串类型，个推开放平台申请的appid\n                        \"appkey\" : \"\", //必填，字符串类型，个推开放平台申请的appkey\n                        \"appsecret\" : \"\", //必填，字符串类型，个推开放平台申请的appsecret\n                        \"icons\" : {\n                            //可选，JSON对象，推送图标配置\n                            \"push\" : {\n                                //可选，JSON对象，Push图标配置\n                                \"ldpi\" : \"\", //可选，字符串类型，普通屏设备推送图标路径，分辨率要求48x48\n                                \"mdpi\" : \"\", //可选，字符串类型，大屏设备设备推送图标路径，分辨率要求48x48\n                                \"hdpi\" : \"\", //可选，字符串类型，高分屏设备推送图标路径，分辨率要求72x72\n                                \"xdpi\" : \"\", //可选，字符串类型，720P高分屏设备推送图标路径，分辨率要求96x96\n                                \"xxdpi\" : \"\", //可选，字符串类型，1080P高密度屏幕推送图标路径，分辨率要求144x144\n                                \"xxxdpi\" : \"\" //可选，字符串类型，4K屏设备推送图标路径，分辨率要求192x192\n                            },\n                            \"smal\" : {\n                                //可选，JSON对象，Push小图标配置\n                                \"ldpi\" : \"\", //可选，字符串类型，普通屏设备推送小图标路径，分辨率要求18x18\n                                \"mdpi\" : \"\", //可选，字符串类型，大屏设备设备推送小图标路径，分辨率要求24x24\n                                \"hdpi\" : \"\", //可选，字符串类型，高分屏设备推送小图标路径，分辨率要求36x36\n                                \"xdpi\" : \"\", //可选，字符串类型，720P高分屏设备推送小图标路径，分辨率要求48x48\n                                \"xxdpi\" : \"\", //可选，字符串类型，1080P高密度屏幕推送小图标路径，分辨率要求72x72\n                                \"xxxdpi\" : \"\" //可选，字符串类型，4K屏设备推送小图标路径，分辨率要求96x96\n                            }\n                        }\n                    }\n                },\n                \"share\" : {\n                    //可选，JSON对象，Share(分享)模块三方SDK配置\n                    \"weixin\" : {\n                        //可选，JSON对象，使用微信分享SDK配置\n                        \"appid\" : \"\", //必填，字符串类型，微信开放平台申请的appid\n                        \"UniversalLinks\" : \"\" //可选，字符串类型，微信开放平台配置的iOS平台通用链接\n                    },\n                    \"qq\" : {\n                        //可选，JSON对象，使用QQ分享SDK配置\n                        \"appid\" : \"\", //必填，字符串类型，QQ开放平台申请的appid\n                        \"UniversalLinks\" : \"\" //可选，字符串类型，QQ开放平台配置的iOS平台通用链接\n                    },\n                    \"sina\" : {\n                        //可选，JSON对象，使用新浪微博分享SDK配置\n                        \"appkey\" : \"\", //必填，字符串类型，新浪微博开放平台申请的appid\n                        \"redirect_uri\" : \"\", //必填，字符串类型，新浪微博开放平台配置的redirect_uri\n                        \"UniversalLinks\" : \"\" //可选，字符串类型，新浪微博开放平台配置的iOS平台通用链接\n                    }\n                },\n                \"speech\" : {\n                    //可选，JSON对象，Speech(语音识别)模块三方SDK配置\n                    \"baidu\" : {\n                        //可选，JSON对象，使用百度语音识别SDK配置\n                        \"appid\" : \"\", //必填，字符串类型，百度开放平台申请的appid\n                        \"apikey\" : \"\", //必填，字符串类型，百度开放平台申请的apikey\n                        \"secretkey\" : \"\" //必填，字符串类型，百度开放平台申请的secretkey\n                    }\n                },\n                \"statics\" : {\n                    //可选，JSON对象，Statistic(统计)模块三方SDK配置\n                    \"umeng\" : {\n                        //可选，JSON对象，使用友盟统计SDK配置\n                        \"appkey_ios\" : \"\", //必填，字符串类型，友盟统计开放平台申请的iOS平台appkey\n                        \"channelid_ios\" : \"\", //可选，字符串类型，友盟统计iOS平台的渠道标识\n                        \"appkey_android\" : \"\", //必填，字符串类型，友盟统计开放平台申请的Android平台appkey\n                        \"channelid_android\" : \"\" //可选，字符串类型，友盟统计Android平台的渠道标识\n                    },\n                    \"google\" : {\n                        //可选，JSON对象，使用Google Analytics for Firebase配置\n                        \"config_ios\" : \"\", //必填，字符串类型，Google Firebase统计开发者后台获取的iOS平台配置文件路径\n                        \"config_android\" : \"\" //必填，字符串类型，Google Firebase统计开发者后台获取的Android平台配置文件路径\n                    }\n                },\n                \"ad\" : {}\n            },\n            //可选，JSON对象，使用互动游戏(变现猫)SDK，无需手动配置，在uni-AD后台申请开通后自动获取配置参数\n            \"icons\" : {\n                //可选，JSON对象，应用图标相关配置\n                \"ios\" : {\n                    //可选，JSON对象，iOS平台图标配置 \n                    \"appstore\" : \"\", //必填，字符串类型，分辨率1024x1024, 提交app sotre使用的图标路径\n                    \"iphone\" : {\n                        //可选，JSON对象，iPhone设备图标配置\n                        \"app@2x\" : \"\", //可选，字符串类型，分辨率120x120，程序图标路径  \n                        \"app@3x\" : \"\", //可选，字符串类型，分辨率180x180，程序图标路径  \n                        \"spotlight@2x\" : \"\", //可选，字符串类型，分辨率80x80，Spotlight搜索图标路径\n                        \"spotlight@3x\" : \"\", //可选，字符串类型，分辨率120x120，Spotlight搜索图标路径\n                        \"settings@2x\" : \"\", //可选，字符串类型，分辨率58x58，Settings设置图标路径\n                        \"settings@3x\" : \"\", //可选，字符串类型，分辨率87x87，Settings设置图标路径\n                        \"notification@2x\" : \"\", //可选，字符串类型，分辨率40x40，通知栏图标路径\n                        \"notification@3x\" : \"\" //可选，字符串类型，分辨率60x60，通知栏图标路径\n                    },\n                    \"ipad\" : {\n                        //可选，JSON对象，iPad设备图标配置\n                        \"app\" : \"\", //可选，字符串类型，分辨率76x76，程序图标图标路径\n                        \"app@2x\" : \"\", //可选，字符串类型，分辨率152x152，程序图标图标路径\n                        \"proapp@2x\" : \"\", //可选，字符串类型，分辨率167x167，程序图标图标路径\n                        \"spotlight\" : \"\", //可选，字符串类型，分辨率40x40，Spotlight搜索图标路径\n                        \"spotlight@2x\" : \"\", //可选，字符串类型，分辨率80x80，Spotlight搜索图标路径\n                        \"settings\" : \"\", //可选，字符串类型，分辨率29x29，Settings设置图标路径\n                        \"settings@2x\" : \"\", //可选，字符串类型，分辨率58x58，Settings设置图标路径\n                        \"notification\" : \"\", //可选，字符串类型，分辨率20x20，通知栏图标路径\n                        \"notification@2x\" : \"\" //可选，字符串类型，分辨率740x40，通知栏图标路径\n                    }\n                },\n                \"android\" : {\n                    //可选，JSON对象，Android平台图标配置\n                    \"ldpi\" : \"\", //可选，字符串类型，普通屏设备程序图标，分辨率要求48x48，已废弃  \n                    \"mdpi\" : \"\", //可选，字符串类型，大屏设备程序图标，分辨率要求48x48，已废弃  \n                    \"hdpi\" : \"\", //可选，字符串类型，高分屏设备程序图标，分辨率要求72x72\n                    \"xhdpi\" : \"\", //可选，字符串类型，720P高分屏设备程序图标，分辨率要求96x96\n                    \"xxhdpi\" : \"\", //可选，字符串类型，1080P高分屏设备程序图标，分辨率要求144x144\n                    \"xxxhdpi\" : \"\" //可选，字符串类型，2K屏设备程序图标，分辨率要求192x192\n                }\n            },\n            \"splashscreen\" : {\n                //可选，JSON对象，启动界面配置\n                \"iosStyle\" : \"common\", //可选，字符串类型，iOS平台启动界面样式，可取值common、default、storyboard\n                \"ios\" : {\n                    //可选，JSON对象，iOS平台启动界面配置 \n                    \"storyboard\" : \"\", //可选，字符串类型，自定义storyboard启动界面文件路径，iosStyle值为storyboard时生效\n                    \"iphone\" : {\n                        //可选，JSON对象，iPhone设备启动图配置，iosStyle值为default时生效\n                        \"default\" : \"\", //可选，字符串类型，分辨率320x480，iPhone3（G/GS）启动图片路径，已废弃  \n                        \"retina35\" : \"\", //可选，字符串类型，分辨率640x960，3.5英寸设备(iPhone4/4S)启动图片路径，已废弃 \n                        \"retina40\" : \"\", //可选，字符串类型，分辨率640x1136，4.0英寸设备(iPhone5/5S)启动图片路径\n                        \"retina40l\" : \"\", //可选，字符串类型，分辨率1136x640，4.0英寸设备(iPhone5/5S)横屏启动图片路径\n                        \"retina47\" : \"\", //可选，字符串类型，分辨率750x1334，4.7英寸设备（iPhone6/7/8）启动图片路径\n                        \"retina47l\" : \"\", //可选，字符串类型，分辨率1334x750，4.7英寸设备（iPhone6/7/8）横屏启动图片路径\n                        \"retina55\" : \"\", //可选，字符串类型，分辨率1242x2208，5.5英寸设备（iPhone6/7/8Plus）启动图片路径  \n                        \"retina55l\" : \"\", //可选，字符串类型，分辨率2208x1242，5.5英寸设备（iPhone6/7/8Plus）横屏启动图片路径\n                        \"iphonex\" : \"\", //可选，字符串类型，分辨率1125x2436，5.8英寸设备（iPhoneX/XS）启动图片路径\n                        \"iphonexl\" : \"\", //可选，字符串类型，分辨率2436x1125，5.8英寸设备（iPhoneX/XS）横屏启动图片路径\n                        \"portrait-896h@2x\" : \"\", //可选，字符串类型，分辨率828x1792，6.1英寸设备（iPhoneXR）启动图片路径\n                        \"landscape-896h@2x\" : \"\", //可选，字符串类型，分辨率1792x828，6.1英寸设备（iPhoneXR）iPhoneXR横屏启动图片路径\n                        \"portrait-896h@3x\" : \"\", //可选，字符串类型，分辨率1242x2688，6.5英寸设备（iPhoneXS Max）启动图片路径\n                        \"landscape-896h@3x\" : \"\" //可选，字符串类型，分辨率2688x1242，6.5英寸设备（iPhoneXS Max）横屏启动图片路径\n                    },\n                    \"ipad\" : {\n                        //可选，JSON对象，iPad设备启动图配置，iosStyle值为default时生效\n                        \"portrait\" : \"\", //可选，字符串类型，分辨率768x1004，iPad竖屏启动图片路径，已废弃  \n                        \"portrait-retina\" : \"\", //可选，字符串类型，分辨率1536x2008，iPad高分屏竖屏启动图片路径，已废弃  \n                        \"landscape\" : \"\", //可选，字符串类型，分辨率1024x748，iPad横屏启动图片路径，已废弃   \n                        \"landscape-retina\" : \"\", //可选，字符串类型，分辨率2048x1496，iPad高分屏横屏启动图片路径，已废弃  \n                        \"portrait7\" : \"\", //可选，字符串类型，分辨率768x1024，9.7/7.9英寸iPad/mini竖屏启动图片路径 \n                        \"landscape7\" : \"\", //可选，字符串类型，分辨率1024x768，9.7/7.9英寸iPad/mini横屏启动图片路径\n                        \"portrait-retina7\" : \"\", //可选，字符串类型，分辨率1536x2048，9.7/7.9英寸iPad/mini高分屏竖屏图片路径\n                        \"landscape-retina7\" : \"\", //可选，字符串类型，分辨率2048x1536，9.7/7.9英寸iPad/mini高分屏横屏启动图片路径\n                        \"portrait-1112h@2x\" : \"\", //可选，字符串类型，分辨率1668x2224，10.5英寸iPad Pro竖屏启动图片路径\n                        \"landscape-1112h@2x\" : \"\", //可选，字符串类型，分辨率2224x1668，10.5英寸iPad Pro横屏启动图片路径\n                        \"portrait-1194h@2x\" : \"\", //可选，字符串类型，分辨率1668x2388，11英寸iPad Pro竖屏启动图片路径\n                        \"landscape-1194h@2x\" : \"\", //可选，字符串类型，分辨率2388x1668，11英寸iPad Pro横屏启动图片路径\n                        \"portrait-1366h@2x\" : \"\", //可选，字符串类型，分辨率2048x2732，12.9英寸iPad Pro竖屏启动图片路径\n                        \"landscape-1366h@2x\" : \"\" //可选，字符串类型，分辨率2732x2048，12.9英寸iPad Pro横屏启动图片路径\n                    }\n                },\n                \"androidStyle\" : \"common\", //可选，字符串类型，Android平台启动界面样式，可取值common、default\n                \"android\" : {\n                    //可选，JSON对象，Android平台启动图片配置， androidStyle值为default时生效\n                    \"ldpi\" : \"\", //可选，字符串类型，分辨率320x442，低密度屏幕启动图片路径，已废弃\n                    \"mdpi\" : \"\", //可选，字符串类型，分辨率240x282，中密度屏幕启动图片路径，已废弃\n                    \"hdpi\" : \"\", //可选，字符串类型，分辨率480x762，高密度屏幕启动图片路径\n                    \"xhdpi\" : \"\", //可选，字符串类型，分辨率720x1242，720P高密度屏幕启动图片路径\n                    \"xxhdpi\" : \"\" //可选，字符串类型，分辨率1080x1882，1080P高密度屏幕启动图片路径\n                }\n            },\n            \"orientation\" : [\n                //可选，字符串数组类型，应用支持的横竖屏，**已废弃，使用screenOrientation配置** \n                \"portrait-primary\",\n                \"portrait-secondary\",\n                \"landscape-primary\",\n                \"landscape-secondary\"\n            ]\n        }\n    },\n    /* 快应用特有相关 */\n    \"quickapp\" : {},\n    /* 小程序特有相关 */\n    \"mp-weixin\" : {\n        \"appid\" : \"wx001e2dc50bf532df\",\n        \"setting\" : {\n            \"urlCheck\" : false\n        },\n        \"usingComponents\" : true,\n        \"permission\" : {\n            \"scope.userLocation\" : {\n                \"desc\" : \"定位最近的门店\"\n            }\n        },\n        \"requiredPrivateInfos\" : [ \"getLocation\", \"chooseLocation\" ]\n    },\n    \"mp-alipay\" : {\n        \"usingComponents\" : true\n    },\n    \"mp-baidu\" : {\n        \"usingComponents\" : true\n    },\n    \"mp-toutiao\" : {\n        \"usingComponents\" : true\n    },\n    \"uniStatistics\" : {\n        \"enable\" : false\n    },\n    \"vueVersion\" : \"3\",\n    \"fallbackLocale\" : \"zh-Hans\",\n    \"h5\" : {\n        \"router\" : {\n            \"base\" : \"/h5/\"\n        },\n\t\t\"sdkConfigs\" : {\n\t\t    // 使用地图或位置相关功能必须填写其一\n\t\t    \"maps\" : {\n\t\t        \"bmap\" : {\n\t\t            // 百度地图秘钥（HBuilderX 3.99+）http://lbsyun.baidu.com/apiconsole/key#/home\n\t\t            \"key\" : \"\"\n\t\t        },\n\t\t        \"qqmap\" : {\n\t\t            \"key\" : \"OGABZ-Y5OCF-5UWJ5-N7DHH-VFIG7-DHFEB\"\n\t\t        }\n\t\t    }\n\t\t}\n    }\n}\n/* 模块配置 *//* 应用发布信息 *//* android打包配置 */\n\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/package.json",
    "content": "{\n  \"name\": \"yshop-miniapp\",\n  \"version\": \"1.2.0\",\n  \"description\": \"\",\n  \"main\": \"main.js\",\n  \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n  },\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"dependencies\": {\n    \"@vant/area-data\": \"^1.5.0\",\n    \"add\": \"^2.0.6\",\n    \"flyio\": \"^0.6.14\",\n    \"jweixin-module\": \"^1.6.0\",\n    \"pinia\": \"^2.1.6\",\n    \"vant\": \"^4.6.2\",\n    \"weixin-js-sdk\": \"^1.6.3\",\n    \"yarn\": \"^1.22.19\"\n  }\n}\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/pages/cart/cart.vue",
    "content": "<template>\n\t<uv-navbar\n\t  :fixed=\"false\"\n\t  :title=\"title\"\n\t  left-arrow\n\t  @leftClick=\"$onClickLeft\"\n\t/>\n\t<view  class=\"cart-popup\">\n\t\t <view class=\"top flex justify-between\">\n\t\t   <text>已点{{ getCartGoodsNumber }}份</text>\n\t\t   <text @tap=\"handleCartClear\">清空</text>\n\t\t </view>\n\t\t <scroll-view class=\"cart-list\" scroll-y>\n\t\t  <view class=\"wrapper\">\n\t\t   <uv-empty mode=\"car\" v-if=\"cart.length == 0\"></uv-empty>\n\t\t   <view class=\"item\" v-for=\"(item, index) in cart\" :key=\"index\">\n\t\t\t<view class=\"left\">\n\t\t\t <view class=\"name\">{{ item.name }}</view>\n\t\t\t <view class=\"props\">{{ item.valueStr }}</view>\n\t\t\t</view>\n\t\t\t<view class=\"center\">\n\t\t\t <text>￥{{ item.price }}</text>\n\t\t\t</view>\n\t\t\t<view class=\"right\">\n\t\t\t <button type=\"default\" plain size=\"mini\" class=\"btn\" hover-class=\"none\"\n\t\t\t  @tap=\"handleCartItemReduce(index)\">\n\t\t\t  <view class=\"iconfont iconsami-select\"></view>\n\t\t\t </button>\n\t\t\t <view class=\"number\">{{ item.number }}</view>\n\t\t\t <button type=\"primary\" class=\"btn\" size=\"min\" hover-class=\"none\"\n\t\t\t  @tap=\"handleCartItemAdd(index)\">\n\t\t\t  <view class=\"iconfont iconadd-select\"></view>\n\t\t\t </button>\n\t\t\t</view>\n\t\t   </view>\t\t\n\t\t  </view>\n\t\t </scroll-view>\n\t </view>\n\t <view class=\"fixed-bottom flex justify-between align-center bg-white \">\n\t \t<view class=\"mx-2 ont-weight-light\">应付:<text class=\"text-danger\">￥{{ getCartGoodsPrice }}</text></view>\n\t \t<view><uv-button type=\"warning\" color=\"#09b4f1\" :customStyle=\"customStyle\" size=\"large\" text=\"去结算\" @click=\"toPay\"></uv-button></view>\n\t </view>\n\t <uv-toast ref=\"uToast\"></uv-toast>\n</template>\n\n<script setup>\nimport {\n  ref,\n  computed\n} from 'vue'\nimport { useMainStore } from '@/store/store'\nimport { storeToRefs } from 'pinia'\nimport { onLoad,onShow} from '@dcloudio/uni-app'\nconst main = useMainStore()\nconst { orderType,address, store,location,isLogin } = storeToRefs(main)\nconst title = ref('购物车')\nconst cart = ref([])\nconst uToast = ref()\n\n// onLoad(() => {\n// \tcart.value = uni.getStorageSync('cart')\n// })\nonShow(() => {\n\t//cart.value = []\n\tcart.value = uni.getStorageSync('cart')\n})\nconst getCartGoodsNumber = computed(() => { //计算购物车总数\n\tif(cart.value.length == 0) {\n\t\treturn 0\n\t}\n\treturn cart.value.reduce((acc, cur) => acc + cur.number, 0)\n})\nconst getCartGoodsPrice = computed(() =>{ //计算购物车总价\n\tif(cart.value.length == 0) {\n\t\treturn 0\n\t}\n\tlet price = cart.value.reduce((acc, cur) => acc + cur.number * cur.price, 0);\n\treturn parseFloat(price).toFixed(2);\n})\nconst customStyle = computed(() =>{ \n\treturn {\n\t\t  paddingLeft:'60rpx',\n\t\t  paddingRight:'60rpx'\n\t\t}\n})\nconst handleCartItemAdd = (index) => {\n\tcart.value[index].number += 1\n\tuni.setStorageSync('cart', JSON.parse(JSON.stringify(cart.value)))\n}\nconst handleCartItemReduce = (index) => {\n\tif (cart.value[index].number === 1) {\n\t\tcart.value.splice(index, 1)\n\t} else {\n\t\tcart.value[index].number -= 1\n\t}\n\tif (!cart.value.length) {\n\t\tcartPopupVisible.value = false\n\t}\n\tuni.setStorageSync('cart', JSON.parse(JSON.stringify(cart.value)))\n}\nconst handleCartClear = () => { //清空购物车\n\tuni.showModal({\n\t\ttitle: '提示',\n\t\tcontent: '确定清空购物车么',\n\t\tsuccess: ({\n\t\t\tconfirm\n\t\t}) => {\n\t\t\tif (confirm) {\n\t\t\t\tcart.value = []\n\t\t\t\tuni.setStorageSync('cart', JSON.parse(JSON.stringify(cart.value)))\n\t\t\t}\n\t\t}\n\t})\n}\nconst toPay = () => {\n\t\n\tif(cart.value.length == 0){\n\t\tuToast.value.show({message:'请先去点餐哦',type: 'error'});\n\t\treturn;\n\t}\n\n\tif (!isLogin.value) {\n\t\tuni.navigateTo({\n\t\t\turl: '/pages/components/pages/login/login'\n\t\t})\n\t\treturn\n\t} else {\n\t\tif (store.value.status == 0) {\n\t\t\tuToast.value.show({message:'不在店铺营业时间内',type: 'error'});\n\t\t\treturn;\n\t\t}\n\t\t// 判断当前是否在配送范围内\n\t\tif (orderType.value == 'takeout' && store.value.distance < store.value.far) {\n\t\t\tuToast.value.show({message:'选中的地址不在配送范围',type: 'error'});\n\t\t\treturn;\n\t\t}\n\n\t\tuni.showLoading({\n\t\t\ttitle: '加载中'\n\t\t})\n\t\tuni.setStorageSync('cart', JSON.parse(JSON.stringify(cart.value)))\n\n\t\tuni.navigateTo({\n\t\t\turl: '/pages/components/pages/pay/pay'\n\t\t})\n\t}\n\n\tuni.hideLoading()\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.cart-popup {\n\t\t.top {\n\t\t\tbackground-color: $bg-color-primary;\n\t\t\t//color: $color-primary;\n\t\t\tcolor: #5A5B5C;\n\t\t\tpadding: 10rpx 30rpx;\n\t\t\tfont-size: 24rpx;\n\t\t\ttext-align: right;\n\t\t}\n\t\t.cart-list {\n\t\t\tbackground-color: #ffffff;\n\t\t\twidth: 100%;\n\t\t\toverflow: hidden;\n\t\t\tmin-height: 1vh;\n\t\t\tmax-height: 60vh;\n\t\n\t\t\t.wrapper {\n\t\t\t\theight: 100%;\n\t\t\t\tdisplay: flex;\n\t\t\t\tflex-direction: column;\n\t\t\t\tpadding: 0 30rpx;\n\t\t\t\tmargin-bottom: 156rpx;\n\t\n\t\t\t\t.item {\n\t\t\t\t\tdisplay: flex;\n\t\t\t\t\tjustify-content: space-between;\n\t\t\t\t\talign-items: center;\n\t\t\t\t\tpadding: 30rpx 0;\n\t\t\t\t\tposition: relative;\n\t\n\t\t\t\t\t&::after {\n\t\t\t\t\t\tcontent: ' ';\n\t\t\t\t\t\tposition: absolute;\n\t\t\t\t\t\tbottom: 0;\n\t\t\t\t\t\tleft: 0;\n\t\t\t\t\t\twidth: 100%;\n\t\t\t\t\t\tbackground-color: $border-color;\n\t\t\t\t\t\theight: 2rpx;\n\t\t\t\t\t\ttransform: scaleY(0.6);\n\t\t\t\t\t}\n\t\n\t\t\t\t\t.left {\n\t\t\t\t\t\tflex: 1;\n\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\tflex-direction: column;\n\t\t\t\t\t\toverflow: hidden;\n\t\t\t\t\t\tmargin-right: 30rpx;\n\t\n\t\t\t\t\t\t.name {\n\t\t\t\t\t\t\tfont-size: $font-size-sm;\n\t\t\t\t\t\t\tcolor: $text-color-base;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t.props {\n\t\t\t\t\t\t\tcolor: $text-color-assist;\n\t\t\t\t\t\t\tfont-size: 24rpx;\n\t\t\t\t\t\t\toverflow: hidden;\n\t\t\t\t\t\t\ttext-overflow: ellipsis;\n\t\t\t\t\t\t\twhite-space: nowrap;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\n\t\t\t\t\t.center {\n\t\t\t\t\t\tmargin-right: 120rpx;\n\t\t\t\t\t\tfont-size: $font-size-base;\n\t\t\t\t\t}\n\t\n\t\t\t\t\t.right {\n\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tjustify-content: space-between;\n\t\n\t\t\t\t\t\t.btn {\n\t\t\t\t\t\t\twidth: 46rpx;\n\t\t\t\t\t\t\theight: 46rpx;\n\t\t\t\t\t\t\tborder-radius: 100%;\n\t\t\t\t\t\t\tpadding: 0;\n\t\t\t\t\t\t\ttext-align: center;\n\t\t\t\t\t\t\tline-height: 46rpx;\n\t\t\t\t\t\t}\n\t\n\t\t\t\t\t\t.number {\n\t\t\t\t\t\t\tfont-size: $font-size-base;\n\t\t\t\t\t\t\twidth: 46rpx;\n\t\t\t\t\t\t\theight: 46rpx;\n\t\t\t\t\t\t\ttext-align: center;\n\t\t\t\t\t\t\tline-height: 46rpx;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/pages/components/pages/address/add.vue",
    "content": "<template>\n\t<uv-navbar\n\t  :fixed=\"false\"\n\t  :title=\"title\"\n\t  left-arrow\n\t  @leftClick=\"$onClickLeft\"\n\t/>\n\t<view class=\"container\">\n\t\t<view class=\"form-box\">\n\t\t\t<view class=\"form\">\n\t\t\t\t<list-cell :hover=\"false\">\n\t\t\t\t\t<view class=\"form-input\">\n\t\t\t\t\t\t<view class=\"label\">收货人</view>\n\t\t\t\t\t\t<input class=\"input\" placeholder=\"请输入收货人\" v-model=\"form.realName\" placeholder-class=\"text-color-assist\" />\n\t\t\t\t\t</view>\n\t\t\t\t</list-cell>\n\t\t\t\t<list-cell :hover=\"false\">\n\t\t\t\t\t<view class=\"form-input\">\n\t\t\t\t\t\t<view class=\"label\">联系方式</view>\n\t\t\t\t\t\t<input class=\"input\" placeholder=\"请输入收货人联系方式\" v-model=\"form.phone\" placeholder-class=\"text-color-assist\" />\n\t\t\t\t\t</view>\n\t\t\t\t</list-cell>\n\t\t\t\t<list-cell :hover=\"false\">\n\t\t\t\t\t<view class=\"form-input\">\n\t\t\t\t\t\t<view class=\"label\">收货地址</view>\n\t\t\t\t\t\t<view class=\"input\" @click=\"chooseLocation\">{{form.address ? form.address : '请选择收货地址'}}</view>\n\t\t\t\t\t</view>\n\t\t\t\t</list-cell>\n\t\t\t\t<list-cell :hover=\"false\">\n\t\t\t\t\t<view class=\"form-input\">\n\t\t\t\t\t\t<view class=\"label\">详细地址</view>\n\t\t\t\t\t\t<input class=\"input\" placeholder=\"请输入收货人详细地址\" v-model=\"form.detail\" placeholder-class=\"text-color-assist\" />\n\t\t\t\t\t</view>\n\t\t\t\t</list-cell>\n\t\t\t\t<list-cell :hover=\"false\">\n\t\t\t\t\t<view class=\"form-input\">\n\t\t\t\t\t\t<view class=\"label\">默认地址</view>\n\t\t\t\t\t\t<view class=\"radio-group\">\n\t\t\t\t\t\t\t<view class=\"radio\" :class=\"{'checked': !form.isDefault}\" style=\"margin-right: 10rpx;\" @tap=\"form.isDefault=0\">否</view>\n\t\t\t\t\t\t\t<view class=\"radio\" :class=\"{'checked': form.isDefault}\" @tap=\"form.isDefault=1\">是</view>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</view>\n\t\t\t\t</list-cell>\n\t\t\t</view>\n\t\t\t<view class=\"btn-section\">\n\t\t\t\t<button type=\"primary\" size=\"default\" @tap=\"save\">保存</button>\n\t\t\t</view>\n\t\t</view>\n\t</view>\n</template>\n\n<script setup>\nimport {\r\n  ref,\n  computed\r\n} from 'vue'\nimport { useMainStore } from '@/store/store'\nimport { storeToRefs } from 'pinia'\nimport { onLoad,onShow } from '@dcloudio/uni-app'\nimport { formatDateTime,prePage } from '@/utils/util'\nimport {\n  getAddressAddAndEdit\r\n} from '@/api/address'\nconst main = useMainStore()\nconst { isLogin,addresses } = storeToRefs(main)\nconst title = ref('编辑地址')\n\nconst form = ref({\n\trealName: '',\n\tisDefault: 0,\n\tphone: '',\n\taddress: '',\n\tdetail: '',\n\tlatitude: '',\n\tlongitude: ''\n})\n\nonLoad((option) => {\n\t//为了方便演示，这里用本地缓存\n\tconsole.log('option:',option)\n\tif (option.id) {\n\t\tform.value = addresses.value.find(item => item.id == option.id)\n\t}\n})\n\nconst save = async() => {\n\tlet data = {}\n\tif (form.value.hasOwnProperty('id')) {\n\t\tdata = await getAddressAddAndEdit(form.value);\n\t} else {\n\t\tdata = await getAddressAddAndEdit(form.value);\n\t}\n\tif (data) {\n\t\tsetTimeout(function(){\n\t\t\tuni.navigateBack()\n\t\t}, 2000);\n\t}\n}\n\nconst chooseLocation = async() => {\n\tuni.chooseLocation({\n\t\tsuccess: function (res) {\n\t\t\tform.value.address = res.address + ' ' + res.name;\n\t\t\tform.value.latitude = res.latitude;\n\t\t\tform.value.longitude = res.longitude;\n\t\t}\n\t});\n\t// let [error, res] = await uni.chooseLocation();\n\t// if (res) {\n\t// \tform.value.address = res.address + ' ' + res.name;\n\t// \tform.value.latitude = res.latitude;\n\t// \tform.value.longitude = res.longitude;\n\t// }\n}\n\n</script>\n\n<style lang=\"scss\" scoped>\n\t.form-box {\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\tpadding: 30rpx;\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tcolor: $text-color-base;\n\n\t\t.form-input {\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\twidth: 100%;\n\t\t}\n\n\t\t.label {\n\t\t\twidth: 200rpx;\n\t\t\tfont-size: $font-size-lg;\n\t\t\tcolor: $text-color-base;\n\t\t\tfont-weight: 500;\n\t\t}\n\n\t\t.input {\n\t\t\tflex: 1;\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t}\n\n\t\t.radio-group {\n\t\t\tdisplay: flex;\n\t\t\tjustify-content: flex-start;\n\n\t\t\t.radio {\n\t\t\t\tpadding: 10rpx 30rpx;\n\t\t\t\tborder-radius: 6rpx;\n\t\t\t\tborder: 2rpx solid $text-color-assist;\n\t\t\t\tcolor: $text-color-assist;\n\t\t\t\tfont-size: $font-size-base;\n\n\t\t\t\t&.checked {\n\t\t\t\t\tbackground-color: $color-primary;\n\t\t\t\t\tcolor: $text-color-white;\n\t\t\t\t\tborder: 2rpx solid $color-primary;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t.btn-section {\n\t\t\tflex: 1;\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\n\t\t\tbutton {\n\t\t\t\tfont-size: $font-size-base;\n\t\t\t\theight: 90rpx;\n\t\t\t\tborder-radius: 50rem !important;\n\t\t\t\twidth: 85%;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tjustify-content: center;\n\t\t\t}\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/pages/components/pages/address/address.vue",
    "content": "<template>\n\t<uv-navbar\n\t  :fixed=\"false\"\n\t  :title=\"title\"\n\t  left-arrow\n\t  @leftClick=\"$onClickLeft\"\n\t/>\r\n\t<view class=\"container\">\r\n\t\t<view class=\"main\">\r\n\t\t\t<uv-empty v-if=\"addresses.length == 0\" mode=\"address\"></uv-empty>\r\n\t\t\t<template v-else>\r\n\t\t\t\t<uv-swipe-action>\r\n\t\t\t\t\t<uv-swipe-action-item class=\"address-wrapper\" :options=\"swipeOption\"\r\n\t\t\t\t\t\t@click=\"handleSwipeClick(address.id)\" v-for=\"(address, index) in addresses\" :key=\"index\">\r\n\t\t\t\t\t\t<view class=\"address\" @tap=\"chooseAddress(address)\">\r\n\t\t\t\t\t\t\t<view class=\"left flex-fill overflow-hidden mr-20\">\r\n\t\t\t\t\t\t\t\t<view class=\"font-size-lg font-weight-bold text-truncate\"\r\n\t\t\t\t\t\t\t\t\tstyle=\"margin-bottom: 10rpx;white-space:normal;\">\r\n\t\t\t\t\t\t\t\t\t{{ address.address + ' ' + address.detail }}\r\n\t\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\t\t<view class=\"font-size-sm text-color-assist\">\r\n\t\t\t\t\t\t\t\t\t{{ address.realName }} {{ address.isDefault ? '默认' : '' }} {{ address.phone }}\r\n\t\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\t<image src=\"/static/images/edit.png\" class=\"edit-icon\" @tap.stop=\"edit(address.id)\"></image>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</uv-swipe-action-item>\r\n\t\t\t\t</uv-swipe-action>\r\n\t\t\t</template>\r\n\t\t</view>\r\n\t\t<view class=\"btn-box\">\r\n\t\t\t<button type=\"primary\" size=\"default\" @tap=\"add\">新增地址</button>\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n\r\n<script setup>\nimport {\r\n  ref,\n  computed\r\n} from 'vue'\nimport { useMainStore } from '@/store/store'\nimport { storeToRefs } from 'pinia'\nimport { onLoad,onShow } from '@dcloudio/uni-app'\nimport { formatDateTime,prePage } from '@/utils/util'\nimport {\n  addressAll,\n  addressDelete,\n  shopGetDistanceFromLocation\r\n} from '@/api/address'\nconst main = useMainStore()\nconst { isLogin,addresses,store } = storeToRefs(main)\nconst title = ref('我的地址')\n\nconst scene = ref('menu')\nconst is_choose = ref(false)\nconst swipeOption = ref(\n[{\r\n\ttext: '删除',\r\n\tstyle: {\r\n\t\tbackgroundColor: '#D12E32'\r\n\t}\r\n}])\n\nonLoad((option) => {\n\tconsole.log('option:',option)\n\tis_choose.value = option.is_choose || false\n\tscene.value = option.scene || 'menu'\n})\nonShow(() => {\n\tinit();\n})\n\nconst init = async() => {\r\n\tlet data = await addressAll();\r\n\tif (data) {\r\n\t\tmain.SET_ADDRESSES(data)\r\n\t}\r\n}\r\nconst add = () => {\r\n\tuni.navigateTo({\r\n\t\turl: '/pages/components/pages/address/add'\r\n\t})\r\n}\r\nconst edit = (id) => {\r\n\tuni.navigateTo({\r\n\t\turl: '/pages/components/pages/address/add?id=' + id\r\n\t})\r\n}\r\nconst handleSwipeClick = async(id) => {\r\n\tuni.showModal({\r\n\t\ttitle: '提示',\r\n\t\tcontent: '确定要删除？',\n\t\tsuccess: async function (res) {\n\t\t\tif (res.confirm) {\n\t\t\t\tlet data = await addressDelete({\n\t\t\t\t\tid: id\n\t\t\t\t});\n\t\t\t\tif (data) {\n\t\t\t\t\tconst index = addresses.value.findIndex(item => item.id == id)\n\t\t\t\t\tconst newaddresses = JSON.parse(JSON.stringify(addresses.value))\n\t\t\t\t\tnewaddresses.splice(index, 1)\n\t\t\t\t\tmain.SET_ADDRESSES(newaddresses)\n\t\t\t\t\tuni.showToast({\n\t\t\t\t\t\ttitle: '删除成功！',\n\t\t\t\t\t\ticon: 'success'\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t} else if (res.cancel) {\n\t\t\t\tconsole.log('用户点击取消');\n\t\t\t}\n\t\t}\r\n\t});\r\n\t// if (res && res.confirm) {\r\n\t\t\r\n\t// }\r\n}\r\nconst chooseAddress = async(address) => {\r\n\tif (!is_choose.value) {\r\n\t\treturn;\r\n\t}\n\t\n\tconsole.log('scene.value:',scene.value)\r\n\r\n\tif (scene.value == 'menu' || scene.value == 'pay') {\r\n\t\tlet data = await shopGetDistanceFromLocation({\r\n\t\t\tlat: address.latitude,\r\n\t\t\tlng: address.longitude,\r\n\t\t\tlat2: main.store.lat,\r\n\t\t\tlng2: main.store.lng\r\n\t\t});\r\n\t\tif (!data) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\t\r\n\t\tif (parseFloat(data) > store.value.distance) {\n\t\t\tuni.showToast({\n\t\t\t\ttitle: '不在配送范围！',\n\t\t\t\ticon: 'error'\n\t\t\t})\r\n\t\t\treturn;\r\n\t\t}\r\n\t\tmain.SET_ADDRESS(address)\r\n\t\tmain.SET_ORDER_TYPE('takeout')\r\n\t\tmain.store.far = data\r\n\t\tmain.store.far_text = data + 'km'\r\n\t\tmain.SET_STORE(main.store)\r\n\t\tmain.SET_LOCATION({\r\n\t\t\tlatitude: address.lat,\r\n\t\t\tlongitude: address.lng\r\n\t\t});\r\n\r\n\t\tif (scene.value == 'menu') {\r\n\t\t\tuni.switchTab({\r\n\t\t\t\turl: '/pages/menu/menu'\r\n\t\t\t})\r\n\t\t} else if (scene.value == 'pay') {\r\n\t\t\tuni.navigateBack();\r\n\t\t}\r\n\t} else if (scene.value == 'scoreShop') {\r\n\t\tmain.SET_ADDRESS(address)\r\n\t\tuni.navigateBack()\r\n\t}\r\n}\n\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t.container {\r\n\t\twidth: 100%;\r\n\t\theight: 100%;\r\n\t}\r\n\r\n\t.main {\r\n\t\twidth: 100%;\r\n\t\tpadding: 30rpx;\r\n\t\tdisplay: flex;\r\n\t\tflex-direction: column;\r\n\t\tpadding-bottom: 100rpx;\r\n\r\n\t\t.address-wrapper {\r\n\t\t\tmargin-bottom: 30rpx;\r\n\t\t}\r\n\r\n\t\t.address {\r\n\t\t\twidth: 100%;\r\n\t\t\tpadding: 40rpx 30rpx;\r\n\t\t\tbackground-color: #FFFFFF;\r\n\t\t\tdisplay: flex;\r\n\t\t\tjustify-content: space-between;\r\n\t\t\talign-items: center;\r\n\r\n\t\t\t.right {\r\n\t\t\t\tflex: 1;\r\n\t\t\t\toverflow: hidden;\r\n\t\t\t\tdisplay: flex;\r\n\t\t\t\tflex-direction: column;\r\n\t\t\t}\r\n\r\n\t\t\t.edit-icon {\r\n\t\t\t\twidth: 50rpx;\r\n\t\t\t\theight: 50rpx;\r\n\t\t\t\tflex-shrink: 0;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t.btn-box {\r\n\t\theight: 100rpx;\r\n\t\tbackground-color: #FFFFFF;\r\n\t\tbox-shadow: 0 0 20rpx rgba($color: $text-color-assist, $alpha: 0.1);\r\n\t\tposition: fixed;\r\n\t\tbottom: 0;\r\n\t\tleft: 0;\r\n\t\tright: 0;\r\n\t\tpadding: 10rpx 0;\r\n\t\tdisplay: flex;\r\n\t\talign-items: center;\r\n\t\tjustify-content: center;\r\n\r\n\t\tbutton {\r\n\t\t\theight: 80rpx;\r\n\t\t\twidth: 80%;\r\n\t\t\tborder-radius: 50rem !important;\r\n\t\t\tdisplay: flex;\r\n\t\t\talign-items: center;\r\n\t\t\tjustify-content: center;\r\n\t\t}\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/pages/components/pages/balance/bill.vue",
    "content": "<template>\n\t<uv-navbar\n\t  :fixed=\"false\"\n\t  :title=\"title\"\n\t  left-arrow\n\t  @leftClick=\"$onClickLeft\"\n\t/>\n\t<view>\n\t\t<view class=\"wrap\">\n\t\t\t<view class=\"bg-white\" v-if=\"cate == 0\">\n\t\t\t\t<uv-tabs activeColor=\"#f29100\" ref=\"tabs\" :list=\"list\" :current=\"current\" @change=\"change\" :scrollable=\"false\" swiperWidth=\"750\"></uv-tabs>\n\t\t\t</view>\n\t\t\t<view class=\"bg-white\" v-else>\n\t\t\t\t<uv-tabs activeColor=\"#f29100\" ref=\"tabs\" :list=\"list1\" :current=\"current\" @change=\"change\" :scrollable=\"false\" swiperWidth=\"750\"></uv-tabs>\n\t\t\t</view>\n\t\t\t<swiper class=\"swiper-box\" :current=\"swiperCurrent\" @transition=\"transition\" @animationfinish=\"animationfinish\">\n\t\t\t\t\n\t\t\t\t<swiper-item class=\"swiper-item\" v-for=\"(item, index) in orderList\" :key=\"index\">\n\t\t\t\t\t<scroll-view scroll-y style=\"height: 100%;width: 100%;\" @scrolltolower=\"reachBottom\">\n\t\t\t\t\t\t<view class=\"page-box\">\n\t\t\t\t\t\t\t<view v-if=\"item.length == 0\">\n\t\t\t\t\t\t\t\t<view class=\"centre\" v-if=\"loadStatus[index] != 'loading'\">\n\t\t\t\t\t\t\t\t\t<view class=\"explain\">\n\t\t\t\t\t\t\t\t\t\t您还没有相关的账单\n\t\t\t\t\t\t\t\t\t\t<view class=\"tips\">可以去看看有那些想买的</view>\n\t\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t\t<view class=\"btn\">\n\t\t\t\t\t\t\t\t\t\t<navigator open-type=\"switchTab\" url=\"/pages/menu/menu\">随便逛逛</navigator>\n\t\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t<view v-else>\n\t\t\t\t\t\t\t\t\t<u-loadmore :status=\"loadStatus[index]\" bgColor=\"#f2f2f2\"></u-loadmore>\n\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t<view v-else>\n\t\t\t\t\t\t\t\t<view class=\"order\" v-for=\"(res, resIndex) in orderList[index]\" :key=\"resIndex\">\n\t\t\t\t\t\t\t\t\t<view class=\"type\">\n\t\t\t\t\t\t\t\t\t\t<view>{{res.title}}</view>\n\t\t\t\t\t\t\t\t\t\t<view>{{res.mark}}</view>\n\t\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t\t<view class=\"total\">\n\t\t\t\t\t\t\t\t\t\t<view>\n\t\t\t\t\t\t\t\t\t\t\t\t{{res.pm == 0 ? '-':'＋'}}￥{{res.number}}元\n\t\t\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t\t\t<view>\n\t\t\t\t\t\t\t\t\t\t\t{{formatDateTime(res.createTime)}}\n\t\t\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t<u-loadmore :status=\"loadStatus[index]\" bgColor=\"#f2f2f2\"></u-loadmore>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</scroll-view>\n\t\t\t\t</swiper-item>\n\t\t\t</swiper>\n\t\t</view>\n\t</view>\n</template>\n\n<script setup>\nimport {\r\n  ref,\n  watch\r\n} from 'vue'\nimport { useMainStore } from '@/store/store'\nimport { storeToRefs } from 'pinia'\nimport { onLoad,onShow} from '@dcloudio/uni-app'\nimport { formatDateTime } from '@/utils/util'\nimport {\n  balanceGetBillList\r\n} from '@/api/user'\nconst main = useMainStore()\nconst { isLogin } = storeToRefs(main)\nconst title = ref('账单')\nconst orderList = ref([[], [], [], []])\nconst list = ref([\n\t\t\t\t{\n\t\t\t\t\tname: '全部'\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: '消费'\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: '充值'\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: '退款'\n\t\t\t\t}\n\t\t\t]\n)\nconst list1 = ref([\n\t\t\t\t{\n\t\t\t\t\tname: '全部'\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tname: '消费'\n\t\t\t\t}\n\t\t\t]\n)\nconst current = ref(0)\nconst swiperCurrent = ref(0)\nconst tabsHeight =ref(0)\nconst dx = ref(0)\nconst loadStatus = ref(['loadmore','loadmore','loadmore','loadmore'])\nconst page = ref(1)\nconst pageSize = ref(20)\nconst tabs = ref()\nconst cate = ref(0)\n\nonLoad((option) => {\n\tconsole.log('cate:',option.cate)\n\tcate.value = option.cate\n\tgetBill();\n})\n\nconst getBill = async() => {\n\tif (loadStatus.value[swiperCurrent.value] == 'loading') {\n\t\treturn;\n\t}\n\tloadStatus.value.splice(swiperCurrent.value,1,\"loading\")\n\tlet data = await balanceGetBillList({cate:cate.value,type:swiperCurrent.value,page:page.value,pagesize:pageSize.value});\n\tif (page.value == 1 ){\n\t\torderList.value[swiperCurrent.value] = [];\n\t}\n\tif (data && data.length > 0) {\n\t\tpage.value++;\n\t\torderList.value[swiperCurrent.value] = orderList.value[swiperCurrent.value].concat(data);\n\t\t\n\t\tloadStatus.value.splice(swiperCurrent.value,1,\"loadmore\")\n\t} else{\n\t\tloadStatus.value.splice(swiperCurrent.value,1,\"nomore\")\n\t}\n}\nconst reachBottom = () => {\n\tif (loadStatus.value[swiperCurrent.value] == 'nomore') {\n\t\treturn;\n\t}\n\tgetBill();\n}\n// tab栏切换\nconst change = (e) => {\n\tswiperCurrent.value = e.index;\n\tpage.value = 1;\n}\nconst transition = ({ detail: { dx } }) => {\n}\nconst animationfinish = ({ detail }) => {\n\tswiperCurrent.value = detail.current;\n\tcurrent.value = detail.current;\n\tpage.value = 1;\n\tgetBill();\n}\n\n\n</script>\n\n<style>\n/* #ifndef H5 */\npage {\n\theight: 100%;\n\tbackground-color: #f2f2f2;\n}\n/* #endif */\n</style>\n\n<style lang=\"scss\" scoped>\n.order {\n\twidth: 710rpx;\n\tbackground-color: #ffffff;\n\tmargin: 20rpx auto;\n\tborder-radius: 20rpx;\n\tbox-sizing: border-box;\n\tpadding: 20rpx;\n\tfont-size: 28rpx;\n\t\n\t.total {\n\t\tdisplay: inline-block;\n\t\ttext-align: right;\n\t\t\n\t\t.total-price {\n\t\t\tfont-size: 36rpx;\n\t\t}\n\t\tfloat: right;\n\t}\n\t.type {\n\t\tdisplay: inline-block;\n\t\t\n\t}\n\t\n}\n.centre {\n\ttext-align: center;\n\tmargin: 200rpx auto;\n\tfont-size: 32rpx;\n\timage {\n\t\twidth: 164rpx;\n\t\theight: 164rpx;\n\t\tborder-radius: 50%;\n\t\tmargin-bottom: 20rpx;\n\t}\n\t.tips {\n\t\tfont-size: 24rpx;\n\t\tcolor: #999999;\n\t\tmargin-top: 20rpx;\n\t}\n\t.btn {\n\t\tmargin: 80rpx auto;\n\t\twidth: 200rpx;\n\t\tborder-radius: 32rpx;\n\t\tline-height: 64rpx;\n\t\tcolor: #ffffff;\n\t\tfont-size: 26rpx;\n\t\tbackground: linear-gradient(270deg, rgba(249, 116, 90, 1) 0%, rgba(255, 158, 1, 1) 100%);\n\t}\n}\n.wrap {\n\tdisplay: flex;\n\tflex-direction: column;\n\theight: calc(100vh - var(--window-top));\n\twidth: 100%;\n}\n.swiper-box {\n\tflex: 1;\n}\n.swiper-item {\n\theight: 100%;\n}\n</style>\n\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/pages/components/pages/coupons/coupons.vue",
    "content": "<template>\n\t<uv-navbar\n\t  :fixed=\"false\"\n\t  :title=\"title\"\n\t  left-arrow\n\t  @leftClick=\"$onClickLeft\"\n\t/>\n\t<view class=\"container position-relative w-100 h-100 overflow-hidden\">\n\t\t<view class=\"exchange-box\">\n\t\t\t<view class=\"input-box\">\n\t\t\t\t<input type=\"text\" v-model=\"exchange_code\" placeholder=\"请输入兑换码\" placeholder-class=\"text-color-assist font-size-base\" />\n\t\t\t\t<button type=\"primary\" @click=\"exchange\">兑换</button>\n\t\t\t</view>\n\t\t</view>\n\t\t<view class=\"tabbar\">\n\t\t\t<view class=\"tab\" :class=\"{active: activeTabIndex == index}\" \n\t\t\t\tv-for=\"(item, index) in tabs\" :key=\"index\" @tap=\"handleTab(index)\">\n\t\t\t\t<view class=\"title\">{{ item.title }}</view>\n\t\t\t</view>\n\t\t</view>\n\t\t<view class=\"flex-fill\">\n\t\t\t<scroll-view scroll-y class=\"coupon-list\" @scrolltolower=\"getCoupons(activeTabIndex)\">\n\t\t\t\t<view class=\"wrapper\"  v-if=\"0 === activeTabIndex\">\n\t\t\t\t\t<uv-empty v-if=\"myCoupons.length == 0\" mode=\"list\"></uv-empty>\n\t\t\t\t\t<view class=\"coupon\" v-for=\"(item, index) in myCoupons\" :key=\"index\" @tap=\"openDetailModal(item,index)\">\n\t\t\t\t\t\t<view class=\"taobao\">\n\t\t\t\t\t\t\t<view class=\"ticket\">\n\t\t\t\t\t\t\t\t<view class=\"left\">\n\t\t\t\t\t\t\t\t\t<image\n\t\t\t\t\t\t\t\t\t\tclass=\"picture\"\n\t\t\t\t\t\t\t\t\t\t:src=\"item.image\"\n\t\t\t\t\t\t\t\t\t\tmode=\"aspectFill\"\n\t\t\t\t\t\t\t\t\t></image>\n\t\t\t\t\t\t\t\t\t<view class=\"introduce\">\n\t\t\t\t\t\t\t\t\t\t<view class=\"top\">\n\t\t\t\t\t\t\t\t\t\t\t￥\n\t\t\t\t\t\t\t\t\t\t\t<text class=\"big\">{{item.value}}</text>\n\t\t\t\t\t\t\t\t\t\t\t<view>\n\t\t\t\t\t\t\t\t\t\t\t\t满{{item.least}}减{{item.value}}\n\t\t\t\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t\t\t<view class=\"type\">{{ item.title }}</view>\n\t\t\t\t\t\t\t\t\t\t<view class=\"date u-line-1\">{{formatDateTime(item.startTime, 'yyyy-MM-dd')}}-{{formatDateTime(item.endTime, 'yyyy-MM-dd')}}</view>\n\t\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t<view class=\"right\" @click.stop=\"\" v-if=\"activeTabIndex == 1\">\n\t\t\t\t\t\t\t\t\t<view class=\"use immediate-use\" :round=\"true\" @tap=\"receive(item, index)\" >立即领取</view>\n\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t<view class=\"right\" @click.stop=\"\" v-if=\"activeTabIndex == 0\">\n\t\t\t\t\t\t\t\t\t<view v-if=\"item.status == 0\" class=\"use immediate-use\" :round=\"true\" @tap=\"useCouponWith(item)\" >立即使用</view>\n\t\t\t\t\t\t\t\t\t<view v-else class=\"used\">已使用</view>\n\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</view>\n\t\t\t\t</view>\n\t\t\t\t<view class=\"wrapper\" v-if=\"1 === activeTabIndex\">\n\t\t\t\t\t<uv-empty v-if=\"notCoupons.length == 0\" mode=\"list\"></uv-empty>\n\t\t\t\t\t<view class=\"coupon\" v-for=\"(item, index) in notCoupons\" :key=\"index\" @tap=\"openDetailModal(item,index)\">\n\t\t\t\t\t\t<view class=\"taobao\">\n\t\t\t\t\t\t\t<view class=\"ticket\">\n\t\t\t\t\t\t\t\t<view class=\"left\">\n\t\t\t\t\t\t\t\t\t<image\n\t\t\t\t\t\t\t\t\t\tclass=\"picture\"\n\t\t\t\t\t\t\t\t\t\t:src=\"item.image\"\n\t\t\t\t\t\t\t\t\t\tmode=\"aspectFill\"\n\t\t\t\t\t\t\t\t\t></image>\n\t\t\t\t\t\t\t\t\t<view class=\"introduce\">\n\t\t\t\t\t\t\t\t\t\t<view class=\"top\">\n\t\t\t\t\t\t\t\t\t\t\t￥\n\t\t\t\t\t\t\t\t\t\t\t<text class=\"big\">{{item.value}}</text>\n\t\t\t\t\t\t\t\t\t\t\t<view>\n\t\t\t\t\t\t\t\t\t\t\t\t满{{item.least}}减{{item.value}}\n\t\t\t\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t\t\t<view class=\"type\">{{ item.title }}</view>\n\t\t\t\t\t\t\t\t\t\t<view class=\"date u-line-1\">{{formatDateTime(item.startTime, 'yyyy-MM-dd')}}-{{formatDateTime(item.endTime, 'yyyy-MM-dd')}}</view>\n\t\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t<view class=\"right\" @click.stop=\"\" v-if=\"activeTabIndex == 1\">\n\t\t\t\t\t\t\t\t\t<!-- <view class=\"use immediate-use\" :round=\"true\" @tap=\"receive(item, index)\" >立即领取</view> -->\n\t\t\t\t\t\t\t\t\t<view  class=\"use immediate-use\" :round=\"true\" v-if=\"item.isReceive == 0\" @tap=\"receive(item, index)\" >立即领取</view>\n\t\t\t\t\t\t\t\t\t<view v-else class=\"used immediate-use\">已领取</view>\n\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t<view class=\"right\" @click.stop=\"\" v-if=\"activeTabIndex == 0\">\n\t\t\t\t\t\t\t\t\t<view v-if=\"item.status == 0\" class=\"use immediate-use\" :round=\"true\" @tap=\"useCouponWith(item)\" >立即使用</view>\n\t\t\t\t\t\t\t\t\t<view v-else class=\"used\">已使用</view>\n\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</view>\n\t\t\t\t</view>\n\t\t\t</scroll-view>\n\t\t</view>\n\t\t<modal custom :show=\"detailModalVisible\" @cancel=\"closeDetailModal\" width=\"90%\">\n\t\t\t<view class=\"modal-content\">\n\t\t\t\t<view class=\"d-flex font-size-extra-lg text-color-base just-content-center mb-20\">{{ coupon.title }}</view>\n\t\t\t\t<view class=\"d-flex font-size-sm text-color-base mb-20\">\n\t\t\t\t\t有效期：{{formatDateTime(coupon.startTime, 'yyyy-MM-dd')}}-{{formatDateTime(coupon.endTime, 'yyyy-MM-dd')}}\n\t\t\t\t</view>\n\t\t\t\t<view class=\"d-flex font-size-sm text-color-base mb-20\">\n\t\t\t\t\t领取时间：{{formatDateTime(coupon.createTime)}}\n\t\t\t\t</view>\n\t\t\t\t<view class=\"d-flex font-size-sm text-color-base mb-20\">\n\t\t\t\t\t券价值：满{{ coupon.least }}减{{ coupon.value }}\n\t\t\t\t</view>\n\t\t\t\t<view class=\"d-flex font-size-sm text-color-base mb-20\" v-if=\"activeTabIndex == 1\">\n\t\t\t\t\t每人限领：{{ coupon.limit }} 张\n\t\t\t\t</view>\n\t\t\t\t<view class=\"d-flex font-size-sm text-color-base mb-20\">\n\t\t\t\t\t适用范围：{{typeInfo(coupon.type)}}\n\t\t\t\t</view>\n\t\t\t\t<view class=\"d-flex font-size-sm text-color-base mb-20\">\n\t\t\t\t\t适用店铺：{{coupon.shopName}}\n\t\t\t\t</view>\n\t\t\t\t<view class=\"d-flex align-items-center just-content-center\" v-if=\"activeTabIndex == 0\">\n\t\t\t\t\t<button type=\"primary\" @tap=\"useCoupon\" class=\"use-coupon-btn\">立即使用</button>\n\t\t\t\t</view>\n\t\t\t\t<view class=\"d-flex align-items-center just-content-center\" v-if=\"activeTabIndex == 1\">\n\t\t\t\t\t<button type=\"primary\" @tap=\"receive(coupon, couponIndex)\" class=\"use-coupon-btn\">立即领取</button>\n\t\t\t\t</view>\n\t\t\t</view>\n\t\t</modal>\n\t\t\n\t\t<!--轻提示-->\n\t\t<uv-toast ref=\"uToast\"></uv-toast>\n\t</view>\n</template>\n\n<script setup>\nimport {\r\n  ref,\n  watch\r\n} from 'vue'\nimport { useMainStore } from '@/store/store'\nimport { storeToRefs } from 'pinia'\nimport { onLoad,onShow ,onPullDownRefresh,onHide} from '@dcloudio/uni-app'\nimport { formatDateTime,kmUnit } from '@/utils/util'\nimport {\n  couponReceive,\n  couponMine,\n  couponIndexApi\r\n} from '@/api/coupon'\nconst main = useMainStore()\nconst { isLogin } = storeToRefs(main)\nconst title = ref('优惠券')\n\nconst tabs = ref([\n\t{title: '我的优惠券', page:1, pagesize:10,\n\t\tcoupons: []\n\t},\n\t{title: '未领优惠券', page:1, pagesize:10,\n\t\tcoupons: []\n\t}\n])\nconst activeTabIndex = ref(0)\nconst detailModalVisible = ref(false)\nconst coupon = ref({})\nconst couponIndex = ref(0)\nconst exchange_code = ref('')\nconst uToast = ref()\nconst myCoupons = ref([])\nconst notCoupons = ref([])\n\nonShow(() => {\n\tgetCoupons(0)\n})\nonPullDownRefresh(() => {\n\tif(activeTabIndex.value == 0) {\n\t\tmyCoupons.value = []\n\t}\n\tif(activeTabIndex.value == 1) {\n\t\tnotCoupons.value = []\n\t}\n\ttabs.value[activeTabIndex.value].page = 1;\n\tgetCoupons(activeTabIndex.value)\n})\nwatch(activeTabIndex, () => {\n   getCoupons(activeTabIndex.value)\n})\n\n// 兑换\nconst exchange = async() => {\n\tlet data = await couponReceive({code:exchange_code.value});\n\tif (data) {\n\t\tuToast.value.show({\n\t\t\tmessage: '兑换成功',\n\t\t\ttype: 'success'\n\t\t});\n\t\ttabs.value[0].coupons = [];\n\t\ttabs.value[0].page = 1;\n\t\tgetCoupons(0)\n\t\ttabs.value[1].coupons = [];\n\t\ttabs.value[1].page = 1;\n\t\tgetCoupons(1)\n\t}\n}\n// 使用范围\nconst typeInfo = (type) => {\n\tif (type == 0) {\n\t\treturn '通用'\n\t}\n\tif (type == 1) {\n\t\treturn '自取'\n\t}\n\tif (type == 2) {\n\t\treturn '外卖'\n\t}\n}\nconst handleTab = (index) => {\n\tconsole.log('activeTabIndex2:',index)\n\tactiveTabIndex.value = index\n}\nconst getCoupons = async(type) => {\n\tlet page = tabs.value[type].page;\n\tlet pagesize = tabs.value[type].pagesize;\n\t// 我的优惠券\n\tlet data = [];\n\tif (type == 0) {\n\t\tmyCoupons.value = await couponMine({page:page,pagesize:pagesize});\n\t}\n\t// 未领优惠券\n\tif (type == 1) {\n\t\tnotCoupons.value = await couponIndexApi({page:page,pagesize:pagesize});\n\t}\n\t//console.log('data:',data)\n\tuni.stopPullDownRefresh();\n\t\n\tconsole.log('tabs.value:',tabs.value[type].title)\n\t//tabs.value[type].page++;\n}\nconst openDetailModal = (coupon,index) => {\n\tcouponIndex.value = index;\n\tcoupon.value = coupon\n\tdetailModalVisible.value = true\n}\nconst useCouponWith = (coupon) => {\n\t//coupon.value = coupon\n\tuseCoupon();\n}\nconst closeDetailModal = () => {\n\tdetailModalVisible.value = false\n\tcoupon.value = {}\n}\nconst useCoupon = () => {\n\tuni.switchTab({\n\t\turl: '/pages/menu/menu'\n\t})\n}\nconst showTip1 = () => {\n\tuni.showToast({\n\t\ttitle: '您暂时还没有赠送中卡券哦~',\n\t\ticon: 'none'\n\t})\n}\nconst showTip2 = () => {\n\tuni.showToast({\n\t\ttitle: '您暂时还没有券码哦~',\n\t\ticon: 'none'\n\t})\n}\n// 领取优惠券\nconst receive = async(coupon,index) => {\n\tlet data = await couponReceive({id:coupon.id});\n\tif (data) {\n\t\tuToast.value.show({\n\t\t\tmessage: '领取成功',\n\t\t\ttype: 'success'\n\t\t});\n\t\tdetailModalVisible.value = false\n\t\tgetCoupons(1)\n\t}\n}\n\n\n</script>\n\n<style lang=\"scss\" scoped>\n/* #ifdef H5 */\npage {\n\theight: 100%;\n}\n/* #endif */\n\n.container {\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n.exchange-box {\n\tflex-shrink: 0;\n\theight: 200rpx;\n\tbackground-color: #ffffff;\n\tdisplay: flex;\n\tflex-direction: column;\n\talign-items: center;\n\tjustify-content: center;\n\n\t.input-box {\n\t\tdisplay: flex;\n\t\talign-items: stretch;\n\t\twidth: 70%;\n\t\tflex-shrink: 0;\n\t\tinput {\n\t\t\tflex: 1;\n\t\t\theight: 80rpx;\n\t\t\tborder: 1rpx solid #eee;\n\t\t\tborder-right: 0;\n\t\t\tborder-radius: 8rpx 0 0 8rpx;\n\t\t\t// padding: 20rpx;\n\t\t\tfont-size: $font-size-base;\n\t\t\tcolor: $text-color-base;\n\t\t}\n\t\tbutton {\n\t\t\tborder-radius: 0 8rpx 8rpx 0;\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t}\n\t}\n}\n\n.tabbar {\n\tflex-shrink: 0;\n\twidth: 100%;\n\theight: 120rpx;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\t\n\t.tab {\n\t\tflex: 1;\n\t\theight: 100%;\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tjustify-content: center;\n\t\talign-items: center;\n\t\tfont-size: $font-size-base;\n\t\tcolor: $text-color-base;\n\t\tposition: relative;\n\t\t\n\t\t.title {\n\t\t\tpadding: 15rpx 0;\n\t\t}\n\t\t\n\t\t&.active {\n\t\t\tcolor: $color-primary;\n\t\t\t\n\t\t\t.title {\n\t\t\t\tborder-bottom: 5rpx solid $color-primary;\n\t\t\t}\n\t\t}\n\t}\n}\n\n\n.coupon-list {\n\theight: calc(100vh - 120rpx - 200rpx);\n\t/* #ifdef H5 */\n\theight: calc(100vh - 120rpx - 200rpx - 44px);\n\t/* #endif */\n}\n\n.wrapper {\n\tpadding: 0 20rpx;\n\tdisplay: flex;\n\tflex-direction: column;\n\t\n\t.coupon {\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tbackground-color: #FFFFFF;\n\t\tmargin-bottom: 30rpx;\n\t\t//padding: 0 30rpx;\n\t\tborder-radius: 6rpx;\n\t\tbox-shadow: 0 10rpx 10rpx -10rpx rgba(15, 15, 15, 0.1);\n\t\tposition: relative;\n\t\t\n\t\t&::before {\n\t\t\tcontent: \"\";\n\t\t\tposition: absolute;\n\t\t\tbackground-color: $bg-color;\n\t\t\twidth: 30rpx;\n\t\t\theight: 30rpx;\n\t\t\tbottom: 65rpx;\n\t\t\tleft: -15rpx;\n\t\t\tborder-radius: 100%;\n\t\t}\n\t\t\n\t\t&::after {\n\t\t\tcontent: \"\";\n\t\t\tposition: absolute;\n\t\t\tbackground-color: $bg-color;\n\t\t\twidth: 30rpx;\n\t\t\theight: 30rpx;\n\t\t\tbottom: 65rpx;\n\t\t\tright: -15rpx;\n\t\t\tborder-radius: 100%;\n\t\t}\n\t\t\n\t\t.detail {\n\t\t\tpadding: 20rpx 0;\n\t\t\tposition: relative;\n\n\t\t    &::after {\n\t\t\t\tcontent: '';\n\t\t\t\tposition: absolute;\n\t\t\t\tleft: 0;\n\t\t\t\tright: 0;\n\t\t\t\tbottom: 0;\n\t\t\t\tborder-bottom: 1rpx dashed #c6c6c6;\n\t\t\t\ttransform: scaleY(0.5);\n\t\t\t}\n\t\t\t\n\t\t\t.coupon-img {\n\t\t\t\twidth: 150rpx;\n\t\t\t\theight: 150rpx;\n\t\t\t\tmargin-right: 40rpx;\n\t\t\t}\n\t\t}\n\t}\n}\n\n.use-coupon-btn {\n\twidth: 95%;\n\tborder-radius: 50rem !important;\n}\n\n\n.taobao {\n\tbackground-color: white;\n\t.title {\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tjustify-content: space-between;\n\t\tmargin-bottom: 20rpx;\n\t\tfont-size: 30rpx;\n\t\t.left {\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t}\n\t\t.store {\n\t\t\tfont-weight: 500;\n\t\t}\n\t\t.buddha {\n\t\t\twidth: 70rpx;\n\t\t\theight: 70rpx;\n\t\t\tborder-radius: 10rpx;\n\t\t\tmargin-right: 10rpx;\n\t\t}\n\t\t.entrance {\n\t\t\tcolor: $uv-info;\n\t\t\tborder: solid 2rpx $uv-info;\n\t\t\tline-height: 48rpx;\n\t\t\tpadding: 0 30rpx;\n\t\t\tbackground: none;\n\t\t\tborder-radius: 15px;\n\t\t}\n\t}\n\t.ticket {\n\t\tdisplay: flex;\n\t\t.left {\n\t\t\twidth: 70%;\n\t\t\tpadding: 20rpx;\n\t\t\tbackground-color: white;//rgb(255, 245, 244);\n\t\t\tborder-radius: 20rpx;\n\t\t\tborder-right: dashed 2rpx rgb(224, 215, 211);\n\t\t\tdisplay: flex;\n\t\t\t.picture {\n\t\t\t\t//width: 172rpx;\n\t\t\t\tborder-radius: 20rpx;\n\t\t\t\twidth: 190rpx;\n\t\t\t\theight: 190rpx;\n\t\t\t}\n\t\t\t.introduce {\n\t\t\t\tmargin-left: 10rpx;\n\t\t\t\t.top{\n\t\t\t\t\tcolor:$uv-warning;\n\t\t\t\t\tfont-size: 28rpx;\n\t\t\t\t\t.big{\n\t\t\t\t\t\tfont-size: 60rpx;\n\t\t\t\t\t\tfont-weight: bold;\n\t\t\t\t\t\tmargin-right: 10rpx;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t.type{\n\t\t\t\t\tfont-size: 28rpx;\n\t\t\t\t\tcolor: $uv-info-dark;\n\t\t\t\t}\n\t\t\t\t.date{\n\t\t\t\t\tmargin-top: 10rpx;\n\t\t\t\t\tfont-size: 20rpx;\n\t\t\t\t\tcolor: $uv-info-dark;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t.right {\n\t\t\twidth: 30%;\n\t\t\tpadding: 40rpx 20rpx;\n\t\t\tbackground-color: white;//rgb(255, 245, 244);\n\t\t\tborder-radius: 20rpx;\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\t.use {\n\t\t\t\theight: auto;\n\t\t\t\tpadding: 0 20rpx;\n\t\t\t\tfont-size: 24rpx;\n\t\t\t\tborder-radius: 40rpx;\n\t\t\t\tcolor: #ffffff!important;\n\t\t\t\tbackground-color: $uv-warning!important;\n\t\t\t\tline-height: 40rpx;\n\t\t\t\tcolor: rgb(117, 142, 165);\n\t\t\t\tmargin-left: 20rpx;\n\t\t\t}\n\t\t\t.used {\n\t\t\t\theight: auto;\n\t\t\t\tpadding: 0 20rpx;\n\t\t\t\tfont-size: 24rpx;\n\t\t\t\tborder-radius: 40rpx;\n\t\t\t\tcolor: #ffffff!important;\n\t\t\t\tbackground-color: $uv-info-dark!important;\n\t\t\t\tline-height: 40rpx;\n\t\t\t\tcolor: rgb(117, 142, 165);\n\t\t\t\tmargin-left: 20rpx;\n\t\t\t}\n\t\t}\n\t\t.right_log {\n\t\t\ttext-align: center;\n\t\t\twidth: 30%;\n\t\t\tpadding: 80rpx 0rpx;\n\t\t\tbackground-color: white;//rgb(255, 245, 244);\n\t\t\tborder-radius: 20rpx;\n\t\t\t\n\t\t\talign-items: center;\n\t\t\t\n\t\t}\n\t}\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/pages/components/pages/login/login.vue",
    "content": "<template>\n\t<layout>\n\t\t<uv-navbar\n\t\t  :fixed=\"false\"\n\t\t  :title=\"title\"\n\t\t  left-arrow\n\t\t  @leftClick=\"$onClickLeft\"\n\t\t/>\n\t\t<view class=\"wrap\">\n\t\t<view class=\"top\"></view>\n\t\t<view class=\"content\">\n\t\t\t<view class=\"title\">欢迎登录</view>\n\t\t\t\n\t\t\t<input class=\"u-border-bottom\" type=\"number\" v-model=\"mobile\" placeholder=\"请输入手机号\" />\n\t\t\t<view class=\"tips\">未注册的手机号验证后自动创建账号</view>\n\n\t\t\t\n\t\t\t<view style=\"display: flex;\">\n\t\t\t\t<view style=\"width: 50%;\">\n\t\t\t\t\t<input style=\"height: 100rpx;\"  class=\"u-border-bottom\" type=\"number\" v-model=\"captcha\" placeholder=\"请输入验证码\" />\n\t\t\t\t</view>\n\t\t\t\t<view style=\"width: 50%;\">\n\t\t\t\t\t<button style=\"height: 100rpx;\" @tap=\"getCaptcha\" :style=\"[captchaStyle]\" class=\"getCaptcha\">\n\t\t\t\t\t\t{{captchaText}}\n\t\t\t\t\t\t<uv-code :seconds=\"seconds\" @end=\"endCaptcha\" @start=\"startCaptcha\" ref=\"uCode\" @change=\"changeCapcha\"></uv-code>\n\t\t\t\t\t<!-- \t<u-verification-code :seconds=\"seconds\" @end=\"endCaptcha\" @change=\"changeCapcha\" @start=\"startCaptcha\" ref=\"uCode\" >\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t</u-verification-code> -->\n\t\t\t\t\t</button>\n\t\t\t\t</view>\n\t\t\t</view>\n\t\t\t\n\t\t\t<button @tap=\"submit\" style=\"color:#fff;background-color: #f9ae3d;\" type=\"primary\" class=\"login\">登录</button>\n\t\t\t\n\t\t</view>\n\t\t<view class=\"buttom\">\n\t\t\t<view class=\"loginType\">\n\t\t\t\t<!-- #ifdef MP-WEIXIN -->\n\t\t\t\t<button type=\"primary\" size=\"default\" class=\"login-btn\" open-type=\"getPhoneNumber\" @getphonenumber=\"loginForWechatMini\">\n\t\t\t\t<!-- \t<image src=\"/static/images/mine/wechat.png\"></image> -->\n\t\t\t\t\t手机号快捷登录\n\t\t\t\t</button>\n\t\t\t\t<!-- #endif -->\n\t\t\t</view>\n\t\t\t<view class=\"hint\">\n\t\t\t<!-- \t<label class=\"label\"> -->\n\t\t\t\t\t<radio value=\"isChecked\" @tap.stop=\"onChange\" />\n\t\t\t\t\t我已经阅读并遵守\n\t\t\t\t\t<text class=\"link\" @tap=\"serv(29,'用户协议')\">《用户协议》</text>与\n\t\t\t\t\t\t<text class=\"link\"  @tap=\"serv(30,'隐私政策')\">《隐私政策》</text>\n\t\t\t<!-- \t</label> -->\n\t\t\t</view>\n\t\t</view>\n\t\t<uv-toast ref=\"uToast\"></uv-toast>\n\t</view>\n\t</layout>\n</template>\n\n<script setup>\nimport {\r\n  ref,\n  computed\r\n} from 'vue'\r\nimport { onLoad,onShow } from '@dcloudio/uni-app'\nimport { useMainStore } from '@/store/store'\nimport {\n  userAuthSession,\n  userLoginForWechatMini,\n  smsSend,\n  userLogin\n} from '@/api/auth'\nimport * as util  from '@/utils/util'\nimport { mobile as testMobible } from '@/uni_modules/uv-ui-tools/libs/function/test'\nconst main = useMainStore()\nconst title = ref('登录')\nconst mobile = ref('')\r\nconst captcha = ref('')\nconst captchaText = ref('获取验证码')\t\nconst password = ref('')\nconst seconds = ref(60)\nconst isChecked = ref(false)\nconst openid = ref(main.openid)\nconst uToast = ref()\nconst uCode = ref()\n\nconst captchaStyle = computed(() => {\n  let style = {};\n  if(mobile.value && captchaText.value == '获取验证码') {\n  \tstyle.color = \"#fff\";\n  \tstyle.backgroundColor = '#f9ae3d';\n  }\n  return style;\n});\n\nonShow(() => {\n   \n\t// #ifdef MP-WEIXIN\n\tif(!openid.value){\n\t\twechatMiniLogin();\n\t}\n\t\n\t// #endif\r\n})\n\n\nconst wechatMiniLogin = () => {\n\t//this.$u.toast('登录中');\n\tuni.login({\n\t\tprovider: 'weixin'\n\t}).then(async (res) => {\n\t\tlet data = await userAuthSession({\n\t\t\tcode: res.code\n\t\t});\n\t\tif (data) {\n\t\t\tconsole.log('data.openId001:',data.openId)\n\t\t\tmain.SET_OPENID(data.openId)\n\t\t\topenid.value = data.openId\n\t\t}\r\n\t});\n}\n\n\n\nconst loginForWechatMini = async (e) => {\n\tif(!isChecked.value){\n\t\tuToast.value.show({\n\t\t\tmessage: '请勾选下面协议',\n\t\t\ttype: 'error'\n\t\t});\n\t\treturn\n\t}\n\tif (e.detail.encryptedData && e.detail.iv) {\n\t\tlet data = await userLoginForWechatMini({\n\t\t\tencryptedData: e.detail.encryptedData,\n\t\t\tiv: e.detail.iv,\n\t\t\topenid: openid.value\n\t\t});\n\t\tif (data) {\n\t\t\tmain.SET_MEMBER(data.userInfo);\n\t\t\tmain.SET_TOKEN(data.accessToken);\n\t\t\tuToast.value.show({\n\t\t\t\ttitle: '登录成功',\n\t\t\t\ttype: 'success'\n\t\t\t});\n\t\t\tsetTimeout(function() {\n\t\t\t\tuni.navigateBack();\n\t\t\t}, 2000);\n\t\t}\n\t}\n}\n\nconst getCaptcha = async () => {\n\t\t\t\n\tif (testMobible(mobile.value) == false) {\n\t\tuToast.value.show({\n\t\t\tmessage: '手机号码格式不对',\n\t\t\ttype: 'error'\n\t\t});\n\t\treturn\n\t}\n\t\n\tlet data = await smsSend({\n\t\tmobile: mobile.value,\n\t\tscene: 1\n\t});\t\t\n\tif (data) {\n\t\tuCode.value.start();\n\t\t\n\t}\n}\n\n// 验证码开始计时\t\nconst startCaptcha = () => {\n\tconsole.log('start')\n}\n// 验证码结束\nconst endCaptcha = () => {\n\tconsole.log('end')\n\tcaptchaText.value = '获取验证码';\n}\nconst changeCapcha = (text)  => {\n\tconsole.log('change:' + text)\n\tcaptchaText.value = text;\n}\n// 提交\nconst submit = () => {\n\t\n\tif (testMobible(mobile.value) == false) {\n\t\tuToast.value.show({\n\t\t\tmessage: '手机号码格式不对',\n\t\t\ttype: 'error'\n\t\t});\n\t\treturn\n\t}\n\t\n\tif(!isChecked.value){\n\t\tuToast.value.show({\n\t\t\tmessage: '请勾选下面协议',\n\t\t\ttype: 'error'\n\t\t});\n\t\treturn\n\t}\n\t\n\tlogin()\n\n}\n\n// 登录\nconst login = async () => {\n\tlet from = 'routine'\n\t// #ifdef H5\n\tfrom = 'h5'\n\tif(util.isWeixin()){\n\t\tfrom = 'wechat'\n\t}\n\t\n\t// #endif\n\tlet data = await userLogin({\n\t\tmobile: mobile.value,\n\t\tcode: captcha.value,\n\t\tfrom: from,\n\t\topenid: openid.value\n\t})\n\tif (data) {\n\t\tuni.setStorage({\n\t\t\tkey: 'userinfo',\n\t\t\tdata: data.userInfo\n\t\t});\n\t\tuni.setStorage({\n\t\t\tkey: 'accessToken',\n\t\t\tdata: data.accessToken\n\t\t});\n\t\tmain.SET_MEMBER(data.userInfo);\n\t\tmain.SET_TOKEN(data.accessToken);\n\t\tuToast.value.show({\n\t\t\tmessage: '登录成功',\n\t\t\ttype: 'success'\n\t\t});\n\t\tsetTimeout(function() {\n\t\t\tuni.navigateBack();\n\t\t}, 2000);\n\t}\n}\n\nconst serv = (id,name) => {\n\tuni.navigateTo({\n\t\t\turl: '/pages/components/pages/mine/content?id=' + id + '&name=' + name\n\t})\n}\n\nconst onChange = () => {\n\tisChecked.value = !isChecked.value\n}\n\n</script>\n\n<style lang=\"scss\" scoped>\n.wrap {\n\tbackground-color: #ffffff;\n\tfont-size: 28rpx;\n\tposition: relative;\n\theight: 100%;\n\t.content {\n\t\twidth: 600rpx;\n\t\tmargin: 0 auto;\n\n\t\t.title {\n\t\t\ttext-align: left;\n\t\t\tfont-size: 60rpx;\n\t\t\tfont-weight: 500;\n\t\t\tmargin-bottom: 100rpx;\n\t\t}\n\t\tinput {\n\t\t\ttext-align: left;\n\t\t\tmargin-bottom: 10rpx;\n\t\t\tpadding-bottom: 6rpx;\n\t\t}\n\t\t.tips {\n\t\t\tcolor: $uv-info;\n\t\t\tmargin-bottom: 60rpx;\n\t\t\tmargin-top: 8rpx;\n\t\t}\n\t\t.getCaptcha {\n\t\t\tbackground-color: rgb(253, 243, 208);\n\t\t\tcolor: $uv-tips-color;\n\t\t\tborder: none;\n\t\t\tfont-size: 30rpx;\n\t\t\tpadding: 12rpx 0;\n\t\t\t\n\t\t\t&::after {\n\t\t\t\tborder: none;\n\t\t\t}\n\t\t}\n\t\t.login {\n\t\t\tbackground-color: rgb(253, 243, 208);\n\t\t\tcolor: $uv-tips-color;\n\t\t\tborder: none;\n\t\t\tfont-size: 30rpx;\n\t\t\tpadding: 12rpx 0;\n\t\t\tmargin-top: 40rpx;\n\t\t\t&::after {\n\t\t\t\tborder: none;\n\t\t\t}\n\t\t}\n\t\t.alternative {\n\t\t\tcolor: $uv-tips-color;\n\t\t\tdisplay: flex;\n\t\t\tjustify-content: space-between;\n\t\t\tmargin-top: 30rpx;\n\t\t}\n\t}\n\t.buttom {\n\t\t//position: absolute;\n\t\tbottom: 0;\n\t\t//display: flex;\n\t\t//flex-direction: column;\n\t\t//align-items: center;\n\t\t//justify-content: center;\n\t\t.loginType {\n\t\t\tpadding: 80rpx;\n\t\t\t//justify-content:space-between;\n\t\t\t\n\t\t\t.login-btn {\n\t\t\t\tbackground-color: #1aad19!important;\n\t\t\t\twidth: 100%;\n\t\t\t\tborder-radius: 50rem !important;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tjustify-content: center;\n\t\t\t\tpadding: 10rpx 0;\n\t\t\t\ttext-align: center;\n\t\t\t\timage {\n\t\t\t\t\twidth: 36rpx;\n\t\t\t\t\theight: 30rpx;\n\t\t\t\t\tmargin-right: 10rpx;\n\t\t\t\t\tvertical-align: middle;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t\n\t\t.hint {\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\t\t\tpadding: 20rpx 40rpx;\n\t\t\tfont-size: 20rpx;\n\t\t\tcolor: $uv-tips-color;\n\t\t\t\n\t\t\t.link {\n\t\t\t\tcolor: $uv-warning;\n\t\t\t}\n\t\t}\n\t}\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/pages/components/pages/login/logout.vue",
    "content": "<template>\n\t<!-- <layout> -->\n\t\t<uv-navbar\n\t\t  :fixed=\"false\"\n\t\t  :title=\"title\"\n\t\t  left-arrow\n\t\t  @leftClick=\"$onClickLeft\"\n\t\t/>\n\t\t<view class=\"top\">\n\t\t\t<button type=\"primary\" size=\"default\" class=\"login-btn\" @click=\"logout\">\n\t\t\t\t确定退出登录\n\t\t\t</button>\n\t\t\t<uv-toast ref=\"uToast\"></uv-toast>\n\t\t</view>\n\t<!-- </layout> -->\n</template>\n\n<script setup>\nimport {\n  ref\n} from 'vue'\nimport { useMainStore } from '@/store/store'\nimport { storeToRefs } from 'pinia'\nconst main = useMainStore()\nconst { member,isLogin } = storeToRefs(main)\nconst uToast = ref()\nconst title = ref('退出')\n\nconst logout = () => {\n\tuni.removeStorageSync( 'userinfo');\n\tuni.removeStorageSync( 'accessToken');\n\tmain.SET_MEMBER({});\n\tmain.SET_TOKEN('');\n\tuToast.value.show({\n\t\tmessage: '已退出',\n\t\ttype: 'success'\n\t});\n\tsetTimeout(function() {\n\t\tuni.redirectTo({\n\t\t\turl: '/pages/components/pages/login/login',\n\t\t})\n\t}, 1000);\n}\n\t\n</script>\n\n<style lang=\"scss\" scoped>\n\t\n\t.top {\n\t\tdisplay: flex; \n\t\theight: 100%;\n\t}\n\tbutton{\n\t\t margin: auto;\n\t}\n</style>\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/pages/components/pages/mine/content.vue",
    "content": "<template>\n\t<layout>\n\t\t<uv-navbar\n\t\t  :fixed=\"false\"\n\t\t  :title=\"title\"\n\t\t  left-arrow\n\t\t  @leftClick=\"$onClickLeft\"\n\t\t/>\n\t\t<view class=\"bg-white p-2\">\n\t\t\t<rich-text :nodes=\"content\"></rich-text>\n\t\t</view>\n\t</layout>\n</template>\n\n<script setup>\nimport {\r\n  ref\r\n} from 'vue'\nimport { onLoad,onShow} from '@dcloudio/uni-app'\nimport {\n  mineServiceContent\r\n} from '@/api/user'\n\n\nconst title = ref('内容')\nconst content = ref('')\n\nonLoad((option) => {\n\tif (option.name) {\n\t\ttitle.value = option.name\n\t}\n\tif (option.id) {\n\t\tgetContent(option.id);\n\t}\n})\t\nconst getContent = async(id) => {\n\tlet data = await mineServiceContent({id:id});\n\tif (data) {\n\t\tcontent.value = data.content;\n\t}\n}\n\n\n</script>\n\n<style lang=\"scss\" scoped>\n\t\n</style>\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/pages/components/pages/mine/userinfo.vue",
    "content": "<template>\n\t<uv-navbar\n\t  :fixed=\"false\"\n\t  :title=\"title\"\n\t  left-arrow\n\t  @leftClick=\"$onClickLeft\"\n\t/>\n\t<view class=\"container d-flex flex-column\">\n\t\t<view class=\"flex-fill form\">\n\t\t\t<list-cell :hover=\"false\">\n\t\t\t\t<view class=\"form-input w-100 d-flex align-items-center\">\n\t\t\t\t\t<view class=\"label\">头像</view>\n\t\t\t\t\t<view class=\"input flex-fill\">\n\t\t\t\t\t\t<view class=\"form-input w-100 d-flex align-items-center\"  style=\"position: relative;\">\n\t\t\t\t\t\t\t<view class=\"flex user-box align-items-center\">\n\t\t\t\t\t\t\t\t<view class=\"mr-1\">\n\t\t\t\t\t\t\t\t\t<uv-avatar :src=\"member.avatar\" size=\"60\"></uv-avatar>\n\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t<view v-if=\"!member.avatar\" class=\"flex-1\" style=\"right:0;height: 60rpx;\">\n\t\t\t\t\t\t\t\t\t<button style=\"background-color: #09b4f1;color:#fff;\" size='mini' open-type=\"chooseAvatar\" @chooseavatar=\"chooseavatar\" type=\"success\">更改头像</button>\n\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</view>\n\t\t\t\t</view>\n\t\t\t\t\n\t\t\t</list-cell>\n\t\t\t<list-cell :hover=\"false\">\n\t\t\t\t<view class=\"form-input w-100 d-flex align-items-center\">\n\t\t\t\t\t<view class=\"label\">昵称</view>\n\t\t\t\t\t<view class=\"input flex-fill\">\n\t\t\t\t\t\t<input type=\"nickname\" placeholder=\"请填写昵称\" placeholder-class=\"text-color-assist font-size-base\" \n\t\t\t\t\t\tv-model=\"member.nickname\">\n\t\t\t\t\t</view>\n\t\t\t\t</view>\n\t\t\t</list-cell>\n\t\t\t<list-cell :hover=\"false\">\n\t\t\t\t<view class=\"form-input w-100 d-flex align-items-center\" style=\"position: relative;\">\n\t\t\t\t\t<view class=\"label\">手机号码</view>\n\t\t\t\t\t<view class=\"input flex-fill\">\n\t\t\t\t\t\t<input type=\"text\" v-model=\"member.mobile\" disabled>\n\t\t\t\t\t</view>\n\t\t\t\t</view>\n\t\t\t</list-cell>\n\t\t\t<list-cell :hover=\"false\">\n\t\t\t\t<view class=\"form-input w-100 d-flex align-items-center\">\n\t\t\t\t\t<view class=\"label\">性别</view>\n\t\t\t\t\t<view class=\"input flex-fill\">\n\t\t\t\t\t\t<view class=\"radio-group\">\n\t\t\t\t\t\t\t<view class=\"radio\" :class=\"{'checked': member.gender == '0'}\" style=\"margin-right: 10rpx;\" @tap=\"member.gender=0\">先生</view>\n\t\t\t\t\t\t\t<view class=\"radio\" :class=\"{'checked': member.gender == '1'}\" @tap=\"member.gender=1\">女士</view>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</view>\n\t\t\t\t</view>\n\t\t\t</list-cell>\n\t\t\t<list-cell :hover=\"false\" :arrow=\"!member.birthday\">\n\t\t\t\t<view class=\"form-input w-100 d-flex align-items-center\">\n\t\t\t\t\t<view class=\"label\">生日</view>\n\t\t\t\t\t<view class=\"input flex-fill\">\n\t\t\t\t\t\t<picker mode=\"date\" :value=\"member.birthday\" :start=\"startDate\" :end=\"endDate\" @change=\"handleDateChange\">\n\t\t\t\t\t\t\t{{member.birthday ? member.birthday : '无'}}\n\t\t\t\t\t\t</picker>\n\t\t\t\t\t</view>\n\t\t\t\t</view>\n\t\t\t</list-cell>\n\t\t\t<list-cell :hover=\"false\" last>\n\t\t\t\t<view class=\"form-input w-100 d-flex align-items-center\">\n\t\t\t\t\t<view class=\"label\">加入时间</view>\n\t\t\t\t\t<view class=\"input flex-fill\">\n\t\t\t\t\t\t<input type=\"text\" v-model=\"member.createTime\" disabled>\n\t\t\t\t\t</view>\n\t\t\t\t</view>\n\t\t\t</list-cell>\n\t\t</view>\n\t\t<view class=\"btn-box d-flex align-items-center  just-content-center\">\n\t\t\t<button type=\"primary\" class=\"save-btn\" @tap=\"save\">保存</button>\n\t\t</view>\n\n\t</view>\n</template>\n\n<script setup>\nimport {\n  ref,\n  computed\n} from 'vue'\nimport { useMainStore } from '@/store/store'\nimport { storeToRefs } from 'pinia'\nimport { onLoad,onShow} from '@dcloudio/uni-app'\nimport { formatDateTime } from '@/utils/util'\nimport {\n  userEdit\n} from '@/api/user'\nimport { VUE_APP_UPLOAD_URL } from '@/config';\nconst main = useMainStore()\nconst { openid, lang } = storeToRefs(main)\n\nconst title = ref('用户设置')\n//const currentDate = ref('')\nconst date = ref('')\nconst member = ref({})\n\nonLoad(() => {\n\tmember.value = main.member;\n\t//this.$util\n\tmember.value.createTime = formatDateTime(member.value.createTime);\n})\t\nonShow(() => {\n\tdate.value = getDate({format: true})\n})\n\nconst startDate = computed(() => { \n\treturn getDate('start');\n})\nconst endDate = computed(() => { \n\treturn getDate('end');\n})\n\nconst chooseavatar = (e) => {\n\tconsole.log('detal:',e.detail);\n\tuni.uploadFile({\n\t\turl: VUE_APP_UPLOAD_URL, \n\t\tfilePath: e.detail.avatarUrl,\n\t\tname: 'file',\n\t\theader: {\n\t\t\tAuthorization: 'Bearer ' + member.value.accessToken,\n\t\t\tlang: lang.value,\n\t\t\t'content-type': 'application/x-www-form-urlencoded;charset=UTF-8'\n\t\t},\n\t\tsuccess(uploadFileResult){\n\t\t\tconsole.log('uploadFileResult:',uploadFileResult)\n\t\t\tif (uploadFileResult) {\n\t\t\t\tconst upload = JSON.parse(uploadFileResult.data);\n\t\t\t\tconsole.log('upload:',upload.data)\n\t\t\t\tmember.value.avatar = upload.data;\n\t\t\t}\n\t\t}, \n\t\tfail(e){\n\t\t\tconsole.log('网络链接错误');\n\t\t\tconsole.log(e)\n\t\t}\n\t});\n}\nconst getDate = (type) => {\n\tconst date = new Date();\n\tlet year = date.getFullYear();\n\tlet month = date.getMonth() + 1;\n\tlet day = date.getDate();\n\n\tif (type === 'start') {\n\t\tyear = year - 60;\n\t} else if (type === 'end') {\n\t\tyear = year + 2;\n\t}\n\tmonth = month > 9 ? month : '0' + month;;\n\tday = day > 9 ? day : '0' + day;\n\treturn `${year}-${month}-${day}`;\n}\nconst handleDateChange = (e) => {\n\tmember.value.birthday = e.target.value\n}\nconst save = async() => {\n\tlet data = await userEdit({\n\t\tnickname: member.value.nickname,\n\t\tmobile: member.value.mobile,\n\t\tgender: member.value.gender,\n\t\tbirthday: member.value.birthday,\n\t\tavatar: member.value.avatar\n\t});\n\tif (data) {\n\t\tconst member2 = Object.assign(main.member, member.value)\n\t\tmain.SET_MEMBER(member2)\t\n\t}\n\t\n}\n\t\n</script>\n\n<style lang=\"scss\" scoped>\npage {\n\theight: 100%;\n}\n\n.container {\n\tpadding: 20rpx 30rpx;\n}\n\n.form {\n\tborder-radius: 8rpx;\n}\n\n.form-input {\n\t.label {\n\t\twidth: 160rpx;\n\t\tfont-size: $font-size-base;\n\t\tcolor: $text-color-base;\n\t}\n\t\n\t.input {\n\t}\n\t\n\t.radio-group {\n\t\tdisplay: flex;\n\t\tjustify-content: flex-start;\n\t\t\n\t\t.radio {\n\t\t\tpadding: 10rpx 30rpx;\n\t\t\tborder-radius: 6rpx;\n\t\t\tborder: 2rpx solid $text-color-assist;\n\t\t\tcolor: $text-color-assist;\n\t\t\tfont-size: $font-size-base;\n\t\t\t\n\t\t\t&.checked {\n\t\t\t\tbackground-color: $color-primary;\n\t\t\t\tcolor: $text-color-white;\n\t\t\t\tborder: 2rpx solid $color-primary;\n\t\t\t}\n\t\t}\n\t}\n}\n\n.btn-box {\n\theight: calc((100vh - 40rpx) / 2);\n}\n.save-btn {\n\twidth: 90%;\n\tborder-radius: 50rem !important;\n\tfont-size: $font-size-lg;\n}\n\n</style>\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/pages/components/pages/orders/detail.vue",
    "content": "<template>\n\t<uv-navbar\n\t  :fixed=\"false\"\n\t  :title=\"title\"\n\t  left-arrow\n\t  @leftClick=\"$onClickLeft\"\n\t/>\n\t<view class=\"container\" style=\"padding:20rpx;\">\n\t\t<view style=\"padding-bottom: 100rpx;\">\n\t\t\t<view class=\"bg-white\">\n\t\t\t\t<view class=\"section\">\n\t\t\t\t\t<!-- store info begin -->\n\t\t\t\t\t<list-cell :hover=\"false\">\n\t\t\t\t\t\t<view class=\"w-100 d-flex align-items-center\">\n\t\t\t\t\t\t\t<view class=\"d-flex flex-column w-60\">\n\t\t\t\t\t\t\t\t<view class=\"w-100 font-size-lg text-color-base text-truncate\">{{ order.shop.name }}</view>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t<view class=\"d-flex justify-content-end align-items-center w-40\">\n\t\t\t\t\t\t\t\t<view class=\"iconfont-yshop icon-mobile\"  @click=\"makePhoneCall(order.shop)\" style=\"font-size: 45rpx;margin-right: 40rpx;\"></view>\n\t\t\t\t\t\t\t\t<view class=\"iconfont-yshop icon-location\"  @click=\"openLocation(order.shop)\" style=\"font-size: 45rpx;\"></view>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</list-cell>\n\t\t\t\t\t<!-- store info end -->\n\t\t\t\t\t<list-cell :hover=\"false\" padding=\"50rpx 30rpx\">\n\t\t\t\t\t\t<view class=\"w-100 d-flex flex-column\">\n\t\t\t\t\t\t\t<view class=\"d-flex align-items-center just-content-center\">\n\t\t\t\t\t\t\t\t<view class=\"sort-num\">{{ order.paid == 1 ? order.numberId : '--' }}</view>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t<!-- steps begin -->\n\t\t\t\t\t\t\t<view class=\"d-flex just-content-center\">\n\t\t\t\t\t\t\t\t<view class=\"steps d-flex flex-column w-80\">\n\t\t\t\t\t\t\t\t\t<view class=\"steps__img-column\">\n\t\t\t\t\t\t\t\t\t\t<view class=\"steps__img-column-item\">\n\t\t\t\t\t\t\t\t\t\t\t<view class=\"iconfont-yshop icon-lamp\"></view>\n\t\t\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t\t\t<view class=\"steps__img-column-item\">\n\t\t\t\t\t\t\t\t\t\t\t<view class=\"iconfont-yshop icon-daojishi\" v-if=\"{active: order.paid == 1 && order.status == 0}\"></view>\n\t\t\t\t\t\t\t\t\t\t\t<view class=\"iconfont-yshop icon-daojishi unactive\" v-else></view>\n\t\t\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t\t\t<view class=\"steps__img-column-item\" v-if=\"order.orderType == 'takeout'\">\n\t\t\t\t\t\t\t\t\t\t\t<view class=\"iconfont-yshop icon-takeout\" v-if=\"order.status == 1\"></view>\n\t\t\t\t\t\t\t\t\t\t\t<view class=\"iconfont-yshop icon-takeout unactive\" v-else></view>\n\t\t\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t\t\t<view class=\"steps__img-column-item\" >\n\t\t\t\t\t\t\t\t\t\t\t<view class=\"iconfont-yshop icon-doorbell\" v-if=\"order.status >= 2\"></view>\n\t\t\t\t\t\t\t\t\t\t\t<view class=\"iconfont-yshop icon-doorbell unactive\" v-else></view>\n\t\t\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t\t<view class=\"steps__text-column\">\n\t\t\t\t\t\t\t\t\t\t<view class=\"steps__text-column-item active\">\n\t\t\t\t\t\t\t\t\t\t\t<view class=\"steps__column-item-line bg-transparent\"></view>\n\t\t\t\t\t\t\t\t\t\t\t<view class=\"steps__text-column-item-text\">已下单</view>\n\t\t\t\t\t\t\t\t\t\t\t<view class=\"steps__column-item-line\"></view>\n\t\t\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t\t\t<view class=\"steps__text-column-item activ\"  :class=\"{active: order.paid == 1}\">\n\t\t\t\t\t\t\t\t\t\t\t<view class=\"steps__column-item-line\"></view>\n\t\t\t\t\t\t\t\t\t\t\t<view class=\"steps__text-column-item-text\">制作中</view>\n\t\t\t\t\t\t\t\t\t\t\t<view class=\"steps__column-item-line\"></view>\n\t\t\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t\t\t<view class=\"steps__text-column-item\" :class=\"{active: order.status == 1}\" v-if=\"order.orderType == 'takeout'\">\n\t\t\t\t\t\t\t\t\t\t\t<view class=\"steps__column-item-line\"></view>\n\t\t\t\t\t\t\t\t\t\t\t<view class=\"steps__text-column-item-text\">配送中</view>\n\t\t\t\t\t\t\t\t\t\t\t<view class=\"steps__column-item-line bg-transparent\"></view>\n\t\t\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t\t\t<view class=\"steps__text-column-item\" :class=\"{active: order.status >= 2}\">\n\t\t\t\t\t\t\t\t\t\t\t<view class=\"steps__column-item-line\"></view>\n\t\t\t\t\t\t\t\t\t\t\t<view class=\"steps__text-column-item-text\">\n\t\t\t\t\t\t\t\t\t\t\t\t{{ order.orderType == 'takeout' ? '已送达' : '请取餐' }}\n\t\t\t\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t\t\t\t<view class=\"steps__column-item-line bg-transparent\"></view>\n\t\t\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t<!-- steps end -->\n\t\t\t\t\t\t\t<view v-if=\"order.status==0 && order.paid > 0\" class=\"d-flex just-content-center align-items-center font-size-base text-color-assist mb-40\">\n\t\t\t\t\t\t\t\t您前面还有 <text class=\"text-color-primary mr-10 ml-10\">{{order.preNum}}</text> 单待制作\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t<!-- goods begin -->\n\t\t\t\t\t\t\t<view class=\"w-100 d-flex flex-column position-relative mt-30\" style=\"margin-bottom: -40rpx;\">\n\t\t\t\t\t\t\t\t<view class=\"w-100 d-flex align-items-center mb-40\" v-for=\"(good, index) in order.products\" :key=\"index\">\n\t\t\t\t\t\t\t\t\t<view class=\"d-flex flex-column w-60 overflow-hidden\">\n\t\t\t\t\t\t\t\t\t\t<view class=\"font-size-lg text-color-base mb-10 text-truncate\">{{ good.title }}</view>\n\t\t\t\t\t\t\t\t\t\t<view class=\"font-size-sm text-color-assist text-truncate\">{{ good.spec }}</view>\n\t\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t\t<view class=\"d-flex w-40 align-items-center justify-content-between pl-30\">\n\t\t\t\t\t\t\t\t\t\t<view class=\"font-size-base text-color-base\">x{{ good.number }}</view>\n\t\t\t\t\t\t\t\t\t\t<view class=\"font-size-base text-color-base font-weight-bold\">￥{{ good.price }}</view>\n\t\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t<!-- goods end -->\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</list-cell>\n\t\t\t\t</view>\n\t\t\t\t<view class=\"section\">\n\t\t\t\t\t<!-- goods begin -->\n\t\t\t\t\t<list-cell :hover=\"false\" padding=\"30rpx 30rpx\">\n\t\t\t\t\t\t<view class=\"w-100 d-flex flex-column position-relative\">\n\t\t\t\t\t\t\t<view class=\"w-100 d-flex align-items-center mb-40\" v-for=\"(good, index) in order.cartInfo\" :key=\"index\">\n\t\t\t\t\t\t\t\t<image :src=\"good.image\" mode=\"aspectFill\" class=\"image\"></image>\n\t\t\t\t\t\t\t\t<view class=\"d-flex flex-column w-60 overflow-hidden\">\n\t\t\t\t\t\t\t\t\t<view class=\"font-size-lg text-color-base mb-10 text-truncate\">{{ good.title }}</view>\n\t\t\t\t\t\t\t\t\t<view class=\"font-size-sm text-color-assist text-truncate\">{{ good.spec }}</view>\n\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t<view class=\"d-flex w-40 align-items-center justify-content-between pl-30\">\n\t\t\t\t\t\t\t\t\t<view class=\"font-size-base text-color-base\">x{{ good.number }}</view>\n\t\t\t\t\t\t\t\t\t<view class=\"font-size-base text-color-base font-weight-bold\">￥{{ good.price }}</view>\n\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</list-cell>\n\t\t\t\t\t<!-- goods end -->\n\t\t\t\t\t<!-- payment and amount begin -->\n\t\t\t\t\t<list-cell :hover=\"false\" padding=\"50rpx 30rpx\">\n\t\t\t\t\t\t<view class=\"w-100 d-flex flex-column\">\n\t\t\t\t\t\t\t<view class=\"pay-cell\">\n\t\t\t\t\t\t\t\t<view>支付方式</view>\n\t\t\t\t\t\t\t\t<view class=\"font-weight-bold\">{{ order.statusDto.payType }}</view>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t<view class=\"pay-cell\">\n\t\t\t\t\t\t\t\t<view>订单金额</view>\n\t\t\t\t\t\t\t\t<view class=\"font-weight-bold\">￥{{ order.totalPrice }}</view>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t<view class=\"pay-cell\" v-if=\"order.orderType == 'takeout'\">\n\t\t\t\t\t\t\t\t<view>配送费</view>\n\t\t\t\t\t\t\t\t<view class=\"font-weight-bold\">￥{{ order.payPostage }}</view>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t<view class=\"pay-cell\">\n\t\t\t\t\t\t\t\t<view>优惠金额</view>\n\t\t\t\t\t\t\t\t<view class=\"font-weight-bold\">￥{{ order.couponPrice }}</view>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t<view class=\"pay-cell\">\n\t\t\t\t\t\t\t\t<view>实付金额</view>\n\t\t\t\t\t\t\t\t<view class=\"font-weight-bold\">￥{{ order.payPrice }}</view>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</list-cell>\n\t\t\t\t\t<!-- payment and amount end -->\n\t\t\t\t</view>\n\t\t\t\t<view class=\"section\">\n\t\t\t\t\t<!-- order info begin -->\n\t\t\t\t\t<list-cell :hover=\"false\" padding=\"50rpx 30rpx\">\n\t\t\t\t\t\t<view class=\"w-100 d-flex flex-column\">\n\t\t\t\t\t\t\t<view class=\"pay-cell\">\n\t\t\t\t\t\t\t\t<view>下单时间</view>\n\t\t\t\t\t\t\t\t<view class=\"font-weight-bold\">{{ formatDateTime(order.createTime )}}</view>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t<view class=\"pay-cell\">\n\t\t\t\t\t\t\t\t<view>下单门店</view>\n\t\t\t\t\t\t\t\t<view class=\"font-weight-bold\">{{ order.shop.name }}</view>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t<view class=\"pay-cell\">\n\t\t\t\t\t\t\t\t<view>订单号</view>\n\t\t\t\t\t\t\t\t<view class=\"font-weight-bold\">{{ order.id }}</view>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</list-cell>\n\t\t\t\t\t<!-- order info end --> \n\t\t\t\t</view>\n\t\t\t\t<!-- order other info begin -->\n\t\t\t\t<list-cell :hover=\"false\" padding=\"50rpx 30rpx 20rpx\" last>\n\t\t\t\t\t<view class=\"w-100 d-flex flex-column\">\n\t\t\t\t\t\t<view class=\"pay-cell\">\n\t\t\t\t\t\t\t<view>享用方式</view>\n\t\t\t\t\t\t\t<view class=\"font-weight-bold\">{{order.orderType == 'takein' ? '自取' : '外卖'}}</view>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t\t<view class=\"pay-cell\">\n\t\t\t\t\t\t\t<view>取餐时间</view>\n\t\t\t\t\t\t\t<view class=\"font-weight-bold\">{{order.getTime ? formatDateTime(order.getTime) : '立即取餐'}}</view>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t\t<view class=\"pay-cell\">\n\t\t\t\t\t\t\t<view>制作完成时间</view>\n\t\t\t\t\t\t\t<view class=\"font-weight-bold\">{{ order.deliveryTime ? formatDateTime(order.deliveryTime) : '无' }}</view>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t\t<view class=\"pay-cell\">\n\t\t\t\t\t\t\t<view>备注</view>\n\t\t\t\t\t\t\t<view class=\"font-weight-bold\">{{ order.remark ? order.remark : '无' }}</view>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</view>\n\t\t\t\t</list-cell>\n\t\t\t\t<!-- order other info end -->\n\t\t\t</view>\n\t\t\t<view class=\"fixed-bottom flex justify-end bg-white p-2\" v-if=\"order.paid > 0 && order.refundStatus == 0\">\n\t\t\t\t<view class=\"mr-1\"><uv-button type=\"success\" v-if=\"order.status < 2\" :plain=\"true\" size=\"small\" text=\"确认收到餐\" @click=\"receive(order)\"></uv-button></view>\n\t\t\t\t<view><uv-button type=\"warning\" :plain=\"true\" size=\"small\" text=\"申请退款\" @click=\"refund(order)\"></uv-button></view>\n\t\t\t</view>\n\t\t</view>\n\t</view>\n</template>\n\n<script setup>\nimport {\n  ref\n} from 'vue'\nimport { onLoad} from '@dcloudio/uni-app'\nimport { formatDateTime } from '@/utils/util'\nimport {\n  orderDetail,\n  orderReceive,\n} from '@/api/order'\nconst title = ref('订单详情')\nconst order = ref({\n\tshop:{name:''},\n\tstatusDto:{payType:''}\n})\nconst numForMading = ref(5)\n\nonLoad((option) => {\n\tdetail(option.id);\n})\n\nconst detail =  async(id) => {\n\tlet data = await orderDetail(id);\n\tif (data) {\n\t\torder.value = data;\n\t}\n}\nconst openLocation = () => {\n\tlet shop = order.value.shop;\n\tuni.openLocation({\n\t\taddress: shop.addressMap + shop.address + \" \" + shop.name,\n\t\tlatitude: parseFloat(shop.lat),\n\t\tlongitude: parseFloat(shop.lng),\n\t\tfail(res) {\n\t\t\tconsole.log(res);\n\t\t}\n\t});\n}\nconst makePhoneCall = () =>{\n\tlet shop = order.value.shop;\n\tuni.makePhoneCall({\n\t\tphoneNumber: shop.mobile,\n\t\tfail(res) {\n\t\t\tconsole.log(res)\n\t\t}\n\t})\n}\n\n// 确认收到货\nconst receive  = async(order) => {\n\tlet data = await orderReceive({uni:order.orderId});\n\tif (data) {\n\t\tawait getOrders(true)\n\t}\n}\n//提交退款\nconst refund = (order) => {\n\tuni.navigateTo({\n\t\turl: '/pages/components/pages/orders/refund?orderId=' + order.orderId + '&payPrice=' + order.payPrice + '&totalPrice=' + order.totalPrice\n\t})\n}\n\n</script>\n\n<style lang=\"scss\" scoped>\n\t.image {\n\t\twidth: 120rpx;\n\t\theight: 120rpx;\n\t\tmargin-right: 30rpx;\n\t\tborder-radius: 8rpx;\n\t}\n\n\n.pay-cell {\n\twidth: 100%;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tfont-size: $font-size-base;\n\tcolor: $text-color-base;\n\tmargin-bottom: 40rpx;\n\n\t&:nth-last-child(1) {\n\t\tmargin-bottom: 0;\n\t}\n}\n\n\n\n/* #ifdef H5 */\n\tpage {\n\t\tmin-height: 100%;\n\t\tbackground-color: $bg-color;\n\t}\n\t/* #endif */\n\t.order-box {\n\t\tpadding: 20rpx;\n\t\t/* #ifdef H5 */\n\t\tmargin-bottom: 100rpx;\n\t\t/* #endif */\n\t}\n\t\n\t.drinks-img {\n\t\twidth: 260rpx;\n\t\theight: 260rpx;\n\t}\n\t\n\t.tips {\n\t\tmargin: 60rpx 0 80rpx;\n\t\tline-height: 48rpx;\n\t}\n\n\t\n\t@mixin arch {\n\t\tcontent: \"\";\n\t\tposition: absolute;\n\t\tbackground-color: $bg-color;\n\t\twidth: 30rpx;\n\t\theight: 30rpx;\n\t\tbottom: -15rpx;\n\t\tz-index: 10;\n\t\tborder-radius: 100%;\n\t}\n\t\n\t.section {\n\t\tposition: relative;\n\t\t\n\t\t&::before {\n\t\t\t@include arch;\n\t\t\tleft: -15rpx;\n\t\t}\n\t\t\n\t\t&::after {\n\t\t\t@include arch;\n\t\t\tright: -15rpx;\n\t\t}\n\t}\n\t\n\t.pay-cell {\n\t\twidth: 100%;\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tjustify-content: space-between;\n\t\tfont-size: $font-size-base;\n\t\tcolor: $text-color-base;\n\t\tmargin-bottom: 40rpx;\n\n\t\t&:nth-last-child(1) {\n\t\t\tmargin-bottom: 0;\n\t\t}\n\t}\n\t\n\t.sort-num {\n\t\tfont-size: 64rpx;\n\t\tfont-weight: bold;\n\t\tcolor: $text-color-base;\n\t\tline-height: 2;\n\t}\n\t\n\t.steps__img-column {\n\t\tdisplay: flex;\n\t\tmargin: 30rpx 0;\n\t\t\n\t\t.steps__img-column-item {\n\t\t\tflex: 1;\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\t\t\t\n\t\t\timage {\n\t\t\t\twidth: 80rpx;\n\t\t\t\theight: 80rpx;\n\t\t\t}\n\t\t\t.unactive {\n\t\t\t\tcolor: #919293;\n\t\t\t}\n\t\t}\n\t}\n\t\n\t.steps__text-column {\n\t\tdisplay: flex;\n\t\tmargin-bottom: 40rpx;\n\t\t\n\t\t.steps__text-column-item {\n\t\t\tflex: 1;\n\t\t\tdisplay: inline-flex;\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\t\t\tfont-size: $font-size-base;\n\t\t\tcolor: $text-color-assist;\n\t\t\t\n\t\t\t&.active {\n\t\t\t\tcolor: $text-color-base;\n\t\t\t\tfont-weight: bold;\n\t\t\t\t\n\t\t\t\t.steps__column-item-line {\n\t\t\t\t\tbackground-color: $text-color-base;\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\t.steps__column-item-line{\n\t\t\t\tflex: 1;\n\t\t\t\theight: 2rpx;\n\t\t\t\tbackground-color: #919293;\n\t\t\t\ttransform: scaleY(0.5);\n\t\t\t}\n\t\t\t\n\t\t\t.steps__text-column-item-text {\n\t\t\t\tmargin: 0 8px;\n\t\t\t}\n\t\t}\n\t}\n\t.icon-lamp, .icon-daojishi, .icon-takeout, .icon-doorbell{\n\t\tfont-size: 60rpx;\n\t}\n\t.iconfont-yshop {\n\t\tcolor: #09b4f1;\n\t}\n</style>\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/pages/components/pages/orders/orders.vue",
    "content": "<template>\n\t<uv-navbar\n\t  :fixed=\"false\"\n\t  :title=\"title\"\n\t  left-arrow\n\t  @leftClick=\"$onClickLeft\"\n\t/>\n\t<view class=\"container\">\n\t\t<view class=\"bg-white\">\n\t\t\t<uv-tabs :list=\"tabList\" :current=\"current\" @change=\"change\" keyName=\"name\" :scrollable=\"false\"></uv-tabs>\n\t\t</view>\n\t\t<view class=\"orders-list d-flex flex-column w-100\" style=\"padding: 20rpx; padding-bottom: 0;\">\n\t\t\t<view class=\"order-item\" v-for=\"(item, index) in orders\" :key=\"index\" style=\"margin-bottom: 30rpx;\" @tap=\"detail(item.orderId)\">\n\t\t\t\t<list-cell :hover=\"false\">\n\t\t\t\t\t<view class=\"w-100 d-flex align-items-center\">\n\t\t\t\t\t\t<view class=\"flex-fill d-flex flex-column\">\n\t\t\t\t\t\t\t<view class=\"font-size-lg text-color-base\" style=\"margin-bottom: 20rpx;\">\n\t\t\t\t\t\t\t\t{{ item.shop.name }}\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t<view class=\"font-size-sm text-color-assist\">取餐号：{{ item.numberId }}</view>\n\t\t\t\t\t\t\t<view class=\"font-size-sm text-color-assist\">订单编号：{{ item.orderId }}</view>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t\t<view class=\"font-size-lg text-color-primary\">\n\t\t\t\t\t\t\t{{ item.statusDto.title }}\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</view>\n\t\t\t\t</list-cell>\n\t\t\t\t<list-cell :hover=\"false\" last>\n\t\t\t\t\t<view class=\"w-100 d-flex flex-column\">\n\t\t\t\t\t\t<view class=\"w-100 text-truncate font-size-lg text-color-base\" style=\"margin-bottom: 20rpx;\">\n\t\t\t\t\t\t\t<view class=\"flex mb-2\" v-for=\"(good,index) in item.cartInfo\" :key=\"index\">  \n\t\t\t\t\t\t\t\t<image :src=\"good.image\" mode=\"aspectFill\" class=\"image\"></image>\n\t\t\t\t\t\t\t\t<view class=\"flex flex-column\">\n\t\t\t\t\t\t\t\t\t<view class=\"font-size-medium mt-1 text-color-base\">{{ good.title }}</view>\n\t\t\t\t\t\t\t\t\t<view class=\"font-size-sm mt-1\">{{ good.spec }}</view>\n\t\t\t\t\t\t\t\t\t<view class=\"font-size-sm mt-2\">×{{ good.number }}  ¥{{ good.price }}</view>\n\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t\t<view class=\"d-flex justify-content-between align-items-center\" style=\"margin-bottom: 30rpx;\">\n\t\t\t\t\t\t\t<view class=\"font-size-sm text-color-assist\">\n\t\t\t\t\t\t\t\t{{formatDateTime(item.createTime) }}\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t<view class=\"d-flex font-size-sm text-color-base align-items-center\">\n\t\t\t\t\t\t\t\t<view style=\"margin-right: 10rpx;\">共{{ goodsNum(item.cartInfo) }}件商品，实付</view>\n\t\t\t\t\t\t\t\t<view class=\"font-size-lg\">￥{{ item.payPrice }}</view>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t\t<view class=\"d-flex align-items-center justify-content-end\">\n\t\t\t\t\t\t\t<view>\n\t\t\t\t\t\t\t\t<button v-if=\"item.paid > 0 && item.status < 2 && item.refundStatus == 0\" class=\"left-margin\"  plain size=\"mini\" @tap.stop=\"receive(item)\">确认收到餐</button>\n\t\t\t\t\t\t\t\t<button class=\"left-margin\"  plain size=\"mini\" @tap=\"detail(item.orderId)\">订单详情</button>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</view>\n\t\t\t\t</list-cell>\n\t\t\t</view>\n\t\t</view>\n\t\t<uv-empty v-if=\"orders.length == 0\" mode=\"order\"></uv-empty>\n\t</view>\n</template>\n\n\n<script setup>\nimport {\r\n  ref,\n  computed\r\n} from 'vue'\nimport { useMainStore } from '@/store/store'\nimport { storeToRefs } from 'pinia'\nimport { onLoad,onPullDownRefresh,onReachBottom} from '@dcloudio/uni-app'\nimport { formatDateTime,kmUnit } from '@/utils/util'\nimport {\n  orderGetOrders,\n  orderReceive\r\n} from '@/api/order'\nconst main = useMainStore()\nconst { isLogin } = storeToRefs(main)\nconst title = ref('我的订单')\n\nconst page = ref(1)\nconst pageSize = ref(10)\nconst orders = ref([])\nconst tabList = ref([{\n\t\t\ttype: -1,\n\t\t\tname: '全部',\n\t\t}, {\n\t\t\ttype: 0,\n\t\t\tname: '待支付',\n\t\t}, {\n\t\t\ttype: 1,\n\t\t\tname: '进行中'\n\t\t}, {\n\t\t\ttype: 4,\n\t\t\tname: '已完成'\n\t\t}, {\n\t\t\ttype: -3,\n\t\t\tname: '退款单'\n\t\t}]\n)\nconst current = ref(0)\nconst type = ref(-1)\n\nconst goodsNum = computed(() => { //计算单个饮品添加到购物车的数量\n\treturn (goods) => {\n\t\tlet num = 0;\n\t\tgoods.forEach(good => num += parseInt(good.number))\n\t\treturn num;\n\t}\n})\nonLoad(() => {\n\tif(!isLogin.value) {\n\t\tuni.navigateTo({url: '/pages/components/pages/login/login'})\n\t}\n\tgetOrders(false)\n})\nonPullDownRefresh(() => {\n\t getOrders(false)\n})\nonReachBottom(() => {\n\tgetOrders(false)\n})\n\n// tab栏切换\nconst change = (e) => {\n\ttype.value = e.type\n\tgetOrders(true)\n}\n\nconst getOrders = async(isRefresh = false) => {\n\tuni.showLoading({\n\t\ttitle: '加载中'\n\t})\n\tif(isRefresh) {\n\t\torders.value = []\n\t\tpage.value = 1\n\t}\n\tlet ordersData = await orderGetOrders({page:page.value, limit:pageSize.value,type:type.value});\n\n\tif(ordersData) {\n\t\torders.value = orders.value.concat(ordersData)\n\t\tpage.value += 1\n\t}\n\tuni.stopPullDownRefresh();\n\tuni.hideLoading()\n}\nconst detail = (id) => {\n\tuni.navigateTo({\n\t\turl: '/pages/components/pages/orders/detail?id=' + id\n\t})\n}\n// 确认收到货\nconst receive  = async(order) => {\n\tlet data = await orderReceive({uni:order.orderId});\n\tif (data) {\n\t\tawait getOrders(true)\n\t}\n}\n\t\n\n</script>\n\n<style lang=\"scss\" scoped>\n\t.left-margin {\n\t\tmargin-left: 10rpx;\n\t}\n\t.image {\n\t\twidth: 160rpx;\n\t\theight: 160rpx;\n\t\tmargin-right: 30rpx;\n\t\tborder-radius: 8rpx;\n\t}\n</style>\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/pages/components/pages/orders/refund.vue",
    "content": "<template>\n\t<uv-navbar\n\t  :fixed=\"false\"\n\t  :title=\"title\"\n\t  left-arrow\n\t  @leftClick=\"$onClickLeft\"\n\t/>\n\t<view class=\"p-2\">\n\t\t<uv-list>\n\t\t\t<uv-list-item title=\"订单金额\"  :rightText=\"'¥'+totalPrice\"></uv-list-item>\n\t\t\t<uv-list-item title=\"退款金额\"  :rightText=\"'¥'+payPrice\"></uv-list-item>\n\t\t\t<uv-list-item title=\"退款类型\"  rightText=\"整个订单\"></uv-list-item>\n\t\t\t<uv-list-item title=\"退款原因\"  rightText=\"协商一致\"></uv-list-item>\n\t\t\t<uv-list-item title=\"退款留言\" >\n\t\t\t\t<template #footer><uv-input placeholder=\"请输入内容\" border=\"none\" v-model=\"refundReasonWapExplain\"></uv-input></template>\n\t\t\t</uv-list-item>\n\t\t</uv-list>\n\t\t<view class=\"mt-2\">\n\t\t\t<uv-button shape=\"circle\" type=\"warning\" :plain=\"true\" size=\"small\" text=\"申请退款\" @click=\"refund()\"></uv-button>\n\t\t</view>\n\t\t<uv-toast ref=\"toast\"></uv-toast>\n\t</view>\n</template>\n\n<script setup>\nimport {\r\n  ref\r\n} from 'vue'\nimport { onLoad} from '@dcloudio/uni-app'\nimport {\n  orderRefund\r\n} from '@/api/order'\nconst title = ref('申请退款')\nconst orderId = ref('')\nconst payPrice = ref('')\nconst totalPrice = ref('')\nconst refundReasonWapExplain = ref('')\nconst toast = ref()\nonLoad((option) => {\n\tconsole.log('option:',option)\n\torderId.value = option.orderId\n\tpayPrice.value = option.payPrice\n\ttotalPrice.value = option.totalPrice\n})\n\nconst refund  = async() => {\n\tlet data = await orderRefund({refundReasonWapExplain:refundReasonWapExplain.value,uni:orderId.value,text:'协商一致'});\n\tif (data) {\n\t\tuni.showToast({\n\t\t\ttitle: '提交成功',\n\t\t\ticon: 'success'\n\t\t})\n\t}\n}\n\n</script>\n\n<style>\n</style>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/pages/components/pages/packages/index.vue",
    "content": "<template>\n\t<uv-navbar\n\t  :fixed=\"false\"\n\t  :title=\"title\"\n\t  left-arrow\n\t  @leftClick=\"$onClickLeft\"\n\t/>\n\t<view class=\"container position-relative w-100 h-100 overflow-hidden\">\n\t\t<uv-empty v-if=\"coupons.length == 0\" mode=\"coupon\"></uv-empty>\n\t\t\n\t\t<scroll-view scroll-y class=\"coupon-list\">\n\t\t\t<view class=\"wrapper\">\n\t\t\t\t<view class=\"coupon\" v-for=\"(item, index) in coupons\" :key=\"index\" @tap=\"openDetailModal(item, index)\">\n\t\t\t\t\t<view class=\"taobao\">\n\t\t\t\t\t\t<view class=\"ticket\" :style=\"{border: item.id == coupon_id ? '1rpx solid red':''}\">\n\t\t\t\t\t\t\t<view class=\"left\">\n\t\t\t\t\t\t\t\t<image class=\"picture\" :src=\"item.image\" mode=\"aspectFill\"></image>\n\t\t\t\t\t\t\t\t<view class=\"introduce\">\n\t\t\t\t\t\t\t\t\t<view class=\"top\">\n\t\t\t\t\t\t\t\t\t\t￥\n\t\t\t\t\t\t\t\t\t\t<text class=\"big\">{{ item.value }}</text>\n\t\t\t\t\t\t\t\t\t\t<view>满{{ item.least }}减{{ item.value }}</view>\n\t\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t\t<view class=\"type\">{{ item.title }}</view>\n\t\t\t\t\t\t\t\t\t<view class=\"date u-line-1\">{{formatDateTime(item.startTime, 'yyyy-MM-dd')}}-{{formatDateTime(item.endTime, 'yyyy-MM-dd')}}</view>\n\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t<view class=\"right\" @click.stop=\"\" v-if=\"activeTabIndex == 0\">\n\t\t\t\t\t\t\t\t<view v-if=\"item.id != coupon_id\" class=\"use immediate-use\" :round=\"true\" @tap=\"useCouponWith(item)\">立即使用</view>\n\t\t\t\t\t\t\t\t<view v-else class=\"use immediate-use\" :round=\"true\" @tap=\"cancelCoupon(item)\">取消使用</view>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</view>\n\t\t\t\t</view>\n\t\t\t</view>\n\t\t</scroll-view>\n\t\t\n\t\t<modal custom :show=\"detailModalVisible\" @cancel=\"closeDetailModal\" width=\"90%\">\n\t\t\t<view class=\"modal-content\">\n\t\t\t\t<view class=\"d-flex font-size-extra-lg text-color-base just-content-center mb-20\">{{ coupon.title }}</view>\n\t\t\t\t<view class=\"d-flex font-size-sm text-color-base mb-20\">有效期：{{formatDateTime(coupon.startTime, 'yyyy-MM-dd')}}至{{formatDateTime(coupon.endTime, 'yyyy-MM-dd')}}</view>\n\t\t\t\t<view class=\"d-flex font-size-sm text-color-base mb-20\">领取时间：{{ coupon.createtime_text }}</view>\n\t\t\t\t<view class=\"d-flex font-size-sm text-color-base mb-20\">卷价值：满{{ coupon.least }}减{{ coupon.value }}</view>\n\t\t\t\t<view class=\"d-flex font-size-sm text-color-base mb-20\">适用范围：{{ typeInfo(coupon.type) }}</view>\n\t\t\t\t<view class=\"d-flex font-size-sm text-color-base mb-20\">适用店铺：{{ coupon.shopName }}</view>\n\t\t\t\t<view class=\"d-flex align-items-center just-content-center\" v-if=\"activeTabIndex == 0\">\n\t\t\t\t\t<button type=\"primary\" @tap=\"useCoupon\" class=\"use-coupon-btn\">立即使用</button>\n\t\t\t\t</view>\n\t\t\t</view>\n\t\t</modal>\n\n\t\t<!--轻提示-->\n\t\t<uv-toast ref=\"uToast\"></uv-toast>\n\t</view>\n</template>\n\n<script setup>\nimport {\r\n  ref,\n  watch,\n  toRaw\r\n} from 'vue'\nimport { useMainStore } from '@/store/store'\nimport { storeToRefs } from 'pinia'\nimport { onLoad,onShow ,onPullDownRefresh,onHide} from '@dcloudio/uni-app'\nimport { formatDateTime,prePage } from '@/utils/util'\nimport {\n  couponMine\r\n} from '@/api/coupon'\nconst main = useMainStore()\nconst { isLogin } = storeToRefs(main)\nconst title = ref('优惠券')\nconst activeTabIndex = ref(0)\nconst detailModalVisible = ref(false)\nconst coupon = ref({})\nconst couponIndex = ref(0)\nconst coupons = ref([])\nconst amount = ref(0)\nconst buttonLock = ref(false)\nconst coupon_id = ref(0)\nconst shop_id = ref(0)\nconst type = ref(0)\nconst uToast = ref()\n\nonLoad((options) => {\r\n\tif (options.amount) {\n\t\tamount.value = options.amount;\n\t}\n\tif (options.coupon_id) {\n\t\tcoupon_id.value = options.coupon_id\n\t}\n\tif (options.shop_id) {\n\t\tshop_id.value = options.shop_id;\n\t}\n\tif (options.type) {\n\t\ttype.value = options.type;\n\t}\n\tactiveTabIndex.value = 0;\n\tgetCoupons();\n})\n\nonPullDownRefresh(() => {\n\tgetCoupons();\n})\n\n// 使用范围\nconst typeInfo = (type) => {\n\tif (type == 0) {\n\t\treturn '外卖和自取';\n\t}\n\tif (type == 1) {\n\t\treturn '自取';\n\t}\n\tif (type == 2) {\n\t\treturn '外卖';\n\t}\n}\nconst getCoupons = async() => {\n\tlet data = await couponMine({shop_id: shop_id.value, type: type.value, page:1, pagesize:10000});\n\tuni.stopPullDownRefresh();\n\tif (data) {\n\t\tcoupons.value = data;\n\t}\n}\nconst openDetailModal = (mycoupon, index) => {\n\tcouponIndex.value = index;\n\tcoupon.value = mycoupon;\n\tdetailModalVisible.value = true;\n}\n// 使用优惠券\nconst useCouponWith = (mycoupon) => {\n\tcoupon.value = mycoupon;\n\tuseCoupon();\n}\n// 取消优惠券\nconst cancelCoupon = () => {\n\tcoupon.value = {}\n\tcoupon_id.value = 0\n\tprePage().coupon = {}\n}\nconst closeDetailModal = () => {\n\tdetailModalVisible.value = false;\n\tcoupon.value = {};\n}\n// 使用优惠及\n\nconst useCoupon = () => {\n\tif (buttonLock.value == true) {\n\t\treturn;\n\t}\n\tbuttonLock.value = true\n\t//console.log('coupon:',coupon.value);\n\tif (parseFloat(coupon.value.least) > parseFloat(amount.value)) {\n\t\t//console.log('pages3:');\n\t\tuToast.value.show({\n\t\t\tmessage: '订单金额满'+coupon.value.least+'才能使用',\n\t\t\ttype: 'error'\n\t\t});\n\t\tbuttonLock.value = false\n\t} else {\n\t    main.SET_COUPON(coupon)\n\t\tconsole.log('main.myconpon:',main.mycoupon)\n\t\t//prePage().coupon = coupon.value;\n\t\t//prePage().coupons = 1; // 哨兵\n\t\t\n\t\tuni.navigateBack({\n\t\t\t\n\t\t})\n\t\t\n\t}\n\t\n}\n\n\n\n</script>\n\n<style lang=\"scss\" scoped>\n/* #ifdef H5 */\npage {\n\theight: 100%;\n}\n/* #endif */\n\n.container {\n\tdisplay: flex;\n\tflex-direction: column;\n}\n\n\n.coupon-list {\n\tmargin-top: 30rpx;\n\theight:calc(100vh - 120rpx); \n\t// height: calc(100vh - 120rpx - 200rpx);\n\t/* #ifdef H5 */\n\t// height: calc(100vh - 120rpx - 200rpx - 44px);\n\t/* #endif */\n}\n\n.wrapper {\n\tpadding: 0 20rpx;\n\tdisplay: flex;\n\tflex-direction: column;\n\n\t.coupon {\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tbackground-color: #ffffff;\n\t\tmargin-bottom: 30rpx;\n\t\t//padding: 0 30rpx;\n\t\tborder-radius: 6rpx;\n\t\tbox-shadow: 0 10rpx 10rpx -10rpx rgba(15, 15, 15, 0.1);\n\t\tposition: relative;\n\n\t\t&::before {\n\t\t\tcontent: '';\n\t\t\tposition: absolute;\n\t\t\tbackground-color: $bg-color;\n\t\t\twidth: 30rpx;\n\t\t\theight: 30rpx;\n\t\t\tbottom: 65rpx;\n\t\t\tleft: -15rpx;\n\t\t\tborder-radius: 100%;\n\t\t}\n\n\t\t&::after {\n\t\t\tcontent: '';\n\t\t\tposition: absolute;\n\t\t\tbackground-color: $bg-color;\n\t\t\twidth: 30rpx;\n\t\t\theight: 30rpx;\n\t\t\tbottom: 65rpx;\n\t\t\tright: -15rpx;\n\t\t\tborder-radius: 100%;\n\t\t}\n\n\t\t.detail {\n\t\t\tpadding: 20rpx 0;\n\t\t\tposition: relative;\n\n\t\t\t&::after {\n\t\t\t\tcontent: '';\n\t\t\t\tposition: absolute;\n\t\t\t\tleft: 0;\n\t\t\t\tright: 0;\n\t\t\t\tbottom: 0;\n\t\t\t\tborder-bottom: 1rpx dashed #c6c6c6;\n\t\t\t\ttransform: scaleY(0.5);\n\t\t\t}\n\n\t\t\t.coupon-img {\n\t\t\t\twidth: 150rpx;\n\t\t\t\theight: 150rpx;\n\t\t\t\tmargin-right: 40rpx;\n\t\t\t}\n\t\t}\n\t}\n}\n\n.use-coupon-btn {\n\twidth: 95%;\n\tborder-radius: 50rem !important;\n}\n\n.taobao {\n\tbackground-color: white;\n\t.title {\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tjustify-content: space-between;\n\t\tmargin-bottom: 20rpx;\n\t\tfont-size: 30rpx;\n\t\t.left {\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t}\n\t\t.store {\n\t\t\tfont-weight: 500;\n\t\t}\n\t\t.buddha {\n\t\t\twidth: 70rpx;\n\t\t\theight: 70rpx;\n\t\t\tborder-radius: 10rpx;\n\t\t\tmargin-right: 10rpx;\n\t\t}\n\t\t.entrance {\n\t\t\tcolor: $uv-info;\n\t\t\tborder: solid 2rpx $uv-info;\n\t\t\tline-height: 48rpx;\n\t\t\tpadding: 0 30rpx;\n\t\t\tbackground: none;\n\t\t\tborder-radius: 15px;\n\t\t}\n\t}\n\t.ticket {\n\t\tdisplay: flex;\n\t\t.left {\n\t\t\twidth: 70%;\n\t\t\tpadding: 20rpx;\n\t\t\tbackground-color: white; //rgb(255, 245, 244);\n\t\t\tborder-radius: 20rpx;\n\t\t\tborder-right: dashed 2rpx rgb(224, 215, 211);\n\t\t\tdisplay: flex;\n\t\t\t.picture {\n\t\t\t\t//width: 172rpx;\n\t\t\t\tborder-radius: 20rpx;\n\t\t\t\twidth: 190rpx;\n\t\t\t\theight: 190rpx;\n\t\t\t}\n\t\t\t.introduce {\n\t\t\t\tmargin-left: 10rpx;\n\t\t\t\t.top {\n\t\t\t\t\tcolor: $uv-warning;\n\t\t\t\t\tfont-size: 28rpx;\n\t\t\t\t\t.big {\n\t\t\t\t\t\tfont-size: 60rpx;\n\t\t\t\t\t\tfont-weight: bold;\n\t\t\t\t\t\tmargin-right: 10rpx;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t.type {\n\t\t\t\t\tfont-size: 28rpx;\n\t\t\t\t\tcolor: $uv-info-dark;\n\t\t\t\t}\n\t\t\t\t.date {\n\t\t\t\t\tmargin-top: 10rpx;\n\t\t\t\t\tfont-size: 20rpx;\n\t\t\t\t\tcolor: $uv-info-dark;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t.right {\n\t\t\twidth: 30%;\n\t\t\tpadding: 40rpx 20rpx;\n\t\t\tbackground-color: white; //rgb(255, 245, 244);\n\t\t\tborder-radius: 20rpx;\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\t.use {\n\t\t\t\theight: auto;\n\t\t\t\tpadding: 0 20rpx;\n\t\t\t\tfont-size: 24rpx;\n\t\t\t\tborder-radius: 40rpx;\n\t\t\t\tcolor: #ffffff !important;\n\t\t\t\tbackground-color: $uv-warning !important;\n\t\t\t\tline-height: 40rpx;\n\t\t\t\tcolor: rgb(117, 142, 165);\n\t\t\t\tmargin-left: 20rpx;\n\t\t\t}\n\t\t\t.used {\n\t\t\t\theight: auto;\n\t\t\t\tpadding: 0 20rpx;\n\t\t\t\tfont-size: 24rpx;\n\t\t\t\tborder-radius: 40rpx;\n\t\t\t\t//color: #ffffff!important;\n\t\t\t\t//background-color: $u-type-warning!important;\n\t\t\t\tline-height: 40rpx;\n\t\t\t\t//color: rgb(117, 142, 165);\n\t\t\t\tmargin-left: 20rpx;\n\t\t\t}\n\t\t}\n\t\t.right_log {\n\t\t\ttext-align: center;\n\t\t\twidth: 30%;\n\t\t\tpadding: 80rpx 0rpx;\n\t\t\tbackground-color: white; //rgb(255, 245, 244);\n\t\t\tborder-radius: 20rpx;\n\n\t\t\talign-items: center;\n\t\t}\n\t}\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/pages/components/pages/pay/pay.vue",
    "content": "<template>\n\t<uv-navbar\n\t  :fixed=\"false\"\n\t  :title=\"title\"\n\t  left-arrow\n\t  @leftClick=\"$onClickLeft\"\n\t/>\n\t<view class=\"container position-relative\">\n\t\t<view style=\"margin-bottom: 130rpx;\">\n\t\t\t<view class=\"section-1\">\n\t\t\t\t<template v-if=\"store.distance > 0\">\n\t\t\t\t\t<list-cell class=\"location\">\n\t\t\t\t\t\t<view class=\"flex-fill d-flex justify-content-between align-items-center\">\n\t\t\t\t\t\t\t<view class=\"store-name flex-fill\">{{ orderType == 'takeout' ? '外卖配送' : '点餐自取' }}</view>\n\t\t\t\t\t\t\t<uv-switch activeColor=\"#09b4f1\" v-model=\"active\" @change=\"takout\">\n\t\t\t\t\t\t\t</uv-switch>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</list-cell>\n\t\t\t\t</template>\n\n\t\t\t\t<template v-if=\"orderType == 'takeout'\">\n\t\t\t\t\t<list-cell @click=\"chooseAddress\">\n\t\t\t\t\t\t<view v-if=\"address.realName\" class=\"w-100 d-flex flex-column\">\n\t\t\t\t\t\t\t<view class=\"d-flex align-items-center justify-content-between mb-10\">\n\t\t\t\t\t\t\t\t<view class=\"font-size-lg text-color-base\">\n\t\t\t\t\t\t\t\t\t{{ address.address + ' ' + address.detail }}\n\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t<image src=\"/static/images/navigator-1.png\" class=\"arrow\"></image>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t<view class=\"d-flex text-color-assist font-size-sm align-items-center\">\n\t\t\t\t\t\t\t\t<view class=\"mr-10\">{{ address.realName }}</view>\n\t\t\t\t\t\t\t\t<view>{{ address.phone }}</view>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t\t<view v-else class=\"flex-fill d-flex justify-content-between align-items-center\">\n\t\t\t\t\t\t\t<view class=\"store-name flex-fill\">选择收货地址</view>\n\t\t\t\t\t\t\t<image src=\"/static/images/navigator-1.png\" class=\"arrow\"></image>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</list-cell>\n\t\t\t\t</template>\n\t\t\t</view>\n\n\t\t\t<view class=\"section-1\">\n\t\t\t\t<template>\n\t\t\t\t\t<list-cell class=\"location\" @click=\"goToShop\">\n\t\t\t\t\t\t<view class=\"flex-fill d-flex justify-content-between align-items-center\">\n\t\t\t\t\t\t\t<view class=\"store-name flex-fill\">{{ store.name }}</view>\n\t\t\t\t\t\t\t<image src=\"/static/images/navigator-1.png\" class=\"arrow\"></image>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</list-cell>\n\t\t\t\t</template>\n\n\t\t\t\t<template>\n\t\t\t\t\t<list-cell arrow class=\"meal-time\" v-if=\"orderType == 'takein'\">\n\t\t\t\t\t\t<view class=\"flex-fill d-flex justify-content-between align-items-center\"\n\t\t\t\t\t\t\t@click=\"takeinTIme = !takeinTIme\">\n\t\t\t\t\t\t\t<view class=\"title\">取餐时间</view>\n\t\t\t\t\t\t\t<view class=\"time\">\n\t\t\t\t\t\t\t\t{{ takeinRange[defaultSelector[0]].name }}\n\t\t\t\t\t\t\t\t<u-picker v-model=\"takeinTIme\" :range=\"takeinRange\" range-key=\"name\" mode=\"selector\"\n\t\t\t\t\t\t\t\t\t@cancel=\"takeinCancelTime\" @confirm=\"takeinConfirmTime\"\n\t\t\t\t\t\t\t\t\t:default-selector=\"defaultSelector\"></u-picker>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</list-cell>\n\t\t\t\t\t<list-cell class=\"contact\" last :hover=\"false\" v-if=\"orderType == 'takein'\">\n\t\t\t\t\t\t<view class=\"flex-fill d-flex justify-content-between align-items-center\">\n\t\t\t\t\t\t\t<view class=\"title flex-fill\">联系电话</view>\n\t\t\t\t\t\t\t<view class=\"time\"><input class=\"text-right\" placeholder=\"请输入手机号码\" :value=\"member.mobile\" />\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t<button class=\"contact-tip font-size-sm\">自动填写</button>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</list-cell>\n\t\t\t\t</template>\n\t\t\t\t<template v-if=\"orderType == 'takeout'\">\n\t\t\t\t\t<list-cell>\n\t\t\t\t\t\t<view class=\"w-100 d-flex flex-column\">\n\t\t\t\t\t\t\t<view class=\"d-flex align-items-center font-size-base text-color-base\">\n\t\t\t\t\t\t\t\t<view class=\"flex-fill\">预计送达时间</view>\n\t\t\t\t\t\t\t\t<view class=\"mr-10\">\n\t\t\t\t\t\t\t\t\t{{ defaultTime }}\n\t\t\t\t\t\t\t\t\t<u-picker :default-time=\"defaultTime\" v-model=\"takeoutTIme\" :params=\"paramsTime\"\n\t\t\t\t\t\t\t\t\t\tmode=\"time\" @cancel=\"cancelTime\" @confirm=\"choiceTime\"></u-picker>\n\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</list-cell>\n\t\t\t\t</template>\n\t\t\t</view>\n\t\t\t<!-- 购物车列表 begin -->\n\t\t\t<view class=\"section-2\">\n\t\t\t\t<view class=\"cart d-flex flex-column\">\n\t\t\t\t\t<list-cell last v-for=\"(item, index) in cart\" :key=\"index\">\n\t\t\t\t\t\t<view class=\"w-100 d-flex flex-column\">\n\t\t\t\t\t\t\t<view class=\"d-flex align-items-center mb-10\">\n\t\t\t\t\t\t\t\t<view\n\t\t\t\t\t\t\t\t\tclass=\"d-flex flex-fill justify-content-between align-items-center text-color-base font-size-lg\">\n\t\t\t\t\t\t\t\t\t<image style=\"width: 80rpx;height: 80rpx;\" mode=\"aspectFill\" :src=\"item.image\">\n\t\t\t\t\t\t\t\t\t</image>\n\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t<view class=\"name-and-props overflow-hidden\">\n\t\t\t\t\t\t\t\t\t<view class=\"text-color-base font-size-lg\">{{ item.name }}</view>\n\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t<view\n\t\t\t\t\t\t\t\t\tclass=\"d-flex flex-fill justify-content-between align-items-center text-color-base font-size-lg\">\n\t\t\t\t\t\t\t\t\t<view>x{{ item.number }}</view>\n\t\t\t\t\t\t\t\t\t<view>￥{{ item.price }}</view>\n\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t<view class=\"text-truncate font-size-base text-color-assist\">{{ item.valueStr }}</view>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</list-cell>\n\t\t\t\t</view>\n\t\t\t\t<list-cell arrow @click=\"goToPackages\">\n\t\t\t\t\t<view class=\"flex-fill d-flex justify-content-between align-items-center\">\n\t\t\t\t\t\t<view class=\"text-color-base\">优惠券</view>\n\t\t\t\t\t\t<view v-if=\"coupons == 0\" class=\"text-color-base\">暂无可用</view>\n\t\t\t\t\t\t<view v-else-if=\"coupon.title\" class=\"text-color-danger\">\n\t\t\t\t\t\t\t{{ coupon.title }}(满{{ coupon.least }}减{{ coupon.value }})\n\t\t\t\t\t\t</view>\n\t\t\t\t\t\t<view v-else class=\"text-color-primary\">可用优惠券{{ coupons }}张</view>\n\t\t\t\t\t</view>\n\t\t\t\t</list-cell>\n\t\t\t\t<list-cell last>\n\t\t\t\t\t<view class=\"flex-fill d-flex justify-content-end align-items-center\">\n\t\t\t\t\t\t<view>\n\t\t\t\t\t\t\t总计￥{{ total }}\n\t\t\t\t\t\t\t<text v-if=\"orderType == 'takeout'\">,配送费￥{{ store.deliveryPrice }}</text>\n\t\t\t\t\t\t\t<text v-if=\"coupon.value\">,￥-{{ coupon.value }}</text>\n\t\t\t\t\t\t\t,实付\n\t\t\t\t\t\t</view>\n\t\t\t\t\t\t<view class=\"font-size-extra-lg font-weight-bold\">￥{{ amount }}</view>\n\t\t\t\t\t</view>\n\t\t\t\t</list-cell>\n\t\t\t</view>\n\t\t\t<!-- 购物车列表 end -->\n\t\t\t<view class=\"d-flex align-items-center justify-content-start font-size-sm text-color-warning\"\n\t\t\t\tstyle=\"padding: 20rpx 0;\">\n\t\t\t</view>\n\t\t\t<!-- 支付方式 begin -->\n\t\t\t<view class=\"payment\">\n\t\t\t\t<list-cell last :hover=\"false\"><text>支付方式</text></list-cell>\n\t\t\t\t<list-cell>\n\t\t\t\t\t<view class=\"d-flex align-items-center justify-content-between w-100 disabled\"\n\t\t\t\t\t\t@click=\"setPayType('yue')\">\n\t\t\t\t\t\t<view class=\"iconfont iconbalance line-height-100 payment-icon\"></view>\n\t\t\t\t\t\t<view class=\"flex-fill\">余额支付（余额￥{{ member.nowMoney }}）</view>\n\t\t\t\t\t\t<view class=\"font-size-sm\" v-if=\"member.nowMoney == 0\">余额不足</view>\n\t\t\t\t\t\t<view class=\"iconfont line-height-100 checkbox checked iconradio-button-on\" v-if=\"payType == 'yue'\">\n\t\t\t\t\t\t</view>\n\t\t\t\t\t\t<view class=\"iconfont line-height-100 checkbox iconradio-button-off\" v-else></view>\n\t\t\t\t\t</view>\n\t\t\t\t</list-cell>\n\t\t\t\t<list-cell last>\n\t\t\t\t\t<view class=\"d-flex align-items-center justify-content-between w-100\" @click=\"setPayType('weixin')\">\n\t\t\t\t\t\t<view class=\"iconfont iconwxpay line-height-100 payment-icon\" style=\"color: #7EB73A\"></view>\n\t\t\t\t\t\t<view class=\"flex-fill\">微信支付</view>\n\t\t\t\t\t\t<view class=\"iconfont line-height-100 checkbox checked iconradio-button-on\" v-if=\"payType == 'weixin'\">\n\t\t\t\t\t\t</view>\n\t\t\t\t\t\t<view class=\"iconfont line-height-100 checkbox iconradio-button-off\" v-else></view>\n\t\t\t\t\t</view>\n\t\t\t\t</list-cell>\n\t\t\t\t<!-- #ifdef H5 -->\n\t\t\t\t<list-cell>\n\t\t\t\t\t<view class=\"d-flex align-items-center justify-content-between w-100\" @click=\"setPayType('alipay')\">\n\t\t\t\t\t\t<view class=\"iconfont-yshop icon-alipay line-height-100 payment-icon\" style=\"color:#07b4fd\" ></view>\n\t\t\t\t\t\t<view class=\"flex-fill\">支付宝</view>\n\t\t\t\t\t\t<view class=\"iconfont line-height-100 checkbox checked iconradio-button-on\" v-if=\"payType == 'alipay'\" ></view>\n\t\t\t\t\t\t<view class=\"iconfont line-height-100 checkbox iconradio-button-off\" v-else ></view>     \n\t\t\t\t\t</view>\n\t\t\t\t</list-cell>\n\t\t\t\t<!-- #endif -->\n\t\t\t</view>\n\t\t\t<!-- 支付方式 end -->\n\t\t\t<!-- 备注 begin -->\n\t\t\t<list-cell  last @click=\"goToRemark\">\n\t\t\t\t<view class=\"d-flex flex-fill align-items-center justify-content-between overflow-hidden\" style=\"margin-bottom: 110rpx;\">\n\t\t\t\t\t<view class=\"flex-shrink-0 mr-20\">备注</view>\n\t\t\t\t\t<view class=\"text-color-primary flex-fill text-truncate text-right\">{{ form.remark || '点击填写备注' }}\n\t\t\t\t\t</view>\n\t\t\t\t</view>\n\t\t\t</list-cell>\n\t\t\t<!-- 备注 end -->\n\t\t</view>\n\t\t<!-- 付款栏 begin -->\n\t\t<view style=\"z-index: 1;\"\n\t\t\tclass=\"w-100 pay-box position-fixed fixed-bottom d-flex align-items-center justify-content-between bg-white\">\n\t\t\t<view class=\"font-size-sm\" style=\"margin-left: 20rpx;\">合计：</view>\n\t\t\t<view class=\"font-size-lg flex-fill\">￥{{ amount }}</view>\n\t\t\t<view class=\"bg-primary h-100 d-flex align-items-center just-content-center text-color-white font-size-base\"\n\t\t\t\tstyle=\"padding: 0 60rpx;\" @tap=\"debounce(submit, 500)\">付款</view>\n\t\t</view>\n\t\t<!-- 付款栏 end -->\n\t\t<modal :show=\"ensureAddressModalVisible\" custom :mask-closable=\"false\" :radius=\"'0rpx'\" width=\"90%\">\n\t\t\t<view class=\"modal-content\">\n\t\t\t\t<view class=\"d-flex justify-content-end\">\n\t\t\t\t\t<image src=\"/static/images/pay/close.png\" style=\"width: 40rpx; height: 40rpx;\"\n\t\t\t\t\t\t@tap=\"ensureAddressModalVisible = false\"></image>\n\t\t\t\t</view>\n\t\t\t\t<view class=\"d-flex just-content-center align-items-center\" style=\"margin-bottom: 40px;\">\n\t\t\t\t\t<view class=\"font-size-extra-lg text-color-base\">请再次确认下单地址</view>\n\t\t\t\t</view>\n\t\t\t\t<view\n\t\t\t\t\tclass=\"d-flex font-size-base text-color-base font-weight-bold align-items-center justify-content-between mb-20\">\n\t\t\t\t\t<view>{{ address.realName }}</view>\n\t\t\t\t\t<view>{{ address.phone }}</view>\n\t\t\t\t</view>\n\t\t\t\t<view class=\"d-flex font-size-sm text-color-assist align-items-center justify-content-between mb-40\">\n\t\t\t\t\t<view style=\"max-width: 60%;\">{{ address.address + address.detail }}</view>\n\t\t\t\t\t<button type=\"primary\" size=\"mini\" plain class=\"change-address-btn\" style=\"white-space: nowrap;\"\n\t\t\t\t\t\t@click=\"chooseAddress\">修改地址</button>\n\t\t\t\t</view>\n\t\t\t\t<button type=\"primary\" class=\"pay_btn\" @tap=\"debounce(pay, 500)\">确认并付款</button>\n\t\t\t</view>\n\t\t</modal>\n\t\t<uv-toast ref=\"uToast\"></uv-toast>\n\t</view>\n</template>\n\n<script setup>\nimport {\n  ref,\n  computed,\n  nextTick\n} from 'vue'\nimport { useMainStore } from '@/store/store'\nimport { storeToRefs } from 'pinia'\nimport { onLoad,onShow ,onPullDownRefresh,onHide} from '@dcloudio/uni-app'\nimport { formatDateTime,isWeixin } from '@/utils/util'\nimport  debounce  from '@/uni_modules/uv-ui-tools/libs/function/debounce'\n\nimport {\n  orderSubmit,\n  payUnify,\n  getWechatConfig\n} from '@/api/order'\nimport {\n  couponCount\n} from '@/api/coupon'\n// #ifdef H5\nimport * as jweixin from 'weixin-js-sdk'\n// #endif\nconst main = useMainStore()\nconst { orderType,address, store,location,isLogin,member,mycoupon } = storeToRefs(main)\nconst active = ref(false)\nconst title = ref('支付')\nconst jsStr = ref('')\nconst cart = ref([])\nconst form = ref({\n\tremark: ''\n})\nconst  ensureAddressModalVisible = ref(false)\nconst  takeoutTIme = ref(false) // 外卖取餐时间picker\nconst paramsTime = ref({\n\tyear: false,\n\tmonth: false,\n\tday: false,\n\thour: true,\n\tminute: true,\n\tsecond: false\n})\nconst defaultTime = ref('00:00')\nconst takeinTIme = ref(false) // 到店自取时间selector\nconst takeinRange = ref([{\n\t\tname: '立即用餐',\n\t\tvalue: 0\n\t},\n\t{\n\t\tname: '10分钟后',\n\t\tvalue: 10\n\t},\n\t{\n\t\tname: '20分钟后',\n\t\tvalue: 20\n\t},\n\t{\n\t\tname: '30分钟后',\n\t\tvalue: 30\n\t},\n\t{\n\t\tname: '40分钟后',\n\t\tvalue: 40\n\t},\n\t{\n\t\tname: '50分钟后',\n\t\tvalue: 50\n\t}\n])\nconst defaultSelector = ref([0])\nconst payType = ref('weixin') // 付款方式\nconst coupons = ref(0) // 可用优惠券数量\nconst coupon = ref(main.mycoupon) // 选中的\nconst subscribeMss = ref({\n\t'takein': '',\n\t'takeout': '',\n\t'takein_made': '',\n\t'takeout_made': ''\n})// 微信订阅信息\nconst uToast = ref()\n\nconst total = computed(() =>{\n\treturn cart.value.reduce((acc, cur) => acc + cur.number * cur.price, 0);\n})\nconst amount = computed(() =>{\n\tlet amount = cart.value.reduce((acc, cur) => acc + cur.number * cur.price, 0);\n\t// 加配送费\n\tif (store.value.distance > 0 && orderType.value == 'takeout') {\n\t\tamount += parseFloat(store.value.deliveryPrice);\n\t}\n\n\t\n\t// 减去优惠券\n\tif (main.mycoupon.hasOwnProperty('id')) {\n\t\tamount -= parseFloat(main.mycoupon.value);\n\t}\n\tif(amount < 0){\n\t\tamount = 0\n\t}\n\treturn amount.toFixed(2);\n})\n\nonShow(() => {\n\tcoupon.value = main.mycoupon\n\tlet date = new Date(new Date().getTime() + 3600000); // 一个小时后\n\tlet hour = date.getHours();\n\tlet minute = date.getMinutes();\n\tif (hour < 10) {\n\t\thour = '0' + hour;\n\t}\n\tif (minute < 10) {\n\t\tminute = '0' + minute;\n\t}\n\tdefaultTime.value = hour + ':' + minute;\n\t\n\tconsole.log('member:',member.value)\n\t\n\tif(orderType.value == 'takeout'){\n\t\tactive.value = true\n\t}else{\n\t\tactive.value = false\n\t}\n\t\n\tgetCoupons();\n\t\n\tlet paytype = uni.getStorageSync('paytype');\n\tpayType.value = paytype ? paytype : 'weixin';\n\t\n})\nonHide(() => {\n\tsubscribeMss.value = [];\n\tcoupons.value = 0;\n})\nonLoad((option) => {\n\tcart.value = uni.getStorageSync('cart')\n\tif(option.remark) {\n\t\tform.value.remark = option.remark\n\t}\n})\n\nconst getSubscribeMss = async() => {\n\t let data = []\n\tif (data) {\n\t\tsubscribeMss.value = data;\n\t}\n}\n// 更改支付方式\nconst setPayType = (paytype) => {\n\tpayType.value = 'weixin';\n\tpayType.value= paytype;\n\tuni.setStorage({\n\t\tkey: 'paytype',\n\t\tdata: paytype\n\t})\n}\nconst getCoupons = async() => {\n\t//0=通用,1=自取,2=外卖\n\tlet type = orderType.value == 'takein' ? 1 : 2;\n\tlet data = await couponCount({\n\t\tshop_id: store.value.id ? store.value.id : 0,\n\t\ttype: type\n\t});\n\tif (data) {\n\t\tcoupons.value = data;\n\t}\n}\n// 选择时间\nconst choiceTime = (value) => {\n\tlet hour = value.hour;\n\tlet minute = value.minute;\n\n\tlet date = new Date(new Date().getTime() + 3600000); // 一个小时后\n\tlet nowhour = date.getHours();\n\tlet nowminute = date.getMinutes();\n\n\tif ((hour * 60 * 60 + minute * 60) * 1000 - 3600000 < (nowhour * 60 * 60 + nowminute * 60) * 1000) {\n\t\tuToast.value.show({\n\t\t\tmessage: '请至少选择一个小时之后',\n\t\t\ttype: 'error'\n\t\t});\n\t\treturn\n\t}\n\n\tif (hour < 10) {\n\t\thour = '0' + hour;\n\t}\n\tif (minute < 10) {\n\t\tminute = '0' + minute;\n\t}\n\tdefaultTime,value = hour + ':' + minute;\n\ttakeoutTIme.value = false;\n}\nconst cancelTime = (value) => {\n\ttakeoutTIme.value = false;\n}\n// 到店自取-取消选择取餐时间\nconst takeinCancelTime = (value) => {\n\ttakeinTIme.value = false;\n}\n// 到店自取-选择取餐时间\nconst takeinConfirmTime = (value) => {\n\tdefaultSelector.value = value;\n}\n// 是否外卖开关\nconst takout = (value) => {\n\tlet type = 'takeout';\n\tif (value == false) {\n\t\ttype = 'takein';\n\t}\n\tmain.SET_ORDER_TYPE(type);\n\n\t// 如果存在优惠券看看需不需要清除\n\tif (coupon.value.hasOwnProperty('type')) {\n\t\t//0=通用,1=自取,2=外卖\n\t\tif (coupon.value.type != 0) {\n\t\t\tif (coupon.value.type == 1 && orderType.value == 'takeout') {\n\t\t\t\tcoupon.value = {};\n\t\t\t}\n\t\t\tif (coupon.value.type == 2 && orderType.value == 'takeint') {\n\t\t\t\tcoupon.value = {};\n\t\t\t}\n\t\t}\n\t}\n\tsubscribeMss.value = [];\n\tcoupons.value = 0;\n\tgetCoupons();\n}\nconst goToRemark = () => {\n\tuni.navigateTo({\n\t\turl: '/pages/components/pages/remark/remark?remark=' + form.value.remark\n\t});\n}\nconst chooseAddress = () => {\n\tuni.navigateTo({\n\t\turl: '/pages/components/pages/address/address?is_choose=true&scene=pay'\n\t});\n}\nconst goToPackages = () => {\n\tlet newamount = amount.value;\n\tlet coupon_id = coupon.value.id ? coupon.value.id : 0;\n\tlet type = orderType.value == 'takein' ? 1 : 2;\n\tlet shop_id = store.value.id;\n\tuni.navigateTo({\n\t\turl: '/pages/components/pages/packages/index?amount=' + newamount + '&coupon_id=' + coupon_id +\n\t\t\t'&shop_id=' + shop_id + '&type=' + type\n\t});\n}\nconst goToShop = () => {\n\tuni.navigateTo({\n\t\turl: `/pages/components/pages/shop/shop`\n\t});\n}\nconst submit = () => {\n\tif (orderType.value == 'takeout') {\n\t\t// 外卖类型\n\t\tif (typeof address.value.id == 'undefined') {\n\t\t\tuToast.value.show({\n\t\t\t\tmessage: '请选择收货地址',\n\t\t\t\ttype: 'error'\n\t\t\t});\n\t\t\treturn\n\t\t}\n\n\t\t// 起送价钱\n\t\tif (store.value.min_price > total.value) {\n\t\t\tuToast.value.show({\n\t\t\t\tmessage: '本店外卖起送价为￥' + store.value.min_price,\n\t\t\t\ttype: 'error'\n\t\t\t});\n\t\t\treturn\n\t\t}\n\n\t\tpay();\n\n\t} else {\n\t\tpay();\n\t}\n}\nconst pay = async() => {\n\tlet that = this;\n\t// // #ifdef MP-WEIXIN\n\t// await new Promise(function(revolve) {\n\t// \t//订阅号信息id\n\t// \t let subscribeMss = ['KBtfY9G1IWCzC6q-ZKo-Q-MmdP7aaF79nx0XFcBf3h4'];\n\n\t// \twx.showModal({\n\t// \t\ttitle: '温馨提示',\n\t// \t\tcontent: '为更好的促进您与商家的交流，小程序需要在您成交时向您发送消息',\n\t// \t\tconfirmText: \"同意\",\n\t// \t\tcancelText: \"拒绝\",\n\t// \t\tsuccess: function(res) {\n\t// \t\t\tif (res.confirm) {\n\t// \t\t\t\tuni.requestSubscribeMessage({\n\t// \t\t\t\t\ttmplIds: subscribeMss,\n\t// \t\t\t\t\tcomplete(res) {\n\t// \t\t\t\t\t\tconsole.log(res)\n\t// \t\t\t\t\t\trevolve(true)\n\t// \t\t\t\t\t}\n\t// \t\t\t\t});\n\t// \t\t\t} else {\n\t// \t\t\t\trevolve(true)\n\t// \t\t\t}\n\t// \t\t}\n\t// \t})\n\t// });\n\t\n\n\t// #endif\n\tif(amount.value == 0){\n\t\tpayType.value = 'yue'\n\t}\n\tuni.showLoading({\n\t\ttitle: '加载中'\n\t});\n\n\tlet data = {\n\t\torderType: orderType.value, // 购买类型:takein=自取,takeout=外卖\n\t\taddressId:orderType.value == 'takeout' ? address.value.id : 0, // 外卖配送地址\n\t\tshopId: store.value.id, // 店铺id\n\t\tmobile: member.value.mobile, // 联系电话\n\t\tgettime: takeinRange.value[defaultSelector.value[0]].value, // 取餐时间\n\t\tpayType: payType.value, // 支付类型\n\t\tremark: form.value.remark, // 备注\n\t\tproductId: [],\n\t\tspec: [],\n\t\tnumber: [],\n\t\tcouponId: coupon.value.id ? coupon.value.id : 0 // 优惠券id\n\t};\n\n\tcart.value.forEach((item, index) => {\n\t\tdata.productId.push(item.id);\n\t\tdata.spec.push(item.valueStr.replace(/,/g, '|'));\n\t\t//data.spec.push(item.valueStr);\n\t\tdata.number.push(item.number);\n\t});\n\n\t//console.log(data);\n\tlet order = await orderSubmit(data);\n\tif (!order) {\n\t\tuni.hideLoading();\n\t\treturn;\n\t}\n\t\n\tmain.DEL_COUPON()\n    if(amount.value == 0){\n\t\tuToast.value.show({\n\t\t\tmessage: '订单金额为0自动走余额支付',\n\t\t\ttype: 'success'\n\t\t});\n\t\tbalancePay(order);\n\t\tuni.hideLoading()\n\t\treturn\n   }\n\n\tif (payType.value == 'weixin') {\n\t\t// 微信支付\n\t\tweixinPay(order);\n\t} else if (payType.value == 'yue') {\n\t\t// 余额支付\n\t\tbalancePay(order);\n\t} else if (payType.value == 'alipay') {\n\t\t// 余额支付\n\t\taliPay(order);\n\t} \n\tuni.hideLoading()\n\treturn\n}\nconst balancePay = async(order) => {\n\tlet from = 'routine'\n\t// #ifdef H5\n\tfrom = 'h5'\n\t// #endif\n\tlet pay = await payUnify({\n\t\tuni: order.orderId,\n\t\tfrom: from,\n\t\tpaytype: 'yue'\n\t});\n\n\tuni.hideLoading();\n\tif (!pay) {\n\t\treturn;\n\t}\n\n\tmember.value.money -= amount.value\n\tmain.SET_MEMBER(member.value)\n\tuni.removeStorageSync('cart');\n\tuni.switchTab({\n\t\turl: '/pages/order/order',\n\t\tfail(res) {\n\t\t\tconsole.log(res);\n\t\t}\n\t});\n}\nconst weixinPay = async(order) => {\n\tlet from = 'routine'\n\t// #ifdef H5\n\tfrom = 'h5'\n\tif(isWeixin()){\n\t\tfrom = 'wechat'\n\t}\n\t\n\t// #endif\n\t//let that = this;\n\tlet data = await payUnify({\n\t\tuni: order.orderId,\n\t\tfrom: from,\n\t\tpaytype: 'weixin'\n\t});\n\tconsole.log('param2:',data)\n\tif (!data) {\n\t\tuni.hideLoading();\n\t\treturn;\n\t}\n\tif (data.trade_type == 'MWEB') {\n\t\t// #ifdef H5\n\t\t// 微信外的H5\n\t\tconsole.log('data:',data)\n\t\tlocation.href = data.data;\n\t\t// #endif\n\t\tconsole.log('data1:',data)\n\t} else if (data.trade_type == 'JSAPI') {\n\t\tconsole.log('param:',data)\n\n\t\t// #ifdef MP-WEIXIN\n\t\tuni.requestPayment({\n\t\t\tprovider: 'wxpay',\n\t\t\ttimeStamp: data.data.timeStamp,\n\t\t\tnonceStr: data.data.nonceStr,\n\t\t\tpackage: data.data.package,\n\t\t\tsignType: 'MD5',\n\t\t\tpaySign: data.data.paySign,\n\t\t\tsuccess: function(res) {\n\n\t\t\t\tuni.removeStorageSync('cart');\n\t\t\t\tuni.switchTab({\n\t\t\t\t\turl: '/pages/order/order'\n\t\t\t\t});\n\t\t\t},\n\t\t\tfail: function(err) {\n\t\t\t\tconsole.log('fail:' + JSON.stringify(err));\n\t\t\t}\n\t\t});\n\t\t// #endif\n\t} else if (data.trade_type == 'W-JSAPI'){\n\t\t//公众号支付\n\t\n\t\t\n\t}else if (data.trade_type == 'APP') {\n\n\t}\n}\nconst aliPay = async(order) => {\n\n\t// #ifdef H5\n\t//let that = this;\n\tif(isWeixin()){\n\t\tuni.showToast({\n\t\t\ttitle: '请普通浏览器打开进行支付宝支付~',\n\t\t\ticon: 'none'\n\t\t})\n\t\treturn\n\t}\n\tlet data = await payUnify({\n\t\tuni: order.orderId,\n\t\tfrom: 'h5',\n\t\tpaytype: 'alipay'\n\t});\n\n\tconsole.log('data:',data.data)\n  // 支付宝支付，这里只要提交表单\n\tlet form = data.data\n\tconst div = document.createElement('formdiv');\n\tdiv.innerHTML = form;\n\tdocument.body.appendChild(div);      \n\t//document.forms[0].setAttribute('target', ' self');\n\tdocument.forms[0].submit();\n\t//div.remove();\n\n// #endif\n\n\n}\n\n\n\n\n</script>\n\n<style lang=\"scss\" scoped>\n\t.container {\n\t\tpadding: 30rpx;\n\t}\n\n\t.arrow {\n\t\twidth: 50rpx;\n\t\theight: 50rpx;\n\t\tposition: relative;\n\t\tmargin-right: -10rpx;\n\t}\n\n\t.location {\n\t\t.store-name {\n\t\t\tfont-size: $font-size-lg;\n\t\t}\n\n\t\t.iconfont {\n\t\t\tfont-size: 50rpx;\n\t\t\tline-height: 100%;\n\t\t\tcolor: $color-primary;\n\t\t}\n\t}\n\n\t.section-1 {\n\t\tmargin-bottom: 30rpx;\n\n\t\t.contact {\n\t\t\t.contact-tip {\n\t\t\t\tmargin-left: 10rpx;\n\t\t\t\tborder: 2rpx solid $color-primary;\n\t\t\t\tpadding: 6rpx 10rpx;\n\t\t\t\tcolor: $color-primary;\n\t\t\t}\n\t\t}\n\t}\n\n\t.section-2 {\n\t\t.name-and-props {\n\t\t\twidth: 65%;\n\t\t}\n\t}\n\n\t.payment {\n\t\tmargin-bottom: 30rpx;\n\n\t\t.disabled {\n\t\t\tcolor: $text-color-grey;\n\t\t}\n\n\t\t.payment-icon {\n\t\t\tfont-size: 44rpx;\n\t\t\tmargin-right: 10rpx;\n\t\t}\n\n\t\t.checkbox {\n\t\t\tfont-size: 36rpx;\n\t\t\tmargin-left: 10rpx;\n\t\t}\n\n\t\t.checked {\n\t\t\tcolor: $color-primary;\n\t\t}\n\t}\n\n\t.pay-box {\n\t\tbox-shadow: 0 0 20rpx rgba(0, 0, 0, 0.1);\n\t\theight: 100rpx;\n\t}\n\n\t.modal-content {\n\t\t.change-address-btn {\n\t\t\tline-height: 2;\n\t\t\tpadding: 0 1em;\n\t\t}\n\n\t\t.pay_btn {\n\t\t\twidth: 100%;\n\t\t\tborder-radius: 50rem !important;\n\t\t\tline-height: 3;\n\t\t}\n\t}\n\n\t.choice {\n\t\tbackground-color: $bg-color-grey;\n\t\tborder-radius: 38rpx;\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tfont-size: $font-size-sm;\n\t\tpadding: 0 38rpx;\n\t\tcolor: $text-color-assist;\n\n\t\t.dinein,\n\t\t.takeout {\n\t\t\twidth: 50%;\n\t\t\tposition: relative;\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\n\t\t\t&.active {\n\t\t\t\tpadding: 14rpx 38rpx;\n\t\t\t\tcolor: #ffffff;\n\t\t\t\tbackground-color: $color-primary;\n\t\t\t\tborder-radius: 38rpx;\n\t\t\t}\n\t\t}\n\n\t\t.takeout {\n\t\t\tmargin-left: 20rpx;\n\t\t\theight: 100%;\n\t\t\tflex: 1;\n\t\t\tpadding: 14rpx 0;\n\t\t}\n\n\t\t.dinein.active {\n\t\t\t//margin-left: -38rpx;\n\t\t}\n\n\t\t.takeout.active {\n\t\t\t//margin-right: -38rpx;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/pages/components/pages/remark/remark.vue",
    "content": "<template>\n\t<uv-navbar\n\t  :fixed=\"false\"\n\t  :title=\"title\"\n\t  left-arrow\n\t  @leftClick=\"$onClickLeft\"\n\t/>\n\t<view class=\"container w-100 h-100 overflow-hidden\">\n\t\t<view class=\"textarea\">\n\t\t\t<textarea placeholder-class=\"text-color-assist font-size-base\" v-model=\"remark\"\n\t\t\t\tclass=\"bg-white w-100 border-box font-size-base remark\"\n\t\t\t\t:class=\"{'text-color-danger': remarkLength > 50, 'text-color-assist' : remarkLength <=50}\"\n\t\t\t\tplaceholder=\"请填写备注信息\" focus/>\n\t\t\t<view class=\"tips\" :class=\"{'text-color-danger': remarkLength > 50, 'text-color-assist' : remarkLength <=50}\">\n\t\t\t\t{{ remarkLength }}/50\n\t\t\t</view>\n\t\t</view>\n\t\t<view class=\"d-flex font-size-base text-color-assist\" style=\"margin: 40rpx 0;\">\n\t\t\t快捷输入\n\t\t</view>\n\t\t<view class=\"quick-inputs d-flex flex-wrap justify-content-start\">\n\t\t\t<view class=\"quick-input\" v-for=\"(item, index) in quickInputs\" :key=\"index\" @tap=\"handleQuickInput(item)\">\n\t\t\t\t{{ item }}\n\t\t\t</view>\n\t\t</view>\n\t\t<view class=\"d-flex just-content-center align-items-center\" style=\"margin-top: 60rpx;\">\n\t\t\t<button type=\"primary\" class=\"submit-btn font-size-base\" @tap=\"submit\">完成</button>\n\t\t</view>\n\t\t<!--轻提示-->\n\t\t<uv-toast ref=\"uToast\"></uv-toast>\n\t</view>\n</template>\n\n<script setup>\nimport {\r\n  ref,\n  computed\r\n} from 'vue'\nimport { onLoad} from '@dcloudio/uni-app'\n\nconst remark = ref('')\nconst quickInputs = ref( ['请放门把手上', '请放门口', '请放前台桌上', '如地址封闭管理，请电话与我联系'])\nconst uToast = ref()\n\t\nonLoad((opt) => {\n\tremark.value = opt.remark\n})\t\n\nconst remarkLength = computed(() => { \n\treturn remark.value.length\n})\nconst isDanger = computed(() => { \n\treturn remark.value.length > 50\n})\n\nconst handleQuickInput = (item) => {\n\tremark.value = remark.value.concat(\" \", item)\n}\nconst submit = () => {\n\tif (remark.value.length > 50) {\n\t\tuToast.value.show({\n\t\t\tmessage: '不能超过50个字符',\n\t\t\ttype: 'error'\n\t\t});\n\t\treturn\n\t}\n\tuni.navigateTo({\n\t\turl: \"/pages/components/pages/pay/pay?remark=\" + remark.value\n\t})\n}\n\t\n\n</script>\n\n<style lang=\"scss\" scoped>\n\t.container {\n\t\tpadding: 30rpx 40rpx;\n\t\t\n\t\t.textarea {\n\t\t\tposition: relative;\n\t\t\t\n\t\t\t.remark {\n\t\t\t\tborder-radius: 8rpx;\n\t\t\t\tpadding: 30rpx 40rpx;\n\t\t\t\theight: 320rpx;\n\t\t\t\tcolor: $font-size-base;\n\t\t\t}\n\t\t\t\n\t\t\t.tips {\n\t\t\t\tposition: absolute;\n\t\t\t\tbottom: 30rpx;\n\t\t\t\tright: 40rpx;\n\t\t\t}\n\t\t}\n\t\t\n\t\t.quick-inputs {\n\t\t\tpadding-right: 20rpx;\n\t\t\t\n\t\t\t.quick-input {\n\t\t\t\tbackground-color: #FFFFFF;\n\t\t\t\tborder: 2rpx solid $color-primary;\n\t\t\t\tcolor: $color-primary;\n\t\t\t\tfont-size: $font-size-base;\n\t\t\t\tpadding: 16rpx 26rpx;\n\t\t\t\tmargin-right: 20rpx;\n\t\t\t\tmargin-bottom: 20rpx;\n\t\t\t}\n\t\t}\n\t\t\n\t\t.submit-btn {\n\t\t\twidth: 90%;\n\t\t\theight: 80rpx;\n\t\t\tborder-radius: 40rpx;\n\t\t\tline-height: 80rpx;\n\t\t}\n\t}\n</style>\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/pages/components/pages/scoreproduct/confirm.vue",
    "content": "<template>\n\t<!-- #ifdef MP-WEIXIN -->\n\t<uv-navbar\n\t  :fixed=\"false\"\n\t  :title=\"title\"\n\t  left-arrow\n\t  @leftClick=\"$onClickLeft\"\n\t/>\n\t<!-- #endif -->\n\t<view class=\"px-2 mt-2\">\n\t\t<view class=\"bg-white p-2\" >\n\t\t\t<uv-form :model=\"form\" ref=\"uForm\" >\n\t\t\t\t<uv-form-item>\n\t\t\t\t\t\t<view v-if=\"address.realName\">\n\t\t\t\t\t\t\t<view>{{ address.address }} {{ address.detail }}</view>\n\t\t\t\t\t\t\t<text>{{ address.realName }} {{ address.phone }}</text>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t\t<view v-else>请选择收货地址</view>\n\t\t\t\t\t\t<template #right>\n\t\t\t\t\t\t\t<uv-icon name=\"arrow-right\" @click=\"chooseAddress\"></uv-icon>\n\t\t\t\t\t\t</template>\n\t\t\t\t</uv-form-item>\n\t\t\t</uv-form>\n\t\t</view>\n\t\t<view class=\"bg-white p-2 mt-2\">\n\t\t\t<view class=\"mb-5\">积分商城</view>\n\t\t\t<view class=\"flex justify-between\">\n\t\t\t\t<view class=\"flex\">\n\t\t\t\t\t<image :src=\"product.image\" mode=\"aspectFill\" class=\"image\"></image>\n\t\t\t\t\t<text class=\"font-size-medium\">{{ product.title }}</text>\n\t\t\t\t</view>\n\t\t\t\t<view class=\"flex flex-column\">\n\t\t\t\t\t<text class=\"font-size-medium\">{{ product.score }}积分</text>\n\t\t\t\t\t<text class=\"font-size-medium text-color-assist mt-2 text-right\">×1</text>\n\t\t\t\t</view>\n\t\t\t</view>\n\t\t\t<view class=\"flex justify-between mt-5\">\n\t\t\t\t<text class=\"font-size-medium\">积分抵扣</text>\n\t\t\t\t<text class=\"font-size-medium\">{{ product.score }}</text>\n\t\t\t</view>\n\t\t\t<view class=\"text-right font-size-lg mt-5 text-color-assist\">合计:<text class=\"font-size-medium text-color-base \">¥{{ product.score }}</text></view>\n\t\t</view>\n\t</view>\n\t<view class=\"fixed-bottom flex justify-between align-center bg-white \">\n\t\t<view class=\"mx-2 ont-weight-light\">合计:<text class=\"text-danger\">￥{{ product.score }}积分</text></view>\n\t\t<view><uv-button type=\"warning\" color=\"#ffcc00\" :customStyle=\"customStyle\" size=\"large\" text=\"立即兑换\" @click=\"submit\"></uv-button></view>\n\t</view>\n\t<uv-toast ref=\"uToast\" />\n</template>\n\n<script setup>\nimport {\r\n  ref,\n  computed,\n  getCurrentInstance\r\n} from 'vue'\nimport { onReachBottom,onLoad,onPullDownRefresh} from '@dcloudio/uni-app'\nimport {\n  scoreShopExchange,\n  scoreShopDetail\r\n} from '@/api/score'\nimport cookie from '@/utils/cookie'\nimport { useMainStore } from '@/store/store'\nimport { storeToRefs } from 'pinia'\nconst { proxy } = getCurrentInstance();\nconst main = useMainStore()\nconst { address,isLogin} = storeToRefs(main)\n\nconst title = ref(\"确认订单\")\nconst product = ref({})\n\nconst customStyle = computed(() =>{\n\treturn {\n\t\t  paddingLeft:'60rpx',\n\t\t  paddingRight:'60rpx'\n\t\t}\n})\n\nonLoad(() => {\n\tconsole.log('address:',address.value)\n\tif(!isLogin.value) {\n\t\tuni.navigateTo({url: '/pages/components/pages/login/login'})\n\t}\n\tproduct.value = cookie.get('score_product')\n\t\n})\n\n// 选择地址\r\nconst chooseAddress = () => {\n\tuni.navigateTo({\n\t\turl: '/pages/components/pages/address/address?is_choose=true&scene=scoreShop'\n\t})\r\n}\n\n// 提交\r\nconst submit = async() => {\r\n\tif (!address.value.id) {\r\n\t\tproxy.$refs.uToast.show({\r\n\t\t\tmessage: '请选择收货地址',\r\n\t\t\ttype: 'warning',\r\n\t\t})\r\n\t\treturn\r\n\t}\r\n\tlet data = await scoreShopExchange({\r\n\t\tproductId: product.value.id,\r\n\t\taddressId: address.value.id,\r\n\t\tnum: 1\r\n\t})\n\r\n\tif (data) {\n\t\tcookie.remove('score_product')\r\n\t\tsetTimeout(function() {\n\t\t\tuni.navigateTo({\n\t\t\t\turl: '/pages/components/pages/scoreproduct/order'\n\t\t\t})\r\n\t\t}, 1000)\r\n\t}\r\n}\n\n</script>\n\n<style lang=\"scss\" scoped>\n\t.image {\n\t\twidth: 160rpx;\n\t\theight: 160rpx;\n\t\tmargin-right: 30rpx;\n\t\tborder-radius: 8rpx;\n\t}\n</style>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/pages/components/pages/scoreproduct/detail.vue",
    "content": "<template>\n\t<!-- #ifdef MP-WEIXIN -->\n\t<uv-navbar\n\t  :fixed=\"false\"\n\t  :title=\"title\"\n\t  left-arrow\n\t  @leftClick=\"$onClickLeft\"\n\t/>\n\t<!-- #endif -->\r\n\t<view class=\"container\">\r\n\t\t<view class=\"carousel\">\r\n\t\t\t<swiper indicator-dots circular=true duration=\"400\">\r\n\t\t\t\t<swiper-item class=\"swiper-item\" v-for=\"(item,index) in product.images\" :key=\"index\">\r\n\t\t\t\t\t<view class=\"image-wrapper\">\r\n\t\t\t\t\t\t<image :src=\"item\" class=\"loaded\" @click=\"previewImage(index)\" mode=\"aspectFill\"></image>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</swiper-item>\r\n\t\t\t</swiper>\r\n\t\t</view>\r\n\r\n\t\t<view class=\"introduce-section\" v-if=\"product.id\">\r\n\t\t\t<text class=\"title\">{{product.title}}</text>\r\n\t\t\t<view class=\"price-box bot-row\">\r\n\t\t\t\t<text>积分： {{product.score}}</text>\r\n\t\t\t\t<text>销量: {{product.sales}}</text>\r\n\t\t\t\t<text>库存: {{product.stock}}</text>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\r\n\t\t<view class=\"detail-desc\" v-if=\"product.desc\">\r\n\t\t\t<rich-text :nodes=\"product.desc\"></rich-text>\r\n\t\t</view>\r\n\r\n\t\t<!-- 底部操作菜单 -->\r\n\t\t<view class=\"fixed-bottom px-2\">\r\n\t\t\t<button class=\"btn\" @click=\"confirm\">立即兑换</button>\r\n\t\t</view>\r\n\r\n\t\t<uv-toast ref=\"uToast\" />\r\n\t</view>\r\n</template>\r\n\r\n<script setup>\nimport {\r\n  ref,\n  computed,\n  getCurrentInstance\r\n} from 'vue'\nimport { onReachBottom,onLoad,onPullDownRefresh} from '@dcloudio/uni-app'\nimport {\n  scoreShopExchange,\n  scoreShopDetail\r\n} from '@/api/score'\nimport cookie from '@/utils/cookie'\nconst { proxy } = getCurrentInstance();\n\nconst buttonText = ref(\"立即兑换\")\nconst specClass = ref('none')\nconst product = ref({})\nconst id = ref(false)\nconst form = ref( \n\t{\r\n\t\taddress: {\r\n\t\t\tid: 0,\r\n\t\t\tdoor_number: \"\",\r\n\t\t\tname: \"请选择收货地址\",\r\n\t\t\taddress: \"\",\r\n\t\t\tmobile: \"\",\r\n\t\t},\r\n\t\tnum: 1 // 兑换数量\r\n\t}\n)\nconst lock = ref(false)\n\nonPullDownRefresh(() => {\n\tgetDetail(id.value);\n})\n\nonLoad((options) => {\r\n   id.value = options.id;\n   getDetail(options.id);\r\n})\n\n\r\n// 选择地址\r\nconst chooseAddress = () => {\n\tuni.navigateTo({\n\t\turl: '/pages/components/pages/address/address?is_choose=true&scene=scoreShop'\n\t})\r\n}\n\nconst confirm = () => {\n\tcookie.set('score_product',product.value)\n\tuni.navigateTo({\n\t\turl: '/pages/components/pages/scoreproduct/confirm'\n\t})\n}\r\n// 该表购买数量\r\nconst valChange = (e) => {\r\n\tform.value.num = e.value\r\n}\r\n// 获取商品详情\r\nconst  getDetail = async(id, flash_id) => {\r\n\tlet data = await scoreShopDetail({\r\n\t\tid: id\r\n\t});\r\n\tuni.stopPullDownRefresh();\r\n\tproduct.value = data;\r\n}\r\n//规格弹窗开关\r\nconst toggleSpec = () => {\r\n\tif (specClass.value === 'show') {\r\n\t\tspecClass.value = 'hide';\r\n\t\tsetTimeout(() => {\r\n\t\t\tspecClass.value = 'none';\r\n\t\t}, 250);\r\n\t} else if (specClass.value === 'none') {\r\n\t\tspecClass.value = 'show';\r\n\t}\r\n}\r\n//stopPrevent() {},\r\n// 查看图片\r\nconst previewImage = (index) => {\r\n\tuni.previewImage({\r\n\t\tcurrent: product.value.images_text[index],\r\n\t\turls: product.value.images_text,\r\n\t\tindicator: \"number\",\r\n\t\tloop: true\r\n\t})\r\n}\n\n\r\n\r\n</script>\r\n\r\n<style lang='scss'>\r\n\t.icon-you {\r\n\t\tcolor: #888;\r\n\t}\r\n\r\n\t.carousel {\r\n\t\theight: 722upx;\r\n\t\tposition: relative;\r\n\r\n\t\tswiper {\r\n\t\t\theight: 100%;\r\n\t\t}\r\n\r\n\t\t.image-wrapper {\r\n\t\t\twidth: 100%;\r\n\t\t\theight: 100%;\r\n\t\t}\r\n\r\n\t\t.swiper-item {\r\n\t\t\tdisplay: flex;\r\n\t\t\tjustify-content: center;\r\n\t\t\talign-content: center;\r\n\t\t\theight: 750upx;\r\n\t\t\toverflow: hidden;\r\n\r\n\t\t\timage {\r\n\t\t\t\twidth: 100%;\r\n\t\t\t\theight: 100%;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t}\r\n\r\n\t/* 标题简介 */\r\n\t.introduce-section {\r\n\t\tbackground: #fff;\r\n\t\tpadding: 20upx 30upx;\r\n\r\n\t\t.title {\r\n\t\t\tfont-size: 32upx;\r\n\t\t\tcolor: #555555;\r\n\t\t\theight: 50upx;\r\n\t\t\tline-height: 50upx;\r\n\t\t}\r\n\r\n\t\t.price-box {\r\n\t\t\tdisplay: flex;\r\n\t\t\talign-items: baseline;\r\n\t\t\theight: 64upx;\r\n\t\t\tpadding: 10upx 0;\r\n\t\t\tfont-size: 30rpx;\r\n\t\t\tcolor: #5A5B5C;\r\n\t\t}\r\n\r\n\t\t.price {\r\n\t\t\tfont-size: 35rpx;\r\n\t\t}\r\n\r\n\t\t.bot-row {\r\n\t\t\tdisplay: flex;\r\n\t\t\talign-items: center;\r\n\r\n\t\t\ttext {\r\n\t\t\t\tflex: 1;\r\n\t\t\t}\r\n\t\t}\r\n\t}\n\t\n\t.btn {\n\t\theight: 100rpx;\n\t\tline-height: 100rpx;\n\t\tborder-radius: 100rpx;\n\t\tbackground: #ffcc00;\n\t\tfont-size: 40rpx;\n\t\tcolor: #fff;\n\t\tmargin: 30upx auto 20upx;\n\t\t\n\t\n\t}\r\n\r\n\t/*  详情 */\r\n\t.detail-desc {\r\n\t\tbackground: #fff;\r\n\t\tmargin-top: 16upx;\r\n\t\tmargin-bottom: 200rpx;\n\t\tpadding: 20rpx;\r\n\t}\r\n\r\n\r\n\t\n\n</style>\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/pages/components/pages/scoreproduct/list.vue",
    "content": "<template>\n\t<!-- #ifdef MP-WEIXIN -->\n\t<uv-navbar\n\t  :fixed=\"false\"\n\t  :title=\"title\"\n\t  left-arrow\n\t  @leftClick=\"$onClickLeft\"\n\t/>\n\t<!-- #endif -->\r\n\t<view>\r\n\t\t<!--商品区-->\r\n\t\t<uv-waterfall v-model=\"list\" :add-time=\"0\" ref=\"uWaterfall\" @changeList=\"changeList\" :left-gap=\"10\"\n\t\t\t:right-gap=\"10\"\n\t\t\t:column-gap=\"1\">\r\n\t\t\t<template v-slot:list1>\r\n\t\t\t\t<view class=\"demo-warter\" v-for=\"(item, index) in list1\" :key=\"index\" @click=\"goDetail(item)\">\n\t\t\t\t\t<image :src=\"item.image\" mode=\"widthFix\" style=\"width: 300rpx;\"></image>\r\n\t\t\t\t\t<view class=\"demo-title\">\r\n\t\t\t\t\t\t{{item.title}}\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"demo-price\">\r\n\t\t\t\t\t\t消耗积分:{{item.score}}\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</template>\r\n\t\t\t<template v-slot:list2>\r\n\t\t\t\t<view class=\"demo-warter\" v-for=\"(item, index) in list2\" :key=\"index\" @click=\"goDetail(item)\">\n\t\t\t\t\t<image :src=\"item.image\" mode=\"widthFix\" style=\"width: 300rpx;\"></image>\n\t\t\t\t\t<view class=\"demo-title\">\r\n\t\t\t\t\t\t{{item.title}}\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"demo-price\">\r\n\t\t\t\t\t\t消耗积分:{{item.score}}\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</template>\r\n\t\t</uv-waterfall>\r\n\t\t<uv-load-more v-if=\"list.length > 0\" :status=\"status\"></uv-load-more>\r\n\t\t<uv-empty v-if=\"list.length == 0\" mode=\"list\"></uv-empty>\r\n\t</view>\r\n</template>\r\n\r\n<script setup>\nimport {\r\n  ref,\n  computed,\n  getCurrentInstance\r\n} from 'vue'\nimport { onReachBottom,onShow,onPullDownRefresh} from '@dcloudio/uni-app'\nimport {\n  scoreShopIndex\r\n} from '@/api/score'\nconst { proxy } = getCurrentInstance();\n\nconst title = ref('积分商品')\nconst list = ref([])\nconst page = ref(1)\nconst pagesize = ref(10)\nconst status = ref('loadmore')\nconst list1 = ref([])\nconst list2 = ref([])\n\nconst uWaterfall = ref()\n\nlet currentInstance = getCurrentInstance()\n\nonShow(() => {\n\tgetProduct()\n})\n\nonReachBottom(async() => {\n\tif (status.value == 'loading') {\n\t\treturn;\n\t}\n\tpage.value++\n\tstatus.value = 'loading'; \n\tlet data = await scoreShopIndex({\n\t\tpage: page.value,\n\t\tpagesize: pagesize.value\n\t});\n\tif (data) {\n\t\tlist.value = list.value.concat(data);\n\t\tif (data.length == 0) {\n\t\t\tpage.value--;\n\t\t\tstatus.value = 'nomore';\n\t\t} else if (data.length < pagesize.value) {\n\t\t\tstatus.value = 'nomore';\n\t\t} else {\n\t\t\tstatus.value = 'loadmore';\n\t\t}\n\t} else {\n\t\tstatus.value = 'loadmore';\n\t}\n})\n\nonPullDownRefresh(() => {\n\tpage.value = 1;\r\n\tgetProduct()\n})\n\nconst changeList = (e) => {\n\tconsole.log('e:',e)\n\tif(e.name == 'list1'){\n\t\tlist1.value.push(e.value)\n\t}else{\n\t\tlist2.value.push(e.value)\n\t}\n\t\n}\nconst goDetail = (item) => {\n\tuni.navigateTo({\n\t\turl: '/pages/components/pages/scoreproduct/detail?id=' + item.id\n\t})\r\n}\r\nconst  getProduct = async() => {\n\tstatus.value = 'loading';\r\n\t//proxy.$refs.uWaterfall.clear();\n\r\n\tlet data = await scoreShopIndex({\r\n\t\tpage: page.value,\n\t\tpagesize: pagesize.value\r\n\t});\n\tconsole.log('data:',data)\r\n\tuni.stopPullDownRefresh();\r\n\tif (data) {\r\n\t\tlist.value = data;\n\t\tconsole.log('data2:',list.value)\n\t\tif (data.length < pagesize.value) {\n\t\t\tstatus.value = 'nomore';\n\t\t}\r\n\t} else {\n\t\tstatus.value = 'nomore';\n\t}\r\n}\n\r\n\r\n</script>\r\n\r\n<style lang=\"scss\">\r\n\t.search {\r\n\t\tmargin: 10rpx !important;\r\n\t}\r\n\r\n\t.demo-warter {\r\n\t\tborder-radius: 8px;\r\n\t\tmargin: 5px;\r\n\t\tbackground-color: #ffcc00;\r\n\t\tpadding: 8px;\r\n\t\tposition: relative;\r\n\t}\r\n\r\n\t.demo-title {\r\n\t\tfont-size: 30rpx;\r\n\t\tmargin-top: 5px;\r\n\t\tcolor: #ffffff;\r\n\t}\r\n\r\n\t.demo-tag {\r\n\t\tdisplay: flex;\r\n\t\tmargin-top: 5px;\r\n\t}\r\n\r\n\t.demo-tag-owner {\r\n\t\tbackground-color: $uv-error;\r\n\t\tcolor: #FFFFFF;\r\n\t\tdisplay: flex;\r\n\t\talign-items: center;\r\n\t\tpadding: 4rpx 14rpx;\r\n\t\tborder-radius: 50rpx;\r\n\t\tfont-size: 20rpx;\r\n\t\tline-height: 1;\r\n\t}\r\n\r\n\t.demo-tag-text {\r\n\t\tborder: 1px solid $uv-primary;\r\n\t\tcolor: $uv-primary;\r\n\t\tmargin-left: 10px;\r\n\t\tborder-radius: 50rpx;\r\n\t\tline-height: 1;\r\n\t\tpadding: 4rpx 14rpx;\r\n\t\tdisplay: flex;\r\n\t\talign-items: center;\r\n\t\tborder-radius: 50rpx;\r\n\t\tfont-size: 20rpx;\r\n\t}\r\n\r\n\t.demo-price {\r\n\t\tfont-size: 30rpx;\r\n\t\tcolor: $bg-color;\r\n\t\tmargin-top: 5px;\r\n\t}\r\n\r\n\t.demo-shop {\r\n\t\tfont-size: 32rpx;\r\n\t\tcolor: #cdad73;\r\n\t\tmargin-top: 5px;\r\n\t}\r\n\r\n\t.page {\r\n\t\tpadding: 10px 0;\r\n\r\n\t\t.demo-layout {\r\n\t\t\ttext-align: center;\r\n\t\t\tbackground-color: #c6caca;\r\n\t\t\tborder-radius: 20rpx;\r\n\t\t\tmargin: 5px 0;\r\n\t\t\tpadding: 3px;\r\n\t\t}\r\n\r\n\t\t.select {\r\n\t\t\tbackground-color: #eea13c;\r\n\t\t\tcolor: #ffffff;\r\n\t\t}\r\n\t}\r\n</style>\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/pages/components/pages/scoreproduct/order.vue",
    "content": "<template>\n\t<!-- #ifdef MP-WEIXIN -->\n\t<uv-navbar\n\t  :fixed=\"false\"\n\t  :title=\"title\"\n\t  left-arrow\n\t  @leftClick=\"$onClickLeft\"\n\t/>\n\t<!-- #endif -->\r\n\t<view class=\"container\">\n\t\t<view class=\"bg-white\">\n\t\t\t<uv-tabs :list=\"tabList\" :current=\"current\" @change=\"change\" keyName=\"name\" :scrollable=\"false\"></uv-tabs>\n\t\t</view>\r\n\t\t<view class=\"orders-list d-flex flex-column w-100\" style=\"padding: 20rpx; padding-bottom: 0;\">\r\n\t\t\t<view class=\"order-item\" v-for=\"(item, index) in orders\" :key=\"index\" style=\"margin-bottom: 30rpx;\">\r\n\t\t\t\t<list-cell :hover=\"false\">\r\n\t\t\t\t\t<view class=\"w-100 d-flex align-items-center\">\r\n\t\t\t\t\t\t<view class=\"flex-fill d-flex flex-column\">\r\n\t\t\t\t\t\t\t<view class=\"font-size-sm text-color-assist\">订单编号：{{ item.orderId }}</view>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<view class=\"font-size-lg text-color-primary\">\r\n\t\t\t\t\t\t\t{{ item.statusText }}\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</list-cell>\r\n\t\t\t\t<list-cell :hover=\"false\" last>\r\n\t\t\t\t\t<view class=\"w-100 d-flex flex-column\">\n\t\t\t\t\t\t<view class=\"flex \">\n\t\t\t\t\t\t\t<image :src=\"item.product.image\" mode=\"aspectFill\" class=\"image\"></image>\n\t\t\t\t\t\t\t<view>\r\n\t\t\t\t\t\t\t\t<view class=\"w-100 text-truncate font-size-lg text-color-base\" style=\"margin-bottom: 20rpx;\">\r\n\t\t\t\t\t\t\t\t\t{{ item.product.title }}\r\n\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t<view class=\"font-size-sm mt-2\">×{{ item.product.number }}  {{ item.product.score }}积分</view>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<view class=\"d-flex justify-content-between align-items-center mt-3\" >\r\n\t\t\t\t\t\t\t<view class=\"font-size-sm text-color-assist\">\r\n\t\t\t\t\t\t\t\t{{formatDateTime(item.createTime) }}\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\t<view class=\"d-flex font-size-sm text-color-base align-items-center\">\r\n\t\t\t\t\t\t\t\t<view style=\"margin-right: 10rpx;\">共{{item.number }}件商品，消耗积分</view>\r\n\t\t\t\t\t\t\t\t<view class=\"font-size-lg\"> {{ item.totalScore }}</view>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<view class=\"d-flex align-items-center justify-content-end mt-3\">\r\n\t\t\t\t\t\t\t<view>\r\n\t\t\t\t\t\t\t\t<button v-if=\"item.havePaid > 0 && item.haveDelivered == 1 && item.haveReceived == 0\"\r\n\t\t\t\t\t\t\t\t\tclass=\"left-margin\" type=\"primary\" plain size=\"mini\"\r\n\t\t\t\t\t\t\t\t\t@tap.stop=\"receive(item)\">确认收到货</button>\n\t\t\t\t\t\t\t\t<button class=\"left-margin\"  plain size=\"mini\" @tap=\"detail(item.id)\">订单详情</button>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</list-cell>\r\n\t\t\t</view>\r\n\t\t</view>\n\t\t<uv-empty v-if=\"orders.length == 0\" mode=\"order\"></uv-empty>\r\n\t</view>\r\n\t<uv-toast ref=\"uToast\" />\n</template>\r\n\r\n<script setup>\nimport {\r\n  ref,\n  computed,\n  getCurrentInstance\r\n} from 'vue'\nimport { useMainStore } from '@/store/store'\nimport { storeToRefs } from 'pinia'\nimport { onLoad,onPullDownRefresh,onReachBottom} from '@dcloudio/uni-app'\nimport { formatDateTime,kmUnit } from '@/utils/util'\nimport {\n  scoreShopOrder,\n  scoreShopReceive\r\n} from '@/api/score'\nconst main = useMainStore()\nconst { proxy } = getCurrentInstance();\nconst { isLogin } = storeToRefs(main)\nconst title = ref('我的积分订单')\n\nconst type = ref(-1)\nconst page = ref(1)\nconst pageSize = ref(10)\nconst orders = ref([])\nconst tabList = ref([{\n\t\t\ttype: -1,\n\t\t\tname: '全部',\n\t\t}, {\n\t\t\ttype: 0,\n\t\t\tname: '待发货',\n\t\t}, {\n\t\t\ttype: 1,\n\t\t\tname: '待收货'\n\t\t}, {\n\t\t\ttype: 2,\n\t\t\tname: '已完成'\n\t\t}]\n)\r\nonLoad(() => {\n\tif (!isLogin.value) {\n\t\tuni.navigateTo({\n\t\t\turl: '/pages/components/pages/login/login'\n\t\t})\n\t}\n\tgetOrders(false)\n})\n\nonReachBottom(() => {\n\tgetOrders(false)\n})\nonPullDownRefresh(() => {\n\t getOrders(false)\n})\n\n// tab栏切换\nconst change = (e) => {\n\ttype.value = e.type\n\tgetOrders(true)\n}\n\nconst  getOrders = async(isRefresh = false) => {\r\n\tuni.showLoading({\r\n\t\ttitle: '加载中'\r\n\t})\r\n\tif (isRefresh) {\r\n\t\torders.value = []\r\n\t\tpage.value = 1\r\n\t}\r\n\tlet data = await scoreShopOrder({\r\n\t\tpage: page.value,\r\n\t\tpagesize: pageSize.value,\n\t\ttype: type.value\r\n\t});\r\n\tif (data.length > 0) {\r\n\t\torders.value = orders.value.concat(data)\r\n\t\tpage.value += 1\r\n\t}\r\n\tuni.stopPullDownRefresh();\r\n\tuni.hideLoading()\r\n}\r\nconst detail = (id) => {\r\n\tuni.navigateTo({\r\n\t\turl: '/pages/components/pages/scoreproduct/orderDetail?id=' + id\r\n\t})\r\n}\r\n// 确认收到货\r\nconst  receive = async(order) => {\r\n\tlet data = await scoreShopReceive({\r\n\t\tid: order.id\r\n\t});\r\n\tif (data) {\r\n\t\tproxy.$refs.uToast.show({\n\t\t\tmessage: '已签收',\n\t\t\ttype: 'success',\n\t\t})\r\n\t\tsetTimeout(function() {\r\n\t\t\tgetOrders(true)\r\n\t\t}, 1000);\r\n\t}\r\n}\n\r\n\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t.left-margin {\r\n\t\tmargin-left: 10rpx;\r\n\t}\n\t.image {\n\t\twidth: 160rpx;\n\t\theight: 160rpx;\n\t\tmargin-right: 30rpx;\n\t\tborder-radius: 8rpx;\n\t}\r\n</style>\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/pages/components/pages/scoreproduct/orderDetail.vue",
    "content": "<template>\n\t<!-- #ifdef MP-WEIXIN -->\n\t<uv-navbar\n\t  :fixed=\"false\"\n\t  :title=\"title\"\n\t  left-arrow\n\t  @leftClick=\"$onClickLeft\"\n\t/>\n\t<!-- #endif -->\n\t<view class=\"container\" style=\"padding:20rpx;\">\n\t\t<view style=\"padding-bottom: 100rpx;\">\n\t\t\t<view class=\"bg-white\">\n\t\t\t\t<view class=\"section\">\n\t\t\t\t\t\n\t\t\t\t\t<list-cell :hover=\"false\" padding=\"50rpx 30rpx\">\n\t\t\t\t\t\t<view class=\"w-100 d-flex flex-column position-relative\" style=\"margin-bottom: -40rpx;\">\n\t\t\t\t\t\t\t<view class=\"w-100 d-flex align-items-center mb-40\">\n\t\t\t\t\t\t\t\t<view class=\"d-flex flex-column w-60 overflow-hidden\">\n\t\t\t\t\t\t\t\t\t<image class=\"product-image\" :src=\"order.product.image\" mode=\"aspectFill\"></image>\n\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t<view class=\"d-flex flex-column w-60 overflow-hidden\">\n\t\t\t\t\t\t\t\t\t<view class=\"font-size-lg text-color-base mb-10 text-truncate\">{{ order.product.title }}</view>\n\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t<view class=\"d-flex w-40 align-items-center justify-content-between pl-30\">\n\t\t\t\t\t\t\t\t\t<view class=\"font-size-base text-color-base\">x{{ order.number }}</view>\n\t\t\t\t\t\t\t\t\t<view class=\"font-size-base text-color-base font-weight-bold\">{{ order.score }}</view>\n\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</list-cell>\n\t\t\t\t</view>\n\t\t\t\t<view class=\"section\">\n\t\t\t\t\t<list-cell :hover=\"false\" padding=\"50rpx 30rpx\">\n\t\t\t\t\t\t<view class=\"w-100 d-flex flex-column\">\n\t\t\t\t\n\t\t\t\t\t\t\t<view class=\"pay-cell\">\n\t\t\t\t\t\t\t\t<view>消耗积分</view>\n\t\t\t\t\t\t\t\t<view class=\"font-weight-bold\">{{ order.totalScore }}</view>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</list-cell>\n\t\t\t\t</view>\n\t\t\t\t<view class=\"section\">\n\t\t\t\t\t<list-cell :hover=\"false\" padding=\"50rpx 30rpx\">\n\t\t\t\t\t\t<view class=\"w-100 d-flex flex-column\">\n\t\t\t\t\t\t\t<view class=\"pay-cell\">\n\t\t\t\t\t\t\t\t<view>订单状态</view>\n\t\t\t\t\t\t\t\t<view class=\"font-weight-bold\">{{ order.statusText }}</view>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t<view class=\"pay-cell\">\n\t\t\t\t\t\t\t\t<view>下单时间</view>\n\t\t\t\t\t\t\t\t<view class=\"font-weight-bold\">{{ formatDateTime(order.createTime) }}</view>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t<view class=\"pay-cell\">\n\t\t\t\t\t\t\t\t<view>订单号</view>\n\t\t\t\t\t\t\t\t<view class=\"font-weight-bold\">{{ order.orderId }}</view>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</list-cell>\n\t\t\t\t\t\n\t\t\t\t\t<list-cell :hover=\"false\">\n\t\t\t\t\t\t<view class=\"w-100 d-flex align-items-center\">\n\t\t\t\t\t\t\t<view class=\"pay-cell\">\n\t\t\t\t\t\t\t\t<view>收货地址</view>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t<view class=\"d-flex flex-column\">\n\t\t\t\t\t\t\t\t<view class=\"w-100 d-flex align-items-center overflow-hidden\">\n\t\t\t\t\t\t\t\t\t<view class=\"font-size-sm text-color-assist\">{{ order.customerAddress + ' ' + order.customerName + ' ' + order.customerPhone }}</view>\n\t\t\t\t\t\t\t\t</view> \n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t</view> \n\t\t\t\t\t</list-cell>\n\t\t\t\t\t\n\t\t\t\t\t<list-cell :hover=\"false\" padding=\"50rpx 30rpx\" v-if=\"order.expressCompany\">\n\t\t\t\t\t\t<view class=\"w-100 d-flex flex-column\">\n\t\t\t\t\t\t\t<view class=\"pay-cell\">\n\t\t\t\t\t\t\t\t<view>快递公司</view>\n\t\t\t\t\t\t\t\t<view class=\"font-weight-bold\">{{order.expressCompany }}</view>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t<view class=\"pay-cell\">\n\t\t\t\t\t\t\t\t<view>快递单号</view>\n\t\t\t\t\t\t\t\t<view class=\"font-weight-bold\">{{order.expressNumber }}<text class=\"copy\" @click=\"copy()\">复制</text></view>\n\t\t\t\t\t\t\t\t<!-- <text class=\"copy\" @click=\"copy()\">复制</text> -->\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</list-cell>\n\t\t\t\t\t\n\n\t\t\t\t\t<uv-steps current=\"0\" direction=\"column\" dot>\n\t\t\t\t\t\t<uv-steps-item :title=\"activity.acceptStation\" :desc=\"activity.acceptTime\" :key=\"index\" v-for=\"(activity, index) in expresssn\">\n\t\t\t\t\t\t</uv-steps-item>\n\t\t\t\t\t</uv-steps>\n\t\t\t\t</view>\n\t\t\t</view>\n\t\t</view>\n\t\t<uv-toast ref=\"uToast\" />\n\t</view>\n\t<view class=\"fixed-bottom flex justify-end bg-white p-2\" v-if=\"order.havePaid > 0\">\n\t\t<view class=\"mr-1\" v-if=\"order.haveDelivered ==0 && order.haveReceived == 0\"><uv-button type=\"success\"  :plain=\"true\" size=\"small\" text=\"确认收货\" @click=\"receive(order)\"></uv-button></view>\n\t\t<view class=\"mr-1\"><uv-button type=\"error\" :plain=\"true\" size=\"small\" text=\"查看物流\" @click=\"getExpresssn()\"></uv-button></view>\n\t</view>\n</template>\n\n<script setup>\nimport {\r\n  ref,\n  computed,\n  getCurrentInstance\r\n} from 'vue'\nimport { useMainStore } from '@/store/store'\nimport { storeToRefs } from 'pinia'\nimport { onLoad,onPullDownRefresh,onReachBottom} from '@dcloudio/uni-app'\nimport { formatDateTime,kmUnit } from '@/utils/util'\nimport {\n  scoreShopOrderDetail,\n  scoreShopReceive,\n  getLogistic\r\n} from '@/api/score'\nconst main = useMainStore()\nconst { proxy } = getCurrentInstance();\nconst { isLogin } = storeToRefs(main)\nconst title = ref('积分订详情')\n\nconst order = ref({\n\tproduct: {title:'',image:''}\n})\nconst id = ref(0)\nconst expresssn = ref([])\n\nonLoad((option) => {\n\tid.value = option.id\n\tdetail(option.id)\n})\nonPullDownRefresh(() => {\n\t detail(id.value)\n})\n\nconst  detail = async(id) =>{\n\tlet data = await scoreShopOrderDetail({id:id});\n\tuni.stopPullDownRefresh();\n\tif (data) {\n\t\torder.value = data;\n\t}\n}\n\nconst getExpresssn = async() => {\n\tif(order.value.haveDelivered == 0){\n\t\tproxy.$refs.uToast.show({\n\t\t\tmessage: '还未发货，暂无物流信息',\n\t\t\ttype: 'error',\n\t\t})\n\t\treturn\t\n\t}\n\t\n\tlet data = await getLogistic({shipperCode:order.value.expressSn,logisticCode:order.value.expressNumber});\n\tif (data.success == \"false\") {\n\t\tuni.showToast({\n\t\t\ttitle: res.reason,\n\t\t\ticon: 'none'\n\t\t})\n\t\treturn\t\n\t}\n\texpresssn.value = data.traces\n}\n\nconst copy = () => {\n\tuni.setClipboardData({\n\t\tdata: order.value.expressNumber\n\t})\n}\n\n// 确认收到货\r\nconst  receive = async(order) => {\r\n\tlet data = await scoreShopReceive({\r\n\t\tid: order.id\r\n\t});\r\n\tif (data) {\r\n\t\tproxy.$refs.uToast.show({\n\t\t\tmessage: '已签收',\n\t\t\ttype: 'success',\n\t\t})\r\n\t\tsetTimeout(function() {\r\n\t\t\tdetail(order.id)\r\n\t\t}, 1000);\r\n\t}\r\n}\n\n\n\n</script>\n\n<style lang=\"scss\" scoped>\n.copy {\n\tcolor: #1296db;\n\tmargin-left: 10rpx;\n}\n@mixin arch {\n\tcontent: \"\";\n\tposition: absolute;\n\tbackground-color: $bg-color;\n\twidth: 30rpx;\n\theight: 30rpx;\n\tbottom: -15rpx;\n\tz-index: 10;\n\tborder-radius: 100%;\n}\n\n.section {\n\tposition: relative;\n\t\n\t&::before {\n\t\t@include arch;\n\t\tleft: -15rpx;\n\t}\n\t\n\t&::after {\n\t\t@include arch;\n\t\tright: -15rpx;\n\t}\n}\n\n.pay-cell {\n\twidth: 100%;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-between;\n\tfont-size: $font-size-base;\n\tcolor: $text-color-base;\n\tmargin-bottom: 40rpx;\n\n\t&:nth-last-child(1) {\n\t\tmargin-bottom: 0;\n\t}\n}\n\n.invote-box {\n\tposition: absolute;\n\twidth: 100%;\n\tleft: 0;\n\ttop: 0;\n\tdisplay: flex;\n\tjustify-content: center;\n\talign-items: center;\n\t\n\timage {\n\t\twidth: 30rpx;\n\t\theight: 30rpx;\n\t}\n}\n\n.btn-box {\n\tbackground-color: #ffffff;\n\tposition: fixed;\n\tbottom: 0;\n\tleft: 0;\n\tright: 0;\n\theight: 120rpx;\n\tbox-shadow: 0 0 20rpx rgba($color: #000000, $alpha: 0.1);\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: space-evenly;\n\tz-index: 11;\n\t\n\t.item {\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tpadding: 20rpx 10rpx;\n\t\tflex: 1;\n\t\tflex-shrink: 0;\n\t\t\n\t\tbutton {\n\t\t\twidth: 100%;\n\t\t\tborder-radius: 50rem !important;\n\t\t\theight: 80rpx;\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tjustify-content: center;\n\t\t\tpadding: 0;\n\t\t}\n\t}\n}\n.product-image {\n\twidth: 140rpx;\n\theight: 140rpx;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/pages/components/pages/shop/shop.vue",
    "content": "<template>\n\t<uv-navbar\n\t  :fixed=\"false\"\n\t  :title=\"title\"\n\t  left-arrow\n\t  @leftClick=\"$onClickLeft\"\n\t/>\n\t<view>\n\t\t<view>\n\t\t\t<uv-search margin=\"30rpx\" v-model=\"keywork\" @custom=\"search(keywork)\"></uv-search>\n\t\t</view>\n\t\t<view v-for=\"(item,index) in list\" :key=\"index\">\n\t\t\t<uni-card @click=\"choice(item)\" :border=\"item.id == store.id\" :title=\"item.name\" :thumbnail=\"item.image\" :thumb-width=\"80\" :sub-title=\"item.status_text\">\n\t\t\t\t<view class=\"body\">\n\t\t\t\t\t<view class=\"body-left\">\n\t\t\t\t\t\t<view>距离您 {{kmUnit(item.dis)}}</view>\n\t\t\t\t\t\t<view v-if=\"item.distance > 0\">配送距离：{{item.distance + 'km '}} & 配送费：{{item.delivery_price}}</view>\n\t\t\t\t\t\t<view v-else>外卖不配送</view>\n\t\t\t\t\t\t<view>{{item.addressMap + ' ' + item.address}}</view>\n\t\t\t\t\t\t<view>营业时间 {{formatDateTime(item.startTime,'hh:mm')}} - {{formatDateTime(item.endTime,'hh:mm')}}</view>\n\t\t\t\t\t</view>\n\t\t\t\t\t<view class=\"body-right\">\n\t\t\t\t\t\t<uv-button @click=\"openLocation(item)\">导航</uv-button>\n\t\t\t\t\t\t<uv-button @click=\"call(item.mobile)\">致电</uv-button>\n\t\t\t\t\t</view>\n\t\t\t\t</view>\n\t\t\t</uni-card>\n\t\t</view>\n\t</view>\n</template>\n\n<script setup>\nimport {\r\n  ref\r\n} from 'vue'\nimport { useMainStore } from '@/store/store'\nimport { storeToRefs } from 'pinia'\nimport { onLoad,onShow ,onPullDownRefresh,onHide} from '@dcloudio/uni-app'\nimport { formatDateTime,kmUnit,prePage } from '@/utils/util'\nimport {\n  shopNearby,\n  menuGoods\r\n} from '@/api/goods'\nimport {\n  shopGetList\r\n} from '@/api/market'\nconst main = useMainStore()\nconst { store,location } = storeToRefs(main)\nconst title = ref('店铺')\nconst list = ref([])\nconst keywork = ref('')\nconst page = ref(1)\nconst pagesize = ref(10)\n\t\nonLoad(() => {\n\tgetShop();\n})\n\nconst getShop = async(keywork = '') => {\n\tlet data = await shopGetList({\n\t\tlat: location.value.latitude ? location.value.latitude : 0,\n\t\tlng: location.value.longitude ? location.value.longitude : 0,\n\t\tkw: keywork,\n\t\tshop_id: 0\n\t});\n\tif (data) {\n\t\t//console.log(data);\n\t\tif (page.value == 1) {\n\t\t\tlist.value = data;\n\t\t} else {\n\t\t\tfor(let i in data) {\n\t\t\t\tlist.value.push(data[i]);\n\t\t\t}\n\t\t}\n\t}\n}\n//打开定位\nconst openLocation = (shop) => {\n\t//console.log(shop);\n\tuni.openLocation({\n\t\tlatitude: parseFloat(shop.lat),\n\t\tlongitude: parseFloat(shop.lng),\n\t\tname:shop.name,\n\t\taddress: shop.addressMap + shop.address,\n\t\tfail: (res) => {\n\t\t\tconsole.log(res);\n\t\t}\n\t})\n}\n// 打电话\nconst call = (mobile) => {\n\tuni.makePhoneCall({\n\t\tphoneNumber:mobile\n\t})\n}\n// 搜索按钮\nconst search = (keywork) => {\n\tpage.value = 1;\n\tgetShop(keywork);\n}\n// 选中店铺\nconst choice = (shop) => {\n\tmain.SET_STORE(shop);\n\tuni.$emit('refreshMenu')\n\tuni.switchTab({ \n\t\turl:'/pages/menu/menu',\n\t\tsuccess(res) {\n\t\t},\n\t\tfail(res) {\n\t\t\tconsole.log(res);\n\t\t}\n\t});\n}\n\n\t\n</script>\n\n<style lang=\"scss\">\n\t.body {\n\t\t\n\t\t.body-left {\n\t\t\tdisplay: inline-block;\n\t\t\twidth: 77%;\n\t\t\tpadding-left: 6rpx;\n\t\t}\n\t\t.body-right {\n\t\t\tdisplay: inline-block;\n\t\t\twidth: 20%;\n\t\t}\n\t}\n\t\n\n</style>\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/pages/index/index.vue",
    "content": "<template>\r\n  <layout>\r\n    <uv-sticky\r\n      bg-color=\"#F5F5F5\"\r\n      offset-top=\"0\"\r\n      customNavHeight=\"0\"\r\n    >\r\n      <uv-navbar\r\n        :fixed=\"false\"\r\n        :safeAreaInsetTop=\"true\"\r\n        height=\"0\"\r\n        bgColor=\"transparent\"\r\n        leftIcon=\"\"\r\n      />\r\n      <!-- #ifndef MP -->\r\n      <blank size=\"10\"></blank>\r\n      <!-- #endif -->\r\n\r\n      <blank size=\"5\"></blank>\r\n    </uv-sticky>\r\n\r\n    <blank size=\"10\"></blank>\n\t<view class=\"container\">\n\t\t\t<view class=\"banner\">\n\t\t\t\t<uv-swiper class=\"bg\" height=\"300\" imgMode=\"aspectFill\" keyName=\"image\" :list=\"listAds\" indicatorMode=\"dot\" indicatorStyle=\"bottom\"></uv-swiper>\n\t\t\t\t<view class=\"intro\">\n\t\t\t\t\t<view class=\"greet\">您好，{{ isLogin ? member.nickname : '游客' }}</view>\n\t\t\t\t\t<view class=\"note\">java-springboot-意向点餐外卖系统</view>\n\t\t\t\t</view>\n\t\t\t</view>\n\t\t\t<view class=\"content\">\n\t\t\t\t<view class=\"flex justify-between yshop-menu\">\n\t\t\t\t\t<view class=\"flex flex-column align-center yshop-menu-item \"  @tap=\"takein\">\n\t\t\t\t\t\t<view><image src=\"/static/images/index002.png\" mode=\"aspectFit\" class=\"img-01\"></image></view>\n\t\t\t\t\t\t<view>自取</view>\n\t\t\t\t\t\t<view class=\"font-small text-light-black\">下单免排队</view>\n\t\t\t\t\t</view>\n\t\t\t\t\t<view class=\"flex flex-column align-center yshop-menu-item \"  @tap=\"takeout\">\n\t\t\t\t\t\t<view><image src=\"/static/images/index003.png\" mode=\"aspectFit\" class=\"img-01\"></image></view>\n\t\t\t\t\t\t<view>外卖</view>\n\t\t\t\t\t\t<view class=\"font-small text-light-black\">美食送到家</view>\n\t\t\t\t\t</view>\n\t\t\t\t</view>\n\t\t\t\t <view class=\"info\">\n\t\t\t\t\t<view class=\"integral_section\">\n\t\t\t\t\t\t<view class=\"top\">\n\t\t\t\t\t\t\t<text class=\"title\">我的卡券</text>\n\t\t\t\t\t\t\t<text class=\"value\">{{member.couponCount}}</text>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t\t<view class=\"bottom\">\n\t\t\t\t\t\t\t可抵扣商品价格哦\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</view>\n\t\t\t\t\t<view class=\"qrcode_section\" @tap=\"coupons\">\n\t\t\t\t\t\t去领个券\n\t\t\t\t<!-- \t\t<view class=\"iconfont iconarrow-right\"></view> -->\n\t\t\t\t\n\t\t\t\t\t</view>\n\t\t\t\t</view> \n\t\t\t\t<view class=\"info\">\n\t\t\t\t\t<view class=\"integral_section\" @tap=\"goScore\">\n\t\t\t\t\t\t<view class=\"top\">\n\t\t\t\t\t\t\t<text class=\"title\">积分商城</text>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t\t<view class=\"bottom\">\n\t\t\t\t\t\t\t进入积分商城兑换奈雪券及周边好礼\n\t\t\t\t\t\t<!-- \t<view class=\"iconfont iconarrow-right\"></view> -->\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</view>\n\t\t\t\t\t<view class=\"qrcode_section\" @tap=\"goScore\">\n\t\t\t\t\t\t<image src=\"/static/images/jifen.png\"></image>\n\t\t\t\t\t\t<text>逛一逛</text>\n\t\t\t\t\t</view>\n\t\t\t\t</view>\n\t\t\t\t\n\t\t\t</view>\n\t</view>\n  </layout>\r\n</template>\r\n\r\n<script setup>\r\nimport {\r\n  ref\r\n} from 'vue'\r\nimport { onLoad } from '@dcloudio/uni-app'\r\nimport {\n  menuAds\r\n} from '@/api/market'\nimport { storeToRefs } from 'pinia'\r\nimport { useMainStore } from '@/store/store'\n//\r\nconst main = useMainStore()\r\nconst { member,store, isLogin} = storeToRefs(main)\r\n//const store = ref(main.store)\nconst listAds = ref([])\n// const isLogin = ref(main.isLogin)\r\n\nconst handGetListAds = async () => {\n\tlet shop_id = store.id ? store.id : 0;\n\tlet data = await menuAds({\n\t\tshop_id: shop_id\n\t});\n\tif (data) {\n\t\tlistAds.value = data.list;\n\t\tconsole.log('listAds:',listAds.value)\n\t\tuni.setStorage({\n\t\t\t\tkey: 'isActive',\n\t\t\t\tdata: data.isActive\n\t\t\t});\n\t\tif(data.list.length > 0){\n\t\t\tuni.setStorage({\n\t\t\t\t\tkey: 'shopAd',\n\t\t\t\t\tdata: data.list[0].image\n\t\t\t });\n\t\t\t}\n\t\t}\n}\n\nconst takein = () => {\n\tmain.SET_ORDER_TYPE('takein')\n\tuni.switchTab({\n\t\turl: '/pages/menu/menu'\n\t})\n}\n\nconst takeout = () => {\n\tmain.SET_ORDER_TYPE('takeout')\n\tuni.switchTab({\n\t\turl: '/pages/menu/menu'\n\t}) \n}\n\nconst coupons = () => { \n\tconsole.log(\"--> % orderType:\\n\", main.orderType)\n\tconsole.log(\"--> % isLogin:\\n\", main.isLogin)\n\tif(!main.isLogin) {\n\t\tuni.navigateTo({url: '/pages/components/pages/login/login'})\n\t\treturn\n\t}\n\tuni.navigateTo({\n\t\turl: '/pages/components/pages/coupons/coupons'\n\t})\n}\n\nconst goScore = () => { \n\tuni.navigateTo({\n\t\turl: '/pages/components/pages/scoreproduct/list'\n\t})\n}\n\n\n\r\n\r\nonLoad(() => {\r\n // main.init()\n  handGetListAds()\r\n})\r\n\r\n</script>\r\n\r\n<style lang=\"scss\">\n.yshop-menu {\n\t//margin-bottom: 10rpx;\n\t// position: relative;\n\tmargin-top: 20rpx;\n}\n.yshop-menu-item {\n\tbackground-color: #ffffff;\n\twidth: 355rpx;\n\tpadding-bottom: 30rpx;\n}\n\t\n.img-01 {\n\twidth: 100px;\n\theight: 100rpx;\n\tmargin-top: 30rpx;\n}\n.img-02 {\n\twidth: 110px;\n\theight: 110rpx;\n\tmargin-top: 20rpx;\n}\n\t\n\t\r\n/* #ifdef H5 */\npage {\n\theight: auto;\n\tmin-height: 100%;\n}\n/* #endif */\npage {\n\t//background-color: #ffffff!important;\n}\n.banner {\n\tposition: relative;\n\twidth: 100%;\n\t//height: 300rpx;\n\t\n\t.bg {\n\t\twidth: 100%;\n\t\theight: 300rpx;\n\t}\n\t\n\t.intro {\n\t\tposition: absolute;\n\t\ttop: calc(50rpx + var(--status-bar-height));\n\t\tleft: 40rpx;\n\t\tcolor: #FFFFFF;\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\t\n\t\t.greet {\n\t\t\tfont-size: $font-size-lg;\n\t\t\tmargin-bottom: 10rpx;\n\t\t}\n\t\t\n\t\t.note {\n\t\t\tfont-size: $font-size-sm;\n\t\t}\n\t}\n}\n\n.content {\n\tpadding: 0 15rpx;\n}\n\n\n.info {\n\tposition: relative;\n\tmargin: 10rpx 0;\n\tborder-radius: 10rpx;\n\tbackground-color: #ffffff;\n\tbox-shadow: $box-shadow;\n\t//padding: 30rpx;\n\tdisplay: flex;\n\talign-items: center;\n\tjustify-content: center;\n\tpadding: 25rpx;\n\t\n\t.integral_section {\n\t\tflex: 1;\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\tjustify-content: center;\n\t\t\n\t\t.top {\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\t\n\t\t\t.title {\n\t\t\t\tcolor: $text-color-base;\n\t\t\t\tfont-size: $font-size-base;\n\t\t\t\tmargin-right: 10rpx;\n\t\t\t}\n\t\t\t.value {\n\t\t\t\tfont-size: 44rpx;\n\t\t\t\tfont-weight: bold;\n\t\t\t}\n\t\t}\n\t\t\n\t\t.bottom {\n\t\t\tfont-size: $font-size-sm;\n\t\t\tcolor: $text-color-assist;\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t}\n\t}\n\t\n\t.qrcode_section {\n\t\tcolor: $color-primary;\n\t\tdisplay: flex;\n\t\t//flex-direction: column;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\tfont-size: $font-size-sm;\n\t\t\n\t\timage {\n\t\t\twidth: 40rpx;\n\t\t\theight: 40rpx;\n\t\t\tmargin-bottom: 10rpx;\n\t\t}\n\t}\n}\n\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/pages/menu/menu.vue",
    "content": "<template>\n\t<layout>\n\t\t<uv-navbar\n\t\t  :fixed=\"false\"\n\t\t  :title=\"title\"\n\t\t  left-arrow\n\t\t  @leftClick=\"$onClickLeft\"\n\t\t/>\n\t\t\n\t\t<view class=\"container\" v-if=\"!loading\">\n\t\t\t<!-- <view>\n\t\t\t\t<image :src=\"shopAd\" mode=\"aspectFill\" class=\"w-100 \" style=\"height: 250rpx;\"></image>\n\t\t\t</view> -->\n\t\t\t<view style=\"height: 60rpx;background-color: #FFFFFF;\" v-if=\"store.notice\">\n\t\t\t\t\t<uv-notice-bar  :text=\"store.notice\"></uv-notice-bar>\n\t\t\t</view>\n\t\t<view class=\"main\">\n\t\t\t<view class=\"nav\">\n\t\t\t\t<view class=\"header\">\n\t\t\t\t\t<view class=\"mr-1\"><image :src=\"store.image\" style=\"width:80rpx ; height: 80rpx; \"></image></view>\n\t\t\t\t\t<view class=\"left\" v-if=\"orderType == 'takein'\" style=\"\">\n\t\t\t\t\t\t<view class=\"store-name\" @click=\"selectShop()\">\n\t\t\t\t\t\t\t<text>{{ store.name }}</text>\n\t\t\t\t\t\t\t<view class=\"iconfont iconarrow-right\"></view>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t\t<view class=\"store-location\">\n\t\t\t\t\t\t\t<text>距离您 {{kmUnit(store.dis)}}</text>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</view>\n\t\t\t\t\t<view class=\"left overflow-hidden\" v-else>\n\t\t\t\t\t\t<view class=\"store-name\" @click=\"selectShop()\">\n\t\t\t\t\t\t\t<view>{{ store.name }}\n\t\t\t\t\t\t\t\t<text class=\"small\" v-if=\"store.distance > 0 && orderType == 'takeout'\">(配送距离:\n\t\t\t\t\t\t\t\t\t{{store.distance}}km)</text>\n\t\t\t\t\t\t\t\t<text class=\"small\" v-else-if=\"orderType == 'takeout'\">(本店不支持外卖)</text>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t<view class=\"iconfont iconarrow-right\"></view>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</view>\n\t\t\t\t\t<view class=\"right\">\n\t\t\t\t\t\t<view class=\"dinein\" :class=\"{active: orderType == 'takein'}\" @tap=\"takein\">\n\t\t\t\t\t\t\t<text>自取</text>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t\t<view class=\"takeout\" :class=\"{active: orderType == 'takeout'}\" @tap=\"takout\">\n\t\t\t\t\t\t\t<text>外卖</text>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</view>\n\t\t\t\t</view>\n\t\t\t</view>\n\t\t\n\t\t\t<!-- #ifdef H5 -->\n\t\t\t<view class=\"content\"\n\t\t\t\t:style=\"{height: 'calc(100vh - 500rpx + '+(store.notice ? '0rpx':'60rpx')+')'}\">\n\t\t\t\t<!-- #endif -->\n\t\t\t\t<!-- #ifndef H5 -->\n\t\t\t\t<view class=\"content\" :style=\"{height: 'calc(100vh - 500rpx + '+(store.notice ? '0rpx':'60rpx')+')'}\">\n\t\t\t\t\t<!-- #endif -->\n\t\t\t\t\t<scroll-view class=\"menus\" :scroll-into-view=\"menuScrollIntoView\" scroll-with-animation scroll-y>\n\t\t\t\t\t\t<view class=\"wrapper\">\n\t\t\t\t\t\t\t<view class=\"menu\" :id=\"`menu-${item.id}`\" :class=\"{'current': item.id === currentCateId}\"\n\t\t\t\t\t\t\t\tv-for=\"(item, index) in goods\" :key=\"index\" @tap=\"handleMenuTap(item.id)\">\n\t\t\t\t\t\t\t\t<text>{{ item.name }}</text>\n\t\t\t\t\t\t\t\t<view class=\"dot\" v-show=\"menuCartNum(item.id)\">{{ menuCartNum(item.id) }}</view>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</scroll-view>\n\t\t\t\t\t<!-- goods list begin -->\n\t\t\t\t\t<scroll-view class=\"goods\" scroll-with-animation scroll-y :scroll-top=\"cateScrollTop\"\n\t\t\t\t\t\t@scroll=\"handleGoodsScroll\">\n\t\t\t\t\t\t<view class=\"wrapper\">\n\t\t\t\t\t\t\t<view class=\"list\">\n\t\t\t\t\t\t\t\t<!-- category begin -->\n\t\t\t\t\t\t\t\t<view class=\"category\" v-for=\"(item, index) in goods\" :key=\"index\"\n\t\t\t\t\t\t\t\t\t:id=\"`cate-${item.id}`\">\n\t\t\t\t\t\t\t\t\t<view class=\"title\">\n\t\t\t\t\t\t\t\t\t\t<text>{{ item.name }}</text>\n\t\t\t\t\t\t\t\t\t\t<image mode=\"aspectFill\" :src=\"item.icon\" class=\"icon\"></image>\n\t\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t\t<view class=\"items\">\n\t\t\t\t\t\t\t\t\t\t<!-- 商品 begin -->\n\t\t\t\t\t\t\t\t\t\t<view class=\"good\" v-for=\"(good, key) in item.goodsList\" :key=\"key\"\n\t\t\t\t\t\t\t\t\t\t\t:class=\"{'backgroud-grey': good.stock <= 0}\">\n\t\t\t\t\t\t\t\t\t\t\t<image mode=\"aspectFill\" :src=\"good.image\" class=\"image\"\n\t\t\t\t\t\t\t\t\t\t\t\t@tap=\"showGoodDetailModal(item, good)\"></image>\n\t\t\t\t\t\t\t\t\t\t\t<view class=\"right\">\n\t\t\t\t\t\t\t\t\t\t\t\t<text class=\"name\">{{ good.storeName }}</text>\n\t\t\t\t\t\t\t\t\t\t\t\t<text class=\"tips\">{{ good.storeInfo }}</text>\n\t\t\t\t\t\t\t\t\t\t\t\t<view class=\"price_and_action\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t<text class=\"price\">￥{{ good.price }}</text>\n\t\t\t\t\t\t\t\t\t\t\t\t\t<view class=\"btn-group\" v-if=\"good.stock > 0\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<button type=\"primary\" class=\"btn property_btn\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\thover-class=\"none\" size=\"mini\"\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t@tap=\"showGoodDetailModal(item, good)\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t选规格\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t</button>\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t<view class=\"dot\" v-show=\"goodCartNum(good.id)\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t{{ goodCartNum(good.id) }}</view>\n\t\t\t\t\t\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t\t\t\t\n\n\t\t\t\t\t\t\t\t\t\t\t\t\t<view v-if=\"good.stock == 0\">已售罄</view>\n\t\t\t\t\t\t\t\t\t\t\t\t</view>\n\n\t\t\t\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t\t\t<!-- 商品 end -->\n\t\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t<!-- category end -->\n\t\t\t\t\t\t\t\t<view style=\"height: 110rpx;\"></view>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</scroll-view>\n\t\t\t\t\t<!-- goods list end -->\n\t\t\t\t</view>\n\t\t\t\t<!-- content end -->\n\t\t\t\t<!-- 购物车栏 begin -->\n\t\t\t\t<view class=\"cart-box\" v-if=\"cart.length > 0 && isCartShow\">\n\t\t\t\t\t<view class=\"mark\">\n\t\t\t\t\t\t<image src=\"/static/images/menu/cart.png\" class=\"cart-img\" @tap=\"openCartPopup\"></image>\n\t\t\t\t\t\t<view class=\"tag\">{{ getCartGoodsNumber }}</view>\n\t\t\t\t\t</view>\n\t\t\t\t\t<view class=\"price\" @tap=\"openCartShow\">￥{{ getCartGoodsPrice }}</view>\n\t\t\t\t\t<button type=\"primary\" class=\"pay-btn\" @tap=\"toPay\" :disabled=\"disabledPay\">\n\t\t\t\t\t\t{{ disabledPay ? `差${spread}元起送` : '去结算' }}\n\t\t\t\t\t</button>\n\t\t\t\t</view>\n\t\t\t\t<!-- 购物车栏 end -->\n\t\t\t</view>\n\t\t\t<!-- 商品详情模态框 begin -->\n\t\t\t<modal :show=\"goodDetailModalVisible\" class=\"good-detail-modal\" color=\"#5A5B5C\" width=\"90%\" custom\n\t\t\t\tpadding=\"0rpx\" radius=\"12rpx\">\n\t\t\t\t<view class=\"cover\">\n\t\t\t\t\t<view class=\"btn-group\">\n\t\t\t\t\t\t<image src=\"/static/images/menu/close.png\" @tap=\"closeGoodDetailModal\"></image>\n\t\t\t\t\t</view>\n\t\t\t\t</view>\n\t\t\t\t<scroll-view class=\"detail\" scroll-y>\n\t\t\t\t\t<view v-if=\"good.image\" class=\"image\">\n\t\t\t\t\t\t<image :src=\"good.image\"></image>\n\t\t\t\t\t</view>\n\n\t\t\t\t\t<view class=\"wrapper\">\n\t\t\t\t\t\t<view class=\"basic\">\n\t\t\t\t\t\t\t<view class=\"name\">{{ good.storeName }}</view>\n\t\t\t\t\t\t\t<view class=\"tips flex justify-between\">{{ good.storeInfo }} <text style=\"color: red;\">可获积分:10</text></view>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t\t<view class=\"properties\">\n\t\t\t\t\t\t\t<view class=\"property\" v-for=\"(item, index) in good.productAttr\" :key=\"index\">\n\t\t\t\t\t\t\t\t<view class=\"title\">\n\t\t\t\t\t\t\t\t\t<text class=\"name\">{{ item.attrName }}</text>\n\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t<view class=\"values\">\n\t\t\t\t\t\t\t\t\t<view class=\"value\" v-for=\"(value, key) in item.attrValueArr\" :key=\"key\"\n\t\t\t\t\t\t\t\t\t\t:class=\"{'default': value == newValue[index]}\"\n\t\t\t\t\t\t\t\t\t\t@tap=\"changePropertyDefault(index, key,false)\">\n\t\t\t\t\t\t\t\t\t\t{{ value }}\n\t\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</view>\n\t\t\t\t</scroll-view>\n\t\t\t\t<view class=\"action\">\n\t\t\t\t\t<view class=\"left\">\n\t\t\t\t\t\t<view class=\"price\">￥{{ good.price }}</view>\n\t\t\t\t\t\t<view class=\"props\">\n\t\t\t\t\t\t\t{{ good.valueStr }}\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</view>\n\t\t\t\t\t<view class=\"btn-group\">\n\t\t\t\t\t\t<text style=\"margin-right: 20rpx;\">库存：{{good.stock}} </text>\n\t\t\t\t\t\t<button type=\"default\" plain class=\"btn\" size=\"mini\" hover-class=\"none\"\n\t\t\t\t\t\t\t@tap=\"handlePropertyReduce\">\n\t\t\t\t\t\t\t<view class=\"iconfont iconsami-select\"></view>\n\t\t\t\t\t\t</button>\n\t\t\t\t\t\t<view class=\"number\">{{ good.number }}</view>\n\t\t\t\t\t\t<button type=\"primary\" class=\"btn\" size=\"min\" hover-class=\"none\" @tap=\"handlePropertyAdd\">\n\t\t\t\t\t\t\t<view class=\"iconfont iconadd-select\"></view>\n\t\t\t\t\t\t</button>\n\t\t\t\t\t</view>\n\t\t\t\t</view>\n\t\t\t\t<view class=\"add-to-cart-btn\" @tap=\"handleAddToCartInModal\">\n\t\t\t\t\t<view>加入购物车</view>\n\t\t\t\t</view>\n\t\t\t</modal>\n\t\t\t<!-- 商品详情模态框 end -->\n\t\t\t<!-- 购物车详情popup -->\n\t\t\t<uv-popup ref=\"popup\" mode=\"bottom\" class=\"cart-popup\">\n\t\t\t\t<template #default>\n\t\t\t\t<view  class=\"cart-popup\">\n\t\t\t\t\t <view class=\"top\">\n\t\t\t\t\t  <text @tap=\"handleCartClear\">清空</text>\n\t\t\t\t\t </view>\n\t\t\t\t\t <scroll-view class=\"cart-list\" scroll-y>\n\t\t\t\t\t  <view class=\"wrapper\">\n\t\t\t\t\t   <view class=\"item\" v-for=\"(item, index) in cart\" :key=\"index\">\n\t\t\t\t\t\t<view class=\"left\">\n\t\t\t\t\t\t <view class=\"name\">{{ item.name }}</view>\n\t\t\t\t\t\t <view class=\"props\">{{ item.valueStr }}</view>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t\t<view class=\"center\">\n\t\t\t\t\t\t <text>￥{{ item.price }}</text>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t\t<view class=\"right\">\n\t\t\t\t\t\t <button type=\"default\" plain size=\"mini\" class=\"btn\" hover-class=\"none\"\n\t\t\t\t\t\t  @tap=\"handleCartItemReduce(index)\">\n\t\t\t\t\t\t  <view class=\"iconfont iconsami-select\"></view>\n\t\t\t\t\t\t </button>\n\t\t\t\t\t\t <view class=\"number\">{{ item.number }}</view>\n\t\t\t\t\t\t <button type=\"primary\" class=\"btn\" size=\"min\" hover-class=\"none\"\n\t\t\t\t\t\t  @tap=\"handleCartItemAdd(index)\">\n\t\t\t\t\t\t  <view class=\"iconfont iconadd-select\"></view>\n\t\t\t\t\t\t </button>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t   </view>\n\t\t\t\t\t\t\t\n\t\t\t\t\t  </view>\n\t\t\t\t\t </scroll-view>\n\t\t\t\t </view>\n\t\t\t\t</template>\n\t\t\t</uv-popup>\n\t\t\t   <!-- 购物车详情popup -->\n\t\t\t<uv-toast ref=\"uToast\"></uv-toast>\n\t\t</view>\n\t\t<!--轻提示-->\n\t\t<view class=\"loading\" v-else>\n\t\t\t<uv-loading-icon  color=\"#DA5650\" size=40 mode=\"circle\" ></uv-loading-icon>\n\t\t\t<button type=\"primary\" style=\"z-index: 3001;position: absolute;top: 650rpx;\" @click=\"init\"\n\t\t\t\tv-if=\"!store.id\">定位最近的门店</button>\n\t\t<!-- \t<uv-toast ref=\"uToast\"></uv-toast> -->\n\t\t</view>\n\t</layout>\n</template>\n\n<script setup>\nimport {\n  ref,\n  computed,\n  nextTick\n} from 'vue'\nimport { useMainStore } from '@/store/store'\nimport { storeToRefs } from 'pinia'\nimport { onLoad,onShow ,onPullDownRefresh,onHide} from '@dcloudio/uni-app'\nimport { formatDateTime,kmUnit } from '@/utils/util'\nimport {\n  shopNearby,\n  menuGoods\n} from '@/api/goods'\nimport {\n  menuAds\n} from '@/api/market'\nconst main = useMainStore()\nconst { orderType,address, store,location,isLogin } = storeToRefs(main)\nconst title = ref('点餐')\nconst text = ref('滚动通知')\n\nconst goods = ref([])\nconst ads = ref([])\nconst loading = ref(true) \nconst currentCateId = ref(0)\nconst cateScrollTop = ref(0)\nconst menuScrollIntoView = ref('')\nconst cart = ref([])\nconst goodDetailModalVisible = ref(false)\nconst good= ref({})\nconst category = ref({})\nconst cartPopupVisible = ref(false)\nconst sizeCalcState = ref(false)\nconst newValue = ref([])\nconst shopAd = ref('')\nconst isCartShow = ref(true)\nconst popup = ref()\n\n\n\nconst newkmUnit = computed(() => (param) =>{\n  console.log('param:',param)\n  return '10km'\n})\nconst goodCartNum = computed(() => { //计算单个饮品添加到购物车的数量\n\treturn (id) => cart.value.reduce((acc, cur) => {\n\t\tif (cur.id === id) {\n\t\t\treturn acc += cur.number\n\t\t}\n\t\treturn acc\n\t}, 0)\n})\nconst menuCartNum = computed(() =>{\n\treturn (id) => cart.value.reduce((acc, cur) => {\n\t\tif (cur.cate_id === id) {\n\t\t\treturn acc += cur.number\n\t\t}\n\t\treturn acc\n\t}, 0)\n})\nconst getCartGoodsNumber = computed(() => { //计算购物车总数\n\treturn cart.value.reduce((acc, cur) => acc + cur.number, 0)\n})\nconst getCartGoodsPrice = computed(() =>{ //计算购物车总价\n\tlet price = cart.value.reduce((acc, cur) => acc + cur.number * cur.price, 0);\n\treturn parseFloat(price).toFixed(2);\n})\nconst disabledPay = computed(() => { //是否达到起送价\n\treturn orderType.value == 'takeout' && (getCartGoodsPrice < parseFloat(store.value.min_price)) ? true :\n\t\tfalse\n})\nconst spread = computed(() => { //差多少元起送\n\tif (orderType.value != 'takeout') return\n\treturn parseFloat((store.value.min_price - getCartGoodsPrice).toFixed(2))\n})\n\n// 监听自定义事件\nuni.$on('refreshMenu', () => {\n\t// 在这里执行onLoad逻辑\n\tconsole.log('refreshMenu1:',store.value.id)\n\tinit()\n})\n\nonPullDownRefresh(() => {\n\tinit()\n})\nonLoad(() => {\n\tinit();\n\trefreshCart()\n})\nonHide(() => {\n\t// 重新进入要重新计算页面高度，否则有问题\n\tsizeCalcState.value = false;\n})\nonShow(() => {\n\t//init();\n\trefreshCart()\n\tshopAd.value = uni.getStorageSync('shopAd')\n})\n\nconst openCartShow = () =>{\n\tisCartShow.value = false\n}\nconst in_array = (search, array) => {\n\tfor (var i in array) {\n\t\tif (array[i] == search) {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\nconst selectShop = () => {\n\tuni.navigateTo({\n\t\turl: '/pages/components/pages/shop/shop'\n\t})\n}\nconst uToast = ref()\nconst  init = async() => { //页面初始化\n\tloading.value = true;\n    \n\t//return\n\tlet error = {},\n\t\tresult = location.value\n\tconsole.log('result:',result)\n\tif (!location.value.hasOwnProperty('latitude')) {\n\t\t  console.log('result1:',location.value)\n\t\t  uni.getLocation(({\n\t\t\t type: 'wgs84',\n\t\t\t success: function (res) {\n\t\t\t   console.log('location1:',res)\n\t\t\n\t\t\t\tresult = {\n\t\t\t\t\tlatitude: res.latitude,\n\t\t\t\t\tlongitude: res.longitude\n\t\t\t\t};\n\t\t\t\tgetShopList(result)\n\t\t\t },\n\t\t\t fail: function (res) {\n\t\t\t  \n\t\t\t   uni.showToast({\n\t\t\t     title: '获取位置失败，请检查是否开启相关权限',\n\t\t\t     duration: 2000,\n\t\t\t     icon: 'error'\n\t\t\t   });\n\t\t\t   // 默认地为你为北京地址\n\t\t\t   result = {\n\t\t\t   \tlatitude: 39.919990,\n\t\t\t   \tlongitude: 116.456270\n\t\t\t   };\n\t\t\t   getShopList(result)\n\t\t\t },\n\t\t\t complete: function (res) {\n\t\t\t }\n\t\t}));\n\t\t if(!result.hasOwnProperty('latitude')){\n\t\t\tresult = {\n\t\t\t\tlatitude: 39.919990,\n\t\t\t\tlongitude: 116.456270\n\t\t\t};\n\t\t\tgetShopList(result)\n\t\t }\n\t\treturn\n\t}\n\t\n\tgetShopList(result)\n   \n\t\n}\nconst getShopList = async(res) => {\n\t console.log('location9:',res)\n\tif (res) {\n\t\tmain.SET_LOCATION(res);\n\t\n\t\tlet shop_id = 0;\n\t\tif (store.value.id) {\n\t\t\tshop_id = store.value.id;\n\t\t}\n\t\n\t\tlet shop = await shopNearby({\n\t\t\tlat: res.latitude,\n\t\t\tlng: res.longitude,\n\t\t\tshop_id: shop_id,\n\t\t\tkw: ''\n\t\t});\n\t\tif (shop) {\n\t\t\t//广告图\n\t\t\tgetAds(shop.id);\n\t\n\t\t\tshop.notice = shop.status == 1 ? shop.notice : '店铺营业时间为:' + formatDateTime(shop.startTime,'hh:mm')+' - '+formatDateTime(shop.endTime,'hh:mm') +\n\t\t\t'，不在营业时间内无法下单';\n\t\t\t// 设置店铺信息\n\t\t\tmain.SET_STORE(shop);\n\t\t\tlet mygoods = await menuGoods({\n\t\t\t\tshopId: shop.id\n\t\t\t});\n\t\t\tif (mygoods) {\n\t\t\t\tgoods.value = mygoods;\n\t\t\t\trefreshCart();\n\t\t\t}\n\t\t\tconsole.log('goods:',mygoods)\n\t\t\tconsole.log('goods:',goods.value)\n\t\t\tloading.value = false;\n\t\t\tuni.stopPullDownRefresh();\n\t\t}\n\t}\n}\nconst refreshCart = () =>{\n\tif (goods.value && goods.value.length > 0) {\n\t\tlet newGoods = goods.value;\n\t\tcart.value = [];\n\t\tlet newCart = uni.getStorageSync('cart') || [];\n\t\tlet tmpCart = [];\n\t\tif (newCart) {\n\t\t\tfor (let i in newCart) {\n\t\t\t\tfor (let ii in newGoods) {\n\t\t\t\t\tfor (let iii in newGoods[ii].goodsList) {\n\t\t\t\t\t\tif (newCart[i].id == newGoods[ii].goodsList[iii].id) {\n\t\t\t\t\t\t\ttmpCart.push(newCart[i]);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tcart.value = tmpCart;\n\t\t\tcartPopupVisible.value = false;\n\t\t}\n\t}\n}\nconst  getAds = async(shop_id) =>{\n\tlet data = await menuAds({\n\t\tshop_id: shop_id ? shop_id : 0\n\t});\n\tif (data) {\n\t\tads.value = data;\n\t}\n}\nconst takout = (force = false) => {\n\tif (orderType.value == 'takeout' && force == false) return\n\tmain.SET_ORDER_TYPE('takeout');\n\n\tif (!isLogin.value) {\n\t\tuni.navigateTo({\n\t\t\turl: '/pages/components/pages/login/login'\n\t\t})\n\t\treturn\n\t} \n\n}\nconst takein = (force = false) => {\n\tif (orderType.value == 'takein' && force == false) return\n\tmain.SET_ORDER_TYPE('takein');\n\n\tif (!isLogin.value) {\n\t\tuni.navigateTo({\n\t\t\turl: '/pages/components/pages/login/login'\n\t\t})\n\t\treturn\n\t} \n\n}\nconst handleMenuTap = (id) => { //点击菜单项事件\n\tif (!sizeCalcState.value) {\n\t\tcalcSize()\n\t}\n\n\tcurrentCateId.value = id\n\tnextTick(() => cateScrollTop.value = goods.value.find(item => item.id == id).top)\n}\nconst handleGoodsScroll = ({ detail }) => { //商品列表滚动事件\n\tif (!sizeCalcState.value) {\n\t\tcalcSize()\n\t}\n\tconsole.log('scrollTop:',detail)\n\tconst {\n\t\tscrollTop\n\t} = detail\n\tlet tabs = goods.value.filter(item => item.top <= scrollTop).reverse()\n\tif (tabs.length > 0) {\n\t\tcurrentCateId.value = tabs[0].id\n\t}\n}\nconst calcSize = () => {\n\tlet h = 10\n\tlet view = uni.createSelectorQuery().select('#ads')\n\tif (view) {\n\t\tview.fields({\n\t\t\tsize: true\n\t\t}, data => {\n\t\t\tif (data) {\n\t\t\t\th += Math.floor(data.height)\n\t\t\t}\n\t\t}).exec()\n\t}\n\tgoods.value.forEach(item => {\n\t\tlet view = uni.createSelectorQuery().select(`#cate-${item.id}`)\n\t\tview.fields({\n\t\t\tsize: true\n\t\t}, data => {\n\t\t\tconsole.log('h3:',h)\n\t\t\titem.top = h\n\t\t\th += data.height\n\t\t\titem.bottom = h\n\t\t}).exec()\n\t})\n\tsizeCalcState.value = true\n}\nconst handleAddToCart = (cate, newGood, num) =>{ //添加到购物车\n\tconst index = cart.value.findIndex(item => {\n\t\tif (newGood) {\n\t\t\treturn (item.id === newGood.id) && (item.valueStr === good.value.valueStr)\n\t\t} else {\n\t\t\treturn item.id === newGood.id\n\t\t}\n\t})\n\tif (index > -1) {\n\t\tcart.value[index].number += num\n\t} else {\n\t\tcart.value.push({\n\t\t\tid: newGood.id,\n\t\t\tcate_id: cate.id,\n\t\t\tname: newGood.storeName,\n\t\t\tprice: newGood.price,\n\t\t\tnumber: num,\n\t\t\timage: newGood.image,\n\t\t\tvalueStr: good.value.valueStr\n\t\t})\n\t}\n\tuni.setStorageSync('cart', JSON.parse(JSON.stringify(cart.value)))\n}\nconst handleReduceFromCart = (item, good) => {\n\tconst index = cart.value.findIndex(item => item.id === good.id)\n\tcart.value[index].number -= 1\n\tif (cart.value[index].number <= 0) {\n\t\tcart.value.splice(index, 1)\n\t}\n\tuni.setStorageSync('cart', JSON.parse(JSON.stringify(cart.value)))\n}\nconst showGoodDetailModal = (item, newGood) => {\n\tisCartShow.value = true\n\tgood.value = JSON.parse(JSON.stringify({\n\t\t...newGood,\n\t\tnumber: 1\n\t}))\n\tcategory.value = JSON.parse(JSON.stringify(item))\n\tgoodDetailModalVisible.value = true;\n\tconsole.log('goodDetailModalVisible:',goodDetailModalVisible.value)\n\tchangePropertyDefault(0, 0,true);\n}\nconst closeGoodDetailModal = () => { //关闭饮品详情模态框\n\tgoodDetailModalVisible.value = false\n    category.value = {}\n\tgood.value = {}\n}\nconst changePropertyDefault = (index, key, isDefault) => { //改变默认属性值\n\tlet valueStr = ''\n\tconsole.log('good:',good.value)\n\tif(isDefault){\n\t\tnewValue.value = []\n\t\tfor(let i = 0;i < good.value.productAttr.length;i++){\n\t\t\tnewValue.value[i] = good.value.productAttr[i].attrValueArr[0]\n\t\t}\n\n\t\t//valueStr = newValue.value.join(',')\n\n\t}else{\n\t\tnewValue.value[index] = good.value.productAttr[index].attrValueArr[key]\n\t\t//valueStr = newValue.value.join(',')\n\t}\n\t\n\tvalueStr = newValue.value.join(',')\n\tlet productValue = good.value.productValue[valueStr]\n\tif(!productValue) {\n\t\tlet skukey = JSON.parse(JSON.stringify(newValue.value))\n\t\tskukey.sort((a, b) => a.localeCompare(b))\n\t\t//console.log('skukey:',skukey)\n\t\tvalueStr = skukey.join(',')\n\t\tproductValue = good.value.productValue[valueStr]\n\t}\n\n\t\n\t//let productValue = good.value.productValue[valueStr]\n\tgood.value.number = 1;\n\tgood.value.price = parseFloat(productValue.price).toFixed(2);\n\tgood.value.stock = productValue.stock;\n\tgood.value.image = productValue.image ? productValue.image : good.value.image;\n\tgood.value.valueStr = valueStr\n\n}\nconst handlePropertyAdd = () => {\n\tgood.value.number += 1\n}\nconst handlePropertyReduce = () => {\n\tif (good.value.number === 1) return\n\tgood.value.number -= 1\n}\nconst handleAddToCartInModal = () => {\n\tif (good.value.stock <= 0) {\n\t\tuToast.value.show({message:'商品库存不足',type: 'error'});\n\t\treturn;\n\t}\n\thandleAddToCart(category.value, good.value, good.value.number)\n\tcloseGoodDetailModal()\n}\nconst openCartPopup = () => { //打开/关闭购物车列表popup\n\tpopup.value.open()\n}\nconst handleCartClear = () => { //清空购物车\n\tuni.showModal({\n\t\ttitle: '提示',\n\t\tcontent: '确定清空购物车么',\n\t\tsuccess: ({\n\t\t\tconfirm\n\t\t}) => {\n\t\t\tif (confirm) {\n\t\t\t\tpopup.value.close()\n\t\t\t\tcart.value = []\n\t\t\t\tuni.setStorageSync('cart', JSON.parse(JSON.stringify(cart.value)))\n\t\t\t}\n\t\t}\n\t})\n}\nconst handleCartItemAdd = (index) => {\n\tcart.value[index].number += 1\n\tuni.setStorageSync('cart', JSON.parse(JSON.stringify(cart.value)))\n}\nconst handleCartItemReduce = (index) => {\n\tif (cart.value[index].number === 1) {\n\t\tcart.value.splice(index, 1)\n\t} else {\n\t\tcart.value[index].number -= 1\n\t}\n\tif (!cart.value.length) {\n\t\tcartPopupVisible.value = false\n\t}\n\tuni.setStorageSync('cart', JSON.parse(JSON.stringify(cart.value)))\n}\nconst toPay = () => {\n\n\tif (!isLogin.value) {\n\t\tuni.navigateTo({\n\t\t\turl: '/pages/components/pages/login/login'\n\t\t})\n\t\treturn\n\t} else {\n\t\tif (store.value.status == 0) {\n\t\t\tuToast.value.show({message:'不在店铺营业时间内',type: 'error'});\n\t\t\treturn;\n\t\t}\n\t\tif(orderType.value == 'takeout' && store.value.distance <= 0){\n\t\t\tuToast.value.show({message:'本店不支持外卖',type: 'error'});\n\t\t\treturn;\n\t\t}\n\t\t// 判断当前是否在配送范围内\n\t\tif (orderType.value == 'takeout' && store.value.distance < store.value.far) {\n\t\t\tuToast.value.show({message:'选中的地址不在配送范围',type: 'error'});\n\t\t\treturn;\n\t\t}\n\n\t\tuni.showLoading({\n\t\t\ttitle: '加载中'\n\t\t})\n\t\tuni.setStorageSync('cart', JSON.parse(JSON.stringify(cart.value)))\n\n\t\tuni.navigateTo({\n\t\t\turl: '/pages/components/pages/pay/pay'\n\t\t})\n\t}\n\n\tuni.hideLoading()\n}\n\n</script>\n\n<style lang=\"scss\" scoped>\n\n\t/* #ifdef H5 */\n\tpage {\n\t\theight: auto;\n\t\tmin-height: 100%;\n\t}\n\t/* #endif */\n\t\n\t.container {\n\t\toverflow: hidden;\n\t\tposition: relative;\n\t}\n\t\n\t.loading {\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tjustify-content: center;\n\t\n\t\timage {\n\t\t\twidth: 260rpx;\n\t\t\theight: 260rpx;\n\t\t\tposition: relative;\n\t\t\tmargin-top: -200rpx;\n\t\t\t/* #ifdef h5 */\n\t\t\tmargin-top: 0;\n\t\t\t/* #endif */\n\t\t}\n\t}\n\t\n\t\n\t.main {\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\tposition: relative;\n\t}\n\t\n\t.nav {\n\t\twidth: 100%;\n\t\t//height: 212rpx;\n\t\theight: 140rpx;\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\n\t\t.header {\n\t\t\twidth: 100%;\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tjustify-content: space-between;\n\t\t\tpadding: 20rpx;\n\t\t\tbackground-color: #ffffff;\n\t\t\theight: 140rpx;\n\t\n\t\t\t.left {\n\t\t\t\tflex: 1;\n\t\t\t\tdisplay: flex;\n\t\t\t\tflex-direction: column;\n\t\n\t\t\t\t.store-name {\n\t\t\t\t\tdisplay: flex;\n\t\t\t\t\tjustify-content: flex-start;\n\t\t\t\t\talign-items: center;\n\t\t\t\t\tfont-size: $font-size-lg;\n\t\t\t\t\tmargin-bottom: 10rpx;\n\t\t\t\t\t.small {\n\t\t\t\t\t\tfont-size: $font-size-sm;\n\t\t\t\t\t\tcolor: $text-color-assist;\n\t\t\t\t\t}\n\t\t\t\t\t.iconfont {\n\t\t\t\t\t\tmargin-left: 10rpx;\n\t\t\t\t\t\tline-height: 100%;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\n\t\t\t\t.store-location {\n\t\t\t\t\tdisplay: flex;\n\t\t\t\t\tjustify-content: flex-start;\n\t\t\t\t\talign-items: center;\n\t\t\t\t\tcolor: $text-color-assist;\n\t\t\t\t\tfont-size: $font-size-sm;\n\t\n\t\t\t\t\t.iconfont {\n\t\t\t\t\t\tvertical-align: middle;\n\t\t\t\t\t\tdisplay: table-cell;\n\t\t\t\t\t\tcolor: $color-primary;\n\t\t\t\t\t\tline-height: 100%;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\n\t\t\t.right {\n\t\t\t\tbackground-color: $bg-color-grey;\n\t\t\t\tborder-radius: 38rpx;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tfont-size: $font-size-sm;\n\t\t\t\tpadding: 0 38rpx;\n\t\t\t\tcolor: $text-color-assist;\n\t\n\t\t\t\t.dinein,\n\t\t\t\t.takeout {\n\t\t\t\t\tposition: relative;\n\t\t\t\t\tdisplay: flex;\n\t\t\t\t\talign-items: center;\n\t\t\t\t\t&.active {\n\t\t\t\t\t\tpadding: 14rpx 38rpx;\n\t\t\t\t\t\tcolor: #ffffff;\n\t\t\t\t\t\tbackground-color: $color-primary;\n\t\t\t\t\t\t//background-color: #5A5B5C;\n\t\t\t\t\t\tborder-radius: 38rpx;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\n\t\t\t\t.takeout {\n\t\t\t\t\tmargin-left: 20rpx;\n\t\t\t\t\theight: 100%;\n\t\t\t\t\tflex: 1;\n\t\t\t\t\tpadding: 14rpx 0;\n\t\t\t\t}\n\t\n\t\t\t\t.dinein.active {\n\t\t\t\t\tmargin-left: -38rpx;\n\t\t\t\t}\n\t\n\t\t\t\t.takeout.active {\n\t\t\t\t\tmargin-right: -38rpx;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t}\n\t\n\t.content {\n\t\twidth: 100%;\n\t\theight: calc(100vh - 212rpx);\n\t\t/* #ifdef H5 */\n\t\theight: calc(100vh - 212rpx - 188rpx);\n\t\t/* #endif */\n\t\tdisplay: flex;\n\t\n\t\t.menus {\n\t\t\twidth: 200rpx;\n\t\t\theight: 100%;\n\t\t\toverflow: hidden;\n\t\t\tbackground-color: $bg-color-grey;\n\t\n\t\t\t.wrapper {\n\t\t\t\twidth: 100%;\n\t\t\t\theight: 100%;\n\t\n\t\t\t\t.menu {\n\t\t\t\t\tdisplay: flex;\n\t\t\t\t\talign-items: center;\n\t\t\t\t\tjustify-content: flex-start;\n\t\t\t\t\tpadding: 30rpx 20rpx;\n\t\t\t\t\tfont-size: 26rpx;\n\t\t\t\t\tcolor: $text-color-assist;\n\t\t\t\t\tposition: relative;\n\t\n\t\t\t\t\t&.current {\n\t\t\t\t\t\tbackground-color: #ffffff;\n\t\t\t\t\t\tcolor: $text-color-base;\n\t\t\t\t\t}\n\t\n\t\t\t\t\t.dot {\n\t\t\t\t\t\tposition: absolute;\n\t\t\t\t\t\twidth: 34rpx;\n\t\t\t\t\t\theight: 34rpx;\n\t\t\t\t\t\tline-height: 34rpx;\n\t\t\t\t\t\tfont-size: 22rpx;\n\t\t\t\t\t\tbackground-color: $color-primary;\n\t\t\t\t\t\t//background-color: #5A5B5C;\n\t\t\t\t\t\tcolor: #ffffff;\n\t\t\t\t\t\ttop: 16rpx;\n\t\t\t\t\t\tright: 10rpx;\n\t\t\t\t\t\tborder-radius: 100%;\n\t\t\t\t\t\ttext-align: center;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t.menu:last-child {\n\t\t\t\t\tmargin-bottom: 200rpx;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\n\t\t.goods {\n\t\t\tflex: 1;\n\t\t\theight: 100%;\n\t\t\toverflow: hidden;\n\t\t\tbackground-color: #ffffff;\n\t\n\t\t\t.wrapper {\n\t\t\t\twidth: 100%;\n\t\t\t\theight: 100%;\n\t\t\t\tpadding: 20rpx;\n\t\n\t\t\t\t.ads {\n\t\t\t\t\theight: calc(300 / 550 * 510rpx);\n\t\n\t\t\t\t\timage {\n\t\t\t\t\t\twidth: 100%;\n\t\t\t\t\t\theight: 100%;\n\t\t\t\t\t\tborder-radius: 8rpx;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\n\t\t\t\t.list {\n\t\t\t\t\twidth: 100%;\n\t\t\t\t\tfont-size: $font-size-base;\n\t\n\t\t\t\t\t.category {\n\t\t\t\t\t\twidth: 100%;\n\t\n\t\t\t\t\t\t.title {\n\t\t\t\t\t\t\tpadding: 30rpx 0;\n\t\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\t\tcolor: $text-color-base;\n\t\n\t\t\t\t\t\t\t.icon {\n\t\t\t\t\t\t\t\twidth: 38rpx;\n\t\t\t\t\t\t\t\theight: 38rpx;\n\t\t\t\t\t\t\t\tmargin-left: 10rpx;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t.category:last-child {\n\t\t\t\t\t\tmargin-bottom: 200rpx;\n\t\t\t\t\t}\n\t\n\t\t\t\t\t.items {\n\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\tflex-direction: column;\n\t\t\t\t\t\tpadding-bottom: -30rpx;\n\t\n\t\t\t\t\t\t.good {\n\t\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\t\t//margin-bottom: 30rpx;\n\t\t\t\t\t\t\tpadding: 15rpx 0;\n\t\t\t\t\t\t\t.image {\n\t\t\t\t\t\t\t\twidth: 160rpx;\n\t\t\t\t\t\t\t\theight: 160rpx;\n\t\t\t\t\t\t\t\tmargin-right: 20rpx;\n\t\t\t\t\t\t\t\tborder-radius: 8rpx;\n\t\t\t\t\t\t\t}\n\t\n\t\t\t\t\t\t\t.right {\n\t\t\t\t\t\t\t\tflex: 1;\n\t\t\t\t\t\t\t\theight: 160rpx;\n\t\t\t\t\t\t\t\toverflow: hidden;\n\t\t\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\t\t\tflex-direction: column;\n\t\t\t\t\t\t\t\talign-items: flex-start;\n\t\t\t\t\t\t\t\tjustify-content: space-between;\n\t\t\t\t\t\t\t\tpadding-right: 14rpx;\n\t\n\t\t\t\t\t\t\t\t.name {\n\t\t\t\t\t\t\t\t\tfont-size: $font-size-base;\n\t\t\t\t\t\t\t\t\tmargin-bottom: 10rpx;\n\t\t\t\t\t\t\t\t\twidth: 100%;\n\t\t\t\t\t\t\t\t\toverflow: hidden;\n\t\t\t\t\t\t\t\t\ttext-overflow: ellipsis;\n\t\t\t\t\t\t\t\t\twhite-space: nowrap;\n\t\t\t\t\t\t\t\t}\n\t\n\t\t\t\t\t\t\t\t.tips {\n\t\t\t\t\t\t\t\t\twidth: 100%;\n\t\t\t\t\t\t\t\t\theight: 40rpx;\n\t\t\t\t\t\t\t\t\tline-height: 40rpx;\n\t\t\t\t\t\t\t\t\toverflow: hidden;\n\t\t\t\t\t\t\t\t\ttext-overflow: ellipsis;\n\t\t\t\t\t\t\t\t\twhite-space: nowrap;\n\t\t\t\t\t\t\t\t\tfont-size: $font-size-sm;\n\t\t\t\t\t\t\t\t\tcolor: $text-color-assist;\n\t\t\t\t\t\t\t\t\tmargin-bottom: 10rpx;\n\t\t\t\t\t\t\t\t}\n\t\n\t\t\t\t\t\t\t\t.price_and_action {\n\t\t\t\t\t\t\t\t\twidth: 100%;\n\t\t\t\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\t\t\t\tjustify-content: space-between;\n\t\t\t\t\t\t\t\t\talign-items: center;\n\t\n\t\t\t\t\t\t\t\t\t.price {\n\t\t\t\t\t\t\t\t\t\tfont-size: $font-size-base;\n\t\t\t\t\t\t\t\t\t\tfont-weight: 600;\n\t\t\t\t\t\t\t\t\t}\n\t\n\t\t\t\t\t\t\t\t\t.btn-group {\n\t\t\t\t\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\t\t\t\t\tjustify-content: space-between;\n\t\t\t\t\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\t\t\t\t\tposition: relative;\n\t\n\t\t\t\t\t\t\t\t\t\t.btn {\n\t\t\t\t\t\t\t\t\t\t\tpadding: 0 20rpx;\n\t\t\t\t\t\t\t\t\t\t\tbox-sizing: border-box;\n\t\t\t\t\t\t\t\t\t\t\tfont-size: $font-size-sm;\n\t\t\t\t\t\t\t\t\t\t\theight: 44rpx;\n\t\t\t\t\t\t\t\t\t\t\tline-height: 44rpx;\n\t\n\t\t\t\t\t\t\t\t\t\t\t&.property_btn {\n\t\t\t\t\t\t\t\t\t\t\t\tborder-radius: 24rpx;\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\n\t\t\t\t\t\t\t\t\t\t\t&.add_btn,\n\t\t\t\t\t\t\t\t\t\t\t&.reduce_btn {\n\t\t\t\t\t\t\t\t\t\t\t\tpadding: 0;\n\t\t\t\t\t\t\t\t\t\t\t\twidth: 44rpx;\n\t\t\t\t\t\t\t\t\t\t\t\tborder-radius: 44rpx;\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\n\t\t\t\t\t\t\t\t\t\t.dot {\n\t\t\t\t\t\t\t\t\t\t\tposition: absolute;\n\t\t\t\t\t\t\t\t\t\t\tbackground-color: #ffffff;\n\t\t\t\t\t\t\t\t\t\t\tborder: 1px solid $color-primary;\n\t\t\t\t\t\t\t\t\t\t\tcolor: $color-primary;\n\t\t\t\t\t\t\t\t\t\t\tfont-size: $font-size-sm;\n\t\t\t\t\t\t\t\t\t\t\twidth: 36rpx;\n\t\t\t\t\t\t\t\t\t\t\theight: 36rpx;\n\t\t\t\t\t\t\t\t\t\t\tline-height: 36rpx;\n\t\t\t\t\t\t\t\t\t\t\ttext-align: center;\n\t\t\t\t\t\t\t\t\t\t\tborder-radius: 100%;\n\t\t\t\t\t\t\t\t\t\t\tright: -12rpx;\n\t\t\t\t\t\t\t\t\t\t\ttop: -10rpx;\n\t\t\t\t\t\t\t\t\t\t}\n\t\n\t\t\t\t\t\t\t\t\t\t.number {\n\t\t\t\t\t\t\t\t\t\t\twidth: 44rpx;\n\t\t\t\t\t\t\t\t\t\t\theight: 44rpx;\n\t\t\t\t\t\t\t\t\t\t\tline-height: 44rpx;\n\t\t\t\t\t\t\t\t\t\t\ttext-align: center;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t\n\t\n\t.good-detail-modal {\n\t\twidth: 100%;\n\t\theight: 100%;\n\t\tdisplay: flex;\n\t\tflex-direction: column;\n\t\n\t\t.cover {\n\t\t\theight: 20rpx;\n\t\t\tdisplay: flex;\n\t\t\tjustify-content: center;\n\t\t\talign-items: center;\n\t\n\t\t\t.btn-group {\n\t\t\t\tposition: absolute;\n\t\t\t\tright: 10rpx;\n\t\t\t\ttop: 0rpx;\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tjustify-content: space-around;\n\t\t\t\tz-index: 210;\n\t\t\t\t \n\t\t\t\timage {\n\t\t\t\t\twidth: 80rpx;\n\t\t\t\t\theight: 80rpx;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\n\t\t.detail {\n\t\t\twidth: 100%;\n\t\t\tmin-height: 1vh;\n\t\t\tmax-height: calc(90vh - 320rpx - 80rpx - 120rpx);\n\t\t\tposition: relative;\n\t\n\t\t\t.image {\n\t\t\t\tdisplay: flex;\n\t\t\t\tjustify-content: center;\n\t\t\t\talign-items: center;\n\t\t\t\timage {\n\t\t\t\t\twidth: 260rpx;\n\t\t\t\t\theight: 260rpx;\n\t\t\t\t}\n\t\t\t}\n\t\t\t.wrapper {\n\t\t\t\twidth: 100%;\n\t\t\t\theight: 100%;\n\t\t\t\toverflow: hidden;\n\t\n\t\t\t\t.basic {\n\t\t\t\t\tpadding: 0 20rpx 30rpx;\n\t\t\t\t\tdisplay: flex;\n\t\t\t\t\tflex-direction: column;\n\t\t\t\t\t.name {\n\t\t\t\t\t\tfont-size: $font-size-base;\n\t\t\t\t\t\tcolor: $text-color-base;\n\t\t\t\t\t\tmargin-bottom: 10rpx;\n\t\t\t\t\t}\n\t\t\t\t\t.tips {\n\t\t\t\t\t\tfont-size: $font-size-sm;\n\t\t\t\t\t\tcolor: $text-color-grey;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\n\t\t\t\t.properties {\n\t\t\t\t\twidth: 100%;\n\t\t\t\t\tborder-top: 2rpx solid $bg-color-grey;\n\t\t\t\t\tpadding: 10rpx 30rpx 0;\n\t\t\t\t\tdisplay: flex;\n\t\t\t\t\tflex-direction: column;\n\t\n\t\t\t\t\t.property {\n\t\t\t\t\t\twidth: 100%;\n\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\tflex-direction: column;\n\t\t\t\t\t\tmargin-bottom: 30rpx;\n\t\t\t\t\t\tpadding-bottom: -16rpx;\n\t\n\t\t\t\t\t\t.title {\n\t\t\t\t\t\t\twidth: 100%;\n\t\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\t\tjustify-content: flex-start;\n\t\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\t\tmargin-bottom: 20rpx;\n\t\n\t\t\t\t\t\t\t.name {\n\t\t\t\t\t\t\t\tfont-size: 26rpx;\n\t\t\t\t\t\t\t\tcolor: $text-color-base;\n\t\t\t\t\t\t\t\tmargin-right: 20rpx;\n\t\t\t\t\t\t\t}\n\t\n\t\t\t\t\t\t\t.desc {\n\t\t\t\t\t\t\t\tflex: 1;\n\t\t\t\t\t\t\t\tfont-size: $font-size-sm;\n\t\t\t\t\t\t\t\tcolor: $color-primary;\n\t\t\t\t\t\t\t\toverflow: hidden;\n\t\t\t\t\t\t\t\ttext-overflow: ellipsis;\n\t\t\t\t\t\t\t\twhite-space: nowrap;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\n\t\t\t\t\t\t.values {\n\t\t\t\t\t\t\twidth: 100%;\n\t\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\t\tflex-wrap: wrap;\n\t\n\t\t\t\t\t\t\t.value {\n\t\t\t\t\t\t\t\tborder-radius: 8rpx;\n\t\t\t\t\t\t\t\tbackground-color: $bg-color-grey;\n\t\t\t\t\t\t\t\tpadding: 16rpx 30rpx;\n\t\t\t\t\t\t\t\tfont-size: 26rpx;\n\t\t\t\t\t\t\t\tcolor: $text-color-assist;\n\t\t\t\t\t\t\t\tmargin-right: 16rpx;\n\t\t\t\t\t\t\t\tmargin-bottom: 16rpx;\n\t\n\t\t\t\t\t\t\t\t&.default {\n\t\t\t\t\t\t\t\t\tbackground-color: $color-primary;\n\t\t\t\t\t\t\t\t\tcolor: $text-color-white;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\n\t\t.action {\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tjustify-content: space-between;\n\t\t\tbackground-color: $bg-color-grey;\n\t\t\theight: 120rpx;\n\t\t\tpadding: 0 26rpx;\n\t\n\t\t\t.left {\n\t\t\t\tflex: 1;\n\t\t\t\tdisplay: flex;\n\t\t\t\tflex-direction: column;\n\t\t\t\tjustify-content: center;\n\t\t\t\tmargin-right: 20rpx;\n\t\t\t\toverflow: hidden;\n\t\n\t\t\t\t.price {\n\t\t\t\t\tfont-size: $font-size-lg;\n\t\t\t\t\tcolor: $text-color-base;\n\t\t\t\t}\n\t\n\t\t\t\t.props {\n\t\t\t\t\tcolor: $text-color-assist;\n\t\t\t\t\tfont-size: 24rpx;\n\t\t\t\t\twidth: 100%;\n\t\t\t\t\toverflow: hidden;\n\t\t\t\t\ttext-overflow: ellipsis;\n\t\t\t\t\twhite-space: nowrap;\n\t\t\t\t}\n\t\t\t}\n\t\t\t.btn-group {\n\t\t\t\tdisplay: flex;\n\t\t\t\talign-items: center;\n\t\t\t\tjustify-content: space-around;\n\t\n\t\t\t\t.number {\n\t\t\t\t\tfont-size: $font-size-base;\n\t\t\t\t\twidth: 44rpx;\n\t\t\t\t\theight: 44rpx;\n\t\t\t\t\tline-height: 44rpx;\n\t\t\t\t\ttext-align: center;\n\t\t\t\t}\n\t\n\t\t\t\t.btn {\n\t\t\t\t\tpadding: 0;\n\t\t\t\t\tfont-size: $font-size-base;\n\t\t\t\t\twidth: 44rpx;\n\t\t\t\t\theight: 44rpx;\n\t\t\t\t\tline-height: 44rpx;\n\t\t\t\t\tborder-radius: 100%;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\n\t\t.add-to-cart-btn {\n\t\t\tdisplay: flex;\n\t\t\tjustify-content: center;\n\t\t\talign-items: center;\n\t\t\tbackground-color: $color-primary;\n\t\t\tcolor: $text-color-white;\n\t\t\tfont-size: $font-size-base;\n\t\t\theight: 80rpx;\n\t\t\tborder-radius: 0 0 12rpx 12rpx;\n\t\t}\n\t}\n\t\n\t.cart-box {\n\t\tposition: fixed;\n\t\tbottom: 30rpx;\n\t\t/* #ifdef H5 */\n\t\tbottom:var(--window-bottom);\n\t\t//bottom: 100rpx;\n\t\t/* #endif */\n\t\tleft: 30rpx;\n\t\tright: 30rpx;\n\t\theight: 96rpx;\n\t\tborder-radius: 48rpx;\n\t\tbox-shadow: 0 0 20rpx rgba(0, 0, 0, 0.2);\n\t\tbackground-color: #ffffff;\n\t\tdisplay: flex;\n\t\talign-items: center;\n\t\tjustify-content: space-between;\n\t\tz-index: 9999;\n\t\n\t\t.cart-img {\n\t\t\twidth: 96rpx;\n\t\t\theight: 96rpx;\n\t\t\tposition: relative;\n\t\t\tmargin-top: -48rpx;\n\t\t}\n\t\n\t\t.pay-btn {\n\t\t\theight: 100%;\n\t\t\tpadding: 0 30rpx;\n\t\t\tcolor: #ffffff;\n\t\t\tborder-radius: 0 50rpx 50rpx 0;\n\t\t\tdisplay: flex;\n\t\t\talign-items: center;\n\t\t\tfont-size: $font-size-base;\n\t\t}\n\t\n\t\t.mark {\n\t\t\tpadding-left: 46rpx;\n\t\t\tmargin-right: 30rpx;\n\t\t\tposition: relative;\n\t\n\t\t\t.tag {\n\t\t\t\t//background-color: $color-warning;\n\t\t\t\tbackground-color: #09b4f1;;\n\t\t\t\tcolor: $text-color-white;\n\t\t\t\tdisplay: flex;\n\t\t\t\tjustify-content: center;\n\t\t\t\talign-items: center;\n\t\t\t\tfont-size: $font-size-sm;\n\t\t\t\tposition: absolute;\n\t\t\t\tright: -10rpx;\n\t\t\t\ttop: -50rpx;\n\t\t\t\tborder-radius: 100%;\n\t\t\t\tpadding: 4rpx;\n\t\t\t\twidth: 40rpx;\n\t\t\t\theight: 40rpx;\n\t\t\t\topacity: 0.9;\n\t\t\t}\n\t\t}\n\t\n\t\t.price {\n\t\t\tflex: 1;\n\t\t\tcolor: $text-color-base;\n\t\t}\n\t}\n\t\n\t.cart-popup {\n\t\t.top {\n\t\t\tbackground-color: $bg-color-primary;\n\t\t\t//color: $color-primary;\n\t\t\tcolor: #5A5B5C;\n\t\t\tpadding: 10rpx 30rpx;\n\t\t\tfont-size: 24rpx;\n\t\t\ttext-align: right;\n\t\t}\n\t\t.cart-list {\n\t\t\tbackground-color: #ffffff;\n\t\t\twidth: 100%;\n\t\t\toverflow: hidden;\n\t\t\tmin-height: 1vh;\n\t\t\tmax-height: 60vh;\n\t\n\t\t\t.wrapper {\n\t\t\t\theight: 100%;\n\t\t\t\tdisplay: flex;\n\t\t\t\tflex-direction: column;\n\t\t\t\tpadding: 0 30rpx;\n\t\t\t\tmargin-bottom: 156rpx;\n\t\n\t\t\t\t.item {\n\t\t\t\t\tdisplay: flex;\n\t\t\t\t\tjustify-content: space-between;\n\t\t\t\t\talign-items: center;\n\t\t\t\t\tpadding: 30rpx 0;\n\t\t\t\t\tposition: relative;\n\t\n\t\t\t\t\t&::after {\n\t\t\t\t\t\tcontent: ' ';\n\t\t\t\t\t\tposition: absolute;\n\t\t\t\t\t\tbottom: 0;\n\t\t\t\t\t\tleft: 0;\n\t\t\t\t\t\twidth: 100%;\n\t\t\t\t\t\tbackground-color: $border-color;\n\t\t\t\t\t\theight: 2rpx;\n\t\t\t\t\t\ttransform: scaleY(0.6);\n\t\t\t\t\t}\n\t\n\t\t\t\t\t.left {\n\t\t\t\t\t\tflex: 1;\n\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\tflex-direction: column;\n\t\t\t\t\t\toverflow: hidden;\n\t\t\t\t\t\tmargin-right: 30rpx;\n\t\n\t\t\t\t\t\t.name {\n\t\t\t\t\t\t\tfont-size: $font-size-sm;\n\t\t\t\t\t\t\tcolor: $text-color-base;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t.props {\n\t\t\t\t\t\t\tcolor: $text-color-assist;\n\t\t\t\t\t\t\tfont-size: 24rpx;\n\t\t\t\t\t\t\toverflow: hidden;\n\t\t\t\t\t\t\ttext-overflow: ellipsis;\n\t\t\t\t\t\t\twhite-space: nowrap;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\n\t\t\t\t\t.center {\n\t\t\t\t\t\tmargin-right: 120rpx;\n\t\t\t\t\t\tfont-size: $font-size-base;\n\t\t\t\t\t}\n\t\n\t\t\t\t\t.right {\n\t\t\t\t\t\tdisplay: flex;\n\t\t\t\t\t\talign-items: center;\n\t\t\t\t\t\tjustify-content: space-between;\n\t\n\t\t\t\t\t\t.btn {\n\t\t\t\t\t\t\twidth: 46rpx;\n\t\t\t\t\t\t\theight: 46rpx;\n\t\t\t\t\t\t\tborder-radius: 100%;\n\t\t\t\t\t\t\tpadding: 0;\n\t\t\t\t\t\t\ttext-align: center;\n\t\t\t\t\t\t\tline-height: 46rpx;\n\t\t\t\t\t\t}\n\t\n\t\t\t\t\t\t.number {\n\t\t\t\t\t\t\tfont-size: $font-size-base;\n\t\t\t\t\t\t\twidth: 46rpx;\n\t\t\t\t\t\t\theight: 46rpx;\n\t\t\t\t\t\t\ttext-align: center;\n\t\t\t\t\t\t\tline-height: 46rpx;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t\n\t.backgroud-grey {\n\t\tbackground-color: #e1e4e4;\n\t\tpadding: 15rpx !important;\n\t}\n</style>\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/pages/mine/mine.vue",
    "content": "<template>\n\t<layout>\n\t\t<uv-navbar\n\t\t  :fixed=\"false\"\n\t\t  :title=\"title\"\n\t\t  left-arrow\n\t\t  @leftClick=\"$onClickLeft\"\n\t\t/>\r\n\t\t<view class=\"container\">\r\n\t\t\t<view style=\"padding: 0 30rpx;\">\r\n\t\t\t\t<!-- user box begin -->\r\n\t\t\t\t<view class=\"d-flex flex-column bg-white user-box\">\r\n\r\n\t\t\t\t\t<view class=\"d-flex align-items-center\">\r\n\t\t\t\t\t\t<view class=\"avatar rounded-circle\">\r\n\t\t\t\t\t\t\t<image :src=\"isLogin ? member.avatar ? member.avatar : '/static/images/mine/default.png' : '/static/images/mine/default.png'\"></image>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<view class=\"d-flex flex-column flex-fill overflow-hidden\" style=\"margin-top: 20rpx;\">\r\n\t\t\t\t\t\t\t<view v-if=\"isLogin\"\r\n\t\t\t\t\t\t\t\tclass=\"font-size-lg font-weight-bold d-flex justify-content-start align-items-center\"\r\n\t\t\t\t\t\t\t\t@tap=\"serv({type:'pages',pages:'/pages/components/pages/mine/userinfo'})\">\r\n\t\t\t\t\t\t\t\t<view class=\"text-truncate\">{{ member.nickname }}</view>\r\n\t\t\t\t\t\t\t\t<view class=\"iconfont iconarrow-right line-height-100\"></view>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\t<view v-else class=\"font-size-lg font-weight-bold\" @tap=\"login\">游客</view>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<!-- user grid begin -->\r\n\t\t\t\t\t<view class=\"w-100 d-flex align-items-center just-content-center\">\r\n\t\t\t\t\t\t<view class=\"user-grid\" @tap=\"serv({type:'pages',pages:'/pages/components/pages/coupons/coupons'})\">\r\n\t\t\t\t\t\t\t<view class=\"value font-size-extra-lg font-weight-bold text-color-base\">\r\n\t\t\t\t\t\t\t\t{{ isLogin ? member.couponCount : 0}}\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\t<view class=\"font-size-sm text-color-assist\">优惠券</view>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<view class=\"user-grid\"  @tap=\"serv({type:'pages', pages: '/pages/components/pages/balance/bill?cate=1'})\">\r\n\t\t\t\t\t\t\t<view class=\"value font-size-extra-lg font-weight-bold text-color-base\">\r\n\t\t\t\t\t\t\t\t{{ isLogin ? member.integral : 0 }}\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\t<view class=\"font-size-sm text-color-assist\">积分</view>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<view class=\"user-grid\">\r\n\t\t\t\t\t\t\t<view class=\"value font-size-extra-lg font-weight-bold text-color-base\">\r\n\t\t\t\t\t\t\t\t{{ isLogin ? member.nowMoney : 0 }}\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\t<view class=\"font-size-sm text-color-assist\">余额</view>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<view class=\"user-grid\" @tap=\"serv({type:'pages', pages: '/pages/components/pages/balance/bill?cate=0'})\">\r\n\t\t\t\t\t\t\t<view class=\"value font-size-extra-lg font-weight-bold text-color-base\">\r\n\t\t\t\t\t\t\t\t{{ isLogin ? member.sumMoney : 0 }}\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\t<view class=\"font-size-sm text-color-assist\">历史消费</view>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<!-- user grid end -->\r\n\t\t\t\t</view>\r\n\t\t\t\t<!-- user box end -->\r\n\r\n\t\t\t</view>\r\n\t\t\t<!-- service box begin -->\r\n\t\t\t<view class=\"service-box\">\r\n\t\t\t\t<view class=\"font-size-lg text-color-base font-weight-bold\" style=\"margin-bottom: 20rpx;\">我的服务</view>\r\n\t\t\t\t<view class=\"u-m-t-20\">\r\n\t\t\t\t\t<uv-cell-group>\r\n\t\t\t\t\t\t<block v-for=\"(item, index) in services\" :key='index'>\r\n\t\t\t\t\t\t\t<uv-cell :title=\"item.name\" v-if=\"item.type == 'contact'\" :isLink=\"true\">\r\n\t\t\t\t\t\t\t\t<template #icon>\r\n\t\t\t\t\t\t\t\t\t<image :src=\"item.image\" style=\"width: 40rpx;height: 40rpx;\" class=\"mr-1\"></image>\r\n\t\t\t\t\t\t\t\t</template>\r\n\t\t\t\t\t\t\t</uv-cell>\r\n\t\t\t\t\t\t\t<uv-cell :isLink=\"true\" :title=\"item.name\" v-else-if=\"item.type == 'call'\" v-on:click=\"makePhoneCall(item.phone)\">\r\n\t\t\t\t\t\t\t\t<template #icon>\r\n\t\t\t\t\t\t\t\t\t<image :src=\"item.image\" style=\"width: 40rpx;height: 40rpx;\" class=\"mr-1\"></image>\r\n\t\t\t\t\t\t\t\t</template>\r\n\t\t\t\t\t\t\t</uv-cell>\r\n\t\t\t\t\t\t\t<uv-cell :isLink=\"true\" :title=\"item.name\" v-else @tap=\"serv(item)\">\r\n\t\t\t\t\t\t\t\t<template #icon>\r\n\t\t\t\t\t\t\t\t\t<image :src=\"item.image\" style=\"width: 40rpx;height: 40rpx;\" class=\"mr-1\"></image>\r\n\t\t\t\t\t\t\t\t</template>\r\n\t\t\t\t\t\t\t</uv-cell>\r\n\t\t\t\t\t\t</block>\r\n\t\t\t\t\t</uv-cell-group>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t</layout>\n</template>\r\n\r\n<script setup>\nimport {\r\n  ref,\n  computed\r\n} from 'vue'\nimport { useMainStore } from '@/store/store'\nimport { storeToRefs } from 'pinia'\nimport { onLoad,onShow} from '@dcloudio/uni-app'\nimport { formatDateTime,kmUnit } from '@/utils/util'\nimport {\n  userGetUserInfo,\n  mineService\r\n} from '@/api/user'\nconst main = useMainStore()\nconst { member,isLogin } = storeToRefs(main)\n\nconst title = ref('个人中心')\nconst services = ref([])\n\nconst growthValue = computed(() => { \n\tif (!isLogin.value) return 0\n\tconst {\n\t\tcurrentValue,\n\t\tneedValue\n\t} = member.value\n\treturn currentValue / (currentValue + needValue) * 100\n})\n\nonLoad(() => {\n\tgetServices();\n})\t\nonShow(() => {\n\tgetUserInfo();\n})\n\n\nconst getUserInfo = async() => {\r\n\tif (isLogin.value) {\r\n\t\tlet data = await userGetUserInfo();\r\n\t\tif (data) {\r\n\t\t\tmain.SET_MEMBER(data);\r\n\t\t}\r\n\t}\r\n}\r\nconst getServices = async() => {\r\n\tlet data = await mineService();\r\n\tif (data) {\r\n\t\tservices.value = data;\r\n\t}\r\n}\r\nconst makePhoneCall = (phoneNumber) => {\r\n\tuni.makePhoneCall({\r\n\t\tphoneNumber: phoneNumber,\r\n\t})\r\n}\r\nconst login = () => {\r\n\tuni.navigateTo({\r\n\t\turl: '/pages/components/pages/login/login'\r\n\t})\r\n}\r\nconst packages = () => {\r\n\tif (!isLogin.value) {\r\n\t\tlogin()\r\n\t\treturn\r\n\t}\r\n\tuni.navigateTo({\r\n\t\turl: '/pages/components/pages/packages/index'\r\n\t})\r\n}\r\nconst serv = (item) => {\r\n\tswitch (item.type) {\r\n\t\tcase 'pages':\r\n\t\t\tif (!isLogin.value) {\r\n\t\t\t\tlogin()\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t\tuni.navigateTo({\r\n\t\t\t\turl: item.pages\r\n\t\t\t})\r\n\t\t\tbreak;\r\n\t\tcase 'miniprogram':\r\n\t\t\tuni.navigateToMiniProgram({\r\n\t\t\t\tappId: item.app_id\r\n\t\t\t})\r\n\t\t\tbreak;\r\n\t\tcase 'menu':\r\n\t\t\tuni.navigateTo({\r\n\t\t\t\turl: '/pages/components/pages/mine/service?id=' + item.id + '&name=' + item.name\r\n\t\t\t})\r\n\t\t\tbreak;\r\n\t\tcase 'content':\r\n\t\t\tuni.navigateTo({\r\n\t\t\t\turl: '/pages/components/pages/mine/content?id=' + item.id + '&name=' + item.name\r\n\t\t\t})\r\n\t\t\tbreak;\r\n\t}\r\n}\n\n\r\n\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\tpage {\r\n\t\theight: auto;\r\n\t\tmin-height: 100%;\r\n\t}\r\n\r\n\r\n\t.user-box {\r\n\t\tposition: relative;\r\n\t\tborder-radius: 8rpx;\r\n\t\tmargin-bottom: 30rpx;\r\n\t\tmargin-top: 70rpx;\r\n\t\tbox-shadow: $box-shadow;\r\n\t}\r\n\r\n\t.avatar {\r\n\t\tposition: relative;\r\n\t\tmargin-top: -35rpx;\r\n\t\tmargin-left: 35rpx;\r\n\t\tmargin-right: 35rpx;\r\n\t\twidth: 160rpx;\r\n\t\theight: 160rpx;\r\n\t\tborder-radius: 20rpx;\r\n\t\tdisplay: flex;\r\n\t\talign-items: center;\r\n\t\tjustify-content: center;\r\n\t\tbackground-color: #FFFFFF;\r\n\t\tbox-shadow: 0 0 20rpx rgba($color: #000000, $alpha: 0.2);\r\n\r\n\t\timage {\r\n\t\t\twidth: 140rpx;\r\n\t\t\theight: 140rpx;\r\n\t\t\tborder-radius: 100%;\r\n\t\t}\r\n\r\n\t\t.badge {\r\n\t\t\tposition: absolute;\r\n\t\t\tright: -10rpx;\r\n\t\t\tbottom: -10rpx;\r\n\t\t\tbackground-color: #FFFFFF;\r\n\t\t\tborder-radius: 50rem;\r\n\t\t\tdisplay: flex;\r\n\t\t\talign-items: center;\r\n\t\t\tjustify-content: center;\r\n\t\t\tcolor: $color-warning;\r\n\t\t\tfont-size: 24rpx;\r\n\t\t\tpadding: 8rpx 16rpx;\r\n\t\t\tbox-shadow: 0 0 20rpx rgba($color: #000000, $alpha: 0.2);\r\n\r\n\t\t\timage {\r\n\t\t\t\twidth: 30rpx;\r\n\t\t\t\theight: 30rpx;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\r\n\t.user-grid {\r\n\t\twidth: 25%;\r\n\t\tpadding: 30rpx;\r\n\t\tdisplay: flex;\r\n\t\tflex-direction: column;\r\n\t\talign-items: center;\r\n\t\tjustify-content: center;\r\n\r\n\t\t.value {\r\n\t\t\tmargin-bottom: 20rpx;\r\n\t\t}\r\n\t}\r\n\r\n\t\r\n\r\n\t.service-box {\r\n\t\twidth: 100%;\r\n\t\tbackground-color: #FFFFFF;\r\n\t\tpadding: 32rpx 30rpx 10rpx;\r\n\t\tbox-shadow: $box-shadow;\r\n\r\n\t\t.row {\r\n\t\t\tdisplay: flex;\r\n\t\t\tflex-wrap: wrap;\r\n\t\t\tcolor: $text-color-assist;\r\n\t\t\tfont-size: $font-size-sm;\r\n\t\t\tpadding-bottom: -40rpx;\r\n\r\n\t\t\t.grid {\r\n\t\t\t\tdisplay: flex;\r\n\t\t\t\tflex-direction: column;\r\n\t\t\t\tjustify-content: center;\r\n\t\t\t\talign-items: center;\r\n\t\t\t\tmargin-bottom: 40rpx;\r\n\t\t\t\twidth: 25%;\r\n\t\t\t\tposition: relative;\r\n\r\n\t\t\t\timage {\r\n\t\t\t\t\twidth: 80rpx;\r\n\t\t\t\t\theight: 80rpx;\r\n\t\t\t\t\tmargin-bottom: 20rpx;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t.opacity-0 {\r\n\t\t\t\tposition: absolute;\r\n\t\t\t\twidth: 100%;\r\n\t\t\t\theight: 100%;\r\n\t\t\t\topacity: 0;\r\n\t\t\t\tz-index: 10;\r\n\t\t\t}\r\n\r\n\t\t}\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/pages/order/order.vue",
    "content": "<template>\n\t<uv-navbar\n\t  :fixed=\"false\"\n\t  :title=\"title\"\n\t  left-arrow\n\t  @leftClick=\"$onClickLeft\"\n\t/>\n\t<view class=\"container\">\n\t\t<view class=\"bg-white\">\n\t\t\t<uv-tabs :list=\"tabList\" :current=\"current\" @change=\"change\" keyName=\"name\" :scrollable=\"false\"></uv-tabs>\n\t\t</view>\n\t\t<view class=\"orders-list d-flex flex-column w-100\" style=\"padding: 20rpx; padding-bottom: 0;\">\n\t\t\t<view class=\"order-item\" v-for=\"(item, index) in orders\" :key=\"index\" style=\"margin-bottom: 30rpx;\" >\n\t\t\t\t<list-cell :hover=\"false\">\n\t\t\t\t\t<view class=\"w-100 d-flex align-items-center\">\n\t\t\t\t\t\t<view class=\"flex-fill d-flex flex-column\">\n\t\t\t\t\t\t\t<view class=\"font-size-lg text-color-base\" style=\"margin-bottom: 20rpx;\">\n\t\t\t\t\t\t\t\t{{ item.shop.name }}\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t<view class=\"font-size-sm text-color-assist\">取餐号：{{ item.numberId }}</view>\n\t\t\t\t\t\t\t<view class=\"font-size-sm text-color-assist\">订单编号：{{ item.orderId }}</view>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t\t<view class=\"font-size-lg text-color-primary\">\n\t\t\t\t\t\t\t{{ item.statusDto.title }}\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</view>\n\t\t\t\t</list-cell>\n\t\t\t\t<list-cell :hover=\"false\" last>\n\t\t\t\t\t<view class=\"w-100 d-flex flex-column\">\n\t\t\t\t\t\t<view class=\"w-100 text-truncate font-size-lg text-color-base\" style=\"margin-bottom: 20rpx;\">\n\t\t\t\t\t\t\t<view class=\"flex mb-2\" v-for=\"(good,index) in item.cartInfo\" :key=\"index\">  \n\t\t\t\t\t\t\t\t<image :src=\"good.image\" mode=\"aspectFill\" class=\"image\"></image>\n\t\t\t\t\t\t\t\t<view class=\"flex flex-column\">\n\t\t\t\t\t\t\t\t\t<view class=\"font-size-medium mt-1 text-color-base\">{{ good.title }}</view>\n\t\t\t\t\t\t\t\t\t<view class=\"font-size-sm mt-1\">{{ good.spec }}</view>\n\t\t\t\t\t\t\t\t\t<view class=\"font-size-sm mt-2\">×{{ good.number }}  ¥{{ good.price }}</view>\n\t\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t\t<view class=\"d-flex justify-content-between align-items-center\" style=\"margin-bottom: 30rpx;\">\n\t\t\t\t\t\t\t<view class=\"font-size-sm text-color-assist\">\n\t\t\t\t\t\t\t\t{{formatDateTime(item.createTime) }}\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t\t<view class=\"d-flex font-size-sm text-color-base align-items-center\">\n\t\t\t\t\t\t\t\t<view style=\"margin-right: 10rpx;\">共{{ goodsNum(item.cartInfo) }}件商品，实付</view>\n\t\t\t\t\t\t\t\t<view class=\"font-size-lg\">￥{{ item.payPrice }}</view>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t\t<view class=\"d-flex align-items-center justify-content-end\">\n\t\t\t\t\t\t\t<view>\n\t\t\t\t\t\t\t\t<button v-if=\"item.paid > 0 && item.status < 2 && item.refundStatus == 0\" class=\"left-margin\"  plain size=\"mini\" @tap.stop=\"receive(item)\">确认收到餐</button>\n\t\t\t\t\t\t\t\t<button class=\"left-margin\"  plain size=\"mini\" @tap=\"detail(item.orderId)\">订单详情</button>\n\t\t\t\t\t\t\t</view>\n\t\t\t\t\t\t</view>\n\t\t\t\t\t</view>\n\t\t\t\t</list-cell>\n\t\t\t</view>\n\t\t</view>\n\t\t<uv-empty v-if=\"orders.length == 0\" mode=\"order\"></uv-empty>\n\t</view>\n</template>\n\n\n<script setup>\nimport {\r\n  ref,\n  computed\r\n} from 'vue'\nimport { useMainStore } from '@/store/store'\nimport { storeToRefs } from 'pinia'\nimport { onLoad,onPullDownRefresh,onReachBottom} from '@dcloudio/uni-app'\nimport { formatDateTime,kmUnit } from '@/utils/util'\nimport {\n  orderGetOrders,\n  orderReceive\r\n} from '@/api/order'\nconst main = useMainStore()\nconst { isLogin } = storeToRefs(main)\nconst title = ref('我的订单')\n\nconst page = ref(1)\nconst pageSize = ref(10)\nconst orders = ref([])\nconst tabList = ref([{\n\t\t\ttype: -1,\n\t\t\tname: '全部',\n\t\t}, {\n\t\t\ttype: 0,\n\t\t\tname: '待支付',\n\t\t}, {\n\t\t\ttype: 1,\n\t\t\tname: '进行中'\n\t\t}, {\n\t\t\ttype: 4,\n\t\t\tname: '已完成'\n\t\t}, {\n\t\t\ttype: -3,\n\t\t\tname: '退款单'\n\t\t}]\n)\nconst current = ref(0)\nconst type = ref(-1)\n\nconst goodsNum = computed(() => { //计算单个饮品添加到购物车的数量\n\treturn (goods) => {\n\t\tlet num = 0;\n\t\tgoods.forEach(good => num += parseInt(good.number))\n\t\treturn num;\n\t}\n})\nonLoad(() => {\n\tif(!isLogin.value) {\n\t\tuni.navigateTo({url: '/pages/components/pages/login/login'})\n\t}\n\tgetOrders(false)\n})\nonPullDownRefresh(() => {\n\t getOrders(false)\n})\nonReachBottom(() => {\n\tgetOrders(false)\n})\n\n// tab栏切换\nconst change = (e) => {\n\t//console.log('e;',e.type)\n\t//console.log('e.index;',e.index)\n\ttype.value = e.type\n\tgetOrders(true)\n}\n\nconst getOrders = async(isRefresh = false) => {\n\tuni.showLoading({\n\t\ttitle: '加载中'\n\t})\n\tif(isRefresh) {\n\t\torders.value = []\n\t\tpage.value = 1\n\t}\n\tlet ordersData = await orderGetOrders({page:page.value, limit:pageSize.value,type:type.value});\n\n\tif(ordersData) {\n\t\torders.value = orders.value.concat(ordersData)\n\t\tpage.value += 1\n\t}\n\tuni.stopPullDownRefresh();\n\tuni.hideLoading()\n}\nconst detail = (id) => {\n\tuni.navigateTo({\n\t\turl: '/pages/components/pages/orders/detail?id=' + id\n\t})\n}\n// 确认收到货\nconst receive  = async(order) => {\n\tlet data = await orderReceive({uni:order.orderId});\n\tif (data) {\n\t\tawait getOrders(true)\n\t}\n}\n\t\n\n</script>\n\n<style lang=\"scss\" scoped>\n\t.left-margin {\n\t\tmargin-left: 10rpx;\n\t}\n\t.image {\n\t\twidth: 160rpx;\n\t\theight: 160rpx;\n\t\tmargin-right: 30rpx;\n\t\tborder-radius: 8rpx;\n\t}\n</style>\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/pages.json",
    "content": "{\n  \"pages\": [\n    {\n      \"path\": \"pages/index/index\",\n      \"style\": {\n        \"navigationBarTitleText\": \"首页\"\n      }\n    },\n   \n      {\n            \"path\" : \"pages/menu/menu\",\n            \"style\" :                                                                                    \n            {\n                \"navigationBarTitleText\": \"点餐\",\n                \"enablePullDownRefresh\": true\n            }\n            \n        }\n        ,{\n            \"path\" : \"pages/order/order\",\n            \"style\" :                                                                                    \n            {\n                \"navigationBarTitleText\": \"订单\",\n                \"enablePullDownRefresh\": true\n            }\n            \n        }\n        ,{\n            \"path\" : \"pages/mine/mine\",\n            \"style\" :                                                                                    \n            {\n                \"navigationBarTitleText\": \"我的\",\n                \"enablePullDownRefresh\": true\n            }\n            \n        }\n        ,{\n            \"path\" : \"pages/cart/cart\",\n            \"style\" :                                                                                    \n            {\n                \"navigationBarTitleText\": \"购物车\",\n                \"enablePullDownRefresh\": false\n            }\n            \n        }\n    ],\n\t\"subPackages\": [{\n\t\t\"root\": \"pages/components\",\n\t\t\"pages\": [ {\n\t\t\t\t\"path\": \"pages/mine/content\",\n\t\t\t\t\"style\": {\n\t\t\t\t\t\"navigationBarTitleText\": \"内容\",\n\t\t\t\t\t\"navigationBarTextStyle\": \"black\",\n\t\t\t\t\t\"navigationBarBackgroundColor\": \"#ffffff\"\n\t\t\t\t}\n\t\t\t}, {\n\t\t\t\t\"path\": \"pages/pay/pay\",\n\t\t\t\t\"style\": {\n\t\t\t\t\t\"navigationBarTitleText\": \"支付\",\n\t\t\t\t\t\"navigationBarTextStyle\": \"black\",\n\t\t\t\t\t\"navigationBarBackgroundColor\": \"#ffffff\"\n\t\t\t\t}\n\t\t\t}, {\n\t\t\t\t\"path\": \"pages/remark/remark\",\n\t\t\t\t\"style\": {\n\t\t\t\t\t\"navigationBarTitleText\": \"备注\",\n\t\t\t\t\t\"navigationBarTextStyle\": \"black\",\n\t\t\t\t\t\"navigationBarBackgroundColor\": \"#ffffff\"\n\t\t\t\t}\n\t\t\t}, {\n\t\t\t\t\"path\": \"pages/packages/index\",\n\t\t\t\t\"style\": {\n\t\t\t\t\t\"navigationBarTitleText\": \"可用优惠券\",\n\t\t\t\t\t\"navigationBarTextStyle\": \"black\",\n\t\t\t\t\t\"navigationBarBackgroundColor\": \"#ffffff\",\n\t\t\t\t\t\"enablePullDownRefresh\": true\n\t\t\t\t}\n\t\t\t },\n\t\t\t {\n\t\t\t\t\"path\": \"pages/login/login\",\n\t\t\t\t\"style\": {\n\t\t\t\t\t\"navigationBarTitleText\": \"登录\",\n\t\t\t\t\t\"navigationBarTextStyle\": \"black\",\n\t\t\t\t\t\"navigationBarBackgroundColor\": \"#ffffff\"\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"path\": \"pages/login/logout\",\n\t\t\t\t\"style\": {\n\t\t\t\t\t\"navigationBarTitleText\": \"退出登录\"\n\t\t\t\t}\n\t\t\t},\n\t\t\t{\n\t\t\t\t\"path\": \"pages/address/address\",\n\t\t\t\t\"style\": {\n\t\t\t\t\t\"navigationBarTitleText\": \"我的地址\",\n\t\t\t\t\t\"navigationBarTextStyle\": \"black\",\n\t\t\t\t\t\"navigationBarBackgroundColor\": \"#ffffff\"\n\t\t\t\t}\n\t\t\t}, {\n\t\t\t\t\"path\": \"pages/address/add\",\n\t\t\t\t\"style\": {\n\t\t\t\t\t\"navigationBarTitleText\": \"添加地址\",\n\t\t\t\t\t\"navigationBarTextStyle\": \"black\",\n\t\t\t\t\t\"navigationBarBackgroundColor\": \"#ffffff\"\n\t\t\t\t}\n\t\t\t}, {\n\t\t\t\t\"path\": \"pages/orders/orders\",\n\t\t\t\t\"style\": {\n\t\t\t\t\t\"navigationBarTitleText\": \"历史订单\",\n\t\t\t\t\t\"navigationBarTextStyle\": \"black\",\n\t\t\t\t\t\"navigationBarBackgroundColor\": \"#ffffff\",\n\t\t\t\t\t\"enablePullDownRefresh\": true,\n\t\t\t\t\t\"onReachBottomDistance\": 50\n\t\t\t\t}\n\t\t\t}, {\n\t\t\t\t\"path\": \"pages/orders/detail\",\n\t\t\t\t\"style\": {\n\t\t\t\t\t\"navigationBarTitleText\": \"订单详情\",\n\t\t\t\t\t\"navigationBarTextStyle\": \"black\",\n\t\t\t\t\t\"navigationBarBackgroundColor\": \"#ffffff\"\n\t\t\t\t}\n\t\t\t}, {\n\t\t\t\t\"path\": \"pages/coupons/coupons\",\n\t\t\t\t\"style\": {\n\t\t\t\t\t\"enablePullDownRefresh\": true,\n\t\t\t\t\t\"navigationBarTitleText\": \"我的卡券\",\n\t\t\t\t\t\"navigationBarTextStyle\": \"black\",\n\t\t\t\t\t\"navigationBarBackgroundColor\": \"#ffffff\"\n\t\t\t\t}\n\t\t\t}, {\n\t\t\t\t\"path\": \"pages/mine/userinfo\",\n\t\t\t\t\"style\": {\n\t\t\t\t\t\"navigationBarTitleText\": \"用户信息\",\n\t\t\t\t\t\"navigationBarTextStyle\": \"black\",\n\t\t\t\t\t\"navigationBarBackgroundColor\": \"#ffffff\"\n\t\t\t\t}\n\t\t\t},\n\t\t\t {\n\t\t\t\t\"path\": \"pages/shop/shop\",\n\t\t\t\t\"style\": {\n\t\t\t\t\t\"navigationBarTitleText\": \"店铺\",\n\t\t\t\t\t\"navigationBarTextStyle\": \"black\",\n\t\t\t\t\t\"navigationBarBackgroundColor\": \"#ffffff\"\n\t\t\t\t}\n\t\t\t}, {\n\t\t\t\t\"path\": \"pages/balance/bill\",\n\t\t\t\t\"style\": {\n\t\t\t\t\t\"navigationBarTitleText\": \"账单\",\n\t\t\t\t\t\"navigationBarTextStyle\": \"black\",\n\t\t\t\t\t\"navigationBarBackgroundColor\": \"#ffffff\"\n\t\t\t\t}\n\t\t\t}\n\t\t    ,{\n                    \"path\" : \"pages/orders/refund\",\n                    \"style\" :                                                                                    \n\t\t\t\t\t{\n\t\t\t\t\t\t\"navigationBarTitleText\": \"退款\",\n\t\t\t\t\t\t\"enablePullDownRefresh\": false\n\t\t\t\t\t}\n                \n            },\n\t\t\t{\n\t\t\t\t\"path\": \"pages/scoreproduct/list\",\n\t\t\t\t\"style\": {\n\t\t\t\t\t\"navigationBarTitleText\": \"积分商城\",\n\t\t\t\t\t\"enablePullDownRefresh\": true\n\t\t\t\t}\n\t\t\t\n\t\t\t}, {\n\t\t\t\t\"path\": \"pages/scoreproduct/detail\",\n\t\t\t\t\"style\": {\n\t\t\t\t\t\"navigationBarTitleText\": \"产品详情\",\n\t\t\t\t\t\"enablePullDownRefresh\": true\n\t\t\t\t}\n\t\t\t\n\t\t\t}, {\n\t\t\t\t\"path\": \"pages/scoreproduct/order\",\n\t\t\t\t\"style\": {\n\t\t\t\t\t\"navigationBarTitleText\": \"订单列表\",\n\t\t\t\t\t\"enablePullDownRefresh\": true\n\t\t\t\t}\n\t\t\t}, {\n\t\t\t\t\"path\": \"pages/scoreproduct/orderDetail\",\n\t\t\t\t\"style\": {\n\t\t\t\t\t\"navigationBarTitleText\": \"订单详情\",\n\t\t\t\t\t\"enablePullDownRefresh\": true\n\t\t\t\t}\n\t\t\t}\n\t\t\t,{\n\t\t\t        \"path\" : \"pages/scoreproduct/confirm\",\n\t\t\t        \"style\" :                                                                                    \n\t\t\t    {\n\t\t\t        \"navigationBarTitleText\": \"确认\",\n\t\t\t        \"enablePullDownRefresh\": false\n\t\t\t    }\n\t\t\t    \n\t\t\t    }\n            ]\n\t}],\n  \"globalStyle\": {\n    \"navigationBarTextStyle\": \"black\",\n    \"navigationBarTitleText\": \" \",\n    \"navigationBarBackgroundColor\": \"#F8F8F8\",\n    \"backgroundColor\": \"#F8F8F8\",\n    \"navigationStyle\": \"custom\"\n  },\n  \"tabBar\": {\n  \t\t\"color\": \"#919293\",\n  \t\t\"selectedColor\": \"#09b4f1\",\n  \t\t\"backgroundColor\": \"#fff\",\n  \t\t\"borderStyle\": \"black\",\n  \t\t\"list\": [{\n  \t\t\t\t\"pagePath\": \"pages/index/index\",\n  \t\t\t\t\"text\": \"首页\",\n  \t\t\t\t\"iconPath\": \"static/images/tabBar/index.png\",\n  \t\t\t\t\"selectedIconPath\": \"static/images/tabBar/index_selected.png\"\n  \t\t\t},\n  \t\t\t{\n  \t\t\t\t\"pagePath\": \"pages/menu/menu\",\n  \t\t\t\t\"text\": \"点餐\",\n  \t\t\t\t\"iconPath\": \"static/images/tabBar/drink.png\",\n  \t\t\t\t\"selectedIconPath\": \"static/images/tabBar/drink-selected.png\"\n  \t\t\t}, {\n  \t\t\t\t\"pagePath\": \"pages/cart/cart\",\n  \t\t\t\t\"text\": \"购物车\",\n  \t\t\t\t\"iconPath\": \"static/images/tabBar/cart.png\",\n  \t\t\t\t\"selectedIconPath\": \"static/images/tabBar/cart-selected.png\"\n  \t\t\t},{\n  \t\t\t\t\"pagePath\": \"pages/order/order\",\n  \t\t\t\t\"text\": \"订单\",\n  \t\t\t\t\"iconPath\": \"static/images/tabBar/order.png\",\n  \t\t\t\t\"selectedIconPath\": \"static/images/tabBar/order-selected.png\"\n  \t\t\t}, {\n  \t\t\t\t\"pagePath\": \"pages/mine/mine\",\n  \t\t\t\t\"text\": \"我的\",\n  \t\t\t\t\"iconPath\": \"static/images/tabBar/mine.png\",\n  \t\t\t\t\"selectedIconPath\": \"static/images/tabBar/mine_selected.png\"\n  \t\t\t}\n  \t\t]\n  \t},\n  \"uniIdRouter\": {}\n}\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/static/iconfont/iconfont.css",
    "content": "@font-face {\n  font-family: \"iconfont\"; /* Project id 4205200 */\n  src: url('~@/static/iconfont/iconfont.woff2?t=1691643169117') format('woff2'),\n       url('~@/static/iconfont/iconfont.woff?t=1691643169117') format('woff'),\n       url('~@/static/iconfont/iconfont.ttf?t=1691643169117') format('truetype');\n}\n\n.iconfont {\n  font-family: \"iconfont\" !important;\n  font-size: 16px;\n  font-style: normal;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n\n.icon-waimai-:before {\n  content: \"\\e607\";\n}\n\n.icon-tangshi:before {\n  content: \"\\e645\";\n}\n\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/static/iconfont/iconfont.scss",
    "content": "\n@font-face {\n  font-family: 'iconfont-yshop';  \n  src: url('//at.alicdn.com/t/font_2012069_r7oux4ay0ls.eot');\n  src: url('//at.alicdn.com/t/font_2012069_r7oux4ay0ls.eot?#iefix') format('embedded-opentype'),\n  url('//at.alicdn.com/t/font_2012069_r7oux4ay0ls.woff2') format('woff2'),\n  url('//at.alicdn.com/t/font_2012069_r7oux4ay0ls.woff') format('woff'),\n  url('//at.alicdn.com/t/font_2012069_r7oux4ay0ls.ttf') format('truetype'),\n  url('//at.alicdn.com/t/font_2012069_r7oux4ay0ls.svg#iconfont-yshop') format('svg');\n\n}\n\n.iconfont-yshop {\n\tfont-family: 'iconfont-yshop' !important;\n\tfont-size: 16px;\n\tfont-style: normal;\n\t-webkit-font-smoothing: antialiased;\n\t-moz-osx-font-smoothing: grayscale;\n\tcolor: #5A5B5C;\n}\n.icon-alipay:before {\n\tcontent: '\\e60a';\n}\n.icon-shop:before {\n\tcontent: '\\e664';\n}\n.icon-takein:before {\n\tcontent: '\\e666';\n}\n.icon-takeout:before {\n\tcontent: '\\e667';\n}\n.icon-location:before {\n\tcontent: '\\e633';\n}\n.icon-mobile:before {\n\tcontent: '\\e602';\n}\n.icon-box:before {\n\tcontent: '\\e665';\n}\n.icon-lamp:before { \n\tcontent: '\\e668';\n}\n.icon-doorbell:before {\n\tcontent: '\\e662';\n}\n.icon-daojishi:before {\n\tcontent: '\\e625';\n}\n\n@font-face {\n\tfont-family: 'iconfont';\n\tsrc: url('//at.alicdn.com/t/font_1789197_z1gzlwq7idq.ttf?t=1589441233693') format('truetype');\n}\n\n.iconfont {\n\tfont-family: 'iconfont' !important;\n\tfont-size: 16px;\n\tfont-style: normal;\n\t-webkit-font-smoothing: antialiased;\n\t-moz-osx-font-smoothing: grayscale;\n}\n\n.iconshoucang:before {\n\tcontent: '\\e667';\n}\n\n.iconshoucangfill:before {\n\tcontent: '\\e6c9';\n}\n\n.iconjifen:before {\n\tcontent: '\\e642';\n}\n\n.iconradio-button-off:before {\n\tcontent: '\\e688';\n}\n\n.iconradio-button-on:before {\n\tcontent: '\\e689';\n}\n\n.iconhelp:before {\n\tcontent: '\\e752';\n}\n\n.iconwxpay:before {\n\tcontent: '\\e611';\n}\n\n.iconbalance:before {\n\tcontent: '\\e619';\n}\n\n.iconadd-select:before {\n\tcontent: '\\e7b0';\n}\n\n.iconsami-select:before {\n\tcontent: '\\e7b1';\n}\n\n.iconmap:before {\n\tcontent: '\\e758';\n}\n\n.iconsuccess:before {\n\tcontent: '\\e767';\n}\n\n.iconsuccess-fill:before {\n\tcontent: '\\e78d';\n}\n\n.iconiconset0136:before {\n\tcontent: '\\e623';\n}\n\n.iconzan:before {\n\tcontent: '\\e640';\n}\n\n.iconjifenqiandao:before {\n\tcontent: '\\e6a6';\n}\n\n.iconshouyeshouye:before {\n\tcontent: '\\e606';\n}\n\n.icondaohang:before {\n\tcontent: '\\e641';\n}\n\n.iconwodelianxikefu:before {\n\tcontent: '\\e671';\n}\n\n.iconwodexinyuan:before {\n\tcontent: '\\e675';\n}\n\n.iconphone:before {\n\tcontent: '\\e6dd';\n}\n\n.icondingdan:before {\n\tcontent: '\\e645';\n}\n\n.iconliwu:before {\n\tcontent: '\\e61c';\n}\n\n.iconyinpinyinliao:before {\n\tcontent: '\\e60d';\n}\n\n.iconyinpin:before {\n\tcontent: '\\e70b';\n}\n\n.iconwaimaixinxi:before {\n\tcontent: '\\e685';\n}\n\n.iconico:before {\n\tcontent: '\\e646';\n}\n\n.iconwode:before {\n\tcontent: '\\e616';\n}\n\n.icongengduofuwu:before {\n\tcontent: '\\e607';\n}\n\n.iconqucan:before {\n\tcontent: '\\e625';\n}\n\n.iconyou:before {\n\tcontent: '\\e618';\n}\n\n.iconshouhuodizhi:before {\n\tcontent: '\\e666';\n}\n\n.iconshangcheng:before {\n\tcontent: '\\e63b';\n}\n\n.iconadd:before {\n\tcontent: '\\e742';\n}\n\n.iconarrow-right:before {\n\tcontent: '\\e743';\n}\n\n.iconarrow-lift:before {\n\tcontent: '\\e744';\n}\n\n.iconarrow-up:before {\n\tcontent: '\\e745';\n}\n\n.iconclose:before {\n\tcontent: '\\e747';\n}\n\n.iconleftbutton:before {\n\tcontent: '\\e755';\n}\n\n.iconreduce:before {\n\tcontent: '\\e75e';\n}\n\n.iconseleted:before {\n\tcontent: '\\e763';\n}\n\n.iconRightbutton:before {\n\tcontent: '\\e765';\n}\n\n.iconleftbutton-fill:before {\n\tcontent: '\\e782';\n}\n\n.iconRightbutton-fill:before {\n\tcontent: '\\e78a';\n}\n\n.iconarrow-down:before {\n\tcontent: '\\e7b2';\n}\n\n.iconaixin1:before {\n\tcontent: '\\e63c';\n}\n\n@font-face {\n\tfont-family: \"iconfont\"; /* Project id 4205200 */\n\tsrc: url('~@/static/iconfont/iconfont.woff2') format('woff2'),\n\t\t url('~@/static/iconfont/iconfont.woff') format('woff'),\n\t\t url('~@/static/iconfont/iconfont.ttf') format('truetype');\n  }\n  \n  .iconfont {\n\tfont-family: \"iconfont\" !important;\n\tfont-size: 16px;\n\tfont-style: normal;\n\t-webkit-font-smoothing: antialiased;\n\t-moz-osx-font-smoothing: grayscale;\n  }\n  \n  .icon-waimai-:before {\n\tcontent: \"\\e607\";\n  }\n  \n  .icon-tangshi:before {\n\tcontent: \"\\e645\";\n  }"
  },
  {
    "path": "yshop-drink-uniapp-vue3/static/style/app.scss",
    "content": "@import '~@/uni.scss';\n\npage,\nview,\nscroll-view,\ntext,\nimage,\ntextarea,\nbutton,\ninput {\n\tbox-sizing: border-box;\n}\n\npage {\n\tbackground-color: #F1F8FA;\n\theight: 100%;\n}\n\n.container {\n\twidth: 100%;\n\theight: 100%;\n}\n\nbutton {\n\tmargin: 0;\n\n\t&[type='primary'] {\n\t\tbackground-color: $color-primary;\n\t\t//background-color: #5A5B5C;\n\t\tcolor: #ffffff;\n\t\tfont-size: $font-size-base;\n\t\t\n\t\t&[disabled] {\n\t\t\tbackground-color: #D1D78C;\n\t\t}\n\t\t\n\t\t&[plain] {\n\t\t\tcolor: $color-primary;\n\t\t\tborder: 1rpx solid $color-primary;\n\t\t}\n\t\t&.button-hover {\n\t\t\tbackground-color: #d5da91;\n\t\t}\n\t\t&::after {\n\t\t\tborder: 0;\n\t\t}\n\t}\n\n\t&[type='default'] {\n\t\t&[plain] {\n\t\t\tcolor: $text-color-assist;\n\t\t\tborder: 1rpx solid $text-color-assist;\n\t\t}\n\t}\n}\n\n.bg-base {\n\tbackground-color: $bg-color;\n}\n\n.bg-white {\n\tbackground-color: #ffffff;\n}\n\n.bg-transparent {\n  background-color: transparent !important;\n}\n\n.bg-primary {\n\tbackground-color: $color-primary;\n}\n\n.bg-warning {\n\tbackground-color: $color-warning;\n}\n\n.d-flex {\n\tdisplay: flex;\n}\n\n.d-none {\n\tdisplay: none !important;\n}\n\n.invisible {\n  visibility: hidden !important;\n}\n\n.d-inline {\n\tdisplay: inline !important;\n}\n\n.d-inline-block {\n\tdisplay: inline-block !important;\n}\n\n.d-block {\n\tdisplay: block !important;\n}\n\n.flex-column {\n\t-ms-flex-direction: column !important;\n\tflex-direction: column !important;\n}\n\n.justify-content-start {\n\tjustify-content: flex-start;\n}\n\n.justify-content-end {\n\tjustify-content: flex-end;\n}\n\n.justify-content-between {\n\tjustify-content: space-between;\n}\n\n.just-content-center {\n\tjustify-content: center;\n}\n\n.justify-content-evenly {\n\tjustify-content: space-evenly !important;\n}\n\n.just-content-around {\n\tjustify-content: space-around;\n}\n\n.align-items-start {\n\talign-items: flex-start;\n}\n\n.align-items-end {\n\talign-items: flex-end;\n}\n\n.align-items-center {\n\talign-items: center;\n}\n\n.align-items-between {\n\talign-items: space-between;\n}\n\n.align-items-around {\n\talign-items: space-around;\n}\n\n.align-items-stretch {\n\talign-items: stretch;\n}\n\n.align-items-baseline {\n\t-ms-flex-align: baseline !important;\n\talign-items: baseline !important;\n}\n\n.flex-fill {\n\t-ms-flex: 1 1 auto !important;\n\tflex: 1 1 auto !important;\n}\n\n.flex-wrap {\n\t-ms-flex-wrap: wrap !important;\n\tflex-wrap: wrap !important;\n}\n\n.flex-nowrap {\n\t-ms-flex-wrap: nowrap !important;\n\tflex-wrap: nowrap !important;\n}\n\n.flex-shrink-0 {\n\t-ms-flex-negative: 0 !important;\n\tflex-shrink: 0 !important;\n}\n\n.font-size-base {\n\tfont-size: 28rpx;\n}\n\n.font-size-sm {\n\tfont-size: 24rpx;\n}\n\n.font-size-medium {\n\tfont-size: 26rpx;\n}\n\n.font-size-lg {\n\tfont-size: 32rpx;\n}\n\n.font-size-extra-lg {\n\tfont-size: 40rpx;\n}\n\n.text-color-base {\n\tcolor: $text-color-base;\n}\n\n.text-color-assist {\n\tcolor: $text-color-assist;\n}\n\n.text-color-primary {\n\tcolor: $color-primary;\n}\n\n.text-color-danger {\n\tcolor: $color-error;\n}\n\n.text-color-white {\n\tcolor: #ffffff;\n}\n\n.text-color-warning {\n\tcolor: $color-warning;\n}\n\n.text-truncate {\n\toverflow: hidden;\n\ttext-overflow: ellipsis;\n\twhite-space: nowrap;\n}\n\n.font-weight-bold {\n\tfont-weight: 700 !important;\n}\n\n.font-weight-light {\n\tfont-weight: 300 !important;\n}\n\n.font-weight-lighter {\n\tfont-weight: lighter !important;\n}\n\n.font-weight-normal {\n\tfont-weight: 400 !important;\n}\n\n.overflow-auto {\n\toverflow: auto !important;\n}\n\n.overflow-hidden {\n\toverflow: hidden !important;\n}\n\n.position-relative {\n\tposition: relative !important;\n}\n\n.position-absolute {\n\tposition: absolute !important;\n}\n\n.position-fixed {\n\tposition: fixed !important;\n}\n\n.fixed-top {\n\tposition: fixed;\n\ttop: 0;\n\tright: 0;\n\tleft: 0;\n\tz-index: 1030;\n}\n\n.fixed-bottom {\n\tposition: fixed;\n\tright: 0;\n\tbottom: 0;\n\tleft: 0;\n\tz-index: 1030;\n}\n\n.line-height-100 {\n\tline-height: 100%;\n}\n\n.line-height-2 {\n\tline-height: 2rem !important;\n}\n\n.line-height-50 {\n\tline-height: 50rem !important;\n}\n\n.w-25 {\n\twidth: 25% !important;\n}\n\n.w-50 {\n\twidth: 50% !important;\n}\n\n.w-75 {\n\twidth: 75% !important;\n}\n\n.w-80 {\n\twidth: 80% !important;\n}\n\n.w-90 {\n\twidth: 90% !important;\n}\n\n.w-100 {\n\twidth: 100% !important;\n}\n\n.h-100 {\n\theight: 100% !important;\n}\n\n.text-left {\n\ttext-align: left !important;\n}\n\n.text-right {\n\ttext-align: right !important;\n}\n\n.text-center {\n\ttext-align: center !important;\n}\n\n.border-box {\n\tbox-sizing: border-box;\n}\n\n.rounded-circle {\n\tborder-radius: 50% !important;\n}\n\n.rounded-pill {\n\tborder-radius: 50rem !important;\n}\n\n.border-radius-base {\n\tborder-radius: $border-radius-base;\n}\n\n.pre-line {\n\twhite-space: pre-line;\n}\n\n.align-top {\n\tvertical-align: top !important;\n}\n\n.align-middle {\n\tvertical-align: middle !important;\n}\n\n.align-bottom {\n\tvertical-align: bottom !important;\n}\n\n.align-text-bottom {\n\tvertical-align: text-bottom !important;\n}\n\n.align-text-top {\n\tvertical-align: text-top !important;\n}\n\n.w-60 {\n\twidth: 60%;\n}\n\n.w-40 {\n\twidth: 40%;\n}\n\n.mb-10 {\n\tmargin-bottom: 10rpx;\n}\n\n.mb-20 {\n\tmargin-bottom: 20rpx;\n}\n\n.mb-30 {\n\tmargin-bottom: 30rpx;\n}\n\n.mb-40 {\n\tmargin-bottom: 40rpx;\n}\n\n.mb-50 {\n\tmargin-bottom: 50rpx;\n}\n\n.mt-30 {\n\tmargin-top: 30rpx;\n}\n\n.ml-10 {\n\tmargin-left: 10rpx;\n}\n\n.ml-20 {\n\tmargin-left: 20rpx;\n}\n\n.ml-30 {\n\tmargin-left: 30rpx;\n}\n\n.mr-10 {\n\tmargin-right: 10rpx;\n}\n\n.mr-20 {\n\tmargin-right: 20rpx;\n}\n\n.mr-30 {\n\tmargin-right: 30rpx;\n}\n\n.mr-40 {\n\tmargin-right: 40rpx;\n}\n\n.pl-30 {\n\tpadding-left: 30rpx;\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/static/style/style.css",
    "content": "body {\n  background-color: #f5f5f5;\n}\npage {\n  background-color: #f5f5f5;\n  min-height: 100%;\n}\nimage {\n  max-width: 100%;\n  height: auto;\n}\n:root {\n  --van-primary-color: #ee6d46;\n}\n.van-grid-item__content {\n  padding: 0 var(--van-padding-base);\n}\n.scroll-view-H {\n  white-space: nowrap;\n  width: 100%;\n}\n.page-space {\n  padding: 0 34rpx;\n}\n.page-card {\n  background: #fff;\n  -webkit-border-radius: 15rpx;\n          border-radius: 15rpx;\n}\n.mb-10 {\n  margin-bottom: 10rpx;\n}\n.mb-15 {\n  margin-bottom: 15rpx;\n}\n.mb-20 {\n  margin-bottom: 20rpx;\n}\n.mb-25 {\n  margin-bottom: 25rpx;\n}\n.mb-30 {\n  margin-bottom: 30rpx;\n}\n.pl-10 {\n  padding-left: 10rpx;\n}\n.pl-15 {\n  padding-left: 15rpx;\n}\n.pl-20 {\n  padding-left: 20rpx;\n}\n.pl-25 {\n  padding-left: 25rpx;\n}\n.pl-30 {\n  padding-left: 30rpx;\n}\n.pl-40 {\n  padding-left: 40rpx;\n}\n.pr-10 {\n  padding-right: 10rpx;\n}\n.pr-15 {\n  padding-right: 15rpx;\n}\n.pr-20 {\n  padding-right: 20rpx;\n}\n.pr-25 {\n  padding-right: 25rpx;\n}\n.pr-30 {\n  padding-right: 30rpx;\n}\n.pr-40 {\n  padding-right: 40rpx;\n}\n.p-40 {\n  padding: 40rpx;\n}\n.paddingH-10 {\n  padding: 0 20rpx;\n}\n.border-top {\n  position: relative;\n}\n.border-top::after {\n  content: '';\n  position: absolute;\n  left: 0;\n  top: 0;\n  right: 0;\n  height: 1rpx;\n  background: #e6e6e6;\n}\n.van-button {\n  -webkit-border-radius: 0;\n          border-radius: 0;\n}\n.van-button--mini {\n  font-size: 24rpx;\n}\n.icon .image {\n  display: block;\n  width: 100%;\n  height: 100%;\n}\n.infos {\n  padding: 20rpx 37rpx 20rpx;\n}\n.infos.infos-right .info-cell-value {\n  text-align: right;\n}\n.infos .info-cell {\n  margin-bottom: 20rpx;\n  display: -webkit-box;\n  display: -webkit-flex;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-align: center;\n  -webkit-align-items: center;\n      -ms-flex-align: center;\n          align-items: center;\n  -webkit-box-pack: justify;\n  -webkit-justify-content: space-between;\n      -ms-flex-pack: justify;\n          justify-content: space-between;\n}\n.infos .info-cell-label {\n  margin-right: 30rpx;\n  line-height: 32rpx;\n  font-size: 24rpx;\n  color: #999999;\n}\n.infos .info-cell-value {\n  -webkit-box-flex: 1;\n  -webkit-flex: 1;\n      -ms-flex: 1;\n          flex: 1;\n  line-height: 32rpx;\n  font-size: 24rpx;\n  color: #333333;\n}\n.infos .info-cell-operation {\n  line-height: 32rpx;\n  font-size: 24rpx;\n  color: #ee6d46;\n}\n.simple-cell {\n  display: -webkit-box;\n  display: -webkit-flex;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-align: center;\n  -webkit-align-items: center;\n      -ms-flex-align: center;\n          align-items: center;\n}\n.simple-cell-icon {\n  width: 64rpx;\n  height: 64rpx;\n}\n.simple-cell-content {\n  margin-left: 15rpx;\n}\n.simple-cell-title {\n  line-height: 40rpx;\n  font-size: 28rpx;\n  font-weight: 500;\n  color: #333333;\n}\n.simple-cell-label {\n  line-height: 30rpx;\n  font-size: 22rpx;\n  color: #333333;\n}\n.time-bar {\n  background: #fff;\n  height: 150rpx;\n}\n.time-bar .van-tab--active .time-bar-item {\n  background: #e96b45;\n}\n.time-bar .van-tab--active .time-bar-item .time {\n  color: #fff;\n}\n.time-bar .van-tab--active .time-bar-item .status {\n  color: #fff;\n}\n.time-bar-item {\n  width: 180rpx;\n  height: 150rpx;\n  background: #ffffff;\n  display: -webkit-box;\n  display: -webkit-flex;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-orient: vertical;\n  -webkit-box-direction: normal;\n  -webkit-flex-direction: column;\n      -ms-flex-direction: column;\n          flex-direction: column;\n  -webkit-box-align: center;\n  -webkit-align-items: center;\n      -ms-flex-align: center;\n          align-items: center;\n}\n.time-bar-item .time {\n  margin-top: 26rpx;\n  line-height: 1em;\n  font-size: 34rpx;\n  font-weight: bold;\n  color: #333333;\n  margin-bottom: 10rpx;\n}\n.time-bar-item .status {\n  line-height: 1em;\n  font-size: 24rpx;\n  font-weight: 400;\n  color: #999999;\n}\n.time-bar-item .countdown {\n  -webkit-box-flex: 1;\n  -webkit-flex: 1;\n      -ms-flex: 1;\n          flex: 1;\n  height: 1em;\n  font-size: 20rpx;\n  color: #ffffff;\n  font-weight: 100;\n}\n.time-bar .van-tab {\n  padding: 0;\n}\n.time-bar .van-tabs--line .van-tabs__wrap {\n  height: 150rpx;\n}\n.time-bar .van-tabs__nav--line {\n  padding-bottom: 0;\n  padding: 0;\n}\n.time-bar .van-tabs__wrap {\n  height: 150rpx;\n}\n.time-bar .van-tabs__line {\n  display: none;\n}\n.card {\n  background: #ffffff;\n  -webkit-border-radius: 15rpx;\n          border-radius: 15rpx;\n}\n.card.noBorder .card-head {\n  border-bottom: 0;\n}\n.card.noBorder .card-title {\n  font-size: 28rpx;\n  color: #999999;\n}\n.card.noBorder .card-content {\n  padding: 0;\n}\n.card.min .card-title {\n  font-size: 28rpx;\n  color: #999999;\n}\n.card.min .card-content {\n  padding: 0;\n}\n.card.full {\n  -webkit-border-radius: 0;\n          border-radius: 0;\n}\n.card.full .card-content {\n  padding: 0 34rpx 20rpx;\n}\n.card-head {\n  height: 85rpx;\n  border-bottom: 1rpx solid #e6e6e6;\n  padding-left: 30rpx;\n  display: -webkit-box;\n  display: -webkit-flex;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-align: center;\n  -webkit-align-items: center;\n      -ms-flex-align: center;\n          align-items: center;\n  -webkit-box-pack: justify;\n  -webkit-justify-content: space-between;\n      -ms-flex-pack: justify;\n          justify-content: space-between;\n}\n.card-title {\n  font-size: 32rpx;\n  font-family: PingFang SC;\n  font-weight: 500;\n  color: #333;\n}\n.card-title .card-title-sub {\n  font-weight: normal;\n  font-size: 22rpx;\n  color: #999999;\n}\n.card-more {\n  font-size: 24rpx;\n  font-family: PingFang SC;\n  font-weight: 400;\n  color: #999999;\n  display: -webkit-box;\n  display: -webkit-flex;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-align: center;\n  -webkit-align-items: center;\n      -ms-flex-align: center;\n          align-items: center;\n}\n.card-more .uv-icon {\n  margin-left: 10px;\n  margin-right: 10px;\n}\n.card-content {\n  padding: 20rpx 0 0;\n  -webkit-border-bottom-left-radius: 15rpx;\n          border-bottom-left-radius: 15rpx;\n  -webkit-border-bottom-right-radius: 15rpx;\n          border-bottom-right-radius: 15rpx;\n  overflow: hidden;\n}\n.card .card-grid-item {\n  display: -webkit-box;\n  display: -webkit-flex;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-orient: vertical;\n  -webkit-box-direction: normal;\n  -webkit-flex-direction: column;\n      -ms-flex-direction: column;\n          flex-direction: column;\n  -webkit-box-pack: center;\n  -webkit-justify-content: center;\n      -ms-flex-pack: center;\n          justify-content: center;\n  -webkit-box-align: center;\n  -webkit-align-items: center;\n      -ms-flex-align: center;\n          align-items: center;\n  padding: 10rpx 0;\n  margin-bottom: 20rpx;\n  position: relative;\n}\n.card .card-grid-item-badge {\n  -webkit-box-align: center;\n  -webkit-align-items: center;\n      -ms-flex-align: center;\n          align-items: center;\n  display: -webkit-box;\n  display: -webkit-flex;\n  display: -ms-flexbox;\n  display: flex;\n  position: absolute;\n  right: 0;\n  top: 0;\n}\n.card .card-grid-item-badge span {\n  margin: 0 !important;\n  color: #fff !important;\n}\n.card .card-grid-item .image {\n  width: 60rpx;\n  height: 60rpx;\n}\n.card .card-grid-item-label {\n  margin-top: 14rpx;\n  line-height: 33rpx;\n  font-size: 24rpx;\n  font-family: PingFang SC;\n  font-weight: 400;\n  color: #333333;\n}\n.search .y-search {\n  background: none;\n}\n.cell-attr .cell-title {\n  line-height: 40rpx;\n  font-size: 28rpx;\n  color: #333333;\n}\n.cell-attr .cell-sub-title {\n  line-height: 40rpx;\n  font-size: 28rpx;\n  color: #999999;\n}\n.storeInfo {\n  padding: 24rpx 35rpx;\n  display: -webkit-box;\n  display: -webkit-flex;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-align: center;\n  -webkit-align-items: center;\n      -ms-flex-align: center;\n          align-items: center;\n  background: #fff;\n}\n.storeInfo-pic {\n  width: 80rpx;\n  height: 80rpx;\n}\n.storeInfo-info {\n  -webkit-box-flex: 1;\n  -webkit-flex: 1;\n      -ms-flex: 1;\n          flex: 1;\n  margin-left: 20rpx;\n}\n.storeInfo-info-name {\n  line-height: 40rpx;\n  font-size: 28rpx;\n  color: #333333;\n}\n.storeInfo-info-address {\n  line-height: 28rpx;\n  font-size: 20rpx;\n  color: #999999;\n  margin-top: 84px;\n}\n.storeInfo-action {\n  width: 120rpx;\n  height: 50rpx;\n  background: #333333;\n  line-height: 50rpx;\n  text-align: center;\n  font-size: 24rpx;\n  line-height: 0rpx;\n  color: #ffffff;\n}\n.center-title {\n  display: -webkit-box;\n  display: -webkit-flex;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-align: center;\n  -webkit-align-items: center;\n      -ms-flex-align: center;\n          align-items: center;\n  -webkit-box-pack: center;\n  -webkit-justify-content: center;\n      -ms-flex-pack: center;\n          justify-content: center;\n  margin: 20rpx 0;\n}\n.center-title-line {\n  width: 36rpx;\n  height: 1rpx;\n  background: #333333;\n}\n.center-title .title {\n  margin: 0 22rpx;\n}\n.blank {\n  height: var(--van-action-bar-height);\n}\n.shopping-bar {\n  bottom: var(--van-tabbar-height);\n}\n.full-btn {\n  height: var(--van-action-bar-height);\n}\n.shopping-checkbox .van-checkbox {\n  padding: 2px;\n  margin-right: 10rpx;\n}\n.shopping-checkbox-cell {\n  display: -webkit-box;\n  display: -webkit-flex;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-align: center;\n  -webkit-align-items: center;\n      -ms-flex-align: center;\n          align-items: center;\n  padding: 26rpx 0 26rpx 30rpx;\n}\n.list {\n  padding: 25rpx 35rpx;\n  position: relative;\n  display: -webkit-box;\n  display: -webkit-flex;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-pack: justify;\n  -webkit-justify-content: space-between;\n      -ms-flex-pack: justify;\n          justify-content: space-between;\n  -webkit-box-align: center;\n  -webkit-align-items: center;\n      -ms-flex-align: center;\n          align-items: center;\n  background: #fff;\n}\n.list-main {\n  display: -webkit-box;\n  display: -webkit-flex;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-align: center;\n  -webkit-align-items: center;\n      -ms-flex-align: center;\n          align-items: center;\n  -webkit-box-flex: 1;\n  -webkit-flex: 1;\n      -ms-flex: 1;\n          flex: 1;\n}\n.list-actions-edit {\n  width: 33rpx;\n  height: 33rpx;\n}\n.list-actions-edit .image {\n  width: 100%;\n  height: 100%;\n  display: block;\n}\n.list.noBorder::after {\n  display: none;\n}\n.list::after {\n  content: '';\n  position: absolute;\n  left: 35rpx;\n  top: 0;\n  right: 0;\n  height: 1rpx;\n  background: #e6e6e6;\n}\n.list-label {\n  line-height: 40rpx;\n  font-size: 28rpx;\n  color: #333333;\n}\n.list-content {\n  -webkit-box-flex: 1;\n  -webkit-flex: 1;\n      -ms-flex: 1;\n          flex: 1;\n}\n.list-content input,\n.list-content .uni-input {\n  line-height: 40rpx;\n  font-size: 28rpx;\n  color: #333333;\n}\n.form-checkbox {\n  padding: 43rpx 0;\n  display: -webkit-box;\n  display: -webkit-flex;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-pack: center;\n  -webkit-justify-content: center;\n      -ms-flex-pack: center;\n          justify-content: center;\n}\n.form-buttons {\n  margin-top: 34rpx;\n  padding: 0 34rpx;\n}\n.background-warp {\n  position: relative;\n}\n.background-warp .background {\n  position: absolute;\n  left: 0;\n  top: 0;\n  right: 0;\n  z-index: 1;\n}\n.background-warp .background .image {\n  width: 100%;\n}\n.background-warp .background-content {\n  position: relative;\n  z-index: 10;\n}\n.order {\n  background-color: #fff;\n  -webkit-border-radius: 15rpx;\n          border-radius: 15rpx;\n  -webkit-box-sizing: border-box;\n          box-sizing: border-box;\n  margin: 10rpx 34rpx;\n}\n.order-header {\n  display: -webkit-box;\n  display: -webkit-flex;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-align: center;\n  -webkit-align-items: center;\n      -ms-flex-align: center;\n          align-items: center;\n  -webkit-box-pack: justify;\n  -webkit-justify-content: space-between;\n      -ms-flex-pack: justify;\n          justify-content: space-between;\n  height: 80rpx;\n  border-bottom: 1rpx solid #e6e6e6;\n  padding: 0 34rpx;\n}\n.order-logo .image {\n  height: 45rpx;\n  width: auto;\n}\n.order-title {\n  line-height: 45rpx;\n  font-size: 32rpx;\n  font-weight: 500;\n  color: #333333;\n}\n.order-status.status-1 {\n  line-height: 40rpx;\n  font-size: 28rpx;\n  color: #999999;\n}\n.order-status.status-2 {\n  line-height: 40rpx;\n  font-size: 28rpx;\n  color: #ee6d46;\n}\n.order-info {\n  border-top: 1rpx solid #e6e6e6;\n  border-bottom: 1rpx solid #e6e6e6;\n  height: 80rpx;\n  line-height: 80rpx;\n  font-size: 24rpx;\n  color: #999999;\n  display: -webkit-box;\n  display: -webkit-flex;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-pack: end;\n  -webkit-justify-content: flex-end;\n      -ms-flex-pack: end;\n          justify-content: flex-end;\n  padding-right: 34rpx;\n}\n.order-info text {\n  margin-left: 10rpx;\n}\n.order-actions {\n  display: -webkit-box;\n  display: -webkit-flex;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-pack: justify;\n  -webkit-justify-content: space-between;\n      -ms-flex-pack: justify;\n          justify-content: space-between;\n  padding-right: 34rpx;\n  padding: 34rpx;\n}\n.order-actions-btns {\n  display: -webkit-box;\n  display: -webkit-flex;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-align: center;\n  -webkit-align-items: center;\n      -ms-flex-align: center;\n          align-items: center;\n}\n.order-actions-delete {\n  width: 169rpx;\n  height: 60rpx;\n  border: 1px solid #333333;\n  opacity: 1;\n  -webkit-border-radius: 0rpx;\n          border-radius: 0rpx;\n  line-height: 58rpx;\n  text-align: center;\n  color: #333333;\n  font-size: 24rpx;\n}\n.order-actions-primary {\n  margin-left: 20rpx;\n  width: 169rpx;\n  height: 60rpx;\n  background: #ee6d46;\n  border: 1px solid #ee6d46;\n  opacity: 1;\n  -webkit-border-radius: 0rpx;\n          border-radius: 0rpx;\n  line-height: 58rpx;\n  text-align: center;\n  color: #ffffff;\n  font-size: 24rpx;\n}\n.address {\n  background: #ffffff;\n  -webkit-border-radius: 15rpx;\n          border-radius: 15rpx;\n  position: relative;\n  display: -webkit-box;\n  display: -webkit-flex;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-pack: justify;\n  -webkit-justify-content: space-between;\n      -ms-flex-pack: justify;\n          justify-content: space-between;\n  -webkit-box-align: center;\n  -webkit-align-items: center;\n      -ms-flex-align: center;\n          align-items: center;\n  margin-bottom: 20rpx;\n  padding: 40rpx 34rpx;\n  background: #fff;\n}\n.address-main {\n  -webkit-box-flex: 1;\n  -webkit-flex: 1;\n      -ms-flex: 1;\n          flex: 1;\n}\n.address-actions-edit {\n  width: 33rpx;\n  height: 33rpx;\n}\n.address-actions-edit .image {\n  width: 100%;\n  height: 100%;\n  display: block;\n}\n.address.noBorder::after {\n  display: none;\n}\n.address::after {\n  content: '';\n  position: absolute;\n  left: 35rpx;\n  top: 0;\n  right: 0;\n  height: 1rpx;\n  background: #e6e6e6;\n}\n.address-icon {\n  margin-right: 20rpx;\n  width: 35rpx;\n  height: 46rpx;\n}\n.address-icon .image {\n  width: 100%;\n  height: 100%;\n  display: block;\n}\n.address-header {\n  display: -webkit-box;\n  display: -webkit-flex;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-align: center;\n  -webkit-align-items: center;\n      -ms-flex-align: center;\n          align-items: center;\n}\n.address-name {\n  line-height: 40rpx;\n  font-size: 28rpx;\n  color: #333333;\n  margin-right: 30rpx;\n}\n.address-phone {\n  line-height: 40rpx;\n  font-size: 28rpx;\n  color: #333333;\n}\n.address-content {\n  display: -webkit-box;\n  display: -webkit-flex;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-align: center;\n  -webkit-align-items: center;\n      -ms-flex-align: center;\n          align-items: center;\n}\n.address-default {\n  margin-right: 82rpx;\n}\n.address-desc {\n  line-height: 33rpx;\n  font-size: 24rpx;\n  color: #999999;\n}\n.bottom-bar {\n  position: fixed;\n  bottom: 0;\n  left: 0;\n  right: 0;\n  background: #fff;\n  display: -webkit-box;\n  display: -webkit-flex;\n  display: -ms-flexbox;\n  display: flex;\n}\n.bottom-bar-bg {\n  height: 150rpx;\n}\n.action-bar {\n  position: fixed;\n  display: -webkit-box;\n  display: -webkit-flex;\n  display: -ms-flexbox;\n  display: flex;\n  bottom: 0;\n  padding-bottom: constant(safe-area-inset-bottom);\n  padding-bottom: env(safe-area-inset-bottom);\n  left: 0;\n  right: 0;\n  background: #fff;\n}\n.action-bar.screen {\n  bottom: 50px;\n}\n.action-bar.screen {\n  padding-bottom: 0;\n}\n.action-bar.column {\n  -webkit-box-orient: vertical;\n  -webkit-box-direction: normal;\n  -webkit-flex-direction: column;\n      -ms-flex-direction: column;\n          flex-direction: column;\n}\n.action-bar.column .action-info {\n  -webkit-box-flex: 0;\n  -webkit-flex: 0 0 100rpx;\n      -ms-flex: 0 0 100rpx;\n          flex: 0 0 100rpx;\n}\n.action-bar.column .action-btns {\n  height: 100rpx;\n  -webkit-box-align: center;\n  -webkit-align-items: center;\n      -ms-flex-align: center;\n          align-items: center;\n}\n.action-bar .action-total {\n  margin-left: 20rpx;\n}\n.action-bar .action-info {\n  -webkit-box-flex: 1;\n  -webkit-flex: 1;\n      -ms-flex: 1;\n          flex: 1;\n  display: -webkit-box;\n  display: -webkit-flex;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-align: center;\n  -webkit-align-items: center;\n      -ms-flex-align: center;\n          align-items: center;\n  padding: 0 34rpx;\n  height: 100rpx;\n}\n.action-bar .action-icons {\n  -webkit-box-flex: 1;\n  -webkit-flex: 1;\n      -ms-flex: 1;\n          flex: 1;\n  display: -webkit-box;\n  display: -webkit-flex;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-align: center;\n  -webkit-align-items: center;\n      -ms-flex-align: center;\n          align-items: center;\n  height: 100rpx;\n  padding-left: 20rpx;\n}\n.action-bar .action-icons-item {\n  -webkit-box-flex: 1;\n  -webkit-flex: 1;\n      -ms-flex: 1;\n          flex: 1;\n  display: -webkit-box;\n  display: -webkit-flex;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-align: center;\n  -webkit-align-items: center;\n      -ms-flex-align: center;\n          align-items: center;\n  -webkit-box-pack: center;\n  -webkit-justify-content: center;\n      -ms-flex-pack: center;\n          justify-content: center;\n}\n.action-bar .action-icons .action-icon {\n  display: -webkit-box;\n  display: -webkit-flex;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-orient: vertical;\n  -webkit-box-direction: normal;\n  -webkit-flex-direction: column;\n      -ms-flex-direction: column;\n          flex-direction: column;\n  -webkit-box-pack: center;\n  -webkit-justify-content: center;\n      -ms-flex-pack: center;\n          justify-content: center;\n  -webkit-box-align: center;\n  -webkit-align-items: center;\n      -ms-flex-align: center;\n          align-items: center;\n  position: relative;\n}\n.action-bar .action-icons .action-icon-badge {\n  position: absolute;\n  right: -10rpx;\n  top: 0;\n}\n.action-bar .action-icons .action-icon .action-icon-img {\n  width: 50rpx;\n  height: 50rpx;\n}\n.action-bar .action-icons .action-icon .action-icon-label {\n  line-height: 28rpx;\n  font-size: 20rpx;\n  color: #333333;\n}\n.action-bar .action-btns {\n  display: -webkit-box;\n  display: -webkit-flex;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-align: center;\n  -webkit-align-items: center;\n      -ms-flex-align: center;\n          align-items: center;\n  padding: 0 24rpx;\n}\n.action-bar .action-btns .uv-button-wrapper {\n  -webkit-box-flex: 1;\n  -webkit-flex: 1;\n      -ms-flex: 1;\n          flex: 1;\n  margin: 0 10rpx;\n}\n.action-bar .action-btns .uv-button {\n  -webkit-border-radius: 0 !important;\n          border-radius: 0 !important;\n}\n.y-list {\n  margin-bottom: 20rpx;\n}\n.y-list.min {\n  margin-bottom: 15rpx;\n}\n.y-list.min .y-list-label {\n  margin-right: 10rpx;\n}\n.y-list.min .y-list-content {\n  height: 88rpx;\n}\n.y-list .uv-list-item__wrapper {\n  -webkit-box-orient: horizontal !important;\n  -webkit-box-direction: normal !important;\n  -webkit-flex-direction: row !important;\n      -ms-flex-direction: row !important;\n          flex-direction: row !important;\n}\n.y-list-content {\n  -webkit-box-flex: 1;\n  -webkit-flex: 1;\n      -ms-flex: 1;\n          flex: 1;\n  display: -webkit-box;\n  display: -webkit-flex;\n  display: -ms-flexbox;\n  display: flex;\n  width: 100%;\n  -webkit-box-align: center;\n  -webkit-align-items: center;\n      -ms-flex-align: center;\n          align-items: center;\n  height: 100rpx;\n  padding-left: 34rpx;\n  padding-right: 34rpx;\n}\n.y-list-content.avatar {\n  height: 130rpx;\n}\n.y-list-label {\n  line-height: 40rpx;\n  font-size: 28rpx;\n  font-weight: 400;\n  color: #999999;\n  margin-right: 30rpx;\n  word-break: normal;\n  word-wrap: normal;\n  text-wrap: nowrap;\n  -webkit-box-flex: 1;\n  -webkit-flex: 1;\n      -ms-flex: 1;\n          flex: 1;\n}\n.y-list-select {\n  line-height: 40rpx;\n  font-size: 28rpx;\n  font-weight: 400;\n  color: #333333;\n  opacity: 1;\n  -webkit-box-flex: 1;\n  -webkit-flex: 1;\n      -ms-flex: 1;\n          flex: 1;\n  text-align: right;\n}\n.y-list-select-placeholder {\n  color: #999999;\n  line-height: 40rpx;\n  font-size: 28rpx;\n  font-weight: 400;\n  color: #999;\n  opacity: 1;\n  -webkit-box-flex: 1;\n  -webkit-flex: 1;\n      -ms-flex: 1;\n          flex: 1;\n  text-align: right;\n}\n.y-list-default {\n  font-size: 28rpx;\n  font-weight: 400;\n  color: #333333;\n  opacity: 1;\n}\n.y-list-value {\n  line-height: 40rpx;\n  font-size: 28rpx;\n  font-weight: 500;\n  color: #ee6d46;\n}\n.y-list-value .uv-input {\n  border: 0;\n}\n.y-list .uvicon-arrow-right {\n  font-size: 10rpx !important;\n}\n.y-list-avatar {\n  padding: 20rpx 0;\n  display: -webkit-box;\n  display: -webkit-flex;\n  display: -ms-flexbox;\n  display: flex;\n  -webkit-box-pack: end;\n  -webkit-justify-content: flex-end;\n      -ms-flex-pack: end;\n          justify-content: flex-end;\n}\n.y-list-avatar .img {\n  width: 90rpx;\n  height: 90rpx;\n  -webkit-border-radius: 50%;\n          border-radius: 50%;\n}\n.uv-list--border-top {\n  background: #e6e6e6 !important;\n}\n.uv-list--border-left {\n  background: #e6e6e6 !important;\n}\n.uv-list--border-bottom {\n  background: #e6e6e6 !important;\n}\n.uv-list--border-right {\n  background: #e6e6e6 !important;\n}\n.uv-list--border:after:after {\n  background: #e6e6e6 !important;\n}\n.uvicon-arrow-right {\n  color: #999999;\n}\n.search-bar {\n  background: #fff;\n  padding: 20rpx 34rpx;\n}\n.y-subsection {\n  padding: 20rpx 33rpx 0;\n}\n.swiper {\n  width: 100%;\n}\n.swiper.detail {\n  height: 750rpx;\n}\n.swiper .image {\n  width: 100%;\n  display: block;\n}\n.uv-button--info {\n  border-color: #333333 !important;\n  color: #333333 !important;\n}\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/static/style/style.less",
    "content": "body {\n  background-color: #f5f5f5;\n}\n\npage {\n  background-color: #f5f5f5;\n  min-height: 100%;\n}\n\nimage {\n  max-width: 100%;\n  height: auto;\n}\n\n:root {\n  --van-primary-color: #ee6d46;\n}\n\n.van-grid-item__content {\n  padding: 0 var(--van-padding-base);\n}\n\n.scroll-view-H {\n  white-space: nowrap;\n  width: 100%;\n}\n\n.page-space {\n  padding: 0 34rpx;\n}\n\n.page-card {\n  background: #fff;\n\n  border-radius: 15rpx;\n}\n\n.mb-10 {\n  margin-bottom: 10rpx;\n}\n\n.mb-15 {\n  margin-bottom: 15rpx;\n}\n\n.mb-20 {\n  margin-bottom: 20rpx;\n}\n\n.mb-25 {\n  margin-bottom: 25rpx;\n}\n\n.mb-30 {\n  margin-bottom: 30rpx;\n}\n\n.pl-10 {\n  padding-left: 10rpx;\n}\n\n.pl-15 {\n  padding-left: 15rpx;\n}\n\n.pl-20 {\n  padding-left: 20rpx;\n}\n\n.pl-25 {\n  padding-left: 25rpx;\n}\n\n.pl-30 {\n  padding-left: 30rpx;\n}\n.pl-40 {\n  padding-left: 40rpx;\n}\n\n.pr-10 {\n  padding-right: 10rpx;\n}\n\n.pr-15 {\n  padding-right: 15rpx;\n}\n\n.pr-20 {\n  padding-right: 20rpx;\n}\n\n.pr-25 {\n  padding-right: 25rpx;\n}\n\n.pr-30 {\n  padding-right: 30rpx;\n}\n\n.pr-40 {\n  padding-right: 40rpx;\n}\n\n.p-40 {\n  padding: 40rpx;\n}\n\n.paddingH-10 {\n  padding: 0 20rpx;\n}\n.border-top {\n  position: relative;\n  &::after {\n    content: '';\n    position: absolute;\n    left: 0;\n    top: 0;\n    right: 0;\n    height: 1rpx;\n    background: #e6e6e6;\n  }\n}\n\n.primary {\n}\n\n.van-button {\n  border-radius: 0;\n  &--primary {\n  }\n  &--mini {\n    font-size: 24rpx;\n  }\n}\n\n.icon {\n  .image {\n    display: block;\n    width: 100%;\n    height: 100%;\n  }\n}\n\n.infos {\n  padding: 20rpx 37rpx 20rpx;\n  &.infos-right {\n    .info-cell-value {\n      text-align: right;\n    }\n  }\n  .info-cell {\n    margin-bottom: 20rpx;\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n    &-label {\n      margin-right: 30rpx;\n      line-height: 32rpx;\n      font-size: 24rpx;\n      color: #999999;\n    }\n    &-value {\n      flex: 1;\n      line-height: 32rpx;\n      font-size: 24rpx;\n      color: #333333;\n    }\n    &-operation {\n      line-height: 32rpx;\n      font-size: 24rpx;\n      color: #ee6d46;\n    }\n  }\n}\n\n.simple-cell {\n  display: flex;\n  align-items: center;\n  &-icon {\n    width: 64rpx;\n    height: 64rpx;\n  }\n  &-content {\n    margin-left: 15rpx;\n  }\n  &-title {\n    line-height: 40rpx;\n    font-size: 28rpx;\n    font-weight: 500;\n    color: #333333;\n  }\n  &-label {\n    line-height: 30rpx;\n    font-size: 22rpx;\n    color: #333333;\n  }\n}\n\n.time-bar {\n  background: #fff;\n  height: 150rpx;\n  .van-tab--active {\n    .time-bar-item {\n      background: #e96b45;\n      .time {\n        color: #fff;\n      }\n\n      .status {\n        color: #fff;\n      }\n    }\n  }\n\n  &-item {\n    width: 180rpx;\n    height: 150rpx;\n    background: #ffffff;\n\n    display: flex;\n    flex-direction: column;\n    align-items: center;\n\n    .time {\n      margin-top: 26rpx;\n      line-height: 1em;\n      font-size: 34rpx;\n      font-weight: bold;\n      color: #333333;\n      margin-bottom: 10rpx;\n    }\n\n    .status {\n      line-height: 1em;\n      font-size: 24rpx;\n      font-weight: 400;\n      color: #999999;\n    }\n    .countdown {\n      flex: 1;\n      height: 1em;\n      font-size: 20rpx;\n      color: #ffffff;\n      font-weight: 100;\n    }\n  }\n\n  .van-tab {\n    padding: 0;\n  }\n\n  .van-tabs--line .van-tabs__wrap {\n    height: 150rpx;\n  }\n\n  .van-tabs__nav--line {\n    padding-bottom: 0;\n    padding: 0;\n  }\n\n  .van-tabs__wrap {\n    height: 150rpx;\n  }\n  .van-tabs__line {\n    display: none;\n  }\n}\n\n.card {\n  background: #ffffff;\n  border-radius: 15rpx;\n\n  &.noBorder {\n    .card-head {\n      border-bottom: 0;\n    }\n    .card-title {\n      font-size: 28rpx;\n      color: #999999;\n    }\n\n    .card-content {\n      padding: 0;\n    }\n  }\n  &.min {\n    .card-head {\n    }\n    .card-title {\n      font-size: 28rpx;\n      color: #999999;\n    }\n\n    .card-content {\n      padding: 0;\n    }\n  }\n\n  &.full {\n    border-radius: 0;\n    .card-content {\n      padding: 0 34rpx 20rpx;\n    }\n  }\n\n  &-head {\n    height: 85rpx;\n    border-bottom: 1rpx solid #e6e6e6;\n    padding-left: 30rpx;\n\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n  }\n\n  &-title {\n    font-size: 32rpx;\n    font-family: PingFang SC;\n    font-weight: 500;\n    color: #333;\n    .card-title-sub {\n      font-weight: normal;\n      font-size: 22rpx;\n      color: #999999;\n    }\n  }\n  &-more {\n    font-size: 24rpx;\n    font-family: PingFang SC;\n    font-weight: 400;\n    color: #999999;\n    display: flex;\n    align-items: center;\n    .uv-icon {\n      margin-left: 10px;\n      margin-right: 10px;\n    }\n  }\n\n  &-content {\n    padding: 20rpx 0 0;\n    border-bottom-left-radius: 15rpx;\n    border-bottom-right-radius: 15rpx;\n    overflow: hidden;\n  }\n\n  .card-grid-item {\n    display: flex;\n    flex-direction: column;\n    justify-content: center;\n    align-items: center;\n    padding: 10rpx 0;\n    margin-bottom: 20rpx;\n    position: relative;\n\n    &-badge {\n      align-items: center;\n      display: flex;\n      position: absolute;\n      right: 0;\n      top: 0;\n      span {\n        margin: 0 !important;\n        color: #fff !important;\n      }\n    }\n\n    .image {\n      width: 60rpx;\n      height: 60rpx;\n    }\n\n    &-label {\n      margin-top: 14rpx;\n      line-height: 33rpx;\n      font-size: 24rpx;\n      font-family: PingFang SC;\n      font-weight: 400;\n      color: #333333;\n    }\n  }\n}\n\n.search {\n  .y-search {\n    background: none;\n  }\n}\n\n.cell-attr {\n  .cell-title {\n    line-height: 40rpx;\n    font-size: 28rpx;\n    color: #333333;\n  }\n  .cell-sub-title {\n    line-height: 40rpx;\n    font-size: 28rpx;\n    color: #999999;\n  }\n}\n\n.storeInfo {\n  padding: 24rpx 35rpx;\n  display: flex;\n  align-items: center;\n  background: #fff;\n  &-pic {\n    width: 80rpx;\n    height: 80rpx;\n  }\n  &-info {\n    flex: 1;\n    margin-left: 20rpx;\n  }\n  &-info-name {\n    line-height: 40rpx;\n    font-size: 28rpx;\n    color: #333333;\n  }\n  &-info-address {\n    line-height: 28rpx;\n    font-size: 20rpx;\n    color: #999999;\n    margin-top: 84px;\n  }\n  &-action {\n    width: 120rpx;\n    height: 50rpx;\n    background: #333333;\n    line-height: 50rpx;\n    text-align: center;\n    font-size: 24rpx;\n    line-height: 0rpx;\n    color: #ffffff;\n  }\n}\n\n.center-title {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  margin: 20rpx 0;\n  &-line {\n    width: 36rpx;\n    height: 1rpx;\n    background: #333333;\n  }\n  .title {\n    margin: 0 22rpx;\n  }\n}\n\n.blank {\n  height: var(--van-action-bar-height);\n}\n\n.shopping-bar {\n  bottom: var(--van-tabbar-height);\n}\n\n.full-btn {\n  height: var(--van-action-bar-height);\n}\n\n.shopping-checkbox {\n  .van-checkbox {\n    padding: 2px;\n    margin-right: 10rpx;\n  }\n}\n\n.shopping-checkbox-cell {\n  display: flex;\n  align-items: center;\n  padding: 26rpx 0 26rpx 30rpx;\n}\n\n.list {\n  padding: 25rpx 35rpx;\n  position: relative;\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n\n  &-main {\n    display: flex;\n    align-items: center;\n    flex: 1;\n  }\n\n  &-actions {\n    &-edit {\n      width: 33rpx;\n      height: 33rpx;\n\n      .image {\n        width: 100%;\n        height: 100%;\n        display: block;\n      }\n    }\n  }\n\n  &.noBorder {\n    &::after {\n      display: none;\n    }\n  }\n\n  &::after {\n    content: '';\n    position: absolute;\n    left: 35rpx;\n    top: 0;\n    right: 0;\n    height: 1rpx;\n    background: #e6e6e6;\n  }\n  &-label {\n    line-height: 40rpx;\n    font-size: 28rpx;\n    color: #333333;\n  }\n\n  &-content {\n    flex: 1;\n    input,\n    .uni-input {\n      line-height: 40rpx;\n      font-size: 28rpx;\n      color: #333333;\n    }\n  }\n\n  background: #fff;\n}\n\n.form-checkbox {\n  padding: 43rpx 0;\n  display: flex;\n  justify-content: center;\n}\n\n.form-buttons {\n  margin-top: 34rpx;\n  padding: 0 34rpx;\n}\n\n.background-warp {\n  position: relative;\n  .background {\n    position: absolute;\n    left: 0;\n    top: 0;\n    right: 0;\n    z-index: 1;\n    .image {\n      width: 100%;\n    }\n\n    &-content {\n      position: relative;\n      z-index: 10;\n    }\n  }\n}\n\n.order {\n  background-color: #fff;\n  border-radius: 15rpx;\n  box-sizing: border-box;\n  margin: 10rpx 34rpx;\n\n  &-header {\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n    height: 80rpx;\n    border-bottom: 1rpx solid #e6e6e6;\n    padding: 0 34rpx;\n  }\n\n  &-logo {\n    .image {\n      height: 45rpx;\n      width: auto;\n    }\n  }\n  &-title {\n    line-height: 45rpx;\n    font-size: 32rpx;\n    font-weight: 500;\n    color: #333333;\n  }\n\n  &-status {\n    &.status-1 {\n      line-height: 40rpx;\n      font-size: 28rpx;\n      color: #999999;\n    }\n\n    &.status-2 {\n      line-height: 40rpx;\n      font-size: 28rpx;\n      color: #ee6d46;\n    }\n  }\n\n  &-goods {\n  }\n\n  &-info {\n    border-top: 1rpx solid #e6e6e6;\n    border-bottom: 1rpx solid #e6e6e6;\n    height: 80rpx;\n    line-height: 80rpx;\n    font-size: 24rpx;\n    color: #999999;\n    display: flex;\n    justify-content: flex-end;\n    padding-right: 34rpx;\n\n    text {\n      margin-left: 10rpx;\n    }\n  }\n\n  &-actions {\n    display: flex;\n    justify-content: space-between;\n    padding-right: 34rpx;\n    padding: 34rpx;\n\n    &-btns {\n      display: flex;\n      align-items: center;\n    }\n  }\n\n  &-actions-delete {\n    width: 169rpx;\n    height: 60rpx;\n    border: 1px solid #333333;\n    opacity: 1;\n    border-radius: 0rpx;\n    line-height: 58rpx;\n    text-align: center;\n    color: #333333;\n    font-size: 24rpx;\n  }\n\n  &-actions-primary {\n    margin-left: 20rpx;\n    width: 169rpx;\n    height: 60rpx;\n    background: #ee6d46;\n    border: 1px solid #ee6d46;\n    opacity: 1;\n    border-radius: 0rpx;\n    line-height: 58rpx;\n    text-align: center;\n    color: #ffffff;\n    font-size: 24rpx;\n  }\n}\n\n.address {\n  background: #ffffff;\n  border-radius: 15rpx;\n  position: relative;\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  margin-bottom: 20rpx;\n  padding: 40rpx 34rpx;\n\n  &-main {\n    flex: 1;\n  }\n\n  &-actions {\n    &-edit {\n      width: 33rpx;\n      height: 33rpx;\n\n      .image {\n        width: 100%;\n        height: 100%;\n        display: block;\n      }\n    }\n  }\n\n  &.noBorder {\n    &::after {\n      display: none;\n    }\n  }\n\n  &::after {\n    content: '';\n    position: absolute;\n    left: 35rpx;\n    top: 0;\n    right: 0;\n    height: 1rpx;\n    background: #e6e6e6;\n  }\n\n  &-icon {\n    margin-right: 20rpx;\n    width: 35rpx;\n    height: 46rpx;\n    .image {\n      width: 100%;\n      height: 100%;\n      display: block;\n    }\n  }\n\n  background: #fff;\n\n  &-header {\n    display: flex;\n    align-items: center;\n  }\n\n  &-name {\n    line-height: 40rpx;\n    font-size: 28rpx;\n    color: #333333;\n    margin-right: 30rpx;\n  }\n\n  &-phone {\n    line-height: 40rpx;\n    font-size: 28rpx;\n    color: #333333;\n  }\n\n  &-content {\n    display: flex;\n    align-items: center;\n  }\n\n  &-default {\n    margin-right: 82rpx;\n  }\n\n  &-desc {\n    line-height: 33rpx;\n    font-size: 24rpx;\n    color: #999999;\n  }\n}\n\n.bottom-bar {\n  position: fixed;\n  bottom: 0;\n  left: 0;\n  right: 0;\n  background: #fff;\n  display: flex;\n}\n\n.bottom-bar-bg {\n  height: 150rpx;\n}\n\n.action-bar {\n  position: fixed;\n  display: flex;\n  bottom: 0;\n  padding-bottom: constant(safe-area-inset-bottom);\n  padding-bottom: env(safe-area-inset-bottom);\n  left: 0;\n  right: 0;\n  background: #fff;\n  //#ifdef H5\n  &.screen {\n    bottom: 50px;\n  }\n  // #endif\n  //#ifndef H5\n  &.screen {\n    padding-bottom: 0;\n    padding-bottom: 0;\n  }\n  // #endif\n  &.column {\n    flex-direction: column;\n    .action-info {\n      flex: 0 0 100rpx;\n    }\n    .action-btns {\n      height: 100rpx;\n      align-items: center;\n    }\n  }\n  .action-total {\n    margin-left: 20rpx;\n  }\n  .action-info {\n    flex: 1;\n    display: flex;\n    align-items: center;\n    padding: 0 34rpx;\n    height: 100rpx;\n  }\n\n  .action-icons {\n    flex: 1;\n    display: flex;\n    align-items: center;\n    height: 100rpx;\n    padding-left: 20rpx;\n    &-item {\n      flex: 1;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n    }\n\n    .action-icon {\n      display: flex;\n      flex-direction: column;\n      justify-content: center;\n      align-items: center;\n      position: relative;\n      &-badge {\n        position: absolute;\n        right: -10rpx;\n        top: 0;\n      }\n      .action-icon-img {\n        width: 50rpx;\n        height: 50rpx;\n      }\n      .action-icon-label {\n        line-height: 28rpx;\n        font-size: 20rpx;\n        color: #333333;\n      }\n    }\n  }\n  .action-btns {\n    display: flex;\n    align-items: center;\n    padding: 0 24rpx;\n    .uv-button-wrapper {\n      flex: 1;\n      margin: 0 10rpx;\n    }\n    .uv-button {\n      border-radius: 0 !important;\n    }\n  }\n}\n\n.y-list {\n  margin-bottom: 20rpx;\n  &.min {\n    margin-bottom: 15rpx;\n    .y-list-label {\n      // font-size: 24rpx;\n      margin-right: 10rpx;\n    }\n    .y-list-content {\n      height: 88rpx;\n      // padding-left: 20rpx;\n    }\n  }\n\n  .uv-list-item__wrapper {\n    flex-direction: row !important;\n  }\n  &-content {\n    flex: 1;\n    display: flex;\n    width: 100%;\n    align-items: center;\n    height: 100rpx;\n    padding-left: 34rpx;\n    padding-right: 34rpx;\n    &.avatar {\n      height: 130rpx;\n    }\n  }\n  &-label {\n    line-height: 40rpx;\n    font-size: 28rpx;\n    font-weight: 400;\n    color: #999999;\n    margin-right: 30rpx;\n    word-break: normal;\n    word-wrap: normal;\n    text-wrap: nowrap;\n    flex: 1;\n  }\n  &-select {\n    line-height: 40rpx;\n    font-size: 28rpx;\n    font-weight: 400;\n    color: #333333;\n    opacity: 1;\n    flex: 1;\n    text-align: right;\n    &-placeholder {\n      color: #999999;\n      line-height: 40rpx;\n      font-size: 28rpx;\n      font-weight: 400;\n      color: #999;\n      opacity: 1;\n      flex: 1;\n      text-align: right;\n    }\n  }\n  &-default {\n    font-size: 28rpx;\n    font-weight: 400;\n    color: #333333;\n    opacity: 1;\n  }\n\n  &-value {\n    line-height: 40rpx;\n    font-size: 28rpx;\n    font-weight: 500;\n    color: #ee6d46;\n    .uv-input {\n      border: 0;\n    }\n  }\n  .uvicon-arrow-right {\n    font-size: 10rpx !important;\n  }\n  &-avatar {\n    padding: 20rpx 0;\n    display: flex;\n    justify-content: flex-end;\n    .img {\n      width: 90rpx;\n      height: 90rpx;\n      border-radius: 50%;\n    }\n  }\n}\n.uv-list--border-top {\n}\n\n.uv-list--border-top {\n  background: #e6e6e6 !important;\n}\n\n.uv-list--border-left {\n  background: #e6e6e6 !important;\n}\n\n.uv-list--border-bottom {\n  background: #e6e6e6 !important;\n}\n\n.uv-list--border-right {\n  background: #e6e6e6 !important;\n}\n\n.uv-list--border:after {\n  &:after {\n    background: #e6e6e6 !important;\n  }\n}\n\n.uvicon-arrow-right {\n  color: #999999;\n}\n\n.search-bar {\n  background: #fff;\n  padding: 20rpx 34rpx;\n}\n\n.y-subsection {\n  padding: 20rpx 33rpx 0;\n}\n\n.swiper {\n  width: 100%;\n\n  &.detail {\n    height: 750rpx;\n  }\n  .image {\n    width: 100%;\n    display: block;\n  }\n}\n.uv-button {\n}\n.uv-button--info {\n  border-color: #333333 !important;\n  color: #333333 !important;\n}\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/static/style/yshop.css",
    "content": "/* #ifndef APP-PLUS-NVUE */\r\n/* scroll-view */\r\n.scroll-row{ width: 100%;white-space: nowrap; }\r\n.scroll-row-item{ display: inline-block!important; }\r\n/* #endif */\r\n/* 图标 */\r\n.iconfont{\r\n\tfont-family:iconfont;\r\n}\r\n.view,.text{\r\n\tfont-size:28rpx; \r\n\tline-height:1.8; \r\n\tcolor:#0E151D;\r\n}\r\n/* 宽度 */\r\n/* #ifndef APP-PLUS-NVUE */\r\n.w-100{ width: 100%; }\r\n/* #endif */\r\n\r\n.row {\r\n  margin-right: -20rpx;\r\n  margin-left: -20rpx;\r\n  /* #ifndef APP-PLUS-NVUE */\r\n  display: flex;\r\n  /* #endif */\r\n  flex-wrap: wrap;\r\n  flex-direction: row;\r\n}\r\n\r\n.col-1,\r\n.col-2,\r\n.col-3,\r\n.col-4,\r\n.col-5,\r\n.col-6,\r\n.col-7,\r\n.col-8,\r\n.col-9,\r\n.col-10,\r\n.col-11,\r\n.col-12{\r\n  position: relative;\r\n  padding-right: 20rpx;\r\n  padding-left: 20rpx;\r\n}\r\n.col-12 { width: 750rpx;}\r\n.col-11 { width: 687.5rpx; }\r\n.col-10 { width: 625rpx; }\r\n.col-9 { width: 562.5rpx; }\r\n.col-8 { width: 500rpx; }\r\n.col-7 { width: 437.5rpx; }\r\n.col-6 { width: 375rpx; }\r\n.col-5 { width: 312.5rpx;}\r\n.col-4 {width: 250rpx;}\r\n.col-3 {width: 187.5rpx;}\r\n.col-2 {width: 125rpx;}\r\n.col-1 {width: 62.5rpx;}\r\n\r\n.col-offset-12 { margin-left: 750rpx;}\r\n.col-offset-11 { margin-left: 687.5rpx; }\r\n.col-offset-10 { margin-left: 625rpx; }\r\n.col-offset-9 { margin-left: 562.5rpx; }\r\n.col-offset-8 { margin-left: 500rpx; }\r\n.col-offset-7 { margin-left: 437.5rpx; }\r\n.col-offset-6 { margin-left: 375rpx; }\r\n.col-offset-5 { margin-left: 312.5rpx;}\r\n.col-offset-4 {margin-left: 250rpx;}\r\n.col-offset-3 {margin-left: 187.5rpx;}\r\n.col-offset-2 {margin-left: 125rpx;}\r\n.col-offset-1 {margin-left: 62.5rpx;}\r\n.col-offset-0 {margin-left: 0;}\r\n\r\n/* flex 布局 */\r\n.flex{\r\n\t/* #ifndef APP-PLUS-NVUE */\r\n\tdisplay:flex;\r\n\t/* #endif */\r\n\tflex-direction:row;\r\n}\n.flex-row{ flex-direction:row!important; }\n.flex-column{ flex-direction:column!important; }\n.flex-row-reverse{ flex-direction:row-reverse!important; }\n.flex-column-reverse{ flex-direction:column-reverse!important; }\n.flex-wrap{ flex-wrap:wrap;}\n.flex-nowrap{ flex-wrap:nowrap;}\r\n.justify-start{justify-content:flex-start;}\n.justify-end{justify-content:flex-end;}\n.justify-between{justify-content:space-between;}\r\n.justify-center{justify-content:center;}\r\n.align-center{ align-items: center; }\r\n.align-stretch{ align-items: stretch; }\r\n.align-start{ align-items: flex-start; }\r\n.align-end{ align-items: flex-end; }\r\n/* #ifndef APP-PLUS-NVUE */\r\n.content-start {align-content: flex-start;}\r\n.content-end {align-content: flex-end;}\r\n.content-center {align-content: center;}\r\n.content-between {align-content: space-between;}\r\n.content-around {align-content: space-around;}\r\n.content-stretch {align-content: stretch;}\r\n/* #endif */\r\n.flex-1{ flex: 1; }\r\n.flex-2{ flex: 2; }\r\n.flex-3{ flex: 3; }\r\n.flex-4{ flex: 4; }\r\n.flex-5{ flex: 5; }\r\n/* #ifndef APP-PLUS-NVUE */\r\n.flex-shrink{ flex-shrink: 0; }\r\n/* #endif */\r\n\r\n/* .container {\r\n  padding-right: 20rpx;\r\n  padding-left: 20rpx;\r\n} */\r\n/*  -- 内外边距 -- */\r\n.m-0 { margin: 0; }\r\n/* #ifndef APP-PLUS-NVUE */\r\n.m-auto{ margin: auto; }\r\n/* #endif */\r\n.m-1 { margin: 10rpx; }\r\n.m-2 { margin: 20rpx; }\r\n.m-3 { margin: 30rpx; }\r\n.m-4 { margin: 40rpx; }\r\n.m-5 { margin: 50rpx; }\r\n.mt-0 { margin-top: 0; }\r\n/* #ifndef APP-PLUS-NVUE */\r\n.mt-auto { margin-top: auto; }\r\n/* #endif */\r\n.mt-1 { margin-top: 10rpx; }\r\n.mt-2 { margin-top: 20rpx; }\r\n.mt-3 { margin-top: 30rpx; }\r\n.mt-4 { margin-top: 40rpx; }\r\n.mt-5 { margin-top: 50rpx; }\r\n.mb-0 { margin-bottom: 0; }\r\n/* #ifndef APP-PLUS-NVUE */\r\n.mb-auto { margin-bottom: auto; }\r\n/* #endif */\r\n.mb-1 { margin-bottom: 10rpx; }\r\n.mb-2 { margin-bottom: 20rpx; }\r\n.mb-3 { margin-bottom: 30rpx; }\r\n.mb-4 { margin-bottom: 40rpx; }\r\n.mb-5 { margin-bottom: 50rpx; }\r\n.ml-0 { margin-left: 0; }\r\n/* #ifndef APP-PLUS-NVUE */\r\n.ml-auto { margin-left: auto; }\r\n/* #endif */\r\n.ml-1 { margin-left: 10rpx; }\r\n.ml-2 { margin-left: 20rpx; }\r\n.ml-3 { margin-left: 30rpx; }\r\n.ml-4 { margin-left: 40rpx; }\r\n.ml-5 { margin-left: 50rpx; }\r\n.mr-0 { margin-right: 0; }\r\n/* #ifndef APP-PLUS-NVUE */\r\n.mr-auto { margin-right: auto; }\r\n/* #endif */\r\n.mr-1 { margin-right: 10rpx; }\r\n.mr-2 { margin-right: 20rpx; }\r\n.mr-3 { margin-right: 30rpx; }\r\n.mr-4 { margin-right: 40rpx; }\r\n.mr-5 { margin-right: 50rpx; }\r\n.my-0 { margin-top: 0; margin-bottom: 0; }\r\n/* #ifndef APP-PLUS-NVUE */\r\n.my-auto { margin-top: auto; margin-bottom: auto; }\r\n/* #endif */\r\n.my-1 { margin-top: 10rpx; margin-bottom: 10rpx; }\r\n.my-2 { margin-top: 20rpx; margin-bottom: 20rpx; }\r\n.my-3 { margin-top: 30rpx; margin-bottom: 30rpx; }\r\n.my-4 { margin-top: 40rpx; margin-bottom: 40rpx; }\r\n.my-5 { margin-top: 50rpx; margin-bottom: 50rpx; }\r\n.mx-0 { margin-left: 0; margin-right: 0; }\r\n/* #ifndef APP-PLUS-NVUE */\r\n.mx-auto { margin-left: auto; margin-right: auto; }\r\n/* #endif */\r\n.mx-1 { margin-left: 10rpx; margin-right: 10rpx;}\r\n.mx-2 { margin-left: 20rpx; margin-right: 20rpx;}\r\n.mx-3 { margin-left: 30rpx; margin-right: 30rpx;}\r\n.mx-4 { margin-left: 40rpx; margin-right: 40rpx;}\r\n.mx-5 { margin-left: 50rpx; margin-right: 50rpx;}\r\n\r\n.p-0 { padding: 0; }\r\n.p { padding: 5rpx; }\r\n.p-1 { padding: 10rpx; }\r\n.p-2 { padding: 20rpx; }\r\n.p-3 { padding: 30rpx; }\r\n.p-4 { padding: 40rpx; }\r\n.p-5 { padding: 50rpx; }\r\n.pt-0 { padding-top: 0; }\r\n.pt { padding-top: 5rpx; }\r\n.pt-1 { padding-top: 10rpx; }\r\n.pt-2 { padding-top: 20rpx; }\r\n.pt-3 { padding-top: 30rpx; }\r\n.pt-4 { padding-top: 40rpx; }\r\n.pt-5 { padding-top: 50rpx; }\r\n.pb-0 { padding-bottom: 0; }\r\n.pb-1 { padding-bottom: 10rpx; }\r\n.pb { padding-bottom: 5rpx; }\r\n.pb-2 { padding-bottom: 20rpx; }\r\n.pb-3 { padding-bottom: 30rpx; }\r\n.pb-4 { padding-bottom: 40rpx; }\r\n.pb-5 { padding-bottom: 50rpx; }\r\n.pl-0 { padding-left: 0; }\r\n.pl { padding-left: 5rpx; }\r\n.pl-1 { padding-left: 10rpx; }\r\n.pl-2 { padding-left: 20rpx; }\r\n.pl-3 { padding-left: 30rpx; }\r\n.pl-4 { padding-left: 40rpx; }\r\n.pl-5 { padding-left: 50rpx; }\r\n.pr-0 { padding-right: 0; }\r\n.pr { padding-right: 5rpx; }\r\n.pr-1 { padding-right: 10rpx; }\r\n.pr-2 { padding-right: 20rpx; }\r\n.pr-3 { padding-right: 30rpx; }\r\n.pr-4 { padding-right: 40rpx; }\r\n.pr-5 { padding-right: 50rpx; }\r\n.py-0 { padding-top: 0; padding-bottom: 0; }\r\n.py { padding-top: 5rpx; padding-bottom: 5rpx; }\r\n.py-1 { padding-top: 10rpx; padding-bottom: 10rpx; }\r\n.py-2 { padding-top: 20rpx; padding-bottom: 20rpx; }\r\n.py-3 { padding-top: 30rpx; padding-bottom: 30rpx; }\r\n.py-4 { padding-top: 40rpx; padding-bottom: 40rpx; }\r\n.py-5 { padding-top: 50rpx; padding-bottom: 50rpx; }\r\n.px-0 { padding-left: 0; padding-right: 0; }\r\n.px-1 { padding-left: 10rpx; padding-right: 10rpx;}\r\n.px { padding-left: 5rpx; padding-right: 5rpx;}\r\n.px-2 { padding-left: 20rpx; padding-right: 20rpx;}\r\n.px-3 { padding-left: 30rpx; padding-right: 30rpx;}\r\n.px-4 { padding-left: 40rpx; padding-right: 40rpx;}\r\n.px-5 { padding-left: 50rpx; padding-right: 50rpx;}\r\n/* 文字大小 */\r\n.font-smaller { font-size: 15rpx;}\r\n.font-small { font-size: 20rpx;}\r\n.font-sm { font-size: 25rpx;}\r\n.font { font-size: 30rpx;}\r\n.font-md { font-size: 35rpx;}\r\n.font-lg { font-size: 40rpx;}\r\n.h1{font-size:80rpx; line-height:1.8;}\n.h2{font-size:60rpx; line-height:1.8;}\n.h3{font-size:45rpx; line-height:1.8;}\n.h4{font-size:32rpx; line-height:1.8;}\n.h5{font-size:30rpx; line-height:1.8;}\r\n.h6{font-size:28rpx; line-height:1.8;}\r\n/* 文字缩进 */\r\n/* #ifndef APP-PLUS-NVUE */\r\n.text-indent{text-indent:2;}\r\n/* #endif */\r\n/* 文字划线 */\r\n.text-through{text-decoration:line-through;}\n/* 文字对齐 */\n.text-left { text-align: left;}\r\n.text-right { text-align: right;}\r\n.text-center { text-align: center;}\r\n/* 文字换行溢出处理 */\r\n.text-ellipsis {\r\n\t/* #ifndef APP-PLUS-NVUE */\r\n\toverflow: hidden;text-overflow: ellipsis;white-space: nowrap;\r\n\t/* #endif */\r\n\t/* #ifdef APP-PLUS-NVUE */\r\n\tlines: 1;\r\n\t/* #endif */\r\n}\r\n/* 文字粗细和斜体 */\r\n.font-weight-light {font-weight: 300;}      /*细*/\r\n.font-weight-lighter {font-weight: 100;}/*更细*/\r\n.font-weight-normal { font-weight: 400;}    /*正常*/\r\n.font-weight-bold { font-weight: 700;}      /*粗*/\r\n.font-weight-bolder { font-weight: bold;} /*更粗*/\r\n.font-italic { font-style: italic;} /*斜体*/\r\n/* 文字颜色 */\r\n.text-white {color: #ffffff;}\r\n.text-primary {color: #007bff;}\r\n.text-hover-primary { color: #0056b3;}\r\n.text-secondary {color: #6c757d;}\r\n.text-hover-secondary { color: #494f54;}\r\n.text-success {color: #28a745;}\r\n.text-hover-success{color: #19692c;}\r\n.text-info { color: #17a2b8;}\r\n.text-hover-info {color: #0f6674;}\r\n.text-warning {color: #ffc107;}\r\n.text-hover-warning { color: #ba8b00;}\r\n.text-danger { color: #dc3545;}\r\n.text-hover-danger { color: #a71d2a;}\r\n.text-light { color: #f8f9fa;}\r\n.text-hover-light { color: #cbd3da;}\r\n.text-dark { color: #343a40;}\r\n.text-hover-dark{ color: #121416;}\r\n.text-body { color: #212529;}\r\n.text-muted { color: #6c757d;}\r\n/* 浅灰色 */\r\n.text-light-muted { color: #A9A5A0;}\r\n.text-light-black { color: rgba(0, 0, 0, 0.5);}\r\n.text-light-white { color: rgba(255, 255, 255, 0.5);}\r\n\n/* 背景颜色 */\r\n.bg-primary { background-color: #007bff;}\r\n.bg-hover-primary:hover{ background-color: #0062cc;}\r\n.bg-secondary { background-color: #6c757d;}\r\n.bg-hover-secondary:hover{ background-color: #545b62;}\r\n.bg-success { background-color: #28a745;}\r\n.bg-hover-success { background-color: #1e7e34;}\r\n.bg-info { background-color: #17a2b8;}\r\n.bg-hover-info { background-color: #117a8b;}\r\n.bg-warning { background-color: #ffc107;}\r\n.bg-hover-warning { background-color: #d39e00;}\r\n.bg-danger { background-color: #dc3545;}\r\n.bg-hover-danger{ background-color: #bd2130;}\r\n.bg-light { background-color: #f8f9fa;}\r\n.bg-hover-light{ background-color: #dae0e5;}\r\n.bg-dark { background-color: #343a40;}\r\n.bg-hover-dark { background-color: #1d2124;}\r\n.bg-white { background-color: #ffffff;}\r\n.bg-transparent { background-color: transparent;}\r\n/* 边框 */\r\n.border { border-width: 1rpx;border-style: solid;border-color: #dee2e6;}\r\n.border-top {\r\n  border-top-width: 1rpx;\r\n  border-top-style: solid;\r\n  border-top-color: #dee2e6;\r\n}\r\n.border-right {\r\n  border-right-width: 1rpx;\r\n  border-right-style: solid;\r\n  border-right-color: #dee2e6;\r\n}\r\n.border-bottom {\r\n  border-bottom-width: 1rpx;\r\n  border-bottom-style: solid;\r\n  border-bottom-color: #dee2e6;\r\n}\r\n.border-left {\r\n  border-left-width: 1rpx;\r\n  border-left-style: solid;\r\n  border-left-color: #dee2e6;\r\n}\r\n.border-0 { border-width: 0!important;}\r\n.border-top-0 { border-top-width: 0!important;}\r\n.border-right-0 {border-right-width: 0!important;}\r\n.border-bottom-0 {border-bottom-width: 0!important;}\r\n.border-left-0 {border-left-width: 0!important;}\r\n.border-primary { border-color: #007bff;}\r\n.border-secondary {border-color: #6c757d;}\r\n.border-light-secondary {border-color: #E9E8E5;}\r\n.border-success {border-color: #28a745;}\r\n.border-info {border-color: #17a2b8;}\r\n.border-warning {border-color: #ffc107;}\r\n.border-danger {border-color: #dc3545;}\r\n.border-light {border-color: #f8f9fa;}\r\n.border-dark {border-color: #343a40;}\r\n.border-white {border-color: #FFFFFF;}\r\n/* 圆角 */\r\n.rounded { border-radius: 8rpx;}\r\n.rounded-top {\r\n  border-top-left-radius: 8rpx;\r\n  border-top-right-radius: 8rpx;\r\n}\r\n.rounded-right {\r\n  border-top-right-radius: 8rpx;\r\n  border-bottom-right-radius: 8rpx;\r\n}\r\n.rounded-bottom {\r\n  border-bottom-right-radius: 8rpx;\r\n  border-bottom-left-radius: 8rpx;\r\n}\r\n.rounded-left {\r\n  border-top-left-radius: 8rpx;\r\n  border-bottom-left-radius: 8rpx;\r\n }\r\n.rounded-circle { border-radius: 100rpx;}\r\n.rounded-0 { border-radius: 0;}\r\n/* 显示 */\r\n/* #ifndef APP-PLUS-NVUE */\r\n.d-none{ display: none; }\r\n.d-inline-block{ display: inline-block; }\r\n.d-block{ display: block; }\r\n/* #endif */\r\n/* 内容溢出 */\r\n.overflow-hidden { overflow: hidden;}\r\n/* 定位 */\r\n.position-relative { position: relative;}\r\n.position-absolute { position: absolute;}\r\n.position-fixed { position: fixed;}\r\n/* 定位 - 固定顶部 */\r\n.fixed-top {\r\n  position: fixed;\r\n  top: 0;\r\n  right: 0;\r\n  left: 0;\r\n  z-index: 1030;\r\n}\r\n/* 定位 - 固定底部 */\r\n.fixed-bottom {\r\n  position: fixed;\r\n  right: 0;\r\n  bottom: 0;\r\n  left: 0;\r\n  z-index: 1030;\r\n}\r\n.top-0 { top: 0; }\r\n.left-0 { left: 0; }\r\n.right-0 { right: 0; }\r\n.bottom-0 { bottom: 0; }\r\n\r\n/* 阴影 */\r\n/* #ifndef APP-PLUS-NVUE */\r\n.shadow { box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.15);}\r\n.shadow-lg { box-shadow: 0rpx 40rpx 100rpx 0rpx rgba(0, 0, 0, 0.175);}\r\n.shadow-none { box-shadow: none !important;}\r\n/* #endif */\r\n\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/store/home.js",
    "content": "import { defineStore } from 'pinia'\n\nimport cookie from '@/utils/cookie'\nimport { getUserInfo } from '@/api/user'\n\nexport const useMainStore = defineStore('home', {\n  state: () => ({\n    banner: [],\n  }),\n  getters: {\n    // setUserInfo(state) {\n    //   // 自动完成! ✨\n    //   return state.user = state\n    // },\n  },\n  actions: {\n    setAccessToken(user) {\n      cookie.set('accessToken', user)\n      return getUserInfo()\n      \n    },\n    getUserInfo(user) {\n      getUserInfo()\n    },\n    getAddressList() {\n      getAddressList\n    }\n  },\n})\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/store/page.js",
    "content": "import { defineStore } from 'pinia'\n\nimport cookie from '@/utils/cookie'\nimport { getUserInfo } from '@/api/user'\n\nexport const usePage = defineStore('page', {\n  state: () => ({\n    banner: [],\n  }),\n  getters: {\n    // setUserInfo(state) {\n    //   // 自动完成! ✨\n    //   return state.user = state\n    // },\n  },\n  actions: {\n    setAccessToken(user) {\n      cookie.set('accessToken', user)\n      return getUserInfo()\n      \n    },\n    getUserInfo(user) {\n      getUserInfo()\n    },\n  },\n})\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/store/store.js",
    "content": "import { defineStore } from 'pinia'\n\nimport cookie from '@/utils/cookie'\nimport { navigateTo } from '@/utils/router'\n\nexport const useMainStore = defineStore('main', {\n  state: () => ({\n\tstore: {},\n\tcart: [],\n\torderType: 'takein',\n\taddress: {},\n\taddresses: {},\n\tmember: {\n\n\t},\n\topenid:\"\",\n\ttoken:\"\",\n\tlang: 'zh-cn',\n\tcookieKey:'YSESSID=yshop-e4dk4o2utr3c0n95tp42p745ai',\n\t// 默认地为你为北京地址\n\tlocation: {},\n\tmycoupon: {}\n  }),\n  getters: {\n\t  \n    isLogin(state) {//是否登录\n      return Object.keys(state.member).length > 0\n\t  //return cookie.get('accessToken') ? true : false\n    }\n\t//isLogin: state => Object.keys(state.member).length > 0\t//是否登录\n  },\n  actions: {\n\tDEL_COUPON() {\n\t    \tthis.mycoupon = {}\n\t},\n\tSET_COUPON(coupon) {\n\t  \tthis.mycoupon = coupon\n\t},\n\tSET_ORDER_TYPE(type) {\n\t  \tthis.orderType = type\n\t},\n\tSET_MEMBER(member) {\n\t\tthis.member = member\n\t\tcookie.set('userinfo', member)\n\t},\n\tSET_ADDRESS(address) {\n\t\tthis.address = address\n\t},\n\tSET_ADDRESSES(addresses) {\n\t\tthis.addresses = addresses\n\t},\n\tSET_STORE(store) {\n\t\tthis.store = store\n\t},\n\tSET_CART(cart) {\n\t\tthis.cart = cart\n\t},\n\tREMOVE_CART(state) {\n\t\tthis.cart = []\n\t},\n\tsetCookie(state, provider) {\n\t\tstate.cookie = provider;\n\t\tuni.setStorage({\n\t\t\tkey: 'cookieKey',\n\t\t\tdata: provider\n\t\t});\n\t},\n\tSET_LOCATION(location) {\n\t\tthis.location = location;\n\t},\n\tSET_OPENID(openid) {\n\t\tthis.openid = openid;\n\t},\n\tSET_TOKEN(token) {\n\t\tthis.token = token;\n\t\tcookie.set('accessToken', token)\n\t},\n\t  \n    setAccessToken(user) {\n      cookie.set('accessToken', user)\n      // return getUserInfo()\n    },\n    setSelectAddress(id) {\n      console.log('--> % setSelectAddress % id:\\n', id)\n      this.selectAddress = this.address.filter(item => item.id == id)[0]\n    },\n    init() {\n      let accessToken = cookie.get('accessToken')\n      if (accessToken) {\n        //return getUserInfo()\n      }\n      return null\n    },\n    logout() {\n      this.user = null\n      this.address = []\n      this.areaList = []\n      this.selectAddress = null\n      navigateTo('/pages/login/login')\n    },\n  },\n})\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni.promisify.adaptor.js",
    "content": "uni.addInterceptor({\n  returnValue (res) {\n    if (!(!!res && (typeof res === \"object\" || typeof res === \"function\") && typeof res.then === \"function\")) {\n      return res;\n    }\n    return new Promise((resolve, reject) => {\n      res.then((res) => res[0] ? reject(res[0]) : resolve(res[1]));\n    });\n  },\n});"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni.scss",
    "content": "/**\n * 这里是uni-app内置的常用样式变量\n *\n * uni-app 官方扩展插件及插件市场（https://ext.dcloud.net.cn）上很多三方插件均使用了这些样式变量\n * 如果你是插件开发者，建议你使用scss预处理，并在插件代码中直接使用这些变量（无需 import 这个文件），方便用户通过搭积木的方式开发整体风格一致的App\n *\n */\n\n/**\n * 如果你是App开发者（插件使用者），你可以通过修改这些变量来定制自己的插件主题，实现自定义主题功能\n *\n * 如果你的项目同样使用了scss预处理，你也可以直接在你的 scss 代码中使用如下变量，同时无需 import 这个文件\n */\n\n/* 颜色变量 */\n/* 行为相关颜色 */\n// $color-primary: #ADB838;\n$color-primary: #09b4f1;\n$color-success: #4cd964;\n$color-warning: #FAB714;\n$color-error: #D12E32;\n\n/* 文字基本颜色 */\n$text-color-base: #5A5B5C; //基本色\n$text-color-assist: #919293; //辅助色\n$text-color-black: #3B3C3E; //黑\n$text-color-grey: #878889; //灰\n$text-color-white: #ffffff; //白\n\n/* 背景颜色 */\n$bg-color: #F1F8FA;\n$bg-color-grey: #F5F5F5;\n//$bg-color-primary: #E8EACF;\n$bg-color-primary: #dbe7ea;\n\n/* 边框颜色 */\n$border-color:#e2e2e2;\n\n/* 尺寸变量 */\n\n/* 文字尺寸 */\n$font-size-sm:24rpx;\n$font-size-base:28rpx;\n$font-size-lg:32rpx;\n\n/* 图片尺寸 */\n$img-size-sm:40rpx;\n$img-size-base:52rpx;\n$img-size-lg:80rpx;\n\n/* Border Radius */\n$border-radius-sm: 4rpx;\n$border-radius-base: 6rpx;\n$border-radius-lg: 12rpx;\n$border-radius-circle: 50%;\n\n/* 水平间距 */\n$spacing-row-sm: 10px;\n$spacing-row-base: 20rpx;\n$spacing-row-lg: 30rpx;\n\n/* 垂直间距 */\n$spacing-col-sm: 8rpx;\n$spacing-col-base: 16rpx;\n$spacing-col-lg: 24rpx;\n\n/* 透明度 */\n$opacity-disabled: 0.3; // 组件禁用态的透明度\n\n/* 文章场景相关 */\n$color-title: #2C405A; // 文章标题颜色\n$font-size-title:40rpx;\n$color-subtitle: #555555; // 二级标题颜色\n$font-size-subtitle:36rpx;\n$color-paragraph: #3F536E; // 文章段落颜色\n$font-size-paragraph:30rpx;\n\n$box-shadow: 0 20rpx 20rpx -20rpx rgba($color: #333, $alpha: 0.1);\n\n\n\n\n$uv-main-color: #333333;\n$uv-content-color: #999999;\n$uv-tips-color: #909193;\n$uv-light-color: #c0c4cc;\n$uv-border-color: #dadbde;\n$uv-bg-color: #f3f4f6;\n$uv-disabled-color: #c8c9cc;\n\n$uv-primary: #EE6D46;\n$uv-primary-dark: #EE6D46;\n$uv-primary-disabled: #FDEDE8;\n$uv-primary-light: #ecf5ff;\n\n$uv-warning: #f9ae3d;\n$uv-warning-dark: #f1a532;\n$uv-warning-disabled: #f9d39b;\n$uv-warning-light: #fdf6ec;\n\n$uv-success: #5ac725;\n$uv-success-dark: #53c21d;\n$uv-success-disabled: #a9e08f;\n$uv-success-light: #f5fff0;\n\n$uv-error: #f56c6c;\n$uv-error-dark: #e45656;\n$uv-error-disabled: #f7b2b2;\n$uv-error-light: #fef0f0;\n\n$uv-info: #909399;\n$uv-info-dark: #767a82;\n$uv-info-disabled: #c4c6c9;\n$uv-info-light: #f4f4f5;\n\n\n@import '~@/static/iconfont/iconfont.scss';\n\n/* uni.scss */\n// @import 'uview-ui/theme.scss';\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uni-card/changelog.md",
    "content": "## 1.3.1（2021-12-20）\n- 修复 在vue页面下略缩图显示不正常的bug\n## 1.3.0（2021-11-19）\n- 重构插槽的用法 ，header 替换为 title \n- 新增 actions 插槽\n- 新增 cover 封面图属性和插槽\n- 新增 padding 内容默认内边距离\n- 新增 margin 卡片默认外边距离\n- 新增 spacing 卡片默认内边距\n- 新增 shadow 卡片阴影属性\n- 取消 mode 属性，可使用组合插槽代替\n- 取消 note 属性 ，使用actions插槽代替\n- 优化 组件UI，并提供设计资源，详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)\n- 文档迁移，详见:[https://uniapp.dcloud.io/component/uniui/uni-card](https://uniapp.dcloud.io/component/uniui/uni-card)\n## 1.2.1（2021-07-30）\n- 优化 vue3下事件警告的问题\n## 1.2.0（2021-07-13）\n- 组件兼容 vue3，如何创建vue3项目详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)\n## 1.1.8（2021-07-01）\n- 优化 图文卡片无图片加载时，提供占位图标\n- 新增 header 插槽，自定义卡片头部（ 图文卡片 mode=\"style\" 时，不支持）\n- 修复 thumbnail 不存在仍然占位的 bug\n## 1.1.7（2021-05-12）\n- 新增 组件示例地址\n## 1.1.6（2021-02-04）\n- 调整为uni_modules目录规范\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uni-card/components/uni-card/uni-card.vue",
    "content": "<template>\r\n\t<view class=\"uni-card\" :class=\"{ 'uni-card--full': isFull, 'uni-card--shadow': isShadow,'uni-card--border':border}\"\r\n\t\t:style=\"{'margin':isFull?0:margin,'padding':spacing,'box-shadow':isShadow?shadow:''}\">\n\t\t<!-- 封面 -->\r\n\t\t<slot name=\"cover\">\r\n\t\t\t<view v-if=\"cover\" class=\"uni-card__cover\">\r\n\t\t\t\t<image class=\"uni-card__cover-image\" mode=\"widthFix\" @click=\"onClick('cover')\" :src=\"cover\"></image>\r\n\t\t\t</view>\r\n\t\t</slot>\r\n\t\t<slot name=\"title\">\r\n\t\t\t<view v-if=\"title || extra\" class=\"uni-card__header\">\r\n\t\t\t\t<!-- 卡片标题 -->\r\n\t\t\t\t<view class=\"uni-card__header-box\" @click=\"onClick('title')\">\r\n\t\t\t\t\t<view v-if=\"thumbnail\" class=\"uni-card__header-avatar\">\r\n\t\t\t\t\t\t<image class=\"uni-card__header-avatar-image\" :src=\"thumbnail\" mode=\"aspectFit\" />\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"uni-card__header-content\">\r\n\t\t\t\t\t\t<text class=\"uni-card__header-content-title uni-ellipsis\">{{ title }}</text>\r\n\t\t\t\t\t\t<text v-if=\"title&&subTitle\"\r\n\t\t\t\t\t\t\tclass=\"uni-card__header-content-subtitle uni-ellipsis\">{{ subTitle }}</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view class=\"uni-card__header-extra\" @click=\"onClick('extra')\">\r\n\t\t\t\t\t<text class=\"uni-card__header-extra-text\">{{ extra }}</text>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t</slot>\r\n\t\t<!-- 卡片内容 -->\r\n\t\t<view class=\"uni-card__content\" :style=\"{padding:padding}\" @click=\"onClick('content')\">\r\n\t\t\t<slot></slot>\r\n\t\t</view>\r\n\t\t<view class=\"uni-card__actions\" @click=\"onClick('actions')\">\r\n\t\t\t<slot name=\"actions\"></slot>\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\t/**\r\n\t * Card 卡片\r\n\t * @description 卡片视图组件\r\n\t * @tutorial https://ext.dcloud.net.cn/plugin?id=22\r\n\t * @property {String} title 标题文字\r\n\t * @property {String} subTitle 副标题\r\n\t * @property {Number} padding 内容内边距\r\n\t * @property {Number} margin 卡片外边距\r\n\t * @property {Number} spacing 卡片内边距\r\n\t * @property {String} extra 标题额外信息\r\n\t * @property {String} cover 封面图（本地路径需要引入）\r\n\t * @property {String} thumbnail 标题左侧缩略图\r\n\t * @property {Boolean} is-full = [true | false] 卡片内容是否通栏，为 true 时将去除padding值\r\n\t * @property {Boolean} is-shadow = [true | false] 卡片内容是否开启阴影\r\n\t * @property {String} shadow 卡片阴影\r\n\t * @property {Boolean} border 卡片边框\r\n\t * @event {Function} click 点击 Card 触发事件\r\n\t */\r\n\texport default {\r\n\t\tname: 'UniCard',\r\n\t\temits: ['click'],\r\n\t\tprops: {\r\n\t\t\ttitle: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: ''\r\n\t\t\t},\r\n\t\t\tsubTitle: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: ''\r\n\t\t\t},\r\n\t\t\tpadding: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: '10px'\r\n\t\t\t},\r\n\t\t\tmargin: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: '15px'\r\n\t\t\t},\r\n\t\t\tspacing: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: '0 10px'\r\n\t\t\t},\r\n\t\t\textra: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: ''\r\n\t\t\t},\r\n\t\t\tcover: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: ''\r\n\t\t\t},\r\n\t\t\tthumbnail: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: ''\r\n\t\t\t},\r\n\t\t\tisFull: {\r\n\t\t\t\t// 内容区域是否通栏\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: false\r\n\t\t\t},\r\n\t\t\tisShadow: {\r\n\t\t\t\t// 是否开启阴影\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: true\r\n\t\t\t},\r\n\t\t\tshadow: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: '0px 0px 3px 1px rgba(0, 0, 0, 0.08)'\r\n\t\t\t},\r\n\t\t\tborder: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: true\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tonClick(type) {\r\n\t\t\t\tthis.$emit('click', type)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\">\r\n\t$uni-border-3: #EBEEF5 !default;\r\n\t$uni-shadow-base:0 0px 6px 1px rgba($color: #a5a5a5, $alpha: 0.2) !default;\r\n\t$uni-main-color: #3a3a3a !default;\r\n\t$uni-base-color: #6a6a6a !default;\r\n\t$uni-secondary-color: #909399 !default;\r\n\t$uni-spacing-sm: 8px !default;\r\n\t$uni-border-color:$uni-border-3;\r\n\t$uni-shadow: $uni-shadow-base;\r\n\t$uni-card-title: 15px;\r\n\t$uni-cart-title-color:$uni-main-color;\r\n\t$uni-card-subtitle: 12px;\r\n\t$uni-cart-subtitle-color:$uni-secondary-color;\r\n\t$uni-card-spacing: 10px;\r\n\t$uni-card-content-color: $uni-base-color;\r\n\r\n\t.uni-card {\r\n\t\tmargin: $uni-card-spacing;\r\n\t\tpadding: 0 $uni-spacing-sm;\r\n\t\tborder-radius: 4px;\r\n\t\toverflow: hidden;\r\n\t\tfont-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Microsoft YaHei, SimSun, sans-serif;\r\n\t\tbackground-color: #fff;\r\n\t\tflex: 1;\r\n\r\n\t\t.uni-card__cover {\r\n\t\t\tposition: relative;\r\n\t\t\tmargin-top: $uni-card-spacing;\n\t\t\tflex-direction: row;\r\n\t\t\toverflow: hidden;\r\n\t\t\tborder-radius: 4px;\n\t\t\t.uni-card__cover-image {\n\t\t\t\tflex: 1;\r\n\t\t\t\t// width: 100%;\n\t\t\t\t/* #ifndef APP-PLUS */\n\t\t\t\tvertical-align: middle;\n\t\t\t\t/* #endif */\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t.uni-card__header {\r\n\t\t\tdisplay: flex;\r\n\t\t\tborder-bottom: 1px $uni-border-color solid;\n\t\t\tflex-direction: row;\r\n\t\t\talign-items: center;\r\n\t\t\tpadding: $uni-card-spacing;\r\n\t\t\toverflow: hidden;\r\n\r\n\t\t\t.uni-card__header-box {\r\n\t\t\t\t/* #ifndef APP-NVUE */\r\n\t\t\t\tdisplay: flex;\r\n\t\t\t\t/* #endif */\r\n\t\t\t\tflex: 1;\r\n\t\t\t\tflex-direction: row;\r\n\t\t\t\talign-items: center;\r\n\t\t\t\toverflow: hidden;\r\n\t\t\t}\r\n\r\n\t\t\t.uni-card__header-avatar {\r\n\t\t\t\twidth: 40px;\r\n\t\t\t\theight: 40px;\r\n\t\t\t\toverflow: hidden;\r\n\t\t\t\tborder-radius: 5px;\r\n\t\t\t\tmargin-right: $uni-card-spacing;\r\n\t\t\t\t.uni-card__header-avatar-image {\n\t\t\t\t\tflex: 1;\r\n\t\t\t\t\twidth: 40px;\n\t\t\t\t\theight: 40px;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t.uni-card__header-content {\r\n\t\t\t\t/* #ifndef APP-NVUE */\r\n\t\t\t\tdisplay: flex;\r\n\t\t\t\t/* #endif */\r\n\t\t\t\tflex-direction: column;\r\n\t\t\t\tjustify-content: center;\r\n\t\t\t\tflex: 1;\r\n\t\t\t\t// height: 40px;\r\n\t\t\t\toverflow: hidden;\r\n\r\n\t\t\t\t.uni-card__header-content-title {\r\n\t\t\t\t\tfont-size: $uni-card-title;\r\n\t\t\t\t\tcolor: $uni-cart-title-color;\r\n\t\t\t\t\t// line-height: 22px;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t.uni-card__header-content-subtitle {\r\n\t\t\t\t\tfont-size: $uni-card-subtitle;\r\n\t\t\t\t\tmargin-top: 5px;\r\n\t\t\t\t\tcolor: $uni-cart-subtitle-color;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t.uni-card__header-extra {\r\n\t\t\t\tline-height: 12px;\r\n\r\n\t\t\t\t.uni-card__header-extra-text {\r\n\t\t\t\t\tfont-size: 12px;\r\n\t\t\t\t\tcolor: $uni-cart-subtitle-color;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t.uni-card__content {\r\n\t\t\tpadding: $uni-card-spacing;\r\n\t\t\tfont-size: 14px;\r\n\t\t\tcolor: $uni-card-content-color;\r\n\t\t\tline-height: 22px;\r\n\t\t}\r\n\r\n\t\t.uni-card__actions {\r\n\t\t\tfont-size: 12px;\r\n\t\t}\r\n\t}\r\n\n\t.uni-card--border {\n\t\tborder: 1px solid $uni-border-color;\n\t}\n\r\n\t.uni-card--shadow {\r\n\t\tposition: relative;\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\tbox-shadow: $uni-shadow;\r\n\t\t/* #endif */\r\n\t}\r\n\r\n\t.uni-card--full {\r\n\t\tmargin: 0;\r\n\t\tborder-left-width: 0;\r\n\t\tborder-left-width: 0;\r\n\t\tborder-radius: 0;\r\n\t}\r\n\r\n\t/* #ifndef APP-NVUE */\r\n\t.uni-card--full:after {\r\n\t\tborder-radius: 0;\r\n\t}\r\n\r\n\t/* #endif */\r\n\t.uni-ellipsis {\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\toverflow: hidden;\r\n\t\twhite-space: nowrap;\r\n\t\ttext-overflow: ellipsis;\r\n\t\t/* #endif */\r\n\t\t/* #ifdef APP-NVUE */\r\n\t\tlines: 1;\r\n\t\t/* #endif */\r\n\t}\r\n</style>\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uni-card/package.json",
    "content": "{\n  \"id\": \"uni-card\",\n  \"displayName\": \"uni-card 卡片\",\n  \"version\": \"1.3.1\",\n  \"description\": \"Card 组件，提供常见的卡片样式。\",\n  \"keywords\": [\n    \"uni-ui\",\n    \"uniui\",\n    \"card\",\n    \"\",\n    \"卡片\"\n],\n  \"repository\": \"https://github.com/dcloudio/uni-ui\",\n  \"engines\": {\n    \"HBuilderX\": \"\"\n  },\n  \"directories\": {\n    \"example\": \"../../temps/example_temps\"\n  },\n  \"dcloudext\": {\n    \"category\": [\n      \"前端组件\",\n      \"通用组件\"\n    ],\n    \"sale\": {\n      \"regular\": {\n        \"price\": \"0.00\"\n      },\n      \"sourcecode\": {\n        \"price\": \"0.00\"\n      }\n    },\n    \"contact\": {\n      \"qq\": \"\"\n    },\n    \"declaration\": {\n      \"ads\": \"无\",\n      \"data\": \"无\",\n      \"permissions\": \"无\"\n    },\n    \"npmurl\": \"https://www.npmjs.com/package/@dcloudio/uni-ui\"\n  },\n  \"uni_modules\": {\n    \"dependencies\": [\n\t\t\t\"uni-icons\",\n\t\t\t\"uni-scss\"\n\t\t],\n    \"encrypt\": [],\n    \"platforms\": {\n      \"cloud\": {\n        \"tcb\": \"y\",\n        \"aliyun\": \"y\"\n      },\n      \"client\": {\n        \"App\": {\n          \"app-vue\": \"y\",\n          \"app-nvue\": \"y\"\n        },\n        \"H5-mobile\": {\n          \"Safari\": \"y\",\n          \"Android Browser\": \"y\",\n          \"微信浏览器(Android)\": \"y\",\n          \"QQ浏览器(Android)\": \"y\"\n        },\n        \"H5-pc\": {\n          \"Chrome\": \"y\",\n          \"IE\": \"y\",\n          \"Edge\": \"y\",\n          \"Firefox\": \"y\",\n          \"Safari\": \"y\"\n        },\n        \"小程序\": {\n          \"微信\": \"y\",\n          \"阿里\": \"y\",\n          \"百度\": \"y\",\n          \"字节跳动\": \"y\",\n          \"QQ\": \"y\"\n        },\n        \"快应用\": {\n          \"华为\": \"u\",\n          \"联盟\": \"u\"\n        },\n        \"Vue\": {\n            \"vue2\": \"y\",\n            \"vue3\": \"y\"\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uni-card/readme.md",
    "content": "\n\n## Card 卡片\n> **组件名：uni-card**\n> 代码块： `uCard`\n\n卡片视图组件。\n\n### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-card)\n#### 如使用过程中有任何问题，或者您对uni-ui有一些好的建议，欢迎加入 uni-ui 交流群：871950839 \n\n\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uni-icons/changelog.md",
    "content": "## 1.3.5（2022-01-24）\n- 优化 size 属性可以传入不带单位的字符串数值\n## 1.3.4（2022-01-24）\n- 优化 size 支持其他单位\n## 1.3.3（2022-01-17）\n- 修复 nvue 有些图标不显示的bug，兼容老版本图标\n## 1.3.2（2021-12-01）\n- 优化 示例可复制图标名称\n## 1.3.1（2021-11-23）\n- 优化 兼容旧组件 type 值\n## 1.3.0（2021-11-19）\n- 新增 更多图标\n- 优化 自定义图标使用方式\n- 优化 组件UI，并提供设计资源，详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)\n- 文档迁移，详见:[https://uniapp.dcloud.io/component/uniui/uni-icons](https://uniapp.dcloud.io/component/uniui/uni-icons)\n## 1.1.7（2021-11-08）\n## 1.2.0（2021-07-30）\n- 组件兼容 vue3，如何创建vue3项目，详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)\n## 1.1.5（2021-05-12）\n- 新增 组件示例地址\n## 1.1.4（2021-02-05）\n- 调整为uni_modules目录规范\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uni-icons/components/uni-icons/icons.js",
    "content": "export default {\n  \"id\": \"2852637\",\n  \"name\": \"uniui图标库\",\n  \"font_family\": \"uniicons\",\n  \"css_prefix_text\": \"uniui-\",\n  \"description\": \"\",\n  \"glyphs\": [\n    {\n      \"icon_id\": \"25027049\",\n      \"name\": \"yanse\",\n      \"font_class\": \"color\",\n      \"unicode\": \"e6cf\",\n      \"unicode_decimal\": 59087\n    },\n    {\n      \"icon_id\": \"25027048\",\n      \"name\": \"wallet\",\n      \"font_class\": \"wallet\",\n      \"unicode\": \"e6b1\",\n      \"unicode_decimal\": 59057\n    },\n    {\n      \"icon_id\": \"25015720\",\n      \"name\": \"settings-filled\",\n      \"font_class\": \"settings-filled\",\n      \"unicode\": \"e6ce\",\n      \"unicode_decimal\": 59086\n    },\n    {\n      \"icon_id\": \"25015434\",\n      \"name\": \"shimingrenzheng-filled\",\n      \"font_class\": \"auth-filled\",\n      \"unicode\": \"e6cc\",\n      \"unicode_decimal\": 59084\n    },\n    {\n      \"icon_id\": \"24934246\",\n      \"name\": \"shop-filled\",\n      \"font_class\": \"shop-filled\",\n      \"unicode\": \"e6cd\",\n      \"unicode_decimal\": 59085\n    },\n    {\n      \"icon_id\": \"24934159\",\n      \"name\": \"staff-filled-01\",\n      \"font_class\": \"staff-filled\",\n      \"unicode\": \"e6cb\",\n      \"unicode_decimal\": 59083\n    },\n    {\n      \"icon_id\": \"24932461\",\n      \"name\": \"VIP-filled\",\n      \"font_class\": \"vip-filled\",\n      \"unicode\": \"e6c6\",\n      \"unicode_decimal\": 59078\n    },\n    {\n      \"icon_id\": \"24932462\",\n      \"name\": \"plus_circle_fill\",\n      \"font_class\": \"plus-filled\",\n      \"unicode\": \"e6c7\",\n      \"unicode_decimal\": 59079\n    },\n    {\n      \"icon_id\": \"24932463\",\n      \"name\": \"folder_add-filled\",\n      \"font_class\": \"folder-add-filled\",\n      \"unicode\": \"e6c8\",\n      \"unicode_decimal\": 59080\n    },\n    {\n      \"icon_id\": \"24932464\",\n      \"name\": \"yanse-filled\",\n      \"font_class\": \"color-filled\",\n      \"unicode\": \"e6c9\",\n      \"unicode_decimal\": 59081\n    },\n    {\n      \"icon_id\": \"24932465\",\n      \"name\": \"tune-filled\",\n      \"font_class\": \"tune-filled\",\n      \"unicode\": \"e6ca\",\n      \"unicode_decimal\": 59082\n    },\n    {\n      \"icon_id\": \"24932455\",\n      \"name\": \"a-rilidaka-filled\",\n      \"font_class\": \"calendar-filled\",\n      \"unicode\": \"e6c0\",\n      \"unicode_decimal\": 59072\n    },\n    {\n      \"icon_id\": \"24932456\",\n      \"name\": \"notification-filled\",\n      \"font_class\": \"notification-filled\",\n      \"unicode\": \"e6c1\",\n      \"unicode_decimal\": 59073\n    },\n    {\n      \"icon_id\": \"24932457\",\n      \"name\": \"wallet-filled\",\n      \"font_class\": \"wallet-filled\",\n      \"unicode\": \"e6c2\",\n      \"unicode_decimal\": 59074\n    },\n    {\n      \"icon_id\": \"24932458\",\n      \"name\": \"paihangbang-filled\",\n      \"font_class\": \"medal-filled\",\n      \"unicode\": \"e6c3\",\n      \"unicode_decimal\": 59075\n    },\n    {\n      \"icon_id\": \"24932459\",\n      \"name\": \"gift-filled\",\n      \"font_class\": \"gift-filled\",\n      \"unicode\": \"e6c4\",\n      \"unicode_decimal\": 59076\n    },\n    {\n      \"icon_id\": \"24932460\",\n      \"name\": \"fire-filled\",\n      \"font_class\": \"fire-filled\",\n      \"unicode\": \"e6c5\",\n      \"unicode_decimal\": 59077\n    },\n    {\n      \"icon_id\": \"24928001\",\n      \"name\": \"refreshempty\",\n      \"font_class\": \"refreshempty\",\n      \"unicode\": \"e6bf\",\n      \"unicode_decimal\": 59071\n    },\n    {\n      \"icon_id\": \"24926853\",\n      \"name\": \"location-ellipse\",\n      \"font_class\": \"location-filled\",\n      \"unicode\": \"e6af\",\n      \"unicode_decimal\": 59055\n    },\n    {\n      \"icon_id\": \"24926735\",\n      \"name\": \"person-filled\",\n      \"font_class\": \"person-filled\",\n      \"unicode\": \"e69d\",\n      \"unicode_decimal\": 59037\n    },\n    {\n      \"icon_id\": \"24926703\",\n      \"name\": \"personadd-filled\",\n      \"font_class\": \"personadd-filled\",\n      \"unicode\": \"e698\",\n      \"unicode_decimal\": 59032\n    },\n    {\n      \"icon_id\": \"24923351\",\n      \"name\": \"back\",\n      \"font_class\": \"back\",\n      \"unicode\": \"e6b9\",\n      \"unicode_decimal\": 59065\n    },\n    {\n      \"icon_id\": \"24923352\",\n      \"name\": \"forward\",\n      \"font_class\": \"forward\",\n      \"unicode\": \"e6ba\",\n      \"unicode_decimal\": 59066\n    },\n    {\n      \"icon_id\": \"24923353\",\n      \"name\": \"arrowthinright\",\n      \"font_class\": \"arrow-right\",\n      \"unicode\": \"e6bb\",\n      \"unicode_decimal\": 59067\n    },\n\t\t{\n\t\t  \"icon_id\": \"24923353\",\n\t\t  \"name\": \"arrowthinright\",\n\t\t  \"font_class\": \"arrowthinright\",\n\t\t  \"unicode\": \"e6bb\",\n\t\t  \"unicode_decimal\": 59067\n\t\t},\n    {\n      \"icon_id\": \"24923354\",\n      \"name\": \"arrowthinleft\",\n      \"font_class\": \"arrow-left\",\n      \"unicode\": \"e6bc\",\n      \"unicode_decimal\": 59068\n    },\n\t\t{\n\t\t  \"icon_id\": \"24923354\",\n\t\t  \"name\": \"arrowthinleft\",\n\t\t  \"font_class\": \"arrowthinleft\",\n\t\t  \"unicode\": \"e6bc\",\n\t\t  \"unicode_decimal\": 59068\n\t\t},\n    {\n      \"icon_id\": \"24923355\",\n      \"name\": \"arrowthinup\",\n      \"font_class\": \"arrow-up\",\n      \"unicode\": \"e6bd\",\n      \"unicode_decimal\": 59069\n    },\n\t\t{\n\t\t  \"icon_id\": \"24923355\",\n\t\t  \"name\": \"arrowthinup\",\n\t\t  \"font_class\": \"arrowthinup\",\n\t\t  \"unicode\": \"e6bd\",\n\t\t  \"unicode_decimal\": 59069\n\t\t},\n    {\n      \"icon_id\": \"24923356\",\n      \"name\": \"arrowthindown\",\n      \"font_class\": \"arrow-down\",\n      \"unicode\": \"e6be\",\n      \"unicode_decimal\": 59070\n    },{\n      \"icon_id\": \"24923356\",\n      \"name\": \"arrowthindown\",\n      \"font_class\": \"arrowthindown\",\n      \"unicode\": \"e6be\",\n      \"unicode_decimal\": 59070\n    },\n    {\n      \"icon_id\": \"24923349\",\n      \"name\": \"arrowdown\",\n      \"font_class\": \"bottom\",\n      \"unicode\": \"e6b8\",\n      \"unicode_decimal\": 59064\n    },{\n      \"icon_id\": \"24923349\",\n      \"name\": \"arrowdown\",\n      \"font_class\": \"arrowdown\",\n      \"unicode\": \"e6b8\",\n      \"unicode_decimal\": 59064\n    },\n    {\n      \"icon_id\": \"24923346\",\n      \"name\": \"arrowright\",\n      \"font_class\": \"right\",\n      \"unicode\": \"e6b5\",\n      \"unicode_decimal\": 59061\n    },\n\t\t{\n\t\t  \"icon_id\": \"24923346\",\n\t\t  \"name\": \"arrowright\",\n\t\t  \"font_class\": \"arrowright\",\n\t\t  \"unicode\": \"e6b5\",\n\t\t  \"unicode_decimal\": 59061\n\t\t},\n    {\n      \"icon_id\": \"24923347\",\n      \"name\": \"arrowup\",\n      \"font_class\": \"top\",\n      \"unicode\": \"e6b6\",\n      \"unicode_decimal\": 59062\n    },\n\t\t{\n\t\t  \"icon_id\": \"24923347\",\n\t\t  \"name\": \"arrowup\",\n\t\t  \"font_class\": \"arrowup\",\n\t\t  \"unicode\": \"e6b6\",\n\t\t  \"unicode_decimal\": 59062\n\t\t},\n    {\n      \"icon_id\": \"24923348\",\n      \"name\": \"arrowleft\",\n      \"font_class\": \"left\",\n      \"unicode\": \"e6b7\",\n      \"unicode_decimal\": 59063\n    },\n\t\t{\n\t\t  \"icon_id\": \"24923348\",\n\t\t  \"name\": \"arrowleft\",\n\t\t  \"font_class\": \"arrowleft\",\n\t\t  \"unicode\": \"e6b7\",\n\t\t  \"unicode_decimal\": 59063\n\t\t},\n    {\n      \"icon_id\": \"24923334\",\n      \"name\": \"eye\",\n      \"font_class\": \"eye\",\n      \"unicode\": \"e651\",\n      \"unicode_decimal\": 58961\n    },\n    {\n      \"icon_id\": \"24923335\",\n      \"name\": \"eye-filled\",\n      \"font_class\": \"eye-filled\",\n      \"unicode\": \"e66a\",\n      \"unicode_decimal\": 58986\n    },\n    {\n      \"icon_id\": \"24923336\",\n      \"name\": \"eye-slash\",\n      \"font_class\": \"eye-slash\",\n      \"unicode\": \"e6b3\",\n      \"unicode_decimal\": 59059\n    },\n    {\n      \"icon_id\": \"24923337\",\n      \"name\": \"eye-slash-filled\",\n      \"font_class\": \"eye-slash-filled\",\n      \"unicode\": \"e6b4\",\n      \"unicode_decimal\": 59060\n    },\n    {\n      \"icon_id\": \"24923305\",\n      \"name\": \"info-filled\",\n      \"font_class\": \"info-filled\",\n      \"unicode\": \"e649\",\n      \"unicode_decimal\": 58953\n    },\n    {\n      \"icon_id\": \"24923299\",\n      \"name\": \"reload-01\",\n      \"font_class\": \"reload\",\n      \"unicode\": \"e6b2\",\n      \"unicode_decimal\": 59058\n    },\n    {\n      \"icon_id\": \"24923195\",\n      \"name\": \"mic_slash_fill\",\n      \"font_class\": \"micoff-filled\",\n      \"unicode\": \"e6b0\",\n      \"unicode_decimal\": 59056\n    },\n    {\n      \"icon_id\": \"24923165\",\n      \"name\": \"map-pin-ellipse\",\n      \"font_class\": \"map-pin-ellipse\",\n      \"unicode\": \"e6ac\",\n      \"unicode_decimal\": 59052\n    },\n    {\n      \"icon_id\": \"24923166\",\n      \"name\": \"map-pin\",\n      \"font_class\": \"map-pin\",\n      \"unicode\": \"e6ad\",\n      \"unicode_decimal\": 59053\n    },\n    {\n      \"icon_id\": \"24923167\",\n      \"name\": \"location\",\n      \"font_class\": \"location\",\n      \"unicode\": \"e6ae\",\n      \"unicode_decimal\": 59054\n    },\n    {\n      \"icon_id\": \"24923064\",\n      \"name\": \"starhalf\",\n      \"font_class\": \"starhalf\",\n      \"unicode\": \"e683\",\n      \"unicode_decimal\": 59011\n    },\n    {\n      \"icon_id\": \"24923065\",\n      \"name\": \"star\",\n      \"font_class\": \"star\",\n      \"unicode\": \"e688\",\n      \"unicode_decimal\": 59016\n    },\n    {\n      \"icon_id\": \"24923066\",\n      \"name\": \"star-filled\",\n      \"font_class\": \"star-filled\",\n      \"unicode\": \"e68f\",\n      \"unicode_decimal\": 59023\n    },\n    {\n      \"icon_id\": \"24899646\",\n      \"name\": \"a-rilidaka\",\n      \"font_class\": \"calendar\",\n      \"unicode\": \"e6a0\",\n      \"unicode_decimal\": 59040\n    },\n    {\n      \"icon_id\": \"24899647\",\n      \"name\": \"fire\",\n      \"font_class\": \"fire\",\n      \"unicode\": \"e6a1\",\n      \"unicode_decimal\": 59041\n    },\n    {\n      \"icon_id\": \"24899648\",\n      \"name\": \"paihangbang\",\n      \"font_class\": \"medal\",\n      \"unicode\": \"e6a2\",\n      \"unicode_decimal\": 59042\n    },\n    {\n      \"icon_id\": \"24899649\",\n      \"name\": \"font\",\n      \"font_class\": \"font\",\n      \"unicode\": \"e6a3\",\n      \"unicode_decimal\": 59043\n    },\n    {\n      \"icon_id\": \"24899650\",\n      \"name\": \"gift\",\n      \"font_class\": \"gift\",\n      \"unicode\": \"e6a4\",\n      \"unicode_decimal\": 59044\n    },\n    {\n      \"icon_id\": \"24899651\",\n      \"name\": \"link\",\n      \"font_class\": \"link\",\n      \"unicode\": \"e6a5\",\n      \"unicode_decimal\": 59045\n    },\n    {\n      \"icon_id\": \"24899652\",\n      \"name\": \"notification\",\n      \"font_class\": \"notification\",\n      \"unicode\": \"e6a6\",\n      \"unicode_decimal\": 59046\n    },\n    {\n      \"icon_id\": \"24899653\",\n      \"name\": \"staff\",\n      \"font_class\": \"staff\",\n      \"unicode\": \"e6a7\",\n      \"unicode_decimal\": 59047\n    },\n    {\n      \"icon_id\": \"24899654\",\n      \"name\": \"VIP\",\n      \"font_class\": \"vip\",\n      \"unicode\": \"e6a8\",\n      \"unicode_decimal\": 59048\n    },\n    {\n      \"icon_id\": \"24899655\",\n      \"name\": \"folder_add\",\n      \"font_class\": \"folder-add\",\n      \"unicode\": \"e6a9\",\n      \"unicode_decimal\": 59049\n    },\n    {\n      \"icon_id\": \"24899656\",\n      \"name\": \"tune\",\n      \"font_class\": \"tune\",\n      \"unicode\": \"e6aa\",\n      \"unicode_decimal\": 59050\n    },\n    {\n      \"icon_id\": \"24899657\",\n      \"name\": \"shimingrenzheng\",\n      \"font_class\": \"auth\",\n      \"unicode\": \"e6ab\",\n      \"unicode_decimal\": 59051\n    },\n    {\n      \"icon_id\": \"24899565\",\n      \"name\": \"person\",\n      \"font_class\": \"person\",\n      \"unicode\": \"e699\",\n      \"unicode_decimal\": 59033\n    },\n    {\n      \"icon_id\": \"24899566\",\n      \"name\": \"email-filled\",\n      \"font_class\": \"email-filled\",\n      \"unicode\": \"e69a\",\n      \"unicode_decimal\": 59034\n    },\n    {\n      \"icon_id\": \"24899567\",\n      \"name\": \"phone-filled\",\n      \"font_class\": \"phone-filled\",\n      \"unicode\": \"e69b\",\n      \"unicode_decimal\": 59035\n    },\n    {\n      \"icon_id\": \"24899568\",\n      \"name\": \"phone\",\n      \"font_class\": \"phone\",\n      \"unicode\": \"e69c\",\n      \"unicode_decimal\": 59036\n    },\n    {\n      \"icon_id\": \"24899570\",\n      \"name\": \"email\",\n      \"font_class\": \"email\",\n      \"unicode\": \"e69e\",\n      \"unicode_decimal\": 59038\n    },\n    {\n      \"icon_id\": \"24899571\",\n      \"name\": \"personadd\",\n      \"font_class\": \"personadd\",\n      \"unicode\": \"e69f\",\n      \"unicode_decimal\": 59039\n    },\n    {\n      \"icon_id\": \"24899558\",\n      \"name\": \"chatboxes-filled\",\n      \"font_class\": \"chatboxes-filled\",\n      \"unicode\": \"e692\",\n      \"unicode_decimal\": 59026\n    },\n    {\n      \"icon_id\": \"24899559\",\n      \"name\": \"contact\",\n      \"font_class\": \"contact\",\n      \"unicode\": \"e693\",\n      \"unicode_decimal\": 59027\n    },\n    {\n      \"icon_id\": \"24899560\",\n      \"name\": \"chatbubble-filled\",\n      \"font_class\": \"chatbubble-filled\",\n      \"unicode\": \"e694\",\n      \"unicode_decimal\": 59028\n    },\n    {\n      \"icon_id\": \"24899561\",\n      \"name\": \"contact-filled\",\n      \"font_class\": \"contact-filled\",\n      \"unicode\": \"e695\",\n      \"unicode_decimal\": 59029\n    },\n    {\n      \"icon_id\": \"24899562\",\n      \"name\": \"chatboxes\",\n      \"font_class\": \"chatboxes\",\n      \"unicode\": \"e696\",\n      \"unicode_decimal\": 59030\n    },\n    {\n      \"icon_id\": \"24899563\",\n      \"name\": \"chatbubble\",\n      \"font_class\": \"chatbubble\",\n      \"unicode\": \"e697\",\n      \"unicode_decimal\": 59031\n    },\n    {\n      \"icon_id\": \"24881290\",\n      \"name\": \"upload-filled\",\n      \"font_class\": \"upload-filled\",\n      \"unicode\": \"e68e\",\n      \"unicode_decimal\": 59022\n    },\n    {\n      \"icon_id\": \"24881292\",\n      \"name\": \"upload\",\n      \"font_class\": \"upload\",\n      \"unicode\": \"e690\",\n      \"unicode_decimal\": 59024\n    },\n    {\n      \"icon_id\": \"24881293\",\n      \"name\": \"weixin\",\n      \"font_class\": \"weixin\",\n      \"unicode\": \"e691\",\n      \"unicode_decimal\": 59025\n    },\n    {\n      \"icon_id\": \"24881274\",\n      \"name\": \"compose\",\n      \"font_class\": \"compose\",\n      \"unicode\": \"e67f\",\n      \"unicode_decimal\": 59007\n    },\n    {\n      \"icon_id\": \"24881275\",\n      \"name\": \"qq\",\n      \"font_class\": \"qq\",\n      \"unicode\": \"e680\",\n      \"unicode_decimal\": 59008\n    },\n    {\n      \"icon_id\": \"24881276\",\n      \"name\": \"download-filled\",\n      \"font_class\": \"download-filled\",\n      \"unicode\": \"e681\",\n      \"unicode_decimal\": 59009\n    },\n    {\n      \"icon_id\": \"24881277\",\n      \"name\": \"pengyouquan\",\n      \"font_class\": \"pyq\",\n      \"unicode\": \"e682\",\n      \"unicode_decimal\": 59010\n    },\n    {\n      \"icon_id\": \"24881279\",\n      \"name\": \"sound\",\n      \"font_class\": \"sound\",\n      \"unicode\": \"e684\",\n      \"unicode_decimal\": 59012\n    },\n    {\n      \"icon_id\": \"24881280\",\n      \"name\": \"trash-filled\",\n      \"font_class\": \"trash-filled\",\n      \"unicode\": \"e685\",\n      \"unicode_decimal\": 59013\n    },\n    {\n      \"icon_id\": \"24881281\",\n      \"name\": \"sound-filled\",\n      \"font_class\": \"sound-filled\",\n      \"unicode\": \"e686\",\n      \"unicode_decimal\": 59014\n    },\n    {\n      \"icon_id\": \"24881282\",\n      \"name\": \"trash\",\n      \"font_class\": \"trash\",\n      \"unicode\": \"e687\",\n      \"unicode_decimal\": 59015\n    },\n    {\n      \"icon_id\": \"24881284\",\n      \"name\": \"videocam-filled\",\n      \"font_class\": \"videocam-filled\",\n      \"unicode\": \"e689\",\n      \"unicode_decimal\": 59017\n    },\n    {\n      \"icon_id\": \"24881285\",\n      \"name\": \"spinner-cycle\",\n      \"font_class\": \"spinner-cycle\",\n      \"unicode\": \"e68a\",\n      \"unicode_decimal\": 59018\n    },\n    {\n      \"icon_id\": \"24881286\",\n      \"name\": \"weibo\",\n      \"font_class\": \"weibo\",\n      \"unicode\": \"e68b\",\n      \"unicode_decimal\": 59019\n    },\n    {\n      \"icon_id\": \"24881288\",\n      \"name\": \"videocam\",\n      \"font_class\": \"videocam\",\n      \"unicode\": \"e68c\",\n      \"unicode_decimal\": 59020\n    },\n    {\n      \"icon_id\": \"24881289\",\n      \"name\": \"download\",\n      \"font_class\": \"download\",\n      \"unicode\": \"e68d\",\n      \"unicode_decimal\": 59021\n    },\n    {\n      \"icon_id\": \"24879601\",\n      \"name\": \"help\",\n      \"font_class\": \"help\",\n      \"unicode\": \"e679\",\n      \"unicode_decimal\": 59001\n    },\n    {\n      \"icon_id\": \"24879602\",\n      \"name\": \"navigate-filled\",\n      \"font_class\": \"navigate-filled\",\n      \"unicode\": \"e67a\",\n      \"unicode_decimal\": 59002\n    },\n    {\n      \"icon_id\": \"24879603\",\n      \"name\": \"plusempty\",\n      \"font_class\": \"plusempty\",\n      \"unicode\": \"e67b\",\n      \"unicode_decimal\": 59003\n    },\n    {\n      \"icon_id\": \"24879604\",\n      \"name\": \"smallcircle\",\n      \"font_class\": \"smallcircle\",\n      \"unicode\": \"e67c\",\n      \"unicode_decimal\": 59004\n    },\n    {\n      \"icon_id\": \"24879605\",\n      \"name\": \"minus-filled\",\n      \"font_class\": \"minus-filled\",\n      \"unicode\": \"e67d\",\n      \"unicode_decimal\": 59005\n    },\n    {\n      \"icon_id\": \"24879606\",\n      \"name\": \"micoff\",\n      \"font_class\": \"micoff\",\n      \"unicode\": \"e67e\",\n      \"unicode_decimal\": 59006\n    },\n    {\n      \"icon_id\": \"24879588\",\n      \"name\": \"closeempty\",\n      \"font_class\": \"closeempty\",\n      \"unicode\": \"e66c\",\n      \"unicode_decimal\": 58988\n    },\n    {\n      \"icon_id\": \"24879589\",\n      \"name\": \"clear\",\n      \"font_class\": \"clear\",\n      \"unicode\": \"e66d\",\n      \"unicode_decimal\": 58989\n    },\n    {\n      \"icon_id\": \"24879590\",\n      \"name\": \"navigate\",\n      \"font_class\": \"navigate\",\n      \"unicode\": \"e66e\",\n      \"unicode_decimal\": 58990\n    },\n    {\n      \"icon_id\": \"24879591\",\n      \"name\": \"minus\",\n      \"font_class\": \"minus\",\n      \"unicode\": \"e66f\",\n      \"unicode_decimal\": 58991\n    },\n    {\n      \"icon_id\": \"24879592\",\n      \"name\": \"image\",\n      \"font_class\": \"image\",\n      \"unicode\": \"e670\",\n      \"unicode_decimal\": 58992\n    },\n    {\n      \"icon_id\": \"24879593\",\n      \"name\": \"mic\",\n      \"font_class\": \"mic\",\n      \"unicode\": \"e671\",\n      \"unicode_decimal\": 58993\n    },\n    {\n      \"icon_id\": \"24879594\",\n      \"name\": \"paperplane\",\n      \"font_class\": \"paperplane\",\n      \"unicode\": \"e672\",\n      \"unicode_decimal\": 58994\n    },\n    {\n      \"icon_id\": \"24879595\",\n      \"name\": \"close\",\n      \"font_class\": \"close\",\n      \"unicode\": \"e673\",\n      \"unicode_decimal\": 58995\n    },\n    {\n      \"icon_id\": \"24879596\",\n      \"name\": \"help-filled\",\n      \"font_class\": \"help-filled\",\n      \"unicode\": \"e674\",\n      \"unicode_decimal\": 58996\n    },\n    {\n      \"icon_id\": \"24879597\",\n      \"name\": \"plus-filled\",\n      \"font_class\": \"paperplane-filled\",\n      \"unicode\": \"e675\",\n      \"unicode_decimal\": 58997\n    },\n    {\n      \"icon_id\": \"24879598\",\n      \"name\": \"plus\",\n      \"font_class\": \"plus\",\n      \"unicode\": \"e676\",\n      \"unicode_decimal\": 58998\n    },\n    {\n      \"icon_id\": \"24879599\",\n      \"name\": \"mic-filled\",\n      \"font_class\": \"mic-filled\",\n      \"unicode\": \"e677\",\n      \"unicode_decimal\": 58999\n    },\n    {\n      \"icon_id\": \"24879600\",\n      \"name\": \"image-filled\",\n      \"font_class\": \"image-filled\",\n      \"unicode\": \"e678\",\n      \"unicode_decimal\": 59000\n    },\n    {\n      \"icon_id\": \"24855900\",\n      \"name\": \"locked-filled\",\n      \"font_class\": \"locked-filled\",\n      \"unicode\": \"e668\",\n      \"unicode_decimal\": 58984\n    },\n    {\n      \"icon_id\": \"24855901\",\n      \"name\": \"info\",\n      \"font_class\": \"info\",\n      \"unicode\": \"e669\",\n      \"unicode_decimal\": 58985\n    },\n    {\n      \"icon_id\": \"24855903\",\n      \"name\": \"locked\",\n      \"font_class\": \"locked\",\n      \"unicode\": \"e66b\",\n      \"unicode_decimal\": 58987\n    },\n    {\n      \"icon_id\": \"24855884\",\n      \"name\": \"camera-filled\",\n      \"font_class\": \"camera-filled\",\n      \"unicode\": \"e658\",\n      \"unicode_decimal\": 58968\n    },\n    {\n      \"icon_id\": \"24855885\",\n      \"name\": \"chat-filled\",\n      \"font_class\": \"chat-filled\",\n      \"unicode\": \"e659\",\n      \"unicode_decimal\": 58969\n    },\n    {\n      \"icon_id\": \"24855886\",\n      \"name\": \"camera\",\n      \"font_class\": \"camera\",\n      \"unicode\": \"e65a\",\n      \"unicode_decimal\": 58970\n    },\n    {\n      \"icon_id\": \"24855887\",\n      \"name\": \"circle\",\n      \"font_class\": \"circle\",\n      \"unicode\": \"e65b\",\n      \"unicode_decimal\": 58971\n    },\n    {\n      \"icon_id\": \"24855888\",\n      \"name\": \"checkmarkempty\",\n      \"font_class\": \"checkmarkempty\",\n      \"unicode\": \"e65c\",\n      \"unicode_decimal\": 58972\n    },\n    {\n      \"icon_id\": \"24855889\",\n      \"name\": \"chat\",\n      \"font_class\": \"chat\",\n      \"unicode\": \"e65d\",\n      \"unicode_decimal\": 58973\n    },\n    {\n      \"icon_id\": \"24855890\",\n      \"name\": \"circle-filled\",\n      \"font_class\": \"circle-filled\",\n      \"unicode\": \"e65e\",\n      \"unicode_decimal\": 58974\n    },\n    {\n      \"icon_id\": \"24855891\",\n      \"name\": \"flag\",\n      \"font_class\": \"flag\",\n      \"unicode\": \"e65f\",\n      \"unicode_decimal\": 58975\n    },\n    {\n      \"icon_id\": \"24855892\",\n      \"name\": \"flag-filled\",\n      \"font_class\": \"flag-filled\",\n      \"unicode\": \"e660\",\n      \"unicode_decimal\": 58976\n    },\n    {\n      \"icon_id\": \"24855893\",\n      \"name\": \"gear-filled\",\n      \"font_class\": \"gear-filled\",\n      \"unicode\": \"e661\",\n      \"unicode_decimal\": 58977\n    },\n    {\n      \"icon_id\": \"24855894\",\n      \"name\": \"home\",\n      \"font_class\": \"home\",\n      \"unicode\": \"e662\",\n      \"unicode_decimal\": 58978\n    },\n    {\n      \"icon_id\": \"24855895\",\n      \"name\": \"home-filled\",\n      \"font_class\": \"home-filled\",\n      \"unicode\": \"e663\",\n      \"unicode_decimal\": 58979\n    },\n    {\n      \"icon_id\": \"24855896\",\n      \"name\": \"gear\",\n      \"font_class\": \"gear\",\n      \"unicode\": \"e664\",\n      \"unicode_decimal\": 58980\n    },\n    {\n      \"icon_id\": \"24855897\",\n      \"name\": \"smallcircle-filled\",\n      \"font_class\": \"smallcircle-filled\",\n      \"unicode\": \"e665\",\n      \"unicode_decimal\": 58981\n    },\n    {\n      \"icon_id\": \"24855898\",\n      \"name\": \"map-filled\",\n      \"font_class\": \"map-filled\",\n      \"unicode\": \"e666\",\n      \"unicode_decimal\": 58982\n    },\n    {\n      \"icon_id\": \"24855899\",\n      \"name\": \"map\",\n      \"font_class\": \"map\",\n      \"unicode\": \"e667\",\n      \"unicode_decimal\": 58983\n    },\n    {\n      \"icon_id\": \"24855825\",\n      \"name\": \"refresh-filled\",\n      \"font_class\": \"refresh-filled\",\n      \"unicode\": \"e656\",\n      \"unicode_decimal\": 58966\n    },\n    {\n      \"icon_id\": \"24855826\",\n      \"name\": \"refresh\",\n      \"font_class\": \"refresh\",\n      \"unicode\": \"e657\",\n      \"unicode_decimal\": 58967\n    },\n    {\n      \"icon_id\": \"24855808\",\n      \"name\": \"cloud-upload\",\n      \"font_class\": \"cloud-upload\",\n      \"unicode\": \"e645\",\n      \"unicode_decimal\": 58949\n    },\n    {\n      \"icon_id\": \"24855809\",\n      \"name\": \"cloud-download-filled\",\n      \"font_class\": \"cloud-download-filled\",\n      \"unicode\": \"e646\",\n      \"unicode_decimal\": 58950\n    },\n    {\n      \"icon_id\": \"24855810\",\n      \"name\": \"cloud-download\",\n      \"font_class\": \"cloud-download\",\n      \"unicode\": \"e647\",\n      \"unicode_decimal\": 58951\n    },\n    {\n      \"icon_id\": \"24855811\",\n      \"name\": \"cloud-upload-filled\",\n      \"font_class\": \"cloud-upload-filled\",\n      \"unicode\": \"e648\",\n      \"unicode_decimal\": 58952\n    },\n    {\n      \"icon_id\": \"24855813\",\n      \"name\": \"redo\",\n      \"font_class\": \"redo\",\n      \"unicode\": \"e64a\",\n      \"unicode_decimal\": 58954\n    },\n    {\n      \"icon_id\": \"24855814\",\n      \"name\": \"images-filled\",\n      \"font_class\": \"images-filled\",\n      \"unicode\": \"e64b\",\n      \"unicode_decimal\": 58955\n    },\n    {\n      \"icon_id\": \"24855815\",\n      \"name\": \"undo-filled\",\n      \"font_class\": \"undo-filled\",\n      \"unicode\": \"e64c\",\n      \"unicode_decimal\": 58956\n    },\n    {\n      \"icon_id\": \"24855816\",\n      \"name\": \"more\",\n      \"font_class\": \"more\",\n      \"unicode\": \"e64d\",\n      \"unicode_decimal\": 58957\n    },\n    {\n      \"icon_id\": \"24855817\",\n      \"name\": \"more-filled\",\n      \"font_class\": \"more-filled\",\n      \"unicode\": \"e64e\",\n      \"unicode_decimal\": 58958\n    },\n    {\n      \"icon_id\": \"24855818\",\n      \"name\": \"undo\",\n      \"font_class\": \"undo\",\n      \"unicode\": \"e64f\",\n      \"unicode_decimal\": 58959\n    },\n    {\n      \"icon_id\": \"24855819\",\n      \"name\": \"images\",\n      \"font_class\": \"images\",\n      \"unicode\": \"e650\",\n      \"unicode_decimal\": 58960\n    },\n    {\n      \"icon_id\": \"24855821\",\n      \"name\": \"paperclip\",\n      \"font_class\": \"paperclip\",\n      \"unicode\": \"e652\",\n      \"unicode_decimal\": 58962\n    },\n    {\n      \"icon_id\": \"24855822\",\n      \"name\": \"settings\",\n      \"font_class\": \"settings\",\n      \"unicode\": \"e653\",\n      \"unicode_decimal\": 58963\n    },\n    {\n      \"icon_id\": \"24855823\",\n      \"name\": \"search\",\n      \"font_class\": \"search\",\n      \"unicode\": \"e654\",\n      \"unicode_decimal\": 58964\n    },\n    {\n      \"icon_id\": \"24855824\",\n      \"name\": \"redo-filled\",\n      \"font_class\": \"redo-filled\",\n      \"unicode\": \"e655\",\n      \"unicode_decimal\": 58965\n    },\n    {\n      \"icon_id\": \"24841702\",\n      \"name\": \"list\",\n      \"font_class\": \"list\",\n      \"unicode\": \"e644\",\n      \"unicode_decimal\": 58948\n    },\n    {\n      \"icon_id\": \"24841489\",\n      \"name\": \"mail-open-filled\",\n      \"font_class\": \"mail-open-filled\",\n      \"unicode\": \"e63a\",\n      \"unicode_decimal\": 58938\n    },\n    {\n      \"icon_id\": \"24841491\",\n      \"name\": \"hand-thumbsdown-filled\",\n      \"font_class\": \"hand-down-filled\",\n      \"unicode\": \"e63c\",\n      \"unicode_decimal\": 58940\n    },\n    {\n      \"icon_id\": \"24841492\",\n      \"name\": \"hand-thumbsdown\",\n      \"font_class\": \"hand-down\",\n      \"unicode\": \"e63d\",\n      \"unicode_decimal\": 58941\n    },\n    {\n      \"icon_id\": \"24841493\",\n      \"name\": \"hand-thumbsup-filled\",\n      \"font_class\": \"hand-up-filled\",\n      \"unicode\": \"e63e\",\n      \"unicode_decimal\": 58942\n    },\n    {\n      \"icon_id\": \"24841494\",\n      \"name\": \"hand-thumbsup\",\n      \"font_class\": \"hand-up\",\n      \"unicode\": \"e63f\",\n      \"unicode_decimal\": 58943\n    },\n    {\n      \"icon_id\": \"24841496\",\n      \"name\": \"heart-filled\",\n      \"font_class\": \"heart-filled\",\n      \"unicode\": \"e641\",\n      \"unicode_decimal\": 58945\n    },\n    {\n      \"icon_id\": \"24841498\",\n      \"name\": \"mail-open\",\n      \"font_class\": \"mail-open\",\n      \"unicode\": \"e643\",\n      \"unicode_decimal\": 58947\n    },\n    {\n      \"icon_id\": \"24841488\",\n      \"name\": \"heart\",\n      \"font_class\": \"heart\",\n      \"unicode\": \"e639\",\n      \"unicode_decimal\": 58937\n    },\n    {\n      \"icon_id\": \"24839963\",\n      \"name\": \"loop\",\n      \"font_class\": \"loop\",\n      \"unicode\": \"e633\",\n      \"unicode_decimal\": 58931\n    },\n    {\n      \"icon_id\": \"24839866\",\n      \"name\": \"pulldown\",\n      \"font_class\": \"pulldown\",\n      \"unicode\": \"e632\",\n      \"unicode_decimal\": 58930\n    },\n    {\n      \"icon_id\": \"24813798\",\n      \"name\": \"scan\",\n      \"font_class\": \"scan\",\n      \"unicode\": \"e62a\",\n      \"unicode_decimal\": 58922\n    },\n    {\n      \"icon_id\": \"24813786\",\n      \"name\": \"bars\",\n      \"font_class\": \"bars\",\n      \"unicode\": \"e627\",\n      \"unicode_decimal\": 58919\n    },\n    {\n      \"icon_id\": \"24813788\",\n      \"name\": \"cart-filled\",\n      \"font_class\": \"cart-filled\",\n      \"unicode\": \"e629\",\n      \"unicode_decimal\": 58921\n    },\n    {\n      \"icon_id\": \"24813790\",\n      \"name\": \"checkbox\",\n      \"font_class\": \"checkbox\",\n      \"unicode\": \"e62b\",\n      \"unicode_decimal\": 58923\n    },\n    {\n      \"icon_id\": \"24813791\",\n      \"name\": \"checkbox-filled\",\n      \"font_class\": \"checkbox-filled\",\n      \"unicode\": \"e62c\",\n      \"unicode_decimal\": 58924\n    },\n    {\n      \"icon_id\": \"24813794\",\n      \"name\": \"shop\",\n      \"font_class\": \"shop\",\n      \"unicode\": \"e62f\",\n      \"unicode_decimal\": 58927\n    },\n    {\n      \"icon_id\": \"24813795\",\n      \"name\": \"headphones\",\n      \"font_class\": \"headphones\",\n      \"unicode\": \"e630\",\n      \"unicode_decimal\": 58928\n    },\n    {\n      \"icon_id\": \"24813796\",\n      \"name\": \"cart\",\n      \"font_class\": \"cart\",\n      \"unicode\": \"e631\",\n      \"unicode_decimal\": 58929\n    }\n  ]\n}\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uni-icons/components/uni-icons/uni-icons.vue",
    "content": "<template>\n\t<!-- #ifdef APP-NVUE -->\n\t<text :style=\"{ color: color, 'font-size': iconSize }\" class=\"uni-icons\" @click=\"_onClick\">{{unicode}}</text>\n\t<!-- #endif -->\n\t<!-- #ifndef APP-NVUE -->\n\t<text :style=\"{ color: color, 'font-size': iconSize }\" class=\"uni-icons\" :class=\"['uniui-'+type,customPrefix,customPrefix?type:'']\" @click=\"_onClick\"></text>\n\t<!-- #endif -->\r\n</template>\r\n\r\n<script>\r\n\timport icons from './icons.js';\n\tconst getVal = (val) => {\n\t\tconst reg = /^[0-9]*$/g\n\t\treturn (typeof val === 'number' ||　reg.test(val) )? val + 'px' : val;\n\t} \r\n\t// #ifdef APP-NVUE\r\n\tvar domModule = weex.requireModule('dom');\r\n\timport iconUrl from './uniicons.ttf'\r\n\tdomModule.addRule('fontFace', {\r\n\t\t'fontFamily': \"uniicons\",\r\n\t\t'src': \"url('\"+iconUrl+\"')\"\r\n\t});\r\n\t// #endif\r\n\r\n\t/**\r\n\t * Icons 图标\r\n\t * @description 用于展示 icons 图标\r\n\t * @tutorial https://ext.dcloud.net.cn/plugin?id=28\r\n\t * @property {Number} size 图标大小\r\n\t * @property {String} type 图标图案，参考示例\r\n\t * @property {String} color 图标颜色\n\t * @property {String} customPrefix 自定义图标\n\t * @event {Function} click 点击 Icon 触发事件\r\n\t */\r\n\texport default {\r\n\t\tname: 'UniIcons',\r\n\t\temits:['click'],\r\n\t\tprops: {\r\n\t\t\ttype: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: ''\r\n\t\t\t},\r\n\t\t\tcolor: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: '#333333'\r\n\t\t\t},\r\n\t\t\tsize: {\r\n\t\t\t\ttype: [Number, String],\r\n\t\t\t\tdefault: 16\r\n\t\t\t},\n\t\t\tcustomPrefix:{\n\t\t\t\ttype: String,\n\t\t\t\tdefault: ''\n\t\t\t}\r\n\t\t},\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\ticons: icons.glyphs\r\n\t\t\t}\r\n\t\t},\n\t\tcomputed:{\n\t\t\tunicode(){\n\t\t\t\tlet code = this.icons.find(v=>v.font_class === this.type)\n\t\t\t\tif(code){\n\t\t\t\t\treturn unescape(`%u${code.unicode}`)\n\t\t\t\t}\n\t\t\t\treturn ''\n\t\t\t},\n\t\t\ticonSize(){\n\t\t\t\treturn getVal(this.size)\n\t\t\t}\n\t\t},\r\n\t\tmethods: {\r\n\t\t\t_onClick() {\r\n\t\t\t\tthis.$emit('click')\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\">\n\t/* #ifndef APP-NVUE */\n\t@import './uniicons.css';\r\n\t@font-face {\r\n\t\tfont-family: uniicons;\r\n\t\tsrc: url('./uniicons.ttf') format('truetype');\r\n\t}\r\n\r\n\t/* #endif */\n\t.uni-icons {\r\n\t\tfont-family: uniicons;\r\n\t\ttext-decoration: none;\r\n\t\ttext-align: center;\r\n\t}\r\n\r\n</style>\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uni-icons/components/uni-icons/uniicons.css",
    "content": ".uniui-color:before {\n  content: \"\\e6cf\";\n}\n\n.uniui-wallet:before {\n  content: \"\\e6b1\";\n}\n\n.uniui-settings-filled:before {\n  content: \"\\e6ce\";\n}\n\n.uniui-auth-filled:before {\n  content: \"\\e6cc\";\n}\n\n.uniui-shop-filled:before {\n  content: \"\\e6cd\";\n}\n\n.uniui-staff-filled:before {\n  content: \"\\e6cb\";\n}\n\n.uniui-vip-filled:before {\n  content: \"\\e6c6\";\n}\n\n.uniui-plus-filled:before {\n  content: \"\\e6c7\";\n}\n\n.uniui-folder-add-filled:before {\n  content: \"\\e6c8\";\n}\n\n.uniui-color-filled:before {\n  content: \"\\e6c9\";\n}\n\n.uniui-tune-filled:before {\n  content: \"\\e6ca\";\n}\n\n.uniui-calendar-filled:before {\n  content: \"\\e6c0\";\n}\n\n.uniui-notification-filled:before {\n  content: \"\\e6c1\";\n}\n\n.uniui-wallet-filled:before {\n  content: \"\\e6c2\";\n}\n\n.uniui-medal-filled:before {\n  content: \"\\e6c3\";\n}\n\n.uniui-gift-filled:before {\n  content: \"\\e6c4\";\n}\n\n.uniui-fire-filled:before {\n  content: \"\\e6c5\";\n}\n\n.uniui-refreshempty:before {\n  content: \"\\e6bf\";\n}\n\n.uniui-location-filled:before {\n  content: \"\\e6af\";\n}\n\n.uniui-person-filled:before {\n  content: \"\\e69d\";\n}\n\n.uniui-personadd-filled:before {\n  content: \"\\e698\";\n}\n\n.uniui-back:before {\n  content: \"\\e6b9\";\n}\n\n.uniui-forward:before {\n  content: \"\\e6ba\";\n}\n\n.uniui-arrow-right:before {\n  content: \"\\e6bb\";\n}\n\n.uniui-arrowthinright:before {\n  content: \"\\e6bb\";\n}\n\n.uniui-arrow-left:before {\n  content: \"\\e6bc\";\n}\n\n.uniui-arrowthinleft:before {\n  content: \"\\e6bc\";\n}\n\n.uniui-arrow-up:before {\n  content: \"\\e6bd\";\n}\n\n.uniui-arrowthinup:before {\n  content: \"\\e6bd\";\n}\n\n.uniui-arrow-down:before {\n  content: \"\\e6be\";\n}\n\n.uniui-arrowthindown:before {\n  content: \"\\e6be\";\n}\n\n.uniui-bottom:before {\n  content: \"\\e6b8\";\n}\n\n.uniui-arrowdown:before {\n  content: \"\\e6b8\";\n}\n\n.uniui-right:before {\n  content: \"\\e6b5\";\n}\n\n.uniui-arrowright:before {\n  content: \"\\e6b5\";\n}\n\n.uniui-top:before {\n  content: \"\\e6b6\";\n}\n\n.uniui-arrowup:before {\n  content: \"\\e6b6\";\n}\n\n.uniui-left:before {\n  content: \"\\e6b7\";\n}\n\n.uniui-arrowleft:before {\n  content: \"\\e6b7\";\n}\n\n.uniui-eye:before {\n  content: \"\\e651\";\n}\n\n.uniui-eye-filled:before {\n  content: \"\\e66a\";\n}\n\n.uniui-eye-slash:before {\n  content: \"\\e6b3\";\n}\n\n.uniui-eye-slash-filled:before {\n  content: \"\\e6b4\";\n}\n\n.uniui-info-filled:before {\n  content: \"\\e649\";\n}\n\n.uniui-reload:before {\n  content: \"\\e6b2\";\n}\n\n.uniui-micoff-filled:before {\n  content: \"\\e6b0\";\n}\n\n.uniui-map-pin-ellipse:before {\n  content: \"\\e6ac\";\n}\n\n.uniui-map-pin:before {\n  content: \"\\e6ad\";\n}\n\n.uniui-location:before {\n  content: \"\\e6ae\";\n}\n\n.uniui-starhalf:before {\n  content: \"\\e683\";\n}\n\n.uniui-star:before {\n  content: \"\\e688\";\n}\n\n.uniui-star-filled:before {\n  content: \"\\e68f\";\n}\n\n.uniui-calendar:before {\n  content: \"\\e6a0\";\n}\n\n.uniui-fire:before {\n  content: \"\\e6a1\";\n}\n\n.uniui-medal:before {\n  content: \"\\e6a2\";\n}\n\n.uniui-font:before {\n  content: \"\\e6a3\";\n}\n\n.uniui-gift:before {\n  content: \"\\e6a4\";\n}\n\n.uniui-link:before {\n  content: \"\\e6a5\";\n}\n\n.uniui-notification:before {\n  content: \"\\e6a6\";\n}\n\n.uniui-staff:before {\n  content: \"\\e6a7\";\n}\n\n.uniui-vip:before {\n  content: \"\\e6a8\";\n}\n\n.uniui-folder-add:before {\n  content: \"\\e6a9\";\n}\n\n.uniui-tune:before {\n  content: \"\\e6aa\";\n}\n\n.uniui-auth:before {\n  content: \"\\e6ab\";\n}\n\n.uniui-person:before {\n  content: \"\\e699\";\n}\n\n.uniui-email-filled:before {\n  content: \"\\e69a\";\n}\n\n.uniui-phone-filled:before {\n  content: \"\\e69b\";\n}\n\n.uniui-phone:before {\n  content: \"\\e69c\";\n}\n\n.uniui-email:before {\n  content: \"\\e69e\";\n}\n\n.uniui-personadd:before {\n  content: \"\\e69f\";\n}\n\n.uniui-chatboxes-filled:before {\n  content: \"\\e692\";\n}\n\n.uniui-contact:before {\n  content: \"\\e693\";\n}\n\n.uniui-chatbubble-filled:before {\n  content: \"\\e694\";\n}\n\n.uniui-contact-filled:before {\n  content: \"\\e695\";\n}\n\n.uniui-chatboxes:before {\n  content: \"\\e696\";\n}\n\n.uniui-chatbubble:before {\n  content: \"\\e697\";\n}\n\n.uniui-upload-filled:before {\n  content: \"\\e68e\";\n}\n\n.uniui-upload:before {\n  content: \"\\e690\";\n}\n\n.uniui-weixin:before {\n  content: \"\\e691\";\n}\n\n.uniui-compose:before {\n  content: \"\\e67f\";\n}\n\n.uniui-qq:before {\n  content: \"\\e680\";\n}\n\n.uniui-download-filled:before {\n  content: \"\\e681\";\n}\n\n.uniui-pyq:before {\n  content: \"\\e682\";\n}\n\n.uniui-sound:before {\n  content: \"\\e684\";\n}\n\n.uniui-trash-filled:before {\n  content: \"\\e685\";\n}\n\n.uniui-sound-filled:before {\n  content: \"\\e686\";\n}\n\n.uniui-trash:before {\n  content: \"\\e687\";\n}\n\n.uniui-videocam-filled:before {\n  content: \"\\e689\";\n}\n\n.uniui-spinner-cycle:before {\n  content: \"\\e68a\";\n}\n\n.uniui-weibo:before {\n  content: \"\\e68b\";\n}\n\n.uniui-videocam:before {\n  content: \"\\e68c\";\n}\n\n.uniui-download:before {\n  content: \"\\e68d\";\n}\n\n.uniui-help:before {\n  content: \"\\e679\";\n}\n\n.uniui-navigate-filled:before {\n  content: \"\\e67a\";\n}\n\n.uniui-plusempty:before {\n  content: \"\\e67b\";\n}\n\n.uniui-smallcircle:before {\n  content: \"\\e67c\";\n}\n\n.uniui-minus-filled:before {\n  content: \"\\e67d\";\n}\n\n.uniui-micoff:before {\n  content: \"\\e67e\";\n}\n\n.uniui-closeempty:before {\n  content: \"\\e66c\";\n}\n\n.uniui-clear:before {\n  content: \"\\e66d\";\n}\n\n.uniui-navigate:before {\n  content: \"\\e66e\";\n}\n\n.uniui-minus:before {\n  content: \"\\e66f\";\n}\n\n.uniui-image:before {\n  content: \"\\e670\";\n}\n\n.uniui-mic:before {\n  content: \"\\e671\";\n}\n\n.uniui-paperplane:before {\n  content: \"\\e672\";\n}\n\n.uniui-close:before {\n  content: \"\\e673\";\n}\n\n.uniui-help-filled:before {\n  content: \"\\e674\";\n}\n\n.uniui-paperplane-filled:before {\n  content: \"\\e675\";\n}\n\n.uniui-plus:before {\n  content: \"\\e676\";\n}\n\n.uniui-mic-filled:before {\n  content: \"\\e677\";\n}\n\n.uniui-image-filled:before {\n  content: \"\\e678\";\n}\n\n.uniui-locked-filled:before {\n  content: \"\\e668\";\n}\n\n.uniui-info:before {\n  content: \"\\e669\";\n}\n\n.uniui-locked:before {\n  content: \"\\e66b\";\n}\n\n.uniui-camera-filled:before {\n  content: \"\\e658\";\n}\n\n.uniui-chat-filled:before {\n  content: \"\\e659\";\n}\n\n.uniui-camera:before {\n  content: \"\\e65a\";\n}\n\n.uniui-circle:before {\n  content: \"\\e65b\";\n}\n\n.uniui-checkmarkempty:before {\n  content: \"\\e65c\";\n}\n\n.uniui-chat:before {\n  content: \"\\e65d\";\n}\n\n.uniui-circle-filled:before {\n  content: \"\\e65e\";\n}\n\n.uniui-flag:before {\n  content: \"\\e65f\";\n}\n\n.uniui-flag-filled:before {\n  content: \"\\e660\";\n}\n\n.uniui-gear-filled:before {\n  content: \"\\e661\";\n}\n\n.uniui-home:before {\n  content: \"\\e662\";\n}\n\n.uniui-home-filled:before {\n  content: \"\\e663\";\n}\n\n.uniui-gear:before {\n  content: \"\\e664\";\n}\n\n.uniui-smallcircle-filled:before {\n  content: \"\\e665\";\n}\n\n.uniui-map-filled:before {\n  content: \"\\e666\";\n}\n\n.uniui-map:before {\n  content: \"\\e667\";\n}\n\n.uniui-refresh-filled:before {\n  content: \"\\e656\";\n}\n\n.uniui-refresh:before {\n  content: \"\\e657\";\n}\n\n.uniui-cloud-upload:before {\n  content: \"\\e645\";\n}\n\n.uniui-cloud-download-filled:before {\n  content: \"\\e646\";\n}\n\n.uniui-cloud-download:before {\n  content: \"\\e647\";\n}\n\n.uniui-cloud-upload-filled:before {\n  content: \"\\e648\";\n}\n\n.uniui-redo:before {\n  content: \"\\e64a\";\n}\n\n.uniui-images-filled:before {\n  content: \"\\e64b\";\n}\n\n.uniui-undo-filled:before {\n  content: \"\\e64c\";\n}\n\n.uniui-more:before {\n  content: \"\\e64d\";\n}\n\n.uniui-more-filled:before {\n  content: \"\\e64e\";\n}\n\n.uniui-undo:before {\n  content: \"\\e64f\";\n}\n\n.uniui-images:before {\n  content: \"\\e650\";\n}\n\n.uniui-paperclip:before {\n  content: \"\\e652\";\n}\n\n.uniui-settings:before {\n  content: \"\\e653\";\n}\n\n.uniui-search:before {\n  content: \"\\e654\";\n}\n\n.uniui-redo-filled:before {\n  content: \"\\e655\";\n}\n\n.uniui-list:before {\n  content: \"\\e644\";\n}\n\n.uniui-mail-open-filled:before {\n  content: \"\\e63a\";\n}\n\n.uniui-hand-down-filled:before {\n  content: \"\\e63c\";\n}\n\n.uniui-hand-down:before {\n  content: \"\\e63d\";\n}\n\n.uniui-hand-up-filled:before {\n  content: \"\\e63e\";\n}\n\n.uniui-hand-up:before {\n  content: \"\\e63f\";\n}\n\n.uniui-heart-filled:before {\n  content: \"\\e641\";\n}\n\n.uniui-mail-open:before {\n  content: \"\\e643\";\n}\n\n.uniui-heart:before {\n  content: \"\\e639\";\n}\n\n.uniui-loop:before {\n  content: \"\\e633\";\n}\n\n.uniui-pulldown:before {\n  content: \"\\e632\";\n}\n\n.uniui-scan:before {\n  content: \"\\e62a\";\n}\n\n.uniui-bars:before {\n  content: \"\\e627\";\n}\n\n.uniui-cart-filled:before {\n  content: \"\\e629\";\n}\n\n.uniui-checkbox:before {\n  content: \"\\e62b\";\n}\n\n.uniui-checkbox-filled:before {\n  content: \"\\e62c\";\n}\n\n.uniui-shop:before {\n  content: \"\\e62f\";\n}\n\n.uniui-headphones:before {\n  content: \"\\e630\";\n}\n\n.uniui-cart:before {\n  content: \"\\e631\";\n}\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uni-icons/package.json",
    "content": "{\n  \"id\": \"uni-icons\",\n  \"displayName\": \"uni-icons 图标\",\n  \"version\": \"1.3.5\",\n  \"description\": \"图标组件，用于展示移动端常见的图标，可自定义颜色、大小。\",\n  \"keywords\": [\n    \"uni-ui\",\n    \"uniui\",\n    \"icon\",\n    \"图标\"\n],\n  \"repository\": \"https://github.com/dcloudio/uni-ui\",\n  \"engines\": {\n    \"HBuilderX\": \"^3.2.14\"\n  },\n  \"directories\": {\n    \"example\": \"../../temps/example_temps\"\n  },\n  \"dcloudext\": {\n    \"category\": [\n      \"前端组件\",\n      \"通用组件\"\n    ],\n    \"sale\": {\n      \"regular\": {\n        \"price\": \"0.00\"\n      },\n      \"sourcecode\": {\n        \"price\": \"0.00\"\n      }\n    },\n    \"contact\": {\n      \"qq\": \"\"\n    },\n    \"declaration\": {\n      \"ads\": \"无\",\n      \"data\": \"无\",\n      \"permissions\": \"无\"\n    },\n    \"npmurl\": \"https://www.npmjs.com/package/@dcloudio/uni-ui\"\n  },\n  \"uni_modules\": {\n    \"dependencies\": [\"uni-scss\"],\n    \"encrypt\": [],\n    \"platforms\": {\n      \"cloud\": {\n        \"tcb\": \"y\",\n        \"aliyun\": \"y\"\n      },\n      \"client\": {\n        \"App\": {\n          \"app-vue\": \"y\",\n          \"app-nvue\": \"y\"\n        },\n        \"H5-mobile\": {\n          \"Safari\": \"y\",\n          \"Android Browser\": \"y\",\n          \"微信浏览器(Android)\": \"y\",\n          \"QQ浏览器(Android)\": \"y\"\n        },\n        \"H5-pc\": {\n          \"Chrome\": \"y\",\n          \"IE\": \"y\",\n          \"Edge\": \"y\",\n          \"Firefox\": \"y\",\n          \"Safari\": \"y\"\n        },\n        \"小程序\": {\n          \"微信\": \"y\",\n          \"阿里\": \"y\",\n          \"百度\": \"y\",\n          \"字节跳动\": \"y\",\n          \"QQ\": \"y\"\n        },\n        \"快应用\": {\n          \"华为\": \"u\",\n          \"联盟\": \"u\"\n        },\n        \"Vue\": {\n            \"vue2\": \"y\",\n            \"vue3\": \"y\"\n        }\n      }\n    }\n  }\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uni-icons/readme.md",
    "content": "## Icons 图标\n> **组件名：uni-icons**\n> 代码块： `uIcons`\n\n用于展示 icons 图标 。\n\n### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-icons)\n#### 如使用过程中有任何问题，或者您对uni-ui有一些好的建议，欢迎加入 uni-ui 交流群：871950839 \n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uni-scss/changelog.md",
    "content": "## 1.0.3（2022-01-21）\n- 优化 组件示例\n## 1.0.2（2021-11-22）\n- 修复 / 符号在 vue 不同版本兼容问题引起的报错问题\n## 1.0.1（2021-11-22）\n- 修复 vue3中scss语法兼容问题\n## 1.0.0（2021-11-18）\n- init\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uni-scss/index.scss",
    "content": "@import './styles/index.scss';\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uni-scss/package.json",
    "content": "{\n  \"id\": \"uni-scss\",\n  \"displayName\": \"uni-scss 辅助样式\",\n  \"version\": \"1.0.3\",\n  \"description\": \"uni-sass是uni-ui提供的一套全局样式 ，通过一些简单的类名和sass变量，实现简单的页面布局操作，比如颜色、边距、圆角等。\",\n  \"keywords\": [\n    \"uni-scss\",\n    \"uni-ui\",\n    \"辅助样式\"\n],\n  \"repository\": \"https://github.com/dcloudio/uni-ui\",\n  \"engines\": {\n    \"HBuilderX\": \"^3.1.0\"\n  },\n  \"dcloudext\": {\n    \"category\": [\n        \"JS SDK\",\n        \"通用 SDK\"\n    ],\n    \"sale\": {\n      \"regular\": {\n        \"price\": \"0.00\"\n      },\n      \"sourcecode\": {\n        \"price\": \"0.00\"\n      }\n    },\n    \"contact\": {\n      \"qq\": \"\"\n    },\n    \"declaration\": {\n      \"ads\": \"无\",\n      \"data\": \"无\",\n      \"permissions\": \"无\"\n    },\n    \"npmurl\": \"https://www.npmjs.com/package/@dcloudio/uni-ui\"\n  },\n  \"uni_modules\": {\n    \"dependencies\": [],\n    \"encrypt\": [],\n    \"platforms\": {\n      \"cloud\": {\n        \"tcb\": \"y\",\n        \"aliyun\": \"y\"\n      },\n      \"client\": {\n        \"App\": {\n          \"app-vue\": \"y\",\n          \"app-nvue\": \"u\"\n        },\n        \"H5-mobile\": {\n          \"Safari\": \"y\",\n          \"Android Browser\": \"y\",\n          \"微信浏览器(Android)\": \"y\",\n          \"QQ浏览器(Android)\": \"y\"\n        },\n        \"H5-pc\": {\n          \"Chrome\": \"y\",\n          \"IE\": \"y\",\n          \"Edge\": \"y\",\n          \"Firefox\": \"y\",\n          \"Safari\": \"y\"\n        },\n        \"小程序\": {\n          \"微信\": \"y\",\n          \"阿里\": \"y\",\n          \"百度\": \"y\",\n          \"字节跳动\": \"y\",\n          \"QQ\": \"y\"\n        },\n        \"快应用\": {\n          \"华为\": \"n\",\n          \"联盟\": \"n\"\n        },\n        \"Vue\": {\n            \"vue2\": \"y\",\n            \"vue3\": \"y\"\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uni-scss/readme.md",
    "content": "`uni-sass` 是 `uni-ui`提供的一套全局样式 ，通过一些简单的类名和`sass`变量，实现简单的页面布局操作，比如颜色、边距、圆角等。\n\n### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-sass)\n#### 如使用过程中有任何问题，或者您对uni-ui有一些好的建议，欢迎加入 uni-ui 交流群：871950839 "
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uni-scss/styles/index.scss",
    "content": "@import './setting/_variables.scss';\n@import './setting/_border.scss';\n@import './setting/_color.scss';\n@import './setting/_space.scss';\n@import './setting/_radius.scss';\n@import './setting/_text.scss';\n@import './setting/_styles.scss';\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uni-scss/styles/setting/_border.scss",
    "content": ".uni-border {\n\tborder: 1px $uni-border-1 solid;\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uni-scss/styles/setting/_color.scss",
    "content": "\n// TODO 暂时不需要 class ，需要用户使用变量实现 ，如果使用类名其实并不推荐\n// @mixin get-styles($k,$c) {\n// \t@if $k == size or $k == weight{\n// \t\tfont-#{$k}:#{$c}\n// \t}@else{\n// \t\t#{$k}:#{$c}\n// \t}\n// }\n$uni-ui-color:(\n\t// 主色\n\tprimary: $uni-primary,\n\tprimary-disable: $uni-primary-disable,\n\tprimary-light: $uni-primary-light,\n\t// 辅助色\n\tsuccess: $uni-success,\n\tsuccess-disable: $uni-success-disable,\n\tsuccess-light: $uni-success-light,\n\twarning: $uni-warning,\n\twarning-disable: $uni-warning-disable,\n\twarning-light: $uni-warning-light,\n\terror: $uni-error,\n\terror-disable: $uni-error-disable,\n\terror-light: $uni-error-light,\n\tinfo: $uni-info,\n\tinfo-disable: $uni-info-disable,\n\tinfo-light: $uni-info-light,\n\t// 中性色\n\tmain-color: $uni-main-color,\n\tbase-color: $uni-base-color,\n\tsecondary-color: $uni-secondary-color,\n\textra-color: $uni-extra-color,\n\t// 背景色\n\tbg-color: $uni-bg-color,\n\t// 边框颜色\n\tborder-1: $uni-border-1,\n\tborder-2: $uni-border-2,\n\tborder-3: $uni-border-3,\n\tborder-4: $uni-border-4,\n\t// 黑色\n\tblack:$uni-black,\n\t// 白色\n\twhite:$uni-white,\n\t// 透明\n\ttransparent:$uni-transparent\n) !default;\n@each $key, $child in $uni-ui-color {\n\t.uni-#{\"\" + $key} {\n\t\tcolor: $child;\n\t}\n\t.uni-#{\"\" + $key}-bg {\n\t\tbackground-color: $child;\n\t}\n}\n.uni-shadow-sm {\n\tbox-shadow: $uni-shadow-sm;\n}\n.uni-shadow-base {\n\tbox-shadow: $uni-shadow-base;\n}\n.uni-shadow-lg {\n\tbox-shadow: $uni-shadow-lg;\n}\n.uni-mask {\n\tbackground-color:$uni-mask;\n}\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uni-scss/styles/setting/_radius.scss",
    "content": "@mixin radius($r,$d:null ,$important: false){\n  $radius-value:map-get($uni-radius, $r) if($important, !important, null);\n  // Key exists within the $uni-radius variable\n  @if (map-has-key($uni-radius, $r) and  $d){\n\t\t@if $d == t {\n\t\t\t\tborder-top-left-radius:$radius-value;\n\t\t\t\tborder-top-right-radius:$radius-value;\n\t\t}@else if $d == r {\n\t\t\t\tborder-top-right-radius:$radius-value;\n\t\t\t\tborder-bottom-right-radius:$radius-value;\n\t\t}@else if $d == b {\n\t\t\t\tborder-bottom-left-radius:$radius-value;\n\t\t\t\tborder-bottom-right-radius:$radius-value;\n\t\t}@else if $d == l {\n\t\t\t\tborder-top-left-radius:$radius-value;\n\t\t\t\tborder-bottom-left-radius:$radius-value;\n\t\t}@else if $d == tl {\n\t\t\t\tborder-top-left-radius:$radius-value;\n\t\t}@else if $d == tr {\n\t\t\t\tborder-top-right-radius:$radius-value;\n\t\t}@else if $d == br {\n\t\t\t\tborder-bottom-right-radius:$radius-value;\n\t\t}@else if $d == bl {\n\t\t\t\tborder-bottom-left-radius:$radius-value;\n\t\t}\n  }@else{\n\t\tborder-radius:$radius-value;\n  }\n}\n\n@each $key, $child in $uni-radius {\n\t@if($key){\n\t\t.uni-radius-#{\"\" + $key} {\n\t\t\t\t@include radius($key)\n\t\t}\n\t}@else{\n\t\t.uni-radius {\n\t\t\t\t@include radius($key)\n\t\t}\n\t}\n}\n\n@each $direction in t, r, b, l,tl, tr, br, bl {\n\t@each $key, $child in $uni-radius {\n\t\t@if($key){\n\t\t\t.uni-radius-#{\"\" + $direction}-#{\"\" + $key} {\n\t\t\t\t@include radius($key,$direction,false)\n\t\t\t}\n\t\t}@else{\n\t\t\t.uni-radius-#{$direction} {\n\t\t\t\t@include radius($key,$direction,false)\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uni-scss/styles/setting/_space.scss",
    "content": "\n@mixin fn($space,$direction,$size,$n) {\n\t@if $n {\n\t\t#{$space}-#{$direction}: #{$size*$uni-space-root}px\n\t} @else {\n\t\t #{$space}-#{$direction}: #{-$size*$uni-space-root}px\n\t}\n}\n@mixin get-styles($direction,$i,$space,$n){\n\t@if $direction == t {\n\t\t@include fn($space, top,$i,$n);\n\t} \n\t@if $direction == r {\n\t\t@include fn($space, right,$i,$n);\n\t} \n\t@if $direction == b {\n\t\t@include fn($space, bottom,$i,$n);\n\t} \n\t@if $direction == l {\n\t @include fn($space, left,$i,$n);\n\t} \n\t@if $direction == x {\n\t\t@include fn($space, left,$i,$n);\n\t\t@include fn($space, right,$i,$n);\n\t} \n\t@if $direction == y {\n\t\t@include fn($space, top,$i,$n);\n\t\t@include fn($space, bottom,$i,$n);\n\t} \n\t@if $direction == a {\n\t\t@if $n {\n\t\t\t#{$space}:#{$i*$uni-space-root}px;\n\t\t} @else {\n\t\t\t#{$space}:#{-$i*$uni-space-root}px;\n\t\t}\n\t} \n}\n\n@each $orientation in m,p {\n\t$space: margin;\n\t@if $orientation == m {\n\t\t$space: margin;\n\t} @else {\n\t\t$space: padding;\n\t}\n\t@for $i from 0 through 16 {\n\t\t@each $direction in t, r, b, l, x, y, a {\n\t\t\t.uni-#{$orientation}#{$direction}-#{$i} { \n\t\t\t\t@include  get-styles($direction,$i,$space,true);\n\t\t\t} \n\t\t\t.uni-#{$orientation}#{$direction}-n#{$i} { \n\t\t\t\t@include  get-styles($direction,$i,$space,false);\n\t\t\t}\n\t\t}\n\t}\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uni-scss/styles/setting/_styles.scss",
    "content": "/* #ifndef APP-NVUE */\n\n$-color-white:#fff;\n$-color-black:#000;\n@mixin base-style($color) {\n\tcolor: #fff;\n\tbackground-color: $color;\n\tborder-color: mix($-color-black, $color, 8%);\n\t&:not([hover-class]):active {\n\t\tbackground: mix($-color-black, $color, 10%);\n\t\tborder-color: mix($-color-black, $color, 20%);\n\t\tcolor: $-color-white;\n\t\toutline: none;\n\t}\n}\n@mixin is-color($color) {\n\t@include base-style($color);\n\t&[loading] {\n\t\t@include base-style($color);\n\t\t&::before {\n\t\t\tmargin-right:5px;\n\t\t}\n\t}\n\t&[disabled] {\n\t  &,\n\t\t&[loading],\n\t  &:not([hover-class]):active {\n\t    color: $-color-white;\n\t\t\tborder-color: mix(darken($color,10%), $-color-white);\n\t    background-color: mix($color, $-color-white);\n\t  }\n\t}\n\n}\n@mixin base-plain-style($color) {\n\tcolor:$color;\n\tbackground-color: mix($-color-white, $color, 90%);\n\tborder-color: mix($-color-white, $color, 70%);\n\t&:not([hover-class]):active {\n\t  background: mix($-color-white, $color, 80%);\n\t  color: $color;\n\t  outline: none;\n\t\tborder-color: mix($-color-white, $color, 50%);\n\t}\n}\n@mixin is-plain($color){\n\t&[plain] {\n\t\t@include base-plain-style($color);\n\t\t&[loading] {\n\t\t\t@include base-plain-style($color);\n\t\t\t&::before {\n\t\t\t\tmargin-right:5px;\n\t\t\t}\n\t\t}\n\t\t&[disabled] {\n\t\t  &,\n\t\t  &:active {\n\t\t    color: mix($-color-white, $color, 40%);\n\t\t    background-color: mix($-color-white, $color, 90%);\n\t\t\t\tborder-color: mix($-color-white, $color, 80%);\n\t\t  }\n\t\t}\n\t}\n}\n\n\n.uni-btn {\n\tmargin: 5px;\n\tcolor: #393939;\n\tborder:1px solid #ccc;\n\tfont-size: 16px;\n\tfont-weight: 200;\n\tbackground-color: #F9F9F9;\n\t// TODO 暂时处理边框隐藏一边的问题\n\toverflow: visible;\n\t&::after{\n\t\tborder: none;\n\t}\n\n\t&:not([type]),&[type=default] {\n\t\tcolor: #999;\n\t\t&[loading] {\n\t\t\tbackground: none;\n\t\t\t&::before {\n\t\t\t\tmargin-right:5px;\n\t\t\t}\n\t\t}\n\n\n\n\t\t&[disabled]{\n\t\t\tcolor: mix($-color-white, #999, 60%);\n\t\t  &,\n\t\t\t&[loading],\n\t\t  &:active {\n\t\t\t\tcolor: mix($-color-white, #999, 60%);\n\t\t    background-color: mix($-color-white,$-color-black , 98%);\n\t\t\t\tborder-color: mix($-color-white,  #999, 85%);\n\t\t  }\n\t\t}\n\n\t\t&[plain] {\n\t\t\tcolor: #999;\n\t\t\tbackground: none;\n\t\t\tborder-color: $uni-border-1;\n\t\t\t&:not([hover-class]):active {\n\t\t\t\tbackground: none;\n\t\t\t  color: mix($-color-white, $-color-black, 80%);\n\t\t\t\tborder-color: mix($-color-white, $-color-black, 90%);\n\t\t\t  outline: none;\n\t\t\t}\n\t\t\t&[disabled]{\n\t\t\t  &,\n\t\t\t\t&[loading],\n\t\t\t  &:active {\n\t\t\t    background: none;\n\t\t\t\t\tcolor: mix($-color-white, #999, 60%);\n\t\t\t\t\tborder-color: mix($-color-white,  #999, 85%);\n\t\t\t  }\n\t\t\t}\n\t\t}\n\t}\n\n\t&:not([hover-class]):active {\n\t  color: mix($-color-white, $-color-black, 50%);\n\t}\n\n\t&[size=mini] {\n\t\tfont-size: 16px;\n\t\tfont-weight: 200;\n\t\tborder-radius: 8px;\n\t}\n\n\n\n\t&.uni-btn-small {\n\t\tfont-size: 14px;\n\t}\n\t&.uni-btn-mini {\n\t\tfont-size: 12px;\n\t}\n\n\t&.uni-btn-radius {\n\t\tborder-radius: 999px;\n\t}\n\t&[type=primary] {\n\t\t@include is-color($uni-primary);\n\t\t@include is-plain($uni-primary)\n\t}\n\t&[type=success] {\n\t\t@include is-color($uni-success);\n\t\t@include is-plain($uni-success)\n\t}\n\t&[type=error] {\n\t\t@include is-color($uni-error);\n\t\t@include is-plain($uni-error)\n\t}\n\t&[type=warning] {\n\t\t@include is-color($uni-warning);\n\t\t@include is-plain($uni-warning)\n\t}\n\t&[type=info] {\n\t\t@include is-color($uni-info);\n\t\t@include is-plain($uni-info)\n\t}\n}\n/* #endif */\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uni-scss/styles/setting/_text.scss",
    "content": "@mixin get-styles($k,$c) {\n\t@if $k == size or $k == weight{\n\t\tfont-#{$k}:#{$c}\n\t}@else{\n\t\t#{$k}:#{$c}\n\t}\n}\n\n@each $key, $child in $uni-headings {\n\t/* #ifndef APP-NVUE */\n\t.uni-#{$key} {\n\t\t@each $k, $c in $child {\n\t\t\t@include get-styles($k,$c)\n\t\t}\n\t}\n\t/* #endif */\n\t/* #ifdef APP-NVUE */\n\t.container .uni-#{$key} {\n\t\t@each $k, $c in $child {\n\t\t\t@include get-styles($k,$c)\n\t\t}\n\t}\n\t/* #endif */\n}\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uni-scss/styles/setting/_variables.scss",
    "content": "// @use \"sass:math\";\n@import  '../tools/functions.scss';\n// 间距基础倍数\n$uni-space-root: 2 !default;\n// 边框半径默认值\n$uni-radius-root:5px !default;\n$uni-radius: () !default;\n// 边框半径断点\n$uni-radius: map-deep-merge(\n  (\n    0: 0,\n\t\t// TODO 当前版本暂时不支持 sm 属性\n    // 'sm': math.div($uni-radius-root, 2),\n    null: $uni-radius-root,\n    'lg': $uni-radius-root * 2,\n    'xl': $uni-radius-root * 6,\n    'pill': 9999px,\n    'circle': 50%\n  ),\n  $uni-radius\n);\n// 字体家族\n$body-font-family: 'Roboto', sans-serif !default;\n// 文本\n$heading-font-family: $body-font-family !default;\n$uni-headings: () !default;\n$letterSpacing: -0.01562em;\n$uni-headings: map-deep-merge(\n  (\n    'h1': (\n      size: 32px,\n\t\t\tweight: 300,\n\t\t\tline-height: 50px,\n\t\t\t// letter-spacing:-0.01562em\n    ),\n    'h2': (\n      size: 28px,\n      weight: 300,\n      line-height: 40px,\n      // letter-spacing: -0.00833em\n    ),\n    'h3': (\n      size: 24px,\n      weight: 400,\n      line-height: 32px,\n      // letter-spacing: normal\n    ),\n    'h4': (\n      size: 20px,\n      weight: 400,\n      line-height: 30px,\n      // letter-spacing: 0.00735em\n    ),\n    'h5': (\n      size: 16px,\n      weight: 400,\n      line-height: 24px,\n      // letter-spacing: normal\n    ),\n    'h6': (\n      size: 14px,\n      weight: 500,\n      line-height: 18px,\n      // letter-spacing: 0.0125em\n    ),\n    'subtitle': (\n      size: 12px,\n      weight: 400,\n      line-height: 20px,\n      // letter-spacing: 0.00937em\n    ),\n    'body': (\n      font-size: 14px,\n\t\t\tfont-weight: 400,\n\t\t\tline-height: 22px,\n\t\t\t// letter-spacing: 0.03125em\n    ),\n    'caption': (\n      'size': 12px,\n      'weight': 400,\n      'line-height': 20px,\n      // 'letter-spacing': 0.03333em,\n      // 'text-transform': false\n    )\n  ),\n  $uni-headings\n);\n\n\n\n// 主色\n$uni-primary: #2979ff !default;\n$uni-primary-disable:lighten($uni-primary,20%) !default;\n$uni-primary-light: lighten($uni-primary,25%) !default;\n\n// 辅助色\n// 除了主色外的场景色，需要在不同的场景中使用（例如危险色表示危险的操作）。\n$uni-success: #18bc37 !default;\n$uni-success-disable:lighten($uni-success,20%) !default;\n$uni-success-light: lighten($uni-success,25%) !default;\n\n$uni-warning: #f3a73f !default;\n$uni-warning-disable:lighten($uni-warning,20%) !default;\n$uni-warning-light: lighten($uni-warning,25%) !default;\n\n$uni-error: #e43d33 !default;\n$uni-error-disable:lighten($uni-error,20%) !default;\n$uni-error-light: lighten($uni-error,25%) !default;\n\n$uni-info: #8f939c !default;\n$uni-info-disable:lighten($uni-info,20%) !default;\n$uni-info-light: lighten($uni-info,25%) !default;\n\n// 中性色\n// 中性色用于文本、背景和边框颜色。通过运用不同的中性色，来表现层次结构。\n$uni-main-color: #3a3a3a !default; \t\t\t// 主要文字\n$uni-base-color: #6a6a6a !default;\t\t\t// 常规文字\n$uni-secondary-color: #909399 !default;\t// 次要文字\n$uni-extra-color: #c7c7c7 !default;\t\t\t// 辅助说明\n\n// 边框颜色\n$uni-border-1: #F0F0F0 !default;\n$uni-border-2: #EDEDED !default;\n$uni-border-3: #DCDCDC !default;\n$uni-border-4: #B9B9B9 !default;\n\n// 常规色\n$uni-black: #000000 !default;\n$uni-white: #ffffff !default;\n$uni-transparent: rgba($color: #000000, $alpha: 0) !default;\n\n// 背景色\n$uni-bg-color: #f7f7f7 !default;\n\n/* 水平间距 */\n$uni-spacing-sm: 8px !default;\n$uni-spacing-base: 15px !default;\n$uni-spacing-lg: 30px !default;\n\n// 阴影\n$uni-shadow-sm:0 0 5px rgba($color: #d8d8d8, $alpha: 0.5) !default;\n$uni-shadow-base:0 1px 8px 1px rgba($color: #a5a5a5, $alpha: 0.2) !default;\n$uni-shadow-lg:0px 1px 10px 2px rgba($color: #a5a4a4, $alpha: 0.5) !default;\n\n// 蒙版\n$uni-mask: rgba($color: #000000, $alpha: 0.4) !default;\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uni-scss/styles/tools/functions.scss",
    "content": "// 合并 map\n@function map-deep-merge($parent-map, $child-map){\n\t$result: $parent-map;\n\t@each $key, $child in $child-map {\n\t\t$parent-has-key: map-has-key($result, $key);\n\t\t$parent-value: map-get($result, $key);\n\t\t$parent-type: type-of($parent-value);\n\t\t$child-type: type-of($child);\n\t\t$parent-is-map: $parent-type == map;\n\t\t$child-is-map: $child-type == map;\n\t\t\t\n\t\t@if (not $parent-has-key) or ($parent-type != $child-type) or (not ($parent-is-map and $child-is-map)){\n\t\t\t$result: map-merge($result, ( $key: $child ));\n\t\t}@else {\n\t\t\t$result: map-merge($result, ( $key: map-deep-merge($parent-value, $child) ));\n\t\t}\n\t}\n\t@return $result;\n};\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uni-scss/theme.scss",
    "content": "// 间距基础倍数\n$uni-space-root: 2;\n// 边框半径默认值\n$uni-radius-root:5px;\n// 主色\n$uni-primary: #2979ff;\n// 辅助色\n$uni-success: #4cd964;\n// 警告色\n$uni-warning: #f0ad4e;\n// 错误色\n$uni-error: #09b4f1;\n// 描述色\n$uni-info: #909399;\n// 中性色\n$uni-main-color: #303133;\n$uni-base-color: #606266;\n$uni-secondary-color: #909399;\n$uni-extra-color: #C0C4CC;\n// 背景色\n$uni-bg-color: #f5f5f5;\n// 边框颜色\n$uni-border-1: #DCDFE6;\n$uni-border-2: #E4E7ED;\n$uni-border-3: #EBEEF5;\n$uni-border-4: #F2F6FC;\n\n// 常规色\n$uni-black: #000000;\n$uni-white: #ffffff;\n$uni-transparent: rgba($color: #000000, $alpha: 0);\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uni-scss/variables.scss",
    "content": "@import './styles/setting/_variables.scss';\n// 间距基础倍数\n$uni-space-root: 2;\n// 边框半径默认值\n$uni-radius-root:5px;\n\n// 主色\n$uni-primary: #2979ff;\n$uni-primary-disable:mix(#fff,$uni-primary,50%);\n$uni-primary-light: mix(#fff,$uni-primary,80%);\n\n// 辅助色\n// 除了主色外的场景色，需要在不同的场景中使用（例如危险色表示危险的操作）。\n$uni-success: #18bc37;\n$uni-success-disable:mix(#fff,$uni-success,50%);\n$uni-success-light: mix(#fff,$uni-success,80%);\n\n$uni-warning: #f3a73f;\n$uni-warning-disable:mix(#fff,$uni-warning,50%);\n$uni-warning-light: mix(#fff,$uni-warning,80%);\n\n$uni-error: #e43d33;\n$uni-error-disable:mix(#fff,$uni-error,50%);\n$uni-error-light: mix(#fff,$uni-error,80%);\n\n$uni-info: #8f939c;\n$uni-info-disable:mix(#fff,$uni-info,50%);\n$uni-info-light: mix(#fff,$uni-info,80%);\n\n// 中性色\n// 中性色用于文本、背景和边框颜色。通过运用不同的中性色，来表现层次结构。\n$uni-main-color: #3a3a3a; \t\t\t// 主要文字\n$uni-base-color: #6a6a6a;\t\t\t// 常规文字\n$uni-secondary-color: #909399;\t// 次要文字\n$uni-extra-color: #c7c7c7;\t\t\t// 辅助说明\n\n// 边框颜色\n$uni-border-1: #F0F0F0;\n$uni-border-2: #EDEDED;\n$uni-border-3: #DCDCDC;\n$uni-border-4: #B9B9B9;\n\n// 常规色\n$uni-black: #000000;\n$uni-white: #ffffff;\n$uni-transparent: rgba($color: #000000, $alpha: 0);\n\n// 背景色\n$uni-bg-color: #f7f7f7;\n\n/* 水平间距 */\n$uni-spacing-sm: 8px;\n$uni-spacing-base: 15px;\n$uni-spacing-lg: 30px;\n\n// 阴影\n$uni-shadow-sm:0 0 5px rgba($color: #d8d8d8, $alpha: 0.5);\n$uni-shadow-base:0 1px 8px 1px rgba($color: #a5a5a5, $alpha: 0.2);\n$uni-shadow-lg:0px 1px 10px 2px rgba($color: #a5a4a4, $alpha: 0.5);\n\n// 蒙版\n$uni-mask: rgba($color: #000000, $alpha: 0.4);\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-action-sheet/changelog.md",
    "content": "## 1.0.2（2023-07-02）\nuv-action-sheet  由于弹出层uv-popup的修改，打开和关闭方法更改，详情参考文档：https://www.uvui.cn/components/actionSheet.html\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-action-sheet 底部操作菜单\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-action-sheet/components/uv-action-sheet/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 标题，有值则显示，同时会显示关闭按钮\r\n\t\ttitle: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 选项上方的描述信息\r\n\t\tdescription: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 数据\r\n\t\tactions: {\r\n\t\t\ttype: Array,\r\n\t\t\tdefault: () => []\r\n\t\t},\r\n\t\t// 取消按钮的文字，不为空时显示按钮\r\n\t\tcancelText: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 点击某个菜单项时是否关闭弹窗\r\n\t\tcloseOnClickAction: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 处理底部安全区（默认true）\r\n\t\tsafeAreaInsetBottom: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 小程序的打开方式\r\n\t\topenType: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 点击遮罩是否允许关闭 (默认true)\r\n\t\tcloseOnClickOverlay: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 圆角值\r\n\t\tround: {\r\n\t\t\ttype: [Boolean, String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t...uni.$uv?.props?.actionSheet\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-action-sheet/components/uv-action-sheet/uv-action-sheet.vue",
    "content": "\r\n<template>\r\n\t<uv-popup\r\n\t  ref=\"popup\"\r\n\t  mode=\"bottom\"\r\n\t  :safeAreaInsetBottom=\"safeAreaInsetBottom\"\r\n\t  :round=\"round\"\r\n\t\t:close-on-click-overlay=\"closeOnClickOverlay\"\r\n\t\t@change=\"popupChange\"\r\n\t>\r\n\t\t<view class=\"uv-action-sheet\">\r\n\t\t\t<view\r\n\t\t\t  class=\"uv-action-sheet__header\"\r\n\t\t\t  v-if=\"title\"\r\n\t\t\t>\r\n\t\t\t\t<text class=\"uv-action-sheet__header__title uv-line-1\">{{title}}</text>\r\n\t\t\t\t<view\r\n\t\t\t\t  class=\"uv-action-sheet__header__icon-wrap\"\r\n\t\t\t\t  @tap.stop=\"cancel\"\r\n\t\t\t\t>\r\n\t\t\t\t\t<uv-icon\r\n\t\t\t\t\t  name=\"close\"\r\n\t\t\t\t\t  size=\"17\"\r\n\t\t\t\t\t  color=\"#c8c9cc\"\r\n\t\t\t\t\t  bold\r\n\t\t\t\t\t></uv-icon>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t\t<text\r\n\t\t\t  class=\"uv-action-sheet__description\"\r\n\t\t\t\t:style=\"[{\r\n\t\t\t\t\tmarginTop: `${title && description ? 0 : '18px'}`\r\n\t\t\t\t}]\"\r\n\t\t\t  v-if=\"description\"\r\n\t\t\t>{{description}}</text>\r\n\t\t\t<slot>\r\n\t\t\t\t<uv-line v-if=\"description\"></uv-line>\r\n\t\t\t\t<view class=\"uv-action-sheet__item-wrap\">\r\n\t\t\t\t\t<view v-for=\"(item, index) in actions\" :key=\"index\">\r\n\t\t\t\t\t\t<!-- #ifdef MP -->\r\n\t\t\t\t\t\t<button\r\n\t\t\t\t\t\t  class=\"uv-reset-button\"\r\n\t\t\t\t\t\t  :openType=\"item.openType\"\r\n\t\t\t\t\t\t  @getuserinfo=\"onGetUserInfo\"\r\n\t\t\t\t\t\t  @contact=\"onContact\"\r\n\t\t\t\t\t\t  @getphonenumber=\"onGetPhoneNumber\"\r\n\t\t\t\t\t\t  @error=\"onError\"\r\n\t\t\t\t\t\t  @launchapp=\"onLaunchApp\"\r\n\t\t\t\t\t\t  @opensetting=\"onOpenSetting\"\r\n\t\t\t\t\t\t  :lang=\"lang\"\r\n\t\t\t\t\t\t  :session-from=\"sessionFrom\"\r\n\t\t\t\t\t\t  :send-message-title=\"sendMessageTitle\"\r\n\t\t\t\t\t\t  :send-message-path=\"sendMessagePath\"\r\n\t\t\t\t\t\t  :send-message-img=\"sendMessageImg\"\r\n\t\t\t\t\t\t  :show-message-card=\"showMessageCard\"\r\n\t\t\t\t\t\t  :app-parameter=\"appParameter\"\r\n\t\t\t\t\t\t  @tap=\"selectHandler(index)\"\r\n\t\t\t\t\t\t  :hover-class=\"!item.disabled && !item.loading ? 'uv-action-sheet--hover' : ''\"\r\n\t\t\t\t\t\t>\r\n\t\t\t\t\t\t\t<!-- #endif -->\r\n\t\t\t\t\t\t\t<view\r\n\t\t\t\t\t\t\t  class=\"uv-action-sheet__item-wrap__item\"\r\n\t\t\t\t\t\t\t  @tap.stop=\"selectHandler(index)\"\r\n\t\t\t\t\t\t\t  :hover-class=\"!item.disabled && !item.loading ? 'uv-action-sheet--hover' : ''\"\r\n\t\t\t\t\t\t\t  :hover-stay-time=\"150\"\r\n\t\t\t\t\t\t\t>\r\n\t\t\t\t\t\t\t\t<template v-if=\"!item.loading\">\r\n\t\t\t\t\t\t\t\t\t<text\r\n\t\t\t\t\t\t\t\t\t  class=\"uv-action-sheet__item-wrap__item__name\"\r\n\t\t\t\t\t\t\t\t\t  :style=\"[itemStyle(index)]\"\r\n\t\t\t\t\t\t\t\t\t>{{ item.name }}</text>\r\n\t\t\t\t\t\t\t\t\t<text\r\n\t\t\t\t\t\t\t\t\t  v-if=\"item.subname\"\r\n\t\t\t\t\t\t\t\t\t  class=\"uv-action-sheet__item-wrap__item__subname\"\r\n\t\t\t\t\t\t\t\t\t>{{ item.subname }}</text>\r\n\t\t\t\t\t\t\t\t</template>\r\n\t\t\t\t\t\t\t\t<uv-loading-icon\r\n\t\t\t\t\t\t\t\t  v-else\r\n\t\t\t\t\t\t\t\t  custom-class=\"van-action-sheet__loading\"\r\n\t\t\t\t\t\t\t\t  size=\"18\"\r\n\t\t\t\t\t\t\t\t  mode=\"circle\"\r\n\t\t\t\t\t\t\t\t/>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\t<!-- #ifdef MP -->\r\n\t\t\t\t\t\t</button>\r\n\t\t\t\t\t\t<!-- #endif -->\r\n\t\t\t\t\t\t<uv-line v-if=\"index !== actions.length - 1\"></uv-line>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</slot>\r\n\t\t\t<uv-gap\r\n\t\t\t  bgColor=\"#eaeaec\"\r\n\t\t\t  height=\"6\"\r\n\t\t\t  v-if=\"cancelText\"\r\n\t\t\t></uv-gap>\r\n\t\t\t<view hover-class=\"uv-action-sheet--hover\">\r\n\t\t\t\t<text\r\n\t\t\t\t  @touchmove.stop.prevent\r\n\t\t\t\t  :hover-stay-time=\"150\"\r\n\t\t\t\t  v-if=\"cancelText\"\r\n\t\t\t\t  class=\"uv-action-sheet__cancel-text\"\r\n\t\t\t\t  @tap=\"cancel\"\r\n\t\t\t\t>{{cancelText}}</text>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t</uv-popup>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport button from '@/uni_modules/uv-ui-tools/libs/mixin/button.js'\r\n\timport openType from '@/uni_modules/uv-ui-tools/libs/mixin/openType.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * ActionSheet 操作菜单\r\n\t * @description 本组件用于从底部弹出一个操作菜单，供用户选择并返回结果。本组件功能类似于uni的uni.showActionSheetAPI，配置更加灵活，所有平台都表现一致。\r\n\t * @tutorial https://www.uvui.cn/components/actionSheet.html\r\n\t * @property {Boolean}\t\t\tshow\t\t\t\t操作菜单是否展示 （默认 false ）\r\n\t * @property {String}\t\t\ttitle\t\t\t\t操作菜单标题\r\n\t * @property {String}\t\t\tdescription\t\t\t选项上方的描述信息\r\n\t * @property {Array<Object>}\tactions\t\t\t\t按钮的文字数组，见官方文档示例\r\n\t * @property {String}\t\t\tcancelText\t\t\t取消按钮的提示文字,不为空时显示按钮\r\n\t * @property {Boolean}\t\t\tcloseOnClickAction\t点击某个菜单项时是否关闭弹窗 （默认 true ）\r\n\t * @property {Boolean}\t\t\tsafeAreaInsetBottom\t处理底部安全区 （默认 true ）\r\n\t * @property {String}\t\t\topenType\t\t\t小程序的打开方式 (contact | launchApp | getUserInfo | openSetting ｜getPhoneNumber ｜error )\r\n\t * @property {Boolean}\t\t\tcloseOnClickOverlay\t点击遮罩是否允许关闭  (默认 true )\r\n\t * @property {String}\t\t\tlang\t\t\t\t指定返回用户信息的语言，zh_CN 简体中文，zh_TW 繁体中文，en 英文\r\n\t * @property {String}\t\t\tsessionFrom\t\t\t会话来源，openType=\"contact\"时有效\r\n\t * @property {String}\t\t\tsendMessageTitle\t会话内消息卡片标题，openType=\"contact\"时有效\r\n\t * @property {String}\t\t\tsendMessagePath\t\t会话内消息卡片点击跳转小程序路径，openType=\"contact\"时有效\r\n\t * @property {String}\t\t\tsendMessageImg\t\t会话内消息卡片图片，openType=\"contact\"时有效\r\n\t * @property {Boolean}\t\t\tshowMessageCard\t\t是否显示会话内消息卡片，设置此参数为 true，用户进入客服会话会在右下角显示\"可能要发送的小程序\"提示，用户点击后可以快速发送小程序消息，openType=\"contact\"时有效 （默认 false ）\r\n\t * @property {String}\t\t\tappParameter\t\t打开 APP 时，向 APP 传递的参数，openType=launchApp 时有效\r\n\t * \r\n\t * @event {Function} select\t\t\t点击ActionSheet列表项时触发 \r\n\t * @event {Function} close\t\t\t点击取消按钮时触发\r\n\t * @event {Function} getuserinfo\t用户点击该按钮时，会返回获取到的用户信息，回调的 detail 数据与 wx.getUserInfo 返回的一致，openType=\"getUserInfo\"时有效\r\n\t * @event {Function} contact\t\t客服消息回调，openType=\"contact\"时有效\r\n\t * @event {Function} getphonenumber\t获取用户手机号回调，openType=\"getPhoneNumber\"时有效\r\n\t * @event {Function} error\t\t\t当使用开放能力时，发生错误的回调，openType=\"error\"时有效\r\n\t * @event {Function} launchapp\t\t打开 APP 成功的回调，openType=\"launchApp\"时有效\r\n\t * @event {Function} opensetting\t在打开授权设置页后回调，openType=\"openSetting\"时有效\r\n\t * @example <uv-action-sheet ref=\"actionSheet\" :actions=\"list\" :title=\"title\" ></uv-action-sheet>\r\n\t */\r\n\texport default {\r\n\t\tname: \"uv-action-sheet\",\r\n\t\tmixins: [openType, button, mpMixin , mixin, props],\r\n\t\temits: ['close', 'select'],\r\n\t\tcomputed: {\r\n\t\t\t// 操作项目的样式\r\n\t\t\titemStyle() {\r\n\t\t\t\treturn (index) => {\r\n\t\t\t\t\tlet style = {};\r\n\t\t\t\t\tif (this.actions[index].color) style.color = this.actions[index].color\r\n\t\t\t\t\tif (this.actions[index].fontSize) style.fontSize = this.$uv.addUnit(this.actions[index].fontSize)\r\n\t\t\t\t\t// 选项被禁用的样式\r\n\t\t\t\t\tif (this.actions[index].disabled) style.color = '#c0c4cc'\r\n\t\t\t\t\treturn style;\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\topen() {\r\n\t\t\t\tthis.$refs.popup.open();\r\n\t\t\t},\r\n\t\t\tclose() {\r\n\t\t\t\tthis.$refs.popup.close();\r\n\t\t\t},\r\n\t\t\tpopupChange(e) {\r\n\t\t\t\tif(!e.show) this.$emit('close');\r\n\t\t\t},\r\n\t\t\t// 点击取消按钮\r\n\t\t\tcancel() {\r\n\t\t\t\tthis.close();\r\n\t\t\t},\r\n\t\t\tselectHandler(index) {\r\n\t\t\t\tconst item = this.actions[index]\r\n\t\t\t\tif (item && !item.disabled && !item.loading) {\r\n\t\t\t\t\tthis.$emit('select', item)\r\n\t\t\t\t\tif (this.closeOnClickAction) {\r\n\t\t\t\t\t\tthis.close();\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t$show-lines: 1;\r\n\t$show-reset-button: 1;\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t$uv-action-sheet-reset-button-width:100% !default;\r\n\t$uv-action-sheet-title-font-size: 16px !default;\r\n\t$uv-action-sheet-title-padding: 12px 30px !default;\r\n\t$uv-action-sheet-title-color: $uv-main-color !default;\r\n\t$uv-action-sheet-header-icon-wrap-right:15px !default;\r\n\t$uv-action-sheet-header-icon-wrap-top:15px !default;\r\n\t$uv-action-sheet-description-font-size:13px !default;\r\n\t$uv-action-sheet-description-color:14px !default;\r\n\t$uv-action-sheet-description-margin: 18px 15px !default;\r\n\t$uv-action-sheet-item-wrap-item-padding:15px !default;\r\n\t$uv-action-sheet-item-wrap-name-font-size:16px !default;\r\n\t$uv-action-sheet-item-wrap-subname-font-size:13px !default;\r\n\t$uv-action-sheet-item-wrap-subname-color: #c0c4cc !default;\r\n\t$uv-action-sheet-item-wrap-subname-margin-top:10px !default;\r\n\t$uv-action-sheet-cancel-text-font-size:16px !default;\r\n\t$uv-action-sheet-cancel-text-color:$uv-content-color !default;\r\n\t$uv-action-sheet-cancel-text-font-size:15px !default;\r\n\t$uv-action-sheet-cancel-text-hover-background-color:rgb(242, 243, 245) !default;\r\n\r\n\t.uv-reset-button {\r\n\t\twidth: $uv-action-sheet-reset-button-width;\r\n\t}\r\n\r\n\t.uv-action-sheet {\r\n\t\ttext-align: center;\r\n\t\t&__header {\r\n\t\t\tposition: relative;\r\n\t\t\tpadding: $uv-action-sheet-title-padding;\r\n\t\t\t&__title {\r\n\t\t\t\tfont-size: $uv-action-sheet-title-font-size;\r\n\t\t\t\tcolor: $uv-action-sheet-title-color;\r\n\t\t\t\tfont-weight: bold;\r\n\t\t\t\ttext-align: center;\r\n\t\t\t}\r\n\r\n\t\t\t&__icon-wrap {\r\n\t\t\t\tposition: absolute;\r\n\t\t\t\tright: $uv-action-sheet-header-icon-wrap-right;\r\n\t\t\t\ttop: $uv-action-sheet-header-icon-wrap-top;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t&__description {\r\n\t\t\tfont-size: $uv-action-sheet-description-font-size;\r\n\t\t\tcolor: $uv-tips-color;\r\n\t\t\tmargin: $uv-action-sheet-description-margin;\r\n\t\t\ttext-align: center;\r\n\t\t}\r\n\r\n\t\t&__item-wrap {\r\n\r\n\t\t\t&__item {\r\n\t\t\t\tpadding: $uv-action-sheet-item-wrap-item-padding;\r\n\t\t\t\t@include flex;\r\n\t\t\t\talign-items: center;\r\n\t\t\t\tjustify-content: center;\r\n\t\t\t\tflex-direction: column;\r\n\r\n\t\t\t\t&__name {\r\n\t\t\t\t\tfont-size: $uv-action-sheet-item-wrap-name-font-size;\r\n\t\t\t\t\tcolor: $uv-main-color;\r\n\t\t\t\t\ttext-align: center;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t&__subname {\r\n\t\t\t\t\tfont-size: $uv-action-sheet-item-wrap-subname-font-size;\r\n\t\t\t\t\tcolor: $uv-action-sheet-item-wrap-subname-color;\r\n\t\t\t\t\tmargin-top: $uv-action-sheet-item-wrap-subname-margin-top;\r\n\t\t\t\t\ttext-align: center;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t&__cancel-text {\r\n\t\t\tfont-size: $uv-action-sheet-cancel-text-font-size;\r\n\t\t\tcolor: $uv-action-sheet-cancel-text-color;\r\n\t\t\ttext-align: center;\r\n\t\t\tpadding: $uv-action-sheet-cancel-text-font-size;\r\n\t\t}\r\n\r\n\t\t&--hover {\r\n\t\t\tbackground-color: $uv-action-sheet-cancel-text-hover-background-color;\r\n\t\t}\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-action-sheet/package.json",
    "content": "{\r\n  \"id\": \"uv-action-sheet\",\r\n  \"displayName\": \"uv-action-sheet 底部操作菜单 全面兼容小程序、nvue、vue2、vue3等多端\",\r\n  \"version\": \"1.0.2\",\r\n  \"description\": \"该组件用于从底部弹出一个操作菜单，供用户选择并返回结果。本组件功能类似于uni的uni.showActionSheet API，配置更加灵活，所有平台都表现一致。\",\r\n  \"keywords\": [\r\n    \"action-sheet\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"操作菜单\",\r\n    \"菜单选择\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n      \"ads\": \"无\",\r\n      \"data\": \"插件不采集任何数据\",\r\n      \"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-popup\",\r\n\t\t\t\"uv-icon\",\r\n\t\t\t\"uv-line\",\r\n\t\t\t\"uv-loading-icon\",\r\n\t\t\t\"uv-gap\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n      \"cloud\": {\r\n        \"tcb\": \"y\",\r\n        \"aliyun\": \"y\"\r\n      },\r\n      \"client\": {\r\n        \"Vue\": {\r\n          \"vue2\": \"y\",\r\n          \"vue3\": \"y\"\r\n        },\r\n        \"App\": {\r\n          \"app-vue\": \"y\",\r\n          \"app-nvue\": \"y\"\r\n        },\r\n        \"H5-mobile\": {\r\n          \"Safari\": \"y\",\r\n          \"Android Browser\": \"y\",\r\n          \"微信浏览器(Android)\": \"y\",\r\n          \"QQ浏览器(Android)\": \"y\"\r\n        },\r\n        \"H5-pc\": {\r\n          \"Chrome\": \"y\",\r\n          \"IE\": \"y\",\r\n          \"Edge\": \"y\",\r\n          \"Firefox\": \"y\",\r\n          \"Safari\": \"y\"\r\n        },\r\n        \"小程序\": {\r\n          \"微信\": \"y\",\r\n          \"阿里\": \"y\",\r\n          \"百度\": \"y\",\r\n          \"字节跳动\": \"y\",\r\n          \"QQ\": \"y\",\r\n          \"钉钉\": \"u\",\r\n          \"快手\": \"u\",\r\n          \"飞书\": \"u\",\r\n          \"京东\": \"u\"\r\n        },\r\n        \"快应用\": {\r\n          \"华为\": \"u\",\r\n          \"联盟\": \"u\"\r\n        }\r\n      }\r\n    }\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-action-sheet/readme.md",
    "content": "## ActionSheet 操作菜单\r\n\r\n> **组件名：uv-action-sheet**\r\n\r\n本组件用于从底部弹出一个操作菜单，供用户选择并返回结果。\r\n\r\n本组件功能类似于uni的uni.showActionSheet API，配置更加灵活，所有平台都表现一致。\r\n\r\n### <a href=\"https://www.uvui.cn/components/actionSheet.html\" target=\"_blank\">查看文档</a>\r\n\r\n### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n#### 如使用过程中有任何问题，或者您对uv-ui有一些好的建议，欢迎加入 uv-ui 交流群：<a href=\"https://ext.dcloud.net.cn/plugin?id=12287\" target=\"_blank\">uv-ui</a>、<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-album/changelog.md",
    "content": "## 1.0.3（2023-10-23）\n1. 修复报错的BUG\n## 1.0.2（2023-10-23）\n1. 修复设置singleSize、multipleSize、space等值带单位，存在不显示的BUG\n## 1.0.1（2023-09-13）\r\n1. 添加依赖\r\n## 1.0.0（2023-08-30）\r\n1. 新增uv-album相册组件\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-album/components/uv-album/uv-album.vue",
    "content": "<template>\r\n  <view class=\"uv-album\">\r\n    <view\r\n      class=\"uv-album__row\"\r\n      ref=\"uv-album__row\"\r\n      v-for=\"(arr, index) in showUrls\"\r\n      :forComputedUse=\"albumWidth\"\r\n      :key=\"index\"\r\n    >\r\n      <view\r\n        class=\"uv-album__row__wrapper\"\r\n        v-for=\"(item, index1) in arr\"\r\n        :key=\"index1\"\r\n        :style=\"[imageStyle(index + 1, index1 + 1)]\"\r\n        @tap=\"previewFullImage ? onPreviewTap(getSrc(item)) : ''\"\r\n      >\r\n        <image\r\n          :src=\"getSrc(item)\"\r\n          :mode=\"\r\n            urls.length === 1\r\n              ? imageHeight > 0\r\n                ? singleMode\r\n                  : 'widthFix'\r\n                  : multipleMode\r\n          \"\r\n          :style=\"[\r\n            {\r\n              width: imageWidth,\r\n              height: imageHeight\r\n            }\r\n          ]\"\r\n        ></image>\r\n        <view\r\n          v-if=\"\r\n            showMore &&\r\n            urls.length > rowCount * showUrls.length &&\r\n            index === showUrls.length - 1 &&\r\n            index1 === showUrls[showUrls.length - 1].length - 1\r\n          \"\r\n          class=\"uv-album__row__wrapper__text\"\r\n        >\r\n          <uv-text\r\n            :text=\"`+${urls.length - maxCount}`\"\r\n            color=\"#fff\"\r\n            :size=\"$uv.getPx(multipleSize) * 0.3\"\r\n            align=\"center\"\r\n            customStyle=\"justify-content: center\"\r\n          ></uv-text>\r\n        </view>\r\n      </view>\r\n    </view>\r\n  </view>\r\n</template>\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\t// #ifdef APP-NVUE\r\n\t// 由于weex为阿里的KPI业绩考核的产物，所以不支持百分比单位，这里需要通过dom查询组件的宽度\r\n\tconst dom = uni.requireNativePlugin('dom')\r\n\t// #endif\r\n\r\n\t/**\r\n\t * Album 相册\r\n\t * @description 本组件提供一个类似相册的功能，让开发者开发起来更加得心应手。减少重复的模板代码\r\n\t * @tutorial https://www.uvui.cn/components/album.html\r\n\t * @property {Array}           urls             图片地址列表 Array<String>|Array<Object>形式\r\n\t * @property {String}          keyName          指定从数组的对象元素中读取哪个属性作为图片地址\r\n\t * @property {String | Number} singleSize       单图时，图片长边的长度  （默认 180 ）\r\n\t * @property {String | Number} multipleSize     多图时，图片边长 （默认 70 ）\r\n\t * @property {String | Number} space            多图时，图片水平和垂直之间的间隔 （默认 6 ）\r\n\t * @property {String}          singleMode       单图时，图片缩放裁剪的模式 （默认 'scaleToFill' ）\r\n\t * @property {String}          multipleMode     多图时，图片缩放裁剪的模式 （默认 'aspectFill' ）\r\n\t * @property {String | Number} maxCount         取消按钮的提示文字 （默认 9 ）\r\n\t * @property {Boolean}         previewFullImage 是否可以预览图片 （默认 true ）\r\n\t * @property {String | Number} rowCount         每行展示图片数量，如设置，singleSize和multipleSize将会无效\t（默认 3 ）\r\n\t * @property {Boolean}         showMore         超出maxCount时是否显示查看更多的提示 （默认 true ）\r\n\t *\r\n\t * @event    {Function}        albumWidth       某些特殊的情况下，需要让文字与相册的宽度相等，这里事件的形式对外发送  （回调参数 width ）\r\n\t * @example <uv-album :urls=\"urls2\" @albumWidth=\"width => albumWidth = width\" multipleSize=\"68\" ></uv-album>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-album',\r\n\t\tmixins: [mpMixin, mixin],\r\n\t\temits: ['albumWidth'],\r\n\t\tprops: {\r\n\t\t\t// 图片地址，Array<String>|Array<Object>形式\r\n\t\t\turls: {\r\n\t\t\t\ttype: Array,\r\n\t\t\t\tdefault: () => []\r\n\t\t\t},\r\n\t\t\t// 指定从数组的对象元素中读取哪个属性作为图片地址\r\n\t\t\tkeyName: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: ''\r\n\t\t\t},\r\n\t\t\t// 单图时，图片长边的长度\r\n\t\t\tsingleSize: {\r\n\t\t\t\ttype: [String, Number],\r\n\t\t\t\tdefault: 180\r\n\t\t\t},\r\n\t\t\t// 多图时，图片边长\r\n\t\t\tmultipleSize: {\r\n\t\t\t\ttype: [String, Number],\r\n\t\t\t\tdefault: 70\r\n\t\t\t},\r\n\t\t\t// 多图时，图片水平和垂直之间的间隔\r\n\t\t\tspace: {\r\n\t\t\t\ttype: [String, Number],\r\n\t\t\t\tdefault: 6\r\n\t\t\t},\r\n\t\t\t// 单图时，图片缩放裁剪的模式\r\n\t\t\tsingleMode: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: 'scaleToFill'\r\n\t\t\t},\r\n\t\t\t// 多图时，图片缩放裁剪的模式\r\n\t\t\tmultipleMode: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: 'aspectFill'\r\n\t\t\t},\r\n\t\t\t// 最多展示的图片数量，超出时最后一个位置将会显示剩余图片数量\r\n\t\t\tmaxCount: {\r\n\t\t\t\ttype: [String, Number],\r\n\t\t\t\tdefault: 9\r\n\t\t\t},\r\n\t\t\t// 是否可以预览图片\r\n\t\t\tpreviewFullImage: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: true\r\n\t\t\t},\r\n\t\t\t// 每行展示图片数量，如设置，singleSize和multipleSize将会无效\r\n\t\t\trowCount: {\r\n\t\t\t\ttype: [String, Number],\r\n\t\t\t\tdefault: 3\r\n\t\t\t},\r\n\t\t\t// 超出maxCount时是否显示查看更多的提示\r\n\t\t\tshowMore: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: true\r\n\t\t\t},\r\n\t\t\t...uni.$uv?.props?.album\r\n\t\t},\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\t// 单图的宽度\r\n\t\t\t\tsingleWidth: 0,\r\n\t\t\t\t// 单图的高度\r\n\t\t\t\tsingleHeight: 0,\r\n\t\t\t\t// 单图时，如果无法获取图片的尺寸信息，让图片宽度默认为容器的一定百分比\r\n\t\t\t\tsinglePercent: 0.6\r\n\t\t\t}\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\turls: {\r\n\t\t\t\timmediate: true,\r\n\t\t\t\thandler(newVal) {\r\n\t\t\t\t\tif (newVal.length === 1) {\r\n\t\t\t\t\t\tthis.getImageRect()\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\timageStyle() {\r\n\t\t\t\treturn (index1, index2) => {\r\n\t\t\t\t\tconst { space, rowCount, multipleSize, urls } = this;\r\n\t\t\t\t\tconst rowLen = this.showUrls.length;\r\n\t\t\t\t\tconst allLen = this.urls.length;\r\n\t\t\t\t\tconst style = {\r\n\t\t\t\t\t\tmarginRight: this.$uv.addUnit(space),\r\n\t\t\t\t\t\tmarginBottom: this.$uv.addUnit(space)\r\n\t\t\t\t\t}\r\n\t\t\t\t\t// 如果为最后一行，则每个图片都无需下边框\r\n\t\t\t\t\tif (index1 === rowLen) style.marginBottom = 0\r\n\t\t\t\t\t// 每行的最右边一张和总长度的最后一张无需右边框\r\n\t\t\t\t\tif (\r\n\t\t\t\t\t\tindex2 === rowCount ||\r\n\t\t\t\t\t\t(index1 === rowLen &&\r\n\t\t\t\t\t\t\tindex2 === this.showUrls[index1 - 1].length)\r\n\t\t\t\t\t)\r\n\t\t\t\t\t\tstyle.marginRight = 0\r\n\t\t\t\t\treturn style\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t// 将数组划分为二维数组\r\n\t\t\tshowUrls() {\r\n\t\t\t\tconst arr = []\r\n\t\t\t\tthis.urls.map((item, index) => {\r\n\t\t\t\t\t// 限制最大展示数量\r\n\t\t\t\t\tif (index + 1 <= this.maxCount) {\r\n\t\t\t\t\t\t// 计算该元素为第几个素组内\r\n\t\t\t\t\t\tconst itemIndex = Math.floor(index / this.rowCount)\r\n\t\t\t\t\t\t// 判断对应的索引是否存在\r\n\t\t\t\t\t\tif (!arr[itemIndex]) {\r\n\t\t\t\t\t\t\tarr[itemIndex] = []\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tarr[itemIndex].push(item)\r\n\t\t\t\t\t}\r\n\t\t\t\t})\r\n\t\t\t\treturn arr\r\n\t\t\t},\r\n\t\t\timageWidth() {\r\n\t\t\t\treturn this.$uv.addUnit(\r\n\t\t\t\t\tthis.urls.length === 1 ? this.singleWidth : this.multipleSize\r\n\t\t\t\t)\r\n\t\t\t},\r\n\t\t\timageHeight() {\r\n\t\t\t\treturn this.$uv.addUnit(\r\n\t\t\t\t\tthis.urls.length === 1 ? this.singleHeight : this.multipleSize\r\n\t\t\t\t)\r\n\t\t\t},\r\n\t\t\t// 此变量无实际用途，仅仅是为了利用computed特性，让其在urls长度等变化时，重新计算图片的宽度\r\n\t\t\t// 因为用户在某些特殊的情况下，需要让文字与相册的宽度相等，所以这里事件的形式对外发送\r\n\t\t\talbumWidth() {\r\n\t\t\t\tlet width = 0\r\n\t\t\t\tif (this.urls.length === 1) {\r\n\t\t\t\t\twidth = this.singleWidth\r\n\t\t\t\t} else {\r\n\t\t\t\t\twidth =\r\n\t\t\t\t\t\tthis.showUrls[0].length * this.$uv.getPx(this.multipleSize) +\r\n\t\t\t\t\t\tthis.$uv.getPx(this.space) * (this.showUrls[0].length - 1)\r\n\t\t\t\t}\r\n\t\t\t\tthis.$emit('albumWidth', width)\r\n\t\t\t\treturn width\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\t// 预览图片\r\n\t\t\tonPreviewTap(url) {\r\n\t\t\t\tconst urls = this.urls.map((item) => {\r\n\t\t\t\t\treturn this.getSrc(item)\r\n\t\t\t\t})\r\n\t\t\t\tuni.previewImage({\r\n\t\t\t\t\tcurrent: url,\r\n\t\t\t\t\turls\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\t// 获取图片的路径\r\n\t\t\tgetSrc(item) {\r\n\t\t\t\treturn this.$uv.test.object(item) ?\r\n\t\t\t\t\t(this.keyName && item[this.keyName]) || item.src :\r\n\t\t\t\t\titem\r\n\t\t\t},\r\n\t\t\t// 单图时，获取图片的尺寸\r\n\t\t\t// 在小程序中，需要将网络图片的的域名添加到小程序的download域名才可能获取尺寸\r\n\t\t\t// 在没有添加的情况下，让单图宽度默认为盒子的一定宽度(singlePercent)\r\n\t\t\tgetImageRect() {\r\n\t\t\t\tconst src = this.getSrc(this.urls[0])\r\n\t\t\t\tuni.getImageInfo({\r\n\t\t\t\t\tsrc,\r\n\t\t\t\t\tsuccess: (res) => {\r\n\t\t\t\t\t\t// 判断图片横向还是竖向展示方式\r\n\t\t\t\t\t\tconst isHorizotal = res.width >= res.height\r\n\t\t\t\t\t\tthis.singleWidth = isHorizotal ?\r\n\t\t\t\t\t\t\tthis.singleSize :\r\n\t\t\t\t\t\t\t(res.width / res.height) * this.$uv.getPx(this.singleSize)\r\n\t\t\t\t\t\tthis.singleHeight = !isHorizotal ?\r\n\t\t\t\t\t\t\tthis.singleSize :\r\n\t\t\t\t\t\t\t(res.height / res.width) * this.singleWidth\r\n\t\t\t\t\t},\r\n\t\t\t\t\tfail: () => {\r\n\t\t\t\t\t\tthis.getComponentWidth()\r\n\t\t\t\t\t}\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\t// 获取组件的宽度\r\n\t\t\tasync getComponentWidth() {\r\n\t\t\t\t// 延时一定时间，以获取dom尺寸\r\n\t\t\t\tawait this.$uv.sleep(30)\r\n\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\tthis.$uGetRect('.uv-album__row').then((size) => {\r\n\t\t\t\t\tthis.singleWidth = size.width * this.singlePercent\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\t// 这里ref=\"uv-album__row\"所在的标签为通过for循环出来，导致this.$refs['uv-album__row']是一个数组\r\n\t\t\t\tconst ref = this.$refs['uv-album__row'][0]\r\n\t\t\t\tref &&\r\n\t\t\t\t\tdom.getComponentRect(ref, (res) => {\r\n\t\t\t\t\t\tthis.singleWidth = res.size.width * this.singlePercent\r\n\t\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t.uv-album {\r\n\t\t@include flex(column);\r\n\t\t&__row {\r\n\t\t\t@include flex(row);\r\n\t\t\tflex-wrap: wrap;\r\n\t\t\t&__wrapper {\r\n\t\t\t\tposition: relative;\r\n\t\t\t\t&__text {\r\n\t\t\t\t\tposition: absolute;\r\n\t\t\t\t\ttop: 0;\r\n\t\t\t\t\tleft: 0;\r\n\t\t\t\t\tright: 0;\r\n\t\t\t\t\tbottom: 0;\r\n\t\t\t\t\tbackground-color: rgba(0, 0, 0, 0.3);\r\n\t\t\t\t\t@include flex(row);\r\n\t\t\t\t\tjustify-content: center;\r\n\t\t\t\t\talign-items: center;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</style>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-album/package.json",
    "content": "{\r\n  \"id\": \"uv-album\",\r\n  \"displayName\": \"uv-album 相册 全面兼容vue3+2、app、h5、小程序等多端\",\r\n  \"version\": \"1.0.3\",\r\n  \"description\": \"本组件提供一个类似相册的功能，让开发者开发起来更加得心应手，功能齐全，灵活配置可以，开箱即用。减少重复的模板代码\",\r\n  \"keywords\": [\r\n    \"album\",\r\n    \"uv-ui\",\r\n    \"uvui\",\r\n    \"相册\",\r\n    \"图片\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-text\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-album/readme.md",
    "content": "# Album 相册\r\n\r\n> **组件名：uv-album**\r\n\r\n本组件提供一个类似相册的功能，让开发者开发起来更加得心应手。\r\n\r\n功能齐全，灵活配置可以，开箱即用。减少重复的模板代码。\r\n\r\n# <a href=\"https://www.uvui.cn/components/album.html\" target=\"_blank\">查看文档</a>\r\n\r\n## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>（请不要 下载插件ZIP）</small>\r\n\r\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n<a href=\"https://ext.dcloud.net.cn/plugin?name=uv-ui\" target=\"_blank\">\r\n\r\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\r\n\r\n</a>\r\n\r\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-alert/changelog.md",
    "content": "## 1.0.2（2023-06-01）\n1. 修复点击触发两次实践的BUG\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-alert 警告提示组件\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-alert/components/uv-alert/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 显示文字\r\n\t\ttitle: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 主题，success/warning/info/error\r\n\t\ttype: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'warning'\r\n\t\t},\r\n\t\t// 辅助性文字\r\n\t\tdescription: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 是否可关闭\r\n\t\tclosable: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否显示图标\r\n\t\tshowIcon: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 浅或深色调，light-浅色，dark-深色\r\n\t\teffect: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'light'\r\n\t\t},\r\n\t\t// 文字是否居中\r\n\t\tcenter: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 字体大小\r\n\t\tfontSize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 14\r\n\t\t},\r\n\t\t...uni.$uv?.props?.alert\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-alert/components/uv-alert/uv-alert.vue",
    "content": "<template>\r\n\t<uv-transition\r\n\t    mode=\"fade\"\r\n\t    :show=\"show\"\r\n\t>\r\n\t\t<view\r\n\t\t    class=\"uv-alert\"\r\n\t\t    :class=\"[`uv-alert--${type}--${effect}`]\"\r\n\t\t    @tap.stop=\"clickHandler\"\r\n\t\t    :style=\"[$uv.addStyle(customStyle)]\"\r\n\t\t>\r\n\t\t\t<view\r\n\t\t\t    class=\"uv-alert__icon\"\r\n\t\t\t    v-if=\"showIcon\"\r\n\t\t\t>\r\n\t\t\t\t<uv-icon\r\n\t\t\t\t    :name=\"iconName\"\r\n\t\t\t\t    size=\"18\"\r\n\t\t\t\t    :color=\"iconColor\"\r\n\t\t\t\t></uv-icon>\r\n\t\t\t</view>\r\n\t\t\t<view\r\n\t\t\t    class=\"uv-alert__content\"\r\n\t\t\t    :style=\"[{\r\n\t\t\t\t\tpaddingRight: closable ? '20px' : 0\r\n\t\t\t\t}]\"\r\n\t\t\t>\r\n\t\t\t\t<text\r\n\t\t\t\t    class=\"uv-alert__content__title\"\r\n\t\t\t\t    v-if=\"title\"\r\n\t\t\t\t\t:style=\"[{\r\n\t\t\t\t\t\tfontSize: $uv.addUnit(fontSize),\r\n\t\t\t\t\t\ttextAlign: center ? 'center' : 'left'\r\n\t\t\t\t\t}]\"\r\n\t\t\t\t    :class=\"[effect === 'dark' ? 'uv-alert__text--dark' : `uv-alert__text--${type}--light`]\"\r\n\t\t\t\t>{{ title }}</text>\r\n\t\t\t\t<text\r\n\t\t\t\t    class=\"uv-alert__content__desc\"\r\n\t\t\t\t\tv-if=\"description\"\r\n\t\t\t\t\t:style=\"[{\r\n\t\t\t\t\t\tfontSize: $uv.addUnit(fontSize),\r\n\t\t\t\t\t\ttextAlign: center ? 'center' : 'left'\r\n\t\t\t\t\t}]\"\r\n\t\t\t\t    :class=\"[effect === 'dark' ? 'uv-alert__text--dark' : `uv-alert__text--${type}--light`]\"\r\n\t\t\t\t>{{ description }}</text>\r\n\t\t\t</view>\r\n\t\t\t<view\r\n\t\t\t    class=\"uv-alert__close\"\r\n\t\t\t    v-if=\"closable\"\r\n\t\t\t    @tap.stop=\"closeHandler\"\r\n\t\t\t>\r\n\t\t\t\t<uv-icon\r\n\t\t\t\t    name=\"close\"\r\n\t\t\t\t    :color=\"iconColor\"\r\n\t\t\t\t    size=\"15\"\r\n\t\t\t\t></uv-icon>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t</uv-transition>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * Alert  警告提示\r\n\t * @description 警告提示，展现需要关注的信息。\r\n\t * @tutorial https://www.uvui.cn/components/alertTips.html\r\n\t * \r\n\t * @property {String}\t\t\ttitle       显示的文字 \r\n\t * @property {String}\t\t\ttype        使用预设的颜色  （默认 'warning' ）\r\n\t * @property {String}\t\t\tdescription 辅助性文字，颜色比title浅一点，字号也小一点，可选  \r\n\t * @property {Boolean}\t\t\tclosable    关闭按钮(默认为叉号icon图标)  （默认 false ）\r\n\t * @property {Boolean}\t\t\tshowIcon    是否显示左边的辅助图标   （ 默认 false ）\r\n\t * @property {String}\t\t\teffect      多图时，图片缩放裁剪的模式  （默认 'light' ）\r\n\t * @property {Boolean}\t\t\tcenter\t\t文字是否居中  （默认 false ）\r\n\t * @property {String | Number}\tfontSize    字体大小  （默认 14 ）\r\n\t * @property {Object}\t\t\tcustomStyle\t定义需要用到的外部样式\r\n\t * @event    {Function}        click       点击组件时触发\r\n\t * @example  <uv-alert :title=\"title\"  type = \"warning\" :closable=\"closable\" :description = \"description\"></uv-alert>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-alert',\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\temits: ['click'],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tshow: true\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\ticonColor() {\r\n\t\t\t\treturn this.effect === 'light' ? this.type : '#fff'\r\n\t\t\t},\r\n\t\t\t// 不同主题对应不同的图标\r\n\t\t\ticonName() {\r\n\t\t\t\tswitch (this.type) {\r\n\t\t\t\t\tcase 'success':\r\n\t\t\t\t\t\treturn 'checkmark-circle-fill';\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase 'error':\r\n\t\t\t\t\t\treturn 'close-circle-fill';\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase 'warning':\r\n\t\t\t\t\t\treturn 'error-circle-fill';\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase 'info':\r\n\t\t\t\t\t\treturn 'info-circle-fill';\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase 'primary':\r\n\t\t\t\t\t\treturn 'more-circle-fill';\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tdefault: \r\n\t\t\t\t\t\treturn 'error-circle-fill';\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\t// 点击内容\r\n\t\t\tclickHandler() {\r\n\t\t\t\tthis.$emit('click')\r\n\t\t\t},\r\n\t\t\t// 点击关闭按钮\r\n\t\t\tcloseHandler() {\r\n\t\t\t\tthis.show = false\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t.uv-alert {\r\n\t\tposition: relative;\r\n\t\tbackground-color: $uv-primary;\r\n\t\tpadding: 8px 10px;\r\n\t\t@include flex(row);\r\n\t\talign-items: center;\r\n\t\tborder-top-left-radius: 4px;\r\n\t\tborder-top-right-radius: 4px;\r\n\t\tborder-bottom-left-radius: 4px;\r\n\t\tborder-bottom-right-radius: 4px;\r\n\r\n\t\t&--primary--dark {\r\n\t\t\tbackground-color: $uv-primary;\r\n\t\t}\r\n\r\n\t\t&--primary--light {\r\n\t\t\tbackground-color: #ecf5ff;\r\n\t\t}\r\n\r\n\t\t&--error--dark {\r\n\t\t\tbackground-color: $uv-error;\r\n\t\t}\r\n\r\n\t\t&--error--light {\r\n\t\t\tbackground-color: #FEF0F0;\r\n\t\t}\r\n\r\n\t\t&--success--dark {\r\n\t\t\tbackground-color: $uv-success;\r\n\t\t}\r\n\r\n\t\t&--success--light {\r\n\t\t\tbackground-color: #f5fff0;\r\n\t\t}\r\n\r\n\t\t&--warning--dark {\r\n\t\t\tbackground-color: $uv-warning;\r\n\t\t}\r\n\r\n\t\t&--warning--light {\r\n\t\t\tbackground-color: #FDF6EC;\r\n\t\t}\r\n\r\n\t\t&--info--dark {\r\n\t\t\tbackground-color: $uv-info;\r\n\t\t}\r\n\r\n\t\t&--info--light {\r\n\t\t\tbackground-color: #f4f4f5;\r\n\t\t}\r\n\r\n\t\t&__icon {\r\n\t\t\tmargin-right: 5px;\r\n\t\t}\r\n\r\n\t\t&__content {\r\n\t\t\t@include flex(column);\r\n\t\t\tflex: 1;\r\n\r\n\t\t\t&__title {\r\n\t\t\t\tcolor: $uv-main-color;\r\n\t\t\t\tfont-size: 14px;\r\n\t\t\t\tfont-weight: bold;\r\n\t\t\t\tcolor: #fff;\r\n\t\t\t\tmargin-bottom: 2px;\r\n\t\t\t}\r\n\r\n\t\t\t&__desc {\r\n\t\t\t\tcolor: $uv-main-color;\r\n\t\t\t\tfont-size: 14px;\r\n\t\t\t\tflex-wrap: wrap;\r\n\t\t\t\tcolor: #fff;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t&__title--dark,\r\n\t\t&__desc--dark {\r\n\t\t\tcolor: #FFFFFF;\r\n\t\t}\r\n\r\n\t\t&__text--primary--light,\r\n\t\t&__text--primary--light {\r\n\t\t\tcolor: $uv-primary;\r\n\t\t}\r\n\r\n\t\t&__text--success--light,\r\n\t\t&__text--success--light {\r\n\t\t\tcolor: $uv-success;\r\n\t\t}\r\n\r\n\t\t&__text--warning--light,\r\n\t\t&__text--warning--light {\r\n\t\t\tcolor: $uv-warning;\r\n\t\t}\r\n\r\n\t\t&__text--error--light,\r\n\t\t&__text--error--light {\r\n\t\t\tcolor: $uv-error;\r\n\t\t}\r\n\r\n\t\t&__text--info--light,\r\n\t\t&__text--info--light {\r\n\t\t\tcolor: $uv-info;\r\n\t\t}\r\n\r\n\t\t&__close {\r\n\t\t\tposition: absolute;\r\n\t\t\ttop: 11px;\r\n\t\t\tright: 10px;\r\n\t\t}\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-alert/package.json",
    "content": "{\r\n\t\"id\": \"uv-alert\",\r\n\t\"displayName\": \"uv-alert 警告提示 全面兼容小程序、nvue、vue2、vue3等多端\",\r\n\t\"version\": \"1.0.2\",\r\n\t\"description\": \"uv-alert 警告提示，展现需要关注的信息。灵活配置，功能齐全，兼容全端\",\r\n\t\"keywords\": [\r\n        \"alert\",\r\n        \"uvui\",\r\n        \"uv-ui\",\r\n        \"警告提示\"\r\n    ],\r\n\t\"repository\": \"\",\r\n\t\"engines\": {\r\n\t\t\"HBuilderX\": \"^3.1.0\"\r\n\t},\r\n\t\"dcloudext\": {\r\n\t\t\"type\": \"component-vue\",\r\n\t\t\"sale\": {\r\n\t\t\t\"regular\": {\r\n\t\t\t\t\"price\": \"0.00\"\r\n\t\t\t},\r\n\t\t\t\"sourcecode\": {\r\n\t\t\t\t\"price\": \"0.00\"\r\n\t\t\t}\r\n\t\t},\r\n\t\t\"contact\": {\r\n\t\t\t\"qq\": \"\"\r\n\t\t},\r\n\t\t\"declaration\": {\r\n\t\t\t\"ads\": \"无\",\r\n\t\t\t\"data\": \"插件不采集任何数据\",\r\n\t\t\t\"permissions\": \"无\"\r\n\t\t},\r\n\t\t\"npmurl\": \"\"\r\n\t},\r\n\t\"uni_modules\": {\r\n\t\t\"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-transition\",\r\n\t\t\t\"uv-icon\"\r\n\t\t],\r\n\t\t\"encrypt\": [],\r\n\t\t\"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-alert/readme.md",
    "content": "## Alert 警告提示\r\n\r\n> **组件名：uv-alert**\r\n\r\n警告提示，展现需要关注的信息。\r\n\r\n当某个页面需要向用户显示警告的信息时。\r\n\r\n非浮层的静态展现形式，始终展现，不会自动消失，用户可以点击关闭。\r\n\r\n### <a href=\"https://www.uvui.cn/components/alert.html\" target=\"_blank\">查看文档</a>\r\n\r\n### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n#### 如使用过程中有任何问题，或者您对uv-ui有一些好的建议，欢迎加入 uv-ui 交流群：<a href=\"https://ext.dcloud.net.cn/plugin?id=12287\" target=\"_blank\">uv-ui</a>、<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-avatar/changelog.md",
    "content": "## 1.0.1（2023-05-16）\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\n2. 优化部分功能\n## 1.0.0（2023-05-10）\nuv-avatar 头像组件\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-avatar/components/uv-avatar/props.js",
    "content": "import { range } from '@/uni_modules/uv-ui-tools/libs/function/test.js'\r\nexport default {\r\n\tprops: {\r\n\t\t// 头像图片路径(不能为相对路径)\r\n\t\tsrc: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 头像形状，circle-圆形，square-方形\r\n\t\tshape: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'circle'\r\n\t\t},\r\n\t\t// 头像尺寸\r\n\t\tsize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 40\r\n\t\t},\r\n\t\t// 裁剪模式\r\n\t\tmode: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'scaleToFill'\r\n\t\t},\r\n\t\t// 显示的文字\r\n\t\ttext: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 背景色\r\n\t\tbgColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#c0c4cc'\r\n\t\t},\r\n\t\t// 文字颜色\r\n\t\tcolor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#fff'\r\n\t\t},\r\n\t\t// 文字大小\r\n\t\tfontSize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 18\r\n\t\t},\r\n\t\t// 显示的图标\r\n\t\ticon: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 显示小程序头像，只对百度，微信，QQ小程序有效\r\n\t\tmpAvatar: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否使用随机背景色\r\n\t\trandomBgColor: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 加载失败的默认头像(组件有内置默认图片)\r\n\t\tdefaultUrl: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 如果配置了randomBgColor为true，且配置了此值，则从默认的背景色数组中取出对应索引的颜色值，取值0-19之间\r\n\t\tcolorIndex: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\t// 校验参数规则，索引在0-19之间\r\n\t\t\tvalidator(n) {\r\n\t\t\t\treturn range(n, [0, 19]) || n === ''\r\n\t\t\t},\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 组件标识符\r\n\t\tname: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t...uni.$uv?.props?.avatar\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-avatar/components/uv-avatar/uv-avatar.vue",
    "content": "<template>\r\n\t<view\r\n\t\tclass=\"uv-avatar\"\r\n\t\t:class=\"[`uv-avatar--${shape}`]\"\r\n\t\t:style=\"[{\r\n\t\t\tbackgroundColor: (text || icon) ? (randomBgColor ? colors[colorIndex !== '' ? colorIndex : $uv.random(0, 19)] : bgColor) : 'transparent',\r\n\t\t\twidth: $uv.addUnit(size),\r\n\t\t\theight: $uv.addUnit(size),\r\n\t\t}, $uv.addStyle(customStyle)]\"\r\n\t\t@tap=\"clickHandler\"\r\n\t>\r\n\t\t<slot>\r\n\t\t\t<!-- #ifdef MP-WEIXIN || MP-QQ || MP-BAIDU  -->\r\n\t\t\t<open-data\r\n\t\t\t\tv-if=\"mpAvatar && allowMp\"\r\n\t\t\t\ttype=\"userAvatarUrl\"\r\n\t\t\t\t:style=\"[{\r\n\t\t\t\t\twidth: $uv.addUnit(size),\r\n\t\t\t\t\theight: $uv.addUnit(size)\r\n\t\t\t\t}]\"\r\n\t\t\t/>\r\n\t\t\t<!-- #endif -->\r\n\t\t\t<!-- #ifndef MP-WEIXIN && MP-QQ && MP-BAIDU  -->\r\n\t\t\t<template v-if=\"mpAvatar && allowMp\"></template>\r\n\t\t\t<!-- #endif -->\r\n\t\t\t<uv-icon\r\n\t\t\t\tv-else-if=\"icon\"\r\n\t\t\t\t:name=\"icon\"\r\n\t\t\t\t:size=\"fontSize\"\r\n\t\t\t\t:color=\"color\"\r\n\t\t\t></uv-icon>\r\n\t\t\t<uv-text\r\n\t\t\t\tv-else-if=\"text\"\r\n\t\t\t\t:text=\"text\"\r\n\t\t\t\t:size=\"fontSize\"\r\n\t\t\t\t:color=\"color\"\r\n\t\t\t\talign=\"center\"\r\n\t\t\t\tcustomStyle=\"justify-content: center\"\r\n\t\t\t></uv-text>\r\n\t\t\t<image\r\n\t\t\t\tclass=\"uv-avatar__image\"\r\n\t\t\t\tv-else\r\n\t\t\t\t:class=\"[`uv-avatar__image--${shape}`]\"\r\n\t\t\t\t:src=\"avatarUrl || defaultUrl\"\r\n\t\t\t\t:mode=\"mode\"\r\n\t\t\t\t@error=\"errorHandler\"\r\n\t\t\t\t:style=\"[{\r\n\t\t\t\t\twidth: $uv.addUnit(size),\r\n\t\t\t\t\theight: $uv.addUnit(size)\r\n\t\t\t\t}]\"\r\n\t\t\t></image>\r\n\t\t</slot>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\tconst base64Avatar =\r\n\t\t\"data:image/jpg;base64,/9j/4QAYRXhpZgAASUkqAAgAAAAAAAAAAAAAAP/sABFEdWNreQABAAQAAAA8AAD/4QMraHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLwA8P3hwYWNrZXQgYmVnaW49Iu+7vyIgaWQ9Ilc1TTBNcENlaGlIenJlU3pOVGN6a2M5ZCI/PiA8eDp4bXBtZXRhIHhtbG5zOng9ImFkb2JlOm5zOm1ldGEvIiB4OnhtcHRrPSJBZG9iZSBYTVAgQ29yZSA1LjMtYzAxMSA2Ni4xNDU2NjEsIDIwMTIvMDIvMDYtMTQ6NTY6MjcgICAgICAgICI+IDxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+IDxyZGY6RGVzY3JpcHRpb24gcmRmOmFib3V0PSIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1sbnM6eG1wTU09Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9tbS8iIHhtbG5zOnN0UmVmPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvc1R5cGUvUmVzb3VyY2VSZWYjIiB4bXA6Q3JlYXRvclRvb2w9IkFkb2JlIFBob3Rvc2hvcCBDUzYgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjREMEQwRkY0RjgwNDExRUE5OTY2RDgxODY3NkJFODMxIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjREMEQwRkY1RjgwNDExRUE5OTY2RDgxODY3NkJFODMxIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6NEQwRDBGRjJGODA0MTFFQTk5NjZEODE4Njc2QkU4MzEiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6NEQwRDBGRjNGODA0MTFFQTk5NjZEODE4Njc2QkU4MzEiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz7/7gAOQWRvYmUAZMAAAAAB/9sAhAAGBAQEBQQGBQUGCQYFBgkLCAYGCAsMCgoLCgoMEAwMDAwMDBAMDg8QDw4MExMUFBMTHBsbGxwfHx8fHx8fHx8fAQcHBw0MDRgQEBgaFREVGh8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx8fHx//wAARCADIAMgDAREAAhEBAxEB/8QAcQABAQEAAwEBAAAAAAAAAAAAAAUEAQMGAgcBAQAAAAAAAAAAAAAAAAAAAAAQAAIBAwICBgkDBQAAAAAAAAABAhEDBCEFMVFBYXGREiKBscHRMkJSEyOh4XLxYjNDFBEBAAAAAAAAAAAAAAAAAAAAAP/aAAwDAQACEQMRAD8A/fAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHbHFyZ/Dam+yLA+Z2L0Pjtyj2poD4AAAAAAAAAAAAAAAAAAAAAAAAKWFs9y6lcvvwQeqj8z9wFaziY1n/HbUX9XF97A7QAGXI23EvJ1goyfzR0YEfN269jeZ+a03pNe0DIAAAAAAAAAAAAAAAAAAAACvtO3RcVkXlWutuL9YFYAAAAAOJRjKLjJVi9GmB5/csH/mu1h/in8PU+QGMAAAAAAAAAAAAAAAAAAaMDG/6MmMH8C80+xAelSSVFolwQAAAAAAAHVlWI37ErUulaPk+hgeYnCUJuElSUXRrrQHAAAAAAAAAAAAAAAAABa2Oz4bM7r4zdF2ICmAAAAAAAAAg7zZ8GX41wuJP0rRgYAAAAAAAAAAAAAAAAAD0m2R8ODaXU33tsDSAAAAAAAAAlb9HyWZcnJd9PcBHAAAAAAAAAAAAAAAAAPS7e64Vn+KA0AAAAAAAAAJm+v8Ftf3ewCKAAAAAAAAAAAAAAAAAX9muqeGo9NttP06+0DcAAAAAAAAAjb7dTu2ra+VOT9P8AQCWAAAAAAAAAAAAAAAAAUNmyPt5Ltv4bui/kuAF0AAAAAAADiUlGLlJ0SVW+oDzOXfd/Ind6JPRdS0QHSAAAAAAAAAAAAAAAAAE2nVaNcGB6Lbs6OTao9LsF51z60BrAAAAAABJ3jOVHjW3r/sa9QEgAAAAAAAAAAAAAAAAAAAPu1duWriuW34ZR4MC9hbnZyEoy8l36XwfYBsAAADaSq9EuLAlZ+7xSdrGdW9Hc5dgEdtt1erfFgAAAAAAAAAAAAAAAAADVjbblX6NR8MH80tEBRs7HYivyzlN8lovaBPzduvY0m6eK10TXtAyAarO55lpJK54orolr+4GqO/Xaea1FvqbXvA+Z77kNeW3GPbV+4DJfzcm/pcm3H6Vou5AdAFLC2ed2Pjv1txa8sV8T6wOL+yZEKu1JXFy4MDBOE4ScZxcZLinoB8gAAAAAAAAAAAB242LeyJ+C3GvN9C7QLmJtePYpKS+5c+p8F2IDYAANJqj1T4oCfk7Nj3G5Wn9qXJax7gJ93Z82D8sVNc4v30A6Xg5i42Z+iLfqARwcyT0sz9MWvWBps7LlTf5Grce9/oBTxdtxseklHxT+uWr9AGoAB138ezfj4bsFJdD6V2MCPm7RdtJzs1uW1xXzL3gTgAAAAAAAAADRhYc8q74I6RWs5ckB6GxYtWLat21SK731sDsAAAAAAAAAAAAAAAASt021NO/YjrxuQXT1oCOAAAAAAABzGLlJRSq26JAelwsWONYjbXxcZvmwO8AAAAAAAAAAAAAAAAAAef3TEWPkVivx3NY9T6UBiAAAAAABo2+VmGXblddIJ8eivRUD0oAAAAAAAAAAAAAAAAAAAYt4tKeFKVNYNSXfRgefAAAAAAAAr7VuSSWPedKaW5v1MCsAAAAAAAAAAAAAAAAAAIe6bj96Ts2n+JPzSXzP3ATgAAAAAAAAFbbt1UUrOQ9FpC4/UwK6aaqtU+DAAAAAAAAAAAAAAA4lKMIuUmoxWrb4ARNx3R3q2rLpa4Sl0y/YCcAAAAAAAAAAANmFud7G8r89r6X0dgFvGzLGRGtuWvTF6NAdwAAAAAAAAAAAy5W442PVN+K59EePp5ARMvOv5MvO6QXCC4AZwAAAAAAAAAAAAAcxlKLUotprg1owN+PvORborq+7Hnwl3gUbO74VzRydt8pKn68ANcJwmqwkpLmnUDkAAAAfNy9atqtyagut0AxXt5xIV8Fbj6lRd7Am5G65V6qUvtwfyx94GMAAAAAAAAAAAAAAAAAAAOU2nVOj5gdsc3LiqRvTpyqwOxbnnrhdfpSfrQB7pnv/AGvuS9gHXPMy5/Fem1yq0v0A6W29XqwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAf//Z\";\r\n\t/**\r\n\t * Avatar  头像\r\n\t * @description 本组件一般用于展示头像的地方，如个人中心，或者评论列表页的用户头像展示等场所。\r\n\t * @tutorial https://www.uvui.cn/components/avatar.html\r\n\t *\r\n\t * @property {String}\t\t\tsrc\t\t\t\t头像路径，如加载失败，将会显示默认头像(不能为相对路径)\r\n\t * @property {String}\t\t\tshape\t\t\t头像形状  （ circle (默认) | square）\r\n\t * @property {String | Number}\tsize\t\t\t头像尺寸，可以为指定字符串(large, default, mini)，或者数值 （默认 40 ）\r\n\t * @property {String}\t\t\tmode\t\t\t头像图片的裁剪类型，与uni的image组件的mode参数一致，如效果达不到需求，可尝试传widthFix值 （默认 'scaleToFill' ）\r\n\t * @property {String}\t\t\ttext\t\t\t用文字替代图片，级别优先于src\r\n\t * @property {String}\t\t\tbgColor\t\t\t背景颜色，一般显示文字时用 （默认 '#c0c4cc' ）\r\n\t * @property {String}\t\t\tcolor\t\t\t文字颜色 （默认 '#ffffff' ）\r\n\t * @property {String | Number}\tfontSize\t\t文字大小  （默认 18 ）\r\n\t * @property {String}\t\t\ticon\t\t\t显示的图标\r\n\t * @property {Boolean}\t\t\tmpAvatar\t\t显示小程序头像，只对百度，微信，QQ小程序有效  （默认 false ）\r\n\t * @property {Boolean}\t\t\trandomBgColor\t是否使用随机背景色  （默认 false ）\r\n\t * @property {String}\t\t\tdefaultUrl\t\t加载失败的默认头像(组件有内置默认图片)\r\n\t * @property {String | Number}\tcolorIndex\t\t如果配置了randomBgColor为true，且配置了此值，则从默认的背景色数组中取出对应索引的颜色值，取值0-19之间\r\n\t * @property {String}\t\t\tname\t\t\t组件标识符  （默认 'level' ）\r\n\t * @property {Object}\t\t\tcustomStyle\t\t定义需要用到的外部样式\r\n\t *\r\n\t * @event    {Function}        click       点击组件时触发   index: 用户传递的标识符\r\n\t * @example  <uv-avatar :src=\"src\" mode=\"square\"></uv-avatar>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-avatar',\r\n\t\temits: ['click'],\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\t// 如果配置randomBgColor参数为true，在图标或者文字的模式下，会随机从中取出一个颜色值当做背景色\r\n\t\t\t\tcolors: ['#ffb34b', '#f2bba9', '#f7a196', '#f18080', '#88a867', '#bfbf39', '#89c152', '#94d554', '#f19ec2',\r\n\t\t\t\t\t'#afaae4', '#e1b0df', '#c38cc1', '#72dcdc', '#9acdcb', '#77b1cc', '#448aca', '#86cefa', '#98d1ee',\r\n\t\t\t\t\t'#73d1f1',\r\n\t\t\t\t\t'#80a7dc'\r\n\t\t\t\t],\r\n\t\t\t\tavatarUrl: this.src,\r\n\t\t\t\tallowMp: false\r\n\t\t\t}\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\t// 监听头像src的变化，赋值给内部的avatarUrl变量，因为图片加载失败时，需要修改图片的src为默认值\r\n\t\t\t// 而组件内部不能直接修改props的值，所以需要一个中间变量\r\n\t\t\tsrc: {\r\n\t\t\t\timmediate: true,\r\n\t\t\t\thandler(newVal) {\r\n\t\t\t\t\tthis.avatarUrl = newVal\r\n\t\t\t\t\t// 如果没有传src，则主动触发error事件，用于显示默认的头像，否则src为''空字符等的时候，会无内容展示\r\n\t\t\t\t\tif(!newVal) {\r\n\t\t\t\t\t\tthis.errorHandler()\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\timageStyle() {\r\n\t\t\t\tconst style = {}\r\n\t\t\t\treturn style\r\n\t\t\t}\r\n\t\t},\r\n\t\tcreated() {\r\n\t\t\tthis.init()\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tinit() {\r\n\t\t\t\t// 目前只有这几个小程序平台具有open-data标签\r\n\t\t\t\t// 其他平台可以通过uni.getUserInfo类似接口获取信息，但是需要弹窗授权(首次)，不合符组件逻辑\r\n\t\t\t\t// 故目前自动获取小程序头像只支持这几个平台\r\n\t\t\t\t// #ifdef MP-WEIXIN || MP-QQ || MP-BAIDU\r\n\t\t\t\tthis.allowMp = true\r\n\t\t\t\t// #endif\r\n\t\t\t},\r\n\t\t\t// 判断传入的name属性，是否图片路径，只要带有\"/\"均认为是图片形式\r\n\t\t\tisImg() {\r\n\t\t\t\treturn this.src.indexOf('/') !== -1\r\n\t\t\t},\r\n\t\t\t// 图片加载时失败时触发\r\n\t\t\terrorHandler() {\r\n\t\t\t\tthis.avatarUrl = this.defaultUrl || base64Avatar\r\n\t\t\t},\r\n\t\t\tclickHandler() {\r\n\t\t\t\tthis.$emit('click', this.name)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\r\n\t.uv-avatar {\r\n\t\t@include flex;\r\n\t\talign-items: center;\r\n\t\tjustify-content: center;\r\n\r\n\t\t&--circle {\r\n\t\t\tborder-radius: 100px;\r\n\t\t}\r\n\r\n\t\t&--square {\r\n\t\t\tborder-radius: 4px;\r\n\t\t}\r\n\r\n\t\t&__image {\r\n\t\t\t&--circle {\r\n\t\t\t\tborder-radius: 100px;\r\n\t\t\t}\r\n\r\n\t\t\t&--square {\r\n\t\t\t\tborder-radius: 4px;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-avatar/components/uv-avatar-group/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 头像图片组\r\n\t\turls: {\r\n\t\t\ttype: Array,\r\n\t\t\tdefault: () => []\r\n\t\t},\r\n\t\t// 最多展示的头像数量\r\n\t\tmaxCount: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 5\r\n\t\t},\r\n\t\t// 头像形状\r\n\t\tshape: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'circle'\r\n\t\t},\r\n\t\t// 图片裁剪模式\r\n\t\tmode: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'scaleToFill'\r\n\t\t},\r\n\t\t// 超出maxCount时是否显示查看更多的提示\r\n\t\tshowMore: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 头像大小\r\n\t\tsize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 40\r\n\t\t},\r\n\t\t// 指定从数组的对象元素中读取哪个属性作为图片地址\r\n\t\tkeyName: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 头像之间的遮挡比例\r\n\t\tgap: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tvalidator(value) {\r\n\t\t\t\treturn value >= 0 && value <= 1\r\n\t\t\t},\r\n\t\t\tdefault: 0.5\r\n\t\t},\r\n\t\t// 需额外显示的值\r\n\t\textraValue: {\r\n\t\t\ttype: [Number, String],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t...uni.$uv?.props?.avatarGroup\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-avatar/components/uv-avatar-group/uv-avatar-group.vue",
    "content": "<template>\r\n\t<view class=\"uv-avatar-group\">\r\n\t\t<view\r\n\t\t    class=\"uv-avatar-group__item\"\r\n\t\t    v-for=\"(item, index) in showUrl\"\r\n\t\t    :key=\"index\"\r\n\t\t    :style=\"{\r\n\t\t\t\tmarginLeft: index === 0 ? 0 : $uv.addUnit(-size * gap)\r\n\t\t\t}\"\r\n\t\t>\r\n\t\t\t<uv-avatar\r\n\t\t\t    :size=\"size\"\r\n\t\t\t    :shape=\"shape\"\r\n\t\t\t    :mode=\"mode\"\r\n\t\t\t    :src=\"$uv.test.object(item) ? keyName && item[keyName] || item.url : item\"\r\n\t\t\t></uv-avatar>\r\n\t\t\t<view\r\n\t\t\t    class=\"uv-avatar-group__item__show-more\"\r\n\t\t\t    v-if=\"showMore && index === showUrl.length - 1 && (urls.length > maxCount || extraValue > 0)\"\r\n\t\t\t\t@tap=\"clickHandler\"\r\n\t\t\t>\r\n\t\t\t\t<uv-text\r\n\t\t\t\t    color=\"#ffffff\"\r\n\t\t\t\t    :size=\"size * 0.4\"\r\n\t\t\t\t    :text=\"`+${extraValue || urls.length - showUrl.length}`\"\r\n\t\t\t\t\talign=\"center\"\r\n\t\t\t\t\tcustomStyle=\"justify-content: center\"\r\n\t\t\t\t></uv-text>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * AvatarGroup  头像组\r\n\t * @description 本组件一般用于展示头像的地方，如个人中心，或者评论列表页的用户头像展示等场所。\r\n\t * @tutorial https://www.uvui.cn/components/avatar.html\r\n\t * \r\n\t * @property {Array}           urls     头像图片组 （默认 [] ）\r\n\t * @property {String | Number} maxCount 最多展示的头像数量 （ 默认 5 ）\r\n\t * @property {String}          shape    头像形状（ 'circle' (默认) | 'square' ）\r\n\t * @property {String}          mode     图片裁剪模式（默认 'scaleToFill' ）\r\n\t * @property {Boolean}         showMore 超出maxCount时是否显示查看更多的提示 （默认 true ）\r\n\t * @property {String | Number} size      头像大小 （默认 40 ）\r\n\t * @property {String}          keyName  指定从数组的对象元素中读取哪个属性作为图片地址 \r\n\t * @property {String | Number} gap      头像之间的遮挡比例（0.4代表遮挡40%）  （默认 0.5 ）\r\n\t * @property {String | Number} extraValue  需额外显示的值\r\n\t * @event    {Function}        showMore 头像组更多点击\r\n\t * @example  <uv-avatar-group:urls=\"urls\" size=\"35\" gap=\"0.4\" ></uv-avatar-group:urls=>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-avatar-group',\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\tshowUrl() {\r\n\t\t\t\treturn this.urls.slice(0, this.maxCount)\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tclickHandler() {\r\n\t\t\t\tthis.$emit('showMore')\r\n\t\t\t}\r\n\t\t},\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\r\n\t.uv-avatar-group {\r\n\t\t@include flex;\r\n\r\n\t\t&__item {\r\n\t\t\tmargin-left: -10px;\r\n\t\t\tposition: relative;\r\n\r\n\t\t\t&--no-indent {\r\n\t\t\t\t// 如果你想质疑作者不会使用:first-child，说明你太年轻，因为nvue不支持\r\n\t\t\t\tmargin-left: 0;\r\n\t\t\t}\r\n\r\n\t\t\t&__show-more {\r\n\t\t\t\tposition: absolute;\r\n\t\t\t\ttop: 0;\r\n\t\t\t\tbottom: 0;\r\n\t\t\t\tleft: 0;\r\n\t\t\t\tright: 0;\r\n\t\t\t\tbackground-color: rgba(0, 0, 0, 0.3);\r\n\t\t\t\t@include flex;\r\n\t\t\t\talign-items: center;\r\n\t\t\t\tjustify-content: center;\r\n\t\t\t\tborder-radius: 100px;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-avatar/package.json",
    "content": "{\r\n\t\"id\": \"uv-avatar\",\r\n\t\"displayName\": \"uv-avatar 头像 全面兼容小程序、nvue、vue2、vue3等多端\",\r\n\t\"version\": \"1.0.1\",\r\n\t\"description\": \"uv-avatar 本组件一般用于展示头像的地方，如个人中心，或者评论列表页的用户头像展示等场所。\",\r\n\t\"keywords\": [\r\n        \"uv-avatar\",\r\n        \"uvui\",\r\n        \"uv-ui\",\r\n        \"avatar\",\r\n        \"头像\"\r\n    ],\r\n\t\"repository\": \"\",\r\n\t\"engines\": {\r\n\t\t\"HBuilderX\": \"^3.1.0\"\r\n\t},\r\n\t\"dcloudext\": {\r\n\t\t\"type\": \"component-vue\",\r\n\t\t\"sale\": {\r\n\t\t\t\"regular\": {\r\n\t\t\t\t\"price\": \"0.00\"\r\n\t\t\t},\r\n\t\t\t\"sourcecode\": {\r\n\t\t\t\t\"price\": \"0.00\"\r\n\t\t\t}\r\n\t\t},\r\n\t\t\"contact\": {\r\n\t\t\t\"qq\": \"\"\r\n\t\t},\r\n\t\t\"declaration\": {\r\n\t\t\t\"ads\": \"无\",\r\n\t\t\t\"data\": \"插件不采集任何数据\",\r\n\t\t\t\"permissions\": \"无\"\r\n\t\t},\r\n\t\t\"npmurl\": \"\"\r\n\t},\r\n\t\"uni_modules\": {\r\n\t\t\"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-icon\",\r\n\t\t\t\"uv-text\"\r\n\t\t],\r\n\t\t\"encrypt\": [],\r\n\t\t\"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-avatar/readme.md",
    "content": "## Avatar 头像\r\n\r\n> **组件名：uv-avatar**\r\n\r\n本组件一般用于展示头像的地方，如个人中心，或者评论列表页的用户头像展示等场所。\r\n\r\n### <a href=\"https://www.uvui.cn/components/avatar.html\" target=\"_blank\">查看文档</a>\r\n\r\n### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n#### 如使用过程中有任何问题，或者您对uv-ui有一些好的建议，欢迎加入 uv-ui 交流群：<a href=\"https://ext.dcloud.net.cn/plugin?id=12287\" target=\"_blank\">uv-ui</a>、<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-back-top/changelog.md",
    "content": "## 1.0.2（2023-07-03）\n1. 优化插槽自定义内容部分\n2. 增加backToTop方法说明\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-back-top 返回顶部\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-back-top/components/uv-back-top/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 返回顶部的形状，circle-圆形，square-方形\r\n\t\tmode: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'circle'\r\n\t\t},\r\n\t\t// 自定义图标\r\n\t\ticon: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'arrow-upward'\r\n\t\t},\r\n\t\t// 提示文字\r\n\t\ttext: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 返回顶部滚动时间\r\n\t\tduration: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 100\r\n\t\t},\r\n\t\t// 滚动距离\r\n\t\tscrollTop: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 距离顶部多少距离显示，单位px\r\n\t\ttop: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 400\r\n\t\t},\r\n\t\t// 返回顶部按钮到底部的距离，单位px\r\n\t\tbottom: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 100\r\n\t\t},\r\n\t\t// 返回顶部按钮到右边的距离，单位px\r\n\t\tright: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 20\r\n\t\t},\r\n\t\t// 层级\r\n\t\tzIndex: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 9\r\n\t\t},\r\n\t\t// 图标的样式，对象形式\r\n\t\ticonStyle: {\r\n\t\t\ttype: Object,\r\n\t\t\tdefault: () => ({\r\n\t\t\t\tcolor: '#909399',\r\n\t\t\t\tfontSize: '19px'\r\n\t\t\t})\r\n\t\t},\r\n\t\t...uni.$uv?.props?.backtop\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-back-top/components/uv-back-top/uv-back-top.vue",
    "content": "<template>\r\n\t<uv-transition mode=\"fade\" :customStyle=\"backTopStyle\" :show=\"show\">\r\n\t\t<slot>\r\n\t\t\t<view class=\"uv-back-top\" :style=\"[contentStyle]\" @click=\"backToTop\">\r\n\t\t\t\t<uv-icon :name=\"icon\" :custom-style=\"iconStyle\"></uv-icon>\r\n\t\t\t\t<text v-if=\"text\" class=\"uv-back-top__text\">{{text}}</text>\r\n\t\t\t</view>\r\n\t\t</slot>\r\n\t</uv-transition>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t// #ifdef APP-NVUE\r\n\tconst dom = weex.requireModule('dom')\r\n\t// #endif\r\n\t/**\r\n\t * backTop 返回顶部\r\n\t * @description 本组件一个用于长页面，滑动一定距离后，出现返回顶部按钮，方便快速返回顶部的场景。\r\n\t * @tutorial https://www.uvui.cn/components/backTop.html\r\n\t * @property {String}\t\t\tmode  \t\t返回顶部的形状，circle-圆形，square-方形 （默认 'circle' ）\r\n\t * @property {String} \t\t\ticon \t\t自定义图标 （默认 'arrow-upward' ） 见官方文档示例\r\n\t * @property {String} \t\t\ttext \t\t提示文字 \r\n\t * @property {String | Number}  duration\t返回顶部滚动时间 （默认 100）\r\n\t * @property {String | Number}  scrollTop\t滚动距离 （默认 0 ）\r\n\t * @property {String | Number}  top  \t\t距离顶部多少距离显示，单位px （默认 400 ）\r\n\t * @property {String | Number}  bottom  \t返回顶部按钮到底部的距离，单位px （默认 100 ）\r\n\t * @property {String | Number}  right  \t\t返回顶部按钮到右边的距离，单位px （默认 20 ）\r\n\t * @property {String | Number}  zIndex \t\t层级   （默认 9 ）\r\n\t * @property {Object<Object>}  \ticonStyle \t图标的样式，对象形式   （默认 {color: '#909399',fontSize: '19px'}）\r\n\t * @property {Object}\t\t\tcustomStyle\t定义需要用到的外部样式\r\n\t * \r\n\t * @example <uv-back-top :scrollTop=\"scrollTop\"></uv-back-top>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-back-top',\r\n\t\temits: ['click'],\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tcomputed: {\r\n\t\t\tbackTopStyle() {\r\n\t\t\t\t// 动画组件样式\r\n\t\t\t\tconst style = {\r\n\t\t\t\t\tbottom: this.$uv.addUnit(this.bottom),\r\n\t\t\t\t\tright: this.$uv.addUnit(this.right),\r\n\t\t\t\t\twidth: '40px',\r\n\t\t\t\t\theight: '40px',\r\n\t\t\t\t\tposition: 'fixed',\r\n\t\t\t\t\tzIndex: 10,\r\n\t\t\t\t}\r\n\t\t\t\treturn style\r\n\t\t\t},\r\n\t\t\tshow() {\r\n\t\t\t\treturn this.$uv.getPx(this.scrollTop) > this.$uv.getPx(this.top)\r\n\t\t\t},\r\n\t\t\tcontentStyle() {\r\n\t\t\t\tconst style = {}\r\n\t\t\t\tlet radius = 0\r\n\t\t\t\t// 是否圆形\r\n\t\t\t\tif (this.mode === 'circle') {\r\n\t\t\t\t\tradius = '100px'\r\n\t\t\t\t} else {\r\n\t\t\t\t\tradius = '4px'\r\n\t\t\t\t}\r\n\t\t\t\t// 为了兼容安卓nvue，只能这么分开写\r\n\t\t\t\tstyle.borderTopLeftRadius = radius\r\n\t\t\t\tstyle.borderTopRightRadius = radius\r\n\t\t\t\tstyle.borderBottomLeftRadius = radius\r\n\t\t\t\tstyle.borderBottomRightRadius = radius\r\n\t\t\t\treturn this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tbackToTop() {\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\tif (!this.$parent.$refs['uv-back-top']) {\r\n\t\t\t\t\tthis.$uv.error(`nvue页面需要给页面最外层元素设置\"ref='uv-back-top'`)\r\n\t\t\t\t}\r\n\t\t\t\tdom.scrollToElement(this.$parent.$refs['uv-back-top'], {\r\n\t\t\t\t\toffset: 0\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\r\n\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\tuni.pageScrollTo({\r\n\t\t\t\t\tscrollTop: 0,\r\n\t\t\t\t\tduration: this.duration\r\n\t\t\t\t});\r\n\t\t\t\t// #endif\r\n\t\t\t\tthis.$emit('click')\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t$uv-back-top-flex: 1 !default;\r\n\t$uv-back-top-height: 100% !default;\r\n\t$uv-back-top-background-color: #E1E1E1 !default;\r\n\t$uv-back-top-tips-font-size: 12px !default;\r\n\t.uv-back-top {\r\n\t\t@include flex;\r\n\t\tflex-direction: column;\r\n\t\talign-items: center;\r\n\t\tflex: $uv-back-top-flex;\r\n\t\theight: $uv-back-top-height;\r\n\t\tjustify-content: center;\r\n\t\tbackground-color: $uv-back-top-background-color;\r\n\t\t&__tips {\r\n\t\t\tfont-size: $uv-back-top-tips-font-size;\r\n\t\t\ttransform: scale(0.8);\r\n\t\t}\r\n\t}\r\n</style>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-back-top/package.json",
    "content": "{\r\n\t\"id\": \"uv-back-top\",\r\n\t\"displayName\": \"uv-back-top 返回顶部  全面兼容小程序、nvue、vue2、vue3等多端\",\r\n\t\"version\": \"1.0.2\",\r\n\t\"description\": \"返回顶部 组件一个用于长页面，滑动一定距离后，出现返回顶部按钮，方便快速返回顶部的场景。\",\r\n\t\"keywords\": [\r\n        \"uv-back-top\",\r\n        \"uvui\",\r\n        \"uv-ui\",\r\n        \"avatar\",\r\n        \"返回顶部\"\r\n    ],\r\n\t\"repository\": \"\",\r\n\t\"engines\": {\r\n\t\t\"HBuilderX\": \"^3.1.0\"\r\n\t},\r\n\t\"dcloudext\": {\r\n\t\t\"type\": \"component-vue\",\r\n\t\t\"sale\": {\r\n\t\t\t\"regular\": {\r\n\t\t\t\t\"price\": \"0.00\"\r\n\t\t\t},\r\n\t\t\t\"sourcecode\": {\r\n\t\t\t\t\"price\": \"0.00\"\r\n\t\t\t}\r\n\t\t},\r\n\t\t\"contact\": {\r\n\t\t\t\"qq\": \"\"\r\n\t\t},\r\n\t\t\"declaration\": {\r\n\t\t\t\"ads\": \"无\",\r\n\t\t\t\"data\": \"插件不采集任何数据\",\r\n\t\t\t\"permissions\": \"无\"\r\n\t\t},\r\n\t\t\"npmurl\": \"\"\r\n\t},\r\n\t\"uni_modules\": {\r\n\t\t\"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-icon\",\r\n\t\t\t\"uv-transition\"\r\n\t\t],\r\n\t\t\"encrypt\": [],\r\n\t\t\"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-back-top/readme.md",
    "content": "## BackTop 返回顶部\r\n\r\n> **组件名：uv-back-top**\r\n\r\n该组件一个用于长页面，滑动一定距离后，出现返回顶部按钮，方便快速返回顶部的场景。\r\n\r\n### <a href=\"https://www.uvui.cn/components/backTop.html\" target=\"_blank\">查看文档</a>\r\n\r\n### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n#### 如使用过程中有任何问题，或者您对uv-ui有一些好的建议，欢迎加入 uv-ui 交流群：<a href=\"https://ext.dcloud.net.cn/plugin?id=12287\" target=\"_blank\">uv-ui</a>、<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-badge/changelog.md",
    "content": "## 1.0.2（2023-06-04）\n1. 修复type等属性为null的时候不显示徽标的BUG\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-badge 徽标数，数字角标\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-badge/components/uv-badge/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 是否显示圆点\r\n\t\tisDot: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 显示的内容\r\n\t\tvalue: {\r\n\t\t\ttype: [Number, String],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 是否显示\r\n\t\tshow: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 最大值，超过最大值会显示 '{max}+'\r\n\t\tmax: {\r\n\t\t\ttype: [Number, String],\r\n\t\t\tdefault: 999\r\n\t\t},\r\n\t\t// 主题类型，error|warning|success|primary\r\n\t\ttype: {\r\n\t\t\ttype: [String,undefined,null],\r\n\t\t\tdefault: 'error'\r\n\t\t},\r\n\t\t// 当数值为 0 时，是否展示 Badge\r\n\t\tshowZero: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 背景颜色，优先级比type高，如设置，type参数会失效\r\n\t\tbgColor: {\r\n\t\t\ttype: [String, null],\r\n\t\t\tdefault: null\r\n\t\t},\r\n\t\t// 字体颜色\r\n\t\tcolor: {\r\n\t\t\ttype: [String, null],\r\n\t\t\tdefault: null\r\n\t\t},\r\n\t\t// 徽标形状，circle-四角均为圆角，horn-左下角为直角\r\n\t\tshape: {\r\n\t\t\ttype: [String,undefined,null],\r\n\t\t\tdefault: 'circle'\r\n\t\t},\r\n\t\t// 设置数字的显示方式，overflow|ellipsis|limit\r\n\t\t// overflow会根据max字段判断，超出显示`${max}+`\r\n\t\t// ellipsis会根据max判断，超出显示`${max}...`\r\n\t\t// limit会依据1000作为判断条件，超出1000，显示`${value/1000}K`，比如2.2k、3.34w，最多保留2位小数\r\n\t\tnumberType: {\r\n\t\t\ttype: [String,undefined,null],\r\n\t\t\tdefault: 'overflow'\r\n\t\t},\r\n\t\t// 设置badge的位置偏移，格式为 [x, y]，也即设置的为top和right的值，absolute为true时有效\r\n\t\toffset: {\r\n\t\t\ttype: Array,\r\n\t\t\tdefault: () => []\r\n\t\t},\r\n\t\t// 是否反转背景和字体颜色\r\n\t\tinverted: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否绝对定位\r\n\t\tabsolute: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t...uni.$uv?.props?.badge\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-badge/components/uv-badge/uv-badge.vue",
    "content": "<template>\r\n\t<text\r\n\t\tv-if=\"show && ((Number(value) === 0 ? showZero : true) || isDot)\"\r\n\t\t:class=\"[isDot ? 'uv-badge--dot' : 'uv-badge--not-dot', inverted && 'uv-badge--inverted', shape === 'horn' && 'uv-badge--horn', `uv-badge--${propsType}${inverted ? '--inverted' : ''}`]\"\r\n\t\t:style=\"[$uv.addStyle(customStyle), badgeStyle]\"\r\n\t\tclass=\"uv-badge\"\r\n\t>{{ isDot ? '' :showValue }}</text>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * badge 徽标数\r\n\t * @description 该组件一般用于图标右上角显示未读的消息数量，提示用户点击，有圆点和圆包含文字两种形式。\r\n\t * @tutorial https://www.uvui.cn/components/badge.html\r\n\t * \r\n\t * @property {Boolean} \t\t\tisDot \t\t是否显示圆点 （默认 false ）\r\n\t * @property {String | Number} \tvalue \t\t显示的内容\r\n\t * @property {Boolean} \t\t\tshow \t\t是否显示 （默认 true ）\r\n\t * @property {String | Number} \tmax \t\t最大值，超过最大值会显示 '{max}+'  （默认999）\r\n\t * @property {String} \t\t\ttype \t\t主题类型，error|warning|success|primary （默认 'error' ）\r\n\t * @property {Boolean} \t\t\tshowZero\t当数值为 0 时，是否展示 Badge （默认 false ）\r\n\t * @property {String} \t\t\tbgColor \t背景颜色，优先级比type高，如设置，type参数会失效\r\n\t * @property {String} \t\t\tcolor \t\t字体颜色 （默认 '#ffffff' ）\r\n\t * @property {String} \t\t\tshape \t\t徽标形状，circle-四角均为圆角，horn-左下角为直角 （默认 'circle' ）\r\n\t * @property {String} \t\t\tnumberType\t设置数字的显示方式，overflow|ellipsis|limit  （默认 'overflow' ）\r\n\t * @property {Array}} \t\t\toffset\t\t设置badge的位置偏移，格式为 [x, y]，也即设置的为top和right的值，absolute为true时有效\r\n\t * @property {Boolean} \t\t\tinverted\t是否反转背景和字体颜色（默认 false ）\r\n\t * @property {Boolean} \t\t\tabsolute\t是否绝对定位（默认 false ）\r\n\t * @property {Object}\t\t\tcustomStyle\t定义需要用到的外部样式\r\n\t * @example <uv-badge :type=\"type\" :count=\"count\"></uv-badge>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-badge',\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tcomputed: {\r\n\t\t\t// 是否将badge中心与父组件右上角重合\r\n\t\t\tboxStyle() {\r\n\t\t\t\tlet style = {};\r\n\t\t\t\treturn style;\r\n\t\t\t},\r\n\t\t\t// 整个组件的样式\r\n\t\t\tbadgeStyle() {\r\n\t\t\t\tconst style = {}\r\n\t\t\t\tif(this.color) {\r\n\t\t\t\t\tstyle.color = this.color\r\n\t\t\t\t}\r\n\t\t\t\tif (this.bgColor && !this.inverted) {\r\n\t\t\t\t\tstyle.backgroundColor = this.bgColor\r\n\t\t\t\t}\r\n\t\t\t\tif (this.absolute) {\r\n\t\t\t\t\tstyle.position = 'absolute'\r\n\t\t\t\t\t// 如果有设置offset参数\r\n\t\t\t\t\tif(this.offset.length) {\r\n\t\t\t\t\t\t// top和right分为为offset的第一个和第二个值，如果没有第二个值，则right等于top\r\n\t\t\t\t\t\tconst top = this.offset[0]\r\n\t\t\t\t\t\tconst right = this.offset[1] || top\r\n\t\t\t\t\t\tstyle.top = this.$uv.addUnit(top)\r\n\t\t\t\t\t\tstyle.right = this.$uv.addUnit(right)\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\treturn style\r\n\t\t\t},\r\n\t\t\tshowValue() {\r\n\t\t\t\tswitch (this.numberType) {\r\n\t\t\t\t\tcase \"overflow\":\r\n\t\t\t\t\t\treturn Number(this.value) > Number(this.max) ? this.max + \"+\" : this.value\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase \"ellipsis\":\r\n\t\t\t\t\t\treturn Number(this.value) > Number(this.max) ? \"...\" : this.value\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase \"limit\":\r\n\t\t\t\t\t\treturn Number(this.value) > 999 ? Number(this.value) >= 9999 ?\r\n\t\t\t\t\t\t\tMath.floor(this.value / 1e4 * 100) / 100 + \"w\" : Math.floor(this.value /\r\n\t\t\t\t\t\t\t\t1e3 * 100) / 100 + \"k\" : this.value\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tdefault:\r\n\t\t\t\t\t\treturn Number(this.value)\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tpropsType(){\r\n\t\t\t\treturn this.type || 'error'\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t$uv-badge-primary: $uv-primary !default;\r\n\t$uv-badge-error: $uv-error !default;\r\n\t$uv-badge-success: $uv-success !default;\r\n\t$uv-badge-info: $uv-info !default;\r\n\t$uv-badge-warning: $uv-warning !default;\r\n\t$uv-badge-dot-radius: 100px !default;\r\n\t$uv-badge-dot-size: 8px !default;\r\n\t$uv-badge-dot-right: 4px !default;\r\n\t$uv-badge-dot-top: 0 !default;\r\n\t$uv-badge-text-font-size: 11px !default;\r\n\t$uv-badge-text-right: 10px !default;\r\n\t$uv-badge-text-padding: 2px 5px !default;\r\n\t$uv-badge-text-align: center !default;\r\n\t$uv-badge-text-color: #FFFFFF !default;\r\n\r\n\t.uv-badge {\r\n\t\tborder-top-right-radius: $uv-badge-dot-radius;\r\n\t\tborder-top-left-radius: $uv-badge-dot-radius;\r\n\t\tborder-bottom-left-radius: $uv-badge-dot-radius;\r\n\t\tborder-bottom-right-radius: $uv-badge-dot-radius;\r\n\t\t@include flex;\r\n\t\tline-height: $uv-badge-text-font-size;\r\n\t\ttext-align: $uv-badge-text-align;\r\n\t\tfont-size: $uv-badge-text-font-size;\r\n\t\tcolor: $uv-badge-text-color;\r\n\r\n\t\t&--dot {\r\n\t\t\theight: $uv-badge-dot-size;\r\n\t\t\twidth: $uv-badge-dot-size;\r\n\t\t}\r\n\t\t\r\n\t\t&--inverted {\r\n\t\t\tfont-size: 13px;\r\n\t\t}\r\n\t\t\r\n\t\t&--not-dot {\r\n\t\t\tpadding: $uv-badge-text-padding;\r\n\t\t}\r\n\r\n\t\t&--horn {\r\n\t\t\tborder-bottom-left-radius: 0;\r\n\t\t}\r\n\r\n\t\t&--primary {\r\n\t\t\tbackground-color: $uv-badge-primary;\r\n\t\t}\r\n\t\t\r\n\t\t&--primary--inverted {\r\n\t\t\tcolor: $uv-badge-primary;\r\n\t\t}\r\n\r\n\t\t&--error {\r\n\t\t\tbackground-color: $uv-badge-error;\r\n\t\t}\r\n\t\t\r\n\t\t&--error--inverted {\r\n\t\t\tcolor: $uv-badge-error;\r\n\t\t}\r\n\r\n\t\t&--success {\r\n\t\t\tbackground-color: $uv-badge-success;\r\n\t\t}\r\n\t\t\r\n\t\t&--success--inverted {\r\n\t\t\tcolor: $uv-badge-success;\r\n\t\t}\r\n\r\n\t\t&--info {\r\n\t\t\tbackground-color: $uv-badge-info;\r\n\t\t}\r\n\t\t\r\n\t\t&--info--inverted {\r\n\t\t\tcolor: $uv-badge-info;\r\n\t\t}\r\n\r\n\t\t&--warning {\r\n\t\t\tbackground-color: $uv-badge-warning;\r\n\t\t}\r\n\t\t\r\n\t\t&--warning--inverted {\r\n\t\t\tcolor: $uv-badge-warning;\r\n\t\t}\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-badge/package.json",
    "content": "{\r\n\t\"id\": \"uv-badge\",\r\n\t\"displayName\": \"uv-badge 徽标数，数字角标 全面兼容小程序、nvue、vue2、vue3等多端\",\r\n\t\"version\": \"1.0.2\",\r\n\t\"description\": \"徽标数一般用于图标右上角显示未读的消息数量，提示用户点击，有圆点和圆包含文字两种形式。\",\r\n\t\"keywords\": [\r\n        \"uv-badge\",\r\n        \"uvui\",\r\n        \"uv-ui\",\r\n        \"徽标数\",\r\n        \"数字角标\"\r\n    ],\r\n\t\"repository\": \"\",\r\n\t\"engines\": {\r\n\t\t\"HBuilderX\": \"^3.1.0\"\r\n\t},\r\n\t\"dcloudext\": {\r\n\t\t\"type\": \"component-vue\",\r\n\t\t\"sale\": {\r\n\t\t\t\"regular\": {\r\n\t\t\t\t\"price\": \"0.00\"\r\n\t\t\t},\r\n\t\t\t\"sourcecode\": {\r\n\t\t\t\t\"price\": \"0.00\"\r\n\t\t\t}\r\n\t\t},\r\n\t\t\"contact\": {\r\n\t\t\t\"qq\": \"\"\r\n\t\t},\r\n\t\t\"declaration\": {\r\n\t\t\t\"ads\": \"无\",\r\n\t\t\t\"data\": \"插件不采集任何数据\",\r\n\t\t\t\"permissions\": \"无\"\r\n\t\t},\r\n\t\t\"npmurl\": \"\"\r\n\t},\r\n\t\"uni_modules\": {\r\n\t\t\"dependencies\": [\r\n\t\t\t\"uv-ui-tools\"\r\n\t\t],\r\n\t\t\"encrypt\": [],\r\n\t\t\"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-badge/readme.md",
    "content": "## Badge 徽标数\r\n\r\n> **组件名：uv-badge**\r\n\r\n该组件一般用于图标右上角显示未读的消息数量，提示用户点击，有圆点和圆包含文字两种形式。\r\n\r\n### <a href=\"https://www.uvui.cn/components/badge.html\" target=\"_blank\">查看文档</a>\r\n\r\n### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n#### 如使用过程中有任何问题，或者您对uv-ui有一些好的建议，欢迎加入 uv-ui 交流群：<a href=\"https://ext.dcloud.net.cn/plugin?id=12287\" target=\"_blank\">uv-ui</a>、<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-button/changelog.md",
    "content": "## 1.0.12（2023-10-19）\n1. 增加后置插槽\n## 1.0.11（2023-09-21）\n1. 修复通过customStyle修改按钮宽度，组件中最外层节点不改变的问题\n## 1.0.10（2023-09-15）\n1. 按钮支持open-type=\"agreePrivacyAuthorization\"\n## 1.0.9（2023-09-11）\n1. 增加参数iconSize，用于控制图标的大小\n## 1.0.8（2023-09-10）\r\n1. 修复多个按钮在一行宽度不正常的BUG\r\n## 1.0.7（2023-09-07）\r\n1. 修复warning颜色对应错误的BUG\r\n## 1.0.6（2023-07-25）\r\n1. 增加customTextStyle属性，方便自定义文字样式\r\n## 1.0.5（2023-07-20）\r\n1. 解决微信小程序动态设置hover-class点击态不消失的BUG\r\n## 1.0.4（2023-06-29）\r\n1. 修改上次更新出现nvue报错异常\r\n## 1.0.3（2023-06-28）\r\n 修复：设置open-type=\"chooseAvatar\"等值不生效的BUG\r\n## 1.0.2（2023-06-01）\r\n1. 修复按钮点击触发两次的BUG\r\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-button 按钮\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-button/components/uv-button/nvue.scss",
    "content": "$uv-button-active-opacity:0.75 !default;\r\n$uv-button-loading-text-margin-left:4px !default;\r\n$uv-button-text-color: #FFFFFF !default;\r\n$uv-button-text-plain-error-color:$uv-error !default;\r\n$uv-button-text-plain-warning-color:$uv-warning !default;\r\n$uv-button-text-plain-success-color:$uv-success !default;\r\n$uv-button-text-plain-info-color:$uv-info !default;\r\n$uv-button-text-plain-primary-color:$uv-primary !default;\r\n.uv-button {\r\n\t&--active {\r\n\t\topacity: $uv-button-active-opacity;\r\n\t}\r\n\t\r\n\t&--active--plain {\r\n\t\tbackground-color: rgb(217, 217, 217);\r\n\t}\r\n\t\r\n\t&__loading-text {\r\n\t\tmargin-left:$uv-button-loading-text-margin-left;\r\n\t}\r\n\t\r\n\t&__text,\r\n\t&__loading-text {\r\n\t\tcolor:$uv-button-text-color;\r\n\t}\r\n\t\r\n\t&__text--plain--error {\r\n\t\tcolor:$uv-button-text-plain-error-color;\r\n\t}\r\n\t\r\n\t&__text--plain--warning {\r\n\t\tcolor:$uv-button-text-plain-warning-color;\r\n\t}\r\n\t\r\n\t&__text--plain--success{\r\n\t\tcolor:$uv-button-text-plain-success-color;\r\n\t}\r\n\t\r\n\t&__text--plain--info {\r\n\t\tcolor:$uv-button-text-plain-info-color;\r\n\t}\r\n\t\r\n\t&__text--plain--primary {\r\n\t\tcolor:$uv-button-text-plain-primary-color;\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-button/components/uv-button/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 是否细边框\r\n\t\thairline: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 按钮的预置样式，info，primary，error，warning，success\r\n\t\ttype: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'info'\r\n\t\t},\r\n\t\t// 按钮尺寸，large，normal，small，mini\r\n\t\tsize: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'normal'\r\n\t\t},\r\n\t\t// 按钮形状，circle（两边为半圆），square（带圆角）\r\n\t\tshape: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'square'\r\n\t\t},\r\n\t\t// 按钮是否镂空\r\n\t\tplain: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否禁止状态\r\n\t\tdisabled: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否加载中\r\n\t\tloading: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 加载中提示文字\r\n\t\tloadingText: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 加载状态图标类型\r\n\t\tloadingMode: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'spinner'\r\n\t\t},\r\n\t\t// 加载图标大小\r\n\t\tloadingSize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 14\r\n\t\t},\r\n\t\t// 开放能力，具体请看uniapp稳定关于button组件部分说明\r\n\t\t// https://uniapp.dcloud.io/component/button\r\n\t\topenType: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 用于 <form> 组件，点击分别会触发 <form> 组件的 submit/reset 事件\r\n\t\t// 取值为submit（提交表单），reset（重置表单）\r\n\t\tformType: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 打开 APP 时，向 APP 传递的参数，open-type=launchApp时有效\r\n\t\t// 只微信小程序、QQ小程序有效\r\n\t\tappParameter: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 指定是否阻止本节点的祖先节点出现点击态，微信小程序有效\r\n\t\thoverStopPropagation: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 指定返回用户信息的语言，zh_CN 简体中文，zh_TW 繁体中文，en 英文。只微信小程序有效\r\n\t\tlang: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'en'\r\n\t\t},\r\n\t\t// 会话来源，open-type=\"contact\"时有效。只微信小程序有效\r\n\t\tsessionFrom: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 会话内消息卡片标题，open-type=\"contact\"时有效\r\n\t\t// 默认当前标题，只微信小程序有效\r\n\t\tsendMessageTitle: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 会话内消息卡片点击跳转小程序路径，open-type=\"contact\"时有效\r\n\t\t// 默认当前分享路径，只微信小程序有效\r\n\t\tsendMessagePath: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 会话内消息卡片图片，open-type=\"contact\"时有效\r\n\t\t// 默认当前页面截图，只微信小程序有效\r\n\t\tsendMessageImg: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 是否显示会话内消息卡片，设置此参数为 true，用户进入客服会话会在右下角显示\"可能要发送的小程序\"提示，\r\n\t\t// 用户点击后可以快速发送小程序消息，open-type=\"contact\"时有效\r\n\t\tshowMessageCard: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 额外传参参数，用于小程序的data-xxx属性，通过target.dataset.name获取\r\n\t\tdataName: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 节流，一定时间内只能触发一次\r\n\t\tthrottleTime: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 按住后多久出现点击态，单位毫秒\r\n\t\thoverStartTime: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 手指松开后点击态保留时间，单位毫秒\r\n\t\thoverStayTime: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 200\r\n\t\t},\r\n\t\t// 按钮文字，之所以通过props传入，是因为slot传入的话\r\n\t\t// nvue中无法控制文字的样式\r\n\t\ttext: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 按钮图标\r\n\t\ticon: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 按钮图标大小\r\n\t\ticonSize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 按钮图标颜色\r\n\t\ticonColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#000000'\r\n\t\t},\r\n\t\t// 按钮颜色，支持传入linear-gradient渐变色\r\n\t\tcolor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 自定义按钮文本样式\r\n\t\tcustomTextStyle: {\r\n\t\t\ttype: [Object,String],\r\n\t\t\tdefault: ()=>{}\r\n\t\t},\r\n\t\t...uni.$uv?.props?.button\r\n\t}\r\n}\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-button/components/uv-button/uv-button.vue",
    "content": "<template>\r\n\t<view \r\n\t\tclass=\"uv-button-wrapper\"\r\n\t\t:style=\"[btnWrapperStyle]\"\r\n\t>\r\n    <!-- #ifndef APP-NVUE -->\r\n\t\t<!-- #ifdef MP -->\r\n\t\t<!-- 为了解决微信小程序动态设置hover-class点击态不消失的BUG -->\r\n\t\t<view class=\"uv-button-wrapper--dis\" v-if=\"disabled || loading\"></view>\r\n\t\t<button\r\n\t\t  :hover-start-time=\"Number(hoverStartTime)\"\r\n\t\t  :hover-stay-time=\"Number(hoverStayTime)\"\r\n\t\t  :form-type=\"formType\"\r\n\t\t  :open-type=\"openType\"\r\n\t\t  :app-parameter=\"appParameter\"\r\n\t\t  :hover-stop-propagation=\"hoverStopPropagation\"\r\n\t\t  :send-message-title=\"sendMessageTitle\"\r\n\t\t  :send-message-path=\"sendMessagePath\"\r\n\t\t  :lang=\"lang\"\r\n\t\t  :data-name=\"dataName\"\r\n\t\t  :session-from=\"sessionFrom\"\r\n\t\t  :send-message-img=\"sendMessageImg\"\r\n\t\t  :show-message-card=\"showMessageCard\"\r\n\t\t  @getphonenumber=\"onGetPhoneNumber\"\r\n\t\t  @getuserinfo=\"onGetUserInfo\"\r\n\t\t  @error=\"onError\"\r\n\t\t  @opensetting=\"onOpenSetting\"\r\n\t\t  @launchapp=\"onLaunchApp\"\r\n\t\t\t@contact=\"onContact\"\r\n\t\t\t@chooseavatar=\"onChooseavatar\"\r\n\t\t\t@agreeprivacyauthorization=\"onAgreeprivacyauthorization\"\r\n\t\t\t@addgroupapp=\"onAddgroupapp\"\r\n\t\t\t@chooseaddress=\"onChooseaddress\"\r\n\t\t\t@subscribe=\"onSubscribe\"\r\n\t\t\t@login=\"onLogin\"\r\n\t\t\t@im=\"onIm\"\r\n\t\t  hover-class=\"uv-button--active\"\r\n\t\t  class=\"uv-button uv-reset-button\"\r\n\t\t  :style=\"[baseColor, $uv.addStyle(customStyle)]\"\r\n\t\t  @tap=\"clickHandler\"\r\n\t\t  :class=\"bemClass\"\r\n\t\t>\r\n\t\t<!-- #endif -->\r\n    <!-- #ifndef MP -->\r\n    <button\r\n      :hover-start-time=\"Number(hoverStartTime)\"\r\n      :hover-stay-time=\"Number(hoverStayTime)\"\r\n      :form-type=\"formType\"\r\n      :open-type=\"openType\"\r\n      :app-parameter=\"appParameter\"\r\n      :hover-stop-propagation=\"hoverStopPropagation\"\r\n      :send-message-title=\"sendMessageTitle\"\r\n      :send-message-path=\"sendMessagePath\"\r\n      :lang=\"lang\"\r\n      :data-name=\"dataName\"\r\n      :session-from=\"sessionFrom\"\r\n      :send-message-img=\"sendMessageImg\"\r\n      :show-message-card=\"showMessageCard\"\r\n      :hover-class=\"!disabled && !loading ? 'uv-button--active' : ''\"\r\n      class=\"uv-button uv-reset-button\"\r\n      :style=\"[baseColor, $uv.addStyle(customStyle)]\"\r\n      @tap=\"clickHandler\"\r\n      :class=\"bemClass\"\r\n    >\r\n    <!-- #endif -->\r\n      <template v-if=\"loading\">\r\n        <uv-loading-icon\r\n          :mode=\"loadingMode\"\r\n          :size=\"loadingSize * 1.15\"\r\n          :color=\"loadingColor\"\r\n        ></uv-loading-icon>\r\n          <text\r\n            class=\"uv-button__loading-text\"\r\n            :style=\"[\r\n\t\t\t\t\t\t\t{ fontSize: textSize + 'px' },\r\n\t\t\t\t\t\t\t$uv.addStyle(customTextStyle)\r\n\t\t\t\t\t\t]\"\r\n          >{{ loadingText || text }}</text>\r\n      </template>\r\n      <template v-else>\r\n        <uv-icon\r\n          v-if=\"icon\"\r\n          :name=\"icon\"\r\n          :color=\"iconColorCom\"\r\n          :size=\"getIconSize\"\r\n          :customStyle=\"{ marginRight: '2px' }\"\r\n        ></uv-icon>\r\n        <slot>\r\n          <text\r\n            class=\"uv-button__text\"\r\n            :style=\"[\r\n\t\t\t\t\t\t\t{ fontSize: textSize + 'px' },\r\n\t\t\t\t\t\t\t$uv.addStyle(customTextStyle)\r\n\t\t\t\t\t\t]\"\r\n            >{{ text }}</text>\r\n        </slot>\r\n\t\t\t\t<slot name=\"suffix\"></slot>\r\n      </template>\r\n    </button>\r\n    <!-- #endif -->\r\n    <!-- #ifdef APP-NVUE -->\r\n    <view\r\n      :hover-start-time=\"Number(hoverStartTime)\"\r\n      :hover-stay-time=\"Number(hoverStayTime)\"\r\n      class=\"uv-button\"\r\n      :hover-class=\"\r\n        !disabled && !loading && !color && (plain || type === 'info')\r\n          ? 'uv-button--active--plain'\r\n          : !disabled && !loading && !plain\r\n          ? 'uv-button--active'\r\n          : ''\r\n      \"\r\n      @tap=\"clickHandler\"\r\n      :class=\"bemClass\"\r\n      :style=\"[baseColor, $uv.addStyle(customStyle)]\"\r\n    >\r\n      <template v-if=\"loading\">\r\n        <uv-loading-icon\r\n          :mode=\"loadingMode\"\r\n          :size=\"loadingSize * 1.15\"\r\n          :color=\"loadingColor\"\r\n        ></uv-loading-icon>\r\n        <text\r\n          class=\"uv-button__loading-text\"\r\n          :style=\"[nvueTextStyle,$uv.addStyle(customTextStyle)]\"\r\n          :class=\"[plain && `uv-button__text--plain--${type}`]\"\r\n          >{{ loadingText || text }}</text>\r\n      </template>\r\n      <template v-else>\r\n        <uv-icon\r\n          v-if=\"icon\"\r\n          :name=\"icon\"\r\n          :color=\"iconColorCom\"\r\n          :size=\"getIconSize\"\r\n        ></uv-icon>\r\n        <text\r\n          class=\"uv-button__text\"\r\n          :style=\"[\r\n            {\r\n              marginLeft: icon ? '2px' : 0,\r\n            },\r\n            nvueTextStyle,\r\n\t\t\t\t\t\t$uv.addStyle(customTextStyle)\r\n          ]\"\r\n          :class=\"[plain && `uv-button__text--plain--${type}`]\"\r\n          >{{ text }}</text>\r\n\t\t\t\t<slot name=\"suffix\"></slot>\r\n      </template>\r\n    </view>\r\n    <!-- #endif -->\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\nimport throttle from '@/uni_modules/uv-ui-tools/libs/function/throttle.js';\r\nimport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\nimport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\nimport button from '@/uni_modules/uv-ui-tools/libs/mixin/button.js'\r\nimport openType from '@/uni_modules/uv-ui-tools/libs/mixin/openType.js'\r\nimport props from \"./props.js\";\r\n/**\r\n * button 按钮\r\n * @description Button 按钮\r\n * @tutorial https://www.uvui.cn/components/button.html\r\n * @property {Boolean}\t\t\thairline\t\t\t\t是否显示按钮的细边框 (默认 true )\r\n * @property {String}\t\t\ttype\t\t\t\t\t按钮的预置样式，info，primary，error，warning，success (默认 'info' )\r\n * @property {String}\t\t\tsize\t\t\t\t\t按钮尺寸，large，normal，mini （默认 normal）\r\n * @property {String}\t\t\tshape\t\t\t\t\t按钮形状，circle（两边为半圆），square（带圆角） （默认 'square' ）\r\n * @property {Boolean}\t\t\tplain\t\t\t\t\t按钮是否镂空，背景色透明 （默认 false）\r\n * @property {Boolean}\t\t\tdisabled\t\t\t\t是否禁用 （默认 false）\r\n * @property {Boolean}\t\t\tloading\t\t\t\t\t按钮名称前是否带 loading 图标(App-nvue 平台，在 ios 上为雪花，Android上为圆圈) （默认 false）\r\n * @property {String | Number}\tloadingText\t\t\t\t加载中提示文字\r\n * @property {String}\t\t\tloadingMode\t\t\t\t加载状态图标类型 （默认 'spinner' ）\r\n * @property {String | Number}\tloadingSize\t\t\t\t加载图标大小 （默认 15 ）\r\n * @property {String}\t\t\topenType\t\t\t\t开放能力，具体请看uniapp稳定关于button组件部分说明\r\n * @property {String}\t\t\tformType\t\t\t\t用于 <form> 组件，点击分别会触发 <form> 组件的 submit/reset 事件\r\n * @property {String}\t\t\tappParameter\t\t\t打开 APP 时，向 APP 传递的参数，open-type=launchApp时有效 （注：只微信小程序、QQ小程序有效）\r\n * @property {Boolean}\t\t\thoverStopPropagation\t指定是否阻止本节点的祖先节点出现点击态，微信小程序有效（默认 true ）\r\n * @property {String}\t\t\tlang\t\t\t\t\t指定返回用户信息的语言，zh_CN 简体中文，zh_TW 繁体中文，en 英文（默认 en ）\r\n * @property {String}\t\t\tsessionFrom\t\t\t\t会话来源，openType=\"contact\"时有效\r\n * @property {String}\t\t\tsendMessageTitle\t\t会话内消息卡片标题，openType=\"contact\"时有效\r\n * @property {String}\t\t\tsendMessagePath\t\t\t会话内消息卡片点击跳转小程序路径，openType=\"contact\"时有效\r\n * @property {String}\t\t\tsendMessageImg\t\t\t会话内消息卡片图片，openType=\"contact\"时有效\r\n * @property {Boolean}\t\t\tshowMessageCard\t\t\t是否显示会话内消息卡片，设置此参数为 true，用户进入客服会话会在右下角显示\"可能要发送的小程序\"提示，用户点击后可以快速发送小程序消息，openType=\"contact\"时有效（默认false）\r\n * @property {String}\t\t\tdataName\t\t\t\t额外传参参数，用于小程序的data-xxx属性，通过target.dataset.name获取\r\n * @property {String | Number}\tthrottleTime\t\t\t节流，一定时间内只能触发一次 （默认 0 )\r\n * @property {String | Number}\thoverStartTime\t\t\t按住后多久出现点击态，单位毫秒 （默认 0 )\r\n * @property {String | Number}\thoverStayTime\t\t\t手指松开后点击态保留时间，单位毫秒 （默认 200 )\r\n * @property {String | Number}\ttext\t\t\t\t\t按钮文字，之所以通过props传入，是因为slot传入的话（注：nvue中无法控制文字的样式）\r\n * @property {String}\t\t\ticon\t\t\t\t\t按钮图标\r\n * @property {String}\t\t\ticonColor\t\t\t\t按钮图标颜色\r\n * @property {String}\t\t\tcolor\t\t\t\t\t按钮颜色，支持传入linear-gradient渐变色\r\n * @property {Object}\t\t\tcustomStyle\t\t\t\t定义需要用到的外部样式\r\n * @event {Function}\tclick\t\t\t非禁止并且非加载中，才能点击\r\n * @event {Function}\tgetphonenumber\topen-type=\"getPhoneNumber\"时有效\r\n * @event {Function}\tgetuserinfo\t\t用户点击该按钮时，会返回获取到的用户信息，从返回参数的detail中获取到的值同uni.getUserInfo\r\n * @event {Function}\terror\t\t\t当使用开放能力时，发生错误的回调\r\n * @event {Function}\topensetting\t\t在打开授权设置页并关闭后回调\r\n * @event {Function}\tlaunchapp\t\t打开 APP 成功的回调\r\n * @example <uv-button>月落</uv-button>\r\n */\r\nexport default {\r\n\t\tname: \"uv-button\",\r\n\t\t// #ifdef MP\r\n\t\tmixins: [mpMixin, mixin, button, openType, props],\r\n\t\t// #endif\r\n\t\t// #ifndef MP\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\t// #endif\r\n\t\temits: ['click'],\r\n\t\tdata() {\r\n\t\t\treturn {};\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\t// 生成bem风格的类名\r\n\t\t\tbemClass() {\r\n\t\t\t\t// this.bem为一个computed变量，在mixin中\r\n\t\t\t\tif (!this.color) {\r\n\t\t\t\t\treturn this.bem(\"button\",\r\n\t\t\t\t\t\t[\"type\", \"shape\", \"size\"],\r\n\t\t\t\t\t\t[\"disabled\", \"plain\", \"hairline\"]);\r\n\t\t\t\t} else {\r\n\t\t\t\t\t// 由于nvue的原因，在有color参数时，不需要传入type，否则会生成type相关的类型，影响最终的样式\r\n\t\t\t\t\treturn this.bem(\"button\",\r\n\t\t\t\t\t\t[\"shape\", \"size\"],\r\n\t\t\t\t\t\t[\"disabled\", \"plain\", \"hairline\"]);\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tloadingColor() {\r\n\t\t\t\tif (this.plain) {\r\n\t\t\t\t\t// 如果有设置color值，则用color值，否则使用type主题颜色\r\n\t\t\t\t\treturn this.color ? this.color : '#3c9cff';\r\n\t\t\t\t}\r\n\t\t\t\tif (this.type === \"info\") {\r\n\t\t\t\t\treturn \"#c9c9c9\";\r\n\t\t\t\t}\r\n\t\t\t\treturn \"rgb(200, 200, 200)\";\r\n\t\t\t},\r\n\t\t\ticonColorCom() {\r\n\t\t\t\t// 如果是镂空状态，设置了color就用color值，否则使用主题颜色，\r\n\t\t\t\t// uv-icon的color能接受一个主题颜色的值\r\n\t\t\t\tif (this.iconColor) return this.iconColor;\r\n\t\t\t\tif (this.plain) {\r\n\t\t\t\t\treturn this.color ? this.color : this.type;\r\n\t\t\t\t} else {\r\n\t\t\t\t\treturn this.type === \"info\" ? \"#000000\" : \"#ffffff\";\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tbaseColor() {\r\n\t\t\t\tlet style = {};\r\n\t\t\t\tif (this.color) {\r\n\t\t\t\t\t// 针对自定义了color颜色的情况，镂空状态下，就是用自定义的颜色\r\n\t\t\t\t\tstyle.color = this.plain ? this.color : \"white\";\r\n\t\t\t\t\tif (!this.plain) {\r\n\t\t\t\t\t\t// 非镂空，背景色使用自定义的颜色\r\n\t\t\t\t\t\tstyle[\"background-color\"] = this.color;\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (this.color.indexOf(\"gradient\") !== -1) {\r\n\t\t\t\t\t\t// 如果自定义的颜色为渐变色，不显示边框，以及通过backgroundImage设置渐变色\r\n\t\t\t\t\t\t// weex文档说明可以写borderWidth的形式，为什么这里需要分开写？\r\n\t\t\t\t\t\t// 因为weex是阿里巴巴为了部门业绩考核而做的你懂的东西，所以需要这么写才有效\r\n\t\t\t\t\t\tstyle.borderTopWidth = 0;\r\n\t\t\t\t\t\tstyle.borderRightWidth = 0;\r\n\t\t\t\t\t\tstyle.borderBottomWidth = 0;\r\n\t\t\t\t\t\tstyle.borderLeftWidth = 0;\r\n\t\t\t\t\t\tif (!this.plain) {\r\n\t\t\t\t\t\t\tstyle.backgroundImage = this.color;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\t// 非渐变色，则设置边框相关的属性\r\n\t\t\t\t\t\tstyle.borderColor = this.color;\r\n\t\t\t\t\t\tstyle.borderWidth = \"1px\";\r\n\t\t\t\t\t\tstyle.borderStyle = \"solid\";\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\treturn style;\r\n\t\t\t},\r\n\t\t\t// nvue版本按钮的字体不会继承父组件的颜色，需要对每一个text组件进行单独的设置\r\n\t\t\tnvueTextStyle() {\r\n\t\t\t\tlet style = {};\r\n\t\t\t\t// 针对自定义了color颜色的情况，镂空状态下，就是用自定义的颜色\r\n\t\t\t\tif (this.type === \"info\") {\r\n\t\t\t\t\tstyle.color = \"#323233\";\r\n\t\t\t\t}\r\n\t\t\t\tif (this.color) {\r\n\t\t\t\t\tstyle.color = this.plain ? this.color : \"white\";\r\n\t\t\t\t}\r\n\t\t\t\tstyle.fontSize = this.textSize + \"px\";\r\n\t\t\t\treturn style;\r\n\t\t\t},\r\n\t\t\t// 字体大小\r\n\t\t\ttextSize() {\r\n\t\t\t\tlet fontSize = 14,\r\n\t\t\t\t\t{ size } = this;\r\n\t\t\t\tif (size === \"large\") fontSize = 16;\r\n\t\t\t\tif (size === \"normal\") fontSize = 14;\r\n\t\t\t\tif (size === \"small\") fontSize = 12;\r\n\t\t\t\tif (size === \"mini\") fontSize = 10;\r\n\t\t\t\treturn fontSize;\r\n\t\t\t},\r\n\t\t\t// 设置图标大小\r\n\t\t\tgetIconSize() {\r\n\t\t\t\tconst size = this.iconSize ? this.iconSize : this.textSize * 1.35;\r\n\t\t\t\treturn this.$uv.addUnit(size);\r\n\t\t\t},\r\n\t\t\t// 设置外层盒子的宽度，其他样式不需要\r\n\t\t\tbtnWrapperStyle() {\r\n\t\t\t\tconst style = {};\r\n\t\t\t\tconst customStyle = this.$uv.addStyle(this.customStyle);\r\n\t\t\t\tif(customStyle.width) style.width = customStyle.width;\r\n\t\t\t\treturn style;\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tclickHandler() {\r\n\t\t\t\t// 非禁止并且非加载中，才能点击\r\n\t\t\t\tif (!this.disabled && !this.loading) {\r\n\t\t\t\t\t// 进行节流控制，每this.throttle毫秒内，只在开始处执行\r\n\t\t\t\t\tthrottle(() => {\r\n\t\t\t\t\t\tthis.$emit(\"click\");\r\n\t\t\t\t\t}, this.throttleTime);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n$show-reset-button: 1;\r\n@import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';\r\n@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\r\n/* #ifndef APP-NVUE */\r\n@import \"./vue.scss\";\r\n/* #endif */\r\n\r\n/* #ifdef APP-NVUE */\r\n@import \"./nvue.scss\";\r\n/* #endif */\r\n\r\n$uv-button-uv-button-height: 40px !default;\r\n$uv-button-text-font-size: 15px !default;\r\n$uv-button-loading-text-font-size: 15px !default;\r\n$uv-button-loading-text-margin-left: 4px !default;\r\n$uv-button-large-width: 100% !default;\r\n$uv-button-large-height: 50px !default;\r\n$uv-button-normal-padding: 0 12px !default;\r\n$uv-button-large-padding: 0 15px !default;\r\n$uv-button-normal-font-size: 14px !default;\r\n$uv-button-small-min-width: 60px !default;\r\n$uv-button-small-height: 30px !default;\r\n$uv-button-small-padding: 0px 8px !default;\r\n$uv-button-mini-padding: 0px 8px !default;\r\n$uv-button-small-font-size: 12px !default;\r\n$uv-button-mini-height: 22px !default;\r\n$uv-button-mini-font-size: 10px !default;\r\n$uv-button-mini-min-width: 50px !default;\r\n$uv-button-disabled-opacity: 0.5 !default;\r\n$uv-button-info-color: #323233 !default;\r\n$uv-button-info-background-color: #fff !default;\r\n$uv-button-info-border-color: #ebedf0 !default;\r\n$uv-button-info-border-width: 1px !default;\r\n$uv-button-info-border-style: solid !default;\r\n$uv-button-success-color: #fff !default;\r\n$uv-button-success-background-color: $uv-success !default;\r\n$uv-button-success-border-color: $uv-button-success-background-color !default;\r\n$uv-button-success-border-width: 1px !default;\r\n$uv-button-success-border-style: solid !default;\r\n$uv-button-primary-color: #fff !default;\r\n$uv-button-primary-background-color: $uv-primary !default;\r\n$uv-button-primary-border-color: $uv-button-primary-background-color !default;\r\n$uv-button-primary-border-width: 1px !default;\r\n$uv-button-primary-border-style: solid !default;\r\n$uv-button-error-color: #fff !default;\r\n$uv-button-error-background-color: $uv-error !default;\r\n$uv-button-error-border-color: $uv-button-error-background-color !default;\r\n$uv-button-error-border-width: 1px !default;\r\n$uv-button-error-border-style: solid !default;\r\n$uv-button-warning-color: #fff !default;\r\n$uv-button-warning-background-color: $uv-warning !default;\r\n$uv-button-warning-border-color: $uv-button-warning-background-color !default;\r\n$uv-button-warning-border-width: 1px !default;\r\n$uv-button-warning-border-style: solid !default;\r\n$uv-button-block-width: 100% !default;\r\n$uv-button-circle-border-top-right-radius: 100px !default;\r\n$uv-button-circle-border-top-left-radius: 100px !default;\r\n$uv-button-circle-border-bottom-left-radius: 100px !default;\r\n$uv-button-circle-border-bottom-right-radius: 100px !default;\r\n$uv-button-square-border-top-right-radius: 3px !default;\r\n$uv-button-square-border-top-left-radius: 3px !default;\r\n$uv-button-square-border-bottom-left-radius: 3px !default;\r\n$uv-button-square-border-bottom-right-radius: 3px !default;\r\n$uv-button-icon-min-width: 1em !default;\r\n$uv-button-plain-background-color: #fff !default;\r\n$uv-button-hairline-border-width: 0.5px !default;\r\n\r\n.uv-button {\r\n    height: $uv-button-uv-button-height;\r\n    position: relative;\r\n    align-items: center;\r\n    justify-content: center;\r\n    @include flex;\r\n    /* #ifndef APP-NVUE */\r\n    box-sizing: border-box;\r\n    /* #endif */\r\n    flex-direction: row;\r\n\r\n    &__text {\r\n        font-size: $uv-button-text-font-size;\r\n    }\r\n\r\n    &__loading-text {\r\n        font-size: $uv-button-loading-text-font-size;\r\n        margin-left: $uv-button-loading-text-margin-left;\r\n    }\r\n\r\n    &--large {\r\n        /* #ifndef APP-NVUE */\r\n        width: $uv-button-large-width;\r\n        /* #endif */\r\n        height: $uv-button-large-height;\r\n        padding: $uv-button-large-padding;\r\n    }\r\n\r\n    &--normal {\r\n        padding: $uv-button-normal-padding;\r\n        font-size: $uv-button-normal-font-size;\r\n    }\r\n\r\n    &--small {\r\n        /* #ifndef APP-NVUE */\r\n        min-width: $uv-button-small-min-width;\r\n        /* #endif */\r\n        height: $uv-button-small-height;\r\n        padding: $uv-button-small-padding;\r\n        font-size: $uv-button-small-font-size;\r\n    }\r\n\r\n    &--mini {\r\n        height: $uv-button-mini-height;\r\n        font-size: $uv-button-mini-font-size;\r\n        /* #ifndef APP-NVUE */\r\n        min-width: $uv-button-mini-min-width;\r\n        /* #endif */\r\n        padding: $uv-button-mini-padding;\r\n    }\r\n\r\n    &--disabled {\r\n        opacity: $uv-button-disabled-opacity;\r\n    }\r\n\r\n    &--info {\r\n        color: $uv-button-info-color;\r\n        background-color: $uv-button-info-background-color;\r\n        border-color: $uv-button-info-border-color;\r\n        border-width: $uv-button-info-border-width;\r\n        border-style: $uv-button-info-border-style;\r\n    }\r\n\r\n    &--success {\r\n        color: $uv-button-success-color;\r\n        background-color: $uv-button-success-background-color;\r\n        border-color: $uv-button-success-border-color;\r\n        border-width: $uv-button-success-border-width;\r\n        border-style: $uv-button-success-border-style;\r\n    }\r\n\r\n    &--primary {\r\n        color: $uv-button-primary-color;\r\n        background-color: $uv-button-primary-background-color;\r\n        border-color: $uv-button-primary-border-color;\r\n        border-width: $uv-button-primary-border-width;\r\n        border-style: $uv-button-primary-border-style;\r\n    }\r\n\r\n    &--error {\r\n        color: $uv-button-error-color;\r\n        background-color: $uv-button-error-background-color;\r\n        border-color: $uv-button-error-border-color;\r\n        border-width: $uv-button-error-border-width;\r\n        border-style: $uv-button-error-border-style;\r\n    }\r\n\r\n    &--warning {\r\n        color: $uv-button-warning-color;\r\n        background-color: $uv-button-warning-background-color;\r\n        border-color: $uv-button-warning-border-color;\r\n        border-width: $uv-button-warning-border-width;\r\n        border-style: $uv-button-warning-border-style;\r\n    }\r\n\r\n    &--block {\r\n        @include flex;\r\n        width: $uv-button-block-width;\r\n    }\r\n\r\n    &--circle {\r\n        border-top-right-radius: $uv-button-circle-border-top-right-radius;\r\n        border-top-left-radius: $uv-button-circle-border-top-left-radius;\r\n        border-bottom-left-radius: $uv-button-circle-border-bottom-left-radius;\r\n        border-bottom-right-radius: $uv-button-circle-border-bottom-right-radius;\r\n    }\r\n\r\n    &--square {\r\n        border-bottom-left-radius: $uv-button-square-border-top-right-radius;\r\n        border-bottom-right-radius: $uv-button-square-border-top-left-radius;\r\n        border-top-left-radius: $uv-button-square-border-bottom-left-radius;\r\n        border-top-right-radius: $uv-button-square-border-bottom-right-radius;\r\n    }\r\n\r\n    &__icon {\r\n        /* #ifndef APP-NVUE */\r\n        min-width: $uv-button-icon-min-width;\r\n        line-height: inherit !important;\r\n        vertical-align: top;\r\n        /* #endif */\r\n    }\r\n\r\n    &--plain {\r\n        background-color: $uv-button-plain-background-color;\r\n    }\r\n\r\n    &--hairline {\r\n        border-width: $uv-button-hairline-border-width !important;\r\n    }\r\n}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-button/components/uv-button/vue.scss",
    "content": "@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n// nvue下hover-class无效\r\n$uv-button-before-top:50% !default;\r\n$uv-button-before-left:50% !default;\r\n$uv-button-before-width:100% !default;\r\n$uv-button-before-height:100% !default;\r\n$uv-button-before-transform:translate(-50%, -50%) !default;\r\n$uv-button-before-opacity:0 !default;\r\n$uv-button-before-background-color:#000 !default;\r\n$uv-button-before-border-color:#000 !default;\r\n$uv-button-active-before-opacity:.15 !default;\r\n$uv-button-icon-margin-left:4px !default;\r\n$uv-button-plain-uv-button-info-color:$uv-info;\r\n$uv-button-plain-uv-button-success-color:$uv-success;\r\n$uv-button-plain-uv-button-error-color:$uv-error;\r\n$uv-button-plain-uv-button-warning-color:$uv-warning;\r\n\r\n.uv-button-wrapper {\r\n\tposition: relative;\r\n\t&--dis {\r\n\t\tposition: absolute;\r\n\t\tleft: 0;\r\n\t\ttop: 0;\r\n\t\tright: 0;\r\n\t\tbottom: 0;\r\n\t\tz-index: 9;\r\n\t}\r\n}\r\n\r\n.uv-button {\r\n\twidth: 100%;\r\n\t\r\n\t&__text {\r\n\t\twhite-space: nowrap;\r\n\t\tline-height: 1;\r\n\t}\r\n\t\r\n\t&:before {\r\n\t\tposition: absolute;\r\n\t\ttop:$uv-button-before-top;\r\n\t\tleft:$uv-button-before-left;\r\n\t\twidth:$uv-button-before-width;\r\n\t\theight:$uv-button-before-height;\r\n\t\tborder: inherit;\r\n\t\tborder-radius: inherit;\r\n\t\ttransform:$uv-button-before-transform;\r\n\t\topacity:$uv-button-before-opacity;\r\n\t\tcontent: \" \";\r\n\t\tbackground-color:$uv-button-before-background-color;\r\n\t\tborder-color:$uv-button-before-border-color;\r\n\t}\r\n\t\r\n\t&--active {\r\n\t\t&:before {\r\n\t\t\topacity: .15\r\n\t\t}\r\n\t}\r\n\t\r\n\t&__icon+&__text:not(:empty),\r\n\t&__loading-text {\r\n\t\tmargin-left:$uv-button-icon-margin-left;\r\n\t}\r\n\t\r\n\t&--plain {\r\n\t\t&.uv-button--primary {\r\n\t\t\tcolor: $uv-primary;\r\n\t\t}\r\n\t}\r\n\t\r\n\t&--plain {\r\n\t\t&.uv-button--info {\r\n\t\t\tcolor:$uv-button-plain-uv-button-info-color;\r\n\t\t}\r\n\t}\r\n\t\r\n\t&--plain {\r\n\t\t&.uv-button--success {\r\n\t\t\tcolor:$uv-button-plain-uv-button-success-color;\r\n\t\t}\r\n\t}\r\n\t\r\n\t&--plain {\r\n\t\t&.uv-button--error {\r\n\t\t\tcolor:$uv-button-plain-uv-button-error-color;\r\n\t\t}\r\n\t}\r\n\t\r\n\t&--plain {\r\n\t\t&.uv-button--warning {\r\n\t\t\tcolor:$uv-button-plain-uv-button-warning-color;\r\n\t\t}\r\n\t}\r\n}\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-button/package.json",
    "content": "{\r\n\t\"id\": \"uv-button\",\r\n\t\"displayName\": \"uv-button 按钮 全面兼容vue3+2、app、h5、小程序等多端\",\r\n\t\"version\": \"1.0.12\",\r\n\t\"description\": \"按钮组件内部实现以uni-app的button组件为基础，进行二次封装，灵活配置，功能齐全，兼容全端。\",\r\n\t\"keywords\": [\r\n        \"uv-button\",\r\n        \"uvui\",\r\n        \"uv-ui\",\r\n        \"button\",\r\n        \"按钮\"\r\n    ],\r\n\t\"repository\": \"\",\r\n\t\"engines\": {\r\n\t\t\"HBuilderX\": \"^3.1.0\"\r\n\t},\r\n\t\"dcloudext\": {\r\n\t\t\"type\": \"component-vue\",\r\n\t\t\"sale\": {\r\n\t\t\t\"regular\": {\r\n\t\t\t\t\"price\": \"0.00\"\r\n\t\t\t},\r\n\t\t\t\"sourcecode\": {\r\n\t\t\t\t\"price\": \"0.00\"\r\n\t\t\t}\r\n\t\t},\r\n\t\t\"contact\": {\r\n\t\t\t\"qq\": \"\"\r\n\t\t},\r\n\t\t\"declaration\": {\r\n\t\t\t\"ads\": \"无\",\r\n\t\t\t\"data\": \"插件不采集任何数据\",\r\n\t\t\t\"permissions\": \"无\"\r\n\t\t},\r\n\t\t\"npmurl\": \"\"\r\n\t},\r\n\t\"uni_modules\": {\r\n\t\t\"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-loading-icon\",\r\n\t\t\t\"uv-icon\"\r\n\t\t],\r\n\t\t\"encrypt\": [],\r\n\t\t\"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-button/readme.md",
    "content": "## Button 按钮\r\n\r\n> **组件名：uv-button**\r\n\r\n该组件内部实现以`uni-app`的`button`组件为基础，进行二次封装，灵活配置，功能齐全，兼容全端。灵活配置，内置状态设置，开箱即用。\r\n\r\n# <a href=\"https://www.uvui.cn/components/button.html\" target=\"_blank\">查看文档</a>\r\n\r\n## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>（请不要 下载插件ZIP）</small>\r\n\r\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n<a href=\"https://ext.dcloud.net.cn/plugin?name=uv-ui\" target=\"_blank\">\r\n\r\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\r\n\r\n</a>\r\n\r\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-calendar/changelog.md",
    "content": "## 1.0.5（2023-07-02）\nuv-calendar  由于弹出层uv-popup的修改，打开和关闭方法更改，详情参考文档：https://www.uvui.cn/components/calendar.html\n## 1.0.4（2023-06-15）\n1. formatter格式化中增加topInfo参数\n## 1.0.3（2023-06-08）\n1. 增加点击日期change回调\n2. 优化\n## 1.0.2（2023-06-05）\r\n1. 修改多个时间选择的时候存在反选的BUG\r\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-calendar 日历\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-calendar/components/uv-calendar/calendar.js",
    "content": "/**\r\n* @1900-2100区间内的公历、农历互转\r\n* @charset UTF-8\r\n* @github  https://github.com/jjonline/calendar.js\r\n* @Author  Jea杨(JJonline@JJonline.Cn)\r\n* @Time    2014-7-21\r\n* @Time    2016-8-13 Fixed 2033hex、Attribution Annals\r\n* @Time    2016-9-25 Fixed lunar LeapMonth Param Bug\r\n* @Time    2017-7-24 Fixed use getTerm Func Param Error.use solar year,NOT lunar year\r\n* @Version 1.0.3\r\n* @公历转农历：calendar.solar2lunar(1987,11,01); //[you can ignore params of prefix 0]\r\n* @农历转公历：calendar.lunar2solar(1987,09,10); //[you can ignore params of prefix 0]\r\n*/\r\n/* eslint-disable */\r\nvar calendar = {\r\n\r\n    /**\r\n        * 农历1900-2100的润大小信息表\r\n        * @Array Of Property\r\n        * @return Hex\r\n        */\r\n    lunarInfo: [0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, // 1900-1909\r\n        0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977, // 1910-1919\r\n        0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970, // 1920-1929\r\n        0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950, // 1930-1939\r\n        0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557, // 1940-1949\r\n        0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0, // 1950-1959\r\n        0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0, // 1960-1969\r\n        0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6, // 1970-1979\r\n        0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570, // 1980-1989\r\n        0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x05ac0, 0x0ab60, 0x096d5, 0x092e0, // 1990-1999\r\n        0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5, // 2000-2009\r\n        0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930, // 2010-2019\r\n        0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530, // 2020-2029\r\n        0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, // 2030-2039\r\n        0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0, // 2040-2049\r\n        /** Add By JJonline@JJonline.Cn**/\r\n        0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0, // 2050-2059\r\n        0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4, // 2060-2069\r\n        0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0, // 2070-2079\r\n        0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160, // 2080-2089\r\n        0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252, // 2090-2099\r\n        0x0d520], // 2100\r\n\r\n    /**\r\n        * 公历每个月份的天数普通表\r\n        * @Array Of Property\r\n        * @return Number\r\n        */\r\n    solarMonth: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],\r\n\r\n    /**\r\n        * 天干地支之天干速查表\r\n        * @Array Of Property trans[\"甲\",\"乙\",\"丙\",\"丁\",\"戊\",\"己\",\"庚\",\"辛\",\"壬\",\"癸\"]\r\n        * @return Cn string\r\n        */\r\n    Gan: ['\\u7532', '\\u4e59', '\\u4e19', '\\u4e01', '\\u620a', '\\u5df1', '\\u5e9a', '\\u8f9b', '\\u58ec', '\\u7678'],\r\n\r\n    /**\r\n        * 天干地支之地支速查表\r\n        * @Array Of Property\r\n        * @trans[\"子\",\"丑\",\"寅\",\"卯\",\"辰\",\"巳\",\"午\",\"未\",\"申\",\"酉\",\"戌\",\"亥\"]\r\n        * @return Cn string\r\n        */\r\n    Zhi: ['\\u5b50', '\\u4e11', '\\u5bc5', '\\u536f', '\\u8fb0', '\\u5df3', '\\u5348', '\\u672a', '\\u7533', '\\u9149', '\\u620c', '\\u4ea5'],\r\n\r\n    /**\r\n        * 天干地支之地支速查表<=>生肖\r\n        * @Array Of Property\r\n        * @trans[\"鼠\",\"牛\",\"虎\",\"兔\",\"龙\",\"蛇\",\"马\",\"羊\",\"猴\",\"鸡\",\"狗\",\"猪\"]\r\n        * @return Cn string\r\n        */\r\n    Animals: ['\\u9f20', '\\u725b', '\\u864e', '\\u5154', '\\u9f99', '\\u86c7', '\\u9a6c', '\\u7f8a', '\\u7334', '\\u9e21', '\\u72d7', '\\u732a'],\r\n\r\n    /**\r\n        * 24节气速查表\r\n        * @Array Of Property\r\n        * @trans[\"小寒\",\"大寒\",\"立春\",\"雨水\",\"惊蛰\",\"春分\",\"清明\",\"谷雨\",\"立夏\",\"小满\",\"芒种\",\"夏至\",\"小暑\",\"大暑\",\"立秋\",\"处暑\",\"白露\",\"秋分\",\"寒露\",\"霜降\",\"立冬\",\"小雪\",\"大雪\",\"冬至\"]\r\n        * @return Cn string\r\n        */\r\n    solarTerm: ['\\u5c0f\\u5bd2', '\\u5927\\u5bd2', '\\u7acb\\u6625', '\\u96e8\\u6c34', '\\u60ca\\u86f0', '\\u6625\\u5206', '\\u6e05\\u660e', '\\u8c37\\u96e8', '\\u7acb\\u590f', '\\u5c0f\\u6ee1', '\\u8292\\u79cd', '\\u590f\\u81f3', '\\u5c0f\\u6691', '\\u5927\\u6691', '\\u7acb\\u79cb', '\\u5904\\u6691', '\\u767d\\u9732', '\\u79cb\\u5206', '\\u5bd2\\u9732', '\\u971c\\u964d', '\\u7acb\\u51ac', '\\u5c0f\\u96ea', '\\u5927\\u96ea', '\\u51ac\\u81f3'],\r\n\r\n    /**\r\n        * 1900-2100各年的24节气日期速查表\r\n        * @Array Of Property\r\n        * @return 0x string For splice\r\n        */\r\n    sTermInfo: ['9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f',\r\n        '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',\r\n        '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa',\r\n        '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f',\r\n        'b027097bd097c36b0b6fc9274c91aa', '9778397bd19801ec9210c965cc920e', '97b6b97bd19801ec95f8c965cc920f',\r\n        '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd197c36c9210c9274c91aa',\r\n        '97b6b97bd19801ec95f8c965cc920e', '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2',\r\n        '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec95f8c965cc920e', '97bcf97c3598082c95f8e1cfcc920f',\r\n        '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e',\r\n        '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',\r\n        '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722',\r\n        '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f',\r\n        '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',\r\n        '97bcf97c359801ec95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',\r\n        '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd097bd07f595b0b6fc920fb0722',\r\n        '9778397bd097c36b0b6fc9210c8dc2', '9778397bd19801ec9210c9274c920e', '97b6b97bd19801ec95f8c965cc920f',\r\n        '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e',\r\n        '97b6b97bd19801ec95f8c965cc920f', '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2',\r\n        '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bd07f1487f595b0b0bc920fb0722',\r\n        '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',\r\n        '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',\r\n        '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',\r\n        '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f531b0b0bb0b6fb0722',\r\n        '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',\r\n        '97bcf7f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',\r\n        '97b6b97bd19801ec9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',\r\n        '9778397bd097c36b0b6fc9210c91aa', '97b6b97bd197c36c9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722',\r\n        '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e',\r\n        '97b6b7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2',\r\n        '9778397bd097c36b0b70c9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722',\r\n        '7f0e397bd097c35b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',\r\n        '7f0e27f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',\r\n        '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',\r\n        '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',\r\n        '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',\r\n        '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9274c91aa',\r\n        '97b6b7f0e47f531b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',\r\n        '9778397bd097c36b0b6fc9210c91aa', '97b6b7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',\r\n        '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '977837f0e37f149b0723b0787b0721',\r\n        '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c35b0b6fc9210c8dc2',\r\n        '977837f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722',\r\n        '7f0e397bd097c35b0b6fc9210c8dc2', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',\r\n        '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '977837f0e37f14998082b0787b06bd',\r\n        '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',\r\n        '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',\r\n        '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',\r\n        '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd',\r\n        '7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',\r\n        '977837f0e37f14998082b0723b06bd', '7f07e7f0e37f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',\r\n        '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b0721',\r\n        '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f595b0b0bb0b6fb0722', '7f0e37f0e37f14898082b0723b02d5',\r\n        '7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f531b0b0bb0b6fb0722',\r\n        '7f0e37f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',\r\n        '7f0e37f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd',\r\n        '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35',\r\n        '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',\r\n        '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f149b0723b0787b0721',\r\n        '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0723b06bd',\r\n        '7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722', '7f0e37f0e366aa89801eb072297c35',\r\n        '7ec967f0e37f14998082b0723b06bd', '7f07e7f0e37f14998083b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',\r\n        '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14898082b0723b02d5', '7f07e7f0e37f14998082b0787b0721',\r\n        '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66aa89801e9808297c35', '665f67f0e37f14898082b0723b02d5',\r\n        '7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66a449801e9808297c35',\r\n        '665f67f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',\r\n        '7f0e36665b66a449801e9808297c35', '665f67f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd',\r\n        '7f07e7f0e47f531b0723b0b6fb0721', '7f0e26665b66a449801e9808297c35', '665f67f0e37f1489801eb072297c35',\r\n        '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722'],\r\n\r\n    /**\r\n        * 数字转中文速查表\r\n        * @Array Of Property\r\n        * @trans ['日','一','二','三','四','五','六','七','八','九','十']\r\n        * @return Cn string\r\n        */\r\n    nStr1: ['\\u65e5', '\\u4e00', '\\u4e8c', '\\u4e09', '\\u56db', '\\u4e94', '\\u516d', '\\u4e03', '\\u516b', '\\u4e5d', '\\u5341'],\r\n\r\n    /**\r\n        * 日期转农历称呼速查表\r\n        * @Array Of Property\r\n        * @trans ['初','十','廿','卅']\r\n        * @return Cn string\r\n        */\r\n    nStr2: ['\\u521d', '\\u5341', '\\u5eff', '\\u5345'],\r\n\r\n    /**\r\n        * 月份转农历称呼速查表\r\n        * @Array Of Property\r\n        * @trans ['正','一','二','三','四','五','六','七','八','九','十','冬','腊']\r\n        * @return Cn string\r\n        */\r\n    nStr3: ['\\u6b63', '\\u4e8c', '\\u4e09', '\\u56db', '\\u4e94', '\\u516d', '\\u4e03', '\\u516b', '\\u4e5d', '\\u5341', '\\u51ac', '\\u814a'],\r\n\r\n    /**\r\n        * 返回农历y年一整年的总天数\r\n        * @param lunar Year\r\n        * @return Number\r\n        * @eg:var count = calendar.lYearDays(1987) ;//count=387\r\n        */\r\n    lYearDays: function (y) {\r\n        var i; var sum = 348\r\n        for (i = 0x8000; i > 0x8; i >>= 1) { sum += (this.lunarInfo[y - 1900] & i) ? 1 : 0 }\r\n        return (sum + this.leapDays(y))\r\n    },\r\n\r\n    /**\r\n        * 返回农历y年闰月是哪个月；若y年没有闰月 则返回0\r\n        * @param lunar Year\r\n        * @return Number (0-12)\r\n        * @eg:var leapMonth = calendar.leapMonth(1987) ;//leapMonth=6\r\n        */\r\n    leapMonth: function (y) { // 闰字编码 \\u95f0\r\n        return (this.lunarInfo[y - 1900] & 0xf)\r\n    },\r\n\r\n    /**\r\n        * 返回农历y年闰月的天数 若该年没有闰月则返回0\r\n        * @param lunar Year\r\n        * @return Number (0、29、30)\r\n        * @eg:var leapMonthDay = calendar.leapDays(1987) ;//leapMonthDay=29\r\n        */\r\n    leapDays: function (y) {\r\n        if (this.leapMonth(y)) {\r\n            return ((this.lunarInfo[y - 1900] & 0x10000) ? 30 : 29)\r\n        }\r\n        return (0)\r\n    },\r\n\r\n    /**\r\n        * 返回农历y年m月（非闰月）的总天数，计算m为闰月时的天数请使用leapDays方法\r\n        * @param lunar Year\r\n        * @return Number (-1、29、30)\r\n        * @eg:var MonthDay = calendar.monthDays(1987,9) ;//MonthDay=29\r\n        */\r\n    monthDays: function (y, m) {\r\n        if (m > 12 || m < 1) { return -1 }// 月份参数从1至12，参数错误返回-1\r\n        return ((this.lunarInfo[y - 1900] & (0x10000 >> m)) ? 30 : 29)\r\n    },\r\n\r\n    /**\r\n        * 返回公历(!)y年m月的天数\r\n        * @param solar Year\r\n        * @return Number (-1、28、29、30、31)\r\n        * @eg:var solarMonthDay = calendar.leapDays(1987) ;//solarMonthDay=30\r\n        */\r\n    solarDays: function (y, m) {\r\n        if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1\r\n        var ms = m - 1\r\n        if (ms == 1) { // 2月份的闰平规律测算后确认返回28或29\r\n            return (((y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0)) ? 29 : 28)\r\n        } else {\r\n            return (this.solarMonth[ms])\r\n        }\r\n    },\r\n\r\n    /**\r\n       * 农历年份转换为干支纪年\r\n       * @param  lYear 农历年的年份数\r\n       * @return Cn string\r\n       */\r\n    toGanZhiYear: function (lYear) {\r\n        var ganKey = (lYear - 3) % 10\r\n        var zhiKey = (lYear - 3) % 12\r\n        if (ganKey == 0) ganKey = 10// 如果余数为0则为最后一个天干\r\n        if (zhiKey == 0) zhiKey = 12// 如果余数为0则为最后一个地支\r\n        return this.Gan[ganKey - 1] + this.Zhi[zhiKey - 1]\r\n    },\r\n\r\n    /**\r\n       * 公历月、日判断所属星座\r\n       * @param  cMonth [description]\r\n       * @param  cDay [description]\r\n       * @return Cn string\r\n       */\r\n    toAstro: function (cMonth, cDay) {\r\n        var s = '\\u9b54\\u7faf\\u6c34\\u74f6\\u53cc\\u9c7c\\u767d\\u7f8a\\u91d1\\u725b\\u53cc\\u5b50\\u5de8\\u87f9\\u72ee\\u5b50\\u5904\\u5973\\u5929\\u79e4\\u5929\\u874e\\u5c04\\u624b\\u9b54\\u7faf'\r\n        var arr = [20, 19, 21, 21, 21, 22, 23, 23, 23, 23, 22, 22]\r\n        return s.substr(cMonth * 2 - (cDay < arr[cMonth - 1] ? 2 : 0), 2) + '\\u5ea7'// 座\r\n    },\r\n\r\n    /**\r\n        * 传入offset偏移量返回干支\r\n        * @param offset 相对甲子的偏移量\r\n        * @return Cn string\r\n        */\r\n    toGanZhi: function (offset) {\r\n        return this.Gan[offset % 10] + this.Zhi[offset % 12]\r\n    },\r\n\r\n    /**\r\n        * 传入公历(!)y年获得该年第n个节气的公历日期\r\n        * @param y公历年(1900-2100)；n二十四节气中的第几个节气(1~24)；从n=1(小寒)算起\r\n        * @return day Number\r\n        * @eg:var _24 = calendar.getTerm(1987,3) ;//_24=4;意即1987年2月4日立春\r\n        */\r\n    getTerm: function (y, n) {\r\n        if (y < 1900 || y > 2100) { return -1 }\r\n        if (n < 1 || n > 24) { return -1 }\r\n        var _table = this.sTermInfo[y - 1900]\r\n        var _info = [\r\n            parseInt('0x' + _table.substr(0, 5)).toString(),\r\n            parseInt('0x' + _table.substr(5, 5)).toString(),\r\n            parseInt('0x' + _table.substr(10, 5)).toString(),\r\n            parseInt('0x' + _table.substr(15, 5)).toString(),\r\n            parseInt('0x' + _table.substr(20, 5)).toString(),\r\n            parseInt('0x' + _table.substr(25, 5)).toString()\r\n        ]\r\n        var _calday = [\r\n            _info[0].substr(0, 1),\r\n            _info[0].substr(1, 2),\r\n            _info[0].substr(3, 1),\r\n            _info[0].substr(4, 2),\r\n\r\n            _info[1].substr(0, 1),\r\n            _info[1].substr(1, 2),\r\n            _info[1].substr(3, 1),\r\n            _info[1].substr(4, 2),\r\n\r\n            _info[2].substr(0, 1),\r\n            _info[2].substr(1, 2),\r\n            _info[2].substr(3, 1),\r\n            _info[2].substr(4, 2),\r\n\r\n            _info[3].substr(0, 1),\r\n            _info[3].substr(1, 2),\r\n            _info[3].substr(3, 1),\r\n            _info[3].substr(4, 2),\r\n\r\n            _info[4].substr(0, 1),\r\n            _info[4].substr(1, 2),\r\n            _info[4].substr(3, 1),\r\n            _info[4].substr(4, 2),\r\n\r\n            _info[5].substr(0, 1),\r\n            _info[5].substr(1, 2),\r\n            _info[5].substr(3, 1),\r\n            _info[5].substr(4, 2)\r\n        ]\r\n        return parseInt(_calday[n - 1])\r\n    },\r\n\r\n    /**\r\n        * 传入农历数字月份返回汉语通俗表示法\r\n        * @param lunar month\r\n        * @return Cn string\r\n        * @eg:var cnMonth = calendar.toChinaMonth(12) ;//cnMonth='腊月'\r\n        */\r\n    toChinaMonth: function (m) { // 月 => \\u6708\r\n        if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1\r\n        var s = this.nStr3[m - 1]\r\n        s += '\\u6708'// 加上月字\r\n        return s\r\n    },\r\n\r\n    /**\r\n        * 传入农历日期数字返回汉字表示法\r\n        * @param lunar day\r\n        * @return Cn string\r\n        * @eg:var cnDay = calendar.toChinaDay(21) ;//cnMonth='廿一'\r\n        */\r\n    toChinaDay: function (d) { // 日 => \\u65e5\r\n        var s\r\n        switch (d) {\r\n            case 10:\r\n                s = '\\u521d\\u5341'; break\r\n            case 20:\r\n                s = '\\u4e8c\\u5341'; break\r\n                break\r\n            case 30:\r\n                s = '\\u4e09\\u5341'; break\r\n                break\r\n            default:\r\n                s = this.nStr2[Math.floor(d / 10)]\r\n                s += this.nStr1[d % 10]\r\n        }\r\n        return (s)\r\n    },\r\n\r\n    /**\r\n        * 年份转生肖[!仅能大致转换] => 精确划分生肖分界线是“立春”\r\n        * @param y year\r\n        * @return Cn string\r\n        * @eg:var animal = calendar.getAnimal(1987) ;//animal='兔'\r\n        */\r\n    getAnimal: function (y) {\r\n        return this.Animals[(y - 4) % 12]\r\n    },\r\n\r\n    /**\r\n        * 传入阳历年月日获得详细的公历、农历object信息 <=>JSON\r\n        * @param y  solar year\r\n        * @param m  solar month\r\n        * @param d  solar day\r\n        * @return JSON object\r\n        * @eg:console.log(calendar.solar2lunar(1987,11,01));\r\n        */\r\n    solar2lunar: function (y, m, d) { // 参数区间1900.1.31~2100.12.31\r\n        // 年份限定、上限\r\n        if (y < 1900 || y > 2100) {\r\n            return -1// undefined转换为数字变为NaN\r\n        }\r\n        // 公历传参最下限\r\n        if (y == 1900 && m == 1 && d < 31) {\r\n            return -1\r\n        }\r\n        // 未传参  获得当天\r\n        if (!y) {\r\n            var objDate = new Date()\r\n        } else {\r\n            var objDate = new Date(y, parseInt(m) - 1, d)\r\n        }\r\n        var i; var leap = 0; var temp = 0\r\n        // 修正ymd参数\r\n        var y = objDate.getFullYear()\r\n        var m = objDate.getMonth() + 1\r\n        var d = objDate.getDate()\r\n        var offset = (Date.UTC(objDate.getFullYear(), objDate.getMonth(), objDate.getDate()) - Date.UTC(1900, 0, 31)) / 86400000\r\n        for (i = 1900; i < 2101 && offset > 0; i++) {\r\n            temp = this.lYearDays(i)\r\n            offset -= temp\r\n        }\r\n        if (offset < 0) {\r\n            offset += temp; i--\r\n        }\r\n\r\n        // 是否今天\r\n        var isTodayObj = new Date()\r\n        var isToday = false\r\n        if (isTodayObj.getFullYear() == y && isTodayObj.getMonth() + 1 == m && isTodayObj.getDate() == d) {\r\n            isToday = true\r\n        }\r\n        // 星期几\r\n        var nWeek = objDate.getDay()\r\n        var cWeek = this.nStr1[nWeek]\r\n        // 数字表示周几顺应天朝周一开始的惯例\r\n        if (nWeek == 0) {\r\n            nWeek = 7\r\n        }\r\n        // 农历年\r\n        var year = i\r\n        var leap = this.leapMonth(i) // 闰哪个月\r\n        var isLeap = false\r\n\r\n        // 效验闰月\r\n        for (i = 1; i < 13 && offset > 0; i++) {\r\n            // 闰月\r\n            if (leap > 0 && i == (leap + 1) && isLeap == false) {\r\n                --i\r\n                isLeap = true; temp = this.leapDays(year) // 计算农历闰月天数\r\n            } else {\r\n                temp = this.monthDays(year, i)// 计算农历普通月天数\r\n            }\r\n            // 解除闰月\r\n            if (isLeap == true && i == (leap + 1)) { isLeap = false }\r\n            offset -= temp\r\n        }\r\n        // 闰月导致数组下标重叠取反\r\n        if (offset == 0 && leap > 0 && i == leap + 1) {\r\n            if (isLeap) {\r\n                isLeap = false\r\n            } else {\r\n                isLeap = true; --i\r\n            }\r\n        }\r\n        if (offset < 0) {\r\n            offset += temp; --i\r\n        }\r\n        // 农历月\r\n        var month = i\r\n        // 农历日\r\n        var day = offset + 1\r\n        // 天干地支处理\r\n        var sm = m - 1\r\n        var gzY = this.toGanZhiYear(year)\r\n\r\n        // 当月的两个节气\r\n        // bugfix-2017-7-24 11:03:38 use lunar Year Param `y` Not `year`\r\n        var firstNode = this.getTerm(y, (m * 2 - 1))// 返回当月「节」为几日开始\r\n        var secondNode = this.getTerm(y, (m * 2))// 返回当月「节」为几日开始\r\n\r\n        // 依据12节气修正干支月\r\n        var gzM = this.toGanZhi((y - 1900) * 12 + m + 11)\r\n        if (d >= firstNode) {\r\n            gzM = this.toGanZhi((y - 1900) * 12 + m + 12)\r\n        }\r\n\r\n        // 传入的日期的节气与否\r\n        var isTerm = false\r\n        var Term = null\r\n        if (firstNode == d) {\r\n            isTerm = true\r\n            Term = this.solarTerm[m * 2 - 2]\r\n        }\r\n        if (secondNode == d) {\r\n            isTerm = true\r\n            Term = this.solarTerm[m * 2 - 1]\r\n        }\r\n        // 日柱 当月一日与 1900/1/1 相差天数\r\n        var dayCyclical = Date.UTC(y, sm, 1, 0, 0, 0, 0) / 86400000 + 25567 + 10\r\n        var gzD = this.toGanZhi(dayCyclical + d - 1)\r\n        // 该日期所属的星座\r\n        var astro = this.toAstro(m, d)\r\n\r\n        return { 'lYear': year, 'lMonth': month, 'lDay': day, 'Animal': this.getAnimal(year), 'IMonthCn': (isLeap ? '\\u95f0' : '') + this.toChinaMonth(month), 'IDayCn': this.toChinaDay(day), 'cYear': y, 'cMonth': m, 'cDay': d, 'gzYear': gzY, 'gzMonth': gzM, 'gzDay': gzD, 'isToday': isToday, 'isLeap': isLeap, 'nWeek': nWeek, 'ncWeek': '\\u661f\\u671f' + cWeek, 'isTerm': isTerm, 'Term': Term, 'astro': astro }\r\n    },\r\n\r\n    /**\r\n        * 传入农历年月日以及传入的月份是否闰月获得详细的公历、农历object信息 <=>JSON\r\n        * @param y  lunar year\r\n        * @param m  lunar month\r\n        * @param d  lunar day\r\n        * @param isLeapMonth  lunar month is leap or not.[如果是农历闰月第四个参数赋值true即可]\r\n        * @return JSON object\r\n        * @eg:console.log(calendar.lunar2solar(1987,9,10));\r\n        */\r\n    lunar2solar: function (y, m, d, isLeapMonth) { // 参数区间1900.1.31~2100.12.1\r\n        var isLeapMonth = !!isLeapMonth\r\n        var leapOffset = 0\r\n        var leapMonth = this.leapMonth(y)\r\n        var leapDay = this.leapDays(y)\r\n        if (isLeapMonth && (leapMonth != m)) { return -1 }// 传参要求计算该闰月公历 但该年得出的闰月与传参的月份并不同\r\n        if (y == 2100 && m == 12 && d > 1 || y == 1900 && m == 1 && d < 31) { return -1 }// 超出了最大极限值\r\n        var day = this.monthDays(y, m)\r\n        var _day = day\r\n        // bugFix 2016-9-25\r\n        // if month is leap, _day use leapDays method\r\n        if (isLeapMonth) {\r\n            _day = this.leapDays(y, m)\r\n        }\r\n        if (y < 1900 || y > 2100 || d > _day) { return -1 }// 参数合法性效验\r\n\r\n        // 计算农历的时间差\r\n        var offset = 0\r\n        for (var i = 1900; i < y; i++) {\r\n            offset += this.lYearDays(i)\r\n        }\r\n        var leap = 0; var isAdd = false\r\n        for (var i = 1; i < m; i++) {\r\n            leap = this.leapMonth(y)\r\n            if (!isAdd) { // 处理闰月\r\n                if (leap <= i && leap > 0) {\r\n                    offset += this.leapDays(y); isAdd = true\r\n                }\r\n            }\r\n            offset += this.monthDays(y, i)\r\n        }\r\n        // 转换闰月农历 需补充该年闰月的前一个月的时差\r\n        if (isLeapMonth) { offset += day }\r\n        // 1900年农历正月一日的公历时间为1900年1月30日0时0分0秒(该时间也是本农历的最开始起始点)\r\n        var stmap = Date.UTC(1900, 1, 30, 0, 0, 0)\r\n        var calObj = new Date((offset + d - 31) * 86400000 + stmap)\r\n        var cY = calObj.getUTCFullYear()\r\n        var cM = calObj.getUTCMonth() + 1\r\n        var cD = calObj.getUTCDate()\r\n\r\n        return this.solar2lunar(cY, cM, cD)\r\n    }\r\n}\r\n\r\nexport default calendar\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-calendar/components/uv-calendar/header.vue",
    "content": "<template>\r\n\t<view class=\"uv-calendar-header uv-border-bottom\">\r\n\t\t<text\r\n\t\t\tclass=\"uv-calendar-header__title\"\r\n\t\t\tv-if=\"showTitle\"\r\n\t\t>{{ title }}</text>\r\n\t\t<text\r\n\t\t\tclass=\"uv-calendar-header__subtitle\"\r\n\t\t\tv-if=\"showSubtitle\"\r\n\t\t>{{ subtitle }}</text>\r\n\t\t<view class=\"uv-calendar-header__weekdays\">\r\n\t\t\t<text class=\"uv-calendar-header__weekdays__weekday\">一</text>\r\n\t\t\t<text class=\"uv-calendar-header__weekdays__weekday\">二</text>\r\n\t\t\t<text class=\"uv-calendar-header__weekdays__weekday\">三</text>\r\n\t\t\t<text class=\"uv-calendar-header__weekdays__weekday\">四</text>\r\n\t\t\t<text class=\"uv-calendar-header__weekdays__weekday\">五</text>\r\n\t\t\t<text class=\"uv-calendar-header__weekdays__weekday\">六</text>\r\n\t\t\t<text class=\"uv-calendar-header__weekdays__weekday\">日</text>\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\texport default {\r\n\t\tname: 'uv-calendar-header',\r\n\t\tmixins: [mpMixin, mixin],\r\n\t\tprops: {\r\n\t\t\t// 标题\r\n\t\t\ttitle: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: ''\r\n\t\t\t},\r\n\t\t\t// 副标题\r\n\t\t\tsubtitle: {\r\n\t\t\t\ttype: [String,null],\r\n\t\t\t\tdefault: ''\r\n\t\t\t},\r\n\t\t\t// 是否显示标题\r\n\t\t\tshowTitle: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: true\r\n\t\t\t},\r\n\t\t\t// 是否显示副标题\r\n\t\t\tshowSubtitle: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: true\r\n\t\t\t},\r\n\t\t},\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tname() {\r\n\r\n\t\t\t}\r\n\t\t},\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t$show-border: 1;\r\n\t$show-border-bottom: 1;\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t.uv-calendar-header {\r\n\t\tpadding-bottom: 4px;\r\n\r\n\t\t&__title {\r\n\t\t\tfont-size: 16px;\r\n\t\t\tcolor: $uv-main-color;\r\n\t\t\ttext-align: center;\r\n\t\t\theight: 42px;\r\n\t\t\tline-height: 42px;\r\n\t\t\tfont-weight: bold;\r\n\t\t}\r\n\r\n\t\t&__subtitle {\r\n\t\t\tfont-size: 14px;\r\n\t\t\tcolor: $uv-main-color;\r\n\t\t\theight: 40px;\r\n\t\t\ttext-align: center;\r\n\t\t\tline-height: 40px;\r\n\t\t\tfont-weight: bold;\r\n\t\t}\r\n\r\n\t\t&__weekdays {\r\n\t\t\t@include flex;\r\n\t\t\tjustify-content: space-between;\r\n\r\n\t\t\t&__weekday {\r\n\t\t\t\tfont-size: 13px;\r\n\t\t\t\tcolor: $uv-main-color;\r\n\t\t\t\tline-height: 30px;\r\n\t\t\t\tflex: 1;\r\n\t\t\t\ttext-align: center;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-calendar/components/uv-calendar/month.vue",
    "content": "<template>\r\n\t<view class=\"uv-calendar-month-wrapper\" ref=\"uv-calendar-month-wrapper\">\r\n\t\t<view v-for=\"(item, index) in months\" :key=\"index\" :class=\"[`uv-calendar-month-${index}`]\"\r\n\t\t\t:ref=\"`uv-calendar-month-${index}`\" :id=\"`month-${index}`\">\r\n\t\t\t<text v-if=\"index !== 0\" class=\"uv-calendar-month__title\">{{ item.year }}年{{ item.month }}月</text>\r\n\t\t\t<view class=\"uv-calendar-month__days\">\r\n\t\t\t\t<view v-if=\"showMark\" class=\"uv-calendar-month__days__month-mark-wrapper\">\r\n\t\t\t\t\t<text class=\"uv-calendar-month__days__month-mark-wrapper__text\">{{ item.month }}</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view class=\"uv-calendar-month__days__day\" v-for=\"(item1, index1) in item.date\" :key=\"index1\"\r\n\t\t\t\t\t:style=\"[dayStyle(index, index1, item1)]\" @tap=\"clickHandler(index, index1, item1)\"\r\n\t\t\t\t\t:class=\"[item1.selected && 'uv-calendar-month__days__day__select--selected']\">\r\n\t\t\t\t\t<view class=\"uv-calendar-month__days__day__select\" :style=\"[daySelectStyle(index, index1, item1)]\">\r\n\t\t\t\t\t\t<text v-if=\"getTopInfo(index, index1, item1)\"\r\n\t\t\t\t\t\tclass=\"uv-calendar-month__days__day__select__top-info\"\r\n\t\t\t\t\t\t:class=\"[item1.disabled && 'uv-calendar-month__days__day__select__top-info--disabled']\"\r\n\t\t\t\t\t\t:style=\"[textStyle(item1)]\"\r\n\t\t\t\t\t\t>{{ getTopInfo(index, index1, item1) }}</text>\r\n\t\t\t\t\t\t<text class=\"uv-calendar-month__days__day__select__info\"\r\n\t\t\t\t\t\t\t:class=\"[item1.disabled && 'uv-calendar-month__days__day__select__info--disabled']\"\r\n\t\t\t\t\t\t\t:style=\"[textStyle(item1)]\">{{ item1.day }}</text>\r\n\t\t\t\t\t\t<text v-if=\"getBottomInfo(index, index1, item1)\"\r\n\t\t\t\t\t\t\tclass=\"uv-calendar-month__days__day__select__buttom-info\"\r\n\t\t\t\t\t\t\t:class=\"[item1.disabled && 'uv-calendar-month__days__day__select__buttom-info--disabled']\"\r\n\t\t\t\t\t\t\t:style=\"[textStyle(item1)]\">{{ getBottomInfo(index, index1, item1) }}</text>\r\n\t\t\t\t\t\t<text v-if=\"item1.dot\" class=\"uv-calendar-month__days__day__select__dot\"></text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\t// #ifdef APP-NVUE\r\n\t// 由于nvue不支持百分比单位，需要查询宽度来计算每个日期的宽度\r\n\tconst dom = uni.requireNativePlugin('dom')\r\n\t// #endif\r\n\timport { colorGradient } from '@/uni_modules/uv-ui-tools/libs/function/colorGradient.js';\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport dayjs from '@/uni_modules/uv-ui-tools/libs/util/dayjs.js'\r\n\texport default {\r\n\t\tname: 'uv-calendar-month',\r\n\t\temits:['monthSelected','updateMonthTop','change'],\r\n\t\tmixins: [mpMixin, mixin],\r\n\t\tprops: {\r\n\t\t\t// 是否显示月份背景色\r\n\t\t\tshowMark: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: true\r\n\t\t\t},\r\n\t\t\t// 主题色，对底部按钮和选中日期有效\r\n\t\t\tcolor: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: '#3c9cff'\r\n\t\t\t},\r\n\t\t\t// 月份数据\r\n\t\t\tmonths: {\r\n\t\t\t\ttype: Array,\r\n\t\t\t\tdefault: () => []\r\n\t\t\t},\r\n\t\t\t// 日期选择类型\r\n\t\t\tmode: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: 'single'\r\n\t\t\t},\r\n\t\t\t// 日期行高\r\n\t\t\trowHeight: {\r\n\t\t\t\ttype: [String, Number],\r\n\t\t\t\tdefault: 58\r\n\t\t\t},\r\n\t\t\t// mode=multiple时，最多可选多少个日期\r\n\t\t\tmaxCount: {\r\n\t\t\t\ttype: [String, Number],\r\n\t\t\t\tdefault: Infinity\r\n\t\t\t},\r\n\t\t\t// mode=range时，第一个日期底部的提示文字\r\n\t\t\tstartText: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: '开始'\r\n\t\t\t},\r\n\t\t\t// mode=range时，最后一个日期底部的提示文字\r\n\t\t\tendText: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: '结束'\r\n\t\t\t},\r\n\t\t\t// 默认选中的日期，mode为multiple或range是必须为数组格式\r\n\t\t\tdefaultDate: {\r\n\t\t\t\ttype: [Array, String, Date],\r\n\t\t\t\tdefault: null\r\n\t\t\t},\r\n\t\t\t// 最小的可选日期\r\n\t\t\tminDate: {\r\n\t\t\t\ttype: [String, Number],\r\n\t\t\t\tdefault: 0\r\n\t\t\t},\r\n\t\t\t// 最大可选日期\r\n\t\t\tmaxDate: {\r\n\t\t\t\ttype: [String, Number],\r\n\t\t\t\tdefault: 0\r\n\t\t\t},\r\n\t\t\t// 如果没有设置maxDate，则往后推多少个月\r\n\t\t\tmaxMonth: {\r\n\t\t\t\ttype: [String, Number],\r\n\t\t\t\tdefault: 2\r\n\t\t\t},\r\n\t\t\t// 是否为只读状态，只读状态下禁止选择日期\r\n\t\t\treadonly: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: false\r\n\t\t\t},\r\n\t\t\t// 日期区间最多可选天数，默认无限制，mode = range时有效\r\n\t\t\tmaxRange: {\r\n\t\t\t\ttype: [Number, String],\r\n\t\t\t\tdefault: Infinity\r\n\t\t\t},\r\n\t\t\t// 范围选择超过最多可选天数时的提示文案，mode = range时有效\r\n\t\t\trangePrompt: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: ''\r\n\t\t\t},\r\n\t\t\t// 范围选择超过最多可选天数时，是否展示提示文案，mode = range时有效\r\n\t\t\tshowRangePrompt: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: true\r\n\t\t\t},\r\n\t\t\t// 是否允许日期范围的起止时间为同一天，mode = range时有效\r\n\t\t\tallowSameDay: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: false\r\n\t\t\t}\r\n\t\t},\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\t// 每个日期的宽度\r\n\t\t\t\twidth: 0,\r\n\t\t\t\t// 当前选中的日期item\r\n\t\t\t\titem: {},\r\n\t\t\t\tselected: []\r\n\t\t\t}\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\tselectedChange: {\r\n\t\t\t\timmediate: true,\r\n\t\t\t\thandler(n) {\r\n\t\t\t\t\tthis.setDefaultDate()\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\t// 多个条件的变化，会引起选中日期的变化，这里统一管理监听\r\n\t\t\tselectedChange() {\r\n\t\t\t\treturn [this.minDate, this.maxDate, this.defaultDate]\r\n\t\t\t},\r\n\t\t\tdayStyle(index1, index2, item) {\r\n\t\t\t\treturn (index1, index2, item) => {\r\n\t\t\t\t\tconst style = {}\r\n\t\t\t\t\tlet week = item.week\r\n\t\t\t\t\t// 不进行四舍五入的形式保留2位小数\r\n\t\t\t\t\tconst dayWidth = Number(parseFloat(this.width / 7).toFixed(3).slice(0, -1))\r\n\t\t\t\t\t// 得出每个日期的宽度\r\n\t\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\t\tstyle.width = this.$uv.addUnit(dayWidth)\r\n\t\t\t\t\t// #endif\r\n\t\t\t\t\tstyle.height = this.$uv.addUnit(this.rowHeight)\r\n\t\t\t\t\tif (index2 === 0) {\r\n\t\t\t\t\t\t// 获取当前为星期几，如果为0，则为星期天，减一为每月第一天时，需要向左偏移的item个数\r\n\t\t\t\t\t\tweek = (week === 0 ? 7 : week) - 1\r\n\t\t\t\t\t\tstyle.marginLeft = this.$uv.addUnit(week * dayWidth)\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (this.mode === 'range') {\r\n\t\t\t\t\t\t// 之所以需要这么写，是因为DCloud公司的iOS客户端的开发者能力有限导致的bug\r\n\t\t\t\t\t\tstyle.paddingLeft = 0\r\n\t\t\t\t\t\tstyle.paddingRight = 0\r\n\t\t\t\t\t\tstyle.paddingBottom = 0\r\n\t\t\t\t\t\tstyle.paddingTop = 0\r\n\t\t\t\t\t}\r\n\t\t\t\t\treturn style\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tdaySelectStyle() {\r\n\t\t\t\treturn (index1, index2, item) => {\r\n\t\t\t\t\tlet date = dayjs(item.date).format(\"YYYY-MM-DD\"),\r\n\t\t\t\t\t\tstyle = {}\r\n\t\t\t\t\t// 判断date是否在selected数组中，因为月份可能会需要补0，所以使用dateSame判断，而不用数组的includes判断\r\n\t\t\t\t\tif (this.selected.some(item => this.dateSame(item, date))) {\r\n\t\t\t\t\t\tstyle.backgroundColor = this.color\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (this.mode === 'single') {\r\n\t\t\t\t\t\tif (date === this.selected[0]) {\r\n\t\t\t\t\t\t\t// 因为需要对nvue的兼容，只能这么写，无法缩写，也无法通过类名控制等等\r\n\t\t\t\t\t\t\tstyle.borderTopLeftRadius = '3px'\r\n\t\t\t\t\t\t\tstyle.borderBottomLeftRadius = '3px'\r\n\t\t\t\t\t\t\tstyle.borderTopRightRadius = '3px'\r\n\t\t\t\t\t\t\tstyle.borderBottomRightRadius = '3px'\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t} else if (this.mode === 'range') {\r\n\t\t\t\t\t\tif (this.selected.length >= 2) {\r\n\t\t\t\t\t\t\tconst len = this.selected.length - 1\r\n\t\t\t\t\t\t\t// 第一个日期设置左上角和左下角的圆角\r\n\t\t\t\t\t\t\tif (this.dateSame(date, this.selected[0])) {\r\n\t\t\t\t\t\t\t\tstyle.borderTopLeftRadius = '3px'\r\n\t\t\t\t\t\t\t\tstyle.borderBottomLeftRadius = '3px'\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t// 最后一个日期设置右上角和右下角的圆角\r\n\t\t\t\t\t\t\tif (this.dateSame(date, this.selected[len])) {\r\n\t\t\t\t\t\t\t\tstyle.borderTopRightRadius = '3px'\r\n\t\t\t\t\t\t\t\tstyle.borderBottomRightRadius = '3px'\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t// 处于第一和最后一个之间的日期，背景色设置为浅色，通过将对应颜色进行等分，再取其尾部的颜色值\r\n\t\t\t\t\t\t\tif (dayjs(date).isAfter(dayjs(this.selected[0])) && dayjs(date).isBefore(dayjs(this\r\n\t\t\t\t\t\t\t\t\t.selected[len]))) {\r\n\t\t\t\t\t\t\t\tstyle.backgroundColor = colorGradient(this.color, '#ffffff', 100)[90]\r\n\t\t\t\t\t\t\t\t// 增加一个透明度，让范围区间的背景色也能看到底部的mark水印字符\r\n\t\t\t\t\t\t\t\tstyle.opacity = 0.7\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t} else if (this.selected.length === 1) {\r\n\t\t\t\t\t\t\t// 之所以需要这么写，是因为DCloud公司的iOS客户端的开发者能力有限导致的bug\r\n\t\t\t\t\t\t\t// 进行还原操作，否则在nvue的iOS，uni-app有bug，会导致诡异的表现\r\n\t\t\t\t\t\t\tstyle.borderTopLeftRadius = '3px'\r\n\t\t\t\t\t\t\tstyle.borderBottomLeftRadius = '3px'\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tif (this.selected.some(item => this.dateSame(item, date))) {\r\n\t\t\t\t\t\t\tstyle.borderTopLeftRadius = '3px'\r\n\t\t\t\t\t\t\tstyle.borderBottomLeftRadius = '3px'\r\n\t\t\t\t\t\t\tstyle.borderTopRightRadius = '3px'\r\n\t\t\t\t\t\t\tstyle.borderBottomRightRadius = '3px'\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\treturn style\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t// 某个日期是否被选中\r\n\t\t\ttextStyle() {\r\n\t\t\t\treturn (item) => {\r\n\t\t\t\t\tconst date = dayjs(item.date).format(\"YYYY-MM-DD\"),\r\n\t\t\t\t\t\tstyle = {}\r\n\t\t\t\t\t// 选中的日期，提示文字设置白色\r\n\t\t\t\t\tif (this.selected.some(item => this.dateSame(item, date))) {\r\n\t\t\t\t\t\tstyle.color = '#ffffff'\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (this.mode === 'range') {\r\n\t\t\t\t\t\tconst len = this.selected.length - 1\r\n\t\t\t\t\t\t// 如果是范围选择模式，第一个和最后一个之间的日期，文字颜色设置为高亮的主题色\r\n\t\t\t\t\t\tif (dayjs(date).isAfter(dayjs(this.selected[0])) && dayjs(date).isBefore(dayjs(this\r\n\t\t\t\t\t\t\t\t.selected[len]))) {\r\n\t\t\t\t\t\t\tstyle.color = this.color\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\treturn style\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t// 获取顶部的提示文字\r\n\t\t\tgetTopInfo() {\r\n\t\t\t\treturn (index1, index2, item) => {\r\n\t\t\t\t\treturn item.topInfo;\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t// 获取底部的提示文字\r\n\t\t\tgetBottomInfo() {\r\n\t\t\t\treturn (index1, index2, item) => {\r\n\t\t\t\t\tconst date = dayjs(item.date).format(\"YYYY-MM-DD\")\r\n\t\t\t\t\tconst bottomInfo = item.bottomInfo\r\n\t\t\t\t\t// 当为日期范围模式时，且选择的日期个数大于0时\r\n\t\t\t\t\tif (this.mode === 'range' && this.selected.length > 0) {\r\n\t\t\t\t\t\tif (this.selected.length === 1) {\r\n\t\t\t\t\t\t\t// 选择了一个日期时，如果当前日期为数组中的第一个日期，则显示底部文字为“开始”\r\n\t\t\t\t\t\t\tif (this.dateSame(date, this.selected[0])) return this.startText\r\n\t\t\t\t\t\t\telse return bottomInfo\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\tconst len = this.selected.length - 1\r\n\t\t\t\t\t\t\t// 如果数组中的日期大于2个时，第一个和最后一个显示为开始和结束日期\r\n\t\t\t\t\t\t\tif (this.dateSame(date, this.selected[0]) && this.dateSame(date, this.selected[1]) &&\r\n\t\t\t\t\t\t\t\tlen === 1) {\r\n\t\t\t\t\t\t\t\t// 如果长度为2，且第一个等于第二个日期，则提示语放在同一个item中\r\n\t\t\t\t\t\t\t\treturn `${this.startText}/${this.endText}`\r\n\t\t\t\t\t\t\t} else if (this.dateSame(date, this.selected[0])) {\r\n\t\t\t\t\t\t\t\treturn this.startText\r\n\t\t\t\t\t\t\t} else if (this.dateSame(date, this.selected[len])) {\r\n\t\t\t\t\t\t\t\treturn this.endText\r\n\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\treturn bottomInfo\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\treturn bottomInfo\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\tmounted() {\r\n\t\t\tthis.init()\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tinit() {\r\n\t\t\t\t// 初始化默认选中\r\n\t\t\t\tthis.$emit('monthSelected', this.selected)\r\n\t\t\t\tthis.$nextTick(() => {\r\n\t\t\t\t\t// 这里需要另一个延时，因为获取宽度后，会进行月份数据渲染，只有渲染完成之后，才有真正的高度\r\n\t\t\t\t\t// 因为nvue下，$nextTick并不是100%可靠的\r\n\t\t\t\t\tthis.$uv.sleep(10).then(() => {\r\n\t\t\t\t\t\tthis.getWrapperWidth()\r\n\t\t\t\t\t\tthis.getMonthRect()\r\n\t\t\t\t\t})\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\t// 判断两个日期是否相等\r\n\t\t\tdateSame(date1, date2) {\r\n\t\t\t\treturn dayjs(date1).isSame(dayjs(date2))\r\n\t\t\t},\r\n\t\t\t// 获取月份数据区域的宽度，因为nvue不支持百分比，所以无法通过css设置每个日期item的宽度\r\n\t\t\tgetWrapperWidth() {\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\tdom.getComponentRect(this.$refs['uv-calendar-month-wrapper'], res => {\r\n\t\t\t\t\tthis.width = res.size.width\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\tthis.$uvGetRect('.uv-calendar-month-wrapper').then(size => {\r\n\t\t\t\t\tthis.width = size.width\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\t\t\t},\r\n\t\t\tgetMonthRect() {\r\n\t\t\t\t// 获取每个月份数据的尺寸，用于父组件在scroll-view滚动事件中，监听当前滚动到了第几个月份\r\n\t\t\t\tconst promiseAllArr = this.months.map((item, index) => this.getMonthRectByPromise(\r\n\t\t\t\t\t`uv-calendar-month-${index}`))\r\n\t\t\t\t// 一次性返回\r\n\t\t\t\tPromise.all(promiseAllArr).then(\r\n\t\t\t\t\tsizes => {\r\n\t\t\t\t\t\tlet height = 1\r\n\t\t\t\t\t\tconst topArr = []\r\n\t\t\t\t\t\tfor (let i = 0; i < this.months.length; i++) {\r\n\t\t\t\t\t\t\t// 添加到months数组中，供scroll-view滚动事件中，判断当前滚动到哪个月份\r\n\t\t\t\t\t\t\ttopArr[i] = height\r\n\t\t\t\t\t\t\theight += sizes[i].height\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t// 由于微信下，无法通过this.months[i].top的形式(引用类型)去修改父组件的month的top值，所以使用事件形式对外发出\r\n\t\t\t\t\t\tthis.$emit('updateMonthTop', topArr)\r\n\t\t\t\t\t})\r\n\t\t\t},\r\n\t\t\t// 获取每个月份区域的尺寸\r\n\t\t\tgetMonthRectByPromise(el) {\r\n\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\t// $uvGetRect为uvui自带的节点查询简化方法，详见文档介绍：https://www.uvui.cn/js/getRect.html\r\n\t\t\t\t// 组件内部一般用this.$uvGetRect，对外的为getRect，二者功能一致，名称不同\r\n\t\t\t\treturn new Promise(resolve => {\r\n\t\t\t\t\tthis.$uvGetRect(`.${el}`).then(size => {\r\n\t\t\t\t\t\tresolve(size)\r\n\t\t\t\t\t})\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\t// nvue下，使用dom模块查询元素高度\r\n\t\t\t\t// 返回一个promise，让调用此方法的主体能使用then回调\r\n\t\t\t\treturn new Promise(resolve => {\r\n\t\t\t\t\tdom.getComponentRect(this.$refs[el][0], res => {\r\n\t\t\t\t\t\tresolve(res.size)\r\n\t\t\t\t\t})\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\t\t\t},\r\n\t\t\t// 点击某一个日期\r\n\t\t\tclickHandler(index1, index2, item) {\r\n\t\t\t\tif (this.readonly) {\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\t\t\t\tthis.item = item\r\n\t\t\t\tconst date = dayjs(item.date).format(\"YYYY-MM-DD\")\r\n\t\t\t\tif (item.disabled) return\r\n\t\t\t\t// 对上一次选择的日期数组进行深度克隆\r\n\t\t\t\tlet selected = this.$uv.deepClone(this.selected)\r\n\t\t\t\tif (this.mode === 'single') {\r\n\t\t\t\t\t// 单选情况下，让数组中的元素为当前点击的日期\r\n\t\t\t\t\tselected = [date]\r\n\t\t\t\t} else if (this.mode === 'multiple') {\r\n\t\t\t\t\tif (selected.some(item => this.dateSame(item, date))) {\r\n\t\t\t\t\t\t// 如果点击的日期已在数组中，则进行移除操作，也就是达到反选的效果\r\n\t\t\t\t\t\tconst itemIndex = selected.findIndex(item => dayjs(item).format(\"YYYY-MM-DD\") === dayjs(date).format(\"YYYY-MM-DD\"))\r\n\t\t\t\t\t\tselected.splice(itemIndex, 1)\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\t// 如果点击的日期不在数组中，且已有的长度小于总可选长度时，则添加到数组中去\r\n\t\t\t\t\t\tif (selected.length < this.maxCount) selected.push(date)\r\n\t\t\t\t\t}\r\n\t\t\t\t} else {\r\n\t\t\t\t\t// 选择区间形式\r\n\t\t\t\t\tif (selected.length === 0 || selected.length >= 2) {\r\n\t\t\t\t\t\t// 如果原来就为0或者大于2的长度，则当前点击的日期，就是开始日期\r\n\t\t\t\t\t\tselected = [date]\r\n\t\t\t\t\t} else if (selected.length === 1) {\r\n\t\t\t\t\t\t// 如果已经选择了开始日期\r\n\t\t\t\t\t\tconst existsDate = selected[0]\r\n\t\t\t\t\t\t// 如果当前选择的日期小于上一次选择的日期，则当前的日期定为开始日期\r\n\t\t\t\t\t\tif (dayjs(date).isBefore(existsDate)) {\r\n\t\t\t\t\t\t\tselected = [date]\r\n\t\t\t\t\t\t} else if (dayjs(date).isAfter(existsDate)) {\r\n\t\t\t\t\t\t\t// 当前日期减去最大可选的日期天数，如果大于起始时间，则进行提示\r\n\t\t\t\t\t\t\tif(dayjs(dayjs(date).subtract(this.maxRange, 'day')).isAfter(dayjs(selected[0])) && this.showRangePrompt) {\r\n\t\t\t\t\t\t\t\tif(this.rangePrompt) {\r\n\t\t\t\t\t\t\t\t\tthis.$uv.toast(this.rangePrompt)\r\n\t\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\t\tthis.$uv.toast(`选择天数不能超过 ${this.maxRange} 天`)\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\treturn\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t// 如果当前日期大于已有日期，将当前的添加到数组尾部\r\n\t\t\t\t\t\t\tselected.push(date)\r\n\t\t\t\t\t\t\tconst startDate = selected[0]\r\n\t\t\t\t\t\t\tconst endDate = selected[1]\r\n\t\t\t\t\t\t\tconst arr = []\r\n\t\t\t\t\t\t\tlet i = 0\r\n\t\t\t\t\t\t\tdo {\r\n\t\t\t\t\t\t\t\t// 将开始和结束日期之间的日期添加到数组中\r\n\t\t\t\t\t\t\t\tarr.push(dayjs(startDate).add(i, 'day').format(\"YYYY-MM-DD\"))\r\n\t\t\t\t\t\t\t\ti++\r\n\t\t\t\t\t\t\t\t// 累加的日期小于结束日期时，继续下一次的循环\r\n\t\t\t\t\t\t\t} while (dayjs(startDate).add(i, 'day').isBefore(dayjs(endDate)))\r\n\t\t\t\t\t\t\t// 为了一次性修改数组，避免computed中多次触发，这里才用arr变量一次性赋值的方式，同时将最后一个日期添加近来\r\n\t\t\t\t\t\t\tarr.push(endDate)\r\n\t\t\t\t\t\t\tselected = arr\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t// 选择区间时，只有一个日期的情况下，且不允许选择起止为同一天的话，不允许选择自己\r\n\t\t\t\t\t\t\tif (selected[0] === date && !this.allowSameDay) return\r\n\t\t\t\t\t\t\tselected.push(date)\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tthis.setSelected(selected)\r\n\t\t\t\tthis.$emit('change',{\r\n\t\t\t\t\tday: date,\r\n\t\t\t\t\tselected: selected\r\n\t\t\t\t});\r\n\t\t\t},\r\n\t\t\t// 设置默认日期\r\n\t\t\tsetDefaultDate() {\r\n\t\t\t\tif (!this.defaultDate) {\r\n\t\t\t\t\t// 如果没有设置默认日期，则将当天日期设置为默认选中的日期\r\n\t\t\t\t\tconst selected = [dayjs().format(\"YYYY-MM-DD\")]\r\n\t\t\t\t\treturn this.setSelected(selected, false)\r\n\t\t\t\t}\r\n\t\t\t\tlet defaultDate = []\r\n\t\t\t\tconst minDate = this.minDate || dayjs().format(\"YYYY-MM-DD\")\r\n\t\t\t\tconst maxDate = this.maxDate || dayjs(minDate).add(this.maxMonth - 1, 'month').format(\"YYYY-MM-DD\")\r\n\t\t\t\tif (this.mode === 'single') {\r\n\t\t\t\t\t// 单选模式，可以是字符串或数组，Date对象等\r\n\t\t\t\t\tif (!this.$uv.test.array(this.defaultDate)) {\r\n\t\t\t\t\t\tdefaultDate = [dayjs(this.defaultDate).format(\"YYYY-MM-DD\")]\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tdefaultDate = [this.defaultDate[0]]\r\n\t\t\t\t\t}\r\n\t\t\t\t} else {\r\n\t\t\t\t\t// 如果为非数组，则不执行\r\n\t\t\t\t\tif (!this.$uv.test.array(this.defaultDate)) return\r\n\t\t\t\t\tdefaultDate = this.defaultDate\r\n\t\t\t\t}\r\n\t\t\t\t// 过滤用户传递的默认数组，取出只在可允许最大值与最小值之间的元素\r\n\t\t\t\tdefaultDate = defaultDate.filter(item => {\r\n\t\t\t\t\treturn dayjs(item).isAfter(dayjs(minDate).subtract(1, 'day')) && dayjs(item).isBefore(dayjs(\r\n\t\t\t\t\t\tmaxDate).add(1, 'day'))\r\n\t\t\t\t})\r\n\t\t\t\tthis.setSelected(defaultDate, false)\r\n\t\t\t},\r\n\t\t\tsetSelected(selected, event = true) {\r\n\t\t\t\tthis.selected = selected\r\n\t\t\t\tevent && this.$emit('monthSelected', this.selected)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t.uv-calendar-month-wrapper {\r\n\t\tmargin-top: 4px;\r\n\t}\r\n\r\n\t.uv-calendar-month {\r\n\r\n\t\t&__title {\r\n\t\t\tfont-size: 14px;\r\n\t\t\tline-height: 42px;\r\n\t\t\theight: 42px;\r\n\t\t\tcolor: $uv-main-color;\r\n\t\t\ttext-align: center;\r\n\t\t\tfont-weight: bold;\r\n\t\t}\r\n\r\n\t\t&__days {\r\n\t\t\tposition: relative;\r\n\t\t\t@include flex;\r\n\t\t\tflex-wrap: wrap;\r\n\r\n\t\t\t&__month-mark-wrapper {\r\n\t\t\t\tposition: absolute;\r\n\t\t\t\ttop: 0;\r\n\t\t\t\tbottom: 0;\r\n\t\t\t\tleft: 0;\r\n\t\t\t\tright: 0;\r\n\t\t\t\t@include flex;\r\n\t\t\t\tjustify-content: center;\r\n\t\t\t\talign-items: center;\r\n\r\n\t\t\t\t&__text {\r\n\t\t\t\t\tfont-size: 155px;\r\n\t\t\t\t\tcolor: rgba(231, 232, 234, 0.83);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t&__day {\r\n\t\t\t\t@include flex;\r\n\t\t\t\tpadding: 2px;\r\n\t\t\t\t/* #ifndef APP-NVUE */\r\n\t\t\t\t// vue下使用css进行宽度计算，因为某些安卓机会无法进行js获取父元素宽度进行计算得出，会有偏移\r\n\t\t\t\twidth: calc(100% / 7);\r\n\t\t\t\tbox-sizing: border-box;\r\n\t\t\t\t/* #endif */\r\n\r\n\t\t\t\t&__select {\r\n\t\t\t\t\tflex: 1;\r\n\t\t\t\t\t@include flex;\r\n\t\t\t\t\talign-items: center;\r\n\t\t\t\t\tjustify-content: center;\r\n\t\t\t\t\tposition: relative;\r\n\r\n\t\t\t\t\t&__dot {\r\n\t\t\t\t\t\twidth: 7px;\r\n\t\t\t\t\t\theight: 7px;\r\n\t\t\t\t\t\tborder-radius: 100px;\r\n\t\t\t\t\t\tbackground-color: $uv-error;\r\n\t\t\t\t\t\tposition: absolute;\r\n\t\t\t\t\t\ttop: 12px;\r\n\t\t\t\t\t\tright: 7px;\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\t&__top-info {\r\n\t\t\t\t\t\tcolor: $uv-content-color;\r\n\t\t\t\t\t\ttext-align: center;\r\n\t\t\t\t\t\tposition: absolute;\r\n\t\t\t\t\t\ttop: 2px;\r\n\t\t\t\t\t\tfont-size: 10px;\r\n\t\t\t\t\t\ttext-align: center;\r\n\t\t\t\t\t\tleft: 0;\r\n\t\t\t\t\t\tright: 0;\r\n\t\t\t\t\t\t&--selected {\r\n\t\t\t\t\t\t\tcolor: #ffffff;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t&--disabled {\r\n\t\t\t\t\t\t\tcolor: #cacbcd;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\t&__buttom-info {\r\n\t\t\t\t\t\tcolor: $uv-content-color;\r\n\t\t\t\t\t\ttext-align: center;\r\n\t\t\t\t\t\tposition: absolute;\r\n\t\t\t\t\t\tbottom: 5px;\r\n\t\t\t\t\t\tfont-size: 10px;\r\n\t\t\t\t\t\ttext-align: center;\r\n\t\t\t\t\t\tleft: 0;\r\n\t\t\t\t\t\tright: 0;\r\n\r\n\t\t\t\t\t\t&--selected {\r\n\t\t\t\t\t\t\tcolor: #ffffff;\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t&--disabled {\r\n\t\t\t\t\t\t\tcolor: #cacbcd;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t&__info {\r\n\t\t\t\t\t\ttext-align: center;\r\n\t\t\t\t\t\tfont-size: 16px;\r\n\r\n\t\t\t\t\t\t&--selected {\r\n\t\t\t\t\t\t\tcolor: #ffffff;\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t&--disabled {\r\n\t\t\t\t\t\t\tcolor: #cacbcd;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t&--selected {\r\n\t\t\t\t\t\tbackground-color: $uv-primary;\r\n\t\t\t\t\t\t@include flex;\r\n\t\t\t\t\t\tjustify-content: center;\r\n\t\t\t\t\t\talign-items: center;\r\n\t\t\t\t\t\tflex: 1;\r\n\t\t\t\t\t\tborder-radius: 3px;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t&--range-selected {\r\n\t\t\t\t\t\topacity: 0.3;\r\n\t\t\t\t\t\tborder-radius: 0;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t&--range-start-selected {\r\n\t\t\t\t\t\tborder-top-right-radius: 0;\r\n\t\t\t\t\t\tborder-bottom-right-radius: 0;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t&--range-end-selected {\r\n\t\t\t\t\t\tborder-top-left-radius: 0;\r\n\t\t\t\t\t\tborder-bottom-left-radius: 0;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-calendar/components/uv-calendar/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 日历顶部标题\r\n\t\ttitle: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '日期选择'\r\n\t\t},\r\n\t\t// 是否显示标题\r\n\t\tshowTitle: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 是否显示副标题\r\n\t\tshowSubtitle: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 日期类型选择，single-选择单个日期，multiple-可以选择多个日期，range-选择日期范围\r\n\t\tmode: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'single'\r\n\t\t},\r\n\t\t// mode=range时，第一个日期底部的提示文字\r\n\t\tstartText: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '开始'\r\n\t\t},\r\n\t\t// mode=range时，最后一个日期底部的提示文字\r\n\t\tendText: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '结束'\r\n\t\t},\r\n\t\t// 自定义列表\r\n\t\tcustomList: {\r\n\t\t\ttype: Array,\r\n\t\t\tdefault: () => []\r\n\t\t},\r\n\t\t// 主题色，对底部按钮和选中日期有效\r\n\t\tcolor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#3c9cff'\r\n\t\t},\r\n\t\t// 最小的可选日期\r\n\t\tminDate: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 最大可选日期\r\n\t\tmaxDate: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 默认选中的日期，mode为multiple或range是必须为数组格式\r\n\t\tdefaultDate: {\r\n\t\t\ttype: [Array, String, Date, null],\r\n\t\t\tdefault: null\r\n\t\t},\r\n\t\t// mode=multiple时，最多可选多少个日期\r\n\t\tmaxCount: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: Number.MAX_SAFE_INTEGER\r\n\t\t},\r\n\t\t// 日期行高\r\n\t\trowHeight: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 56\r\n\t\t},\r\n\t\t// 日期格式化函数\r\n\t\tformatter: {\r\n\t\t\ttype: [Function, null],\r\n\t\t\tdefault: null\r\n\t\t},\r\n\t\t// 是否显示农历\r\n\t\tshowLunar: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否显示月份背景色\r\n\t\tshowMark: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 确定按钮的文字\r\n\t\tconfirmText: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '确定'\r\n\t\t},\r\n\t\t// 确认按钮处于禁用状态时的文字\r\n\t\tconfirmDisabledText: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '确定'\r\n\t\t},\r\n\t\t// 是否允许点击遮罩关闭日历\r\n\t\tcloseOnClickOverlay: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否允许点击确认按钮关闭日历\r\n\t\tcloseOnClickConfirm: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 是否为只读状态，只读状态下禁止选择日期\r\n\t\treadonly: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// \t是否展示确认按钮\r\n\t\tshowConfirm: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 日期区间最多可选天数，默认无限制，mode = range时有效 Infinity\r\n\t\tmaxRange: {\r\n\t\t\ttype: [Number, String],\r\n\t\t\tdefault: Number.MAX_SAFE_INTEGER\r\n\t\t},\r\n\t\t// 范围选择超过最多可选天数时的提示文案，mode = range时有效\r\n\t\trangePrompt: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 范围选择超过最多可选天数时，是否展示提示文案，mode = range时有效\r\n\t\tshowRangePrompt: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 是否允许日期范围的起止时间为同一天，mode = range时有效\r\n\t\tallowSameDay: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 圆角值\r\n\t\tround: {\r\n\t\t\ttype: [Boolean, String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 最多展示月份数量\r\n\t\tmonthNum: {\r\n\t\t\ttype: [Number, String],\r\n\t\t\tdefault: 3\r\n\t\t},\r\n\t\t...uni.$uv?.props?.calendar\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-calendar/components/uv-calendar/uv-calendar.vue",
    "content": "<template>\r\n\t<uv-popup\r\n\t\tref=\"calendarPopup\"\r\n\t\tmode=\"bottom\"\r\n\t\tcloseable\r\n\t\t:round=\"round\"\r\n\t\t:closeOnClickOverlay=\"closeOnClickOverlay\"\r\n\t\t@change=\"popupChange\"\r\n\t>\r\n\t\t<view class=\"uv-calendar\">\r\n\t\t\t<uvHeader\r\n\t\t\t\t:title=\"title\"\r\n\t\t\t\t:subtitle=\"subtitle\"\r\n\t\t\t\t:showSubtitle=\"showSubtitle\"\r\n\t\t\t\t:showTitle=\"showTitle\"\r\n\t\t\t></uvHeader>\r\n\t\t\t<scroll-view\r\n\t\t\t\t:style=\"{ height: $uv.addUnit(listHeight) }\"\r\n\t\t\t\tscroll-y\r\n\t\t\t\t@scroll=\"onScroll\"\r\n\t\t\t\t:scroll-top=\"scrollTop\"\r\n\t\t\t\t:scrollIntoView=\"scrollIntoView\"\r\n\t\t\t>\r\n\t\t\t\t<uvMonth\r\n\t\t\t\t\t:color=\"color\"\r\n\t\t\t\t\t:rowHeight=\"rowHeight\"\r\n\t\t\t\t\t:showMark=\"showMark\"\r\n\t\t\t\t\t:months=\"months\"\r\n\t\t\t\t\t:mode=\"mode\"\r\n\t\t\t\t\t:maxCount=\"maxCount\"\r\n\t\t\t\t\t:startText=\"startText\"\r\n\t\t\t\t\t:endText=\"endText\"\r\n\t\t\t\t\t:defaultDate=\"defaultDate\"\r\n\t\t\t\t\t:minDate=\"innerMinDate\"\r\n\t\t\t\t\t:maxDate=\"innerMaxDate\"\r\n\t\t\t\t\t:maxMonth=\"monthNum\"\r\n\t\t\t\t\t:readonly=\"readonly\"\r\n\t\t\t\t\t:maxRange=\"maxRange\"\r\n\t\t\t\t\t:rangePrompt=\"rangePrompt\"\r\n\t\t\t\t\t:showRangePrompt=\"showRangePrompt\"\r\n\t\t\t\t\t:allowSameDay=\"allowSameDay\"\r\n\t\t\t\t\tref=\"month\"\r\n\t\t\t\t\t@monthSelected=\"monthSelected\"\r\n\t\t\t\t\t@updateMonthTop=\"updateMonthTop\"\r\n\t\t\t\t\t@change=\"changeDay\"\r\n\t\t\t\t></uvMonth>\r\n\t\t\t</scroll-view>\r\n\t\t\t<slot name=\"footer\" v-if=\"showConfirm\">\r\n\t\t\t\t<view class=\"uv-calendar__confirm\">\r\n\t\t\t\t\t<uv-button\r\n\t\t\t\t\t\tshape=\"circle\"\r\n\t\t\t\t\t\t:text=\"buttonDisabled ? confirmDisabledText : confirmText\"\r\n\t\t\t\t\t\t:color=\"color\"\r\n\t\t\t\t\t\t@click=\"confirm\"\r\n\t\t\t\t\t\t:disabled=\"buttonDisabled\"\r\n\t\t\t\t\t></uv-button>\r\n\t\t\t\t</view>\r\n\t\t\t</slot>\r\n\t\t</view>\r\n\t</uv-popup>\r\n</template>\r\n\r\n<script>\r\nimport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\nimport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\nimport uvHeader from './header.vue'\r\nimport uvMonth from './month.vue'\r\nimport props from './props.js'\r\nimport dayjs from '@/uni_modules/uv-ui-tools/libs/util/dayjs.js'\r\nimport Calendar from './calendar.js'\r\n/**\r\n * Calendar 日历\r\n * @description  此组件用于单个选择日期，范围选择日期等，日历被包裹在底部弹起的容器中.\r\n * @tutorial https://www.uvui.cn/components/calendar.html\r\n *\r\n * @property {String}\t\t\t\ttitle\t\t\t\t标题内容 (默认 日期选择 )\r\n * @property {Boolean}\t\t\t\tshowTitle\t\t\t是否显示标题  (默认 true )\r\n * @property {Boolean}\t\t\t\tshowSubtitle\t\t是否显示副标题\t(默认 true )\r\n * @property {String}\t\t\t\tmode\t\t\t\t日期类型选择  single-选择单个日期，multiple-可以选择多个日期，range-选择日期范围 （ 默认 'single' )\r\n * @property {String}\t\t\t\tstartText\t\t\tmode=range时，第一个日期底部的提示文字  (默认 '开始' )\r\n * @property {String}\t\t\t\tendText\t\t\t\tmode=range时，最后一个日期底部的提示文字 (默认 '结束' )\r\n * @property {Array}\t\t\t\tcustomList\t\t\t自定义列表\r\n * @property {String}\t\t\t\tcolor\t\t\t\t主题色，对底部按钮和选中日期有效  (默认 ‘#3c9cff' )\r\n * @property {String | Number}\t\tminDate\t\t\t\t最小的可选日期\t (默认 0 )\r\n * @property {String | Number}\t\tmaxDate\t\t\t\t最大可选日期  (默认 0 )\r\n * @property {Array | String| Date}\tdefaultDate\t\t\t默认选中的日期，mode为multiple或range是必须为数组格式\r\n * @property {String | Number}\t\tmaxCount\t\t\tmode=multiple时，最多可选多少个日期  (默认 \tNumber.MAX_SAFE_INTEGER  )\r\n * @property {String | Number}\t\trowHeight\t\t\t日期行高 (默认 56 )\r\n * @property {Function}\t\t\t\tformatter\t\t\t日期格式化函数\r\n * @property {Boolean}\t\t\t\tshowLunar\t\t\t是否显示农历  (默认 false )\r\n * @property {Boolean}\t\t\t\tshowMark\t\t\t是否显示月份背景色 (默认 true )\r\n * @property {String}\t\t\t\tconfirmText\t\t\t确定按钮的文字 (默认 '确定' )\r\n * @property {String}\t\t\t\tconfirmDisabledText\t确认按钮处于禁用状态时的文字 (默认 '确定' )\r\n * @property {Boolean}\t\t\t\tshow\t\t\t\t是否显示日历弹窗 (默认 false )\r\n * @property {Boolean}\t\t\t\tcloseOnClickOverlay\t是否允许点击遮罩关闭日历 (默认 false )\r\n * @property {Boolean}\t\t\t\tcloseOnClickConfirm\t是否允许点击确认按钮关闭日历，设置为false不影响confirm事件返回 (默认 true )\r\n * @property {Boolean}\t\t\t\treadonly\t        是否为只读状态，只读状态下禁止选择日期 (默认 false )\r\n * @property {String | Number}\t\tmaxRange\t        日期区间最多可选天数，默认无限制，mode = range时有效\r\n * @property {String}\t\t\t\trangePrompt\t        范围选择超过最多可选天数时的提示文案，mode = range时有效\r\n * @property {Boolean}\t\t\t\tshowRangePrompt\t    范围选择超过最多可选天数时，是否展示提示文案，mode = range时有效 (默认 true )\r\n * @property {Boolean}\t\t\t\tallowSameDay\t    是否允许日期范围的起止时间为同一天，mode = range时有效 (默认 false )\r\n * @property {Number|String}\t    round\t\t\t\t圆角值，默认无圆角  (默认 0 )\r\n * @property {Number|String}\t    monthNum\t\t\t最多展示的月份数量  (默认 3 )\r\n *\r\n * @event {Function()} confirm \t\t点击确定按钮时触发\t\t选择日期相关的返回参数\r\n * @event {Function()} close \t\t日历关闭时触发\t\t\t可定义页面关闭时的回调事件\r\n * @example <uv-calendar ref=\"calendar\" :defaultDate=\"defaultDateMultiple\" mode=\"multiple\" @confirm=\"confirm\">\r\n\t</uv-calendar>\r\n * */\r\nexport default {\r\n\tname: 'uv-calendar',\r\n\temits:['confirm','close','change'],\r\n\tmixins: [mpMixin, mixin, props],\r\n\tcomponents: {\r\n\t\tuvHeader,\r\n\t\tuvMonth\r\n\t},\r\n\tdata() {\r\n\t\treturn {\r\n\t\t\t// 需要显示的月份的数组\r\n\t\t\tmonths: [],\r\n\t\t\t// 在月份滚动区域中，当前视图中月份的index索引\r\n\t\t\tmonthIndex: 0,\r\n\t\t\t// 月份滚动区域的高度\r\n\t\t\tlistHeight: 0,\r\n\t\t\t// month组件中选择的日期数组\r\n\t\t\tselected: [],\r\n\t\t\tscrollIntoView: '',\r\n\t\t\tscrollTop:0,\r\n\t\t\t// 过滤处理方法\r\n\t\t\tinnerFormatter: (value) => value\r\n\t\t}\r\n\t},\r\n\twatch: {\r\n\t\tselectedChange: {\r\n\t\t\timmediate: true,\r\n\t\t\thandler(n) {\r\n\t\t\t\tthis.setMonth()\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\tcomputed: {\r\n\t\t// 由于maxDate和minDate可以为字符串(2021-10-10)，或者数值(时间戳)，但是dayjs如果接受字符串形式的时间戳会有问题，这里进行处理\r\n\t\tinnerMaxDate() {\r\n\t\t\treturn this.$uv.test.number(this.maxDate)\r\n\t\t\t\t? Number(this.maxDate)\r\n\t\t\t\t: this.maxDate\r\n\t\t},\r\n\t\tinnerMinDate() {\r\n\t\t\treturn this.$uv.test.number(this.minDate)\r\n\t\t\t\t? Number(this.minDate)\r\n\t\t\t\t: this.minDate\r\n\t\t},\r\n\t\t// 多个条件的变化，会引起选中日期的变化，这里统一管理监听\r\n\t\tselectedChange() {\r\n\t\t\treturn [this.innerMinDate, this.innerMaxDate, this.defaultDate]\r\n\t\t},\r\n\t\tsubtitle() {\r\n\t\t\t// 初始化时，this.months为空数组，所以需要特别判断处理\r\n\t\t\tif (this.months.length) {\r\n\t\t\t\treturn `${this.months[this.monthIndex].year}年${\r\n\t\t\t\t\tthis.months[this.monthIndex].month\r\n\t\t\t\t}月`\r\n\t\t\t} else {\r\n\t\t\t\treturn ''\r\n\t\t\t}\r\n\t\t},\r\n\t\tbuttonDisabled() {\r\n\t\t\t// 如果为range类型，且选择的日期个数不足1个时，让底部的按钮出于disabled状态\r\n\t\t\tif (this.mode === 'range') {\r\n\t\t\t\tif (this.selected.length <= 1) {\r\n\t\t\t\t\treturn true\r\n\t\t\t\t} else {\r\n\t\t\t\t\treturn false\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\treturn false\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\tmounted() {\r\n\t\tthis.start = Date.now()\r\n\t\tthis.init()\r\n\t},\r\n\tmethods: {\r\n\t\t// 在微信小程序中，不支持将函数当做props参数，故只能通过ref形式调用\r\n\t\tsetFormatter(e) {\r\n\t\t\tthis.innerFormatter = e\r\n\t\t},\r\n\t\t// 点击日期框触发\r\n\t\tchangeDay(e) {\r\n\t\t\tthis.$emit('change',e);\r\n\t\t},\r\n\t\t// month组件内部选择日期后，通过事件通知给父组件\r\n\t\tmonthSelected(e) {\r\n\t\t\tthis.selected = e\r\n\t\t\tif (!this.showConfirm) {\r\n\t\t\t\t// 在不需要确认按钮的情况下，如果为单选，或者范围多选且已选长度大于2，则直接进行返还\r\n\t\t\t\tif (\r\n\t\t\t\t\tthis.mode === 'multiple' ||\r\n\t\t\t\t\tthis.mode === 'single' ||\r\n\t\t\t\t\t(this.mode === 'range' && this.selected.length >= 2)\r\n\t\t\t\t) {\r\n\t\t\t\t\tthis.$emit('confirm', this.selected)\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\tinit() {\r\n\t\t\t// 校验maxDate，不能小于minDate\r\n\t\t\tif (\r\n\t\t\t\tthis.innerMaxDate &&\r\n\t\t\t\tthis.innerMinDate &&\r\n\t\t\t\tnew Date(this.innerMaxDate).getTime() < new Date(this.innerMinDate).getTime()\r\n\t\t\t) {\r\n\t\t\t\treturn this.$uv.error('maxDate不能小于minDate')\r\n\t\t\t}\r\n\t\t\t// 滚动区域的高度\r\n\t\t\tthis.listHeight = this.rowHeight * 5 + 30\r\n\t\t\tthis.setMonth()\r\n\t\t},\r\n\t\topen() {\r\n\t\t\tthis.setMonth()\r\n\t\t\tthis.$refs.calendarPopup.open();\r\n\t\t},\r\n\t\tpopupChange(e) {\r\n\t\t\tif(!e.show) {\r\n\t\t\t\tthis.$emit('close');\r\n\t\t\t}\r\n\t\t},\r\n\t\t// 点击确定按钮\r\n\t\tconfirm() {\r\n\t\t\tif (!this.buttonDisabled) {\r\n\t\t\t\tthis.$emit('confirm', this.selected)\r\n\t\t\t}\r\n\t\t\tif (this.closeOnClickConfirm) {\r\n\t\t\t\tthis.$refs.calendarPopup.close();\r\n\t\t\t}\r\n\t\t},\r\n\t\t// 获得两个日期之间的月份数\r\n\t\tgetMonths(minDate, maxDate) {\r\n\t\t\tconst minYear = dayjs(minDate).year()\r\n\t\t\tconst minMonth = dayjs(minDate).month() + 1\r\n\t\t\tconst maxYear = dayjs(maxDate).year()\r\n\t\t\tconst maxMonth = dayjs(maxDate).month() + 1\r\n\t\t\treturn (maxYear - minYear) * 12 + (maxMonth - minMonth) + 1\r\n\t\t},\r\n\t\t// 设置月份数据\r\n\t\tsetMonth() {\r\n\t\t\t// 最小日期的毫秒数\r\n\t\t\tconst minDate = this.innerMinDate || dayjs().valueOf()\r\n\t\t\t// 如果没有指定最大日期，则往后推3个月\r\n\t\t\tconst maxDate =\r\n\t\t\t\tthis.innerMaxDate ||\r\n\t\t\t\tdayjs(minDate)\r\n\t\t\t\t\t.add(this.monthNum - 1, 'month')\r\n\t\t\t\t\t.valueOf()\r\n\t\t\t// 最大最小月份之间的共有多少个月份，\r\n\t\t\tconst months = this.$uv.range(\r\n\t\t\t\t1,\r\n\t\t\t\tthis.monthNum,\r\n\t\t\t\tthis.getMonths(minDate, maxDate)\r\n\t\t\t)\r\n\t\t\t// 先清空数组\r\n\t\t\tthis.months = []\r\n\t\t\tfor (let i = 0; i < months; i++) {\r\n\t\t\t\tthis.months.push({\r\n\t\t\t\t\tdate: new Array(\r\n\t\t\t\t\t\tdayjs(minDate).add(i, 'month').daysInMonth()\r\n\t\t\t\t\t)\r\n\t\t\t\t\t\t.fill(1)\r\n\t\t\t\t\t\t.map((item, index) => {\r\n\t\t\t\t\t\t\t// 日期，取值1-31\r\n\t\t\t\t\t\t\tlet day = index + 1\r\n\t\t\t\t\t\t\t// 星期，0-6，0为周日\r\n\t\t\t\t\t\t\tconst week = dayjs(minDate)\r\n\t\t\t\t\t\t\t\t.add(i, 'month')\r\n\t\t\t\t\t\t\t\t.date(day)\r\n\t\t\t\t\t\t\t\t.day()\r\n\t\t\t\t\t\t\tconst date = dayjs(minDate)\r\n\t\t\t\t\t\t\t\t.add(i, 'month')\r\n\t\t\t\t\t\t\t\t.date(day)\r\n\t\t\t\t\t\t\t\t.format('YYYY-MM-DD')\r\n\t\t\t\t\t\t\tlet topInfo = ''\r\n\t\t\t\t\t\t\tlet bottomInfo = ''\r\n\t\t\t\t\t\t\tif (this.showLunar) {\r\n\t\t\t\t\t\t\t\t// 将日期转为农历格式\r\n\t\t\t\t\t\t\t\tconst lunar = Calendar.solar2lunar(\r\n\t\t\t\t\t\t\t\t\tdayjs(date).year(),\r\n\t\t\t\t\t\t\t\t\tdayjs(date).month() + 1,\r\n\t\t\t\t\t\t\t\t\tdayjs(date).date()\r\n\t\t\t\t\t\t\t\t)\r\n\t\t\t\t\t\t\t\tbottomInfo = lunar.IDayCn\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\tlet config = {\r\n\t\t\t\t\t\t\t\tday,\r\n\t\t\t\t\t\t\t\tweek,\r\n\t\t\t\t\t\t\t\t// 小于最小允许的日期，或者大于最大的日期，则设置为disabled状态\r\n\t\t\t\t\t\t\t\tdisabled:\r\n\t\t\t\t\t\t\t\t\tdayjs(date).isBefore(\r\n\t\t\t\t\t\t\t\t\t\tdayjs(minDate).format('YYYY-MM-DD')\r\n\t\t\t\t\t\t\t\t\t) ||\r\n\t\t\t\t\t\t\t\t\tdayjs(date).isAfter(\r\n\t\t\t\t\t\t\t\t\t\tdayjs(maxDate).format('YYYY-MM-DD')\r\n\t\t\t\t\t\t\t\t\t),\r\n\t\t\t\t\t\t\t\t// 返回一个日期对象，供外部的formatter获取当前日期的年月日等信息，进行加工处理\r\n\t\t\t\t\t\t\t\tdate: new Date(date),\r\n\t\t\t\t\t\t\t\ttopInfo,\r\n\t\t\t\t\t\t\t\tbottomInfo,\r\n\t\t\t\t\t\t\t\tdot: false,\r\n\t\t\t\t\t\t\t\tmonth:\r\n\t\t\t\t\t\t\t\t\tdayjs(minDate).add(i, 'month').month() + 1\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\tconst formatter =\r\n\t\t\t\t\t\t\t\tthis.formatter || this.innerFormatter\r\n\t\t\t\t\t\t\treturn formatter(config)\r\n\t\t\t\t\t\t}),\r\n\t\t\t\t\t// 当前所属的月份\r\n\t\t\t\t\tmonth: dayjs(minDate).add(i, 'month').month() + 1,\r\n\t\t\t\t\t// 当前年份\r\n\t\t\t\t\tyear: dayjs(minDate).add(i, 'month').year()\r\n\t\t\t\t})\r\n\t\t\t}\r\n\r\n\t\t},\r\n\t\t// 滚动到默认设置的月份\r\n\t\tscrollIntoDefaultMonth(selected) {\r\n\t\t\t// 查询默认日期在可选列表的下标\r\n\t\t\tconst _index = this.months.findIndex(({\r\n\t\t\t\t  year,\r\n\t\t\t\t  month\r\n\t\t\t  }) => {\r\n\t\t\t\tmonth = this.$uv.padZero(month)\r\n\t\t\t\treturn `${year}-${month}` === selected\r\n\t\t\t})\r\n\t\t\tif (_index !== -1) {\r\n\t\t\t\t// #ifndef MP-WEIXIN\r\n\t\t\t\tthis.$nextTick(() => {\r\n\t\t\t\t\tthis.scrollIntoView = `month-${_index}`\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\t\t\t\t// #ifdef MP-WEIXIN\r\n\t\t\t\tthis.scrollTop = this.months[_index].top || 0;\r\n\t\t\t\t// #endif\r\n\t\t\t}\r\n\t\t},\r\n\t\t// scroll-view滚动监听\r\n\t\tonScroll(event) {\r\n\t\t\t// 不允许小于0的滚动值，如果scroll-view到顶了，继续下拉，会出现负数值\r\n\t\t\tconst scrollTop = Math.max(0, event.detail.scrollTop)\r\n\t\t\t// 将当前滚动条数值，除以滚动区域的高度，可以得出当前滚动到了哪一个月份的索引\r\n\t\t\tfor (let i = 0; i < this.months.length; i++) {\r\n\t\t\t\tif (scrollTop >= (this.months[i].top || this.listHeight)) {\r\n\t\t\t\t\tthis.monthIndex = i\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\t// 更新月份的top值\r\n\t\tupdateMonthTop(topArr = []) {\r\n\t\t\t// 设置对应月份的top值，用于onScroll方法更新月份\r\n\t\t\ttopArr.map((item, index) => {\r\n\t\t\t\tthis.months[index].top = item\r\n\t\t\t})\r\n\r\n\t\t\t// 获取默认日期的下标\r\n\t\t\tif (!this.defaultDate) {\r\n\t\t\t\t// 如果没有设置默认日期，则将当天日期设置为默认选中的日期\r\n\t\t\t\tconst selected = dayjs().format(\"YYYY-MM\")\r\n\t\t\t\tthis.scrollIntoDefaultMonth(selected)\r\n\t\t\t\treturn\r\n\t\t\t}\r\n\t\t\tlet selected = dayjs().format(\"YYYY-MM\");\r\n\t\t\t// 单选模式，可以是字符串或数组，Date对象等\r\n\t\t\tif (!this.$uv.test.array(this.defaultDate)) {\r\n\t\t\t\tselected = dayjs(this.defaultDate).format(\"YYYY-MM\")\r\n\t\t\t} else {\r\n\t\t\t\tselected = dayjs(this.defaultDate[0]).format(\"YYYY-MM\");\r\n\t\t\t}\r\n\t\t\tthis.scrollIntoDefaultMonth(selected)\r\n\t\t}\r\n\t}\r\n}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n.uv-calendar {\r\n\t&__confirm {\r\n\t\tpadding: 7px 18px;\r\n\t}\r\n}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-calendar/package.json",
    "content": "{\r\n\t\"id\": \"uv-calendar\",\r\n\t\"displayName\": \"uv-calendar 日历 全面兼容小程序、nvue、vue2、vue3等多端\",\r\n\t\"version\": \"1.0.5\",\r\n\t\"description\": \"日历组件用于单个选择日期，范围选择日期等，日历被包裹在底部弹起的容器中，灵活配置，功能齐全，兼容全端。\",\r\n\t\"keywords\": [\r\n        \"uv-calendar\",\r\n        \"uvui\",\r\n        \"uv-ui\",\r\n        \"calendar\",\r\n        \"日历\"\r\n    ],\r\n\t\"repository\": \"\",\r\n\t\"engines\": {\r\n\t\t\"HBuilderX\": \"^3.1.0\"\r\n\t},\r\n\t\"dcloudext\": {\r\n\t\t\"type\": \"component-vue\",\r\n\t\t\"sale\": {\r\n\t\t\t\"regular\": {\r\n\t\t\t\t\"price\": \"0.00\"\r\n\t\t\t},\r\n\t\t\t\"sourcecode\": {\r\n\t\t\t\t\"price\": \"0.00\"\r\n\t\t\t}\r\n\t\t},\r\n\t\t\"contact\": {\r\n\t\t\t\"qq\": \"\"\r\n\t\t},\r\n\t\t\"declaration\": {\r\n\t\t\t\"ads\": \"无\",\r\n\t\t\t\"data\": \"插件不采集任何数据\",\r\n\t\t\t\"permissions\": \"无\"\r\n\t\t},\r\n\t\t\"npmurl\": \"\"\r\n\t},\r\n\t\"uni_modules\": {\r\n\t\t\"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-button\",\r\n\t\t\t\"uv-popup\"\r\n\t\t],\r\n\t\t\"encrypt\": [],\r\n\t\t\"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-calendar/readme.md",
    "content": "## Calendar 日历 \r\n\r\n> **组件名：uv-calendar**\r\n\r\n此组件用于单个选择日期，范围选择日期等，日历被包裹在底部弹起的容器中。灵活配置，功能齐全，兼容全端。\r\n\r\n### <a href=\"https://www.uvui.cn/components/calendar.html\" target=\"_blank\">查看文档</a>\r\n\r\n### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n#### 如使用过程中有任何问题，或者您对uv-ui有一些好的建议，欢迎加入 uv-ui 交流群：<a href=\"https://ext.dcloud.net.cn/plugin?id=12287\" target=\"_blank\">uv-ui</a>、<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-calendars/changelog.md",
    "content": "## 1.0.14（2023-10-12）\n1. 修复selected没有设置了info或者info设置为空字符串后，文本则无法恢复BUG\n## 1.0.13（2023-09-19）\r\n1. 修复range模式下，selected设置了info后选中后，导致文本不恢复的问题\r\n2. 修复multiple模式下，selected自定义信息的颜色没变，依然是白色\r\n## 1.0.12（2023-09-14）\r\n1. 优化\r\n## 1.0.11（2023-09-14）\r\n1. 增加allowSameDay参数，是否允许日期范围的起止时间为同一天，mode = range时有效\r\n2. 修复在vue2+小程序渲染时闪烁的问题\r\n## 1.0.10（2023-09-07）\r\n1. 修复国际化失效的BUG\r\n## 1.0.9（2023-09-01）\r\n1. 修复在pages.json中设置easycom会报错的BUG\r\n## 1.0.8（2023-08-29）\r\n1. 修复mainjs中设置setConfig修改属性不生效的问题，出自评论区：https://ext.dcloud.net.cn/plugin?id=12287\r\n## 1.0.7（2023-08-26）\r\n1. 去除range参数，由mode=\"range\"替换\r\n2. 新增mode参数，不传 / multiple / range，分别为单日期， 多个日期，选择日期范围\r\n3. 与uv-calendar选择日期的功能保持一致\r\n## 1.0.6（2023-08-25）\r\n1. 修复点击返回今天按钮时，monthSwitch方法回调参数返回月份不是当天对应月份：https://github.com/climblee/uv-ui/issues/7\r\n## 1.0.5（2023-08-13）\r\n1. 修复选择月份弹窗层级的问题\r\n## 1.0.4（2023-08-06）\r\n1. 优化\r\n## 1.0.3（2023-08-06）\r\n1. 修复高度不对的BUG\r\n2. 修复文案在小屏幕的BUG\r\n## 1.0.2（2023-08-05）\r\n1. 增加startText参数\r\n2. 增加endText参数\r\n3. 增加selected中的参数\r\n4. 优化日历范围选择\r\n## 1.0.1（2023-08-04）\r\n1. 修复 自定义主题时 颜色错误的BUG\r\n## 1.0.0（2023-08-03）\r\n1. 新增 uv-calendars 新版日历发布\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-calendars/components/uv-calendars/calendar-body.vue",
    "content": "<template>\r\n\t<view class=\"uv-calendar-body\">\r\n\t\t<view class=\"uv-calendar__header\">\r\n\t\t\t<view class=\"uv-calendar__header-btn-box\" @click.stop=\"pre\">\r\n\t\t\t\t<view class=\"uv-calendar__header-btn uv-calendar--left\"></view>\r\n\t\t\t</view>\r\n\t\t\t<picker mode=\"date\" :value=\"getDate\" fields=\"month\" @change=\"bindDateChange\">\r\n\t\t\t\t<text class=\"uv-calendar__header-text\">{{ (nowDate.year||'') +' / '+( nowDate.month||'')}}</text>\r\n\t\t\t</picker>\r\n\t\t\t<view class=\"uv-calendar__header-btn-box\" @click.stop=\"next\">\r\n\t\t\t\t<view class=\"uv-calendar__header-btn uv-calendar--right\"></view>\r\n\t\t\t</view>\r\n\t\t\t<text class=\"uv-calendar__backtoday\" @click=\"backToday\">{{todayText}}</text>\r\n\t\t</view>\r\n\t\t<view class=\"uv-calendar__box\">\r\n\t\t\t<view v-if=\"showMonth\" class=\"uv-calendar__box-bg\">\r\n\t\t\t\t<text class=\"uv-calendar__box-bg-text\">{{nowDate.month}}</text>\r\n\t\t\t</view>\r\n\t\t\t<view class=\"uv-calendar__weeks uv-calendar__weeks-week\">\r\n\t\t\t\t<view class=\"uv-calendar__weeks-day\">\r\n\t\t\t\t\t<text class=\"uv-calendar__weeks-day-text\">{{SUNText}}</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view class=\"uv-calendar__weeks-day\">\r\n\t\t\t\t\t<text class=\"uv-calendar__weeks-day-text\">{{monText}}</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view class=\"uv-calendar__weeks-day\">\r\n\t\t\t\t\t<text class=\"uv-calendar__weeks-day-text\">{{TUEText}}</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view class=\"uv-calendar__weeks-day\">\r\n\t\t\t\t\t<text class=\"uv-calendar__weeks-day-text\">{{WEDText}}</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view class=\"uv-calendar__weeks-day\">\r\n\t\t\t\t\t<text class=\"uv-calendar__weeks-day-text\">{{THUText}}</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view class=\"uv-calendar__weeks-day\">\r\n\t\t\t\t\t<text class=\"uv-calendar__weeks-day-text\">{{FRIText}}</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view class=\"uv-calendar__weeks-day\">\r\n\t\t\t\t\t<text class=\"uv-calendar__weeks-day-text\">{{SATText}}</text>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t\t<view class=\"uv-calendar__weeks\" v-for=\"(item,weekIndex) in weeks\" :key=\"weekIndex\">\r\n\t\t\t\t<view class=\"uv-calendar__weeks-item\" v-for=\"(weeks,weeksIndex) in item\" :key=\"weeksIndex\">\r\n\t\t\t\t\t<calendar-item class=\"uv-calendar-item--hook\" :weeks=\"weeks\" :rangeInfoText=\"rangeInfoText(weeks)\" :multiple=\"multiple\" :range=\"range\" :calendar=\"calendar\" :selected=\"selected\" :lunar=\"lunar\" :color=\"color\" @change=\"choiceDate\"></calendar-item>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js';\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js';\r\n\r\n\timport CalendarItem from './calendar-item.vue';\r\n\r\n\timport { initVueI18n } from '@dcloudio/uni-i18n';\r\n\timport i18nMessages from './i18n/index.js';\r\n\tconst { t } = initVueI18n(i18nMessages);\r\n\texport default {\r\n\t\tmixins: [mpMixin, mixin],\r\n\t\tcomponents: {\r\n\t\t\tCalendarItem\r\n\t\t},\r\n\t\tprops: {\r\n\t\t\tdate: {\r\n\t\t\t\ttype: [String,Array],\r\n\t\t\t\tdefault: ''\r\n\t\t\t},\r\n\t\t\tnowDate: {\r\n\t\t\t\ttype: [String, Object],\r\n\t\t\t\tdefault: ''\r\n\t\t\t},\r\n\t\t\tweeks: {\r\n\t\t\t\ttype: [Array, Object],\r\n\t\t\t\tdefault () {\r\n\t\t\t\t\treturn []\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tcalendar: {\r\n\t\t\t\ttype: Object,\r\n\t\t\t\tdefault () {\r\n\t\t\t\t\treturn {}\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tselected: {\r\n\t\t\t\ttype: Array,\r\n\t\t\t\tdefault () {\r\n\t\t\t\t\treturn []\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tlunar: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: false\r\n\t\t\t},\r\n\t\t\tshowMonth: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: true\r\n\t\t\t},\r\n\t\t\tcolor: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: '#3c9cff'\r\n\t\t\t},\r\n\t\t\tstartText: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: '开始'\r\n\t\t\t},\r\n\t\t\tendText: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: '结束'\r\n\t\t\t},\r\n\t\t\trange: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: false\r\n\t\t\t},\r\n\t\t\tmultiple: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: false\r\n\t\t\t},\r\n\t\t\t// 是否允许日期范围的起止时间为同一天，mode = range时有效\r\n\t\t\tallowSameDay: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: false\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\tgetDate() {\r\n\t\t\t\treturn Array.isArray(this.date) ? this.date[0] : this.date;\r\n\t\t\t},\r\n\t\t\t/**\r\n\t\t\t * for i18n\r\n\t\t\t */\r\n\t\t\ttodayText() {\r\n\t\t\t\treturn t(\"uv-calender.today\")\r\n\t\t\t},\r\n\t\t\tmonText() {\r\n\t\t\t\treturn t(\"uv-calender.MON\")\r\n\t\t\t},\r\n\t\t\tTUEText() {\r\n\t\t\t\treturn t(\"uv-calender.TUE\")\r\n\t\t\t},\r\n\t\t\tWEDText() {\r\n\t\t\t\treturn t(\"uv-calender.WED\")\r\n\t\t\t},\r\n\t\t\tTHUText() {\r\n\t\t\t\treturn t(\"uv-calender.THU\")\r\n\t\t\t},\r\n\t\t\tFRIText() {\r\n\t\t\t\treturn t(\"uv-calender.FRI\")\r\n\t\t\t},\r\n\t\t\tSATText() {\r\n\t\t\t\treturn t(\"uv-calender.SAT\")\r\n\t\t\t},\r\n\t\t\tSUNText() {\r\n\t\t\t\treturn t(\"uv-calender.SUN\")\r\n\t\t\t},\r\n\t\t\trangeInfoText(weeks) {\r\n\t\t\t\treturn weeks=> {\r\n\t\t\t\t\tif(this.allowSameDay && weeks.beforeRange && weeks.afterRange && weeks.dateEqual) {\r\n\t\t\t\t\t\treturn this.setInfo(weeks,`${this.startText}/${this.endText}`);\r\n\t\t\t\t\t} \r\n\t\t\t\t\tif(weeks.beforeRange) {\r\n\t\t\t\t\t\treturn this.setInfo(weeks,this.startText);\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif(weeks.afterRange) {\r\n\t\t\t\t\t\treturn this.setInfo(weeks,this.endText);\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif(weeks.extraInfo?.info_old == ' ') {\r\n\t\t\t\t\t\tweeks.extraInfo.info = null;\r\n\t\t\t\t\t}else if(weeks.extraInfo?.info_old) {\r\n\t\t\t\t\t\tweeks.extraInfo.info = weeks.extraInfo.info_old;\r\n\t\t\t\t\t} \r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tsetInfo(weeks,text) {\r\n\t\t\t\tthis.setInfoOld(weeks);\r\n\t\t\t\tif(weeks.extraInfo) {\r\n\t\t\t\t\tweeks.extraInfo.info = text;\r\n\t\t\t\t}else {\r\n\t\t\t\t\tweeks.extraInfo = {\r\n\t\t\t\t\t\tinfo: text\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tsetInfoOld(weeks) {\r\n\t\t\t\tif(weeks.extraInfo) {\r\n\t\t\t\t\tweeks.extraInfo.info_old = weeks.extraInfo.info ? weeks.extraInfo.info_old || weeks.extraInfo.info : ' ';\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tbindDateChange(e) {\r\n\t\t\t\tthis.$emit('bindDateChange', e);\r\n\t\t\t},\r\n\t\t\tbackToday() {\r\n\t\t\t\tthis.$emit('backToday');\r\n\t\t\t},\r\n\t\t\tpre() {\r\n\t\t\t\tthis.$emit('pre');\r\n\t\t\t},\r\n\t\t\tnext() {\r\n\t\t\t\tthis.$emit('next');\r\n\t\t\t},\r\n\t\t\tchoiceDate(e) {\r\n\t\t\t\tthis.$emit('choiceDate', e);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n\t@mixin flex($direction: row) {\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\tdisplay: flex;\r\n\t\t/* #endif */\r\n\t\tflex-direction: $direction;\r\n\t}\r\n\t$uv-bg-color-mask: rgba($color: #000000, $alpha: 0.4);\r\n\t$uv-border-color: #EDEDED !default;\r\n\t$uv-text-color: #333;\r\n\t$uv-bg-color-hover: #f1f1f1;\r\n\t$uv-font-size-base: 14px;\r\n\t$uv-text-color-placeholder: #808080;\r\n\t$uv-color-subtitle: #555555;\r\n\t$uv-text-color-grey: #999;\r\n\t.uv-calendar {\r\n\t\t@include flex(column);\r\n\t}\r\n\t.uv-calendar__mask {\r\n\t\tposition: fixed;\r\n\t\tbottom: 0;\r\n\t\ttop: 0;\r\n\t\tleft: 0;\r\n\t\tright: 0;\r\n\t\tbackground-color: $uv-bg-color-mask;\r\n\t\ttransition-property: opacity;\r\n\t\ttransition-duration: 0.3s;\r\n\t\topacity: 0;\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\tz-index: 99;\r\n\t\t/* #endif */\r\n\t}\r\n\t.uv-calendar--mask-show {\r\n\t\topacity: 1\r\n\t}\r\n\t.uv-calendar--fixed {\r\n\t\tposition: fixed;\r\n\t\t/* #ifdef APP-NVUE */\r\n\t\tbottom: 0;\r\n\t\t/* #endif */\r\n\t\tleft: 0;\r\n\t\tright: 0;\r\n\t\ttransition-property: transform;\r\n\t\ttransition-duration: 0.3s;\r\n\t\ttransform: translateY(460px);\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\tbottom: calc(var(--window-bottom));\r\n\t\tz-index: 99;\r\n\t\t/* #endif */\r\n\t}\r\n\t.uv-calendar--ani-show {\r\n\t\ttransform: translateY(0);\r\n\t}\r\n\t.uv-calendar__content {\r\n\t\tbackground-color: #fff;\r\n\t}\r\n\t.uv-calendar__header {\r\n\t\tposition: relative;\r\n\t\t@include flex;\r\n\t\tjustify-content: center;\r\n\t\talign-items: center;\r\n\t\theight: 50px;\r\n\t\tborder-bottom-color: $uv-border-color;\r\n\t\tborder-bottom-style: solid;\r\n\t\tborder-bottom-width: 1px;\r\n\t}\r\n\t.uv-calendar--fixed-top {\r\n\t\t@include flex;\r\n\t\tjustify-content: space-between;\r\n\t\tborder-top-color: $uv-border-color;\r\n\t\tborder-top-style: solid;\r\n\t\tborder-top-width: 1px;\r\n\t}\r\n\t.uv-calendar--fixed-width {\r\n\t\twidth: 50px;\r\n\t}\r\n\t.uv-calendar__backtoday {\r\n\t\tposition: absolute;\r\n\t\tright: 0;\r\n\t\ttop: 25rpx;\r\n\t\tpadding: 0 5px;\r\n\t\tpadding-left: 10px;\r\n\t\theight: 25px;\r\n\t\tline-height: 25px;\r\n\t\tfont-size: 12px;\r\n\t\tborder-top-left-radius: 25px;\r\n\t\tborder-bottom-left-radius: 25px;\r\n\t\tcolor: $uv-text-color;\r\n\t\tbackground-color: $uv-bg-color-hover;\r\n\t}\r\n\t.uv-calendar__header-text {\r\n\t\ttext-align: center;\r\n\t\twidth: 100px;\r\n\t\tfont-size: $uv-font-size-base;\r\n\t\tcolor: $uv-text-color;\r\n\t}\r\n\t.uv-calendar__header-btn-box {\r\n\t\t@include flex;\r\n\t\talign-items: center;\r\n\t\tjustify-content: center;\r\n\t\twidth: 50px;\r\n\t\theight: 50px;\r\n\t}\r\n\t.uv-calendar__header-btn {\r\n\t\twidth: 10px;\r\n\t\theight: 10px;\r\n\t\tborder-left-color: $uv-text-color-placeholder;\r\n\t\tborder-left-style: solid;\r\n\t\tborder-left-width: 2px;\r\n\t\tborder-top-color: $uv-color-subtitle;\r\n\t\tborder-top-style: solid;\r\n\t\tborder-top-width: 2px;\r\n\t}\r\n\t.uv-calendar--left {\r\n\t\ttransform: rotate(-45deg);\r\n\t}\r\n\t.uv-calendar--right {\r\n\t\ttransform: rotate(135deg);\r\n\t}\r\n\t.uv-calendar__weeks {\r\n\t\tposition: relative;\r\n\t\t@include flex;\r\n\t}\r\n\t.uv-calendar__weeks-week {\r\n\t\tpadding: 0 0 2rpx;\r\n\t}\r\n\t.uv-calendar__weeks-item {\r\n\t\tflex: 1;\r\n\t}\r\n\t.uv-calendar__weeks-day {\r\n\t\tflex: 1;\r\n\t\t@include flex(column);\r\n\t\tjustify-content: center;\r\n\t\talign-items: center;\r\n\t\theight: 45px;\r\n\t\tborder-bottom-color: #F5F5F5;\r\n\t\tborder-bottom-style: solid;\r\n\t\tborder-bottom-width: 1px;\r\n\t}\r\n\t.uv-calendar__weeks-day-text {\r\n\t\tfont-size: 14px;\r\n\t}\r\n\t.uv-calendar__box {\r\n\t\tposition: relative;\r\n\t}\r\n\t.uv-calendar__box-bg {\r\n\t\t@include flex(column);\r\n\t\tjustify-content: center;\r\n\t\talign-items: center;\r\n\t\tposition: absolute;\r\n\t\ttop: 0;\r\n\t\tleft: 0;\r\n\t\tright: 0;\r\n\t\tbottom: 0;\r\n\t}\r\n\t.uv-calendar__box-bg-text {\r\n\t\tfont-size: 200px;\r\n\t\tfont-weight: bold;\r\n\t\tcolor: $uv-text-color-grey;\r\n\t\topacity: 0.1;\r\n\t\ttext-align: center;\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\tline-height: 1;\r\n\t\t/* #endif */\r\n\t}\r\n</style>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-calendars/components/uv-calendars/calendar-item.vue",
    "content": "<template>\r\n\t<view class=\"uv-calendar-item__weeks-box\" :class=\"{\r\n\t\t'uv-calendar-item--disable':weeks.disable || (weeks.extraInfo&&weeks.extraInfo.disable),\r\n\t\t'uv-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay && !multiple,\r\n\t\t'uv-calendar-item--checked':(calendar.fullDate === weeks.fullDate && !weeks.isDay && !multiple) ,\r\n\t\t'uv-calendar-item--before-checked':weeks.beforeRange,\r\n\t\t'uv-calendar-item--range': weeks.range,\r\n\t\t'uv-calendar-item--after-checked':weeks.afterRange,\r\n\t\t'uv-calendar-item--multiple':weeks.multiple\r\n\t\t}\" :style=\"[itemBoxStyle]\" @click=\"choiceDate(weeks)\">\r\n\t\t<view class=\"uv-calendar-item__weeks-box-item\">\r\n\t\t\t<text v-if=\"selected&&weeks.extraInfo&&weeks.extraInfo.badge\" class=\"uv-calendar-item__weeks-box-circle\"></text>\r\n\t\t\t<text class=\"uv-calendar-item__weeks-top-text\" v-if=\"weeks.extraInfo&&weeks.extraInfo.topinfo\" :style=\"[infoStyle('top')]\">{{weeks.extraInfo&&weeks.extraInfo.topinfo}}</text>\r\n\t\t\t<text class=\"uv-calendar-item__weeks-box-text\" :class=\"{\r\n\t\t\t\t'uv-calendar-item--isDay-text': weeks.isDay,\r\n\t\t\t\t'uv-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay && !multiple,\r\n\t\t\t\t'uv-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay && !multiple,\r\n\t\t\t\t'uv-calendar-item--before-checked':weeks.beforeRange,\r\n\t\t\t\t'uv-calendar-item--range': weeks.range,\r\n\t\t\t\t'uv-calendar-item--after-checked':weeks.afterRange,\r\n\t\t\t\t'uv-calendar-item--multiple':weeks.multiple,\r\n\t\t\t\t'uv-calendar-item--disable':weeks.disable || (weeks.extraInfo&&weeks.extraInfo.disable)\r\n\t\t\t\t}\" :style=\"[itemBoxStyle]\">{{weeks.date}}</text>\r\n\t\t\t<text v-if=\"!lunar&&!weeks.extraInfo && weeks.isDay\" class=\"uv-calendar-item__weeks-lunar-text\" :class=\"{\r\n\t\t\t\t'uv-calendar-item--isDay-text':weeks.isDay,\r\n\t\t\t\t'uv-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay && !multiple,\r\n\t\t\t\t'uv-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay && !multiple,\r\n\t\t\t\t'uv-calendar-item--before-checked':weeks.beforeRange,\r\n\t\t\t\t'uv-calendar-item--range': weeks.range,\r\n\t\t\t\t'uv-calendar-item--after-checked':weeks.afterRange,\r\n\t\t\t\t'uv-calendar-item--multiple':weeks.multiple\r\n\t\t\t\t}\" :style=\"[itemBoxStyle]\">{{todayText}}</text>\r\n\t\t\t<text v-if=\"lunar&&!weeks.extraInfo\" class=\"uv-calendar-item__weeks-lunar-text\" :class=\"{\r\n\t\t\t\t'uv-calendar-item--isDay-text':weeks.isDay,\r\n\t\t\t\t'uv-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay && !multiple,\r\n\t\t\t\t'uv-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay && !multiple,\r\n\t\t\t\t'uv-calendar-item--before-checked':weeks.beforeRange,\r\n\t\t\t\t'uv-calendar-item--range': weeks.range,\r\n\t\t\t\t'uv-calendar-item--after-checked':weeks.afterRange,\r\n\t\t\t\t'uv-calendar-item--multiple':weeks.multiple,\r\n\t\t\t\t'uv-calendar-item--disable':weeks.disable || (weeks.extraInfo&&weeks.extraInfo.disable)\r\n\t\t\t\t}\" :style=\"[itemBoxStyle]\">{{weeks.isDay ? todayText : (weeks.lunar.IDayCn === '初一'?weeks.lunar.IMonthCn:weeks.lunar.IDayCn)}}</text>\r\n\t\t\t<text v-if=\"weeks.extraInfo&&weeks.extraInfo.info\" class=\"uv-calendar-item__weeks-lunar-text\" :class=\"{\r\n\t\t\t\t\t\t'uv-calendar-item__weeks-lunar-text--equal': weeks.dateEqual\r\n\t\t\t\t\t}\" :style=\"[infoStyle('bottom')]\">{{weeks.extraInfo.info}}</text>\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n<script>\r\n\timport { colorGradient } from '@/uni_modules/uv-ui-tools/libs/function/colorGradient.js';\r\n\timport { initVueI18n } from '@dcloudio/uni-i18n'\r\n\timport i18nMessages from './i18n/index.js'\r\n\tconst { t } = initVueI18n(i18nMessages)\r\n\texport default {\r\n\t\temits: ['change'],\r\n\t\tprops: {\r\n\t\t\tweeks: {\r\n\t\t\t\ttype: Object,\r\n\t\t\t\tdefault () {\r\n\t\t\t\t\treturn {}\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tcalendar: {\r\n\t\t\t\ttype: Object,\r\n\t\t\t\tdefault: () => {\r\n\t\t\t\t\treturn {}\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tselected: {\r\n\t\t\t\ttype: Array,\r\n\t\t\t\tdefault: () => {\r\n\t\t\t\t\treturn []\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tlunar: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: false\r\n\t\t\t},\r\n\t\t\tcolor: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: '#3c9cff'\r\n\t\t\t},\r\n\t\t\trange: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: false\r\n\t\t\t},\r\n\t\t\tmultiple: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: false\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\ttodayText() {\r\n\t\t\t\treturn t(\"uv-calender.today\")\r\n\t\t\t},\r\n\t\t\titemBoxStyle() {\r\n\t\t\t\tconst style = {};\r\n\t\t\t\tif (this.multiple) { // 多选状态\r\n\t\t\t\t\tif (this.weeks.multiple) {\r\n\t\t\t\t\t\tstyle.backgroundColor = this.color;\r\n\t\t\t\t\t\tstyle.color = '#fff';\r\n\t\t\t\t\t} else if (this.weeks.isDay) {\r\n\t\t\t\t\t\tstyle.color = this.color;\r\n\t\t\t\t\t}\r\n\t\t\t\t} else if (this.range) { // 范围选择\r\n\t\t\t\t\tif (this.weeks.beforeRange || this.weeks.afterRange) {\r\n\t\t\t\t\t\tstyle.backgroundColor = this.color;\r\n\t\t\t\t\t} else if (this.weeks.range) {\r\n\t\t\t\t\t\tstyle.backgroundColor = colorGradient(this.color, '#ffffff', 100)[90]\r\n\t\t\t\t\t\tstyle.color = this.color;\r\n\t\t\t\t\t\tstyle.opacity = 0.8;\r\n\t\t\t\t\t\tstyle.borderRadius = 0;\r\n\t\t\t\t\t}\r\n\t\t\t\t} else {\r\n\t\t\t\t\tif (this.weeks.isDay) {\r\n\t\t\t\t\t\tstyle.color = this.color;\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (this.calendar.fullDate === this.weeks.fullDate) {\r\n\t\t\t\t\t\tstyle.backgroundColor = this.color;\r\n\t\t\t\t\t\tstyle.color = '#fff';\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\treturn style;\r\n\t\t\t},\r\n\t\t\tinfoStyle(val) {\r\n\t\t\t\treturn val => {\r\n\t\t\t\t\tconst style = {};\r\n\t\t\t\t\tif (!this.weeks.multiple) {\r\n\t\t\t\t\t\tif (val == 'top') {\r\n\t\t\t\t\t\t\tstyle.color = this.weeks.extraInfo.topinfoColor ? this.weeks.extraInfo.topinfoColor : '#606266';\r\n\t\t\t\t\t\t} else if (val == 'bottom') {\r\n\t\t\t\t\t\t\tstyle.color = this.weeks.extraInfo.infoColor ? this.weeks.extraInfo.infoColor : '#f56c6c';\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tif (this.weeks.range) {\r\n\t\t\t\t\t\t\tstyle.color = this.color;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tif (this.calendar.fullDate === this.weeks.fullDate || this.weeks.beforeRange || this.weeks.afterRange) {\r\n\t\t\t\t\t\t\tstyle.color = this.multiple ? style.color : '#fff';\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tstyle.color = '#fff';\r\n\t\t\t\t\t}\r\n\t\t\t\t\treturn style;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tchoiceDate(weeks) {\r\n\t\t\t\tif (this.weeks.extraInfo && this.weeks.extraInfo.disable) return;\r\n\t\t\t\tthis.$emit('change', weeks)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n<style lang=\"scss\" scoped>\r\n\t@mixin flex($direction: row) {\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\tdisplay: flex;\r\n\t\t/* #endif */\r\n\t\tflex-direction: $direction;\r\n\t}\r\n\t$uv-font-size-base: 14px;\r\n\t$uv-text-color: #333;\r\n\t$uv-font-size-sm: 24rpx;\r\n\t$uv-error: #f56c6c !default;\r\n\t$uv-opacity-disabled: 0.3;\r\n\t$uv-text-color-disable: #c0c0c0;\r\n\t$uv-primary: #3c9cff !default;\r\n\t$info-height: 32rpx;\r\n\t.uv-calendar-item__weeks-box {\r\n\t\tflex: 1;\r\n\t\t@include flex(column);\r\n\t\tjustify-content: center;\r\n\t\talign-items: center;\r\n\t\tborder-radius: 4px;\r\n\t}\r\n\t.uv-calendar-item__weeks-top-text {\r\n\t\theight: $info-height;\r\n\t\tline-height: $info-height;\r\n\t\tfont-size: $uv-font-size-sm;\r\n\t}\r\n\t.uv-calendar-item__weeks-box-text {\r\n\t\tfont-size: $uv-font-size-base;\r\n\t\tcolor: $uv-text-color;\r\n\t}\r\n\t.uv-calendar-item__weeks-lunar-text {\r\n\t\theight: $info-height;\r\n\t\tline-height: $info-height;\r\n\t\tfont-size: $uv-font-size-sm;\r\n\t\tcolor: $uv-text-color;\r\n\t}\r\n\t.uv-calendar-item__weeks-lunar-text--equal {\r\n\t\t/* #ifdef H5 */\r\n\t\twhite-space: nowrap;\r\n\t\ttransform: scale(.8);\r\n\t\t/* #endif */\r\n\t\t/* #ifndef H5 */\r\n\t\tfont-size: 20rpx;\r\n\t\t/* #endif */\r\n\t}\r\n\t.uv-calendar-item__weeks-box-item {\r\n\t\tposition: relative;\r\n\t\t@include flex(column);\r\n\t\tjustify-content: center;\r\n\t\talign-items: center;\r\n\t\twidth: 106rpx;\r\n\t\theight: 56px;\r\n\t}\r\n\t.uv-calendar-item__weeks-box-circle {\r\n\t\tposition: absolute;\r\n\t\ttop: 5px;\r\n\t\tright: 5px;\r\n\t\twidth: 8px;\r\n\t\theight: 8px;\r\n\t\tborder-radius: 8px;\r\n\t\tbackground-color: $uv-error;\r\n\t}\r\n\t.uv-calendar-item--disable {\r\n\t\tbackground-color: rgba(249, 249, 249, $uv-opacity-disabled);\r\n\t\tcolor: $uv-text-color-disable;\r\n\t}\r\n\t.uv-calendar-item--isDay-text {\r\n\t\tcolor: $uv-primary;\r\n\t}\r\n\t.uv-calendar-item--isDay {\r\n\t\tbackground-color: $uv-primary;\r\n\t\tcolor: #fff;\r\n\t}\r\n\t.uv-calendar-item--checked {\r\n\t\tbackground-color: $uv-primary;\r\n\t\tcolor: #fff;\r\n\t\tborder-radius: 4px;\r\n\t}\r\n\t// .uv-calendar-item--range {\r\n\t// \tbackground-color: $uv-primary;\r\n\t// \tcolor: #fff;\r\n\t// }\r\n\t.uv-calendar-item--before-checked {\r\n\t\tcolor: #fff;\r\n\t}\r\n\t.uv-calendar-item--after-checked {\r\n\t\tcolor: #fff;\r\n\t}\r\n\t.uv-calendar-item--multiple {\r\n\t\tbackground-color: $uv-primary;\r\n\t\tcolor: #fff;\r\n\t}\r\n</style>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-calendars/components/uv-calendars/calendar.js",
    "content": "/**\r\n* @1900-2100区间内的公历、农历互转\r\n* @charset UTF-8\r\n* @github  https://github.com/jjonline/calendar.js\r\n* @Author  Jea杨(JJonline@JJonline.Cn)\r\n* @Time    2014-7-21\r\n* @Time    2016-8-13 Fixed 2033hex、Attribution Annals\r\n* @Time    2016-9-25 Fixed lunar LeapMonth Param Bug\r\n* @Time    2017-7-24 Fixed use getTerm Func Param Error.use solar year,NOT lunar year\r\n* @Version 1.0.3\r\n* @公历转农历：calendar.solar2lunar(1987,11,01); //[you can ignore params of prefix 0]\r\n* @农历转公历：calendar.lunar2solar(1987,09,10); //[you can ignore params of prefix 0]\r\n*/\r\n/* eslint-disable */\r\nvar calendar = {\r\n\r\n  /**\r\n      * 农历1900-2100的润大小信息表\r\n      * @Array Of Property\r\n      * @return Hex\r\n      */\r\n  lunarInfo: [0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, // 1900-1909\r\n    0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977, // 1910-1919\r\n    0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970, // 1920-1929\r\n    0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950, // 1930-1939\r\n    0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557, // 1940-1949\r\n    0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0, // 1950-1959\r\n    0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0, // 1960-1969\r\n    0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6, // 1970-1979\r\n    0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570, // 1980-1989\r\n    0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x05ac0, 0x0ab60, 0x096d5, 0x092e0, // 1990-1999\r\n    0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5, // 2000-2009\r\n    0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930, // 2010-2019\r\n    0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530, // 2020-2029\r\n    0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, // 2030-2039\r\n    0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0, // 2040-2049\r\n    /** Add By JJonline@JJonline.Cn**/\r\n    0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0, // 2050-2059\r\n    0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4, // 2060-2069\r\n    0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0, // 2070-2079\r\n    0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160, // 2080-2089\r\n    0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252, // 2090-2099\r\n    0x0d520], // 2100\r\n\r\n  /**\r\n      * 公历每个月份的天数普通表\r\n      * @Array Of Property\r\n      * @return Number\r\n      */\r\n  solarMonth: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],\r\n\r\n  /**\r\n      * 天干地支之天干速查表\r\n      * @Array Of Property trans[\"甲\",\"乙\",\"丙\",\"丁\",\"戊\",\"己\",\"庚\",\"辛\",\"壬\",\"癸\"]\r\n      * @return Cn string\r\n      */\r\n  Gan: ['\\u7532', '\\u4e59', '\\u4e19', '\\u4e01', '\\u620a', '\\u5df1', '\\u5e9a', '\\u8f9b', '\\u58ec', '\\u7678'],\r\n\r\n  /**\r\n      * 天干地支之地支速查表\r\n      * @Array Of Property\r\n      * @trans[\"子\",\"丑\",\"寅\",\"卯\",\"辰\",\"巳\",\"午\",\"未\",\"申\",\"酉\",\"戌\",\"亥\"]\r\n      * @return Cn string\r\n      */\r\n  Zhi: ['\\u5b50', '\\u4e11', '\\u5bc5', '\\u536f', '\\u8fb0', '\\u5df3', '\\u5348', '\\u672a', '\\u7533', '\\u9149', '\\u620c', '\\u4ea5'],\r\n\r\n  /**\r\n      * 天干地支之地支速查表<=>生肖\r\n      * @Array Of Property\r\n      * @trans[\"鼠\",\"牛\",\"虎\",\"兔\",\"龙\",\"蛇\",\"马\",\"羊\",\"猴\",\"鸡\",\"狗\",\"猪\"]\r\n      * @return Cn string\r\n      */\r\n  Animals: ['\\u9f20', '\\u725b', '\\u864e', '\\u5154', '\\u9f99', '\\u86c7', '\\u9a6c', '\\u7f8a', '\\u7334', '\\u9e21', '\\u72d7', '\\u732a'],\r\n\r\n  /**\r\n      * 24节气速查表\r\n      * @Array Of Property\r\n      * @trans[\"小寒\",\"大寒\",\"立春\",\"雨水\",\"惊蛰\",\"春分\",\"清明\",\"谷雨\",\"立夏\",\"小满\",\"芒种\",\"夏至\",\"小暑\",\"大暑\",\"立秋\",\"处暑\",\"白露\",\"秋分\",\"寒露\",\"霜降\",\"立冬\",\"小雪\",\"大雪\",\"冬至\"]\r\n      * @return Cn string\r\n      */\r\n  solarTerm: ['\\u5c0f\\u5bd2', '\\u5927\\u5bd2', '\\u7acb\\u6625', '\\u96e8\\u6c34', '\\u60ca\\u86f0', '\\u6625\\u5206', '\\u6e05\\u660e', '\\u8c37\\u96e8', '\\u7acb\\u590f', '\\u5c0f\\u6ee1', '\\u8292\\u79cd', '\\u590f\\u81f3', '\\u5c0f\\u6691', '\\u5927\\u6691', '\\u7acb\\u79cb', '\\u5904\\u6691', '\\u767d\\u9732', '\\u79cb\\u5206', '\\u5bd2\\u9732', '\\u971c\\u964d', '\\u7acb\\u51ac', '\\u5c0f\\u96ea', '\\u5927\\u96ea', '\\u51ac\\u81f3'],\r\n\r\n  /**\r\n      * 1900-2100各年的24节气日期速查表\r\n      * @Array Of Property\r\n      * @return 0x string For splice\r\n      */\r\n  sTermInfo: ['9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f',\r\n    '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',\r\n    '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa',\r\n    '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f',\r\n    'b027097bd097c36b0b6fc9274c91aa', '9778397bd19801ec9210c965cc920e', '97b6b97bd19801ec95f8c965cc920f',\r\n    '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd197c36c9210c9274c91aa',\r\n    '97b6b97bd19801ec95f8c965cc920e', '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2',\r\n    '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec95f8c965cc920e', '97bcf97c3598082c95f8e1cfcc920f',\r\n    '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e',\r\n    '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',\r\n    '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722',\r\n    '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f',\r\n    '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',\r\n    '97bcf97c359801ec95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',\r\n    '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd097bd07f595b0b6fc920fb0722',\r\n    '9778397bd097c36b0b6fc9210c8dc2', '9778397bd19801ec9210c9274c920e', '97b6b97bd19801ec95f8c965cc920f',\r\n    '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e',\r\n    '97b6b97bd19801ec95f8c965cc920f', '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2',\r\n    '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bd07f1487f595b0b0bc920fb0722',\r\n    '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',\r\n    '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',\r\n    '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',\r\n    '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f531b0b0bb0b6fb0722',\r\n    '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',\r\n    '97bcf7f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',\r\n    '97b6b97bd19801ec9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',\r\n    '9778397bd097c36b0b6fc9210c91aa', '97b6b97bd197c36c9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722',\r\n    '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e',\r\n    '97b6b7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2',\r\n    '9778397bd097c36b0b70c9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722',\r\n    '7f0e397bd097c35b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',\r\n    '7f0e27f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',\r\n    '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',\r\n    '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',\r\n    '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',\r\n    '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9274c91aa',\r\n    '97b6b7f0e47f531b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',\r\n    '9778397bd097c36b0b6fc9210c91aa', '97b6b7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',\r\n    '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '977837f0e37f149b0723b0787b0721',\r\n    '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c35b0b6fc9210c8dc2',\r\n    '977837f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722',\r\n    '7f0e397bd097c35b0b6fc9210c8dc2', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',\r\n    '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '977837f0e37f14998082b0787b06bd',\r\n    '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',\r\n    '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',\r\n    '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',\r\n    '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd',\r\n    '7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',\r\n    '977837f0e37f14998082b0723b06bd', '7f07e7f0e37f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',\r\n    '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b0721',\r\n    '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f595b0b0bb0b6fb0722', '7f0e37f0e37f14898082b0723b02d5',\r\n    '7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f531b0b0bb0b6fb0722',\r\n    '7f0e37f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',\r\n    '7f0e37f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd',\r\n    '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35',\r\n    '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',\r\n    '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f149b0723b0787b0721',\r\n    '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0723b06bd',\r\n    '7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722', '7f0e37f0e366aa89801eb072297c35',\r\n    '7ec967f0e37f14998082b0723b06bd', '7f07e7f0e37f14998083b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',\r\n    '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14898082b0723b02d5', '7f07e7f0e37f14998082b0787b0721',\r\n    '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66aa89801e9808297c35', '665f67f0e37f14898082b0723b02d5',\r\n    '7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66a449801e9808297c35',\r\n    '665f67f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',\r\n    '7f0e36665b66a449801e9808297c35', '665f67f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd',\r\n    '7f07e7f0e47f531b0723b0b6fb0721', '7f0e26665b66a449801e9808297c35', '665f67f0e37f1489801eb072297c35',\r\n    '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722'],\r\n\r\n  /**\r\n      * 数字转中文速查表\r\n      * @Array Of Property\r\n      * @trans ['日','一','二','三','四','五','六','七','八','九','十']\r\n      * @return Cn string\r\n      */\r\n  nStr1: ['\\u65e5', '\\u4e00', '\\u4e8c', '\\u4e09', '\\u56db', '\\u4e94', '\\u516d', '\\u4e03', '\\u516b', '\\u4e5d', '\\u5341'],\r\n\r\n  /**\r\n      * 日期转农历称呼速查表\r\n      * @Array Of Property\r\n      * @trans ['初','十','廿','卅']\r\n      * @return Cn string\r\n      */\r\n  nStr2: ['\\u521d', '\\u5341', '\\u5eff', '\\u5345'],\r\n\r\n  /**\r\n      * 月份转农历称呼速查表\r\n      * @Array Of Property\r\n      * @trans ['正','一','二','三','四','五','六','七','八','九','十','冬','腊']\r\n      * @return Cn string\r\n      */\r\n  nStr3: ['\\u6b63', '\\u4e8c', '\\u4e09', '\\u56db', '\\u4e94', '\\u516d', '\\u4e03', '\\u516b', '\\u4e5d', '\\u5341', '\\u51ac', '\\u814a'],\r\n\r\n  /**\r\n      * 返回农历y年一整年的总天数\r\n      * @param lunar Year\r\n      * @return Number\r\n      * @eg:var count = calendar.lYearDays(1987) ;//count=387\r\n      */\r\n  lYearDays: function (y) {\r\n    var i; var sum = 348\r\n    for (i = 0x8000; i > 0x8; i >>= 1) { sum += (this.lunarInfo[y - 1900] & i) ? 1 : 0 }\r\n    return (sum + this.leapDays(y))\r\n  },\r\n\r\n  /**\r\n      * 返回农历y年闰月是哪个月；若y年没有闰月 则返回0\r\n      * @param lunar Year\r\n      * @return Number (0-12)\r\n      * @eg:var leapMonth = calendar.leapMonth(1987) ;//leapMonth=6\r\n      */\r\n  leapMonth: function (y) { // 闰字编码 \\u95f0\r\n    return (this.lunarInfo[y - 1900] & 0xf)\r\n  },\r\n\r\n  /**\r\n      * 返回农历y年闰月的天数 若该年没有闰月则返回0\r\n      * @param lunar Year\r\n      * @return Number (0、29、30)\r\n      * @eg:var leapMonthDay = calendar.leapDays(1987) ;//leapMonthDay=29\r\n      */\r\n  leapDays: function (y) {\r\n    if (this.leapMonth(y)) {\r\n      return ((this.lunarInfo[y - 1900] & 0x10000) ? 30 : 29)\r\n    }\r\n    return (0)\r\n  },\r\n\r\n  /**\r\n      * 返回农历y年m月（非闰月）的总天数，计算m为闰月时的天数请使用leapDays方法\r\n      * @param lunar Year\r\n      * @return Number (-1、29、30)\r\n      * @eg:var MonthDay = calendar.monthDays(1987,9) ;//MonthDay=29\r\n      */\r\n  monthDays: function (y, m) {\r\n    if (m > 12 || m < 1) { return -1 }// 月份参数从1至12，参数错误返回-1\r\n    return ((this.lunarInfo[y - 1900] & (0x10000 >> m)) ? 30 : 29)\r\n  },\r\n\r\n  /**\r\n      * 返回公历(!)y年m月的天数\r\n      * @param solar Year\r\n      * @return Number (-1、28、29、30、31)\r\n      * @eg:var solarMonthDay = calendar.leapDays(1987) ;//solarMonthDay=30\r\n      */\r\n  solarDays: function (y, m) {\r\n    if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1\r\n    var ms = m - 1\r\n    if (ms == 1) { // 2月份的闰平规律测算后确认返回28或29\r\n      return (((y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0)) ? 29 : 28)\r\n    } else {\r\n      return (this.solarMonth[ms])\r\n    }\r\n  },\r\n\r\n  /**\r\n     * 农历年份转换为干支纪年\r\n     * @param  lYear 农历年的年份数\r\n     * @return Cn string\r\n     */\r\n  toGanZhiYear: function (lYear) {\r\n    var ganKey = (lYear - 3) % 10\r\n    var zhiKey = (lYear - 3) % 12\r\n    if (ganKey == 0) ganKey = 10// 如果余数为0则为最后一个天干\r\n    if (zhiKey == 0) zhiKey = 12// 如果余数为0则为最后一个地支\r\n    return this.Gan[ganKey - 1] + this.Zhi[zhiKey - 1]\r\n  },\r\n\r\n  /**\r\n     * 公历月、日判断所属星座\r\n     * @param  cMonth [description]\r\n     * @param  cDay [description]\r\n     * @return Cn string\r\n     */\r\n  toAstro: function (cMonth, cDay) {\r\n    var s = '\\u9b54\\u7faf\\u6c34\\u74f6\\u53cc\\u9c7c\\u767d\\u7f8a\\u91d1\\u725b\\u53cc\\u5b50\\u5de8\\u87f9\\u72ee\\u5b50\\u5904\\u5973\\u5929\\u79e4\\u5929\\u874e\\u5c04\\u624b\\u9b54\\u7faf'\r\n    var arr = [20, 19, 21, 21, 21, 22, 23, 23, 23, 23, 22, 22]\r\n    return s.substr(cMonth * 2 - (cDay < arr[cMonth - 1] ? 2 : 0), 2) + '\\u5ea7'// 座\r\n  },\r\n\r\n  /**\r\n      * 传入offset偏移量返回干支\r\n      * @param offset 相对甲子的偏移量\r\n      * @return Cn string\r\n      */\r\n  toGanZhi: function (offset) {\r\n    return this.Gan[offset % 10] + this.Zhi[offset % 12]\r\n  },\r\n\r\n  /**\r\n      * 传入公历(!)y年获得该年第n个节气的公历日期\r\n      * @param y公历年(1900-2100)；n二十四节气中的第几个节气(1~24)；从n=1(小寒)算起\r\n      * @return day Number\r\n      * @eg:var _24 = calendar.getTerm(1987,3) ;//_24=4;意即1987年2月4日立春\r\n      */\r\n  getTerm: function (y, n) {\r\n    if (y < 1900 || y > 2100) { return -1 }\r\n    if (n < 1 || n > 24) { return -1 }\r\n    var _table = this.sTermInfo[y - 1900]\r\n    var _info = [\r\n      parseInt('0x' + _table.substr(0, 5)).toString(),\r\n      parseInt('0x' + _table.substr(5, 5)).toString(),\r\n      parseInt('0x' + _table.substr(10, 5)).toString(),\r\n      parseInt('0x' + _table.substr(15, 5)).toString(),\r\n      parseInt('0x' + _table.substr(20, 5)).toString(),\r\n      parseInt('0x' + _table.substr(25, 5)).toString()\r\n    ]\r\n    var _calday = [\r\n      _info[0].substr(0, 1),\r\n      _info[0].substr(1, 2),\r\n      _info[0].substr(3, 1),\r\n      _info[0].substr(4, 2),\r\n\r\n      _info[1].substr(0, 1),\r\n      _info[1].substr(1, 2),\r\n      _info[1].substr(3, 1),\r\n      _info[1].substr(4, 2),\r\n\r\n      _info[2].substr(0, 1),\r\n      _info[2].substr(1, 2),\r\n      _info[2].substr(3, 1),\r\n      _info[2].substr(4, 2),\r\n\r\n      _info[3].substr(0, 1),\r\n      _info[3].substr(1, 2),\r\n      _info[3].substr(3, 1),\r\n      _info[3].substr(4, 2),\r\n\r\n      _info[4].substr(0, 1),\r\n      _info[4].substr(1, 2),\r\n      _info[4].substr(3, 1),\r\n      _info[4].substr(4, 2),\r\n\r\n      _info[5].substr(0, 1),\r\n      _info[5].substr(1, 2),\r\n      _info[5].substr(3, 1),\r\n      _info[5].substr(4, 2)\r\n    ]\r\n    return parseInt(_calday[n - 1])\r\n  },\r\n\r\n  /**\r\n      * 传入农历数字月份返回汉语通俗表示法\r\n      * @param lunar month\r\n      * @return Cn string\r\n      * @eg:var cnMonth = calendar.toChinaMonth(12) ;//cnMonth='腊月'\r\n      */\r\n  toChinaMonth: function (m) { // 月 => \\u6708\r\n    if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1\r\n    var s = this.nStr3[m - 1]\r\n    s += '\\u6708'// 加上月字\r\n    return s\r\n  },\r\n\r\n  /**\r\n      * 传入农历日期数字返回汉字表示法\r\n      * @param lunar day\r\n      * @return Cn string\r\n      * @eg:var cnDay = calendar.toChinaDay(21) ;//cnMonth='廿一'\r\n      */\r\n  toChinaDay: function (d) { // 日 => \\u65e5\r\n    var s\r\n    switch (d) {\r\n      case 10:\r\n        s = '\\u521d\\u5341'; break\r\n      case 20:\r\n        s = '\\u4e8c\\u5341'; break\r\n        break\r\n      case 30:\r\n        s = '\\u4e09\\u5341'; break\r\n        break\r\n      default :\r\n        s = this.nStr2[Math.floor(d / 10)]\r\n        s += this.nStr1[d % 10]\r\n    }\r\n    return (s)\r\n  },\r\n\r\n  /**\r\n      * 年份转生肖[!仅能大致转换] => 精确划分生肖分界线是“立春”\r\n      * @param y year\r\n      * @return Cn string\r\n      * @eg:var animal = calendar.getAnimal(1987) ;//animal='兔'\r\n      */\r\n  getAnimal: function (y) {\r\n    return this.Animals[(y - 4) % 12]\r\n  },\r\n\r\n  /**\r\n      * 传入阳历年月日获得详细的公历、农历object信息 <=>JSON\r\n      * @param y  solar year\r\n      * @param m  solar month\r\n      * @param d  solar day\r\n      * @return JSON object\r\n      * @eg:console.log(calendar.solar2lunar(1987,11,01));\r\n      */\r\n  solar2lunar: function (y, m, d) { // 参数区间1900.1.31~2100.12.31\r\n    // 年份限定、上限\r\n    if (y < 1900 || y > 2100) {\r\n      return -1// undefined转换为数字变为NaN\r\n    }\r\n    // 公历传参最下限\r\n    if (y == 1900 && m == 1 && d < 31) {\r\n      return -1\r\n    }\r\n    // 未传参  获得当天\r\n    if (!y) {\r\n      var objDate = new Date()\r\n    } else {\r\n      var objDate = new Date(y, parseInt(m) - 1, d)\r\n    }\r\n    var i; var leap = 0; var temp = 0\r\n    // 修正ymd参数\r\n    var y = objDate.getFullYear()\r\n    var m = objDate.getMonth() + 1\r\n    var d = objDate.getDate()\r\n    var offset = (Date.UTC(objDate.getFullYear(), objDate.getMonth(), objDate.getDate()) - Date.UTC(1900, 0, 31)) / 86400000\r\n    for (i = 1900; i < 2101 && offset > 0; i++) {\r\n      temp = this.lYearDays(i)\r\n      offset -= temp\r\n    }\r\n    if (offset < 0) {\r\n      offset += temp; i--\r\n    }\r\n\r\n    // 是否今天\r\n    var isTodayObj = new Date()\r\n    var isToday = false\r\n    if (isTodayObj.getFullYear() == y && isTodayObj.getMonth() + 1 == m && isTodayObj.getDate() == d) {\r\n      isToday = true\r\n    }\r\n    // 星期几\r\n    var nWeek = objDate.getDay()\r\n    var cWeek = this.nStr1[nWeek]\r\n    // 数字表示周几顺应天朝周一开始的惯例\r\n    if (nWeek == 0) {\r\n      nWeek = 7\r\n    }\r\n    // 农历年\r\n    var year = i\r\n    var leap = this.leapMonth(i) // 闰哪个月\r\n    var isLeap = false\r\n\r\n    // 效验闰月\r\n    for (i = 1; i < 13 && offset > 0; i++) {\r\n      // 闰月\r\n      if (leap > 0 && i == (leap + 1) && isLeap == false) {\r\n        --i\r\n        isLeap = true; temp = this.leapDays(year) // 计算农历闰月天数\r\n      } else {\r\n        temp = this.monthDays(year, i)// 计算农历普通月天数\r\n      }\r\n      // 解除闰月\r\n      if (isLeap == true && i == (leap + 1)) { isLeap = false }\r\n      offset -= temp\r\n    }\r\n    // 闰月导致数组下标重叠取反\r\n    if (offset == 0 && leap > 0 && i == leap + 1) {\r\n      if (isLeap) {\r\n        isLeap = false\r\n      } else {\r\n        isLeap = true; --i\r\n      }\r\n    }\r\n    if (offset < 0) {\r\n      offset += temp; --i\r\n    }\r\n    // 农历月\r\n    var month = i\r\n    // 农历日\r\n    var day = offset + 1\r\n    // 天干地支处理\r\n    var sm = m - 1\r\n    var gzY = this.toGanZhiYear(year)\r\n\r\n    // 当月的两个节气\r\n    // bugfix-2017-7-24 11:03:38 use lunar Year Param `y` Not `year`\r\n    var firstNode = this.getTerm(y, (m * 2 - 1))// 返回当月「节」为几日开始\r\n    var secondNode = this.getTerm(y, (m * 2))// 返回当月「节」为几日开始\r\n\r\n    // 依据12节气修正干支月\r\n    var gzM = this.toGanZhi((y - 1900) * 12 + m + 11)\r\n    if (d >= firstNode) {\r\n      gzM = this.toGanZhi((y - 1900) * 12 + m + 12)\r\n    }\r\n\r\n    // 传入的日期的节气与否\r\n    var isTerm = false\r\n    var Term = null\r\n    if (firstNode == d) {\r\n      isTerm = true\r\n      Term = this.solarTerm[m * 2 - 2]\r\n    }\r\n    if (secondNode == d) {\r\n      isTerm = true\r\n      Term = this.solarTerm[m * 2 - 1]\r\n    }\r\n    // 日柱 当月一日与 1900/1/1 相差天数\r\n    var dayCyclical = Date.UTC(y, sm, 1, 0, 0, 0, 0) / 86400000 + 25567 + 10\r\n    var gzD = this.toGanZhi(dayCyclical + d - 1)\r\n    // 该日期所属的星座\r\n    var astro = this.toAstro(m, d)\r\n\r\n    return { 'lYear': year, 'lMonth': month, 'lDay': day, 'Animal': this.getAnimal(year), 'IMonthCn': (isLeap ? '\\u95f0' : '') + this.toChinaMonth(month), 'IDayCn': this.toChinaDay(day), 'cYear': y, 'cMonth': m, 'cDay': d, 'gzYear': gzY, 'gzMonth': gzM, 'gzDay': gzD, 'isToday': isToday, 'isLeap': isLeap, 'nWeek': nWeek, 'ncWeek': '\\u661f\\u671f' + cWeek, 'isTerm': isTerm, 'Term': Term, 'astro': astro }\r\n  },\r\n\r\n  /**\r\n      * 传入农历年月日以及传入的月份是否闰月获得详细的公历、农历object信息 <=>JSON\r\n      * @param y  lunar year\r\n      * @param m  lunar month\r\n      * @param d  lunar day\r\n      * @param isLeapMonth  lunar month is leap or not.[如果是农历闰月第四个参数赋值true即可]\r\n      * @return JSON object\r\n      * @eg:console.log(calendar.lunar2solar(1987,9,10));\r\n      */\r\n  lunar2solar: function (y, m, d, isLeapMonth) { // 参数区间1900.1.31~2100.12.1\r\n    var isLeapMonth = !!isLeapMonth\r\n    var leapOffset = 0\r\n    var leapMonth = this.leapMonth(y)\r\n    var leapDay = this.leapDays(y)\r\n    if (isLeapMonth && (leapMonth != m)) { return -1 }// 传参要求计算该闰月公历 但该年得出的闰月与传参的月份并不同\r\n    if (y == 2100 && m == 12 && d > 1 || y == 1900 && m == 1 && d < 31) { return -1 }// 超出了最大极限值\r\n    var day = this.monthDays(y, m)\r\n    var _day = day\r\n    // bugFix 2016-9-25\r\n    // if month is leap, _day use leapDays method\r\n    if (isLeapMonth) {\r\n      _day = this.leapDays(y, m)\r\n    }\r\n    if (y < 1900 || y > 2100 || d > _day) { return -1 }// 参数合法性效验\r\n\r\n    // 计算农历的时间差\r\n    var offset = 0\r\n    for (var i = 1900; i < y; i++) {\r\n      offset += this.lYearDays(i)\r\n    }\r\n    var leap = 0; var isAdd = false\r\n    for (var i = 1; i < m; i++) {\r\n      leap = this.leapMonth(y)\r\n      if (!isAdd) { // 处理闰月\r\n        if (leap <= i && leap > 0) {\r\n          offset += this.leapDays(y); isAdd = true\r\n        }\r\n      }\r\n      offset += this.monthDays(y, i)\r\n    }\r\n    // 转换闰月农历 需补充该年闰月的前一个月的时差\r\n    if (isLeapMonth) { offset += day }\r\n    // 1900年农历正月一日的公历时间为1900年1月30日0时0分0秒(该时间也是本农历的最开始起始点)\r\n    var stmap = Date.UTC(1900, 1, 30, 0, 0, 0)\r\n    var calObj = new Date((offset + d - 31) * 86400000 + stmap)\r\n    var cY = calObj.getUTCFullYear()\r\n    var cM = calObj.getUTCMonth() + 1\r\n    var cD = calObj.getUTCDate()\r\n\r\n    return this.solar2lunar(cY, cM, cD)\r\n  }\r\n}\r\n\r\nexport default calendar\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-calendars/components/uv-calendars/i18n/en.json",
    "content": "{\r\n\t\"uv-calender.ok\": \"ok\",\r\n\t\"uv-calender.cancel\": \"cancel\",\r\n\t\"uv-calender.today\": \"today\",\r\n\t\"uv-calender.MON\": \"MON\",\r\n\t\"uv-calender.TUE\": \"TUE\",\r\n\t\"uv-calender.WED\": \"WED\",\r\n\t\"uv-calender.THU\": \"THU\",\r\n\t\"uv-calender.FRI\": \"FRI\",\r\n\t\"uv-calender.SAT\": \"SAT\",\r\n\t\"uv-calender.SUN\": \"SUN\"\r\n}\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-calendars/components/uv-calendars/i18n/index.js",
    "content": "import en from './en.json'\r\nimport zhHans from './zh-Hans.json'\r\nimport zhHant from './zh-Hant.json'\r\nexport default {\r\n\ten,\r\n\t'zh-Hans': zhHans,\r\n\t'zh-Hant': zhHant\r\n}\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-calendars/components/uv-calendars/i18n/zh-Hans.json",
    "content": "{\r\n\t\"uv-calender.ok\": \"确定\",\r\n\t\"uv-calender.cancel\": \"取消\",\r\n\t\"uv-calender.today\": \"今日\",\r\n\t\"uv-calender.SUN\": \"日\",\r\n\t\"uv-calender.MON\": \"一\",\r\n\t\"uv-calender.TUE\": \"二\",\r\n\t\"uv-calender.WED\": \"三\",\r\n\t\"uv-calender.THU\": \"四\",\r\n\t\"uv-calender.FRI\": \"五\",\r\n\t\"uv-calender.SAT\": \"六\"\r\n}\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-calendars/components/uv-calendars/i18n/zh-Hant.json",
    "content": "{\r\n\t\"uv-calender.ok\": \"確定\",\r\n\t\"uv-calender.cancel\": \"取消\",\r\n\t\"uv-calender.today\": \"今日\",\r\n\t\"uv-calender.SUN\": \"日\",\r\n\t\"uv-calender.MON\": \"一\",\r\n\t\"uv-calender.TUE\": \"二\",\r\n\t\"uv-calender.WED\": \"三\",\r\n\t\"uv-calender.THU\": \"四\",\r\n\t\"uv-calender.FRI\": \"五\",\r\n\t\"uv-calender.SAT\": \"六\"\r\n}\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-calendars/components/uv-calendars/util.js",
    "content": "import CALENDAR from './calendar.js'\r\nclass Calendar {\r\n\tconstructor({\r\n\t\tdate,\r\n\t\tselected,\r\n\t\tstartDate,\r\n\t\tendDate,\r\n\t\trange,\r\n\t\tmultiple,\r\n\t\tallowSameDay\r\n\t} = {}) {\r\n\t\t// 当前日期\r\n\t\tthis.date = this.getDate(new Date()) // 当前初入日期\r\n\t\t// 打点信息\r\n\t\tthis.selected = selected || [];\r\n\t\t// 范围开始\r\n\t\tthis.startDate = startDate\r\n\t\t// 范围结束\r\n\t\tthis.endDate = endDate\r\n\t\tthis.range = range\r\n\t\tthis.multiple = multiple\r\n\t\tthis.allowSameDay = allowSameDay\r\n\t\t// 多选状态\r\n\t\tthis.cleanRangeStatus()\r\n\t\t// 范围状态\r\n\t\tthis.cleanMultipleStatus()\r\n\t\t// 每周日期\r\n\t\tthis.weeks = {}\r\n\t\t// this._getWeek(this.date.fullDate)\r\n\t}\r\n\t/**\r\n\t * 设置日期\r\n\t * @param {Object} date\r\n\t */\r\n\tsetDate(date, status) {\r\n\t\tif (this.range && status == 'init') {\r\n\t\t\tthis.cleanRangeStatus();\r\n\t\t\tif (Array.isArray(date)) {\r\n\t\t\t\tthis.rangeStatus.before = date[0];\r\n\t\t\t\tthis.rangeStatus.after = date.length > 1 ? date[date.length - 1] : '';\r\n\t\t\t\tif (this.rangeStatus.after && this.dateCompare(this.rangeStatus.before, this.rangeStatus.after)) {\r\n\t\t\t\t\tthis.rangeStatus.data = this.geDateAll(this.rangeStatus.before, this.rangeStatus.after)\r\n\t\t\t\t}\r\n\t\t\t\tthis.selectDate = this.getDate(date[0])\r\n\t\t\t\tthis._getWeek(this.selectDate.fullDate)\r\n\t\t\t} else {\r\n\t\t\t\tthis.selectDate = this.getDate(date)\r\n\t\t\t\tthis.rangeStatus.before = this.selectDate.fullDate;\r\n\t\t\t\tthis._getWeek(this.selectDate.fullDate)\r\n\t\t\t}\r\n\t\t} else if (this.multiple && status == 'init') {\r\n\t\t\tthis.cleanMultipleStatus();\r\n\t\t\tif (Array.isArray(date)) {\r\n\t\t\t\tthis.multipleStatus.data = date;\r\n\t\t\t\tthis.selectDate = this.getDate(date[0])\r\n\t\t\t\tthis._getWeek(this.selectDate.fullDate)\r\n\t\t\t} else {\r\n\t\t\t\tthis.selectDate = this.getDate(date)\r\n\t\t\t\tthis.multipleStatus.data = [this.selectDate.fullDate];\r\n\t\t\t\tthis._getWeek(this.selectDate.fullDate)\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tif (Array.isArray(date)) {\r\n\t\t\t\tthis.selectDate = this.getDate(date[0])\r\n\t\t\t\tthis._getWeek(this.selectDate.fullDate)\r\n\t\t\t} else {\r\n\t\t\t\tthis.selectDate = this.getDate(date)\r\n\t\t\t\tthis._getWeek(this.selectDate.fullDate)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t/**\r\n\t * 清理多选状态\r\n\t */\r\n\tcleanRangeStatus() {\r\n\t\tthis.rangeStatus = {\r\n\t\t\tbefore: '',\r\n\t\t\tafter: '',\r\n\t\t\tdata: []\r\n\t\t}\r\n\t}\r\n\t/**\r\n\t * 清理多选状态\r\n\t */\r\n\tcleanMultipleStatus() {\r\n\t\tthis.multipleStatus = {\r\n\t\t\tdata: []\r\n\t\t}\r\n\t}\r\n\t/**\r\n\t * 重置开始日期\r\n\t */\r\n\tresetSatrtDate(startDate) {\r\n\t\t// 范围开始\r\n\t\tthis.startDate = startDate\r\n\t}\r\n\t/**\r\n\t * 重置结束日期\r\n\t */\r\n\tresetEndDate(endDate) {\r\n\t\t// 范围结束\r\n\t\tthis.endDate = endDate\r\n\t}\r\n\t/**\r\n\t * 获取任意时间\r\n\t */\r\n\tgetDate(date, AddDayCount = 0, str = 'day') {\r\n\t\tif (!date) {\r\n\t\t\tdate = new Date()\r\n\t\t}\r\n\t\tif (typeof date !== 'object') {\r\n\t\t\tdate = date.replace(/-/g, '/')\r\n\t\t}\r\n\t\tconst dd = new Date(date)\r\n\t\tswitch (str) {\r\n\t\t\tcase 'day':\r\n\t\t\t\tdd.setDate(dd.getDate() + AddDayCount) // 获取AddDayCount天后的日期\r\n\t\t\t\tbreak\r\n\t\t\tcase 'month':\r\n\t\t\t\tif (dd.getDate() === 31 && AddDayCount > 0) {\r\n\t\t\t\t\tdd.setDate(dd.getDate() + AddDayCount)\r\n\t\t\t\t} else {\r\n\t\t\t\t\tconst preMonth = dd.getMonth()\r\n\t\t\t\t\tdd.setMonth(preMonth + AddDayCount) // 获取AddDayCount天后的日期\r\n\t\t\t\t\tconst nextMonth = dd.getMonth()\r\n\t\t\t\t\t// 处理 pre 切换月份目标月份为2月没有当前日(30 31) 切换错误问题\r\n\t\t\t\t\tif (AddDayCount < 0 && preMonth !== 0 && nextMonth - preMonth > AddDayCount) {\r\n\t\t\t\t\t\tdd.setMonth(nextMonth + (nextMonth - preMonth + AddDayCount))\r\n\t\t\t\t\t}\r\n\t\t\t\t\t// 处理 next 切换月份目标月份为2月没有当前日(30 31) 切换错误问题\r\n\t\t\t\t\tif (AddDayCount > 0 && nextMonth - preMonth > AddDayCount) {\r\n\t\t\t\t\t\tdd.setMonth(nextMonth - (nextMonth - preMonth - AddDayCount))\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tbreak\r\n\t\t\tcase 'year':\r\n\t\t\t\tdd.setFullYear(dd.getFullYear() + AddDayCount) // 获取AddDayCount天后的日期\r\n\t\t\t\tbreak\r\n\t\t}\r\n\t\tconst y = dd.getFullYear()\r\n\t\tconst m = dd.getMonth() + 1 < 10 ? '0' + (dd.getMonth() + 1) : dd.getMonth() + 1 // 获取当前月份的日期，不足10补0\r\n\t\tconst d = dd.getDate() < 10 ? '0' + dd.getDate() : dd.getDate() // 获取当前几号，不足10补0\r\n\t\treturn {\r\n\t\t\tfullDate: y + '-' + m + '-' + d,\r\n\t\t\tyear: y,\r\n\t\t\tmonth: m,\r\n\t\t\tdate: d,\r\n\t\t\tday: dd.getDay()\r\n\t\t}\r\n\t}\r\n\t/**\r\n\t * 获取上月剩余天数\r\n\t */\r\n\t_getLastMonthDays(firstDay, full) {\r\n\t\tlet dateArr = []\r\n\t\tfor (let i = firstDay; i > 0; i--) {\r\n\t\t\tconst beforeDate = new Date(full.year, full.month - 1, -i + 1).getDate()\r\n\t\t\tdateArr.push({\r\n\t\t\t\tdate: beforeDate,\r\n\t\t\t\tmonth: full.month - 1,\r\n\t\t\t\tlunar: this.getlunar(full.year, full.month - 1, beforeDate),\r\n\t\t\t\tdisable: true\r\n\t\t\t})\r\n\t\t}\r\n\t\treturn dateArr\r\n\t}\r\n\t/**\r\n\t * 获取本月天数\r\n\t */\r\n\t_currentMonthDys(dateData, full) {\r\n\t\tlet dateArr = []\r\n\t\tlet fullDate = this.date.fullDate\r\n\t\tfor (let i = 1; i <= dateData; i++) {\r\n\t\t\tlet nowDate = full.year + '-' + (full.month < 10 ? full.month : full.month) + '-' + (i < 10 ? '0' + i : i)\r\n\t\t\t// 是否今天\r\n\t\t\tlet isDay = fullDate === nowDate\r\n\t\t\t// 获取打点信息\r\n\t\t\tlet info = this.selected && this.selected.find((item) => {\r\n\t\t\t\tif (this.dateEqual(nowDate, item.date)) {\r\n\t\t\t\t\treturn item\r\n\t\t\t\t}\r\n\t\t\t})\r\n\t\t\t// 日期禁用\r\n\t\t\tlet disableBefore = true\r\n\t\t\tlet disableAfter = true\r\n\t\t\tif (this.startDate) {\r\n\t\t\t\t// let dateCompBefore = this.dateCompare(this.startDate, fullDate)\r\n\t\t\t\t// disableBefore = this.dateCompare(dateCompBefore ? this.startDate : fullDate, nowDate)\r\n\t\t\t\tdisableBefore = this.dateCompare(this.startDate, nowDate)\r\n\t\t\t}\r\n\t\t\tif (this.endDate) {\r\n\t\t\t\t// let dateCompAfter = this.dateCompare(fullDate, this.endDate)\r\n\t\t\t\t// disableAfter = this.dateCompare(nowDate, dateCompAfter ? this.endDate : fullDate)\r\n\t\t\t\tdisableAfter = this.dateCompare(nowDate, this.endDate)\r\n\t\t\t}\r\n\t\t\tlet ranges = this.rangeStatus.data\r\n\t\t\tlet checked = false\r\n\t\t\tlet rangesStatus = -1\r\n\t\t\tif (this.range) {\r\n\t\t\t\tif (ranges) {\r\n\t\t\t\t\trangesStatus = ranges.findIndex((item) => {\r\n\t\t\t\t\t\treturn this.dateEqual(item, nowDate)\r\n\t\t\t\t\t})\r\n\t\t\t\t}\r\n\t\t\t\tif (rangesStatus !== -1) {\r\n\t\t\t\t\tchecked = true\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tlet multiples = this.multipleStatus.data\r\n\t\t\tlet checked_multiple = false\r\n\t\t\tlet multiplesStatus = -1\r\n\t\t\tif (this.multiple) {\r\n\t\t\t\tif (multiples) {\r\n\t\t\t\t\tmultiplesStatus = multiples.findIndex((item) => {\r\n\t\t\t\t\t\treturn this.dateEqual(item, nowDate)\r\n\t\t\t\t\t})\r\n\t\t\t\t}\r\n\t\t\t\tif (multiplesStatus !== -1) {\r\n\t\t\t\t\tchecked_multiple = true\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tlet data = {\r\n\t\t\t\tfullDate: nowDate,\r\n\t\t\t\tyear: full.year,\r\n\t\t\t\tdate: i,\r\n\t\t\t\trange: this.range ? checked : false,\r\n\t\t\t\tmultiple: this.multiple ? checked_multiple : false,\r\n\t\t\t\tbeforeRange: this.dateEqual(this.rangeStatus.before, nowDate),\r\n\t\t\t\tafterRange: this.dateEqual(this.rangeStatus.after, nowDate),\r\n\t\t\t\tdateEqual: this.range && checked && this.dateEqual(this.rangeStatus.before, this.rangeStatus.after),\r\n\t\t\t\tmonth: full.month,\r\n\t\t\t\tlunar: this.getlunar(full.year, full.month, i),\r\n\t\t\t\tdisable: !(disableBefore && disableAfter),\r\n\t\t\t\tisDay\r\n\t\t\t}\r\n\t\t\tif (info) {\r\n\t\t\t\tdata.extraInfo = info\r\n\t\t\t}\r\n\t\t\tdateArr.push(data)\r\n\t\t}\r\n\t\treturn dateArr\r\n\t}\r\n\t/**\r\n\t * 获取下月天数\r\n\t */\r\n\t_getNextMonthDays(surplus, full) {\r\n\t\tlet dateArr = []\r\n\t\tfor (let i = 1; i < surplus + 1; i++) {\r\n\t\t\tdateArr.push({\r\n\t\t\t\tdate: i,\r\n\t\t\t\tmonth: Number(full.month) + 1,\r\n\t\t\t\tlunar: this.getlunar(full.year, Number(full.month) + 1, i),\r\n\t\t\t\tdisable: true\r\n\t\t\t})\r\n\t\t}\r\n\t\treturn dateArr\r\n\t}\r\n\t/**\r\n\t * 获取当前日期详情\r\n\t * @param {Object} date\r\n\t */\r\n\tgetInfo(date) {\r\n\t\tif (!date) {\r\n\t\t\tdate = new Date()\r\n\t\t} else if (Array.isArray(date)) {\r\n\t\t\tdate = date[0]\r\n\t\t}\r\n\t\tconst dateInfo = this.canlender.find(item => item.fullDate === this.getDate(date).fullDate)\r\n\t\treturn dateInfo\r\n\t}\r\n\t/**\r\n\t * 比较时间大小\r\n\t */\r\n\tdateCompare(startDate, endDate) {\r\n\t\t// 计算截止时间\r\n\t\tstartDate = new Date(startDate.replace('-', '/').replace('-', '/'))\r\n\t\t// 计算详细项的截止时间\r\n\t\tendDate = new Date(endDate.replace('-', '/').replace('-', '/'))\r\n\t\tif (startDate <= endDate) {\r\n\t\t\treturn true\r\n\t\t} else {\r\n\t\t\treturn false\r\n\t\t}\r\n\t}\r\n\t/**\r\n\t * 比较时间是否相等\r\n\t */\r\n\tdateEqual(before, after) {\r\n\t\t// 计算截止时间\r\n\t\tbefore = new Date(before.replace('-', '/').replace('-', '/'))\r\n\t\t// 计算详细项的截止时间\r\n\t\tafter = new Date(after.replace('-', '/').replace('-', '/'))\r\n\t\tif (before.getTime() - after.getTime() === 0) {\r\n\t\t\treturn true\r\n\t\t} else {\r\n\t\t\treturn false\r\n\t\t}\r\n\t}\r\n\t/**\r\n\t * 比较after时间是否大于before时间\r\n\t */\r\n\tdateAfterLgBefore(before, after) {\r\n\t\t// 计算截止时间\r\n\t\tbefore = new Date(before.replace('-', '/').replace('-', '/'))\r\n\t\t// 计算详细项的截止时间\r\n\t\tafter = new Date(after.replace('-', '/').replace('-', '/'))\r\n\t\tif (after.getTime() - before.getTime() > 0) {\r\n\t\t\treturn true\r\n\t\t} else {\r\n\t\t\treturn false\r\n\t\t}\r\n\t}\r\n\t/**\r\n\t * 获取日期范围内所有日期\r\n\t * @param {Object} begin\r\n\t * @param {Object} end\r\n\t */\r\n\tgeDateAll(begin, end) {\r\n\t\tvar arr = []\r\n\t\tvar ab = begin.split('-')\r\n\t\tvar ae = end.split('-')\r\n\t\tvar db = new Date()\r\n\t\tdb.setFullYear(ab[0], ab[1] - 1, ab[2])\r\n\t\tvar de = new Date()\r\n\t\tde.setFullYear(ae[0], ae[1] - 1, ae[2])\r\n\t\tvar unixDb = db.getTime() - 24 * 60 * 60 * 1000\r\n\t\tvar unixDe = de.getTime() - 24 * 60 * 60 * 1000\r\n\t\tfor (var k = unixDb; k <= unixDe;) {\r\n\t\t\tk = k + 24 * 60 * 60 * 1000\r\n\t\t\tarr.push(this.getDate(new Date(parseInt(k))).fullDate)\r\n\t\t}\r\n\t\treturn arr\r\n\t}\r\n\t/**\r\n\t * 计算阴历日期显示\r\n\t */\r\n\tgetlunar(year, month, date) {\r\n\t\treturn CALENDAR.solar2lunar(year, month, date)\r\n\t}\r\n\t/**\r\n\t * 设置打点\r\n\t */\r\n\tsetSelectInfo(data, value) {\r\n\t\tthis.selected = value\r\n\t\tthis._getWeek(data)\r\n\t}\r\n\t/**\r\n\t * 获取多选状态\r\n\t */\r\n\tsetMultiple(fullDate) {\r\n\t\tif (!this.multiple) return\r\n\t\tlet multiples = this.multipleStatus.data;\r\n\t\tconst findIndex = multiples.findIndex(item => this.dateEqual(fullDate, item));\r\n\t\tif (findIndex < 0) {\r\n\t\t\tthis.multipleStatus.data = this.multipleStatus.data.concat([fullDate]);\r\n\t\t} else {\r\n\t\t\tthis.multipleStatus.data.splice(findIndex, 1);\r\n\t\t}\r\n\t\tthis._getWeek(fullDate)\r\n\t}\r\n\t/**\r\n\t *  获取范围状态\r\n\t */\r\n\tsetRange(fullDate) {\r\n\t\tlet {\r\n\t\t\tbefore,\r\n\t\t\tafter\r\n\t\t} = this.rangeStatus\r\n\t\tif (!this.range) return\r\n\t\tif (before && after) {\r\n\t\t\tthis.cleanRangeStatus();\r\n\t\t\tthis.rangeStatus.before = fullDate\r\n\t\t} else {\r\n\t\t\tif (!before) {\r\n\t\t\t\tthis.rangeStatus.before = fullDate\r\n\t\t\t} else {\r\n\t\t\t\tif (this.allowSameDay && this.dateEqual(before, fullDate)) {\r\n\t\t\t\t\tthis.rangeStatus.after = fullDate\r\n\t\t\t\t} else if (!this.dateAfterLgBefore(this.rangeStatus.before, fullDate)) {\r\n\t\t\t\t\tthis.cleanRangeStatus();\r\n\t\t\t\t\tthis.rangeStatus.before = fullDate\r\n\t\t\t\t\tthis._getWeek(fullDate)\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\t\t\t\tthis.rangeStatus.after = fullDate\r\n\t\t\t\tif (this.dateCompare(this.rangeStatus.before, this.rangeStatus.after)) {\r\n\t\t\t\t\tthis.rangeStatus.data = this.geDateAll(this.rangeStatus.before, this.rangeStatus.after);\r\n\t\t\t\t} else {\r\n\t\t\t\t\tthis.rangeStatus.data = this.geDateAll(this.rangeStatus.after, this.rangeStatus.before);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\tthis._getWeek(fullDate)\r\n\t}\r\n\t/**\r\n\t * 获取每周数据\r\n\t * @param {Object} dateData\r\n\t */\r\n\t_getWeek(dateData) {\r\n\t\tconst {\r\n\t\t\tyear,\r\n\t\t\tmonth\r\n\t\t} = this.getDate(dateData)\r\n\t\tlet firstDay = new Date(year, month - 1, 1).getDay()\r\n\t\tlet currentDay = new Date(year, month, 0).getDate()\r\n\t\tlet dates = {\r\n\t\t\tlastMonthDays: this._getLastMonthDays(firstDay, this.getDate(dateData)), // 上个月末尾几天\r\n\t\t\tcurrentMonthDys: this._currentMonthDys(currentDay, this.getDate(dateData)), // 本月天数\r\n\t\t\tnextMonthDays: [], // 下个月开始几天\r\n\t\t\tweeks: []\r\n\t\t}\r\n\t\tlet canlender = []\r\n\t\tconst surplus = 42 - (dates.lastMonthDays.length + dates.currentMonthDys.length)\r\n\t\tdates.nextMonthDays = this._getNextMonthDays(surplus, this.getDate(dateData))\r\n\t\tcanlender = canlender.concat(dates.lastMonthDays, dates.currentMonthDys, dates.nextMonthDays)\r\n\t\tlet weeks = {}\r\n\t\t// 拼接数组  上个月开始几天 + 本月天数+ 下个月开始几天\r\n\t\tfor (let i = 0; i < canlender.length; i++) {\r\n\t\t\tif (i % 7 === 0) {\r\n\t\t\t\tweeks[parseInt(i / 7)] = new Array(7)\r\n\t\t\t}\r\n\t\t\tweeks[parseInt(i / 7)][i % 7] = canlender[i]\r\n\t\t}\r\n\t\tthis.canlender = canlender\r\n\t\tthis.weeks = weeks\r\n\t}\r\n\t//静态方法\r\n\t// static init(date) {\r\n\t// \tif (!this.instance) {\r\n\t// \t\tthis.instance = new Calendar(date);\r\n\t// \t}\r\n\t// \treturn this.instance;\r\n\t// }\r\n}\r\nexport default Calendar"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-calendars/components/uv-calendars/uv-calendar-body.vue",
    "content": "<template>\r\n\t<view class=\"uv-calendar-body\">\r\n\t\t<view class=\"uv-calendar__header\">\r\n\t\t\t<view class=\"uv-calendar__header-btn-box\" @click.stop=\"pre\">\r\n\t\t\t\t<view class=\"uv-calendar__header-btn uv-calendar--left\"></view>\r\n\t\t\t</view>\r\n\t\t\t<picker mode=\"date\" :value=\"getDate\" fields=\"month\" @change=\"bindDateChange\">\r\n\t\t\t\t<text class=\"uv-calendar__header-text\">{{ (nowDate.year||'') +' / '+( nowDate.month||'')}}</text>\r\n\t\t\t</picker>\r\n\t\t\t<view class=\"uv-calendar__header-btn-box\" @click.stop=\"next\">\r\n\t\t\t\t<view class=\"uv-calendar__header-btn uv-calendar--right\"></view>\r\n\t\t\t</view>\r\n\t\t\t<text class=\"uv-calendar__backtoday\" @click=\"backToday\">{{todayText}}</text>\r\n\t\t</view>\r\n\t\t<view class=\"uv-calendar__box\">\r\n\t\t\t<view v-if=\"showMonth\" class=\"uv-calendar__box-bg\">\r\n\t\t\t\t<text class=\"uv-calendar__box-bg-text\">{{nowDate.month}}</text>\r\n\t\t\t</view>\r\n\t\t\t<view class=\"uv-calendar__weeks uv-calendar__weeks-week\">\r\n\t\t\t\t<view class=\"uv-calendar__weeks-day\">\r\n\t\t\t\t\t<text class=\"uv-calendar__weeks-day-text\">{{SUNText}}</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view class=\"uv-calendar__weeks-day\">\r\n\t\t\t\t\t<text class=\"uv-calendar__weeks-day-text\">{{monText}}</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view class=\"uv-calendar__weeks-day\">\r\n\t\t\t\t\t<text class=\"uv-calendar__weeks-day-text\">{{TUEText}}</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view class=\"uv-calendar__weeks-day\">\r\n\t\t\t\t\t<text class=\"uv-calendar__weeks-day-text\">{{WEDText}}</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view class=\"uv-calendar__weeks-day\">\r\n\t\t\t\t\t<text class=\"uv-calendar__weeks-day-text\">{{THUText}}</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view class=\"uv-calendar__weeks-day\">\r\n\t\t\t\t\t<text class=\"uv-calendar__weeks-day-text\">{{FRIText}}</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view class=\"uv-calendar__weeks-day\">\r\n\t\t\t\t\t<text class=\"uv-calendar__weeks-day-text\">{{SATText}}</text>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t\t<view class=\"uv-calendar__weeks\" v-for=\"(item,weekIndex) in weeks\" :key=\"weekIndex\">\r\n\t\t\t\t<view class=\"uv-calendar__weeks-item\" v-for=\"(weeks,weeksIndex) in item\" :key=\"weeksIndex\">\r\n\t\t\t\t\t<calendar-item class=\"uv-calendar-item--hook\" :weeks=\"weeks\" :rangeInfoText=\"rangeInfoText(weeks)\" :multiple=\"multiple\" :calendar=\"calendar\" :selected=\"selected\" :lunar=\"lunar\" :color=\"color\" @change=\"choiceDate\"></calendar-item>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js';\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js';\r\n\r\n\timport CalendarItem from './uv-calendar-item.vue';\r\n\r\n\timport { initVueI18n } from '@dcloudio/uni-i18n';\r\n\timport i18nMessages from './i18n/index.js';\r\n\tconst { t } = initVueI18n(i18nMessages);\r\n\texport default {\r\n\t\tmixins: [mpMixin, mixin],\r\n\t\tcomponents: {\r\n\t\t\tCalendarItem\r\n\t\t},\r\n\t\tprops: {\r\n\t\t\tdate: {\r\n\t\t\t\ttype: [String,Array],\r\n\t\t\t\tdefault: ''\r\n\t\t\t},\r\n\t\t\tnowDate: {\r\n\t\t\t\ttype: [String, Object],\r\n\t\t\t\tdefault: ''\r\n\t\t\t},\r\n\t\t\tweeks: {\r\n\t\t\t\ttype: [Array, Object],\r\n\t\t\t\tdefault () {\r\n\t\t\t\t\treturn []\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tcalendar: {\r\n\t\t\t\ttype: Object,\r\n\t\t\t\tdefault () {\r\n\t\t\t\t\treturn {}\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tselected: {\r\n\t\t\t\ttype: Array,\r\n\t\t\t\tdefault () {\r\n\t\t\t\t\treturn []\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tlunar: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: false\r\n\t\t\t},\r\n\t\t\tshowMonth: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: true\r\n\t\t\t},\r\n\t\t\tcolor: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: '#3c9cff'\r\n\t\t\t},\r\n\t\t\tstartText: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: '开始'\r\n\t\t\t},\r\n\t\t\tendText: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: '结束'\r\n\t\t\t},\r\n\t\t\tmultiple: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: false\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\tgetDate() {\r\n\t\t\t\treturn Array.isArray(this.date) ? this.date[0] : this.date;\r\n\t\t\t},\r\n\t\t\t/**\r\n\t\t\t * for i18n\r\n\t\t\t */\r\n\t\t\ttodayText() {\r\n\t\t\t\treturn t(\"uv-calender.today\")\r\n\t\t\t},\r\n\t\t\tmonText() {\r\n\t\t\t\treturn t(\"uv-calender.MON\")\r\n\t\t\t},\r\n\t\t\tTUEText() {\r\n\t\t\t\treturn t(\"uv-calender.TUE\")\r\n\t\t\t},\r\n\t\t\tWEDText() {\r\n\t\t\t\treturn t(\"uv-calender.WED\")\r\n\t\t\t},\r\n\t\t\tTHUText() {\r\n\t\t\t\treturn t(\"uv-calender.THU\")\r\n\t\t\t},\r\n\t\t\tFRIText() {\r\n\t\t\t\treturn t(\"uv-calender.FRI\")\r\n\t\t\t},\r\n\t\t\tSATText() {\r\n\t\t\t\treturn t(\"uv-calender.SAT\")\r\n\t\t\t},\r\n\t\t\tSUNText() {\r\n\t\t\t\treturn t(\"uv-calender.SUN\")\r\n\t\t\t},\r\n\t\t\trangeInfoText(weeks) {\r\n\t\t\t\treturn weeks=> {\r\n\t\t\t\t\tif(weeks.beforeRange) {\r\n\t\t\t\t\t\tif(weeks.extraInfo) {\r\n\t\t\t\t\t\t\tweeks.extraInfo.info = this.startText;\r\n\t\t\t\t\t\t}else {\r\n\t\t\t\t\t\t\tweeks.extraInfo = {\r\n\t\t\t\t\t\t\t\tinfo: this.startText\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif(weeks.afterRange) {\r\n\t\t\t\t\t\tif(weeks.extraInfo) {\r\n\t\t\t\t\t\t\tweeks.extraInfo.info = this.endText;\r\n\t\t\t\t\t\t}else {\r\n\t\t\t\t\t\t\tweeks.extraInfo = {\r\n\t\t\t\t\t\t\t\tinfo: this.endText\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tbindDateChange(e) {\r\n\t\t\t\tthis.$emit('bindDateChange', e);\r\n\t\t\t},\r\n\t\t\tbackToday() {\r\n\t\t\t\tthis.$emit('backToday');\r\n\t\t\t},\r\n\t\t\tpre() {\r\n\t\t\t\tthis.$emit('pre');\r\n\t\t\t},\r\n\t\t\tnext() {\r\n\t\t\t\tthis.$emit('next');\r\n\t\t\t},\r\n\t\t\tchoiceDate(e) {\r\n\t\t\t\tthis.$emit('choiceDate', e);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style scoped lang=\"scss\">\r\n\t@mixin flex($direction: row) {\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\tdisplay: flex;\r\n\t\t/* #endif */\r\n\t\tflex-direction: $direction;\r\n\t}\r\n\t$uv-bg-color-mask: rgba($color: #000000, $alpha: 0.4);\r\n\t$uv-border-color: #EDEDED !default;\r\n\t$uv-text-color: #333;\r\n\t$uv-bg-color-hover: #f1f1f1;\r\n\t$uv-font-size-base: 14px;\r\n\t$uv-text-color-placeholder: #808080;\r\n\t$uv-color-subtitle: #555555;\r\n\t$uv-text-color-grey: #999;\r\n\t.uv-calendar {\r\n\t\t@include flex(column);\r\n\t}\r\n\t.uv-calendar__mask {\r\n\t\tposition: fixed;\r\n\t\tbottom: 0;\r\n\t\ttop: 0;\r\n\t\tleft: 0;\r\n\t\tright: 0;\r\n\t\tbackground-color: $uv-bg-color-mask;\r\n\t\ttransition-property: opacity;\r\n\t\ttransition-duration: 0.3s;\r\n\t\topacity: 0;\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\tz-index: 99;\r\n\t\t/* #endif */\r\n\t}\r\n\t.uv-calendar--mask-show {\r\n\t\topacity: 1\r\n\t}\r\n\t.uv-calendar--fixed {\r\n\t\tposition: fixed;\r\n\t\t/* #ifdef APP-NVUE */\r\n\t\tbottom: 0;\r\n\t\t/* #endif */\r\n\t\tleft: 0;\r\n\t\tright: 0;\r\n\t\ttransition-property: transform;\r\n\t\ttransition-duration: 0.3s;\r\n\t\ttransform: translateY(460px);\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\tbottom: calc(var(--window-bottom));\r\n\t\tz-index: 99;\r\n\t\t/* #endif */\r\n\t}\r\n\t.uv-calendar--ani-show {\r\n\t\ttransform: translateY(0);\r\n\t}\r\n\t.uv-calendar__content {\r\n\t\tbackground-color: #fff;\r\n\t}\r\n\t.uv-calendar__header {\r\n\t\tposition: relative;\r\n\t\t@include flex;\r\n\t\tjustify-content: center;\r\n\t\talign-items: center;\r\n\t\theight: 50px;\r\n\t\tborder-bottom-color: $uv-border-color;\r\n\t\tborder-bottom-style: solid;\r\n\t\tborder-bottom-width: 1px;\r\n\t}\r\n\t.uv-calendar--fixed-top {\r\n\t\t@include flex;\r\n\t\tjustify-content: space-between;\r\n\t\tborder-top-color: $uv-border-color;\r\n\t\tborder-top-style: solid;\r\n\t\tborder-top-width: 1px;\r\n\t}\r\n\t.uv-calendar--fixed-width {\r\n\t\twidth: 50px;\r\n\t}\r\n\t.uv-calendar__backtoday {\r\n\t\tposition: absolute;\r\n\t\tright: 0;\r\n\t\ttop: 25rpx;\r\n\t\tpadding: 0 5px;\r\n\t\tpadding-left: 10px;\r\n\t\theight: 25px;\r\n\t\tline-height: 25px;\r\n\t\tfont-size: 12px;\r\n\t\tborder-top-left-radius: 25px;\r\n\t\tborder-bottom-left-radius: 25px;\r\n\t\tcolor: $uv-text-color;\r\n\t\tbackground-color: $uv-bg-color-hover;\r\n\t}\r\n\t.uv-calendar__header-text {\r\n\t\ttext-align: center;\r\n\t\twidth: 100px;\r\n\t\tfont-size: $uv-font-size-base;\r\n\t\tcolor: $uv-text-color;\r\n\t}\r\n\t.uv-calendar__header-btn-box {\r\n\t\t@include flex;\r\n\t\talign-items: center;\r\n\t\tjustify-content: center;\r\n\t\twidth: 50px;\r\n\t\theight: 50px;\r\n\t}\r\n\t.uv-calendar__header-btn {\r\n\t\twidth: 10px;\r\n\t\theight: 10px;\r\n\t\tborder-left-color: $uv-text-color-placeholder;\r\n\t\tborder-left-style: solid;\r\n\t\tborder-left-width: 2px;\r\n\t\tborder-top-color: $uv-color-subtitle;\r\n\t\tborder-top-style: solid;\r\n\t\tborder-top-width: 2px;\r\n\t}\r\n\t.uv-calendar--left {\r\n\t\ttransform: rotate(-45deg);\r\n\t}\r\n\t.uv-calendar--right {\r\n\t\ttransform: rotate(135deg);\r\n\t}\r\n\t.uv-calendar__weeks {\r\n\t\tposition: relative;\r\n\t\t@include flex;\r\n\t}\r\n\t.uv-calendar__weeks-week {\r\n\t\tpadding: 0 0 2rpx;\r\n\t}\r\n\t.uv-calendar__weeks-item {\r\n\t\tflex: 1;\r\n\t}\r\n\t.uv-calendar__weeks-day {\r\n\t\tflex: 1;\r\n\t\t@include flex(column);\r\n\t\tjustify-content: center;\r\n\t\talign-items: center;\r\n\t\theight: 45px;\r\n\t\tborder-bottom-color: #F5F5F5;\r\n\t\tborder-bottom-style: solid;\r\n\t\tborder-bottom-width: 1px;\r\n\t}\r\n\t.uv-calendar__weeks-day-text {\r\n\t\tfont-size: 14px;\r\n\t}\r\n\t.uv-calendar__box {\r\n\t\tposition: relative;\r\n\t}\r\n\t.uv-calendar__box-bg {\r\n\t\t@include flex(column);\r\n\t\tjustify-content: center;\r\n\t\talign-items: center;\r\n\t\tposition: absolute;\r\n\t\ttop: 0;\r\n\t\tleft: 0;\r\n\t\tright: 0;\r\n\t\tbottom: 0;\r\n\t}\r\n\t.uv-calendar__box-bg-text {\r\n\t\tfont-size: 200px;\r\n\t\tfont-weight: bold;\r\n\t\tcolor: $uv-text-color-grey;\r\n\t\topacity: 0.1;\r\n\t\ttext-align: center;\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\tline-height: 1;\r\n\t\t/* #endif */\r\n\t}\r\n</style>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-calendars/components/uv-calendars/uv-calendar-item.vue",
    "content": "<template>\r\n\t<view class=\"uv-calendar-item__weeks-box\" :class=\"{\r\n\t\t'uv-calendar-item--disable':weeks.disable || (weeks.extraInfo&&weeks.extraInfo.disable),\r\n\t\t'uv-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay && !multiple,\r\n\t\t'uv-calendar-item--checked':(calendar.fullDate === weeks.fullDate && !weeks.isDay && !multiple) ,\r\n\t\t'uv-calendar-item--before-checked':weeks.beforeRange,\r\n\t\t'uv-calendar-item--range': weeks.range,\r\n\t\t'uv-calendar-item--after-checked':weeks.afterRange,\r\n\t\t'uv-calendar-item--multiple':weeks.multiple\r\n\t\t}\" :style=\"[itemBoxStyle]\" @click=\"choiceDate(weeks)\">\r\n\t\t<view class=\"uv-calendar-item__weeks-box-item\">\r\n\t\t\t<text v-if=\"selected&&weeks.extraInfo&&weeks.extraInfo.badge\" class=\"uv-calendar-item__weeks-box-circle\"></text>\r\n\t\t\t<text \r\n\t\t\t\tclass=\"uv-calendar-item__weeks-top-text\" \r\n\t\t\t\tv-if=\"weeks.extraInfo&&weeks.extraInfo.topinfo\"\r\n\t\t\t\t:style=\"[infoStyle('top')]\"\r\n\t\t\t>{{weeks.extraInfo&&weeks.extraInfo.topinfo}}</text>\r\n\t\t\t<text class=\"uv-calendar-item__weeks-box-text\" :class=\"{\r\n\t\t\t\t'uv-calendar-item--isDay-text': weeks.isDay,\r\n\t\t\t\t'uv-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay && !multiple,\r\n\t\t\t\t'uv-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay && !multiple,\r\n\t\t\t\t'uv-calendar-item--before-checked':weeks.beforeRange,\r\n\t\t\t\t'uv-calendar-item--range': weeks.range,\r\n\t\t\t\t'uv-calendar-item--after-checked':weeks.afterRange,\r\n\t\t\t\t'uv-calendar-item--multiple':weeks.multiple,\r\n\t\t\t\t'uv-calendar-item--disable':weeks.disable || (weeks.extraInfo&&weeks.extraInfo.disable)\r\n\t\t\t\t}\" :style=\"[itemBoxStyle]\">{{weeks.date}}</text>\r\n\t\t\t<text v-if=\"!lunar&&!weeks.extraInfo && weeks.isDay\" class=\"uv-calendar-item__weeks-lunar-text\" :class=\"{\r\n\t\t\t\t'uv-calendar-item--isDay-text':weeks.isDay,\r\n\t\t\t\t'uv-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay && !multiple,\r\n\t\t\t\t'uv-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay && !multiple,\r\n\t\t\t\t'uv-calendar-item--before-checked':weeks.beforeRange,\r\n\t\t\t\t'uv-calendar-item--range': weeks.range,\r\n\t\t\t\t'uv-calendar-item--after-checked':weeks.afterRange,\r\n\t\t\t\t'uv-calendar-item--multiple':weeks.multiple\r\n\t\t\t\t}\" :style=\"[itemBoxStyle]\">{{todayText}}</text>\r\n\t\t\t<text v-if=\"lunar&&!weeks.extraInfo\" class=\"uv-calendar-item__weeks-lunar-text\" :class=\"{\r\n\t\t\t\t'uv-calendar-item--isDay-text':weeks.isDay,\r\n\t\t\t\t'uv-calendar-item--isDay':calendar.fullDate === weeks.fullDate && weeks.isDay && !multiple,\r\n\t\t\t\t'uv-calendar-item--checked':calendar.fullDate === weeks.fullDate && !weeks.isDay && !multiple,\r\n\t\t\t\t'uv-calendar-item--before-checked':weeks.beforeRange,\r\n\t\t\t\t'uv-calendar-item--range': weeks.range,\r\n\t\t\t\t'uv-calendar-item--after-checked':weeks.afterRange,\r\n\t\t\t\t'uv-calendar-item--multiple':weeks.multiple,\r\n\t\t\t\t'uv-calendar-item--disable':weeks.disable || (weeks.extraInfo&&weeks.extraInfo.disable)\r\n\t\t\t\t}\" :style=\"[itemBoxStyle]\">{{weeks.isDay ? todayText : (weeks.lunar.IDayCn === '初一'?weeks.lunar.IMonthCn:weeks.lunar.IDayCn)}}</text>\r\n\t\t\t\t<text \r\n\t\t\t\t\tv-if=\"weeks.extraInfo&&weeks.extraInfo.info\" \r\n\t\t\t\t\tclass=\"uv-calendar-item__weeks-lunar-text\" \r\n\t\t\t\t\t:style=\"[infoStyle('bottom')]\"\r\n\t\t\t\t>{{weeks.extraInfo.info}}</text>\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport { colorGradient } from '@/uni_modules/uv-ui-tools/libs/function/colorGradient.js';\r\n\r\n\timport { initVueI18n } from '@dcloudio/uni-i18n'\r\n\timport i18nMessages from './i18n/index.js'\r\n\tconst { t } = initVueI18n(i18nMessages)\r\n\texport default {\r\n\t\temits: ['change'],\r\n\t\tprops: {\r\n\t\t\tweeks: {\r\n\t\t\t\ttype: Object,\r\n\t\t\t\tdefault () {\r\n\t\t\t\t\treturn {}\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tcalendar: {\r\n\t\t\t\ttype: Object,\r\n\t\t\t\tdefault: () => {\r\n\t\t\t\t\treturn {}\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tselected: {\r\n\t\t\t\ttype: Array,\r\n\t\t\t\tdefault: () => {\r\n\t\t\t\t\treturn []\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tlunar: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: false\r\n\t\t\t},\r\n\t\t\tcolor: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: '#3c9cff'\r\n\t\t\t},\r\n\t\t\tmultiple: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: false\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\ttodayText() {\r\n\t\t\t\treturn t(\"uv-calender.today\")\r\n\t\t\t},\r\n\t\t\titemBoxStyle() {\r\n\t\t\t\tconst style = {};\r\n\t\t\t\tif (this.weeks.beforeRange || this.weeks.afterRange) {\r\n\t\t\t\t\tstyle.backgroundColor = this.color;\r\n\t\t\t\t} else if (this.weeks.range) {\r\n\t\t\t\t\tstyle.backgroundColor = colorGradient(this.color, '#ffffff', 100)[90]\r\n\t\t\t\t\tstyle.color = this.color;\r\n\t\t\t\t\tstyle.opacity = 0.8;\r\n\t\t\t\t\tstyle.borderRadius = 0;\r\n\t\t\t\t} else if (this.calendar.fullDate === this.weeks.fullDate && !this.weeks.isDay && !this.multiple) {\r\n\t\t\t\t\tstyle.backgroundColor = this.color;\r\n\t\t\t\t\tstyle.color = '#fff';\r\n\t\t\t\t} else if (this.weeks.isDay && this.calendar.fullDate === this.weeks.fullDate && !this.multiple) {\r\n\t\t\t\t\tstyle.backgroundColor = this.color;\r\n\t\t\t\t\tstyle.color = '#fff';\r\n\t\t\t\t} else if (this.weeks.isDay && !this.multiple) {\r\n\t\t\t\t\tstyle.color = this.color;\r\n\t\t\t\t} else if (this.multiple && this.weeks.multiple){\r\n\t\t\t\t\tstyle.backgroundColor = this.color;\r\n\t\t\t\t\tstyle.color = '#fff';\r\n\t\t\t\t} else if (this.weeks.isDay && this.multiple) {\r\n\t\t\t\t\tstyle.color = this.color;\r\n\t\t\t\t}\r\n\t\t\t\treturn style;\r\n\t\t\t},\r\n\t\t\tinfoStyle(val) {\r\n\t\t\t\treturn val => {\r\n\t\t\t\t\tconst style = {};\r\n\t\t\t\t\tif (val == 'top') {\r\n\t\t\t\t\t\tstyle.color = this.weeks.extraInfo.topinfoColor ? this.weeks.extraInfo.topinfoColor : '#606266';\r\n\t\t\t\t\t} else if (val == 'bottom') {\r\n\t\t\t\t\t\tstyle.color = this.weeks.extraInfo.infoColor ? this.weeks.extraInfo.infoColor : '#f56c6c';\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (this.weeks.range) {\r\n\t\t\t\t\t\tstyle.color = this.color;\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (this.calendar.fullDate === this.weeks.fullDate || this.weeks.beforeRange || this.weeks.afterRange) {\r\n\t\t\t\t\t\tstyle.color = '#fff';\r\n\t\t\t\t\t}\r\n\t\t\t\t\treturn style;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tchoiceDate(weeks) {\r\n\t\t\t\tif (this.weeks.extraInfo && this.weeks.extraInfo.disable) return;\r\n\t\t\t\tthis.$emit('change', weeks)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@mixin flex($direction: row) {\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\tdisplay: flex;\r\n\t\t/* #endif */\r\n\t\tflex-direction: $direction;\r\n\t}\r\n\t$uv-font-size-base: 14px;\r\n\t$uv-text-color: #333;\r\n\t$uv-font-size-sm: 24rpx;\r\n\t$uv-error: #f56c6c !default;\r\n\t$uv-opacity-disabled: 0.3;\r\n\t$uv-text-color-disable: #c0c0c0;\r\n\t$uv-primary: #3c9cff !default;\r\n\t$info-height: 32rpx;\r\n\t.uv-calendar-item__weeks-box {\r\n\t\tflex: 1;\r\n\t\t@include flex(column);\r\n\t\tjustify-content: center;\r\n\t\talign-items: center;\r\n\t\tborder-radius: 4px;\r\n\t}\r\n\t.uv-calendar-item__weeks-top-text {\r\n\t\theight: $info-height;\r\n\t\tline-height: $info-height;\r\n\t\tfont-size: $uv-font-size-sm;\r\n\t}\r\n\t.uv-calendar-item__weeks-box-text {\r\n\t\tfont-size: $uv-font-size-base;\r\n\t\tcolor: $uv-text-color;\r\n\t}\r\n\t.uv-calendar-item__weeks-lunar-text {\r\n\t\theight: $info-height;\r\n\t\tline-height: $info-height;\r\n\t\tfont-size: $uv-font-size-sm;\r\n\t\tcolor: $uv-text-color;\r\n\t}\r\n\t.uv-calendar-item__weeks-box-item {\r\n\t\tposition: relative;\r\n\t\t@include flex(column);\r\n\t\tjustify-content: center;\r\n\t\talign-items: center;\r\n\t\twidth: 106rpx;\r\n\t\theight: 56px;\r\n\t}\r\n\t.uv-calendar-item__weeks-box-circle {\r\n\t\tposition: absolute;\r\n\t\ttop: 5px;\r\n\t\tright: 5px;\r\n\t\twidth: 8px;\r\n\t\theight: 8px;\r\n\t\tborder-radius: 8px;\r\n\t\tbackground-color: $uv-error;\r\n\t}\r\n\t.uv-calendar-item--disable {\r\n\t\tbackground-color: rgba(249, 249, 249, $uv-opacity-disabled);\r\n\t\tcolor: $uv-text-color-disable;\r\n\t}\r\n\t.uv-calendar-item--isDay-text {\r\n\t\tcolor: $uv-primary;\r\n\t}\r\n\t.uv-calendar-item--isDay {\r\n\t\tbackground-color: $uv-primary;\r\n\t\tcolor: #fff;\r\n\t}\r\n\t.uv-calendar-item--checked {\r\n\t\tbackground-color: $uv-primary;\r\n\t\tcolor: #fff;\r\n\t\tborder-radius: 4px;\r\n\t}\r\n\t.uv-calendar-item--range {\r\n\t\tbackground-color: $uv-primary;\r\n\t\tcolor: #fff;\r\n\t}\r\n\t.uv-calendar-item--before-checked {\r\n\t\tcolor: #fff;\r\n\t}\r\n\t.uv-calendar-item--after-checked {\r\n\t\tcolor: #fff;\r\n\t}\r\n\t.uv-calendar-item--multiple {\r\n\t\tbackground-color: $uv-primary;\r\n\t\tcolor: #fff;\r\n\t}\r\n</style>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-calendars/components/uv-calendars/uv-calendars.vue",
    "content": "<template>\r\n\t<view class=\"uv-calendar\">\r\n\t\t<view class=\"uv-calendar__content\" v-if=\"insert\">\r\n\t\t\t<calendar-body\r\n\t\t\t\t:date=\"date\"\r\n\t\t\t\t:nowDate=\"nowDate\"\r\n\t\t\t\t:weeks=\"weeks\"\r\n\t\t\t\t:calendar=\"calendar\"\r\n\t\t\t\t:selected=\"selected\"\r\n\t\t\t\t:lunar=\"lunar\"\r\n\t\t\t\t:showMonth=\"showMonth\"\r\n\t\t\t\t:color=\"color\"\r\n\t\t\t\t:startText=\"startText\"\r\n\t\t\t\t:endText=\"endText\"\r\n\t\t\t\t:range=\"range\"\r\n\t\t\t\t:multiple=\"multiple\"\r\n\t\t\t\t:allowSameDay=\"allowSameDay\"\r\n\t\t\t\t@bindDateChange=\"bindDateChange\"\r\n\t\t\t\t@pre=\"pre\"\r\n\t\t\t\t@next=\"next\"\r\n\t\t\t\t@backToday=\"backToday\"\r\n\t\t\t\t@choiceDate=\"choiceDate\"\r\n\t\t\t></calendar-body>\r\n\t\t</view>\r\n\t\t<uv-popup ref=\"popup\" mode=\"bottom\" v-else :round=\"round\" z-index=\"998\" :close-on-click-overlay=\"closeOnClickOverlay\" @maskClick=\"maskClick\">\r\n\t\t\t<view style=\"min-height: 100px;\">\r\n\t\t\t\t<uv-toolbar\r\n\t\t\t\t\t:show=\"true\"\r\n\t\t\t\t\t:cancelColor=\"cancelColor\"\r\n\t\t\t\t\t:confirmColor=\"getConfirmColor\"\r\n\t\t\t\t\t:cancelText=\"cancelText\"\r\n\t\t\t\t\t:confirmText=\"confirmText\"\r\n\t\t\t\t\t:title=\"title\"\r\n\t\t\t\t\t@cancel=\"close\"\r\n\t\t\t\t\t@confirm=\"confirm\"></uv-toolbar>\r\n\t\t\t\t<view class=\"line\"></view>\r\n\t\t\t\t<calendar-body\r\n\t\t\t\t\t:nowDate=\"nowDate\"\r\n\t\t\t\t\t:weeks=\"weeks\"\r\n\t\t\t\t\t:calendar=\"calendar\"\r\n\t\t\t\t\t:selected=\"selected\"\r\n\t\t\t\t\t:lunar=\"lunar\"\r\n\t\t\t\t\t:showMonth=\"showMonth\"\r\n\t\t\t\t\t:color=\"color\"\r\n\t\t\t\t\t:startText=\"startText\"\r\n\t\t\t\t\t:endText=\"endText\"\r\n\t\t\t\t\t:range=\"range\"\r\n\t\t\t\t\t:multiple=\"multiple\"\r\n\t\t\t\t\t:allowSameDay=\"allowSameDay\"\r\n\t\t\t\t\t@bindDateChange=\"bindDateChange\"\r\n\t\t\t\t\t@pre=\"pre\"\r\n\t\t\t\t\t@next=\"next\"\r\n\t\t\t\t\t@backToday=\"backToday\"\r\n\t\t\t\t\t@choiceDate=\"choiceDate\"\r\n\t\t\t\t></calendar-body>\r\n\t\t\t</view>\r\n\t\t</uv-popup>\r\n\t</view>\r\n</template>\r\n<script>\r\n\t/**\r\n\t * Calendar 日历\r\n\t * @description 日历组件可以查看日期，选择任意范围内的日期，打点操作。常用场景如：酒店日期预订、火车机票选择购买日期、上下班打卡等\r\n\t * @tutorial https://ext.dcloud.net.cn/plugin?name=uv-calendar\r\n\t * @property {String} date 自定义当前时间，默认为今天\r\n\t * @property {Boolean} lunar 显示农历\r\n\t * @property {String} startDate 日期选择范围-开始日期\r\n\t * @property {String} endDate 日期选择范围-结束日期\r\n\t * @property {String} mode = [不传 | multiple | range ]  多个日期 | 选择日期范围 默认单日期\r\n\t * @property {Boolean} insert = [true|false] 插入模式,默认为false\r\n\t * \t@value true 弹窗模式\r\n\t * \t@value false 插入模式\r\n\t * @property {Boolean} clearDate = [true|false] 弹窗模式是否清空上次选择内容\r\n\t * @property {Array} selected 打点，期待格式[{date: '2019-06-27', info: '签到', data: { custom: '自定义信息', name: '自定义消息头',xxx:xxx... }}]\r\n\t * @property {String} cancelColor 取消按钮颜色\r\n\t * @property {String} confirmColor  确认按钮颜色，默认#3c9cff\r\n\t * @property {String} title 头部工具条中间的标题文字\r\n\t * @property {String} color 主题色，默认#3c9cff\r\n\t * @property {Number} round :insert=\"false\"时的圆角\r\n\t * @property {Boolean} closeOnClickOverlay 点击遮罩是否关闭\r\n\t * @property {String} startText range为true时，第一个日期底部的提示文字\r\n\t * @property {String} endText range为true时，最后一个日期底部的提示文字\r\n\t * \r\n\t * @event {Function} change 日期改变，`insert :ture` 时生效\r\n\t * @event {Function} confirm 确认选择`insert :false` 时生效\r\n\t * @event {Function} monthSwitch 切换月份时触发\r\n\t * \r\n\t * @example <uv-calendar :insert=\"true\":lunar=\"true\" :start-date=\"'2019-3-2'\":end-date=\"'2019-5-20'\"@change=\"change\" />\r\n\t */\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js';\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js';\r\n\timport Calendar from './util.js';\r\n\timport calendarBody from './calendar-body.vue';\r\n\timport { initVueI18n } from '@dcloudio/uni-i18n';\r\n\timport i18nMessages from './i18n/index.js';\r\n\tconst { t } = initVueI18n(i18nMessages);\r\n\texport default {\r\n\t\tcomponents: {\r\n\t\t\tcalendarBody\r\n\t\t},\r\n\t\tmixins: [mpMixin, mixin],\r\n\t\temits: ['close', 'confirm', 'change', 'monthSwitch'],\r\n\t\tprops: {\r\n\t\t\t// 取消按钮颜色\r\n\t\t\tcancelColor: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: ''\r\n\t\t\t},\r\n\t\t\t// 确认按钮颜色，range模式下未选全显示灰色\r\n\t\t\tconfirmColor: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: '#3c9cff'\r\n\t\t\t},\r\n\t\t\t// 标题\r\n\t\t\ttitle: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: ''\r\n\t\t\t},\r\n\t\t\t// 主题色\r\n\t\t\tcolor: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: '#3c9cff'\r\n\t\t\t},\r\n\t\t\t// 默认显示日期\r\n\t\t\tdate: {\r\n\t\t\t\ttype: [String,Array],\r\n\t\t\t\tdefault: ''\r\n\t\t\t},\r\n\t\t\t// 打点等设置\r\n\t\t\tselected: {\r\n\t\t\t\ttype: Array,\r\n\t\t\t\tdefault () {\r\n\t\t\t\t\treturn []\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t// 是否显示农历\r\n\t\t\tlunar: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: false\r\n\t\t\t},\r\n\t\t\t// 可选择的起始日期\r\n\t\t\tstartDate: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: ''\r\n\t\t\t},\r\n\t\t\t// 可选择的结束日期\r\n\t\t\tendDate: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: ''\r\n\t\t\t},\r\n\t\t\t// multiple - 选择多日期  range - 选择日期范围\r\n\t\t\tmode: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: ''\r\n\t\t\t},\r\n\t\t\t// 是否插入模式\r\n\t\t\tinsert: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: false\r\n\t\t\t},\r\n\t\t\t// 是否显示月份为背景\r\n\t\t\tshowMonth: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: true\r\n\t\t\t},\r\n\t\t\t// 弹窗模式是否清空上次选择内容\r\n\t\t\tclearDate: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: true\r\n\t\t\t},\r\n\t\t\t// 弹窗圆角\r\n\t\t\tround: {\r\n\t\t\t\ttype: [Number,String],\r\n\t\t\t\tdefault: 8\r\n\t\t\t},\r\n\t\t\t// 点击遮罩是否关闭弹窗\r\n\t\t\tcloseOnClickOverlay: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: true\r\n\t\t\t},\r\n\t\t\t// range为true时，第一个日期底部的提示文字\r\n\t\t\tstartText: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: '开始'\r\n\t\t\t},\r\n\t\t\t// range为true时，最后一个日期底部的提示文字\r\n\t\t\tendText: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: '结束'\r\n\t\t\t},\r\n\t\t\t// 是否允许日期范围的起止时间为同一天，mode = range时有效\r\n\t\t\tallowSameDay: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: false\r\n\t\t\t},\r\n\t\t\t...uni.$uv?.props?.calendars\r\n\t\t},\r\n\t\tdata(){\r\n\t\t\treturn {\r\n\t\t\t\tweeks: [],\r\n\t\t\t\tcalendar: {},\r\n\t\t\t\tnowDate: '',\r\n\t\t\t\tallowConfirm: false,\r\n\t\t\t\tmultiple: false,\r\n\t\t\t\trange: false\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed:{\r\n\t\t\t/**\r\n\t\t\t * for i18n\r\n\t\t\t */\r\n\t\t\tconfirmText() {\r\n\t\t\t\treturn t(\"uv-calender.ok\")\r\n\t\t\t},\r\n\t\t\tcancelText() {\r\n\t\t\t\treturn t(\"uv-calender.cancel\")\r\n\t\t\t},\r\n\t\t\tgetConfirmColor() {\r\n\t\t\t\tif(this.range || this.multiple) {\r\n\t\t\t\t\treturn this.allowConfirm? this.confirmColor: '#999'\r\n\t\t\t\t}else {\r\n\t\t\t\t\treturn this.confirmColor;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\tdate(newVal) {\r\n\t\t\t\tthis.init(newVal)\r\n\t\t\t},\r\n\t\t\tstartDate(val) {\r\n\t\t\t\tthis.cale.resetSatrtDate(val)\r\n\t\t\t\tthis.cale.setDate(this.nowDate.fullDate)\r\n\t\t\t\tthis.weeks = this.cale.weeks\r\n\t\t\t},\r\n\t\t\tendDate(val) {\r\n\t\t\t\tthis.cale.resetEndDate(val)\r\n\t\t\t\tthis.cale.setDate(this.nowDate.fullDate)\r\n\t\t\t\tthis.weeks = this.cale.weeks\r\n\t\t\t},\r\n\t\t\tselected(newVal) {\r\n\t\t\t\tthis.cale.setSelectInfo(this.nowDate.fullDate, newVal)\r\n\t\t\t\tthis.weeks = this.cale.weeks\r\n\t\t\t}\r\n\t\t},\r\n\t\tcreated() {\r\n\t\t\tthis.setMode();\r\n\t\t\tthis.cale = new Calendar({\r\n\t\t\t\tselected: this.selected,\r\n\t\t\t\tstartDate: this.startDate,\r\n\t\t\t\tendDate: this.endDate,\r\n\t\t\t\trange: this.range,\r\n\t\t\t\tmultiple: this.multiple,\r\n\t\t\t\tallowSameDay: this.allowSameDay\r\n\t\t\t})\r\n\t\t\tthis.init(this.date)\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tsetMode() {\r\n\t\t\t\tswitch (this.mode){\r\n\t\t\t\t\tcase 'range':\r\n\t\t\t\t\t\tthis.range = true;\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase 'multiple':\r\n\t\t\t\t\t\tthis.multiple = true;\r\n\t\t\t\t\tdefault:\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tasync open() {\r\n\t\t\t\tif (this.clearDate && !this.insert) {\r\n\t\t\t\t\tthis.cale.cleanRangeStatus()\r\n\t\t\t\t\tthis.init(this.date)\r\n\t\t\t\t}\r\n\t\t\t\tif(!this.insert){\r\n\t\t\t\t\tthis.$refs.popup.open();\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tclose() {\r\n\t\t\t\tthis.$refs.popup.close();\r\n\t\t\t\tthis.$emit('close');\r\n\t\t\t},\r\n\t\t\tconfirm() {\r\n\t\t\t\tif(this.range && !this.cale.rangeStatus.after) {\r\n\t\t\t\t\treturn;\r\n\t\t\t\t} else if(this.multiple && this.cale.multipleStatus.data.length == 0){\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\t\t\t\tthis.setEmit('confirm');\r\n\t\t\t\tthis.close()\r\n\t\t\t},\r\n\t\t\tmaskClick() {\r\n\t\t\t\tif(this.closeOnClickOverlay) {\r\n\t\t\t\t\tthis.$emit('close');\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tbindDateChange(e) {\r\n\t\t\t\tconst value = e.detail.value + '-1'\r\n\t\t\t\tthis.setDate(value)\r\n\t\t\r\n\t\t\t\tconst { year, month } = this.cale.getDate(value)\r\n\t\t\t\tthis.$emit('monthSwitch', {\r\n\t\t\t\t\tyear,\r\n\t\t\t\t\tmonth\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\t/**\r\n\t\t\t * 初始化日期显示\r\n\t\t\t * @param {Object} date\r\n\t\t\t */\r\n\t\t\tinit(date) {\r\n\t\t\t\tif(this.range) {\r\n\t\t\t\t\t// 重置范围选择状态\r\n\t\t\t\t\tthis.cale.cleanRangeStatus();\r\n\t\t\t\t}else if(this.multiple){\r\n\t\t\t\t\t// 重置多选状态\r\n\t\t\t\t\tthis.cale.cleanMultipleStatus();\r\n\t\t\t\t}\r\n\t\t\t\tthis.cale.setDate(date,'init')\r\n\t\t\t\tthis.weeks = this.cale.weeks\r\n\t\t\t\tthis.nowDate = this.calendar = this.cale.getInfo(date)\r\n\t\t\t\tthis.changeConfirmStatus();\r\n\t\t\t},\r\n\t\t\t/**\r\n\t\t\t * 变化触发\r\n\t\t\t */\r\n\t\t\tchange() {\r\n\t\t\t\tthis.changeConfirmStatus();\r\n\t\t\t\tif (!this.insert) return\r\n\t\t\t\tthis.setEmit('change')\r\n\t\t\t},\r\n\t\t\tchangeConfirmStatus() {\r\n\t\t\t\tif (this.range) {\r\n\t\t\t\t\tthis.allowConfirm = this.cale.rangeStatus.after ? true : false;\r\n\t\t\t\t} else if(this.multiple) {\r\n\t\t\t\t\tthis.allowConfirm = this.cale.multipleStatus.data.length > 0 ? true : false;\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t/**\r\n\t\t\t * 选择月份触发\r\n\t\t\t */\r\n\t\t\tmonthSwitch() {\r\n\t\t\t\tlet {\r\n\t\t\t\t\tyear,\r\n\t\t\t\t\tmonth\r\n\t\t\t\t} = this.nowDate\r\n\t\t\t\tthis.$emit('monthSwitch', {\r\n\t\t\t\t\tyear,\r\n\t\t\t\t\tmonth: Number(month)\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\t/**\r\n\t\t\t * 派发事件\r\n\t\t\t * @param {Object} name\r\n\t\t\t */\r\n\t\t\tsetEmit(name) {\r\n\t\t\t\tlet {\r\n\t\t\t\t\tyear,\r\n\t\t\t\t\tmonth,\r\n\t\t\t\t\tdate,\r\n\t\t\t\t\tfullDate,\r\n\t\t\t\t\tlunar,\r\n\t\t\t\t\textraInfo\r\n\t\t\t\t} = this.calendar\r\n\t\t\t\tthis.$emit(name, {\r\n\t\t\t\t\trange: this.cale.rangeStatus,\r\n\t\t\t\t\tmultiple: this.cale.multipleStatus,\r\n\t\t\t\t\tyear,\r\n\t\t\t\t\tmonth,\r\n\t\t\t\t\tdate,\r\n\t\t\t\t\tfulldate: fullDate,\r\n\t\t\t\t\tlunar,\r\n\t\t\t\t\textraInfo: extraInfo || {}\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\t/**\r\n\t\t\t * 选择天触发\r\n\t\t\t * @param {Object} weeks\r\n\t\t\t */\r\n\t\t\tchoiceDate(weeks) {\r\n\t\t\t\tif (weeks.disable) return\r\n\t\t\t\tthis.calendar = weeks\r\n\t\t\t\t// 设置范围选择\r\n\t\t\t\tthis.cale.setRange(this.calendar.fullDate)\r\n\t\t\t\t// 设置多选\r\n\t\t\t\tthis.cale.setMultiple(this.calendar.fullDate);\r\n\t\t\t\tthis.weeks = this.cale.weeks\r\n\t\t\t\tthis.change()\r\n\t\t\t},\r\n\t\t\t/**\r\n\t\t\t * 回到今天\r\n\t\t\t */\r\n\t\t\tbackToday() {\r\n\t\t\t\tconst nowYearMonth = `${this.nowDate.year}-${this.nowDate.month}`\r\n\t\t\t\tconst date = this.cale.getDate(new Date())\r\n\t\t\t\tconst todayYearMonth = `${date.year}-${date.month}`\r\n\t\t\t\tthis.init(date.fullDate)\r\n\t\t\t\tif (nowYearMonth !== todayYearMonth) {\r\n\t\t\t\t\tthis.monthSwitch()\r\n\t\t\t\t}\r\n\t\t\t\tthis.change()\r\n\t\t\t},\r\n\t\t\t/**\r\n\t\t\t * 上个月\r\n\t\t\t */\r\n\t\t\tpre() {\r\n\t\t\t\tconst preDate = this.cale.getDate(this.nowDate.fullDate, -1, 'month').fullDate\r\n\t\t\t\tthis.setDate(preDate)\r\n\t\t\t\tthis.monthSwitch()\r\n\t\t\t},\r\n\t\t\t/**\r\n\t\t\t * 下个月\r\n\t\t\t */\r\n\t\t\tnext() {\r\n\t\t\t\tconst nextDate = this.cale.getDate(this.nowDate.fullDate, +1, 'month').fullDate\r\n\t\t\t\tthis.setDate(nextDate)\r\n\t\t\t\tthis.monthSwitch()\r\n\t\t\t},\r\n\t\t\t/**\r\n\t\t\t * 设置日期\r\n\t\t\t * @param {Object} date\r\n\t\t\t */\r\n\t\t\tsetDate(date) {\r\n\t\t\t\tthis.cale.setDate(date)\r\n\t\t\t\tthis.weeks = this.cale.weeks\r\n\t\t\t\tthis.nowDate = this.cale.getInfo(date)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n<style scoped lang=\"scss\">\r\n\t$uv-border-color: #EDEDED !default;\r\n\t.uv-calendar__content {\r\n\t\tbackground-color: #fff;\r\n\t}\r\n\t.line {\r\n\t\twidth: 750rpx;\r\n\t\theight: 1px;\r\n\t\tborder-bottom-color: $uv-border-color;\r\n\t\tborder-bottom-style: solid;\r\n\t\tborder-bottom-width: 1px;\r\n\t}\r\n</style>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-calendars/package.json",
    "content": "{\r\n  \"id\": \"uv-calendars\",\r\n  \"displayName\": \"uv-calendars 最新日历 全面兼容vue3+2、app、h5、小程序等多端\",\r\n  \"version\": \"1.0.14\",\r\n  \"description\": \"新版本uv-calendars，不仅拥有老版本的所有功能，还增加了更加适用的插入页面等强大功能，且更加简洁。查看日期、选择单个或多个或任意范围日期，打点操作，自定义文案，自定义主题等强大功能。\",\r\n  \"keywords\": [\r\n    \"uv-ui\",\r\n    \"uvui\",\r\n    \"日历\",\r\n    \"打卡\",\r\n    \"日历选择\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\":{\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-popup\",\r\n\t\t\t\"uv-toolbar\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-calendars/readme.md",
    "content": "## Calendars 全新日历\r\n\r\n> **组件名：uv-calendars**\r\n\r\n为了解决老版本`uv-calendar`性能问题，特别是对日期选择范围有很大限制，体验不友好等缺点。于是有了新版日历组件。\n\n新版本`uv-calendars`，不仅拥有老版本的所有功能，还增加了更加适用的插入页面等强大功能，且更加简洁。查看日期、选择单个或多个或任意范围日期，打点操作，自定义文案，自定义主题等强大功能。\n\n常用场景：酒店日期预订、火车机票选择购买日期、上下班打卡等。\r\n\r\n# <a href=\"https://www.uvui.cn/components/calendars.html\" target=\"_blank\">查看文档</a>\r\n\r\n## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>（请不要 下载插件ZIP）</small>\r\n\r\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n<a href=\"https://ext.dcloud.net.cn/plugin?name=uv-ui\" target=\"_blank\">\r\n\r\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\r\n\r\n</a>\r\n\r\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-cell/changelog.md",
    "content": "## 1.0.4（2023-09-19）\n1. 增加cellStyle参数，方便自定义单元格的样式\n## 1.0.3（2023-07-03）\r\n去除插槽判断，避免某些平台不显示的BUG\r\n## 1.0.2（2023-06-21）\r\n1. 优化\r\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-cell 单元格\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-cell/components/uv-cell/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 标题\r\n\t\ttitle: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 标题下方的描述信息\r\n\t\tlabel: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 右侧的内容\r\n\t\tvalue: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 左侧图标名称，或者图片链接(本地文件建议使用绝对地址)\r\n\t\ticon: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 是否禁用cell\r\n\t\tdisabled: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否显示下边框\r\n\t\tborder: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 内容是否垂直居中(主要是针对右侧的value部分)\r\n\t\tcenter: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 点击后跳转的URL地址\r\n\t\turl: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 链接跳转的方式，内部使用的是uvui封装的route方法，可能会进行拦截操作\r\n\t\tlinkType: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'navigateTo'\r\n\t\t},\r\n\t\t// 是否开启点击反馈(表现为点击时加上灰色背景)\r\n\t\tclickable: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否展示右侧箭头并开启点击反馈\r\n\t\tisLink: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否显示表单状态下的必填星号(此组件可能会内嵌入input组件)\r\n\t\trequired: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 右侧的图标箭头\r\n\t\trightIcon: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'arrow-right'\r\n\t\t},\r\n\t\t// 右侧箭头的方向，可选值为：left，up，down\r\n\t\tarrowDirection: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 左侧图标样式\r\n\t\ticonStyle: {\r\n\t\t\ttype: [Object, String],\r\n\t\t\tdefault: () => {\r\n\t\t\t\treturn {}\r\n\t\t\t}\r\n\t\t},\r\n\t\t// 右侧箭头图标的样式\r\n\t\trightIconStyle: {\r\n\t\t\ttype: [Object, String],\r\n\t\t\tdefault: () => {\r\n\t\t\t\treturn {}\r\n\t\t\t}\r\n\t\t},\r\n\t\t// 标题的样式\r\n\t\ttitleStyle: {\r\n\t\t\ttype: [Object, String],\r\n\t\t\tdefault: () => {\r\n\t\t\t\treturn {}\r\n\t\t\t}\r\n\t\t},\r\n\t\t// 单位元的大小，可选值为large\r\n\t\tsize: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 点击cell是否阻止事件传播\r\n\t\tstop: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 标识符，cell被点击时返回\r\n\t\tname: {\r\n\t\t\ttype: [Number, String],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 单元格自定义样式\r\n\t\tcellStyle: {\r\n\t\t\ttype: [Object, String],\r\n\t\t\tdefault: () => {}\r\n\t\t},\r\n\t\t...uni.$uv?.props?.cell\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-cell/components/uv-cell/uv-cell.vue",
    "content": "<template>\r\n\t<view class=\"uv-cell\" :class=\"[customClass]\" :style=\"[$uv.addStyle(customStyle)]\"\r\n\t\t:hover-class=\"(!disabled && (clickable || isLink)) ? 'uv-cell--clickable' : ''\" :hover-stay-time=\"250\"\r\n\t\t@click=\"clickHandler\">\r\n\t\t<view class=\"uv-cell__body\" \r\n\t\t\t:class=\"[ center && 'uv-cell--center', size === 'large' && 'uv-cell__body--large']\"\r\n\t\t\t:style=\"[cellStyle]\"\r\n\t\t>\r\n\t\t\t<view class=\"uv-cell__body__content\">\r\n\t\t\t\t<view class=\"uv-cell__left-icon-wrap\">\r\n\t\t\t\t\t<slot name=\"icon\">\r\n\t\t\t\t\t\t<uv-icon v-if=\"icon\" :name=\"icon\" :custom-style=\"iconStyle\" :size=\"size === 'large' ? 22 : 18\"></uv-icon>\r\n\t\t\t\t\t</slot>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view class=\"uv-cell__title\">\r\n\t\t\t\t\t<slot name=\"title\">\r\n\t\t\t\t\t\t<text v-if=\"title\" class=\"uv-cell__title-text\" :style=\"[titleTextStyle]\"\r\n\t\t\t\t\t\t\t:class=\"[disabled && 'uv-cell--disabled', size === 'large' && 'uv-cell__title-text--large']\">{{ title }}</text>\r\n\t\t\t\t\t</slot>\r\n\t\t\t\t\t<slot name=\"label\">\r\n\t\t\t\t\t\t<text class=\"uv-cell__label\" v-if=\"label\"\r\n\t\t\t\t\t\t\t:class=\"[disabled && 'uv-cell--disabled', size === 'large' && 'uv-cell__label--large']\">{{ label }}</text>\r\n\t\t\t\t\t</slot>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t\t<slot name=\"value\">\r\n\t\t\t\t<text class=\"uv-cell__value\"\r\n\t\t\t\t\t:class=\"[disabled && 'uv-cell--disabled', size === 'large' && 'uv-cell__value--large']\"\r\n\t\t\t\t\tv-if=\"!$uv.test.empty(value)\">{{ value }}</text>\r\n\t\t\t</slot>\r\n\t\t\t<view class=\"uv-cell__right-icon-wrap\" v-if=\"$slots['right-icon'] || isLink\"\r\n\t\t\t\t:class=\"[`uv-cell__right-icon-wrap--${arrowDirection}`]\">\r\n\t\t\t\t<slot name=\"right-icon\" v-if=\"$slots['right-icon']\">\r\n\t\t\t\t</slot>\r\n\t\t\t\t<uv-icon v-else :name=\"rightIcon\" :custom-style=\"rightIconStyle\" :color=\"disabled ? '#c8c9cc' : 'info'\"\r\n\t\t\t\t\t:size=\"size === 'large' ? 18 : 16\"></uv-icon>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t\t<uv-line v-if=\"border\"></uv-line>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * cell  单元格\r\n\t * @description cell单元格一般用于一组列表的情况，比如个人中心页，设置页等。\r\n\t * @tutorial https://www.uvui.cn/components/cell.html\r\n\t * @property {String | Number}\ttitle\t\t\t标题\r\n\t * @property {String | Number}\tlabel\t\t\t标题下方的描述信息\r\n\t * @property {String | Number}\tvalue\t\t\t右侧的内容\r\n\t * @property {String}\t\t\ticon\t\t\t左侧图标名称，或者图片链接(本地文件建议使用绝对地址)\r\n\t * @property {Boolean}\t\t\tdisabled\t\t是否禁用cell\t\r\n\t * @property {Boolean}\t\t\tborder\t\t\t是否显示下边框 (默认 true )\r\n\t * @property {Boolean}\t\t\tcenter\t\t\t内容是否垂直居中(主要是针对右侧的value部分) (默认 false )\r\n\t * @property {String}\t\t\turl\t\t\t\t点击后跳转的URL地址\r\n\t * @property {String}\t\t\tlinkType\t\t链接跳转的方式，内部使用的是uvui封装的route方法，可能会进行拦截操作 (默认 'navigateTo' )\r\n\t * @property {Boolean}\t\t\tclickable\t\t是否开启点击反馈(表现为点击时加上灰色背景) （默认 false ） \r\n\t * @property {Boolean}\t\t\tisLink\t\t\t是否展示右侧箭头并开启点击反馈 （默认 false ）\r\n\t * @property {Boolean}\t\t\trequired\t\t是否显示表单状态下的必填星号(此组件可能会内嵌入input组件) （默认 false ）\r\n\t * @property {String}\t\t\trightIcon\t\t右侧的图标箭头 （默认 'arrow-right'）\r\n\t * @property {String}\t\t\tarrowDirection\t右侧箭头的方向，可选值为：left，up，down\r\n\t * @property {Object | String}\t\t\trightIconStyle\t右侧箭头图标的样式\r\n\t * @property {Object | String}\t\t\ttitleStyle\t\t标题的样式\r\n\t * @property {Object | String}\t\t\ticonStyle\t\t左侧图标样式\r\n\t * @property {String}\t\t\tsize\t\t\t单位元的大小，可选值为 large，normal，mini \r\n\t * @property {Boolean}\t\t\tstop\t\t\t点击cell是否阻止事件传播 (默认 true )\r\n\t * @property {Object | String}  cellStyle 单元格自定义样式\r\n\t * @property {Object}\t\t\tcustomStyle\t\t定义需要用到的外部样式\r\n\t * \r\n\t * @event {Function}\t\t\tclick\t\t\t点击cell列表时触发\r\n\t * @example 该组件需要搭配cell-group组件使用，见官方文档示例\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-cell',\r\n\t\temits: ['click'],\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tcomputed: {\r\n\t\t\ttitleTextStyle() {\r\n\t\t\t\treturn this.$uv.addStyle(this.titleStyle)\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\t// 点击cell\r\n\t\t\tclickHandler(e) {\r\n\t\t\t\tif (this.disabled) return\r\n\t\t\t\tthis.$emit('click', {\r\n\t\t\t\t\tname: this.name\r\n\t\t\t\t})\r\n\t\t\t\t// 如果配置了url(此props参数通过mixin引入)参数，跳转页面\r\n\t\t\t\tthis.openPage()\r\n\t\t\t\t// 是否阻止事件传播\r\n\t\t\t\tthis.stop && this.preventEvent(e)\r\n\t\t\t},\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t$uv-cell-padding: 10px 15px !default;\r\n\t$uv-cell-font-size: 15px !default;\r\n\t$uv-cell-line-height: 24px !default;\r\n\t$uv-cell-color: $uv-main-color !default;\r\n\t$uv-cell-icon-size: 16px !default;\r\n\t$uv-cell-title-font-size: 15px !default;\r\n\t$uv-cell-title-line-height: 22px !default;\r\n\t$uv-cell-title-color: $uv-main-color !default;\r\n\t$uv-cell-label-font-size: 12px !default;\r\n\t$uv-cell-label-color: $uv-tips-color !default;\r\n\t$uv-cell-label-line-height: 18px !default;\r\n\t$uv-cell-value-font-size: 14px !default;\r\n\t$uv-cell-value-color: $uv-content-color !default;\r\n\t$uv-cell-clickable-color: $uv-bg-color !default;\r\n\t$uv-cell-disabled-color: #c8c9cc !default;\r\n\t$uv-cell-padding-top-large: 13px !default;\r\n\t$uv-cell-padding-bottom-large: 13px !default;\r\n\t$uv-cell-value-font-size-large: 15px !default;\r\n\t$uv-cell-label-font-size-large: 14px !default;\r\n\t$uv-cell-title-font-size-large: 16px !default;\r\n\t$uv-cell-left-icon-wrap-margin-right: 4px !default;\r\n\t$uv-cell-right-icon-wrap-margin-left: 4px !default;\r\n\t$uv-cell-title-flex: 1 !default;\r\n\t$uv-cell-label-margin-top: 5px !default;\r\n\t.uv-cell {\r\n\t\t&__body {\r\n\t\t\t@include flex();\r\n\t\t\t/* #ifndef APP-NVUE */\r\n\t\t\tbox-sizing: border-box;\r\n\t\t\t/* #endif */\r\n\t\t\tpadding: $uv-cell-padding;\r\n\t\t\tfont-size: $uv-cell-font-size;\r\n\t\t\tcolor: $uv-cell-color;\r\n\t\t\t&__content {\r\n\t\t\t\t@include flex(row);\r\n\t\t\t\talign-items: center;\r\n\t\t\t\tflex: 1;\r\n\t\t\t}\r\n\t\t\t&--large {\r\n\t\t\t\tpadding-top: $uv-cell-padding-top-large;\r\n\t\t\t\tpadding-bottom: $uv-cell-padding-bottom-large;\r\n\t\t\t}\r\n\t\t}\r\n\t\t&__left-icon-wrap,\r\n\t\t&__right-icon-wrap {\r\n\t\t\t@include flex();\r\n\t\t\talign-items: center;\r\n\t\t\t// height: $uv-cell-line-height;\r\n\t\t\tfont-size: $uv-cell-icon-size;\r\n\t\t}\r\n\t\t&__left-icon-wrap {\r\n\t\t\tmargin-right: $uv-cell-left-icon-wrap-margin-right;\r\n\t\t}\r\n\t\t&__right-icon-wrap {\r\n\t\t\tmargin-left: $uv-cell-right-icon-wrap-margin-left;\r\n\t\t\ttransition: transform 0.3s;\r\n\t\t\t&--up {\r\n\t\t\t\ttransform: rotate(-90deg);\r\n\t\t\t}\r\n\t\t\t&--down {\r\n\t\t\t\ttransform: rotate(90deg);\r\n\t\t\t}\r\n\t\t}\r\n\t\t&__title {\r\n\t\t\tflex: $uv-cell-title-flex;\r\n\t\t\t&-text {\r\n\t\t\t\tfont-size: $uv-cell-title-font-size;\r\n\t\t\t\tline-height: $uv-cell-title-line-height;\r\n\t\t\t\tcolor: $uv-cell-title-color;\r\n\t\t\t\t&--large {\r\n\t\t\t\t\tfont-size: $uv-cell-title-font-size-large;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t&__label {\r\n\t\t\tmargin-top: $uv-cell-label-margin-top;\r\n\t\t\tfont-size: $uv-cell-label-font-size;\r\n\t\t\tcolor: $uv-cell-label-color;\r\n\t\t\tline-height: $uv-cell-label-line-height;\r\n\t\t\t&--large {\r\n\t\t\t\tfont-size: $uv-cell-label-font-size-large;\r\n\t\t\t}\r\n\t\t}\r\n\t\t&__value {\r\n\t\t\ttext-align: right;\r\n\t\t\tfont-size: $uv-cell-value-font-size;\r\n\t\t\tline-height: $uv-cell-line-height;\r\n\t\t\tcolor: $uv-cell-value-color;\r\n\t\t\t&--large {\r\n\t\t\t\tfont-size: $uv-cell-value-font-size-large;\r\n\t\t\t}\r\n\t\t}\r\n\t\t&--clickable {\r\n\t\t\tbackground-color: $uv-cell-clickable-color;\r\n\t\t}\r\n\t\t&--disabled {\r\n\t\t\tcolor: $uv-cell-disabled-color;\r\n\t\t\t/* #ifndef APP-NVUE */\r\n\t\t\tcursor: not-allowed;\r\n\t\t\t/* #endif */\r\n\t\t}\r\n\t\t&--center {\r\n\t\t\talign-items: center;\r\n\t\t}\r\n\t}\r\n</style>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-cell/components/uv-cell-group/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 分组标题\r\n\t\ttitle: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 是否显示外边框\r\n\t\tborder: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t...uni.$uv?.props?.cellGroup\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-cell/components/uv-cell-group/uv-cell-group.vue",
    "content": "<template>\r\n    <view :style=\"[$uv.addStyle(customStyle)]\" :class=\"[customClass]\" class=\"uv-cell-group\">\r\n        <view v-if=\"title\" class=\"uv-cell-group__title\">\r\n            <slot name=\"title\">\r\n\t\t\t\t<text class=\"uv-cell-group__title__text\">{{ title }}</text>\r\n\t\t\t</slot>\r\n        </view>\r\n        <view class=\"uv-cell-group__wrapper\">\r\n\t\t\t<uv-line v-if=\"border\"></uv-line>\r\n            <slot />\r\n        </view>\r\n    </view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * cellGroup  单元格\r\n\t * @description cell单元格一般用于一组列表的情况，比如个人中心页，设置页等。\r\n\t * @tutorial https://www.uvui.cn/components/cell.html\r\n\t * \r\n\t * @property {String}\ttitle\t\t分组标题\r\n\t * @property {Boolean}\tborder\t\t是否显示外边框 (默认 true )\r\n\t * @property {Object}\tcustomStyle\t定义需要用到的外部样式\r\n\t * \r\n\t * @event {Function} click \t点击cell列表时触发\r\n\t * @example <uv-cell-group title=\"设置喜好\">\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-cell-group',\r\n\t\tmixins: [mpMixin, mixin, props]\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t$uv-cell-group-title-padding: 16px 16px 8px !default;\r\n\t$uv-cell-group-title-font-size: 15px !default;\r\n\t$uv-cell-group-title-line-height: 16px !default;\r\n\t$uv-cell-group-title-color: $uv-main-color !default;\r\n\r\n    .uv-cell-group {\r\n\t\tflex: 1;\r\n\t\t\r\n        &__title {\r\n            padding: $uv-cell-group-title-padding;\r\n\r\n            &__text {\r\n                font-size: $uv-cell-group-title-font-size;\r\n                line-height: $uv-cell-group-title-line-height;\r\n                color: $uv-cell-group-title-color;\r\n            }\r\n        }\r\n\t\t\r\n\t\t&__wrapper {\r\n\t\t\tposition: relative;\r\n\t\t}\r\n    }\r\n</style>\r\n\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-cell/package.json",
    "content": "{\r\n  \"id\": \"uv-cell\",\r\n  \"displayName\": \"uv-cell 单元格 全面兼容小程序、nvue、vue2、vue3等多端\",\r\n  \"version\": \"1.0.4\",\r\n  \"description\": \"cell单元格一般用于一组列表的情况，比如个人中心页，设置页等。\",\r\n  \"keywords\": [\r\n    \"uv-cell\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"单元格\",\r\n    \"设置页\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-icon\",\r\n\t\t\t\"uv-line\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-cell/readme.md",
    "content": "## Cell 单元格\r\n\r\n> **组件名：uv-cell**\r\n\r\ncell单元格一般用于一组列表的情况，比如个人中心页，设置页等。\r\n\r\n### <a href=\"https://www.uvui.cn/components/cell.html\" target=\"_blank\">查看文档</a>\r\n\r\n### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n#### 如使用过程中有任何问题，或者您对uv-ui有一些好的建议，欢迎加入 uv-ui 交流群：<a href=\"https://ext.dcloud.net.cn/plugin?id=12287\" target=\"_blank\">uv-ui</a>、<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-checkbox/changelog.md",
    "content": "## 1.0.13（2023-10-11）\n1. 优化同类问题：https://gitee.com/climblee/uv-ui/issues/I872VD\n## 1.0.12（2023-09-22）\n1. 修复change回调中v-model值不更新的BUG\n## 1.0.11（2023-09-01）\n1. 修复点击空隙处无效的问题\n2. label支持插槽下可点击\n## 1.0.10（2023-08-27）\r\n1. 修复label设置布尔值不生效的BUG\r\n## 1.0.9（2023-08-16）\r\n1. 解决数据多不换行的BUG\r\n## 1.0.8（2023-07-13）\r\n1. 修复  uv-checkbox设置value属性不生效的BUG\r\n## 1.0.7（2023-07-05）\r\n修复vue3模式下，动态修改v-model绑定的值无效的BUG\r\n## 1.0.6（2023-06-29）\r\n1. 增加label插槽，与radio保持一致\r\n2. 优化文档\r\n## 1.0.5（2023-06-12）\r\n1. 修复1.0.4改出的问题\r\n## 1.0.4（2023-06-08）\r\n1. 复选框修复全局设置不生效的BUG\r\n## 1.0.3（2023-06-06）\r\n1. uv-checkbox-group 兼容自定义样式customStyle，方便通过样式调整整体位置等；\r\n2. .uv-checkbox-group--row增加flex-wrap: wrap;允许换行\r\n## 1.0.2（2023-05-30）\r\n1. 修复error报错的BUG\r\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-checkbox 复选框\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-checkbox/components/uv-checkbox/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// checkbox的名称\r\n\t\tname: {\r\n\t\t\ttype: [String, Number, Boolean],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 形状，square为方形，circle为圆型\r\n\t\tshape: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 整体的大小\r\n\t\tsize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 是否默认选中\r\n\t\tchecked: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否禁用\r\n\t\tdisabled: {\r\n\t\t\ttype: [String, Boolean],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 选中状态下的颜色，如设置此值，将会覆盖parent的activeColor值\r\n\t\tactiveColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 未选中的颜色\r\n\t\tinactiveColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 图标的大小，单位px\r\n\t\ticonSize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 图标颜色\r\n\t\ticonColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// label提示文字，因为nvue下，直接slot进来的文字，由于特殊的结构，无法修改样式\r\n\t\tlabel: {\r\n\t\t\ttype: [String, Number, Boolean],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// label的字体大小，px单位\r\n\t\tlabelSize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// label的颜色\r\n\t\tlabelColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 是否禁止点击提示语选中复选框\r\n\t\tlabelDisabled: {\r\n\t\t\ttype: [String, Boolean],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t...uni.$uv?.props?.checkbox\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-checkbox/components/uv-checkbox/uv-checkbox.vue",
    "content": "<template>\r\n\t<view\r\n\t  class=\"uv-checkbox\"\r\n\t  :style=\"[checkboxStyle]\"\r\n\t  @tap.stop=\"wrapperClickHandler\"\r\n\t  :class=\"[`uv-checkbox-label--${parentData.iconPlacement}`, parentData.borderBottom && parentData.placement === 'column' && 'uv-border-bottom']\"\r\n\t>\r\n\t\t<view\r\n\t\t  class=\"uv-checkbox__icon-wrap\"\r\n\t\t  @tap.stop=\"iconClickHandler\"\r\n\t\t  :class=\"iconClasses\"\r\n\t\t  :style=\"[iconWrapStyle]\"\r\n\t\t>\r\n\t\t\t<slot name=\"icon\">\r\n\t\t\t\t<uv-icon\r\n\t\t\t\t  class=\"uv-checkbox__icon-wrap__icon\"\r\n\t\t\t\t  name=\"checkbox-mark\"\r\n\t\t\t\t  :size=\"elIconSize\"\r\n\t\t\t\t  :color=\"elIconColor\"\r\n\t\t\t\t/>\r\n\t\t\t</slot>\r\n\t\t</view>\r\n\t\t<view \r\n\t\t\tclass=\"uv-checkbox__label-wrap\" \r\n\t\t\t@tap.stop=\"labelClickHandler\">\r\n\t\t\t<slot>\r\n\t\t\t\t<text\r\n\t\t\t\t  :style=\"{\r\n\t\t\t\t\t\tcolor: elDisabled ? elInactiveColor : elLabelColor,\r\n\t\t\t\t\t\tfontSize: elLabelSize,\r\n\t\t\t\t\t\tlineHeight: elLabelSize\r\n\t\t\t\t\t}\"\r\n\t\t\t\t>{{label}}</text>\r\n\t\t\t</slot>\r\n\t\t</view>\r\n\t\t\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * checkbox  复选框\r\n\t * @description 复选框组件一般用于需要多个选择的场景，该组件功能完整，使用方便\r\n\t * @tutorial https://www.uvui.cn/components/checkbox.html\r\n\t * @property {String | Number | Boolean}\tname\t\t\tcheckbox组件的标示符\r\n\t * @property {String}\t\t\t\t\t\tshape\t\t\t形状，square为方形，circle为圆型\r\n\t * @property {String | Number}\t\t\t\tsize\t\t\t整体的大小\r\n\t * @property {Boolean}\t\t\t\t\t\tchecked\t\t\t是否默认选中\r\n\t * @property {String | Boolean}\t\t\t\tdisabled\t\t是否禁用\r\n\t * @property {String}\t\t\t\t\t\tactiveColor\t\t选中状态下的颜色，如设置此值，将会覆盖parent的activeColor值\r\n\t * @property {String}\t\t\t\t\t\tinactiveColor\t未选中的颜色\r\n\t * @property {String | Number}\t\t\t\ticonSize\t\t图标的大小，单位px\r\n\t * @property {String}\t\t\t\t\t\ticonColor\t\t图标颜色\r\n\t * @property {String | Number}\t\t\t\tlabel\t\t\tlabel提示文字，因为nvue下，直接slot进来的文字，由于特殊的结构，无法修改样式\r\n\t * @property {String}\t\t\t\t\t\tlabelColor \t\tlabel的颜色\r\n\t * @property {String | Number}\t\t\t\tlabelSize\t\tlabel的字体大小，px单位\r\n\t * @property {String | Boolean}\t\t\t\tlabelDisabled\t是否禁止点击提示语选中复选框\r\n\t * @property {Object}\t\t\t\t\t\tcustomStyle\t\t定义需要用到的外部样式\r\n\t * \r\n\t * @event {Function}\tchange\t任一个checkbox状态发生变化时触发，回调为一个对象\r\n\t * @example <uv-checkbox v-model=\"checked\" :disabled=\"false\">天涯</uv-checkbox>\r\n\t */\r\n\texport default {\r\n\t\tname: \"uv-checkbox\",\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tisChecked: false,\r\n\t\t\t\t// 父组件的默认值，因为头条小程序不支持在computed中使用this.parent.shape的形式\r\n\t\t\t\t// 故只能使用如此方法\r\n\t\t\t\tparentData: {\r\n\t\t\t\t\ticonSize: 12,\r\n\t\t\t\t\tlabelDisabled: null,\r\n\t\t\t\t\tdisabled: null,\r\n\t\t\t\t\tshape: 'square',\r\n\t\t\t\t\tactiveColor: null,\r\n\t\t\t\t\tinactiveColor: null,\r\n\t\t\t\t\tsize: 18,\r\n\t\t\t\t\tvalue: null,\r\n\t\t\t\t\tmodelValue: null,\r\n\t\t\t\t\ticonColor: null,\r\n\t\t\t\t\tplacement: 'row',\r\n\t\t\t\t\tborderBottom: false,\r\n\t\t\t\t\ticonPlacement: 'left',\r\n\t\t\t\t\tlabelSize: 14,\r\n\t\t\t\t\tlabelColor: '#303133'\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\t// 是否禁用，如果父组件uv-raios-group禁用的话，将会忽略子组件的配置\r\n\t\t\telDisabled() {\r\n\t\t\t\treturn this.disabled !== '' ? this.disabled : this.parentData.disabled !== null ? this.parentData.disabled : false;\r\n\t\t\t},\r\n\t\t\t// 是否禁用label点击\r\n\t\t\telLabelDisabled() {\r\n\t\t\t\treturn this.labelDisabled !== '' ? this.labelDisabled : this.parentData.labelDisabled !== null ? this.parentData.labelDisabled :\r\n\t\t\t\t\tfalse;\r\n\t\t\t},\r\n\t\t\t// 组件尺寸，对应size的值，默认值为21px\r\n\t\t\telSize() {\r\n\t\t\t\treturn this.size ? this.size : (this.parentData.size ? this.parentData.size : 21);\r\n\t\t\t},\r\n\t\t\t// 组件的勾选图标的尺寸，默认12px\r\n\t\t\telIconSize() {\r\n\t\t\t\treturn this.iconSize ? this.iconSize : (this.parentData.iconSize ? this.parentData.iconSize : 12);\r\n\t\t\t},\r\n\t\t\t// 组件选中激活时的颜色\r\n\t\t\telActiveColor() {\r\n\t\t\t\treturn this.activeColor ? this.activeColor : (this.parentData.activeColor ? this.parentData.activeColor : '#2979ff');\r\n\t\t\t},\r\n\t\t\t// 组件选未中激活时的颜色\r\n\t\t\telInactiveColor() {\r\n\t\t\t\treturn this.inactiveColor ? this.inactiveColor : (this.parentData.inactiveColor ? this.parentData.inactiveColor :\r\n\t\t\t\t\t'#c8c9cc');\r\n\t\t\t},\r\n\t\t\t// label的颜色\r\n\t\t\telLabelColor() {\r\n\t\t\t\treturn this.labelColor ? this.labelColor : (this.parentData.labelColor ? this.parentData.labelColor : '#606266')\r\n\t\t\t},\r\n\t\t\t// 组件的形状\r\n\t\t\telShape() {\r\n\t\t\t\treturn this.shape ? this.shape : (this.parentData.shape ? this.parentData.shape : 'circle');\r\n\t\t\t},\r\n\t\t\t// label大小\r\n\t\t\telLabelSize() {\r\n\t\t\t\treturn this.$uv.addUnit(this.labelSize ? this.labelSize : (this.parentData.labelSize ? this.parentData.labelSize :\r\n\t\t\t\t\t'15'))\r\n\t\t\t},\r\n\t\t\telIconColor() {\r\n\t\t\t\tconst iconColor = this.iconColor ? this.iconColor : (this.parentData.iconColor ? this.parentData.iconColor :\r\n\t\t\t\t\t'#ffffff');\r\n\t\t\t\t// 图标的颜色\r\n\t\t\t\tif (this.elDisabled) {\r\n\t\t\t\t\t// disabled状态下，已勾选的checkbox图标改为elInactiveColor\r\n\t\t\t\t\treturn this.isChecked ? this.elInactiveColor : 'transparent'\r\n\t\t\t\t} else {\r\n\t\t\t\t\treturn this.isChecked ? iconColor : 'transparent'\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\ticonClasses() {\r\n\t\t\t\tlet classes = []\r\n\t\t\t\t// 组件的形状\r\n\t\t\t\tclasses.push('uv-checkbox__icon-wrap--' + this.elShape)\r\n\t\t\t\tif (this.elDisabled) {\r\n\t\t\t\t\tclasses.push('uv-checkbox__icon-wrap--disabled')\r\n\t\t\t\t}\r\n\t\t\t\tif (this.isChecked && this.elDisabled) {\r\n\t\t\t\t\tclasses.push('uv-checkbox__icon-wrap--disabled--checked')\r\n\t\t\t\t}\r\n\t\t\t\t// 支付宝，头条小程序无法动态绑定一个数组类名，否则解析出来的结果会带有\",\"，而导致失效\r\n\t\t\t\t// #ifdef MP-ALIPAY || MP-TOUTIAO\r\n\t\t\t\tclasses = classes.join(' ')\r\n\t\t\t\t// #endif\r\n\t\t\t\treturn classes\r\n\t\t\t},\r\n\t\t\ticonWrapStyle() {\r\n\t\t\t\t// checkbox的整体样式\r\n\t\t\t\tconst style = {}\r\n\t\t\t\tstyle.backgroundColor = this.isChecked && !this.elDisabled ? this.elActiveColor : '#ffffff'\r\n\t\t\t\tstyle.borderColor = this.isChecked && !this.elDisabled ? this.elActiveColor : this.elInactiveColor\r\n\t\t\t\tstyle.width = this.$uv.addUnit(this.elSize)\r\n\t\t\t\tstyle.height = this.$uv.addUnit(this.elSize)\r\n\t\t\t\t// 如果是图标在右边的话，移除它的右边距\r\n\t\t\t\tif (this.parentData.iconPlacement === 'right') {\r\n\t\t\t\t\tstyle.marginRight = 0\r\n\t\t\t\t}\r\n\t\t\t\treturn style\r\n\t\t\t},\r\n\t\t\tcheckboxStyle() {\r\n\t\t\t\tconst style = {}\r\n\t\t\t\tif (this.parentData.borderBottom && this.parentData.placement === 'row') {\r\n\t\t\t\t\tthis.$uv.error('检测到您将borderBottom设置为true，需要同时将uv-checkbox-group的placement设置为column才有效')\r\n\t\t\t\t}\r\n\t\t\t\t// 当父组件设置了显示下边框并且排列形式为纵向时，给内容和边框之间加上一定间隔\r\n\t\t\t\tif (this.parentData.borderBottom && this.parentData.placement === 'column') {\r\n\t\t\t\t\tstyle.paddingBottom = '8px'\r\n\t\t\t\t}\r\n\t\t\t\treturn this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))\r\n\t\t\t}\r\n\t\t},\r\n\t\tcreated() {\r\n\t\t\tthis.init()\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tinit() {\r\n\t\t\t\t// 支付宝小程序不支持provide/inject，所以使用这个方法获取整个父组件，在created定义，避免循环引用\r\n\t\t\t\tthis.updateParentData()\r\n\t\t\t\tif (!this.parent) {\r\n\t\t\t\t\tthis.$uv.error('uv-checkbox必须搭配uv-checkbox-group组件使用')\r\n\t\t\t\t}\r\n\t\t\t\tthis.$nextTick(()=>{\r\n\t\t\t\t\tlet parentValue = [];\r\n\t\t\t\t\tif(this.parentData.value.length) {\r\n\t\t\t\t\t\tparentValue = this.parentData.value;\r\n\t\t\t\t\t} else if (this.parentData.modelValue.length){\r\n\t\t\t\t\t\tparentValue = this.parentData.modelValue;\r\n\t\t\t\t\t}\r\n\t\t\t\t\t// 设置初始化时，是否默认选中的状态，父组件uv-checkbox-group的value可能是array，所以额外判断\r\n\t\t\t\t\tif (this.checked) {\r\n\t\t\t\t\t\tthis.isChecked = true\r\n\t\t\t\t\t} else if (this.$uv.test.array(parentValue)) {\r\n\t\t\t\t\t\t// 查找数组是是否存在this.name元素值\r\n\t\t\t\t\t\tthis.isChecked = parentValue.some(item => {\r\n\t\t\t\t\t\t\treturn item === this.name\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t}\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\tupdateParentData() {\r\n\t\t\t\tthis.getParentData('uv-checkbox-group')\r\n\t\t\t},\r\n\t\t\t// 横向两端排列时，点击组件即可触发选中事件\r\n\t\t\twrapperClickHandler(e) {\r\n\t\t\t\tthis.parentData.iconPlacement === 'right' && this.iconClickHandler(e)\r\n\t\t\t},\r\n\t\t\t// 点击图标\r\n\t\t\ticonClickHandler(e) {\r\n\t\t\t\tthis.preventEvent(e)\r\n\t\t\t\t// 如果整体被禁用，不允许被点击\r\n\t\t\t\tif (!this.elDisabled) {\r\n\t\t\t\t\tthis.setRadioCheckedStatus()\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t// 点击label\r\n\t\t\tlabelClickHandler(e) {\r\n\t\t\t\tthis.preventEvent(e)\r\n\t\t\t\t// 如果按钮整体被禁用或者label被禁用，则不允许点击文字修改状态\r\n\t\t\t\tif (!this.elLabelDisabled && !this.elDisabled) {\r\n\t\t\t\t\tthis.setRadioCheckedStatus()\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\temitEvent() {\r\n\t\t\t\tthis.$emit('change', this.isChecked)\r\n\t\t\t\t// 尝试调用uv-form的验证方法，进行一定延迟，否则微信小程序更新可能会不及时\r\n\t\t\t\tthis.$nextTick(() => {\r\n\t\t\t\t\tthis.$uv.formValidate(this, 'change')\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\t// 改变组件选中状态\r\n\t\t\t// 这里的改变的依据是，更改本组件的checked值为true，同时通过父组件遍历所有uv-checkbox实例\r\n\t\t\t// 将本组件外的其他uv-checkbox的checked都设置为false(都被取消选中状态)，因而只剩下一个为选中状态\r\n\t\t\tsetRadioCheckedStatus() {\r\n\t\t\t\t// 将本组件标记为与原来相反的状态\r\n\t\t\t\tthis.isChecked = !this.isChecked\r\n\t\t\t\tthis.emitEvent()\r\n\t\t\t\ttypeof this.parent.unCheckedOther === 'function' && this.parent.unCheckedOther(this)\r\n\t\t\t}\r\n\t\t},\r\n\t\twatch:{\r\n\t\t\tchecked(){\r\n\t\t\t\tthis.isChecked = this.checked\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t$show-border: 1;\r\n\t$show-border-bottom: 1;\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t$uv-checkbox-label-wrap-padding-right:6px !default;\r\n\t$uv-checkbox-icon-wrap-font-size:6px !default;\r\n\t$uv-checkbox-icon-wrap-border-width:1px !default;\r\n\t$uv-checkbox-icon-wrap-border-color:#c8c9cc !default;\r\n\t$uv-checkbox-icon-wrap-icon-line-height:0 !default;\r\n\t$uv-checkbox-icon-wrap-circle-border-radius:100% !default;\r\n\t$uv-checkbox-icon-wrap-square-border-radius:3px !default;\r\n\t$uv-checkbox-icon-wrap-checked-color:#fff !default;\r\n\t$uv-checkbox-icon-wrap-checked-background-color:red !default;\r\n\t$uv-checkbox-icon-wrap-checked-border-color:#2979ff !default;\r\n\t$uv-checkbox-icon-wrap-disabled-background-color:#ebedf0 !default;\r\n\t$uv-checkbox-icon-wrap-disabled-checked-color:#c8c9cc !default;\r\n\t$uv-checkbox-label-margin-left:5px !default;\r\n\t$uv-checkbox-label-margin-right:12px !default;\r\n\t$uv-checkbox-label-color:$uv-content-color !default;\r\n\t$uv-checkbox-label-font-size:15px !default;\r\n\t$uv-checkbox-label-disabled-color:#c8c9cc !default;\r\n\r\n\t.uv-checkbox {\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\t@include flex(row);\r\n\t\t/* #endif */\r\n\t\toverflow: hidden;\r\n\t\tflex-direction: row;\r\n\t\talign-items: center;\r\n\r\n\t\t&-label--left {\r\n\t\t\tflex-direction: row\r\n\t\t}\r\n\r\n\t\t&-label--right {\r\n\t\t\tflex-direction: row-reverse;\r\n\t\t\tjustify-content: space-between\r\n\t\t}\r\n\r\n\t\t&__icon-wrap {\r\n\t\t\t/* #ifndef APP-NVUE */\r\n\t\t\tbox-sizing: border-box;\r\n\t\t\t// nvue下，border-color过渡有问题\r\n\t\t\ttransition-property: border-color, background-color, color;\r\n\t\t\ttransition-duration: 0.2s;\r\n\t\t\t/* #endif */\r\n\t\t\tcolor: $uv-content-color;\r\n\t\t\t@include flex;\r\n\t\t\talign-items: center;\r\n\t\t\tjustify-content: center;\r\n\t\t\tcolor: transparent;\r\n\t\t\ttext-align: center;\r\n\t\t\tfont-size: $uv-checkbox-icon-wrap-font-size;\r\n\t\t\tborder-width: $uv-checkbox-icon-wrap-border-width;\r\n\t\t\tborder-color: $uv-checkbox-icon-wrap-border-color;\r\n\t\t\tborder-style: solid;\r\n\r\n\t\t\t/* #ifdef MP-TOUTIAO */\r\n\t\t\t// 头条小程序兼容性问题，需要设置行高为0，否则图标偏下\r\n\t\t\t&__icon {\r\n\t\t\t\tline-height: $uv-checkbox-icon-wrap-icon-line-height;\r\n\t\t\t}\r\n\r\n\t\t\t/* #endif */\r\n\r\n\t\t\t&--circle {\r\n\t\t\t\tborder-radius: $uv-checkbox-icon-wrap-circle-border-radius;\r\n\t\t\t}\r\n\r\n\t\t\t&--square {\r\n\t\t\t\tborder-radius: $uv-checkbox-icon-wrap-square-border-radius;\r\n\t\t\t}\r\n\r\n\t\t\t&--checked {\r\n\t\t\t\tcolor: $uv-checkbox-icon-wrap-checked-color;\r\n\t\t\t\tbackground-color: $uv-checkbox-icon-wrap-checked-background-color;\r\n\t\t\t\tborder-color: $uv-checkbox-icon-wrap-checked-border-color;\r\n\t\t\t}\r\n\r\n\t\t\t&--disabled {\r\n\t\t\t\tbackground-color: $uv-checkbox-icon-wrap-disabled-background-color !important;\r\n\t\t\t}\r\n\r\n\t\t\t&--disabled--checked {\r\n\t\t\t\tcolor: $uv-checkbox-icon-wrap-disabled-checked-color !important;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t&__label {\r\n\t\t\t/* #ifndef APP-NVUE */\r\n\t\t\tword-wrap: break-word;\r\n\t\t\t/* #endif */\r\n\t\t\tmargin-left: $uv-checkbox-label-margin-left;\r\n\t\t\tmargin-right: $uv-checkbox-label-margin-right;\r\n\t\t\tcolor: $uv-checkbox-label-color;\r\n\t\t\tfont-size: $uv-checkbox-label-font-size;\r\n\r\n\t\t\t&--disabled {\r\n\t\t\t\tcolor: $uv-checkbox-label-disabled-color;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t&__label-wrap {\r\n\t\t\tpadding-left: $uv-checkbox-label-wrap-padding-right;\r\n\t\t}\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-checkbox/components/uv-checkbox-group/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 绑定的值\r\n\t\tvalue: {\r\n\t\t\ttype: Array,\r\n\t\t\tdefault: () => []\r\n\t\t},\r\n\t\tmodelValue: {\r\n\t\t\ttype: Array,\r\n\t\t\tdefault: () => []\r\n\t\t},\r\n\t\t// 标识符\r\n\t\tname: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 形状，circle-圆形，square-方形\r\n\t\tshape: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'square'\r\n\t\t},\r\n\t\t// 是否禁用全部checkbox\r\n\t\tdisabled: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 选中状态下的颜色，如设置此值，将会覆盖parent的activeColor值\r\n\t\tactiveColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#2979ff'\r\n\t\t},\r\n\t\t// 未选中的颜色\r\n\t\tinactiveColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#c8c9cc'\r\n\t\t},\r\n\t\t// 整个组件的尺寸，默认px\r\n\t\tsize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 18\r\n\t\t},\r\n\t\t// 布局方式，row-横向，column-纵向\r\n\t\tplacement: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'row'\r\n\t\t},\r\n\t\t// label的字体大小，px单位\r\n\t\tlabelSize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 14\r\n\t\t},\r\n\t\t// label的字体颜色\r\n\t\tlabelColor: {\r\n\t\t\ttype: [String],\r\n\t\t\tdefault: '#303133'\r\n\t\t},\r\n\t\t// 是否禁止点击文本操作\r\n\t\tlabelDisabled: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 图标颜色\r\n\t\ticonColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#fff'\r\n\t\t},\r\n\t\t// 图标的大小，单位px\r\n\t\ticonSize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 12\r\n\t\t},\r\n\t\t// 勾选图标的对齐方式，left-左边，right-右边\r\n\t\ticonPlacement: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'left'\r\n\t\t},\r\n\t\t// 竖向配列时，是否显示下划线\r\n\t\tborderBottom: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t...uni.$uv?.props?.checkboxGroup\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-checkbox/components/uv-checkbox-group/uv-checkbox-group.vue",
    "content": "<template>\r\n\t<view\r\n\t    class=\"uv-checkbox-group\"\r\n\t    :class=\"bemClass\"\r\n\t\t\t:style=\"[$uv.addStyle(this.customStyle)]\"\r\n\t>\r\n\t\t<slot></slot>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * checkboxGroup 复选框组\r\n\t * @description 复选框组件一般用于需要多个选择的场景，该组件功能完整，使用方便\r\n\t * @tutorial https://www.uvui.cn/components/checkbox.html\r\n\t * @property {String}\t\t\tname\t\t\t标识符 \r\n\t * @property {Array}\t\t\tvalue\t\t\t绑定的值\r\n\t * @property {String}\t\t\tshape\t\t\t形状，circle-圆形，square-方形 （默认 'square' ）\r\n\t * @property {Boolean}\t\t\tdisabled\t\t是否禁用全部checkbox （默认 false ）\r\n\t * @property {String}\t\t\tactiveColor\t\t选中状态下的颜色，如设置此值，将会覆盖parent的activeColor值 （默认 '#2979ff' ）\r\n\t * @property {String}\t\t\tinactiveColor\t未选中的颜色 （默认 '#c8c9cc' ）\r\n\t * @property {String | Number}\tsize\t\t\t整个组件的尺寸 单位px （默认 18 ）\r\n\t * @property {String}\t\t\tplacement\t\t布局方式，row-横向，column-纵向 （默认 'row' ）\r\n\t * @property {String | Number}\tlabelSize\t\tlabel的字体大小，px单位  （默认 14 ）\r\n\t * @property {String}\t\t\tlabelColor\t\tlabel的字体颜色 （默认 '#303133' ）\r\n\t * @property {Boolean}\t\t\tlabelDisabled\t是否禁止点击文本操作 (默认 false )\r\n\t * @property {String}\t\t\ticonColor\t\t图标颜色 （默认 '#ffffff' ）\r\n\t * @property {String | Number}\ticonSize\t\t图标的大小，单位px （默认 12 ）\r\n\t * @property {String}\t\t\ticonPlacement\t勾选图标的对齐方式，left-左边，right-右边  （默认 'left' ）\r\n\t * @property {Boolean}\t\t\tborderBottom\tplacement为row时，是否显示下边框 （默认 false ）\r\n\t * @event {Function}\tchange\t任一个checkbox状态发生变化时触发，回调为一个对象\r\n\t * @event {Function}\tinput\t修改通过v-model绑定的值时触发，回调为一个对象\r\n\t * @example <uv-checkbox-group></uv-checkbox-group>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-checkbox-group',\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tcomputed: {\r\n\t\t\t// 这里computed的变量，都是子组件uv-checkbox需要用到的，由于头条小程序的兼容性差异，子组件无法实时监听父组件参数的变化\r\n\t\t\t// 所以需要手动通知子组件，这里返回一个parentData变量，供watch监听，在其中去通知每一个子组件重新从父组件(uv-checkbox-group)\r\n\t\t\t// 拉取父组件新的变化后的参数\r\n\t\t\tparentData() {\r\n\t\t\t\tlet value = [];\r\n\t\t\t\tif(this.value.length) {\r\n\t\t\t\t\tvalue  = this.value;\r\n\t\t\t\t} else if (this.modelValue.length){\r\n\t\t\t\t\tvalue = this.modelValue;\r\n\t\t\t\t}\r\n\t\t\t\treturn [value, this.disabled, this.inactiveColor, this.activeColor, this.size, this.labelDisabled, this.shape,\r\n\t\t\t\t\tthis.iconSize, this.borderBottom, this.placement, this.labelSize, this.labelColor]\r\n\t\t\t},\r\n\t\t\tbemClass() {\r\n\t\t\t\t// this.bem为一个computed变量，在mixin中\r\n\t\t\t\treturn this.bem('checkbox-group', ['placement'])\r\n\t\t\t},\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\t// 当父组件需要子组件需要共享的参数发生了变化，手动通知子组件\r\n\t\t\tparentData() {\r\n\t\t\t\tif (this.children.length) {\r\n\t\t\t\t\tthis.children.map(child => {\r\n\t\t\t\t\t\t// 判断子组件(uv-checkbox)如果有init方法的话，就就执行(执行的结果是子组件重新从父组件拉取了最新的值)\r\n\t\t\t\t\t\ttypeof(child.init) === 'function' && child.init()\r\n\t\t\t\t\t})\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t},\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\r\n\t\t\t}\r\n\t\t},\r\n\t\tcreated() {\r\n\t\t\tthis.children = []\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\t// 将其他的checkbox设置为未选中的状态\r\n\t\t\tunCheckedOther(childInstance) {\r\n\t\t\t\tconst values = []\r\n\t\t\t\tthis.children.map(child => {\r\n\t\t\t\t\t// 将被选中的checkbox，放到数组中返回\r\n\t\t\t\t\tif (child.isChecked) {\r\n\t\t\t\t\t\tvalues.push(child.name)\r\n\t\t\t\t\t}\r\n\t\t\t\t})\r\n\t\t\t\t// 修改通过v-model绑定的值\r\n\t\t\t\t// #ifdef VUE2\r\n\t\t\t\tthis.$emit('input', values)\r\n\t\t\t\t// #endif\r\n\t\t\t\t// #ifdef VUE3\r\n\t\t\t\tthis.$emit('update:modelValue',values)\r\n\t\t\t\t// #endif\r\n\t\t\t\t// 发出事件\r\n\t\t\t\tthis.$emit('change', values)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\r\n\t.uv-checkbox-group {\r\n\t\tflex: 1;\r\n\t\t\r\n\t\t&--row {\r\n\t\t\t@include flex;\r\n\t\t\tflex-wrap: wrap;\r\n\t\t}\r\n\r\n\t\t&--column {\r\n\t\t\t@include flex(column);\r\n\t\t}\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-checkbox/package.json",
    "content": "{\r\n  \"id\": \"uv-checkbox\",\r\n  \"displayName\": \"uv-checkbox 复选框 全面兼容vue3+2、app、h5、小程序等多端\",\r\n  \"version\": \"1.0.13\",\r\n  \"description\": \"复选框组件一般用于需要多个选择的场景，该组件功能完整，使用方便。\",\r\n  \"keywords\": [\r\n    \"uv-checkbox\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"checkbox\",\r\n    \"复选框\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-icon\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-checkbox/readme.md",
    "content": "## Checkbox 复选框\r\n\r\n> **组件名：uv-checkbox**\r\n\r\n复选框组件一般用于需要多个选择的场景，该组件功能完整，使用方便。可配合 `uv-form` 组件进行表单验证等场景使用。 \r\n\r\n# <a href=\"https://www.uvui.cn/components/checkbox.html\" target=\"_blank\">查看文档</a>\r\n\r\n## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>（请不要 下载插件ZIP）</small>\r\n\r\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n<a href=\"https://ext.dcloud.net.cn/plugin?name=uv-ui\" target=\"_blank\">\r\n\r\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\r\n\r\n</a>\r\n\r\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-code/changelog.md",
    "content": "## 1.0.3（2023-10-13）\n1. 优化\n## 1.0.2（2023-10-13）\n1. unmounted兼容vue3\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-code 验证码倒计时\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-code/components/uv-code/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 倒计时总秒数\r\n\t\tseconds: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 60\r\n\t\t},\r\n\t\t// 尚未开始时提示\r\n\t\tstartText: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '获取验证码'\r\n\t\t},\r\n\t\t// 正在倒计时中的提示\r\n\t\tchangeText: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'X秒重新获取'\r\n\t\t},\r\n\t\t// 倒计时结束时的提示\r\n\t\tendText: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '重新获取'\r\n\t\t},\r\n\t\t// 是否在H5刷新或各端返回再进入时继续倒计时\r\n\t\tkeepRunning: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 为了区分多个页面，或者一个页面多个倒计时组件本地存储的继续倒计时变了\r\n\t\tuniqueKey: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t...uni.$uv?.props?.code\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-code/components/uv-code/uv-code.vue",
    "content": "<template>\r\n\t<view class=\"uv-code\">\r\n\t\t<!-- 此组件功能由js完成，无需写html逻辑 -->\r\n\t</view>\r\n</template>\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * Code 验证码倒计时\r\n\t * @description 考虑到用户实际发送验证码的场景，可能是一个按钮，也可能是一段文字，提示语各有不同，所以本组件不提供界面显示，只提供倒计时文本，由用户将文本嵌入到具体的场景\r\n\t * @tutorial https://www.uvui.cn/components/code.html\r\n\t * @property {String | Number}\tseconds\t\t\t倒计时所需的秒数（默认 60 ）\r\n\t * @property {String}\t\t\tstartText\t\t开始前的提示语，见官网说明（默认 '获取验证码' ）\r\n\t * @property {String}\t\t\tchangeText\t\t倒计时期间的提示语，必须带有字母\"x\"，见官网说明（默认 'X秒重新获取' ）\r\n\t * @property {String}\t\t\tendText\t\t\t倒计结束的提示语，见官网说明（默认 '重新获取' ）\r\n\t * @property {Boolean}\t\t\tkeepRunning\t\t是否在H5刷新或各端返回再进入时继续倒计时（ 默认false ）\r\n\t * @property {String}\t\t\tuniqueKey\t\t为了区分多个页面，或者一个页面多个倒计时组件本地存储的继续倒计时变了\r\n\t *\r\n\t * @event {Function}\tchange\t倒计时期间，每秒触发一次\r\n\t * @event {Function}\tstart\t开始倒计时触发\r\n\t * @event {Function}\tend\t\t结束倒计时触发\r\n\t * @example <uv-code ref=\"uCode\" @change=\"codeChange\" seconds=\"20\"></uv-code>\r\n\t */\r\n\texport default {\r\n\t\tname: \"uv-code\",\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tsecNum: this.seconds,\r\n\t\t\t\ttimer: null,\r\n\t\t\t\tcanGetCode: true, // 是否可以执行验证码操作\r\n\t\t\t}\r\n\t\t},\r\n\t\tmounted() {\r\n\t\t\tthis.checkKeepRunning()\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\tseconds: {\r\n\t\t\t\timmediate: true,\r\n\t\t\t\thandler(n) {\r\n\t\t\t\t\tthis.secNum = n\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tcheckKeepRunning() {\r\n\t\t\t\t// 获取上一次退出页面(H5还包括刷新)时的时间戳，如果没有上次的保存，此值可能为空\r\n\t\t\t\tlet lastTimestamp = Number(uni.getStorageSync(this.uniqueKey + '_$uCountDownTimestamp'))\r\n\t\t\t\tif (!lastTimestamp) return this.changeEvent(this.startText)\r\n\t\t\t\t// 当前秒的时间戳\r\n\t\t\t\tlet nowTimestamp = Math.floor((+new Date()) / 1000)\r\n\t\t\t\t// 判断当前的时间戳，是否小于上一次的本该按设定结束，却提前结束的时间戳\r\n\t\t\t\tif (this.keepRunning && lastTimestamp && lastTimestamp > nowTimestamp) {\r\n\t\t\t\t\t// 剩余尚未执行完的倒计秒数\r\n\t\t\t\t\tthis.secNum = lastTimestamp - nowTimestamp\r\n\t\t\t\t\t// 清除本地保存的变量\r\n\t\t\t\t\tuni.removeStorageSync(this.uniqueKey + '_$uCountDownTimestamp')\r\n\t\t\t\t\t// 开始倒计时\r\n\t\t\t\t\tthis.start()\r\n\t\t\t\t} else {\r\n\t\t\t\t\t// 如果不存在需要继续上一次的倒计时，执行正常的逻辑\r\n\t\t\t\t\tthis.changeEvent(this.startText)\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t// 开始倒计时\r\n\t\t\tstart() {\r\n\t\t\t\t// 防止快速点击获取验证码的按钮而导致内部产生多个定时器导致混乱\r\n\t\t\t\tif (this.timer) {\r\n\t\t\t\t\tclearInterval(this.timer)\r\n\t\t\t\t\tthis.timer = null\r\n\t\t\t\t}\r\n\t\t\t\tthis.$emit('start')\r\n\t\t\t\tthis.canGetCode = false\r\n\t\t\t\t// 这里放这句，是为了一开始时就提示，否则要等setInterval的1秒后才会有提示\r\n\t\t\t\tthis.changeEvent(this.changeText.replace(/x|X/, this.secNum))\r\n\t\t\t\tthis.timer = setInterval(() => {\r\n\t\t\t\t\tif (--this.secNum) {\r\n\t\t\t\t\t\t// 用当前倒计时的秒数替换提示字符串中的\"x\"字母\r\n\t\t\t\t\t\tthis.changeEvent(this.changeText.replace(/x|X/, this.secNum))\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tclearInterval(this.timer)\r\n\t\t\t\t\t\tthis.timer = null\r\n\t\t\t\t\t\tthis.changeEvent(this.endText)\r\n\t\t\t\t\t\tthis.secNum = this.seconds\r\n\t\t\t\t\t\tthis.$emit('end')\r\n\t\t\t\t\t\tthis.canGetCode = true\r\n\t\t\t\t\t}\r\n\t\t\t\t}, 1000)\r\n\t\t\t\tthis.setTimeToStorage()\r\n\t\t\t},\r\n\t\t\t// 重置，可以让用户再次获取验证码\r\n\t\t\treset() {\r\n\t\t\t\tthis.canGetCode = true\r\n\t\t\t\tclearInterval(this.timer)\r\n\t\t\t\tthis.secNum = this.seconds\r\n\t\t\t\tthis.changeEvent(this.endText)\r\n\t\t\t},\r\n\t\t\tchangeEvent(text) {\r\n\t\t\t\tthis.$emit('change', text)\r\n\t\t\t},\r\n\t\t\t// 保存时间戳，为了防止倒计时尚未结束，H5刷新或者各端的右上角返回上一页再进来\r\n\t\t\tsetTimeToStorage() {\r\n\t\t\t\tif (!this.keepRunning || !this.timer) return\r\n\t\t\t\t// 记录当前的时间戳，为了下次进入页面，如果还在倒计时内的话，继续倒计时\r\n\t\t\t\t// 倒计时尚未结束，结果大于0；倒计时已经开始，就会小于初始值，如果等于初始值，说明没有开始倒计时，无需处理\r\n\t\t\t\tif (this.secNum > 0 && this.secNum <= this.seconds) {\r\n\t\t\t\t\t// 获取当前时间戳(+ new Date()为特殊写法)，除以1000变成秒，再去除小数部分\r\n\t\t\t\t\tlet nowTimestamp = Math.floor((+new Date()) / 1000)\r\n\t\t\t\t\t// 将本该结束时候的时间戳保存起来 => 当前时间戳 + 剩余的秒数\r\n\t\t\t\t\tuni.setStorage({\r\n\t\t\t\t\t\tkey: this.uniqueKey + '_$uCountDownTimestamp',\r\n\t\t\t\t\t\tdata: nowTimestamp + Number(this.secNum)\r\n\t\t\t\t\t})\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\t// #ifdef VUE2\r\n\t\t// 组件销毁的时候，清除定时器，否则定时器会继续存在，系统不会自动清除\r\n\t\tbeforeDestroy() {\r\n\t\t\tthis.setTimeToStorage()\r\n\t\t\tclearTimeout(this.timer)\r\n\t\t\tthis.timer = null\r\n\t\t},\r\n\t\t// #endif\r\n\t\t// #ifdef VUE3\r\n\t\t// 组件销毁，兼容vue3\r\n\t\tunmounted() {\r\n\t\t\tthis.setTimeToStorage()\r\n\t\t\tclearTimeout(this.timer)\r\n\t\t\tthis.timer = null\r\n\t\t}\r\n\t\t// #endif\r\n\t}\r\n</script>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-code/package.json",
    "content": "{\r\n  \"id\": \"uv-code\",\r\n  \"displayName\": \"uv-code 验证码倒计时 全面兼容小程序、nvue、vue2、vue3等多端\",\r\n  \"version\": \"1.0.3\",\r\n  \"description\": \"考虑到用户实际发送验证码的场景，可能是一个按钮，也可能是一段文字，提示语各有不同，所以本组件不提供界面显示，只提供倒计时文本，由用户将文本嵌入到具体的场景。\",\r\n  \"keywords\": [\r\n    \"uv-code\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"code\",\r\n    \"验证码倒计时\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-code/readme.md",
    "content": "## Code 验证码输入框\r\n\r\n> **组件名：uv-code**\r\n\r\n考虑到用户实际发送验证码的场景，可能是一个按钮，也可能是一段文字，提示语各有不同，所以本组件不提供界面显示，只提供倒计时文本，由用户将文本嵌入到具体的场景。\r\n\r\n### <a href=\"https://www.uvui.cn/components/code.html\" target=\"_blank\">查看文档</a>\r\n\r\n### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n#### 如使用过程中有任何问题，或者您对uv-ui有一些好的建议，欢迎加入 uv-ui 交流群：<a href=\"https://ext.dcloud.net.cn/plugin?id=12287\" target=\"_blank\">uv-ui</a>、<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-code-input/changelog.md",
    "content": "## 1.0.5（2023-08-05）\n在vue2模式下，v-model设置为0时不生效的BUG\n## 1.0.4（2023-07-13）\n1. 修复value/v-model更改不生效的BUG\n## 1.0.3（2023-06-28）\n修复：使用:disabledKeyboard=\"true\"属性，事件全部失效的BUG\n## 1.0.2（2023-06-23）\r\n优化下边框\r\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-code-input 验证码输入\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-code-input/components/uv-code-input/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\tvalue: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\tmodelValue: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 键盘弹起时，是否自动上推页面\r\n\t\tadjustPosition: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 最大输入长度\r\n\t\tmaxlength: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 6\r\n\t\t},\r\n\t\t// 是否用圆点填充\r\n\t\tdot: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 显示模式，box-盒子模式，line-底部横线模式\r\n\t\tmode: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'box'\r\n\t\t},\r\n\t\t// 是否细边框\r\n\t\thairline: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 字符间的距离\r\n\t\tspace: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 10\r\n\t\t},\r\n\t\t// 是否自动获取焦点\r\n\t\tfocus: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 字体是否加粗\r\n\t\tbold: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 字体颜色\r\n\t\tcolor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#606266'\r\n\t\t},\r\n\t\t// 字体大小\r\n\t\tfontSize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 18\r\n\t\t},\r\n\t\t// 输入框的大小，宽等于高\r\n\t\tsize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 35\r\n\t\t},\r\n\t\t// 是否隐藏原生键盘，如果想用自定义键盘的话，需设置此参数为true\r\n\t\tdisabledKeyboard: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 边框和线条颜色\r\n\t\tborderColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#c9cacc'\r\n\t\t},\r\n\t\t// 是否禁止输入\".\"符号\r\n\t\tdisabledDot: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t...uni.$uv?.props?.codeInput\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-code-input/components/uv-code-input/uv-code-input.vue",
    "content": "<template>\r\n\t<view class=\"uv-code-input\">\r\n\t\t<view\r\n\t\t\tclass=\"uv-code-input__item\"\r\n\t\t\t:style=\"[itemStyle(index)]\"\r\n\t\t\tv-for=\"(item, index) in codeLength\"\r\n\t\t\t:key=\"index\"\r\n\t\t>\r\n\t\t\t<view\r\n\t\t\t\tclass=\"uv-code-input__item__dot\"\r\n\t\t\t\tv-if=\"dot && codeArray.length > index\"\r\n\t\t\t></view>\r\n\t\t\t<text\r\n\t\t\t\tv-else\r\n\t\t\t\t:style=\"{\r\n\t\t\t\t\tfontSize: $uv.addUnit(fontSize),\r\n\t\t\t\t\tfontWeight: bold ? 'bold' : 'normal',\r\n\t\t\t\t\tcolor: color\r\n\t\t\t\t}\"\r\n\t\t\t>{{codeArray[index]}}</text>\r\n\t\t\t<view\r\n\t\t\t\tclass=\"uv-code-input__item__line\"\r\n\t\t\t\tv-if=\"mode === 'line'\"\r\n\t\t\t\t:style=\"[lineStyle]\"\r\n\t\t\t></view>\r\n\t\t\t<!-- #ifndef APP-PLUS -->\r\n\t\t\t<view v-if=\"isFocus && codeArray.length === index\" :style=\"{backgroundColor: color}\" class=\"uv-code-input__item__cursor\"></view>\r\n\t\t\t<!-- #endif -->\r\n\t\t</view>\r\n\t\t<input\r\n\t\t\t:disabled=\"disabledKeyboard\"\r\n\t\t\ttype=\"number\"\r\n\t\t\t:focus=\"focus\"\r\n\t\t\t:value=\"inputValue\"\r\n\t\t\t:maxlength=\"maxlength\"\r\n\t\t\t:adjustPosition=\"adjustPosition\"\r\n\t\t\tclass=\"uv-code-input__input\"\r\n\t\t\t@input=\"inputHandler\"\r\n\t\t\t:style=\"{\r\n\t\t\t\theight: $uv.addUnit(size) \r\n\t\t\t}\"\r\n\t\t\t@focus=\"isFocus = true\"\r\n\t\t\t@blur=\"isFocus = false\"\r\n\t\t/>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * CodeInput 验证码输入\r\n\t * @description 该组件一般用于验证用户短信验证码的场景，也可以结合uvui的键盘组件使用\r\n\t * @tutorial https://www.uvui.cn/components/codeInput.html\r\n\t * @property {String | Number}\tvalue / v-model\t\t\t\t预置值\r\n\t * @property {String | Number}\tmaxlength\t\t\t最大输入长度 （默认 6 ）\r\n\t * @property {Boolean}\t\t\tdot\t\t\t\t\t是否用圆点填充 （默认 false ）\r\n\t * @property {String}\t\t\tmode\t\t\t\t显示模式，box-盒子模式，line-底部横线模式 （默认 'box' ）\r\n\t * @property {Boolean}\t\t\thairline\t\t\t是否细边框 （默认 false ）\r\n\t * @property {String | Number}\tspace\t\t\t\t字符间的距离 （默认 10 ）\r\n\t * @property {Boolean}\t\t\tfocus\t\t\t\t是否自动获取焦点 （默认 false ）\r\n\t * @property {Boolean}\t\t\tbold\t\t\t\t字体和输入横线是否加粗 （默认 false ）\r\n\t * @property {String}\t\t\tcolor\t\t\t\t字体颜色 （默认 '#606266' ）\r\n\t * @property {String | Number}\tfontSize\t\t\t字体大小，单位px （默认 18 ）\r\n\t * @property {String | Number}\tsize\t\t\t\t输入框的大小，宽等于高 （默认 35 ）\r\n\t * @property {Boolean}\t\t\tdisabledKeyboard\t是否隐藏原生键盘，如果想用自定义键盘的话，需设置此参数为true （默认 false ）\r\n\t * @property {String}\t\t\tborderColor\t\t\t边框和线条颜色 （默认 '#c9cacc' ）\r\n\t * @property {Boolean}\t\t\tdisabledDot\t\t\t是否禁止输入\".\"符号 （默认 true ）\r\n\t * \r\n\t * @event {Function}\tchange\t输入内容发生改变时触发，具体见上方说明\t\t\tvalue：当前输入的值\r\n\t * @event {Function}\tfinish\t输入字符个数达maxlength值时触发，见上方说明\tvalue：当前输入的值\r\n\t * @example\t<uv-code-input v-model=\"value4\" :focus=\"true\"></uv-code-input>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-code-input',\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tinputValue: '',\r\n\t\t\t\tisFocus: this.focus\r\n\t\t\t}\r\n\t\t},\r\n\t\tcreated() {\r\n\t\t\tconst value = String(this.value) || String(this.modelValue);\r\n\t\t\tthis.inputValue = String(value).substring(0, this.maxlength);\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\tvalue(newVal) {\r\n\t\t\t\t// 转为字符串，超出部分截掉\r\n\t\t\t\tthis.inputValue = String(newVal).substring(0, this.maxlength);\r\n\t\t\t\tif (this.disabledKeyboard) {\r\n\t\t\t\t\tthis.customInput();\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tmodelValue(newVal) {\r\n\t\t\t\t// 转为字符串，超出部分截掉\r\n\t\t\t\tthis.inputValue = String(newVal).substring(0, this.maxlength);\r\n\t\t\t\tif (this.disabledKeyboard) {\r\n\t\t\t\t\tthis.customInput();\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\t// 根据长度，循环输入框的个数，因为头条小程序数值不能用于v-for\r\n\t\t\tcodeLength() {\r\n\t\t\t\treturn new Array(Number(this.maxlength))\r\n\t\t\t},\r\n\t\t\t// 循环item的样式\r\n\t\t\titemStyle() {\r\n\t\t\t\treturn index => {\r\n\t\t\t\t\tconst addUnit = this.$uv.addUnit\r\n\t\t\t\t\tconst style = {\r\n\t\t\t\t\t\twidth: addUnit(this.size),\r\n\t\t\t\t\t\theight: addUnit(this.size)\r\n\t\t\t\t\t}\r\n\t\t\t\t\t// 盒子模式下，需要额外进行处理\r\n\t\t\t\t\tif (this.mode === 'box') {\r\n\t\t\t\t\t\t// 设置盒子的边框，如果是细边框，则设置为0.5px宽度\r\n\t\t\t\t\t\tstyle.border = `${this.hairline ? 0.5 : 1}px solid ${this.borderColor}`\r\n\t\t\t\t\t\t// 如果盒子间距为0的话\r\n\t\t\t\t\t\tif (this.$uv.getPx(this.space) === 0) {\r\n\t\t\t\t\t\t\t// 给第一和最后一个盒子设置圆角\r\n\t\t\t\t\t\t\tif (index === 0) {\r\n\t\t\t\t\t\t\t\tstyle.borderTopLeftRadius = '3px'\r\n\t\t\t\t\t\t\t\tstyle.borderBottomLeftRadius = '3px'\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\tif (index === this.codeLength.length - 1) {\r\n\t\t\t\t\t\t\t\tstyle.borderTopRightRadius = '3px'\r\n\t\t\t\t\t\t\t\tstyle.borderBottomRightRadius = '3px'\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t// 最后一个盒子的右边框需要保留\r\n\t\t\t\t\t\t\tif (index !== this.codeLength.length - 1) {\r\n\t\t\t\t\t\t\t\tstyle.borderRight = 'none'\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (index !== this.codeLength.length - 1) {\r\n\t\t\t\t\t\t// 设置验证码字符之间的距离，通过margin-right设置，最后一个字符，无需右边框\r\n\t\t\t\t\t\tstyle.marginRight = addUnit(this.space)\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\t// 最后一个盒子的有边框需要保留\r\n\t\t\t\t\t\tstyle.marginRight = 0\r\n\t\t\t\t\t}\r\n\t\t\t\t\treturn style\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t// 将输入的值，转为数组，给item历遍时，根据当前的索引显示数组的元素\r\n\t\t\tcodeArray() {\r\n\t\t\t\treturn String(this.inputValue).split('')\r\n\t\t\t},\r\n\t\t\t// 下划线模式下，横线的样式\r\n\t\t\tlineStyle() {\r\n\t\t\t\tconst style = {}\r\n\t\t\t\tstyle.height = this.hairline ? '1px' : '4px'\r\n\t\t\t\tstyle.width = this.$uv.addUnit(this.size)\r\n\t\t\t\t// 线条模式下，背景色即为边框颜色\r\n\t\t\t\tstyle.backgroundColor = this.borderColor\r\n\t\t\t\treturn style\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\t// 监听输入框的值发生变化\r\n\t\t\tinputHandler(e) {\r\n\t\t\t\tconst value = e.detail.value\r\n\t\t\t\tthis.inputValue = value\r\n\t\t\t\t// 是否允许输入“.”符号\r\n\t\t\t\tif (this.disabledDot) {\r\n\t\t\t\t\tthis.$nextTick(() => {\r\n\t\t\t\t\t\tthis.inputValue = value.replace('.', '')\r\n\t\t\t\t\t})\r\n\t\t\t\t}\r\n\t\t\t\t// 未达到maxlength之前，发送change事件，达到后发送finish事件\r\n\t\t\t\tthis.$emit('change', value)\r\n\t\t\t\t// 修改通过v-model双向绑定的值\r\n\t\t\t\tthis.$emit('input', value)\r\n\t\t\t\tthis.$emit('update:modelValue', value)\r\n\t\t\t\t// 达到用户指定输入长度时，发出完成事件\r\n\t\t\t\tif (String(value).length >= Number(this.maxlength)) {\r\n\t\t\t\t\tthis.$emit('finish', value)\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t// 自定义键盘输入值监听\r\n\t\t\tcustomInput() {\r\n\t\t\t\tconst value = this.inputValue;\r\n\t\t\t\t// 是否允许输入“.”符号\r\n\t\t\t\tif (this.disabledDot) {\r\n\t\t\t\t\tthis.$nextTick(() => {\r\n\t\t\t\t\t\tthis.inputValue = value.replace('.', '')\r\n\t\t\t\t\t})\r\n\t\t\t\t}\r\n\t\t\t\t// 未达到maxlength之前，发送change事件，达到后发送finish事件\r\n\t\t\t\tthis.$emit('change', value)\r\n\t\t\t\t// 达到用户指定输入长度时，发出完成事件\r\n\t\t\t\tif (String(value).length >= Number(this.maxlength)) {\r\n\t\t\t\t\tthis.$emit('finish', value)\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t$uv-code-input-cursor-width: 1px;\r\n\t$uv-code-input-cursor-height: 40%;\r\n\t$uv-code-input-cursor-animation-duration: 1s;\r\n\t$uv-code-input-cursor-animation-name: uv-cursor-flicker;\r\n\t.uv-code-input {\r\n\t\t@include flex;\r\n\t\tposition: relative;\r\n\t\toverflow: hidden;\r\n\t\t&__item {\r\n\t\t\t@include flex;\r\n\t\t\tjustify-content: center;\r\n\t\t\talign-items: center;\r\n\t\t\tposition: relative;\r\n\t\t\t&__text {\r\n\t\t\t\tfont-size: 15px;\r\n\t\t\t\tcolor: $uv-content-color;\r\n\t\t\t}\r\n\t\t\t&__dot {\r\n\t\t\t\twidth: 7px;\r\n\t\t\t\theight: 7px;\r\n\t\t\t\tborder-radius: 100px;\r\n\t\t\t\tbackground-color: $uv-content-color;\r\n\t\t\t}\r\n\t\t\t&__line {\r\n\t\t\t\tposition: absolute;\r\n\t\t\t\tbottom: 0;\r\n\t\t\t\theight: 4px;\r\n\t\t\t\tborder-radius: 100px;\r\n\t\t\t\twidth: 40px;\r\n\t\t\t\tbackground-color: $uv-content-color;\r\n\t\t\t}\r\n\t\t\t/* #ifndef APP-PLUS */\r\n\t\t\t&__cursor {\r\n\t\t\t\tposition: absolute;\r\n\t\t\t\ttop: 50%;\r\n\t\t\t\tleft: 50%;\r\n\t\t\t\ttransform: translate(-50%, -50%);\r\n\t\t\t\twidth: $uv-code-input-cursor-width;\r\n\t\t\t\theight: $uv-code-input-cursor-height;\r\n\t\t\t\tanimation: $uv-code-input-cursor-animation-duration uv-cursor-flicker infinite;\r\n\t\t\t}\r\n\t\t\t/* #endif */\r\n\t\t}\r\n\t\t&__input {\r\n\t\t\t// 之所以需要input输入框，是因为有它才能唤起键盘\r\n\t\t\t// 这里将它设置为两倍的屏幕宽度，再将左边的一半移出屏幕，为了不让用户看到输入的内容\r\n\t\t\tposition: absolute;\r\n\t\t\tleft: -750rpx;\r\n\t\t\twidth: 1500rpx;\r\n\t\t\ttop: 0;\r\n\t\t\tbackground-color: transparent;\r\n\t\t\ttext-align: left;\r\n\t\t}\r\n\t}\r\n\t/* #ifndef APP-PLUS */\r\n\t@keyframes uv-cursor-flicker {\r\n\t\t0% {\r\n\t\t\topacity: 0;\r\n\t\t}\r\n\t\t50% {\r\n\t\t\topacity: 1;\r\n\t\t}\r\n\t\t100% {\r\n\t\t\topacity: 0;\r\n\t\t}\r\n\t}\r\n\t/* #endif */\r\n</style>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-code-input/package.json",
    "content": "{\r\n  \"id\": \"uv-code-input\",\r\n  \"displayName\": \"uv-code-input 验证码输入 全面兼容vue3+2、app、h5、小程序等多端\",\r\n  \"version\": \"1.0.5\",\r\n  \"description\": \"验证码输入组件一般用于验证用户短信验证码的场景，输入框或横线多种模式可选。\",\r\n  \"keywords\": [\r\n    \"uv-code-input\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"code\",\r\n    \"验证码输入\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-code-input/readme.md",
    "content": "## CodeInput 验证码输入框\r\n\r\n> **组件名：uv-code-input**\r\n\r\n该组件一般用于验证用户短信验证码的场景，输入框或横线多种模式可选。\r\n\r\n# <a href=\"https://www.uvui.cn/components/codeInput.html\" target=\"_blank\">查看文档</a>\r\n\r\n## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>（请不要 下载插件ZIP）</small>\r\n\r\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n<a href=\"https://ext.dcloud.net.cn/plugin?name=uv-ui\" target=\"_blank\">\r\n\r\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\r\n\r\n</a>\r\n\r\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-collapse/changelog.md",
    "content": "## 1.0.1（2023-05-16）\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\n2. 优化部分功能\n## 1.0.0（2023-05-10）\r\nuv-collapse 折叠面板\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-collapse/components/uv-collapse/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 当前展开面板的name，非手风琴模式：[<string | number>]，手风琴模式：string | number\r\n\t\tvalue: {\r\n\t\t\ttype: [String, Number, Array, null],\r\n\t\t\tdefault: null\r\n\t\t},\r\n\t\t// 是否手风琴模式\r\n\t\taccordion: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否显示外边框\r\n\t\tborder: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t...uni.$uv?.props?.collapse\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-collapse/components/uv-collapse/uv-collapse.vue",
    "content": "<template>\r\n\t<view class=\"uv-collapse\">\r\n\t\t<uv-line v-if=\"border\"></uv-line>\r\n\t\t<slot />\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * collapse 折叠面板 \r\n\t * @description 通过折叠面板收纳内容区域\r\n\t * @tutorial https://www.uvui.cn/components/collapse.html\r\n\t * @property {String | Number | Array}\tvalue\t\t当前展开面板的name，非手风琴模式：[<string | number>]，手风琴模式：string | number\r\n\t * @property {Boolean}\t\t\t\t\taccordion\t是否手风琴模式（ 默认 false ）\r\n\t * @property {Boolean}\t\t\t\t\tborder\t\t是否显示外边框 ( 默认 true ）\r\n\t * @event {Function}\tchange \t\t当前激活面板展开时触发(如果是手风琴模式，参数activeNames类型为String，否则为Array)\r\n\t * @example <uv-collapse></uv-collapse>\r\n\t */\r\n\texport default {\r\n\t\tname: \"uv-collapse\",\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\twatch: {\r\n\t\t\tneedInit() {\r\n\t\t\t\tthis.init()\r\n\t\t\t},\r\n\t\t\t// 当父组件需要子组件需要共享的参数发生了变化，手动通知子组件\r\n\t\t\tparentData() {\r\n\t\t\t\tif (this.children.length) {\r\n\t\t\t\t\tthis.children.map(child => {\r\n\t\t\t\t\t\t// 判断子组件(uv-checkbox)如果有updateParentData方法的话，就就执行(执行的结果是子组件重新从父组件拉取了最新的值)\r\n\t\t\t\t\t\ttypeof(child.updateParentData) === 'function' && child.updateParentData()\r\n\t\t\t\t\t})\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\tcreated() {\r\n\t\t\tthis.children = []\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\tneedInit() {\r\n\t\t\t\t// 通过computed，同时监听accordion和value值的变化\r\n\t\t\t\t// 再通过watch去执行init()方法，进行再一次的初始化\r\n\t\t\t\treturn [this.accordion, this.value]\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\t// 重新初始化一次内部的所有子元素\r\n\t\t\tinit() {\r\n\t\t\t\tthis.children.map(child => {\r\n\t\t\t\t\tchild.init()\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\t/**\r\n\t\t\t * collapse-item被点击时触发，由collapse统一处理各子组件的状态\r\n\t\t\t * @param {Object} target 被操作的面板的实例\r\n\t\t\t */\r\n\t\t\tonChange(target) {\r\n\t\t\t\tlet changeArr = []\r\n\t\t\t\tthis.children.map((child, index) => {\r\n\t\t\t\t\t// 如果是手风琴模式，将其他的折叠面板收起来\r\n\t\t\t\t\tif (this.accordion) {\r\n\t\t\t\t\t\tchild.expanded = child === target ? !target.expanded : false\r\n\t\t\t\t\t\tchild.setContentAnimate()\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tif(child === target) {\r\n\t\t\t\t\t\t\tchild.expanded = !child.expanded\r\n\t\t\t\t\t\t\tchild.setContentAnimate()\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\t// 拼接change事件中，数组元素的状态\r\n\t\t\t\t\tchangeArr.push({\r\n\t\t\t\t\t\t// 如果没有定义name属性，则默认返回组件的index索引\r\n\t\t\t\t\t\tname: child.name || index,\r\n\t\t\t\t\t\tstatus: child.expanded ? 'open' : 'close'\r\n\t\t\t\t\t})\r\n\t\t\t\t})\r\n\r\n\t\t\t\tthis.$emit('change', changeArr)\r\n\t\t\t\tthis.$emit(target.expanded ? 'open' : 'close', target.name)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-collapse/components/uv-collapse-item/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 标题\r\n\t\ttitle: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 标题右侧内容\r\n\t\tvalue: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 标题下方的描述信息\r\n\t\tlabel: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 是否禁用折叠面板\r\n\t\tdisabled: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否展示右侧箭头并开启点击反馈\r\n\t\tisLink: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 是否开启点击反馈\r\n\t\tclickable: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 是否显示内边框\r\n\t\tborder: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 标题的对齐方式\r\n\t\talign: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'left'\r\n\t\t},\r\n\t\t// 唯一标识符\r\n\t\tname: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 标题左侧图片，可为绝对路径的图片或内置图标\r\n\t\ticon: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 面板展开收起的过渡时间，单位ms\r\n\t\tduration: {\r\n\t\t\ttype: Number,\r\n\t\t\tdefault: 300\r\n\t\t},\r\n\t\t...uni.$uv?.props?.collapseItem\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-collapse/components/uv-collapse-item/uv-collapse-item.vue",
    "content": "<template>\r\n\t<view class=\"uv-collapse-item\">\r\n\t\t<uv-cell\r\n\t\t\t:title=\"title\"\r\n\t\t\t:value=\"value\"\r\n\t\t\t:label=\"label\"\r\n\t\t\t:icon=\"icon\"\r\n\t\t\t:isLink=\"isLink\"\r\n\t\t\t:clickable=\"clickable\"\r\n\t\t\t:border=\"parentData.border && showBorder\"\r\n\t\t\t@click=\"clickHandler\"\r\n\t\t\t:arrowDirection=\"expanded ? 'up' : 'down'\"\r\n\t\t\t:disabled=\"disabled\"\r\n\t\t>\r\n\t\t\t<!-- #ifndef MP-WEIXIN -->\r\n\t\t\t<!-- 微信小程序不支持，因为微信中不支持 <slot name=\"title\" slot=\"title\" />的写法 -->\r\n\t\t\t<template slot=\"title\">\r\n\t\t\t\t<slot name=\"title\"></slot>\r\n\t\t\t</template>\r\n\t\t\t<template slot=\"icon\">\r\n\t\t\t\t<slot name=\"icon\"></slot>\r\n\t\t\t</template>\r\n\t\t\t<template slot=\"value\">\r\n\t\t\t\t<slot name=\"value\"></slot>\r\n\t\t\t</template>\r\n\t\t\t<template slot=\"right-icon\">\r\n\t\t\t\t<slot name=\"right-icon\"></slot>\r\n\t\t\t</template>\r\n\t\t\t<!-- #endif -->\r\n\t\t</uv-cell>\r\n\t\t<view\r\n\t\t\tclass=\"uv-collapse-item__content\"\r\n\t\t\t:animation=\"animationData\"\r\n\t\t\tref=\"animation\"\r\n\t\t>\r\n\t\t\t<view\r\n\t\t\t\tclass=\"uv-collapse-item__content__text content-class\"\r\n\t\t\t\t:id=\"elId\"\r\n\t\t\t\t:ref=\"elId\"\r\n\t\t\t><slot /></view>\r\n\t\t</view>\r\n\t\t<uv-line v-if=\"parentData.border\"></uv-line>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t// #ifdef APP-NVUE\r\n\tconst animation = uni.requireNativePlugin('animation')\r\n\tconst dom = uni.requireNativePlugin('dom')\r\n\t// #endif\r\n\t/**\r\n\t * collapseItem 折叠面板Item\r\n\t * @description 通过折叠面板收纳内容区域（搭配uv-collapse使用）\r\n\t * @tutorial https://www.uvui.cn/components/collapse.html\r\n\t * @property {String}\t\t\ttitle \t\t标题\r\n\t * @property {String}\t\t\tvalue \t\t标题右侧内容\r\n\t * @property {String}\t\t\tlabel \t\t标题下方的描述信息\r\n\t * @property {Boolean}\t\t\tdisbled \t是否禁用折叠面板 ( 默认 false )\r\n\t * @property {Boolean}\t\t\tisLink \t\t是否展示右侧箭头并开启点击反馈 ( 默认 true )\r\n\t * @property {Boolean}\t\t\tclickable\t是否开启点击反馈 ( 默认 true )\r\n\t * @property {Boolean}\t\t\tborder\t\t是否显示内边框 ( 默认 true )\r\n\t * @property {String}\t\t\talign\t\t标题的对齐方式 ( 默认 'left' )\r\n\t * @property {String | Number}\tname\t\t唯一标识符\r\n\t * @property {String}\t\t\ticon\t\t标题左侧图片，可为绝对路径的图片或内置图标\r\n\t * @event {Function}\t\t\tchange \t\t\t某个item被打开或者收起时触发\r\n\t * @example <uv-collapse-item :title=\"item.head\" v-for=\"(item, index) in itemList\" :key=\"index\">{{item.body}}</uv-collapse-item>\r\n\t */\r\n\texport default {\r\n\t\tname: \"uv-collapse-item\",\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\telId: '',\r\n\t\t\t\t// uni.createAnimation的导出数据\r\n\t\t\t\tanimationData: {},\r\n\t\t\t\t// 是否展开状态\r\n\t\t\t\texpanded: false,\r\n\t\t\t\t// 根据expanded确定是否显示border，为了控制展开时，cell的下划线更好的显示效果，进行一定时间的延时\r\n\t\t\t\tshowBorder: false,\r\n\t\t\t\t// 是否动画中，如果是则不允许继续触发点击\r\n\t\t\t\tanimating: false,\r\n\t\t\t\t// 父组件uv-collapse的参数\r\n\t\t\t\tparentData: {\r\n\t\t\t\t\taccordion: false,\r\n\t\t\t\t\tborder: false\r\n\t\t\t\t}\r\n\t\t\t};\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\texpanded(n) {\r\n\t\t\t\tclearTimeout(this.timer)\r\n\t\t\t\tthis.timer = null\r\n\t\t\t\t// 这里根据expanded的值来进行一定的延时，是为了cell的下划线更好的显示效果\r\n\t\t\t\tthis.timer = setTimeout(() => {\r\n\t\t\t\t\tthis.showBorder = n\r\n\t\t\t\t}, n ? 10 : 290)\r\n\t\t\t}\r\n\t\t},\r\n\t\tcreated() {\r\n\t\t\tthis.elId = this.$uv.guid();\r\n\t\t},\r\n\t\tmounted() {\r\n\t\t\tthis.init()\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\t// 异步获取内容，或者动态修改了内容时，需要重新初始化\r\n\t\t\tinit() {\r\n\t\t\t\t// 初始化数据\r\n\t\t\t\tthis.updateParentData()\r\n\t\t\t\tif (!this.parent) {\r\n\t\t\t\t\treturn this.$uv.error('uv-collapse-item必须要搭配uv-collapse组件使用')\r\n\t\t\t\t}\r\n\t\t\t\tconst {\r\n\t\t\t\t\tvalue,\r\n\t\t\t\t\taccordion,\r\n\t\t\t\t\tchildren = []\r\n\t\t\t\t} = this.parent\r\n\r\n\t\t\t\tif (accordion) {\r\n\t\t\t\t\tif (this.$uv.test.array(value)) {\r\n\t\t\t\t\t\treturn this.$uv.error('手风琴模式下，uv-collapse组件的value参数不能为数组')\r\n\t\t\t\t\t}\r\n\t\t\t\t\tthis.expanded = this.name == value\r\n\t\t\t\t} else {\r\n\t\t\t\t\tif (!this.$uv.test.array(value) && value !== null) {\r\n\t\t\t\t\t\treturn this.$uv.error('非手风琴模式下，uv-collapse组件的value参数必须为数组')\r\n\t\t\t\t\t}\r\n\t\t\t\t\tthis.expanded = (value || []).some(item => item == this.name)\r\n\t\t\t\t}\r\n\t\t\t\t// 设置组件的展开或收起状态\r\n\t\t\t\tthis.$nextTick(function() {\r\n\t\t\t\t\tthis.setContentAnimate()\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\tupdateParentData() {\r\n\t\t\t\t// 此方法在mixin中\r\n\t\t\t\tthis.getParentData('uv-collapse')\r\n\t\t\t},\r\n\t\t\tasync setContentAnimate() {\r\n\t\t\t\t// 每次面板打开或者收起时，都查询元素尺寸\r\n\t\t\t\t// 好处是，父组件从服务端获取内容后，变更折叠面板后可以获得最新的高度\r\n\t\t\t\tconst rect = await this.queryRect()\r\n\t\t\t\tconst height = this.expanded ? rect.height : 0\r\n\t\t\t\tthis.animating = true\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\tconst ref = this.$refs['animation'].ref\r\n\t\t\t\tanimation.transition(ref, {\r\n\t\t\t\t\tstyles: {\r\n\t\t\t\t\t\theight: height + 'px'\r\n\t\t\t\t\t},\r\n\t\t\t\t\tduration: this.duration,\r\n\t\t\t\t\t// 必须设置为true，否则会到面板收起或展开时，页面其他元素不会随之调整它们的布局\r\n\t\t\t\t\tneedLayout: true,\r\n\t\t\t\t\ttimingFunction: 'ease-in-out',\r\n\t\t\t\t}, () => {\r\n\t\t\t\t\tthis.animating = false\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\r\n\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\tconst animation = uni.createAnimation({\r\n\t\t\t\t\ttimingFunction: 'ease-in-out',\r\n\t\t\t\t});\r\n\t\t\t\tanimation\r\n\t\t\t\t\t.height(height)\r\n\t\t\t\t\t.step({\r\n\t\t\t\t\t\tduration: this.duration,\r\n\t\t\t\t\t})\r\n\t\t\t\t\t.step()\r\n\t\t\t\t// 导出动画数据给面板的animationData值\r\n\t\t\t\tthis.animationData = animation.export()\r\n\t\t\t\t// 标识动画结束\r\n\t\t\t\tthis.$uv.sleep(this.duration).then(() => {\r\n\t\t\t\t\tthis.animating = false\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\t\t\t},\r\n\t\t\t// 点击collapsehead头部\r\n\t\t\tclickHandler() {\r\n\t\t\t\tif (this.disabled && this.animating) return\r\n\t\t\t\t// 设置本组件为相反的状态\r\n\t\t\t\tthis.parent && this.parent.onChange(this)\r\n\t\t\t},\r\n\t\t\t// 查询内容高度\r\n\t\t\tqueryRect() {\r\n\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\t// 组件内部一般用this.$uvGetRect，对外的为getRect，二者功能一致，名称不同\r\n\t\t\t\treturn new Promise(resolve => {\r\n\t\t\t\t\tthis.$uvGetRect(`#${this.elId}`).then(size => {\r\n\t\t\t\t\t\tresolve(size)\r\n\t\t\t\t\t})\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\t// nvue下，使用dom模块查询元素高度\r\n\t\t\t\t// 返回一个promise，让调用此方法的主体能使用then回调\r\n\t\t\t\treturn new Promise(resolve => {\r\n\t\t\t\t\tdom.getComponentRect(this.$refs[this.elId], res => {\r\n\t\t\t\t\t\tresolve(res.size)\r\n\t\t\t\t\t})\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\t\t\t}\r\n\t\t},\r\n\t};\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t.uv-collapse-item {\r\n\r\n\t\t&__content {\r\n\t\t\toverflow: hidden;\r\n\t\t\theight: 0;\r\n\r\n\t\t\t&__text {\r\n\t\t\t\tpadding: 12px 15px;\r\n\t\t\t\tcolor: $uv-content-color;\r\n\t\t\t\tfont-size: 14px;\r\n\t\t\t\tline-height: 18px;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-collapse/package.json",
    "content": "{\r\n  \"id\": \"uv-collapse\",\r\n  \"displayName\": \"uv-collapse 折叠面板 全面兼容小程序、nvue、vue2、vue3等多端\",\r\n  \"version\": \"1.0.1\",\r\n  \"description\": \"折叠面板组件，通过折叠面板收纳内容区域，点击可展开收起，多功能参数可配置。\",\r\n  \"keywords\": [\r\n    \"uv-collapse\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"collapse\",\r\n    \"折叠面板\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-line\",\r\n\t\t\t\"uv-cell\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-collapse/readme.md",
    "content": "## Collapse 折叠面板\r\n\r\n> **组件名：uv-collapse**\r\n\r\n通过折叠面板收纳内容区域，点击可展开收起，多功能参数可配置。\r\n\r\n### <a href=\"https://www.uvui.cn/components/collapse.html\" target=\"_blank\">查看文档</a>\r\n\r\n### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n#### 如使用过程中有任何问题，或者您对uv-ui有一些好的建议，欢迎加入 uv-ui 交流群：<a href=\"https://ext.dcloud.net.cn/plugin?id=12287\" target=\"_blank\">uv-ui</a>、<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-count-down/changelog.md",
    "content": "## 1.0.3（2023-10-13）\n1. unmounted兼容vue3\n## 1.0.2（2023-06-20）\r\n1. 增加外部样式customStyle参数\r\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-count-down 倒计时\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-count-down/components/uv-count-down/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 倒计时时长，单位ms\r\n\t\ttime: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 时间格式，DD-日，HH-时，mm-分，ss-秒，SSS-毫秒\r\n\t\tformat: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'HH:mm:ss'\r\n\t\t},\r\n\t\t// 是否自动开始倒计时\r\n\t\tautoStart: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 是否展示毫秒倒计时\r\n\t\tmillisecond: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t...uni.$uv?.props?.countDown\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-count-down/components/uv-count-down/utils.js",
    "content": "// 补0，如1 -> 01\r\nfunction padZero(num, targetLength = 2) {\r\n    let str = `${num}`\r\n    while (str.length < targetLength) {\r\n        str = `0${str}`\r\n    }\r\n    return str\r\n}\r\nconst SECOND = 1000\r\nconst MINUTE = 60 * SECOND\r\nconst HOUR = 60 * MINUTE\r\nconst DAY = 24 * HOUR\r\nexport function parseTimeData(time) {\r\n    const days = Math.floor(time / DAY)\r\n    const hours = Math.floor((time % DAY) / HOUR)\r\n    const minutes = Math.floor((time % HOUR) / MINUTE)\r\n    const seconds = Math.floor((time % MINUTE) / SECOND)\r\n    const milliseconds = Math.floor(time % SECOND)\r\n    return {\r\n        days,\r\n        hours,\r\n        minutes,\r\n        seconds,\r\n        milliseconds\r\n    }\r\n}\r\nexport function parseFormat(format, timeData) {\r\n    let {\r\n        days,\r\n        hours,\r\n        minutes,\r\n        seconds,\r\n        milliseconds\r\n    } = timeData\r\n    // 如果格式化字符串中不存在DD(天)，则将天的时间转为小时中去\r\n    if (format.indexOf('DD') === -1) {\r\n        hours += days * 24\r\n    } else {\r\n        // 对天补0\r\n        format = format.replace('DD', padZero(days))\r\n    }\r\n    // 其他同理于DD的格式化处理方式\r\n    if (format.indexOf('HH') === -1) {\r\n        minutes += hours * 60\r\n    } else {\r\n        format = format.replace('HH', padZero(hours))\r\n    }\r\n    if (format.indexOf('mm') === -1) {\r\n        seconds += minutes * 60\r\n    } else {\r\n        format = format.replace('mm', padZero(minutes))\r\n    }\r\n    if (format.indexOf('ss') === -1) {\r\n        milliseconds += seconds * 1000\r\n    } else {\r\n        format = format.replace('ss', padZero(seconds))\r\n    }\r\n    return format.replace('SSS', padZero(milliseconds, 3))\r\n}\r\nexport function isSameSecond(time1, time2) {\r\n    return Math.floor(time1 / 1000) === Math.floor(time2 / 1000)\r\n}\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-count-down/components/uv-count-down/uv-count-down.vue",
    "content": "<template>\r\n\t<view \r\n\t\tclass=\"uv-count-down\"\r\n\t\t:style=\"[$uv.addStyle(customStyle)]\">\r\n\t\t<slot>\r\n\t\t\t<text class=\"uv-count-down__text\">{{ formattedTime }}</text>\r\n\t\t</slot>\r\n\t</view>\r\n</template>\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\timport {\r\n\t\tisSameSecond,\r\n\t\tparseFormat,\r\n\t\tparseTimeData\r\n\t} from './utils';\r\n\t/**\r\n\t * uv-count-down 倒计时\r\n\t * @description 该组件一般使用于某个活动的截止时间上，通过数字的变化，给用户明确的时间感受，提示用户进行某一个行为操作。\r\n\t * @tutorial https://www.uvui.cn/components/countDown.html\r\n\t * @property {String | Number}\ttime\t\t倒计时时长，单位ms （默认 0 ）\r\n\t * @property {String}\t\t\tformat\t\t时间格式，DD-日，HH-时，mm-分，ss-秒，SSS-毫秒  （默认 'HH:mm:ss' ）\r\n\t * @property {Boolean}\t\t\tautoStart\t是否自动开始倒计时 （默认 true ）\r\n\t * @property {Boolean}\t\t\tmillisecond\t是否展示毫秒倒计时 （默认 false ）\r\n\t * @event {Function} finish 倒计时结束时触发 \r\n\t * @event {Function} change 倒计时变化时触发 \r\n\t * @event {Function} start\t开始倒计时\r\n\t * @event {Function} pause\t暂停倒计时 \r\n\t * @event {Function} reset\t重设倒计时，若 auto-start 为 true，重设后会自动开始倒计时 \r\n\t * @example <uv-count-down :time=\"time\"></uv-count-down>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-count-down',\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\ttimer: null,\r\n\t\t\t\t// 各单位(天，时，分等)剩余时间\r\n\t\t\t\ttimeData: parseTimeData(0),\r\n\t\t\t\t// 格式化后的时间，如\"03:23:21\"\r\n\t\t\t\tformattedTime: '0',\r\n\t\t\t\t// 倒计时是否正在进行中\r\n\t\t\t\truning: false,\r\n\t\t\t\tendTime: 0, // 结束的毫秒时间戳\r\n\t\t\t\tremainTime: 0, // 剩余的毫秒时间\r\n\t\t\t}\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\ttime(n) {\r\n\t\t\t\tthis.reset()\r\n\t\t\t}\r\n\t\t},\r\n\t\tmounted() {\r\n\t\t\tthis.init()\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tinit() {\r\n\t\t\t\tthis.reset()\r\n\t\t\t},\r\n\t\t\t// 开始倒计时\r\n\t\t\tstart() {\r\n\t\t\t\tif (this.runing) return\r\n\t\t\t\t// 标识为进行中\r\n\t\t\t\tthis.runing = true\r\n\t\t\t\t// 结束时间戳 = 此刻时间戳 + 剩余的时间\r\n\t\t\t\tthis.endTime = Date.now() + this.remainTime\r\n\t\t\t\tthis.toTick()\r\n\t\t\t},\r\n\t\t\t// 根据是否展示毫秒，执行不同操作函数\r\n\t\t\ttoTick() {\r\n\t\t\t\tif (this.millisecond) {\r\n\t\t\t\t\tthis.microTick()\r\n\t\t\t\t} else {\r\n\t\t\t\t\tthis.macroTick()\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tmacroTick() {\r\n\t\t\t\tthis.clearTimeout()\r\n\t\t\t\t// 每隔一定时间，更新一遍定时器的值\r\n\t\t\t\t// 同时此定时器的作用也能带来毫秒级的更新\r\n\t\t\t\tthis.timer = setTimeout(() => {\r\n\t\t\t\t\t// 获取剩余时间\r\n\t\t\t\t\tconst remain = this.getRemainTime()\r\n\t\t\t\t\t// 重设剩余时间\r\n\t\t\t\t\tif (!isSameSecond(remain, this.remainTime) || remain === 0) {\r\n\t\t\t\t\t\tthis.setRemainTime(remain)\r\n\t\t\t\t\t}\r\n\t\t\t\t\t// 如果剩余时间不为0，则继续检查更新倒计时\r\n\t\t\t\t\tif (this.remainTime !== 0) {\r\n\t\t\t\t\t\tthis.macroTick()\r\n\t\t\t\t\t}\r\n\t\t\t\t}, 30)\r\n\t\t\t},\r\n\t\t\tmicroTick() {\r\n\t\t\t\tthis.clearTimeout()\r\n\t\t\t\tthis.timer = setTimeout(() => {\r\n\t\t\t\t\tthis.setRemainTime(this.getRemainTime())\r\n\t\t\t\t\tif (this.remainTime !== 0) {\r\n\t\t\t\t\t\tthis.microTick()\r\n\t\t\t\t\t}\r\n\t\t\t\t}, 50)\r\n\t\t\t},\r\n\t\t\t// 获取剩余的时间\r\n\t\t\tgetRemainTime() {\r\n\t\t\t\t// 取最大值，防止出现小于0的剩余时间值\r\n\t\t\t\treturn Math.max(this.endTime - Date.now(), 0)\r\n\t\t\t},\r\n\t\t\t// 设置剩余的时间\r\n\t\t\tsetRemainTime(remain) {\r\n\t\t\t\tthis.remainTime = remain\r\n\t\t\t\t// 根据剩余的毫秒时间，得出该有天，小时，分钟等的值，返回一个对象\r\n\t\t\t\tconst timeData = parseTimeData(remain)\r\n\t\t\t\tthis.$emit('change', timeData)\r\n\t\t\t\t// 得出格式化后的时间\r\n\t\t\t\tthis.formattedTime = parseFormat(this.format, timeData)\r\n\t\t\t\t// 如果时间已到，停止倒计时\r\n\t\t\t\tif (remain <= 0) {\r\n\t\t\t\t\tthis.pause()\r\n\t\t\t\t\tthis.$emit('finish')\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t// 重置倒计时\r\n\t\t\treset() {\r\n\t\t\t\tthis.pause()\r\n\t\t\t\tthis.remainTime = this.time\r\n\t\t\t\tthis.setRemainTime(this.remainTime)\r\n\t\t\t\tif (this.autoStart) {\r\n\t\t\t\t\tthis.start()\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t// 暂停倒计时\r\n\t\t\tpause() {\r\n\t\t\t\tthis.runing = false;\r\n\t\t\t\tthis.clearTimeout()\r\n\t\t\t},\r\n\t\t\t// 清空定时器\r\n\t\t\tclearTimeout() {\r\n\t\t\t\tclearTimeout(this.timer)\r\n\t\t\t\tthis.timer = null\r\n\t\t\t}\r\n\t\t},\r\n\t\t// #ifdef VUE2\r\n\t\tbeforeDestroy() {\r\n\t\t\tthis.clearTimeout()\r\n\t\t},\r\n\t\t// #endif\r\n\t\t// #ifdef VUE3\r\n\t\tunmounted() {\r\n\t\t\tthis.clearTimeout()\r\n\t\t}\r\n\t\t// #endif\r\n\t}\r\n</script>\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t$uv-count-down-text-color: $uv-content-color !default;\r\n\t$uv-count-down-text-font-size: 15px !default;\r\n\t$uv-count-down-text-line-height: 22px !default;\r\n\t.uv-count-down {\r\n\t\t&__text {\r\n\t\t\tcolor: $uv-count-down-text-color;\r\n\t\t\tfont-size: $uv-count-down-text-font-size;\r\n\t\t\tline-height: $uv-count-down-text-line-height;\r\n\t\t}\r\n\t}\r\n</style>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-count-down/package.json",
    "content": "{\r\n  \"id\": \"uv-count-down\",\r\n  \"displayName\": \"uv-count-down 倒计时 全面兼容小程序、nvue、vue2、vue3等多端\",\r\n  \"version\": \"1.0.3\",\r\n  \"description\": \"该倒计时组件一般使用于某个活动的截止时间上，通过数字的变化，给用户明确的时间感受，提示用户进行某一个行为操作。\",\r\n  \"keywords\": [\r\n    \"uv-count-down\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"countDown\",\r\n    \"倒计时\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-count-down/readme.md",
    "content": "## CountDown 倒计时\r\n\r\n> **组件名：uv-count-down**\r\n\r\n该组件一般使用于某个活动的截止时间上，通过数字的变化，给用户明确的时间感受，提示用户进行某一个行为操作。\r\n\r\n### <a href=\"https://www.uvui.cn/components/countDown.html\" target=\"_blank\">查看文档</a>\r\n\r\n### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n#### 如使用过程中有任何问题，或者您对uv-ui有一些好的建议，欢迎加入 uv-ui 交流群：<a href=\"https://ext.dcloud.net.cn/plugin?id=12287\" target=\"_blank\">uv-ui</a>、<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-count-to/changelog.md",
    "content": "## 1.0.4（2023-06-20）\n1. 优化\n## 1.0.3（2023-06-20）\r\n1. 修复继续滚动的函数\r\n2. 修复其他\r\n## 1.0.2（2023-06-20）\r\n1. 适配px和rpx的单位\r\n2. 适配customStyle参数\r\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-count-to 数字滚动\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-count-to/components/uv-count-to/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 开始的数值，默认从0增长到某一个数\r\n\t\tstartVal: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 要滚动的目标数值，必须\r\n\t\tendVal: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 滚动到目标数值的动画持续时间，单位为毫秒（ms）\r\n\t\tduration: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 2000\r\n\t\t},\r\n\t\t// 设置数值后是否自动开始滚动\r\n\t\tautoplay: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 要显示的小数位数\r\n\t\tdecimals: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 是否在即将到达目标数值的时候，使用缓慢滚动的效果\r\n\t\tuseEasing: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 十进制分割\r\n\t\tdecimal: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: '.'\r\n\t\t},\r\n\t\t// 字体颜色\r\n\t\tcolor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#606266'\r\n\t\t},\r\n\t\t// 字体大小\r\n\t\tfontSize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 22\r\n\t\t},\r\n\t\t// 是否加粗字体\r\n\t\tbold: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 千位分隔符，类似金额的分割(￥23,321.05中的\",\")\r\n\t\tseparator: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t...uni.$uv?.props?.countTo\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-count-to/components/uv-count-to/uv-count-to.vue",
    "content": "<template>\r\n\t<text\r\n\t\tclass=\"uv-count-num\"\r\n\t\t:style=\"[{\r\n\t\t\tfontSize: $uv.addUnit(fontSize),\r\n\t\t\tfontWeight: bold ? 'bold' : 'normal',\r\n\t\t\tcolor: color\r\n\t\t},$uv.addStyle(customStyle)]\"\r\n\t>{{ displayValue }}</text>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n/**\r\n * countTo 数字滚动\r\n * @description 该组件一般用于需要滚动数字到某一个值的场景，目标要求是一个递增的值。\r\n * @tutorial https://www.uvui.cn/components/countTo.html\r\n * @property {String | Number}\tstartVal\t开始的数值，默认从0增长到某一个数（默认 0 ）\r\n * @property {String | Number}\tendVal\t\t要滚动的目标数值，必须 （默认 0 ）\r\n * @property {String | Number}\tduration\t滚动到目标数值的动画持续时间，单位为毫秒（ms） （默认 2000 ）\r\n * @property {Boolean}\t\t\tautoplay\t设置数值后是否自动开始滚动 （默认 true ）\r\n * @property {String | Number}\tdecimals\t要显示的小数位数，见官网说明（默认 0 ）\r\n * @property {Boolean}\t\t\tuseEasing\t滚动结束时，是否缓动结尾，见官网说明（默认 true ）\r\n * @property {String}\t\t\tdecimal\t\t十进制分割 （ 默认 \".\" ）\r\n * @property {String}\t\t\tcolor\t\t字体颜色（ 默认 '#606266' )\r\n * @property {String | Number}\tfontSize\t字体大小，单位px（ 默认 22 ）\r\n * @property {Boolean}\t\t\tbold\t\t字体是否加粗（默认 false ）\r\n * @property {String}\t\t\tseparator\t千位分隔符，见官网说明\r\n * @event {Function} end 数值滚动到目标值时触发\r\n * @example <uv-count-to ref=\"uCountTo\" :end-val=\"endVal\" :autoplay=\"autoplay\"></uv-count-to>\r\n */\r\nexport default {\r\n\tname: 'uv-count-to',\r\n\tdata() {\r\n\t\treturn {\r\n\t\t\tlocalStartVal: this.startVal,\r\n\t\t\tdisplayValue: this.formatNumber(this.startVal),\r\n\t\t\tprintVal: null,\r\n\t\t\tpaused: false, // 是否暂停\r\n\t\t\tlocalDuration: Number(this.duration),\r\n\t\t\tstartTime: null, // 开始的时间\r\n\t\t\ttimestamp: null, // 时间戳\r\n\t\t\tremaining: null, // 停留的时间\r\n\t\t\trAF: null,\r\n\t\t\tlastTime: 0 // 上一次的时间\r\n\t\t};\r\n\t},\r\n\tmixins: [mpMixin, mixin, props],\r\n\tcomputed: {\r\n\t\tcountDown() {\r\n\t\t\treturn this.startVal > this.endVal;\r\n\t\t}\r\n\t},\r\n\twatch: {\r\n\t\tstartVal() {\r\n\t\t\tthis.autoplay && this.start();\r\n\t\t},\r\n\t\tendVal() {\r\n\t\t\tthis.autoplay && this.start();\r\n\t\t}\r\n\t},\r\n\tmounted() {\r\n\t\tthis.autoplay && this.start();\r\n\t},\r\n\tmethods: {\r\n\t\teasingFn(t, b, c, d) {\r\n\t\t\treturn (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b;\r\n\t\t},\r\n\t\trequestAnimationFrame(callback) {\r\n\t\t\tconst currTime = new Date().getTime();\r\n\t\t\t// 为了使setTimteout的尽可能的接近每秒60帧的效果\r\n\t\t\tconst timeToCall = Math.max(0, 16 - (currTime - this.lastTime));\r\n\t\t\tconst id = setTimeout(() => {\r\n\t\t\t\tcallback(currTime + timeToCall);\r\n\t\t\t}, timeToCall);\r\n\t\t\tthis.lastTime = currTime + timeToCall;\r\n\t\t\treturn id;\r\n\t\t},\r\n\t\tcancelAnimationFrame(id) {\r\n\t\t\tclearTimeout(id);\r\n\t\t},\r\n\t\t// 开始滚动数字\r\n\t\tstart() {\r\n\t\t\tthis.localStartVal = this.startVal;\r\n\t\t\tthis.startTime = null;\r\n\t\t\tthis.localDuration = this.duration;\r\n\t\t\tthis.paused = false;\r\n\t\t\tthis.rAF = this.requestAnimationFrame(this.count);\r\n\t\t},\r\n\t\t// 暂定状态，从暂停状态开始滚动；或者滚动状态下，暂停\r\n\t\trestart() {\r\n\t\t\tif (this.paused) {\r\n\t\t\t\tthis.resume();\r\n\t\t\t\tthis.paused = false;\r\n\t\t\t} else {\r\n\t\t\t\tthis.stop();\r\n\t\t\t\tthis.paused = true;\r\n\t\t\t}\r\n\t\t},\r\n\t\t// 暂停\r\n\t\tstop() {\r\n\t\t\tthis.cancelAnimationFrame(this.rAF);\r\n\t\t\tthis.paused = true;\r\n\t\t},\r\n\t\t// 重新开始(暂停的情况下)\r\n\t\tresume() {\r\n\t\t\tif (!this.remaining) return;\r\n\t\t\tthis.startTime = 0;\r\n\t\t\tthis.localDuration = this.remaining;\r\n\t\t\tthis.localStartVal = this.printVal;\r\n\t\t\tthis.requestAnimationFrame(this.count);\r\n\t\t},\r\n\t\t// 重置\r\n\t\treset() {\r\n\t\t\tthis.startTime = null;\r\n\t\t\tthis.cancelAnimationFrame(this.rAF);\r\n\t\t\tthis.displayValue = this.formatNumber(this.startVal);\r\n\t\t},\r\n\t\tcount(timestamp) {\r\n\t\t\tif (!this.startTime) this.startTime = timestamp;\r\n\t\t\tthis.timestamp = timestamp;\r\n\t\t\tconst progress = timestamp - this.startTime;\r\n\t\t\tthis.remaining = this.localDuration - progress;\r\n\t\t\tif (this.useEasing) {\r\n\t\t\t\tif (this.countDown) {\r\n\t\t\t\t\tthis.printVal = this.localStartVal - this.easingFn(progress, 0, this.localStartVal - this.endVal, this.localDuration);\r\n\t\t\t\t} else {\r\n\t\t\t\t\tthis.printVal = this.easingFn(progress, this.localStartVal, this.endVal - this.localStartVal, this.localDuration);\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\tif (this.countDown) {\r\n\t\t\t\t\tthis.printVal = this.localStartVal - (this.localStartVal - this.endVal) * (progress / this.localDuration);\r\n\t\t\t\t} else {\r\n\t\t\t\t\tthis.printVal = this.localStartVal + (this.endVal - this.localStartVal) * (progress / this.localDuration);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tif (this.countDown) {\r\n\t\t\t\tthis.printVal = this.printVal < this.endVal ? this.endVal : this.printVal;\r\n\t\t\t} else {\r\n\t\t\t\tthis.printVal = this.printVal > this.endVal ? this.endVal : this.printVal;\r\n\t\t\t}\r\n\t\t\tthis.displayValue = this.formatNumber(this.printVal) || 0;\r\n\t\t\tif (progress < this.localDuration) {\r\n\t\t\t\tthis.rAF = this.requestAnimationFrame(this.count);\r\n\t\t\t} else {\r\n\t\t\t\tthis.$emit('end');\r\n\t\t\t}\r\n\t\t},\r\n\t\t// 判断是否数字\r\n\t\tisNumber(val) {\r\n\t\t\treturn !isNaN(parseFloat(val));\r\n\t\t},\r\n\t\tformatNumber(num) {\r\n\t\t\t// 将num转为Number类型，因为其值可能为字符串数值，调用toFixed会报错\r\n\t\t\tnum = Number(num);\r\n\t\t\tnum = num.toFixed(Number(this.decimals));\r\n\t\t\tnum += '';\r\n\t\t\tconst x = num.split('.');\r\n\t\t\tlet x1 = x[0];\r\n\t\t\tconst x2 = x.length > 1 ? `${this.decimal}${x[1]}` : '';\r\n\t\t\tconst rgx = /(\\d+)(\\d{3})/;\r\n\t\t\tif (this.separator && !this.isNumber(this.separator)) {\r\n\t\t\t\twhile (rgx.test(x1)) {\r\n\t\t\t\t\tx1 = x1.replace(rgx, '$1' + this.separator + '$2');\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\treturn x1 + x2;\r\n\t\t},\r\n\t\tdestroyed() {\r\n\t\t\tthis.cancelAnimationFrame(this.rAF);\r\n\t\t}\r\n\t}\r\n};\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\r\n.uv-count-num {\r\n\t/* #ifndef APP-NVUE */\r\n\tdisplay: inline-flex;\r\n\t/* #endif */\r\n\ttext-align: center;\r\n}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-count-to/package.json",
    "content": "{\r\n  \"id\": \"uv-count-to\",\r\n  \"displayName\": \"uv-count-to 数字滚动 全面兼容小程序、nvue、vue2、vue3等多端\",\r\n  \"version\": \"1.0.4\",\r\n  \"description\": \"该数字滚动组件一般用于需要滚动数字到某一个值的场景，目标要求是一个递增的值，一种数字上升的视觉冲击效果。\",\r\n  \"keywords\": [\r\n    \"countTo\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"数字滚动\",\r\n    \"数字变化\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-count-to/readme.md",
    "content": "## CountTo 数字滚动\r\n\r\n> **组件名：uv-count-to**\r\n\r\n该组件一般用于需要滚动数字到某一个值的场景，目标要求是一个递增的值。\r\n\r\n### <a href=\"https://www.uvui.cn/components/countTo.html\" target=\"_blank\">查看文档</a>\r\n\r\n### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n#### 如使用过程中有任何问题，或者您对uv-ui有一些好的建议，欢迎加入 uv-ui 交流群：<a href=\"https://ext.dcloud.net.cn/plugin?id=12287\" target=\"_blank\">uv-ui</a>、<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-datetime-picker/changelog.md",
    "content": "## 1.0.11（2023-10-11）\n1. 修复设置minDate出现选择错乱的BUG\n## 1.0.10（2023-09-01）\n1. 增加clearDate参数，是否清除上次选择，默认false\n## 1.0.9（2023-08-31）\n1. 增加mode=\"year\"，方便只选择年\n## 1.0.8（2023-07-17）\r\n1. 优化文档\r\n2. 优化其他\r\n## 1.0.7（2023-07-13）\r\n1. 修复 uv-datetime-picker 设置value属性不生效的BUG \r\n## 1.0.6（2023-07-05）\r\n修复vue3模式下，动态修改v-model绑定的值无效的BUG\r\n## 1.0.5（2023-07-02）\r\nuv-datetime-picker  由于弹出层uv-popup的修改，打开和关闭方法更改，详情参考文档：https://www.uvui.cn/components/datetimePicker.html\r\n## 1.0.4（2023-06-29）\r\n1. 修复抖音小程序报错的BUG\r\n## 1.0.3（2023-06-07）\r\n1.  取消defaultIndex参数，传该值没实际意义，后续更新文档\r\n## 1.0.2（2023-06-02）\r\n1. 修复v-model重新赋值不更新的BUG\r\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-datetime-picker 时间选择器\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-datetime-picker/components/uv-datetime-picker/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\tvalue: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\tmodelValue: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 是否打开组件\r\n\t\tshow: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否展示顶部的操作栏\r\n\t\tshowToolbar: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 顶部标题\r\n\t\ttitle: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 展示格式，mode=date为日期选择，mode=time为时间选择，mode=year-month为年月选择，mode=datetime为日期时间选择\r\n\t\tmode: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'datetime'\r\n\t\t},\r\n\t\t// 可选的最大时间\r\n\t\tmaxDate: {\r\n\t\t\ttype: Number,\r\n\t\t\t// 最大默认值为后10年\r\n\t\t\tdefault: new Date(new Date().getFullYear() + 10, 0, 1).getTime()\r\n\t\t},\r\n\t\t// 可选的最小时间\r\n\t\tminDate: {\r\n\t\t\ttype: Number,\r\n\t\t\t// 最小默认值为前10年\r\n\t\t\tdefault: new Date(new Date().getFullYear() - 10, 0, 1).getTime()\r\n\t\t},\r\n\t\t// 可选的最小小时，仅mode=time有效\r\n\t\tminHour: {\r\n\t\t\ttype: Number,\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 可选的最大小时，仅mode=time有效\r\n\t\tmaxHour: {\r\n\t\t\ttype: Number,\r\n\t\t\tdefault: 23\r\n\t\t},\r\n\t\t// 可选的最小分钟，仅mode=time有效\r\n\t\tminMinute: {\r\n\t\t\ttype: Number,\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 可选的最大分钟，仅mode=time有效\r\n\t\tmaxMinute: {\r\n\t\t\ttype: Number,\r\n\t\t\tdefault: 59\r\n\t\t},\r\n\t\t// 选项过滤函数\r\n\t\tfilter: {\r\n\t\t\ttype: [Function, null],\r\n\t\t\tdefault: null\r\n\t\t},\r\n\t\t// 选项格式化函数\r\n\t\tformatter: {\r\n\t\t\ttype: [Function, null],\r\n\t\t\tdefault: null\r\n\t\t},\r\n\t\t// 是否显示加载中状态\r\n\t\tloading: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 各列中，单个选项的高度\r\n\t\titemHeight: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 44\r\n\t\t},\r\n\t\t// 取消按钮的文字\r\n\t\tcancelText: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '取消'\r\n\t\t},\r\n\t\t// 确认按钮的文字\r\n\t\tconfirmText: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '确认'\r\n\t\t},\r\n\t\t// 取消按钮的颜色\r\n\t\tcancelColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#909193'\r\n\t\t},\r\n\t\t// 确认按钮的颜色\r\n\t\tconfirmColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#3c9cff'\r\n\t\t},\r\n\t\t// 每列中可见选项的数量\r\n\t\tvisibleItemCount: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 5\r\n\t\t},\r\n\t\t// 是否允许点击遮罩关闭选择器\r\n\t\tcloseOnClickOverlay: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 是否允许点击确认关闭选择器\r\n\t\tcloseOnClickConfirm: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 是否清空上次选择内容\r\n\t\tclearDate: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t...uni.$uv?.props?.datetimePicker\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-datetime-picker/components/uv-datetime-picker/uv-datetime-picker.vue",
    "content": "<template>\r\n\t<uv-picker\r\n\t\tref=\"picker\"\r\n\t\t:closeOnClickOverlay=\"closeOnClickOverlay\"\r\n\t\t:closeOnClickConfirm=\"closeOnClickConfirm\"\r\n\t\t:columns=\"columns\"\r\n\t\t:title=\"title\"\r\n\t\t:itemHeight=\"itemHeight\"\r\n\t\t:showToolbar=\"showToolbar\"\r\n\t\t:visibleItemCount=\"visibleItemCount\"\r\n\t\t:defaultIndex=\"innerDefaultIndex\"\r\n\t\t:cancelText=\"cancelText\"\r\n\t\t:confirmText=\"confirmText\"\r\n\t\t:cancelColor=\"cancelColor\"\r\n\t\t:confirmColor=\"confirmColor\"\r\n\t\t@close=\"close\"\r\n\t\t@cancel=\"cancel\"\r\n\t\t@confirm=\"confirm\"\r\n\t\t@change=\"change\"\r\n\t>\r\n\t</uv-picker>\r\n</template>\r\n<script>\r\n\tfunction times(n, iteratee) {\r\n\t\tlet index = -1\r\n\t\tconst result = Array(n < 0 ? 0 : n)\r\n\t\twhile (++index < n) {\r\n\t\t\tresult[index] = iteratee(index)\r\n\t\t}\r\n\t\treturn result\r\n\t}\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\timport dayjs from '@/uni_modules/uv-ui-tools/libs/util/dayjs.js'\r\n\t/**\r\n\t * DatetimePicker 时间日期选择器\r\n\t * @description 此选择器用于时间日期\r\n\t * @tutorial https://www.uvui.cn/components/datetimePicker.html\r\n\t * @property {Boolean}\t\t\tshowToolbar\t\t\t是否显示顶部的操作栏  ( 默认 true )\r\n\t * @property {String | Number}\tvalue\t\t\t\t绑定值\r\n\t * @property {String}\t\t\ttitle\t\t\t\t顶部标题\r\n\t * @property {String}\t\t\tmode\t\t\t\t展示格式 mode=date为日期选择，mode=time为时间选择，mode=year-month为年月选择，mode=datetime为日期时间选择  ( 默认 ‘datetime )\r\n\t * @property {Number}\t\t\tmaxDate\t\t\t\t可选的最大时间  默认值为后10年\r\n\t * @property {Number}\t\t\tminDate\t\t\t\t可选的最小时间  默认值为前10年\r\n\t * @property {Number}\t\t\tminHour\t\t\t\t可选的最小小时，仅mode=time有效   ( 默认 0 )\r\n\t * @property {Number}\t\t\tmaxHour\t\t\t\t可选的最大小时，仅mode=time有效\t  ( 默认 23 )\r\n\t * @property {Number}\t\t\tminMinute\t\t\t可选的最小分钟，仅mode=time有效\t  ( 默认 0 )\r\n\t * @property {Number}\t\t\tmaxMinute\t\t\t可选的最大分钟，仅mode=time有效   ( 默认 59 )\r\n\t * @property {Function}\t\t\tfilter\t\t\t\t选项过滤函数\r\n\t * @property {Function}\t\t\tformatter\t\t\t选项格式化函数\r\n\t * @property {Boolean}\t\t\tloading\t\t\t\t是否显示加载中状态   ( 默认 false )\r\n\t * @property {String | Number}\titemHeight\t\t\t各列中，单个选项的高度   ( 默认 44 )\r\n\t * @property {String}\t\t\tcancelText\t\t\t取消按钮的文字  ( 默认 '取消' )\r\n\t * @property {String}\t\t\tconfirmText\t\t\t确认按钮的文字  ( 默认 '确认' )\r\n\t * @property {String}\t\t\tcancelColor\t\t\t取消按钮的颜色  ( 默认 '#909193' )\r\n\t * @property {String}\t\t\tconfirmColor\t\t确认按钮的颜色  ( 默认 '#3c9cff' )\r\n\t * @property {String | Number}\tvisibleItemCount\t每列中可见选项的数量  ( 默认 5 )\r\n\t * @property {Boolean}\t\t\tcloseOnClickOverlay\t是否允许点击遮罩关闭选择器  ( 默认 true )\r\n\t * @event {Function} close 关闭选择器时触发\r\n\t * @event {Function} confirm 点击确定按钮，返回当前选择的值\r\n\t * @event {Function} change 当选择值变化时触发\r\n\t * @event {Function} cancel 点击取消按钮\r\n\t * @example  <uv-datetime-picker ref=\"datetimepicker\" :value=\"value1\"  mode=\"datetime\" ></uv-datetime-picker>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-datetime-picker',\r\n\t\temits: ['close', 'cancel', 'confirm', 'input', 'change', 'update:modelValue'],\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tcolumns: [],\r\n\t\t\t\tinnerDefaultIndex: [],\r\n\t\t\t\tinnerFormatter: (type, value) => value\r\n\t\t\t}\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\tpropsChange() {\r\n\t\t\t\tthis.init()\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\t// 如果以下这些变量发生了变化，意味着需要重新初始化各列的值\r\n\t\t\tpropsChange() {\r\n\t\t\t\tconst propsValue = this.value || this.modelValue;\r\n\t\t\t\treturn [this.mode, this.maxDate, this.minDate, this.minHour, this.maxHour, this.minMinute, this.maxMinute, this.filter, propsValue]\r\n\t\t\t}\r\n\t\t},\r\n\t\tmounted() {\r\n\t\t\tthis.init()\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tinit() {\r\n\t\t\t\tthis.getValue();\r\n\t\t\t\tthis.updateColumnValue(this.innerValue)\r\n\t\t\t},\r\n\t\t\tgetValue() {\r\n\t\t\t\tconst propsValue = this.value || this.modelValue;\r\n\t\t\t\tthis.innerValue = this.correctValue(propsValue)\r\n\t\t\t},\r\n\t\t\t// 在微信小程序中，不支持将函数当做props参数，故只能通过ref形式调用\r\n\t\t\tsetFormatter(e) {\r\n\t\t\t\tthis.innerFormatter = e\r\n\t\t\t},\r\n\t\t\topen() {\r\n\t\t\t\tthis.$refs.picker.open();\r\n\t\t\t\tthis.getValue();\r\n\t\t\t\tthis.updateColumnValue(this.innerValue);\r\n\t\t\t},\r\n\t\t\tclose() {\r\n\t\t\t\tthis.$emit('close');\r\n\t\t\t},\r\n\t\t\t// 点击工具栏的取消按钮\r\n\t\t\tcancel() {\r\n\t\t\t\tthis.$emit('cancel')\r\n\t\t\t},\r\n\t\t\t// 点击工具栏的确定按钮\r\n\t\t\tconfirm() {\r\n\t\t\t\tthis.$emit('confirm', {\r\n\t\t\t\t\tvalue: this.innerValue,\r\n\t\t\t\t\tmode: this.mode\r\n\t\t\t\t})\r\n\t\t\t\tif(!this.clearDate) {\r\n\t\t\t\t\tthis.$emit('input', this.innerValue)\r\n\t\t\t\t\tthis.$emit('update:modelValue', this.innerValue)\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t//用正则截取输出值,当出现多组数字时,抛出错误\r\n\t\t\tintercept(e, type) {\r\n\t\t\t\tlet judge = e.match(/\\d+/g)\r\n\t\t\t\t//判断是否掺杂数字\r\n\t\t\t\tif (judge.length > 1) {\r\n\t\t\t\t\tthis.$uv.error(\"请勿在过滤或格式化函数时添加数字\")\r\n\t\t\t\t\treturn 0\r\n\t\t\t\t} else if (type && judge[0].length == 4) { //判断是否是年份\r\n\t\t\t\t\treturn judge[0]\r\n\t\t\t\t} else if (judge[0].length > 2) {\r\n\t\t\t\t\tthis.$uv.error(\"请勿在过滤或格式化函数时添加数字\")\r\n\t\t\t\t\treturn 0\r\n\t\t\t\t} else {\r\n\t\t\t\t\treturn judge[0]\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t// 列发生变化时触发\r\n\t\t\tchange(e) {\r\n\t\t\t\tconst { indexs, values } = e\r\n\t\t\t\tlet selectValue = ''\r\n\t\t\t\tif (this.mode === 'time') {\r\n\t\t\t\t\t// 根据value各列索引，从各列数组中，取出当前时间的选中值\r\n\t\t\t\t\tselectValue = `${this.intercept(values[0][indexs[0]])}:${this.intercept(values[1][indexs[1]])}`\r\n\t\t\t\t} else if (this.mode === 'year') {\r\n\t\t\t\t\tconst year = parseInt(this.intercept(values[0][indexs[0]], 'year'));\r\n\t\t\t\t\tselectValue = Number(new Date(year, 0));\r\n\t\t\t\t} else {\r\n\t\t\t\t\t// 将选择的值转为数值，比如'03'转为数值的3，'2019'转为数值的2019\r\n\t\t\t\t\tconst year = parseInt(this.intercept(values[0][indexs[0]], 'year'))\r\n\t\t\t\t\tconst month = parseInt(this.intercept(values[1][indexs[1]]))\r\n\t\t\t\t\tlet date = parseInt(values[2] ? this.intercept(values[2][indexs[2]]) : 1)\r\n\t\t\t\t\tlet hour = 0,\r\n\t\t\t\t\t\tminute = 0\r\n\t\t\t\t\t// 此月份的最大天数\r\n\t\t\t\t\tconst maxDate = dayjs(`${year}-${month}`).daysInMonth()\r\n\t\t\t\t\t// year-month模式下，date不会出现在列中，设置为1，为了符合后边需要减1的需求\r\n\t\t\t\t\tif (this.mode === 'year-month') {\r\n\t\t\t\t\t\tdate = 1\r\n\t\t\t\t\t}\r\n\t\t\t\t\t// 不允许超过maxDate值\r\n\t\t\t\t\tdate = Math.min(maxDate, date)\r\n\t\t\t\t\tif (this.mode === 'datetime') {\r\n\t\t\t\t\t\thour = parseInt(this.intercept(values[3][indexs[3]]))\r\n\t\t\t\t\t\tminute = parseInt(this.intercept(values[4][indexs[4]]))\r\n\t\t\t\t\t}\r\n\t\t\t\t\t// 转为时间模式\r\n\t\t\t\t\tselectValue = Number(new Date(year, month - 1, date, hour, minute))\r\n\t\t\t\t}\r\n\t\t\t\t// 取出准确的合法值，防止超越边界的情况\r\n\t\t\t\tselectValue = this.correctValue(selectValue)\r\n\t\t\t\tthis.innerValue = selectValue\r\n\t\t\t\tthis.updateColumnValue(selectValue)\r\n\t\t\t\t// 发出change时间，value为当前选中的时间戳\r\n\t\t\t\tthis.$emit('change', {\r\n\t\t\t\t\tvalue: selectValue,\r\n\t\t\t\t\tmode: this.mode\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\t// 更新各列的值，进行补0、格式化等操作\r\n\t\t\tupdateColumnValue(value) {\r\n\t\t\t\tthis.innerValue = value\r\n\t\t\t\tthis.updateColumns()\r\n\t\t\t\tthis.updateIndexs(value)\r\n\t\t\t},\r\n\t\t\t// 更新索引\r\n\t\t\tupdateIndexs(value) {\r\n\t\t\t\tlet values = []\r\n\t\t\t\tconst formatter = this.formatter || this.innerFormatter;\r\n\t\t\t\tif (this.mode === 'time') {\r\n\t\t\t\t\t// 将time模式的时间用:分隔成数组\r\n\t\t\t\t\tconst timeArr = value.split(':')\r\n\t\t\t\t\t// 使用formatter格式化方法进行管道处理\r\n\t\t\t\t\tvalues = [formatter('hour', timeArr[0]), formatter('minute', timeArr[1])]\r\n\t\t\t\t} else {\r\n\t\t\t\t\tconst date = new Date(value)\r\n\t\t\t\t\tvalues = [\r\n\t\t\t\t\t\tformatter('year', `${dayjs(value).year()}`),\r\n\t\t\t\t\t\t// 月份补0\r\n\t\t\t\t\t\tformatter('month', this.$uv.padZero(dayjs(value).month() + 1))\r\n\t\t\t\t\t]\r\n\t\t\t\t\tif (this.mode === 'date') {\r\n\t\t\t\t\t\t// date模式，需要添加天列\r\n\t\t\t\t\t\tvalues.push(formatter('day', this.$uv.padZero(dayjs(value).date())))\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (this.mode === 'datetime') {\r\n\t\t\t\t\t\t// 数组的push方法，可以写入多个参数\r\n\t\t\t\t\t\tvalues.push(formatter('day', this.$uv.padZero(dayjs(value).date())), formatter('hour', this.$uv.padZero(dayjs(value).hour())), formatter('minute', this.$uv.padZero(dayjs(value).minute())))\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t// 根据当前各列的所有值，从各列默认值中找到默认值在各列中的索引\r\n\t\t\t\tconst indexs = this.columns.map((column, index) => {\r\n\t\t\t\t\t// 通过取大值，可以保证不会出现找不到索引的-1情况\r\n\t\t\t\t\treturn Math.max(0, column.findIndex(item => item === values[index]))\r\n\t\t\t\t})\r\n\t\t\t\tthis.$nextTick(()=>{\r\n\t\t\t\t\tthis.$uv.sleep(100).then(res=>{\r\n\t\t\t\t\t\tthis.$refs.picker.setIndexs(indexs,true);\r\n\t\t\t\t\t})\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\t// 更新各列的值\r\n\t\t\tupdateColumns() {\r\n\t\t\t\tconst formatter = this.formatter || this.innerFormatter\r\n\t\t\t\t// 获取各列的值，并且map后，对各列的具体值进行补0操作\r\n\t\t\t\tconst results = this.getOriginColumns().map((column) => column.values.map((value) => formatter(column.type, value)))\r\n\t\t\t\tthis.columns = results\r\n\t\t\t},\r\n\t\t\tgetOriginColumns() {\r\n\t\t\t\t// 生成各列的值\r\n\t\t\t\tconst results = this.getRanges().map(({ type, range }) => {\r\n\t\t\t\t\tlet values = times(range[1] - range[0] + 1, (index) => {\r\n\t\t\t\t\t\tlet value = range[0] + index\r\n\t\t\t\t\t\tvalue = type === 'year' ? `${value}` : this.$uv.padZero(value)\r\n\t\t\t\t\t\treturn value\r\n\t\t\t\t\t})\r\n\t\t\t\t\t// 进行过滤\r\n\t\t\t\t\tif (this.filter) {\r\n\t\t\t\t\t\tvalues = this.filter(type, values)\r\n\t\t\t\t\t}\r\n\t\t\t\t\treturn { type, values }\r\n\t\t\t\t})\r\n\t\t\t\treturn results\r\n\t\t\t},\r\n\t\t\t// 通过最大值和最小值生成数组\r\n\t\t\tgenerateArray(start, end) {\r\n\t\t\t\treturn Array.from(new Array(end + 1).keys()).slice(start)\r\n\t\t\t},\r\n\t\t\t// 得出合法的时间\r\n\t\t\tcorrectValue(value) {\r\n\t\t\t\tconst isDateMode = this.mode !== 'time'\r\n\t\t\t\tif (isDateMode && !this.$uv.test.date(value)) {\r\n\t\t\t\t\t// 如果是日期类型，但是又没有设置合法的当前时间的话，使用最小时间为当前时间\r\n\t\t\t\t\tvalue = this.minDate\r\n\t\t\t\t} else if (!isDateMode && !value) {\r\n\t\t\t\t\t// 如果是时间类型，而又没有默认值的话，就用最小时间\r\n\t\t\t\t\tvalue = `${this.$uv.padZero(this.minHour)}:${this.$uv.padZero(this.minMinute)}`\r\n\t\t\t\t}\r\n\t\t\t\t// 时间类型\r\n\t\t\t\tif (!isDateMode) {\r\n\t\t\t\t\tif (String(value).indexOf(':') === -1) return this.$uv.error('时间错误，请传递如12:24的格式')\r\n\t\t\t\t\tlet [hour, minute] = value.split(':')\r\n\t\t\t\t\t// 对时间补零，同时控制在最小值和最大值之间\r\n\t\t\t\t\thour = this.$uv.padZero(this.$uv.range(this.minHour, this.maxHour, Number(hour)))\r\n\t\t\t\t\tminute = this.$uv.padZero(this.$uv.range(this.minMinute, this.maxMinute, Number(minute)))\r\n\t\t\t\t\treturn `${ hour }:${ minute }`\r\n\t\t\t\t} else {\r\n\t\t\t\t\t// 如果是日期格式，控制在最小日期和最大日期之间\r\n\t\t\t\t\tvalue = dayjs(value).isBefore(dayjs(this.minDate)) ? this.minDate : value\r\n\t\t\t\t\tvalue = dayjs(value).isAfter(dayjs(this.maxDate)) ? this.maxDate : value\r\n\t\t\t\t\treturn value\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t// 获取每列的最大和最小值\r\n\t\t\tgetRanges() {\r\n\t\t\t\tif (this.mode === 'time') {\r\n\t\t\t\t\treturn [{\r\n\t\t\t\t\t\ttype: 'hour',\r\n\t\t\t\t\t\trange: [this.minHour, this.maxHour],\r\n\t\t\t\t\t}, {\r\n\t\t\t\t\t\ttype: 'minute',\r\n\t\t\t\t\t\trange: [this.minMinute, this.maxMinute],\r\n\t\t\t\t\t}, ];\r\n\t\t\t\t}\r\n\t\t\t\tconst { maxYear, maxDate, maxMonth, maxHour, maxMinute, } = this.getBoundary('max', this.innerValue);\r\n\t\t\t\tconst { minYear, minDate, minMonth, minHour, minMinute, } = this.getBoundary('min', this.innerValue);\r\n\t\t\t\tconst result = [{\r\n\t\t\t\t\ttype: 'year',\r\n\t\t\t\t\trange: [minYear, maxYear],\r\n\t\t\t\t}, {\r\n\t\t\t\t\ttype: 'month',\r\n\t\t\t\t\trange: [minMonth, maxMonth],\r\n\t\t\t\t}, {\r\n\t\t\t\t\ttype: 'day',\r\n\t\t\t\t\trange: [minDate, maxDate],\r\n\t\t\t\t}, {\r\n\t\t\t\t\ttype: 'hour',\r\n\t\t\t\t\trange: [minHour, maxHour],\r\n\t\t\t\t}, {\r\n\t\t\t\t\ttype: 'minute',\r\n\t\t\t\t\trange: [minMinute, maxMinute],\r\n\t\t\t\t}, ];\r\n\t\t\t\tif (this.mode === 'date') result.splice(3, 2);\r\n\t\t\t\tif (this.mode === 'year-month') result.splice(2, 3);\r\n\t\t\t\tif (this.mode === 'year') result.splice(1, 4);\r\n\t\t\t\treturn result;\r\n\t\t\t},\r\n\t\t\t// 根据minDate、maxDate、minHour、maxHour等边界值，判断各列的开始和结束边界值\r\n\t\t\tgetBoundary(type, innerValue) {\r\n\t\t\t\tconst value = new Date(innerValue)\r\n\t\t\t\tconst boundary = new Date(this[`${type}Date`])\r\n\t\t\t\tconst year = dayjs(boundary).year()\r\n\t\t\t\tlet month = 1\r\n\t\t\t\tlet date = 1\r\n\t\t\t\tlet hour = 0\r\n\t\t\t\tlet minute = 0\r\n\t\t\t\tif (type === 'max') {\r\n\t\t\t\t\tmonth = 12\r\n\t\t\t\t\t// 月份的天数\r\n\t\t\t\t\tdate = dayjs(value).daysInMonth()\r\n\t\t\t\t\thour = 23\r\n\t\t\t\t\tminute = 59\r\n\t\t\t\t}\r\n\t\t\t\t// 获取边界值，逻辑是：当年达到了边界值(最大或最小年)，就检查月允许的最大和最小值，以此类推\r\n\t\t\t\tif (dayjs(value).year() === year) {\r\n\t\t\t\t\tmonth = dayjs(boundary).month() + 1\r\n\t\t\t\t\tif (dayjs(value).month() + 1 === month) {\r\n\t\t\t\t\t\tdate = dayjs(boundary).date()\r\n\t\t\t\t\t\tif (dayjs(value).date() === date) {\r\n\t\t\t\t\t\t\thour = dayjs(boundary).hour()\r\n\t\t\t\t\t\t\tif (dayjs(value).hour() === hour) {\r\n\t\t\t\t\t\t\t\tminute = dayjs(boundary).minute()\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\treturn {\r\n\t\t\t\t\t[`${type}Year`]: year,\r\n\t\t\t\t\t[`${type}Month`]: month,\r\n\t\t\t\t\t[`${type}Date`]: date,\r\n\t\t\t\t\t[`${type}Hour`]: hour,\r\n\t\t\t\t\t[`${type}Minute`]: minute\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t},\r\n\t}\r\n</script>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-datetime-picker/package.json",
    "content": "{\r\n  \"id\": \"uv-datetime-picker\",\r\n  \"displayName\": \"uv-datetime-picker 时间选择器 全面兼容vue3+2、app、h5、小程序等多端\",\r\n  \"version\": \"1.0.11\",\r\n  \"description\": \"时间选择器组件用于时间日期，主要用于年月日时分的选择，具体选择的精确度由参数控制。\",\r\n  \"keywords\": [\r\n    \"datetime-picker\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"datetime\",\r\n    \"时间选择\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-picker\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-datetime-picker/readme.md",
    "content": "## DatetimePicker 时间选择器\r\n\r\n> **组件名：uv-datetime-picker**\r\n\r\n此选择器用于时间日期，主要用于年月日时分的选择，具体选择的精确度由参数控制。\r\n\r\n# <a href=\"https://www.uvui.cn/components/datetimePicker.html\" target=\"_blank\">查看文档</a>\r\n\r\n## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>（请不要 下载插件ZIP）</small>\r\n\r\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n<a href=\"https://ext.dcloud.net.cn/plugin?name=uv-ui\" target=\"_blank\">\r\n\r\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\r\n\r\n</a>\r\n\r\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-divider/changelog.md",
    "content": "## 1.0.2（2023-06-01）\n1. 修复点击触发两次事件的BUG\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-divider 分割线\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-divider/components/uv-divider/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 是否虚线\r\n\t\tdashed: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否细线\r\n\t\thairline: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 是否以点替代文字，优先于text字段起作用\r\n\t\tdot: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 内容文本的位置，left-左边，center-中间，right-右边\r\n\t\ttextPosition: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'center'\r\n\t\t},\r\n\t\t// 文本内容\r\n\t\ttext: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 文本大小\r\n\t\ttextSize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 14\r\n\t\t},\r\n\t\t// 文本颜色\r\n\t\ttextColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#909399'\r\n\t\t},\r\n\t\t// 线条颜色\r\n\t\tlineColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#dcdfe6'\r\n\t\t},\r\n\t\t...uni.$uv?.props?.divider\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-divider/components/uv-divider/uv-divider.vue",
    "content": "<template>\r\n\t<view \r\n\t\tclass=\"uv-divider\"\r\n\t\t:style=\"[$uv.addStyle(customStyle)]\"\r\n\t\t@tap=\"click\"\r\n\t\t>\r\n\t\t<uv-line \r\n\t\t\t:color=\"lineColor\"\r\n\t\t\t:customStyle=\"leftLineStyle\"\r\n\t\t\t:hairline=\"hairline\"\r\n\t\t\t:dashed=\"dashed\"></uv-line>\r\n\t\t<text \r\n\t\t\tv-if=\"dot\"\r\n\t\t\tclass=\"uv-divider__dot\"\r\n\t\t>●</text>\r\n\t\t<text \r\n\t\t\tv-else-if=\"text\"\r\n\t\t\tclass=\"uv-divider__text\"\r\n\t\t\t:style=\"[textStyle]\"\r\n\t\t>{{text}}</text>\r\n\t\t<uv-line \r\n\t\t\t:color=\"lineColor\"\r\n\t\t\t:customStyle=\"rightLineStyle\"\r\n\t\t\t:hairline=\"hairline\"\r\n\t\t\t:dashed=\"dashed\"\r\n\t\t></uv-line>\r\n\t</view>\r\n</template>\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * divider 分割线\r\n\t * @description 区隔内容的分割线，一般用于页面底部\"没有更多\"的提示。\r\n\t * @tutorial https://www.uvui.cn/components/divider.html\r\n\t * @property {Boolean}\t\t\tdashed\t\t\t是否虚线 （默认 false ）\r\n\t * @property {Boolean}\t\t\thairline\t\t是否细线 （默认  true ）\r\n\t * @property {Boolean}\t\t\tdot\t\t\t\t是否以点替代文字，优先于text字段起作用 （默认 false ）\r\n\t * @property {String}\t\t\ttextPosition\t内容文本的位置，left-左边，center-中间，right-右边 （默认 'center' ）\r\n\t * @property {String | Number}\ttext\t\t\t文本内容\r\n\t * @property {String | Number}\ttextSize\t\t文本大小 （默认 14）\r\n\t * @property {String}\t\t\ttextColor\t\t文本颜色 （默认 '#909399' ）\r\n\t * @property {String}\t\t\tlineColor\t\t线条颜色 （默认 '#dcdfe6' ）\r\n\t * @property {Object}\t\t\tcustomStyle\t\t定义需要用到的外部样式\r\n\t *\r\n\t * @event {Function}\tclick\tdivider组件被点击时触发\r\n\t * @example <uv-divider :color=\"color\">锦瑟无端五十弦</uv-divider>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-divider',\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\temits: ['click'],\r\n\t\tcomputed: {\r\n\t\t\ttextStyle() {\r\n\t\t\t\tconst style = {}\r\n\t\t\t\tstyle.fontSize = this.$uv.addUnit(this.textSize)\r\n\t\t\t\tstyle.color = this.textColor\r\n\t\t\t\treturn style\r\n\t\t\t},\r\n\t\t\t// 左边线条的的样式\r\n\t\t\tleftLineStyle() {\r\n\t\t\t\tconst style = {}\r\n\t\t\t\t// 如果是在左边，设置左边的宽度为固定值\r\n\t\t\t\tif (this.textPosition === 'left') {\r\n\t\t\t\t\tstyle.width = '80rpx'\r\n\t\t\t\t} else {\r\n\t\t\t\t\tstyle.flex = 1\r\n\t\t\t\t}\r\n\t\t\t\treturn style\r\n\t\t\t},\r\n\t\t\t// 右边线条的的样式\r\n\t\t\trightLineStyle() {\r\n\t\t\t\tconst style = {}\r\n\t\t\t\t// 如果是在右边，设置右边的宽度为固定值\r\n\t\t\t\tif (this.textPosition === 'right') {\r\n\t\t\t\t\tstyle.width = '80rpx'\r\n\t\t\t\t} else {\r\n\t\t\t\t\tstyle.flex = 1\r\n\t\t\t\t}\r\n\t\t\t\treturn style\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\t// divider组件被点击时触发\r\n\t\t\tclick() {\r\n\t\t\t\tthis.$emit('click');\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t$uv-divider-margin: 15px 0 !default;\r\n\t$uv-divider-text-margin: 0 15px !default;\r\n\t$uv-divider-dot-font-size: 12px !default;\r\n\t$uv-divider-dot-margin: 0 12px !default;\r\n\t$uv-divider-dot-color: #c0c4cc !default;\r\n\t.uv-divider {\r\n\t\t@include flex;\r\n\t\tflex-direction: row;\r\n\t\talign-items: center;\r\n\t\tmargin: $uv-divider-margin;\r\n\t\t&__text {\r\n\t\t\tmargin: $uv-divider-text-margin;\r\n\t\t}\r\n\t\t&__dot {\r\n\t\t\tfont-size: $uv-divider-dot-font-size;\r\n\t\t\tmargin: $uv-divider-dot-margin;\r\n\t\t\tcolor: $uv-divider-dot-color;\r\n\t\t}\r\n\t}\r\n</style>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-divider/package.json",
    "content": "{\r\n  \"id\": \"uv-divider\",\r\n  \"displayName\": \"uv-divider 分割线 全面兼容小程序、nvue、vue2、vue3等多端\",\r\n  \"version\": \"1.0.2\",\r\n  \"description\": \"区隔内容的分割线，一般用于页面底部没有更多的提示。\",\r\n  \"keywords\": [\r\n    \"divider\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"分割线\",\r\n    \"没有更多\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-line\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-divider/readme.md",
    "content": "## Divider 分割线\r\n\r\n> **组件名：uv-divider**\r\n\r\n区隔内容的分割线，一般用于页面底部\"没有更多\"的提示。\r\n\r\n### <a href=\"https://www.uvui.cn/components/divider.html\" target=\"_blank\">查看文档</a>\r\n\r\n### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n#### 如使用过程中有任何问题，或者您对uv-ui有一些好的建议，欢迎加入 uv-ui 交流群：<a href=\"https://ext.dcloud.net.cn/plugin?id=12287\" target=\"_blank\">uv-ui</a>、<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-drop-down/changelog.md",
    "content": "## 1.0.4（2023-09-28）\n1. 增加uv-sticky依赖\n## 1.0.3（2023-08-29）\r\n1. 修复自定义内容，点击自定义内容时会自动关闭弹窗的问题\r\n## 1.0.2（2023-08-22）\r\n1. 优化\r\n## 1.0.1（2023-08-22）\r\n1. 增加@change回调，返回弹窗关闭状态\r\n2. 增加init方法，方便位置改变进行调整\r\n## 1.0.0（2023-07-30）\r\n新增uv-drop-down 下拉筛选组件\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-drop-down/components/uv-drop-down/uv-drop-down.vue",
    "content": "<template>\r\n\t<uv-sticky :disabled=\"!isSticky\">\r\n\t\t<view ref=\"dropDownRef\" class=\"uv-drop-down\" :style=\"[$uv.addStyle(customStyle)]\">\r\n\t\t\t<slot></slot>\r\n\t\t</view>\r\n\t</uv-sticky>\r\n</template>\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js';\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js';\r\n\t// #ifdef APP-NVUE\r\n\tconst dom = uni.requireNativePlugin('dom');\r\n\t// #endif\r\n\t/**\r\n\t * DropDown 下拉框\r\n\t * @description 下拉筛选\r\n\t * @tutorial https://ext.dcloud.net.cn/plugin?name=uv-drop-down\r\n\t * @property {String | Number} sign 组件唯一标识，需要手动传\r\n\t * @property {Boolean} is-sticky = [true|false] 是否吸顶\r\n\t * @property {Array} default-value 默认值，表示参数value属于这里面的值，就说明是未选中即是默认展示的值。eg：上面示例中的{label: '全部',value: 'all'} 即是默认值。后续处理逻辑也可以根据是否是其中值进行过滤。\r\n\t * @property {String} textSize 每项字体大小\r\n\t * @property {String} textColor 每项文本颜色\r\n\t * @property {String} textActiveSize 每项选中状态字体大小\r\n\t * @property {String} textActiveColor 每项选中状态文本颜色\r\n\t * @property {Object} extraIcon 每项右侧图标\r\n\t * @property {Object} extraActiveIcon 每项选中后右侧图标\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-drop-down',\r\n\t\tmixins: [mpMixin, mixin],\r\n\t\temits: ['click'],\r\n\t\tprops: {\r\n\t\t\tisSticky: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: true\r\n\t\t\t},\r\n\t\t\tsign: {\r\n\t\t\t\ttype: [String, Number],\r\n\t\t\t\tdefault: 'UVDROPDOWN'\r\n\t\t\t},\r\n\t\t\tdefaultValue: {\r\n\t\t\t\ttype: Array,\r\n\t\t\t\tdefault: () => [0, '0', 'all']\r\n\t\t\t},\r\n\t\t\ttextSize: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: '30rpx'\r\n\t\t\t},\r\n\t\t\ttextColor: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: '#333'\r\n\t\t\t},\r\n\t\t\ttextActiveSize: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: '30rpx'\r\n\t\t\t},\r\n\t\t\ttextActiveColor: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: '#3c9cff'\r\n\t\t\t},\r\n\t\t\textraIcon: {\r\n\t\t\t\ttype: Object,\r\n\t\t\t\tdefault () {\r\n\t\t\t\t\treturn {\r\n\t\t\t\t\t\tname: 'arrow-down',\r\n\t\t\t\t\t\tsize: '30rpx',\r\n\t\t\t\t\t\tcolor: '#333'\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\textraActiveIcon: {\r\n\t\t\t\ttype: Object,\r\n\t\t\t\tdefault () {\r\n\t\t\t\t\treturn {\r\n\t\t\t\t\t\tname: 'arrow-up',\r\n\t\t\t\t\t\tsize: '30rpx',\r\n\t\t\t\t\t\tcolor: '#3c9cff'\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\tparentData() {\r\n\t\t\t\treturn [this.defaultValue, this.textSize, this.textColor, this.textActiveColor, this.textActiveSize, this.extraIcon, this.extraActiveIcon, this.sign, this.clickHandler];\r\n\t\t\t}\r\n\t\t},\r\n\t\tmounted() {\r\n\t\t\tthis.init();\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tinit() {\r\n\t\t\t\tuni.$emit(`${this.sign}_CLICKMENU`, {\r\n\t\t\t\t\tshow: false\r\n\t\t\t\t});\r\n\t\t\t\tthis.$nextTick(async () => {\r\n\t\t\t\t\tconst rect = await this.queryRect();\r\n\t\t\t\t\tuni.$emit(`${this.sign}_GETRECT`, rect);\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\t// 查询内容高度\r\n\t\t\tqueryRect() {\r\n\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\t// 组件内部一般用this.$uvGetRect，对外的为getRect，二者功能一致，名称不同\r\n\t\t\t\treturn new Promise(resolve => {\r\n\t\t\t\t\tthis.$uvGetRect(`.uv-drop-down`).then(size => {\r\n\t\t\t\t\t\tresolve(size)\r\n\t\t\t\t\t})\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\t// nvue下，使用dom模块查询元素高度\r\n\t\t\t\t// 返回一个promise，让调用此方法的主体能使用then回调\r\n\t\t\t\treturn new Promise(resolve => {\r\n\t\t\t\t\tdom.getComponentRect(this.$refs.dropDownRef, res => {\r\n\t\t\t\t\t\tres.size.top = res.size.top <= 0 ? 0 : res.size.top;\r\n\t\t\t\t\t\tresolve(res.size)\r\n\t\t\t\t\t})\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\t\t\t},\r\n\t\t\tclickHandler(data) {\r\n\t\t\t\tthis.$emit('click', data);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n<style scoped lang=\"scss\">\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t.uv-drop-down {\r\n\t\t@include flex;\r\n\t\tjustify-content: space-between;\r\n\t\tbackground-color: #fff;\r\n\t\tborder-bottom: 1px solid #dadbde;\r\n\t}\r\n</style>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-drop-down/components/uv-drop-down-item/uv-drop-down-item.vue",
    "content": "<template>\r\n\t<view class=\"uv-drop-down-item\" @click=\"clickHandler\">\r\n\t\t<uv-text :text=\"label\" :size=\"getTextStyle.size\" :color=\"getTextStyle.color\" lines=\"1\" :custom-style=\"{marginRight: '10rpx',maxWidth:'200rpx'}\"></uv-text>\r\n\t\t<uv-icon :name=\"getDownIcon.name\" :size=\"getDownIcon.size\" :color=\"getDownIcon.color\" v-if=\"[1,'1'].indexOf(type)==-1\"></uv-icon>\r\n\t</view>\r\n</template>\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js';\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js';\r\n\t/**\r\n\t * DropDown 下拉框\r\n\t * @description 下拉筛选\r\n\t * @tutorial https://ext.dcloud.net.cn/plugin?name=uv-drop-down\r\n\t * @property {String | Number} name 字段标识\r\n\t * @property {String | Number} type 类型 1 没有筛选项，直接进行选中和不选中  2 有多个选项\r\n\t * @property {String | Number} label 筛选项的文本\r\n\t * @property {Boolean} isDrop 该项是否打开\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-drop-down-item',\r\n\t\tmixins: [mpMixin, mixin],\r\n\t\temits: ['click'],\r\n\t\tprops: {\r\n\t\t\tname: {\r\n\t\t\t\ttype: [String, Number],\r\n\t\t\t\tdefault: ''\r\n\t\t\t},\r\n\t\t\t// 类型 1 没有筛选项，直接进行选中和不选中  2 有多个选项\r\n\t\t\ttype: {\r\n\t\t\t\ttype: [String, Number],\r\n\t\t\t\tdefault: '2'\r\n\t\t\t},\r\n\t\t\t// 筛选的文本\r\n\t\t\tlabel: {\r\n\t\t\t\ttype: [String],\r\n\t\t\t\tdefault: ''\r\n\t\t\t},\r\n\t\t\t// 筛选值\r\n\t\t\tvalue: {\r\n\t\t\t\ttype: [String, Number, null],\r\n\t\t\t\tdefault: ''\r\n\t\t\t},\r\n\t\t\t// 是否下拉菜单打开\r\n\t\t\tisDrop: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: false\r\n\t\t\t}\r\n\t\t},\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tparentData: {\r\n\t\t\t\t\tdefaultValue: [0, '0', 'all'],\r\n\t\t\t\t\ttextSize: '30rpx',\r\n\t\t\t\t\ttextColor: '#333',\r\n\t\t\t\t\ttextActiveSize: '30rpx',\r\n\t\t\t\t\ttextActiveColor: '#3c9cff',\r\n\t\t\t\t\textraIcon: {},\r\n\t\t\t\t\textraActiveIcon: {},\r\n\t\t\t\t\tsign: '',\r\n\t\t\t\t\tclickHandler: Function\r\n\t\t\t\t},\r\n\t\t\t\tactive: false,\r\n\t\t\t\tisDroped: false,\r\n\t\t\t\telId: ''\r\n\t\t\t}\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\tisDrop: {\r\n\t\t\t\thandler(newVal) {\r\n\t\t\t\t\tthis.isDroped = newVal;\r\n\t\t\t\t},\r\n\t\t\t\timmediate: true\r\n\t\t\t},\r\n\t\t\tvalue: {\r\n\t\t\t\thandler(newVal) {\r\n\t\t\t\t\tthis.active = this.parentData.defaultValue.indexOf(newVal) == -1;\r\n\t\t\t\t},\r\n\t\t\t\timmediate: true\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\tgetDownIcon() {\r\n\t\t\t\tconst style = {\r\n\t\t\t\t\tsize: '30rpx',\r\n\t\t\t\t\tcolor: '#333',\r\n\t\t\t\t\t...this.parentData.extraIcon\r\n\t\t\t\t}\r\n\t\t\t\tif (this.active || this.isDroped) {\r\n\t\t\t\t\tstyle.color = this.parentData.extraActiveIcon?.color ? this.parentData.extraActiveIcon?.color : '#3c9cff';\r\n\t\t\t\t\tstyle.size = this.parentData.extraActiveIcon?.size ? this.parentData.extraActiveIcon?.size : '30rpx';\r\n\t\t\t\t}\r\n\t\t\t\tif (this.isDroped) {\r\n\t\t\t\t\tstyle.name = this.parentData.extraActiveIcon?.name;\r\n\t\t\t\t}\r\n\t\t\t\treturn style;\r\n\t\t\t},\r\n\t\t\tgetTextStyle() {\r\n\t\t\t\tconst style = {\r\n\t\t\t\t\tsize: this.parentData.textSize,\r\n\t\t\t\t\tcolor: this.parentData.textColor\r\n\t\t\t\t};\r\n\t\t\t\tif (this.active || this.isDroped) {\r\n\t\t\t\t\tstyle.size = this.parentData.textActiveSize;\r\n\t\t\t\t\tstyle.color = this.parentData.textActiveColor;\r\n\t\t\t\t}\r\n\t\t\t\treturn style;\r\n\t\t\t}\r\n\t\t},\r\n\t\tcreated() {\r\n\t\t\tthis.init();\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tinit() {\r\n\t\t\t\tthis.elId = this.$uv.guid();\r\n\t\t\t\tthis.getParentData('uv-drop-down');\r\n\t\t\t\tif (!this.parent) {\r\n\t\t\t\t\tthis.$uv.error('uv-drop-down必须搭配uv-drop-down-item组件使用');\r\n\t\t\t\t}\r\n\t\t\t\tuni.$on('HANDLE_DROPDOWN_ONE', id => {\r\n\t\t\t\t\tif (this.isDroped && this.elId != id) {\r\n\t\t\t\t\t\tthis.isDroped = false;\r\n\t\t\t\t\t}\r\n\t\t\t\t})\r\n\t\t\t\tuni.$on(`${this.parentData.sign}_CLOSEPOPUP`, async () => {\r\n\t\t\t\t\tif (this.isDroped) {\r\n\t\t\t\t\t\tthis.isDroped = false;\r\n\t\t\t\t\t}\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\tasync clickHandler() {\r\n\t\t\t\tlet data = {};\r\n\t\t\t\tuni.$emit('HANDLE_DROPDOWN_ONE', this.elId);\r\n\t\t\t\tswitch (+this.type) {\r\n\t\t\t\t\tcase 1:\r\n\t\t\t\t\t\tthis.active = !this.active;\r\n\t\t\t\t\t\tdata = {\r\n\t\t\t\t\t\t\tname: this.name,\r\n\t\t\t\t\t\t\tactive: this.active,\r\n\t\t\t\t\t\t\ttype: this.type\r\n\t\t\t\t\t\t};\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase 2:\r\n\t\t\t\t\t\tthis.isDroped = !this.isDroped;\r\n\t\t\t\t\t\tdata = {\r\n\t\t\t\t\t\t\tname: this.name,\r\n\t\t\t\t\t\t\tactive: this.isDroped,\r\n\t\t\t\t\t\t\ttype: this.type\r\n\t\t\t\t\t\t};\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t\tthis.parentData.clickHandler(data);\r\n\t\t\t\tthis.$emit('click', data);\r\n\t\t\t\tuni.$emit(`${this.parentData.sign}_CLICKMENU`, {\r\n\t\t\t\t\tshow: +this.type > 1 && this.isDroped\r\n\t\t\t\t});\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n<style scoped lang=\"scss\">\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t.uv-drop-down-item {\r\n\t\t@include flex;\r\n\t\talign-items: center;\r\n\t\tpadding: 20rpx;\r\n\t}\r\n</style>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-drop-down/components/uv-drop-down-popup/uv-drop-down-popup.vue",
    "content": "<template>\r\n\t<view class=\"uv-drop-down-popup\">\r\n\t\t<uv-transition :show=\"show\" mode=\"fade\" :duration=\"300\" :custom-style=\"overlayStyle\" @click=\"clickOverlay\">\r\n\t\t\t<view class=\"uv-dp__container\" ref=\"uvDPContainer\" :style=\"{height: `${height}px`}\" @click.stop=\"blockClick\">\r\n\t\t\t\t<view class=\"uv-dp__container__list\" ref=\"uvDPList\">\r\n\t\t\t\t\t<slot>\r\n\t\t\t\t\t\t<view class=\"uv-dp__container__list--item\" v-for=\"(item,index) in list\" :key=\"index\" @click=\"clickHandler(item,index)\" :style=\"[itemCustomStyle(index)]\">\r\n\t\t\t\t\t\t\t<uv-text :text=\"item[keyName]\" :size=\"getTextSize(index)\" :color=\"getTextColor(index)\"></uv-text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</slot>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t</uv-transition>\r\n\t</view>\r\n</template>\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js';\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js';\r\n\t// #ifdef APP-NVUE\r\n\tconst animation = uni.requireNativePlugin('animation');\r\n\tconst dom = uni.requireNativePlugin('dom');\r\n\t// #endif\r\n\t/**\r\n\t * DropDownPopup 下拉框\r\n\t * @description 下拉筛选框\r\n\t * @tutorial https://ext.dcloud.net.cn/plugin?name=uv-drop-down\r\n\t * @property {String | Number} name 字段标识\r\n\t * @property {String | Number} zIndex 弹出层的层级\r\n\t * @property {String | Number} opacity 遮罩层的透明度\r\n\t * @property {Boolean} clickOverlayOnClose 是否允许点击遮罩层关闭弹窗\r\n\t * @property {Object} currentDropItem 当前下拉筛选菜单对象\r\n\t * @property {String} keyName 指定从当前下拉筛选菜单对象元素中读取哪个属性作为文本展示\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-drop-down-popup',\r\n\t\tmixins: [mpMixin, mixin],\r\n\t\tprops: {\r\n\t\t\tsign: {\r\n\t\t\t\ttype: [String, Number],\r\n\t\t\t\tdefault: 'UVDROPDOWN'\r\n\t\t\t},\r\n\t\t\tzIndex: {\r\n\t\t\t\ttype: [Number, String],\r\n\t\t\t\tdefault: 999\r\n\t\t\t},\r\n\t\t\topacity: {\r\n\t\t\t\ttype: [Number, String],\r\n\t\t\t\tdefault: 0.5\r\n\t\t\t},\r\n\t\t\tclickOverlayOnClose: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: true\r\n\t\t\t},\r\n\t\t\t// 当前下拉选项对象\r\n\t\t\tcurrentDropItem: {\r\n\t\t\t\ttype: Object,\r\n\t\t\t\tdefault () {\r\n\t\t\t\t\treturn {\r\n\t\t\t\t\t\tactiveIndex: 0,\r\n\t\t\t\t\t\tchild: []\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tkeyName: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: 'label'\r\n\t\t\t}\r\n\t\t},\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tshow: false,\r\n\t\t\t\trect: {},\r\n\t\t\t\theight: 0\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\toverlayStyle() {\r\n\t\t\t\tlet { height = 0, top = 0 } = this.rect;\r\n\t\t\t\t// #ifdef H5\r\n\t\t\t\ttop += this.$uv.sys().windowTop;\r\n\t\t\t\t// #endif\r\n\t\t\t\tconst style = {\r\n\t\t\t\t\tposition: 'fixed',\r\n\t\t\t\t\ttop: `${top+height}px`,\r\n\t\t\t\t\tleft: 0,\r\n\t\t\t\t\tright: 0,\r\n\t\t\t\t\tzIndex: this.zIndex,\r\n\t\t\t\t\tbottom: 0,\r\n\t\t\t\t\t'background-color': `rgba(0, 0, 0, ${this.opacity})`\r\n\t\t\t\t}\r\n\t\t\t\treturn this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))\r\n\t\t\t},\r\n\t\t\tlist() {\r\n\t\t\t\ttry {\r\n\t\t\t\t\treturn Array.isArray(this.currentDropItem.child) ? this.currentDropItem.child : [];\r\n\t\t\t\t} catch (e) {\r\n\t\t\t\t\treturn [];\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tgetTextColor(index) {\r\n\t\t\t\treturn index => {\r\n\t\t\t\t\tconst active = this.currentDropItem.activeIndex == index;\r\n\t\t\t\t\tconst color = this.currentDropItem.color;\r\n\t\t\t\t\tconst activeColor = this.currentDropItem.activeColor;\r\n\t\t\t\t\tif (active) {\r\n\t\t\t\t\t\treturn activeColor ? activeColor : '#3c9cff';\r\n\t\t\t\t\t}\r\n\t\t\t\t\treturn color ? color : '#333';\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tgetTextSize(index) {\r\n\t\t\t\treturn index => {\r\n\t\t\t\t\tconst active = this.currentDropItem.activeIndex == index;\r\n\t\t\t\t\tconst size = this.currentDropItem.size;\r\n\t\t\t\t\tconst activeSize = this.currentDropItem.activeSize;\r\n\t\t\t\t\tif (active) {\r\n\t\t\t\t\t\treturn activeSize ? activeSize : '30rpx';\r\n\t\t\t\t\t}\r\n\t\t\t\t\treturn size ? size : '30rpx';\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\titemCustomStyle() {\r\n\t\t\t\treturn index => {\r\n\t\t\t\t\tconst active = this.currentDropItem.activeIndex == index;\r\n\t\t\t\t\tconst style = {};\r\n\t\t\t\t\tif (active && this.currentDropItem.itemActiveCustomStyle) {\r\n\t\t\t\t\t\treturn this.$uv.deepMerge(style, this.$uv.addStyle(this.currentDropItem.itemActiveCustomStyle));\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (this.currentDropItem.itemCustomStyle) {\r\n\t\t\t\t\t\treturn this.$uv.deepMerge(style, this.$uv.addStyle(this.currentDropItem.itemCustomStyle))\r\n\t\t\t\t\t}\r\n\t\t\t\t\treturn style;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\tcreated() {\r\n\t\t\tthis.init();\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tblockClick() {},\r\n\t\t\tclickHandler(item, index) {\r\n\t\t\t\tthis.currentDropItem.activeIndex = index;\r\n\t\t\t\tthis.$emit('clickItem', item);\r\n\t\t\t\tthis.close();\r\n\t\t\t},\r\n\t\t\tinit() {\r\n\t\t\t\tuni.$off(`${this.sign}_GETRECT`);\r\n\t\t\t\tuni.$on(`${this.sign}_GETRECT`, rect => {\r\n\t\t\t\t\tthis.rect = rect;\r\n\t\t\t\t})\r\n\t\t\t\tuni.$off(`${this.sign}_CLICKMENU`);\r\n\t\t\t\tuni.$on(`${this.sign}_CLICKMENU`, async res => {\r\n\t\t\t\t\tif (res.show) {\r\n\t\t\t\t\t\tthis.open();\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tthis.close();\r\n\t\t\t\t\t}\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\topen() {\r\n\t\t\t\tthis.show = true;\r\n\t\t\t\tthis.$nextTick(async () => {\r\n\t\t\t\t\t// #ifndef H5 || MP-WEIXIN\r\n\t\t\t\t\tawait this.$uv.sleep(60);\r\n\t\t\t\t\t// #endif\r\n\t\t\t\t\tconst res = await this.queryRect();\r\n\t\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\t\tthis.height = res.height;\r\n\t\t\t\t\t// #endif\r\n\t\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\t\tthis.animation(res.height);\r\n\t\t\t\t\t// #endif\r\n\t\t\t\t\tthis.$emit('popupChange', { show: true });\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\tclose() {\r\n\t\t\t\tif(!this.show) return;\r\n\t\t\t\tthis.height = 0;\r\n\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\tthis.height = 0;\r\n\t\t\t\t// #endif\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\tthis.animation(0);\r\n\t\t\t\t// #endif\r\n\t\t\t\tthis.show = false;\r\n\t\t\t\tuni.$emit(`${this.sign}_CLOSEPOPUP`);\r\n\t\t\t\tthis.$emit('popupChange', { show: false });\r\n\t\t\t},\r\n\t\t\tclickOverlay() {\r\n\t\t\t\tif (this.clickOverlayOnClose) {\r\n\t\t\t\t\tthis.close();\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t// 查询内容高度\r\n\t\t\tqueryRect() {\r\n\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\t// 组件内部一般用this.$uvGetRect，对外的为getRect，二者功能一致，名称不同\r\n\t\t\t\treturn new Promise(resolve => {\r\n\t\t\t\t\tthis.$uvGetRect(`.uv-dp__container__list`).then(size => {\r\n\t\t\t\t\t\tresolve(size)\r\n\t\t\t\t\t})\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\t// nvue下，使用dom模块查询元素高度\r\n\t\t\t\t// 返回一个promise，让调用此方法的主体能使用then回调\r\n\t\t\t\treturn new Promise(resolve => {\r\n\t\t\t\t\tdom.getComponentRect(this.$refs.uvDPList, res => {\r\n\t\t\t\t\t\tresolve(res.size)\r\n\t\t\t\t\t})\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\t\t\t},\r\n\t\t\t// nvue下设置高度\r\n\t\t\tanimation(height, duration = 200) {\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\tconst ref = this.$refs['uvDPContainer'];\r\n\t\t\t\tanimation.transition(ref, {\r\n\t\t\t\t\tstyles: {\r\n\t\t\t\t\t\theight: `${height}px`\r\n\t\t\t\t\t},\r\n\t\t\t\t\tduration\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n<style scoped lang=\"scss\">\r\n\t.uv-dp__container {\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\toverflow: hidden;\r\n\t\ttransition: all .15s;\r\n\t\t/* #endif */\r\n\t\tbackground-color: #fff;\r\n\t}\r\n\t.uv-dp__container__list {\r\n\t\t&--item {\r\n\t\t\tpadding: 20rpx 60rpx;\r\n\t\t}\r\n\t}\r\n</style>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-drop-down/package.json",
    "content": "{\r\n\t\"id\": \"uv-drop-down\",\r\n\t\"displayName\": \"uv-drop-down 下拉筛选 全面兼容vue3+2、app、h5、小程序等多端\",\r\n\t\"version\": \"1.0.4\",\r\n\t\"description\": \"该组件主要提供筛选下拉筛选框，内置基础筛选功能，可以根据自己的需求自定义筛选项\",\r\n\t\"keywords\": [\r\n        \"uv-drop-down\",\r\n        \"uvui\",\r\n        \"uv-ui\",\r\n        \"下拉筛选\",\r\n        \"筛选\"\r\n    ],\r\n\t\"repository\": \"\",\r\n\t\"engines\": {\r\n\t\t\"HBuilderX\": \"^3.1.0\"\r\n\t},\r\n\t\"dcloudext\": {\r\n\t\t\"type\": \"component-vue\",\r\n\t\t\"sale\": {\r\n\t\t\t\"regular\": {\r\n\t\t\t\t\"price\": \"0.00\"\r\n\t\t\t},\r\n\t\t\t\"sourcecode\": {\r\n\t\t\t\t\"price\": \"0.00\"\r\n\t\t\t}\r\n\t\t},\r\n\t\t\"contact\": {\r\n\t\t\t\"qq\": \"\"\r\n\t\t},\r\n\t\t\"declaration\": {\r\n\t\t\t\"ads\": \"无\",\r\n\t\t\t\"data\": \"插件不采集任何数据\",\r\n\t\t\t\"permissions\": \"无\"\r\n\t\t},\r\n\t\t\"npmurl\": \"\"\r\n\t},\r\n\t\"uni_modules\": {\r\n\t\t\"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-icon\",\r\n\t\t\t\"uv-text\",\r\n\t\t\t\"uv-transition\",\r\n\t\t\t\"uv-sticky\"\r\n\t\t],\r\n\t\t\"encrypt\": [],\r\n\t\t\"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-drop-down/readme.md",
    "content": "## DropDown 下拉筛选\r\n\r\n> **组件名：uv-drop-down**\r\n\r\n该组件主要提供筛选下拉筛选框，内置基础筛选功能，可以根据自己的需求自定义筛选项。\r\n\r\n为了兼容app-nvue，需要内置三个组件进行配合使用，uv-drop-down属于菜单项（其实还包括子组件uv-drop-down-item），uv-drop-down-popup属于筛选框。\r\n\r\n只需要做简单的约定式配置，即可使用该功能，兼容性良好，已经在多端进行了多次测试。\r\n\r\n# <a href=\"https://www.uvui.cn/components/dropDown.html\" target=\"_blank\">查看文档</a>\r\n\r\n## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>（请不要 下载插件ZIP）</small>\r\n\r\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n<a href=\"https://ext.dcloud.net.cn/plugin?name=uv-ui\" target=\"_blank\">\r\n\r\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\r\n\r\n</a>\r\n\r\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-empty/changelog.md",
    "content": "## 1.0.4（2023-08-04）\n1. icon支持base64图片\n## 1.0.3（2023-07-17）\n1. 修复  uv-empty 恢复设置mode属性的内置图标\n## 1.0.2（2023-07-03）\n去除插槽判断，避免某些平台不显示的BUG\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-empty 内容为空\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-empty/components/uv-empty/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 内置图标名称，或图片路径，建议绝对路径\r\n\t\ticon: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 提示文字\r\n\t\ttext: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 文字颜色\r\n\t\ttextColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#c0c4cc'\r\n\t\t},\r\n\t\t// 文字大小\r\n\t\ttextSize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 14\r\n\t\t},\r\n\t\t// 图标的颜色\r\n\t\ticonColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#c0c4cc'\r\n\t\t},\r\n\t\t// 图标的大小\r\n\t\ticonSize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 90\r\n\t\t},\r\n\t\t// 选择预置的图标类型\r\n\t\tmode: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'data'\r\n\t\t},\r\n\t\t//  图标宽度，单位px\r\n\t\twidth: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 160\r\n\t\t},\r\n\t\t// 图标高度，单位px\r\n\t\theight: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 160\r\n\t\t},\r\n\t\t// 是否显示组件\r\n\t\tshow: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 组件距离上一个元素之间的距离，默认px单位\r\n\t\tmarginTop: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t...uni.$uv?.props?.empty\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-empty/components/uv-empty/uv-empty.vue",
    "content": "<template>\r\n\t<view\r\n\t  class=\"uv-empty\"\r\n\t  :style=\"[emptyStyle]\"\r\n\t  v-if=\"show\"\r\n\t>\r\n\t\t<uv-icon\r\n\t\t  v-if=\"!isImg\"\r\n\t\t  :name=\"mode === 'message' ? 'chat' : `empty-${mode}`\"\r\n\t\t  :size=\"iconSize\"\r\n\t\t  :color=\"iconColor\"\r\n\t\t  margin-top=\"14\"\r\n\t\t></uv-icon>\r\n\t\t<image\r\n\t\t  v-else\r\n\t\t  :style=\"{\r\n\t\t\t\twidth: $uv.addUnit(width),\r\n\t\t\t\theight: $uv.addUnit(height)\r\n\t\t\t}\"\r\n\t\t  :src=\"icon\"\r\n\t\t  mode=\"widthFix\"\r\n\t\t></image>\r\n\t\t<text\r\n\t\t  class=\"uv-empty__text\"\r\n\t\t  :style=\"[textStyle]\"\r\n\t\t>{{text ? text : icons[mode]}}</text>\r\n\t\t<view class=\"uv-empty__wrap\">\r\n\t\t\t<slot />\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * empty 内容为空\r\n\t * @description 该组件用于需要加载内容，但是加载的第一页数据就为空，提示一个\"没有内容\"的场景， 我们精心挑选了十几个场景的图标，方便您使用。\r\n\t * @tutorial https://www.uvui.cn/components/empty.html\r\n\t * @property {String}\t\t\ticon\t\t内置图标名称，或图片路径，建议绝对路径\r\n\t * @property {String}\t\t\ttext\t\t提示文字\r\n\t * @property {String}\t\t\ttextColor\t文字颜色 (默认 '#c0c4cc' )\r\n\t * @property {String | Number}\ttextSize\t文字大小 （默认 14 ）\r\n\t * @property {String}\t\t\ticonColor\t图标的颜色 （默认 '#c0c4cc' ）\r\n\t * @property {String | Number}\ticonSize\t图标的大小 （默认 90 ）\r\n\t * @property {String}\t\t\tmode\t\t选择预置的图标类型 （默认 'data' ）\r\n\t * @property {String | Number}\twidth\t\t图标宽度，单位px （默认 160 ）\r\n\t * @property {String | Number}\theight\t\t图标高度，单位px （默认 160 ）\r\n\t * @property {Boolean}\t\t\tshow\t\t是否显示组件 （默认 true ）\r\n\t * @property {String | Number}\tmarginTop\t组件距离上一个元素之间的距离，默认px单位 （默认 0 ）\r\n\t * @property {Object}\t\t\tcustomStyle\t定义需要用到的外部样式\r\n\t * \r\n\t * @event {Function} click 点击组件时触发\r\n\t * @event {Function} close 点击关闭按钮时触发\r\n\t * @example <uv-empty text=\"所谓伊人，在水一方\" mode=\"list\"></uv-empty>\r\n\t */\r\n\texport default {\r\n\t\tname: \"uv-empty\",\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\ticons: {\r\n\t\t\t\t\tcar: '购物车为空',\r\n\t\t\t\t\tpage: '页面不存在',\r\n\t\t\t\t\tsearch: '没有搜索结果',\r\n\t\t\t\t\taddress: '没有收货地址',\r\n\t\t\t\t\t'wifi-off': '没有WiFi',\r\n\t\t\t\t\torder: '订单为空',\r\n\t\t\t\t\tcoupon: '没有优惠券',\r\n\t\t\t\t\tfavor: '暂无收藏',\r\n\t\t\t\t\tpermission: '无权限',\r\n\t\t\t\t\thistory: '无历史记录',\r\n\t\t\t\t\tnews: '无新闻列表',\r\n\t\t\t\t\tmessage: '消息列表为空',\r\n\t\t\t\t\tlist: '列表为空',\r\n\t\t\t\t\tdata: '数据为空',\r\n\t\t\t\t\tcomment: '暂无评论',\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\t// 组件样式\r\n\t\t\temptyStyle() {\r\n\t\t\t\tconst style = {}\r\n\t\t\t\tstyle.marginTop = this.$uv.addUnit(this.marginTop)\r\n\t\t\t\t// 合并customStyle样式，此参数通过mixin中的props传递\r\n\t\t\t\treturn this.$uv.deepMerge(this.$uv.addStyle(this.customStyle), style)\r\n\t\t\t},\r\n\t\t\t// 文本样式\r\n\t\t\ttextStyle() {\r\n\t\t\t\tconst style = {}\r\n\t\t\t\tstyle.color = this.textColor\r\n\t\t\t\tstyle.fontSize = this.$uv.addUnit(this.textSize)\r\n\t\t\t\treturn style\r\n\t\t\t},\r\n\t\t\t// 判断icon是否图片路径\r\n\t\t\tisImg() {\r\n\t\t\t\tconst isBase64 = this.icon.indexOf('data:') > -1 && this.icon.indexOf('base64') > -1;\r\n\t\t\t\treturn this.icon.indexOf('/') !== -1 || isBase64;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t$uv-empty-text-margin-top: 20rpx !default;\r\n\t$uv-empty-slot-margin-top: 20rpx !default;\r\n\t.uv-empty {\r\n\t\t@include flex;\r\n\t\tflex-direction: column;\r\n\t\tjustify-content: center;\r\n\t\talign-items: center;\r\n\t\t&__text {\r\n\t\t\t@include flex;\r\n\t\t\tjustify-content: center;\r\n\t\t\talign-items: center;\r\n\t\t\tmargin-top: $uv-empty-text-margin-top;\r\n\t\t}\r\n\t}\r\n\t.uv-slot-wrap {\r\n\t\t@include flex;\r\n\t\tjustify-content: center;\r\n\t\talign-items: center;\r\n\t\tmargin-top: $uv-empty-slot-margin-top;\r\n\t}\r\n</style>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-empty/package.json",
    "content": "{\r\n  \"id\": \"uv-empty\",\r\n  \"displayName\": \"uv-empty 内容为空 全面兼容vue3+2、app、h5、小程序等多端\",\r\n  \"version\": \"1.0.4\",\r\n  \"description\": \"该组件用于需要加载内容，但是加载的第一页数据就为空，提示一个 没有内容 的场景， 我们精心挑选了十几个场景的图标，方便您使用。\",\r\n  \"keywords\": [\r\n    \"empty\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"空数据\",\r\n    \"暂无数据\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-icon\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-empty/readme.md",
    "content": "## Empty 内容为空\r\n\r\n> **组件名：uv-empty**\r\n\r\n该组件用于需要加载内容，但是加载的第一页数据就为空，提示一个\"没有内容\"的场景， 我们精心挑选了十几个场景的图标，方便您使用。\r\n\r\n# <a href=\"https://www.uvui.cn/components/empty.html\" target=\"_blank\">查看文档</a>\r\n\r\n## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>（请不要 下载插件ZIP）</small>\r\n\r\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n<a href=\"https://ext.dcloud.net.cn/plugin?name=uv-ui\" target=\"_blank\">\r\n\r\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\r\n\r\n</a>\r\n\r\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-form/changelog.md",
    "content": "## 1.0.9（2023-08-14）\n1. 修复设置labelWidth属性时，节点渲染有闪动的BUG\n## 1.0.8（2023-08-13）\n1. 修复未设置rules的情况下报错的BUG\n2. 优化错误提示\n## 1.0.7（2023-08-10）\n1. 修复在vue3+setup语法糖中错误文字动画错乱\n## 1.0.6（2023-07-17）\n1. 优化文档\n2. 优化其他\n## 1.0.5（2023-07-03）\n去除插槽判断，避免某些平台不显示的BUG\n## 1.0.4（2023-07-02）\r\nuv-form  由于弹出层uv-transition的修改，组件内部做了相应的修改，参数不变。\r\n## 1.0.3（2023-06-18）\r\n1. 修改某些情况下的BUG\r\n## 1.0.2（2023-06-15）\r\n1.  修复支付宝报错的BUG\r\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-form 表单\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-form/components/uv-form/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 当前form的需要验证字段的集合\r\n\t\tmodel: {\r\n\t\t\ttype: Object,\r\n\t\t\tdefault: () => ({})\r\n\t\t},\r\n\t\t// 验证规则\r\n\t\trules: {\r\n\t\t\ttype: [Object, Function, Array],\r\n\t\t\tdefault: () => ({})\r\n\t\t},\r\n\t\t// 有错误时的提示方式，message-提示信息，toast-进行toast提示\r\n\t\t// border-bottom-下边框呈现红色，none-无提示\r\n\t\terrorType: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'message'\r\n\t\t},\r\n\t\t// 是否显示表单域的下划线边框\r\n\t\tborderBottom: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// label的位置，left-左边，top-上边\r\n\t\tlabelPosition: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'left'\r\n\t\t},\r\n\t\t// label的宽度，单位px\r\n\t\tlabelWidth: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 45\r\n\t\t},\r\n\t\t// lable字体的对齐方式\r\n\t\tlabelAlign: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'left'\r\n\t\t},\r\n\t\t// lable的样式，对象形式\r\n\t\tlabelStyle: {\r\n\t\t\ttype: Object,\r\n\t\t\tdefault: () => ({})\r\n\t\t},\r\n\t\t...uni.$uv?.props?.form\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-form/components/uv-form/uv-form.vue",
    "content": "<template>\r\n\t<view class=\"uv-form\">\r\n\t\t<slot />\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from \"./props.js\";\r\n\timport Schema from \"./valid.js\";\r\n\t// 去除警告信息\r\n\tSchema.warning = function() {};\r\n\t/**\r\n\t * Form 表单\r\n\t * @description 此组件一般用于表单场景，可以配置Input输入框，Select弹出框，进行表单验证等。\r\n\t * @tutorial https://www.uvui.cn/components/form.html\r\n\t * @property {Object}\t\t\t\t\t\tmodel\t\t\t当前form的需要验证字段的集合\r\n\t * @property {Object | Function | Array}\trules\t\t\t验证规则\r\n\t * @property {String}\t\t\t\t\t\terrorType\t\t错误的提示方式，见上方说明 ( 默认 message )\r\n\t * @property {Boolean}\t\t\t\t\t\tborderBottom\t是否显示表单域的下划线边框   ( 默认 true ）\r\n\t * @property {String}\t\t\t\t\t\tlabelPosition\t表单域提示文字的位置，left-左侧，top-上方 ( 默认 'left' ）\r\n\t * @property {String | Number}\t\t\t\tlabelWidth\t\t提示文字的宽度，单位px  ( 默认 45 ）\r\n\t * @property {String}\t\t\t\t\t\tlabelAlign\t\tlable字体的对齐方式   ( 默认 ‘left' ）\r\n\t * @property {Object}\t\t\t\t\t\tlabelStyle\t\tlable的样式，对象形式\r\n\t * @example <uv-form labelPosition=\"left\" :model=\"model1\" :rules=\"rules\" ref=\"form1\"></uv-form>\r\n\t */\r\n\texport default {\r\n\t\tname: \"uv-form\",\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tprovide() {\r\n\t\t\treturn {\r\n\t\t\t\tuForm: this,\r\n\t\t\t};\r\n\t\t},\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tformRules: {},\r\n\t\t\t\t// 规则校验器\r\n\t\t\t\tvalidator: {},\r\n\t\t\t\t// 原始的model快照，用于resetFields方法重置表单时使用\r\n\t\t\t\toriginalModel: null,\r\n\t\t\t};\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\t// 监听规则的变化\r\n\t\t\trules: {\r\n\t\t\t\timmediate: true,\r\n\t\t\t\thandler(n) {\r\n\t\t\t\t\tthis.setRules(n);\r\n\t\t\t\t},\r\n\t\t\t},\r\n\t\t\t// 监听属性的变化，通知子组件uv-form-item重新获取信息\r\n\t\t\tpropsChange(n) {\r\n\t\t\t\tif (this.children?.length) {\r\n\t\t\t\t\tthis.children.map((child) => {\r\n\t\t\t\t\t\t// 判断子组件(uv-form-item)如果有updateParentData方法的话，就就执行(执行的结果是子组件重新从父组件拉取了最新的值)\r\n\t\t\t\t\t\ttypeof child.updateParentData == \"function\" &&\r\n\t\t\t\t\t\t\tchild.updateParentData();\r\n\t\t\t\t\t});\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t// 监听model的初始值作为重置表单的快照\r\n\t\t\tmodel: {\r\n\t\t\t\timmediate: true,\r\n\t\t\t\thandler(n) {\r\n\t\t\t\t\tif (!this.originalModel) {\r\n\t\t\t\t\t\tthis.originalModel = this.$uv.deepClone(n);\r\n\t\t\t\t\t}\r\n\t\t\t\t},\r\n\t\t\t},\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\tpropsChange() {\r\n\t\t\t\treturn [\r\n\t\t\t\t\tthis.errorType,\r\n\t\t\t\t\tthis.borderBottom,\r\n\t\t\t\t\tthis.labelPosition,\r\n\t\t\t\t\tthis.labelWidth,\r\n\t\t\t\t\tthis.labelAlign,\r\n\t\t\t\t\tthis.labelStyle,\r\n\t\t\t\t];\r\n\t\t\t},\r\n\t\t},\r\n\t\tcreated() {\r\n\t\t\t// 存储当前form下的所有uv-form-item的实例\r\n\t\t\t// 不能定义在data中，否则微信小程序会造成循环引用而报错\r\n\t\t\tthis.children = [];\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\t// 手动设置校验的规则，如果规则中有函数的话，微信小程序中会过滤掉，所以只能手动调用设置规则\r\n\t\t\tsetRules(rules) {\r\n\t\t\t\t// 判断是否有规则\r\n\t\t\t\tif (Object.keys(rules).length === 0) return;\r\n\t\t\t\tif (process.env.NODE_ENV === 'development' && Object.keys(this.model).length === 0) {\r\n\t\t\t\t\tthis.$uv.error('设置rules，model必须设置！如果已经设置，请刷新页面。');\r\n\t\t\t\t\treturn;\r\n\t\t\t\t};\r\n\t\t\t\tthis.formRules = rules;\r\n\t\t\t\t// 重新将规则赋予Validator\r\n\t\t\t\tthis.validator = new Schema(rules);\r\n\t\t\t},\r\n\t\t\t// 清空所有uv-form-item组件的内容，本质上是调用了uv-form-item组件中的resetField()方法\r\n\t\t\tresetFields() {\r\n\t\t\t\tthis.resetModel();\r\n\t\t\t},\r\n\t\t\t// 重置model为初始值的快照\r\n\t\t\tresetModel(obj) {\r\n\t\t\t\t// 历遍所有uv-form-item，根据其prop属性，还原model的原始快照\r\n\t\t\t\tthis.children.map((child) => {\r\n\t\t\t\t\tconst prop = child?.prop;\r\n\t\t\t\t\tconst value = this.$uv.getProperty(this.originalModel, prop);\r\n\t\t\t\t\tthis.$uv.setProperty(this.model, prop, value);\r\n\t\t\t\t});\r\n\t\t\t},\r\n\t\t\t// 清空校验结果\r\n\t\t\tclearValidate(props) {\r\n\t\t\t\tprops = [].concat(props);\r\n\t\t\t\tthis.children.map((child) => {\r\n\t\t\t\t\t// 如果uv-form-item的prop在props数组中，则清除对应的校验结果信息\r\n\t\t\t\t\tif (props[0] === undefined || props.includes(child.prop)) {\r\n\t\t\t\t\t\tchild.message = null;\r\n\t\t\t\t\t}\r\n\t\t\t\t});\r\n\t\t\t},\r\n\t\t\t// 对部分表单字段进行校验\r\n\t\t\tasync validateField(value, callback, event = null) {\r\n\t\t\t\t// $nextTick是必须的，否则model的变更，可能会延后于此方法的执行\r\n\t\t\t\tthis.$nextTick(() => {\r\n\t\t\t\t\t// 校验错误信息，返回给回调方法，用于存放所有form-item的错误信息\r\n\t\t\t\t\tconst errorsRes = [];\r\n\t\t\t\t\t// 如果为字符串，转为数组\r\n\t\t\t\t\tvalue = [].concat(value);\r\n\t\t\t\t\t// 历遍children所有子form-item\r\n\t\t\t\t\tthis.children.map((child) => {\r\n\t\t\t\t\t\t// 用于存放form-item的错误信息\r\n\t\t\t\t\t\tconst childErrors = [];\r\n\t\t\t\t\t\tif (value.includes(child.prop)) {\r\n\t\t\t\t\t\t\t// 获取对应的属性，通过类似'a.b.c'的形式\r\n\t\t\t\t\t\t\tconst propertyVal = this.$uv.getProperty(\r\n\t\t\t\t\t\t\t\tthis.model,\r\n\t\t\t\t\t\t\t\tchild.prop\r\n\t\t\t\t\t\t\t);\r\n\t\t\t\t\t\t\t// 属性链数组\r\n\t\t\t\t\t\t\tconst propertyChain = child.prop.split(\".\");\r\n\t\t\t\t\t\t\tconst propertyName =\r\n\t\t\t\t\t\t\t\tpropertyChain[propertyChain.length - 1];\r\n\r\n\t\t\t\t\t\t\tconst rule = this.formRules[child.prop];\r\n\t\t\t\t\t\t\t// 如果不存在对应的规则，直接返回，否则校验器会报错\r\n\t\t\t\t\t\t\tif (!rule) return;\r\n\t\t\t\t\t\t\t// rule规则可为数组形式，也可为对象形式，此处拼接成为数组\r\n\t\t\t\t\t\t\tconst rules = [].concat(rule);\r\n\r\n\t\t\t\t\t\t\t// 对rules数组进行校验\r\n\t\t\t\t\t\t\tfor (let i = 0; i < rules.length; i++) {\r\n\t\t\t\t\t\t\t\tconst ruleItem = rules[i];\r\n\t\t\t\t\t\t\t\t// 将uv-form-item的触发器转为数组形式\r\n\t\t\t\t\t\t\t\tconst trigger = [].concat(ruleItem?.trigger);\r\n\t\t\t\t\t\t\t\t// 如果是有传入触发事件，但是此form-item却没有配置此触发器的话，不执行校验操作\r\n\t\t\t\t\t\t\t\tif (event && !trigger.includes(event)) continue;\r\n\t\t\t\t\t\t\t\t// 实例化校验对象，传入构造规则\r\n\t\t\t\t\t\t\t\tconst validator = new Schema({\r\n\t\t\t\t\t\t\t\t\t[propertyName]: ruleItem,\r\n\t\t\t\t\t\t\t\t});\r\n\t\t\t\t\t\t\t\tvalidator.validate({\r\n\t\t\t\t\t\t\t\t\t\t[propertyName]: propertyVal,\r\n\t\t\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t\t\t\t(errors, fields) => {\r\n\t\t\t\t\t\t\t\t\t\tif (this.$uv.test.array(errors)) {\r\n\t\t\t\t\t\t\t\t\t\t\terrorsRes.push(...errors);\r\n\t\t\t\t\t\t\t\t\t\t\tchildErrors.push(...errors);\r\n\t\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\t\tthis.$nextTick(() => {\r\n\t\t\t\t\t\t\t\t\t\t\tchild.message = childErrors[0]?.message ? childErrors[0]?.message : null;\r\n\t\t\t\t\t\t\t\t\t\t})\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t});\r\n\t\t\t\t\t// 执行回调函数\r\n\t\t\t\t\ttypeof callback === \"function\" && callback(errorsRes);\r\n\t\t\t\t});\r\n\t\t\t},\r\n\t\t\t// 校验全部数据\r\n\t\t\tvalidate(callback) {\r\n\t\t\t\treturn new Promise((resolve, reject) => {\r\n\t\t\t\t\t// $nextTick是必须的，否则model的变更，可能会延后于validate方法\r\n\t\t\t\t\tthis.$nextTick(() => {\r\n\t\t\t\t\t\t// 获取所有form-item的prop，交给validateField方法进行校验\r\n\t\t\t\t\t\tconst formItemProps = this.children.map(\r\n\t\t\t\t\t\t\t(item) => item.prop\r\n\t\t\t\t\t\t);\r\n\t\t\t\t\t\tthis.validateField(formItemProps, (errors) => {\r\n\t\t\t\t\t\t\tif (errors.length) {\r\n\t\t\t\t\t\t\t\t// 如果错误提示方式为toast，则进行提示\r\n\t\t\t\t\t\t\t\tthis.errorType === 'toast' && this.$uv.toast(errors[0].message)\r\n\t\t\t\t\t\t\t\treject(errors)\r\n\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\tresolve(true)\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t});\r\n\t\t\t\t});\r\n\t\t\t},\r\n\t\t},\r\n\t};\r\n</script>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-form/components/uv-form/valid.js",
    "content": "function _extends() {\r\n    _extends = Object.assign || function (target) {\r\n        for (let i = 1; i < arguments.length; i++) {\r\n            const source = arguments[i]\r\n\r\n            for (const key in source) {\r\n                if (Object.prototype.hasOwnProperty.call(source, key)) {\r\n                    target[key] = source[key]\r\n                }\r\n            }\r\n        }\r\n\r\n        return target\r\n    }\r\n\r\n    return _extends.apply(this, arguments)\r\n}\r\n\r\n/* eslint no-console:0 */\r\nconst formatRegExp = /%[sdj%]/g\r\nlet warning = function warning() {} // don't print warning message when in production env or node runtime\r\n\r\nif (typeof process !== 'undefined' && process.env && process.env.NODE_ENV !== 'production' && typeof window\r\n\t!== 'undefined' && typeof document !== 'undefined') {\r\n    warning = function warning(type, errors) {\r\n        if (typeof console !== 'undefined' && console.warn) {\r\n            if (errors.every((e) => typeof e === 'string')) {\r\n                console.warn(type, errors)\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\nfunction convertFieldsError(errors) {\r\n    if (!errors || !errors.length) return null\r\n    const fields = {}\r\n    errors.forEach((error) => {\r\n        const { field } = error\r\n        fields[field] = fields[field] || []\r\n        fields[field].push(error)\r\n    })\r\n    return fields\r\n}\r\n\r\nfunction format() {\r\n    for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {\r\n        args[_key] = arguments[_key]\r\n    }\r\n\r\n    let i = 1\r\n    const f = args[0]\r\n    const len = args.length\r\n\r\n    if (typeof f === 'function') {\r\n        return f.apply(null, args.slice(1))\r\n    }\r\n\r\n    if (typeof f === 'string') {\r\n        let str = String(f).replace(formatRegExp, (x) => {\r\n            if (x === '%%') {\r\n                return '%'\r\n            }\r\n\r\n            if (i >= len) {\r\n                return x\r\n            }\r\n\r\n            switch (x) {\r\n            case '%s':\r\n                return String(args[i++])\r\n\r\n            case '%d':\r\n                return Number(args[i++])\r\n\r\n            case '%j':\r\n                try {\r\n                    return JSON.stringify(args[i++])\r\n                } catch (_) {\r\n                    return '[Circular]'\r\n                }\r\n\r\n                break\r\n\r\n            default:\r\n                return x\r\n            }\r\n        })\r\n\r\n        for (let arg = args[i]; i < len; arg = args[++i]) {\r\n            str += ` ${arg}`\r\n        }\r\n\r\n        return str\r\n    }\r\n\r\n    return f\r\n}\r\n\r\nfunction isNativeStringType(type) {\r\n    return type === 'string' || type === 'url' || type === 'hex' || type === 'email' || type === 'pattern'\r\n}\r\n\r\nfunction isEmptyValue(value, type) {\r\n    if (value === undefined || value === null) {\r\n        return true\r\n    }\r\n\r\n    if (type === 'array' && Array.isArray(value) && !value.length) {\r\n        return true\r\n    }\r\n\r\n    if (isNativeStringType(type) && typeof value === 'string' && !value) {\r\n        return true\r\n    }\r\n\r\n    return false\r\n}\r\n\r\nfunction asyncParallelArray(arr, func, callback) {\r\n    const results = []\r\n    let total = 0\r\n    const arrLength = arr.length\r\n\r\n    function count(errors) {\r\n        results.push.apply(results, errors)\r\n        total++\r\n\r\n        if (total === arrLength) {\r\n            callback(results)\r\n        }\r\n    }\r\n\r\n    arr.forEach((a) => {\r\n        func(a, count)\r\n    })\r\n}\r\n\r\nfunction asyncSerialArray(arr, func, callback) {\r\n    let index = 0\r\n    const arrLength = arr.length\r\n\r\n    function next(errors) {\r\n        if (errors && errors.length) {\r\n            callback(errors)\r\n            return\r\n        }\r\n\r\n        const original = index\r\n        index += 1\r\n\r\n        if (original < arrLength) {\r\n            func(arr[original], next)\r\n        } else {\r\n            callback([])\r\n        }\r\n    }\r\n\r\n    next([])\r\n}\r\n\r\nfunction flattenObjArr(objArr) {\r\n    const ret = []\r\n    Object.keys(objArr).forEach((k) => {\r\n        ret.push.apply(ret, objArr[k])\r\n    })\r\n    return ret\r\n}\r\n\r\nfunction asyncMap(objArr, option, func, callback) {\r\n    if (option.first) {\r\n        const _pending = new Promise((resolve, reject) => {\r\n            const next = function next(errors) {\r\n                callback(errors)\r\n                return errors.length ? reject({\r\n                    errors,\r\n                    fields: convertFieldsError(errors)\r\n                }) : resolve()\r\n            }\r\n\r\n            const flattenArr = flattenObjArr(objArr)\r\n            asyncSerialArray(flattenArr, func, next)\r\n        })\r\n\r\n        _pending.catch((e) => e)\r\n\r\n        return _pending\r\n    }\r\n\r\n    let firstFields = option.firstFields || []\r\n\r\n    if (firstFields === true) {\r\n        firstFields = Object.keys(objArr)\r\n    }\r\n\r\n    const objArrKeys = Object.keys(objArr)\r\n    const objArrLength = objArrKeys.length\r\n    let total = 0\r\n    const results = []\r\n    const pending = new Promise((resolve, reject) => {\r\n        const next = function next(errors) {\r\n            results.push.apply(results, errors)\r\n            total++\r\n\r\n            if (total === objArrLength) {\r\n                callback(results)\r\n                return results.length ? reject({\r\n                    errors: results,\r\n                    fields: convertFieldsError(results)\r\n                }) : resolve()\r\n            }\r\n        }\r\n\r\n        if (!objArrKeys.length) {\r\n            callback(results)\r\n            resolve()\r\n        }\r\n\r\n        objArrKeys.forEach((key) => {\r\n            const arr = objArr[key]\r\n\r\n            if (firstFields.indexOf(key) !== -1) {\r\n                asyncSerialArray(arr, func, next)\r\n            } else {\r\n                asyncParallelArray(arr, func, next)\r\n            }\r\n        })\r\n    })\r\n    pending.catch((e) => e)\r\n    return pending\r\n}\r\n\r\nfunction complementError(rule) {\r\n    return function (oe) {\r\n        if (oe && oe.message) {\r\n            oe.field = oe.field || rule.fullField\r\n            return oe\r\n        }\r\n\r\n        return {\r\n            message: typeof oe === 'function' ? oe() : oe,\r\n            field: oe.field || rule.fullField\r\n        }\r\n    }\r\n}\r\n\r\nfunction deepMerge(target, source) {\r\n    if (source) {\r\n        for (const s in source) {\r\n            if (source.hasOwnProperty(s)) {\r\n                const value = source[s]\r\n\r\n                if (typeof value === 'object' && typeof target[s] === 'object') {\r\n                    target[s] = { ...target[s], ...value }\r\n                } else {\r\n                    target[s] = value\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    return target\r\n}\r\n\r\n/**\r\n *  Rule for validating required fields.\r\n *\r\n *  @param rule The validation rule.\r\n *  @param value The value of the field on the source object.\r\n *  @param source The source object being validated.\r\n *  @param errors An array of errors that this rule may add\r\n *  validation errors to.\r\n *  @param options The validation options.\r\n *  @param options.messages The validation messages.\r\n */\r\n\r\nfunction required(rule, value, source, errors, options, type) {\r\n    if (rule.required && (!source.hasOwnProperty(rule.field) || isEmptyValue(value, type || rule.type))) {\r\n        errors.push(format(options.messages.required, rule.fullField))\r\n    }\r\n}\r\n\r\n/**\r\n *  Rule for validating whitespace.\r\n *\r\n *  @param rule The validation rule.\r\n *  @param value The value of the field on the source object.\r\n *  @param source The source object being validated.\r\n *  @param errors An array of errors that this rule may add\r\n *  validation errors to.\r\n *  @param options The validation options.\r\n *  @param options.messages The validation messages.\r\n */\r\n\r\nfunction whitespace(rule, value, source, errors, options) {\r\n    if (/^\\s+$/.test(value) || value === '') {\r\n        errors.push(format(options.messages.whitespace, rule.fullField))\r\n    }\r\n}\r\n\r\n/* eslint max-len:0 */\r\n\r\nconst pattern = {\r\n    // http://emailregex.com/\r\n    email: /^(([^<>()\\[\\]\\\\.,;:\\s@\"]+(\\.[^<>()\\[\\]\\\\.,;:\\s@\"]+)*)|(\".+\"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$/,\r\n    url: new RegExp(\r\n        '^(?!mailto:)(?:(?:http|https|ftp)://|//)(?:\\\\S+(?::\\\\S*)?@)?(?:(?:(?:[1-9]\\\\d?|1\\\\d\\\\d|2[01]\\\\d|22[0-3])(?:\\\\.(?:1?\\\\d{1,2}|2[0-4]\\\\d|25[0-5])){2}(?:\\\\.(?:[0-9]\\\\d?|1\\\\d\\\\d|2[0-4]\\\\d|25[0-4]))|(?:(?:[a-z\\\\u00a1-\\\\uffff0-9]+-*)*[a-z\\\\u00a1-\\\\uffff0-9]+)(?:\\\\.(?:[a-z\\\\u00a1-\\\\uffff0-9]+-*)*[a-z\\\\u00a1-\\\\uffff0-9]+)*(?:\\\\.(?:[a-z\\\\u00a1-\\\\uffff]{2,})))|localhost)(?::\\\\d{2,5})?(?:(/|\\\\?|#)[^\\\\s]*)?$',\r\n        'i'\r\n    ),\r\n    hex: /^#?([a-f0-9]{6}|[a-f0-9]{3})$/i\r\n}\r\nvar types = {\r\n    integer: function integer(value) {\r\n        return /^(-)?\\d+$/.test(value);\r\n    },\r\n    float: function float(value) {\r\n        return /^(-)?\\d+(\\.\\d+)?$/.test(value);\r\n    },\r\n    array: function array(value) {\r\n        return Array.isArray(value)\r\n    },\r\n    regexp: function regexp(value) {\r\n        if (value instanceof RegExp) {\r\n            return true\r\n        }\r\n\r\n        try {\r\n            return !!new RegExp(value)\r\n        } catch (e) {\r\n            return false\r\n        }\r\n    },\r\n    date: function date(value) {\r\n        return typeof value.getTime === 'function' && typeof value.getMonth === 'function' && typeof value.getYear\r\n\t\t\t=== 'function'\r\n    },\r\n    number: function number(value) {\r\n        if (isNaN(value)) {\r\n            return false\r\n        }\r\n\r\n        // 修改源码，将字符串数值先转为数值\r\n        return typeof +value === 'number'\r\n    },\r\n    object: function object(value) {\r\n        return typeof value === 'object' && !types.array(value)\r\n    },\r\n    method: function method(value) {\r\n        return typeof value === 'function'\r\n    },\r\n    email: function email(value) {\r\n        return typeof value === 'string' && !!value.match(pattern.email) && value.length < 255\r\n    },\r\n    url: function url(value) {\r\n        return typeof value === 'string' && !!value.match(pattern.url)\r\n    },\r\n    hex: function hex(value) {\r\n        return typeof value === 'string' && !!value.match(pattern.hex)\r\n    }\r\n}\r\n/**\r\n *  Rule for validating the type of a value.\r\n *\r\n *  @param rule The validation rule.\r\n *  @param value The value of the field on the source object.\r\n *  @param source The source object being validated.\r\n *  @param errors An array of errors that this rule may add\r\n *  validation errors to.\r\n *  @param options The validation options.\r\n *  @param options.messages The validation messages.\r\n */\r\n\r\nfunction type(rule, value, source, errors, options) {\r\n    if (rule.required && value === undefined) {\r\n        required(rule, value, source, errors, options)\r\n        return\r\n    }\r\n\r\n    const custom = ['integer', 'float', 'array', 'regexp', 'object', 'method', 'email', 'number', 'date', 'url', 'hex']\r\n    const ruleType = rule.type\r\n\r\n    if (custom.indexOf(ruleType) > -1) {\r\n        if (!types[ruleType](value)) {\r\n            errors.push(format(options.messages.types[ruleType], rule.fullField, rule.type))\r\n        } // straight typeof check\r\n    } else if (ruleType && typeof value !== rule.type) {\r\n        errors.push(format(options.messages.types[ruleType], rule.fullField, rule.type))\r\n    }\r\n}\r\n\r\n/**\r\n *  Rule for validating minimum and maximum allowed values.\r\n *\r\n *  @param rule The validation rule.\r\n *  @param value The value of the field on the source object.\r\n *  @param source The source object being validated.\r\n *  @param errors An array of errors that this rule may add\r\n *  validation errors to.\r\n *  @param options The validation options.\r\n *  @param options.messages The validation messages.\r\n */\r\n\r\nfunction range(rule, value, source, errors, options) {\r\n    const len = typeof rule.len === 'number'\r\n    const min = typeof rule.min === 'number'\r\n    const max = typeof rule.max === 'number' // 正则匹配码点范围从U+010000一直到U+10FFFF的文字（补充平面Supplementary Plane）\r\n\r\n    const spRegexp = /[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]/g\r\n    let val = value\r\n    let key = null\r\n    const num = typeof value === 'number'\r\n    const str = typeof value === 'string'\r\n    const arr = Array.isArray(value)\r\n\r\n    if (num) {\r\n        key = 'number'\r\n    } else if (str) {\r\n        key = 'string'\r\n    } else if (arr) {\r\n        key = 'array'\r\n    } // if the value is not of a supported type for range validation\r\n    // the validation rule rule should use the\r\n    // type property to also test for a particular type\r\n\r\n    if (!key) {\r\n        return false\r\n    }\r\n\r\n    if (arr) {\r\n        val = value.length\r\n    }\r\n\r\n    if (str) {\r\n        // 处理码点大于U+010000的文字length属性不准确的bug，如\"𠮷𠮷𠮷\".lenght !== 3\r\n        val = value.replace(spRegexp, '_').length\r\n    }\r\n\r\n    if (len) {\r\n        if (val !== rule.len) {\r\n            errors.push(format(options.messages[key].len, rule.fullField, rule.len))\r\n        }\r\n    } else if (min && !max && val < rule.min) {\r\n        errors.push(format(options.messages[key].min, rule.fullField, rule.min))\r\n    } else if (max && !min && val > rule.max) {\r\n        errors.push(format(options.messages[key].max, rule.fullField, rule.max))\r\n    } else if (min && max && (val < rule.min || val > rule.max)) {\r\n        errors.push(format(options.messages[key].range, rule.fullField, rule.min, rule.max))\r\n    }\r\n}\r\n\r\nconst ENUM = 'enum'\r\n/**\r\n *  Rule for validating a value exists in an enumerable list.\r\n *\r\n *  @param rule The validation rule.\r\n *  @param value The value of the field on the source object.\r\n *  @param source The source object being validated.\r\n *  @param errors An array of errors that this rule may add\r\n *  validation errors to.\r\n *  @param options The validation options.\r\n *  @param options.messages The validation messages.\r\n */\r\n\r\nfunction enumerable(rule, value, source, errors, options) {\r\n    rule[ENUM] = Array.isArray(rule[ENUM]) ? rule[ENUM] : []\r\n\r\n    if (rule[ENUM].indexOf(value) === -1) {\r\n        errors.push(format(options.messages[ENUM], rule.fullField, rule[ENUM].join(', ')))\r\n    }\r\n}\r\n\r\n/**\r\n *  Rule for validating a regular expression pattern.\r\n *\r\n *  @param rule The validation rule.\r\n *  @param value The value of the field on the source object.\r\n *  @param source The source object being validated.\r\n *  @param errors An array of errors that this rule may add\r\n *  validation errors to.\r\n *  @param options The validation options.\r\n *  @param options.messages The validation messages.\r\n */\r\n\r\nfunction pattern$1(rule, value, source, errors, options) {\r\n    if (rule.pattern) {\r\n        if (rule.pattern instanceof RegExp) {\r\n            // if a RegExp instance is passed, reset `lastIndex` in case its `global`\r\n            // flag is accidentally set to `true`, which in a validation scenario\r\n            // is not necessary and the result might be misleading\r\n            rule.pattern.lastIndex = 0\r\n\r\n            if (!rule.pattern.test(value)) {\r\n                errors.push(format(options.messages.pattern.mismatch, rule.fullField, value, rule.pattern))\r\n            }\r\n        } else if (typeof rule.pattern === 'string') {\r\n            const _pattern = new RegExp(rule.pattern)\r\n\r\n            if (!_pattern.test(value)) {\r\n                errors.push(format(options.messages.pattern.mismatch, rule.fullField, value, rule.pattern))\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\nconst rules = {\r\n    required,\r\n    whitespace,\r\n    type,\r\n    range,\r\n    enum: enumerable,\r\n    pattern: pattern$1\r\n}\r\n\r\n/**\r\n *  Performs validation for string types.\r\n *\r\n *  @param rule The validation rule.\r\n *  @param value The value of the field on the source object.\r\n *  @param callback The callback function.\r\n *  @param source The source object being validated.\r\n *  @param options The validation options.\r\n *  @param options.messages The validation messages.\r\n */\r\n\r\nfunction string(rule, value, callback, source, options) {\r\n    const errors = []\r\n    const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)\r\n\r\n    if (validate) {\r\n        if (isEmptyValue(value, 'string') && !rule.required) {\r\n            return callback()\r\n        }\r\n\r\n        rules.required(rule, value, source, errors, options, 'string')\r\n\r\n        if (!isEmptyValue(value, 'string')) {\r\n            rules.type(rule, value, source, errors, options)\r\n            rules.range(rule, value, source, errors, options)\r\n            rules.pattern(rule, value, source, errors, options)\r\n\r\n            if (rule.whitespace === true) {\r\n                rules.whitespace(rule, value, source, errors, options)\r\n            }\r\n        }\r\n    }\r\n\r\n    callback(errors)\r\n}\r\n\r\n/**\r\n *  Validates a function.\r\n *\r\n *  @param rule The validation rule.\r\n *  @param value The value of the field on the source object.\r\n *  @param callback The callback function.\r\n *  @param source The source object being validated.\r\n *  @param options The validation options.\r\n *  @param options.messages The validation messages.\r\n */\r\n\r\nfunction method(rule, value, callback, source, options) {\r\n    const errors = []\r\n    const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)\r\n\r\n    if (validate) {\r\n        if (isEmptyValue(value) && !rule.required) {\r\n            return callback()\r\n        }\r\n\r\n        rules.required(rule, value, source, errors, options)\r\n\r\n        if (value !== undefined) {\r\n            rules.type(rule, value, source, errors, options)\r\n        }\r\n    }\r\n\r\n    callback(errors)\r\n}\r\n\r\n/**\r\n *  Validates a number.\r\n *\r\n *  @param rule The validation rule.\r\n *  @param value The value of the field on the source object.\r\n *  @param callback The callback function.\r\n *  @param source The source object being validated.\r\n *  @param options The validation options.\r\n *  @param options.messages The validation messages.\r\n */\r\n\r\nfunction number(rule, value, callback, source, options) {\r\n    const errors = []\r\n    const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)\r\n\r\n    if (validate) {\r\n        if (value === '') {\r\n            value = undefined\r\n        }\r\n\r\n        if (isEmptyValue(value) && !rule.required) {\r\n            return callback()\r\n        }\r\n\r\n        rules.required(rule, value, source, errors, options)\r\n\r\n        if (value !== undefined) {\r\n            rules.type(rule, value, source, errors, options)\r\n            rules.range(rule, value, source, errors, options)\r\n        }\r\n    }\r\n\r\n    callback(errors)\r\n}\r\n\r\n/**\r\n *  Validates a boolean.\r\n *\r\n *  @param rule The validation rule.\r\n *  @param value The value of the field on the source object.\r\n *  @param callback The callback function.\r\n *  @param source The source object being validated.\r\n *  @param options The validation options.\r\n *  @param options.messages The validation messages.\r\n */\r\n\r\nfunction _boolean(rule, value, callback, source, options) {\r\n    const errors = []\r\n    const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)\r\n\r\n    if (validate) {\r\n        if (isEmptyValue(value) && !rule.required) {\r\n            return callback()\r\n        }\r\n\r\n        rules.required(rule, value, source, errors, options)\r\n\r\n        if (value !== undefined) {\r\n            rules.type(rule, value, source, errors, options)\r\n        }\r\n    }\r\n\r\n    callback(errors)\r\n}\r\n\r\n/**\r\n *  Validates the regular expression type.\r\n *\r\n *  @param rule The validation rule.\r\n *  @param value The value of the field on the source object.\r\n *  @param callback The callback function.\r\n *  @param source The source object being validated.\r\n *  @param options The validation options.\r\n *  @param options.messages The validation messages.\r\n */\r\n\r\nfunction regexp(rule, value, callback, source, options) {\r\n    const errors = []\r\n    const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)\r\n\r\n    if (validate) {\r\n        if (isEmptyValue(value) && !rule.required) {\r\n            return callback()\r\n        }\r\n\r\n        rules.required(rule, value, source, errors, options)\r\n\r\n        if (!isEmptyValue(value)) {\r\n            rules.type(rule, value, source, errors, options)\r\n        }\r\n    }\r\n\r\n    callback(errors)\r\n}\r\n\r\n/**\r\n *  Validates a number is an integer.\r\n *\r\n *  @param rule The validation rule.\r\n *  @param value The value of the field on the source object.\r\n *  @param callback The callback function.\r\n *  @param source The source object being validated.\r\n *  @param options The validation options.\r\n *  @param options.messages The validation messages.\r\n */\r\n\r\nfunction integer(rule, value, callback, source, options) {\r\n    const errors = []\r\n    const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)\r\n\r\n    if (validate) {\r\n        if (isEmptyValue(value) && !rule.required) {\r\n            return callback()\r\n        }\r\n\r\n        rules.required(rule, value, source, errors, options)\r\n\r\n        if (value !== undefined) {\r\n            rules.type(rule, value, source, errors, options)\r\n            rules.range(rule, value, source, errors, options)\r\n        }\r\n    }\r\n\r\n    callback(errors)\r\n}\r\n\r\n/**\r\n *  Validates a number is a floating point number.\r\n *\r\n *  @param rule The validation rule.\r\n *  @param value The value of the field on the source object.\r\n *  @param callback The callback function.\r\n *  @param source The source object being validated.\r\n *  @param options The validation options.\r\n *  @param options.messages The validation messages.\r\n */\r\n\r\nfunction floatFn(rule, value, callback, source, options) {\r\n    const errors = []\r\n    const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)\r\n\r\n    if (validate) {\r\n        if (isEmptyValue(value) && !rule.required) {\r\n            return callback()\r\n        }\r\n\r\n        rules.required(rule, value, source, errors, options)\r\n\r\n        if (value !== undefined) {\r\n            rules.type(rule, value, source, errors, options)\r\n            rules.range(rule, value, source, errors, options)\r\n        }\r\n    }\r\n\r\n    callback(errors)\r\n}\r\n\r\n/**\r\n *  Validates an array.\r\n *\r\n *  @param rule The validation rule.\r\n *  @param value The value of the field on the source object.\r\n *  @param callback The callback function.\r\n *  @param source The source object being validated.\r\n *  @param options The validation options.\r\n *  @param options.messages The validation messages.\r\n */\r\n\r\nfunction array(rule, value, callback, source, options) {\r\n    const errors = []\r\n    const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)\r\n\r\n    if (validate) {\r\n        if (isEmptyValue(value, 'array') && !rule.required) {\r\n            return callback()\r\n        }\r\n\r\n        rules.required(rule, value, source, errors, options, 'array')\r\n\r\n        if (!isEmptyValue(value, 'array')) {\r\n            rules.type(rule, value, source, errors, options)\r\n            rules.range(rule, value, source, errors, options)\r\n        }\r\n    }\r\n\r\n    callback(errors)\r\n}\r\n\r\n/**\r\n *  Validates an object.\r\n *\r\n *  @param rule The validation rule.\r\n *  @param value The value of the field on the source object.\r\n *  @param callback The callback function.\r\n *  @param source The source object being validated.\r\n *  @param options The validation options.\r\n *  @param options.messages The validation messages.\r\n */\r\n\r\nfunction object(rule, value, callback, source, options) {\r\n    const errors = []\r\n    const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)\r\n\r\n    if (validate) {\r\n        if (isEmptyValue(value) && !rule.required) {\r\n            return callback()\r\n        }\r\n\r\n        rules.required(rule, value, source, errors, options)\r\n\r\n        if (value !== undefined) {\r\n            rules.type(rule, value, source, errors, options)\r\n        }\r\n    }\r\n\r\n    callback(errors)\r\n}\r\n\r\nconst ENUM$1 = 'enum'\r\n/**\r\n *  Validates an enumerable list.\r\n *\r\n *  @param rule The validation rule.\r\n *  @param value The value of the field on the source object.\r\n *  @param callback The callback function.\r\n *  @param source The source object being validated.\r\n *  @param options The validation options.\r\n *  @param options.messages The validation messages.\r\n */\r\n\r\nfunction enumerable$1(rule, value, callback, source, options) {\r\n    const errors = []\r\n    const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)\r\n\r\n    if (validate) {\r\n        if (isEmptyValue(value) && !rule.required) {\r\n            return callback()\r\n        }\r\n\r\n        rules.required(rule, value, source, errors, options)\r\n\r\n        if (value !== undefined) {\r\n            rules[ENUM$1](rule, value, source, errors, options)\r\n        }\r\n    }\r\n\r\n    callback(errors)\r\n}\r\n\r\n/**\r\n *  Validates a regular expression pattern.\r\n *\r\n *  Performs validation when a rule only contains\r\n *  a pattern property but is not declared as a string type.\r\n *\r\n *  @param rule The validation rule.\r\n *  @param value The value of the field on the source object.\r\n *  @param callback The callback function.\r\n *  @param source The source object being validated.\r\n *  @param options The validation options.\r\n *  @param options.messages The validation messages.\r\n */\r\n\r\nfunction pattern$2(rule, value, callback, source, options) {\r\n    const errors = []\r\n    const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)\r\n\r\n    if (validate) {\r\n        if (isEmptyValue(value, 'string') && !rule.required) {\r\n            return callback()\r\n        }\r\n\r\n        rules.required(rule, value, source, errors, options)\r\n\r\n        if (!isEmptyValue(value, 'string')) {\r\n            rules.pattern(rule, value, source, errors, options)\r\n        }\r\n    }\r\n\r\n    callback(errors)\r\n}\r\n\r\nfunction date(rule, value, callback, source, options) {\r\n    const errors = []\r\n    const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)\r\n\r\n    if (validate) {\r\n        if (isEmptyValue(value) && !rule.required) {\r\n            return callback()\r\n        }\r\n\r\n        rules.required(rule, value, source, errors, options)\r\n\r\n        if (!isEmptyValue(value)) {\r\n            let dateObject\r\n\r\n            if (typeof value === 'number') {\r\n                dateObject = new Date(value)\r\n            } else {\r\n                dateObject = value\r\n            }\r\n\r\n            rules.type(rule, dateObject, source, errors, options)\r\n\r\n            if (dateObject) {\r\n                rules.range(rule, dateObject.getTime(), source, errors, options)\r\n            }\r\n        }\r\n    }\r\n\r\n    callback(errors)\r\n}\r\n\r\nfunction required$1(rule, value, callback, source, options) {\r\n    const errors = []\r\n    const type = Array.isArray(value) ? 'array' : typeof value\r\n    rules.required(rule, value, source, errors, options, type)\r\n    callback(errors)\r\n}\r\n\r\nfunction type$1(rule, value, callback, source, options) {\r\n    const ruleType = rule.type\r\n    const errors = []\r\n    const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)\r\n\r\n    if (validate) {\r\n        if (isEmptyValue(value, ruleType) && !rule.required) {\r\n            return callback()\r\n        }\r\n\r\n        rules.required(rule, value, source, errors, options, ruleType)\r\n\r\n        if (!isEmptyValue(value, ruleType)) {\r\n            rules.type(rule, value, source, errors, options)\r\n        }\r\n    }\r\n\r\n    callback(errors)\r\n}\r\n\r\n/**\r\n *  Performs validation for any type.\r\n *\r\n *  @param rule The validation rule.\r\n *  @param value The value of the field on the source object.\r\n *  @param callback The callback function.\r\n *  @param source The source object being validated.\r\n *  @param options The validation options.\r\n *  @param options.messages The validation messages.\r\n */\r\n\r\nfunction any(rule, value, callback, source, options) {\r\n    const errors = []\r\n    const validate = rule.required || !rule.required && source.hasOwnProperty(rule.field)\r\n\r\n    if (validate) {\r\n        if (isEmptyValue(value) && !rule.required) {\r\n            return callback()\r\n        }\r\n\r\n        rules.required(rule, value, source, errors, options)\r\n    }\r\n\r\n    callback(errors)\r\n}\r\n\r\nconst validators = {\r\n    string,\r\n    method,\r\n    number,\r\n    boolean: _boolean,\r\n    regexp,\r\n    integer,\r\n    float: floatFn,\r\n    array,\r\n    object,\r\n    enum: enumerable$1,\r\n    pattern: pattern$2,\r\n    date,\r\n    url: type$1,\r\n    hex: type$1,\r\n    email: type$1,\r\n    required: required$1,\r\n    any\r\n}\r\n\r\nfunction newMessages() {\r\n    return {\r\n        default: 'Validation error on field %s',\r\n        required: '%s is required',\r\n        enum: '%s must be one of %s',\r\n        whitespace: '%s cannot be empty',\r\n        date: {\r\n            format: '%s date %s is invalid for format %s',\r\n            parse: '%s date could not be parsed, %s is invalid ',\r\n            invalid: '%s date %s is invalid'\r\n        },\r\n        types: {\r\n            string: '%s is not a %s',\r\n            method: '%s is not a %s (function)',\r\n            array: '%s is not an %s',\r\n            object: '%s is not an %s',\r\n            number: '%s is not a %s',\r\n            date: '%s is not a %s',\r\n            boolean: '%s is not a %s',\r\n            integer: '%s is not an %s',\r\n            float: '%s is not a %s',\r\n            regexp: '%s is not a valid %s',\r\n            email: '%s is not a valid %s',\r\n            url: '%s is not a valid %s',\r\n            hex: '%s is not a valid %s'\r\n        },\r\n        string: {\r\n            len: '%s must be exactly %s characters',\r\n            min: '%s must be at least %s characters',\r\n            max: '%s cannot be longer than %s characters',\r\n            range: '%s must be between %s and %s characters'\r\n        },\r\n        number: {\r\n            len: '%s must equal %s',\r\n            min: '%s cannot be less than %s',\r\n            max: '%s cannot be greater than %s',\r\n            range: '%s must be between %s and %s'\r\n        },\r\n        array: {\r\n            len: '%s must be exactly %s in length',\r\n            min: '%s cannot be less than %s in length',\r\n            max: '%s cannot be greater than %s in length',\r\n            range: '%s must be between %s and %s in length'\r\n        },\r\n        pattern: {\r\n            mismatch: '%s value %s does not match pattern %s'\r\n        },\r\n        clone: function clone() {\r\n            const cloned = JSON.parse(JSON.stringify(this))\r\n            cloned.clone = this.clone\r\n            return cloned\r\n        }\r\n    }\r\n}\r\nconst messages = newMessages()\r\n\r\n/**\r\n *  Encapsulates a validation schema.\r\n *\r\n *  @param descriptor An object declaring validation rules\r\n *  for this schema.\r\n */\r\n\r\nfunction Schema(descriptor) {\r\n    this.rules = null\r\n    this._messages = messages\r\n    this.define(descriptor)\r\n}\r\n\r\nSchema.prototype = {\r\n    messages: function messages(_messages) {\r\n        if (_messages) {\r\n            this._messages = deepMerge(newMessages(), _messages)\r\n        }\r\n\r\n        return this._messages\r\n    },\r\n    define: function define(rules) {\r\n        if (!rules) {\r\n            throw new Error('Cannot configure a schema with no rules')\r\n        }\r\n\r\n        if (typeof rules !== 'object' || Array.isArray(rules)) {\r\n            throw new Error('Rules must be an object')\r\n        }\r\n\r\n        this.rules = {}\r\n        let z\r\n        let item\r\n\r\n        for (z in rules) {\r\n            if (rules.hasOwnProperty(z)) {\r\n                item = rules[z]\r\n                this.rules[z] = Array.isArray(item) ? item : [item]\r\n            }\r\n        }\r\n    },\r\n    validate: function validate(source_, o, oc) {\r\n        const _this = this\r\n\r\n        if (o === void 0) {\r\n            o = {}\r\n        }\r\n\r\n        if (oc === void 0) {\r\n            oc = function oc() {}\r\n        }\r\n\r\n        let source = source_\r\n        let options = o\r\n        let callback = oc\r\n\r\n        if (typeof options === 'function') {\r\n            callback = options\r\n            options = {}\r\n        }\r\n\r\n        if (!this.rules || Object.keys(this.rules).length === 0) {\r\n            if (callback) {\r\n                callback()\r\n            }\r\n\r\n            return Promise.resolve()\r\n        }\r\n\r\n        function complete(results) {\r\n            let i\r\n            let errors = []\r\n            let fields = {}\r\n\r\n            function add(e) {\r\n                if (Array.isArray(e)) {\r\n                    let _errors\r\n\r\n                    errors = (_errors = errors).concat.apply(_errors, e)\r\n                } else {\r\n                    errors.push(e)\r\n                }\r\n            }\r\n\r\n            for (i = 0; i < results.length; i++) {\r\n                add(results[i])\r\n            }\r\n\r\n            if (!errors.length) {\r\n                errors = null\r\n                fields = null\r\n            } else {\r\n                fields = convertFieldsError(errors)\r\n            }\r\n\r\n            callback(errors, fields)\r\n        }\r\n\r\n        if (options.messages) {\r\n            let messages$1 = this.messages()\r\n\r\n            if (messages$1 === messages) {\r\n                messages$1 = newMessages()\r\n            }\r\n\r\n            deepMerge(messages$1, options.messages)\r\n            options.messages = messages$1\r\n        } else {\r\n            options.messages = this.messages()\r\n        }\r\n\r\n        let arr\r\n        let value\r\n        const series = {}\r\n        const keys = options.keys || Object.keys(this.rules)\r\n        keys.forEach((z) => {\r\n            arr = _this.rules[z]\r\n            value = source[z]\r\n            arr.forEach((r) => {\r\n                let rule = r\r\n\r\n                if (typeof rule.transform === 'function') {\r\n                    if (source === source_) {\r\n                        source = { ...source }\r\n                    }\r\n\r\n                    value = source[z] = rule.transform(value)\r\n                }\r\n\r\n                if (typeof rule === 'function') {\r\n                    rule = {\r\n                        validator: rule\r\n                    }\r\n                } else {\r\n                    rule = { ...rule }\r\n                }\r\n\r\n                rule.validator = _this.getValidationMethod(rule)\r\n                rule.field = z\r\n                rule.fullField = rule.fullField || z\r\n                rule.type = _this.getType(rule)\r\n\r\n                if (!rule.validator) {\r\n                    return\r\n                }\r\n\r\n                series[z] = series[z] || []\r\n                series[z].push({\r\n                    rule,\r\n                    value,\r\n                    source,\r\n                    field: z\r\n                })\r\n            })\r\n        })\r\n        const errorFields = {}\r\n        return asyncMap(series, options, (data, doIt) => {\r\n            const { rule } = data\r\n            let deep = (rule.type === 'object' || rule.type === 'array') && (typeof rule.fields === 'object' || typeof rule.defaultField\r\n\t\t\t\t=== 'object')\r\n            deep = deep && (rule.required || !rule.required && data.value)\r\n            rule.field = data.field\r\n\r\n            function addFullfield(key, schema) {\r\n                return { ...schema, fullField: `${rule.fullField}.${key}` }\r\n            }\r\n\r\n            function cb(e) {\r\n                if (e === void 0) {\r\n                    e = []\r\n                }\r\n\r\n                let errors = e\r\n\r\n                if (!Array.isArray(errors)) {\r\n                    errors = [errors]\r\n                }\r\n\r\n                if (!options.suppressWarning && errors.length) {\r\n                    Schema.warning('async-validator:', errors)\r\n                }\r\n\r\n                if (errors.length && rule.message) {\r\n                    errors = [].concat(rule.message)\r\n                }\r\n\r\n                errors = errors.map(complementError(rule))\r\n\r\n                if (options.first && errors.length) {\r\n                    errorFields[rule.field] = 1\r\n                    return doIt(errors)\r\n                }\r\n\r\n                if (!deep) {\r\n                    doIt(errors)\r\n                } else {\r\n                    // if rule is required but the target object\r\n                    // does not exist fail at the rule level and don't\r\n                    // go deeper\r\n                    if (rule.required && !data.value) {\r\n                        if (rule.message) {\r\n                            errors = [].concat(rule.message).map(complementError(rule))\r\n                        } else if (options.error) {\r\n                            errors = [options.error(rule, format(options.messages.required, rule.field))]\r\n                        } else {\r\n                            errors = []\r\n                        }\r\n\r\n                        return doIt(errors)\r\n                    }\r\n\r\n                    let fieldsSchema = {}\r\n\r\n                    if (rule.defaultField) {\r\n                        for (const k in data.value) {\r\n                            if (data.value.hasOwnProperty(k)) {\r\n                                fieldsSchema[k] = rule.defaultField\r\n                            }\r\n                        }\r\n                    }\r\n\r\n                    fieldsSchema = { ...fieldsSchema, ...data.rule.fields }\r\n\r\n                    for (const f in fieldsSchema) {\r\n                        if (fieldsSchema.hasOwnProperty(f)) {\r\n                            const fieldSchema = Array.isArray(fieldsSchema[f]) ? fieldsSchema[f] : [fieldsSchema[f]]\r\n                            fieldsSchema[f] = fieldSchema.map(addFullfield.bind(null, f))\r\n                        }\r\n                    }\r\n\r\n                    const schema = new Schema(fieldsSchema)\r\n                    schema.messages(options.messages)\r\n\r\n                    if (data.rule.options) {\r\n                        data.rule.options.messages = options.messages\r\n                        data.rule.options.error = options.error\r\n                    }\r\n\r\n                    schema.validate(data.value, data.rule.options || options, (errs) => {\r\n                        const finalErrors = []\r\n\r\n                        if (errors && errors.length) {\r\n                            finalErrors.push.apply(finalErrors, errors)\r\n                        }\r\n\r\n                        if (errs && errs.length) {\r\n                            finalErrors.push.apply(finalErrors, errs)\r\n                        }\r\n\r\n                        doIt(finalErrors.length ? finalErrors : null)\r\n                    })\r\n                }\r\n            }\r\n\r\n            let res\r\n\r\n            if (rule.asyncValidator) {\r\n                res = rule.asyncValidator(rule, data.value, cb, data.source, options)\r\n            } else if (rule.validator) {\r\n                res = rule.validator(rule, data.value, cb, data.source, options)\r\n\r\n                if (res === true) {\r\n                    cb()\r\n                } else if (res === false) {\r\n                    cb(rule.message || `${rule.field} fails`)\r\n                } else if (res instanceof Array) {\r\n                    cb(res)\r\n                } else if (res instanceof Error) {\r\n                    cb(res.message)\r\n                }\r\n            }\r\n\r\n            if (res && res.then) {\r\n                res.then(() => cb(), (e) => cb(e))\r\n            }\r\n        }, (results) => {\r\n            complete(results)\r\n        })\r\n    },\r\n    getType: function getType(rule) {\r\n        if (rule.type === undefined && rule.pattern instanceof RegExp) {\r\n            rule.type = 'pattern'\r\n        }\r\n\r\n        if (typeof rule.validator !== 'function' && rule.type && !validators.hasOwnProperty(rule.type)) {\r\n            throw new Error(format('Unknown rule type %s', rule.type))\r\n        }\r\n\r\n        return rule.type || 'string'\r\n    },\r\n    getValidationMethod: function getValidationMethod(rule) {\r\n        if (typeof rule.validator === 'function') {\r\n            return rule.validator\r\n        }\r\n\r\n        const keys = Object.keys(rule)\r\n        const messageIndex = keys.indexOf('message')\r\n\r\n        if (messageIndex !== -1) {\r\n            keys.splice(messageIndex, 1)\r\n        }\r\n\r\n        if (keys.length === 1 && keys[0] === 'required') {\r\n            return validators.required\r\n        }\r\n\r\n        return validators[this.getType(rule)] || false\r\n    }\r\n}\r\n\r\nSchema.register = function register(type, validator) {\r\n    if (typeof validator !== 'function') {\r\n        throw new Error('Cannot register a validator by type, validator is not a function')\r\n    }\r\n\r\n    validators[type] = validator\r\n}\r\n\r\nSchema.warning = warning\r\nSchema.messages = messages\r\n\r\nexport default Schema\r\n// # sourceMappingURL=index.js.map\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-form/components/uv-form-item/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// input的label提示语\r\n\t\tlabel: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 绑定的值\r\n\t\tprop: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 是否显示表单域的下划线边框\r\n\t\tborderBottom: {\r\n\t\t\ttype: [Boolean],\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// label的位置，left-左边，top-上边\r\n\t\tlabelPosition: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// label的宽度，单位px\r\n\t\tlabelWidth: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 右侧图标\r\n\t\trightIcon: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 左侧图标\r\n\t\tleftIcon: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 是否显示左边的必填星号，只作显示用，具体校验必填的逻辑，请在rules中配置\r\n\t\trequired: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\tleftIconStyle: {\r\n\t\t\ttype: [String, Object],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t...uni.$uv?.props?.formItem\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-form/components/uv-form-item/uv-form-item.vue",
    "content": "<template>\r\n\t<view class=\"uv-form-item\">\r\n\t\t<view\r\n\t\t\tclass=\"uv-form-item__body\"\r\n\t\t\t@tap=\"clickHandler\"\r\n\t\t\t:style=\"[$uv.addStyle(customStyle), {\r\n\t\t\t\tflexDirection: (labelPosition || parentData.labelPosition) === 'left' ? 'row' : 'column'\r\n\t\t\t}]\"\r\n\t\t>\r\n\t\t\t<!-- 微信小程序中，将一个参数设置空字符串，结果会变成字符串\"true\" -->\r\n\t\t\t<slot name=\"label\">\r\n\t\t\t\t<view\r\n\t\t\t\t\tclass=\"uv-form-item__body__left\"\r\n\t\t\t\t\tv-if=\"required || leftIcon || label\"\r\n\t\t\t\t\t:style=\"{\r\n\t\t\t\t\t\twidth: $uv.addUnit(labelWidth || parentData.labelWidth),\r\n\t\t\t\t\t\tmarginBottom: parentData.labelPosition === 'left' ? 0 : '5px',\r\n\t\t\t\t\t}\"\r\n\t\t\t\t>\r\n\t\t\t\t\t<!-- 为了块对齐 -->\r\n\t\t\t\t\t<view class=\"uv-form-item__body__left__content\">\r\n\t\t\t\t\t\t<!-- nvue不支持伪元素before -->\r\n\t\t\t\t\t\t<text\r\n\t\t\t\t\t\t\tv-if=\"required\"\r\n\t\t\t\t\t\t\tclass=\"uv-form-item__body__left__content__required\"\r\n\t\t\t\t\t\t>*</text>\r\n\t\t\t\t\t\t<view\r\n\t\t\t\t\t\t\tclass=\"uv-form-item__body__left__content__icon\"\r\n\t\t\t\t\t\t\tv-if=\"leftIcon\"\r\n\t\t\t\t\t\t>\r\n\t\t\t\t\t\t\t<uv-icon\r\n\t\t\t\t\t\t\t\t:name=\"leftIcon\"\r\n\t\t\t\t\t\t\t\t:custom-style=\"leftIconStyle\"\r\n\t\t\t\t\t\t\t></uv-icon>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<text\r\n\t\t\t\t\t\t\tclass=\"uv-form-item__body__left__content__label\"\r\n\t\t\t\t\t\t\t:style=\"[parentData.labelStyle, {\r\n\t\t\t\t\t\t\t\tjustifyContent: parentData.labelAlign === 'left' ? 'flex-start' : parentData.labelAlign === 'center' ? 'center' : 'flex-end'\r\n\t\t\t\t\t\t\t}]\"\r\n\t\t\t\t\t\t>{{ label }}</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</slot>\r\n\t\t\t<view class=\"uv-form-item__body__right\">\r\n\t\t\t\t<view class=\"uv-form-item__body__right__content\">\r\n\t\t\t\t\t<view class=\"uv-form-item__body__right__content__slot\">\r\n\t\t\t\t\t\t<slot />\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"item__body__right__content__icon\">\r\n\t\t\t\t\t\t<slot name=\"right\" />\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t\t<slot name=\"error\">\r\n\t\t\t<uv-transition \r\n\t\t\t:show=\"true\" \r\n\t\t\t:duration=\"100\" \r\n\t\t\tmode=\"fade\" \r\n\t\t\tv-if=\"!!message && parentData.errorType === 'message'\"\r\n\t\t>\r\n\t\t\t\t<text\r\n\t\t\t\t\tclass=\"uv-form-item__body__right__message\"\r\n\t\t\t\t\t:style=\"{\r\n\t\t\t\t\t\tmarginLeft:  $uv.addUnit(parentData.labelPosition === 'top' ? 0 : (labelWidth || parentData.labelWidth))\r\n\t\t\t\t\t}\"\r\n\t\t\t\t>{{ message }}</text>\r\n\t\t\t</uv-transition>\r\n\t\t</slot>\r\n\t\t<uv-line\r\n\t\t\tv-if=\"borderBottom\"\r\n\t\t\t:color=\"message && parentData.errorType === 'border-bottom' ? '#f56c6c' : '#d6d7d9'\"\r\n\t\t></uv-line>\r\n\t</view>\r\n</template>\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * Form 表单\r\n\t * @description 此组件一般用于表单场景，可以配置Input输入框，Select弹出框，进行表单验证等。\r\n\t * @tutorial https://www.uvui.cn/components/form.html\r\n\t * @property {String}\t\t\tlabel\t\t\tinput的label提示语\r\n\t * @property {String}\t\t\tprop\t\t\t绑定的值\r\n\t * @property {String | Boolean}\tborderBottom\t是否显示表单域的下划线边框\r\n\t * @property {String | Number}\tlabelWidth\t\tlabel的宽度，单位px\r\n\t * @property {String}\t\t\trightIcon\t\t右侧图标\r\n\t * @property {String}\t\t\tleftIcon\t\t左侧图标\r\n\t * @property {String | Object} leftIconStyle 左侧图标的样式\r\n\t * @property {Boolean}\t\t\trequired\t\t是否显示左边的必填星号，只作显示用，具体校验必填的逻辑，请在rules中配置 (默认 false )\r\n\t *\r\n\t * @example <uv-form-item label=\"姓名\" prop=\"userInfo.name\" borderBottom ref=\"item1\"></uv-form-item>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-form-item',\r\n\t\temits: ['click'],\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\t// 错误提示语\r\n\t\t\t\tmessage: '',\r\n\t\t\t\tparentData: {\r\n\t\t\t\t\t// 提示文本的位置\r\n\t\t\t\t\tlabelPosition: 'left',\r\n\t\t\t\t\t// 提示文本对齐方式\r\n\t\t\t\t\tlabelAlign: 'left',\r\n\t\t\t\t\t// 提示文本的样式\r\n\t\t\t\t\tlabelStyle: {},\r\n\t\t\t\t\t// 提示文本的宽度\r\n\t\t\t\t\tlabelWidth: 45,\r\n\t\t\t\t\t// 错误提示方式\r\n\t\t\t\t\terrorType: 'message'\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\tcreated() {\r\n\t\t\tthis.init()\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tinit() {\r\n\t\t\t\t// 父组件的实例\r\n\t\t\t\tthis.updateParentData()\r\n\t\t\t\tif (!this.parent) {\r\n\t\t\t\t\tthis.$uv.error('uv-form-item需要结合uv-form组件使用')\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t// 获取父组件的参数\r\n\t\t\tupdateParentData() {\r\n\t\t\t\t// 此方法写在mixin中\r\n\t\t\t\tthis.getParentData('uv-form');\r\n\t\t\t},\r\n\t\t\t// 移除uv-form-item的校验结果\r\n\t\t\tclearValidate() {\r\n\t\t\t\tthis.message = null\r\n\t\t\t},\r\n\t\t\t// 清空当前的组件的校验结果，并重置为初始值\r\n\t\t\tresetField() {\r\n\t\t\t\t// 找到原始值\r\n\t\t\t\tconst value = this.$uv.getProperty(this.parent.originalModel, this.prop)\r\n\t\t\t\t// 将uv-form的model的prop属性链还原原始值\r\n\t\t\t\tthis.$uv.setProperty(this.parent.model, this.prop, value)\r\n\t\t\t\t// 移除校验结果\r\n\t\t\t\tthis.message = null\r\n\t\t\t},\r\n\t\t\t// 点击组件\r\n\t\t\tclickHandler() {\r\n\t\t\t\tthis.$emit('click')\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t.uv-form-item {\r\n\t\t@include flex(column);\r\n\t\tfont-size: 14px;\r\n\t\tcolor: $uv-main-color;\r\n\t\t&__body {\r\n\t\t\t@include flex;\r\n\t\t\tpadding: 10px 0;\r\n\t\t\t&__left {\r\n\t\t\t\t@include flex;\r\n\t\t\t\talign-items: center;\r\n\t\t\t\t&__content {\r\n\t\t\t\t\tposition: relative;\r\n\t\t\t\t\t@include flex;\r\n\t\t\t\t\talign-items: center;\r\n\t\t\t\t\tpadding-right: 10rpx;\r\n\t\t\t\t\tflex: 1;\r\n\t\t\t\t\t&__icon {\r\n\t\t\t\t\t\tmargin-right: 8rpx;\r\n\t\t\t\t\t}\r\n\t\t\t\t\t&__required {\r\n\t\t\t\t\t\tposition: absolute;\r\n\t\t\t\t\t\tleft: -9px;\r\n\t\t\t\t\t\tcolor: $uv-error;\r\n\t\t\t\t\t\tline-height: 20px;\r\n\t\t\t\t\t\tfont-size: 20px;\r\n\t\t\t\t\t\ttop: 3px;\r\n\t\t\t\t\t}\r\n\t\t\t\t\t&__label {\r\n\t\t\t\t\t\t@include flex;\r\n\t\t\t\t\t\talign-items: center;\r\n\t\t\t\t\t\tflex: 1;\r\n\t\t\t\t\t\tcolor: $uv-main-color;\r\n\t\t\t\t\t\tfont-size: 15px;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t&__right {\r\n\t\t\t\tflex: 1;\r\n\t\t\t\t&__content {\r\n\t\t\t\t\t@include flex;\r\n\t\t\t\t\talign-items: center;\r\n\t\t\t\t\tflex: 1;\r\n\t\t\t\t\t&__slot {\r\n\t\t\t\t\t\tflex: 1;\r\n\t\t\t\t\t\t/* #ifndef MP */\r\n\t\t\t\t\t\t@include flex;\r\n\t\t\t\t\t\talign-items: center;\r\n\t\t\t\t\t\t/* #endif */\r\n\t\t\t\t\t}\r\n\t\t\t\t\t&__icon {\r\n\t\t\t\t\t\tmargin-left: 10rpx;\r\n\t\t\t\t\t\tcolor: $uv-light-color;\r\n\t\t\t\t\t\tfont-size: 30rpx;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t&__message__box {\r\n\t\t\t\t\theight: 16px;\r\n\t\t\t\t\tline-height: 16px;\r\n\t\t\t\t}\r\n\t\t\t\t&__message {\r\n\t\t\t\t\tmargin-top: -6px;\r\n\t\t\t\t\tline-height: 24px;\r\n\t\t\t\t\tfont-size: 12px;\r\n\t\t\t\t\tcolor: $uv-error;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</style>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-form/package.json",
    "content": "{\r\n  \"id\": \"uv-form\",\r\n  \"displayName\": \"uv-form 表单 全面兼容vue3+2、app、h5、小程序等多端\",\r\n  \"version\": \"1.0.9\",\r\n  \"description\": \"此组件一般用于表单场景，可以配置Input输入框，Textarea文本域，Checkbox复选框，Radio单选框，开关选择器等，进行表单验证等。\",\r\n  \"keywords\": [\r\n    \"form\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"表单\",\r\n    \"表单验证\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-icon\",\r\n\t\t\t\"uv-line\",\r\n\t\t\t\"uv-transition\",\r\n\t\t\t\"uv-action-sheet\",\r\n\t\t\t\"uv-input\",\r\n\t\t\t\"uv-button\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-form/readme.md",
    "content": "## Form 表单\n\n> **组件名：uv-form**\n\n此组件一般用于表单场景，可以配置`Input`输入框，`Textarea`文本域，`Checkbox`复选框，`Radio`单选框，开关选择器等，进行表单验证等。\n\n# <a href=\"https://www.uvui.cn/components/form.html\" target=\"_blank\">查看文档</a>\n\n## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small style=\"font-size:14px;font-weight:700;\">（请不要 下载插件ZIP）</small>\n\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\n\n<a href=\"https://ext.dcloud.net.cn/plugin?name=uv-ui\" target=\"_blank\">\n\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\n\n</a>\n\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-gap/changelog.md",
    "content": "## 1.0.1（2023-05-16）\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\n2. 优化部分功能\n## 1.0.0（2023-05-10）\n1. 新增间隔槽组件\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-gap/components/uv-gap/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 背景颜色（默认transparent）\r\n\t\tbgColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'transparent'\r\n\t\t},\r\n\t\t// 分割槽高度，单位px（默认20）\r\n\t\theight: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 20\r\n\t\t},\r\n\t\t// 与上一个组件的距离\r\n\t\tmarginTop: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 与下一个组件的距离\r\n\t\tmarginBottom: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t...uni.$uv?.props?.gap\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-gap/components/uv-gap/uv-gap.vue",
    "content": "<template>\r\n\t<view class=\"uv-gap\" :style=\"[gapStyle]\"></view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * gap 间隔槽\r\n\t * @description 该组件一般用于内容块之间的用一个灰色块隔开的场景，方便用户风格统一，减少工作量\r\n\t * @tutorial https://www.uvui.cn/components/gap.html\r\n\t * @property {String}\t\t\tbgColor\t\t\t背景颜色 （默认 'transparent' ）\r\n\t * @property {String | Number}\theight\t\t\t分割槽高度，单位px （默认 20 ）\r\n\t * @property {String | Number}\tmarginTop\t\t与前一个组件的距离，单位px（ 默认 0 ）\r\n\t * @property {String | Number}\tmarginBottom\t与后一个组件的距离，单位px （默认 0 ）\r\n\t * @property {Object}\t\t\tcustomStyle\t\t定义需要用到的外部样式\r\n\t * \r\n\t * @example <uv-gap height=\"80\" bg-color=\"#bbb\"></uv-gap>\r\n\t */\r\n\texport default {\r\n\t\tname: \"uv-gap\",\r\n\t\tmixins: [mpMixin, mixin,props],\r\n\t\tcomputed: {\r\n\t\t\tgapStyle() {\r\n\t\t\t\tconst style = {\r\n\t\t\t\t\tbackgroundColor: this.bgColor,\r\n\t\t\t\t\theight: this.$uv.addUnit(this.height),\r\n\t\t\t\t\tmarginTop: this.$uv.addUnit(this.marginTop),\r\n\t\t\t\t\tmarginBottom: this.$uv.addUnit(this.marginBottom),\r\n\t\t\t\t}\r\n\t\t\t\treturn this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))\r\n\t\t\t}\r\n\t\t}\r\n\t};\r\n</script>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-gap/package.json",
    "content": "{\r\n  \"id\": \"uv-gap\",\r\n  \"displayName\": \"uv-gap 间隔槽 全面兼容小程序、nvue、vue2、vue3等多端\",\r\n  \"version\": \"1.0.1\",\r\n  \"description\": \"该组件一般用于内容块之间的用一个灰色块隔开的场景，方便用户风格统一，减少工作量。\",\r\n  \"keywords\": [\r\n    \"gap\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"间隔槽\",\r\n    \"内容块\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-gap/readme.md",
    "content": "## Gap 间隔槽\r\n\r\n> **组件名：uv-gap**\r\n\r\n该组件一般用于内容块之间的用一个灰色块隔开的场景，方便用户风格统一，减少工作量。\r\n\r\n### <a href=\"https://www.uvui.cn/components/gap.html\" target=\"_blank\">查看文档</a>\r\n\r\n### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n#### 如使用过程中有任何问题，或者您对uv-ui有一些好的建议，欢迎加入 uv-ui 交流群：<a href=\"https://ext.dcloud.net.cn/plugin?id=12287\" target=\"_blank\">uv-ui</a>、<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-grid/changelog.md",
    "content": "## 1.0.7（2023-10-13）\n1. unmounted兼容vue3\n## 1.0.6（2023-08-14）\r\n1. 修复初始的时候闪动的BUG\r\n## 1.0.5（2023-06-22）\r\n1. 优化修改\r\n## 1.0.4（2023-06-21）\r\n1. 修复BUG\r\n## 1.0.3（2023-06-01）\r\n1. 修复点击触发两次事件的BUG \r\n## 1.0.2（2023-05-23）\r\n1. 优化\r\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-grid 宫格布局\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-grid/components/uv-grid/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 分成几列\r\n\t\tcol: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 3\r\n\t\t},\r\n\t\t// 是否显示边框\r\n\t\tborder: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 宫格对齐方式，表现为数量少的时候，靠左，居中，还是靠右\r\n\t\talign: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'left'\r\n\t\t},\r\n\t\t...uni.$uv?.props?.grid\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-grid/components/uv-grid/uv-grid.vue",
    "content": "<template>\r\n\t<view\r\n\t    class=\"uv-grid\"\r\n\t\tref='uv-grid'\r\n\t    :style=\"[gridStyle]\"\r\n\t>\r\n\t\t<slot />\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * grid 宫格布局\r\n\t * @description 宫格组件一般用于同时展示多个同类项目的场景，可以给宫格的项目设置徽标组件(badge)，或者图标等，也可以扩展为左右滑动的轮播形式。\r\n\t * @tutorial https://www.uvui.cn/components/grid.html\r\n\t * @property {String | Number}\tcol\t\t\t宫格的列数（默认 3 ）\r\n\t * @property {Boolean}\t\t\tborder\t\t是否显示宫格的边框（默认 false ）\r\n\t * @property {String}\t\t\talign\t\t宫格对齐方式，表现为数量少的时候，靠左，居中，还是靠右 （默认 'left' ）\r\n\t * @property {Object}\t\t\tcustomStyle\t定义需要用到的外部样式\r\n\t * @event {Function} click 点击宫格触发\r\n\t * @example <uv-grid :col=\"3\" @click=\"click\"></uv-grid>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-grid',\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\temits: ['click'],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tindex: 0,\r\n\t\t\t\twidth: 0\r\n\t\t\t}\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\t// 当父组件需要子组件需要共享的参数发生了变化，手动通知子组件\r\n\t\t\tparentData() {\r\n\t\t\t\tif (this.children.length) {\r\n\t\t\t\t\tthis.children.map(child => {\r\n\t\t\t\t\t\t// 判断子组件(uv-radio)如果有updateParentData方法的话，就就执行(执行的结果是子组件重新从父组件拉取了最新的值)\r\n\t\t\t\t\t\ttypeof(child.updateParentData) == 'function' && child.updateParentData();\r\n\t\t\t\t\t})\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t},\r\n\t\tcreated() {\r\n\t\t\t// 如果将children定义在data中，在微信小程序会造成循环引用而报错\r\n\t\t\tthis.children = []\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\t// 计算父组件的值是否发生变化\r\n\t\t\tparentData() {\r\n\t\t\t\treturn [this.hoverClass, this.col, this.size, this.border];\r\n\t\t\t},\r\n\t\t\t// 宫格对齐方式\r\n\t\t\tgridStyle() {\r\n\t\t\t\tlet style = {};\r\n\t\t\t\tswitch (this.align) {\r\n\t\t\t\t\tcase 'left':\r\n\t\t\t\t\t\tstyle.justifyContent = 'flex-start';\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase 'center':\r\n\t\t\t\t\t\tstyle.justifyContent = 'center';\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase 'right':\r\n\t\t\t\t\t\tstyle.justifyContent = 'flex-end';\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tdefault:\r\n\t\t\t\t\t\tstyle.justifyContent = 'flex-start';\r\n\t\t\t\t};\r\n\t\t\t\treturn this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle));\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\t// 此方法由uv-grid-item触发，用于在uv-grid发出事件\r\n\t\t\tchildClick(name) {\r\n\t\t\t\tthis.$emit('click', name)\r\n\t\t\t}\r\n\t\t}\r\n\t};\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n     $uv-grid-width:100% !default;\r\n\t.uv-grid {\r\n\t\t/* #ifdef MP */\r\n\t\twidth: $uv-grid-width;\r\n\t\tposition: relative;\r\n\t\tbox-sizing: border-box;\r\n\t\toverflow: hidden;\r\n\t\tdisplay: block;\r\n\t\t/* #endif */\r\n\t\tjustify-content: center;\r\n\t\t@include flex;\r\n\t\tflex-wrap: wrap;\r\n\t\talign-items: center;\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-grid/components/uv-grid-item/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 宫格的name\r\n\t\tname: {\r\n\t\t\ttype: [String, Number, null],\r\n\t\t\tdefault: null\r\n\t\t},\r\n\t\t// 背景颜色\r\n\t\tbgColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'transparent'\r\n\t\t},\r\n\t\t...uni.$uv?.props?.gridItem\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-grid/components/uv-grid-item/uv-grid-item.vue",
    "content": "<template>\r\n\t<!-- #ifndef APP-NVUE -->\r\n\t<view\r\n\t    class=\"uv-grid-item\"\r\n\t    hover-class=\"uv-grid-item--hover-class\"\r\n\t    :hover-stay-time=\"200\"\r\n\t    @tap=\"clickHandler\"\r\n\t    :class=\"classes\"\r\n\t    :style=\"[itemStyle]\"\r\n\t>\r\n\t\t<slot />\r\n\t</view>\r\n\t<!-- #endif -->\r\n\t<!-- #ifdef APP-NVUE -->\r\n\t<view\r\n\t    class=\"uv-grid-item\"\r\n\t    :hover-stay-time=\"200\"\r\n\t    @tap=\"clickHandler\"\r\n\t    :class=\"classes\"\r\n\t    :style=\"[itemStyle]\"\r\n\t>\r\n\t\t<slot />\r\n\t</view>\r\n\t<!-- #endif -->\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * gridItem 提示\r\n\t * @description 宫格组件一般用于同时展示多个同类项目的场景，可以给宫格的项目设置徽标组件(badge)，或者图标等，也可以扩展为左右滑动的轮播形式。搭配uv-grid使用\r\n\t * @tutorial https://www.uvui.cn/components/grid.html\r\n\t * @property {String | Number}\tname\t\t宫格的name ( 默认 null )\r\n\t * @property {String}\t\t\tbgColor\t\t宫格的背景颜色 （默认 'transparent' ）\r\n\t * @property {Object}\t\t\tcustomStyle\t自定义样式，对象形式\r\n\t * @event {Function} click 点击宫格触发\r\n\t * @example <uv-grid-item></uv-grid-item>\r\n\t */\r\n\texport default {\r\n\t\tname: \"uv-grid-item\",\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\temits: ['$uvGridItem','click'],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tparentData: {\r\n\t\t\t\t\tcol: 3, // 父组件划分的宫格数\r\n\t\t\t\t\tborder: true, // 是否显示边框，根据父组件决定\r\n\t\t\t\t},\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\twidth: 0, // nvue下才这么计算，vue下放到computed中，否则会因为延时造成闪烁\r\n\t\t\t\t// #endif\r\n\t\t\t\tclasses: [], // 类名集合，用于判断是否显示右边和下边框\r\n\t\t\t};\r\n\t\t},\r\n\t\tcreated() {\r\n\t\t\t// 父组件的实例\r\n\t\t\tthis.updateParentData()\r\n\t\t},\r\n\t\tmounted() {\r\n\t\t\tthis.init()\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\t// #ifndef APP-NVUE\r\n\t\t\t// vue下放到computed中，否则会因为延时造成闪烁\r\n\t\t\twidth() {\r\n\t\t\t\treturn 100 / Number(this.parentData.col) + '%'\r\n\t\t\t},\r\n\t\t\t// #endif\r\n\t\t\titemStyle() {\r\n\t\t\t\tconst style = {\r\n\t\t\t\t\tbackground: this.bgColor,\r\n\t\t\t\t\twidth: this.width\r\n\t\t\t\t}\r\n\t\t\t\treturn this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tinit() {\r\n\t\t\t\t// 用于在父组件uv-grid的children中被添加入子组件时，\r\n\t\t\t\t// 重新计算item的边框\r\n\t\t\t\tuni.$on('$uvGridItem', () => {\r\n\t\t\t\t\tthis.gridItemClasses()\r\n\t\t\t\t})\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\t// 获取元素该有的长度，nvue下要延时才准确\r\n\t\t\t\tthis.$nextTick(function(){\r\n\t\t\t\t\tthis.getItemWidth()\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\t\t\t\t// 发出事件，通知所有的grid-item都重新计算自己的边框\r\n\t\t\t\tuni.$emit('$uvGridItem')\r\n\t\t\t\tthis.gridItemClasses()\r\n\t\t\t},\r\n\t\t\t// 获取父组件的参数\r\n\t\t\tupdateParentData() {\r\n\t\t\t\t// 此方法写在mixin中\r\n\t\t\t\tthis.getParentData('uv-grid');\r\n\t\t\t},\r\n\t\t\tclickHandler() {\r\n\t\t\t\tlet name = this.name\r\n\t\t\t\t// 如果没有设置name属性，历遍父组件的children数组，判断当前的元素是否和本实例this相等，找出当前组件的索引\r\n\t\t\t\tconst children = this.parent?.children\r\n\t\t\t\tif(children && this.name === null) {\r\n\t\t\t\t\tname = children.findIndex(child => child === this)\r\n\t\t\t\t}\r\n\t\t\t\t// 调用父组件方法，发出事件\r\n\t\t\t\tthis.parent && this.parent.childClick(name)\r\n\t\t\t\tthis.$emit('click', name)\r\n\t\t\t},\r\n\t\t\tasync getItemWidth() {\r\n\t\t\t\t// 如果是nvue，不能使用百分比，只能使用固定宽度\r\n\t\t\t\tlet width = 0\r\n\t\t\t\tif(this.parent) {\r\n\t\t\t\t\t// 获取父组件宽度后，除以栅格数，得出每个item的宽度\r\n\t\t\t\t\tconst parentWidth = await this.getParentWidth()\r\n\t\t\t\t\twidth = parentWidth / Number(this.parentData.col) + 'px'\r\n\t\t\t\t}\r\n\t\t\t\tthis.width = width\r\n\t\t\t},\r\n\t\t\t// 获取父元素的尺寸\r\n\t\t\tgetParentWidth() {\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\t// 返回一个promise，让调用者可以用await同步获取\r\n\t\t\t\tconst dom = uni.requireNativePlugin('dom')\r\n\t\t\t\treturn new Promise(resolve => {\r\n\t\t\t\t\t// 调用父组件的ref\r\n\t\t\t\t\tdom.getComponentRect(this.parent.$refs['uv-grid'], res => {\r\n\t\t\t\t\t\tresolve(res.size.width)\r\n\t\t\t\t\t})\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\t\t\t},\r\n\t\t\tgridItemClasses() {\r\n\t\t\t\tif(this.parentData.border) {\r\n\t\t\t\t\tlet classes = []\r\n\t\t\t\t\tthis.parent.children.map((child, index) =>{\r\n\t\t\t\t\t\tif(this === child) {\r\n\t\t\t\t\t\t\tconst len = this.parent.children.length\r\n\t\t\t\t\t\t\t// 贴近右边屏幕边沿的child，并且最后一个（比如只有横向2个的时候），无需右边框\r\n\t\t\t\t\t\t\tif((index + 1) % this.parentData.col !== 0 && index + 1 !== len) {\r\n\t\t\t\t\t\t\t\tclasses.push('uv-border-right')\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t// 总的宫格数量对列数取余的值\r\n\t\t\t\t\t\t\t// 如果取余后，值为0，则意味着要将最后一排的宫格，都不需要下边框\r\n\t\t\t\t\t\t\tconst lessNum = len % this.parentData.col === 0 ? this.parentData.col : len % this.parentData.col\r\n\t\t\t\t\t\t\t// 最下面的一排child，无需下边框\r\n\t\t\t\t\t\t\tif(index < len - lessNum) {\r\n\t\t\t\t\t\t\t\tclasses.push('uv-border-bottom')\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t})\r\n\t\t\t\t\t// 支付宝，头条小程序无法动态绑定一个数组类名，否则解析出来的结果会带有\",\"，而导致失效\r\n\t\t\t\t\t// #ifdef MP-ALIPAY || MP-TOUTIAO\r\n\t\t\t\t\tclasses = classes.join(' ')\r\n\t\t\t\t\t// #endif\r\n\t\t\t\t\tthis.classes = classes\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\t// #ifdef VUE2\r\n\t\tbeforeDestroy() {\r\n\t\t\t// 移除事件监听，释放性能\r\n\t\t\tuni.$off('$uvGridItem')\r\n\t\t},\r\n\t\t// #endif\r\n\t\t// #ifdef VUE3\r\n\t\tunmounted() {\r\n\t\t\t// 移除事件监听，释放性能\r\n\t\t\tuni.$off('$uvGridItem')\r\n\t\t}\r\n\t\t// #endif\r\n\t};\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t$show-border: 1;\r\n\t$show-border-right: 1;\r\n\t$show-border-bottom: 1;\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t$uv-grid-item-hover-class-opcatiy:.5 !default;\r\n\t$uv-grid-item-margin-top:1rpx !default;\r\n\t$uv-grid-item-border-right-width:0.5px !default;\r\n\t$uv-grid-item-border-bottom-width:0.5px !default;\r\n\t$uv-grid-item-border-right-color:$uv-border-color !default;\r\n\t$uv-grid-item-border-bottom-color:$uv-border-color !default;\r\n\t.uv-grid-item {\r\n\t\talign-items: center;\r\n\t\tjustify-content: center;\r\n\t\tposition: relative;\r\n\t\tflex-direction: column;\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\tbox-sizing: border-box;\r\n\t\tdisplay: flex;\r\n\t\t/* #endif */\r\n\r\n\t\t/* #ifdef MP */\r\n\t\tposition: relative;\r\n\t\tfloat: left;\r\n\t\t/* #endif */\r\n\r\n\t\t/* #ifdef MP-WEIXIN */\r\n\t\tmargin-top:$uv-grid-item-margin-top;\r\n\t\t/* #endif */\r\n\r\n\t\t&--hover-class {\r\n\t\t\topacity:$uv-grid-item-hover-class-opcatiy;\r\n\t\t}\r\n\t}\r\n\r\n\t/* #ifdef APP-NVUE */\r\n\t// 由于nvue不支持组件内引入app.vue中再引入的样式，所以需要写在这里\r\n\t.uv-border-right {\r\n\t\tborder-right-width:$uv-grid-item-border-right-width;\r\n\t\tborder-color: $uv-grid-item-border-right-color;\r\n\t}\r\n\r\n\t.uv-border-bottom {\r\n\t\tborder-bottom-width:$uv-grid-item-border-bottom-width;\r\n\t\tborder-color:$uv-grid-item-border-bottom-color;\r\n\t}\r\n\t/* #endif */\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-grid/package.json",
    "content": "{\r\n  \"id\": \"uv-grid\",\r\n  \"displayName\": \"uv-grid 宫格布局 全面兼容vue3+2、app、h5、小程序等多端\",\r\n  \"version\": \"1.0.7\",\r\n  \"description\": \"uv-grid 宫格组件一般用于同时展示多个同类项目的场景，可以给宫格的项目设置徽标组件(badge)，或者图标等，也可以扩展为左右滑动的轮播形式。\",\r\n  \"keywords\": [\r\n    \"uv-grid\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"宫格布局\",\r\n    \"grid\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-icon\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-grid/readme.md",
    "content": "## Grid 宫格布局\r\n\r\n> **组件名：uv-grid**\r\n\r\n宫格组件一般用于同时展示多个同类项目的场景，可以给宫格的项目设置徽标组件(badge)，或者图标等，也可以扩展为左右滑动的轮播形式。\r\n\r\n# <a href=\"https://www.uvui.cn/components/grid.html\" target=\"_blank\">查看文档</a>\r\n\r\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n<a href=\"https://ext.dcloud.net.cn/plugin?name=uv-ui\" target=\"_blank\">\r\n\r\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\r\n\r\n</a>\r\n\r\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-icon/changelog.md",
    "content": "## 1.0.10（2023-08-13）\n1. 优化nvue，方便自定义图标\n## 1.0.9（2023-07-28）\r\n1. 修改几个对应错误图标的BUG\r\n## 1.0.8（2023-07-24）\r\n1. 优化 支持base64图片\r\n## 1.0.7（2023-07-17）\r\n1. 修复  uv-icon 恢复uv-empty相关的图标\r\n## 1.0.6（2023-07-13）\r\n1. 修复icon设置name属性对应图标错误的BUG\r\n## 1.0.5（2023-07-04）\r\n1. 更新图标，删除一些不常用的图标\r\n2. 删除base64，修改成ttf文件引入读取图标\r\n3. 自定义图标文档说明：https://www.uvui.cn/guide/customIcon.html\r\n## 1.0.4（2023-07-03）\r\n1. 修复主题颜色在APP不生效的BUG\r\n## 1.0.3（2023-05-24）\r\n1. 将线上ttf字体包替换成base64，避免加载时或者网络差时候显示白色方块\r\n## 1.0.2（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.1（2023-05-10）\r\n1. 修复小程序中异常显示\r\n## 1.0.0（2023-05-04）\r\n新发版\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-icon/components/uv-icon/icons.js",
    "content": "export default {\r\n\t'uvicon-level': 'e68f',\r\n\t'uvicon-checkbox-mark': 'e659',\r\n\t'uvicon-folder': 'e694',\r\n\t'uvicon-movie': 'e67c',\r\n\t'uvicon-star-fill': 'e61e',\r\n\t'uvicon-star': 'e618',\r\n\t'uvicon-phone-fill': 'e6ac',\r\n\t'uvicon-phone': 'e6ba',\r\n\t'uvicon-apple-fill': 'e635',\r\n\t'uvicon-backspace': 'e64d',\r\n\t'uvicon-attach': 'e640',\r\n\t'uvicon-empty-data': 'e671',\r\n\t'uvicon-empty-address': 'e68a',\r\n\t'uvicon-empty-favor': 'e662',\r\n\t'uvicon-empty-car': 'e657',\r\n\t'uvicon-empty-order': 'e66b',\r\n\t'uvicon-empty-list': 'e672',\r\n\t'uvicon-empty-search': 'e677',\r\n\t'uvicon-empty-permission': 'e67d',\r\n\t'uvicon-empty-news': 'e67e',\r\n\t'uvicon-empty-history': 'e685',\r\n\t'uvicon-empty-coupon': 'e69b',\r\n\t'uvicon-empty-page': 'e60e',\r\n\t'uvicon-empty-wifi-off': 'e6cc',\r\n\t'uvicon-reload': 'e627',\r\n\t'uvicon-order': 'e695',\r\n\t'uvicon-server-man': 'e601',\r\n\t'uvicon-search': 'e632',\r\n\t'uvicon-more-dot-fill': 'e66f',\r\n\t'uvicon-scan': 'e631',\r\n\t'uvicon-map': 'e665',\r\n\t'uvicon-map-fill': 'e6a8',\r\n\t'uvicon-tags': 'e621',\r\n\t'uvicon-tags-fill': 'e613',\r\n\t'uvicon-eye': 'e664',\r\n\t'uvicon-eye-fill': 'e697',\r\n\t'uvicon-eye-off': 'e69c',\r\n\t'uvicon-eye-off-outline': 'e688',\r\n\t'uvicon-mic': 'e66d',\r\n\t'uvicon-mic-off': 'e691',\r\n\t'uvicon-calendar': 'e65c',\r\n\t'uvicon-trash': 'e623',\r\n\t'uvicon-trash-fill': 'e6ce',\r\n\t'uvicon-play-left': 'e6bf',\r\n\t'uvicon-play-right': 'e6b3',\r\n\t'uvicon-minus': 'e614',\r\n\t'uvicon-plus': 'e625',\r\n\t'uvicon-info-circle': 'e69f',\r\n\t'uvicon-info-circle-fill': 'e6a7',\r\n\t'uvicon-question-circle': 'e622',\r\n\t'uvicon-question-circle-fill': 'e6bc',\r\n\t'uvicon-close': 'e65a',\r\n\t'uvicon-checkmark': 'e64a',\r\n\t'uvicon-checkmark-circle': 'e643',\r\n\t'uvicon-checkmark-circle-fill': 'e668',\r\n\t'uvicon-setting': 'e602',\r\n\t'uvicon-setting-fill': 'e6d0',\r\n\t'uvicon-heart': 'e6a2',\r\n\t'uvicon-heart-fill': 'e68b',\r\n\t'uvicon-camera': 'e642',\r\n\t'uvicon-camera-fill': 'e650',\r\n\t'uvicon-more-circle': 'e69e',\r\n\t'uvicon-more-circle-fill': 'e684',\r\n\t'uvicon-chat': 'e656',\r\n\t'uvicon-chat-fill': 'e63f',\r\n\t'uvicon-bag': 'e647',\r\n\t'uvicon-error-circle': 'e66e',\r\n\t'uvicon-error-circle-fill': 'e655',\r\n\t'uvicon-close-circle': 'e64e',\r\n\t'uvicon-close-circle-fill': 'e666',\r\n\t'uvicon-share': 'e629',\r\n\t'uvicon-share-fill': 'e6bb',\r\n\t'uvicon-share-square': 'e6c4',\r\n\t'uvicon-shopping-cart': 'e6cb',\r\n\t'uvicon-shopping-cart-fill': 'e630',\r\n\t'uvicon-bell': 'e651',\r\n\t'uvicon-bell-fill': 'e604',\r\n\t'uvicon-list': 'e690',\r\n\t'uvicon-list-dot': 'e6a9',\r\n\t'uvicon-zhifubao-circle-fill': 'e617',\r\n\t'uvicon-weixin-circle-fill': 'e6cd',\r\n\t'uvicon-weixin-fill': 'e620',\r\n\t'uvicon-qq-fill': 'e608',\r\n\t'uvicon-qq-circle-fill': 'e6b9',\r\n\t'uvicon-moments-circel-fill': 'e6c2',\r\n\t'uvicon-moments': 'e6a0',\r\n\t'uvicon-car': 'e64f',\r\n\t'uvicon-car-fill': 'e648',\r\n\t'uvicon-warning-fill': 'e6c7',\r\n\t'uvicon-warning': 'e6c1',\r\n\t'uvicon-clock-fill': 'e64b',\r\n\t'uvicon-clock': 'e66c',\r\n\t'uvicon-edit-pen': 'e65d',\r\n\t'uvicon-edit-pen-fill': 'e679',\r\n\t'uvicon-email': 'e673',\r\n\t'uvicon-email-fill': 'e683',\r\n\t'uvicon-minus-circle': 'e6a5',\r\n\t'uvicon-plus-circle': 'e603',\r\n\t'uvicon-plus-circle-fill': 'e611',\r\n\t'uvicon-file-text': 'e687',\r\n\t'uvicon-file-text-fill': 'e67f',\r\n\t'uvicon-pushpin': 'e6d1',\r\n\t'uvicon-pushpin-fill': 'e6b6',\r\n\t'uvicon-grid': 'e68c',\r\n\t'uvicon-grid-fill': 'e698',\r\n\t'uvicon-play-circle': 'e6af',\r\n\t'uvicon-play-circle-fill': 'e62a',\r\n\t'uvicon-pause-circle-fill': 'e60c',\r\n\t'uvicon-pause': 'e61c',\r\n\t'uvicon-pause-circle': 'e696',\r\n\t'uvicon-gift-fill': 'e6b0',\r\n\t'uvicon-gift': 'e680',\r\n\t'uvicon-kefu-ermai': 'e660',\r\n\t'uvicon-server-fill': 'e610',\r\n\t'uvicon-coupon-fill': 'e64c',\r\n\t'uvicon-coupon': 'e65f',\r\n\t'uvicon-integral': 'e693',\r\n\t'uvicon-integral-fill': 'e6b1',\r\n\t'uvicon-home-fill': 'e68e',\r\n\t'uvicon-home': 'e67b',\r\n\t'uvicon-account': 'e63a',\r\n\t'uvicon-account-fill': 'e653',\r\n\t'uvicon-thumb-down-fill': 'e628',\r\n\t'uvicon-thumb-down': 'e60a',\r\n\t'uvicon-thumb-up': 'e612',\r\n\t'uvicon-thumb-up-fill': 'e62c',\r\n\t'uvicon-lock-fill': 'e6a6',\r\n\t'uvicon-lock-open': 'e68d',\r\n\t'uvicon-lock-opened-fill': 'e6a1',\r\n\t'uvicon-lock': 'e69d',\r\n\t'uvicon-red-packet': 'e6c3',\r\n\t'uvicon-photo-fill': 'e6b4',\r\n\t'uvicon-photo': 'e60d',\r\n\t'uvicon-volume-off-fill': 'e6c8',\r\n\t'uvicon-volume-off': 'e6bd',\r\n\t'uvicon-volume-fill': 'e624',\r\n\t'uvicon-volume': 'e605',\r\n\t'uvicon-download': 'e670',\r\n\t'uvicon-arrow-up-fill': 'e636',\r\n\t'uvicon-arrow-down-fill': 'e638',\r\n\t'uvicon-play-left-fill': 'e6ae',\r\n\t'uvicon-play-right-fill': 'e6ad',\r\n\t'uvicon-arrow-downward': 'e634',\r\n\t'uvicon-arrow-leftward': 'e63b',\r\n\t'uvicon-arrow-rightward': 'e644',\r\n\t'uvicon-arrow-upward': 'e641',\r\n\t'uvicon-arrow-down': 'e63e',\r\n\t'uvicon-arrow-right': 'e63c',\r\n\t'uvicon-arrow-left': 'e646',\r\n\t'uvicon-arrow-up': 'e633',\r\n\t'uvicon-skip-back-left': 'e6c5',\r\n\t'uvicon-skip-forward-right': 'e61f',\r\n\t'uvicon-arrow-left-double': 'e637',\r\n\t'uvicon-man': 'e675',\r\n\t'uvicon-woman': 'e626',\r\n\t'uvicon-en': 'e6b8',\r\n\t'uvicon-twitte': 'e607',\r\n\t'uvicon-twitter-circle-fill': 'e6cf'\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-icon/components/uv-icon/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 图标类名\r\n\t\tname: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 图标颜色，可接受主题色\r\n\t\tcolor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#606266'\r\n\t\t},\r\n\t\t// 字体大小，单位px\r\n\t\tsize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: '16px'\r\n\t\t},\r\n\t\t// 是否显示粗体\r\n\t\tbold: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 点击图标的时候传递事件出去的index（用于区分点击了哪一个）\r\n\t\tindex: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: null\r\n\t\t},\r\n\t\t// 触摸图标时的类名\r\n\t\thoverClass: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 自定义扩展前缀，方便用户扩展自己的图标库\r\n\t\tcustomPrefix: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'uvicon'\r\n\t\t},\r\n\t\t// 图标右边或者下面的文字\r\n\t\tlabel: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// label的位置，只能右边或者下边\r\n\t\tlabelPos: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'right'\r\n\t\t},\r\n\t\t// label的大小\r\n\t\tlabelSize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: '15px'\r\n\t\t},\r\n\t\t// label的颜色\r\n\t\tlabelColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#606266'\r\n\t\t},\r\n\t\t// label与图标的距离\r\n\t\tspace: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: '3px'\r\n\t\t},\r\n\t\t// 图片的mode\r\n\t\timgMode: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 用于显示图片小图标时，图片的宽度\r\n\t\twidth: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 用于显示图片小图标时，图片的高度\r\n\t\theight: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 用于解决某些情况下，让图标垂直居中的用途\r\n\t\ttop: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 是否阻止事件传播\r\n\t\tstop: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t...uni.$uv?.props?.icon\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-icon/components/uv-icon/uv-icon.vue",
    "content": "<template>\r\n\t<view\r\n\t  class=\"uv-icon\"\r\n\t  @tap=\"clickHandler\"\r\n\t  :class=\"['uv-icon--' + labelPos]\"\r\n\t>\r\n\t\t<image\r\n\t\t  class=\"uv-icon__img\"\r\n\t\t  v-if=\"isImg\"\r\n\t\t  :src=\"name\"\r\n\t\t  :mode=\"imgMode\"\r\n\t\t  :style=\"[imgStyle, $uv.addStyle(customStyle)]\"\r\n\t\t></image>\r\n\t\t<text\r\n\t\t  v-else\r\n\t\t  class=\"uv-icon__icon\"\r\n\t\t  :class=\"uClasses\"\r\n\t\t  :style=\"[iconStyle, $uv.addStyle(customStyle)]\"\r\n\t\t  :hover-class=\"hoverClass\"\r\n\t\t>{{icon}}</text>\r\n\t\t<!-- 这里进行空字符串判断，如果仅仅是v-if=\"label\"，可能会出现传递0的时候，结果也无法显示 -->\r\n\t\t<text\r\n\t\t  v-if=\"label !== ''\" \r\n\t\t  class=\"uv-icon__label\"\r\n\t\t  :style=\"{\r\n\t\t\tcolor: labelColor,\r\n\t\t\tfontSize: $uv.addUnit(labelSize),\r\n\t\t\tmarginLeft: labelPos == 'right' ? $uv.addUnit(space) : 0,\r\n\t\t\tmarginTop: labelPos == 'bottom' ? $uv.addUnit(space) : 0,\r\n\t\t\tmarginRight: labelPos == 'left' ? $uv.addUnit(space) : 0,\r\n\t\t\tmarginBottom: labelPos == 'top' ? $uv.addUnit(space) : 0\r\n\t\t}\"\r\n\t\t>{{ label }}</text>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\t// #ifdef APP-NVUE\r\n\t// nvue通过weex的dom模块引入字体，相关文档地址如下：\r\n\t// https://weex.apache.org/zh/docs/modules/dom.html#addrule\r\n\timport iconUrl from './uvicons.ttf';\r\n\tconst domModule = weex.requireModule('dom')\r\n\tdomModule.addRule('fontFace', {\r\n\t\t'fontFamily': \"uvicon-iconfont\",\r\n\t\t'src': \"url('\" + iconUrl + \"')\"\r\n\t})\r\n\t// #endif\r\n\t// 引入图标名称，已经对应的unicode\r\n\timport icons from './icons';\r\n\timport props from './props.js';\r\n\t/**\r\n\t * icon 图标\r\n\t * @description 基于字体的图标集，包含了大多数常见场景的图标。\r\n\t * @tutorial https://www.uvui.cn/components/icon.html\r\n\t * @property {String}\t\t\tname\t\t\t图标名称，见示例图标集\r\n\t * @property {String}\t\t\tcolor\t\t\t图标颜色,可接受主题色 （默认 color['uv-content-color'] ）\r\n\t * @property {String | Number}\tsize\t\t\t图标字体大小，单位px （默认 '16px' ）\r\n\t * @property {Boolean}\t\t\tbold\t\t\t是否显示粗体 （默认 false ）\r\n\t * @property {String | Number}\tindex\t\t\t点击图标的时候传递事件出去的index（用于区分点击了哪一个）\r\n\t * @property {String}\t\t\thoverClass\t\t图标按下去的样式类，用法同uni的view组件的hoverClass参数，详情见官网\r\n\t * @property {String}\t\t\tcustomPrefix\t自定义扩展前缀，方便用户扩展自己的图标库 （默认 'uicon' ）\r\n\t * @property {String | Number}\tlabel\t\t\t图标右侧的label文字\r\n\t * @property {String}\t\t\tlabelPos\t\tlabel相对于图标的位置，只能right或bottom （默认 'right' ）\r\n\t * @property {String | Number}\tlabelSize\t\tlabel字体大小，单位px （默认 '15px' ）\r\n\t * @property {String}\t\t\tlabelColor\t\t图标右侧的label文字颜色 （ 默认 color['uv-content-color'] ）\r\n\t * @property {String | Number}\tspace\t\t\tlabel与图标的距离，单位px （默认 '3px' ）\r\n\t * @property {String}\t\t\timgMode\t\t\t图片的mode\r\n\t * @property {String | Number}\twidth\t\t\t显示图片小图标时的宽度\r\n\t * @property {String | Number}\theight\t\t\t显示图片小图标时的高度\r\n\t * @property {String | Number}\ttop\t\t\t\t图标在垂直方向上的定位 用于解决某些情况下，让图标垂直居中的用途  （默认 0 ）\r\n\t * @property {Boolean}\t\t\tstop\t\t\t是否阻止事件传播 （默认 false ）\r\n\t * @property {Object}\t\t\tcustomStyle\t\ticon的样式，对象形式\r\n\t * @event {Function} click 点击图标时触发\r\n\t * @event {Function} touchstart 事件触摸时触发\r\n\t * @example <uv-icon name=\"photo\" color=\"#2979ff\" size=\"28\"></uv-icon>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-icon',\r\n\t\temits: ['click'],\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tcolorType: [\r\n\t\t\t\t\t'primary',\r\n\t\t\t\t\t'success',\r\n\t\t\t\t\t'info',\r\n\t\t\t\t\t'error',\r\n\t\t\t\t\t'warning'\r\n\t\t\t\t]\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\tuClasses() {\r\n\t\t\t\tlet classes = []\r\n\t\t\t\tclasses.push(this.customPrefix)\r\n\t\t\t\tclasses.push(this.customPrefix + '-' + this.name)\r\n\t\t\t\t// 主题色，通过类配置\r\n\t\t\t\tif (this.color && this.colorType.includes(this.color)) classes.push('uv-icon__icon--' + this.color)\r\n\t\t\t\t// 阿里，头条，百度小程序通过数组绑定类名时，无法直接使用[a, b, c]的形式，否则无法识别\r\n\t\t\t\t// 故需将其拆成一个字符串的形式，通过空格隔开各个类名\r\n\t\t\t\t//#ifdef MP-ALIPAY || MP-TOUTIAO || MP-BAIDU\r\n\t\t\t\tclasses = classes.join(' ')\r\n\t\t\t\t//#endif\r\n\t\t\t\treturn classes\r\n\t\t\t},\r\n\t\t\ticonStyle() {\r\n\t\t\t\tlet style = {}\r\n\t\t\t\tstyle = {\r\n\t\t\t\t\tfontSize: this.$uv.addUnit(this.size),\r\n\t\t\t\t\tlineHeight: this.$uv.addUnit(this.size),\r\n\t\t\t\t\tfontWeight: this.bold ? 'bold' : 'normal',\r\n\t\t\t\t\t// 某些特殊情况需要设置一个到顶部的距离，才能更好的垂直居中\r\n\t\t\t\t\ttop: this.$uv.addUnit(this.top)\r\n\t\t\t\t}\r\n\t\t\t\t// 非主题色值时，才当作颜色值\r\n\t\t\t\tif (this.color && !this.colorType.includes(this.color)) style.color = this.color\r\n\t\t\t\treturn style\r\n\t\t\t},\r\n\t\t\t// 判断传入的name属性，是否图片路径，只要带有\"/\"均认为是图片形式\r\n\t\t\tisImg() {\r\n\t\t\t\tconst isBase64 = this.name.indexOf('data:') > -1 && this.name.indexOf('base64') > -1;\r\n\t\t\t\treturn this.name.indexOf('/') !== -1 || isBase64;\r\n\t\t\t},\r\n\t\t\timgStyle() {\r\n\t\t\t\tlet style = {}\r\n\t\t\t\t// 如果设置width和height属性，则优先使用，否则使用size属性\r\n\t\t\t\tstyle.width = this.width ? this.$uv.addUnit(this.width) : this.$uv.addUnit(this.size)\r\n\t\t\t\tstyle.height = this.height ? this.$uv.addUnit(this.height) : this.$uv.addUnit(this.size)\r\n\t\t\t\treturn style\r\n\t\t\t},\r\n\t\t\t// 通过图标名，查找对应的图标\r\n\t\t\ticon() {\r\n\t\t\t\t// 如果内置的图标中找不到对应的图标，就直接返回name值，因为用户可能传入的是unicode代码\r\n\t\t\t\tconst code = icons['uvicon-' + this.name];\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\tif(!code) {\r\n\t\t\t\t\treturn code ? unescape(`%u${code}`) : ['uvicon'].indexOf(this.customPrefix) > -1 ? unescape(`%u${this.name}`) : '';\r\n\t\t\t\t}\r\n\t\t\t\t// #endif\r\n\t\t\t\treturn code ? unescape(`%u${code}`) : ['uvicon'].indexOf(this.customPrefix) > -1 ? this.name : '';\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tclickHandler(e) {\r\n\t\t\t\tthis.$emit('click', this.index)\r\n\t\t\t\t// 是否阻止事件冒泡\r\n\t\t\t\tthis.stop && this.preventEvent(e)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t// 变量定义\r\n\t$uv-icon-primary: $uv-primary !default;\r\n\t$uv-icon-success: $uv-success !default;\r\n\t$uv-icon-info: $uv-info !default;\r\n\t$uv-icon-warning: $uv-warning !default;\r\n\t$uv-icon-error: $uv-error !default;\r\n\t$uv-icon-label-line-height: 1 !default;\r\n\t/* #ifndef APP-NVUE */\r\n\t// 非nvue下加载字体\r\n\t@font-face {\r\n\t\tfont-family: 'uvicon-iconfont';\r\n\t\tsrc: url('./uvicons.ttf') format('truetype');\r\n\t}\r\n\t/* #endif */\r\n\t.uv-icon {\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\tdisplay: flex;\r\n\t\t/* #endif */\r\n\t\talign-items: center;\r\n\t\t&--left {\r\n\t\t\tflex-direction: row-reverse;\r\n\t\t\talign-items: center;\r\n\t\t}\r\n\t\t&--right {\r\n\t\t\tflex-direction: row;\r\n\t\t\talign-items: center;\r\n\t\t}\r\n\t\t&--top {\r\n\t\t\tflex-direction: column-reverse;\r\n\t\t\tjustify-content: center;\r\n\t\t}\r\n\t\t&--bottom {\r\n\t\t\tflex-direction: column;\r\n\t\t\tjustify-content: center;\r\n\t\t}\r\n\t\t&__icon {\r\n\t\t\tfont-family: uvicon-iconfont;\r\n\t\t\tposition: relative;\r\n\t\t\t@include flex;\r\n\t\t\talign-items: center;\r\n\t\t\t&--primary {\r\n\t\t\t\tcolor: $uv-icon-primary;\r\n\t\t\t}\r\n\t\t\t&--success {\r\n\t\t\t\tcolor: $uv-icon-success;\r\n\t\t\t}\r\n\t\t\t&--error {\r\n\t\t\t\tcolor: $uv-icon-error;\r\n\t\t\t}\r\n\t\t\t&--warning {\r\n\t\t\t\tcolor: $uv-icon-warning;\r\n\t\t\t}\r\n\t\t\t&--info {\r\n\t\t\t\tcolor: $uv-icon-info;\r\n\t\t\t}\r\n\t\t}\r\n\t\t&__img {\r\n\t\t\t/* #ifndef APP-NVUE */\r\n\t\t\theight: auto;\r\n\t\t\twill-change: transform;\r\n\t\t\t/* #endif */\r\n\t\t}\r\n\t\t&__label {\r\n\t\t\t/* #ifndef APP-NVUE */\r\n\t\t\tline-height: $uv-icon-label-line-height;\r\n\t\t\t/* #endif */\r\n\t\t}\r\n\t}\r\n</style>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-icon/package.json",
    "content": "{\r\n  \"id\": \"uv-icon\",\r\n  \"displayName\": \"uv-icon 图标 全面兼容vue3+2、app、h5、小程序等多端\",\r\n  \"version\": \"1.0.10\",\r\n  \"description\": \"基于字体的图标集，包含了大多数常见场景的图标，支持自定义，支持自定义图片图标等。可自定义颜色、大小。\",\r\n  \"keywords\": [\r\n    \"uv-ui,uvui,uv-icon,icon,图标,字体图标\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n      \"ads\": \"无\",\r\n      \"data\": \"插件不采集任何数据\",\r\n      \"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n      \"cloud\": {\r\n        \"tcb\": \"y\",\r\n        \"aliyun\": \"y\"\r\n      },\r\n      \"client\": {\r\n        \"Vue\": {\r\n          \"vue2\": \"y\",\r\n          \"vue3\": \"y\"\r\n        },\r\n        \"App\": {\r\n          \"app-vue\": \"y\",\r\n          \"app-nvue\": \"y\"\r\n        },\r\n        \"H5-mobile\": {\r\n          \"Safari\": \"y\",\r\n          \"Android Browser\": \"y\",\r\n          \"微信浏览器(Android)\": \"y\",\r\n          \"QQ浏览器(Android)\": \"y\"\r\n        },\r\n        \"H5-pc\": {\r\n          \"Chrome\": \"y\",\r\n          \"IE\": \"y\",\r\n          \"Edge\": \"y\",\r\n          \"Firefox\": \"y\",\r\n          \"Safari\": \"y\"\r\n        },\r\n        \"小程序\": {\r\n          \"微信\": \"y\",\r\n          \"阿里\": \"y\",\r\n          \"百度\": \"y\",\r\n          \"字节跳动\": \"y\",\r\n          \"QQ\": \"y\",\r\n          \"钉钉\": \"u\",\r\n          \"快手\": \"u\",\r\n          \"飞书\": \"u\",\r\n          \"京东\": \"u\"\r\n        },\r\n        \"快应用\": {\r\n          \"华为\": \"u\",\r\n          \"联盟\": \"u\"\r\n        }\r\n      }\r\n    }\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-icon/readme.md",
    "content": "## uv-icon 图标库\r\n\r\n> **组件名：uv-icon**\r\n\r\n基于字体的图标集，包含了大多数常见场景的图标，支持自定义，支持自定义图片图标等。\r\n\r\n# <a href=\"https://www.uvui.cn/components/icon.html\" target=\"_blank\">查看文档</a>\r\n\r\n## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\r\n\r\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-image/changelog.md",
    "content": "## 1.0.12（2023-10-11）\n1. 修复懒加载报错：https://gitee.com/climblee/uv-ui/issues/I869JS\n## 1.0.11（2023-08-31）\n1. 修复设置widthFix时出现显示不全的BUG\n2. 修复抖音等平台在width和height属性改变时出现不显示的BUG\n## 1.0.10（2023-08-29）\r\n1. 修复异步修改宽高不生效的问题，问题来源：https://gitee.com/climblee/uv-ui/issues/I7WUQ3\r\n## 1.0.9（2023-08-21）\r\n1. 修复设置宽高为百分比不生效的BUG\r\n## 1.0.8（2023-07-24）\r\n1. 优化 nvue模式下增加cellChild参数，是否在list中cell节点下，nvue中cell下建议设置成true\r\n## 1.0.7（2023-07-02）\r\n修复VUE3模式下可能不显示的BUG\r\n## 1.0.6（2023-07-02）\r\n优化修改\r\n## 1.0.5（2023-06-28）\r\n修复duration属性不生效的BUG\r\n## 1.0.4（2023-05-27）\r\n1. 修复可能报错的问题\r\n## 1.0.3（2023-05-24）\r\n1. 去掉template中存在的this.导致头条小程序编译警告\r\n## 1.0.2（2023-05-23）\r\n1. 优化\r\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-image 图片\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-image/components/uv-image/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 图片地址\r\n\t\tsrc: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 裁剪模式\r\n\t\tmode: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'aspectFill'\r\n\t\t},\r\n\t\t// 宽度，单位任意\r\n\t\twidth: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: '300'\r\n\t\t},\r\n\t\t// 高度，单位任意\r\n\t\theight: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: '225'\r\n\t\t},\r\n\t\t// 图片形状，circle-圆形，square-方形\r\n\t\tshape: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'square'\r\n\t\t},\r\n\t\t// 圆角，单位任意\r\n\t\tradius: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 是否懒加载，微信小程序、App、百度小程序、字节跳动小程序\r\n\t\tlazyLoad: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 是否开启observer懒加载，nvue不生效\r\n\t\tobserveLazyLoad: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 开启长按图片显示识别微信小程序码菜单\r\n\t\tshowMenuByLongpress: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 加载中的图标，或者小图片\r\n\t\tloadingIcon: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'photo'\r\n\t\t},\r\n\t\t// 加载失败的图标，或者小图片\r\n\t\terrorIcon: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'error-circle'\r\n\t\t},\r\n\t\t// 是否显示加载中的图标或者自定义的slot\r\n\t\tshowLoading: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 是否显示加载错误的图标或者自定义的slot\r\n\t\tshowError: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 是否需要淡入效果\r\n\t\tfade: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 只支持网络资源，只对微信小程序有效\r\n\t\twebp: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 过渡时间，单位ms\r\n\t\tduration: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 500\r\n\t\t},\r\n\t\t// 背景颜色，用于深色页面加载图片时，为了和背景色融合\r\n\t\tbgColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#f3f4f6'\r\n\t\t},\r\n\t\t// nvue模式下 是否直接显示，在uv-list等cell下面使用就需要设置\r\n\t\tcellChild: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t...uni.$uv?.props?.image\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-image/components/uv-image/uv-image.vue",
    "content": "<template>\r\n\t<uv-transition\r\n\t\tv-if=\"show\"\r\n\t\t:show=\"show\"\r\n\t\tmode=\"fade\"\r\n\t\t:duration=\"fade ? duration : 0\"\r\n\t\t:cell-child=\"cellChild\"\r\n\t\t:custom-style=\"wrapStyle\"\r\n\t>\r\n\t\t<view\r\n\t\t\tclass=\"uv-image\"\r\n\t\t\t:class=\"[`uv-image--${elIndex}`]\"\r\n\t\t\t@tap=\"onClick\"\r\n\t\t\t:style=\"[wrapStyle, backgroundStyle]\"\r\n\t\t>\r\n\t\t\t<image\r\n\t\t\t\tv-if=\"!isError && observeShow\"\r\n\t\t\t\t:src=\"src\"\r\n\t\t\t\t:mode=\"mode\"\r\n\t\t\t\t@error=\"onErrorHandler\"\r\n\t\t\t\t@load=\"onLoadHandler\"\r\n\t\t\t\t:show-menuv-by-longpress=\"showMenuByLongpress\"\r\n\t\t\t\t:lazy-load=\"lazyLoad\"\r\n\t\t\t\tclass=\"uv-image__image\"\r\n\t\t\t\t:style=\"[imageStyle]\"\r\n\t\t\t></image>\r\n\t\t\t<view\r\n\t\t\t\tv-if=\"showLoading && loading\"\r\n\t\t\t\tclass=\"uv-image__loading\"\r\n\t\t\t\t:style=\"{\r\n\t\t\t\t\tborderRadius: shape == 'circle' ? '50%' : $uv.addUnit(radius),\r\n\t\t\t\t\tbackgroundColor: bgColor,\r\n\t\t\t\t\twidth: $uv.addUnit(width),\r\n\t\t\t\t\theight: $uv.addUnit(height)\r\n\t\t\t\t}\"\r\n\t\t\t>\r\n\t\t\t\t<slot name=\"loading\">\r\n\t\t\t\t\t<uv-icon\r\n\t\t\t\t\t\t:name=\"loadingIcon\"\r\n\t\t\t\t\t\t:width=\"width\"\r\n\t\t\t\t\t\t:height=\"height\"\r\n\t\t\t\t\t></uv-icon>\r\n\t\t\t\t</slot>\r\n\t\t\t</view>\r\n\t\t\t<view\r\n\t\t\t\tv-if=\"showError && isError && !loading\"\r\n\t\t\t\tclass=\"uv-image__error\"\r\n\t\t\t\t:style=\"{\r\n\t\t\t\t\tborderRadius: shape == 'circle' ? '50%' : $uv.addUnit(radius),\r\n\t\t\t\t\twidth: $uv.addUnit(width),\r\n\t\t\t\t\theight: $uv.addUnit(height)\r\n\t\t\t\t}\"\r\n\t\t\t>\r\n\t\t\t\t<slot name=\"error\">\r\n\t\t\t\t\t<uv-icon\r\n\t\t\t\t\t\t:name=\"errorIcon\"\r\n\t\t\t\t\t\t:width=\"width\"\r\n\t\t\t\t\t\t:height=\"height\"\r\n\t\t\t\t\t></uv-icon>\r\n\t\t\t\t</slot>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t</uv-transition>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * Image 图片\r\n\t * @description 此组件为uni-app的image组件的加强版，在继承了原有功能外，还支持淡入动画、加载中、加载失败提示、圆角值和形状等。\r\n\t * @tutorial https://www.uvui.cn/components/image.html\r\n\t * @property {String}\t\t\tsrc \t\t\t\t图片地址\r\n\t * @property {String}\t\t\tmode \t\t\t\t裁剪模式，见官网说明 （默认 'aspectFill' ）\r\n\t * @property {String | Number}\twidth \t\t\t\t宽度，单位任意，如果为数值，则为px单位 （默认 '300' ）\r\n\t * @property {String | Number}\theight \t\t\t\t高度，单位任意，如果为数值，则为px单位 （默认 '225' ）\r\n\t * @property {String}\t\t\tshape \t\t\t\t图片形状，circle-圆形，square-方形 （默认 'square' ）\r\n\t * @property {String | Number}\tradius\t\t \t\t圆角值，单位任意，如果为数值，则为px单位 （默认 0 ）\r\n\t * @property {Boolean}\t\t\tlazyLoad\t\t\t是否懒加载，仅微信小程序、App、百度小程序、字节跳动小程序有效 （默认 true ）\r\n\t * @property {Boolean}\t\t\tshowMenuByLongpress\t是否开启长按图片显示识别小程序码菜单，仅微信小程序有效 （默认 true ）\r\n\t * @property {String}\t\t\tloadingIcon \t\t加载中的图标，或者小图片 （默认 'photo' ）\r\n\t * @property {String}\t\t\terrorIcon \t\t\t加载失败的图标，或者小图片 （默认 'error-circle' ）\r\n\t * @property {Boolean}\t\t\tshowLoading \t\t是否显示加载中的图标或者自定义的slot （默认 true ）\r\n\t * @property {Boolean}\t\t\tshowError \t\t\t是否显示加载错误的图标或者自定义的slot （默认 true ）\r\n\t * @property {Boolean}\t\t\tfade \t\t\t\t是否需要淡入效果 （默认 true ）\r\n\t * @property {Boolean}\t\t\twebp \t\t\t\t只支持网络资源，只对微信小程序有效 （默认 false ）\r\n\t * @property {String | Number}\tduration \t\t\t搭配fade参数的过渡时间，单位ms （默认 500 ）\r\n\t * @property {String}\t\t\tbgColor \t\t\t背景颜色，用于深色页面加载图片时，为了和背景色融合  (默认 '#f3f4f6' )\r\n\t * @property {Object}\t\t\tcustomStyle  \t\t定义需要用到的外部样式\r\n\t * @event {Function}\tclick\t点击图片时触发\r\n\t * @event {Function}\terror\t图片加载失败时触发\r\n\t * @event {Function} load 图片加载成功时触发\r\n\t * @example <uv-image width=\"100%\" height=\"300px\" :src=\"src\"></uv-image>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-image',\r\n\t\temits: ['click','load','error'],\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\t// 图片是否加载错误，如果是，则显示错误占位图\r\n\t\t\t\tisError: false,\r\n\t\t\t\t// 初始化组件时，默认为加载中状态\r\n\t\t\t\tloading: true,\r\n\t\t\t\t// 图片加载完成时，去掉背景颜色，因为如果是png图片，就会显示灰色的背景\r\n\t\t\t\tbackgroundStyle: {},\r\n\t\t\t\t// 用于fade模式的控制组件显示与否\r\n\t\t\t\tshow: false,\r\n\t\t\t\t// 是否开启图片出现在可视范围进行加载（另一种懒加载）\r\n\t\t\t\tobserveShow: !this.observeLazyLoad,\r\n\t\t\t\telIndex: '',\r\n\t\t\t\t// 因为props的值无法修改，故需要一个中间值\r\n\t\t\t\timgWidth: this.width,\r\n\t\t\t\t// 因为props的值无法修改，故需要一个中间值\r\n\t\t\t\timgHeight: this.height,\r\n\t\t\t\tthresholdValue: 50\r\n\t\t\t};\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\tsrc: {\r\n\t\t\t\timmediate: true,\r\n\t\t\t\thandler(n) {\r\n\t\t\t\t\tif (!n) {\r\n\t\t\t\t\t\t// 如果传入null或者''，或者false，或者undefined，标记为错误状态\r\n\t\t\t\t\t\tthis.isError = true\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tthis.isError = false;\r\n\t\t\t\t\t\tthis.loading = true;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\twidth(newVal){\r\n\t\t\t\t// 这样做的目的是避免在更新时候，某些平台动画会恢复关闭状态\r\n\t\t\t\tthis.show = false;\r\n\t\t\t\tthis.$uv.sleep(2).then(res=>{\r\n\t\t\t\t\tthis.show = true;\r\n\t\t\t\t});\r\n\t\t\t\tthis.imgWidth = newVal;\r\n\t\t\t},\r\n\t\t\theight(newVal){\r\n\t\t\t\t// 这样做的目的是避免在更新时候，某些平台动画会恢复关闭状态\r\n\t\t\t\tthis.show = false;\r\n\t\t\t\tthis.$uv.sleep(2).then(res=>{\r\n\t\t\t\t\tthis.show = true;\r\n\t\t\t\t});\r\n\t\t\t\tthis.imgHeight = newVal;\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\twrapStyle() {\r\n\t\t\t\tlet style = {};\r\n\t\t\t\t// 通过调用addUnit()方法，如果有单位，如百分比，px单位等，直接返回，如果是纯粹的数值，则加上rpx单位\r\n\t\t\t\tif(this.mode !== 'heightFix') {\r\n\t\t\t\t\tstyle.width = this.$uv.addUnit(this.imgWidth);\r\n\t\t\t\t}\r\n\t\t\t\tif(this.mode !== 'widthFix') {\r\n\t\t\t\t\tstyle.height = this.$uv.addUnit(this.imgHeight);\r\n\t\t\t\t}\r\n\t\t\t\t// 如果是显示圆形，设置一个很多的半径值即可\r\n\t\t\t\tstyle.borderRadius = this.shape == 'circle' ? '10000px' : this.$uv.addUnit(this.radius)\r\n\t\t\t\t// 如果设置圆角，必须要有hidden，否则可能圆角无效\r\n\t\t\t\tstyle.overflow = this.radius > 0 ? 'hidden' : 'visible'\r\n\t\t\t\treturn this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle));\r\n\t\t\t},\r\n\t\t\timageStyle() {\r\n\t\t\t\tlet style = {};\r\n\t\t\t\tstyle.borderRadius = this.shape == 'circle' ? '10000px' : this.$uv.addUnit(this.radius);\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\tstyle.width = this.$uv.addUnit(this.imgWidth);\r\n\t\t\t\tstyle.height = this.$uv.addUnit(this.imgHeight);\r\n\t\t\t\t// #endif\r\n\t\t\t\treturn style;\r\n\t\t\t}\r\n\t\t},\r\n\t\tcreated() {\r\n\t\t\tthis.elIndex = this.$uv.guid();\r\n\t\t\tthis.observer = {}\r\n\t\t\tthis.observerName = 'lazyLoadContentObserver'\r\n\t\t},\r\n\t\tmounted() {\r\n\t\t\tthis.show = true;\r\n\t\t\tthis.$nextTick(()=>{\r\n\t\t\t\tif(this.observeLazyLoad) this.observerFn();\r\n\t\t\t})\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\t// 点击图片\r\n\t\t\tonClick() {\r\n\t\t\t\tthis.$emit('click')\r\n\t\t\t},\r\n\t\t\t// 图片加载失败\r\n\t\t\tonErrorHandler(err) {\r\n\t\t\t\tthis.loading = false\r\n\t\t\t\tthis.isError = true\r\n\t\t\t\tthis.$emit('error', err)\r\n\t\t\t},\r\n\t\t\t// 图片加载完成，标记loading结束\r\n\t\t\tonLoadHandler(event) {\r\n\t\t\t\tif(this.mode == 'widthFix') this.imgHeight = 'auto'\r\n\t\t\t\tif(this.mode == 'heightFix') this.imgWidth = 'auto'\r\n\t\t\t\tthis.loading = false\r\n\t\t\t\tthis.isError = false\r\n\t\t\t\tthis.$emit('load', event)\r\n\t\t\t\tthis.removeBgColor()\r\n\t\t\t},\r\n\t\t\t// 移除图片的背景色\r\n\t\t\tremoveBgColor() {\r\n\t\t\t\t// 淡入动画过渡完成后，将背景设置为透明色，否则png图片会看到灰色的背景\r\n\t\t\t\tthis.backgroundStyle = {\r\n\t\t\t\t\tbackgroundColor: 'transparent'\r\n\t\t\t\t};\r\n\t\t\t},\r\n\t\t\t// 观察图片是否在可见视口\r\n\t\t\tobserverFn(){\r\n\t\t\t\t// 在需要用到懒加载的页面，在触发底部的时候触发tOnLazyLoadReachBottom事件，保证所有图片进行加载\r\n\t\t\t\tthis.$nextTick(() => {\r\n\t\t\t\t\tuni.$once('onLazyLoadReachBottom', () => {\r\n\t\t\t\t\t\tif (!this.observeShow) this.observeShow = true\r\n\t\t\t\t\t})\r\n\t\t\t\t})\r\n\t\t\t\tsetTimeout(() => {\r\n\t\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\t\tthis.disconnectObserver(this.observerName)\r\n\t\t\t\t\tconst contentObserver = uni.createIntersectionObserver(this)\r\n\t\t\t\t\tcontentObserver.relativeToViewport({\r\n\t\t\t\t\t\tbottom: this.thresholdValue\r\n\t\t\t\t\t}).observe(`.uv-image--${this.elIndex}`, (res) => {\r\n\t\t\t\t\t\tif (res.intersectionRatio > 0) {\r\n\t\t\t\t\t\t\t// 懒加载状态改变\r\n\t\t\t\t\t\t\tthis.observeShow = true\r\n\t\t\t\t\t\t\t// 如果图片已经加载，去掉监听，减少性能消耗\r\n\t\t\t\t\t\t\tthis.disconnectObserver(this.observerName)\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t})\r\n\t\t\t\t\tthis[this.observerName] = contentObserver\r\n\t\t\t\t\t// #endif\r\n\t\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\t\tthis.observeShow = true;\r\n\t\t\t\t\t// #endif\r\n\t\t\t\t}, 50)\r\n\t\t\t},\r\n\t\t\tdisconnectObserver(observerName) {\r\n\t\t\t\tconst observer = this[observerName]\r\n\t\t\t\tobserver && observer.disconnect()\r\n\t\t\t}\r\n\t\t}\r\n\t};\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t$uv-image-error-top:0px !default;\r\n\t$uv-image-error-left:0px !default;\r\n\t$uv-image-error-width:100% !default;\r\n\t$uv-image-error-hight:100% !default;\r\n\t$uv-image-error-background-color:$uv-bg-color !default;\r\n\t$uv-image-error-color:$uv-tips-color !default;\r\n\t$uv-image-error-font-size: 46rpx !default;\r\n\r\n\t.uv-image {\r\n\t\tposition: relative;\r\n\t\ttransition: opacity 0.5s ease-in-out;\r\n\r\n\t\t&__image {\r\n\t\t\twidth: 100%;\r\n\t\t\theight: 100%;\r\n\t\t}\r\n\r\n\t\t&__loading,\r\n\t\t&__error {\r\n\t\t\tposition: absolute;\r\n\t\t\ttop: $uv-image-error-top;\r\n\t\t\tleft: $uv-image-error-left;\r\n\t\t\twidth: $uv-image-error-width;\r\n\t\t\theight: $uv-image-error-hight;\r\n\t\t\t@include flex;\r\n\t\t\talign-items: center;\r\n\t\t\tjustify-content: center;\r\n\t\t\tbackground-color: $uv-image-error-background-color;\r\n\t\t\tcolor: $uv-image-error-color;\r\n\t\t\tfont-size: $uv-image-error-font-size;\r\n\t\t}\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-image/package.json",
    "content": "{\r\n  \"id\": \"uv-image\",\r\n  \"displayName\": \"uv-image 图片 全面兼容vue3+2、app、h5、小程序等多端\",\r\n  \"version\": \"1.0.12\",\r\n  \"description\": \"uv-image 此组件为uni-app的image组件的加强版，在继承了原有功能外，增加observer懒加载功能，还支持淡入动画、加载中、加载失败提示、圆角值和形状等。\",\r\n  \"keywords\": [\r\n    \"uv-image\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"image\",\r\n    \"图片\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-transition\",\r\n\t\t\t\"uv-icon\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-image/readme.md",
    "content": "## Image 图片\r\n\r\n> **组件名：uv-image**\r\n\r\n此组件为`uni-app`的`image`组件的加强版，在继承了原有功能外，增加`observer`懒加载功能，还支持淡入动画、加载中、加载失败提示、圆角值和形状等。\r\n\r\n# <a href=\"https://www.uvui.cn/components/image.html\" target=\"_blank\">查看文档</a>\r\n\r\n## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\r\n\r\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-index-list/changelog.md",
    "content": "## 1.0.6（2023-09-01）\n1. 修复设置customNavHeight导致定位不准确的BUG\n## 1.0.5（2023-08-23）\n1. 修复ios端快速滑动+点击右侧导航会出现白屏的BUG\n## 1.0.4（2023-07-25）\n1. 修复全局设置成rpx存在的高度BUG\n2. 修复其他BUG\n## 1.0.3（2023-07-03）\n去除插槽判断，避免某些平台不显示的BUG\n## 1.0.2（2023-05-27）\n1. select事件修复\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-index-list 索引列表\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-index-list/components/uv-index-anchor/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 列表锚点文本内容\r\n\t\ttext: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 列表锚点文字颜色\r\n\t\tcolor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#606266'\r\n\t\t},\r\n\t\t// 列表锚点文字大小，单位默认px\r\n\t\tsize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 14\r\n\t\t},\r\n\t\t// 列表锚点背景颜色\r\n\t\tbgColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#dedede'\r\n\t\t},\r\n\t\t// 列表锚点高度，单位默认px\r\n\t\theight: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 32\r\n\t\t},\r\n\t\t...uni.$uv?.props?.indexAnchor\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-index-list/components/uv-index-anchor/uv-index-anchor.vue",
    "content": "<template>\r\n\t<!-- #ifdef APP-NVUE -->\r\n\t<header>\r\n\t<!-- #endif -->\r\n\t<view\r\n\t    class=\"uv-index-anchor\"\r\n\t\t:ref=\"`uv-index-anchor-${text}`\"\r\n\t    :style=\"{\r\n\t\t\theight: $uv.addUnit(height),\r\n\t\t\tbackgroundColor: bgColor\r\n\t\t}\"\r\n\t>\r\n\t\t<text\r\n\t\t    class=\"uv-index-anchor__text\"\r\n\t\t    :style=\"{\r\n\t\t\t\tfontSize: $uv.addUnit(size),\r\n\t\t\t\tcolor: color\r\n\t\t\t}\"\r\n\t\t>{{ text }}</text>\r\n\t</view>\r\n\t<!-- #ifdef APP-NVUE -->\r\n\t</header>\r\n\t<!-- #endif -->\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t// #ifdef APP-NVUE\r\n\tconst dom = uni.requireNativePlugin('dom')\r\n\t// #endif\r\n\t/**\r\n\t * IndexAnchor 列表锚点\r\n\t * @description \r\n\t * @tutorial https://www.uvui.cn/components/indexList.html\r\n\t * @property {String | Number}\ttext\t列表锚点文本内容\r\n\t * @property {String}\t\t\tcolor\t列表锚点文字颜色 ( 默认 '#606266' )\r\n\t * @property {String | Number}\tsize\t列表锚点文字大小，单位默认px ( 默认 14 )\r\n\t * @property {String}\t\t\tbgColor\t列表锚点背景颜色 ( 默认 '#dedede' )\r\n\t * @property {String | Number}\theight\t列表锚点高度，单位默认px ( 默认 32 )\r\n\t * @example <uv-index-anchor :text=\"indexList[index]\"></uv-index-anchor>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-index-anchor',\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t}\r\n\t\t},\r\n\t\tmounted() {\r\n\t\t\tthis.init()\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tinit() {\r\n\t\t\t\t// 此处会活动父组件实例，并赋值给实例的parent属性\r\n\t\t\t\tconst indexList = this.$uv.$parent.call(this, 'uv-index-list')\r\n\t\t\t\tif (!indexList) { \r\n\t\t\t\t\treturn this.$uv.error('uv-index-anchor必须要搭配uv-index-list组件使用')\r\n\t\t\t\t}\r\n\t\t\t\t// 将当前实例放入到uv-index-list中\r\n\t\t\t\tindexList.anchors.push(this)\r\n\t\t\t\tconst indexListItem = this.$uv.$parent.call(this, 'uv-index-item')\r\n\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\t// 只有在非nvue下，uv-index-anchor才是嵌套在uv-index-item中的\r\n\t\t\t\tif (!indexListItem) {\r\n\t\t\t\t\treturn this.$uv.error('uv-index-anchor必须要搭配uv-index-item组件使用')\r\n\t\t\t\t}\r\n\t\t\t\t// 设置uv-index-item的id为anchor的text标识符，因为非nvue下滚动列表需要依赖scroll-view滚动到元素的特性\r\n\t\t\t\tindexListItem.id = this.text.charCodeAt(0)\r\n\t\t\t\t// #endif\r\n\t\t\t}\r\n\t\t},\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\r\n\t.uv-index-anchor {\r\n\t\tposition: sticky;\r\n\t\ttop: 0;\r\n\t\t@include flex;\r\n\t\talign-items: center;\r\n\t\tpadding-left: 15px;\r\n\t\tz-index: 1;\r\n\r\n\t\t&__text {\r\n\t\t\t@include flex;\r\n\t\t\talign-items: center;\r\n\t\t}\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-index-list/components/uv-index-item/uv-index-item.vue",
    "content": "<template>\r\n\t<!-- #ifdef APP-NVUE -->\r\n\t<cell ref=\"uv-index-item\">\r\n\t\t<!-- #endif -->\r\n\t\t<view\r\n\t\t\tclass=\"uv-index-item\"\r\n\t\t\t:id=\"`uv-index-item-${id}`\"\r\n\t\t\t:class=\"[`uv-index-item-${id}`]\"\r\n\t\t>\r\n\t\t\t<slot />\r\n\t\t</view>\r\n\t\t<!-- #ifdef APP-NVUE -->\r\n\t</cell>\r\n\t<!-- #endif -->\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\t// #ifdef APP-NVUE\r\n\t// 由于weex为阿里的KPI业绩考核的产物，所以不支持百分比单位，这里需要通过dom查询组件的宽度\r\n\tconst dom = uni.requireNativePlugin('dom')\r\n\t// #endif\r\n\t/**\r\n\t * IndexItem \r\n\t * @description \r\n\t * @tutorial https://www.uvui.cn/components/indexList.html\r\n\t * @property {String}\r\n\t * @event {Function}\r\n\t * @example\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-index-item',\r\n\t\tmixins: [mpMixin, mixin],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\t// 本组件到滚动条顶部的距离\r\n\t\t\t\ttop: 0,\r\n\t\t\t\theight: 0,\r\n\t\t\t\tid: ''\r\n\t\t\t}\r\n\t\t},\r\n\t\tcreated() {\r\n\t\t\t// 子组件uv-index-anchor的实例\r\n\t\t\tthis.anchor = {}\r\n\t\t},\r\n\t\tmounted() {\r\n\t\t\tthis.init()\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tinit() {\r\n\t\t\t\t// 此处会活动父组件实例，并赋值给实例的parent属性\r\n\t\t\t\tthis.getParentData('uv-index-list')\r\n\t\t\t\tif (!this.parent) {\r\n\t\t\t\t\treturn this.$uv.error('uv-index-item必须要搭配uv-index-list组件使用')\r\n\t\t\t\t}\r\n\t\t\t\tthis.$uv.sleep().then(() =>{\r\n\t\t\t\t\tthis.getIndexItemRect().then(size => {\r\n\t\t\t\t\t\t// 由于对象的引用特性，此处会同时生效到父组件的children数组的本实例的top属性中，供父组件判断读取\r\n\t\t\t\t\t\tthis.top = Math.ceil(size.top)\r\n\t\t\t\t\t\tthis.height = Math.ceil(size.height)\r\n\t\t\t\t\t})\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\tgetIndexItemRect() {\r\n\t\t\t\treturn new Promise(resolve => {\r\n\t\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\t\tthis.$uvGetRect('.uv-index-item').then(size => {\r\n\t\t\t\t\t\tresolve(size)\r\n\t\t\t\t\t})\r\n\t\t\t\t\t// #endif\r\n\r\n\t\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\t\tconst ref = this.$refs['uv-index-item']\r\n\t\t\t\t\tdom.getComponentRect(ref, res => {\r\n\t\t\t\t\t\tresolve(res.size)\r\n\t\t\t\t\t})\r\n\t\t\t\t\t// #endif\r\n\t\t\t\t}) \r\n\t\t\t}\r\n\t\t},\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-index-list/components/uv-index-list/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 右边锚点非激活的颜色\r\n\t\tinactiveColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#606266'\r\n\t\t},\r\n\t\t// 右边锚点激活的颜色\r\n\t\tactiveColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#5677fc'\r\n\t\t},\r\n\t\t// 索引字符列表，数组形式\r\n\t\tindexList: {\r\n\t\t\ttype: Array,\r\n\t\t\tdefault: () => []\r\n\t\t},\r\n\t\t// 是否开启锚点自动吸顶\r\n\t\tsticky: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 自定义导航栏的高度\r\n\t\tcustomNavHeight: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t...uni.$uv?.props?.indexList\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-index-list/components/uv-index-list/uv-index-list.vue",
    "content": "<template>\r\n\t<view class=\"uv-index-list\">\r\n\t\t<!-- #ifdef APP-NVUE -->\r\n\t\t<list\r\n\t\t\t:scrollTop=\"scrollTop\"\r\n\t\t\tenable-back-to-top\r\n\t\t\t:offset-accuracy=\"1\"\r\n\t\t\t:style=\"{\r\n\t\t\t\tmaxHeight: $uv.addUnit(scrollViewHeight,'px')\r\n\t\t\t}\"\r\n\t\t\t@scroll=\"scrollHandler\"\r\n\t\t\tref=\"uvList\"\r\n\t\t>\r\n\t\t\t<cell ref=\"header\">\r\n\t\t\t\t<slot name=\"header\" />\r\n\t\t\t</cell>\r\n\t\t\t<slot />\r\n\t\t\t<cell>\r\n\t\t\t\t<slot name=\"footer\" />\r\n\t\t\t</cell>\r\n\t\t</list>\r\n\t\t<!-- #endif -->\r\n\t\t<!-- #ifndef APP-NVUE -->\r\n\t\t<scroll-view\r\n\t\t\t:scrollTop=\"scrollTop\"\r\n\t\t\t:scrollIntoView=\"scrollIntoView\"\r\n\t\t\t:offset-accuracy=\"1\"\r\n\t\t\t:style=\"{\r\n\t\t\t\tmaxHeight: $uv.addUnit(scrollViewHeight,'px')\r\n\t\t\t}\"\r\n\t\t\tscroll-y\r\n\t\t\t@scroll=\"scrollHandler\"\r\n\t\t\tref=\"uvList\"\r\n\t\t>\r\n\t\t\t<view>\r\n\t\t\t\t<slot name=\"header\" />\r\n\t\t\t</view>\r\n\t\t\t<slot />\r\n\t\t\t<view>\r\n\t\t\t\t<slot name=\"footer\" />\r\n\t\t\t</view>\r\n\t\t</scroll-view>\r\n\t\t<!-- #endif -->\r\n\t\t<view\r\n\t\t\tclass=\"uv-index-list__letter\"\r\n\t\t\tref=\"uv-index-list__letter\"\r\n\t\t\t:style=\"{ top: $uv.addUnit(letterInfo.top || 100 ,'px') }\"\r\n\t\t\t@touchstart=\"touchStart\"\r\n\t\t\t@touchmove.stop.prevent=\"touchMove\"\r\n\t\t\t@touchend.stop.prevent=\"touchEnd\"\r\n\t\t\t@touchcancel.stop.prevent=\"touchEnd\"\r\n\t\t>\r\n\t\t\t<view\r\n\t\t\t\tclass=\"uv-index-list__letter__item\"\r\n\t\t\t\tv-for=\"(item, index) in uIndexList\"\r\n\t\t\t\t:key=\"index\"\r\n\t\t\t\t:style=\"{\r\n\t\t\t\t\tbackgroundColor: activeIndex === index ? activeColor : 'transparent'\r\n\t\t\t\t}\"\r\n\t\t\t>\r\n\t\t\t\t<text\r\n\t\t\t\t\tclass=\"uv-index-list__letter__item__index\"\r\n\t\t\t\t\t:style=\"{color: activeIndex === index ? '#fff' : inactiveColor}\"\r\n\t\t\t\t>{{ item }}</text>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t\t<uv-transition\r\n\t\t\tmode=\"fade\"\r\n\t\t\t:show=\"touching\"\r\n\t\t\t:customStyle=\"{\r\n\t\t\t\tposition: 'fixed',\r\n\t\t\t\tright: '40px',\r\n\t\t\t\ttop: $uv.addUnit(indicatorTop,'px'),\r\n\t\t\t\tzIndex: 2\r\n\t\t\t}\"\r\n\t\t>\r\n\t\t\t<view class=\"uv-index-list__indicator__box\">\r\n\t\t\t\t<view\r\n\t\t\t\t\tclass=\"uv-index-list__indicator\"\r\n\t\t\t\t\t:class=\"['uv-index-list__indicator--show']\"\r\n\t\t\t\t\t:style=\"{\r\n\t\t\t\t\t\theight: $uv.addUnit(indicatorHeight,'px'),\r\n\t\t\t\t\t\twidth: $uv.addUnit(indicatorHeight,'px')\r\n\t\t\t\t\t}\"\r\n\t\t\t\t>\r\n\t\t\t\t\t<text class=\"uv-index-list__indicator__text\">{{ uIndexList[activeIndex] }}</text>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t</uv-transition>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\tconst indexList = () => {\r\n\t\tconst indexList = [];\r\n\t\tconst charCodeOfA = 'A'.charCodeAt(0);\r\n\t\tfor (let i = 0; i < 26; i++) {\r\n\t\t\tindexList.push(String.fromCharCode(charCodeOfA + i));\r\n\t\t}\r\n\t\treturn indexList;\r\n\t}\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t// #ifdef APP-NVUE\r\n\t// 由于weex为阿里的KPI业绩考核的产物，所以不支持百分比单位，这里需要通过dom查询组件的宽度\r\n\tconst dom = uni.requireNativePlugin('dom')\r\n\t// #endif\r\n\t/**\r\n\t * IndexList 索引列表\r\n\t * @description  通过折叠面板收纳内容区域\r\n\t * @tutorial https://www.uvui.cn/components/indexList.html\r\n\t * @property {String}\t\t\tinactiveColor\t右边锚点非激活的颜色 ( 默认 '#606266' )\r\n\t * @property {String}\t\t\tactiveColor\t\t右边锚点激活的颜色 ( 默认 '#5677fc' )\r\n\t * @property {Array}\t\t\tindexList\t\t索引字符列表，数组形式\r\n\t * @property {Boolean}\t\t\tsticky\t\t\t是否开启锚点自动吸顶 ( 默认 true )\r\n\t * @property {String | Number}\tcustomNavHeight\t自定义导航栏的高度 ( 默认 0 )\r\n\t * */ \r\n\texport default {\r\n\t\tname: 'uv-index-list',\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\t// #ifdef MP-WEIXIN\r\n\t\t// 将自定义节点设置成虚拟的，更加接近Vue组件的表现，能更好的使用flex属性\r\n\t\toptions: {\r\n\t\t\tvirtualHost: true\r\n\t\t},\r\n\t\t// #endif\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\t// 当前正在被选中的字母索引\r\n\t\t\t\tactiveIndex: -1,\r\n\t\t\t\ttouchmoveIndex: 1,\r\n\t\t\t\t// 索引字母的信息\r\n\t\t\t\tletterInfo: {\r\n\t\t\t\t\theight: 0,\r\n\t\t\t\t\titemHeight: 0,\r\n\t\t\t\t\ttop: 0\r\n\t\t\t\t},\r\n\t\t\t\t// 设置字母指示器的高度，后面为了让指示器跟随字母，并将尖角部分指向字母的中部，需要依赖此值\r\n\t\t\t\tindicatorHeight: 50,\r\n\t\t\t\t// 字母放大指示器的top值，为了让其指向当前激活的字母\r\n\t\t\t\t// indicatorTop: 0\r\n\t\t\t\t// 当前是否正在被触摸状态\r\n\t\t\t\ttouching: false,\r\n\t\t\t\t// 滚动条顶部top值\r\n\t\t\t\tscrollTop: 0,\r\n\t\t\t\t// scroll-view的高度\r\n\t\t\t\tscrollViewHeight: 0,\r\n\t\t\t\t// 系统信息\r\n\t\t\t\tsys: '',\r\n\t\t\t\tscrolling: false,\r\n\t\t\t\tscrollIntoView: '',\r\n\t\t\t\thasHeight: 0,\r\n\t\t\t\ttimer: 0,\r\n\t\t\t\tdisTap: false\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\t// 如果有传入外部的indexList锚点数组则使用，否则使用内部生成A-Z字母\r\n\t\t\tuIndexList() {\r\n\t\t\t\treturn this.indexList.length ? this.indexList : indexList()\r\n\t\t\t},\r\n\t\t\t// 字母放大指示器的top值，为了让其指向当前激活的字母\r\n\t\t\tindicatorTop() {\r\n\t\t\t\tconst {\r\n\t\t\t\t\ttop,\r\n\t\t\t\t\titemHeight\r\n\t\t\t\t} = this.letterInfo\r\n\t\t\t\treturn Math.floor(top + itemHeight * this.activeIndex + itemHeight / 2 - this.indicatorHeight / 2) - 8\r\n\t\t\t}\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\t// 监听字母索引的变化，重新设置尺寸\r\n\t\t\tuIndexList: {\r\n\t\t\t\timmediate: true,\r\n\t\t\t\thandler() {\r\n\t\t\t\t\tthis.$uv.sleep().then(() => {\r\n\t\t\t\t\t\tthis.setIndexListLetterInfo()\r\n\t\t\t\t\t})\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\tcreated() {\r\n\t\t\tthis.sys = this.$uv.sys()\r\n\t\t\tthis.children = []\r\n\t\t\tthis.anchors = []\r\n\t\t\tthis.init()\r\n\t\t},\r\n\t\tmounted() {\r\n\t\t\tthis.setIndexListLetterInfo()\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tinit() {\r\n\t\t\t\t// 设置列表的高度为整个屏幕的高度\r\n\t\t\t\t//减去this.customNavHeight，并将this.scrollViewHeight设置为maxHeight\r\n\t\t\t\t//解决当uv-index-list组件放在tabbar页面时,scroll-view内容较少时，还能滚动\r\n\t\t\t\tthis.scrollViewHeight = this.sys.windowHeight - this.$uv.getPx(this.customNavHeight)\r\n\t\t\t},\r\n\t\t\t// 索引列表被触摸\r\n\t\t\ttouchStart(e) {\r\n\t\t\t\t// 获取触摸点信息\r\n\t\t\t\tconst touchStart = e.changedTouches[0]\r\n\t\t\t\tif (!touchStart || this.disTap) return\r\n\t\t\t\tthis.touching = true\r\n\t\t\t\tconst {\r\n\t\t\t\t\tpageY\r\n\t\t\t\t} = touchStart\r\n\t\t\t\t// 根据当前触摸点的坐标，获取当前触摸的为第几个字母\r\n\t\t\t\tconst currentIndex = this.getIndexListLetter(pageY)\r\n\t\t\t\tthis.setValueForTouch(currentIndex)\r\n\t\t\t},\r\n\t\t\t// 索引字母列表被触摸滑动中\r\n\t\t\ttouchMove(e) {\r\n\t\t\t\t// 获取触摸点信息\r\n\t\t\t\tlet touchMove = e.changedTouches[0]\r\n\t\t\t\tif (!touchMove || this.disTap) return;\r\n\r\n\t\t\t\t// 滑动结束后迅速开始第二次滑动时候 touching 为 false 造成不显示 indicator 问题\r\n\t\t\t\tif (!this.touching) {\r\n\t\t\t\t\tthis.touching = true\r\n\t\t\t\t}\r\n\t\t\t\tconst {\r\n\t\t\t\t\tpageY\r\n\t\t\t\t} = touchMove\r\n\t\t\t\tconst currentIndex = this.getIndexListLetter(pageY)\r\n\t\t\t\tthis.setValueForTouch(currentIndex)\r\n\t\t\t},\r\n\t\t\t// 触摸结束\r\n\t\t\ttouchEnd(e) {\r\n\t\t\t\t// 延时一定时间后再隐藏指示器，为了让用户看的更直观，同时也是为了消除快速切换uv-transition的show带来的影响\r\n\t\t\t\tthis.$uv.sleep(300).then(() => {\r\n\t\t\t\t\tthis.touching = false\r\n\t\t\t\t})\r\n\t\t\t\tthis.$emit('select',this.activeIndex);\r\n\t\t\t},\r\n\t\t\t// 获取索引列表的尺寸以及单个字符的尺寸信息\r\n\t\t\tgetIndexListLetterRect() {\r\n\t\t\t\treturn new Promise(resolve => {\r\n\t\t\t\t\t// 延时一定时间，以获取dom尺寸\r\n\t\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\t\tthis.$uvGetRect('.uv-index-list__letter').then(size => {\r\n\t\t\t\t\t\tresolve(size)\r\n\t\t\t\t\t})\r\n\t\t\t\t\t// #endif\r\n\r\n\t\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\t\tconst ref = this.$refs['uv-index-list__letter']\r\n\t\t\t\t\tdom.getComponentRect(ref, res => {\r\n\t\t\t\t\t\tresolve(res.size)\r\n\t\t\t\t\t})\r\n\t\t\t\t\t// #endif\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\t// 设置indexList索引的尺寸信息\r\n\t\t\tsetIndexListLetterInfo() {\r\n\t\t\t\tthis.getIndexListLetterRect().then(size => {\r\n\t\t\t\t\tconst {\r\n\t\t\t\t\t\ttop,\r\n\t\t\t\t\t\theight\r\n\t\t\t\t\t} = size\r\n\t\t\t\t\tconst windowHeight = this.$uv.sys().windowHeight;\r\n\t\t\t\t\tlet customNavHeight = 0\r\n\t\t\t\t\t// 消除各端导航栏非原生和原生导致的差异，让索引列表字母对屏幕垂直居中\r\n\t\t\t\t\tif (this.customNavHeight == 0) {\r\n\t\t\t\t\t\t// #ifdef H5\r\n\t\t\t\t\t\tcustomNavHeight = this.$uv.sys().windowTop\r\n\t\t\t\t\t\t// #endif\r\n\t\t\t\t\t\t// #ifndef H5\r\n\t\t\t\t\t\t// 在非H5中，为原生导航栏，其高度不算在windowHeight内，这里设置为负值，后面相加时变成减去其高度的一半\r\n\t\t\t\t\t\tcustomNavHeight = 0\r\n\t\t\t\t\t\t// #endif\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tcustomNavHeight = this.$uv.getPx(this.customNavHeight)\r\n\t\t\t\t\t}\r\n\t\t\t\t\tthis.letterInfo = {\r\n\t\t\t\t\t\theight,\r\n\t\t\t\t\t\t// 为了让字母列表对屏幕绝对居中，让其对导航栏进行修正，也即往上偏移导航栏的一半高度\r\n\t\t\t\t\t\ttop: (windowHeight - height) / 2 + customNavHeight / 2,\r\n\t\t\t\t\t\titemHeight: Math.floor(height / this.uIndexList.length)\r\n\t\t\t\t\t}\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\t// 获取当前被触摸的索引字母\r\n\t\t\tgetIndexListLetter(pageY) {\r\n\t\t\t\tconst {\r\n\t\t\t\t\ttop,\r\n\t\t\t\t\theight,\r\n\t\t\t\t\titemHeight\r\n\t\t\t\t} = this.letterInfo\r\n\t\t\t\t// 对H5的pageY进行修正，这是由于uni-app自作多情在H5中将触摸点的坐标跟H5的导航栏结合导致的问题\r\n\t\t\t\t// #ifdef H5\r\n\t\t\t\tpageY += this.$uv.sys().windowTop\r\n\t\t\t\t// #endif\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\tpageY += top\r\n\t\t\t\t// #endif\r\n\t\t\t\t// 对第一和最后一个字母做边界处理，因为用户可能在字母列表上触摸到两端的尽头后依然继续滑动\r\n\t\t\t\tif (pageY < top) {\r\n\t\t\t\t\treturn 0\r\n\t\t\t\t} else if (pageY >= top + height) {\r\n\t\t\t\t\t// 如果超出了，取最后一个字母\r\n\t\t\t\t\treturn this.uIndexList.length - 1\r\n\t\t\t\t} else {\r\n\t\t\t\t\t// 将触摸点的Y轴偏移值，减去索引字母的top值，除以每个字母的高度，即可得到当前触摸点落在哪个字母上\r\n\t\t\t\t\treturn Math.floor((pageY - top) / itemHeight);\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t// 设置各项由触摸而导致变化的值\r\n\t\t\tsetValueForTouch(currentIndex) {\r\n\t\t\t\t// 如果偏移量太小，前后得出的会是同一个索引字母，为了防抖，进行返回\r\n\t\t\t\tif (currentIndex === this.activeIndex) return\r\n\t\t\t\tthis.activeIndex = currentIndex\r\n\t\t\t\t// #ifndef APP-NVUE || MP-WEIXIN\r\n\t\t\t\t// 在非nvue中，由于anchor和item都在uv-index-item中，所以需要对index-item进行偏移\r\n\t\t\t\tthis.scrollIntoView = `uv-index-item-${this.uIndexList[currentIndex].charCodeAt(0)}`\r\n\t\t\t\t// #endif\r\n\t\t\t\t// #ifdef MP-WEIXIN\r\n\t\t\t\t// 微信小程序下，scroll-view的scroll-into-view属性无法对slot中的内容的id生效，只能通过设置scrollTop的形式去移动滚动条\r\n\t\t\t\tthis.scrollTop = this.children[currentIndex].top\r\n\t\t\t\t// #endif\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\t// 在nvue中，由于cell和header为同级元素，所以实际是需要对header(anchor)进行偏移\r\n\t\t\t\tconst anchor = `uv-index-anchor-${this.uIndexList[currentIndex]}`\r\n\t\t\t\tdom.scrollToElement(this.anchors[currentIndex].$refs[anchor], {\r\n\t\t\t\t\toffset: 0,\r\n\t\t\t\t\tanimated: false\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\t\t\t},\r\n\t\t\tgetHeaderRect() {\r\n\t\t\t\t// 获取header slot的高度，因为list组件中获取元素的尺寸是没有top值的\r\n\t\t\t\treturn new Promise(resolve => {\r\n\t\t\t\t\tdom.getComponentRect(this.$refs.header, res => {\r\n\t\t\t\t\t\tresolve(res.size)\r\n\t\t\t\t\t})\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\t// scroll-view的滚动事件\r\n\t\t\tasync scrollHandler(e) {\r\n\t\t\t\tif (this.touching || this.scrolling) return\r\n\t\t\t\t// 每过一定时间取样一次，减少资源损耗以及可能带来的卡顿\r\n\t\t\t\tthis.scrolling = true\r\n\t\t\t\tthis.disTap = true;\r\n\t\t\t\tclearTimeout(this.timer);\r\n\t\t\t\tthis.timer = setTimeout(()=>{\r\n\t\t\t\t\tthis.disTap = false;\r\n\t\t\t\t},200)\r\n\t\t\t\tthis.$uv.sleep(30).then(() => {\r\n\t\t\t\t\tthis.scrolling = false\r\n\t\t\t\t})\r\n\t\t\t\tlet scrollTop = 0\r\n\t\t\t\tconst len = this.children.length\r\n\t\t\t\tlet children = this.children\r\n\t\t\t\tconst anchors = this.anchors\r\n\t\t\t\tconst customNavHeight = this.$uv.getPx(this.customNavHeight);\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\t// nvue下获取的滚动条偏移为负数，需要转为正数\r\n\t\t\t\tscrollTop = Math.abs(e.contentOffset.y)\r\n\t\t\t\t// 获取header slot的尺寸信息\r\n\t\t\t\tconst header = await this.getHeaderRect()\r\n\t\t\t\t// item的top值，在nvue下，模拟出的anchor的top，类似非nvue下的index-item的top\r\n\t\t\t\tlet top = header.height\r\n\t\t\t\t// 由于list组件无法获取cell的top值，这里通过header slot和各个item之间的height，模拟出类似非nvue下的位置信息\r\n\t\t\t\tchildren = this.children.map((item, index) => {\r\n\t\t\t\t\tif(item.height>0) this.hasHeight = item.height;\r\n\t\t\t\t\titem.height = item.height>0?item.height:this.hasHeight;\r\n\t\t\t\t\tconst child = {\r\n\t\t\t\t\t\theight: item.height,\r\n\t\t\t\t\t\ttop\r\n\t\t\t\t\t}\r\n\t\t\t\t\t// 进行累加，给下一个item提供计算依据\r\n\t\t\t\t\ttop += item.height + anchors[index].height\r\n\t\t\t\t\treturn child\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\t// 非nvue通过detail获取滚动条位移\r\n\t\t\t\tscrollTop = e.detail.scrollTop\r\n\t\t\t\t// #endif\r\n\t\t\t\tfor (let i = 0; i < len; i++) {\r\n\t\t\t\t\tconst item = children[i],\r\n\t\t\t\t\t\tnextItem = children[i + 1]\r\n\t\t\t\t\t// 如果滚动条高度小于第一个item的top值，此时无需设置任意字母为高亮\r\n\t\t\t\t\tif (scrollTop + customNavHeight <= children[0].top || scrollTop >= children[len - 1].top + children[len -\r\n\t\t\t\t\t\t\t1].height) {\r\n\t\t\t\t\t\tthis.activeIndex = -1\r\n\t\t\t\t\t\tbreak\r\n\t\t\t\t\t} else if (!nextItem) { \r\n\t\t\t\t\t\t// 当不存在下一个item时，意味着历遍到了最后一个\r\n\t\t\t\t\t\tthis.activeIndex = len - 1\r\n\t\t\t\t\t\tbreak\r\n\t\t\t\t\t} else if (scrollTop + customNavHeight > item.top && scrollTop + customNavHeight < nextItem.top) {\r\n\t\t\t\t\t\tthis.activeIndex = i\r\n\t\t\t\t\t\tbreak\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t},\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t.uv-index-list {\r\n\r\n\t\t&__letter {\r\n\t\t\tposition: fixed;\r\n\t\t\tright: 0;\r\n\t\t\ttext-align: center;\r\n\t\t\tz-index: 3;\r\n\t\t\tpadding: 0 6px;\r\n\r\n\t\t\t&__item {\r\n\t\t\t\twidth: 16px;\r\n\t\t\t\theight: 16px;\r\n\t\t\t\tborder-radius: 100px;\r\n\t\t\t\tmargin: 1px 0;\r\n\t\t\t\t@include flex;\r\n\t\t\t\talign-items: center;\r\n\t\t\t\tjustify-content: center;\r\n\r\n\t\t\t\t&--active {\r\n\t\t\t\t\tbackground-color: $uv-primary;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t&__index {\r\n\t\t\t\t\tfont-size: 12px;\r\n\t\t\t\t\ttext-align: center;\r\n\t\t\t\t\tline-height: 12px;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t&__indicator__box {\r\n\t\t\twidth: 65px;\r\n\t\t\theight: 65px;\r\n\t\t\tpadding: 6px;\r\n\t\t\ttransform: rotate(-45deg);\r\n\t\t}\r\n\t\t&__indicator {\r\n\t\t\twidth: 50px;\r\n\t\t\theight: 50px;\r\n\t\t\tborder-radius: 100px 100px 0 100px;\r\n\t\t\ttext-align: center;\r\n\t\t\tcolor: #ffffff;\r\n\t\t\tbackground-color: #c9c9c9;\r\n\t\t\t@include flex;\r\n\t\t\tjustify-content: center;\r\n\t\t\talign-items: center;\r\n\r\n\t\t\t&__text {\r\n\t\t\t\tfont-size: 28px;\r\n\t\t\t\tline-height: 28px;\r\n\t\t\t\tfont-weight: bold;\r\n\t\t\t\tcolor: #fff;\r\n\t\t\t\ttransform: rotate(45deg);\r\n\t\t\t\ttext-align: center;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-index-list/package.json",
    "content": "{\r\n  \"id\": \"uv-index-list\",\r\n  \"displayName\": \"uv-index-list 索引列表 全面兼容vue3+2、app、h5、小程序等多端\",\r\n  \"version\": \"1.0.6\",\r\n  \"description\": \"该组件用于展示索引列表，右侧带索引的列表，方便快速定位到具体内容，通常用于城市/机场选择等场景。类似于微信通讯录页面\",\r\n  \"keywords\": [\r\n    \"uv-index-list\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"index-list\",\r\n    \"索引列表\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-transition\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-index-list/readme.md",
    "content": "## IndexList 索引列表\r\n\r\n> **组件名：uv-index-list**\r\n\r\n用于展示索引列表，右侧带索引的列表，方便快速定位到具体内容，通常用于城市/机场选择等场景，类似于微信通讯录页面。\r\n\r\n# <a href=\"https://www.uvui.cn/components/indexList.html\" target=\"_blank\">查看文档</a>\r\n\r\n## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>（请不要 下载插件ZIP）</small>\r\n\r\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n<a href=\"https://ext.dcloud.net.cn/plugin?name=uv-ui\" target=\"_blank\">\r\n\r\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\r\n\r\n</a>\r\n\r\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-input/changelog.md",
    "content": "## 1.0.10（2023-10-07）\n1. 修复搜狗输入法下存在不可清空的情况\n## 1.0.9（2023-09-14）\n1. 修复H5等情况设置禁用或可读情况下，点击事件无效的问题\n## 1.0.8（2023-08-22）\r\n1. 修复无法@keyboardheightchange无法获取键盘高度的BUG\r\n## 1.0.7（2023-08-18）\r\n1. 修复ios端不能输入的BUG\r\n## 1.0.6（2023-08-05）\r\n1. 修复在vue2模式下，v-model设置为0时不生效的BUG\r\n## 1.0.5（2023-07-18）\r\n1. 修复在微信小程序端清除内容存在不能清除的BUG\r\n## 1.0.4（2023-07-13）\r\n1.  修复value/v-model更改不生效的BUG\r\n## 1.0.3（2023-07-03）\r\n去除插槽判断，避免某些平台不显示的BUG\r\n## 1.0.2（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.1（2023-05-12）\r\n1. 修复vue3双向绑定的BUG\r\n## 1.0.0（2023-05-10）\r\nuv-input 输入框\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-input/components/uv-input/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\tvalue: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\tmodelValue: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 输入框类型\r\n\t\t// number-数字输入键盘，app-vue下可以输入浮点数，app-nvue和小程序平台下只能输入整数\r\n\t\t// idcard-身份证输入键盘，微信、支付宝、百度、QQ小程序\r\n\t\t// digit-带小数点的数字键盘，App的nvue页面、微信、支付宝、百度、头条、QQ小程序\r\n\t\t// text-文本输入键盘\r\n\t\ttype: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'text'\r\n\t\t},\r\n\t\t// 是否禁用输入框\r\n\t\tdisabled: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 禁用状态时的背景色\r\n\t\tdisabledColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#f5f7fa'\r\n\t\t},\r\n\t\t// 是否显示清除控件\r\n\t\tclearable: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否密码类型\r\n\t\tpassword: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 最大输入长度，设置为 -1 的时候不限制最大长度\r\n\t\tmaxlength: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: -1\r\n\t\t},\r\n\t\t// \t输入框为空时的占位符\r\n\t\tplaceholder: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: null\r\n\t\t},\r\n\t\t// 指定placeholder的样式类，注意页面或组件的style中写了scoped时，需要在类名前写/deep/\r\n\t\tplaceholderClass: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'input-placeholder'\r\n\t\t},\r\n\t\t// 指定placeholder的样式\r\n\t\tplaceholderStyle: {\r\n\t\t\ttype: [String, Object],\r\n\t\t\tdefault: 'color: #c0c4cc'\r\n\t\t},\r\n\t\t// 设置右下角按钮的文字，有效值：send|search|next|go|done，兼容性详见uni-app文档\r\n\t\t// https://uniapp.dcloud.io/component/input\r\n\t\t// https://uniapp.dcloud.io/component/textarea\r\n\t\tconfirmType: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'done'\r\n\t\t},\r\n\t\t// 点击键盘右下角按钮时是否保持键盘不收起，H5无效\r\n\t\tconfirmHold: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// focus时，点击页面的时候不收起键盘，微信小程序有效\r\n\t\tholdKeyboard: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 自动获取焦点\r\n\t\t// 在 H5 平台能否聚焦以及软键盘是否跟随弹出，取决于当前浏览器本身的实现。nvue 页面不支持，需使用组件的 focus()、blur() 方法控制焦点\r\n\t\tfocus: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 键盘收起时，是否自动失去焦点，目前仅App3.0.0+有效\r\n\t\tautoBlur: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 指定focus时光标的位置\r\n\t\tcursor: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: -1\r\n\t\t},\r\n\t\t// 输入框聚焦时底部与键盘的距离\r\n\t\tcursorSpacing: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 30\r\n\t\t},\r\n\t\t// 光标起始位置，自动聚集时有效，需与selection-end搭配使用\r\n\t\tselectionStart: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: -1\r\n\t\t},\r\n\t\t// 光标结束位置，自动聚集时有效，需与selection-start搭配使用\r\n\t\tselectionEnd: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: -1\r\n\t\t},\r\n\t\t// 键盘弹起时，是否自动上推页面\r\n\t\tadjustPosition: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 输入框内容对齐方式，可选值为：left|center|right\r\n\t\tinputAlign: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'left'\r\n\t\t},\r\n\t\t// 输入框字体的大小\r\n\t\tfontSize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: '14px'\r\n\t\t},\r\n\t\t// 输入框字体颜色\r\n\t\tcolor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#303133'\r\n\t\t},\r\n\t\t// 输入框前置图标\r\n\t\tprefixIcon: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 前置图标样式，对象或字符串\r\n\t\tprefixIconStyle: {\r\n\t\t\ttype: [String, Object],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 输入框后置图标\r\n\t\tsuffixIcon: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 后置图标样式，对象或字符串\r\n\t\tsuffixIconStyle: {\r\n\t\t\ttype: [String, Object],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 边框类型，surround-四周边框，bottom-底部边框，none-无边框\r\n\t\tborder: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'surround'\r\n\t\t},\r\n\t\t// 是否只读，与disabled不同之处在于disabled会置灰组件，而readonly则不会\r\n\t\treadonly: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 输入框形状，circle-圆形，square-方形\r\n\t\tshape: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'square'\r\n\t\t},\r\n\t\t// 用于处理或者过滤输入框内容的方法\r\n\t\tformatter: {\r\n\t\t\ttype: [Function, null],\r\n\t\t\tdefault: null\r\n\t\t},\r\n\t\t// 是否忽略组件内对文本合成系统事件的处理\r\n\t\tignoreCompositionEvent: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t...uni.$uv?.props?.input\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-input/components/uv-input/uv-input.vue",
    "content": "<template>\r\n    <view class=\"uv-input\" :class=\"inputClass\" :style=\"[wrapperStyle]\">\r\n      <view class=\"uv-input__content\">\r\n        <view class=\"uv-input__content__prefix-icon\">\r\n          <slot name=\"prefix\">\r\n            <uv-icon\r\n\t\t\t\t\t\t\tv-if=\"prefixIcon\"\r\n              :name=\"prefixIcon\"\r\n              size=\"18\"\r\n              :customStyle=\"prefixIconStyle\"\r\n            ></uv-icon>\r\n          </slot>\r\n        </view>\r\n        <view class=\"uv-input__content__field-wrapper\" @click=\"clickHandler\">\r\n\t\t\t\t<!-- 根据uni-app的input组件文档，H5和APP中只要声明了password参数(无论true还是false)，type均失效，此时\r\n\t\t\t\t为了防止type=number时，又存在password属性，type无效，此时需要设置password为undefined\r\n\t\t\t -->\r\n        \t<input\r\n        \t  class=\"uv-input__content__field-wrapper__field\"\r\n        \t  :style=\"[inputStyle]\"\r\n        \t  :type=\"type\"\r\n        \t  :focus=\"focus\"\r\n        \t  :cursor=\"cursor\"\r\n        \t  :value=\"innerValue\"\r\n        \t  :auto-blur=\"autoBlur\"\r\n        \t  :disabled=\"disabled || readonly\"\r\n        \t  :maxlength=\"maxlength\"\r\n        \t  :placeholder=\"placeholder\"\r\n        \t  :placeholder-style=\"placeholderStyle\"\r\n        \t  :placeholder-class=\"placeholderClass\"\r\n        \t  :confirm-type=\"confirmType\"\r\n        \t  :confirm-hold=\"confirmHold\"\r\n        \t  :hold-keyboard=\"holdKeyboard\"\r\n        \t  :cursor-spacing=\"cursorSpacing\"\r\n        \t  :adjust-position=\"adjustPosition\"\r\n        \t  :selection-end=\"selectionEnd\"\r\n        \t  :selection-start=\"selectionStart\"\r\n        \t  :password=\"password || type === 'password' || undefined\"\r\n            :ignoreCompositionEvent=\"ignoreCompositionEvent\"\r\n        \t  @input=\"onInput\"\r\n        \t  @blur=\"onBlur\"\r\n        \t  @focus=\"onFocus\"\r\n        \t  @confirm=\"onConfirm\"\r\n        \t  @keyboardheightchange=\"onkeyboardheightchange\"\r\n        \t/>\r\n        </view>\r\n        <view\r\n          class=\"uv-input__content__clear\"\r\n          v-if=\"isShowClear\"\r\n          @tap=\"onClear\"\r\n        >\r\n          <uv-icon\r\n            name=\"close\"\r\n            size=\"11\"\r\n            color=\"#ffffff\"\r\n            customStyle=\"line-height: 12px\"\r\n          ></uv-icon>\r\n        </view>\r\n        <view class=\"uv-input__content__subfix-icon\">\r\n          <slot name=\"suffix\">\r\n            <uv-icon\r\n\t\t\t\t\t\t\tv-if=\"suffixIcon\"\r\n              :name=\"suffixIcon\"\r\n              size=\"18\"\r\n              :customStyle=\"suffixIconStyle\"\r\n            ></uv-icon>\r\n          </slot>\r\n        </view>\r\n      </view>\r\n    </view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from \"./props.js\";\r\n\t/**\r\n\t * Input 输入框\r\n\t * @description  此组件为一个输入框，默认没有边框和样式，是专门为配合表单组件uv-form而设计的，利用它可以快速实现表单验证，输入内容，下拉选择等功能。\r\n\t * @tutorial https://www.uvui.cn/components/input.html\r\n\t * @property {String | Number}\tvalue\t\t\t\t\t输入的值\r\n\t * @property {String}\t\t\ttype\t\t\t\t\t输入框类型，见上方说明 （ 默认 'text' ）\r\n\t * @property {Boolean}\t\t\tfixed\t\t\t\t\t如果 textarea 是在一个 position:fixed 的区域，需要显示指定属性 fixed 为 true，兼容性：微信小程序、百度小程序、字节跳动小程序、QQ小程序 （ 默认 false ）\r\n\t * @property {Boolean}\t\t\tdisabled\t\t\t\t是否禁用输入框 （ 默认 false ）\r\n\t * @property {String}\t\t\tdisabledColor\t\t\t禁用状态时的背景色（ 默认 '#f5f7fa' ）\r\n\t * @property {Boolean}\t\t\tclearable\t\t\t\t是否显示清除控件 （ 默认 false ）\r\n\t * @property {Boolean}\t\t\tpassword\t\t\t\t是否密码类型 （ 默认 false ）\r\n\t * @property {String | Number}\tmaxlength\t\t\t\t最大输入长度，设置为 -1 的时候不限制最大长度 （ 默认 -1 ）\r\n\t * @property {String}\t\t\tplaceholder\t\t\t\t输入框为空时的占位符\r\n\t * @property {String}\t\t\tplaceholderClass\t\t指定placeholder的样式类，注意页面或组件的style中写了scoped时，需要在类名前写/deep/ （ 默认 'input-placeholder' ）\r\n\t * @property {String | Object}\tplaceholderStyle\t\t指定placeholder的样式，字符串/对象形式，如\"color: red;\"\r\n\t * @property {Boolean}\t\t\tshowWordLimit\t\t\t是否显示输入字数统计，只在 type =\"text\"或type =\"textarea\"时有效 （ 默认 false ）\r\n\t * @property {String}\t\t\tconfirmType\t\t\t\t设置右下角按钮的文字，兼容性详见uni-app文档 （ 默认 'done' ）\r\n\t * @property {Boolean}\t\t\tconfirmHold\t\t\t\t点击键盘右下角按钮时是否保持键盘不收起，H5无效 （ 默认 false ）\r\n\t * @property {Boolean}\t\t\tholdKeyboard\t\t\tfocus时，点击页面的时候不收起键盘，微信小程序有效 （ 默认 false ）\r\n\t * @property {Boolean}\t\t\tfocus\t\t\t\t\t自动获取焦点，在 H5 平台能否聚焦以及软键盘是否跟随弹出，取决于当前浏览器本身的实现。nvue 页面不支持，需使用组件的 focus()、blur() 方法控制焦点 （ 默认 false ）\r\n\t * @property {Boolean}\t\t\tautoBlur\t\t\t\t键盘收起时，是否自动失去焦点，目前仅App3.0.0+有效 （ 默认 false ）\r\n\t * @property {Boolean}\t\t\tdisableDefaultPadding\t是否去掉 iOS 下的默认内边距，仅微信小程序，且type=textarea时有效 （ 默认 false ）\r\n\t * @property {String ｜ Number}\tcursor\t\t\t\t\t指定focus时光标的位置（ 默认 -1 ）\r\n\t * @property {String ｜ Number}\tcursorSpacing\t\t\t输入框聚焦时底部与键盘的距离 （ 默认 30 ）\r\n\t * @property {String ｜ Number}\tselectionStart\t\t\t光标起始位置，自动聚集时有效，需与selection-end搭配使用 （ 默认 -1 ）\r\n\t * @property {String ｜ Number}\tselectionEnd\t\t\t光标结束位置，自动聚集时有效，需与selection-start搭配使用 （ 默认 -1 ）\r\n\t * @property {Boolean}\t\t\tadjustPosition\t\t\t键盘弹起时，是否自动上推页面 （ 默认 true ）\r\n\t * @property {String}\t\t\tinputAlign\t\t\t\t输入框内容对齐方式（ 默认 'left' ）\r\n\t * @property {String | Number}\tfontSize\t\t\t\t输入框字体的大小 （ 默认 '15px' ）\r\n\t * @property {String}\t\t\tcolor\t\t\t\t\t输入框字体颜色\t（ 默认 '#303133' ）\r\n\t * @property {Function}\t\t\tformatter\t\t\t    内容式化函数\r\n\t * @property {String}\t\t\tprefixIcon\t\t\t\t输入框前置图标\r\n\t * @property {String | Object}\tprefixIconStyle\t\t\t前置图标样式，对象或字符串\r\n\t * @property {String}\t\t\tsuffixIcon\t\t\t\t输入框后置图标\r\n\t * @property {String | Object}\tsuffixIconStyle\t\t\t后置图标样式，对象或字符串\r\n\t * @property {String}\t\t\tborder\t\t\t\t\t边框类型，surround-四周边框，bottom-底部边框，none-无边框 （ 默认 'surround' ）\r\n\t * @property {Boolean}\t\t\treadonly\t\t\t\t是否只读，与disabled不同之处在于disabled会置灰组件，而readonly则不会 （ 默认 false ）\r\n\t * @property {String}\t\t\tshape\t\t\t\t\t输入框形状，circle-圆形，square-方形 （ 默认 'square' ）\r\n\t * @property {Object}\t\t\tcustomStyle\t\t\t\t定义需要用到的外部样式\r\n\t * @property {Boolean}\t\t\tignoreCompositionEvent\t是否忽略组件内对文本合成系统事件的处理。\r\n\t * @example <uv-input v-model=\"value\" :password=\"true\" suffix-icon=\"lock-fill\" />\r\n\t */\r\n\texport default {\r\n\t\tname: \"uv-input\",\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\t// 输入框的值\r\n\t\t\t\tinnerValue: \"\",\r\n\t\t\t\t// 是否处于获得焦点状态\r\n\t\t\t\tfocused: false,\r\n\t\t\t\t// 过滤处理方法\r\n\t\t\t\tinnerFormatter: value => value\r\n\t\t\t}\r\n\t\t},\r\n\t\tcreated() {\r\n\t\t\t// #ifdef VUE2\r\n\t\t\tthis.innerValue = this.value;\r\n\t\t\t// #endif\r\n\t\t\t// #ifdef VUE3\r\n\t\t\tthis.innerValue = this.modelValue;\r\n\t\t\t// #endif\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\tvalue(newVal){\r\n\t\t\t\tthis.innerValue = newVal;\r\n\t\t\t},\r\n\t\t\tmodelValue(newVal){\r\n\t\t\t\tthis.innerValue = newVal;\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\t// 是否显示清除控件\r\n\t\t\tisShowClear() {\r\n\t\t\t\tconst { clearable, readonly, focused, innerValue } = this;\r\n\t\t\t\treturn !!clearable && !readonly && !!focused && innerValue !== \"\";\r\n\t\t\t},\r\n\t\t\t// 组件的类名\r\n\t\t\tinputClass() {\r\n\t\t\t\tlet classes = [],\r\n\t\t\t\t\t{ border, disabled, shape } = this;\r\n\t\t\t\tborder === \"surround\" &&\r\n\t\t\t\t\t(classes = classes.concat([\"uv-border\", \"uv-input--radius\"]));\r\n\t\t\t\tclasses.push(`uv-input--${shape}`);\r\n\t\t\t\tborder === \"bottom\" &&\r\n\t\t\t\t\t(classes = classes.concat([\r\n\t\t\t\t\t\t\"uv-border-bottom\",\r\n\t\t\t\t\t\t\"uv-input--no-radius\",\r\n\t\t\t\t\t]));\r\n\t\t\t\treturn classes.join(\" \");\r\n\t\t\t},\r\n\t\t\t// 组件的样式\r\n\t\t\twrapperStyle() {\r\n\t\t\t\tconst style = {};\r\n\t\t\t\t// 禁用状态下，被背景色加上对应的样式\r\n\t\t\t\tif (this.disabled) {\r\n\t\t\t\t\tstyle.backgroundColor = this.disabledColor;\r\n\t\t\t\t}\r\n\t\t\t\t// 无边框时，去除内边距\r\n\t\t\t\tif (this.border === \"none\") {\r\n\t\t\t\t\tstyle.padding = \"0\";\r\n\t\t\t\t} else {\r\n\t\t\t\t\t// 由于uni-app的iOS开发者能力有限，导致需要分开写才有效\r\n\t\t\t\t\tstyle.paddingTop = \"6px\";\r\n\t\t\t\t\tstyle.paddingBottom = \"6px\";\r\n\t\t\t\t\tstyle.paddingLeft = \"9px\";\r\n\t\t\t\t\tstyle.paddingRight = \"9px\";\r\n\t\t\t\t}\r\n\t\t\t\treturn this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle));\r\n\t\t\t},\r\n\t\t\t// 输入框的样式\r\n\t\t\tinputStyle() {\r\n\t\t\t\tconst style = {\r\n\t\t\t\t\tcolor: this.color,\r\n\t\t\t\t\tfontSize: this.$uv.addUnit(this.fontSize),\r\n\t\t\t\t\ttextAlign: this.inputAlign\r\n\t\t\t\t};\r\n\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\tif(this.disabled || this.readonly) {\r\n\t\t\t\t\tstyle['pointer-events'] = 'none';\r\n\t\t\t\t}\r\n\t\t\t\t// #endif\r\n\t\t\t\treturn style;\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\t// 在微信小程序中，不支持将函数当做props参数，故只能通过ref形式调用\r\n\t\t\tsetFormatter(e) {\r\n\t\t\t\tthis.innerFormatter = e\r\n\t\t\t},\r\n\t\t\t// 当键盘输入时，触发input事件\r\n\t\t\tonInput(e) {\r\n\t\t\t\tlet { value = \"\" } = e.detail || {};\r\n\t\t\t\t// 格式化过滤方法\r\n\t\t\t\tconst formatter = this.formatter || this.innerFormatter\r\n\t\t\t\tconst formatValue = formatter(value)\r\n\t\t\t\t// 为了避免props的单向数据流特性，需要先将innerValue值设置为当前值，再在$nextTick中重新赋予设置后的值才有效\r\n\t\t\t\tthis.innerValue = value\r\n\t\t\t\tthis.$nextTick(() => {\r\n\t\t\t\t\tthis.innerValue = formatValue;\r\n\t\t\t\t\tthis.valueChange();\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\t// 输入框失去焦点时触发\r\n\t\t\tonBlur(event) {\r\n\t\t\t\tthis.$emit(\"blur\", event.detail.value);\r\n\t\t\t\t// H5端的blur会先于点击清除控件的点击click事件触发，导致focused\r\n\t\t\t\t// 瞬间为false，从而隐藏了清除控件而无法被点击到\r\n\t\t\t\tthis.$uv.sleep(100).then(() => {\r\n\t\t\t\t\tthis.focused = false;\r\n\t\t\t\t});\r\n\t\t\t\t// 尝试调用uv-form的验证方法\r\n\t\t\t\tthis.$uv.formValidate(this, \"blur\");\r\n\t\t\t},\r\n\t\t\t// 输入框聚焦时触发\r\n\t\t\tonFocus(event) {\r\n\t\t\t\tthis.focused = true;\r\n\t\t\t\tthis.$emit(\"focus\");\r\n\t\t\t},\r\n\t\t\t// 点击完成按钮时触发\r\n\t\t\tonConfirm(event) {\r\n\t\t\t\tthis.$emit(\"confirm\", this.innerValue);\r\n\t\t\t},\r\n\t\t\t// 键盘高度发生变化的时候触发此事件\r\n\t\t\t// 兼容性：微信小程序2.7.0+、App 3.1.0+\r\n\t\t\tonkeyboardheightchange(e) {\r\n\t\t\t\tthis.$emit(\"keyboardheightchange\",e);\r\n\t\t\t},\r\n\t\t\t// 内容发生变化，进行处理\r\n\t\t\tvalueChange() {\r\n\t\t\t\tif(this.isClear) this.innerValue = '';\r\n\t\t\t\tconst value = this.innerValue;\r\n\t\t\t\tthis.$nextTick(() => {\r\n\t\t\t\t\tthis.$emit(\"input\", value);\r\n\t\t\t\t\tthis.$emit(\"update:modelValue\", value);\r\n\t\t\t\t\tthis.$emit(\"change\", value);\r\n\t\t\t\t\t// 尝试调用uv-form的验证方法\r\n\t\t\t\t\tthis.$uv.formValidate(this, \"change\");\r\n\t\t\t\t});\r\n\t\t\t},\r\n\t\t\t// 点击清除控件\r\n\t\t\tonClear() {\r\n\t\t\t\tthis.innerValue = \"\";\r\n\t\t\t\tthis.isClear = true;\r\n\t\t\t\tthis.$uv.sleep(100).then(res=>{\r\n\t\t\t\t\tthis.isClear = false;\r\n\t\t\t\t})\r\n\t\t\t\tthis.$nextTick(() => {\r\n\t\t\t\t\tthis.$emit(\"clear\");\r\n\t\t\t\t\tthis.valueChange();\r\n\t\t\t\t});\r\n\t\t\t},\r\n\t\t\t/**\r\n\t\t\t * 在安卓nvue上，事件无法冒泡\r\n\t\t\t * 在某些时间，我们希望监听uv-from-item的点击事件，此时会导致点击uv-form-item内的uv-input后\r\n\t\t\t * 无法触发uv-form-item的点击事件，这里通过手动调用uv-form-item的方法进行触发\r\n\t\t\t */\r\n\t\t\tclickHandler() {\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\tif (this.$uv.os() === \"android\") {\r\n\t\t\t\t\tconst formItem = this.$uv.$parent.call(this, \"uv-form-item\");\r\n\t\t\t\t\tif (formItem) {\r\n\t\t\t\t\t\tformItem.clickHandler();\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t// #endif\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t$show-border: 1;\r\n\t$show-border-surround: 1;\r\n\t$show-border-bottom: 1;\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t.uv-input {\r\n\t\t@include flex(row);\r\n\t\talign-items: center;\r\n\t\tjustify-content: space-between;\r\n\t\tflex: 1;\r\n\t\t&--radius,\r\n\t\t&--square {\r\n\t\t\tborder-radius: 4px;\r\n\t\t}\r\n\t\t&--no-radius {\r\n\t\t\tborder-radius: 0;\r\n\t\t}\r\n\t\t&--circle {\r\n\t\t\tborder-radius: 100px;\r\n\t\t}\r\n\t\t&__content {\r\n\t\t\tflex: 1;\r\n\t\t\t@include flex(row);\r\n\t\t\talign-items: center;\r\n\t\t\tjustify-content: space-between;\r\n\t\t\t&__field-wrapper {\r\n\t\t\t\tposition: relative;\r\n\t\t\t\t@include flex(row);\r\n\t\t\t\tmargin: 0;\r\n\t\t\t\tflex: 1;\r\n\t\t\t\t&__field {\r\n\t\t\t\t\tline-height: 26px;\r\n\t\t\t\t\ttext-align: left;\r\n\t\t\t\t\tcolor: $uv-main-color;\r\n\t\t\t\t\theight: 24px;\r\n\t\t\t\t\tfont-size: 15px;\r\n\t\t\t\t\tflex: 1;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t&__clear {\r\n\t\t\t\twidth: 20px;\r\n\t\t\t\theight: 20px;\r\n\t\t\t\tborder-radius: 100px;\r\n\t\t\t\tbackground-color: #c6c7cb;\r\n\t\t\t\t@include flex(row);\r\n\t\t\t\talign-items: center;\r\n\t\t\t\tjustify-content: center;\r\n\t\t\t\ttransform: scale(0.82);\r\n\t\t\t\tmargin-left: 4px;\r\n\t\t\t}\r\n\t\t\t&__subfix-icon {\r\n\t\t\t\tmargin-left: 4px;\r\n\t\t\t}\r\n\t\t\t&__prefix-icon {\r\n\t\t\t\tmargin-right: 4px;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</style>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-input/package.json",
    "content": "{\r\n  \"id\": \"uv-input\",\r\n  \"displayName\": \"uv-input 输入框 全面兼容vue3+2、app、h5、小程序等多端\",\r\n  \"version\": \"1.0.10\",\r\n  \"description\": \"uv-input 该组件为一个输入框，默认没有边框和样式，是专门为配合表单组件uv-form而设计的，利用它可以快速实现表单验证，输入内容，下拉选择等功能。\",\r\n  \"keywords\": [\r\n    \"uv-input\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"input\",\r\n    \"输入框\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-icon\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-input/readme.md",
    "content": "## Input 输入框\r\n\r\n> **组件名：uv-input**\r\n\r\n此组件为一个输入框，默认没有边框和样式，是专门为配合表单组件uv-form而设计的，利用它可以快速实现表单验证，输入内容，下拉选择等功能。\r\n\r\n# <a href=\"https://www.uvui.cn/components/input.html\" target=\"_blank\">查看文档</a>\r\n\r\n## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>（请不要 下载插件ZIP）</small>\r\n\r\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n<a href=\"https://ext.dcloud.net.cn/plugin?name=uv-ui\" target=\"_blank\">\r\n\r\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\r\n\r\n</a>\r\n\r\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-keyboard/changelog.md",
    "content": "## 1.0.5（2023-10-12）\n1. 增加disKeys参数，mode = \"car\"下，被禁用的键，如：['I','O']\n2. 增加customabc参数，mode = \"car\"下，是否启用自定义中英文切换内容模式，为了兼容支付宝等小程序不兼容嵌套插槽，导致同时显示自定义内容和原始内容\n3. 增加ref方法changeCarMode，mode = \"car\"下， 调用此方法可以切换中英文模式\n4. 增加@changeCarInputMode，mode = \"car\"下，调用此方法可以进行切换中英文\n5. 增加插槽abc，mode = \"car\"下，自定义中英文切换内容，具体参考[车牌键盘自定义中英文切换及禁用键等设置](https://www.uvui.cn/components/keyboard.html#车牌键盘自定义中英文切换及禁用键等设置)\n## 1.0.4（2023-09-04）\r\n1. 优化，修改文件名称\r\n## 1.0.3（2023-09-04）\r\n1. 修复键盘change回调事件产生冲突的BUG\r\n## 1.0.2（2023-07-02）\r\nuv-keyboard  由于弹出层uv-popup的修改，打开和关闭方法更改，详情参考文档：https://www.uvui.cn/components/keyboard.html\r\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-keyboard 键盘\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-keyboard/components/uv-car-keyboard/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 是否打乱键盘按键的顺序\r\n\t\trandom: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 输入一个中文后，是否自动切换到英文\r\n\t\tautoChange: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t}\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-keyboard/components/uv-car-keyboard/uv-car-keyboard.vue",
    "content": "<template>\r\n\t<view\r\n\t\tclass=\"uv-keyboard\"\r\n\t\t@touchmove.stop.prevent=\"noop\"\r\n\t>\r\n\t\t<view\r\n\t\t\tv-for=\"(group, i) in abc ? engKeyBoardList : areaList\"\r\n\t\t\t:key=\"i\"\r\n\t\t\tclass=\"uv-keyboard__button\"\r\n\t\t\t:index=\"i\"\r\n\t\t\t:class=\"[i + 1 === 4 && 'uv-keyboard__button--center']\"\r\n\t\t>\r\n\t\t\t<view\r\n\t\t\t\tv-if=\"i === 3\"\r\n\t\t\t\tclass=\"uv-keyboard__button__inner-wrapper\"\r\n\t\t\t>\r\n\t\t\t\t<view\r\n\t\t\t\t\tclass=\"uv-keyboard__button__inner-wrapper__left\"\r\n\t\t\t\t\thover-class=\"uv-hover-class\"\r\n\t\t\t\t\t:hover-stay-time=\"200\"\r\n\t\t\t\t\t@tap=\"changeCarInputMode\"\r\n\t\t\t\t>\r\n\t\t\t\t\t<text\r\n\t\t\t\t\t\tclass=\"uv-keyboard__button__inner-wrapper__left__lang\"\r\n\t\t\t\t\t\t:class=\"[!abc && 'uv-keyboard__button__inner-wrapper__left__lang--active']\"\r\n\t\t\t\t\t>中</text>\r\n\t\t\t\t\t<text class=\"uv-keyboard__button__inner-wrapper__left__line\">/</text>\r\n\t\t\t\t\t<text\r\n\t\t\t\t\t\tclass=\"uv-keyboard__button__inner-wrapper__left__lang\"\r\n\t\t\t\t\t\t:class=\"[abc && 'uv-keyboard__button__inner-wrapper__left__lang--active']\"\r\n\t\t\t\t\t>英</text>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t\t<view\r\n\t\t\t\tclass=\"uv-keyboard__button__inner-wrapper\"\r\n\t\t\t\tv-for=\"(item, j) in group\"\r\n\t\t\t\t:key=\"j\"\r\n\t\t\t>\r\n\t\t\t\t<view\r\n\t\t\t\t\tclass=\"uv-keyboard__button__inner-wrapper__inner\"\r\n\t\t\t\t\t:hover-stay-time=\"200\"\r\n\t\t\t\t\t@tap=\"carInputClick(i, j)\"\r\n\t\t\t\t\thover-class=\"uv-hover-class\"\r\n\t\t\t\t>\r\n\t\t\t\t\t<text class=\"uv-keyboard__button__inner-wrapper__inner__text\">{{ item }}</text>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t\t<view\r\n\t\t\t\tv-if=\"i === 3\"\r\n\t\t\t\t@touchstart=\"backspaceClick\"\r\n\t\t\t\t@touchend=\"clearTimer\"\r\n\t\t\t\tclass=\"uv-keyboard__button__inner-wrapper\"\r\n\t\t\t>\r\n\t\t\t\t<view\r\n\t\t\t\t\tclass=\"uv-keyboard__button__inner-wrapper__right\"\r\n\t\t\t\t\thover-class=\"uv-hover-class\"\r\n\t\t\t\t\t:hover-stay-time=\"200\"\r\n\t\t\t\t>\r\n\t\t\t\t\t<uv-icon\r\n\t\t\t\t\t\tsize=\"28\"\r\n\t\t\t\t\t\tname=\"backspace\"\r\n\t\t\t\t\t\tcolor=\"#303133\"\r\n\t\t\t\t\t></uv-icon>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * keyboard 键盘组件\r\n\t * @description 此为uvui自定义的键盘面板，内含了数字键盘，车牌号键，身份证号键盘3种模式，都有可以打乱按键顺序的选项。\r\n\t * @tutorial https://www.uvui.cn/components/keyboard.html\r\n\t * @property {Boolean} random 是否打乱键盘的顺序\r\n\t * @event {Function} change 点击键盘触发\r\n\t * @event {Function} backspace 点击退格键触发\r\n\t * @example <uv-keyboard ref=\"uKeyboard\" mode=\"car\" v-model=\"show\"></uv-keyboard>\r\n\t */\r\n\texport default {\r\n\t\tname: \"uv-keyboard\",\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\t// 车牌输入时，abc=true为输入车牌号码，bac=false为输入省份中文简称\r\n\t\t\t\tabc: false\r\n\t\t\t};\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\tareaList() {\r\n\t\t\t\tlet data = [\r\n\t\t\t\t\t'京',\r\n\t\t\t\t\t'沪',\r\n\t\t\t\t\t'粤',\r\n\t\t\t\t\t'津',\r\n\t\t\t\t\t'冀',\r\n\t\t\t\t\t'豫',\r\n\t\t\t\t\t'云',\r\n\t\t\t\t\t'辽',\r\n\t\t\t\t\t'黑',\r\n\t\t\t\t\t'湘',\r\n\t\t\t\t\t'皖',\r\n\t\t\t\t\t'鲁',\r\n\t\t\t\t\t'苏',\r\n\t\t\t\t\t'浙',\r\n\t\t\t\t\t'赣',\r\n\t\t\t\t\t'鄂',\r\n\t\t\t\t\t'桂',\r\n\t\t\t\t\t'甘',\r\n\t\t\t\t\t'晋',\r\n\t\t\t\t\t'陕',\r\n\t\t\t\t\t'蒙',\r\n\t\t\t\t\t'吉',\r\n\t\t\t\t\t'闽',\r\n\t\t\t\t\t'贵',\r\n\t\t\t\t\t'渝',\r\n\t\t\t\t\t'川',\r\n\t\t\t\t\t'青',\r\n\t\t\t\t\t'琼',\r\n\t\t\t\t\t'宁',\r\n\t\t\t\t\t'挂',\r\n\t\t\t\t\t'藏',\r\n\t\t\t\t\t'港',\r\n\t\t\t\t\t'澳',\r\n\t\t\t\t\t'新',\r\n\t\t\t\t\t'使',\r\n\t\t\t\t\t'学'\r\n\t\t\t\t];\r\n\t\t\t\tlet tmp = [];\r\n\t\t\t\t// 打乱顺序\r\n\t\t\t\tif (this.random) data = this.$uv.randomArray(data);\r\n\t\t\t\t// 切割成二维数组\r\n\t\t\t\ttmp[0] = data.slice(0, 10);\r\n\t\t\t\ttmp[1] = data.slice(10, 20);\r\n\t\t\t\ttmp[2] = data.slice(20, 30);\r\n\t\t\t\ttmp[3] = data.slice(30, 36);\r\n\t\t\t\treturn tmp;\r\n\t\t\t},\r\n\t\t\tengKeyBoardList() {\r\n\t\t\t\tlet data = [\r\n\t\t\t\t\t1,\r\n\t\t\t\t\t2,\r\n\t\t\t\t\t3,\r\n\t\t\t\t\t4,\r\n\t\t\t\t\t5,\r\n\t\t\t\t\t6,\r\n\t\t\t\t\t7,\r\n\t\t\t\t\t8,\r\n\t\t\t\t\t9,\r\n\t\t\t\t\t0,\r\n\t\t\t\t\t'Q',\r\n\t\t\t\t\t'W',\r\n\t\t\t\t\t'E',\r\n\t\t\t\t\t'R',\r\n\t\t\t\t\t'T',\r\n\t\t\t\t\t'Y',\r\n\t\t\t\t\t'U',\r\n\t\t\t\t\t'I',\r\n\t\t\t\t\t'O',\r\n\t\t\t\t\t'P',\r\n\t\t\t\t\t'A',\r\n\t\t\t\t\t'S',\r\n\t\t\t\t\t'D',\r\n\t\t\t\t\t'F',\r\n\t\t\t\t\t'G',\r\n\t\t\t\t\t'H',\r\n\t\t\t\t\t'J',\r\n\t\t\t\t\t'K',\r\n\t\t\t\t\t'L',\r\n\t\t\t\t\t'Z',\r\n\t\t\t\t\t'X',\r\n\t\t\t\t\t'C',\r\n\t\t\t\t\t'V',\r\n\t\t\t\t\t'B',\r\n\t\t\t\t\t'N',\r\n\t\t\t\t\t'M'\r\n\t\t\t\t];\r\n\t\t\t\tlet tmp = [];\r\n\t\t\t\tif (this.random) data = this.$uv.randomArray(data);\r\n\t\t\t\ttmp[0] = data.slice(0, 10);\r\n\t\t\t\ttmp[1] = data.slice(10, 20);\r\n\t\t\t\ttmp[2] = data.slice(20, 30);\r\n\t\t\t\ttmp[3] = data.slice(30, 36);\r\n\t\t\t\treturn tmp;\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\t// 点击键盘按钮\r\n\t\t\tcarInputClick(i, j) {\r\n\t\t\t\tlet value = '';\r\n\t\t\t\t// 不同模式，获取不同数组的值\r\n\t\t\t\tif (this.abc) value = this.engKeyBoardList[i][j];\r\n\t\t\t\telse value = this.areaList[i][j];\r\n\t\t\t\t// 如果允许自动切换，则将中文状态切换为英文\r\n\t\t\t\tif (!this.abc && this.autoChange) this.$uv.sleep(200).then(() => this.abc = true)\r\n\t\t\t\tthis.$emit('change', value);\r\n\t\t\t},\r\n\t\t\t// 修改汽车牌键盘的输入模式，中文|英文\r\n\t\t\tchangeCarInputMode() {\r\n\t\t\t\tthis.abc = !this.abc;\r\n\t\t\t},\r\n\t\t\t// 点击退格键\r\n\t\t\tbackspaceClick() {\r\n\t\t\t\tthis.$emit('backspace');\r\n\t\t\t\tclearInterval(this.timer); //再次清空定时器，防止重复注册定时器\r\n\t\t\t\tthis.timer = null;\r\n\t\t\t\tthis.timer = setInterval(() => {\r\n\t\t\t\t\tthis.$emit('backspace');\r\n\t\t\t\t}, 250);\r\n\t\t\t},\r\n\t\t\tclearTimer() {\r\n\t\t\t\tclearInterval(this.timer);\r\n\t\t\t\tthis.timer = null;\r\n\t\t\t},\r\n\t\t}\r\n\t};\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t$show-hover: 1;\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t$uv-car-keyboard-background-color: rgb(224, 228, 230) !default;\r\n\t$uv-car-keyboard-padding:6px 0 6px !default;\r\n\t$uv-car-keyboard-button-inner-width:64rpx !default;\r\n\t$uv-car-keyboard-button-inner-background-color:#FFFFFF !default;\r\n\t$uv-car-keyboard-button-height:80rpx !default;\r\n\t$uv-car-keyboard-button-inner-box-shadow:0 1px 0px #999992 !default;\r\n\t$uv-car-keyboard-button-border-radius:4px !default;\r\n\t$uv-car-keyboard-button-inner-margin:8rpx 5rpx !default;\r\n\t$uv-car-keyboard-button-text-font-size:16px !default;\r\n\t$uv-car-keyboard-button-text-color:$uv-main-color !default;\r\n\t$uv-car-keyboard-center-inner-margin: 0 4rpx !default;\r\n\t$uv-car-keyboard-special-button-width:134rpx !default;\r\n\t$uv-car-keyboard-lang-font-size:16px !default;\r\n\t$uv-car-keyboard-lang-color:$uv-main-color !default;\r\n\t$uv-car-keyboard-active-color:$uv-primary !default;\r\n\t$uv-car-keyboard-line-font-size:15px !default;\r\n\t$uv-car-keyboard-line-color:$uv-main-color !default;\r\n\t$uv-car-keyboard-line-margin:0 1px !default;\r\n\t$uv-car-keyboard-uv-hover-class-background-color:#BBBCC6 !default;\r\n\r\n\t.uv-keyboard {\r\n\t\t@include flex(column);\r\n\t\tjustify-content: space-around;\r\n\t\tbackground-color: $uv-car-keyboard-background-color;\r\n\t\talign-items: stretch;\r\n\t\tpadding: $uv-car-keyboard-padding;\r\n\r\n\t\t&__button {\r\n\t\t\t@include flex;\r\n\t\t\tjustify-content: center;\r\n\t\t\tflex: 1;\r\n\t\t\t/* #ifndef APP-NVUE */\r\n\t\t\t/* #endif */\r\n\r\n\t\t\t&__inner-wrapper {\r\n\t\t\t\tbox-shadow: $uv-car-keyboard-button-inner-box-shadow;\r\n\t\t\t\tmargin: $uv-car-keyboard-button-inner-margin;\r\n\t\t\t\tborder-radius: $uv-car-keyboard-button-border-radius;\r\n\r\n\t\t\t\t&__inner {\r\n\t\t\t\t\t@include flex;\r\n\t\t\t\t\tjustify-content: center;\r\n\t\t\t\t\talign-items: center;\r\n\t\t\t\t\twidth: $uv-car-keyboard-button-inner-width;\r\n\t\t\t\t\tbackground-color: $uv-car-keyboard-button-inner-background-color;\r\n\t\t\t\t\theight: $uv-car-keyboard-button-height;\r\n\t\t\t\t\tborder-radius: $uv-car-keyboard-button-border-radius;\r\n\r\n\t\t\t\t\t&__text {\r\n\t\t\t\t\t\tfont-size: $uv-car-keyboard-button-text-font-size;\r\n\t\t\t\t\t\tcolor: $uv-car-keyboard-button-text-color;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\t&__left,\r\n\t\t\t\t&__right {\r\n\t\t\t\t\tborder-radius: $uv-car-keyboard-button-border-radius;\r\n\t\t\t\t\twidth: $uv-car-keyboard-special-button-width;\r\n\t\t\t\t\theight: $uv-car-keyboard-button-height;\r\n\t\t\t\t\tbackground-color: $uv-car-keyboard-uv-hover-class-background-color;\r\n\t\t\t\t\t@include flex;\r\n\t\t\t\t\tjustify-content: center;\r\n\t\t\t\t\talign-items: center;\r\n\t\t\t\t\tbox-shadow: $uv-car-keyboard-button-inner-box-shadow;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t&__left {\r\n\t\t\t\t\t&__line {\r\n\t\t\t\t\t\tfont-size: $uv-car-keyboard-line-font-size;\r\n\t\t\t\t\t\tcolor: $uv-car-keyboard-line-color;\r\n\t\t\t\t\t\tmargin: $uv-car-keyboard-line-margin;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t&__lang {\r\n\t\t\t\t\t\tfont-size: $uv-car-keyboard-lang-font-size;\r\n\t\t\t\t\t\tcolor: $uv-car-keyboard-lang-color;\r\n\r\n\t\t\t\t\t\t&--active {\r\n\t\t\t\t\t\t\tcolor: $uv-car-keyboard-active-color;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t.uv-hover-class {\r\n\t\tbackground-color: $uv-car-keyboard-uv-hover-class-background-color;\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-keyboard/components/uv-keyboard/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 键盘的类型，number-数字键盘，card-身份证键盘，car-车牌号键盘\r\n\t\tmode: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'number'\r\n\t\t},\r\n\t\t// 是否显示键盘的\".\"符号\r\n\t\tdotDisabled: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否显示顶部工具条\r\n\t\ttooltip: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 是否显示工具条中间的提示\r\n\t\tshowTips: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 工具条中间的提示文字\r\n\t\ttips: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 是否显示工具条左边的\"取消\"按钮\r\n\t\tshowCancel: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 是否显示工具条右边的\"完成\"按钮\r\n\t\tshowConfirm: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 是否打乱键盘按键的顺序\r\n\t\trandom: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否开启底部安全区适配，开启的话，会在iPhoneX机型底部添加一定的内边距\r\n\t\tsafeAreaInsetBottom: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 是否允许通过点击遮罩关闭键盘\r\n\t\tcloseOnClickOverlay: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 是否允许点击确认按钮关闭组件\r\n\t\tcloseOnClickConfirm: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 是否显示遮罩，某些时候数字键盘时，用户希望看到自己的数值，所以可能不想要遮罩\r\n\t\toverlay: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// z-index值\r\n\t\tzIndex: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 10075\r\n\t\t},\r\n\t\t// 取消按钮的文字\r\n\t\tcancelText: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '取消'\r\n\t\t},\r\n\t\t// 确认按钮的文字\r\n\t\tconfirmText: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '确定'\r\n\t\t},\r\n\t\t// 输入一个中文后，是否自动切换到英文\r\n\t\tautoChange: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 被禁用的键\r\n\t\tdisKeys: {\r\n\t\t\ttype: Array,\r\n\t\t\tdefault: ()=>[]\r\n\t\t},\r\n\t\t// 是否自定义abc\r\n\t\tcustomabc: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t...uni.$uv?.props?.keyboard\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-keyboard/components/uv-keyboard/uv-keyboard.vue",
    "content": "<template>\r\n\t<uv-popup\r\n\t\tref=\"keyboardPopup\"\r\n\t\tmode=\"bottom\"\r\n\t  :overlay=\"overlay\"\r\n\t  :closeOnClickOverlay=\"closeOnClickOverlay\"\r\n\t  :safeAreaInsetBottom=\"safeAreaInsetBottom\"\r\n\t  :zIndex=\"zIndex\"\r\n\t  :customStyle=\"{ backgroundColor: 'rgb(214, 218, 220)' }\"\r\n\t\t@change=\"popupChange\"\r\n\t>\r\n\t\t<view class=\"uv-keyboard\">\r\n\t\t\t<slot />\r\n\t\t\t<view\r\n\t\t\t  class=\"uv-keyboard__tooltip\"\r\n\t\t\t  v-if=\"tooltip\"\r\n\t\t\t>\r\n\t\t\t\t<view\r\n\t\t\t\t  hover-class=\"uv-hover-class\"\r\n\t\t\t\t  :hover-stay-time=\"100\"\r\n\t\t\t\t>\r\n\t\t\t\t\t<text\r\n\t\t\t\t\t  class=\"uv-keyboard__tooltip__item uv-keyboard__tooltip__cancel\"\r\n\t\t\t\t\t  v-if=\"showCancel\"\r\n\t\t\t\t\t  @tap=\"onCancel\"\r\n\t\t\t\t\t>{{showCancel && cancelText}}</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view>\r\n\t\t\t\t\t<text\r\n\t\t\t\t\t  v-if=\"showTips\"\r\n\t\t\t\t\t  class=\"uv-keyboard__tooltip__item uv-keyboard__tooltip__tips\"\r\n\t\t\t\t\t>{{tips ? tips : mode == 'number' ? '数字键盘' : mode == 'card' ? '身份证键盘' : '车牌号键盘'}}</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view\r\n\t\t\t\t  hover-class=\"uv-hover-class\"\r\n\t\t\t\t  :hover-stay-time=\"100\"\r\n\t\t\t\t>\r\n\t\t\t\t\t<text\r\n\t\t\t\t\t  v-if=\"showConfirm\"\r\n\t\t\t\t\t  @tap=\"onConfirm\"\r\n\t\t\t\t\t  class=\"uv-keyboard__tooltip__item uv-keyboard__tooltip__submit\"\r\n\t\t\t\t\t  hover-class=\"uv-hover-class\"\r\n\t\t\t\t\t>{{showConfirm && confirmText}}</text>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t\t<template v-if=\"mode == 'number' || mode == 'card'\">\r\n\t\t\t\t<uv-keyboard-number\r\n\t\t\t\t  :random=\"random\"\r\n\t\t\t\t  @backspace=\"backspace\"\r\n\t\t\t\t  @change=\"change\"\r\n\t\t\t\t  :mode=\"mode\"\r\n\t\t\t\t  :dotDisabled=\"dotDisabled\"\r\n\t\t\t\t></uv-keyboard-number>\r\n\t\t\t</template>\r\n\t\t\t<template v-else>\r\n\t\t\t\t<uv-keyboard-car\r\n\t\t\t\t\tref=\"uvKeyboardCarRef\"\r\n\t\t\t\t  :random=\"random\"\r\n\t\t\t\t\t:autoChange=\"autoChange\"\r\n\t\t\t\t\t:disKeys=\"disKeys\"\r\n\t\t\t\t\t:customabc=\"customabc\"\r\n\t\t\t\t  @backspace=\"backspace\"\r\n\t\t\t\t  @change=\"change\"\r\n\t\t\t\t\t@changeCarInputMode=\"changeCarInputMode\"\r\n\t\t\t\t>\r\n\t\t\t\t\t<slot name=\"abc\"></slot>\r\n\t\t\t\t</uv-keyboard-car>\r\n\t\t\t</template>\r\n\t\t</view>\r\n\t</uv-popup>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * keyboard 键盘\r\n\t * @description 此为uViw自定义的键盘面板，内含了数字键盘，车牌号键，身份证号键盘3中模式，都有可以打乱按键顺序的选项。\r\n\t * @tutorial https://www.uvui.cn/components/keyboard.html\r\n\t * @property {String}\t\t\tmode\t\t\t\t键盘类型，见官网基本使用的说明 （默认 'number' ）\r\n\t * @property {Boolean}\t\t\tdotDisabled\t\t\t是否显示\".\"按键，只在mode=number时有效 （默认 false ）\r\n\t * @property {Boolean}\t\t\ttooltip\t\t\t\t是否显示键盘顶部工具条 （默认 true ）\r\n\t * @property {Boolean}\t\t\tshowTips\t\t\t是否显示工具条中间的提示 （默认 true ）\r\n\t * @property {String}\t\t\ttips\t\t\t\t工具条中间的提示文字，见上方基本使用的说明，如不需要，请传\"\"空字符\r\n\t * @property {Boolean}\t\t\tshowCancel\t\t\t是否显示工具条左边的\"取消\"按钮 （默认 true ）\r\n\t * @property {Boolean}\t\t\tshowConfirm\t\t\t是否显示工具条右边的\"完成\"按钮（ 默认 true ）\r\n\t * @property {Boolean}\t\t\trandom\t\t\t\t是否打乱键盘按键的顺序 （默认 false ）\r\n\t * @property {Boolean}\t\t\tsafeAreaInsetBottom\t是否开启底部安全区适配 （默认 true ）\r\n\t * @property {Boolean}\t\t\tcloseOnClickOverlay\t是否允许点击遮罩收起键盘 （默认 true ）\r\n\t * @property {Boolean}\t\t\tshow\t\t\t\t控制键盘的弹出与收起（默认 false ）\r\n\t * @property {Boolean}\t\t\toverlay\t\t\t\t是否显示遮罩 （默认 true ）\r\n\t * @property {String | Number}\tzIndex\t\t\t\t弹出键盘的z-index值 （默认 1075 ）\r\n\t * @property {String}\t\t\tcancelText\t\t\t取消按钮的文字 （默认 '取消' ）\r\n\t * @property {String}\t\t\tconfirmText\t\t\t确认按钮的文字 （默认 '确认' ）\r\n\t * @property {Object}\t\t\tcustomStyle\t\t\t自定义样式，对象形式\r\n\t * @event {Function} change 按键被点击(不包含退格键被点击)\r\n\t * @event {Function} cancel 键盘顶部工具条左边的\"取消\"按钮被点击\r\n\t * @event {Function} confirm 键盘顶部工具条右边的\"完成\"按钮被点击\r\n\t * @event {Function} backspace 键盘退格键被点击\r\n\t * @example <uv-keyboard mode=\"number\" v-model=\"show\"></uv-keyboard>\r\n\t */\r\n\texport default {\r\n\t\tname: \"uv-keyboard\",\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\temits: ['close','change','confirm','cancel','backspace','changeCarInputMode'],\r\n\t\tmethods: {\r\n\t\t\topen() {\r\n\t\t\t\tthis.$refs.keyboardPopup.open();\r\n\t\t\t},\r\n\t\t\tclose() {\r\n\t\t\t\tthis.$refs.keyboardPopup.close();\r\n\t\t\t},\r\n\t\t\tpopupChange(e) {\r\n\t\t\t\tif(!e.show) this.$emit('close');\r\n\t\t\t},\r\n\t\t\tchange(e) {\r\n\t\t\t\tthis.$emit('change', e);\r\n\t\t\t},\r\n\t\t\t// 输入完成\r\n\t\t\tonConfirm() {\r\n\t\t\t\tthis.$emit('confirm');\r\n\t\t\t\tif(this.closeOnClickConfirm) this.close();\r\n\t\t\t},\r\n\t\t\t// 取消输入\r\n\t\t\tonCancel() {\r\n\t\t\t\tthis.$emit('cancel');\r\n\t\t\t\tthis.close();\r\n\t\t\t},\r\n\t\t\t// 退格键\r\n\t\t\tbackspace() {\r\n\t\t\t\tthis.$emit('backspace');\r\n\t\t\t},\r\n\t\t\t// car模式切换中文|英文方法\r\n\t\t\tchangeCarInputMode(e) {\r\n\t\t\t\tthis.$emit('changeCarInputMode',e);\r\n\t\t\t},\r\n\t\t\tchangeCarMode() {\r\n\t\t\t\tthis.$refs.uvKeyboardCarRef && this.$refs.uvKeyboardCarRef.changeCarInputMode();\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t$show-hover: 1;\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t.uv-keyboard {\r\n\r\n\t\t&__tooltip {\r\n\t\t\t@include flex;\r\n\t\t\tjustify-content: space-between;\r\n\t\t\tbackground-color: #FFFFFF;\r\n\t\t\tpadding: 14px 12px;\r\n\r\n\t\t\t&__item {\r\n\t\t\t\tcolor: #333333;\r\n\t\t\t\tflex: 1;\r\n\t\t\t\ttext-align: center;\r\n\t\t\t\tfont-size: 15px;\r\n\t\t\t}\r\n\r\n\t\t\t&__submit {\r\n\t\t\t\ttext-align: right;\r\n\t\t\t\tcolor: $uv-primary;\r\n\t\t\t}\r\n\r\n\t\t\t&__cancel {\r\n\t\t\t\ttext-align: left;\r\n\t\t\t\tcolor: #888888;\r\n\t\t\t}\r\n\r\n\t\t\t&__tips {\r\n\t\t\t\tcolor: $uv-tips-color;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-keyboard/components/uv-keyboard-car/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 是否打乱键盘按键的顺序\r\n\t\trandom: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 输入一个中文后，是否自动切换到英文\r\n\t\tautoChange: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 被禁用的键\r\n\t\tdisKeys: {\r\n\t\t\ttype: Array,\r\n\t\t\tdefault: ()=>[]\r\n\t\t},\r\n\t\t// 是否自定义abc\r\n\t\tcustomabc: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t}\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-keyboard/components/uv-keyboard-car/uv-keyboard-car.vue",
    "content": "<template>\r\n\t<view\r\n\t\tclass=\"uv-keyboard\"\r\n\t\t@touchmove.stop.prevent=\"noop\"\r\n\t>\r\n\t\t<view\r\n\t\t\tv-for=\"(group, i) in abc ? engKeyBoardList : areaList\"\r\n\t\t\t:key=\"i\"\r\n\t\t\tclass=\"uv-keyboard__button\"\r\n\t\t\t:index=\"i\"\r\n\t\t\t:class=\"[i + 1 === 4 && 'uv-keyboard__button--center']\"\r\n\t\t>\r\n\t\t\t<view\r\n\t\t\t\tv-if=\"i === 3\"\r\n\t\t\t\tclass=\"uv-keyboard__button__inner-wrapper\"\r\n\t\t\t>\r\n\t\t\t\t<view\r\n\t\t\t\t\tclass=\"uv-keyboard__button__inner-wrapper__left\"\r\n\t\t\t\t\thover-class=\"uv-hover-class\"\r\n\t\t\t\t\t:hover-stay-time=\"200\"\r\n\t\t\t\t\t@tap=\"changeCarInputMode\"\r\n\t\t\t\t>\r\n\t\t\t\t\t<slot>\r\n\t\t\t\t\t\t<template v-if=\"!customabc\">\r\n\t\t\t\t\t\t\t<text\r\n\t\t\t\t\t\t\t\tclass=\"uv-keyboard__button__inner-wrapper__left__lang\"\r\n\t\t\t\t\t\t\t\t:class=\"[!abc && 'uv-keyboard__button__inner-wrapper__left__lang--active']\"\r\n\t\t\t\t\t\t\t>中</text>\r\n\t\t\t\t\t\t\t<text class=\"uv-keyboard__button__inner-wrapper__left__line\">/</text>\r\n\t\t\t\t\t\t\t<text\r\n\t\t\t\t\t\t\t\tclass=\"uv-keyboard__button__inner-wrapper__left__lang\"\r\n\t\t\t\t\t\t\t\t:class=\"[abc && 'uv-keyboard__button__inner-wrapper__left__lang--active']\"\r\n\t\t\t\t\t\t\t>英</text>\r\n\t\t\t\t\t\t</template>\r\n\t\t\t\t\t</slot>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t\t<view\r\n\t\t\t\tclass=\"uv-keyboard__button__inner-wrapper\"\r\n\t\t\t\tv-for=\"(item, j) in group\"\r\n\t\t\t\t:key=\"j\"\r\n\t\t\t>\r\n\t\t\t\t<view\r\n\t\t\t\t\t:class=\"['uv-keyboard__button__inner-wrapper__inner',{'uv-keyboard__button__inner-wrapper__inner--disabled': isDisabled(i,j)}]\"\r\n\t\t\t\t\t:hover-stay-time=\"200\"\r\n\t\t\t\t\t@tap=\"carInputClick(i, j)\"\r\n\t\t\t\t\t:hover-class=\"isDisabled(i,j)?'none':'uv-hover-class'\"\r\n\t\t\t\t>\r\n\t\t\t\t\t<text class=\"uv-keyboard__button__inner-wrapper__inner__text\">{{ item }}</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view class=\"uv-keyboard__button__inner-wrapper__disabled--mask\" v-if=\"isDisabled(i,j)\"></view>\r\n\t\t\t</view>\r\n\t\t\t<view\r\n\t\t\t\tv-if=\"i === 3\"\r\n\t\t\t\t@touchstart=\"backspaceClick\"\r\n\t\t\t\t@touchend=\"clearTimer\"\r\n\t\t\t\tclass=\"uv-keyboard__button__inner-wrapper\"\r\n\t\t\t>\r\n\t\t\t\t<view\r\n\t\t\t\t\tclass=\"uv-keyboard__button__inner-wrapper__right\"\r\n\t\t\t\t\thover-class=\"uv-hover-class\"\r\n\t\t\t\t\t:hover-stay-time=\"200\"\r\n\t\t\t\t>\r\n\t\t\t\t\t<uv-icon\r\n\t\t\t\t\t\tsize=\"28\"\r\n\t\t\t\t\t\tname=\"backspace\"\r\n\t\t\t\t\t\tcolor=\"#303133\"\r\n\t\t\t\t\t></uv-icon>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * keyboard 键盘组件\r\n\t * @description 此为uvui自定义的键盘面板，内含了数字键盘，车牌号键，身份证号键盘3种模式，都有可以打乱按键顺序的选项。\r\n\t * @tutorial https://www.uvui.cn/components/keyboard.html\r\n\t * @property {Boolean} random 是否打乱键盘的顺序\r\n\t * @event {Function} change 点击键盘触发\r\n\t * @event {Function} backspace 点击退格键触发\r\n\t * @example <uv-keyboard ref=\"uKeyboard\" mode=\"car\" v-model=\"show\"></uv-keyboard>\r\n\t */\r\n\texport default {\r\n\t\tname: \"uv-keyboard\",\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\temits: ['backspace','change','changeCarInputMode'],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\t// 车牌输入时，abc=true为输入车牌号码，bac=false为输入省份中文简称\r\n\t\t\t\tabc: false\r\n\t\t\t};\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\tareaList() {\r\n\t\t\t\tlet data = [\r\n\t\t\t\t\t'京',\r\n\t\t\t\t\t'沪',\r\n\t\t\t\t\t'粤',\r\n\t\t\t\t\t'津',\r\n\t\t\t\t\t'冀',\r\n\t\t\t\t\t'豫',\r\n\t\t\t\t\t'云',\r\n\t\t\t\t\t'辽',\r\n\t\t\t\t\t'黑',\r\n\t\t\t\t\t'湘',\r\n\t\t\t\t\t'皖',\r\n\t\t\t\t\t'鲁',\r\n\t\t\t\t\t'苏',\r\n\t\t\t\t\t'浙',\r\n\t\t\t\t\t'赣',\r\n\t\t\t\t\t'鄂',\r\n\t\t\t\t\t'桂',\r\n\t\t\t\t\t'甘',\r\n\t\t\t\t\t'晋',\r\n\t\t\t\t\t'陕',\r\n\t\t\t\t\t'蒙',\r\n\t\t\t\t\t'吉',\r\n\t\t\t\t\t'闽',\r\n\t\t\t\t\t'贵',\r\n\t\t\t\t\t'渝',\r\n\t\t\t\t\t'川',\r\n\t\t\t\t\t'青',\r\n\t\t\t\t\t'琼',\r\n\t\t\t\t\t'宁',\r\n\t\t\t\t\t'挂',\r\n\t\t\t\t\t'藏',\r\n\t\t\t\t\t'港',\r\n\t\t\t\t\t'澳',\r\n\t\t\t\t\t'新',\r\n\t\t\t\t\t'使',\r\n\t\t\t\t\t'学'\r\n\t\t\t\t];\r\n\t\t\t\tlet tmp = [];\r\n\t\t\t\t// 打乱顺序\r\n\t\t\t\tif (this.random) data = this.$uv.randomArray(data);\r\n\t\t\t\t// 切割成二维数组\r\n\t\t\t\ttmp[0] = data.slice(0, 10);\r\n\t\t\t\ttmp[1] = data.slice(10, 20);\r\n\t\t\t\ttmp[2] = data.slice(20, 30);\r\n\t\t\t\ttmp[3] = data.slice(30, 36);\r\n\t\t\t\treturn tmp;\r\n\t\t\t},\r\n\t\t\tengKeyBoardList() {\r\n\t\t\t\tlet data = [\r\n\t\t\t\t\t1,\r\n\t\t\t\t\t2,\r\n\t\t\t\t\t3,\r\n\t\t\t\t\t4,\r\n\t\t\t\t\t5,\r\n\t\t\t\t\t6,\r\n\t\t\t\t\t7,\r\n\t\t\t\t\t8,\r\n\t\t\t\t\t9,\r\n\t\t\t\t\t0,\r\n\t\t\t\t\t'Q',\r\n\t\t\t\t\t'W',\r\n\t\t\t\t\t'E',\r\n\t\t\t\t\t'R',\r\n\t\t\t\t\t'T',\r\n\t\t\t\t\t'Y',\r\n\t\t\t\t\t'U',\r\n\t\t\t\t\t'I',\r\n\t\t\t\t\t'O',\r\n\t\t\t\t\t'P',\r\n\t\t\t\t\t'A',\r\n\t\t\t\t\t'S',\r\n\t\t\t\t\t'D',\r\n\t\t\t\t\t'F',\r\n\t\t\t\t\t'G',\r\n\t\t\t\t\t'H',\r\n\t\t\t\t\t'J',\r\n\t\t\t\t\t'K',\r\n\t\t\t\t\t'L',\r\n\t\t\t\t\t'Z',\r\n\t\t\t\t\t'X',\r\n\t\t\t\t\t'C',\r\n\t\t\t\t\t'V',\r\n\t\t\t\t\t'B',\r\n\t\t\t\t\t'N',\r\n\t\t\t\t\t'M'\r\n\t\t\t\t];\r\n\t\t\t\tlet tmp = [];\r\n\t\t\t\tif (this.random) data = this.$uv.randomArray(data);\r\n\t\t\t\ttmp[0] = data.slice(0, 10);\r\n\t\t\t\ttmp[1] = data.slice(10, 20);\r\n\t\t\t\ttmp[2] = data.slice(20, 30);\r\n\t\t\t\ttmp[3] = data.slice(30, 36);\r\n\t\t\t\treturn tmp;\r\n\t\t\t},\r\n\t\t\tisDisabled(i,j) {\r\n\t\t\t\treturn (i,j)=>{\r\n\t\t\t\t\tlet value = '';\r\n\t\t\t\t\tif (this.abc) value = this.engKeyBoardList[i][j];\r\n\t\t\t\t\telse value = this.areaList[i][j];\r\n\t\t\t\t\treturn this.disKeys.indexOf(value) > -1;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\t// 点击键盘按钮\r\n\t\t\tcarInputClick(i, j) {\r\n\t\t\t\tif(this.isDisabled(i,j)) return;\r\n\t\t\t\tlet value = '';\r\n\t\t\t\t// 不同模式，获取不同数组的值\r\n\t\t\t\tif (this.abc) value = this.engKeyBoardList[i][j];\r\n\t\t\t\telse value = this.areaList[i][j];\r\n\t\t\t\t// 如果允许自动切换，则将中文状态切换为英文\r\n\t\t\t\tif (!this.abc && this.autoChange) this.$uv.sleep(200).then(() => this.abc = true)\r\n\t\t\t\tthis.$emit('change', value);\r\n\t\t\t},\r\n\t\t\t// 修改汽车牌键盘的输入模式，中文|英文\r\n\t\t\tchangeCarInputMode() {\r\n\t\t\t\tthis.abc = !this.abc;\r\n\t\t\t\tthis.$emit('changeCarInputMode',this.abc);\r\n\t\t\t},\r\n\t\t\t// 点击退格键\r\n\t\t\tbackspaceClick() {\r\n\t\t\t\tthis.$emit('backspace');\r\n\t\t\t\tclearInterval(this.timer); //再次清空定时器，防止重复注册定时器\r\n\t\t\t\tthis.timer = null;\r\n\t\t\t\tthis.timer = setInterval(() => {\r\n\t\t\t\t\tthis.$emit('backspace');\r\n\t\t\t\t}, 250);\r\n\t\t\t},\r\n\t\t\tclearTimer() {\r\n\t\t\t\tclearInterval(this.timer);\r\n\t\t\t\tthis.timer = null;\r\n\t\t\t},\r\n\t\t}\r\n\t};\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t$show-hover: 1;\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t$uv-car-keyboard-background-color: rgb(224, 228, 230) !default;\r\n\t$uv-car-keyboard-padding:6px 0 6px !default;\r\n\t$uv-car-keyboard-button-inner-width:64rpx !default;\r\n\t$uv-car-keyboard-button-inner-background-color:#FFFFFF !default;\r\n\t$uv-car-keyboard-button-height:80rpx !default;\r\n\t$uv-car-keyboard-button-inner-box-shadow:0 1px 0px #999992 !default;\r\n\t$uv-car-keyboard-button-border-radius:4px !default;\r\n\t$uv-car-keyboard-button-inner-margin:8rpx 5rpx !default;\r\n\t$uv-car-keyboard-button-text-font-size:16px !default;\r\n\t$uv-car-keyboard-button-text-color:$uv-main-color !default;\r\n\t$uv-car-keyboard-center-inner-margin: 0 4rpx !default;\r\n\t$uv-car-keyboard-special-button-width:134rpx !default;\r\n\t$uv-car-keyboard-lang-font-size:16px !default;\r\n\t$uv-car-keyboard-lang-color:$uv-main-color !default;\r\n\t$uv-car-keyboard-active-color:$uv-primary !default;\r\n\t$uv-car-keyboard-line-font-size:15px !default;\r\n\t$uv-car-keyboard-line-color:$uv-main-color !default;\r\n\t$uv-car-keyboard-line-margin:0 1px !default;\r\n\t$uv-car-keyboard-uv-hover-class-background-color:#BBBCC6 !default;\r\n\r\n\t.uv-keyboard {\r\n\t\t@include flex(column);\r\n\t\tjustify-content: space-around;\r\n\t\tbackground-color: $uv-car-keyboard-background-color;\r\n\t\talign-items: stretch;\r\n\t\tpadding: $uv-car-keyboard-padding;\r\n\r\n\t\t&__button {\r\n\t\t\t@include flex;\r\n\t\t\tjustify-content: center;\r\n\t\t\tflex: 1;\r\n\t\t\t/* #ifndef APP-NVUE */\r\n\t\t\t/* #endif */\r\n\r\n\t\t\t&__inner-wrapper {\r\n\t\t\t\tposition: relative;\r\n\t\t\t\tbox-shadow: $uv-car-keyboard-button-inner-box-shadow;\r\n\t\t\t\tmargin: $uv-car-keyboard-button-inner-margin;\r\n\t\t\t\tborder-radius: $uv-car-keyboard-button-border-radius;\r\n\t\t\t\t\r\n\t\t\t\t&__disabled--mask {\r\n\t\t\t\t\tposition: absolute;\r\n\t\t\t\t\tleft: 0;\r\n\t\t\t\t\ttop: 0;\r\n\t\t\t\t\tbottom: 0;\r\n\t\t\t\t\tright: 0;\r\n\t\t\t\t\twidth: $uv-car-keyboard-button-inner-width;\r\n\t\t\t\t\theight: $uv-car-keyboard-button-height;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t&__inner {\r\n\t\t\t\t\t@include flex;\r\n\t\t\t\t\tjustify-content: center;\r\n\t\t\t\t\talign-items: center;\r\n\t\t\t\t\twidth: $uv-car-keyboard-button-inner-width;\r\n\t\t\t\t\tbackground-color: $uv-car-keyboard-button-inner-background-color;\r\n\t\t\t\t\theight: $uv-car-keyboard-button-height;\r\n\t\t\t\t\tborder-radius: $uv-car-keyboard-button-border-radius;\r\n\t\t\t\t\t\r\n\t\t\t\t\t&--disabled {\r\n\t\t\t\t\t\topacity: 0.5;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t&__text {\r\n\t\t\t\t\t\tfont-size: $uv-car-keyboard-button-text-font-size;\r\n\t\t\t\t\t\tcolor: $uv-car-keyboard-button-text-color;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\t&__left,\r\n\t\t\t\t&__right {\r\n\t\t\t\t\tborder-radius: $uv-car-keyboard-button-border-radius;\r\n\t\t\t\t\twidth: $uv-car-keyboard-special-button-width;\r\n\t\t\t\t\theight: $uv-car-keyboard-button-height;\r\n\t\t\t\t\tbackground-color: $uv-car-keyboard-uv-hover-class-background-color;\r\n\t\t\t\t\t@include flex;\r\n\t\t\t\t\tjustify-content: center;\r\n\t\t\t\t\talign-items: center;\r\n\t\t\t\t\tbox-shadow: $uv-car-keyboard-button-inner-box-shadow;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t&__left {\r\n\t\t\t\t\t&__line {\r\n\t\t\t\t\t\tfont-size: $uv-car-keyboard-line-font-size;\r\n\t\t\t\t\t\tcolor: $uv-car-keyboard-line-color;\r\n\t\t\t\t\t\tmargin: $uv-car-keyboard-line-margin;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t&__lang {\r\n\t\t\t\t\t\tfont-size: $uv-car-keyboard-lang-font-size;\r\n\t\t\t\t\t\tcolor: $uv-car-keyboard-lang-color;\r\n\r\n\t\t\t\t\t\t&--active {\r\n\t\t\t\t\t\t\tcolor: $uv-car-keyboard-active-color;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t.uv-hover-class {\r\n\t\tbackground-color: $uv-car-keyboard-uv-hover-class-background-color;\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-keyboard/components/uv-keyboard-number/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 键盘的类型，number-数字键盘，card-身份证键盘\r\n\t\tmode: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'number'\r\n\t\t},\r\n\t\t// 是否显示键盘的\".\"符号\r\n\t\tdotDisabled: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否打乱键盘按键的顺序\r\n\t\trandom: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t}\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-keyboard/components/uv-keyboard-number/uv-keyboard-number.vue",
    "content": "<template>\r\n\t<view\r\n\t\tclass=\"uv-keyboard\"\r\n\t\t@touchmove.stop.prevent=\"noop\"\r\n\t>\r\n\t\t<view\r\n\t\t\tclass=\"uv-keyboard__button-wrapper\"\r\n\t\t\tv-for=\"(item, index) in numList\"\r\n\t\t\t:key=\"index\"\r\n\t\t>\r\n\t\t\t<view\r\n\t\t\t\tclass=\"uv-keyboard__button-wrapper__button\"\r\n\t\t\t\t:style=\"[itemStyle(index)]\"\r\n\t\t\t\t@tap=\"keyboardClick(item)\"\r\n\t\t\t\thover-class=\"uv-hover-class\"\r\n\t\t\t\t:hover-stay-time=\"200\"\r\n\t\t\t>\r\n\t\t\t\t<text class=\"uv-keyboard__button-wrapper__button__text\">{{ item }}</text>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t\t<view\r\n\t\t\tclass=\"uv-keyboard__button-wrapper\"\r\n\t\t>\r\n\t\t\t<view\r\n\t\t\t\tclass=\"uv-keyboard__button-wrapper__button uv-keyboard__button-wrapper__button--gray\"\r\n\t\t\t\thover-class=\"uv-hover-class\"\r\n\t\t\t\t:hover-stay-time=\"200\"\r\n\t\t\t\t@touchstart.stop=\"backspaceClick\"\r\n\t\t\t\t@touchend=\"clearTimer\"\r\n\t\t\t>\r\n\t\t\t\t<uv-icon\r\n\t\t\t\t\tname=\"backspace\"\r\n\t\t\t\t\tcolor=\"#303133\"\r\n\t\t\t\t\tsize=\"28\"\r\n\t\t\t\t></uv-icon>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * keyboard 键盘组件\r\n\t * @description\r\n\t * @tutorial\r\n\t * @property {String}\tmode\t\t键盘的类型，number-数字键盘，card-身份证键盘\r\n\t * @property {Boolean}\tdotDisabled\t是否显示键盘的\".\"符号\r\n\t * @property {Boolean}\trandom\t\t是否打乱键盘按键的顺序\r\n\t * @event {Function} change\t\t点击键盘触发\r\n\t * @event {Function} backspace\t点击退格键触发\r\n\t * @example\r\n\t */\r\n\texport default {\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\temits: ['backspace','change'],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tbackspace: 'backspace', // 退格键内容\r\n\t\t\t\tdot: '.', // 点\r\n\t\t\t\ttimer: null, // 长按多次删除的事件监听\r\n\t\t\t\tcardX: 'X' // 身份证的X符号\r\n\t\t\t};\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\t// 键盘需要显示的内容\r\n\t\t\tnumList() {\r\n\t\t\t\tlet tmp = [];\r\n\t\t\t\tif (this.dotDisabled && this.mode == 'number') {\r\n\t\t\t\t\tif (!this.random) {\r\n\t\t\t\t\t\treturn [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\treturn this.$uv.randomArray([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]);\r\n\t\t\t\t\t}\r\n\t\t\t\t} else if (!this.dotDisabled && this.mode == 'number') {\r\n\t\t\t\t\tif (!this.random) {\r\n\t\t\t\t\t\treturn [1, 2, 3, 4, 5, 6, 7, 8, 9, this.dot, 0];\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\treturn this.$uv.randomArray([1, 2, 3, 4, 5, 6, 7, 8, 9, this.dot, 0]);\r\n\t\t\t\t\t}\r\n\t\t\t\t} else if (this.mode == 'card') {\r\n\t\t\t\t\tif (!this.random) {\r\n\t\t\t\t\t\treturn [1, 2, 3, 4, 5, 6, 7, 8, 9, this.cardX, 0];\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\treturn this.$uv.randomArray([1, 2, 3, 4, 5, 6, 7, 8, 9, this.cardX, 0]);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t// 按键的样式，在非乱序&&数字键盘&&不显示点按钮时，index为9时，按键占位两个空间\r\n\t\t\titemStyle() {\r\n\t\t\t\treturn index => {\r\n\t\t\t\t\tlet style = {};\r\n\t\t\t\t\tif (this.mode == 'number' && this.dotDisabled && index == 9) style.width = '464rpx';\r\n\t\t\t\t\treturn style;\r\n\t\t\t\t};\r\n\t\t\t},\r\n\t\t\t// 是否让按键显示灰色，只在非乱序&&数字键盘&&且允许点按键的时候\r\n\t\t\tbtnBgGray() {\r\n\t\t\t\treturn index => {\r\n\t\t\t\t\tif (!this.random && index == 9 && (this.mode != 'number' || (this.mode == 'number' && !this\r\n\t\t\t\t\t\t\t.dotDisabled))) return true;\r\n\t\t\t\t\telse return false;\r\n\t\t\t\t};\r\n\t\t\t},\r\n\t\t},\r\n\t\tcreated() {\r\n\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\t// 点击退格键\r\n\t\t\tbackspaceClick() {\r\n\t\t\t\tthis.$emit('backspace');\r\n\t\t\t\tclearInterval(this.timer); //再次清空定时器，防止重复注册定时器\r\n\t\t\t\tthis.timer = null;\r\n\t\t\t\tthis.timer = setInterval(() => {\r\n\t\t\t\t\tthis.$emit('backspace');\r\n\t\t\t\t}, 250);\r\n\t\t\t},\r\n\t\t\tclearTimer() {\r\n\t\t\t\tclearInterval(this.timer);\r\n\t\t\t\tthis.timer = null;\r\n\t\t\t},\r\n\t\t\t// 获取键盘显示的内容\r\n\t\t\tkeyboardClick(val) {\r\n\t\t\t\t// 允许键盘显示点模式和触发非点按键时，将内容转为数字类型\r\n\t\t\t\tif (!this.dotDisabled && val != this.dot && val != this.cardX) val = Number(val);\r\n\t\t\t\tthis.$emit('change', val);\r\n\t\t\t}\r\n\t\t}\r\n\t};\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t$show-hover: 1;\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t$uv-number-keyboard-background-color:rgb(224, 228, 230) !default;\r\n\t$uv-number-keyboard-padding:8px 10rpx 8px 10rpx !default;\r\n\t$uv-number-keyboard-button-width:222rpx !default;\r\n\t$uv-number-keyboard-button-margin:4px 6rpx !default;\r\n\t$uv-number-keyboard-button-border-top-left-radius:4px !default;\r\n\t$uv-number-keyboard-button-border-top-right-radius:4px !default;\r\n\t$uv-number-keyboard-button-border-bottom-left-radius:4px !default;\r\n\t$uv-number-keyboard-button-border-bottom-right-radius:4px !default;\r\n\t$uv-number-keyboard-button-height: 90rpx!default;\r\n\t$uv-number-keyboard-button-background-color:#FFFFFF !default;\r\n\t$uv-number-keyboard-button-box-shadow:0 2px 0px #BBBCBE !default;\r\n\t$uv-number-keyboard-text-font-size:20px !default;\r\n\t$uv-number-keyboard-text-font-weight:500 !default;\r\n\t$uv-number-keyboard-text-color:$uv-main-color !default;\r\n\t$uv-number-keyboard-gray-background-color:rgb(200, 202, 210) !default;\r\n\t$uv-number-keyboard-uv-hover-class-background-color: #BBBCC6 !default;\r\n\r\n\t.uv-keyboard {\r\n\t\t@include flex;\r\n\t\tflex-direction: row;\r\n\t\tjustify-content: space-around;\r\n\t\tbackground-color: $uv-number-keyboard-background-color;\r\n\t\tflex-wrap: wrap;\r\n\t\tpadding: $uv-number-keyboard-padding;\r\n\r\n\t\t&__button-wrapper {\r\n\t\t\tbox-shadow: $uv-number-keyboard-button-box-shadow;\r\n\t\t\tmargin: $uv-number-keyboard-button-margin;\r\n\t\t\tborder-top-left-radius: $uv-number-keyboard-button-border-top-left-radius;\r\n\t\t\tborder-top-right-radius: $uv-number-keyboard-button-border-top-right-radius;\r\n\t\t\tborder-bottom-left-radius: $uv-number-keyboard-button-border-bottom-left-radius;\r\n\t\t\tborder-bottom-right-radius: $uv-number-keyboard-button-border-bottom-right-radius;\r\n\r\n\t\t\t&__button {\r\n\t\t\t\twidth: $uv-number-keyboard-button-width;\r\n\t\t\t\theight: $uv-number-keyboard-button-height;\r\n\t\t\t\tbackground-color: $uv-number-keyboard-button-background-color;\r\n\t\t\t\t@include flex;\r\n\t\t\t\tjustify-content: center;\r\n\t\t\t\talign-items: center;\r\n\t\t\t\tborder-top-left-radius: $uv-number-keyboard-button-border-top-left-radius;\r\n\t\t\t\tborder-top-right-radius: $uv-number-keyboard-button-border-top-right-radius;\r\n\t\t\t\tborder-bottom-left-radius: $uv-number-keyboard-button-border-bottom-left-radius;\r\n\t\t\t\tborder-bottom-right-radius: $uv-number-keyboard-button-border-bottom-right-radius;\r\n\r\n\t\t\t\t&__text {\r\n\t\t\t\t\tfont-size: $uv-number-keyboard-text-font-size;\r\n\t\t\t\t\tfont-weight: $uv-number-keyboard-text-font-weight;\r\n\t\t\t\t\tcolor: $uv-number-keyboard-text-color;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t&--gray {\r\n\t\t\t\t\tbackground-color: $uv-number-keyboard-gray-background-color;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t.uv-hover-class {\r\n\t\tbackground-color: $uv-number-keyboard-uv-hover-class-background-color;\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-keyboard/components/uv-number-keyboard/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 键盘的类型，number-数字键盘，card-身份证键盘\r\n\t\tmode: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'number'\r\n\t\t},\r\n\t\t// 是否显示键盘的\".\"符号\r\n\t\tdotDisabled: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否打乱键盘按键的顺序\r\n\t\trandom: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t}\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-keyboard/components/uv-number-keyboard/uv-number-keyboard.vue",
    "content": "<template>\r\n\t<view\r\n\t\tclass=\"uv-keyboard\"\r\n\t\t@touchmove.stop.prevent=\"noop\"\r\n\t>\r\n\t\t<view\r\n\t\t\tclass=\"uv-keyboard__button-wrapper\"\r\n\t\t\tv-for=\"(item, index) in numList\"\r\n\t\t\t:key=\"index\"\r\n\t\t>\r\n\t\t\t<view\r\n\t\t\t\tclass=\"uv-keyboard__button-wrapper__button\"\r\n\t\t\t\t:style=\"[itemStyle(index)]\"\r\n\t\t\t\t@tap=\"keyboardClick(item)\"\r\n\t\t\t\thover-class=\"uv-hover-class\"\r\n\t\t\t\t:hover-stay-time=\"200\"\r\n\t\t\t>\r\n\t\t\t\t<text class=\"uv-keyboard__button-wrapper__button__text\">{{ item }}</text>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t\t<view\r\n\t\t\tclass=\"uv-keyboard__button-wrapper\"\r\n\t\t>\r\n\t\t\t<view\r\n\t\t\t\tclass=\"uv-keyboard__button-wrapper__button uv-keyboard__button-wrapper__button--gray\"\r\n\t\t\t\thover-class=\"uv-hover-class\"\r\n\t\t\t\t:hover-stay-time=\"200\"\r\n\t\t\t\t@touchstart.stop=\"backspaceClick\"\r\n\t\t\t\t@touchend=\"clearTimer\"\r\n\t\t\t>\r\n\t\t\t\t<uv-icon\r\n\t\t\t\t\tname=\"backspace\"\r\n\t\t\t\t\tcolor=\"#303133\"\r\n\t\t\t\t\tsize=\"28\"\r\n\t\t\t\t></uv-icon>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * keyboard 键盘组件\r\n\t * @description\r\n\t * @tutorial\r\n\t * @property {String}\tmode\t\t键盘的类型，number-数字键盘，card-身份证键盘\r\n\t * @property {Boolean}\tdotDisabled\t是否显示键盘的\".\"符号\r\n\t * @property {Boolean}\trandom\t\t是否打乱键盘按键的顺序\r\n\t * @event {Function} change\t\t点击键盘触发\r\n\t * @event {Function} backspace\t点击退格键触发\r\n\t * @example\r\n\t */\r\n\texport default {\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tbackspace: 'backspace', // 退格键内容\r\n\t\t\t\tdot: '.', // 点\r\n\t\t\t\ttimer: null, // 长按多次删除的事件监听\r\n\t\t\t\tcardX: 'X' // 身份证的X符号\r\n\t\t\t};\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\t// 键盘需要显示的内容\r\n\t\t\tnumList() {\r\n\t\t\t\tlet tmp = [];\r\n\t\t\t\tif (this.dotDisabled && this.mode == 'number') {\r\n\t\t\t\t\tif (!this.random) {\r\n\t\t\t\t\t\treturn [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\treturn this.$uv.randomArray([1, 2, 3, 4, 5, 6, 7, 8, 9, 0]);\r\n\t\t\t\t\t}\r\n\t\t\t\t} else if (!this.dotDisabled && this.mode == 'number') {\r\n\t\t\t\t\tif (!this.random) {\r\n\t\t\t\t\t\treturn [1, 2, 3, 4, 5, 6, 7, 8, 9, this.dot, 0];\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\treturn this.$uv.randomArray([1, 2, 3, 4, 5, 6, 7, 8, 9, this.dot, 0]);\r\n\t\t\t\t\t}\r\n\t\t\t\t} else if (this.mode == 'card') {\r\n\t\t\t\t\tif (!this.random) {\r\n\t\t\t\t\t\treturn [1, 2, 3, 4, 5, 6, 7, 8, 9, this.cardX, 0];\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\treturn this.$uv.randomArray([1, 2, 3, 4, 5, 6, 7, 8, 9, this.cardX, 0]);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t// 按键的样式，在非乱序&&数字键盘&&不显示点按钮时，index为9时，按键占位两个空间\r\n\t\t\titemStyle() {\r\n\t\t\t\treturn index => {\r\n\t\t\t\t\tlet style = {};\r\n\t\t\t\t\tif (this.mode == 'number' && this.dotDisabled && index == 9) style.width = '464rpx';\r\n\t\t\t\t\treturn style;\r\n\t\t\t\t};\r\n\t\t\t},\r\n\t\t\t// 是否让按键显示灰色，只在非乱序&&数字键盘&&且允许点按键的时候\r\n\t\t\tbtnBgGray() {\r\n\t\t\t\treturn index => {\r\n\t\t\t\t\tif (!this.random && index == 9 && (this.mode != 'number' || (this.mode == 'number' && !this\r\n\t\t\t\t\t\t\t.dotDisabled))) return true;\r\n\t\t\t\t\telse return false;\r\n\t\t\t\t};\r\n\t\t\t},\r\n\t\t},\r\n\t\tcreated() {\r\n\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\t// 点击退格键\r\n\t\t\tbackspaceClick() {\r\n\t\t\t\tthis.$emit('backspace');\r\n\t\t\t\tclearInterval(this.timer); //再次清空定时器，防止重复注册定时器\r\n\t\t\t\tthis.timer = null;\r\n\t\t\t\tthis.timer = setInterval(() => {\r\n\t\t\t\t\tthis.$emit('backspace');\r\n\t\t\t\t}, 250);\r\n\t\t\t},\r\n\t\t\tclearTimer() {\r\n\t\t\t\tclearInterval(this.timer);\r\n\t\t\t\tthis.timer = null;\r\n\t\t\t},\r\n\t\t\t// 获取键盘显示的内容\r\n\t\t\tkeyboardClick(val) {\r\n\t\t\t\t// 允许键盘显示点模式和触发非点按键时，将内容转为数字类型\r\n\t\t\t\tif (!this.dotDisabled && val != this.dot && val != this.cardX) val = Number(val);\r\n\t\t\t\tthis.$emit('change', val);\r\n\t\t\t}\r\n\t\t}\r\n\t};\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t$show-hover: 1;\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t$uv-number-keyboard-background-color:rgb(224, 228, 230) !default;\r\n\t$uv-number-keyboard-padding:8px 10rpx 8px 10rpx !default;\r\n\t$uv-number-keyboard-button-width:222rpx !default;\r\n\t$uv-number-keyboard-button-margin:4px 6rpx !default;\r\n\t$uv-number-keyboard-button-border-top-left-radius:4px !default;\r\n\t$uv-number-keyboard-button-border-top-right-radius:4px !default;\r\n\t$uv-number-keyboard-button-border-bottom-left-radius:4px !default;\r\n\t$uv-number-keyboard-button-border-bottom-right-radius:4px !default;\r\n\t$uv-number-keyboard-button-height: 90rpx!default;\r\n\t$uv-number-keyboard-button-background-color:#FFFFFF !default;\r\n\t$uv-number-keyboard-button-box-shadow:0 2px 0px #BBBCBE !default;\r\n\t$uv-number-keyboard-text-font-size:20px !default;\r\n\t$uv-number-keyboard-text-font-weight:500 !default;\r\n\t$uv-number-keyboard-text-color:$uv-main-color !default;\r\n\t$uv-number-keyboard-gray-background-color:rgb(200, 202, 210) !default;\r\n\t$uv-number-keyboard-uv-hover-class-background-color: #BBBCC6 !default;\r\n\r\n\t.uv-keyboard {\r\n\t\t@include flex;\r\n\t\tflex-direction: row;\r\n\t\tjustify-content: space-around;\r\n\t\tbackground-color: $uv-number-keyboard-background-color;\r\n\t\tflex-wrap: wrap;\r\n\t\tpadding: $uv-number-keyboard-padding;\r\n\r\n\t\t&__button-wrapper {\r\n\t\t\tbox-shadow: $uv-number-keyboard-button-box-shadow;\r\n\t\t\tmargin: $uv-number-keyboard-button-margin;\r\n\t\t\tborder-top-left-radius: $uv-number-keyboard-button-border-top-left-radius;\r\n\t\t\tborder-top-right-radius: $uv-number-keyboard-button-border-top-right-radius;\r\n\t\t\tborder-bottom-left-radius: $uv-number-keyboard-button-border-bottom-left-radius;\r\n\t\t\tborder-bottom-right-radius: $uv-number-keyboard-button-border-bottom-right-radius;\r\n\r\n\t\t\t&__button {\r\n\t\t\t\twidth: $uv-number-keyboard-button-width;\r\n\t\t\t\theight: $uv-number-keyboard-button-height;\r\n\t\t\t\tbackground-color: $uv-number-keyboard-button-background-color;\r\n\t\t\t\t@include flex;\r\n\t\t\t\tjustify-content: center;\r\n\t\t\t\talign-items: center;\r\n\t\t\t\tborder-top-left-radius: $uv-number-keyboard-button-border-top-left-radius;\r\n\t\t\t\tborder-top-right-radius: $uv-number-keyboard-button-border-top-right-radius;\r\n\t\t\t\tborder-bottom-left-radius: $uv-number-keyboard-button-border-bottom-left-radius;\r\n\t\t\t\tborder-bottom-right-radius: $uv-number-keyboard-button-border-bottom-right-radius;\r\n\r\n\t\t\t\t&__text {\r\n\t\t\t\t\tfont-size: $uv-number-keyboard-text-font-size;\r\n\t\t\t\t\tfont-weight: $uv-number-keyboard-text-font-weight;\r\n\t\t\t\t\tcolor: $uv-number-keyboard-text-color;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t&--gray {\r\n\t\t\t\t\tbackground-color: $uv-number-keyboard-gray-background-color;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t.uv-hover-class {\r\n\t\tbackground-color: $uv-number-keyboard-uv-hover-class-background-color;\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-keyboard/package.json",
    "content": "{\r\n  \"id\": \"uv-keyboard\",\r\n  \"displayName\": \"uv-keyboard 键盘 全面兼容vue3+2、app、h5、小程序等多端\",\r\n  \"version\": \"1.0.5\",\r\n  \"description\": \"uv-keyboard 该组件为自定义的键盘面板，内含了数字键盘，车牌号键，身份证号键盘3种模式，都有可以打乱按键顺序的选项。\",\r\n  \"keywords\": [\r\n    \"uv-keyboard\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"keyboard\",\r\n    \"键盘\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-popup\",\r\n\t\t\t\"uv-icon\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-keyboard/readme.md",
    "content": "## Keyboard 键盘\r\n\r\n> **组件名：uv-keyboard**\r\n\r\n该组件为自定义的键盘面板，内含了数字键盘，车牌号键，身份证号键盘3种模式，都有可以打乱按键顺序的选项。\r\n\r\n# <a href=\"https://www.uvui.cn/components/keyboard.html\" target=\"_blank\">查看文档</a>\r\n\r\n## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>（请不要 下载插件ZIP）</small>\r\n\r\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n<a href=\"https://ext.dcloud.net.cn/plugin?name=uv-ui\" target=\"_blank\">\r\n\r\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\r\n\r\n</a>\r\n\r\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-line/changelog.md",
    "content": "## 1.0.1（2023-05-16）\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\n2. 优化部分功能\n## 1.0.0（2023-05-10）\n1. 新增线条组件\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-line/components/uv-line/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\tcolor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#d6d7d9'\r\n\t\t},\r\n\t\t// 长度，竖向时表现为高度，横向时表现为长度，可以为百分比，带px单位的值等\r\n\t\tlength: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: '100%'\r\n\t\t},\r\n\t\t// 线条方向，col-竖向，row-横向\r\n\t\tdirection: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'row'\r\n\t\t},\r\n\t\t// 是否显示细边框\r\n\t\thairline: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 线条与上下左右元素的间距，字符串形式，如\"30px\"、\"20px 30px\"\r\n\t\tmargin: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 是否虚线，true-虚线，false-实线\r\n\t\tdashed: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t...uni.$uv?.props?.line\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-line/components/uv-line/uv-line.vue",
    "content": "<template>\r\n\t<view\r\n\t    class=\"uv-line\"\r\n\t    :style=\"[lineStyle]\"\r\n\t>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * line 线条\r\n\t * @description 此组件一般用于显示一根线条，用于分隔内容块，有横向和竖向两种模式，且能设置0.5px线条，使用也很简单\r\n\t * @tutorial https://www.uvui.cn/components/line.html\r\n\t * @property {String}\t\t\tcolor\t\t线条的颜色 ( 默认 '#d6d7d9' )\r\n\t * @property {String | Number}\tlength\t\t长度，竖向时表现为高度，横向时表现为长度，可以为百分比，带px单位的值等 ( 默认 '100%' )\r\n\t * @property {String}\t\t\tdirection\t线条的方向，row-横向，col-竖向 (默认 'row' )\r\n\t * @property {Boolean}\t\t\thairline\t是否显示细线条 (默认 true )\r\n\t * @property {String | Number}\tmargin\t\t线条与上下左右元素的间距，字符串形式，如\"30px\"  (默认 0 )\r\n\t * @property {Boolean}\t\t\tdashed\t\t是否虚线，true-虚线，false-实线 (默认 false )\r\n\t * @property {Object}\t\t\tcustomStyle\t定义需要用到的外部样式\r\n\t * @example <uv-line color=\"red\"></uv-line>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-line',\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tcomputed: {\r\n\t\t\tlineStyle() {\r\n\t\t\t\tconst style = {}\r\n\t\t\t\tstyle.margin = this.margin\r\n\t\t\t\t// 如果是水平线条，边框高度为1px，再通过transform缩小一半，就是0.5px了\r\n\t\t\t\tif (this.direction === 'row') {\r\n\t\t\t\t\t// 此处采用兼容分开写，兼容nvue的写法\r\n\t\t\t\t\tstyle.borderBottomWidth = '1px'\r\n\t\t\t\t\tstyle.borderBottomStyle = this.dashed ? 'dashed' : 'solid'\r\n\t\t\t\t\tstyle.width = this.$uv.addUnit(this.length)\r\n\t\t\t\t\tif (this.hairline) style.transform = 'scaleY(0.5)'\r\n\t\t\t\t} else {\r\n\t\t\t\t\t// 如果是竖向线条，边框宽度为1px，再通过transform缩小一半，就是0.5px了\r\n\t\t\t\t\tstyle.borderLeftWidth = '1px'\r\n\t\t\t\t\tstyle.borderLeftStyle = this.dashed ? 'dashed' : 'solid'\r\n\t\t\t\t\tstyle.height = this.$uv.addUnit(this.length)\r\n\t\t\t\t\tif (this.hairline) style.transform = 'scaleX(0.5)'\r\n\t\t\t\t}\r\n\t\t\t\tstyle.borderColor = this.color\r\n\t\t\t\treturn this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t.uv-line {\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\tvertical-align: middle;\r\n\t\t/* #endif */\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-line/package.json",
    "content": "{\r\n  \"id\": \"uv-line\",\r\n  \"displayName\": \"uv-line 线条  全面兼容小程序、nvue、vue2、vue3等多端\",\r\n  \"version\": \"1.0.1\",\r\n  \"description\": \"uv-line 此组件一般用于显示一根线条，用于分隔内容块，有横向和竖向两种模式，且能设置0.5px线条，使用也很简单。\",\r\n  \"keywords\": [\r\n    \"uv-line\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"line\",\r\n    \"线条\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-line/readme.md",
    "content": "## Line 线条\r\n\r\n> **组件名：uv-line**\r\n\r\n此组件一般用于显示一根线条，用于分隔内容块，有横向和竖向两种模式，且能设置0.5px线条，使用也很简单。\r\n\r\n### <a href=\"https://www.uvui.cn/components/line.html\" target=\"_blank\">查看文档</a>\r\n\r\n### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n#### 如使用过程中有任何问题，或者您对uv-ui有一些好的建议，欢迎加入 uv-ui 交流群：<a href=\"https://ext.dcloud.net.cn/plugin?id=12287\" target=\"_blank\">uv-ui</a>、<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-line-progress/changelog.md",
    "content": "## 1.0.2（2023-06-20）\n1. 适配height参数携带单位\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-line-progress 线形进度条\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-line-progress/components/uv-line-progress/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 激活部分的颜色\r\n\t\tactiveColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#19be6b'\r\n\t\t},\r\n\t\tinactiveColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#ececec'\r\n\t\t},\r\n\t\t// 进度百分比，数值\r\n\t\tpercentage: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 是否在进度条内部显示百分比的值\r\n\t\tshowText: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 进度条的高度，单位px\r\n\t\theight: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 12\r\n\t\t},\r\n\t\t...uni.$uv?.props?.lineProgress\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-line-progress/components/uv-line-progress/uv-line-progress.vue",
    "content": "<template>\r\n\t<view\r\n\t    class=\"uv-line-progress\"\r\n\t    :style=\"[$uv.addStyle(customStyle)]\"\r\n\t>\r\n\t\t<view\r\n\t\t    class=\"uv-line-progress__background\"\r\n\t\t    ref=\"uv-line-progress__background\"\r\n\t\t    :style=\"[{\r\n\t\t\t\tbackgroundColor: inactiveColor,\r\n\t\t\t\theight: $uv.addUnit($uv.getPx(height))\r\n\t\t\t}]\"\r\n\t\t>\r\n\t\t</view>\r\n\t\t<view\r\n\t\t    class=\"uv-line-progress__line\"\r\n\t\t    :style=\"[progressStyle]\"\r\n\t\t> \r\n\t\t\t<slot>\r\n\t\t\t\t<text v-if=\"showText && percentage >= 10\" class=\"uv-line-progress__text\">{{innserPercentage + '%'}}</text>\r\n\t\t\t</slot> \r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t// #ifdef APP-NVUE\r\n\tconst dom = uni.requireNativePlugin('dom')\r\n\t// #endif\r\n\t/**\r\n\t * lineProgress 线型进度条\r\n\t * @description 展示操作或任务的当前进度，比如上传文件，是一个线形的进度条。\r\n\t * @tutorial https://www.uvui.cn/components/lineProgress.html\r\n\t * @property {String}\t\t\tactiveColor\t\t激活部分的颜色 ( 默认 '#19be6b' )\r\n\t * @property {String}\t\t\tinactiveColor\t背景色 ( 默认 '#ececec' )\r\n\t * @property {String | Number}\tpercentage\t\t进度百分比，数值 ( 默认 0 )\r\n\t * @property {Boolean}\t\t\tshowText\t\t是否在进度条内部显示百分比的值 ( 默认 true )\r\n\t * @property {String | Number}\theight\t\t\t进度条的高度，单位px ( 默认 12 )\r\n\t * \r\n\t * @example <uv-line-progress :percent=\"70\" :show-percent=\"true\"></uv-line-progress>\r\n\t */\r\n\texport default {\r\n\t\tname: \"uv-line-progress\",\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tlineWidth: 0,\r\n\t\t\t}\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\tpercentage(n) {\r\n\t\t\t\tthis.resizeProgressWidth()\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\tprogressStyle() { \r\n\t\t\t\tlet style = {}\r\n\t\t\t\tstyle.width = this.lineWidth\r\n\t\t\t\tstyle.backgroundColor = this.activeColor\r\n\t\t\t\tstyle.height = this.$uv.addUnit(this.$uv.getPx(this.height))\r\n\t\t\t\treturn style\r\n\t\t\t},\r\n\t\t\tinnserPercentage() {\r\n\t\t\t\t// 控制范围在0-100之间\r\n\t\t\t\treturn this.$uv.range(0, 100, this.percentage)\r\n\t\t\t}\r\n\t\t},\r\n\t\tmounted() {\r\n\t\t\tthis.init()\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tinit() {\r\n\t\t\t\tthis.$uv.sleep(20).then(() => {\r\n\t\t\t\t\tthis.resizeProgressWidth()\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\tgetProgressWidth() {\r\n\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\treturn this.$uvGetRect('.uv-line-progress__background')\r\n\t\t\t\t// #endif\r\n\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\t// 返回一个promise\r\n\t\t\t\treturn new Promise(resolve => {\r\n\t\t\t\t\tdom.getComponentRect(this.$refs['uv-line-progress__background'], (res) => {\r\n\t\t\t\t\t\tresolve(res.size)\r\n\t\t\t\t\t})\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\t\t\t},\r\n\t\t\tresizeProgressWidth() {\r\n\t\t\t\tthis.getProgressWidth().then(size => {\r\n\t\t\t\t\tconst {\r\n\t\t\t\t\t\twidth\r\n\t\t\t\t\t} = size\r\n\t\t\t\t\t// 通过设置的percentage值，计算其所占总长度的百分比\r\n\t\t\t\t\tthis.lineWidth = width * this.innserPercentage / 100 + 'px'\r\n\t\t\t\t})\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\r\n\t.uv-line-progress {\r\n\t\talign-items: stretch;\r\n\t\tposition: relative;\r\n\t\t@include flex(row);\r\n\t\tflex: 1;\r\n\t\toverflow: hidden;\r\n\t\tborder-radius: 100px;\r\n\r\n\t\t&__background {\r\n\t\t\tbackground-color: #ececec;\r\n\t\t\tborder-radius: 100px;\r\n\t\t\tflex: 1;\r\n\t\t}\r\n\r\n\t\t&__line {\r\n\t\t\tposition: absolute;\r\n\t\t\ttop: 0;\r\n\t\t\tleft: 0;\r\n\t\t\tbottom: 0;\r\n\t\t\talign-items: center;\r\n\t\t\t@include flex(row);\r\n\t\t\tcolor: #ffffff;\r\n\t\t\tborder-radius: 100px;\r\n\t\t\ttransition: width 0.5s ease;\r\n\t\t\tjustify-content: flex-end;\r\n\t\t}\r\n\r\n\t\t&__text {\r\n\t\t\tfont-size: 10px;\r\n\t\t\talign-items: center;\r\n\t\t\ttext-align: right;\r\n\t\t\tcolor: #FFFFFF;\r\n\t\t\tmargin-right: 5px;\r\n\t\t\ttransform: scale(0.9);\r\n\t\t}\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-line-progress/package.json",
    "content": "{\r\n  \"id\": \"uv-line-progress\",\r\n  \"displayName\": \"uv-line-progress 线形进度条  全面兼容小程序、nvue、vue2、vue3等多端\",\r\n  \"version\": \"1.0.2\",\r\n  \"description\": \"uv-line-progress 该组件展示操作或任务的当前进度，比如上传文件，是一个线形的进度条。\",\r\n  \"keywords\": [\r\n    \"uv-line-progress\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"progress\",\r\n    \"进度条\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-line-progress/readme.md",
    "content": "## LineProgress 线形进度条\r\n\r\n> **组件名：uv-line-progress**\r\n\r\n展示操作或任务的当前进度，比如上传文件，是一个线形的进度条。\r\n\r\n### <a href=\"https://www.uvui.cn/components/lineProgress.html\" target=\"_blank\">查看文档</a>\r\n\r\n### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n#### 如使用过程中有任何问题，或者您对uv-ui有一些好的建议，欢迎加入 uv-ui 交流群：<a href=\"https://ext.dcloud.net.cn/plugin?id=12287\" target=\"_blank\">uv-ui</a>、<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-link/changelog.md",
    "content": "## 1.0.2（2023-08-13）\n1. 修复报错的BUG\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-link 超链接组件\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-link/components/uv-link/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 文字颜色\r\n\t\tcolor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 字体大小，单位px\r\n\t\tfontSize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 14\r\n\t\t},\r\n\t\t// 是否显示下划线\r\n\t\tunderLine: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 要跳转的链接\r\n\t\thref: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 小程序中复制到粘贴板的提示语\r\n\t\tmpTips: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '链接已复制，请在浏览器打开'\r\n\t\t},\r\n\t\t// 下划线颜色\r\n\t\tlineColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 超链接的问题，不使用slot形式传入，是因为nvue下无法修改颜色\r\n\t\ttext: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t...uni.$uv?.props?.link\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-link/components/uv-link/uv-link.vue",
    "content": "<template>\r\n\t<text\r\n\t    class=\"uv-link\"\r\n\t    @tap.stop=\"openLink\"\r\n\t    :style=\"[linkStyle, $uv.addStyle(customStyle)]\"\r\n\t>{{text}}</text>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * link 超链接\r\n\t * @description 该组件为超链接组件，在不同平台有不同表现形式：在APP平台会通过plus环境打开内置浏览器，在小程序中把链接复制到粘贴板，同时提示信息，在H5中通过window.open打开链接。\r\n\t * @tutorial https://www.uvui.cn/components/link.html\r\n\t * @property {String}\t\t\tcolor\t\t文字颜色 （默认 color['uv-primary'] ）\r\n\t * @property {String ｜ Number}\tfontSize\t字体大小，单位px （默认 15 ）\r\n\t * @property {Boolean}\t\t\tunderLine\t是否显示下划线 （默认 false ）\r\n\t * @property {String}\t\t\thref\t\t跳转的链接，要带上http(s)\r\n\t * @property {String}\t\t\tmpTips\t\t各个小程序平台把链接复制到粘贴板后的提示语（默认“链接已复制，请在浏览器打开”）\r\n\t * @property {String}\t\t\tlineColor\t下划线颜色，默认同color参数颜色 \r\n\t * @property {String}\t\t\ttext\t\t超链接的问题，不使用slot形式传入，是因为nvue下无法修改颜色 \r\n\t * @property {Object}\t\t\tcustomStyle\t定义需要用到的外部样式\r\n\t * \r\n\t * @example <uv-link href=\"http://www.uvui.cn\">蜀道难，难于上青天</uv-link>\r\n\t */\r\n\texport default {\r\n\t\tname: \"uv-link\",\r\n\t\temits:['click'],\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tcomputed: {\r\n\t\t\tlinkStyle() {\r\n\t\t\t\tconst style = {\r\n\t\t\t\t\tcolor: this.color,\r\n\t\t\t\t\tfontSize: this.$uv.addUnit(this.fontSize),\r\n\t\t\t\t\t// line-height设置为比字体大小多2px\r\n\t\t\t\t\tlineHeight: this.$uv.addUnit(this.$uv.getPx(this.fontSize) + 2),\r\n\t\t\t\t\ttextDecoration: this.underLine ? 'underline' : 'none'\r\n\t\t\t\t}\r\n\t\t\t\treturn style\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\topenLink() {\r\n\t\t\t\t// #ifdef APP-PLUS\r\n\t\t\t\tplus.runtime.openURL(this.href)\r\n\t\t\t\t// #endif\r\n\t\t\t\t// #ifdef H5\r\n\t\t\t\twindow.open(this.href)\r\n\t\t\t\t// #endif\r\n\t\t\t\t// #ifdef MP\r\n\t\t\t\tuni.setClipboardData({\r\n\t\t\t\t\tdata: this.href,\r\n\t\t\t\t\tsuccess: () => {\r\n\t\t\t\t\t\tuni.hideToast();\r\n\t\t\t\t\t\tthis.$nextTick(() => {\r\n\t\t\t\t\t\t\tthis.$uv.toast(this.mpTips);\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t}\r\n\t\t\t\t});\r\n\t\t\t\t// #endif\r\n\t\t\t\tthis.$emit('click')\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t$uv-link-line-height:1 !default;\r\n\r\n\t.uv-link {\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\tline-height: $uv-link-line-height;\r\n\t\t/* #endif */\r\n\t\t@include flex;\r\n\t\tflex-wrap: wrap;\r\n\t\tflex: 1;\r\n\t\tcolor: $uv-primary;\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-link/package.json",
    "content": "{\r\n  \"id\": \"uv-link\",\r\n  \"displayName\": \"uv-link 超链接  全面兼容小程序、nvue、vue2、vue3等多端\",\r\n  \"version\": \"1.0.2\",\r\n  \"description\": \"uv-link 该组件为超链接组件\",\r\n  \"keywords\": [\r\n    \"uv-link\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"link\",\r\n    \"超链接\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-link/readme.md",
    "content": "## Link 超链接\r\n\r\n> **组件名：uv-link**\r\n\r\n该组件为超链接组件，在不同平台有不同表现形式。\r\n\r\n### <a href=\"https://www.uvui.cn/components/link.html\" target=\"_blank\">查看文档</a>\r\n\r\n### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n#### 如使用过程中有任何问题，或者您对uv-ui有一些好的建议，欢迎加入 uv-ui 交流群：<a href=\"https://ext.dcloud.net.cn/plugin?id=12287\" target=\"_blank\">uv-ui</a>、<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-list/changelog.md",
    "content": "## 1.0.8（2023-09-20）\n1. listItem优化可使用customStyle变量进行样式控制\n## 1.0.7（2023-08-29）\n1. 修复边框的BUG\n## 1.0.6（2023-08-16）\r\n1. 修复switch开关返回undefined的问题\r\n2. 优化初始化可能导致的闪动\r\n## 1.0.5（2023-08-07）\r\n1. 修复分包页面在ios端，nvue编译不能滚动的BUG\r\n## 1.0.4（2023-08-04）\r\n1. nvue修复  触底不触发事件的BUG\r\n2. 更新文档说明事件触发\r\n## 1.0.3（2023-07-28）\r\n1. 修改可能造成样式污染的BUG\r\n## 1.0.2（2023-07-26）\r\n1. 全面重构，用法与之前保持一致，参数全部变化\r\n2. 新增多个功能参数，方便一键构建列表\r\n3. List列表组件，包含基本列表样式、默认插槽机制、可扩展插槽机制、长列表性能优化、多端兼容。\r\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-list 列表\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-list/components/uv-list/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 控制是否出现滚动条，仅nvue有效\r\n\t\tshowScrollbar: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 距底部多少时触发scrolltolower事件\r\n\t\tlowerThreshold: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 50\r\n\t\t},\r\n\t\t// 距顶部多少时触发scrolltoupper事件，非nvue有效\r\n\t\tupperThreshold: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 设置竖向滚动条位置\r\n\t\tscrollTop: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 控制 onscroll 事件触发的频率，仅nvue有效\r\n\t\toffsetAccuracy: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 10\r\n\t\t},\r\n\t\t// 启用 flexbox 布局。开启后，当前节点声明了display: flex就会成为flex container，并作用于其孩子节点，仅微信小程序有效\r\n\t\tenableFlex: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否按分页模式显示List，默认值false\r\n\t\tpagingEnabled: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否允许List滚动\r\n\t\tscrollable: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 值应为某子元素id（id不能以数字开头）\r\n\t\tscrollIntoView: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 在设置滚动条位置时使用动画过渡\r\n\t\tscrollWithAnimation: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// iOS点击顶部状态栏、安卓双击标题栏时，滚动条返回顶部，只对微信小程序有效\r\n\t\tenableBackToTop: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 列表的高度\r\n\t\theight: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 列表宽度\r\n\t\twidth: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 列表前后预渲染的屏数，1代表一个屏幕的高度，1.5代表1个半屏幕高度\r\n\t\tpreLoadScreen: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 1\r\n\t\t},\r\n\t\t...uni.$uv?.props?.list\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-list/components/uv-list/uv-list.vue",
    "content": "<template>\r\n\t<!-- #ifndef APP-NVUE -->\r\n\t<view \r\n\t\tclass=\"uv-list\"\r\n\t\t:style=\"[$uv.addStyle(customStyle)]\"\r\n\t>\r\n\t\t<view \r\n\t\t\tv-if=\"border\" \r\n\t\t\tclass=\"uv-list--border-top\"\r\n\t\t\t:style=\"[{ 'background-color': borderColor }]\"\r\n\t\t></view>\r\n\t\t<slot />\r\n\t\t<view \r\n\t\t\tv-if=\"border\" \r\n\t\t\tclass=\"uv-list--border-bottom\"\r\n\t\t\t:style=\"[{ 'background-color': borderColor }]\"\r\n\t\t></view>\r\n\t</view>\r\n\t<!-- #endif -->\r\n\t<!-- #ifdef APP-NVUE -->\r\n\t<list \r\n\t\t:bounce=\"true\" \r\n\t\t:scrollable=\"true\" \r\n\t\tshow-scrollbar \r\n\t\t:render-reverse=\"false\" \r\n\t\tclass=\"uv-list\" \r\n\t\t:class=\"{ 'uv-list--border': border }\"\r\n\t\t:style=\"[\r\n\t\t\t{ 'border-top-color': borderColor, 'border-bottom-color':borderColor  },\r\n\t\t\t$uv.addStyle(customStyle)\r\n\t\t]\"\r\n\t\t:enableBackToTop=\"false\"\r\n\t\t:loadmoreoffset=\"15\"\r\n\t\t@scroll=\"scroll\"\r\n\t\t@loadmore=\"loadMore\">\r\n\t\t<slot />\r\n\t</list>\r\n\t<!-- #endif -->\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\t/**\r\n\t * List 列表\r\n\t * @description 列表组件\r\n\t * @tutorial https://www.uvui.cn/components/list.html\r\n\t * @property {Boolean} border = [true|false] 是否显示边框\r\n\t * @property {String} borderColor 边框颜色\r\n\t * @property {String} direction 排版方向，默认row，列表里面使用其他组件  最好设置成column\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-list',\r\n\t\tmixins: [mpMixin, mixin],\r\n\t\t'mp-weixin': {\r\n\t\t\toptions: {\r\n\t\t\t\tmultipleSlots: false\r\n\t\t\t}\r\n\t\t},\r\n\t\tprops: {\r\n\t\t\tborder: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: false\r\n\t\t\t},\r\n\t\t\tborderColor: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: '#dadbde'\r\n\t\t\t},\r\n\t\t\t// 排版方向，默认row，列表里面使用其他组件  最好设置成column\r\n\t\t\tdirection: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: 'row'\r\n\t\t\t},\r\n\t\t\t// 内边距\r\n\t\t\tpadding: {\r\n\t\t\t\ttype: [String,Number],\r\n\t\t\t\tdefault: '20rpx 30rpx'\r\n\t\t\t}\r\n\t\t},\r\n\t\tcreated() {\r\n\t\t\tthis.firstChildAppend = false;\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\tparentData() {\r\n\t\t\t\treturn [this.direction,this.padding];\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tloadMore(e) {\r\n\t\t\t\tthis.$emit('scrolltolower');\r\n\t\t\t},\r\n\t\t\tscroll(e) {\r\n\t\t\t\tthis.$emit('scroll', e);\r\n\t\t\t}\r\n\t\t}\r\n\t};\r\n</script>\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t\r\n\t.uv-list {\r\n\t\tposition: relative;\r\n\t\t@include flex(column);\r\n\t\tbackground-color: #fff;\r\n\t}\r\n\r\n\t.uv-list--border {\r\n\t\tposition: relative;\r\n\t\t/* #ifdef APP-NVUE */\r\n\t\tborder-top-color: $uv-border-color;\r\n\t\tborder-top-style: solid;\r\n\t\tborder-top-width: 0.5px;\r\n\t\tborder-bottom-color: $uv-border-color;\r\n\t\tborder-bottom-style: solid;\r\n\t\tborder-bottom-width: 0.5px;\r\n\t\t/* #endif */\r\n\t\tz-index: -1;\r\n\t}\r\n\r\n\t/* #ifndef APP-NVUE */\r\n\r\n\t.uv-list--border-top {\r\n\t\tposition: absolute;\r\n\t\ttop: 0;\r\n\t\tright: 0;\r\n\t\tleft: 0;\r\n\t\theight: 1px;\r\n\t\t-webkit-transform: scaleY(0.5);\r\n\t\ttransform: scaleY(0.5);\r\n\t\tbackground-color: $uv-border-color;\r\n\t\tz-index: 1;\r\n\t}\r\n\r\n\t.uv-list--border-bottom {\r\n\t\tposition: absolute;\r\n\t\tbottom: 0;\r\n\t\tright: 0;\r\n\t\tleft: 0;\r\n\t\theight: 1px;\r\n\t\t-webkit-transform: scaleY(0.5);\r\n\t\ttransform: scaleY(0.5);\r\n\t\tbackground-color: $uv-border-color;\r\n\t}\r\n\r\n\t/* #endif */\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-list/components/uv-list-item/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 用于滚动到指定item\r\n\t\tanchor: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t}\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-list/components/uv-list-item/uv-list-item.vue",
    "content": "<template>\r\n\t<!-- #ifdef APP-NVUE -->\r\n\t<cell :keep-scroll-position=\"keepScrollPosition\">\r\n\t\t<!-- #endif -->\r\n\t\t<view \r\n\t\t\t:class=\"{ 'uv-list-item--disabled': disabled }\" \r\n\t\t\t:style=\"[$uv.addStyle(customStyle),{'background-color':customStyle.backgroundColor?customStyle.backgroundColor:'#fff'}]\"\r\n\t\t\t:hover-class=\"(!clickable && !link) || disabled || showSwitch ? '' : 'uv-list-item--hover'\"\r\n\t\t\tclass=\"uv-list-item\" @click=\"onClick\">\r\n\t\t\t<view v-if=\"!isFirstChild\" class=\"border--left\" :class=\"{ 'uv-list--border': border }\"></view>\r\n\t\t\t<view class=\"uv-list-item__wrapper\">\r\n\t\t\t\t<slot>\r\n\t\t\t\t\t<view class=\"uv-list-item__container\"\r\n\t\t\t\t\t\t:class=\"{ 'container--right': showArrow || link, 'flex--direction': directionData === 'column'}\"\r\n\t\t\t\t\t\t:style=\"{paddingTop:padding.top,paddingLeft:padding.left,paddingRight:padding.right,paddingBottom:padding.bottom}\">\r\n\t\t\t\t\t\t<slot name=\"header\">\r\n\t\t\t\t\t\t\t<view class=\"uv-list-item__header\">\r\n\t\t\t\t\t\t\t\t<view v-if=\"thumb\" class=\"uv-list-item__icon\">\r\n\t\t\t\t\t\t\t\t\t<image :src=\"thumb\" class=\"uv-list-item__icon-img\" :class=\"['uv-list--' + thumbSize]\" />\r\n\t\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\t\t<view v-else-if=\"showExtraIcon\" class=\"uv-list-item__icon\">\r\n\t\t\t\t\t\t\t\t\t<uv-icon :name=\"extraIcon.icon\" :customPrefix=\"extraIcon.customPrefix\" :color=\"extraIcon.color\" :size=\"extraIcon.size\" />\r\n\t\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t</slot>\r\n\t\t\t\t\t\t<slot name=\"body\">\r\n\t\t\t\t\t\t\t<view class=\"uv-list-item__content\"\r\n\t\t\t\t\t\t\t\t:class=\"{ 'uv-list-item__content--center': thumb || showExtraIcon || showBadge || showSwitch }\">\r\n\t\t\t\t\t\t\t\t<text v-if=\"title\" class=\"uv-list-item__content-title\"\r\n\t\t\t\t\t\t\t\t\t:class=\"[ellipsis !== 0 && ellipsis <= 2 ? 'uv-ellipsis-' + ellipsis : '']\">{{ title }}</text>\r\n\t\t\t\t\t\t\t\t<text v-if=\"note\" class=\"uv-list-item__content-note\">{{ note }}</text>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t</slot>\r\n\t\t\t\t\t\t<slot name=\"footer\">\r\n\t\t\t\t\t\t\t<view v-if=\"rightText || showBadge || showSwitch\" class=\"uv-list-item__extra\"\r\n\t\t\t\t\t\t\t\t:class=\"{ 'flex--justify': directionData === 'column' }\">\r\n\t\t\t\t\t\t\t\t<text v-if=\"rightText\" class=\"uv-list-item__extra-text\">{{ rightText }}</text>\r\n\t\t\t\t\t\t\t\t<uv-badge\r\n\t\t\t\t\t\t\t\t\tv-if=\"showBadge\"\r\n\t\t\t\t\t\t\t\t\t:show=\"!!(badge.show || badge.isDot || badge.value)\"\r\n\t\t\t\t\t\t\t\t\t:isDot=\"badge.isDot\"\r\n\t\t\t\t\t\t\t\t\t:value=\"badge.value\"\r\n\t\t\t\t\t\t\t\t\t:max=\"badge.max\"\r\n\t\t\t\t\t\t\t\t\t:type=\"badge.type\"\r\n\t\t\t\t\t\t\t\t\t:showZero=\"badge.showZero\"\r\n\t\t\t\t\t\t\t\t\t:bgColor=\"badge.bgColor\"\r\n\t\t\t\t\t\t\t\t\t:color=\"badge.color\"\r\n\t\t\t\t\t\t\t\t\t:shape=\"badge.shape\"\r\n\t\t\t\t\t\t\t\t\t:numberType=\"badge.numberType\"\r\n\t\t\t\t\t\t\t\t\t:inverted=\"badge.inverted\"\r\n\t\t\t\t\t\t\t\t\tcustomStyle=\"margin-left: 4px;\"\r\n\t\t\t\t\t\t\t\t></uv-badge>\r\n\t\t\t\t\t\t\t\t<uv-switch v-if=\"showSwitch\" :value=\"switchChecked\" :disabled=\"disabled\" @change=\"onSwitchChange\"></uv-switch>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t</slot>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</slot>\r\n\t\t\t</view>\r\n\t\t\t<uv-icon v-if=\"showArrow || link\" size=\"34rpx\" class=\"uv-icon-wrapper\" color=\"#bbb\" name=\"arrow-right\" />\r\n\t\t</view>\r\n\t\t<!-- #ifdef APP-NVUE -->\r\n\t</cell>\r\n\t<!-- #endif -->\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\t/**\r\n\t * ListItem 列表子组件\r\n\t * @description 列表子组件\r\n\t * @tutorial https://ext.dcloud.net.cn/plugin?id=24\r\n\t * @property {String} \ttitle \t\t\t\t\t\t\t标题\r\n\t * @property {String} \tnote \t\t\t\t\t\t\t描述\r\n\t * @property {String} \tthumb \t\t\t\t\t\t\t左侧缩略图，若thumb有值，则不会显示扩展图标\r\n\t * @property {String}  \tthumbSize = [lg|base|sm]\t\t略缩图大小\r\n\t * \t@value \t lg\t\t\t大图\r\n\t * \t@value \t base\t\t一般\r\n\t * \t@value \t sm\t\t\t小图\r\n\t * @property {String} \trightText \t\t\t\t\t\t右侧文字内容\r\n\t * @property {Boolean} \tdisabled = [true|false]\t\t\t是否禁用\r\n\t * @property {Boolean} \tclickable = [true|false] \t\t是否开启点击反馈\r\n\t * @property {String} \tlink = [navigateTo|redirectTo|reLaunch|switchTab] 是否展示右侧箭头并开启点击反馈\r\n\t *  @value \tnavigateTo \t同 uni.navigateTo()\r\n\t * \t@value redirectTo \t同 uni.redirectTo()\r\n\t * \t@value reLaunch   \t同 uni.reLaunch()\r\n\t * \t@value switchTab  \t同 uni.switchTab()\r\n\t * @property {String | PageURIString} \tto  \t\t\t跳转目标页面\r\n\t * @property {Boolean} \tshowBadge = [true|false] \t\t是否显示数字角标\r\n\t * @property {Object} \tbadge  扩展数字角标的参数，格式为 :badge=\"{value: 122}\"\r\n\t * @property {Boolean} \tshowSwitch = [true|false] \t\t是否显示Switch\r\n\t * @property {Boolean} \tswitchChecked = [true|false] \tSwitch是否被选中\r\n\t * @property {Boolean} \tshowExtraIcon = [true|false] \t左侧是否显示扩展图标\r\n\t * @property {Object} \textraIcon \t\t\t\t\t\t扩展图标参数，格式为 :extraIcon=\"{icon: 'photo',size: '30px'}\"\r\n\t * @property {String} \tdirection = [row|column]\t\t排版方向\r\n\t * @value row \t\t\t水平排列\r\n\t * @value column \t\t垂直排列\r\n\t * @event {Function} \tclick \t\t\t\t\t\t\t点击 uniListItem 触发事件\r\n\t * @event {Function} \tswitchChange \t\t\t\t\t点击切换 Switch 时触发\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-list-item',\r\n\t\tmixins: [mpMixin, mixin],\r\n\t\temits: ['click', 'switchChange'],\r\n\t\tprops: {\r\n\t\t\tdirection: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: 'row'\r\n\t\t\t},\r\n\t\t\ttitle: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: ''\r\n\t\t\t},\r\n\t\t\tnote: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: ''\r\n\t\t\t},\r\n\t\t\tellipsis: {\r\n\t\t\t\ttype: [Number, String],\r\n\t\t\t\tdefault: 0\r\n\t\t\t},\r\n\t\t\tdisabled: {\r\n\t\t\t\ttype: [Boolean, String],\r\n\t\t\t\tdefault: false\r\n\t\t\t},\r\n\t\t\tclickable: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: false\r\n\t\t\t},\r\n\t\t\tshowArrow: {\r\n\t\t\t\ttype: [Boolean, String],\r\n\t\t\t\tdefault: false\r\n\t\t\t},\r\n\t\t\tlink: {\r\n\t\t\t\ttype: [Boolean, String],\r\n\t\t\t\tdefault: false\r\n\t\t\t},\r\n\t\t\tto: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: ''\r\n\t\t\t},\r\n\t\t\tshowSwitch: {\r\n\t\t\t\ttype: [Boolean, String],\r\n\t\t\t\tdefault: false\r\n\t\t\t},\r\n\t\t\tswitchChecked: {\r\n\t\t\t\ttype: [Boolean, String],\r\n\t\t\t\tdefault: false\r\n\t\t\t},\r\n\t\t\tshowBadge: {\r\n\t\t\t\ttype: [Boolean, String],\r\n\t\t\t\tdefault: false\r\n\t\t\t},\r\n\t\t\tbadge: {\r\n\t\t\t\ttype: Object,\r\n\t\t\t\tdefault () {\r\n\t\t\t\t\treturn {}\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\trightText: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: ''\r\n\t\t\t},\r\n\t\t\tthumb: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: ''\r\n\t\t\t},\r\n\t\t\tthumbSize: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: 'base'\r\n\t\t\t},\r\n\t\t\tshowExtraIcon: {\r\n\t\t\t\ttype: [Boolean, String],\r\n\t\t\t\tdefault: false\r\n\t\t\t},\r\n\t\t\textraIcon: {\r\n\t\t\t\ttype: Object,\r\n\t\t\t\tdefault () {\r\n\t\t\t\t\treturn {\r\n\t\t\t\t\t\tname: '',\r\n\t\t\t\t\t\tcolor: '#000000',\r\n\t\t\t\t\t\tsize: 20,\r\n\t\t\t\t\t\tcustomPrefix: ''\r\n\t\t\t\t\t};\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tborder: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: false\r\n\t\t\t},\r\n\t\t\tcustomStyle: {\r\n\t\t\t\ttype: Object,\r\n\t\t\t\tdefault () {\r\n\t\t\t\t\treturn {\r\n\t\t\t\t\t\tpadding: '',\r\n\t\t\t\t\t\tbackgroundColor: '#FFFFFF'\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tkeepScrollPosition: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: false\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\tdirectionData(){\r\n\t\t\t\treturn this.direction ? this.direction : (this.parentData.direction ? this.parentData.direction : 'row');\r\n\t\t\t}\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\t'customStyle.padding': {\r\n\t\t\t\thandler(padding) {\r\n\t\t\t\t\tif(padding) this.setPadding(padding);\r\n\t\t\t\t},\r\n\t\t\t\timmediate: true\r\n\t\t\t}\r\n\t\t},\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tisFirstChild: false,\r\n\t\t\t\tpadding: {\r\n\t\t\t\t\ttop: \"\",\r\n\t\t\t\t\tright: \"\",\r\n\t\t\t\t\tbottom: \"\",\r\n\t\t\t\t\tleft: \"\"\r\n\t\t\t\t},\r\n\t\t\t\tparentData: {\r\n\t\t\t\t\tdirection: 'row',\r\n\t\t\t\t\tpadding: 0\r\n\t\t\t\t}\r\n\t\t\t};\r\n\t\t},\r\n\t\tcreated() {\r\n\t\t\tthis.updateParentData();\r\n\t\t},\r\n\t\tmounted() {\r\n\t\t\tthis.init();\r\n\t\t\tthis.list = this.getForm()\r\n\t\t\t// 判断是否存在 uv-list 组件\r\n\t\t\tif (this.list) {\r\n\t\t\t\tif (!this.list.firstChildAppend) {\r\n\t\t\t\t\tthis.list.firstChildAppend = true;\r\n\t\t\t\t\tthis.isFirstChild = true;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tinit(){\r\n\t\t\t\tif (!this.parent) {\r\n\t\t\t\t\tthis.$uv.error('uv-list-item必须搭配uv-list组件使用');\r\n\t\t\t\t}\r\n\t\t\t\tthis.$nextTick(()=>{\r\n\t\t\t\t\tif(!(this.padding.top || this.padding.right|| this.padding.bottom|| this.padding.left)){\r\n\t\t\t\t\t\tthis.setPadding(this.parentData.padding);\r\n\t\t\t\t\t}\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\tupdateParentData() {\r\n\t\t\t\tthis.getParentData('uv-list');\r\n\t\t\t},\r\n\t\t\tsetPadding(padding){\r\n\t\t\t\tif(typeof padding == 'number'){\r\n\t\t\t\t\tpadding += ''\r\n\t\t\t\t}\r\n\t\t\t\tlet paddingArr = padding.split(' ')\r\n\t\t\t\tif (paddingArr.length === 1) {\r\n\t\t\t\t\tconst allPadding = paddingArr[0]\r\n\t\t\t\t\tthis.padding = {\r\n\t\t\t\t\t\t\"top\": allPadding,\r\n\t\t\t\t\t\t\"right\": allPadding,\r\n\t\t\t\t\t\t\"bottom\": allPadding,\r\n\t\t\t\t\t\t\"left\": allPadding\r\n\t\t\t\t\t}\r\n\t\t\t\t} else if (paddingArr.length === 2) {\r\n\t\t\t\t\tconst [verticalPadding, horizontalPadding] = paddingArr;\r\n\t\t\t\t\tthis.padding = {\r\n\t\t\t\t\t\t\"top\": verticalPadding,\r\n\t\t\t\t\t\t\"right\": horizontalPadding,\r\n\t\t\t\t\t\t\"bottom\": verticalPadding,\r\n\t\t\t\t\t\t\"left\": horizontalPadding\r\n\t\t\t\t\t}\r\n\t\t\t\t} else if (paddingArr.length === 4) {\r\n\t\t\t\t\t\tconst [topPadding, rightPadding, bottomPadding, leftPadding] = paddingArr;\r\n\t\t\t\t\t\tthis.padding = {\r\n\t\t\t\t\t\t\t\"top\": topPadding,\r\n\t\t\t\t\t\t\t\"right\": rightPadding,\r\n\t\t\t\t\t\t\t\"bottom\": bottomPadding,\r\n\t\t\t\t\t\t\t\"left\": leftPadding\r\n\t\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t/**\r\n\t\t\t * 获取父元素实例\r\n\t\t\t */\r\n\t\t\tgetForm(name = 'uniList') {\r\n\t\t\t\tlet parent = this.$parent;\r\n\t\t\t\tlet parentName = parent.$options.name;\r\n\t\t\t\twhile (parentName !== name) {\r\n\t\t\t\t\tparent = parent.$parent;\r\n\t\t\t\t\tif (!parent) return false\r\n\t\t\t\t\tparentName = parent.$options.name;\r\n\t\t\t\t}\r\n\t\t\t\treturn parent;\r\n\t\t\t},\r\n\t\t\tonClick() {\r\n\t\t\t\tif (this.to !== '') {\r\n\t\t\t\t\tthis.openPage();\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\t\t\t\tif (this.clickable || this.link) {\r\n\t\t\t\t\tthis.$emit('click', {\r\n\t\t\t\t\t\tdata: {}\r\n\t\t\t\t\t});\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tonSwitchChange(e) {\r\n\t\t\t\tthis.$emit('switchChange', e);\r\n\t\t\t},\r\n\t\t\topenPage() {\r\n\t\t\t\tif (['navigateTo', 'redirectTo', 'reLaunch', 'switchTab'].indexOf(this.link) !== -1) {\r\n\t\t\t\t\tthis.pageApi(this.link);\r\n\t\t\t\t} else {\r\n\t\t\t\t\tthis.pageApi('navigateTo');\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tpageApi(api) {\r\n\t\t\t\tlet callback = {\r\n\t\t\t\t\turl: this.to,\r\n\t\t\t\t\tsuccess: res => {\r\n\t\t\t\t\t\tthis.$emit('click', {\r\n\t\t\t\t\t\t\tdata: res\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t},\r\n\t\t\t\t\tfail: err => {\r\n\t\t\t\t\t\tthis.$emit('click', {\r\n\t\t\t\t\t\t\tdata: err\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tswitch (api) {\r\n\t\t\t\t\tcase 'navigateTo':\r\n\t\t\t\t\t\tuni.navigateTo(callback)\r\n\t\t\t\t\t\tbreak\r\n\t\t\t\t\tcase 'redirectTo':\r\n\t\t\t\t\t\tuni.redirectTo(callback)\r\n\t\t\t\t\t\tbreak\r\n\t\t\t\t\tcase 'reLaunch':\r\n\t\t\t\t\t\tuni.reLaunch(callback)\r\n\t\t\t\t\t\tbreak\r\n\t\t\t\t\tcase 'switchTab':\r\n\t\t\t\t\t\tuni.switchTab(callback)\r\n\t\t\t\t\t\tbreak\r\n\t\t\t\t\tdefault:\r\n\t\t\t\t\t\tuni.navigateTo(callback)\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t};\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t$uv-font-size-sm:12px;\r\n\t$uv-font-size-base:14px;\r\n\t$uv-font-size-lg:16px;\r\n\t$uv-spacing-col-lg: 12px;\r\n\t$uv-spacing-row-lg: 15px;\r\n\t$uv-img-size-sm:20px;\r\n\t$uv-img-size-base:26px;\r\n\t$uv-img-size-lg:40px;\r\n\t$uv-border-color:#e5e5e5;\r\n\t$uv-bg-color-hover:#f1f1f1;\r\n\t$uv-text-color-grey:#999;\r\n\t$list-item-pd: $uv-spacing-col-lg $uv-spacing-row-lg;\r\n\r\n\t.uv-list-item {\r\n\t\t@include flex(row);\r\n\t\tfont-size: $uv-font-size-lg;\r\n\t\tposition: relative;\r\n\t\tjustify-content: space-between;\r\n\t\talign-items: center;\r\n\t\tbackground-color: #fff;\r\n\t\t/* #ifdef H5 */\r\n\t\tcursor: pointer;\r\n\t\t/* #endif */\r\n\t}\r\n\r\n\t.uv-list-item--disabled {\r\n\t\topacity: 0.3;\r\n\t}\r\n\r\n\t.uv-list-item--hover {\r\n\t\tbackground-color: $uv-bg-color-hover !important;\r\n\t}\r\n\t\r\n\t.uv-list-item__wrapper {\r\n\t\t@include flex(column);\r\n\t\tflex: 1;\r\n\t}\r\n\r\n\t.uv-list-item__container {\r\n\t\tposition: relative;\r\n\t\t@include flex(row);\r\n\t\tpadding: $list-item-pd;\r\n\t\tpadding-left: $uv-spacing-row-lg;\r\n\t\tflex: 1;\r\n\t\toverflow: hidden;\r\n\t}\r\n\r\n\t.container--right {\r\n\t\tpadding-right: 0;\r\n\t}\r\n\r\n\t.uv-list--border {\r\n\t\tposition: absolute;\r\n\t\ttop: 0;\r\n\t\tright: 0;\r\n\t\tleft: 0;\r\n\t\t/* #ifdef APP-NVUE */\r\n\t\tborder-top-color: $uv-border-color;\r\n\t\tborder-top-style: solid;\r\n\t\tborder-top-width: 0.5px;\r\n\t\t/* #endif */\r\n\t}\r\n\r\n\t/* #ifndef APP-NVUE */\r\n\t.uv-list--border:after {\r\n\t\tposition: absolute;\r\n\t\ttop: 0;\r\n\t\tright: 0;\r\n\t\tleft: 0;\r\n\t\theight: 1px;\r\n\t\tcontent: '';\r\n\t\t-webkit-transform: scaleY(0.5);\r\n\t\ttransform: scaleY(0.5);\r\n\t\tbackground-color: $uv-border-color;\r\n\t}\r\n\r\n\t/* #endif */\r\n\t.uv-list-item__content {\r\n\t\t@include flex(column);\r\n\t\tpadding-right: 8px;\r\n\t\tflex: 1;\r\n\t\tcolor: #3b4144;\r\n\t\tjustify-content: space-between;\r\n\t\toverflow: hidden;\r\n\t}\r\n\r\n\t.uv-list-item__content--center {\r\n\t\tjustify-content: center;\r\n\t}\r\n\r\n\t.uv-list-item__content-title {\r\n\t\tfont-size: $uv-font-size-base;\r\n\t\tcolor: #3b4144;\r\n\t\toverflow: hidden;\r\n\t}\r\n\r\n\t.uv-list-item__content-note {\r\n\t\tmargin-top: 6rpx;\r\n\t\tcolor: $uv-text-color-grey;\r\n\t\tfont-size: $uv-font-size-sm;\r\n\t\toverflow: hidden;\r\n\t}\r\n\r\n\t.uv-list-item__extra {\r\n\t\t@include flex(row);\r\n\t\tjustify-content: flex-end;\r\n\t\talign-items: center;\r\n\t}\r\n\r\n\t.uv-list-item__header {\r\n\t\t@include flex(row);\r\n\t\talign-items: center;\r\n\t}\r\n\r\n\t.uv-list-item__icon {\r\n\t\tmargin-right: 18rpx;\r\n\t\tflex-direction: row;\r\n\t\tjustify-content: center;\r\n\t\talign-items: center;\r\n\t}\r\n\r\n\t.uv-list-item__icon-img {\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\tdisplay: block;\r\n\t\t/* #endif */\r\n\t\theight: $uv-img-size-base;\r\n\t\twidth: $uv-img-size-base;\r\n\t\tmargin-right: 10px;\r\n\t}\r\n\r\n\t.uv-icon-wrapper {\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\tdisplay: flex;\r\n\t\t/* #endif */\r\n\t\talign-items: center;\r\n\t\tpadding: 0 10px;\r\n\t}\r\n\r\n\t.flex--direction {\r\n\t\tflex-direction: column;\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\talign-items: initial;\r\n\t\t/* #endif */\r\n\t}\r\n\r\n\t.flex--justify {\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\tjustify-content: initial;\r\n\t\t/* #endif */\r\n\t}\r\n\r\n\t.uv-list--lg {\r\n\t\theight: $uv-img-size-lg;\r\n\t\twidth: $uv-img-size-lg;\r\n\t}\r\n\r\n\t.uv-list--base {\r\n\t\theight: $uv-img-size-base;\r\n\t\twidth: $uv-img-size-base;\r\n\t}\r\n\r\n\t.uv-list--sm {\r\n\t\theight: $uv-img-size-sm;\r\n\t\twidth: $uv-img-size-sm;\r\n\t}\r\n\r\n\t.uv-list-item__extra-text {\r\n\t\tcolor: $uv-text-color-grey;\r\n\t\tfont-size: $uv-font-size-sm;\r\n\t}\r\n\r\n\t.uv-ellipsis-1 {\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\toverflow: hidden;\r\n\t\twhite-space: nowrap;\r\n\t\ttext-overflow: ellipsis;\r\n\t\t/* #endif */\r\n\t\t/* #ifdef APP-NVUE */\r\n\t\tlines: 1;\r\n\t\ttext-overflow: ellipsis;\r\n\t\t/* #endif */\r\n\t}\r\n\r\n\t.uv-ellipsis-2 {\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\toverflow: hidden;\r\n\t\ttext-overflow: ellipsis;\r\n\t\tdisplay: -webkit-box;\r\n\t\t-webkit-line-clamp: 2;\r\n\t\t-webkit-box-orient: vertical;\r\n\t\t/* #endif */\r\n\t\t/* #ifdef APP-NVUE */\r\n\t\tlines: 2;\r\n\t\ttext-overflow: ellipsis;\r\n\t\t/* #endif */\r\n\t}\r\n</style>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-list/package.json",
    "content": "{\r\n  \"id\": \"uv-list\",\r\n  \"displayName\": \"uv-list 列表 全面兼容vue3+2、app、h5、小程序等多端\",\r\n  \"version\": \"1.0.8\",\r\n  \"description\": \"uv-list 多功能高性能列表组件\",\r\n  \"keywords\": [\r\n    \"uv-list\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"list\",\r\n    \"列表\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-list/readme.md",
    "content": "## List 列表\r\n\r\n> **组件名：uv-list**\r\n\r\nList列表组件，包含基本列表样式、默认插槽机制、可扩展插槽机制、长列表性能优化、多端兼容。\r\n\r\n在vue页面里，它默认使用页面级滚动，这样做的目的是性能更加友好。在app-nvue页面里，它默认使用原生list组件滚动，这样的长列表，在滚动出屏幕外后，系统会回收不可见区域的渲染内存资源，不会造成滚动越长手机越卡的问题。\r\n\r\nuv-list组件是父容器，里面的核心是uv-list-item子组件，它代表列表中的一个可重复行，子组件可以无限循环。\r\n\r\nuv-list-item有很多风格，uv-list-item组件通过内置的属性，满足一些常用的场景。当内置属性不满足需求时，可以通过扩展插槽来自定义列表内容，插槽包括：默认插槽（完全自定义内容）、具名插槽（header | body | footer），根据需求进行扩展。\r\n\r\n内置属性可以覆盖的场景包括：导航列表、设置列表、小图标列表等，其他不能满足的场景使用插槽进行扩展。\r\n\r\n# <a href=\"https://www.uvui.cn/components/list.html\" target=\"_blank\">查看文档</a>\r\n\r\n## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>（请不要 下载插件ZIP）</small>\r\n\r\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n<a href=\"https://ext.dcloud.net.cn/plugin?name=uv-ui\" target=\"_blank\">\r\n\r\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\r\n\r\n</a>\r\n\r\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-load-more/changelog.md",
    "content": "## 1.0.2（2023-06-21）\n1. 优化customStyle属性\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-load-more 加载更多\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-load-more/components/uv-load-more/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 组件状态，loadmore-加载前的状态，loading-加载中的状态，nomore-没有更多的状态\r\n\t\tstatus: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'loadmore'\r\n\t\t},\r\n\t\t// 组件背景色\r\n\t\tbgColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'transparent'\r\n\t\t},\r\n\t\t// 是否显示加载中的图标\r\n\t\ticon: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 字体大小\r\n\t\tfontSize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 14\r\n\t\t},\r\n\t\t// 图标大小\r\n\t\ticonSize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 16\r\n\t\t},\r\n\t\t// 字体颜色\r\n\t\tcolor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#606266'\r\n\t\t},\r\n\t\t// 加载中状态的图标，spinner-花朵状图标，circle-圆圈状，semicircle-半圆\r\n\t\tloadingIcon: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'spinner'\r\n\t\t},\r\n\t\t// 加载前的提示语\r\n\t\tloadmoreText: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '加载更多'\r\n\t\t},\r\n\t\t// 加载中提示语\r\n\t\tloadingText: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '正在加载...'\r\n\t\t},\r\n\t\t// 没有更多的提示语\r\n\t\tnomoreText: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '没有更多了'\r\n\t\t},\r\n\t\t// 在“没有更多”状态下，是否显示粗点\r\n\t\tisDot: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 加载中图标的颜色\r\n\t\ticonColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#b7b7b7'\r\n\t\t},\r\n\t\t// 上边距\r\n\t\tmarginTop: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 10\r\n\t\t},\r\n\t\t// 下边距\r\n\t\tmarginBottom: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 10\r\n\t\t},\r\n\t\t// 高度，单位px\r\n\t\theight: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 'auto'\r\n\t\t},\r\n\t\t// 是否显示左边分割线\r\n\t\tline: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 线条颜色\r\n\t\tlineColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#E6E8EB'\r\n\t\t},\r\n\t\t// 是否虚线，true-虚线，false-实线\r\n\t\tdashed: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t...uni.$uv?.props?.loadmore\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-load-more/components/uv-load-more/uv-load-more.vue",
    "content": "<template>\r\n\t<view\r\n\t    class=\"uv-loadmore\"\r\n\t    :style=\"[{\r\n\t\t\t\tbackgroundColor: bgColor,\r\n\t\t\t\tmarginBottom: $uv.addUnit(marginBottom),\r\n\t\t\t\tmarginTop: $uv.addUnit(marginTop),\r\n\t\t\t\theight: $uv.addUnit(height),\r\n\t\t\t},\r\n\t\t\t$uv.addStyle(customStyle)]\"\r\n\t>\r\n\t\t<uv-line\r\n\t\t    length=\"140rpx\"\r\n\t\t    :color=\"lineColor\"\r\n\t\t    :hairline=\"false\"\r\n\t\t\t:dashed=\"dashed\"\r\n\t\t\tv-if=\"line\"\r\n\t\t></uv-line>\r\n\t\t<!-- 加载中和没有更多的状态才显示两边的横线 -->\r\n\t\t<view\r\n\t\t    :class=\"status == 'loadmore' || status == 'nomore' ? 'uv-more' : ''\"\r\n\t\t    class=\"uv-loadmore__content\"\r\n\t\t>\r\n\t\t\t<view\r\n\t\t\t    class=\"uv-loadmore__content__icon-wrap\"\r\n\t\t\t    v-if=\"status === 'loading' && icon\"\r\n\t\t\t>\r\n\t\t\t\t<uv-loading-icon\r\n\t\t\t\t    :color=\"iconColor\"\r\n\t\t\t\t    :size=\"iconSize\"\r\n\t\t\t\t    :mode=\"loadingIcon\"\r\n\t\t\t\t></uv-loading-icon>\r\n\t\t\t</view>\r\n\t\t\t<!-- 如果没有更多的状态下，显示内容为dot（粗点），加载特定样式 -->\r\n\t\t\t<text\r\n\t\t\t    class=\"uv-line-1\"\r\n\t\t\t    :style=\"[loadTextStyle]\"\r\n\t\t\t    :class=\"[(status == 'nomore' && isDot == true) ? 'uv-loadmore__content__dot-text' : 'uv-loadmore__content__text']\"\r\n\t\t\t    @tap=\"loadMore\"\r\n\t\t\t>{{ showText }}</text>\r\n\t\t</view>\r\n\t\t<uv-line\r\n\t\t    length=\"140rpx\"\r\n\t\t    :color=\"lineColor\"\r\n\t\t\t:hairline=\"false\"\r\n\t\t\t:dashed=\"dashed\"\r\n\t\t\tv-if=\"line\"\r\n\t\t></uv-line>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\r\n\t/**\r\n\t * loadmore 加载更多\r\n\t * @description 此组件一般用于标识页面底部加载数据时的状态。\r\n\t * @tutorial https://www.uvui.cn/components/loadMore.html\r\n\t * @property {String}\t\t\tstatus\t\t\t组件状态（默认 'loadmore' ）\r\n\t * @property {String}\t\t\tbgColor\t\t\t组件背景颜色，在页面是非白色时会用到（默认 'transparent' ）\r\n\t * @property {Boolean}\t\t\ticon\t\t\t加载中时是否显示图标（默认 true ）\r\n\t * @property {String | Number}\tfontSize\t\t字体大小（默认 14 ）\r\n\t * @property {String | Number}\ticonSize\t\t图标大小（默认 17 ）\r\n\t * @property {String}\t\t\tcolor\t\t\t字体颜色（默认 '#606266' ）\r\n\t * @property {String}\t\t\tloadingIcon\t\t加载图标（默认 'circle' ）\r\n\t * @property {String}\t\t\tloadmoreText\t加载前的提示语（默认 '加载更多' ）\r\n\t * @property {String}\t\t\tloadingText\t\t加载中提示语（默认 '正在加载...' ）\r\n\t * @property {String}\t\t\tnomoreText\t\t没有更多的提示语（默认 '没有更多了' ）\r\n\t * @property {Boolean}\t\t\tisDot\t\t\t到上一个相邻元素的距离 （默认 false ）\r\n\t * @property {String}\t\t\ticonColor\t\t加载中图标的颜色 （默认 '#b7b7b7' ）\r\n\t * @property {String}\t\t\tlineColor\t\t线条颜色（默认 #E6E8EB ）\r\n\t * @property {String | Number}\tmarginTop\t\t上边距 （默认 10 ）\r\n\t * @property {String | Number}\tmarginBottom\t下边距 （默认 10 ）\r\n\t * @property {String | Number}\theight\t\t\t高度，单位px （默认 'auto' ）\r\n\t * @property {Boolean}\t\t\tline\t\t\t是否显示左边分割线  （默认 false ）\r\n\t * @property {Boolean}\t\t\tdashed\t\t// 是否虚线，true-虚线，false-实线  （默认 false ）\r\n\t * @event {Function} loadmore status为loadmore时，点击组件会发出此事件\r\n\t * @example <uv-loadmore :status=\"status\" icon-type=\"iconType\" load-text=\"loadText\" />\r\n\t */\r\n\texport default {\r\n\t\tname: \"uv-loadmore\",\r\n\t\tmixins: [mpMixin, mixin,props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\t// 粗点\r\n\t\t\t\tdotText: \"●\"\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\t// 加载的文字显示的样式\r\n\t\t\tloadTextStyle() {\r\n\t\t\t\treturn {\r\n\t\t\t\t\tcolor: this.color,\r\n\t\t\t\t\tfontSize: this.$uv.addUnit(this.fontSize),\r\n\t\t\t\t\tlineHeight: this.$uv.addUnit(this.fontSize),\r\n\t\t\t\t\tbackgroundColor: this.bgColor,\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t// 显示的提示文字\r\n\t\t\tshowText() {\r\n\t\t\t\tlet text = '';\r\n\t\t\t\tif (this.status == 'loadmore') text = this.loadmoreText\r\n\t\t\t\telse if (this.status == 'loading') text = this.loadingText\r\n\t\t\t\telse if (this.status == 'nomore' && this.isDot) text = this.dotText;\r\n\t\t\t\telse text = this.nomoreText;\r\n\t\t\t\treturn text;\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tloadMore() {\r\n\t\t\t\t// 只有在“加载更多”的状态下才发送点击事件，内容不满一屏时无法触发底部上拉事件，所以需要点击来触发\r\n\t\t\t\tif (this.status == 'loadmore') this.$emit('loadmore');\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t$show-lines: 1;\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t.uv-loadmore {\r\n\t\t@include flex(row);\r\n\t\talign-items: center;\r\n\t\tjustify-content: center;\r\n\t\tflex: 1;\r\n\r\n\t\t&__content {\r\n\t\t\tmargin: 0 15px;\r\n\t\t\t@include flex(row);\r\n\t\t\talign-items: center;\r\n\t\t\tjustify-content: center;\r\n\r\n\t\t\t&__icon-wrap {\r\n\t\t\t\tmargin-right: 8px;\r\n\t\t\t}\r\n\r\n\t\t\t&__text {\r\n\t\t\t\tfont-size: 14px;\r\n\t\t\t\tcolor: $uv-content-color;\r\n\t\t\t}\r\n\r\n\t\t\t&__dot-text {\r\n\t\t\t\tfont-size: 15px;\r\n\t\t\t\tcolor: $uv-tips-color;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-load-more/package.json",
    "content": "{\r\n  \"id\": \"uv-load-more\",\r\n  \"displayName\": \"uv-load-more 加载更多  全面兼容小程序、nvue、vue2、vue3等多端\",\r\n  \"version\": \"1.0.2\",\r\n  \"description\": \"uv-load-more 此组件一般用于标识页面底部加载数据时的状态，共有三种状态：加载前、加载中、加载后。\",\r\n  \"keywords\": [\r\n    \"uv-load-more\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"more\",\r\n    \"加载更多\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-line\",\r\n\t\t\t\"uv-loading-icon\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-load-more/readme.md",
    "content": "## LoadMore 加载更多\r\n\r\n> **组件名：uv-load-more**\r\n\r\n此组件一般用于标识页面底部加载数据时的状态，共有三种状态：加载前、加载中、加载后。\r\n\r\n### <a href=\"https://www.uvui.cn/components/loadMore.html\" target=\"_blank\">查看文档</a>\r\n\r\n### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n#### 如使用过程中有任何问题，或者您对uv-ui有一些好的建议，欢迎加入 uv-ui 交流群：<a href=\"https://ext.dcloud.net.cn/plugin?id=12287\" target=\"_blank\">uv-ui</a>、<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-loading-icon/changelog.md",
    "content": "## 1.0.3（2023-08-14）\n1. 新增参数textStyle，自定义文本样式\n## 1.0.2（2023-06-27）\n优化\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\n1. 新增uv-loading-icon组件\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-loading-icon/components/uv-loading-icon/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 是否显示组件\r\n\t\tshow: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 颜色\r\n\t\tcolor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#909193'\r\n\t\t},\r\n\t\t// 提示文字颜色\r\n\t\ttextColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#909193'\r\n\t\t},\r\n\t\t// 文字和图标是否垂直排列\r\n\t\tvertical: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 模式选择，circle-圆形，spinner-花朵形，semicircle-半圆形\r\n\t\tmode: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'spinner'\r\n\t\t},\r\n\t\t// 图标大小，单位默认px\r\n\t\tsize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 24\r\n\t\t},\r\n\t\t// 文字大小\r\n\t\ttextSize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 15\r\n\t\t},\r\n\t\t// 文字样式\r\n\t\ttextStyle: {\r\n\t\t\ttype: Object,\r\n\t\t\tdefault () {\r\n\t\t\t\treturn {}\r\n\t\t\t}\r\n\t\t},\r\n\t\t// 文字内容\r\n\t\ttext: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 动画模式 https://www.runoob.com/cssref/css3-pr-animation-timing-function.html\r\n\t\ttimingFunction: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'linear'\r\n\t\t},\r\n\t\t// 动画执行周期时间\r\n\t\tduration: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 1200\r\n\t\t},\r\n\t\t// mode=circle时的暗边颜色\r\n\t\tinactiveColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t...uni.$uv?.props?.loadingIcon\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-loading-icon/components/uv-loading-icon/uv-loading-icon.vue",
    "content": "<template>\r\n\t<view\r\n\t\tclass=\"uv-loading-icon\"\r\n\t\t:style=\"[$uv.addStyle(customStyle)]\"\r\n\t\t:class=\"[vertical && 'uv-loading-icon--vertical']\"\r\n\t\tv-if=\"show\"\r\n\t>\r\n\t\t<view\r\n\t\t\tv-if=\"!webviewHide\"\r\n\t\t\tclass=\"uv-loading-icon__spinner\"\r\n\t\t\t:class=\"[`uv-loading-icon__spinner--${mode}`]\"\r\n\t\t\tref=\"ani\"\r\n\t\t\t:style=\"{\r\n\t\t\t\tcolor: color,\r\n\t\t\t\twidth: $uv.addUnit(size),\r\n\t\t\t\theight: $uv.addUnit(size),\r\n\t\t\t\tborderTopColor: color,\r\n\t\t\t\tborderBottomColor: otherBorderColor,\r\n\t\t\t\tborderLeftColor: otherBorderColor,\r\n\t\t\t\tborderRightColor: otherBorderColor,\r\n\t\t\t\t'animation-duration': `${duration}ms`,\r\n\t\t\t\t'animation-timing-function': mode === 'semicircle' || mode === 'circle' ? timingFunction : ''\r\n\t\t\t}\"\r\n\t\t>\r\n\t\t\t<block v-if=\"mode === 'spinner'\">\r\n\t\t\t\t<!-- #ifndef APP-NVUE -->\r\n\t\t\t\t<view\r\n\t\t\t\t\tv-for=\"(item, index) in array12\"\r\n\t\t\t\t\t:key=\"index\"\r\n\t\t\t\t\tclass=\"uv-loading-icon__dot\"\r\n\t\t\t\t>\r\n\t\t\t\t</view>\r\n\t\t\t\t<!-- #endif -->\r\n\t\t\t\t<!-- #ifdef APP-NVUE -->\r\n\t\t\t\t<!-- 此组件内部图标部分无法设置宽高，即使通过width和height配置了也无效 -->\r\n\t\t\t\t<loading-indicator\r\n\t\t\t\t\tv-if=\"!webviewHide\"\r\n\t\t\t\t\tclass=\"uv-loading-indicator\"\r\n\t\t\t\t\t:animating=\"true\"\r\n\t\t\t\t\t:style=\"{\r\n\t\t\t\t\t\tcolor: color,\r\n\t\t\t\t\t\twidth: $uv.addUnit(size),\r\n\t\t\t\t\t\theight: $uv.addUnit(size)\r\n\t\t\t\t\t}\"\r\n\t\t\t\t/>\r\n\t\t\t\t<!-- #endif -->\r\n\t\t\t</block>\r\n\t\t</view>\r\n\t\t<text\r\n\t\t\tv-if=\"text\"\r\n\t\t\tclass=\"uv-loading-icon__text\"\r\n\t\t\t:style=\"[{\r\n\t\t\t\tfontSize: $uv.addUnit(textSize),\r\n\t\t\t\tcolor: textColor,\r\n\t\t\t},$uv.addStyle(textStyle)]\"\r\n\t\t>{{text}}</text>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport { colorGradient } from '@/uni_modules/uv-ui-tools/libs/function/colorGradient.js'\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t// #ifdef APP-NVUE\r\n\tconst animation = weex.requireModule('animation');\r\n\t// #endif\r\n\t/**\r\n\t * loading 加载动画\r\n\t * @description 警此组件为一个小动画，目前用在uvui的loadmore加载更多和switch开关等组件的正在加载状态场景。\r\n\t * @tutorial https://www.uvui.cn/components/loading.html\r\n\t * @property {Boolean}\t\t\tshow\t\t\t是否显示组件  (默认 true)\r\n\t * @property {String}\t\t\tcolor\t\t\t动画活动区域的颜色，只对 mode = flower 模式有效（默认#909193）\r\n\t * @property {String}\t\t\ttextColor\t\t提示文本的颜色（默认#909193）\r\n\t * @property {Boolean}\t\t\tvertical\t\t文字和图标是否垂直排列 (默认 false )\r\n\t * @property {String}\t\t\tmode\t\t\t模式选择，见官网说明（默认 'circle' ）\r\n\t * @property {String | Number}\tsize\t\t\t加载图标的大小，单位px （默认 24 ）\r\n\t * @property {String | Number}\ttextSize\t\t文字大小（默认 15 ）\r\n\t * @property {String | Number}\ttext\t\t\t文字内容 \r\n\t * @property {Object}\ttextStyle 文字样式\r\n\t * @property {String}\t\t\ttimingFunction\t动画模式 （默认 'ease-in-out' ）\r\n\t * @property {String | Number}\tduration\t\t动画执行周期时间（默认 1200）\r\n\t * @property {String}\t\t\tinactiveColor\tmode=circle时的暗边颜色 \r\n\t * @property {Object}\t\t\tcustomStyle\t\t定义需要用到的外部样式\r\n\t * @example <uv-loading mode=\"circle\"></uv-loading>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-loading-icon',\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\t// Array.form可以通过一个伪数组对象创建指定长度的数组\r\n\t\t\t\t// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/from\r\n\t\t\t\tarray12: Array.from({\r\n\t\t\t\t\tlength: 12\r\n\t\t\t\t}),\r\n\t\t\t\t// 这里需要设置默认值为360，否则在安卓nvue上，会延迟一个duration周期后才执行\r\n\t\t\t\t// 在iOS nvue上，则会一开始默认执行两个周期的动画\r\n\t\t\t\taniAngel: 360, // 动画旋转角度\r\n\t\t\t\twebviewHide: false, // 监听webview的状态，如果隐藏了页面，则停止动画，以免性能消耗\r\n\t\t\t\tloading: false, // 是否运行中，针对nvue使用\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\t// 当为circle类型时，给其另外三边设置一个更轻一些的颜色\r\n\t\t\t// 之所以需要这么做的原因是，比如父组件传了color为红色，那么需要另外的三个边为浅红色\r\n\t\t\t// 而不能是固定的某一个其他颜色(因为这个固定的颜色可能浅蓝，导致效果没有那么细腻良好)\r\n\t\t\totherBorderColor() {\r\n\t\t\t\tconst lightColor = colorGradient(this.color, '#ffffff', 100)[80]\r\n\t\t\t\tif (this.mode === 'circle') {\r\n\t\t\t\t\treturn this.inactiveColor ? this.inactiveColor : lightColor\r\n\t\t\t\t} else {\r\n\t\t\t\t\treturn 'transparent'\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\tshow(n) {\r\n\t\t\t\t// nvue中，show为true，且为非loading状态，就重新执行动画模块\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\tif (n && !this.loading) {\r\n\t\t\t\t\tsetTimeout(() => {\r\n\t\t\t\t\t\tthis.startAnimate()\r\n\t\t\t\t\t}, 30)\r\n\t\t\t\t}\r\n\t\t\t\t// #endif\r\n\t\t\t}\r\n\t\t},\r\n\t\tmounted() {\r\n\t\t\tthis.init()\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tinit() {\r\n\t\t\t\tsetTimeout(() => {\r\n\t\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\t\tthis.show && this.nvueAnimate()\r\n\t\t\t\t\t// #endif\r\n\t\t\t\t\t// #ifdef APP-PLUS \r\n\t\t\t\t\tthis.show && this.addEventListenerToWebview()\r\n\t\t\t\t\t// #endif\r\n\t\t\t\t}, 20)\r\n\t\t\t},\r\n\t\t\t// 监听webview的显示与隐藏\r\n\t\t\taddEventListenerToWebview() {\r\n\t\t\t\t// webview的堆栈\r\n\t\t\t\tconst pages = getCurrentPages()\r\n\t\t\t\t// 当前页面\r\n\t\t\t\tconst page = pages[pages.length - 1]\r\n\t\t\t\t// 当前页面的webview实例\r\n\t\t\t\tconst currentWebview = page.$getAppWebview()\r\n\t\t\t\t// 监听webview的显示与隐藏，从而停止或者开始动画(为了性能)\r\n\t\t\t\tcurrentWebview.addEventListener('hide', () => {\r\n\t\t\t\t\tthis.webviewHide = true\r\n\t\t\t\t})\r\n\t\t\t\tcurrentWebview.addEventListener('show', () => {\r\n\t\t\t\t\tthis.webviewHide = false\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\t// #ifdef APP-NVUE\r\n\t\t\tnvueAnimate() {\r\n\t\t\t\t// nvue下，非spinner类型时才需要旋转，因为nvue的spinner类型，使用了weex的\r\n\t\t\t\t// loading-indicator组件，自带旋转功能\r\n\t\t\t\tthis.mode !== 'spinner' && this.startAnimate()\r\n\t\t\t},\r\n\t\t\t// 执行nvue的animate模块动画\r\n\t\t\tstartAnimate() {\r\n\t\t\t\tthis.loading = true\r\n\t\t\t\tconst ani = this.$refs.ani\r\n\t\t\t\tif (!ani) return\r\n\t\t\t\tanimation.transition(ani, {\r\n\t\t\t\t\t// 进行角度旋转\r\n\t\t\t\t\tstyles: {\r\n\t\t\t\t\t\ttransform: `rotate(${this.aniAngel}deg)`,\r\n\t\t\t\t\t\ttransformOrigin: 'center center'\r\n\t\t\t\t\t},\r\n\t\t\t\t\tduration: this.duration,\r\n\t\t\t\t\ttimingFunction: this.timingFunction,\r\n\t\t\t\t\t// delay: 10\r\n\t\t\t\t}, () => {\r\n\t\t\t\t\t// 每次增加360deg，为了让其重新旋转一周\r\n\t\t\t\t\tthis.aniAngel += 360\r\n\t\t\t\t\t// 动画结束后，继续循环执行动画，需要同时判断webviewHide变量\r\n\t\t\t\t\t// nvue安卓，页面隐藏后依然会继续执行startAnimate方法\r\n\t\t\t\t\tthis.show && !this.webviewHide ? this.startAnimate() : this.loading = false\r\n\t\t\t\t})\r\n\t\t\t}\r\n\t\t\t// #endif\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t$uv-loading-icon-color: #c8c9cc !default;\r\n\t$uv-loading-icon-text-margin-left:4px !default;\r\n\t$uv-loading-icon-text-color:$uv-content-color !default;\r\n\t$uv-loading-icon-text-font-size:14px !default;\r\n\t$uv-loading-icon-text-line-height:20px !default;\r\n\t$uv-loading-width:30px !default;\r\n\t$uv-loading-height:30px !default;\r\n\t$uv-loading-max-width:100% !default;\r\n\t$uv-loading-max-height:100% !default;\r\n\t$uv-loading-semicircle-border-width: 2px !default;\r\n\t$uv-loading-semicircle-border-color:transparent !default;\r\n\t$uv-loading-semicircle-border-top-right-radius: 100px !default;\r\n\t$uv-loading-semicircle-border-top-left-radius: 100px !default;\r\n\t$uv-loading-semicircle-border-bottom-left-radius: 100px !default;\r\n\t$uv-loading-semicircle-border-bottom-right-radiu: 100px !default;\r\n\t$uv-loading-semicircle-border-style: solid !default;\r\n\t$uv-loading-circle-border-top-right-radius: 100px !default;\r\n\t$uv-loading-circle-border-top-left-radius: 100px !default;\r\n\t$uv-loading-circle-border-bottom-left-radius: 100px !default;\r\n\t$uv-loading-circle-border-bottom-right-radiu: 100px !default;\r\n\t$uv-loading-circle-border-width:2px !default;\r\n\t$uv-loading-circle-border-top-color:#e5e5e5 !default;\r\n\t$uv-loading-circle-border-right-color:$uv-loading-circle-border-top-color !default;\r\n\t$uv-loading-circle-border-bottom-color:$uv-loading-circle-border-top-color !default;\r\n\t$uv-loading-circle-border-left-color:$uv-loading-circle-border-top-color !default;\r\n\t$uv-loading-circle-border-style:solid !default;\r\n\t$uv-loading-icon-host-font-size:0px !default;\r\n\t$uv-loading-icon-host-line-height:1 !default;\r\n\t$uv-loading-icon-vertical-margin:6px 0 0 !default;\r\n\t$uv-loading-icon-dot-top:0 !default;\r\n\t$uv-loading-icon-dot-left:0 !default;\r\n\t$uv-loading-icon-dot-width:100% !default;\r\n\t$uv-loading-icon-dot-height:100% !default;\r\n\t$uv-loading-icon-dot-before-width:2px !default;\r\n\t$uv-loading-icon-dot-before-height:25% !default;\r\n\t$uv-loading-icon-dot-before-margin:0 auto !default;\r\n\t$uv-loading-icon-dot-before-background-color:currentColor !default;\r\n\t$uv-loading-icon-dot-before-border-radius:40% !default;\r\n\r\n\t.uv-loading-icon {\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\t// display: inline-flex;\r\n\t\t/* #endif */\r\n\t\tflex-direction: row;\r\n\t\talign-items: center;\r\n\t\tjustify-content: center;\r\n\t\tcolor: $uv-loading-icon-color;\r\n\r\n\t\t&__text {\r\n\t\t\tmargin-left: $uv-loading-icon-text-margin-left;\r\n\t\t\tcolor: $uv-loading-icon-text-color;\r\n\t\t\tfont-size: $uv-loading-icon-text-font-size;\r\n\t\t\tline-height: $uv-loading-icon-text-line-height;\r\n\t\t}\r\n\r\n\t\t&__spinner {\r\n\t\t\twidth: $uv-loading-width;\r\n\t\t\theight: $uv-loading-height;\r\n\t\t\tposition: relative;\r\n\t\t\t/* #ifndef APP-NVUE */\r\n\t\t\tbox-sizing: border-box;\r\n\t\t\tmax-width: $uv-loading-max-width;\r\n\t\t\tmax-height: $uv-loading-max-height;\r\n\t\t\tanimation: uv-rotate 1s linear infinite;\r\n\t\t\t/* #endif */\r\n\t\t}\r\n\r\n\t\t&__spinner--semicircle {\r\n\t\t\tborder-width: $uv-loading-semicircle-border-width;\r\n\t\t\tborder-color: $uv-loading-semicircle-border-color;\r\n\t\t\tborder-top-right-radius: $uv-loading-semicircle-border-top-right-radius;\r\n\t\t\tborder-top-left-radius: $uv-loading-semicircle-border-top-left-radius;\r\n\t\t\tborder-bottom-left-radius: $uv-loading-semicircle-border-bottom-left-radius;\r\n\t\t\tborder-bottom-right-radius: $uv-loading-semicircle-border-bottom-right-radiu;\r\n\t\t\tborder-style: $uv-loading-semicircle-border-style;\r\n\t\t}\r\n\r\n\t\t&__spinner--circle {\r\n\t\t\tborder-top-right-radius: $uv-loading-circle-border-top-right-radius;\r\n\t\t\tborder-top-left-radius: $uv-loading-circle-border-top-left-radius;\r\n\t\t\tborder-bottom-left-radius: $uv-loading-circle-border-bottom-left-radius;\r\n\t\t\tborder-bottom-right-radius: $uv-loading-circle-border-bottom-right-radiu;\r\n\t\t\tborder-width: $uv-loading-circle-border-width;\r\n\t\t\tborder-top-color: $uv-loading-circle-border-top-color;\r\n\t\t\tborder-right-color: $uv-loading-circle-border-right-color;\r\n\t\t\tborder-bottom-color: $uv-loading-circle-border-bottom-color;\r\n\t\t\tborder-left-color: $uv-loading-circle-border-left-color;\r\n\t\t\tborder-style: $uv-loading-circle-border-style;\r\n\t\t}\r\n\r\n\t\t&--vertical {\r\n\t\t\tflex-direction: column\r\n\t\t}\r\n\t}\r\n\r\n\t/* #ifndef APP-NVUE */\r\n\t:host {\r\n\t\tfont-size: $uv-loading-icon-host-font-size;\r\n\t\tline-height: $uv-loading-icon-host-line-height;\r\n\t}\r\n\r\n\t.uv-loading-icon {\r\n\t\t&__spinner--spinner {\r\n\t\t\tanimation-timing-function: steps(12)\r\n\t\t}\r\n\r\n\t\t&__text:empty {\r\n\t\t\tdisplay: none\r\n\t\t}\r\n\r\n\t\t&--vertical &__text {\r\n\t\t\tmargin: $uv-loading-icon-vertical-margin;\r\n\t\t\tcolor: $uv-content-color;\r\n\t\t}\r\n\r\n\t\t&__dot {\r\n\t\t\tposition: absolute;\r\n\t\t\ttop: $uv-loading-icon-dot-top;\r\n\t\t\tleft: $uv-loading-icon-dot-left;\r\n\t\t\twidth: $uv-loading-icon-dot-width;\r\n\t\t\theight: $uv-loading-icon-dot-height;\r\n\r\n\t\t\t&:before {\r\n\t\t\t\tdisplay: block;\r\n\t\t\t\twidth: $uv-loading-icon-dot-before-width;\r\n\t\t\t\theight: $uv-loading-icon-dot-before-height;\r\n\t\t\t\tmargin: $uv-loading-icon-dot-before-margin;\r\n\t\t\t\tbackground-color: $uv-loading-icon-dot-before-background-color;\r\n\t\t\t\tborder-radius: $uv-loading-icon-dot-before-border-radius;\r\n\t\t\t\tcontent: \" \"\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t@for $i from 1 through 12 {\r\n\t\t.uv-loading-icon__dot:nth-of-type(#{$i}) {\r\n\t\t\ttransform: rotate($i * 30deg);\r\n\t\t\topacity: 1 - 0.0625 * ($i - 1);\r\n\t\t}\r\n\t}\r\n\r\n\t@keyframes uv-rotate {\r\n\t\t0% {\r\n\t\t\ttransform: rotate(0deg)\r\n\t\t}\r\n\r\n\t\tto {\r\n\t\t\ttransform: rotate(1turn)\r\n\t\t}\r\n\t}\r\n\r\n\t/* #endif */\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-loading-icon/package.json",
    "content": "{\r\n  \"id\": \"uv-loading-icon\",\r\n  \"displayName\": \"uv-loading-icon 加载动画 全面兼容vue3+2、app、h5、小程序等多端\",\r\n  \"version\": \"1.0.3\",\r\n  \"description\": \"此组件为一个小动画，目前用在uv-ui的uv-load-more加载更多等组件，还可以运用在项目中正在加载状态场景。\",\r\n  \"keywords\": [\r\n    \"uv-loading-icon\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"loading\",\r\n    \"加载动画\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-loading-icon/readme.md",
    "content": "## LoadingIcon 加载动画\r\n\r\n> **组件名：uv-loading-icon**\r\n\r\n此组件为一个小动画，目前用在 `uv-ui` 的 `uv-load-more` 加载更多等组件，还可以运用在项目中正在加载状态场景。\r\n\r\n# <a href=\"https://www.uvui.cn/components/loadingIcon.html\" target=\"_blank\">查看文档</a>\r\n\r\n## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>（请不要 下载插件ZIP）</small>\r\n\r\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n<a href=\"https://ext.dcloud.net.cn/plugin?name=uv-ui\" target=\"_blank\">\r\n\r\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\r\n\r\n</a>\r\n\r\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-loading-page/changelog.md",
    "content": "## 1.0.2（2023-07-02）\nuv-loading-page  由于弹出层uv-transition的修改，组件内部做了相应的修改，参数不变。\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-loading-page 加载页\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-loading-page/components/uv-loading-page/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 提示内容\r\n\t\tloadingText: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 文字上方用于替换loading动画的图片\r\n\t\timage: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 加载动画的模式，circle-圆形，spinner-花朵形，semicircle-半圆形\r\n\t\tloadingMode: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'circle'\r\n\t\t},\r\n\t\t// 是否加载中\r\n\t\tloading: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 背景色\r\n\t\tbgColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#fff'\r\n\t\t},\r\n\t\t// 文字颜色\r\n\t\tcolor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#C8C8C8'\r\n\t\t},\r\n\t\t// 文字大小\r\n\t\tfontSize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 18\r\n\t\t},\r\n\t\t// 图标大小\r\n\t\ticonSize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 28\r\n\t\t},\r\n\t\t// 加载中图标的颜色，只能rgb或者十六进制颜色值\r\n\t\tloadingColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#C8C8C8'\r\n\t\t},\r\n\t\t...uni.$uv?.props?.loadingPage\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-loading-page/components/uv-loading-page/uv-loading-page.vue",
    "content": "<template>\r\n    <uv-transition\r\n      :show=\"loading\"\r\n      :custom-style=\"{\r\n        position: 'fixed',\r\n        top: 0,\r\n        left: 0,\r\n        right: 0,\r\n        bottom: 0,\r\n\t\t\t\tzIndex: 999,\r\n        backgroundColor: bgColor,\r\n        display: 'flex'\r\n      }\"\r\n    >\r\n      <view class=\"uv-loading-page\">\r\n        <view class=\"uv-loading-page__warpper\">\r\n          <view class=\"uv-loading-page__warpper__loading-icon\">\r\n            <image\r\n              v-if=\"image\"\r\n              :src=\"image\"\r\n              class=\"uv-loading-page__warpper__loading-icon__img\"\r\n              mode=\"widthFit\"\r\n\t\t\t\t\t\t\t:style=\"{\r\n\t\t\t\t\t\t\t\twidth: $uv.addUnit(iconSize),\r\n\t\t\t\t\t\t\t\theight: $uv.addUnit(iconSize)\r\n\t\t\t\t\t\t\t}\"\r\n            ></image>\r\n            <uv-loading-icon\r\n              v-else\r\n              :mode=\"loadingMode\"\r\n              :size=\"$uv.addUnit(iconSize)\"\r\n              :color=\"loadingColor\"\r\n            ></uv-loading-icon>\r\n          </view>\r\n          <slot>\r\n            <text\r\n              class=\"uv-loading-page__warpper__text\"\r\n              :style=\"{\r\n                fontSize: $uv.addUnit(fontSize),\r\n                color: color,\r\n              }\">{{ loadingText }}</text\r\n            >\r\n          </slot>\r\n        </view>\r\n      </view>\r\n    </uv-transition>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from \"./props.js\";\r\n/**\r\n * loadingPage 加载动画\r\n * @description 警此组件为一个小动画，目前用在uvui的loadmore加载更多和switch开关等组件的正在加载状态场景。\r\n * @tutorial https://www.uvui.cn/components/loading.html\r\n * @property {String | Number}\tloadingText\t\t提示内容  (默认 '正在加载' )\r\n * @property {String}\t\timage\t\t文字上方用于替换loading动画的图片\r\n * @property {String}\t\tloadingMode\t\t加载动画的模式，circle-圆形，spinner-花朵形，semicircle-半圆形 （默认 'circle' ）\r\n * @property {Boolean}\tloading\t\t是否加载中 （默认 false ）\r\n * @property {String}\t\tbgColor\t\t背景色 （默认 '#ffffff' ）\r\n * @property {String}\t\tcolor\t\t文字颜色 （默认 '#C8C8C8' ）\r\n * @property {String | Number}\tfontSize\t\t文字大小 （默认 19 ）\r\n * @property {String | Number}\ticonSize\t\t图标大小 （默认 28 ）\r\n * @property {String}\t\t\tloadingColor\t加载中图标的颜色，只能rgb或者十六进制颜色值 （默认 '#C8C8C8' ）\r\n * @example <uv-loading mode=\"circle\"></uv-loading>\r\n */\r\nexport default {\r\n    name: \"uv-loading-page\",\r\n    mixins: [mpMixin, mixin, props]\r\n};\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n$text-color: rgb(200, 200, 200) !default;\r\n$text-size: 19px !default;\r\n$uv-loading-icon-margin-bottom: 10px !default;\r\n.uv-loading-page {\r\n    @include flex(column);\r\n    flex: 1;\r\n    align-items: center;\r\n    justify-content: center;\r\n    &__warpper {\r\n        margin-top: -150px;\r\n        justify-content: center;\r\n        align-items: center;\r\n        /* #ifndef APP-NVUE */\r\n        color: $text-color;\r\n        font-size: $text-size;\r\n        /* #endif */\r\n        @include flex(column);\r\n        &__loading-icon {\r\n            margin-bottom: $uv-loading-icon-margin-bottom;\r\n            &__img {\r\n                width: 40px;\r\n                height: 40px;\r\n            }\r\n        }\r\n        &__text {\r\n            font-size: $text-size;\r\n            color: $text-color;\r\n        }\r\n    }\r\n}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-loading-page/package.json",
    "content": "{\r\n  \"id\": \"uv-loading-page\",\r\n  \"displayName\": \"uv-loading-page 加载页  全面兼容小程序、nvue、vue2、vue3等多端\",\r\n  \"version\": \"1.0.2\",\r\n  \"description\": \"uv-loading-page 该组件是一个页面级的加载效果，可以在页面初始化数据等场景使用，与骨架屏有相似之处。\",\r\n  \"keywords\": [\r\n    \"uv-loading-page\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"page\",\r\n    \"loading\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-transition\",\r\n\t\t\t\"uv-loading-icon\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-loading-page/readme.md",
    "content": "## LoadingPage 加载页\r\n\r\n> **组件名：uv-loading-page**\r\n\r\n该组件是一个页面级的加载效果，可以在页面初始化数据等场景使用，与骨架屏有相似之处。\r\n\r\n### <a href=\"https://www.uvui.cn/components/loadingPage.html\" target=\"_blank\">查看文档</a>\r\n\r\n### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n#### 如使用过程中有任何问题，或者您对uv-ui有一些好的建议，欢迎加入 uv-ui 交流群：<a href=\"https://ext.dcloud.net.cn/plugin?id=12287\" target=\"_blank\">uv-ui</a>、<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-modal/changelog.md",
    "content": "## 1.0.8（2023-09-08）\n1. 修复两个按钮之间竖线不显示的问题\n2. uv-ui项目自定义按钮示例修改\n## 1.0.7（2023-08-30）\n1. 增加align参数，设置文本对齐方式，left  center right\n2. 增加textStyle参数，设置文本样式\n## 1.0.6（2023-08-23）\r\n1. 修复异步loading时，确认回调还会一直触发\r\n## 1.0.5（2023-07-03）\r\n去除插槽判断，避免某些平台不显示的BUG\r\n## 1.0.4（2023-07-02）\r\nuv-modal  由于弹出层uv-popup的修改，打开和关闭方法更改，详情参考文档：https://www.uvui.cn/components/modal.html\r\n## 1.0.3（2023-06-29）\r\n1. 增加closeLoading方法，方便异步加载手动取消\r\n2. 更新文档\r\n## 1.0.2（2023-06-11）\r\n1. 新增zIndex参数\r\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-modal 模态框\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-modal/components/uv-modal/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 标题\r\n\t\ttitle: {\r\n\t\t\ttype: [String],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 弹窗内容\r\n\t\tcontent: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 确认文案\r\n\t\tconfirmText: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '确认'\r\n\t\t},\r\n\t\t// 取消文案\r\n\t\tcancelText: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '取消'\r\n\t\t},\r\n\t\t// 是否显示确认按钮\r\n\t\tshowConfirmButton: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 是否显示取消按钮\r\n\t\tshowCancelButton: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 确认按钮颜色\r\n\t\tconfirmColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#2979ff'\r\n\t\t},\r\n\t\t// 取消文字颜色\r\n\t\tcancelColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#606266'\r\n\t\t},\r\n\t\t// 对调确认和取消的位置\r\n\t\tbuttonReverse: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否开启缩放效果\r\n\t\tzoom: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 层级\r\n\t\tzIndex: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 10075\r\n\t\t},\r\n\t\t// 是否异步关闭，只对确定按钮有效\r\n\t\tasyncClose: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否允许点击遮罩关闭modal\r\n\t\tcloseOnClickOverlay: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 给一个负的margin-top，往上偏移，避免和键盘重合的情况\r\n\t\tnegativeTop: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// modal宽度，不支持百分比，可以数值，px，rpx单位\r\n\t\twidth: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: '650rpx'\r\n\t\t},\r\n\t\t// 文本对齐方式，默认left\r\n\t\talign: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'left'\r\n\t\t},\r\n\t\t// 文本自定义样式\r\n\t\ttextStyle: {\r\n\t\t\ttype: Object,\r\n\t\t\tdefault: ()=>{}\r\n\t\t},\r\n\t\t...uni.$uv?.props?.modal\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-modal/components/uv-modal/uv-modal.vue",
    "content": "<template>\r\n\t<uv-popup\r\n\t\tref=\"modalPopup\"\r\n\t\tmode=\"center\"\r\n\t\t:zoom=\"zoom\"\r\n    :zIndex=\"zIndex\"\r\n\t\t:customStyle=\"{\r\n\t\t\tborderRadius: '6px', \r\n\t\t\toverflow: 'hidden',\r\n\t\t\tmarginTop: `-${$uv.addUnit(negativeTop)}`\r\n\t\t}\"\r\n\t\t:closeOnClickOverlay=\"closeOnClickOverlay\"\r\n\t\t:safeAreaInsetBottom=\"false\"\r\n\t\t:duration=\"400\"\r\n\t\t@change=\"popupChange\"\r\n\t>\r\n\t\t<view\r\n\t\t\tclass=\"uv-modal\"\r\n\t\t\t:style=\"{\r\n\t\t\t\twidth: $uv.addUnit(width),\r\n\t\t\t}\"\r\n\t\t>\r\n\t\t\t<text\r\n\t\t\t\tclass=\"uv-modal__title\"\r\n\t\t\t\tv-if=\"title\"\r\n\t\t\t>{{ title }}</text>\r\n\t\t\t<view\r\n\t\t\t\tclass=\"uv-modal__content\"\r\n\t\t\t\t:style=\"{\r\n\t\t\t\t\tpaddingTop: `${title ? 12 : 25}px`\r\n\t\t\t\t}\"\r\n\t\t\t>\r\n\t\t\t\t<slot>\r\n\t\t\t\t\t<text \r\n\t\t\t\t\t\tclass=\"uv-modal__content__text\"\r\n\t\t\t\t\t\t:style=\"[\r\n\t\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\t\ttextAlign: align\r\n\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t\tnvueStyle,\r\n\t\t\t\t\t\t\t$uv.addStyle(textStyle)\r\n\t\t\t\t\t\t]\"\r\n\t\t\t\t\t>{{ content }}</text>\r\n\t\t\t\t</slot>\r\n\t\t\t</view>\r\n\t\t\t<slot name=\"confirmButton\">\r\n\t\t\t\t<uv-line></uv-line>\r\n\t\t\t\t<view\r\n\t\t\t\t\tclass=\"uv-modal__button-group\"\r\n\t\t\t\t\t:style=\"{\r\n\t\t\t\t\t\tflexDirection: buttonReverse ? 'row-reverse' : 'row'\r\n\t\t\t\t\t}\"\r\n\t\t\t\t>\r\n\t\t\t\t\t<view\r\n\t\t\t\t\t\tclass=\"uv-modal__button-group__wrapper uv-modal__button-group__wrapper--cancel\"\r\n\t\t\t\t\t\t:hover-stay-time=\"150\"\r\n\t\t\t\t\t\thover-class=\"uv-modal__button-group__wrapper--hover\"\r\n\t\t\t\t\t\t:class=\"[showCancelButton && !showConfirmButton && 'uv-modal__button-group__wrapper--only-cancel']\"\r\n\t\t\t\t\t\tv-if=\"showCancelButton\"\r\n\t\t\t\t\t\t@tap=\"cancelHandler\"\r\n\t\t\t\t\t>\r\n\t\t\t\t\t\t<text\r\n\t\t\t\t\t\t\tclass=\"uv-modal__button-group__wrapper__text\"\r\n\t\t\t\t\t\t\t:style=\"{\r\n\t\t\t\t\t\t\t\tcolor: cancelColor\r\n\t\t\t\t\t\t\t}\"\r\n\t\t\t\t\t\t>{{ cancelText }}</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<uv-line\r\n\t\t\t\t\t\tdirection=\"column\"\r\n\t\t\t\t\t\tv-if=\"showConfirmButton && showCancelButton\"\r\n\t\t\t\t\t></uv-line>\r\n\t\t\t\t\t<view\r\n\t\t\t\t\t\tclass=\"uv-modal__button-group__wrapper uv-modal__button-group__wrapper--confirm\"\r\n\t\t\t\t\t\t:hover-stay-time=\"150\"\r\n\t\t\t\t\t\thover-class=\"uv-modal__button-group__wrapper--hover\"\r\n\t\t\t\t\t\t:class=\"[!showCancelButton && showConfirmButton && 'uv-modal__button-group__wrapper--only-confirm']\"\r\n\t\t\t\t\t\tv-if=\"showConfirmButton\"\r\n\t\t\t\t\t\t@tap=\"confirmHandler\"\r\n\t\t\t\t\t>\r\n\t\t\t\t\t\t<uv-loading-icon v-if=\"loading\"></uv-loading-icon>\r\n\t\t\t\t\t\t<text\r\n\t\t\t\t\t\t\tv-else\r\n\t\t\t\t\t\t\tclass=\"uv-modal__button-group__wrapper__text\"\r\n\t\t\t\t\t\t\t:style=\"{\r\n\t\t\t\t\t\t\t\tcolor: confirmColor\r\n\t\t\t\t\t\t\t}\"\r\n\t\t\t\t\t\t>{{ confirmText }}</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</slot>\r\n\t\t</view>\r\n\t</uv-popup>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * Modal 模态框\r\n\t * @description 弹出模态框，常用于消息提示、消息确认、在当前页面内完成特定的交互操作。\r\n\t * @tutorial https://www.uvui.cn/components/modul.html\r\n\t * @property {String}\t\t\ttitle\t\t\t\t标题内容\r\n\t * @property {String}\t\t\tcontent\t\t\t\t模态框内容，如传入slot内容，则此参数无效\r\n\t * @property {String}\t\t\tconfirmText\t\t\t确认按钮的文字 （默认 '确认' ）\r\n\t * @property {String}\t\t\tcancelText\t\t\t取消按钮的文字 （默认 '取消' ）\r\n\t * @property {Boolean}\t\t\tshowConfirmButton\t是否显示确认按钮 （默认 true ）\r\n\t * @property {Boolean}\t\t\tshowCancelButton\t是否显示取消按钮 （默认 false ）\r\n\t * @property {String}\t\t\tconfirmColor\t\t确认按钮的颜色 （默认 '#2979ff' ）\r\n\t * @property {String}\t\t\tcancelColor\t\t\t取消按钮的颜色 （默认 '#606266' ）\r\n\t * @property {Boolean}\t\t\tbuttonReverse\t\t对调确认和取消的位置 （默认 false ）\r\n\t * @property {Boolean}\t\t\tzoom\t\t\t\t是否开启缩放模式 （默认 true ）\r\n\t * @property {Boolean}\t\t\tasyncClose\t\t\t是否异步关闭，只对确定按钮有效，见上方说明 （默认 false ）\r\n\t * @property {Boolean}\t\t\tcloseOnClickOverlay\t是否允许点击遮罩关闭该组件 （默认 true ）\r\n\t * @property {String | Number}\tnegativeTop\t\t\t往上偏移的值，给一个负的margin-top，往上偏移，避免和键盘重合的情况，单位任意，数值则默认为px单位 （默认 0 ）\r\n\t * @property {String | Number}\twidth\t\t\t\tmodal宽度，不支持百分比，可以数值，px，rpx单位 （默认 '650rpx' ）\r\n\t * @property {String} align 文本对齐方式 （默认'left'） \r\n\t * @property {String | Object} textStyle 文本扩展样式\r\n\t * @event {Function} confirm\t点击确认按钮时触发\r\n\t * @event {Function} cancel\t\t点击取消按钮时触发\r\n\t * @event {Function} close\t\t点击遮罩关闭出发，closeOnClickOverlay为true有效\r\n\t * @example <uv-modal ref=\"modalPopup\" title=\"title\" content=\"content\"></uv-modal>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-modal',\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tloading: false\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\tnvueStyle() {\r\n\t\t\t\tconst style = {};\r\n\t\t\t\t// 避免nvue中不能换行的问题\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\tstyle.width = this.$uv.addUnit(this.$uv.getPx(this.width) - 50,'px');\r\n\t\t\t\t// #endif\r\n\t\t\t\treturn style;\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\topen() {\r\n\t\t\t\tthis.$refs.modalPopup.open();\r\n\t\t\t\tif (this.loading) this.loading = false;\r\n\t\t\t},\r\n\t\t\tclose() {\r\n\t\t\t\tthis.$refs.modalPopup.close();\r\n\t\t\t},\r\n\t\t\tpopupChange(e) {\r\n\t\t\t\tif(!e.show) this.$emit('close');\r\n\t\t\t},\r\n\t\t\t// 点击确定按钮\r\n\t\t\tconfirmHandler() {\r\n\t\t\t\tif(!this.loading) {\r\n\t\t\t\t\tthis.$emit('confirm');\r\n\t\t\t\t}\r\n\t\t\t\t// 如果配置了异步关闭，将按钮值为loading状态\r\n\t\t\t\tif (this.asyncClose) {\r\n\t\t\t\t\tthis.loading = true;\r\n\t\t\t\t} else {\r\n\t\t\t\t\tthis.close();\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t// 点击取消按钮\r\n\t\t\tcancelHandler() {\r\n\t\t\t\tthis.$emit('cancel');\r\n\t\t\t\tthis.close();\r\n\t\t\t},\r\n\t\t\tcloseLoading() {\r\n\t\t\t\tthis.loading = false;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t$uv-modal-border-radius: 6px;\r\n\r\n\t.uv-modal {\r\n\t\twidth: 650rpx;\r\n\t\tborder-radius: $uv-modal-border-radius;\r\n\t\toverflow: hidden;\r\n\r\n\t\t&__title {\r\n\t\t\tfont-size: 16px;\r\n\t\t\tfont-weight: bold;\r\n\t\t\tcolor: $uv-content-color;\r\n\t\t\ttext-align: center;\r\n\t\t\tpadding-top: 25px;\r\n\t\t}\r\n\r\n\t\t&__content {\r\n\t\t\tpadding: 12px 25px 25px 25px;\r\n\t\t\t@include flex;\r\n\t\t\tjustify-content: center;\r\n\r\n\t\t\t&__text {\r\n\t\t\t\tline-height: 48rpx;\r\n\t\t\t\tfont-size: 15px;\r\n\t\t\t\tcolor: $uv-content-color;\r\n\t\t\t\tflex: 1;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t&__button-group {\r\n\t\t\t@include flex;\r\n\t\t\theight: 48px;\r\n\r\n\t\t\t&__wrapper {\r\n\t\t\t\tflex: 1;\r\n\t\t\t\t@include flex;\r\n\t\t\t\tjustify-content: center;\r\n\t\t\t\talign-items: center;\r\n\t\t\t\theight: 48px;\r\n\t\t\t\t\r\n\t\t\t\t&--confirm,\r\n\t\t\t\t&--only-cancel {\r\n\t\t\t\t\tborder-bottom-right-radius: $uv-modal-border-radius;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t&--cancel,\r\n\t\t\t\t&--only-confirm {\r\n\t\t\t\t\tborder-bottom-left-radius: $uv-modal-border-radius;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t&--hover {\r\n\t\t\t\t\tbackground-color: $uv-bg-color;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t&__text {\r\n\t\t\t\t\tcolor: $uv-content-color;\r\n\t\t\t\t\tfont-size: 16px;\r\n\t\t\t\t\ttext-align: center;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-modal/package.json",
    "content": "{\r\n  \"id\": \"uv-modal\",\r\n  \"displayName\": \"uv-modal 模态框 全面兼容vue3+2、app、h5、小程序等多端\",\r\n  \"version\": \"1.0.8\",\r\n  \"description\": \"支持自定义内容，与uniapp提供的API uni.showModal类似，但是功能更强大，更加灵活\",\r\n  \"keywords\": [\r\n    \"uv-modal\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"modal\",\r\n    \"模态框\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-popup\",\r\n\t\t\t\"uv-line\",\r\n\t\t\t\"uv-loading-icon\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-modal/readme.md",
    "content": "## Modal 模态框\r\n\r\n> **组件名：uv-modal**\r\n\r\n弹出模态框，常用于消息提示、消息确认、在当前页面内完成特定的交互操作。\r\n\r\n特性：支持自定义内容，与uniapp提供的API `uni.showModal` 类似，但是功能更强大，更加灵活。\r\n\r\n运用场景：弹窗验证码输入等场景\r\n\r\n# <a href=\"https://www.uvui.cn/components/modal.html\" target=\"_blank\">查看文档</a>\r\n\r\n## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>（请不要 下载插件ZIP）</small>\r\n\r\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n<a href=\"https://ext.dcloud.net.cn/plugin?name=uv-ui\" target=\"_blank\">\r\n\r\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\r\n\r\n</a>\r\n\r\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-navbar/changelog.md",
    "content": "## 1.0.7（2023-08-16）\n1. 修复ios可能存在点击返回按钮点不到的情况\n## 1.0.6（2023-08-07）\n1. 修复nvue在ios端可能存在背景图样式错乱的BUG\n## 1.0.5（2023-08-04）\n1.  bgColor设置背景图片，增加imgMode属性\n## 1.0.4（2023-08-01）\n1.  bgColor属性支持背景图片，在线图片或base64图片都可以\n## 1.0.3（2023-07-03）\r\n去除插槽判断，避免某些平台不显示的BUG\r\n## 1.0.2（2023-06-05）\r\n1. 兼容渐变背景色\r\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-navbar 自定义导航栏\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-navbar/components/uv-navbar/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 是否开启顶部安全区适配\r\n\t\tsafeAreaInsetTop: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 固定在顶部时，是否生成一个等高元素，以防止塌陷\r\n\t\tplaceholder: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否固定在顶部\r\n\t\tfixed: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 是否显示下边框\r\n\t\tborder: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 左边的图标\r\n\t\tleftIcon: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'arrow-left'\r\n\t\t},\r\n\t\t// 左边的提示文字\r\n\t\tleftText: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 左右的提示文字\r\n\t\trightText: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 右边的图标\r\n\t\trightIcon: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 标题\r\n\t\ttitle: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 背景颜色\r\n\t\tbgColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#ffffff'\r\n\t\t},\r\n\t\timgMode: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'aspectFill'\r\n\t\t},\r\n\t\t// 标题的宽度\r\n\t\ttitleWidth: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: '400rpx'\r\n\t\t},\r\n\t\t// 导航栏高度\r\n\t\theight: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: '44px'\r\n\t\t},\r\n\t\t// 左侧返回图标的大小\r\n\t\tleftIconSize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 20\r\n\t\t},\r\n\t\t// 左侧返回图标的颜色\r\n\t\tleftIconColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#303133'\r\n\t\t},\r\n\t\t// 点击左侧区域(返回图标)，是否自动返回上一页\r\n\t\tautoBack: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 标题的样式，对象或字符串\r\n\t\ttitleStyle: {\r\n\t\t\ttype: [String, Object],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t...uni.$uv?.props?.navbar\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-navbar/components/uv-navbar/uv-navbar.vue",
    "content": "<template>\r\n\t<view class=\"uv-navbar\">\r\n\t\t<view\r\n\t\t\tclass=\"uv-navbar__placeholder\"\r\n\t\t\tv-if=\"fixed && placeholder\"\r\n\t\t\t:style=\"{\r\n\t\t\t\theight: $uv.addUnit($uv.getPx(height) + $uv.sys().statusBarHeight,'px'),\r\n\t\t\t}\"\r\n\t\t></view>\r\n\t\t<view :class=\"[fixed && 'uv-navbar--fixed']\">\r\n\t\t\t<image class=\"uv-navbar--bgimg\" :src=\"bgColor\" :mode=\"imgMode\" v-if=\"isImg\" :style=\"[bgImgStyle]\"></image>\r\n\t\t\t<uv-status-bar\r\n\t\t\t\tv-if=\"safeAreaInsetTop\"\r\n\t\t\t\t:bgColor=\"getStatusbgColor\"\r\n\t\t\t></uv-status-bar>\r\n\t\t\t<view\r\n\t\t\t\tclass=\"uv-navbar__content\"\r\n\t\t\t\t:class=\"[border && 'uv-border-bottom']\"\r\n\t\t\t\t:style=\"[{\r\n\t\t\t\t\theight: $uv.addUnit(height)\r\n\t\t\t\t},getBgColor]\"\r\n\t\t\t>\r\n\t\t\t\t<view\r\n\t\t\t\t\tclass=\"uv-navbar__content__left\"\r\n\t\t\t\t\thover-class=\"uv-navbar__content__left--hover\"\r\n\t\t\t\t\thover-start-time=\"150\"\r\n\t\t\t\t\t@tap=\"leftClick\"\r\n\t\t\t\t>\r\n\t\t\t\t\t<slot name=\"left\">\r\n\t\t\t\t\t\t<uv-icon\r\n\t\t\t\t\t\t\tv-if=\"leftIcon\"\r\n\t\t\t\t\t\t\t:name=\"leftIcon\"\r\n\t\t\t\t\t\t\t:size=\"leftIconSize\"\r\n\t\t\t\t\t\t\t:color=\"leftIconColor\"\r\n\t\t\t\t\t\t></uv-icon>\r\n\t\t\t\t\t\t<text\r\n\t\t\t\t\t\t\tv-if=\"leftText\"\r\n\t\t\t\t\t\t\t:style=\"{\r\n\t\t\t\t\t\t\t\tcolor: leftIconColor\r\n\t\t\t\t\t\t\t}\"\r\n\t\t\t\t\t\t\tclass=\"uv-navbar__content__left__text\"\r\n\t\t\t\t\t\t>{{ leftText }}</text>\r\n\t\t\t\t\t</slot>\r\n\t\t\t\t</view>\r\n\t\t\t\t<slot name=\"center\">\r\n\t\t\t\t\t<text\r\n\t\t\t\t\t\tclass=\"uv-line-1 uv-navbar__content__title\"\r\n\t\t\t\t\t\t:style=\"[{\r\n\t\t\t\t\t\t\twidth: $uv.addUnit(titleWidth),\r\n\t\t\t\t\t\t\tflex: '0 1 auto'\r\n\t\t\t\t\t\t}, $uv.addStyle(titleStyle)]\"\r\n\t\t\t\t\t>{{ title }}</text>\r\n\t\t\t\t</slot>\r\n\t\t\t\t<view\r\n\t\t\t\t\tclass=\"uv-navbar__content__right\"\r\n\t\t\t\t\t@tap=\"rightClick\"\r\n\t\t\t\t>\r\n\t\t\t\t\t<slot name=\"right\">\r\n\t\t\t\t\t\t<uv-icon\r\n\t\t\t\t\t\t\tv-if=\"rightIcon\"\r\n\t\t\t\t\t\t\t:name=\"rightIcon\"\r\n\t\t\t\t\t\t\tsize=\"20\"\r\n\t\t\t\t\t\t></uv-icon>\r\n\t\t\t\t\t\t<text\r\n\t\t\t\t\t\t\tv-if=\"rightText\"\r\n\t\t\t\t\t\t\tclass=\"uv-navbar__content__right__text\"\r\n\t\t\t\t\t\t>{{ rightText }}</text>\r\n\t\t\t\t\t</slot>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * Navbar 自定义导航栏\r\n\t * @description 此组件一般用于在特殊情况下，需要自定义导航栏的时候用到，一般建议使用uni-app带的导航栏。\r\n\t * @tutorial https://www.uvui.cn/components/navbar.html\r\n\t * @property {Boolean}\t\t\tsafeAreaInsetTop\t是否开启顶部安全区适配  （默认 true ）\r\n\t * @property {Boolean}\t\t\tplaceholder\t\t\t固定在顶部时，是否生成一个等高元素，以防止塌陷 （默认 false ）\r\n\t * @property {Boolean}\t\t\tfixed\t\t\t\t导航栏是否固定在顶部 （默认 false ）\r\n\t * @property {Boolean}\t\t\tborder\t\t\t\t导航栏底部是否显示下边框 （默认 false ）\r\n\t * @property {String}\t\t\tleftIcon\t\t\t左边返回图标的名称，只能为uvui自带的图标 （默认 'arrow-left' ）\r\n\t * @property {String}\t\t\tleftText\t\t\t左边的提示文字\r\n\t * @property {String}\t\t\trightText\t\t\t右边的提示文字\r\n\t * @property {String}\t\t\trightIcon\t\t\t右边返回图标的名称，只能为uvui自带的图标\r\n\t * @property {String}\t\t\ttitle\t\t\t\t导航栏标题，如设置为空字符，将会隐藏标题占位区域\r\n\t * @property {String}\t\t\tbgColor\t\t\t\t导航栏背景设置 （默认 '#ffffff' ）\r\n\t * @property {String | Number}\ttitleWidth\t\t\t导航栏标题的最大宽度，内容超出会以省略号隐藏 （默认 '400rpx' ）\r\n\t * @property {String | Number}\theight\t\t\t\t导航栏高度(不包括状态栏高度在内，内部自动加上)（默认 '44px' ）\r\n\t * @property {String | Number}\tleftIconSize\t\t左侧返回图标的大小（默认 20px ）\r\n\t * @property {String | Number}\tleftIconColor\t\t左侧返回图标的颜色（默认 #303133 ）\r\n\t * @property {Boolean}\t        autoBack\t\t\t点击左侧区域(返回图标)，是否自动返回上一页（默认 false ）\r\n\t * @property {Object | String}\ttitleStyle\t\t\t标题的样式，对象或字符串\r\n\t * @event {Function} leftClick\t\t点击左侧区域\r\n\t * @event {Function} rightClick\t\t点击右侧区域\r\n\t * @example <uv-navbar title=\"剑未配妥，出门已是江湖\" left-text=\"返回\" right-text=\"帮助\" @click-left=\"onClickBack\" @click-right=\"onClickRight\"></uv-navbar>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-navbar',\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\tgetBgColor(){\r\n\t\t\t\tconst style = {};\r\n\t\t\t\tif(this.bgColor){\r\n\t\t\t\t\tif (this.bgColor.indexOf(\"gradient\") > -1) {// 渐变色\r\n\t\t\t\t\t\tstyle.backgroundImage = this.bgColor;\r\n\t\t\t\t\t}else if(this.isImg){\r\n\t\t\t\t\t\tstyle.background = 'transparent';\r\n\t\t\t\t\t}else {\r\n\t\t\t\t\t\tstyle.background = this.bgColor;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\treturn style;\r\n\t\t\t},\r\n\t\t\tgetStatusbgColor() {\r\n\t\t\t\tif(this.bgColor){\r\n\t\t\t\t\tif(this.isImg){\r\n\t\t\t\t\t\treturn 'transparent';\r\n\t\t\t\t\t}else {\r\n\t\t\t\t\t\treturn this.bgColor;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t// 判断传入的bgColor属性，是否图片路径，只要带有\"/\"均认为是图片形式\r\n\t\t\tisImg() {\r\n\t\t\t\tconst isBase64 = this.bgColor.indexOf('data:') > -1 && this.bgColor.indexOf('base64') > -1;\r\n\t\t\t\treturn this.bgColor.indexOf('/') !== -1 || isBase64;\r\n\t\t\t},\r\n\t\t\tbgImgStyle() {\r\n\t\t\t\tconst style = {};\r\n\t\t\t\tif(this.safeAreaInsetTop) {\r\n\t\t\t\t\tstyle.height = this.$uv.addUnit(this.$uv.sys().statusBarHeight + 44, 'px');\r\n\t\t\t\t} else {\r\n\t\t\t\t\tstyle.height = '44px';\r\n\t\t\t\t}\r\n\t\t\t\treturn style;\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\t// 点击左侧区域\r\n\t\t\tleftClick() {\r\n\t\t\t\t// 如果配置了autoBack，自动返回上一页\r\n\t\t\t\tthis.$emit('leftClick')\r\n\t\t\t\tif(this.autoBack) {\r\n\t\t\t\t\tuni.navigateBack()\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t// 点击右侧区域\r\n\t\t\trightClick() {\r\n\t\t\t\tthis.$emit('rightClick')\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n \t$show-border: 1;\r\n\t$show-border-bottom: 1;\r\n\t$show-lines: 1;\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t.uv-navbar {\r\n\t\tposition: relative;\r\n\t\t&--fixed {\r\n\t\t\tposition: fixed;\r\n\t\t\tleft: 0;\r\n\t\t\tright: 0;\r\n\t\t\ttop: 0;\r\n\t\t\tz-index: 11;\r\n\t\t}\r\n\t\t\r\n\t\t&--bgimg {\r\n\t\t\tposition: absolute;\r\n\t\t\tleft: 0;\r\n\t\t\tright: 0;\r\n\t\t\tbottom: 0;\r\n\t\t\ttop: 0;\r\n\t\t\t/* #ifndef APP-NVUE */\r\n\t\t\twidth: 100%;\r\n\t\t\theight: 100%;\r\n\t\t\t/* #endif */\r\n\t\t\t/* #ifdef APP-NVUE */\r\n\t\t\twidth: 750rpx;\r\n\t\t\t/* #endif */\r\n\t\t}\r\n\r\n\t\t&__content {\r\n\t\t\t@include flex(row);\r\n\t\t\talign-items: center;\r\n\t\t\theight: 44px;\r\n\t\t\tbackground-color: #9acafc;\r\n\t\t\tposition: relative;\r\n\t\t\tjustify-content: center;\r\n\r\n\t\t\t&__left,\r\n\t\t\t&__right {\r\n\t\t\t\tpadding: 0 13px;\r\n\t\t\t\tposition: absolute;\r\n\t\t\t\ttop: 0;\r\n\t\t\t\tbottom: 0;\r\n\t\t\t\t@include flex(row);\r\n\t\t\t\talign-items: center;\r\n\t\t\t}\r\n\r\n\t\t\t&__left {\r\n\t\t\t\tleft: 0;\r\n\t\t\t\t\r\n\t\t\t\t&--hover {\r\n\t\t\t\t\topacity: 0.7;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t&__text {\r\n\t\t\t\t\tfont-size: 15px;\r\n\t\t\t\t\tmargin-left: 3px;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t&__title {\r\n\t\t\t\ttext-align: center;\r\n\t\t\t\tfont-size: 16px;\r\n\t\t\t\tcolor: $uv-main-color;\r\n\t\t\t}\r\n\r\n\t\t\t&__right {\r\n\t\t\t\tright: 0;\r\n\r\n\t\t\t\t&__text {\r\n\t\t\t\t\tfont-size: 15px;\r\n\t\t\t\t\tmargin-left: 3px;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-navbar/package.json",
    "content": "{\r\n  \"id\": \"uv-navbar\",\r\n  \"displayName\": \"uv-navbar 自定义导航栏 全面兼容vue3+2、app、h5、小程序等多端\",\r\n  \"version\": \"1.0.7\",\r\n  \"description\": \"uv-navbar 此组件一般用于在特殊情况下，需要自定义导航栏的时候用到，一般建议使用自带的原生导航栏。\",\r\n  \"keywords\": [\r\n    \"uv-navbar\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"navbar\",\r\n    \"自定义导航栏\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-icon\",\r\n\t\t\t\"uv-status-bar\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-navbar/readme.md",
    "content": "## Navbar 自定义导航栏\r\n\r\n> **组件名：uv-navbar**\r\n\r\n此组件一般用于在特殊情况下，需要自定义导航栏的时候用到，一般建议使用自带的原生导航栏，支持渐变色、透明色、图片背景。\r\n\r\n# <a href=\"https://www.uvui.cn/components/navbar.html\" target=\"_blank\">查看文档</a>\r\n\r\n## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>（请不要 下载插件ZIP）</small>\n\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\n\n<a href=\"https://ext.dcloud.net.cn/plugin?name=uv-ui\" target=\"_blank\">\n\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\n\n</a>\n\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-no-network/changelog.md",
    "content": "## 1.0.1（2023-05-16）\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\n2. 优化部分功能\n## 1.0.0（2023-05-10）\r\nuv-no-network 无网络提示\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-no-network/components/uv-no-network/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 页面文字提示\r\n\t\ttips: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '哎呀，网络信号丢失'\r\n\t\t},\r\n\t\t// 一个z-index值，用于设置没有网络这个组件的层次，因为页面可能会有其他定位的元素层级过高，导致此组件被覆盖\r\n\t\tzIndex: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// image 没有网络的图片提示\r\n\t\timage: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAYAAAB5fY51AAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAABLKADAAQAAAABAAABLAAAAADYYILnAABAAElEQVR4Ae29CZhkV3kefNeq6m2W7tn3nl0aCbHIAgmQPGB+sLCNzSID9g9PYrAf57d/+4+DiW0cy8QBJ06c2In/PLFDHJ78+MGCGNsYgyxwIwktwEijAc1ohtmnZ+2Z7p5eq6vu9r/vuXWrq25VdVV1V3dXVX9Hmj73nv285963vvOd75yraeIEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQaD8E9PbrkvRopSMwMBBYRs+5O/yJS68cPnzYXel4tFP/jXbqjPRFEAiCQNe6Bw/6gdFn9Oy9Q90LLG2DgBBW2wyldIQIPPPCte2a5q3jtR+4ff/4wuBuXotrDwSEsNpjHKUXQODppy+udYJMEUEZgbd94DvnNwlA7YGAEFZ7jOOK78Xp06eTTkq7sxwQhmXuf/754VXl4iSstRAQwmqt8ZLWlkHg0UcD49qYfUjXfLtMtOZ7npExJu4iqZWLl7DWQUAIq3XGSlpaAYHD77q8xwuCOSUoXw8Sl0eMux977DGzQjES3AIICGG1wCBJEysj8PXnz230XXdr5RQFMYbRvWnv6w8UhMhliyGwYghr4Pjg3oEXL34ey9zyC9tiD2ml5h47dr1LN7S6CMjz/A3PvHh1Z6UyJby5EVgRhKUe7Kz/JU0LfvrJo5f+Y3MPibSuFgQGBgasYSd9l6GDsup0WS/T/9RTp9fXmU2SNwECdQ92E7S57iaMeJnPQLK6ixkDLfjlb7546RfrLkQyNBcC3dsP6oHWMd9G+V3JgwPHh7rnm1/yLQ8CbU9Y33zp0j+nZFUMb/DHmB7+SHGY3LUKAk8cObtD00xlHDrfNge+Z2ozU3c9dvx4Yr5lSL6lR6CtCWvg6OAPw9z538ZhhZRl6XrwhW8du1KX/iNejtwvPQIDR8+vSRqJ/obU7GupjdNdh2gW0ZDypJBFR6BtB2rg2OVtuub9JcmpHIpBoK1xfffLzx4f7C0XL2HNiYDp6bs9z23Ypn1fC1Y/9PCFDc3ZW2lVHIG2JKzTp4Ok7nv/G6Q054MIvda+bNb74pEgKGtwGAdL7pcfAa8vOKEZ2kyjWuLr7uDh+/qvN6o8KWdxEWhLwroyeek/g4zuqwU6kNrhyZcu/UktaSXN8iNwuL9/RuvVXtJ9PbPQ1vhmcP6t9+47u9ByJP/SIdB2hDVw9MJHQFYfrQdCph84evFX68kjaZcPAZJWwjMXRFpJ2zr91tfuvrh8vZCa54NA2xGWrunvmg8QWCJ/N4ir7fCYDxatkOeBB7an501agXbygVdvv9IK/ZQ2FiPQdi9osGbH+zRNf7y4m9Xu9Me7N9nv0HXdr5ZS4psHgXpJC9P/wDRTx0Vn1TxjWG9LGrbaUm/Fi5meSvcrkxf/Cg/ow9XqAUk91v3qHT97r6471dJKfHMi8Oyzgx1Z03t1YAQVT2MwgsC3u+yXHzi0faQ5eyGtqgWBtpOw2Ol9+/TM+sTOn8L08MtzgQCy+tOHXr3jA0JWc6HU/HF5Scssr4jXcYqfP6V/T8iq+ceyWgvbUsKKOn38eJAYyl56TAuCEr2WYei//9Crd/5GlFb81kdASVopSFrerKRlaoZj9HR+700H10+0fg+lB21NWBxe2lhNHsUpDZr27mi4dV379R9+za4/iO7Fbx8ECknLCPTsTDJ17O33bJpqnx6u7J60PWFxeAcCbMV56dJfQKf1bkMLfuGh1+76zMoe9vbuPUnLsb2DtmOe5HSxvXsrvWtLBEhaTx29+Ma27Jx0ShAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQaEsEVoQdVluO3BJ06ptHL34b1XRjp4Ch6Rq24+kmjG4Nwwg+9uA9u/73EjRBqhAEihAoe3xwUQq5WTYEzp0b3ZnV/Ncf6O/9AvY9wlh/6dy3X7ncN512Zw9BVLXjuAP4np44vnQtkZoEgVkEhLBmsWiKqwsXpjbPBOn3gRfenwnc+7GBe+zsjclvonFDS9nA9Iy/u3x9+vAP3735VPk4CRUEFhcBIazFxbfm0k9fHD7k+v4nQFaPQIrx8Gmyx/GJ0J/t7ez7mw0b9MmaC2pQQgh0/ZSm4g5TwueWWtqLt0HuVy4CQljLPPYnB0depTn+b3t+8B4t0AdBUv93h2H9xc6da0aXs2m+r1WQsLRnl7NdUvfKRkAIa5nG//r1oGtsZvjTgev/kqYHF/TA+AXoqv4npJemOEiQU1Eo2l+G0movBK1UBBPU7s9E1+ILAkuNgKwSLjXiqO/khVtvARH8dxDBRkMzPrF/V+9/BlG5y9CUqlXinHv9mRPXtvuus88L9H3JPv2zD2yXExCqAicJBIFWRwAvv3Xqwq0/Pnn+lv/K+ZvfPH3p9p5W75O0fxaBp793ce3AwIDMWmYhafiVgNtwSMsXeHp4eNXJC8Nf0PAdRCiuf/XgrnWUqsqotcvnl9DmRkCdweX4b9N7+m/ih+mbMraLM14yJVwcXItKpT1VRve+ArC3Qqn+3gM7132jKEGZm6tXg86J7OhDfuA/iHwPUpfUZSfu2L59tXxEoQxeyxkEgjKeOnLxHb4RqC+NY5H3+2953d4XlrNN7Vq3ENYij+yZwbG9jpt9GkBPQ5H9zgP9607OVeWp87cOQtn9zwJf+xDMNFfj+jryPqXpxj8c2Nn7P+SXey70lidu4IXzb0DNB4tr9751+HV7zxSHyd1CERDCWiiCc+QPjUCnsaqmZ62O5IN7N/VUNP48ee7mAZDTf4Tt049iUG4Guv4ZfNLos9UIbo7qJWoJEHjy+bP7fNsoOcnW0A0/aacef8PdG28sQTNWTBVCWIs01OfPj66BpfqTmq732UnjgT1bei+Vq4pTv7HM8Ceg2/o1qLQug7T+FaaM3IqTLZdewpoHgYEjV9fphvOj+OShWa5V+CxvZtpzv/LwG/aNl4uXsPoRwI+4uEYjAJ2GmdG8L0FK2mYa+tsrkdXZy+P7x2ZuHdW14P+BLdank9q6Qwd3rf+ckFWjR6Tx5Q2cP58K9Jm3VCIr1ogt48lO237r3//96YofeG18y9q7RFklXITxPXV+5DchKb3ZDMy37Nu5tuxG4R9cHH6b42QfAzlds+3EPXu2rfrBIjRFilwkBIIR7SHoJDurFU89ZOd680Gke6JaWomvjoBIWNUxqivFD87fej0e0n8Fwvr0/t1rnyqX+QfnRz7g+8FX8Rv8vL3auF/IqhxKzR2WCPxXqKeq3krDTdj2ierpJEUtCIgOqxaUakwzNBR0D09yiqePHOjveyOkpxLr9VMXb73V97S/h3nDXx7Y2fdPkAYbncW1IgIDxy5vM7LZt/hgrnLtxyaBrJNxv/72N+6tuNhSLp+EVUZACKsyNnXHvHL+1qcgNf2KbSXu2bt9dcmS9qlzo/fARgcmCtpzB3b1/Vg5QiuslLowENyDWDn8cSjl98PgdBviu03N+rl9/WufLEwr18uDwLdevLTF1YK3xnVZ2HI1bUxrT7z5zTuXdRP78qCyeLUKYTUI25OXbm4JPO00TBj+6I7+db8ZL3ZwMOiYdG4dA1lN9HWte2iuI2NAVPapC8O/CGPR34Ip/AZIbIMo7yX8G9QMbcS09P+2b1vf5XgdrXaPfiYns9oeLLEd8D1/B7Dp0E1jGP042pXQj7RKf546cmGzp+tv1TRf6YQD35/QO3seP3xow5IfC9QqmM23naJ0ny9ysXwgq98BWc0kVhv/Nhalbqe8kd/Fr8MOSEr3zEVWrwyO3I29hl+E9LUHGf+nAXI6sGPdd8uV2YphIKnE5IyL6bLxk7cn3bdkHHefrpvJAExMZ1uBZmqeNzXtfzUzk/m/ens7LjV7Px+8d9e1579/44l0duZtge+Np5zEEw8c2pBu9na3YvtEwmrAqNE8IZvNHsep5//yjl3r/0O8yFOXbv0QCO05gP0JGIL+fjw+uj91YeRh/Dp/PtCDM7Zpfmjvjt6Xo7hW9ycmJjaYduf7Hdf/8HTGfa3rG9rYxLSWnsloPg7fijZV8oFM2Ja2a9t6EJd7bCztvHP7us4rrdD/r3/7ct9I99jEI4cOiQ3dIg2YEFYDgOUJDFj1e8TqX7cT4kImXuQr5279A4DeBEX8ayvprU4N3rovcALot/TH13T0fXDTJn0qXk4r3k9OTm4y7a6PzjjORzOOvn1kbEqbnEprPhRzwAKzwFLHk05hv6Yd6N+o3R6beG50aPSdr3qV6IJKkVp5ITIlXOCYn4Yexr0w/DO6YXymHFlR0e5r7tsM3fxgJbI6fW1ivTeT+SsYmr54cFff+5Cu5X+hb94Merp6/J/PusGvTE6724eGJ7RpSFOkKPCUZvBPBccoHBet3Rwe13rX9tw/PjXzZ5hKvr8SfhWKkeA2REAIa4GD6p0feRdWBnvxjv2PckVhVfBf4A29uG/X2i+Ui2eYn8n8NryuDr3jPfWSFV5k44UT137eshIP2K7/64cObbheqZ6lCp+Ydt8TBO7vTM5od1+/NR4SFVhoLpKKt410lnE8LTMzo3V2dLznxLkhYgQ9obiVjEDln7mVjEodfYcpw+MAsftg/7qSDbAnb97sCSb0Yei2fqOcbovVqKNnNO8HmAE9Cv3Wp+uoWjt27HpXNqH9WTKR+kBHKqEFbvo5y3N/avfu4g23R45f3WGa1k9ZicTd0zPTf/f6O7f8dT311Jp2fHzmgJlI/N70jPPe4bEZ6Kg4qw0lqlrLiNKBiLWerpTW25PUbkPXZViW62ecHz+4d8PXojTirzwEyhq8rTwYFtRjvpX/rlwJ+iSXugPbMuyKBOHo3geRJtuT7PujcmVUCuPJlhnL/9NUqvMD2eyM5sxMaIlE4n7XML907tyNjcxHQjty4sZv66Z1xEok/xNW5n4uZSf+8sT5m++vVO58wkEu5sR09pd9w/rWyET2vReujiqygrSopn/zKZN5qMeirotKeTyolm7p/+X06Wvr51ue5Gt9BISwFjiGsLl6N6SrvylXDNTK70D4mX071pwtF88w6Jd/DG/1E1u26NOV0pQL71y3/8PJVOcHMzPTWkcCH2YGOaTTaS2RTN6f1fQvvvDK1bdnbO2JZCr1SeRfn05Pa1PTU0gXJBKW+ecnzlxvCGndhFQ1NRP8bcY1/vjS9bF1V26MwHwsVKiXa3etYVw1TNhYJ3TDjQCO42jJVMcez7J+t9YyJF37ISCEtahjGjxkGDr2DJZ31D8h5vUQJL5RPkXlUMM07u3qSGidICvkzzuSlmlZb0olrK9hD9v9JCrPC196JoPMAolFg6CV+PPj54YeyWecx8Vk2v1Q0rSfhFT18LnBmzBRyNalp5qrSuq7kiAsh4SFa7oZ9M0wzI+cPHOjZPo9V1kS1z4ICGEt4lhiCvZrSa2jol7qzPXJPk6nIGbVbWfUvcr7hO9MP97ZVXpggOu6ajplYStj7l1XvbRMXbPAbp6HzSSBlkraNknrvfVCcPt2sHYi7f3pTDb47KUbYxuvKqkKpYBXKBnV869c3WgbDEixAck0FGFFfEzJzbIsO9C1TyrcymWWsLZGIHoW2rqTzdo5dXyykz0NC8l779i5vu4zwM+eHVntGP5jqVTq/6AkVc5NZ3wNH2lVxNWZNIukMSjiNd9z0+CHp5DXAdX4SAg203w8GB5IATtODHzdK8C15kEjhXvNS9rWA11dnfcMDY9prscss48RySakrOLWqODCoIKAgkuVgsS0urtD60haeV1YYVbbtjUn6/74HXvW/11huFy3PwKzT1r797Upe3jq4sib9u9Y+wxe+vh7W1N7jx49v6ZzbffnQD4/Cj1Pfjx54XiBls6GVuTUc9mQsOIO9mPQFdkIRlz4fy5JLm2ZMOqTcJaXIqpcqnixVe+rdbZ3dbc2OT0D0wZIibHSksmklslknvx+//q3PiKnXcTQae/b+LPQ3r1t0969cOL6G7o6E09qgZegdMJBpVQ1DbKCpyUt6oPKz/4NEJalCAuZFIuEVBJd+jgLh4rvAiFqUVGkhJZMWFp3Z0obGSu/d5gSnWmavuO6h+/cvYHSobgVgoAYjrb4QPMUiGtj1/79jBMkLBwiTlMASlYzTkhWCJyTrGAyMOFkst/BoYMmuIIyGJYcMXMMdNwHPhYN1qWS1t6ZLGaKZL8yzFXTr15BooLLMugHMBRNKgW+It8y9TEcJGt4rvcRFCCEVQbFdg0Swmrxkb0+cf2XOzq73kgdFieEXF2jdEUJKQH6SVWQrNjtZDKlpTPp38U58iUbthk/Ph7sN6zg/xudSGvD4xkq6otcnnjyF0XRRTflkyC0IIJE1JG0QbqGNpMNp5xFhRTcZDNoj66988SFm5vv3LX+WkGUXLYxAuXnCW3c4XbqGs9hwjv+a9lsuN+ahOJSCoLjNDAFvVUll0p1aNPp6adTweSflEszPO48oFn+4yOTmR+6enOshKyYhzWpf/jDuuf6x2aV/qNRaPG/1d0gUXWCA0uu7GhMmkqmerEc8KOVU0lMuyFQ+Ylut562YX9Sncmf7Ojo3BDZWbGLtMkiUVXSWTFNuMqWuYG530f7+/tnGFboxsfdd9mm8XdDo9O7rg6NFq0CFqZr5DWlK9qV0fZqGvZchSuPlevB2VmG/hOV4yWm3RAQwmrhEcW64qu4ykfJho52Vp3J8quBYQooqWDKADftBd6HD+5efyoKj/zR8ew/hWXY56/cnFh7a3RCTTGjuMX0SVB9qzu1qfQM+jO3dBW1g6uVSHv/qVNX10Vh4rc3AkJYLTy+WA/8ou9kJjo7bOh+DLVFZ64TEbCyBktxI5PJZj56R//Gx+NdH5vM4vuI+p8NXh9LjU1iw3EZhXc8TyPuuV9wDaaCfBjTM06N0hVWQmHBDzvSDZ5tvqYR7ZAymh8BIazmH6OKLbzv0KZvJEz3ZzEFnEolaEtV2XEaCLKadrIz//TQnk1/EU85NuH8th8Yf4j9gMZUOrNkZEVZCnsbtTU9KW18GqcKFyjh420sd2+j33pg3F8uTsLaDwEhrBYf04O7N/2t7/o/C2FoGnsIy/YGlvAwSfCvZzLOe+8oR1ZT3u/5uvHJC9dGtJlMrfqjslXVHwjpat2aLi2rjFFLjUSrFUjlO0juddXSSXx7ICCE1QbjiHO0/hofbPgwpnDTOR2V6hWNQqGUx34890noet5yaO+Gko3Y45PO7/uB/lvnrwxrWdha1absbgxo1FWtwplXqYSJY5Nn5lU3bLHQmGA/yko0plVSSjMjIITVzKNTR9sO7dv8RSeb/T9BWmMkKv4D+YzBXuljV7yxd+zfte6VeHGKrHTz4+cv38JWmyUmKzSGG5z7VndoE7kz3uPtq+Welvhwm39weVjOyaoFsBZPI4TV4gNY2Pw79mz8KyebeRIH+VEZTaX0sf27+v794TKmCxNTzr/2NOPj5wZBVjjdYSklq6jN69dyKuhqmWztivYob+RTSkPbe/xMdlMUJn77IiCE1W5jq+s4dYEO6mzsYAmvi/+CrH7LDYxPcBq4HGTFVcG1ULLT5orS1ULIkoSFI2cMHKG8obiXcteOCAhhtdmo6gaOh4EWWlkyYU9gvHswXfgV19d/7+LVkSWfBrItJJhObL/p7elQR8fUZnEV70XxPc01sM+xrzhU7toRgZIHuh07uZL6xA3LBaYB+Ar8rBsfz34YX1j+D5eu317QNGy2xPquSE4mDuXb2IujY2AgytNE67RiKFshzuwCR5s9ZSMlsK0QEMJqq+GkBKOF5yFzRoidK5BoFCeMjM/8mG+a//Xy0Li55KYLBRiTrGjwOQ1br4VMBQuKVJeQKVPxMLlvPwSEsNpsTEECmBLSgbHUpwD1YGwse59l2p+9fmuig4fiNZIowrqq/6Xeqm9Vh9JbjcOKvqFtACX7gV8kTVZvkaRoRQSEsFpx1OZoM2iKxxuHLtDcsZlgLzYZfv7m7XSv+r7fIm234XSP/8o5ktWqzqSyZr89PoXPYDTYkZvziw0NLluKayoEyq4iNVULpTF1IaDjHHZmoAW4aep9geN8fiLt998cGYdtVp7K6iqzXGJFUCAi7jdkuapsBJKcPBwgyP8YRyV7B04Q3dDbpY3jg6gupoMNla5U41BbUN9n0sr1ScKaHwEhrOYfo7paCAW0WiWknihhW/0Tabf/6tDtxpIVSIhGnz1dSXUkDL8fSHKi4/lWPId9Kp3Vxqegp8J/m9f14D6DQ/nmb281FwgkZ1Dj7bnSSFx7ICCE1R7jmO8FJJr8jCvjeNrIxFjDJBpKVaSlXhwDw384MyucBoLAGEfHI5ptO6n1YAq4FjorH9IWjUOnFlF3pj62aui3whbI33ZGQAir/UY3XCVEvzgdw/8NcSyGUhSlpVWQrFg2p39xp0JYLyIohaXxdZ2FGofG6yi85/QS32F0Asu8URgu1+2JgCjd22xcsVElPC85169Gaa1YTkRWJKpSqooBiQQzONvq9sRULKKxtzzAEJw1api2EFZjoW3K0oSwmnJY5tcoSD09HanEDztubnfO/IopyUWC6sUmZUpW5aSqkgwgK04DxxaZrFivacCaIdAuH9zaM1rSDgloOwSEsNpoSMenvU93dXb+EE5taFivKElRqd67qrNmsqIF+yjMF/i56MV2JqadYKxXMDXM6+4Wu04pf/kQEMJaPuwbWvPticwj4Il/NnTrdl7JrqaDC5wTUle1GmdWWVCw1+JotjA6PgnThsIdQrXknF8arkJi/+R355dbcrUaArU9ha3WqxXW3tHR9C5dN//T9eEJ3aGdUwP7T0V7F86Mr0VW4mF6o2NTS/ilaB2HDmb8wA2+08AuS1FNjIAQVhMPTi1NgwRkGKbxRxMz3uaJSRzVUkumOtLwo6Zc7aOkVdEhynN9NQ1cyuNqeEqD67mX9TXGyxXbJhFthYAQVosP58S0909czfqJqzdGODVqaG/IUbCWr2p0yukfp4FUtDfeir1yl8IPUGjPHFy/fqJyKolpJwSEsFp4NEfT6Z3YBvOp8MvMc0hAi9hHNQ1cBrJil5TUZxhfXsTuSdFNhoAQVpMNSD3NMTzzU1PZYAM/ProYkg3UV5rHT8lXmA7SwnwEq4FLLVkRI04HM+n0LdvzvlEPZpK2tREQwmrR8ZucCd7hePr7rw2N5PfxLUZXON1zHKz4kb0KnIttP6Njk8tyaimbwXPrsW/yq3v3bhoqaJZctjkCQlgtOMCYCnU4GedTI+NpQ32XbxH7QOmKG5nzdIWZJz8HNkKygqI9TmSL2JSiovGVn0A39c8WBcpN2yMghNWCQ4zPc0HRbr6GEs6chJFnmfl3knZO4/hmII1B6fiFG9br0s6qAeXPp2WUrhzHeXH/jr6n5pNf8rQuAkJYLTZ2kK7Wul7w6zeGx9DyUsZovOodOizosTg1TM9k1Wogpa7lIisOF+w48E/7E5B1Y/cgtdizsBKbK6c1tNioT6X9n3MDcyePOo7OoJqrC6S0+ZIYV+GSOHxvc18PJCxXG4ed13I727axqTp9yk9rX1jutkj9S4+ASFhLj/m8axwdDdbgELxfGsLpoZyqVXPVU1QugVJUV0dC27p+FaaBWWxknq6ceAljTNMiAf/BoUMbJpewWqmqSRAQCatJBqKWZpgJ731Zx9pJM4aK0hXe5vlKVFEbKFlxs3PvqpSSqpbzKztRm+gnEkktnU6/2GFMfa4wXK5XDgJCWC0y1iAR6/Z49iOjY7C5qkG6mk+3SFQGlEP8FFdnygrNFqBsn1OxP5+K5pGHbcBhqhT8fqu/v39mHkVIljZAQAirRQYx7Wj3Zj3tddQjVVJ4l50CMjHe8mqOTJCCvmoTyIrENXx7Uinbm4Gs2PZUqkObnp76i0N7N36tWl8kvn0RaGnCGhgILKPn3B3+xKVXDh8+nPseX3sOlpt13+P4uonv71WeDqLr1ampFB8S1JrulNaHc9rTMxltcpofOeWns0rTLkeIZUHRnpm5YibMf7kc9UudzYNAyyrd8ZLpWvfgQT8w+oyevXeo++bBtaEtQd9s1/ffRsV3I6eDJCp+nourgH04UZQnhIYfWm1o8xdUGCU8/E/bil89sH3dlQUVJplbHoGWJaxnXri2HTvd1nEEcCBS3z++MLi75UejQgcmJjL92ax/gNJPo6QekhVXAbdvXI3D+XQ1Bcxiu02zTAEjKFIdHTQS/S8Hd2/4YhQm/spFoCUJ6+mnL651gkwRQRmBt33gO+c3teNQYin/oG6aKX5rcKEukqqoWN+Ij5vy81v8UATDG0WGC21jlJ96K6wKPpWd8H8jChN/ZSPQcoR1+vTppJPS7iw3bIZl7n/++eFV5eJaOczX9Z2YvM1LPxWpocBHKv8qHHdMqSphGUqqahaThfj40ITBcbLnsDj6oXvu2bS4n96JVy73TYtASxHWo48GxrUx+5Cu+XY5RH3PMzLGxF0ktXLxrRoGNVPPfNtOolIrgElLGYH2wbZqcipdIFVFlDbfGhqfj9bskCaHHS/7gTt3r73Y+BqkxFZFoKUI6/C7Lu/Bl1jmlKB8PUhcHjHufuyxx/g5lbZw+BL7bX4EoiZqyS0T0uM0j1+82QSl+ua+bhxj7GjD2LicwWkLzaarigbKsmDJ7gcTmezMBw/t3ixntUfAiK8QaBmzhq8/f26j77pbaxo3w+jetPf1B5D2RE3pmzyR4/nH+Mti4Wx1dUrCHO0lSVGqskFUnakkpn6mhu086jgYHkWTW3Wbo4Tli6L5gqYHE47vfeDufVv+YflaIjU3KwItIWEdO3a9Szc0ElDNDqcLbHjmxas7a87QxAnX9ljfxcr+Mzs29ykpi1O8iJjoR/cm5o7dnUl89LRLW93dyWmVIip+Kp7pmlWqIvQ8Mga9Gslm3Efu3LX+K008HNK0ZUSgplnGMrZPGxgYsIKeXa/TA61jPu0w0+7xBx/cd3M+eZspD0wbDgWm+RXP13cODY/jWGKuGAb48jG+agNpilbqlKZoWDqDY2AyjtNUlupzYZlKpXgaxIVMNv0zd+/d+uxcaSVuZSPQ/IT13TN34QRvZW81n6HSDdMLUqmjh9tgd//Fi8OHEl3JL3Z2dh3MzGA7XU664llVWRz/QhLjNYmsmaWp/DjCjqIDdlaZTOZZ1/A+fGj7hjP5OLkQBMog0NSE9cSRszuswNhdpt31BRnazM3U9IuPHDrUuG+419eChqU+cvzqjp7u5P9KJpMPpqc51Zv9QntLkFQBEqZluVCw/7nhaP9i376+8YIouRQEyiLQtIQ1cPT8GjOw7vE8tyFtxBrb2MBXdh579FF99g0vC0nzB548ebNHT2l/aFmJj1BPBYyav9EFLaQ+jdPAVNL8/pZ13a8qiJLLOhAAjvrTRy/d0enbF+69d0tzHFhWR/vnk7Rple6mp+9uFFkRGF8LVj/08IUN8wGp2fIcPLh+4sCu9R+F3ucj0MLf4vaVVnChqYWmdaQS2jpY2vd0djh86Vqh7c3Yxm8dudTPxaW0lrn7yJEjZW0Tm7HdC2lT0xKW1xecgHE3FDWNcb7uDh6+r/96Y0prjlIO7ur7TOD5b3ayzt9ylY0Gl83qKFXZsCXrXdOlrV3djf2LBr556JOshLDmMWhPPXV6vav5O5jVxYLUhNl3iIbV8yiqpbI0bQcP85C2Xu0l3dczC0XUN4Pzb71339mFltOM+Q/0rzu5f2fvu1zH+QDOt3uZ0pbVRMRFouJK5qqeTkhVqyBdtdUmhGV5JI4cudrpd5kHiyp3tTU/8s6r+4rC2vCmaQmLWJO0Ep65INJK2tbpt75298U2HLuiLh3oX/95L+0/kHUyvwTieiUJHVEimVzy1UKeWMqv2pCoKEVFRNXT1aHawnBx80eAZj7TwcxdAc5Gi5fiaNnNT37nCk4xaV/X1IRF2B94YHt63qQVaCcfePX2K+07fMU9U7qtHev+xE/7r3cc70O+6w1gxuV0dHZiusgvJS/O7IskRXLs6KCxqj+B26t9a3uUREWi4plbQlTFYzXvu+7tB3EIUGel/L6e3TNw5NS8zYAqldss4YvzBC9C7559drAja3qvDoyg6pwCP+KBZaVOPPjazS1vMLpQKE9fuPnawDB+EqehPwzWuAuSl8LPg90WVxhJJPWQCUmPBAWTBEz1TFUGpqO3wYYvIPgr2az35a2b1/50V6f1e1NTlVcvEzB0xRekj67usu5FmS2/crvQcaol/zeeObfTSOj91dIq28PxiaOHDx9quy8LtQxhcZBqIS0Dhkl2l/3yA4e2j1Qb2JUUD1Iyz1waOQib0vsxKXsAFvH3wMB0JySwtZC+DBPTN5BOCEnhrI1BuKe9l6tIzsVCiD6E0DOabrwI2elZ09aP7N3aNxjheXvK+a1OENa0EFYEyYL9rz072Ju03ZpNQKj7Xd899cKhNrA9LASvZTY/s9GcHoK0XsrakLS8UklLxyl+/rj+/Qfu2367sJNyTS7SuZfneO7ffweBGScu3NwAqWgrTvTc5jjBZmw87tMCfRXYKQWOgula4OiBOQUZ7DZuhrAGdQXxV0zPuCaGnkv3VPGHOpPw7+QPR62OM5HhdNddGOeX2kmCbSnC4mDlSStVTFr4eLljdHV+702vWz9R66Cu5HS5h5hmHvz3QiOxwJTRo2BGgY06dm7OVhewYGAY6s75oD+ZDs4JPY9JyqSCQ7ABqftd5VFM3/j2Ja4mtsWpJQSq6ZXu5UZTKeJnsHpohiYPRqBn04nkS2+CQWW59BK2dAjwS0Y4IHDz2ERWG8Gnwm7iK9W3sFmbvrqGPzw6gW8eTmvTM07XmTPX28KYd7EQ3rjnvv1QFHbPt3zT9DcMPHd+13zzN1s+/hC2rKOo7NjeQdsxT5LEWrYjbdLw05eHtwWe9jl0542u62HZHZIVpalY/yIlP5X3MHYddLLZfy4fmYiBhNuB509vw+rG3tKY+kOwGHLi7W/cS91jS7v4s9TSnZHGLx8CICH9lXNDX+zpWfXuycnaBV2e3e567nAm4973qv0bzy1fD5qr5oEB7KXt0u7B3Loh7yhWVfypbOalh9+wr6U3mbfklLC5Hi1pDRE4ef7Wj+EEiZ+amqpvJT2bzWjJRLIPR3n9riA5i4DZg720DSIrlsrvHXSZ9p7ZGlrzSgirNcetqVp9/vz5FJTqj6JRejTdq6eBMzNpHP9s//QrF4bvrydfO6f1JrCX1mvcXlo98Kembjotr3wXwmrnp36J+pYNeh5JdqRem83O77gxkpxtW3bgOZ/g1HKJmt3U1Rw+3D+zrc89aunagnWzpq6PdxujLz388L4F78tdbtCEsJZ7BFq8/sHBoMPX/I9hyrGgnuDUUZzrnnz7yQu3HlxQQW2Ued++fZmJ1e5LoPB5k5ZpWCPXz+08du+99zrtAI0QVjuM4jL2YcIZeh+2+9wF49MFtYJSlgmHE0g/JlLWLJQPg7RmhtyXsJ18eja0tivsXhj6xy9ve/mRR5TRcG2ZmjyViN9NPkDN3Dz1FW5z9XM4i+s1ME1YcFNpUIrVLHzJzHnwjl0bn1twgW1UwPHjxxPXpztejR0HFTc+F3YXRwxdfdM9W08D0zrs4wtLaM5rkbCac1xaolWOvurhZIPIih0OdVm2haNTfqUlAFjCRnJP4HBn+iUqz6tVa2nGpTe/etsP2o2s2G8hrGqjL/FlEQC5GHghfplSUSMdvwaEA/9+4vjpa3c2stx2KIsfUek2dr+EuXNF2xEjSJx98w/tbFt7NiGsdniSl6EPp84O3W/Z1oPzXRms1GRKWdCJdeCIlJ+vlGYlh997r+70+EPH8NHJEtLCauCph+7bmj81ox1xEsJqx1Fdij4Zxi9AT2KSYBrtslgxhOD2gWOyz7AstFzx6zFHj1mGobYUYAgC9cHge3ddK5uhjQKFsNpoMJeqK6+8cm0X6noXiWUxHA8WxAdWNyQM45HFKL8dyiRpueM7jllmMGpnjO+1w9fNaxmXxiogaqlR0jQdAkeOBPjczrnOiQ6jw88ESSOA6KT7iQzOHEvavu1pZsLQg4QPP/DdZG9Xx/vWrOr+mfR03SvtNffdxleAQIgvTzjBT0w409Mpu2faufZy+vDhw5WPMa25dEnYqggIYbXqyNXY7i/jCyvdfmaVb5hdVsLp9LJGp43j1/1A7/RdvdMwPRzEboRnLVHe9vEvL3eXBOB4ZMta22H+TiqV2LJQ26u5u6Bju44Z3J7O/Lvp6cwPmBanOwQ4uNHRTWMK21bSvh1Mm642nTWCtKkH07rnTE72aOO0XZq7bIltVQSEsFp15HLthg5J/+aJE12m3tVjOPYq1/dW4cTjHnwMYhXOce8xDd3y/PJW6OpMdsTRVy4iK/rKMR/jwvz825VIHFzT3fkx13UW/dnhRy3GJyeeHEs7n1XNibUPFvY6vtGDw5vV9w0Vofn81qGhZfDhi3HX8SfQ/3HPMse9CWcCX0gel2OIFJIt+2fRH7qWRaYJG85NxldGzV4tGayFSLQ24+q9ULyu9gJfMU5ELTn6wUISTl03NHz1KzyiJLqmX657OLLdSJgoXTO7cBxyN172blier4YCvBsFdSNXV2dC35tKJrbzfPfFdjwvC/qs9MSMxxNRsSqmT6LhUDQHE+jUBE7UnATXTuLsrRn01K2l/x6+qItiR3TNG8V59KNB0DGSfNXGUXwJY2Gm+osNhpSvEBDCasIHgVLTt75/aQ0MnXpBNb2QgNYEntfr4wu/nBYpKQLtxtdwAh0SBX3VDe7nM/Ha5vf1Fb/CURS2bCTAWWuxR229qRsbQQQbUed61LfW14JVKKsTJ5sk8WUcHbtlNANyTOhgcmAGKH7p3m1FWpqtuZCu+LByVdKHVMjpKEQrBwIW9tnpXOIH+QTDSH/D9f0bmCLewDn1I4HmwtAypPDZ/oe9oXKf/aMPsWxSs/RR13FHrURiZE1gDR86tKHEdCDMKX+XCwEhrOVCvqBeHNaW6ui11/mWDtLQ1kEiWodXE4rwYgepAPssTPCMOjIdAk94TZ8pMZjch8HjDorGFUTUAwlkh64be0A9/ZCatiDZWtOyE7ClQmIdJICJFYhA+TRV4Fo5/QIHiUvrTEbkVRCxiJfsSBbfYk87OTExXxdazY5yUgiRKfpHQ1YSkONmAZY+gV4NIeVFfCXoLNA5h/Plb5LzWAyzF+IVXdNnvO/6GcsyhjC1vmWZ7s2pO3fdOqzriy9asnJxZREoerDLppDAhiIAEtCfO3F5rW0a6z1PX4/nf53nG5RqqrpieSnULEVh8cx4E7ugH78H8tG9eP/24oVezY+pkpA8b/abhPF8le75BqdsXUtaFeaTlTI2IByEoU1l8oq1mkokcZHElIRoWmpejMMCMyCvQXyy7JjjuUcgOl4tLCzCMpTHgFpcgkViX/dH/ax2Szf8m2Yqc/MN+1r7BM/C/rfCtRDWEozSkbMjq7NTY5t13dqE6dhG3wsSqlp+C9DDi0ifLrqmT1f6BgUaPjiHN0lJAGAfvpWcI4XjiHIMF6ocO/EjmMa9HeelQ1LT1PRpoce/sJwOTCQtc+kfGQp6Uxl+9JWtmL+jNEaJ0gKBgbsygR58B4sHfwV5aliVWg3vCHv6ymHcdG868IzrVsK6pnd71+/dsmXxbD3m3/W2ybn0T1/bQFe5I8euX+9ybuqbXMPbDA7ZCKV4uMOecyz+9OfmWvj9x9zEw6JW+JuOX298WhE6qtwLEV3TL1tb/AWj7sqwfqaro/sdmcyM+vBp2XzzDEzaBiQsNH+e+eeTjQ+ohwqnG0BYhfVzNYKrkOmpyauYYH8KvD8G6RPBszrC6Jq+ystl0ghzXEZjR5+O4+iZwTh+eG7Yqa5rq/3hGzzTSkXKn4YgIITVABjBP+ZzP7i8ydasrZCetuCHvIvFRs92SEdlpnCYE2LOQi12OA7RNf1yjrphHIyE9yOXPnfNMDg70DpdTf8DWDKs5rRvMVwChAWrUgh21HzllD0NrigqlxKVC7bKQuOOWeGiuI7OTkhb6T8C/Xw3xkel9cXxj6eIxiY3Hhx3X9dHsWJwDaa3l1+zd9Mt/F4tUk/ijWnP+/DBb8++LWqvnh0c7NDGta0pO7kl6zpb8AJzEUr91kYEFdeBRCt69Nm4+AsSl6jwjVGckY6VwPwUpLhLURx9xliWvxFHi/w+zB0SWCnLsVpxnoXesSI2ngp4zmRJXPgf/0IleGH51R6uwjeX5MR76qtITh7+8N9Cp4GF7Sm8Zl1s35pVXVomm/5c1vG+Wm284njHJeJq44/FjixUAld8w7uijW6+xo3MhW2S6+oIVHumqpewglJ87+LFtcFUcqur+1vxwPcZJqYPMOyhXw6GKI4+4/GwQpjCBhe+6XDIpFb06PM+np5hhS5eXzw9bLJ2pBLGv4Fe36BU4kA6IQGw8MUY6MJywVeqDs54Z69zrWdY7jI3G1ZtUiSV6zzDI3IqLLew/wu9jspl+yywrA1pEed5QceXPT3jBb/DLrA5ua5UHZ/4eMTbFx+fwvE3DJO8fANrjlctL7giJhRx9MrfR89R+VgJ1Y6currONuwd0FNsxwtV02mPlWGLy1TxlPHf6Hh8PH9xesvw9yRM+5PIRT2ZIgVKKZxWUY/PT8aTFPji0i3m4Ed1hDWV/7uY9bNGtiGqAyorJRWSqCgdkrQiR5KddrwPlsq8xfhG6efvx8dvtiQczDdmmPaldDBxSVYeZ3GJXxUMWzxq5d4fPz7Ym7X1HTAL2A7NqtJHEQ3qtCPjw3LoxB/v+OMZ5VVzR5aHWRuErYA+y4uu6fM+Xl9J/lh7bFvbY+vmv0bWos9tsXAWSLIiaSnyApHxJz6SbFSFuXTw8i86r5vVRW1m+6IHmUREAuI0lcREP5q2ztWPrO9/YK54xsXHI56+cePvj3qBfimZNS+J5FWMcrjptThsRd4dPX9+DcwEd5iQphwozfkCwJKaLv9ewHYKeicfSudwShcnJDBBOD3MTwGRO0cqLIj73jQTaejDBYaPHTBgJ/i5+HyYijd95sFhRzkzB7yL2IrCtGwezj9nOQVTUlfPwiicifnu5J0qHHd8mXHIG6ZD7JQqIk9kJK6QwAokMWRUhMaSeJ0vcfaiXNhs7PyuwpYV51Vh+EM/Pu2M9GckpyiOuZm2Wvtom+Y4me8xPbvIIujzPu6Wbvyt1ejL3U7Sv/v754ZHsORwaX3KGdwiJhO5pzY+Mivk/urVq52jTnIXlEc78LKu8qAMx/G8kHhyOicosz0ovM3IrIDKb15HSvDoOoqv+hMLYCOWI8ash0vmufryZVcqLz4u8fym3ov1xT/EVp4UDUTn4/iS0xW+sZTMojASmLqGp64iH4FRXJQ2TKj+lv7JVRTVxwQkm9APyaboGnGMzSVR6VR87ipsVT645ovOzi5tamb6zzB1/nqzjz+s9YetwLioZW5C8jq08K9+1IxS8yQsfF6ap1WL2BK8VOaJc6NbPcPrx7wJ++hmHQUPvOaQgMJ3ETtVlERDP0wVsQ19uPgcLQyt/Dc+p4jlL6k/1xa2qVyh5ApEzEoErm/DsPOTXV3de6anq36roFyRdYWVbVSshHJEMt98saIXfIu9koplYZL6m/hUz7kS/Jt0/PE8+Jj6X/Y6k+fv2tA1BKIvB/OC8WnGAmp5dpqx3XW36fjgYK/upXbhFd+BrRlqn16MfkrspkoC4hnirYjbUVWzs4rHx8uL3cerjwt0TA4RcBcsuX8Rn97q54okVsCKJJ9YkSvy1gJR4aOtnAr6OJP+L13d+BKBKMEzHhAfgDh6yzD+vqHjTDDvYpAxLqwEfVdbE9bpIEi6V27tdLP+LnzPrWS/XrRTnz5d4e79+LNY7r4kP+Z7Jv7z1LyPL0B4Tb+ci9cXLy+eJ54e8Rw//rqqcUR+HOrgYVprJbBl5E2w63oI64J7k8mUDZLGhmAXs19ucVkxP8gKQu4ptCxbMy2TW3KAGI4u1P207ztH3CDx/7bL+Cdse8h1Zy5ev7Dp8uHD7blJuy0J69TV8XW6l92Dl3cbLG6g98idbhDgdANcY1ZY9o2N4mpNr96GRf1Da3Wui0RW69F1bWslvp81LD2xDTOGu9DhQzBc7AcYfYlkAqo6A6ozqHNBYJTESGitTGShsp0qQSxT4AcoPJQw0LBlEPhBFakHDjoLvY+XgVIyg7WK77tG8n9pvpHXBbXL+OMBd7FN6KLu+uf27esbX9RHdIkLbxvCGhgYsDb3v2a7obt7YHakpKmYiqgE2ioqJbzIOszXcSov/DAzRRNehyJKvPx4+igv/ZLKEaCkoZxUFMYXE1I8f7Xyq/UHp9CkAlfbCF3NdlhS7IQguA0N2wiJYy1ktC5IISb1Okr5jSYruy2SGlYkIkKLSC3yy/WrUWGzSnjaTUX/QEhYQuNewLCdwBFKRkpOuAfr4sBnwwfDg6B0MHagORhBHNqHw5WxTwYav6lAt/42MBLfrYZXHO9w3Ftr/B0Hp0pY+tkD29ddAz5ln8NGjddSlNPyhHV8aKjbzAS7Dd3egRcvgRHJWyrHASw9Pyp+vlSxEluH0jWAGQF9VVZMpxHVRZ/xSKQU4PR5Xy0+/sLQZCFS9DN/XKtSeh5WrL2x+sMyZv+W67+vwz5eC7oDx12rm9pakNg639B68XL3Qh+2Bm94DySxHhg0daBHSQhiCbyyyMS9SDi8RhEHyYP1qD9qak0S4VGn5VYrSTRKEkKHWYYiHuQmCYb/YKYLqS+3H5LYckxJmz6qhSYJ5yNgzgtuclESpncBfN8Fj3lgJdCSGpHcGECoxrouMoHjzO+4evLLMB1VKxJV8Wyj8Q80Ix043jnTu32hlTdkh08Yn7UWcnio9Qs3pzZm0lN7LCOxIdIZxbuQ1+lAVFFxJB7aMeUIiPkiPRPjo2v6dPF4FVjHnxi/oQK0Az/bymf5uI7ayGLj6eM63nrbF5VNXzV7nv3HViQL3JAEaSV1z0iBNJIgJBCYkSKJYbdjEiSHw7a0BI5s6QBBbINUswMUsQ6E11UojZGccA9dcZDBdQY+TgyFTgkiEKYyIBvstAQzIRk8cBJ+A2j4gZFDFWAqjAp3V5IhQYYwwUJ57ByS0QINzMYK8FyrRxt3KNbXb2qG/UVNT5wDyCt6/A0boGbdqzPA4tD21SPquWihPy1FWHjQzYs3xnZkM95ePIZd8RccBx1xez/UPowp46I4+uVcLD9/8Plq0Gfy6Jp+uez5uqPyY+UtNN5DuVQc06drpv4bIDXsjtsMpdkOSC79QK4Xog3PzwF4IBNCBiIhpBSpoE8jioqWaM2KCRuOqwLXgIQItKIe0lCYD/lZjoqgGIo0+J++SsmMKA8eqQ21qHuUh2PfzQHN6vgG6vVK8GfmQhcbr3Yff+AEi3rtdCtNF8u/eIWD2ATXx4Mg0XH1Vr/hm7sDQw8PvyvTrriKWocEE0C6oM/kJRJHrAykgj6WGlq+JUifu6YfS6pu4/UVa6AgQcXKi78ApekhcWFBwMstEkTX9MvVHw+Lt2ex+4+Pg62CxgsHEwZbAdgWIJfA+ICkfDRYtyAwWWB7Ay8F8VT/KB0bOJ4Gx/CQfUKSwZGrJJs8iZHYgB0zMB+zk8hopQ8hEcEog2ERASIBAOL5fIrVIKLxXKtzKPZLgZUckvGf+/nH5HsK0+Uz3316zeAjj3D23Lwu90w0ZwNpiZ72UnvwfO/AXIFnXfLBxLOsHn6yiLqmr3oQ04LHX9hq6TFHI6txrlYWkHj98UT1lh8vryR/rIKq6aO204drdP8hRWF3itmLUw42QnW1CSTSA2IAIXkWOBYKLWw8wjVqNkEaFqjFwLQNJhWI4ZiFoiq6QX0SbsEo6HMoWVFCYprwjw6FP65BXCSoXJwiOwpnFK9A6yiWkQhRDwA9XAfpwLS/AqnqSKP7jwapquiznXFXMn6x8Yg/X/HySvLHKqiaPlZfvf0H6BloAM/v3tpzHkJwUx59Uxb4GE5Lfnt2ZGS16SX3+F5mq4llfegtwnaSR6J5EC8hPUV6IDaS6aDnoZ5DpYe6AtdgOr4pyhXLNPH0KKCo/DDP7N+S+mI6qHzbQr7AbdgW+iylWn0l5cf6E29ftfSN6L9lGl04x30tOtMHklmLhxpClW9BL4S1T+i2uNPRp+0FflD0AN9A9LHnmHGBBfJCE3QL9ALiguoJqiu+64gDzWGIIAlhzhaSDsMV/yjJi3BxyY9khP9BXBSzEMY/AFORGMmM1yyKZfmm+ZKuJf4uMHV1THEj+o+S864E7zYd/8Dliqp2MamvPbt9uw4dY/M4DnXTuMuXx/scK9iHLcbryzfKwvOJBSGNPl10Tb8WV0xYyMFymDdXXv46Kq+ueChJQI4WlSUqf8StOf5CNdXqr9afxe8/Gm6AoLAqGKyCGLSG350ACFzKM2FvaeOseEhFOsjItdQ2S6wYYmkOdl2+CfLBvmpIV55vYY2Qn6uAxAWC40zbhxSmWArcQj0TSIiSU37mx0kgVesgLereOSz8E5EWJa6Qzyh1hZEcO7xY4Ct9WLfNvwa+5xA2h6uGP6vMPxMsZ8WNf0Gf+cOCw9usq51a5+kNG9Sn1IjJsjoO0LI7EpVra/vxhPdFs7JyjYriohlbTAKGxO1C6oJEljseOLqmTxfPX66OucJK66OUNzuDjK7p05UIbGwX25I/vrj4BYrnD0uZ/Rtvfzz9fPsPIkgkbL0DZNMFRVEHFEY2ZCBTcwMLdfCsCCVN4SwpE9YG+ARNgD24IDHYSYB1yNCYDkLRFoC8oOUG40AKQx5IYyAmlQ6SF7dDoSof0hbJiApzqLs43aPc5UG+AvVQ/4T7nGQFQiJ5kdbAkmgH2Sz0FaWB4gLrad22v4nmuvPt/yzCc1+V4t0e4z93r8PYwDCvNANxLSthkai0jmCf5+jq6y6Y4SkjTfoKprgWufj9Dg3AozBmiK7pl3H8WDH3u0YfLY6u6c/HVS2vSvsxoygyTF2q/qNenEyjJ5NJPYGPRidME1M1/JYqwyoNq32Ihu4J0z5M+WA2DoqwEI9wfmEaEhQJzPNsKNOh0jJwrfRVJqbnNOrC6IGwQFzgHiKrpCuq2kE+FizrMXWE7IWCEKemg7hSiimOQchNIC3EchqpHlBO95TshQThkwF5TL9k+Mm/MZLGzVo3AlQdLzagDle1vCYd/wU9/5Z5ZcyZPnNow/J8ZHZZCGtsbKw3rdn7nIzTx42o0WfP1cPKuYJ6XPFs5q7p8zmKx5v8cdcxDeMPOR1fj+gh4X10TV/dukiC+nJPeLy8eH1hrtm/UVvpKxcrP2oL/dlcs1eQ9PCeo73wGcp+R2Xyvlp74vH19B9EkoA2CYKUlcQqJCQj6vkoyBjh/IurcJiy4Zxy2FMptRBO7sK3kClR0UYUZAX+wMqfC1ICiYHMYBsKSQsSFKaAUEqZLoiK00ASFsgpN0UEUWE6yOkiiArE6NmUb91OWwAAEuNJREFUszCNxA0c/uBoF04W86YOarWQAYjGmHBBEIkUiXEqib025hNmInWknv6zKo77Sh3/RvcfSx5Xl4O4yr5Y7NxiuEEQFT4uvs8yrF5VvosX28LLS185vsiRHkc9YPiJtrCbJIzHyx3gJdfpl80flZWPR6qIxJghus7xjSqj4E9UNn2VvN76Csqq6XIR+48OYEeGlcAaXhLfQwxNQcgQEI9IErOOxBUuCuDLz9Arm5iyOTaYy7Jty8hAb2VCm43ZmwnwQTbgFpAWyA4SGEKhaMdgYNpngKAcpeMCAfFjYGE4yAqco3RZ0LorUqOkxVkf6AgzvFBPFbISSsOUD+WRrWijpcwbmI4Gomj4yxAIv4bPVU+q9sfxk/EP36UlfP49N3vNWr/m9CZdX/zzjDDofAoW3XHVr9NPHdB8p2+uORl/mjFLUktMbBTtkSJbpLCRxYyD5OpJps/4+DJuvq5IIgoLqfi3pLzcRuloM7QSzKImsBSWG80LVKkxkSvOkFHaCjL5QvrPN9rwvaSVtEg2ICmQCNRQkGjwnlOpNktMxdds+GxcRFrIyCmhTQMEUJjl4qwtzPbAOVC8o0DUZroGiMmBpEUfRBZ4DvRUJC4/1GOpij1ML9XU0PJdFxIZGsOpJkkOQ0YdFh5CPodKl0WfRqQkVUhTIEf1iN4GkdJU4Rx/xsJfHkpfMv4cd+IAUJb1+YdkfSU7NXp6+/bti7qquKiEdfVq0Gl2TO2DonYzAcUTCv0slCB8FuGia/q8j7iAPl30aNIPHVKq55w+00MvjFLo05WmV8H5P9XLzydVF/H0xbGl9UGfjm226B98po2u6fO+0f3H9M7SbT1h+FoS00ybSmm+5/RZHxzbwWvVHtSvNuLRR4BKl0vPtHRhWh1SESUsNBkH0qjvNiAx4MA1JDBc4yBmTPmwJArJCFM+dA1SE5XsmFIqRTzKUrZYkMio78IUkauFoW6Mcbin1GWrOR8nqOEUEUQFmuK3ZdEw6NFg92s9j3XLp0CIsAuS8VdPkcKhCZ9/KAc81x/c3NdzFjy6KHZc0YPNh7VhDg9jYnh4co9n2dvx1nLalys7Rimx2xLGigfEJBQ0Xr149FkBVb04BQiTlPAFbTiDxRGKM1pJf5AgarPKG0sQu413N07hkCANO5m0fSebtCwziW5DqMISHTRMJCDF23inYbmsauNCHq+Vn1ta5dErzKN8psP/RiIXVpAegKJQ30Y06AQSEXdAIpdL0wbTNsLpoSIeCwRJHZYBpTusIFAIlPC0iqL5AxoCcmLPQkkLdITRCc0dSFqQD1A51g4pLOXmhZCwDMO2BpH9q6ZtDoU4oKQIy5yEynFnv+mzw+0+/q3Sf5yT4aYs89zq1alLIK7wYeQANcCpgW5AOaqIARzxcudrXrMTz+cuFAxBI1Rw06eLKz3xsnDikt+Mmr9mWBlXrbySeJAlTt8MXJImXHRNv0zx2GpWZ3r0KKqzXHlRHH26+fQf+mkbg56ADjppUuihMJl7BEhGtmnj+4Phj1lEUAzjaQcgJkzcqPPmlI/yjdJV8Trf/+hbeYyP0uMS0zSVF8SEaSELxkhR6a7IC1IVHkNMBWEkCljxYQ7YXgWKrDCHw2ohJDDKSkr5Tst3TANBp7DdgkTFKSOpxYMtV2i3hXQoJjwbBo3L4oibAajdXmSbCl01PEvi6x3PetMvwfi3cv+xHpPRk8GZvo6Oq5y5FvZlvtfqQZ5v5igfH7iRdHqrn/H24McyEb6ejCUxkCwqEATi8JDNKtWRIxI6wrLj+aOyQgIqLT/KTZ+OLYnCFGHE60PdSgzIgVmcfrbt5evjYkB97VeNyv8plx/UYoChElhYgB7KtD3PAUWRpejIVNzNAjNzyDuYRqnrMF5dIx4CkTrlAJQRps2FhZIX5lqYwfFLOygTBeSmkUhDEgNvIC7MR5ML6JhozoCpn+858G1utbH4j7BRT0Z9VlZzbTyOKJCKeCjkqYbkFBJh+DXCPVcKuXKIFURlm8WBoZSFOBCYmk6i33ioT+Kw1CegEMspcFfe+M8+rRySNum/YUwm9I7TPT04NWOBDg/nwtz16xMbEp3mPswIOuI6G7wBSlynz1pQWZEIP0smIcEEWN3QsfJDn+nj9FFSPh73wilgdE2f+eOumo4pPqWI2kI/LKu4RVXLq7H/kJopRUFhnkj4joNT9KC/BlZgAIVD1I+cwASVUBgCIsF1KEQxJLpGPKHGP5LYrAs5ikREnmJ61KF4K5cG1+REVS6HC1JauGroYYcOrLWUEp6MSF0UpoZgK5hV2dgEzeNLYbMBnRQZEUPnOwGMT6GOp57Kg/0WTCMYjnsQHpDmlJFTR5IcNt/alvV1PdF5NsKcLSpGG03L6QcjnWDpeIXqgFYb//A9wGi1+fMPDeqY7nae6uvT530KKp+JebkhHJyX6Fqz33X83tCgRr1d6gXBH+XnFtEwDmEVMBfAtbK7UvHxVTb1gGLQokbFVBZMDtUJHmT+dsPxmqSRU2nkrxkWxhfbOfEVwLov4sIaonSRr1qZy6vy8xliPbn+qPjYHxSm6mJwdB357DfaVtJ/BMLeW0/ayVQSR6TA5AB7h8kwmFeRrFBUSFYkJk7GsM+F5SuiCQmFBEriCskHYcxfEM9ozBjBS/yaKD//rBzndjD3BHswAcmqwFdhOWGugCw5owwpEt9sxMlVGWQEK4GlcAOi1XAcL6eLICfdcMFmNDnH7xdO/YTCHTkxM2B6EiSPbuXmHrZO5eJy4Iu6lfo2Gu8orFfA+PM9UMjnHpBIx9v+/Q9Wm8nMfcMTE1d7u7vP4Ec6fzy1wqOGP3xI63JHjgT2/rsy/boTbMP0pe78dVUWS5wjK0VUjIqNN3kA62ZYeIcfxofXDFNFUZBTT4W6m71mWBlXrb4yWSoEYWh0jVIUdJEmzA6o18mRDN7dCplCEkK8IiP4WRAU9OO8j5wimZB3SAhKYlJEphLkJCaSEP7PEdxsfVG5UWFxP6qPPngTlvBED6IWLN8dTPmg8ocFPPRXWBdlFWqqCEmLlhAgLRtKdLaAkpQNfRUM6DUQGOUiTimNEaT7FvRVw/F6K91XG4/mHf9KPaovvJ36jzfSS1mpc6mUdhnvhZL4a0GjZsKBKK+n0+kt0AHvztCAsIzjeeAeUKVPF1l101cBWCICxcGmcPalUeHRnyguIsJYej79fFnpKxdjrKhu+spVK69Ke+OW6SXlh7Xk/8b7D5umJKY6nUiQAEmp5ZKoD5Ay8kTFzcAsJIrL+ZREYCWAaU4ubXRNP8wfpuSuGubHMwCJhSuGPCiYJIMw5GV6xkfY0Wd+WoPiBAlEhvnzNluw3SKZYTkQHIQ5J1RQDg7Lw/QQGUIdFp4wcC9KgQ/7KkxjucEHROVmc3ZaCFfEjMxUvlPvBZ0WhT1Q1zG06hQKyGPA9qEh4bPRJuO/0p//WvoPyXpa77BPr9L1mn64QiJRT0vlP3jg1oyn0/th1dnN6VOkQyh8wVRuPpLUH9GHi+sckD4vLaj43NSHLwfv8cKjbGxdgc97JUpFpIRbpovKYHTUltkpHYkyEqNYf1gWfZU+Vn+JiMZERS4qKyTAMv1hmwoItLT/aL6OL9cn8A4mknhDkR5CUuh43ExhAXjnIQVxRQ9UwnU1JM73meHISINzlY/1Ir3jwNQBtui5IpU3K2mFZbEUEhgJiHlZhkqI8rws7hPFxBHlZ5romu1CGRSv2HyQEQiLPkwefJcSk2o0mU+F8Z46KswbKd8qvRUWiq7BsuoYlF/q+Jd839p4/KNnFHhw+Fbc819r/y3dHO7qsk9D2lLPBvEq59SLXC6CYSCq1OTk5F48g+FxLyQSvvyzhFK8taaYL1ACiYdkkSOg/HVO4irmAySLlR8+yHy5wnaWysTF7YmnRxdyecMXFDcxx3KjNCUEGUtb2r4Iixwh5qebxEG58v2Hkh0ERqlLp5kClNLkngLSyF8XExrZi089SYbFm9DRg1FCbEKyoxQE8sqFkTOgTwrDVIPCP/k8qpRcGrxMEXmxnpwjUeXbhjpgA2bBNsp0HPQWOiwNOnddw5YcNIdSFyzTlUKehEbrLDxDNn7osjCXPw5FO22qgPfKHn/pf8XxxxetvSvYlX8BxBVKCdGDmPPDhz0W+Oijjxof//jHt+Hh2oko/qKqFx4l0BJQmQIwS3RNn/fxZXqGFbq4nQzimI9tKFs+S1S1KJ9XoQkEfUQwtKg98fSzefMMwmx5F28/IqK2RLjM2b54/gX0H0v6+IiDZSVgHJogfYWNzDMUpCtsUkKg4pKIUJAsnNTlkjNWzfBCPMOhi8JAiCSqPBmyMFVQ1OdctQwLywNZ5cPCpDl80D6IhjzBASQF0sUeREpSJCyE4ceSpJXbEO2612AHepaTSRn/YrtEAD3n8xV/ntv4+S96nyGRO9gccQZmEPiBK3bRi5kPHcG+v2T32n2+53bxNY8oQyWIB0SR9OmqxMeTh5lm/8azx8srEbCQNSqTpUTX+eagwCiPqiWeQAXO/olHV2tPaYUFjWCxsQJjt7MV564K6iOB2Xj1adNGa3PqDMFl4XwSSnAQCUIibqFPlwtTwbiOkoSR+JvLx3KYv9BXaSrlLyifSegQBNMFTAWhiIeFArRZnoX+8Y2EzKhbnuNlYO9wFpZXkwoH5Kmj/6qOFTz+0n8+Y4Y/2pVIcJqY35+YJ6wjEN33ZzL9kPY3hWjx6Sv+RcByLIQAZZYQJSn2C944FRF/QkvjQ31XZDcV04GVPOGl+WdJEhVGbaNPV3d7Va7ZP83U/1ACgzTjkg4gjUFvHhGWkrPAPnnBLNeFSEKKfAbzOu9yBAUdVj6cZURpZuU3XOUILioD93x2IEnxxFGc9c6M+M93cHSNZVzHquBQDeMn4x898wQ2us7pgGvAbyU8/z5e5EupVEqtJirCgp4KHxVI7sbrQIYKHyKF3+yvIvEEX8FsQNk9qXwgBpgQwNo7p9OKrukzfdzF08+WTmYrV35YF+tU8bEpYImInGtLVH+8PkzZ8iQcVpjrawXCLOHH5uo/9JmWjbXHJMQcNhVW8bOklbsumnJw7Q+cgtVK2mJxAUNNKKncp54KHuzAwnjCE01B1UIHA1A80ik/IkdIfTj6mE8MXh2sSKZhdHUd+IcDykwFLj4eMv7Fv+il75c8/xEmeHaojD+jZ4LgbsPVVvO5iutg4oSAFCCiAqVp/jrUKRU8mzVexsube05ff3tiD0Q1wkP/ojrYgeiaftiheHsjLKL4GrudTxYvb0H9h94bpzeAwCD4cAqJf5SmlBjFH5D8ChVC1Q8KyIkrjtgbE64y4lqtINJHel5Hq4q4ZdsYzsWBWaU+rkFWtFzQbiNNnWciNbT/qD4+Hitq/FdE/3mWzmvQU+W4hZZPenQuRHRNfylcvfVjpUqz0Tj6dNE1/fm4euufTx1z5am3/hr6z6lj9A9ElneKwPJ3IYEVEpqKys0YFeUhoDBP4TV/+bjVIkfqKuu8/ixC/+tqR73111V4DYnrrb+G8a+h1tkk9dY/m7MxV7XUzwdP3ApBgCYG6Co+L6/+kcB4X0g0ERFFzwXjojBc5q8ZhqOKtWEoROmLEwSWBIHowVySyqSS5kIABEYhisRFEov8SgRWGD6K9OMgq8IwBIkTBBYXASGsxcW3pUoHgfF5iIiLPv9x+03kuLxMqaqsUj1KJL4gsFgICGEtFrJtUG6OwDhtJHHhqLOl+dBAG0AnXRAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBAFBQBAQBAQBQUAQEAQEAUFAEBAEBIGVhMD/D0fV/fpMMM+gAAAAAElFTkSuQmCC'\r\n\t\t},\r\n\t\t...uni.$uv?.props?.noNetwork\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-no-network/components/uv-no-network/uv-no-network.vue",
    "content": "<template>\r\n\t<uv-overlay\r\n\t    :show=\"!isConnected\"\r\n\t\t:zIndex=\"zIndex\"\r\n\t    @touchmove.stop.prevent=\"noop\"\r\n\t\t:customStyle=\"{\r\n\t\t\tbackgroundColor: '#fff',\r\n\t\t\tdisplay: 'flex',\r\n\t\t\tjustifyContent: 'center',\r\n\t\t}\"\r\n\t>\r\n\t\t<view\r\n\t\t    class=\"uv-no-network\"\r\n\t\t>\r\n\t\t\t<uv-icon\r\n\t\t\t    :name=\"image\"\r\n\t\t\t    size=\"150\"\r\n\t\t\t    imgMode=\"widthFit\"\r\n\t\t\t    class=\"uv-no-network__error-icon\"\r\n\t\t\t></uv-icon>\r\n\t\t\t<text class=\"uv-no-network__tips\">{{tips}}</text>\r\n\t\t\t<!-- 只有APP平台，才能跳转设置页，因为需要调用plus环境 -->\r\n\t\t\t<!-- #ifdef APP-PLUS -->\r\n\t\t\t<view class=\"uv-no-network__app\">\r\n\t\t\t\t<text class=\"uv-no-network__app__setting\">请检查网络，或前往</text>\r\n\t\t\t\t<text\r\n\t\t\t\t    class=\"uv-no-network__app__to-setting\"\r\n\t\t\t\t    @tap=\"openSettings\"\r\n\t\t\t\t>设置</text>\r\n\t\t\t</view>\r\n\t\t\t<!-- #endif -->\r\n\t\t\t<view class=\"uv-no-network__retry\">\r\n\t\t\t\t<uv-button\r\n\t\t\t\t    size=\"mini\"\r\n\t\t\t\t    text=\"重试\"\r\n\t\t\t\t    type=\"primary\"\r\n\t\t\t\t\tplain\r\n\t\t\t\t    @click=\"retry\"\r\n\t\t\t\t></uv-button>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t</uv-overlay>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\r\n\t/**\r\n\t * noNetwork 无网络提示\r\n\t * @description 该组件无需任何配置，引入即可，内部自动处理所有功能和事件。\r\n\t * @tutorial https://www.uvui.cn/components/noNetwork.html\r\n\t * @property {String}\t\t\ttips \t没有网络时的提示语 （默认：'哎呀，网络信号丢失' ）\r\n\t * @property {String | Number}\tzIndex\t组件的z-index值 \r\n\t * @property {String}\t\t\timage\t无网络的图片提示，可用的src地址或base64图片 \r\n\t * @event {Function}\t\t\tretry\t用户点击页面的\"重试\"按钮时触发\r\n\t * @example <uv-no-network></uv-no-network>\r\n\t */\r\n\texport default {\r\n\t\tname: \"uv-no-network\",\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tisConnected: true, // 是否有网络连接\r\n\t\t\t\tnetworkType: \"none\", // 网络类型\r\n\t\t\t}\r\n\t\t},\r\n\t\tmounted() {\r\n\t\t\tthis.isIOS = (uni.getSystemInfoSync().platform === 'ios')\r\n\t\t\tuni.onNetworkStatusChange((res) => {\r\n\t\t\t\tthis.isConnected = res.isConnected\r\n\t\t\t\tthis.networkType = res.networkType\r\n\t\t\t\tthis.emitEvent(this.networkType)\r\n\t\t\t})\r\n\t\t\tuni.getNetworkType({\r\n\t\t\t\tsuccess: (res) => {\r\n\t\t\t\t\tthis.networkType = res.networkType\r\n\t\t\t\t\tthis.emitEvent(this.networkType)\r\n\t\t\t\t\tif (res.networkType == 'none') {\r\n\t\t\t\t\t\tthis.isConnected = false\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tthis.isConnected = true\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t})\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tretry() {\r\n\t\t\t\t// 重新检查网络\r\n\t\t\t\tuni.getNetworkType({\r\n\t\t\t\t\tsuccess: (res) => {\r\n\t\t\t\t\t\tthis.networkType = res.networkType\r\n\t\t\t\t\t\tthis.emitEvent(this.networkType)\r\n\t\t\t\t\t\tif (res.networkType == 'none') {\r\n\t\t\t\t\t\t\tthis.$uv.toast('无网络连接')\r\n\t\t\t\t\t\t\tthis.isConnected = false\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\tthis.$uv.toast('网络已连接')\r\n\t\t\t\t\t\t\tthis.isConnected = true\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t})\r\n\t\t\t\tthis.$emit('retry')\r\n\t\t\t},\r\n\t\t\t// 发出事件给父组件\r\n\t\t\temitEvent(networkType) {\r\n\t\t\t\tthis.$emit(networkType === 'none' ? 'disconnected' : 'connected')\r\n\t\t\t},\r\n\t\t\tasync openSettings() {\r\n\t\t\t\tif (this.networkType == \"none\") {\r\n\t\t\t\t\tthis.openSystemSettings()\r\n\t\t\t\t\treturn\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\topenAppSettings() {\r\n\t\t\t\tthis.gotoAppSetting()\r\n\t\t\t},\r\n\t\t\topenSystemSettings() {\r\n\t\t\t\t// 以下方法来自5+范畴，如需深究，请自行查阅相关文档\r\n\t\t\t\t// https://ask.dcloud.net.cn/docs/\r\n\t\t\t\tif (this.isIOS) {\r\n\t\t\t\t\tthis.gotoiOSSetting()\r\n\t\t\t\t} else {\r\n\t\t\t\t\tthis.gotoAndroidSetting()\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tnetwork() {\r\n\t\t\t\tvar result = null\r\n\t\t\t\tvar cellularData = plus.ios.newObject(\"CTCellularData\")\r\n\t\t\t\tvar state = cellularData.plusGetAttribute(\"restrictedState\")\r\n\t\t\t\tif (state == 0) {\r\n\t\t\t\t\tresult = null\r\n\t\t\t\t} else if (state == 2) {\r\n\t\t\t\t\tresult = 1\r\n\t\t\t\t} else if (state == 1) {\r\n\t\t\t\t\tresult = 2\r\n\t\t\t\t}\r\n\t\t\t\tplus.ios.deleteObject(cellularData)\r\n\t\t\t\treturn result\r\n\t\t\t},\r\n\t\t\tgotoAppSetting() {\r\n\t\t\t\tif (this.isIOS) {\r\n\t\t\t\t\tvar UIApplication = plus.ios.import(\"UIApplication\")\r\n\t\t\t\t\tvar application2 = UIApplication.sharedApplication()\r\n\t\t\t\t\tvar NSURL2 = plus.ios.import(\"NSURL\")\r\n\t\t\t\t\tvar setting2 = NSURL2.URLWithString(\"app-settings:\")\r\n\t\t\t\t\tapplication2.openURL(setting2)\r\n\t\t\t\t\tplus.ios.deleteObject(setting2)\r\n\t\t\t\t\tplus.ios.deleteObject(NSURL2)\r\n\t\t\t\t\tplus.ios.deleteObject(application2)\r\n\t\t\t\t} else {\r\n\t\t\t\t\tvar Intent = plus.android.importClass(\"android.content.Intent\")\r\n\t\t\t\t\tvar Settings = plus.android.importClass(\"android.provider.Settings\")\r\n\t\t\t\t\tvar Uri = plus.android.importClass(\"android.net.Uri\")\r\n\t\t\t\t\tvar mainActivity = plus.android.runtimeMainActivity()\r\n\t\t\t\t\tvar intent = new Intent()\r\n\t\t\t\t\tintent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)\r\n\t\t\t\t\tvar uri = Uri.fromParts(\"package\", mainActivity.getPackageName(), null)\r\n\t\t\t\t\tintent.setData(uri)\r\n\t\t\t\t\tmainActivity.startActivity(intent)\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tgotoiOSSetting() {\r\n\t\t\t\tvar UIApplication = plus.ios.import(\"UIApplication\")\r\n\t\t\t\tvar application2 = UIApplication.sharedApplication()\r\n\t\t\t\tvar NSURL2 = plus.ios.import(\"NSURL\")\r\n\t\t\t\tvar setting2 = NSURL2.URLWithString(\"App-prefs:root=General\")\r\n\t\t\t\tapplication2.openURL(setting2)\r\n\t\t\t\tplus.ios.deleteObject(setting2)\r\n\t\t\t\tplus.ios.deleteObject(NSURL2)\r\n\t\t\t\tplus.ios.deleteObject(application2)\r\n\t\t\t},\r\n\t\t\tgotoAndroidSetting() {\r\n\t\t\t\tvar Intent = plus.android.importClass(\"android.content.Intent\")\r\n\t\t\t\tvar Settings = plus.android.importClass(\"android.provider.Settings\")\r\n\t\t\t\tvar mainActivity = plus.android.runtimeMainActivity()\r\n\t\t\t\tvar intent = new Intent(Settings.ACTION_SETTINGS)\r\n\t\t\t\tmainActivity.startActivity(intent)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t.uv-no-network {\r\n\t\t@include flex(column);\r\n\t\tjustify-content: center;\r\n\t\talign-items: center;\r\n\t\tmargin-top: -100px;\r\n\r\n\t\t&__tips {\r\n\t\t\tcolor: $uv-tips-color;\r\n\t\t\tfont-size: 14px;\r\n\t\t\tmargin-top: 15px;\r\n\t\t}\r\n\r\n\t\t&__app {\r\n\t\t\t@include flex(row);\r\n\t\t\tmargin-top: 6px;\r\n\r\n\t\t\t&__setting {\r\n\t\t\t\tcolor: $uv-light-color;\r\n\t\t\t\tfont-size: 13px;\r\n\t\t\t}\r\n\r\n\t\t\t&__to-setting {\r\n\t\t\t\tfont-size: 13px;\r\n\t\t\t\tcolor: $uv-primary;\r\n\t\t\t\tmargin-left: 3px;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t&__retry {\r\n\t\t\t@include flex(row);\r\n\t\t\tjustify-content: center;\r\n\t\t\tmargin-top: 15px;\r\n\t\t}\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-no-network/package.json",
    "content": "{\r\n  \"id\": \"uv-no-network\",\r\n  \"displayName\": \"uv-no-network 无网络提示  全面兼容小程序、nvue、vue2、vue3等多端\",\r\n  \"version\": \"1.0.1\",\r\n  \"description\": \"uv-no-network 该组件在没有任何网络的情况下，显示在内容上方，无需任何配置，引入即可，内部自动处理所有功能和事件。\",\r\n  \"keywords\": [\r\n    \"uv-no-network\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"network\",\r\n    \"无网络\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-overlay\",\r\n\t\t\t\"uv-icon\",\r\n\t\t\t\"uv-button\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-no-network/readme.md",
    "content": "## NoNetwork 无网络提示\r\n\r\n> **组件名：uv-no-network**\r\n\r\n该组件在没有任何网络的情况下，显示在内容上方，无需任何配置，引入即可，内部自动处理所有功能和事件。\r\n\r\n### <a href=\"https://www.uvui.cn/components/noNetwork.html\" target=\"_blank\">查看文档</a>\r\n\r\n### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n#### 如使用过程中有任何问题，或者您对uv-ui有一些好的建议，欢迎加入 uv-ui 交流群：<a href=\"https://ext.dcloud.net.cn/plugin?id=12287\" target=\"_blank\">uv-ui</a>、<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-notice-bar/changelog.md",
    "content": "## 1.0.7（2023-10-13）\n1. unmounted兼容vue3\n## 1.0.6（2023-08-03）\r\n1. 竖向滚动时候增加change回调\r\n## 1.0.5（2023-07-21）\r\n1. 增加icon类型，支持设置false不显示图标\r\n2. 优化文档\r\n## 1.0.4（2023-07-03）\r\n1. 增加disableScroll 属性，禁止自动滚动\r\n2. 优化文档\r\n## 1.0.3（2023-06-04）\r\n1.  修复text传值为null报错的问题\r\n## 1.0.2（2023-05-30）\r\n1. 修复error报错的BUG\r\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-notice-bar 滚动通知\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-notice-bar/components/uv-column-notice/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 显示的内容，字符串\r\n\t\ttext: {\r\n\t\t\ttype: [Array],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 是否显示左侧的音量图标\r\n\t\ticon: {\r\n\t\t\ttype: [String, Boolean, null],\r\n\t\t\tdefault: 'volume'\r\n\t\t},\r\n\t\t// 通告模式，link-显示右箭头，closable-显示右侧关闭图标\r\n\t\tmode: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 文字颜色，各图标也会使用文字颜色\r\n\t\tcolor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#f9ae3d'\r\n\t\t},\r\n\t\t// 背景颜色\r\n\t\tbgColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#fdf6ec'\r\n\t\t},\r\n\t\t// 字体大小，单位px\r\n\t\tfontSize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 14\r\n\t\t},\r\n\t\t// 水平滚动时的滚动速度，即每秒滚动多少px(px)，这有利于控制文字无论多少时，都能有一个恒定的速度\r\n\t\tspeed: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 80\r\n\t\t},\r\n\t\t// direction = row时，是否使用步进形式滚动\r\n\t\tstep: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 滚动一个周期的时间长，单位ms\r\n\t\tduration: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 1500\r\n\t\t},\r\n\t\t// 是否禁止用手滑动切换，仅`direction=\"column\"生效`\r\n\t\t// 目前HX2.6.11，只支持App 2.5.5+、H5 2.5.5+、支付宝小程序、字节跳动小程序\r\n\t\tdisableTouch: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 是否禁止滚动，仅`direction=\"column\"生效`\r\n\t\tdisableScroll: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t...uni.$uv?.props?.columnNotice\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-notice-bar/components/uv-column-notice/uv-column-notice.vue",
    "content": "<template>\r\n\t<view\r\n\t\tclass=\"uv-notice\"\r\n\t\t@tap=\"clickHandler\"\r\n\t>\r\n\t\t<slot name=\"icon\">\r\n\t\t\t<view\r\n\t\t\t\tclass=\"uv-notice__left-icon\"\r\n\t\t\t\tv-if=\"icon\"\r\n\t\t\t>\r\n\t\t\t\t<uv-icon\r\n\t\t\t\t\t:name=\"icon\"\r\n\t\t\t\t\t:color=\"color\"\r\n\t\t\t\t\tsize=\"19\"\r\n\t\t\t\t></uv-icon>\r\n\t\t\t</view>\r\n\t\t</slot>\r\n\t\t<swiper\r\n\t\t\t:disable-touch=\"disableTouch\"\r\n\t\t\t:vertical=\"step ? false : true\"\r\n\t\t\tcircular\r\n\t\t\t:interval=\"duration\"\r\n\t\t\t:autoplay=\"!disableScroll\"\r\n\t\t\tclass=\"uv-notice__swiper\"\r\n\t\t\t:style=\"[swiperStyle]\"\r\n\t\t\t@change=\"noticeChange\"\r\n\t\t>\r\n\t\t\t<swiper-item\r\n\t\t\t\tv-for=\"(item, index) in text\"\r\n\t\t\t\t:key=\"index\"\r\n\t\t\t\tclass=\"uv-notice__swiper__item\"\r\n\t\t\t>\r\n\t\t\t\t<text\r\n\t\t\t\t\tclass=\"uv-notice__swiper__item__text uv-line-1\"\r\n\t\t\t\t\t:style=\"[textStyle]\"\r\n\t\t\t\t>{{ item }}</text>\r\n\t\t\t</swiper-item>\r\n\t\t</swiper>\r\n\t\t<view\r\n\t\t\tclass=\"uv-notice__right-icon\"\r\n\t\t\tv-if=\"['link', 'closable'].includes(mode)\"\r\n\t\t>\r\n\t\t\t<uv-icon\r\n\t\t\t\tv-if=\"mode === 'link'\"\r\n\t\t\t\tname=\"arrow-right\"\r\n\t\t\t\t:size=\"17\"\r\n\t\t\t\t:color=\"color\"\r\n\t\t\t></uv-icon>\r\n\t\t\t<uv-icon\r\n\t\t\t\tv-if=\"mode === 'closable'\"\r\n\t\t\t\tname=\"close\"\r\n\t\t\t\t:size=\"16\"\r\n\t\t\t\t:color=\"color\"\r\n\t\t\t\t@click=\"close\"\r\n\t\t\t></uv-icon>\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * ColumnNotice 滚动通知中的垂直滚动 内部组件\r\n\t * @description 该组件用于滚动通告场景，是其中的垂直滚动方式\r\n\t * @tutorial https://www.uvui.cn/components/noticeBar.html\r\n\t * @property {Array}\t\t\ttext \t\t\t显示的内容，字符串\r\n\t * @property {String}\t\t\ticon \t\t\t是否显示左侧的音量图标 （ 默认 'volume' ）\r\n\t * @property {String}\t\t\tmode \t\t\t通告模式，link-显示右箭头，closable-显示右侧关闭图标\r\n\t * @property {String}\t\t\tcolor \t\t\t文字颜色，各图标也会使用文字颜色 （ 默认 '#f9ae3d' ）\r\n\t * @property {String}\t\t\tbgColor \t\t背景颜色 （ 默认 '#fdf6ec' ）\r\n\t * @property {String | Number}\tfontSize\t\t字体大小，单位px  （ 默认 14 ）\r\n\t * @property {String | Number}\tspeed\t\t\t水平滚动时的滚动速度，即每秒滚动多少px(rpx)，这有利于控制文字无论多少时，都能有一个恒定的速度 （ 默认 80 ）\r\n\t * @property {Boolean}\t\t\tstep\t\t\tdirection = row时，是否使用步进形式滚动 （ 默认 false ）\r\n\t * @property {String | Number}\tduration\t\t滚动一个周期的时间长，单位ms （ 默认 1500 ）\r\n\t * @property {Boolean}\t\t\tdisableTouch\t是否禁止用手滑动切换   目前HX2.6.11，只支持App 2.5.5+、H5 2.5.5+、支付宝小程序、字节跳动小程序 （ 默认 true ）\r\n\t * @example \r\n\t */\r\n\texport default {\r\n\t\temits: ['click','close','change'],\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\twatch: {\r\n\t\t\ttext: {\r\n\t\t\t\timmediate: true,\r\n\t\t\t\thandler(newValue, oldValue) {\r\n\t\t\t\t\tif(!this.$uv.test.array(newValue)) {\r\n\t\t\t\t\t\tthis.$uv.error('noticebar组件direction为column时，要求text参数为数组形式')\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\t// 文字内容的样式\r\n\t\t\ttextStyle() {\r\n\t\t\t\tlet style = {}\r\n\t\t\t\tstyle.color = this.color\r\n\t\t\t\tstyle.fontSize = this.$uv.addUnit(this.fontSize)\r\n\t\t\t\treturn style\r\n\t\t\t},\r\n\t\t\t// 垂直或者水平滚动\r\n\t\t\tvertical() {\r\n\t\t\t\tif (this.mode == 'horizontal') return false\r\n\t\t\t\telse return true\r\n\t\t\t},\r\n\t\t\t// NVUE中的swiper在css中样式不生效\r\n\t\t\tswiperStyle(){\r\n\t\t\t\tconst style = {};\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\tstyle.flex = 1;\r\n\t\t\t\tstyle.height = '16px';\r\n\t\t\t\t// #endif\r\n\t\t\t\treturn style;\r\n\t\t\t}\r\n\t\t},\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tindex:0\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tnoticeChange(e){\r\n\t\t\t\tthis.index = e.detail.current\r\n\t\t\t\tthis.$emit('change', this.index);\r\n\t\t\t},\r\n\t\t\t// 点击通告栏\r\n\t\t\tclickHandler() {\r\n\t\t\t\tthis.$emit('click', this.index)\r\n\t\t\t},\r\n\t\t\t// 点击关闭按钮\r\n\t\t\tclose() {\r\n\t\t\t\tthis.$emit('close')\r\n\t\t\t}\r\n\t\t}\r\n\t};\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t$show-lines: 1;\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t.uv-notice {\r\n\t\t@include flex;\r\n\t\talign-items: center;\r\n\t\tjustify-content: space-between;\r\n\r\n\t\t&__left-icon {\r\n\t\t\talign-items: center;\r\n\t\t\tmargin-right: 5px;\r\n\t\t}\r\n\r\n\t\t&__right-icon {\r\n\t\t\tmargin-left: 5px;\r\n\t\t\talign-items: center;\r\n\t\t}\r\n\r\n\t\t&__swiper {\r\n\t\t\theight: 16px;\r\n\t\t\t@include flex;\r\n\t\t\talign-items: center;\r\n\t\t\tflex: 1;\r\n\r\n\t\t\t&__item {\r\n\t\t\t\t@include flex;\r\n\t\t\t\talign-items: center;\r\n\t\t\t\toverflow: hidden;\r\n\r\n\t\t\t\t&__text {\r\n\t\t\t\t\tfont-size: 14px;\r\n\t\t\t\t\tcolor: $uv-warning;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-notice-bar/components/uv-notice-bar/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 显示的内容，数组\r\n\t\ttext: {\r\n\t\t\ttype: [Array, String],\r\n\t\t\tdefault: () => []\r\n\t\t},\r\n\t\t// 通告滚动模式，row-横向滚动，column-竖向滚动\r\n\t\tdirection: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'row'\r\n\t\t},\r\n\t\t// direction = row时，是否使用步进形式滚动\r\n\t\tstep: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否显示左侧的音量图标\r\n\t\ticon: {\r\n\t\t\ttype: [String, Boolean, null],\r\n\t\t\tdefault: 'volume'\r\n\t\t},\r\n\t\t// 通告模式，link-显示右箭头，closable-显示右侧关闭图标\r\n\t\tmode: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 文字颜色，各图标也会使用文字颜色\r\n\t\tcolor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#f9ae3d'\r\n\t\t},\r\n\t\t// 背景颜色\r\n\t\tbgColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#fdf6ec'\r\n\t\t},\r\n\t\t// 水平滚动时的滚动速度，即每秒滚动多少px(px)，这有利于控制文字无论多少时，都能有一个恒定的速度\r\n\t\tspeed: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 80\r\n\t\t},\r\n\t\t// 字体大小\r\n\t\tfontSize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 14\r\n\t\t},\r\n\t\t// 滚动一个周期的时间长，单位ms\r\n\t\tduration: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 2000\r\n\t\t},\r\n\t\t// 跳转的页面路径\r\n\t\turl: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 页面跳转的类型\r\n\t\tlinkType: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'navigateTo'\r\n\t\t},\r\n\t\t// 是否禁止用手滑动切换\r\n\t\t// 目前HX2.6.11，只支持App 2.5.5+、H5 2.5.5+、支付宝小程序、字节跳动小程序\r\n\t\tdisableTouch: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 是否禁止滚动，仅`direction=\"column\"生效`\r\n\t\tdisableScroll: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t...uni.$uv?.props?.noticeBar\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-notice-bar/components/uv-notice-bar/uv-notice-bar.vue",
    "content": "<template>\r\n\t<view\r\n\t\tclass=\"uv-notice-bar\"\r\n\t\tv-if=\"show\"\r\n\t\t:style=\"[{\r\n\t\t\tbackgroundColor: bgColor\r\n\t\t}, $uv.addStyle(customStyle)]\"\r\n\t>\r\n\t\t<template v-if=\"direction === 'column' || (direction === 'row' && step)\">\r\n\t\t\t<uv-column-notice\r\n\t\t\t\t:color=\"color\"\r\n\t\t\t\t:bgColor=\"bgColor\"\r\n\t\t\t\t:text=\"text\"\r\n\t\t\t\t:mode=\"mode\"\r\n\t\t\t\t:step=\"step\"\r\n\t\t\t\t:icon=\"icon\"\r\n\t\t\t\t:disable-touch=\"disableTouch\"\r\n\t\t\t\t:disable-scroll=\"disableScroll\"\r\n\t\t\t\t:fontSize=\"fontSize\"\r\n\t\t\t\t:duration=\"duration\"\r\n\t\t\t\t@close=\"close\"\r\n\t\t\t\t@click=\"click\"\r\n\t\t\t\t@change=\"change\"\r\n\t\t\t></uv-column-notice>\r\n\t\t</template>\r\n\t\t<template v-else>\r\n\t\t\t<uv-row-notice\r\n\t\t\t\t:color=\"color\"\r\n\t\t\t\t:bgColor=\"bgColor\"\r\n\t\t\t\t:text=\"text\"\r\n\t\t\t\t:mode=\"mode\"\r\n\t\t\t\t:fontSize=\"fontSize\"\r\n\t\t\t\t:speed=\"speed\"\r\n\t\t\t\t:url=\"url\"\r\n\t\t\t\t:linkType=\"linkType\"\r\n\t\t\t\t:icon=\"icon\"\r\n\t\t\t\t@close=\"close\"\r\n\t\t\t\t@click=\"click\"\r\n\t\t\t></uv-row-notice>\r\n\t\t</template>\r\n\t</view>\r\n</template>\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\r\n\t/**\r\n\t * noticeBar 滚动通知\r\n\t * @description 该组件用于滚动通告场景，有多种模式可供选择\r\n\t * @tutorial https://www.uvui.cn/components/noticeBar.html\r\n\t * @property {Array | String}\ttext\t\t\t显示的内容，数组\r\n\t * @property {String}\t\t\tdirection\t\t通告滚动模式，row-横向滚动，column-竖向滚动 ( 默认 'row' )\r\n\t * @property {Boolean}\t\t\tstep\t\t\tdirection = row时，是否使用步进形式滚动  ( 默认 false )\r\n\t * @property {String}\t\t\ticon\t\t\t是否显示左侧的音量图标 ( 默认 'volume' )\r\n\t * @property {String}\t\t\tmode\t\t\t通告模式，link-显示右箭头，closable-显示右侧关闭图标\r\n\t * @property {String}\t\t\tcolor\t\t\t文字颜色，各图标也会使用文字颜色 ( 默认 '#f9ae3d' )\r\n\t * @property {String}\t\t\tbgColor\t\t\t背景颜色 ( 默认 '#fdf6ec' )\r\n\t * @property {String | Number}\tspeed\t\t\t水平滚动时的滚动速度，即每秒滚动多少px(px)，这有利于控制文字无论多少时，都能有一个恒定的速度 ( 默认 80 )\r\n\t * @property {String | Number}\tfontSize\t\t字体大小 ( 默认 14 )\r\n\t * @property {String | Number}\tduration\t\t滚动一个周期的时间长，单位ms ( 默认 2000 )\r\n\t * @property {Boolean}\t\t\tdisableTouch\t是否禁止用手滑动切换 目前HX2.6.11，只支持App 2.5.5+、H5 2.5.5+、支付宝小程序、字节跳动小程序（默认34） ( 默认 true )\r\n\t * @property {String}\t\t\turl\t\t\t\t跳转的页面路径\r\n\t * @property {String}\t\t\tlinkType\t\t页面跳转的类型 ( 默认 navigateTo )\r\n\t * @property {Object}\t\t\tcustomStyle\t\t定义需要用到的外部样式\r\n\t * \r\n\t * @event {Function}\t\t\tclick\t\t\t点击通告文字触发\r\n\t * @event {Function}\t\t\tclose\t\t\t点击右侧关闭图标触发\r\n\t * @example <uv-notice-bar :more-icon=\"true\" :list=\"list\"></uv-notice-bar>\r\n\t */\r\n\texport default {\r\n\t\tname: \"uv-notice-bar\",\r\n\t\temits: ['click','close','change'],\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tshow: true\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\t// 点击通告栏\r\n\t\t\tclick(index) {\r\n\t\t\t\tthis.$emit('click', index)\r\n\t\t\t\tif (this.url && this.linkType) {\r\n\t\t\t\t\t// 此方法写在mixin中，另外跳转的url和linkType参数也在mixin的props中\r\n\t\t\t\t\tthis.openPage()\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t// 点击关闭按钮\r\n\t\t\tclose() {\r\n\t\t\t\tthis.show = false\r\n\t\t\t\tthis.$emit('close')\r\n\t\t\t},\r\n\t\t\t// 竖向滚动时触发\r\n\t\t\tchange(index){\r\n\t\t\t\tthis.$emit('change',index)\r\n\t\t\t}\r\n\t\t}\r\n\t};\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\r\n\t.uv-notice-bar {\r\n\t\toverflow: hidden;\r\n\t\tpadding: 9px 12px;\r\n\t\tflex: 1;\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-notice-bar/components/uv-row-notice/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 显示的内容，字符串\r\n\t\ttext: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 是否显示左侧的音量图标\r\n\t\ticon: {\r\n\t\t\ttype: [String, Boolean, null],\r\n\t\t\tdefault: 'volume'\r\n\t\t},\r\n\t\t// 通告模式，link-显示右箭头，closable-显示右侧关闭图标\r\n\t\tmode: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 文字颜色，各图标也会使用文字颜色\r\n\t\tcolor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#f9ae3d'\r\n\t\t},\r\n\t\t// 背景颜色\r\n\t\tbgColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#fdf6ec'\r\n\t\t},\r\n\t\t// 字体大小，单位px\r\n\t\tfontSize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 14\r\n\t\t},\r\n\t\t// 水平滚动时的滚动速度，即每秒滚动多少px(rpx)，这有利于控制文字无论多少时，都能有一个恒定的速度\r\n\t\tspeed: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 80\r\n\t\t},\r\n\t\t...uni.$uv?.props?.rowNotice\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-notice-bar/components/uv-row-notice/uv-row-notice.vue",
    "content": "<template>\r\n\t<view\r\n\t\tclass=\"uv-notice\"\r\n\t\t@tap=\"clickHandler\"\r\n\t>\r\n\t\t<slot name=\"icon\">\r\n\t\t\t<view\r\n\t\t\t\tclass=\"uv-notice__left-icon\"\r\n\t\t\t\tv-if=\"icon\"\r\n\t\t\t>\r\n\t\t\t\t<uv-icon\r\n\t\t\t\t\t:name=\"icon\"\r\n\t\t\t\t\t:color=\"color\"\r\n\t\t\t\t\tsize=\"19\"\r\n\t\t\t\t></uv-icon>\r\n\t\t\t</view>\r\n\t\t</slot>\r\n\t\t<view\r\n\t\t\tclass=\"uv-notice__content\"\r\n\t\t\tref=\"uv-notice__content\"\r\n\t\t>\r\n\t\t\t<view\r\n\t\t\t\tref=\"uv-notice__content__text\"\r\n\t\t\t\tclass=\"uv-notice__content__text\"\r\n\t\t\t\t:style=\"[animationStyle]\"\r\n\t\t\t>\r\n\t\t\t\t<text\r\n\t\t\t\t\tv-for=\"(item, index) in innerText\"\r\n\t\t\t\t\t:key=\"index\"\r\n\t\t\t\t\t:style=\"[textStyle]\"\r\n\t\t\t\t>{{item}}</text>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t\t<view\r\n\t\t\tclass=\"uv-notice__right-icon\"\r\n\t\t\tv-if=\"['link', 'closable'].includes(mode)\"\r\n\t\t>\r\n\t\t\t<uv-icon\r\n\t\t\t\tv-if=\"mode === 'link'\"\r\n\t\t\t\tname=\"arrow-right\"\r\n\t\t\t\t:size=\"17\"\r\n\t\t\t\t:color=\"color\"\r\n\t\t\t></uv-icon>\r\n\t\t\t<uv-icon\r\n\t\t\t\tv-if=\"mode === 'closable'\"\r\n\t\t\t\t@click=\"close\"\r\n\t\t\t\tname=\"close\"\r\n\t\t\t\t:size=\"16\"\r\n\t\t\t\t:color=\"color\"\r\n\t\t\t></uv-icon>\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t// #ifdef APP-NVUE\r\n\tconst animation = uni.requireNativePlugin('animation')\r\n\tconst dom = uni.requireNativePlugin('dom')\r\n\t// #endif\r\n\t/**\r\n\t * RowNotice 滚动通知中的水平滚动模式\r\n\t * @description 水平滚动\r\n\t * @tutorial https://www.uvui.cn/components/noticeBar.html\r\n\t * @property {String | Number}\ttext\t\t\t显示的内容，字符串\r\n\t * @property {String}\t\t\ticon\t\t\t是否显示左侧的音量图标 (默认 'volume' )\r\n\t * @property {String}\t\t\tmode\t\t\t通告模式，link-显示右箭头，closable-显示右侧关闭图标\r\n\t * @property {String}\t\t\tcolor\t\t\t文字颜色，各图标也会使用文字颜色 (默认 '#f9ae3d' )\r\n\t * @property {String}\t\t\tbgColor\t\t\t背景颜色 (默认 ''#fdf6ec' )\r\n\t * @property {String | Number}\tfontSize\t\t字体大小，单位px (默认 14 )\r\n\t * @property {String | Number}\tspeed\t\t\t水平滚动时的滚动速度，即每秒滚动多少px(rpx)，这有利于控制文字无论多少时，都能有一个恒定的速度  (默认 80 )\r\n\t * \r\n\t * @event {Function} click 点击通告文字触发\r\n\t * @event {Function} close 点击右侧关闭图标触发\r\n\t * @example \r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-row-notice',\r\n\t\temits: ['click','close'],\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tanimationDuration: '0', // 动画执行时间\r\n\t\t\t\tanimationPlayState: 'paused', // 动画的开始和结束执行\r\n\t\t\t\t// nvue下，内容发生变化，导致滚动宽度也变化，需要标志为是否需要重新计算宽度\r\n\t\t\t\t// 不能在内容变化时直接重新计算，因为nvue的animation模块上一次的滚动不是刚好结束，会有影响\r\n\t\t\t\tnvueInit: true,\r\n\t\t\t\tshow: true\r\n\t\t\t};\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\ttext: {\r\n\t\t\t\timmediate: true,\r\n\t\t\t\thandler(newValue, oldValue) {\r\n\t\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\t\tthis.nvueInit = true\r\n\t\t\t\t\t// #endif\r\n\t\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\t\tthis.vue()\r\n\t\t\t\t\t// #endif\r\n\t\t\t\t\t\r\n\t\t\t\t\tif(!this.$uv.test.string(newValue)) {\r\n\t\t\t\t\t\tthis.$uv.error('noticebar组件direction为row时，要求text参数为字符串形式')\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tfontSize() {\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\tthis.nvueInit = true\r\n\t\t\t\t// #endif\r\n\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\tthis.vue()\r\n\t\t\t\t// #endif\r\n\t\t\t},\r\n\t\t\tspeed() {\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\tthis.nvueInit = true\r\n\t\t\t\t// #endif\r\n\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\tthis.vue()\r\n\t\t\t\t// #endif\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\t// 文字内容的样式\r\n\t\t\ttextStyle() {\r\n\t\t\t\tlet style = {}\r\n\t\t\t\tstyle.color = this.color\r\n\t\t\t\tstyle.fontSize = this.$uv.addUnit(this.fontSize)\r\n\t\t\t\treturn style\r\n\t\t\t},\r\n\t\t\tanimationStyle() {\r\n\t\t\t\tlet style = {}\r\n\t\t\t\tstyle.animationDuration = this.animationDuration\r\n\t\t\t\tstyle.animationPlayState = this.animationPlayState\r\n\t\t\t\treturn style\r\n\t\t\t},\r\n\t\t\t// 内部对用户传入的数据进一步分割，放到多个text标签循环，否则如果用户传入的字符串很长（100个字符以上）\r\n\t\t\t// 放在一个text标签中进行滚动，在低端安卓机上，动画可能会出现抖动现象，需要分割到多个text中可解决此问题\r\n\t\t\tinnerText() {\r\n\t\t\t\tlet result = [],\r\n\t\t\t\t\t// 每组text标签的字符长度\r\n\t\t\t\t\tlen = 20\r\n\t\t\t\tconst textArr = this.text? this.text.split(''):[]\r\n\t\t\t\tfor (let i = 0; i < textArr.length; i += len) {\r\n\t\t\t\t\t// 对拆分的后的text进行slice分割，得到的为数组再进行join拼接为字符串\r\n\t\t\t\t\tresult.push(textArr.slice(i, i + len).join(''))\r\n\t\t\t\t}\r\n\t\t\t\treturn result\r\n\t\t\t}\r\n\t\t},\r\n\t\tmounted() {\r\n\t\t\t// #ifdef APP-PLUS\r\n\t\t\t// 在APP上(含nvue)，监听当前webview是否处于隐藏状态(进入下一页时即为hide状态)\r\n\t\t\t// 如果webivew隐藏了，为了节省性能的损耗，应停止动画的执行，同时也是为了保持进入下一页返回后，滚动位置保持不变\r\n\t\t\tvar pages = getCurrentPages()\r\n\t\t\tvar page = pages[pages.length - 1]\r\n\t\t\tvar currentWebview = page.$getAppWebview()\r\n\t\t\tcurrentWebview.addEventListener('hide', () => {\r\n\t\t\t\tthis.webviewHide = true\r\n\t\t\t})\r\n\t\t\tcurrentWebview.addEventListener('show', () => {\r\n\t\t\t\tthis.webviewHide = false\r\n\t\t\t})\r\n\t\t\t// #endif\r\n\r\n\t\t\tthis.init()\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tinit() {\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\tthis.nvue()\r\n\t\t\t\t// #endif\r\n\r\n\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\tthis.vue()\r\n\t\t\t\t// #endif\r\n\t\t\t\t\r\n\t\t\t\tif(!this.$uv.test.string(this.text)) {\r\n\t\t\t\t\tthis.$uv.error('noticebar组件direction为row时，要求text参数为字符串形式')\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t// vue版处理\r\n\t\t\tasync vue() {\r\n\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\tlet boxWidth = 0,\r\n\t\t\t\t\ttextWidth = 0\r\n\t\t\t\t// 进行一定的延时\r\n\t\t\t\tawait this.$uv.sleep()\r\n\t\t\t\t// 查询盒子和文字的宽度\r\n\t\t\t\ttextWidth = (await this.$uvGetRect('.uv-notice__content__text')).width\r\n\t\t\t\tboxWidth = (await this.$uvGetRect('.uv-notice__content')).width\r\n\t\t\t\t// 根据t=s/v(时间=路程/速度)，这里为何不需要加上#uv-notice-box的宽度，因为中设置了.uv-notice-content样式中设置了padding-left: 100%\r\n\t\t\t\t// 恰巧计算出来的结果中已经包含了#uv-notice-box的宽度\r\n\t\t\t\tthis.animationDuration = `${textWidth / this.$uv.getPx(this.speed)}s`\r\n\t\t\t\t// 这里必须这样开始动画，否则在APP上动画速度不会改变\r\n\t\t\t\tthis.animationPlayState = 'paused'\r\n\t\t\t\tsetTimeout(() => {\r\n\t\t\t\t\tthis.animationPlayState = 'running'\r\n\t\t\t\t}, 10)\r\n\t\t\t\t// #endif\r\n\t\t\t},\r\n\t\t\t// nvue版处理\r\n\t\t\tasync nvue() {\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\tthis.nvueInit = false\r\n\t\t\t\tlet boxWidth = 0,\r\n\t\t\t\t\ttextWidth = 0\r\n\t\t\t\t// 进行一定的延时\r\n\t\t\t\tawait this.$uv.sleep()\r\n\t\t\t\t// 查询盒子和文字的宽度\r\n\t\t\t\ttextWidth = (await this.getNvueRect('uv-notice__content__text')).width\r\n\t\t\t\tboxWidth = (await this.getNvueRect('uv-notice__content')).width\r\n\t\t\t\t// 将文字移动到盒子的右边沿，之所以需要这么做，是因为nvue不支持100%单位，否则可以通过css设置\r\n\t\t\t\tanimation.transition(this.$refs['uv-notice__content__text'], {\r\n\t\t\t\t\tstyles: {\r\n\t\t\t\t\t\ttransform: `translateX(${boxWidth}px)`\r\n\t\t\t\t\t},\r\n\t\t\t\t}, () => {\r\n\t\t\t\t\t// 如果非禁止动画，则开始滚动\r\n\t\t\t\t\t!this.stopAnimation && this.loopAnimation(textWidth, boxWidth)\r\n\t\t\t\t});\r\n\t\t\t\t// #endif\r\n\t\t\t},\r\n\t\t\tloopAnimation(textWidth, boxWidth) {\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\tanimation.transition(this.$refs['uv-notice__content__text'], {\r\n\t\t\t\t\tstyles: {\r\n\t\t\t\t\t\t// 目标移动终点为-textWidth，也即当文字的最右边贴到盒子的左边框的位置\r\n\t\t\t\t\t\ttransform: `translateX(-${textWidth}px)`\r\n\t\t\t\t\t},\r\n\t\t\t\t\t// 滚动时间的计算为，时间 = 路程(boxWidth + textWidth) / 速度，最后转为毫秒\r\n\t\t\t\t\tduration: (boxWidth + textWidth) / this.$uv.getPx(this.speed) * 1000,\r\n\t\t\t\t\tdelay: 10\r\n\t\t\t\t}, () => {\r\n\t\t\t\t\tanimation.transition(this.$refs['uv-notice__content__text'], {\r\n\t\t\t\t\t\tstyles: {\r\n\t\t\t\t\t\t\t// 重新将文字移动到盒子的右边沿\r\n\t\t\t\t\t\t\ttransform: `translateX(${this.stopAnimation ? 0 : boxWidth}px)`\r\n\t\t\t\t\t\t},\r\n\t\t\t\t\t}, () => {\r\n\t\t\t\t\t\t// 如果非禁止动画，则继续下一轮滚动\r\n\t\t\t\t\t\tif (!this.stopAnimation) {\r\n\t\t\t\t\t\t\t// 判断是否需要初始化计算尺寸\r\n\t\t\t\t\t\t\tif (this.nvueInit) {\r\n\t\t\t\t\t\t\t\tthis.nvue()\r\n\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\tthis.loopAnimation(textWidth, boxWidth)\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t});\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\t\t\t},\r\n\t\t\tgetNvueRect(el) {\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\t// 返回一个promise\r\n\t\t\t\treturn new Promise(resolve => {\r\n\t\t\t\t\tdom.getComponentRect(this.$refs[el], (res) => {\r\n\t\t\t\t\t\tresolve(res.size)\r\n\t\t\t\t\t})\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\t\t\t},\r\n\t\t\t// 点击通告栏\r\n\t\t\tclickHandler(index) {\r\n\t\t\t\tthis.$emit('click')\r\n\t\t\t},\r\n\t\t\t// 点击右侧按钮，需要判断点击的是关闭图标还是箭头图标\r\n\t\t\tclose() {\r\n\t\t\t\tthis.$emit('close')\r\n\t\t\t}\r\n\t\t},\r\n\t\t// #ifdef APP-NVUE\r\n\t\t// #ifdef VUE2\r\n\t\tbeforeDestroy() {\r\n\t\t\tthis.stopAnimation = true\r\n\t\t},\r\n\t\t// #endif\r\n\t\t// #ifdef VUE3\r\n\t\tunmounted() {\r\n\t\t\tthis.stopAnimation = true\r\n\t\t}\r\n\t\t// #endif\r\n\t\t// #endif\r\n\t};\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t.uv-notice {\r\n\t\t@include flex;\r\n\t\talign-items: center;\r\n\t\tjustify-content: space-between;\r\n\r\n\t\t&__left-icon {\r\n\t\t\talign-items: center;\r\n\t\t\tmargin-right: 5px;\r\n\t\t}\r\n\r\n\t\t&__right-icon {\r\n\t\t\tmargin-left: 5px;\r\n\t\t\talign-items: center;\r\n\t\t}\r\n\r\n\t\t&__content {\r\n\t\t\ttext-align: right;\r\n\t\t\tflex: 1;\r\n\t\t\t@include flex;\r\n\t\t\tflex-wrap: nowrap;\r\n\t\t\toverflow: hidden;\r\n\r\n\t\t\t&__text {\r\n\t\t\t\tfont-size: 14px;\r\n\t\t\t\tcolor: $uv-warning;\r\n\t\t\t\t/* #ifndef APP-NVUE */\r\n\t\t\t\t// 这一句很重要，为了能让滚动左右连接起来\r\n\t\t\t\tpadding-left: 100%;\r\n\t\t\t\tword-break: keep-all;\r\n\t\t\t\twhite-space: nowrap;\r\n\t\t\t\tanimation: uv-loop-animation 10s linear infinite both;\r\n\t\t\t\t/* #endif */\r\n\t\t\t\t@include flex(row);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t}\r\n\t/* #ifndef APP-NVUE */\r\n\t@keyframes uv-loop-animation {\r\n\t\t0% {\r\n\t\t\ttransform: translate3d(0, 0, 0);\r\n\t\t}\r\n\r\n\t\t100% {\r\n\t\t\ttransform: translate3d(-100%, 0, 0);\r\n\t\t}\r\n\t}\r\n\t/* #endif */\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-notice-bar/package.json",
    "content": "{\r\n  \"id\": \"uv-notice-bar\",\r\n  \"displayName\": \"uv-notice-bar 滚动通知 全面兼容vue3+2、app、h5、小程序等多端\",\r\n  \"version\": \"1.0.7\",\r\n  \"description\": \"uv-notice-bar 该组件用于滚动通告场景，有多种模式可供选择。\",\r\n  \"keywords\": [\r\n    \"uv-notice-bar\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"notice\",\r\n    \"滚动公告\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-icon\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-notice-bar/readme.md",
    "content": "## NoticeBar 滚动通知\r\n\r\n> **组件名：uv-notice-bar**\r\n\r\n该组件用于滚动通告场景，有多种模式可供选择。灵活配置，开箱即用。\r\n\r\n# <a href=\"https://www.uvui.cn/components/noticeBar.html\" target=\"_blank\">查看文档</a>\r\n\r\n## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small style=\"font-size:14px;font-weight:700;\">（请不要 下载插件ZIP）</small>\r\n\r\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n<a href=\"https://ext.dcloud.net.cn/plugin?name=uv-ui\" target=\"_blank\">\r\n\r\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\r\n\r\n</a>\r\n\r\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-notify/changelog.md",
    "content": "## 1.0.3（2023-10-13）\n1. unmounted兼容vue3\n## 1.0.2（2023-07-02）\r\nuv-notify  由于弹出层uv-popup的修改，打开和关闭方法更改，详情参考文档：https://www.uvui.cn/components/notify.html\r\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-notify 消息提示\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-notify/components/uv-notify/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 到顶部的距离\r\n\t\ttop: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// type主题，primary，success，warning，error\r\n\t\ttype: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'primary'\r\n\t\t},\r\n\t\t// 字体颜色\r\n\t\tcolor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#ffffff'\r\n\t\t},\r\n\t\t// 背景颜色\r\n\t\tbgColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 展示的文字内容\r\n\t\tmessage: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 展示时长，为0时不消失，单位ms\r\n\t\tduration: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 3000\r\n\t\t},\r\n\t\t// 字体大小\r\n\t\tfontSize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 15\r\n\t\t},\r\n\t\t// 是否留出顶部安全距离（状态栏高度）\r\n\t\tsafeAreaInsetTop: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t...uni.$uv?.props?.notify\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-notify/components/uv-notify/uv-notify.vue",
    "content": "<template>\r\n\t<uv-transition\r\n\t\tmode=\"slide-top\"\r\n\t\t:customStyle=\"containerStyle\"\r\n\t\t:show=\"open\"\r\n\t>\r\n\t\t<view\r\n\t\t\tclass=\"uv-notify\"\r\n\t\t\t:class=\"[`uv-notify--${tmpConfig.type}`]\"\r\n\t\t\t:style=\"[backgroundColor, $uv.addStyle(customStyle)]\"\r\n\t\t>\r\n\t\t\t<uv-status-bar v-if=\"tmpConfig.safeAreaInsetTop\"></uv-status-bar>\r\n\t\t\t<view class=\"uv-notify__warpper\">\r\n\t\t\t\t<slot name=\"icon\">\r\n\t\t\t\t\t<uv-icon\r\n\t\t\t\t\t\tv-if=\"['success', 'warning', 'error'].includes(tmpConfig.type)\"\r\n\t\t\t\t\t\t:name=\"tmpConfig.icon\"\r\n\t\t\t\t\t\t:color=\"tmpConfig.color\"\r\n\t\t\t\t\t\t:size=\"1.3 * tmpConfig.fontSize\"\r\n\t\t\t\t\t\t:customStyle=\"{marginRight: '4px'}\"\r\n\t\t\t\t\t></uv-icon>\r\n\t\t\t\t</slot>\r\n\t\t\t\t<text\r\n\t\t\t\t\tclass=\"uv-notify__warpper__text\"\r\n\t\t\t\t\t:style=\"{\r\n\t\t\t\t\t\tfontSize: $uv.addUnit(tmpConfig.fontSize),\r\n\t\t\t\t\t\tcolor: tmpConfig.color\r\n\t\t\t\t\t}\"\r\n\t\t\t\t>{{ tmpConfig.message }}</text>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t</uv-transition>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * notify 顶部提示\r\n\t * @description 该组件一般用于页面顶部向下滑出一个提示，尔后自动收起的场景\r\n\t * @tutorial\r\n\t * @property {String | Number}\ttop\t\t\t\t\t到顶部的距离 ( 默认 0 )\r\n\t * @property {String}\t\t\ttype\t\t\t\t主题，primary，success，warning，error ( 默认 'primary' )\r\n\t * @property {String}\t\t\tcolor\t\t\t\t字体颜色 ( 默认 '#ffffff' )\r\n\t * @property {String}\t\t\tbgColor\t\t\t\t背景颜色\r\n\t * @property {String}\t\t\tmessage\t\t\t\t展示的文字内容\r\n\t * @property {String | Number}\tduration\t\t\t展示时长，为0时不消失，单位ms ( 默认 3000 )\r\n\t * @property {String | Number}\tfontSize\t\t\t字体大小 ( 默认 15 )\r\n\t * @property {Boolean}\t\t\tsafeAreaInsetTop\t是否留出顶部安全距离（状态栏高度） ( 默认 false )\r\n\t * @property {Object}\t\t\tcustomStyle\t\t\t组件的样式，对象形式\r\n\t * @event {Function}\topen\t开启组件时调用的函数\r\n\t * @event {Function}\tclose\t关闭组件式调用的函数\r\n\t * @example <uv-notify message=\"Hi uvui\"></uv-notify>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-notify',\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\t// 是否展示组件\r\n\t\t\t\topen: false,\r\n\t\t\t\ttimer: null,\r\n\t\t\t\tconfig: {\r\n\t\t\t\t\t// 到顶部的距离\r\n\t\t\t\t\ttop: this.top,\r\n\t\t\t\t\t// type主题，primary，success，warning，error\r\n\t\t\t\t\ttype: this.type,\r\n\t\t\t\t\t// 字体颜色\r\n\t\t\t\t\tcolor: this.color,\r\n\t\t\t\t\t// 背景颜色\r\n\t\t\t\t\tbgColor: this.bgColor,\r\n\t\t\t\t\t// 展示的文字内容\r\n\t\t\t\t\tmessage: this.message,\r\n\t\t\t\t\t// 展示时长，为0时不消失，单位ms\r\n\t\t\t\t\tduration: this.duration,\r\n\t\t\t\t\t// 字体大小\r\n\t\t\t\t\tfontSize: this.fontSize,\r\n\t\t\t\t\t// 是否留出顶部安全距离（状态栏高度）\r\n\t\t\t\t\tsafeAreaInsetTop: this.safeAreaInsetTop\r\n\t\t\t\t},\r\n\t\t\t\t// 合并后的配置，避免多次调用组件后，可能会复用之前使用的配置参数\r\n\t\t\t\ttmpConfig: {}\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\tcontainerStyle() {\r\n\t\t\t\tlet top = 0\r\n\t\t\t\tif (this.tmpConfig.top === 0) {\r\n\t\t\t\t\t// #ifdef H5\r\n\t\t\t\t\t// H5端，导航栏为普通元素，需要将组件移动到导航栏的下边沿\r\n\t\t\t\t\t// H5的导航栏高度为44px\r\n\t\t\t\t\ttop = 44\r\n\t\t\t\t\t// #endif\r\n\t\t\t\t}\r\n\t\t\t\tconst style = {\r\n\t\t\t\t\ttop: this.$uv.addUnit(this.tmpConfig.top === 0 ? top : this.tmpConfig.top),\r\n\t\t\t\t\t// 因为组件底层为uv-transition组件，必须将其设置为fixed定位\r\n\t\t\t\t\t// 让其出现在导航栏底部\r\n\t\t\t\t\tposition: 'fixed',\r\n\t\t\t\t\tleft: 0,\r\n\t\t\t\t\tright: 0,\r\n\t\t\t\t\tzIndex: 10076\r\n\t\t\t\t}\r\n\t\t\t\treturn style\r\n\t\t\t},\r\n\t\t\t// 组件背景颜色\r\n\t\t\tbackgroundColor() {\r\n\t\t\t\tconst style = {}\r\n\t\t\t\tif (this.tmpConfig.bgColor) {\r\n\t\t\t\t\tstyle.backgroundColor = this.tmpConfig.bgColor\r\n\t\t\t\t}\r\n\t\t\t\treturn style\r\n\t\t\t},\r\n\t\t\t// 默认主题下的图标\r\n\t\t\ticon() {\r\n\t\t\t\tlet icon\r\n\t\t\t\tif (this.tmpConfig.type === 'success') {\r\n\t\t\t\t\ticon = 'checkmark-circle'\r\n\t\t\t\t} else if (this.tmpConfig.type === 'error') {\r\n\t\t\t\t\ticon = 'close-circle'\r\n\t\t\t\t} else if (this.tmpConfig.type === 'warning') {\r\n\t\t\t\t\ticon = 'error-circle'\r\n\t\t\t\t}\r\n\t\t\t\treturn icon\r\n\t\t\t}\r\n\t\t},\r\n\t\tcreated() {\r\n\t\t\t// 通过主题的形式调用toast，批量生成方法函数\r\n\t\t\t['primary', 'success', 'error', 'warning'].map(item => {\r\n\t\t\t\tthis[item] = message => this.show({\r\n\t\t\t\t\ttype: item,\r\n\t\t\t\t\tmessage\r\n\t\t\t\t})\r\n\t\t\t})\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tshow(options) {\r\n\t\t\t\t// 不将结果合并到this.config变量，避免多次调用uv-toast，前后的配置造成混乱\r\n\t\t\t\tthis.tmpConfig = this.$uv.deepMerge(this.config, options)\r\n\t\t\t\t// 任何定时器初始化之前，都要执行清除操作，否则可能会造成混乱\r\n\t\t\t\tthis.clearTimer()\r\n\t\t\t\tthis.open = true\r\n\t\t\t\tif (this.tmpConfig.duration > 0) {\r\n\t\t\t\t\tthis.timer = setTimeout(() => {\r\n\t\t\t\t\t\tthis.open = false\r\n\t\t\t\t\t\t// 倒计时结束，清除定时器，隐藏toast组件\r\n\t\t\t\t\t\tthis.clearTimer()\r\n\t\t\t\t\t\t// 判断是否存在callback方法，如果存在就执行\r\n\t\t\t\t\t\ttypeof(this.tmpConfig.complete) === 'function' && this.tmpConfig.complete()\r\n\t\t\t\t\t}, this.tmpConfig.duration)\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t// 关闭notify\r\n\t\t\tclose() {\r\n\t\t\t\tthis.clearTimer()\r\n\t\t\t},\r\n\t\t\tclearTimer() {\r\n\t\t\t\tthis.open = false\r\n\t\t\t\t// 清除定时器\r\n\t\t\t\tclearTimeout(this.timer)\r\n\t\t\t\tthis.timer = null\r\n\t\t\t}\r\n\t\t},\r\n\t\t// #ifdef VUE2\r\n\t\tbeforeDestroy() {\r\n\t\t\tthis.clearTimer()\r\n\t\t},\r\n\t\t// #endif\r\n\t\t// #ifdef VUE3\r\n\t\tunmounted() {\r\n\t\t\tthis.clearTimer()\r\n\t\t}\r\n\t\t// #endif\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t$uv-notify-padding: 8px 10px !default;\r\n\t$uv-notify-text-font-size: 15px !default;\r\n\t$uv-notify-primary-bgColor: $uv-primary !default;\r\n\t$uv-notify-success-bgColor: $uv-success !default;\r\n\t$uv-notify-error-bgColor: $uv-error !default;\r\n\t$uv-notify-warning-bgColor: $uv-warning !default;\r\n\r\n\r\n\t.uv-notify {\r\n\t\tpadding: $uv-notify-padding;\r\n\r\n\t\t&__warpper {\r\n\t\t\t@include flex;\r\n\t\t\talign-items: center;\r\n\t\t\ttext-align: center;\r\n\t\t\tjustify-content: center;\r\n\r\n\t\t\t&__text {\r\n\t\t\t\tfont-size: $uv-notify-text-font-size;\r\n\t\t\t\ttext-align: center;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t&--primary {\r\n\t\t\tbackground-color: $uv-notify-primary-bgColor;\r\n\t\t}\r\n\r\n\t\t&--success {\r\n\t\t\tbackground-color: $uv-notify-success-bgColor;\r\n\t\t}\r\n\r\n\t\t&--error {\r\n\t\t\tbackground-color: $uv-notify-error-bgColor;\r\n\t\t}\r\n\r\n\t\t&--warning {\r\n\t\t\tbackground-color: $uv-notify-warning-bgColor;\r\n\t\t}\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-notify/package.json",
    "content": "{\r\n  \"id\": \"uv-notify\",\r\n  \"displayName\": \"uv-notify 消息提示  全面兼容小程序、nvue、vue2、vue3等多端\",\r\n  \"version\": \"1.0.3\",\r\n  \"description\": \"uv-notify 该组件一般用于页面顶部向下滑出一个提示，后自动收起的场景。\",\r\n  \"keywords\": [\r\n    \"uv-notify\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"notify\",\r\n    \"消息提示\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-status-bar\",\r\n\t\t\t\"uv-overlay\",\r\n\t\t\t\"uv-transition\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-notify/readme.md",
    "content": "## Notify 消息提示\r\n\r\n> **组件名：uv-notify**\r\n\r\n该组件一般用于页面顶部向下滑出一个提示，后自动收起的场景。\r\n\r\n### <a href=\"https://www.uvui.cn/components/notify.html\" target=\"_blank\">查看文档</a>\r\n\r\n### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n#### 如使用过程中有任何问题，或者您对uv-ui有一些好的建议，欢迎加入 uv-ui 交流群：<a href=\"https://ext.dcloud.net.cn/plugin?id=12287\" target=\"_blank\">uv-ui</a>、<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-number-box/changelog.md",
    "content": "## 1.0.2（2023-07-13）\n1. 修复  uv-number-box设置value属性不生效的BUG\n## 1.0.1（2023-05-16）\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\n2. 优化部分功能\n## 1.0.0（2023-05-10）\r\nuv-number-box 步进器\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-number-box/components/uv-number-box/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\tvalue: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\tmodelValue: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 步进器标识符，在change回调返回\r\n\t\tname: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 最小值\r\n\t\tmin: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 1\r\n\t\t},\r\n\t\t// 最大值\r\n\t\tmax: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: Number.MAX_SAFE_INTEGER\r\n\t\t},\r\n\t\t// 加减的步长，可为小数\r\n\t\tstep: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 1\r\n\t\t},\r\n\t\t// 是否只允许输入整数\r\n\t\tinteger: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否禁用，包括输入框，加减按钮\r\n\t\tdisabled: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否禁用输入框\r\n\t\tdisabledInput: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否开启异步变更，开启后需要手动控制输入值\r\n\t\tasyncChange: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 输入框宽度，单位为px\r\n\t\tinputWidth: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 35\r\n\t\t},\r\n\t\t// 是否显示减少按钮\r\n\t\tshowMinus: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 是否显示增加按钮\r\n\t\tshowPlus: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 显示的小数位数\r\n\t\tdecimalLength: {\r\n\t\t\ttype: [String, Number, null],\r\n\t\t\tdefault: null\r\n\t\t},\r\n\t\t// 是否开启长按加减手势\r\n\t\tlongPress: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 输入框文字和加减按钮图标的颜色\r\n\t\tcolor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#323233'\r\n\t\t},\r\n\t\t// 按钮大小，宽高等于此值，单位px，输入框高度和此值保持一致\r\n\t\tbuttonSize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 30\r\n\t\t},\r\n\t\t// 输入框和按钮的背景颜色\r\n\t\tbgColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#EBECEE'\r\n\t\t},\r\n\t\t// 指定光标于键盘的距离，避免键盘遮挡输入框，单位px\r\n\t\tcursorSpacing: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 100\r\n\t\t},\r\n\t\t// 是否禁用增加按钮\r\n\t\tdisablePlus: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否禁用减少按钮\r\n\t\tdisableMinus: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 加减按钮图标的样式\r\n\t\ticonStyle: {\r\n\t\t\ttype: [Object, String],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t...uni.$uv?.props?.numberBox\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-number-box/components/uv-number-box/uv-number-box.vue",
    "content": "<template>\r\n\t<view class=\"uv-number-box\">\r\n\t\t<view\r\n\t\t  class=\"uv-number-box__slot\"\r\n\t\t  @tap.stop=\"clickHandler('minus')\"\r\n\t\t  @touchstart=\"onTouchStart('minus')\"\r\n\t\t  @touchend.stop=\"clearTimeout\"\r\n\t\t  v-if=\"showMinus && $slots.minus\"\r\n\t\t>\r\n\t\t\t<slot name=\"minus\" />\r\n\t\t</view>\r\n\t\t<view\r\n\t\t  v-else-if=\"showMinus\"\r\n\t\t  class=\"uv-number-box__minus\"\r\n\t\t  @tap.stop=\"clickHandler('minus')\"\r\n\t\t  @touchstart=\"onTouchStart('minus')\"\r\n\t\t  @touchend.stop=\"clearTimeout\"\r\n\t\t  hover-class=\"uv-number-box__minus--hover\"\r\n\t\t  hover-stay-time=\"150\"\r\n\t\t  :class=\"{ 'uv-number-box__minus--disabled': isDisabled('minus') }\"\r\n\t\t  :style=\"[buttonStyle('minus')]\"\r\n\t\t>\r\n\t\t\t<uv-icon\r\n\t\t\t  name=\"minus\"\r\n\t\t\t  :color=\"isDisabled('minus') ? '#c8c9cc' : '#323233'\"\r\n\t\t\t  size=\"15\"\r\n\t\t\t  bold\r\n\t\t\t\t:customStyle=\"iconStyle\"\r\n\t\t\t></uv-icon>\r\n\t\t</view>\r\n\t\t<slot name=\"input\">\r\n\t\t\t<input\r\n\t\t\t  :disabled=\"disabledInput || disabled\"\r\n\t\t\t  :cursor-spacing=\"getCursorSpacing\"\r\n\t\t\t  :class=\"{ 'uv-number-box__input--disabled': disabled || disabledInput }\"\r\n\t\t\t  v-model=\"currentValue\"\r\n\t\t\t  class=\"uv-number-box__input\"\r\n\t\t\t  @blur=\"onBlur\"\r\n\t\t\t  @focus=\"onFocus\"\r\n\t\t\t  @input=\"onInput\"\r\n\t\t\t  type=\"number\"\r\n\t\t\t  :style=\"[inputStyle]\"\r\n\t\t\t/>\r\n\t\t</slot>\r\n\t\t<view\r\n\t\t  class=\"uv-number-box__slot\"\r\n\t\t  @tap.stop=\"clickHandler('plus')\"\r\n\t\t  @touchstart=\"onTouchStart('plus')\"\r\n\t\t  @touchend.stop=\"clearTimeout\"\r\n\t\t  v-if=\"showPlus && $slots.plus\"\r\n\t\t>\r\n\t\t\t<slot name=\"plus\" />\r\n\t\t</view>\r\n\t\t<view\r\n\t\t  v-else-if=\"showPlus\"\r\n\t\t  class=\"uv-number-box__plus\"\r\n\t\t  @tap.stop=\"clickHandler('plus')\"\r\n\t\t  @touchstart=\"onTouchStart('plus')\"\r\n\t\t  @touchend.stop=\"clearTimeout\"\r\n\t\t  hover-class=\"uv-number-box__plus--hover\"\r\n\t\t  hover-stay-time=\"150\"\r\n\t\t  :class=\"{ 'uv-number-box__minus--disabled': isDisabled('plus') }\"\r\n\t\t  :style=\"[buttonStyle('plus')]\"\r\n\t\t>\r\n\t\t\t<uv-icon\r\n\t\t\t  name=\"plus\"\r\n\t\t\t  :color=\"isDisabled('plus') ? '#c8c9cc' : '#323233'\"\r\n\t\t\t  size=\"15\"\r\n\t\t\t  bold\r\n\t\t\t\t:customStyle=\"iconStyle\"\r\n\t\t\t></uv-icon>\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * numberBox 步进器\r\n\t * @description 该组件一般用于商城购物选择物品数量的场景。\r\n\t * @tutorial https://www.uvui.cn/components/numberBox.html\r\n\t * @property {String | Number}\tvalue / v-model\t\t\t用于双向绑定的值，初始化时设置设为默认min值(最小值)  （默认 0 ）\r\n\t * @property {String | Number}\tname\t\t\t步进器标识符，在change回调返回\r\n\t * @property {String | Number}\tmin\t\t\t\t最小值 （默认 1 ）\r\n\t * @property {String | Number}\tmax\t\t\t\t最大值 （默认 Number.MAX_SAFE_INTEGER ）\r\n\t * @property {String | Number}\tstep\t\t\t加减的步长，可为小数 （默认 1 ）\r\n\t * @property {Boolean}\t\t\tinteger\t\t\t是否只允许输入整数 （默认 false ）\r\n\t * @property {Boolean}\t\t\tdisabled\t\t是否禁用，包括输入框，加减按钮 （默认 false ）\r\n\t * @property {Boolean}\t\t\tdisabledInput\t是否禁用输入框 （默认 false ）\r\n\t * @property {Boolean}\t\t\tasyncChange\t\t是否开启异步变更，开启后需要手动控制输入值 （默认 false ）\r\n\t * @property {String | Number}\tinputWidth\t\t输入框宽度，单位为px （默认 35 ）\r\n\t * @property {Boolean}\t\t\tshowMinus\t\t是否显示减少按钮 （默认 true ）\r\n\t * @property {Boolean}\t\t\tshowPlus\t\t是否显示增加按钮 （默认 true ）\r\n\t * @property {String | Number}\tdecimalLength\t显示的小数位数\r\n\t * @property {Boolean}\t\t\tlongPress\t\t是否开启长按加减手势 （默认 true ）\r\n\t * @property {String}\t\t\tcolor\t\t\t输入框文字和加减按钮图标的颜色 （默认 '#323233' ）\r\n\t * @property {String | Number}\tbuttonSize\t\t按钮大小，宽高等于此值，单位px，输入框高度和此值保持一致 （默认 30 ）\r\n\t * @property {String}\t\t\tbgColor\t\t\t输入框和按钮的背景颜色 （默认 '#EBECEE' ）\r\n\t * @property {String | Number}\tcursorSpacing\t指定光标于键盘的距离，避免键盘遮挡输入框，单位px （默认 100 ）\r\n\t * @property {Boolean}\t\t\tdisablePlus\t\t是否禁用增加按钮 （默认 false ）\r\n\t * @property {Boolean}\t\t\tdisableMinus\t是否禁用减少按钮 （默认 false ）\r\n\t * @property {Object ｜ String}\ticonStyle\t\t加减按钮图标的样式\r\n\t * @event {Function}\tonFocus\t输入框活动焦点\r\n\t * @event {Function}\tonBlur\t输入框失去焦点\r\n\t * @event {Function}\tonInput\t输入框值发生变化\r\n\t * @event {Function}\tonChange\r\n\t * @example <uv-number-box v-model=\"value\" @change=\"valChange\"></uv-number-box>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-number-box',\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\t// 输入框实际操作的值\r\n\t\t\t\tcurrentValue: '',\r\n\t\t\t\t// 定时器\r\n\t\t\t\tlongPressTimer: null\r\n\t\t\t}\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\t// 多个值之间，只要一个值发生变化，都要重新检查check()函数\r\n\t\t\twatchChange(n) {\r\n\t\t\t\tthis.check()\r\n\t\t\t},\r\n\t\t\tvalue(newVal) {\r\n\t\t\t\tif (newVal !== this.currentValue) {\r\n\t\t\t\t\tthis.currentValue = this.format(this.value)\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tmodelValue(newVal) {\r\n\t\t\t\tif (newVal !== this.currentValue) {\r\n\t\t\t\t\tthis.currentValue = this.format(this.modelValue)\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\tgetCursorSpacing() {\r\n\t\t\t\t// 判断传入的单位，如果为px单位，需要转成px\r\n\t\t\t\treturn this.$uv.getPx(this.cursorSpacing)\r\n\t\t\t},\r\n\t\t\t// 按钮的样式\r\n\t\t\tbuttonStyle() {\r\n\t\t\t\treturn (type) => {\r\n\t\t\t\t\tconst style = {\r\n\t\t\t\t\t\tbackgroundColor: this.bgColor,\r\n\t\t\t\t\t\theight: this.$uv.addUnit(this.buttonSize),\r\n\t\t\t\t\t\tcolor: this.color\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (this.isDisabled(type)) {\r\n\t\t\t\t\t\tstyle.backgroundColor = '#f7f8fa'\r\n\t\t\t\t\t}\r\n\t\t\t\t\treturn style\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t// 输入框的样式\r\n\t\t\tinputStyle() {\r\n\t\t\t\tconst disabled = this.disabled || this.disabledInput\r\n\t\t\t\tconst style = {\r\n\t\t\t\t\tcolor: this.color,\r\n\t\t\t\t\tbackgroundColor: this.bgColor,\r\n\t\t\t\t\theight: this.$uv.addUnit(this.buttonSize),\r\n\t\t\t\t\twidth: this.$uv.addUnit(this.inputWidth)\r\n\t\t\t\t}\r\n\t\t\t\treturn style\r\n\t\t\t},\r\n\t\t\t// 用于监听多个值发生变化\r\n\t\t\twatchChange() {\r\n\t\t\t\treturn [this.integer, this.decimalLength, this.min, this.max]\r\n\t\t\t},\r\n\t\t\tisDisabled() {\r\n\t\t\t\treturn (type) => {\r\n\t\t\t\t\tif (type === 'plus') {\r\n\t\t\t\t\t\t// 在点击增加按钮情况下，判断整体的disabled，是否单独禁用增加按钮，以及当前值是否大于最大的允许值\r\n\t\t\t\t\t\treturn (\r\n\t\t\t\t\t\t\tthis.disabled ||\r\n\t\t\t\t\t\t\tthis.disablePlus ||\r\n\t\t\t\t\t\t\tthis.currentValue >= this.max\r\n\t\t\t\t\t\t)\r\n\t\t\t\t\t}\r\n\t\t\t\t\t// 点击减少按钮同理\r\n\t\t\t\t\treturn (\r\n\t\t\t\t\t\tthis.disabled ||\r\n\t\t\t\t\t\tthis.disableMinus ||\r\n\t\t\t\t\t\tthis.currentValue <= this.min\r\n\t\t\t\t\t)\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t},\r\n\t\tcreated() {\r\n\t\t\tthis.init()\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tinit() {\r\n\t\t\t\tconst value = this.value || this.modelValue;\r\n\t\t\t\tthis.currentValue = this.format(value)\r\n\t\t\t},\r\n\t\t\t// 格式化整理数据，限制范围\r\n\t\t\tformat(value) {\r\n\t\t\t\tvalue = this.filter(value)\r\n\t\t\t\t// 如果为空字符串，那么设置为0，同时将值转为Number类型\r\n\t\t\t\tvalue = value === '' ? 0 : +value\r\n\t\t\t\t// 对比最大最小值，取在min和max之间的值\r\n\t\t\t\tvalue = Math.max(Math.min(this.max, value), this.min)\r\n\t\t\t\t// 如果设定了最大的小数位数，使用toFixed去进行格式化\r\n\t\t\t\tif (this.decimalLength !== null) {\r\n\t\t\t\t\tvalue = value.toFixed(this.decimalLength)\r\n\t\t\t\t}\r\n\t\t\t\treturn value\r\n\t\t\t},\r\n\t\t\t// 过滤非法的字符\r\n\t\t\tfilter(value) {\r\n\t\t\t\t// 只允许0-9之间的数字，\".\"为小数点，\"-\"为负数时候使用\r\n\t\t\t\tvalue = String(value).replace(/[^0-9.-]/g, '')\r\n\t\t\t\t// 如果只允许输入整数，则过滤掉小数点后的部分\r\n\t\t\t\tif (this.integer && value.indexOf('.') !== -1) {\r\n\t\t\t\t\tvalue = value.split('.')[0]\r\n\t\t\t\t}\r\n\t\t\t\treturn value;\r\n\t\t\t},\r\n\t\t\tcheck() {\r\n\t\t\t\t// 格式化了之后，如果前后的值不相等，那么设置为格式化后的值\r\n\t\t\t\tconst val = this.format(this.currentValue);\r\n\t\t\t\tif (val !== this.currentValue) {\r\n\t\t\t\t\tthis.currentValue = val\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t// 输入框活动焦点\r\n\t\t\tonFocus(event) {\r\n\t\t\t\tthis.$emit('focus', {\r\n\t\t\t\t\t...event.detail,\r\n\t\t\t\t\tname: this.name,\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\t// 输入框失去焦点\r\n\t\t\tonBlur(event) {\r\n\t\t\t\t// 对输入值进行格式化\r\n\t\t\t\tconst value = this.format(event.detail.value)\r\n\t\t\t\t// 发出blur事件\r\n\t\t\t\tthis.$emit(\r\n\t\t\t\t\t'blur', {\r\n\t\t\t\t\t\t...event.detail,\r\n\t\t\t\t\t\tname: this.name,\r\n\t\t\t\t\t}\r\n\t\t\t\t)\r\n\t\t\t},\r\n\t\t\t// 输入框值发生变化\r\n\t\t\tonInput(e) {\r\n\t\t\t\tconst {\r\n\t\t\t\t\tvalue = ''\r\n\t\t\t\t} = e.detail || {}\r\n\t\t\t\t// 为空返回\r\n\t\t\t\tif (value === '') return\r\n\t\t\t\tlet formatted = this.filter(value)\r\n\t\t\t\t// 最大允许的小数长度\r\n\t\t\t\tif (this.decimalLength !== null && formatted.indexOf('.') !== -1) {\r\n\t\t\t\t\tconst pair = formatted.split('.');\r\n\t\t\t\t\tformatted = `${pair[0]}.${pair[1].slice(0, this.decimalLength)}`\r\n\t\t\t\t}\r\n\t\t\t\tformatted = this.format(formatted)\r\n\t\t\t\tthis.emitChange(formatted);\r\n\t\t\t},\r\n\t\t\t// 发出change事件\r\n\t\t\temitChange(value) {\r\n\t\t\t\t// 如果开启了异步变更值，则不修改内部的值，需要用户手动在外部通过v-model变更\r\n\t\t\t\tif (!this.asyncChange) {\r\n\t\t\t\t\tthis.$nextTick(() => {\r\n\t\t\t\t\t\tthis.$emit('input', value)\r\n\t\t\t\t\t\tthis.$emit('update:modelValue', value)\r\n\t\t\t\t\t\tthis.currentValue = value\r\n\t\t\t\t\t\tthis.$forceUpdate()\r\n\t\t\t\t\t})\r\n\t\t\t\t}\r\n\t\t\t\tthis.$emit('change', {\r\n\t\t\t\t\tvalue,\r\n\t\t\t\t\tname: this.name,\r\n\t\t\t\t});\r\n\t\t\t},\r\n\t\t\tonChange() {\r\n\t\t\t\tconst {\r\n\t\t\t\t\ttype\r\n\t\t\t\t} = this\r\n\t\t\t\tif (this.isDisabled(type)) {\r\n\t\t\t\t\treturn this.$emit('overlimit', type)\r\n\t\t\t\t}\r\n\t\t\t\tconst diff = type === 'minus' ? -this.step : +this.step\r\n\t\t\t\tconst value = this.format(this.add(+this.currentValue, diff))\r\n\t\t\t\tthis.emitChange(value)\r\n\t\t\t\tthis.$emit(type)\r\n\t\t\t},\r\n\t\t\t// 对值扩大后进行四舍五入，再除以扩大因子，避免出现浮点数操作的精度问题\r\n\t\t\tadd(num1, num2) {\r\n\t\t\t\tconst cardinal = Math.pow(10, 10);\r\n\t\t\t\treturn Math.round((num1 + num2) * cardinal) / cardinal\r\n\t\t\t},\r\n\t\t\t// 点击加减按钮\r\n\t\t\tclickHandler(type) {\r\n\t\t\t\tthis.type = type\r\n\t\t\t\tthis.onChange()\r\n\t\t\t},\r\n\t\t\tlongPressStep() {\r\n\t\t\t\t// 每隔一段时间，重新调用longPressStep方法，实现长按加减\r\n\t\t\t\tthis.clearTimeout()\r\n\t\t\t\tthis.longPressTimer = setTimeout(() => {\r\n\t\t\t\t\tthis.onChange()\r\n\t\t\t\t\tthis.longPressStep()\r\n\t\t\t\t}, 250);\r\n\t\t\t},\r\n\t\t\tonTouchStart(type) {\r\n\t\t\t\tif (!this.longPress) return\r\n\t\t\t\tthis.clearTimeout()\r\n\t\t\t\tthis.type = type\r\n\t\t\t\t// 一定时间后，默认达到长按状态\r\n\t\t\t\tthis.longPressTimer = setTimeout(() => {\r\n\t\t\t\t\tthis.onChange()\r\n\t\t\t\t\tthis.longPressStep()\r\n\t\t\t\t}, 600)\r\n\t\t\t},\r\n\t\t\t// 触摸结束，清除定时器，停止长按加减\r\n\t\t\tonTouchEnd() {\r\n\t\t\t\tif (!this.longPress) return\r\n\t\t\t\tthis.clearTimeout()\r\n\t\t\t},\r\n\t\t\t// 清除定时器\r\n\t\t\tclearTimeout() {\r\n\t\t\t\tclearTimeout(this.longPressTimer)\r\n\t\t\t\tthis.longPressTimer = null\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t$uv-numberBox-hover-bgColor: #E6E6E6 !default;\r\n\t$uv-numberBox-disabled-color: #c8c9cc !default;\r\n\t$uv-numberBox-disabled-bgColor: #f7f8fa !default;\r\n\t$uv-numberBox-plus-radius: 4px !default;\r\n\t$uv-numberBox-minus-radius: 4px !default;\r\n\t$uv-numberBox-input-text-align: center !default;\r\n\t$uv-numberBox-input-font-size: 15px !default;\r\n\t$uv-numberBox-input-padding: 0 !default;\r\n\t$uv-numberBox-input-margin: 0 2px !default;\r\n\t$uv-numberBox-input-disabled-color: #c8c9cc !default;\r\n\t$uv-numberBox-input-disabled-bgColor: #f2f3f5 !default;\r\n\t.uv-number-box {\r\n\t\t@include flex(row);\r\n\t\talign-items: center;\r\n\t\t&__slot {\r\n\t\t\t/* #ifndef APP-NVUE */\r\n\t\t\ttouch-action: none;\r\n\t\t\t/* #endif */\r\n\t\t}\r\n\t\t&__plus,\r\n\t\t&__minus {\r\n\t\t\twidth: 35px;\r\n\t\t\t@include flex;\r\n\t\t\tjustify-content: center;\r\n\t\t\talign-items: center;\r\n\t\t\t/* #ifndef APP-NVUE */\r\n\t\t\ttouch-action: none;\r\n\t\t\t/* #endif */\r\n\t\t\t&--hover {\r\n\t\t\t\tbackground-color: $uv-numberBox-hover-bgColor !important;\r\n\t\t\t}\r\n\t\t\t&--disabled {\r\n\t\t\t\tcolor: $uv-numberBox-disabled-color;\r\n\t\t\t\tbackground-color: $uv-numberBox-disabled-bgColor;\r\n\t\t\t}\r\n\t\t}\r\n\t\t&__plus {\r\n\t\t\tborder-top-right-radius: $uv-numberBox-plus-radius;\r\n\t\t\tborder-bottom-right-radius: $uv-numberBox-plus-radius;\r\n\t\t}\r\n\t\t&__minus {\r\n\t\t\tborder-top-left-radius: $uv-numberBox-minus-radius;\r\n\t\t\tborder-bottom-left-radius: $uv-numberBox-minus-radius;\r\n\t\t}\r\n\t\t&__input {\r\n\t\t\tposition: relative;\r\n\t\t\ttext-align: $uv-numberBox-input-text-align;\r\n\t\t\tfont-size: $uv-numberBox-input-font-size;\r\n\t\t\tpadding: $uv-numberBox-input-padding;\r\n\t\t\tmargin: $uv-numberBox-input-margin;\r\n\t\t\t@include flex;\r\n\t\t\talign-items: center;\r\n\t\t\tjustify-content: center;\r\n\t\t\t&--disabled {\r\n\t\t\t\tcolor: $uv-numberBox-input-disabled-color;\r\n\t\t\t\tbackground-color: $uv-numberBox-input-disabled-bgColor;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</style>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-number-box/package.json",
    "content": "{\r\n  \"id\": \"uv-number-box\",\r\n  \"displayName\": \"uv-number-box 步进器  全面兼容小程序、nvue、vue2、vue3等多端\",\r\n  \"version\": \"1.0.2\",\r\n  \"description\": \"uv-number-box 该组件一般用于商城购物选择物品数量的场景。\",\r\n  \"keywords\": [\r\n    \"uv-number-box\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"number\",\r\n    \"步进器\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-icon\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-number-box/readme.md",
    "content": "## NumberBox 步进器\r\n\r\n> **组件名：uv-number-box**\r\n\r\n该组件一般用于商城购物选择物品数量的场景。\r\n\r\n### <a href=\"https://www.uvui.cn/components/numberBox.html\" target=\"_blank\">查看文档</a>\r\n\r\n### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n#### 如使用过程中有任何问题，或者您对uv-ui有一些好的建议，欢迎加入 uv-ui 交流群：<a href=\"https://ext.dcloud.net.cn/plugin?id=12287\" target=\"_blank\">uv-ui</a>、<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-overlay/changelog.md",
    "content": "## 1.0.3（2023-07-02）\nuv-overlay  由于弹出层uv-transition的修改，组件内部做了相应的修改，参数不变。\n## 1.0.2（2023-06-29）\r\n1. 优化，H5端禁止穿透滚动\r\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\n1. 新增uv-overlay组件\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-overlay/components/uv-overlay/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 是否显示遮罩\r\n\t\tshow: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 层级z-index\r\n\t\tzIndex: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 10070\r\n\t\t},\r\n\t\t// 遮罩的过渡时间，单位为ms\r\n\t\tduration: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 300\r\n\t\t},\r\n\t\t// 不透明度值，当做rgba的第四个参数\r\n\t\topacity: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0.5\r\n\t\t},\r\n\t\t...uni.$uv?.props?.overlay\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-overlay/components/uv-overlay/uv-overlay.vue",
    "content": "<template>\r\n\t<uv-transition\r\n\t  :show=\"show\"\r\n\t\tmode=\"fade\"\r\n\t  custom-class=\"uv-overlay\"\r\n\t  :duration=\"duration\"\r\n\t  :custom-style=\"overlayStyle\"\r\n\t  @click=\"clickHandler\"\r\n\t\t@touchmove.stop.prevent=\"clear\"\r\n\t>\r\n\t\t<slot />\r\n\t</uv-transition>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\r\n\t/**\r\n\t * overlay 遮罩\r\n\t * @description 创建一个遮罩层，用于强调特定的页面元素，并阻止用户对遮罩下层的内容进行操作，一般用于弹窗场景\r\n\t * @tutorial https://www.uvui.cn/components/overlay.html\r\n\t * @property {Boolean}\t\t\tshow\t\t是否显示遮罩（默认 false ）\r\n\t * @property {String | Number}\tzIndex\t\tzIndex 层级（默认 10070 ）\r\n\t * @property {String | Number}\tduration\t动画时长，单位毫秒（默认 300 ）\r\n\t * @property {String | Number}\topacity\t\t不透明度值，当做rgba的第四个参数 （默认 0.5 ）\r\n\t * @property {Object}\t\t\tcustomStyle\t定义需要用到的外部样式\r\n\t * @event {Function} click 点击遮罩发送事件\r\n\t * @example <uv-overlay :show=\"show\" @click=\"show = false\"></uv-overlay>\r\n\t */\r\n\texport default {\r\n\t\tname: \"uv-overlay\",\r\n\t\temits: ['click'],\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\twatch: {\r\n\t\t\tshow(newVal){\r\n\t\t\t\t// #ifdef H5\r\n\t\t\t\tif(newVal){\r\n\t\t\t\t\tdocument.querySelector('body').style.overflow = 'hidden';\r\n\t\t\t\t}else{\r\n\t\t\t\t\tdocument.querySelector('body').style.overflow = '';\r\n\t\t\t\t}\r\n\t\t\t\t// #endif\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\toverlayStyle() {\r\n\t\t\t\tconst style = {\r\n\t\t\t\t\tposition: 'fixed',\r\n\t\t\t\t\ttop: 0,\r\n\t\t\t\t\tleft: 0,\r\n\t\t\t\t\tright: 0,\r\n\t\t\t\t\tzIndex: this.zIndex,\r\n\t\t\t\t\tbottom: 0,\r\n\t\t\t\t\t'background-color': `rgba(0, 0, 0, ${this.opacity})`\r\n\t\t\t\t}\r\n\t\t\t\treturn this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tclickHandler() {\r\n\t\t\t\tthis.$emit('click')\r\n\t\t\t},\r\n\t\t\tclear() {}\r\n\t\t}\r\n\t}\r\n</script>\r\n<style lang=\"scss\" scoped>\n/* #ifndef APP-NVUE */\r\n$uv-overlay-top:0 !default;\n$uv-overlay-left:0 !default;\n$uv-overlay-width:100% !default;\n$uv-overlay-height:100% !default;\n$uv-overlay-background-color:rgba(0, 0, 0, .7) !default;\n.uv-overlay {\n\tposition: fixed;\n\ttop:$uv-overlay-top;\n\tleft:$uv-overlay-left;\n\twidth: $uv-overlay-width;\n\theight:$uv-overlay-height;\n\tbackground-color:$uv-overlay-background-color;\n}\r\n/* #endif */\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-overlay/package.json",
    "content": "{\r\n  \"id\": \"uv-overlay\",\r\n  \"displayName\": \"uv-overlay 遮罩层  全面兼容小程序、nvue、vue2、vue3等多端\",\r\n  \"version\": \"1.0.3\",\r\n  \"description\": \"uv-overlay 创建一个遮罩层，用于强调特定的页面元素，并阻止用户对遮罩下层的内容进行操作，一般用于弹窗场景，uv-popup、uv-toast、uv-tooltip等组件就是用了该组件。\",\r\n  \"keywords\": [\r\n    \"uv-overlay\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"overlay\",\r\n    \"遮罩层\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-transition\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-overlay/readme.md",
    "content": "## Overlay 遮罩层\r\n\r\n> **组件名：uv-overlay**\r\n\r\n创建一个遮罩层，用于强调特定的页面元素，并阻止用户对遮罩下层的内容进行操作，一般用于弹窗场景，uv-popup、uv-toast、uv-tooltip等组件就是用了该组件。\r\n\r\n### <a href=\"https://www.uvui.cn/components/overlay.html\" target=\"_blank\">查看文档</a>\r\n\r\n### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n#### 如使用过程中有任何问题，或者您对uv-ui有一些好的建议，欢迎加入 uv-ui 交流群：<a href=\"https://ext.dcloud.net.cn/plugin?id=12287\" target=\"_blank\">uv-ui</a>、<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-parse/changelog.md",
    "content": "## 1.0.4（2023-07-17）\n1. 优化文档\n2. 优化其他\n## 1.0.3（2023-06-19）\r\n1. 修复nvue模式下不显示的BUG\r\n## 1.0.2（2023-06-02）\r\n1. 修复可能存在的BUG\r\n2. 优化\r\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-parse 富文本解析器\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-parse/components/uv-parse/node/node.vue",
    "content": "<template>\r\n  <view :id=\"attrs.id\" :class=\"'_block _'+name+' '+attrs.class\" :style=\"attrs.style\">\r\n    <block v-for=\"(n, i) in childs\" v-bind:key=\"i\">\r\n      <!-- 图片 -->\r\n      <!-- 占位图 -->\r\n      <image v-if=\"n.name==='img'&&!n.t&&((opts[1]&&!ctrl[i])||ctrl[i]<0)\" class=\"_img\" :style=\"n.attrs.style\" :src=\"ctrl[i]<0?opts[2]:opts[1]\" mode=\"widthFix\" />\r\n      <!-- 显示图片 -->\r\n      <!-- #ifdef H5 || (APP-PLUS && VUE2) -->\r\n      <img v-if=\"n.name==='img'\" :id=\"n.attrs.id\" :class=\"'_img '+n.attrs.class\" :style=\"(ctrl[i]===-1?'display:none;':'')+n.attrs.style\" :src=\"n.attrs.src||(ctrl.load?n.attrs['data-src']:'')\" :data-i=\"i\" @load=\"imgLoad\" @error=\"mediaError\" @tap.stop=\"imgTap\" @longpress=\"imgLongTap\" />\r\n      <!-- #endif -->\r\n      <!-- #ifndef H5 || (APP-PLUS && VUE2) -->\r\n      <!-- 表格中的图片，使用 rich-text 防止大小不正确 -->\r\n      <rich-text v-if=\"n.name==='img'&&n.t\" :style=\"'display:'+n.t\" :nodes=\"[{attrs:{style:n.attrs.style,src:n.attrs.src},name:'img'}]\" :data-i=\"i\" @tap.stop=\"imgTap\" />\r\n      <!-- #endif -->\r\n      <!-- #ifndef H5 || APP-PLUS -->\r\n      <image v-else-if=\"n.name==='img'\" :id=\"n.attrs.id\" :class=\"'_img '+n.attrs.class\" :style=\"(ctrl[i]===-1?'display:none;':'')+'width:'+(ctrl[i]||1)+'px;height:1px;'+n.attrs.style\" :src=\"n.attrs.src\" :mode=\"!n.h?'widthFix':(!n.w?'heightFix':'')\" :lazy-load=\"opts[0]\" :webp=\"n.webp\" :show-menu-by-longpress=\"opts[3]&&!n.attrs.ignore\" :image-menu-prevent=\"!opts[3]||n.attrs.ignore\" :data-i=\"i\" @load=\"imgLoad\" @error=\"mediaError\" @tap.stop=\"imgTap\" @longpress=\"imgLongTap\" />\r\n      <!-- #endif -->\r\n      <!-- #ifdef APP-PLUS && VUE3 -->\r\n      <image v-else-if=\"n.name==='img'\" :id=\"n.attrs.id\" :class=\"'_img '+n.attrs.class\" :style=\"(ctrl[i]===-1?'display:none;':'')+'width:'+(ctrl[i]||1)+'px;'+n.attrs.style\" :src=\"n.attrs.src||(ctrl.load?n.attrs['data-src']:'')\" :mode=\"!n.h?'widthFix':(!n.w?'heightFix':'')\" :data-i=\"i\" @load=\"imgLoad\" @error=\"mediaError\" @tap.stop=\"imgTap\" @longpress=\"imgLongTap\" />\r\n      <!-- #endif -->\r\n      <!-- 文本 -->\r\n      <!-- #ifdef MP-WEIXIN -->\r\n      <text v-else-if=\"n.text\" :user-select=\"opts[4]=='force'&&isiOS\" decode>{{n.text}}</text>\r\n      <!-- #endif -->\r\n      <!-- #ifndef MP-WEIXIN || MP-BAIDU || MP-ALIPAY || MP-TOUTIAO -->\r\n      <text v-else-if=\"n.text\" decode>{{n.text}}</text>\r\n      <!-- #endif -->\r\n      <text v-else-if=\"n.name==='br'\">\\n</text>\r\n      <!-- 链接 -->\r\n      <view v-else-if=\"n.name==='a'\" :id=\"n.attrs.id\" :class=\"(n.attrs.href?'_a ':'')+n.attrs.class\" hover-class=\"_hover\" :style=\"'display:inline;'+n.attrs.style\" :data-i=\"i\" @tap.stop=\"linkTap\">\r\n        <node name=\"span\" :childs=\"n.children\" :opts=\"opts\" style=\"display:inherit\" />\r\n      </view>\r\n      <!-- 视频 -->\r\n      <!-- #ifdef APP-PLUS -->\r\n      <view v-else-if=\"n.html\" :id=\"n.attrs.id\" :class=\"'_video '+n.attrs.class\" :style=\"n.attrs.style\" v-html=\"n.html\" @vplay.stop=\"play\" />\r\n      <!-- #endif -->\r\n      <!-- #ifndef APP-PLUS -->\r\n      <video v-else-if=\"n.name==='video'\" :id=\"n.attrs.id\" :class=\"n.attrs.class\" :style=\"n.attrs.style\" :autoplay=\"n.attrs.autoplay\" :controls=\"n.attrs.controls\" :loop=\"n.attrs.loop\" :muted=\"n.attrs.muted\" :object-fit=\"n.attrs['object-fit']\" :poster=\"n.attrs.poster\" :src=\"n.src[ctrl[i]||0]\" :data-i=\"i\" @play=\"play\" @error=\"mediaError\" />\r\n      <!-- #endif -->\r\n      <!-- #ifdef H5 || APP-PLUS -->\r\n      <iframe v-else-if=\"n.name==='iframe'\" :style=\"n.attrs.style\" :allowfullscreen=\"n.attrs.allowfullscreen\" :frameborder=\"n.attrs.frameborder\" :src=\"n.attrs.src\" />\r\n      <embed v-else-if=\"n.name==='embed'\" :style=\"n.attrs.style\" :src=\"n.attrs.src\" />\r\n      <!-- #endif -->\r\n      <!-- #ifndef MP-TOUTIAO || ((H5 || APP-PLUS) && VUE3) -->\r\n      <!-- 音频 -->\r\n      <audio v-else-if=\"n.name==='audio'\" :id=\"n.attrs.id\" :class=\"n.attrs.class\" :style=\"n.attrs.style\" :author=\"n.attrs.author\" :controls=\"n.attrs.controls\" :loop=\"n.attrs.loop\" :name=\"n.attrs.name\" :poster=\"n.attrs.poster\" :src=\"n.src[ctrl[i]||0]\" :data-i=\"i\" @play=\"play\" @error=\"mediaError\" />\r\n      <!-- #endif -->\r\n      <view v-else-if=\"(n.name==='table'&&n.c)||n.name==='li'\" :id=\"n.attrs.id\" :class=\"'_'+n.name+' '+n.attrs.class\" :style=\"n.attrs.style\">\r\n        <node v-if=\"n.name==='li'\" :childs=\"n.children\" :opts=\"opts\" />\r\n        <view v-else v-for=\"(tbody, x) in n.children\" v-bind:key=\"x\" :class=\"'_'+tbody.name+' '+tbody.attrs.class\" :style=\"tbody.attrs.style\">\r\n          <node v-if=\"tbody.name==='td'||tbody.name==='th'\" :childs=\"tbody.children\" :opts=\"opts\" />\r\n          <block v-else v-for=\"(tr, y) in tbody.children\" v-bind:key=\"y\">\r\n            <view v-if=\"tr.name==='td'||tr.name==='th'\" :class=\"'_'+tr.name+' '+tr.attrs.class\" :style=\"tr.attrs.style\">\r\n              <node :childs=\"tr.children\" :opts=\"opts\" />\r\n            </view>\r\n            <view v-else :class=\"'_'+tr.name+' '+tr.attrs.class\" :style=\"tr.attrs.style\">\r\n              <view v-for=\"(td, z) in tr.children\" v-bind:key=\"z\" :class=\"'_'+td.name+' '+td.attrs.class\" :style=\"td.attrs.style\">\r\n                <node :childs=\"td.children\" :opts=\"opts\" />\r\n              </view>\r\n            </view>\r\n          </block>\r\n        </view>\r\n      </view>\r\n      \r\n      <!-- 富文本 -->\r\n      <!-- #ifdef H5 || ((MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE2) -->\r\n      <rich-text v-else-if=\"!n.c&&!handler.isInline(n.name, n.attrs.style)\" :id=\"n.attrs.id\" :style=\"n.f\" :user-select=\"opts[4]\" :nodes=\"[n]\" />\r\n      <!-- #endif -->\r\n      <!-- #ifndef H5 || ((MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE2) -->\r\n      <rich-text v-else-if=\"!n.c\" :id=\"n.attrs.id\" :style=\"'display:inline;'+n.f\" :preview=\"false\" :selectable=\"opts[4]\" :user-select=\"opts[4]\" :nodes=\"[n]\" />\r\n      <!-- #endif -->\r\n      <!-- 继续递归 -->\r\n      <view v-else-if=\"n.c===2\" :id=\"n.attrs.id\" :class=\"'_block _'+n.name+' '+n.attrs.class\" :style=\"n.f+';'+n.attrs.style\">\r\n        <node v-for=\"(n2, j) in n.children\" v-bind:key=\"j\" :style=\"n2.f\" :name=\"n2.name\" :attrs=\"n2.attrs\" :childs=\"n2.children\" :opts=\"opts\" />\r\n      </view>\r\n      <node v-else :style=\"n.f\" :name=\"n.name\" :attrs=\"n.attrs\" :childs=\"n.children\" :opts=\"opts\" />\r\n    </block>\r\n  </view>\r\n</template>\r\n<script module=\"handler\" lang=\"wxs\">\r\n// 行内标签列表\r\nvar inlineTags = {\r\n  abbr: true,\r\n  b: true,\r\n  big: true,\r\n  code: true,\r\n  del: true,\r\n  em: true,\r\n  i: true,\r\n  ins: true,\r\n  label: true,\r\n  q: true,\r\n  small: true,\r\n  span: true,\r\n  strong: true,\r\n  sub: true,\r\n  sup: true\r\n}\r\n/**\r\n * @description 判断是否为行内标签\r\n */\r\nmodule.exports = {\r\n  isInline: function (tagName, style) {\r\n    return inlineTags[tagName] || (style || '').indexOf('display:inline') !== -1\r\n  }\r\n}\r\n</script>\r\n<script>\r\n\r\nimport node from './node'\r\nexport default {\r\n  name: 'node',\r\n  options: {\r\n    // #ifdef MP-WEIXIN\r\n    virtualHost: true,\r\n    // #endif\r\n    // #ifdef MP-TOUTIAO\r\n    addGlobalClass: false\r\n    // #endif\r\n  },\r\n  data () {\r\n    return {\r\n      ctrl: {},\r\n      // #ifdef MP-WEIXIN\r\n      isiOS: uni.getSystemInfoSync().system.includes('iOS')\r\n      // #endif\r\n    }\r\n  },\r\n  props: {\r\n    name: String,\r\n    attrs: {\r\n      type: Object,\r\n      default () {\r\n        return {}\r\n      }\r\n    },\r\n    childs: Array,\r\n    opts: Array\r\n  },\r\n  components: {\r\n\r\n    // #ifndef (H5 || APP-PLUS) && VUE3\r\n    node\r\n    // #endif\r\n  },\r\n  mounted () {\r\n    this.$nextTick(() => {\r\n      for (this.root = this.$parent; this.root.$options.name !== 'uv-parse'; this.root = this.root.$parent);\r\n    })\r\n    // #ifdef H5 || APP-PLUS\r\n    if (this.opts[0]) {\r\n      let i\r\n      for (i = this.childs.length; i--;) {\r\n        if (this.childs[i].name === 'img') break\r\n      }\r\n      if (i !== -1) {\r\n        this.observer = uni.createIntersectionObserver(this).relativeToViewport({\r\n          top: 500,\r\n          bottom: 500\r\n        })\r\n        this.observer.observe('._img', res => {\r\n          if (res.intersectionRatio) {\r\n            this.$set(this.ctrl, 'load', 1)\r\n            this.observer.disconnect()\r\n          }\r\n        })\r\n      }\r\n    }\r\n    // #endif\r\n  },\r\n  beforeDestroy () {\r\n    // #ifdef H5 || APP-PLUS\r\n    if (this.observer) {\r\n      this.observer.disconnect()\r\n    }\r\n    // #endif\r\n  },\r\n  methods:{\r\n    // #ifdef MP-WEIXIN\r\n    toJSON () { return this },\r\n    // #endif\r\n    /**\r\n     * @description 播放视频事件\r\n     * @param {Event} e\r\n     */\r\n    play (e) {\r\n      this.root.$emit('play')\r\n      // #ifndef APP-PLUS\r\n      if (this.root.pauseVideo) {\r\n        let flag = false\r\n        const id = e.target.id\r\n        for (let i = this.root._videos.length; i--;) {\r\n          if (this.root._videos[i].id === id) {\r\n            flag = true\r\n          } else {\r\n            this.root._videos[i].pause() // 自动暂停其他视频\r\n          }\r\n        }\r\n        // 将自己加入列表\r\n        if (!flag) {\r\n          const ctx = uni.createVideoContext(id\r\n            // #ifndef MP-BAIDU\r\n            , this\r\n            // #endif\r\n          )\r\n          ctx.id = id\r\n          if (this.root.playbackRate) {\r\n            ctx.playbackRate(this.root.playbackRate)\r\n          }\r\n          this.root._videos.push(ctx)\r\n        }\r\n      }\r\n      // #endif\r\n    },\r\n\r\n    /**\r\n     * @description 图片点击事件\r\n     * @param {Event} e\r\n     */\r\n    imgTap (e) {\r\n      const node = this.childs[e.currentTarget.dataset.i]\r\n      if (node.a) {\r\n        this.linkTap(node.a)\r\n        return\r\n      }\r\n      if (node.attrs.ignore) return\r\n      // #ifdef H5 || APP-PLUS\r\n      node.attrs.src = node.attrs.src || node.attrs['data-src']\r\n      // #endif\r\n      this.root.$emit('imgtap', node.attrs)\r\n      // 自动预览图片\r\n      if (this.root.previewImg) {\r\n        uni.previewImage({\r\n          // #ifdef MP-WEIXIN\r\n          showmenu: this.root.showImgMenu,\r\n          // #endif\r\n          // #ifdef MP-ALIPAY\r\n          enablesavephoto: this.root.showImgMenu,\r\n          enableShowPhotoDownload: this.root.showImgMenu,\r\n          // #endif\r\n          current: parseInt(node.attrs.i),\r\n          urls: this.root.imgList\r\n        })\r\n      }\r\n    },\r\n\r\n    /**\r\n     * @description 图片长按\r\n     */\r\n    imgLongTap (e) {\r\n      // #ifdef APP-PLUS\r\n      const attrs = this.childs[e.currentTarget.dataset.i].attrs\r\n      if (this.opts[3] && !attrs.ignore) {\r\n        uni.showActionSheet({\r\n          itemList: ['保存图片'],\r\n          success: () => {\r\n            const save = path => {\r\n              uni.saveImageToPhotosAlbum({\r\n                filePath: path,\r\n                success () {\r\n                  uni.showToast({\r\n                    title: '保存成功'\r\n                  })\r\n                }\r\n              })\r\n            }\r\n            if (this.root.imgList[attrs.i].startsWith('http')) {\r\n              uni.downloadFile({\r\n                url: this.root.imgList[attrs.i],\r\n                success: res => save(res.tempFilePath)\r\n              })\r\n            } else {\r\n              save(this.root.imgList[attrs.i])\r\n            }\r\n          }\r\n        })\r\n      }\r\n      // #endif\r\n    },\r\n\r\n    /**\r\n     * @description 图片加载完成事件\r\n     * @param {Event} e\r\n     */\r\n    imgLoad (e) {\r\n      const i = e.currentTarget.dataset.i\r\n      /* #ifndef H5 || (APP-PLUS && VUE2) */\r\n      if (!this.childs[i].w) {\r\n        // 设置原宽度\r\n        this.$set(this.ctrl, i, e.detail.width)\r\n      } else /* #endif */ if ((this.opts[1] && !this.ctrl[i]) || this.ctrl[i] === -1) {\r\n        // 加载完毕，取消加载中占位图\r\n        this.$set(this.ctrl, i, 1)\r\n      }\r\n      this.checkReady()\r\n    },\r\n\r\n    /**\r\n     * @description 检查是否所有图片加载完毕\r\n     */\r\n    checkReady () {\r\n      if (this.root && !this.root.lazyLoad) {\r\n        this.root._unloadimgs -= 1\r\n        if (!this.root._unloadimgs) {\r\n          setTimeout(() => {\r\n            this.root.getRect().then(rect => {\r\n              this.root.$emit('ready', rect)\r\n            }).catch(() => {\r\n              this.root.$emit('ready', {})\r\n            })\r\n          }, 350)\r\n        }\r\n      }\r\n    },\r\n\r\n    /**\r\n     * @description 链接点击事件\r\n     * @param {Event} e\r\n     */\r\n    linkTap (e) {\r\n      const node = e.currentTarget ? this.childs[e.currentTarget.dataset.i] : {}\r\n      const attrs = node.attrs || e\r\n      const href = attrs.href\r\n      this.root.$emit('linktap', Object.assign({\r\n        innerText: this.root.getText(node.children || []) // 链接内的文本内容\r\n      }, attrs))\r\n      if (href) {\r\n        if (href[0] === '#') {\r\n          // 跳转锚点\r\n          this.root.navigateTo(href.substring(1)).catch(() => { })\r\n        } else if (href.split('?')[0].includes('://')) {\r\n          // 复制外部链接\r\n          if (this.root.copyLink) {\r\n            // #ifdef H5\r\n            window.open(href)\r\n            // #endif\r\n            // #ifdef MP\r\n            uni.setClipboardData({\r\n              data: href,\r\n              success: () =>\r\n                uni.showToast({\r\n                  title: '链接已复制'\r\n                })\r\n            })\r\n            // #endif\r\n            // #ifdef APP-PLUS\r\n            plus.runtime.openWeb(href)\r\n            // #endif\r\n          }\r\n        } else {\r\n          // 跳转页面\r\n          uni.navigateTo({\r\n            url: href,\r\n            fail () {\r\n              uni.switchTab({\r\n                url: href,\r\n                fail () { }\r\n              })\r\n            }\r\n          })\r\n        }\r\n      }\r\n    },\r\n\r\n    /**\r\n     * @description 错误事件\r\n     * @param {Event} e\r\n     */\r\n    mediaError (e) {\r\n      const i = e.currentTarget.dataset.i\r\n      const node = this.childs[i]\r\n      // 加载其他源\r\n      if (node.name === 'video' || node.name === 'audio') {\r\n        let index = (this.ctrl[i] || 0) + 1\r\n        if (index > node.src.length) {\r\n          index = 0\r\n        }\r\n        if (index < node.src.length) {\r\n          this.$set(this.ctrl, i, index)\r\n          return\r\n        }\r\n      } else if (node.name === 'img') {\r\n        // #ifdef H5 && VUE3\r\n        if (this.opts[0] && !this.ctrl.load) return\r\n        // #endif\r\n        // 显示错误占位图\r\n        if (this.opts[2]) {\r\n          this.$set(this.ctrl, i, -1)\r\n        }\r\n        this.checkReady()\r\n      }\r\n      if (this.root) {\r\n        this.root.$emit('error', {\r\n          source: node.name,\r\n          attrs: node.attrs,\r\n          // #ifndef H5 && VUE3\r\n          errMsg: e.detail.errMsg\r\n          // #endif\r\n        })\r\n      }\r\n    }\r\n  }\r\n}\r\n</script>\r\n<style>\r\n/* a 标签默认效果 */\r\n._a {\r\n  padding: 1.5px 0 1.5px 0;\r\n  color: #366092;\r\n  word-break: break-all;\r\n}\r\n\r\n/* a 标签点击态效果 */\r\n._hover {\r\n  text-decoration: underline;\r\n  opacity: 0.7;\r\n}\r\n\r\n/* 图片默认效果 */\r\n._img {\r\n  max-width: 100%;\r\n  -webkit-touch-callout: none;\r\n}\r\n\r\n/* 内部样式 */\r\n\r\n._block {\r\n  display: block;\r\n}\r\n\r\n._b,\r\n._strong {\r\n  font-weight: bold;\r\n}\r\n\r\n._code {\r\n  font-family: monospace;\r\n}\r\n\r\n._del {\r\n  text-decoration: line-through;\r\n}\r\n\r\n._em,\r\n._i {\r\n  font-style: italic;\r\n}\r\n\r\n._h1 {\r\n  font-size: 2em;\r\n}\r\n\r\n._h2 {\r\n  font-size: 1.5em;\r\n}\r\n\r\n._h3 {\r\n  font-size: 1.17em;\r\n}\r\n\r\n._h5 {\r\n  font-size: 0.83em;\r\n}\r\n\r\n._h6 {\r\n  font-size: 0.67em;\r\n}\r\n\r\n._h1,\r\n._h2,\r\n._h3,\r\n._h4,\r\n._h5,\r\n._h6 {\r\n  display: block;\r\n  font-weight: bold;\r\n}\r\n\r\n._image {\r\n  height: 1px;\r\n}\r\n\r\n._ins {\r\n  text-decoration: underline;\r\n}\r\n\r\n._li {\r\n  display: list-item;\r\n}\r\n\r\n._ol {\r\n  list-style-type: decimal;\r\n}\r\n\r\n._ol,\r\n._ul {\r\n  display: block;\r\n  padding-left: 40px;\r\n  margin: 1em 0;\r\n}\r\n\r\n._q::before {\r\n  content: '\"';\r\n}\r\n\r\n._q::after {\r\n  content: '\"';\r\n}\r\n\r\n._sub {\r\n  font-size: smaller;\r\n  vertical-align: sub;\r\n}\r\n\r\n._sup {\r\n  font-size: smaller;\r\n  vertical-align: super;\r\n}\r\n\r\n._thead,\r\n._tbody,\r\n._tfoot {\r\n  display: table-row-group;\r\n}\r\n\r\n._tr {\r\n  display: table-row;\r\n}\r\n\r\n._td,\r\n._th {\r\n  display: table-cell;\r\n  vertical-align: middle;\r\n}\r\n\r\n._th {\r\n  font-weight: bold;\r\n  text-align: center;\r\n}\r\n\r\n._ul {\r\n  list-style-type: disc;\r\n}\r\n\r\n._ul ._ul {\r\n  margin: 0;\r\n  list-style-type: circle;\r\n}\r\n\r\n._ul ._ul ._ul {\r\n  list-style-type: square;\r\n}\r\n\r\n._abbr,\r\n._b,\r\n._code,\r\n._del,\r\n._em,\r\n._i,\r\n._ins,\r\n._label,\r\n._q,\r\n._span,\r\n._strong,\r\n._sub,\r\n._sup {\r\n  display: inline;\r\n}\r\n\r\n/* #ifdef APP-PLUS */\r\n._video {\r\n  width: 300px;\r\n  height: 225px;\r\n}\r\n/* #endif */\r\n</style>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-parse/components/uv-parse/parser.js",
    "content": "/**\r\n * @fileoverview html 解析器\r\n */\r\n\r\n// 配置\r\nconst config = {\r\n  // 信任的标签（保持标签名不变）\r\n  trustTags: makeMap('a,abbr,ad,audio,b,blockquote,br,code,col,colgroup,dd,del,dl,dt,div,em,fieldset,h1,h2,h3,h4,h5,h6,hr,i,img,ins,label,legend,li,ol,p,q,ruby,rt,source,span,strong,sub,sup,table,tbody,td,tfoot,th,thead,tr,title,ul,video'),\r\n\r\n  // 块级标签（转为 div，其他的非信任标签转为 span）\r\n  blockTags: makeMap('address,article,aside,body,caption,center,cite,footer,header,html,nav,pre,section'),\r\n\r\n  // #ifdef (MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE3\r\n  // 行内标签\r\n  inlineTags: makeMap('abbr,b,big,code,del,em,i,ins,label,q,small,span,strong,sub,sup'),\r\n  // #endif\r\n\r\n  // 要移除的标签\r\n  ignoreTags: makeMap('area,base,canvas,embed,frame,head,iframe,input,link,map,meta,param,rp,script,source,style,textarea,title,track,wbr'),\r\n\r\n  // 自闭合的标签\r\n  voidTags: makeMap('area,base,br,col,circle,ellipse,embed,frame,hr,img,input,line,link,meta,param,path,polygon,rect,source,track,use,wbr'),\r\n\r\n  // html 实体\r\n  entities: {\r\n    lt: '<',\r\n    gt: '>',\r\n    quot: '\"',\r\n    apos: \"'\",\r\n    ensp: '\\u2002',\r\n    emsp: '\\u2003',\r\n    nbsp: '\\xA0',\r\n    semi: ';',\r\n    ndash: '–',\r\n    mdash: '—',\r\n    middot: '·',\r\n    lsquo: '‘',\r\n    rsquo: '’',\r\n    ldquo: '“',\r\n    rdquo: '”',\r\n    bull: '•',\r\n    hellip: '…',\r\n    larr: '←',\r\n    uarr: '↑',\r\n    rarr: '→',\r\n    darr: '↓'\r\n  },\r\n\r\n  // 默认的标签样式\r\n  tagStyle: {\r\n    // #ifndef APP-PLUS-NVUE\r\n    address: 'font-style:italic',\r\n    big: 'display:inline;font-size:1.2em',\r\n    caption: 'display:table-caption;text-align:center',\r\n    center: 'text-align:center',\r\n    cite: 'font-style:italic',\r\n    dd: 'margin-left:40px',\r\n    mark: 'background-color:yellow',\r\n    pre: 'font-family:monospace;white-space:pre',\r\n    s: 'text-decoration:line-through',\r\n    small: 'display:inline;font-size:0.8em',\r\n    strike: 'text-decoration:line-through',\r\n    u: 'text-decoration:underline'\r\n    // #endif\r\n  },\r\n\r\n  // svg 大小写对照表\r\n  svgDict: {\r\n    animatetransform: 'animateTransform',\r\n    lineargradient: 'linearGradient',\r\n    viewbox: 'viewBox',\r\n    attributename: 'attributeName',\r\n    repeatcount: 'repeatCount',\r\n    repeatdur: 'repeatDur'\r\n  }\r\n}\r\nconst tagSelector={}\r\nconst {\r\n  windowWidth,\r\n  // #ifdef MP-WEIXIN\r\n  system\r\n  // #endif\r\n} = uni.getSystemInfoSync()\r\nconst blankChar = makeMap(' ,\\r,\\n,\\t,\\f')\r\nlet idIndex = 0\r\n\r\n// #ifdef H5 || APP-PLUS\r\nconfig.ignoreTags.iframe = undefined\r\nconfig.trustTags.iframe = true\r\nconfig.ignoreTags.embed = undefined\r\nconfig.trustTags.embed = true\r\n// #endif\r\n// #ifdef APP-PLUS-NVUE\r\nconfig.ignoreTags.source = undefined\r\nconfig.ignoreTags.style = undefined\r\n// #endif\r\n\r\n/**\r\n * @description 创建 map\r\n * @param {String} str 逗号分隔\r\n */\r\nfunction makeMap (str) {\r\n  const map = Object.create(null)\r\n  const list = str.split(',')\r\n  for (let i = list.length; i--;) {\r\n    map[list[i]] = true\r\n  }\r\n  return map\r\n}\r\n\r\n/**\r\n * @description 解码 html 实体\r\n * @param {String} str 要解码的字符串\r\n * @param {Boolean} amp 要不要解码 &amp;\r\n * @returns {String} 解码后的字符串\r\n */\r\nfunction decodeEntity (str, amp) {\r\n  let i = str.indexOf('&')\r\n  while (i !== -1) {\r\n    const j = str.indexOf(';', i + 3)\r\n    let code\r\n    if (j === -1) break\r\n    if (str[i + 1] === '#') {\r\n      // &#123; 形式的实体\r\n      code = parseInt((str[i + 2] === 'x' ? '0' : '') + str.substring(i + 2, j))\r\n      if (!isNaN(code)) {\r\n        str = str.substr(0, i) + String.fromCharCode(code) + str.substr(j + 1)\r\n      }\r\n    } else {\r\n      // &nbsp; 形式的实体\r\n      code = str.substring(i + 1, j)\r\n      if (config.entities[code] || (code === 'amp' && amp)) {\r\n        str = str.substr(0, i) + (config.entities[code] || '&') + str.substr(j + 1)\r\n      }\r\n    }\r\n    i = str.indexOf('&', i + 1)\r\n  }\r\n  return str\r\n}\r\n\r\n/**\r\n * @description 合并多个块级标签，加快长内容渲染\r\n * @param {Array} nodes 要合并的标签数组\r\n */\r\nfunction mergeNodes (nodes) {\r\n  let i = nodes.length - 1\r\n  for (let j = i; j >= -1; j--) {\r\n    if (j === -1 || nodes[j].c || !nodes[j].name || (nodes[j].name !== 'div' && nodes[j].name !== 'p' && nodes[j].name[0] !== 'h') || (nodes[j].attrs.style || '').includes('inline')) {\r\n      if (i - j >= 5) {\r\n        nodes.splice(j + 1, i - j, {\r\n          name: 'div',\r\n          attrs: {},\r\n          children: nodes.slice(j + 1, i + 1)\r\n        })\r\n      }\r\n      i = j - 1\r\n    }\r\n  }\r\n}\r\n\r\n/**\r\n * @description html 解析器\r\n * @param {Object} vm 组件实例\r\n */\r\nfunction Parser (vm) {\r\n  this.options = vm || {}\r\n  this.tagStyle = Object.assign({}, config.tagStyle, this.options.tagStyle)\r\n  this.imgList = vm.imgList || []\r\n  this.imgList._unloadimgs = 0\r\n  this.plugins = vm.plugins || []\r\n  this.attrs = Object.create(null)\r\n  this.stack = []\r\n  this.nodes = []\r\n  this.pre = (this.options.containerStyle || '').includes('white-space') && this.options.containerStyle.includes('pre') ? 2 : 0\r\n}\r\n\r\n/**\r\n * @description 执行解析\r\n * @param {String} content 要解析的文本\r\n */\r\nParser.prototype.parse = function (content) {\r\n  // 插件处理\r\n  for (let i = this.plugins.length; i--;) {\r\n    if (this.plugins[i].onUpdate) {\r\n      content = this.plugins[i].onUpdate(content, config) || content\r\n    }\r\n  }\r\n\r\n  new Lexer(this).parse(content)\r\n  // 出栈未闭合的标签\r\n  while (this.stack.length) {\r\n    this.popNode()\r\n  }\r\n  if (this.nodes.length > 50) {\r\n    mergeNodes(this.nodes)\r\n  }\r\n  return this.nodes\r\n}\r\n\r\n/**\r\n * @description 将标签暴露出来（不被 rich-text 包含）\r\n */\r\nParser.prototype.expose = function () {\r\n  // #ifndef APP-PLUS-NVUE\r\n  for (let i = this.stack.length; i--;) {\r\n    const item = this.stack[i]\r\n    if (item.c || item.name === 'a' || item.name === 'video' || item.name === 'audio') return\r\n    item.c = 1\r\n  }\r\n  // #endif\r\n}\r\n\r\n/**\r\n * @description 处理插件\r\n * @param {Object} node 要处理的标签\r\n * @returns {Boolean} 是否要移除此标签\r\n */\r\nParser.prototype.hook = function (node) {\r\n  for (let i = this.plugins.length; i--;) {\r\n    if (this.plugins[i].onParse && this.plugins[i].onParse(node, this) === false) {\r\n      return false\r\n    }\r\n  }\r\n  return true\r\n}\r\n\r\n/**\r\n * @description 将链接拼接上主域名\r\n * @param {String} url 需要拼接的链接\r\n * @returns {String} 拼接后的链接\r\n */\r\nParser.prototype.getUrl = function (url) {\r\n  const domain = this.options.domain\r\n  if (url[0] === '/') {\r\n    if (url[1] === '/') {\r\n      // // 开头的补充协议名\r\n      url = (domain ? domain.split('://')[0] : 'http') + ':' + url\r\n    } else if (domain) {\r\n      // 否则补充整个域名\r\n      url = domain + url\r\n    } /* #ifdef APP-PLUS */ else {\r\n      url = plus.io.convertLocalFileSystemURL(url)\r\n    } /* #endif */\r\n  } else if (!url.includes('data:') && !url.includes('://')) {\r\n    if (domain) {\r\n      url = domain + '/' + url\r\n    } /* #ifdef APP-PLUS */ else {\r\n      url = plus.io.convertLocalFileSystemURL(url)\r\n    } /* #endif */\r\n  }\r\n  return url\r\n}\r\n\r\n/**\r\n * @description 解析样式表\r\n * @param {Object} node 标签\r\n * @returns {Object}\r\n */\r\nParser.prototype.parseStyle = function (node) {\r\n  const attrs = node.attrs\r\n  const list = (this.tagStyle[node.name] || '').split(';').concat((attrs.style || '').split(';'))\r\n  const styleObj = {}\r\n  let tmp = ''\r\n\r\n  if (attrs.id && !this.xml) {\r\n    // 暴露锚点\r\n    if (this.options.useAnchor) {\r\n      this.expose()\r\n    } else if (node.name !== 'img' && node.name !== 'a' && node.name !== 'video' && node.name !== 'audio') {\r\n      attrs.id = undefined\r\n    }\r\n  }\r\n\r\n  // 转换 width 和 height 属性\r\n  if (attrs.width) {\r\n    styleObj.width = parseFloat(attrs.width) + (attrs.width.includes('%') ? '%' : 'px')\r\n    attrs.width = undefined\r\n  }\r\n  if (attrs.height) {\r\n    styleObj.height = parseFloat(attrs.height) + (attrs.height.includes('%') ? '%' : 'px')\r\n    attrs.height = undefined\r\n  }\r\n\r\n  for (let i = 0, len = list.length; i < len; i++) {\r\n    const info = list[i].split(':')\r\n    if (info.length < 2) continue\r\n    const key = info.shift().trim().toLowerCase()\r\n    let value = info.join(':').trim()\r\n    if ((value[0] === '-' && value.lastIndexOf('-') > 0) || value.includes('safe')) {\r\n      // 兼容性的 css 不压缩\r\n      tmp += `;${key}:${value}`\r\n    } else if (!styleObj[key] || value.includes('import') || !styleObj[key].includes('import')) {\r\n      // 重复的样式进行覆盖\r\n      if (value.includes('url')) {\r\n        // 填充链接\r\n        let j = value.indexOf('(') + 1\r\n        if (j) {\r\n          while (value[j] === '\"' || value[j] === \"'\" || blankChar[value[j]]) {\r\n            j++\r\n          }\r\n          value = value.substr(0, j) + this.getUrl(value.substr(j))\r\n        }\r\n      } else if (value.includes('rpx')) {\r\n        // 转换 rpx（rich-text 内部不支持 rpx）\r\n        value = value.replace(/[0-9.]+\\s*rpx/g, $ => parseFloat($) * windowWidth / 750 + 'px')\r\n      }\r\n      styleObj[key] = value\r\n    }\r\n  }\r\n\r\n  node.attrs.style = tmp\r\n  return styleObj\r\n}\r\n\r\n/**\r\n * @description 解析到标签名\r\n * @param {String} name 标签名\r\n * @private\r\n */\r\nParser.prototype.onTagName = function (name) {\r\n  this.tagName = this.xml ? name : name.toLowerCase()\r\n  if (this.tagName === 'svg') {\r\n    this.xml = (this.xml || 0) + 1 // svg 标签内大小写敏感\r\n    config.ignoreTags.style = undefined // svg 标签内 style 可用\r\n  }\r\n}\r\n\r\n/**\r\n * @description 解析到属性名\r\n * @param {String} name 属性名\r\n * @private\r\n */\r\nParser.prototype.onAttrName = function (name) {\r\n  name = this.xml ? name : name.toLowerCase()\r\n  if (name.substr(0, 5) === 'data-') {\r\n    if (name === 'data-src' && !this.attrs.src) {\r\n      // data-src 自动转为 src\r\n      this.attrName = 'src'\r\n    } else if (this.tagName === 'img' || this.tagName === 'a') {\r\n      // a 和 img 标签保留 data- 的属性，可以在 imgtap 和 linktap 事件中使用\r\n      this.attrName = name\r\n    } else {\r\n      // 剩余的移除以减小大小\r\n      this.attrName = undefined\r\n    }\r\n  } else {\r\n    this.attrName = name\r\n    this.attrs[name] = 'T' // boolean 型属性缺省设置\r\n  }\r\n}\r\n\r\n/**\r\n * @description 解析到属性值\r\n * @param {String} val 属性值\r\n * @private\r\n */\r\nParser.prototype.onAttrVal = function (val) {\r\n  const name = this.attrName || ''\r\n  if (name === 'style' || name === 'href') {\r\n    // 部分属性进行实体解码\r\n    this.attrs[name] = decodeEntity(val, true)\r\n  } else if (name.includes('src')) {\r\n    // 拼接主域名\r\n    this.attrs[name] = this.getUrl(decodeEntity(val, true))\r\n  } else if (name) {\r\n    this.attrs[name] = val\r\n  }\r\n}\r\n\r\n/**\r\n * @description 解析到标签开始\r\n * @param {Boolean} selfClose 是否有自闭合标识 />\r\n * @private\r\n */\r\nParser.prototype.onOpenTag = function (selfClose) {\r\n  // 拼装 node\r\n  const node = Object.create(null)\r\n  node.name = this.tagName\r\n  node.attrs = this.attrs\r\n  // 避免因为自动 diff 使得 type 被设置为 null 导致部分内容不显示\r\n  if (this.options.nodes.length) {\r\n    node.type = 'node'\r\n  }\r\n  this.attrs = Object.create(null)\r\n\r\n  const attrs = node.attrs\r\n  const parent = this.stack[this.stack.length - 1]\r\n  const siblings = parent ? parent.children : this.nodes\r\n  const close = this.xml ? selfClose : config.voidTags[node.name]\r\n\r\n  // 替换标签名选择器\r\n  if (tagSelector[node.name]) {\r\n    attrs.class = tagSelector[node.name] + (attrs.class ? ' ' + attrs.class : '')\r\n  }\r\n\r\n  // 转换 embed 标签\r\n  if (node.name === 'embed') {\r\n    // #ifndef H5 || APP-PLUS\r\n    const src = attrs.src || ''\r\n    // 按照后缀名和 type 将 embed 转为 video 或 audio\r\n    if (src.includes('.mp4') || src.includes('.3gp') || src.includes('.m3u8') || (attrs.type || '').includes('video')) {\r\n      node.name = 'video'\r\n    } else if (src.includes('.mp3') || src.includes('.wav') || src.includes('.aac') || src.includes('.m4a') || (attrs.type || '').includes('audio')) {\r\n      node.name = 'audio'\r\n    }\r\n    if (attrs.autostart) {\r\n      attrs.autoplay = 'T'\r\n    }\r\n    attrs.controls = 'T'\r\n    // #endif\r\n    // #ifdef H5 || APP-PLUS\r\n    this.expose()\r\n    // #endif\r\n  }\r\n\r\n  // #ifndef APP-PLUS-NVUE\r\n  // 处理音视频\r\n  if (node.name === 'video' || node.name === 'audio') {\r\n    // 设置 id 以便获取 context\r\n    if (node.name === 'video' && !attrs.id) {\r\n      attrs.id = 'v' + idIndex++\r\n    }\r\n    // 没有设置 controls 也没有设置 autoplay 的自动设置 controls\r\n    if (!attrs.controls && !attrs.autoplay) {\r\n      attrs.controls = 'T'\r\n    }\r\n    // 用数组存储所有可用的 source\r\n    node.src = []\r\n    if (attrs.src) {\r\n      node.src.push(attrs.src)\r\n      attrs.src = undefined\r\n    }\r\n    this.expose()\r\n  }\r\n  // #endif\r\n\r\n  // 处理自闭合标签\r\n  if (close) {\r\n    if (!this.hook(node) || config.ignoreTags[node.name]) {\r\n      // 通过 base 标签设置主域名\r\n      if (node.name === 'base' && !this.options.domain) {\r\n        this.options.domain = attrs.href\r\n      } /* #ifndef APP-PLUS-NVUE */ else if (node.name === 'source' && parent && (parent.name === 'video' || parent.name === 'audio') && attrs.src) {\r\n        // 设置 source 标签（仅父节点为 video 或 audio 时有效）\r\n        parent.src.push(attrs.src)\r\n      } /* #endif */\r\n      return\r\n    }\r\n\r\n    // 解析 style\r\n    const styleObj = this.parseStyle(node)\r\n\r\n    // 处理图片\r\n    if (node.name === 'img') {\r\n      if (attrs.src) {\r\n        // 标记 webp\r\n        if (attrs.src.includes('webp')) {\r\n          node.webp = 'T'\r\n        }\r\n        // data url 图片如果没有设置 original-src 默认为不可预览的小图片\r\n        if (attrs.src.includes('data:') && !attrs['original-src']) {\r\n          attrs.ignore = 'T'\r\n        }\r\n        if (!attrs.ignore || node.webp || attrs.src.includes('cloud://')) {\r\n          for (let i = this.stack.length; i--;) {\r\n            const item = this.stack[i]\r\n            if (item.name === 'a') {\r\n              node.a = item.attrs\r\n            }\r\n            if (item.name === 'table' && !node.webp && !attrs.src.includes('cloud://')) {\r\n              if (!styleObj.display || styleObj.display.includes('inline')) {\r\n                node.t = 'inline-block'\r\n              } else {\r\n                node.t = styleObj.display\r\n              }\r\n              styleObj.display = undefined\r\n            }\r\n            // #ifndef H5 || APP-PLUS\r\n            const style = item.attrs.style || ''\r\n            if (style.includes('flex:') && !style.includes('flex:0') && !style.includes('flex: 0') && (!styleObj.width || parseInt(styleObj.width) > 100)) {\r\n              styleObj.width = '100% !important'\r\n              styleObj.height = ''\r\n              for (let j = i + 1; j < this.stack.length; j++) {\r\n                this.stack[j].attrs.style = (this.stack[j].attrs.style || '').replace('inline-', '')\r\n              }\r\n            } else if (style.includes('flex') && styleObj.width === '100%') {\r\n              for (let j = i + 1; j < this.stack.length; j++) {\r\n                const style = this.stack[j].attrs.style || ''\r\n                if (!style.includes(';width') && !style.includes(' width') && style.indexOf('width') !== 0) {\r\n                  styleObj.width = ''\r\n                  break\r\n                }\r\n              }\r\n            } else if (style.includes('inline-block')) {\r\n              if (styleObj.width && styleObj.width[styleObj.width.length - 1] === '%') {\r\n                item.attrs.style += ';max-width:' + styleObj.width\r\n                styleObj.width = ''\r\n              } else {\r\n                item.attrs.style += ';max-width:100%'\r\n              }\r\n            }\r\n            // #endif\r\n            item.c = 1\r\n          }\r\n          attrs.i = this.imgList.length.toString()\r\n          let src = attrs['original-src'] || attrs.src\r\n          // #ifndef H5 || MP-ALIPAY || APP-PLUS || MP-360\r\n          if (this.imgList.includes(src)) {\r\n            // 如果有重复的链接则对域名进行随机大小写变换避免预览时错位\r\n            let i = src.indexOf('://')\r\n            if (i !== -1) {\r\n              i += 3\r\n              let newSrc = src.substr(0, i)\r\n              for (; i < src.length; i++) {\r\n                if (src[i] === '/') break\r\n                newSrc += Math.random() > 0.5 ? src[i].toUpperCase() : src[i]\r\n              }\r\n              newSrc += src.substr(i)\r\n              src = newSrc\r\n            }\r\n          }\r\n          // #endif\r\n          this.imgList.push(src)\r\n          if (!node.t) {\r\n            this.imgList._unloadimgs += 1\r\n          }\r\n          // #ifdef H5 || APP-PLUS\r\n          if (this.options.lazyLoad) {\r\n            attrs['data-src'] = attrs.src\r\n            attrs.src = undefined\r\n          }\r\n          // #endif\r\n        }\r\n      }\r\n      if (styleObj.display === 'inline') {\r\n        styleObj.display = ''\r\n      }\r\n      // #ifndef APP-PLUS-NVUE\r\n      if (attrs.ignore) {\r\n        styleObj['max-width'] = styleObj['max-width'] || '100%'\r\n        attrs.style += ';-webkit-touch-callout:none'\r\n      }\r\n      // #endif\r\n      // 设置的宽度超出屏幕，为避免变形，高度转为自动\r\n      if (parseInt(styleObj.width) > windowWidth) {\r\n        styleObj.height = undefined\r\n      }\r\n      // 记录是否设置了宽高\r\n      if (!isNaN(parseInt(styleObj.width))) {\r\n        node.w = 'T'\r\n      }\r\n      if (!isNaN(parseInt(styleObj.height)) && (!styleObj.height.includes('%') || (parent && (parent.attrs.style || '').includes('height')))) {\r\n        node.h = 'T'\r\n      }\r\n    } else if (node.name === 'svg') {\r\n      siblings.push(node)\r\n      this.stack.push(node)\r\n      this.popNode()\r\n      return\r\n    }\r\n    for (const key in styleObj) {\r\n      if (styleObj[key]) {\r\n        attrs.style += `;${key}:${styleObj[key].replace(' !important', '')}`\r\n      }\r\n    }\r\n    attrs.style = attrs.style.substr(1) || undefined\r\n    // #ifdef (MP-WEIXIN || MP-QQ) && VUE3\r\n    if (!attrs.style) {\r\n      delete attrs.style\r\n    }\r\n    // #endif\r\n  } else {\r\n    if ((node.name === 'pre' || ((attrs.style || '').includes('white-space') && attrs.style.includes('pre'))) && this.pre !== 2) {\r\n      this.pre = node.pre = 1\r\n    }\r\n    node.children = []\r\n    this.stack.push(node)\r\n  }\r\n\r\n  // 加入节点树\r\n  siblings.push(node)\r\n}\r\n\r\n/**\r\n * @description 解析到标签结束\r\n * @param {String} name 标签名\r\n * @private\r\n */\r\nParser.prototype.onCloseTag = function (name) {\r\n  // 依次出栈到匹配为止\r\n  name = this.xml ? name : name.toLowerCase()\r\n  let i\r\n  for (i = this.stack.length; i--;) {\r\n    if (this.stack[i].name === name) break\r\n  }\r\n  if (i !== -1) {\r\n    while (this.stack.length > i) {\r\n      this.popNode()\r\n    }\r\n  } else if (name === 'p' || name === 'br') {\r\n    const siblings = this.stack.length ? this.stack[this.stack.length - 1].children : this.nodes\r\n    siblings.push({\r\n      name,\r\n      attrs: {\r\n        class: tagSelector[name] || '',\r\n        style: this.tagStyle[name] || ''\r\n      }\r\n    })\r\n  }\r\n}\r\n\r\n/**\r\n * @description 处理标签出栈\r\n * @private\r\n */\r\nParser.prototype.popNode = function () {\r\n  const node = this.stack.pop()\r\n  let attrs = node.attrs\r\n  const children = node.children\r\n  const parent = this.stack[this.stack.length - 1]\r\n  const siblings = parent ? parent.children : this.nodes\r\n\r\n  if (!this.hook(node) || config.ignoreTags[node.name]) {\r\n    // 获取标题\r\n    if (node.name === 'title' && children.length && children[0].type === 'text' && this.options.setTitle) {\r\n      uni.setNavigationBarTitle({\r\n        title: children[0].text\r\n      })\r\n    }\r\n    siblings.pop()\r\n    return\r\n  }\r\n\r\n  if (node.pre && this.pre !== 2) {\r\n    // 是否合并空白符标识\r\n    this.pre = node.pre = undefined\r\n    for (let i = this.stack.length; i--;) {\r\n      if (this.stack[i].pre) {\r\n        this.pre = 1\r\n      }\r\n    }\r\n  }\r\n\r\n  const styleObj = {}\r\n\r\n  // 转换 svg\r\n  if (node.name === 'svg') {\r\n    if (this.xml > 1) {\r\n      // 多层 svg 嵌套\r\n      this.xml--\r\n      return\r\n    }\r\n    // #ifdef APP-PLUS-NVUE\r\n    (function traversal (node) {\r\n      if (node.name) {\r\n        // 调整 svg 的大小写\r\n        node.name = config.svgDict[node.name] || node.name\r\n        for (const item in node.attrs) {\r\n          if (config.svgDict[item]) {\r\n            node.attrs[config.svgDict[item]] = node.attrs[item]\r\n            node.attrs[item] = undefined\r\n          }\r\n        }\r\n        for (let i = 0; i < (node.children || []).length; i++) {\r\n          traversal(node.children[i])\r\n        }\r\n      }\r\n    })(node)\r\n    // #endif\r\n    // #ifndef APP-PLUS-NVUE\r\n    let src = ''\r\n    const style = attrs.style\r\n    attrs.style = ''\r\n    attrs.xmlns = 'http://www.w3.org/2000/svg';\r\n    (function traversal (node) {\r\n      if (node.type === 'text') {\r\n        src += node.text\r\n        return\r\n      }\r\n      const name = config.svgDict[node.name] || node.name\r\n      src += '<' + name\r\n      for (const item in node.attrs) {\r\n        const val = node.attrs[item]\r\n        if (val) {\r\n          src += ` ${config.svgDict[item] || item}=\"${val}\"`\r\n        }\r\n      }\r\n      if (!node.children) {\r\n        src += '/>'\r\n      } else {\r\n        src += '>'\r\n        for (let i = 0; i < node.children.length; i++) {\r\n          traversal(node.children[i])\r\n        }\r\n        src += '</' + name + '>'\r\n      }\r\n    })(node)\r\n    node.name = 'img'\r\n    node.attrs = {\r\n      src: 'data:image/svg+xml;utf8,' + src.replace(/#/g, '%23'),\r\n      style,\r\n      ignore: 'T'\r\n    }\r\n    node.children = undefined\r\n    // #endif\r\n    this.xml = false\r\n    config.ignoreTags.style = true\r\n    return\r\n  }\r\n\r\n  // #ifndef APP-PLUS-NVUE\r\n  // 转换 align 属性\r\n  if (attrs.align) {\r\n    if (node.name === 'table') {\r\n      if (attrs.align === 'center') {\r\n        styleObj['margin-inline-start'] = styleObj['margin-inline-end'] = 'auto'\r\n      } else {\r\n        styleObj.float = attrs.align\r\n      }\r\n    } else {\r\n      styleObj['text-align'] = attrs.align\r\n    }\r\n    attrs.align = undefined\r\n  }\r\n\r\n  // 转换 dir 属性\r\n  if (attrs.dir) {\r\n    styleObj.direction = attrs.dir\r\n    attrs.dir = undefined\r\n  }\r\n\r\n  // 转换 font 标签的属性\r\n  if (node.name === 'font') {\r\n    if (attrs.color) {\r\n      styleObj.color = attrs.color\r\n      attrs.color = undefined\r\n    }\r\n    if (attrs.face) {\r\n      styleObj['font-family'] = attrs.face\r\n      attrs.face = undefined\r\n    }\r\n    if (attrs.size) {\r\n      let size = parseInt(attrs.size)\r\n      if (!isNaN(size)) {\r\n        if (size < 1) {\r\n          size = 1\r\n        } else if (size > 7) {\r\n          size = 7\r\n        }\r\n        styleObj['font-size'] = ['x-small', 'small', 'medium', 'large', 'x-large', 'xx-large', 'xxx-large'][size - 1]\r\n      }\r\n      attrs.size = undefined\r\n    }\r\n  }\r\n  // #endif\r\n\r\n  // 一些编辑器的自带 class\r\n  if ((attrs.class || '').includes('align-center')) {\r\n    styleObj['text-align'] = 'center'\r\n  }\r\n\r\n  Object.assign(styleObj, this.parseStyle(node))\r\n\r\n  if (node.name !== 'table' && parseInt(styleObj.width) > windowWidth) {\r\n    styleObj['max-width'] = '100%'\r\n    styleObj['box-sizing'] = 'border-box'\r\n  }\r\n\r\n  // #ifndef APP-PLUS-NVUE\r\n  if (config.blockTags[node.name]) {\r\n    node.name = 'div'\r\n  } else if (!config.trustTags[node.name] && !this.xml) {\r\n    // 未知标签转为 span，避免无法显示\r\n    node.name = 'span'\r\n  }\r\n\r\n  if (node.name === 'a' || node.name === 'ad'\r\n    // #ifdef H5 || APP-PLUS\r\n    || node.name === 'iframe' // eslint-disable-line\r\n    // #endif\r\n  ) {\r\n    this.expose()\r\n  } else if (node.name === 'video') {\r\n    if ((styleObj.height || '').includes('auto')) {\r\n      styleObj.height = undefined\r\n    }\r\n    /* #ifdef APP-PLUS */\r\n    let str = '<video style=\"width:100%;height:100%\"'\r\n    for (const item in attrs) {\r\n      if (attrs[item]) {\r\n        str += ' ' + item + '=\"' + attrs[item] + '\"'\r\n      }\r\n    }\r\n    if (this.options.pauseVideo) {\r\n      str += ' onplay=\"this.dispatchEvent(new CustomEvent(\\'vplay\\',{bubbles:!0}));for(var e=document.getElementsByTagName(\\'video\\'),t=0;t<e.length;t++)e[t]!=this&&e[t].pause()\"'\r\n    }\r\n    str += '>'\r\n    for (let i = 0; i < node.src.length; i++) {\r\n      str += '<source src=\"' + node.src[i] + '\">'\r\n    }\r\n    str += '</video>'\r\n    node.html = str\r\n    /* #endif */\r\n  } else if ((node.name === 'ul' || node.name === 'ol') && node.c) {\r\n    // 列表处理\r\n    const types = {\r\n      a: 'lower-alpha',\r\n      A: 'upper-alpha',\r\n      i: 'lower-roman',\r\n      I: 'upper-roman'\r\n    }\r\n    if (types[attrs.type]) {\r\n      attrs.style += ';list-style-type:' + types[attrs.type]\r\n      attrs.type = undefined\r\n    }\r\n    for (let i = children.length; i--;) {\r\n      if (children[i].name === 'li') {\r\n        children[i].c = 1\r\n      }\r\n    }\r\n  } else if (node.name === 'table') {\r\n    // 表格处理\r\n    // cellpadding、cellspacing、border 这几个常用表格属性需要通过转换实现\r\n    let padding = parseFloat(attrs.cellpadding)\r\n    let spacing = parseFloat(attrs.cellspacing)\r\n    const border = parseFloat(attrs.border)\r\n    const bordercolor = styleObj['border-color']\r\n    const borderstyle = styleObj['border-style']\r\n    if (node.c) {\r\n      // padding 和 spacing 默认 2\r\n      if (isNaN(padding)) {\r\n        padding = 2\r\n      }\r\n      if (isNaN(spacing)) {\r\n        spacing = 2\r\n      }\r\n    }\r\n    if (border) {\r\n      attrs.style += `;border:${border}px ${borderstyle || 'solid'} ${bordercolor || 'gray'}`\r\n    }\r\n    if (node.flag && node.c) {\r\n      // 有 colspan 或 rowspan 且含有链接的表格通过 grid 布局实现\r\n      styleObj.display = 'grid'\r\n      if (spacing) {\r\n        styleObj['grid-gap'] = spacing + 'px'\r\n        styleObj.padding = spacing + 'px'\r\n      } else if (border) {\r\n        // 无间隔的情况下避免边框重叠\r\n        attrs.style += ';border-left:0;border-top:0'\r\n      }\r\n\r\n      const width = [] // 表格的列宽\r\n      const trList = [] // tr 列表\r\n      const cells = [] // 保存新的单元格\r\n      const map = {}; // 被合并单元格占用的格子\r\n\r\n      (function traversal (nodes) {\r\n        for (let i = 0; i < nodes.length; i++) {\r\n          if (nodes[i].name === 'tr') {\r\n            trList.push(nodes[i])\r\n          } else {\r\n            traversal(nodes[i].children || [])\r\n          }\r\n        }\r\n      })(children)\r\n\r\n      for (let row = 1; row <= trList.length; row++) {\r\n        let col = 1\r\n        for (let j = 0; j < trList[row - 1].children.length; j++) {\r\n          const td = trList[row - 1].children[j]\r\n          if (td.name === 'td' || td.name === 'th') {\r\n            // 这个格子被上面的单元格占用，则列号++\r\n            while (map[row + '.' + col]) {\r\n              col++\r\n            }\r\n            let style = td.attrs.style || ''\r\n            let start = style.indexOf('width') ? style.indexOf(';width') : 0\r\n            // 提取出 td 的宽度\r\n            if (start !== -1) {\r\n              let end = style.indexOf(';', start + 6)\r\n              if (end === -1) {\r\n                end = style.length\r\n              }\r\n              if (!td.attrs.colspan) {\r\n                width[col] = style.substring(start ? start + 7 : 6, end)\r\n              }\r\n              style = style.substr(0, start) + style.substr(end)\r\n            }\r\n            // 设置竖直对齐\r\n            style += ';display:flex'\r\n            start = style.indexOf('vertical-align')\r\n            if (start !== -1) {\r\n              const val = style.substr(start + 15, 10)\r\n              if (val.includes('middle')) {\r\n                style += ';align-items:center'\r\n              } else if (val.includes('bottom')) {\r\n                style += ';align-items:flex-end'\r\n              }\r\n            } else {\r\n              style += ';align-items:center'\r\n            }\r\n            // 设置水平对齐\r\n            start = style.indexOf('text-align')\r\n            if (start !== -1) {\r\n              const val = style.substr(start + 11, 10)\r\n              if (val.includes('center')) {\r\n                style += ';justify-content: center'\r\n              } else if (val.includes('right')) {\r\n                style += ';justify-content: right'\r\n              }\r\n            }\r\n            style = (border ? `;border:${border}px ${borderstyle || 'solid'} ${bordercolor || 'gray'}` + (spacing ? '' : ';border-right:0;border-bottom:0') : '') + (padding ? `;padding:${padding}px` : '') + ';' + style\r\n            // 处理列合并\r\n            if (td.attrs.colspan) {\r\n              style += `;grid-column-start:${col};grid-column-end:${col + parseInt(td.attrs.colspan)}`\r\n              if (!td.attrs.rowspan) {\r\n                style += `;grid-row-start:${row};grid-row-end:${row + 1}`\r\n              }\r\n              col += parseInt(td.attrs.colspan) - 1\r\n            }\r\n            // 处理行合并\r\n            if (td.attrs.rowspan) {\r\n              style += `;grid-row-start:${row};grid-row-end:${row + parseInt(td.attrs.rowspan)}`\r\n              if (!td.attrs.colspan) {\r\n                style += `;grid-column-start:${col};grid-column-end:${col + 1}`\r\n              }\r\n              // 记录下方单元格被占用\r\n              for (let rowspan = 1; rowspan < td.attrs.rowspan; rowspan++) {\r\n                for (let colspan = 0; colspan < (td.attrs.colspan || 1); colspan++) {\r\n                  map[(row + rowspan) + '.' + (col - colspan)] = 1\r\n                }\r\n              }\r\n            }\r\n            if (style) {\r\n              td.attrs.style = style\r\n            }\r\n            cells.push(td)\r\n            col++\r\n          }\r\n        }\r\n        if (row === 1) {\r\n          let temp = ''\r\n          for (let i = 1; i < col; i++) {\r\n            temp += (width[i] ? width[i] : 'auto') + ' '\r\n          }\r\n          styleObj['grid-template-columns'] = temp\r\n        }\r\n      }\r\n      node.children = cells\r\n    } else {\r\n      // 没有使用合并单元格的表格通过 table 布局实现\r\n      if (node.c) {\r\n        styleObj.display = 'table'\r\n      }\r\n      if (!isNaN(spacing)) {\r\n        styleObj['border-spacing'] = spacing + 'px'\r\n      }\r\n      if (border || padding) {\r\n        // 遍历\r\n        (function traversal (nodes) {\r\n          for (let i = 0; i < nodes.length; i++) {\r\n            const td = nodes[i]\r\n            if (td.name === 'th' || td.name === 'td') {\r\n              if (border) {\r\n                td.attrs.style = `border:${border}px ${borderstyle || 'solid'} ${bordercolor || 'gray'};${td.attrs.style || ''}`\r\n              }\r\n              if (padding) {\r\n                td.attrs.style = `padding:${padding}px;${td.attrs.style || ''}`\r\n              }\r\n            } else if (td.children) {\r\n              traversal(td.children)\r\n            }\r\n          }\r\n        })(children)\r\n      }\r\n    }\r\n    // 给表格添加一个单独的横向滚动层\r\n    if (this.options.scrollTable && !(attrs.style || '').includes('inline')) {\r\n      const table = Object.assign({}, node)\r\n      node.name = 'div'\r\n      node.attrs = {\r\n        style: 'overflow:auto'\r\n      }\r\n      node.children = [table]\r\n      attrs = table.attrs\r\n    }\r\n  } else if ((node.name === 'td' || node.name === 'th') && (attrs.colspan || attrs.rowspan)) {\r\n    for (let i = this.stack.length; i--;) {\r\n      if (this.stack[i].name === 'table') {\r\n        this.stack[i].flag = 1 // 指示含有合并单元格\r\n        break\r\n      }\r\n    }\r\n  } else if (node.name === 'ruby') {\r\n    // 转换 ruby\r\n    node.name = 'span'\r\n    for (let i = 0; i < children.length - 1; i++) {\r\n      if (children[i].type === 'text' && children[i + 1].name === 'rt') {\r\n        children[i] = {\r\n          name: 'div',\r\n          attrs: {\r\n            style: 'display:inline-block;text-align:center'\r\n          },\r\n          children: [{\r\n            name: 'div',\r\n            attrs: {\r\n              style: 'font-size:50%;' + (children[i + 1].attrs.style || '')\r\n            },\r\n            children: children[i + 1].children\r\n          }, children[i]]\r\n        }\r\n        children.splice(i + 1, 1)\r\n      }\r\n    }\r\n  } else if (node.c) {\r\n    (function traversal (node) {\r\n      node.c = 2\r\n      for (let i = node.children.length; i--;) {\r\n        const child = node.children[i]\r\n        // #ifdef (MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE3\r\n        if (child.name && (config.inlineTags[child.name] || ((child.attrs.style || '').includes('inline') && child.children)) && !child.c) {\r\n          traversal(child)\r\n        }\r\n        // #endif\r\n        if (!child.c || child.name === 'table') {\r\n          node.c = 1\r\n        }\r\n      }\r\n    })(node)\r\n  }\r\n\r\n  if ((styleObj.display || '').includes('flex') && !node.c) {\r\n    for (let i = children.length; i--;) {\r\n      const item = children[i]\r\n      if (item.f) {\r\n        item.attrs.style = (item.attrs.style || '') + item.f\r\n        item.f = undefined\r\n      }\r\n    }\r\n  }\r\n  // flex 布局时部分样式需要提取到 rich-text 外层\r\n  const flex = parent && ((parent.attrs.style || '').includes('flex') || (parent.attrs.style || '').includes('grid'))\r\n    // #ifdef MP-WEIXIN\r\n    // 检查基础库版本 virtualHost 是否可用\r\n    && !(node.c && wx.getNFCAdapter) // eslint-disable-line\r\n    // #endif\r\n    // #ifndef MP-WEIXIN || MP-QQ || MP-BAIDU || MP-TOUTIAO\r\n    && !node.c // eslint-disable-line\r\n  // #endif\r\n  if (flex) {\r\n    node.f = ';max-width:100%'\r\n  }\r\n\r\n  if (children.length >= 50 && node.c && !(styleObj.display || '').includes('flex')) {\r\n    mergeNodes(children)\r\n  }\r\n  // #endif\r\n\r\n  for (const key in styleObj) {\r\n    if (styleObj[key]) {\r\n      const val = `;${key}:${styleObj[key].replace(' !important', '')}`\r\n      /* #ifndef APP-PLUS-NVUE */\r\n      if (flex && ((key.includes('flex') && key !== 'flex-direction') || key === 'align-self' || key.includes('grid') || styleObj[key][0] === '-' || (key.includes('width') && val.includes('%')))) {\r\n        node.f += val\r\n        if (key === 'width') {\r\n          attrs.style += ';width:100%'\r\n        }\r\n      } else /* #endif */ {\r\n        attrs.style += val\r\n      }\r\n    }\r\n  }\r\n  attrs.style = attrs.style.substr(1) || undefined\r\n  // #ifdef (MP-WEIXIN || MP-QQ) && VUE3\r\n  for (const key in attrs) {\r\n    if (!attrs[key]) {\r\n      delete attrs[key]\r\n    }\r\n  }\r\n  // #endif\r\n}\r\n\r\n/**\r\n * @description 解析到文本\r\n * @param {String} text 文本内容\r\n */\r\nParser.prototype.onText = function (text) {\r\n  if (!this.pre) {\r\n    // 合并空白符\r\n    let trim = ''\r\n    let flag\r\n    for (let i = 0, len = text.length; i < len; i++) {\r\n      if (!blankChar[text[i]]) {\r\n        trim += text[i]\r\n      } else {\r\n        if (trim[trim.length - 1] !== ' ') {\r\n          trim += ' '\r\n        }\r\n        if (text[i] === '\\n' && !flag) {\r\n          flag = true\r\n        }\r\n      }\r\n    }\r\n    // 去除含有换行符的空串\r\n    if (trim === ' ') {\r\n      if (flag) return\r\n      // #ifdef VUE3\r\n      else {\r\n        const parent = this.stack[this.stack.length - 1]\r\n        if (parent && parent.name[0] === 't') return\r\n      }\r\n      // #endif\r\n    }\r\n    text = trim\r\n  }\r\n  const node = Object.create(null)\r\n  node.type = 'text'\r\n  // #ifdef (MP-BAIDU || MP-ALIPAY || MP-TOUTIAO) && VUE3\r\n  node.attrs = {}\r\n  // #endif\r\n  node.text = decodeEntity(text)\r\n  if (this.hook(node)) {\r\n    // #ifdef MP-WEIXIN\r\n    if (this.options.selectable === 'force' && system.includes('iOS') && !uni.canIUse('rich-text.user-select')) {\r\n      this.expose()\r\n    }\r\n    // #endif\r\n    const siblings = this.stack.length ? this.stack[this.stack.length - 1].children : this.nodes\r\n    siblings.push(node)\r\n  }\r\n}\r\n\r\n/**\r\n * @description html 词法分析器\r\n * @param {Object} handler 高层处理器\r\n */\r\nfunction Lexer (handler) {\r\n  this.handler = handler\r\n}\r\n\r\n/**\r\n * @description 执行解析\r\n * @param {String} content 要解析的文本\r\n */\r\nLexer.prototype.parse = function (content) {\r\n  this.content = content || ''\r\n  this.i = 0 // 标记解析位置\r\n  this.start = 0 // 标记一个单词的开始位置\r\n  this.state = this.text // 当前状态\r\n  for (let len = this.content.length; this.i !== -1 && this.i < len;) {\r\n    this.state()\r\n  }\r\n}\r\n\r\n/**\r\n * @description 检查标签是否闭合\r\n * @param {String} method 如果闭合要进行的操作\r\n * @returns {Boolean} 是否闭合\r\n * @private\r\n */\r\nLexer.prototype.checkClose = function (method) {\r\n  const selfClose = this.content[this.i] === '/'\r\n  if (this.content[this.i] === '>' || (selfClose && this.content[this.i + 1] === '>')) {\r\n    if (method) {\r\n      this.handler[method](this.content.substring(this.start, this.i))\r\n    }\r\n    this.i += selfClose ? 2 : 1\r\n    this.start = this.i\r\n    this.handler.onOpenTag(selfClose)\r\n    if (this.handler.tagName === 'script') {\r\n      this.i = this.content.indexOf('</', this.i)\r\n      if (this.i !== -1) {\r\n        this.i += 2\r\n        this.start = this.i\r\n      }\r\n      this.state = this.endTag\r\n    } else {\r\n      this.state = this.text\r\n    }\r\n    return true\r\n  }\r\n  return false\r\n}\r\n\r\n/**\r\n * @description 文本状态\r\n * @private\r\n */\r\nLexer.prototype.text = function () {\r\n  this.i = this.content.indexOf('<', this.i) // 查找最近的标签\r\n  if (this.i === -1) {\r\n    // 没有标签了\r\n    if (this.start < this.content.length) {\r\n      this.handler.onText(this.content.substring(this.start, this.content.length))\r\n    }\r\n    return\r\n  }\r\n  const c = this.content[this.i + 1]\r\n  if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {\r\n    // 标签开头\r\n    if (this.start !== this.i) {\r\n      this.handler.onText(this.content.substring(this.start, this.i))\r\n    }\r\n    this.start = ++this.i\r\n    this.state = this.tagName\r\n  } else if (c === '/' || c === '!' || c === '?') {\r\n    if (this.start !== this.i) {\r\n      this.handler.onText(this.content.substring(this.start, this.i))\r\n    }\r\n    const next = this.content[this.i + 2]\r\n    if (c === '/' && ((next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z'))) {\r\n      // 标签结尾\r\n      this.i += 2\r\n      this.start = this.i\r\n      this.state = this.endTag\r\n      return\r\n    }\r\n    // 处理注释\r\n    let end = '-->'\r\n    if (c !== '!' || this.content[this.i + 2] !== '-' || this.content[this.i + 3] !== '-') {\r\n      end = '>'\r\n    }\r\n    this.i = this.content.indexOf(end, this.i)\r\n    if (this.i !== -1) {\r\n      this.i += end.length\r\n      this.start = this.i\r\n    }\r\n  } else {\r\n    this.i++\r\n  }\r\n}\r\n\r\n/**\r\n * @description 标签名状态\r\n * @private\r\n */\r\nLexer.prototype.tagName = function () {\r\n  if (blankChar[this.content[this.i]]) {\r\n    // 解析到标签名\r\n    this.handler.onTagName(this.content.substring(this.start, this.i))\r\n    while (blankChar[this.content[++this.i]]);\r\n    if (this.i < this.content.length && !this.checkClose()) {\r\n      this.start = this.i\r\n      this.state = this.attrName\r\n    }\r\n  } else if (!this.checkClose('onTagName')) {\r\n    this.i++\r\n  }\r\n}\r\n\r\n/**\r\n * @description 属性名状态\r\n * @private\r\n */\r\nLexer.prototype.attrName = function () {\r\n  let c = this.content[this.i]\r\n  if (blankChar[c] || c === '=') {\r\n    // 解析到属性名\r\n    this.handler.onAttrName(this.content.substring(this.start, this.i))\r\n    let needVal = c === '='\r\n    const len = this.content.length\r\n    while (++this.i < len) {\r\n      c = this.content[this.i]\r\n      if (!blankChar[c]) {\r\n        if (this.checkClose()) return\r\n        if (needVal) {\r\n          // 等号后遇到第一个非空字符\r\n          this.start = this.i\r\n          this.state = this.attrVal\r\n          return\r\n        }\r\n        if (this.content[this.i] === '=') {\r\n          needVal = true\r\n        } else {\r\n          this.start = this.i\r\n          this.state = this.attrName\r\n          return\r\n        }\r\n      }\r\n    }\r\n  } else if (!this.checkClose('onAttrName')) {\r\n    this.i++\r\n  }\r\n}\r\n\r\n/**\r\n * @description 属性值状态\r\n * @private\r\n */\r\nLexer.prototype.attrVal = function () {\r\n  const c = this.content[this.i]\r\n  const len = this.content.length\r\n  if (c === '\"' || c === \"'\") {\r\n    // 有冒号的属性\r\n    this.start = ++this.i\r\n    this.i = this.content.indexOf(c, this.i)\r\n    if (this.i === -1) return\r\n    this.handler.onAttrVal(this.content.substring(this.start, this.i))\r\n  } else {\r\n    // 没有冒号的属性\r\n    for (; this.i < len; this.i++) {\r\n      if (blankChar[this.content[this.i]]) {\r\n        this.handler.onAttrVal(this.content.substring(this.start, this.i))\r\n        break\r\n      } else if (this.checkClose('onAttrVal')) return\r\n    }\r\n  }\r\n  while (blankChar[this.content[++this.i]]);\r\n  if (this.i < len && !this.checkClose()) {\r\n    this.start = this.i\r\n    this.state = this.attrName\r\n  }\r\n}\r\n\r\n/**\r\n * @description 结束标签状态\r\n * @returns {String} 结束的标签名\r\n * @private\r\n */\r\nLexer.prototype.endTag = function () {\r\n  const c = this.content[this.i]\r\n  if (blankChar[c] || c === '>' || c === '/') {\r\n    this.handler.onCloseTag(this.content.substring(this.start, this.i))\r\n    if (c !== '>') {\r\n      this.i = this.content.indexOf('>', this.i)\r\n      if (this.i === -1) return\r\n    }\r\n    this.start = ++this.i\r\n    this.state = this.text\r\n  } else {\r\n    this.i++\r\n  }\r\n}\r\n\r\nexport default Parser\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-parse/components/uv-parse/uv-parse.vue",
    "content": "<template>\r\n  <view id=\"_root\" :class=\"(selectable?'_select ':'')+'_root'\" :style=\"containerStyle\">\r\n    <slot v-if=\"!nodes[0]\" />\r\n    <!-- #ifndef APP-PLUS-NVUE -->\r\n    <node v-else :childs=\"nodes\" :opts=\"[lazyLoad,loadingImg,errorImg,showImgMenu,selectable]\" name=\"span\" />\r\n    <!-- #endif -->\r\n    <!-- #ifdef APP-PLUS-NVUE -->\r\n    <web-view ref=\"web\" src=\"/uni_modules/uv-parse/static/app-plus/uv-parse/local.html\" :style=\"'margin-top:-2px;height:' + height + 'px'\" @onPostMessage=\"_onMessage\" />\r\n    <!-- #endif -->\r\n  </view>\r\n</template>\r\n\r\n<script>\r\n/**\r\n * uv-parse v1.0.3\r\n * @description 富文本组件\r\n * @tutorial https://www.uvui.cn/components/parse.html\r\n * @property {String} container-style 容器的样式\r\n * @property {String} content 用于渲染的 html 字符串\r\n * @property {Boolean} copy-link 是否允许外部链接被点击时自动复制\r\n * @property {String} domain 主域名，用于拼接链接\r\n * @property {String} error-img 图片出错时的占位图链接\r\n * @property {Boolean} lazy-load 是否开启图片懒加载\r\n * @property {string} loading-img 图片加载过程中的占位图链接\r\n * @property {Boolean} pause-video 是否在播放一个视频时自动暂停其他视频\r\n * @property {Boolean} preview-img 是否允许图片被点击时自动预览\r\n * @property {Boolean} scroll-table 是否给每个表格添加一个滚动层使其能单独横向滚动\r\n * @property {Boolean | String} selectable 是否开启长按复制\r\n * @property {Boolean} set-title 是否将 title 标签的内容设置到页面标题\r\n * @property {Boolean} show-img-menu 是否允许图片被长按时显示菜单\r\n * @property {Object} tag-style 标签的默认样式\r\n * @property {Boolean | Number} use-anchor 是否使用锚点链接\r\n * @event {Function} load dom 结构加载完毕时触发\r\n * @event {Function} ready 所有图片加载完毕时触发\r\n * @event {Function} imgtap 图片被点击时触发\r\n * @event {Function} linktap 链接被点击时触发\r\n * @event {Function} play 音视频播放时触发\r\n * @event {Function} error 媒体加载出错时触发\r\n */\r\n// #ifndef APP-PLUS-NVUE\r\nimport node from './node/node'\r\n// #endif\r\nimport Parser from './parser'\r\nconst plugins=[]\r\n// #ifdef APP-PLUS-NVUE\r\nconst dom = weex.requireModule('dom')\r\n// #endif\r\nexport default {\r\n  name: 'uv-parse',\r\n  data () {\r\n    return {\r\n      nodes: [],\r\n      // #ifdef APP-PLUS-NVUE\r\n      height: 3\r\n      // #endif\r\n    }\r\n  },\r\n  props: {\r\n    containerStyle: {\r\n      type: String,\r\n      default: ''\r\n    },\r\n    content: {\r\n      type: String,\r\n      default: ''\r\n    },\r\n    copyLink: {\r\n      type: [Boolean, String],\r\n      default: true\r\n    },\r\n    domain: String,\r\n    errorImg: {\r\n      type: String,\r\n      default: ''\r\n    },\r\n    lazyLoad: {\r\n      type: [Boolean, String],\r\n      default: false\r\n    },\r\n    loadingImg: {\r\n      type: String,\r\n      default: ''\r\n    },\r\n    pauseVideo: {\r\n      type: [Boolean, String],\r\n      default: true\r\n    },\r\n    previewImg: {\r\n      type: [Boolean, String],\r\n      default: true\r\n    },\r\n    scrollTable: [Boolean, String],\r\n    selectable: [Boolean, String],\r\n    setTitle: {\r\n      type: [Boolean, String],\r\n      default: true\r\n    },\r\n    showImgMenu: {\r\n      type: [Boolean, String],\r\n      default: true\r\n    },\r\n    tagStyle: Object,\r\n    useAnchor: [Boolean, Number]\r\n  },\r\n  // #ifdef VUE3\r\n  emits: ['load', 'ready', 'imgtap', 'linktap', 'play', 'error'],\r\n  // #endif\r\n  // #ifndef APP-PLUS-NVUE\r\n  components: {\r\n    node\r\n  },\r\n  // #endif\r\n  watch: {\r\n    content (content) {\r\n      this.setContent(content)\r\n    }\r\n  },\r\n  created () {\r\n    this.plugins = []\r\n    for (let i = plugins.length; i--;) {\r\n      this.plugins.push(new plugins[i](this))\r\n    }\r\n  },\r\n  mounted () {\r\n    if (this.content && !this.nodes.length) {\r\n      this.setContent(this.content)\r\n    }\r\n  },\r\n  beforeDestroy () {\r\n    this._hook('onDetached')\r\n  },\r\n  methods: {\r\n    /**\r\n     * @description 将锚点跳转的范围限定在一个 scroll-view 内\r\n     * @param {Object} page scroll-view 所在页面的示例\r\n     * @param {String} selector scroll-view 的选择器\r\n     * @param {String} scrollTop scroll-view scroll-top 属性绑定的变量名\r\n     */\r\n    in (page, selector, scrollTop) {\r\n      // #ifndef APP-PLUS-NVUE\r\n      if (page && selector && scrollTop) {\r\n        this._in = {\r\n          page,\r\n          selector,\r\n          scrollTop\r\n        }\r\n      }\r\n      // #endif\r\n    },\r\n\r\n    /**\r\n     * @description 锚点跳转\r\n     * @param {String} id 要跳转的锚点 id\r\n     * @param {Number} offset 跳转位置的偏移量\r\n     * @returns {Promise}\r\n     */\r\n    navigateTo (id, offset) {\r\n      return new Promise((resolve, reject) => {\r\n        if (!this.useAnchor) {\r\n          reject(Error('Anchor is disabled'))\r\n          return\r\n        }\r\n        offset = offset || parseInt(this.useAnchor) || 0\r\n        // #ifdef APP-PLUS-NVUE\r\n        if (!id) {\r\n          dom.scrollToElement(this.$refs.web, {\r\n            offset\r\n          })\r\n          resolve()\r\n        } else {\r\n          this._navigateTo = {\r\n            resolve,\r\n            reject,\r\n            offset\r\n          }\r\n          this.$refs.web.evalJs('uni.postMessage({data:{action:\"getOffset\",offset:(document.getElementById(' + id + ')||{}).offsetTop}})')\r\n        }\r\n        // #endif\r\n        // #ifndef APP-PLUS-NVUE\r\n        let deep = ' '\r\n        // #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO\r\n        deep = '>>>'\r\n        // #endif\r\n        const selector = uni.createSelectorQuery()\r\n          // #ifndef MP-ALIPAY\r\n          .in(this._in ? this._in.page : this)\r\n          // #endif\r\n          .select((this._in ? this._in.selector : '._root') + (id ? `${deep}#${id}` : '')).boundingClientRect()\r\n        if (this._in) {\r\n          selector.select(this._in.selector).scrollOffset()\r\n            .select(this._in.selector).boundingClientRect()\r\n        } else {\r\n          // 获取 scroll-view 的位置和滚动距离\r\n          selector.selectViewport().scrollOffset() // 获取窗口的滚动距离\r\n        }\r\n        selector.exec(res => {\r\n          if (!res[0]) {\r\n            reject(Error('Label not found'))\r\n            return\r\n          }\r\n          const scrollTop = res[1].scrollTop + res[0].top - (res[2] ? res[2].top : 0) + offset\r\n          if (this._in) {\r\n            // scroll-view 跳转\r\n            this._in.page[this._in.scrollTop] = scrollTop\r\n          } else {\r\n            // 页面跳转\r\n            uni.pageScrollTo({\r\n              scrollTop,\r\n              duration: 300\r\n            })\r\n          }\r\n          resolve()\r\n        })\r\n        // #endif\r\n      })\r\n    },\r\n\r\n    /**\r\n     * @description 获取文本内容\r\n     * @return {String}\r\n     */\r\n    getText (nodes) {\r\n      let text = '';\r\n      (function traversal (nodes) {\r\n        for (let i = 0; i < nodes.length; i++) {\r\n          const node = nodes[i]\r\n          if (node.type === 'text') {\r\n            text += node.text.replace(/&amp;/g, '&')\r\n          } else if (node.name === 'br') {\r\n            text += '\\n'\r\n          } else {\r\n            // 块级标签前后加换行\r\n            const isBlock = node.name === 'p' || node.name === 'div' || node.name === 'tr' || node.name === 'li' || (node.name[0] === 'h' && node.name[1] > '0' && node.name[1] < '7')\r\n            if (isBlock && text && text[text.length - 1] !== '\\n') {\r\n              text += '\\n'\r\n            }\r\n            // 递归获取子节点的文本\r\n            if (node.children) {\r\n              traversal(node.children)\r\n            }\r\n            if (isBlock && text[text.length - 1] !== '\\n') {\r\n              text += '\\n'\r\n            } else if (node.name === 'td' || node.name === 'th') {\r\n              text += '\\t'\r\n            }\r\n          }\r\n        }\r\n      })(nodes || this.nodes)\r\n      return text\r\n    },\r\n\r\n    /**\r\n     * @description 获取内容大小和位置\r\n     * @return {Promise}\r\n     */\r\n    getRect () {\r\n      return new Promise((resolve, reject) => {\r\n        uni.createSelectorQuery()\r\n          // #ifndef MP-ALIPAY\r\n          .in(this)\r\n          // #endif\r\n          .select('#_root').boundingClientRect().exec(res => res[0] ? resolve(res[0]) : reject(Error('Root label not found')))\r\n      })\r\n    },\r\n\r\n    /**\r\n     * @description 暂停播放媒体\r\n     */\r\n    pauseMedia () {\r\n      for (let i = (this._videos || []).length; i--;) {\r\n        this._videos[i].pause()\r\n      }\r\n      // #ifdef APP-PLUS\r\n      const command = 'for(var e=document.getElementsByTagName(\"video\"),i=e.length;i--;)e[i].pause()'\r\n      // #ifndef APP-PLUS-NVUE\r\n      let page = this.$parent\r\n      while (!page.$scope) page = page.$parent\r\n      page.$scope.$getAppWebview().evalJS(command)\r\n      // #endif\r\n      // #ifdef APP-PLUS-NVUE\r\n      this.$refs.web.evalJs(command)\r\n      // #endif\r\n      // #endif\r\n    },\r\n\r\n    /**\r\n     * @description 设置媒体播放速率\r\n     * @param {Number} rate 播放速率\r\n     */\r\n    setPlaybackRate (rate) {\r\n      this.playbackRate = rate\r\n      for (let i = (this._videos || []).length; i--;) {\r\n        this._videos[i].playbackRate(rate)\r\n      }\r\n      // #ifdef APP-PLUS\r\n      const command = 'for(var e=document.getElementsByTagName(\"video\"),i=e.length;i--;)e[i].playbackRate=' + rate\r\n      // #ifndef APP-PLUS-NVUE\r\n      let page = this.$parent\r\n      while (!page.$scope) page = page.$parent\r\n      page.$scope.$getAppWebview().evalJS(command)\r\n      // #endif\r\n      // #ifdef APP-PLUS-NVUE\r\n      this.$refs.web.evalJs(command)\r\n      // #endif\r\n      // #endif\r\n    },\r\n\r\n    /**\r\n     * @description 设置内容\r\n     * @param {String} content html 内容\r\n     * @param {Boolean} append 是否在尾部追加\r\n     */\r\n    setContent (content, append) {\r\n      if (!append || !this.imgList) {\r\n        this.imgList = []\r\n      }\r\n      const nodes = new Parser(this).parse(content)\r\n      // #ifdef APP-PLUS-NVUE\r\n      if (this._ready) {\r\n        this._set(nodes, append)\r\n      }\r\n      // #endif\r\n      this.$set(this, 'nodes', append ? (this.nodes || []).concat(nodes) : nodes)\r\n\r\n      // #ifndef APP-PLUS-NVUE\r\n      this._videos = []\r\n      this.$nextTick(() => {\r\n        this._hook('onLoad')\r\n        this.$emit('load')\r\n      })\r\n\r\n      if (this.lazyLoad || this.imgList._unloadimgs < this.imgList.length / 2) {\r\n        // 设置懒加载，每 350ms 获取高度，不变则认为加载完毕\r\n        let height = 0\r\n        const callback = rect => {\r\n          if (!rect || !rect.height) rect = {}\r\n          // 350ms 总高度无变化就触发 ready 事件\r\n          if (rect.height === height) {\r\n            this.$emit('ready', rect)\r\n          } else {\r\n            height = rect.height\r\n            setTimeout(() => {\r\n              this.getRect().then(callback).catch(callback)\r\n            }, 350)\r\n          }\r\n        }\r\n        this.getRect().then(callback).catch(callback)\r\n      } else {\r\n        // 未设置懒加载，等待所有图片加载完毕\r\n        if (!this.imgList._unloadimgs) {\r\n          this.getRect().then(rect => {\r\n            this.$emit('ready', rect)\r\n          }).catch(() => {\r\n            this.$emit('ready', {})\r\n          })\r\n        }\r\n      }\r\n      // #endif\r\n    },\r\n\r\n    /**\r\n     * @description 调用插件钩子函数\r\n     */\r\n    _hook (name) {\r\n      for (let i = plugins.length; i--;) {\r\n        if (this.plugins[i][name]) {\r\n          this.plugins[i][name]()\r\n        }\r\n      }\r\n    },\r\n\r\n    // #ifdef APP-PLUS-NVUE\r\n    /**\r\n     * @description 设置内容\r\n     */\r\n    _set (nodes, append) {\r\n      this.$refs.web.evalJs('setContent(' + JSON.stringify(nodes).replace(/%22/g, '') + ',' + JSON.stringify([this.containerStyle.replace(/(?:margin|padding)[^;]+/g, ''), this.errorImg, this.loadingImg, this.pauseVideo, this.scrollTable, this.selectable]) + ',' + append + ')')\r\n    },\r\n\r\n    /**\r\n     * @description 接收到 web-view 消息\r\n     */\r\n    _onMessage (e) {\r\n      const message = e.detail.data[0]\r\n      switch (message.action) {\r\n        // web-view 初始化完毕\r\n        case 'onJSBridgeReady':\r\n          this._ready = true\r\n          if (this.nodes) {\r\n            this._set(this.nodes)\r\n          }\r\n          break\r\n        // 内容 dom 加载完毕\r\n        case 'onLoad':\r\n          this.height = message.height\r\n          this._hook('onLoad')\r\n          this.$emit('load')\r\n          break\r\n        // 所有图片加载完毕\r\n        case 'onReady':\r\n          this.getRect().then(res => {\r\n            this.$emit('ready', res)\r\n          }).catch(() => {\r\n            this.$emit('ready', {})\r\n          })\r\n          break\r\n        // 总高度发生变化\r\n        case 'onHeightChange':\r\n          this.height = message.height\r\n          break\r\n        // 图片点击\r\n        case 'onImgTap':\r\n          this.$emit('imgtap', message.attrs)\r\n          if (this.previewImg) {\r\n            uni.previewImage({\r\n              current: parseInt(message.attrs.i),\r\n              urls: this.imgList\r\n            })\r\n          }\r\n          break\r\n        // 链接点击\r\n        case 'onLinkTap': {\r\n          const href = message.attrs.href\r\n          this.$emit('linktap', message.attrs)\r\n          if (href) {\r\n            // 锚点跳转\r\n            if (href[0] === '#') {\r\n              if (this.useAnchor) {\r\n                dom.scrollToElement(this.$refs.web, {\r\n                  offset: message.offset\r\n                })\r\n              }\r\n            } else if (href.includes('://')) {\r\n              // 打开外链\r\n              if (this.copyLink) {\r\n                plus.runtime.openWeb(href)\r\n              }\r\n            } else {\r\n              uni.navigateTo({\r\n                url: href,\r\n                fail () {\r\n                  uni.switchTab({\r\n                    url: href\r\n                  })\r\n                }\r\n              })\r\n            }\r\n          }\r\n          break\r\n        }\r\n        case 'onPlay':\r\n          this.$emit('play')\r\n          break\r\n        // 获取到锚点的偏移量\r\n        case 'getOffset':\r\n          if (typeof message.offset === 'number') {\r\n            dom.scrollToElement(this.$refs.web, {\r\n              offset: message.offset + this._navigateTo.offset\r\n            })\r\n            this._navigateTo.resolve()\r\n          } else {\r\n            this._navigateTo.reject(Error('Label not found'))\r\n          }\r\n          break\r\n        // 点击\r\n        case 'onClick':\r\n          this.$emit('tap')\r\n          this.$emit('click')\r\n          break\r\n        // 出错\r\n        case 'onError':\r\n          this.$emit('error', {\r\n            source: message.source,\r\n            attrs: message.attrs\r\n          })\r\n      }\r\n    }\r\n    // #endif\r\n  }\r\n}\r\n</script>\r\n\r\n<style>\r\n/* #ifndef APP-PLUS-NVUE */\r\n/* 根节点样式 */\r\n._root {\r\n  padding: 1px 0;\r\n  overflow-x: auto;\r\n  overflow-y: hidden;\r\n  -webkit-overflow-scrolling: touch;\r\n}\r\n\r\n/* 长按复制 */\r\n._select {\r\n  user-select: text;\r\n}\r\n/* #endif */\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-parse/package.json",
    "content": "{\r\n  \"id\": \"uv-parse\",\r\n  \"displayName\": \"uv-parse 富文本解析器 全面兼容vue3+2、app、h5、小程序等多端\",\r\n  \"version\": \"1.0.4\",\r\n  \"description\": \"uv-parse 该组件一般用于富文本解析场景，比如解析文章内容，商品详情，带原生HTML标签的各类字符串等，此组件和uni-app官方的rich-text组件功能有重合之处，但是也有不同的地方。\",\r\n  \"keywords\": [\r\n    \"uv-parse\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"parse\",\r\n    \"富文本\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-parse/readme.md",
    "content": "## Parse 富文本解析器\n\n> **组件名：uv-parse**\n\n该组件一般用于富文本解析场景，比如解析文章内容，商品详情，带原生`HTML`标签的各类字符串等，此组件和`uni-app`官方的`rich-text`组件功能有重合之处，但是也有不同的地方。\n\n该插件只提供富文本的解析，该功能已经足够丰富。如果需要富文本的编辑，可使用`uniapp`官方提供的组件。\n\n# <a href=\"https://www.uvui.cn/components/parse.html\" target=\"_blank\">查看文档</a>\n\n## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui)\n\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\n\n<a href=\"https://ext.dcloud.net.cn/plugin?name=uv-ui\" target=\"_blank\">\n\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\n\n</a>\n\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-parse/static/app-plus/uv-parse/js/handler.js",
    "content": "'use strict'\r\n\r\n// 等待初始化完毕\r\ndocument.addEventListener('UniAppJSBridgeReady', () => {\r\n    document.body.onclick = function () {\r\n        return uni.postMessage({\r\n            data: {\r\n                action: 'onClick'\r\n            }\r\n        })\r\n    }\r\n\r\n    uni.postMessage({\r\n        data: {\r\n            action: 'onJSBridgeReady'\r\n        }\r\n    })\r\n})\r\nlet options\r\nlet medias = []\r\n/**\r\n * @description 获取标签的所有属性\r\n * @param {Element} ele\r\n */\r\n\r\nfunction getAttrs(ele) {\r\n    const attrs = Object.create(null)\r\n\r\n    for (let i = ele.attributes.length; i--;) {\r\n        attrs[ele.attributes[i].name] = ele.attributes[i].value\r\n    }\r\n\r\n    return attrs\r\n}\r\n/**\r\n * @description 图片加载出错\r\n */\r\n\r\nfunction onImgError() {\r\n    if (options[1]) {\r\n        this.src = options[1]\r\n        this.onerror = null\r\n    } // 取消监听点击\r\n\r\n    this.onclick = null\r\n    this.ontouchstart = null\r\n    uni.postMessage({\r\n        data: {\r\n            action: 'onError',\r\n            source: 'img',\r\n            attrs: getAttrs(this)\r\n        }\r\n    })\r\n}\r\n/**\r\n * @description 创建 dom 结构\r\n * @param {object[]} nodes 节点数组\r\n * @param {Element} parent 父节点\r\n * @param {string} namespace 命名空间\r\n */\r\n\r\nfunction createDom(nodes, parent, namespace) {\r\n    const _loop = function _loop(i) {\r\n        const node = nodes[i]\r\n        let ele = void 0\r\n\r\n        if (!node.type || node.type == 'node') {\r\n            let { name } = node // svg 需要设置 namespace\r\n\r\n            if (name == 'svg') namespace = 'http://www.w3.org/2000/svg'\r\n            if (name == 'html' || name == 'body') name = 'div' // 创建标签\r\n\r\n            if (!namespace) ele = document.createElement(name); else ele = document.createElementNS(namespace, name) // 设置属性\r\n\r\n            for (const item in node.attrs) {\r\n                ele.setAttribute(item, node.attrs[item])\r\n            } // 递归创建子节点\r\n\r\n            if (node.children) createDom(node.children, ele, namespace) // 处理图片\r\n\r\n            if (name == 'img') {\r\n                if (!ele.src && ele.getAttribute('data-src')) ele.src = ele.getAttribute('data-src')\r\n\r\n                if (!node.attrs.ignore) {\r\n                    // 监听图片点击事件\r\n                    ele.onclick = function (e) {\r\n                        e.stopPropagation()\r\n                        uni.postMessage({\r\n                            data: {\r\n                                action: 'onImgTap',\r\n                                attrs: getAttrs(this)\r\n                            }\r\n                        })\r\n                    }\r\n                }\r\n\r\n                if (options[2]) {\r\n                    image = new Image()\r\n                    image.src = ele.src\r\n                    ele.src = options[2]\r\n\r\n                    image.onload = function () {\r\n                        ele.src = this.src\r\n                    }\r\n\r\n                    image.onerror = function () {\r\n                        ele.onerror()\r\n                    }\r\n                }\r\n\r\n                ele.onerror = onImgError\r\n            } // 处理链接\r\n            else if (name == 'a') {\r\n                ele.addEventListener('click', function (e) {\r\n                    e.stopPropagation()\r\n                    e.preventDefault() // 阻止默认跳转\r\n\r\n                    const href = this.getAttribute('href')\r\n                    let offset\r\n                    if (href && href[0] == '#') offset = (document.getElementById(href.substr(1)) || {}).offsetTop\r\n                    uni.postMessage({\r\n                        data: {\r\n                            action: 'onLinkTap',\r\n                            attrs: getAttrs(this),\r\n                            offset\r\n                        }\r\n                    })\r\n                }, true)\r\n            } // 处理音视频\r\n            else if (name == 'video' || name == 'audio') {\r\n                medias.push(ele)\r\n\r\n                if (!node.attrs.autoplay) {\r\n                    if (!node.attrs.controls) ele.setAttribute('controls', 'true') // 空白图占位\r\n\r\n                    if (!node.attrs.poster) ele.setAttribute('poster', \"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'/>\")\r\n                }\r\n\r\n                if (options[3]) {\r\n                    ele.onplay = function () {\r\n                        for (let _i = 0; _i < medias.length; _i++) {\r\n                            if (medias[_i] != this) medias[_i].pause()\r\n                        }\r\n                    }\r\n                }\r\n\r\n                ele.onerror = function () {\r\n                    uni.postMessage({\r\n                        data: {\r\n                            action: 'onError',\r\n                            source: name,\r\n                            attrs: getAttrs(this)\r\n                        }\r\n                    })\r\n                }\r\n            } // 处理表格\r\n            else if (name == 'table' && options[4] && !ele.style.cssText.includes('inline')) {\r\n                const div = document.createElement('div')\r\n                div.style.overflow = 'auto'\r\n                div.appendChild(ele)\r\n                ele = div\r\n            } else if (name == 'svg') namespace = void 0\r\n        } else ele = document.createTextNode(node.text.replace(/&amp;/g, '&'))\r\n\r\n        parent.appendChild(ele)\r\n    }\r\n\r\n    for (let i = 0; i < nodes.length; i++) {\r\n        var image\r\n\r\n        _loop(i)\r\n    }\r\n} // 设置 html 内容\r\n\r\nwindow.setContent = function (nodes, opts, append) {\r\n    const ele = document.getElementById('content') // 背景颜色\r\n\r\n    if (opts[0]) document.body.bgColor = opts[0] // 长按复制\r\n\r\n    if (!opts[5]) ele.style.userSelect = 'none'\r\n\r\n    if (!append) {\r\n        ele.innerHTML = '' // 不追加则先清空\r\n\r\n        medias = []\r\n    }\r\n\r\n    options = opts\r\n    const fragment = document.createDocumentFragment()\r\n    createDom(nodes, fragment)\r\n    ele.appendChild(fragment) // 触发事件\r\n\r\n    let height = ele.scrollHeight\r\n    uni.postMessage({\r\n        data: {\r\n            action: 'onLoad',\r\n            height\r\n        }\r\n    })\r\n    clearInterval(window.timer)\r\n    let ready = false\r\n    window.timer = setInterval(() => {\r\n        if (ele.scrollHeight != height) {\r\n            height = ele.scrollHeight\r\n            uni.postMessage({\r\n                data: {\r\n                    action: 'onHeightChange',\r\n                    height\r\n                }\r\n            })\r\n        } else if (!ready) {\r\n            ready = true\r\n            uni.postMessage({\r\n                data: {\r\n                    action: 'onReady'\r\n                }\r\n            })\r\n        }\r\n    }, 350)\r\n} // 回收计时器\r\n\r\nwindow.onunload = function () {\r\n    clearInterval(window.timer)\r\n}\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-parse/static/app-plus/uv-parse/local.html",
    "content": "<head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no\"><style>body,html{width:100%;height:100%;overflow:hidden}body{margin:0}video{width:300px;height:225px}img{max-width:100%;-webkit-touch-callout:none}@keyframes show{0%{opacity:0}100%{opacity:1}}</style></head><body><div id=\"content\"></div><script type=\"text/javascript\" src=\"./js/uni.webview.min.js\"></script><script type=\"text/javascript\" src=\"./js/handler.js\"></script></body>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-pick-color/changelog.md",
    "content": "## 1.0.8（2023-08-14）\n1. 取消scrollTop参数，使用clientY\n## 1.0.7（2023-08-13）\n1. 增加scrollTop参数，设置滚动条的位置。不设置如果页面出现滚动就需要传该值，会出现颜色面板无法进行选颜色的情况。\n## 1.0.6（2023-08-04）\n1. 修复颜色值错误的BUG\n## 1.0.5（2023-08-02）\n1. 更新依赖\n## 1.0.4（2023-07-02）\r\nuv-pick-color  由于弹出层uv-popup的修改，打开和关闭方法更改，详情参考文档：https://www.uvui.cn/components/pickColor.html\r\n## 1.0.3（2023-06-12）\r\n1. 修复在选择颜色时候百度小程序报错的BUG\r\n## 1.0.2（2023-05-27）\r\n1. 兼容非移动h5端不能选择的BUG\r\n## 1.0.1（2023-05-24）\r\n1.  去掉template中存在的this.导致头条小程序编译警告\r\n## 1.0.0（2023-05-23）\r\nuv-pick-color 新增颜色选择器\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-pick-color/components/uv-pick-color/colors.js",
    "content": "/**\r\n * hsb 转 rgb\r\n * @param {Object} hsb 颜色模式  H(hues)表示色相，S(saturation)表示饱和度，B（brightness）表示亮度\r\n */\r\nexport function hsbToRgb(hsb) {\r\n\tlet rgb = {};\r\n\tlet h = hsb.h;\r\n\tlet s = hsb.s * 255 / 100;\r\n\tlet v = hsb.b * 255 / 100;\r\n\tif (s == 0) {\r\n\t\trgb.r = rgb.g = rgb.b = v;\r\n\t} else {\r\n\t\tlet t1 = v;\r\n\t\tlet t2 = ((255 - s) * v) / 255;\r\n\t\tlet t3 = ((t1 - t2) * (h % 60)) / 60;\r\n\t\tif (h == 360) h = 0;\r\n\t\tif (h < 60) {\r\n\t\t\trgb.r = t1;\r\n\t\t\trgb.b = t2;\r\n\t\t\trgb.g = t2 + t3;\r\n\t\t} else if (h < 120) {\r\n\t\t\trgb.g = t1;\r\n\t\t\trgb.b = t2;\r\n\t\t\trgb.r = t1 - t3;\r\n\t\t} else if (h < 180) {\r\n\t\t\trgb.g = t1;\r\n\t\t\trgb.r = t2;\r\n\t\t\trgb.b = t2 + t3;\r\n\t\t} else if (h < 240) {\r\n\t\t\trgb.b = t1;\r\n\t\t\trgb.r = t2;\r\n\t\t\trgb.g = t1 - t3;\r\n\t\t} else if (h < 300) {\r\n\t\t\trgb.b = t1;\r\n\t\t\trgb.g = t2;\r\n\t\t\trgb.r = t2 + t3;\r\n\t\t} else if (h < 360) {\r\n\t\t\trgb.r = t1;\r\n\t\t\trgb.g = t2;\r\n\t\t\trgb.b = t1 - t3;\r\n\t\t} else {\r\n\t\t\trgb.r = 0;\r\n\t\t\trgb.g = 0;\r\n\t\t\trgb.b = 0;\r\n\t\t}\r\n\t}\r\n\treturn {\r\n\t\tr: Math.round(rgb.r),\r\n\t\tg: Math.round(rgb.g),\r\n\t\tb: Math.round(rgb.b)\r\n\t};\r\n}\r\n/**\r\n * rgb转hsb\r\n * @param {Object} rgb 颜色rgb值\r\n */\r\nexport function rgbToHsb(rgb) {\r\n\tlet hsb = {\r\n\t\th: 0,\r\n\t\ts: 0,\r\n\t\tb: 0\r\n\t};\r\n\tlet h = 0,\r\n\t\ts = 0,\r\n\t\tv = 0;\r\n\tlet r = rgb.r,\r\n\t\tg = rgb.g,\r\n\t\tb = rgb.b;\r\n\tlet min = Math.min(rgb.r, rgb.g, rgb.b);\r\n\tlet max = Math.max(rgb.r, rgb.g, rgb.b);\r\n\tv = max / 255;\r\n\tif (max === 0) {\r\n\t\ts = 0;\r\n\t} else {\r\n\t\ts = 1 - (min / max);\r\n\t}\r\n\tif (max === min) {\r\n\t\th = 0; //事实上，max===min的时候，h无论为多少都无所谓\r\n\t} else if (max === r && g >= b) {\r\n\t\th = 60 * ((g - b) / (max - min)) + 0;\r\n\t} else if (max === r && g < b) {\r\n\t\th = 60 * ((g - b) / (max - min)) + 360\r\n\t} else if (max === g) {\r\n\t\th = 60 * ((b - r) / (max - min)) + 120\r\n\t} else if (max === b) {\r\n\t\th = 60 * ((r - g) / (max - min)) + 240\r\n\t}\r\n\thsb.h = parseInt(h);\r\n\thsb.s = parseInt(s * 100);\r\n\thsb.b = parseInt(v * 100);\r\n\treturn hsb;\r\n}\r\n/**\r\n * rgb 转 二进制 hex\r\n * @param {Object} rgb\r\n */\r\nexport function rgbToHex(rgb) {\r\n\tlet hex = [rgb.r.toString(16), rgb.g.toString(16), rgb.b.toString(16)];\r\n\thex.map(function(str, i) {\r\n\t\tif (str.length == 1) {\r\n\t\t\thex[i] = '0' + str;\r\n\t\t}\r\n\t});\r\n\treturn hex.join('');\r\n}\r\n//预制颜色\r\nexport const colorList = [{\r\n\tr: 60,\r\n\tg: 156,\r\n\tb: 255,\r\n\ta: 1\r\n}, {\r\n\tr: 245,\r\n\tg: 108,\r\n\tb: 108,\r\n\ta: 1\r\n}, {\r\n\tr: 249,\r\n\tg: 174,\r\n\tb: 61,\r\n\ta: 1\r\n}, {\r\n\tr: 90,\r\n\tg: 199,\r\n\tb: 37,\r\n\ta: 1\r\n}, {\r\n\tr: 144,\r\n\tg: 147,\r\n\tb: 153,\r\n\ta: 1\r\n}, {\r\n\tr: 48,\r\n\tg: 49,\r\n\tb: 51,\r\n\ta: 1\r\n}, {\r\n\tr: 233,\r\n\tg: 30,\r\n\tb: 99,\r\n\ta: 1\r\n}, {\r\n\tr: 156,\r\n\tg: 39,\r\n\tb: 176,\r\n\ta: 1\r\n}, {\r\n\tr: 103,\r\n\tg: 58,\r\n\tb: 183,\r\n\ta: 1\r\n}, {\r\n\tr: 63,\r\n\tg: 81,\r\n\tb: 181,\r\n\ta: 1\r\n}, {\r\n\tr: 0,\r\n\tg: 188,\r\n\tb: 212,\r\n\ta: 1\r\n}, {\r\n\tr: 0,\r\n\tg: 150,\r\n\tb: 136,\r\n\ta: 1\r\n}, {\r\n\tr: 139,\r\n\tg: 195,\r\n\tb: 74,\r\n\ta: 1\r\n}, {\r\n\tr: 205,\r\n\tg: 220,\r\n\tb: 57,\r\n\ta: 1\r\n}, {\r\n\tr: 255,\r\n\tg: 235,\r\n\tb: 59,\r\n\ta: 1\r\n}, {\r\n\tr: 255,\r\n\tg: 193,\r\n\tb: 7,\r\n\ta: 1\r\n}, {\r\n\tr: 255,\r\n\tg: 152,\r\n\tb: 0,\r\n\ta: 1\r\n}, {\r\n\tr: 255,\r\n\tg: 87,\r\n\tb: 34,\r\n\ta: 1\r\n}, {\r\n\tr: 121,\r\n\tg: 85,\r\n\tb: 72,\r\n\ta: 1\r\n}, {\r\n\tr: 158,\r\n\tg: 158,\r\n\tb: 158,\r\n\ta: 1\r\n}, {\r\n\tr: 0,\r\n\tg: 0,\r\n\tb: 0,\r\n\ta: 0.5\r\n}, {\r\n\tr: 0,\r\n\tg: 0,\r\n\tb: 0,\r\n\ta: 0\r\n}]"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-pick-color/components/uv-pick-color/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 颜色选择器初始颜色\r\n\t\tcolor: {\r\n\t\t\ttype: Object,\r\n\t\t\tdefault: () => {\r\n\t\t\t\treturn { r: 0, g: 0, b: 0, a: 0 }\r\n\t\t\t}\r\n\t\t},\r\n\t\t// 预制颜色\r\n\t\tprefabColor: {\r\n\t\t\ttype: Array,\r\n\t\t\tdefault: () => []\r\n\t\t},\r\n\t\t// 是否允许点击遮罩关闭\r\n\t\tcloseOnClickOverlay: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 顶部标题\r\n\t\ttitle: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 取消按钮的文字\r\n\t\tcancelText: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '取消'\r\n\t\t},\r\n\t\t// 确认按钮的文字\r\n\t\tconfirmText: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '确定'\r\n\t\t},\r\n\t\t// 取消按钮的颜色\r\n\t\tcancelColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#909193'\r\n\t\t},\r\n\t\t// 确认按钮的颜色\r\n\t\tconfirmColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#3c9cff'\r\n\t\t},\r\n\t\t...uni.$uv?.props?.pickColor\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-pick-color/components/uv-pick-color/uv-pick-color.vue",
    "content": "<template>\r\n\t<!-- #ifndef APP-NVUE -->\r\n\t<uv-popup \r\n\t\tref=\"pickerColorPopup\"\r\n\t\tmode=\"bottom\"\r\n\t\t:close-on-click-overlay=\"closeOnClickOverlay\"\r\n\t\t@change=\"popupChange\">\r\n\t\t<view class=\"uv-pick-color\">\r\n\t\t\t<uv-toolbar\r\n\t\t\t\t:show=\"showToolbar\"\r\n\t\t\t\t:cancelColor=\"cancelColor\"\r\n\t\t\t\t:confirmColor=\"confirmColor\"\r\n\t\t\t\t:cancelText=\"cancelText\"\r\n\t\t\t\t:confirmText=\"confirmText\"\r\n\t\t\t\t:title=\"title\"\r\n\t\t\t\t:show-border=\"true\"\r\n\t\t\t\t@cancel=\"cancelHandler\"\r\n\t\t\t\t@confirm=\"confirmHandler\"></uv-toolbar>\r\n\t\t\t<view \r\n\t\t\t\tclass=\"uv-pick-color__box\"\r\n\t\t\t\t:style=\"{\r\n\t\t\t\t\tbackground:`rgb(${bgcolor.r},${bgcolor.g},${bgcolor.b})`\r\n\t\t\t\t}\"\r\n\t\t\t>\r\n\t\t\t\t<!-- #ifdef H5 -->\r\n\t\t\t\t<view\r\n\t\t\t\t\tclass=\"uv-pick-color__box__bg drag-box\"\r\n\t\t\t\t\t@tap.stop.prevent=\"touchstart($event, 0)\"\r\n\t\t\t\t\t@touchstart.stop.prevent=\"touchstart($event, 0)\"\r\n\t\t\t\t\t@touchmove.stop.prevent=\"touchmove($event, 0)\"\r\n\t\t\t\t\t@touchend.stop.prevent=\"touchend($event, 0)\"\r\n\t\t\t\t>\r\n\t\t\t\t<!-- #endif -->\r\n\t\t\t\t<!-- #ifndef H5 -->\r\n\t\t\t\t<view\r\n\t\t\t\t\tclass=\"uv-pick-color__box__bg drag-box\"\r\n\t\t\t\t\t@touchstart.stop.prevent=\"touchstart($event, 0)\"\r\n\t\t\t\t\t@touchmove.stop.prevent=\"touchmove($event, 0)\"\r\n\t\t\t\t\t@touchend.stop.prevent=\"touchend($event, 0)\"\r\n\t\t\t\t>\r\n\t\t\t\t<!-- #endif -->\r\n\t\t\t\t\t<view class=\"uv-pick-color__box__bg-mask\"></view>\r\n\t\t\t\t\t<view \r\n\t\t\t\t\t\tclass=\"uv-pick-color__box__bg-pointer\"\r\n\t\t\t\t\t\t:style=\"[pointerStyle]\"\r\n\t\t\t\t\t>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t\t<view class=\"uv-pick-color__control\">\r\n\t\t\t\t<view class=\"uv-pick-color__control__alpha\">\r\n\t\t\t\t\t<view \r\n\t\t\t\t\t\tclass=\"uv-pick-color__control__alpha--color\"\r\n\t\t\t\t\t\t:style=\"{ background:`rgba(${rgba.r},${rgba.g},${rgba.b},${rgba.a})` }\"\r\n\t\t\t\t\t></view>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view class=\"uv-pick-color__control__item\">\r\n\t\t\t\t\t<!-- #ifdef H5 -->\r\n\t\t\t\t\t<view\r\n\t\t\t\t\t\tclass=\"uv-pick-color__control__item__drag drag-box\"\r\n\t\t\t\t\t\t@tap.stop=\"touchstart($event, 1)\"\r\n\t\t\t\t\t\t@touchstart.stop=\"touchstart($event, 1)\"\r\n\t\t\t\t\t\t@touchmove.stop=\"touchmove($event, 1)\"\r\n\t\t\t\t\t\t@touchend.stop=\"touchend($event, 1)\"\r\n\t\t\t\t\t>\r\n\t\t\t\t\t<!-- #endif -->\r\n\t\t\t\t\t<!-- #ifndef H5 -->\r\n\t\t\t\t\t<view\r\n\t\t\t\t\t\tclass=\"uv-pick-color__control__item__drag drag-box\"\r\n\t\t\t\t\t\t@touchstart.stop=\"touchstart($event, 1)\"\r\n\t\t\t\t\t\t@touchmove.stop=\"touchmove($event, 1)\"\r\n\t\t\t\t\t\t@touchend.stop=\"touchend($event, 1)\"\r\n\t\t\t\t\t>\r\n\t\t\t\t\t<!-- #endif -->\r\n\t\t\t\t\t\t<view class=\"uv-pick-color__control__item__drag--hue\"></view>\r\n\t\t\t\t\t\t<view \r\n\t\t\t\t\t\t\tclass=\"uv-pick-color__control__item__drag--circle\"\r\n\t\t\t\t\t\t\t:style=\"{ \r\n\t\t\t\t\t\t\t\tleft: $uv.getPx(site[1].left - 10,true)\r\n\t\t\t\t\t\t\t}\"\r\n\t\t\t\t\t\t></view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<!-- #ifdef H5 -->\r\n\t\t\t\t\t<view\r\n\t\t\t\t\t\tclass=\"uv-pick-color__control__item__drag drag-box\"\r\n\t\t\t\t\t\t@tap.stop=\"touchstart($event, 2)\"\r\n\t\t\t\t\t\t@touchstart.stop=\"touchstart($event, 2)\"\r\n\t\t\t\t\t\t@touchmove.stop=\"touchmove($event, 2)\"\r\n\t\t\t\t\t\t@touchend.stop=\"touchend($event, 2)\"\r\n\t\t\t\t\t>\r\n\t\t\t\t\t<!-- #endif -->\r\n\t\t\t\t\t<!-- #ifndef H5 -->\r\n\t\t\t\t\t<view\r\n\t\t\t\t\t\tclass=\"uv-pick-color__control__item__drag drag-box\"\r\n\t\t\t\t\t\t@touchstart.stop=\"touchstart($event, 2)\"\r\n\t\t\t\t\t\t@touchmove.stop=\"touchmove($event, 2)\"\r\n\t\t\t\t\t\t@touchend.stop=\"touchend($event, 2)\"\r\n\t\t\t\t\t>\r\n\t\t\t\t\t<!-- #endif -->\r\n\t\t\t\t\t\t<view class=\"uv-pick-color__control__item__drag--alpha\"></view>\r\n\t\t\t\t\t\t<view \r\n\t\t\t\t\t\t\tclass=\"uv-pick-color__control__item__drag--circle\"\r\n\t\t\t\t\t\t\t:style=\"{ \r\n\t\t\t\t\t\t\t\tleft: $uv.getPx(site[2].left - 10,true)\r\n\t\t\t\t\t\t\t}\"\r\n\t\t\t\t\t\t></view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t\t<view class=\"uv-pick-color__result\">\r\n\t\t\t\t<view \r\n\t\t\t\t\tclass=\"uv-pick-color__result__select\"\r\n\t\t\t\t\thover-class=\"uv-hover-class\"\r\n\t\t\t\t\t@click.stop=\"select\"\r\n\t\t\t\t>\r\n\t\t\t\t\t<text class=\"text\">切换</text>\r\n\t\t\t\t\t<text class=\"text\">模式</text>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view class=\"uv-pick-color__result__item\" v-if=\"mode\">\r\n\t\t\t\t\t<view class=\"uv-pick-color__result__item--value uv-border\">\r\n\t\t\t\t\t\t<text>{{hex}}</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"uv-pick-color__result__item--hex\">\r\n\t\t\t\t\t\t<text>HEX</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t\t<template v-else>\r\n\t\t\t\t\t<view class=\"uv-pick-color__result__item\">\r\n\t\t\t\t\t\t<view class=\"uv-pick-color__result__item--value uv-border\">\r\n\t\t\t\t\t\t\t<text>{{rgba.r}}</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<view class=\"uv-pick-color__result__item--rgba\">\r\n\t\t\t\t\t\t\t<text>R</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"uv-pick-color__result__gap\"></view>\r\n\t\t\t\t\t<view class=\"uv-pick-color__result__item\">\r\n\t\t\t\t\t\t<view class=\"uv-pick-color__result__item--value uv-border\">\r\n\t\t\t\t\t\t\t<text>{{rgba.g}}</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<view class=\"uv-pick-color__result__item--rgba\">\r\n\t\t\t\t\t\t\t<text>G</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"uv-pick-color__result__gap\"></view>\r\n\t\t\t\t\t<view class=\"uv-pick-color__result__item\">\r\n\t\t\t\t\t\t<view class=\"uv-pick-color__result__item--value uv-border\">\r\n\t\t\t\t\t\t\t<text>{{rgba.b}}</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<view class=\"uv-pick-color__result__item--rgba\">\r\n\t\t\t\t\t\t\t<text>B</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"uv-pick-color__result__gap\"></view>\r\n\t\t\t\t\t<view class=\"uv-pick-color__result__item\">\r\n\t\t\t\t\t\t<view class=\"uv-pick-color__result__item--value uv-border\">\r\n\t\t\t\t\t\t\t<text>{{rgba.a}}</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<view class=\"uv-pick-color__result__item--rgba\">\r\n\t\t\t\t\t\t\t<text>A</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</template>\r\n\t\t\t</view>\r\n\t\t\t<view class=\"uv-pick-color__prefab\">\r\n\t\t\t\t<view \r\n\t\t\t\t\tclass=\"uv-pick-color__prefab__item\"\r\n\t\t\t\t\tv-for=\"(item,index) in colorList\"\r\n\t\t\t\t\t:key=\"index\"\r\n\t\t\t\t>\r\n\t\t\t\t\t<view \r\n\t\t\t\t\t\tclass=\"uv-pick-color__prefab__item--color\"\r\n\t\t\t\t\t\t:style=\"{ background:`rgba(${item.r},${item.g},${item.b},${item.a})` }\"\r\n\t\t\t\t\t\t@click.stop=\"setColorBySelect(item)\"\r\n\t\t\t\t\t></view>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t</uv-popup>\r\n\t<!-- #endif -->\r\n\t<!-- #ifdef APP-NVUE -->\r\n\t<view>\r\n\t\t<text>nvue暂不支持uv-pick-color组件</text>\r\n\t</view>\r\n\t<!-- #endif -->\r\n</template>\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js';\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js';\r\n\timport { rgbToHsb, hsbToRgb, rgbToHex, colorList } from './colors.js';\r\n\timport props from './props.js';\r\n\texport default {\r\n\t\tname: 'uv-pick-color',\r\n\t\temits: ['confirm', 'cancel', 'close', 'change'],\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tcomputed: {\r\n\t\t\tpointerStyle() {\r\n\t\t\t\tconst style = {};\r\n\t\t\t\tstyle.top = this.$uv.addUnit(this.site[0].top - 8);\r\n\t\t\t\tstyle.left = this.$uv.addUnit(this.site[0].left - 8);\r\n\t\t\t\treturn style;\r\n\t\t\t}\r\n\t\t},\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tshowToolbar: false,\r\n\t\t\t\t// rgba 颜色\r\n\t\t\t\trgba: {\r\n\t\t\t\t\tr: 0,\r\n\t\t\t\t\tg: 0,\r\n\t\t\t\t\tb: 0,\r\n\t\t\t\t\ta: 1\r\n\t\t\t\t},\r\n\t\t\t\t// hsb 颜色\r\n\t\t\t\thsb: {\r\n\t\t\t\t\th: 0,\r\n\t\t\t\t\ts: 0,\r\n\t\t\t\t\tb: 0\r\n\t\t\t\t},\r\n\t\t\t\tsite: [{\r\n\t\t\t\t\ttop: 0,\r\n\t\t\t\t\tleft: 0\r\n\t\t\t\t}, {\r\n\t\t\t\t\tleft: 0\r\n\t\t\t\t}, {\r\n\t\t\t\t\tleft: 0\r\n\t\t\t\t}],\r\n\t\t\t\tindex: 0,\r\n\t\t\t\tbgcolor: {\r\n\t\t\t\t\tr: 255,\r\n\t\t\t\t\tg: 0,\r\n\t\t\t\t\tb: 0,\r\n\t\t\t\t\ta: 1\r\n\t\t\t\t},\r\n\t\t\t\thex: '#000000',\r\n\t\t\t\tmode: true,\r\n\t\t\t\tcolorList: colorList\r\n\t\t\t};\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\tprefabColor(newVal) {\r\n\t\t\t\tthis.colorList = newVal;\r\n\t\t\t}\r\n\t\t},\r\n\t\tcreated() {\r\n\t\t\t// #ifdef APP-NVUE\r\n\t\t\treturn this.$uv.error('nvue暂不支持uv-pick-color组件');\r\n\t\t\t// #endif\r\n\t\t\tthis.rgba = this.color;\r\n\t\t\tif (this.prefabColor.length) this.colorList = this.prefabColor;\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\topen() {\r\n\t\t\t\tthis.$refs.pickerColorPopup.open();\r\n\t\t\t\tthis.showToolbar = true;\r\n\t\t\t\tthis.$nextTick(async () => {\r\n\t\t\t\t\tawait this.$uv.sleep(350);\r\n\t\t\t\t\tthis.getSelectorQuery();\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\tclose() {\r\n\t\t\t\tthis.$refs.pickerColorPopup.close();\r\n\t\t\t},\r\n\t\t\tpopupChange(e) {\r\n\t\t\t\tif(!e.show) this.$emit('close');\r\n\t\t\t},\r\n\t\t\t// 点击工具栏的取消按钮\r\n\t\t\tcancelHandler() {\r\n\t\t\t\tthis.$emit('cancel');\r\n\t\t\t\tthis.close();\r\n\t\t\t},\r\n\t\t\t// 点击工具栏的确定按钮\r\n\t\t\tconfirmHandler() {\r\n\t\t\t\tthis.$emit('confirm', {\r\n\t\t\t\t\trgba: this.rgba,\r\n\t\t\t\t\thex: this.hex\r\n\t\t\t\t})\r\n\t\t\t\tthis.close();\r\n\t\t\t},\r\n\t\t\t// 初始化\r\n\t\t\tinit() {\r\n\t\t\t\t// hsb 颜色\r\n\t\t\t\tthis.hsb = rgbToHsb(this.rgba);\r\n\t\t\t\tthis.setValue(this.rgba);\r\n\t\t\t},\r\n\t\t\tasync getSelectorQuery() {\r\n\t\t\t\tconst data = await this.$uvGetRect('.drag-box',true);\r\n\t\t\t\tthis.position = data;\r\n\t\t\t\tthis.setColorBySelect(this.rgba);\r\n\t\t\t},\r\n\t\t\t// 选择模式\r\n\t\t\tselect() {\r\n\t\t\t\tthis.mode = !this.mode;\r\n\t\t\t},\r\n\t\t\ttouchstart(e, index) {\r\n\t\t\t\tconst { clientX, clientY } = e.touches[0];\r\n\t\t\t\tthis.pageX = clientX;\r\n\t\t\t\tthis.pageY = clientY;\r\n\t\t\t\tthis.setPosition(clientX, clientY, index);\r\n\t\t\t},\r\n\t\t\ttouchmove(e, index) {\r\n\t\t\t\tconst { clientX, clientY } = e.touches[0];\r\n\t\t\t\tthis.moveX = clientX;\r\n\t\t\t\tthis.moveY = clientY;\r\n\t\t\t\tthis.setPosition(clientX, clientY, index);\r\n\t\t\t},\r\n\t\t\ttouchend(e, index) {},\r\n\t\t\t/**\r\n\t\t\t * 设置位置\r\n\t\t\t */\r\n\t\t\tsetPosition(x, y, index) {\r\n\t\t\t\tthis.index = index;\r\n\t\t\t\tconst { top, left, width, height } = this.position[index];\r\n\t\t\t\t// 设置最大最小值\r\n\t\t\t\tthis.site[index].left = Math.max(0, Math.min(parseInt(x - left), width));\r\n\t\t\t\tif (index === 0) {\r\n\t\t\t\t\tthis.site[index].top = Math.max(0, Math.min(parseInt(y - top), height));\r\n\t\t\t\t\t// 设置颜色\r\n\t\t\t\t\tthis.hsb.s = parseInt((100 * this.site[index].left) / width);\r\n\t\t\t\t\tthis.hsb.b = parseInt(100 - (100 * this.site[index].top) / height);\r\n\t\t\t\t\tthis.setColor();\r\n\t\t\t\t\tthis.setValue(this.rgba);\r\n\t\t\t\t} else {\r\n\t\t\t\t\tthis.setControl(index, this.site[index].left);\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t/**\r\n\t\t\t * 设置 rgb 颜色\r\n\t\t\t */\r\n\t\t\tsetColor() {\r\n\t\t\t\tconst rgb = hsbToRgb(this.hsb);\r\n\t\t\t\tthis.rgba.r = rgb.r;\r\n\t\t\t\tthis.rgba.g = rgb.g;\r\n\t\t\t\tthis.rgba.b = rgb.b;\r\n\t\t\t},\r\n\t\t\t/**\r\n\t\t\t * 设置二进制颜色\r\n\t\t\t * @param {Object} rgb\r\n\t\t\t */\r\n\t\t\tsetValue(rgb) {\r\n\t\t\t\tthis.hex = `#${(rgbToHex(rgb))}`;\r\n\t\t\t},\r\n\t\t\tsetControl(index, x) {\r\n\t\t\t\tconst {\r\n\t\t\t\t\ttop,\r\n\t\t\t\t\tleft,\r\n\t\t\t\t\twidth,\r\n\t\t\t\t\theight\r\n\t\t\t\t} = this.position[index];\r\n\r\n\t\t\t\tif (index === 1) {\r\n\t\t\t\t\tthis.hsb.h = parseInt((360 * x) / width);\r\n\t\t\t\t\tthis.bgcolor = hsbToRgb({\r\n\t\t\t\t\t\th: this.hsb.h,\r\n\t\t\t\t\t\ts: 100,\r\n\t\t\t\t\t\tb: 100\r\n\t\t\t\t\t});\r\n\t\t\t\t\tthis.setColor()\r\n\t\t\t\t} else {\r\n\t\t\t\t\tthis.rgba.a = +(x / width).toFixed(1);\r\n\t\t\t\t}\r\n\t\t\t\tthis.setValue(this.rgba);\r\n\t\t\t},\r\n\t\t\tsetColorBySelect(getrgb) {\r\n\t\t\t\tconst { r, g, b, a } = getrgb;\r\n\t\t\t\tlet rgb = {};\r\n\t\t\t\trgb = {\r\n\t\t\t\t\tr: r ? parseInt(r) : 0,\r\n\t\t\t\t\tg: g ? parseInt(g) : 0,\r\n\t\t\t\t\tb: b ? parseInt(b) : 0,\r\n\t\t\t\t\ta: a ? a : 0\r\n\t\t\t\t};\r\n\t\t\t\tthis.rgba = rgb;\r\n\t\t\t\tthis.hsb = rgbToHsb(rgb);\r\n\t\t\t\tthis.changeViewByHsb();\r\n\t\t\t},\r\n\t\t\tchangeViewByHsb() {\r\n\t\t\t\tconst [a, b, c] = this.position;\r\n\t\t\t\tthis.site[0].left = parseInt(this.hsb.s * a.width / 100);\r\n\t\t\t\tthis.site[0].top = parseInt((100 - this.hsb.b) * a.height / 100);\r\n\t\t\t\tthis.setColor(this.hsb.h);\r\n\t\t\t\tthis.setValue(this.rgba);\r\n\t\t\t\tthis.bgcolor = hsbToRgb({\r\n\t\t\t\t\th: this.hsb.h,\r\n\t\t\t\t\ts: 100,\r\n\t\t\t\t\tb: 100\r\n\t\t\t\t});\r\n\t\t\t\tthis.site[1].left = this.hsb.h / 360 * b.width;\r\n\t\t\t\tthis.site[2].left = this.rgba.a * c.width;\r\n\t\t\t}\r\n\t\t}\r\n\t};\r\n</script>\r\n<style scoped lang=\"scss\">\r\n\t$show-border: 1;\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t$height: 400rpx;\r\n\t/* #ifndef APP-NVUE */\r\n\t.uv-pick-color {\r\n\t\t&__box {\r\n\t\t\tposition: relative;\r\n\t\t\theight: $height;\r\n\t\t\tbackground: rgb(255, 0, 0);\r\n\t\t\tmargin: 20rpx;\r\n\t\t\t&__bg {\r\n\t\t\t\tposition: absolute;\r\n\t\t\t\ttop: 0;\r\n\t\t\t\tleft: 0;\r\n\t\t\t\tright: 0;\r\n\t\t\t\tbottom: 0;\r\n\t\t\t\tbackground: linear-gradient(to right, #fff, rgba(255, 255, 255, 0));\r\n\t\t\t\t&-mask {\r\n\t\t\t\t\tposition: absolute;\r\n\t\t\t\t\ttop: 0;\r\n\t\t\t\t\tleft: 0;\r\n\t\t\t\t\tright: 0;\r\n\t\t\t\t\tbottom: 0;\r\n\t\t\t\t\theight: $height;\r\n\t\t\t\t\tbackground: linear-gradient(to top, #000, rgba(0, 0, 0, 0));\r\n\t\t\t\t}\r\n\t\t\t\t&-pointer {\r\n\t\t\t\t\tposition: absolute;\r\n\t\t\t\t\ttop: -8px;\r\n\t\t\t\t\tleft: -8px;\r\n\t\t\t\t\tz-index: 2;\r\n\t\t\t\t\twidth: 16px;\r\n\t\t\t\t\theight: 16px;\r\n\t\t\t\t\tborder: 1px #fff solid;\r\n\t\t\t\t\tborder-radius: 8px;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t&__control {\r\n\t\t\t@include flex;\r\n\t\t\tpadding: 10rpx 20rpx;\r\n\t\t\t&__alpha {\r\n\t\t\t\twidth: 100rpx;\r\n\t\t\t\theight: 100rpx;\r\n\t\t\t\tborder-radius: 50rpx;\r\n\t\t\t\tbackground-color: #fff;\r\n\t\t\t\tbackground-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee),linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee);\r\n\t\t\t\tbackground-size: 36rpx 36rpx;\r\n\t\t\t\tbackground-position: 0 0, 18rpx 18rpx;\r\n\t\t\t\tborder: 1px #eee solid;\r\n\t\t\t\toverflow: hidden;\r\n\t\t\t\t&--color {\r\n\t\t\t\t\twidth: 100%;\r\n\t\t\t\t\theight: 100%;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t&__item {\r\n\t\t\t\tflex: 1;\r\n\t\t\t\t@include flex(column);\r\n\t\t\t\tjustify-content: space-between;\r\n\t\t\t\theight: 100rpx;\r\n\t\t\t\tpadding: 6rpx 0 6rpx 30rpx;\r\n\t\t\t}\r\n\t\t\t&__item__drag {\r\n\t\t\t\tposition: relative;\r\n\t\t\t\theight: 16px;\r\n\t\t\t\tbackground-color: #fff;\r\n\t\t\t\tbackground-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee),\r\n\t\t\t\tlinear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee);\r\n\t\t\t\tbackground-size: 32rpx 32rpx;\r\n\t\t\t\tbackground-position: 0 0, 16rpx 16rpx;\r\n\t\t\t\t&--hue {\r\n\t\t\t\t\twidth: 100%;\r\n\t\t\t\t\theight: 100%;\r\n\t\t\t\t\tbackground: linear-gradient(to right, #f00 0%, #ff0 17%, #0f0 33%, #0ff 50%, #00f 67%, #f0f 83%, #f00 100%);\r\n\t\t\t\t}\r\n\t\t\t\t&--alpha {\r\n\t\t\t\t\twidth: 100%;\r\n\t\t\t\t\theight: 100%;\r\n\t\t\t\t\tbackground: linear-gradient(to right, rgba(0, 0, 0, 0) 0%, rgb(0, 0, 0));\r\n\t\t\t\t}\r\n\t\t\t\t&--circle {\r\n\t\t\t\t\tposition: absolute;\r\n\t\t\t\t\ttop: -2px;\r\n\t\t\t\t\twidth: 20px;\r\n\t\t\t\t\theight: 20px;\r\n\t\t\t\t\tbox-sizing: border-box;\r\n\t\t\t\t\tborder-radius: 10px;\r\n\t\t\t\t\tbackground: #fff;\r\n\t\t\t\t\tbox-shadow: 0 0 2px 1px rgba(0, 0, 0, 0.1);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t&__result {\r\n\t\t\t@include flex;\r\n\t\t\tpadding: 20rpx;\r\n\t\t\ttext-align: center;\r\n\t\t\t&__select {\r\n\t\t\t\t@include flex(column);\r\n\t\t\t\tjustify-content: center;\r\n\t\t\t\twidth: 100rpx;\r\n\t\t\t\theight: 100rpx;\r\n\t\t\t\tmargin-right: 10px;\r\n\t\t\t\tborder-radius: 10rpx;\r\n\t\t\t\tbox-shadow: 1px 1px 2px 1px rgba(0, 0, 0, 0.1);\r\n\t\t\t\tfont-size: 14px;\r\n\t\t\t\tcolor: #999;\r\n\t\t\t}\r\n\t\t\t&__item {\r\n\t\t\t\tflex: 1;\r\n\t\t\t\theight: 100rpx;\r\n\t\t\t\t&--value {\r\n\t\t\t\t\theight: 50rpx;\r\n\t\t\t\t\tline-height: 50rpx;\r\n\t\t\t\t\tborder-radius: 4rpx;\r\n\t\t\t\t\tfont-size: 28rpx;\r\n\t\t\t\t\tcolor: #999;\r\n\t\t\t\t}\r\n\t\t\t\t&--hex,\r\n\t\t\t\t&--rgba {\r\n\t\t\t\t\theight: 50rpx;\r\n\t\t\t\t\tline-height: 50rpx;\r\n\t\t\t\t\tfont-size: 30rpx;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t&__gap {\r\n\t\t\t\twidth: 10px;\r\n\t\t\t\theight: 10px;\r\n\t\t\t}\r\n\t\t}\r\n\t\t&__prefab {\r\n\t\t\t@include flex;\r\n\t\t\tmargin: 0 -8rpx;\r\n\t\t\tpadding: 0 20rpx 20rpx;\r\n\t\t\tflex-wrap: wrap;\r\n\t\t\t&__item {\r\n\t\t\t\twidth: 50rpx;\r\n\t\t\t\theight: 50rpx;\r\n\t\t\t\tmargin: 8rpx;\r\n\t\t\t\tborder-radius: 6rpx;\r\n\t\t\t\tbackground-color: #fff;\r\n\t\t\t\tbackground-image: linear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee),\r\n\t\t\t\t\tlinear-gradient(45deg, #eee 25%, transparent 25%, transparent 75%, #eee 75%, #eee);\r\n\t\t\t\tbackground-size: 36rpx 36rpx;\r\n\t\t\t\tbackground-position: 0 0, 18rpx 18rpx;\r\n\t\t\t\tborder: 1px #eee solid;\r\n\t\t\t\t&--color {\r\n\t\t\t\t\twidth: 100%;\r\n\t\t\t\t\theight: 100%;\r\n\t\t\t\t\tborder-radius: 6rpx;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t/* #endif */\r\n</style>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-pick-color/package.json",
    "content": "{\r\n  \"id\": \"uv-pick-color\",\r\n  \"displayName\": \"uv-pick-color 颜色选择器 全面兼容vue3+2、app、h5、小程序等多端\",\r\n  \"version\": \"1.0.8\",\r\n  \"description\": \"该组件为颜色选择器，支持预制颜色，初始化颜色，简单配置，开箱即用\",\r\n  \"keywords\": [\r\n    \"uv-pick-color\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"颜色\",\r\n    \"color\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-popup\",\r\n\t\t\t\"uv-toolbar\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-pick-color/readme.md",
    "content": "## PickColor 颜色选择器\r\n\r\n> **组件名：uv-pick-color**\r\n\r\n该组件为颜色选择器，支持预制颜色，初始化颜色，简单配置，开箱即用。\r\n\r\n# <a href=\"https://www.uvui.cn/components/pickColor.html\" target=\"_blank\">查看文档</a>\r\n\r\n## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>（请不要 下载插件ZIP）</small>\n\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\n\n<a href=\"https://ext.dcloud.net.cn/plugin?name=uv-ui\" target=\"_blank\">\n\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\n\n</a>\n\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-picker/changelog.md",
    "content": "## 1.0.11（2023-10-11）\n1. 将immediate-change默认值改为true，该值在于change回调的及时性，微信小程序生效\n## 1.0.10（2023-08-25）\n1. 增加round属性设置弹窗圆角，默认为0\n## 1.0.9（2023-08-24）\n1. 修复cli项目不返回值的问题\n## 1.0.8（2023-08-04）\r\n1. 优化\r\n## 1.0.7（2023-08-02）\r\n1. 改组件中删除uv-toolbar组件，请单独下载uv-toolbar组件\r\n## 1.0.6（2023-07-02）\r\nuv-picker  由于弹出层uv-popup的修改，打开和关闭方法更改，详情参考文档：https://www.uvui.cn/components/picker.html\r\n## 1.0.5（2023-06-26）\r\n1. 增加color参数\r\n2. 增加activeColor参数\r\n## 1.0.4（2023-06-15）\r\n1. 修改支付宝报错的BUG\r\n## 1.0.3（2023-06-12）\r\n1. setColumnValues的使用统一化，避免某些平台报错\r\n2. 取消change回调回传的组件实例，直接统一通过ref的方式调取setColumnValues方法\r\n## 1.0.2（2023-05-23）\r\n1. uv-toolbar组件新增下边框属性 \r\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-picker 选择器\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-picker/components/uv-picker/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 是否展示顶部的操作栏\r\n\t\tshowToolbar: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 顶部标题\r\n\t\ttitle: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 弹窗圆角\r\n\t\tround: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 对象数组，设置每一列的数据\r\n\t\tcolumns: {\r\n\t\t\ttype: Array,\r\n\t\t\tdefault: () => []\r\n\t\t},\r\n\t\t// 是否显示加载中状态\r\n\t\tloading: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 各列中，单个选项的高度\r\n\t\titemHeight: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 44\r\n\t\t},\r\n\t\t// 取消按钮的文字\r\n\t\tcancelText: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '取消'\r\n\t\t},\r\n\t\t// 确认按钮的文字\r\n\t\tconfirmText: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '确定'\r\n\t\t},\r\n\t\t// 取消按钮的颜色\r\n\t\tcancelColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#909193'\r\n\t\t},\r\n\t\t// 确认按钮的颜色\r\n\t\tconfirmColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#3c9cff'\r\n\t\t},\r\n\t\t// 文字颜色\r\n\t\tcolor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 选中文字的颜色\r\n\t\tactiveColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 每列中可见选项的数量\r\n\t\tvisibleItemCount: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 5\r\n\t\t},\r\n\t\t// 选项对象中，需要展示的属性键名\r\n\t\tkeyName: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'text'\r\n\t\t},\r\n\t\t// 是否允许点击遮罩关闭选择器\r\n\t\tcloseOnClickOverlay: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 是否允许点击确认关闭选择器\r\n\t\tcloseOnClickConfirm: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 各列的默认索引\r\n\t\tdefaultIndex: {\r\n\t\t\ttype: Array,\r\n\t\t\tdefault: () => [],\r\n\t\t},\r\n\t\t// 是否在手指松开时立即触发 change 事件。若不开启则会在滚动动画结束后触发 change 事件，只在微信2.21.1及以上有效\r\n\t\timmediateChange: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t...uni.$uv?.props?.picker\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-picker/components/uv-picker/uv-picker.vue",
    "content": "<template>\r\n\t<uv-popup\r\n\t\tref=\"pickerPopup\"\r\n\t\tmode=\"bottom\"\r\n\t\t:round=\"round\"\r\n\t\t:close-on-click-overlay=\"closeOnClickOverlay\"\r\n\t\t@change=\"popupChange\"\r\n\t>\r\n\t\t<view class=\"uv-picker\">\r\n\t\t\t<uv-toolbar\r\n\t\t\t\tv-if=\"showToolbar\"\r\n\t\t\t\t:cancelColor=\"cancelColor\"\r\n\t\t\t\t:confirmColor=\"confirmColor\"\r\n\t\t\t\t:cancelText=\"cancelText\"\r\n\t\t\t\t:confirmText=\"confirmText\"\r\n\t\t\t\t:title=\"title\"\r\n\t\t\t\t@cancel=\"cancel\"\r\n\t\t\t\t@confirm=\"confirm\"\r\n\t\t\t></uv-toolbar>\r\n\t\t\t<picker-view\r\n\t\t\t\tclass=\"uv-picker__view\"\r\n\t\t\t\t:indicatorStyle=\"`height: ${$uv.addUnit(itemHeight)}`\"\r\n\t\t\t\t:value=\"innerIndex\"\r\n\t\t\t\t:immediateChange=\"immediateChange\"\r\n\t\t\t\t:style=\"{\r\n\t\t\t\t\theight: `${$uv.addUnit(visibleItemCount * itemHeight)}`\r\n\t\t\t\t}\"\r\n\t\t\t\t@change=\"changeHandler\"\r\n\t\t\t>\r\n\t\t\t\t<picker-view-column\r\n\t\t\t\t\tv-for=\"(item, index) in innerColumns\"\r\n\t\t\t\t\t:key=\"index\"\r\n\t\t\t\t\tclass=\"uv-picker__view__column\"\r\n\t\t\t\t>\r\n\t\t\t\t\t<text\r\n\t\t\t\t\t\tv-if=\"$uv.test.array(item)\"\r\n\t\t\t\t\t\tclass=\"uv-picker__view__column__item uv-line-1\"\r\n\t\t\t\t\t\tv-for=\"(item1, index1) in item\"\r\n\t\t\t\t\t\t:key=\"index1\"\r\n\t\t\t\t\t\t:style=\"[{\r\n\t\t\t\t\t\t\t\theight: $uv.addUnit(itemHeight),\r\n\t\t\t\t\t\t\t\tlineHeight: $uv.addUnit(itemHeight),\r\n\t\t\t\t\t\t\t\tfontWeight: index1 === innerIndex[index] ? 'bold' : 'normal'\r\n\t\t\t\t\t\t\t},textStyle(index,index1)]\"\r\n\t\t\t\t\t>{{ getItemText(item1) }}</text>\r\n\t\t\t\t</picker-view-column>\r\n\t\t\t</picker-view>\r\n\t\t\t<view\r\n\t\t\t\tv-if=\"loading\"\r\n\t\t\t\tclass=\"uv-picker--loading\"\r\n\t\t\t>\r\n\t\t\t\t<uv-loading-icon mode=\"circle\"></uv-loading-icon>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t</uv-popup>\r\n</template>\r\n\r\n<script>\r\n/**\r\n * uv-picker\r\n * @description 选择器\r\n * @property {Boolean}\t\t\tshowToolbar\t\t\t是否显示顶部的操作栏（默认 true ）\r\n * @property {String}\t\t\ttitle\t\t\t\t顶部标题\r\n * @property {Array}\t\t\tcolumns\t\t\t\t对象数组，设置每一列的数据\r\n * @property {Boolean}\t\t\tloading\t\t\t\t是否显示加载中状态（默认 false ）\r\n * @property {String | Number}\titemHeight\t\t\t各列中，单个选项的高度（默认 44 ）\r\n * @property {String}\t\t\tcancelText\t\t\t取消按钮的文字（默认 '取消' ）\r\n * @property {String}\t\t\tconfirmText\t\t\t确认按钮的文字（默认 '确定' ）\r\n * @property {String}\t\t\tcancelColor\t\t\t取消按钮的颜色（默认 '#909193' ）\r\n * @property {String}\t\t\tconfirmColor\t\t确认按钮的颜色（默认 '#3c9cff' ）\r\n * @property {String}\t\t\tcolor\t\t文字颜色（默认 '' ）\r\n * @property {String}\t\t\tactiveColor\t\t选中文字的颜色（默认 '' ）\r\n * @property {String | Number}\tvisibleItemCount\t每列中可见选项的数量（默认 5 ）\r\n * @property {String}\t\t\tkeyName\t\t\t\t选项对象中，需要展示的属性键名（默认 'text' ）\r\n * @property {Boolean}\t\t\tcloseOnClickOverlay\t是否允许点击遮罩关闭选择器（默认 false ）\r\n * @property {Array}\t\t\tdefaultIndex\t\t各列的默认索引\r\n * @property {Boolean}\t\t\timmediateChange\t\t是否在手指松开时立即触发change事件（默认 false ）\r\n * @event {Function} close\t\t关闭选择器时触发\r\n * @event {Function} cancel\t\t点击取消按钮触发\r\n * @event {Function} change\t\t当选择值变化时触发\r\n * @event {Function} confirm\t点击确定按钮，返回当前选择的值\r\n */\r\nimport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\nimport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\nimport props from './props.js';\r\nexport default {\r\n\tname: 'uv-picker',\r\n\temits: ['confirm','cancel','close','change'],\r\n\tmixins: [mpMixin, mixin, props],\r\n\tcomputed: {\r\n\t\t// 为了解决支付宝不生效\r\n\t\ttextStyle(){\r\n\t\t\treturn (index,index1) => {\r\n\t\t\t\tconst style = {};\r\n\t\t\t\t// #ifndef APP-NVUE \r\n\t\t\t\tstyle.display = 'block';\r\n\t\t\t\t// #endif\r\n\t\t\t\tif(this.color) {\r\n\t\t\t\t\tstyle.color = this.color;\r\n\t\t\t\t}\r\n\t\t\t\tif(this.activeColor && index1 === this.innerIndex[index]) {\r\n\t\t\t\t\tstyle.color = this.activeColor;\r\n\t\t\t\t}\r\n\t\t\t\treturn style;\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\tdata() {\r\n\t\treturn {\r\n\t\t\t// 上一次选择的列索引\r\n\t\t\tlastIndex: [],\r\n\t\t\t// 索引值 ，对应picker-view的value\r\n\t\t\tinnerIndex: [],\r\n\t\t\t// 各列的值\r\n\t\t\tinnerColumns: [],\r\n\t\t\t// 上一次的变化列索引\r\n\t\t\tcolumnIndex: 0,\r\n\t\t}\r\n\t},\r\n\twatch: {\r\n\t\t// 监听默认索引的变化，重新设置对应的值\r\n\t\tdefaultIndex: {\r\n\t\t\timmediate: true,\r\n\t\t\thandler(n) {\r\n\t\t\t\tthis.setIndexs(n, true)\r\n\t\t\t}\r\n\t\t},\r\n\t\t// 监听columns参数的变化\r\n\t\tcolumns: {\r\n\t\t\timmediate: true,\r\n\t\t\thandler(n) {\r\n\t\t\t\tthis.setColumns(n)\r\n\t\t\t}\r\n\t\t},\r\n\t},\r\n\tmethods: {\r\n\t\topen() {\r\n\t\t\tthis.$refs.pickerPopup.open();\r\n\t\t},\r\n\t\tclose() {\r\n\t\t\tthis.$refs.pickerPopup.close();\r\n\t\t},\r\n\t\tpopupChange(e) {\r\n\t\t\tif(!e.show) this.$emit('close');\r\n\t\t},\r\n\t\t// 获取item需要显示的文字，判别为对象还是文本\r\n\t\tgetItemText(item) {\r\n\t\t\tif (this.$uv.test.object(item)) {\r\n\t\t\t\treturn item[this.keyName]\r\n\t\t\t} else {\r\n\t\t\t\treturn item\r\n\t\t\t}\r\n\t\t},\r\n\t\t// 点击工具栏的取消按钮\r\n\t\tcancel() {\r\n\t\t\tthis.$emit('cancel');\r\n\t\t\tthis.close();\r\n\t\t},\r\n\t\t// 点击工具栏的确定按钮\r\n\t\tconfirm() {\r\n\t\t\t// 在这里使用deepClone拷贝后，vue3会自动转换成原始对象，这样处理是因为cli项目可能出现不返回值的情况\r\n\t\t\tthis.$emit('confirm', this.$uv.deepClone({\r\n\t\t\t\tindexs: this.innerIndex,\r\n\t\t\t\tvalue: this.innerColumns.map((item, index) => item[this.innerIndex[index]]),\r\n\t\t\t\tvalues: this.innerColumns\r\n\t\t\t}));\r\n\t\t\tif(this.closeOnClickConfirm) {\r\n\t\t\t\tthis.close();\r\n\t\t\t}\r\n\t\t},\r\n\t\t// 选择器某一列的数据发生变化时触发\r\n\t\tchangeHandler(e) {\r\n\t\t\tconst {\r\n\t\t\t\tvalue\r\n\t\t\t} = e.detail\r\n\t\t\tlet index = 0,\r\n\t\t\t\tcolumnIndex = 0\r\n\t\t\t// 通过对比前后两次的列索引，得出当前变化的是哪一列\r\n\t\t\tfor (let i = 0; i < value.length; i++) {\r\n\t\t\t\tlet item = value[i]\r\n\t\t\t\tif (item !== (this.lastIndex[i] || 0)) { // 把undefined转为合法假值0\r\n\t\t\t\t\t// 设置columnIndex为当前变化列的索引\r\n\t\t\t\t\tcolumnIndex = i\r\n\t\t\t\t\t// index则为变化列中的变化项的索引\r\n\t\t\t\t\tindex = item\r\n\t\t\t\t\tbreak // 终止循环，即使少一次循环，也是性能的提升\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tthis.columnIndex = columnIndex\r\n\t\t\tconst values = this.innerColumns\r\n\t\t\t// 将当前的各项变化索引，设置为\"上一次\"的索引变化值\r\n\t\t\tthis.setLastIndex(value)\r\n\t\t\tthis.setIndexs(value)\r\n\r\n\t\t\tthis.$emit('change', {\r\n\t\t\t\tvalue: this.innerColumns.map((item, index) => item[value[index]]),\r\n\t\t\t\tindex,\r\n\t\t\t\tindexs: value,\r\n\t\t\t\t// values为当前变化列的数组内容\r\n\t\t\t\tvalues,\r\n\t\t\t\tcolumnIndex\r\n\t\t\t})\r\n\t\t},\r\n\t\t// 设置index索引，此方法可被外部调用设置\r\n\t\tsetIndexs(index, setLastIndex) {\r\n\t\t\tthis.innerIndex = this.$uv.deepClone(index)\r\n\t\t\tif (setLastIndex) {\r\n\t\t\t\tthis.setLastIndex(index)\r\n\t\t\t}\r\n\t\t},\r\n\t\t// 记录上一次的各列索引位置\r\n\t\tsetLastIndex(index) {\r\n\t\t\t// 当能进入此方法，意味着当前设置的各列默认索引，即为“上一次”的选中值，需要记录，是因为changeHandler中\r\n\t\t\t// 需要拿前后的变化值进行对比，得出当前发生改变的是哪一列\r\n\t\t\tthis.lastIndex = this.$uv.deepClone(index)\r\n\t\t},\r\n\t\t// 设置对应列选项的所有值\r\n\t\tsetColumnValues(columnIndex, values) {\r\n\t\t\t// 替换innerColumns数组中columnIndex索引的值为values，使用的是数组的splice方法\r\n\t\t\tthis.innerColumns.splice(columnIndex, 1, values)\r\n\t\t\t// 拷贝一份原有的innerIndex做临时变量，将大于当前变化列的所有的列的默认索引设置为0\r\n\t\t\tlet tmpIndex = this.$uv.deepClone(this.innerIndex)\r\n\t\t\tfor (let i = 0; i < this.innerColumns.length; i++) {\r\n\t\t\t\tif (i > this.columnIndex) {\r\n\t\t\t\t\ttmpIndex[i] = 0\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t// 一次性赋值，不能单个修改，否则无效\r\n\t\t\tthis.setIndexs(tmpIndex)\r\n\t\t},\r\n\t\t// 获取对应列的所有选项\r\n\t\tgetColumnValues(columnIndex) {\r\n\t\t\t// 进行同步阻塞，因为外部得到change事件之后，可能需要执行setColumnValues更新列的值\r\n\t\t\t// 索引如果在外部change的回调中调用getColumnValues的话，可能无法得到变更后的列值，这里进行一定延时，保证值的准确性\r\n\t\t\t(async () => {\r\n\t\t\t\tawait this.$uv.sleep()\r\n\t\t\t})()\r\n\t\t\treturn this.innerColumns[columnIndex]\r\n\t\t},\r\n\t\t// 设置整体各列的columns的值\r\n\t\tsetColumns(columns) {\r\n\t\t\tthis.innerColumns = this.$uv.deepClone(columns)\r\n\t\t\t// 如果在设置各列数据时，没有被设置默认的各列索引defaultIndex，那么用0去填充它，数组长度为列的数量\r\n\t\t\tif (this.innerIndex.length === 0) {\r\n\t\t\t\tthis.innerIndex = new Array(columns.length).fill(0)\r\n\t\t\t}\r\n\t\t},\r\n\t\t// 获取各列选中值对应的索引\r\n\t\tgetIndexs() {\r\n\t\t\treturn this.innerIndex\r\n\t\t},\r\n\t\t// 获取各列选中的值\r\n\t\tgetValues() {\r\n\t\t\t// 进行同步阻塞，因为外部得到change事件之后，可能需要执行setColumnValues更新列的值\r\n\t\t\t// 索引如果在外部change的回调中调用getValues的话，可能无法得到变更后的列值，这里进行一定延时，保证值的准确性\r\n\t\t\t(async () => {\r\n\t\t\t\tawait this.$uv.sleep()\r\n\t\t\t})()\r\n\t\t\treturn this.innerColumns.map((item, index) => item[this.innerIndex[index]])\r\n\t\t}\r\n\t},\r\n}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t$show-lines: 1;\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t.uv-picker {\r\n\t\tposition: relative;\r\n\r\n\t\t&__view {\r\n\r\n\t\t\t&__column {\r\n\t\t\t\t@include flex;\r\n\t\t\t\tflex: 1;\r\n\t\t\t\tjustify-content: center;\r\n\r\n\t\t\t\t&__item {\r\n\t\t\t\t\t@include flex;\r\n\t\t\t\t\tjustify-content: center;\r\n\t\t\t\t\talign-items: center;\r\n\t\t\t\t\tfont-size: 16px;\r\n\t\t\t\t\ttext-align: center;\r\n\t\t\t\t\t/* #ifndef APP-NVUE */\r\n\t\t\t\t\tdisplay: block;\r\n\t\t\t\t\t/* #endif */\r\n\t\t\t\t\tcolor: $uv-main-color;\r\n\r\n\t\t\t\t\t&--disabled {\r\n\t\t\t\t\t\t/* #ifndef APP-NVUE */\r\n\t\t\t\t\t\tcursor: not-allowed;\r\n\t\t\t\t\t\t/* #endif */\r\n\t\t\t\t\t\topacity: 0.35;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t&--loading {\r\n\t\t\tposition: absolute;\r\n\t\t\ttop: 0;\r\n\t\t\tright: 0;\r\n\t\t\tleft: 0;\r\n\t\t\tbottom: 0;\r\n\t\t\t@include flex;\r\n\t\t\tjustify-content: center;\r\n\t\t\talign-items: center;\r\n\t\t\tbackground-color: rgba(255, 255, 255, 0.87);\r\n\t\t\tz-index: 1000;\r\n\t\t}\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-picker/package.json",
    "content": "{\r\n  \"id\": \"uv-picker\",\r\n  \"displayName\": \"uv-picker 选择器 全面兼容vue3+2、app、h5、小程序等多端\",\r\n  \"version\": \"1.0.11\",\r\n  \"description\": \"uv-picker 此选择器用于单列，多列，多列联动的选择场景...\",\r\n  \"keywords\": [\r\n    \"uv-picker\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"picker\",\r\n    \"联动选择\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-popup\",\r\n\t\t\t\"uv-loading-icon\",\r\n\t\t\t\"uv-toolbar\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-picker/readme.md",
    "content": "## Picker 选择器\r\n\r\n> **组件名：uv-picker**\r\n\r\n此选择器用于单列，多列，多列联动的选择场景。\r\n\r\n`uv-datetime-picker`等组件也用到了该组件，功能完善，需要特别注意的是`columns`参数的形式是数组嵌套。\r\n\r\n# <a href=\"https://www.uvui.cn/components/picker.html\" target=\"_blank\">查看文档</a>\r\n\r\n## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>（请不要 下载插件ZIP）</small>\r\n\r\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n<a href=\"https://ext.dcloud.net.cn/plugin?name=uv-ui\" target=\"_blank\">\r\n\r\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\r\n\r\n</a>\r\n\r\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-popup/changelog.md",
    "content": "## 1.0.6（2023-10-13）\n1. 优化vue，内容有背景色，设置圆角被遮挡的情况\n## 1.0.5（2023-09-10）\r\n1. 修复H5默认层级过高的问题\r\n2. 修复全局设置prop无效的问题\r\n## 1.0.4（2023-08-08）\r\n1. 修复修改zIndex不生效的BUG\r\n## 1.0.3（2023-07-02）\r\nuv-popup  弹出层，代码重构优化，性能翻倍，小程序体验性能更加，避免卡顿。打开和关闭方法更改，详情参考文档：https://www.uvui.cn/components/popup.html\r\n## 1.0.2（2023-06-11）\r\n1. 修复zIndex层级问题\r\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\n1. 新增uv-popup组件\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-popup/components/uv-popup/keypress.js",
    "content": "// #ifdef H5\r\nexport default {\r\n  name: 'Keypress',\r\n  props: {\r\n    disable: {\r\n      type: Boolean,\r\n      default: false\r\n    }\r\n  },\r\n  mounted () {\r\n    const keyNames = {\r\n      esc: ['Esc', 'Escape'],\r\n      tab: 'Tab',\r\n      enter: 'Enter',\r\n      space: [' ', 'Spacebar'],\r\n      up: ['Up', 'ArrowUp'],\r\n      left: ['Left', 'ArrowLeft'],\r\n      right: ['Right', 'ArrowRight'],\r\n      down: ['Down', 'ArrowDown'],\r\n      delete: ['Backspace', 'Delete', 'Del']\r\n    }\r\n    const listener = ($event) => {\r\n      if (this.disable) {\r\n        return\r\n      }\r\n      const keyName = Object.keys(keyNames).find(key => {\r\n        const keyName = $event.key\r\n        const value = keyNames[key]\r\n        return value === keyName || (Array.isArray(value) && value.includes(keyName))\r\n      })\r\n      if (keyName) {\r\n        // 避免和其他按键事件冲突\r\n        setTimeout(() => {\r\n          this.$emit(keyName, {})\r\n        }, 0)\r\n      }\r\n    }\r\n    document.addEventListener('keyup', listener)\r\n    // this.$once('hook:beforeDestroy', () => {\r\n    //   document.removeEventListener('keyup', listener)\r\n    // })\r\n  },\r\n\trender: () => {}\r\n}\r\n// #endif\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-popup/components/uv-popup/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 是否展示弹窗\r\n\t\tshow: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否显示遮罩\r\n\t\toverlay: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 弹出的方向，可选值为 top bottom right left center\r\n\t\tmode: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'bottom'\r\n\t\t},\r\n\t\t// 动画时长，单位ms\r\n\t\tduration: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 300\r\n\t\t},\r\n\t\t// 是否显示关闭图标\r\n\t\tcloseable: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 自定义遮罩的样式\r\n\t\toverlayStyle: {\r\n\t\t\ttype: [Object, String],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 点击遮罩是否关闭弹窗\r\n\t\tcloseOnClickOverlay: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 层级\r\n\t\tzIndex: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 10075\r\n\t\t},\r\n\t\t// 是否为iPhoneX留出底部安全距离\r\n\t\tsafeAreaInsetBottom: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 是否留出顶部安全距离（状态栏高度）\r\n\t\tsafeAreaInsetTop: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 自定义关闭图标位置，top-left为左上角，top-right为右上角，bottom-left为左下角，bottom-right为右下角\r\n\t\tcloseIconPos: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'top-right'\r\n\t\t},\r\n\t\t// 圆角值\r\n\t\tround: {\r\n\t\t\ttype: [Boolean, String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// mode=center，也即中部弹出时，是否使用缩放模式\r\n\t\tzoom: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 弹窗背景色，设置为transparent可去除白色背景\r\n\t\tbgColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 遮罩的透明度，0-1之间\r\n\t\toverlayOpacity: {\r\n\t\t\ttype: [Number, String],\r\n\t\t\tdefault: 0.5\r\n\t\t},\r\n\t\t...uni.$uv?.props?.popup\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-popup/components/uv-popup/uv-popup.vue",
    "content": "<template>\r\n\t<view \r\n\t\tv-if=\"showPopup\" \r\n\t\tclass=\"uv-popup\" \r\n\t\t:class=\"[popupClass, isDesktop ? 'fixforpc-z-index' : '']\"\r\n\t\t:style=\"[{zIndex: zIndex}]\"\r\n\t>\r\n\t\t<view @touchstart=\"touchstart\">\r\n\t\t\t<!-- 遮罩层 -->\r\n\t\t\t<uv-overlay\r\n\t\t\t\tkey=\"1\"\r\n\t\t\t\tv-if=\"maskShow && overlay\"\r\n\t\t\t\t:show=\"showTrans\"\r\n\t\t\t\t:duration=\"duration\"\r\n\t\t\t\t:custom-style=\"overlayStyle\"\r\n\t\t\t\t:opacity=\"overlayOpacity\"\r\n\t\t\t  :zIndex=\"zIndex\"\r\n\t\t\t\t@click=\"onTap\"\r\n\t\t\t></uv-overlay>\r\n\t\t\t<uv-transition \r\n\t\t\t\tkey=\"2\" \r\n\t\t\t\t:mode=\"ani\" \r\n\t\t\t\tname=\"content\" \r\n\t\t\t\t:custom-style=\"transitionStyle\" \r\n\t\t\t\t:duration=\"duration\"\r\n\t\t\t\t:show=\"showTrans\" \r\n\t\t\t\t@click=\"onTap\"\r\n\t\t\t>\r\n\t\t\t\t<view \r\n\t\t\t\t\tclass=\"uv-popup__content\" \r\n\t\t\t\t\t:style=\"[contentStyle]\" \r\n\t\t\t\t\t:class=\"[popupClass]\" \r\n\t\t\t\t\t@click=\"clear\"\r\n\t\t\t\t>\r\n\t\t\t\t\t<uv-status-bar v-if=\"safeAreaInsetTop\"></uv-status-bar>\r\n\t\t\t\t\t<slot />\r\n\t\t\t\t\t<uv-safe-bottom v-if=\"safeAreaInsetBottom\"></uv-safe-bottom>\r\n\t\t\t\t\t<view\r\n\t\t\t\t\t\tv-if=\"closeable\"\r\n\t\t\t\t\t\t@tap.stop=\"close\"\r\n\t\t\t\t\t\tclass=\"uv-popup__content__close\"\r\n\t\t\t\t\t\t:class=\"['uv-popup__content__close--' + closeIconPos]\"\r\n\t\t\t\t\t\thover-class=\"uv-popup__content__close--hover\"\r\n\t\t\t\t\t\thover-stay-time=\"150\"\r\n\t\t\t\t\t>\r\n\t\t\t\t\t\t<uv-icon\r\n\t\t\t\t\t\t\tname=\"close\"\r\n\t\t\t\t\t\t\tcolor=\"#909399\"\r\n\t\t\t\t\t\t\tsize=\"18\"\r\n\t\t\t\t\t\t\tbold\r\n\t\t\t\t\t\t></uv-icon>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</uv-transition>\r\n\t\t</view>\r\n\t\t<!-- #ifdef H5 -->\r\n\t\t<keypress v-if=\"maskShow\" @esc=\"onTap\" />\r\n\t\t<!-- #endif -->\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\t// #ifdef H5\r\n\timport keypress from './keypress.js'\r\n\t// #endif\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\t/**\r\n\t* PopUp 弹出层\r\n\t* @description 弹出层组件，为了解决遮罩弹层的问题\r\n\t* @tutorial https://www.uvui.cn/components/popup.html\r\n\t* @property {String} mode = [top|center|bottom|left|right] 弹出方式\r\n\t* \t@value top 顶部弹出\r\n\t* \t@value center 中间弹出\r\n\t* \t@value bottom 底部弹出\r\n\t* \t@value left\t\t左侧弹出\r\n\t* \t@value right  右侧弹出\r\n\t* @property {Number} duration 动画时长，默认300\r\n\t* @property {Boolean} overlay 是否显示遮罩，默认true\r\n\t* @property {Boolean} overlayOpacity 遮罩透明度，默认0.5 \r\n\t* @property {Object} overlayStyle 遮罩自定义样式\r\n\t* @property {Boolean} closeOnClickOverlay = [true|false] 蒙版点击是否关闭弹窗，默认true\r\n\t* @property {Number | String} zIndex 弹出层的层级\r\n\t* @property {Boolean} safeAreaInsetTop 是否留出顶部安全区（状态栏高度），默认false\r\n\t* @property {Boolean} safeAreaInsetBottom 是否为留出底部安全区适配，默认true\r\n\t* @property {Boolean} closeable 是否显示关闭图标，默认false\r\n\t* @property {Boolean} closeIconPos 自定义关闭图标位置，`top-left`-左上角，`top-right`-右上角，`bottom-left`-左下角，`bottom-right`-右下角，默认top-right\r\n\t* @property {String}  bgColor 主窗口背景色\r\n\t* @property {String}  maskBackgroundColor 蒙版颜色\r\n\t* @property {Boolean} customStyle 自定义样式\r\n\t* @event {Function} change 打开关闭弹窗触发，e={show: false}\r\n\t* @event {Function} maskClick 点击遮罩触发\r\n\t*/\r\n\texport default {\r\n\t\tname: 'uv-popup',\r\n\t\tcomponents: {\r\n\t\t\t// #ifdef H5\r\n\t\t\tkeypress\r\n\t\t\t// #endif\r\n\t\t},\r\n\t\tmixins: [mpMixin, mixin],\r\n\t\temits: ['change', 'maskClick'],\r\n\t\tprops: {\r\n\t\t\t// 弹出层类型，可选值，top: 顶部弹出层；bottom：底部弹出层；center：全屏弹出层\r\n\t\t\t// message: 消息提示 ; dialog : 对话框\r\n\t\t\tmode: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: 'center'\r\n\t\t\t},\r\n\t\t\t// 动画时长，单位ms\r\n\t\t\tduration: {\r\n\t\t\t\ttype: [String, Number],\r\n\t\t\t\tdefault: 300\r\n\t\t\t},\r\n\t\t\t// 层级\r\n\t\t\tzIndex: {\r\n\t\t\t\ttype: [String, Number],\r\n\t\t\t\t// #ifdef H5\r\n\t\t\t\tdefault: 997\r\n\t\t\t\t// #endif\r\n\t\t\t\t// #ifndef H5\r\n\t\t\t\tdefault: 10075\r\n\t\t\t\t// #endif\r\n\t\t\t},\r\n\t\t\tbgColor: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: '#ffffff'\r\n\t\t\t},\r\n\t\t\tsafeArea: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: true\r\n\t\t\t},\r\n\t\t\t// 是否显示遮罩\r\n\t\t\toverlay: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: true\r\n\t\t\t},\r\n\t\t\t// 点击遮罩是否关闭弹窗\r\n\t\t\tcloseOnClickOverlay: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: true\r\n\t\t\t},\r\n\t\t\t// 遮罩的透明度，0-1之间\r\n\t\t\toverlayOpacity: {\r\n\t\t\t\ttype: [Number, String],\r\n\t\t\t\tdefault: 0.4\r\n\t\t\t},\r\n\t\t\t// 自定义遮罩的样式\r\n\t\t\toverlayStyle: {\r\n\t\t\t\ttype: [Object, String],\r\n\t\t\t\tdefault: ''\r\n\t\t\t},\r\n\t\t\t// 是否为iPhoneX留出底部安全距离\r\n\t\t\tsafeAreaInsetBottom: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: true\r\n\t\t\t},\r\n\t\t\t// 是否留出顶部安全距离（状态栏高度）\r\n\t\t\tsafeAreaInsetTop: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: false\r\n\t\t\t},\r\n\t\t\t// 是否显示关闭图标\r\n\t\t\tcloseable: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: false\r\n\t\t\t},\r\n\t\t\t// 自定义关闭图标位置，top-left为左上角，top-right为右上角，bottom-left为左下角，bottom-right为右下角\r\n\t\t\tcloseIconPos: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: 'top-right'\r\n\t\t\t},\r\n\t\t\t// mode=center，也即中部弹出时，是否使用缩放模式\r\n\t\t\tzoom: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: true\r\n\t\t\t},\r\n\t\t\tround: {\r\n\t\t\t\ttype: [Number, String],\r\n\t\t\t\tdefault: 0\r\n\t\t\t},\r\n\t\t\t...uni.$uv?.props?.popup\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\t/**\r\n\t\t\t * 监听type类型\r\n\t\t\t */\r\n\t\t\ttype: {\r\n\t\t\t\thandler: function(type) {\r\n\t\t\t\t\tif (!this.config[type]) return\r\n\t\t\t\t\tthis[this.config[type]](true)\r\n\t\t\t\t},\r\n\t\t\t\timmediate: true\r\n\t\t\t},\r\n\t\t\tisDesktop: {\r\n\t\t\t\thandler: function(newVal) {\r\n\t\t\t\t\tif (!this.config[newVal]) return\r\n\t\t\t\t\tthis[this.config[this.mode]](true)\r\n\t\t\t\t},\r\n\t\t\t\timmediate: true\r\n\t\t\t},\r\n\t\t\t// H5 下禁止底部滚动\r\n\t\t\tshowPopup(show) {\r\n\t\t\t\t// #ifdef H5\r\n\t\t\t\t// fix by mehaotian 处理 h5 滚动穿透的问题\r\n\t\t\t\tdocument.getElementsByTagName('body')[0].style.overflow = show ? 'hidden' : 'visible'\r\n\t\t\t\t// #endif\r\n\t\t\t}\r\n\t\t},\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tani: [],\r\n\t\t\t\tshowPopup: false,\r\n\t\t\t\tshowTrans: false,\r\n\t\t\t\tpopupWidth: 0,\r\n\t\t\t\tpopupHeight: 0,\r\n\t\t\t\tconfig: {\r\n\t\t\t\t\ttop: 'top',\r\n\t\t\t\t\tbottom: 'bottom',\r\n\t\t\t\t\tcenter: 'center',\r\n\t\t\t\t\tleft: 'left',\r\n\t\t\t\t\tright: 'right',\r\n\t\t\t\t\tmessage: 'top',\r\n\t\t\t\t\tdialog: 'center',\r\n\t\t\t\t\tshare: 'bottom'\r\n\t\t\t\t},\r\n\t\t\t\ttransitionStyle: {\r\n\t\t\t\t\tposition: 'fixed',\r\n\t\t\t\t\tleft: 0,\r\n\t\t\t\t\tright: 0\r\n\t\t\t\t},\r\n\t\t\t\tmaskShow: true,\r\n\t\t\t\tmkclick: true,\r\n\t\t\t\tpopupClass: this.isDesktop ? 'fixforpc-top' : 'top'\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\tisDesktop() {\r\n\t\t\t\treturn this.popupWidth >= 500 && this.popupHeight >= 500\r\n\t\t\t},\r\n\t\t\tbg() {\r\n\t\t\t\tif (this.bgColor === '' || this.bgColor === 'none' || this.$uv.getPx(this.round)>0) {\r\n\t\t\t\t\treturn 'transparent'\r\n\t\t\t\t}\r\n\t\t\t\treturn this.bgColor\r\n\t\t\t},\r\n\t\t\tcontentStyle() {\r\n\t\t\t\tconst style = {};\r\n\t\t\t\tif (this.bgColor) {\r\n\t\t\t\t\tstyle.backgroundColor = this.bg\r\n\t\t\t\t}\r\n\t\t\t\tif(this.round) {\r\n\t\t\t\t\tconst value = this.$uv.addUnit(this.round)\r\n\t\t\t\t\tstyle.backgroundColor = this.bgColor\r\n\t\t\t\t\tif(this.mode === 'top') {\r\n\t\t\t\t\t\tstyle.borderBottomLeftRadius = value\r\n\t\t\t\t\t\tstyle.borderBottomRightRadius = value\r\n\t\t\t\t\t} else if(this.mode === 'bottom') {\r\n\t\t\t\t\t\tstyle.borderTopLeftRadius = value\r\n\t\t\t\t\t\tstyle.borderTopRightRadius = value\r\n\t\t\t\t\t} else if(this.mode === 'center') {\r\n\t\t\t\t\t\tstyle.borderRadius = value\r\n\t\t\t\t\t} \r\n\t\t\t\t}\r\n\t\t\t\treturn this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))\r\n\t\t\t}\r\n\t\t},\r\n\t\t// #ifndef VUE3\r\n\t\t// TODO vue2\r\n\t\tdestroyed() {\r\n\t\t\tthis.setH5Visible()\r\n\t\t},\r\n\t\t// #endif\r\n\t\t// #ifdef VUE3\r\n\t\t// TODO vue3\r\n\t\tunmounted() {\r\n\t\t\tthis.setH5Visible()\r\n\t\t},\r\n\t\t// #endif\r\n\t\tcreated() {\r\n\t\t\t// TODO 处理 message 组件生命周期异常的问题\r\n\t\t\tthis.messageChild = null\r\n\t\t\t// TODO 解决头条冒泡的问题\r\n\t\t\tthis.clearPropagation = false\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tsetH5Visible() {\r\n\t\t\t\t// #ifdef H5\r\n\t\t\t\t// fix by mehaotian 处理 h5 滚动穿透的问题\r\n\t\t\t\tdocument.getElementsByTagName('body')[0].style.overflow = 'visible'\r\n\t\t\t\t// #endif\r\n\t\t\t},\r\n\t\t\t/**\r\n\t\t\t * 公用方法，不显示遮罩层\r\n\t\t\t */\r\n\t\t\tcloseMask() {\r\n\t\t\t\tthis.maskShow = false\r\n\t\t\t},\r\n\t\t\t// TODO nvue 取消冒泡\r\n\t\t\tclear(e) {\r\n\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\te.stopPropagation()\r\n\t\t\t\t// #endif\r\n\t\t\t\tthis.clearPropagation = true\r\n\t\t\t},\r\n\r\n\t\t\topen(direction) {\r\n\t\t\t\t// fix by mehaotian 处理快速打开关闭的情况\r\n\t\t\t\tif (this.showPopup) {\r\n\t\t\t\t\treturn\r\n\t\t\t\t}\r\n\t\t\t\tlet innerType = ['top', 'center', 'bottom', 'left', 'right', 'message', 'dialog', 'share']\r\n\t\t\t\tif (!(direction && innerType.indexOf(direction) !== -1)) {\r\n\t\t\t\t\tdirection = this.mode\r\n\t\t\t\t}\r\n\t\t\t\tif (!this.config[direction]) {\r\n\t\t\t\t\treturn this.$uv.error(`缺少类型：${direction}`);\r\n\t\t\t\t}\r\n\t\t\t\tthis[this.config[direction]]()\r\n\t\t\t\tthis.$emit('change', {\r\n\t\t\t\t\tshow: true,\r\n\t\t\t\t\ttype: direction\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\tclose(type) {\r\n\t\t\t\tthis.showTrans = false\r\n\t\t\t\tthis.$emit('change', {\r\n\t\t\t\t\tshow: false,\r\n\t\t\t\t\ttype: this.mode\r\n\t\t\t\t})\r\n\t\t\t\tclearTimeout(this.timer)\r\n\t\t\t\t// // 自定义关闭事件\r\n\t\t\t\tthis.timer = setTimeout(() => {\r\n\t\t\t\t\tthis.showPopup = false\r\n\t\t\t\t}, 300)\r\n\t\t\t},\r\n\t\t\t// TODO 处理冒泡事件，头条的冒泡事件有问题 ，先这样兼容\r\n\t\t\ttouchstart() {\r\n\t\t\t\tthis.clearPropagation = false\r\n\t\t\t},\r\n\t\t\tonTap() {\r\n\t\t\t\tif (this.clearPropagation) {\r\n\t\t\t\t\t// fix by mehaotian 兼容 nvue\r\n\t\t\t\t\tthis.clearPropagation = false\r\n\t\t\t\t\treturn\r\n\t\t\t\t}\r\n\t\t\t\tthis.$emit('maskClick')\r\n\t\t\t\tif (!this.closeOnClickOverlay) return\r\n\t\t\t\tthis.close()\r\n\t\t\t},\r\n\t\t\t/**\r\n\t\t\t * 顶部弹出样式处理\r\n\t\t\t */\r\n\t\t\ttop(type) {\r\n\t\t\t\tthis.popupClass = this.isDesktop ? 'fixforpc-top' : 'top'\r\n\t\t\t\tthis.ani = ['slide-top']\r\n\t\t\t\tthis.transitionStyle = {\r\n\t\t\t\t\tposition: 'fixed',\r\n\t\t\t\t\tzIndex: this.zIndex,\r\n\t\t\t\t\tleft: 0,\r\n\t\t\t\t\tright: 0,\r\n\t\t\t\t\tbackgroundColor: this.bg\r\n\t\t\t\t}\r\n\t\t\t\t// TODO 兼容 type 属性 ，后续会废弃\r\n\t\t\t\tif (type) return\r\n\t\t\t\tthis.showPopup = true\r\n\t\t\t\tthis.showTrans = true\r\n\t\t\t\tthis.$nextTick(() => {\r\n\t\t\t\t\tif (this.messageChild && this.mode === 'message') {\r\n\t\t\t\t\t\tthis.messageChild.timerClose()\r\n\t\t\t\t\t}\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\t/**\r\n\t\t\t * 底部弹出样式处理\r\n\t\t\t */\r\n\t\t\tbottom(type) {\r\n\t\t\t\tthis.popupClass = 'bottom'\r\n\t\t\t\tthis.ani = ['slide-bottom']\r\n\t\t\t\tthis.transitionStyle = {\r\n\t\t\t\t\tposition: 'fixed',\r\n\t\t\t\t\tzIndex: this.zIndex,\r\n\t\t\t\t\tleft: 0,\r\n\t\t\t\t\tright: 0,\r\n\t\t\t\t\tbottom: 0,\r\n\t\t\t\t\tbackgroundColor: this.bg\r\n\t\t\t\t}\r\n\t\t\t\t// TODO 兼容 type 属性 ，后续会废弃\r\n\t\t\t\tif (type) return\r\n\t\t\t\tthis.showPopup = true\r\n\t\t\t\tthis.showTrans = true\r\n\t\t\t},\r\n\t\t\t/**\r\n\t\t\t * 中间弹出样式处理\r\n\t\t\t */\r\n\t\t\tcenter(type) {\r\n\t\t\t\tthis.popupClass = 'center'\r\n\t\t\t\tthis.ani = this.zoom?['zoom-in', 'fade']:['fade'];\r\n\t\t\t\tthis.transitionStyle = {\r\n\t\t\t\t\tposition: 'fixed',\r\n\t\t\t\t\tzIndex: this.zIndex,\r\n\t\t\t\t\t/* #ifndef APP-NVUE */\r\n\t\t\t\t\tdisplay: 'flex',\r\n\t\t\t\t\tflexDirection: 'column',\r\n\t\t\t\t\t/* #endif */\r\n\t\t\t\t\tbottom: 0,\r\n\t\t\t\t\tleft: 0,\r\n\t\t\t\t\tright: 0,\r\n\t\t\t\t\ttop: 0,\r\n\t\t\t\t\tjustifyContent: 'center',\r\n\t\t\t\t\talignItems: 'center'\r\n\t\t\t\t}\r\n\t\t\t\t// TODO 兼容 type 属性 ，后续会废弃\r\n\t\t\t\tif (type) return\r\n\t\t\t\tthis.showPopup = true\r\n\t\t\t\tthis.showTrans = true\r\n\t\t\t},\r\n\t\t\tleft(type) {\r\n\t\t\t\tthis.popupClass = 'left'\r\n\t\t\t\tthis.ani = ['slide-left']\r\n\t\t\t\tthis.transitionStyle = {\r\n\t\t\t\t\tposition: 'fixed',\r\n\t\t\t\t\tzIndex: this.zIndex,\r\n\t\t\t\t\tleft: 0,\r\n\t\t\t\t\tbottom: 0,\r\n\t\t\t\t\ttop: 0,\r\n\t\t\t\t\tbackgroundColor: this.bg,\r\n\t\t\t\t\t/* #ifndef APP-NVUE */\r\n\t\t\t\t\tdisplay: 'flex',\r\n\t\t\t\t\tflexDirection: 'column'\r\n\t\t\t\t\t/* #endif */\r\n\t\t\t\t}\r\n\t\t\t\t// TODO 兼容 type 属性 ，后续会废弃\r\n\t\t\t\tif (type) return\r\n\t\t\t\tthis.showPopup = true\r\n\t\t\t\tthis.showTrans = true\r\n\t\t\t},\r\n\t\t\tright(type) {\r\n\t\t\t\tthis.popupClass = 'right'\r\n\t\t\t\tthis.ani = ['slide-right']\r\n\t\t\t\tthis.transitionStyle = {\r\n\t\t\t\t\tposition: 'fixed',\r\n\t\t\t\t\tzIndex: this.zIndex,\r\n\t\t\t\t\tbottom: 0,\r\n\t\t\t\t\tright: 0,\r\n\t\t\t\t\ttop: 0,\r\n\t\t\t\t\tbackgroundColor: this.bg,\r\n\t\t\t\t\t/* #ifndef APP-NVUE */\r\n\t\t\t\t\tdisplay: 'flex',\r\n\t\t\t\t\tflexDirection: 'column'\r\n\t\t\t\t\t/* #endif */\r\n\t\t\t\t}\r\n\t\t\t\t// TODO 兼容 type 属性 ，后续会废弃\r\n\t\t\t\tif (type) return\r\n\t\t\t\tthis.showPopup = true\r\n\t\t\t\tthis.showTrans = true\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n<style lang=\"scss\" scoped>\r\n\t.uv-popup {\r\n\t\tposition: fixed;\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\tz-index: 99;\r\n\r\n\t\t/* #endif */\r\n\t\t&.top,\r\n\t\t&.left,\r\n\t\t&.right {\r\n\t\t\t/* #ifdef H5 */\r\n\t\t\ttop: var(--window-top);\r\n\t\t\t/* #endif */\r\n\t\t\t/* #ifndef H5 */\r\n\t\t\ttop: 0;\r\n\t\t\t/* #endif */\r\n\t\t}\r\n\r\n\t\t.uv-popup__content {\r\n\t\t\t/* #ifndef APP-NVUE */\r\n\t\t\tdisplay: block;\r\n\t\t\toverflow: hidden;\r\n\t\t\t/* #endif */\r\n\t\t\tposition: relative;\r\n\r\n\t\t\t&.left,\r\n\t\t\t&.right {\r\n\t\t\t\t/* #ifdef H5 */\r\n\t\t\t\tpadding-top: var(--window-top);\r\n\t\t\t\t/* #endif */\r\n\t\t\t\t/* #ifndef H5 */\r\n\t\t\t\tpadding-top: 0;\r\n\t\t\t\t/* #endif */\r\n\t\t\t\tflex: 1;\r\n\t\t\t}\r\n\t\t\t&__close {\r\n\t\t\t\tposition: absolute;\r\n\r\n\t\t\t\t&--hover {\r\n\t\t\t\t\topacity: 0.4;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t&__close--top-left {\r\n\t\t\t\ttop: 15px;\r\n\t\t\t\tleft: 15px;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t&__close--top-right {\r\n\t\t\t\ttop: 15px;\r\n\t\t\t\tright: 15px;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t&__close--bottom-left {\r\n\t\t\t\tbottom: 15px;\r\n\t\t\t\tleft: 15px;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t&__close--bottom-right {\r\n\t\t\t\tright: 15px;\r\n\t\t\t\tbottom: 15px;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t.fixforpc-z-index {\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\tz-index: 999;\r\n\t\t/* #endif */\r\n\t}\r\n\r\n\t.fixforpc-top {\r\n\t\ttop: 0;\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-popup/package.json",
    "content": "{\r\n  \"id\": \"uv-popup\",\r\n  \"displayName\": \"uv-popup 弹出层 全面兼容vue3+2、app、h5、小程序等多端\",\r\n  \"version\": \"1.0.6\",\r\n  \"description\": \"uv-popup 弹出层容器，用于展示弹窗、信息提示等内容，支持上、下、左、右和中部弹出。组件只提供容器，内部内容由用户自定义。\",\r\n  \"keywords\": [\r\n    \"uv-popup\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"popup\",\r\n    \"弹出层\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-overlay\",\r\n\t\t\t\"uv-transition\",\r\n\t\t\t\"uv-icon\",\r\n\t\t\t\"uv-status-bar\",\r\n\t\t\t\"uv-safe-bottom\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-popup/readme.md",
    "content": "## Popup 弹出层\r\n\r\n> **组件名：uv-popup**\r\n\r\n弹出层容器，用于展示弹窗、信息提示等内容，支持上、下、左、右和中部弹出。组件只提供容器，内部内容由用户自定义。\r\n\r\n该组件已经放弃原来`uview2.x`的写法，参照了官方`uni-popup`的写法进行重构。在小程序端的性能大大提升，打开和关闭避免延迟，调用方法与之前相比也有所差异，具体请查看文档。\r\n\r\n# <a href=\"https://www.uvui.cn/components/popup.html\" target=\"_blank\">查看文档</a>\r\n\r\n## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>（请不要 下载插件ZIP）</small>\r\n\r\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n<a href=\"https://ext.dcloud.net.cn/plugin?name=uv-ui\" target=\"_blank\">\r\n\r\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\r\n\r\n</a>\r\n\r\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-qrcode/changelog.md",
    "content": "## 1.0.4（2023-07-17）\n1. 优化文档\n2. 优化其他\n## 1.0.3（2023-06-26）\n1. H5增加属性h5SaveTip 保存二维码时候是否显示提示\n## 1.0.2（2023-06-01）\r\n1. 修复点击触发两次事件的BUG \r\n## 1.0.1（2023-05-23）\r\n1. 修复在部分平台不显示加载效果的BUG\r\n## 1.0.0（2023-05-17）\r\n1. 新增uv-qrcode组件\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-qrcode/components/uv-qrcode/cache.js",
    "content": "export const cacheImageList = [];"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/bridge/bridge-weex.js",
    "content": "const isWeex = typeof WXEnvironment !== 'undefined';\r\nconst isWeexIOS = isWeex && /ios/i.test(WXEnvironment.platform);\r\nconst isWeexAndroid = isWeex && !isWeexIOS;\r\n\r\nimport GLmethod from '../context-webgl/GLmethod';\r\n\r\nconst GCanvasModule =\r\n    (typeof weex !== 'undefined' && weex.requireModule) ? (weex.requireModule('gcanvas')) :\r\n        (typeof __weex_require__ !== 'undefined') ? (__weex_require__('@weex-module/gcanvas')) : {};\r\n\r\nlet isDebugging = false;\r\n\r\nlet isComboDisabled = false;\r\n\r\nconst logCommand = (function () {\r\n    const methodQuery = [];\r\n    Object.keys(GLmethod).forEach(key => {\r\n        methodQuery[GLmethod[key]] = key;\r\n    })\r\n    const queryMethod = (id) => {\r\n        return methodQuery[parseInt(id)] || 'NotFoundMethod';\r\n    }\r\n    const logCommand = (id, cmds) => {\r\n        const mId = cmds.split(',')[0];\r\n        const mName = queryMethod(mId);\r\n        console.log(`=== callNative - componentId:${id}; method: ${mName}; cmds: ${cmds}`);\r\n    }\r\n    return logCommand;\r\n})();\r\n\r\nfunction joinArray(arr, sep) {\r\n    let res = '';\r\n    for (let i = 0; i < arr.length; i++) {\r\n        if (i !== 0) {\r\n            res += sep;\r\n        }\r\n        res += arr[i];\r\n    }\r\n    return res;\r\n}\r\n\r\nconst commandsCache = {}\r\n\r\nconst GBridge = {\r\n\r\n    callEnable: (ref, configArray) => {\r\n\r\n        commandsCache[ref] = [];\r\n\r\n        return GCanvasModule.enable({\r\n            componentId: ref,\r\n            config: configArray\r\n        });\r\n    },\r\n\r\n    callEnableDebug: () => {\r\n        isDebugging = true;\r\n    },\r\n\r\n    callEnableDisableCombo: () => {\r\n        isComboDisabled = true;\r\n    },\r\n\r\n    callSetContextType: function (componentId, context_type) {\r\n        GCanvasModule.setContextType(context_type, componentId);\r\n    },\r\n\r\n    callReset: function(id){\r\n        GCanvasModule.resetComponent && canvasModule.resetComponent(componentId);\r\n    },\r\n\r\n    render: isWeexIOS ? function (componentId) {\r\n        return GCanvasModule.extendCallNative({\r\n            contextId: componentId,\r\n            type: 0x60000001\r\n        });\r\n    } : function (componentId) {\r\n        return callGCanvasLinkNative(componentId, 0x60000001, 'render');\r\n    },\r\n\r\n    render2d: isWeexIOS ? function (componentId, commands, callback) {\r\n\r\n        if (isDebugging) {\r\n            console.log('>>> >>> render2d ===');\r\n            console.log('>>> commands: ' + commands);\r\n        }\r\n\t\t\r\n        GCanvasModule.render([commands, callback?true:false], componentId, callback);\r\n\r\n    } : function (componentId, commands,callback) {\r\n\r\n        if (isDebugging) {\r\n            console.log('>>> >>> render2d ===');\r\n            console.log('>>> commands: ' + commands);\r\n        }\r\n\r\n        callGCanvasLinkNative(componentId, 0x20000001, commands);\r\n\t\tif(callback){\r\n\t\tcallback();\r\n\t\t}\r\n    },\r\n\r\n    callExtendCallNative: isWeexIOS ? function (componentId, cmdArgs) {\r\n\r\n        throw 'should not be here anymore ' + cmdArgs;\r\n\r\n    } : function (componentId, cmdArgs) {\r\n\r\n        throw 'should not be here anymore ' + cmdArgs;\r\n\r\n    },\r\n\r\n\r\n    flushNative: isWeexIOS ? function (componentId) {\r\n\r\n        const cmdArgs = joinArray(commandsCache[componentId], ';');\r\n        commandsCache[componentId] = [];\r\n\r\n        if (isDebugging) {\r\n            console.log('>>> >>> flush native ===');\r\n            console.log('>>> commands: ' + cmdArgs);\r\n        }\r\n\r\n        const result = GCanvasModule.extendCallNative({\r\n            \"contextId\": componentId,\r\n            \"type\": 0x60000000,\r\n            \"args\": cmdArgs\r\n        });\r\n\r\n        const res = result && result.result;\r\n\r\n        if (isDebugging) {\r\n            console.log('>>> result: ' + res);\r\n        }\r\n\r\n        return res;\r\n\r\n    } : function (componentId) {\r\n\r\n        const cmdArgs = joinArray(commandsCache[componentId], ';');\r\n        commandsCache[componentId] = [];\r\n\r\n        if (isDebugging) {\r\n            console.log('>>> >>> flush native ===');\r\n            console.log('>>> commands: ' + cmdArgs);\r\n        }\r\n\r\n        const result = callGCanvasLinkNative(componentId, 0x60000000, cmdArgs);\r\n\r\n        if (isDebugging) {\r\n            console.log('>>> result: ' + result);\r\n        }\r\n\r\n        return result;\r\n    },\r\n\r\n    callNative: function (componentId, cmdArgs, cache) {\r\n\r\n        if (isDebugging) {\r\n            logCommand(componentId, cmdArgs);\r\n        }\r\n\r\n        commandsCache[componentId].push(cmdArgs);\r\n\r\n        if (!cache || isComboDisabled) {\r\n            return GBridge.flushNative(componentId);\r\n        } else {\r\n            return undefined;\r\n        }\r\n    },\r\n\r\n    texImage2D(componentId, ...args) {\r\n        if (isWeexIOS) {\r\n            if (args.length === 6) {\r\n                const [target, level, internalformat, format, type, image] = args;\r\n                GBridge.callNative(\r\n                    componentId,\r\n                    GLmethod.texImage2D + ',' + 6 + ',' + target + ',' + level + ',' + internalformat + ',' + format + ',' + type + ',' + image.src\r\n                )\r\n            } else if (args.length === 9) {\r\n                const [target, level, internalformat, width, height, border, format, type, image] = args;\r\n                GBridge.callNative(\r\n                    componentId,\r\n                    GLmethod.texImage2D + ',' + 9 + ',' + target + ',' + level + ',' + internalformat + ',' + width + ',' + height + ',' + border + ',' +\r\n                    + format + ',' + type + ',' + (image ? image.src : 0)\r\n                )\r\n            }\r\n        } else if (isWeexAndroid) {\r\n            if (args.length === 6) {\r\n                const [target, level, internalformat, format, type, image] = args;\r\n                GCanvasModule.texImage2D(componentId, target, level, internalformat, format, type, image.src);\r\n            } else if (args.length === 9) {\r\n                const [target, level, internalformat, width, height, border, format, type, image] = args;\r\n                GCanvasModule.texImage2D(componentId, target, level, internalformat, width, height, border, format, type, (image ? image.src : 0));\r\n            }\r\n        }\r\n    },\r\n\r\n    texSubImage2D(componentId, target, level, xoffset, yoffset, format, type, image) {\r\n        if (isWeexIOS) {\r\n            if (arguments.length === 8) {\r\n                GBridge.callNative(\r\n                    componentId,\r\n                    GLmethod.texSubImage2D + ',' + 6 + ',' + target + ',' + level + ',' + xoffset + ',' + yoffset, + ',' + format + ',' + type + ',' + image.src\r\n                )\r\n            }\r\n        } else if (isWeexAndroid) {\r\n            GCanvasModule.texSubImage2D(componentId, target, level, xoffset, yoffset, format, type, image.src);\r\n        }\r\n    },\r\n\r\n    bindImageTexture(componentId, src, imageId) {\r\n        GCanvasModule.bindImageTexture([src, imageId], componentId);\r\n    },\r\n\r\n    perloadImage([url, id], callback) {\r\n        GCanvasModule.preLoadImage([url, id], function (image) {\r\n            image.url = url;\r\n            image.id = id;\r\n            callback(image);\r\n        });\r\n    },\r\n\t\r\n\tmeasureText(text, fontStyle, componentId) {\r\n\t    return GCanvasModule.measureText([text, fontStyle], componentId);\r\n\t},\r\n\t\r\n\tgetImageData (componentId, x, y, w, h, callback) {\r\n\t\tGCanvasModule.getImageData([x, y,w,h],componentId,callback);\r\n\t},\r\n\t\r\n\tputImageData (componentId, data, x, y, w, h, callback) {\r\n\t\tGCanvasModule.putImageData([x, y,w,h,data],componentId,callback);\r\n\t},\r\n\t\r\n\ttoTempFilePath(componentId, x, y, width, height, destWidth, destHeight, fileType, quality, callback){ \r\n\t\tGCanvasModule.toTempFilePath([x, y, width,height, destWidth, destHeight, fileType, quality], componentId, callback);\r\n\t}\r\n}\r\n\r\nexport default GBridge;"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-2d/FillStyleLinearGradient.js",
    "content": "class FillStyleLinearGradient {\r\n\r\n    constructor(x0, y0, x1, y1) {\r\n        this._start_pos = { _x: x0, _y: y0 };\r\n        this._end_pos = { _x: x1, _y: y1 };\r\n        this._stop_count = 0;\r\n        this._stops = [0, 0, 0, 0, 0];\r\n    }\r\n\r\n    addColorStop = function (pos, color) {\r\n        if (this._stop_count < 5 && 0.0 <= pos && pos <= 1.0) {\r\n            this._stops[this._stop_count] = { _pos: pos, _color: color };\r\n            this._stop_count++;\r\n        }\r\n    }\r\n}\r\n\r\nexport default FillStyleLinearGradient;"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-2d/FillStylePattern.js",
    "content": "class FillStylePattern {\r\n    constructor(img, pattern) {\r\n        this._style = pattern;\r\n        this._img = img;\r\n    }\r\n}\r\n\r\nexport default FillStylePattern;"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-2d/FillStyleRadialGradient.js",
    "content": "class FillStyleRadialGradient {\r\n    constructor(x0, y0, r0, x1, y1, r1) {\r\n        this._start_pos = { _x: x0, _y: y0, _r: r0 };\r\n        this._end_pos = { _x: x1, _y: y1, _r: r1 };\r\n        this._stop_count = 0;\r\n        this._stops = [0, 0, 0, 0, 0];\r\n    }\r\n\r\n    addColorStop(pos, color) {\r\n        if (this._stop_count < 5 && 0.0 <= pos && pos <= 1.0) {\r\n            this._stops[this._stop_count] = { _pos: pos, _color: color };\r\n            this._stop_count++;\r\n        }\r\n    }\r\n}\r\n\r\nexport default FillStyleRadialGradient;"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-2d/RenderingContext.js",
    "content": "import FillStylePattern from './FillStylePattern';\r\nimport FillStyleLinearGradient from './FillStyleLinearGradient';\r\nimport FillStyleRadialGradient from './FillStyleRadialGradient';\r\nimport GImage from '../env/image.js';\r\nimport {\r\n\tArrayBufferToBase64,\r\n\tBase64ToUint8ClampedArray\r\n} from '../env/tool.js';\r\n\r\nexport default class CanvasRenderingContext2D {\r\n\r\n\t_drawCommands = '';\r\n\r\n\t_globalAlpha = 1.0;\r\n\r\n\t_fillStyle = 'rgb(0,0,0)';\r\n\t_strokeStyle = 'rgb(0,0,0)';\r\n\r\n\t_lineWidth = 1;\r\n\t_lineCap = 'butt';\r\n\t_lineJoin = 'miter';\r\n\r\n\t_miterLimit = 10;\r\n\r\n\t_globalCompositeOperation = 'source-over';\r\n\r\n\t_textAlign = 'start';\r\n\t_textBaseline = 'alphabetic';\r\n\r\n\t_font = '10px sans-serif';\r\n\r\n\t_savedGlobalAlpha = [];\r\n\r\n\ttimer = null;\r\n\tcomponentId = null;\r\n\r\n\t_notCommitDrawImageCache = [];\r\n\t_needRedrawImageCache = [];\r\n\t_redrawCommands = '';\r\n\t_autoSaveContext = true;\r\n\t// _imageMap = new GHashMap();\r\n\t// _textureMap = new GHashMap();\r\n\r\n\tconstructor() {\r\n\t\tthis.className = 'CanvasRenderingContext2D';\r\n\t\t//this.save()\r\n\t}\r\n\r\n\tsetFillStyle(value) {\r\n\t\tthis.fillStyle = value;\r\n\t}\r\n\r\n\tset fillStyle(value) {\r\n\t\tthis._fillStyle = value;\r\n\r\n\t\tif (typeof(value) == 'string') {\r\n\t\t\tthis._drawCommands = this._drawCommands.concat(\"F\" + value + \";\");\r\n\t\t} else if (value instanceof FillStylePattern) {\r\n\t\t\tconst image = value._img;\r\n\t\t\tif (!image.complete) {\r\n\t\t\t\timage.onload = () => {\r\n\t\t\t\t\tvar index = this._needRedrawImageCache.indexOf(image);\r\n\t\t\t\t\tif (index > -1) {\r\n\t\t\t\t\t\tthis._needRedrawImageCache.splice(index, 1);\r\n\t\t\t\t\t\tCanvasRenderingContext2D.GBridge.bindImageTexture(this.componentId, image.src, image._id);\r\n\t\t\t\t\t\tthis._redrawflush(true);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tthis._notCommitDrawImageCache.push(image);\r\n\t\t\t} else {\r\n\t\t\t\tCanvasRenderingContext2D.GBridge.bindImageTexture(this.componentId, image.src, image._id);\r\n\t\t\t}\r\n\r\n\t\t\t//CanvasRenderingContext2D.GBridge.bindImageTexture(this.componentId, image.src, image._id);\r\n\t\t\tthis._drawCommands = this._drawCommands.concat(\"G\" + image._id + \",\" + value._style + \";\");\r\n\t\t} else if (value instanceof FillStyleLinearGradient) {\r\n\t\t\tvar command = \"D\" + value._start_pos._x.toFixed(2) + \",\" + value._start_pos._y.toFixed(2) + \",\" +\r\n\t\t\t\tvalue._end_pos._x.toFixed(2) + \",\" + value._end_pos._y.toFixed(2) + \",\" +\r\n\t\t\t\tvalue._stop_count;\r\n\t\t\tfor (var i = 0; i < value._stop_count; ++i) {\r\n\t\t\t\tcommand += (\",\" + value._stops[i]._pos + \",\" + value._stops[i]._color);\r\n\t\t\t}\r\n\t\t\tthis._drawCommands = this._drawCommands.concat(command + \";\");\r\n\t\t} else if (value instanceof FillStyleRadialGradient) {\r\n\t\t\tvar command = \"H\" + value._start_pos._x.toFixed(2) + \",\" + value._start_pos._y.toFixed(2) + \",\" + value._start_pos._r\r\n\t\t\t\t.toFixed(2) + \",\" +\r\n\t\t\t\tvalue._end_pos._x.toFixed(2) + \",\" + value._end_pos._y.toFixed(2) + \",\" + value._end_pos._r.toFixed(2) + \",\" +\r\n\t\t\t\tvalue._stop_count;\r\n\t\t\tfor (var i = 0; i < value._stop_count; ++i) {\r\n\t\t\t\tcommand += (\",\" + value._stops[i]._pos + \",\" + value._stops[i]._color);\r\n\t\t\t}\r\n\t\t\tthis._drawCommands = this._drawCommands.concat(command + \";\");\r\n\t\t}\r\n\t}\r\n\r\n\tget fillStyle() {\r\n\t\treturn this._fillStyle;\r\n\t}\r\n\r\n\tget globalAlpha() {\r\n\t\treturn this._globalAlpha;\r\n\t}\r\n\r\n\tsetGlobalAlpha(value) {\r\n\t\tthis.globalAlpha = value;\r\n\t}\r\n\r\n\tset globalAlpha(value) {\r\n\t\tthis._globalAlpha = value;\r\n\t\tthis._drawCommands = this._drawCommands.concat(\"a\" + value.toFixed(2) + \";\");\r\n\t}\r\n\r\n\r\n\tget strokeStyle() {\r\n\t\treturn this._strokeStyle;\r\n\t}\r\n\r\n\tsetStrokeStyle(value) {\r\n\t\tthis.strokeStyle = value;\r\n\t}\r\n\r\n\tset strokeStyle(value) {\r\n\r\n\t\tthis._strokeStyle = value;\r\n\r\n\t\tif (typeof(value) == 'string') {\r\n\t\t\tthis._drawCommands = this._drawCommands.concat(\"S\" + value + \";\");\r\n\t\t} else if (value instanceof FillStylePattern) {\r\n\t\t\tCanvasRenderingContext2D.GBridge.bindImageTexture(this.componentId, image.src, image._id);\r\n\t\t\tthis._drawCommands = this._drawCommands.concat(\"G\" + image._id + \",\" + value._style + \";\");\r\n\t\t} else if (value instanceof FillStyleLinearGradient) {\r\n\t\t\tvar command = \"D\" + value._start_pos._x.toFixed(2) + \",\" + value._start_pos._y.toFixed(2) + \",\" +\r\n\t\t\t\tvalue._end_pos._x.toFixed(2) + \",\" + value._end_pos._y.toFixed(2) + \",\" +\r\n\t\t\t\tvalue._stop_count;\r\n\r\n\t\t\tfor (var i = 0; i < value._stop_count; ++i) {\r\n\t\t\t\tcommand += (\",\" + value._stops[i]._pos + \",\" + value._stops[i]._color);\r\n\t\t\t}\r\n\t\t\tthis._drawCommands = this._drawCommands.concat(command + \";\");\r\n\t\t} else if (value instanceof FillStyleRadialGradient) {\r\n\t\t\tvar command = \"H\" + value._start_pos._x.toFixed(2) + \",\" + value._start_pos._y.toFixed(2) + \",\" + value._start_pos._r\r\n\t\t\t\t.toFixed(2) + \",\" +\r\n\t\t\t\tvalue._end_pos._x.toFixed(2) + \",\" + value._end_pos._y + \",\".toFixed(2) + value._end_pos._r.toFixed(2) + \",\" +\r\n\t\t\t\tvalue._stop_count;\r\n\r\n\t\t\tfor (var i = 0; i < value._stop_count; ++i) {\r\n\t\t\t\tcommand += (\",\" + value._stops[i]._pos + \",\" + value._stops[i]._color);\r\n\t\t\t}\r\n\t\t\tthis._drawCommands = this._drawCommands.concat(command + \";\");\r\n\t\t}\r\n\t}\r\n\r\n\tget lineWidth() {\r\n\t\treturn this._lineWidth;\r\n\t}\r\n\r\n\tsetLineWidth(value) {\r\n\t\tthis.lineWidth = value;\r\n\t}\r\n\r\n\tset lineWidth(value) {\r\n\t\tthis._lineWidth = value;\r\n\t\tthis._drawCommands = this._drawCommands.concat(\"W\" + value + \";\");\r\n\t}\r\n\r\n\tget lineCap() {\r\n\t\treturn this._lineCap;\r\n\t}\r\n\r\n\tsetLineCap(value) {\r\n\t\tthis.lineCap = value;\r\n\t}\r\n\r\n\tset lineCap(value) {\r\n\t\tthis._lineCap = value;\r\n\t\tthis._drawCommands = this._drawCommands.concat(\"C\" + value + \";\");\r\n\t}\r\n\r\n\tget lineJoin() {\r\n\t\treturn this._lineJoin;\r\n\t}\r\n\r\n\tsetLineJoin(value) {\r\n\t\tthis.lineJoin = value\r\n\t}\r\n\r\n\tset lineJoin(value) {\r\n\t\tthis._lineJoin = value;\r\n\t\tthis._drawCommands = this._drawCommands.concat(\"J\" + value + \";\");\r\n\t}\r\n\r\n\tget miterLimit() {\r\n\t\treturn this._miterLimit;\r\n\t}\r\n\r\n\tsetMiterLimit(value) {\r\n\t\tthis.miterLimit = value\r\n\t}\r\n\r\n\tset miterLimit(value) {\r\n\t\tthis._miterLimit = value;\r\n\t\tthis._drawCommands = this._drawCommands.concat(\"M\" + value + \";\");\r\n\t}\r\n\r\n\tget globalCompositeOperation() {\r\n\t\treturn this._globalCompositeOperation;\r\n\t}\r\n\r\n\tset globalCompositeOperation(value) {\r\n\r\n\t\tthis._globalCompositeOperation = value;\r\n\t\tlet mode = 0;\r\n\t\tswitch (value) {\r\n\t\t\tcase \"source-over\":\r\n\t\t\t\tmode = 0;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"source-atop\":\r\n\t\t\t\tmode = 5;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"source-in\":\r\n\t\t\t\tmode = 0;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"source-out\":\r\n\t\t\t\tmode = 2;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"destination-over\":\r\n\t\t\t\tmode = 4;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"destination-atop\":\r\n\t\t\t\tmode = 4;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"destination-in\":\r\n\t\t\t\tmode = 4;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"destination-out\":\r\n\t\t\t\tmode = 3;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"lighter\":\r\n\t\t\t\tmode = 1;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"copy\":\r\n\t\t\t\tmode = 2;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"xor\":\r\n\t\t\t\tmode = 6;\r\n\t\t\t\tbreak;\r\n\t\t\tdefault:\r\n\t\t\t\tmode = 0;\r\n\t\t}\r\n\r\n\t\tthis._drawCommands = this._drawCommands.concat(\"B\" + mode + \";\");\r\n\t}\r\n\r\n\tget textAlign() {\r\n\t\treturn this._textAlign;\r\n\t}\r\n\r\n\tsetTextAlign(value) {\r\n\t\tthis.textAlign = value\r\n\t}\r\n\r\n\tset textAlign(value) {\r\n\r\n\t\tthis._textAlign = value;\r\n\t\tlet Align = 0;\r\n\t\tswitch (value) {\r\n\t\t\tcase \"start\":\r\n\t\t\t\tAlign = 0;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"end\":\r\n\t\t\t\tAlign = 1;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"left\":\r\n\t\t\t\tAlign = 2;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"center\":\r\n\t\t\t\tAlign = 3;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"right\":\r\n\t\t\t\tAlign = 4;\r\n\t\t\t\tbreak;\r\n\t\t\tdefault:\r\n\t\t\t\tAlign = 0;\r\n\t\t}\r\n\r\n\t\tthis._drawCommands = this._drawCommands.concat(\"A\" + Align + \";\");\r\n\t}\r\n\r\n\tget textBaseline() {\r\n\t\treturn this._textBaseline;\r\n\t}\r\n\r\n\tsetTextBaseline(value) {\r\n\t\tthis.textBaseline = value\r\n\t}\r\n\r\n\tset textBaseline(value) {\r\n\t\tthis._textBaseline = value;\r\n\t\tlet baseline = 0;\r\n\t\tswitch (value) {\r\n\t\t\tcase \"alphabetic\":\r\n\t\t\t\tbaseline = 0;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"middle\":\r\n\t\t\t\tbaseline = 1;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"top\":\r\n\t\t\t\tbaseline = 2;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"hanging\":\r\n\t\t\t\tbaseline = 3;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"bottom\":\r\n\t\t\t\tbaseline = 4;\r\n\t\t\t\tbreak;\r\n\t\t\tcase \"ideographic\":\r\n\t\t\t\tbaseline = 5;\r\n\t\t\t\tbreak;\r\n\t\t\tdefault:\r\n\t\t\t\tbaseline = 0;\r\n\t\t\t\tbreak;\r\n\t\t}\r\n\r\n\t\tthis._drawCommands = this._drawCommands.concat(\"E\" + baseline + \";\");\r\n\t}\r\n\r\n\tget font() {\r\n\t\treturn this._font;\r\n\t}\r\n\r\n\tsetFontSize(size) {\r\n\t\tvar str = this._font;\r\n\t\tvar strs = str.trim().split(/\\s+/);\r\n\t\tfor (var i = 0; i < strs.length; i++) {\r\n\t\t\tvar values = [\"normal\", \"italic\", \"oblique\", \"normal\", \"small-caps\", \"normal\", \"bold\",\r\n\t\t\t\t\"bolder\", \"lighter\", \"100\", \"200\", \"300\", \"400\", \"500\", \"600\", \"700\", \"800\", \"900\",\r\n\t\t\t\t\"normal\", \"ultra-condensed\", \"extra-condensed\", \"condensed\", \"semi-condensed\",\r\n\t\t\t\t\"semi-expanded\", \"expanded\", \"extra-expanded\", \"ultra-expanded\"\r\n\t\t\t];\r\n\r\n\t\t\tif (-1 == values.indexOf(strs[i].trim())) {\r\n\t\t\t\tif (typeof size === 'string') {\r\n\t\t\t\t\tstrs[i] = size;\r\n\t\t\t\t} else if (typeof size === 'number') {\r\n\t\t\t\t\tstrs[i] = String(size) + 'px';\r\n\t\t\t\t}\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\t\tthis.font = strs.join(\" \");\r\n\t}\r\n\r\n\tset font(value) {\r\n\t\tthis._font = value;\r\n\t\tthis._drawCommands = this._drawCommands.concat(\"j\" + value + \";\");\r\n\t}\r\n\r\n\tsetTransform(a, b, c, d, tx, ty) {\r\n\t\tthis._drawCommands = this._drawCommands.concat(\"t\" +\r\n\t\t\t(a === 1 ? \"1\" : a.toFixed(2)) + \",\" +\r\n\t\t\t(b === 0 ? \"0\" : b.toFixed(2)) + \",\" +\r\n\t\t\t(c === 0 ? \"0\" : c.toFixed(2)) + \",\" +\r\n\t\t\t(d === 1 ? \"1\" : d.toFixed(2)) + \",\" + tx.toFixed(2) + \",\" + ty.toFixed(2) + \";\");\r\n\t}\r\n\r\n\ttransform(a, b, c, d, tx, ty) {\r\n\t\tthis._drawCommands = this._drawCommands.concat(\"f\" +\r\n\t\t\t(a === 1 ? \"1\" : a.toFixed(2)) + \",\" +\r\n\t\t\t(b === 0 ? \"0\" : b.toFixed(2)) + \",\" +\r\n\t\t\t(c === 0 ? \"0\" : c.toFixed(2)) + \",\" +\r\n\t\t\t(d === 1 ? \"1\" : d.toFixed(2)) + \",\" + tx + \",\" + ty + \";\");\r\n\t}\r\n\r\n\tresetTransform() {\r\n\t\tthis._drawCommands = this._drawCommands.concat(\"m;\");\r\n\t}\r\n\r\n\tscale(a, d) {\r\n\t\tthis._drawCommands = this._drawCommands.concat(\"k\" + a.toFixed(2) + \",\" +\r\n\t\t\td.toFixed(2) + \";\");\r\n\t}\r\n\r\n\trotate(angle) {\r\n\t\tthis._drawCommands = this._drawCommands\r\n\t\t\t.concat(\"r\" + angle.toFixed(6) + \";\");\r\n\t}\r\n\r\n\ttranslate(tx, ty) {\r\n\t\tthis._drawCommands = this._drawCommands.concat(\"l\" + tx.toFixed(2) + \",\" + ty.toFixed(2) + \";\");\r\n\t}\r\n\r\n\tsave() {\r\n\t\tthis._savedGlobalAlpha.push(this._globalAlpha);\r\n\t\tthis._drawCommands = this._drawCommands.concat(\"v;\");\r\n\t}\r\n\r\n\trestore() {\r\n\t\tthis._drawCommands = this._drawCommands.concat(\"e;\");\r\n\t\tthis._globalAlpha = this._savedGlobalAlpha.pop();\r\n\t}\r\n\r\n\tcreatePattern(img, pattern) {\r\n\t\tif (typeof img === 'string') {\r\n\t\t\tvar imgObj = new GImage();\r\n\t\t\timgObj.src = img;\r\n\t\t\timg = imgObj;\r\n\t\t}\r\n\t\treturn new FillStylePattern(img, pattern);\r\n\t}\r\n\r\n\tcreateLinearGradient(x0, y0, x1, y1) {\r\n\t\treturn new FillStyleLinearGradient(x0, y0, x1, y1);\r\n\t}\r\n\r\n\tcreateRadialGradient = function(x0, y0, r0, x1, y1, r1) {\r\n\t\treturn new FillStyleRadialGradient(x0, y0, r0, x1, y1, r1);\r\n\t};\r\n\r\n\tcreateCircularGradient = function(x0, y0, r0) {\r\n\t\treturn new FillStyleRadialGradient(x0, y0, 0, x0, y0, r0);\r\n\t};\r\n\r\n\tstrokeRect(x, y, w, h) {\r\n\t\tthis._drawCommands = this._drawCommands.concat(\"s\" + x + \",\" + y + \",\" + w + \",\" + h + \";\");\r\n\t}\r\n\r\n\r\n\tclearRect(x, y, w, h) {\r\n\t\tthis._drawCommands = this._drawCommands.concat(\"c\" + x + \",\" + y + \",\" + w +\r\n\t\t\t\",\" + h + \";\");\r\n\t}\r\n\r\n\tclip() {\r\n\t\tthis._drawCommands = this._drawCommands.concat(\"p;\");\r\n\t}\r\n\r\n\tresetClip() {\r\n\t\tthis._drawCommands = this._drawCommands.concat(\"q;\");\r\n\t}\r\n\r\n\tclosePath() {\r\n\t\tthis._drawCommands = this._drawCommands.concat(\"o;\");\r\n\t}\r\n\r\n\tmoveTo(x, y) {\r\n\t\tthis._drawCommands = this._drawCommands.concat(\"g\" + x.toFixed(2) + \",\" + y.toFixed(2) + \";\");\r\n\t}\r\n\r\n\tlineTo(x, y) {\r\n\t\tthis._drawCommands = this._drawCommands.concat(\"i\" + x.toFixed(2) + \",\" + y.toFixed(2) + \";\");\r\n\t}\r\n\r\n\tquadraticCurveTo = function(cpx, cpy, x, y) {\r\n\t\tthis._drawCommands = this._drawCommands.concat(\"u\" + cpx + \",\" + cpy + \",\" + x + \",\" + y + \";\");\r\n\t}\r\n\r\n\tbezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y, ) {\r\n\t\tthis._drawCommands = this._drawCommands.concat(\r\n\t\t\t\"z\" + cp1x.toFixed(2) + \",\" + cp1y.toFixed(2) + \",\" + cp2x.toFixed(2) + \",\" + cp2y.toFixed(2) + \",\" +\r\n\t\t\tx.toFixed(2) + \",\" + y.toFixed(2) + \";\");\r\n\t}\r\n\r\n\tarcTo(x1, y1, x2, y2, radius) {\r\n\t\tthis._drawCommands = this._drawCommands.concat(\"h\" + x1 + \",\" + y1 + \",\" + x2 + \",\" + y2 + \",\" + radius + \";\");\r\n\t}\r\n\r\n\tbeginPath() {\r\n\t\tthis._drawCommands = this._drawCommands.concat(\"b;\");\r\n\t}\r\n\r\n\r\n\tfillRect(x, y, w, h) {\r\n\t\tthis._drawCommands = this._drawCommands.concat(\"n\" + x + \",\" + y + \",\" + w +\r\n\t\t\t\",\" + h + \";\");\r\n\t}\r\n\r\n\trect(x, y, w, h) {\r\n\t\tthis._drawCommands = this._drawCommands.concat(\"w\" + x + \",\" + y + \",\" + w + \",\" + h + \";\");\r\n\t}\r\n\r\n\tfill() {\r\n\t\tthis._drawCommands = this._drawCommands.concat(\"L;\");\r\n\t}\r\n\r\n\tstroke(path) {\r\n\t\tthis._drawCommands = this._drawCommands.concat(\"x;\");\r\n\t}\r\n\r\n\tarc(x, y, radius, startAngle, endAngle, anticlockwise) {\r\n\r\n\t\tlet ianticlockwise = 0;\r\n\t\tif (anticlockwise) {\r\n\t\t\tianticlockwise = 1;\r\n\t\t}\r\n\r\n\t\tthis._drawCommands = this._drawCommands.concat(\r\n\t\t\t\"y\" + x.toFixed(2) + \",\" + y.toFixed(2) + \",\" +\r\n\t\t\tradius.toFixed(2) + \",\" + startAngle + \",\" + endAngle + \",\" + ianticlockwise +\r\n\t\t\t\";\"\r\n\t\t);\r\n\t}\r\n\r\n\tfillText(text, x, y) {\r\n\t\tlet tmptext = text.replace(/!/g, \"!!\");\r\n\t\ttmptext = tmptext.replace(/,/g, \"!,\");\r\n\t\ttmptext = tmptext.replace(/;/g, \"!;\");\r\n\t\tthis._drawCommands = this._drawCommands.concat(\"T\" + tmptext + \",\" + x + \",\" + y + \",0.0;\");\r\n\t}\r\n\r\n\tstrokeText = function(text, x, y) {\r\n\t\tlet tmptext = text.replace(/!/g, \"!!\");\r\n\t\ttmptext = tmptext.replace(/,/g, \"!,\");\r\n\t\ttmptext = tmptext.replace(/;/g, \"!;\");\r\n\t\tthis._drawCommands = this._drawCommands.concat(\"U\" + tmptext + \",\" + x + \",\" + y + \",0.0;\");\r\n\t}\r\n\r\n\tmeasureText(text) {\r\n\t\treturn CanvasRenderingContext2D.GBridge.measureText(text, this.font, this.componentId);\r\n\t}\r\n\r\n\tisPointInPath = function(x, y) {\r\n\t\tthrow new Error('GCanvas not supported yet');\r\n\t}\r\n\r\n\tdrawImage(image, sx, sy, sw, sh, dx, dy, dw, dh) {\r\n\t\tif (typeof image === 'string') {\r\n\t\t\tvar imgObj = new GImage();\r\n\t\t\timgObj.src = image;\r\n\t\t\timage = imgObj;\r\n\t\t}\r\n\t\tif (image instanceof GImage) {\r\n\t\t\tif (!image.complete) {\r\n\t\t\t\timgObj.onload = () => {\r\n\t\t\t\t\tvar index = this._needRedrawImageCache.indexOf(image);\r\n\t\t\t\t\tif (index > -1) {\r\n\t\t\t\t\t\tthis._needRedrawImageCache.splice(index, 1);\r\n\t\t\t\t\t\tCanvasRenderingContext2D.GBridge.bindImageTexture(this.componentId, image.src, image._id);\r\n\t\t\t\t\t\tthis._redrawflush(true);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tthis._notCommitDrawImageCache.push(image);\r\n\t\t\t} else {\r\n\t\t\t\tCanvasRenderingContext2D.GBridge.bindImageTexture(this.componentId, image.src, image._id);\r\n\t\t\t}\r\n\t\t\tvar srcArgs = [image, sx, sy, sw, sh, dx, dy, dw, dh];\r\n\t\t\tvar args = [];\r\n\t\t\tfor (var arg in srcArgs) {\r\n\t\t\t\tif (typeof(srcArgs[arg]) != 'undefined') {\r\n\t\t\t\t\targs.push(srcArgs[arg]);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tthis.__drawImage.apply(this, args);\r\n\t\t\t//this.__drawImage(image,sx, sy, sw, sh, dx, dy, dw, dh);\r\n\t\t}\r\n\t}\r\n\r\n\t__drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh) {\r\n\t\tconst numArgs = arguments.length;\r\n\r\n\t\tfunction drawImageCommands() {\r\n\r\n\t\t\tif (numArgs === 3) {\r\n\t\t\t\tconst x = parseFloat(sx) || 0.0;\r\n\t\t\t\tconst y = parseFloat(sy) || 0.0;\r\n\r\n\t\t\t\treturn (\"d\" + image._id + \",0,0,\" +\r\n\t\t\t\t\timage.width + \",\" + image.height + \",\" +\r\n\t\t\t\t\tx + \",\" + y + \",\" + image.width + \",\" + image.height + \";\");\r\n\t\t\t} else if (numArgs === 5) {\r\n\t\t\t\tconst x = parseFloat(sx) || 0.0;\r\n\t\t\t\tconst y = parseFloat(sy) || 0.0;\r\n\t\t\t\tconst width = parseInt(sw) || image.width;\r\n\t\t\t\tconst height = parseInt(sh) || image.height;\r\n\r\n\t\t\t\treturn (\"d\" + image._id + \",0,0,\" +\r\n\t\t\t\t\timage.width + \",\" + image.height + \",\" +\r\n\t\t\t\t\tx + \",\" + y + \",\" + width + \",\" + height + \";\");\r\n\t\t\t} else if (numArgs === 9) {\r\n\t\t\t\tsx = parseFloat(sx) || 0.0;\r\n\t\t\t\tsy = parseFloat(sy) || 0.0;\r\n\t\t\t\tsw = parseInt(sw) || image.width;\r\n\t\t\t\tsh = parseInt(sh) || image.height;\r\n\t\t\t\tdx = parseFloat(dx) || 0.0;\r\n\t\t\t\tdy = parseFloat(dy) || 0.0;\r\n\t\t\t\tdw = parseInt(dw) || image.width;\r\n\t\t\t\tdh = parseInt(dh) || image.height;\r\n\r\n\t\t\t\treturn (\"d\" + image._id + \",\" +\r\n\t\t\t\t\tsx + \",\" + sy + \",\" + sw + \",\" + sh + \",\" +\r\n\t\t\t\t\tdx + \",\" + dy + \",\" + dw + \",\" + dh + \";\");\r\n\t\t\t}\r\n\t\t}\r\n\t\tthis._drawCommands += drawImageCommands();\r\n\t}\r\n\r\n\t_flush(reserve, callback) {\r\n\t\tconst commands = this._drawCommands;\r\n\t\tthis._drawCommands = '';\r\n\t\tCanvasRenderingContext2D.GBridge.render2d(this.componentId, commands, callback);\r\n\t\tthis._needRender = false;\r\n\t}\r\n\r\n\t_redrawflush(reserve, callback) {\r\n\t\tconst commands = this._redrawCommands;\r\n\t\tCanvasRenderingContext2D.GBridge.render2d(this.componentId, commands, callback);\r\n\t\tif (this._needRedrawImageCache.length == 0) {\r\n\t\t\tthis._redrawCommands = '';\r\n\t\t}\r\n\t}\r\n\r\n\tdraw(reserve, callback) {\r\n\t\tif (!reserve) {\r\n\t\t\tthis._globalAlpha = this._savedGlobalAlpha.pop();\r\n\t\t\tthis._savedGlobalAlpha.push(this._globalAlpha);\r\n\t\t\tthis._redrawCommands = this._drawCommands;\r\n\t\t\tthis._needRedrawImageCache = this._notCommitDrawImageCache;\r\n\t\t\tif (this._autoSaveContext) {\r\n\t\t\t\tthis._drawCommands = (\"v;\" + this._drawCommands);\r\n\t\t\t\tthis._autoSaveContext = false;\r\n\t\t\t} else {\r\n\t\t\t\tthis._drawCommands = (\"e;X;v;\" + this._drawCommands);\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tthis._needRedrawImageCache = this._needRedrawImageCache.concat(this._notCommitDrawImageCache);\r\n\t\t\tthis._redrawCommands += this._drawCommands;\r\n\t\t\tif (this._autoSaveContext) {\r\n\t\t\t\tthis._drawCommands = (\"v;\" + this._drawCommands);\r\n\t\t\t\tthis._autoSaveContext = false;\r\n\t\t\t}\r\n\t\t}\r\n\t\tthis._notCommitDrawImageCache = [];\r\n\t\tif (this._flush) {\r\n\t\t\tthis._flush(reserve, callback);\r\n\t\t}\r\n\t}\r\n\r\n\tgetImageData(x, y, w, h, callback) {\r\n\t\tCanvasRenderingContext2D.GBridge.getImageData(this.componentId, x, y, w, h, function(res) {\r\n\t\t\tres.data = Base64ToUint8ClampedArray(res.data);\r\n\t\t\tif (typeof(callback) == 'function') {\r\n\t\t\t\tcallback(res);\r\n\t\t\t}\r\n\t\t});\r\n\t}\r\n\r\n\tputImageData(data, x, y, w, h, callback) {\r\n\t\tif (data instanceof Uint8ClampedArray) {\r\n\t\t\tdata = ArrayBufferToBase64(data);\r\n\t\t\tCanvasRenderingContext2D.GBridge.putImageData(this.componentId, data, x, y, w, h, function(res) {\r\n\t\t\t\tif (typeof(callback) == 'function') {\r\n\t\t\t\t\tcallback(res);\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t}\r\n\t}\r\n\r\n\ttoTempFilePath(x, y, width, height, destWidth, destHeight, fileType, quality, callback) {\r\n\t\tCanvasRenderingContext2D.GBridge.toTempFilePath(this.componentId, x, y, width, height, destWidth, destHeight,\r\n\t\t\tfileType, quality,\r\n\t\t\tfunction(res) {\r\n\t\t\t\tif (typeof(callback) == 'function') {\r\n\t\t\t\t\tcallback(res);\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t}\r\n}\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/ActiveInfo.js",
    "content": "export default class WebGLActiveInfo {\r\n    className = 'WebGLActiveInfo';\r\n\r\n    constructor({\r\n        type, name, size\r\n    }) {\r\n        this.type = type;\r\n        this.name = name;\r\n        this.size = size;\r\n    }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/Buffer.js",
    "content": "import {getTransferedObjectUUID} from './classUtils';\r\n\r\nconst name = 'WebGLBuffer';\r\n\r\nfunction uuid(id) {\r\n    return getTransferedObjectUUID(name, id);\r\n}\r\n\r\nexport default class WebGLBuffer {\r\n    className = name;\r\n\r\n    constructor(id) {\r\n        this.id = id;\r\n    }\r\n\r\n    static uuid = uuid;\r\n\r\n    uuid() {\r\n        return uuid(this.id);\r\n    }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/Framebuffer.js",
    "content": "import {getTransferedObjectUUID} from './classUtils';\r\n\r\nconst name = 'WebGLFrameBuffer';\r\n\r\nfunction uuid(id) {\r\n    return getTransferedObjectUUID(name, id);\r\n}\r\n\r\nexport default class WebGLFramebuffer {\r\n    className = name;\r\n\r\n    constructor(id) {\r\n        this.id = id;\r\n    }\r\n\r\n    static uuid = uuid;\r\n\r\n    uuid() {\r\n        return uuid(this.id);\r\n    }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/GLenum.js",
    "content": "export default {\r\n    \"DEPTH_BUFFER_BIT\": 256,\r\n    \"STENCIL_BUFFER_BIT\": 1024,\r\n    \"COLOR_BUFFER_BIT\": 16384,\r\n    \"POINTS\": 0,\r\n    \"LINES\": 1,\r\n    \"LINE_LOOP\": 2,\r\n    \"LINE_STRIP\": 3,\r\n    \"TRIANGLES\": 4,\r\n    \"TRIANGLE_STRIP\": 5,\r\n    \"TRIANGLE_FAN\": 6,\r\n    \"ZERO\": 0,\r\n    \"ONE\": 1,\r\n    \"SRC_COLOR\": 768,\r\n    \"ONE_MINUS_SRC_COLOR\": 769,\r\n    \"SRC_ALPHA\": 770,\r\n    \"ONE_MINUS_SRC_ALPHA\": 771,\r\n    \"DST_ALPHA\": 772,\r\n    \"ONE_MINUS_DST_ALPHA\": 773,\r\n    \"DST_COLOR\": 774,\r\n    \"ONE_MINUS_DST_COLOR\": 775,\r\n    \"SRC_ALPHA_SATURATE\": 776,\r\n    \"FUNC_ADD\": 32774,\r\n    \"BLEND_EQUATION\": 32777,\r\n    \"BLEND_EQUATION_RGB\": 32777,\r\n    \"BLEND_EQUATION_ALPHA\": 34877,\r\n    \"FUNC_SUBTRACT\": 32778,\r\n    \"FUNC_REVERSE_SUBTRACT\": 32779,\r\n    \"BLEND_DST_RGB\": 32968,\r\n    \"BLEND_SRC_RGB\": 32969,\r\n    \"BLEND_DST_ALPHA\": 32970,\r\n    \"BLEND_SRC_ALPHA\": 32971,\r\n    \"CONSTANT_COLOR\": 32769,\r\n    \"ONE_MINUS_CONSTANT_COLOR\": 32770,\r\n    \"CONSTANT_ALPHA\": 32771,\r\n    \"ONE_MINUS_CONSTANT_ALPHA\": 32772,\r\n    \"BLEND_COLOR\": 32773,\r\n    \"ARRAY_BUFFER\": 34962,\r\n    \"ELEMENT_ARRAY_BUFFER\": 34963,\r\n    \"ARRAY_BUFFER_BINDING\": 34964,\r\n    \"ELEMENT_ARRAY_BUFFER_BINDING\": 34965,\r\n    \"STREAM_DRAW\": 35040,\r\n    \"STATIC_DRAW\": 35044,\r\n    \"DYNAMIC_DRAW\": 35048,\r\n    \"BUFFER_SIZE\": 34660,\r\n    \"BUFFER_USAGE\": 34661,\r\n    \"CURRENT_VERTEX_ATTRIB\": 34342,\r\n    \"FRONT\": 1028,\r\n    \"BACK\": 1029,\r\n    \"FRONT_AND_BACK\": 1032,\r\n    \"TEXTURE_2D\": 3553,\r\n    \"CULL_FACE\": 2884,\r\n    \"BLEND\": 3042,\r\n    \"DITHER\": 3024,\r\n    \"STENCIL_TEST\": 2960,\r\n    \"DEPTH_TEST\": 2929,\r\n    \"SCISSOR_TEST\": 3089,\r\n    \"POLYGON_OFFSET_FILL\": 32823,\r\n    \"SAMPLE_ALPHA_TO_COVERAGE\": 32926,\r\n    \"SAMPLE_COVERAGE\": 32928,\r\n    \"NO_ERROR\": 0,\r\n    \"INVALID_ENUM\": 1280,\r\n    \"INVALID_VALUE\": 1281,\r\n    \"INVALID_OPERATION\": 1282,\r\n    \"OUT_OF_MEMORY\": 1285,\r\n    \"CW\": 2304,\r\n    \"CCW\": 2305,\r\n    \"LINE_WIDTH\": 2849,\r\n    \"ALIASED_POINT_SIZE_RANGE\": 33901,\r\n    \"ALIASED_LINE_WIDTH_RANGE\": 33902,\r\n    \"CULL_FACE_MODE\": 2885,\r\n    \"FRONT_FACE\": 2886,\r\n    \"DEPTH_RANGE\": 2928,\r\n    \"DEPTH_WRITEMASK\": 2930,\r\n    \"DEPTH_CLEAR_VALUE\": 2931,\r\n    \"DEPTH_FUNC\": 2932,\r\n    \"STENCIL_CLEAR_VALUE\": 2961,\r\n    \"STENCIL_FUNC\": 2962,\r\n    \"STENCIL_FAIL\": 2964,\r\n    \"STENCIL_PASS_DEPTH_FAIL\": 2965,\r\n    \"STENCIL_PASS_DEPTH_PASS\": 2966,\r\n    \"STENCIL_REF\": 2967,\r\n    \"STENCIL_VALUE_MASK\": 2963,\r\n    \"STENCIL_WRITEMASK\": 2968,\r\n    \"STENCIL_BACK_FUNC\": 34816,\r\n    \"STENCIL_BACK_FAIL\": 34817,\r\n    \"STENCIL_BACK_PASS_DEPTH_FAIL\": 34818,\r\n    \"STENCIL_BACK_PASS_DEPTH_PASS\": 34819,\r\n    \"STENCIL_BACK_REF\": 36003,\r\n    \"STENCIL_BACK_VALUE_MASK\": 36004,\r\n    \"STENCIL_BACK_WRITEMASK\": 36005,\r\n    \"VIEWPORT\": 2978,\r\n    \"SCISSOR_BOX\": 3088,\r\n    \"COLOR_CLEAR_VALUE\": 3106,\r\n    \"COLOR_WRITEMASK\": 3107,\r\n    \"UNPACK_ALIGNMENT\": 3317,\r\n    \"PACK_ALIGNMENT\": 3333,\r\n    \"MAX_TEXTURE_SIZE\": 3379,\r\n    \"MAX_VIEWPORT_DIMS\": 3386,\r\n    \"SUBPIXEL_BITS\": 3408,\r\n    \"RED_BITS\": 3410,\r\n    \"GREEN_BITS\": 3411,\r\n    \"BLUE_BITS\": 3412,\r\n    \"ALPHA_BITS\": 3413,\r\n    \"DEPTH_BITS\": 3414,\r\n    \"STENCIL_BITS\": 3415,\r\n    \"POLYGON_OFFSET_UNITS\": 10752,\r\n    \"POLYGON_OFFSET_FACTOR\": 32824,\r\n    \"TEXTURE_BINDING_2D\": 32873,\r\n    \"SAMPLE_BUFFERS\": 32936,\r\n    \"SAMPLES\": 32937,\r\n    \"SAMPLE_COVERAGE_VALUE\": 32938,\r\n    \"SAMPLE_COVERAGE_INVERT\": 32939,\r\n    \"COMPRESSED_TEXTURE_FORMATS\": 34467,\r\n    \"DONT_CARE\": 4352,\r\n    \"FASTEST\": 4353,\r\n    \"NICEST\": 4354,\r\n    \"GENERATE_MIPMAP_HINT\": 33170,\r\n    \"BYTE\": 5120,\r\n    \"UNSIGNED_BYTE\": 5121,\r\n    \"SHORT\": 5122,\r\n    \"UNSIGNED_SHORT\": 5123,\r\n    \"INT\": 5124,\r\n    \"UNSIGNED_INT\": 5125,\r\n    \"FLOAT\": 5126,\r\n    \"DEPTH_COMPONENT\": 6402,\r\n    \"ALPHA\": 6406,\r\n    \"RGB\": 6407,\r\n    \"RGBA\": 6408,\r\n    \"LUMINANCE\": 6409,\r\n    \"LUMINANCE_ALPHA\": 6410,\r\n    \"UNSIGNED_SHORT_4_4_4_4\": 32819,\r\n    \"UNSIGNED_SHORT_5_5_5_1\": 32820,\r\n    \"UNSIGNED_SHORT_5_6_5\": 33635,\r\n    \"FRAGMENT_SHADER\": 35632,\r\n    \"VERTEX_SHADER\": 35633,\r\n    \"MAX_VERTEX_ATTRIBS\": 34921,\r\n    \"MAX_VERTEX_UNIFORM_VECTORS\": 36347,\r\n    \"MAX_VARYING_VECTORS\": 36348,\r\n    \"MAX_COMBINED_TEXTURE_IMAGE_UNITS\": 35661,\r\n    \"MAX_VERTEX_TEXTURE_IMAGE_UNITS\": 35660,\r\n    \"MAX_TEXTURE_IMAGE_UNITS\": 34930,\r\n    \"MAX_FRAGMENT_UNIFORM_VECTORS\": 36349,\r\n    \"SHADER_TYPE\": 35663,\r\n    \"DELETE_STATUS\": 35712,\r\n    \"LINK_STATUS\": 35714,\r\n    \"VALIDATE_STATUS\": 35715,\r\n    \"ATTACHED_SHADERS\": 35717,\r\n    \"ACTIVE_UNIFORMS\": 35718,\r\n    \"ACTIVE_ATTRIBUTES\": 35721,\r\n    \"SHADING_LANGUAGE_VERSION\": 35724,\r\n    \"CURRENT_PROGRAM\": 35725,\r\n    \"NEVER\": 512,\r\n    \"LESS\": 513,\r\n    \"EQUAL\": 514,\r\n    \"LEQUAL\": 515,\r\n    \"GREATER\": 516,\r\n    \"NOTEQUAL\": 517,\r\n    \"GEQUAL\": 518,\r\n    \"ALWAYS\": 519,\r\n    \"KEEP\": 7680,\r\n    \"REPLACE\": 7681,\r\n    \"INCR\": 7682,\r\n    \"DECR\": 7683,\r\n    \"INVERT\": 5386,\r\n    \"INCR_WRAP\": 34055,\r\n    \"DECR_WRAP\": 34056,\r\n    \"VENDOR\": 7936,\r\n    \"RENDERER\": 7937,\r\n    \"VERSION\": 7938,\r\n    \"NEAREST\": 9728,\r\n    \"LINEAR\": 9729,\r\n    \"NEAREST_MIPMAP_NEAREST\": 9984,\r\n    \"LINEAR_MIPMAP_NEAREST\": 9985,\r\n    \"NEAREST_MIPMAP_LINEAR\": 9986,\r\n    \"LINEAR_MIPMAP_LINEAR\": 9987,\r\n    \"TEXTURE_MAG_FILTER\": 10240,\r\n    \"TEXTURE_MIN_FILTER\": 10241,\r\n    \"TEXTURE_WRAP_S\": 10242,\r\n    \"TEXTURE_WRAP_T\": 10243,\r\n    \"TEXTURE\": 5890,\r\n    \"TEXTURE_CUBE_MAP\": 34067,\r\n    \"TEXTURE_BINDING_CUBE_MAP\": 34068,\r\n    \"TEXTURE_CUBE_MAP_POSITIVE_X\": 34069,\r\n    \"TEXTURE_CUBE_MAP_NEGATIVE_X\": 34070,\r\n    \"TEXTURE_CUBE_MAP_POSITIVE_Y\": 34071,\r\n    \"TEXTURE_CUBE_MAP_NEGATIVE_Y\": 34072,\r\n    \"TEXTURE_CUBE_MAP_POSITIVE_Z\": 34073,\r\n    \"TEXTURE_CUBE_MAP_NEGATIVE_Z\": 34074,\r\n    \"MAX_CUBE_MAP_TEXTURE_SIZE\": 34076,\r\n    \"TEXTURE0\": 33984,\r\n    \"TEXTURE1\": 33985,\r\n    \"TEXTURE2\": 33986,\r\n    \"TEXTURE3\": 33987,\r\n    \"TEXTURE4\": 33988,\r\n    \"TEXTURE5\": 33989,\r\n    \"TEXTURE6\": 33990,\r\n    \"TEXTURE7\": 33991,\r\n    \"TEXTURE8\": 33992,\r\n    \"TEXTURE9\": 33993,\r\n    \"TEXTURE10\": 33994,\r\n    \"TEXTURE11\": 33995,\r\n    \"TEXTURE12\": 33996,\r\n    \"TEXTURE13\": 33997,\r\n    \"TEXTURE14\": 33998,\r\n    \"TEXTURE15\": 33999,\r\n    \"TEXTURE16\": 34000,\r\n    \"TEXTURE17\": 34001,\r\n    \"TEXTURE18\": 34002,\r\n    \"TEXTURE19\": 34003,\r\n    \"TEXTURE20\": 34004,\r\n    \"TEXTURE21\": 34005,\r\n    \"TEXTURE22\": 34006,\r\n    \"TEXTURE23\": 34007,\r\n    \"TEXTURE24\": 34008,\r\n    \"TEXTURE25\": 34009,\r\n    \"TEXTURE26\": 34010,\r\n    \"TEXTURE27\": 34011,\r\n    \"TEXTURE28\": 34012,\r\n    \"TEXTURE29\": 34013,\r\n    \"TEXTURE30\": 34014,\r\n    \"TEXTURE31\": 34015,\r\n    \"ACTIVE_TEXTURE\": 34016,\r\n    \"REPEAT\": 10497,\r\n    \"CLAMP_TO_EDGE\": 33071,\r\n    \"MIRRORED_REPEAT\": 33648,\r\n    \"FLOAT_VEC2\": 35664,\r\n    \"FLOAT_VEC3\": 35665,\r\n    \"FLOAT_VEC4\": 35666,\r\n    \"INT_VEC2\": 35667,\r\n    \"INT_VEC3\": 35668,\r\n    \"INT_VEC4\": 35669,\r\n    \"BOOL\": 35670,\r\n    \"BOOL_VEC2\": 35671,\r\n    \"BOOL_VEC3\": 35672,\r\n    \"BOOL_VEC4\": 35673,\r\n    \"FLOAT_MAT2\": 35674,\r\n    \"FLOAT_MAT3\": 35675,\r\n    \"FLOAT_MAT4\": 35676,\r\n    \"SAMPLER_2D\": 35678,\r\n    \"SAMPLER_CUBE\": 35680,\r\n    \"VERTEX_ATTRIB_ARRAY_ENABLED\": 34338,\r\n    \"VERTEX_ATTRIB_ARRAY_SIZE\": 34339,\r\n    \"VERTEX_ATTRIB_ARRAY_STRIDE\": 34340,\r\n    \"VERTEX_ATTRIB_ARRAY_TYPE\": 34341,\r\n    \"VERTEX_ATTRIB_ARRAY_NORMALIZED\": 34922,\r\n    \"VERTEX_ATTRIB_ARRAY_POINTER\": 34373,\r\n    \"VERTEX_ATTRIB_ARRAY_BUFFER_BINDING\": 34975,\r\n    \"IMPLEMENTATION_COLOR_READ_TYPE\": 35738,\r\n    \"IMPLEMENTATION_COLOR_READ_FORMAT\": 35739,\r\n    \"COMPILE_STATUS\": 35713,\r\n    \"LOW_FLOAT\": 36336,\r\n    \"MEDIUM_FLOAT\": 36337,\r\n    \"HIGH_FLOAT\": 36338,\r\n    \"LOW_INT\": 36339,\r\n    \"MEDIUM_INT\": 36340,\r\n    \"HIGH_INT\": 36341,\r\n    \"FRAMEBUFFER\": 36160,\r\n    \"RENDERBUFFER\": 36161,\r\n    \"RGBA4\": 32854,\r\n    \"RGB5_A1\": 32855,\r\n    \"RGB565\": 36194,\r\n    \"DEPTH_COMPONENT16\": 33189,\r\n    \"STENCIL_INDEX8\": 36168,\r\n    \"DEPTH_STENCIL\": 34041,\r\n    \"RENDERBUFFER_WIDTH\": 36162,\r\n    \"RENDERBUFFER_HEIGHT\": 36163,\r\n    \"RENDERBUFFER_INTERNAL_FORMAT\": 36164,\r\n    \"RENDERBUFFER_RED_SIZE\": 36176,\r\n    \"RENDERBUFFER_GREEN_SIZE\": 36177,\r\n    \"RENDERBUFFER_BLUE_SIZE\": 36178,\r\n    \"RENDERBUFFER_ALPHA_SIZE\": 36179,\r\n    \"RENDERBUFFER_DEPTH_SIZE\": 36180,\r\n    \"RENDERBUFFER_STENCIL_SIZE\": 36181,\r\n    \"FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE\": 36048,\r\n    \"FRAMEBUFFER_ATTACHMENT_OBJECT_NAME\": 36049,\r\n    \"FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL\": 36050,\r\n    \"FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE\": 36051,\r\n    \"COLOR_ATTACHMENT0\": 36064,\r\n    \"DEPTH_ATTACHMENT\": 36096,\r\n    \"STENCIL_ATTACHMENT\": 36128,\r\n    \"DEPTH_STENCIL_ATTACHMENT\": 33306,\r\n    \"NONE\": 0,\r\n    \"FRAMEBUFFER_COMPLETE\": 36053,\r\n    \"FRAMEBUFFER_INCOMPLETE_ATTACHMENT\": 36054,\r\n    \"FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT\": 36055,\r\n    \"FRAMEBUFFER_INCOMPLETE_DIMENSIONS\": 36057,\r\n    \"FRAMEBUFFER_UNSUPPORTED\": 36061,\r\n    \"FRAMEBUFFER_BINDING\": 36006,\r\n    \"RENDERBUFFER_BINDING\": 36007,\r\n    \"MAX_RENDERBUFFER_SIZE\": 34024,\r\n    \"INVALID_FRAMEBUFFER_OPERATION\": 1286,\r\n    \"UNPACK_FLIP_Y_WEBGL\": 37440,\r\n    \"UNPACK_PREMULTIPLY_ALPHA_WEBGL\": 37441,\r\n    \"CONTEXT_LOST_WEBGL\": 37442,\r\n    \"UNPACK_COLORSPACE_CONVERSION_WEBGL\": 37443,\r\n    \"BROWSER_DEFAULT_WEBGL\": 37444\r\n};"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/GLmethod.js",
    "content": "let i = 1;\r\n\r\nconst GLmethod = {};\r\n\r\nGLmethod.activeTexture = i++;         //1\r\nGLmethod.attachShader = i++;\r\nGLmethod.bindAttribLocation = i++;\r\nGLmethod.bindBuffer = i++;\r\nGLmethod.bindFramebuffer = i++;\r\nGLmethod.bindRenderbuffer = i++;\r\nGLmethod.bindTexture = i++;\r\nGLmethod.blendColor = i++;\r\nGLmethod.blendEquation = i++;\r\nGLmethod.blendEquationSeparate = i++; //10\r\nGLmethod.blendFunc = i++;\r\nGLmethod.blendFuncSeparate = i++;\r\nGLmethod.bufferData = i++;\r\nGLmethod.bufferSubData = i++;\r\nGLmethod.checkFramebufferStatus = i++;\r\nGLmethod.clear = i++;\r\nGLmethod.clearColor = i++;\r\nGLmethod.clearDepth = i++;\r\nGLmethod.clearStencil = i++;\r\nGLmethod.colorMask = i++;              //20\r\nGLmethod.compileShader = i++;\r\nGLmethod.compressedTexImage2D = i++;\r\nGLmethod.compressedTexSubImage2D = i++;\r\nGLmethod.copyTexImage2D = i++;\r\nGLmethod.copyTexSubImage2D = i++;\r\nGLmethod.createBuffer = i++;\r\nGLmethod.createFramebuffer = i++;\r\nGLmethod.createProgram = i++;\r\nGLmethod.createRenderbuffer = i++;\r\nGLmethod.createShader = i++;           //30\r\nGLmethod.createTexture = i++;\r\nGLmethod.cullFace = i++;\r\nGLmethod.deleteBuffer = i++;\r\nGLmethod.deleteFramebuffer = i++;\r\nGLmethod.deleteProgram = i++;\r\nGLmethod.deleteRenderbuffer = i++;\r\nGLmethod.deleteShader = i++;\r\nGLmethod.deleteTexture = i++;\r\nGLmethod.depthFunc = i++;\r\nGLmethod.depthMask = i++;              //40\r\nGLmethod.depthRange = i++;\r\nGLmethod.detachShader = i++;\r\nGLmethod.disable = i++;\r\nGLmethod.disableVertexAttribArray = i++;\r\nGLmethod.drawArrays = i++;\r\nGLmethod.drawArraysInstancedANGLE = i++;\r\nGLmethod.drawElements = i++;\r\nGLmethod.drawElementsInstancedANGLE = i++;\r\nGLmethod.enable = i++;\r\nGLmethod.enableVertexAttribArray = i++;    //50\r\nGLmethod.flush = i++;\r\nGLmethod.framebufferRenderbuffer = i++;\r\nGLmethod.framebufferTexture2D = i++;\r\nGLmethod.frontFace = i++;\r\nGLmethod.generateMipmap = i++;\r\nGLmethod.getActiveAttrib = i++;\r\nGLmethod.getActiveUniform = i++;\r\nGLmethod.getAttachedShaders = i++;\r\nGLmethod.getAttribLocation = i++;\r\nGLmethod.getBufferParameter = i++;         //60\r\nGLmethod.getContextAttributes = i++;\r\nGLmethod.getError = i++;\r\nGLmethod.getExtension = i++;\r\nGLmethod.getFramebufferAttachmentParameter = i++;\r\nGLmethod.getParameter = i++;\r\nGLmethod.getProgramInfoLog = i++;\r\nGLmethod.getProgramParameter = i++;\r\nGLmethod.getRenderbufferParameter = i++;\r\nGLmethod.getShaderInfoLog = i++;\r\nGLmethod.getShaderParameter = i++;         //70\r\nGLmethod.getShaderPrecisionFormat = i++;\r\nGLmethod.getShaderSource = i++;\r\nGLmethod.getSupportedExtensions = i++;\r\nGLmethod.getTexParameter = i++;\r\nGLmethod.getUniform = i++;\r\nGLmethod.getUniformLocation = i++;\r\nGLmethod.getVertexAttrib = i++;\r\nGLmethod.getVertexAttribOffset = i++;\r\nGLmethod.isBuffer = i++;\r\nGLmethod.isContextLost = i++;              //80\r\nGLmethod.isEnabled = i++;\r\nGLmethod.isFramebuffer = i++;\r\nGLmethod.isProgram = i++;\r\nGLmethod.isRenderbuffer = i++;\r\nGLmethod.isShader = i++;\r\nGLmethod.isTexture = i++;\r\nGLmethod.lineWidth = i++;\r\nGLmethod.linkProgram = i++;\r\nGLmethod.pixelStorei = i++;\r\nGLmethod.polygonOffset = i++;              //90\r\nGLmethod.readPixels = i++;\r\nGLmethod.renderbufferStorage = i++;\r\nGLmethod.sampleCoverage = i++;\r\nGLmethod.scissor = i++;\r\nGLmethod.shaderSource = i++;\r\nGLmethod.stencilFunc = i++;\r\nGLmethod.stencilFuncSeparate = i++;\r\nGLmethod.stencilMask = i++;\r\nGLmethod.stencilMaskSeparate = i++;\r\nGLmethod.stencilOp = i++;                  //100\r\nGLmethod.stencilOpSeparate = i++;\r\nGLmethod.texImage2D = i++;\r\nGLmethod.texParameterf = i++;\r\nGLmethod.texParameteri = i++;\r\nGLmethod.texSubImage2D = i++;\r\nGLmethod.uniform1f = i++;\r\nGLmethod.uniform1fv = i++;\r\nGLmethod.uniform1i = i++;\r\nGLmethod.uniform1iv = i++;\r\nGLmethod.uniform2f = i++;                  //110\r\nGLmethod.uniform2fv = i++;\r\nGLmethod.uniform2i = i++;\r\nGLmethod.uniform2iv = i++;\r\nGLmethod.uniform3f = i++;\r\nGLmethod.uniform3fv = i++;\r\nGLmethod.uniform3i = i++;\r\nGLmethod.uniform3iv = i++;\r\nGLmethod.uniform4f = i++;\r\nGLmethod.uniform4fv = i++;\r\nGLmethod.uniform4i = i++;                  //120\r\nGLmethod.uniform4iv = i++;\r\nGLmethod.uniformMatrix2fv = i++;\r\nGLmethod.uniformMatrix3fv = i++;\r\nGLmethod.uniformMatrix4fv = i++;\r\nGLmethod.useProgram = i++;\r\nGLmethod.validateProgram = i++;\r\nGLmethod.vertexAttrib1f = i++; //new\r\nGLmethod.vertexAttrib2f = i++; //new\r\nGLmethod.vertexAttrib3f = i++; //new\r\nGLmethod.vertexAttrib4f = i++; //new       //130\r\nGLmethod.vertexAttrib1fv = i++; //new\r\nGLmethod.vertexAttrib2fv = i++; //new\r\nGLmethod.vertexAttrib3fv = i++; //new\r\nGLmethod.vertexAttrib4fv = i++; //new\r\nGLmethod.vertexAttribPointer = i++;\r\nGLmethod.viewport = i++;\r\n\r\nexport default GLmethod;"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/GLtype.js",
    "content": "const GLtype = {};\r\n\r\n[\r\n    \"GLbitfield\",    \r\n    \"GLboolean\",\r\n    \"GLbyte\",\r\n    \"GLclampf\",\r\n    \"GLenum\",\r\n    \"GLfloat\",\r\n    \"GLint\",\r\n    \"GLintptr\",\r\n    \"GLsizei\",\r\n    \"GLsizeiptr\",\r\n    \"GLshort\",\r\n    \"GLubyte\",\r\n    \"GLuint\",\r\n    \"GLushort\"\r\n].sort().map((typeName, i) => GLtype[typeName] = 1 >> (i + 1));\r\n\r\nexport default GLtype;\r\n\r\n\r\n\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/Program.js",
    "content": "import {getTransferedObjectUUID} from './classUtils';\r\n\r\nconst name = 'WebGLProgram';\r\n\r\nfunction uuid(id) {\r\n    return getTransferedObjectUUID(name, id);\r\n}\r\n\r\nexport default class WebGLProgram {\r\n    className = name;\r\n\r\n    constructor(id) {\r\n        this.id = id;\r\n    }\r\n\r\n    static uuid = uuid;\r\n\r\n    uuid() {\r\n        return uuid(this.id);\r\n    }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/Renderbuffer.js",
    "content": "import {getTransferedObjectUUID} from './classUtils';\r\n\r\nconst name = 'WebGLRenderBuffer';\r\n\r\nfunction uuid(id) {\r\n    return getTransferedObjectUUID(name, id);\r\n}\r\n\r\nexport default class WebGLRenderbuffer {\r\n    className = name;\r\n\r\n    constructor(id) {\r\n        this.id = id;\r\n    }\r\n\r\n    static uuid = uuid;\r\n\r\n    uuid() {\r\n        return uuid(this.id);\r\n    }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/RenderingContext.js",
    "content": "import GLenum from './GLenum';\r\nimport ActiveInfo from './ActiveInfo';\r\nimport Buffer from './Buffer';\r\nimport Framebuffer from './Framebuffer';\r\nimport Renderbuffer from './Renderbuffer';\r\nimport Texture from './Texture';\r\nimport Program from './Program';\r\nimport Shader from './Shader';\r\nimport ShaderPrecisionFormat from './ShaderPrecisionFormat';\r\nimport UniformLocation from './UniformLocation';\r\nimport GLmethod from './GLmethod';\r\n\r\nconst processArray = (array, checkArrayType = false) => {\r\n\r\n    function joinArray(arr, sep) {\r\n        let res = '';\r\n        for (let i = 0; i < arr.length; i++) {\r\n            if (i !== 0) {\r\n                res += sep;\r\n            }\r\n            res += arr[i];\r\n        }\r\n        return res;\r\n    }\r\n\r\n    let type = 'Float32Array';\r\n    if (checkArrayType) {\r\n        if (array instanceof Uint8Array) {\r\n            type = 'Uint8Array'\r\n        } else if (array instanceof Uint16Array) {\r\n            type = 'Uint16Array';\r\n        } else if (array instanceof Uint32Array) {\r\n            type = 'Uint32Array';\r\n        } else if (array instanceof Float32Array) {\r\n            type = 'Float32Array';\r\n        } else {\r\n            throw new Error('Check array type failed. Array type is ' + typeof array);\r\n        }\r\n    }\r\n\r\n    const ArrayTypes = {\r\n        Uint8Array: 1,\r\n        Uint16Array: 2,\r\n        Uint32Array: 4,\r\n        Float32Array: 14\r\n    };\r\n    return ArrayTypes[type] + ',' + btoa(joinArray(array, ','))\r\n}\r\n\r\nexport default class WebGLRenderingContext {\r\n\r\n    // static GBridge = null;\r\n\r\n    className = 'WebGLRenderingContext';\r\n\r\n    constructor(canvas, type, attrs) {\r\n        this._canvas = canvas;\r\n        this._type = type;\r\n        this._version = 'WebGL 1.0';\r\n        this._attrs = attrs;\r\n        this._map = new Map();\r\n\r\n        Object.keys(GLenum)\r\n            .forEach(name => Object.defineProperty(this, name, {\r\n                value: GLenum[name]\r\n            }));\r\n    }\r\n\r\n    get canvas() {\r\n        return this._canvas;\r\n    }\r\n\r\n    activeTexture = function (textureUnit) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.activeTexture + ',' + textureUnit,\r\n            true\r\n        );\r\n    }\r\n\r\n    attachShader = function (progarm, shader) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.attachShader + ',' + progarm.id + ',' + shader.id,\r\n            true\r\n        );\r\n    }\r\n\r\n    bindAttribLocation = function (program, index, name) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.bindAttribLocation + ',' + program.id + ',' + index + ',' + name,\r\n            true\r\n        )\r\n    }\r\n\r\n    bindBuffer = function (target, buffer) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.bindBuffer + ',' + target + ',' + (buffer ? buffer.id : 0),\r\n            true\r\n        );\r\n    }\r\n\r\n    bindFramebuffer = function (target, framebuffer) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.bindFramebuffer + ',' + target + ',' + (framebuffer ? framebuffer.id : 0),\r\n            true\r\n        )\r\n    }\r\n\r\n    bindRenderbuffer = function (target, renderBuffer) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.bindRenderbuffer + ',' + target + ',' + (renderBuffer ? renderBuffer.id : 0),\r\n            true\r\n        )\r\n    }\r\n\r\n    bindTexture = function (target, texture) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.bindTexture + ',' + target + ',' + (texture ? texture.id : 0),\r\n            true\r\n        )\r\n    }\r\n\r\n    blendColor = function (r, g, b, a) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.blendColor + ',' + target + ',' + r + ',' + g + ',' + b + ',' + a,\r\n            true\r\n        )\r\n    }\r\n\r\n    blendEquation = function (mode) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.blendEquation + ',' + mode,\r\n            true\r\n        )\r\n    }\r\n\r\n    blendEquationSeparate = function (modeRGB, modeAlpha) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.blendEquationSeparate + ',' + modeRGB + ',' + modeAlpha,\r\n            true\r\n        )\r\n    }\r\n\r\n\r\n    blendFunc = function (sfactor, dfactor) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.blendFunc + ',' + sfactor + ',' + dfactor,\r\n            true\r\n        );\r\n    }\r\n\r\n    blendFuncSeparate = function (srcRGB, dstRGB, srcAlpha, dstAlpha) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.blendFuncSeparate + ',' + srcRGB + ',' + dstRGB + ',' + srcAlpha + ',' + dstAlpha,\r\n            true\r\n        );\r\n    }\r\n\r\n    bufferData = function (target, data, usage) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.bufferData + ',' + target + ',' + processArray(data, true) + ',' + usage,\r\n            true\r\n        )\r\n    }\r\n\r\n    bufferSubData = function (target, offset, data) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.bufferSubData + ',' + target + ',' + offset + ',' + processArray(data, true),\r\n            true\r\n        )\r\n    }\r\n\r\n    checkFramebufferStatus = function (target) {\r\n        const result = WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.checkFramebufferStatus + ',' + target\r\n        );\r\n        return Number(result);\r\n    }\r\n\r\n    clear = function (mask) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.clear + ',' + mask\r\n        );\r\n        this._canvas._needRender = true;\r\n    }\r\n\r\n    clearColor = function (r, g, b, a) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.clearColor + ',' + r + ',' + g + ',' + b,\r\n            true\r\n        )\r\n    }\r\n\r\n    clearDepth = function (depth) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.clearDepth + ',' + depth,\r\n            true\r\n        )\r\n    }\r\n\r\n    clearStencil = function (s) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.clearStencil + ',' + s\r\n        );\r\n    }\r\n\r\n    colorMask = function (r, g, b, a) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.colorMask + ',' + r + ',' + g + ',' + b + ',' + a\r\n        )\r\n    }\r\n\r\n    compileShader = function (shader) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.compileShader + ',' + shader.id,\r\n            true\r\n        )\r\n    }\r\n\r\n    compressedTexImage2D = function (target, level, internalformat, width, height, border, pixels) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.compressedTexImage2D + ',' + target + ',' + level + ',' + internalformat + ',' +\r\n            width + ',' + height + ',' + border + ',' + processArray(pixels),\r\n            true\r\n        )\r\n    }\r\n\r\n    compressedTexSubImage2D = function (target, level, xoffset, yoffset, width, height, format, pixels) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.compressedTexSubImage2D + ',' + target + ',' + level + ',' + xoffset + ',' + yoffset + ',' +\r\n            width + ',' + height + ',' + format + ',' + processArray(pixels),\r\n            true\r\n        )\r\n    }\r\n\r\n\r\n    copyTexImage2D = function (target, level, internalformat, x, y, width, height, border) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.copyTexImage2D + ',' + target + ',' + level + ',' + internalformat + ',' + x + ',' + y + ',' +\r\n            width + ',' + height + ',' + border,\r\n            true\r\n        );\r\n    }\r\n\r\n    copyTexSubImage2D = function (target, level, xoffset, yoffset, x, y, width, height) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.copyTexSubImage2D + ',' + target + ',' + level + ',' + xoffset + ',' + yoffset + ',' + x + ',' + y + ',' +\r\n            width + ',' + height\r\n        );\r\n    }\r\n\r\n    createBuffer = function () {\r\n        const result = WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.createBuffer + ''\r\n        );\r\n        const buffer = new Buffer(result);\r\n        this._map.set(buffer.uuid(), buffer);\r\n        return buffer;\r\n    }\r\n\r\n    createFramebuffer = function () {\r\n        const result = WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.createFramebuffer + ''\r\n        );\r\n        const framebuffer = new Framebuffer(result);\r\n        this._map.set(framebuffer.uuid(), framebuffer);\r\n        return framebuffer;\r\n    }\r\n\r\n\r\n    createProgram = function () {\r\n        const id = WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.createProgram + ''\r\n        );\r\n        const program = new Program(id);\r\n        this._map.set(program.uuid(), program);\r\n        return program;\r\n    }\r\n\r\n    createRenderbuffer = function () {\r\n        const id = WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.createRenderbuffer + ''\r\n        )\r\n        const renderBuffer = new Renderbuffer(id);\r\n        this._map.set(renderBuffer.uuid(), renderBuffer);\r\n        return renderBuffer;\r\n    }\r\n\r\n    createShader = function (type) {\r\n        const id = WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.createShader + ',' + type\r\n        )\r\n        const shader = new Shader(id, type);\r\n        this._map.set(shader.uuid(), shader);\r\n        return shader;\r\n    }\r\n\r\n    createTexture = function () {\r\n        const id = WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.createTexture + ''\r\n        );\r\n        const texture = new Texture(id);\r\n        this._map.set(texture.uuid(), texture);\r\n        return texture;\r\n    }\r\n\r\n    cullFace = function (mode) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.cullFace + ',' + mode,\r\n            true\r\n        )\r\n    }\r\n\r\n\r\n    deleteBuffer = function (buffer) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.deleteBuffer + ',' + buffer.id,\r\n            true\r\n        )\r\n    }\r\n\r\n    deleteFramebuffer = function (framebuffer) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.deleteFramebuffer + ',' + framebuffer.id,\r\n            true\r\n        )\r\n    }\r\n\r\n    deleteProgram = function (program) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.deleteProgram + ',' + program.id,\r\n            true\r\n        )\r\n    }\r\n\r\n    deleteRenderbuffer = function (renderbuffer) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.deleteRenderbuffer + ',' + renderbuffer.id,\r\n            true\r\n        )\r\n    }\r\n\r\n    deleteShader = function (shader) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.deleteShader + ',' + shader.id,\r\n            true\r\n        )\r\n    }\r\n\r\n    deleteTexture = function (texture) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.deleteTexture + ',' + texture.id,\r\n            true\r\n        )\r\n    }\r\n\r\n    depthFunc = function (func) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.depthFunc + ',' + func\r\n        )\r\n    }\r\n\r\n    depthMask = function (flag) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.depthMask + ',' + Number(flag),\r\n            true\r\n        )\r\n    }\r\n\r\n    depthRange = function (zNear, zFar) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.depthRange + ',' + zNear + ',' + zFar,\r\n            true\r\n        )\r\n    }\r\n\r\n    detachShader = function (program, shader) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.detachShader + ',' + program.id + ',' + shader.id,\r\n            true\r\n        )\r\n    }\r\n\r\n    disable = function (cap) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.disable + ',' + cap,\r\n            true\r\n        )\r\n    }\r\n\r\n    disableVertexAttribArray = function (index) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.disableVertexAttribArray + ',' + index,\r\n            true\r\n        );\r\n    }\r\n\r\n    drawArrays = function (mode, first, count) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.drawArrays + ',' + mode + ',' + first + ',' + count\r\n        )\r\n        this._canvas._needRender = true;\r\n    }\r\n\r\n    drawElements = function (mode, count, type, offset) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.drawElements + ',' + mode + ',' + count + ',' + type + ',' + offset + ';'\r\n        );\r\n        this._canvas._needRender = true;\r\n    }\r\n\r\n    enable = function (cap) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.enable + ',' + cap,\r\n            true\r\n        );\r\n    }\r\n\r\n    enableVertexAttribArray = function (index) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.enableVertexAttribArray + ',' + index,\r\n            true\r\n        )\r\n    }\r\n\r\n\r\n    flush = function () {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.flush + ''\r\n        )\r\n    }\r\n\r\n    framebufferRenderbuffer = function (target, attachment, textarget, texture, level) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.framebufferRenderbuffer + ',' + target + ',' + attachment + ',' + textarget + ',' + (texture ? texture.id : 0) + ',' + level,\r\n            true\r\n        )\r\n    }\r\n\r\n    framebufferTexture2D = function (target, attachment, textarget, texture, level) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.framebufferTexture2D + ',' + target + ',' + attachment + ',' + textarget + ',' + (texture ? texture.id : 0) + ',' + level,\r\n            true\r\n        )\r\n    }\r\n\r\n    frontFace = function (mode) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.frontFace + ',' + mode,\r\n            true\r\n        )\r\n    }\r\n\r\n    generateMipmap = function (target) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.generateMipmap + ',' + target,\r\n            true\r\n        )\r\n    }\r\n\r\n    getActiveAttrib = function (progarm, index) {\r\n        const resultString = WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.getActiveAttrib + ',' + progarm.id + ',' + index\r\n        )\r\n        const [type, size, name] = resultString.split(',');\r\n        return new ActiveInfo({\r\n            type: Number(type),\r\n            size: Number(size),\r\n            name\r\n        });\r\n    }\r\n\r\n    getActiveUniform = function (progarm, index) {\r\n        const resultString = WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.getActiveUniform + ',' + progarm.id + ',' + index\r\n        );\r\n        const [type, size, name] = resultString.split(',');\r\n        return new ActiveInfo({\r\n            type: Number(type),\r\n            size: Number(size),\r\n            name\r\n        })\r\n    }\r\n\r\n    getAttachedShaders = function (progarm) {\r\n        const result = WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.getAttachedShaders + ',' + progarm.id\r\n        );\r\n        const [type, ...ids] = result;\r\n        return ids.map(id => this._map.get(Shader.uuid(id)));\r\n    }\r\n\r\n    getAttribLocation = function (progarm, name) {\r\n        return WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.getAttribLocation + ',' + progarm.id + ',' + name\r\n        )\r\n    }\r\n\r\n    getBufferParameter = function (target, pname) {\r\n        const result = WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.getBufferParameter + ',' + target + ',' + pname\r\n        );\r\n        const [type, res] = getBufferParameter;\r\n        return res;\r\n    }\r\n\r\n    getError = function () {\r\n        const result = WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.getError + ''\r\n        )\r\n        return result;\r\n    }\r\n\r\n    getExtension = function (name) {\r\n        return null;\r\n    }\r\n\r\n    getFramebufferAttachmentParameter = function (target, attachment, pname) {\r\n        const result = WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.getFramebufferAttachmentParameter + ',' + target + ',' + attachment + ',' + pname\r\n        )\r\n        switch (pname) {\r\n            case GLenum.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME:\r\n                return this._map.get(Renderbuffer.uuid(result)) || this._map.get(Texture.uuid(result)) || null;\r\n            default:\r\n                return result;\r\n        }\r\n    }\r\n\r\n    getParameter = function (pname) {\r\n        const result = WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.getParameter + ',' + pname\r\n        )\r\n        switch (pname) {\r\n            case GLenum.VERSION:\r\n                return this._version;\r\n            case GLenum.ARRAY_BUFFER_BINDING: // buffer\r\n            case GLenum.ELEMENT_ARRAY_BUFFER_BINDING: // buffer\r\n                return this._map.get(Buffer.uuid(result)) || null;\r\n            case GLenum.CURRENT_PROGRAM: // program\r\n                return this._map.get(Program.uuid(result)) || null;\r\n            case GLenum.FRAMEBUFFER_BINDING: // framebuffer\r\n                return this._map.get(Framebuffer.uuid(result)) || null;\r\n            case GLenum.RENDERBUFFER_BINDING: // renderbuffer\r\n                return this._map.get(Renderbuffer.uuid(result)) || null;\r\n            case GLenum.TEXTURE_BINDING_2D: // texture\r\n            case GLenum.TEXTURE_BINDING_CUBE_MAP: // texture\r\n                return this._map.get(Texture.uuid(result)) || null;\r\n            case GLenum.ALIASED_LINE_WIDTH_RANGE: // Float32Array\r\n            case GLenum.ALIASED_POINT_SIZE_RANGE: // Float32Array\r\n            case GLenum.BLEND_COLOR: // Float32Array\r\n            case GLenum.COLOR_CLEAR_VALUE: // Float32Array\r\n            case GLenum.DEPTH_RANGE: // Float32Array\r\n            case GLenum.MAX_VIEWPORT_DIMS: // Int32Array\r\n            case GLenum.SCISSOR_BOX: // Int32Array\r\n            case GLenum.VIEWPORT: // Int32Array            \r\n            case GLenum.COMPRESSED_TEXTURE_FORMATS: // Uint32Array\r\n            default:\r\n                const [type, ...res] = result.split(',');\r\n                if (res.length === 1) {\r\n                    return Number(res[0]);\r\n                } else {\r\n                    return res.map(Number);\r\n                }\r\n        }\r\n    }\r\n\r\n    getProgramInfoLog = function (progarm) {\r\n        return WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.getProgramInfoLog + ',' + progarm.id\r\n        )\r\n    }\r\n\r\n    getProgramParameter = function (program, pname) {\r\n        const res = WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.getProgramParameter + ',' + program.id + ',' + pname\r\n        );\r\n\r\n        const [type, result] = res.split(',').map(i => parseInt(i));\r\n\r\n        if (type === 1) {\r\n            return Boolean(result);\r\n        } else if (type === 2) {\r\n            return result;\r\n        } else {\r\n            throw new Error('Unrecongized program paramater ' + res + ', type: ' + typeof res);\r\n        }\r\n    }\r\n\r\n\r\n    getRenderbufferParameter = function (target, pname) {\r\n        const result = WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.getRenderbufferParameter + ',' + target + ',' + pname\r\n        )\r\n        return result;\r\n    }\r\n\r\n\r\n    getShaderInfoLog = function (shader) {\r\n        return WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.getShaderInfoLog + ',' + shader.id\r\n        );\r\n    }\r\n\r\n    getShaderParameter = function (shader, pname) {\r\n        return WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.getShaderParameter + ',' + shader.id + ',' + pname\r\n        )\r\n    }\r\n\r\n    getShaderPrecisionFormat = function (shaderType, precisionType) {\r\n        const [rangeMin, rangeMax, precision] = WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.getShaderPrecisionFormat + ',' + shaderType + ',' + precisionType\r\n        );\r\n        const shaderPrecisionFormat = new ShaderPrecisionFormat({\r\n            rangeMin: Number(rangeMin),\r\n            rangeMax: Number(rangeMax),\r\n            precision: Number(precision)\r\n        });\r\n        return shaderPrecisionFormat;\r\n    }\r\n\r\n    getShaderSource = function (shader) {\r\n        const result = WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.getShaderSource + ',' + shader.id\r\n        );\r\n        return result;\r\n    }\r\n\r\n    getSupportedExtensions = function () {\r\n        return Object.keys({});\r\n    }\r\n\r\n    getTexParameter = function (target, pname) {\r\n        const result = WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.getTexParameter + ',' + target + ',' + pname\r\n        )\r\n        return result;\r\n    }\r\n\r\n    getUniformLocation = function (program, name) {\r\n        const id = WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.getUniformLocation + ',' + program.id + ',' + name\r\n        );\r\n        if (id === -1) {\r\n            return null;\r\n        } else {\r\n            return new UniformLocation(Number(id));\r\n        }\r\n    }\r\n\r\n    getVertexAttrib = function (index, pname) {\r\n        const result = WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.getVertexAttrib + ',' + index + ',' + pname\r\n        );\r\n        switch (pname) {\r\n            case GLenum.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING:\r\n                return this._map.get(Buffer.uuid(result)) || null;\r\n            case GLenum.CURRENT_VERTEX_ATTRIB: // Float32Array\r\n            default:\r\n                return result;\r\n        }\r\n    }\r\n\r\n    getVertexAttribOffset = function (index, pname) {\r\n        const result = WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.getVertexAttribOffset + ',' + index + ',' + pname\r\n        )\r\n        return Number(result);\r\n    }\r\n\r\n    isBuffer = function (buffer) {\r\n        const result = WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.isBuffer + ',' + buffer.id\r\n        )\r\n        return Boolean(result);\r\n    }\r\n\r\n    isContextLost = function () {\r\n        return false;\r\n    }\r\n\r\n    isEnabled = function (cap) {\r\n        const result = WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.isEnabled + ',' + cap\r\n        )\r\n        return Boolean(result);\r\n    }\r\n\r\n    isFramebuffer = function (framebuffer) {\r\n        const result = WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.isFramebuffer + ',' + framebuffer.id\r\n        )\r\n        return Boolean(result);\r\n    }\r\n\r\n    isProgram = function (program) {\r\n        const result = WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.isProgram + ',' + program.id\r\n        )\r\n        return Boolean(result);\r\n    }\r\n\r\n    isRenderbuffer = function (renderBuffer) {\r\n        const result = WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.isRenderbuffer + ',' + renderbuffer.id\r\n        )\r\n        return Boolean(result);\r\n    }\r\n\r\n    isShader = function (shader) {\r\n        const result = WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.isShader + ',' + shader.id\r\n        )\r\n        return Boolean(result);\r\n    }\r\n\r\n    isTexture = function (texture) {\r\n        const result = WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.isTexture + ',' + texture.id\r\n        );\r\n        return Boolean(result);\r\n    }\r\n\r\n    lineWidth = function (width) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.lineWidth + ',' + width,\r\n            true\r\n        )\r\n    }\r\n\r\n    linkProgram = function (program) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.linkProgram + ',' + program.id,\r\n            true\r\n        );\r\n    }\r\n\r\n\r\n    pixelStorei = function (pname, param) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.pixelStorei + ',' + pname + ',' + Number(param)\r\n        )\r\n    }\r\n\r\n    polygonOffset = function (factor, units) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.polygonOffset + ',' + factor + ',' + units\r\n        )\r\n    }\r\n\r\n    readPixels = function (x, y, width, height, format, type, pixels) {\r\n        const result = WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.readPixels + ',' + x + ',' + y + ',' + width + ',' + height + ',' + format + ',' + type\r\n        )\r\n        return result;\r\n    }\r\n\r\n    renderbufferStorage = function (target, internalFormat, width, height) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.renderbufferStorage + ',' + target + ',' + internalFormat + ',' + width + ',' + height,\r\n            true\r\n        )\r\n    }\r\n\r\n    sampleCoverage = function (value, invert) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.sampleCoverage + ',' + value + ',' + Number(invert),\r\n            true\r\n        )\r\n    }\r\n\r\n    scissor = function (x, y, width, height) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.scissor + ',' + x + ',' + y + ',' + width + ',' + height,\r\n            true\r\n        )\r\n    }\r\n\r\n    shaderSource = function (shader, source) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.shaderSource + ',' + shader.id + ',' + source\r\n        )\r\n    }\r\n\r\n    stencilFunc = function (func, ref, mask) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.stencilFunc + ',' + func + ',' + ref + ',' + mask,\r\n            true\r\n        )\r\n    }\r\n\r\n    stencilFuncSeparate = function (face, func, ref, mask) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.stencilFuncSeparate + ',' + face + ',' + func + ',' + ref + ',' + mask,\r\n            true\r\n        )\r\n    }\r\n\r\n    stencilMask = function (mask) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.stencilMask + ',' + mask,\r\n            true\r\n        )\r\n    }\r\n\r\n    stencilMaskSeparate = function (face, mask) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.stencilMaskSeparate + ',' + face + ',' + mask,\r\n            true\r\n        )\r\n    }\r\n\r\n    stencilOp = function (fail, zfail, zpass) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.stencilOp + ',' + fail + ',' + zfail + ',' + zpass\r\n        )\r\n    }\r\n\r\n    stencilOpSeparate = function (face, fail, zfail, zpass) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.stencilOp + ',' + face + ',' + fail + ',' + zfail + ',' + zpass,\r\n            true\r\n        )\r\n    }\r\n\r\n    texImage2D = function (...args) {\r\n        WebGLRenderingContext.GBridge.texImage2D(this._canvas.id, ...args);\r\n    }\r\n\r\n\r\n    texParameterf = function (target, pname, param) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.texParameterf + ',' + target + ',' + pname + ',' + param,\r\n            true\r\n        )\r\n    }\r\n\r\n    texParameteri = function (target, pname, param) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.texParameteri + ',' + target + ',' + pname + ',' + param\r\n        )\r\n    }\r\n\r\n    texSubImage2D = function (...args) {\r\n        WebGLRenderingContext.GBridge.texSubImage2D(this._canvas.id, ...args);\r\n    }\r\n\r\n    uniform1f = function (location, v0) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.uniform1f + ',' + location.id + ',' + v0\r\n        )\r\n    }\r\n\r\n    uniform1fv = function (location, value) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.uniform1fv + ',' + location.id + ',' + processArray(value),\r\n            true\r\n        )\r\n    }\r\n\r\n    uniform1i = function (location, v0) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.uniform1i + ',' + location.id + ',' + v0,\r\n            // true\r\n        )\r\n    }\r\n\r\n    uniform1iv = function (location, value) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.uniform1iv + ',' + location.id + ',' + processArray(value),\r\n            true\r\n        )\r\n    }\r\n\r\n    uniform2f = function (location, v0, v1) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.uniform2f + ',' + location.id + ',' + v0 + ',' + v1,\r\n            true\r\n        )\r\n    }\r\n\r\n    uniform2fv = function (location, value) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.uniform2fv + ',' + location.id + ',' + processArray(value),\r\n            true\r\n        )\r\n    }\r\n\r\n    uniform2i = function (location, v0, v1) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.uniform2i + ',' + location.id + ',' + v0 + ',' + v1,\r\n            true\r\n        )\r\n    }\r\n\r\n    uniform2iv = function (location, value) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.uniform2iv + ',' + location.id + ',' + processArray(value),\r\n            true\r\n        )\r\n    }\r\n\r\n    uniform3f = function (location, v0, v1, v2) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.uniform3f + ',' + location.id + ',' + v0 + ',' + v1 + ',' + v2,\r\n            true\r\n        )\r\n    }\r\n\r\n    uniform3fv = function (location, value) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.uniform3fv + ',' + location.id + ',' + processArray(value),\r\n            true\r\n        )\r\n    }\r\n\r\n    uniform3i = function (location, v0, v1, v2) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.uniform3i + ',' + location.id + ',' + v0 + ',' + v1 + ',' + v2,\r\n            true\r\n        )\r\n    }\r\n\r\n    uniform3iv = function (location, value) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.uniform3iv + ',' + location.id + ',' + processArray(value),\r\n            true\r\n        )\r\n    }\r\n\r\n    uniform4f = function (location, v0, v1, v2, v3) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.uniform4f + ',' + location.id + ',' + v0 + ',' + v1 + ',' + v2 + ',' + v3,\r\n            true\r\n        )\r\n    }\r\n\r\n    uniform4fv = function (location, value) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.uniform4fv + ',' + location.id + ',' + processArray(value),\r\n            true\r\n        )\r\n    }\r\n\r\n    uniform4i = function (location, v0, v1, v2, v3) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.uniform4i + ',' + location.id + ',' + v0 + ',' + v1 + ',' + v2 + ',' + v3,\r\n            true\r\n        )\r\n    }\r\n\r\n    uniform4iv = function (location, value) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.uniform4iv + ',' + location.id + ',' + processArray(value, true),\r\n            true\r\n        )\r\n    }\r\n\r\n    uniformMatrix2fv = function (location, transpose, value) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.uniformMatrix2fv + ',' + location.id + ',' + Number(transpose) + ',' + processArray(value),\r\n            true\r\n        )\r\n    }\r\n\r\n    uniformMatrix3fv = function (location, transpose, value) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.uniformMatrix3fv + ',' + location.id + ',' + Number(transpose) + ',' + processArray(value),\r\n            true\r\n        )\r\n    }\r\n\r\n    uniformMatrix4fv = function (location, transpose, value) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.uniformMatrix4fv + ',' + location.id + ',' + Number(transpose) + ',' + processArray(value),\r\n            true\r\n        );\r\n    }\r\n\r\n    useProgram = function (progarm) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.useProgram + ',' + progarm.id + '',\r\n            true\r\n        )\r\n    }\r\n\r\n\r\n    validateProgram = function (program) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.validateProgram + ',' + program.id,\r\n            true\r\n        )\r\n    }\r\n\r\n    vertexAttrib1f = function (index, v0) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.vertexAttrib1f + ',' + index + ',' + v0,\r\n            true\r\n        )\r\n    }\r\n\r\n    vertexAttrib2f = function (index, v0, v1) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.vertexAttrib2f + ',' + index + ',' + v0 + ',' + v1,\r\n            true\r\n        )\r\n    }\r\n\r\n    vertexAttrib3f = function (index, v0, v1, v2) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.vertexAttrib3f + ',' + index + ',' + v0 + ',' + v1 + ',' + v2,\r\n            true\r\n        )\r\n    }\r\n\r\n    vertexAttrib4f = function (index, v0, v1, v2, v3) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.vertexAttrib4f + ',' + index + ',' + v0 + ',' + v1 + ',' + v2 + ',' + v3,\r\n            true\r\n        )\r\n    }\r\n\r\n    vertexAttrib1fv = function (index, value) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.vertexAttrib1fv + ',' + index + ',' + processArray(value),\r\n            true\r\n        )\r\n    }\r\n\r\n    vertexAttrib2fv = function (index, value) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.vertexAttrib2fv + ',' + index + ',' + processArray(value),\r\n            true\r\n        )\r\n    }\r\n\r\n    vertexAttrib3fv = function (index, value) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.vertexAttrib3fv + ',' + index + ',' + processArray(value),\r\n            true\r\n        )\r\n    }\r\n\r\n    vertexAttrib4fv = function (index, value) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.vertexAttrib4fv + ',' + index + ',' + processArray(value),\r\n            true\r\n        )\r\n    }\r\n\r\n    vertexAttribPointer = function (index, size, type, normalized, stride, offset) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.vertexAttribPointer + ',' + index + ',' + size + ',' + type + ',' + Number(normalized) + ',' + stride + ',' + offset,\r\n            true\r\n        )\r\n    }\r\n\r\n    viewport = function (x, y, width, height) {\r\n        WebGLRenderingContext.GBridge.callNative(\r\n            this._canvas.id,\r\n            GLmethod.viewport + ',' + x + ',' + y + ',' + width + ',' + height,\r\n            true\r\n        )\r\n    }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/Shader.js",
    "content": "import {getTransferedObjectUUID} from './classUtils';\r\n\r\nconst name = 'WebGLShader';\r\n\r\nfunction uuid(id) {\r\n    return getTransferedObjectUUID(name, id);\r\n}\r\n\r\nexport default class WebGLShader {\r\n    className = name;\r\n\r\n    constructor(id, type) {\r\n        this.id = id;\r\n        this.type = type;\r\n    }\r\n\r\n    static uuid = uuid;\r\n\r\n    uuid() {\r\n        return uuid(this.id);\r\n    }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/ShaderPrecisionFormat.js",
    "content": "export default class WebGLShaderPrecisionFormat {\r\n    className = 'WebGLShaderPrecisionFormat';\r\n\r\n    constructor({\r\n        rangeMin, rangeMax, precision\r\n    }) {\r\n        this.rangeMin = rangeMin;\r\n        this.rangeMax = rangeMax;\r\n        this.precision = precision;\r\n    }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/Texture.js",
    "content": "import {getTransferedObjectUUID} from './classUtils';\r\n\r\nconst name = 'WebGLTexture';\r\n\r\nfunction uuid(id) {\r\n    return getTransferedObjectUUID(name, id);\r\n}\r\n\r\nexport default class WebGLTexture {\r\n    className = name;\r\n\r\n    constructor(id, type) {\r\n        this.id = id;\r\n        this.type = type;\r\n    }\r\n\r\n    static uuid = uuid;\r\n\r\n    uuid() {\r\n        return uuid(this.id);\r\n    }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/UniformLocation.js",
    "content": "import {getTransferedObjectUUID} from './classUtils';\r\n\r\nconst name = 'WebGLUniformLocation';\r\n\r\nfunction uuid(id) {\r\n    return getTransferedObjectUUID(name, id);\r\n}\r\n\r\nexport default class WebGLUniformLocation {\r\n    className = name;\r\n\r\n    constructor(id, type) {\r\n        this.id = id;\r\n        this.type = type;\r\n    }\r\n\r\n    static uuid = uuid;\r\n\r\n    uuid() {\r\n        return uuid(this.id);\r\n    }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/context-webgl/classUtils.js",
    "content": "export function getTransferedObjectUUID(name, id) {\r\n    return `${name.toLowerCase()}-${id}`;\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/env/canvas.js",
    "content": "import GContext2D from '../context-2d/RenderingContext';\r\nimport GContextWebGL from '../context-webgl/RenderingContext';\r\n\r\nexport default class GCanvas {\r\n\r\n    // static GBridge = null;\r\n\r\n    id = null;\r\n\r\n    _needRender = true;\r\n\r\n    constructor(id, { disableAutoSwap }) {\r\n        this.id = id;\r\n\r\n        this._disableAutoSwap = disableAutoSwap;\r\n        if (disableAutoSwap) {\r\n            this._swapBuffers = () => {\r\n                GCanvas.GBridge.render(this.id);\r\n            }\r\n        }\r\n    }\r\n\r\n    getContext(type) {\r\n\r\n        let context = null;\r\n\r\n        if (type.match(/webgl/i)) {\r\n            context = new GContextWebGL(this);\r\n\r\n            context.componentId = this.id;\r\n\r\n            if (!this._disableAutoSwap) {\r\n                const render = () => {\r\n                    if (this._needRender) {\r\n                        GCanvas.GBridge.render(this.id);\r\n                        this._needRender = false;\r\n                    }\r\n                }\r\n                setInterval(render, 16);\r\n            }\r\n\r\n            GCanvas.GBridge.callSetContextType(this.id, 1); // 0 for 2d; 1 for webgl\r\n        } else if (type.match(/2d/i)) {\r\n            context = new GContext2D(this);\r\n\r\n            context.componentId = this.id;\r\n\r\n//             const render = ( callback ) => {\r\n// \r\n//                 const commands = context._drawCommands;\r\n//                 context._drawCommands = '';\r\n// \r\n//                 GCanvas.GBridge.render2d(this.id, commands, callback);\r\n//                 this._needRender = false;\r\n//             }\r\n// \t\t\t//draw方法触发\r\n// \t\t\tcontext._flush = render;\r\n//             //setInterval(render, 16);\r\n\r\n            GCanvas.GBridge.callSetContextType(this.id, 0);\r\n        } else {\r\n            throw new Error('not supported context ' + type);\r\n        }\r\n\r\n        return context;\r\n\r\n    }\r\n\r\n    reset() {\r\n        GCanvas.GBridge.callReset(this.id);\r\n    }\r\n\r\n\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/env/image.js",
    "content": "let incId = 1;\r\n\r\nconst noop = function () { };\r\n\r\nclass GImage {\r\n\r\n    static GBridge = null;\r\n\r\n    constructor() {\r\n        this._id = incId++;\r\n        this._width = 0;\r\n        this._height = 0;\r\n        this._src = undefined;\r\n        this._onload = noop;\r\n        this._onerror = noop;\r\n        this.complete = false;\r\n    }\r\n\r\n    get width() {\r\n        return this._width;\r\n    }\r\n    set width(v) {\r\n        this._width = v;\r\n    }\r\n\r\n    get height() {\r\n        return this._height;\r\n    }\r\n\r\n    set height(v) {\r\n        this._height = v;\r\n    }\r\n\r\n    get src() {\r\n        return this._src;\r\n    }\r\n\r\n    set src(v) {\r\n\r\n        if (v.startsWith('//')) {\r\n            v = 'http:' + v;\r\n        }\r\n\r\n        this._src = v;\r\n\r\n        GImage.GBridge.perloadImage([this._src, this._id], (data) => {\r\n            if (typeof data === 'string') {\r\n                data = JSON.parse(data);\r\n            }\r\n            if (data.error) {\r\n                var evt = { type: 'error', target: this };\r\n                this.onerror(evt);\r\n            } else {\r\n                this.complete = true;\r\n                this.width = typeof data.width === 'number' ? data.width : 0;\r\n                this.height = typeof data.height === 'number' ? data.height : 0;\r\n                var evt = { type: 'load', target: this };\r\n                this.onload(evt);\r\n            }\r\n        });\r\n    }\r\n\r\n    addEventListener(name, listener) {\r\n        if (name === 'load') {\r\n            this.onload = listener;\r\n        } else if (name === 'error') {\r\n            this.onerror = listener;\r\n        }\r\n    }\r\n\r\n    removeEventListener(name, listener) {\r\n        if (name === 'load') {\r\n            this.onload = noop;\r\n        } else if (name === 'error') {\r\n            this.onerror = noop;\r\n        }\r\n    }\r\n\r\n    get onload() {\r\n        return this._onload;\r\n    }\r\n\r\n    set onload(v) {\r\n        this._onload = v;\r\n    }\r\n\r\n    get onerror() {\r\n        return this._onerror;\r\n    }\r\n\r\n    set onerror(v) {\r\n        this._onerror = v;\r\n    }\r\n}\r\n\r\nexport default GImage;"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/env/tool.js",
    "content": "\r\nexport function ArrayBufferToBase64 (buffer) {\r\n    var binary = '';\r\n    var bytes = new Uint8ClampedArray(buffer);\r\n    for (var len = bytes.byteLength, i = 0; i < len; i++) {\r\n        binary += String.fromCharCode(bytes[i]);\r\n    }\r\n    return btoa(binary);\r\n}\r\n\t\r\nexport function Base64ToUint8ClampedArray(base64String) {\r\n\tconst padding = '='.repeat((4 - base64String.length % 4) % 4);\r\n\tconst base64 = (base64String + padding)\r\n\t\t.replace(/\\-/g, '+')\r\n\t\t.replace(/_/g, '/');\r\n\r\n\tconst rawData = atob(base64);\r\n\tconst outputArray = new Uint8ClampedArray(rawData.length);\r\n\r\n\tfor (let i = 0; i < rawData.length; ++i) {\r\n\t\toutputArray[i] = rawData.charCodeAt(i);\r\n\t}\r\n\treturn outputArray;\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-qrcode/components/uv-qrcode/gcanvas/index.js",
    "content": "import GCanvas from './env/canvas';\r\nimport GImage from './env/image';\r\n\r\nimport GWebGLRenderingContext from './context-webgl/RenderingContext';\r\nimport GContext2D from './context-2d/RenderingContext';\r\n\r\nimport GBridgeWeex from './bridge/bridge-weex';\r\n\r\nexport let Image = GImage;\r\n\r\nexport let WeexBridge = GBridgeWeex;\r\n\r\nexport function enable(el, { bridge, debug, disableAutoSwap, disableComboCommands } = {}) {\r\n\r\n    const GBridge = GImage.GBridge = GCanvas.GBridge = GWebGLRenderingContext.GBridge = GContext2D.GBridge = bridge;\r\n\r\n    GBridge.callEnable(el.ref, [\r\n        0,      // renderMode: 0--RENDERMODE_WHEN_DIRTY, 1--RENDERMODE_CONTINUOUSLY\r\n        -1,     // hybridLayerType:  0--LAYER_TYPE_NONE 1--LAYER_TYPE_SOFTWARE 2--LAYER_TYPE_HARDWARE\r\n        false,  // supportScroll\r\n        false,  // newCanvasMode\r\n        1,      // compatible\r\n        'white',// clearColor\r\n        false   // sameLevel: newCanvasMode = true && true => GCanvasView and Webview is same level\r\n    ]);\r\n\r\n    if (debug === true) {\r\n        GBridge.callEnableDebug();\r\n    }\r\n    if (disableComboCommands) {\r\n        GBridge.callEnableDisableCombo();\r\n    }\r\n\r\n    var canvas = new GCanvas(el.ref, { disableAutoSwap });\r\n    canvas.width = el.style.width;\r\n    canvas.height = el.style.height;\r\n\r\n    return canvas;\r\n};"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-qrcode/components/uv-qrcode/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t//二维码内容\r\n\t\tvalue: {\r\n\t\t\ttype: [String, Number]\r\n\t\t},\r\n\t\t//选项\r\n\t\toptions: {\r\n\t\t\ttype: Object,\r\n\t\t\tdefault: () => {\r\n\t\t\t\treturn {};\r\n\t\t\t}\r\n\t\t},\r\n\t\t//二维码大小\r\n\t\tsize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 200\r\n\t\t},\r\n\t\t//导出的文件类型\r\n\t\tfileType: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'png'\r\n\t\t},\r\n\t\t//是否初始化组件后就开始生成\r\n\t\tstart: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t//是否数据发生改变自动重绘\r\n\t\tauto: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t//隐藏组件\r\n\t\thide: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t/**\r\n\t\t * canvas 类型，微信小程序默认使用2d，非2d微信官方已放弃维护，问题比较多\r\n\t\t * 注意：微信小程序type2d手机上正常，PC上微信内打开小程序toDataURL报错，看后期微信官方团队会不会做兼容，不兼容的话只能在自行判断在PC使用非2d，或者直接提示用户请在手机上操作，微信团队的海报中心小程序就是这么做的\r\n\t\t */\r\n\t\ttype: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: () => {\r\n\t\t\t\t// #ifdef MP-WEIXIN\r\n\t\t\t\treturn '2d';\r\n\t\t\t\t// #endif\r\n\t\t\t\t// #ifndef MP-WEIXIN\r\n\t\t\t\treturn 'normal';\r\n\t\t\t\t// #endif\r\n\t\t\t}\r\n\t\t},\r\n\t\t//队列绘制，主要针对NVue端\r\n\t\tqueue: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t//是否队列加载图片，可减少canvas发起的网络资源请求，节省服务器资源\r\n\t\tisQueueLoadImage: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t//loading态\r\n\t\tloading: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: undefined\r\n\t\t},\r\n\t\t//H5保存即自动下载（在支持的环境下），默认false为仅弹层提示用户需要长按图片保存，不会自动下载\r\n\t\th5SaveIsDownload: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t//H5下载名称\r\n\t\th5DownloadName: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'uvQRCode'\r\n\t\t},\r\n\t\t// H5保存二维码时候是否显示提示\r\n\t\th5SaveTip: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t}\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-qrcode/components/uv-qrcode/qrcode.js",
    "content": "//---------------------------------------------------------------------\r\n// uQRCode二维码生成插件 v4.0.6\r\n// \r\n// uQRCode是一款基于Javascript环境开发的二维码生成插件，适用所有Javascript运行环境的前端应用和Node.js。\r\n// \r\n// Copyright (c) Sansnn uQRCode All rights reserved.\r\n// \r\n// Licensed under the Apache License, Version 2.0.\r\n//   http://www.apache.org/licenses/LICENSE-2.0\r\n// \r\n// github地址：\r\n//   https://github.com/Sansnn/uQRCode\r\n// \r\n// npm地址：\r\n//   https://www.npmjs.com/package/uqrcodejs\r\n// \r\n// uni-app插件市场地址：\r\n//   https://ext.dcloud.net.cn/plugin?id=1287\r\n// \r\n// 复制使用请保留本段注释，感谢支持开源！\r\n// \r\n//---------------------------------------------------------------------\r\n\r\n//---------------------------------------------------------------------\r\n// 当前文件格式为 es，将 bundle 保留为 ES 模块文件，适用于其他打包工具以及支持 <script type=module> 标签的浏览器（别名: esm，module）\r\n// 如需在其他环境使用，请获取环境对应的格式文件\r\n// 格式说明：\r\n// amd - 异步模块定义，适用于 RequireJS 等模块加载器\r\n// cjs - CommonJS，适用于 Node 环境和其他打包工具（别名：commonjs）\r\n// es - 将 bundle 保留为 ES 模块文件，适用于其他打包工具以及支持 <script type=module> 标签的浏览器（别名: esm，module）\r\n// umd - 通用模块定义，生成的包同时支持 amd、cjs 和 iife 三种格式\r\n//---------------------------------------------------------------------\r\n\r\nfunction o(o){this.mode=r.MODE_8BIT_BYTE,this.data=o;}function e(o,e){this.typeNumber=o,this.errorCorrectLevel=e,this.modules=null,this.moduleCount=0,this.dataCache=null,this.dataList=new Array;}o.prototype={getLength:function(o){return this.data.length},write:function(o){for(var e=0;e<this.data.length;e++)o.put(this.data.charCodeAt(e),8);}},e.prototype={addData:function(e){var r=new o(e);this.dataList.push(r),this.dataCache=null;},isDark:function(o,e){if(o<0||this.moduleCount<=o||e<0||this.moduleCount<=e)throw new Error(o+\",\"+e);return this.modules[o][e]},getModuleCount:function(){return this.moduleCount},make:function(){if(this.typeNumber<1){var o=1;for(o=1;o<40;o++){for(var e=v.getRSBlocks(o,this.errorCorrectLevel),r=new p,t=0,i=0;i<e.length;i++)t+=e[i].dataCount;for(i=0;i<this.dataList.length;i++){var n=this.dataList[i];r.put(n.mode,4),r.put(n.getLength(),h.getLengthInBits(n.mode,o)),n.write(r);}if(r.getLengthInBits()<=8*t)break}this.typeNumber=o;}this.makeImpl(!1,this.getBestMaskPattern());},makeImpl:function(o,r){this.moduleCount=4*this.typeNumber+17,this.modules=new Array(this.moduleCount);for(var t=0;t<this.moduleCount;t++){this.modules[t]=new Array(this.moduleCount);for(var i=0;i<this.moduleCount;i++)this.modules[t][i]=null;}this.setupPositionProbePattern(0,0),this.setupPositionProbePattern(this.moduleCount-7,0),this.setupPositionProbePattern(0,this.moduleCount-7),this.setupPositionAdjustPattern(),this.setupTimingPattern(),this.setupTypeInfo(o,r),this.typeNumber>=7&&this.setupTypeNumber(o),null==this.dataCache&&(this.dataCache=e.createData(this.typeNumber,this.errorCorrectLevel,this.dataList)),this.mapData(this.dataCache,r);},setupPositionProbePattern:function(o,e){for(var r=-1;r<=7;r++)if(!(o+r<=-1||this.moduleCount<=o+r))for(var t=-1;t<=7;t++)e+t<=-1||this.moduleCount<=e+t||(this.modules[o+r][e+t]=0<=r&&r<=6&&(0==t||6==t)||0<=t&&t<=6&&(0==r||6==r)||2<=r&&r<=4&&2<=t&&t<=4);},getBestMaskPattern:function(){for(var o=0,e=0,r=0;r<8;r++){this.makeImpl(!0,r);var t=h.getLostPoint(this);(0==r||o>t)&&(o=t,e=r);}return e},createMovieClip:function(o,e,r){var t=o.createEmptyMovieClip(e,r);this.make();for(var i=0;i<this.modules.length;i++)for(var n=1*i,a=0;a<this.modules[i].length;a++){var d=1*a;this.modules[i][a]&&(t.beginFill(0,100),t.moveTo(d,n),t.lineTo(d+1,n),t.lineTo(d+1,n+1),t.lineTo(d,n+1),t.endFill());}return t},setupTimingPattern:function(){for(var o=8;o<this.moduleCount-8;o++)null==this.modules[o][6]&&(this.modules[o][6]=o%2==0);for(var e=8;e<this.moduleCount-8;e++)null==this.modules[6][e]&&(this.modules[6][e]=e%2==0);},setupPositionAdjustPattern:function(){for(var o=h.getPatternPosition(this.typeNumber),e=0;e<o.length;e++)for(var r=0;r<o.length;r++){var t=o[e],i=o[r];if(null==this.modules[t][i])for(var n=-2;n<=2;n++)for(var a=-2;a<=2;a++)this.modules[t+n][i+a]=-2==n||2==n||-2==a||2==a||0==n&&0==a;}},setupTypeNumber:function(o){for(var e=h.getBCHTypeNumber(this.typeNumber),r=0;r<18;r++){var t=!o&&1==(e>>r&1);this.modules[Math.floor(r/3)][r%3+this.moduleCount-8-3]=t;}for(r=0;r<18;r++){t=!o&&1==(e>>r&1);this.modules[r%3+this.moduleCount-8-3][Math.floor(r/3)]=t;}},setupTypeInfo:function(o,e){for(var r=this.errorCorrectLevel<<3|e,t=h.getBCHTypeInfo(r),i=0;i<15;i++){var n=!o&&1==(t>>i&1);i<6?this.modules[i][8]=n:i<8?this.modules[i+1][8]=n:this.modules[this.moduleCount-15+i][8]=n;}for(i=0;i<15;i++){n=!o&&1==(t>>i&1);i<8?this.modules[8][this.moduleCount-i-1]=n:i<9?this.modules[8][15-i-1+1]=n:this.modules[8][15-i-1]=n;}this.modules[this.moduleCount-8][8]=!o;},mapData:function(o,e){for(var r=-1,t=this.moduleCount-1,i=7,n=0,a=this.moduleCount-1;a>0;a-=2)for(6==a&&a--;;){for(var d=0;d<2;d++)if(null==this.modules[t][a-d]){var u=!1;n<o.length&&(u=1==(o[n]>>>i&1)),h.getMask(e,t,a-d)&&(u=!u),this.modules[t][a-d]=u,-1==--i&&(n++,i=7);}if((t+=r)<0||this.moduleCount<=t){t-=r,r=-r;break}}}},e.PAD0=236,e.PAD1=17,e.createData=function(o,r,t){for(var i=v.getRSBlocks(o,r),n=new p,a=0;a<t.length;a++){var d=t[a];n.put(d.mode,4),n.put(d.getLength(),h.getLengthInBits(d.mode,o)),d.write(n);}var u=0;for(a=0;a<i.length;a++)u+=i[a].dataCount;if(n.getLengthInBits()>8*u)throw new Error(\"code length overflow. (\"+n.getLengthInBits()+\">\"+8*u+\")\");for(n.getLengthInBits()+4<=8*u&&n.put(0,4);n.getLengthInBits()%8!=0;)n.putBit(!1);for(;!(n.getLengthInBits()>=8*u||(n.put(e.PAD0,8),n.getLengthInBits()>=8*u));)n.put(e.PAD1,8);return e.createBytes(n,i)},e.createBytes=function(o,e){for(var r=0,t=0,i=0,n=new Array(e.length),a=new Array(e.length),d=0;d<e.length;d++){var u=e[d].dataCount,s=e[d].totalCount-u;t=Math.max(t,u),i=Math.max(i,s),n[d]=new Array(u);for(var g=0;g<n[d].length;g++)n[d][g]=255&o.buffer[g+r];r+=u;var l=h.getErrorCorrectPolynomial(s),c=new f(n[d],l.getLength()-1).mod(l);a[d]=new Array(l.getLength()-1);for(g=0;g<a[d].length;g++){var m=g+c.getLength()-a[d].length;a[d][g]=m>=0?c.get(m):0;}}var v=0;for(g=0;g<e.length;g++)v+=e[g].totalCount;var p=new Array(v),C=0;for(g=0;g<t;g++)for(d=0;d<e.length;d++)g<n[d].length&&(p[C++]=n[d][g]);for(g=0;g<i;g++)for(d=0;d<e.length;d++)g<a[d].length&&(p[C++]=a[d][g]);return p};for(var r={MODE_NUMBER:1,MODE_ALPHA_NUM:2,MODE_8BIT_BYTE:4,MODE_KANJI:8},t={L:1,M:0,Q:3,H:2},i=0,n=1,a=2,d=3,u=4,s=5,g=6,l=7,h={PATTERN_POSITION_TABLE:[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]],G15:1335,G18:7973,G15_MASK:21522,getBCHTypeInfo:function(o){for(var e=o<<10;h.getBCHDigit(e)-h.getBCHDigit(h.G15)>=0;)e^=h.G15<<h.getBCHDigit(e)-h.getBCHDigit(h.G15);return (o<<10|e)^h.G15_MASK},getBCHTypeNumber:function(o){for(var e=o<<12;h.getBCHDigit(e)-h.getBCHDigit(h.G18)>=0;)e^=h.G18<<h.getBCHDigit(e)-h.getBCHDigit(h.G18);return o<<12|e},getBCHDigit:function(o){for(var e=0;0!=o;)e++,o>>>=1;return e},getPatternPosition:function(o){return h.PATTERN_POSITION_TABLE[o-1]},getMask:function(o,e,r){switch(o){case i:return (e+r)%2==0;case n:return e%2==0;case a:return r%3==0;case d:return (e+r)%3==0;case u:return (Math.floor(e/2)+Math.floor(r/3))%2==0;case s:return e*r%2+e*r%3==0;case g:return (e*r%2+e*r%3)%2==0;case l:return (e*r%3+(e+r)%2)%2==0;default:throw new Error(\"bad maskPattern:\"+o)}},getErrorCorrectPolynomial:function(o){for(var e=new f([1],0),r=0;r<o;r++)e=e.multiply(new f([1,c.gexp(r)],0));return e},getLengthInBits:function(o,e){if(1<=e&&e<10)switch(o){case r.MODE_NUMBER:return 10;case r.MODE_ALPHA_NUM:return 9;case r.MODE_8BIT_BYTE:case r.MODE_KANJI:return 8;default:throw new Error(\"mode:\"+o)}else if(e<27)switch(o){case r.MODE_NUMBER:return 12;case r.MODE_ALPHA_NUM:return 11;case r.MODE_8BIT_BYTE:return 16;case r.MODE_KANJI:return 10;default:throw new Error(\"mode:\"+o)}else {if(!(e<41))throw new Error(\"type:\"+e);switch(o){case r.MODE_NUMBER:return 14;case r.MODE_ALPHA_NUM:return 13;case r.MODE_8BIT_BYTE:return 16;case r.MODE_KANJI:return 12;default:throw new Error(\"mode:\"+o)}}},getLostPoint:function(o){for(var e=o.getModuleCount(),r=0,t=0;t<e;t++)for(var i=0;i<e;i++){for(var n=0,a=o.isDark(t,i),d=-1;d<=1;d++)if(!(t+d<0||e<=t+d))for(var u=-1;u<=1;u++)i+u<0||e<=i+u||0==d&&0==u||a==o.isDark(t+d,i+u)&&n++;n>5&&(r+=3+n-5);}for(t=0;t<e-1;t++)for(i=0;i<e-1;i++){var s=0;o.isDark(t,i)&&s++,o.isDark(t+1,i)&&s++,o.isDark(t,i+1)&&s++,o.isDark(t+1,i+1)&&s++,0!=s&&4!=s||(r+=3);}for(t=0;t<e;t++)for(i=0;i<e-6;i++)o.isDark(t,i)&&!o.isDark(t,i+1)&&o.isDark(t,i+2)&&o.isDark(t,i+3)&&o.isDark(t,i+4)&&!o.isDark(t,i+5)&&o.isDark(t,i+6)&&(r+=40);for(i=0;i<e;i++)for(t=0;t<e-6;t++)o.isDark(t,i)&&!o.isDark(t+1,i)&&o.isDark(t+2,i)&&o.isDark(t+3,i)&&o.isDark(t+4,i)&&!o.isDark(t+5,i)&&o.isDark(t+6,i)&&(r+=40);var g=0;for(i=0;i<e;i++)for(t=0;t<e;t++)o.isDark(t,i)&&g++;return r+=10*(Math.abs(100*g/e/e-50)/5)}},c={glog:function(o){if(o<1)throw new Error(\"glog(\"+o+\")\");return c.LOG_TABLE[o]},gexp:function(o){for(;o<0;)o+=255;for(;o>=256;)o-=255;return c.EXP_TABLE[o]},EXP_TABLE:new Array(256),LOG_TABLE:new Array(256)},m=0;m<8;m++)c.EXP_TABLE[m]=1<<m;for(m=8;m<256;m++)c.EXP_TABLE[m]=c.EXP_TABLE[m-4]^c.EXP_TABLE[m-5]^c.EXP_TABLE[m-6]^c.EXP_TABLE[m-8];for(m=0;m<255;m++)c.LOG_TABLE[c.EXP_TABLE[m]]=m;function f(o,e){if(null==o.length)throw new Error(o.length+\"/\"+e);for(var r=0;r<o.length&&0==o[r];)r++;this.num=new Array(o.length-r+e);for(var t=0;t<o.length-r;t++)this.num[t]=o[t+r];}function v(o,e){this.totalCount=o,this.dataCount=e;}function p(){this.buffer=new Array,this.length=0;}function C(o){return o.setFillStyle=o.setFillStyle||function(e){o.fillStyle=e;},o.setFontSize=o.setFontSize||function(e){o.font=`${e}px`;},o.setTextAlign=o.setTextAlign||function(e){o.textAlign=e;},o.setTextBaseline=o.setTextBaseline||function(e){o.textBaseline=e;},o.setGlobalAlpha=o.setGlobalAlpha||function(e){o.globalAlpha=e;},o.setStrokeStyle=o.setStrokeStyle||function(e){o.strokeStyle=e;},o.setShadow=o.setShadow||function(e,r,t,i){o.shadowOffsetX=e,o.shadowOffsetY=r,o.shadowBlur=t,o.shadowColor=i;},o.draw=o.draw||function(o,e){e&&e();},o.clearRect=o.clearRect||function(e,r,t,i){o.draw(!1);},o}function b(o,e){var r=this.data=\"\",t=this.size=200;this.useDynamicSize=!1,this.dynamicSize=t;var i=this.typeNumber=-1;this.errorCorrectLevel=b.errorCorrectLevel.H;var n=this.margin=0;this.areaColor=\"#FFFFFF\",this.backgroundColor=\"rgba(255,255,255,0)\",this.backgroundImageSrc=void 0;var a=this.backgroundImageWidth=void 0,d=this.backgroundImageHeight=void 0,u=this.backgroundImageX=void 0,s=this.backgroundImageY=void 0;this.backgroundImageAlpha=1,this.backgroundImageBorderRadius=0;var g=this.backgroundPadding=0;this.foregroundColor=\"#000000\",this.foregroundImageSrc=void 0;var l=this.foregroundImageWidth=void 0,h=this.foregroundImageHeight=void 0,c=this.foregroundImageX=void 0,m=this.foregroundImageY=void 0,f=this.foregroundImagePadding=0;this.foregroundImageBackgroundColor=\"#FFFFFF\";var v=this.foregroundImageBorderRadius=0,p=this.foregroundImageShadowOffsetX=0,k=this.foregroundImageShadowOffsetY=0,y=this.foregroundImageShadowBlur=0;this.foregroundImageShadowColor=\"#808080\";var w=this.foregroundPadding=0,I=this.positionProbeBackgroundColor=void 0,B=this.positionProbeForegroundColor=void 0,S=this.separatorColor=void 0,P=this.positionAdjustBackgroundColor=void 0,L=this.positionAdjustForegroundColor=void 0,D=this.timingBackgroundColor=void 0,A=this.timingForegroundColor=void 0,E=this.typeNumberBackgroundColor=void 0,T=this.typeNumberForegroundColor=void 0,N=this.darkBlockColor=void 0;this.base=void 0,this.modules=[],this.moduleCount=0,this.drawModules=[];var M=this.canvasContext=void 0;this.loadImage,this.drawReserve=!1,this.isMaked=!1,Object.defineProperties(this,{data:{get(){if(\"\"===r||void 0===r)throw console.error(\"[uQRCode]: data must be set!\"),new b.Error(\"data must be set!\");return r},set(o){r=String(o);}},size:{get:()=>t,set(o){t=Number(o);}},typeNumber:{get:()=>i,set(o){i=Number(o);}},margin:{get:()=>n,set(o){n=Number(o);}},backgroundImageWidth:{get(){return void 0===a?this.dynamicSize:this.useDynamicSize?this.dynamicSize/this.size*a:a},set(o){a=Number(o);}},backgroundImageHeight:{get(){return void 0===d?this.dynamicSize:this.useDynamicSize?this.dynamicSize/this.size*d:d},set(o){d=Number(o);}},backgroundImageX:{get(){return void 0===u?0:this.useDynamicSize?this.dynamicSize/this.size*u:u},set(o){u=Number(o);}},backgroundImageY:{get(){return void 0===s?0:this.useDynamicSize?this.dynamicSize/this.size*s:s},set(o){s=Number(o);}},backgroundPadding:{get:()=>g,set(o){g=o>1?1:o<0?0:o;}},foregroundImageWidth:{get(){return void 0===l?(this.dynamicSize-2*this.margin)/4:this.useDynamicSize?this.dynamicSize/this.size*l:l},set(o){l=Number(o);}},foregroundImageHeight:{get(){return void 0===h?(this.dynamicSize-2*this.margin)/4:this.useDynamicSize?this.dynamicSize/this.size*h:h},set(o){h=Number(o);}},foregroundImageX:{get(){return void 0===c?this.dynamicSize/2-this.foregroundImageWidth/2:this.useDynamicSize?this.dynamicSize/this.size*c:c},set(o){c=Number(o);}},foregroundImageY:{get(){return void 0===m?this.dynamicSize/2-this.foregroundImageHeight/2:this.useDynamicSize?this.dynamicSize/this.size*m:m},set(o){m=Number(o);}},foregroundImagePadding:{get(){return this.useDynamicSize?this.dynamicSize/this.size*f:f},set(o){f=Number(o);}},foregroundImageBorderRadius:{get(){return this.useDynamicSize?this.dynamicSize/this.size*v:v},set(o){v=Number(o);}},foregroundImageShadowOffsetX:{get(){return this.useDynamicSize?this.dynamicSize/this.size*p:p},set(o){p=Number(o);}},foregroundImageShadowOffsetY:{get(){return this.useDynamicSize?this.dynamicSize/this.size*k:k},set(o){k=Number(o);}},foregroundImageShadowBlur:{get(){return this.useDynamicSize?this.dynamicSize/this.size*y:y},set(o){y=Number(o);}},foregroundPadding:{get:()=>w,set(o){w=o>1?1:o<0?0:o;}},positionProbeBackgroundColor:{get(){return I||this.backgroundColor},set(o){I=o;}},positionProbeForegroundColor:{get(){return B||this.foregroundColor},set(o){B=o;}},separatorColor:{get(){return S||this.backgroundColor},set(o){S=o;}},positionAdjustBackgroundColor:{get(){return P||this.backgroundColor},set(o){P=o;}},positionAdjustForegroundColor:{get(){return L||this.foregroundColor},set(o){L=o;}},timingBackgroundColor:{get(){return D||this.backgroundColor},set(o){D=o;}},timingForegroundColor:{get(){return A||this.foregroundColor},set(o){A=o;}},typeNumberBackgroundColor:{get(){return E||this.backgroundColor},set(o){E=o;}},typeNumberForegroundColor:{get(){return T||this.foregroundColor},set(o){T=o;}},darkBlockColor:{get(){return N||this.foregroundColor},set(o){N=o;}},canvasContext:{get(){if(void 0===M)throw console.error(\"[uQRCode]: use drawCanvas, you need to set the canvasContext!\"),new b.Error(\"use drawCanvas, you need to set the canvasContext!\");return M},set(o){M=C(o);}}}),b.plugins.forEach((o=>o(b,this,!1))),o&&this.setOptions(o),e&&(this.canvasContext=C(e));}f.prototype={get:function(o){return this.num[o]},getLength:function(){return this.num.length},multiply:function(o){for(var e=new Array(this.getLength()+o.getLength()-1),r=0;r<this.getLength();r++)for(var t=0;t<o.getLength();t++)e[r+t]^=c.gexp(c.glog(this.get(r))+c.glog(o.get(t)));return new f(e,0)},mod:function(o){if(this.getLength()-o.getLength()<0)return this;for(var e=c.glog(this.get(0))-c.glog(o.get(0)),r=new Array(this.getLength()),t=0;t<this.getLength();t++)r[t]=this.get(t);for(t=0;t<o.getLength();t++)r[t]^=c.gexp(c.glog(o.get(t))+e);return new f(r,0).mod(o)}},v.RS_BLOCK_TABLE=[[1,26,19],[1,26,16],[1,26,13],[1,26,9],[1,44,34],[1,44,28],[1,44,22],[1,44,16],[1,70,55],[1,70,44],[2,35,17],[2,35,13],[1,100,80],[2,50,32],[2,50,24],[4,25,9],[1,134,108],[2,67,43],[2,33,15,2,34,16],[2,33,11,2,34,12],[2,86,68],[4,43,27],[4,43,19],[4,43,15],[2,98,78],[4,49,31],[2,32,14,4,33,15],[4,39,13,1,40,14],[2,121,97],[2,60,38,2,61,39],[4,40,18,2,41,19],[4,40,14,2,41,15],[2,146,116],[3,58,36,2,59,37],[4,36,16,4,37,17],[4,36,12,4,37,13],[2,86,68,2,87,69],[4,69,43,1,70,44],[6,43,19,2,44,20],[6,43,15,2,44,16],[4,101,81],[1,80,50,4,81,51],[4,50,22,4,51,23],[3,36,12,8,37,13],[2,116,92,2,117,93],[6,58,36,2,59,37],[4,46,20,6,47,21],[7,42,14,4,43,15],[4,133,107],[8,59,37,1,60,38],[8,44,20,4,45,21],[12,33,11,4,34,12],[3,145,115,1,146,116],[4,64,40,5,65,41],[11,36,16,5,37,17],[11,36,12,5,37,13],[5,109,87,1,110,88],[5,65,41,5,66,42],[5,54,24,7,55,25],[11,36,12],[5,122,98,1,123,99],[7,73,45,3,74,46],[15,43,19,2,44,20],[3,45,15,13,46,16],[1,135,107,5,136,108],[10,74,46,1,75,47],[1,50,22,15,51,23],[2,42,14,17,43,15],[5,150,120,1,151,121],[9,69,43,4,70,44],[17,50,22,1,51,23],[2,42,14,19,43,15],[3,141,113,4,142,114],[3,70,44,11,71,45],[17,47,21,4,48,22],[9,39,13,16,40,14],[3,135,107,5,136,108],[3,67,41,13,68,42],[15,54,24,5,55,25],[15,43,15,10,44,16],[4,144,116,4,145,117],[17,68,42],[17,50,22,6,51,23],[19,46,16,6,47,17],[2,139,111,7,140,112],[17,74,46],[7,54,24,16,55,25],[34,37,13],[4,151,121,5,152,122],[4,75,47,14,76,48],[11,54,24,14,55,25],[16,45,15,14,46,16],[6,147,117,4,148,118],[6,73,45,14,74,46],[11,54,24,16,55,25],[30,46,16,2,47,17],[8,132,106,4,133,107],[8,75,47,13,76,48],[7,54,24,22,55,25],[22,45,15,13,46,16],[10,142,114,2,143,115],[19,74,46,4,75,47],[28,50,22,6,51,23],[33,46,16,4,47,17],[8,152,122,4,153,123],[22,73,45,3,74,46],[8,53,23,26,54,24],[12,45,15,28,46,16],[3,147,117,10,148,118],[3,73,45,23,74,46],[4,54,24,31,55,25],[11,45,15,31,46,16],[7,146,116,7,147,117],[21,73,45,7,74,46],[1,53,23,37,54,24],[19,45,15,26,46,16],[5,145,115,10,146,116],[19,75,47,10,76,48],[15,54,24,25,55,25],[23,45,15,25,46,16],[13,145,115,3,146,116],[2,74,46,29,75,47],[42,54,24,1,55,25],[23,45,15,28,46,16],[17,145,115],[10,74,46,23,75,47],[10,54,24,35,55,25],[19,45,15,35,46,16],[17,145,115,1,146,116],[14,74,46,21,75,47],[29,54,24,19,55,25],[11,45,15,46,46,16],[13,145,115,6,146,116],[14,74,46,23,75,47],[44,54,24,7,55,25],[59,46,16,1,47,17],[12,151,121,7,152,122],[12,75,47,26,76,48],[39,54,24,14,55,25],[22,45,15,41,46,16],[6,151,121,14,152,122],[6,75,47,34,76,48],[46,54,24,10,55,25],[2,45,15,64,46,16],[17,152,122,4,153,123],[29,74,46,14,75,47],[49,54,24,10,55,25],[24,45,15,46,46,16],[4,152,122,18,153,123],[13,74,46,32,75,47],[48,54,24,14,55,25],[42,45,15,32,46,16],[20,147,117,4,148,118],[40,75,47,7,76,48],[43,54,24,22,55,25],[10,45,15,67,46,16],[19,148,118,6,149,119],[18,75,47,31,76,48],[34,54,24,34,55,25],[20,45,15,61,46,16]],v.getRSBlocks=function(o,e){var r=v.getRsBlockTable(o,e);if(null==r)throw new Error(\"bad rs block @ typeNumber:\"+o+\"/errorCorrectLevel:\"+e);for(var t=r.length/3,i=new Array,n=0;n<t;n++)for(var a=r[3*n+0],d=r[3*n+1],u=r[3*n+2],s=0;s<a;s++)i.push(new v(d,u));return i},v.getRsBlockTable=function(o,e){switch(e){case t.L:return v.RS_BLOCK_TABLE[4*(o-1)+0];case t.M:return v.RS_BLOCK_TABLE[4*(o-1)+1];case t.Q:return v.RS_BLOCK_TABLE[4*(o-1)+2];case t.H:return v.RS_BLOCK_TABLE[4*(o-1)+3];default:return}},p.prototype={get:function(o){var e=Math.floor(o/8);return 1==(this.buffer[e]>>>7-o%8&1)},put:function(o,e){for(var r=0;r<e;r++)this.putBit(1==(o>>>e-r-1&1));},getLengthInBits:function(){return this.length},putBit:function(o){var e=Math.floor(this.length/8);this.buffer.length<=e&&this.buffer.push(0),o&&(this.buffer[e]|=128>>>this.length%8),this.length++;}},e.errorCorrectLevel=t,b.errorCorrectLevel=e.errorCorrectLevel,b.Error=function(o){this.errMsg=\"[uQRCode]: \"+o;},b.plugins=[],b.use=function(o){\"function\"==typeof o&&b.plugins.push(o);},b.prototype.loadImage=function(o){return Promise.resolve(o)},b.prototype.setOptions=function(o){var e,r,t,i,n,a,d,u,s,g,l,h,c,m,f,v,p,C,b,k,y,w,I,B,S,P,L,D,A,E,T,N,M,z,R,_,O,F,x,H,X,Y,j,W,G,K,Q,U,$,J,q,V,Z,oo,eo,ro;o&&(Object.keys(o).forEach((e=>{this[e]=o[e];})),function(o={},e={},r=!1){let t;t=r?o:{...o};for(let o in e){var i=e[o];null!=i&&(i.constructor==Object?t[o]=this.deepReplace(t[o],i):i.constructor!=String||i?t[o]=i:t[o]=t[o]);}}(this,{data:o.data||o.text,size:o.size,useDynamicSize:o.useDynamicSize,typeNumber:o.typeNumber,errorCorrectLevel:o.errorCorrectLevel,margin:o.margin,areaColor:o.areaColor,backgroundColor:o.backgroundColor||(null===(e=o.background)||void 0===e?void 0:e.color),backgroundImageSrc:o.backgroundImageSrc||(null===(r=o.background)||void 0===r||null===(t=r.image)||void 0===t?void 0:t.src),backgroundImageWidth:o.backgroundImageWidth||(null===(i=o.background)||void 0===i||null===(n=i.image)||void 0===n?void 0:n.width),backgroundImageHeight:o.backgroundImageHeight||(null===(a=o.background)||void 0===a||null===(d=a.image)||void 0===d?void 0:d.height),backgroundImageX:o.backgroundImageX||(null===(u=o.background)||void 0===u||null===(s=u.image)||void 0===s?void 0:s.x),backgroundImageY:o.backgroundImageY||(null===(g=o.background)||void 0===g||null===(l=g.image)||void 0===l?void 0:l.y),backgroundImageAlpha:o.backgroundImageAlpha||(null===(h=o.background)||void 0===h||null===(c=h.image)||void 0===c?void 0:c.alpha),backgroundImageBorderRadius:o.backgroundImageBorderRadius||(null===(m=o.background)||void 0===m||null===(f=m.image)||void 0===f?void 0:f.borderRadius),backgroundPadding:o.backgroundPadding,foregroundColor:o.foregroundColor||(null===(v=o.foreground)||void 0===v?void 0:v.color),foregroundImageSrc:o.foregroundImageSrc||(null===(p=o.foreground)||void 0===p||null===(C=p.image)||void 0===C?void 0:C.src),foregroundImageWidth:o.foregroundImageWidth||(null===(b=o.foreground)||void 0===b||null===(k=b.image)||void 0===k?void 0:k.width),foregroundImageHeight:o.foregroundImageHeight||(null===(y=o.foreground)||void 0===y||null===(w=y.image)||void 0===w?void 0:w.height),foregroundImageX:o.foregroundImageX||(null===(I=o.foreground)||void 0===I||null===(B=I.image)||void 0===B?void 0:B.x),foregroundImageY:o.foregroundImageY||(null===(S=o.foreground)||void 0===S||null===(P=S.image)||void 0===P?void 0:P.y),foregroundImagePadding:o.foregroundImagePadding||(null===(L=o.foreground)||void 0===L||null===(D=L.image)||void 0===D?void 0:D.padding),foregroundImageBackgroundColor:o.foregroundImageBackgroundColor||(null===(A=o.foreground)||void 0===A||null===(E=A.image)||void 0===E?void 0:E.backgroundColor),foregroundImageBorderRadius:o.foregroundImageBorderRadius||(null===(T=o.foreground)||void 0===T||null===(N=T.image)||void 0===N?void 0:N.borderRadius),foregroundImageShadowOffsetX:o.foregroundImageShadowOffsetX||(null===(M=o.foreground)||void 0===M||null===(z=M.image)||void 0===z?void 0:z.shadowOffsetX),foregroundImageShadowOffsetY:o.foregroundImageShadowOffsetY||(null===(R=o.foreground)||void 0===R||null===(_=R.image)||void 0===_?void 0:_.shadowOffsetY),foregroundImageShadowBlur:o.foregroundImageShadowBlur||(null===(O=o.foreground)||void 0===O||null===(F=O.image)||void 0===F?void 0:F.shadowBlur),foregroundImageShadowColor:o.foregroundImageShadowColor||(null===(x=o.foreground)||void 0===x||null===(H=x.image)||void 0===H?void 0:H.shadowColor),foregroundPadding:o.foregroundPadding,positionProbeBackgroundColor:o.positionProbeBackgroundColor||(null===(X=o.positionProbe)||void 0===X?void 0:X.backgroundColor)||(null===(Y=o.positionDetection)||void 0===Y?void 0:Y.backgroundColor),positionProbeForegroundColor:o.positionProbeForegroundColor||(null===(j=o.positionProbe)||void 0===j?void 0:j.foregroundColor)||(null===(W=o.positionDetection)||void 0===W?void 0:W.foregroundColor),separatorColor:o.separatorColor||(null===(G=o.separator)||void 0===G?void 0:G.color),positionAdjustBackgroundColor:o.positionAdjustBackgroundColor||(null===(K=o.positionAdjust)||void 0===K?void 0:K.backgroundColor)||(null===(Q=o.alignment)||void 0===Q?void 0:Q.backgroundColor),positionAdjustForegroundColor:o.positionAdjustForegroundColor||(null===(U=o.positionAdjust)||void 0===U?void 0:U.foregroundColor)||(null===($=o.alignment)||void 0===$?void 0:$.foregroundColor),timingBackgroundColor:o.timingBackgroundColor||(null===(J=o.timing)||void 0===J?void 0:J.backgroundColor),timingForegroundColor:o.timingForegroundColor||(null===(q=o.timing)||void 0===q?void 0:q.foregroundColor),typeNumberBackgroundColor:o.typeNumberBackgroundColor||(null===(V=o.typeNumber)||void 0===V?void 0:V.backgroundColor)||(null===(Z=o.versionInformation)||void 0===Z?void 0:Z.backgroundColor),typeNumberForegroundColor:o.typeNumberForegroundColor||(null===(oo=o.typeNumber)||void 0===oo?void 0:oo.foregroundColor)||(null===(eo=o.versionInformation)||void 0===eo?void 0:eo.foregroundColor),darkBlockColor:o.darkBlockColor||(null===(ro=o.darkBlock)||void 0===ro?void 0:ro.color)},!0));},b.prototype.make=function(){let{foregroundColor:o,backgroundColor:r,typeNumber:t,errorCorrectLevel:i,data:n,size:a,margin:d,useDynamicSize:u}=this;if(o===r)throw console.error(\"[uQRCode]: foregroundColor and backgroundColor cannot be the same!\"),new b.Error(\"foregroundColor and backgroundColor cannot be the same!\");var s=new e(t,i);s.addData(function(o){o=o.toString();for(var e,r=\"\",t=0;t<o.length;t++)(e=o.charCodeAt(t))>=1&&e<=127?r+=o.charAt(t):e>2047?(r+=String.fromCharCode(224|e>>12&15),r+=String.fromCharCode(128|e>>6&63),r+=String.fromCharCode(128|e>>0&63)):(r+=String.fromCharCode(192|e>>6&31),r+=String.fromCharCode(128|e>>0&63));return r}(n)),s.make(),this.base=s,this.typeNumber=s.typeNumber,this.modules=s.modules,this.moduleCount=s.moduleCount,this.dynamicSize=u?Math.ceil((a-2*d)/s.moduleCount)*s.moduleCount+2*d:a,function(o){let{dynamicSize:e,margin:r,backgroundColor:t,backgroundPadding:i,foregroundColor:n,foregroundPadding:a,modules:d,moduleCount:u}=o,s=(e-2*r)/u,g=s,l=0;i>0&&(l=g*i/2,g-=2*l);let h=s,c=0;a>0&&(c=h*a/2,h-=2*c);for(var m=0;m<u;m++)for(var f=0;f<u;f++){var v=f*s+r,p=m*s+r;if(d[m][f]){var C=c,b=v+c,k=p+c,y=h,w=h;d[m][f]={type:[\"foreground\"],color:n,isBlack:!0,isDrawn:!1,destX:v,destY:p,destWidth:s,destHeight:s,x:b,y:k,width:y,height:w,paddingTop:C,paddingRight:C,paddingBottom:C,paddingLeft:C};}else C=l,b=v+l,k=p+l,y=g,w=g,d[m][f]={type:[\"background\"],color:t,isBlack:!1,isDrawn:!1,destX:v,destY:p,destWidth:s,destHeight:s,x:b,y:k,width:y,height:w,paddingTop:C,paddingRight:C,paddingBottom:C,paddingLeft:C};}}(this),function(o){let{modules:e,moduleCount:r,positionProbeBackgroundColor:t,positionProbeForegroundColor:i}=o,n=r-7;[[0,0,1],[1,0,1],[2,0,1],[3,0,1],[4,0,1],[5,0,1],[6,0,1],[0,1,1],[1,1,0],[2,1,0],[3,1,0],[4,1,0],[5,1,0],[6,1,1],[0,2,1],[1,2,0],[2,2,1],[3,2,1],[4,2,1],[5,2,0],[6,2,1],[0,3,1],[1,3,0],[2,3,1],[3,3,1],[4,3,1],[5,3,0],[6,3,1],[0,4,1],[1,4,0],[2,4,1],[3,4,1],[4,4,1],[5,4,0],[6,4,1],[0,5,1],[1,5,0],[2,5,0],[3,5,0],[4,5,0],[5,5,0],[6,5,1],[0,6,1],[1,6,1],[2,6,1],[3,6,1],[4,6,1],[5,6,1],[6,6,1]].forEach((o=>{var r=e[o[0]][o[1]],a=e[o[0]+n][o[1]],d=e[o[0]][o[1]+n];d.type.push(\"positionProbe\"),a.type.push(\"positionProbe\"),r.type.push(\"positionProbe\"),r.color=1==o[2]?i:t,a.color=1==o[2]?i:t,d.color=1==o[2]?i:t;}));}(this),function(o){let{modules:e,moduleCount:r,separatorColor:t}=o;[[7,0],[7,1],[7,2],[7,3],[7,4],[7,5],[7,6],[7,7],[0,7],[1,7],[2,7],[3,7],[4,7],[5,7],[6,7]].forEach((o=>{var i=e[o[0]][o[1]],n=e[r-o[0]-1][o[1]],a=e[o[0]][r-o[1]-1];a.type.push(\"separator\"),n.type.push(\"separator\"),i.type.push(\"separator\"),i.color=t,n.color=t,a.color=t;}));}(this),function(o){let{typeNumber:e,modules:r,moduleCount:t,foregroundColor:i,backgroundColor:n,positionAdjustForegroundColor:a,positionAdjustBackgroundColor:d,timingForegroundColor:u,timingBackgroundColor:s}=o;const g=[[],[6,18],[6,22],[6,26],[6,30],[6,34],[6,22,38],[6,24,42],[6,26,46],[6,28,50],[6,30,54],[6,32,58],[6,34,62],[6,26,46,66],[6,26,48,70],[6,26,50,74],[6,30,54,78],[6,30,56,82],[6,30,58,86],[6,34,62,90],[6,28,50,72,94],[6,26,50,74,98],[6,30,54,78,102],[6,28,54,80,106],[6,32,58,84,110],[6,30,58,86,114],[6,34,62,90,118],[6,26,50,74,98,122],[6,30,54,78,102,126],[6,26,52,78,104,130],[6,30,56,82,108,134],[6,34,60,86,112,138],[6,30,58,86,114,142],[6,34,62,90,118,146],[6,30,54,78,102,126,150],[6,24,50,76,102,128,154],[6,28,54,80,106,132,158],[6,32,58,84,110,136,162],[6,26,54,82,110,138,166],[6,30,58,86,114,142,170]][e-1];if(g){const o=[[-2,-2,1],[-1,-2,1],[0,-2,1],[1,-2,1],[2,-2,1],[-2,-1,1],[-1,-1,0],[0,-1,0],[1,-1,0],[2,-1,1],[-2,0,1],[-1,0,0],[0,0,1],[1,0,0],[2,0,1],[-2,1,1],[-1,1,0],[0,1,0],[1,1,0],[2,1,1],[-2,2,1],[-1,2,1],[0,2,1],[1,2,1],[2,2,1]],e=g.length;for(let l=0;l<e;l++)for(let h=0;h<e;h++){let{x:e,y:c}={x:g[l],y:g[h]};e<9&&c<9||e>t-9-1&&c<9||c>t-9-1&&e<9||o.forEach((o=>{var t=r[e+o[0]][c+o[1]];t.type.push(\"positionAdjust\"),t.type.includes(\"timing\")?1==o[2]?t.color=a==i?u:a:t.color=a==i&&d==n?s:d:t.color=1==o[2]?a:d;}));}}}(this),function(o){let{modules:e,moduleCount:r,timingForegroundColor:t,timingBackgroundColor:i}=o,n=r-16;for(let o=0;o<n;o++){var a=e[6][8+o],d=e[8+o][6];a.type.push(\"timing\"),d.type.push(\"timing\"),a.color=1&o^1?t:i,d.color=1&o^1?t:i;}}(this),function(o){let{modules:e,moduleCount:r,darkBlockColor:t}=o;var i=e[r-7-1][8];i.type.push(\"darkBlock\"),i.color=t;}(this),function(o){let{typeNumber:e,modules:r,moduleCount:t,typeNumberBackgroundColor:i,typeNumberForegroundColor:n}=o;if(e<7)return r;const a=[0,0,0,0,0,0,0,\"000111110010010100\",\"001000010110111100\",\"001001101010011001\",\"001010010011010011\",\"001011101111110110\",\"001100011101100010\",\"001101100001000111\",\"001110011000001101\",\"001111100100101000\",\"010000101101111000\",\"010001010001011101\",\"010010101000010111\",\"010011010100110010\",\"010100100110100110\",\"010101011010000011\",\"010110100011001001\",\"010111011111101100\",\"011000111011000100\",\"011001000111100001\",\"011010111110101011\",\"011011000010001110\",\"011100110000011010\",\"011101001100111111\",\"011110110101110101\",\"011111001001010000\",\"100000100111010101\",\"100001011011110000\",\"100010100010111010\",\"100011011110011111\",\"100100101100001011\",\"100101010000101110\",\"100110101001100100\",\"100111010101000001\",\"101000110001101001\"];let d=a[e]+a[e],u=[t-11,t-10,t-9];[[5,u[2]],[5,u[1]],[5,u[0]],[4,u[2]],[4,u[1]],[4,u[0]],[3,u[2]],[3,u[1]],[3,u[0]],[2,u[2]],[2,u[1]],[2,u[0]],[1,u[2]],[1,u[1]],[1,u[0]],[0,u[2]],[0,u[1]],[0,u[0]],[u[2],5],[u[1],5],[u[0],5],[u[2],4],[u[1],4],[u[0],4],[u[2],3],[u[1],3],[u[0],3],[u[2],2],[u[1],2],[u[0],2],[u[2],1],[u[1],1],[u[0],1],[u[2],0],[u[1],0],[u[0],0]].forEach(((o,e)=>{var t=r[o[0]][o[1]];t.type.push(\"typeNumber\"),t.color=\"1\"==d[e]?n:i;}));}(this),this.isMaked=!0,this.drawModules=[];},b.prototype.getDrawModules=function(){if(this.drawModules&&this.drawModules.length>0)return this.drawModules;let o=this.drawModules=[],{modules:e,moduleCount:r,dynamicSize:t,areaColor:i,backgroundImageSrc:n,backgroundImageX:a,backgroundImageY:d,backgroundImageWidth:u,backgroundImageHeight:s,backgroundImageAlpha:g,backgroundImageBorderRadius:l,foregroundImageSrc:h,foregroundImageX:c,foregroundImageY:m,foregroundImageWidth:f,foregroundImageHeight:v,foregroundImagePadding:p,foregroundImageBackgroundColor:C,foregroundImageBorderRadius:b,foregroundImageShadowOffsetX:k,foregroundImageShadowOffsetY:y,foregroundImageShadowBlur:w,foregroundImageShadowColor:I}=this;i&&o.push({name:\"area\",type:\"area\",color:i,x:0,y:0,width:t,height:t}),n&&o.push({name:\"backgroundImage\",type:\"image\",imageSrc:n,mappingName:\"backgroundImageSrc\",x:a,y:d,width:u,height:s,alpha:g,borderRadius:l});for(var B=0;B<r;B++)for(var S=0;S<r;S++){var P=e[B][S];P.isDrawn||(P.type.includes(\"foreground\")?o.push({name:\"foreground\",type:\"tile\",color:P.color,destX:P.destX,destY:P.destY,destWidth:P.destWidth,destHeight:P.destHeight,x:P.x,y:P.y,width:P.width,height:P.height,paddingTop:P.paddingTop,paddingRight:P.paddingRight,paddingBottom:P.paddingBottom,paddingLeft:P.paddingLeft,rowIndex:B,colIndex:S}):o.push({name:\"background\",type:\"tile\",color:P.color,destX:P.destX,destY:P.destY,destWidth:P.destWidth,destHeight:P.destHeight,x:P.x,y:P.y,width:P.width,height:P.height,paddingTop:P.paddingTop,paddingRight:P.paddingRight,paddingBottom:P.paddingBottom,paddingLeft:P.paddingLeft,rowIndex:B,colIndex:S}),P.isDrawn=!0);}return h&&o.push({name:\"foregroundImage\",type:\"image\",imageSrc:h,mappingName:\"foregroundImageSrc\",x:c,y:m,width:f,height:v,padding:p,backgroundColor:C,borderRadius:b,shadowOffsetX:k,shadowOffsetY:y,shadowBlur:w,shadowColor:I}),o},b.prototype.isBlack=function(o,e){var r=this.moduleCount;return !(0>o||0>e||o>=r||e>=r)&&this.modules[o][e].isBlack},b.prototype.drawCanvas=function(){let{isMaked:o,canvasContext:e,useDynamicSize:r,dynamicSize:t,foregroundColor:i,foregroundPadding:n,backgroundColor:a,backgroundPadding:d,drawReserve:u,margin:s}=this;if(!o)return console.error(\"[uQRCode]: please execute the make method first!\"),Promise.reject(new b.Error(\"please execute the make method first!\"));let g=this.getDrawModules(),l=async(o,r)=>{try{e.clearRect(0,0,t,t),e.draw(!1);for(var i=0;i<g.length;i++){var n=g[i];switch(e.save(),n.type){case\"area\":e.setFillStyle(n.color),e.fillRect(n.x,n.y,n.width,n.height);break;case\"tile\":var a=n.x,d=n.y,s=n.width,l=n.height;e.setFillStyle(n.color),e.fillRect(a,d,s,l);break;case\"image\":if(\"backgroundImage\"===n.name){a=Math.round(n.x),d=Math.round(n.y),s=Math.round(n.width),l=Math.round(n.height);s<2*(c=Math.round(n.borderRadius))&&(c=s/2),l<2*c&&(c=l/2),e.setGlobalAlpha(n.alpha),c>0&&(e.beginPath(),e.moveTo(a+c,d),e.arcTo(a+s,d,a+s,d+l,c),e.arcTo(a+s,d+l,a,d+l,c),e.arcTo(a,d+l,a,d,c),e.arcTo(a,d,a+s,d,c),e.closePath(),e.setStrokeStyle(\"rgba(0,0,0,0)\"),e.stroke(),e.clip());try{var h=await this.loadImage(n.imageSrc);e.drawImage(h,a,d,s,l);}catch(o){throw console.error(`[uQRCode]: ${n.mappingName} invalid!`),new b.Error(`${n.mappingName} invalid!`)}}else if(\"foregroundImage\"===n.name){a=Math.round(n.x),d=Math.round(n.y),s=Math.round(n.width),l=Math.round(n.height);var c,m=Math.round(n.padding);s<2*(c=Math.round(n.borderRadius))&&(c=s/2),l<2*c&&(c=l/2);var f=a-m,v=d-m,p=s+2*m,C=l+2*m,k=Math.round(p/s*c);p<2*k&&(k=p/2),C<2*k&&(k=C/2),e.save(),e.setShadow(n.shadowOffsetX,n.shadowOffsetY,n.shadowBlur,n.shadowColor),k>0?(e.beginPath(),e.moveTo(f+k,v),e.arcTo(f+p,v,f+p,v+C,k),e.arcTo(f+p,v+C,f,v+C,k),e.arcTo(f,v+C,f,v,k),e.arcTo(f,v,f+p,v,k),e.closePath(),e.setFillStyle(n.backgroundColor),e.fill()):(e.setFillStyle(n.backgroundColor),e.fillRect(f,v,p,C)),e.restore(),e.save(),k>0?(e.beginPath(),e.moveTo(f+k,v),e.arcTo(f+p,v,f+p,v+C,k),e.arcTo(f+p,v+C,f,v+C,k),e.arcTo(f,v+C,f,v,k),e.arcTo(f,v,f+p,v,k),e.closePath(),e.setFillStyle(m>0?n.backgroundColor:\"rgba(0,0,0,0)\"),e.fill()):(e.setFillStyle(m>0?n.backgroundColor:\"rgba(0,0,0,0)\"),e.fillRect(f,v,p,C)),e.restore(),c>0&&(e.beginPath(),e.moveTo(a+c,d),e.arcTo(a+s,d,a+s,d+l,c),e.arcTo(a+s,d+l,a,d+l,c),e.arcTo(a,d+l,a,d,c),e.arcTo(a,d,a+s,d,c),e.closePath(),e.setStrokeStyle(\"rgba(0,0,0,0)\"),e.stroke(),e.clip());try{h=await this.loadImage(n.imageSrc);e.drawImage(h,a,d,s,l);}catch(o){throw console.error(`[uQRCode]: ${n.mappingName} invalid!`),new b.Error(`${n.mappingName} invalid!`)}}}u&&e.draw(!0),e.restore();}e.draw(!0),setTimeout(o,150);}catch(o){if(!(o instanceof b.Error))throw o;r(o);}};return new Promise(((o,e)=>{l(o,e);}))},b.prototype.draw=function(){return this.drawCanvas()},b.prototype.register=function(o){o&&o(b,this,!0);};export{b as default};"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-qrcode/components/uv-qrcode/queue.js",
    "content": "function Queue() {\r\n  let waitingQueue = this.waitingQueue = [];\r\n  let isRunning = this.isRunning = false; // 记录是否有未完成的任务\r\n\r\n  function execute(task, resolve, reject) {\r\n    task()\r\n      .then((data) => {\r\n        resolve(data);\r\n      })\r\n      .catch((e) => {\r\n        reject(e);\r\n      })\r\n      .finally(() => {\r\n        // 等待任务队列中如果有任务，则触发它；否则设置isRunning = false,表示无任务状态\r\n        if (waitingQueue.length) {\r\n          const next = waitingQueue.shift();\r\n          execute(next.task, next.resolve, next.reject);\r\n        } else {\r\n          isRunning = false;\r\n        }\r\n      });\r\n  }\r\n  this.exec = function(task) {\r\n    return new Promise((resolve, reject) => {\r\n      if (isRunning) {\r\n        waitingQueue.push({\r\n          task,\r\n          resolve,\r\n          reject\r\n        });\r\n      } else {\r\n        isRunning = true;\r\n        execute(task, resolve, reject);\r\n      }\r\n    });\r\n  }\r\n}\r\n\r\n/* 队列实例，某些平台一起使用多个组件时需要通过队列逐一绘制，否则部分绘制方法异常，nvue端的iOS gcanvas尤其明显，在不通过队列绘制时会出现图片丢失的情况 */\r\nexport const queueDraw = new Queue();\r\nexport const queueLoadImage = new Queue();"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-qrcode/components/uv-qrcode/uv-qrcode.vue",
    "content": "<template>\r\n\t<view class=\"uqrcode\"\r\n\t\t:class=\"{ 'uqrcode-hide': hide }\"\r\n\t\t:style=\"{ width: `${templateOptions.width}px`, height: `${templateOptions.height}px` }\">\r\n\t\t<view class=\"uqrcode-canvas-wrapper\">\r\n\t\t\t<!-- 画布 -->\r\n\t\t\t<!-- #ifndef APP-NVUE -->\r\n\t\t\t<canvas class=\"uqrcode-canvas\"\r\n\t\t\t\t:id=\"canvasId\"\r\n\t\t\t\t:canvas-id=\"canvasId\"\r\n\t\t\t\t:type=\"canvasType\"\r\n\t\t\t\t:style=\"{\r\n          width: `${templateOptions.canvasWidth}px`,\r\n          height: `${templateOptions.canvasHeight}px`,\r\n          transform: templateOptions.canvasTransform\r\n        }\"\r\n\t\t\t\tv-if=\"templateOptions.canvasDisplay\"\r\n\t\t\t\t@click=\"onClick\"></canvas>\r\n\t\t\t<!-- #endif -->\r\n\r\n\t\t\t<!-- nvue用gcanvas -->\r\n\t\t\t<!-- #ifdef APP-NVUE -->\r\n\t\t\t<gcanvas class=\"uqrcode-canvas\"\r\n\t\t\t\tref=\"gcanvas\"\r\n\t\t\t\t:style=\"{\r\n          width: `${templateOptions.canvasWidth}px`,\r\n          height: `${templateOptions.canvasHeight}px`\r\n        }\"\r\n\t\t\t\tv-if=\"templateOptions.canvasDisplay\"\r\n\t\t\t\t@click=\"onClick\"></gcanvas>\r\n\t\t\t<!-- #endif -->\r\n\t\t</view>\r\n\r\n\t\t<!-- 加载效果 -->\r\n\t\t<view class=\"uqrcode-makeing\"\r\n\t\t\tv-if=\"loading === undefined || !loading ? makeing : loading\">\r\n\t\t\t<slot name=\"loading\">\r\n\t\t\t\t<image class=\"uqrcode-makeing-image\"\r\n\t\t\t\t\t:style=\"{ width: `${templateOptions.size / 4}px`, height: `${templateOptions.size / 4}px` }\"\r\n\t\t\t\t\tsrc=\"data:image/gif;base64,R0lGODlhAAEAAfIEAOHh4SSsWuDg4N3d3f///wAAAAAAAAAAACH/C05FVFNDQVBFMi4wAwEAAAAh/wtYTVAgRGF0YVhNUDw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDYuMC1jMDAyIDc5LjE2NDQ4OCwgMjAyMC8wNy8xMC0yMjowNjo1MyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIDIyLjAgKFdpbmRvd3MpIiB4bXBNTTpJbnN0YW5jZUlEPSJ4bXAuaWlkOjAyODhGMzM4RDEwMTExRUM4MDhCRkVBQkE2QUZDQzkwIiB4bXBNTTpEb2N1bWVudElEPSJ4bXAuZGlkOjAyODhGMzM5RDEwMTExRUM4MDhCRkVBQkE2QUZDQzkwIj4gPHhtcE1NOkRlcml2ZWRGcm9tIHN0UmVmOmluc3RhbmNlSUQ9InhtcC5paWQ6MDI4OEYzMzZEMTAxMTFFQzgwOEJGRUFCQTZBRkNDOTAiIHN0UmVmOmRvY3VtZW50SUQ9InhtcC5kaWQ6MDI4OEYzMzdEMTAxMTFFQzgwOEJGRUFCQTZBRkNDOTAiLz4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4B//79/Pv6+fj39vX08/Lx8O/u7ezr6uno5+bl5OPi4eDf3t3c29rZ2NfW1dTT0tHQz87NzMvKycjHxsXEw8LBwL++vby7urm4t7a1tLOysbCvrq2sq6qpqKempaSjoqGgn56dnJuamZiXlpWUk5KRkI+OjYyLiomIh4aFhIOCgYB/fn18e3p5eHd2dXRzcnFwb25tbGtqaWhnZmVkY2JhYF9eXVxbWllYV1ZVVFNSUVBPTk1MS0pJSEdGRURDQkFAPz49PDs6OTg3NjU0MzIxMC8uLSwrKikoJyYlJCMiISAfHh0cGxoZGBcWFRQTEhEQDw4NDAsKCQgHBgUEAwIBAAAh+QQFFAAEACwAAAAAAAEAAQAD/0i63P4wykmrvTjrzbv/YCiOZGmeaKqubOu+cCzPdG3feK7vfO//wKBwSCwaj8ikcslsOp/QqHRKrVqv2Kx2y+16v+CweEwum8/otHrNbrvf8Lh8Tq/b7/i8fs/v+/+AgYKDhIWGh4iJiouMjY6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanigCqq6ytrieusbISAbW2t7i5uru8vb66bLLCrLDDw7S/ycrLzLXBxsLF0LHIzdbXzc/Trybb1BHY4eK92t6r0uaq1ePs4+Xp6PDg7fTh7+bx+PP1/Mz33vkA7utH0Ne/bQERDizIMNfBaQkhLmxIMcBDaBExTqzI8P+isYwfN3Ik6PFYt3TnRI7kVzLaSZQA1q0s2HLWS5QyZ/ar+a0ETHUqdbLjyc3nz5xC6RFtBdIkhKQ01/yMeVPeU6g7pR6tqu8q1npLiXEV6PVru7ApjcJEquyEPa1rxyosm83EWzVTm7qk688uNrRA1eIMatDvNcBUBVt9cJdEYzR55Urku8ztX7iDFXdlfLnE4zORNZPlfNiwNcR6bVJua7ou3q2i55I+3brv67ixJ8927bhzmtAkgDv4HIJ4GeEikDMw/oH5GOUgoCtw3oF6GOkesFvfsP0L9g7afY/o7uU7h/ClPYsHDTt4++Hri8c//j55/eXzm+d/fj96/+n/+1UX4HX/ZVcgeRggyIV5G6BHmycMauAgb5xEmMGEtnViIQYYVvbJhhd0yBqEBYJ34ICUgGiBiMmAomIFLP7iYonnnZiehjQ2aOODOE7l449MERbVai1iBuSRO67EVpG3IenkYvDptKSMRj5pZUhENjRlYU1e6aVqu420JTlVfmlmYGFyNCYviJ2ZWZoVrblLm25uFuVMcgJTZp1X5gmWkGzuyeeTfioF6JyCDopkoWcdqmeXilrJ6FCOOpRopD9O6k6luNCJ6V5wUqSpRZd+mqSYnN7iqalFhaplqrasyqpYWXYEqzOlzmpnA0mNKquuiblqa61kQgrsqWreSqqx/8e+eaeSyqIi7bTUVmvttdhmq+223Hbr7bejCCDuuOSWa+656Kar7rrnSjDAu/DGK++89NZr77340vsru/z2224E+QYs8MAEw7uvvwj3627BDDfM8MEJR5zuwg5XbHG9EEusMbkUX+zxxRlvvHHHH5f8cK4ip+wvySa3HHDIKifMsss0Y4xyzDijO3PNPBt8c85Aj7tzzzzDHPS6QxNNs9FHTwyw0lAPwHTT/0IQNdRTU11u0ld/nLXWQj/dddE/g50y12Nb/LXZaKft8Npgt+32ycyafbTccxMMt9Z45y3w3lT37Xe+qEnGruDxzihxalU/ULHiETNuLuI+k7i44f9Ii013j5Fjri7l70Ius+dOW/32hxpLvrXmBYuOsOocs6436pfndrjsA7u+Muk64/437Z3bnrnpDeuuMO+NO/A48KML/7nvLzP/OvKTQ0+49Ls7X7rjp1sevHu1c1889sdr3zvxm1eYOvWro986+fzCHrb7s3vfPPjfK9895/ePMLL1+DKe3c6Hv/fZb4DPM5++4IfA9hWwfvxrIAH9tz/1STCBD8wdAy8oNfYlboMXlF/oQChBEXbwgByMnQLnJcAUmrCFHDTh4FhYNrZ5cIY2q5sLb4hDGuowhjzs4Qd/GMIgCnGERCyhEY8IOAxS8IgVZE8Kk2cfKI4viQ2UIRPAaxi3JQqxiXcDoBXtVbgVOlB/YzTgb9ZnRhWKL40axCIVQ/A/+sExgFwU1wvFeMchrjF8T8xfA/oYxz8Kko5sfCMh71XGDJZPkYvMoSH7V8VDLiCS15Nj9do4P0hiUl6NDCQlGfBJRoLrlKhMpSpXycpWuvKVsIylLGdJy1ra8pa4zKUud8nLXvryl8AMpjCHScxiGvOYyEymMpfJzGY685nQjKY0p0nNalrzmtjMpja3yc1uevOb4AynOMdJhwQAACH5BAUUAAQALDIAMgCcAJwAAAP/KLrcTjDKSWt0OFsIuv9gKI5kaZ6Ztq1s6iorKs90/apsTt1pbP/AIA+mK16Gj41wyWwan8ikpUmtRp/GaMNn7Xq3WJ2Wwf2arWHxmDg9u6np3JpdeduX8da8fO8j83xXSn6EQ4CDa4GFi2CHO3uIjJJkjo+JkZOTlZZjipmFmxNzAp6ffqESo6Wmd6hHl22sjK4ckLGyoLSqmLh9tAS7t72+urZ1QL+LycacNcuEz528M9HErsHHP9WtxbDZNtt24YbTMuNu5zerJulm7S7rJe9e8zjfzt2n+VrxJPVo+wQJo/GvSsFG9wgGFLeQ3EBqDdFFVFcOxUEnE1/0G3GR/0lHOs0UXss10ltIiCX1peRX8cRHIS83iniJLVRNUcgyfonZkp1Oej/tnTT3K87NSkdfgSuaJukhp8ByMsUCNQ/UIFPDVDXKDKe2rFC6IhWrFB/YIlubkq319awak5uuSnWrB+5Yu2VF0pUpBZXctnt7jhqMl63KhMMIU3z4hm9ixY4xMn6sGENkj4IpVyaVuctlzdImn/kMWiDixp1L/z08VPVm0lhTuw59WqLo2YNhz22NO7dsOL9789ANmLfwwlGhBT8Obzke58wtQ499O/qf6bu9WvddHWj37RqxF9cOHrky8ZvTs/wOkH2IwPDjy59Pv779+/jz69/Pv7////8ABijggAQWaOCBCCao4FQDNOjggxBGKOGEFFZooYQrBKDhhhx26OGHIIYo4ogfXmjiiSim6GCGJLbo4oswaqjijDTSyGKMOOYYY4089ljhjToGKWSJPhZpJJBDJimkkUz2iKSSUO7Y5JQqPhnllSRSqeWJVmLpJZFbhjlhl1+WKaOYaEJIpplfpulmg2uyieWbbsYpZ5R0pmnnnUrmieaefA7pp5iABhrkoGEWamiOiG6p6KJSNjrlo5C+KCmVlFba4qWTbqCpl5w2memnIvLIkwVB6mdqUBh6qqOqNZ5aQar5rbpSiqMGAKuNrEaY664zykoBrfjZ6lesruYIbJX/vaqZLI7L4trsg7/WiuytKFZb7LXH8orqq9Z6222wz8YYbbbTrlgujOdymS6c677YronCTkDsfcbaxO2w4G4rrr7/2tsvvvvGVbAE99qXr8EBIzywwgc7srDDyoZLLrbufluxv6EOUFTC9XWsLi0g0ycyvCQ/HPLJH6tsMsu/lDzfyR7H7PLMMKe8McEit7wzxD3b/PPKQesMrcWh+kxqnzm7sjSeTaPyNJQ0Kz31oVGHcnWSVQu9tY5dG/01jmE7PTbYWW9yNtpFm712pDQ3HMHbZEf8lN0E0A03sxjTG6/eIU4sMd6AW4q3VYQXvunhXMkNgeKLOw6I4I9DPiLlGZMnbnngjKsl+ealdq6V5qB7iDnin5f+YQIAIfkEBRQABAAsMgAyAJwAnAAAA/84utxOMMpJa3Q4Wyi6/2AojmRpnpm2rWzqKisqz3T9qmxO3Wls/8AgD6YrXoaPjXDJbBqfyKSlSa1Gn8Zow2fterdYnZbB/ZqtYfGYOD27qencml1525fx1rx87yPzfFdKfoRDgINrgYWLYIc7e4iMkmSOj4mRk5OVlmOKmYWbE3MDnp9+oRKjpaZ3qEeXbayMrhyQsbKgtKqYuH20BLu3vb66tnVAv4vJxpw1y4TPnbwz0cSuwcc/1a3FsNk223bhhtMy427nN6sm6WbtLusl717zON/O3af5WvEk9Wj7BAmj8a9KwUb3CAYUt5DcQGoN0UVUVw7FQScTX/QbcZH/SUc6zRReyzXSW0iIJfWl5FfxxEchLzeKeIktVE1RyDJ+idmSnU56P+2dNPcrzs1KR1+BK5om6SGnwHIyxQI1D9QgU8NUNcoMp7asULoiFasUH9giW5uSrfX1rBqTm65KdasH7li7ZUXSlSkFldy2e3uOGoyXrcqEwwhTfPiGb2LFjjEyfqwYQ2SPgilXJpW5y2XN0iaf+QxaIOLGnUv/PTxU9WbSWFO7Dn1aoujZg2HPbY07t2w4v3vz0A2Yt/DCUaEFPw5vOR7nzC1Dj307+p/pu71a910daPftGrEX1w4euTLxm9Oz/A6QfYjA8OPLn0+/vv37+PPr38+/v////wAGKOCABBZo4IEIJqjgVAE06OCDEEYo4YQUVmihhMQBoOGGHHbo4YcghsjhhSSWaOKJDmYo4oostqghijDGGKOKLtZo44sy5qgjhTTe6OOKOwYpZAA9/mikh0MmKWORRzYJgJJQnsikk0ZGaeWFU1Lp45VcTpilljZ2KeaDX4Lp4pholmkmi2iOqeaaIrYp5ptwgihnl3TWieSdV+ap54h8WunnnzgGCuWghBoaJaJ/KnooeoTW6KiSjOo5aZKV1pnjL5tCp1+nroBaG4ufLkmLqMaJWOqMp5rqXoerwsipq6OuGCuKs7L6Koe3StmqrrWqmh+qmxCbipG9mpirrP+eDktrKMbmVWOyJS6La7P4RXuItsn5SC2J1vq664bfYvkrs+NqWK6F4SqL7X3c5sHtketW2G6179oXbxzzIusssNA+S56N9fJ47rXpAlCwlweLG2yIC7fJU7aXkhnUhxGnebGHGbu5Maz/Vkzkx7yGXPHE8IrcIMr6qjzySgSbfCnL9bn8sl/+UqwyTZHeaDPPPUvqMtBBt/gzyUVvOTTSSYe5NMxNr3k01FGDOTXOVWv6NNZZS721TV3DaXO/YZu5bxpkl63l2WGkrbaTbGPh9ttHxv3E3HT/aLcReOfts8CV9O230AAXC7i0gxOOLiqCJ87m4dtC3q3jThceuOQElP+YAAAh+QQFFAAEACwyADIAnACcAAAD/xi63E4wyklrdDhbOLr/YCiOZGmKWcpsbEuoMHvOdG17sOruVJ7Kt6Aw6NPwjq/iYzNsOkvKJXIXbQCfWGx1NaVuFdesWPgFd13lQHjMpqXP6PK6TSe94ay7pc6HyvEbehV9hCGCgBOHE4WMHYqIEI8RjYySiJYElIWYeJiahJxwnp98oWejpHSmXaipbKtTra5isEiys1p/kIm6g7hjtUe3v03BPMM0uxTFvcpJX3M1zhLM0NORzYtD1xxDxl7We9vc1Vvcz+ZM49flVefIM+ftUe/Z1OvT80r14b5C8t7sQYJ3AiAZgZcQZsLnTF8RfunE/SMXsJ8zgiYMElHYSf9hE403vsWxqG0iu4oRp2EsAdKGyBYrSbSs8TKPR4bKHPqA6E6dyXwoe16LOWKmG46ibv5sGJQeN6IijM6oGUhpkHMdSe6CGgJrUq0Drd7wegppWbDdlpIFl/KiWBtrY5ll9VZaXGFz5aJdqPZu1b1Z25a86petUJV1kxUeKXhr4niLYaaZTFmKP03RjlbePDkzIc8nOIt+3Ae0idGonUrE7HNj6tc6WlMy7Qe2bcvLSNG2c7v3gt1tgKPw7Vv4GOMgiBeX3Qj5B+W9nWOR7gi6bepOsFu/zpyR9u2vsX/srhn8aPE47x00f578Z/eh2bdfPRv+afmi0fed1BQ/VzH/3/lXmX6E0eeSgAPaV0eACP6XBXaRRSjhhBRWaOGFGGao4YYcdujhhyCGKOKIJJZo4okopqjiimQB4OKLMMYo44w01mjjjTMSKMCOPPbo449ABinkkDgWaeSROOpI5JJMNonkk1BGqaSTVFYZ5ZVY3jillVx2meWXSG7p5Zhkgmmmi2KWqeaZbBqZ5ppwtilnjG/GaeecbNZ55554Yqknn4D2eeSfgRYqaI2EGqrooS8muiijkDr6KKSCSjoppXNaeimmeSq46aec2qgpqKH66SmpqJYKwKipqjroqa3yKVWSsP64oaknSVmrj7deOauWu/bYq665QgmhhrgCRexl/1UOayxFy+bGpbNP/ipqsDxSGya0zxropLavFlsttjuC6ya343rbpLlFWosouQKwS6u426rLpLzA0hsus1Tie62+59q7pL/vAtwuvATT6K7CCCPrK7r18vutw9Hm9LDARCacI8T7SmulxjIuvDHGQ4JMJ8cBS7wuxa6GjPK9LLcMo8i2xiwzmi8PbPPNNPO6s8w9C/tzy0FnO7SrRZd7tKpJx7t0qU2bzGjUT4fadKxYn2xw1lwfvHXXYDP8ddhkN5pz2WhfjTbQZ68dttpuM9123De7PDbddZvJatZUk4x3xbsk6/Hfa/atMuGCWww4f4gXPrfYhzferbKTDy554hmBXxz55R0rXvlgnGvO1OJphS665+luTncCADs=\">\r\n\t\t\t\t</image>\r\n\t\t\t</slot>\r\n\t\t</view>\r\n\r\n\t\t<!-- 错误处理 -->\r\n\t\t<view class=\"uqrcode-error\"\r\n\t\t\tv-if=\"isError\"\r\n\t\t\t@click=\"onClick\">\r\n\t\t\t<slot name=\"error\"\r\n\t\t\t\t:error=\"error\">\r\n\t\t\t\t<text class=\"uqrcode-error-message\">{{ error.errMsg }}</text>\r\n\t\t\t</slot>\r\n\t\t</view>\r\n\r\n\t\t<!-- H5保存提示 -->\r\n\t\t<!-- #ifdef H5 -->\r\n\t\t<view class=\"uqrcode-h5-save\"\r\n\t\t\tv-if=\"isH5Save\">\r\n\t\t\t<slot name=\"h5save\"\r\n\t\t\t\t:tempFilePath=\"tempFilePath\">\r\n\t\t\t\t<image class=\"uqrcode-h5-save-image\"\r\n\t\t\t\t\t:src=\"tempFilePath\"></image>\r\n\t\t\t\t<text class=\"uqrcode-h5-save-text\">{{ h5SaveIsDownload ? '若保存失败，' : '' }}请长按二维码进行保存</text>\r\n\t\t\t</slot>\r\n\t\t\t<view class=\"uqrcode-h5-save-close\"\r\n\t\t\t\t@click.stop=\"isH5Save = false\">\r\n\t\t\t\t<view class=\"uqrcode-h5-save-close-before\"></view>\r\n\t\t\t\t<view class=\"uqrcode-h5-save-close-after\"></view>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t\t<!-- #endif -->\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport props from './props.js';\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\t// #ifdef VUE3\r\n\timport { toRaw } from 'vue';\r\n\t// #endif\r\n\t/* 引入uvQRCode核心js */\r\n\timport UQRCode from './qrcode';\r\n\t/* 引入nvue所需模块 */\r\n\t// #ifdef APP-NVUE\r\n\timport { enable, WeexBridge } from './gcanvas';\r\n\tconst modal = weex.requireModule('modal');\r\n\t// #endif\r\n\t/* 引入队列 */\r\n\timport { queueDraw, queueLoadImage } from './queue';\r\n\t/* 引入缓存图片 */\r\n\timport { cacheImageList } from './cache';\r\n\tlet instance = null;\r\n\t/**\r\n\t * qrcode 二维码\r\n\t * @description 二维码生成插件，可扩展性高，它支持自定义渲染二维码，可通过uQRCode API得到二维码绘制关键信息后，使用canvas、svg或js操作dom的方式绘制二维码图案。还可自定义二维码样式，如随机颜色、圆点、方块、块与块之间的间距等。\r\n\t * @tutorial https://www.uvui.cn/components/qrcode.html\r\n\t * @property {String}\t value\t 二维码内容 (start为true时必填 )\r\n\t * @property {Object}\t options  二维码配置选项 (data|size|margin...)\r\n\t * @property {String}\t fileType  导出的文件类型  (jpg | png)  \r\n\t * @property {String}\t start  是否初始化组件后就开始生成 (默认 true)\r\n\t * @property {String}\t auto  是否数据发生改变自动重绘 (默认 false)\r\n\t * @property {String}\t hide  隐藏组件。如果只需要导出二维码作为图片使用，可设置为true，不能在组件或组件父级元素设置v-if=\"false\"、v-show=\"false\"、style=\"display:none;\"等实现隐藏效果，这样会导致导出二维码空白  (默认 false)\r\n\t * @property {String}\t type  canvas组件类型。微信小程序默认2d (默认 undefined)\r\n\t * @property {String}\t queue  队列绘制 (默认 false)\r\n\t * @property {String}\t isQueueLoadImage  是否队列加载图片，选择true将通过队列缓存所需要加载的图片。优点是加载重复资源可减少资源请求次数，节省网络资源，缺点是会转化为同步请求，资源不重复且多的情况下，等待时间会更久。总之，请求重复资源较多则选择true，请求不重复资源较多则选择false (默认 false)\r\n\t * @property {String}\t loading  loading态 (默认 false)\r\n\t * @property {String}\t h5SaveIsDownload  H5保存即自动下载（在支持的环境下），默认false为仅弹层提示用户需要长按图片保存，不会自动下载 (默认 false)\r\n\t * @property {String}\t h5DownloadName  H5下载名称 \r\n\t * @property {String}\t h5SaveTip  H5保存二维码时候是否显示提示\r\n\t * @example <uv-qrcode ref=\"uvqrcode\" size=\"400rpx\" canvas-id=\"qrcode\" value=\"https://www.uvui.cn\"></uv-qrcode>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-qrcode',\r\n\t\tmixins: [mpMixin,mixin,props],\r\n\t\temits: ['click','change','complete'],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tcanvasId: \"\",\r\n\t\t\t\tcanvas: undefined,\r\n\t\t\t\tcanvasType: undefined,\r\n\t\t\t\tcanvasContext: undefined,\r\n\t\t\t\tmakeDelegate: undefined,\r\n\t\t\t\tdrawDelegate: undefined,\r\n\t\t\t\ttoTempFilePathDelegate: undefined,\r\n\t\t\t\tmakeExecuted: false,\r\n\t\t\t\tmakeing: false,\r\n\t\t\t\tdrawing: false,\r\n\t\t\t\tisError: false,\r\n\t\t\t\terror: undefined,\r\n\t\t\t\tisH5Save: false,\r\n\t\t\t\ttempFilePath: '',\r\n\t\t\t\ttemplateOptions: {\r\n\t\t\t\t\tsize: 0,\r\n\t\t\t\t\twidth: 0, // 组件宽度\r\n\t\t\t\t\theight: 0,\r\n\t\t\t\t\tcanvasWidth: 0, // canvas宽度\r\n\t\t\t\t\tcanvasHeight: 0,\r\n\t\t\t\t\tcanvasTransform: '',\r\n\t\t\t\t\tcanvasDisplay: false\r\n\t\t\t\t},\r\n\t\t\t\tuqrcodeOptions: {\r\n\t\t\t\t\tdata: ''\r\n\t\t\t\t},\r\n\t\t\t\tplugins: [],\r\n\t\t\t\tmakeingPattern: [\r\n\t\t\t\t\t[\r\n\t\t\t\t\t\t[true, true, true, false, false, false, false, true, true, true],\r\n\t\t\t\t\t\t[true, true, true, false, false, false, false, true, true, true],\r\n\t\t\t\t\t\t[true, true, true, false, false, false, false, true, true, true],\r\n\t\t\t\t\t\t[true, true, true, false, false, false, false, true, true, true],\r\n\t\t\t\t\t\t[true, true, true, false, false, false, false, true, true, true],\r\n\t\t\t\t\t\t[true, true, true, false, false, false, false, true, true, true],\r\n\t\t\t\t\t\t[true, true, true, false, false, false, false, true, true, true],\r\n\t\t\t\t\t\t[true, true, true, true, true, true, true, true, true, true],\r\n\t\t\t\t\t\t[true, true, true, true, true, true, true, true, true, true],\r\n\t\t\t\t\t\t[true, true, true, true, true, true, true, true, true, true]\r\n\t\t\t\t\t],\r\n\t\t\t\t\t[\r\n\t\t\t\t\t\t[true, true, true, true, true, true, true, true, true, true],\r\n\t\t\t\t\t\t[true, true, true, true, true, true, true, true, true, true],\r\n\t\t\t\t\t\t[true, true, true, true, true, true, true, true, true, true],\r\n\t\t\t\t\t\t[true, true, true, false, false, false, false, true, true, true],\r\n\t\t\t\t\t\t[true, true, true, false, false, false, false, true, true, true],\r\n\t\t\t\t\t\t[true, true, true, false, false, false, false, true, true, true],\r\n\t\t\t\t\t\t[true, true, true, false, false, false, false, false, false, false],\r\n\t\t\t\t\t\t[true, true, true, true, true, true, false, true, true, true],\r\n\t\t\t\t\t\t[true, true, true, true, true, true, false, true, true, true],\r\n\t\t\t\t\t\t[true, true, true, true, true, true, false, true, true, true]\r\n\t\t\t\t\t],\r\n\t\t\t\t\t[\r\n\t\t\t\t\t\t[true, true, true, true, true, true, true, true, true, true],\r\n\t\t\t\t\t\t[true, true, true, true, true, true, true, true, true, true],\r\n\t\t\t\t\t\t[true, true, true, true, true, true, true, true, true, true],\r\n\t\t\t\t\t\t[true, true, true, false, false, false, false, true, true, true],\r\n\t\t\t\t\t\t[true, true, true, false, false, false, false, true, true, true],\r\n\t\t\t\t\t\t[true, true, true, true, true, true, true, false, false, false],\r\n\t\t\t\t\t\t[true, true, true, true, true, true, true, false, false, false],\r\n\t\t\t\t\t\t[true, true, true, true, true, true, true, false, false, false],\r\n\t\t\t\t\t\t[true, true, true, false, false, false, false, true, true, true],\r\n\t\t\t\t\t\t[true, true, true, false, false, false, false, true, true, true]\r\n\t\t\t\t\t],\r\n\t\t\t\t\t[\r\n\t\t\t\t\t\t[true, true, true, true, true, true, true, true, true, true],\r\n\t\t\t\t\t\t[true, true, true, true, true, true, true, true, true, true],\r\n\t\t\t\t\t\t[true, true, true, true, true, true, true, true, true, true],\r\n\t\t\t\t\t\t[true, true, true, false, false, false, false, false, false, false],\r\n\t\t\t\t\t\t[true, true, true, false, false, false, false, false, false, false],\r\n\t\t\t\t\t\t[true, true, true, false, false, false, false, false, false, false],\r\n\t\t\t\t\t\t[true, true, true, false, false, false, false, false, false, false],\r\n\t\t\t\t\t\t[true, true, true, true, true, true, true, true, true, true],\r\n\t\t\t\t\t\t[true, true, true, true, true, true, true, true, true, true],\r\n\t\t\t\t\t\t[true, true, true, true, true, true, true, true, true, true]\r\n\t\t\t\t\t]\r\n\t\t\t\t]\r\n\t\t\t};\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\ttype: {\r\n\t\t\t\thandler(val) {\r\n\t\t\t\t\tconst types = ['2d'];\r\n\t\t\t\t\tif (types.includes(val)) {\r\n\t\t\t\t\t\tthis.canvasType = val;\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tthis.canvasType = undefined;\r\n\t\t\t\t\t}\r\n\t\t\t\t},\r\n\t\t\t\timmediate: true\r\n\t\t\t},\r\n\t\t\tvalue: {\r\n\t\t\t\thandler() {\r\n\t\t\t\t\tif (this.auto) {\r\n\t\t\t\t\t\tthis.remake();\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tsize: {\r\n\t\t\t\thandler() {\r\n\t\t\t\t\tif (this.auto) {\r\n\t\t\t\t\t\tthis.remake();\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\toptions: {\r\n\t\t\t\thandler() {\r\n\t\t\t\t\tif (this.auto) {\r\n\t\t\t\t\t\tthis.remake();\r\n\t\t\t\t\t}\r\n\t\t\t\t},\r\n\t\t\t\tdeep: true\r\n\t\t\t},\r\n\t\t\tmakeing: {\r\n\t\t\t\thandler(val) {\r\n\t\t\t\t\tif (!val) {\r\n\t\t\t\t\t\tif (typeof this.toTempFilePathDelegate === 'function') {\r\n\t\t\t\t\t\t\tthis.toTempFilePathDelegate();\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\tcreated() {\r\n\t\t\tthis.canvasId = this.$uv.guid();\r\n\t\t},\r\n\t\tmounted() {\r\n\t\t\tthis.templateOptions.size = this.$uv.getPx(this.size);\r\n\t\t\tthis.templateOptions.width = this.templateOptions.size;\r\n\t\t\tthis.templateOptions.height = this.templateOptions.size;\r\n\t\t\tthis.templateOptions.canvasWidth = this.templateOptions.size;\r\n\t\t\tthis.templateOptions.canvasHeight = this.templateOptions.size;\r\n\t\t\tif (this.canvasType == '2d') {\r\n\t\t\t\t// #ifndef MP-WEIXIN\r\n\t\t\t\tthis.templateOptions.canvasTransform = `scale(${this.templateOptions.size / this.templateOptions.canvasWidth}, ${this.templateOptions.size /\r\n        this.templateOptions.canvasHeight})`;\r\n\t\t\t\t// #endif\r\n\t\t\t} else {\r\n\t\t\t\tthis.templateOptions.canvasTransform = `scale(${this.templateOptions.size / this.templateOptions.canvasWidth}, ${this.templateOptions.size /\r\n        this.templateOptions.canvasHeight})`;\r\n\t\t\t}\r\n\t\t\tif (this.start) {\r\n\t\t\t\tthis.make();\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\t/**\r\n\t\t\t * 获取模板选项\r\n\t\t\t */\r\n\t\t\tgetTemplateOptions() {\r\n\t\t\t\tvar size = this.$uv.getPx(this.size);\r\n\t\t\t\treturn deepReplace(this.templateOptions, {\r\n\t\t\t\t\tsize,\r\n\t\t\t\t\twidth: size,\r\n\t\t\t\t\theight: size\r\n\t\t\t\t});\r\n\t\t\t},\r\n\t\t\t/**\r\n\t\t\t * 获取插件选项\r\n\t\t\t */\r\n\t\t\tgetUqrcodeOptions() {\r\n\t\t\t\treturn deepReplace(this.options, {\r\n\t\t\t\t\tdata: String(this.value),\r\n\t\t\t\t\tsize: Number(this.templateOptions.size)\r\n\t\t\t\t});\r\n\t\t\t},\r\n\t\t\t/**\r\n\t\t\t * 重置画布\r\n\t\t\t */\r\n\t\t\tresetCanvas(callback) {\r\n\t\t\t\tthis.templateOptions.canvasDisplay = false;\r\n\t\t\t\tthis.$nextTick(() => {\r\n\t\t\t\t\tthis.templateOptions.canvasDisplay = true;\r\n\t\t\t\t\tthis.$nextTick(() => {\r\n\t\t\t\t\t\tcallback && callback();\r\n\t\t\t\t\t});\r\n\t\t\t\t});\r\n\t\t\t},\r\n\t\t\t/**\r\n\t\t\t * 绘制二维码\r\n\t\t\t */\r\n\t\t\tasync draw(callback = {}, isDrawDelegate = false) {\r\n\t\t\t\tif (typeof callback.success != 'function') {\r\n\t\t\t\t\tcallback.success = () => {};\r\n\t\t\t\t}\r\n\t\t\t\tif (typeof callback.fail != 'function') {\r\n\t\t\t\t\tcallback.fail = () => {};\r\n\t\t\t\t}\r\n\t\t\t\tif (typeof callback.complete != 'function') {\r\n\t\t\t\t\tcallback.complete = () => {};\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (this.drawing) {\r\n\t\t\t\t\tif (!isDrawDelegate) {\r\n\t\t\t\t\t\tthis.drawDelegate = () => {\r\n\t\t\t\t\t\t\tthis.draw(callback, true);\r\n\t\t\t\t\t\t};\r\n\t\t\t\t\t\treturn;\r\n\t\t\t\t\t}\r\n\t\t\t\t} else {\r\n\t\t\t\t\tthis.drawing = true;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (!this.canvasId) {\r\n\t\t\t\t\tconsole.error('[uQRCode]: canvasId must be set!');\r\n\t\t\t\t\tthis.isError = true;\r\n\t\t\t\t\tthis.drawing = false;\r\n\t\t\t\t\tcallback.fail({\r\n\t\t\t\t\t\terrMsg: '[uQRCode]: canvasId must be set!'\r\n\t\t\t\t\t});\r\n\t\t\t\t\tcallback.complete({\r\n\t\t\t\t\t\terrMsg: '[uQRCode]: canvasId must be set!'\r\n\t\t\t\t\t});\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\t\t\t\tif (!this.value) {\r\n\t\t\t\t\tconsole.error('[uQRCode]: value must be set!');\r\n\t\t\t\t\tthis.isError = true;\r\n\t\t\t\t\tthis.drawing = false;\r\n\t\t\t\t\tcallback.fail({\r\n\t\t\t\t\t\terrMsg: '[uQRCode]: value must be set!'\r\n\t\t\t\t\t});\r\n\t\t\t\t\tcallback.complete({\r\n\t\t\t\t\t\terrMsg: '[uQRCode]: value must be set!'\r\n\t\t\t\t\t});\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t/* 组件数据 */\r\n\t\t\t\tthis.templateOptions = this.getTemplateOptions();\r\n\t\t\t\t/* uQRCode选项 */\r\n\t\t\t\tthis.uqrcodeOptions = this.getUqrcodeOptions();\r\n\t\t\t\t/* 纠错等级兼容字母写法 */\r\n\t\t\t\tif (typeof this.uqrcodeOptions.errorCorrectLevel === 'string') {\r\n\t\t\t\t\tthis.uqrcodeOptions.errorCorrectLevel = UQRCode.errorCorrectLevel[this.uqrcodeOptions.errorCorrectLevel];\r\n\t\t\t\t}\r\n\t\t\t\t/* nvue不支持动态修改gcanvas尺寸，除nvue外，默认使用useDynamicSize */\r\n\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\tif (typeof this.options.useDynamicSize === 'undefined') {\r\n\t\t\t\t\tthis.uqrcodeOptions.useDynamicSize = true;\r\n\t\t\t\t}\r\n\t\t\t\t// #endif\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\tif (typeof this.options.useDynamicSize === 'undefined') {\r\n\t\t\t\t\tthis.uqrcodeOptions.useDynamicSize = false;\r\n\t\t\t\t}\r\n\t\t\t\t// if (typeof this.options.drawReserve === 'undefined') {\r\n\t\t\t\t//   this.uqrcodeOptions.drawReserve = true;\r\n\t\t\t\t// }\r\n\t\t\t\t// #endif\r\n\r\n\t\t\t\t/* 获取uQRCode实例 */\r\n\t\t\t\tconst qr = instance = new UQRCode();\r\n\t\t\t\t/* 注册扩展 */\r\n\t\t\t\tthis.plugins.forEach(p => qr.register(p.plugin));\r\n\t\t\t\t/* 设置uQRCode选项 */\r\n\t\t\t\tqr.setOptions(this.uqrcodeOptions);\r\n\t\t\t\t/* 调用制作二维码方法 */\r\n\t\t\t\tqr.make();\r\n\r\n\t\t\t\t/* 获取canvas上下文 */\r\n\t\t\t\tlet canvasContext = null;\r\n\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\tif (this.canvasType === '2d') {\r\n\t\t\t\t\t// #ifdef MP-WEIXIN\r\n\t\t\t\t\t/* 微信小程序获取canvas2d上下文方式 */\r\n\t\t\t\t\tconst canvas = (this.canvas = await new Promise(resolve => {\r\n\t\t\t\t\t\tuni\r\n\t\t\t\t\t\t\t.createSelectorQuery()\r\n\t\t\t\t\t\t\t.in(this) // 在组件内使用需要\r\n\t\t\t\t\t\t\t.select(`#${this.canvasId}`)\r\n\t\t\t\t\t\t\t.fields({\r\n\t\t\t\t\t\t\t\tnode: true,\r\n\t\t\t\t\t\t\t\tsize: true\r\n\t\t\t\t\t\t\t})\r\n\t\t\t\t\t\t\t.exec(res => {\r\n\t\t\t\t\t\t\t\tresolve(res[0].node);\r\n\t\t\t\t\t\t\t});\r\n\t\t\t\t\t}));\r\n\t\t\t\t\tcanvasContext = this.canvasContext = canvas.getContext('2d');\r\n\t\t\t\t\t/* 2d的组件设置宽高与实际canvas绘制宽高不是一个，打个比方，组件size=200，canvas.width设置为100，那么绘制出来就是100=200，组件size=400，canvas.width设置为800，绘制大小还是800=400，所以无需理会下方返回的dynamicSize是多少，按dpr重新赋值给canvas即可 */\r\n\t\t\t\t\tthis.templateOptions.canvasWidth = qr.size;\r\n\t\t\t\t\tthis.templateOptions.canvasHeight = qr.size;\r\n\t\t\t\t\tthis.templateOptions.canvasTransform = '';\r\n\t\t\t\t\t/* 使用dynamicSize+scale，可以解决小块间出现白线问题，dpr可以解决模糊问题 */\r\n\t\t\t\t\tconst dpr = uni.getSystemInfoSync().pixelRatio;\r\n\t\t\t\t\tcanvas.width = qr.dynamicSize * dpr;\r\n\t\t\t\t\tcanvas.height = qr.dynamicSize * dpr;\r\n\t\t\t\t\tcanvasContext.scale(dpr, dpr);\r\n\t\t\t\t\t/* 微信小程序获取图像方式 */\r\n\t\t\t\t\tqr.loadImage = this.getLoadImage(function(src) {\r\n\t\t\t\t\t\t/* 小程序下获取网络图片信息需先配置download域名白名单才能生效 */\r\n\t\t\t\t\t\treturn new Promise((resolve, reject) => {\r\n\t\t\t\t\t\t\tconst img = canvas.createImage();\r\n\t\t\t\t\t\t\timg.src = src;\r\n\t\t\t\t\t\t\timg.onload = () => {\r\n\t\t\t\t\t\t\t\tresolve(img);\r\n\t\t\t\t\t\t\t};\r\n\t\t\t\t\t\t\timg.onerror = err => {\r\n\t\t\t\t\t\t\t\treject(err);\r\n\t\t\t\t\t\t\t};\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t});\r\n\t\t\t\t\t// #endif\r\n\t\t\t\t\t// #ifndef MP-WEIXIN\r\n\t\t\t\t\t/* 非微信小程序不支持2d，切换回uniapp获取canvas上下文方式 */\r\n\t\t\t\t\tcanvasContext = this.canvasContext = uni.createCanvasContext(this.canvasId, this);\r\n\t\t\t\t\t/* 使用dynamicSize，可以解决小块间出现白线问题，再通过scale缩放至size，使其达到所设尺寸 */\r\n\t\t\t\t\tthis.templateOptions.canvasWidth = qr.dynamicSize;\r\n\t\t\t\t\tthis.templateOptions.canvasHeight = qr.dynamicSize;\r\n\t\t\t\t\tthis.templateOptions.canvasTransform = `scale(${this.templateOptions.size / this.templateOptions.canvasWidth}, ${this.templateOptions.size /\r\n          this.templateOptions.canvasHeight})`;\r\n\t\t\t\t\t/* uniapp获取图像方式 */\r\n\t\t\t\t\tqr.loadImage = this.getLoadImage(function(src) {\r\n\t\t\t\t\t\treturn new Promise((resolve, reject) => {\r\n\t\t\t\t\t\t\tif (src.startsWith('http')) {\r\n\t\t\t\t\t\t\t\tuni.getImageInfo({\r\n\t\t\t\t\t\t\t\t\tsrc,\r\n\t\t\t\t\t\t\t\t\tsuccess: res => {\r\n\t\t\t\t\t\t\t\t\t\tresolve(res.path);\r\n\t\t\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t\t\t\tfail: err => {\r\n\t\t\t\t\t\t\t\t\t\treject(err);\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t});\r\n\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\tif (src.startsWith('.')) {\r\n\t\t\t\t\t\t\t\t\tconsole.error('[uQRCode]: 本地图片路径仅支持绝对路径！');\r\n\t\t\t\t\t\t\t\t\tthrow new Error('[uQRCode]: local image path only supports absolute path!');\r\n\t\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\t\tresolve(src);\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t});\r\n\t\t\t\t\t// #endif\r\n\t\t\t\t} else {\r\n\t\t\t\t\t/* uniapp获取canvas上下文方式 */\r\n\t\t\t\t\tcanvasContext = this.canvasContext = uni.createCanvasContext(this.canvasId, this);\r\n\t\t\t\t\t/* 使用dynamicSize，可以解决小块间出现白线问题，再通过scale缩放至size，使其达到所设尺寸 */\r\n\t\t\t\t\tthis.templateOptions.canvasWidth = qr.dynamicSize;\r\n\t\t\t\t\tthis.templateOptions.canvasHeight = qr.dynamicSize;\r\n\t\t\t\t\tthis.templateOptions.canvasTransform = `scale(${this.templateOptions.size / this.templateOptions.canvasWidth}, ${this.templateOptions.size /\r\n          this.templateOptions.canvasHeight})`;\r\n\t\t\t\t\t/* uniapp获取图像方式 */\r\n\t\t\t\t\tqr.loadImage = this.getLoadImage(function(src) {\r\n\t\t\t\t\t\treturn new Promise((resolve, reject) => {\r\n\t\t\t\t\t\t\t/* getImageInfo在微信小程序的bug：本地路径返回路径会把开头的/或../移除，导致路径错误，解决方法：限制只能使用绝对路径 */\r\n\t\t\t\t\t\t\tif (src.startsWith('http')) {\r\n\t\t\t\t\t\t\t\tuni.getImageInfo({\r\n\t\t\t\t\t\t\t\t\tsrc,\r\n\t\t\t\t\t\t\t\t\tsuccess: res => {\r\n\t\t\t\t\t\t\t\t\t\tresolve(res.path);\r\n\t\t\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t\t\t\tfail: err => {\r\n\t\t\t\t\t\t\t\t\t\treject(err);\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t});\r\n\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\tif (src.startsWith('.')) {\r\n\t\t\t\t\t\t\t\t\tconsole.error('[uQRCode]: 本地图片路径仅支持绝对路径！');\r\n\t\t\t\t\t\t\t\t\tthrow new Error('[uQRCode]: local image path only supports absolute path!');\r\n\t\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\t\tresolve(src);\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t});\r\n\t\t\t\t}\r\n\t\t\t\t// #endif\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\t/* NVue获取canvas上下文方式 */\r\n\t\t\t\tconst gcanvas = this.$refs['gcanvas'];\r\n\t\t\t\tconst canvas = enable(gcanvas, {\r\n\t\t\t\t\tbridge: WeexBridge\r\n\t\t\t\t});\r\n\t\t\t\tcanvasContext = this.canvasContext = canvas.getContext('2d');\r\n\t\t\t\t/* NVue获取图像方式 */\r\n\t\t\t\tqr.loadImage = this.getLoadImage(function(src) {\r\n\t\t\t\t\treturn new Promise((resolve, reject) => {\r\n\t\t\t\t\t\t/* getImageInfo在nvue的bug：获取同一个路径的图片信息，同一时间第一次获取成功，后续失败，猜测是写入本地时产生文件写入冲突，所以没有返回，特别是对于网络资源 --- 已实现队列绘制，已解决此问题 */\r\n\t\t\t\t\t\tif (src.startsWith('.')) {\r\n\t\t\t\t\t\t\tconsole.error('[uQRCode]: 本地图片路径仅支持绝对路径！');\r\n\t\t\t\t\t\t\tthrow new Error('[uQRCode]: local image path only supports absolute path!');\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\tuni.getImageInfo({\r\n\t\t\t\t\t\t\t\tsrc,\r\n\t\t\t\t\t\t\t\tsuccess: res => {\r\n\t\t\t\t\t\t\t\t\tresolve(res.path);\r\n\t\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t\t\tfail: err => {\r\n\t\t\t\t\t\t\t\t\treject(err);\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t});\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t});\r\n\t\t\t\t});\r\n\t\t\t\t// #endif\r\n\r\n\t\t\t\t/* 设置uQRCode实例的canvas上下文 */\r\n\t\t\t\tqr.canvasContext = canvasContext;\r\n\t\t\t\t/* 延时等待页面重新绘制完毕 */\r\n\t\t\t\tsetTimeout(() => {\r\n\t\t\t\t\t/* 从插件获取具体要调用哪一个扩展函数 */\r\n\t\t\t\t\tvar plugin = this.plugins.find(p => p.name == qr.style);\r\n\t\t\t\t\tvar drawCanvasName = plugin ? plugin.drawCanvas : 'drawCanvas';\r\n\t\t\t\t\t/* 虽然qr[drawCanvasName]是直接返回Promise的，但由于js内部this指向问题，故不能直接exec(qr[drawCanvasName])此方式执行，需要改成exec(() => qr[drawCanvasName]())才能正确获取this */\r\n\t\t\t\t\tvar drawCanvas;\r\n\t\t\t\t\tif (this.queue) {\r\n\t\t\t\t\t\tdrawCanvas = () => queueDraw.exec(() => qr[drawCanvasName]());\r\n\t\t\t\t\t\t// drawCanvas = () => queueDraw.exec(() => new Promise((resolve, reject) => {\r\n\t\t\t\t\t\t//   setTimeout(() => {\r\n\t\t\t\t\t\t//     qr[drawCanvasName]().then(resolve).catch(reject);\r\n\t\t\t\t\t\t//   }, 1000);\r\n\t\t\t\t\t\t// }));\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tdrawCanvas = () => qr[drawCanvasName]();\r\n\t\t\t\t\t}\r\n\t\t\t\t\t/* 调用绘制方法将二维码图案绘制到canvas上 */\r\n\t\t\t\t\tdrawCanvas()\r\n\t\t\t\t\t\t.then(() => {\r\n\t\t\t\t\t\t\tif (this.drawDelegate) {\r\n\t\t\t\t\t\t\t\t/* 高频重绘纠正 */\r\n\t\t\t\t\t\t\t\tlet delegate = this.drawDelegate;\r\n\t\t\t\t\t\t\t\tthis.drawDelegate = undefined;\r\n\t\t\t\t\t\t\t\tdelegate();\r\n\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\tthis.drawing = false;\r\n\t\t\t\t\t\t\t\tcallback.success();\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t\t.catch(err => {\r\n\t\t\t\t\t\t\tconsole.log(err);\r\n\t\t\t\t\t\t\tif (this.drawDelegate) {\r\n\t\t\t\t\t\t\t\t/* 高频重绘纠正 */\r\n\t\t\t\t\t\t\t\tlet delegate = this.drawDelegate;\r\n\t\t\t\t\t\t\t\tthis.drawDelegate = undefined;\r\n\t\t\t\t\t\t\t\tdelegate();\r\n\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\tthis.drawing = false;\r\n\t\t\t\t\t\t\t\tthis.isError = true;\r\n\t\t\t\t\t\t\t\tcallback.fail(err);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t\t.finally(() => {\r\n\t\t\t\t\t\t\tcallback.complete();\r\n\t\t\t\t\t\t});\r\n\t\t\t\t}, 300);\r\n\t\t\t},\r\n\t\t\t/**\r\n\t\t\t * 生成二维码\r\n\t\t\t */\r\n\t\t\tmake(callback = {}) {\r\n\t\t\t\tthis.makeExecuted = true;\r\n\t\t\t\tthis.makeing = true;\r\n\t\t\t\tthis.isError = false;\r\n\r\n\t\t\t\tif (typeof callback.success != 'function') {\r\n\t\t\t\t\tcallback.success = () => {};\r\n\t\t\t\t}\r\n\t\t\t\tif (typeof callback.fail != 'function') {\r\n\t\t\t\t\tcallback.fail = () => {};\r\n\t\t\t\t}\r\n\t\t\t\tif (typeof callback.complete != 'function') {\r\n\t\t\t\t\tcallback.complete = () => {};\r\n\t\t\t\t}\r\n\r\n\t\t\t\tthis.resetCanvas(() => {\r\n\t\t\t\t\tclearTimeout(this.makeDelegate);\r\n\t\t\t\t\tthis.makeDelegate = setTimeout(() => {\r\n\t\t\t\t\t\tthis.draw({\r\n\t\t\t\t\t\t\tsuccess: () => {\r\n\t\t\t\t\t\t\t\tsetTimeout(() => {\r\n\t\t\t\t\t\t\t\t\tcallback.success();\r\n\t\t\t\t\t\t\t\t\tthis.complete(true);\r\n\t\t\t\t\t\t\t\t}, 300);\r\n\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t\tfail: err => {\r\n\t\t\t\t\t\t\t\tcallback.fail(err);\r\n\t\t\t\t\t\t\t\tthis.error = err;\r\n\t\t\t\t\t\t\t\tthis.complete(false, err.errMsg);\r\n\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t\tcomplete: () => {\r\n\t\t\t\t\t\t\t\tcallback.complete();\r\n\t\t\t\t\t\t\t\tthis.makeing = false;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t}, 300);\r\n\t\t\t\t});\r\n\t\t\t},\r\n\t\t\t/**\r\n\t\t\t * 重新生成\r\n\t\t\t */\r\n\t\t\tremake(callback) {\r\n\t\t\t\tthis.$emit('change');\r\n\t\t\t\tthis.make(callback);\r\n\t\t\t},\r\n\t\t\t/**\r\n\t\t\t * 生成完成\r\n\t\t\t */\r\n\t\t\tcomplete(success = true, errMsg = '') {\r\n\t\t\t\tif (success) {\r\n\t\t\t\t\tthis.$emit('complete', {\r\n\t\t\t\t\t\tsuccess\r\n\t\t\t\t\t});\r\n\t\t\t\t} else {\r\n\t\t\t\t\tthis.$emit('complete', {\r\n\t\t\t\t\t\tsuccess,\r\n\t\t\t\t\t\terrMsg\r\n\t\t\t\t\t});\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t/**\r\n\t\t\t * 导出临时路径\r\n\t\t\t */\r\n\t\t\ttoTempFilePath(callback = {}) {\r\n\t\t\t\tif (typeof callback.success != 'function') {\r\n\t\t\t\t\tcallback.success = () => {};\r\n\t\t\t\t}\r\n\t\t\t\tif (typeof callback.fail != 'function') {\r\n\t\t\t\t\tcallback.fail = () => {};\r\n\t\t\t\t}\r\n\t\t\t\tif (typeof callback.complete != 'function') {\r\n\t\t\t\t\tcallback.complete = () => {};\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (!this.makeExecuted) {\r\n\t\t\t\t\tconsole.error('[uQRCode]: make() 方法从未调用！请先成功调用 make() 后再进行操作。');\r\n\t\t\t\t\tvar err = {\r\n\t\t\t\t\t\terrMsg: '[uQRCode]: make() method has never been executed! please execute make() successfully before operating.'\r\n\t\t\t\t\t};\r\n\t\t\t\t\tcallback.fail(err);\r\n\t\t\t\t\tcallback.complete(err);\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (this.isError) {\r\n\t\t\t\t\tcallback.fail(this.error);\r\n\t\t\t\t\tcallback.complete(this.error);\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (this.makeing) {\r\n\t\t\t\t\t/* 如果还在生成状态，那当前操作将托管到委托，监听生成完成后再通过委托复调当前方法 */\r\n\t\t\t\t\tthis.toTempFilePathDelegate = () => {\r\n\t\t\t\t\t\tthis.toTempFilePath(callback);\r\n\t\t\t\t\t};\r\n\t\t\t\t\treturn;\r\n\t\t\t\t} else {\r\n\t\t\t\t\tthis.toTempFilePathDelegate = null;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\tif (this.canvasType === '2d') {\r\n\t\t\t\t\t// #ifdef MP-WEIXIN\r\n\t\t\t\t\ttry {\r\n\t\t\t\t\t\tlet dataURL = null;\r\n\t\t\t\t\t\t// #ifdef VUE3\r\n\t\t\t\t\t\tdataURL = toRaw(this.canvas)\r\n\t\t\t\t\t\t\t.toDataURL();\r\n\t\t\t\t\t\t// #endif\r\n\t\t\t\t\t\t// #ifndef VUE3\r\n\t\t\t\t\t\tdataURL = this.canvas.toDataURL();\r\n\t\t\t\t\t\t// #endif\r\n\t\t\t\t\t\tcallback.success({\r\n\t\t\t\t\t\t\ttempFilePath: dataURL\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t\tcallback.complete({\r\n\t\t\t\t\t\t\ttempFilePath: dataURL\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t} catch (e) {\r\n\t\t\t\t\t\tcallback.fail(e);\r\n\t\t\t\t\t\tcallback.complete(e);\r\n\t\t\t\t\t}\r\n\t\t\t\t\t// #endif\r\n\t\t\t\t} else {\r\n\t\t\t\t\tuni.canvasToTempFilePath({\r\n\t\t\t\t\t\t\tcanvasId: this.canvasId,\r\n\t\t\t\t\t\t\tfileType: this.fileType,\r\n\t\t\t\t\t\t\twidth: Number(this.templateOptions.canvasWidth),\r\n\t\t\t\t\t\t\theight: Number(this.templateOptions.canvasHeight),\r\n\t\t\t\t\t\t\tdestWidth: Number(this.templateOptions.size),\r\n\t\t\t\t\t\t\tdestHeight: Number(this.templateOptions.size),\r\n\t\t\t\t\t\t\tsuccess: res => {\r\n\t\t\t\t\t\t\t\tcallback.success(res);\r\n\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t\tfail: err => {\r\n\t\t\t\t\t\t\t\tcallback.fail(err);\r\n\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t\tcomplete: () => {\r\n\t\t\t\t\t\t\t\tcallback.complete();\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t},\r\n\t\t\t\t\t\tthis\r\n\t\t\t\t\t);\r\n\t\t\t\t}\r\n\t\t\t\t// #endif\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\tconst dpr = uni.getSystemInfoSync().pixelRatio;\r\n\t\t\t\tthis.canvasContext.toTempFilePath(\r\n\t\t\t\t\t0,\r\n\t\t\t\t\t0,\r\n\t\t\t\t\tthis.templateOptions.canvasWidth * dpr,\r\n\t\t\t\t\tthis.templateOptions.canvasHeight * dpr,\r\n\t\t\t\t\tthis.templateOptions.size * dpr,\r\n\t\t\t\t\tthis.templateOptions.size * dpr,\r\n\t\t\t\t\t'',\r\n\t\t\t\t\t1,\r\n\t\t\t\t\tres => {\r\n\t\t\t\t\t\tcallback.success(res);\r\n\t\t\t\t\t\tcallback.complete(res);\r\n\t\t\t\t\t}\r\n\t\t\t\t);\r\n\t\t\t\t// #endif\r\n\t\t\t},\r\n\t\t\t/**\r\n\t\t\t * 保存\r\n\t\t\t */\r\n\t\t\tsave(callback = {}) {\r\n\t\t\t\tif (typeof callback.success != 'function') {\r\n\t\t\t\t\tcallback.success = () => {};\r\n\t\t\t\t}\r\n\t\t\t\tif (typeof callback.fail != 'function') {\r\n\t\t\t\t\tcallback.fail = () => {};\r\n\t\t\t\t}\r\n\t\t\t\tif (typeof callback.complete != 'function') {\r\n\t\t\t\t\tcallback.complete = () => {};\r\n\t\t\t\t}\r\n\r\n\t\t\t\tthis.toTempFilePath({\r\n\t\t\t\t\tsuccess: res => {\r\n\t\t\t\t\t\t// #ifndef H5\r\n\t\t\t\t\t\tif (this.canvasType === '2d') {\r\n\t\t\t\t\t\t\t// #ifdef MP-WEIXIN\r\n\t\t\t\t\t\t\t/* 需要将 data:image/png;base64, 这段去除 writeFile 才能正常打开文件，否则是损坏文件，无法打开 */\r\n\t\t\t\t\t\t\tconst reg = new RegExp('^data:image/png;base64,', 'g');\r\n\t\t\t\t\t\t\tconst dataURL = res.tempFilePath.replace(reg, '');\r\n\t\t\t\t\t\t\tconst fs = wx.getFileSystemManager();\r\n\t\t\t\t\t\t\tconst tempFilePath = `${wx.env.USER_DATA_PATH}/${new Date().getTime()}${\r\n                Math.random()\r\n                  .toString()\r\n                  .split('.')[1]\r\n              }.png`;\r\n\t\t\t\t\t\t\tfs.writeFile({\r\n\t\t\t\t\t\t\t\tfilePath: tempFilePath, // 要写入的文件路径 (本地路径)\r\n\t\t\t\t\t\t\t\tdata: dataURL, // base64图片\r\n\t\t\t\t\t\t\t\tencoding: 'base64',\r\n\t\t\t\t\t\t\t\tsuccess: res1 => {\r\n\t\t\t\t\t\t\t\t\tuni.saveImageToPhotosAlbum({\r\n\t\t\t\t\t\t\t\t\t\tfilePath: tempFilePath,\r\n\t\t\t\t\t\t\t\t\t\tsuccess: res2 => {\r\n\t\t\t\t\t\t\t\t\t\t\tcallback.success(res2);\r\n\t\t\t\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t\t\t\t\tfail: err2 => {\r\n\t\t\t\t\t\t\t\t\t\t\tcallback.fail(err2);\r\n\t\t\t\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t\t\t\t\tcomplete: () => {\r\n\t\t\t\t\t\t\t\t\t\t\tcallback.complete();\r\n\t\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\t});\r\n\t\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t\t\tfail: err => {\r\n\t\t\t\t\t\t\t\t\tcallback.fail(err);\r\n\t\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t\t\tcomplete: () => {\r\n\t\t\t\t\t\t\t\t\tcallback.complete();\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t});\r\n\t\t\t\t\t\t\t// #endif\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\tuni.saveImageToPhotosAlbum({\r\n\t\t\t\t\t\t\t\tfilePath: res.tempFilePath,\r\n\t\t\t\t\t\t\t\tsuccess: res1 => {\r\n\t\t\t\t\t\t\t\t\tcallback.success(res1);\r\n\t\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t\t\tfail: err1 => {\r\n\t\t\t\t\t\t\t\t\tcallback.fail(err1);\r\n\t\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t\t\tcomplete: () => {\r\n\t\t\t\t\t\t\t\t\tcallback.complete();\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t});\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t// #endif\r\n\r\n\t\t\t\t\t\t// #ifdef H5\r\n\t\t\t\t\t\t/* 可以在电脑浏览器下载，移动端iOS不行，安卓微信浏览器不行，安卓外部浏览器可以 */\r\n\t\t\t\t\t\tthis.isH5Save = this.h5SaveTip;\r\n\t\t\t\t\t\tthis.tempFilePath = res.tempFilePath;\r\n\t\t\t\t\t\tif (this.h5SaveIsDownload) {\r\n\t\t\t\t\t\t\tconst aEle = document.createElement('a');\r\n\t\t\t\t\t\t\taEle.download = this.h5DownloadName; // 设置下载的文件名，默认是'下载'\r\n\t\t\t\t\t\t\taEle.href = res.tempFilePath;\r\n\t\t\t\t\t\t\tdocument.body.appendChild(aEle);\r\n\t\t\t\t\t\t\taEle.click();\r\n\t\t\t\t\t\t\taEle.remove(); // 下载之后把创建的元素删除\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tcallback.success({\r\n\t\t\t\t\t\t\terrMsg: 'ok'\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t\tcallback.complete({\r\n\t\t\t\t\t\t\terrMsg: 'ok'\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t\t// #endif\r\n\t\t\t\t\t},\r\n\t\t\t\t\tfail: err => {\r\n\t\t\t\t\t\tcallback.fail(err);\r\n\t\t\t\t\t\tcallback.complete(err);\r\n\t\t\t\t\t}\r\n\t\t\t\t});\r\n\t\t\t},\r\n\t\t\t/**\r\n\t\t\t * 注册click事件\r\n\t\t\t */\r\n\t\t\tonClick(e) {\r\n\t\t\t\tthis.$emit('click', e);\r\n\t\t\t},\r\n\t\t\t/**\r\n\t\t\t * 获取实例\r\n\t\t\t */\r\n\t\t\tgetInstance() {\r\n\t\t\t\treturn instance;\r\n\t\t\t},\r\n\t\t\t/**\r\n\t\t\t * 注册扩展，组件仅支持注册type为style的drawCanvas扩展\r\n\t\t\t * @param {Object} plugin\r\n\t\t\t */\r\n\t\t\tregisterStyle(plugin) {\r\n\t\t\t\tif (plugin.Type != 'style') {\r\n\t\t\t\t\tconsole.warn('[uQRCode]: registerStyle 仅支持注册 style 类型的扩展！');\r\n\t\t\t\t\treturn {\r\n\t\t\t\t\t\terrMsg: 'registerStyle 仅支持注册 style 类型的扩展！'\r\n\t\t\t\t\t};\r\n\t\t\t\t}\r\n\t\t\t\tif (typeof plugin === 'function') {\r\n\t\t\t\t\tthis.plugins.push({\r\n\t\t\t\t\t\tplugin,\r\n\t\t\t\t\t\tname: plugin.Name,\r\n\t\t\t\t\t\tdrawCanvas: plugin.DrawCanvas\r\n\t\t\t\t\t});\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tgetLoadImage(loadImage) {\r\n\t\t\t\tvar that = this;\r\n\t\t\t\tif (typeof loadImage == 'function') {\r\n\t\t\t\t\treturn function(src) {\r\n\t\t\t\t\t\t/* 判断是否是队列加载图片的 */\r\n\t\t\t\t\t\tif (that.isQueueLoadImage) {\r\n\t\t\t\t\t\t\t/* 解决iOS APP||NVUE同时绘制多个二维码导致图片丢失需使用队列 */\r\n\t\t\t\t\t\t\treturn queueLoadImage.exec(() => {\r\n\t\t\t\t\t\t\t\treturn new Promise((resolve, reject) => {\r\n\t\t\t\t\t\t\t\t\tsetTimeout(() => {\r\n\t\t\t\t\t\t\t\t\t\tconst cache = cacheImageList.find(x => x.src == src);\r\n\t\t\t\t\t\t\t\t\t\tif (cache) {\r\n\t\t\t\t\t\t\t\t\t\t\tresolve(cache.img);\r\n\t\t\t\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\t\t\t\tloadImage(src)\r\n\t\t\t\t\t\t\t\t\t\t\t\t.then(img => {\r\n\t\t\t\t\t\t\t\t\t\t\t\t\tcacheImageList.push({\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\tsrc,\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t\timg\r\n\t\t\t\t\t\t\t\t\t\t\t\t\t});\r\n\t\t\t\t\t\t\t\t\t\t\t\t\tresolve(img);\r\n\t\t\t\t\t\t\t\t\t\t\t\t})\r\n\t\t\t\t\t\t\t\t\t\t\t\t.catch(err => {\r\n\t\t\t\t\t\t\t\t\t\t\t\t\treject(err);\r\n\t\t\t\t\t\t\t\t\t\t\t\t});\r\n\t\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\t}, 10);\r\n\t\t\t\t\t\t\t\t});\r\n\t\t\t\t\t\t\t});\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\treturn loadImage(src);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t};\r\n\t\t\t\t} else {\r\n\t\t\t\t\treturn function(src) {\r\n\t\t\t\t\t\treturn Promise.resolve(src);\r\n\t\t\t\t\t};\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t};\r\n\r\n\t/**\r\n\t * 对象属性深度替换\r\n\t * @param {Object} o 原始对象/默认对象/被替换的对象\r\n\t * @param {Object} r 从这个对象里取值替换到o对象里\r\n\t * @return {Object} 替换后的新对象\r\n\t */\r\n\tfunction deepReplace(o = {}, r = {}, c = false) {\r\n\t\tlet obj;\r\n\t\tif (c) {\r\n\t\t\t// 从源替换\r\n\t\t\tobj = o;\r\n\t\t} else {\r\n\t\t\t// 不替换源，copy一份备份来替换\r\n\t\t\tobj = {\r\n\t\t\t\t...o\r\n\t\t\t};\r\n\t\t}\r\n\t\tfor (let k in r) {\r\n\t\t\tvar vr = r[k];\r\n\t\t\tif (vr != undefined) {\r\n\t\t\t\tif (vr.constructor == Object) {\r\n\t\t\t\t\tobj[k] = this.deepReplace(obj[k], vr);\r\n\t\t\t\t} else if (vr.constructor == String && !vr) {\r\n\t\t\t\t\tobj[k] = obj[k];\r\n\t\t\t\t} else {\r\n\t\t\t\t\tobj[k] = vr;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn obj;\r\n\t}\r\n</script>\r\n\r\n<style scoped>\r\n\t.uqrcode {\r\n\t\tposition: relative;\r\n\t}\r\n\t.uqrcode-hide {\r\n\t\tposition: fixed;\r\n\t\tleft: 7500rpx;\r\n\t}\r\n\t.uqrcode-canvas {\r\n\t\ttransform-origin: top left;\r\n\t}\r\n\t.uqrcode-makeing {\r\n\t\tposition: absolute;\r\n\t\ttop: 0;\r\n\t\tright: 0;\r\n\t\tbottom: 0;\r\n\t\tleft: 0;\r\n\t\tz-index: 10;\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\tdisplay: flex;\r\n\t\t/* #endif */\r\n\t\tjustify-content: center;\r\n\t\talign-items: center;\r\n\t}\r\n\t.uqrcode-makeing-image {\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\tdisplay: block;\r\n\t\tmax-width: 120px;\r\n\t\tmax-height: 120px;\r\n\t\t/* #endif */\r\n\t}\r\n\t.uqrcode-error {\r\n\t\tposition: absolute;\r\n\t\ttop: 0;\r\n\t\tright: 0;\r\n\t\tbottom: 0;\r\n\t\tleft: 0;\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\tdisplay: flex;\r\n\t\t/* #endif */\r\n\t\tjustify-content: center;\r\n\t\talign-items: center;\r\n\t}\r\n\t.uqrcode-error-message {\r\n\t\tfont-size: 12px;\r\n\t\tcolor: #939291;\r\n\t}\r\n\t/* #ifdef H5 */\r\n\t.uqrcode-h5-save {\r\n\t\tposition: fixed;\r\n\t\ttop: 0;\r\n\t\tright: 0;\r\n\t\tbottom: 0;\r\n\t\tleft: 0;\r\n\t\tz-index: 100;\r\n\t\tbackground-color: rgba(0, 0, 0, 0.68);\r\n\t\tdisplay: flex;\r\n\t\tflex-direction: column;\r\n\t\tjustify-content: center;\r\n\t\talign-items: center;\r\n\t}\r\n\t.uqrcode-h5-save-image {\r\n\t\twidth: 512rpx;\r\n\t\theight: 512rpx;\r\n\t\tpadding: 32rpx;\r\n\t}\r\n\t.uqrcode-h5-save-text {\r\n\t\tmargin-top: 20rpx;\r\n\t\tfont-size: 32rpx;\r\n\t\tfont-weight: 700;\r\n\t\tcolor: #ffffff;\r\n\t}\r\n\t.uqrcode-h5-save-close {\r\n\t\tposition: relative;\r\n\t\tmargin-top: 72rpx;\r\n\t\twidth: 60rpx;\r\n\t\theight: 60rpx;\r\n\t\tborder: 2rpx solid #ffffff;\r\n\t\tborder-radius: 60rpx;\r\n\t\tpadding: 10rpx;\r\n\t}\r\n\t.uqrcode-h5-save-close-before {\r\n\t\tposition: absolute;\r\n\t\ttop: 50%;\r\n\t\tleft: 50%;\r\n\t\ttransform: translate(-50%, -50%) rotate(45deg);\r\n\t\twidth: 40rpx;\r\n\t\theight: 4rpx;\r\n\t\tbackground: #ffffff;\r\n\t}\r\n\t.uqrcode-h5-save-close-after {\r\n\t\tposition: absolute;\r\n\t\ttop: 50%;\r\n\t\tleft: 50%;\r\n\t\ttransform: translate(-50%, -50%) rotate(-45deg);\r\n\t\twidth: 40rpx;\r\n\t\theight: 4rpx;\r\n\t\tbackground: #ffffff;\r\n\t}\r\n\t/* #endif */\r\n</style>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-qrcode/package.json",
    "content": "{\r\n  \"id\": \"uv-qrcode\",\r\n  \"displayName\": \"uv-qrcode 二维码 全面兼容vue3+2、app、h5、小程序等多端\",\r\n  \"version\": \"1.0.4\",\r\n  \"description\": \"该组件是超级强大二维码生成功能，可扩展性高。自定义二维码样式，如随机颜色、圆点、方块、块与块之间的间距等等，具体请查看文档。\",\r\n  \"keywords\": [\r\n    \"uv-qrcode\",\r\n    \"qrcode\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"二维码\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-qrcode/readme.md",
    "content": "## QRCode 二维码生成器\n\n> **组件名：uv-qrcode**\n\n超级强大二维码生成组件，可扩展性高。自定义二维码样式，如随机颜色、圆点、方块、块与块之间的间距等等，具体请在下方查看文档。\n\n灵活配置，开箱即用，文档全面，支持自定义二维码风格。\n\n# <a href=\"https://www.uvui.cn/components/qrcode.html\" target=\"_blank\">查看文档</a>\n\n## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small style=\"font-size:14px;\">（请不要 下载插件ZIP）</small>\n\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\n\n<a href=\"https://ext.dcloud.net.cn/plugin?name=uv-ui\" target=\"_blank\">\n\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\n\n</a>\n\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-radio/changelog.md",
    "content": "## 1.0.11（2023-10-11）\n1. 优化：https://gitee.com/climblee/uv-ui/issues/I872VD\n## 1.0.10（2023-09-01）\n1. 修复点击空隙处无效的问题\n2. label支持插槽下可点击\n## 1.0.9（2023-08-27）\r\n1. 优化\r\n## 1.0.8（2023-08-26）\r\n1. 修复v-model 绑定布尔值控制台报警\r\n## 1.0.7（2023-08-26）\r\n1. 修复设置 labelSize 属性设置无效的问题：https://gitee.com/climblee/uv-ui/issues/I7W6UN\r\n## 1.0.6（2023-08-04）\r\n1. 修复name为数字0时不能选中的BUG\r\n## 1.0.5（2023-07-13）\r\n1.  修复  uv-radio设置value属性不生效的BUG \r\n## 1.0.4（2023-07-05）\r\n修复vue3模式下，动态修改v-model绑定的值无效的BUG\r\n## 1.0.3（2023-06-06）\r\n1. 修正语法问题\r\n## 1.0.2（2023-06-06）\r\n1.  uv-radio-group 兼容自定义样式customStyle,方便通过样式调整整体位置等\r\n2.  .uv-radio-group--row增加flex-wrap: wrap;允许换行\r\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-radio 单选框\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-radio/components/uv-radio/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// radio的名称\r\n\t\tname: {\r\n\t\t\ttype: [String, Number, Boolean],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 形状，square为方形，circle为圆型\r\n\t\tshape: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 是否禁用\r\n\t\tdisabled: {\r\n\t\t\ttype: [String, Boolean],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 是否禁止点击提示语选中单选框\r\n\t\tlabelDisabled: {\r\n\t\t\ttype: [String, Boolean],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 选中状态下的颜色，如设置此值，将会覆盖parent的activeColor值\r\n\t\tactiveColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 未选中的颜色\r\n\t\tinactiveColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 图标的大小，单位px\r\n\t\ticonSize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// label的字体大小，px单位\r\n\t\tlabelSize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// label提示文字，因为nvue下，直接slot进来的文字，由于特殊的结构，无法修改样式\r\n\t\tlabel: {\r\n\t\t\ttype: [String, Number, Boolean],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 整体的大小\r\n\t\tsize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 图标颜色\r\n\t\ticonColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// label的颜色\r\n\t\tlabelColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t...uni.$uv?.props?.radio\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-radio/components/uv-radio/uv-radio.vue",
    "content": "<template>\r\n\t<view\r\n\t  class=\"uv-radio\"\r\n\t\t@tap.stop=\"wrapperClickHandler\"\r\n\t  :style=\"[radioStyle]\"\r\n\t  :class=\"[`uv-radio-label--${parentData.iconPlacement}`, parentData.borderBottom && parentData.placement === 'column' && 'uv-border-bottom']\"\r\n\t>\r\n\t\t<view\r\n\t\t  class=\"uv-radio__icon-wrap\"\r\n\t\t  @tap.stop=\"iconClickHandler\"\r\n\t\t  :class=\"iconClasses\"\r\n\t\t  :style=\"[iconWrapStyle]\"\r\n\t\t>\r\n\t\t\t<slot name=\"icon\">\r\n\t\t\t\t<uv-icon\r\n\t\t\t\t  class=\"uv-radio__icon-wrap__icon\"\r\n\t\t\t\t  name=\"checkbox-mark\"\r\n\t\t\t\t  :size=\"elIconSize\"\r\n\t\t\t\t  :color=\"elIconColor\"\r\n\t\t\t\t/>\r\n\t\t\t</slot>\r\n\t\t</view>\r\n\t\t<view\r\n\t\t\tclass=\"uv-radio__label-wrap\" \r\n\t\t\t@tap.stop=\"labelClickHandler\">\r\n\t\t\t<slot>\r\n\t\t\t\t<text\r\n\t\t\t\t\t:style=\"{\r\n\t\t\t\t\t\tcolor: elDisabled ? elInactiveColor : elLabelColor,\r\n\t\t\t\t\t\tfontSize: elLabelSize,\r\n\t\t\t\t\t\tlineHeight: elLabelSize\r\n\t\t\t\t\t}\"\r\n\t\t\t\t>{{label}}</text>\r\n\t\t\t</slot>\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * radio 单选框\r\n\t * @description 单选框用于有一个选择，用户只能选择其中一个的场景。搭配uv-radio-group使用\r\n\t * @tutorial https://www.uvui.cn/components/radio.html\r\n\t * @property {String | Number}\tname\t\t\tradio的名称\r\n\t * @property {String}\t\t\tshape\t\t\t形状，square为方形，circle为圆型\r\n\t * @property {Boolean}\t\t\tdisabled\t\t是否禁用\r\n\t * @property {String | Boolean}\tlabelDisabled\t是否禁止点击提示语选中单选框\r\n\t * @property {String}\t\t\tactiveColor\t\t选中时的颜色，如设置parent的active-color将失效\r\n\t * @property {String}\t\t\tinactiveColor\t未选中的颜色\r\n\t * @property {String | Number}\ticonSize\t\t图标大小，单位px\r\n\t * @property {String | Number}\tlabelSize\t\tlabel字体大小，单位px\r\n\t * @property {String | Number}\tlabel\t\t\tlabel提示文字，因为nvue下，直接slot进来的文字，由于特殊的结构，无法修改样式\r\n\t * @property {String | Number}\tsize\t\t\t整体的大小\r\n\t * @property {String}\t\t\ticonColor\t\t图标颜色\r\n\t * @property {String}\t\t\tlabelColor\t\tlabel的颜色\r\n\t * @property {Object}\t\t\tcustomStyle\t\t组件的样式，对象形式\r\n\t * \r\n\t * @event {Function} change 某个radio状态发生变化时触发(选中状态)\r\n\t * @example <uv-radio :labelDisabled=\"false\">门掩黄昏，无计留春住</uv-radio>\r\n\t */\r\n\texport default {\r\n\t\tname: \"uv-radio\",\r\n\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tchecked: false,\r\n\t\t\t\t// 当你看到这段代码的时候，\r\n\t\t\t\t// 父组件的默认值，因为头条小程序不支持在computed中使用this.parent.shape的形式\r\n\t\t\t\t// 故只能使用如此方法\r\n\t\t\t\tparentData: {\r\n\t\t\t\t\ticonSize: 12,\r\n\t\t\t\t\tlabelSize: 14,\r\n\t\t\t\t\tlabelDisabled: null,\r\n\t\t\t\t\tdisabled: null,\r\n\t\t\t\t\tshape: null,\r\n\t\t\t\t\tactiveColor: null,\r\n\t\t\t\t\tinactiveColor: null,\r\n\t\t\t\t\tsize: 18,\r\n\t\t\t\t\tvalue: null,\r\n\t\t\t\t\tmodelValue: null,\r\n\t\t\t\t\ticonColor: null,\r\n\t\t\t\t\tplacement: 'row',\r\n\t\t\t\t\tborderBottom: false,\r\n\t\t\t\t\ticonPlacement: 'left'\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\t// 是否禁用，如果父组件uv-raios-group禁用的话，将会忽略子组件的配置\r\n\t\t\telDisabled() {\r\n\t\t\t\treturn this.disabled !== '' ? this.disabled : this.parentData.disabled !== null ? this.parentData.disabled : false;\r\n\t\t\t},\r\n\t\t\t// 是否禁用label点击\r\n\t\t\telLabelDisabled() {\r\n\t\t\t\treturn this.labelDisabled !== '' ? this.labelDisabled : this.parentData.labelDisabled !== null ? this.parentData.labelDisabled :\r\n\t\t\t\t\tfalse;\r\n\t\t\t},\r\n\t\t\t// 组件尺寸，对应size的值，默认值为21px\r\n\t\t\telSize() {\r\n\t\t\t\treturn this.size ? this.size : (this.parentData.size ? this.parentData.size : 21);\r\n\t\t\t},\r\n\t\t\t// 组件的勾选图标的尺寸，默认12px\r\n\t\t\telIconSize() {\r\n\t\t\t\treturn this.iconSize ? this.iconSize : (this.parentData.iconSize ? this.parentData.iconSize : 12);\r\n\t\t\t},\r\n\t\t\t// 组件选中激活时的颜色\r\n\t\t\telActiveColor() {\r\n\t\t\t\treturn this.activeColor ? this.activeColor : (this.parentData.activeColor ? this.parentData.activeColor : '#2979ff');\r\n\t\t\t},\r\n\t\t\t// 组件选未中激活时的颜色\r\n\t\t\telInactiveColor() {\r\n\t\t\t\treturn this.inactiveColor ? this.inactiveColor : (this.parentData.inactiveColor ? this.parentData.inactiveColor :\r\n\t\t\t\t\t'#c8c9cc');\r\n\t\t\t},\r\n\t\t\t// label的颜色\r\n\t\t\telLabelColor() {\r\n\t\t\t\treturn this.labelColor ? this.labelColor : (this.parentData.labelColor ? this.parentData.labelColor : '#606266')\r\n\t\t\t},\r\n\t\t\t// 组件的形状\r\n\t\t\telShape() {\r\n\t\t\t\treturn this.shape ? this.shape : (this.parentData.shape ? this.parentData.shape : 'circle');\r\n\t\t\t},\r\n\t\t\t// label大小\r\n\t\t\telLabelSize() {\r\n\t\t\t\treturn this.$uv.addUnit(this.labelSize ? this.labelSize : (this.parentData.labelSize ? this.parentData.labelSize :\r\n\t\t\t\t\t'15'))\r\n\t\t\t},\r\n\t\t\telIconColor() {\r\n\t\t\t\tconst iconColor = this.iconColor ? this.iconColor : (this.parentData.iconColor ? this.parentData.iconColor :\r\n\t\t\t\t\t'#ffffff');\r\n\t\t\t\t// 图标的颜色\r\n\t\t\t\tif (this.elDisabled) {\r\n\t\t\t\t\t// disabled状态下，已勾选的radio图标改为elInactiveColor\r\n\t\t\t\t\treturn this.checked ? this.elInactiveColor : 'transparent'\r\n\t\t\t\t} else {\r\n\t\t\t\t\treturn this.checked ? iconColor : 'transparent'\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\ticonClasses() {\r\n\t\t\t\tlet classes = []\r\n\t\t\t\t// 组件的形状\r\n\t\t\t\tclasses.push('uv-radio__icon-wrap--' + this.elShape)\r\n\t\t\t\tif (this.elDisabled) {\r\n\t\t\t\t\tclasses.push('uv-radio__icon-wrap--disabled')\r\n\t\t\t\t}\r\n\t\t\t\tif (this.checked && this.elDisabled) {\r\n\t\t\t\t\tclasses.push('uv-radio__icon-wrap--disabled--checked')\r\n\t\t\t\t}\r\n\t\t\t\t// 支付宝，头条小程序无法动态绑定一个数组类名，否则解析出来的结果会带有\",\"，而导致失效\r\n\t\t\t\t// #ifdef MP-ALIPAY || MP-TOUTIAO\r\n\t\t\t\tclasses = classes.join(' ')\r\n\t\t\t\t// #endif\r\n\t\t\t\treturn classes\r\n\t\t\t},\r\n\t\t\ticonWrapStyle() {\r\n\t\t\t\t// radio的整体样式\r\n\t\t\t\tconst style = {}\r\n\t\t\t\tstyle.backgroundColor = this.checked && !this.elDisabled ? this.elActiveColor : '#ffffff'\r\n\t\t\t\tstyle.borderColor = this.checked && !this.elDisabled ? this.elActiveColor : this.elInactiveColor\r\n\t\t\t\tstyle.width = this.$uv.addUnit(this.elSize)\r\n\t\t\t\tstyle.height = this.$uv.addUnit(this.elSize)\r\n\t\t\t\t// 如果是图标在右边的话，移除它的右边距\r\n\t\t\t\tif (this.parentData.iconPlacement === 'right') {\r\n\t\t\t\t\tstyle.marginRight = 0\r\n\t\t\t\t}\r\n\t\t\t\treturn style\r\n\t\t\t},\r\n\t\t\tradioStyle() {\r\n\t\t\t\tconst style = {}\r\n\t\t\t\tif (this.parentData.borderBottom && this.parentData.placement === 'row') {\r\n\t\t\t\t\tthis.$uv.error('检测到您将borderBottom设置为true，需要同时将uv-radio-group的placement设置为column才有效')\r\n\t\t\t\t}\r\n\t\t\t\t// 当父组件设置了显示下边框并且排列形式为纵向时，给内容和边框之间加上一定间隔\r\n\t\t\t\tif (this.parentData.borderBottom && this.parentData.placement === 'column') {\r\n\t\t\t\t\t// ios像素密度高，需要多一点的距离\r\n\t\t\t\t\tstyle.paddingBottom = this.$uv.os() === 'ios' ? '12px' : '8px'\r\n\t\t\t\t}\r\n\t\t\t\treturn this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))\r\n\t\t\t}\r\n\t\t},\r\n\t\tcreated() {\r\n\t\t\tthis.init()\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tinit() {\r\n\t\t\t\t// 支付宝小程序不支持provide/inject，所以使用这个方法获取整个父组件，在created定义，避免循环引用\r\n\t\t\t\tthis.updateParentData()\r\n\t\t\t\tif (!this.parent) {\r\n\t\t\t\t\tthis.$uv.error('uv-radio必须搭配uv-radio-group组件使用')\r\n\t\t\t\t}\r\n\t\t\t\t// 设置初始化时，是否默认选中的状态\r\n\t\t\t\tthis.$nextTick(()=>{\r\n\t\t\t\t\tlet parentValue = null;\r\n\t\t\t\t\t// #ifdef VUE2\r\n\t\t\t\t\tparentValue = this.parentData.value;\r\n\t\t\t\t\t// #endif\r\n\t\t\t\t\t// #ifdef VUE3\r\n\t\t\t\t\tparentValue = this.parentData.modelValue;\r\n\t\t\t\t\t// #endif\r\n\t\t\t\t\tthis.checked = this.name === parentValue\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\tupdateParentData() {\r\n\t\t\t\tthis.getParentData('uv-radio-group')\r\n\t\t\t},\r\n\t\t\t// 点击图标\r\n\t\t\ticonClickHandler(e) {\r\n\t\t\t\tthis.preventEvent(e)\r\n\t\t\t\t// 如果整体被禁用，不允许被点击\r\n\t\t\t\tif (!this.elDisabled) {\r\n\t\t\t\t\tthis.setRadioCheckedStatus()\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t// 横向两端排列时，点击组件即可触发选中事件\r\n\t\t\twrapperClickHandler(e) {\r\n\t\t\t\tthis.parentData.iconPlacement === 'right' && this.iconClickHandler(e)\r\n\t\t\t},\r\n\t\t\t// 点击label\r\n\t\t\tlabelClickHandler(e) {\r\n\t\t\t\tthis.preventEvent(e)\r\n\t\t\t\t// 如果按钮整体被禁用或者label被禁用，则不允许点击文字修改状态\r\n\t\t\t\tif (!this.elLabelDisabled && !this.elDisabled) {\r\n\t\t\t\t\tthis.setRadioCheckedStatus()\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\temitEvent() {\r\n\t\t\t\t// uv-radio的checked不为true时(意味着未选中)，才发出事件，避免多次点击触发事件\r\n\t\t\t\tif (!this.checked) {\r\n\t\t\t\t\tthis.$emit('change', this.name)\r\n\t\t\t\t\t// 尝试调用uv-form的验证方法，进行一定延迟，否则微信小程序更新可能会不及时\r\n\t\t\t\t\tthis.$nextTick(() => {\r\n\t\t\t\t\t\tthis.$uv.formValidate(this, 'change')\r\n\t\t\t\t\t})\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t// 改变组件选中状态\r\n\t\t\t// 这里的改变的依据是，更改本组件的checked值为true，同时通过父组件遍历所有uv-radio实例\r\n\t\t\t// 将本组件外的其他uv-radio的checked都设置为false(都被取消选中状态)，因而只剩下一个为选中状态\r\n\t\t\tsetRadioCheckedStatus() {\r\n\t\t\t\tthis.emitEvent()\r\n\t\t\t\t// 将本组件标记为选中状态\r\n\t\t\t\tthis.checked = true\r\n\t\t\t\ttypeof this.parent.unCheckedOther === 'function' && this.parent.unCheckedOther(this)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t$show-border: 1;\r\n\t$show-border-bottom: 1;\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t$uv-radio-label-wrap-padding-right: 6px !default;\r\n\t$uv-radio-wrap-font-size: 20px !default;\r\n\t$uv-radio-wrap-border-width: 1px !default;\r\n\t$uv-radio-wrap-border-color: #c8c9cc !default;\r\n\t$uv-radio-line-height: 0 !default;\r\n\t$uv-radio-circle-border-radius: 100% !default;\r\n\t$uv-radio-square-border-radius: 3px !default;\r\n\t$uv-radio-checked-color: #fff !default;\r\n\t$uv-radio-checked-background-color: red !default;\r\n\t$uv-radio-checked-border-color: #2979ff !default;\r\n\t$uv-radio-disabled-background-color: #ebedf0 !default;\r\n\t$uv-radio-disabled--checked-color: #c8c9cc !default;\r\n\t$uv-radio-label-margin-left: 5px !default;\r\n\t$uv-radio-label-margin-right: 12px !default;\r\n\t$uv-radio-label-color: $uv-content-color !default;\r\n\t$uv-radio-label-font-size: 15px !default;\r\n\t$uv-radio-label-disabled-color: #c8c9cc !default;\r\n\t.uv-radio {\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\t@include flex(row);\r\n\t\t/* #endif */\r\n\t\toverflow: hidden;\r\n\t\tflex-direction: row;\r\n\t\talign-items: center;\r\n\t\t&-label--left {\r\n\t\t\tflex-direction: row\r\n\t\t}\r\n\t\t&-label--right {\r\n\t\t\tflex-direction: row-reverse;\r\n\t\t\tjustify-content: space-between\r\n\t\t}\r\n\t\t&__icon-wrap {\r\n\t\t\t/* #ifndef APP-NVUE */\r\n\t\t\tbox-sizing: border-box;\r\n\t\t\t// nvue下，border-color过渡有问题\r\n\t\t\ttransition-property: border-color, background-color, color;\r\n\t\t\ttransition-duration: 0.2s;\r\n\t\t\t/* #endif */\r\n\t\t\tcolor: $uv-content-color;\r\n\t\t\t@include flex;\r\n\t\t\talign-items: center;\r\n\t\t\tjustify-content: center;\r\n\t\t\tcolor: transparent;\r\n\t\t\ttext-align: center;\r\n\t\t\tfont-size: $uv-radio-wrap-font-size;\r\n\t\t\tborder-width: $uv-radio-wrap-border-width;\r\n\t\t\tborder-color: $uv-radio-wrap-border-color;\r\n\t\t\tborder-style: solid;\r\n\t\t\t/* #ifdef MP-TOUTIAO */\r\n\t\t\t// 头条小程序兼容性问题，需要设置行高为0，否则图标偏下\r\n\t\t\t&__icon {\r\n\t\t\t\tline-height: $uv-radio-line-height;\r\n\t\t\t}\r\n\t\t\t/* #endif */\r\n\t\t\t&--circle {\r\n\t\t\t\tborder-radius: $uv-radio-circle-border-radius;\r\n\t\t\t}\r\n\t\t\t&--square {\r\n\t\t\t\tborder-radius: $uv-radio-square-border-radius;\r\n\t\t\t}\r\n\t\t\t&--checked {\r\n\t\t\t\tcolor: $uv-radio-checked-color;\r\n\t\t\t\tbackground-color: $uv-radio-checked-background-color;\r\n\t\t\t\tborder-color: $uv-radio-checked-border-color;\r\n\t\t\t}\r\n\t\t\t&--disabled {\r\n\t\t\t\tbackground-color: $uv-radio-disabled-background-color !important;\r\n\t\t\t}\r\n\t\t\t&--disabled--checked {\r\n\t\t\t\tcolor: $uv-radio-disabled--checked-color !important;\r\n\t\t\t}\r\n\t\t}\r\n\t\t&__label {\r\n\t\t\t/* #ifndef APP-NVUE */\r\n\t\t\tword-wrap: break-word;\r\n\t\t\t/* #endif */\r\n\t\t\tmargin-left: $uv-radio-label-margin-left;\r\n\t\t\tmargin-right: $uv-radio-label-margin-right;\r\n\t\t\tcolor: $uv-radio-label-color;\r\n\t\t\tfont-size: $uv-radio-label-font-size;\r\n\t\t\t&--disabled {\r\n\t\t\t\tcolor: $uv-radio-label-disabled-color;\r\n\t\t\t}\r\n\t\t}\r\n\t\t&__label-wrap {\r\n\t\t\tpadding-left: $uv-radio-label-wrap-padding-right;\r\n\t\t}\r\n\t}\r\n</style>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-radio/components/uv-radio-group/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 绑定的值\r\n\t\tvalue: {\r\n\t\t\ttype: [String, Number, Boolean],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\tmodelValue: {\r\n\t\t\ttype: [String, Number, Boolean],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 是否禁用全部radio\r\n\t\tdisabled: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 形状，circle-圆形，square-方形\r\n\t\tshape: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'circle'\r\n\t\t},\r\n\t\t// 选中状态下的颜色，如设置此值，将会覆盖parent的activeColor值\r\n\t\tactiveColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#2979ff'\r\n\t\t},\r\n\t\t// 未选中的颜色\r\n\t\tinactiveColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#c8c9cc'\r\n\t\t},\r\n\t\t// 标识符\r\n\t\tname: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 整个组件的尺寸，默认px\r\n\t\tsize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 18\r\n\t\t},\r\n\t\t// 布局方式，row-横向，column-纵向\r\n\t\tplacement: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'row'\r\n\t\t},\r\n\t\t// label的文本\r\n\t\tlabel: {\r\n\t\t\ttype: [String],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// label的颜色 （默认 '#303133' ）\r\n\t\tlabelColor: {\r\n\t\t\ttype: [String],\r\n\t\t\tdefault: '#303133'\r\n\t\t},\r\n\t\t// label的字体大小，px单位\r\n\t\tlabelSize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 14\r\n\t\t},\r\n\t\t// 是否禁止点击文本操作checkbox(默认 false )\r\n\t\tlabelDisabled: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 图标颜色\r\n\t\ticonColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#fff'\r\n\t\t},\r\n\t\t// 图标的大小，单位px\r\n\t\ticonSize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 12\r\n\t\t},\r\n\t\t// 竖向配列时，是否显示下划线\r\n\t\tborderBottom: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 图标与文字的对齐方式\r\n\t\ticonPlacement: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'left'\r\n\t\t},\r\n\t\t...uni.$uv?.props?.radioGroup\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-radio/components/uv-radio-group/uv-radio-group.vue",
    "content": "<template>\r\n\t<view\r\n\t    class=\"uv-radio-group\"\r\n\t    :class=\"bemClass\"\r\n\t\t\t:style=\"[$uv.addStyle(this.customStyle)]\"\r\n\t>\r\n\t\t<slot></slot>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * radioRroup 单选框父组件\r\n\t * @description 单选框用于有一个选择，用户只能选择其中一个的场景。搭配uv-radio使用\r\n\t * @tutorial https://www.uvui.cn/components/radio.html\r\n\t * @property {String | Number | Boolean}\tvalue \t\t\t绑定的值\r\n\t * @property {Boolean}\t\t\t\t\t\tdisabled\t\t是否禁用所有radio（默认 false ）\r\n\t * @property {String}\t\t\t\t\t\tshape\t\t\t外观形状，shape-方形，circle-圆形(默认 circle )\r\n\t * @property {String}\t\t\t\t\t\tactiveColor\t\t选中时的颜色，应用到所有子Radio组件（默认 '#2979ff' ）\r\n\t * @property {String}\t\t\t\t\t\tinactiveColor\t未选中的颜色 (默认 '#c8c9cc' )\r\n\t * @property {String}\t\t\t\t\t\tname\t\t\t标识符\r\n\t * @property {String | Number}\t\t\t\tsize\t\t\t组件整体的大小，单位px（默认 18 ）\r\n\t * @property {String}\t\t\t\t\t\tplacement\t\t布局方式，row-横向，column-纵向 （默认 'row' ）\r\n\t * @property {String}\t\t\t\t\t\tlabel\t\t\t文本\r\n\t * @property {String}\t\t\t\t\t\tlabelColor\t\tlabel的颜色 （默认 '#303133' ）\r\n\t * @property {String | Number}\t\t\t\tlabelSize\t\tlabel的字体大小，px单位 （默认 14 ）\r\n\t * @property {Boolean}\t\t\t\t\t\tlabelDisabled\t是否禁止点击文本操作checkbox(默认 false )\r\n\t * @property {String}\t\t\t\t\t\ticonColor\t\t图标颜色 （默认 '#ffffff' ）\r\n\t * @property {String | Number}\t\t\t\ticonSize\t\t图标的大小，单位px （默认 12 ）\r\n\t * @property {Boolean}\t\t\t\t\t\tborderBottom\tplacement为row时，是否显示下边框 （默认 false ）\r\n\t * @property {String}\t\t\t\t\t\ticonPlacement\t图标与文字的对齐方式 （默认 'left' ）\r\n     * @property {Object}\t\t\t\t\t\tcustomStyle\t\t组件的样式，对象形式\r\n\t * @event {Function} change 任一个radio状态发生变化时触发\r\n\t * @example <uv-radio-group v-model=\"value\"></uv-radio-group>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-radio-group',\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tcomputed: {\r\n\t\t\t// 这里computed的变量，都是子组件uv-radio需要用到的，由于头条小程序的兼容性差异，子组件无法实时监听父组件参数的变化\r\n\t\t\t// 所以需要手动通知子组件，这里返回一个parentData变量，供watch监听，在其中去通知每一个子组件重新从父组件(uv-radio-group)\r\n\t\t\t// 拉取父组件新的变化后的参数\r\n\t\t\tparentData() {\r\n\t\t\t\tconst value = this.value || this.modelValue;\r\n\t\t\t\treturn [value, this.disabled, this.inactiveColor, this.activeColor, this.size, this.labelDisabled, this.shape,\r\n\t\t\t\t\tthis.iconSize, this.borderBottom, this.placement]\r\n\t\t\t},\r\n\t\t\tbemClass() {\r\n\t\t\t\t// this.bem为一个computed变量，在mixin中\r\n\t\t\t\treturn this.bem('radio-group', ['placement'])\r\n\t\t\t},\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\t// 当父组件需要子组件需要共享的参数发生了变化，手动通知子组件\r\n\t\t\tparentData() {\r\n\t\t\t\tif (this.children.length) {\r\n\t\t\t\t\tthis.children.map(child => {\r\n\t\t\t\t\t\t// 判断子组件(uv-radio)如果有init方法的话，就就执行(执行的结果是子组件重新从父组件拉取了最新的值)\r\n\t\t\t\t\t\ttypeof(child.init) === 'function' && child.init()\r\n\t\t\t\t\t})\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t},\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\r\n\t\t\t}\r\n\t\t},\r\n\t\tcreated() {\r\n\t\t\tthis.children = []\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\t// 将其他的radio设置为未选中的状态\r\n\t\t\tunCheckedOther(childInstance) {\r\n\t\t\t\tthis.children.map(child => {\r\n\t\t\t\t\t// 所有子radio中，被操作组件实例的checked的值无需修改\r\n\t\t\t\t\tif (childInstance !== child) {\r\n\t\t\t\t\t\tchild.checked = false\r\n\t\t\t\t\t}\r\n\t\t\t\t})\r\n\t\t\t\tconst {\r\n\t\t\t\t\tname\r\n\t\t\t\t} = childInstance\r\n\t\t\t\t// 通过emit事件，设置父组件通过v-model双向绑定的值\r\n\t\t\t\t// #ifdef VUE2\r\n\t\t\t\tthis.$emit('input', name)\r\n\t\t\t\t// #endif\r\n\t\t\t\t// #ifdef VUE3\r\n\t\t\t\tthis.$emit('update:modelValue',name)\r\n\t\t\t\t// #endif\r\n\t\t\t\t// 发出事件\r\n\t\t\t\tthis.$emit('change', name)\r\n\t\t\t},\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t.uv-radio-group {\r\n\t\tflex: 1;\r\n\r\n\t\t&--row {\r\n\t\t\t@include flex;\r\n\t\t\tflex-wrap: wrap;\r\n\t\t}\r\n\r\n\t\t&--column {\r\n\t\t\t@include flex(column);\r\n\t\t}\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-radio/package.json",
    "content": "{\r\n  \"id\": \"uv-radio\",\r\n  \"displayName\": \"uv-radio 单选框 全面兼容vue3+2、app、h5、小程序等多端\",\r\n  \"version\": \"1.0.11\",\r\n  \"description\": \"uv-radio 单选框用于有一个选择，用户只能选择其中一个的场景。\",\r\n  \"keywords\": [\r\n    \"uv-radio\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"radio\",\r\n    \"单选框\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-icon\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-radio/readme.md",
    "content": "## Radio 单选框\r\n\r\n> **组件名：uv-radio**\r\n\r\n单选框用于有一个选择，用户只能选择其中一个的场景。\t\r\n\r\n# <a href=\"https://www.uvui.cn/components/radio.html\" target=\"_blank\">查看文档</a>\r\n\r\n## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>（请不要 下载插件ZIP）</small>\r\n\r\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n<a href=\"https://ext.dcloud.net.cn/plugin?name=uv-ui\" target=\"_blank\">\r\n\r\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\r\n\r\n</a>\r\n\r\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-rate/changelog.md",
    "content": "## 1.0.7（2023-08-24）\n1. 修复支付宝不能选半星的BUG\n## 1.0.6（2023-08-23）\n1. 修复支付宝报错的BUG\n## 1.0.5（2023-07-13）\r\n优化代码\r\n## 1.0.4（2023-07-13）\r\n1. 修复value/v-model更改不生效的BUG\r\n## 1.0.3（2023-06-05）\r\n1. 修复只读或禁止状态下设置value无效的问题\r\n## 1.0.2（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.1（2023-05-12）\r\n1. 修复vue3中双向绑定问题\r\n## 1.0.0（2023-05-10）\r\nuv-rate 评分\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-rate/components/uv-rate/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\tvalue: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\tmodelValue: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 要显示的星星数量\r\n\t\tcount: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 5\r\n\t\t},\r\n\t\t// 是否不可选中\r\n\t\tdisabled: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否只读\r\n\t\treadonly: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 星星的大小，单位px\r\n\t\tsize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 18\r\n\t\t},\r\n\t\t// 未选中时的颜色\r\n\t\tinactiveColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#b2b2b2'\r\n\t\t},\r\n\t\t// 选中的颜色\r\n\t\tactiveColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#FA3534'\r\n\t\t},\r\n\t\t// 星星之间的间距，单位px\r\n\t\tgutter: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 4\r\n\t\t},\r\n\t\t// 最少能选择的星星个数\r\n\t\tminCount: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 1\r\n\t\t},\r\n\t\t// 是否允许半星\r\n\t\tallowHalf: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 选中时的图标(星星)\r\n\t\tactiveIcon: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'star-fill'\r\n\t\t},\r\n\t\t// 未选中时的图标(星星)\r\n\t\tinactiveIcon: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'star'\r\n\t\t},\r\n\t\t// 是否可以通过滑动手势选择评分\r\n\t\ttouchable: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t...uni.$uv?.props?.rate\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-rate/components/uv-rate/uv-rate.vue",
    "content": "<template>\r\n\t<view \r\n\t\tclass=\"uv-rate\"\r\n\t\t:id=\"elId\"\r\n\t\tref=\"uv-rate\"\r\n\t\t:style=\"[$uv.addStyle(customStyle)]\"\r\n\t>\r\n\t\t<view class=\"uv-rate__content\"\r\n\t\t\t@touchmove.stop=\"touchMove\"\r\n\t\t\t@touchend.stop=\"touchEnd\">\r\n\t\t\t<view class=\"uv-rate__content__item\"\r\n\t\t\t\tv-for=\"(item, index) in Number(count)\"\r\n\t\t\t\t:key=\"index\"\r\n\t\t\t\t:class=\"[elClass]\">\r\n\t\t\t\t<view class=\"uv-rate__content__item__icon-wrap\"\r\n\t\t\t\t\tref=\"uv-rate__content__item__icon-wrap\"\r\n\t\t\t\t\t@tap.stop=\"clickHandler($event, index + 1)\">\r\n\t\t\t\t\t<uv-icon \r\n\t\t\t\t\t\t:name=\"Math.floor(activeIndex) > index? activeIcon : inactiveIcon\"\r\n\t\t\t\t\t\t:color=\"disabled ? '#c8c9cc' : Math.floor(activeIndex) > index ? activeColor : inactiveColor\"\r\n\t\t\t\t\t\t:custom-style=\"{\r\n              'padding-left': $uv.addUnit(gutter / 2),\r\n\t\t\t\t\t\t\t'padding-right': $uv.addUnit(gutter / 2)\r\n            }\"\r\n\t\t\t\t\t\t:size=\"size\"\r\n\t\t\t\t\t></uv-icon>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view v-if=\"allowHalf\"\r\n\t\t\t\t\t@tap.stop=\"clickHandler($event, index + 1)\"\r\n\t\t\t\t\tclass=\"uv-rate__content__item__icon-wrap uv-rate__content__item__icon-wrap--half\"\r\n\t\t\t\t\t:style=\"[{ width: $uv.addUnit(rateWidth / 2)}]\"\r\n\t\t\t\t\tref=\"uv-rate__content__item__icon-wrap\">\r\n\t\t\t\t\t<uv-icon \r\n\t\t\t\t\t\t:name=\" Math.ceil(activeIndex) > index ? activeIcon : inactiveIcon \"\r\n\t\t\t\t\t\t:color=\" disabled ? '#c8c9cc' : Math.ceil(activeIndex) > index ? activeColor : inactiveColor \"\r\n\t\t\t\t\t\t:custom-style=\"{\r\n\t\t\t\t\t\t\t'padding-left': $uv.addUnit(gutter / 2),\r\n\t\t\t\t\t\t\t'padding-right': $uv.addUnit(gutter / 2)\r\n            }\"\r\n\t\t\t\t\t\t:size=\"size\">\r\n\t\t\t\t\t</uv-icon>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t// #ifdef APP-NVUE\r\n\tconst dom = weex.requireModule(\"dom\");\r\n\t// #endif\r\n\t/**\r\n\t * rate 评分\r\n\t * @description 该组件一般用于满意度调查，星型评分的场景\r\n\t * @tutorial https://www.uvui.cn/components/rate.html\r\n\t * @property {String | Number}\tvalue\t\t\t用于v-model双向绑定选中的星星数量 (默认 1 )\r\n\t * @property {String | Number}\tcount\t\t\t最多可选的星星数量 （默认 5 ）\r\n\t * @property {Boolean}\t\t\tdisabled\t\t是否禁止用户操作 （默认 false ）\r\n\t * @property {Boolean}\t\t\treadonly\t\t是否只读 （默认 false ）\r\n\t * @property {String | Number}\tsize\t\t\t星星的大小，单位px （默认 18 ）\r\n\t * @property {String}\t\t\tinactiveColor\t未选中星星的颜色 （默认 '#b2b2b2' ）\r\n\t * @property {String}\t\t\tactiveColor\t\t选中的星星颜色 （默认 '#FA3534' ）\r\n\t * @property {String | Number}\tgutter\t\t\t星星之间的距离 （默认 4 ）\r\n\t * @property {String | Number}\tminCount\t\t最少选中星星的个数 （默认 1 ）\r\n\t * @property {Boolean}\t\t\tallowHalf\t\t是否允许半星选择 （默认 false ）\r\n\t * @property {String}\t\t\tactiveIcon\t\t选中时的图标名，只能为uvui的内置图标 （默认 'star-fill' ）\r\n\t * @property {String}\t\t\tinactiveIcon\t未选中时的图标名，只能为uvui的内置图标 （默认 'star' ）\r\n\t * @property {Boolean}\t\t\ttouchable\t\t是否可以通过滑动手势选择评分 （默认 'false' ）\r\n\t * @property {Object}\t\t\tcustomStyle\t\t组件的样式，对象形式\r\n\t * @event {Function} change 选中的星星发生变化时触发\r\n\t * @example <uv-rate :count=\"count\" :value=\"2\"></uv-rate>\r\n\t */\r\n\texport default {\r\n\t\tname: \"uv-rate\",\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\t// 生成一个唯一id，否则一个页面多个评分组件，会造成冲突\r\n\t\t\t\telId: '',\r\n\t\t\t\telClass: '',\r\n\t\t\t\trateBoxLeft: 0, // 评分盒子左边到屏幕左边的距离，用于滑动选择时计算距离\r\n\t\t\t\tactiveIndex: 0,\r\n\t\t\t\trateWidth: 0, // 每个星星的宽度\r\n\t\t\t\t// 标识是否正在滑动，由于iOS事件上touch比click先触发，导致快速滑动结束后，接着触发click，导致事件混乱而出错\r\n\t\t\t\tmoving: false\r\n\t\t\t}\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\tvalue(newVal){\r\n\t\t\t\tthis.activeIndex = newVal;\r\n\t\t\t},\r\n\t\t\tmodelValue(newVal){\r\n\t\t\t\tthis.activeIndex = newVal;\r\n\t\t\t}\r\n\t\t},\r\n\t\tcreated() {\r\n\t\t\tthis.activeIndex = Number(this.value || this.modelValue);\r\n\t\t\tthis.elId = this.$uv.guid();\r\n\t\t\tthis.elClass = this.$uv.guid();\r\n\t\t},\r\n\t\tmounted() {\r\n\t\t\tthis.init();\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tinit() {\r\n\t\t\t\tthis.$uv.sleep(200).then(() => {\r\n\t\t\t\t\tthis.getRateItemRect();\r\n\t\t\t\t\tthis.getRateIconWrapRect();\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\t// 获取评分组件盒子的布局信息\r\n\t\t\tasync getRateItemRect() {\r\n\t\t\t\tawait this.$uv.sleep();\r\n\t\t\t\t// uvui封装的获取节点的方法，详见文档\r\n\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\tthis.$uvGetRect(\"#\" + this.elId).then((res) => {\r\n\t\t\t\t\tthis.rateBoxLeft = res.left;\r\n\t\t\t\t});\r\n\t\t\t\t// #endif\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\tdom.getComponentRect(this.$refs[\"uv-rate\"], (res) => {\r\n\t\t\t\t\tthis.rateBoxLeft = res.size.left;\r\n\t\t\t\t});\r\n\t\t\t\t// #endif\r\n\t\t\t},\r\n\t\t\t// 获取单个星星的尺寸\r\n\t\t\tgetRateIconWrapRect() {\r\n\t\t\t\t// uvui封装的获取节点的方法，详见文档\r\n\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\tthis.$uvGetRect(\".\" + this.elClass).then((res) => {\r\n\t\t\t\t\tthis.rateWidth = res.width;\r\n\t\t\t\t});\r\n\t\t\t\t// #endif\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\tdom.getComponentRect(this.$refs[\"uv-rate__content__item__icon-wrap\"][0],\r\n\t\t\t\t\t(res) => {\r\n\t\t\t\t\t\tthis.rateWidth = res.size.width;\r\n\t\t\t\t\t});\r\n\t\t\t\t// #endif\r\n\t\t\t},\r\n\t\t\t// 手指滑动\r\n\t\t\ttouchMove(e) {\r\n\t\t\t\t// 如果禁止通过手动滑动选择，返回\r\n\t\t\t\tif (!this.touchable) {\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\t\t\t\tthis.preventEvent(e);\r\n\t\t\t\tconst x = e.changedTouches && e.changedTouches[0].pageX || e.detail && e.detail.pageX;\r\n\t\t\t\tthis.getActiveIndex(x);\r\n\t\t\t},\r\n\t\t\t// 停止滑动\r\n\t\t\ttouchEnd(e) {\r\n\t\t\t\t// 如果禁止通过手动滑动选择，返回\r\n\t\t\t\tif (!this.touchable) {\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\t\t\t\tthis.preventEvent(e);\r\n\t\t\t\tconst x = e.changedTouches && e.changedTouches[0].pageX || e.detail && e.detail.pageX;\r\n\t\t\t\tthis.getActiveIndex(x);\r\n\t\t\t},\r\n\t\t\t// 通过点击，直接选中\r\n\t\t\tclickHandler(e, index) {\r\n\t\t\t\t// ios上，moving状态取消事件触发\r\n\t\t\t\tif (this.$uv.os() === \"ios\" && this.moving) {\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\t\t\t\tthis.preventEvent(e);\r\n\t\t\t\tlet x = 0;\r\n\t\t\t\t// 点击时，在nvue上，无法获得点击的坐标，所以无法实现点击半星选择\r\n\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\tx = e.changedTouches && e.changedTouches[0].pageX || e.detail && e.detail.pageX;\r\n\t\t\t\t// #endif\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\t// nvue下，无法通过点击获得坐标信息，这里通过元素的位置尺寸值模拟坐标\r\n\t\t\t\tx = index * this.rateWidth + this.rateBoxLeft;\r\n\t\t\t\t// #endif\r\n\t\t\t\tthis.getActiveIndex(x, true);\r\n\t\t\t},\r\n\t\t\t// 发出事件\r\n\t\t\tchangeEvent() {\r\n\t\t\t\tthis.$emit(\"change\", this.activeIndex);\r\n\t\t\t\tthis.$emit(\"input\", this.activeIndex);\r\n\t\t\t\tthis.$emit(\"update:modelValue\", this.activeIndex);\r\n\t\t\t},\r\n\t\t\t// 获取当前激活的评分图标\r\n\t\t\tgetActiveIndex(x, isClick = false) {\r\n\t\t\t\tif (this.disabled || this.readonly) {\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\t\t\t\t// 判断当前操作的点的x坐标值，是否在允许的边界范围内\r\n\t\t\t\tconst allRateWidth = this.rateWidth * this.count + this.rateBoxLeft;\r\n\t\t\t\t// 如果小于第一个图标的左边界，设置为最小值，如果大于所有图标的宽度，则设置为最大值\r\n\t\t\t\tx = this.$uv.range(this.rateBoxLeft, allRateWidth, x) - this.rateBoxLeft\r\n\t\t\t\t// 滑动点相对于评分盒子左边的距离\r\n\t\t\t\tconst distance = x;\r\n\t\t\t\t// 滑动的距离，相当于多少颗星星\r\n\t\t\t\tlet index;\r\n\t\t\t\t// 判断是否允许半星\r\n\t\t\t\tif (this.allowHalf) {\r\n\t\t\t\t\tindex = Math.floor(distance / this.rateWidth);\r\n\t\t\t\t\t// 取余，判断小数的区间范围\r\n\t\t\t\t\tconst decimal = distance % this.rateWidth;\r\n\t\t\t\t\tif (decimal <= this.rateWidth / 2 && decimal > 0) {\r\n\t\t\t\t\t\tindex += 0.5;\r\n\t\t\t\t\t} else if (decimal > this.rateWidth / 2) {\r\n\t\t\t\t\t\tindex++;\r\n\t\t\t\t\t}\r\n\t\t\t\t} else {\r\n\t\t\t\t\tindex = Math.floor(distance / this.rateWidth);\r\n\t\t\t\t\t// 取余，判断小数的区间范围\r\n\t\t\t\t\tconst decimal = distance % this.rateWidth;\r\n\t\t\t\t\t// 非半星时，只有超过了图标的一半距离，才认为是选择了这颗星\r\n\t\t\t\t\tif (isClick) {\r\n\t\t\t\t\t\tif (decimal > 0) index++;\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tif (decimal > this.rateWidth / 2) index++;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tthis.activeIndex = Math.min(index, this.count);\r\n\t\t\t\t// 对最少颗星星的限制\r\n\t\t\t\tif (this.activeIndex < this.minCount) {\r\n\t\t\t\t\tthis.activeIndex = this.minCount;\r\n\t\t\t\t}\r\n\t\t\t\tthis.changeEvent();\r\n\t\t\t\t// 设置延时为了让click事件在touchmove之前触发\r\n\t\t\t\tsetTimeout(() => {\r\n\t\t\t\t\tthis.moving = true;\r\n\t\t\t\t}, 10);\r\n\t\t\t\t// 一定时间后，取消标识为移动中状态，是为了让click事件无效\r\n\t\t\t\tsetTimeout(() => {\r\n\t\t\t\t\tthis.moving = false;\r\n\t\t\t\t}, 10);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t$uv-rate-margin: 0 !default;\r\n\t$uv-rate-padding: 0 !default;\r\n\t$uv-rate-item-icon-wrap-half-top: 0 !default;\r\n\t$uv-rate-item-icon-wrap-half-left: 0 !default;\r\n\t.uv-rate {\r\n\t\t@include flex;\r\n\t\talign-items: center;\r\n\t\tmargin: $uv-rate-margin;\r\n\t\tpadding: $uv-rate-padding;\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\ttouch-action: none;\r\n\t\t/* #endif */\r\n\t\t&__content {\r\n\t\t\t@include flex;\r\n\t\t\t&__item {\r\n\t\t\t\tposition: relative;\r\n\t\t\t\t&__icon-wrap {\r\n\t\t\t\t\t&--half {\r\n\t\t\t\t\t\tposition: absolute;\r\n\t\t\t\t\t\toverflow: hidden;\r\n\t\t\t\t\t\ttop: $uv-rate-item-icon-wrap-half-top;\r\n\t\t\t\t\t\tleft: $uv-rate-item-icon-wrap-half-left;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t.uv-icon {\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\tbox-sizing: border-box;\r\n\t\t/* #endif */\r\n\t}\r\n</style>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-rate/package.json",
    "content": "{\r\n  \"id\": \"uv-rate\",\r\n  \"displayName\": \"uv-rate 评分 全面兼容vue3+2、app、h5、小程序等多端\",\r\n  \"version\": \"1.0.7\",\r\n  \"description\": \"该组件为星型评分，可用于满意度调查等场景...\",\r\n  \"keywords\": [\r\n    \"uv-rate\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"rate\",\r\n    \"评分\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-icon\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-rate/readme.md",
    "content": "## Rate 评分\r\n\r\n> **组件名：uv-rate**\r\n\r\n该组件为星型评分，可用于满意度调查等场景。\t\r\n\r\n# <a href=\"https://www.uvui.cn/components/rate.html\" target=\"_blank\">查看文档</a>\r\n\r\n## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>（请不要 下载插件ZIP）</small>\r\n\r\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n<a href=\"https://ext.dcloud.net.cn/plugin?name=uv-ui\" target=\"_blank\">\r\n\r\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\r\n\r\n</a>\r\n\r\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-read-more/changelog.md",
    "content": "## 1.0.2（2023-09-13）\n1. 修复全局设置rpx，导致展开高度不对的BUG\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-read-more 展开阅读更多\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-read-more/components/uv-read-more/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 默认的显示占位高度\r\n\t\tshowHeight: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 400\r\n\t\t},\r\n\t\t// 展开后是否显示\"收起\"按钮\r\n\t\ttoggle: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 关闭时的提示文字\r\n\t\tcloseText: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '展开阅读全文'\r\n\t\t},\r\n\t\t// 展开时的提示文字\r\n\t\topenText: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '收起'\r\n\t\t},\r\n\t\t// 提示的文字颜色\r\n\t\tcolor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#2979ff'\r\n\t\t},\r\n\t\t// 提示文字的大小\r\n\t\tfontSize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 14\r\n\t\t},\r\n\t\t// 是否显示阴影\r\n\t\t// 此参数不能写在props/readMore.js中进行默认配置，因为使用了条件编译，在外部js中\r\n\t\t// uni无法准确识别当前是否处于nvue还是非nvue下\r\n\t\tshadowStyle: {\r\n\t\t\ttype: Object,\r\n\t\t\tdefault: () => ({\r\n\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\tbackgroundImage: 'linear-gradient(-180deg, rgba(255, 255, 255, 0) 0%, #fff 80%)',\r\n\t\t\t\t// #endif\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\t// nvue上不支持设置复杂的backgroundImage属性\r\n\t\t\t\tbackgroundImage: 'linear-gradient(to top, #fff, rgba(255, 255, 255, 0.5))',\r\n\t\t\t\t// #endif\r\n\t\t\t\tpaddingTop: '100px',\r\n\t\t\t\tmarginTop: '-100px'\r\n\t\t\t})\r\n\t\t},\r\n\t\t// 段落首行缩进的字符个数\r\n\t\ttextIndent: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '2em'\r\n\t\t},\r\n\t\t// open和close事件时，将此参数返回在回调参数中\r\n\t\tname: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t...uni.$uv?.props?.readMore\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-read-more/components/uv-read-more/uv-read-more.vue",
    "content": "<template>\r\n\t<view class=\"uv-read-more\">\r\n\t\t<view\r\n\t\t    class=\"uv-read-more__content\"\r\n\t\t    :style=\"{\r\n\t\t\t\theight: isLongContent && status === 'close' ? $uv.addUnit(showHeight) : $uv.addUnit(contentHeight,'px'),\r\n\t\t\t\ttextIndent: textIndent\r\n\t\t\t}\"\r\n\t\t>\r\n\t\t\t<view\r\n\t\t\t    class=\"uv-read-more__content__inner\"\r\n\t\t\t    ref=\"uv-read-more__content__inner\"\r\n\t\t\t    :class=\"[elId]\"\r\n\t\t\t>\r\n\t\t\t\t<slot></slot>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t\t<view\r\n\t\t    class=\"uv-read-more__toggle\"\r\n\t\t    :style=\"[innerShadowStyle]\"\r\n\t\t    v-if=\"isLongContent\"\r\n\t\t>\r\n\t\t\t<slot name=\"toggle\">\r\n\t\t\t\t<view\r\n\t\t\t\t    class=\"uv-read-more__toggle__text\"\r\n\t\t\t\t    @tap=\"toggleReadMore\"\r\n\t\t\t\t>\r\n\t\t\t\t\t<uv-text\r\n\t\t\t\t\t    :text=\"status === 'close' ? closeText : openText\"\r\n\t\t\t\t\t    :color=\"color\"\r\n\t\t\t\t\t    :size=\"fontSize\"\r\n\t\t\t\t\t    :lineHeight=\"fontSize\"\r\n\t\t\t\t\t    margin=\"0 5px 0 0\"\r\n\t\t\t\t\t></uv-text>\r\n\t\t\t\t\t<view class=\"uv-read-more__toggle__icon\">\r\n\t\t\t\t\t\t<uv-icon\r\n\t\t\t\t\t\t    :color=\"color\"\r\n\t\t\t\t\t\t    :size=\"fontSize + 2\"\r\n\t\t\t\t\t\t    :name=\"status === 'close' ? 'arrow-down' : 'arrow-up'\"\r\n\t\t\t\t\t\t></uv-icon>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</slot>\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\t// #ifdef APP-NVUE\r\n\tconst dom = uni.requireNativePlugin('dom')\r\n\t// #endif\r\n\timport props from './props.js';\r\n\t/**\r\n\t * readMore 阅读更多\r\n\t * @description 该组件一般用于内容较长，预先收起一部分，点击展开全部内容的场景。\r\n\t * @tutorial https://www.uvui.cn/components/readMore.html\r\n\t * @property {String | Number}\tshowHeight\t内容超出此高度才会显示展开全文按钮，单位px（默认 400 ）\r\n\t * @property {Boolean}\t\t\ttoggle\t\t展开后是否显示收起按钮（默认 false ）\r\n\t * @property {String}\t\t\tcloseText\t关闭时的提示文字（默认 '展开阅读全文' ）\r\n\t * @property {String}\t\t\topenText\t展开时的提示文字（默认 '收起' ）\r\n\t * @property {String}\t\t\tcolor\t\t提示文字的颜色（默认 '#2979ff' ）\r\n\t * @property {String | Number}\tfontSize\t提示文字的大小，单位px （默认 14 ）\r\n\t * @property {Object}\t\t\tshadowStyle\t显示阴影的样式\r\n\t * @property {String}\t\t\ttextIndent\t段落首行缩进的字符个数 （默认 '2em' ）\r\n\t * @property {String | Number}\tname\t\t用于在 open 和 close 事件中当作回调参数返回\r\n\t * @event {Function} open 内容被展开时触发\r\n\t * @event {Function} close 内容被收起时触发\r\n\t * @example <uv-read-more><rich-text :nodes=\"content\"></rich-text></uv-read-more>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-read-more',\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tisLongContent: false, // 是否需要隐藏一部分内容\r\n\t\t\t\tstatus: 'close', // 当前隐藏与显示的状态，close-收起状态，open-展开状态\r\n\t\t\t\telId: '', // 生成唯一class\r\n\t\t\t\tcontentHeight: 100, // 内容高度\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\t// 展开后无需阴影，收起时才需要阴影样式\r\n\t\t\tinnerShadowStyle() {\r\n\t\t\t\tif (this.status === 'open') return {}\r\n\t\t\t\telse return this.shadowStyle\r\n\t\t\t}\r\n\t\t},\r\n\t\tcreated() {\r\n\t\t\tthis.elId = this.$uv.guid();\r\n\t\t},\r\n\t\tmounted() {\r\n\t\t\tthis.init()\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tasync init() {\r\n\t\t\t\tthis.getContentHeight().then(height => {\r\n\t\t\t\t\tthis.contentHeight = height\r\n\t\t\t\t\t// 判断高度，如果真实内容高度大于占位高度，则显示收起与展开的控制按钮\r\n\t\t\t\t\tif (height > this.$uv.getPx(this.showHeight)) {\r\n\t\t\t\t\t\tthis.isLongContent = true\r\n\t\t\t\t\t\tthis.status = 'close'\r\n\t\t\t\t\t}\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\t// 获取内容的高度\r\n\t\t\tasync getContentHeight() {\r\n\t\t\t\t// 延时一定时间再获取节点\r\n\t\t\t\tawait this.$uv.sleep(30)\r\n\t\t\t\treturn new Promise(resolve => {\r\n\t\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\t\tthis.$uvGetRect('.' + this.elId).then(res => {\r\n\t\t\t\t\t\tresolve(res.height)\r\n\t\t\t\t\t})\r\n\t\t\t\t\t// #endif\r\n\r\n\t\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\t\tconst ref = this.$refs['uv-read-more__content__inner']\r\n\t\t\t\t\tdom.getComponentRect(ref, (res) => {\r\n\t\t\t\t\t\tresolve(res.size.height)\r\n\t\t\t\t\t})\r\n\t\t\t\t\t// #endif\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\t// 展开或者收起\r\n\t\t\ttoggleReadMore() {\r\n\t\t\t\tthis.status = this.status === 'close' ? 'open' : 'close'\r\n\t\t\t\t// 如果toggle为false，隐藏\"收起\"部分的内容\r\n\t\t\t\tif (this.toggle == false) this.isLongContent = false\r\n\t\t\t\t// 发出打开或者收齐的事件\r\n\t\t\t\tthis.$emit(this.status, this.name)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n.uv-read-more {\r\n\r\n\t&__content {\r\n\t\toverflow: hidden;\r\n\t\tcolor: $uv-content-color;\r\n\t\tfont-size: 15px;\r\n\t\ttext-align: left;\r\n\t}\r\n\r\n\t&__toggle {\r\n\t\t@include flex;\r\n\t\tjustify-content: center;\r\n\r\n\t\t&__text {\r\n\t\t\t@include flex;\r\n\t\t\talign-items: center;\r\n\t\t\tjustify-content: center;\r\n\t\t\tmargin-top: 5px;\r\n\t\t}\r\n\t}\r\n}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-read-more/package.json",
    "content": "{\r\n  \"id\": \"uv-read-more\",\r\n  \"displayName\": \"uv-read-more 展开阅读更多  全面兼容小程序、nvue、vue2、vue3等多端\",\r\n  \"version\": \"1.0.2\",\r\n  \"description\": \"uv-read-more 该组件一般用于内容较长，预先收起一部分，点击展开全部内容的场景。\",\r\n  \"keywords\": [\r\n    \"uv-read-more\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"read\",\r\n    \"展开阅读更多\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-icon\",\r\n\t\t\t\"uv-text\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-read-more/readme.md",
    "content": "## ReadMore 展开阅读更多 \r\n\r\n> **组件名：uv-read-more**\r\n\r\n该组件一般用于内容较长，预先收起一部分，点击展开全部内容的场景。\r\n\r\n### <a href=\"https://www.uvui.cn/components/readMore.html\" target=\"_blank\">查看文档</a>\r\n\r\n### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n#### 如使用过程中有任何问题，或者您对uv-ui有一些好的建议，欢迎加入 uv-ui 交流群：<a href=\"https://ext.dcloud.net.cn/plugin?id=12287\" target=\"_blank\">uv-ui</a>、<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-row/changelog.md",
    "content": "## 1.0.1（2023-05-16）\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\n2. 优化部分功能\n## 1.0.0（2023-05-10）\r\nuv-row layout布局\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-row/components/uv-col/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 占父容器宽度的多少等分，总分为12份\r\n\t\tspan: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 12\r\n\t\t},\r\n\t\t// 指定栅格左侧的间隔数(总12栏)\r\n\t\toffset: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 水平排列方式，可选值为`start`(或`flex-start`)、`end`(或`flex-end`)、`center`、`around`(或`space-around`)、`between`(或`space-between`)\r\n\t\tjustify: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'start'\r\n\t\t},\r\n\t\t// 垂直对齐方式，可选值为top、center、bottom、stretch\r\n\t\talign: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'stretch'\r\n\t\t},\r\n\t\t// 文字对齐方式\r\n\t\ttextAlign: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'left'\r\n\t\t},\r\n\t\t...uni.$uv?.props?.col\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-row/components/uv-col/uv-col.vue",
    "content": "<template>\r\n\t<view\r\n\t    class=\"uv-col\"\r\n\t\tref=\"uv-col\"\r\n\t    :class=\"[\r\n\t\t\t'uv-col-' + span\r\n\t\t]\"\r\n\t    :style=\"[colStyle]\"\r\n\t    @tap=\"clickHandler\"\r\n\t>\r\n\t\t<slot></slot>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * CodeInput 栅格系统的列 \r\n\t * @description 该组件一般用于Layout 布局 通过基础的 12 分栏，迅速简便地创建布局\r\n\t * @tutorial https://www.uvui.cn/components/Layout.html\r\n\t * @property {String | Number}\tspan\t\t栅格占据的列数，总12等份 (默认 12 ) \r\n\t * @property {String | Number}\toffset\t\t分栏左边偏移，计算方式与span相同 (默认 0 ) \r\n\t * @property {String}\t\t\tjustify\t\t水平排列方式，可选值为`start`(或`flex-start`)、`end`(或`flex-end`)、`center`、`around`(或`space-around`)、`between`(或`space-between`)  (默认 'start' ) \r\n\t * @property {String}\t\t\talign\t\t垂直对齐方式，可选值为top、center、bottom、stretch (默认 'stretch' ) \r\n\t * @property {String}\t\t\ttextAlign\t文字水平对齐方式 (默认 'left' ) \r\n\t * @property {Object}\t\t\tcustomStyle\t定义需要用到的外部样式\r\n\t * @event {Function}\tclick\tcol被点击，会阻止事件冒泡到row\r\n\t * @example\t <uv-col  span=\"3\" offset=\"3\" > <view class=\"demo-layout bg-purple\"></view> </uv-col>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-col',\r\n\t\temits: ['click'],\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\twidth: 0,\r\n\t\t\t\tparentData: {\r\n\t\t\t\t\tgutter: 0\r\n\t\t\t\t},\r\n\t\t\t\tgridNum: 12\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\tuJustify() {\r\n\t\t\t\tif (this.justify == 'end' || this.justify == 'start') return 'flex-' + this.justify\r\n\t\t\t\telse if (this.justify == 'around' || this.justify == 'between') return 'space-' + this.justify\r\n\t\t\t\telse return this.justify\r\n\t\t\t},\r\n\t\t\tuAlignItem() {\r\n\t\t\t\tif (this.align == 'top') return 'flex-start'\r\n\t\t\t\tif (this.align == 'bottom') return 'flex-end'\r\n\t\t\t\telse return this.align\r\n\t\t\t},\r\n\t\t\tcolStyle() {\r\n\t\t\t\tconst style = {\r\n\t\t\t\t\t// 这里写成\"padding: 0 10px\"的形式是因为nvue的需要\r\n\t\t\t\t\tpaddingLeft: this.$uv.addUnit(this.$uv.getPx(this.parentData.gutter)/2),\r\n\t\t\t\t\tpaddingRight: this.$uv.addUnit(this.$uv.getPx(this.parentData.gutter)/2),\r\n\t\t\t\t\talignItems: this.uAlignItem,\r\n\t\t\t\t\tjustifyContent: this.uJustify,\r\n\t\t\t\t\ttextAlign: this.textAlign,\r\n\t\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\t\t// 在非nvue上，使用百分比形式\r\n\t\t\t\t\tflex: `0 0 ${100 / this.gridNum * this.span}%`,\r\n\t\t\t\t\tmarginLeft: 100 / 12 * this.offset + '%',\r\n\t\t\t\t\t// #endif\r\n\t\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\t\t// 在nvue上，由于无法使用百分比单位，这里需要获取父组件的宽度，再计算得出该有对应的百分比尺寸\r\n\t\t\t\t\twidth: this.$uv.addUnit(Math.floor(this.width / this.gridNum * Number(this.span))),\r\n\t\t\t\t\tmarginLeft: this.$uv.addUnit(Math.floor(this.width / this.gridNum * Number(this.offset))),\r\n\t\t\t\t\t// #endif\r\n\t\t\t\t}\r\n\t\t\t\treturn this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))\r\n\t\t\t}\r\n\t\t},\r\n\t\tmounted() {\r\n\t\t\tthis.init()\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tasync init() {\r\n\t\t\t\t// 支付宝小程序不支持provide/inject，所以使用这个方法获取整个父组件，在created定义，避免循环引用\r\n\t\t\t\tthis.updateParentData()\r\n\t\t\t\tthis.width = await this.parent.getComponentWidth()\r\n\t\t\t},\r\n\t\t\tupdateParentData() {\r\n\t\t\t\tthis.getParentData('uv-row')\r\n\t\t\t},\r\n\t\t\tclickHandler(e) {\r\n\t\t\t\tthis.$emit('click');\r\n\t\t\t}\r\n\t\t},\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\r\n\t.uv-col {\r\n\t\tpadding: 0;\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\tbox-sizing:border-box;\r\n\t\t/* #endif */\r\n\t\t/* #ifdef MP */\r\n\t\tdisplay: block;\r\n\t\t/* #endif */\r\n\t}\r\n\r\n\t// nvue下百分比无效\r\n\t/* #ifndef APP-NVUE */\r\n\t.uv-col-0 {\r\n\t\twidth: 0;\r\n\t}\r\n\r\n\t.uv-col-1 {\r\n\t\twidth: calc(100%/12);\r\n\t}\r\n\r\n\t.uv-col-2 {\r\n\t\twidth: calc(100%/12 * 2);\r\n\t}\r\n\r\n\t.uv-col-3 {\r\n\t\twidth: calc(100%/12 * 3);\r\n\t}\r\n\r\n\t.uv-col-4 {\r\n\t\twidth: calc(100%/12 * 4);\r\n\t}\r\n\r\n\t.uv-col-5 {\r\n\t\twidth: calc(100%/12 * 5);\r\n\t}\r\n\r\n\t.uv-col-6 {\r\n\t\twidth: calc(100%/12 * 6);\r\n\t}\r\n\r\n\t.uv-col-7 {\r\n\t\twidth: calc(100%/12 * 7);\r\n\t}\r\n\r\n\t.uv-col-8 {\r\n\t\twidth: calc(100%/12 * 8);\r\n\t}\r\n\r\n\t.uv-col-9 {\r\n\t\twidth: calc(100%/12 * 9);\r\n\t}\r\n\r\n\t.uv-col-10 {\r\n\t\twidth: calc(100%/12 * 10);\r\n\t}\r\n\r\n\t.uv-col-11 {\r\n\t\twidth: calc(100%/12 * 11);\r\n\t}\r\n\r\n\t.uv-col-12 {\r\n\t\twidth: calc(100%/12 * 12);\r\n\t}\r\n\r\n\t/* #endif */\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-row/components/uv-row/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 给col添加间距，左右边距各占一半\r\n\t\tgutter: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 水平排列方式，可选值为`start`(或`flex-start`)、`end`(或`flex-end`)、`center`、`around`(或`space-around`)、`between`(或`space-between`)\r\n\t\tjustify: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'start'\r\n\t\t},\r\n\t\t// 垂直对齐方式，可选值为top、center、bottom\r\n\t\talign: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'center'\r\n\t\t},\r\n\t\t...uni.$uv?.props?.row\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-row/components/uv-row/uv-row.vue",
    "content": "<template>\r\n\t<view\r\n\t    class=\"uv-row\"\r\n\t\tref=\"uv-row\"\r\n\t    :style=\"[rowStyle]\"\r\n\t    @tap=\"clickHandler\"\r\n\t>\r\n\t\t<slot />\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\t// #ifdef APP-NVUE\r\n\tconst dom = uni.requireNativePlugin('dom')\r\n\t// #endif\r\n\timport props from './props.js';\r\n\t/**\r\n\t * Row 栅格系统中的行\r\n\t * @description 通过基础的 12 分栏，迅速简便地创建布局 \r\n\t * @tutorial https://www.uvui.cn/components/layout.html\r\n\t * @property {String | Number}\tgutter\t\t栅格间隔，左右各为此值的一半，单位px  (默认 0 )\r\n\t * @property {String}\t\t\tjustify\t\t水平排列方式(微信小程序暂不支持) 可选值为`start`(或`flex-start`)、`end`(或`flex-end`)、`center`、`around`(或`space-around`)、`between`(或`space-between`)  (默认 'start' )\r\n\t * @property {String}\t\t\talign\t\t垂直排列方式 (默认 'center' )\r\n\t * @property {Object}\t\t\tcustomStyle\t定义需要用到的外部样式\r\n\t * \r\n\t * @event {Function} click row被点击\r\n\t * @example <uv-row justify=\"space-between\" customStyle=\"margin-bottom: 10px\"></uv-row>\r\n\t */\r\n\texport default {\r\n\t\tname: \"uv-row\",\r\n\t\temits: ['click'],\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\t\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\tuJustify() {\r\n\t\t\t\tif (this.justify == 'end' || this.justify == 'start') return 'flex-' + this.justify\r\n\t\t\t\telse if (this.justify == 'around' || this.justify == 'between') return 'space-' + this.justify\r\n\t\t\t\telse return this.justify\r\n\t\t\t},\r\n\t\t\tuAlignItem() {\r\n\t\t\t\tif (this.align == 'top') return 'flex-start'\r\n\t\t\t\tif (this.align == 'bottom') return 'flex-end'\r\n\t\t\t\telse return this.align\r\n\t\t\t},\r\n\t\t\trowStyle() {\r\n\t\t\t\tconst style = {\r\n\t\t\t\t\talignItems: this.uAlignItem,\r\n\t\t\t\t\tjustifyContent: this.uJustify\r\n\t\t\t\t}\r\n\t\t\t\t// 通过给uv-row左右两边的负外边距，消除uv-col在有gutter时，第一个和最后一个元素的左内边距和右内边距造成的影响\r\n\t\t\t\tif(this.gutter) {\r\n\t\t\t\t\tstyle.marginLeft = this.$uv.addUnit(-Number(this.gutter)/2)\r\n\t\t\t\t\tstyle.marginRight = this.$uv.addUnit(-Number(this.gutter)/2)\r\n\t\t\t\t}\r\n\t\t\t\treturn this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tclickHandler(e) {\r\n\t\t\t\tthis.$emit('click')\r\n\t\t\t},\r\n\t\t\tasync getComponentWidth() {\r\n\t\t\t\t// 延时一定时间，以确保节点渲染完成\r\n\t\t\t\tawait this.$uv.sleep()\r\n\t\t\t\treturn new Promise(resolve => {\r\n\t\t\t\t\t// uvui封装的获取节点的方法，详见文档\r\n\t\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\t\tthis.$uvGetRect('.uv-row').then(res => {\r\n\t\t\t\t\t\tresolve(res.width)\r\n\t\t\t\t\t})\r\n\t\t\t\t\t// #endif\r\n\t\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\t\t// nvue的dom模块用于获取节点\r\n\t\t\t\t\tdom.getComponentRect(this.$refs['uv-row'], (res) => {\r\n\t\t\t\t\t\tresolve(res.size.width)\r\n\t\t\t\t\t})\r\n\t\t\t\t\t// #endif\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t.uv-row {\r\n\t\t@include flex;\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-row/package.json",
    "content": "{\r\n  \"id\": \"uv-row\",\r\n  \"displayName\": \"uv-row layout布局  全面兼容小程序、nvue、vue2、vue3等多端\",\r\n  \"version\": \"1.0.1\",\r\n  \"description\": \"uv-row 通过基础的 12 分栏，迅速简便地创建布局\",\r\n  \"keywords\": [\r\n    \"uv-row\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"layout\",\r\n    \"布局\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-row/readme.md",
    "content": "## Layout 布局 \r\n\r\n> **组件名：uv-row**\r\n\r\n通过基础的 12 分栏，迅速简便地创建布局\r\n\r\n### <a href=\"https://www.uvui.cn/components/layout.html\" target=\"_blank\">查看文档</a>\r\n\r\n### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n#### 如使用过程中有任何问题，或者您对uv-ui有一些好的建议，欢迎加入 uv-ui 交流群：<a href=\"https://ext.dcloud.net.cn/plugin?id=12287\" target=\"_blank\">uv-ui</a>、<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-safe-bottom/changelog.md",
    "content": "## 1.0.4（2023-09-14）\n1. 飞书小程序支持\n## 1.0.3（2023-08-14）\r\n1. 修复百度报错的BUG\r\n## 1.0.2（2023-07-02）\r\nuv-safe-bottom 修复，在百度程序，抖音小程序不生效的BUG\r\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-safe-bottom 底部安全区组件\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-safe-bottom/components/uv-safe-bottom/uv-safe-bottom.vue",
    "content": "<template>\r\n\t<view\r\n\t\tclass=\"uv-safe-bottom\"\r\n\t\t:style=\"[style]\"\r\n\t\t:class=\"[!isNvue && 'uv-safe-area-inset-bottom']\"\r\n\t>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\t/**\r\n\t * SafeBottom 底部安全区\r\n\t * @description 这个适配，主要是针对IPhone X等一些底部带指示条的机型，指示条的操作区域与页面底部存在重合，容易导致用户误操作，因此我们需要针对这些机型进行底部安全区适配。\r\n\t * @tutorial https://www.uvui.cn/components/safeAreaInset.html\r\n\t * @property {type}\t\tprop_name\r\n\t * @property {Object}\tcustomStyle\t定义需要用到的外部样式\r\n\t *\r\n\t * @event {Function()}\r\n\t * @example <uv-status-bar></uv-status-bar>\r\n\t */\r\n\texport default {\r\n\t\tname: \"uv-safe-bottom\",\r\n\t\tmixins: [mpMixin, mixin],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tsafeAreaBottomHeight: 0,\r\n\t\t\t\tisNvue: false,\r\n\t\t\t};\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\tstyle() {\r\n\t\t\t\tconst style = {};\r\n\t\t\t\t// #ifdef APP-NVUE || MP-TOUTIAO || MP-LARK\r\n\t\t\t\t// nvue下，高度使用js计算填充\r\n\t\t\t\tstyle.height = this.$uv.addUnit(this.$uv.sys()?.safeAreaInsets?.bottom, 'px');\r\n\t\t\t\t// #endif\r\n\t\t\t\treturn this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle));\r\n\t\t\t},\r\n\t\t},\r\n\t\tmounted() {\r\n\t\t\t// #ifdef APP-NVUE\r\n\t\t\t// 标识为是否nvue\r\n\t\t\tthis.isNvue = true;\r\n\t\t\t// #endif\r\n\t\t},\r\n\t};\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t.uv-safe-bottom {\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\twidth: 100%;\r\n\t\t/* #endif */\r\n\t}\r\n\t/* #ifndef APP-NVUE */\r\n\t// 历遍生成4个方向的底部安全区\r\n\t@each $d in top, right, bottom, left {\r\n\t\t.uv-safe-area-inset-#{$d} {\r\n\t\t\tpadding-#{$d}: 0;\r\n\t\t\tpadding-#{$d}: constant(safe-area-inset-#{$d});  \r\n\t\t\tpadding-#{$d}: env(safe-area-inset-#{$d});  \r\n\t\t}\r\n\t}\r\n\t/* #endif */\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-safe-bottom/package.json",
    "content": "{\r\n  \"id\": \"uv-safe-bottom\",\r\n  \"displayName\": \"uv-safe-bottom 底部安全区  全面兼容小程序、nvue、vue2、vue3等多端\",\r\n  \"version\": \"1.0.4\",\r\n  \"description\": \"这个适配，主要是针对IPhone X等一些底部带指示条的机型，指示条的操作区域与页面底部存在重合，容易导致用户误操作，因此我们需要针对这些机型进行底部安全区适配。\",\r\n  \"keywords\": [\r\n    \"uv-safe-bottom\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"bottom\",\r\n    \"底部安全区\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-safe-bottom/readme.md",
    "content": "## SafeBottom 底部安全区 \r\n\r\n> **组件名：uv-safe-bottom**\r\n\r\n这个适配，主要是针对IPhone X等一些底部带指示条的机型，指示条的操作区域与页面底部存在重合，容易导致用户误操作，因此我们需要针对这些机型进行底部安全区适配。\r\n\r\n### <a href=\"https://www.uvui.cn/guide/safeAreaInset.html\" target=\"_blank\">查看文档</a>\r\n\r\n### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n#### 如使用过程中有任何问题，或者您对uv-ui有一些好的建议，欢迎加入 uv-ui 交流群：<a href=\"https://ext.dcloud.net.cn/plugin?id=12287\" target=\"_blank\">uv-ui</a>、<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-scroll-list/changelog.md",
    "content": "## 1.0.3（2023-08-19）\n1.  修复报错导致不能移动指示器的BUG\n## 1.0.2（2023-08-08）\n1. 修复vue2编译报错的BUG\n## 1.0.1（2023-07-21）\r\n1. 优化\r\n## 1.0.0（2023-07-21）\r\n1. 新增uv-scroll-liast 横向滚动表组件\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-scroll-list/components/uv-scroll-list/nvue.js",
    "content": "// 引入bindingx，此库类似于微信小程序wxs，目的是让js运行在视图层，减少视图层和逻辑层的通信折损\r\nconst BindingX = uni.requireNativePlugin('bindingx')\r\nexport default {\r\n\tmethods: {\r\n\t\t// 此处不写注释，请自行体会\r\n\t\tnvueScrollHandler(e) {\r\n\t\t\tif (this.indicator) {\r\n\t\t\t\tconst anchor = this.$refs['uv-scroll-list__scroll-view'].ref\r\n\t\t\t\tconst element = this.$refs['uv-scroll-list__indicator__line__bar'].ref\r\n\t\t\t\tconst scrollLeft = e.contentOffset.x\r\n\t\t\t\tconst contentSize = e.contentSize.width\r\n\t\t\t\tconst { scrollWidth } = this\r\n\t\t\t\tconst barAllMoveWidth = this.indicatorWidth - this.indicatorBarWidth\r\n\t\t\t\t// 在安卓和iOS上，需要除的倍数不一样，iOS需要除以2\r\n\t\t\t\tconst actionNum = this.$uv.os() === 'ios' ? 2 : 1\r\n\t\t\t\tconst expression = `(x / ${actionNum}) / ${contentSize - scrollWidth} * ${barAllMoveWidth}`\r\n\t\t\t\tBindingX.bind({\r\n\t\t\t\t\tanchor,\r\n\t\t\t\t\teventType: 'scroll',\r\n\t\t\t\t\tprops: [{\r\n\t\t\t\t\t\telement,\r\n\t\t\t\t\t\tproperty: 'transform.translateX',\r\n\t\t\t\t\t\texpression\r\n\t\t\t\t\t}]\r\n\t\t\t\t})\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-scroll-list/components/uv-scroll-list/other.js",
    "content": ""
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-scroll-list/components/uv-scroll-list/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 指示器的整体宽度\r\n\t\tindicatorWidth: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 50\r\n\t\t},\r\n\t\t// 滑块的宽度\r\n\t\tindicatorBarWidth: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 20\r\n\t\t},\r\n\t\t// 是否显示面板指示器\r\n\t\tindicator: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 指示器非激活颜色\r\n\t\tindicatorColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#f2f2f2'\r\n\t\t},\r\n\t\t// 指示器的激活颜色\r\n\t\tindicatorActiveColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#3c9cff'\r\n\t\t},\r\n\t\t// 指示器样式，可通过bottom，left，right进行定位\r\n\t\tindicatorStyle: {\r\n\t\t\ttype: [String, Object],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t...uni.$uv?.props?.scrollList\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-scroll-list/components/uv-scroll-list/scrollWxs.wxs",
    "content": "function scroll(event, ownerInstance) {\r\n\t// detail中含有scroll-view的信息，比如scroll-view的实际宽度，当前时间点scroll-view的移动距离等\r\n\tvar detail = event.detail\r\n\tvar scrollWidth = detail.scrollWidth\r\n\tvar scrollLeft = detail.scrollLeft\r\n\t// 获取当前组件的dataset，说白了就是祸国殃民的腾xun搞出来的垃ji\r\n\tvar dataset = event.currentTarget.dataset\r\n\t// 此为scroll-view外部包裹元素的宽度\r\n\t// 某些HX版本(3.1.18)，发现view元素中大写的data-scrollWidth，在wxs中，变成了全部小写，所以这里需要特别处理\r\n\tvar scrollComponentWidth = dataset.scrollWidth || dataset.scrollwidth || 0\r\n\t// 指示器和滑块的宽度\r\n\tvar indicatorWidth = dataset.indicatorWidth || dataset.indicatorwidth || 0\r\n\tvar barWidth = dataset.barWidth || dataset.barwidth || 0\r\n\t// 此处的计算理由为：scroll-view的滚动距离与目标滚动距离(scroll-view的实际宽度减去包裹元素的宽度)之比，等于滑块当前移动距离与总需\r\n\t// 滑动距离(指示器的总宽度减去滑块宽度)的比值\r\n\tvar x = scrollLeft / (scrollWidth - scrollComponentWidth) * (indicatorWidth - barWidth)\r\n\tsetBarStyle(ownerInstance, x)\r\n}\r\n\r\n// 由于webview的无能，无法保证scroll-view在滑动过程中，一直触发scroll事件，会导致\r\n// 无法监听到某些滚动值，当在首尾临界值无法监听到时，这是致命的，因为错失这些值会导致滑块无法回到起点和终点\r\n// 所以这里需要对临界值做监听并处理\r\nfunction scrolltolower(event, ownerInstance) {\r\n\townerInstance.callMethod('scrollEvent', 'right')\r\n\t// 获取当前组件的dataset\r\n\tvar dataset = event.currentTarget.dataset\r\n\t// 指示器和滑块的宽度\r\n\tvar indicatorWidth = dataset.indicatorWidth || dataset.indicatorwidth || 0\r\n\tvar barWidth = dataset.barWidth || dataset.barwidth || 0\r\n\t// scroll-view滚动到右边终点时，将滑块也设置为到右边的终点，它所需移动的距离为：指示器宽度 - 滑块宽度\r\n\tsetBarStyle(ownerInstance, indicatorWidth - barWidth)\r\n}\r\n\r\nfunction scrolltoupper(event, ownerInstance) {\r\n\townerInstance.callMethod('scrollEvent', 'left')\r\n\t// 滚动到左边时，将滑块设置为0的偏移距离，回到起点\r\n\tsetBarStyle(ownerInstance, 0)\r\n}\r\n\r\nfunction setBarStyle(ownerInstance, x) {\r\n\townerInstance.selectComponent('.uv-scroll-list__indicator__line__bar') && ownerInstance.selectComponent('.uv-scroll-list__indicator__line__bar').setStyle({\r\n\t\ttransform: 'translateX(' + x + 'px)'\r\n\t})\r\n}\r\n\r\nmodule.exports = {\r\n\tscroll: scroll,\r\n\tscrolltolower: scrolltolower,\r\n\tscrolltoupper: scrolltoupper\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-scroll-list/components/uv-scroll-list/uv-scroll-list.vue",
    "content": "<template>\r\n\t<view\r\n\t\tclass=\"uv-scroll-list\"\r\n\t\tref=\"uv-scroll-list\"\r\n\t>\r\n\t\t<!-- #ifdef APP-NVUE -->\r\n\t\t<!-- nvue使用bindingX实现，以得到更好的性能 -->\r\n\t\t<scroller\r\n\t\t\tclass=\"uv-scroll-list__scroll-view\"\r\n\t\t\tref=\"uv-scroll-list__scroll-view\"\r\n\t\t\tscroll-direction=\"horizontal\"\r\n\t\t\t:show-scrollbar=\"false\"\r\n\t\t\t:offset-accuracy=\"1\"\r\n\t\t\t@scroll=\"nvueScrollHandler\"\r\n\t\t>\r\n\t\t\t<view class=\"uv-scroll-list__scroll-view__content\">\r\n\t\t\t\t<slot />\r\n\t\t\t</view>\r\n\t\t</scroller>\r\n\t\t<!-- #endif -->\r\n\t\t<!-- #ifndef APP-NVUE -->\r\n\t\t<!-- #ifdef MP-WEIXIN || APP-VUE || H5 || MP-QQ -->\r\n\t\t<!-- 以上平台，支持wxs -->\r\n\t\t<scroll-view\r\n\t\t\tclass=\"uv-scroll-list__scroll-view\"\r\n\t\t\tscroll-x\r\n\t\t\t@scroll=\"wxs.scroll\"\r\n\t\t\t@scrolltoupper=\"wxs.scrolltoupper\"\r\n\t\t\t@scrolltolower=\"wxs.scrolltolower\"\r\n\t\t\t:data-scrollWidth=\"scrollWidth\"\r\n\t\t\t:data-barWidth=\"$uv.getPx(indicatorBarWidth)\"\r\n\t\t\t:data-indicatorWidth=\"$uv.getPx(indicatorWidth)\"\r\n\t\t\t:show-scrollbar=\"false\"\r\n\t\t\t:upper-threshold=\"0\"\r\n\t\t\t:lower-threshold=\"0\"\r\n\t\t>\r\n\t\t\t<!-- #endif -->\r\n\t\t\t<!-- #ifndef APP-NVUE || MP-WEIXIN || H5 || APP-VUE || MP-QQ -->\r\n\t\t\t<!-- 非以上平台，只能使用普通js实现 -->\r\n\t\t\t<scroll-view\r\n\t\t\t\tclass=\"uv-scroll-list__scroll-view\"\r\n\t\t\t\tscroll-x\r\n\t\t\t\t@scroll=\"scrollHandler\"\r\n\t\t\t\t@scrolltoupper=\"scrolltoupperHandler\"\r\n\t\t\t\t@scrolltolower=\"scrolltolowerHandler\"\r\n\t\t\t\t:show-scrollbar=\"false\"\r\n\t\t\t\t:upper-threshold=\"0\"\r\n\t\t\t\t:lower-threshold=\"0\"\r\n\t\t\t>\r\n\t\t\t\t<!-- #endif -->\r\n\t\t\t\t<view class=\"uv-scroll-list__scroll-view__content\">\r\n\t\t\t\t\t<slot />\r\n\t\t\t\t</view>\r\n\t\t\t</scroll-view>\r\n\t\t\t<!-- #endif -->\r\n\t\t\t<view\r\n\t\t\t\tclass=\"uv-scroll-list__indicator\"\r\n\t\t\t\tv-if=\"indicator\"\r\n\t\t\t\t:style=\"[$uv.addStyle(indicatorStyle)]\"\r\n\t\t\t>\r\n\t\t\t\t<view\r\n\t\t\t\t\tclass=\"uv-scroll-list__indicator__line\"\r\n\t\t\t\t\t:style=\"[lineStyle]\"\r\n\t\t\t\t>\r\n\t\t\t\t\t<view\r\n\t\t\t\t\t\tclass=\"uv-scroll-list__indicator__line__bar\"\r\n\t\t\t\t\t\t:style=\"[barStyle]\"\r\n\t\t\t\t\t\tref=\"uv-scroll-list__indicator__line__bar\"\r\n\t\t\t\t\t></view>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t\t<!-- 避免报错 -->\r\n\t\t\t<!-- #ifdef H5 -->\r\n\t\t\t<view v-else class=\"uv-scroll-list__indicator__line__bar\"></view>\r\n\t\t\t<!-- #endif -->\r\n\t</view>\r\n</template>\r\n<script src=\"./scrollWxs.wxs\" module=\"wxs\" lang=\"wxs\"></script>\r\n<script>\r\n\t/**\r\n\t * scrollList 横向滚动列表\r\n\t * @description 该组件一般用于同时展示多个商品、分类的场景，也可以完成左右滑动的列表。\r\n\t * @tutorial https://www.uviewui.com/components/scrollList.html\r\n\t * @property {String | Number}\tindicatorWidth\t\t\t指示器的整体宽度 (默认 50 )\r\n\t * @property {String | Number}\tindicatorBarWidth\t\t滑块的宽度 (默认 20 )\r\n\t * @property {Boolean}\t\t\tindicator\t\t\t\t是否显示面板指示器 (默认 true )\r\n\t * @property {String}\t\t\tindicatorColor\t\t\t指示器非激活颜色 (默认 '#f2f2f2' )\r\n\t * @property {String}\t\t\tindicatorActiveColor\t指示器的激活颜色 (默认 '#3c9cff' )\r\n\t * @property {String | Object}\tindicatorStyle\t\t\t指示器样式，可通过bottom，left，right进行定位\r\n\t * @event {Function} left\t滑动到左边时触发\r\n\t * @event {Function} right\t滑动到右边时触发\r\n\t * @example\r\n\t */\r\n\t// #ifdef APP-NVUE\r\n\tconst dom = uni.requireNativePlugin('dom')\r\n\timport nvueMixin from \"./nvue.js\"\r\n\t// #endif\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\texport default {\r\n\t\tname: 'uv-scroll-list',\r\n\t\t// #ifndef APP-NVUE\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\t// #endif\r\n\t\t// #ifdef APP-NVUE\r\n\t\tmixins: [mpMixin, mixin, nvueMixin, props],\r\n\t\t// #endif\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tscrollInfo: {\r\n\t\t\t\t\tscrollLeft: 0,\r\n\t\t\t\t\tscrollWidth: 0\r\n\t\t\t\t},\r\n\t\t\t\tscrollWidth: 0\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\t// 指示器为线型的样式\r\n\t\t\tbarStyle() {\r\n\t\t\t\tconst style = {}\r\n\t\t\t\t// #ifndef APP-NVUE || MP-WEIXIN || H5 || APP-VUE || MP-QQ\r\n\t\t\t\t// 此为普通js方案，只有在非nvue和不支持wxs方案的端才使用、\r\n\t\t\t\t// 此处的计算理由为：scroll-view的滚动距离与目标滚动距离(scroll-view的实际宽度减去包裹元素的宽度)之比，等于滑块当前移动距离与总需\r\n\t\t\t\t// 滑动距离(指示器的总宽度减去滑块宽度)的比值\r\n\t\t\t\tconst scrollLeft = this.scrollInfo.scrollLeft,\r\n\t\t\t\t\tscrollWidth = this.scrollInfo.scrollWidth,\r\n\t\t\t\t\tbarAllMoveWidth = this.indicatorWidth - this.indicatorBarWidth\r\n\t\t\t\tconst x = scrollLeft / (scrollWidth - this.scrollWidth) * barAllMoveWidth\r\n\t\t\t\tstyle.transform = `translateX(${ x }px)`\r\n\t\t\t\t// #endif\r\n\t\t\t\t// 设置滑块的宽度和背景色，是每个平台都需要的\r\n\t\t\t\tstyle.width = this.$uv.addUnit(this.indicatorBarWidth)\r\n\t\t\t\tstyle.backgroundColor = this.indicatorActiveColor\r\n\t\t\t\treturn style\r\n\t\t\t},\r\n\t\t\tlineStyle() {\r\n\t\t\t\tconst style = {}\r\n\t\t\t\t// 指示器整体的样式，需要设置其宽度和背景色\r\n\t\t\t\tstyle.width = this.$uv.addUnit(this.indicatorWidth)\r\n\t\t\t\tstyle.backgroundColor = this.indicatorColor\r\n\t\t\t\treturn style\r\n\t\t\t}\r\n\t\t},\r\n\t\tmounted() {\r\n\t\t\tthis.init()\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tinit() {\r\n\t\t\t\tthis.getComponentWidth()\r\n\t\t\t},\r\n\t\t\t// #ifndef APP-NVUE || MP-WEIXIN || H5 || APP-VUE || MP-QQ\r\n\t\t\t// scroll-view触发滚动事件\r\n\t\t\tscrollHandler(e) {\r\n\t\t\t\tthis.scrollInfo = e.detail\r\n\t\t\t},\r\n\t\t\tscrolltoupperHandler() {\r\n\t\t\t\tthis.scrollEvent('left')\r\n\t\t\t\tthis.scrollInfo.scrollLeft = 0\r\n\t\t\t},\r\n\t\t\tscrolltolowerHandler() {\r\n\t\t\t\tthis.scrollEvent('right')\r\n\t\t\t\t// 在普通js方案中，滚动到右边时，通过设置this.scrollInfo，模拟出滚动到右边的情况\r\n\t\t\t\t// 因为上方是用过computed计算的，设置后，会自动调整滑块的位置\r\n\t\t\t\tthis.scrollInfo.scrollLeft = this.$uv.getPx(this.indicatorWidth) - this.$uv.getPx(this.indicatorBarWidth)\r\n\t\t\t},\r\n\t\t\t// #endif\r\n\t\t\tscrollEvent(status) {\r\n\t\t\t\tthis.$emit(status)\r\n\t\t\t},\r\n\t\t\t// 获取组件的宽度\r\n\t\t\tasync getComponentWidth() {\r\n\t\t\t\t// 延时一定时间，以获取dom尺寸\r\n\t\t\t\tawait this.$uv.sleep(30)\r\n\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\tthis.$uvGetRect('.uv-scroll-list').then(size => {\r\n\t\t\t\t\tthis.scrollWidth = size.width\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\tconst ref = this.$refs['uv-scroll-list']\r\n\t\t\t\tref && dom.getComponentRect(ref, (res) => {\r\n\t\t\t\t\tthis.scrollWidth = res.size.width\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\t\t\t},\r\n\t\t}\r\n\t}\r\n</script>\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t.uv-scroll-list {\r\n\t\tpadding-bottom: 10px;\r\n\t\t&__scroll-view {\r\n\t\t\t@include flex;\r\n\t\t\t&__content {\r\n\t\t\t\t@include flex;\r\n\t\t\t}\r\n\t\t}\r\n\t\t&__indicator {\r\n\t\t\t@include flex;\r\n\t\t\tjustify-content: center;\r\n\t\t\tmargin-top: 15px;\r\n\t\t\t&__line {\r\n\t\t\t\twidth: 60px;\r\n\t\t\t\theight: 4px;\r\n\t\t\t\tborder-radius: 100px;\r\n\t\t\t\toverflow: hidden;\r\n\t\t\t\t&__bar {\r\n\t\t\t\t\twidth: 20px;\r\n\t\t\t\t\theight: 4px;\r\n\t\t\t\t\tborder-radius: 100px;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</style>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-scroll-list/package.json",
    "content": "{\r\n\t\"id\": \"uv-scroll-list\",\r\n\t\"displayName\": \"uv-scroll-list 横向滚动列表 全面兼容vue3+2、app、h5、小程序等多端\",\r\n\t\"version\": \"1.0.3\",\r\n\t\"description\": \"该组件一般用于同时展示多个商品、分类的场景，也可以完成左右滑动的列表，往往数量不确定，数量较多支持左右滚动。灵活配置，开箱即用。\",\r\n\t\"keywords\": [\r\n        \"uv-scroll-list\",\r\n        \"uvui\",\r\n        \"uv-ui\",\r\n        \"横向滚动列表\",\r\n        \"scroll-view\"\r\n    ],\r\n\t\"repository\": \"\",\r\n\t\"engines\": {\r\n\t\t\"HBuilderX\": \"^3.1.0\"\r\n\t},\r\n\t\"dcloudext\": {\r\n\t\t\"type\": \"component-vue\",\r\n\t\t\"sale\": {\r\n\t\t\t\"regular\": {\r\n\t\t\t\t\"price\": \"0.00\"\r\n\t\t\t},\r\n\t\t\t\"sourcecode\": {\r\n\t\t\t\t\"price\": \"0.00\"\r\n\t\t\t}\r\n\t\t},\r\n\t\t\"contact\": {\r\n\t\t\t\"qq\": \"\"\r\n\t\t},\r\n\t\t\"declaration\": {\r\n\t\t\t\"ads\": \"无\",\r\n\t\t\t\"data\": \"无\",\r\n\t\t\t\"permissions\": \"无\"\r\n\t\t},\r\n\t\t\"npmurl\": \"\"\r\n\t},\r\n\t\"uni_modules\": {\r\n\t\t\"dependencies\": [\r\n\t\t\t\"uv-ui-tools\"\r\n\t\t],\r\n\t\t\"encrypt\": [],\r\n\t\t\"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-scroll-list/readme.md",
    "content": "## ScrollList 横向滚动列表\r\n\r\n> **组件名：uv-scroll-list**\r\n\r\n该组件一般用于同时展示多个商品、分类的场景，也可以完成左右滑动的列表，往往数量不确定，数量较多支持左右滚动。灵活配置，开箱即用。\r\n\r\n# <a href=\"https://www.uvui.cn/components/scrollList.html\" target=\"_blank\">查看文档</a>\r\n\r\n## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>（请不要 下载插件ZIP）</small>\r\n\r\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n<a href=\"https://ext.dcloud.net.cn/plugin?name=uv-ui\" target=\"_blank\">\r\n\r\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\r\n\r\n</a>\r\n\r\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-search/changelog.md",
    "content": "## 1.0.9（2023-08-18）\n1. 修复boxStyle失效的BUG\n## 1.0.8（2023-08-07）\n修复值为null或undefined时显示错误的bug\n## 1.0.7（2023-08-05）\n1.  修复在vue2模式下，v-model设置为0时不生效的BUG\n## 1.0.6（2023-07-26）\n1. 增加boxStyle参数，方便控制输入框部分的样式\n## 1.0.5（2023-07-26）\n1. 增加prefix和suffix  前置和后置插槽\n## 1.0.4（2023-07-13）\n1. 修复value值清空不生效的BUG\n## 1.0.3（2023-07-13）\n1. 修复value/v-model更改不生效的BUG\n## 1.0.2（2023-07-03）\n去除无用的插槽\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-search 搜索\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-search/components/uv-search/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\tvalue: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\tmodelValue: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 搜索框形状，round-圆形，square-方形\r\n\t\tshape: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'round'\r\n\t\t},\r\n\t\t// 搜索框背景色，默认值#f2f2f2\r\n\t\tbgColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#f2f2f2'\r\n\t\t},\r\n\t\t// 占位提示文字\r\n\t\tplaceholder: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '请输入关键字'\r\n\t\t},\r\n\t\t// 是否启用清除控件\r\n\t\tclearabled: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 是否自动聚焦\r\n\t\tfocus: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否在搜索框右侧显示取消按钮\r\n\t\tshowAction: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 右边控件的样式\r\n\t\tactionStyle: {\r\n\t\t\ttype: Object,\r\n\t\t\tdefault: () => ({})\r\n\t\t},\r\n\t\t// 取消按钮文字\r\n\t\tactionText: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '搜索'\r\n\t\t},\r\n\t\t// 输入框内容对齐方式，可选值为 left|center|right\r\n\t\tinputAlign: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'left'\r\n\t\t},\r\n\t\t// input输入框的样式，可以定义文字颜色，大小等，对象形式\r\n\t\tinputStyle: {\r\n\t\t\ttype: Object,\r\n\t\t\tdefault: () => ({})\r\n\t\t},\r\n\t\t// 是否禁用输入框\r\n\t\tdisabled: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 边框颜色\r\n\t\tborderColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'transparent'\r\n\t\t},\r\n\t\t// 搜索图标的颜色，默认同输入框字体颜色\r\n\t\tsearchIconColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#909399'\r\n\t\t},\r\n\t\t// 输入框字体颜色\r\n\t\tcolor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#606266'\r\n\t\t},\r\n\t\t// placeholder的颜色\r\n\t\tplaceholderColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#909399'\r\n\t\t},\r\n\t\t// 左边输入框的图标，可以为uvui图标名称或图片路径\r\n\t\tsearchIcon: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'search'\r\n\t\t},\r\n\t\tsearchIconSize: {\r\n\t\t\ttype: [Number, String],\r\n\t\t\tdefault: 22\r\n\t\t},\r\n\t\t// 组件与其他上下左右元素之间的距离，带单位的字符串形式，如\"30px\"、\"30px 20px\"等写法\r\n\t\tmargin: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '0'\r\n\t\t},\r\n\t\t// 开启showAction时，是否在input获取焦点时才显示\r\n\t\tanimation: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 输入框最大能输入的长度，-1为不限制长度(来自uniapp文档)\r\n\t\tmaxlength: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: -1\r\n\t\t},\r\n\t\t// 搜索框高度，单位px\r\n\t\theight: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 32\r\n\t\t},\r\n\t\t// 搜索框左侧文本\r\n\t\tlabel: {\r\n\t\t\ttype: [String, Number, null],\r\n\t\t\tdefault: null\r\n\t\t},\r\n\t\t// 搜索框扩展样式\r\n\t\tboxStyle: {\r\n\t\t\ttype: [String, Object],\r\n\t\t\tdefault: () => ({})\r\n\t\t},\r\n\t\t...uni.$uv?.props?.search\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-search/components/uv-search/uv-search.vue",
    "content": "<template>\r\n\t<view\r\n\t  class=\"uv-search\"\r\n\t  @tap=\"clickHandler\"\r\n\t  :style=\"[{\r\n\t\t\tmargin: margin,\r\n\t\t}, $uv.addStyle(customStyle)]\"\r\n\t>\r\n\t\t<view\r\n\t\t  class=\"uv-search__content\"\r\n\t\t  :style=\"[{\r\n\t\t\t\tbackgroundColor: bgColor,\r\n\t\t\t\tborderRadius: shape == 'round' ? '100px' : '4px',\r\n\t\t\t\tborderColor: borderColor,\r\n\t\t\t},$uv.addStyle(boxStyle)]\"\r\n\t\t>\r\n\t\t\t<slot name=\"prefix\">\r\n\t\t\t\t<view class=\"uv-search__content__icon\">\r\n\t\t\t\t\t<uv-icon\r\n\t\t\t\t\t\t@tap=\"clickIcon\"\r\n\t\t\t\t\t  :size=\"searchIconSize\"\r\n\t\t\t\t\t  :name=\"searchIcon\"\r\n\t\t\t\t\t  :color=\"searchIconColor ? searchIconColor : color\"\r\n\t\t\t\t\t></uv-icon>\r\n\t\t\t\t</view>\r\n\t\t\t</slot>\r\n\t\t\t<input\r\n\t\t\t  confirm-type=\"search\"\r\n\t\t\t  @blur=\"blur\"\r\n\t\t\t  :value=\"keyword\"\r\n\t\t\t  @confirm=\"search\"\r\n\t\t\t  @input=\"inputChange\"\r\n\t\t\t  :disabled=\"disabled\"\r\n\t\t\t  @focus=\"getFocus\"\r\n\t\t\t  :focus=\"focus\"\r\n\t\t\t  :maxlength=\"maxlength\"\r\n\t\t\t  placeholder-class=\"uv-search__content__input--placeholder\"\r\n\t\t\t  :placeholder=\"placeholder\"\r\n\t\t\t  :placeholder-style=\"`color: ${placeholderColor}`\"\r\n\t\t\t  class=\"uv-search__content__input\"\r\n\t\t\t  type=\"text\"\r\n\t\t\t  :style=\"[{\r\n\t\t\t\t\ttextAlign: inputAlign,\r\n\t\t\t\t\tcolor: color,\r\n\t\t\t\t\tbackgroundColor: bgColor,\r\n\t\t\t\t\theight: $uv.addUnit(height)\r\n\t\t\t\t}, inputStyle]\"\r\n\t\t\t/>\r\n\t\t\t<view\r\n\t\t\t  class=\"uv-search__content__icon uv-search__content__close\"\r\n\t\t\t  v-if=\"keyword && clearabled && focused\"\r\n\t\t\t  @tap=\"clear\"\r\n\t\t\t>\r\n\t\t\t\t<uv-icon\r\n\t\t\t\t  name=\"close\"\r\n\t\t\t\t  size=\"11\"\r\n\t\t\t\t  color=\"#ffffff\"\r\n\t\t\t\t\tcustomStyle=\"line-height: 12px\"\r\n\t\t\t\t></uv-icon>\r\n\t\t\t</view>\r\n\t\t\t<slot name=\"suffix\"></slot>\r\n\t\t</view>\r\n\t\t<text\r\n\t\t  :style=\"[actionStyle]\"\r\n\t\t  class=\"uv-search__action\"\r\n\t\t  :class=\"[(showActionBtn || show) && 'uv-search__action--active']\"\r\n\t\t  @tap.stop.prevent=\"custom\"\r\n\t\t>{{ actionText }}</text>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * search 搜索框\r\n\t * @description 搜索组件，集成了常见搜索框所需功能，用户可以一键引入，开箱即用。\r\n\t * @tutorial https://www.uvui.cn/components/search.html\r\n\t * @property {String}\t\t\tvalue/v-model\t\t\t\t输入框初始值\r\n\t * @property {String}\t\t\tshape\t\t\t\t搜索框形状，round-圆形，square-方形（默认 'round' ）\r\n\t * @property {String}\t\t\tbgColor\t\t\t\t搜索框背景颜色（默认 '#f2f2f2' ）\r\n\t * @property {String}\t\t\tplaceholder\t\t\t占位文字内容（默认 '请输入关键字' ）\r\n\t * @property {Boolean}\t\t\tclearabled\t\t\t是否启用清除控件（默认 true ）\r\n\t * @property {Boolean}\t\t\tfocus\t\t\t\t是否自动获得焦点（默认 false ）\r\n\t * @property {Boolean}\t\t\tshowAction\t\t\t是否显示右侧控件（默认 true ）\r\n\t * @property {Object}\t\t\tactionStyle\t\t\t右侧控件的样式，对象形式\r\n\t * @property {String}\t\t\tactionText\t\t\t右侧控件文字（默认 '搜索' ）\r\n\t * @property {String}\t\t\tinputAlign\t\t\t输入框内容水平对齐方式 （默认 'left' ）\r\n\t * @property {Object}\t\t\tinputStyle\t\t\t自定义输入框样式，对象形式\r\n\t * @property {Boolean}\t\t\tdisabled\t\t\t是否启用输入框（默认 false ）\r\n\t * @property {String}\t\t\tborderColor\t\t\t边框颜色，配置了颜色，才会有边框 (默认 'transparent' )\r\n\t * @property {String}\t\t\tsearchIconColor\t\t搜索图标的颜色，默认同输入框字体颜色 (默认 '#909399' )\r\n\t * @property {Number | String}\tsearchIconSize 搜索图标的字体，默认22\r\n\t * @property {String}\t\t\tcolor\t\t\t\t输入框字体颜色（默认 '#606266' ）\r\n\t * @property {String}\t\t\tplaceholderColor\tplaceholder的颜色（默认 '#909399' ）\r\n\t * @property {String}\t\t\tsearchIcon\t\t\t输入框左边的图标，可以为uvui图标名称或图片路径  (默认 'search' )\r\n\t * @property {String}\t\t\tmargin\t\t\t\t组件与其他上下左右元素之间的距离，带单位的字符串形式，如\"30px\"   (默认 '0' )\r\n\t * @property {Boolean} \t\t\tanimation\t\t\t是否开启动画，见上方说明（默认 false ）\r\n\t * @property {String | Number}\tmaxlength\t\t\t输入框最大能输入的长度，-1为不限制长度  (默认 '-1' )\r\n\t * @property {String | Number}\theight\t\t\t\t输入框高度，单位px（默认 64 ）\r\n\t * @property {String | Number}\tlabel\t\t\t\t搜索框左边显示内容\r\n\t * @property {Object} boxStyle 自定义内容框样式，设置padding \r\n\t * @property {Object}\t\t\tcustomStyle\t\t\t定义需要用到的外部样式\r\n\t *\r\n\t * @event {Function} change 输入框内容发生变化时触发\r\n\t * @event {Function} search 用户确定搜索时触发，用户按回车键，或者手机键盘右下角的\"搜索\"键时触发\r\n\t * @event {Function} custom 用户点击右侧控件时触发\r\n\t * @event {Function} clear 用户点击清除按钮时触发\r\n\t * @example <uv-search placeholder=\"日照香炉生紫烟\" v-model=\"keyword\"></uv-search>\r\n\t */\r\n\texport default {\r\n\t\tname: \"uv-search\",\r\n\t\temits: ['click', 'input', 'change', 'clear', 'search', 'custom', 'focus', 'blur', 'clickIcon', 'update:modelValue'],\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tkeyword: '',\r\n\t\t\t\tshowClear: false, // 是否显示右边的清除图标\r\n\t\t\t\tshow: false,\r\n\t\t\t\t// 标记input当前状态是否处于聚焦中，如果是，才会显示右侧的清除控件\r\n\t\t\t\tfocused: this.focus\r\n\t\t\t};\r\n\t\t},\r\n\t\tcreated() {\r\n\t\t\t// #ifndef VUE3\r\n\t\t\tthis.keyword = this.value;\r\n\t\t\t// #endif\r\n\t\t\t// #ifdef VUE3\r\n\t\t\tthis.keyword = this.modelValue;\r\n\t\t\t// #endif\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\tvalue(nVal){\r\n\t\t\t\tthis.keyword = nVal;\r\n\t\t\t},\r\n\t\t\tmodelValue(nVal){\r\n\t\t\t\tthis.keyword = nVal;\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\tshowActionBtn() {\r\n\t\t\t\treturn !this.animation && this.showAction\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tkeywordChange(){\r\n\t\t\t\tthis.$emit('input', this.keyword)\r\n\t\t\t\tthis.$emit('update:modelValue', this.keyword)\r\n\t\t\t\tthis.$emit('change', this.keyword);\r\n\t\t\t},\r\n\t\t\t// 目前HX2.6.9 v-model双向绑定无效，故监听input事件获取输入框内容的变化\r\n\t\t\tinputChange(e) {\r\n\t\t\t\tthis.keyword = e.detail.value;\r\n\t\t\t\tthis.keywordChange();\r\n\t\t\t},\r\n\t\t\t// 清空输入\r\n\t\t\t// 也可以作为用户通过this.$refs形式调用清空输入框内容\r\n\t\t\tclear() {\r\n\t\t\t\tthis.keyword = '';\r\n\t\t\t\tthis.$nextTick(() => {\r\n\t\t\t\t\tthis.$emit('clear');\r\n\t\t\t\t})\r\n\t\t\t\tthis.keywordChange();\r\n\t\t\t},\r\n\t\t\t// 确定搜索\r\n\t\t\tsearch(e) {\r\n\t\t\t\tthis.$emit('search', e.detail.value);\r\n\t\t\t\ttry {\r\n\t\t\t\t\t// 收起键盘\r\n\t\t\t\t\tuni.hideKeyboard();\r\n\t\t\t\t} catch (e) {}\r\n\t\t\t},\r\n\t\t\t// 点击右边自定义按钮的事件\r\n\t\t\tcustom() {\r\n\t\t\t\tthis.$emit('custom', this.keyword);\r\n\t\t\t\ttry {\r\n\t\t\t\t\t// 收起键盘\r\n\t\t\t\t\tuni.hideKeyboard();\r\n\t\t\t\t} catch (e) {}\r\n\t\t\t},\r\n\t\t\t// 获取焦点\r\n\t\t\tgetFocus() {\r\n\t\t\t\tthis.focused = true;\r\n\t\t\t\t// 开启右侧搜索按钮展开的动画效果\r\n\t\t\t\tif (this.animation && this.showAction) this.show = true;\r\n\t\t\t\tthis.$emit('focus', this.keyword);\r\n\t\t\t},\r\n\t\t\t// 失去焦点\r\n\t\t\tblur() {\r\n\t\t\t\t// 最开始使用的是监听图标@touchstart事件，自从hx2.8.4后，此方法在微信小程序出错\r\n\t\t\t\t// 这里改为监听点击事件，手点击清除图标时，同时也发生了@blur事件，导致图标消失而无法点击，这里做一个延时\r\n\t\t\t\tsetTimeout(() => {\r\n\t\t\t\t\tthis.focused = false;\r\n\t\t\t\t}, 100)\r\n\t\t\t\tthis.show = false;\r\n\t\t\t\tthis.$emit('blur', this.keyword);\r\n\t\t\t},\r\n\t\t\t// 点击搜索框，只有disabled=true时才发出事件，因为禁止了输入，意味着是想跳转真正的搜索页\r\n\t\t\tclickHandler() {\r\n\t\t\t\tif (this.disabled) this.$emit('click');\r\n\t\t\t},\r\n\t\t\t// 点击左边图标\r\n\t\t\tclickIcon() {\r\n\t\t\t\tthis.$emit('clickIcon');\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t$uv-search-content-padding: 0 10px !default;\r\n\t$uv-search-label-color: $uv-main-color !default;\r\n\t$uv-search-label-font-size: 14px !default;\r\n\t$uv-search-label-margin: 0 4px !default;\r\n\t$uv-search-close-size: 20px !default;\r\n\t$uv-search-close-radius: 100px !default;\r\n\t$uv-search-close-bgColor: #C6C7CB !default;\r\n\t$uv-search-close-transform: scale(0.82) !default;\r\n\t$uv-search-input-font-size: 14px !default;\r\n\t$uv-search-input-margin: 0 5px !default;\r\n\t$uv-search-input-color: $uv-main-color !default;\r\n\t$uv-search-input-placeholder-color: $uv-tips-color !default;\r\n\t$uv-search-action-font-size: 14px !default;\r\n\t$uv-search-action-color: $uv-main-color !default;\r\n\t$uv-search-action-width: 0 !default;\r\n\t$uv-search-action-active-width: 40px !default;\r\n\t$uv-search-action-margin-left: 5px !default;\r\n\t/* #ifdef H5 */\r\n\t// iOS15在H5下，hx的某些版本，input type=search时，会多了一个搜索图标，进行移除\r\n\t[type=\"search\"]::-webkit-search-decoration {\r\n\t\tdisplay: none;\r\n\t}\r\n\t/* #endif */\r\n\t.uv-search {\r\n\t\t@include flex(row);\r\n\t\talign-items: center;\r\n\t\tflex: 1;\r\n\t\t&__content {\r\n\t\t\t@include flex;\r\n\t\t\talign-items: center;\r\n\t\t\tpadding: $uv-search-content-padding;\r\n\t\t\tflex: 1;\r\n\t\t\tjustify-content: space-between;\r\n\t\t\tborder-width: 1px;\r\n\t\t\tborder-color: transparent;\r\n\t\t\tborder-style: solid;\r\n\t\t\toverflow: hidden;\r\n\t\t\t&__icon {\r\n\t\t\t\t@include flex;\r\n\t\t\t\talign-items: center;\r\n\t\t\t}\r\n\t\t\t&__label {\r\n\t\t\t\tcolor: $uv-search-label-color;\r\n\t\t\t\tfont-size: $uv-search-label-font-size;\r\n\t\t\t\tmargin: $uv-search-label-margin;\r\n\t\t\t}\r\n\t\t\t&__close {\r\n\t\t\t\twidth: $uv-search-close-size;\r\n\t\t\t\theight: $uv-search-close-size;\r\n\t\t\t\tborder-top-left-radius: $uv-search-close-radius;\r\n\t\t\t\tborder-top-right-radius: $uv-search-close-radius;\r\n\t\t\t\tborder-bottom-left-radius: $uv-search-close-radius;\r\n\t\t\t\tborder-bottom-right-radius: $uv-search-close-radius;\r\n\t\t\t\tbackground-color: $uv-search-close-bgColor;\r\n\t\t\t\t@include flex(row);\r\n\t\t\t\talign-items: center;\r\n\t\t\t\tjustify-content: center;\r\n\t\t\t\ttransform: $uv-search-close-transform;\r\n\t\t\t}\r\n\t\t\t&__input {\r\n\t\t\t\tflex: 1;\r\n\t\t\t\tfont-size: $uv-search-input-font-size;\r\n\t\t\t\tline-height: 1;\r\n\t\t\t\tmargin: $uv-search-input-margin;\r\n\t\t\t\tcolor: $uv-search-input-color;\r\n\t\t\t\t&--placeholder {\r\n\t\t\t\t\tcolor: $uv-search-input-placeholder-color;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t&__action {\r\n\t\t\tfont-size: $uv-search-action-font-size;\r\n\t\t\tcolor: $uv-search-action-color;\r\n\t\t\twidth: $uv-search-action-width;\r\n\t\t\toverflow: hidden;\r\n\t\t\ttransition-property: width;\r\n\t\t\ttransition-duration: 0.3s;\r\n\t\t\t/* #ifndef APP-NVUE */\r\n\t\t\twhite-space: nowrap;\r\n\t\t\t/* #endif */\r\n\t\t\ttext-align: center;\r\n\t\t\t&--active {\r\n\t\t\t\twidth: $uv-search-action-active-width;\r\n\t\t\t\tmargin-left: $uv-search-action-margin-left;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</style>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-search/package.json",
    "content": "{\r\n  \"id\": \"uv-search\",\r\n  \"displayName\": \"uv-search 搜索 全面兼容vue3+2、app、h5、小程序等多端\",\r\n  \"version\": \"1.0.9\",\r\n  \"description\": \"搜索组件，集成了常见搜索框所需功能，支持前后插槽，用户可以一键引入，开箱即用\",\r\n  \"keywords\": [\r\n    \"uv-search\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"search\",\r\n    \"搜索\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-icon\",\r\n\t\t\t\"uv-loading-icon\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-search/readme.md",
    "content": "## Search 搜索 \r\n\r\n> **组件名：uv-search**\r\n\r\n搜索组件，集成了常见搜索框所需功能，支持前后插槽，用户可以一键引入，开箱即用。\r\n\r\n# <a href=\"https://www.uvui.cn/components/search.html\" target=\"_blank\">查看文档</a>\r\n\r\n## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <span>（请不要 下载插件ZIP）</span>\r\n\r\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n<a href=\"https://ext.dcloud.net.cn/plugin?name=uv-ui\" target=\"_blank\">\r\n\r\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\r\n\r\n</a>\r\n\r\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-skeleton/changelog.md",
    "content": "## 1.0.1（2023-05-16）\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\n2. 优化部分功能\n## 1.0.0（2023-05-10）\r\nuv-skeleton 骨架屏\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-skeleton/components/uv-skeleton/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 是否展示骨架组件\r\n\t\tloading: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 是否开启动画效果\r\n\t\tanimate: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 段落占位图行数\r\n\t\trows: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 段落占位图的宽度\r\n\t\trowsWidth: {\r\n\t\t\ttype: [String, Number, Array],\r\n\t\t\tdefault: '100%'\r\n\t\t},\r\n\t\t// 段落占位图的高度\r\n\t\trowsHeight: {\r\n\t\t\ttype: [String, Number, Array],\r\n\t\t\tdefault: 18\r\n\t\t},\r\n\t\t// 是否展示标题占位图\r\n\t\ttitle: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 段落标题的宽度\r\n\t\ttitleWidth: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: '50%'\r\n\t\t},\r\n\t\t// 段落标题的高度\r\n\t\ttitleHeight: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 18\r\n\t\t},\r\n\t\t// 是否展示头像占位图\r\n\t\tavatar: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 头像占位图大小\r\n\t\tavatarSize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 32\r\n\t\t},\r\n\t\t// 头像占位图的形状，circle-圆形，square-方形\r\n\t\tavatarShape: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'circle'\r\n\t\t},\r\n\t\t...uni.$uv?.props?.skeleton\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-skeleton/components/uv-skeleton/uv-skeleton.vue",
    "content": "<template>\r\n\t<view class=\"uv-skeleton\">\r\n\t\t<view\r\n\t\t    class=\"uv-skeleton__wrapper\"\r\n\t\t    ref=\"uv-skeleton__wrapper\"\r\n\t\t    v-if=\"loading\"\r\n\t\t\tstyle=\"display: flex; flex-direction: row;\"\r\n\t\t>\r\n\t\t\t<view\r\n\t\t\t    class=\"uv-skeleton__wrapper__avatar\"\r\n\t\t\t    v-if=\"avatar\"\r\n\t\t\t    :class=\"[`uv-skeleton__wrapper__avatar--${avatarShape}`, animate && 'animate']\"\r\n\t\t\t    :style=\"{\r\n\t\t\t\t\t\theight: $uv.addUnit(avatarSize),\r\n\t\t\t\t\t\twidth: $uv.addUnit(avatarSize)\r\n\t\t\t\t\t}\"\r\n\t\t\t></view>\r\n\t\t\t<view\r\n\t\t\t    class=\"uv-skeleton__wrapper__content\"\r\n\t\t\t    ref=\"uv-skeleton__wrapper__content\"\r\n\t\t\t\tstyle=\"flex: 1;\"\r\n\t\t\t>\r\n\t\t\t\t<view\r\n\t\t\t\t    class=\"uv-skeleton__wrapper__content__title\"\r\n\t\t\t\t    v-if=\"title\"\r\n\t\t\t\t    :style=\"{\r\n\t\t\t\t\t\t\twidth: uTitleWidth,\r\n\t\t\t\t\t\t\theight: $uv.addUnit(titleHeight),\r\n\t\t\t\t\t\t}\"\r\n\t\t\t\t    :class=\"[animate && 'animate']\"\r\n\t\t\t\t></view>\r\n\t\t\t\t<view\r\n\t\t\t\t    class=\"uv-skeleton__wrapper__content__rows\"\r\n\t\t\t\t    :class=\"[animate && 'animate']\"\r\n\t\t\t\t    v-for=\"(item, index) in rowsArray\"\r\n\t\t\t\t    :key=\"index\"\r\n\t\t\t\t    :style=\"{\r\n\t\t\t\t\t\t\t width: item.width,\r\n\t\t\t\t\t\t\t height: item.height,\r\n\t\t\t\t\t\t\t marginTop: item.marginTop\r\n\t\t\t\t\t\t}\"\r\n\t\t\t\t>\r\n\t\t\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t\t<slot v-else />\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t// #ifdef APP-NVUE\r\n\t// 由于weex为阿里的KPI业绩考核的产物，所以不支持百分比单位，这里需要通过dom查询组件的宽度\r\n\tconst dom = uni.requireNativePlugin('dom')\r\n\tconst animation = uni.requireNativePlugin('animation')\r\n\t// #endif\r\n\t/**\r\n\t * Skeleton 骨架屏\r\n\t * @description 骨架屏一般用于页面在请求远程数据尚未完成时，页面用灰色块预显示本来的页面结构，给用户更好的体验。\r\n\t * @tutorial https://www.uvui.cn/components/skeleton.html\r\n\t * @property {Boolean}\t\t\t\t\tloading\t\t是否显示骨架占位图，设置为false将会展示子组件内容 (默认 true )\r\n\t * @property {Boolean}\t\t\t\t\tanimate\t\t是否开启动画效果 (默认 true )\r\n\t * @property {String | Number}\t\t\trows\t\t段落占位图行数 (默认 0 )\r\n\t * @property {String | Number | Array}\trowsWidth\t段落占位图的宽度，可以为百分比，数值，带单位字符串等，可通过数组传入指定每个段落行的宽度 (默认 '100%' )\r\n\t * @property {String | Number | Array}\trowsHeight\t段落的高度 (默认 18 )\r\n\t * @property {Boolean}\t\t\t\t\ttitle\t\t是否展示标题占位图 (默认 true )\r\n\t * @property {String | Number}\t\t\ttitleWidth\t标题的宽度 (默认 '50%' )\r\n\t * @property {String | Number}\t\t\ttitleHeight\t标题的高度 (默认 18 )\r\n\t * @property {Boolean}\t\t\t\t\tavatar\t\t是否展示头像占位图 (默认 false )\r\n\t * @property {String | Number}\t\t\tavatarSize\t头像占位图大小 (默认 32 )\r\n\t * @property {String}\t\t\t\t\tavatarShape\t头像占位图的形状，circle-圆形，square-方形 (默认 'circle' )\r\n\t * @example <uv-search placeholder=\"日照香炉生紫烟\" v-model=\"keyword\"></uv-search>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-skeleton',\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\twidth: 0,\r\n\t\t\t}\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\tloading() {\r\n\t\t\t\tthis.getComponentWidth()\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\trowsArray() {\r\n\t\t\t\tif (/%$/.test(this.rowsHeight)) {\r\n\t\t\t\t\tthis.$uv.error('rowsHeight参数不支持百分比单位')\r\n\t\t\t\t}\r\n\t\t\t\tconst rows = []\r\n\t\t\t\tfor (let i = 0; i < this.rows; i++) {\r\n\t\t\t\t\tlet item = {},\r\n\t\t\t\t\t\t// 需要预防超出数组边界的情况\r\n\t\t\t\t\t\trowWidth = this.$uv.test.array(this.rowsWidth) ? (this.rowsWidth[i] || (i === this.row - 1 ? '70%' : '100%')) : i ===\r\n\t\t\t\t\t\tthis.rows - 1 ? '70%' : this.rowsWidth,\r\n\t\t\t\t\t\trowHeight = this.$uv.test.array(this.rowsHeight) ? (this.rowsHeight[i] || '18px') : this.rowsHeight\r\n\t\t\t\t\t// 如果有title占位图，第一个段落占位图的外边距需要大一些，如果没有title占位图，第一个段落占位图则无需外边距\r\n\t\t\t\t\t// 之所以需要这么做，是因为weex的无能，以提升性能为借口不支持css的一些伪类\r\n\t\t\t\t\titem.marginTop = !this.title && i === 0 ? 0 : this.title && i === 0 ? '20px' : '12px'\r\n\t\t\t\t\t// 如果设置的为百分比的宽度，转换为px值，因为nvue不支持百分比单位\r\n\t\t\t\t\tif (/%$/.test(rowWidth)) {\r\n\t\t\t\t\t\t// 通过parseInt提取出百分比单位中的数值部分，除以100得到百分比的小数值\r\n\t\t\t\t\t\titem.width = this.$uv.addUnit(this.width * parseInt(rowWidth) / 100)\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\titem.width = this.$uv.addUnit(rowWidth)\r\n\t\t\t\t\t}\r\n\t\t\t\t\titem.height = this.$uv.addUnit(rowHeight)\r\n\t\t\t\t\trows.push(item)\r\n\t\t\t\t}\r\n\t\t\t\t// console.log(rows);\r\n\t\t\t\treturn rows\r\n\t\t\t},\r\n\t\t\tuTitleWidth() {\r\n\t\t\t\tlet tWidth = 0\r\n\t\t\t\tif (/%$/.test(this.titleWidth)) {\r\n\t\t\t\t\t// 通过parseInt提取出百分比单位中的数值部分，除以100得到百分比的小数值\r\n\t\t\t\t\ttWidth = this.$uv.addUnit(this.width * parseInt(this.titleWidth) / 100)\r\n\t\t\t\t} else {\r\n\t\t\t\t\ttWidth = this.$uv.addUnit(this.titleWidth)\r\n\t\t\t\t}\r\n\t\t\t\treturn this.$uv.addUnit(tWidth)\r\n\t\t\t},\r\n\t\t\t\r\n\t\t},\r\n\t\tmounted() {\r\n\t\t\tthis.init()\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tinit() {\r\n\t\t\t\tthis.getComponentWidth()\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\tthis.loading && this.animate && this.setNvueAnimation()\r\n\t\t\t\t// #endif\r\n\t\t\t},\r\n\t\t\tasync setNvueAnimation() {\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\t// 为了让opacity:1的状态保持一定时间，这里做一个延时\r\n\t\t\t\tawait this.$uv.sleep(500)\r\n\t\t\t\tconst skeleton = this.$refs['uv-skeleton__wrapper'];\r\n\t\t\t\tskeleton && this.loading && this.animate && animation.transition(skeleton, {\r\n\t\t\t\t\tstyles: {\r\n\t\t\t\t\t\topacity: 0.5\r\n\t\t\t\t\t},\r\n\t\t\t\t\tduration: 600,\r\n\t\t\t\t}, () => {\r\n\t\t\t\t\t// 这里无需判断是否loading和开启动画状态，因为最终的状态必须达到opacity: 1，否则可能\r\n\t\t\t\t\t// 会停留在opacity: 0.5的状态中\r\n\t\t\t\t\tanimation.transition(skeleton, {\r\n\t\t\t\t\t\tstyles: {\r\n\t\t\t\t\t\t\topacity: 1\r\n\t\t\t\t\t\t},\r\n\t\t\t\t\t\tduration: 600,\r\n\t\t\t\t\t}, () => {\r\n\t\t\t\t\t\t// 只有在loading中时，才执行动画\r\n\t\t\t\t\t\tthis.loading && this.animate && this.setNvueAnimation()\r\n\t\t\t\t\t})\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\t\t\t},\r\n\t\t\t// 获取组件的宽度\r\n\t\t\tasync getComponentWidth() {\r\n\t\t\t\t// 延时一定时间，以获取dom尺寸\r\n\t\t\t\tawait this.$uv.sleep(20)\r\n\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\tthis.$uvGetRect('.uv-skeleton__wrapper__content').then(size => {\r\n\t\t\t\t\tthis.width = size.width\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\tconst ref = this.$refs['uv-skeleton__wrapper__content']\r\n\t\t\t\tref && dom.getComponentRect(ref, (res) => {\r\n\t\t\t\t\tthis.width = res.size.width\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\r\n\t@mixin background {\r\n\t\t/* #ifdef APP-NVUE */\r\n\t\tbackground-color: #F1F2F4;\r\n\t\t/* #endif */\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\tbackground: linear-gradient(90deg, #F1F2F4 25%, #e6e6e6 37%, #F1F2F4 50%);\r\n\t\tbackground-size: 400% 100%;\r\n\t\t/* #endif */\r\n\t}\r\n\r\n\t.uv-skeleton {\r\n\t\tflex: 1;\r\n\t\t\r\n\t\t&__wrapper {\r\n\t\t\t@include flex(row);\r\n\t\t\t\r\n\t\t\t&__avatar {\r\n\t\t\t\t@include background;\r\n\t\t\t\tmargin-right: 15px;\r\n\t\t\t\r\n\t\t\t\t&--circle {\r\n\t\t\t\t\tborder-radius: 100px;\r\n\t\t\t\t}\r\n\t\t\t\r\n\t\t\t\t&--square {\r\n\t\t\t\t\tborder-radius: 4px;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t&__content {\r\n\t\t\t\tflex: 1;\r\n\t\t\t\r\n\t\t\t\t&__rows,\r\n\t\t\t\t&__title {\r\n\t\t\t\t\t@include background;\r\n\t\t\t\t\tborder-radius: 3px;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t/* #ifndef APP-NVUE */\r\n\t.animate {\r\n\t\tanimation: skeleton 1.8s ease infinite\r\n\t}\r\n\r\n\t@keyframes skeleton {\r\n\t\t0% {\r\n\t\t\tbackground-position: 100% 50%\r\n\t\t}\r\n\r\n\t\t100% {\r\n\t\t\tbackground-position: 0 50%\r\n\t\t}\r\n\t}\r\n\r\n\t/* #endif */\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-skeleton/package.json",
    "content": "{\r\n  \"id\": \"uv-skeleton\",\r\n  \"displayName\": \"uv-skeleton 骨架屏  全面兼容小程序、nvue、vue2、vue3等多端\",\r\n  \"version\": \"1.0.1\",\r\n  \"description\": \"骨架屏一般用于页面在请求远程数据尚未完成时，页面用灰色块预显示本来的页面结构，给用户更好的体验。\",\r\n  \"keywords\": [\r\n    \"uv-skeleton\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"skeleton\",\r\n    \"骨架屏\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-skeleton/readme.md",
    "content": "## Skeleton 骨架屏\r\n\r\n> **组件名：uv-skeleton**\r\n\r\n骨架屏一般用于页面在请求远程数据尚未完成时，页面用灰色块预显示本来的页面结构，给用户更好的体验。\r\n\r\n### <a href=\"https://www.uvui.cn/components/skeleton.html\" target=\"_blank\">查看文档</a>\r\n\r\n### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n#### 如使用过程中有任何问题，或者您对uv-ui有一些好的建议，欢迎加入 uv-ui 交流群：<a href=\"https://ext.dcloud.net.cn/plugin?id=12287\" target=\"_blank\">uv-ui</a>、<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-slider/changelog.md",
    "content": "## 1.0.3（2023-07-25）\n1. 将inactiveColor修正为backgroundColor 参数，避免不生效\n## 1.0.2（2023-07-13）\n1.  修复 uv-slider设置value属性不生效的BUG \n## 1.0.1（2023-05-16）\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\n2. 优化部分功能\n## 1.0.0（2023-05-10）\r\nuv-slider 滑动选择器\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-slider/components/uv-slider/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\tvalue: {\r\n\t\t\ttype: [Number, String],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\tmodelValue: {\r\n\t\t\ttype: [Number, String],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 最小可选值\r\n\t\tmin: {\r\n\t\t\ttype: [Number, String],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 最大可选值\r\n\t\tmax: {\r\n\t\t\ttype: [Number, String],\r\n\t\t\tdefault: 100\r\n\t\t},\r\n\t\t// 步长，取值必须大于 0，并且可被(max - min)整除\r\n\t\tstep: {\r\n\t\t\ttype: [Number, String],\r\n\t\t\tdefault: 1\r\n\t\t},\r\n\t\t// 滑块右侧已选择部分的背景色\r\n\t\tactiveColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#2979ff'\r\n\t\t},\r\n\t\t// 滑块左侧未选择部分的背景色\r\n\t\tbackgroundColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#c0c4cc'\r\n\t\t},\r\n\t\t// 滑块的大小，取值范围为 12 - 28\r\n\t\tblockSize: {\r\n\t\t\ttype: [Number, String],\r\n\t\t\tdefault: 18\r\n\t\t},\r\n\t\t// 滑块的颜色\r\n\t\tblockColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#ffffff'\r\n\t\t},\r\n\t\t// 禁用状态\r\n\t\tdisabled: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否显示当前的选择值\r\n\t\tshowValue: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t...uni.$uv?.props?.slider\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-slider/components/uv-slider/uv-slider.vue",
    "content": "<template>\r\n\t<view\r\n\t\tclass=\"uv-slider\"\r\n\t\t:style=\"[$uv.addStyle(customStyle)]\"\r\n\t>\r\n\t\t<slider\r\n\t\t\t:min=\"min\"\r\n\t\t\t:max=\"max\"\r\n\t\t\t:step=\"step\"\r\n\t\t\t:value=\"sliderValue\"\r\n\t\t\t:activeColor=\"activeColor\"\r\n\t\t\t:backgroundColor=\"backgroundColor\"\r\n\t\t\t:blockSize=\"$uv.getPx(blockSize)\"\r\n\t\t\t:blockColor=\"blockColor\"\r\n\t\t\t:showValue=\"showValue\"\r\n\t\t\t:disabled=\"disabled\"\r\n\t\t\t@changing=\"changingHandler\"\r\n\t\t\t@change=\"changeHandler\"\r\n\t\t></slider>\r\n\t</view>\r\n</template>\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js'\r\n\texport default {\r\n\t\tname: 'uv-slider',\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tcomputed: {\r\n\t\t\tsliderValue(){\r\n\t\t\t\treturn this.value || this.modelValue;\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\t// 拖动过程中触发\r\n\t\t\tchangingHandler(e) {\r\n\t\t\t\tconst { value } = e.detail\r\n\t\t\t\t// 更新v-model的值\r\n\t\t\t\tthis.$emit('input', value)\r\n\t\t\t\tthis.$emit('update:modelValue',value)\r\n\t\t\t\t// 触发事件\r\n\t\t\t\tthis.$emit('changing', value)\r\n\t\t\t},\r\n\t\t\t// 滑动结束时触发\r\n\t\t\tchangeHandler(e) {\r\n\t\t\t\tconst {\r\n\t\t\t\t\tvalue\r\n\t\t\t\t} = e.detail\r\n\t\t\t\t// 更新v-model的值\r\n\t\t\t\tthis.$emit('input', value)\r\n\t\t\t\tthis.$emit('update:modelValue',value)\r\n\t\t\t\t// 触发事件\r\n\t\t\t\tthis.$emit('change', value)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-slider/package.json",
    "content": "{\r\n  \"id\": \"uv-slider\",\r\n  \"displayName\": \"uv-slider 滑动选择器 全面兼容vue3+2、app、h5、小程序等多端\",\r\n  \"version\": \"1.0.3\",\r\n  \"description\": \"滑动选择器组件一般用于表单中，手动选择一个区间范围的场景。\",\r\n  \"keywords\": [\r\n    \"uv-slider\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"slider\",\r\n    \"滑动选择器\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-slider/readme.md",
    "content": "## Slider 滑动选择器\r\n\r\n> **组件名：uv-slider**\r\n\r\n该组件一般用于表单中，手动选择一个区间范围的场景。\r\n\r\n# <a href=\"https://www.uvui.cn/components/slider.html\" target=\"_blank\">查看文档</a>\r\n\r\n## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\r\n\r\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-status-bar/changelog.md",
    "content": "## 1.0.2（2023-06-05）\n1. 兼容渐变背景色\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\n1. 新增uv-status-bar组件\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-status-bar/components/uv-status-bar/props.js",
    "content": "export default {\n    props: {\n        bgColor: {\n            type: String,\n            default: 'transparent'\n        }\n    }\n}\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-status-bar/components/uv-status-bar/uv-status-bar.vue",
    "content": "<template>\r\n\t<view\r\n\t    :style=\"[style]\"\r\n\t    class=\"uv-status-bar\"\r\n\t>\r\n\t\t<slot />\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * StatbusBar 状态栏占位\r\n\t * @description 本组件主要用于状态填充，比如在自定导航栏的时候，它会自动适配一个恰当的状态栏高度。\r\n\t * @tutorial https://www.uvui.cn/components/statusBar.html\r\n\t * @property {String}\t\t\tbgColor\t\t\t背景色 (默认 'transparent' )\r\n\t * @property {String | Object}\tcustomStyle\t\t自定义样式 \r\n\t * @example <uv-status-bar></uv-status-bar>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-status-bar',\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\tstyle() {\r\n\t\t\t\tconst style = {}\r\n\t\t\t\t// 状态栏高度，由于某些安卓和微信开发工具无法识别css的顶部状态栏变量，所以使用js获取的方式\r\n\t\t\t\tstyle.height = this.$uv.addUnit(this.$uv.sys().statusBarHeight, 'px')\r\n\t\t\t\tif(this.bgColor){\r\n\t\t\t\t\tif (this.bgColor.indexOf(\"gradient\") > -1) {// 渐变色\r\n\t\t\t\t\t\tstyle.backgroundImage = this.bgColor;\r\n\t\t\t\t\t}else{\r\n\t\t\t\t\t\tstyle.background = this.bgColor;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\treturn this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))\r\n\t\t\t}\r\n\t\t},\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t.uv-status-bar {\r\n\t\t// nvue会默认100%，如果nvue下，显式写100%的话，会导致宽度不为100%而异常\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\twidth: 100%;\r\n\t\t/* #endif */\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-status-bar/package.json",
    "content": "{\r\n  \"id\": \"uv-status-bar\",\r\n  \"displayName\": \"uv-status-bar 状态栏占位\",\r\n  \"version\": \"1.0.2\",\r\n  \"description\": \"状态栏占位组件主要用于状态填充，比如在自定导航栏的时候，它会自动适配一个恰当的状态栏高度。\",\r\n  \"keywords\": [\r\n    \"uv-status-bar\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"status-bar\",\r\n    \"状态栏\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-status-bar/readme.md",
    "content": "## StatbusBar 状态栏占位\r\n\r\n> **组件名：uv-status-bar**\r\n\r\n本组件主要用于状态填充，比如在自定导航栏的时候，它会自动适配一个恰当的状态栏高度。\r\n\r\n### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n#### 如使用过程中有任何问题，或者您对uv-ui有一些好的建议，欢迎加入 uv-ui 交流群：<a href=\"https://ext.dcloud.net.cn/plugin?id=12287\" target=\"_blank\">uv-ui</a>、<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-steps/changelog.md",
    "content": "## 1.0.3（2023-06-29）\n1. 增加customStyle参数，方便设置样式\n## 1.0.2（2023-06-29）\n1. 新增title自定义\n2. 完善文档说明\n## 1.0.1（2023-05-16）\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\n2. 优化部分功能\n## 1.0.0（2023-05-10）\r\nuv-steps 步骤条\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-steps/components/uv-steps/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 排列方向\r\n\t\tdirection: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'row'\r\n\t\t},\r\n\t\t// 设置第几个步骤\r\n\t\tcurrent: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 激活状态颜色\r\n\t\tactiveColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#3c9cff'\r\n\t\t},\r\n\t\t// 未激活状态颜色\r\n\t\tinactiveColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#969799'\r\n\t\t},\r\n\t\t// 激活状态的图标\r\n\t\tactiveIcon: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 未激活状态图标\r\n\t\tinactiveIcon: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 是否显示点类型\r\n\t\tdot: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t...uni.$uv?.props?.steps\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-steps/components/uv-steps/uv-steps.vue",
    "content": "<template>\r\n\t<view\r\n\t  :class=\"['uv-steps',`uv-steps--${direction}`]\"\r\n\t\t:style=\"[$uv.addStyle(customStyle)]\"\r\n\t>\r\n\t\t<slot></slot>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport { func } from '@/uni_modules/uv-ui-tools/libs/function/test.js'\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * Steps 步骤条\r\n\t * @description 该组件一般用于完成一个任务要分几个步骤，标识目前处于第几步的场景。\r\n\t * @tutorial https://www.uvui.cn/components/steps.html\r\n\t * @property {String}\t\t\tdirection\t\trow-横向，column-竖向 (默认 'row' )\r\n\t * @property {String | Number}\tcurrent\t\t\t设置当前处于第几步 (默认 0 )\r\n\t * @property {String}\t\t\tactiveColor\t\t激活状态颜色 (默认 '#3c9cff' )\r\n\t * @property {String}\t\t\tinactiveColor\t未激活状态颜色 (默认 '#969799' )\r\n\t * @property {String}\t\t\tactiveIcon\t\t激活状态的图标\r\n\t * @property {String}\t\t\tinactiveIcon\t未激活状态图标 \r\n\t * @property {Boolean}\t\t\tdot\t\t\t\t是否显示点类型 (默认 false )\r\n\t * @example <uv-steps current=\"0\"><uv-steps-item title=\"已出库\" desc=\"10:35\" ></uv-steps-item></uv-steps>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-steps',\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t}\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\tchildren() {\r\n\t\t\t\tthis.updateChildData()\r\n\t\t\t},\r\n\t\t\tparentData() {\r\n\t\t\t\tthis.updateChildData()\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\t// 监听参数的变化，通过watch中，手动去更新子组件的数据，否则子组件不会自动变化\r\n\t\t\tparentData() {\r\n\t\t\t\treturn [this.current, this.direction, this.activeColor, this.inactiveColor, this.activeIcon, this.inactiveIcon, this.dot]\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\t// 更新子组件的数据\r\n\t\t\tupdateChildData() {\r\n\t\t\t\tthis.children.map(child => {\r\n\t\t\t\t\t// 先判断子组件是否存在对应的方法\r\n\t\t\t\t\tfunc((child || {}).updateFromParent()) && child.updateFromParent()\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\t// 接受子组件的通知，去修改其他子组件的数据\r\n\t\t\tupdateFromChild() {\r\n\t\t\t\tthis.updateChildData()\r\n\t\t\t}\r\n\t\t},\r\n\t\tcreated() {\r\n\t\t\tthis.children = []\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\r\n\t.uv-steps {\r\n\t\t@include flex;\r\n\r\n\t\t&--column {\r\n\t\t\tflex-direction: column\r\n\t\t}\r\n\r\n\t\t&--row {\r\n\t\t\tflex-direction: row;\r\n\t\t\tflex: 1;\r\n\t\t}\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-steps/components/uv-steps-item/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 标题\r\n\t\ttitle: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 描述文本\r\n\t\tdesc: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 图标大小\r\n\t\ticonSize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 17\r\n\t\t},\r\n\t\t// 当前步骤是否处于失败状态\r\n\t\terror: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t...uni.$uv?.props?.stepsItem\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-steps/components/uv-steps-item/uv-steps-item.vue",
    "content": "<template>\r\n\t<view \r\n\t\tclass=\"uv-steps-item\" \r\n\t\tref=\"uv-steps-item\" \r\n\t\t:class=\"[`uv-steps-item--${parentData.direction}`]\"\r\n\t\t:style=\"[$uv.addStyle(customStyle)]\"\r\n\t\t>\r\n\t\t<view \r\n\t\t\tclass=\"uv-steps-item__line\" \r\n\t\t\tv-if=\"index + 1 < childLength\"\r\n\t\t\t:class=\"[`uv-steps-item__line--${parentData.direction}`]\" \r\n\t\t\t:style=\"[lineStyle]\">\r\n\t\t</view>\r\n\t\t<view \r\n\t\t\t:class=\"[\r\n\t\t\t\t'uv-steps-item__wrapper',\r\n\t\t\t\t`uv-steps-item__wrapper--${parentData.direction}`, \r\n\t\t\t\tparentData.dot && `uv-steps-item__wrapper--${parentData.direction}--dot`\r\n\t\t\t]\">\r\n\t\t\t<slot name=\"icon\">\r\n\t\t\t\t<view \r\n\t\t\t\t\tclass=\"uv-steps-item__wrapper__dot\" \r\n\t\t\t\t\tv-if=\"parentData.dot\" \r\n\t\t\t\t\t:style=\"{\r\n\t\t\t\t\t\tbackgroundColor: statusColor\r\n\t\t\t\t\t}\">\r\n\t\t\t\t</view>\r\n\t\t\t\t<view \r\n\t\t\t\t\tclass=\"uv-steps-item__wrapper__icon\" \r\n\t\t\t\t\tv-else-if=\"parentData.activeIcon || parentData.inactiveIcon\">\r\n\t\t\t\t\t<uv-icon \r\n\t\t\t\t\t\t:name=\"index <= parentData.current ? parentData.activeIcon : parentData.inactiveIcon\"\r\n\t\t\t\t\t\t:size=\"iconSize\"\r\n\t\t\t\t\t\t:color=\"index <= parentData.current ? parentData.activeColor : parentData.inactiveColor\">\r\n\t\t\t\t\t</uv-icon>\r\n\t\t\t\t</view>\r\n\t\t\t\t<view \r\n\t\t\t\t\tv-else \r\n\t\t\t\t\t:style=\"{\r\n\t\t\t\t\t\tbackgroundColor: statusClass === 'process' ? parentData.activeColor : 'transparent',\r\n\t\t\t\t\t\tborderColor: statusColor\r\n\t\t\t\t\t}\" \r\n\t\t\t\t\tclass=\"uv-steps-item__wrapper__circle\">\r\n\t\t\t\t\t<text \r\n\t\t\t\t\t\tv-if=\"statusClass === 'process' || statusClass === 'wait'\"\r\n\t\t\t\t\t\tclass=\"uv-steps-item__wrapper__circle__text\" \r\n\t\t\t\t\t\t:style=\"{\r\n\t\t\t\t\t\t\tcolor: index == parentData.current ? '#ffffff' : parentData.inactiveColor\r\n\t\t\t\t\t\t}\">{{ index + 1}}</text>\r\n\t\t\t\t\t<uv-icon \r\n\t\t\t\t\t\tv-else \r\n\t\t\t\t\t\t:color=\"statusClass === 'error' ? 'error' : parentData.activeColor\" \r\n\t\t\t\t\t\tsize=\"12\"\r\n\t\t\t\t\t\t:name=\"statusClass === 'error' ? 'close' : 'checkmark'\">\r\n\t\t\t\t\t</uv-icon>\r\n\t\t\t\t</view>\r\n\t\t\t</slot>\r\n\t\t</view>\r\n\t\t<view \r\n\t\t\t:class=\"[\r\n\t\t\t\t'uv-steps-item__content',\r\n\t\t\t\t`uv-steps-item__content--${parentData.direction}`\r\n\t\t\t]\"\r\n\t\t\t:style=\"[contentStyle]\"\r\n\t\t>\r\n\t\t\t<slot name=\"title\">\r\n\t\t\t\t<uv-text\r\n\t\t\t\t\t:text=\"title\" \r\n\t\t\t\t\t:type=\"parentData.current == index ? 'main' : 'content'\" \r\n\t\t\t\t\tlineHeight=\"20px\"\r\n\t\t\t\t\t:size=\"parentData.current == index ? 14 : 13\"\r\n\t\t\t\t></uv-text>\r\n\t\t\t</slot>\r\n\t\t\t<slot name=\"desc\">\r\n\t\t\t\t<uv-text \r\n\t\t\t\t\t:text=\"desc\" \r\n\t\t\t\t\ttype=\"tips\" \r\n\t\t\t\t\tsize=\"12\"\r\n\t\t\t\t></uv-text>\r\n\t\t\t</slot>\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t// #ifdef APP-NVUE\r\n\tconst dom = uni.requireNativePlugin('dom')\r\n\t// #endif\r\n\t/**\r\n\t * StepsItem 步骤条的子组件\r\n\t * @description 本组件需要和uv-steps配合使用\r\n\t * @tutorial https://www.uvui.cn/components/steps.html\r\n\t * @property {String}\t\t\ttitle\t\t\t标题文字\r\n\t * @property {String}\t\t\tcurrent\t\t\t描述文本\r\n\t * @property {String | Number}\ticonSize\t\t图标大小  (默认 17 )\r\n\t * @property {Boolean}\t\t\terror\t\t\t当前步骤是否处于失败状态  (默认 false )\r\n\t * @example <uv-steps current=\"0\"><uv-steps-item title=\"已出库\" desc=\"10:35\" ></uv-steps-item></uv-steps>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-steps-item',\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tindex: 0,\r\n\t\t\t\tchildLength: 0,\r\n\t\t\t\tshowLine: false,\r\n\t\t\t\tsize: {\r\n\t\t\t\t\theight: 0,\r\n\t\t\t\t\twidth: 0\r\n\t\t\t\t},\r\n\t\t\t\tparentData: {\r\n\t\t\t\t\tdirection: 'row',\r\n\t\t\t\t\tcurrent: 0,\r\n\t\t\t\t\tactiveColor: '',\r\n\t\t\t\t\tinactiveColor: '',\r\n\t\t\t\t\tactiveIcon: '',\r\n\t\t\t\t\tinactiveIcon: '',\r\n\t\t\t\t\tdot: false\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\t'parentData'(newValue, oldValue) {}\r\n\t\t},\r\n\t\tcreated() {\r\n\t\t\tthis.init()\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\tlineStyle() {\r\n\t\t\t\tconst style = {}\r\n\t\t\t\tif (this.parentData.direction === 'row') {\r\n\t\t\t\t\tstyle.width = this.size.width + 'px'\r\n\t\t\t\t\tstyle.left = this.size.width / 2 + 'px'\r\n\t\t\t\t} else {\r\n\t\t\t\t\tstyle.height = this.size.height + 'px'\r\n\t\t\t\t}\r\n\t\t\t\tstyle.backgroundColor = this.parent.children?.[this.index + 1]?.error ? '#f56c6c' : this.index < this.parentData.current ? this.parentData.activeColor : this.parentData.inactiveColor\r\n\t\t\t\treturn style\r\n\t\t\t},\r\n\t\t\tstatusClass() {\r\n\t\t\t\tconst {\r\n\t\t\t\t\tindex,\r\n\t\t\t\t\terror\r\n\t\t\t\t} = this\r\n\t\t\t\tconst {\r\n\t\t\t\t\tcurrent\r\n\t\t\t\t} = this.parentData\r\n\t\t\t\tif (current == index) {\r\n\t\t\t\t\treturn error === true ? 'error' : 'process'\r\n\t\t\t\t} else if (error) {\r\n\t\t\t\t\treturn 'error'\r\n\t\t\t\t} else if (current > index) {\r\n\t\t\t\t\treturn 'finish'\r\n\t\t\t\t} else {\r\n\t\t\t\t\treturn 'wait'\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tstatusColor() {\r\n\t\t\t\tlet color = ''\r\n\t\t\t\tswitch (this.statusClass) {\r\n\t\t\t\t\tcase 'finish':\r\n\t\t\t\t\t\tcolor = this.parentData.activeColor\r\n\t\t\t\t\t\tbreak\r\n\t\t\t\t\tcase 'error':\r\n\t\t\t\t\t\tcolor = '#f56c6c'\r\n\t\t\t\t\t\tbreak\r\n\t\t\t\t\tcase 'process':\r\n\t\t\t\t\t\tcolor = this.parentData.dot ? this.parentData.activeColor : 'transparent'\r\n\t\t\t\t\t\tbreak\r\n\t\t\t\t\tdefault:\r\n\t\t\t\t\t\tcolor = this.parentData.inactiveColor\r\n\t\t\t\t\t\tbreak\r\n\t\t\t\t}\r\n\t\t\t\treturn color\r\n\t\t\t},\r\n\t\t\tcontentStyle() {\r\n\t\t\t\tconst style = {}\r\n\t\t\t\tif (this.parentData.direction === 'column') {\r\n\t\t\t\t\tstyle.marginLeft = this.parentData.dot ? '2px' : '6px'\r\n\t\t\t\t\tstyle.marginTop = this.parentData.dot ? '0px' : '6px'\r\n\t\t\t\t} else {\r\n\t\t\t\t\tstyle.marginTop = this.parentData.dot ? '2px' : '6px'\r\n\t\t\t\t\tstyle.marginLeft = this.parentData.dot ? '2px' : '6px'\r\n\t\t\t\t}\r\n\t\t\t\treturn style\r\n\t\t\t}\r\n\t\t},\r\n\t\tmounted() {\r\n\t\t\tthis.parent && this.parent.updateFromChild()\r\n\t\t\tthis.$uv.sleep().then(() => {\r\n\t\t\t\tthis.getStepsItemRect()\r\n\t\t\t})\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tinit() {\r\n\t\t\t\t// 初始化数据\r\n\t\t\t\tthis.updateParentData()\r\n\t\t\t\tif (!this.parent) {\r\n\t\t\t\t\treturn this.$uv.error('uv-steps-item必须要搭配uv-steps组件使用')\r\n\t\t\t\t}\r\n\t\t\t\tthis.index = this.parent.children.indexOf(this)\r\n\t\t\t\tthis.childLength = this.parent.children.length\r\n\t\t\t},\r\n\t\t\tupdateParentData() {\r\n\t\t\t\t// 此方法在mixin中\r\n\t\t\t\tthis.getParentData('uv-steps')\r\n\t\t\t},\r\n\t\t\t// 父组件数据发生变化\r\n\t\t\tupdateFromParent() {\r\n\t\t\t\tthis.init()\r\n\t\t\t},\r\n\t\t\t// 获取组件的尺寸，用于设置横线的位置\r\n\t\t\tgetStepsItemRect() {\r\n\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\tthis.$uvGetRect('.uv-steps-item').then(size => {\r\n\t\t\t\t\tthis.size = size\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\tdom.getComponentRect(this.$refs['uv-steps-item'], res => {\r\n\t\t\t\t\tconst {\r\n\t\t\t\t\t\tsize\r\n\t\t\t\t\t} = res\r\n\t\t\t\t\tthis.size = size\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t.uv-steps-item {\r\n\t\tflex: 1;\r\n\t\t@include flex;\r\n\r\n\t\t&--row {\r\n\t\t\tflex-direction: column;\r\n\t\t\talign-items: center;\r\n\t\t\tposition: relative;\r\n\t\t}\r\n\r\n\t\t&--column {\r\n\t\t\tposition: relative;\r\n\t\t\tflex-direction: row;\r\n\t\t\tjustify-content: flex-start;\r\n\t\t\tpadding-bottom: 5px;\r\n\t\t}\r\n\r\n\t\t&__wrapper {\r\n\t\t\t@include flex;\r\n\t\t\tjustify-content: center;\r\n\t\t\talign-items: center;\r\n\t\t\tposition: relative;\r\n\t\t\tbackground-color: #fff;\r\n\r\n\t\t\t&--column {\r\n\t\t\t\twidth: 20px;\r\n\t\t\t\theight: 32px;\r\n\r\n\t\t\t\t&--dot {\r\n\t\t\t\t\theight: 20px;\r\n\t\t\t\t\twidth: 20px;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t&--row {\r\n\t\t\t\twidth: 32px;\r\n\t\t\t\theight: 20px;\r\n\r\n\t\t\t\t&--dot {\r\n\t\t\t\t\twidth: 20px;\r\n\t\t\t\t\theight: 20px;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t&__circle {\r\n\t\t\t\twidth: 20px;\r\n\t\t\t\theight: 20px;\r\n\t\t\t\t/* #ifndef APP-NVUE */\r\n\t\t\t\tbox-sizing: border-box;\r\n\t\t\t\tflex-shrink: 0;\r\n\t\t\t\t/* #endif */\r\n\t\t\t\tborder-radius: 100px;\r\n\t\t\t\tborder-width: 1px;\r\n\t\t\t\tborder-color: $uv-tips-color;\r\n\t\t\t\tborder-style: solid;\r\n\t\t\t\t@include flex(row);\r\n\t\t\t\talign-items: center;\r\n\t\t\t\tjustify-content: center;\r\n\t\t\t\ttransition: background-color 0.3s;\r\n\r\n\t\t\t\t&__text {\r\n\t\t\t\t\tcolor: $uv-tips-color;\r\n\t\t\t\t\tfont-size: 11px;\r\n\t\t\t\t\t@include flex(row);\r\n\t\t\t\t\talign-items: center;\r\n\t\t\t\t\tjustify-content: center;\r\n\t\t\t\t\ttext-align: center;\r\n\t\t\t\t\tline-height: 11px;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t&__dot {\r\n\t\t\t\twidth: 10px;\r\n\t\t\t\theight: 10px;\r\n\t\t\t\tborder-radius: 100px;\r\n\t\t\t\tbackground-color: $uv-content-color;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t&__content {\r\n\t\t\t@include flex;\r\n\t\t\tflex: 1;\r\n\r\n\t\t\t&--row {\r\n\t\t\t\tflex-direction: column;\r\n\t\t\t\talign-items: center;\r\n\t\t\t}\r\n\r\n\t\t\t&--column {\r\n\t\t\t\tflex-direction: column;\r\n\t\t\t\tmargin-left: 6px;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t&__line {\r\n\t\t\tposition: absolute;\r\n\t\t\tbackground: $uv-tips-color;\r\n\r\n\t\t\t&--row {\r\n\t\t\t\ttop: 10px;\r\n\t\t\t\theight: 1px;\r\n\t\t\t}\r\n\r\n\t\t\t&--column {\r\n\t\t\t\twidth: 1px;\r\n\t\t\t\tleft: 10px;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-steps/package.json",
    "content": "{\r\n  \"id\": \"uv-steps\",\r\n  \"displayName\": \"uv-steps 步骤条  全面兼容小程序、nvue、vue2、vue3等多端\",\r\n  \"version\": \"1.0.3\",\r\n  \"description\": \"步骤条组件一般用于完成一个任务要分几个步骤，标识目前处于第几步的场景。\",\r\n  \"keywords\": [\r\n    \"uv-steps\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"steps\",\r\n    \"步骤条\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-icon\",\r\n\t\t\t\"uv-text\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"n\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-steps/readme.md",
    "content": "## Steps 步骤条\r\n\r\n> **组件名：uv-steps**\r\n\r\n该组件一般用于完成一个任务要分几个步骤，标识目前处于第几步的场景。\r\n\r\n### <a href=\"https://www.uvui.cn/components/steps.html\" target=\"_blank\">查看文档</a>\r\n\r\n### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n#### 如使用过程中有任何问题，或者您对uv-ui有一些好的建议，欢迎加入 uv-ui 交流群：<a href=\"https://ext.dcloud.net.cn/plugin?id=12287\" target=\"_blank\">uv-ui</a>、<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-sticky/changelog.md",
    "content": "## 1.0.3（2023-10-13）\n1. unmounted兼容vue3\n## 1.0.2（2023-08-27）\r\n1. 优化\r\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-sticky 吸顶\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-sticky/components/uv-sticky/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 吸顶容器到顶部某个距离的时候，进行吸顶，在H5平台，NavigationBar为44px\r\n\t\toffsetTop: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 自定义导航栏的高度\r\n\t\tcustomNavHeight: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\t// #ifdef H5\r\n\t\t\t// H5端的导航栏属于“自定义”导航栏的范畴，因为它是非原生的，与普通元素一致\r\n\t\t\tdefault: 44,\r\n\t\t\t// #endif\r\n\t\t\t// #ifndef H5\r\n\t\t\tdefault: 0\r\n\t\t\t// #endif\r\n\t\t},\r\n\t\t// 是否禁用吸顶功能\r\n\t\tdisabled: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 吸顶区域的背景颜色\r\n\t\tbgColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'transparent'\r\n\t\t},\r\n\t\t// z-index值\r\n\t\tzIndex: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 列表中的索引值\r\n\t\tindex: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t...uni.$uv?.props?.sticky\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-sticky/components/uv-sticky/uv-sticky.vue",
    "content": "<template>\r\n\t<view\r\n\t\tclass=\"uv-sticky\"\r\n\t\t:id=\"elId\"\r\n\t\t:style=\"[style]\"\r\n\t>\r\n\t\t<view\r\n\t\t\t:style=\"[stickyContent]\"\r\n\t\t\tclass=\"uv-sticky__content\"\r\n\t\t>\r\n\t\t\t<slot />\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';;\r\n\t/**\r\n\t * sticky 吸顶\r\n\t * @description 该组件与CSS中position: sticky属性实现的效果一致，当组件达到预设的到顶部距离时， 就会固定在指定位置，组件位置大于预设的顶部距离时，会重新按照正常的布局排列。\r\n\t * @tutorial https://www.uvui.cn/components/sticky.html\r\n\t * @property {String ｜ Number}\toffsetTop\t\t吸顶时与顶部的距离，单位px（默认 0 ）\r\n\t * @property {String ｜ Number}\tcustomNavHeight\t自定义导航栏的高度 （h5 默认44  其他默认 0 ）\r\n\t * @property {Boolean}\t\t\tdisabled\t\t是否开启吸顶功能 （默认 false ）\r\n\t * @property {String}\t\t\tbgColor\t\t\t组件背景颜色（默认 '#ffffff' ）\r\n\t * @property {String ｜ Number}\tzIndex\t\t\t吸顶时的z-index值\r\n\t * @property {String ｜ Number}\tindex\t\t\t自定义标识，用于区分是哪一个组件\r\n\t * @property {Object}\t\t\tcustomStyle\t\t组件的样式，对象形式\r\n\t * @example <uv-sticky offsetTop=\"200\"><view>塞下秋来风景异，衡阳雁去无留意</view></uv-sticky>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-sticky',\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tcssSticky: false, // 是否使用css的sticky实现\r\n\t\t\t\tstickyTop: 0, // 吸顶的top值，因为可能受自定义导航栏影响，最终的吸顶值非offsetTop值\r\n\t\t\t\telId: '',\r\n\t\t\t\tleft: 0, // js模式时，吸顶的内容因为处于postition: fixed模式，为了和原来保持一致的样式，需要记录并重新设置它的left，height，width属性\r\n\t\t\t\twidth: 'auto',\r\n\t\t\t\theight: 'auto',\r\n\t\t\t\tfixed: false, // js模式时，是否处于吸顶模式\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\tstyle() {\r\n\t\t\t\tconst style = {}\r\n\t\t\t\tif(!this.disabled) {\r\n\t\t\t\t\tif (this.cssSticky) {\r\n\t\t\t\t\t\tstyle.position = 'sticky'\r\n\t\t\t\t\t\tstyle.zIndex = this.uZindex\r\n\t\t\t\t\t\tstyle.top = this.$uv.addUnit(this.stickyTop)\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tstyle.height = this.fixed ? this.height + 'px' : 'auto'\r\n\t\t\t\t\t}\r\n\t\t\t\t} else {\r\n\t\t\t\t\t// 无需吸顶时，设置会默认的relative(nvue)和非nvue的static静态模式即可\r\n\t\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\t\tstyle.position = 'relative'\r\n\t\t\t\t\t// #endif\r\n\t\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\t\tstyle.position = 'static'\r\n\t\t\t\t\t// #endif\r\n\t\t\t\t}\r\n\t\t\t\tstyle.backgroundColor = this.bgColor\r\n\t\t\t\treturn this.$uv.deepMerge(this.$uv.addStyle(this.customStyle), style)\r\n\t\t\t},\r\n\t\t\t// 吸顶内容的样式\r\n\t\t\tstickyContent() {\r\n\t\t\t\tconst style = {}\r\n\t\t\t\tif (!this.cssSticky) {\r\n\t\t\t\t\tstyle.position = this.fixed ? 'fixed' : 'static'\r\n\t\t\t\t\tstyle.top = this.stickyTop + 'px'\r\n\t\t\t\t\tstyle.left = this.left + 'px'\r\n\t\t\t\t\tstyle.width = this.width == 'auto' ? 'auto' : this.width + 'px'\r\n\t\t\t\t\tstyle.zIndex = this.uZindex\r\n\t\t\t\t}\r\n\t\t\t\treturn style\r\n\t\t\t},\r\n\t\t\tuZindex() {\r\n\t\t\t\treturn this.zIndex ? this.zIndex : 970\r\n\t\t\t}\r\n\t\t},\r\n\t\tcreated() {\r\n\t\t\tthis.elId = this.$uv.guid();\r\n\t\t},\r\n\t\tmounted() {\r\n\t\t\tthis.init()\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tinit() {\r\n\t\t\t\tthis.getStickyTop()\r\n\t\t\t\t// 判断使用的模式\r\n\t\t\t\tthis.checkSupportCssSticky()\r\n\t\t\t\t// 如果不支持css sticky，则使用js方案，此方案性能比不上css方案\r\n\t\t\t\tif (!this.cssSticky) {\r\n\t\t\t\t\t!this.disabled && this.initObserveContent()\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tinitObserveContent() {\r\n\t\t\t\t// 获取吸顶内容的高度，用于在js吸顶模式时，给父元素一个填充高度，防止\"塌陷\"\r\n\t\t\t\tthis.$uvGetRect('#' + this.elId).then((res) => {\r\n\t\t\t\t\tthis.height = res.height\r\n\t\t\t\t\tthis.left = res.left\r\n\t\t\t\t\tthis.width = res.width\r\n\t\t\t\t\tthis.$nextTick(() => {\r\n\t\t\t\t\t\tthis.observeContent()\r\n\t\t\t\t\t})\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\tobserveContent() {\r\n\t\t\t\t// 先断掉之前的观察\r\n\t\t\t\tthis.disconnectObserver('contentObserver')\r\n\t\t\t\tconst contentObserver = uni.createIntersectionObserver({\r\n\t\t\t\t\t// 检测的区间范围\r\n\t\t\t\t\tthresholds: [0.95, 0.98, 1]\r\n\t\t\t\t})\r\n\t\t\t\t// 到屏幕顶部的高度时触发\r\n\t\t\t\tcontentObserver.relativeToViewport({\r\n\t\t\t\t\ttop: -this.stickyTop\r\n\t\t\t\t})\r\n\t\t\t\t// 绑定观察的元素\r\n\t\t\t\tcontentObserver.observe(`#${this.elId}`, res => {\r\n\t\t\t\t\tthis.setFixed(res.boundingClientRect.top)\r\n\t\t\t\t})\r\n\t\t\t\tthis.contentObserver = contentObserver\r\n\t\t\t},\r\n\t\t\tsetFixed(top) {\r\n\t\t\t\t// 判断是否出于吸顶条件范围\r\n\t\t\t\tconst fixed = top <= this.stickyTop\r\n\t\t\t\tthis.fixed = fixed\r\n\t\t\t},\r\n\t\t\tdisconnectObserver(observerName) {\r\n\t\t\t\t// 断掉观察，释放资源\r\n\t\t\t\tconst observer = this[observerName]\r\n\t\t\t\tobserver && observer.disconnect()\r\n\t\t\t},\r\n\t\t\tgetStickyTop() {\r\n\t\t\t\tthis.stickyTop = this.$uv.getPx(this.offsetTop) + this.$uv.getPx(this.customNavHeight)\r\n\t\t\t},\r\n\t\t\tasync checkSupportCssSticky() {\r\n\t\t\t\t// #ifdef H5\r\n\t\t\t\t// H5，一般都是现代浏览器，是支持css sticky的，这里使用创建元素嗅探的形式判断\r\n\t\t\t\tif (this.checkCssStickyForH5()) {\r\n\t\t\t\t\tthis.cssSticky = true\r\n\t\t\t\t}\r\n\t\t\t\t// #endif\r\n\r\n\t\t\t\t// 如果安卓版本高于8.0，依然认为是支持css sticky的(因为安卓7在某些机型，可能不支持sticky)\r\n\t\t\t\tif (this.$uv.os() === 'android' && Number(this.$uv.sys().system) > 8) {\r\n\t\t\t\t\tthis.cssSticky = true\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// APP-Vue和微信平台，通过computedStyle判断是否支持css sticky\r\n\t\t\t\t// #ifdef APP-VUE || MP-WEIXIN\r\n\t\t\t\tthis.cssSticky = await this.checkComputedStyle()\r\n\t\t\t\t// #endif\r\n\r\n\t\t\t\t// ios上，从ios6开始，都是支持css sticky的\r\n\t\t\t\tif (this.$uv.os() === 'ios') {\r\n\t\t\t\t\tthis.cssSticky = true\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// nvue，是支持css sticky的\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\tthis.cssSticky = true\r\n\t\t\t\t// #endif\r\n\t\t\t},\r\n\t\t\t// 在APP和微信小程序上，通过uni.createSelectorQuery可以判断是否支持css sticky\r\n\t\t\tcheckComputedStyle() {\r\n\t\t\t\t// 方法内进行判断，避免在其他平台生成无用代码\r\n\t\t\t\t// #ifdef APP-VUE || MP-WEIXIN\r\n\t\t\t\treturn new Promise(resolve => {\r\n\t\t\t\t\tuni.createSelectorQuery().in(this).select('.uv-sticky').fields({\r\n\t\t\t\t\t\tcomputedStyle: [\"position\"]\r\n\t\t\t\t\t}).exec(e => {\r\n\t\t\t\t\t\tresolve('sticky' === e[0].position)\r\n\t\t\t\t\t})\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\t\t\t},\r\n\t\t\t// H5通过创建元素的形式嗅探是否支持css sticky\r\n\t\t\t// 判断浏览器是否支持sticky属性\r\n\t\t\tcheckCssStickyForH5() {\r\n\t\t\t\t// 方法内进行判断，避免在其他平台生成无用代码\r\n\t\t\t\t// #ifdef H5\r\n\t\t\t\tconst vendorList = ['', '-webkit-', '-ms-', '-moz-', '-o-'],\r\n\t\t\t\t\tvendorListLength = vendorList.length,\r\n\t\t\t\t\tstickyElement = document.createElement('div')\r\n\t\t\t\tfor (let i = 0; i < vendorListLength; i++) {\r\n\t\t\t\t\tstickyElement.style.position = vendorList[i] + 'sticky'\r\n\t\t\t\t\tif (stickyElement.style.position !== '') {\r\n\t\t\t\t\t\treturn true\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\treturn false;\r\n\t\t\t\t// #endif\r\n\t\t\t}\r\n\t\t},\r\n\t\t// #ifdef VUE2\r\n\t\tbeforeDestroy() {\r\n\t\t\tthis.disconnectObserver('contentObserver')\r\n\t\t},\r\n\t\t// #endif\r\n\t\t// #ifdef VUE3\r\n\t\tunmounted() {\r\n\t\t\tthis.disconnectObserver('contentObserver')\r\n\t\t}\r\n\t\t// #endif\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t.uv-sticky {\r\n\t\t/* #ifdef APP-VUE || MP-WEIXIN */\r\n\t\t// 此处默认写sticky属性，是为了给微信和APP通过uni.createSelectorQuery查询是否支持css sticky使用\r\n\t\tposition: sticky;\r\n\t\t/* #endif */\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-sticky/package.json",
    "content": "{\r\n  \"id\": \"uv-sticky\",\r\n  \"displayName\": \"uv-sticky 吸顶  全面兼容小程序、nvue、vue2、vue3等多端\",\r\n  \"version\": \"1.0.3\",\r\n  \"description\": \"吸顶组件与CSS中position: sticky属性实现的效果一致，当组件达到预设的到顶部距离时， 就会固定在指定位置，组件位置大于预设的顶部距离时，会重新按照正常的布局排列。\",\r\n  \"keywords\": [\r\n    \"uv-sticky\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"sticky\",\r\n    \"吸顶\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-sticky/readme.md",
    "content": "## Sticky 吸顶\r\n\r\n> **组件名：uv-sticky**\r\n\r\n该组件与CSS中position: sticky属性实现的效果一致，当组件达到预设的到顶部距离时， 就会固定在指定位置，组件位置大于预设的顶部距离时，会重新按照正常的布局排列。\r\n\r\n### <a href=\"https://www.uvui.cn/components/sticky.html\" target=\"_blank\">查看文档</a>\r\n\r\n### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n#### 如使用过程中有任何问题，或者您对uv-ui有一些好的建议，欢迎加入 uv-ui 交流群：<a href=\"https://ext.dcloud.net.cn/plugin?id=12287\" target=\"_blank\">uv-ui</a>、<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-subsection/changelog.md",
    "content": "## 1.0.1（2023-05-16）\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\n2. 优化部分功能\n## 1.0.0（2023-05-10）\r\nuv-subsection 分段器\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-subsection/components/uv-subsection/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// tab的数据\r\n\t\tlist: {\r\n\t\t\ttype: Array,\r\n\t\t\tdefault: () => []\r\n\t\t},\r\n\t\t// 当前活动的tab的index\r\n\t\tcurrent: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 激活的颜色\r\n\t\tactiveColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#3c9cff'\r\n\t\t},\r\n\t\t// 未激活的颜色\r\n\t\tinactiveColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#303133'\r\n\t\t},\r\n\t\t// 模式选择，mode=button为按钮形式，mode=subsection时为分段模式\r\n\t\tmode: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'button'\r\n\t\t},\r\n\t\t// 字体大小\r\n\t\tfontSize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 12\r\n\t\t},\r\n\t\t// 激活tab的字体是否加粗\r\n\t\tbold: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// mode = button时，组件背景颜色\r\n\t\tbgColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#eeeeef'\r\n\t\t},\r\n\t\t// 从list元素对象中读取的键名\r\n\t\tkeyName: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'name'\r\n\t\t},\r\n\t\t...uni.$uv?.props?.subsection\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-subsection/components/uv-subsection/uv-subsection.vue",
    "content": "<template>\r\n\t<view class=\"uv-subsection\"\r\n\t\tref=\"uv-subsection\"\r\n\t\t:class=\"[`uv-subsection--${mode}`]\"\r\n\t\t:style=\"[$uv.addStyle(customStyle), wrapperStyle]\">\r\n\t\t<view class=\"uv-subsection__bar\"\r\n\t\t\tref=\"uv-subsection__bar\"\r\n\t\t\t:style=\"[barStyle]\"\r\n\t\t\t:class=\"[\r\n                mode === 'button' && 'uv-subsection--button__bar',\r\n                current === 0 &&\r\n                    mode === 'subsection' &&\r\n                    'uv-subsection__bar--first',\r\n                current > 0 &&\r\n                    current < list.length - 1 &&\r\n                    mode === 'subsection' &&\r\n                    'uv-subsection__bar--center',\r\n                current === list.length - 1 &&\r\n                    mode === 'subsection' &&\r\n                    'uv-subsection__bar--last',\r\n            ]\"></view>\r\n\t\t<view class=\"uv-subsection__item\"\r\n\t\t\t:class=\"[\r\n                `uv-subsection__item--${index}`,\r\n                index < list.length - 1 &&\r\n                    'uv-subsection__item--no-border-right',\r\n                index === 0 && 'uv-subsection__item--first',\r\n                index === list.length - 1 && 'uv-subsection__item--last',\r\n            ]\"\r\n\t\t\t:ref=\"`uv-subsection__item--${index}`\"\r\n\t\t\t:style=\"[itemStyle(index)]\"\r\n\t\t\t@tap=\"clickHandler(index)\"\r\n\t\t\tv-for=\"(item, index) in list\"\r\n\t\t\t:key=\"index\">\r\n\t\t\t<text class=\"uv-subsection__item__text\"\r\n\t\t\t\t:style=\"[textStyle(index)]\">{{ getText(item) }}</text>\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\t// #ifdef APP-NVUE\r\n\tconst dom = uni.requireNativePlugin(\"dom\");\r\n\tconst animation = uni.requireNativePlugin(\"animation\");\r\n\t// #endif\r\n\timport props from \"./props.js\";\r\n\t/**\r\n\t * Subsection 分段器\r\n\t * @description 该分段器一般用于用户从几个选项中选择某一个的场景\r\n\t * @tutorial https://www.uvui.cn/components/subsection.html\r\n\t * @property {Array}\t\t\tlist\t\t\ttab的数据\r\n\t * @property {String ｜ Number}\tcurrent\t\t\t当前活动的tab的index（默认 0 ）\r\n\t * @property {String}\t\t\tactiveColor\t\t激活时的颜色（默认 '#3c9cff' ）\r\n\t * @property {String}\t\t\tinactiveColor\t未激活时的颜色（默认 '#303133' ）\r\n\t * @property {String}\t\t\tmode\t\t\t模式选择，mode=button为按钮形式，mode=subsection时为分段模式（默认 'button' ）\r\n\t * @property {String ｜ Number}\tfontSize\t\t字体大小，单位px（默认 12 ）\r\n\t * @property {Boolean}\t\t\tbold\t\t\t激活选项的字体是否加粗（默认 true ）\r\n\t * @property {String}\t\t\tbgColor\t\t\t组件背景颜色，mode为button时有效（默认 '#eeeeef' ）\r\n\t * @property {Object}\t\t\tcustomStyle\t\t定义需要用到的外部样式\r\n\t * @property {String}\t        keyName\t        从`list`元素对象中读取的键名（默认 'name' ）\r\n\t *\r\n\t * @event {Function} change\t\t分段器选项发生改变时触发  回调 index：选项的index索引值，从0开始\r\n\t * @example <uv-subsection :list=\"list\" :current=\"curNow\" @change=\"sectionChange\"></uv-subsection>\r\n\t */\r\n\texport default {\r\n\t\tname: \"uv-subsection\",\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\t// 组件尺寸\r\n\t\t\t\titemRect: {\r\n\t\t\t\t\twidth: 0,\r\n\t\t\t\t\theight: 0,\r\n\t\t\t\t},\r\n\t\t\t};\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\tlist(newValue, oldValue) {\r\n\t\t\t\tthis.init();\r\n\t\t\t},\r\n\t\t\tcurrent: {\r\n\t\t\t\timmediate: true,\r\n\t\t\t\thandler(n) {\r\n\t\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\t\t// 在安卓nvue上，如果通过translateX进行位移，到最后一个时，会导致右侧无法绘制圆角\r\n\t\t\t\t\t// 故用animation模块进行位移\r\n\t\t\t\t\tconst ref = this.$refs?.[\"uv-subsection__bar\"]?.ref;\r\n\t\t\t\t\t// 不存在ref的时候(理解为第一次初始化时，需要渲染dom，进行一定延时再获取ref)，这里的100ms是经过测试得出的结果(某些安卓需要延时久一点)，勿随意修改\r\n\t\t\t\t\tthis.$uv.sleep(ref ? 0 : 100).then(() => {\r\n\t\t\t\t\t\tanimation.transition(this.$refs[\"uv-subsection__bar\"].ref, {\r\n\t\t\t\t\t\t\tstyles: {\r\n\t\t\t\t\t\t\t\ttransform: `translateX(${\r\n                                n * this.itemRect.width\r\n                            }px)`,\r\n\t\t\t\t\t\t\t\ttransformOrigin: \"center center\",\r\n\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t\tduration: 300,\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t});\r\n\t\t\t\t\t// #endif\r\n\t\t\t\t},\r\n\t\t\t},\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\twrapperStyle() {\r\n\t\t\t\tconst style = {};\r\n\t\t\t\t// button模式时，设置背景色\r\n\t\t\t\tif (this.mode === \"button\") {\r\n\t\t\t\t\tstyle.backgroundColor = this.bgColor;\r\n\t\t\t\t}\r\n\t\t\t\treturn style;\r\n\t\t\t},\r\n\t\t\t// 滑块的样式\r\n\t\t\tbarStyle() {\r\n\t\t\t\tconst style = {};\r\n\t\t\t\tstyle.width = `${this.itemRect.width}px`;\r\n\t\t\t\tstyle.height = `${this.itemRect.height}px`;\r\n\t\t\t\t// 通过translateX移动滑块，其移动的距离为索引*item的宽度\r\n\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\tstyle.transform = `translateX(${\r\n                this.current * this.itemRect.width\r\n            }px)`;\r\n\t\t\t\t// #endif\r\n\t\t\t\tif (this.mode === \"subsection\") {\r\n\t\t\t\t\t// 在subsection模式下，需要动态设置滑块的圆角，因为移动滑块使用的是translateX，无法通过父元素设置overflow: hidden隐藏滑块的直角\r\n\t\t\t\t\tstyle.backgroundColor = this.activeColor;\r\n\t\t\t\t}\r\n\t\t\t\treturn style;\r\n\t\t\t},\r\n\t\t\t// 分段器item的样式\r\n\t\t\titemStyle(index) {\r\n\t\t\t\treturn (index) => {\r\n\t\t\t\t\tconst style = {};\r\n\t\t\t\t\tif (this.mode === \"subsection\") {\r\n\t\t\t\t\t\t// 设置border的样式\r\n\t\t\t\t\t\tstyle.borderColor = this.activeColor;\r\n\t\t\t\t\t\tstyle.borderWidth = \"1px\";\r\n\t\t\t\t\t\tstyle.borderStyle = \"solid\";\r\n\t\t\t\t\t}\r\n\t\t\t\t\treturn style;\r\n\t\t\t\t};\r\n\t\t\t},\r\n\t\t\t// 分段器文字颜色\r\n\t\t\ttextStyle(index) {\r\n\t\t\t\treturn (index) => {\r\n\t\t\t\t\tconst style = {};\r\n\t\t\t\t\tstyle.fontWeight =\r\n\t\t\t\t\t\tthis.bold && this.current === index ? \"bold\" : \"normal\";\r\n\t\t\t\t\tstyle.fontSize = this.$uv.addUnit(this.fontSize);\r\n\t\t\t\t\t// subsection模式下，激活时默认为白色的文字\r\n\t\t\t\t\tif (this.mode === \"subsection\") {\r\n\t\t\t\t\t\tstyle.color =\r\n\t\t\t\t\t\t\tthis.current === index ? \"#fff\" : this.inactiveColor;\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\t// button模式下，激活时文字颜色默认为activeColor\r\n\t\t\t\t\t\tstyle.color =\r\n\t\t\t\t\t\t\tthis.current === index ?\r\n\t\t\t\t\t\t\tthis.activeColor :\r\n\t\t\t\t\t\t\tthis.inactiveColor;\r\n\t\t\t\t\t}\r\n\t\t\t\t\treturn style;\r\n\t\t\t\t};\r\n\t\t\t},\r\n\t\t},\r\n\t\tmounted() {\r\n\t\t\tthis.init();\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tinit() {\r\n\t\t\t\tthis.$uv.sleep().then(() => this.getRect());\r\n\t\t\t},\r\n\t\t\t// 判断展示文本\r\n\t\t\tgetText(item) {\r\n\t\t\t\treturn typeof item === 'object' ? item[this.keyName] : item\r\n\t\t\t},\r\n\t\t\t// 获取组件的尺寸\r\n\t\t\tgetRect() {\r\n\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\tthis.$uvGetRect(\".uv-subsection__item--0\").then((size) => {\r\n\t\t\t\t\tthis.itemRect = size;\r\n\t\t\t\t});\r\n\t\t\t\t// #endif\r\n\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\tconst ref = this.$refs[\"uv-subsection__item--0\"][0];\r\n\t\t\t\tref &&\r\n\t\t\t\t\tdom.getComponentRect(ref, (res) => {\r\n\t\t\t\t\t\tthis.itemRect = res.size;\r\n\t\t\t\t\t});\r\n\t\t\t\t// #endif\r\n\t\t\t},\r\n\t\t\tclickHandler(index) {\r\n\t\t\t\tthis.$emit(\"change\", index);\r\n\t\t\t},\r\n\t\t},\r\n\t};\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t.uv-subsection {\r\n\t\t@include flex;\r\n\t\tposition: relative;\r\n\t\toverflow: hidden;\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\twidth: 100%;\r\n\t\tbox-sizing: border-box;\r\n\t\t/* #endif */\r\n\t\t&--button {\r\n\t\t\theight: 32px;\r\n\t\t\tbackground-color: rgb(238, 238, 239);\r\n\t\t\tpadding: 3px;\r\n\t\t\tborder-radius: 3px;\r\n\t\t\talign-items: stretch;\r\n\t\t\t&__bar {\r\n\t\t\t\tbackground-color: #ffffff;\r\n\t\t\t\tborder-radius: 3px !important;\r\n\t\t\t}\r\n\t\t}\r\n\t\t&--subsection {\r\n\t\t\theight: 30px;\r\n\t\t}\r\n\t\t&__bar {\r\n\t\t\tposition: absolute;\r\n\t\t\t/* #ifndef APP-NVUE */\r\n\t\t\ttransition-property: transform, color;\r\n\t\t\ttransition-duration: 0.3s;\r\n\t\t\ttransition-timing-function: ease-in-out;\r\n\t\t\t/* #endif */\r\n\t\t\t&--first {\r\n\t\t\t\tborder-top-left-radius: 3px;\r\n\t\t\t\tborder-bottom-left-radius: 3px;\r\n\t\t\t\tborder-top-right-radius: 0px;\r\n\t\t\t\tborder-bottom-right-radius: 0px;\r\n\t\t\t}\r\n\t\t\t&--center {\r\n\t\t\t\tborder-top-left-radius: 0px;\r\n\t\t\t\tborder-bottom-left-radius: 0px;\r\n\t\t\t\tborder-top-right-radius: 0px;\r\n\t\t\t\tborder-bottom-right-radius: 0px;\r\n\t\t\t}\r\n\t\t\t&--last {\r\n\t\t\t\tborder-top-left-radius: 0px;\r\n\t\t\t\tborder-bottom-left-radius: 0px;\r\n\t\t\t\tborder-top-right-radius: 3px;\r\n\t\t\t\tborder-bottom-right-radius: 3px;\r\n\t\t\t}\r\n\t\t}\r\n\t\t&__item {\r\n\t\t\t@include flex;\r\n\t\t\tflex: 1;\r\n\t\t\tjustify-content: center;\r\n\t\t\talign-items: center;\r\n\t\t\t// vue环境下，需要设置相对定位，因为滑块为绝对定位，item需要在滑块的上面\r\n\t\t\tposition: relative;\r\n\t\t\t&--no-border-right {\r\n\t\t\t\tborder-right-width: 0 !important;\r\n\t\t\t}\r\n\t\t\t&--first {\r\n\t\t\t\tborder-top-left-radius: 3px;\r\n\t\t\t\tborder-bottom-left-radius: 3px;\r\n\t\t\t}\r\n\t\t\t&--last {\r\n\t\t\t\tborder-top-right-radius: 3px;\r\n\t\t\t\tborder-bottom-right-radius: 3px;\r\n\t\t\t}\r\n\t\t\t&__text {\r\n\t\t\t\tfont-size: 12px;\r\n\t\t\t\tline-height: 12px;\r\n\t\t\t\t@include flex;\r\n\t\t\t\talign-items: center;\r\n\t\t\t\ttransition-property: color;\r\n\t\t\t\ttransition-duration: 0.3s;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</style>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-subsection/package.json",
    "content": "{\r\n  \"id\": \"uv-subsection\",\r\n  \"displayName\": \"uv-subsection 分段器  全面兼容小程序、nvue、vue2、vue3等多端\",\r\n  \"version\": \"1.0.1\",\r\n  \"description\": \"该分段器组件一般用于用户从几个选项中选择某一个的场景。\",\r\n  \"keywords\": [\r\n    \"uv-subsection\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"subsection\",\r\n    \"分段器\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-subsection/readme.md",
    "content": "## Subsection 分段器\r\n\r\n> **组件名：uv-subsection**\r\n\r\n该分段器一般用于用户从几个选项中选择某一个的场景。\r\n\r\n### <a href=\"https://www.uvui.cn/components/subsection.html\" target=\"_blank\">查看文档</a>\r\n\r\n### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n#### 如使用过程中有任何问题，或者您对uv-ui有一些好的建议，欢迎加入 uv-ui 交流群：<a href=\"https://ext.dcloud.net.cn/plugin?id=12287\" target=\"_blank\">uv-ui</a>、<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-swipe-action/changelog.md",
    "content": "## 1.0.4（2023-10-13）\n1. 优化\n## 1.0.3（2023-10-13）\n1. unmounted兼容vue3\n## 1.0.2（2023-05-27）\r\n1. 不支持抖音小程序说明\r\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-swipe-action 滑动单元格\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-swipe-action/components/uv-swipe-action/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 是否自动关闭其他swipe按钮组\r\n\t\tautoClose: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t...uni.$uv?.props?.swipeAction\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-swipe-action/components/uv-swipe-action/uv-swipe-action.vue",
    "content": "<template>\r\n\t<view class=\"uv-swipe-action\">\r\n\t\t<slot></slot>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * SwipeAction 滑动单元格 \r\n\t * @description 该组件一般用于左滑唤出操作菜单的场景，用的最多的是左滑删除操作\r\n\t * @tutorial https://www.uvui.cn/components/swipeAction.html\r\n\t * @property {Boolean}\tautoClose\t是否自动关闭其他swipe按钮组\r\n\t * @event {Function(index)}\tclick\t点击组件时触发\r\n\t * @example\t<uv-swipe-action><uv-swipe-action-item :rightOptions=\"options1\" ></uv-swipe-action-item></uv-swipe-action>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-swipe-action',\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {}\r\n\t\t},\r\n\t\tprovide() {\r\n\t\t\treturn {\r\n\t\t\t\tswipeAction: this\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\t// 这里computed的变量，都是子组件uv-swipe-action-item需要用到的，由于头条小程序的兼容性差异，子组件无法实时监听父组件参数的变化\r\n\t\t\t// 所以需要手动通知子组件，这里返回一个parentData变量，供watch监听，在其中去通知每一个子组件重新从父组件(uv-swipe-action-item)\r\n\t\t\t// 拉取父组件新的变化后的参数\r\n\t\t\tparentData() {\r\n\t\t\t\treturn [this.autoClose]\r\n\t\t\t}\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\t// 当父组件需要子组件需要共享的参数发生了变化，手动通知子组件\r\n\t\t\tparentData() {\r\n\t\t\t\tif (this.children.length) {\r\n\t\t\t\t\tthis.children.map(child => {\r\n\t\t\t\t\t\t// 判断子组件(uv-swipe-action-item)如果有updateParentData方法的话，就就执行(执行的结果是子组件重新从父组件拉取了最新的值)\r\n\t\t\t\t\t\ttypeof(child.updateParentData) === 'function' && child.updateParentData()\r\n\t\t\t\t\t})\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t},\r\n\t\tcreated() {\r\n\t\t\tthis.children = []\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tcloseOther(child) {\r\n\t\t\t\tif (this.autoClose) {\r\n\t\t\t\t\t// 历遍所有的单元格，找出非当前操作中的单元格，进行关闭\r\n\t\t\t\t\tthis.children.map((item, index) => {\r\n\t\t\t\t\t\tif (child !== item) {\r\n\t\t\t\t\t\t\titem.closeHandler()\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t})\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-swipe-action/components/uv-swipe-action-item/index - backup.wxs",
    "content": "/**\r\n * 此为wxs模块，只支持APP-VUE，微信和QQ小程序以及H5平台\r\n * wxs内部不支持es6语法，变量只能使用var定义，无法使用解构，箭头函数等特性\r\n */\r\n\r\n// 开始触摸\r\nfunction touchstart(event, ownerInstance) {\r\n\t// 触发事件的组件的ComponentDescriptor实例\r\n\tvar instance = event.instance\r\n\t// wxs内的局部变量快照，此快照是属于整个组件的，在touchstart和touchmove事件中都能获取到相同的结果\r\n\tvar state = instance.getState()\r\n\tif (state.disable) return\r\n\tvar touches = event.touches\r\n\t// 如果进行的是多指触控，不允许进行操作\r\n\tif (touches && touches.length > 1) return\r\n\t// 标识当前为滑动中状态\r\n\tstate.moving = true\r\n\t// 记录触摸开始点的坐标值\r\n\tstate.startX = touches[0].pageX\r\n\tstate.startY = touches[0].pageY\r\n}\r\n\r\n// 触摸滑动\r\nfunction touchmove(event, ownerInstance) {\r\n\t// 触发事件的组件的ComponentDescriptor实例\r\n\tvar instance = event.instance\r\n\t// wxs内的局部变量快照\r\n\tvar state = instance.getState()\r\n\tif (state.disabled || !state.moving) return\r\n\r\n\tvar touches = event.touches\r\n\tvar pageX = touches[0].pageX\r\n\tvar pageY = touches[0].pageY\r\n\tvar moveX = pageX - state.startX\r\n\tvar moveY = pageY - state.startY\r\n\tvar buttonsWidth = state.buttonsWidth\r\n\r\n\t// 移动的X轴距离大于Y轴距离，也即终点与起点位置连线，与X轴夹角小于45度时，禁止页面滚动\r\n\tif (Math.abs(moveX) > Math.abs(moveY) || Math.abs(moveX) > state.threshold) {\r\n\t\tevent.preventDefault()\r\n\t\tevent.stopPropagation()\r\n\t}\r\n\t// 如果移动的X轴距离小于Y轴距离，也即终点位置与起点位置连线，与Y轴夹角小于45度时，认为是页面上下滑动，而不是左右滑动单元格\r\n\tif (Math.abs(moveX) < Math.abs(moveY)) return\r\n\r\n\t// 限制右滑的距离，不允许内容部分往右偏移，右滑会导致X轴偏移值大于0，以此做判断\r\n\t// 此处不能直接return，因为滑动过程中会缺失某些关键点坐标，会导致错乱，最好的办法就是\r\n\t// 在超出后，设置为0\r\n\tif (state.status === 'open') {\r\n\t\t// 在开启状态下，向左滑动，需忽略\r\n\t\tif (moveX < 0) moveX = 0\r\n\t\t// 想要收起菜单，最大能移动的距离为按钮的总宽度\r\n\t\tif (moveX > buttonsWidth) moveX = buttonsWidth\r\n\t\t// 如果是已经打开了的状态，向左滑动时，移动收起菜单\r\n\t\tmoveSwipeAction(-buttonsWidth + moveX, instance, ownerInstance)\r\n\t} else {\r\n\t\t// 关闭状态下，右滑动需忽略\r\n\t\tif (moveX > 0) moveX = 0\r\n\t\t// 滑动的距离不允许超过所有按钮的总宽度，此时只能是左滑，最终设置按钮的总宽度，同时为负数\r\n\t\tif (Math.abs(moveX) > buttonsWidth) moveX = -buttonsWidth\r\n\t\t// 只要是在滑过程中，就不断移动菜单的内容部分，从而使隐藏的菜单显示出来\r\n\t\tmoveSwipeAction(moveX, instance, ownerInstance)\r\n\t}\r\n}\r\n\r\n// 触摸结束\r\nfunction touchend(event, ownerInstance) {\r\n\t// 触发事件的组件的ComponentDescriptor实例\r\n\tvar instance = event.instance\r\n\t// wxs内的局部变量快照\r\n\tvar state = instance.getState()\r\n\tif (!state.moving || state.disabled) return\r\n\tvar touches = event.changedTouches ? event.changedTouches[0] : {}\r\n\tvar pageX = touches.pageX\r\n\tvar pageY = touches.pageY\r\n\tvar moveX = pageX - state.startX\r\n\tif (state.status === 'open') {\r\n\t\t// 在展开的状态下，继续左滑，无需操作\r\n\t\tif (moveX < 0) return\r\n\t\t// 在开启状态下，点击一下内容区域，moveX为0，也即没有进行移动，这时执行收起菜单逻辑\r\n\t\tif (moveX === 0) {\r\n\t\t\treturn closeSwipeAction(instance, ownerInstance)\r\n\t\t}\r\n\t\t// 在开启状态下，滑动距离小于阈值，则默认为不关闭，同时恢复原来的打开状态\r\n\t\tif (Math.abs(moveX) < state.threshold) {\r\n\t\t\topenSwipeAction(instance, ownerInstance)\r\n\t\t} else {\r\n\t\t\t// 如果滑动距离大于阈值，则执行收起逻辑\r\n\t\t\tcloseSwipeAction(instance, ownerInstance)\r\n\t\t}\r\n\t} else {\r\n\t\t// 在关闭的状态下，右滑，无需操作\r\n\t\tif (moveX > 0) return\r\n\t\t// 理由同上\r\n\t\tif (Math.abs(moveX) < state.threshold) {\r\n\t\t\tcloseSwipeAction(instance, ownerInstance)\r\n\t\t} else {\r\n\t\t\topenSwipeAction(instance, ownerInstance)\r\n\t\t}\r\n\t}\r\n}\r\n\r\n// 获取过渡时间\r\nfunction getDuration(value) {\r\n\tif (value.toString().indexOf('s') >= 0) return value\r\n\treturn value > 30 ? value + 'ms' : value + 's'\r\n}\r\n\r\n// 滑动结束时判断滑动的方向\r\nfunction getMoveDirection(instance, ownerInstance) {\r\n\tvar state = instance.getState()\r\n}\r\n\r\n// 移动滑动选择器内容区域，同时显示出其隐藏的菜单\r\nfunction moveSwipeAction(moveX, instance, ownerInstance) {\r\n\tvar state = instance.getState()\r\n\t// 获取所有按钮的实例，需要通过它去设置按钮的位移\r\n\tvar buttons = ownerInstance.selectAllComponents('.uv-swipe-action-item__right__button')\r\n\tvar len = buttons.length\r\n\tvar previewButtonsMoveX = 0\r\n\r\n\t// 设置菜单内容部分的偏移\r\n\tinstance.requestAnimationFrame(function() {\r\n\t\tinstance.setStyle({\r\n\t\t\t// 设置translateX的值\r\n\t\t\t'transition': 'none',\r\n\t\t\ttransform: 'translateX(' + moveX + 'px)',\r\n\t\t\t'-webkit-transform': 'translateX(' + moveX + 'px)'\r\n\t\t})\r\n\t\t// 折叠按钮动画\r\n\t\tfor (var i = len - 1; i >= 0; i--) {\r\n\t\t\t// 通过比例，得出元素自身该移动的距离\r\n\t\t\tvar translateX = state.buttons[i].width / state.buttonsWidth * moveX\r\n\t\t\t// 最终移动的距离，是通过自身比例算出的距离，再加上在它之前所有按钮移动的距离之和\r\n\t\t\tvar realTranslateX = translateX + previewButtonsMoveX\r\n\t\t\tbuttons[i].setStyle({\r\n\t\t\t\t// 在移动期间，不能使用过渡效果，否则会造成卡顿，本质原因是每次移动一点，就要花一定时间去过渡这个过程\r\n\t\t\t\t'transition': 'none',\r\n\t\t\t\t'transform': 'translateX(' + realTranslateX + 'px)',\r\n\t\t\t\t'-webkit-transform': 'translateX(' + realTranslateX + 'px)'\r\n\t\t\t})\r\n\t\t\t// 记录本按钮之前的所有按钮的移动距离之和\r\n\t\t\tpreviewButtonsMoveX += translateX\r\n\t\t}\r\n\t})\r\n}\r\n\r\n// 一次性展开滑动菜单\r\nfunction openSwipeAction(instance, ownerInstance) {\r\n\tvar state = instance.getState()\r\n\t// 获取所有按钮的实例，需要通过它去设置按钮的位移\r\n\tvar buttons = ownerInstance.selectAllComponents('.uv-swipe-action-item__right__button')\r\n\tvar len = buttons.length\r\n\t// 处理duration单位问题\r\n\tconst duration = getDuration(state.duration)\r\n\t// 展开过程中，是向左移动，所以X的偏移应该为负值\r\n\tvar buttonsWidth = -state.buttonsWidth\r\n\tvar previewButtonsMoveX = 0\r\n\tinstance.requestAnimationFrame(function() {\r\n\t\t// 设置菜单主体内容\r\n\t\tinstance.setStyle({\r\n\t\t\t'transition': 'transform ' + duration,\r\n\t\t\t'transform': 'translateX(' + buttonsWidth + 'px)',\r\n\t\t\t'-webkit-transform': 'translateX(' + buttonsWidth + 'px)',\r\n\t\t})\r\n\t\t// 设置各个隐藏的按钮为展开的状态\r\n\t\tfor (var i = len - 1; i >= 0; i--) {\r\n\t\t\t// 通过比例，得出元素自身该移动的距离\r\n\t\t\tvar translateX = state.buttons[i].width / state.buttonsWidth * buttonsWidth\r\n\t\t\t// 最终移动的距离，是通过自身比例算出的距离，再加上在它之前所有按钮移动的距离之和\r\n\t\t\tvar realTranslateX = translateX + previewButtonsMoveX\r\n\t\t\tbuttons[i].setStyle({\r\n\t\t\t\t// 在移动期间，需要加上动画效果\r\n\t\t\t\t'transition': 'transform ' + duration,\r\n\t\t\t\t'transform': 'translateX(' + realTranslateX + 'px)',\r\n\t\t\t\t'-webkit-transform': 'translateX(' + realTranslateX + 'px)'\r\n\t\t\t})\r\n\t\t\t// 记录本按钮之前的所有按钮的移动距离之和\r\n\t\t\tpreviewButtonsMoveX += translateX\r\n\t\t}\r\n\t})\r\n\tsetStatus('open', instance)\r\n}\r\n\r\n// 标记菜单的当前状态，open-已经打开，close-已经关闭\r\nfunction setStatus(status, instance) {\r\n\tvar state = instance.getState()\r\n\tstate.status = status\r\n}\r\n\r\n// 一次性收起滑动菜单\r\nfunction closeSwipeAction(instance, ownerInstance) {\r\n\tvar state = instance.getState()\r\n\t// 获取所有按钮的实例，需要通过它去设置按钮的位移\r\n\tvar buttons = ownerInstance.selectAllComponents('.uv-swipe-action-item__right__button')\r\n\tvar len = buttons.length\r\n\t// 处理duration单位问题\r\n\tconst duration = getDuration(state.duration)\r\n\tinstance.requestAnimationFrame(function() {\r\n\t\t// 设置菜单主体内容\r\n\t\tinstance.setStyle({\r\n\t\t\t'transition': 'transform ' + duration,\r\n\t\t\t'transform': 'translateX(0px)',\r\n\t\t\t'-webkit-transform': 'translateX(0px)'\r\n\t\t})\r\n\t\t// 设置各个隐藏的按钮为收起的状态\r\n\t\tfor (var i = len - 1; i >= 0; i--) {\r\n\t\t\tbuttons[i].setStyle({\r\n\t\t\t\t'transition': 'transform ' + duration,\r\n\t\t\t\t'transform': 'translateX(0px)',\r\n\t\t\t\t'-webkit-transform': 'translateX(0px)'\r\n\t\t\t})\r\n\t\t}\r\n\t})\r\n\tsetStatus('close', instance)\r\n}\r\n\r\n// show的状态发生变化\r\nfunction showChange(newValue, oldValue, ownerInstance, instance) {\r\n\tvar state = instance.getState()\r\n\tif (state.disabled) return\r\n\t// 打开或关闭单元格\r\n\tif (newValue) {\r\n\t\topenSwipeAction(instance, ownerInstance)\r\n\t} else {\r\n\t\tcloseSwipeAction(instance, ownerInstance)\r\n\t}\r\n}\r\n\r\n// 菜单尺寸发生变化\r\nfunction sizeChange(newValue, oldValue, ownerInstance, instance) {\r\n\t// wxs内的局部变量快照\r\n\tvar state = instance.getState()\r\n\tstate.disabled = newValue.disabled\r\n\tstate.duration = newValue.duration\r\n\tstate.show = newValue.show\r\n\tstate.threshold = newValue.threshold\r\n\tstate.buttons = newValue.buttons\r\n\r\n\tvar len = state.buttons.length\r\n\tif (len) {\r\n\t\tvar buttonsWidth = 0\r\n\t\tvar buttons = newValue.buttons\r\n\t\tfor (var i = 0; i < len; i++) {\r\n\t\t\tbuttonsWidth += buttons[i].width\r\n\t\t}\r\n\t}\r\n\tstate.buttonsWidth = buttonsWidth\r\n}\r\n\r\nmodule.exports = {\r\n\ttouchstart: touchstart,\r\n\ttouchmove: touchmove,\r\n\ttouchend: touchend,\r\n\tsizeChange: sizeChange\r\n}\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-swipe-action/components/uv-swipe-action-item/index.wxs",
    "content": "/**\r\n * 此为wxs模块，只支持APP-VUE，微信和QQ小程序以及H5平台\r\n * wxs内部不支持es6语法，变量只能使用var定义，无法使用解构，箭头函数等特性\r\n */\r\n\r\n// 开始触摸\r\nfunction touchstart(event, ownerInstance) {\r\n\t// 触发事件的组件的ComponentDescriptor实例\r\n\tvar instance = event.instance\r\n\t// wxs内的局部变量快照，此快照是属于整个组件的，在touchstart和touchmove事件中都能获取到相同的结果\r\n\tvar state = instance.getState()\r\n\tif (state.disabled) return\r\n\tvar touches = event.touches\r\n\t// 如果进行的是多指触控，不允许进行操作\r\n\tif (touches && touches.length > 1) return\r\n\t// 标识当前为滑动中状态\r\n\tstate.moving = true\r\n\t// 记录触摸开始点的坐标值\r\n\tstate.startX = touches[0].pageX\r\n\tstate.startY = touches[0].pageY\r\n\t\r\n\townerInstance.callMethod('closeOther')\r\n}\r\n\r\n// 触摸滑动\r\nfunction touchmove(event, ownerInstance) {\r\n\t// 触发事件的组件的ComponentDescriptor实例\r\n\tvar instance = event.instance\r\n\t// wxs内的局部变量快照\r\n\tvar state = instance.getState()\r\n\tif (state.disabled || !state.moving) return\r\n\tvar touches = event.touches\r\n\tvar pageX = touches[0].pageX\r\n\tvar pageY = touches[0].pageY\r\n\tvar moveX = pageX - state.startX\r\n\tvar moveY = pageY - state.startY\r\n\tvar buttonsWidth = state.buttonsWidth\r\n\r\n\t// 移动的X轴距离大于Y轴距离，也即终点与起点位置连线，与X轴夹角小于45度时，禁止页面滚动\r\n\tif (Math.abs(moveX) > Math.abs(moveY) || Math.abs(moveX) > state.threshold) {\r\n\t\tevent.preventDefault && event.preventDefault()\r\n\t\tevent.stopPropagation && event.stopPropagation()\r\n\t}\r\n\t// 如果移动的X轴距离小于Y轴距离，也即终点位置与起点位置连线，与Y轴夹角小于45度时，认为是页面上下滑动，而不是左右滑动单元格\r\n\tif (Math.abs(moveX) < Math.abs(moveY)) return\r\n\r\n\t// 限制右滑的距离，不允许内容部分往右偏移，右滑会导致X轴偏移值大于0，以此做判断\r\n\t// 此处不能直接return，因为滑动过程中会缺失某些关键点坐标，会导致错乱，最好的办法就是\r\n\t// 在超出后，设置为0\r\n\tif (state.status === 'open') {\r\n\t\t// 在开启状态下，向左滑动，需忽略\r\n\t\tif (moveX < 0) moveX = 0\r\n\t\t// 想要收起菜单，最大能移动的距离为按钮的总宽度\r\n\t\tif (moveX > buttonsWidth) moveX = buttonsWidth\r\n\t\t// 如果是已经打开了的状态，向左滑动时，移动收起菜单\r\n\t\tmoveSwipeAction(-buttonsWidth + moveX, instance, ownerInstance)\r\n\t} else {\r\n\t\t// 关闭状态下，右滑动需忽略\r\n\t\tif (moveX > 0) moveX = 0\r\n\t\t// 滑动的距离不允许超过所有按钮的总宽度，此时只能是左滑，最终设置按钮的总宽度，同时为负数\r\n\t\tif (Math.abs(moveX) > buttonsWidth) moveX = -buttonsWidth\r\n\t\t// 只要是在滑过程中，就不断移动单元格内容部分，从而使隐藏的菜单显示出来\r\n\t\tmoveSwipeAction(moveX, instance, ownerInstance)\r\n\t}\r\n}\r\n\r\n// 触摸结束\r\nfunction touchend(event, ownerInstance) {\r\n\t// 触发事件的组件的ComponentDescriptor实例\r\n\tvar instance = event.instance\r\n\t// wxs内的局部变量快照\r\n\tvar state = instance.getState()\r\n\tif (!state.moving || state.disabled) return\r\n\tvar touches = event.changedTouches ? event.changedTouches[0] : {}\r\n\tvar pageX = touches.pageX\r\n\tvar pageY = touches.pageY\r\n\tvar moveX = pageX - state.startX\r\n\tif (state.status === 'open') {\r\n\t\t// 在展开的状态下，继续左滑，无需操作\r\n\t\tif (moveX < 0) return\r\n\t\t// 在开启状态下，点击一下内容区域，moveX为0，也即没有进行移动，这时执行收起菜单逻辑\r\n\t\tif (moveX === 0) {\r\n\t\t\treturn closeSwipeAction(instance, ownerInstance)\r\n\t\t}\r\n\t\t// 在开启状态下，滑动距离小于阈值，则默认为不关闭，同时恢复原来的打开状态\r\n\t\tif (Math.abs(moveX) < state.threshold) {\r\n\t\t\topenSwipeAction(instance, ownerInstance)\r\n\t\t} else {\r\n\t\t\t// 如果滑动距离大于阈值，则执行收起逻辑\r\n\t\t\tcloseSwipeAction(instance, ownerInstance)\r\n\t\t}\r\n\t} else {\r\n\t\t// 在关闭的状态下，右滑，无需操作\r\n\t\tif (moveX > 0) return\r\n\t\t// 理由同上\r\n\t\tif (Math.abs(moveX) < state.threshold) {\r\n\t\t\tcloseSwipeAction(instance, ownerInstance)\r\n\t\t} else {\r\n\t\t\topenSwipeAction(instance, ownerInstance)\r\n\t\t}\r\n\t}\r\n}\r\n\r\n// 获取过渡时间\r\nfunction getDuration(value) {\r\n\tif (value.toString().indexOf('s') >= 0) return value\r\n\treturn value > 30 ? value + 'ms' : value + 's'\r\n}\r\n\r\n// 滑动结束时判断滑动的方向\r\nfunction getMoveDirection(instance, ownerInstance) {\r\n\tvar state = instance.getState()\r\n}\r\n\r\n// 移动滑动选择器内容区域，同时显示出其隐藏的菜单\r\nfunction moveSwipeAction(moveX, instance, ownerInstance) {\r\n\tvar state = instance.getState()\r\n\t// 获取所有按钮的实例，需要通过它去设置按钮的位移\r\n\tvar buttons = ownerInstance.selectAllComponents('.uv-swipe-action-item__right__button')\r\n\r\n\t// 设置菜单内容部分的偏移\r\n\tinstance.requestAnimationFrame(function() {\r\n\t\tinstance.setStyle({\r\n\t\t\t// 设置translateX的值\r\n\t\t\t'transition': 'none',\r\n\t\t\ttransform: 'translateX(' + moveX + 'px)',\r\n\t\t\t'-webkit-transform': 'translateX(' + moveX + 'px)'\r\n\t\t})\r\n\t})\r\n}\r\n\r\n// 一次性展开滑动菜单\r\nfunction openSwipeAction(instance, ownerInstance) {\r\n\tvar state = instance.getState()\r\n\t// 获取所有按钮的实例，需要通过它去设置按钮的位移\r\n\tvar buttons = ownerInstance.selectAllComponents('.uv-swipe-action-item__right__button')\r\n\t// 处理duration单位问题\r\n\tvar duration = getDuration(state.duration)\r\n\t// 展开过程中，是向左移动，所以X的偏移应该为负值\r\n\tvar buttonsWidth = -state.buttonsWidth\r\n\tinstance.requestAnimationFrame(function() {\r\n\t\t// 设置菜单主体内容\r\n\t\tinstance.setStyle({\r\n\t\t\t'transition': 'transform ' + duration,\r\n\t\t\t'transform': 'translateX(' + buttonsWidth + 'px)',\r\n\t\t\t'-webkit-transform': 'translateX(' + buttonsWidth + 'px)',\r\n\t\t})\r\n\t})\r\n\tsetStatus('open', instance, ownerInstance)\r\n}\r\n\r\n// 标记菜单的当前状态，open-已经打开，close-已经关闭\r\nfunction setStatus(status, instance, ownerInstance) {\r\n\tvar state = instance.getState()\r\n\tstate.status = status\r\n\townerInstance.callMethod('setState', status)\r\n}\r\n\r\n// 一次性收起滑动菜单\r\nfunction closeSwipeAction(instance, ownerInstance) {\r\n\tvar state = instance.getState()\r\n\t// 获取所有按钮的实例，需要通过它去设置按钮的位移\r\n\tvar buttons = ownerInstance.selectAllComponents('.uv-swipe-action-item__right__button')\r\n\tvar len = buttons.length\r\n\t// 处理duration单位问题\r\n\tvar duration = getDuration(state.duration)\r\n\tinstance.requestAnimationFrame(function() {\r\n\t\t// 设置菜单主体内容\r\n\t\tinstance.setStyle({\r\n\t\t\t'transition': 'transform ' + duration,\r\n\t\t\t'transform': 'translateX(0px)',\r\n\t\t\t'-webkit-transform': 'translateX(0px)'\r\n\t\t})\r\n\t\t// 设置各个隐藏的按钮为收起的状态\r\n\t\tfor (var i = len - 1; i >= 0; i--) {\r\n\t\t\tbuttons[i].setStyle({\r\n\t\t\t\t'transition': 'transform ' + duration,\r\n\t\t\t\t'transform': 'translateX(0px)',\r\n\t\t\t\t'-webkit-transform': 'translateX(0px)'\r\n\t\t\t})\r\n\t\t}\r\n\t})\r\n\tsetStatus('close', instance, ownerInstance)\r\n}\r\n\r\n// status的状态发生变化\r\nfunction statusChange(newValue, oldValue, ownerInstance, instance) {\r\n\tvar state = instance.getState()\r\n\tif (state.disabled) return\r\n\t// 打开或关闭单元格\r\n\tif (newValue === 'close' && state.status === 'open') {\r\n\t\tcloseSwipeAction(instance, ownerInstance)\r\n\t} else if(newValue === 'open' && state.status === 'close') {\r\n\t\topenSwipeAction(instance, ownerInstance)\r\n\t}\r\n}\r\n\r\n// 菜单尺寸发生变化\r\nfunction sizeChange(newValue, oldValue, ownerInstance, instance) {\r\n\t// wxs内的局部变量快照\r\n\tvar state = instance.getState()\r\n\tstate.disabled = newValue.disabled\r\n\tstate.duration = newValue.duration\r\n\tstate.show = newValue.show\r\n\tstate.threshold = newValue.threshold\r\n\tstate.buttons = newValue.buttons\r\n\r\n\tif (state.buttons) {\r\n\t\tvar len = state.buttons.length\r\n\t\tvar buttonsWidth = 0\r\n\t\tvar buttons = newValue.buttons\r\n\t\tfor (var i = 0; i < len; i++) {\r\n\t\t\tbuttonsWidth += buttons[i].width\r\n\t\t}\r\n\t}\r\n\tstate.buttonsWidth = buttonsWidth\r\n}\r\n\r\nmodule.exports = {\r\n\ttouchstart: touchstart,\r\n\ttouchmove: touchmove,\r\n\ttouchend: touchend,\r\n\tsizeChange: sizeChange,\r\n\tstatusChange: statusChange\r\n}\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-swipe-action/components/uv-swipe-action-item/nvue - backup.js",
    "content": "// nvue操作dom的库，用于获取dom的尺寸信息\r\nconst dom = uni.requireNativePlugin('dom')\r\n// nvue中用于操作元素动画的库，类似于uni.animation，只不过uni.animation不能用于nvue\r\nconst animation = uni.requireNativePlugin('animation')\r\nimport { sleep } from '@/uni_modules/uv-ui-tools/libs/function/index.js'\r\nexport default {\r\n    data() {\r\n        return {\r\n            // 是否滑动中\r\n            moving: false,\r\n            // 状态，open-打开状态，close-关闭状态\r\n            status: 'close',\r\n            // 开始触摸点的X和Y轴坐标\r\n            startX: 0,\r\n            startY: 0,\r\n            // 所有隐藏按钮的尺寸信息数组\r\n            buttons: [],\r\n            // 所有按钮的总宽度\r\n            buttonsWidth: 0,\r\n            // 记录上一次移动的位置值\r\n            moveX: 0,\r\n            // 记录上一次滑动的位置，用于前后两次做对比，如果移动的距离小于某一阈值，则认为前后之间没有移动，为了解决可能存在的通信阻塞问题\r\n            lastX: 0\r\n        }\r\n    },\r\n    computed: {\r\n        // 获取过渡时间\r\n        getDuratin() {\r\n            let duration = String(this.duration)\r\n            // 如果ms为单位，返回ms的数值部分\r\n            if (duration.indexOf('ms') >= 0) return parseInt(duration)\r\n            // 如果s为单位，为了得到ms的数值，需要乘以1000\r\n            if (duration.indexOf('s') >= 0) return parseInt(duration) * 1000\r\n            // 如果值传了数值，且小于30，认为是s单位\r\n            duration = Number(duration)\r\n            return duration < 30 ? duration * 1000 : duration\r\n        }\r\n    },\r\n    watch: {\r\n        show: {\r\n            immediate: true,\r\n            handler(n) {\r\n                \r\n            }\r\n        }\r\n    },\r\n    mounted() {\r\n        sleep(20).then(() => {\r\n            this.queryRect()\r\n        })\r\n    },\r\n    methods: {\r\n        close() {\r\n            this.closeSwipeAction()\r\n        },\r\n        // 触摸单元格\r\n        touchstart(event) {\r\n            if (this.disabled) return\r\n            this.closeOther()\r\n            const { touches } = event\r\n            // 记录触摸开始点的坐标值\r\n            this.startX = touches[0].pageX\r\n            this.startY = touches[0].pageY\r\n        },\r\n        // // 触摸滑动\r\n        touchmove(event) {\r\n            if (this.disabled) return\r\n            const { touches } = event\r\n            const { pageX } = touches[0]\r\n            const { pageY } = touches[0]\r\n            let moveX = pageX - this.startX\r\n            const moveY = pageY - this.startY\r\n            const { buttonsWidth } = this\r\n            const len = this.buttons.length\r\n\r\n            // 判断前后两次的移动距离，如果小于一定值，则不进行移动处理\r\n            if (Math.abs(pageX - this.lastX) < 0.3) return\r\n            this.lastX = pageX\r\n\r\n            // 移动的X轴距离大于Y轴距离，也即终点与起点位置连线，与X轴夹角小于45度时，禁止页面滚动\r\n            if (Math.abs(moveX) > Math.abs(moveY) || Math.abs(moveX) > this.threshold) {\r\n                event.stopPropagation()\r\n            }\r\n            // 如果移动的X轴距离小于Y轴距离，也即终点位置与起点位置连线，与Y轴夹角小于45度时，认为是页面上下滑动，而不是左右滑动单元格\r\n            if (Math.abs(moveX) < Math.abs(moveY)) return\r\n\r\n            // 限制右滑的距离，不允许内容部分往右偏移，右滑会导致X轴偏移值大于0，以此做判断\r\n            // 此处不能直接return，因为滑动过程中会缺失某些关键点坐标，会导致错乱，最好的办法就是\r\n            // 在超出后，设置为0\r\n            if (this.status === 'open') {\r\n                // 在开启状态下，向左滑动，需忽略\r\n                if (moveX < 0) moveX = 0\r\n                // 想要收起菜单，最大能移动的距离为按钮的总宽度\r\n                if (moveX > buttonsWidth) moveX = buttonsWidth\r\n                // 如果是已经打开了的状态，向左滑动时，移动收起菜单\r\n                this.moveSwipeAction(-buttonsWidth + moveX)\r\n            } else {\r\n                // 关闭状态下，右滑动需忽略\r\n                if (moveX > 0) moveX = 0\r\n                // 滑动的距离不允许超过所有按钮的总宽度，此时只能是左滑，最终设置按钮的总宽度，同时为负数\r\n                if (Math.abs(moveX) > buttonsWidth) moveX = -buttonsWidth\r\n                // 只要是在滑过程中，就不断移动菜单的内容部分，从而使隐藏的菜单显示出来\r\n                this.moveSwipeAction(moveX)\r\n            }\r\n        },\r\n        // 单元格结束触摸\r\n        touchend(event) {\r\n            if (this.disabled) return\r\n            const touches = event.changedTouches ? event.changedTouches[0] : {}\r\n            const { pageX } = touches\r\n            const { pageY } = touches\r\n            const { buttonsWidth } = this\r\n            this.moveX = pageX - this.startX\r\n            if (this.status === 'open') {\r\n                // 在展开的状态下，继续左滑，无需操作\r\n                if (this.moveX < 0) this.moveX = 0\r\n                if (this.moveX > buttonsWidth) this.moveX = buttonsWidth\r\n                // 在开启状态下，点击一下内容区域，moveX为0，也即没有进行移动，这时执行收起菜单逻辑\r\n                if (this.moveX === 0) {\r\n                    return this.closeSwipeAction()\r\n                }\r\n                // 在开启状态下，滑动距离小于阈值，则默认为不关闭，同时恢复原来的打开状态\r\n                if (Math.abs(this.moveX) < this.threshold) {\r\n                    this.openSwipeAction()\r\n                } else {\r\n                    // 如果滑动距离大于阈值，则执行收起逻辑\r\n                    this.closeSwipeAction()\r\n                }\r\n            } else {\r\n                // 在关闭的状态下，右滑，无需操作\r\n                if (this.moveX >= 0) this.moveX = 0\r\n                if (this.moveX <= -buttonsWidth) this.moveX = -buttonsWidth\r\n                // 理由同上\r\n                if (Math.abs(this.moveX) < this.threshold) {\r\n                    this.closeSwipeAction()\r\n                } else {\r\n                    this.openSwipeAction()\r\n                }\r\n            }\r\n        },\r\n        // 移动滑动选择器内容区域，同时显示出其隐藏的菜单\r\n        moveSwipeAction(moveX) {\r\n            if (this.moving) return\r\n            this.moving = true\r\n\r\n            let previewButtonsMoveX = 0\r\n            const len = this.buttons.length\r\n            animation.transition(this.$refs['uv-swipe-action-item__content'].ref, {\r\n                styles: {\r\n                    transform: `translateX(${moveX}px)`\r\n                },\r\n                timingFunction: 'linear'\r\n            }, () => {\r\n                this.moving = false\r\n            })\r\n            // 按钮的组的长度\r\n            for (let i = len - 1; i >= 0; i--) {\r\n                const buttonRef = this.$refs[`uv-swipe-action-item__right__button-${i}`][0].ref\r\n                // 通过比例，得出元素自身该移动的距离\r\n                const translateX = this.buttons[i].width / this.buttonsWidth * moveX\r\n                // 最终移动的距离，是通过自身比例算出的距离，再加上在它之前所有按钮移动的距离之和\r\n                const realTranslateX = translateX + previewButtonsMoveX\r\n                animation.transition(buttonRef, {\r\n                    styles: {\r\n                        transform: `translateX(${realTranslateX}px)`\r\n                    },\r\n                    duration: 0,\r\n                    delay: 0,\r\n                    timingFunction: 'linear'\r\n                }, () => {})\r\n                // 记录本按钮之前的所有按钮的移动距离之和\r\n                previewButtonsMoveX += translateX\r\n            }\r\n        },\r\n        // 关闭菜单\r\n        closeSwipeAction() {\r\n            if (this.status === 'close') return\r\n            this.moving = true\r\n            const { buttonsWidth } = this\r\n            animation.transition(this.$refs['uv-swipe-action-item__content'].ref, {\r\n                styles: {\r\n                    transform: 'translateX(0px)'\r\n                },\r\n                duration: this.getDuratin,\r\n                timingFunction: 'ease-in-out'\r\n            }, () => {\r\n                this.status = 'close'\r\n                this.moving = false\r\n                this.closeHandler()\r\n            })\r\n            // 按钮的组的长度\r\n            const len = this.buttons.length\r\n            for (let i = len - 1; i >= 0; i--) {\r\n                const buttonRef = this.$refs[`uv-swipe-action-item__right__button-${i}`][0].ref\r\n                // 如果不满足边界条件，返回\r\n                if (this.buttons.length === 0 || !this.buttons[i] || !this.buttons[i].width) return\r\n\r\n                animation.transition(buttonRef, {\r\n                    styles: {\r\n                        transform: 'translateX(0px)'\r\n                    },\r\n                    duration: this.getDuratin,\r\n                    timingFunction: 'ease-in-out'\r\n                }, () => {})\r\n            }\r\n        },\r\n        // 打开菜单\r\n        openSwipeAction() {\r\n            if (this.status === 'open') return\r\n            this.moving = true\r\n            const buttonsWidth = -this.buttonsWidth\r\n            let previewButtonsMoveX = 0\r\n            animation.transition(this.$refs['uv-swipe-action-item__content'].ref, {\r\n                styles: {\r\n                    transform: `translateX(${buttonsWidth}px)`\r\n                },\r\n                duration: this.getDuratin,\r\n                timingFunction: 'ease-in-out'\r\n            }, () => {\r\n                this.status = 'open'\r\n                this.moving = false\r\n                this.openHandler()\r\n            })\r\n            // 按钮的组的长度\r\n            const len = this.buttons.length\r\n            for (let i = len - 1; i >= 0; i--) {\r\n                const buttonRef = this.$refs[`uv-swipe-action-item__right__button-${i}`][0].ref\r\n                // 如果不满足边界条件，返回\r\n                if (this.buttons.length === 0 || !this.buttons[i] || !this.buttons[i].width) return\r\n                // 通过比例，得出元素自身该移动的距离\r\n                const translateX = this.buttons[i].width / this.buttonsWidth * buttonsWidth\r\n                // 最终移动的距离，是通过自身比例算出的距离，再加上在它之前所有按钮移动的距离之和\r\n                const realTranslateX = translateX + previewButtonsMoveX\r\n                animation.transition(buttonRef, {\r\n                    styles: {\r\n                        transform: `translateX(${realTranslateX}px)`\r\n                    },\r\n                    duration: this.getDuratin,\r\n                    timingFunction: 'ease-in-out'\r\n                }, () => {})\r\n                previewButtonsMoveX += translateX\r\n            }\r\n        },\r\n        // 查询按钮节点信息\r\n        queryRect() {\r\n            // 历遍所有按钮数组，通过getRectByDom返回一个promise\r\n            const promiseAll = this.rightOptions.map((item, index) => this.getRectByDom(this.$refs[`uv-swipe-action-item__right__button-${index}`][0]))\r\n            // 通过promise.all方法，让所有按钮的查询结果返回一个数组的形式\r\n            Promise.all(promiseAll).then((sizes) => {\r\n                this.buttons = sizes\r\n                // 计算所有按钮总宽度\r\n                this.buttonsWidth = sizes.reduce((sum, cur) => sum + cur.width, 0)\r\n            })\r\n        },\r\n        // 通过nvue的dom模块，查询节点信息\r\n        getRectByDom(ref) {\r\n            return new Promise((resolve) => {\r\n                dom.getComponentRect(ref, (res) => {\r\n                    resolve(res.size)\r\n                })\r\n            })\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-swipe-action/components/uv-swipe-action-item/nvue.js",
    "content": "// nvue操作dom的库，用于获取dom的尺寸信息\r\nconst dom = uni.requireNativePlugin('dom');\r\nconst bindingX = uni.requireNativePlugin('bindingx');\r\nconst animation = uni.requireNativePlugin('animation');\r\nimport { getDuration, getPx } from '@/uni_modules/uv-ui-tools/libs/function/index.js'\r\nexport default {\r\n\tdata() {\r\n\t\treturn {\r\n\t\t\t// 所有按钮的总宽度\r\n\t\t\tbuttonsWidth: 0,\r\n\t\t\t// 是否正在移动中\r\n\t\t\tmoving: false\r\n\t\t}\r\n\t},\r\n\tcomputed: {\r\n\t\t// 获取过渡时间\r\n\t\tgetDuratin() {\r\n\t\t\tlet duration = String(this.duration)\r\n\t\t\t// 如果ms为单位，返回ms的数值部分\r\n\t\t\tif (duration.indexOf('ms') >= 0) return parseInt(duration)\r\n\t\t\t// 如果s为单位，为了得到ms的数值，需要乘以1000\r\n\t\t\tif (duration.indexOf('s') >= 0) return parseInt(duration) * 1000\r\n\t\t\t// 如果值传了数值，且小于30，认为是s单位\r\n\t\t\tduration = Number(duration)\r\n\t\t\treturn duration < 30 ? duration * 1000 : duration\r\n\t\t}\r\n\t},\r\n\twatch: {\r\n\t\tshow(n) {\r\n\t\t\tif(n) {\r\n\t\t\t\tthis.moveCellByAnimation('open') \r\n\t\t\t} else {\r\n\t\t\t\tthis.moveCellByAnimation('close') \r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\tmounted() {\r\n\t\tsetTimeout(()=>{\r\n\t\t\tthis.initialize()\r\n\t\t},20)\r\n\t},\r\n\tmethods: {\r\n\t\tinitialize() {\r\n\t\t\tthis.queryRect() \r\n\t\t},\r\n\t\t// 关闭单元格，用于打开一个，自动关闭其他单元格的场景\r\n\t\tcloseHandler() {\r\n\t\t\tif(this.status === 'open') {\r\n\t\t\t\t// 如果在打开状态下，进行点击的话，直接关闭单元格\r\n\t\t\t\treturn this.moveCellByAnimation('close') && this.unbindBindingX()\r\n\t\t\t}\r\n\t\t},\r\n\t\t// 点击单元格\r\n\t\tclickHandler() {\r\n\t\t\t// 如果在移动中被点击，进行忽略\r\n\t\t\tif(this.moving) return\r\n\t\t\t// 尝试关闭其他打开的单元格\r\n\t\t\tthis.parent && this.parent.closeOther(this)\r\n\t\t\tif(this.status === 'open') {\r\n\t\t\t\t// 如果在打开状态下，进行点击的话，直接关闭单元格\r\n\t\t\t\treturn this.moveCellByAnimation('close') && this.unbindBindingX()\r\n\t\t\t}\r\n\t\t},\r\n\t\t// 滑动单元格\r\n\t\tonTouchstart(e) {\r\n\t\t\t// 如果当前正在移动中，或者disabled状态，则返回\r\n\t\t\tif(this.moving || this.disabled) { \r\n\t\t\t\treturn this.unbindBindingX()   \r\n\t\t\t}\r\n\t\t\tif(this.status === 'open') {\r\n\t\t\t\t// 如果在打开状态下，进行点击的话，直接关闭单元格\r\n\t\t\t\treturn this.moveCellByAnimation('close') && this.unbindBindingX()\r\n\t\t\t}\r\n\t\t\t// 特殊情况下，e可能不为一个对象\r\n\t\t\te?.stopPropagation && e.stopPropagation() \r\n\t\t\te?.preventDefault && e.preventDefault()\r\n\t\t\tthis.moving = true\r\n\t\t\t// 获取元素ref\r\n\t\t\tconst content = this.getContentRef()\r\n\t\t\tlet expression = `min(max(${-this.buttonsWidth}, x), 0)`\r\n\t\t\t// 尝试关闭其他打开的单元格\r\n\t\t\tthis.parent && this.parent.closeOther(this)\r\n\t\t\t// 阿里为了KPI而开源的BindingX\r\n\t\t\tthis.panEvent = bindingX.bind({\r\n\t\t\t\tanchor: content,\r\n\t\t\t\teventType: 'pan',\r\n\t\t\t\tprops: [{\r\n\t\t\t\t\telement: content,\r\n\t\t\t\t\t// 绑定width属性，设置其宽度值\r\n\t\t\t\t\tproperty: 'transform.translateX',\r\n\t\t\t\t\texpression\r\n\t\t\t\t}]\r\n\t\t\t}, (res) => {\r\n\t\t\t\tthis.moving = false\r\n\t\t\t\tif (res.state === 'end' || res.state === 'exit') {\r\n\t\t\t\t\tconst deltaX = res.deltaX\r\n\t\t\t\t\tif(deltaX <= -this.buttonsWidth || deltaX >= 0) {\r\n\t\t\t\t\t\t// 如果触摸滑动的过程中，大于单元格的总宽度，或者大于0，意味着已经动过滑动达到了打开或者关闭的状态\r\n\t\t\t\t\t\t// 这里直接进行状态的标记\r\n\t\t\t\t\t\tthis.$nextTick(() => {\r\n\t\t\t\t\t\t\tthis.status = deltaX <= -this.buttonsWidth ? 'open' : 'close'\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t} else if(Math.abs(deltaX) > getPx(this.threshold)) {\r\n\t\t\t\t\t\t// 在移动大于阈值、并且小于总按钮宽度时，进行自动打开或者关闭\r\n\t\t\t\t\t\t// 移动距离大于0时，意味着需要关闭状态\r\n\t\t\t\t\t\tif(Math.abs(deltaX) < this.buttonsWidth) {\r\n\t\t\t\t\t\t\tthis.moveCellByAnimation(deltaX > 0 ? 'close' : 'open')\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\t// 在小于阈值时，进行关闭操作(如果在打开状态下，将不会执行bindingX)\r\n\t\t\t\t\t\tthis.moveCellByAnimation('close')\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t})\r\n\t\t},\r\n\t\t// 释放bindingX\r\n\t\tunbindBindingX() {\r\n\t\t\t// 释放上一次的资源\r\n\t\t\tif (this?.panEvent?.token != 0) {\r\n\t\t\t\tbindingX.unbind({\r\n\t\t\t\t\ttoken: this.panEvent?.token,\r\n\t\t\t\t\t// pan为手势事件\r\n\t\t\t\t\teventType: 'pan'\r\n\t\t\t\t})\r\n\t\t\t}\r\n\t\t},\r\n\t\t// 查询按钮节点信息\r\n\t\tqueryRect() {\r\n\t\t\t// 历遍所有按钮数组，通过getRectByDom返回一个promise\r\n\t\t\tconst promiseAll = this.options.map(async(item, index) => {\r\n\t\t\t\treturn await this.getRectByDom(this.$refs[`uv-swipe-action-item__right__button-${index}`][0])\r\n\t\t\t})\r\n\t\t\t// 通过promise.all方法，让所有按钮的查询结果返回一个数组的形式\r\n\t\t\tPromise.all(promiseAll).then(sizes => {\r\n\t\t\t\tthis.buttons = sizes\r\n\t\t\t\t// 计算所有按钮总宽度\r\n\t\t\t\tthis.buttonsWidth = sizes.reduce((sum, cur) => sum + cur.width, 0)\r\n\t\t\t})\r\n\t\t},\r\n\t\t// 通过nvue的dom模块，查询节点信息\r\n\t\tgetRectByDom(ref) {\r\n\t\t\treturn new Promise(resolve => {\r\n\t\t\t\tdom.getComponentRect(ref, res => {\r\n\t\t\t\t\tresolve(res.size)\r\n\t\t\t\t})\r\n\t\t\t}) \r\n\t\t},\r\n\t\t// 移动单元格到左边或者右边尽头\r\n\t\tmoveCellByAnimation(status = 'open') {\r\n\t\t\tif(this.moving) return\r\n\t\t\t// 标识当前状态\r\n\t\t\tthis.moveing = true\r\n\t\t\tconst content = this.getContentRef()\r\n\t\t\tconst x = status === 'open' ? -this.buttonsWidth : 0 \r\n\t\t\tanimation.transition(content, {\r\n\t\t\t\tstyles: {\r\n\t\t\t\t\ttransform: `translateX(${x}px)`,\r\n\t\t\t\t},\r\n\t\t\t\tduration: getDuration(this.duration, false),\r\n\t\t\t\ttimingFunction: 'ease-in-out'\r\n\t\t\t}, () => {\r\n\t\t\t\tthis.moving = false\r\n\t\t\t\tthis.status = status\r\n\t\t\t\tthis.unbindBindingX()\r\n\t\t\t})\r\n\t\t},\r\n\t\t// 获取元素ref\r\n\t\tgetContentRef() {\r\n\t\t\treturn this.$refs['uv-swipe-action-item__content'].ref\r\n\t\t}\r\n\t},\r\n\t// #ifdef VUE2\r\n\tbeforeDestroy() {\r\n\t\tthis.unbindBindingX()\r\n\t},\r\n\t// #endif\r\n\t// #ifdef VUE3\r\n\tunmounted() {\r\n\t\tthis.unbindBindingX()\r\n\t}\r\n\t// #endif\r\n}\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-swipe-action/components/uv-swipe-action-item/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 控制打开或者关闭\r\n\t\tshow: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 标识符，如果是v-for，可用index索引值\r\n\t\tname: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 是否禁用\r\n\t\tdisabled: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否自动关闭其他swipe按钮组\r\n\t\tautoClose: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 滑动距离阈值，只有大于此值，才被认为是要打开菜单\r\n\t\tthreshold: {\r\n\t\t\ttype: Number,\r\n\t\t\tdefault: 20\r\n\t\t},\r\n\t\t// 右侧按钮内容\r\n\t\toptions: {\r\n\t\t\ttype: Array,\r\n\t\t\tdefault: () => []\r\n\t\t},\r\n\t\t// 动画过渡时间，单位ms\r\n\t\tduration: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 300\r\n\t\t},\r\n\t\t...uni.$uv?.props?.swipeActionItem\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-swipe-action/components/uv-swipe-action-item/uv-swipe-action-item.vue",
    "content": "<template>\r\n\t<view class=\"uv-swipe-action-item\" ref=\"uv-swipe-action-item\">\r\n\t\t<view class=\"uv-swipe-action-item__right\">\r\n\t\t\t<slot name=\"button\">\r\n\t\t\t\t<view v-for=\"(item,index) in options\" :key=\"index\" class=\"uv-swipe-action-item__right__button\"\r\n\t\t\t\t\t:ref=\"`uv-swipe-action-item__right__button-${index}`\" :style=\"[{\r\n\t\t\t\t\t\talignItems: item.style && item.style.borderRadius ? 'center' : 'stretch'\r\n\t\t\t\t\t}]\" @tap=\"buttonClickHandler(item, index)\">\r\n\t\t\t\t\t<view class=\"uv-swipe-action-item__right__button__wrapper\" :style=\"[{\r\n\t\t\t\t\t\t\tbackgroundColor: item.style && item.style.backgroundColor ? item.style.backgroundColor : '#C7C6CD',\r\n\t\t\t\t\t\t\tborderRadius: item.style && item.style.borderRadius ? item.style.borderRadius : '0',\r\n\t\t\t\t\t\t\tpadding: item.style && item.style.borderRadius ? '0' : '0 15px',\r\n\t\t\t\t\t\t}, item.style]\">\r\n\t\t\t\t\t\t<uv-icon v-if=\"item.icon\" :name=\"item.icon\"\r\n\t\t\t\t\t\t\t:color=\"item.style && item.style.color ? item.style.color : '#ffffff'\"\r\n\t\t\t\t\t\t\t:size=\"item.iconSize ? $uv.addUnit(item.iconSize) : item.style && item.style.fontSize ? $uv.getPx(item.style.fontSize) * 1.2 : 17\"\r\n\t\t\t\t\t\t\t:customStyle=\"{\r\n\t\t\t\t\t\t\t\tmarginRight: item.text ? '2px' : 0\r\n\t\t\t\t\t\t\t}\"></uv-icon>\r\n\t\t\t\t\t\t<text v-if=\"item.text\" class=\"uv-swipe-action-item__right__button__wrapper__text uv-line-1\"\r\n\t\t\t\t\t\t\t:style=\"[{\r\n\t\t\t\t\t\t\t\tcolor: item.style && item.style.color ? item.style.color : '#ffffff',\r\n\t\t\t\t\t\t\t\tfontSize: item.style && item.style.fontSize ? item.style.fontSize : '16px',\r\n\t\t\t\t\t\t\t\tlineHeight: item.style && item.style.fontSize ? item.style.fontSize : '16px',\r\n\t\t\t\t\t\t\t}]\">{{ item.text }}</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</slot>\r\n\t\t</view>\r\n\t\t<!-- #ifndef APP-NVUE -->\r\n\t\t<view class=\"uv-swipe-action-item__content\" @touchstart=\"wxs.touchstart\" @touchmove=\"wxs.touchmove\"\r\n\t\t\t@touchend=\"wxs.touchend\" :status=\"status\" :change:status=\"wxs.statusChange\" :size=\"size\"\r\n\t\t\t:change:size=\"wxs.sizeChange\">\r\n\t\t\t<!-- #endif -->\r\n\t\t\t<!-- #ifdef APP-NVUE -->\r\n\t\t\t<view class=\"uv-swipe-action-item__content\" ref=\"uv-swipe-action-item__content\" @panstart=\"onTouchstart\"\r\n\t\t\t\t@tap=\"clickHandler\">\r\n\t\t\t\t<!-- #endif -->\r\n\t\t\t\t<slot />\r\n\t\t\t</view>\r\n\t\t</view>\r\n</template>\r\n<!-- #ifdef APP-VUE || MP-WEIXIN || H5 || MP-QQ -->\r\n<script src=\"./index.wxs\" module=\"wxs\" lang=\"wxs\"></script>\r\n<!-- #endif -->\r\n<script>\r\n\timport touch from '@/uni_modules/uv-ui-tools/libs/mixin/touch.js'\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t// #ifdef APP-NVUE\r\n\timport nvue from './nvue.js';\r\n\t// #endif\r\n\t// #ifdef APP-VUE || MP-WEIXIN || H5 || MP-QQ\r\n\timport wxs from './wxs.js';\r\n\t// #endif\r\n\t/**\r\n\t * SwipeActionItem 滑动单元格子组件\r\n\t * @description 该组件一般用于左滑唤出操作菜单的场景，用的最多的是左滑删除操作\r\n\t * @tutorial https://www.uvui.cn/components/swipeAction.html\r\n\t * @property {Boolean}\t\t\tshow\t\t\t控制打开或者关闭（默认 false ）\r\n\t * @property {String | Number}\tindex\t\t\t标识符，如果是v-for，可用index索引\r\n\t * @property {Boolean}\t\t\tdisabled\t\t是否禁用（默认 false ）\r\n\t * @property {Boolean}\t\t\tautoClose\t\t是否自动关闭其他swipe按钮组（默认 true ）\r\n\t * @property {Number}\t\t\tthreshold\t\t滑动距离阈值，只有大于此值，才被认为是要打开菜单（默认 30 ）\r\n\t * @property {Array}\t\t\toptions\t\t\t右侧按钮内容\r\n\t * @property {String | Number}\tduration\t\t动画过渡时间，单位ms（默认 350 ）\r\n\t * @event {Function(index)}\topen\t组件打开时触发\r\n\t * @event {Function(index)}\tclose\t组件关闭时触发\r\n\t * @example\t<uv-swipe-action><uv-swipe-action-item :options=\"options1\" ></uv-swipe-action-item></uv-swipe-action>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-swipe-action-item',\r\n\t\temits: ['click'],\r\n\t\t// #ifndef APP-NVUE\r\n\t\tmixins: [mpMixin, mixin, props, touch],\r\n\t\t// #endif\r\n\t\t// #ifdef APP-NVUE\r\n\t\tmixins: [mpMixin, mixin, props, nvue , touch],\r\n\t\t// #endif\r\n\t\t// #ifdef APP-VUE || MP-WEIXIN || H5 || MP-QQ\r\n\t\tmixins: [mpMixin, mixin, props, touch, wxs],\r\n\t\t// #endif\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\t// 按钮的尺寸信息\r\n\t\t\t\tsize: {},\r\n\t\t\t\t// 父组件uv-swipe-action的参数\r\n\t\t\t\tparentData: {\r\n\t\t\t\t\tautoClose: true,\r\n\t\t\t\t},\r\n\t\t\t\t// 当前状态，open-打开，close-关闭\r\n\t\t\t\tstatus: 'close',\r\n\t\t\t}\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\t// 由于wxs无法直接读取外部的值，需要在外部值变化时，重新执行赋值逻辑\r\n\t\t\twxsInit(newValue, oldValue) {\r\n\t\t\t\tthis.queryRect()\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\twxsInit() {\r\n\t\t\t\treturn [this.disabled, this.autoClose, this.threshold, this.options, this.duration]\r\n\t\t\t}\r\n\t\t},\r\n\t\tmounted() {\r\n\t\t\t// #ifdef MP-TOUTIAO\r\n\t\t\tthis.$uv.error('抖音小程序暂不支持wxs，故该组件暂不支持抖音小程序');\r\n\t\t\t// #endif\r\n\t\t\tthis.init()\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tinit() {\r\n\t\t\t\t// 初始化父组件数据\r\n\t\t\t\tthis.updateParentData()\r\n\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\tthis.$uv.sleep().then(() => {\r\n\t\t\t\t\tthis.queryRect()\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\t\t\t},\r\n\t\t\tupdateParentData() {\r\n\t\t\t\t// 此方法在mixin中\r\n\t\t\t\tthis.getParentData('uv-swipe-action')\r\n\t\t\t},\r\n\t\t\t// #ifndef APP-NVUE\r\n\t\t\t// 查询节点\r\n\t\t\tqueryRect() {\r\n\t\t\t\tthis.$uvGetRect('.uv-swipe-action-item__right__button', true).then(buttons => {\r\n\t\t\t\t\tthis.size = {\r\n\t\t\t\t\t\tbuttons,\r\n\t\t\t\t\t\tshow: this.show,\r\n\t\t\t\t\t\tdisabled: this.disabled,\r\n\t\t\t\t\t\tthreshold: this.threshold,\r\n\t\t\t\t\t\tduration: this.duration\r\n\t\t\t\t\t}\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\t// #endif\r\n\t\t\t// 按钮被点击\r\n\t\t\tbuttonClickHandler(item, index) {\r\n\t\t\t\tthis.$emit('click', {\r\n\t\t\t\t\tindex,\r\n\t\t\t\t\tname: this.name\r\n\t\t\t\t})\r\n\t\t\t}\r\n\t\t},\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t$show-lines: 1;\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\r\n\t.uv-swipe-action-item {\r\n\t\tposition: relative;\r\n\t\toverflow: hidden;\r\n\t\t/* #ifndef APP-NVUE || MP-WEIXIN */\r\n\t\ttouch-action: pan-y;\r\n\t\t/* #endif */\r\n\r\n\t\t&__content {\r\n\t\t\tbackground-color: #FFFFFF;\r\n\t\t\tz-index: 10;\r\n\t\t}\r\n\r\n\t\t&__right {\r\n\t\t\tposition: absolute;\r\n\t\t\ttop: 0;\r\n\t\t\tbottom: 0;\r\n\t\t\tright: 0;\r\n\t\t\t@include flex;\r\n\r\n\t\t\t&__button {\r\n\t\t\t\t@include flex;\r\n\t\t\t\tjustify-content: center;\r\n\t\t\t\toverflow: hidden;\r\n\t\t\t\talign-items: center;\r\n\r\n\t\t\t\t&__wrapper {\r\n\t\t\t\t\t@include flex;\r\n\t\t\t\t\talign-items: center;\r\n\t\t\t\t\tjustify-content: center;\r\n\t\t\t\t\tpadding: 0 15px;\r\n\r\n\t\t\t\t\t&__text {\r\n\t\t\t\t\t\t@include flex;\r\n\t\t\t\t\t\talign-items: center;\r\n\t\t\t\t\t\tcolor: #FFFFFF;\r\n\t\t\t\t\t\tfont-size: 15px;\r\n\t\t\t\t\t\ttext-align: center;\r\n\t\t\t\t\t\tjustify-content: center;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-swipe-action/components/uv-swipe-action-item/wxs.js",
    "content": "export default {\r\n    methods: {\r\n        // 关闭时执行\r\n        closeHandler() {\r\n            this.status = 'close'\r\n        },\r\n        setState(status) {\r\n            this.status = status\r\n        },\r\n        closeOther() {\r\n            // 尝试关闭其他打开的单元格\r\n            this.parent && this.parent.closeOther(this)\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-swipe-action/package.json",
    "content": "{\r\n  \"id\": \"uv-swipe-action\",\r\n  \"displayName\": \"uv-swipe-action 滑动单元格  全面兼容小程序、nvue、vue2、vue3等多端\",\r\n  \"version\": \"1.0.4\",\r\n  \"description\": \"滑动单元格组件一般用于左滑唤出操作菜单的场景，用的最多的是左滑删除操作。\",\r\n  \"keywords\": [\r\n    \"uv-swipe-action\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"swipe-action\",\r\n    \"滑动单元格\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-icon\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-swipe-action/readme.md",
    "content": "## SwipeAction 滑动单元格 \r\n\r\n> **组件名：uv-swipe-action**\r\n\r\n该组件一般用于左滑唤出操作菜单的场景，用的最多的是左滑删除操作。\r\n\r\n### <a href=\"https://www.uvui.cn/components/swipeAction.html\" target=\"_blank\">查看文档</a>\r\n\r\n### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n#### 如使用过程中有任何问题，或者您对uv-ui有一些好的建议，欢迎加入 uv-ui 交流群：<a href=\"https://ext.dcloud.net.cn/plugin?id=12287\" target=\"_blank\">uv-ui</a>、<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-swiper/changelog.md",
    "content": "## 1.0.5（2023-08-24）\n1. 修复标题文字过多未隐藏掉的BUG\n## 1.0.4（2023-07-24）\r\n1. 增加 滑动方向是否为纵向  属性vertical\r\n## 1.0.3（2023-06-27）\r\n1. 增加titleStyle属性，方便修改标题样式\r\n2. 标题上去掉是否是图片的判断，避免无后缀的图片不显示\r\n## 1.0.2（2023-06-01）\r\n1. 修复点击触发两次事件的BUG \r\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-swiper 轮播图，走马灯\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-swiper/components/uv-swiper/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 列表数组，元素可为字符串，如为对象可通过keyName指定目标属性名\r\n\t\tlist: {\r\n\t\t\ttype: Array,\r\n\t\t\tdefault: () => []\r\n\t\t},\r\n\t\t// 是否显示面板指示器\r\n\t\tindicator: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 指示器非激活颜色\r\n\t\tindicatorActiveColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#fff'\r\n\t\t},\r\n\t\t// 指示器的激活颜色\r\n\t\tindicatorInactiveColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'rgba(255, 255, 255, 0.35)'\r\n\t\t},\r\n\t\t// 指示器样式，可通过bottom，left，right进行定位\r\n\t\tindicatorStyle: {\r\n\t\t\ttype: [String, Object],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 指示器模式，line-线型，dot-点型\r\n\t\tindicatorMode: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'line'\r\n\t\t},\r\n\t\t// 是否自动切换\r\n\t\tautoplay: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 当前所在滑块的 index\r\n\t\tcurrent: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 当前所在滑块的 item-id ，不能与 current 被同时指定\r\n\t\tcurrentItemId: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 滑块自动切换时间间隔\r\n\t\tinterval: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 3000\r\n\t\t},\r\n\t\t// 滑块切换过程所需时间\r\n\t\tduration: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 300\r\n\t\t},\r\n\t\t// 播放到末尾后是否重新回到开头\r\n\t\tcircular: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 滑动方向是否为纵向\r\n\t\tvertical: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 前边距，可用于露出前一项的一小部分，nvue和支付宝不支持\r\n\t\tpreviousMargin: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 后边距，可用于露出后一项的一小部分，nvue和支付宝不支持\r\n\t\tnextMargin: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 当开启时，会根据滑动速度，连续滑动多屏，支付宝不支持\r\n\t\tacceleration: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 同时显示的滑块数量，nvue、支付宝小程序不支持\r\n\t\tdisplayMultipleItems: {\r\n\t\t\ttype: Number,\r\n\t\t\tdefault: 1\r\n\t\t},\r\n\t\t// 指定swiper切换缓动动画类型，有效值：default、linear、easeInCubic、easeOutCubic、easeInOutCubic\r\n\t\t// 只对微信小程序有效\r\n\t\teasingFunction: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'default'\r\n\t\t},\r\n\t\t// list数组中指定对象的目标属性名\r\n\t\tkeyName: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'url'\r\n\t\t},\r\n\t\t// 图片的裁剪模式\r\n\t\timgMode: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'aspectFill'\r\n\t\t},\r\n\t\t// 组件高度\r\n\t\theight: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 130\r\n\t\t},\r\n\t\t// 背景颜色\r\n\t\tbgColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#f3f4f6'\r\n\t\t},\r\n\t\t// 组件圆角，数值或带单位的字符串\r\n\t\tradius: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 4\r\n\t\t},\r\n\t\t// 是否加载中\r\n\t\tloading: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否显示标题，要求数组对象中有title属性\r\n\t\tshowTitle: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 显示的标题样式\r\n\t\ttitleStyle: {\r\n\t\t\ttype: Object,\r\n\t\t\tdefault: ()=>{}\r\n\t\t},\r\n\t\t...uni.$uv?.props?.swiper\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-swiper/components/uv-swiper/uv-swiper.vue",
    "content": "<template>\r\n\t<view\r\n\t\tclass=\"uv-swiper\"\r\n\t\t:style=\"{\r\n\t\t\tbackgroundColor: bgColor,\r\n\t\t\theight: $uv.addUnit(height),\r\n\t\t\tborderRadius: $uv.addUnit(radius)\r\n\t\t}\"\r\n\t>\r\n\t\t<view\r\n\t\t\tclass=\"uv-swiper__loading\"\r\n\t\t\tv-if=\"loading\"\r\n\t\t>\r\n\t\t\t<uv-loading-icon mode=\"circle\"></uv-loading-icon>\r\n\t\t</view>\r\n\t\t<swiper\r\n\t\t\tv-else\r\n\t\t\tclass=\"uv-swiper__wrapper\"\r\n\t\t\t:style=\"{\r\n\t\t\t\theight: $uv.addUnit(height),\r\n\t\t\t\tflex: 1\r\n\t\t\t}\"\r\n\t\t\t@change=\"change\"\r\n\t\t\t:circular=\"circular\"\r\n\t\t\t:vertical=\"vertical\"\r\n\t\t\t:interval=\"interval\"\r\n\t\t\t:duration=\"duration\"\r\n\t\t\t:autoplay=\"autoplay\"\r\n\t\t\t:current=\"current\"\r\n\t\t\t:currentItemId=\"currentItemId\"\r\n\t\t\t:previousMargin=\"$uv.addUnit(previousMargin)\"\r\n\t\t\t:nextMargin=\"$uv.addUnit(nextMargin)\"\r\n\t\t\t:acceleration=\"acceleration\"\r\n\t\t\t:displayMultipleItems=\"displayMultipleItems\"\r\n\t\t\t:easingFunction=\"easingFunction\"\r\n\t\t>\r\n\t\t\t<swiper-item\r\n\t\t\t\tclass=\"uv-swiper__wrapper__item\"\r\n\t\t\t\tv-for=\"(item, index) in list\"\r\n\t\t\t\t:key=\"index\"\r\n\t\t\t>\r\n\t\t\t\t<view\r\n\t\t\t\t\tclass=\"uv-swiper__wrapper__item__wrapper\"\r\n\t\t\t\t\t:style=\"[itemStyle(index)]\"\r\n\t\t\t\t>\r\n\t\t\t\t\t<!-- 在nvue中，image图片的宽度默认为屏幕宽度，需要通过flex:1撑开，另外必须设置高度才能显示图片 -->\r\n\t\t\t\t\t<image\r\n\t\t\t\t\t\tclass=\"uv-swiper__wrapper__item__wrapper__image\"\r\n\t\t\t\t\t\tv-if=\"getItemType(item) === 'image'\"\r\n\t\t\t\t\t\t:src=\"getSource(item)\"\r\n\t\t\t\t\t\t:mode=\"imgMode\"\r\n\t\t\t\t\t\t@tap=\"clickHandler(index)\"\r\n\t\t\t\t\t\t:style=\"{\r\n\t\t\t\t\t\t\theight: $uv.addUnit(height),\r\n\t\t\t\t\t\t\tborderRadius: $uv.addUnit(radius)\r\n\t\t\t\t\t\t}\"\r\n\t\t\t\t\t></image>\r\n\t\t\t\t\t<video\r\n\t\t\t\t\t\tclass=\"uv-swiper__wrapper__item__wrapper__video\"\r\n\t\t\t\t\t\tv-if=\"getItemType(item) === 'video'\"\r\n\t\t\t\t\t\t:id=\"`video-${index}`\"\r\n\t\t\t\t\t\t:enable-progress-gesture=\"false\"\r\n\t\t\t\t\t\t:src=\"getSource(item)\"\r\n\t\t\t\t\t\t:poster=\"getPoster(item)\"\r\n\t\t\t\t\t\t:title=\"showTitle && $uv.test.object(item) && item.title ? item.title : ''\"\r\n\t\t\t\t\t\t:style=\"{\r\n\t\t\t\t\t\t\theight: $uv.addUnit(height)\r\n\t\t\t\t\t\t}\"\r\n\t\t\t\t\t\tcontrols\r\n\t\t\t\t\t\t@tap=\"clickHandler(index)\"\r\n\t\t\t\t\t></video>\r\n\t\t\t\t\t<text\r\n\t\t\t\t\t\tv-if=\"showTitle && $uv.test.object(item) && item.title\"\r\n\t\t\t\t\t\tclass=\"uv-swiper__wrapper__item__wrapper__title uv-line-1\"\r\n\t\t\t\t\t\t:style=\"[$uv.addStyle(titleStyle)]\"\r\n\t\t\t\t\t>{{ item.title }}</text>\r\n\t\t\t\t</view>\r\n\t\t\t</swiper-item>\r\n\t\t</swiper>\r\n\t\t<view class=\"uv-swiper__indicator\" :style=\"[$uv.addStyle(indicatorStyle)]\">\r\n\t\t\t<slot name=\"indicator\">\r\n\t\t\t\t<uv-swiper-indicator\r\n\t\t\t\t\tv-if=\"!loading && indicator && !showTitle\"\r\n\t\t\t\t\t:indicatorActiveColor=\"indicatorActiveColor\"\r\n\t\t\t\t\t:indicatorInactiveColor=\"indicatorInactiveColor\"\r\n\t\t\t\t\t:length=\"list.length\"\r\n\t\t\t\t\t:current=\"currentIndex\"\r\n\t\t\t\t\t:indicatorMode=\"indicatorMode\"\r\n\t\t\t\t></uv-swiper-indicator>\r\n\t\t\t</slot>\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * Swiper 轮播图\r\n\t * @description 该组件一般用于导航轮播，广告展示等场景,可开箱即用，\r\n\t * @tutorial https://www.uvui.cn/components/swiper.html\r\n\t * @property {Array}\t\t\tlist\t\t\t\t\t轮播图数据\r\n\t * @property {Boolean}\t\t\tindicator\t\t\t\t是否显示面板指示器（默认 false ）\r\n\t * @property {String}\t\t\tindicatorActiveColor\t指示器非激活颜色（默认 '#FFFFFF' ）\r\n\t * @property {String}\t\t\tindicatorInactiveColor\t指示器的激活颜色（默认 'rgba(255, 255, 255, 0.35)' ）\r\n\t * @property {String | Object}\tindicatorStyle\t\t\t指示器样式，可通过bottom，left，right进行定位\r\n\t * @property {String}\t\t\tindicatorMode\t\t\t指示器模式（默认 'line' ）\r\n\t * @property {Boolean}\t\t\tautoplay\t\t\t\t是否自动切换（默认 true ）\r\n\t * @property {String | Number}\tcurrent\t\t\t\t\t当前所在滑块的 index（默认 0 ）\r\n\t * @property {String}\t\t\tcurrentItemId\t\t\t当前所在滑块的 item-id ，不能与 current 被同时指定\r\n\t * @property {String | Number}\tinterval\t\t\t\t滑块自动切换时间间隔（ms）（默认 3000 ）\r\n\t * @property {String | Number}\tduration\t\t\t\t滑块切换过程所需时间（ms）（默认 300 ）\r\n\t * @property {Boolean}\t\t\tcircular\t\t\t\t播放到末尾后是否重新回到开头（默认 false ）\r\n\t * @property {String | Number}\tpreviousMargin\t\t\t前边距，可用于露出前一项的一小部分，nvue和支付宝不支持（默认 0 ）\r\n\t * @property {String | Number}\tnextMargin\t\t\t\t后边距，可用于露出后一项的一小部分，nvue和支付宝不支持（默认 0 ）\r\n\t * @property {Boolean}\t\t\tacceleration\t\t\t当开启时，会根据滑动速度，连续滑动多屏，支付宝不支持（默认 false ）\r\n\t * @property {Number}\t\t\tdisplayMultipleItems\t同时显示的滑块数量，nvue、支付宝小程序不支持（默认 1 ）\r\n\t * @property {String}\t\t\teasingFunction\t\t\t指定swiper切换缓动动画类型， 只对微信小程序有效（默认 'default' ）\r\n\t * @property {String}\t\t\tkeyName\t\t\t\t\tlist数组中指定对象的目标属性名（默认 'url' ）\r\n\t * @property {String}\t\t\timgMode\t\t\t\t\t图片的裁剪模式（默认 'aspectFill' ）\r\n\t * @property {String | Number}\theight\t\t\t\t\t组件高度（默认 130 ）\r\n\t * @property {String}\t\t\tbgColor\t\t\t\t\t背景颜色（默认 \t'#f3f4f6' ）\r\n\t * @property {String | Number}\tradius\t\t\t\t\t组件圆角，数值或带单位的字符串（默认 4 ）\r\n\t * @property {Boolean}\t\t\tloading\t\t\t\t\t是否加载中（默认 false ）\r\n\t * @property {Boolean}\t\t\tshowTitle\t\t\t\t是否显示标题，要求数组对象中有title属性（默认 false ）\r\n\t * @event {Function(index)}\tclick\t点击轮播图时触发\tindex：点击了第几张图片，从0开始\r\n\t * @event {Function(index)}\tchange\t轮播图切换时触发(自动或者手动切换)\tindex：切换到了第几张图片，从0开始\r\n\t * @example\t<uv-swiper :list=\"list4\" keyName=\"url\" :autoplay=\"false\"></uv-swiper>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-swiper',\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\temits: ['click','change'],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tcurrentIndex: 0\r\n\t\t\t}\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\tcurrent(val, preVal) {\r\n\t\t\t\tif(val === preVal) return;\r\n\t\t\t\tthis.currentIndex = val; // 和上游数据关联上\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\titemStyle() {\r\n\t\t\t\treturn index => {\r\n\t\t\t\t\tconst style = {}\r\n\t\t\t\t\t// #ifndef APP-NVUE || MP-TOUTIAO\r\n\t\t\t\t\t// 左右流出空间的写法不支持nvue和头条\r\n\t\t\t\t\t// 只有配置了此二值，才加上对应的圆角，以及缩放\r\n\t\t\t\t\tif (this.nextMargin && this.previousMargin) {\r\n\t\t\t\t\t\tstyle.borderRadius = this.$uv.addUnit(this.radius)\r\n\t\t\t\t\t\tif (index !== this.currentIndex) style.transform = 'scale(0.92)'\r\n\t\t\t\t\t}\r\n\t\t\t\t\t// #endif\r\n\t\t\t\t\treturn style\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n      getItemType(item) {\r\n        if (typeof item === 'string') return this.$uv.test.video(this.getSource(item)) ? 'video' : 'image'\r\n        if (typeof item === 'object' && this.keyName) {\r\n          if (!item.type) return this.$uv.test.video(this.getSource(item)) ? 'video' : 'image'\r\n          if (item.type === 'image') return 'image'\r\n          if (item.type === 'video') return 'video'\r\n          return 'image'\r\n        }\r\n      },\r\n\t\t\t// 获取目标路径，可能数组中为字符串，对象的形式，额外可指定对象的目标属性名keyName\r\n\t\t\tgetSource(item) {\r\n\t\t\t\tif (typeof item === 'string') return item\r\n\t\t\t\tif (typeof item === 'object' && this.keyName) return item[this.keyName]\r\n\t\t\t\telse this.$uv.error('请按格式传递列表参数')\r\n\t\t\t\treturn ''\r\n\t\t\t},\r\n\t\t\t// 轮播切换事件\r\n\t\t\tchange(e) {\r\n\t\t\t\t// 当前的激活索引\r\n\t\t\t\tconst {\r\n\t\t\t\t\tcurrent\r\n\t\t\t\t} = e.detail\r\n\t\t\t\tthis.pauseVideo(this.currentIndex)\r\n\t\t\t\tthis.currentIndex = current\r\n\t\t\t\tthis.$emit('change', e.detail)\r\n\t\t\t},\r\n\t\t\t// 切换轮播时，暂停视频播放\r\n\t\t\tpauseVideo(index) {\r\n\t\t\t\tconst lastItem = this.getSource(this.list[index])\r\n\t\t\t\tif (this.$uv.test.video(lastItem)) {\r\n\t\t\t\t\t// 当视频隐藏时，暂停播放\r\n\t\t\t\t\tconst video = uni.createVideoContext(`video-${index}`, this)\r\n\t\t\t\t\tvideo.pause()\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t// 当一个轮播item为视频时，获取它的视频海报\r\n\t\t\tgetPoster(item) {\r\n\t\t\t\treturn typeof item === 'object' && item.poster ? item.poster : ''\r\n\t\t\t},\r\n\t\t\t// 点击某个item\r\n\t\t\tclickHandler(index) {\r\n\t\t\t\tthis.$emit('click', index)\r\n\t\t\t}\r\n\t\t},\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t$show-lines: 1;\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t.uv-swiper {\r\n\t\t@include flex;\r\n\t\tjustify-content: center;\r\n\t\talign-items: center;\r\n\t\tposition: relative;\r\n\t\toverflow: hidden;\r\n\r\n\t\t&__wrapper {\r\n\t\t\tflex: 1;\r\n\r\n\t\t\t&__item {\r\n\t\t\t\tflex: 1;\r\n\r\n\t\t\t\t&__wrapper {\r\n\t\t\t\t\t@include flex;\r\n\t\t\t\t\tposition: relative;\r\n\t\t\t\t\toverflow: hidden;\r\n\t\t\t\t\ttransition: transform 0.3s;\r\n\t\t\t\t\tflex: 1;\r\n\r\n\t\t\t\t\t&__image {\r\n\t\t\t\t\t\tflex: 1;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t&__video {\r\n\t\t\t\t\t\tflex: 1;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t&__title {\r\n\t\t\t\t\t\tposition: absolute;\r\n\t\t\t\t\t\tbackground-color: rgba(0, 0, 0, 0.3);\r\n\t\t\t\t\t\tbottom: 0;\r\n\t\t\t\t\t\tleft: 0;\r\n\t\t\t\t\t\tright: 0;\r\n\t\t\t\t\t\tfont-size: 28rpx;\r\n\t\t\t\t\t\theight: 60rpx;\r\n\t\t\t\t\t\tline-height: 60rpx;\r\n\t\t\t\t\t\tcolor: #FFFFFF;\r\n\t\t\t\t\t\tflex: 1;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t&__indicator {\r\n\t\t\tposition: absolute;\r\n\t\t\tbottom: 10px;\r\n\t\t}\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-swiper/components/uv-swiper-indicator/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 轮播的长度\r\n\t\tlength: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 当前处于活动状态的轮播的索引\r\n\t\tcurrent: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 指示器非激活颜色\r\n\t\tindicatorActiveColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 指示器的激活颜色\r\n\t\tindicatorInactiveColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 指示器模式，line-线型，dot-点型\r\n\t\tindicatorMode: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t...uni.$uv?.props?.swiperIndicator\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-swiper/components/uv-swiper-indicator/uv-swiper-indicator.vue",
    "content": "<template>\r\n\t<view class=\"uv-swiper-indicator\">\r\n\t\t<view\r\n\t\t\tclass=\"uv-swiper-indicator__wrapper\"\r\n\t\t\tv-if=\"indicatorMode === 'line'\"\r\n\t\t\t:class=\"[`uv-swiper-indicator__wrapper--${indicatorMode}`]\"\r\n\t\t\t:style=\"{\r\n\t\t\t\twidth: $uv.addUnit(lineWidth * length),\r\n\t\t\t\tbackgroundColor: indicatorInactiveColor\r\n\t\t\t}\"\r\n\t\t>\r\n\t\t\t<view\r\n\t\t\t\tclass=\"uv-swiper-indicator__wrapper--line__bar\"\r\n\t\t\t\t:style=\"[lineStyle]\"\r\n\t\t\t></view>\r\n\t\t</view>\r\n\t\t<view\r\n\t\t\tclass=\"uv-swiper-indicator__wrapper\"\r\n\t\t\tv-if=\"indicatorMode === 'dot'\"\r\n\t\t>\r\n\t\t\t<view\r\n\t\t\t\tclass=\"uv-swiper-indicator__wrapper__dot\"\r\n\t\t\t\tv-for=\"(item, index) in length\"\r\n\t\t\t\t:key=\"index\"\r\n\t\t\t\t:class=\"[index === current && 'uv-swiper-indicator__wrapper__dot--active']\"\r\n\t\t\t\t:style=\"[dotStyle(index)]\"\r\n\t\t\t>\r\n\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * SwiperIndicator 轮播图指示器\r\n\t * @description 该组件一般用于导航轮播，广告展示等场景,可开箱即用，\r\n\t * @tutorial https://www.uvui.cn/components/swiper.html\r\n\t * @property {String | Number}\tlength\t\t\t\t\t轮播的长度（默认 0 ）\r\n\t * @property {String | Number}\tcurrent\t\t\t\t\t当前处于活动状态的轮播的索引（默认 0 ）\r\n\t * @property {String}\t\t\tindicatorActiveColor\t指示器非激活颜色\r\n\t * @property {String}\t\t\tindicatorInactiveColor\t指示器的激活颜色\r\n\t * @property {String}\t\t\tindicatorMode\t\t\t指示器模式（默认 'line' ）\r\n\t * @example\t<uv-swiper :list=\"list4\" indicator keyName=\"url\" :autoplay=\"false\"></uv-swiper>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-swiper-indicator',\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tlineWidth: 22\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\t// 指示器为线型的样式\r\n\t\t\tlineStyle() {\r\n\t\t\t\tlet style = {}\r\n\t\t\t\tstyle.width = this.$uv.addUnit(this.lineWidth)\r\n\t\t\t\tstyle.transform = `translateX(${ this.$uv.addUnit(this.current * this.lineWidth) })`\r\n\t\t\t\tstyle.backgroundColor = this.indicatorActiveColor\r\n\t\t\t\treturn style\r\n\t\t\t},\r\n\t\t\t// 指示器为点型的样式\r\n\t\t\tdotStyle() {\r\n\t\t\t\treturn index => {\r\n\t\t\t\t\tlet style = {}\r\n\t\t\t\t\tstyle.backgroundColor = index === this.current ? this.indicatorActiveColor : this.indicatorInactiveColor\r\n\t\t\t\t\treturn style\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\r\n\t.uv-swiper-indicator {\r\n\r\n\t\t&__wrapper {\r\n\t\t\t@include flex;\r\n\r\n\t\t\t&--line {\r\n\t\t\t\tborder-radius: 100px;\r\n\t\t\t\theight: 4px;\r\n\r\n\t\t\t\t&__bar {\r\n\t\t\t\t\twidth: 22px;\r\n\t\t\t\t\theight: 4px;\r\n\t\t\t\t\tborder-radius: 100px;\r\n\t\t\t\t\tbackground-color: #FFFFFF;\r\n\t\t\t\t\ttransition: transform 0.3s;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t&__dot {\r\n\t\t\t\twidth: 5px;\r\n\t\t\t\theight: 5px;\r\n\t\t\t\tborder-radius: 100px;\r\n\t\t\t\tmargin: 0 4px;\r\n\r\n\t\t\t\t&--active {\r\n\t\t\t\t\twidth: 12px;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t}\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-swiper/package.json",
    "content": "{\r\n  \"id\": \"uv-swiper\",\r\n  \"displayName\": \"uv-swiper 轮播图 全面兼容vue3+2、app、h5、小程序等多端\",\r\n  \"version\": \"1.0.5\",\r\n  \"description\": \"该组件为轮播、跑马灯，支持卡片式。一般用于导航轮播，广告展示等场景，开箱即用\",\r\n  \"keywords\": [\r\n    \"swiper\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"轮播图\",\r\n    \"走马灯\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-loading-icon\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-swiper/readme.md",
    "content": "## Swiper 轮播图 \r\n\r\n> **组件名：uv-swiper**\r\n\r\n该组件为轮播、跑马灯，支持卡片式。一般用于导航轮播，广告展示等场景，开箱即用。\r\n\r\n# <a href=\"https://www.uvui.cn/components/swiper.html\" target=\"_blank\">查看文档</a>\r\n\r\n## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>（请不要 下载插件ZIP）</small>\r\n\r\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n<a href=\"https://ext.dcloud.net.cn/plugin?name=uv-ui\" target=\"_blank\">\r\n\r\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\r\n\r\n</a>\r\n\r\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-switch/changelog.md",
    "content": "## 1.0.5（2023-09-21）\n1. 优化细节\n## 1.0.4（2023-08-23）\n1. 取消value传值，只能使用v-model传值，避免异步操作不生效的BUG\n## 1.0.3（2023-07-13）\r\n1. 修复  uv-switch设置value属性不生效的BUG\r\n## 1.0.2（2023-06-19）\r\n1. size属性兼容和单位一起使用\r\n\r\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-switch 开关选择器\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-switch/components/uv-switch/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\tvalue: {\r\n\t\t\ttype: [Boolean, String, Number],\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\tmodelValue: {\r\n\t\t\ttype: [Boolean, String, Number],\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否为加载中状态\r\n\t\tloading: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否为禁用装填\r\n\t\tdisabled: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 开关尺寸，单位px\r\n\t\tsize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 25\r\n\t\t},\r\n\t\t// 打开时的背景颜色\r\n\t\tactiveColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#2979ff'\r\n\t\t},\r\n\t\t// 关闭时的背景颜色\r\n\t\tinactiveColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#fff'\r\n\t\t},\r\n\t\t// switch打开时的值\r\n\t\tactiveValue: {\r\n\t\t\ttype: [String, Number, Boolean],\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// switch关闭时的值\r\n\t\tinactiveValue: {\r\n\t\t\ttype: [String, Number, Boolean],\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否开启异步变更，开启后需要手动控制输入值\r\n\t\tasyncChange: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 圆点与外边框的距离\r\n\t\tspace: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t...uni.$uv?.props?.switch\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-switch/components/uv-switch/uv-switch.vue",
    "content": "<template>\r\n\t<view\r\n\t  class=\"uv-switch\"\r\n\t  :class=\"[disabled && 'uv-switch--disabled']\"\r\n\t  :style=\"[switchStyle, $uv.addStyle(customStyle)]\"\r\n\t  @tap=\"clickHandler\"\r\n\t>\r\n\t\t<view\r\n\t\t  class=\"uv-switch__bg\"\r\n\t\t  :style=\"[bgStyle]\"\r\n\t\t>\r\n\t\t</view>\r\n\t\t<view\r\n\t\t  class=\"uv-switch__node\"\r\n\t\t  :class=\"[innerValue && 'uv-switch__node--on']\"\r\n\t\t  :style=\"[nodeStyle]\"\r\n\t\t  ref=\"uv-switch__node\"\r\n\t\t>\r\n\t\t\t<uv-loading-icon\r\n\t\t\t  :show=\"loading\"\r\n\t\t\t  mode=\"circle\"\r\n\t\t\t  timingFunction='linear'\r\n\t\t\t  :color=\"innerValue ? activeColor : '#AAABAD'\"\r\n\t\t\t  :size=\"size * 0.6\"\r\n\t\t\t/>\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * switch 开关选择器\r\n\t * @description 选择开关一般用于只有两个选择，且只能选其一的场景。\r\n\t * @tutorial https://www.uvui.cn/components/switch.html\r\n\t * @property {Boolean}\t\t\t\t\t\tloading\t\t\t是否处于加载中（默认 false ）\r\n\t * @property {Boolean}\t\t\t\t\t\tdisabled\t\t是否禁用（默认 false ）\r\n\t * @property {String | Number}\t\t\t\tsize\t\t\t开关尺寸，单位px （默认 25 ）\r\n\t * @property {String}\t\t\t\t\t\tactiveColor\t\t打开时的背景色 （默认 '#2979ff' ）\r\n\t * @property {String} \t\t\t\t\t\tinactiveColor\t关闭时的背景色 （默认 '#ffffff' ）\r\n\t * @property {Boolean | String | Number}\tvalue\t\t\t通过v-model双向绑定的值 （默认 false ）\r\n\t * @property {Boolean | String | Number}\tactiveValue\t\t打开选择器时通过change事件发出的值 （默认 true ）\r\n\t * @property {Boolean | String | Number}\tinactiveValue\t关闭选择器时通过change事件发出的值 （默认 false ）\r\n\t * @property {Boolean}\t\t\t\t\t\tasyncChange\t\t是否开启异步变更，开启后需要手动控制输入值 （默认 false ）\r\n\t * @property {String | Number}\t\t\t\tspace\t\t\t圆点与外边框的距离 （默认 0 ）\r\n\t * @property {Object}\t\t\t\t\t\tcustomStyle\t\t定义需要用到的外部样式\r\n\t *\r\n\t * @event {Function} change 在switch打开或关闭时触发\r\n\t * @example <uv-switch v-model=\"checked\" active-color=\"red\" inactive-color=\"#eee\"></uv-switch>\r\n\t */\r\n\texport default {\r\n\t\tname: \"uv-switch\",\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tbgColor: '#ffffff',\r\n\t\t\t\tinnerValue: false\r\n\t\t\t}\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\t// #ifdef VUE2\r\n\t\t\tvalue: {\r\n\t\t\t\timmediate: true,\r\n\t\t\t\thandler(newVal) {\r\n\t\t\t\t\tif (newVal !== this.inactiveValue && newVal !== this.activeValue) {\r\n\t\t\t\t\t\treturn this.$uv.error('v-model绑定的值必须为inactiveValue、activeValue二者之一')\r\n\t\t\t\t\t}\r\n\t\t\t\t\tthis.innerValue = newVal;\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t// #endif\r\n\t\t\t// #ifndef VUE2\r\n\t\t\tmodelValue: {\r\n\t\t\t\timmediate: true,\r\n\t\t\t\thandler(newVal) {\r\n\t\t\t\t\tif (newVal !== this.inactiveValue && newVal !== this.activeValue) {\r\n\t\t\t\t\t\treturn this.$uv.error('v-model绑定的值必须为inactiveValue、activeValue二者之一')\r\n\t\t\t\t\t}\r\n\t\t\t\t\tthis.innerValue = newVal;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t// #endif\r\n\t\t},\r\n\t\tcreated() {\r\n\t\t\tthis.innerValue = this.value || this.modelValue;\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\tisActive() {\r\n\t\t\t\treturn this.innerValue === this.activeValue;\r\n\t\t\t},\r\n\t\t\tswitchStyle() {\r\n\t\t\t\tlet style = {}\r\n\t\t\t\t// 这里需要加2，是为了腾出边框的距离，否则圆点node会和外边框紧贴在一起\r\n\t\t\t\tstyle.width = this.$uv.addUnit(this.$uv.getPx(this.size) * 2 + 2)\r\n\t\t\t\tstyle.height = this.$uv.addUnit(this.$uv.getPx(this.size) + 2)\r\n\t\t\t\t// 如果自定义了“非激活”演示，name边框颜色设置为透明(跟非激活颜色一致)\r\n\t\t\t\t// 这里不能简单的设置为非激活的颜色，否则打开状态时，会有边框，所以需要透明\r\n\t\t\t\tif (this.customInactiveColor) {\r\n\t\t\t\t\tstyle.borderColor = 'rgba(0, 0, 0, 0)'\r\n\t\t\t\t}\r\n\t\t\t\tstyle.backgroundColor = this.isActive ? this.activeColor : this.inactiveColor\r\n\t\t\t\treturn style;\r\n\t\t\t},\r\n\t\t\tnodeStyle() {\r\n\t\t\t\tlet style = {}\r\n\t\t\t\t// 如果自定义非激活颜色，将node圆点的尺寸减少两个像素，让其与外边框距离更大一点\r\n\t\t\t\tstyle.width = this.$uv.addUnit(this.$uv.getPx(this.size) - this.space)\r\n\t\t\t\tstyle.height = this.$uv.addUnit(this.$uv.getPx(this.size) - this.space)\r\n\t\t\t\tconst translateX = this.isActive ? this.$uv.addUnit(this.space) : this.$uv.addUnit(this.$uv.getPx(this.size));\r\n\t\t\t\tstyle.transform = `translateX(-${translateX})`\r\n\t\t\t\treturn style\r\n\t\t\t},\r\n\t\t\tbgStyle() {\r\n\t\t\t\tlet style = {}\r\n\t\t\t\t// 这里配置一个多余的元素在HTML中，是为了让switch切换时，有更良好的背景色扩充体验(见实际效果)\r\n\t\t\t\tstyle.width = this.$uv.addUnit(this.$uv.getPx(this.size) * 2 - this.$uv.getPx(this.size) / 2)\r\n\t\t\t\tstyle.height = this.$uv.addUnit(this.$uv.getPx(this.size))\r\n\t\t\t\tstyle.backgroundColor = this.inactiveColor\r\n\t\t\t\t// 打开时，让此元素收缩，否则反之\r\n\t\t\t\tstyle.transform = `scale(${this.isActive ? 0 : 1})`\r\n\t\t\t\treturn style\r\n\t\t\t},\r\n\t\t\tcustomInactiveColor() {\r\n\t\t\t\t// 之所以需要判断是否自定义了“非激活”颜色，是为了让node圆点离外边框更宽一点的距离\r\n\t\t\t\treturn this.inactiveColor !== '#fff' && this.inactiveColor !== '#ffffff'\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tclickHandler() {\r\n\t\t\t\tif (!this.disabled && !this.loading) {\r\n\t\t\t\t\tconst oldValue = this.isActive ? this.inactiveValue : this.activeValue\r\n\t\t\t\t\tif (!this.asyncChange) {\r\n\t\t\t\t\t\tthis.$emit('input', oldValue)\r\n\t\t\t\t\t\tthis.$emit('update:modelValue', oldValue)\r\n\t\t\t\t\t}\r\n\t\t\t\t\t// 放到下一个生命周期，因为双向绑定的value修改父组件状态需要时间，且是异步的\r\n\t\t\t\t\tthis.$nextTick(() => {\r\n\t\t\t\t\t\tthis.$emit('change', oldValue)\r\n\t\t\t\t\t})\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t};\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t.uv-switch {\r\n\t\t@include flex(row);\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\tbox-sizing: border-box;\r\n\t\t/* #endif */\r\n\t\tposition: relative;\r\n\t\tbackground-color: #fff;\r\n\t\tborder-width: 1px;\r\n\t\tborder-radius: 100px;\r\n\t\ttransition: background-color 0.4s;\r\n\t\tborder-color: rgba(0, 0, 0, 0.12);\r\n\t\tborder-style: solid;\r\n\t\tjustify-content: flex-end;\r\n\t\talign-items: center;\r\n\t\t// 由于weex为阿里逗着玩的KPI项目，导致bug奇多，这必须要写这一行，\r\n\t\t// 否则在iOS上，点击页面任意地方，都会触发switch的点击事件\r\n\t\toverflow: hidden;\r\n\t\t&__node {\r\n\t\t\t@include flex(row);\r\n\t\t\talign-items: center;\r\n\t\t\tjustify-content: center;\r\n\t\t\tborder-radius: 100px;\r\n\t\t\tbackground-color: #fff;\r\n\t\t\tborder-radius: 100px;\r\n\t\t\tbox-shadow: 1px 1px 1px 0 rgba(0, 0, 0, 0.25);\r\n\t\t\ttransition-property: transform;\r\n\t\t\ttransition-duration: 0.4s;\r\n\t\t\ttransition-timing-function: cubic-bezier(0.3, 1.05, 0.4, 1.05);\r\n\t\t}\r\n\t\t&__bg {\r\n\t\t\tposition: absolute;\r\n\t\t\tborder-radius: 100px;\r\n\t\t\tbackground-color: #FFFFFF;\r\n\t\t\ttransition-property: transform;\r\n\t\t\ttransition-duration: 0.4s;\r\n\t\t\tborder-top-left-radius: 0;\r\n\t\t\tborder-bottom-left-radius: 0;\r\n\t\t\ttransition-timing-function: ease;\r\n\t\t}\r\n\t\t&--disabled {\r\n\t\t\topacity: 0.6;\r\n\t\t}\r\n\t}\r\n</style>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-switch/package.json",
    "content": "{\r\n  \"id\": \"uv-switch\",\r\n  \"displayName\": \"uv-switch 开关选择器 全面兼容vue3+2、app、h5、小程序等多端\",\r\n  \"version\": \"1.0.5\",\r\n  \"description\": \"选择开关用于在打开和关闭状态之间进行切换，支持异步操作。\",\r\n  \"keywords\": [\r\n    \"uv-switch\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"switch\",\r\n    \"开关选择器\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-loading-icon\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-switch/readme.md",
    "content": "## Switch 开关选择器\r\n\r\n> **组件名：uv-switch**\r\n\r\n选择开关用于在打开和关闭状态之间进行切换，支持异步操作。\r\n\r\n# <a href=\"https://www.uvui.cn/components/switch.html\" target=\"_blank\">查看文档</a>\r\n\r\n## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>（请不要 下载插件ZIP）</small>\r\n\r\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n<a href=\"https://ext.dcloud.net.cn/plugin?name=uv-ui\" target=\"_blank\">\r\n\r\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\r\n\r\n</a>\r\n\r\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-tabbar/changelog.md",
    "content": "## 1.0.5（2023-07-17）\n1. 优化文档\n2. 优化其他\n## 1.0.4（2023-06-13）\r\n1. 底部安全距离依赖添加\r\n## 1.0.3（2023-06-09）\r\n1. 增加iconSize参数\r\n## 1.0.2（2023-06-01）\r\n1. 修复点击触发两次事件的BUG \r\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-tabbar 底部导航栏 \r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-tabbar/components/uv-tabbar/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 当前匹配项的name\r\n\t\tvalue: {\r\n\t\t\ttype: [String, Number, null],\r\n\t\t\tdefault: null\r\n\t\t},\r\n\t\t// 是否为iPhoneX留出底部安全距离\r\n\t\tsafeAreaInsetBottom: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 是否显示上方边框\r\n\t\tborder: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 元素层级z-index\r\n\t\tzIndex: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 9\r\n\t\t},\r\n\t\t// 选中标签的颜色\r\n\t\tactiveColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#1989fa'\r\n\t\t},\r\n\t\t// 未选中标签的颜色\r\n\t\tinactiveColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#7d7e80'\r\n\t\t},\r\n\t\t// 是否固定在底部\r\n\t\tfixed: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// fixed定位固定在底部时，是否生成一个等高元素防止塌陷\r\n\t\tplaceholder: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 图标大小\r\n\t\ticonSize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 20\r\n\t\t},\r\n\t\t...uni.$uv?.props?.tabbar\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-tabbar/components/uv-tabbar/uv-tabbar.vue",
    "content": "<template>\r\n\t<view class=\"uv-tabbar\">\r\n\t\t<view\r\n\t\t    class=\"uv-tabbar__content\"\r\n\t\t    ref=\"uv-tabbar__content\"\r\n\t\t    @touchmove.stop.prevent=\"noop\"\r\n\t\t    :class=\"[border && 'uv-border-top', fixed && 'uv-tabbar--fixed']\"\r\n\t\t    :style=\"[tabbarStyle]\"\r\n\t\t>\r\n\t\t\t<view class=\"uv-tabbar__content__item-wrapper\">\r\n\t\t\t\t<slot />\r\n\t\t\t</view>\r\n\t\t\t<uv-safe-bottom v-if=\"safeAreaInsetBottom\"></uv-safe-bottom>\r\n\t\t</view>\r\n\t\t<view\r\n\t\t    class=\"uv-tabbar__placeholder\"\r\n\t\t\tv-if=\"placeholder\"\r\n\t\t    :style=\"{\r\n\t\t\t\theight: placeholderHeight + 'px',\r\n\t\t\t}\"\r\n\t\t></view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t// #ifdef APP-NVUE\r\n\tconst dom = uni.requireNativePlugin('dom')\r\n\t// #endif\r\n\t/**\r\n\t * Tabbar 底部导航栏\r\n\t * @description 此组件提供了自定义tabbar的能力。\r\n\t * @tutorial https://www.uvui.cn/components/tabbar.html\r\n\t * @property {String | Number}\tvalue\t\t\t\t当前匹配项的name\r\n\t * @property {Boolean}\t\t\tsafeAreaInsetBottom\t是否为iPhoneX留出底部安全距离（默认 true ）\r\n\t * @property {Boolean}\t\t\tborder\t\t\t\t是否显示上方边框（默认 true ）\r\n\t * @property {String | Number}\tzIndex\t\t\t\t元素层级z-index（默认 1 ）\r\n\t * @property {String}\t\t\tactiveColor\t\t\t选中标签的颜色（默认 '#1989fa' ）\r\n\t * @property {String}\t\t\tinactiveColor\t\t未选中标签的颜色（默认 '#7d7e80' ）\r\n\t * @property {Boolean}\t\t\tfixed\t\t\t\t是否固定在底部（默认 true ）\r\n\t * @property {Boolean}\t\t\tplaceholder\t\t\tfixed定位固定在底部时，是否生成一个等高元素防止塌陷（默认 true ）\r\n\t * @property {Object}\t\t\tcustomStyle\t\t\t定义需要用到的外部样式\r\n\t * \r\n\t * @example <uv-tabbar :value=\"value2\" :placeholder=\"false\" @change=\"name => value2 = name\" :fixed=\"false\" :safeAreaInsetBottom=\"false\"><uv-tabbar-item text=\"首页\" icon=\"home\" dot ></uv-tabbar-item></uv-tabbar>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-tabbar',\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tplaceholderHeight: 0\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\ttabbarStyle() {\r\n\t\t\t\tconst style = {\r\n\t\t\t\t\tzIndex: this.zIndex\r\n\t\t\t\t}\r\n\t\t\t\t// 合并来自父组件的customStyle样式\r\n\t\t\t\treturn this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))\r\n\t\t\t},\r\n\t\t\t// 监听多个参数的变化，通过在computed执行对应的操作\r\n\t\t\tupdateChild() {\r\n\t\t\t\treturn [this.value, this.activeColor, this.inactiveColor]\r\n\t\t\t},\r\n\t\t\tupdatePlaceholder() {\r\n\t\t\t\treturn [this.fixed, this.placeholder]\r\n\t\t\t}\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\tupdateChild() {\r\n\t\t\t\t// 如果updateChildren中的元素发生了变化，则执行子元素初始化操作\r\n\t\t\t\tthis.updateChildren()\r\n\t\t\t},\r\n\t\t\tupdatePlaceholder() {\r\n\t\t\t\t// 如果fixed，placeholder等参数发生变化，重新计算占位元素的高度\r\n\t\t\t\tthis.setPlaceholderHeight()\r\n\t\t\t}\r\n\t\t},\r\n\t\tcreated() {\r\n\t\t\tthis.children = []\r\n\t\t},\r\n\t\tmounted() {\r\n\t\t\tthis.setPlaceholderHeight()\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tupdateChildren() {\r\n\t\t\t\t// 如果存在子元素，则执行子元素的updateFromParent进行更新数据\r\n\t\t\t\tthis.children.length && this.children.map(child => child.updateFromParent())\r\n\t\t\t},\r\n\t\t\t// 设置用于防止塌陷元素的高度\r\n\t\t\tasync setPlaceholderHeight() {\r\n\t\t\t\tif (!this.fixed || !this.placeholder) return\r\n\t\t\t\t// 延时一定时间\r\n\t\t\t\tawait this.$uv.sleep(20)\r\n\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\tthis.$uvGetRect('.uv-tabbar__content').then(({height = 50}) => {\r\n\t\t\t\t\t// 修复IOS safearea bottom 未填充高度\r\n\t\t\t\t\tthis.placeholderHeight = height\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\tdom.getComponentRect(this.$refs['uv-tabbar__content'], (res) => {\r\n\t\t\t\t\tconst {\r\n\t\t\t\t\t\tsize\r\n\t\t\t\t\t} = res\r\n\t\t\t\t\tthis.placeholderHeight = size.height\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t$show-border: 1;\r\n\t$show-border-top: 1;\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t.uv-tabbar {\r\n\t\t@include flex(column);\r\n\t\tflex: 1;\r\n\t\tjustify-content: center;\r\n\t\t\r\n\t\t&__content {\r\n\t\t\t@include flex(column);\r\n\t\t\tbackground-color: #fff;\r\n\t\t\t\r\n\t\t\t&__item-wrapper {\r\n\t\t\t\theight: 50px;\r\n\t\t\t\t@include flex(row);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t&--fixed {\r\n\t\t\tposition: fixed;\r\n\t\t\tbottom: 0;\r\n\t\t\tleft: 0;\r\n\t\t\tright: 0;\r\n\t\t}\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-tabbar/components/uv-tabbar-item/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// item标签的名称，作为与uv-tabbar的value参数匹配的标识符\r\n\t\tname: {\r\n\t\t\ttype: [String, Number, null],\r\n\t\t\tdefault: null\r\n\t\t},\r\n\t\t// uv-ui内置图标或者绝对路径的图片\r\n\t\ticon: {\r\n\t\t\ticon: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 图标大小，默认uv-tabbar的iconSize=20\r\n\t\ticonSize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 右上角的角标提示信息\r\n\t\tbadge: {\r\n\t\t\ttype: [String, Number, null],\r\n\t\t\tdefault: null\r\n\t\t},\r\n\t\t// 是否显示圆点，将会覆盖badge参数\r\n\t\tdot: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 描述文本\r\n\t\ttext: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 控制徽标的位置，对象或者字符串形式，可以设置top和right属性\r\n\t\tbadgeStyle: {\r\n\t\t\ttype: [Object, String],\r\n\t\t\tdefault: 'top: 6px;right:2px;'\r\n\t\t},\r\n\t\t...uni.$uv?.props?.tabbarItem\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-tabbar/components/uv-tabbar-item/uv-tabbar-item.vue",
    "content": "<template>\r\n\t<view\r\n\t    class=\"uv-tabbar-item\"\r\n\t    :style=\"[$uv.addStyle(customStyle)]\"\r\n\t    @tap=\"clickHandler\"\r\n\t>\r\n\t\t<view class=\"uv-tabbar-item__icon\">\r\n\t\t\t<uv-icon\r\n\t\t\t    v-if=\"icon\"\r\n\t\t\t    :name=\"icon\"\r\n\t\t\t    :color=\"isActive? parentData.activeColor : parentData.inactiveColor\"\r\n\t\t\t    :size=\"iconSize? iconSize: parentData.iconSize\"\r\n\t\t\t></uv-icon>\r\n\t\t\t<template v-else>\r\n\t\t\t\t<slot\r\n\t\t\t\t    v-if=\"isActive\"\r\n\t\t\t\t    name=\"active-icon\"\r\n\t\t\t\t/>\r\n\t\t\t\t<slot\r\n\t\t\t\t    v-else\r\n\t\t\t\t    name=\"inactive-icon\"\r\n\t\t\t\t/>\r\n\t\t\t</template>\r\n\t\t\t<uv-badge\r\n\t\t\t\tabsolute\r\n\t\t\t\t:offset=\"[0, dot ? '34rpx' : badge > 9 ? '14rpx' : '20rpx']\"\r\n\t\t\t    :customStyle=\"badgeStyle\"\r\n\t\t\t    :isDot=\"dot\"\r\n\t\t\t    :value=\"badge || (dot ? 1 : null)\"\r\n\t\t\t    :show=\"dot || badge > 0\"\r\n\t\t\t></uv-badge>\r\n\t\t</view>\r\n\t\t\r\n\t\t<slot name=\"text\">\r\n\t\t\t<text\r\n\t\t\t    class=\"uv-tabbar-item__text\"\r\n\t\t\t    :style=\"{\r\n\t\t\t\t\tcolor: isActive? parentData.activeColor : parentData.inactiveColor\r\n\t\t\t\t}\"\r\n\t\t\t>{{ text }}</text>\r\n\t\t</slot>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * TabbarItem 底部导航栏子组件\r\n\t * @description 此组件提供了自定义tabbar的能力。\r\n\t * @tutorial https://www.uvui.cn/components/tabbar.html\r\n\t * @property {String | Number}\tname\t\titem标签的名称，作为与uv-tabbar的value参数匹配的标识符\r\n\t * @property {String}\t\t\ticon\t\tuvui内置图标或者绝对路径的图片\r\n\t * @property {String | Number}\tbadge\t\t右上角的角标提示信息\r\n\t * @property {Boolean}\t\t\tdot\t\t\t是否显示圆点，将会覆盖badge参数（默认 false ）\r\n\t * @property {String}\t\t\ttext\t\t描述文本\r\n\t * @property {Object | String}\tbadgeStyle\t控制徽标的位置，对象或者字符串形式，可以设置top和right属性（默认 'top: 6px;right:2px;' ）\r\n\t * @property {Object}\t\t\tcustomStyle\t定义需要用到的外部样式\r\n\t * \r\n\t * @example <uv-tabbar :value=\"value2\" :placeholder=\"false\" @change=\"name => value2 = name\" :fixed=\"false\" :safeAreaInsetBottom=\"false\"><uv-tabbar-item text=\"首页\" icon=\"home\" dot ></uv-tabbar-item></uv-tabbar>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-tabbar-item',\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\temits: ['click','change'],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tisActive: false, // 是否处于激活状态\r\n\t\t\t\tparentData: {\r\n\t\t\t\t\tvalue: null,\r\n\t\t\t\t\tactiveColor: '',\r\n\t\t\t\t\tinactiveColor: '',\r\n\t\t\t\t\ticonSize: 20\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\tcreated() {\r\n\t\t\tthis.init()\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tinit() {\r\n\t\t\t\t// 支付宝小程序不支持provide/inject，所以使用这个方法获取整个父组件，在created定义，避免循环引用\r\n\t\t\t\tthis.updateParentData()\r\n\t\t\t\tif (!this.parent) {\r\n\t\t\t\t\tthis.$uv.error('uv-tabbar-item必须搭配uv-tabbar组件使用')\r\n\t\t\t\t}\r\n\t\t\t\t// 本子组件在uv-tabbar的children数组中的索引\r\n\t\t\t\tconst index = this.parent.children.indexOf(this)\r\n\t\t\t\t// 判断本组件的name(如果没有定义name，就用index索引)是否等于父组件的value参数\r\n\t\t\t\tthis.isActive = (this.name || index) === this.parentData.value\r\n\t\t\t},\r\n\t\t\tupdateParentData() {\r\n\t\t\t\t// 此方法在mixin中\r\n\t\t\t\tthis.getParentData('uv-tabbar')\r\n\t\t\t},\r\n\t\t\t// 此方法将会被父组件uv-tabbar调用\r\n\t\t\tupdateFromParent() {\r\n\t\t\t\t// 重新初始化\r\n\t\t\t\tthis.init()\r\n\t\t\t},\r\n\t\t\tclickHandler() {\r\n\t\t\t\tthis.$nextTick(() => {\r\n\t\t\t\t\tconst index = this.parent.children.indexOf(this)\r\n\t\t\t\t\tconst name = this.name || index\r\n\t\t\t\t\t// 点击的item为非激活的item才发出change事件\r\n\t\t\t\t\tif (name !== this.parent.value) {\r\n\t\t\t\t\t\tthis.parent.$emit('change', name)\r\n\t\t\t\t\t}\r\n\t\t\t\t\tthis.$emit('click', name)\r\n\t\t\t\t})\r\n\t\t\t}\r\n\t\t},\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t.uv-tabbar-item {\r\n\t\t@include flex(column);\r\n\t\talign-items: center;\r\n\t\tjustify-content: center;\r\n\t\tflex: 1;\r\n\t\t\r\n\t\t&__icon {\r\n\t\t\t@include flex;\r\n\t\t\tposition: relative;\r\n\t\t\twidth: 150rpx;\r\n\t\t\tjustify-content: center;\r\n\t\t}\r\n\r\n\t\t&__text {\r\n\t\t\tmargin-top: 2px;\r\n\t\t\tfont-size: 12px;\r\n\t\t\tcolor: $uv-content-color;\r\n\t\t}\r\n\t}\r\n\r\n\t/* #ifdef MP */\r\n\t// 由于小程序都使用shadow DOM形式实现，需要给影子宿主设置flex: 1才能让其撑开\r\n\t:host {\r\n\t\tflex: 1\r\n\t}\r\n\t/* #endif */\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-tabbar/package.json",
    "content": "{\r\n  \"id\": \"uv-tabbar\",\r\n  \"displayName\": \"uv-tabbar 底部导航栏 全面兼容vue3+2、app、h5、小程序等多端\",\r\n  \"version\": \"1.0.5\",\r\n  \"description\": \"底部导航栏组件提供了自定义tabbar的能力，可以满足某些需要鉴权跳转等场景。\",\r\n  \"keywords\": [\r\n    \"uv-tabbar\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"tabbar\",\r\n    \"底部导航栏\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-icon\",\r\n\t\t\t\"uv-badge\",\r\n\t\t\t\"uv-safe-bottom\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-tabbar/readme.md",
    "content": "## Tabbar 底部导航栏\n\n> **组件名：uv-tabbar**\n\n此组件提供了自定义`tabbar`的能力。\n\n一般固定在底部，可以满足某些需要鉴权跳转等场景。一旦自定义底部导航栏后，为了达到不闪烁的效果，建议在`tabbar`页面进行组件切换。\n\n# <a href=\"https://www.uvui.cn/components/tabbar.html\" target=\"_blank\">查看文档</a>\n\n## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <span style=\"font-size:14px;font-weight:700;\">（请不要 下载插件ZIP）</span>\n\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\n\n<a href=\"https://ext.dcloud.net.cn/plugin?name=uv-ui\" target=\"_blank\">\n\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\n\n</a>\n\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-tabs/changelog.md",
    "content": "## 1.0.8（2023-10-13）\n1. 优化点击一个选项，change事件重复派发的问题\n## 1.0.7（2023-09-14）\r\n1. 优化首次加载时，处理下划线会有左到右的过渡效果\r\n## 1.0.6（2023-09-13）\r\n1. 修复设置lineWidth未带单位产生的误差BUG\r\n## 1.0.5（2023-06-23）\r\n添加uv-icon依赖\r\n## 1.0.4（2023-06-16）\r\n1. 增加customStyle参数\r\n## 1.0.3（2023-06-12）\r\n1. activeStyle设置字体大小，导致下划线位置不对的BUG，增加this.$nextTick机制\r\n## 1.0.2（2023-05-23）\r\n1. 修复nvue中不滚动的BUG\r\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-tabs 标签选项卡\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-tabs/components/uv-tabs/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 滑块的移动过渡时间，单位ms\r\n\t\tduration: {\r\n\t\t\ttype: Number,\r\n\t\t\tdefault: 300\r\n\t\t},\r\n\t\t// tabs标签数组\r\n\t\tlist: {\r\n\t\t\ttype: Array,\r\n\t\t\tdefault: () => []\r\n\t\t},\r\n\t\t// 滑块颜色\r\n\t\tlineColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#3c9cff'\r\n\t\t},\r\n\t\t// 菜单选择中时的样式\r\n\t\tactiveStyle: {\r\n\t\t\ttype: [String, Object],\r\n\t\t\tdefault: () => ({\r\n\t\t\t\tcolor: '#303133'\r\n\t\t\t})\r\n\t\t},\r\n\t\t// 菜单非选中时的样式\r\n\t\tinactiveStyle: {\r\n\t\t\ttype: [String, Object],\r\n\t\t\tdefault: () => ({\r\n\t\t\t\tcolor: '#606266'\r\n\t\t\t})\r\n\t\t},\r\n\t\t// 滑块长度\r\n\t\tlineWidth: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 20\r\n\t\t},\r\n\t\t// 滑块高度\r\n\t\tlineHeight: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 3\r\n\t\t},\r\n\t\t// 滑块背景显示大小，当滑块背景设置为图片时使用\r\n\t\tlineBgSize: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'cover'\r\n\t\t},\r\n\t\t// 菜单item的样式\r\n\t\titemStyle: {\r\n\t\t\ttype: [String, Object],\r\n\t\t\tdefault: () => ({\r\n\t\t\t\theight: '44px'\r\n\t\t\t})\r\n\t\t},\r\n\t\t// 菜单是否可滚动\r\n\t\tscrollable: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 当前选中标签的索引\r\n\t\tcurrent: {\r\n\t\t\ttype: [Number, String],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 默认读取的键名\r\n\t\tkeyName: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'name'\r\n\t\t},\r\n\t\t...uni.$uv?.props?.tabs\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-tabs/components/uv-tabs/uv-tabs.vue",
    "content": "<template>\r\n\t<view class=\"uv-tabs\" :style=\"[$uv.addStyle(customStyle)]\">\r\n\t\t<view class=\"uv-tabs__wrapper\">\r\n\t\t\t<slot name=\"left\" />\r\n\t\t\t<view class=\"uv-tabs__wrapper__scroll-view-wrapper\">\r\n\t\t\t\t<scroll-view\r\n\t\t\t\t\t:scroll-x=\"scrollable\"\r\n\t\t\t\t\t:scroll-left=\"scrollLeft\"\r\n\t\t\t\t\tscroll-with-animation\r\n\t\t\t\t\tclass=\"uv-tabs__wrapper__scroll-view\"\r\n\t\t\t\t\t:show-scrollbar=\"false\"\r\n\t\t\t\t\tref=\"uv-tabs__wrapper__scroll-view\"\r\n\t\t\t\t>\r\n\t\t\t\t\t<view\r\n\t\t\t\t\t\tclass=\"uv-tabs__wrapper__nav\"\r\n\t\t\t\t\t\tref=\"uv-tabs__wrapper__nav\"\r\n\t\t\t\t\t\t:style=\"{\r\n\t\t\t\t\t\t\tflex: scrollable ? '' : 1\r\n\t\t\t\t\t\t}\"\r\n\t\t\t\t\t>\r\n\t\t\t\t\t\t<view\r\n\t\t\t\t\t\t\tclass=\"uv-tabs__wrapper__nav__item\"\r\n\t\t\t\t\t\t\tv-for=\"(item, index) in list\"\r\n\t\t\t\t\t\t\t:key=\"index\"\r\n\t\t\t\t\t\t\t@tap=\"clickHandler(item, index)\"\r\n\t\t\t\t\t\t\t:ref=\"`uv-tabs__wrapper__nav__item-${index}`\"\r\n\t\t\t\t\t\t\t:style=\"[{flex: scrollable ? '' : 1},$uv.addStyle(itemStyle)]\"\r\n\t\t\t\t\t\t\t:class=\"[`uv-tabs__wrapper__nav__item-${index}`, item.disabled && 'uv-tabs__wrapper__nav__item--disabled']\"\r\n\t\t\t\t\t\t>\r\n\t\t\t\t\t\t\t<text\r\n\t\t\t\t\t\t\t\t:class=\"[item.disabled && 'uv-tabs__wrapper__nav__item__text--disabled']\"\r\n\t\t\t\t\t\t\t\tclass=\"uv-tabs__wrapper__nav__item__text\"\r\n\t\t\t\t\t\t\t\t:style=\"[textStyle(index)]\"\r\n\t\t\t\t\t\t\t>{{ item[keyName] }}</text>\r\n\t\t\t\t\t\t\t<uv-badge\r\n\t\t\t\t\t\t\t\t:show=\"!!(item.badge && (item.badge.show || item.badge.isDot || item.badge.value))\"\r\n\t\t\t\t\t\t\t\t:isDot=\"item.badge && item.badge.isDot || propsBadge.isDot\"\r\n\t\t\t\t\t\t\t\t:value=\"item.badge && item.badge.value || propsBadge.value\"\r\n\t\t\t\t\t\t\t\t:max=\"item.badge && item.badge.max || propsBadge.max\"\r\n\t\t\t\t\t\t\t\t:type=\"item.badge && item.badge.type || propsBadge.type\"\r\n\t\t\t\t\t\t\t\t:showZero=\"item.badge && item.badge.showZero || propsBadge.showZero\"\r\n\t\t\t\t\t\t\t\t:bgColor=\"item.badge && item.badge.bgColor || propsBadge.bgColor\"\r\n\t\t\t\t\t\t\t\t:color=\"item.badge && item.badge.color || propsBadge.color\"\r\n\t\t\t\t\t\t\t\t:shape=\"item.badge && item.badge.shape || propsBadge.shape\"\r\n\t\t\t\t\t\t\t\t:numberType=\"item.badge && item.badge.numberType || propsBadge.numberType\"\r\n\t\t\t\t\t\t\t\t:inverted=\"item.badge && item.badge.inverted || propsBadge.inverted\"\r\n\t\t\t\t\t\t\t\tcustomStyle=\"margin-left: 4px;\"\r\n\t\t\t\t\t\t\t></uv-badge>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<!-- #ifdef APP-NVUE -->\r\n\t\t\t\t\t\t<view\r\n\t\t\t\t\t\t\tclass=\"uv-tabs__wrapper__nav__line\"\r\n\t\t\t\t\t\t\tref=\"uv-tabs__wrapper__nav__line\"\r\n\t\t\t\t\t\t\t:style=\"[{\r\n\t\t\t\t\t\t\t\twidth: $uv.addUnit(lineWidth),\r\n\t\t\t\t\t\t\t\theight: firstTime?0:$uv.addUnit(lineHeight),\r\n\t\t\t\t\t\t\t\tbackground: lineColor,\r\n\t\t\t\t\t\t\t\tbackgroundSize: lineBgSize\r\n\t\t\t\t\t\t\t}]\"\r\n\t\t\t\t\t\t>\r\n\t\t\t\t\t\t<!-- #endif -->\r\n\t\t\t\t\t\t<!-- #ifndef APP-NVUE -->\r\n\t\t\t\t\t\t<view\r\n\t\t\t\t\t\t\tclass=\"uv-tabs__wrapper__nav__line\"\r\n\t\t\t\t\t\t\tref=\"uv-tabs__wrapper__nav__line\"\r\n\t\t\t\t\t\t\t:style=\"[{\r\n\t\t\t\t\t\t\t\t\twidth: $uv.addUnit(lineWidth),\r\n\t\t\t\t\t\t\t\t\ttransform: `translate(${lineOffsetLeft}px)`,\r\n\t\t\t\t\t\t\t\t\ttransitionDuration: `${firstTime ? 0 : duration}ms`,\r\n\t\t\t\t\t\t\t\t\theight: firstTime?0:$uv.addUnit(lineHeight),\r\n\t\t\t\t\t\t\t\t\tbackground: lineColor,\r\n\t\t\t\t\t\t\t\t\tbackgroundSize: lineBgSize,\r\n\t\t\t\t\t\t\t\t}]\"\r\n\t\t\t\t\t\t>\r\n\t\t\t\t\t\t<!-- #endif -->\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</scroll-view>\r\n\t\t\t</view>\r\n\t\t\t<slot name=\"right\" />\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport uvBadgeProps from '@/uni_modules/uv-badge/components/uv-badge/props.js'\r\n\t// #ifdef APP-NVUE\r\n\tconst animation = uni.requireNativePlugin('animation')\r\n\tconst dom = uni.requireNativePlugin('dom')\r\n\t// #endif\r\n\timport props from './props.js';\r\n\t/**\r\n\t * Tabs 标签\r\n\t * @description tabs标签组件，在标签多的时候，可以配置为左右滑动，标签少的时候，可以禁止滑动。 该组件的一个特点是配置为滚动模式时，激活的tab会自动移动到组件的中间位置。\r\n\t * @tutorial https://www.uvui.cn/components/tabs.html\r\n\t * @property {String | Number}\tduration\t\t\t滑块移动一次所需的时间，单位秒（默认 200 ）\r\n\t * @property {String | Number}\tswierWidth\t\t\tswiper的宽度（默认 '750rpx' ）\r\n\t * @property {String}\tkeyName\t 从`list`元素对象中读取的键名（默认 'name' ）\r\n\t * @event {Function(index)} change 标签改变时触发 index: 点击了第几个tab，索引从0开始\r\n\t * @event {Function(index)} click 点击标签时触发 index: 点击了第几个tab，索引从0开始\r\n\t * @example <uv-tabs :list=\"list\" :is-scroll=\"false\" :current=\"current\" @change=\"change\"></uv-tabs>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-tabs',\r\n\t\temits: ['click','change'],\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tfirstTime: true,\r\n\t\t\t\tscrollLeft: 0,\r\n\t\t\t\tscrollViewWidth: 0,\r\n\t\t\t\tlineOffsetLeft: 0,\r\n\t\t\t\ttabsRect: {\r\n\t\t\t\t\tleft: 0\r\n\t\t\t\t},\r\n\t\t\t\tinnerCurrent: 0,\r\n\t\t\t\tmoving: false,\r\n\t\t\t}\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\tcurrent: {\r\n\t\t\t\timmediate: true,\r\n\t\t\t\thandler (newValue, oldValue) {\r\n\t\t\t\t\t// 内外部值不相等时，才尝试移动滑块\r\n\t\t\t\t\tif (newValue !== this.innerCurrent) {\r\n\t\t\t\t\t\tthis.innerCurrent = newValue\r\n\t\t\t\t\t\tthis.$nextTick(() => {\r\n\t\t\t\t\t\t\tthis.resize()\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t// list变化时，重新渲染list各项信息\r\n\t\t\tlist() {\r\n\t\t\t\tthis.$nextTick(() => {\r\n\t\t\t\t\tthis.resize()\r\n\t\t\t\t})\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\ttextStyle() {\r\n\t\t\t\treturn index => {\r\n\t\t\t\t\tconst style = {}\r\n\t\t\t\t\t// 取当期是否激活的样式\r\n\t\t\t\t\tconst customeStyle = index === this.innerCurrent ? this.$uv.addStyle(this.activeStyle) : this.$uv\r\n\t\t\t\t\t\t.addStyle(\r\n\t\t\t\t\t\t\tthis.inactiveStyle)\r\n\t\t\t\t\t// 如果当前菜单被禁用，则加上对应颜色，需要在此做处理，是因为nvue下，无法在style样式中通过!import覆盖标签的内联样式\r\n\t\t\t\t\tif (this.list[index].disabled) {\r\n\t\t\t\t\t\tstyle.color = '#c8c9cc'\r\n\t\t\t\t\t}\r\n\t\t\t\t\treturn this.$uv.deepMerge(customeStyle, style)\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tpropsBadge() {\r\n\t\t\t\treturn uvBadgeProps\r\n\t\t\t}\r\n\t\t},\r\n\t\tasync mounted() {\r\n\t\t\tthis.init()\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tsetLineLeft() {\r\n\t\t\t\tconst tabItem = this.list[this.innerCurrent];\r\n\t\t\t\tif (!tabItem) {\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\t\t\t\t// 获取滑块该移动的位置\r\n\t\t\t\tlet lineOffsetLeft = this.list\r\n\t\t\t\t\t.slice(0, this.innerCurrent)\r\n\t\t\t\t\t.reduce((total, curr) => total + curr.rect.width, 0);\r\n        // 获取下划线的数值px表示法\r\n\t\t\t\tlet lineWidth = this.$uv.getPx(this.lineWidth);\r\n\t\t\t\t// 如果传的值未带单位+设置了全局单位，则带上单位计算，这样才没有误差\r\n\t\t\t\tif (this.$uv.test.number(this.lineWidth) && this.$uv.unit) {\r\n\t\t\t\t\tlineWidth = this.$uv.getPx(`${this.lineWidth}${this.$uv.unit}`);\r\n\t\t\t\t}\r\n\t\t\t\tthis.lineOffsetLeft = lineOffsetLeft + (tabItem.rect.width - lineWidth) / 2\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\t// 第一次移动滑块，无需过渡时间\r\n\t\t\t\tthis.animation(this.lineOffsetLeft, this.firstTime ? 0 : parseInt(this.duration))\r\n\t\t\t\t// #endif\r\n\r\n\t\t\t\t// 如果是第一次执行此方法，让滑块在初始化时，瞬间滑动到第一个tab item的中间\r\n\t\t\t\t// 这里需要一个定时器，因为在非nvue下，是直接通过style绑定过渡时间，需要等其过渡完成后，再设置为false(非第一次移动滑块)\r\n\t\t\t\tif (this.firstTime) {\r\n\t\t\t\t\tsetTimeout(() => {\r\n\t\t\t\t\t\tthis.firstTime = false\r\n\t\t\t\t\t}, 20);\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t// nvue下设置滑块的位置\r\n\t\t\tanimation(x, duration = 0) {\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\tconst ref = this.$refs['uv-tabs__wrapper__nav__line']\r\n\t\t\t\tanimation.transition(ref, {\r\n\t\t\t\t\tstyles: {\r\n\t\t\t\t\t\ttransform: `translateX(${x}px)`\r\n\t\t\t\t\t},\r\n\t\t\t\t\tduration\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\t\t\t},\r\n\t\t\t// 点击某一个标签\r\n\t\t\tclickHandler(item, index) {\r\n\t\t\t\t// 因为标签可能为disabled状态，所以click是一定会发出的，但是change事件是需要可用的状态才发出\r\n\t\t\t\tthis.$emit('click', {\r\n\t\t\t\t\t...item,\r\n\t\t\t\t\tindex\r\n\t\t\t\t})\r\n\t\t\t\t// 如果disabled状态，返回\r\n\t\t\t\tif (item.disabled) return\r\n\t\t\t\tif(this.innerCurrent != index) {\r\n\t\t\t\t\tthis.$emit('change', {\r\n\t\t\t\t\t\t...item,\r\n\t\t\t\t\t\tindex\r\n\t\t\t\t\t})\r\n\t\t\t\t}\r\n\t\t\t\tthis.innerCurrent = index\r\n\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\tthis.$nextTick(()=>{\r\n\t\t\t\t\tthis.resize()\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\tthis.$nextTick(()=>{\r\n\t\t\t\t\t// nvue模式下再给点延时，确保万无一失\r\n\t\t\t\t\tthis.$uv.sleep(30).then(res=>{\r\n\t\t\t\t\t\tthis.resize()\r\n\t\t\t\t\t});\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\t\t\t},\r\n\t\t\tinit() {\r\n\t\t\t\tthis.$uv.sleep().then(() => {\r\n\t\t\t\t\tthis.resize()\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\tsetScrollLeft() {\r\n\t\t\t\t// 当前活动tab的布局信息，有tab菜单的width和left(为元素左边界到父元素左边界的距离)等信息\r\n\t\t\t\tconst tabRect = this.list[this.innerCurrent]\r\n\t\t\t\t// 累加得到当前item到左边的距离\r\n\t\t\t\tconst offsetLeft = this.list\r\n\t\t\t\t\t.slice(0, this.innerCurrent)\r\n\t\t\t\t\t.reduce((total, curr) => {\r\n\t\t\t\t\t\treturn total + curr.rect.width\r\n\t\t\t\t\t}, 0)\r\n\t\t\t\t// 此处为屏幕宽度\r\n\t\t\t\tconst windowWidth = this.$uv.sys().windowWidth\r\n\t\t\t\t// 将活动的tabs-item移动到屏幕正中间，实际上是对scroll-view的移动\r\n\t\t\t\tlet scrollLeft = offsetLeft - (this.tabsRect.width - tabRect.rect.width) / 2 - (windowWidth - this.tabsRect\r\n\t\t\t\t\t.right) / 2 + this.tabsRect.left / 2\r\n\t\t\t\t// 这里做一个限制，限制scrollLeft的最大值为整个scroll-view宽度减去tabs组件的宽度\r\n\t\t\t\tscrollLeft = Math.min(scrollLeft, this.scrollViewWidth - this.tabsRect.width)\r\n\t\t\t\tthis.scrollLeft = Math.max(0, scrollLeft)\r\n\t\t\t},\r\n\t\t\t// 获取所有标签的尺寸\r\n\t\t\tresize() {\r\n\t\t\t\t// 如果不存在list，则不处理\r\n\t\t\t\tif(this.list.length === 0) {\r\n\t\t\t\t\treturn\r\n\t\t\t\t}\r\n\t\t\t\tPromise.all([this.getTabsRect(), this.getAllItemRect()]).then(([tabsRect, itemRect = []]) => {\r\n\t\t\t\t\tthis.tabsRect = tabsRect\r\n\t\t\t\t\tthis.scrollViewWidth = 0\r\n\t\t\t\t\titemRect.map((item, index) => {\r\n\t\t\t\t\t\t// 计算scroll-view的宽度，这里\r\n\t\t\t\t\t\tthis.scrollViewWidth += item.width\r\n\t\t\t\t\t\t// 另外计算每一个item的中心点X轴坐标\r\n\t\t\t\t\t\tthis.list[index].rect = item\r\n\t\t\t\t\t})\r\n\t\t\t\t\t// 获取了tabs的尺寸之后，设置滑块的位置\r\n\t\t\t\t\tthis.setLineLeft()\r\n\t\t\t\t\tthis.setScrollLeft()\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\t// 获取导航菜单的尺寸\r\n\t\t\tgetTabsRect() {\r\n\t\t\t\treturn new Promise(resolve => {\r\n\t\t\t\t\tthis.queryRect('uv-tabs__wrapper__scroll-view').then(size => resolve(size))\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\t// 获取所有标签的尺寸\r\n\t\t\tgetAllItemRect() {\r\n\t\t\t\treturn new Promise(resolve => {\r\n\t\t\t\t\tconst promiseAllArr = this.list.map((item, index) => this.queryRect(\r\n\t\t\t\t\t\t`uv-tabs__wrapper__nav__item-${index}`, true))\r\n\t\t\t\t\tPromise.all(promiseAllArr).then(sizes => resolve(sizes))\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\t// 获取各个标签的尺寸\r\n\t\t\tqueryRect(el, item) {\r\n\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\t// $uvGetRect为uni-ui自带的节点查询简化方法，详见文档介绍：https://www.uvui.cn/js/getRect.html\r\n\t\t\t\t// 组件内部一般用this.$uvGetRect，对外的为getRect，二者功能一致，名称不同\r\n\t\t\t\treturn new Promise(resolve => {\r\n\t\t\t\t\tthis.$uvGetRect(`.${el}`).then(size => {\r\n\t\t\t\t\t\tresolve(size)\r\n\t\t\t\t\t})\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\t// nvue下，使用dom模块查询元素高度\r\n\t\t\t\t// 返回一个promise，让调用此方法的主体能使用then回调\r\n\t\t\t\treturn new Promise(resolve => {\r\n\t\t\t\t\tdom.getComponentRect(item ? this.$refs[el][0] : this.$refs[el], res => {\r\n\t\t\t\t\t\tresolve(res.size)\r\n\t\t\t\t\t})\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\t\t\t},\r\n\t\t},\r\n\t}\r\n</script>\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t.uv-tabs {\r\n\r\n\t\t&__wrapper {\r\n\t\t\t@include flex;\r\n\t\t\talign-items: center;\r\n\r\n\t\t\t&__scroll-view-wrapper {\r\n\t\t\t\tflex: 1;\r\n\t\t\t\t/* #ifndef APP-NVUE */\r\n\t\t\t\toverflow: auto hidden;\r\n\t\t\t\t/* #endif */\r\n\t\t\t}\r\n\r\n\t\t\t&__scroll-view {\r\n\t\t\t\t@include flex;\r\n\t\t\t\tflex: 1;\r\n\t\t\t}\r\n\r\n\t\t\t&__nav {\r\n\t\t\t\t@include flex;\r\n\t\t\t\tposition: relative;\r\n\r\n\t\t\t\t&__item {\r\n\t\t\t\t\tpadding: 0 11px;\r\n\t\t\t\t\t@include flex;\r\n\t\t\t\t\talign-items: center;\r\n\t\t\t\t\tjustify-content: center;\r\n\r\n\t\t\t\t\t&--disabled {\r\n\t\t\t\t\t\t/* #ifndef APP-NVUE */\r\n\t\t\t\t\t\tcursor: not-allowed;\r\n\t\t\t\t\t\t/* #endif */\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t&__text {\r\n\t\t\t\t\t\tfont-size: 15px;\r\n\t\t\t\t\t\tcolor: $uv-content-color;\r\n\r\n\t\t\t\t\t\t&--disabled {\r\n\t\t\t\t\t\t\tcolor: $uv-disabled-color !important;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\t&__line {\r\n\t\t\t\t\theight: 3px;\r\n\t\t\t\t\tbackground: $uv-primary;\r\n\t\t\t\t\twidth: 30px;\r\n\t\t\t\t\tposition: absolute;\r\n\t\t\t\t\tbottom: 2px;\r\n\t\t\t\t\tborder-radius: 100px;\r\n\t\t\t\t\ttransition-property: transform;\r\n\t\t\t\t\ttransition-duration: 300ms;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-tabs/package.json",
    "content": "{\r\n  \"id\": \"uv-tabs\",\r\n  \"displayName\": \"uv-tabs 标签选项卡 全面兼容vue3+2、app、h5、小程序等多端\",\r\n  \"version\": \"1.0.8\",\r\n  \"description\": \"标签选项卡组件，是一个tabs标签组件，在标签多的时候，可以配置为左右滑动，标签少的时候，可以禁止滑动。 该组件的一个特点是配置为滚动模式时，激活的tab会自动移动到组件的中间位置。\",\r\n  \"keywords\": [\r\n    \"选项卡\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"tab\",\r\n    \"标签\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-badge\",\r\n\t\t\t\"uv-sticky\",\r\n\t\t\t\"uv-icon\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-tabs/readme.md",
    "content": "## Tabs 标签选项卡\r\n\r\n> **组件名：uv-tabs**\r\n\r\n标签选项卡组件，是一个`tabs`标签组件，在标签多的时候，可以配置为左右滑动，标签少的时候，可以禁止滑动。 该组件的一个特点是配置为滚动模式时，激活的`tab`会自动移动到组件的中间位置。\r\n\r\n# <a href=\"https://www.uvui.cn/components/tabs.html\" target=\"_blank\">查看文档</a>\r\n\r\n## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>（请不要 下载插件ZIP）</small>\r\n\r\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n<a href=\"https://ext.dcloud.net.cn/plugin?name=uv-ui\" target=\"_blank\">\r\n\r\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\r\n\r\n</a>\r\n\r\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-tags/changelog.md",
    "content": "## 1.0.3（2023-10-02）\n1.  兼容customStyle参数\n2.  优化\n## 1.0.2（2023-07-31）\r\n1. nvue模式下增加cell-child参数，避免在list中使用出现回收后不显示的BUG\r\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-tags 标签\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-tags/components/uv-tags/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 标签类型info、primary、success、warning、error\r\n\t\ttype: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'primary'\r\n\t\t},\r\n\t\t// 不可用\r\n\t\tdisabled: {\r\n\t\t\ttype: [Boolean, String],\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 标签的大小，large，medium，mini\r\n\t\tsize: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'medium'\r\n\t\t},\r\n\t\t// tag的形状，circle（两边半圆形）, square（方形，带圆角）\r\n\t\tshape: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'square'\r\n\t\t},\r\n\t\t// 标签文字\r\n\t\ttext: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 背景颜色，默认为空字符串，即不处理\r\n\t\tbgColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 标签字体颜色，默认为空字符串，即不处理\r\n\t\tcolor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 标签的边框颜色\r\n\t\tborderColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 点击时返回的索引值，用于区分例遍的数组哪个元素被点击了\r\n\t\tname: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 镂空时是否填充背景色\r\n\t\tplainFill: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否镂空\r\n\t\tplain: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否可关闭\r\n\t\tclosable: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 关闭按钮图标的颜色\r\n\t\tcloseColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#C6C7CB'\r\n\t\t},\r\n\t\t// 关闭按钮图标的位置 right（右边）right-top（右上） 默认right-top\r\n\t\tclosePlace: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'right-top'\r\n\t\t},\r\n\t\t// 是否显示\r\n\t\tshow: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 内置图标，或绝对路径的图片\r\n\t\ticon: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 图标颜色\r\n\t\ticonColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// nvue模式下 是否直接显示，在uv-list等cell下面使用就需要设置\r\n\t\tcellChild: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t...uni.$uv?.props?.tags\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-tags/components/uv-tags/uv-tags.vue",
    "content": "<template>\r\n\t<uv-transition\r\n\t\tmode=\"fade\"\r\n\t\t:show=\"show\"\r\n\t\t:cell-child=\"cellChild\"\r\n\t>\r\n\t\t<view class=\"uv-tags-wrapper\">\r\n\t\t\t<view\r\n\t\t\t\tclass=\"uv-tags\"\r\n\t\t\t\t:class=\"[`uv-tags--${shape}`, !plain && `uv-tags--${type}`, plain && `uv-tags--${type}--plain`, `uv-tags--${size}`,`uv-tags--${size}--${closePlace}`, plain && plainFill && `uv-tags--${type}--plain--fill`]\"\r\n\t\t\t\t@tap.stop=\"clickHandler\"\r\n\t\t\t\t:style=\"[{\r\n\t\t\t\t\tmarginRight: closable&& closePlace=='right-top' ? '10px' : 0,\r\n\t\t\t\t\tmarginTop: closable && closePlace=='right-top' ? '10px' : 0,\r\n\t\t\t\t}, style, $uv.addStyle(customStyle)]\"\r\n\t\t\t>\r\n\t\t\t\t<slot name=\"icon\">\r\n\t\t\t\t\t<view\r\n\t\t\t\t\t\tclass=\"uv-tags__icon\"\r\n\t\t\t\t\t\tv-if=\"icon\"\r\n\t\t\t\t\t>\r\n\t\t\t\t\t\t<image\r\n\t\t\t\t\t\t\tv-if=\"$uv.test.image(icon)\"\r\n\t\t\t\t\t\t\t:src=\"icon\"\r\n\t\t\t\t\t\t\t:style=\"[imgStyle]\"\r\n\t\t\t\t\t\t></image>\r\n\t\t\t\t\t\t<uv-icon\r\n\t\t\t\t\t\t\tv-else\r\n\t\t\t\t\t\t\t:color=\"elIconColor\"\r\n\t\t\t\t\t\t\t:name=\"icon\"\r\n\t\t\t\t\t\t\t:size=\"iconSize\"\r\n\t\t\t\t\t\t></uv-icon>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</slot>\r\n\t\t\t\t<text\r\n\t\t\t\t\tclass=\"uv-tags__text\"\r\n\t\t\t\t\t:style=\"[textColor]\"\r\n\t\t\t\t\t:class=\"[`uv-tags__text--${type}`, plain && `uv-tags__text--${type}--plain`, `uv-tags__text--${size}`]\"\r\n\t\t\t\t>{{ text }}</text>\r\n\t\t\t\t<view\r\n\t\t\t\t\tclass=\"uv-tags__close\"\r\n\t\t\t\t\t:class=\"[`uv-tags__close--${size}`,`uv-tags__close--${closePlace}`]\"\r\n\t\t\t\t\tv-if=\"closable && closePlace=='right'\"\r\n\t\t\t\t\t@tap.stop=\"closeHandler\"\r\n\t\t\t\t\t:style=\"{backgroundColor: closeColor}\"\r\n\t\t\t\t>\r\n\t\t\t\t\t<uv-icon\r\n\t\t\t\t\t\tname=\"close\"\r\n\t\t\t\t\t\t:size=\"closeSize\"\r\n\t\t\t\t\t\tcolor=\"#ffffff\"\r\n\t\t\t\t\t></uv-icon>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t\t<view\r\n\t\t\t\tclass=\"uv-tags__close\"\r\n\t\t\t\t:class=\"[`uv-tags__close--${size}`,`uv-tags__close--${closePlace}`]\"\r\n\t\t\t\tv-if=\"closable && closePlace=='right-top'\"\r\n\t\t\t\t@tap.stop=\"closeHandler\"\r\n\t\t\t\t:style=\"{backgroundColor: closeColor}\"\r\n\t\t\t>\r\n\t\t\t\t<uv-icon\r\n\t\t\t\t\tname=\"close\"\r\n\t\t\t\t\t:size=\"closeSize\"\r\n\t\t\t\t\tcolor=\"#ffffff\"\r\n\t\t\t\t></uv-icon>\r\n\t\t\t</view>\r\n\t\t</view>\r\n\t</uv-transition>\r\n</template>\r\n\r\n<script>\r\n\timport { image } from '@/uni_modules/uv-ui-tools/libs/function/test.js'\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * Tag 标签\r\n\t * @description tag组件一般用于标记和选择，我们提供了更加丰富的表现形式，能够较全面的涵盖您的使用场景\r\n\t * @tutorial https://www.uvui.cn/components/tag.html\r\n\t * @property {String}\t\t\ttype\t\t标签类型info、primary、success、warning、error （默认 'primary' ）\r\n\t * @property {Boolean | String}\tdisabled\t不可用（默认 false ）\r\n\t * @property {String}\t\t\tsize\t\t标签的大小，large，medium，mini （默认 'medium' ）\r\n\t * @property {String}\t\t\tshape\t\ttag的形状，circle（两边半圆形）, square（方形，带圆角）（默认 'square' ）\r\n\t * @property {String | Number}\ttext\t\t标签的文字内容 \r\n\t * @property {String}\t\t\tbgColor\t\t背景颜色，默认为空字符串，即不处理\r\n\t * @property {String}\t\t\tcolor\t\t标签字体颜色，默认为空字符串，即不处理\r\n\t * @property {String}\t\t\tborderColor\t镂空形式标签的边框颜色\r\n\t * @property {String}\t\t\tcloseColor\t关闭按钮图标的颜色（默认 #C6C7CB）\r\n\t * @property {String | Number}\tname\t\t点击时返回的索引值，用于区分例遍的数组哪个元素被点击了\r\n\t * @property {Boolean}\t\t\tplainFill\t镂空时是否填充背景色（默认 false ）\r\n\t * @property {Boolean}\t\t\tplain\t\t是否镂空（默认 false ）\r\n\t * @property {Boolean}\t\t\tclosable\t是否可关闭，设置为true，文字右边会出现一个关闭图标（默认 false ）\r\n\t * @property {Boolean}\t\t\tshow\t\t标签显示与否（默认 true ）\r\n\t * @property {String}\t\t\ticon\t\t内置图标，或绝对路径的图片\r\n\t * @event {Function(index)} click 点击标签时触发 index: 传递的index参数值\r\n\t * @event {Function(index)} close closable为true时，点击标签关闭按钮触发 index: 传递的index参数值\t\r\n\t * @example <uv-tags text=\"标签\" type=\"error\" plain plainFill></uv-tags>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-tags',\r\n\t\temits: ['click','close'],\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tcomputed: {\r\n\t\t\tstyle() {\r\n\t\t\t\tconst style = {}\r\n\t\t\t\tif (this.bgColor) {\r\n\t\t\t\t\tstyle.backgroundColor = this.bgColor\r\n\t\t\t\t}\r\n\t\t\t\tif (this.color) {\r\n\t\t\t\t\tstyle.color = this.color\r\n\t\t\t\t}\r\n\t\t\t\tif(this.borderColor) {\r\n\t\t\t\t\tstyle.borderColor = this.borderColor\r\n\t\t\t\t}\r\n\t\t\t\treturn style\r\n\t\t\t},\r\n\t\t\t// nvue下，文本颜色无法继承父元素\r\n\t\t\ttextColor() {\r\n\t\t\t\tconst style = {}\r\n\t\t\t\tif (this.color) {\r\n\t\t\t\t\tstyle.color = this.color\r\n\t\t\t\t}\r\n\t\t\t\treturn style\r\n\t\t\t},\r\n\t\t\timgStyle() {\r\n\t\t\t\tconst width = this.size === 'large' ? '17px' : this.size === 'medium' ? '15px' : '13px'\r\n\t\t\t\treturn {\r\n\t\t\t\t\twidth,\r\n\t\t\t\t\theight: width\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t// 文本的样式\r\n\t\t\tcloseSize() {\r\n\t\t\t\tconst size = this.size === 'large' ? 15 : this.size === 'medium' ? 13 : 12\r\n\t\t\t\treturn size\r\n\t\t\t},\r\n\t\t\t// 图标大小\r\n\t\t\ticonSize() {\r\n\t\t\t\tconst size = this.size === 'large' ? 21 : this.size === 'medium' ? 19 : 16\r\n\t\t\t\treturn size\r\n\t\t\t},\r\n\t\t\t// 图标颜色\r\n\t\t\telIconColor() {\r\n\t\t\t\treturn this.iconColor ? this.iconColor : this.plain ? this.type : '#ffffff'\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\t// 点击关闭按钮\r\n\t\t\tcloseHandler() {\r\n\t\t\t\tthis.$emit('close', this.name)\r\n\t\t\t},\r\n\t\t\t// 点击标签\r\n\t\t\tclickHandler() {\r\n\t\t\t\tthis.$emit('click', this.name)\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped >\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t.uv-tags-wrapper {\r\n\t\tposition: relative;\r\n\t}\r\n\r\n\t.uv-tags {\r\n\t\t@include flex;\r\n\t\talign-items: center;\r\n\t\tborder-style: solid;\r\n\r\n\t\t&--circle {\r\n\t\t\tborder-radius: 100px;\r\n\t\t}\r\n\r\n\t\t&--square {\r\n\t\t\tborder-radius: 3px;\r\n\t\t}\r\n\r\n\t\t&__icon {\r\n\t\t\tmargin-right: 4px;\r\n\t\t}\r\n\r\n\t\t&__text {\r\n\t\t\t&--mini {\r\n\t\t\t\tfont-size: 12px;\r\n\t\t\t\tline-height: 12px;\r\n\t\t\t}\r\n\r\n\t\t\t&--medium {\r\n\t\t\t\tfont-size: 13px;\r\n\t\t\t\tline-height: 13px;\r\n\t\t\t}\r\n\r\n\t\t\t&--large {\r\n\t\t\t\tfont-size: 15px;\r\n\t\t\t\tline-height: 15px;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t&--mini {\r\n\t\t\theight: 22px;\r\n\t\t\tline-height: 22px;\r\n\t\t\tpadding: 0 5px;\r\n\t\t\t&--right {\r\n\t\t\t\tpadding-right: 2px;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t&--medium {\r\n\t\t\theight: 26px;\r\n\t\t\tline-height: 22px;\r\n\t\t\tpadding: 0 10px;\r\n\t\t\t&--right {\r\n\t\t\t\tpadding: 0 4px 0 8px;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t&--large {\r\n\t\t\theight: 32px;\r\n\t\t\tline-height: 32px;\r\n\t\t\tpadding: 0 15px;\r\n\t\t\t&--right {\r\n\t\t\t\tpadding: 0 4px 0 8px;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t&--primary {\r\n\t\t\tbackground-color: $uv-primary;\r\n\t\t\tborder-width: 1px;\r\n\t\t\tborder-color: $uv-primary;\r\n\t\t}\r\n\r\n\t\t&--primary--plain {\r\n\t\t\tborder-width: 1px;\r\n\t\t\tborder-color: $uv-primary;\r\n\t\t}\r\n\r\n\t\t&--primary--plain--fill {\r\n\t\t\tbackground-color: #ecf5ff;\r\n\t\t}\r\n\r\n\t\t&__text--primary {\r\n\t\t\tcolor: #FFFFFF;\r\n\t\t}\r\n\r\n\t\t&__text--primary--plain {\r\n\t\t\tcolor: $uv-primary;\r\n\t\t}\r\n\r\n\t\t&--error {\r\n\t\t\tbackground-color: $uv-error;\r\n\t\t\tborder-width: 1px;\r\n\t\t\tborder-color: $uv-error;\r\n\t\t}\r\n\r\n\t\t&--error--plain {\r\n\t\t\tborder-width: 1px;\r\n\t\t\tborder-color: $uv-error;\r\n\t\t}\r\n\r\n\t\t&--error--plain--fill {\r\n\t\t\tbackground-color: #fef0f0;\r\n\t\t}\r\n\r\n\t\t&__text--error {\r\n\t\t\tcolor: #FFFFFF;\r\n\t\t}\r\n\r\n\t\t&__text--error--plain {\r\n\t\t\tcolor: $uv-error;\r\n\t\t}\r\n\r\n\t\t&--warning {\r\n\t\t\tbackground-color: $uv-warning;\r\n\t\t\tborder-width: 1px;\r\n\t\t\tborder-color: $uv-warning;\r\n\t\t}\r\n\r\n\t\t&--warning--plain {\r\n\t\t\tborder-width: 1px;\r\n\t\t\tborder-color: $uv-warning;\r\n\t\t}\r\n\r\n\t\t&--warning--plain--fill {\r\n\t\t\tbackground-color: #fdf6ec;\r\n\t\t}\r\n\r\n\t\t&__text--warning {\r\n\t\t\tcolor: #FFFFFF;\r\n\t\t}\r\n\r\n\t\t&__text--warning--plain {\r\n\t\t\tcolor: $uv-warning;\r\n\t\t}\r\n\r\n\t\t&--success {\r\n\t\t\tbackground-color: $uv-success;\r\n\t\t\tborder-width: 1px;\r\n\t\t\tborder-color: $uv-success;\r\n\t\t}\r\n\r\n\t\t&--success--plain {\r\n\t\t\tborder-width: 1px;\r\n\t\t\tborder-color: $uv-success;\r\n\t\t}\r\n\r\n\t\t&--success--plain--fill {\r\n\t\t\tbackground-color: #f5fff0;\r\n\t\t}\r\n\r\n\t\t&__text--success {\r\n\t\t\tcolor: #FFFFFF;\r\n\t\t}\r\n\r\n\t\t&__text--success--plain {\r\n\t\t\tcolor: $uv-success;\r\n\t\t}\r\n\r\n\t\t&--info {\r\n\t\t\tbackground-color: $uv-info;\r\n\t\t\tborder-width: 1px;\r\n\t\t\tborder-color: $uv-info;\r\n\t\t}\r\n\r\n\t\t&--info--plain {\r\n\t\t\tborder-width: 1px;\r\n\t\t\tborder-color: $uv-info;\r\n\t\t}\r\n\r\n\t\t&--info--plain--fill {\r\n\t\t\tbackground-color: #f4f4f5;\r\n\t\t}\r\n\r\n\t\t&__text--info {\r\n\t\t\tcolor: #FFFFFF;\r\n\t\t}\r\n\r\n\t\t&__text--info--plain {\r\n\t\t\tcolor: $uv-info;\r\n\t\t}\r\n\r\n\t\t&__close {\r\n\t\t\tborder-radius: 100px;\r\n\t\t\tbackground-color: #C6C7CB;\r\n\t\t\t@include flex(row);\r\n\t\t\talign-items: center;\r\n\t\t\tjustify-content: center;\r\n\t\t\ttransform: scale(0.6);\r\n\t\t\t&--right-top {\r\n\t\t\t\tposition: absolute;\r\n\t\t\t\tz-index: 999;\r\n\t\t\t\ttop: 10px;\r\n\t\t\t\tright: 10px;\r\n\t\t\t\t/* #ifndef APP-NVUE */\r\n\t\t\t\ttransform: scale(0.6) translate(80%,-80%);\r\n\t\t\t\t/* #endif */\r\n\t\t\t\t/* #ifdef APP-NVUE */\r\n\t\t\t\ttransform: scale(0.6) translate(50%, -50%);\r\n\t\t\t\t/* #endif */\r\n\t\t\t}\r\n\t\t\t&--mini {\r\n\t\t\t\twidth: 18px;\r\n\t\t\t\theight: 18px;\r\n\t\t\t}\r\n\r\n\t\t\t&--medium {\r\n\t\t\t\twidth: 22px;\r\n\t\t\t\theight: 22px;\r\n\t\t\t}\r\n\r\n\t\t\t&--large {\r\n\t\t\t\twidth: 25px;\r\n\t\t\t\theight: 25px;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-tags/package.json",
    "content": "{\r\n  \"id\": \"uv-tags\",\r\n  \"displayName\": \"uv-tags 标签  全面兼容小程序、nvue、vue2、vue3等多端\",\r\n  \"version\": \"1.0.3\",\r\n  \"description\": \"tag组件一般用于标记和选择，我们提供了更加丰富的表现形式，能够较全面的涵盖您的使用场景。\",\r\n  \"keywords\": [\r\n    \"uv-tags\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"tag\",\r\n    \"标签\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-transition\",\r\n\t\t\t\"uv-icon\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-tags/readme.md",
    "content": "## Tag 标签\r\n\r\n> **组件名：uv-tags**\r\n\r\ntag组件一般用于标记和选择，我们提供了更加丰富的表现形式，能够较全面的涵盖您的使用场景。\r\n\r\n### <a href=\"https://www.uvui.cn/components/tag.html\" target=\"_blank\">查看文档</a>\r\n\r\n### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n#### 如使用过程中有任何问题，或者您对uv-ui有一些好的建议，欢迎加入 uv-ui 交流群：<a href=\"https://ext.dcloud.net.cn/plugin?id=12287\" target=\"_blank\">uv-ui</a>、<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-text/changelog.md",
    "content": "## 1.0.4（2023-08-29）\n1. nvue修复设置align不生效的BUG\n## 1.0.3（2023-08-24）\r\n1. 修复在nvue不能换行的问题\r\n## 1.0.2（2023-05-24）\r\n1. 去掉多余的data-index属性，避免警告\r\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-text 文本组件\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-text/components/uv-text/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 主题颜色\r\n\t\ttype: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 是否显示\r\n\t\tshow: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 显示的值\r\n\t\ttext: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 前置图标\r\n\t\tprefixIcon: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 后置图标\r\n\t\tsuffixIcon: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 文本处理的匹配模式\r\n\t\t// text-普通文本，price-价格，phone-手机号，name-姓名，date-日期，link-超链接\r\n\t\tmode: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// mode=link下，配置的链接\r\n\t\thref: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 格式化规则\r\n\t\tformat: {\r\n\t\t\ttype: [String, Function],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// mode=phone时，点击文本是否拨打电话\r\n\t\tcall: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 小程序的打开方式\r\n\t\topenType: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 是否粗体，默认normal\r\n\t\tbold: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否块状\r\n\t\tblock: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 文本显示的行数，如果设置，超出此行数，将会显示省略号\r\n\t\tlines: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 文本颜色\r\n\t\tcolor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#303133'\r\n\t\t},\r\n\t\t// 字体大小\r\n\t\tsize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 15\r\n\t\t},\r\n\t\t// 图标的样式\r\n\t\ticonStyle: {\r\n\t\t\ttype: [Object, String],\r\n\t\t\tdefault: () => ({\r\n\t\t\t\tfontSize: '15px'\r\n\t\t\t})\r\n\t\t},\r\n\t\t// 文字装饰，下划线，中划线等，可选值 none|underline|line-through\r\n\t\tdecoration: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'none'\r\n\t\t},\r\n\t\t// 外边距，对象、字符串，数值形式均可\r\n\t\tmargin: {\r\n\t\t\ttype: [Object, String, Number],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 文本行高\r\n\t\tlineHeight: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 文本对齐方式，可选值left|center|right\r\n\t\talign: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'left'\r\n\t\t},\r\n\t\t// 文字换行，可选值break-word|normal|anywhere\r\n\t\twordWrap: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'normal'\r\n\t\t},\r\n\t\t...uni.$uv?.props?.text\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-text/components/uv-text/uv-text.vue",
    "content": "<template>\r\n    <view\r\n      class=\"uv-text\"\r\n      :class=\"[]\"\r\n      v-if=\"show\"\r\n      :style=\"{\r\n        margin: margin,\r\n\t\t\t\tjustifyContent: align === 'left' ? 'flex-start' : align === 'center' ? 'center' : 'flex-end'\r\n      }\"\r\n      @tap=\"clickHandler\"\r\n    >\r\n      <text\r\n        :class=\"['uv-text__price', type && `uv-text__value--${type}`]\"\r\n        v-if=\"mode === 'price'\"\r\n        :style=\"[valueStyle]\"\r\n        >￥</text\r\n      >\r\n      <view class=\"uv-text__prefix-icon\" v-if=\"prefixIcon\">\r\n        <uv-icon\r\n          :name=\"prefixIcon\"\r\n          :customStyle=\"$uv.addStyle(iconStyle)\"\r\n        ></uv-icon>\r\n      </view>\r\n      <uv-link\r\n        v-if=\"mode === 'link'\"\r\n        :text=\"value\"\r\n        :href=\"href\"\r\n        underLine\r\n      ></uv-link>\r\n      <template v-else-if=\"openType && isMp\">\r\n        <button\r\n          class=\"uv-reset-button uv-text__value\"\r\n          :style=\"[valueStyle]\"\r\n          :openType=\"openType\"\r\n          @getuserinfo=\"onGetUserInfo\"\r\n          @contact=\"onContact\"\r\n          @getphonenumber=\"onGetPhoneNumber\"\r\n          @error=\"onError\"\r\n          @launchapp=\"onLaunchApp\"\r\n          @opensetting=\"onOpenSetting\"\r\n          :lang=\"lang\"\r\n          :session-from=\"sessionFrom\"\r\n          :send-message-title=\"sendMessageTitle\"\r\n          :send-message-path=\"sendMessagePath\"\r\n          :send-message-img=\"sendMessageImg\"\r\n          :show-message-card=\"showMessageCard\"\r\n          :app-parameter=\"appParameter\"\r\n        >\r\n          {{ value }}\r\n        </button>\r\n      </template>\r\n      <text\r\n        v-else\r\n        class=\"uv-text__value\"\r\n        :style=\"[valueStyle]\"\r\n        :class=\"[\r\n          type && `uv-text__value--${type}`,\r\n          lines && `uv-line-${lines}`\r\n        ]\"\r\n        >{{ value }}</text\r\n      >\r\n      <view class=\"uv-text__suffix-icon\" v-if=\"suffixIcon\">\r\n        <uv-icon\r\n          :name=\"suffixIcon\"\r\n          :customStyle=\"$uv.addStyle(iconStyle)\"\r\n        ></uv-icon>\r\n      </view>\r\n    </view>\r\n</template>\r\n<script>\r\n\timport value from './value.js'\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport button from '@/uni_modules/uv-ui-tools/libs/mixin/button.js'\r\n\timport openType from '@/uni_modules/uv-ui-tools/libs/mixin/openType.js'\r\n\timport props from './props.js'\r\n\t/**\r\n\t * Text 文本\r\n\t * @description 此组件集成了文本类在项目中的常用功能，包括状态，拨打电话，格式化日期，*替换，超链接...等功能。 您大可不必在使用特殊文本时自己定义，text组件几乎涵盖您能使用的大部分场景。\r\n\t * @tutorial https://www.uvui.cn/components/loading.html\r\n\t * @property {String} \t\t\t\t\ttype\t\t主题颜色\r\n\t * @property {Boolean} \t\t\t\t\tshow\t\t是否显示（默认 true ）\r\n\t * @property {String | Number}\t\t\ttext\t\t显示的值\r\n\t * @property {String}\t\t\t\t\tprefixIcon\t前置图标\r\n\t * @property {String} \t\t\t\t\tsuffixIcon\t后置图标\r\n\t * @property {String} \t\t\t\t\tmode\t\t文本处理的匹配模式 text-普通文本，price-价格，phone-手机号，name-姓名，date-日期，link-超链接\r\n\t * @property {String} \t\t\t\t\thref\t\tmode=link下，配置的链接\r\n\t * @property {String | Function} \t\tformat\t\t格式化规则\r\n\t * @property {Boolean} \t\t\t\t\tcall\t\tmode=phone时，点击文本是否拨打电话（默认 false ）\r\n\t * @property {String} \t\t\t\t\topenType\t小程序的打开方式\r\n\t * @property {Boolean} \t\t\t\t\tbold\t\t是否粗体，默认normal（默认 false ）\r\n\t * @property {Boolean} \t\t\t\t\tblock\t\t是否块状（默认 false ）\r\n\t * @property {String | Number} \t\t\tlines\t\t文本显示的行数，如果设置，超出此行数，将会显示省略号\r\n\t * @property {String} \t\t\t\t\tcolor\t\t文本颜色（默认 '#303133' ）\r\n\t * @property {String | Number} \t\t\tsize\t\t字体大小（默认 15 ）\r\n\t * @property {Object | String} \t\t\ticonStyle\t图标的样式 （默认 {fontSize: '15px'} ）\r\n\t * @property {String} \t\t\t\t\tdecoration\t文字装饰，下划线，中划线等，可选值 none|underline|line-through（默认 'none' ）\r\n\t * @property {Object | String | Number}\tmargin\t\t外边距，对象、字符串，数值形式均可（默认 0 ）\r\n\t * @property {String | Number} \t\t\tlineHeight\t文本行高\r\n\t * @property {String} \t\t\t\t\talign\t\t文本对齐方式，可选值left|center|right（默认 'left' ）\r\n\t * @property {String} \t\t\t\t\twordWrap\t文字换行，可选值break-word|normal|anywhere（默认 'normal' ）\r\n\t * @event {Function} click  点击触发事件\r\n\t * @example <uv-text text=\"我用十年青春,赴你最后之约\"></uv-text>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-text',\r\n\t\temits: ['click'],\r\n\t\t// #ifdef MP\r\n\t\tmixins: [mpMixin, mixin, value, button, openType, props],\r\n\t\t// #endif\r\n\t\t// #ifndef MP\r\n\t\tmixins: [mpMixin, mixin, value, props],\r\n\t\t// #endif\r\n\t\tcomputed: {\r\n\t\t\tvalueStyle() {\r\n\t\t\t\tconst style = {\r\n\t\t\t\t\ttextDecoration: this.decoration,\r\n\t\t\t\t\tfontWeight: this.bold ? 'bold' : 'normal',\r\n\t\t\t\t\twordWrap: this.wordWrap,\r\n\t\t\t\t\tfontSize: this.$uv.addUnit(this.size)\r\n\t\t\t\t};\r\n\t\t\t\t!this.type && (style.color = this.color);\r\n\t\t\t\tthis.isNvue && this.lines && (style.lines = this.lines);\r\n\t\t\t\tif(this.isNvue && this.mode != 'price' && !this.prefixIcon && !this.suffixIcon) {\r\n\t\t\t\t\t style.flex = 1;\r\n\t\t\t\t\t style.textAlign = this.align === 'left' ? 'flex-start' : this.align === 'center' ? 'center' : 'right';\r\n\t\t\t\t}\r\n\t\t\t\tthis.lineHeight && (style.lineHeight = this.$uv.addUnit(this.lineHeight));\r\n\t\t\t\t!this.isNvue && this.block && (style.display = 'block');\r\n\t\t\t\treturn this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle));\r\n\t\t\t},\r\n\t\t\tisNvue() {\r\n\t\t\t\tlet nvue = false\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\tnvue = true\r\n\t\t\t\t// #endif\r\n\t\t\t\treturn nvue\r\n\t\t\t},\r\n\t\t\tisMp() {\r\n\t\t\t\tlet mp = false\r\n\t\t\t\t// #ifdef MP\r\n\t\t\t\tmp = true\r\n\t\t\t\t// #endif\r\n\t\t\t\treturn mp\r\n\t\t\t}\r\n\t\t},\r\n\t\tdata() {\r\n\t\t\treturn {}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tclickHandler() {\r\n\t\t\t\t// 如果为手机号模式，拨打电话\r\n\t\t\t\tif (this.call && this.mode === 'phone') {\r\n\t\t\t\t\tuni.makePhoneCall({\r\n\t\t\t\t\t\tphoneNumber: this.text\r\n\t\t\t\t\t})\r\n\t\t\t\t}\r\n\t\t\t\tthis.$emit('click')\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n<style lang=\"scss\" scoped>\r\n\t$show-lines: 1;\r\n\t$show-reset-button: 1;\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t.uv-text {\r\n\t\t@include flex(row);\r\n\t\talign-items: center;\r\n\t\tflex-wrap: nowrap;\r\n\t\tflex: 1;\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\twidth: 100%;\r\n\t\t/* #endif */\r\n\t\t&__price {\r\n\t\t\tfont-size: 14px;\r\n\t\t\tcolor: $uv-content-color;\r\n\t\t}\r\n\t\t&__value {\r\n\t\t\tfont-size: 14px;\r\n\t\t\t@include flex;\r\n\t\t\tcolor: $uv-content-color;\r\n\t\t\tflex-wrap: wrap;\r\n\t\t\t// flex: 1;\r\n\t\t\ttext-overflow: ellipsis;\r\n\t\t\talign-items: center;\r\n\t\t\t&--primary {\r\n\t\t\t\tcolor: $uv-primary;\r\n\t\t\t}\r\n\t\t\t&--warning {\r\n\t\t\t\tcolor: $uv-warning;\r\n\t\t\t}\r\n\t\t\t&--success {\r\n\t\t\t\tcolor: $uv-success;\r\n\t\t\t}\r\n\t\t\t&--info {\r\n\t\t\t\tcolor: $uv-info;\r\n\t\t\t}\r\n\t\t\t&--error {\r\n\t\t\t\tcolor: $uv-error;\r\n\t\t\t}\r\n\t\t\t&--main {\r\n\t\t\t\tcolor: $uv-main-color;\r\n\t\t\t}\r\n\t\t\t&--content {\r\n\t\t\t\tcolor: $uv-content-color;\r\n\t\t\t}\r\n\t\t\t&--tips {\r\n\t\t\t\tcolor: $uv-tips-color;\r\n\t\t\t}\r\n\t\t\t&--light {\r\n\t\t\t\tcolor: $uv-light-color;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</style>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-text/components/uv-text/value.js",
    "content": "import { func, date, url } from '@/uni_modules/uv-ui-tools/libs/function/test.js'\r\nimport { error, timeFormat, priceFormat } from '@/uni_modules/uv-ui-tools/libs/function/index.js'\r\nexport default {\r\n    computed: {\r\n        // 经处理后需要显示的值\r\n        value() {\r\n            const {\r\n                text,\r\n                mode,\r\n                format,\r\n                href\r\n            } = this\r\n            // 价格类型\r\n            if (mode === 'price') {\r\n                // 如果text不为金额进行提示\r\n                if (!/^\\d+(\\.\\d+)?$/.test(text)) {\r\n                    error('金额模式下，text参数需要为金额格式');\r\n                }\r\n                // 进行格式化，判断用户传入的format参数为正则，或者函数，如果没有传入format，则使用默认的金额格式化处理\r\n                if (func(format)) {\r\n                    // 如果用户传入的是函数，使用函数格式化\r\n                    return format(text)\r\n                }\r\n                // 如果format非正则，非函数，则使用默认的金额格式化方法进行操作\r\n                return priceFormat(text, 2)\r\n            } if (mode === 'date') {\r\n                // 判断是否合法的日期或者时间戳\r\n                !date(text) && error('日期模式下，text参数需要为日期或时间戳格式')\r\n                // 进行格式化，判断用户传入的format参数为正则，或者函数，如果没有传入format，则使用默认的格式化处理\r\n                if (func(format)) {\r\n                    // 如果用户传入的是函数，使用函数格式化\r\n                    return format(text)\r\n                } if (format) {\r\n                    // 如果format非正则，非函数，则使用默认的时间格式化方法进行操作\r\n                    return timeFormat(text, format)\r\n                }\r\n                // 如果没有设置format，则设置为默认的时间格式化形式\r\n                return timeFormat(text, 'yyyy-mm-dd')\r\n            } if (mode === 'phone') {\r\n                // 判断是否合法的手机号\r\n                // !mobile(text) && error('手机号模式下，text参数需要为手机号码格式')\r\n                if (func(format)) {\r\n                    // 如果用户传入的是函数，使用函数格式化\r\n                    return format(text)\r\n                } if (format === 'encrypt') {\r\n                    // 如果format为encrypt，则将手机号进行星号加密处理\r\n                    return `${text.substr(0, 3)}****${text.substr(7)}`\r\n                }\r\n                return text\r\n            } if (mode === 'name') {\r\n                // 判断是否合法的字符粗\r\n                !(typeof (text) === 'string') && error('姓名模式下，text参数需要为字符串格式')\r\n                if (func(format)) {\r\n                    // 如果用户传入的是函数，使用函数格式化\r\n                    return format(text)\r\n                } if (format === 'encrypt') {\r\n                    // 如果format为encrypt，则将姓名进行星号加密处理\r\n                    return this.formatName(text)\r\n                }\r\n                return text\r\n            } if (mode === 'link') {\r\n                // 判断是否合法的字符粗\r\n                !url(href) && error('超链接模式下，href参数需要为URL格式')\r\n                return text\r\n            }\r\n            return text\r\n        }\r\n    },\r\n    methods: {\r\n        // 默认的姓名脱敏规则\r\n        formatName(name) {\r\n            let value = ''\r\n            if (name.length === 2) {\r\n                value = name.substr(0, 1) + '*'\r\n            } else if (name.length > 2) {\r\n                let char = ''\r\n                for (let i = 0, len = name.length - 2; i < len; i++) {\r\n                    char += '*'\r\n                }\r\n                value = name.substr(0, 1) + char + name.substr(-1, 1)\r\n            } else {\r\n                value = name\r\n            }\r\n            return value\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-text/package.json",
    "content": "{\r\n  \"id\": \"uv-text\",\r\n  \"displayName\": \"uv-text 文本 全面兼容vue3+2、app、h5、小程序等多端\",\r\n  \"version\": \"1.0.4\",\r\n  \"description\": \"此组件集成了文本类在项目中的常用功能，包括状态，拨打电话，格式化日期，*替换，超链接...等功能。 您大可不必在使用特殊文本时自己定义，text组件涵盖您能使用的大部分场景。\",\r\n  \"keywords\": [\r\n    \"uv-text\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"text\",\r\n    \"文本\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-icon\",\r\n\t\t\t\"uv-link\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-text/readme.md",
    "content": "## Text 文本\r\n\r\n> **组件名：uv-text**\r\n\r\n此组件集成了文本类在项目中的常用功能，包括状态，拨打电话，格式化日期，*替换，超链接...等功能。 您大可不必在使用特殊文本时自己定义，text组件涵盖您能使用的大部分场景。\r\n\r\n# <a href=\"https://www.uvui.cn/components/text.html\" target=\"_blank\">查看文档</a>\r\n\r\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n<a href=\"https://ext.dcloud.net.cn/plugin?name=uv-ui\" target=\"_blank\">\r\n\r\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\r\n\r\n</a>\r\n\r\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-textarea/changelog.md",
    "content": "## 1.0.10（2023-09-13）\n1. 修复设置autoHeight后出现高度异常的BUG\n## 1.0.9（2023-08-08）\r\n1. 优化\r\n## 1.0.8（2023-08-07）\r\n1. 修复值为null或undefined时显示错误的bug\r\n## 1.0.7（2023-08-05）\r\n1. v-model设置为数据时的BUG\r\n2. 复制过多内容，计数显示错误的BUG\r\n3. maxlength为-1改成不显示计数\r\n## 1.0.6（2023-07-30）\r\n1. 增加confirm-hold参数，参考官方文档\r\n## 1.0.5（2023-07-25）\r\n1. 增加textStyle属性，自定义文本样式\r\n2. 增加countStyle属性，自定义统计数字的样式\r\n## 1.0.4（2023-07-14）\r\n1. 修复  设置maxlength为-1时不生效的BUG\r\n## 1.0.3（2023-07-13）\r\n1. 修复  uv-textarea设置value属性不生效的BUG \r\n## 1.0.2（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.1（2023-05-12）\r\n1. 修复vue3中双向绑定问题\r\n## 1.0.0（2023-05-10）\r\nuv-textarea 文本域\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-textarea/components/uv-textarea/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\tvalue: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\tmodelValue: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 输入框为空时占位符\r\n\t\tplaceholder: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 指定placeholder的样式类，注意页面或组件的style中写了scoped时，需要在类名前写/deep/\r\n\t\tplaceholderClass: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'textarea-placeholder'\r\n\t\t},\r\n\t\t// 指定placeholder的样式\r\n\t\tplaceholderStyle: {\r\n\t\t\ttype: [String, Object],\r\n\t\t\tdefault: 'color: #c0c4cc'\r\n\t\t},\r\n\t\t// 输入框高度\r\n\t\theight: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 70\r\n\t\t},\r\n\t\t// 设置键盘右下角按钮的文字，仅微信小程序，App-vue和H5有效\r\n\t\tconfirmType: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'done'\r\n\t\t},\r\n\t\t// 是否禁用\r\n\t\tdisabled: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否显示统计字数\r\n\t\tcount: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否自动获取焦点，nvue不支持，H5取决于浏览器的实现\r\n\t\tfocus: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否自动增加高度\r\n\t\tautoHeight: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 如果textarea是在一个position:fixed的区域，需要显示指定属性fixed为true\r\n\t\tfixed: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 指定光标与键盘的距离\r\n\t\tcursorSpacing: {\r\n\t\t\ttype: Number,\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 指定focus时的光标位置\r\n\t\tcursor: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 是否显示键盘上方带有”完成“按钮那一栏，\r\n\t\tshowConfirmBar: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 光标起始位置，自动聚焦时有效，需与selection-end搭配使用\r\n\t\tselectionStart: {\r\n\t\t\ttype: Number,\r\n\t\t\tdefault: -1\r\n\t\t},\r\n\t\t// 光标结束位置，自动聚焦时有效，需与selection-start搭配使用\r\n\t\tselectionEnd: {\r\n\t\t\ttype: Number,\r\n\t\t\tdefault: -1\r\n\t\t},\r\n\t\t// 键盘弹起时，是否自动上推页面\r\n\t\tadjustPosition: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 是否去掉 iOS 下的默认内边距，只微信小程序有效\r\n\t\tdisableDefaultPadding: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// focus时，点击页面的时候不收起键盘，只微信小程序有效\r\n\t\tholdKeyboard: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 最大输入长度，设置为 -1 的时候不限制最大长度\r\n\t\tmaxlength: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 140\r\n\t\t},\r\n\t\t// 边框类型，surround-四周边框，bottom-底部边框\r\n\t\tborder: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'surround'\r\n\t\t},\r\n\t\t// 用于处理或者过滤输入框内容的方法\r\n\t\tformatter: {\r\n\t\t\ttype: [Function, null],\r\n\t\t\tdefault: null\r\n\t\t},\r\n\t\t// 是否忽略组件内对文本合成系统事件的处理\r\n\t\tignoreCompositionEvent: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 是否忽略组件内对文本合成系统事件的处理\r\n\t\tconfirmHold: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 文本样式\r\n\t\ttextStyle: {\r\n\t\t\ttype: [Object, String],\r\n\t\t\tdefault: () => {}\r\n\t\t},\r\n\t\t// 统计数字的样式\r\n\t\tcountStyle: {\r\n\t\t\ttype: [Object, String],\r\n\t\t\tdefault: () => {}\r\n\t\t},\r\n\t\t...uni.$uv?.props?.textarea\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-textarea/components/uv-textarea/uv-textarea.vue",
    "content": "<template>\r\n\t<view class=\"uv-textarea\"\r\n\t\t:class=\"textareaClass\"\r\n\t\t:style=\"[textareaStyle]\">\r\n\t\t<textarea class=\"uv-textarea__field\"\r\n\t\t\t:value=\"innerValue\"\r\n\t\t\t:style=\"[\r\n\t\t\t\t{height: autoHeight ? 'auto' :$uv.addUnit(height)},\r\n\t\t\t\t$uv.addStyle(textStyle)\r\n\t\t\t]\"\r\n\t\t\t:placeholder=\"placeholder\"\r\n\t\t\t:placeholder-style=\"$uv.addStyle(placeholderStyle, 'string')\"\r\n\t\t\t:placeholder-class=\"placeholderClass\"\r\n\t\t\t:disabled=\"disabled\"\r\n\t\t\t:focus=\"focus\"\r\n\t\t\t:autoHeight=\"autoHeight\"\r\n\t\t\t:fixed=\"fixed\"\r\n\t\t\t:cursorSpacing=\"cursorSpacing\"\r\n\t\t\t:cursor=\"cursor\"\r\n\t\t\t:showConfirmBar=\"showConfirmBar\"\r\n\t\t\t:selectionStart=\"selectionStart\"\r\n\t\t\t:selectionEnd=\"selectionEnd\"\r\n\t\t\t:adjustPosition=\"adjustPosition\"\r\n\t\t\t:disableDefaultPadding=\"disableDefaultPadding\"\r\n\t\t\t:holdKeyboard=\"holdKeyboard\"\r\n\t\t\t:maxlength=\"maxlen\"\r\n\t\t\t:confirmType=\"confirmType\"\r\n\t\t\t:ignoreCompositionEvent=\"ignoreCompositionEvent\"\r\n\t\t\t:confirm-hold=\"confirmHold\"\r\n\t\t\t@focus=\"onFocus\"\r\n\t\t\t@blur=\"onBlur\"\r\n\t\t\t@linechange=\"onLinechange\"\r\n\t\t\t@input=\"onInput\"\r\n\t\t\t@confirm=\"onConfirm\"\r\n\t\t\t@keyboardheightchange=\"onKeyboardheightchange\"></textarea>\r\n\t\t\t<text class=\"uv-textarea__count\"\r\n\t\t\t:style=\"[{\r\n        'background-color': disabled ? 'transparent' : '#fff',\r\n      },$uv.addStyle(countStyle)]\"\r\n\t\t\tv-if=\"count && maxlen!=-1\">{{ getCount }}/{{ maxlen }}</text>\r\n\t</view>\r\n</template>\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from \"./props.js\";\r\n\t/**\r\n\t * Textarea 文本域\r\n\t * @description 文本域此组件满足了可能出现的表单信息补充，编辑等实际逻辑的功能，内置了字数校验等\r\n\t * @tutorial https://www.uvui.cn/components/textarea.html\r\n\t *\r\n\t * @property {String | Number} \t\tvalue\t/ v-model\t\t\t\t输入框的内容\r\n\t * @property {String | Number}\t\tplaceholder\t\t\t\t输入框为空时占位符\r\n\t * @property {String}\t\t\t    placeholderClass\t\t指定placeholder的样式类，注意页面或组件的style中写了scoped时，需要在类名前写/deep/ （ 默认 'input-placeholder' ）\r\n\t * @property {String | Object}\t    placeholderStyle\t\t指定placeholder的样式，字符串/对象形式，如\"color: red;\"\r\n\t * @property {String | Number}\t\theight\t\t\t\t\t输入框高度（默认 70 ）\r\n\t * @property {String}\t\t\t\tconfirmType\t\t\t\t设置键盘右下角按钮的文字，仅微信小程序，App-vue和H5有效（默认 'done' ）\r\n\t * @property {Boolean}\t\t\t\tdisabled\t\t\t\t是否禁用（默认 false ）\r\n\t * @property {Boolean}\t\t\t\tcount\t\t\t\t\t是否显示统计字数（默认 false ）\r\n\t * @property {Boolean}\t\t\t\tfocus\t\t\t\t\t是否自动获取焦点，nvue不支持，H5取决于浏览器的实现（默认 false ）\r\n\t * @property {Boolean | Function}\tautoHeight\t\t\t\t是否自动增加高度（默认 false ）\r\n\t * @property {Boolean}\t\t\t\tfixed\t\t\t\t\t如果textarea是在一个position:fixed的区域，需要显示指定属性fixed为true（默认 false ）\r\n\t * @property {Number}\t\t\t\tcursorSpacing\t\t\t指定光标与键盘的距离（默认 0 ）\r\n\t * @property {String | Number}\t\tcursor\t\t\t\t\t指定focus时的光标位置\r\n\t * @property {Function}\t\t\t    formatter\t\t\t    内容式化函数\r\n\t * @property {Boolean}\t\t\t\tshowConfirmBar\t\t\t是否显示键盘上方带有”完成“按钮那一栏，（默认 true ）\r\n\t * @property {Number}\t\t\t\tselectionStart\t\t\t光标起始位置，自动聚焦时有效，需与selection-end搭配使用，（默认 -1 ）\r\n\t * @property {Number | Number}\t\tselectionEnd\t\t\t光标结束位置，自动聚焦时有效，需与selection-start搭配使用（默认 -1 ）\r\n\t * @property {Boolean}\t\t\t\tadjustPosition\t\t\t键盘弹起时，是否自动上推页面（默认 true ）\r\n\t * @property {Boolean | Number}\t\tdisableDefaultPadding\t是否去掉 iOS 下的默认内边距，只微信小程序有效（默认 false ）\r\n\t * @property {Boolean}\t\t\t\tholdKeyboard\t\t\tfocus时，点击页面的时候不收起键盘，只微信小程序有效（默认 false ）\r\n\t * @property {String | Number}\t\tmaxlength\t\t\t\t最大输入长度，设置为 -1 的时候不限制最大长度（默认 140 ）\r\n\t * @property {String}\t\t\t\tborder\t\t\t\t\t边框类型，surround-四周边框，none-无边框，bottom-底部边框（默认 'surround' ）\r\n\t * @property {Boolean}\t\t\t\tignoreCompositionEvent\t是否忽略组件内对文本合成系统事件的处理\r\n\t * @property {Boolean} confirmHold 点击键盘右下角按钮时是否保持键盘不收起\r\n\t * @property {Object}\ttextStyle\t文本样式\r\n\t * @property {Object}\tcountStyle\t统计数字的样式\r\n\t *\r\n\t * @event {Function(e)} focus\t\t\t\t\t输入框聚焦时触发，event.detail = { value, height }，height 为键盘高度\r\n\t * @event {Function(e)} blur\t\t\t\t\t输入框失去焦点时触发，event.detail = {value, cursor}\r\n\t * @event {Function(e)} linechange\t\t\t\t输入框行数变化时调用，event.detail = {height: 0, heightRpx: 0, lineCount: 0}\r\n\t * @event {Function(e)} input\t\t\t\t\t当键盘输入时，触发 input 事件\r\n\t * @event {Function(e)} confirm\t\t\t\t\t点击完成时， 触发 confirm 事件\r\n\t * @event {Function(e)} keyboardheightchange\t键盘高度发生变化的时候触发此事件\r\n\t * @example <uv--textarea v-model=\"value1\" placeholder=\"请输入内容\" ></uv--textarea>\r\n\t */\r\n\texport default {\r\n\t\tname: \"uv-textarea\",\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\t// 输入框的值\r\n\t\t\t\tinnerValue: \"\",\r\n\t\t\t\t// 是否处于获得焦点状态\r\n\t\t\t\tfocused: false,\r\n\t\t\t\t// 过滤处理方法\r\n\t\t\t\tinnerFormatter: value => value\r\n\t\t\t}\r\n\t\t},\r\n\t\tcreated() {\r\n\t\t\t// #ifndef VUE3\r\n\t\t\tthis.innerValue = this.value;\r\n\t\t\t// #endif\r\n\t\t\t// #ifdef VUE3\r\n\t\t\tthis.innerValue = this.modelValue;\r\n\t\t\t// #endif\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\tvalue(newVal) {\r\n\t\t\t\tthis.innerValue = newVal;\r\n\t\t\t},\r\n\t\t\tmodelValue(newVal) {\r\n\t\t\t\tthis.innerValue = newVal;\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\t// 组件的类名\r\n\t\t\ttextareaClass() {\r\n\t\t\t\tlet classes = [],\r\n\t\t\t\t\t{ border, disabled } = this;\r\n\t\t\t\tborder === \"surround\" && (classes = classes.concat([\"uv-border\", \"uv-textarea--radius\"]));\r\n\t\t\t\tborder === \"bottom\" && (classes = classes.concat([\"uv-border-bottom\", \"uv-textarea--no-radius\", ]));\r\n\t\t\t\tdisabled && classes.push(\"uv-textarea--disabled\");\r\n\t\t\t\treturn classes.join(\" \");\r\n\t\t\t},\r\n\t\t\t// 组件的样式\r\n\t\t\ttextareaStyle() {\r\n\t\t\t\tconst style = {};\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\t// 由于textarea在安卓nvue上的差异性，需要额外再调整其内边距\r\n\t\t\t\tif (this.$uv.os() === \"android\") {\r\n\t\t\t\t\tstyle.paddingTop = \"6px\";\r\n\t\t\t\t\tstyle.paddingLeft = \"9px\";\r\n\t\t\t\t\tstyle.paddingBottom = \"3px\";\r\n\t\t\t\t\tstyle.paddingRight = \"6px\";\r\n\t\t\t\t}\r\n\t\t\t\t// #endif\r\n\t\t\t\treturn this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle));\r\n\t\t\t},\r\n\t\t\tmaxlen() {\r\n\t\t\t\treturn this.maxlength < 0 ? this.maxlength < 0 ? -1 : 140 : this.maxlength;\r\n\t\t\t},\r\n\t\t\tgetCount() {\r\n\t\t\t\ttry{\r\n\t\t\t\t\treturn this.innerValue.length > this.maxlen ? this.maxlen: this.innerValue.length;\r\n\t\t\t\t}catch(e){\r\n\t\t\t\t\treturn 0;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\t// 在微信小程序中，不支持将函数当做props参数，故只能通过ref形式调用\r\n\t\t\tsetFormatter(e) {\r\n\t\t\t\tthis.innerFormatter = e\r\n\t\t\t},\r\n\t\t\tonFocus(e) {\r\n\t\t\t\tthis.$emit(\"focus\", e);\r\n\t\t\t},\r\n\t\t\tonBlur(e) {\r\n\t\t\t\tthis.$emit(\"blur\", e);\r\n\t\t\t\t// 尝试调用uv-form的验证方法\r\n\t\t\t\tthis.$uv.formValidate(this, \"blur\");\r\n\t\t\t},\r\n\t\t\tonLinechange(e) {\r\n\t\t\t\tthis.$emit(\"linechange\", e);\r\n\t\t\t},\r\n\t\t\tonInput(e) {\r\n\t\t\t\tlet { value = \"\" } = e.detail || {};\r\n\t\t\t\t// 格式化过滤方法\r\n\t\t\t\tconst formatter = this.formatter || this.innerFormatter\r\n\t\t\t\tconst formatValue = formatter(value)\r\n\t\t\t\t// 为了避免props的单向数据流特性，需要先将innerValue值设置为当前值，再在$nextTick中重新赋予设置后的值才有效\r\n\t\t\t\tthis.innerValue = value\r\n\t\t\t\tthis.$nextTick(() => {\r\n\t\t\t\t\tthis.innerValue = formatValue;\r\n\t\t\t\t\tthis.valueChange();\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\t// 内容发生变化，进行处理\r\n\t\t\tvalueChange() {\r\n\t\t\t\tconst value = this.innerValue;\r\n\t\t\t\tthis.$nextTick(() => {\r\n\t\t\t\t\tthis.$emit(\"input\", value);\r\n\t\t\t\t\tthis.$emit(\"update:modelValue\", value);\r\n\t\t\t\t\tthis.$emit(\"change\", value);\r\n\t\t\t\t\t// 尝试调用uv-form的验证方法\r\n\t\t\t\t\tthis.$uv.formValidate(this, \"change\");\r\n\t\t\t\t});\r\n\t\t\t},\r\n\t\t\tonConfirm(e) {\r\n\t\t\t\tthis.$emit(\"confirm\", e);\r\n\t\t\t},\r\n\t\t\tonKeyboardheightchange(e) {\r\n\t\t\t\tthis.$emit(\"keyboardheightchange\", e);\r\n\t\t\t},\r\n\t\t},\r\n\t};\r\n</script>\r\n<style lang=\"scss\" scoped>\r\n\t$show-border: 1;\r\n\t$show-border-surround: 1;\r\n\t$show-border-bottom: 1;\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t.uv-textarea {\r\n\t\tborder-radius: 4px;\r\n\t\tbackground-color: #fff;\r\n\t\tposition: relative;\r\n\t\t@include flex;\r\n\t\tflex: 1;\r\n\t\tpadding: 9px;\r\n\t\t&--radius {\r\n\t\t\tborder-radius: 4px;\r\n\t\t}\r\n\t\t&--no-radius {\r\n\t\t\tborder-radius: 0;\r\n\t\t}\r\n\t\t&--disabled {\r\n\t\t\tbackground-color: #f5f7fa;\r\n\t\t}\r\n\t\t&__field {\r\n\t\t\tflex: 1;\r\n\t\t\tfont-size: 15px;\r\n\t\t\tcolor: $uv-content-color;\r\n\t\t\twidth: 100%;\r\n\t\t}\r\n\t\t&__count {\r\n\t\t\tposition: absolute;\r\n\t\t\tright: 5px;\r\n\t\t\tbottom: 2px;\r\n\t\t\tfont-size: 12px;\r\n\t\t\tcolor: $uv-tips-color;\r\n\t\t\tbackground-color: #ffffff;\r\n\t\t\tpadding: 1px 4px;\r\n\t\t}\r\n\t}\r\n</style>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-textarea/package.json",
    "content": "{\r\n  \"id\": \"uv-textarea\",\r\n  \"displayName\": \"uv-textarea 文本域 全面兼容vue3+2、app、h5、小程序等多端\",\r\n  \"version\": \"1.0.10\",\r\n  \"description\": \"文本域此组件满足了可能出现的表单信息补充，编辑等实际逻辑的功能，内置了字数校验等。\",\r\n  \"keywords\": [\r\n    \"uv-textarea\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"textarea\",\r\n    \"文本域\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-textarea/readme.md",
    "content": "## Textarea 文本域\r\n\r\n> **组件名：uv-textarea**\r\n\r\n文本域此组件满足了可能出现的表单信息补充，编辑等实际逻辑的功能，内置了字数校验等。\r\n\r\n# <a href=\"https://www.uvui.cn/components/textarea.html\" target=\"_blank\">查看文档</a>\r\n\r\n## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>（请不要 下载插件ZIP）</small>\r\n\r\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n<a href=\"https://ext.dcloud.net.cn/plugin?name=uv-ui\" target=\"_blank\">\r\n\r\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\r\n\r\n</a>\r\n\r\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-toast/changelog.md",
    "content": "## 1.0.2（2023-10-13）\n1. unmounted兼容vue3\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-toast 消息提示\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-toast/components/uv-toast/uv-toast.vue",
    "content": "<template>\r\n\t<view class=\"uv-toast\">\r\n\t\t<uv-overlay\r\n\t\t\t:show=\"isShow\"\r\n\t\t\t:custom-style=\"overlayStyle\"\r\n\t\t>\r\n\t\t\t<view\r\n\t\t\t\tclass=\"uv-toast__content\"\r\n\t\t\t\t:style=\"[contentStyle]\"\r\n\t\t\t\t:class=\"['uv-type-' + tmpConfig.type, (tmpConfig.type === 'loading' || tmpConfig.loading) ?  'uv-toast__content--loading' : '']\"\r\n\t\t\t>\r\n\t\t\t\t<uv-loading-icon\r\n\t\t\t\t\tv-if=\"tmpConfig.type === 'loading'\"\r\n\t\t\t\t\tmode=\"circle\"\r\n\t\t\t\t\tcolor=\"rgb(255, 255, 255)\"\r\n\t\t\t\t\tinactiveColor=\"rgb(120, 120, 120)\"\r\n\t\t\t\t\tsize=\"25\"\r\n\t\t\t\t></uv-loading-icon>\r\n\t\t\t\t<uv-icon\r\n\t\t\t\t\tv-else-if=\"tmpConfig.type !== 'defalut' && iconName\"\r\n\t\t\t\t\t:name=\"iconName\"\r\n\t\t\t\t\tsize=\"17\"\r\n\t\t\t\t\t:color=\"tmpConfig.type\"\r\n\t\t\t\t\t:customStyle=\"iconStyle\"\r\n\t\t\t\t></uv-icon>\r\n\t\t\t\t<uv-gap\r\n\t\t\t\t\tv-if=\"tmpConfig.type === 'loading' || tmpConfig.loading\"\r\n\t\t\t\t\theight=\"12\"\r\n\t\t\t\t\tbgColor=\"transparent\"\r\n\t\t\t\t></uv-gap>\r\n\t\t\t\t<text\r\n\t\t\t\t\tclass=\"uv-toast__content__text\"\r\n\t\t\t\t\t:class=\"['uv-toast__content__text--' + tmpConfig.type]\"\r\n\t\t\t\t\tstyle=\"max-width: 400rpx;\"\r\n\t\t\t\t>{{ tmpConfig.message }}</text>\r\n\t\t\t</view>\r\n\t\t</uv-overlay>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport { hexToRgb } from '@/uni_modules/uv-ui-tools/libs/function/colorGradient.js'\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\t/**\r\n\t * toast 消息提示\r\n\t * @description 此组件表现形式类似uni的uni.showToastAPI，但也有不同的地方。\r\n\t * @tutorial https://www.uvui.cn/components/toast.html\r\n\t * @property {String | Number}\tzIndex\t\ttoast展示时的zIndex值 (默认 10090 )\r\n\t * @property {Boolean}\t\t\tloading\t\t是否加载中 （默认 false ）\r\n\t * @property {String | Number}\tmessage\t\t显示的文字内容\r\n\t * @property {String}\t\t\ticon\t\t图标，或者绝对路径的图片\r\n\t * @property {String}\t\t\ttype\t\t主题类型 （默认 default）\r\n\t * @property {Boolean}\t\t\tshow\t\t是否显示该组件 （默认 false）\r\n\t * @property {Boolean}\t\t\toverlay\t\t是否显示透明遮罩，防止点击穿透 （默认 false ）\r\n\t * @property {String}\t\t\tposition\t位置 （默认 'center' ）\r\n\t * @property {Object}\t\t\tparams\t\t跳转的参数 \r\n\t * @property {String | Number}  duration\t展示时间，单位ms （默认 2000 ）\r\n\t * @property {Boolean}\t\t\tisTab\t\t是否返回的为tab页面 （默认 false ）\r\n\t * @property {String}\t\t\turl\t\t\ttoast消失后是否跳转页面，有则跳转，优先级高于back参数 \r\n\t * @property {Function}\t\t\tcomplete\t执行完后的回调函数 \r\n\t * @property {Boolean}\t\t\tback\t\t结束toast是否自动返回上一页 （默认 false ）\r\n\t * @property {Object}\t\t\tcustomStyle\t组件的样式，对象形式\r\n\t * @event {Function} show 显示toast，如需一进入页面就显示toast，请在onReady生命周期调用\r\n\t * @example <uv-toast ref=\"uToast\" />\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-toast',\r\n\t\tmixins: [mpMixin, mixin],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tisShow: false,\r\n\t\t\t\ttimer: null, // 定时器\r\n\t\t\t\tconfig: {\r\n\t\t\t\t\tmessage: '', // 显示文本\r\n\t\t\t\t\ttype: '', // 主题类型，primary，success，error，warning，black\r\n\t\t\t\t\tduration: 2000, // 显示的时间，毫秒\r\n\t\t\t\t\ticon: true, // 显示的图标\r\n\t\t\t\t\tposition: 'center', // toast出现的位置\r\n\t\t\t\t\tcomplete: null, // 执行完后的回调函数\r\n\t\t\t\t\toverlay: false, // 是否防止触摸穿透\r\n\t\t\t\t\tloading: false, // 是否加载中状态\r\n\t\t\t\t},\r\n\t\t\t\ttmpConfig: {}, // 将用户配置和内置配置合并后的临时配置变量\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\ticonName() {\r\n\t\t\t\t// 只有不为none，并且type为error|warning|succes|info时候，才显示图标\r\n\t\t\t\tif(!this.tmpConfig.icon || this.tmpConfig.icon == 'none') {\r\n\t\t\t\t\treturn '';\r\n\t\t\t\t}\r\n\t\t\t\tif (['error', 'warning', 'success', 'primary'].includes(this.tmpConfig.type)) {\r\n\t\t\t\t\treturn this.$uv.type2icon(this.tmpConfig.type)\r\n\t\t\t\t} else {\r\n\t\t\t\t\treturn ''\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\toverlayStyle() {\r\n\t\t\t\tconst style = {\r\n\t\t\t\t\tjustifyContent: 'center',\r\n\t\t\t\t\talignItems: 'center',\r\n\t\t\t\t\tdisplay: 'flex'\r\n\t\t\t\t}\r\n\t\t\t\t// 将遮罩设置为100%透明度，避免出现灰色背景\r\n\t\t\t\tstyle.backgroundColor = 'rgba(0, 0, 0, 0)'\r\n\t\t\t\treturn style\r\n\t\t\t},\r\n\t\t\ticonStyle() {\r\n\t\t\t\tconst style = {}\r\n\t\t\t\t// 图标需要一个右边距，以跟右边的文字有隔开的距离\r\n\t\t\t\tstyle.marginRight = '4px'\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\t// iOSAPP下，图标有1px的向下偏移，这里进行修正\r\n\t\t\t\tif (this.$uv.os() === 'ios') {\r\n\t\t\t\t\tstyle.marginTop = '-1px'\r\n\t\t\t\t}\r\n\t\t\t\t// #endif\r\n\t\t\t\treturn style\r\n\t\t\t},\r\n\t\t\t// 内容盒子的样式\r\n\t\t\tcontentStyle() {\r\n\t\t\t\tconst windowHeight = this.$uv.sys().windowHeight, style = {}\r\n\t\t\t\tlet value = 0\r\n\t\t\t\t// 根据top和bottom，对Y轴进行窗体高度的百分比偏移\r\n\t\t\t\tif(this.tmpConfig.position === 'top') {\r\n\t\t\t\t\tvalue = - windowHeight * 0.25\r\n\t\t\t\t} else if(this.tmpConfig.position === 'bottom') {\r\n\t\t\t\t\tvalue = windowHeight * 0.25\r\n\t\t\t\t}\r\n\t\t\t\tstyle.transform = `translateY(${value}px)`\r\n\t\t\t\treturn style\r\n\t\t\t}\r\n\t\t},\r\n\t\tcreated() {\r\n\t\t\t// 通过主题的形式调用toast，批量生成方法函数\r\n\t\t\t['primary', 'success', 'error', 'warning', 'default', 'loading'].map(item => {\r\n\t\t\t\tthis[item] = message => this.show({\r\n\t\t\t\t\ttype: item,\r\n\t\t\t\t\tmessage\r\n\t\t\t\t})\r\n\t\t\t})\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\t// 显示toast组件，由父组件通过this.$refs.xxx.show(options)形式调用\r\n\t\t\tshow(options) {\r\n\t\t\t\t// 不将结果合并到this.config变量，避免多次调用uv-toast，前后的配置造成混乱\r\n\t\t\t\tthis.tmpConfig = this.$uv.deepMerge(this.config, options)\r\n\t\t\t\t// 清除定时器\r\n\t\t\t\tthis.clearTimer()\r\n\t\t\t\tthis.isShow = true\r\n\t\t\t\tthis.timer = setTimeout(() => {\r\n\t\t\t\t\t// 倒计时结束，清除定时器，隐藏toast组件\r\n\t\t\t\t\tthis.clearTimer()\r\n\t\t\t\t\t// 判断是否存在callback方法，如果存在就执行\r\n\t\t\t\t\ttypeof(this.tmpConfig.complete) === 'function' && this.tmpConfig.complete()\r\n\t\t\t\t}, this.tmpConfig.duration)\r\n\t\t\t},\r\n\t\t\t// 隐藏toast组件，由父组件通过this.$refs.xxx.hide()形式调用\r\n\t\t\thide() {\r\n\t\t\t\tthis.clearTimer()\r\n\t\t\t},\r\n\t\t\tclearTimer() {\r\n\t\t\t\tthis.isShow = false\r\n\t\t\t\t// 清除定时器\r\n\t\t\t\tclearTimeout(this.timer)\r\n\t\t\t\tthis.timer = null\r\n\t\t\t}\r\n\t\t},\r\n\t\t// #ifdef VUE2\r\n\t\tbeforeDestroy() {\r\n\t\t\tthis.clearTimer()\r\n\t\t},\r\n\t\t// #endif\r\n\t\t// #ifdef VUE3\r\n\t\tunmounted() {\r\n\t\t\tthis.clearTimer()\r\n\t\t}\r\n\t\t// #endif\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t$uv-toast-color:#fff !default;\r\n\t$uv-toast-border-radius:4px !default;\r\n\t$uv-toast-border-background-color:#585858 !default;\r\n\t$uv-toast-border-font-size:14px !default;\r\n\t$uv-toast-border-padding:12px 20px !default;\r\n\t$uv-toast-loading-border-padding: 20px 20px !default;\r\n\t$uv-toast-content-text-color:#fff !default;\r\n\t$uv-toast-content-text-font-size:15px !default;\r\n\t$uv-toast-uv-icon:10rpx !default;\r\n\t$uv-toast-uv-type-primary-color:$uv-primary !default;\r\n\t$uv-toast-uv-type-primary-background-color:#ecf5ff !default;\r\n\t$uv-toast-uv-type-primary-border-color:rgb(215, 234, 254) !default;\r\n\t$uv-toast-uv-type-primary-border-width:1px !default;\r\n\t$uv-toast-uv-type-success-color: $uv-success !default;\r\n\t$uv-toast-uv-type-success-background-color: #dbf1e1 !default;\r\n\t$uv-toast-uv-type-success-border-color: #BEF5C8 !default;\r\n\t$uv-toast-uv-type-success-border-width: 1px !default;\r\n\t$uv-toast-uv-type-error-color:$uv-error !default;\r\n\t$uv-toast-uv-type-error-background-color:#fef0f0 !default;\r\n\t$uv-toast-uv-type-error-border-color:#fde2e2 !default;\r\n\t$uv-toast-uv-type-error-border-width: 1px !default;\r\n\t$uv-toast-uv-type-warning-color:$uv-warning !default;\r\n\t$uv-toast-uv-type-warning-background-color:#fdf6ec !default;\r\n\t$uv-toast-uv-type-warning-border-color:#faecd8 !default;\r\n\t$uv-toast-uv-type-warning-border-width: 1px !default;\r\n\t$uv-toast-uv-type-default-color:#fff !default;\r\n\t$uv-toast-uv-type-default-background-color:#585858 !default;\r\n\r\n\t.uv-toast {\r\n\t\t&__content {\r\n\t\t\t@include flex;\r\n\t\t\tpadding: $uv-toast-border-padding;\r\n\t\t\tborder-radius: $uv-toast-border-radius;\r\n\t\t\tbackground-color: $uv-toast-border-background-color;\r\n\t\t\tcolor: $uv-toast-color;\r\n\t\t\talign-items: center;\r\n\t\t\t/* #ifndef APP-NVUE */\r\n\t\t\tmax-width: 600rpx;\r\n\t\t\t/* #endif */\r\n\t\t\tposition: relative;\r\n\r\n\t\t\t&--loading {\r\n\t\t\t\tflex-direction: column;\r\n\t\t\t\tpadding: $uv-toast-loading-border-padding;\r\n\t\t\t}\r\n\r\n\t\t\t&__text {\r\n\t\t\t\tcolor: $uv-toast-content-text-color;\r\n\t\t\t\tfont-size: $uv-toast-content-text-font-size;\r\n\t\t\t\tline-height: $uv-toast-content-text-font-size;\r\n\r\n\t\t\t\t&--default {\r\n\t\t\t\t\tcolor: $uv-toast-content-text-color;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t&--error {\r\n\t\t\t\t\tcolor: $uv-error;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t&--primary {\r\n\t\t\t\t\tcolor: $uv-primary;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t&--success {\r\n\t\t\t\t\tcolor: $uv-success;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t&--warning {\r\n\t\t\t\t\tcolor: $uv-warning;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t.uv-type-primary {\r\n\t\tcolor: $uv-toast-uv-type-primary-color;\r\n\t\tbackground-color: $uv-toast-uv-type-primary-background-color;\r\n\t\tborder-color: $uv-toast-uv-type-primary-border-color;\r\n\t\tborder-width: $uv-toast-uv-type-primary-border-width;\r\n\t}\r\n\r\n\t.uv-type-success {\r\n\t\tcolor: $uv-toast-uv-type-success-color;\r\n\t\tbackground-color: $uv-toast-uv-type-success-background-color;\r\n\t\tborder-color: $uv-toast-uv-type-success-border-color;\r\n\t\tborder-width: 1px;\r\n\t}\r\n\r\n\t.uv-type-error {\r\n\t\tcolor: $uv-toast-uv-type-error-color;\r\n\t\tbackground-color: $uv-toast-uv-type-error-background-color;\r\n\t\tborder-color: $uv-toast-uv-type-error-border-color;\r\n\t\tborder-width: $uv-toast-uv-type-error-border-width;\r\n\t}\r\n\r\n\t.uv-type-warning {\r\n\t\tcolor: $uv-toast-uv-type-warning-color;\r\n\t\tbackground-color: $uv-toast-uv-type-warning-background-color;\r\n\t\tborder-color: $uv-toast-uv-type-warning-border-color;\r\n\t\tborder-width: 1px;\r\n\t}\r\n\r\n\t.uv-type-default {\r\n\t\tcolor: $uv-toast-uv-type-default-color;\r\n\t\tbackground-color: $uv-toast-uv-type-default-background-color;\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-toast/package.json",
    "content": "{\r\n  \"id\": \"uv-toast\",\r\n  \"displayName\": \"uv-toast 消息提示  全面兼容小程序、nvue、vue2、vue3等多端\",\r\n  \"version\": \"1.0.2\",\r\n  \"description\": \"Toast 组件主要用于消息通知、加载提示、操作结果提示等醒目提示效果，我们为其提供了多种丰富的API。\",\r\n  \"keywords\": [\r\n    \"uv-toast\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"toast\",\r\n    \"消息提示\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-overlay\",\r\n\t\t\t\"uv-loading-icon\",\r\n\t\t\t\"uv-gap\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-toast/readme.md",
    "content": "## Toast 消息提示\r\n\r\n> **组件名：uv-toast**\r\n\r\nToast 组件主要用于消息通知、加载提示、操作结果提示等醒目提示效果，我们为其提供了多种丰富的API。\r\n\r\n### <a href=\"https://www.uvui.cn/components/toast.html\" target=\"_blank\">查看文档</a>\r\n\r\n### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n#### 如使用过程中有任何问题，或者您对uv-ui有一些好的建议，欢迎加入 uv-ui 交流群：<a href=\"https://ext.dcloud.net.cn/plugin?id=12287\" target=\"_blank\">uv-ui</a>、<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-toolbar/changelog.md",
    "content": "## 1.0.0（2023-08-02）\n1. 新增工具条组件\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-toolbar/components/uv-toolbar/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 是否展示工具条\r\n\t\tshow: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 是否显示下边框\r\n\t\tshowBorder: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 取消按钮的文字\r\n\t\tcancelText: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '取消'\r\n\t\t},\r\n\t\t// 确认按钮的文字\r\n\t\tconfirmText: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '确认'\r\n\t\t},\r\n\t\t// 取消按钮的颜色\r\n\t\tcancelColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#909193'\r\n\t\t},\r\n\t\t// 确认按钮的颜色\r\n\t\tconfirmColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#3c9cff'\r\n\t\t},\r\n\t\t// 标题文字\r\n\t\ttitle: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t...uni.$uv?.props?.toolbar\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-toolbar/components/uv-toolbar/uv-toolbar.vue",
    "content": "<template>\r\n\t<view\r\n\t\t:class=\"['uv-toolbar',{'uv-border-bottom':showBorder}]\"\r\n\t\t@touchmove.stop.prevent=\"noop\"\r\n\t\tv-if=\"show\"\r\n\t>\r\n\t\t<view\r\n\t\t\tclass=\"uv-toolbar__cancel__wrapper\"\r\n\t\t\thover-class=\"uv-hover-class\"\r\n\t\t>\r\n\t\t\t<text\r\n\t\t\t\tclass=\"uv-toolbar__wrapper__cancel\"\r\n\t\t\t\t@tap=\"cancel\"\r\n\t\t\t\t:style=\"{\r\n\t\t\t\t\tcolor: cancelColor\r\n\t\t\t\t}\"\r\n\t\t\t>{{ cancelText }}</text>\r\n\t\t</view>\r\n\t\t<text\r\n\t\t\tclass=\"uv-toolbar__title uv-line-1\"\r\n\t\t\tv-if=\"title\"\r\n\t\t>{{ title }}</text>\r\n\t\t<view\r\n\t\t\tclass=\"uv-toolbar__confirm__wrapper\"\r\n\t\t\thover-class=\"uv-hover-class\"\r\n\t\t>\r\n\t\t\t<text\r\n\t\t\t\tclass=\"uv-toolbar__wrapper__confirm\"\r\n\t\t\t\t@tap=\"confirm\"\r\n\t\t\t\t:style=\"{\r\n\t\t\t\tcolor: confirmColor\r\n\t\t\t}\"\r\n\t\t\t>{{ confirmText }}</text>\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * Toolbar 工具条\r\n\t * @description \r\n\t * @tutorial https://www.uvui.cn/components/toolbar.html\r\n\t * @property {Boolean}\tshow\t\t\t是否展示工具条（默认 true ）\r\n\t * @property {Boolean}\tshowBorder\t\t\t是否展示工具条下方边框（默认 false ）\r\n\t * @property {String}\tcancelText\t\t取消按钮的文字（默认 '取消' ）\r\n\t * @property {String}\tconfirmText\t\t确认按钮的文字（默认 '确认' ）\r\n\t * @property {String}\tcancelColor\t\t取消按钮的颜色（默认 '#909193' ）\r\n\t * @property {String}\tconfirmColor\t确认按钮的颜色（默认 '#3c9cff' ）\r\n\t * @property {String}\ttitle\t标题文字\r\n\t * @event {Function} \r\n\t * @example \r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-toolbar',\r\n\t\temits: ['confirm', 'cancel'],\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tmethods: {\r\n\t\t\t// 点击取消按钮\r\n\t\t\tcancel() {\r\n\t\t\t\tthis.$emit('cancel')\r\n\t\t\t},\r\n\t\t\t// 点击确定按钮\r\n\t\t\tconfirm() {\r\n\t\t\t\tthis.$emit('confirm')\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t$show-lines: 1;\r\n\t$show-hover: 1;\r\n\t$show-border: 1;\r\n\t$show-border-bottom: 1;\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t$uv-tips-color: #909193 !default;\r\n\t$uv-main-color: #303133 !default;\r\n\t$uv-primary: #3c9cff !default;\r\n\t.uv-toolbar {\r\n\t\theight: 42px;\r\n\t\t@include flex;\r\n\t\tjustify-content: space-between;\r\n\t\talign-items: center;\r\n\t\t&__wrapper {\r\n\t\t\t&__cancel {\r\n\t\t\t\tcolor: $uv-tips-color;\r\n\t\t\t\tfont-size: 15px;\r\n\t\t\t\tpadding: 0 15px;\r\n\t\t\t}\r\n\t\t}\r\n\t\t&__title {\r\n\t\t\tcolor: $uv-main-color;\r\n\t\t\tpadding: 0 60rpx;\r\n\t\t\tfont-size: 16px;\r\n\t\t\tflex: 1;\r\n\t\t\ttext-align: center;\r\n\t\t}\r\n\t\t&__wrapper {\r\n\t\t\t&__confirm {\r\n\t\t\t\tcolor: $uv-primary;\r\n\t\t\t\tfont-size: 15px;\r\n\t\t\t\tpadding: 0 15px;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</style>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-toolbar/package.json",
    "content": "{\n  \"id\": \"uv-toolbar\",\n  \"displayName\": \"uv-toolbar 工具条\",\n  \"version\": \"1.0.0\",\n  \"description\": \"该组价是仅用于uv-ui中一个公共小工具，提供一个取消和确定的样式，可以设置标题，主要用于弹窗顶部的选择确定工具条\",\n  \"keywords\": [\n    \"uv-toolbar\",\n    \"uvui\",\n    \"uv-ui\",\n    \"工具条\",\n    \"工具\"\n],\n  \"repository\": \"\",\n  \"engines\": {\n    \"HBuilderX\": \"^3.1.0\"\n  },\n  \"dcloudext\": {\n    \"type\": \"component-vue\",\n    \"sale\": {\n      \"regular\": {\n        \"price\": \"0.00\"\n      },\n      \"sourcecode\": {\n        \"price\": \"0.00\"\n      }\n    },\n    \"contact\": {\n      \"qq\": \"\"\n    },\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\n    \"npmurl\": \"\"\n  },\n  \"uni_modules\": {\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\"\r\n\t\t],\n    \"encrypt\": [],\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\n  }\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-toolbar/readme.md",
    "content": "## Toolbar 工具条\n\n> **组件名：uv-toolbar**\n\n该组价是仅用于uv-ui中一个公共小工具，提供一个取消和确定的样式，可以设置标题，主要用于弹窗顶部的选择确定工具条。\n\n### 基本使用\n\n```vue\n<uv-toolbar title=\"标题文字\"></uv-toolbar>\n```\n\n### Toolbar Props\n\n| 属性名 | 类型 | 默认值 | 说明 |\n|:-|:-|:-|:-|\n| show | Boolean | true | 是否展示工具条 |\n| showBorder | Boolean | false | 是否显示下边框 |\n| cancelText | String | '取消' | 取消按钮的文字 |\n| confirmText | String | '确定' | 确定按钮的文字 |\n| cancelColor | String | '#909193' | 取消按钮的颜色 |\n| confirmColor | String | '#3c9cff' | 确认按钮的颜色 |\n| title | String | - | 标题文字 |\n\n## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui)\n\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\n\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\n\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-tooltip/changelog.md",
    "content": "## 1.0.5（2023-07-02）\n修改VUE3模式不显示的BUG\n## 1.0.4（2023-07-02）\nuv-tooltip  由于弹出层uv-transition的修改，组件内部做了相应的修改，参数不变。\n## 1.0.3（2023-05-17）\r\n1. 修复报错的BUG\r\n## 1.0.2（2023-05-17）\r\n1. vue2模式下报错的BUG修复\r\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-tooltip 长按提示\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-tooltip/components/uv-tooltip/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 需要显示的提示文字\r\n\t\ttext: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 点击复制按钮时，复制的文本，为空则使用text值\r\n\t\tcopyText: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 文本大小\r\n\t\tsize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 14\r\n\t\t},\r\n\t\t// 字体颜色\r\n\t\tcolor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#606266'\r\n\t\t},\r\n\t\t// 弹出提示框时，文本的背景色\r\n\t\tbgColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'transparent'\r\n\t\t},\r\n\t\t// 弹出提示的方向，top-上方，bottom-下方\r\n\t\tdirection: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'top'\r\n\t\t},\r\n\t\t// 弹出提示的z-index，nvue无效\r\n\t\tzIndex: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 10071\r\n\t\t},\r\n\t\t// 是否显示复制按钮\r\n\t\tshowCopy: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 扩展的按钮组\r\n\t\tbuttons: {\r\n\t\t\ttype: Array,\r\n\t\t\tdefault: () => []\r\n\t\t},\r\n\t\t// 是否显示透明遮罩以防止触摸穿透\r\n\t\toverlay: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 是否显示复制成功或者失败的toast\r\n\t\tshowToast: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t...uni.$uv?.props?.tooltip\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-tooltip/components/uv-tooltip/uv-tooltip.vue",
    "content": "<template>\r\n\t<view\r\n\t\tclass=\"uv-tooltip\"\r\n\t\t:style=\"[$uv.addStyle(customStyle)]\"\r\n\t>\r\n\t\t<uv-overlay\r\n\t\t\t:show=\"showTooltip && tooltipTop !== -10000 && overlay && timeout>0\"\r\n\t\t\tcustomStyle=\"backgroundColor: rgba(0, 0, 0, 0)\"\r\n\t\t\t@click=\"overlayClickHandler\"\r\n\t\t></uv-overlay>\r\n\t\t<view class=\"uv-tooltip__wrapper\">\r\n\t\t\t<template v-if=\"isShow\">\r\n\t\t\t\t<text\r\n\t\t\t\t\tclass=\"uv-tooltip__wrapper__text\"\r\n\t\t\t\t\t:id=\"textId\"\r\n\t\t\t\t\t:ref=\"textId\"\r\n\t\t\t\t\t:userSelect=\"false\"\r\n\t\t\t\t\t:selectable=\"false\"\r\n\t\t\t\t\t@click.stop=\"longpressHandler\"\r\n\t\t\t\t\t:style=\"{\r\n\t\t\t\t\t\tcolor: color,\r\n\t\t\t\t\t\tbackgroundColor: bgColor && showTooltip && tooltipTop !== -10000 ? bgColor : 'transparent'\r\n\t\t\t\t\t}\"\r\n\t\t\t\t>{{ text }}</text>\r\n\t\t\t</template>\r\n\t\t\t<template v-else>\r\n\t\t\t\t<text\r\n\t\t\t\t\tclass=\"uv-tooltip__wrapper__text\"\r\n\t\t\t\t\t:id=\"textId\"\r\n\t\t\t\t\t:ref=\"textId\"\r\n\t\t\t\t\t:userSelect=\"false\"\r\n\t\t\t\t\t:selectable=\"false\"\r\n\t\t\t\t\t@longpress.stop=\"longpressHandler\"\r\n\t\t\t\t\t:style=\"{\r\n\t\t\t\t\t\tcolor: color,\r\n\t\t\t\t\t\tbackgroundColor: bgColor && showTooltip && tooltipTop !== -10000 ? bgColor : 'transparent'\r\n\t\t\t\t\t}\"\r\n\t\t\t\t>{{ text }}</text>\r\n\t\t\t</template>\r\n\t\t\t<uv-transition\r\n\t\t\t\tmode=\"fade\"\r\n\t\t\t\t:show=\"showTooltip\"\r\n\t\t\t\t:duration=\"300\"\r\n\t\t\t\t:customStyle=\"{\r\n\t\t\t\t\tposition: 'absolute', \r\n\t\t\t\t\ttop: $uv.addUnit(tooltipTop),\r\n\t\t\t\t\tzIndex: zIndex,\r\n\t\t\t\t\t...tooltipStyle\r\n\t\t\t\t}\"\r\n\t\t\t>\r\n\t\t\t\t<view\r\n\t\t\t\t\tclass=\"uv-tooltip__wrapper__popup\"\r\n\t\t\t\t\t:id=\"tooltipId\"\r\n\t\t\t\t\t:ref=\"tooltipId\"\r\n\t\t\t\t>\r\n\t\t\t\t\t<view\r\n\t\t\t\t\t\tclass=\"uv-tooltip__wrapper__popup__indicator\"\r\n\t\t\t\t\t\thover-class=\"uv-tooltip__wrapper__popup__indicator--hover\"\r\n\t\t\t\t\t\tv-if=\"showCopy || buttons.length\"\r\n\t\t\t\t\t\t:style=\"[indicatorStyle, {\r\n\t\t\t\t\t\t\twidth: $uv.addUnit(indicatorWidth),\r\n\t\t\t\t\t\t\theight: $uv.addUnit(indicatorWidth),\r\n\t\t\t\t\t\t}]\"\r\n\t\t\t\t\t>\r\n\t\t\t\t\t\t<!-- 由于nvue不支持三角形绘制，这里就做一个四方形，再旋转45deg，得到露出的一个三角 -->\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"uv-tooltip__wrapper__popup__list\">\r\n\t\t\t\t\t\t<view\r\n\t\t\t\t\t\t\tv-if=\"showCopy\"\r\n\t\t\t\t\t\t\tclass=\"uv-tooltip__wrapper__popup__list__btn\"\r\n\t\t\t\t\t\t\thover-class=\"uv-tooltip__wrapper__popup__list__btn--hover\"\r\n\t\t\t\t\t\t\t@tap=\"setClipboardData\"\r\n\t\t\t\t\t\t>\r\n\t\t\t\t\t\t\t<text\r\n\t\t\t\t\t\t\t\tclass=\"uv-tooltip__wrapper__popup__list__btn__text\"\r\n\t\t\t\t\t\t\t>复制</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<uv-line\r\n\t\t\t\t\t\t\tdirection=\"column\"\r\n\t\t\t\t\t\t\tcolor=\"#8d8e90\"\r\n\t\t\t\t\t\t\tv-if=\"showCopy && buttons.length > 0\"\r\n\t\t\t\t\t\t\tlength=\"18\"\r\n\t\t\t\t\t\t></uv-line>\r\n\t\t\t\t\t\t<block v-for=\"(item , index) in buttons\" :key=\"index\">\r\n\t\t\t\t\t\t\t<view\r\n\t\t\t\t\t\t\t\tclass=\"uv-tooltip__wrapper__popup__list__btn\"\r\n\t\t\t\t\t\t\t\thover-class=\"uv-tooltip__wrapper__popup__list__btn--hover\"\r\n\t\t\t\t\t\t\t>\r\n\t\t\t\t\t\t\t\t<text\r\n\t\t\t\t\t\t\t\t\tclass=\"uv-tooltip__wrapper__popup__list__btn__text\"\r\n\t\t\t\t\t\t\t\t\t@tap=\"btnClickHandler(index)\"\r\n\t\t\t\t\t\t\t\t>{{ item }}</text>\r\n\t\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t\t<uv-line\r\n\t\t\t\t\t\t\t\tdirection=\"column\"\r\n\t\t\t\t\t\t\t\tcolor=\"#8d8e90\"\r\n\t\t\t\t\t\t\t\tv-if=\"index < buttons.length - 1\"\r\n\t\t\t\t\t\t\t\tlength=\"18\"\r\n\t\t\t\t\t\t\t></uv-line>\r\n\t\t\t\t\t\t</block>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</uv-transition>\r\n\t\t</view>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t// #ifdef APP-NVUE \r\n\tconst dom = uni.requireNativePlugin('dom')\r\n\t// #endif\r\n\t/**\r\n\t * Tooltip \r\n\t * @description \r\n\t * @tutorial https://www.uvui.cn/components/tooltip.html\r\n\t * @property {String | Number}\ttext\t\t需要显示的提示文字\r\n\t * @property {String | Number}\tcopyText\t点击复制按钮时，复制的文本，为空则使用text值\r\n\t * @property {String | Number}\tsize\t\t文本大小（默认 14 ）\r\n\t * @property {String}\t\t\tcolor\t\t字体颜色（默认 '#606266' ）\r\n\t * @property {String}\t\t\tbgColor\t\t弹出提示框时，文本的背景色（默认 'transparent' ）\r\n\t * @property {String}\t\t\tdirection\t弹出提示的方向，top-上方，bottom-下方（默认 'top' ）\r\n\t * @property {String | Number}\tzIndex\t\t弹出提示的z-index，nvue无效（默认 10071 ）\r\n\t * @property {Boolean}\t\t\tshowCopy\t是否显示复制按钮（默认 true ）\r\n\t * @property {Array}\t\t\tbuttons\t\t扩展的按钮组\r\n\t * @property {Boolean}\t\t\toverlay\t\t是否显示透明遮罩以防止触摸穿透（默认 true ）\r\n\t * @property {Object}\t\t\tcustomStyle\t定义需要用到的外部样式\r\n\t * \r\n\t * @event {Function} \r\n\t * @example \r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-tooltip',\r\n\t\temits: ['click'],\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\t// 是否展示气泡\r\n\t\t\t\tshowTooltip: true,\r\n\t\t\t\t// 生成唯一id，防止一个页面多个组件，造成干扰\r\n\t\t\t\ttextId: '',\r\n\t\t\t\ttooltipId: '',\r\n\t\t\t\t// 初始时甚至为很大的值，让其移到屏幕外面，为了计算元素的尺寸\r\n\t\t\t\ttooltipTop: -10000,\r\n\t\t\t\t// 气泡的位置信息\r\n\t\t\t\ttooltipInfo: {\r\n\t\t\t\t\twidth: 0,\r\n\t\t\t\t\tleft: 0\r\n\t\t\t\t},\r\n\t\t\t\t// 文本的位置信息\r\n\t\t\t\ttextInfo: {\r\n\t\t\t\t\twidth: 0,\r\n\t\t\t\t\tleft: 0\r\n\t\t\t\t},\r\n\t\t\t\t// 三角形指示器的样式\r\n\t\t\t\tindicatorStyle: {},\r\n\t\t\t\t// 气泡在可能超出屏幕边沿范围时，重新定位后，距离屏幕边沿的距离\r\n\t\t\t\tscreenGap: 12,\r\n\t\t\t\t// 三角形指示器的宽高，由于对元素进行了角度旋转，精确计算指示器位置时，需要用到其尺寸信息\r\n\t\t\t\tindicatorWidth: 14,\r\n\t\t\t\ttimeout: 0\r\n\t\t\t}\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\tpropsChange() {\r\n\t\t\t\tthis.getElRect()\r\n\t\t\t},\r\n\t\t\tshowTooltip(newVal){\r\n\t\t\t\tif(!newVal) this.timeout = 0;\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\tisShow(){\r\n\t\t\t\tconst isPC = this.$uv.sys()?.model == 'PC';\r\n\t\t\t\tif(isPC) {\r\n\t\t\t\t\treturn true;\r\n\t\t\t\t}\r\n\t\t\t\t// #ifdef MP-WEIXIN || H5\r\n\t\t\t\treturn false;\r\n\t\t\t\t// #endif\r\n\t\t\t\treturn true;\r\n\t\t\t},\r\n\t\t\t// 特别处理H5的复制，因为H5浏览器是自带系统复制功能的，在H5环境\r\n\t\t\t// 当一些依赖参数变化时，需要重新计算气泡和指示器的位置信息\r\n\t\t\tpropsChange() {\r\n\t\t\t\treturn [this.text, this.buttons]\r\n\t\t\t},\r\n\t\t\t// 计算气泡和指示器的位置信息\r\n\t\t\ttooltipStyle() {\r\n\t\t\t\tconst style = {};\r\n\t\t\t\tif (this.tooltipInfo.width / 2 > this.textInfo.left + this.textInfo.width / 2 - this.screenGap) {\r\n\t\t\t\t\tthis.indicatorStyle = {}\r\n\t\t\t\t\tstyle.left = `-${this.$uv.addUnit(this.textInfo.left - this.screenGap)}`\r\n\t\t\t\t\tthis.indicatorStyle.left = this.$uv.addUnit(this.textInfo.width / 2 - this.$uv.getPx(style.left) - this.indicatorWidth /\r\n\t\t\t\t\t\t2)\r\n\t\t\t\t} else if (this.tooltipInfo.width / 2 > this.$uv.sys().windowWidth - this.textInfo.right + this.textInfo.width / 2 -\r\n\t\t\t\t\tthis.screenGap) {\r\n\t\t\t\t\tthis.indicatorStyle = {}\r\n\t\t\t\t\tstyle.right = `-${this.$uv.addUnit(this.$uv.sys().windowWidth - this.textInfo.right - this.screenGap)}`\r\n\t\t\t\t\tthis.indicatorStyle.right = this.$uv.addUnit(this.textInfo.width / 2 - this.$uv.getPx(style.right) - this\r\n\t\t\t\t\t\t.indicatorWidth / 2)\r\n\t\t\t\t} else {\r\n\t\t\t\t\tconst left = Math.abs(this.textInfo.width / 2 - this.tooltipInfo.width / 2)\r\n\t\t\t\t\tstyle.left = this.textInfo.width > this.tooltipInfo.width ? this.$uv.addUnit(left) : -this.$uv.addUnit(left)\r\n\t\t\t\t\tthis.indicatorStyle = {}\r\n\t\t\t\t}\r\n\t\t\t\tif (this.direction === 'top') {\r\n\t\t\t\t\tstyle.marginTop = `-${10 + this.tooltipInfo.height}px`\r\n\t\t\t\t\tthis.indicatorStyle.bottom = '-4px'\r\n\t\t\t\t} else {\r\n\t\t\t\t\tstyle.marginTop = `${this.tooltipInfo.height-10}px`\r\n\t\t\t\t\tthis.indicatorStyle.top = '-4px'\r\n\t\t\t\t}\r\n\t\t\t\treturn style\r\n\t\t\t}\r\n\t\t},\r\n\t\tcreated() {\r\n\t\t\tthis.textId = this.$uv.guid();\r\n\t\t\tthis.tooltipId = this.$uv.guid();\r\n\t\t},\r\n\t\tmounted() {\r\n\t\t\tthis.init()\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tinit() {\r\n\t\t\t\tthis.getElRect()\r\n\t\t\t},\r\n\t\t\t// 长按触发事件\r\n\t\t\tasync longpressHandler(e) {\r\n\t\t\t\tthis.tooltipTop = 0\r\n\t\t\t\tthis.showTooltip = true\r\n\t\t\t\tsetTimeout(()=>{\r\n\t\t\t\t\tthis.timeout = 1;\r\n\t\t\t\t},300)\r\n\t\t\t},\r\n\t\t\t// 点击透明遮罩\r\n\t\t\toverlayClickHandler() {\r\n\t\t\t\tthis.showTooltip = false\r\n\t\t\t},\r\n\t\t\t// 点击弹出按钮\r\n\t\t\tbtnClickHandler(index) {\r\n\t\t\t\tthis.showTooltip = false\r\n\t\t\t\t// 如果需要展示复制按钮，此处index需要加1，因为复制按钮在第一个位置\r\n\t\t\t\tthis.$emit('click', this.showCopy ? index + 1 : index)\r\n\t\t\t},\r\n\t\t\t// 查询内容高度\r\n\t\t\tqueryRect(ref) {\r\n\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\t// 组件内部一般用this.$uvGetRect，对外的为getRect，二者功能一致，名称不同\r\n\t\t\t\treturn new Promise(resolve => {\r\n\t\t\t\t\tthis.$uvGetRect(`#${ref}`).then(size => {\r\n\t\t\t\t\t\tresolve(size)\r\n\t\t\t\t\t})\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\t// nvue下，使用dom模块查询元素高度\r\n\t\t\t\t// 返回一个promise，让调用此方法的主体能使用then回调\r\n\t\t\t\treturn new Promise(resolve => {\r\n\t\t\t\t\tdom.getComponentRect(this.$refs[ref], res => {\r\n\t\t\t\t\t\tresolve(res.size)\r\n\t\t\t\t\t})\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\t\t\t},\r\n\t\t\t// 元素尺寸\r\n\t\t\tgetElRect() {\r\n\t\t\t\t// 调用之前，先将指示器调整到屏幕外，方便获取尺寸\r\n\t\t\t\tthis.showTooltip = true\r\n\t\t\t\tthis.tooltipTop = -10000\r\n\t\t\t\tthis.$uv.sleep(500).then(() => {\r\n\t\t\t\t\tthis.queryRect(this.tooltipId).then(size => {\r\n\t\t\t\t\t\tthis.tooltipInfo = size\r\n\t\t\t\t\t\t// 获取气泡尺寸之后，将其隐藏，为了让下次切换气泡显示与隐藏时，有淡入淡出的效果\r\n\t\t\t\t\t\tthis.showTooltip = false\r\n\t\t\t\t\t})\r\n\t\t\t\t\tthis.queryRect(this.textId).then(size => {\r\n\t\t\t\t\t\tthis.textInfo = size\r\n\t\t\t\t\t})\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\t// 复制文本到粘贴板\r\n\t\t\tsetClipboardData() {\r\n\t\t\t\t// 关闭组件\r\n\t\t\t\tthis.showTooltip = false\r\n\t\t\t\tthis.$emit('click', 0)\r\n\t\t\t\tuni.setClipboardData({\r\n\t\t\t\t\t// 优先使用copyText字段，如果没有，则默认使用text字段当做复制的内容\r\n\t\t\t\t\tdata: this.copyText || this.text,\r\n\t\t\t\t\tsuccess: () => {\r\n\t\t\t\t\t\tthis.showToast && this.$uv.toast('复制成功')\r\n\t\t\t\t\t},\r\n\t\t\t\t\tfail: () => {\r\n\t\t\t\t\t\tthis.showToast && this.$uv.toast('复制失败')\r\n\t\t\t\t\t},\r\n\t\t\t\t\tcomplete: () => {\r\n\t\t\t\t\t\tthis.showTooltip = false\r\n\t\t\t\t\t}\r\n\t\t\t\t})\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\r\n\t.uv-tooltip {\r\n\t\tposition: relative;\r\n\t\t@include flex;\r\n\r\n\t\t&__wrapper {\r\n\t\t\t@include flex;\r\n\t\t\tjustify-content: center;\r\n\t\t\t/* #ifndef APP-NVUE */\r\n\t\t\twhite-space: nowrap;\r\n\t\t\t/* #endif */\r\n\r\n\t\t\t&__text {\r\n\t\t\t\tfont-size: 14px;\r\n\t\t\t}\r\n\r\n\t\t\t&__popup {\r\n\t\t\t\t@include flex;\r\n\t\t\t\tjustify-content: center;\r\n\r\n\t\t\t\t&__list {\r\n\t\t\t\t\tbackground-color: #060607;\r\n\t\t\t\t\tposition: relative;\r\n\t\t\t\t\tflex: 1;\r\n\t\t\t\t\tborder-radius: 5px;\r\n\t\t\t\t\tpadding: 0px 0;\r\n\t\t\t\t\t@include flex(row);\r\n\t\t\t\t\talign-items: center;\r\n\t\t\t\t\toverflow: hidden;\r\n\r\n\t\t\t\t\t&__btn {\r\n\t\t\t\t\t\tpadding: 11px 13px;\r\n\r\n\t\t\t\t\t\t&--hover {\r\n\t\t\t\t\t\t\tbackground-color: #58595B;\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t&__text {\r\n\t\t\t\t\t\t\tline-height: 12px;\r\n\t\t\t\t\t\t\tfont-size: 13px;\r\n\t\t\t\t\t\t\tcolor: #FFFFFF;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\t&__indicator {\r\n\t\t\t\t\tposition: absolute;\r\n\t\t\t\t\tbackground-color: #060607;\r\n\t\t\t\t\twidth: 14px;\r\n\t\t\t\t\theight: 14px;\r\n\t\t\t\t\tbottom: -4px;\r\n\t\t\t\t\ttransform: rotate(45deg);\r\n\t\t\t\t\tborder-radius: 2px;\r\n\t\t\t\t\tz-index: -1;\r\n\r\n\t\t\t\t\t&--hover {\r\n\t\t\t\t\t\tbackground-color: #58595B;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-tooltip/package.json",
    "content": "{\r\n  \"id\": \"uv-tooltip\",\r\n  \"displayName\": \"uv-tooltip 长按提示\",\r\n  \"version\": \"1.0.5\",\r\n  \"description\": \"Tooltip组件主要用于长按操作，类似微信的长按气泡。\",\r\n  \"keywords\": [\r\n    \"uv-tooltip\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"tooltip\",\r\n    \"长按提示\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-overlay\",\r\n\t\t\t\"uv-transition\",\r\n\t\t\t\"uv-line\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"n\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-tooltip/readme.md",
    "content": "## Tooltip 长按提示\r\n\r\n> **组件名：uv-tooltip**\r\n\r\nTooltip组件主要用于长按操作，类似微信的长按气泡。\r\n\r\n### <a href=\"https://www.uvui.cn/components/tooltip.html\" target=\"_blank\">查看文档</a>\r\n\r\n### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n#### 如使用过程中有任何问题，或者您对uv-ui有一些好的建议，欢迎加入 uv-ui 交流群：<a href=\"https://ext.dcloud.net.cn/plugin?id=12287\" target=\"_blank\">uv-ui</a>、<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-transition/changelog.md",
    "content": "## 1.0.8（2023-10-18）\n1. 修复在APP上不能正常显示的BUG\n## 1.0.7（2023-10-12）\r\n1. 修复部分情况，修改某属性自动关闭的BUG\r\n## 1.0.6（2023-07-24）\r\n1. 优化  nvue模式下增加cellChild参数，是否在list中cell节点下，nvue中cell下建议设置成true\r\n## 1.0.5（2023-07-02）\r\n修改VUE3模式下可能存在的BUG\r\n## 1.0.4（2023-07-02）\r\nuv-transition  动画组件，代码重构优化，性能更加友好，增加自定义动画功能。详情参考文档：https://www.uvui.cn/components/transition.html\r\n## 1.0.3（2023-06-12）\r\n1. 恢复this.$nextTick的使用，经过测试百度等平台无问题\r\n## 1.0.2（2023-05-23）\r\n1. 百度小程序等平台不支持this.$nextick，修改成延时\r\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\n1. 新增动画组件\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-transition/components/uv-transition/createAnimation.js",
    "content": "// const defaultOption = {\r\n// \tduration: 300,\r\n// \ttimingFunction: 'linear',\r\n// \tdelay: 0,\r\n// \ttransformOrigin: '50% 50% 0'\r\n// }\r\n// #ifdef APP-NVUE\r\nconst nvueAnimation = uni.requireNativePlugin('animation')\r\n// #endif\r\nclass MPAnimation {\r\n\tconstructor(options, _this) {\r\n\t\tthis.options = options\r\n\t\t// 在iOS10+QQ小程序平台下，传给原生的对象一定是个普通对象而不是Proxy对象，否则会报parameter should be Object instead of ProxyObject的错误\r\n\t\tthis.animation = uni.createAnimation({\r\n\t\t\t...options\r\n\t\t})\r\n\t\tthis.currentStepAnimates = {}\r\n\t\tthis.next = 0\r\n\t\tthis.$ = _this\r\n\r\n\t}\r\n\r\n\t_nvuePushAnimates(type, args) {\r\n\t\tlet aniObj = this.currentStepAnimates[this.next]\r\n\t\tlet styles = {}\r\n\t\tif (!aniObj) {\r\n\t\t\tstyles = {\r\n\t\t\t\tstyles: {},\r\n\t\t\t\tconfig: {}\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tstyles = aniObj\r\n\t\t}\r\n\t\tif (animateTypes1.includes(type)) {\r\n\t\t\tif (!styles.styles.transform) {\r\n\t\t\t\tstyles.styles.transform = ''\r\n\t\t\t}\r\n\t\t\tlet unit = ''\r\n\t\t\tif(type === 'rotate'){\r\n\t\t\t\tunit = 'deg'\r\n\t\t\t}\r\n\t\t\tstyles.styles.transform += `${type}(${args+unit}) `\r\n\t\t} else {\r\n\t\t\tstyles.styles[type] = `${args}`\r\n\t\t}\r\n\t\tthis.currentStepAnimates[this.next] = styles\r\n\t}\r\n\t_animateRun(styles = {}, config = {}) {\r\n\t\tlet ref = this.$.$refs['ani'].ref\r\n\t\tif (!ref) return\r\n\t\treturn new Promise((resolve, reject) => {\r\n\t\t\tnvueAnimation.transition(ref, {\r\n\t\t\t\tstyles,\r\n\t\t\t\t...config\r\n\t\t\t}, res => {\r\n\t\t\t\tresolve()\r\n\t\t\t})\r\n\t\t})\r\n\t}\r\n\r\n\t_nvueNextAnimate(animates, step = 0, fn) {\r\n\t\tlet obj = animates[step]\r\n\t\tif (obj) {\r\n\t\t\tlet {\r\n\t\t\t\tstyles,\r\n\t\t\t\tconfig\r\n\t\t\t} = obj\r\n\t\t\tthis._animateRun(styles, config).then(() => {\r\n\t\t\t\tstep += 1\r\n\t\t\t\tthis._nvueNextAnimate(animates, step, fn)\r\n\t\t\t})\r\n\t\t} else {\r\n\t\t\tthis.currentStepAnimates = {}\r\n\t\t\ttypeof fn === 'function' && fn()\r\n\t\t\tthis.isEnd = true\r\n\t\t}\r\n\t}\r\n\r\n\tstep(config = {}) {\r\n\t\t// #ifndef APP-NVUE\r\n\t\tthis.animation.step(config)\r\n\t\t// #endif\r\n\t\t// #ifdef APP-NVUE\r\n\t\tthis.currentStepAnimates[this.next].config = Object.assign({}, this.options, config)\r\n\t\tthis.currentStepAnimates[this.next].styles.transformOrigin = this.currentStepAnimates[this.next].config.transformOrigin\r\n\t\tthis.next++\r\n\t\t// #endif\r\n\t\treturn this\r\n\t}\r\n\r\n\trun(fn) {\r\n\t\t// #ifndef APP-NVUE\r\n\t\tthis.$.animationData = this.animation.export()\r\n\t\tthis.$.timer = setTimeout(() => {\r\n\t\t\ttypeof fn === 'function' && fn()\r\n\t\t}, this.$.durationTime)\r\n\t\t// #endif\r\n\t\t// #ifdef APP-NVUE\r\n\t\tthis.isEnd = false\r\n\t\tlet ref = this.$.$refs['ani'] && this.$.$refs['ani'].ref\r\n\t\tif(!ref) return\r\n\t\tthis._nvueNextAnimate(this.currentStepAnimates, 0, fn)\r\n\t\tthis.next = 0\r\n\t\t// #endif\r\n\t}\r\n}\r\n\r\n\r\nconst animateTypes1 = ['matrix', 'matrix3d', 'rotate', 'rotate3d', 'rotateX', 'rotateY', 'rotateZ', 'scale', 'scale3d',\r\n\t'scaleX', 'scaleY', 'scaleZ', 'skew', 'skewX', 'skewY', 'translate', 'translate3d', 'translateX', 'translateY',\r\n\t'translateZ'\r\n]\r\nconst animateTypes2 = ['opacity', 'backgroundColor']\r\nconst animateTypes3 = ['width', 'height', 'left', 'right', 'top', 'bottom']\r\nanimateTypes1.concat(animateTypes2, animateTypes3).forEach(type => {\r\n\tMPAnimation.prototype[type] = function(...args) {\r\n\t\t// #ifndef APP-NVUE\r\n\t\tthis.animation[type](...args)\r\n\t\t// #endif\r\n\t\t// #ifdef APP-NVUE\r\n\t\tthis._nvuePushAnimates(type, args)\r\n\t\t// #endif\r\n\t\treturn this\r\n\t}\r\n})\r\n\r\nexport function createAnimation(option, _this) {\r\n\tif(!_this) return\r\n\tclearTimeout(_this.timer)\r\n\treturn new MPAnimation(option, _this)\r\n}\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-transition/components/uv-transition/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 是否展示组件\r\n\t\tshow: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 使用的动画模式\r\n\t\tmode: {\r\n\t\t\ttype: [Array, String, null],\r\n\t\t\tdefault() {\r\n\t\t\t\treturn 'fade'\r\n\t\t\t}\r\n\t\t},\r\n\t\t// 动画的执行时间，单位ms\r\n\t\tduration: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 300\r\n\t\t},\r\n\t\t// 使用的动画过渡函数\r\n\t\ttimingFunction: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'ease-out'\r\n\t\t},\r\n\t\tcustomClass: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t...uni.$uv?.props?.transition\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-transition/components/uv-transition/uv-transition.vue",
    "content": "<template>\r\n  <!-- #ifndef APP-NVUE -->\r\n  <view \r\n\t\tv-if=\"isShow\" \r\n\t\tref=\"ani\" \r\n\t\t:animation=\"animationData\" \r\n\t\t:class=\"customClass\" \r\n\t\t:style=\"transformStyles\" \r\n\t\t@click=\"onClick\">\r\n\t\t<slot></slot>\r\n\t</view>\r\n  <!-- #endif -->\r\n  <!-- #ifdef APP-NVUE -->\r\n  <view \r\n\t\tv-if=\"isShow\" \r\n\t\tref=\"ani\" \r\n\t\t:animation=\"animationData\" \r\n\t\t:class=\"customClass\" \r\n\t\t:style=\"transformStyles\" \r\n\t\t@click=\"onClick\">\r\n\t\t<slot></slot>\r\n\t</view>\r\n  <!-- #endif -->\r\n</template>\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport { createAnimation } from './createAnimation'\r\n\t/**\r\n\t* transition  动画组件\r\n\t* @description\r\n\t* @tutorial\r\n\t* @property {Boolean}\tshow\t控制组件显示或关闭 （默认 false ）\r\n\t* @property {Array | String\t}\tmode\t内置过渡动画类型 （默认 'fade' ）\r\n\t* @value fade 渐隐渐出过渡\r\n\t* @value slide-top 由上至下过渡\r\n\t* @value slide-bottom 由下至上过渡\r\n\t* @value slide-left 由左至右过渡\r\n\t* @value slide-right 由右至左过渡\r\n\t* @value zoom-in 由小到大过渡\r\n\t* @value zoom-out 由大到小过渡\r\n\t* @property {String | Number}\tduration\t动画的执行时间，单位ms （默认 300 ）\r\n\t* @property {String} timingFunction\t使用的动画过渡函数 （默认 'ease-out' ）\r\n\t* @property {Object} customStyle\t自定义样式\r\n\t* @property {String} customClass\t自定义类名\r\n\t* @event {Function} click 点击组件触发\t\r\n\t* @event {Function} change\t过渡动画结束时触发\t\r\n\t* @example \r\n\t*/\r\n\texport default {\r\n\t\tname: 'uv-transition',\r\n\t\tmixins: [mpMixin,mixin],\r\n\t\temits:['click','change'],\r\n\t\tprops: {\r\n\t\t\t// 是否展示组件\r\n\t\t\tshow: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: false\r\n\t\t\t},\r\n\t\t\t// 使用的动画模式\r\n\t\t\tmode: {\r\n\t\t\t\ttype: [Array, String, null],\r\n\t\t\t\tdefault() {\r\n\t\t\t\t\treturn 'fade'\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t// 动画的执行时间，单位ms\r\n\t\t\tduration: {\r\n\t\t\t\ttype: [String, Number],\r\n\t\t\t\tdefault: 300\r\n\t\t\t},\r\n\t\t\t// 使用的动画过渡函数\r\n\t\t\ttimingFunction: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: 'ease-out'\r\n\t\t\t},\r\n\t\t\tcustomClass: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: ''\r\n\t\t\t},\r\n\t\t\t// nvue模式下 是否直接显示，在uv-list等cell下面使用就需要设置\r\n\t\t\tcellChild: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: false\r\n\t\t\t}\r\n\t\t},\r\n\t\tdata(){\r\n\t\t\treturn {\r\n\t\t\t\tisShow: false,\r\n\t\t\t\ttransform: '',\r\n\t\t\t\topacity: 1,\r\n\t\t\t\tanimationData: {},\r\n\t\t\t\tdurationTime: 300,\r\n\t\t\t\tconfig: {}\r\n\t\t\t}\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\tshow: {\r\n\t\t\t\thandler(newVal) {\r\n\t\t\t\t\tif (newVal) {\r\n\t\t\t\t\t\tthis.open();\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\t// 避免上来就执行 close,导致动画错乱\r\n\t\t\t\t\t\tif (this.isShow) {\r\n\t\t\t\t\t\t\tthis.close();\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t},\r\n\t\t\t\timmediate: true\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\t// 初始化动画条件\r\n\t\t\ttransformStyles() {\r\n\t\t\t\tconst style = {\r\n\t\t\t\t\ttransform: this.transform,\r\n\t\t\t\t\topacity: this.opacity,\r\n\t\t\t\t\t...this.$uv.addStyle(this.customStyle),\r\n\t\t\t\t\t'transition-duration': `${this.duration / 1000}s`\r\n\t\t\t\t};\r\n\t\t\t\treturn this.$uv.addStyle(style,'string');\r\n\t\t\t}\r\n\t\t},\r\n\t\tcreated() {\r\n\t\t\t// 动画默认配置\r\n\t\t\tthis.config = {\r\n\t\t\t\tduration: this.duration,\r\n\t\t\t\ttimingFunction: this.timingFunction,\r\n\t\t\t\ttransformOrigin: '50% 50%',\r\n\t\t\t\tdelay: 0\r\n\t\t\t};\r\n\t\t\tthis.durationTime = this.duration;\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\t/**\r\n\t\t\t *  ref 触发 初始化动画\r\n\t\t\t */\r\n\t\t\tinit(obj = {}) {\r\n\t\t\t\tif (obj.duration) {\r\n\t\t\t\t\tthis.durationTime = obj.duration;\r\n\t\t\t\t}\r\n\t\t\t\tthis.animation = createAnimation(Object.assign(this.config, obj),this);\r\n\t\t\t},\r\n\t\t\t/**\r\n\t\t\t * 点击组件触发回调\r\n\t\t\t */\r\n\t\t\tonClick() {\r\n\t\t\t\tthis.$emit('click', {\r\n\t\t\t\t\tdetail: this.isShow\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\t/**\r\n\t\t\t * ref 触发 动画分组\r\n\t\t\t * @param {Object} obj\r\n\t\t\t */\r\n\t\t\tstep(obj, config = {}) {\r\n\t\t\t\tif (!this.animation) return;\r\n\t\t\t\tfor (let i in obj) {\r\n\t\t\t\t\ttry {\r\n\t\t\t\t\t\tif(typeof obj[i] === 'object'){\r\n\t\t\t\t\t\t\tthis.animation[i](...obj[i]);\r\n\t\t\t\t\t\t}else{\r\n\t\t\t\t\t\t\tthis.animation[i](obj[i]);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t} catch (e) {\r\n\t\t\t\t\t\tconsole.error(`方法 ${i} 不存在`);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tthis.animation.step(config);\r\n\t\t\t\treturn this;\r\n\t\t\t},\r\n\t\t\t/**\r\n\t\t\t *  ref 触发 执行动画\r\n\t\t\t */\r\n\t\t\trun(fn) {\r\n\t\t\t\tif (!this.animation) return;\r\n\t\t\t\tthis.animation.run(fn);\r\n\t\t\t},\r\n\t\t\t// 开始过度动画\r\n\t\t\topen() {\r\n\t\t\t\tclearTimeout(this.timer);\r\n\t\t\t\tthis.transform = '';\r\n\t\t\t\tthis.isShow = true;\r\n\t\t\t\tlet { opacity, transform } = this.styleInit(false);\r\n\t\t\t\tif (typeof opacity !== 'undefined') {\r\n\t\t\t\t\tthis.opacity = opacity;\r\n\t\t\t\t}\r\n\t\t\t\tthis.transform = transform;\r\n\t\t\t\t// 确保动态样式已经生效后，执行动画，如果不加 nextTick ，会导致 wx 动画执行异常\r\n\t\t\t\tthis.$nextTick(() => {\r\n\t\t\t\t\t// TODO 定时器保证动画完全执行，目前有些问题，后面会取消定时器\r\n\t\t\t\t\tthis.timer = setTimeout(() => {\r\n\t\t\t\t\t\tthis.animation = createAnimation(this.config, this);\r\n\t\t\t\t\t\tthis.tranfromInit(false).step();\r\n\t\t\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\t\t\tif(this.cellChild) {\r\n\t\t\t\t\t\t\tthis.opacity = 1;\r\n\t\t\t\t\t\t} else{\r\n\t\t\t\t\t\t\tthis.animation.run();\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t// #endif\r\n\t\t\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\t\t\tthis.animation.run();\r\n\t\t\t\t\t\t// #endif\r\n\t\t\t\t\t\t// #ifdef VUE3\r\n\t\t\t\t\t\t// #ifdef H5\r\n\t\t\t\t\t\tthis.opacity = 1;\r\n\t\t\t\t\t\t// #endif\r\n\t\t\t\t\t\t// #endif\r\n\t\t\t\t\t\tthis.$emit('change', {\r\n\t\t\t\t\t\t\tdetail: this.isShow\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t\t// #ifdef H5\r\n\t\t\t\t\t\t// #ifdef VUE3\r\n\t\t\t\t\t\tthis.transform = '';\r\n\t\t\t\t\t\t// #endif\r\n\t\t\t\t\t\t// #endif\r\n\t\t\t\t\t}, 20);\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\t// 关闭过渡动画\r\n\t\t\tclose(type) {\r\n\t\t\t\tif (!this.animation) return;\r\n\t\t\t\tthis.tranfromInit(true)\r\n\t\t\t\t\t.step()\r\n\t\t\t\t\t.run(() => {\r\n\t\t\t\t\t\tthis.isShow = false;\r\n\t\t\t\t\t\tthis.animationData = null;\r\n\t\t\t\t\t\tthis.animation = null;\r\n\t\t\t\t\t\tlet { opacity, transform } = this.styleInit(false);\r\n\t\t\t\t\t\tthis.opacity = opacity || 1;\r\n\t\t\t\t\t\tthis.transform = transform;\r\n\t\t\t\t\t\tthis.$emit('change', {\r\n\t\t\t\t\t\t\tdetail: this.isShow\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t})\r\n\t\t\t},\r\n\t\t\t// 处理动画开始前的默认样式\r\n\t\t\tstyleInit(type) {\r\n\t\t\t\tlet styles = {\r\n\t\t\t\t\ttransform: ''\r\n\t\t\t\t};\r\n\t\t\t\tlet buildStyle = (type, mode) => {\r\n\t\t\t\t\tif (mode === 'fade') {\r\n\t\t\t\t\t\tstyles.opacity = this.animationType(type)[mode];\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tstyles.transform += this.animationType(type)[mode] + ' ';\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tif (typeof this.mode === 'string') {\r\n\t\t\t\t\tbuildStyle(type, this.mode);\r\n\t\t\t\t} else {\r\n\t\t\t\t\tthis.mode.forEach(mode => {\r\n\t\t\t\t\t\tbuildStyle(type, mode)\r\n\t\t\t\t\t})\r\n\t\t\t\t}\r\n\t\t\t\treturn styles\r\n\t\t\t},\r\n\t\t\t// 处理内置组合动画\r\n\t\t\ttranfromInit(type) {\r\n\t\t\t\tlet buildTranfrom = (type, mode) => {\r\n\t\t\t\t\tlet aniNum = null;\r\n\t\t\t\t\tif (mode === 'fade') {\r\n\t\t\t\t\t\taniNum = type ? 0 : 1;\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\taniNum = type ? '-100%' : '0';\r\n\t\t\t\t\t\tif (mode === 'zoom-in') {\r\n\t\t\t\t\t\t\taniNum = type ? 0.8 : 1\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tif (mode === 'zoom-out') {\r\n\t\t\t\t\t\t\taniNum = type ? 1.2 : 1\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tif (mode === 'slide-right') {\r\n\t\t\t\t\t\t\taniNum = type ? '100%' : '0'\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tif (mode === 'slide-bottom') {\r\n\t\t\t\t\t\t\taniNum = type ? '100%' : '0'\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\tthis.animation[this.animationMode()[mode]](aniNum)\r\n\t\t\t\t}\r\n\t\t\t\tif (typeof this.mode === 'string') {\r\n\t\t\t\t\tbuildTranfrom(type, this.mode)\r\n\t\t\t\t} else {\r\n\t\t\t\t\tthis.mode.forEach(mode => {\r\n\t\t\t\t\t\tbuildTranfrom(type, mode)\r\n\t\t\t\t\t})\r\n\t\t\t\t}\r\n\t\t\t\treturn this.animation;\r\n\t\t\t},\r\n\t\t\tanimationType(type) {\r\n\t\t\t\treturn {\r\n\t\t\t\t\tfade: type ? 1 : 0,\r\n\t\t\t\t\t'slide-top': `translateY(${type ? '0' : '-100%'})`,\r\n\t\t\t\t\t'slide-right': `translateX(${type ? '0' : '100%'})`,\r\n\t\t\t\t\t'slide-bottom': `translateY(${type ? '0' : '100%'})`,\r\n\t\t\t\t\t'slide-left': `translateX(${type ? '0' : '-100%'})`,\r\n\t\t\t\t\t'zoom-in': `scaleX(${type ? 1 : 0.8}) scaleY(${type ? 1 : 0.8})`,\r\n\t\t\t\t\t'zoom-out': `scaleX(${type ? 1 : 1.2}) scaleY(${type ? 1 : 1.2})`\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t// 内置动画类型与实际动画对应字典\r\n\t\t\tanimationMode() {\r\n\t\t\t\treturn {\r\n\t\t\t\t\tfade: 'opacity',\r\n\t\t\t\t\t'slide-top': 'translateY',\r\n\t\t\t\t\t'slide-right': 'translateX',\r\n\t\t\t\t\t'slide-bottom': 'translateY',\r\n\t\t\t\t\t'slide-left': 'translateX',\r\n\t\t\t\t\t'zoom-in': 'scale',\r\n\t\t\t\t\t'zoom-out': 'scale'\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t// 驼峰转中横线\r\n\t\t\ttoLine(name) {\r\n\t\t\t\treturn name.replace(/([A-Z])/g, '-$1').toLowerCase()\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-transition/package.json",
    "content": "{\r\n  \"id\": \"uv-transition\",\r\n  \"displayName\": \"uv-transition 动画 全面兼容vue3+2、app、h5、小程序等多端\",\r\n  \"version\": \"1.0.8\",\r\n  \"description\": \"transition 该组件用于组件的动画过渡效果。\",\r\n  \"keywords\": [\r\n    \"uv-transition\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"transition\",\r\n    \"动画\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-transition/readme.md",
    "content": "## Transition 动画\r\n\r\n> **组件名：uv-transition**\r\n\r\n该组件用于组件的动画过渡效果，支持自定义动画，开箱即用。\r\n\r\n# <a href=\"https://www.uvui.cn/components/transition.html\" target=\"_blank\">查看文档</a>\r\n\r\n## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\r\n\r\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui/changelog.md",
    "content": "## 1.1.15（2023-10-12）\n1. 优化 uv-keyboard a. 增加disKeys参数，mode = \"car\"下，被禁用的键，如：['I','O']； b. 增加customabc参数，mode = \"car\"下，是否启用自定义中英文切换内容模式，为了兼容支付宝等小程序不兼容嵌套插槽，导致同时显示自定义内容和原始内容； c. 增加ref方法changeCarMode，mode = \"car\"下， 调用此方法可以切换中英文模式； d. 增加@changeCarInputMode，mode = \"car\"下，调用此方法可以进行切换中英文； e. 增加插槽abc，mode = \"car\"下，自定义中英文切换内容\n2. 优化 uv-checkbox uv-radio 优化：https://gitee.com/climblee/uv-ui/issues/I872VD\n3. 优化 uv-picker 将immediate-change默认值改为true，该值在于change回调的及时性，微信小程序生效\n4. 优化 uv-tags 兼容customStyle参数等优化\n5. 修复 uv-transition 部分情况，修改某属性自动关闭的BUG\n6. 修复 uv-calendars 懒加载报错：https://gitee.com/climblee/uv-ui/issues/I869JS\n7. 修复 uv-datetime-picker 设置minDate出现选择错乱的BUG\n8. 修复 uv-input 搜狗输入法下存在不可清空的情况\n9. 修复 uv-calendars selected没有设置了info或者info设置为空字符串后，文本则无法恢复BUG\n## 1.1.14（2023-09-27）\r\n1. 优化 uv-list-item 可使用customStyle变量进行样式控制\r\n2. 优化 uv-cell 增加cellStyle参数，方便自定义单元格的样式\r\n3. 优化 uv-switch 优化细节\r\n4. 优化 不断优化[文档](https://www.uvui.cn/)\r\n5. 修复 uv-button 通过customStyle修改按钮宽度，组件中最外层节点不改变的问题\r\n6. 修复 uv-calendars a. 修复range模式下，selected设置了info后选中后，导致文本不恢复的问题；b. 修复multiple模式下，selected自定义信息的颜色没变，依然是白色\r\n7. 修复 uv-checkbox uv-checkbox-group之change回调中v-model值不更新的BUG\r\n## 1.1.13（2023-09-15）\r\n1. 优化 uv-button a. 增加参数iconSize，用于控制图标的大小；b. 增加open-type=\"agreePrivacyAuthorization\"类型，用户同意隐私协议事件回调\r\n2. 优化 uv-picker 三级联动的案例：[https://www.uvui.cn/components/picker.html#省市区三级联动](https://www.uvui.cn/components/picker.html#省市区三级联动)\r\n3. 修复 uv-read-more 全局设置rpx时，导致展开高度不对的BUG\r\n4. 修复 uv-tabs a. 设置lineWidth未带单位产生的误差BUG；b. 首次加载时，处理下划线会有左到右的过渡效果\r\n5. 修复 uv-textaera 设置autoHeight后出现高度异常的BUG\r\n6. 修复 uv-input H5等情况设置禁用或可读情况下，点击事件无效的问题，nvue需要特殊处理\r\n7. 修复 uv-calendars a. 在vue2+小程序渲染时闪烁的问题；b. 增加allowSameDay参数，是否允许日期范围的起止时间为同一天，mode=range时有效\r\n8. 修复 uv-safe-bottom 兼容飞书小程序\r\n9. 修复 uv-album 添加依赖，避免导入运行有误\r\n10. 修复 uv-ui-tools 优化组件用到的相关\r\n## 1.1.12（2023-09-10）\r\n1. 修复 uv-popup a. h5初始zIndex错误的问题；b. 修复全局设置prop无效的问题\r\n2. 修复 uv-button 修复多个按钮由view包裹，显示在一行宽度不正常的BUG\r\n3. 修复 uv-modal a. 修复两个按钮之间竖线不显示的问题；b. uv-ui项目自定义按钮示例修复\r\n4. 修复 uv-calendars 修复国际化失效的BUG\r\n5. 修复 uv-keyboard 修复键盘change回调事件产生冲突的BUG\r\n## 1.1.11（2023-09-02）\r\n1. 优化 uv-calendars a. 去除range参数，由mode=\"range\"替换；b. 新增mode参数，不传 / multiple / range，分别为单日期， 多个日期，选择日期范围；c. 与uv-calendar选择日期的功能保持一致\r\n2. 优化 uv-modal a. 增加align参数，设置文本对齐方式；b. 增加textStyle参数，扩展文本样式\r\n3. 优化 uv-datetime-picker a. 增加mode=\"year\"模式，方便只选择年；b. 增加clearDate参数，是否清除上次选择\r\n4. 修复 uv-ui-tools 设置customstyle同名计算属性报错：The computed property \"customStyle\" is already defined as a prop\r\n5. 修复 uv-image a. 设置widthFix时出现显示不全的BUG；b. 修复抖音等平台在width和height属性改变时出现不显示的BUG\r\n6. 修复 uv-checkbox 点击空隙处或label插槽内容不会选中的问题\r\n7. 修复 uv-radio 点击空隙处或label插槽内容不会选中的问题\r\n8. 修复 uv-calendars 在pages.json中设置easycom会报错的BUG\r\n9. 修复 uv-index-list 设置customNavHeight导致定位不准确的BUG\r\n## 1.1.10（2023-08-30）\r\n1. 交流反馈 欢迎加入uv-ui官方群1交流反馈： 549833913\r\n2. 交流反馈 欢迎加入uv-ui官方群2交流反馈： 206060892\r\n3. 优化 uv-calendars 1. 去除range参数，由mode=\"range\"替换；2. 新增mode参数，不传 / multiple / range，分别为单日期， 多个日期，选择日期范围；3. 与uv-calendar选择日期的功能保持一致\r\n4. 新增 uv-album 新增相册组件及相关文档\r\n5. 优化 其他优化\r\n6. 修复 uv-text app-nvue设置align不生效的BUG\r\n7. 修复 uv-drop-down 自定义内容，点击自定义内容时会自动关闭弹窗的问题\r\n8. 修复 uv-image 异步修改宽高不生效的问题，问题来源：https://gitee.com/climblee/uv-ui/issues/I7WUQ3\r\n9. 修复 uv-calendars 通过setConfig修改属性不生效的问题，出自评论区：https://ext.dcloud.net.cn/plugin?id=12287\r\n10. 修复 uv-list 设置边框不生效的BUG\r\n## 1.1.9（2023-08-27）\r\n1. 优化 uv-calendars 1. 去除range参数，由mode=\"range\"替换；2. 新增mode参数，不传 / multiple / range，分别为单日期， 多个日期，选择日期范围；3. 与uv-calendar选择日期的功能保持一致\r\n2. 优化 uv-picker 增加round属性，设置圆角\r\n3. 修复 uv-calendars 点击返回今天按钮时，monthSwitch方法回调参数返回月份不是当天对应月份\r\n4. 修复 uv-radio 1. 设置 labelSize 属性设置无效的问题：https://gitee.com/climblee/uv-ui/issues/I7W6UN；2. v-model 绑定布尔值控制台报警：https://gitee.com/climblee/uv-ui/issues/I7W714\r\n5. 修复 uv-checkbox 1. 设置 label 属性为布尔值不生效的BUG\r\n## 1.1.8（2023-08-24）\r\n1. 优化 uv-popup 弹出不丝滑优化思路：https://www.uvui.cn/components/popup.html#yh\r\n2. 修复 uv-switch 取消value传值，只能使用v-model传值，避免异步操作不生效的BUG\r\n3. 修复 uv-index-list ios端滚动过程中+快速点击右侧导航页面出现空白的BUG\r\n4. 修复 uv-rate 1. 支付宝报错的BUG; 2. 不能选半星的BUG\r\n5. 修复 uv-model 异步loading时，确认回调还会一直触发的BUG\r\n6. 修复 uv-swiper 标题文字过多未隐藏掉的BUG\r\n7. 修复 uv-text app-nvue编译不能自动换行的BUG\r\n## 1.1.7（2023-08-22）\r\n1. 优化 uv-drop-down a. 增加@change回调，返回弹窗关闭状态； b. 增加init方法，方便位置改变进行调整\r\n2. 优化 部分文档优化\r\n3. 修复 uv-input a. app-nvue-ios端不能输入的BUG；b. 键盘高度等值不返回BUG\r\n4. 修复 uv-scroll-list 报错导致不能移动指示器的BUG\r\n5. 修复 uv-search 边距值在上次更新中误改导致不对的BUG\r\n6. 修复 uv-image 设置width和height为100%不生效的BUG\r\n## 1.1.6（2023-08-18）\r\n1. 优化 优化文档\r\n2. 修复 uv-list 使用列表右侧显示 switch，switchChange回调中返回数据为undefined的BUG\r\n3. 修复 uv-checkbox 数据多不换行的BUG\r\n4. 修复 uv-upload 1. 图片预览位置错误的BUG；2. 视频预览不生效的BUG；3. 改变上传视频宽高不生效的BUG\r\n5. 修复 uv-navbar 在部分ios高版本机型，返回按钮不好操作的问题\r\n6. 修复 uv-waterfall 只有一条数据的时候，切换的时候数据会左右显示错误的BUG\r\n## 1.1.5（2023-08-14）\r\n1. 优化 uv-pick-color 删除scrollTop参数，内部修改后就不需要了\r\n2. 优化 uv-loading-icon 增加textStyle参数，可自定义文本样式，比如给上边距\r\n3. 修复 uv-safe-bottom 百度小程序报错的BUG\r\n4. 修复 uv-form 设置labelWidth属性时，节点渲染有闪动的BUG\r\n5. 修复 uv-grid 设置col属性时，节点渲染有闪动的BUG\r\n6. 修复 uv-parse 阻止a标签跳转文档说明\r\n## 1.1.4（2023-08-13）\r\n1. 优化 nvue自定义图标 [详细文档-nvue中自定义图标库](https://www.uvui.cn/guide/customIcon.html#nvue%E4%B8%AD%E8%87%AA%E5%AE%9A%E4%B9%89%E5%9B%BE%E6%A0%87%E5%BA%93)\r\n2. 优化 uv.$uv.http 在APP.vue页面使用报错的BUG： [Api集中管理](https://www.uvui.cn/js/http.html#_3-api%E9%9B%86%E4%B8%AD%E7%AE%A1%E7%90%86)\r\n3. 修复 uv-navbar app-nvue运行ios存在背景图片错乱的问题\r\n4. 修复 uv-list app-nvue运行ios存在，分包页面不滚动\r\n5. 修复 uv-textarea 值为null或undefined时显示错误的bug\r\n6. 修复 uv-search 值为null或undefined时显示错误的bug\r\n7. 修复 uv-scroll-list vue2编译报错的BUG\r\n8. 修复 uv-calendars 选择月份弹窗层级的问题\r\n9. 修复 uv-form 动画在vue3 setup语法糖中错乱，以及表单其他相关问题解决： [Issues](https://gitee.com/my_dear_li_pan/uv-ui/issues/I7SNTT)\r\n10. 修复 uv-picker-color 滚动页面无法点击的BUG：增加scrollTop参数，设置滚动条的位置。不设置如果页面出现滚动就需要传该值，会出现颜色面板无法进行选颜色的情况。\r\n11. 交流反馈 欢迎加入uv-ui官方群1交流反馈： [549833913](https://www.uvui.cn/components/addQQGroup.html)\r\n12. 交流反馈 欢迎加入uv-ui官方群2交流反馈： [206060892](https://www.uvui.cn/components/addQQGroup.html)\r\n## 1.1.3（2023-08-06）\r\n1. 优化 uv-calendars 1. 增加startText参数; 2. 增加endText参数; 3. 增加selected中的参数; 4. 优化日历范围选择\r\n2. 优化 uv-empty icon属性支持base64图片\r\n3. 优化 uv-navbar 增加背景图片的裁剪模式参数imgMode\r\n4. 优化 uv-picker-color 颜色值不对的BUG\r\n5. 优化 [API文档优化](https://www.uvui.cn/components/changelog.html)\r\n6. 优化 常见问题增加：[怎么隐藏uv-tabs等组件的滚动条](https://www.uvui.cn/components/problem.html#%E4%B9%9D%E3%80%81%E6%80%8E%E4%B9%88%E9%9A%90%E8%97%8Fuv-tabs%E7%AD%89%E7%BB%84%E4%BB%B6%E7%9A%84%E6%BB%9A%E5%8A%A8%E6%9D%A1)\r\n7. 修复 uv-radio name为数字0时不能选中的BUG\r\n8. 修复 uv-textarea 1. v-model设置为数据时的BUG；2. 复制过多内容，计数显示错误的BUG；3. maxlength为-1改成不显示计数\r\n9. 修复 uv-code-input 在vue2模式下，v-model设置为0时不生效的BUG\r\n10. 修复 uv-input 在vue2模式下，v-model设置为0时不生效的BUG\r\n11. 修复 uv-search 在vue2模式下，v-model设置为0时不生效的BUG\r\n12. 修复 uv-ui-tools 1. 路由拦截修复；2. 增加events参数\r\n## 1.1.2（2023-08-03）\r\n1. 新增 uv-calendars 新版日历发布\r\n2. 新增 uv-toolbar 组件独立发布，老用户更新uv-picker，需要手动删除uv-picker目录下的uv-toolbar目录，否则会有冲突提示\r\n3. 优化 uv-tags 增加cellChild参数\r\n4. 优化 uv-navbar 兼容背景图片\r\n5. 优化 uv-notice-bar 竖向滚动时候增加change回调\r\n## 1.1.1（2023-07-30）\r\n1. 新增 uv-drop-down 下拉筛选组件，兼容app-nvue及多端\r\n2. 优化 uv-textarea 增加confirm-hold参数，方便设置进行换行处理\r\n3. 优化 其他关于文档的优化等\r\n## 1.1.0（2023-07-26）\r\n1. 重构 uv-list  全面重构，提高性能，放弃使用scroll-view，具体文档参考：uv-list列表\r\n2. 优化 uv-search 1. 增加prefix和suffix 前置和后置插槽；2. 增加boxStyle参数，方便控制输入框部分的样式\r\n3. 优化 文档优化：获取节点布局信息，文档新增nvue获取方式的说明\r\n## 1.0.22（2023-07-26）\r\n1. 优化 uv-textarea 组件 增加textStyle和countStyle属性，方便控制文本样式\r\n2. 优化 uv-swiper 增加竖向播放属性：vertical\r\n3. 优化 uv-icon 支持base64图片格式\r\n4. 优化 uv-transition 和 uv-image 增加参数cellChild属性，避免nvue中出现回收后不显示的BUG\r\n5. 优化 uv-button 增加customTextStyle属性，方便自定义按钮文字样式\r\n6. 优化 优化部分文档说明\r\n7. 修复 uv-slider 修改背景颜色属性为backgroundColor，避免设置不生效\r\n8. 修复 uv-index-list 1. 修复全局设置成rpx存在的高度BUG；2. 修复其他BUG\r\n## 1.0.21（2023-07-22）\r\n1. 新增 uv-scroll-list 横向滚动列表组件\r\n2. 优化 增加测试占位图，方便开发者使用线上图片进行测试：[https://www.uvui.cn/components/testPic.html](https://www.uvui.cn/components/testPic.html)\r\n3. 优化 uv-calendar 组件文档示例等优化，增加setFormatter说明\r\n4. 优化 uv-notice-bar 优化文档，说明不显示左边图标的使用方法\r\n5. 修复 uv-input 在微信小程序端清除内容存在不能清除的BUG\r\n6. 修复 uv-button 1. 解决微信小程序动态设置hover-class点击态不消失的BUG; 2. 文档优化\r\n7. 修复 uv-waterfall 在tab切换等场景快速切换时，会出现报错的BUG\r\n8. 优化 优化其他\r\n## 1.0.20（2023-07-18）\r\n1. 修复 uv-textarea 设置-1不生效\r\n2. 修复 uv-icon 恢复uv-empty相关的图标\r\n3. 修复 uv-empty 恢复设置mode属性的内置图标\r\n4. 优化 [优化文档](https://www.uvui.cn)\r\n## 1.0.19（2023-07-14）\r\n1. 优化 uv-waterfall 当changeList未处理数据时，正确返回对应列的数据，避免误导\r\n2. 修复 uv-rate VUE3模式下设置value属性不生效的BUG\r\n3. 修复 uv-input VUE3模式下设置value属性不生效的BUG\r\n4. 修复 uv-search VUE3模式下设置value属性不生效的BUG\r\n5. 修复 uv-code-input VUE3模式下设置value属性不生效的BUG\r\n6. 修复 uv-number-box VUE3模式下设置value属性不生效的BUG\r\n7. 修复 uv-radio VUE3模式下设置value属性不生效的BUG\r\n8. 修复 uv-checkbox VUE3模式下设置value属性不生效的BUG\r\n9. 修复 uv-textarea VUE3模式下设置value属性不生效的BUG\r\n10. 修复 uv-switch VUE3模式下设置value属性不生效的BUG\r\n11. 修复 uv-slider VUE3模式下设置value属性不生效的BUG\r\n12. 修复 uv-datetime-picker VUE3模式下设置value属性不生效的BUG\r\n13. 修复 uv-icon 部分图标错误的BUG\r\n## 1.0.18（2023-07-06）\r\n1. 优化 uv-icon 1. 更新图标，删除一些不常用的图标；2. 删除base64，修改成ttf文件引入读取图标。uv-icon 图标\r\n2. 优化 uv-icon nvue自定义图标用法，文档说明：[点击跳转](https://www.uvui.cn/guide/customIcon.html)\r\n3. 优化 uv-upload 文档示例代码，增加fileList参数说明：[点击跳转](https://www.uvui.cn/components/upload.html#filelist-options)\r\n4. 修复 uv-checkbox vue3模式下，动态修改v-model绑定的值无效的BUG\r\n5. 修复 uv-radio vue3模式下，动态修改v-model绑定的值无效的BUG\r\n6. 修复 uv-datetime-picker vue3模式下，动态修改v-model绑定的值无效的BUG\r\n## 1.0.17（2023-07-04）\r\n1. 优化 uv-icon 修复，NVUE平台主题颜色在APP不生效的BUG\r\n2. 优化 uv-notice-bar 优化，增加disableScroll属性\r\n3. 优化 uv-input uv-back-top uv-cell uv-form uv-search uv-modal uv-navbar uv-index-list uv-empty uv-upload 去除插槽判断，避免某些平台不显示的BUG\r\n4. 优化 uv-form 优化文档\r\n5. 优化 优化其他相关文档\r\n## 1.0.16（2023-07-03）\r\n1. 优化 uv-transition 动画组件，代码重构优化，性能更加友好，增加自定义动画功能。详情[参考文档](https://www.uvui.cn/components/transition.html)\r\n2. 优化 uv-popup 弹出层，代码重构优化，性能翻倍，小程序体验性能更加，避免卡顿。打开和关闭方法更改，详情[参考文档](https://www.uvui.cn/components/popup.html)\r\n3. 优化 uv-calendar 由于弹出层uv-popup的修改，打开和关闭方法更改，详情[参考文档](https://www.uvui.cn/components/actionSheet.html)\r\n4. 优化 uv-action-sheet 由于弹出层uv-popup的修改，打开和关闭方法更改，详情[参考文档](https://www.uvui.cn/components/calendar.html)\r\n5. 优化 uv-datetime-picker 由于弹出层uv-popup的修改，打开和关闭方法更改，详情[参考文档](https://www.uvui.cn/components/datetimePicker.html)\r\n6. 优化 uv-form 由于弹出层uv-transition的修改，组件内部做了相应的修改，参数不变。\r\n7. 优化 uv-keyboard 由于弹出层uv-popup的修改，打开和关闭方法更改，详情[参考文档](https://www.uvui.cn/components/keyboard.html)\r\n8. 优化 uv-modal 由于弹出层uv-popup的修改，打开和关闭方法更改，详情[参考文档](https://www.uvui.cn/components/modal.html)\r\n9. 优化 uv-notify 由于弹出层uv-popup的修改，打开和关闭方法更改，详情[参考文档](https://www.uvui.cn/components/notify.html)\r\n10. 优化 uv-overlay 由于弹出层uv-transition的修改，组件内部做了相应的修改，参数不变。\r\n11. 优化 uv-pick-color 由于弹出层uv-popup的修改，打开和关闭方法更改，详情[参考文档](https://www.uvui.cn/components/pickColor.html)\r\n12. 优化 uv-picker 由于弹出层uv-popup的修改，打开和关闭方法更改，详情[参考文档](https://www.uvui.cn/components/picker.html)\r\n13. 优化 uv-tooltip 由于弹出层uv-transition的修改，组件内部做了相应的修改，参数不变。\r\n14. 优化 uv-loading-page 由于弹出层uv-transition的修改，组件内部做了相应的修改，参数不变。\r\n15. 优化 相关文档的优化更改。\r\n16. 修复 uv-safe-bottom 修复，在百度程序，抖音小程序不生效的BUG\r\n## 1.0.15（2023-06-29）\r\n1. 欢迎加QQ群交流：[549833913](https://www.uvui.cn/components/addQQGroup.html)\r\n2. 优化 uv-swiper 优化：1. 增加titleStyle属性，方便修改标题样式；2. 标题上去掉是否是图片的判断，避免无后缀的图片不显示\r\n3. 优化 uv-steps 优化：1. 增加插槽title; 3. 文档关于插槽相关的参数说明完善；增加customStyle属性\r\n4. 优化 uv-checkbox 优化：增加label文字插槽，与radio保持一致，优化文档相关说明\r\n5. 优化 uv-modal 优化：增加closeLoading方法，方便异步加载手动取消加载状态，更新文档\r\n6. 优化 uv-image 增加文档说明：uv-list、 uv-waterfall等组件在 Android平台使用了list封装，所以在该组件中仍然不能使用uv-image等组件\r\n7. 优化 优化更多文档\r\n8. 修复 uv-vtabs 修复非联动情况下，内容过多的情况，滚动一段距离，再切换未滚动到顶部的BUG\r\n9. 修复 uv-image 修复：duration属性不生效的BUG\r\n10. 修复 uv-code-input 修复：使用:disabledKeyboard=\"true\"属性，事件全部失效的BUG\r\n11. 修复 uv-button 修复：设置open-type=\"chooseAvatar\"等值不生效的BUG\r\n## 1.0.14（2023-06-25）\r\n1. 欢迎加QQ群交流：[549833913](https://www.uvui.cn/components/addQQGroup.html)\r\n2. 优化 uv-count-down 增加外部样式customStyle参数\r\n3. 优化 文档的全面优化\r\n4. 修复 uv-count-to 1. 修复继续滚动的函数 2. 修改文档错误 4. 适配px和rpx的单位 4. 适配customStyle参数\r\n5. 修复 uv-load-more 修复customStyle参数设置背景等不生效的BUG\r\n6. 修复 uv-code-input 优化下边框\r\n7. 修复 uv-tabs 添加uv-icon依赖\r\n8. 修复 uv-grid 优化修改\r\n9. 修复 uv-cell 优化修改\r\n## 1.0.13（2023-06-20）\r\n1. 优化 uv-calendar formatter格式化中增加topInfo参数\r\n2. 优化 uv-tabs 增加customStyle参数\r\n3. 优化 文档优化，便于开发者直接开干\r\n4. 优化 uv-switch 优化size属性，适配单位传递\r\n5. 修复 uv-ui-tools、uv-form、uv-picker 修复vue3编译支付宝异常\r\n6. 修复 uv-ui-tools、uv-form、uv-picker 修复vue3编译支付宝异常\r\n7. 修复 uv-parse 修复在nvue不显示的BUG\r\n8. 修复 uv-form 修复某些条件下报错的BUG\r\n## 1.0.12（2023-06-14）\r\n1. 优化部分组件，优化文档部分细节\r\n2. uv-popup、uv-modal 修复遮罩层zIndex问题\r\n3. uv-form 在vue3的setup语法中ref使用uvForm会导致报错\r\n4. uv-tabs activeStyle设置字体大小，可能会导致下划线位置不对BUG\r\n5. uv-pick-color 百度小程序点击报错\r\n6. uv-transition 恢复this.$nextTick\r\n7. uv-picker 抖音小程序选择的时候报错，导致不能关闭的BUG\r\n8. uv-checkbox 多余的属性labelDisabled，导致APP中报错提示\r\n9. uv-tabbar 底部安全距离组件无效的BUG\r\n10. uv-vtabs 头部存在的时候，联动不准确的BUG\r\n## 1.0.11（2023-06-12）\r\n1. uv-radio-group、uv-checkbox-group 兼容自定义样式customStyle，方便通过样式调整整体位置等，数据较多时允许换行\r\n2. uv-ui-tools 优化内置样式等，解决微信小程序使用uvui提示 Some selectors are not allowed in component wxss, including tag name selectors, ID selectors, and attribute selectors，[详情](https://www.uvui.cn/components/problem.html)\r\n3. uv-datetime-picker 取消defaultIndex参数，目前传该值也没实际意义\r\n4. uv-tabbar 增加iconSize参数\r\n5. uv-calendar 增加change回调\r\n6. uv-calendar 修复BUG\r\n7. uv-rate 修复只读或禁止状态下设置value无效的问题\r\n8. uv-popup 修复zIndex问题\r\n9. uv-modal 修复zIndex问题\r\n10. 文档-扩展配置更新：[扩展配置](https://www.uvui.cn/components/setting.html)\r\n11. 文档-优化更新：[uv-ui文档](https://www.uvui.cn/components/changelog.html)\r\n12. 文档-新增常见问题：[常见问题](https://www.uvui.cn/components/problem.html)\r\n13. 优化其他\r\n## 1.0.10（2023-06-05）\r\n1. uv-navbar 渐变背景色兼容\r\n2. uv-calendar 日历选择BUG修复\r\n## 1.0.9（2023-06-05）\r\n1. 新增uv-vtabs垂直选项卡组件，主要用于分类展示，分类切换功能，支持联动和不联动两种模式\r\n2. uv-qrcode，uv-datetime-picker，uv-subsection等文档说明优化，避免开发困难；优化API相关说明\r\n3. uv-notice-bar 1. 修复在触发error函数报错的BUG；2. 修复在text值为undefined的时候，解决报错BUG\r\n4. uv-button 等组件修复触发两次事件的BUG\r\n5. uv-datetime-picker 1. 修复重置值存在不更新的BUG；2. 优化文档，增加filter使用方法说明\r\n6. uv-badge 修复type等属性为null或undefined的时候不显示徽标的BUG\r\n7. uv-ui-tools 优化工具组件，兼容更多功能，小程序分享功能优化等\r\n...\r\n## 1.0.8（2023-05-27）\r\n1. uv-waterfall修复在百度小程序中可能存在的BUG；去掉原有的slot方式\r\n2. uv-image修复可能报错的问题\r\n3. uv-pick-color 在文档预览模式中无法点击的问题\r\n4. uv-index-list 修复select事件不触发的问题\r\n5. 优化其他组件及示例项目等\r\n## 1.0.7（2023-05-25）\r\n1. uv-icon 将线上ttf字体包替换成base64，避免加载时或者网络差时候显示白色方块\r\n2. uv-text 去掉多余的data-index属性，避免警告\r\n3. uv-upload 在fileList的watch中增加deep属性\r\n4. uv-pick-color 去掉template中存在的this.导致头条小程序编译警告\r\n5. uv-image 去掉template中存在的this.导致头条小程序编译警告\r\n## 1.0.6（2023-05-23）\r\n1. 新增uv-pick-color颜色选择器组件\r\n2. uv-toolbar组件增加showBorder属性，是否显示下边框\r\n3. uv-transition组件在百度小程序等平台不支持this.$nextick导致下面的逻辑不执行，使用延时替换方案\r\n4. uv-ui-tools组件中bem()函数兼容百度/头条小程序等\r\n5. uv-waterfall组件修复在百度/头条小程序显示异常等BUG，增加changeList回调函数处理数据，同步更新示例等\r\n6. uv-image组件修复在百度/头条小程序等开启observeLazyLoad后显示异常BUG\r\n7. uv-tabs组件修复上次更新导致的在nvue中不滚动的BUG\r\n8. uv-qrcode组件修复在部分平台不显示加载的BUG\r\n9. 修复其他已知问题等\r\n## 1.0.5（2023-05-17）\r\n1. 新增uv-qrcode二维码组件\r\n2. 修复uv-tooltip在vue2模式下的BUG\r\n3. 优化部分问题\r\n## 1.0.4（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.3（2023-05-12）\r\n1. 修复uv-input在vue3模式下双向绑定问题\r\n2. 修复uv-textarea在vue3模式下双向绑定问题\r\n3. 修复uv-rate在vue3模式下双向绑定问题\r\n## 1.0.2（2023-05-11）\r\n1. 更新文档\r\n2. 增加插件下载入口\r\n## 1.0.1（2023-05-10）\r\n1. 所有组件依赖\r\n2. 上传示例项目\r\n## 1.0.0（2023-05-10）\r\n1. uv-ui\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui/components/uv-ui/uv-ui.vue",
    "content": "<template>\r\n\t<view>占位组件，请勿使用；如需下载示例项目，请使用【下载插件并导入HBuilderX】或【使用 HBuilderX 导入示例项目】或【下载示例项目ZIP】</view>\r\n</template>\r\n<script>\r\n</script>\r\n<style>\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui/package.json",
    "content": "{\r\n\t\"id\": \"uv-ui\",\r\n\t\"displayName\": \"uv-ui 破釜沉舟之兼容vue3+2、app、h5、小程序等多端，灵活导入，利剑出击\",\r\n\t\"version\": \"1.1.15\",\r\n\t\"description\": \"uv-ui 是基于uni-app、部分组件基于uView2.x、全端兼容、支持独立导入、内容丰富的UI框架。破釜沉舟之兼容vue3+2、app、h5、小程序等多端，利剑出击，开箱即用。\",\r\n\t\"keywords\": [\r\n        \"uv-ui\",\r\n        \"uvui\",\r\n        \"UI组件库\",\r\n        \"ui框架\",\r\n        \"ui库\"\r\n    ],\r\n\t\"repository\": \"https://github.com/climblee/uv-ui\",\r\n\t\"engines\": {\r\n\t\t\"HBuilderX\": \"^3.1.0\"\r\n\t},\r\n\t\"dcloudext\": {\r\n\t\t\"type\": \"component-vue\",\r\n\t\t\"sale\": {\r\n\t\t\t\"regular\": {\r\n\t\t\t\t\"price\": \"0.00\"\r\n\t\t\t},\r\n\t\t\t\"sourcecode\": {\r\n\t\t\t\t\"price\": \"0.00\"\r\n\t\t\t}\r\n\t\t},\r\n\t\t\"contact\": {\r\n\t\t\t\"qq\": \"\"\r\n\t\t},\r\n\t\t\"declaration\": {\r\n\t\t\t\"ads\": \"无\",\r\n\t\t\t\"data\": \"无\",\r\n\t\t\t\"permissions\": \"无\"\r\n\t\t},\r\n\t\t\"npmurl\": \"https://www.npmjs.com/package/@climblee/uv-ui\"\r\n\t},\r\n\t\"uni_modules\": {\r\n\t\t\"dependencies\": [\r\n\t\t\t\"uv-album\",\r\n\t\t\t\"uv-drop-down\",\r\n\t\t\t\"uv-calendars\",\r\n\t\t\t\"uv-scroll-list\",\r\n\t\t\t\"uv-vtabs\",\r\n\t\t\t\"uv-pick-color\",\r\n\t\t\t\"uv-qrcode\",\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-action-sheet\",\r\n\t\t\t\"uv-alert\",\r\n\t\t\t\"uv-avatar\",\r\n\t\t\t\"uv-back-top\",\r\n\t\t\t\"uv-badge\",\r\n\t\t\t\"uv-button\",\r\n\t\t\t\"uv-calendar\",\r\n\t\t\t\"uv-cell\",\r\n\t\t\t\"uv-checkbox\",\r\n\t\t\t\"uv-code\",\r\n\t\t\t\"uv-code-input\",\r\n\t\t\t\"uv-collapse\",\r\n\t\t\t\"uv-count-down\",\r\n\t\t\t\"uv-count-to\",\r\n\t\t\t\"uv-datetime-picker\",\r\n\t\t\t\"uv-divider\",\r\n\t\t\t\"uv-empty\",\r\n\t\t\t\"uv-form\",\r\n\t\t\t\"uv-gap\",\r\n\t\t\t\"uv-grid\",\r\n\t\t\t\"uv-icon\",\r\n\t\t\t\"uv-image\",\r\n\t\t\t\"uv-index-list\",\r\n\t\t\t\"uv-input\",\r\n\t\t\t\"uv-keyboard\",\r\n\t\t\t\"uv-line\",\r\n\t\t\t\"uv-line-progress\",\r\n\t\t\t\"uv-link\",\r\n\t\t\t\"uv-list\",\r\n\t\t\t\"uv-loading-icon\",\r\n\t\t\t\"uv-loading-page\",\r\n\t\t\t\"uv-load-more\",\r\n\t\t\t\"uv-modal\",\r\n\t\t\t\"uv-navbar\",\r\n\t\t\t\"uv-no-network\",\r\n\t\t\t\"uv-notice-bar\",\r\n\t\t\t\"uv-notify\",\r\n\t\t\t\"uv-number-box\",\r\n\t\t\t\"uv-overlay\",\r\n\t\t\t\"uv-parse\",\r\n\t\t\t\"uv-picker\",\r\n\t\t\t\"uv-popup\",\r\n\t\t\t\"uv-radio\",\r\n\t\t\t\"uv-rate\",\r\n\t\t\t\"uv-read-more\",\r\n\t\t\t\"uv-row\",\r\n\t\t\t\"uv-safe-bottom\",\r\n\t\t\t\"uv-search\",\r\n\t\t\t\"uv-skeleton\",\r\n\t\t\t\"uv-slider\",\r\n\t\t\t\"uv-status-bar\",\r\n\t\t\t\"uv-steps\",\r\n\t\t\t\"uv-sticky\",\r\n\t\t\t\"uv-subsection\",\r\n\t\t\t\"uv-swipe-action\",\r\n\t\t\t\"uv-swiper\",\r\n\t\t\t\"uv-switch\",\r\n\t\t\t\"uv-tabbar\",\r\n\t\t\t\"uv-tabs\",\r\n\t\t\t\"uv-tags\",\r\n\t\t\t\"uv-text\",\r\n\t\t\t\"uv-textarea\",\r\n\t\t\t\"uv-toast\",\r\n\t\t\t\"uv-tooltip\",\r\n\t\t\t\"uv-transition\",\r\n\t\t\t\"uv-upload\",\r\n\t\t\t\"uv-waterfall\"\r\n\t\t],\r\n\t\t\"encrypt\": [],\r\n\t\t\"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui/readme.md",
    "content": "<p align=\"center\">\r\n    <span>&nbsp;&nbsp;&nbsp;&nbsp;</span><img alt=\"logo\" src=\"https://www.uvui.cn/common/logo.png\" width=\"120\" height=\"120\">\r\n</p>\r\n<h3 align=\"center\">uv-ui</h3>\r\n<h3 align=\"center\">兼容vue3+2多平台快速开发的UI框架</h3>\r\n\r\n[![star](https://gitee.com/climblee/uv-ui/badge/star.svg?theme=gvp)](https://gitee.com/climblee/uv-ui)\r\n[![star](https://gitee.com/climblee/uv-ui/badge/fork.svg?theme=gvp)](https://gitee.com/climblee/uv-ui)\r\n[![star](https://img.shields.io/github/stars/climblee/uv-ui?style=flat-square&logo=GitHub)](https://github.com/climblee/uv-ui)\r\n[![issues](https://img.shields.io/github/issues/climblee/uv-ui?style=flat-square&logo=GitHub)](https://github.com/climblee/uv-ui/issues)\r\n[![Website](https://img.shields.io/badge/uvui-up-blue?style=flat-square)](https://www.uvui.cn)\r\n[![version](https://img.shields.io/badge/version-1.1.15-brightgreen.svg)](https://www.uvui.cn/components/changelog.html)\r\n[![license](https://img.shields.io/github/license/climblee/uv-ui?style=flat-square)](https://en.wikipedia.org/wiki/MIT_License)\r\n\r\n## 温馨提示：如需下载uv-ui示例项目，请不要使用【下载插件ZIP】按钮。\r\n\r\n### uvui官方群1：<a href=\"http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=aaoyDvfV7nSee9vzWfzyZM1vKlu3xrNZ&authKey=pdU6HDpyzGUoc1QvQvCmzCbwzsoHgBbToF%2F0ChP4lNjPIPgWHGRE4I99XYGiTcNI&noverify=0&group_code=549833913\" target=\"_blank\">549833913</a>\r\n### uvui官方群2：<a href=\"http://qm.qq.com/cgi-bin/qm/qr?_wv=1027&k=2QbEeKBn6ysCyQ09V4mgst1W8onxFybQ&authKey=aMTGL5zCYwsinG%2FeH0qMlAqXAdyKr5AjXVf2oMnsmj7NCg%2F2HraMU%2FNFxELLIPvp&noverify=0&group_code=206060892\" target=\"_blank\">206060892</a>\r\n\r\n## uvui特点\r\n\r\n1. **uv-ui的前世今生**，`uv-ui` 是基于 `uview2.x` 版本改造而来。重命名也是为了避开发布冲突和很多组件 `u-`在  `nvue` 中不能使用的情况，所以这才诞生了`uv-ui`。感谢 `uview-ui` 作者的开源奉献，再次为开源点赞。 同时 `uv-ui` 也是无条件开源。\r\n\r\n2. **全端兼容**，`uv-ui`支持vue3、vue2、app-vue、app-nvue、h5、小程序等。`uv-ui`的组件都是多端自适应的，底层会抹平很多小程序平台的差异或bug。\r\n\r\n3. **扩展配置**，`uv-ui`内置的方法默认不再挂载到`uni`对象之上，也就意味着默认情况下不能在项目中直接使用`uni.$uv.xxx`使用内置方法。但是可以通过扩展可以解决，通过如下方式进行配置即可，使用方式请参考[扩展配置](https://www.uvui.cn/components/setting.html)。其中包括[ JS工具库](https://www.uvui.cn/components/setting.html#%E6%89%A9%E5%B1%95%E9%85%8D%E7%BD%AE-js%E5%B7%A5%E5%85%B7%E5%BA%93)、[ 自定义主题](https://www.uvui.cn/components/setting.html#%E6%89%A9%E5%B1%95%E9%85%8D%E7%BD%AE-%E8%87%AA%E5%AE%9A%E4%B9%89%E4%B8%BB%E9%A2%98)、[ 基础样式](https://www.uvui.cn/components/setting.html#%E6%89%A9%E5%B1%95%E9%85%8D%E7%BD%AE-%E5%9F%BA%E7%A1%80%E6%A0%B7%E5%BC%8F)、[ setconfig](https://www.uvui.cn/components/setting.html#%E6%89%A9%E5%B1%95%E9%85%8D%E7%BD%AE-setconfig)等。\r\n\r\n## 预览\r\n\r\n通过微信（APP下载不支持微信扫码）或浏览器扫码查看演示效果。\r\n\r\n<img src=\"https://mp-31ed6237-7b7b-4597-8448-a53d6d6031cf.cdn.bspapp.com/uvui/download-wx.png\" width=\"200\" />&nbsp;&nbsp;&nbsp;&nbsp;<img src=\"https://mp-31ed6237-7b7b-4597-8448-a53d6d6031cf.cdn.bspapp.com/uvui/download-app.png\" width=\"200\" />&nbsp;&nbsp;&nbsp;&nbsp;<img src=\"https://mp-31ed6237-7b7b-4597-8448-a53d6d6031cf.cdn.bspapp.com/uvui/download-h5.png\" width=\"200\" />\r\n\r\n## 链接\r\n\r\n- [官方文档](https://www.uvui.cn)\r\n- [演示地址](https://h5.uvui.cn)\r\n- [更新日志](https://www.uvui.cn/components/changelog.html)\r\n- [关于我们](https://www.uvui.cn/cooperation/about.html)\r\n- <a href=\"#list\">组件列表</a>\r\n\r\n## 交流反馈\r\n\r\n欢迎加入我们的QQ群交流反馈：[点此跳转](https://www.uvui.cn/components/addQQGroup.html)\r\n\r\n## 快速开始\r\n\r\n方式一：`uv-ui` 强烈建议通过 `下载插件并导入HbuilderX` 导入组件。\r\n\r\n方式二：下载完整 [uv-ui项目](https://ext.dcloud.net.cn/plugin?id=12287) 将 `uni_modules` 复制到自己的项目。\r\n\r\n方式三：通过 `npm i @climblee/uv-ui` 下载，此方法需要配置 easycom，配置详情可查看[安装](https://www.uvui.cn/components/install.html)。\r\n\r\n请通过[快速上手](https://www.uvui.cn/components/quickstart.html)了解更详细的内容。\r\n\r\n**注意：导入插件后，建议`HBuilderX`重新运行项目，可能新导入的插件不能实时更新而导致不能运行。**\r\n\r\n## 使用方法\r\n\r\n组件导入 `uni_modules` 后，直接在项目中使用，无需通过import引入组件。\r\n\r\n```html\r\n<template>\r\n    <uv-icon name=\"baidu\" size=\"30\" color=\"#909399\"></uv-icon>\r\n</template>\r\n```\r\n\r\n## 扩展功能\r\n\r\n`uv-ui` 内置了强大的工具函数、请求封装等，可以根据自身需求进行扩展配置，详情请查看[扩展配置](https://www.uvui.cn/components/setting.html)。\r\n\r\n**注意：只有[扩展配置](https://www.uvui.cn/components/setting.html)后才能在自己的项目页面中使用组件库内置方法和变量等**。\r\n\r\n<div id=\"list\"></div>\r\n\r\n## 组件列表\r\n\r\n下表为 `uv-ui` 的扩展组件清单，点击每个组件**点击下载&安装**即可在详情页面导入组件到项目下，导入后建议重新运行即可直接使用，组件无需import和注册。\r\n\r\n| 组件名 | 组件说明 |\r\n| --- | --- |\r\n| uv-calendars | [新版日历（推荐）](https://www.uvui.cn/components/calendars.html) |\r\n| uv-drop-down | [下拉筛选](https://www.uvui.cn/components/dropDown.html) |\r\n| uv-scroll-list | [横向滚动列表](https://www.uvui.cn/components/scrollList.html) |\r\n| uv-vtabs | [垂直选项卡](https://www.uvui.cn/components/vtabs.html) |\r\n| uv-pick-color | [颜色选择器](https://www.uvui.cn/components/pickColor.html) |\r\n| uv-qrcode | [二维码](https://www.uvui.cn/components/qrcode.html) |\r\n| uv-waterfall | [瀑布流](https://www.uvui.cn/components/waterfall.html) |\r\n| uv-row | [Layout 布局](https://www.uvui.cn/components/layout.html) |\r\n| uv-icon | [图标](https://www.uvui.cn/components/icon.html) |\r\n| uv-button | [按钮](https://www.uvui.cn/components/button.html) |\r\n| uv-text | [文本](https://www.uvui.cn/components/text.html) |\r\n| uv-link | [超链接](https://www.uvui.cn/components/link.html) |\r\n| uv-image | [图片](https://www.uvui.cn/components/image.html) |\r\n| uv-transition | [动画](https://www.uvui.cn/components/transition.html) |\r\n| uv-form | [表单](https://www.uvui.cn/components/form.html) |\r\n| uv-input | [增强输入框](https://www.uvui.cn/components/input.html) |\r\n| uv-textarea | [增强文本域](https://www.uvui.cn/components/textarea.html) |\r\n| uv-checkbox | [复选框](https://www.uvui.cn/components/checkbox.html) |\r\n| uv-radio | [单选框](https://www.uvui.cn/components/radio.html) |\r\n| uv-switch | [开关选择器](https://www.uvui.cn/components/switch.html) |\r\n| uv-calendar | [日历](https://www.uvui.cn/components/calendar.html) |\r\n| uv-picker | [选择器](https://www.uvui.cn/components/picker.html) |\r\n| uv-datetime-picker | [时间选择器](https://www.uvui.cn/components/datetimePicker.html) |\r\n| uv-code | [验证码倒计时](https://www.uvui.cn/components/code.html) |\r\n| uv-keyboard | [键盘](https://www.uvui.cn/components/keyboard.html) |\r\n| uv-rate | [评分](https://www.uvui.cn/components/rate.html) |\r\n| uv-search | [多功能搜索框](https://www.uvui.cn/components/search.html) |\r\n| uv-number-box | [步进器](https://www.uvui.cn/components/numberBox.html) |\r\n| uv-upload | [上传](https://www.uvui.cn/components/upload.html) |\r\n| uv-slider | [滑动选择器](https://www.uvui.cn/components/slider.html) |\r\n| uv-list | [列表](https://www.uvui.cn/components/list.html) |\r\n| uv-index-list | [索引列表](https://www.uvui.cn/components/indexList.html) |\r\n| uv-tags | [标签](https://www.uvui.cn/components/tag.html) |\r\n| uv-line-progress | [线形进度条](https://www.uvui.cn/components/lineProgress.html) |\r\n| uv-badge | [徽标数](https://www.uvui.cn/components/badge.html) |\r\n| uv-count-down | [倒计时](https://www.uvui.cn/components/countDown.html) |\r\n| uv-count-to | [数字滚动](https://www.uvui.cn/components/countTo.html) |\r\n| uv-avatar | [头像](https://www.uvui.cn/components/avatar.html) |\r\n| uv-skeleton | [骨架屏](https://www.uvui.cn/components/skeleton.html) |\r\n| uv-loading-icon | [加载动画](https://www.uvui.cn/components/loadingIcon.html) |\r\n| uv-loading-page | [加载页](https://www.uvui.cn/components/loadingPage.html) |\r\n| uv-load-more | [加载更多](https://www.uvui.cn/components/loadMore.html) |\r\n| uv-empty | [内容为空](https://www.uvui.cn/components/empty.html) |\r\n| uv-tooltip | [长按提示](https://www.uvui.cn/components/tooltip.html) |\r\n| uv-alert | [警告提示](https://www.uvui.cn/components/alert.html) |\r\n| uv-toast | [消息提示](https://www.uvui.cn/components/toast.html) |\r\n| uv-notice-bar | [滚动通知](https://www.uvui.cn/components/noticeBar.html) |\r\n| uv-notify | [消息提示](https://www.uvui.cn/components/notify.html) |\r\n| uv-no-network | [无网络提示](https://www.uvui.cn/components/noNetwork.html) |\r\n| uv-popup | [弹出层](https://www.uvui.cn/components/popup.html) |\r\n| uv-modal | [模态框](https://www.uvui.cn/components/modal.html) |\r\n| uv-cell | [单元格](https://www.uvui.cn/components/cell.html) |\r\n| uv-swipe-action | [滑动单元格](https://www.uvui.cn/components/swipeAction.html) |\r\n| uv-swiper | [轮播图](https://www.uvui.cn/components/swiper.html) |\r\n| uv-collapse | [折叠面板](https://www.uvui.cn/components/collapse.html) |\r\n| uv-grid | [宫格布局](https://www.uvui.cn/components/grid.html) |\r\n| uv-album | [相册](https://www.uvui.cn/components/album.html) |\r\n| uv-tabbar | [底部导航栏](https://www.uvui.cn/components/tabbar.html) |\r\n| uv-back-top | [返回顶部](https://www.uvui.cn/components/backTop.html) |\r\n| uv-navbar | [自定义导航栏](https://www.uvui.cn/components/navbar.html) |\r\n| uv-action-sheet | [底部操作菜单](https://www.uvui.cn/components/actionSheet.html) |\r\n| uv-tabs | [标签选项卡](https://www.uvui.cn/components/tabs.html) |\r\n| uv-steps | [步骤条](https://www.uvui.cn/components/steps.html) |\r\n| uv-subsection | [分段器](https://www.uvui.cn/components/subsection.html) |\r\n| uv-sticky | [吸顶](https://www.uvui.cn/components/sticky.html) |\r\n| uv-parse | [富文本解析器](https://www.uvui.cn/components/parse.html) |\r\n| uv-overlay | [遮罩层](https://www.uvui.cn/components/overlay.html) |\r\n| uv-code-input | [验证码输入](https://www.uvui.cn/components/codeInput.html) |\r\n| uv-read-more | [展开阅读更多](https://www.uvui.cn/components/readMore.html) |\r\n| uv-line | [线条](https://www.uvui.cn/components/line.html) |\r\n| uv-gap | [间隔槽](https://www.uvui.cn/components/gap.html) |\r\n| uv-divider | [分割线](https://www.uvui.cn/components/divider.html) |\r\n\r\n## 版权信息\r\nuv-ui遵循[MIT](https://en.wikipedia.org/wiki/MIT_License)开源协议，意味着您无需支付任何费用，也无需授权，即可将uv-ui应用到您的产品中。\r\n\r\n## 作者想说\r\n- 开源真的不易，不图大家的钱财，所以希望大家多多鼓励支持，希望不要恶意评论，有问题加群快速解决。\r\n- 遇到BUG，是一件很正常的事情，是程序肯定就有BUG，所以希望大家能以理解的心态去提出BUG，然后作者才有动力去努力修复。\r\n- 最后觉得好用的小伙伴，不要吝啬你的双手，给个好评就是给我们最大的鼓励。\r\n\r\n# 恶评者手下留情，有事加QQ群解决：549833913"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/changelog.md",
    "content": "## 1.1.19（2023-10-13）\n1. 兼容vue3\n## 1.1.18（2023-10-12）\r\n1. 1.1.15版本\r\n## 1.1.17（2023-09-27）\r\n1. 1.1.14版本发布\r\n## 1.1.16（2023-09-15）\r\n1. 1.1.13版本发布\r\n## 1.1.15（2023-09-15）\r\n1. 更新button.js相关按钮支持open-type=\"agreePrivacyAuthorization\"\r\n## 1.1.14（2023-09-14）\r\n1. 优化dayjs\r\n## 1.1.13（2023-09-13）\r\n1. 优化，$uv中增加unit参数，方便组件中使用\r\n## 1.1.12（2023-09-10）\r\n1. 升级版本\r\n## 1.1.11（2023-09-04）\r\n1. 1.1.11版本\r\n## 1.1.10（2023-08-31）\r\n1. 修复customStyle和customClass存在冲突的问题\r\n## 1.1.9（2023-08-27）\r\n1. 版本升级\r\n2. 优化\r\n## 1.1.8（2023-08-24）\r\n1. 版本升级\r\n## 1.1.7（2023-08-22）\r\n1. 版本升级\r\n## 1.1.6（2023-08-18）\r\nuvui版本：1.1.6\r\n## 1.0.15（2023-08-14）\r\n1. 更新uvui版本号\r\n## 1.0.13（2023-08-06）\r\n1. 优化\r\n## 1.0.12（2023-08-06）\r\n1. 修改版本号\r\n## 1.0.11（2023-08-06）\r\n1. 路由增加events参数\r\n2. 路由拦截修复\r\n## 1.0.10（2023-08-01）\r\n1. 优化\r\n## 1.0.9（2023-06-28）\r\n优化openType.js\r\n## 1.0.8（2023-06-15）\r\n1. 修改支付宝报错的BUG\r\n## 1.0.7（2023-06-07）\r\n1. 解决微信小程序使用uvui提示 Some selectors are not allowed in component wxss, including tag name selectors, ID selectors, and attribute selectors\r\n2. 解决上述提示，需要在uni.scss配置$uvui-nvue-style: false; 然后在APP.vue下面引入uvui内置的基础样式:@import '@/uni_modules/uv-ui-tools/index.scss';\r\n## 1.0.6（2023-06-04）\r\n1.  uv-ui-tools 优化工具组件，兼容更多功能\r\n2.  小程序分享功能优化等\r\n## 1.0.5（2023-06-02）\r\n1. 修改扩展使用mixin中方法的问题\r\n## 1.0.4（2023-05-23）\r\n1. 兼容百度小程序修改bem函数\r\n## 1.0.3（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.2（2023-05-10）\r\n1. 增加Http请求封装\r\n2. 优化\r\n## 1.0.1（2023-05-04）\r\n1. 修改名称及备注\r\n## 1.0.0（2023-05-04）\r\n1. uv-ui工具集首次发布\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/components/uv-ui-tools/uv-ui-tools.vue",
    "content": "<template>\r\n</template>\r\n<script>\r\n</script>\r\n<style>\r\n</style>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/index.js",
    "content": "// 全局挂载引入http相关请求拦截插件\r\nimport Request from './libs/luch-request'\r\n\r\n// 引入全局mixin\r\nimport mixin from './libs/mixin/mixin.js'\r\n// 小程序特有的mixin\r\nimport mpMixin from './libs/mixin/mpMixin.js'\r\n// #ifdef MP\r\nimport mpShare from '@/uni_modules/uv-ui-tools/libs/mixin/mpShare.js'\r\n// #endif\r\n\r\n// 路由封装\r\nimport route from './libs/util/route.js'\r\n// 公共工具函数\r\nimport * as index from './libs/function/index.js'\r\n// 防抖方法\r\nimport debounce from './libs/function/debounce.js'\r\n// 节流方法\r\nimport throttle from './libs/function/throttle.js'\r\n// 规则检验\r\nimport * as test from './libs/function/test.js'\r\n\r\n// 颜色渐变相关,colorGradient-颜色渐变,hexToRgb-十六进制颜色转rgb颜色,rgbToHex-rgb转十六进制\r\nimport * as colorGradient from './libs/function/colorGradient.js'\r\n\r\n// 配置信息\r\nimport config from './libs/config/config.js'\r\n// 平台\r\nimport platform from './libs/function/platform'\r\n\r\nconst $uv = {\r\n\troute,\r\n\tconfig,\r\n\ttest,\r\n\tdate: index.timeFormat, // 另名date\r\n\t...index,\r\n\tcolorGradient: colorGradient.colorGradient,\r\n\thexToRgb: colorGradient.hexToRgb,\r\n\trgbToHex: colorGradient.rgbToHex,\r\n\tcolorToRgba: colorGradient.colorToRgba,\r\n\thttp: new Request(),\r\n\tdebounce,\r\n\tthrottle,\r\n\tplatform,\r\n\tmixin,\r\n\tmpMixin\r\n}\r\nuni.$uv = $uv;\r\nconst install = (Vue,options={}) => {\r\n\t\t// #ifndef APP-NVUE\r\n\t\tconst cloneMixin = index.deepClone(mixin);\r\n\t\tdelete cloneMixin?.props?.customClass;\r\n\t\tdelete cloneMixin?.props?.customStyle;\r\n\t\tVue.mixin(cloneMixin);\r\n\t\t// #ifdef MP\r\n\t\tif(options.mpShare){\r\n\t\t\tVue.mixin(mpShare);\r\n\t\t}\r\n\t\t// #endif\r\n\t\t// #endif\r\n\t\t// #ifdef VUE2\r\n\t\t// 时间格式化，同时两个名称，date和timeFormat\r\n\t\tVue.filter('timeFormat', (timestamp, format) => uni.$uv.timeFormat(timestamp, format));\r\n\t\tVue.filter('date', (timestamp, format) => uni.$uv.timeFormat(timestamp, format));\r\n\t\t// 将多久以前的方法，注入到全局过滤器\r\n\t\tVue.filter('timeFrom', (timestamp, format) => uni.$uv.timeFrom(timestamp, format));\r\n\t\t// 同时挂载到uni和Vue.prototype中\r\n\t\t// #ifndef APP-NVUE\r\n\t\t// 只有vue，挂载到Vue.prototype才有意义，因为nvue中全局Vue.prototype和Vue.mixin是无效的\r\n\t\tVue.prototype.$uv = $uv;\r\n\t\t// #endif\r\n\t\t// #endif\r\n\t\t// #ifdef VUE3\r\n\t\tVue.config.globalProperties.$uv = $uv;\r\n\t\t// #endif\r\n}\r\nexport default {\r\n\tinstall\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/index.scss",
    "content": "// 引入公共基础类\r\n@import \"./libs/css/common.scss\";\r\n\r\n// 非nvue的样式\r\n/* #ifndef APP-NVUE */\r\n@import \"./libs/css/vue.scss\";\r\n/* #endif */"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/libs/config/config.js",
    "content": "// 此版本发布于2023-10-12\r\nconst version = '1.1.15'\r\n\r\n// 开发环境才提示，生产环境不会提示\r\nif (process.env.NODE_ENV === 'development') {\r\n\tconsole.log(`\\n %c uvui V${version} https://www.uvui.cn/ \\n\\n`, 'color: #ffffff; background: #3c9cff; padding:5px 0; border-radius: 5px;');\r\n}\r\n\r\nexport default {\r\n    v: version,\r\n    version,\r\n    // 主题名称\r\n    type: [\r\n        'primary',\r\n        'success',\r\n        'info',\r\n        'error',\r\n        'warning'\r\n    ],\r\n    // 颜色部分，本来可以通过scss的:export导出供js使用，但是奈何nvue不支持\r\n    color: {\r\n        'uv-primary': '#2979ff',\r\n        'uv-warning': '#ff9900',\r\n        'uv-success': '#19be6b',\r\n        'uv-error': '#fa3534',\r\n        'uv-info': '#909399',\r\n        'uv-main-color': '#303133',\r\n        'uv-content-color': '#606266',\r\n        'uv-tips-color': '#909399',\r\n        'uv-light-color': '#c0c4cc'\r\n    },\r\n\t// 默认单位，可以通过配置为rpx，那么在用于传入组件大小参数为数值时，就默认为rpx\r\n\tunit: 'px'\r\n}\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/libs/css/color.scss",
    "content": "$uv-main-color: #303133 !default;\r\n$uv-content-color: #606266 !default;\r\n$uv-tips-color: #909193 !default;\r\n$uv-light-color: #c0c4cc !default;\r\n$uv-border-color: #dadbde !default;\r\n$uv-bg-color: #f3f4f6 !default;\r\n$uv-disabled-color: #c8c9cc !default;\r\n\r\n$uv-primary: #3c9cff !default;\r\n$uv-primary-dark: #398ade !default;\r\n$uv-primary-disabled: #9acafc !default;\r\n$uv-primary-light: #ecf5ff !default;\r\n\r\n$uv-warning: #f9ae3d !default;\r\n$uv-warning-dark: #f1a532 !default;\r\n$uv-warning-disabled: #f9d39b !default;\r\n$uv-warning-light: #fdf6ec !default;\r\n\r\n$uv-success: #5ac725 !default;\r\n$uv-success-dark: #53c21d !default;\r\n$uv-success-disabled: #a9e08f !default;\r\n$uv-success-light: #f5fff0;\r\n\r\n$uv-error: #f56c6c !default;\r\n$uv-error-dark: #e45656 !default;\r\n$uv-error-disabled: #f7b2b2 !default;\r\n$uv-error-light: #fef0f0 !default;\r\n\r\n$uv-info: #909399 !default;\r\n$uv-info-dark: #767a82 !default;\r\n$uv-info-disabled: #c4c6c9 !default;\r\n$uv-info-light: #f4f4f5 !default;\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/libs/css/common.scss",
    "content": "// 超出行数，自动显示行尾省略号，最多5行\r\n// 来自uvui的温馨提示：当您在控制台看到此报错，说明需要在App.vue的style标签加上【lang=\"scss\"】\r\n@for $i from 1 through 5 {\r\n\t.uv-line-#{$i} {\r\n\t\t/* #ifdef APP-NVUE */\r\n\t\t// nvue下，可以直接使用lines属性，这是weex特有样式\r\n\t\tlines: $i;\r\n\t\ttext-overflow: ellipsis;\r\n\t\toverflow: hidden;\r\n\t\tflex: 1;\r\n\t\t/* #endif */\r\n\r\n\t\t/* #ifndef APP-NVUE */\r\n\t\t// vue下，单行和多行显示省略号需要单独处理\r\n\t\t@if $i == '1' {\r\n\t\t\toverflow: hidden;\r\n\t\t\twhite-space: nowrap;\r\n\t\t\ttext-overflow: ellipsis;\r\n\t\t} @else {\r\n\t\t\tdisplay: -webkit-box!important;\r\n\t\t\toverflow: hidden;\r\n\t\t\ttext-overflow: ellipsis;\r\n\t\t\tword-break: break-all;\r\n\t\t\t-webkit-line-clamp: $i;\r\n\t\t\t-webkit-box-orient: vertical!important;\r\n\t\t}\r\n\t\t/* #endif */\r\n\t}\r\n}\r\n$uv-bordercolor: #dadbde;\r\n@if variable-exists(uv-border-color) {\r\n\t$uv-bordercolor: $uv-border-color;\r\n}\r\n\r\n// 此处加上!important并非随意乱用，而是因为目前*.nvue页面编译到H5时，\r\n// App.vue的样式会被uni-app的view元素的自带border属性覆盖，导致无效\r\n// 综上，这是uni-app的缺陷导致我们为了多端兼容，而必须要加上!important\r\n// 移动端兼容性较好，直接使用0.5px去实现细边框，不使用伪元素形式实现\r\n.uv-border {\r\n\tborder-width: 0.5px!important;\r\n\tborder-color: $uv-bordercolor!important;\r\n    border-style: solid;\r\n}\r\n\r\n.uv-border-top {\r\n\tborder-top-width: 0.5px!important;\r\n\tborder-color: $uv-bordercolor!important;\r\n    border-top-style: solid;\r\n}\r\n\r\n.uv-border-left {\r\n\tborder-left-width: 0.5px!important;\r\n\tborder-color: $uv-bordercolor!important;\r\n    border-left-style: solid;\r\n}\r\n\r\n.uv-border-right {\r\n\tborder-right-width: 0.5px!important;\r\n\tborder-color: $uv-bordercolor!important;\r\n    border-right-style: solid;\r\n}\r\n\r\n.uv-border-bottom {\r\n\tborder-bottom-width: 0.5px!important;\r\n\tborder-color: $uv-bordercolor!important;\r\n    border-bottom-style: solid;\r\n}\r\n\r\n.uv-border-top-bottom {\r\n\tborder-top-width: 0.5px!important;\r\n\tborder-bottom-width: 0.5px!important;\r\n\tborder-color: $uv-bordercolor!important;\r\n    border-top-style: solid;\r\n    border-bottom-style: solid;\r\n}\r\n\r\n// 去除button的所有默认样式，让其表现跟普通的view、text元素一样\r\n.uv-reset-button {\r\n\tpadding: 0;\r\n\tbackground-color: transparent;\r\n\t/* #ifndef APP-PLUS */\r\n\tfont-size: inherit;\r\n\tline-height: inherit;\r\n\tcolor: inherit;\r\n\t/* #endif */\r\n\t/* #ifdef APP-NVUE */\r\n\tborder-width: 0;\r\n\t/* #endif */\r\n}\r\n\r\n/* #ifndef APP-NVUE */\r\n.uv-reset-button::after {\r\n   border: none;\r\n}\r\n/* #endif */\r\n\r\n.uv-hover-class {\r\n\topacity: 0.7;\r\n}\r\n\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/libs/css/components.scss",
    "content": "@mixin flex($direction: row) {\r\n\t/* #ifndef APP-NVUE */\r\n\tdisplay: flex;\r\n\t/* #endif */\r\n\tflex-direction: $direction;\r\n}\r\n\r\n/* #ifndef APP-NVUE */\r\n// 由于uvui是基于nvue环境进行开发的，此环境中普通元素默认为flex-direction: column;\r\n// 所以在非nvue中，需要对元素进行重置为flex-direction: column; 否则可能会表现异常\r\n$uvui-nvue-style: true !default;\r\n@if $uvui-nvue-style == true {\r\n\tview, scroll-view, swiper-item {\r\n\t\tdisplay: flex;\r\n\t\tflex-direction: column;\r\n\t\tflex-shrink: 0;\r\n\t\tflex-grow: 0;\r\n\t\tflex-basis: auto;\r\n\t\talign-items: stretch;\r\n\t\talign-content: flex-start;\r\n\t}\r\n}\r\n/* #endif */\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/libs/css/variable.scss",
    "content": "// 超出行数，自动显示行尾省略号，最多5行\r\n// 来自uvui的温馨提示：当您在控制台看到此报错，说明需要在App.vue的style标签加上【lang=\"scss\"】\r\n@if variable-exists(show-lines) {\r\n\t@for $i from 1 through 5 {\r\n\t\t.uv-line-#{$i} {\r\n\t\t\t/* #ifdef APP-NVUE */\r\n\t\t\t// nvue下，可以直接使用lines属性，这是weex特有样式\r\n\t\t\tlines: $i;\r\n\t\t\ttext-overflow: ellipsis;\r\n\t\t\toverflow: hidden;\r\n\t\t\tflex: 1;\r\n\t\t\t/* #endif */\r\n\r\n\t\t\t/* #ifndef APP-NVUE */\r\n\t\t\t// vue下，单行和多行显示省略号需要单独处理\r\n\t\t\t@if $i == '1' {\r\n\t\t\t\toverflow: hidden;\r\n\t\t\t\twhite-space: nowrap;\r\n\t\t\t\ttext-overflow: ellipsis;\r\n\t\t\t} @else {\r\n\t\t\t\tdisplay: -webkit-box!important;\r\n\t\t\t\toverflow: hidden;\r\n\t\t\t\ttext-overflow: ellipsis;\r\n\t\t\t\tword-break: break-all;\r\n\t\t\t\t-webkit-line-clamp: $i;\r\n\t\t\t\t-webkit-box-orient: vertical!important;\r\n\t\t\t}\r\n\t\t\t/* #endif */\r\n\t\t}\r\n\t}\r\n}\r\n@if variable-exists(show-border) {\r\n\t$uv-bordercolor: #dadbde;\r\n\t@if variable-exists(uv-border-color) {\r\n\t\t$uv-bordercolor: $uv-border-color;\r\n\t}\r\n\t// 此处加上!important并非随意乱用，而是因为目前*.nvue页面编译到H5时，\r\n\t// App.vue的样式会被uni-app的view元素的自带border属性覆盖，导致无效\r\n\t// 综上，这是uni-app的缺陷导致我们为了多端兼容，而必须要加上!important\r\n\t// 移动端兼容性较好，直接使用0.5px去实现细边框，不使用伪元素形式实现\r\n\t@if variable-exists(show-border-surround) {\r\n\t\t.uv-border {\r\n\t\t\tborder-width: 0.5px!important;\r\n\t\t\tborder-color: $uv-bordercolor!important;\r\n\t\t\tborder-style: solid;\r\n\t\t}\r\n\t}\r\n\t@if variable-exists(show-border-top) {\r\n\t\t.uv-border-top {\r\n\t\t\tborder-top-width: 0.5px!important;\r\n\t\t\tborder-color: $uv-bordercolor!important;\r\n\t\t\tborder-top-style: solid;\r\n\t\t}\r\n\t}\r\n\t@if variable-exists(show-border-left) {\r\n\t\t.uv-border-left {\r\n\t\t\tborder-left-width: 0.5px!important;\r\n\t\t\tborder-color: $uv-bordercolor!important;\r\n\t\t\tborder-left-style: solid;\r\n\t\t}\r\n\t}\r\n\t@if variable-exists(show-border-right) {\r\n\t\t.uv-border-right {\r\n\t\t\tborder-right-width: 0.5px!important;\r\n\t\t\tborder-color: $uv-bordercolor!important;\r\n\t\t\tborder-right-style: solid;\r\n\t\t}\r\n\t}\r\n\t@if variable-exists(show-border-bottom) {\r\n\t\t.uv-border-bottom {\r\n\t\t\tborder-bottom-width: 0.5px!important;\r\n\t\t\tborder-color: $uv-bordercolor!important;\r\n\t\t\t\tborder-bottom-style: solid;\r\n\t\t}\r\n\t}\r\n\t@if variable-exists(show-border-top-bottom) {\r\n\t\t.uv-border-top-bottom {\r\n\t\t\tborder-top-width: 0.5px!important;\r\n\t\t\tborder-bottom-width: 0.5px!important;\r\n\t\t\tborder-color: $uv-bordercolor!important;\r\n\t\t\tborder-top-style: solid;\r\n\t\t\tborder-bottom-style: solid;\r\n\t\t}\r\n\t}\r\n}\r\n@if variable-exists(show-reset-button) {\r\n\t// 去除button的所有默认样式，让其表现跟普通的view、text元素一样\r\n\t.uv-reset-button {\r\n\t\tpadding: 0;\r\n\t\tbackground-color: transparent;\r\n\t\t/* #ifndef APP-PLUS */\r\n\t\tfont-size: inherit;\r\n\t\tline-height: inherit;\r\n\t\tcolor: inherit;\r\n\t\t/* #endif */\r\n\t\t/* #ifdef APP-NVUE */\r\n\t\tborder-width: 0;\r\n\t\t/* #endif */\r\n\t}\r\n\r\n\t/* #ifndef APP-NVUE */\r\n\t.uv-reset-button::after {\r\n\t\t border: none;\r\n\t}\r\n\t/* #endif */\r\n}\r\n@if variable-exists(show-hover) {\r\n\t.uv-hover-class {\r\n\t\topacity: 0.7;\r\n\t}\r\n}\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/libs/css/vue.scss",
    "content": "// 历遍生成4个方向的底部安全区\r\n@each $d in top, right, bottom, left {\r\n\t.uv-safe-area-inset-#{$d} {\r\n\t\tpadding-#{$d}: 0;\r\n\t\tpadding-#{$d}: constant(safe-area-inset-#{$d});  \r\n\t\tpadding-#{$d}: env(safe-area-inset-#{$d});  \r\n\t}\r\n}\r\n\r\n//提升H5端uni.toast()的层级，避免被uvui的modal等遮盖\r\n/* #ifdef H5 */\r\nuni-toast {\r\n    z-index: 10090;\r\n}\r\nuni-toast .uni-toast {\r\n   z-index: 10090;\r\n}\r\n/* #endif */\r\n\r\n// 隐藏scroll-view的滚动条\r\n::-webkit-scrollbar {\r\n    display: none;  \r\n    width: 0 !important;  \r\n    height: 0 !important;  \r\n    -webkit-appearance: none;  \r\n    background: transparent;  \r\n}\r\n\r\n$uvui-nvue-style: true !default;\r\n@if $uvui-nvue-style == false {\r\n\tview, scroll-view, swiper-item {\r\n\t\tdisplay: flex;\r\n\t\tflex-direction: column;\r\n\t\tflex-shrink: 0;\r\n\t\tflex-grow: 0;\r\n\t\tflex-basis: auto;\r\n\t\talign-items: stretch;\r\n\t\talign-content: flex-start;\r\n\t}\r\n}\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/libs/function/colorGradient.js",
    "content": "/**\r\n * 求两个颜色之间的渐变值\r\n * @param {string} startColor 开始的颜色\r\n * @param {string} endColor 结束的颜色\r\n * @param {number} step 颜色等分的份额\r\n * */\r\nfunction colorGradient(startColor = 'rgb(0, 0, 0)', endColor = 'rgb(255, 255, 255)', step = 10) {\r\n    const startRGB = hexToRgb(startColor, false) // 转换为rgb数组模式\r\n    const startR = startRGB[0]\r\n    const startG = startRGB[1]\r\n    const startB = startRGB[2]\r\n\r\n    const endRGB = hexToRgb(endColor, false)\r\n    const endR = endRGB[0]\r\n    const endG = endRGB[1]\r\n    const endB = endRGB[2]\r\n\r\n    const sR = (endR - startR) / step // 总差值\r\n    const sG = (endG - startG) / step\r\n    const sB = (endB - startB) / step\r\n    const colorArr = []\r\n    for (let i = 0; i < step; i++) {\r\n        // 计算每一步的hex值\r\n        let hex = rgbToHex(`rgb(${Math.round((sR * i + startR))},${Math.round((sG * i + startG))},${Math.round((sB\r\n\t\t\t* i + startB))})`)\r\n        // 确保第一个颜色值为startColor的值\r\n        if (i === 0) hex = rgbToHex(startColor)\r\n        // 确保最后一个颜色值为endColor的值\r\n        if (i === step - 1) hex = rgbToHex(endColor)\r\n        colorArr.push(hex)\r\n    }\r\n    return colorArr\r\n}\r\n\r\n// 将hex表示方式转换为rgb表示方式(这里返回rgb数组模式)\r\nfunction hexToRgb(sColor, str = true) {\r\n    const reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/\r\n    sColor = String(sColor).toLowerCase()\r\n    if (sColor && reg.test(sColor)) {\r\n        if (sColor.length === 4) {\r\n            let sColorNew = '#'\r\n            for (let i = 1; i < 4; i += 1) {\r\n                sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1))\r\n            }\r\n            sColor = sColorNew\r\n        }\r\n        // 处理六位的颜色值\r\n        const sColorChange = []\r\n        for (let i = 1; i < 7; i += 2) {\r\n            sColorChange.push(parseInt(`0x${sColor.slice(i, i + 2)}`))\r\n        }\r\n        if (!str) {\r\n            return sColorChange\r\n        }\r\n        return `rgb(${sColorChange[0]},${sColorChange[1]},${sColorChange[2]})`\r\n    } if (/^(rgb|RGB)/.test(sColor)) {\r\n        const arr = sColor.replace(/(?:\\(|\\)|rgb|RGB)*/g, '').split(',')\r\n        return arr.map((val) => Number(val))\r\n    }\r\n    return sColor\r\n}\r\n\r\n// 将rgb表示方式转换为hex表示方式\r\nfunction rgbToHex(rgb) {\r\n    const _this = rgb\r\n    const reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/\r\n    if (/^(rgb|RGB)/.test(_this)) {\r\n        const aColor = _this.replace(/(?:\\(|\\)|rgb|RGB)*/g, '').split(',')\r\n        let strHex = '#'\r\n        for (let i = 0; i < aColor.length; i++) {\r\n            let hex = Number(aColor[i]).toString(16)\r\n            hex = String(hex).length == 1 ? `${0}${hex}` : hex // 保证每个rgb的值为2位\r\n            if (hex === '0') {\r\n                hex += hex\r\n            }\r\n            strHex += hex\r\n        }\r\n        if (strHex.length !== 7) {\r\n            strHex = _this\r\n        }\r\n        return strHex\r\n    } if (reg.test(_this)) {\r\n        const aNum = _this.replace(/#/, '').split('')\r\n        if (aNum.length === 6) {\r\n            return _this\r\n        } if (aNum.length === 3) {\r\n            let numHex = '#'\r\n            for (let i = 0; i < aNum.length; i += 1) {\r\n                numHex += (aNum[i] + aNum[i])\r\n            }\r\n            return numHex\r\n        }\r\n    } else {\r\n        return _this\r\n    }\r\n}\r\n\r\n/**\r\n* JS颜色十六进制转换为rgb或rgba,返回的格式为 rgba（255，255，255，0.5）字符串\r\n* sHex为传入的十六进制的色值\r\n* alpha为rgba的透明度\r\n*/\r\nfunction colorToRgba(color, alpha) {\r\n    color = rgbToHex(color)\r\n    // 十六进制颜色值的正则表达式\r\n    const reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/\r\n    /* 16进制颜色转为RGB格式 */\r\n    let sColor = String(color).toLowerCase()\r\n    if (sColor && reg.test(sColor)) {\r\n        if (sColor.length === 4) {\r\n            let sColorNew = '#'\r\n            for (let i = 1; i < 4; i += 1) {\r\n                sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1))\r\n            }\r\n            sColor = sColorNew\r\n        }\r\n        // 处理六位的颜色值\r\n        const sColorChange = []\r\n        for (let i = 1; i < 7; i += 2) {\r\n            sColorChange.push(parseInt(`0x${sColor.slice(i, i + 2)}`))\r\n        }\r\n        // return sColorChange.join(',')\r\n        return `rgba(${sColorChange.join(',')},${alpha})`\r\n    }\r\n\r\n    return sColor\r\n}\r\n\r\nexport {\r\n    colorGradient,\r\n    hexToRgb,\r\n    rgbToHex,\r\n    colorToRgba\r\n}\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/libs/function/debounce.js",
    "content": "let timeout = null\r\n\r\n/**\r\n * 防抖原理：一定时间内，只有最后一次操作，再过wait毫秒后才执行函数\r\n *\r\n * @param {Function} func 要执行的回调函数\r\n * @param {Number} wait 延时的时间\r\n * @param {Boolean} immediate 是否立即执行\r\n * @return null\r\n */\r\nfunction debounce(func, wait = 500, immediate = false) {\r\n    // 清除定时器\r\n    if (timeout !== null) clearTimeout(timeout)\r\n    // 立即执行，此类情况一般用不到\r\n    if (immediate) {\r\n        const callNow = !timeout\r\n        timeout = setTimeout(() => {\r\n            timeout = null\r\n        }, wait)\r\n        if (callNow) typeof func === 'function' && func()\r\n    } else {\r\n        // 设置定时器，当最后一次操作后，timeout不会再被清除，所以在延时wait毫秒后执行func回调方法\r\n        timeout = setTimeout(() => {\r\n            typeof func === 'function' && func()\r\n        }, wait)\r\n    }\r\n}\r\n\r\nexport default debounce\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/libs/function/digit.js",
    "content": "let _boundaryCheckingState = true; // 是否进行越界检查的全局开关\r\n\r\n/**\r\n * 把错误的数据转正\r\n * @private\r\n * @example strip(0.09999999999999998)=0.1\r\n */\r\nfunction strip(num, precision = 15) {\r\n  return +parseFloat(Number(num).toPrecision(precision));\r\n}\r\n\r\n/**\r\n * Return digits length of a number\r\n * @private\r\n * @param {*number} num Input number\r\n */\r\nfunction digitLength(num) {\r\n  // Get digit length of e\r\n  const eSplit = num.toString().split(/[eE]/);\r\n  const len = (eSplit[0].split('.')[1] || '').length - +(eSplit[1] || 0);\r\n  return len > 0 ? len : 0;\r\n}\r\n\r\n/**\r\n * 把小数转成整数,如果是小数则放大成整数\r\n * @private\r\n * @param {*number} num 输入数\r\n */\r\nfunction float2Fixed(num) {\r\n  if (num.toString().indexOf('e') === -1) {\r\n    return Number(num.toString().replace('.', ''));\r\n  }\r\n  const dLen = digitLength(num);\r\n  return dLen > 0 ? strip(Number(num) * Math.pow(10, dLen)) : Number(num);\r\n}\r\n\r\n/**\r\n * 检测数字是否越界，如果越界给出提示\r\n * @private\r\n * @param {*number} num 输入数\r\n */\r\nfunction checkBoundary(num) {\r\n  if (_boundaryCheckingState) {\r\n    if (num > Number.MAX_SAFE_INTEGER || num < Number.MIN_SAFE_INTEGER) {\r\n      console.warn(`${num} 超出了精度限制，结果可能不正确`);\r\n    }\r\n  }\r\n}\r\n\r\n/**\r\n * 把递归操作扁平迭代化\r\n * @param {number[]} arr 要操作的数字数组\r\n * @param {function} operation 迭代操作\r\n * @private\r\n */\r\nfunction iteratorOperation(arr, operation) {\r\n  const [num1, num2, ...others] = arr;\r\n  let res = operation(num1, num2);\r\n\r\n  others.forEach((num) => {\r\n    res = operation(res, num);\r\n  });\r\n\r\n  return res;\r\n}\r\n\r\n/**\r\n * 高精度乘法\r\n * @export\r\n */\r\nexport function times(...nums) {\r\n  if (nums.length > 2) {\r\n    return iteratorOperation(nums, times);\r\n  }\r\n\r\n  const [num1, num2] = nums;\r\n  const num1Changed = float2Fixed(num1);\r\n  const num2Changed = float2Fixed(num2);\r\n  const baseNum = digitLength(num1) + digitLength(num2);\r\n  const leftValue = num1Changed * num2Changed;\r\n\r\n  checkBoundary(leftValue);\r\n\r\n  return leftValue / Math.pow(10, baseNum);\r\n}\r\n\r\n/**\r\n * 高精度加法\r\n * @export\r\n */\r\nexport function plus(...nums) {\r\n  if (nums.length > 2) {\r\n    return iteratorOperation(nums, plus);\r\n  }\r\n\r\n  const [num1, num2] = nums;\r\n  // 取最大的小数位\r\n  const baseNum = Math.pow(10, Math.max(digitLength(num1), digitLength(num2)));\r\n  // 把小数都转为整数然后再计算\r\n  return (times(num1, baseNum) + times(num2, baseNum)) / baseNum;\r\n}\r\n\r\n/**\r\n * 高精度减法\r\n * @export\r\n */\r\nexport function minus(...nums) {\r\n  if (nums.length > 2) {\r\n    return iteratorOperation(nums, minus);\r\n  }\r\n\r\n  const [num1, num2] = nums;\r\n  const baseNum = Math.pow(10, Math.max(digitLength(num1), digitLength(num2)));\r\n  return (times(num1, baseNum) - times(num2, baseNum)) / baseNum;\r\n}\r\n\r\n/**\r\n * 高精度除法\r\n * @export\r\n */\r\nexport function divide(...nums) {\r\n  if (nums.length > 2) {\r\n    return iteratorOperation(nums, divide);\r\n  }\r\n\r\n  const [num1, num2] = nums;\r\n  const num1Changed = float2Fixed(num1);\r\n  const num2Changed = float2Fixed(num2);\r\n  checkBoundary(num1Changed);\r\n  checkBoundary(num2Changed);\r\n  // 重要，这里必须用strip进行修正\r\n  return times(num1Changed / num2Changed, strip(Math.pow(10, digitLength(num2) - digitLength(num1))));\r\n}\r\n\r\n/**\r\n * 四舍五入\r\n * @export\r\n */\r\nexport function round(num, ratio) {\r\n  const base = Math.pow(10, ratio);\r\n  let result = divide(Math.round(Math.abs(times(num, base))), base);\r\n  if (num < 0 && result !== 0) {\r\n    result = times(result, -1);\r\n  }\r\n  // 位数不足则补0\r\n  return result;\r\n}\r\n\r\n/**\r\n * 是否进行边界检查，默认开启\r\n * @param flag 标记开关，true 为开启，false 为关闭，默认为 true\r\n * @export\r\n */\r\nexport function enableBoundaryChecking(flag = true) {\r\n  _boundaryCheckingState = flag;\r\n}\r\n\r\n\r\nexport default {\r\n  times,\r\n  plus,\r\n  minus,\r\n  divide,\r\n  round,\r\n  enableBoundaryChecking,\r\n};\r\n\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/libs/function/index.js",
    "content": "import { number, empty } from './test.js'\r\nimport { round } from './digit.js'\r\n/**\r\n * @description 如果value小于min，取min；如果value大于max，取max\r\n * @param {number} min\r\n * @param {number} max\r\n * @param {number} value\r\n */\r\nfunction range(min = 0, max = 0, value = 0) {\r\n\treturn Math.max(min, Math.min(max, Number(value)))\r\n}\r\n\r\n/**\r\n * @description 用于获取用户传递值的px值  如果用户传递了\"xxpx\"或者\"xxrpx\"，取出其数值部分，如果是\"xxxrpx\"还需要用过uni.upx2px进行转换\r\n * @param {number|string} value 用户传递值的px值\r\n * @param {boolean} unit\r\n * @returns {number|string}\r\n */\r\nfunction getPx(value, unit = false) {\r\n\tif (number(value)) {\r\n\t\treturn unit ? `${value}px` : Number(value)\r\n\t}\r\n\t// 如果带有rpx，先取出其数值部分，再转为px值\r\n\tif (/(rpx|upx)$/.test(value)) {\r\n\t\treturn unit ? `${uni.upx2px(parseInt(value))}px` : Number(uni.upx2px(parseInt(value)))\r\n\t}\r\n\treturn unit ? `${parseInt(value)}px` : parseInt(value)\r\n}\r\n\r\n/**\r\n * @description 进行延时，以达到可以简写代码的目的 比如: await uni.$uv.sleep(20)将会阻塞20ms\r\n * @param {number} value 堵塞时间 单位ms 毫秒\r\n * @returns {Promise} 返回promise\r\n */\r\nfunction sleep(value = 30) {\r\n\treturn new Promise((resolve) => {\r\n\t\tsetTimeout(() => {\r\n\t\t\tresolve()\r\n\t\t}, value)\r\n\t})\r\n}\r\n/**\r\n * @description 运行期判断平台\r\n * @returns {string} 返回所在平台(小写)\r\n * @link 运行期判断平台 https://uniapp.dcloud.io/frame?id=判断平台\r\n */\r\nfunction os() {\r\n\treturn uni.getSystemInfoSync().platform.toLowerCase()\r\n}\r\n/**\r\n * @description 获取系统信息同步接口\r\n * @link 获取系统信息同步接口 https://uniapp.dcloud.io/api/system/info?id=getsysteminfosync\r\n */\r\nfunction sys() {\r\n\treturn uni.getSystemInfoSync()\r\n}\r\n\r\n/**\r\n * @description 取一个区间数\r\n * @param {Number} min 最小值\r\n * @param {Number} max 最大值\r\n */\r\nfunction random(min, max) {\r\n\tif (min >= 0 && max > 0 && max >= min) {\r\n\t\tconst gab = max - min + 1\r\n\t\treturn Math.floor(Math.random() * gab + min)\r\n\t}\r\n\treturn 0\r\n}\r\n\r\n/**\r\n * @param {Number} len uuid的长度\r\n * @param {Boolean} firstU 将返回的首字母置为\"u\"\r\n * @param {Nubmer} radix 生成uuid的基数(意味着返回的字符串都是这个基数),2-二进制,8-八进制,10-十进制,16-十六进制\r\n */\r\nfunction guid(len = 32, firstU = true, radix = null) {\r\n\tconst chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('')\r\n\tconst uuid = []\r\n\tradix = radix || chars.length\r\n\r\n\tif (len) {\r\n\t\t// 如果指定uuid长度,只是取随机的字符,0|x为位运算,能去掉x的小数位,返回整数位\r\n\t\tfor (let i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix]\r\n\t} else {\r\n\t\tlet r\r\n\t\t// rfc4122标准要求返回的uuid中,某些位为固定的字符\r\n\t\tuuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'\r\n\t\tuuid[14] = '4'\r\n\r\n\t\tfor (let i = 0; i < 36; i++) {\r\n\t\t\tif (!uuid[i]) {\r\n\t\t\t\tr = 0 | Math.random() * 16\r\n\t\t\t\tuuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\t// 移除第一个字符,并用u替代,因为第一个字符为数值时,该guuid不能用作id或者class\r\n\tif (firstU) {\r\n\t\tuuid.shift()\r\n\t\treturn `u${uuid.join('')}`\r\n\t}\r\n\treturn uuid.join('')\r\n}\r\n\r\n/**\r\n* @description 获取父组件的参数，因为支付宝小程序不支持provide/inject的写法\r\n   this.$parent在非H5中，可以准确获取到父组件，但是在H5中，需要多次this.$parent.$parent.xxx\r\n   这里默认值等于undefined有它的含义，因为最顶层元素(组件)的$parent就是undefined，意味着不传name\r\n   值(默认为undefined)，就是查找最顶层的$parent\r\n*  @param {string|undefined} name 父组件的参数名\r\n*/\r\nfunction $parent(name = undefined) {\r\n\tlet parent = this.$parent\r\n\t// 通过while历遍，这里主要是为了H5需要多层解析的问题\r\n\twhile (parent) {\r\n\t\t// 父组件\r\n\t\tif (parent.$options && parent.$options.name !== name) {\r\n\t\t\t// 如果组件的name不相等，继续上一级寻找\r\n\t\t\tparent = parent.$parent\r\n\t\t} else {\r\n\t\t\treturn parent\r\n\t\t}\r\n\t}\r\n\treturn false\r\n}\r\n\r\n/**\r\n * @description 样式转换\r\n * 对象转字符串，或者字符串转对象\r\n * @param {object | string} customStyle 需要转换的目标\r\n * @param {String} target 转换的目的，object-转为对象，string-转为字符串\r\n * @returns {object|string}\r\n */\r\nfunction addStyle(customStyle, target = 'object') {\r\n\t// 字符串转字符串，对象转对象情形，直接返回\r\n\tif (empty(customStyle) || typeof(customStyle) === 'object' && target === 'object' || target === 'string' &&\r\n\t\ttypeof(customStyle) === 'string') {\r\n\t\treturn customStyle\r\n\t}\r\n\t// 字符串转对象\r\n\tif (target === 'object') {\r\n\t\t// 去除字符串样式中的两端空格(中间的空格不能去掉，比如padding: 20px 0如果去掉了就错了)，空格是无用的\r\n\t\tcustomStyle = trim(customStyle)\r\n\t\t// 根据\";\"将字符串转为数组形式\r\n\t\tconst styleArray = customStyle.split(';')\r\n\t\tconst style = {}\r\n\t\t// 历遍数组，拼接成对象\r\n\t\tfor (let i = 0; i < styleArray.length; i++) {\r\n\t\t\t// 'font-size:20px;color:red;'，如此最后字符串有\";\"的话，会导致styleArray最后一个元素为空字符串，这里需要过滤\r\n\t\t\tif (styleArray[i]) {\r\n\t\t\t\tconst item = styleArray[i].split(':')\r\n\t\t\t\tstyle[trim(item[0])] = trim(item[1])\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn style\r\n\t}\r\n\t// 这里为对象转字符串形式\r\n\tlet string = ''\r\n\tfor (const i in customStyle) {\r\n\t\t// 驼峰转为中划线的形式，否则css内联样式，无法识别驼峰样式属性名\r\n\t\tconst key = i.replace(/([A-Z])/g, '-$1').toLowerCase()\r\n\t\tstring += `${key}:${customStyle[i]};`\r\n\t}\r\n\t// 去除两端空格\r\n\treturn trim(string)\r\n}\r\n\r\n/**\r\n * @description 添加单位，如果有rpx，upx，%，px等单位结尾或者值为auto，直接返回，否则加上px单位结尾\r\n * @param {string|number} value 需要添加单位的值\r\n * @param {string} unit 添加的单位名 比如px\r\n */\r\nfunction addUnit(value = 'auto', unit = uni?.$uv?.config?.unit ? uni?.$uv?.config?.unit : 'px') {\r\n\tvalue = String(value)\r\n\t// 用uvui内置验证规则中的number判断是否为数值\r\n\treturn number(value) ? `${value}${unit}` : value\r\n}\r\n\r\n/**\r\n * @description 深度克隆\r\n * @param {object} obj 需要深度克隆的对象\r\n * @param cache 缓存\r\n * @returns {*} 克隆后的对象或者原值（不是对象）\r\n */\r\nfunction deepClone(obj, cache = new WeakMap()) {\r\n\tif (obj === null || typeof obj !== 'object') return obj;\r\n\tif (cache.has(obj)) return cache.get(obj);\r\n\tlet clone;\r\n\tif (obj instanceof Date) {\r\n\t\tclone = new Date(obj.getTime());\r\n\t} else if (obj instanceof RegExp) {\r\n\t\tclone = new RegExp(obj);\r\n\t} else if (obj instanceof Map) {\r\n\t\tclone = new Map(Array.from(obj, ([key, value]) => [key, deepClone(value, cache)]));\r\n\t} else if (obj instanceof Set) {\r\n\t\tclone = new Set(Array.from(obj, value => deepClone(value, cache)));\r\n\t} else if (Array.isArray(obj)) {\r\n\t\tclone = obj.map(value => deepClone(value, cache));\r\n\t} else if (Object.prototype.toString.call(obj) === '[object Object]') {\r\n\t\tclone = Object.create(Object.getPrototypeOf(obj));\r\n\t\tcache.set(obj, clone);\r\n\t\tfor (const [key, value] of Object.entries(obj)) {\r\n\t\t\tclone[key] = deepClone(value, cache);\r\n\t\t}\r\n\t} else {\r\n\t\tclone = Object.assign({}, obj);\r\n\t}\r\n\tcache.set(obj, clone);\r\n\treturn clone;\r\n}\r\n\r\n/**\r\n * @description JS对象深度合并\r\n * @param {object} target 需要拷贝的对象\r\n * @param {object} source 拷贝的来源对象\r\n * @returns {object|boolean} 深度合并后的对象或者false（入参有不是对象）\r\n */\r\nfunction deepMerge(target = {}, source = {}) {\r\n\ttarget = deepClone(target)\r\n\tif (typeof target !== 'object' || target === null || typeof source !== 'object' || source === null) return target;\r\n\tconst merged = Array.isArray(target) ? target.slice() : Object.assign({}, target);\r\n\tfor (const prop in source) {\r\n\t\tif (!source.hasOwnProperty(prop)) continue;\r\n\t\tconst sourceValue = source[prop];\r\n\t\tconst targetValue = merged[prop];\r\n\t\tif (sourceValue instanceof Date) {\r\n\t\t\tmerged[prop] = new Date(sourceValue);\r\n\t\t} else if (sourceValue instanceof RegExp) {\r\n\t\t\tmerged[prop] = new RegExp(sourceValue);\r\n\t\t} else if (sourceValue instanceof Map) {\r\n\t\t\tmerged[prop] = new Map(sourceValue);\r\n\t\t} else if (sourceValue instanceof Set) {\r\n\t\t\tmerged[prop] = new Set(sourceValue);\r\n\t\t} else if (typeof sourceValue === 'object' && sourceValue !== null) {\r\n\t\t\tmerged[prop] = deepMerge(targetValue, sourceValue);\r\n\t\t} else {\r\n\t\t\tmerged[prop] = sourceValue;\r\n\t\t}\r\n\t}\r\n\treturn merged;\r\n}\r\n\r\n/**\r\n * @description error提示\r\n * @param {*} err 错误内容\r\n */\r\nfunction error(err) {\r\n\t// 开发环境才提示，生产环境不会提示\r\n\tif (process.env.NODE_ENV === 'development') {\r\n\t\tconsole.error(`uvui提示：${err}`)\r\n\t}\r\n}\r\n\r\n/**\r\n * @description 打乱数组\r\n * @param {array} array 需要打乱的数组\r\n * @returns {array} 打乱后的数组\r\n */\r\nfunction randomArray(array = []) {\r\n\t// 原理是sort排序,Math.random()产生0<= x < 1之间的数,会导致x-0.05大于或者小于0\r\n\treturn array.sort(() => Math.random() - 0.5)\r\n}\r\n\r\n// padStart 的 polyfill，因为某些机型或情况，还无法支持es7的padStart，比如电脑版的微信小程序\r\n// 所以这里做一个兼容polyfill的兼容处理\r\nif (!String.prototype.padStart) {\r\n\t// 为了方便表示这里 fillString 用了ES6 的默认参数，不影响理解\r\n\tString.prototype.padStart = function(maxLength, fillString = ' ') {\r\n\t\tif (Object.prototype.toString.call(fillString) !== '[object String]') {\r\n\t\t\tthrow new TypeError(\r\n\t\t\t\t'fillString must be String'\r\n\t\t\t)\r\n\t\t}\r\n\t\tconst str = this\r\n\t\t// 返回 String(str) 这里是为了使返回的值是字符串字面量，在控制台中更符合直觉\r\n\t\tif (str.length >= maxLength) return String(str)\r\n\r\n\t\tconst fillLength = maxLength - str.length\r\n\t\tlet times = Math.ceil(fillLength / fillString.length)\r\n\t\twhile (times >>= 1) {\r\n\t\t\tfillString += fillString\r\n\t\t\tif (times === 1) {\r\n\t\t\t\tfillString += fillString\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn fillString.slice(0, fillLength) + str\r\n\t}\r\n}\r\n\r\n/**\r\n * @description 格式化时间\r\n * @param {String|Number} dateTime 需要格式化的时间戳\r\n * @param {String} fmt 格式化规则 yyyy:mm:dd|yyyy:mm|yyyy年mm月dd日|yyyy年mm月dd日 hh时MM分等,可自定义组合 默认yyyy-mm-dd\r\n * @returns {string} 返回格式化后的字符串\r\n */\r\nfunction timeFormat(dateTime = null, formatStr = 'yyyy-mm-dd') {\r\n\tlet date\r\n\t// 若传入时间为假值，则取当前时间\r\n\tif (!dateTime) {\r\n\t\tdate = new Date()\r\n\t}\r\n\t// 若为unix秒时间戳，则转为毫秒时间戳（逻辑有点奇怪，但不敢改，以保证历史兼容）\r\n\telse if (/^\\d{10}$/.test(dateTime?.toString().trim())) {\r\n\t\tdate = new Date(dateTime * 1000)\r\n\t}\r\n\t// 若用户传入字符串格式时间戳，new Date无法解析，需做兼容\r\n\telse if (typeof dateTime === 'string' && /^\\d+$/.test(dateTime.trim())) {\r\n\t\tdate = new Date(Number(dateTime))\r\n\t}\r\n\t// 处理平台性差异，在Safari/Webkit中，new Date仅支持/作为分割符的字符串时间\r\n\t// 处理 '2022-07-10 01:02:03'，跳过 '2022-07-10T01:02:03'\r\n\telse if (typeof dateTime === 'string' && dateTime.includes('-') && !dateTime.includes('T')) {\r\n\t\tdate = new Date(dateTime.replace(/-/g, '/'))\r\n\t}\r\n\t// 其他都认为符合 RFC 2822 规范\r\n\telse {\r\n\t\tdate = new Date(dateTime)\r\n\t}\r\n\r\n\tconst timeSource = {\r\n\t\t'y': date.getFullYear().toString(), // 年\r\n\t\t'm': (date.getMonth() + 1).toString().padStart(2, '0'), // 月\r\n\t\t'd': date.getDate().toString().padStart(2, '0'), // 日\r\n\t\t'h': date.getHours().toString().padStart(2, '0'), // 时\r\n\t\t'M': date.getMinutes().toString().padStart(2, '0'), // 分\r\n\t\t's': date.getSeconds().toString().padStart(2, '0') // 秒\r\n\t\t// 有其他格式化字符需求可以继续添加，必须转化成字符串\r\n\t}\r\n\r\n\tfor (const key in timeSource) {\r\n\t\tconst [ret] = new RegExp(`${key}+`).exec(formatStr) || []\r\n\t\tif (ret) {\r\n\t\t\t// 年可能只需展示两位\r\n\t\t\tconst beginIndex = key === 'y' && ret.length === 2 ? 2 : 0\r\n\t\t\tformatStr = formatStr.replace(ret, timeSource[key].slice(beginIndex))\r\n\t\t}\r\n\t}\r\n\r\n\treturn formatStr\r\n}\r\n\r\n/**\r\n * @description 时间戳转为多久之前\r\n * @param {String|Number} timestamp 时间戳\r\n * @param {String|Boolean} format\r\n * 格式化规则如果为时间格式字符串，超出一定时间范围，返回固定的时间格式；\r\n * 如果为布尔值false，无论什么时间，都返回多久以前的格式\r\n * @returns {string} 转化后的内容\r\n */\r\nfunction timeFrom(timestamp = null, format = 'yyyy-mm-dd') {\r\n\tif (timestamp == null) timestamp = Number(new Date())\r\n\ttimestamp = parseInt(timestamp)\r\n\t// 判断用户输入的时间戳是秒还是毫秒,一般前端js获取的时间戳是毫秒(13位),后端传过来的为秒(10位)\r\n\tif (timestamp.toString().length == 10) timestamp *= 1000\r\n\tlet timer = (new Date()).getTime() - timestamp\r\n\ttimer = parseInt(timer / 1000)\r\n\t// 如果小于5分钟,则返回\"刚刚\",其他以此类推\r\n\tlet tips = ''\r\n\tswitch (true) {\r\n\t\tcase timer < 300:\r\n\t\t\ttips = '刚刚'\r\n\t\t\tbreak\r\n\t\tcase timer >= 300 && timer < 3600:\r\n\t\t\ttips = `${parseInt(timer / 60)}分钟前`\r\n\t\t\tbreak\r\n\t\tcase timer >= 3600 && timer < 86400:\r\n\t\t\ttips = `${parseInt(timer / 3600)}小时前`\r\n\t\t\tbreak\r\n\t\tcase timer >= 86400 && timer < 2592000:\r\n\t\t\ttips = `${parseInt(timer / 86400)}天前`\r\n\t\t\tbreak\r\n\t\tdefault:\r\n\t\t\t// 如果format为false，则无论什么时间戳，都显示xx之前\r\n\t\t\tif (format === false) {\r\n\t\t\t\tif (timer >= 2592000 && timer < 365 * 86400) {\r\n\t\t\t\t\ttips = `${parseInt(timer / (86400 * 30))}个月前`\r\n\t\t\t\t} else {\r\n\t\t\t\t\ttips = `${parseInt(timer / (86400 * 365))}年前`\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\ttips = timeFormat(timestamp, format)\r\n\t\t\t}\r\n\t}\r\n\treturn tips\r\n}\r\n\r\n/**\r\n * @description 去除空格\r\n * @param String str 需要去除空格的字符串\r\n * @param String pos both(左右)|left|right|all 默认both\r\n */\r\nfunction trim(str, pos = 'both') {\r\n\tstr = String(str)\r\n\tif (pos == 'both') {\r\n\t\treturn str.replace(/^\\s+|\\s+$/g, '')\r\n\t}\r\n\tif (pos == 'left') {\r\n\t\treturn str.replace(/^\\s*/, '')\r\n\t}\r\n\tif (pos == 'right') {\r\n\t\treturn str.replace(/(\\s*$)/g, '')\r\n\t}\r\n\tif (pos == 'all') {\r\n\t\treturn str.replace(/\\s+/g, '')\r\n\t}\r\n\treturn str\r\n}\r\n\r\n/**\r\n * @description 对象转url参数\r\n * @param {object} data,对象\r\n * @param {Boolean} isPrefix,是否自动加上\"?\"\r\n * @param {string} arrayFormat 规则 indices|brackets|repeat|comma\r\n */\r\nfunction queryParams(data = {}, isPrefix = true, arrayFormat = 'brackets') {\r\n\tconst prefix = isPrefix ? '?' : ''\r\n\tconst _result = []\r\n\tif (['indices', 'brackets', 'repeat', 'comma'].indexOf(arrayFormat) == -1) arrayFormat = 'brackets'\r\n\tfor (const key in data) {\r\n\t\tconst value = data[key]\r\n\t\t// 去掉为空的参数\r\n\t\tif (['', undefined, null].indexOf(value) >= 0) {\r\n\t\t\tcontinue\r\n\t\t}\r\n\t\t// 如果值为数组，另行处理\r\n\t\tif (value.constructor === Array) {\r\n\t\t\t// e.g. {ids: [1, 2, 3]}\r\n\t\t\tswitch (arrayFormat) {\r\n\t\t\t\tcase 'indices':\r\n\t\t\t\t\t// 结果: ids[0]=1&ids[1]=2&ids[2]=3\r\n\t\t\t\t\tfor (let i = 0; i < value.length; i++) {\r\n\t\t\t\t\t\t_result.push(`${key}[${i}]=${value[i]}`)\r\n\t\t\t\t\t}\r\n\t\t\t\t\tbreak\r\n\t\t\t\tcase 'brackets':\r\n\t\t\t\t\t// 结果: ids[]=1&ids[]=2&ids[]=3\r\n\t\t\t\t\tvalue.forEach((_value) => {\r\n\t\t\t\t\t\t_result.push(`${key}[]=${_value}`)\r\n\t\t\t\t\t})\r\n\t\t\t\t\tbreak\r\n\t\t\t\tcase 'repeat':\r\n\t\t\t\t\t// 结果: ids=1&ids=2&ids=3\r\n\t\t\t\t\tvalue.forEach((_value) => {\r\n\t\t\t\t\t\t_result.push(`${key}=${_value}`)\r\n\t\t\t\t\t})\r\n\t\t\t\t\tbreak\r\n\t\t\t\tcase 'comma':\r\n\t\t\t\t\t// 结果: ids=1,2,3\r\n\t\t\t\t\tlet commaStr = ''\r\n\t\t\t\t\tvalue.forEach((_value) => {\r\n\t\t\t\t\t\tcommaStr += (commaStr ? ',' : '') + _value\r\n\t\t\t\t\t})\r\n\t\t\t\t\t_result.push(`${key}=${commaStr}`)\r\n\t\t\t\t\tbreak\r\n\t\t\t\tdefault:\r\n\t\t\t\t\tvalue.forEach((_value) => {\r\n\t\t\t\t\t\t_result.push(`${key}[]=${_value}`)\r\n\t\t\t\t\t})\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\t_result.push(`${key}=${value}`)\r\n\t\t}\r\n\t}\r\n\treturn _result.length ? prefix + _result.join('&') : ''\r\n}\r\n\r\n/**\r\n * 显示消息提示框\r\n * @param {String} title 提示的内容，长度与 icon 取值有关。\r\n * @param {Number} duration 提示的延迟时间，单位毫秒，默认：2000\r\n */\r\nfunction toast(title, duration = 2000) {\r\n\tuni.showToast({\r\n\t\ttitle: String(title),\r\n\t\ticon: 'none',\r\n\t\tduration\r\n\t})\r\n}\r\n\r\n/**\r\n * @description 根据主题type值,获取对应的图标\r\n * @param {String} type 主题名称,primary|info|error|warning|success\r\n * @param {boolean} fill 是否使用fill填充实体的图标\r\n */\r\nfunction type2icon(type = 'success', fill = false) {\r\n\t// 如果非预置值,默认为success\r\n\tif (['primary', 'info', 'error', 'warning', 'success'].indexOf(type) == -1) type = 'success'\r\n\tlet iconName = ''\r\n\t// 目前(2019-12-12),info和primary使用同一个图标\r\n\tswitch (type) {\r\n\t\tcase 'primary':\r\n\t\t\ticonName = 'info-circle'\r\n\t\t\tbreak\r\n\t\tcase 'info':\r\n\t\t\ticonName = 'info-circle'\r\n\t\t\tbreak\r\n\t\tcase 'error':\r\n\t\t\ticonName = 'close-circle'\r\n\t\t\tbreak\r\n\t\tcase 'warning':\r\n\t\t\ticonName = 'error-circle'\r\n\t\t\tbreak\r\n\t\tcase 'success':\r\n\t\t\ticonName = 'checkmark-circle'\r\n\t\t\tbreak\r\n\t\tdefault:\r\n\t\t\ticonName = 'checkmark-circle'\r\n\t}\r\n\t// 是否是实体类型,加上-fill,在icon组件库中,实体的类名是后面加-fill的\r\n\tif (fill) iconName += '-fill'\r\n\treturn iconName\r\n}\r\n\r\n/**\r\n * @description 数字格式化\r\n * @param {number|string} number 要格式化的数字\r\n * @param {number} decimals 保留几位小数\r\n * @param {string} decimalPoint 小数点符号\r\n * @param {string} thousandsSeparator 千分位符号\r\n * @returns {string} 格式化后的数字\r\n */\r\nfunction priceFormat(number, decimals = 0, decimalPoint = '.', thousandsSeparator = ',') {\r\n\tnumber = (`${number}`).replace(/[^0-9+-Ee.]/g, '')\r\n\tconst n = !isFinite(+number) ? 0 : +number\r\n\tconst prec = !isFinite(+decimals) ? 0 : Math.abs(decimals)\r\n\tconst sep = (typeof thousandsSeparator === 'undefined') ? ',' : thousandsSeparator\r\n\tconst dec = (typeof decimalPoint === 'undefined') ? '.' : decimalPoint\r\n\tlet s = ''\r\n\r\n\ts = (prec ? round(n, prec) + '' : `${Math.round(n)}`).split('.')\r\n\tconst re = /(-?\\d+)(\\d{3})/\r\n\twhile (re.test(s[0])) {\r\n\t\ts[0] = s[0].replace(re, `$1${sep}$2`)\r\n\t}\r\n\r\n\tif ((s[1] || '').length < prec) {\r\n\t\ts[1] = s[1] || ''\r\n\t\ts[1] += new Array(prec - s[1].length + 1).join('0')\r\n\t}\r\n\treturn s.join(dec)\r\n}\r\n\r\n/**\r\n * @description 获取duration值\r\n * 如果带有ms或者s直接返回，如果大于一定值，认为是ms单位，小于一定值，认为是s单位\r\n * 比如以30位阈值，那么300大于30，可以理解为用户想要的是300ms，而不是想花300s去执行一个动画\r\n * @param {String|number} value 比如: \"1s\"|\"100ms\"|1|100\r\n * @param {boolean} unit  提示: 如果是false 默认返回number\r\n * @return {string|number}\r\n */\r\nfunction getDuration(value, unit = true) {\r\n\tconst valueNum = parseInt(value)\r\n\tif (unit) {\r\n\t\tif (/s$/.test(value)) return value\r\n\t\treturn value > 30 ? `${value}ms` : `${value}s`\r\n\t}\r\n\tif (/ms$/.test(value)) return valueNum\r\n\tif (/s$/.test(value)) return valueNum > 30 ? valueNum : valueNum * 1000\r\n\treturn valueNum\r\n}\r\n\r\n/**\r\n * @description 日期的月或日补零操作\r\n * @param {String} value 需要补零的值\r\n */\r\nfunction padZero(value) {\r\n\treturn `00${value}`.slice(-2)\r\n}\r\n\r\n/**\r\n * @description 在uv-form的子组件内容发生变化，或者失去焦点时，尝试通知uv-form执行校验方法\r\n * @param {*} instance\r\n * @param {*} event\r\n */\r\nfunction formValidate(instance, event) {\r\n\tconst formItem = $parent.call(instance, 'uv-form-item')\r\n\tconst form = $parent.call(instance, 'uv-form')\r\n\t// 如果发生变化的input或者textarea等，其父组件中有uv-form-item或者uv-form等，就执行form的validate方法\r\n\t// 同时将form-item的pros传递给form，让其进行精确对象验证\r\n\tif (formItem && form) {\r\n\t\tform.validateField(formItem.prop, () => {}, event)\r\n\t}\r\n}\r\n\r\n/**\r\n * @description 获取某个对象下的属性，用于通过类似'a.b.c'的形式去获取一个对象的的属性的形式\r\n * @param {object} obj 对象\r\n * @param {string} key 需要获取的属性字段\r\n * @returns {*}\r\n */\r\nfunction getProperty(obj, key) {\r\n\tif (!obj) {\r\n\t\treturn\r\n\t}\r\n\tif (typeof key !== 'string' || key === '') {\r\n\t\treturn ''\r\n\t}\r\n\tif (key.indexOf('.') !== -1) {\r\n\t\tconst keys = key.split('.')\r\n\t\tlet firstObj = obj[keys[0]] || {}\r\n\r\n\t\tfor (let i = 1; i < keys.length; i++) {\r\n\t\t\tif (firstObj) {\r\n\t\t\t\tfirstObj = firstObj[keys[i]]\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn firstObj\r\n\t}\r\n\treturn obj[key]\r\n}\r\n\r\n/**\r\n * @description 设置对象的属性值，如果'a.b.c'的形式进行设置\r\n * @param {object} obj 对象\r\n * @param {string} key 需要设置的属性\r\n * @param {string} value 设置的值\r\n */\r\nfunction setProperty(obj, key, value) {\r\n\tif (!obj) {\r\n\t\treturn\r\n\t}\r\n\t// 递归赋值\r\n\tconst inFn = function(_obj, keys, v) {\r\n\t\t// 最后一个属性key\r\n\t\tif (keys.length === 1) {\r\n\t\t\t_obj[keys[0]] = v\r\n\t\t\treturn\r\n\t\t}\r\n\t\t// 0~length-1个key\r\n\t\twhile (keys.length > 1) {\r\n\t\t\tconst k = keys[0]\r\n\t\t\tif (!_obj[k] || (typeof _obj[k] !== 'object')) {\r\n\t\t\t\t_obj[k] = {}\r\n\t\t\t}\r\n\t\t\tconst key = keys.shift()\r\n\t\t\t// 自调用判断是否存在属性，不存在则自动创建对象\r\n\t\t\tinFn(_obj[k], keys, v)\r\n\t\t}\r\n\t}\r\n\r\n\tif (typeof key !== 'string' || key === '') {\r\n\r\n\t} else if (key.indexOf('.') !== -1) { // 支持多层级赋值操作\r\n\t\tconst keys = key.split('.')\r\n\t\tinFn(obj, keys, value)\r\n\t} else {\r\n\t\tobj[key] = value\r\n\t}\r\n}\r\n\r\n/**\r\n * @description 获取当前页面路径\r\n */\r\nfunction page() {\r\n\tconst pages = getCurrentPages();\r\n\tconst route = pages[pages.length - 1]?.route;\r\n\t// 某些特殊情况下(比如页面进行redirectTo时的一些时机)，pages可能为空数组\r\n\treturn `/${route ? route : ''}`\r\n}\r\n\r\n/**\r\n * @description 获取当前路由栈实例数组\r\n */\r\nfunction pages() {\r\n\tconst pages = getCurrentPages()\r\n\treturn pages\r\n}\r\n\r\n/**\r\n * 获取页面历史栈指定层实例\r\n * @param back {number} [0] - 0或者负数，表示获取历史栈的哪一层，0表示获取当前页面实例，-1 表示获取上一个页面实例。默认0。\r\n */\r\nfunction getHistoryPage(back = 0) {\r\n\tconst pages = getCurrentPages()\r\n\tconst len = pages.length\r\n\treturn pages[len - 1 + back]\r\n}\r\n\r\n\r\n\r\n/**\r\n * @description 修改uvui内置属性值\r\n * @param {object} props 修改内置props属性\r\n * @param {object} config 修改内置config属性\r\n * @param {object} color 修改内置color属性\r\n * @param {object} zIndex 修改内置zIndex属性\r\n */\r\nfunction setConfig({\r\n\tprops = {},\r\n\tconfig = {},\r\n\tcolor = {},\r\n\tzIndex = {}\r\n}) {\r\n\tconst {\r\n\t\tdeepMerge,\r\n\t} = uni.$uv\r\n\tuni.$uv.config = deepMerge(uni.$uv.config, config)\r\n\tuni.$uv.props = deepMerge(uni.$uv.props, props)\r\n\tuni.$uv.color = deepMerge(uni.$uv.color, color)\r\n\tuni.$uv.zIndex = deepMerge(uni.$uv.zIndex, zIndex)\r\n}\r\n\r\nexport {\r\n\trange,\r\n\tgetPx,\r\n\tsleep,\r\n\tos,\r\n\tsys,\r\n\trandom,\r\n\tguid,\r\n\t$parent,\r\n\taddStyle,\r\n\taddUnit,\r\n\tdeepClone,\r\n\tdeepMerge,\r\n\terror,\r\n\trandomArray,\r\n\ttimeFormat,\r\n\ttimeFrom,\r\n\ttrim,\r\n\tqueryParams,\r\n\ttoast,\r\n\ttype2icon,\r\n\tpriceFormat,\r\n\tgetDuration,\r\n\tpadZero,\r\n\tformValidate,\r\n\tgetProperty,\r\n\tsetProperty,\r\n\tpage,\r\n\tpages,\r\n\tgetHistoryPage,\r\n\tsetConfig\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/libs/function/platform.js",
    "content": "/**\r\n * 注意：\r\n * 此部分内容，在vue-cli模式下，需要在vue.config.js加入如下内容才有效：\r\n * module.exports = {\r\n *     transpileDependencies: ['uview-v2']\r\n * }\r\n */\r\n\r\nlet platform = 'none'\r\n\r\n// #ifdef VUE3\r\nplatform = 'vue3'\r\n// #endif\r\n\r\n// #ifdef VUE2\r\nplatform = 'vue2'\r\n// #endif\r\n\r\n// #ifdef APP-PLUS\r\nplatform = 'plus'\r\n// #endif\r\n\r\n// #ifdef APP-NVUE\r\nplatform = 'nvue'\r\n// #endif\r\n\r\n// #ifdef H5\r\nplatform = 'h5'\r\n// #endif\r\n\r\n// #ifdef MP-WEIXIN\r\nplatform = 'weixin'\r\n// #endif\r\n\r\n// #ifdef MP-ALIPAY\r\nplatform = 'alipay'\r\n// #endif\r\n\r\n// #ifdef MP-BAIDU\r\nplatform = 'baidu'\r\n// #endif\r\n\r\n// #ifdef MP-TOUTIAO\r\nplatform = 'toutiao'\r\n// #endif\r\n\r\n// #ifdef MP-QQ\r\nplatform = 'qq'\r\n// #endif\r\n\r\n// #ifdef MP-KUAISHOU\r\nplatform = 'kuaishou'\r\n// #endif\r\n\r\n// #ifdef MP-360\r\nplatform = '360'\r\n// #endif\r\n\r\n// #ifdef MP\r\nplatform = 'mp'\r\n// #endif\r\n\r\n// #ifdef QUICKAPP-WEBVIEW\r\nplatform = 'quickapp-webview'\r\n// #endif\r\n\r\n// #ifdef QUICKAPP-WEBVIEW-HUAWEI\r\nplatform = 'quickapp-webview-huawei'\r\n// #endif\r\n\r\n// #ifdef QUICKAPP-WEBVIEW-UNION\r\nplatform = 'quckapp-webview-union'\r\n// #endif\r\n\r\nexport default platform\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/libs/function/test.js",
    "content": "/**\r\n * 验证电子邮箱格式\r\n */\r\nfunction email(value) {\r\n    return /^\\w+((-\\w+)|(\\.\\w+))*\\@[A-Za-z0-9]+((\\.|-)[A-Za-z0-9]+)*\\.[A-Za-z0-9]+$/.test(value)\r\n}\r\n\r\n/**\r\n * 验证手机格式\r\n */\r\nfunction mobile(value) {\r\n    return /^1([3589]\\d|4[5-9]|6[1-2,4-7]|7[0-8])\\d{8}$/.test(value)\r\n}\r\n\r\n/**\r\n * 验证URL格式\r\n */\r\nfunction url(value) {\r\n    return /^((https|http|ftp|rtsp|mms):\\/\\/)(([0-9a-zA-Z_!~*'().&=+$%-]+: )?[0-9a-zA-Z_!~*'().&=+$%-]+@)?(([0-9]{1,3}.){3}[0-9]{1,3}|([0-9a-zA-Z_!~*'()-]+.)*([0-9a-zA-Z][0-9a-zA-Z-]{0,61})?[0-9a-zA-Z].[a-zA-Z]{2,6})(:[0-9]{1,4})?((\\/?)|(\\/[0-9a-zA-Z_!~*'().;?:@&=+$,%#-]+)+\\/?)$/\r\n        .test(value)\r\n}\r\n\r\n/**\r\n * 验证日期格式\r\n */\r\nfunction date(value) {\r\n    if (!value) return false\r\n    // 判断是否数值或者字符串数值(意味着为时间戳)，转为数值，否则new Date无法识别字符串时间戳\r\n    if (number(value)) value = +value\r\n    return !/Invalid|NaN/.test(new Date(value).toString())\r\n}\r\n\r\n/**\r\n * 验证ISO类型的日期格式\r\n */\r\nfunction dateISO(value) {\r\n    return /^\\d{4}[\\/\\-](0?[1-9]|1[012])[\\/\\-](0?[1-9]|[12][0-9]|3[01])$/.test(value)\r\n}\r\n\r\n/**\r\n * 验证十进制数字\r\n */\r\nfunction number(value) {\r\n    return /^[\\+-]?(\\d+\\.?\\d*|\\.\\d+|\\d\\.\\d+e\\+\\d+)$/.test(value)\r\n}\r\n\r\n/**\r\n * 验证字符串\r\n */\r\nfunction string(value) {\r\n    return typeof value === 'string'\r\n}\r\n\r\n/**\r\n * 验证整数\r\n */\r\nfunction digits(value) {\r\n    return /^\\d+$/.test(value)\r\n}\r\n\r\n/**\r\n * 验证身份证号码\r\n */\r\nfunction idCard(value) {\r\n    return /^[1-9]\\d{5}[1-9]\\d{3}((0\\d)|(1[0-2]))(([0|1|2]\\d)|3[0-1])\\d{3}([0-9]|X)$/.test(\r\n        value\r\n    )\r\n}\r\n\r\n/**\r\n * 是否车牌号\r\n */\r\nfunction carNo(value) {\r\n    // 新能源车牌\r\n    const xreg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}(([0-9]{5}[DF]$)|([DF][A-HJ-NP-Z0-9][0-9]{4}$))/\r\n    // 旧车牌\r\n    const creg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳]{1}$/\r\n    if (value.length === 7) {\r\n        return creg.test(value)\r\n    } if (value.length === 8) {\r\n        return xreg.test(value)\r\n    }\r\n    return false\r\n}\r\n\r\n/**\r\n * 金额,只允许2位小数\r\n */\r\nfunction amount(value) {\r\n    // 金额，只允许保留两位小数\r\n    return /^[1-9]\\d*(,\\d{3})*(\\.\\d{1,2})?$|^0\\.\\d{1,2}$/.test(value)\r\n}\r\n\r\n/**\r\n * 中文\r\n */\r\nfunction chinese(value) {\r\n    const reg = /^[\\u4e00-\\u9fa5]+$/gi\r\n    return reg.test(value)\r\n}\r\n\r\n/**\r\n * 只能输入字母\r\n */\r\nfunction letter(value) {\r\n    return /^[a-zA-Z]*$/.test(value)\r\n}\r\n\r\n/**\r\n * 只能是字母或者数字\r\n */\r\nfunction enOrNum(value) {\r\n    // 英文或者数字\r\n    const reg = /^[0-9a-zA-Z]*$/g\r\n    return reg.test(value)\r\n}\r\n\r\n/**\r\n * 验证是否包含某个值\r\n */\r\nfunction contains(value, param) {\r\n    return value.indexOf(param) >= 0\r\n}\r\n\r\n/**\r\n * 验证一个值范围[min, max]\r\n */\r\nfunction range(value, param) {\r\n    return value >= param[0] && value <= param[1]\r\n}\r\n\r\n/**\r\n * 验证一个长度范围[min, max]\r\n */\r\nfunction rangeLength(value, param) {\r\n    return value.length >= param[0] && value.length <= param[1]\r\n}\r\n\r\n/**\r\n * 是否固定电话\r\n */\r\nfunction landline(value) {\r\n    const reg = /^\\d{3,4}-\\d{7,8}(-\\d{3,4})?$/\r\n    return reg.test(value)\r\n}\r\n\r\n/**\r\n * 判断是否为空\r\n */\r\nfunction empty(value) {\r\n    switch (typeof value) {\r\n    case 'undefined':\r\n        return true\r\n    case 'string':\r\n        if (value.replace(/(^[ \\t\\n\\r]*)|([ \\t\\n\\r]*$)/g, '').length == 0) return true\r\n        break\r\n    case 'boolean':\r\n        if (!value) return true\r\n        break\r\n    case 'number':\r\n        if (value === 0 || isNaN(value)) return true\r\n        break\r\n    case 'object':\r\n        if (value === null || value.length === 0) return true\r\n        for (const i in value) {\r\n            return false\r\n        }\r\n        return true\r\n    }\r\n    return false\r\n}\r\n\r\n/**\r\n * 是否json字符串\r\n */\r\nfunction jsonString(value) {\r\n    if (typeof value === 'string') {\r\n        try {\r\n            const obj = JSON.parse(value)\r\n            if (typeof obj === 'object' && obj) {\r\n                return true\r\n            }\r\n            return false\r\n        } catch (e) {\r\n            return false\r\n        }\r\n    }\r\n    return false\r\n}\r\n\r\n/**\r\n * 是否数组\r\n */\r\nfunction array(value) {\r\n    if (typeof Array.isArray === 'function') {\r\n        return Array.isArray(value)\r\n    }\r\n    return Object.prototype.toString.call(value) === '[object Array]'\r\n}\r\n\r\n/**\r\n * 是否对象\r\n */\r\nfunction object(value) {\r\n    return Object.prototype.toString.call(value) === '[object Object]'\r\n}\r\n\r\n/**\r\n * 是否短信验证码\r\n */\r\nfunction code(value, len = 6) {\r\n    return new RegExp(`^\\\\d{${len}}$`).test(value)\r\n}\r\n\r\n/**\r\n * 是否函数方法\r\n * @param {Object} value\r\n */\r\nfunction func(value) {\r\n    return typeof value === 'function'\r\n}\r\n\r\n/**\r\n * 是否promise对象\r\n * @param {Object} value\r\n */\r\nfunction promise(value) {\r\n    return object(value) && func(value.then) && func(value.catch)\r\n}\r\n\r\n/** 是否图片格式\r\n * @param {Object} value\r\n */\r\nfunction image(value) {\r\n    const newValue = value.split('?')[0]\r\n    const IMAGE_REGEXP = /\\.(jpeg|jpg|gif|png|svg|webp|jfif|bmp|dpg)/i\r\n    return IMAGE_REGEXP.test(newValue)\r\n}\r\n\r\n/**\r\n * 是否视频格式\r\n * @param {Object} value\r\n */\r\nfunction video(value) {\r\n    const VIDEO_REGEXP = /\\.(mp4|mpg|mpeg|dat|asf|avi|rm|rmvb|mov|wmv|flv|mkv|m3u8)/i\r\n    return VIDEO_REGEXP.test(value)\r\n}\r\n\r\n/**\r\n * 是否为正则对象\r\n * @param {Object}\r\n * @return {Boolean}\r\n */\r\nfunction regExp(o) {\r\n    return o && Object.prototype.toString.call(o) === '[object RegExp]'\r\n}\r\n\r\nexport {\r\n    email,\r\n    mobile,\r\n    url,\r\n    date,\r\n    dateISO,\r\n    number,\r\n    digits,\r\n    idCard,\r\n    carNo,\r\n    amount,\r\n    chinese,\r\n    letter,\r\n    enOrNum,\r\n    contains,\r\n    range,\r\n    rangeLength,\r\n    empty,\r\n    jsonString,\r\n    landline,\r\n    object,\r\n    array,\r\n    code,\r\n    func,\r\n    promise,\r\n    video,\r\n    image,\r\n    regExp,\r\n    string\r\n}\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/libs/function/throttle.js",
    "content": "let timer; let\r\n    flag\r\n/**\r\n * 节流原理：在一定时间内，只能触发一次\r\n *\r\n * @param {Function} func 要执行的回调函数\r\n * @param {Number} wait 延时的时间\r\n * @param {Boolean} immediate 是否立即执行\r\n * @return null\r\n */\r\nfunction throttle(func, wait = 500, immediate = true) {\r\n    if (immediate) {\r\n        if (!flag) {\r\n            flag = true\r\n            // 如果是立即执行，则在wait毫秒内开始时执行\r\n            typeof func === 'function' && func()\r\n            timer = setTimeout(() => {\r\n                flag = false\r\n            }, wait)\r\n        }\r\n    } else if (!flag) {\r\n        flag = true\r\n        // 如果是非立即执行，则在wait毫秒内的结束处执行\r\n        timer = setTimeout(() => {\r\n            flag = false\r\n            typeof func === 'function' && func()\r\n        }, wait)\r\n    }\r\n}\r\nexport default throttle\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/libs/luch-request/adapters/index.js",
    "content": "import buildURL from '../helpers/buildURL'\r\nimport buildFullPath from '../core/buildFullPath'\r\nimport settle from '../core/settle'\r\nimport { isUndefined } from '../utils'\r\n\r\n/**\r\n * 返回可选值存在的配置\r\n * @param {Array} keys - 可选值数组\r\n * @param {Object} config2 - 配置\r\n * @return {{}} - 存在的配置项\r\n */\r\nconst mergeKeys = (keys, config2) => {\r\n    const config = {}\r\n    keys.forEach((prop) => {\r\n        if (!isUndefined(config2[prop])) {\r\n            config[prop] = config2[prop]\r\n        }\r\n    })\r\n    return config\r\n}\r\nexport default (config) => new Promise((resolve, reject) => {\r\n    const fullPath = buildURL(buildFullPath(config.baseURL, config.url), config.params)\r\n    const _config = {\r\n        url: fullPath,\r\n        header: config.header,\r\n        complete: (response) => {\r\n            config.fullPath = fullPath\r\n            response.config = config\r\n            try {\r\n                // 对可能字符串不是json 的情况容错\r\n                if (typeof response.data === 'string') {\r\n                    response.data = JSON.parse(response.data)\r\n                }\r\n                // eslint-disable-next-line no-empty\r\n            } catch (e) {\r\n            }\r\n            settle(resolve, reject, response)\r\n        }\r\n    }\r\n    let requestTask\r\n    if (config.method === 'UPLOAD') {\r\n        delete _config.header['content-type']\r\n        delete _config.header['Content-Type']\r\n        const otherConfig = {\r\n        // #ifdef MP-ALIPAY\r\n            fileType: config.fileType,\r\n            // #endif\r\n            filePath: config.filePath,\r\n            name: config.name\r\n        }\r\n        const optionalKeys = [\r\n        // #ifdef APP-PLUS || H5\r\n            'files',\r\n            // #endif\r\n            // #ifdef H5\r\n            'file',\r\n            // #endif\r\n            // #ifdef H5 || APP-PLUS\r\n            'timeout',\r\n            // #endif\r\n            'formData'\r\n        ]\r\n        requestTask = uni.uploadFile({ ..._config, ...otherConfig, ...mergeKeys(optionalKeys, config) })\r\n    } else if (config.method === 'DOWNLOAD') {\r\n        // #ifdef H5 || APP-PLUS\r\n        if (!isUndefined(config.timeout)) {\r\n            _config.timeout = config.timeout\r\n        }\r\n        // #endif\r\n        requestTask = uni.downloadFile(_config)\r\n    } else {\r\n        const optionalKeys = [\r\n            'data',\r\n            'method',\r\n            // #ifdef H5 || APP-PLUS || MP-ALIPAY || MP-WEIXIN\r\n            'timeout',\r\n            // #endif\r\n            'dataType',\r\n            // #ifndef MP-ALIPAY\r\n            'responseType',\r\n            // #endif\r\n            // #ifdef APP-PLUS\r\n            'sslVerify',\r\n            // #endif\r\n            // #ifdef H5\r\n            'withCredentials',\r\n            // #endif\r\n            // #ifdef APP-PLUS\r\n            'firstIpv4'\r\n        // #endif\r\n        ]\r\n        requestTask = uni.request({ ..._config, ...mergeKeys(optionalKeys, config) })\r\n    }\r\n    if (config.getTask) {\r\n        config.getTask(requestTask, config)\r\n    }\r\n})\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/libs/luch-request/core/InterceptorManager.js",
    "content": "'use strict'\r\n\r\nfunction InterceptorManager() {\r\n    this.handlers = []\r\n}\r\n\r\n/**\r\n * Add a new interceptor to the stack\r\n *\r\n * @param {Function} fulfilled The function to handle `then` for a `Promise`\r\n * @param {Function} rejected The function to handle `reject` for a `Promise`\r\n *\r\n * @return {Number} An ID used to remove interceptor later\r\n */\r\nInterceptorManager.prototype.use = function use(fulfilled, rejected) {\r\n    this.handlers.push({\r\n        fulfilled,\r\n        rejected\r\n    })\r\n    return this.handlers.length - 1\r\n}\r\n\r\n/**\r\n * Remove an interceptor from the stack\r\n *\r\n * @param {Number} id The ID that was returned by `use`\r\n */\r\nInterceptorManager.prototype.eject = function eject(id) {\r\n    if (this.handlers[id]) {\r\n        this.handlers[id] = null\r\n    }\r\n}\r\n\r\n/**\r\n * Iterate over all the registered interceptors\r\n *\r\n * This method is particularly useful for skipping over any\r\n * interceptors that may have become `null` calling `eject`.\r\n *\r\n * @param {Function} fn The function to call for each interceptor\r\n */\r\nInterceptorManager.prototype.forEach = function forEach(fn) {\r\n    this.handlers.forEach((h) => {\r\n        if (h !== null) {\r\n            fn(h)\r\n        }\r\n    })\r\n}\r\n\r\nexport default InterceptorManager\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/libs/luch-request/core/Request.js",
    "content": "/**\r\n * @Class Request\r\n * @description luch-request http请求插件\r\n * @version 3.0.7\r\n * @Author lu-ch\r\n * @Date 2021-09-04\r\n * @Email webwork.s@qq.com\r\n * 文档: https://www.quanzhan.co/luch-request/\r\n * github: https://github.com/lei-mu/luch-request\r\n * DCloud: http://ext.dcloud.net.cn/plugin?id=392\r\n * HBuilderX: beat-3.0.4 alpha-3.0.4\r\n */\r\n\r\nimport dispatchRequest from './dispatchRequest'\r\nimport InterceptorManager from './InterceptorManager'\r\nimport mergeConfig from './mergeConfig'\r\nimport defaults from './defaults'\r\nimport { isPlainObject } from '../utils'\r\nimport clone from '../utils/clone'\r\n\r\nexport default class Request {\r\n    /**\r\n   * @param {Object} arg - 全局配置\r\n   * @param {String} arg.baseURL - 全局根路径\r\n   * @param {Object} arg.header - 全局header\r\n   * @param {String} arg.method = [GET|POST|PUT|DELETE|CONNECT|HEAD|OPTIONS|TRACE] - 全局默认请求方式\r\n   * @param {String} arg.dataType = [json] - 全局默认的dataType\r\n   * @param {String} arg.responseType = [text|arraybuffer] - 全局默认的responseType。支付宝小程序不支持\r\n   * @param {Object} arg.custom - 全局默认的自定义参数\r\n   * @param {Number} arg.timeout - 全局默认的超时时间，单位 ms。默认60000。H5(HBuilderX 2.9.9+)、APP(HBuilderX 2.9.9+)、微信小程序（2.10.0）、支付宝小程序\r\n   * @param {Boolean} arg.sslVerify - 全局默认的是否验证 ssl 证书。默认true.仅App安卓端支持（HBuilderX 2.3.3+）\r\n   * @param {Boolean} arg.withCredentials - 全局默认的跨域请求时是否携带凭证（cookies）。默认false。仅H5支持（HBuilderX 2.6.15+）\r\n   * @param {Boolean} arg.firstIpv4 - 全DNS解析时优先使用ipv4。默认false。仅 App-Android 支持 (HBuilderX 2.8.0+)\r\n   * @param {Function(statusCode):Boolean} arg.validateStatus - 全局默认的自定义验证器。默认statusCode >= 200 && statusCode < 300\r\n   */\r\n    constructor(arg = {}) {\r\n        if (!isPlainObject(arg)) {\r\n            arg = {}\r\n            console.warn('设置全局参数必须接收一个Object')\r\n        }\r\n        this.config = clone({ ...defaults, ...arg })\r\n        this.interceptors = {\r\n            request: new InterceptorManager(),\r\n            response: new InterceptorManager()\r\n        }\r\n    }\r\n\r\n    /**\r\n   * @Function\r\n   * @param {Request~setConfigCallback} f - 设置全局默认配置\r\n   */\r\n    setConfig(f) {\r\n        this.config = f(this.config)\r\n    }\r\n\r\n    middleware(config) {\r\n        config = mergeConfig(this.config, config)\r\n        const chain = [dispatchRequest, undefined]\r\n        let promise = Promise.resolve(config)\r\n\r\n        this.interceptors.request.forEach((interceptor) => {\r\n            chain.unshift(interceptor.fulfilled, interceptor.rejected)\r\n        })\r\n\r\n        this.interceptors.response.forEach((interceptor) => {\r\n            chain.push(interceptor.fulfilled, interceptor.rejected)\r\n        })\r\n\r\n        while (chain.length) {\r\n            promise = promise.then(chain.shift(), chain.shift())\r\n        }\r\n\r\n        return promise\r\n    }\r\n\r\n    /**\r\n   * @Function\r\n   * @param {Object} config - 请求配置项\r\n   * @prop {String} options.url - 请求路径\r\n   * @prop {Object} options.data - 请求参数\r\n   * @prop {Object} [options.responseType = config.responseType] [text|arraybuffer] - 响应的数据类型\r\n   * @prop {Object} [options.dataType = config.dataType] - 如果设为 json，会尝试对返回的数据做一次 JSON.parse\r\n   * @prop {Object} [options.header = config.header] - 请求header\r\n   * @prop {Object} [options.method = config.method] - 请求方法\r\n   * @returns {Promise<unknown>}\r\n   */\r\n    request(config = {}) {\r\n        return this.middleware(config)\r\n    }\r\n\r\n    get(url, options = {}) {\r\n        return this.middleware({\r\n            url,\r\n            method: 'GET',\r\n            ...options\r\n        })\r\n    }\r\n\r\n    post(url, data, options = {}) {\r\n        return this.middleware({\r\n            url,\r\n            data,\r\n            method: 'POST',\r\n            ...options\r\n        })\r\n    }\r\n\r\n    // #ifndef MP-ALIPAY\r\n    put(url, data, options = {}) {\r\n        return this.middleware({\r\n            url,\r\n            data,\r\n            method: 'PUT',\r\n            ...options\r\n        })\r\n    }\r\n\r\n    // #endif\r\n\r\n    // #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-BAIDU\r\n    delete(url, data, options = {}) {\r\n        return this.middleware({\r\n            url,\r\n            data,\r\n            method: 'DELETE',\r\n            ...options\r\n        })\r\n    }\r\n\r\n    // #endif\r\n\r\n    // #ifdef H5 || MP-WEIXIN\r\n    connect(url, data, options = {}) {\r\n        return this.middleware({\r\n            url,\r\n            data,\r\n            method: 'CONNECT',\r\n            ...options\r\n        })\r\n    }\r\n\r\n    // #endif\r\n\r\n    // #ifdef  H5 || MP-WEIXIN || MP-BAIDU\r\n    head(url, data, options = {}) {\r\n        return this.middleware({\r\n            url,\r\n            data,\r\n            method: 'HEAD',\r\n            ...options\r\n        })\r\n    }\r\n\r\n    // #endif\r\n\r\n    // #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-BAIDU\r\n    options(url, data, options = {}) {\r\n        return this.middleware({\r\n            url,\r\n            data,\r\n            method: 'OPTIONS',\r\n            ...options\r\n        })\r\n    }\r\n\r\n    // #endif\r\n\r\n    // #ifdef H5 || MP-WEIXIN\r\n    trace(url, data, options = {}) {\r\n        return this.middleware({\r\n            url,\r\n            data,\r\n            method: 'TRACE',\r\n            ...options\r\n        })\r\n    }\r\n\r\n    // #endif\r\n\r\n    upload(url, config = {}) {\r\n        config.url = url\r\n        config.method = 'UPLOAD'\r\n        return this.middleware(config)\r\n    }\r\n\r\n    download(url, config = {}) {\r\n        config.url = url\r\n        config.method = 'DOWNLOAD'\r\n        return this.middleware(config)\r\n    }\r\n}\r\n\r\n/**\r\n * setConfig回调\r\n * @return {Object} - 返回操作后的config\r\n * @callback Request~setConfigCallback\r\n * @param {Object} config - 全局默认config\r\n */\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/libs/luch-request/core/buildFullPath.js",
    "content": "'use strict'\r\n\r\nimport isAbsoluteURL from '../helpers/isAbsoluteURL'\r\nimport combineURLs from '../helpers/combineURLs'\r\n\r\n/**\r\n * Creates a new URL by combining the baseURL with the requestedURL,\r\n * only when the requestedURL is not already an absolute URL.\r\n * If the requestURL is absolute, this function returns the requestedURL untouched.\r\n *\r\n * @param {string} baseURL The base URL\r\n * @param {string} requestedURL Absolute or relative URL to combine\r\n * @returns {string} The combined full path\r\n */\r\nexport default function buildFullPath(baseURL, requestedURL) {\r\n    if (baseURL && !isAbsoluteURL(requestedURL)) {\r\n        return combineURLs(baseURL, requestedURL)\r\n    }\r\n    return requestedURL\r\n}\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/libs/luch-request/core/defaults.js",
    "content": "/**\r\n * 默认的全局配置\r\n */\r\n\r\nexport default {\r\n    baseURL: '',\r\n    header: {},\r\n    method: 'GET',\r\n    dataType: 'json',\r\n    // #ifndef MP-ALIPAY\r\n    responseType: 'text',\r\n    // #endif\r\n    custom: {},\r\n    // #ifdef H5 || APP-PLUS || MP-ALIPAY || MP-WEIXIN\r\n    timeout: 60000,\r\n    // #endif\r\n    // #ifdef APP-PLUS\r\n    sslVerify: true,\r\n    // #endif\r\n    // #ifdef H5\r\n    withCredentials: false,\r\n    // #endif\r\n    // #ifdef APP-PLUS\r\n    firstIpv4: false,\r\n    // #endif\r\n    validateStatus: function validateStatus(status) {\r\n        return status >= 200 && status < 300\r\n    }\r\n}\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/libs/luch-request/core/dispatchRequest.js",
    "content": "import adapter from '../adapters/index'\r\n\r\nexport default (config) => adapter(config)\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/libs/luch-request/core/mergeConfig.js",
    "content": "import { deepMerge, isUndefined } from '../utils'\r\n\r\n/**\r\n * 合并局部配置优先的配置，如果局部有该配置项则用局部，如果全局有该配置项则用全局\r\n * @param {Array} keys - 配置项\r\n * @param {Object} globalsConfig - 当前的全局配置\r\n * @param {Object} config2 - 局部配置\r\n * @return {{}}\r\n */\r\nconst mergeKeys = (keys, globalsConfig, config2) => {\r\n    const config = {}\r\n    keys.forEach((prop) => {\r\n        if (!isUndefined(config2[prop])) {\r\n            config[prop] = config2[prop]\r\n        } else if (!isUndefined(globalsConfig[prop])) {\r\n            config[prop] = globalsConfig[prop]\r\n        }\r\n    })\r\n    return config\r\n}\r\n/**\r\n *\r\n * @param globalsConfig - 当前实例的全局配置\r\n * @param config2 - 当前的局部配置\r\n * @return - 合并后的配置\r\n */\r\nexport default (globalsConfig, config2 = {}) => {\r\n    const method = config2.method || globalsConfig.method || 'GET'\r\n    let config = {\r\n        baseURL: globalsConfig.baseURL || '',\r\n        method,\r\n        url: config2.url || '',\r\n        params: config2.params || {},\r\n        custom: { ...(globalsConfig.custom || {}), ...(config2.custom || {}) },\r\n        header: deepMerge(globalsConfig.header || {}, config2.header || {})\r\n    }\r\n    const defaultToConfig2Keys = ['getTask', 'validateStatus']\r\n    config = { ...config, ...mergeKeys(defaultToConfig2Keys, globalsConfig, config2) }\r\n\r\n    // eslint-disable-next-line no-empty\r\n    if (method === 'DOWNLOAD') {\r\n    // #ifdef H5 || APP-PLUS\r\n        if (!isUndefined(config2.timeout)) {\r\n            config.timeout = config2.timeout\r\n        } else if (!isUndefined(globalsConfig.timeout)) {\r\n            config.timeout = globalsConfig.timeout\r\n        }\r\n    // #endif\r\n    } else if (method === 'UPLOAD') {\r\n        delete config.header['content-type']\r\n        delete config.header['Content-Type']\r\n        const uploadKeys = [\r\n            // #ifdef APP-PLUS || H5\r\n            'files',\r\n            // #endif\r\n            // #ifdef MP-ALIPAY\r\n            'fileType',\r\n            // #endif\r\n            // #ifdef H5\r\n            'file',\r\n            // #endif\r\n            'filePath',\r\n            'name',\r\n            // #ifdef H5 || APP-PLUS\r\n            'timeout',\r\n            // #endif\r\n            'formData'\r\n        ]\r\n        uploadKeys.forEach((prop) => {\r\n            if (!isUndefined(config2[prop])) {\r\n                config[prop] = config2[prop]\r\n            }\r\n        })\r\n        // #ifdef H5 || APP-PLUS\r\n        if (isUndefined(config.timeout) && !isUndefined(globalsConfig.timeout)) {\r\n            config.timeout = globalsConfig.timeout\r\n        }\r\n    // #endif\r\n    } else {\r\n        const defaultsKeys = [\r\n            'data',\r\n            // #ifdef H5 || APP-PLUS || MP-ALIPAY || MP-WEIXIN\r\n            'timeout',\r\n            // #endif\r\n            'dataType',\r\n            // #ifndef MP-ALIPAY\r\n            'responseType',\r\n            // #endif\r\n            // #ifdef APP-PLUS\r\n            'sslVerify',\r\n            // #endif\r\n            // #ifdef H5\r\n            'withCredentials',\r\n            // #endif\r\n            // #ifdef APP-PLUS\r\n            'firstIpv4'\r\n            // #endif\r\n        ]\r\n        config = { ...config, ...mergeKeys(defaultsKeys, globalsConfig, config2) }\r\n    }\r\n\r\n    return config\r\n}\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/libs/luch-request/core/settle.js",
    "content": "/**\r\n * Resolve or reject a Promise based on response status.\r\n *\r\n * @param {Function} resolve A function that resolves the promise.\r\n * @param {Function} reject A function that rejects the promise.\r\n * @param {object} response The response.\r\n */\r\nexport default function settle(resolve, reject, response) {\r\n    const { validateStatus } = response.config\r\n    const status = response.statusCode\r\n    if (status && (!validateStatus || validateStatus(status))) {\r\n        resolve(response)\r\n    } else {\r\n        reject(response)\r\n    }\r\n}\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/libs/luch-request/helpers/buildURL.js",
    "content": "'use strict'\r\n\r\nimport * as utils from '../utils'\r\n\r\nfunction encode(val) {\r\n    return encodeURIComponent(val)\r\n        .replace(/%40/gi, '@')\r\n        .replace(/%3A/gi, ':')\r\n        .replace(/%24/g, '$')\r\n        .replace(/%2C/gi, ',')\r\n        .replace(/%20/g, '+')\r\n        .replace(/%5B/gi, '[')\r\n        .replace(/%5D/gi, ']')\r\n}\r\n\r\n/**\r\n * Build a URL by appending params to the end\r\n *\r\n * @param {string} url The base of the url (e.g., http://www.google.com)\r\n * @param {object} [params] The params to be appended\r\n * @returns {string} The formatted url\r\n */\r\nexport default function buildURL(url, params) {\r\n    /* eslint no-param-reassign:0 */\r\n    if (!params) {\r\n        return url\r\n    }\r\n\r\n    let serializedParams\r\n    if (utils.isURLSearchParams(params)) {\r\n        serializedParams = params.toString()\r\n    } else {\r\n        const parts = []\r\n\r\n        utils.forEach(params, (val, key) => {\r\n            if (val === null || typeof val === 'undefined') {\r\n                return\r\n            }\r\n\r\n            if (utils.isArray(val)) {\r\n                key = `${key}[]`\r\n            } else {\r\n                val = [val]\r\n            }\r\n\r\n            utils.forEach(val, (v) => {\r\n                if (utils.isDate(v)) {\r\n                    v = v.toISOString()\r\n                } else if (utils.isObject(v)) {\r\n                    v = JSON.stringify(v)\r\n                }\r\n                parts.push(`${encode(key)}=${encode(v)}`)\r\n            })\r\n        })\r\n\r\n        serializedParams = parts.join('&')\r\n    }\r\n\r\n    if (serializedParams) {\r\n        const hashmarkIndex = url.indexOf('#')\r\n        if (hashmarkIndex !== -1) {\r\n            url = url.slice(0, hashmarkIndex)\r\n        }\r\n\r\n        url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams\r\n    }\r\n\r\n    return url\r\n}\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/libs/luch-request/helpers/combineURLs.js",
    "content": "'use strict'\r\n\r\n/**\r\n * Creates a new URL by combining the specified URLs\r\n *\r\n * @param {string} baseURL The base URL\r\n * @param {string} relativeURL The relative URL\r\n * @returns {string} The combined URL\r\n */\r\nexport default function combineURLs(baseURL, relativeURL) {\r\n    return relativeURL\r\n        ? `${baseURL.replace(/\\/+$/, '')}/${relativeURL.replace(/^\\/+/, '')}`\r\n        : baseURL\r\n}\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/libs/luch-request/helpers/isAbsoluteURL.js",
    "content": "'use strict'\r\n\r\n/**\r\n * Determines whether the specified URL is absolute\r\n *\r\n * @param {string} url The URL to test\r\n * @returns {boolean} True if the specified URL is absolute, otherwise false\r\n */\r\nexport default function isAbsoluteURL(url) {\r\n    // A URL is considered absolute if it begins with \"<scheme>://\" or \"//\" (protocol-relative URL).\r\n    // RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed\r\n    // by any combination of letters, digits, plus, period, or hyphen.\r\n    return /^([a-z][a-z\\d+\\-.]*:)?\\/\\//i.test(url)\r\n}\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/libs/luch-request/index.d.ts",
    "content": "type AnyObject = Record<string | number | symbol, any>\r\ntype HttpPromise<T> = Promise<HttpResponse<T>>;\r\ntype Tasks = UniApp.RequestTask | UniApp.UploadTask | UniApp.DownloadTask\r\nexport interface RequestTask {\r\n  abort: () => void;\r\n  offHeadersReceived: () => void;\r\n  onHeadersReceived: () => void;\r\n}\r\nexport interface HttpRequestConfig<T = Tasks> {\r\n  /** 请求基地址 */\r\n  baseURL?: string;\r\n  /** 请求服务器接口地址 */\r\n  url?: string;\r\n\r\n  /** 请求查询参数，自动拼接为查询字符串 */\r\n  params?: AnyObject;\r\n  /** 请求体参数 */\r\n  data?: AnyObject;\r\n\r\n  /** 文件对应的 key */\r\n  name?: string;\r\n  /** HTTP 请求中其他额外的 form data */\r\n  formData?: AnyObject;\r\n  /** 要上传文件资源的路径。 */\r\n  filePath?: string;\r\n  /** 需要上传的文件列表。使用 files 时，filePath 和 name 不生效，App、H5（ 2.6.15+） */\r\n  files?: Array<{\r\n    name?: string;\r\n    file?: File;\r\n    uri: string;\r\n  }>;\r\n  /** 要上传的文件对象，仅H5（2.6.15+）支持 */\r\n  file?: File;\r\n\r\n  /** 请求头信息 */\r\n  header?: AnyObject;\r\n  /** 请求方式 */\r\n  method?: \"GET\" | \"POST\" | \"PUT\" | \"DELETE\" | \"CONNECT\" | \"HEAD\" | \"OPTIONS\" | \"TRACE\" | \"UPLOAD\" | \"DOWNLOAD\";\r\n  /** 如果设为 json，会尝试对返回的数据做一次 JSON.parse */\r\n  dataType?: string;\r\n  /** 设置响应的数据类型，支付宝小程序不支持 */\r\n  responseType?: \"text\" | \"arraybuffer\";\r\n  /** 自定义参数 */\r\n  custom?: AnyObject;\r\n  /** 超时时间，仅微信小程序（2.10.0）、支付宝小程序支持 */\r\n  timeout?: number;\r\n  /** DNS解析时优先使用ipv4，仅 App-Android 支持 (HBuilderX 2.8.0+) */\r\n  firstIpv4?: boolean;\r\n  /** 验证 ssl 证书 仅5+App安卓端支持（HBuilderX 2.3.3+） */\r\n  sslVerify?: boolean;\r\n  /** 跨域请求时是否携带凭证（cookies）仅H5支持（HBuilderX 2.6.15+） */\r\n  withCredentials?: boolean;\r\n\r\n  /** 返回当前请求的task, options。请勿在此处修改options。 */\r\n  getTask?: (task: T, options: HttpRequestConfig<T>) => void;\r\n  /**  全局自定义验证器 */\r\n  validateStatus?: (statusCode: number) => boolean | void;\r\n}\r\nexport interface HttpResponse<T = any> {\r\n  config: HttpRequestConfig;\r\n  statusCode: number;\r\n  cookies: Array<string>;\r\n  data: T;\r\n  errMsg: string;\r\n  header: AnyObject;\r\n}\r\nexport interface HttpUploadResponse<T = any> {\r\n  config: HttpRequestConfig;\r\n  statusCode: number;\r\n  data: T;\r\n  errMsg: string;\r\n}\r\nexport interface HttpDownloadResponse extends HttpResponse {\r\n  tempFilePath: string;\r\n}\r\nexport interface HttpError {\r\n  config: HttpRequestConfig;\r\n  statusCode?: number;\r\n  cookies?: Array<string>;\r\n  data?: any;\r\n  errMsg: string;\r\n  header?: AnyObject;\r\n}\r\nexport interface HttpInterceptorManager<V, E = V> {\r\n  use(\r\n    onFulfilled?: (config: V) => Promise<V> | V,\r\n    onRejected?: (config: E) => Promise<E> | E\r\n  ): void;\r\n  eject(id: number): void;\r\n}\r\nexport abstract class HttpRequestAbstract {\r\n  constructor(config?: HttpRequestConfig);\r\n  config: HttpRequestConfig;\r\n  interceptors: {\r\n    request: HttpInterceptorManager<HttpRequestConfig, HttpRequestConfig>;\r\n    response: HttpInterceptorManager<HttpResponse, HttpError>;\r\n  }\r\n  middleware<T = any>(config: HttpRequestConfig): HttpPromise<T>;\r\n  request<T = any>(config: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;\r\n  get<T = any>(url: string, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;\r\n  upload<T = any>(url: string, config?: HttpRequestConfig<UniApp.UploadTask>): HttpPromise<T>;\r\n  delete<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;\r\n  head<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;\r\n  post<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;\r\n  put<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;\r\n  connect<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;\r\n  options<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;\r\n  trace<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;\r\n\r\n  download(url: string, config?: HttpRequestConfig<UniApp.DownloadTask>): Promise<HttpDownloadResponse>;\r\n\r\n  setConfig(onSend: (config: HttpRequestConfig) => HttpRequestConfig): void;\r\n}\r\n\r\ndeclare class HttpRequest extends HttpRequestAbstract { }\r\nexport default HttpRequest;\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/libs/luch-request/index.js",
    "content": "import Request from './core/Request'\r\n\r\nexport default Request\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/libs/luch-request/utils/clone.js",
    "content": "/* eslint-disable */\r\nvar clone = (function() {\r\n  'use strict';\r\n\r\n  function _instanceof(obj, type) {\r\n    return type != null && obj instanceof type;\r\n  }\r\n\r\n  var nativeMap;\r\n  try {\r\n    nativeMap = Map;\r\n  } catch(_) {\r\n    // maybe a reference error because no `Map`. Give it a dummy value that no\r\n    // value will ever be an instanceof.\r\n    nativeMap = function() {};\r\n  }\r\n\r\n  var nativeSet;\r\n  try {\r\n    nativeSet = Set;\r\n  } catch(_) {\r\n    nativeSet = function() {};\r\n  }\r\n\r\n  var nativePromise;\r\n  try {\r\n    nativePromise = Promise;\r\n  } catch(_) {\r\n    nativePromise = function() {};\r\n  }\r\n\r\n  /**\r\n   * Clones (copies) an Object using deep copying.\r\n   *\r\n   * This function supports circular references by default, but if you are certain\r\n   * there are no circular references in your object, you can save some CPU time\r\n   * by calling clone(obj, false).\r\n   *\r\n   * Caution: if `circular` is false and `parent` contains circular references,\r\n   * your program may enter an infinite loop and crash.\r\n   *\r\n   * @param `parent` - the object to be cloned\r\n   * @param `circular` - set to true if the object to be cloned may contain\r\n   *    circular references. (optional - true by default)\r\n   * @param `depth` - set to a number if the object is only to be cloned to\r\n   *    a particular depth. (optional - defaults to Infinity)\r\n   * @param `prototype` - sets the prototype to be used when cloning an object.\r\n   *    (optional - defaults to parent prototype).\r\n   * @param `includeNonEnumerable` - set to true if the non-enumerable properties\r\n   *    should be cloned as well. Non-enumerable properties on the prototype\r\n   *    chain will be ignored. (optional - false by default)\r\n   */\r\n  function clone(parent, circular, depth, prototype, includeNonEnumerable) {\r\n    if (typeof circular === 'object') {\r\n      depth = circular.depth;\r\n      prototype = circular.prototype;\r\n      includeNonEnumerable = circular.includeNonEnumerable;\r\n      circular = circular.circular;\r\n    }\r\n    // maintain two arrays for circular references, where corresponding parents\r\n    // and children have the same index\r\n    var allParents = [];\r\n    var allChildren = [];\r\n\r\n    var useBuffer = typeof Buffer != 'undefined';\r\n\r\n    if (typeof circular == 'undefined')\r\n      circular = true;\r\n\r\n    if (typeof depth == 'undefined')\r\n      depth = Infinity;\r\n\r\n    // recurse this function so we don't reset allParents and allChildren\r\n    function _clone(parent, depth) {\r\n      // cloning null always returns null\r\n      if (parent === null)\r\n        return null;\r\n\r\n      if (depth === 0)\r\n        return parent;\r\n\r\n      var child;\r\n      var proto;\r\n      if (typeof parent != 'object') {\r\n        return parent;\r\n      }\r\n\r\n      if (_instanceof(parent, nativeMap)) {\r\n        child = new nativeMap();\r\n      } else if (_instanceof(parent, nativeSet)) {\r\n        child = new nativeSet();\r\n      } else if (_instanceof(parent, nativePromise)) {\r\n        child = new nativePromise(function (resolve, reject) {\r\n          parent.then(function(value) {\r\n            resolve(_clone(value, depth - 1));\r\n          }, function(err) {\r\n            reject(_clone(err, depth - 1));\r\n          });\r\n        });\r\n      } else if (clone.__isArray(parent)) {\r\n        child = [];\r\n      } else if (clone.__isRegExp(parent)) {\r\n        child = new RegExp(parent.source, __getRegExpFlags(parent));\r\n        if (parent.lastIndex) child.lastIndex = parent.lastIndex;\r\n      } else if (clone.__isDate(parent)) {\r\n        child = new Date(parent.getTime());\r\n      } else if (useBuffer && Buffer.isBuffer(parent)) {\r\n        if (Buffer.from) {\r\n          // Node.js >= 5.10.0\r\n          child = Buffer.from(parent);\r\n        } else {\r\n          // Older Node.js versions\r\n          child = new Buffer(parent.length);\r\n          parent.copy(child);\r\n        }\r\n        return child;\r\n      } else if (_instanceof(parent, Error)) {\r\n        child = Object.create(parent);\r\n      } else {\r\n        if (typeof prototype == 'undefined') {\r\n          proto = Object.getPrototypeOf(parent);\r\n          child = Object.create(proto);\r\n        }\r\n        else {\r\n          child = Object.create(prototype);\r\n          proto = prototype;\r\n        }\r\n      }\r\n\r\n      if (circular) {\r\n        var index = allParents.indexOf(parent);\r\n\r\n        if (index != -1) {\r\n          return allChildren[index];\r\n        }\r\n        allParents.push(parent);\r\n        allChildren.push(child);\r\n      }\r\n\r\n      if (_instanceof(parent, nativeMap)) {\r\n        parent.forEach(function(value, key) {\r\n          var keyChild = _clone(key, depth - 1);\r\n          var valueChild = _clone(value, depth - 1);\r\n          child.set(keyChild, valueChild);\r\n        });\r\n      }\r\n      if (_instanceof(parent, nativeSet)) {\r\n        parent.forEach(function(value) {\r\n          var entryChild = _clone(value, depth - 1);\r\n          child.add(entryChild);\r\n        });\r\n      }\r\n\r\n      for (var i in parent) {\r\n        var attrs = Object.getOwnPropertyDescriptor(parent, i);\r\n        if (attrs) {\r\n          child[i] = _clone(parent[i], depth - 1);\r\n        }\r\n\r\n        try {\r\n          var objProperty = Object.getOwnPropertyDescriptor(parent, i);\r\n          if (objProperty.set === 'undefined') {\r\n            // no setter defined. Skip cloning this property\r\n            continue;\r\n          }\r\n          child[i] = _clone(parent[i], depth - 1);\r\n        } catch(e){\r\n          if (e instanceof TypeError) {\r\n            // when in strict mode, TypeError will be thrown if child[i] property only has a getter\r\n            // we can't do anything about this, other than inform the user that this property cannot be set.\r\n            continue\r\n          } else if (e instanceof ReferenceError) {\r\n            //this may happen in non strict mode\r\n            continue\r\n          }\r\n        }\r\n\r\n      }\r\n\r\n      if (Object.getOwnPropertySymbols) {\r\n        var symbols = Object.getOwnPropertySymbols(parent);\r\n        for (var i = 0; i < symbols.length; i++) {\r\n          // Don't need to worry about cloning a symbol because it is a primitive,\r\n          // like a number or string.\r\n          var symbol = symbols[i];\r\n          var descriptor = Object.getOwnPropertyDescriptor(parent, symbol);\r\n          if (descriptor && !descriptor.enumerable && !includeNonEnumerable) {\r\n            continue;\r\n          }\r\n          child[symbol] = _clone(parent[symbol], depth - 1);\r\n          Object.defineProperty(child, symbol, descriptor);\r\n        }\r\n      }\r\n\r\n      if (includeNonEnumerable) {\r\n        var allPropertyNames = Object.getOwnPropertyNames(parent);\r\n        for (var i = 0; i < allPropertyNames.length; i++) {\r\n          var propertyName = allPropertyNames[i];\r\n          var descriptor = Object.getOwnPropertyDescriptor(parent, propertyName);\r\n          if (descriptor && descriptor.enumerable) {\r\n            continue;\r\n          }\r\n          child[propertyName] = _clone(parent[propertyName], depth - 1);\r\n          Object.defineProperty(child, propertyName, descriptor);\r\n        }\r\n      }\r\n\r\n      return child;\r\n    }\r\n\r\n    return _clone(parent, depth);\r\n  }\r\n\r\n  /**\r\n   * Simple flat clone using prototype, accepts only objects, usefull for property\r\n   * override on FLAT configuration object (no nested props).\r\n   *\r\n   * USE WITH CAUTION! This may not behave as you wish if you do not know how this\r\n   * works.\r\n   */\r\n  clone.clonePrototype = function clonePrototype(parent) {\r\n    if (parent === null)\r\n      return null;\r\n\r\n    var c = function () {};\r\n    c.prototype = parent;\r\n    return new c();\r\n  };\r\n\r\n// private utility functions\r\n\r\n  function __objToStr(o) {\r\n    return Object.prototype.toString.call(o);\r\n  }\r\n  clone.__objToStr = __objToStr;\r\n\r\n  function __isDate(o) {\r\n    return typeof o === 'object' && __objToStr(o) === '[object Date]';\r\n  }\r\n  clone.__isDate = __isDate;\r\n\r\n  function __isArray(o) {\r\n    return typeof o === 'object' && __objToStr(o) === '[object Array]';\r\n  }\r\n  clone.__isArray = __isArray;\r\n\r\n  function __isRegExp(o) {\r\n    return typeof o === 'object' && __objToStr(o) === '[object RegExp]';\r\n  }\r\n  clone.__isRegExp = __isRegExp;\r\n\r\n  function __getRegExpFlags(re) {\r\n    var flags = '';\r\n    if (re.global) flags += 'g';\r\n    if (re.ignoreCase) flags += 'i';\r\n    if (re.multiline) flags += 'm';\r\n    return flags;\r\n  }\r\n  clone.__getRegExpFlags = __getRegExpFlags;\r\n\r\n  return clone;\r\n})();\r\n\r\nexport default clone\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/libs/luch-request/utils.js",
    "content": "'use strict'\r\n\r\n// utils is a library of generic helper functions non-specific to axios\r\n\r\nconst { toString } = Object.prototype\r\n\r\n/**\r\n * Determine if a value is an Array\r\n *\r\n * @param {Object} val The value to test\r\n * @returns {boolean} True if value is an Array, otherwise false\r\n */\r\nexport function isArray(val) {\r\n    return toString.call(val) === '[object Array]'\r\n}\r\n\r\n/**\r\n * Determine if a value is an Object\r\n *\r\n * @param {Object} val The value to test\r\n * @returns {boolean} True if value is an Object, otherwise false\r\n */\r\nexport function isObject(val) {\r\n    return val !== null && typeof val === 'object'\r\n}\r\n\r\n/**\r\n * Determine if a value is a Date\r\n *\r\n * @param {Object} val The value to test\r\n * @returns {boolean} True if value is a Date, otherwise false\r\n */\r\nexport function isDate(val) {\r\n    return toString.call(val) === '[object Date]'\r\n}\r\n\r\n/**\r\n * Determine if a value is a URLSearchParams object\r\n *\r\n * @param {Object} val The value to test\r\n * @returns {boolean} True if value is a URLSearchParams object, otherwise false\r\n */\r\nexport function isURLSearchParams(val) {\r\n    return typeof URLSearchParams !== 'undefined' && val instanceof URLSearchParams\r\n}\r\n\r\n/**\r\n * Iterate over an Array or an Object invoking a function for each item.\r\n *\r\n * If `obj` is an Array callback will be called passing\r\n * the value, index, and complete array for each item.\r\n *\r\n * If 'obj' is an Object callback will be called passing\r\n * the value, key, and complete object for each property.\r\n *\r\n * @param {Object|Array} obj The object to iterate\r\n * @param {Function} fn The callback to invoke for each item\r\n */\r\nexport function forEach(obj, fn) {\r\n    // Don't bother if no value provided\r\n    if (obj === null || typeof obj === 'undefined') {\r\n        return\r\n    }\r\n\r\n    // Force an array if not already something iterable\r\n    if (typeof obj !== 'object') {\r\n    /* eslint no-param-reassign:0 */\r\n        obj = [obj]\r\n    }\r\n\r\n    if (isArray(obj)) {\r\n    // Iterate over array values\r\n        for (let i = 0, l = obj.length; i < l; i++) {\r\n            fn.call(null, obj[i], i, obj)\r\n        }\r\n    } else {\r\n    // Iterate over object keys\r\n        for (const key in obj) {\r\n            if (Object.prototype.hasOwnProperty.call(obj, key)) {\r\n                fn.call(null, obj[key], key, obj)\r\n            }\r\n        }\r\n    }\r\n}\r\n\r\n/**\r\n * 是否为boolean 值\r\n * @param val\r\n * @returns {boolean}\r\n */\r\nexport function isBoolean(val) {\r\n    return typeof val === 'boolean'\r\n}\r\n\r\n/**\r\n * 是否为真正的对象{} new Object\r\n * @param {any} obj - 检测的对象\r\n * @returns {boolean}\r\n */\r\nexport function isPlainObject(obj) {\r\n    return Object.prototype.toString.call(obj) === '[object Object]'\r\n}\r\n\r\n/**\r\n * Function equal to merge with the difference being that no reference\r\n * to original objects is kept.\r\n *\r\n * @see merge\r\n * @param {Object} obj1 Object to merge\r\n * @returns {Object} Result of all merge properties\r\n */\r\nexport function deepMerge(/* obj1, obj2, obj3, ... */) {\r\n    const result = {}\r\n    function assignValue(val, key) {\r\n        if (typeof result[key] === 'object' && typeof val === 'object') {\r\n            result[key] = deepMerge(result[key], val)\r\n        } else if (typeof val === 'object') {\r\n            result[key] = deepMerge({}, val)\r\n        } else {\r\n            result[key] = val\r\n        }\r\n    }\r\n    for (let i = 0, l = arguments.length; i < l; i++) {\r\n        forEach(arguments[i], assignValue)\r\n    }\r\n    return result\r\n}\r\n\r\nexport function isUndefined(val) {\r\n    return typeof val === 'undefined'\r\n}\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/libs/mixin/button.js",
    "content": "export default {\r\n    props: {\r\n        lang: String,\r\n        sessionFrom: String,\r\n        sendMessageTitle: String,\r\n        sendMessagePath: String,\r\n        sendMessageImg: String,\r\n        showMessageCard: Boolean,\r\n        appParameter: String,\r\n        formType: String,\r\n        openType: String\r\n    }\r\n}\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/libs/mixin/mixin.js",
    "content": "import * as index from '../function/index.js';\r\nimport * as test from '../function/test.js';\r\nexport default {\r\n\t// 定义每个组件都可能需要用到的外部样式以及类名\r\n\tprops: {\r\n\t\t// 每个组件都有的父组件传递的样式，可以为字符串或者对象形式\r\n\t\tcustomStyle: {\r\n\t\t\ttype: [Object, String],\r\n\t\t\tdefault: () => ({})\r\n\t\t},\r\n\t\tcustomClass: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 跳转的页面路径\r\n\t\turl: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 页面跳转的类型\r\n\t\tlinkType: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'navigateTo'\r\n\t\t}\r\n\t},\r\n\tdata() {\r\n\t\treturn {}\r\n\t},\r\n\tonLoad() {\r\n\t\t// getRect挂载到$uv上，因为这方法需要使用in(this)，所以无法把它独立成一个单独的文件导出\r\n\t\tthis.$uv.getRect = this.$uvGetRect\r\n\t},\r\n\tcreated() {\r\n\t\t// 组件当中，只有created声明周期，为了能在组件使用，故也在created中将方法挂载到$uv\r\n\t\tthis.$uv.getRect = this.$uvGetRect\r\n\t},\r\n\tcomputed: {\r\n\t\t$uv() {\r\n\t\t\treturn {\r\n\t\t\t\t...index,\r\n\t\t\t\ttest,\r\n\t\t\t\tunit: uni?.$uv?.config?.unit\r\n\t\t\t}\r\n\t\t},\r\n\t\t/**\r\n\t\t * 生成bem规则类名\r\n\t\t * 由于微信小程序，H5，nvue之间绑定class的差异，无法通过:class=\"[bem()]\"的形式进行同用\r\n\t\t * 故采用如下折中做法，最后返回的是数组（一般平台）或字符串（支付宝和字节跳动平台），类似['a', 'b', 'c']或'a b c'的形式\r\n\t\t * @param {String} name 组件名称\r\n\t\t * @param {Array} fixed 一直会存在的类名\r\n\t\t * @param {Array} change 会根据变量值为true或者false而出现或者隐藏的类名\r\n\t\t * @returns {Array|string}\r\n\t\t */\r\n\t\tbem() {\r\n\t\t\treturn function(name, fixed, change) {\r\n\t\t\t\t// 类名前缀\r\n\t\t\t\tconst prefix = `uv-${name}--`\r\n\t\t\t\tconst classes = {}\r\n\t\t\t\tif (fixed) {\r\n\t\t\t\t\tfixed.map((item) => {\r\n\t\t\t\t\t\t// 这里的类名，会一直存在\r\n\t\t\t\t\t\tclasses[prefix + this[item]] = true\r\n\t\t\t\t\t})\r\n\t\t\t\t}\r\n\t\t\t\tif (change) {\r\n\t\t\t\t\tchange.map((item) => {\r\n\t\t\t\t\t\t// 这里的类名，会根据this[item]的值为true或者false，而进行添加或者移除某一个类\r\n\t\t\t\t\t\tthis[item] ? (classes[prefix + item] = this[item]) : (delete classes[prefix + item])\r\n\t\t\t\t\t})\r\n\t\t\t\t}\r\n\t\t\t\treturn Object.keys(classes)\r\n\t\t\t\t\t// 支付宝，头条小程序无法动态绑定一个数组类名，否则解析出来的结果会带有\",\"，而导致失效\r\n\t\t\t\t\t// #ifdef MP-ALIPAY || MP-TOUTIAO || MP-LARK || MP-BAIDU\r\n\t\t\t\t\t.join(' ')\r\n\t\t\t\t// #endif\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\tmethods: {\r\n\t\t// 跳转某一个页面\r\n\t\topenPage(urlKey = 'url') {\r\n\t\t\tconst url = this[urlKey]\r\n\t\t\tif (url) {\r\n\t\t\t\t// 执行类似uni.navigateTo的方法\r\n\t\t\t\tuni[this.linkType]({\r\n\t\t\t\t\turl\r\n\t\t\t\t})\r\n\t\t\t}\r\n\t\t},\r\n\t\t// 查询节点信息\r\n\t\t// 目前此方法在支付宝小程序中无法获取组件跟接点的尺寸，为支付宝的bug(2020-07-21)\r\n\t\t// 解决办法为在组件根部再套一个没有任何作用的view元素\r\n\t\t$uvGetRect(selector, all) {\r\n\t\t\treturn new Promise((resolve) => {\r\n\t\t\t\tuni.createSelectorQuery()\r\n\t\t\t\t\t.in(this)[all ? 'selectAll' : 'select'](selector)\r\n\t\t\t\t\t.boundingClientRect((rect) => {\r\n\t\t\t\t\t\tif (all && Array.isArray(rect) && rect.length) {\r\n\t\t\t\t\t\t\tresolve(rect)\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tif (!all && rect) {\r\n\t\t\t\t\t\t\tresolve(rect)\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t})\r\n\t\t\t\t\t.exec()\r\n\t\t\t})\r\n\t\t},\r\n\t\tgetParentData(parentName = '') {\r\n\t\t\t// 避免在created中去定义parent变量\r\n\t\t\tif (!this.parent) this.parent = {}\r\n\t\t\t// 这里的本质原理是，通过获取父组件实例(也即类似uv-radio的父组件uv-radio-group的this)\r\n\t\t\t// 将父组件this中对应的参数，赋值给本组件(uv-radio的this)的parentData对象中对应的属性\r\n\t\t\t// 之所以需要这么做，是因为所有端中，头条小程序不支持通过this.parent.xxx去监听父组件参数的变化\r\n\t\t\t// 此处并不会自动更新子组件的数据，而是依赖父组件uv-radio-group去监听data的变化，手动调用更新子组件的方法去重新获取\r\n\t\t\tthis.parent = this.$uv.$parent.call(this, parentName)\r\n\t\t\tif (this.parent.children) {\r\n\t\t\t\t// 如果父组件的children不存在本组件的实例，才将本实例添加到父组件的children中\r\n\t\t\t\tthis.parent.children.indexOf(this) === -1 && this.parent.children.push(this)\r\n\t\t\t}\r\n\t\t\tif (this.parent && this.parentData) {\r\n\t\t\t\t// 历遍parentData中的属性，将parent中的同名属性赋值给parentData\r\n\t\t\t\tObject.keys(this.parentData).map((key) => {\r\n\t\t\t\t\tthis.parentData[key] = this.parent[key]\r\n\t\t\t\t})\r\n\t\t\t}\r\n\t\t},\r\n\t\t// 阻止事件冒泡\r\n\t\tpreventEvent(e) {\r\n\t\t\te && typeof(e.stopPropagation) === 'function' && e.stopPropagation()\r\n\t\t},\r\n\t\t// 空操作\r\n\t\tnoop(e) {\r\n\t\t\tthis.preventEvent(e)\r\n\t\t}\r\n\t},\r\n\tonReachBottom() {\r\n\t\tuni.$emit('uvOnReachBottom')\r\n\t},\r\n\tbeforeDestroy() {\r\n\t\t// 判断当前页面是否存在parent和chldren，一般在checkbox和checkbox-group父子联动的场景会有此情况\r\n\t\t// 组件销毁时，移除子组件在父组件children数组中的实例，释放资源，避免数据混乱\r\n\t\tif (this.parent && test.array(this.parent.children)) {\r\n\t\t\t// 组件销毁时，移除父组件中的children数组中对应的实例\r\n\t\t\tconst childrenList = this.parent.children\r\n\t\t\tchildrenList.map((child, index) => {\r\n\t\t\t\t// 如果相等，则移除\r\n\t\t\t\tif (child === this) {\r\n\t\t\t\t\tchildrenList.splice(index, 1)\r\n\t\t\t\t}\r\n\t\t\t})\r\n\t\t}\r\n\t},\r\n\t// 兼容vue3\r\n\tunmounted() {\r\n\t\tif (this.parent && test.array(this.parent.children)) {\r\n\t\t\t// 组件销毁时，移除父组件中的children数组中对应的实例\r\n\t\t\tconst childrenList = this.parent.children\r\n\t\t\tchildrenList.map((child, index) => {\r\n\t\t\t\t// 如果相等，则移除\r\n\t\t\t\tif (child === this) {\r\n\t\t\t\t\tchildrenList.splice(index, 1)\r\n\t\t\t\t}\r\n\t\t\t})\r\n\t\t}\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js",
    "content": "export default {\r\n    // #ifdef MP-WEIXIN\r\n    // 将自定义节点设置成虚拟的（去掉自定义组件包裹层），更加接近Vue组件的表现，能更好的使用flex属性\r\n    options: {\r\n        virtualHost: true\r\n    }\r\n    // #endif\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/libs/mixin/mpShare.js",
    "content": "export default {\r\n\tonLoad() {\r\n\t    // 设置默认的转发参数\r\n\t    uni.$uv.mpShare = {\r\n\t        title: '', // 默认为小程序名称\r\n\t        path: '', // 默认为当前页面路径\r\n\t        imageUrl: '' // 默认为当前页面的截图\r\n\t    }\r\n\t},\r\n\tonShareAppMessage() {\r\n\t    return uni.$uv.mpShare\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/libs/mixin/openType.js",
    "content": "export default {\r\n    props: {\r\n        openType: String\r\n    },\r\n\t\temits: ['getphonenumber','getuserinfo','error','opensetting','launchapp','contact','chooseavatar','addgroupapp','chooseaddress','subscribe','login','im'],\r\n    methods: {\r\n        onGetPhoneNumber(event) {\r\n            this.$emit('getphonenumber', event.detail)\r\n        },\r\n        onGetUserInfo(event) {\r\n            this.$emit('getuserinfo', event.detail)\r\n        },\r\n        onError(event) {\r\n            this.$emit('error', event.detail)\r\n        },\r\n        onOpenSetting(event) {\r\n            this.$emit('opensetting', event.detail)\r\n        },\r\n        onLaunchApp(event) {\r\n            this.$emit('launchapp', event.detail)\r\n        },\r\n        onContact(event) {\r\n            this.$emit('contact', event.detail)\r\n        },\r\n        onChooseavatar(event) {\r\n            this.$emit('chooseavatar', event.detail)\r\n        },\r\n        onAgreeprivacyauthorization(event) {\r\n            this.$emit('agreeprivacyauthorization', event.detail)\r\n        },\r\n        onAddgroupapp(event) {\r\n            this.$emit('addgroupapp', event.detail)\r\n        },\r\n        onChooseaddress(event) {\r\n            this.$emit('chooseaddress', event.detail)\r\n        },\r\n        onSubscribe(event) {\r\n            this.$emit('subscribe', event.detail)\r\n        },\r\n        onLogin(event) {\r\n            this.$emit('login', event.detail)\r\n        },\r\n        onIm(event) {\r\n            this.$emit('im', event.detail)\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/libs/mixin/touch.js",
    "content": "const MIN_DISTANCE = 10\r\n\r\nfunction getDirection(x, y) {\r\n    if (x > y && x > MIN_DISTANCE) {\r\n        return 'horizontal'\r\n    }\r\n    if (y > x && y > MIN_DISTANCE) {\r\n        return 'vertical'\r\n    }\r\n    return ''\r\n}\r\n\r\nexport default {\r\n    methods: {\r\n        getTouchPoint(e) {\r\n            if (!e) {\r\n                return {\r\n                    x: 0,\r\n                    y: 0\r\n                }\r\n            } if (e.touches && e.touches[0]) {\r\n                return {\r\n                    x: e.touches[0].pageX,\r\n                    y: e.touches[0].pageY\r\n                }\r\n            } if (e.changedTouches && e.changedTouches[0]) {\r\n                return {\r\n                    x: e.changedTouches[0].pageX,\r\n                    y: e.changedTouches[0].pageY\r\n                }\r\n            }\r\n            return {\r\n                x: e.clientX || 0,\r\n                y: e.clientY || 0\r\n            }\r\n        },\r\n        resetTouchStatus() {\r\n            this.direction = ''\r\n            this.deltaX = 0\r\n            this.deltaY = 0\r\n            this.offsetX = 0\r\n            this.offsetY = 0\r\n        },\r\n        touchStart(event) {\r\n            this.resetTouchStatus()\r\n            const touch = this.getTouchPoint(event)\r\n            this.startX = touch.x\r\n            this.startY = touch.y\r\n        },\r\n        touchMove(event) {\r\n            const touch = this.getTouchPoint(event)\r\n            this.deltaX = touch.x - this.startX\r\n            this.deltaY = touch.y - this.startY\r\n            this.offsetX = Math.abs(this.deltaX)\r\n            this.offsetY = Math.abs(this.deltaY)\r\n            this.direction =\t\t\t\tthis.direction || getDirection(this.offsetX, this.offsetY)\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/libs/util/dayjs.js",
    "content": "var __getOwnPropNames = Object.getOwnPropertyNames;\r\nvar __commonJS = (cb, mod) => function __require() {\r\n  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;\r\n};\r\n\r\nvar require_dayjs_min = __commonJS({\r\n  \"uvuidayjs\"(exports, module) {\r\n    !function(t, e) {\r\n      \"object\" == typeof exports && \"undefined\" != typeof module ? module.exports = e() : \"function\" == typeof define && define.amd ? define(e) : (t = \"undefined\" != typeof globalThis ? globalThis : t || self).dayjs = e();\r\n    }(exports, function() {\r\n      \"use strict\";\r\n      var t = 1e3, e = 6e4, n = 36e5, r = \"millisecond\", i = \"second\", s = \"minute\", u = \"hour\", a = \"day\", o = \"week\", f = \"month\", h = \"quarter\", c = \"year\", d = \"date\", l = \"Invalid Date\", $ = /^(\\d{4})[-/]?(\\d{1,2})?[-/]?(\\d{0,2})[Tt\\s]*(\\d{1,2})?:?(\\d{1,2})?:?(\\d{1,2})?[.:]?(\\d+)?$/, y = /\\[([^\\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g, M = { name: \"en\", weekdays: \"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday\".split(\"_\"), months: \"January_February_March_April_May_June_July_August_September_October_November_December\".split(\"_\"), ordinal: function(t2) {\r\n        var e2 = [\"th\", \"st\", \"nd\", \"rd\"], n2 = t2 % 100;\r\n        return \"[\" + t2 + (e2[(n2 - 20) % 10] || e2[n2] || e2[0]) + \"]\";\r\n      } }, m = function(t2, e2, n2) {\r\n        var r2 = String(t2);\r\n        return !r2 || r2.length >= e2 ? t2 : \"\" + Array(e2 + 1 - r2.length).join(n2) + t2;\r\n      }, v = { s: m, z: function(t2) {\r\n        var e2 = -t2.utcOffset(), n2 = Math.abs(e2), r2 = Math.floor(n2 / 60), i2 = n2 % 60;\r\n        return (e2 <= 0 ? \"+\" : \"-\") + m(r2, 2, \"0\") + \":\" + m(i2, 2, \"0\");\r\n      }, m: function t2(e2, n2) {\r\n        if (e2.date() < n2.date())\r\n          return -t2(n2, e2);\r\n        var r2 = 12 * (n2.year() - e2.year()) + (n2.month() - e2.month()), i2 = e2.clone().add(r2, f), s2 = n2 - i2 < 0, u2 = e2.clone().add(r2 + (s2 ? -1 : 1), f);\r\n        return +(-(r2 + (n2 - i2) / (s2 ? i2 - u2 : u2 - i2)) || 0);\r\n      }, a: function(t2) {\r\n        return t2 < 0 ? Math.ceil(t2) || 0 : Math.floor(t2);\r\n      }, p: function(t2) {\r\n        return { M: f, y: c, w: o, d: a, D: d, h: u, m: s, s: i, ms: r, Q: h }[t2] || String(t2 || \"\").toLowerCase().replace(/s$/, \"\");\r\n      }, u: function(t2) {\r\n        return void 0 === t2;\r\n      } }, g = \"en\", D = {};\r\n      D[g] = M;\r\n      var p = function(t2) {\r\n        return t2 instanceof _;\r\n      }, S = function t2(e2, n2, r2) {\r\n        var i2;\r\n        if (!e2)\r\n          return g;\r\n        if (\"string\" == typeof e2) {\r\n          var s2 = e2.toLowerCase();\r\n          D[s2] && (i2 = s2), n2 && (D[s2] = n2, i2 = s2);\r\n          var u2 = e2.split(\"-\");\r\n          if (!i2 && u2.length > 1)\r\n            return t2(u2[0]);\r\n        } else {\r\n          var a2 = e2.name;\r\n          D[a2] = e2, i2 = a2;\r\n        }\r\n        return !r2 && i2 && (g = i2), i2 || !r2 && g;\r\n      }, w = function(t2, e2) {\r\n        if (p(t2))\r\n          return t2.clone();\r\n        var n2 = \"object\" == typeof e2 ? e2 : {};\r\n        return n2.date = t2, n2.args = arguments, new _(n2);\r\n      }, O = v;\r\n      O.l = S, O.i = p, O.w = function(t2, e2) {\r\n        return w(t2, { locale: e2.$L, utc: e2.$u, x: e2.$x, $offset: e2.$offset });\r\n      };\r\n      var _ = function() {\r\n        function M2(t2) {\r\n          this.$L = S(t2.locale, null, true), this.parse(t2);\r\n        }\r\n        var m2 = M2.prototype;\r\n        return m2.parse = function(t2) {\r\n          this.$d = function(t3) {\r\n            var e2 = t3.date, n2 = t3.utc;\r\n            if (null === e2)\r\n              return new Date(NaN);\r\n            if (O.u(e2))\r\n              return new Date();\r\n            if (e2 instanceof Date)\r\n              return new Date(e2);\r\n            if (\"string\" == typeof e2 && !/Z$/i.test(e2)) {\r\n              var r2 = e2.match($);\r\n              if (r2) {\r\n                var i2 = r2[2] - 1 || 0, s2 = (r2[7] || \"0\").substring(0, 3);\r\n                return n2 ? new Date(Date.UTC(r2[1], i2, r2[3] || 1, r2[4] || 0, r2[5] || 0, r2[6] || 0, s2)) : new Date(r2[1], i2, r2[3] || 1, r2[4] || 0, r2[5] || 0, r2[6] || 0, s2);\r\n              }\r\n            }\r\n            return new Date(e2);\r\n          }(t2), this.$x = t2.x || {}, this.init();\r\n        }, m2.init = function() {\r\n          var t2 = this.$d;\r\n          this.$y = t2.getFullYear(), this.$M = t2.getMonth(), this.$D = t2.getDate(), this.$W = t2.getDay(), this.$H = t2.getHours(), this.$m = t2.getMinutes(), this.$s = t2.getSeconds(), this.$ms = t2.getMilliseconds();\r\n        }, m2.$utils = function() {\r\n          return O;\r\n        }, m2.isValid = function() {\r\n          return !(this.$d.toString() === l);\r\n        }, m2.isSame = function(t2, e2) {\r\n          var n2 = w(t2);\r\n          return this.startOf(e2) <= n2 && n2 <= this.endOf(e2);\r\n        }, m2.isAfter = function(t2, e2) {\r\n          return w(t2) < this.startOf(e2);\r\n        }, m2.isBefore = function(t2, e2) {\r\n          return this.endOf(e2) < w(t2);\r\n        }, m2.$g = function(t2, e2, n2) {\r\n          return O.u(t2) ? this[e2] : this.set(n2, t2);\r\n        }, m2.unix = function() {\r\n          return Math.floor(this.valueOf() / 1e3);\r\n        }, m2.valueOf = function() {\r\n          return this.$d.getTime();\r\n        }, m2.startOf = function(t2, e2) {\r\n          var n2 = this, r2 = !!O.u(e2) || e2, h2 = O.p(t2), l2 = function(t3, e3) {\r\n            var i2 = O.w(n2.$u ? Date.UTC(n2.$y, e3, t3) : new Date(n2.$y, e3, t3), n2);\r\n            return r2 ? i2 : i2.endOf(a);\r\n          }, $2 = function(t3, e3) {\r\n            return O.w(n2.toDate()[t3].apply(n2.toDate(\"s\"), (r2 ? [0, 0, 0, 0] : [23, 59, 59, 999]).slice(e3)), n2);\r\n          }, y2 = this.$W, M3 = this.$M, m3 = this.$D, v2 = \"set\" + (this.$u ? \"UTC\" : \"\");\r\n          switch (h2) {\r\n            case c:\r\n              return r2 ? l2(1, 0) : l2(31, 11);\r\n            case f:\r\n              return r2 ? l2(1, M3) : l2(0, M3 + 1);\r\n            case o:\r\n              var g2 = this.$locale().weekStart || 0, D2 = (y2 < g2 ? y2 + 7 : y2) - g2;\r\n              return l2(r2 ? m3 - D2 : m3 + (6 - D2), M3);\r\n            case a:\r\n            case d:\r\n              return $2(v2 + \"Hours\", 0);\r\n            case u:\r\n              return $2(v2 + \"Minutes\", 1);\r\n            case s:\r\n              return $2(v2 + \"Seconds\", 2);\r\n            case i:\r\n              return $2(v2 + \"Milliseconds\", 3);\r\n            default:\r\n              return this.clone();\r\n          }\r\n        }, m2.endOf = function(t2) {\r\n          return this.startOf(t2, false);\r\n        }, m2.$set = function(t2, e2) {\r\n          var n2, o2 = O.p(t2), h2 = \"set\" + (this.$u ? \"UTC\" : \"\"), l2 = (n2 = {}, n2[a] = h2 + \"Date\", n2[d] = h2 + \"Date\", n2[f] = h2 + \"Month\", n2[c] = h2 + \"FullYear\", n2[u] = h2 + \"Hours\", n2[s] = h2 + \"Minutes\", n2[i] = h2 + \"Seconds\", n2[r] = h2 + \"Milliseconds\", n2)[o2], $2 = o2 === a ? this.$D + (e2 - this.$W) : e2;\r\n          if (o2 === f || o2 === c) {\r\n            var y2 = this.clone().set(d, 1);\r\n            y2.$d[l2]($2), y2.init(), this.$d = y2.set(d, Math.min(this.$D, y2.daysInMonth())).$d;\r\n          } else\r\n            l2 && this.$d[l2]($2);\r\n          return this.init(), this;\r\n        }, m2.set = function(t2, e2) {\r\n          return this.clone().$set(t2, e2);\r\n        }, m2.get = function(t2) {\r\n          return this[O.p(t2)]();\r\n        }, m2.add = function(r2, h2) {\r\n          var d2, l2 = this;\r\n          r2 = Number(r2);\r\n          var $2 = O.p(h2), y2 = function(t2) {\r\n            var e2 = w(l2);\r\n            return O.w(e2.date(e2.date() + Math.round(t2 * r2)), l2);\r\n          };\r\n          if ($2 === f)\r\n            return this.set(f, this.$M + r2);\r\n          if ($2 === c)\r\n            return this.set(c, this.$y + r2);\r\n          if ($2 === a)\r\n            return y2(1);\r\n          if ($2 === o)\r\n            return y2(7);\r\n          var M3 = (d2 = {}, d2[s] = e, d2[u] = n, d2[i] = t, d2)[$2] || 1, m3 = this.$d.getTime() + r2 * M3;\r\n          return O.w(m3, this);\r\n        }, m2.subtract = function(t2, e2) {\r\n          return this.add(-1 * t2, e2);\r\n        }, m2.format = function(t2) {\r\n          var e2 = this, n2 = this.$locale();\r\n          if (!this.isValid())\r\n            return n2.invalidDate || l;\r\n          var r2 = t2 || \"YYYY-MM-DDTHH:mm:ssZ\", i2 = O.z(this), s2 = this.$H, u2 = this.$m, a2 = this.$M, o2 = n2.weekdays, f2 = n2.months, h2 = function(t3, n3, i3, s3) {\r\n            return t3 && (t3[n3] || t3(e2, r2)) || i3[n3].slice(0, s3);\r\n          }, c2 = function(t3) {\r\n            return O.s(s2 % 12 || 12, t3, \"0\");\r\n          }, d2 = n2.meridiem || function(t3, e3, n3) {\r\n            var r3 = t3 < 12 ? \"AM\" : \"PM\";\r\n            return n3 ? r3.toLowerCase() : r3;\r\n          }, $2 = { YY: String(this.$y).slice(-2), YYYY: this.$y, M: a2 + 1, MM: O.s(a2 + 1, 2, \"0\"), MMM: h2(n2.monthsShort, a2, f2, 3), MMMM: h2(f2, a2), D: this.$D, DD: O.s(this.$D, 2, \"0\"), d: String(this.$W), dd: h2(n2.weekdaysMin, this.$W, o2, 2), ddd: h2(n2.weekdaysShort, this.$W, o2, 3), dddd: o2[this.$W], H: String(s2), HH: O.s(s2, 2, \"0\"), h: c2(1), hh: c2(2), a: d2(s2, u2, true), A: d2(s2, u2, false), m: String(u2), mm: O.s(u2, 2, \"0\"), s: String(this.$s), ss: O.s(this.$s, 2, \"0\"), SSS: O.s(this.$ms, 3, \"0\"), Z: i2 };\r\n          return r2.replace(y, function(t3, e3) {\r\n            return e3 || $2[t3] || i2.replace(\":\", \"\");\r\n          });\r\n        }, m2.utcOffset = function() {\r\n          return 15 * -Math.round(this.$d.getTimezoneOffset() / 15);\r\n        }, m2.diff = function(r2, d2, l2) {\r\n          var $2, y2 = O.p(d2), M3 = w(r2), m3 = (M3.utcOffset() - this.utcOffset()) * e, v2 = this - M3, g2 = O.m(this, M3);\r\n          return g2 = ($2 = {}, $2[c] = g2 / 12, $2[f] = g2, $2[h] = g2 / 3, $2[o] = (v2 - m3) / 6048e5, $2[a] = (v2 - m3) / 864e5, $2[u] = v2 / n, $2[s] = v2 / e, $2[i] = v2 / t, $2)[y2] || v2, l2 ? g2 : O.a(g2);\r\n        }, m2.daysInMonth = function() {\r\n          return this.endOf(f).$D;\r\n        }, m2.$locale = function() {\r\n          return D[this.$L];\r\n        }, m2.locale = function(t2, e2) {\r\n          if (!t2)\r\n            return this.$L;\r\n          var n2 = this.clone(), r2 = S(t2, e2, true);\r\n          return r2 && (n2.$L = r2), n2;\r\n        }, m2.clone = function() {\r\n          return O.w(this.$d, this);\r\n        }, m2.toDate = function() {\r\n          return new Date(this.valueOf());\r\n        }, m2.toJSON = function() {\r\n          return this.isValid() ? this.toISOString() : null;\r\n        }, m2.toISOString = function() {\r\n          return this.$d.toISOString();\r\n        }, m2.toString = function() {\r\n          return this.$d.toUTCString();\r\n        }, M2;\r\n      }(), T = _.prototype;\r\n      return w.prototype = T, [[\"$ms\", r], [\"$s\", i], [\"$m\", s], [\"$H\", u], [\"$W\", a], [\"$M\", f], [\"$y\", c], [\"$D\", d]].forEach(function(t2) {\r\n        T[t2[1]] = function(e2) {\r\n          return this.$g(e2, t2[0], t2[1]);\r\n        };\r\n      }), w.extend = function(t2, e2) {\r\n        return t2.$i || (t2(e2, _, w), t2.$i = true), w;\r\n      }, w.locale = S, w.isDayjs = p, w.unix = function(t2) {\r\n        return w(1e3 * t2);\r\n      }, w.en = D[g], w.Ls = D, w.p = {}, w;\r\n    });\r\n  }\r\n});\r\nexport default require_dayjs_min();\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/libs/util/route.js",
    "content": "/**\r\n * 路由跳转方法，该方法相对于直接使用uni.xxx的好处是使用更加简单快捷\r\n * 并且带有路由拦截功能\r\n */\r\nimport { queryParams, deepMerge, page } from '@/uni_modules/uv-ui-tools/libs/function/index.js'\r\nclass Router {\r\n\tconstructor() {\r\n\t\t// 原始属性定义\r\n\t\tthis.config = {\r\n\t\t\ttype: 'navigateTo',\r\n\t\t\turl: '',\r\n\t\t\tdelta: 1, // navigateBack页面后退时,回退的层数\r\n\t\t\tparams: {}, // 传递的参数\r\n\t\t\tanimationType: 'pop-in', // 窗口动画,只在APP有效\r\n\t\t\tanimationDuration: 300, // 窗口动画持续时间,单位毫秒,只在APP有效\r\n\t\t\tintercept: false ,// 是否需要拦截\r\n\t\t\tevents: {} // 页面间通信接口，用于监听被打开页面发送到当前页面的数据。hbuilderx 2.8.9+ 开始支持。\r\n\t\t}\r\n\t\t// 因为route方法是需要对外赋值给另外的对象使用，同时route内部有使用this，会导致route失去上下文\r\n\t\t// 这里在构造函数中进行this绑定\r\n\t\tthis.route = this.route.bind(this)\r\n\t}\r\n\r\n\t// 判断url前面是否有\"/\"，如果没有则加上，否则无法跳转\r\n\taddRootPath(url) {\r\n\t\treturn url[0] === '/' ? url : `/${url}`\r\n\t}\r\n\r\n\t// 整合路由参数\r\n\tmixinParam(url, params) {\r\n\t\turl = url && this.addRootPath(url)\r\n\r\n\t\t// 使用正则匹配，主要依据是判断是否有\"/\",\"?\",\"=\"等，如“/page/index/index?name=mary\"\r\n\t\t// 如果有url中有get参数，转换后无需带上\"?\"\r\n\t\tlet query = ''\r\n\t\tif (/.*\\/.*\\?.*=.*/.test(url)) {\r\n\t\t\t// object对象转为get类型的参数\r\n\t\t\tquery = queryParams(params, false)\r\n\t\t\t// 因为已有get参数,所以后面拼接的参数需要带上\"&\"隔开\r\n\t\t\treturn url += `&${query}`\r\n\t\t}\r\n\t\t// 直接拼接参数，因为此处url中没有后面的query参数，也就没有\"?/&\"之类的符号\r\n\t\tquery = queryParams(params)\r\n\t\treturn url += query\r\n\t}\r\n\r\n\t// 对外的方法名称\r\n\tasync route(options = {}, params = {}) {\r\n\t\t// 合并用户的配置和内部的默认配置\r\n\t\tlet mergeConfig = {}\r\n\r\n\t\tif (typeof options === 'string') {\r\n\t\t\t// 如果options为字符串，则为route(url, params)的形式\r\n\t\t\tmergeConfig.url = this.mixinParam(options, params)\r\n\t\t\tmergeConfig.type = 'navigateTo'\r\n\t\t} else {\r\n\t\t\tmergeConfig = deepMerge(this.config, options)\r\n\t\t\t// 否则正常使用mergeConfig中的url和params进行拼接\r\n\t\t\tmergeConfig.url = this.mixinParam(options.url, options.params)\r\n\t\t}\r\n\t\t// 如果本次跳转的路径和本页面路径一致，不执行跳转，防止用户快速点击跳转按钮，造成多次跳转同一个页面的问题\r\n\t\tif (mergeConfig.url === page()) return\r\n\r\n\t\tif (params.intercept) {\r\n\t\t\tmergeConfig.intercept = params.intercept\r\n\t\t}\r\n\t\t// params参数也带给拦截器\r\n\t\tmergeConfig.params = params\r\n\t\t// 合并内外部参数\r\n\t\tmergeConfig = deepMerge(this.config, mergeConfig)\r\n\t\t// 判断用户是否定义了拦截器\r\n\t\tif (typeof mergeConfig.intercept === 'function') {\r\n\t\t\t// 定一个promise，根据用户执行resolve(true)或者resolve(false)来决定是否进行路由跳转\r\n\t\t\tconst isNext = await new Promise((resolve, reject) => {\r\n\t\t\t\tmergeConfig.intercept(mergeConfig, resolve)\r\n\t\t\t})\r\n\t\t\t// 如果isNext为true，则执行路由跳转\r\n\t\t\tisNext && this.openPage(mergeConfig)\r\n\t\t} else {\r\n\t\t\tthis.openPage(mergeConfig)\r\n\t\t}\r\n\t}\r\n\r\n\t// 执行路由跳转\r\n\topenPage(config) {\r\n\t\t// 解构参数\r\n\t\tconst {\r\n\t\t\turl,\r\n\t\t\ttype,\r\n\t\t\tdelta,\r\n\t\t\tanimationType,\r\n\t\t\tanimationDuration,\r\n\t\t\tevents\r\n\t\t} = config\r\n\t\tif (config.type == 'navigateTo' || config.type == 'to') {\r\n\t\t\tuni.navigateTo({\r\n\t\t\t\turl,\r\n\t\t\t\tanimationType,\r\n\t\t\t\tanimationDuration,\r\n\t\t\t\tevents\r\n\t\t\t})\r\n\t\t}\r\n\t\tif (config.type == 'redirectTo' || config.type == 'redirect') {\r\n\t\t\tuni.redirectTo({\r\n\t\t\t\turl\r\n\t\t\t})\r\n\t\t}\r\n\t\tif (config.type == 'switchTab' || config.type == 'tab') {\r\n\t\t\tuni.switchTab({\r\n\t\t\t\turl\r\n\t\t\t})\r\n\t\t}\r\n\t\tif (config.type == 'reLaunch' || config.type == 'launch') {\r\n\t\t\tuni.reLaunch({\r\n\t\t\t\turl\r\n\t\t\t})\r\n\t\t}\r\n\t\tif (config.type == 'navigateBack' || config.type == 'back') {\r\n\t\t\tuni.navigateBack({\r\n\t\t\t\tdelta\r\n\t\t\t})\r\n\t\t}\r\n\t}\r\n}\r\n\r\nexport default (new Router()).route"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/package.json",
    "content": "{\r\n  \"id\": \"uv-ui-tools\",\r\n  \"displayName\": \"uv-ui-tools 工具集 全面兼容vue3+2、app、h5、小程序等多端\",\r\n  \"version\": \"1.1.19\",\r\n  \"description\": \"uv-ui-tools，集成工具库，强大的Http请求封装，清晰的文档说明，开箱即用。方便使用，可以全局使用\",\r\n  \"keywords\": [\r\n    \"uv-ui-tools,uv-ui组件库,工具集,uvui,uView2.x\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n      \"ads\": \"无\",\r\n      \"data\": \"插件不采集任何数据\",\r\n      \"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n      \"cloud\": {\r\n        \"tcb\": \"y\",\r\n        \"aliyun\": \"y\"\r\n      },\r\n      \"client\": {\r\n        \"Vue\": {\r\n          \"vue2\": \"y\",\r\n          \"vue3\": \"y\"\r\n        },\r\n        \"App\": {\r\n          \"app-vue\": \"y\",\r\n          \"app-nvue\": \"y\"\r\n        },\r\n        \"H5-mobile\": {\r\n          \"Safari\": \"y\",\r\n          \"Android Browser\": \"y\",\r\n          \"微信浏览器(Android)\": \"y\",\r\n          \"QQ浏览器(Android)\": \"y\"\r\n        },\r\n        \"H5-pc\": {\r\n          \"Chrome\": \"y\",\r\n          \"IE\": \"y\",\r\n          \"Edge\": \"y\",\r\n          \"Firefox\": \"y\",\r\n          \"Safari\": \"y\"\r\n        },\r\n        \"小程序\": {\r\n          \"微信\": \"y\",\r\n          \"阿里\": \"y\",\r\n          \"百度\": \"y\",\r\n          \"字节跳动\": \"y\",\r\n          \"QQ\": \"y\",\r\n          \"钉钉\": \"y\",\r\n          \"快手\": \"y\",\r\n          \"飞书\": \"y\",\r\n          \"京东\": \"y\"\r\n        },\r\n        \"快应用\": {\r\n          \"华为\": \"y\",\r\n          \"联盟\": \"y\"\r\n        }\r\n      }\r\n    }\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/readme.md",
    "content": "## uv-ui-tools 工具集\r\n\r\n> **组件名：uv-ui-tools**\r\n\r\nuv-ui工具集成，包括网络Http请求、便捷工具、节流防抖、对象操作、时间格式化、路由跳转、全局唯一标识符、规则校验等等。\r\n\r\n该组件推荐配合[uv-ui组件库](https://www.uvui.cn/components/intro.html)使用，单独下载也可以在自己项目中使用，需要做相应的配置，可查看文档。强烈推荐使用[uv-ui组件库](https://www.uvui.cn/components/intro.html)，导入组件都会自动导入`uv-ui-tools`。需要在自己的项目中使用请参考[扩展配置](https://www.uvui.cn/components/setting.html)。\r\n\r\nuv-ui破釜沉舟之兼容vue3+2、app、h5、多端小程序的uni-app生态框架，大部分组件基于uView2.x，在经过改进后全面支持vue3，部分组件做了进一步的优化，修复大量BUG，支持单独导入，方便开发者选择导入需要的组件。开箱即用，灵活配置。\r\n\r\n# <a href=\"https://www.uvui.cn/js/intro.html\" target=\"_blank\">查看文档</a>\r\n\r\n## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>（请不要 下载插件ZIP）</small>\r\n\r\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n<a href=\"https://ext.dcloud.net.cn/plugin?name=uv-ui\" target=\"_blank\">\r\n\r\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\r\n\r\n</a>\r\n\r\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-ui-tools/theme.scss",
    "content": "// 此文件为uvUI的主题变量，这些变量目前只能通过uni.scss引入才有效，另外由于\r\n// uni.scss中引入的样式会同时混入到全局样式文件和单独每一个页面的样式中，造成微信程序包太大，\r\n// 故uni.scss只建议放scss变量名相关样式，其他的样式可以通过main.js或者App.vue引入\r\n\r\n$uv-main-color: #303133;\r\n$uv-content-color: #606266;\r\n$uv-tips-color: #909193;\r\n$uv-light-color: #c0c4cc;\r\n$uv-border-color: #dadbde;\r\n$uv-bg-color: #f3f4f6;\r\n$uv-disabled-color: #c8c9cc;\r\n\r\n$uv-primary: #3c9cff;\r\n$uv-primary-dark: #398ade;\r\n$uv-primary-disabled: #9acafc;\r\n$uv-primary-light: #ecf5ff;\r\n\r\n$uv-warning: #f9ae3d;\r\n$uv-warning-dark: #f1a532;\r\n$uv-warning-disabled: #f9d39b;\r\n$uv-warning-light: #fdf6ec;\r\n\r\n$uv-success: #5ac725;\r\n$uv-success-dark: #53c21d;\r\n$uv-success-disabled: #a9e08f;\r\n$uv-success-light: #f5fff0;\r\n\r\n$uv-error: #f56c6c;\r\n$uv-error-dark: #e45656;\r\n$uv-error-disabled: #f7b2b2;\r\n$uv-error-light: #fef0f0;\r\n\r\n$uv-info: #909399;\r\n$uv-info-dark: #767a82;\r\n$uv-info-disabled: #c4c6c9;\r\n$uv-info-light: #f4f4f5;\r\n\r\n@mixin flex($direction: row) {\r\n\t/* #ifndef APP-NVUE */\r\n\tdisplay: flex;\r\n\t/* #endif */\r\n\tflex-direction: $direction;\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-upload/changelog.md",
    "content": "## 1.0.5（2023-08-31）\n1. 添加uv-popup依赖\n## 1.0.4（2023-08-18）\r\n1. 修复图片预览位置错误的BUG\r\n2. 修复视频预览不生效的BUG\r\n3. 修复改变上传视频宽高不生效的BUG\r\n## 1.0.3（2023-07-03）\r\n去除插槽判断，避免某些平台不显示的BUG\r\n## 1.0.2（2023-05-24）\r\n1. 优化fileList，watch中增加deep属性\r\n## 1.0.1（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.0（2023-05-10）\r\nuv-upload 上传\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-upload/components/uv-preview-video/uv-preview-video.vue",
    "content": "<template>\r\n\t<uv-popup ref=\"popup\" @change=\"change\">\r\n\t\t<view class=\"video-view\" v-if=\"show\">\r\n\t\t\t<video class=\"video\" :src=\"getSec\" :autoplay=\"autoplay\"></video>\r\n\t\t</view>\r\n\t</uv-popup>\r\n</template>\r\n<script>\r\n\texport default {\r\n\t\tprops: {\r\n\t\t\tsrc: {\r\n\t\t\t\ttype: String,\r\n\t\t\t\tdefault: ''\r\n\t\t\t},\r\n\t\t\tautoplay: {\r\n\t\t\t\ttype: Boolean,\r\n\t\t\t\tdefault: true\r\n\t\t\t}\r\n\t\t},\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tvideoSrc: '',\r\n\t\t\t\tshow: false\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\tgetSec() {\r\n\t\t\t\treturn this.src || this.videoSrc;\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\topen(url) {\r\n\t\t\t\tthis.videoSrc = url;\r\n\t\t\t\tthis.$refs.popup.open();\r\n\t\t\t},\r\n\t\t\tclose() {\r\n\t\t\t\tthis.$refs.popup.close();\r\n\t\t\t},\r\n\t\t\tchange(e) {\r\n\t\t\t\tthis.show = e.show;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n<style scoped lang=\"scss\">\r\n\t.video-view {\r\n\t\twidth: 750rpx;\r\n\t\t.video {\r\n\t\t\twidth: 750rpx;\r\n\t\t}\r\n\t}\r\n</style>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-upload/components/uv-upload/mixin.js",
    "content": "import { error } from '@/uni_modules/uv-ui-tools/libs/function/index.js'\r\nexport default {\r\n    watch: {\r\n        // 监听accept的变化，判断是否符合个平台要求\r\n        // 只有微信小程序才支持选择媒体，文件类型，所以这里做一个判断提示\r\n        accept: {\r\n            immediate: true,\r\n            handler(val) {\r\n                // #ifndef MP-WEIXIN\r\n                if (val === 'all' || val === 'media') {\r\n                    error('只有微信小程序才支持把accept配置为all、media之一')\r\n                }\r\n                // #endif\r\n                // #ifndef H5 || MP-WEIXIN\r\n                if (val === 'file') {\r\n                    error('只有微信小程序和H5(HX2.9.9)才支持把accept配置为file')\r\n                }\r\n                // #endif\r\n            }\r\n        }\r\n    }\r\n}\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-upload/components/uv-upload/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 接受的文件类型, 可选值为all media image file video\r\n\t\taccept: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'image'\r\n\t\t},\r\n\t\t// \t图片或视频拾取模式，当accept为image类型时设置capture可选额外camera可以直接调起摄像头\r\n\t\tcapture: {\r\n\t\t\ttype: [String, Array],\r\n\t\t\tdefault: () => ['album', 'camera']\r\n\t\t},\r\n\t\t// 当accept为video时生效，是否压缩视频，默认为true\r\n\t\tcompressed: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 当accept为video时生效，可选值为back或front\r\n\t\tcamera: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'back'\r\n\t\t},\r\n\t\t// 当accept为video时生效，拍摄视频最长拍摄时间，单位秒\r\n\t\tmaxDuration: {\r\n\t\t\ttype: Number,\r\n\t\t\tdefault: 60\r\n\t\t},\r\n\t\t// 上传区域的图标，只能内置图标\r\n\t\tuploadIcon: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'camera-fill'\r\n\t\t},\r\n\t\t// 上传区域的图标的颜色，默认\r\n\t\tuploadIconColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: '#D3D4D6'\r\n\t\t},\r\n\t\t// 是否开启文件读取前事件\r\n\t\tuseBeforeRead: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 读取后的处理函数\r\n\t\tafterRead: {\r\n\t\t\ttype: Function,\r\n\t\t\tdefault: null\r\n\t\t},\r\n\t\t// 读取前的处理函数\r\n\t\tbeforeRead: {\r\n\t\t\ttype: Function,\r\n\t\t\tdefault: null\r\n\t\t},\r\n\t\t// 是否开启图片预览功能\r\n\t\tpreviewFullImage: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 是否开启视频预览功能\r\n\t\tpreviewFullVideo: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 最大上传数量\r\n\t\tmaxCount: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 52\r\n\t\t},\r\n\t\t// 是否禁用\r\n\t\tdisabled: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 预览上传的图片时的裁剪模式，和image组件mode属性一致\r\n\t\timageMode: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'aspectFill'\r\n\t\t},\r\n\t\t// 标识符，可以在回调函数的第二项参数中获取\r\n\t\tname: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 所选的图片的尺寸, 可选值为original compressed\r\n\t\tsizeType: {\r\n\t\t\ttype: Array,\r\n\t\t\tdefault: () => ['original', 'compressed']\r\n\t\t},\r\n\t\t// 是否开启图片多选，部分安卓机型不支持\r\n\t\tmultiple: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 是否展示删除按钮\r\n\t\tdeletable: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 文件大小限制，单位为byte\r\n\t\tmaxSize: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: Number.MAX_VALUE\r\n\t\t},\r\n\t\t// 显示已上传的文件列表\r\n\t\tfileList: {\r\n\t\t\ttype: Array,\r\n\t\t\tdefault: () => []\r\n\t\t},\r\n\t\t// 上传区域的提示文字\r\n\t\tuploadText: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 内部预览图片区域和选择图片按钮的区域宽度\r\n\t\twidth: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 80\r\n\t\t},\r\n\t\t// 内部预览图片区域和选择图片按钮的区域高度\r\n\t\theight: {\r\n\t\t\ttype: [String, Number],\r\n\t\t\tdefault: 80\r\n\t\t},\r\n\t\t// 是否在上传完成后展示预览图\r\n\t\tpreviewImage: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t...uni.$uv?.props?.upload\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-upload/components/uv-upload/utils.js",
    "content": "function pickExclude(obj, keys) {\r\n\t// 某些情况下，type可能会为\r\n    if (!['[object Object]', '[object File]'].includes(Object.prototype.toString.call(obj))) {\r\n        return {}\r\n    }\r\n    return Object.keys(obj).reduce((prev, key) => {\r\n        if (!keys.includes(key)) {\r\n            prev[key] = obj[key]\r\n        }\r\n        return prev\r\n    }, {})\r\n}\r\n\r\nfunction formatImage(res) {\r\n    return res.tempFiles.map((item) => ({\r\n        ...pickExclude(item, ['path']),\r\n        type: 'image',\r\n        url: item.path,\r\n        thumb: item.path,\r\n\t\tsize: item.size,\r\n\t\t// #ifdef H5\r\n\t\tname: item.name\r\n\t\t// #endif\r\n    }))\r\n}\r\n\r\nfunction formatVideo(res) {\r\n    return [\r\n        {\r\n            ...pickExclude(res, ['tempFilePath', 'thumbTempFilePath', 'errMsg']),\r\n            type: 'video',\r\n            url: res.tempFilePath,\r\n            thumb: res.thumbTempFilePath,\r\n\t\t\tsize: res.size,\r\n\t\t\t// #ifdef H5\r\n\t\t\tname: res.name\r\n\t\t\t// #endif\r\n        }\r\n    ]\r\n}\r\n\r\nfunction formatMedia(res) {\r\n    return res.tempFiles.map((item) => ({\r\n        ...pickExclude(item, ['fileType', 'thumbTempFilePath', 'tempFilePath']),\r\n        type: res.type,\r\n        url: item.tempFilePath,\r\n        thumb: res.type === 'video' ? item.thumbTempFilePath : item.tempFilePath,\r\n\t\tsize: item.size\r\n    }))\r\n}\r\n\r\nfunction formatFile(res) {\r\n    return res.tempFiles.map((item) => ({ \r\n\t\t...pickExclude(item, ['path']), \r\n\t\turl: item.path, \r\n\t\tsize:item.size,\r\n\t\t// #ifdef H5\r\n\t\tname: item.name,\r\n\t\ttype: item.type\r\n\t\t// #endif \r\n\t}))\r\n}\r\nexport function chooseFile({\r\n    accept,\r\n    multiple,\r\n    capture,\r\n    compressed,\r\n    maxDuration,\r\n    sizeType,\r\n    camera,\r\n    maxCount\r\n}) {\r\n    return new Promise((resolve, reject) => {\r\n        switch (accept) {\r\n        case 'image':\r\n            uni.chooseImage({\r\n                count: multiple ? Math.min(maxCount, 9) : 1,\r\n                sourceType: capture,\r\n                sizeType,\r\n                success: (res) => resolve(formatImage(res)),\r\n                fail: reject\r\n            })\r\n            break\r\n            // #ifdef MP-WEIXIN\r\n            // 只有微信小程序才支持chooseMedia接口\r\n        case 'media':\r\n            wx.chooseMedia({\r\n                count: multiple ? Math.min(maxCount, 9) : 1,\r\n                sourceType: capture,\r\n                maxDuration,\r\n                sizeType,\r\n                camera,\r\n                success: (res) => resolve(formatMedia(res)),\r\n                fail: reject\r\n            })\r\n            break\r\n            // #endif\r\n        case 'video':\r\n            uni.chooseVideo({\r\n                sourceType: capture,\r\n                compressed,\r\n                maxDuration,\r\n                camera,\r\n                success: (res) => resolve(formatVideo(res)),\r\n                fail: reject\r\n            })\r\n            break\r\n            // #ifdef MP-WEIXIN || H5\r\n            // 只有微信小程序才支持chooseMessageFile接口\r\n        case 'file':\r\n            // #ifdef MP-WEIXIN\r\n            wx.chooseMessageFile({\r\n                count: multiple ? maxCount : 1,\r\n                type: accept,\r\n                success: (res) => resolve(formatFile(res)),\r\n                fail: reject\r\n            })\r\n            // #endif\r\n            // #ifdef H5\r\n            // 需要hx2.9.9以上才支持uni.chooseFile\r\n            uni.chooseFile({\r\n                count: multiple ? maxCount : 1,\r\n                type: accept,\r\n                success: (res) => resolve(formatFile(res)),\r\n                fail: reject\r\n            })\r\n            // #endif\r\n            break\r\n\t\t\t\t// #endif\r\n\t\tdefault: \r\n\t\t\t// 此为保底选项，在accept不为上面任意一项的时候选取全部文件\r\n\t\t\t// #ifdef MP-WEIXIN\r\n\t\t\twx.chooseMessageFile({\r\n\t\t\t    count: multiple ? maxCount : 1,\r\n\t\t\t    type: 'all',\r\n\t\t\t    success: (res) => resolve(formatFile(res)),\r\n\t\t\t    fail: reject\r\n\t\t\t})\r\n\t\t\t// #endif\r\n\t\t\t// #ifdef H5\r\n\t\t\t// 需要hx2.9.9以上才支持uni.chooseFile\r\n\t\t\tuni.chooseFile({\r\n\t\t\t\tcount: multiple ? maxCount : 1,\r\n\t\t\t\ttype: 'all',\r\n\t\t\t\tsuccess: (res) => resolve(formatFile(res)),\r\n\t\t\t\tfail: reject\r\n\t\t\t})\r\n\t\t\t// #endif\r\n        }\r\n    })\r\n}\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-upload/components/uv-upload/uv-upload.vue",
    "content": "<template>\r\n\t<view class=\"uv-upload\" :style=\"[$uv.addStyle(customStyle)]\">\r\n\t\t<view class=\"uv-upload__wrap\">\r\n\t\t\t<template v-if=\"previewImage\">\r\n\t\t\t\t<view class=\"uv-upload__wrap__preview\" v-for=\"(item, index) in lists\" :key=\"index\">\r\n\t\t\t\t\t<image \r\n\t\t\t\t\t\tv-if=\"item.isImage || (item.type && item.type === 'image')\" \r\n\t\t\t\t\t\t:src=\"item.thumb || item.url\" :mode=\"imageMode\" \r\n\t\t\t\t\t\tclass=\"uv-upload__wrap__preview__image\" \r\n\t\t\t\t\t\t@tap=\"onPreviewImage(item,index)\" \r\n\t\t\t\t\t\t:style=\"[{\r\n\t\t\t\t\t\t\twidth: $uv.addUnit(width),\r\n\t\t\t\t\t\t\theight: $uv.addUnit(height)\r\n\t\t\t\t\t\t}]\" \r\n\t\t\t\t\t\t/>\r\n\t\t\t\t\t<view \r\n\t\t\t\t\t\tv-else \r\n\t\t\t\t\t\tclass=\"uv-upload__wrap__preview__other\" \r\n\t\t\t\t\t\t@tap=\"onPreviewVideo(item,index)\" \r\n\t\t\t\t\t\t:style=\"[{\r\n\t\t\t\t\t\t\twidth: $uv.addUnit(width),\r\n\t\t\t\t\t\t\theight: $uv.addUnit(height)\r\n\t\t\t\t\t\t}]\"\r\n\t\t\t\t\t\t>\r\n\t\t\t\t\t\t<uv-icon color=\"#80CBF9\" size=\"26\" :name=\"item.isVideo || (item.type && item.type === 'video') ? 'movie' : 'folder'\"></uv-icon>\r\n\t\t\t\t\t\t<text class=\"uv-upload__wrap__preview__other__text\">{{item.isVideo || (item.type && item.type === 'video') ? '视频' : '文件'}}</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"uv-upload__status\" v-if=\"item.status === 'uploading' || item.status === 'failed'\">\r\n\t\t\t\t\t\t<view class=\"uv-upload__status__icon\">\r\n\t\t\t\t\t\t\t<uv-icon v-if=\"item.status === 'failed'\" name=\"close-circle\" color=\"#ffffff\" size=\"25\" />\r\n\t\t\t\t\t\t\t<uv-loading-icon size=\"22\" mode=\"circle\" v-else />\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<text v-if=\"item.message\" class=\"uv-upload__status__message\">{{ item.message }}</text>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"uv-upload__deletable\" v-if=\"item.status !== 'uploading' && (deletable || item.deletable)\" @tap.stop=\"deleteItem(index)\">\r\n\t\t\t\t\t\t<view class=\"uv-upload__deletable__icon\">\r\n\t\t\t\t\t\t\t<uv-icon name=\"close\" color=\"#ffffff\" size=\"10\"></uv-icon>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</view>\r\n\t\t\t\t\t<view class=\"uv-upload__success\" v-if=\"item.status === 'success'\">\r\n\t\t\t\t\t\t<!-- #ifdef APP-NVUE -->\r\n\t\t\t\t\t\t<image :src=\"successIcon\" class=\"uv-upload__success__icon\"></image>\r\n\t\t\t\t\t\t<!-- #endif -->\r\n\t\t\t\t\t\t<!-- #ifndef APP-NVUE -->\r\n\t\t\t\t\t\t<view class=\"uv-upload__success__icon\">\r\n\t\t\t\t\t\t\t<uv-icon name=\"checkmark\" color=\"#ffffff\" size=\"12\"></uv-icon>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t\t<!-- #endif -->\r\n\t\t\t\t\t</view>\r\n\t\t\t\t</view>\r\n\t\t\t</template>\r\n\t\t\t<template v-if=\"isInCount\">\r\n\t\t\t\t<view @tap=\"chooseFile\">\r\n\t\t\t\t\t<slot>\r\n\t\t\t\t\t\t<view class=\"uv-upload__button\" :hover-class=\"!disabled ? 'uv-upload__button--hover' : ''\" hover-stay-time=\"150\" @tap.stop=\"chooseFile\" :class=\"[disabled && 'uv-upload__button--disabled']\" :style=\"[{\r\n\t\t\t\t\t\t\t\twidth: $uv.addUnit(width),\r\n\t\t\t\t\t\t\t\theight: $uv.addUnit(height)\r\n\t\t\t\t\t\t\t}]\">\r\n\t\t\t\t\t\t\t<uv-icon :name=\"uploadIcon\" size=\"26\" :color=\"uploadIconColor\"></uv-icon>\r\n\t\t\t\t\t\t\t<text v-if=\"uploadText\" class=\"uv-upload__button__text\">{{ uploadText }}</text>\r\n\t\t\t\t\t\t</view>\r\n\t\t\t\t\t</slot>\r\n\t\t\t\t</view>\r\n\t\t\t</template>\r\n\t\t</view>\r\n\t\t<uv-preview-video ref=\"previewVideo\"></uv-preview-video>\r\n\t</view>\r\n</template>\r\n\r\n<script>\r\n\timport { func, image, video, array, promise } from '@/uni_modules/uv-ui-tools/libs/function/test.js';\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport { chooseFile } from './utils';\r\n\timport mixin_accept from './mixin.js';\r\n\timport props from './props.js';\r\n\t/**\r\n\t * upload 上传\r\n\t * @description 该组件用于上传图片场景\r\n\t * @tutorial https://www.uvui.cn/components/upload.html\r\n\t * @property {String}\t\t\taccept\t\t\t\t接受的文件类型, 可选值为all media image file video （默认 'image' ）\r\n\t * @property {String | Array}\tcapture\t\t\t\t图片或视频拾取模式，当accept为image类型时设置capture可选额外camera可以直接调起摄像头（默认 ['album', 'camera'] ）\r\n\t * @property {Boolean}\t\t\tcompressed\t\t\t当accept为video时生效，是否压缩视频，默认为true（默认 true ）\r\n\t * @property {String}\t\t\tcamera\t\t\t\t当accept为video时生效，可选值为back或front（默认 'back' ）\r\n\t * @property {Number}\t\t\tmaxDuration\t\t\t当accept为video时生效，拍摄视频最长拍摄时间，单位秒（默认 60 ）\r\n\t * @property {String}\t\t\tuploadIcon\t\t\t上传区域的图标，只能内置图标（默认 'camera-fill' ）\r\n\t * @property {String}\t\t\tuploadIconColor\t\t上传区域的图标的字体颜色，只能内置图标（默认 #D3D4D6 ）\r\n\t * @property {Boolean}\t\t\tuseBeforeRead\t\t是否开启文件读取前事件（默认 false ）\r\n\t * @property {Boolean}\t\t\tpreviewFullImage\t是否开启图片预览功能（默认 true ）\r\n\t * @property {Boolean}\t\t\tpreviewFullVideo\t是否开启视频预览功能（默认 true ）\r\n\t * @property {String | Number}\tmaxCount\t\t\t最大上传数量（默认 52 ）\r\n\t * @property {Boolean}\t\t\tdisabled\t\t\t是否启用（默认 false ）\r\n\t * @property {String}\t\t\timageMode\t\t\t预览上传的图片时的裁剪模式，和image组件mode属性一致（默认 'aspectFill' ）\r\n\t * @property {String}\t\t\tname\t\t\t\t标识符，可以在回调函数的第二项参数中获取\r\n\t * @property {Array}\t\t\tsizeType\t\t\t所选的图片的尺寸, 可选值为original compressed（默认 ['original', 'compressed'] ）\r\n\t * @property {Boolean}\t\t\tmultiple\t\t\t是否开启图片多选，部分安卓机型不支持 （默认 false ）\r\n\t * @property {Boolean}\t\t\tdeletable\t\t\t是否展示删除按钮（默认 true ）\r\n\t * @property {String | Number}\tmaxSize\t\t\t\t文件大小限制，单位为byte （默认 Number.MAX_VALUE ）\r\n\t * @property {Array}\t\t\tfileList\t\t\t显示已上传的文件列表\r\n\t * @property {String}\t\t\tuploadText\t\t\t上传区域的提示文字\r\n\t * @property {String | Number}\twidth\t\t\t\t内部预览图片区域和选择图片按钮的区域宽度（默认 80 ）\r\n\t * @property {String | Number}\theight\t\t\t\t内部预览图片区域和选择图片按钮的区域高度（默认 80 ）\r\n\t * @property {Object}\t\t\tcustomStyle\t\t\t组件的样式，对象形式\r\n\t * @event {Function} afterRead\t\t读取后的处理函数\r\n\t * @event {Function} beforeRead\t\t读取前的处理函数\r\n\t * @event {Function} oversize\t\t文件超出大小限制\r\n\t * @event {Function} clickPreview\t点击预览时触发\r\n\t * @event {Function} delete \t\t删除图片\r\n\t * @example <uv-upload :action=\"action\" :fileList=\"fileList\" ></uv-upload>\r\n\t */\r\n\texport default {\r\n\t\tname: \"uv-upload\",\r\n\t\t// #ifdef VUE3\r\n\t\temits: ['error', 'beforeRead', 'oversize', 'afterRead', 'delete', 'clickPreview'],\r\n\t\t// #endif\r\n\t\tmixins: [mpMixin, mixin, mixin_accept, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\tsuccessIcon: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACgAAAAoCAYAAACM/rhtAAAAAXNSR0IArs4c6QAAAERlWElmTU0AKgAAAAgAAYdpAAQAAAABAAAAGgAAAAAAA6ABAAMAAAABAAEAAKACAAQAAAABAAAAKKADAAQAAAABAAAAKAAAAAB65masAAACP0lEQVRYCc3YXygsURwH8K/dpcWyG3LF5u/6/+dKVylSypuUl6uUPMifKMWL8oKEB1EUT1KeUPdR3uTNUsSLxb2udG/cbvInNuvf2rVnazZ/ZndmZ87snjM1Z+Z3zpzfp9+Z5mEAhlvjRtZgCKs+gnPAOcAkkMOR4jEHfItjDvgRxxSQD8cM0BuOCaAvXNCBQrigAsXgggYUiwsK0B9cwIH+4gIKlIILGFAqLiBAOTjFgXJxigJp4BQD0sIpAqSJow6kjSNAFTnRaHJwLenD6Mud52VQAcrBfTd2oyq+HtGaGGWAcnAVcXWoM3bCZrdi+ncPfaAcXE5UKVpdW/vitGPqqAtn98d0gXJwX7Qp6MmegUYVhvmTIezdmHlxJCjpHRTCFerLkRRu4k0aqdajN3sWOo0BK//msHa+xDuPC/oNFMKRhTtM4xjIX0SCNpXL4+7VIaHuyiWEp2L7ahWLf8fejfPdqPmC3mJicORZUp1CQzm+GiphvljGk+PBvWRbxii+xVTj5M6CiZ/tsDufvaXyxEUDxeLIyvu3m0iOyEFWVAkydcVYdyFrE9tQk9iMq6f/GNlvwt3LjQfh60LUrw9/cFyyMJUW/XkLSNMV4Mi6C5ML+ui4x5ClAX9sB9w0wV6wglJwJCv5fOxcr6EstgbGiEw4XcfUry4cWrcEUW8n+ARKxXEJHhw2WG43UKSvwI/TSZgvl7kh0b3XLZaLEy0QmMgLZAVH7J+ALOE+AVnDvQOyiPMAWcW5gSzjCPAV+78S5WE0GrQAAAAASUVORK5CYII=',\r\n\t\t\t\t// #endif\r\n\t\t\t\tlists: [],\r\n\t\t\t\tisInCount: true,\r\n\t\t\t}\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\t// 监听文件列表的变化，重新整理内部数据\r\n\t\t\tfileList: {\r\n\t\t\t\tdeep: true,\r\n\t\t\t\timmediate: true,\r\n\t\t\t\thandler() {\r\n\t\t\t\t\tthis.formatFileList()\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tformatFileList() {\r\n\t\t\t\tconst {\r\n\t\t\t\t\tfileList = [], maxCount\r\n\t\t\t\t} = this;\r\n\t\t\t\tconst lists = fileList.map((item) =>\r\n\t\t\t\t\tObject.assign(Object.assign({}, item), {\r\n\t\t\t\t\t\t// 如果item.url为本地选择的blob文件的话，无法判断其为video还是image，此处优先通过accept做判断处理\r\n\t\t\t\t\t\tisImage: this.accept === 'image' || image(item.url || item.thumb),\r\n\t\t\t\t\t\tisVideo: this.accept === 'video' || video(item.url || item.thumb),\r\n\t\t\t\t\t\tdeletable: typeof(item.deletable) === 'boolean' ? item.deletable : this.deletable,\r\n\t\t\t\t\t})\r\n\t\t\t\t);\r\n\t\t\t\tthis.lists = lists\r\n\t\t\t\tthis.isInCount = lists.length < maxCount\r\n\t\t\t},\r\n\t\t\tchooseFile() {\r\n\t\t\t\tthis.timer && clearTimeout(this.timer);\r\n\t\t\t\tthis.timer = setTimeout(() => {\r\n\t\t\t\t\tconst {\r\n\t\t\t\t\t\tmaxCount,\r\n\t\t\t\t\t\tmultiple,\r\n\t\t\t\t\t\tlists,\r\n\t\t\t\t\t\tdisabled\r\n\t\t\t\t\t} = this;\r\n\t\t\t\t\tif (disabled) return;\r\n\t\t\t\t\t// 如果用户传入的是字符串，需要格式化成数组\r\n\t\t\t\t\tlet capture;\r\n\t\t\t\t\ttry {\r\n\t\t\t\t\t\tcapture = array(this.capture) ? this.capture : this.capture.split(',');\r\n\t\t\t\t\t} catch (e) {\r\n\t\t\t\t\t\tcapture = [];\r\n\t\t\t\t\t}\r\n\t\t\t\t\tchooseFile(\r\n\t\t\t\t\t\t\tObject.assign({\r\n\t\t\t\t\t\t\t\taccept: this.accept,\r\n\t\t\t\t\t\t\t\tmultiple: this.multiple,\r\n\t\t\t\t\t\t\t\tcapture: capture,\r\n\t\t\t\t\t\t\t\tcompressed: this.compressed,\r\n\t\t\t\t\t\t\t\tmaxDuration: this.maxDuration,\r\n\t\t\t\t\t\t\t\tsizeType: this.sizeType,\r\n\t\t\t\t\t\t\t\tcamera: this.camera,\r\n\t\t\t\t\t\t\t}, {\r\n\t\t\t\t\t\t\t\tmaxCount: maxCount - lists.length,\r\n\t\t\t\t\t\t\t})\r\n\t\t\t\t\t\t)\r\n\t\t\t\t\t\t.then((res) => {\r\n\t\t\t\t\t\t\tthis.onBeforeRead(multiple ? res : res[0]);\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t\t.catch((error) => {\r\n\t\t\t\t\t\t\tthis.$emit('error', error);\r\n\t\t\t\t\t\t});\r\n\t\t\t\t}, 100)\r\n\t\t\t},\r\n\t\t\t// 文件读取之前\r\n\t\t\tonBeforeRead(file) {\r\n\t\t\t\tconst {\r\n\t\t\t\t\tbeforeRead,\r\n\t\t\t\t\tuseBeforeRead,\r\n\t\t\t\t} = this;\r\n\t\t\t\tlet res = true\r\n\t\t\t\t// beforeRead是否为一个方法\r\n\t\t\t\tif (func(beforeRead)) {\r\n\t\t\t\t\t// 如果用户定义了此方法，则去执行此方法，并传入读取的文件回调\r\n\t\t\t\t\tres = beforeRead(file, this.getDetail());\r\n\t\t\t\t}\r\n\t\t\t\tif (useBeforeRead) {\r\n\t\t\t\t\tres = new Promise((resolve, reject) => {\r\n\t\t\t\t\t\tthis.$emit(\r\n\t\t\t\t\t\t\t'beforeRead',\r\n\t\t\t\t\t\t\tObject.assign(Object.assign({\r\n\t\t\t\t\t\t\t\tfile\r\n\t\t\t\t\t\t\t}, this.getDetail()), {\r\n\t\t\t\t\t\t\t\tcallback: (ok) => {\r\n\t\t\t\t\t\t\t\t\tok ? resolve() : reject();\r\n\t\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t\t})\r\n\t\t\t\t\t\t);\r\n\t\t\t\t\t});\r\n\t\t\t\t}\r\n\t\t\t\tif (!res) {\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\t\t\t\tif (promise(res)) {\r\n\t\t\t\t\tres.then((data) => this.onAfterRead(data || file));\r\n\t\t\t\t} else {\r\n\t\t\t\t\tthis.onAfterRead(file);\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tgetDetail(index) {\r\n\t\t\t\treturn {\r\n\t\t\t\t\tname: this.name,\r\n\t\t\t\t\tindex: index == null ? this.fileList.length : index,\r\n\t\t\t\t};\r\n\t\t\t},\r\n\t\t\tonAfterRead(file) {\r\n\t\t\t\tconst {\r\n\t\t\t\t\tmaxSize,\r\n\t\t\t\t\tafterRead\r\n\t\t\t\t} = this;\r\n\t\t\t\tconst oversize = Array.isArray(file) ?\r\n\t\t\t\t\tfile.some((item) => item.size > maxSize) :\r\n\t\t\t\t\tfile.size > maxSize;\r\n\t\t\t\tif (oversize) {\r\n\t\t\t\t\tthis.$emit('oversize', Object.assign({\r\n\t\t\t\t\t\tfile\r\n\t\t\t\t\t}, this.getDetail()));\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\t\t\t\tif (typeof afterRead === 'function') {\r\n\t\t\t\t\tafterRead(file, this.getDetail());\r\n\t\t\t\t}\r\n\t\t\t\tthis.$emit('afterRead', Object.assign({\r\n\t\t\t\t\tfile\r\n\t\t\t\t}, this.getDetail()));\r\n\t\t\t},\r\n\t\t\tdeleteItem(index) {\r\n\t\t\t\tthis.$emit(\r\n\t\t\t\t\t'delete',\r\n\t\t\t\t\tObject.assign(Object.assign({}, this.getDetail(index)), {\r\n\t\t\t\t\t\tfile: this.fileList[index],\r\n\t\t\t\t\t})\r\n\t\t\t\t);\r\n\t\t\t},\r\n\t\t\t// 预览图片\r\n\t\t\tonPreviewImage(item, index) {\r\n\t\t\t\tconst lists = this.$uv.deepClone(this.lists);\r\n\t\t\t\tlists.map((i,j)=>{\r\n\t\t\t\t\tif(j == index) {\r\n\t\t\t\t\t\ti.current = true;\r\n\t\t\t\t\t}\r\n\t\t\t\t});\r\n\t\t\t\tconst filters = lists.filter(i=>i.isImage);\r\n\t\t\t\tconst findIndex = filters.findIndex(i=>i.current);\r\n\t\t\t\tthis.onClickPreview(item, index);\r\n\t\t\t\tif (!item.isImage || !this.previewFullImage) return\r\n\t\t\t\tuni.previewImage({\r\n\t\t\t\t\t// 先filter找出为图片的item，再返回filter结果中的图片url\r\n\t\t\t\t\turls: this.lists.filter((item) => this.accept === 'image' || image(item.url || item.thumb)).map((item) => item.url || item.thumb),\r\n\t\t\t\t\tcurrent: findIndex,\r\n\t\t\t\t\tfail() {\r\n\t\t\t\t\t\tthis.$uv.toast('预览图片失败')\r\n\t\t\t\t\t},\r\n\t\t\t\t});\r\n\t\t\t},\r\n\t\t\tonPreviewVideo(item, index) {\r\n\t\t\t\tthis.onClickPreview(item, index);\r\n\t\t\t\tif (!this.previewFullVideo || !item.isVideo) return;\r\n\t\t\t\tthis.$refs.previewVideo.open(item.url);\r\n\t\t\t},\r\n\t\t\tonClickPreview(item, index) {\r\n\t\t\t\tthis.$emit(\r\n\t\t\t\t\t'clickPreview',\r\n\t\t\t\t\tObject.assign(Object.assign({}, item), this.getDetail(index))\r\n\t\t\t\t);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t$uv-upload-preview-border-radius: 2px !default;\r\n\t$uv-upload-preview-margin: 0 8px 8px 0 !default;\r\n\t$uv-upload-image-width: 80px !default;\r\n\t$uv-upload-image-height: $uv-upload-image-width;\r\n\t$uv-upload-other-bgColor: rgb(242, 242, 242) !default;\r\n\t$uv-upload-other-flex: 1 !default;\r\n\t$uv-upload-text-font-size: 11px !default;\r\n\t$uv-upload-text-color: $uv-tips-color !default;\r\n\t$uv-upload-text-margin-top: 2px !default;\r\n\t$uv-upload-deletable-right: 0 !default;\r\n\t$uv-upload-deletable-top: 0 !default;\r\n\t$uv-upload-deletable-bgColor: rgb(55, 55, 55) !default;\r\n\t$uv-upload-deletable-height: 14px !default;\r\n\t$uv-upload-deletable-width: $uv-upload-deletable-height;\r\n\t$uv-upload-deletable-boder-bottom-left-radius: 100px !default;\r\n\t$uv-upload-deletable-zIndex: 3 !default;\r\n\t$uv-upload-success-bottom: 0 !default;\r\n\t$uv-upload-success-right: 0 !default;\r\n\t$uv-upload-success-border-style: solid !default;\r\n\t$uv-upload-success-border-top-color: transparent !default;\r\n\t$uv-upload-success-border-left-color: transparent !default;\r\n\t$uv-upload-success-border-bottom-color: $uv-success !default;\r\n\t$uv-upload-success-border-right-color: $uv-upload-success-border-bottom-color;\r\n\t$uv-upload-success-border-width: 9px !default;\r\n\t$uv-upload-icon-top: 0px !default;\r\n\t$uv-upload-icon-right: 0px !default;\r\n\t$uv-upload-icon-h5-top: 1px !default;\r\n\t$uv-upload-icon-h5-right: 0 !default;\r\n\t$uv-upload-icon-width: 16px !default;\r\n\t$uv-upload-icon-height: $uv-upload-icon-width;\r\n\t$uv-upload-success-icon-bottom: -10px !default;\r\n\t$uv-upload-success-icon-right: -10px !default;\r\n\t$uv-upload-status-right: 0 !default;\r\n\t$uv-upload-status-left: 0 !default;\r\n\t$uv-upload-status-bottom: 0 !default;\r\n\t$uv-upload-status-top: 0 !default;\r\n\t$uv-upload-status-bgColor: rgba(0, 0, 0, 0.5) !default;\r\n\t$uv-upload-status-icon-Zindex: 1 !default;\r\n\t$uv-upload-message-font-size: 12px !default;\r\n\t$uv-upload-message-color: #FFFFFF !default;\r\n\t$uv-upload-message-margin-top: 5px !default;\r\n\t$uv-upload-button-width: 80px !default;\r\n\t$uv-upload-button-height: $uv-upload-button-width;\r\n\t$uv-upload-button-bgColor: rgb(244, 245, 247) !default;\r\n\t$uv-upload-button-border-radius: 2px !default;\r\n\t$uv-upload-botton-margin: 0 8px 8px 0 !default;\r\n\t$uv-upload-text-font-size: 11px !default;\r\n\t$uv-upload-text-color: $uv-tips-color !default;\r\n\t$uv-upload-text-margin-top: 2px !default;\r\n\t$uv-upload-hover-bgColor: rgb(230, 231, 233) !default;\r\n\t$uv-upload-disabled-opacity: .5 !default;\r\n\t.uv-upload {\r\n\t\t@include flex(column);\r\n\t\tflex: 1;\r\n\t\t&__wrap {\r\n\t\t\t@include flex;\r\n\t\t\tflex-wrap: wrap;\r\n\t\t\tflex: 1;\r\n\t\t\t&__preview {\r\n\t\t\t\tborder-radius: $uv-upload-preview-border-radius;\r\n\t\t\t\tmargin: $uv-upload-preview-margin;\r\n\t\t\t\tposition: relative;\r\n\t\t\t\toverflow: hidden;\r\n\t\t\t\t@include flex;\r\n\t\t\t\t&__image {\r\n\t\t\t\t\twidth: $uv-upload-image-width;\r\n\t\t\t\t\theight: $uv-upload-image-height;\r\n\t\t\t\t}\r\n\t\t\t\t&__other {\r\n\t\t\t\t\twidth: $uv-upload-image-width;\r\n\t\t\t\t\theight: $uv-upload-image-height;\r\n\t\t\t\t\tbackground-color: $uv-upload-other-bgColor;\r\n\t\t\t\t\tflex: $uv-upload-other-flex;\r\n\t\t\t\t\t@include flex(column);\r\n\t\t\t\t\tjustify-content: center;\r\n\t\t\t\t\talign-items: center;\r\n\t\t\t\t\t&__text {\r\n\t\t\t\t\t\tfont-size: $uv-upload-text-font-size;\r\n\t\t\t\t\t\tcolor: $uv-upload-text-color;\r\n\t\t\t\t\t\tmargin-top: $uv-upload-text-margin-top;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t&__deletable {\r\n\t\t\tposition: absolute;\r\n\t\t\ttop: $uv-upload-deletable-top;\r\n\t\t\tright: $uv-upload-deletable-right;\r\n\t\t\tbackground-color: $uv-upload-deletable-bgColor;\r\n\t\t\theight: $uv-upload-deletable-height;\r\n\t\t\twidth: $uv-upload-deletable-width;\r\n\t\t\t@include flex;\r\n\t\t\tborder-bottom-left-radius: $uv-upload-deletable-boder-bottom-left-radius;\r\n\t\t\talign-items: center;\r\n\t\t\tjustify-content: center;\r\n\t\t\tz-index: $uv-upload-deletable-zIndex;\r\n\t\t\t&__icon {\r\n\t\t\t\tposition: absolute;\r\n\t\t\t\ttransform: scale(0.7);\r\n\t\t\t\ttop: $uv-upload-icon-top;\r\n\t\t\t\tright: $uv-upload-icon-right;\r\n\t\t\t\t/* #ifdef H5 */\r\n\t\t\t\ttop: $uv-upload-icon-h5-top;\r\n\t\t\t\tright: $uv-upload-icon-h5-right;\r\n\t\t\t\t/* #endif */\r\n\t\t\t}\r\n\t\t}\r\n\t\t&__success {\r\n\t\t\tposition: absolute;\r\n\t\t\tbottom: $uv-upload-success-bottom;\r\n\t\t\tright: $uv-upload-success-right;\r\n\t\t\t@include flex;\r\n\t\t\t// 由于weex(nvue)为阿里巴巴的KPI(部门业绩考核)的laji产物，不支持css绘制三角形\r\n\t\t\t// 所以在nvue下使用图片，非nvue下使用css实现\r\n\t\t\t/* #ifndef APP-NVUE */\r\n\t\t\tborder-style: $uv-upload-success-border-style;\r\n\t\t\tborder-top-color: $uv-upload-success-border-top-color;\r\n\t\t\tborder-left-color: $uv-upload-success-border-left-color;\r\n\t\t\tborder-bottom-color: $uv-upload-success-border-bottom-color;\r\n\t\t\tborder-right-color: $uv-upload-success-border-right-color;\r\n\t\t\tborder-width: $uv-upload-success-border-width;\r\n\t\t\talign-items: center;\r\n\t\t\tjustify-content: center;\r\n\t\t\t/* #endif */\r\n\t\t\t&__icon {\r\n\t\t\t\t/* #ifndef APP-NVUE */\r\n\t\t\t\tposition: absolute;\r\n\t\t\t\ttransform: scale(0.7);\r\n\t\t\t\tbottom: $uv-upload-success-icon-bottom;\r\n\t\t\t\tright: $uv-upload-success-icon-right;\r\n\t\t\t\t/* #endif */\r\n\t\t\t\t/* #ifdef APP-NVUE */\r\n\t\t\t\twidth: $uv-upload-icon-width;\r\n\t\t\t\theight: $uv-upload-icon-height;\r\n\t\t\t\t/* #endif */\r\n\t\t\t}\r\n\t\t}\r\n\t\t&__status {\r\n\t\t\tposition: absolute;\r\n\t\t\ttop: $uv-upload-status-top;\r\n\t\t\tbottom: $uv-upload-status-bottom;\r\n\t\t\tleft: $uv-upload-status-left;\r\n\t\t\tright: $uv-upload-status-right;\r\n\t\t\tbackground-color: $uv-upload-status-bgColor;\r\n\t\t\t@include flex(column);\r\n\t\t\talign-items: center;\r\n\t\t\tjustify-content: center;\r\n\t\t\t&__icon {\r\n\t\t\t\tposition: relative;\r\n\t\t\t\tz-index: $uv-upload-status-icon-Zindex;\r\n\t\t\t}\r\n\t\t\t&__message {\r\n\t\t\t\tfont-size: $uv-upload-message-font-size;\r\n\t\t\t\tcolor: $uv-upload-message-color;\r\n\t\t\t\tmargin-top: $uv-upload-message-margin-top;\r\n\t\t\t}\r\n\t\t}\r\n\t\t&__button {\r\n\t\t\t@include flex(column);\r\n\t\t\talign-items: center;\r\n\t\t\tjustify-content: center;\r\n\t\t\twidth: $uv-upload-button-width;\r\n\t\t\theight: $uv-upload-button-height;\r\n\t\t\tbackground-color: $uv-upload-button-bgColor;\r\n\t\t\tborder-radius: $uv-upload-button-border-radius;\r\n\t\t\tmargin: $uv-upload-botton-margin;\r\n\t\t\t/* #ifndef APP-NVUE */\r\n\t\t\tbox-sizing: border-box;\r\n\t\t\t/* #endif */\r\n\t\t\t&__text {\r\n\t\t\t\tfont-size: $uv-upload-text-font-size;\r\n\t\t\t\tcolor: $uv-upload-text-color;\r\n\t\t\t\tmargin-top: $uv-upload-text-margin-top;\r\n\t\t\t}\r\n\t\t\t&--hover {\r\n\t\t\t\tbackground-color: $uv-upload-hover-bgColor;\r\n\t\t\t}\r\n\t\t\t&--disabled {\r\n\t\t\t\topacity: $uv-upload-disabled-opacity;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</style>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-upload/package.json",
    "content": "{\r\n  \"id\": \"uv-upload\",\r\n  \"displayName\": \"uv-upload 上传  全面兼容小程序、nvue、vue2、vue3等多端\",\r\n  \"version\": \"1.0.5\",\r\n  \"description\": \"该组件用于上传图片等文件场景。\",\r\n  \"keywords\": [\r\n    \"uv-upload\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"upload\",\r\n    \"上传\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-icon\",\r\n\t\t\t\"uv-loading-icon\",\r\n\t\t\t\"uv-popup\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-upload/readme.md",
    "content": "## Upload 上传\r\n\r\n> **组件名：uv-upload**\r\n\r\n该组件用于上传图片等文件场景。\r\n\r\n### <a href=\"https://www.uvui.cn/components/upload.html\" target=\"_blank\">查看文档</a>\r\n\r\n### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n#### 如使用过程中有任何问题，或者您对uv-ui有一些好的建议，欢迎加入 uv-ui 交流群：<a href=\"https://ext.dcloud.net.cn/plugin?id=12287\" target=\"_blank\">uv-ui</a>、<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-vtabs/changelog.md",
    "content": "## 1.0.5（2023-06-27）\n修复：非联动，内容过多的情况，滚动一段距离，再切换未滚动到顶部的BUG\n## 1.0.4（2023-06-13）\r\n1. 增加scrolltolower回调函数\r\n2. 优化\r\n## 1.0.3（2023-06-13）\r\n1. 优化\r\n## 1.0.2（2023-06-13）\r\n1. 增加hdHeight参数，避免顶部有内容计算联动不准确的BUG\r\n2. 优化滑动触发频率，避免跳动\r\n## 1.0.1（2023-06-04）\r\n1. 文档说明\r\n## 1.0.0（2023-06-04）\r\n1. 新增垂直选项卡组件\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-vtabs/components/uv-vtabs/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 列表数据\r\n\t\tlist: {\r\n\t\t\ttype: Array,\r\n\t\t\tdefault: ()=>[]\r\n\t\t},\r\n\t\t// 从list元素对象中读取的键名，默认name\r\n\t\tkeyName: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'name'\r\n\t\t},\r\n\t\t// 当前选中项\r\n\t\tcurrent: {\r\n\t\t\ttype: [Number, String],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 头部内容的高度\r\n\t\thdHeight: {\r\n\t\t\ttype: [Number, String],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 是否联动，默认开启联动\r\n\t\tchain: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 整个列表的高度，默认auto屏幕高度\r\n\t\theight: {\r\n\t\t\ttype: [Number, String],\r\n\t\t\tdefault: 'auto'\r\n\t\t},\r\n\t\t// 左边列表的宽度，默认200rpx\r\n\t\tbarWidth: {\r\n\t\t\ttype: [Number, String],\r\n\t\t\tdefault: '180rpx'\r\n\t\t},\r\n\t\t// 左边列表是否允许滚动\r\n\t\tbarScrollable: {\r\n\t\t\ttype: Boolean,\r\n\t\t\tdefault: true\r\n\t\t},\r\n\t\t// 背景颜色 默认主题颜色 $bg-color\r\n\t\tbarBgColor: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 左边列表的自定义样式\r\n\t\tbarStyle: {\r\n\t\t\ttype: Object,\r\n\t\t\tdefault: ()=>{}\r\n\t\t},\r\n\t\t// 左边列表项的自定义样式\r\n\t\tbarItemStyle: {\r\n\t\t\ttype: Object,\r\n\t\t\tdefault: ()=>{}\r\n\t\t},\r\n\t\t// 左边选择项激活时的自定义样式\r\n\t\tbarItemActiveStyle: {\r\n\t\t\ttype: Object,\r\n\t\t\tdefault: ()=>{}\r\n\t\t},\r\n\t\t// 左边选择项激活时的左边线条自定义样式\r\n\t\tbarItemActiveLineStyle: {\r\n\t\t\ttype: Object,\r\n\t\t\tdefault: ()=>{}\r\n\t\t},\r\n\t\t// 菜单项中的徽标自定义样式，比如定位位置\r\n\t\tbarItemBadgeStyle: {\r\n\t\t\ttype: Object,\r\n\t\t\tdefault: ()=>{}\r\n\t\t},\r\n\t\t// 右边区域自定义样式\r\n\t\tcontentStyle: {\r\n\t\t\ttype: Object,\r\n\t\t\tdefault: ()=>{}\r\n\t\t}\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-vtabs/components/uv-vtabs/uv-vtabs.vue",
    "content": "<template>\r\n\t<view\r\n\t\tclass=\"uv-vtabs\"\r\n\t\t:style=\"[vtabsStyle]\"\r\n\t>\r\n\t\t<scroll-view\r\n\t\t\tclass=\"uv-vtabs__bar\"\r\n\t\t\tref=\"uv-vtabs__bar\"\r\n\t\t\t:style=\"[getBarStyle]\"\r\n\t\t\t:scroll-y=\"barScrollable\" \r\n\t\t\t:scroll-x=\"scrollX\"\r\n\t\t\t:show-scrollbar=\"false\"\r\n\t\t\t:scroll-with-animation=\"true\"\r\n\t\t\t:scroll-top=\"barScrollTop\"\r\n\t\t\t:scroll-into-view=\"barScrollToView\"\r\n\t\t>\r\n\t\t\t<view\r\n\t\t\t\t:class=\"[\r\n\t\t\t\t\t'uv-vtabs__bar-item',\r\n\t\t\t\t\t`uv-vtabs__bar-item--${index}`,\r\n\t\t\t\t\tindex == activeIndex && 'uv-vtabs__bar-item-active'\r\n\t\t\t\t]\"\r\n\t\t\t\t:ref=\"`uv-vtabs__bar-item--${index}`\"\r\n\t\t\t\tv-for=\"(item,index) in list\"\r\n\t\t\t\t:key=\"index\"\r\n\t\t\t\t:id=\"`bar_${index}`\"\r\n\t\t\t\t:style=\"[itemStyle(index)]\"\r\n\t\t\t\t@tap.stop=\"clickHandler(index)\"\r\n\t\t\t>\r\n\t\t\t\t<view \r\n\t\t\t\t\tclass=\"uv-vtabs__bar-item--line\"\r\n\t\t\t\t\tv-if=\"index == activeIndex\"\r\n\t\t\t\t\t:style=\"[$uv.addStyle(barItemActiveLineStyle)]\"\r\n\t\t\t\t></view>\r\n\t\t\t\t<text \r\n\t\t\t\t\t:class=\"[\r\n\t\t\t\t\t\t'uv-vtabs__bar-item--value',\r\n\t\t\t\t\t\tindex == activeIndex && 'uv-vtabs__bar-item-active--value'\r\n\t\t\t\t\t]\"\r\n\t\t\t\t\t:style=\"[itemStyle(index),textStyle(index)]\"\r\n\t\t\t\t>{{item[keyName]}}</text>\r\n\t\t\t\t<view \r\n\t\t\t\t\tclass=\"uv-vtabs__bar-item--badge\"\r\n\t\t\t\t\t:style=\"[$uv.addStyle(barItemBadgeStyle)]\"\r\n\t\t\t\t\tv-if=\"!!(item.badge && (item.badge.show || item.badge.isDot || item.badge.value))\"\r\n\t\t\t\t>\r\n\t\t\t\t\t<uv-badge\r\n\t\t\t\t\t\t:show=\"!!(item.badge && (item.badge.show || item.badge.isDot || item.badge.value))\"\r\n\t\t\t\t\t\t:isDot=\"item.badge && item.badge.isDot || propsBadge.isDot\"\r\n\t\t\t\t\t\t:value=\"item.badge && item.badge.value || propsBadge.value\"\r\n\t\t\t\t\t\t:max=\"item.badge && item.badge.max || propsBadge.max\"\r\n\t\t\t\t\t\t:type=\"item.badge && item.badge.type || propsBadge.type\"\r\n\t\t\t\t\t\t:showZero=\"item.badge && item.badge.showZero || propsBadge.showZero\"\r\n\t\t\t\t\t\t:bgColor=\"item.badge && item.badge.bgColor || propsBadge.bgColor\"\r\n\t\t\t\t\t\t:color=\"item.badge && item.badge.color || propsBadge.color\"\r\n\t\t\t\t\t\t:shape=\"item.badge && item.badge.shape || propsBadge.shape\"\r\n\t\t\t\t\t\t:numberType=\"item.badge && item.badge.numberType || propsBadge.numberType\"\r\n\t\t\t\t\t\t:inverted=\"item.badge && item.badge.inverted || propsBadge.inverted\"\r\n\t\t\t\t\t></uv-badge>\r\n\t\t\t\t</view>\r\n\t\t\t</view>\r\n\t\t</scroll-view>\r\n\t\t<scroll-view\r\n\t\t\tclass=\"uv-vtabs__content\"\r\n\t\t\t:style=\"[getContentStyle,$uv.addStyle(contentStyle)]\"\r\n\t\t\t:scroll-y=\"true\" \r\n\t\t\t:scroll-x=\"scrollX\"\r\n\t\t\t:show-scrollbar=\"false\"\r\n\t\t\t:scroll-top=\"contentScrollTop\"\r\n\t\t\t:scroll-into-view=\"contentScrollTo\"\r\n\t\t\t:scroll-with-animation=\"true\"\r\n\t\t\t@scroll=\"scrollHandler\"\r\n\t\t\t@scrolltolower=\"scrolltolower\"\r\n\t\t\tv-if=\"chain\"\r\n\t\t>\r\n\t\t\t<slot />\r\n\t\t</scroll-view>\r\n\t\t<scroll-view \r\n\t\t\tv-else\r\n\t\t\tclass=\"uv-vtabs__content\"\r\n\t\t\t:style=\"[getContentStyle,$uv.addStyle(contentStyle)]\"\r\n\t\t\t:scroll-y=\"true\" \r\n\t\t\t:scroll-x=\"scrollX\"\r\n\t\t\t:show-scrollbar=\"false\"\r\n\t\t\t:scroll-top=\"contentScrollTop2\"\r\n\t\t\t@scrolltolower=\"scrolltolower\"\r\n\t\t>\r\n\t\t\t<slot />\r\n\t\t</scroll-view>\r\n\t</view>\r\n</template>\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport debounce from '@/uni_modules/uv-ui-tools/libs/function/debounce.js'\r\n\timport throttle from '@/uni_modules/uv-ui-tools/libs/function/throttle.js'\r\n\timport uvBadgeProps from '@/uni_modules/uv-badge/components/uv-badge/props.js'\r\n\timport props from './props.js';\r\n\t// #ifdef APP-NVUE\r\n\tconst dom = uni.requireNativePlugin('dom')\r\n\t// #endif\r\n\t/**\r\n\t * 垂直选项卡\r\n\t * @description 该组件兼容所有端，提供了分类展示和联动等功能\r\n\t * @tutorial https://www.uvui.cn/components/vtabs.html\r\n\t * @property {Array}\tlist  选项数组，元素为对象，如[{name:'uv-ui'}]（默认 [] ）\r\n\t * @property {String}  keyName  从list元素对象中读取的键名（默认 name ）\r\n\t * @property {Number}  current  当前选中项，从0开始（默认 0 ）\r\n\t * @property {Number | String}  hdHeight  头部内容的高度，头部有内容必传，否则会有联动误差（默认 0 ）\r\n\t * @property {Boolean}  chain  是否开启联动，开启后右边区域可以滑动查看内容（默认 true ）\r\n\t * @property {Number|String}  height  整个列表的高度，默认auto或空则为屏幕高度（默认 auto屏幕高度 ）\r\n\t * @property {Number|String}  barWidth  左边选项区域的宽度（默认 180rpx ）\r\n\t * @property {Boolean}  barScrollable  左边选项区域是否允许滚动 （默认 true ）\r\n\t * @property {String}  barBgColor  左边选项区域的背景颜色（默认$uv-bg-color）\r\n\t * @property {Object}  barStyle  左边选项区域的自定义样式 （默认{}）\r\n\t * @property {Object}  barItemStyle  左边选项区域每个选项的自定义样式 （默认{}）\r\n\t * @property {Object}  barItemActiveStyle  左边选项区域选中选项的自定义样式 （默认{}）\r\n\t * @property {Object}  barItemActiveLineStyle  左边选项区域选中选项竖线条的自定义样式 （默认{}）\r\n\t * @property {Object}  barItemBadgeStyle  左边选项区域选中选项徽标的自定义样式，主要用于设置位置 （默认{}）\r\n\t * @property {Object}  contentStyle  右边区域自定义样式 （默认{}）\r\n\t * @example <uv-vtabs :list=\"list\"><uv-vtabs-item>...</uv-vtabs-item></uv-vtabs>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-vtabs',\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tcreated() {\r\n\t\t\tthis.children = []\r\n\t\t},\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tactiveIndex: 0,\r\n\t\t\t\t// 微信小程序下，scroll-view的scroll-into-view属性无法对slot中的内容的id生效，只能通过设置scrollTop的形式去移动滚动条\r\n\t\t\t\tcontentScrollTop: 0,\r\n\t\t\t\tcontentScrollTop2: 0,//针对非联动\r\n\t\t\t\tcontentScrollTo: '',\r\n\t\t\t\tscrolling: false,\r\n\t\t\t\tbarScrolling: false,\r\n\t\t\t\ttouching: false,\r\n\t\t\t\thasHeight: 0,\r\n\t\t\t\tscrollViewHeight: 0,\r\n\t\t\t\tbarScrollTop: 0,\r\n\t\t\t\tbarScrollToView: ''\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\tscrollX(){\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\treturn true;\r\n\t\t\t\t// #endif\r\n\t\t\t\treturn false;\r\n\t\t\t},\r\n\t\t\tvtabsStyle() {\r\n\t\t\t\tconst style = {};\r\n\t\t\t\tstyle.height = this.getHeight();\r\n\t\t\t\treturn this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle));\r\n\t\t\t},\r\n\t\t\tgetBarStyle() {\r\n\t\t\t\tconst style = {};\r\n\t\t\t\tstyle.width = this.$uv.getPx(this.barWidth, true);\r\n\t\t\t\tstyle.background = this.barBgColor;\r\n\t\t\t\tstyle.height = this.getHeight();\r\n\t\t\t\treturn this.$uv.deepMerge(style, this.$uv.addStyle(this.barStyle));\r\n\t\t\t},\r\n\t\t\titemStyle(){\r\n\t\t\t\treturn index =>{\r\n\t\t\t\t\tconst style = {};\r\n\t\t\t\t\tlet barItemInitStyle = this.barItemStyle;\r\n\t\t\t\t\t// 避免在nvue模式下，切换时候上一个选中颜色不变\r\n\t\t\t\t\tif(this.barItemStyle && !this.barItemStyle?.background) {\r\n\t\t\t\t\t\tbarItemInitStyle.background = 'transparent';\r\n\t\t\t\t\t}\r\n\t\t\t\t\t// 是否激活的样式\r\n\t\t\t\t\tconst customeStyle = index === this.activeIndex ? this.$uv.addStyle(this.barItemActiveStyle) : this.$uv.addStyle(barItemInitStyle);\r\n\t\t\t\t\tif (this.list[index].disabled) {\r\n\t\t\t\t\t\tstyle.color = '#c8c9cc'\r\n\t\t\t\t\t}\r\n\t\t\t\t\treturn this.$uv.deepMerge(customeStyle, style);\r\n\t\t\t\t} \r\n\t\t\t},\r\n\t\t\t// nvue设置字体样式必须要text标签上进行\r\n\t\t\ttextStyle(){\r\n\t\t\t\treturn index=>{\r\n\t\t\t\t\tconst style = {};\r\n\t\t\t\t\tstyle.width = this.$uv.getPx(this.barWidth, true);\r\n\t\t\t\t\treturn style;\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tgetContentStyle() {\r\n\t\t\t\tconst style = {};\r\n\t\t\t\tstyle.height = this.getHeight();\r\n\t\t\t\treturn style;\r\n\t\t\t},\r\n\t\t\tpropsBadge() {\r\n\t\t\t\treturn uvBadgeProps\r\n\t\t\t}\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\tcurrent: {\r\n\t\t\t\tdeep: true,\r\n\t\t\t\timmediate: true,\r\n\t\t\t\thandler(newVal){\r\n\t\t\t\t\tthis.init(newVal?newVal:0);\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tlist(newVal) {\r\n\t\t\t\tif (newVal.length) {\r\n\t\t\t\t\tthis.$uv.sleep(30).then(res => {\r\n\t\t\t\t\t\tthis.resize();\r\n\t\t\t\t\t})\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tactiveIndex(newVal){\r\n\t\t\t\tif(!this.chain) {// 解决：非联动，内容过多的情况，滚动一段距离，再切换未滚动到顶部的BUG\r\n\t\t\t\t\tthis.contentScrollTop2 = 0 - Math.random() * 4 - 4;\r\n\t\t\t\t}\r\n\t\t\t\tthis.$emit('change',newVal);\r\n\t\t\t}\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tinit(index){\r\n\t\t\t\tlet num = 0;\r\n\t\t\t\tthis.timer2 && clearInterval(this.timer2);\r\n\t\t\t\tthis.timer2 = setInterval(async ()=>{\r\n\t\t\t\t\tnum++;\r\n\t\t\t\t\tif(num>50) clearInterval(this.timer2);\r\n\t\t\t\t\tif(this.children.length) {\r\n\t\t\t\t\t\tclearInterval(this.timer2);\r\n\t\t\t\t\t\tawait this.$uv.sleep(300);\r\n\t\t\t\t\t\tthis.clickHandler(index);\r\n\t\t\t\t\t}\r\n\t\t\t\t},100)\r\n\t\t\t},\r\n\t\t\t// 内容滚动到底部触发\r\n\t\t\tscrolltolower(){\r\n\t\t\t\tthis.$emit('scrolltolower',this.activeIndex);\r\n\t\t\t},\r\n\t\t\tasync resize() {\r\n\t\t\t\t// 如果list数组长度为0就不处理 || 选中目标未变则不处理\r\n\t\t\t\tif (this.list.length == 0 || !this.barScrollable) return;\r\n\t\t\t\t// 避免滑太快，修复位置\r\n\t\t\t\tPromise.all([this.getTabsRect(), this.getAllItemRect()]).then(([tabsRect, itemRect = []]) => {\r\n\t\t\t\t\tthis.tabsRect = tabsRect;\r\n\t\t\t\t\tthis.scrollViewHeight = 0\r\n\t\t\t\t\titemRect.map((item, index) => {\r\n\t\t\t\t\t\tthis.scrollViewHeight += item.height;\r\n\t\t\t\t\t\tthis.list[index].rect = item;\r\n\t\t\t\t\t})\r\n\t\t\t\t\tthis.setBarScrollTop();\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\t// 设置左边菜单滚动条的位置，目标：将当前的选项移到中间位置\r\n\t\t\tsetBarScrollTop() {\r\n\t\t\t\tconst tabRect = this.list[this.activeIndex];\r\n\t\t\t\tconst offsetTop = this.list\r\n\t\t\t\t\t.slice(0, this.activeIndex)\r\n\t\t\t\t\t.reduce((total, item) => {\r\n\t\t\t\t\t\treturn total + item.rect.height;\r\n\t\t\t\t\t}, 0);\r\n\t\t\t\tconst scrollViewHeight = this.$uv.getPx(this.getHeight());\r\n\t\t\t\tlet barScrollTop = tabRect.rect.height / 2 + offsetTop - scrollViewHeight / 2;\r\n\t\t\t\t// 先给一点随机值，避免出现不能滚动的BUG\r\n\t\t\t\tbarScrollTop = Math.min(barScrollTop, this.scrollViewHeight - this.tabsRect.height);\r\n\t\t\t\tthis.barScrollTop = Math.max(0, barScrollTop);\r\n\t\t\t\t// 已经不能滚动的时候，就使用scroll-into-view的方式进行定位，避免失效\r\n\t\t\t\tif(barScrollTop>=(this.scrollViewHeight - this.tabsRect.height)) {\r\n\t\t\t\t\tthis.timer && clearTimeout(this.timer);\r\n\t\t\t\t\tthis.timer = setTimeout(()=>{\r\n\t\t\t\t\t\tthis.barScrollToView = `bar_${this.activeIndex}`;\r\n\t\t\t\t\t},400)\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t// 左边菜单点击\r\n\t\t\tasync clickHandler(currentIndex) {\r\n\t\t\t\tif (currentIndex == this.activeIndex) return;\r\n\t\t\t\tthis.touching = true;\r\n\t\t\t\tthis.activeIndex = currentIndex;\r\n\t\t\t\tif(this.chain) {\r\n\t\t\t\t\t// 给一点随机值，避免出现不能滚动的BUG。微信端必须用此方法\r\n\t\t\t\t\tthis.contentScrollTop = this.children[currentIndex].top - this.$uv.getPx(this.hdHeight) - Math.random() * 4 - 4;\r\n\t\t\t\t\t// #ifndef MP-WEIXIN\r\n\t\t\t\t\tthis.contentScrollTo = `content_${currentIndex}`;\r\n\t\t\t\t\t// #endif\r\n\t\t\t\t}\r\n\t\t\t\tthis.timer && clearTimeout(this.timer);\r\n\t\t\t\tthrottle(()=>{\r\n\t\t\t\t\tthis.resize();\r\n\t\t\t\t},300,false)\r\n\t\t\t\tdebounce(() => {\r\n\t\t\t\t\tthis.touching = false;\r\n\t\t\t\t}, 900)\r\n\t\t\t},\r\n\t\t\t// 内容滚动\r\n\t\t\tscrollHandler(e) {\r\n\t\t\t\tif (this.touching || this.scrolling) return;\r\n\t\t\t\t// 每过一定时间取样一次，减少资源损耗以及可能带来的卡顿\r\n\t\t\t\tthis.scrolling = true;\r\n\t\t\t\tthis.$uv.sleep(80).then(() => {\r\n\t\t\t\t\tthis.scrolling = false;\r\n\t\t\t\t})\r\n\t\t\t\tconst scrollTop = e.detail.scrollTop;\r\n\t\t\t\tlet children = this.children;\r\n\t\t\t\tconst len = children.length;\r\n\t\t\t\tlet top = 0;\r\n\t\t\t\tlet activeIndex = 0;\r\n\t\t\t\tchildren = this.children.map((item, index) => {\r\n\t\t\t\t\tif (item.height > 0) this.hasHeight = item.height;\r\n\t\t\t\t\titem.height = item.height > 0 ? item.height : this.hasHeight;\r\n\t\t\t\t\tconst child = {\r\n\t\t\t\t\t\theight: item.height,\r\n\t\t\t\t\t\ttop\r\n\t\t\t\t\t}\r\n\t\t\t\t\t// 进行累加，给下一个item提供计算依据\r\n\t\t\t\t\ttop += item.height;\r\n\t\t\t\t\treturn child;\r\n\t\t\t\t})\r\n\t\t\t\tfor (let i = 0; i < len; i++) {\r\n\t\t\t\t\tconst item = children[i];\r\n\t\t\t\t\tconst nextItem = children[i + 1];\r\n\t\t\t\t\t// 如果滚动条高度小于第一个item的top值，此时无需设置任意字母为高亮\r\n\t\t\t\t\tif (scrollTop <= children[0].top) {\r\n\t\t\t\t\t\tactiveIndex = 0;\r\n\t\t\t\t\t\tbreak\r\n\t\t\t\t\t} else if (!nextItem) {\r\n\t\t\t\t\t\t// 当不存在下一个item时，意味着历遍到了最后一个\r\n\t\t\t\t\t\tactiveIndex = len - 1;\r\n\t\t\t\t\t\tbreak\r\n\t\t\t\t\t} else if (scrollTop > item.top && scrollTop < nextItem.top) {\r\n\t\t\t\t\t\tactiveIndex = i;\r\n\t\t\t\t\t\tbreak\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tthis.activeIndex = activeIndex;\r\n\t\t\t\t// 当前选中项索引必然来源于前后两个索引，满足才执行，避免闪烁的bug\r\n\t\t\t\t\tthis.timer4 && clearTimeout(this.timer4);\r\n\t\t\t\tthis.timer4 = setTimeout(()=>{\r\n\t\t\t\t\tthis.resize();\r\n\t\t\t\t},100)\r\n\t\t\t},\r\n\t\t\t// 设置高度\r\n\t\t\tgetHeight() {\r\n\t\t\t\tlet height = 0;\r\n\t\t\t\tconst isEmpty = this.$uv.test.empty(this.height);\r\n\t\t\t\tif (isEmpty || this.height=='auto') height = this.$uv.addUnit(this.$uv.sys().windowHeight);\r\n\t\t\t\telse height = this.$uv.getPx(this.height, true);\r\n\t\t\t\treturn height;\r\n\t\t\t},\r\n\t\t\t// 获取导航菜单的尺寸\r\n\t\t\tgetTabsRect() {\r\n\t\t\t\treturn new Promise(resolve => {\r\n\t\t\t\t\tthis.queryRect('uv-vtabs__bar').then(size => resolve(size))\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\t// 获取所有标签的尺寸\r\n\t\t\tgetAllItemRect() {\r\n\t\t\t\treturn new Promise(resolve => {\r\n\t\t\t\t\tconst promiseAllArr = this.list.map((item, index) => this.queryRect(\r\n\t\t\t\t\t\t`uv-vtabs__bar-item--${index}`, true))\r\n\t\t\t\t\tPromise.all(promiseAllArr).then(sizes => resolve(sizes))\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\t// 获取各个标签的尺寸\r\n\t\t\tqueryRect(el, item) {\r\n\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\t// $uvGetRect为uv-ui自带的节点查询简化方法，详见文档介绍：https://www.uvui.cn/js/getRect.html\r\n\t\t\t\t// 组件内部一般用this.$uvGetRect，对外的为getRect，二者功能一致，名称不同\r\n\t\t\t\treturn new Promise(resolve => {\r\n\t\t\t\t\tthis.$uvGetRect(`.${el}`).then(size => {\r\n\t\t\t\t\t\tresolve(size)\r\n\t\t\t\t\t})\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\t// nvue下，使用dom模块查询元素高度\r\n\t\t\t\t// 返回一个promise，让调用此方法的主体能使用then回调\r\n\t\t\t\treturn new Promise(resolve => {\r\n\t\t\t\t\tdom.getComponentRect(item ? this.$refs[el][0] : this.$refs[el], res => {\r\n\t\t\t\t\t\tresolve(res.size)\r\n\t\t\t\t\t})\r\n\t\t\t\t})\r\n\t\t\t\t// #endif\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n<style scoped lang=\"scss\">\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';\r\n\t.uv-vtabs {\r\n\t\t@include flex;\r\n\t\t&__bar {\r\n\t\t\tbackground: $uv-bg-color;\r\n\t\t\t&-item {\r\n\t\t\t\tposition: relative;\r\n\t\t\t\t@include flex;\r\n\t\t\t\talign-items: center;\r\n\t\t\t\tjustify-content: center;\r\n\t\t\t\tpadding: 35rpx 12rpx 35rpx 20rpx;\r\n\t\t\t\t&--value {\r\n\t\t\t\t\t/* #ifdef APP-NVUE */\r\n\t\t\t\t\tpadding: 0 12rpx;\r\n\t\t\t\t\t/* #endif */\r\n\t\t\t\t\tfont-size: 14px;\r\n\t\t\t\t\tcolor: $uv-content-color;\r\n\t\t\t\t}\r\n\t\t\t\t&-active {\r\n\t\t\t\t\tbackground: #fff;\r\n\t\t\t\t\t&--value {\r\n\t\t\t\t\t\tcolor: $uv-primary;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t&--line {\r\n\t\t\t\t\tposition: absolute;\r\n\t\t\t\t\twidth: 2px;\r\n\t\t\t\t\tleft: 0;\r\n\t\t\t\t\ttop: 0;\r\n\t\t\t\t\tbottom: 0;\r\n\t\t\t\t\tz-index: 1;\r\n\t\t\t\t\tbackground-color: $uv-primary;\r\n\t\t\t\t}\r\n\t\t\t\t&--badge {\r\n\t\t\t\t\tposition: absolute;\r\n\t\t\t\t\ttop: 4px;\r\n\t\t\t\t\tright: 10px;\r\n\t\t\t\t\tz-index: 1;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t&__content {\r\n\t\t\tflex: 1;\r\n\t\t\tbackground: #fff;\r\n\t\t}\r\n\t}\r\n</style>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-vtabs/components/uv-vtabs-item/uv-vtabs-item.vue",
    "content": "<template>\r\n\t\t<view \r\n\t\t\tclass=\"uv-vtabs-item\"\r\n\t\t\t:id=\"`content_${index}`\"\r\n\t\t\tref=\"uv-vtabs-item\"\r\n\t\t>\r\n\t\t\t<slot />\r\n\t\t</view>\r\n</template>\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js';\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js';\r\n\t// #ifdef APP-NVUE\r\n\tconst dom = uni.requireNativePlugin('dom');\r\n\t// #endif\r\n\texport default {\r\n\t\tname: 'uv-vtabs-item',\r\n\t\tmixins: [mpMixin, mixin],\r\n\t\tprops: {\r\n\t\t\tindex: {\r\n\t\t\t\ttype: [Number,String],\r\n\t\t\t\tdefault: 0\r\n\t\t\t}\r\n\t\t},\r\n\t\tdata(){\r\n\t\t\treturn {\r\n\t\t\t\t// 记录item的离顶部的距离\r\n\t\t\t\ttop: 0,\r\n\t\t\t\t// 记录item的高度\r\n\t\t\t\theight: 0\r\n\t\t\t\t// 是否为联动\r\n\t\t\t}\r\n\t\t},\r\n\t\tmounted() {\r\n\t\t\tthis.init();\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\tasync init(){\r\n\t\t\t\tthis.getParentData('uv-vtabs');\r\n\t\t\t\tif (!this.parent) {\r\n\t\t\t\t\treturn this.$uv.error('uv-vtabs必须要搭配uv-vtabs-item组件使用')\r\n\t\t\t\t}\r\n\t\t\t\tif(!this.parent.chain) return;\r\n\t\t\t\tawait this.$uv.sleep();\r\n\t\t\t\tthis.getItemRect().then(size=>{\r\n\t\t\t\t\t// 由于对象的引用特性，此处会同时生效到父组件的children数组的本实例的top属性中，供父组件判断读取\r\n\t\t\t\t\tthis.top = size.top;\r\n\t\t\t\t\tthis.height = size.height;\r\n\t\t\t\t});\r\n\t\t\t},\r\n\t\t\tgetItemRect(){\r\n\t\t\t\treturn new Promise(resolve => {\r\n\t\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\t\tthis.$uvGetRect('.uv-vtabs-item').then(size => {\r\n\t\t\t\t\t\tresolve(size)\r\n\t\t\t\t\t})\r\n\t\t\t\t\t// #endif\r\n\t\t\t\t\t// #ifdef APP-NVUE\r\n\t\t\t\t\tconst ref = this.$refs['uv-vtabs-item']\r\n\t\t\t\t\tdom.getComponentRect(ref, res => {\r\n\t\t\t\t\t\tresolve(res.size)\r\n\t\t\t\t\t})\r\n\t\t\t\t\t// #endif\r\n\t\t\t\t})\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-vtabs/package.json",
    "content": "{\r\n  \"id\": \"uv-vtabs\",\r\n  \"displayName\": \"uv-vtabs 垂直选项卡  商品分类  灵活配置  多端兼容开箱即用\",\r\n  \"version\": \"1.0.5\",\r\n  \"description\": \"uv-vtabs 垂直分类组件主要用于分类选择，简单配置即可使用，左右自动进行联动，不用自己再去做复杂的计算，组件内部已经完成相关计算。支持联动和不联动，vue3和vue2多端兼容，开箱即用。\",\r\n  \"keywords\": [\r\n    \"uv-vtabs\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"垂直分类\",\r\n    \"垂直选项卡\"\r\n],\r\n  \"repository\": \"\",\r\n\"engines\": {\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-badge\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-vtabs/readme.md",
    "content": "## Vtabs 垂直选项卡\r\n\r\n> **组件名：uv-vtabs**\r\n\r\n该组件主要用于分类选择，开箱即用，简单配置参数即可使用，左右自动进行联动，不用自己再去做复杂的计算，组件内部已经完成相关计算。联动和不联动两种可选方式，联动-左右均可滚动，不联动-右边区域只会在选中时显示。\r\n\r\n\r\n### <a href=\"https://www.uvui.cn/components/vtabs.html\" target=\"_blank\">查看文档</a>\r\n\r\n### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)\r\n\r\n#### 如使用过程中有任何问题，或者您对uv-ui有一些好的建议，欢迎加入 uv-ui 交流群：<a href=\"https://ext.dcloud.net.cn/plugin?id=12287\" target=\"_blank\">uv-ui</a>、<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-waterfall/changelog.md",
    "content": "## 1.0.8（2023-08-17）\n1. 修复只有一条数据切换时可能存在位置错误的BUG\n## 1.0.7（2023-07-22）\n1. 避免快速切换报错的BUG\n## 1.0.6（2023-07-17）\n1. 优化文档\n2. 优化其他\n## 1.0.5（2023-07-14）\r\n1. 优化changeList未处理数据时，正确返回对应列的数据，避免误导\r\n## 1.0.4（2023-05-27）\r\n1. 修复在百度小程序中可能存在的BUG\r\n2. 去掉原有的slot方式\r\n## 1.0.3（2023-05-23）\r\n1. 修复在百度/头条小程序显示异常等BUG\r\n2. 增加changeList回调函数处理数据\r\n3. 更新示例\r\n## 1.0.2（2023-05-16）\r\n1. 优化组件依赖，修改后无需全局引入，组件导入即可使用\r\n2. 优化部分功能\r\n## 1.0.1（2023-05-12）\r\n1. 增加clear回调函数\r\n2. 增加remove回调函数\r\n## 1.0.0（2023-05-10）\r\nuv-waterfall 瀑布流\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-waterfall/components/uv-waterfall/props.js",
    "content": "export default {\r\n\tprops: {\r\n\t\t// 瀑布流数据\r\n\t\t// #ifdef VUE2\r\n\t\tvalue: {\r\n\t\t\ttype: Array,\r\n\t\t\tdefault: () => []\r\n\t\t},\r\n\t\t// #endif\r\n\t\t// #ifdef VUE3\r\n\t\tmodelValue: {\r\n\t\t\ttype: Array,\r\n\t\t\tdefault: () => []\r\n\t\t},\r\n\t\t// #endif\r\n\t\t// 数据的id值，根据id值对数据执行删除操作\r\n\t\t// 如数据为：{id: 1, name: 'uv-ui'}，那么该值设置为id\r\n\t\tidKey: {\r\n\t\t\ttype: String,\r\n\t\t\tdefault: 'id'\r\n\t\t},\r\n\t\t// 每次插入数据的事件间隔，间隔越长能保证两列高度相近，但是用户体验不好，单位ms\r\n\t\taddTime: {\r\n\t\t\ttype: Number,\r\n\t\t\tdefault: 200\r\n\t\t},\r\n\t\t// 瀑布流的列数，默认2，最高为5\r\n\t\tcolumnCount: {\r\n\t\t\ttype: [Number, String],\r\n\t\t\tdefault: 2\r\n\t\t},\r\n\t\t// 列与列的间隙，默认20\r\n\t\tcolumnGap: {\r\n\t\t\ttype: [Number, String],\r\n\t\t\tdefault: 20\r\n\t\t},\r\n\t\t// 左边和列表的间隙\r\n\t\tleftGap: {\r\n\t\t\ttype: [Number, String],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 右边和列表的间隙\r\n\t\trightGap: {\r\n\t\t\ttype: [Number, String],\r\n\t\t\tdefault: 0\r\n\t\t},\r\n\t\t// 是否显示滚动条，仅nvue生效\r\n\t\tshowScrollbar: {\r\n\t\t\ttype: [Boolean],\r\n\t\t\tdefault: false\r\n\t\t},\r\n\t\t// 列宽，nvue生效\r\n\t\tcolumnWidth: {\r\n\t\t\ttype: [Number, String],\r\n\t\t\tdefault: 'auto'\r\n\t\t},\r\n\t\t// 瀑布流的宽度，nvue生效\r\n\t\twidth: {\r\n\t\t\ttype: [Number, String],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t// 瀑布流的高度，nvue生效\r\n\t\theight: {\r\n\t\t\ttype: [Number, String],\r\n\t\t\tdefault: ''\r\n\t\t},\r\n\t\t...uni.$uv?.props?.waterfall\r\n\t}\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-waterfall/components/uv-waterfall/uv-waterfall.vue",
    "content": "<template>\r\n\t<view class=\"uv-waterfall\">\r\n\t\t<!-- #ifndef APP-NVUE -->\r\n\t\t<view class=\"uv-waterfall__gap_left\" :style=\"[gapLeftStyle]\"></view>\r\n\t\t<template v-if=\"columnNum>=1\">\r\n\t\t\t<view id=\"uv-waterfall-1\" class=\"uv-waterfall__column\">\r\n\t\t\t\t<slot name=\"list1\"></slot>\r\n\t\t\t</view>\r\n\t\t</template>\r\n\t\t<template v-if=\"columnNum>=2\">\r\n\t\t\t<view class=\"uv-waterfall__gap_center\" :style=\"[gapCenterStyle]\"></view>\r\n\t\t\t<view id=\"uv-waterfall-2\" class=\"uv-waterfall__column\">\r\n\t\t\t\t<slot name=\"list2\"></slot>\r\n\t\t\t</view>\r\n\t\t</template>\r\n\t\t<template v-if=\"columnNum>=3\">\r\n\t\t\t<view class=\"uv-waterfall__gap_center\" :style=\"[gapCenterStyle]\"></view>\r\n\t\t\t<view id=\"uv-waterfall-3\" class=\"uv-waterfall__column\">\r\n\t\t\t\t<slot name=\"list3\"></slot>\r\n\t\t\t</view>\r\n\t\t</template>\r\n\t\t<template v-if=\"columnNum>=4\">\r\n\t\t\t<view class=\"uv-waterfall__gap_center\" :style=\"[gapCenterStyle]\">\r\n\t\t\t</view>\r\n\t\t\t<view id=\"uv-waterfall-4\" class=\"uv-waterfall__column\">\r\n\t\t\t\t<slot name=\"list4\"></slot>\r\n\t\t\t</view>\r\n\t\t</template>\r\n\t\t<template v-if=\"columnNum>=5\">\r\n\t\t\t<view class=\"uv-waterfall__gap_center\" :style=\"[gapCenterStyle]\">\r\n\t\t\t</view>\r\n\t\t\t<view id=\"uv-waterfall-5\" class=\"uv-waterfall__column\">\r\n\t\t\t\t<slot name=\"list5\"></slot>\r\n\t\t\t</view>\r\n\t\t</template>\r\n\t\t<view class=\"uv-waterfall__gap_right\" :style=\"[gapRightStyle]\">\r\n\t\t</view>\r\n\t\t<!-- #endif -->\r\n\t\t<!-- #ifdef APP-NVUE -->\r\n\t\t<view class=\"waterfall-warapper\">\r\n\t\t\t<waterfall :column-count=\"columnNum\" :show-scrollbar=\"false\" column-width=\"auto\" :column-gap=\"columnGap\" :left-gap=\"leftGap\" :right-gap=\"rightGap\" :always-scrollable-vertical=\"true\" :style=\"[nvueWaterfallStyle]\"\r\n\t\t\t\t@loadmore=\"scrolltolower\">\r\n\t\t\t\t<slot></slot>\r\n\t\t\t</waterfall>\r\n\t\t</view>\r\n\t\t<!-- #endif -->\r\n\t</view>\r\n</template>\r\n<script>\r\n\timport mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'\r\n\timport mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'\r\n\timport props from './props.js';\r\n\t/**\r\n\t * 瀑布流\r\n\t * @description 该组件兼容所有端，nvue参考https://uniapp.dcloud.net.cn/component/waterfall.html\r\n\t * @tutorial https://www.uvui.cn/components/list.html\r\n\t * @property {Array}\tvalue/modelValue\t瀑布流数组数据，非nvue生效 （默认 [] ）\r\n\t * @property {String}\tidKey\t  数据的id值，根据id值对数据执行删除操作，如数据为：{id: 1, name: 'uv-ui'}，那么该值设置为id，非nvue有效 （默认 '' ）\r\n\t * @property {String ｜ Number}\taddTime\t\t每次插入数据的事件间隔，间隔越长能保证两列高度相近，但是用户体验不好，单位ms，非nvue生效（默认 200 ）\r\n\t * @property {String ｜ Number}\tcolumnCount\t\t瀑布流的列数（默认 2 ）\r\n\t * @property {String ｜ Number}\t\t\tcolumnGap\t\t列与列的间隙（默认 0 ）\r\n\t * @property {String ｜ Number}\t\t\tleftGap\t\t左边和列表的间隙（默认 0 ）\r\n\t * @property {String ｜ Number}\t\t\trightGap\t右边和列表的间隙（默认 0 ）\r\n\t * @property {Boolean}\tshowScrollbar\t\t控制是否出现滚动条，仅nvue有效 （默认 false ）\r\n\t * @property {String ｜ Number}\t\tcolumnWidth\t\t描述瀑布流每一列的列宽，nvue生效 （默认 auto）\r\n\t * @property {String ｜ Number}\t  width\t  瀑布流的宽度，nvue生效 （默认 屏幕宽 ）\r\n\t * @property {String ｜ Number}\t\theight\t 瀑布流的高度，nvue生效 （默认 屏幕高 ）\r\n\t * @property {Object}\tcustomStyle\t\t定义需要用到的外部样式\r\n\t *\r\n\t * @example <uv-waterfall v-model=\"list\"></uv-waterfall>\r\n\t */\r\n\texport default {\r\n\t\tname: 'uv-waterfall',\r\n\t\tmixins: [mpMixin, mixin, props],\r\n\t\tdata() {\r\n\t\t\treturn {\r\n\t\t\t\tlist1: [],\r\n\t\t\t\tlist2: [],\r\n\t\t\t\tlist3: [],\r\n\t\t\t\tlist4: [],\r\n\t\t\t\tlist5: [],\r\n\t\t\t\t// 临时列表\r\n\t\t\t\ttempList: []\r\n\t\t\t}\r\n\t\t},\r\n\t\tcomputed: {\r\n\t\t\t// 破坏value变量引用，否则数据会保持不变\r\n\t\t\tcopyValue() {\r\n\t\t\t\t// #ifdef VUE2\r\n\t\t\t\treturn this.$uv.deepClone(this.value)\r\n\t\t\t\t// #endif\r\n\t\t\t\t// #ifdef VUE3\r\n\t\t\t\treturn this.$uv.deepClone(this.modelValue)\r\n\t\t\t\t// #endif\r\n\t\t\t},\r\n\t\t\tcolumnNum() {\r\n\t\t\t\treturn this.columnCount <= 0 ? 0 : this.columnCount >= 5 ? 5 : this.columnCount;\r\n\t\t\t},\r\n\t\t\tgapLeftStyle() {\r\n\t\t\t\tconst style = {}\r\n\t\t\t\tstyle.width = this.$uv.addUnit(this.leftGap)\r\n\t\t\t\treturn style;\r\n\t\t\t},\r\n\t\t\tgapRightStyle() {\r\n\t\t\t\tconst style = {}\r\n\t\t\t\tstyle.width = this.$uv.addUnit(this.rightGap)\r\n\t\t\t\treturn style;\r\n\t\t\t},\r\n\t\t\tgapCenterStyle() {\r\n\t\t\t\tconst style = {}\r\n\t\t\t\tstyle.width = this.$uv.addUnit(this.columnGap)\r\n\t\t\t\treturn style;\r\n\t\t\t},\r\n\t\t\tnvueWaterfallStyle() {\r\n\t\t\t\tconst style = {};\r\n\t\t\t\tif (this.width != 0) style.width = this.$uv.addUnit(this.width)\r\n\t\t\t\tif (this.height != 0) style.height = this.$uv.addUnit(this.height)\r\n\t\t\t\t// 如果没有定义列表高度，则默认使用屏幕高度\r\n\t\t\t\tif (!style.width) style.width = this.$uv.addUnit(this.$uv.sys().windowWidth, 'px')\r\n\t\t\t\tif (!style.height) style.height = this.$uv.addUnit(this.$uv.sys().windowHeight, 'px')\r\n\t\t\t\treturn this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))\r\n\t\t\t}\r\n\t\t},\r\n\t\twatch: {\r\n\t\t\tcopyValue(nVal, oVal) {\r\n\t\t\t\t// #ifndef APP-NVUE\r\n\t\t\t\tif (nVal.length != 0) {\r\n\t\t\t\t\t// 取出数组发生变化的部分\r\n\t\t\t\t\tlet startIndex = Array.isArray(oVal) && oVal.length > 0 ? oVal.length : 0\r\n\t\t\t\t\t// 拼接原有数据\r\n\t\t\t\t\tthis.tempList = this.tempList.concat(this.$uv.deepClone(nVal.slice(startIndex)))\r\n\t\t\t\t\tthis.splitData()\r\n\t\t\t\t}\r\n\t\t\t\t// #endif\r\n\t\t\t}\r\n\t\t},\r\n\t\tmounted() {\r\n\t\t\t// #ifndef APP-NVUE\r\n\t\t\tthis.tempList = this.$uv.deepClone(this.copyValue)\r\n\t\t\tthis.splitData()\r\n\t\t\t// #endif\r\n\t\t},\r\n\t\tmethods: {\r\n\t\t\t// 滚动到底部触发事件\r\n\t\t\tscrolltolower(e) {\r\n\t\t\t\tthis.$uv.sleep(30).then(() => {\r\n\t\t\t\t\tthis.$emit('scrolltolower')\r\n\t\t\t\t})\r\n\t\t\t},\r\n\t\t\t// 拆分数据\r\n\t\t\tasync splitData() {\r\n\t\t\t\tlet rectArr = [];\r\n\t\t\t\tlet emitList = {};\r\n\t\t\t\tif (!this.tempList.length) return\r\n\t\t\t\tfor (let i = 1; i <= this.columnNum; i++) {\r\n\t\t\t\t\tconst rect = await this.$uvGetRect(`#uv-waterfall-${i}`);\r\n\t\t\t\t\trectArr.push({ ...rect, name: i });\r\n\t\t\t\t}\r\n\t\t\t\tlet item = this.tempList[0]\r\n\t\t\t\t// 因为经过上面两个await节点查询和定时器，数组有可能会变成空[]，导致item的值为undefined\r\n\t\t\t\t// 解决多次快速滚动会导致数据乱的问题\r\n\t\t\t\tif (!item) return\r\n\t\t\t\tconst minCol = this.getMin(rectArr);\r\n\t\t\t\t// 列宽可能使用的到\r\n\t\t\t\titem.width = minCol.width;\r\n\t\t\t\tthis[`list${minCol.name}`].push(item);\r\n\t\t\t\temitList.name = `list${minCol.name}`;\r\n\t\t\t\temitList.value = item;\r\n\t\t\t\tthis.$emit('changeList', emitList);\r\n\t\t\t\t// 移除临时数组中已处理的数据\r\n\t\t\t\tthis.tempList.splice(0, 1)\r\n\t\t\t\t// 如果还有数据则继续执行\r\n\t\t\t\tif (this.tempList.length) {\r\n\t\t\t\t\tlet _timeout = this.addTime;\r\n\t\t\t\t\t// 部分平台在延时较短的情况会出现BUG\r\n\t\t\t\t\t// #ifdef MP-BAIDU\r\n\t\t\t\t\t_timeout = _timeout < 200 ? 200 : _timeout;\r\n\t\t\t\t\t// #endif\r\n\t\t\t\t\tawait this.$uv.sleep(_timeout);\r\n\t\t\t\t\tthis.splitData()\r\n\t\t\t\t} else {\r\n\t\t\t\t\tthis.$emit('finish')\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tgetMin(arr) {\r\n\t\t\t\tlet result = null;\r\n\t\t\t\tconst filter = arr.filter(item => item.height == 0);\r\n\t\t\t\tif (!filter.length) {\r\n\t\t\t\t\tconst min = Math.min.apply(Math, arr.map(item => {\r\n\t\t\t\t\t\treturn item.height;\r\n\t\t\t\t\t}))\r\n\t\t\t\t\tconst [item] = arr.filter(item => item.height == min);\r\n\t\t\t\t\tresult = item;\r\n\t\t\t\t} else {\r\n\t\t\t\t\tlet newArr = [];\r\n\t\t\t\t\tarr.map((item, index) => {\r\n\t\t\t\t\t\tnewArr.push({ len: this[`list${index+1}`].length, item: item });\r\n\t\t\t\t\t});\r\n\t\t\t\t\tconst minLen = Math.min.apply(Math, newArr.map(item => {\r\n\t\t\t\t\t\treturn item.len;\r\n\t\t\t\t\t}))\r\n\t\t\t\t\ttry {\r\n\t\t\t\t\t\tconst { item } = newArr.find(item => item.len == minLen && item.item.height == 0);\r\n\t\t\t\t\t\tresult = item;\r\n\t\t\t\t\t} catch (e) {\r\n\t\t\t\t\t\tconst { item } = newArr.find(item => item.item.height == 0);\r\n\t\t\t\t\t\tresult = item;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\treturn result;\r\n\t\t\t},\r\n\t\t\t// 清空数据列表\r\n\t\t\tasync clear() {\r\n\t\t\t\t// 清除数据\r\n\t\t\t\tfor (let i = 0; i < this.columnCount; i++) {\r\n\t\t\t\t\tthis[`list${i+1}`] = [];\r\n\t\t\t\t}\r\n\t\t\t\t// #ifdef VUE2\r\n\t\t\t\tthis.$emit('input', [])\r\n\t\t\t\t// #endif\r\n\t\t\t\t// #ifdef VUE3\r\n\t\t\t\tthis.$emit('update:modelValue', [])\r\n\t\t\t\t// #endif\r\n\t\t\t\tthis.tempList = []\r\n\t\t\t\tawait this.$uv.sleep(300);\r\n\t\t\t\tthis.$emit('clear');\r\n\t\t\t},\r\n\t\t\t// 清除指定的某一条数据，根据id来实现\r\n\t\t\tremove(id) {\r\n\t\t\t\tlet index = -1\r\n\t\t\t\t// 删除组件数据\r\n\t\t\t\tfor (let i = 1; i <= this.columnCount; i++) {\r\n\t\t\t\t\tindex = this[`list${i}`].findIndex(item => item[this.idKey] == id)\r\n\t\t\t\t\tif (index != -1) {\r\n\t\t\t\t\t\tthis[`list${i}`].splice(index, 1)\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t// 同时删除父组件对应的数据\r\n\t\t\t\t// #ifdef VUE2\r\n\t\t\t\tindex = this.value.findIndex(item => item[this.idKey] == id)\r\n\t\t\t\tif (index != -1) this.$emit('input', this.value.splice(index, 1))\r\n\t\t\t\t// #endif\r\n\t\t\t\t// #ifdef VUE3\r\n\t\t\t\tindex = this.modelValue.findIndex(item => item[this.idKey] == id)\r\n\t\t\t\tif (index != -1) this.$emit('update:modelValue', this.modelValue.splice(index, 1))\r\n\t\t\t\t// #endif\r\n\t\t\t\tthis.$emit('remove', id);\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n</script>\r\n<style lang=\"scss\" scoped>\r\n\t@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';\r\n\t.uv-waterfall {\r\n\t\t@include flex(row);\r\n\t\talign-items: flex-start;\r\n\t\t&__column {\r\n\t\t\t@include flex(column);\r\n\t\t\tflex: 1;\r\n\t\t\t// #ifndef APP-NVUE\r\n\t\t\theight: auto;\r\n\t\t\t// #endif\r\n\t\t}\r\n\t}\r\n</style>"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-waterfall/package.json",
    "content": "{\r\n  \"id\": \"uv-waterfall\",\r\n  \"displayName\": \"uv-waterfall 瀑布流 全面兼容vue3+2、app、h5、小程序等多端\",\r\n  \"version\": \"1.0.8\",\r\n  \"description\": \"该组件主要用于瀑布流式布局显示，视觉表现为参差不齐的多栏布局，随着页面滚动条向下滚动，这种布局还会不断加载数据块并附加至当前尾部，同时集成nvue的原生瀑布流。\",\r\n  \"keywords\": [\r\n    \"uv-waterfall\",\r\n    \"uvui\",\r\n    \"uv-ui\",\r\n    \"waterfall\",\r\n    \"瀑布流\"\r\n],\r\n  \"repository\": \"\",\r\n  \"engines\": {\r\n    \"HBuilderX\": \"^3.1.0\"\r\n  },\r\n  \"dcloudext\": {\r\n    \"type\": \"component-vue\",\r\n    \"sale\": {\r\n      \"regular\": {\r\n        \"price\": \"0.00\"\r\n      },\r\n      \"sourcecode\": {\r\n        \"price\": \"0.00\"\r\n      }\r\n    },\r\n    \"contact\": {\r\n      \"qq\": \"\"\r\n    },\r\n    \"declaration\": {\r\n    \t\"ads\": \"无\",\r\n    \t\"data\": \"插件不采集任何数据\",\r\n    \t\"permissions\": \"无\"\r\n    },\r\n    \"npmurl\": \"\"\r\n  },\r\n  \"uni_modules\": {\r\n    \"dependencies\": [\r\n\t\t\t\"uv-ui-tools\",\r\n\t\t\t\"uv-image\",\r\n\t\t\t\"uv-loading-icon\"\r\n\t\t],\r\n    \"encrypt\": [],\r\n    \"platforms\": {\r\n\t\t\t\"cloud\": {\r\n\t\t\t\t\"tcb\": \"y\",\r\n\t\t\t\t\"aliyun\": \"y\"\r\n\t\t\t},\r\n\t\t\t\"client\": {\r\n\t\t\t\t\"Vue\": {\r\n\t\t\t\t\t\"vue2\": \"y\",\r\n\t\t\t\t\t\"vue3\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"App\": {\r\n\t\t\t\t\t\"app-vue\": \"y\",\r\n\t\t\t\t\t\"app-nvue\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-mobile\": {\r\n\t\t\t\t\t\"Safari\": \"y\",\r\n\t\t\t\t\t\"Android Browser\": \"y\",\r\n\t\t\t\t\t\"微信浏览器(Android)\": \"y\",\r\n\t\t\t\t\t\"QQ浏览器(Android)\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"H5-pc\": {\r\n\t\t\t\t\t\"Chrome\": \"y\",\r\n\t\t\t\t\t\"IE\": \"y\",\r\n\t\t\t\t\t\"Edge\": \"y\",\r\n\t\t\t\t\t\"Firefox\": \"y\",\r\n\t\t\t\t\t\"Safari\": \"y\"\r\n\t\t\t\t},\r\n\t\t\t\t\"小程序\": {\r\n\t\t\t\t\t\"微信\": \"y\",\r\n\t\t\t\t\t\"阿里\": \"y\",\r\n\t\t\t\t\t\"百度\": \"y\",\r\n\t\t\t\t\t\"字节跳动\": \"y\",\r\n\t\t\t\t\t\"QQ\": \"y\",\r\n\t\t\t\t\t\"钉钉\": \"u\",\r\n\t\t\t\t\t\"快手\": \"u\",\r\n\t\t\t\t\t\"飞书\": \"u\",\r\n\t\t\t\t\t\"京东\": \"u\"\r\n\t\t\t\t},\r\n\t\t\t\t\"快应用\": {\r\n\t\t\t\t\t\"华为\": \"u\",\r\n\t\t\t\t\t\"联盟\": \"u\"\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n  }\r\n}"
  },
  {
    "path": "yshop-drink-uniapp-vue3/uni_modules/uv-waterfall/readme.md",
    "content": "## Waterfall 瀑布流\n\n> **组件名：uv-waterfall**\n\n该组件主要用于瀑布流式布局显示，视觉表现为参差不齐的多栏布局，随着页面滚动条向下滚动，这种布局还会不断加载数据块并附加至当前尾部，同时集成`nvue`的原生瀑布流用于`app-nvue`。常用于一些电商商品展示等，如某宝首页、x红书等。\n\n研究uniapp瀑布流多年，**该方式是目前小程序端最佳方案**，灵活配置，简单易用，开箱即用。\n\n该插件请根据文档耐心查看，`vue`的写法稍微麻烦点，但是效果是很好的，比之前上线的两个版本的瀑布流适用，更有扩展性，我自己的上线项目也是用的此插件。\n\n# <a href=\"https://www.uvui.cn/components/waterfall.html\" target=\"_blank\">查看文档</a>\n\n## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui)\n\n### [更多插件，请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)\n\n![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)\n\n#### 如使用过程中有任何问题反馈，或者您对uv-ui有一些好的建议，欢迎加入uv-ui官方交流群：<a href=\"https://www.uvui.cn/components/addQQGroup.html\" target=\"_blank\">官方QQ群</a>\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/utils/cookie.js",
    "content": "\r\nconst doc = null\r\nconst CACHE_KEY = 'clear_0.0.1'\r\n// const doc = window.document;\r\n\r\nfunction get(key) {\r\n  return uni.getStorageSync(key)\r\n}\r\n\r\nfunction all() {\r\n  return uni.getStorageInfoSync()\r\n}\r\n\r\nfunction set(key, data, time) {\r\n  console.log(\"--> % set % key:\\n\", key)\r\n  console.log(\"--> % set % data:\\n\", data)\r\n  if (!key) {\r\n    return\r\n  }\r\n  uni.setStorageSync(key, data)\r\n}\r\n\r\nfunction remove(key) {\r\n  if (!key || !_has(key)) {\r\n    return\r\n  }\r\n  uni.removeStorageSync(key)\r\n}\r\n\r\nfunction clearAll() {\r\n  const res = uni.getStorageInfoSync()\r\n  res.keys.map(item => {\r\n    if (item == 'redirect' || item == 'spread' || item == CACHE_KEY) {\r\n      return\r\n    }\r\n    remove(item)\r\n  })\r\n}\r\n\r\nfunction _has(key) {\r\n  if (!key) {\r\n    return\r\n  }\r\n  let value = uni.getStorageSync(key)\r\n  if (value) {\r\n    return true\r\n  }\r\n  return false\r\n}\r\n\r\nexport default {\r\n  get,\r\n  all,\r\n  set,\r\n  remove,\r\n  clearAll,\r\n  has: _has,\r\n  CACHE_KEY,\r\n}\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/utils/index.js",
    "content": "import stringify from '@/utils/querystring'\n\nimport router from './router'\nimport cookie from './cookie'\n\nexport const handleLoginFailure = () => {\n  // router.replace({\n  //   path: '/pages/login/login',\n  // })\n  uni.redirectTo({\n    url: '/pages/login/login',\n  })\n}\n\nexport function parseUrl(location) {\n  if (typeof location === 'string') return location\n  const { url, query } = location\n\n  const queryStr = stringify(query)\n\n  if (!queryStr) {\n    return url\n  }\n\n  return `${url}?${queryStr}`\n}\n\nconst toAuth = () => {\n  uni.showToast({\n    title: '暂未开放',\n    icon: 'none',\n    duration: 2000,\n  })\n}\n\nexport default {\n  install: (app, options) => {\n    // 在这里编写插件代码\n    // 注入一个全局可用的 $translate() 方法\n    app.config.globalProperties.$yrouter = router\n    app.config.globalProperties.$cookie = cookie\n    app.config.globalProperties.$toAuth = toAuth\n    app.config.globalProperties.$onClickLeft = () => {\n      router.back()\n\t  //uni.navigateBack()\n\t  //const mypage = getCurrentPages()\n\t  //console.log('mypage:',mypage)\n    }\n\n    // #ifdef H5\n    app.config.globalProperties.$platform = 'h5'\n    // #endif\n\n    // #ifdef APP-PLUS\n    // app端\n    app.config.globalProperties.$platform = 'app'\n    // #endif\n\n    // #ifdef MP-WEIXIN\n    app.config.globalProperties.$platform = 'routine'\n    // #endif\n  },\n}\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/utils/querystring.js",
    "content": "// Copyright Joyent, Inc. and other Node contributors.\r\n//\r\n// Permission is hereby granted, free of charge, to any person obtaining a\r\n// copy of this software and associated documentation files (the\r\n// \"Software\"), to deal in the Software without restriction, including\r\n// without limitation the rights to use, copy, modify, merge, publish,\r\n// distribute, sublicense, and/or sell copies of the Software, and to permit\r\n// persons to whom the Software is furnished to do so, subject to the\r\n// following conditions:\r\n//\r\n// The above copyright notice and this permission notice shall be included\r\n// in all copies or substantial portions of the Software.\r\n//\r\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS\r\n// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF\r\n// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN\r\n// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,\r\n// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\r\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE\r\n// USE OR OTHER DEALINGS IN THE SOFTWARE.\r\n\r\nvar stringifyPrimitive = function (v) {\r\n  switch (typeof v) {\r\n    case 'string':\r\n      return v\r\n\r\n    case 'boolean':\r\n      return v ? 'true' : 'false'\r\n\r\n    case 'number':\r\n      return isFinite(v) ? v : ''\r\n\r\n    default:\r\n      return ''\r\n  }\r\n}\r\n\r\nfunction stringify(obj, sep, eq, name) {\r\n  sep = sep || '&'\r\n  eq = eq || '='\r\n  if (obj === null) {\r\n    obj = undefined\r\n  }\r\n\r\n  if (typeof obj === 'object') {\r\n    return Object.keys(obj).map(function (k) {\r\n      var ks = stringifyPrimitive(k) + eq\r\n      if (Array.isArray(obj[k])) {\r\n        return obj[k].map(function (v) {\r\n          return ks + stringifyPrimitive(v)\r\n        }).join(sep)\r\n      } else {\r\n        return ks + stringifyPrimitive(obj[k])\r\n      }\r\n    }).filter(Boolean).join(sep)\r\n\r\n  }\r\n\r\n  if (!name) return ''\r\n  return stringifyPrimitive(name) + eq + stringifyPrimitive(obj)\r\n}\r\n\r\nexport default stringify\r\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/utils/router.js",
    "content": "import { parseUrl } from '@/utils'\n\nexport function navigateTo(location, complete, fail, success) {\n  console.log({\n    url: parseUrl(location),\n    complete,\n    fail,\n    success,\n  })\n  uni.navigateTo({\n    url: parseUrl(location),\n    complete,\n    fail,\n    success,\n  })\n}\n\nexport function replace(location, complete, fail, success) {\n  uni.redirectTo({\n    url: parseUrl(location),\n    complete,\n    fail,\n    success,\n  })\n}\n\nexport function reLaunch(location, complete, fail, success) {\n  uni.reLaunch({\n    url: parseUrl(location),\n    complete,\n    fail,\n    success,\n  })\n}\n\nexport function go(delta) {\n  uni.navigateBack({\n    delta,\n  })\n}\n\nexport function back() {\n\tconst mypage = getCurrentPages()\n\tif(mypage.length == 1) {\n\t\tuni.switchTab({\n\t\t\turl: '/pages/index/index'\n\t\t})\n\t\treturn\n\t}\n  uni.navigateBack({\n    delta: 1,\n    success: function (e) {},\n    fail: function (e) {\n\t\tconsole.log('aaaa:')\n\t\t\n\t},\n  })\n}\n\nexport function switchTab(location, complete, fail, success) {\n  uni.switchTab({\n    url: parseUrl(location),\n    complete,\n    fail,\n    success,\n  })\n}\n\nexport default {\n  back,\n  navigateTo,\n  replace,\n  reLaunch,\n  switchTab,\n}\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/utils/util.js",
    "content": "export function formatTime(time) {\n\tif (typeof time !== 'number' || time < 0) {\n\t\treturn time\n\t}\n\n\tvar hour = parseInt(time / 3600)\n\ttime = time % 3600\n\tvar minute = parseInt(time / 60)\n\ttime = time % 60\n\tvar second = time\n\n\treturn ([hour, minute, second]).map(function(n) {\n\t\tn = n.toString()\n\t\treturn n[1] ? n : '0' + n\n\t}).join(':')\n}\n\nexport function formatDateTime(date, fmt = 'yyyy-MM-dd hh:mm:ss') {\n\tif(!date) {\n\t\treturn ''\n\t}\n    if (typeof (date) === 'number') {\n        date = new Date(date)\n    }\n    var o = {\n        \"M+\": date.getMonth() + 1, //月份\n        \"d+\": date.getDate(), //日\n        \"h+\": date.getHours(), //小时\n        \"m+\": date.getMinutes(), //分\n        \"s+\": date.getSeconds(), //秒\n        \"q+\": Math.floor((date.getMonth() + 3) / 3), //季度\n        \"S\": date.getMilliseconds() //毫秒\n    }\n    if (/(y+)/.test(fmt)) fmt = fmt.replace(RegExp.$1, (date.getFullYear() + \"\").substr(4 - RegExp.$1.length))\n    for (var k in o)\n        if (new RegExp(\"(\" + k + \")\").test(fmt))\n            fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : ((\"00\" + o[k]).substr((\"\" + o[k]).length)))\n    return fmt\n}\n\nexport function formatLocation(longitude, latitude) {\n\tif (typeof longitude === 'string' && typeof latitude === 'string') {\n\t\tlongitude = parseFloat(longitude)\n\t\tlatitude = parseFloat(latitude)\n\t}\n\n\tlongitude = longitude.toFixed(2)\n\tlatitude = latitude.toFixed(2)\n\n\treturn {\n\t\tlongitude: longitude.toString().split('.'),\n\t\tlatitude: latitude.toString().split('.')\n\t}\n}\n\nvar dateUtils = {\n\tUNITS: {\n\t\t'年': 31557600000,\n\t\t'月': 2629800000,\n\t\t'天': 86400000,\n\t\t'小时': 3600000,\n\t\t'分钟': 60000,\n\t\t'秒': 1000\n\t},\n\thumanize: function(milliseconds) {\n\t\tvar humanize = '';\n\t\tfor (var key in this.UNITS) {\n\t\t\tif (milliseconds >= this.UNITS[key]) {\n\t\t\t\thumanize = Math.floor(milliseconds / this.UNITS[key]) + key + '前';\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t\treturn humanize || '刚刚';\n\t},\n\tformat: function(dateStr) {\n\t\tvar date = this.parse(dateStr)\n\t\tvar diff = Date.now() - date.getTime();\n\t\tif (diff < this.UNITS['天']) {\n\t\t\treturn this.humanize(diff);\n\t\t}\n\t\tvar _format = function(number) {\n\t\t\treturn (number < 10 ? ('0' + number) : number);\n\t\t};\n\t\treturn date.getFullYear() + '/' + _format(date.getMonth() + 1) + '/' + _format(date.getDate()) + '-' +\n\t\t\t_format(date.getHours()) + ':' + _format(date.getMinutes());\n\t},\n\tparse: function(str) { //将\"yyyy-mm-dd HH:MM:ss\"格式的字符串，转化为一个Date对象\n\t\tvar a = str.split(/[^0-9]/);\n\t\treturn new Date(a[0], a[1] - 1, a[2], a[3], a[4], a[5]);\n\t}\n};\n\n// 返回上一页\nexport function prePage(page = null){\n\tlet pages = getCurrentPages();\n\t//console.log('pages:',pages);\n\tlet prePage = pages[pages.length - 2];\n\tif (page !== null) {\n\t\tprePage = pages[page];\n\t}\n\t// #ifdef H5\n\treturn prePage;\n\t// #endif\n\treturn prePage.$vm;\n}\n\nexport function kmUnit(m){\n    var v;\n\tif(typeof m === 'number' && !isNaN(m)){\n\t\tif (m >= 1000) {\n            v = (m / 1000).toFixed(2) + 'km'\n        } else {\n           \tv = m + 'm'\n        }\n\t}else{\n\t\tv = '0m'\n\t}\n    return v;\n}\n\nexport function isWeixin() {\n\tif (navigator && navigator.userAgent && navigator.userAgent.toLowerCase().indexOf('micromessenger') !== -1) {\n\t  return true\n\t}\n\treturn false\n  }\n  \nexport function parseQuery() {\r\n    let res = {}\r\n  \r\n    // #ifdef H5\r\n    const query = (location.href.split('?')[1] || '').trim().replace(/^(\\?|#|&)/, '')\r\n  \r\n    if (!query) {\r\n      return res\r\n    }\r\n  \r\n    query.split('&').forEach(param => {\r\n      const parts = param.replace(/\\+/g, ' ').split('=')\r\n      const key = decodeURIComponent(parts.shift())\r\n      const val = parts.length > 0 ? decodeURIComponent(parts.join('=')) : null\r\n  \r\n      if (res[key] === undefined) {\r\n        res[key] = val\r\n      } else if (Array.isArray(res[key])) {\r\n        res[key].push(val)\r\n      } else {\r\n        res[key] = [res[key], val]\r\n      }\r\n    })\r\n    // #endif\r\n    // #ifndef H5\r\n    var pages = getCurrentPages() //获取加载的页面\r\n    var currentPage = pages[pages.length - 1] //获取当前页面的对象\r\n    var url = currentPage.route //当前页面url\r\n    res = currentPage.options //如果要获取url中所带的参数可以查看options\r\n    // #endif\r\n  \r\n    return res\r\n  }\n  \n\n"
  },
  {
    "path": "yshop-drink-uniapp-vue3/vue.config.js",
    "content": "// module.exports = {\n//   devServer: {\n//     proxy: {\n//       '/app-api': {\n//         target: 'http://yshop.l1.ttut.cc/app-api',\n//         changeOrigin: true,\n//       },\n//     },\n//   },\n// }\n"
  },
  {
    "path": "yshop-drink-vue3/.editorconfig",
    "content": "root = true\n[*.{js,ts,vue}]\ncharset = utf-8 # 设置文件字符集为 utf-8\nend_of_line = lf # 控制换行类型(lf | cr | crlf)\ninsert_final_newline = true # 始终在文件末尾插入一个新行\nindent_style = space # 缩进风格（tab | space）\nindent_size = 2 # 缩进大小\nmax_line_length = 100 # 最大行长度\n\n[*.md] # 仅 md 文件适用以下规则\nmax_line_length = off # 关闭最大行长度限制\ntrim_trailing_whitespace = false # 关闭末尾空格修剪\n"
  },
  {
    "path": "yshop-drink-vue3/.eslintignore",
    "content": "/build/\n/config/\n/dist/\n/*.js\n/test/unit/coverage/\n/node_modules/*\n/dist*\n/src/main.ts\n"
  },
  {
    "path": "yshop-drink-vue3/.eslintrc-auto-import.json",
    "content": "{\n  \"globals\": {\n    \"EffectScope\": true,\n    \"ElMessage\": true,\n    \"ElMessageBox\": true,\n    \"ElTag\": true,\n    \"asyncComputed\": true,\n    \"autoResetRef\": true,\n    \"computed\": true,\n    \"computedAsync\": true,\n    \"computedEager\": true,\n    \"computedInject\": true,\n    \"computedWithControl\": true,\n    \"controlledComputed\": true,\n    \"controlledRef\": true,\n    \"createApp\": true,\n    \"createEventHook\": true,\n    \"createGlobalState\": true,\n    \"createInjectionState\": true,\n    \"createReactiveFn\": true,\n    \"createSharedComposable\": true,\n    \"createUnrefFn\": true,\n    \"customRef\": true,\n    \"debouncedRef\": true,\n    \"debouncedWatch\": true,\n    \"defineAsyncComponent\": true,\n    \"defineComponent\": true,\n    \"eagerComputed\": true,\n    \"effectScope\": true,\n    \"extendRef\": true,\n    \"getCurrentInstance\": true,\n    \"getCurrentScope\": true,\n    \"h\": true,\n    \"ignorableWatch\": true,\n    \"inject\": true,\n    \"isDefined\": true,\n    \"isProxy\": true,\n    \"isReactive\": true,\n    \"isReadonly\": true,\n    \"isRef\": true,\n    \"makeDestructurable\": true,\n    \"markRaw\": true,\n    \"nextTick\": true,\n    \"onActivated\": true,\n    \"onBeforeMount\": true,\n    \"onBeforeUnmount\": true,\n    \"onBeforeUpdate\": true,\n    \"onClickOutside\": true,\n    \"onDeactivated\": true,\n    \"onErrorCaptured\": true,\n    \"onKeyStroke\": true,\n    \"onLongPress\": true,\n    \"onMounted\": true,\n    \"onRenderTracked\": true,\n    \"onRenderTriggered\": true,\n    \"onScopeDispose\": true,\n    \"onServerPrefetch\": true,\n    \"onStartTyping\": true,\n    \"onUnmounted\": true,\n    \"onUpdated\": true,\n    \"pausableWatch\": true,\n    \"provide\": true,\n    \"reactify\": true,\n    \"reactifyObject\": true,\n    \"reactive\": true,\n    \"reactiveComputed\": true,\n    \"reactiveOmit\": true,\n    \"reactivePick\": true,\n    \"readonly\": true,\n    \"ref\": true,\n    \"refAutoReset\": true,\n    \"refDebounced\": true,\n    \"refDefault\": true,\n    \"refThrottled\": true,\n    \"refWithControl\": true,\n    \"resolveComponent\": true,\n    \"resolveRef\": true,\n    \"resolveUnref\": true,\n    \"shallowReactive\": true,\n    \"shallowReadonly\": true,\n    \"shallowRef\": true,\n    \"syncRef\": true,\n    \"syncRefs\": true,\n    \"templateRef\": true,\n    \"throttledRef\": true,\n    \"throttledWatch\": true,\n    \"toRaw\": true,\n    \"toReactive\": true,\n    \"toRef\": true,\n    \"toRefs\": true,\n    \"triggerRef\": true,\n    \"tryOnBeforeMount\": true,\n    \"tryOnBeforeUnmount\": true,\n    \"tryOnMounted\": true,\n    \"tryOnScopeDispose\": true,\n    \"tryOnUnmounted\": true,\n    \"unref\": true,\n    \"unrefElement\": true,\n    \"until\": true,\n    \"useActiveElement\": true,\n    \"useArrayEvery\": true,\n    \"useArrayFilter\": true,\n    \"useArrayFind\": true,\n    \"useArrayFindIndex\": true,\n    \"useArrayJoin\": true,\n    \"useArrayMap\": true,\n    \"useArrayReduce\": true,\n    \"useArraySome\": true,\n    \"useAsyncQueue\": true,\n    \"useAsyncState\": true,\n    \"useAttrs\": true,\n    \"useBase64\": true,\n    \"useBattery\": true,\n    \"useBluetooth\": true,\n    \"useBreakpoints\": true,\n    \"useBroadcastChannel\": true,\n    \"useBrowserLocation\": true,\n    \"useCached\": true,\n    \"useClipboard\": true,\n    \"useColorMode\": true,\n    \"useConfirmDialog\": true,\n    \"useCounter\": true,\n    \"useCssModule\": true,\n    \"useCssVar\": true,\n    \"useCssVars\": true,\n    \"useCurrentElement\": true,\n    \"useCycleList\": true,\n    \"useDark\": true,\n    \"useDateFormat\": true,\n    \"useDebounce\": true,\n    \"useDebounceFn\": true,\n    \"useDebouncedRefHistory\": true,\n    \"useDeviceMotion\": true,\n    \"useDeviceOrientation\": true,\n    \"useDevicePixelRatio\": true,\n    \"useDevicesList\": true,\n    \"useDisplayMedia\": true,\n    \"useDocumentVisibility\": true,\n    \"useDraggable\": true,\n    \"useDropZone\": true,\n    \"useElementBounding\": true,\n    \"useElementByPoint\": true,\n    \"useElementHover\": true,\n    \"useElementSize\": true,\n    \"useElementVisibility\": true,\n    \"useEventBus\": true,\n    \"useEventListener\": true,\n    \"useEventSource\": true,\n    \"useEyeDropper\": true,\n    \"useFavicon\": true,\n    \"useFetch\": true,\n    \"useFileDialog\": true,\n    \"useFileSystemAccess\": true,\n    \"useFocus\": true,\n    \"useFocusWithin\": true,\n    \"useFps\": true,\n    \"useFullscreen\": true,\n    \"useGamepad\": true,\n    \"useGeolocation\": true,\n    \"useIdle\": true,\n    \"useImage\": true,\n    \"useInfiniteScroll\": true,\n    \"useIntersectionObserver\": true,\n    \"useInterval\": true,\n    \"useIntervalFn\": true,\n    \"useKeyModifier\": true,\n    \"useLastChanged\": true,\n    \"useLocalStorage\": true,\n    \"useMagicKeys\": true,\n    \"useManualRefHistory\": true,\n    \"useMediaControls\": true,\n    \"useMediaQuery\": true,\n    \"useMemoize\": true,\n    \"useMemory\": true,\n    \"useMounted\": true,\n    \"useMouse\": true,\n    \"useMouseInElement\": true,\n    \"useMousePressed\": true,\n    \"useMutationObserver\": true,\n    \"useNavigatorLanguage\": true,\n    \"useNetwork\": true,\n    \"useNow\": true,\n    \"useObjectUrl\": true,\n    \"useOffsetPagination\": true,\n    \"useOnline\": true,\n    \"usePageLeave\": true,\n    \"useParallax\": true,\n    \"usePermission\": true,\n    \"usePointer\": true,\n    \"usePointerSwipe\": true,\n    \"usePreferredColorScheme\": true,\n    \"usePreferredDark\": true,\n    \"usePreferredLanguages\": true,\n    \"useRafFn\": true,\n    \"useRefHistory\": true,\n    \"useResizeObserver\": true,\n    \"useRoute\": true,\n    \"useRouter\": true,\n    \"useScreenOrientation\": true,\n    \"useScreenSafeArea\": true,\n    \"useScriptTag\": true,\n    \"useScroll\": true,\n    \"useScrollLock\": true,\n    \"useSessionStorage\": true,\n    \"useShare\": true,\n    \"useSlots\": true,\n    \"useSpeechRecognition\": true,\n    \"useSpeechSynthesis\": true,\n    \"useStepper\": true,\n    \"useStorage\": true,\n    \"useStorageAsync\": true,\n    \"useStyleTag\": true,\n    \"useSupported\": true,\n    \"useSwipe\": true,\n    \"useTemplateRefsList\": true,\n    \"useTextDirection\": true,\n    \"useTextSelection\": true,\n    \"useTextareaAutosize\": true,\n    \"useThrottle\": true,\n    \"useThrottleFn\": true,\n    \"useThrottledRefHistory\": true,\n    \"useTimeAgo\": true,\n    \"useTimeout\": true,\n    \"useTimeoutFn\": true,\n    \"useTimeoutPoll\": true,\n    \"useTimestamp\": true,\n    \"useTitle\": true,\n    \"useToggle\": true,\n    \"useTransition\": true,\n    \"useUrlSearchParams\": true,\n    \"useUserMedia\": true,\n    \"useVModel\": true,\n    \"useVModels\": true,\n    \"useVibrate\": true,\n    \"useVirtualList\": true,\n    \"useWakeLock\": true,\n    \"useWebNotification\": true,\n    \"useWebSocket\": true,\n    \"useWebWorker\": true,\n    \"useWebWorkerFn\": true,\n    \"useWindowFocus\": true,\n    \"useWindowScroll\": true,\n    \"useWindowSize\": true,\n    \"watch\": true,\n    \"watchArray\": true,\n    \"watchAtMost\": true,\n    \"watchDebounced\": true,\n    \"watchEffect\": true,\n    \"watchIgnorable\": true,\n    \"watchOnce\": true,\n    \"watchPausable\": true,\n    \"watchPostEffect\": true,\n    \"watchSyncEffect\": true,\n    \"watchThrottled\": true,\n    \"watchTriggerable\": true,\n    \"watchWithFilter\": true,\n    \"whenever\": true\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/.eslintrc.js",
    "content": "// @ts-check\nconst { defineConfig } = require('eslint-define-config')\nmodule.exports = defineConfig({\n  root: true,\n  env: {\n    browser: true,\n    node: true,\n    es6: true\n  },\n  parser: 'vue-eslint-parser',\n  parserOptions: {\n    parser: '@typescript-eslint/parser',\n    ecmaVersion: 2020,\n    sourceType: 'module',\n    jsxPragma: 'React',\n    ecmaFeatures: {\n      jsx: true\n    }\n  },\n  extends: [\n    'plugin:vue/vue3-recommended',\n    'plugin:@typescript-eslint/recommended',\n    'prettier',\n    'plugin:prettier/recommended',\n    '@unocss'\n  ],\n  rules: {\n    'vue/no-setup-props-destructure': 'off',\n    'vue/script-setup-uses-vars': 'error',\n    'vue/no-reserved-component-names': 'off',\n    '@typescript-eslint/ban-ts-ignore': 'off',\n    '@typescript-eslint/explicit-function-return-type': 'off',\n    '@typescript-eslint/no-explicit-any': 'off',\n    '@typescript-eslint/no-var-requires': 'off',\n    '@typescript-eslint/no-empty-function': 'off',\n    'vue/custom-event-name-casing': 'off',\n    'no-use-before-define': 'off',\n    '@typescript-eslint/no-use-before-define': 'off',\n    '@typescript-eslint/ban-ts-comment': 'off',\n    '@typescript-eslint/ban-types': 'off',\n    '@typescript-eslint/no-non-null-assertion': 'off',\n    '@typescript-eslint/explicit-module-boundary-types': 'off',\n    '@typescript-eslint/no-unused-vars': 'off',\n    'no-unused-vars': 'off',\n    'space-before-function-paren': 'off',\n\n    'vue/attributes-order': 'off',\n    'vue/one-component-per-file': 'off',\n    'vue/html-closing-bracket-newline': 'off',\n    'vue/max-attributes-per-line': 'off',\n    'vue/multiline-html-element-content-newline': 'off',\n    'vue/singleline-html-element-content-newline': 'off',\n    'vue/attribute-hyphenation': 'off',\n    'vue/require-default-prop': 'off',\n    'vue/require-explicit-emits': 'off',\n    'vue/require-toggle-inside-transition': 'off',\n    'vue/html-self-closing': [\n      'error',\n      {\n        html: {\n          void: 'always',\n          normal: 'never',\n          component: 'always'\n        },\n        svg: 'always',\n        math: 'always'\n      }\n    ],\n    'vue/multi-word-component-names': 'off',\n    'vue/no-v-html': 'off',\n    'prettier/prettier': 'off', // 芋艿：默认关闭 prettier 的 ESLint 校验，因为我们使用的是 IDE 的 Prettier 插件\n    '@unocss/order': 'off', // 芋艿：禁用 unocss 【css】顺序的提示，因为暂时不需要这么严格，警告也有点繁琐\n    '@unocss/order-attributify': 'off' // 芋艿：禁用 unocss 【属性】顺序的提示，因为暂时不需要这么严格，警告也有点繁琐\n  }\n})\n"
  },
  {
    "path": "yshop-drink-vue3/.gitignore",
    "content": "node_modules\n.DS_Store\ndist\ndist-ssr\n*.local\n/dist*\npnpm-debug\nauto-*.d.ts\n.idea\n.history\n"
  },
  {
    "path": "yshop-drink-vue3/.prettierignore",
    "content": "/node_modules/**\n/dist/\n/dist*\n/public/*\n/docs/*\n/vite.config.ts\n/src/types/env.d.ts\n/src/types/auto-components.d.ts\n/src/types/auto-imports.d.ts\n/docs/**/*\nCHANGELOG\n"
  },
  {
    "path": "yshop-drink-vue3/.stylelintignore",
    "content": "/dist/*\n/public/*\npublic/*\n/dist*\n/src/types/env.d.ts\n/docs/**/*\n"
  },
  {
    "path": "yshop-drink-vue3/.vscode/extensions.json",
    "content": "{\n  \"recommendations\": [\n    \"christian-kohler.path-intellisense\",\n    \"vscode-icons-team.vscode-icons\",\n    \"davidanson.vscode-markdownlint\",\n    \"dbaeumer.vscode-eslint\",\n    \"esbenp.prettier-vscode\",\n    \"mrmlnc.vscode-less\",\n    \"lokalise.i18n-ally\",\n    \"redhat.vscode-yaml\",\n    \"csstools.postcss\",\n    \"mikestead.dotenv\",\n    \"eamodio.gitlens\",\n    \"antfu.iconify\",\n    \"antfu.unocss\",\n    \"Vue.volar\"\n  ]\n}\n"
  },
  {
    "path": "yshop-drink-vue3/.vscode/launch.json",
    "content": "{\n  // Use IntelliSense to learn about possible attributes.\n  // Hover to view descriptions of existing attributes.\n  // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387\n  \"version\": \"0.2.0\",\n  \"configurations\": [\n    {\n      \"type\": \"msedge\",\n      \"request\": \"launch\",\n      \"name\": \"Launch Edge against localhost\",\n      \"url\": \"http://localhost\",\n      \"webRoot\": \"${workspaceFolder}/src\",\n      \"sourceMaps\": true\n    }\n  ]\n}\n"
  },
  {
    "path": "yshop-drink-vue3/.vscode/settings.json",
    "content": "{\n  \"typescript.tsdk\": \"node_modules/typescript/lib\",\n  \"npm.packageManager\": \"pnpm\",\n  \"editor.tabSize\": 2,\n  \"prettier.printWidth\": 100, // 超过最大值换行\n  \"editor.defaultFormatter\": \"esbenp.prettier-vscode\",\n  \"files.eol\": \"\\n\",\n  \"search.exclude\": {\n    \"**/node_modules\": true,\n    \"**/*.log\": true,\n    \"**/*.log*\": true,\n    \"**/bower_components\": true,\n    \"**/dist\": true,\n    \"**/elehukouben\": true,\n    \"**/.git\": true,\n    \"**/.gitignore\": true,\n    \"**/.svn\": true,\n    \"**/.DS_Store\": true,\n    \"**/.idea\": true,\n    \"**/.vscode\": false,\n    \"**/yarn.lock\": true,\n    \"**/tmp\": true,\n    \"out\": true,\n    \"dist\": true,\n    \"node_modules\": true,\n    \"CHANGELOG.md\": true,\n    \"examples\": true,\n    \"res\": true,\n    \"screenshots\": true,\n    \"yarn-error.log\": true,\n    \"**/.yarn\": true\n  },\n  \"files.exclude\": {\n    \"**/.cache\": true,\n    \"**/.editorconfig\": true,\n    \"**/.eslintcache\": true,\n    \"**/bower_components\": true,\n    \"**/.idea\": true,\n    \"**/tmp\": true,\n    \"**/.git\": true,\n    \"**/.svn\": true,\n    \"**/.hg\": true,\n    \"**/CVS\": true,\n    \"**/.DS_Store\": true\n  },\n  \"files.watcherExclude\": {\n    \"**/.git/objects/**\": true,\n    \"**/.git/subtree-cache/**\": true,\n    \"**/.vscode/**\": true,\n    \"**/node_modules/**\": true,\n    \"**/tmp/**\": true,\n    \"**/bower_components/**\": true,\n    \"**/dist/**\": true,\n    \"**/yarn.lock\": true\n  },\n  \"stylelint.enable\": true,\n  \"stylelint.validate\": [\"css\", \"less\", \"postcss\", \"scss\", \"vue\", \"sass\"],\n  \"path-intellisense.mappings\": {\n    \"@/\": \"${workspaceRoot}/src\"\n  },\n  \"[javascriptreact]\": {\n    \"editor.defaultFormatter\": \"esbenp.prettier-vscode\"\n  },\n  \"[typescript]\": {\n    \"editor.defaultFormatter\": \"rvest.vs-code-prettier-eslint\"\n  },\n  \"[typescriptreact]\": {\n    \"editor.defaultFormatter\": \"rvest.vs-code-prettier-eslint\"\n  },\n  \"[html]\": {\n    \"editor.defaultFormatter\": \"esbenp.prettier-vscode\"\n  },\n  \"[css]\": {\n    \"editor.defaultFormatter\": \"rvest.vs-code-prettier-eslint\"\n  },\n  \"[less]\": {\n    \"editor.defaultFormatter\": \"esbenp.prettier-vscode\"\n  },\n  \"[scss]\": {\n    \"editor.defaultFormatter\": \"esbenp.prettier-vscode\"\n  },\n  \"[markdown]\": {\n    \"editor.defaultFormatter\": \"esbenp.prettier-vscode\"\n  },\n  \"editor.codeActionsOnSave\": {\n    \"source.fixAll.eslint\": \"explicit\"\n  },\n  \"[vue]\": {\n    \"editor.defaultFormatter\": \"rvest.vs-code-prettier-eslint\"\n  },\n  \"i18n-ally.localesPaths\": [\"src/locales\"],\n  \"i18n-ally.keystyle\": \"nested\",\n  \"i18n-ally.sortKeys\": true,\n  \"i18n-ally.namespace\": false,\n  \"i18n-ally.enabledParsers\": [\"ts\"],\n  \"i18n-ally.sourceLanguage\": \"en\",\n  \"i18n-ally.displayLanguage\": \"zh-CN\",\n  \"i18n-ally.enabledFrameworks\": [\"vue\", \"react\"],\n  \"cSpell.words\": [\n    \"brotli\",\n    \"browserslist\",\n    \"codemirror\",\n    \"commitlint\",\n    \"cropperjs\",\n    \"echart\",\n    \"echarts\",\n    \"esnext\",\n    \"esno\",\n    \"iconify\",\n    \"INTLIFY\",\n    \"lintstagedrc\",\n    \"logicflow\",\n    \"nprogress\",\n    \"pinia\",\n    \"pnpm\",\n    \"qrcode\",\n    \"sider\",\n    \"sortablejs\",\n    \"stylelint\",\n    \"svgs\",\n    \"unocss\",\n    \"unplugin\",\n    \"unref\",\n    \"videojs\",\n    \"VITE\",\n    \"vitejs\",\n    \"vueuse\",\n    \"wangeditor\",\n    \"xingyu\",\n    \"yudao\",\n    \"zxcvbn\"\n  ],\n  // 控制相关文件嵌套展示\n  \"explorer.fileNesting.enabled\": true,\n  \"explorer.fileNesting.expand\": false,\n  \"explorer.fileNesting.patterns\": {\n    \"*.ts\": \"$(capture).test.ts, $(capture).test.tsx\",\n    \"*.tsx\": \"$(capture).test.ts, $(capture).test.tsx\",\n    \"*.env\": \"$(capture).env.*\",\n    \"package.json\": \"pnpm-lock.yaml,yarn.lock,LICENSE,README*,CHANGELOG*,CNAME,.gitattributes,.eslintrc-auto-import.json,.gitignore,prettier.config.js,stylelint.config.js,commitlint.config.js,.stylelintignore,.prettierignore,.gitpod.yml,.eslintrc.js,.eslintignore\"\n  },\n  \"terminal.integrated.scrollback\": 10000,\n  \"nuxt.isNuxtApp\": false\n}\n"
  },
  {
    "path": "yshop-drink-vue3/README.md",
    "content": "意向订餐系统，类似肯德基点餐小程序模式，支持多门店模式，基础技术Java，uniapp（支持H5、微信小程序）\n采用当前流行技术组合的前后端分离点餐系统： SpringBoot3+jdk17+vue3、Spring Security OAuth2、MybatisPlus、SpringSecurity、jwt、redis、Vue3的前后端分离的系统，\n包含店铺管理、积分兑换、云小票打印、图片素材库、订单管理、多规格sku、积分、优惠券、充值、多门店、微信公众号等功能，更适合企业或个人二次开发."
  },
  {
    "path": "yshop-drink-vue3/build/vite/index.ts",
    "content": "import { resolve } from 'path'\nimport Vue from '@vitejs/plugin-vue'\nimport VueJsx from '@vitejs/plugin-vue-jsx'\nimport progress from 'vite-plugin-progress'\nimport EslintPlugin from 'vite-plugin-eslint'\nimport PurgeIcons from 'vite-plugin-purge-icons'\nimport { ViteEjsPlugin } from 'vite-plugin-ejs'\n// @ts-ignore\nimport ElementPlus from 'unplugin-element-plus/vite'\nimport AutoImport from 'unplugin-auto-import/vite'\nimport Components from 'unplugin-vue-components/vite'\nimport { ElementPlusResolver } from 'unplugin-vue-components/resolvers'\nimport viteCompression from 'vite-plugin-compression'\nimport topLevelAwait from 'vite-plugin-top-level-await'\nimport VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite'\nimport { createSvgIconsPlugin } from 'vite-plugin-svg-icons'\nimport UnoCSS from 'unocss/vite'\n\nexport function createVitePlugins() {\n  const root = process.cwd()\n\n  // 路径查找\n  function pathResolve(dir: string) {\n    return resolve(root, '.', dir)\n  }\n\n  return [\n    Vue(),\n    VueJsx(),\n    UnoCSS(),\n    progress(),\n    PurgeIcons(),\n    ElementPlus({}),\n    AutoImport({\n      include: [\n        /\\.[tj]sx?$/, // .ts, .tsx, .js, .jsx\n        /\\.vue$/,\n        /\\.vue\\?vue/, // .vue\n        /\\.md$/ // .md\n      ],\n      imports: [\n        'vue',\n        'vue-router',\n        // 可额外添加需要 autoImport 的组件\n        {\n          '@/hooks/web/useI18n': ['useI18n'],\n          '@/hooks/web/useMessage': ['useMessage'],\n          '@/hooks/web/useTable': ['useTable'],\n          '@/hooks/web/useCrudSchemas': ['useCrudSchemas'],\n          '@/utils/formRules': ['required'],\n          '@/utils/dict': ['DICT_TYPE']\n        }\n      ],\n      dts: 'src/types/auto-imports.d.ts',\n      resolvers: [ElementPlusResolver()],\n      eslintrc: {\n        enabled: false, // Default `false`\n        filepath: './.eslintrc-auto-import.json', // Default `./.eslintrc-auto-import.json`\n        globalsPropValue: true // Default `true`, (true | false | 'readonly' | 'readable' | 'writable' | 'writeable')\n      }\n    }),\n    Components({\n      // 生成自定义 `auto-components.d.ts` 全局声明\n      dts: 'src/types/auto-components.d.ts',\n      // 自定义组件的解析器\n      resolvers: [ElementPlusResolver()],\n      globs: [\"src/components/**/**.{vue, md}\", '!src/components/DiyEditor/components/mobile/**']\n    }),\n    EslintPlugin({\n      cache: false,\n      include: ['src/**/*.vue', 'src/**/*.ts', 'src/**/*.tsx'] // 检查的文件\n    }),\n    VueI18nPlugin({\n      runtimeOnly: true,\n      compositionOnly: true,\n      include: [resolve(__dirname, 'src/locales/**')]\n    }),\n    createSvgIconsPlugin({\n      iconDirs: [pathResolve('src/assets/svgs')],\n      symbolId: 'icon-[dir]-[name]',\n      svgoOptions: true\n    }),\n    viteCompression({\n      verbose: true, // 是否在控制台输出压缩结果\n      disable: false, // 是否禁用\n      threshold: 10240, // 体积大于 threshold 才会被压缩,单位 b\n      algorithm: 'gzip', // 压缩算法,可选 [ 'gzip' , 'brotliCompress' ,'deflate' , 'deflateRaw']\n      ext: '.gz', // 生成的压缩包后缀\n      deleteOriginFile: false //压缩后是否删除源文件\n    }),\n    ViteEjsPlugin(),\n    topLevelAwait({\n      // https://juejin.cn/post/7152191742513512485\n      // The export name of top-level await promise for each chunk module\n      promiseExportName: '__tla',\n      // The function to generate import names of top-level await promise in each chunk module\n      promiseImportName: (i) => `__tla_${i}`\n    })\n  ]\n}\n"
  },
  {
    "path": "yshop-drink-vue3/build/vite/optimize.ts",
    "content": "const include = [\n  'qs',\n  'url',\n  'vue',\n  'sass',\n  'mitt',\n  'axios',\n  'pinia',\n  'dayjs',\n  'qrcode',\n  'unocss',\n  'vue-router',\n  'vue-types',\n  'vue-i18n',\n  'crypto-js',\n  'cropperjs',\n  'lodash-es',\n  'nprogress',\n  'web-storage-cache',\n  '@iconify/iconify',\n  '@vueuse/core',\n  '@zxcvbn-ts/core',\n  'echarts/core',\n  'echarts/charts',\n  'echarts/components',\n  'echarts/renderers',\n  'echarts-wordcloud',\n  '@wangeditor/editor',\n  '@wangeditor/editor-for-vue',\n  'element-plus',\n  'element-plus/es',\n  'element-plus/es/locale/lang/zh-cn',\n  'element-plus/es/locale/lang/en',\n  'element-plus/es/components/avatar/style/css',\n  'element-plus/es/components/space/style/css',\n  'element-plus/es/components/backtop/style/css',\n  'element-plus/es/components/form/style/css',\n  'element-plus/es/components/radio-group/style/css',\n  'element-plus/es/components/radio/style/css',\n  'element-plus/es/components/checkbox/style/css',\n  'element-plus/es/components/checkbox-group/style/css',\n  'element-plus/es/components/switch/style/css',\n  'element-plus/es/components/time-picker/style/css',\n  'element-plus/es/components/date-picker/style/css',\n  'element-plus/es/components/descriptions/style/css',\n  'element-plus/es/components/descriptions-item/style/css',\n  'element-plus/es/components/link/style/css',\n  'element-plus/es/components/tooltip/style/css',\n  'element-plus/es/components/drawer/style/css',\n  'element-plus/es/components/dialog/style/css',\n  'element-plus/es/components/checkbox-button/style/css',\n  'element-plus/es/components/option-group/style/css',\n  'element-plus/es/components/radio-button/style/css',\n  'element-plus/es/components/cascader/style/css',\n  'element-plus/es/components/color-picker/style/css',\n  'element-plus/es/components/input-number/style/css',\n  'element-plus/es/components/rate/style/css',\n  'element-plus/es/components/select-v2/style/css',\n  'element-plus/es/components/tree-select/style/css',\n  'element-plus/es/components/slider/style/css',\n  'element-plus/es/components/time-select/style/css',\n  'element-plus/es/components/autocomplete/style/css',\n  'element-plus/es/components/image-viewer/style/css',\n  'element-plus/es/components/upload/style/css',\n  'element-plus/es/components/col/style/css',\n  'element-plus/es/components/form-item/style/css',\n  'element-plus/es/components/alert/style/css',\n  'element-plus/es/components/breadcrumb/style/css',\n  'element-plus/es/components/select/style/css',\n  'element-plus/es/components/input/style/css',\n  'element-plus/es/components/breadcrumb-item/style/css',\n  'element-plus/es/components/tag/style/css',\n  'element-plus/es/components/pagination/style/css',\n  'element-plus/es/components/table/style/css',\n  'element-plus/es/components/table-v2/style/css',\n  'element-plus/es/components/table-column/style/css',\n  'element-plus/es/components/card/style/css',\n  'element-plus/es/components/row/style/css',\n  'element-plus/es/components/button/style/css',\n  'element-plus/es/components/menu/style/css',\n  'element-plus/es/components/sub-menu/style/css',\n  'element-plus/es/components/menu-item/style/css',\n  'element-plus/es/components/option/style/css',\n  'element-plus/es/components/dropdown/style/css',\n  'element-plus/es/components/dropdown-menu/style/css',\n  'element-plus/es/components/dropdown-item/style/css',\n  'element-plus/es/components/skeleton/style/css',\n  'element-plus/es/components/skeleton/style/css',\n  'element-plus/es/components/backtop/style/css',\n  'element-plus/es/components/menu/style/css',\n  'element-plus/es/components/sub-menu/style/css',\n  'element-plus/es/components/menu-item/style/css',\n  'element-plus/es/components/dropdown/style/css',\n  'element-plus/es/components/tree/style/css',\n  'element-plus/es/components/dropdown-menu/style/css',\n  'element-plus/es/components/dropdown-item/style/css',\n  'element-plus/es/components/badge/style/css',\n  'element-plus/es/components/breadcrumb/style/css',\n  'element-plus/es/components/breadcrumb-item/style/css',\n  'element-plus/es/components/image/style/css',\n  'element-plus/es/components/collapse-transition/style/css',\n  'element-plus/es/components/timeline/style/css',\n  'element-plus/es/components/timeline-item/style/css',\n  'element-plus/es/components/collapse/style/css',\n  'element-plus/es/components/collapse-item/style/css',\n  'element-plus/es/components/button-group/style/css',\n  'element-plus/es/components/text/style/css'\n]\n\nconst exclude = ['@iconify/json']\n\nexport { include, exclude }\n"
  },
  {
    "path": "yshop-drink-vue3/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" href=\"/favicon.ico\" />\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <meta\n      name=\"keywords\"\n      content=\"yshop管理系统 基于 vue3 + CompositionAPI + typescript + vite3 + element plus 的后台开源免费管理系统！\"\n    />\n    <meta\n      name=\"description\"\n      content=\"yshop管理系统 基于 vue3 + CompositionAPI + typescript + vite3 + element plus 的后台开源免费管理系统！\"\n    />\n    <title>%VITE_APP_TITLE%</title>\n  </head>\n  <body>\n    <div id=\"app\">\n      <style>\n        .app-loading {\n          display: flex;\n          width: 100%;\n          height: 100%;\n          justify-content: center;\n          align-items: center;\n          flex-direction: column;\n          background: #f0f2f5;\n        }\n\n        .app-loading .app-loading-wrap {\n          position: absolute;\n          top: 50%;\n          left: 50%;\n          display: flex;\n          -webkit-transform: translate3d(-50%, -50%, 0);\n          transform: translate3d(-50%, -50%, 0);\n          justify-content: center;\n          align-items: center;\n          flex-direction: column;\n        }\n\n        .app-loading .app-loading-title {\n          margin-bottom: 30px;\n          font-size: 20px;\n          font-weight: bold;\n          text-align: center;\n        }\n\n        .app-loading .app-loading-logo {\n          width: 100px;\n          margin: 0 auto 15px auto;\n        }\n\n        .app-loading .app-loading-item {\n          position: relative;\n          display: inline-block;\n          width: 60px;\n          height: 60px;\n          vertical-align: middle;\n          border-radius: 50%;\n        }\n\n        .app-loading .app-loading-outter {\n          position: absolute;\n          width: 100%;\n          height: 100%;\n          border: 4px solid #2d8cf0;\n          border-bottom: 0;\n          border-left-color: transparent;\n          border-radius: 50%;\n          animation: loader-outter 1s cubic-bezier(0.42, 0.61, 0.58, 0.41) infinite;\n        }\n\n        .app-loading .app-loading-inner {\n          position: absolute;\n          top: calc(50% - 20px);\n          left: calc(50% - 20px);\n          width: 40px;\n          height: 40px;\n          border: 4px solid #87bdff;\n          border-right: 0;\n          border-top-color: transparent;\n          border-radius: 50%;\n          animation: loader-inner 1s cubic-bezier(0.42, 0.61, 0.58, 0.41) infinite;\n        }\n\n        @-webkit-keyframes loader-outter {\n          0% {\n            -webkit-transform: rotate(0deg);\n            transform: rotate(0deg);\n          }\n\n          100% {\n            -webkit-transform: rotate(360deg);\n            transform: rotate(360deg);\n          }\n        }\n\n        @keyframes loader-outter {\n          0% {\n            -webkit-transform: rotate(0deg);\n            transform: rotate(0deg);\n          }\n\n          100% {\n            -webkit-transform: rotate(360deg);\n            transform: rotate(360deg);\n          }\n        }\n\n        @-webkit-keyframes loader-inner {\n          0% {\n            -webkit-transform: rotate(0deg);\n            transform: rotate(0deg);\n          }\n\n          100% {\n            -webkit-transform: rotate(-360deg);\n            transform: rotate(-360deg);\n          }\n        }\n\n        @keyframes loader-inner {\n          0% {\n            -webkit-transform: rotate(0deg);\n            transform: rotate(0deg);\n          }\n\n          100% {\n            -webkit-transform: rotate(-360deg);\n            transform: rotate(-360deg);\n          }\n        }\n      </style>\n      <div class=\"app-loading\">\n        <div class=\"app-loading-wrap\">\n          <div class=\"app-loading-title\">\n            <img src=\"/logo.gif\" class=\"app-loading-logo\" alt=\"Logo\" />\n            <div class=\"app-loading-title\">%VITE_APP_TITLE%</div>\n          </div>\n          <div class=\"app-loading-item\">\n            <div class=\"app-loading-outter\"></div>\n            <div class=\"app-loading-inner\"></div>\n          </div>\n        </div>\n      </div>\n    </div>\n    <script type=\"module\" src=\"/src/main.ts\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "yshop-drink-vue3/package.json",
    "content": "{\n  \"name\": \"yshop-drink-vue3\",\n  \"version\": \"3.0.0\",\n  \"description\": \"基于vue3、vite4、element-plus、typesScript\",\n  \"author\": \"yshop\",\n  \"private\": false,\n  \"scripts\": {\n    \"i\": \"pnpm install\",\n    \"dev\": \"vite\",\n    \"dev-server\": \"vite --mode dev\",\n    \"ts:check\": \"vue-tsc --noEmit\",\n    \"build:local\": \"node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build\",\n    \"build:dev\": \"node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode dev\",\n    \"build:test\": \"node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode test\",\n    \"build:stage\": \"node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode stage\",\n    \"build:prod\": \"node --max_old_space_size=8192 ./node_modules/vite/bin/vite.js build --mode prod\",\n    \"serve:dev\": \"vite preview --mode dev\",\n    \"serve:prod\": \"vite preview --mode prod\",\n    \"preview\": \"pnpm build:local && vite preview\",\n    \"clean\": \"npx rimraf node_modules\",\n    \"clean:cache\": \"npx rimraf node_modules/.cache\",\n    \"lint:eslint\": \"eslint --fix --ext .js,.ts,.vue ./src\",\n    \"lint:format\": \"prettier --write --loglevel warn \\\"src/**/*.{js,ts,json,tsx,css,less,scss,vue,html,md}\\\"\",\n    \"lint:style\": \"stylelint --fix \\\"./src/**/*.{vue,less,postcss,css,scss}\\\" --cache --cache-location node_modules/.cache/stylelint/\",\n    \"lint:lint-staged\": \"lint-staged -c \"\n  },\n  \"dependencies\": {\n    \"@element-plus/icons-vue\": \"^2.1.0\",\n    \"@form-create/designer\": \"^3.1.3\",\n    \"@form-create/element-ui\": \"^3.1.24\",\n    \"@iconify/iconify\": \"^3.1.1\",\n    \"@videojs-player/vue\": \"^1.0.0\",\n    \"@vueuse/core\": \"^10.9.0\",\n    \"@wangeditor/editor\": \"^5.1.23\",\n    \"@wangeditor/editor-for-vue\": \"^5.1.10\",\n    \"@zxcvbn-ts/core\": \"^3.0.4\",\n    \"animate.css\": \"^4.1.1\",\n    \"axios\": \"^1.6.8\",\n    \"benz-amr-recorder\": \"^1.1.5\",\n    \"bpmn-js-token-simulation\": \"^0.10.0\",\n    \"camunda-bpmn-moddle\": \"^7.0.1\",\n    \"cropperjs\": \"^1.6.1\",\n    \"crypto-js\": \"^4.2.0\",\n    \"dayjs\": \"^1.11.10\",\n    \"diagram-js\": \"^12.8.0\",\n    \"driver.js\": \"^1.3.1\",\n    \"echarts\": \"^5.5.0\",\n    \"echarts-wordcloud\": \"^2.1.0\",\n    \"element-plus\": \"2.6.1\",\n    \"fast-xml-parser\": \"^4.3.2\",\n    \"highlight.js\": \"^11.9.0\",\n    \"jsencrypt\": \"^3.3.2\",\n    \"lodash-es\": \"^4.17.21\",\n    \"min-dash\": \"^4.1.1\",\n    \"mitt\": \"^3.0.1\",\n    \"nprogress\": \"^0.2.0\",\n    \"pinia\": \"^2.1.7\",\n    \"pinia-plugin-persistedstate\": \"^3.2.1\",\n    \"qrcode\": \"^1.5.3\",\n    \"qs\": \"^6.12.0\",\n    \"steady-xml\": \"^0.1.0\",\n    \"url\": \"^0.11.3\",\n    \"video.js\": \"^7.21.5\",\n    \"vue\": \"3.4.21\",\n    \"vue-dompurify-html\": \"^4.1.4\",\n    \"vue-i18n\": \"9.10.2\",\n    \"vue-router\": \"^4.3.0\",\n    \"vue-types\": \"^5.1.1\",\n    \"vuedraggable\": \"^4.1.0\",\n    \"web-storage-cache\": \"^1.1.1\",\n    \"xml-js\": \"^1.6.11\",\n    \"vue-ueditor-wrap\": \"^3.0.8\",\n    \"vue-baidu-map-3x\": \"^1.0.34\"\n  },\n  \"devDependencies\": {\n    \"@commitlint/cli\": \"^19.0.1\",\n    \"@commitlint/config-conventional\": \"^19.0.0\",\n    \"@iconify/json\": \"^2.2.187\",\n    \"@intlify/unplugin-vue-i18n\": \"^2.0.0\",\n    \"@purge-icons/generated\": \"^0.9.0\",\n    \"@types/lodash-es\": \"^4.17.12\",\n    \"@types/node\": \"^20.11.21\",\n    \"@types/nprogress\": \"^0.2.3\",\n    \"@types/qrcode\": \"^1.5.5\",\n    \"@types/qs\": \"^6.9.12\",\n    \"@typescript-eslint/eslint-plugin\": \"^7.1.0\",\n    \"@typescript-eslint/parser\": \"^7.1.0\",\n    \"@unocss/transformer-variant-group\": \"^0.58.5\",\n    \"@unocss/eslint-config\": \"^0.57.4\",\n    \"@vitejs/plugin-legacy\": \"^5.3.1\",\n    \"@vitejs/plugin-vue\": \"^5.0.4\",\n    \"@vitejs/plugin-vue-jsx\": \"^3.1.0\",\n    \"autoprefixer\": \"^10.4.17\",\n    \"bpmn-js\": \"8.9.0\",\n    \"bpmn-js-properties-panel\": \"0.46.0\",\n    \"consola\": \"^3.2.3\",\n    \"eslint\": \"^8.57.0\",\n    \"eslint-config-prettier\": \"^9.1.0\",\n    \"eslint-define-config\": \"^2.1.0\",\n    \"eslint-plugin-prettier\": \"^5.1.3\",\n    \"eslint-plugin-vue\": \"^9.22.0\",\n    \"lint-staged\": \"^15.2.2\",\n    \"postcss\": \"^8.4.35\",\n    \"postcss-html\": \"^1.6.0\",\n    \"postcss-scss\": \"^4.0.9\",\n    \"prettier\": \"^3.2.5\",\n    \"prettier-eslint\": \"^16.3.0\",\n    \"rimraf\": \"^5.0.5\",\n    \"rollup\": \"^4.12.0\",\n    \"sass\": \"^1.69.5\",\n    \"stylelint\": \"^16.2.1\",\n    \"stylelint-config-html\": \"^1.1.0\",\n    \"stylelint-config-recommended\": \"^14.0.0\",\n    \"stylelint-config-standard\": \"^36.0.0\",\n    \"stylelint-order\": \"^6.0.4\",\n    \"terser\": \"^5.28.1\",\n    \"typescript\": \"5.3.3\",\n    \"unocss\": \"^0.58.5\",\n    \"unplugin-auto-import\": \"^0.16.7\",\n    \"unplugin-element-plus\": \"^0.8.0\",\n    \"unplugin-vue-components\": \"^0.25.2\",\n    \"vite\": \"5.1.4\",\n    \"vite-plugin-compression\": \"^0.5.1\",\n    \"vite-plugin-ejs\": \"^1.7.0\",\n    \"vite-plugin-eslint\": \"^1.8.1\",\n    \"vite-plugin-progress\": \"^0.0.7\",\n    \"vite-plugin-purge-icons\": \"^0.10.0\",\n    \"vite-plugin-svg-icons\": \"^2.0.1\",\n    \"vite-plugin-top-level-await\": \"^1.3.1\",\n    \"vue-eslint-parser\": \"^9.3.2\",\n    \"vue-tsc\": \"^1.8.27\"\n  },\n  \"license\": \"MIT\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://gitee.com/guchengwuyue/yshop-drink\"\n  },\n  \"bugs\": {\n    \"url\": \"https://gitee.com/guchengwuyue/yshop-drink\"\n  },\n  \"homepage\": \"https://gitee.com/guchengwuyue/yshop-drink\",\n  \"engines\": {\n    \"node\": \">= 16.0.0\",\n    \"pnpm\": \">=8.6.0\"\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/postcss.config.js",
    "content": "module.exports = {\n  plugins: {\n    autoprefixer: {}\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/prettier.config.js",
    "content": "module.exports = {\n  printWidth: 100, // 每行代码长度（默认80）\n  tabWidth: 2, // 每个tab相当于多少个空格（默认2）ab进行缩进（默认false）\n  useTabs: false, // 是否使用tab\n  semi: false, // 声明结尾使用分号(默认true)\n  vueIndentScriptAndStyle: false,\n  singleQuote: true, // 使用单引号（默认false）\n  quoteProps: 'as-needed',\n  bracketSpacing: true, // 对象字面量的大括号间使用空格（默认true）\n  trailingComma: 'none', // 多行使用拖尾逗号（默认none）\n  jsxSingleQuote: false,\n  // 箭头函数参数括号 默认avoid 可选 avoid| always\n  // avoid 能省略括号的时候就省略 例如x => x\n  // always 总是有括号\n  arrowParens: 'always',\n  insertPragma: false,\n  requirePragma: false,\n  proseWrap: 'never',\n  htmlWhitespaceSensitivity: 'strict',\n  endOfLine: 'auto',\n  rangeStart: 0\n}\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/anchor/anchor.html",
    "content": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\r\n    \"http://www.w3.org/TR/html4/loose.dtd\">\r\n<html>\r\n    <head>\r\n        <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\r\n        <title></title>\r\n        <style type=\"text/css\">\r\n            *{color: #838383;margin: 0;padding: 0}\r\n            html,body {font-size: 12px;overflow: hidden; }\r\n            .content{padding:5px 0 0 15px;}\r\n            input{width:210px;height:21px;line-height:21px;margin-left: 4px;}\r\n        </style>\r\n    </head>\r\n    <body>\r\n        <div class=\"content\">\r\n            <span><var id=\"lang_input_anchorName\"></var></span><input id=\"anchorName\"  value=\"\" />\r\n        </div>\r\n        <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n        <script type=\"text/javascript\">\r\n            var anchorInput = $G('anchorName'),\r\n                node = editor.selection.getRange().getClosedNode();\r\n            if(node && node.tagName == 'IMG' && (node = node.getAttribute('anchorname'))){\r\n                anchorInput.value = node;\r\n            }\r\n            anchorInput.onkeydown = function(evt){\r\n                evt = evt || window.event;\r\n                if(evt.keyCode == 13){\r\n                    editor.execCommand('anchor', anchorInput.value);\r\n                    dialog.close();\r\n                    domUtils.preventDefault(evt)\r\n                }\r\n            };\r\n            dialog.onok = function (){\r\n                editor.execCommand('anchor', anchorInput.value);\r\n                dialog.close();\r\n            };\r\n            $focus(anchorInput);\r\n        </script>\r\n    </body>\r\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/attachment/attachment.css",
    "content": "@charset \"utf-8\";\r\n/* dialog样式 */\r\n.wrapper {\r\n    zoom: 1;\r\n    width: 630px;\r\n    *width: 626px;\r\n    height: 380px;\r\n    margin: 0 auto;\r\n    padding: 10px;\r\n    position: relative;\r\n    font-family: sans-serif;\r\n}\r\n\r\n/*tab样式框大小*/\r\n.tabhead {\r\n    float:left;\r\n}\r\n.tabbody {\r\n    width: 100%;\r\n    height: 346px;\r\n    position: relative;\r\n    clear: both;\r\n}\r\n\r\n.tabbody .panel {\r\n    position: absolute;\r\n    width: 0;\r\n    height: 0;\r\n    background: #fff;\r\n    overflow: hidden;\r\n    display: none;\r\n}\r\n\r\n.tabbody .panel.focus {\r\n    width: 100%;\r\n    height: 346px;\r\n    display: block;\r\n}\r\n\r\n/* 上传附件 */\r\n.tabbody #upload.panel {\r\n    width: 0;\r\n    height: 0;\r\n    overflow: hidden;\r\n    position: absolute !important;\r\n    clip: rect(1px, 1px, 1px, 1px);\r\n    background: #fff;\r\n    display: block;\r\n}\r\n\r\n.tabbody #upload.panel.focus {\r\n    width: 100%;\r\n    height: 346px;\r\n    display: block;\r\n    clip: auto;\r\n}\r\n\r\n#upload .queueList {\r\n    margin: 0;\r\n    width: 100%;\r\n    height: 100%;\r\n    position: absolute;\r\n    overflow: hidden;\r\n}\r\n\r\n#upload p {\r\n    margin: 0;\r\n}\r\n\r\n.element-invisible {\r\n    width: 0 !important;\r\n    height: 0 !important;\r\n    border: 0;\r\n    padding: 0;\r\n    margin: 0;\r\n    overflow: hidden;\r\n    position: absolute !important;\r\n    clip: rect(1px, 1px, 1px, 1px);\r\n}\r\n\r\n#upload .placeholder {\r\n    margin: 10px;\r\n    border: 2px dashed #e6e6e6;\r\n    *border: 0px dashed #e6e6e6;\r\n    height: 172px;\r\n    padding-top: 150px;\r\n    text-align: center;\r\n    background: url(./images/image.png) center 70px no-repeat;\r\n    color: #cccccc;\r\n    font-size: 18px;\r\n    position: relative;\r\n    top:0;\r\n    *top: 10px;\r\n}\r\n\r\n#upload .placeholder .webuploader-pick {\r\n    font-size: 18px;\r\n    background: #00b7ee;\r\n    border-radius: 3px;\r\n    line-height: 44px;\r\n    padding: 0 30px;\r\n    *width: 120px;\r\n    color: #fff;\r\n    display: inline-block;\r\n    margin: 0 auto 20px auto;\r\n    cursor: pointer;\r\n    box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);\r\n}\r\n\r\n#upload .placeholder .webuploader-pick-hover {\r\n    background: #00a2d4;\r\n}\r\n\r\n\r\n#filePickerContainer {\r\n    text-align: center;\r\n}\r\n\r\n#upload .placeholder .flashTip {\r\n    color: #666666;\r\n    font-size: 12px;\r\n    position: absolute;\r\n    width: 100%;\r\n    text-align: center;\r\n    bottom: 20px;\r\n}\r\n\r\n#upload .placeholder .flashTip a {\r\n    color: #0785d1;\r\n    text-decoration: none;\r\n}\r\n\r\n#upload .placeholder .flashTip a:hover {\r\n    text-decoration: underline;\r\n}\r\n\r\n#upload .placeholder.webuploader-dnd-over {\r\n    border-color: #999999;\r\n}\r\n\r\n#upload .filelist {\r\n    list-style: none;\r\n    margin: 0;\r\n    padding: 0;\r\n    overflow-x: hidden;\r\n    overflow-y: auto;\r\n    position: relative;\r\n    height: 300px;\r\n}\r\n\r\n#upload .filelist:after {\r\n    content: '';\r\n    display: block;\r\n    width: 0;\r\n    height: 0;\r\n    overflow: hidden;\r\n    clear: both;\r\n}\r\n\r\n#upload .filelist li {\r\n    width: 113px;\r\n    height: 113px;\r\n    background: url(./images/bg.png);\r\n    text-align: center;\r\n    margin: 9px 0 0 9px;\r\n    *margin: 6px 0 0 6px;\r\n    position: relative;\r\n    display: block;\r\n    float: left;\r\n    overflow: hidden;\r\n    font-size: 12px;\r\n}\r\n\r\n#upload .filelist li p.log {\r\n    position: relative;\r\n    top: -45px;\r\n}\r\n\r\n#upload .filelist li p.title {\r\n    position: absolute;\r\n    top: 0;\r\n    left: 0;\r\n    width: 100%;\r\n    overflow: hidden;\r\n    white-space: nowrap;\r\n    text-overflow: ellipsis;\r\n    top: 5px;\r\n    text-indent: 5px;\r\n    text-align: left;\r\n}\r\n\r\n#upload .filelist li p.progress {\r\n    position: absolute;\r\n    width: 100%;\r\n    bottom: 0;\r\n    left: 0;\r\n    height: 8px;\r\n    overflow: hidden;\r\n    z-index: 50;\r\n    margin: 0;\r\n    border-radius: 0;\r\n    background: none;\r\n    -webkit-box-shadow: 0 0 0;\r\n}\r\n\r\n#upload .filelist li p.progress span {\r\n    display: none;\r\n    overflow: hidden;\r\n    width: 0;\r\n    height: 100%;\r\n    background: #1483d8 url(./images/progress.png) repeat-x;\r\n\r\n    -webit-transition: width 200ms linear;\r\n    -moz-transition: width 200ms linear;\r\n    -o-transition: width 200ms linear;\r\n    -ms-transition: width 200ms linear;\r\n    transition: width 200ms linear;\r\n\r\n    -webkit-animation: progressmove 2s linear infinite;\r\n    -moz-animation: progressmove 2s linear infinite;\r\n    -o-animation: progressmove 2s linear infinite;\r\n    -ms-animation: progressmove 2s linear infinite;\r\n    animation: progressmove 2s linear infinite;\r\n\r\n    -webkit-transform: translateZ(0);\r\n}\r\n\r\n@-webkit-keyframes progressmove {\r\n    0% {\r\n        background-position: 0 0;\r\n    }\r\n    100% {\r\n        background-position: 17px 0;\r\n    }\r\n}\r\n\r\n@-moz-keyframes progressmove {\r\n    0% {\r\n        background-position: 0 0;\r\n    }\r\n    100% {\r\n        background-position: 17px 0;\r\n    }\r\n}\r\n\r\n@keyframes progressmove {\r\n    0% {\r\n        background-position: 0 0;\r\n    }\r\n    100% {\r\n        background-position: 17px 0;\r\n    }\r\n}\r\n\r\n#upload .filelist li p.imgWrap {\r\n    position: relative;\r\n    z-index: 2;\r\n    line-height: 113px;\r\n    vertical-align: middle;\r\n    overflow: hidden;\r\n    width: 113px;\r\n    height: 113px;\r\n\r\n    -webkit-transform-origin: 50% 50%;\r\n    -moz-transform-origin: 50% 50%;\r\n    -o-transform-origin: 50% 50%;\r\n    -ms-transform-origin: 50% 50%;\r\n    transform-origin: 50% 50%;\r\n\r\n    -webit-transition: 200ms ease-out;\r\n    -moz-transition: 200ms ease-out;\r\n    -o-transition: 200ms ease-out;\r\n    -ms-transition: 200ms ease-out;\r\n    transition: 200ms ease-out;\r\n}\r\n#upload .filelist li p.imgWrap.notimage {\r\n    margin-top: 0;\r\n    width: 111px;\r\n    height: 111px;\r\n    border: 1px #eeeeee solid;\r\n}\r\n#upload .filelist li p.imgWrap.notimage i.file-preview {\r\n    margin-top: 15px;\r\n}\r\n\r\n#upload .filelist li img {\r\n    width: 100%;\r\n}\r\n\r\n#upload .filelist li p.error {\r\n    background: #f43838;\r\n    color: #fff;\r\n    position: absolute;\r\n    bottom: 0;\r\n    left: 0;\r\n    height: 28px;\r\n    line-height: 28px;\r\n    width: 100%;\r\n    z-index: 100;\r\n    display:none;\r\n}\r\n\r\n#upload .filelist li .success {\r\n    display: block;\r\n    position: absolute;\r\n    left: 0;\r\n    bottom: 0;\r\n    height: 40px;\r\n    width: 100%;\r\n    z-index: 200;\r\n    background: url(./images/success.png) no-repeat right bottom;\r\n    background-image: url(./images/success.gif) \\9;\r\n}\r\n\r\n#upload .filelist li.filePickerBlock {\r\n    width: 113px;\r\n    height: 113px;\r\n    background: url(./images/image.png) no-repeat center 12px;\r\n    border: 1px solid #eeeeee;\r\n    border-radius: 0;\r\n}\r\n#upload .filelist li.filePickerBlock div.webuploader-pick  {\r\n    width: 100%;\r\n    height: 100%;\r\n    margin: 0;\r\n    padding: 0;\r\n    opacity: 0;\r\n    background: none;\r\n    font-size: 0;\r\n}\r\n\r\n#upload .filelist div.file-panel {\r\n    position: absolute;\r\n    height: 0;\r\n    filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr='#80000000', endColorstr='#80000000') \\0;\r\n    background: rgba(0, 0, 0, 0.5);\r\n    width: 100%;\r\n    top: 0;\r\n    left: 0;\r\n    overflow: hidden;\r\n    z-index: 300;\r\n}\r\n\r\n#upload .filelist div.file-panel span {\r\n    width: 24px;\r\n    height: 24px;\r\n    display: inline;\r\n    float: right;\r\n    text-indent: -9999px;\r\n    overflow: hidden;\r\n    background: url(./images/icons.png) no-repeat;\r\n    background: url(./images/icons.gif) no-repeat \\9;\r\n    margin: 5px 1px 1px;\r\n    cursor: pointer;\r\n    -webkit-tap-highlight-color: rgba(0,0,0,0);\r\n    -webkit-user-select: none;\r\n    -moz-user-select: none;\r\n    -ms-user-select: none;\r\n    user-select: none;\r\n}\r\n\r\n#upload .filelist div.file-panel span.rotateLeft {\r\n    display:none;\r\n    background-position: 0 -24px;\r\n}\r\n\r\n#upload .filelist div.file-panel span.rotateLeft:hover {\r\n    background-position: 0 0;\r\n}\r\n\r\n#upload .filelist div.file-panel span.rotateRight {\r\n    display:none;\r\n    background-position: -24px -24px;\r\n}\r\n\r\n#upload .filelist div.file-panel span.rotateRight:hover {\r\n    background-position: -24px 0;\r\n}\r\n\r\n#upload .filelist div.file-panel span.cancel {\r\n    background-position: -48px -24px;\r\n}\r\n\r\n#upload .filelist div.file-panel span.cancel:hover {\r\n    background-position: -48px 0;\r\n}\r\n\r\n#upload .statusBar {\r\n    height: 45px;\r\n    border-bottom: 1px solid #dadada;\r\n    margin: 0 10px;\r\n    padding: 0;\r\n    line-height: 45px;\r\n    vertical-align: middle;\r\n    position: relative;\r\n}\r\n\r\n#upload .statusBar .progress {\r\n    border: 1px solid #1483d8;\r\n    width: 198px;\r\n    background: #fff;\r\n    height: 18px;\r\n    position: absolute;\r\n    top: 12px;\r\n    display: none;\r\n    text-align: center;\r\n    line-height: 18px;\r\n    color: #6dbfff;\r\n    margin: 0 10px 0 0;\r\n}\r\n#upload .statusBar .progress span.percentage {\r\n    width: 0;\r\n    height: 100%;\r\n    left: 0;\r\n    top: 0;\r\n    background: #1483d8;\r\n    position: absolute;\r\n}\r\n#upload .statusBar .progress span.text {\r\n    position: relative;\r\n    z-index: 10;\r\n}\r\n\r\n#upload .statusBar .info {\r\n    display: inline-block;\r\n    font-size: 14px;\r\n    color: #666666;\r\n}\r\n\r\n#upload .statusBar .btns {\r\n    position: absolute;\r\n    top: 7px;\r\n    right: 0;\r\n    line-height: 30px;\r\n}\r\n\r\n#filePickerBtn {\r\n    display: inline-block;\r\n    float: left;\r\n}\r\n#upload .statusBar .btns .webuploader-pick,\r\n#upload .statusBar .btns .uploadBtn,\r\n#upload .statusBar .btns .uploadBtn.state-uploading,\r\n#upload .statusBar .btns .uploadBtn.state-paused {\r\n    background: #ffffff;\r\n    border: 1px solid #cfcfcf;\r\n    color: #565656;\r\n    padding: 0 18px;\r\n    display: inline-block;\r\n    border-radius: 3px;\r\n    margin-left: 10px;\r\n    cursor: pointer;\r\n    font-size: 14px;\r\n    float: left;\r\n    -webkit-user-select: none;\r\n    -moz-user-select: none;\r\n    -ms-user-select: none;\r\n    user-select: none;\r\n}\r\n#upload .statusBar .btns .webuploader-pick-hover,\r\n#upload .statusBar .btns .uploadBtn:hover,\r\n#upload .statusBar .btns .uploadBtn.state-uploading:hover,\r\n#upload .statusBar .btns .uploadBtn.state-paused:hover {\r\n    background: #f0f0f0;\r\n}\r\n\r\n#upload .statusBar .btns .uploadBtn,\r\n#upload .statusBar .btns .uploadBtn.state-paused{\r\n    background: #00b7ee;\r\n    color: #fff;\r\n    border-color: transparent;\r\n}\r\n#upload .statusBar .btns .uploadBtn:hover,\r\n#upload .statusBar .btns .uploadBtn.state-paused:hover{\r\n    background: #00a2d4;\r\n}\r\n\r\n#upload .statusBar .btns .uploadBtn.disabled {\r\n    pointer-events: none;\r\n    filter:alpha(opacity=60);\r\n    -moz-opacity:0.6;\r\n    -khtml-opacity: 0.6;\r\n    opacity: 0.6;\r\n}\r\n\r\n\r\n\r\n/* 图片管理样式 */\r\n#online {\r\n    width: 100%;\r\n    height: 336px;\r\n    padding: 10px 0 0 0;\r\n}\r\n#online #fileList{\r\n    width: 100%;\r\n    height: 100%;\r\n    overflow-x: hidden;\r\n    overflow-y: auto;\r\n    position: relative;\r\n}\r\n#online ul {\r\n    display: block;\r\n    list-style: none;\r\n    margin: 0;\r\n    padding: 0;\r\n}\r\n#online li {\r\n    float: left;\r\n    display: block;\r\n    list-style: none;\r\n    padding: 0;\r\n    width: 113px;\r\n    height: 113px;\r\n    margin: 0 0 9px 9px;\r\n    *margin: 0 0 6px 6px;\r\n    background-color: #eee;\r\n    overflow: hidden;\r\n    cursor: pointer;\r\n    position: relative;\r\n}\r\n#online li.clearFloat {\r\n    float: none;\r\n    clear: both;\r\n    display: block;\r\n    width:0;\r\n    height:0;\r\n    margin: 0;\r\n    padding: 0;\r\n}\r\n#online li img {\r\n    cursor: pointer;\r\n}\r\n#online li div.file-wrapper {\r\n    cursor: pointer;\r\n    position: absolute;\r\n    display: block;\r\n    width: 111px;\r\n    height: 111px;\r\n    border: 1px solid #eee;\r\n    background: url(\"./images/bg.png\") repeat;\r\n}\r\n#online li div span.file-title{\r\n    display: block;\r\n    padding: 0 3px;\r\n    margin: 3px 0 0 0;\r\n    font-size: 12px;\r\n    height: 13px;\r\n    color: #555555;\r\n    text-align: center;\r\n    width: 107px;\r\n    white-space: nowrap;\r\n    word-break: break-all;\r\n    overflow: hidden;\r\n    text-overflow: ellipsis;\r\n}\r\n#online li .icon {\r\n    cursor: pointer;\r\n    width: 113px;\r\n    height: 113px;\r\n    position: absolute;\r\n    top: 0;\r\n    left: 0;\r\n    z-index: 2;\r\n    border: 0;\r\n    background-repeat: no-repeat;\r\n}\r\n#online li .icon:hover {\r\n    width: 107px;\r\n    height: 107px;\r\n    border: 3px solid #1094fa;\r\n}\r\n#online li.selected .icon {\r\n    background-image: url(images/success.png);\r\n    background-image: url(images/success.gif) \\9;\r\n    background-position: 75px 75px;\r\n}\r\n#online li.selected .icon:hover {\r\n    width: 107px;\r\n    height: 107px;\r\n    border: 3px solid #1094fa;\r\n    background-position: 72px 72px;\r\n}\r\n\r\n\r\n/* 在线文件的文件预览图标 */\r\ni.file-preview {\r\n    display: block;\r\n    margin: 10px auto;\r\n    width: 70px;\r\n    height: 70px;\r\n    background-image: url(\"./images/file-icons.png\");\r\n    background-image: url(\"./images/file-icons.gif\") \\9;\r\n    background-position: -140px center;\r\n    background-repeat: no-repeat;\r\n}\r\ni.file-preview.file-type-dir{\r\n    background-position: 0 center;\r\n}\r\ni.file-preview.file-type-file{\r\n    background-position: -140px center;\r\n}\r\ni.file-preview.file-type-filelist{\r\n    background-position: -210px center;\r\n}\r\ni.file-preview.file-type-zip,\r\ni.file-preview.file-type-rar,\r\ni.file-preview.file-type-7z,\r\ni.file-preview.file-type-tar,\r\ni.file-preview.file-type-gz,\r\ni.file-preview.file-type-bz2{\r\n    background-position: -280px center;\r\n}\r\ni.file-preview.file-type-xls,\r\ni.file-preview.file-type-xlsx{\r\n    background-position: -350px center;\r\n}\r\ni.file-preview.file-type-doc,\r\ni.file-preview.file-type-docx{\r\n    background-position: -420px center;\r\n}\r\ni.file-preview.file-type-ppt,\r\ni.file-preview.file-type-pptx{\r\n    background-position: -490px center;\r\n}\r\ni.file-preview.file-type-vsd{\r\n    background-position: -560px center;\r\n}\r\ni.file-preview.file-type-pdf{\r\n    background-position: -630px center;\r\n}\r\ni.file-preview.file-type-txt,\r\ni.file-preview.file-type-md,\r\ni.file-preview.file-type-json,\r\ni.file-preview.file-type-htm,\r\ni.file-preview.file-type-xml,\r\ni.file-preview.file-type-html,\r\ni.file-preview.file-type-js,\r\ni.file-preview.file-type-css,\r\ni.file-preview.file-type-php,\r\ni.file-preview.file-type-jsp,\r\ni.file-preview.file-type-asp{\r\n    background-position: -700px center;\r\n}\r\ni.file-preview.file-type-apk{\r\n    background-position: -770px center;\r\n}\r\ni.file-preview.file-type-exe{\r\n    background-position: -840px center;\r\n}\r\ni.file-preview.file-type-ipa{\r\n    background-position: -910px center;\r\n}\r\ni.file-preview.file-type-mp4,\r\ni.file-preview.file-type-swf,\r\ni.file-preview.file-type-mkv,\r\ni.file-preview.file-type-avi,\r\ni.file-preview.file-type-flv,\r\ni.file-preview.file-type-mov,\r\ni.file-preview.file-type-mpg,\r\ni.file-preview.file-type-mpeg,\r\ni.file-preview.file-type-ogv,\r\ni.file-preview.file-type-webm,\r\ni.file-preview.file-type-rm,\r\ni.file-preview.file-type-rmvb{\r\n    background-position: -980px center;\r\n}\r\ni.file-preview.file-type-ogg,\r\ni.file-preview.file-type-wav,\r\ni.file-preview.file-type-wmv,\r\ni.file-preview.file-type-mid,\r\ni.file-preview.file-type-mp3{\r\n    background-position: -1050px center;\r\n}\r\ni.file-preview.file-type-jpg,\r\ni.file-preview.file-type-jpeg,\r\ni.file-preview.file-type-gif,\r\ni.file-preview.file-type-bmp,\r\ni.file-preview.file-type-png,\r\ni.file-preview.file-type-psd{\r\n    background-position: -140px center;\r\n}"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/attachment/attachment.html",
    "content": "<!doctype html>\r\n<html>\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <title>ueditor图片对话框</title>\r\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n\r\n    <!-- jquery -->\r\n    <script type=\"text/javascript\" src=\"../../third-party/jquery-1.10.2.min.js\"></script>\r\n\r\n    <!-- webuploader -->\r\n    <script src=\"../../third-party/webuploader/webuploader.min.js\"></script>\r\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"../../third-party/webuploader/webuploader.css\">\r\n\r\n    <!-- attachment dialog -->\r\n    <link rel=\"stylesheet\" href=\"attachment.css\" type=\"text/css\" />\r\n</head>\r\n<body>\r\n\r\n    <div class=\"wrapper\">\r\n        <div id=\"tabhead\" class=\"tabhead\">\r\n            <span class=\"tab focus\" data-content-id=\"upload\"><var id=\"lang_tab_upload\"></var></span>\r\n            <span class=\"tab\" data-content-id=\"online\"><var id=\"lang_tab_online\"></var></span>\r\n        </div>\r\n        <div id=\"tabbody\" class=\"tabbody\">\r\n            <!-- 上传图片 -->\r\n            <div id=\"upload\" class=\"panel focus\">\r\n                <div id=\"queueList\" class=\"queueList\">\r\n                    <div class=\"statusBar element-invisible\">\r\n                        <div class=\"progress\">\r\n                            <span class=\"text\">0%</span>\r\n                            <span class=\"percentage\"></span>\r\n                        </div><div class=\"info\"></div>\r\n                        <div class=\"btns\">\r\n                            <div id=\"filePickerBtn\"></div>\r\n                            <div class=\"uploadBtn\"><var id=\"lang_start_upload\"></var></div>\r\n                        </div>\r\n                    </div>\r\n                    <div id=\"dndArea\" class=\"placeholder\">\r\n                        <div class=\"filePickerContainer\">\r\n                            <div id=\"filePickerReady\"></div>\r\n                        </div>\r\n                    </div>\r\n                    <ul class=\"filelist element-invisible\">\r\n                        <li id=\"filePickerBlock\" class=\"filePickerBlock\"></li>\r\n                    </ul>\r\n                </div>\r\n            </div>\r\n\r\n            <!-- 在线图片 -->\r\n            <div id=\"online\" class=\"panel\">\r\n                <div id=\"fileList\"><var id=\"lang_imgLoading\"></var></div>\r\n            </div>\r\n\r\n        </div>\r\n    </div>\r\n    <script type=\"text/javascript\" src=\"attachment.js\"></script>\r\n\r\n</body>\r\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/attachment/attachment.js",
    "content": "/**\r\n * User: Jinqn\r\n * Date: 14-04-08\r\n * Time: 下午16:34\r\n * 上传图片对话框逻辑代码,包括tab: 远程图片/上传图片/在线图片/搜索图片\r\n */\r\n\r\n(function () {\r\n\r\n    var uploadFile,\r\n        onlineFile;\r\n\r\n    window.onload = function () {\r\n        initTabs();\r\n        initButtons();\r\n    };\r\n\r\n    /* 初始化tab标签 */\r\n    function initTabs() {\r\n        var tabs = $G('tabhead').children;\r\n        for (var i = 0; i < tabs.length; i++) {\r\n            domUtils.on(tabs[i], \"click\", function (e) {\r\n                var target = e.target || e.srcElement;\r\n                setTabFocus(target.getAttribute('data-content-id'));\r\n            });\r\n        }\r\n\r\n        setTabFocus('upload');\r\n    }\r\n\r\n    /* 初始化tabbody */\r\n    function setTabFocus(id) {\r\n        if(!id) return;\r\n        var i, bodyId, tabs = $G('tabhead').children;\r\n        for (i = 0; i < tabs.length; i++) {\r\n            bodyId = tabs[i].getAttribute('data-content-id')\r\n            if (bodyId == id) {\r\n                domUtils.addClass(tabs[i], 'focus');\r\n                domUtils.addClass($G(bodyId), 'focus');\r\n            } else {\r\n                domUtils.removeClasses(tabs[i], 'focus');\r\n                domUtils.removeClasses($G(bodyId), 'focus');\r\n            }\r\n        }\r\n        switch (id) {\r\n            case 'upload':\r\n                uploadFile = uploadFile || new UploadFile('queueList');\r\n                break;\r\n            case 'online':\r\n                onlineFile = onlineFile || new OnlineFile('fileList');\r\n                break;\r\n        }\r\n    }\r\n\r\n    /* 初始化onok事件 */\r\n    function initButtons() {\r\n\r\n        dialog.onok = function () {\r\n            var list = [], id, tabs = $G('tabhead').children;\r\n            for (var i = 0; i < tabs.length; i++) {\r\n                if (domUtils.hasClass(tabs[i], 'focus')) {\r\n                    id = tabs[i].getAttribute('data-content-id');\r\n                    break;\r\n                }\r\n            }\r\n\r\n            switch (id) {\r\n                case 'upload':\r\n                    list = uploadFile.getInsertList();\r\n                    var count = uploadFile.getQueueCount();\r\n                    if (count) {\r\n                        $('.info', '#queueList').html('<span style=\"color:red;\">' + '还有2个未上传文件'.replace(/[\\d]/, count) + '</span>');\r\n                        return false;\r\n                    }\r\n                    break;\r\n                case 'online':\r\n                    list = onlineFile.getInsertList();\r\n                    break;\r\n            }\r\n\r\n            editor.execCommand('insertfile', list);\r\n        };\r\n    }\r\n\r\n\r\n    /* 上传附件 */\r\n    function UploadFile(target) {\r\n        this.$wrap = target.constructor == String ? $('#' + target) : $(target);\r\n        this.init();\r\n    }\r\n    UploadFile.prototype = {\r\n        init: function () {\r\n            this.fileList = [];\r\n            this.initContainer();\r\n            this.initUploader();\r\n        },\r\n        initContainer: function () {\r\n            this.$queue = this.$wrap.find('.filelist');\r\n        },\r\n        /* 初始化容器 */\r\n        initUploader: function () {\r\n            var _this = this,\r\n                $ = jQuery,    // just in case. Make sure it's not an other libaray.\r\n                $wrap = _this.$wrap,\r\n            // 图片容器\r\n                $queue = $wrap.find('.filelist'),\r\n            // 状态栏，包括进度和控制按钮\r\n                $statusBar = $wrap.find('.statusBar'),\r\n            // 文件总体选择信息。\r\n                $info = $statusBar.find('.info'),\r\n            // 上传按钮\r\n                $upload = $wrap.find('.uploadBtn'),\r\n            // 上传按钮\r\n                $filePickerBtn = $wrap.find('.filePickerBtn'),\r\n            // 上传按钮\r\n                $filePickerBlock = $wrap.find('.filePickerBlock'),\r\n            // 没选择文件之前的内容。\r\n                $placeHolder = $wrap.find('.placeholder'),\r\n            // 总体进度条\r\n                $progress = $statusBar.find('.progress').hide(),\r\n            // 添加的文件数量\r\n                fileCount = 0,\r\n            // 添加的文件总大小\r\n                fileSize = 0,\r\n            // 优化retina, 在retina下这个值是2\r\n                ratio = window.devicePixelRatio || 1,\r\n            // 缩略图大小\r\n                thumbnailWidth = 113 * ratio,\r\n                thumbnailHeight = 113 * ratio,\r\n            // 可能有pedding, ready, uploading, confirm, done.\r\n                state = '',\r\n            // 所有文件的进度信息，key为file id\r\n                percentages = {},\r\n                supportTransition = (function () {\r\n                    var s = document.createElement('p').style,\r\n                        r = 'transition' in s ||\r\n                            'WebkitTransition' in s ||\r\n                            'MozTransition' in s ||\r\n                            'msTransition' in s ||\r\n                            'OTransition' in s;\r\n                    s = null;\r\n                    return r;\r\n                })(),\r\n            // WebUploader实例\r\n                uploader,\r\n                actionUrl = editor.getActionUrl(editor.getOpt('fileActionName')),\r\n                fileMaxSize = editor.getOpt('fileMaxSize'),\r\n                acceptExtensions = (editor.getOpt('fileAllowFiles') || []).join('').replace(/\\./g, ',').replace(/^[,]/, '');;\r\n\r\n            if (!WebUploader.Uploader.support()) {\r\n                $('#filePickerReady').after($('<div>').html(lang.errorNotSupport)).hide();\r\n                return;\r\n            } else if (!editor.getOpt('fileActionName')) {\r\n                $('#filePickerReady').after($('<div>').html(lang.errorLoadConfig)).hide();\r\n                return;\r\n            }\r\n\r\n            uploader = _this.uploader = WebUploader.create({\r\n                pick: {\r\n                    id: '#filePickerReady',\r\n                    label: lang.uploadSelectFile\r\n                },\r\n                swf: '../../third-party/webuploader/Uploader.swf',\r\n                server: actionUrl,\r\n                fileVal: editor.getOpt('fileFieldName'),\r\n                duplicate: true,\r\n                fileSingleSizeLimit: fileMaxSize,\r\n                compress: false\r\n            });\r\n            uploader.addButton({\r\n                id: '#filePickerBlock'\r\n            });\r\n            uploader.addButton({\r\n                id: '#filePickerBtn',\r\n                label: lang.uploadAddFile\r\n            });\r\n\r\n            setState('pedding');\r\n\r\n            // 当有文件添加进来时执行，负责view的创建\r\n            function addFile(file) {\r\n                var $li = $('<li id=\"' + file.id + '\">' +\r\n                        '<p class=\"title\">' + file.name + '</p>' +\r\n                        '<p class=\"imgWrap\"></p>' +\r\n                        '<p class=\"progress\"><span></span></p>' +\r\n                        '</li>'),\r\n\r\n                    $btns = $('<div class=\"file-panel\">' +\r\n                        '<span class=\"cancel\">' + lang.uploadDelete + '</span>' +\r\n                        '<span class=\"rotateRight\">' + lang.uploadTurnRight + '</span>' +\r\n                        '<span class=\"rotateLeft\">' + lang.uploadTurnLeft + '</span></div>').appendTo($li),\r\n                    $prgress = $li.find('p.progress span'),\r\n                    $wrap = $li.find('p.imgWrap'),\r\n                    $info = $('<p class=\"error\"></p>').hide().appendTo($li),\r\n\r\n                    showError = function (code) {\r\n                        switch (code) {\r\n                            case 'exceed_size':\r\n                                text = lang.errorExceedSize;\r\n                                break;\r\n                            case 'interrupt':\r\n                                text = lang.errorInterrupt;\r\n                                break;\r\n                            case 'http':\r\n                                text = lang.errorHttp;\r\n                                break;\r\n                            case 'not_allow_type':\r\n                                text = lang.errorFileType;\r\n                                break;\r\n                            default:\r\n                                text = lang.errorUploadRetry;\r\n                                break;\r\n                        }\r\n                        $info.text(text).show();\r\n                    };\r\n\r\n                if (file.getStatus() === 'invalid') {\r\n                    showError(file.statusText);\r\n                } else {\r\n                    $wrap.text(lang.uploadPreview);\r\n                    if ('|png|jpg|jpeg|bmp|gif|'.indexOf('|'+file.ext.toLowerCase()+'|') == -1) {\r\n                        $wrap.empty().addClass('notimage').append('<i class=\"file-preview file-type-' + file.ext.toLowerCase() + '\"></i>' +\r\n                        '<span class=\"file-title\" title=\"' + file.name + '\">' + file.name + '</span>');\r\n                    } else {\r\n                        if (browser.ie && browser.version <= 7) {\r\n                            $wrap.text(lang.uploadNoPreview);\r\n                        } else {\r\n                            uploader.makeThumb(file, function (error, src) {\r\n                                if (error || !src) {\r\n                                    $wrap.text(lang.uploadNoPreview);\r\n                                } else {\r\n                                    var $img = $('<img src=\"' + src + '\">');\r\n                                    $wrap.empty().append($img);\r\n                                    $img.on('error', function () {\r\n                                        $wrap.text(lang.uploadNoPreview);\r\n                                    });\r\n                                }\r\n                            }, thumbnailWidth, thumbnailHeight);\r\n                        }\r\n                    }\r\n                    percentages[ file.id ] = [ file.size, 0 ];\r\n                    file.rotation = 0;\r\n\r\n                    /* 检查文件格式 */\r\n                    if (!file.ext || acceptExtensions.indexOf(file.ext.toLowerCase()) == -1) {\r\n                        showError('not_allow_type');\r\n                        uploader.removeFile(file);\r\n                    }\r\n                }\r\n\r\n                file.on('statuschange', function (cur, prev) {\r\n                    if (prev === 'progress') {\r\n                        $prgress.hide().width(0);\r\n                    } else if (prev === 'queued') {\r\n                        $li.off('mouseenter mouseleave');\r\n                        $btns.remove();\r\n                    }\r\n                    // 成功\r\n                    if (cur === 'error' || cur === 'invalid') {\r\n                        showError(file.statusText);\r\n                        percentages[ file.id ][ 1 ] = 1;\r\n                    } else if (cur === 'interrupt') {\r\n                        showError('interrupt');\r\n                    } else if (cur === 'queued') {\r\n                        percentages[ file.id ][ 1 ] = 0;\r\n                    } else if (cur === 'progress') {\r\n                        $info.hide();\r\n                        $prgress.css('display', 'block');\r\n                    } else if (cur === 'complete') {\r\n                    }\r\n\r\n                    $li.removeClass('state-' + prev).addClass('state-' + cur);\r\n                });\r\n\r\n                $li.on('mouseenter', function () {\r\n                    $btns.stop().animate({height: 30});\r\n                });\r\n                $li.on('mouseleave', function () {\r\n                    $btns.stop().animate({height: 0});\r\n                });\r\n\r\n                $btns.on('click', 'span', function () {\r\n                    var index = $(this).index(),\r\n                        deg;\r\n\r\n                    switch (index) {\r\n                        case 0:\r\n                            uploader.removeFile(file);\r\n                            return;\r\n                        case 1:\r\n                            file.rotation += 90;\r\n                            break;\r\n                        case 2:\r\n                            file.rotation -= 90;\r\n                            break;\r\n                    }\r\n\r\n                    if (supportTransition) {\r\n                        deg = 'rotate(' + file.rotation + 'deg)';\r\n                        $wrap.css({\r\n                            '-webkit-transform': deg,\r\n                            '-mos-transform': deg,\r\n                            '-o-transform': deg,\r\n                            'transform': deg\r\n                        });\r\n                    } else {\r\n                        $wrap.css('filter', 'progid:DXImageTransform.Microsoft.BasicImage(rotation=' + (~~((file.rotation / 90) % 4 + 4) % 4) + ')');\r\n                    }\r\n\r\n                });\r\n\r\n                $li.insertBefore($filePickerBlock);\r\n            }\r\n\r\n            // 负责view的销毁\r\n            function removeFile(file) {\r\n                var $li = $('#' + file.id);\r\n                delete percentages[ file.id ];\r\n                updateTotalProgress();\r\n                $li.off().find('.file-panel').off().end().remove();\r\n            }\r\n\r\n            function updateTotalProgress() {\r\n                var loaded = 0,\r\n                    total = 0,\r\n                    spans = $progress.children(),\r\n                    percent;\r\n\r\n                $.each(percentages, function (k, v) {\r\n                    total += v[ 0 ];\r\n                    loaded += v[ 0 ] * v[ 1 ];\r\n                });\r\n\r\n                percent = total ? loaded / total : 0;\r\n\r\n                spans.eq(0).text(Math.round(percent * 100) + '%');\r\n                spans.eq(1).css('width', Math.round(percent * 100) + '%');\r\n                updateStatus();\r\n            }\r\n\r\n            function setState(val, files) {\r\n\r\n                if (val != state) {\r\n\r\n                    var stats = uploader.getStats();\r\n\r\n                    $upload.removeClass('state-' + state);\r\n                    $upload.addClass('state-' + val);\r\n\r\n                    switch (val) {\r\n\r\n                        /* 未选择文件 */\r\n                        case 'pedding':\r\n                            $queue.addClass('element-invisible');\r\n                            $statusBar.addClass('element-invisible');\r\n                            $placeHolder.removeClass('element-invisible');\r\n                            $progress.hide(); $info.hide();\r\n                            uploader.refresh();\r\n                            break;\r\n\r\n                        /* 可以开始上传 */\r\n                        case 'ready':\r\n                            $placeHolder.addClass('element-invisible');\r\n                            $queue.removeClass('element-invisible');\r\n                            $statusBar.removeClass('element-invisible');\r\n                            $progress.hide(); $info.show();\r\n                            $upload.text(lang.uploadStart);\r\n                            uploader.refresh();\r\n                            break;\r\n\r\n                        /* 上传中 */\r\n                        case 'uploading':\r\n                            $progress.show(); $info.hide();\r\n                            $upload.text(lang.uploadPause);\r\n                            break;\r\n\r\n                        /* 暂停上传 */\r\n                        case 'paused':\r\n                            $progress.show(); $info.hide();\r\n                            $upload.text(lang.uploadContinue);\r\n                            break;\r\n\r\n                        case 'confirm':\r\n                            $progress.show(); $info.hide();\r\n                            $upload.text(lang.uploadStart);\r\n\r\n                            stats = uploader.getStats();\r\n                            if (stats.successNum && !stats.uploadFailNum) {\r\n                                setState('finish');\r\n                                return;\r\n                            }\r\n                            break;\r\n\r\n                        case 'finish':\r\n                            $progress.hide(); $info.show();\r\n                            if (stats.uploadFailNum) {\r\n                                $upload.text(lang.uploadRetry);\r\n                            } else {\r\n                                $upload.text(lang.uploadStart);\r\n                            }\r\n                            break;\r\n                    }\r\n\r\n                    state = val;\r\n                    updateStatus();\r\n\r\n                }\r\n\r\n                if (!_this.getQueueCount()) {\r\n                    $upload.addClass('disabled')\r\n                } else {\r\n                    $upload.removeClass('disabled')\r\n                }\r\n\r\n            }\r\n\r\n            function updateStatus() {\r\n                var text = '', stats;\r\n\r\n                if (state === 'ready') {\r\n                    text = lang.updateStatusReady.replace('_', fileCount).replace('_KB', WebUploader.formatSize(fileSize));\r\n                } else if (state === 'confirm') {\r\n                    stats = uploader.getStats();\r\n                    if (stats.uploadFailNum) {\r\n                        text = lang.updateStatusConfirm.replace('_', stats.successNum).replace('_', stats.successNum);\r\n                    }\r\n                } else {\r\n                    stats = uploader.getStats();\r\n                    text = lang.updateStatusFinish.replace('_', fileCount).\r\n                        replace('_KB', WebUploader.formatSize(fileSize)).\r\n                        replace('_', stats.successNum);\r\n\r\n                    if (stats.uploadFailNum) {\r\n                        text += lang.updateStatusError.replace('_', stats.uploadFailNum);\r\n                    }\r\n                }\r\n\r\n                $info.html(text);\r\n            }\r\n\r\n            uploader.on('fileQueued', function (file) {\r\n                fileCount++;\r\n                fileSize += file.size;\r\n\r\n                if (fileCount === 1) {\r\n                    $placeHolder.addClass('element-invisible');\r\n                    $statusBar.show();\r\n                }\r\n\r\n                addFile(file);\r\n            });\r\n\r\n            uploader.on('fileDequeued', function (file) {\r\n                fileCount--;\r\n                fileSize -= file.size;\r\n\r\n                removeFile(file);\r\n                updateTotalProgress();\r\n            });\r\n\r\n            uploader.on('filesQueued', function (file) {\r\n                if (!uploader.isInProgress() && (state == 'pedding' || state == 'finish' || state == 'confirm' || state == 'ready')) {\r\n                    setState('ready');\r\n                }\r\n                updateTotalProgress();\r\n            });\r\n\r\n            uploader.on('all', function (type, files) {\r\n                switch (type) {\r\n                    case 'uploadFinished':\r\n                        setState('confirm', files);\r\n                        break;\r\n                    case 'startUpload':\r\n                        /* 添加额外的GET参数 */\r\n                        var params = utils.serializeParam(editor.queryCommandValue('serverparam')) || '',\r\n                            url = utils.formatUrl(actionUrl + (actionUrl.indexOf('?') == -1 ? '?':'&') + 'encode=utf-8&' + params);\r\n                        uploader.option('server', url);\r\n                        setState('uploading', files);\r\n                        break;\r\n                    case 'stopUpload':\r\n                        setState('paused', files);\r\n                        break;\r\n                }\r\n            });\r\n\r\n            uploader.on('uploadBeforeSend', function (file, data, header) {\r\n                //这里可以通过data对象添加POST参数\r\n                header['X_Requested_With'] = 'XMLHttpRequest';\r\n                // HaoChuan9421\r\n                if(editor.options.headers && Object.prototype.toString.apply(editor.options.headers) === \"[object Object]\"){\r\n                    for(var key in editor.options.headers){\r\n                        header[key] = editor.options.headers[key]\r\n                    }\r\n                }\r\n            });\r\n\r\n            uploader.on('uploadProgress', function (file, percentage) {\r\n                var $li = $('#' + file.id),\r\n                    $percent = $li.find('.progress span');\r\n\r\n                $percent.css('width', percentage * 100 + '%');\r\n                percentages[ file.id ][ 1 ] = percentage;\r\n                updateTotalProgress();\r\n            });\r\n\r\n            uploader.on('uploadSuccess', function (file, ret) {\r\n                var $file = $('#' + file.id);\r\n                try {\r\n                    var responseText = (ret._raw || ret),\r\n                        json = utils.str2json(responseText);\r\n                    if (json.state == 'SUCCESS') {\r\n                        _this.fileList.push(json);\r\n                        $file.append('<span class=\"success\"></span>');\r\n                    } else {\r\n                        $file.find('.error').text(json.state).show();\r\n                    }\r\n                } catch (e) {\r\n                    $file.find('.error').text(lang.errorServerUpload).show();\r\n                }\r\n            });\r\n\r\n            uploader.on('uploadError', function (file, code) {\r\n            });\r\n            uploader.on('error', function (code, file) {\r\n                if (code == 'Q_TYPE_DENIED' || code == 'F_EXCEED_SIZE') {\r\n                    addFile(file);\r\n                }\r\n            });\r\n            uploader.on('uploadComplete', function (file, ret) {\r\n            });\r\n\r\n            $upload.on('click', function () {\r\n                if ($(this).hasClass('disabled')) {\r\n                    return false;\r\n                }\r\n\r\n                if (state === 'ready') {\r\n                    uploader.upload();\r\n                } else if (state === 'paused') {\r\n                    uploader.upload();\r\n                } else if (state === 'uploading') {\r\n                    uploader.stop();\r\n                }\r\n            });\r\n\r\n            $upload.addClass('state-' + state);\r\n            updateTotalProgress();\r\n        },\r\n        getQueueCount: function () {\r\n            var file, i, status, readyFile = 0, files = this.uploader.getFiles();\r\n            for (i = 0; file = files[i++]; ) {\r\n                status = file.getStatus();\r\n                if (status == 'queued' || status == 'uploading' || status == 'progress') readyFile++;\r\n            }\r\n            return readyFile;\r\n        },\r\n        getInsertList: function () {\r\n            var i, link, data, list = [],\r\n                prefix = editor.getOpt('fileUrlPrefix');\r\n            for (i = 0; i < this.fileList.length; i++) {\r\n                data = this.fileList[i];\r\n                link = data.url;\r\n                list.push({\r\n                    title: data.original || link.substr(link.lastIndexOf('/') + 1),\r\n                    url: prefix + link\r\n                });\r\n            }\r\n            return list;\r\n        }\r\n    };\r\n\r\n\r\n    /* 在线附件 */\r\n    function OnlineFile(target) {\r\n        this.container = utils.isString(target) ? document.getElementById(target) : target;\r\n        this.init();\r\n    }\r\n    OnlineFile.prototype = {\r\n        init: function () {\r\n            this.initContainer();\r\n            this.initEvents();\r\n            this.initData();\r\n        },\r\n        /* 初始化容器 */\r\n        initContainer: function () {\r\n            this.container.innerHTML = '';\r\n            this.list = document.createElement('ul');\r\n            this.clearFloat = document.createElement('li');\r\n\r\n            domUtils.addClass(this.list, 'list');\r\n            domUtils.addClass(this.clearFloat, 'clearFloat');\r\n\r\n            this.list.appendChild(this.clearFloat);\r\n            this.container.appendChild(this.list);\r\n        },\r\n        /* 初始化滚动事件,滚动到地步自动拉取数据 */\r\n        initEvents: function () {\r\n            var _this = this;\r\n\r\n            /* 滚动拉取图片 */\r\n            domUtils.on($G('fileList'), 'scroll', function(e){\r\n                var panel = this;\r\n                if (panel.scrollHeight - (panel.offsetHeight + panel.scrollTop) < 10) {\r\n                    _this.getFileData();\r\n                }\r\n            });\r\n            /* 选中图片 */\r\n            domUtils.on(this.list, 'click', function (e) {\r\n                var target = e.target || e.srcElement,\r\n                    li = target.parentNode;\r\n\r\n                if (li.tagName.toLowerCase() == 'li') {\r\n                    if (domUtils.hasClass(li, 'selected')) {\r\n                        domUtils.removeClasses(li, 'selected');\r\n                    } else {\r\n                        domUtils.addClass(li, 'selected');\r\n                    }\r\n                }\r\n            });\r\n        },\r\n        /* 初始化第一次的数据 */\r\n        initData: function () {\r\n\r\n            /* 拉取数据需要使用的值 */\r\n            this.state = 0;\r\n            this.listSize = editor.getOpt('fileManagerListSize');\r\n            this.listIndex = 0;\r\n            this.listEnd = false;\r\n\r\n            /* 第一次拉取数据 */\r\n            this.getFileData();\r\n        },\r\n        /* 向后台拉取图片列表数据 */\r\n        getFileData: function () {\r\n            var _this = this;\r\n\r\n            if(!_this.listEnd && !this.isLoadingData) {\r\n                this.isLoadingData = true;\r\n                ajax.request(editor.getActionUrl(editor.getOpt('fileManagerActionName')), {\r\n                    timeout: 100000,\r\n                    data: utils.extend({\r\n                            start: this.listIndex,\r\n                            size: this.listSize\r\n                        }, editor.queryCommandValue('serverparam')),\r\n                    method: 'get',\r\n                    onsuccess: function (r) {\r\n                        try {\r\n                            var json = eval('(' + r.responseText + ')');\r\n                            if (json.state == 'SUCCESS') {\r\n                                _this.pushData(json.list);\r\n                                _this.listIndex = parseInt(json.start) + parseInt(json.list.length);\r\n                                if(_this.listIndex >= json.total) {\r\n                                    _this.listEnd = true;\r\n                                }\r\n                                _this.isLoadingData = false;\r\n                            }\r\n                        } catch (e) {\r\n                            if(r.responseText.indexOf('ue_separate_ue') != -1) {\r\n                                var list = r.responseText.split(r.responseText);\r\n                                _this.pushData(list);\r\n                                _this.listIndex = parseInt(list.length);\r\n                                _this.listEnd = true;\r\n                                _this.isLoadingData = false;\r\n                            }\r\n                        }\r\n                    },\r\n                    onerror: function () {\r\n                        _this.isLoadingData = false;\r\n                    }\r\n                });\r\n            }\r\n        },\r\n        /* 添加图片到列表界面上 */\r\n        pushData: function (list) {\r\n            var i, item, img, filetype, preview, icon, _this = this,\r\n                urlPrefix = editor.getOpt('fileManagerUrlPrefix');\r\n            for (i = 0; i < list.length; i++) {\r\n                if(list[i] && list[i].url) {\r\n                    item = document.createElement('li');\r\n                    icon = document.createElement('span');\r\n                    filetype = list[i].url.substr(list[i].url.lastIndexOf('.') + 1);\r\n\r\n                    if ( \"png|jpg|jpeg|gif|bmp\".indexOf(filetype) != -1 ) {\r\n                        preview = document.createElement('img');\r\n                        domUtils.on(preview, 'load', (function(image){\r\n                            return function(){\r\n                                _this.scale(image, image.parentNode.offsetWidth, image.parentNode.offsetHeight);\r\n                            };\r\n                        })(preview));\r\n                        preview.width = 113;\r\n                        preview.setAttribute('src', urlPrefix + list[i].url + (list[i].url.indexOf('?') == -1 ? '?noCache=':'&noCache=') + (+new Date()).toString(36) );\r\n                    } else {\r\n                        var ic = document.createElement('i'),\r\n                            textSpan = document.createElement('span');\r\n                        textSpan.innerHTML = list[i].url.substr(list[i].url.lastIndexOf('/') + 1);\r\n                        preview = document.createElement('div');\r\n                        preview.appendChild(ic);\r\n                        preview.appendChild(textSpan);\r\n                        domUtils.addClass(preview, 'file-wrapper');\r\n                        domUtils.addClass(textSpan, 'file-title');\r\n                        domUtils.addClass(ic, 'file-type-' + filetype);\r\n                        domUtils.addClass(ic, 'file-preview');\r\n                    }\r\n                    domUtils.addClass(icon, 'icon');\r\n                    item.setAttribute('data-url', urlPrefix + list[i].url);\r\n                    if (list[i].original) {\r\n                        item.setAttribute('data-title', list[i].original);\r\n                    }\r\n\r\n                    item.appendChild(preview);\r\n                    item.appendChild(icon);\r\n                    this.list.insertBefore(item, this.clearFloat);\r\n                }\r\n            }\r\n        },\r\n        /* 改变图片大小 */\r\n        scale: function (img, w, h, type) {\r\n            var ow = img.width,\r\n                oh = img.height;\r\n\r\n            if (type == 'justify') {\r\n                if (ow >= oh) {\r\n                    img.width = w;\r\n                    img.height = h * oh / ow;\r\n                    img.style.marginLeft = '-' + parseInt((img.width - w) / 2) + 'px';\r\n                } else {\r\n                    img.width = w * ow / oh;\r\n                    img.height = h;\r\n                    img.style.marginTop = '-' + parseInt((img.height - h) / 2) + 'px';\r\n                }\r\n            } else {\r\n                if (ow >= oh) {\r\n                    img.width = w * ow / oh;\r\n                    img.height = h;\r\n                    img.style.marginLeft = '-' + parseInt((img.width - w) / 2) + 'px';\r\n                } else {\r\n                    img.width = w;\r\n                    img.height = h * oh / ow;\r\n                    img.style.marginTop = '-' + parseInt((img.height - h) / 2) + 'px';\r\n                }\r\n            }\r\n        },\r\n        getInsertList: function () {\r\n            var i, lis = this.list.children, list = [];\r\n            for (i = 0; i < lis.length; i++) {\r\n                if (domUtils.hasClass(lis[i], 'selected')) {\r\n                    var url = lis[i].getAttribute('data-url');\r\n                    var title = lis[i].getAttribute('data-title') || url.substr(url.lastIndexOf('/') + 1);\r\n                    list.push({\r\n                        title: title,\r\n                        url: url\r\n                    });\r\n                }\r\n            }\r\n            return list;\r\n        }\r\n    };\r\n\r\n\r\n})();"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/background/background.css",
    "content": ".wrapper{ width: 424px;margin: 10px auto; zoom:1;position: relative}\r\n.tabbody{height:225px;}\r\n.tabbody .panel { position: absolute;width:100%; height:100%;background: #fff; display: none;}\r\n.tabbody .focus { display: block;}\r\n\r\nbody{font-size: 12px;color: #888;overflow: hidden;}\r\ninput,label{vertical-align:middle}\r\n.clear{clear: both;}\r\n.pl{padding-left: 18px;padding-left: 23px\\9;}\r\n\r\n#imageList {width: 420px;height: 215px;margin-top: 10px;overflow: hidden;overflow-y: auto;}\r\n#imageList div {float: left;width: 100px;height: 95px;margin: 5px 10px;}\r\n#imageList img {cursor: pointer;border: 2px solid white;}\r\n\r\n.bgarea{margin: 10px;padding: 5px;height: 84%;border: 1px solid #A8A297;}\r\n.content div{margin: 10px 0 10px 5px;}\r\n.content .iptradio{margin: 0px 5px 5px 0px;}\r\n.txt{width:280px;}\r\n\r\n.wrapcolor{height: 19px;}\r\ndiv.color{float: left;margin: 0;}\r\n#colorPicker{width: 17px;height: 17px;border: 1px solid #CCC;display: inline-block;border-radius: 3px;box-shadow: 2px 2px 5px #D3D6DA;margin: 0;float: left;}\r\ndiv.alignment,#custom{margin-left: 23px;margin-left: 28px\\9;}\r\n#custom input{height: 15px;min-height: 15px;width:20px;}\r\n#repeatType{width:100px;}\r\n\r\n\r\n/* 图片管理样式 */\r\n#imgManager {\r\n    width: 100%;\r\n    height: 225px;\r\n}\r\n#imgManager #imageList{\r\n    width: 100%;\r\n    overflow-x: hidden;\r\n    overflow-y: auto;\r\n}\r\n#imgManager ul {\r\n    display: block;\r\n    list-style: none;\r\n    margin: 0;\r\n    padding: 0;\r\n}\r\n#imgManager li {\r\n    float: left;\r\n    display: block;\r\n    list-style: none;\r\n    padding: 0;\r\n    width: 113px;\r\n    height: 113px;\r\n    margin: 9px 0 0 19px;\r\n    background-color: #eee;\r\n    overflow: hidden;\r\n    cursor: pointer;\r\n    position: relative;\r\n}\r\n#imgManager li.clearFloat {\r\n    float: none;\r\n    clear: both;\r\n    display: block;\r\n    width:0;\r\n    height:0;\r\n    margin: 0;\r\n    padding: 0;\r\n}\r\n#imgManager li img {\r\n    cursor: pointer;\r\n}\r\n#imgManager li .icon {\r\n    cursor: pointer;\r\n    width: 113px;\r\n    height: 113px;\r\n    position: absolute;\r\n    top: 0;\r\n    left: 0;\r\n    z-index: 2;\r\n    border: 0;\r\n    background-repeat: no-repeat;\r\n}\r\n#imgManager li .icon:hover {\r\n    width: 107px;\r\n    height: 107px;\r\n    border: 3px solid #1094fa;\r\n}\r\n#imgManager li.selected .icon {\r\n    background-image: url(images/success.png);\r\n    background-position: 75px 75px;\r\n}\r\n#imgManager li.selected .icon:hover {\r\n    width: 107px;\r\n    height: 107px;\r\n    border: 3px solid #1094fa;\r\n    background-position: 72px 72px;\r\n}"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/background/background.html",
    "content": "<!DOCTYPE HTML>\r\n<html>\r\n<head>\r\n    <meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"/>\r\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"background.css\">\r\n</head>\r\n<body>\r\n    <div id=\"bg_container\" class=\"wrapper\">\r\n        <div id=\"tabHeads\" class=\"tabhead\">\r\n            <span class=\"focus\" data-content-id=\"normal\"><var id=\"lang_background_normal\"></var></span>\r\n            <span class=\"\" data-content-id=\"imgManager\"><var id=\"lang_background_local\"></var></span>\r\n        </div>\r\n        <div id=\"tabBodys\" class=\"tabbody\">\r\n            <div id=\"normal\" class=\"panel focus\">\r\n                <fieldset class=\"bgarea\">\r\n                    <legend><var id=\"lang_background_set\"></var></legend>\r\n                    <div class=\"content\">\r\n                        <div>\r\n                            <label><input id=\"nocolorRadio\" class=\"iptradio\" type=\"radio\" name=\"t\" value=\"none\" checked=\"checked\"><var id=\"lang_background_none\"></var></label>\r\n                            <label><input id=\"coloredRadio\" class=\"iptradio\" type=\"radio\" name=\"t\" value=\"color\"><var id=\"lang_background_colored\"></var></label>\r\n                        </div>\r\n                        <div class=\"wrapcolor pl\">\r\n                            <div class=\"color\">\r\n                                <var id=\"lang_background_color\"></var>:\r\n                            </div>\r\n                            <div id=\"colorPicker\"></div>\r\n                            <div class=\"clear\"></div>\r\n                        </div>\r\n                        <div class=\"wrapcolor pl\">\r\n                            <label><var id=\"lang_background_netimg\"></var>:</label><input class=\"txt\" type=\"text\" id=\"url\">\r\n                        </div>\r\n                        <div id=\"alignment\" class=\"alignment\">\r\n                            <var id=\"lang_background_align\"></var>:<select id=\"repeatType\">\r\n                                <option value=\"center\"></option>\r\n                                <option value=\"repeat-x\"></option>\r\n                                <option value=\"repeat-y\"></option>\r\n                                <option value=\"repeat\"></option>\r\n                                <option value=\"self\"></option>\r\n                            </select>\r\n                        </div>\r\n                        <div id=\"custom\" >\r\n                            <var id=\"lang_background_position\"></var>:x:<input type=\"text\" size=\"1\" id=\"x\" maxlength=\"4\" value=\"0\">px&nbsp;&nbsp;y:<input type=\"text\" size=\"1\" id=\"y\" maxlength=\"4\" value=\"0\">px\r\n                        </div>\r\n                    </div>\r\n                </fieldset>\r\n\r\n            </div>\r\n            <div id=\"imgManager\" class=\"panel\">\r\n                <div id=\"imageList\" style=\"\"></div>\r\n            </div>\r\n        </div>\r\n    </div>\r\n    <script type=\"text/javascript\" src=\"background.js\"></script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/background/background.js",
    "content": "(function () {\r\n\r\n    var onlineImage,\r\n        backupStyle = editor.queryCommandValue('background');\r\n\r\n    window.onload = function () {\r\n        initTabs();\r\n        initColorSelector();\r\n    };\r\n\r\n    /* 初始化tab标签 */\r\n    function initTabs(){\r\n        var tabs = $G('tabHeads').children;\r\n        for (var i = 0; i < tabs.length; i++) {\r\n            domUtils.on(tabs[i], \"click\", function (e) {\r\n                var target = e.target || e.srcElement;\r\n                for (var j = 0; j < tabs.length; j++) {\r\n                    if(tabs[j] == target){\r\n                        tabs[j].className = \"focus\";\r\n                        var contentId = tabs[j].getAttribute('data-content-id');\r\n                        $G(contentId).style.display = \"block\";\r\n                        if(contentId == 'imgManager') {\r\n                            initImagePanel();\r\n                        }\r\n                    }else {\r\n                        tabs[j].className = \"\";\r\n                        $G(tabs[j].getAttribute('data-content-id')).style.display = \"none\";\r\n                    }\r\n                }\r\n            });\r\n        }\r\n    }\r\n\r\n    /* 初始化颜色设置 */\r\n    function initColorSelector () {\r\n        var obj = editor.queryCommandValue('background');\r\n        if (obj) {\r\n            var color = obj['background-color'],\r\n                repeat = obj['background-repeat'] || 'repeat',\r\n                image = obj['background-image'] || '',\r\n                position = obj['background-position'] || 'center center',\r\n                pos = position.split(' '),\r\n                x = parseInt(pos[0]) || 0,\r\n                y = parseInt(pos[1]) || 0;\r\n\r\n            if(repeat == 'no-repeat' && (x || y)) repeat = 'self';\r\n\r\n            image = image.match(/url[\\s]*\\(([^\\)]*)\\)/);\r\n            image = image ? image[1]:'';\r\n            updateFormState('colored', color, image, repeat, x, y);\r\n        } else {\r\n            updateFormState();\r\n        }\r\n\r\n        var updateHandler = function () {\r\n            updateFormState();\r\n            updateBackground();\r\n        }\r\n        domUtils.on($G('nocolorRadio'), 'click', updateBackground);\r\n        domUtils.on($G('coloredRadio'), 'click', updateHandler);\r\n        domUtils.on($G('url'), 'keyup', function(){\r\n            if($G('url').value && $G('alignment').style.display == \"none\") {\r\n                utils.each($G('repeatType').children, function(item){\r\n                    item.selected = ('repeat' == item.getAttribute('value') ? 'selected':false);\r\n                });\r\n            }\r\n            updateHandler();\r\n        });\r\n        domUtils.on($G('repeatType'), 'change', updateHandler);\r\n        domUtils.on($G('x'), 'keyup', updateBackground);\r\n        domUtils.on($G('y'), 'keyup', updateBackground);\r\n\r\n        initColorPicker();\r\n    }\r\n\r\n    /* 初始化颜色选择器 */\r\n    function initColorPicker() {\r\n        var me = editor,\r\n            cp = $G(\"colorPicker\");\r\n\r\n        /* 生成颜色选择器ui对象 */\r\n        var popup = new UE.ui.Popup({\r\n            content: new UE.ui.ColorPicker({\r\n                noColorText: me.getLang(\"clearColor\"),\r\n                editor: me,\r\n                onpickcolor: function (t, color) {\r\n                    updateFormState('colored', color);\r\n                    updateBackground();\r\n                    UE.ui.Popup.postHide();\r\n                },\r\n                onpicknocolor: function (t, color) {\r\n                    updateFormState('colored', 'transparent');\r\n                    updateBackground();\r\n                    UE.ui.Popup.postHide();\r\n                }\r\n            }),\r\n            editor: me,\r\n            onhide: function () {\r\n            }\r\n        });\r\n\r\n        /* 设置颜色选择器 */\r\n        domUtils.on(cp, \"click\", function () {\r\n            popup.showAnchor(this);\r\n        });\r\n        domUtils.on(document, 'mousedown', function (evt) {\r\n            var el = evt.target || evt.srcElement;\r\n            UE.ui.Popup.postHide(el);\r\n        });\r\n        domUtils.on(window, 'scroll', function () {\r\n            UE.ui.Popup.postHide();\r\n        });\r\n    }\r\n\r\n    /* 初始化在线图片列表 */\r\n    function initImagePanel() {\r\n        onlineImage = onlineImage || new OnlineImage('imageList');\r\n    }\r\n\r\n    /* 更新背景色设置面板 */\r\n    function updateFormState (radio, color, url, align, x, y) {\r\n        var nocolorRadio = $G('nocolorRadio'),\r\n            coloredRadio = $G('coloredRadio');\r\n\r\n        if(radio) {\r\n            nocolorRadio.checked = (radio == 'colored' ? false:'checked');\r\n            coloredRadio.checked = (radio == 'colored' ? 'checked':false);\r\n        }\r\n        if(color) {\r\n            domUtils.setStyle($G(\"colorPicker\"), \"background-color\", color);\r\n        }\r\n\r\n        if(url && /^\\//.test(url)) {\r\n            var a = document.createElement('a');\r\n            a.href = url;\r\n            browser.ie && (a.href = a.href);\r\n            url = browser.ie ? a.href:(a.protocol + '//' + a.host + a.pathname + a.search + a.hash);\r\n        }\r\n\r\n        if(url || url === '') {\r\n            $G('url').value = url;\r\n        }\r\n        if(align) {\r\n            utils.each($G('repeatType').children, function(item){\r\n                item.selected = (align == item.getAttribute('value') ? 'selected':false);\r\n            });\r\n        }\r\n        if(x || y) {\r\n            $G('x').value = parseInt(x) || 0;\r\n            $G('y').value = parseInt(y) || 0;\r\n        }\r\n\r\n        $G('alignment').style.display = coloredRadio.checked && $G('url').value ? '':'none';\r\n        $G('custom').style.display = coloredRadio.checked && $G('url').value && $G('repeatType').value == 'self' ? '':'none';\r\n    }\r\n\r\n    /* 更新背景颜色 */\r\n    function updateBackground () {\r\n        if ($G('coloredRadio').checked) {\r\n            var color = domUtils.getStyle($G(\"colorPicker\"), \"background-color\"),\r\n                bgimg = $G(\"url\").value,\r\n                align = $G(\"repeatType\").value,\r\n                backgroundObj = {\r\n                    \"background-repeat\": \"no-repeat\",\r\n                    \"background-position\": \"center center\"\r\n                };\r\n\r\n            if (color) backgroundObj[\"background-color\"] = color;\r\n            if (bgimg) backgroundObj[\"background-image\"] = 'url(' + bgimg + ')';\r\n            if (align == 'self') {\r\n                backgroundObj[\"background-position\"] = $G(\"x\").value + \"px \" + $G(\"y\").value + \"px\";\r\n            } else if (align == 'repeat-x' || align == 'repeat-y' || align == 'repeat') {\r\n                backgroundObj[\"background-repeat\"] = align;\r\n            }\r\n\r\n            editor.execCommand('background', backgroundObj);\r\n        } else {\r\n            editor.execCommand('background', null);\r\n        }\r\n    }\r\n\r\n\r\n    /* 在线图片 */\r\n    function OnlineImage(target) {\r\n        this.container = utils.isString(target) ? document.getElementById(target) : target;\r\n        this.init();\r\n    }\r\n    OnlineImage.prototype = {\r\n        init: function () {\r\n            this.reset();\r\n            this.initEvents();\r\n        },\r\n        /* 初始化容器 */\r\n        initContainer: function () {\r\n            this.container.innerHTML = '';\r\n            this.list = document.createElement('ul');\r\n            this.clearFloat = document.createElement('li');\r\n\r\n            domUtils.addClass(this.list, 'list');\r\n            domUtils.addClass(this.clearFloat, 'clearFloat');\r\n\r\n            this.list.id = 'imageListUl';\r\n            this.list.appendChild(this.clearFloat);\r\n            this.container.appendChild(this.list);\r\n        },\r\n        /* 初始化滚动事件,滚动到地步自动拉取数据 */\r\n        initEvents: function () {\r\n            var _this = this;\r\n\r\n            /* 滚动拉取图片 */\r\n            domUtils.on($G('imageList'), 'scroll', function(e){\r\n                var panel = this;\r\n                if (panel.scrollHeight - (panel.offsetHeight + panel.scrollTop) < 10) {\r\n                    _this.getImageData();\r\n                }\r\n            });\r\n            /* 选中图片 */\r\n            domUtils.on(this.container, 'click', function (e) {\r\n                var target = e.target || e.srcElement,\r\n                    li = target.parentNode,\r\n                    nodes = $G('imageListUl').childNodes;\r\n\r\n                if (li.tagName.toLowerCase() == 'li') {\r\n                    updateFormState('nocolor', null, '');\r\n                    for (var i = 0, node; node = nodes[i++];) {\r\n                        if (node == li && !domUtils.hasClass(node, 'selected')) {\r\n                            domUtils.addClass(node, 'selected');\r\n                            updateFormState('colored', null, li.firstChild.getAttribute(\"_src\"), 'repeat');\r\n                        } else {\r\n                            domUtils.removeClasses(node, 'selected');\r\n                        }\r\n                    }\r\n                    updateBackground();\r\n                }\r\n            });\r\n        },\r\n        /* 初始化第一次的数据 */\r\n        initData: function () {\r\n\r\n            /* 拉取数据需要使用的值 */\r\n            this.state = 0;\r\n            this.listSize = editor.getOpt('imageManagerListSize');\r\n            this.listIndex = 0;\r\n            this.listEnd = false;\r\n\r\n            /* 第一次拉取数据 */\r\n            this.getImageData();\r\n        },\r\n        /* 重置界面 */\r\n        reset: function() {\r\n            this.initContainer();\r\n            this.initData();\r\n        },\r\n        /* 向后台拉取图片列表数据 */\r\n        getImageData: function () {\r\n            var _this = this;\r\n\r\n            if(!_this.listEnd && !this.isLoadingData) {\r\n                this.isLoadingData = true;\r\n                var url = editor.getActionUrl(editor.getOpt('imageManagerActionName')),\r\n                    isJsonp = utils.isCrossDomainUrl(url);\r\n                ajax.request(url, {\r\n                    'timeout': 100000,\r\n                    'dataType': isJsonp ? 'jsonp':'',\r\n                    'data': utils.extend({\r\n                            start: this.listIndex,\r\n                            size: this.listSize\r\n                        }, editor.queryCommandValue('serverparam')),\r\n                    'method': 'get',\r\n                    'onsuccess': function (r) {\r\n                        try {\r\n                            var json = isJsonp ? r:eval('(' + r.responseText + ')');\r\n                            if (json.state == 'SUCCESS') {\r\n                                _this.pushData(json.list);\r\n                                _this.listIndex = parseInt(json.start) + parseInt(json.list.length);\r\n                                if(_this.listIndex >= json.total) {\r\n                                    _this.listEnd = true;\r\n                                }\r\n                                _this.isLoadingData = false;\r\n                            }\r\n                        } catch (e) {\r\n                            if(r.responseText.indexOf('ue_separate_ue') != -1) {\r\n                                var list = r.responseText.split(r.responseText);\r\n                                _this.pushData(list);\r\n                                _this.listIndex = parseInt(list.length);\r\n                                _this.listEnd = true;\r\n                                _this.isLoadingData = false;\r\n                            }\r\n                        }\r\n                    },\r\n                    'onerror': function () {\r\n                        _this.isLoadingData = false;\r\n                    }\r\n                });\r\n            }\r\n        },\r\n        /* 添加图片到列表界面上 */\r\n        pushData: function (list) {\r\n            var i, item, img, icon, _this = this,\r\n                urlPrefix = editor.getOpt('imageManagerUrlPrefix');\r\n            for (i = 0; i < list.length; i++) {\r\n                if(list[i] && list[i].url) {\r\n                    item = document.createElement('li');\r\n                    img = document.createElement('img');\r\n                    icon = document.createElement('span');\r\n\r\n                    domUtils.on(img, 'load', (function(image){\r\n                        return function(){\r\n                            _this.scale(image, image.parentNode.offsetWidth, image.parentNode.offsetHeight);\r\n                        }\r\n                    })(img));\r\n                    img.width = 113;\r\n                    img.setAttribute('src', urlPrefix + list[i].url + (list[i].url.indexOf('?') == -1 ? '?noCache=':'&noCache=') + (+new Date()).toString(36) );\r\n                    img.setAttribute('_src', urlPrefix + list[i].url);\r\n                    domUtils.addClass(icon, 'icon');\r\n\r\n                    item.appendChild(img);\r\n                    item.appendChild(icon);\r\n                    this.list.insertBefore(item, this.clearFloat);\r\n                }\r\n            }\r\n        },\r\n        /* 改变图片大小 */\r\n        scale: function (img, w, h, type) {\r\n            var ow = img.width,\r\n                oh = img.height;\r\n\r\n            if (type == 'justify') {\r\n                if (ow >= oh) {\r\n                    img.width = w;\r\n                    img.height = h * oh / ow;\r\n                    img.style.marginLeft = '-' + parseInt((img.width - w) / 2) + 'px';\r\n                } else {\r\n                    img.width = w * ow / oh;\r\n                    img.height = h;\r\n                    img.style.marginTop = '-' + parseInt((img.height - h) / 2) + 'px';\r\n                }\r\n            } else {\r\n                if (ow >= oh) {\r\n                    img.width = w * ow / oh;\r\n                    img.height = h;\r\n                    img.style.marginLeft = '-' + parseInt((img.width - w) / 2) + 'px';\r\n                } else {\r\n                    img.width = w;\r\n                    img.height = h * oh / ow;\r\n                    img.style.marginTop = '-' + parseInt((img.height - h) / 2) + 'px';\r\n                }\r\n            }\r\n        },\r\n        getInsertList: function () {\r\n            var i, lis = this.list.children, list = [], align = getAlign();\r\n            for (i = 0; i < lis.length; i++) {\r\n                if (domUtils.hasClass(lis[i], 'selected')) {\r\n                    var img = lis[i].firstChild,\r\n                        src = img.getAttribute('_src');\r\n                    list.push({\r\n                        src: src,\r\n                        _src: src,\r\n                        floatStyle: align\r\n                    });\r\n                }\r\n\r\n            }\r\n            return list;\r\n        }\r\n    };\r\n\r\n    dialog.onok = function () {\r\n        updateBackground();\r\n        editor.fireEvent('saveScene');\r\n    };\r\n    dialog.oncancel = function () {\r\n        editor.execCommand('background', backupStyle);\r\n    };\r\n\r\n})();"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/charts/chart.config.js",
    "content": "/*\r\n * 图表配置文件\r\n * */\r\n\r\n\r\n//不同类型的配置\r\nvar typeConfig = [\r\n    {\r\n        chart: {\r\n            type: 'line'\r\n        },\r\n        plotOptions: {\r\n            line: {\r\n                dataLabels: {\r\n                    enabled: false\r\n                },\r\n                enableMouseTracking: true\r\n            }\r\n        }\r\n    }, {\r\n        chart: {\r\n            type: 'line'\r\n        },\r\n        plotOptions: {\r\n            line: {\r\n                dataLabels: {\r\n                    enabled: true\r\n                },\r\n                enableMouseTracking: false\r\n            }\r\n        }\r\n    }, {\r\n        chart: {\r\n            type: 'area'\r\n        }\r\n    }, {\r\n        chart: {\r\n            type: 'bar'\r\n        }\r\n    }, {\r\n        chart: {\r\n            type: 'column'\r\n        }\r\n    }, {\r\n        chart: {\r\n            plotBackgroundColor: null,\r\n            plotBorderWidth: null,\r\n            plotShadow: false\r\n        },\r\n        plotOptions: {\r\n            pie: {\r\n                allowPointSelect: true,\r\n                cursor: 'pointer',\r\n                dataLabels: {\r\n                    enabled: true,\r\n                    color: '#000000',\r\n                    connectorColor: '#000000',\r\n                    formatter: function() {\r\n                        return '<b>'+ this.point.name +'</b>: '+ ( Math.round( this.point.percentage*100 ) / 100 ) +' %';\r\n                    }\r\n                }\r\n            }\r\n        }\r\n    }\r\n];\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/charts/charts.css",
    "content": "html, body {\r\n    width: 100%;\r\n    height: 100%;\r\n    margin: 0;\r\n    padding: 0;\r\n    overflow-x: hidden;\r\n}\r\n\r\n.main {\r\n    width: 100%;\r\n    overflow: hidden;\r\n}\r\n\r\n.table-view {\r\n    height: 100%;\r\n    float: left;\r\n    margin: 20px;\r\n    width: 40%;\r\n}\r\n\r\n.table-view .table-container {\r\n    width: 100%;\r\n    margin-bottom: 50px;\r\n    overflow: scroll;\r\n}\r\n\r\n.table-view th {\r\n    padding: 5px 10px;\r\n    background-color: #F7F7F7;\r\n}\r\n\r\n.table-view td {\r\n    width: 50px;\r\n    text-align: center;\r\n    padding:0;\r\n}\r\n\r\n.table-container input {\r\n    width: 40px;\r\n    padding: 5px;\r\n    border: none;\r\n    outline: none;\r\n}\r\n\r\n.table-view caption {\r\n    font-size: 18px;\r\n    text-align: left;\r\n}\r\n\r\n.charts-view {\r\n    /*margin-left: 49%!important;*/\r\n    width: 50%;\r\n    margin-left: 49%;\r\n    height: 400px;\r\n}\r\n\r\n.charts-container {\r\n    border-left: 1px solid #c3c3c3;\r\n}\r\n\r\n.charts-format fieldset {\r\n    padding-left: 20px;\r\n    margin-bottom: 50px;\r\n}\r\n\r\n.charts-format legend {\r\n    padding-left: 10px;\r\n    padding-right: 10px;\r\n}\r\n\r\n.format-item-container {\r\n    padding: 20px;\r\n}\r\n\r\n.format-item-container label {\r\n    display: block;\r\n    margin: 10px 0;\r\n}\r\n\r\n.charts-format .data-item {\r\n    border: 1px solid black;\r\n    outline: none;\r\n    padding: 2px 3px;\r\n}\r\n\r\n/* 图表类型 */\r\n\r\n.charts-type {\r\n    margin-top: 50px;\r\n    height: 300px;\r\n}\r\n\r\n.scroll-view {\r\n    border: 1px solid #c3c3c3;\r\n    border-left: none;\r\n    border-right: none;\r\n    overflow: hidden;\r\n}\r\n\r\n.scroll-container {\r\n    margin: 20px;\r\n    width: 100%;\r\n    overflow: hidden;\r\n}\r\n\r\n.scroll-bed {\r\n    width: 10000px;\r\n    _margin-top: 20px;\r\n    -webkit-transition: margin-left .5s ease;\r\n    -moz-transition: margin-left .5s ease;\r\n    transition: margin-left .5s ease;\r\n}\r\n\r\n.view-box {\r\n    display: inline-block;\r\n    *display: inline;\r\n    *zoom: 1;\r\n    margin-right: 20px;\r\n    border: 2px solid white;\r\n    line-height: 0;\r\n    overflow: hidden;\r\n    cursor: pointer;\r\n}\r\n\r\n.view-box img {\r\n    border: 1px solid #cecece;\r\n}\r\n\r\n.view-box.selected {\r\n    border-color: #7274A7;\r\n}\r\n\r\n.button-container {\r\n    margin-bottom: 20px;\r\n    text-align: center;\r\n}\r\n\r\n.button-container a {\r\n    display: inline-block;\r\n    width: 100px;\r\n    height: 25px;\r\n    line-height: 25px;\r\n    border: 1px solid #c2ccd1;\r\n    margin-right: 30px;\r\n    text-decoration: none;\r\n    color: black;\r\n    -webkit-border-radius: 2px;\r\n    -moz-border-radius: 2px;\r\n    border-radius: 2px;\r\n}\r\n\r\n.button-container a:HOVER {\r\n    background: #fcfcfc;\r\n}\r\n\r\n.button-container a:ACTIVE {\r\n    border-top-color: #c2ccd1;\r\n    box-shadow:inset 0 5px 4px -4px rgba(49, 49, 64, 0.1);\r\n}\r\n\r\n.edui-charts-not-data {\r\n    height: 100px;\r\n    line-height: 100px;\r\n    text-align: center;\r\n}"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/charts/charts.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n    <head>\r\n        <title>chart</title>\r\n        <meta chartset=\"utf-8\">\r\n        <link rel=\"stylesheet\" type=\"text/css\" href=\"charts.css\">\r\n        <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n    </head>\r\n    <body>\r\n        <div class=\"main\">\r\n            <div class=\"table-view\">\r\n                <h3><var id=\"lang_data_source\"></var></h3>\r\n                <div id=\"tableContainer\" class=\"table-container\"></div>\r\n                <h3><var id=\"lang_chart_format\"></var></h3>\r\n                <form name=\"data-form\">\r\n                    <div class=\"charts-format\">\r\n                        <fieldset>\r\n                            <legend><var id=\"lang_data_align\"></var></legend>\r\n                            <div class=\"format-item-container\">\r\n                                <label>\r\n                                    <input type=\"radio\" class=\"format-ctrl not-pie-item\" name=\"charts-format\" value=\"1\" checked=\"checked\">\r\n                                    <var id=\"lang_chart_align_same\"></var>\r\n                                </label>\r\n                                <label>\r\n                                    <input type=\"radio\" class=\"format-ctrl not-pie-item\" name=\"charts-format\" value=\"-1\">\r\n                                    <var id=\"lang_chart_align_reverse\"></var>\r\n                                </label>\r\n                                <br>\r\n                            </div>\r\n                        </fieldset>\r\n                        <fieldset>\r\n                            <legend><var id=\"lang_chart_title\"></var></legend>\r\n                            <div class=\"format-item-container\">\r\n                                <label>\r\n                                    <var id=\"lang_chart_main_title\"></var><input type=\"text\" name=\"title\" class=\"data-item\">\r\n                                </label>\r\n                                <label>\r\n                                    <var id=\"lang_chart_sub_title\"></var><input type=\"text\" name=\"sub-title\" class=\"data-item not-pie-item\">\r\n                                </label>\r\n                                <label>\r\n                                    <var id=\"lang_chart_x_title\"></var><input type=\"text\" name=\"x-title\" class=\"data-item not-pie-item\">\r\n                                </label>\r\n                                <label>\r\n                                    <var id=\"lang_chart_y_title\"></var><input type=\"text\" name=\"y-title\" class=\"data-item not-pie-item\">\r\n                                </label>\r\n                            </div>\r\n                        </fieldset>\r\n                        <fieldset>\r\n                            <legend><var id=\"lang_chart_tip\"></var></legend>\r\n                            <div class=\"format-item-container\">\r\n                                <label>\r\n                                    <var id=\"lang_cahrt_tip_prefix\"></var>\r\n                                    <input type=\"text\" id=\"tipInput\" name=\"tip\" class=\"data-item\" disabled=\"disabled\">\r\n                                </label>\r\n                                <p><var id=\"lang_cahrt_tip_description\"></var></p>\r\n                            </div>\r\n                        </fieldset>\r\n                        <fieldset>\r\n                            <legend><var id=\"lang_chart_data_unit\"></var></legend>\r\n                            <div class=\"format-item-container\">\r\n                                <label><var id=\"lang_chart_data_unit_title\"></var><input type=\"text\" name=\"unit\" class=\"data-item\"></label>\r\n                                <p><var id=\"lang_chart_data_unit_description\"></var></p>\r\n                            </div>\r\n                        </fieldset>\r\n                    </div>\r\n                </form>\r\n            </div>\r\n            <div class=\"charts-view\">\r\n                <div id=\"chartsContainer\" class=\"charts-container\"></div>\r\n                <div id=\"chartsType\" class=\"charts-type\">\r\n                    <h3><var id=\"lang_chart_type\"></var></h3>\r\n                    <div class=\"scroll-view\">\r\n                        <div class=\"scroll-container\">\r\n                            <div id=\"scrollBed\" class=\"scroll-bed\"></div>\r\n                        </div>\r\n                        <div id=\"buttonContainer\" class=\"button-container\">\r\n                            <a href=\"#\" data-title=\"prev\"><var id=\"lang_prev_btn\"></var></a>\r\n                            <a href=\"#\" data-title=\"next\"><var id=\"lang_next_btn\"></var></a>\r\n                        </div>\r\n                    </div>\r\n                </div>\r\n            </div>\r\n        </div>\r\n        <script src=\"../../third-party/jquery-1.10.2.min.js\"></script>\r\n        <script src=\"../../third-party/highcharts/highcharts.js\"></script>\r\n        <script src=\"chart.config.js\"></script>\r\n        <script src=\"charts.js\"></script>\r\n    </body>\r\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/charts/charts.js",
    "content": "/*\r\n * 图片转换对话框脚本\r\n **/\r\n\r\nvar tableData = [],\r\n    //编辑器页面table\r\n    editorTable = null,\r\n    chartsConfig = window.typeConfig,\r\n    resizeTimer = null,\r\n    //初始默认图表类型\r\n    currentChartType = 0;\r\n\r\nwindow.onload = function () {\r\n\r\n    editorTable = domUtils.findParentByTagName( editor.selection.getRange().startContainer, 'table', true);\r\n\r\n    //未找到表格， 显示错误页面\r\n    if ( !editorTable ) {\r\n        document.body.innerHTML = \"<div class='edui-charts-not-data'>未找到数据</div>\";\r\n        return;\r\n    }\r\n\r\n    //初始化图表类型选择\r\n    initChartsTypeView();\r\n    renderTable( editorTable );\r\n    initEvent();\r\n    initUserConfig( editorTable.getAttribute( \"data-chart\" ) );\r\n    $( \"#scrollBed .view-box:eq(\"+ currentChartType +\")\" ).trigger( \"click\" );\r\n    updateViewType( currentChartType );\r\n\r\n    dialog.addListener( \"resize\", function () {\r\n\r\n        if ( resizeTimer != null ) {\r\n            window.clearTimeout( resizeTimer );\r\n        }\r\n\r\n        resizeTimer = window.setTimeout( function () {\r\n\r\n            resizeTimer = null;\r\n\r\n            renderCharts();\r\n\r\n        }, 500 );\r\n\r\n    } );\r\n\r\n};\r\n\r\nfunction initChartsTypeView () {\r\n\r\n    var contents = [];\r\n\r\n    for ( var i = 0, len = chartsConfig.length; i<len; i++ ) {\r\n\r\n        contents.push( '<div class=\"view-box\" data-chart-type=\"'+ i +'\"><img width=\"300\" src=\"images/charts'+ i +'.png\"></div>' );\r\n\r\n    }\r\n\r\n    $( \"#scrollBed\" ).html( contents.join( \"\" ) );\r\n\r\n}\r\n\r\n//渲染table， 以便用户修改数据\r\nfunction renderTable ( table ) {\r\n\r\n    var tableHtml = [];\r\n\r\n    //构造数据\r\n    for ( var i = 0, row; row = table.rows[ i ]; i++ ) {\r\n\r\n        tableData[ i ] = [];\r\n        tableHtml[ i ] = [];\r\n\r\n        for ( var j = 0, cell; cell = row.cells[ j ]; j++ ) {\r\n\r\n            var value = getCellValue( cell );\r\n\r\n            if ( i > 0 && j > 0 ) {\r\n                value = +value;\r\n            }\r\n\r\n            if ( i === 0 || j === 0 ) {\r\n                tableHtml[ i ].push( '<th>'+ value +'</th>' );\r\n            } else {\r\n                tableHtml[ i ].push( '<td><input type=\"text\" class=\"data-item\" value=\"'+ value +'\"></td>' );\r\n            }\r\n\r\n            tableData[ i ][ j ] = value;\r\n\r\n        }\r\n\r\n        tableHtml[ i ] = tableHtml[ i ].join( \"\" );\r\n\r\n    }\r\n\r\n    //draw 表格\r\n    $( \"#tableContainer\" ).html( '<table id=\"showTable\" border=\"1\"><tbody><tr>'+ tableHtml.join( \"</tr><tr>\" ) +'</tr></tbody></table>' );\r\n\r\n}\r\n\r\n/*\r\n * 根据表格已有的图表属性初始化当前图表属性\r\n */\r\nfunction initUserConfig ( config ) {\r\n\r\n    var parsedConfig = {};\r\n\r\n    if ( !config ) {\r\n        return;\r\n    }\r\n\r\n    config = config.split( \";\" );\r\n\r\n    $.each( config, function ( index, item ) {\r\n\r\n        item = item.split( \":\" );\r\n        parsedConfig[ item[ 0 ] ] = item[ 1 ];\r\n\r\n    } );\r\n\r\n    setUserConfig( parsedConfig );\r\n\r\n}\r\n\r\nfunction initEvent () {\r\n\r\n    var cacheValue = null,\r\n        //图表类型数\r\n        typeViewCount = chartsConfig.length- 1,\r\n        $chartsTypeViewBox = $( '#scrollBed .view-box' );\r\n\r\n    $( \".charts-format\" ).delegate( \".format-ctrl\", \"change\", function () {\r\n\r\n        renderCharts();\r\n\r\n    } )\r\n\r\n    $( \".table-view\" ).delegate( \".data-item\", \"focus\", function () {\r\n\r\n        cacheValue = this.value;\r\n\r\n    } ).delegate( \".data-item\", \"blur\", function () {\r\n\r\n        if ( this.value !== cacheValue ) {\r\n            renderCharts();\r\n        }\r\n\r\n        cacheValue = null;\r\n\r\n    } );\r\n\r\n    $( \"#buttonContainer\" ).delegate( \"a\", \"click\", function (e) {\r\n\r\n        e.preventDefault();\r\n\r\n        if ( this.getAttribute( \"data-title\" ) === 'prev' ) {\r\n\r\n            if ( currentChartType > 0 ) {\r\n                currentChartType--;\r\n                updateViewType( currentChartType );\r\n            }\r\n\r\n        } else {\r\n\r\n            if ( currentChartType < typeViewCount ) {\r\n                currentChartType++;\r\n                updateViewType( currentChartType );\r\n            }\r\n\r\n        }\r\n\r\n    } );\r\n\r\n    //图表类型变化\r\n    $( '#scrollBed' ).delegate( \".view-box\", \"click\", function (e) {\r\n\r\n        var index = $( this ).attr( \"data-chart-type\" );\r\n        $chartsTypeViewBox.removeClass( \"selected\" );\r\n        $( $chartsTypeViewBox[ index ] ).addClass( \"selected\" );\r\n\r\n        currentChartType = index | 0;\r\n\r\n        //饼图， 禁用部分配置\r\n        if ( currentChartType === chartsConfig.length - 1 ) {\r\n\r\n            disableNotPieConfig();\r\n\r\n        //启用完整配置\r\n        } else {\r\n\r\n            enableNotPieConfig();\r\n\r\n        }\r\n\r\n        renderCharts();\r\n\r\n    } );\r\n\r\n}\r\n\r\nfunction renderCharts () {\r\n\r\n    var data = collectData();\r\n\r\n    $('#chartsContainer').highcharts( $.extend( {}, chartsConfig[ currentChartType ], {\r\n\r\n        credits: {\r\n            enabled: false\r\n        },\r\n        exporting: {\r\n            enabled: false\r\n        },\r\n        title: {\r\n            text: data.title,\r\n            x: -20 //center\r\n        },\r\n        subtitle: {\r\n            text: data.subTitle,\r\n            x: -20\r\n        },\r\n        xAxis: {\r\n            title: {\r\n                text: data.xTitle\r\n            },\r\n            categories: data.categories\r\n        },\r\n        yAxis: {\r\n            title: {\r\n                text: data.yTitle\r\n            },\r\n            plotLines: [{\r\n                value: 0,\r\n                width: 1,\r\n                color: '#808080'\r\n            }]\r\n        },\r\n        tooltip: {\r\n            enabled: true,\r\n            valueSuffix: data.suffix\r\n        },\r\n        legend: {\r\n            layout: 'vertical',\r\n            align: 'right',\r\n            verticalAlign: 'middle',\r\n            borderWidth: 1\r\n        },\r\n        series: data.series\r\n\r\n    } ));\r\n\r\n}\r\n\r\nfunction updateViewType ( index ) {\r\n\r\n    $( \"#scrollBed\" ).css( 'marginLeft', -index*324+'px' );\r\n\r\n}\r\n\r\nfunction collectData () {\r\n\r\n    var form = document.forms[ 'data-form' ],\r\n        data = null;\r\n\r\n    if ( currentChartType !== chartsConfig.length - 1 ) {\r\n\r\n        data = getSeriesAndCategories();\r\n        $.extend( data, getUserConfig() );\r\n\r\n    //饼图数据格式\r\n    } else {\r\n        data = getSeriesForPieChart();\r\n        data.title = form[ 'title' ].value;\r\n        data.suffix = form[ 'unit' ].value;\r\n    }\r\n\r\n    return data;\r\n\r\n}\r\n\r\n/**\r\n * 获取用户配置信息\r\n */\r\nfunction getUserConfig () {\r\n\r\n    var form = document.forms[ 'data-form' ],\r\n        info = {\r\n            title: form[ 'title' ].value,\r\n            subTitle: form[ 'sub-title' ].value,\r\n            xTitle: form[ 'x-title' ].value,\r\n            yTitle: form[ 'y-title' ].value,\r\n            suffix: form[ 'unit' ].value,\r\n            //数据对齐方式\r\n            tableDataFormat: getTableDataFormat (),\r\n            //饼图提示文字\r\n            tip: $( \"#tipInput\" ).val()\r\n        };\r\n\r\n    return info;\r\n\r\n}\r\n\r\nfunction setUserConfig ( config ) {\r\n\r\n    var form = document.forms[ 'data-form' ];\r\n\r\n    config.title && ( form[ 'title' ].value = config.title );\r\n    config.subTitle && ( form[ 'sub-title' ].value = config.subTitle );\r\n    config.xTitle && ( form[ 'x-title' ].value = config.xTitle );\r\n    config.yTitle && ( form[ 'y-title' ].value = config.yTitle );\r\n    config.suffix && ( form[ 'unit' ].value = config.suffix );\r\n    config.dataFormat == \"-1\" && ( form[ 'charts-format' ][ 1 ].checked = true );\r\n    config.tip && ( form[ 'tip' ].value = config.tip );\r\n    currentChartType = config.chartType || 0;\r\n\r\n}\r\n\r\nfunction getSeriesAndCategories () {\r\n\r\n    var form = document.forms[ 'data-form' ],\r\n        series = [],\r\n        categories = [],\r\n        tmp = [],\r\n        tableData = getTableData();\r\n\r\n    //反转数据\r\n    if ( getTableDataFormat() === \"-1\" ) {\r\n\r\n        for ( var i = 0, len = tableData.length; i < len; i++ ) {\r\n\r\n            for ( var j = 0, jlen = tableData[ i ].length; j < jlen; j++ ) {\r\n\r\n                if ( !tmp[ j ] ) {\r\n                    tmp[ j ] = [];\r\n                }\r\n\r\n                tmp[ j ][ i ] = tableData[ i ][ j ];\r\n\r\n            }\r\n\r\n        }\r\n\r\n        tableData = tmp;\r\n\r\n    }\r\n\r\n    categories = tableData[0].slice( 1 );\r\n\r\n    for ( var i = 1, data; data = tableData[ i ]; i++ ) {\r\n\r\n        series.push( {\r\n            name: data[ 0 ],\r\n            data: data.slice( 1 )\r\n        } );\r\n\r\n    }\r\n\r\n    return {\r\n        series: series,\r\n        categories: categories\r\n    };\r\n\r\n}\r\n\r\n/*\r\n * 获取数据源数据对齐方式\r\n */\r\nfunction getTableDataFormat () {\r\n\r\n    var form = document.forms[ 'data-form' ],\r\n        items = form['charts-format'];\r\n\r\n    return items[ 0 ].checked ? items[ 0 ].value : items[ 1 ].value;\r\n\r\n}\r\n\r\n/*\r\n * 禁用非饼图类型的配置项\r\n */\r\nfunction disableNotPieConfig() {\r\n\r\n    updateConfigItem( 'disable' );\r\n\r\n}\r\n\r\n/*\r\n * 启用非饼图类型的配置项\r\n */\r\nfunction enableNotPieConfig() {\r\n\r\n    updateConfigItem( 'enable' );\r\n\r\n}\r\n\r\nfunction updateConfigItem ( value ) {\r\n\r\n    var table = $( \"#showTable\" )[ 0 ],\r\n        isDisable = value === 'disable' ? true : false;\r\n\r\n    //table中的input处理\r\n    for ( var i = 2 , row; row = table.rows[ i ]; i++ ) {\r\n\r\n        for ( var j = 1, cell; cell = row.cells[ j ]; j++ ) {\r\n\r\n            $( \"input\", cell ).attr( \"disabled\", isDisable );\r\n\r\n        }\r\n\r\n    }\r\n\r\n    //其他项处理\r\n    $( \"input.not-pie-item\" ).attr( \"disabled\", isDisable );\r\n    $( \"#tipInput\" ).attr( \"disabled\", !isDisable )\r\n\r\n}\r\n\r\n/*\r\n * 获取饼图数据\r\n * 饼图的数据只取第一行的\r\n **/\r\nfunction getSeriesForPieChart () {\r\n\r\n    var series = {\r\n            type: 'pie',\r\n            name: $(\"#tipInput\").val(),\r\n            data: []\r\n        },\r\n        tableData = getTableData();\r\n\r\n\r\n    for ( var j = 1, jlen = tableData[ 0 ].length; j < jlen; j++ ) {\r\n\r\n        var title = tableData[ 0 ][ j ],\r\n            val = tableData[ 1 ][ j ];\r\n\r\n        series.data.push( [ title, val ] );\r\n\r\n    }\r\n\r\n    return {\r\n        series: [ series ]\r\n    };\r\n\r\n}\r\n\r\nfunction getTableData () {\r\n\r\n    var table = document.getElementById( \"showTable\" ),\r\n        xCount = table.rows[0].cells.length - 1,\r\n        values = getTableInputValue();\r\n\r\n    for ( var i = 0, value; value = values[ i ]; i++ ) {\r\n\r\n        tableData[ Math.floor( i / xCount ) + 1 ][ i % xCount + 1 ] = values[ i ];\r\n\r\n    }\r\n\r\n    return tableData;\r\n\r\n}\r\n\r\nfunction getTableInputValue () {\r\n\r\n    var table = document.getElementById( \"showTable\" ),\r\n        inputs = table.getElementsByTagName( \"input\" ),\r\n        values = [];\r\n\r\n    for ( var i = 0, input; input = inputs[ i ]; i++ ) {\r\n        values.push( input.value | 0 );\r\n    }\r\n\r\n    return values;\r\n\r\n}\r\n\r\nfunction getCellValue ( cell ) {\r\n\r\n    var value = utils.trim( ( cell.innerText || cell.textContent || '' ) );\r\n\r\n    return value.replace( new RegExp( UE.dom.domUtils.fillChar, 'g' ), '' ).replace( /^\\s+|\\s+$/g, '' );\r\n\r\n}\r\n\r\n\r\n//dialog确认事件\r\ndialog.onok = function () {\r\n\r\n    //收集信息\r\n    var form = document.forms[ 'data-form' ],\r\n        info = getUserConfig();\r\n\r\n    //添加图表类型\r\n    info.chartType = currentChartType;\r\n\r\n    //同步表格数据到编辑器\r\n    syncTableData();\r\n\r\n    //执行图表命令\r\n    editor.execCommand( 'charts', info );\r\n\r\n};\r\n\r\n/*\r\n * 同步图表编辑视图的表格数据到编辑器里的原始表格\r\n */\r\nfunction syncTableData () {\r\n\r\n    var tableData = getTableData();\r\n\r\n    for ( var i = 1, row; row = editorTable.rows[ i ]; i++ ) {\r\n\r\n        for ( var j = 1, cell; cell = row.cells[ j ]; j++ ) {\r\n\r\n            cell.innerHTML = tableData[ i ] [ j ];\r\n\r\n        }\r\n\r\n    }\r\n\r\n}"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/emotion/emotion.css",
    "content": ".jd img{\r\n    background:transparent url(images/jxface2.gif?v=1.1) no-repeat scroll left top;\r\n    cursor:pointer;width:35px;height:35px;display:block;\r\n}\r\n.pp img{\r\n    background:transparent url(images/fface.gif?v=1.1) no-repeat scroll left top;\r\n    cursor:pointer;width:25px;height:25px;display:block;\r\n}\r\n.ldw img{\r\n    background:transparent url(images/wface.gif?v=1.1) no-repeat scroll left top;\r\n    cursor:pointer;width:35px;height:35px;display:block;\r\n}\r\n.tsj img{\r\n    background:transparent url(images/tface.gif?v=1.1) no-repeat scroll left top;\r\n    cursor:pointer;width:35px;height:35px;display:block;\r\n}\r\n.cat img{\r\n    background:transparent url(images/cface.gif?v=1.1) no-repeat scroll left top;\r\n    cursor:pointer;width:35px;height:35px;display:block;\r\n}\r\n.bb img{\r\n    background:transparent url(images/bface.gif?v=1.1) no-repeat scroll left top;\r\n    cursor:pointer;width:35px;height:35px;display:block;\r\n}\r\n.youa img{\r\n    background:transparent url(images/yface.gif?v=1.1) no-repeat scroll left top;\r\n    cursor:pointer;width:35px;height:35px;display:block;\r\n}\r\n\r\n.smileytable td {height: 37px;}\r\n#tabPanel{margin-left:5px;overflow: hidden;}\r\n#tabContent {float:left;background:#FFFFFF;}\r\n#tabContent div{display: none;width:480px;overflow:hidden;}\r\n#tabIconReview.show{left:17px;display:block;}\r\n.menuFocus{background:#ACCD3C;}\r\n.menuDefault{background:#FFFFFF;}\r\n#tabIconReview{position:absolute;left:406px;left:398px \\9;top:41px;z-index:65533;width:90px;height:76px;}\r\nimg.review{width:90px;height:76px;border:2px solid #9cb945;background:#FFFFFF;background-position:center;background-repeat:no-repeat;}\r\n\r\n.wrapper .tabbody{position:relative;float:left;clear:both;padding:10px;width: 95%;}\r\n.tabbody table{width: 100%;}\r\n.tabbody td{border:1px solid #BAC498;}\r\n.tabbody td span{display: block;zoom:1;padding:0 4px;}"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/emotion/emotion.html",
    "content": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" >\r\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\r\n<head>\r\n    <title></title>\r\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\r\n    <meta name=\"robots\" content=\"noindex, nofollow\"/>\r\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"emotion.css\">\r\n</head>\r\n<body>\r\n<div id=\"tabPanel\" class=\"wrapper\">\r\n    <div id=\"tabHeads\" class=\"tabhead\">\r\n        <span><var id=\"lang_input_choice\"></var></span>\r\n        <span><var id=\"lang_input_Tuzki\"></var></span>\r\n        <span><var id=\"lang_input_lvdouwa\"></var></span>\r\n        <span><var id=\"lang_input_BOBO\"></var></span>\r\n        <span><var id=\"lang_input_babyCat\"></var></span>\r\n        <span><var id=\"lang_input_bubble\"></var></span>\r\n        <span><var id=\"lang_input_youa\"></var></span>\r\n    </div>\r\n    <div id=\"tabBodys\" class=\"tabbody\">\r\n        <div id=\"tab0\"></div>\r\n        <div id=\"tab1\"></div>\r\n        <div id=\"tab2\"></div>\r\n        <div id=\"tab3\"></div>\r\n        <div id=\"tab4\"></div>\r\n        <div id=\"tab5\"></div>\r\n        <div id=\"tab6\"></div>\r\n    </div>\r\n</div>\r\n<div id=\"tabIconReview\">\r\n    <img id='faceReview' class='review' src=\"../../themes/default/images/spacer.gif\"/>\r\n</div>\r\n<script type=\"text/javascript\" src=\"emotion.js\"></script>\r\n<script type=\"text/javascript\">\r\n    var emotion = {\r\n        tabNum:7, //切换面板数量\r\n        SmilmgName:{ tab0:['j_00', 84], tab1:['t_00', 40], tab2:['w_00', 52], tab3:['B_00', 63], tab4:['C_00', 20], tab5:['i_f', 50], tab6:['y_00', 40] }, //图片前缀名\r\n        imageFolders:{ tab0:'jx2/', tab1:'tsj/', tab2:'ldw/', tab3:'bobo/', tab4:'babycat/', tab5:'face/', tab6:'youa/'}, //图片对应文件夹路径\r\n        imageCss:{tab0:'jd', tab1:'tsj', tab2:'ldw', tab3:'bb', tab4:'cat', tab5:'pp', tab6:'youa'}, //图片css类名\r\n        imageCssOffset:{tab0:35, tab1:35, tab2:35, tab3:35, tab4:35, tab5:25, tab6:35}, //图片偏移\r\n        SmileyInfor:{\r\n            tab0:['Kiss', 'Love', 'Yeah', '啊！', '背扭', '顶', '抖胸', '88', '汗', '瞌睡', '鲁拉', '拍砖', '揉脸', '生日快乐', '大笑', '瀑布汗~', '惊讶', '臭美', '傻笑', '抛媚眼', '发怒', '打酱油', '俯卧撑', '气愤', '?', '吻', '怒', '胜利', 'HI', 'KISS', '不说', '不要', '扯花', '大心', '顶', '大惊', '飞吻', '鬼脸', '害羞', '口水', '狂哭', '来', '发财了', '吃西瓜', '套牢', '害羞', '庆祝', '我来了', '敲打', '晕了', '胜利', '臭美', '被打了', '贪吃', '迎接', '酷', '微笑', '亲吻', '调皮', '惊恐', '耍酷', '发火', '害羞', '汗水', '大哭', '', '加油', '困', '你NB', '晕倒', '开心', '偷笑', '大哭', '滴汗', '叹气', '超赞', '??', '飞吻', '天使', '撒花', '生气', '被砸', '吓傻', '随意吐'],\r\n            tab1:['Kiss', 'Love', 'Yeah', '啊！', '背扭', '顶', '抖胸', '88', '汗', '瞌睡', '鲁拉', '拍砖', '揉脸', '生日快乐', '摊手', '睡觉', '瘫坐', '无聊', '星星闪', '旋转', '也不行', '郁闷', '正Music', '抓墙', '撞墙至死', '歪头', '戳眼', '飘过', '互相拍砖', '砍死你', '扔桌子', '少林寺', '什么？', '转头', '我爱牛奶', '我踢', '摇晃', '晕厥', '在笼子里', '震荡'],\r\n            tab2:['大笑', '瀑布汗~', '惊讶', '臭美', '傻笑', '抛媚眼', '发怒', '我错了', 'money', '气愤', '挑逗', '吻', '怒', '胜利', '委屈', '受伤', '说啥呢？', '闭嘴', '不', '逗你玩儿', '飞吻', '眩晕', '魔法', '我来了', '睡了', '我打', '闭嘴', '打', '打晕了', '刷牙', '爆揍', '炸弹', '倒立', '刮胡子', '邪恶的笑', '不要不要', '爱恋中', '放大仔细看', '偷窥', '超高兴', '晕', '松口气', '我跑', '享受', '修养', '哭', '汗', '啊~', '热烈欢迎', '打酱油', '俯卧撑', '?'],\r\n            tab3:['HI', 'KISS', '不说', '不要', '扯花', '大心', '顶', '大惊', '飞吻', '鬼脸', '害羞', '口水', '狂哭', '来', '泪眼', '流泪', '生气', '吐舌', '喜欢', '旋转', '再见', '抓狂', '汗', '鄙视', '拜', '吐血', '嘘', '打人', '蹦跳', '变脸', '扯肉', '吃To', '吃花', '吹泡泡糖', '大变身', '飞天舞', '回眸', '可怜', '猛抽', '泡泡', '苹果', '亲', '', '骚舞', '烧香', '睡', '套娃娃', '捅捅', '舞倒', '西红柿', '爱慕', '摇', '摇摆', '杂耍', '招财', '被殴', '被球闷', '大惊', '理想', '欧打', '呕吐', '碎', '吐痰'],\r\n            tab4:['发财了', '吃西瓜', '套牢', '害羞', '庆祝', '我来了', '敲打', '晕了', '胜利', '臭美', '被打了', '贪吃', '迎接', '酷', '顶', '幸运', '爱心', '躲', '送花', '选择'],\r\n            tab5:['微笑', '亲吻', '调皮', '惊讶', '耍酷', '发火', '害羞', '汗水', '大哭', '得意', '鄙视', '困', '夸奖', '晕倒', '疑问', '媒婆', '狂吐', '青蛙', '发愁', '亲吻', '', '爱心', '心碎', '玫瑰', '礼物', '哭', '奸笑', '可爱', '得意', '呲牙', '暴汗', '楚楚可怜', '困', '哭', '生气', '惊讶', '口水', '彩虹', '夜空', '太阳', '钱钱', '灯泡', '咖啡', '蛋糕', '音乐', '爱', '胜利', '赞', '鄙视', 'OK'],\r\n            tab6:['男兜', '女兜', '开心', '乖乖', '偷笑', '大笑', '抽泣', '大哭', '无奈', '滴汗', '叹气', '狂晕', '委屈', '超赞', '??', '疑问', '飞吻', '天使', '撒花', '生气', '被砸', '口水', '泪奔', '吓傻', '吐舌头', '点头', '随意吐', '旋转', '困困', '鄙视', '狂顶', '篮球', '再见', '欢迎光临', '恭喜发财', '稍等', '我在线', '恕不议价', '库房有货', '货在路上']\r\n        }\r\n    };\r\n</script>\r\n</body>\r\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/emotion/emotion.js",
    "content": "window.onload = function () {\r\n    editor.setOpt({\r\n        emotionLocalization:false\r\n    });\r\n\r\n    emotion.SmileyPath = editor.options.emotionLocalization === true ? 'images/' : \"http://img.baidu.com/hi/\";\r\n    emotion.SmileyBox = createTabList( emotion.tabNum );\r\n    emotion.tabExist = createArr( emotion.tabNum );\r\n\r\n    initImgName();\r\n    initEvtHandler( \"tabHeads\" );\r\n};\r\n\r\nfunction initImgName() {\r\n    for ( var pro in emotion.SmilmgName ) {\r\n        var tempName = emotion.SmilmgName[pro],\r\n                tempBox = emotion.SmileyBox[pro],\r\n                tempStr = \"\";\r\n\r\n        if ( tempBox.length ) return;\r\n        for ( var i = 1; i <= tempName[1]; i++ ) {\r\n            tempStr = tempName[0];\r\n            if ( i < 10 ) tempStr = tempStr + '0';\r\n            tempStr = tempStr + i + '.gif';\r\n            tempBox.push( tempStr );\r\n        }\r\n    }\r\n}\r\n\r\nfunction initEvtHandler( conId ) {\r\n    var tabHeads = $G( conId );\r\n    for ( var i = 0, j = 0; i < tabHeads.childNodes.length; i++ ) {\r\n        var tabObj = tabHeads.childNodes[i];\r\n        if ( tabObj.nodeType == 1 ) {\r\n            domUtils.on( tabObj, \"click\", (function ( index ) {\r\n                return function () {\r\n                    switchTab( index );\r\n                };\r\n            })( j ) );\r\n            j++;\r\n        }\r\n    }\r\n    switchTab( 0 );\r\n    $G( \"tabIconReview\" ).style.display = 'none';\r\n}\r\n\r\nfunction InsertSmiley( url, evt ) {\r\n    var obj = {\r\n        src:editor.options.emotionLocalization ? editor.options.UEDITOR_HOME_URL + \"dialogs/emotion/\" + url : url\r\n    };\r\n    obj._src = obj.src;\r\n    editor.execCommand( 'insertimage', obj );\r\n    if ( !evt.ctrlKey ) {\r\n        dialog.popup.hide();\r\n    }\r\n}\r\n\r\nfunction switchTab( index ) {\r\n\r\n    autoHeight( index );\r\n    if ( emotion.tabExist[index] == 0 ) {\r\n        emotion.tabExist[index] = 1;\r\n        createTab( 'tab' + index );\r\n    }\r\n    //获取呈现元素句柄数组\r\n    var tabHeads = $G( \"tabHeads\" ).getElementsByTagName( \"span\" ),\r\n            tabBodys = $G( \"tabBodys\" ).getElementsByTagName( \"div\" ),\r\n            i = 0, L = tabHeads.length;\r\n    //隐藏所有呈现元素\r\n    for ( ; i < L; i++ ) {\r\n        tabHeads[i].className = \"\";\r\n        tabBodys[i].style.display = \"none\";\r\n    }\r\n    //显示对应呈现元素\r\n    tabHeads[index].className = \"focus\";\r\n    tabBodys[index].style.display = \"block\";\r\n}\r\n\r\nfunction autoHeight( index ) {\r\n    var iframe = dialog.getDom( \"iframe\" ),\r\n            parent = iframe.parentNode.parentNode;\r\n    switch ( index ) {\r\n        case 0:\r\n            iframe.style.height = \"380px\";\r\n            parent.style.height = \"392px\";\r\n            break;\r\n        case 1:\r\n            iframe.style.height = \"220px\";\r\n            parent.style.height = \"232px\";\r\n            break;\r\n        case 2:\r\n            iframe.style.height = \"260px\";\r\n            parent.style.height = \"272px\";\r\n            break;\r\n        case 3:\r\n            iframe.style.height = \"300px\";\r\n            parent.style.height = \"312px\";\r\n            break;\r\n        case 4:\r\n            iframe.style.height = \"140px\";\r\n            parent.style.height = \"152px\";\r\n            break;\r\n        case 5:\r\n            iframe.style.height = \"260px\";\r\n            parent.style.height = \"272px\";\r\n            break;\r\n        case 6:\r\n            iframe.style.height = \"230px\";\r\n            parent.style.height = \"242px\";\r\n            break;\r\n        default:\r\n\r\n    }\r\n}\r\n\r\n\r\nfunction createTab( tabName ) {\r\n    var faceVersion = \"?v=1.1\", //版本号\r\n            tab = $G( tabName ), //获取将要生成的Div句柄\r\n            imagePath = emotion.SmileyPath + emotion.imageFolders[tabName], //获取显示表情和预览表情的路径\r\n            positionLine = 11 / 2, //中间数\r\n            iWidth = iHeight = 35, //图片长宽\r\n            iColWidth = 3, //表格剩余空间的显示比例\r\n            tableCss = emotion.imageCss[tabName],\r\n            cssOffset = emotion.imageCssOffset[tabName],\r\n            textHTML = ['<table class=\"smileytable\">'],\r\n            i = 0, imgNum = emotion.SmileyBox[tabName].length, imgColNum = 11, faceImage,\r\n            sUrl, realUrl, posflag, offset, infor;\r\n\r\n    for ( ; i < imgNum; ) {\r\n        textHTML.push( '<tr>' );\r\n        for ( var j = 0; j < imgColNum; j++, i++ ) {\r\n            faceImage = emotion.SmileyBox[tabName][i];\r\n            if ( faceImage ) {\r\n                sUrl = imagePath + faceImage + faceVersion;\r\n                realUrl = imagePath + faceImage;\r\n                posflag = j < positionLine ? 0 : 1;\r\n                offset = cssOffset * i * (-1) - 1;\r\n                infor = emotion.SmileyInfor[tabName][i];\r\n\r\n                textHTML.push( '<td  class=\"' + tableCss + '\"   border=\"1\" width=\"' + iColWidth + '%\" style=\"border-collapse:collapse;\" align=\"center\"  bgcolor=\"transparent\" onclick=\"InsertSmiley(\\'' + realUrl.replace( /'/g, \"\\\\'\" ) + '\\',event)\" onmouseover=\"over(this,\\'' + sUrl + '\\',\\'' + posflag + '\\')\" onmouseout=\"out(this)\">' );\r\n                textHTML.push( '<span>' );\r\n                textHTML.push( '<img  style=\"background-position:left ' + offset + 'px;\" title=\"' + infor + '\" src=\"' + emotion.SmileyPath + (editor.options.emotionLocalization ? '0.gif\" width=\"' : 'default/0.gif\" width=\"') + iWidth + '\" height=\"' + iHeight + '\"></img>' );\r\n                textHTML.push( '</span>' );\r\n            } else {\r\n                textHTML.push( '<td width=\"' + iColWidth + '%\"   bgcolor=\"#FFFFFF\">' );\r\n            }\r\n            textHTML.push( '</td>' );\r\n        }\r\n        textHTML.push( '</tr>' );\r\n    }\r\n    textHTML.push( '</table>' );\r\n    textHTML = textHTML.join( \"\" );\r\n    tab.innerHTML = textHTML;\r\n}\r\n\r\nfunction over( td, srcPath, posFlag ) {\r\n    td.style.backgroundColor = \"#ACCD3C\";\r\n    $G( 'faceReview' ).style.backgroundImage = \"url(\" + srcPath + \")\";\r\n    if ( posFlag == 1 ) $G( \"tabIconReview\" ).className = \"show\";\r\n    $G( \"tabIconReview\" ).style.display = 'block';\r\n}\r\n\r\nfunction out( td ) {\r\n    td.style.backgroundColor = \"transparent\";\r\n    var tabIconRevew = $G( \"tabIconReview\" );\r\n    tabIconRevew.className = \"\";\r\n    tabIconRevew.style.display = 'none';\r\n}\r\n\r\nfunction createTabList( tabNum ) {\r\n    var obj = {};\r\n    for ( var i = 0; i < tabNum; i++ ) {\r\n        obj[\"tab\" + i] = [];\r\n    }\r\n    return obj;\r\n}\r\n\r\nfunction createArr( tabNum ) {\r\n    var arr = [];\r\n    for ( var i = 0; i < tabNum; i++ ) {\r\n        arr[i] = 0;\r\n    }\r\n    return arr;\r\n}\r\n\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/gmap/gmap.html",
    "content": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\r\n        \"http://www.w3.org/TR/html4/loose.dtd\">\r\n<html>\r\n<head>\r\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\r\n    <title></title>\r\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n    <style type=\"text/css\">\r\n        .content{width:530px; height: 350px;margin: 10px auto;}\r\n        .content table{width: 100%}\r\n        .content table td{vertical-align: middle;}\r\n        #address{width:220px;height:21px;background: #FFF;border:1px solid #d7d7d7; line-height: 21px;}\r\n    </style>\r\n    <script type=\"text/javascript\" src=\"http://maps.googleapis.com/maps/api/js?sensor=false\"></script>\r\n</head>\r\n<body>\r\n<div class=\"content\">\r\n    <table>\r\n        <tr>\r\n            <td><label for=\"address\"><var id=\"lang_input_address\"></var></label></td>\r\n            <td><input id=\"address\" type=\"text\" /></td>\r\n            <td><a id=\"doSearch\" href=\"javascript:void(0)\" class=\"button\"><var id=\"lang_input_search\"></var></a></td>\r\n        </tr>\r\n    </table>\r\n    <div id=\"container\" style=\"width: 100%; height: 340px;margin: 5px auto; border: 1px solid gray;\"></div>\r\n</div>\r\n<script type=\"text/javascript\">\r\n    domUtils.on(window,\"load\",function(){\r\n        var map = new google.maps.Map(document.getElementById('container'), {\r\n                zoom: 3,\r\n                streetViewControl: false,\r\n                scaleControl: true,\r\n                mapTypeId: google.maps.MapTypeId.ROADMAP\r\n            });\r\n            var imgcss;\r\n            var marker = new google.maps.Marker({\r\n                map: map,\r\n                draggable: true\r\n            });\r\n            function doSearch(){\r\n                var address = document.getElementById('address').value;\r\n                var geocoder = new google.maps.Geocoder();\r\n                geocoder.geocode( { 'address': address}, function (results, status) {\r\n                    if (status == google.maps.GeocoderStatus.OK) {\r\n                        var bounds = results[0].geometry.viewport;\r\n                        map.fitBounds(bounds);\r\n                        marker.setPosition(results[0].geometry.location);\r\n                        marker.setTitle(address);\r\n                    } else alert(lang.searchError);\r\n                });\r\n            }\r\n            $G('address').onkeydown = function (evt){\r\n                evt = evt || event;\r\n                if (evt.keyCode == 13) {\r\n                    doSearch();\r\n                }\r\n            };\r\n            $G(\"doSearch\").onclick = doSearch;\r\n            dialog.onok = function (){\r\n                var center = map.getCenter();\r\n                var point = marker.getPosition();\r\n                var url = \"http://maps.googleapis.com/maps/api/staticmap?center=\" + center.lat() + ',' + center.lng() + \"&zoom=\" + map.zoom + \"&size=520x340&maptype=\" + map.getMapTypeId() + \"&markers=\" + point.lat() + ',' + point.lng() + \"&sensor=false\";\r\n                editor.execCommand('inserthtml', '<img width=\"520\" height=\"340\" src=\"' + url + '\"' + (imgcss ? ' style=\"' + imgcss + '\"' :'') + '/>');\r\n            };\r\n\r\n            function getPars(str,par){\r\n                var reg = new RegExp(par+\"=((\\\\d+|[.,])*)\",\"g\");\r\n                return reg.exec(str)[1];\r\n            }\r\n            var img = editor.selection.getRange().getClosedNode();\r\n            if(img && img.src.indexOf(\"http://maps.googleapis.com/maps/api/staticmap\")!=-1){\r\n                var url = img.getAttribute(\"src\");\r\n                var centers = getPars(url,\"center\").split(\",\");\r\n                point = new google.maps.LatLng(Number(centers[0]),Number(centers[1]));\r\n                map.setCenter(point);\r\n                map.setZoom(Number(getPars(url,\"zoom\")));\r\n                centers = getPars(url,\"markers\").split(\",\");\r\n                marker.setPosition(new google.maps.LatLng(Number(centers[0]),Number(centers[1])));\r\n                imgcss = img.style.cssText;\r\n            }else{\r\n                setTimeout(function(){\r\n                    doSearch();\r\n                },30)\r\n            }\r\n    });\r\n\r\n</script>\r\n</body>\r\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/help/help.css",
    "content": ".wrapper{width: 370px;margin: 10px auto;zoom: 1;}\r\n.tabbody{height: 360px;}\r\n.tabbody .panel{width:100%;height: 360px;position: absolute;background: #fff;}\r\n.tabbody .panel h1{font-size:26px;margin: 5px 0 0 5px;}\r\n.tabbody .panel p{font-size:12px;margin: 5px 0 0 5px;}\r\n.tabbody table{width:90%;line-height: 20px;margin: 5px 0 0 5px;;}\r\n.tabbody table thead{font-weight: bold;line-height: 25px;}"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/help/help.html",
    "content": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\r\n        \"http://www.w3.org/TR/html4/loose.dtd\">\r\n<html>\r\n<head>\r\n    <title>帮助</title>\r\n    <meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\"/>\r\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"help.css\">\r\n</head>\r\n<body>\r\n<div class=\"wrapper\" id=\"helptab\">\r\n    <div id=\"tabHeads\" class=\"tabhead\">\r\n        <span class=\"focus\" tabsrc=\"about\"><var id=\"lang_input_about\"></var></span>\r\n        <span tabsrc=\"shortcuts\"><var id=\"lang_input_shortcuts\"></var></span>\r\n    </div>\r\n    <div id=\"tabBodys\" class=\"tabbody\">\r\n        <div id=\"about\" class=\"panel\">\r\n            <h1>UEditor</h1>\r\n            <p id=\"version\"></p>\r\n            <p><var id=\"lang_input_introduction\"></var></p>\r\n        </div>\r\n        <div id=\"shortcuts\" class=\"panel\">\r\n            <table>\r\n                <thead>\r\n                <tr>\r\n                    <td><var id=\"lang_Txt_shortcuts\"></var></td>\r\n                    <td><var id=\"lang_Txt_func\"></var></td>\r\n                </tr>\r\n                </thead>\r\n                <tbody>\r\n                <tr>\r\n                    <td>ctrl+b</td>\r\n                    <td><var id=\"lang_Txt_bold\"></var></td>\r\n                </tr>\r\n                <tr>\r\n                    <td>ctrl+c</td>\r\n                    <td><var id=\"lang_Txt_copy\"></var></td>\r\n                </tr>\r\n                <tr>\r\n                    <td>ctrl+x</td>\r\n                    <td><var id=\"lang_Txt_cut\"></var></td>\r\n                </tr>\r\n                <tr>\r\n                    <td>ctrl+v</td>\r\n                    <td><var id=\"lang_Txt_Paste\"></var></td>\r\n                </tr>\r\n                <tr>\r\n                    <td>ctrl+y</td>\r\n                    <td><var id=\"lang_Txt_undo\"></var></td>\r\n                </tr>\r\n                <tr>\r\n                    <td>ctrl+z</td>\r\n                    <td><var id=\"lang_Txt_redo\"></var></td>\r\n                </tr>\r\n                <tr>\r\n                    <td>ctrl+i</td>\r\n                    <td><var id=\"lang_Txt_italic\"></var></td>\r\n                </tr>\r\n                <tr>\r\n                    <td>ctrl+u</td>\r\n                    <td><var id=\"lang_Txt_underline\"></var></td>\r\n                </tr>\r\n                <tr>\r\n                    <td>ctrl+a</td>\r\n                    <td><var id=\"lang_Txt_selectAll\"></var></td>\r\n                </tr>\r\n                <tr>\r\n                    <td>shift+enter</td>\r\n                    <td><var id=\"lang_Txt_visualEnter\"></var></td>\r\n                </tr>\r\n                <tr>\r\n                    <td>alt+z</td>\r\n                    <td><var id=\"lang_Txt_fullscreen\"></var></td>\r\n                </tr>\r\n                </tbody>\r\n            </table>\r\n        </div>\r\n    </div>\r\n</div>\r\n<script type=\"text/javascript\" src=\"help.js\"></script>\r\n</body>\r\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/help/help.js",
    "content": "/**\r\n * Created with JetBrains PhpStorm.\r\n * User: xuheng\r\n * Date: 12-9-26\r\n * Time: 下午1:06\r\n * To change this template use File | Settings | File Templates.\r\n */\r\n/**\r\n * tab点击处理事件\r\n * @param tabHeads\r\n * @param tabBodys\r\n * @param obj\r\n */\r\nfunction clickHandler( tabHeads,tabBodys,obj ) {\r\n    //head样式更改\r\n    for ( var k = 0, len = tabHeads.length; k < len; k++ ) {\r\n        tabHeads[k].className = \"\";\r\n    }\r\n    obj.className = \"focus\";\r\n    //body显隐\r\n    var tabSrc = obj.getAttribute( \"tabSrc\" );\r\n    for ( var j = 0, length = tabBodys.length; j < length; j++ ) {\r\n        var body = tabBodys[j],\r\n            id = body.getAttribute( \"id\" );\r\n        body.onclick = function(){\r\n            this.style.zoom = 1;\r\n        };\r\n        if ( id != tabSrc ) {\r\n            body.style.zIndex = 1;\r\n        } else {\r\n            body.style.zIndex = 200;\r\n        }\r\n    }\r\n\r\n}\r\n\r\n/**\r\n * TAB切换\r\n * @param tabParentId  tab的父节点ID或者对象本身\r\n */\r\nfunction switchTab( tabParentId ) {\r\n    var tabElements = $G( tabParentId ).children,\r\n        tabHeads = tabElements[0].children,\r\n        tabBodys = tabElements[1].children;\r\n\r\n    for ( var i = 0, length = tabHeads.length; i < length; i++ ) {\r\n        var head = tabHeads[i];\r\n        if ( head.className === \"focus\" )clickHandler(tabHeads,tabBodys, head );\r\n        head.onclick = function () {\r\n            clickHandler(tabHeads,tabBodys,this);\r\n        }\r\n    }\r\n}\r\nswitchTab(\"helptab\");\r\n\r\ndocument.getElementById('version').innerHTML = parent.UE.version;"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/image/image.css",
    "content": "@charset \"utf-8\";\r\n/* dialog样式 */\r\n.wrapper {\r\n    zoom: 1;\r\n    width: 630px;\r\n    *width: 626px;\r\n    height: 380px;\r\n    margin: 0 auto;\r\n    padding: 10px;\r\n    position: relative;\r\n    font-family: sans-serif;\r\n}\r\n\r\n/*tab样式框大小*/\r\n.tabhead {\r\n    float:left;\r\n}\r\n.tabbody {\r\n    width: 100%;\r\n    height: 346px;\r\n    position: relative;\r\n    clear: both;\r\n}\r\n\r\n.tabbody .panel {\r\n    position: absolute;\r\n    width: 0;\r\n    height: 0;\r\n    background: #fff;\r\n    overflow: hidden;\r\n    display: none;\r\n}\r\n\r\n.tabbody .panel.focus {\r\n    width: 100%;\r\n    height: 346px;\r\n    display: block;\r\n}\r\n\r\n/* 图片对齐方式 */\r\n.alignBar{\r\n    float:right;\r\n    margin-top: 5px;\r\n    position: relative;\r\n}\r\n\r\n.alignBar .algnLabel{\r\n    float:left;\r\n    height: 20px;\r\n    line-height: 20px;\r\n}\r\n\r\n.alignBar #alignIcon{\r\n    zoom:1;\r\n    _display: inline;\r\n    display: inline-block;\r\n    position: relative;\r\n}\r\n.alignBar #alignIcon span{\r\n    float: left;\r\n    cursor: pointer;\r\n    display: block;\r\n    width: 19px;\r\n    height: 17px;\r\n    margin-right: 3px;\r\n    margin-left: 3px;\r\n    background-image: url(./images/alignicon.jpg);\r\n}\r\n.alignBar #alignIcon .none-align{\r\n    background-position: 0 -18px;\r\n}\r\n.alignBar #alignIcon .left-align{\r\n    background-position: -20px -18px;\r\n}\r\n.alignBar #alignIcon .right-align{\r\n    background-position: -40px -18px;\r\n}\r\n.alignBar #alignIcon .center-align{\r\n    background-position: -60px -18px;\r\n}\r\n.alignBar #alignIcon .none-align.focus{\r\n    background-position: 0 0;\r\n}\r\n.alignBar #alignIcon .left-align.focus{\r\n    background-position: -20px 0;\r\n}\r\n.alignBar #alignIcon .right-align.focus{\r\n    background-position: -40px 0;\r\n}\r\n.alignBar #alignIcon .center-align.focus{\r\n    background-position: -60px 0;\r\n}\r\n\r\n\r\n\r\n\r\n/* 远程图片样式 */\r\n#remote {\r\n    z-index: 200;\r\n}\r\n\r\n#remote .top{\r\n    width: 100%;\r\n    margin-top: 25px;\r\n}\r\n#remote .left{\r\n    display: block;\r\n    float: left;\r\n    width: 300px;\r\n    height:10px;\r\n}\r\n#remote .right{\r\n    display: block;\r\n    float: right;\r\n    width: 300px;\r\n    height:10px;\r\n}\r\n#remote .row{\r\n    margin-left: 20px;\r\n    clear: both;\r\n    height: 40px;\r\n}\r\n\r\n#remote .row label{\r\n    text-align: center;\r\n    width: 50px;\r\n    zoom:1;\r\n    _display: inline;\r\n    display:inline-block;\r\n    vertical-align: middle;\r\n}\r\n#remote .row label.algnLabel{\r\n    float: left;\r\n\r\n}\r\n\r\n#remote input.text{\r\n    width: 150px;\r\n    padding: 3px 6px;\r\n    font-size: 14px;\r\n    line-height: 1.42857143;\r\n    color: #555;\r\n    background-color: #fff;\r\n    background-image: none;\r\n    border: 1px solid #ccc;\r\n    border-radius: 4px;\r\n    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\r\n    box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\r\n    -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\r\n    transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\r\n}\r\n#remote input.text:focus {\r\n    border-color: #66afe9;\r\n    outline: 0;\r\n    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6);\r\n    box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6);\r\n}\r\n#remote #url{\r\n    width: 500px;\r\n    margin-bottom: 2px;\r\n}\r\n#remote #width,\r\n#remote #height{\r\n    width: 20px;\r\n    margin-left: 2px;\r\n    margin-right: 2px;\r\n}\r\n#remote #border,\r\n#remote #vhSpace,\r\n#remote #title{\r\n    width: 180px;\r\n    margin-right: 5px;\r\n}\r\n#remote #lock{\r\n}\r\n#remote #lockicon{\r\n    zoom: 1;\r\n    _display:inline;\r\n    display: inline-block;\r\n    width: 20px;\r\n    height: 20px;\r\n    background: url(\"../../themes/default/images/lock.gif\") -13px -13px no-repeat;\r\n    vertical-align: middle;\r\n}\r\n#remote #preview{\r\n    clear: both;\r\n    width: 260px;\r\n    height: 240px;\r\n    z-index: 9999;\r\n    margin-top: 10px;\r\n    background-color: #eee;\r\n    overflow: hidden;\r\n}\r\n\r\n/* 上传图片 */\r\n.tabbody #upload.panel {\r\n    width: 0;\r\n    height: 0;\r\n    overflow: hidden;\r\n    position: absolute !important;\r\n    clip: rect(1px, 1px, 1px, 1px);\r\n    background: #fff;\r\n    display: block;\r\n}\r\n\r\n.tabbody #upload.panel.focus {\r\n    width: 100%;\r\n    height: 346px;\r\n    display: block;\r\n    clip: auto;\r\n}\r\n\r\n#upload .queueList {\r\n    margin: 0;\r\n    width: 100%;\r\n    height: 100%;\r\n    position: absolute;\r\n    overflow: hidden;\r\n}\r\n\r\n#upload p {\r\n    margin: 0;\r\n}\r\n\r\n.element-invisible {\r\n    width: 0 !important;\r\n    height: 0 !important;\r\n    border: 0;\r\n    padding: 0;\r\n    margin: 0;\r\n    overflow: hidden;\r\n    position: absolute !important;\r\n    clip: rect(1px, 1px, 1px, 1px);\r\n}\r\n\r\n#upload .placeholder {\r\n    margin: 10px;\r\n    border: 2px dashed #e6e6e6;\r\n    *border: 0px dashed #e6e6e6;\r\n    height: 172px;\r\n    padding-top: 150px;\r\n    text-align: center;\r\n    background: url(./images/image.png) center 70px no-repeat;\r\n    color: #cccccc;\r\n    font-size: 18px;\r\n    position: relative;\r\n    top:0;\r\n    *top: 10px;\r\n}\r\n\r\n#upload .placeholder .webuploader-pick {\r\n    font-size: 18px;\r\n    background: #00b7ee;\r\n    border-radius: 3px;\r\n    line-height: 44px;\r\n    padding: 0 30px;\r\n    *width: 120px;\r\n    color: #fff;\r\n    display: inline-block;\r\n    margin: 0 auto 20px auto;\r\n    cursor: pointer;\r\n    box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);\r\n}\r\n\r\n#upload .placeholder .webuploader-pick-hover {\r\n    background: #00a2d4;\r\n}\r\n\r\n\r\n#filePickerContainer {\r\n    text-align: center;\r\n}\r\n\r\n#upload .placeholder .flashTip {\r\n    color: #666666;\r\n    font-size: 12px;\r\n    position: absolute;\r\n    width: 100%;\r\n    text-align: center;\r\n    bottom: 20px;\r\n}\r\n\r\n#upload .placeholder .flashTip a {\r\n    color: #0785d1;\r\n    text-decoration: none;\r\n}\r\n\r\n#upload .placeholder .flashTip a:hover {\r\n    text-decoration: underline;\r\n}\r\n\r\n#upload .placeholder.webuploader-dnd-over {\r\n    border-color: #999999;\r\n}\r\n\r\n#upload .filelist {\r\n    list-style: none;\r\n    margin: 0;\r\n    padding: 0;\r\n    overflow-x: hidden;\r\n    overflow-y: auto;\r\n    position: relative;\r\n    height: 300px;\r\n}\r\n\r\n#upload .filelist:after {\r\n    content: '';\r\n    display: block;\r\n    width: 0;\r\n    height: 0;\r\n    overflow: hidden;\r\n    clear: both;\r\n    position: relative;\r\n}\r\n\r\n#upload .filelist li {\r\n    width: 113px;\r\n    height: 113px;\r\n    background: url(./images/bg.png);\r\n    text-align: center;\r\n    margin: 9px 0 0 9px;\r\n    *margin: 6px 0 0 6px;\r\n    position: relative;\r\n    display: block;\r\n    float: left;\r\n    overflow: hidden;\r\n    font-size: 12px;\r\n}\r\n\r\n#upload .filelist li p.log {\r\n    position: relative;\r\n    top: -45px;\r\n}\r\n\r\n#upload .filelist li p.title {\r\n    position: absolute;\r\n    top: 0;\r\n    left: 0;\r\n    width: 100%;\r\n    overflow: hidden;\r\n    white-space: nowrap;\r\n    text-overflow: ellipsis;\r\n    top: 5px;\r\n    text-indent: 5px;\r\n    text-align: left;\r\n}\r\n\r\n#upload .filelist li p.progress {\r\n    position: absolute;\r\n    width: 100%;\r\n    bottom: 0;\r\n    left: 0;\r\n    height: 8px;\r\n    overflow: hidden;\r\n    z-index: 50;\r\n    margin: 0;\r\n    border-radius: 0;\r\n    background: none;\r\n    -webkit-box-shadow: 0 0 0;\r\n}\r\n\r\n#upload .filelist li p.progress span {\r\n    display: none;\r\n    overflow: hidden;\r\n    width: 0;\r\n    height: 100%;\r\n    background: #1483d8 url(./images/progress.png) repeat-x;\r\n\r\n    -webit-transition: width 200ms linear;\r\n    -moz-transition: width 200ms linear;\r\n    -o-transition: width 200ms linear;\r\n    -ms-transition: width 200ms linear;\r\n    transition: width 200ms linear;\r\n\r\n    -webkit-animation: progressmove 2s linear infinite;\r\n    -moz-animation: progressmove 2s linear infinite;\r\n    -o-animation: progressmove 2s linear infinite;\r\n    -ms-animation: progressmove 2s linear infinite;\r\n    animation: progressmove 2s linear infinite;\r\n\r\n    -webkit-transform: translateZ(0);\r\n}\r\n\r\n@-webkit-keyframes progressmove {\r\n    0% {\r\n        background-position: 0 0;\r\n    }\r\n    100% {\r\n        background-position: 17px 0;\r\n    }\r\n}\r\n\r\n@-moz-keyframes progressmove {\r\n    0% {\r\n        background-position: 0 0;\r\n    }\r\n    100% {\r\n        background-position: 17px 0;\r\n    }\r\n}\r\n\r\n@keyframes progressmove {\r\n    0% {\r\n        background-position: 0 0;\r\n    }\r\n    100% {\r\n        background-position: 17px 0;\r\n    }\r\n}\r\n\r\n#upload .filelist li p.imgWrap {\r\n    position: relative;\r\n    z-index: 2;\r\n    line-height: 113px;\r\n    vertical-align: middle;\r\n    overflow: hidden;\r\n    width: 113px;\r\n    height: 113px;\r\n\r\n    -webkit-transform-origin: 50% 50%;\r\n    -moz-transform-origin: 50% 50%;\r\n    -o-transform-origin: 50% 50%;\r\n    -ms-transform-origin: 50% 50%;\r\n    transform-origin: 50% 50%;\r\n\r\n    -webit-transition: 200ms ease-out;\r\n    -moz-transition: 200ms ease-out;\r\n    -o-transition: 200ms ease-out;\r\n    -ms-transition: 200ms ease-out;\r\n    transition: 200ms ease-out;\r\n}\r\n\r\n#upload .filelist li img {\r\n    width: 100%;\r\n}\r\n\r\n#upload .filelist li p.error {\r\n    background: #f43838;\r\n    color: #fff;\r\n    position: absolute;\r\n    bottom: 0;\r\n    left: 0;\r\n    height: 28px;\r\n    line-height: 28px;\r\n    width: 100%;\r\n    z-index: 100;\r\n    display:none;\r\n}\r\n\r\n#upload .filelist li .success {\r\n    display: block;\r\n    position: absolute;\r\n    left: 0;\r\n    bottom: 0;\r\n    height: 40px;\r\n    width: 100%;\r\n    z-index: 200;\r\n    background: url(./images/success.png) no-repeat right bottom;\r\n    background: url(./images/success.gif) no-repeat right bottom \\9;\r\n}\r\n\r\n#upload .filelist li.filePickerBlock {\r\n    width: 113px;\r\n    height: 113px;\r\n    background: url(./images/image.png) no-repeat center 12px;\r\n    border: 1px solid #eeeeee;\r\n    border-radius: 0;\r\n}\r\n#upload .filelist li.filePickerBlock div.webuploader-pick  {\r\n    width: 100%;\r\n    height: 100%;\r\n    margin: 0;\r\n    padding: 0;\r\n    opacity: 0;\r\n    background: none;\r\n    font-size: 0;\r\n}\r\n\r\n#upload .filelist div.file-panel {\r\n    position: absolute;\r\n    height: 0;\r\n    filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr='#80000000', endColorstr='#80000000') \\0;\r\n    background: rgba(0, 0, 0, 0.5);\r\n    width: 100%;\r\n    top: 0;\r\n    left: 0;\r\n    overflow: hidden;\r\n    z-index: 300;\r\n}\r\n\r\n#upload .filelist div.file-panel span {\r\n    width: 24px;\r\n    height: 24px;\r\n    display: inline;\r\n    float: right;\r\n    text-indent: -9999px;\r\n    overflow: hidden;\r\n    background: url(./images/icons.png) no-repeat;\r\n    background: url(./images/icons.gif) no-repeat \\9;\r\n    margin: 5px 1px 1px;\r\n    cursor: pointer;\r\n    -webkit-tap-highlight-color: rgba(0,0,0,0);\r\n    -webkit-user-select: none;\r\n    -moz-user-select: none;\r\n    -ms-user-select: none;\r\n    user-select: none;\r\n}\r\n\r\n#upload .filelist div.file-panel span.rotateLeft {\r\n    display:none;\r\n    background-position: 0 -24px;\r\n}\r\n\r\n#upload .filelist div.file-panel span.rotateLeft:hover {\r\n    background-position: 0 0;\r\n}\r\n\r\n#upload .filelist div.file-panel span.rotateRight {\r\n    display:none;\r\n    background-position: -24px -24px;\r\n}\r\n\r\n#upload .filelist div.file-panel span.rotateRight:hover {\r\n    background-position: -24px 0;\r\n}\r\n\r\n#upload .filelist div.file-panel span.cancel {\r\n    background-position: -48px -24px;\r\n}\r\n\r\n#upload .filelist div.file-panel span.cancel:hover {\r\n    background-position: -48px 0;\r\n}\r\n\r\n#upload .statusBar {\r\n    height: 45px;\r\n    border-bottom: 1px solid #dadada;\r\n    margin: 0 10px;\r\n    padding: 0;\r\n    line-height: 45px;\r\n    vertical-align: middle;\r\n    position: relative;\r\n}\r\n\r\n#upload .statusBar .progress {\r\n    border: 1px solid #1483d8;\r\n    width: 198px;\r\n    background: #fff;\r\n    height: 18px;\r\n    position: absolute;\r\n    top: 12px;\r\n    display: none;\r\n    text-align: center;\r\n    line-height: 18px;\r\n    color: #6dbfff;\r\n    margin: 0 10px 0 0;\r\n}\r\n#upload .statusBar .progress span.percentage {\r\n    width: 0;\r\n    height: 100%;\r\n    left: 0;\r\n    top: 0;\r\n    background: #1483d8;\r\n    position: absolute;\r\n}\r\n#upload .statusBar .progress span.text {\r\n    position: relative;\r\n    z-index: 10;\r\n}\r\n\r\n#upload .statusBar .info {\r\n    display: inline-block;\r\n    font-size: 14px;\r\n    color: #666666;\r\n}\r\n\r\n#upload .statusBar .btns {\r\n    position: absolute;\r\n    top: 7px;\r\n    right: 0;\r\n    line-height: 30px;\r\n}\r\n\r\n#filePickerBtn {\r\n    display: inline-block;\r\n    float: left;\r\n}\r\n#upload .statusBar .btns .webuploader-pick,\r\n#upload .statusBar .btns .uploadBtn,\r\n#upload .statusBar .btns .uploadBtn.state-uploading,\r\n#upload .statusBar .btns .uploadBtn.state-paused {\r\n    background: #ffffff;\r\n    border: 1px solid #cfcfcf;\r\n    color: #565656;\r\n    padding: 0 18px;\r\n    display: inline-block;\r\n    border-radius: 3px;\r\n    margin-left: 10px;\r\n    cursor: pointer;\r\n    font-size: 14px;\r\n    float: left;\r\n    -webkit-user-select: none;\r\n    -moz-user-select: none;\r\n    -ms-user-select: none;\r\n    user-select: none;\r\n}\r\n#upload .statusBar .btns .webuploader-pick-hover,\r\n#upload .statusBar .btns .uploadBtn:hover,\r\n#upload .statusBar .btns .uploadBtn.state-uploading:hover,\r\n#upload .statusBar .btns .uploadBtn.state-paused:hover {\r\n    background: #f0f0f0;\r\n}\r\n\r\n#upload .statusBar .btns .uploadBtn,\r\n#upload .statusBar .btns .uploadBtn.state-paused{\r\n    background: #00b7ee;\r\n    color: #fff;\r\n    border-color: transparent;\r\n}\r\n#upload .statusBar .btns .uploadBtn:hover,\r\n#upload .statusBar .btns .uploadBtn.state-paused:hover{\r\n    background: #00a2d4;\r\n}\r\n\r\n#upload .statusBar .btns .uploadBtn.disabled {\r\n    pointer-events: none;\r\n    filter:alpha(opacity=60);\r\n    -moz-opacity:0.6;\r\n    -khtml-opacity: 0.6;\r\n    opacity: 0.6;\r\n}\r\n\r\n\r\n\r\n/* 图片管理样式 */\r\n#online {\r\n    width: 100%;\r\n    height: 336px;\r\n    padding: 10px 0 0 0;\r\n}\r\n#online #imageList{\r\n    width: 100%;\r\n    height: 100%;\r\n    overflow-x: hidden;\r\n    overflow-y: auto;\r\n    position: relative;\r\n}\r\n#online ul {\r\n    display: block;\r\n    list-style: none;\r\n    margin: 0;\r\n    padding: 0;\r\n}\r\n#online li {\r\n    float: left;\r\n    display: block;\r\n    list-style: none;\r\n    padding: 0;\r\n    width: 113px;\r\n    height: 113px;\r\n    margin: 0 0 9px 9px;\r\n    *margin: 0 0 6px 6px;\r\n    background-color: #eee;\r\n    overflow: hidden;\r\n    cursor: pointer;\r\n    position: relative;\r\n}\r\n#online li.clearFloat {\r\n    float: none;\r\n    clear: both;\r\n    display: block;\r\n    width:0;\r\n    height:0;\r\n    margin: 0;\r\n    padding: 0;\r\n}\r\n#online li img {\r\n    cursor: pointer;\r\n}\r\n#online li .icon {\r\n    cursor: pointer;\r\n    width: 113px;\r\n    height: 113px;\r\n    position: absolute;\r\n    top: 0;\r\n    left: 0;\r\n    z-index: 2;\r\n    border: 0;\r\n    background-repeat: no-repeat;\r\n}\r\n#online li .icon:hover {\r\n    width: 107px;\r\n    height: 107px;\r\n    border: 3px solid #1094fa;\r\n}\r\n#online li.selected .icon {\r\n    background-image: url(images/success.png);\r\n    background-image: url(images/success.gif)\\9;\r\n    background-position: 75px 75px;\r\n}\r\n#online li.selected .icon:hover {\r\n    width: 107px;\r\n    height: 107px;\r\n    border: 3px solid #1094fa;\r\n    background-position: 72px 72px;\r\n}\r\n\r\n\r\n/* 图片搜索样式 */\r\n#search .searchBar {\r\n    width: 100%;\r\n    height: 30px;\r\n    margin: 10px 0 5px 0;\r\n    padding: 0;\r\n}\r\n\r\n#search input.text{\r\n    width: 150px;\r\n    padding: 3px 6px;\r\n    font-size: 14px;\r\n    line-height: 1.42857143;\r\n    color: #555;\r\n    background-color: #fff;\r\n    background-image: none;\r\n    border: 1px solid #ccc;\r\n    border-radius: 4px;\r\n    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\r\n    box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\r\n    -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\r\n    transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\r\n}\r\n#search input.text:focus {\r\n    border-color: #66afe9;\r\n    outline: 0;\r\n    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6);\r\n    box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6);\r\n}\r\n#search input.searchTxt {\r\n    margin-left:5px;\r\n    padding-left: 5px;\r\n    background: #FFF;\r\n    width: 300px;\r\n    *width: 260px;\r\n    height: 21px;\r\n    line-height: 21px;\r\n    float: left;\r\n    dislay: block;\r\n}\r\n\r\n#search .searchType {\r\n    width: 65px;\r\n    height: 28px;\r\n    padding:0;\r\n    line-height: 28px;\r\n    border: 1px solid #d7d7d7;\r\n    border-radius: 0;\r\n    vertical-align: top;\r\n    margin-left: 5px;\r\n    float: left;\r\n    dislay: block;\r\n}\r\n\r\n#search #searchBtn,\r\n#search #searchReset {\r\n    display: inline-block;\r\n    margin-bottom: 0;\r\n    margin-right: 5px;\r\n    padding: 4px 10px;\r\n    font-weight: 400;\r\n    text-align: center;\r\n    vertical-align: middle;\r\n    cursor: pointer;\r\n    background-image: none;\r\n    border: 1px solid transparent;\r\n    white-space: nowrap;\r\n    font-size: 14px;\r\n    border-radius: 4px;\r\n    -webkit-user-select: none;\r\n    -moz-user-select: none;\r\n    -ms-user-select: none;\r\n    user-select: none;\r\n    vertical-align: top;\r\n    float: right;\r\n}\r\n\r\n#search #searchBtn {\r\n    color: white;\r\n    border-color: #285e8e;\r\n    background-color: #3b97d7;\r\n}\r\n#search #searchReset {\r\n    color: #333;\r\n    border-color: #ccc;\r\n    background-color: #fff;\r\n}\r\n#search #searchBtn:hover {\r\n    background-color: #3276b1;\r\n}\r\n#search #searchReset:hover {\r\n    background-color: #eee;\r\n}\r\n\r\n#search .msg {\r\n    margin-left: 5px;\r\n}\r\n\r\n#search .searchList{\r\n    width: 100%;\r\n    height: 300px;\r\n    overflow: hidden;\r\n    clear: both;\r\n}\r\n#search .searchList ul{\r\n    margin:0;\r\n    padding:0;\r\n    list-style:none;\r\n    clear: both;\r\n    width: 100%;\r\n    height: 100%;\r\n    overflow-x: hidden;\r\n    overflow-y: auto;\r\n    zoom: 1;\r\n    position: relative;\r\n}\r\n\r\n#search .searchList li {\r\n    list-style:none;\r\n    float: left;\r\n    display: block;\r\n    width: 115px;\r\n    margin: 5px 10px 5px 20px;\r\n    *margin: 5px 10px 5px 15px;\r\n    padding:0;\r\n    font-size: 12px;\r\n    box-shadow: 0 1px 3px rgba(0, 0, 0, .3);\r\n    -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, .3);\r\n    -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, .3);\r\n    position: relative;\r\n    vertical-align: top;\r\n    text-align: center;\r\n    overflow: hidden;\r\n    cursor: pointer;\r\n    filter: alpha(Opacity=100);\r\n    -moz-opacity: 1;\r\n    opacity: 1;\r\n    border: 2px solid #eee;\r\n}\r\n\r\n#search .searchList li.selected {\r\n    filter: alpha(Opacity=40);\r\n    -moz-opacity: 0.4;\r\n    opacity: 0.4;\r\n    border: 2px solid #00a0e9;\r\n}\r\n\r\n#search .searchList li p {\r\n    background-color: #eee;\r\n    margin: 0;\r\n    padding: 0;\r\n    position: relative;\r\n    width:100%;\r\n    height:115px;\r\n    overflow: hidden;\r\n}\r\n\r\n#search .searchList li p img {\r\n    cursor: pointer;\r\n    border: 0;\r\n}\r\n\r\n#search .searchList li a {\r\n    color: #999;\r\n    border-top: 1px solid #F2F2F2;\r\n    background: #FAFAFA;\r\n    text-align: center;\r\n    display: block;\r\n    padding: 0 5px;\r\n    width: 105px;\r\n    height:32px;\r\n    line-height:32px;\r\n    white-space:nowrap;\r\n    text-overflow:ellipsis;\r\n    text-decoration: none;\r\n    overflow: hidden;\r\n    word-break: break-all;\r\n}\r\n\r\n#search .searchList a:hover {\r\n    text-decoration: underline;\r\n    color: #333;\r\n}\r\n#search .searchList .clearFloat{\r\n    clear: both;\r\n}"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/image/image.html",
    "content": "<!doctype html>\r\n<html>\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <title>ueditor图片对话框</title>\r\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n\r\n    <!-- jquery -->\r\n    <script type=\"text/javascript\" src=\"../../third-party/jquery-1.10.2.min.js\"></script>\r\n\r\n    <!-- webuploader -->\r\n    <script src=\"../../third-party/webuploader/webuploader.min.js\"></script>\r\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"../../third-party/webuploader/webuploader.css\">\r\n\r\n    <!-- image dialog -->\r\n    <link rel=\"stylesheet\" href=\"image.css\" type=\"text/css\" />\r\n</head>\r\n<body>\r\n\r\n    <div class=\"wrapper\">\r\n        <div id=\"tabhead\" class=\"tabhead\">\r\n            <span class=\"tab\" data-content-id=\"remote\"><var id=\"lang_tab_remote\"></var></span>\r\n            <span class=\"tab focus\" data-content-id=\"upload\"><var id=\"lang_tab_upload\"></var></span>\r\n            <span class=\"tab\" data-content-id=\"online\"><var id=\"lang_tab_online\"></var></span>\r\n            <span class=\"tab\" data-content-id=\"search\"><var id=\"lang_tab_search\"></var></span>\r\n        </div>\r\n        <div class=\"alignBar\">\r\n            <label class=\"algnLabel\"><var id=\"lang_input_align\"></var></label>\r\n                    <span id=\"alignIcon\">\r\n                        <span id=\"noneAlign\" class=\"none-align focus\" data-align=\"none\"></span>\r\n                        <span id=\"leftAlign\" class=\"left-align\" data-align=\"left\"></span>\r\n                        <span id=\"rightAlign\" class=\"right-align\" data-align=\"right\"></span>\r\n                        <span id=\"centerAlign\" class=\"center-align\" data-align=\"center\"></span>\r\n                    </span>\r\n            <input id=\"align\" name=\"align\" type=\"hidden\" value=\"none\"/>\r\n        </div>\r\n        <div id=\"tabbody\" class=\"tabbody\">\r\n\r\n            <!-- 远程图片 -->\r\n            <div id=\"remote\" class=\"panel\">\r\n                <div class=\"top\">\r\n                    <div class=\"row\">\r\n                        <label for=\"url\"><var id=\"lang_input_url\"></var></label>\r\n                        <span><input class=\"text\" id=\"url\" type=\"text\"/></span>\r\n                    </div>\r\n                </div>\r\n                <div class=\"left\">\r\n                    <div class=\"row\">\r\n                        <label><var id=\"lang_input_size\"></var></label>\r\n                        <span><var id=\"lang_input_width\">&nbsp;&nbsp;</var><input class=\"text\" type=\"text\" id=\"width\"/>px </span>\r\n                        <span><var id=\"lang_input_height\">&nbsp;&nbsp;</var><input class=\"text\" type=\"text\" id=\"height\"/>px </span>\r\n                        <span><input id=\"lock\" type=\"checkbox\" disabled=\"disabled\"><span id=\"lockicon\"></span></span>\r\n                    </div>\r\n                    <div class=\"row\">\r\n                        <label><var id=\"lang_input_border\"></var></label>\r\n                        <span><input class=\"text\" type=\"text\" id=\"border\"/>px </span>\r\n                    </div>\r\n                    <div class=\"row\">\r\n                        <label><var id=\"lang_input_vhspace\"></var></label>\r\n                        <span><input class=\"text\" type=\"text\" id=\"vhSpace\"/>px </span>\r\n                    </div>\r\n                    <div class=\"row\">\r\n                        <label><var id=\"lang_input_title\"></var></label>\r\n                        <span><input class=\"text\" type=\"text\" id=\"title\"/></span>\r\n                    </div>\r\n                </div>\r\n                <div class=\"right\"><div id=\"preview\"></div></div>\r\n            </div>\r\n\r\n            <!-- 上传图片 -->\r\n            <div id=\"upload\" class=\"panel focus\">\r\n                <div id=\"queueList\" class=\"queueList\">\r\n                    <div class=\"statusBar element-invisible\">\r\n                        <div class=\"progress\">\r\n                            <span class=\"text\">0%</span>\r\n                            <span class=\"percentage\"></span>\r\n                        </div><div class=\"info\"></div>\r\n                        <div class=\"btns\">\r\n                            <div id=\"filePickerBtn\"></div>\r\n                            <div class=\"uploadBtn\"><var id=\"lang_start_upload\"></var></div>\r\n                        </div>\r\n                    </div>\r\n                    <div id=\"dndArea\" class=\"placeholder\">\r\n                        <div class=\"filePickerContainer\">\r\n                            <div id=\"filePickerReady\"></div>\r\n                        </div>\r\n                    </div>\r\n                    <ul class=\"filelist element-invisible\">\r\n                        <li id=\"filePickerBlock\" class=\"filePickerBlock\"></li>\r\n                    </ul>\r\n                </div>\r\n            </div>\r\n\r\n            <!-- 在线图片 -->\r\n            <div id=\"online\" class=\"panel\">\r\n                <div id=\"imageList\"><var id=\"lang_imgLoading\"></var></div>\r\n            </div>\r\n\r\n            <!-- 搜索图片 -->\r\n            <div id=\"search\" class=\"panel\">\r\n                <div class=\"searchBar\">\r\n                    <input id=\"searchTxt\" class=\"searchTxt text\" type=\"text\" />\r\n                    <select id=\"searchType\" class=\"searchType\">\r\n                        <option value=\"&s=4&z=0\"></option>\r\n                        <option value=\"&s=1&z=19\"></option>\r\n                        <option value=\"&s=2&z=0\"></option>\r\n                        <option value=\"&s=3&z=0\"></option>\r\n                    </select>\r\n                    <input id=\"searchReset\" type=\"button\"  />\r\n                    <input id=\"searchBtn\" type=\"button\"  />\r\n                </div>\r\n                <div id=\"searchList\" class=\"searchList\"><ul id=\"searchListUl\"></ul></div>\r\n            </div>\r\n\r\n        </div>\r\n    </div>\r\n    <script type=\"text/javascript\" src=\"image.js\"></script>\r\n\r\n</body>\r\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/image/image.js",
    "content": "/**\r\n * User: Jinqn\r\n * Date: 14-04-08\r\n * Time: 下午16:34\r\n * 上传图片对话框逻辑代码,包括tab: 远程图片/上传图片/在线图片/搜索图片\r\n */\r\n\r\n(function () {\r\n\r\n    var remoteImage,\r\n        uploadImage,\r\n        onlineImage,\r\n        searchImage;\r\n\r\n    window.onload = function () {\r\n        initTabs();\r\n        initAlign();\r\n        initButtons();\r\n    };\r\n\r\n    /* 初始化tab标签 */\r\n    function initTabs() {\r\n        var tabs = $G('tabhead').children;\r\n        for (var i = 0; i < tabs.length; i++) {\r\n            domUtils.on(tabs[i], \"click\", function (e) {\r\n                var target = e.target || e.srcElement;\r\n                setTabFocus(target.getAttribute('data-content-id'));\r\n            });\r\n        }\r\n\r\n        var img = editor.selection.getRange().getClosedNode();\r\n        if (img && img.tagName && img.tagName.toLowerCase() == 'img') {\r\n            setTabFocus('remote');\r\n        } else {\r\n            setTabFocus('upload');\r\n        }\r\n    }\r\n\r\n    /* 初始化tabbody */\r\n    function setTabFocus(id) {\r\n        if(!id) return;\r\n        var i, bodyId, tabs = $G('tabhead').children;\r\n        for (i = 0; i < tabs.length; i++) {\r\n            bodyId = tabs[i].getAttribute('data-content-id');\r\n            if (bodyId == id) {\r\n                domUtils.addClass(tabs[i], 'focus');\r\n                domUtils.addClass($G(bodyId), 'focus');\r\n            } else {\r\n                domUtils.removeClasses(tabs[i], 'focus');\r\n                domUtils.removeClasses($G(bodyId), 'focus');\r\n            }\r\n        }\r\n        switch (id) {\r\n            case 'remote':\r\n                remoteImage = remoteImage || new RemoteImage();\r\n                break;\r\n            case 'upload':\r\n                setAlign(editor.getOpt('imageInsertAlign'));\r\n                uploadImage = uploadImage || new UploadImage('queueList');\r\n                break;\r\n            case 'online':\r\n                setAlign(editor.getOpt('imageManagerInsertAlign'));\r\n                onlineImage = onlineImage || new OnlineImage('imageList');\r\n                onlineImage.reset();\r\n                break;\r\n            case 'search':\r\n                setAlign(editor.getOpt('imageManagerInsertAlign'));\r\n                searchImage = searchImage || new SearchImage();\r\n                break;\r\n        }\r\n    }\r\n\r\n    /* 初始化onok事件 */\r\n    function initButtons() {\r\n\r\n        dialog.onok = function () {\r\n            var remote = false, list = [], id, tabs = $G('tabhead').children;\r\n            for (var i = 0; i < tabs.length; i++) {\r\n                if (domUtils.hasClass(tabs[i], 'focus')) {\r\n                    id = tabs[i].getAttribute('data-content-id');\r\n                    break;\r\n                }\r\n            }\r\n\r\n            switch (id) {\r\n                case 'remote':\r\n                    list = remoteImage.getInsertList();\r\n                    break;\r\n                case 'upload':\r\n                    list = uploadImage.getInsertList();\r\n                    var count = uploadImage.getQueueCount();\r\n                    if (count) {\r\n                        $('.info', '#queueList').html('<span style=\"color:red;\">' + '还有2个未上传文件'.replace(/[\\d]/, count) + '</span>');\r\n                        return false;\r\n                    }\r\n                    break;\r\n                case 'online':\r\n                    list = onlineImage.getInsertList();\r\n                    break;\r\n                case 'search':\r\n                    list = searchImage.getInsertList();\r\n                    remote = true;\r\n                    break;\r\n            }\r\n\r\n            if(list) {\r\n                editor.execCommand('insertimage', list);\r\n                remote && editor.fireEvent(\"catchRemoteImage\");\r\n            }\r\n        };\r\n    }\r\n\r\n\r\n    /* 初始化对其方式的点击事件 */\r\n    function initAlign(){\r\n        /* 点击align图标 */\r\n        domUtils.on($G(\"alignIcon\"), 'click', function(e){\r\n            var target = e.target || e.srcElement;\r\n            if(target.className && target.className.indexOf('-align') != -1) {\r\n                setAlign(target.getAttribute('data-align'));\r\n            }\r\n        });\r\n    }\r\n\r\n    /* 设置对齐方式 */\r\n    function setAlign(align){\r\n        align = align || 'none';\r\n        var aligns = $G(\"alignIcon\").children;\r\n        for(i = 0; i < aligns.length; i++){\r\n            if(aligns[i].getAttribute('data-align') == align) {\r\n                domUtils.addClass(aligns[i], 'focus');\r\n                $G(\"align\").value = aligns[i].getAttribute('data-align');\r\n            } else {\r\n                domUtils.removeClasses(aligns[i], 'focus');\r\n            }\r\n        }\r\n    }\r\n    /* 获取对齐方式 */\r\n    function getAlign(){\r\n        var align = $G(\"align\").value || 'none';\r\n        return align == 'none' ? '':align;\r\n    }\r\n\r\n\r\n    /* 在线图片 */\r\n    function RemoteImage(target) {\r\n        this.container = utils.isString(target) ? document.getElementById(target) : target;\r\n        this.init();\r\n    }\r\n    RemoteImage.prototype = {\r\n        init: function () {\r\n            this.initContainer();\r\n            this.initEvents();\r\n        },\r\n        initContainer: function () {\r\n            this.dom = {\r\n                'url': $G('url'),\r\n                'width': $G('width'),\r\n                'height': $G('height'),\r\n                'border': $G('border'),\r\n                'vhSpace': $G('vhSpace'),\r\n                'title': $G('title'),\r\n                'align': $G('align')\r\n            };\r\n            var img = editor.selection.getRange().getClosedNode();\r\n            if (img) {\r\n                this.setImage(img);\r\n            }\r\n        },\r\n        initEvents: function () {\r\n            var _this = this,\r\n                locker = $G('lock');\r\n\r\n            /* 改变url */\r\n            domUtils.on($G(\"url\"), 'keyup', updatePreview);\r\n            domUtils.on($G(\"border\"), 'keyup', updatePreview);\r\n            domUtils.on($G(\"title\"), 'keyup', updatePreview);\r\n\r\n            domUtils.on($G(\"width\"), 'keyup', function(){\r\n                updatePreview();\r\n                if(locker.checked) {\r\n                    var proportion =locker.getAttribute('data-proportion');\r\n                    $G('height').value = Math.round(this.value / proportion);\r\n                } else {\r\n                    _this.updateLocker();\r\n                }\r\n            });\r\n            domUtils.on($G(\"height\"), 'keyup', function(){\r\n                updatePreview();\r\n                if(locker.checked) {\r\n                    var proportion =locker.getAttribute('data-proportion');\r\n                    $G('width').value = Math.round(this.value * proportion);\r\n                } else {\r\n                    _this.updateLocker();\r\n                }\r\n            });\r\n            domUtils.on($G(\"lock\"), 'change', function(){\r\n                var proportion = parseInt($G(\"width\").value) /parseInt($G(\"height\").value);\r\n                locker.setAttribute('data-proportion', proportion);\r\n            });\r\n\r\n            function updatePreview(){\r\n                _this.setPreview();\r\n            }\r\n        },\r\n        updateLocker: function(){\r\n            var width = $G('width').value,\r\n                height = $G('height').value,\r\n                locker = $G('lock');\r\n            if(width && height && width == parseInt(width) && height == parseInt(height)) {\r\n                locker.disabled = false;\r\n                locker.title = '';\r\n            } else {\r\n                locker.checked = false;\r\n                locker.disabled = 'disabled';\r\n                locker.title = lang.remoteLockError;\r\n            }\r\n        },\r\n        setImage: function(img){\r\n            /* 不是正常的图片 */\r\n            if (!img.tagName || img.tagName.toLowerCase() != 'img' && !img.getAttribute(\"src\") || !img.src) return;\r\n\r\n            var wordImgFlag = img.getAttribute(\"word_img\"),\r\n                src = wordImgFlag ? wordImgFlag.replace(\"&amp;\", \"&\") : (img.getAttribute('_src') || img.getAttribute(\"src\", 2).replace(\"&amp;\", \"&\")),\r\n                align = editor.queryCommandValue(\"imageFloat\");\r\n\r\n            /* 防止onchange事件循环调用 */\r\n            if (src !== $G(\"url\").value) $G(\"url\").value = src;\r\n            if(src) {\r\n                /* 设置表单内容 */\r\n                $G(\"width\").value = img.width || '';\r\n                $G(\"height\").value = img.height || '';\r\n                $G(\"border\").value = img.getAttribute(\"border\") || '0';\r\n                $G(\"vhSpace\").value = img.getAttribute(\"vspace\") || '0';\r\n                $G(\"title\").value = img.title || img.alt || '';\r\n                setAlign(align);\r\n                this.setPreview();\r\n                this.updateLocker();\r\n            }\r\n        },\r\n        getData: function(){\r\n            var data = {};\r\n            for(var k in this.dom){\r\n                data[k] = this.dom[k].value;\r\n            }\r\n            return data;\r\n        },\r\n        setPreview: function(){\r\n            var url = $G('url').value,\r\n                ow = parseInt($G('width').value, 10) || 0,\r\n                oh = parseInt($G('height').value, 10) || 0,\r\n                border = parseInt($G('border').value, 10) || 0,\r\n                title = $G('title').value,\r\n                preview = $G('preview'),\r\n                width,\r\n                height;\r\n\r\n            url = utils.unhtmlForUrl(url);\r\n            title = utils.unhtml(title);\r\n\r\n            width = ((!ow || !oh) ? preview.offsetWidth:Math.min(ow, preview.offsetWidth));\r\n            width = width+(border*2) > preview.offsetWidth ? width:(preview.offsetWidth - (border*2));\r\n            height = (!ow || !oh) ? '':width*oh/ow;\r\n\r\n            if(url) {\r\n                preview.innerHTML = '<img src=\"' + url + '\" width=\"' + width + '\" height=\"' + height + '\" border=\"' + border + 'px solid #000\" title=\"' + title + '\" />';\r\n            }\r\n        },\r\n        getInsertList: function () {\r\n            var data = this.getData();\r\n            if(data['url']) {\r\n                return [{\r\n                    src: data['url'],\r\n                    _src: data['url'],\r\n                    width: data['width'] || '',\r\n                    height: data['height'] || '',\r\n                    border: data['border'] || '',\r\n                    floatStyle: data['align'] || '',\r\n                    vspace: data['vhSpace'] || '',\r\n                    title: data['title'] || '',\r\n                    alt: data['title'] || '',\r\n                    style: \"width:\" + data['width'] + \"px;height:\" + data['height'] + \"px;\"\r\n                }];\r\n            } else {\r\n                return [];\r\n            }\r\n        }\r\n    };\r\n\r\n\r\n\r\n    /* 上传图片 */\r\n    function UploadImage(target) {\r\n        this.$wrap = target.constructor == String ? $('#' + target) : $(target);\r\n        this.init();\r\n    }\r\n    UploadImage.prototype = {\r\n        init: function () {\r\n            this.imageList = [];\r\n            this.initContainer();\r\n            this.initUploader();\r\n        },\r\n        initContainer: function () {\r\n            this.$queue = this.$wrap.find('.filelist');\r\n        },\r\n        /* 初始化容器 */\r\n        initUploader: function () {\r\n            var _this = this,\r\n                $ = jQuery,    // just in case. Make sure it's not an other libaray.\r\n                $wrap = _this.$wrap,\r\n            // 图片容器\r\n                $queue = $wrap.find('.filelist'),\r\n            // 状态栏，包括进度和控制按钮\r\n                $statusBar = $wrap.find('.statusBar'),\r\n            // 文件总体选择信息。\r\n                $info = $statusBar.find('.info'),\r\n            // 上传按钮\r\n                $upload = $wrap.find('.uploadBtn'),\r\n            // 上传按钮\r\n                $filePickerBtn = $wrap.find('.filePickerBtn'),\r\n            // 上传按钮\r\n                $filePickerBlock = $wrap.find('.filePickerBlock'),\r\n            // 没选择文件之前的内容。\r\n                $placeHolder = $wrap.find('.placeholder'),\r\n            // 总体进度条\r\n                $progress = $statusBar.find('.progress').hide(),\r\n            // 添加的文件数量\r\n                fileCount = 0,\r\n            // 添加的文件总大小\r\n                fileSize = 0,\r\n            // 优化retina, 在retina下这个值是2\r\n                ratio = window.devicePixelRatio || 1,\r\n            // 缩略图大小\r\n                thumbnailWidth = 113 * ratio,\r\n                thumbnailHeight = 113 * ratio,\r\n            // 可能有pedding, ready, uploading, confirm, done.\r\n                state = '',\r\n            // 所有文件的进度信息，key为file id\r\n                percentages = {},\r\n                supportTransition = (function () {\r\n                    var s = document.createElement('p').style,\r\n                        r = 'transition' in s ||\r\n                            'WebkitTransition' in s ||\r\n                            'MozTransition' in s ||\r\n                            'msTransition' in s ||\r\n                            'OTransition' in s;\r\n                    s = null;\r\n                    return r;\r\n                })(),\r\n            // WebUploader实例\r\n                uploader,\r\n                actionUrl = editor.getActionUrl(editor.getOpt('imageActionName')),\r\n                acceptExtensions = (editor.getOpt('imageAllowFiles') || []).join('').replace(/\\./g, ',').replace(/^[,]/, ''),\r\n                imageMaxSize = editor.getOpt('imageMaxSize'),\r\n                imageCompressBorder = editor.getOpt('imageCompressBorder');\r\n\r\n            if (!WebUploader.Uploader.support()) {\r\n                $('#filePickerReady').after($('<div>').html(lang.errorNotSupport)).hide();\r\n                return;\r\n            } else if (!editor.getOpt('imageActionName')) {\r\n                $('#filePickerReady').after($('<div>').html(lang.errorLoadConfig)).hide();\r\n                return;\r\n            }\r\n\r\n            uploader = _this.uploader = WebUploader.create({\r\n                pick: {\r\n                    id: '#filePickerReady',\r\n                    label: lang.uploadSelectFile\r\n                },\r\n                accept: {\r\n                    title: 'Images',\r\n                    extensions: acceptExtensions,\r\n                    mimeTypes: 'image/*'\r\n                },\r\n                swf: '../../third-party/webuploader/Uploader.swf',\r\n                server: actionUrl,\r\n                fileVal: editor.getOpt('imageFieldName'),\r\n                duplicate: true,\r\n                fileSingleSizeLimit: imageMaxSize,    // 默认 2 M\r\n                compress: editor.getOpt('imageCompressEnable') ? {\r\n                    width: imageCompressBorder,\r\n                    height: imageCompressBorder,\r\n                    // 图片质量，只有type为`image/jpeg`的时候才有效。\r\n                    quality: 90,\r\n                    // 是否允许放大，如果想要生成小图的时候不失真，此选项应该设置为false.\r\n                    allowMagnify: false,\r\n                    // 是否允许裁剪。\r\n                    crop: false,\r\n                    // 是否保留头部meta信息。\r\n                    preserveHeaders: true\r\n                }:false\r\n            });\r\n            uploader.addButton({\r\n                id: '#filePickerBlock'\r\n            });\r\n            uploader.addButton({\r\n                id: '#filePickerBtn',\r\n                label: lang.uploadAddFile\r\n            });\r\n\r\n            setState('pedding');\r\n\r\n            // 当有文件添加进来时执行，负责view的创建\r\n            function addFile(file) {\r\n                var $li = $('<li id=\"' + file.id + '\">' +\r\n                        '<p class=\"title\">' + file.name + '</p>' +\r\n                        '<p class=\"imgWrap\"></p>' +\r\n                        '<p class=\"progress\"><span></span></p>' +\r\n                        '</li>'),\r\n\r\n                    $btns = $('<div class=\"file-panel\">' +\r\n                        '<span class=\"cancel\">' + lang.uploadDelete + '</span>' +\r\n                        '<span class=\"rotateRight\">' + lang.uploadTurnRight + '</span>' +\r\n                        '<span class=\"rotateLeft\">' + lang.uploadTurnLeft + '</span></div>').appendTo($li),\r\n                    $prgress = $li.find('p.progress span'),\r\n                    $wrap = $li.find('p.imgWrap'),\r\n                    $info = $('<p class=\"error\"></p>').hide().appendTo($li),\r\n\r\n                    showError = function (code) {\r\n                        switch (code) {\r\n                            case 'exceed_size':\r\n                                text = lang.errorExceedSize;\r\n                                break;\r\n                            case 'interrupt':\r\n                                text = lang.errorInterrupt;\r\n                                break;\r\n                            case 'http':\r\n                                text = lang.errorHttp;\r\n                                break;\r\n                            case 'not_allow_type':\r\n                                text = lang.errorFileType;\r\n                                break;\r\n                            default:\r\n                                text = lang.errorUploadRetry;\r\n                                break;\r\n                        }\r\n                        $info.text(text).show();\r\n                    };\r\n\r\n                if (file.getStatus() === 'invalid') {\r\n                    showError(file.statusText);\r\n                } else {\r\n                    $wrap.text(lang.uploadPreview);\r\n                    if (browser.ie && browser.version <= 7) {\r\n                        $wrap.text(lang.uploadNoPreview);\r\n                    } else {\r\n                        uploader.makeThumb(file, function (error, src) {\r\n                            if (error || !src) {\r\n                                $wrap.text(lang.uploadNoPreview);\r\n                            } else {\r\n                                var $img = $('<img src=\"' + src + '\">');\r\n                                $wrap.empty().append($img);\r\n                                $img.on('error', function () {\r\n                                    $wrap.text(lang.uploadNoPreview);\r\n                                });\r\n                            }\r\n                        }, thumbnailWidth, thumbnailHeight);\r\n                    }\r\n                    percentages[ file.id ] = [ file.size, 0 ];\r\n                    file.rotation = 0;\r\n\r\n                    /* 检查文件格式 */\r\n                    if (!file.ext || acceptExtensions.indexOf(file.ext.toLowerCase()) == -1) {\r\n                        showError('not_allow_type');\r\n                        uploader.removeFile(file);\r\n                    }\r\n                }\r\n\r\n                file.on('statuschange', function (cur, prev) {\r\n                    if (prev === 'progress') {\r\n                        $prgress.hide().width(0);\r\n                    } else if (prev === 'queued') {\r\n                        $li.off('mouseenter mouseleave');\r\n                        $btns.remove();\r\n                    }\r\n                    // 成功\r\n                    if (cur === 'error' || cur === 'invalid') {\r\n                        showError(file.statusText);\r\n                        percentages[ file.id ][ 1 ] = 1;\r\n                    } else if (cur === 'interrupt') {\r\n                        showError('interrupt');\r\n                    } else if (cur === 'queued') {\r\n                        percentages[ file.id ][ 1 ] = 0;\r\n                    } else if (cur === 'progress') {\r\n                        $info.hide();\r\n                        $prgress.css('display', 'block');\r\n                    } else if (cur === 'complete') {\r\n                    }\r\n\r\n                    $li.removeClass('state-' + prev).addClass('state-' + cur);\r\n                });\r\n\r\n                $li.on('mouseenter', function () {\r\n                    $btns.stop().animate({height: 30});\r\n                });\r\n                $li.on('mouseleave', function () {\r\n                    $btns.stop().animate({height: 0});\r\n                });\r\n\r\n                $btns.on('click', 'span', function () {\r\n                    var index = $(this).index(),\r\n                        deg;\r\n\r\n                    switch (index) {\r\n                        case 0:\r\n                            uploader.removeFile(file);\r\n                            return;\r\n                        case 1:\r\n                            file.rotation += 90;\r\n                            break;\r\n                        case 2:\r\n                            file.rotation -= 90;\r\n                            break;\r\n                    }\r\n\r\n                    if (supportTransition) {\r\n                        deg = 'rotate(' + file.rotation + 'deg)';\r\n                        $wrap.css({\r\n                            '-webkit-transform': deg,\r\n                            '-mos-transform': deg,\r\n                            '-o-transform': deg,\r\n                            'transform': deg\r\n                        });\r\n                    } else {\r\n                        $wrap.css('filter', 'progid:DXImageTransform.Microsoft.BasicImage(rotation=' + (~~((file.rotation / 90) % 4 + 4) % 4) + ')');\r\n                    }\r\n\r\n                });\r\n\r\n                $li.insertBefore($filePickerBlock);\r\n            }\r\n\r\n            // 负责view的销毁\r\n            function removeFile(file) {\r\n                var $li = $('#' + file.id);\r\n                delete percentages[ file.id ];\r\n                updateTotalProgress();\r\n                $li.off().find('.file-panel').off().end().remove();\r\n            }\r\n\r\n            function updateTotalProgress() {\r\n                var loaded = 0,\r\n                    total = 0,\r\n                    spans = $progress.children(),\r\n                    percent;\r\n\r\n                $.each(percentages, function (k, v) {\r\n                    total += v[ 0 ];\r\n                    loaded += v[ 0 ] * v[ 1 ];\r\n                });\r\n\r\n                percent = total ? loaded / total : 0;\r\n\r\n                spans.eq(0).text(Math.round(percent * 100) + '%');\r\n                spans.eq(1).css('width', Math.round(percent * 100) + '%');\r\n                updateStatus();\r\n            }\r\n\r\n            function setState(val, files) {\r\n\r\n                if (val != state) {\r\n\r\n                    var stats = uploader.getStats();\r\n\r\n                    $upload.removeClass('state-' + state);\r\n                    $upload.addClass('state-' + val);\r\n\r\n                    switch (val) {\r\n\r\n                        /* 未选择文件 */\r\n                        case 'pedding':\r\n                            $queue.addClass('element-invisible');\r\n                            $statusBar.addClass('element-invisible');\r\n                            $placeHolder.removeClass('element-invisible');\r\n                            $progress.hide(); $info.hide();\r\n                            uploader.refresh();\r\n                            break;\r\n\r\n                        /* 可以开始上传 */\r\n                        case 'ready':\r\n                            $placeHolder.addClass('element-invisible');\r\n                            $queue.removeClass('element-invisible');\r\n                            $statusBar.removeClass('element-invisible');\r\n                            $progress.hide(); $info.show();\r\n                            $upload.text(lang.uploadStart);\r\n                            uploader.refresh();\r\n                            break;\r\n\r\n                        /* 上传中 */\r\n                        case 'uploading':\r\n                            $progress.show(); $info.hide();\r\n                            $upload.text(lang.uploadPause);\r\n                            break;\r\n\r\n                        /* 暂停上传 */\r\n                        case 'paused':\r\n                            $progress.show(); $info.hide();\r\n                            $upload.text(lang.uploadContinue);\r\n                            break;\r\n\r\n                        case 'confirm':\r\n                            $progress.show(); $info.hide();\r\n                            $upload.text(lang.uploadStart);\r\n\r\n                            stats = uploader.getStats();\r\n                            if (stats.successNum && !stats.uploadFailNum) {\r\n                                setState('finish');\r\n                                return;\r\n                            }\r\n                            break;\r\n\r\n                        case 'finish':\r\n                            $progress.hide(); $info.show();\r\n                            if (stats.uploadFailNum) {\r\n                                $upload.text(lang.uploadRetry);\r\n                            } else {\r\n                                $upload.text(lang.uploadStart);\r\n                            }\r\n                            break;\r\n                    }\r\n\r\n                    state = val;\r\n                    updateStatus();\r\n\r\n                }\r\n\r\n                if (!_this.getQueueCount()) {\r\n                    $upload.addClass('disabled')\r\n                } else {\r\n                    $upload.removeClass('disabled')\r\n                }\r\n\r\n            }\r\n\r\n            function updateStatus() {\r\n                var text = '', stats;\r\n\r\n                if (state === 'ready') {\r\n                    text = lang.updateStatusReady.replace('_', fileCount).replace('_KB', WebUploader.formatSize(fileSize));\r\n                } else if (state === 'confirm') {\r\n                    stats = uploader.getStats();\r\n                    if (stats.uploadFailNum) {\r\n                        text = lang.updateStatusConfirm.replace('_', stats.successNum).replace('_', stats.successNum);\r\n                    }\r\n                } else {\r\n                    stats = uploader.getStats();\r\n                    text = lang.updateStatusFinish.replace('_', fileCount).\r\n                        replace('_KB', WebUploader.formatSize(fileSize)).\r\n                        replace('_', stats.successNum);\r\n\r\n                    if (stats.uploadFailNum) {\r\n                        text += lang.updateStatusError.replace('_', stats.uploadFailNum);\r\n                    }\r\n                }\r\n\r\n                $info.html(text);\r\n            }\r\n\r\n            uploader.on('fileQueued', function (file) {\r\n                fileCount++;\r\n                fileSize += file.size;\r\n\r\n                if (fileCount === 1) {\r\n                    $placeHolder.addClass('element-invisible');\r\n                    $statusBar.show();\r\n                }\r\n\r\n                addFile(file);\r\n            });\r\n\r\n            uploader.on('fileDequeued', function (file) {\r\n                fileCount--;\r\n                fileSize -= file.size;\r\n\r\n                removeFile(file);\r\n                updateTotalProgress();\r\n            });\r\n\r\n            uploader.on('filesQueued', function (file) {\r\n                if (!uploader.isInProgress() && (state == 'pedding' || state == 'finish' || state == 'confirm' || state == 'ready')) {\r\n                    setState('ready');\r\n                }\r\n                updateTotalProgress();\r\n            });\r\n\r\n            uploader.on('all', function (type, files) {\r\n                switch (type) {\r\n                    case 'uploadFinished':\r\n                        setState('confirm', files);\r\n                        break;\r\n                    case 'startUpload':\r\n                        /* 添加额外的GET参数 */\r\n                        var params = utils.serializeParam(editor.queryCommandValue('serverparam')) || '',\r\n                            url = utils.formatUrl(actionUrl + (actionUrl.indexOf('?') == -1 ? '?':'&') + 'encode=utf-8&' + params);\r\n                        uploader.option('server', url);\r\n                        setState('uploading', files);\r\n                        break;\r\n                    case 'stopUpload':\r\n                        setState('paused', files);\r\n                        break;\r\n                }\r\n            });\r\n\r\n            uploader.on('uploadBeforeSend', function (file, data, header) {\r\n                //这里可以通过data对象添加POST参数\r\n                header['X_Requested_With'] = 'XMLHttpRequest';\r\n                // HaoChuan9421\r\n                if(editor.options.headers && Object.prototype.toString.apply(editor.options.headers) === \"[object Object]\"){\r\n                    for(var key in editor.options.headers){\r\n                        header[key] = editor.options.headers[key]\r\n                    }\r\n                }\r\n            });\r\n\r\n            uploader.on('uploadProgress', function (file, percentage) {\r\n                var $li = $('#' + file.id),\r\n                    $percent = $li.find('.progress span');\r\n\r\n                $percent.css('width', percentage * 100 + '%');\r\n                percentages[ file.id ][ 1 ] = percentage;\r\n                updateTotalProgress();\r\n            });\r\n\r\n            uploader.on('uploadSuccess', function (file, ret) {\r\n                var $file = $('#' + file.id);\r\n                try {\r\n                    var responseText = (ret._raw || ret),\r\n                        json = utils.str2json(responseText);\r\n                    if (json.state == 'SUCCESS') {\r\n                        _this.imageList.push(json);\r\n                        $file.append('<span class=\"success\"></span>');\r\n                    } else {\r\n                        $file.find('.error').text(json.state).show();\r\n                    }\r\n                } catch (e) {\r\n                    $file.find('.error').text(lang.errorServerUpload).show();\r\n                }\r\n            });\r\n\r\n            uploader.on('uploadError', function (file, code) {\r\n            });\r\n            uploader.on('error', function (code, file) {\r\n                if (code == 'Q_TYPE_DENIED' || code == 'F_EXCEED_SIZE') {\r\n                    addFile(file);\r\n                }\r\n            });\r\n            uploader.on('uploadComplete', function (file, ret) {\r\n            });\r\n\r\n            $upload.on('click', function () {\r\n                if ($(this).hasClass('disabled')) {\r\n                    return false;\r\n                }\r\n\r\n                if (state === 'ready') {\r\n                    uploader.upload();\r\n                } else if (state === 'paused') {\r\n                    uploader.upload();\r\n                } else if (state === 'uploading') {\r\n                    uploader.stop();\r\n                }\r\n            });\r\n\r\n            $upload.addClass('state-' + state);\r\n            updateTotalProgress();\r\n        },\r\n        getQueueCount: function () {\r\n            var file, i, status, readyFile = 0, files = this.uploader.getFiles();\r\n            for (i = 0; file = files[i++]; ) {\r\n                status = file.getStatus();\r\n                if (status == 'queued' || status == 'uploading' || status == 'progress') readyFile++;\r\n            }\r\n            return readyFile;\r\n        },\r\n        destroy: function () {\r\n            this.$wrap.remove();\r\n        },\r\n        getInsertList: function () {\r\n            var i, data, list = [],\r\n                align = getAlign(),\r\n                prefix = editor.getOpt('imageUrlPrefix');\r\n            for (i = 0; i < this.imageList.length; i++) {\r\n                data = this.imageList[i];\r\n                list.push({\r\n                    src: prefix + data.url,\r\n                    _src: prefix + data.url,\r\n                    title: data.title,\r\n                    alt: data.original,\r\n                    floatStyle: align\r\n                });\r\n            }\r\n            return list;\r\n        }\r\n    };\r\n\r\n\r\n    /* 在线图片 */\r\n    function OnlineImage(target) {\r\n        this.container = utils.isString(target) ? document.getElementById(target) : target;\r\n        this.init();\r\n    }\r\n    OnlineImage.prototype = {\r\n        init: function () {\r\n            this.reset();\r\n            this.initEvents();\r\n        },\r\n        /* 初始化容器 */\r\n        initContainer: function () {\r\n            this.container.innerHTML = '';\r\n            this.list = document.createElement('ul');\r\n            this.clearFloat = document.createElement('li');\r\n\r\n            domUtils.addClass(this.list, 'list');\r\n            domUtils.addClass(this.clearFloat, 'clearFloat');\r\n\r\n            this.list.appendChild(this.clearFloat);\r\n            this.container.appendChild(this.list);\r\n        },\r\n        /* 初始化滚动事件,滚动到地步自动拉取数据 */\r\n        initEvents: function () {\r\n            var _this = this;\r\n\r\n            /* 滚动拉取图片 */\r\n            domUtils.on($G('imageList'), 'scroll', function(e){\r\n                var panel = this;\r\n                if (panel.scrollHeight - (panel.offsetHeight + panel.scrollTop) < 10) {\r\n                    _this.getImageData();\r\n                }\r\n            });\r\n            /* 选中图片 */\r\n            domUtils.on(this.container, 'click', function (e) {\r\n                var target = e.target || e.srcElement,\r\n                    li = target.parentNode;\r\n\r\n                if (li.tagName.toLowerCase() == 'li') {\r\n                    if (domUtils.hasClass(li, 'selected')) {\r\n                        domUtils.removeClasses(li, 'selected');\r\n                    } else {\r\n                        domUtils.addClass(li, 'selected');\r\n                    }\r\n                }\r\n            });\r\n        },\r\n        /* 初始化第一次的数据 */\r\n        initData: function () {\r\n\r\n            /* 拉取数据需要使用的值 */\r\n            this.state = 0;\r\n            this.listSize = editor.getOpt('imageManagerListSize');\r\n            this.listIndex = 0;\r\n            this.listEnd = false;\r\n\r\n            /* 第一次拉取数据 */\r\n            this.getImageData();\r\n        },\r\n        /* 重置界面 */\r\n        reset: function() {\r\n            this.initContainer();\r\n            this.initData();\r\n        },\r\n        /* 向后台拉取图片列表数据 */\r\n        getImageData: function () {\r\n            var _this = this;\r\n\r\n            if(!_this.listEnd && !this.isLoadingData) {\r\n                this.isLoadingData = true;\r\n                var url = editor.getActionUrl(editor.getOpt('imageManagerActionName')),\r\n                    isJsonp = utils.isCrossDomainUrl(url);\r\n                ajax.request(url, {\r\n                    'timeout': 100000,\r\n                    'dataType': isJsonp ? 'jsonp':'',\r\n                    'data': utils.extend({\r\n                            start: this.listIndex,\r\n                            size: this.listSize\r\n                        }, editor.queryCommandValue('serverparam')),\r\n                    'method': 'get',\r\n                    'onsuccess': function (r) {\r\n                        try {\r\n                            var json = isJsonp ? r:eval('(' + r.responseText + ')');\r\n                            if (json.state == 'SUCCESS') {\r\n                                _this.pushData(json.list);\r\n                                _this.listIndex = parseInt(json.start) + parseInt(json.list.length);\r\n                                if(_this.listIndex >= json.total) {\r\n                                    _this.listEnd = true;\r\n                                }\r\n                                _this.isLoadingData = false;\r\n                            }\r\n                        } catch (e) {\r\n                            if(r.responseText.indexOf('ue_separate_ue') != -1) {\r\n                                var list = r.responseText.split(r.responseText);\r\n                                _this.pushData(list);\r\n                                _this.listIndex = parseInt(list.length);\r\n                                _this.listEnd = true;\r\n                                _this.isLoadingData = false;\r\n                            }\r\n                        }\r\n                    },\r\n                    'onerror': function () {\r\n                        _this.isLoadingData = false;\r\n                    }\r\n                });\r\n            }\r\n        },\r\n        /* 添加图片到列表界面上 */\r\n        pushData: function (list) {\r\n            var i, item, img, icon, _this = this,\r\n                urlPrefix = editor.getOpt('imageManagerUrlPrefix');\r\n            for (i = 0; i < list.length; i++) {\r\n                if(list[i] && list[i].url) {\r\n                    item = document.createElement('li');\r\n                    img = document.createElement('img');\r\n                    icon = document.createElement('span');\r\n\r\n                    domUtils.on(img, 'load', (function(image){\r\n                        return function(){\r\n                            _this.scale(image, image.parentNode.offsetWidth, image.parentNode.offsetHeight);\r\n                        }\r\n                    })(img));\r\n                    img.width = 113;\r\n                    img.setAttribute('src', urlPrefix + list[i].url + (list[i].url.indexOf('?') == -1 ? '?noCache=':'&noCache=') + (+new Date()).toString(36) );\r\n                    img.setAttribute('_src', urlPrefix + list[i].url);\r\n                    domUtils.addClass(icon, 'icon');\r\n\r\n                    item.appendChild(img);\r\n                    item.appendChild(icon);\r\n                    this.list.insertBefore(item, this.clearFloat);\r\n                }\r\n            }\r\n        },\r\n        /* 改变图片大小 */\r\n        scale: function (img, w, h, type) {\r\n            var ow = img.width,\r\n                oh = img.height;\r\n\r\n            if (type == 'justify') {\r\n                if (ow >= oh) {\r\n                    img.width = w;\r\n                    img.height = h * oh / ow;\r\n                    img.style.marginLeft = '-' + parseInt((img.width - w) / 2) + 'px';\r\n                } else {\r\n                    img.width = w * ow / oh;\r\n                    img.height = h;\r\n                    img.style.marginTop = '-' + parseInt((img.height - h) / 2) + 'px';\r\n                }\r\n            } else {\r\n                if (ow >= oh) {\r\n                    img.width = w * ow / oh;\r\n                    img.height = h;\r\n                    img.style.marginLeft = '-' + parseInt((img.width - w) / 2) + 'px';\r\n                } else {\r\n                    img.width = w;\r\n                    img.height = h * oh / ow;\r\n                    img.style.marginTop = '-' + parseInt((img.height - h) / 2) + 'px';\r\n                }\r\n            }\r\n        },\r\n        getInsertList: function () {\r\n            var i, lis = this.list.children, list = [], align = getAlign();\r\n            for (i = 0; i < lis.length; i++) {\r\n                if (domUtils.hasClass(lis[i], 'selected')) {\r\n                    var img = lis[i].firstChild,\r\n                        src = img.getAttribute('_src');\r\n                    list.push({\r\n                        src: src,\r\n                        _src: src,\r\n                        alt: src.substr(src.lastIndexOf('/') + 1),\r\n                        floatStyle: align\r\n                    });\r\n                }\r\n\r\n            }\r\n            return list;\r\n        }\r\n    };\r\n\r\n    /*搜索图片 */\r\n    function SearchImage() {\r\n        this.init();\r\n    }\r\n    SearchImage.prototype = {\r\n        init: function () {\r\n            this.initEvents();\r\n        },\r\n        initEvents: function(){\r\n            var _this = this;\r\n\r\n            /* 点击搜索按钮 */\r\n            domUtils.on($G('searchBtn'), 'click', function(){\r\n                var key = $G('searchTxt').value;\r\n                if(key && key != lang.searchRemind) {\r\n                    _this.getImageData();\r\n                }\r\n            });\r\n            /* 点击清除妞 */\r\n            domUtils.on($G('searchReset'), 'click', function(){\r\n                $G('searchTxt').value = lang.searchRemind;\r\n                $G('searchListUl').innerHTML = '';\r\n                $G('searchType').selectedIndex = 0;\r\n            });\r\n            /* 搜索框聚焦 */\r\n            domUtils.on($G('searchTxt'), 'focus', function(){\r\n                var key = $G('searchTxt').value;\r\n                if(key && key == lang.searchRemind) {\r\n                    $G('searchTxt').value = '';\r\n                }\r\n            });\r\n            /* 搜索框回车键搜索 */\r\n            domUtils.on($G('searchTxt'), 'keydown', function(e){\r\n                var keyCode = e.keyCode || e.which;\r\n                if (keyCode == 13) {\r\n                    $G('searchBtn').click();\r\n                }\r\n            });\r\n\r\n            /* 选中图片 */\r\n            domUtils.on($G('searchList'), 'click', function(e){\r\n                var target = e.target || e.srcElement,\r\n                    li = target.parentNode.parentNode;\r\n\r\n                if (li.tagName.toLowerCase() == 'li') {\r\n                    if (domUtils.hasClass(li, 'selected')) {\r\n                        domUtils.removeClasses(li, 'selected');\r\n                    } else {\r\n                        domUtils.addClass(li, 'selected');\r\n                    }\r\n                }\r\n            });\r\n        },\r\n        encodeToGb2312:function (str){\r\n            if(!str) return '';\r\n            var strOut = \"\",\r\n                z = 'D2BBB6A18140C6DF814181428143CDF2D5C9C8FDC9CFCFC2D8A2B2BBD3EB8144D8A4B3F38145D7A8C7D2D8A7CAC08146C7F0B1FBD2B5B4D4B6ABCBBFD8A9814781488149B6AA814AC1BDD1CF814BC9A5D8AD814CB8F6D1BEE3DCD6D0814D814EB7E1814FB4AE8150C1D98151D8BC8152CDE8B5A4CEAAD6F78153C0F6BED9D8AF815481558156C4CB8157BEC38158D8B1C3B4D2E58159D6AECEDAD5A7BAF5B7A6C0D6815AC6B9C5D2C7C7815BB9D4815CB3CBD2D2815D815ED8BFBEC5C6F2D2B2CFB0CFE7815F816081618162CAE981638164D8C081658166816781688169816AC2F2C2D2816BC8E9816C816D816E816F817081718172817381748175C7AC8176817781788179817A817B817CC1CB817DD3E8D5F9817ECAC2B6FED8A1D3DABFF78180D4C6BBA5D8C1CEE5BEAE81818182D8A88183D1C7D0A9818481858186D8BDD9EFCDF6BFBA8187BDBBBAA5D2E0B2FABAE0C4B68188CFEDBEA9CDA4C1C18189818A818BC7D7D9F1818CD9F4818D818E818F8190C8CBD8E9819181928193D2DACAB2C8CAD8ECD8EAD8C6BDF6C6CDB3F08194D8EBBDF1BDE98195C8D4B4D381968197C2D88198B2D6D7D0CACBCBFBD5CCB8B6CFC98199819A819BD9DAD8F0C7AA819CD8EE819DB4FAC1EED2D4819E819FD8ED81A0D2C7D8EFC3C781A181A281A3D1F681A4D6D9D8F281A5D8F5BCFEBCDB81A681A781A8C8CE81A9B7DD81AAB7C281ABC6F381AC81AD81AE81AF81B081B181B2D8F8D2C181B381B4CEE9BCBFB7FCB7A5D0DD81B581B681B781B881B9D6DAD3C5BBEFBBE1D8F181BA81BBC9A1CEB0B4AB81BCD8F381BDC9CBD8F6C2D7D8F781BE81BFCEB1D8F981C081C181C2B2AEB9C081C3D9A381C4B0E981C5C1E681C6C9EC81C7CBC581C8CBC6D9A481C981CA81CB81CC81CDB5E881CE81CFB5AB81D081D181D281D381D481D5CEBBB5CDD7A1D7F4D3D381D6CCE581D7BACE81D8D9A2D9DCD3E0D8FDB7F0D7F7D8FED8FAD9A1C4E381D981DAD3B6D8F4D9DD81DBD8FB81DCC5E581DD81DEC0D081DF81E0D1F0B0DB81E181E2BCD1D9A681E3D9A581E481E581E681E7D9ACD9AE81E8D9ABCAB981E981EA81EBD9A9D6B681EC81ED81EEB3DED9A881EFC0FD81F0CACC81F1D9AA81F2D9A781F381F4D9B081F581F6B6B181F781F881F9B9A981FAD2C081FB81FCCFC081FD81FEC2C28240BDC4D5ECB2E0C7C8BFEBD9AD8241D9AF8242CEEABAEE82438244824582468247C7D682488249824A824B824C824D824E824F8250B1E3825182528253B4D9B6EDD9B48254825582568257BFA182588259825AD9DEC7CEC0FED9B8825B825C825D825E825FCBD7B7FD8260D9B58261D9B7B1A3D3E1D9B98262D0C58263D9B682648265D9B18266D9B2C1A9D9B382678268BCF3D0DEB8A98269BEE3826AD9BD826B826C826D826ED9BA826FB0B3827082718272D9C28273827482758276827782788279827A827B827C827D827E8280D9C4B1B68281D9BF82828283B5B98284BEF3828582868287CCC8BAF2D2D08288D9C38289828ABDE8828BB3AB828C828D828ED9C5BEEB828FD9C6D9BBC4DF8290D9BED9C1D9C0829182928293829482958296829782988299829A829BD5AE829CD6B5829DC7E3829E829F82A082A1D9C882A282A382A4BCD9D9CA82A582A682A7D9BC82A8D9CBC6AB82A982AA82AB82AC82ADD9C982AE82AF82B082B1D7F682B2CDA382B382B482B582B682B782B882B982BABDA182BB82BC82BD82BE82BF82C0D9CC82C182C282C382C482C582C682C782C882C9C5BCCDB582CA82CB82CCD9CD82CD82CED9C7B3A5BFFE82CF82D082D182D2B8B582D382D4C0FC82D582D682D782D8B0F882D982DA82DB82DC82DD82DE82DF82E082E182E282E382E482E582E682E782E882E982EA82EB82EC82EDB4F682EED9CE82EFD9CFB4A2D9D082F082F1B4DF82F282F382F482F582F6B0C182F782F882F982FA82FB82FC82FDD9D1C9B582FE8340834183428343834483458346834783488349834A834B834C834D834E834F83508351CFF1835283538354835583568357D9D283588359835AC1C5835B835C835D835E835F836083618362836383648365D9D6C9AE8366836783688369D9D5D9D4D9D7836A836B836C836DCBDB836EBDA9836F8370837183728373C6A7837483758376837783788379837A837B837C837DD9D3D9D8837E83808381D9D9838283838384838583868387C8E583888389838A838B838C838D838E838F839083918392839383948395C0DC8396839783988399839A839B839C839D839E839F83A083A183A283A383A483A583A683A783A883A983AA83AB83AC83AD83AE83AF83B083B183B2B6F9D8A3D4CA83B3D4AAD0D6B3E4D5D783B4CFC8B9E283B5BFCB83B6C3E283B783B883B9B6D283BA83BBCDC3D9EED9F083BC83BD83BEB5B383BFB6B583C083C183C283C383C4BEA483C583C6C8EB83C783C8C8AB83C983CAB0CBB9ABC1F9D9E283CBC0BCB9B283CCB9D8D0CBB1F8C6E4BEDFB5E4D7C883CDD1F8BCE6CADE83CE83CFBCBDD9E6D8E783D083D1C4DA83D283D3B8D4C8BD83D483D5B2E1D4D983D683D783D883D9C3B083DA83DBC3E1DAA2C8DF83DCD0B483DDBEFCC5A983DE83DF83E0B9DA83E1DAA383E2D4A9DAA483E383E483E583E683E7D9FBB6AC83E883E9B7EBB1F9D9FCB3E5BEF683EABFF6D2B1C0E483EB83EC83EDB6B3D9FED9FD83EE83EFBEBB83F083F183F2C6E083F3D7BCDAA183F4C1B983F5B5F2C1E883F683F7BCF583F8B4D583F983FA83FB83FC83FD83FE844084418442C1DD8443C4FD84448445BCB8B7B284468447B7EF84488449844A844B844C844DD9EC844EC6BE844FBFADBBCB84508451B5CA8452DBC9D0D78453CDB9B0BCB3F6BBF7DBCABAAF8454D4E4B5B6B5F3D8D6C8D084558456B7D6C7D0D8D78457BFAF84588459DBBBD8D8845A845BD0CCBBAE845C845D845EEBBEC1D0C1F5D4F2B8D5B4B4845FB3F584608461C9BE846284638464C5D0846584668467C5D9C0FB8468B1F08469D8D9B9CE846AB5BD846B846CD8DA846D846ED6C6CBA2C8AFC9B2B4CCBFCC846FB9F48470D8DBD8DCB6E7BCC1CCEA847184728473847484758476CFF78477D8DDC7B084788479B9D0BDA3847A847BCCDE847CC6CA847D847E848084818482D8E08483D8DE84848485D8DF848684878488B0FE8489BEE7848ACAA3BCF4848B848C848D848EB8B1848F8490B8EE849184928493849484958496849784988499849AD8E2849BBDCB849CD8E4D8E3849D849E849F84A084A1C5FC84A284A384A484A584A684A784A8D8E584A984AAD8E684AB84AC84AD84AE84AF84B084B1C1A684B2C8B0B0ECB9A6BCD3CEF1DBBDC1D384B384B484B584B6B6AFD6FAC5ACBDD9DBBEDBBF84B784B884B9C0F8BEA2C0CD84BA84BB84BC84BD84BE84BF84C084C184C284C3DBC0CAC684C484C584C6B2AA84C784C884C9D3C284CAC3E384CBD1AB84CC84CD84CE84CFDBC284D0C0D584D184D284D3DBC384D4BFB184D584D684D784D884D984DAC4BC84DB84DC84DD84DEC7DA84DF84E084E184E284E384E484E584E684E784E884E9DBC484EA84EB84EC84ED84EE84EF84F084F1D9E8C9D784F284F384F4B9B4CEF0D4C884F584F684F784F8B0FCB4D284F9D0D984FA84FB84FC84FDD9E984FEDECBD9EB8540854185428543D8B0BBAFB1B18544B3D7D8CE85458546D4D185478548BDB3BFEF8549CFBB854A854BD8D0854C854D854EB7CB854F85508551D8D185528553855485558556855785588559855A855BC6A5C7F8D2BD855C855DD8D2C4E4855ECAAE855FC7A78560D8A68561C9FDCEE7BBDCB0EB856285638564BBAAD0AD8565B1B0D7E4D7BF8566B5A5C2F4C4CF85678568B2A98569B2B7856AB1E5DFB2D5BCBFA8C2ACD8D5C2B1856BD8D4CED4856CDAE0856DCEC0856E856FD8B4C3AED3A1CEA38570BCB4C8B4C2D18571BEEDD0B68572DAE18573857485758576C7E485778578B3A78579B6F2CCFCC0FA857A857BC0F7857CD1B9D1E1D8C7857D857E85808581858285838584B2DE85858586C0E58587BAF185888589D8C8858AD4AD858B858CCFE1D8C9858DD8CACFC3858EB3F8BEC7858F859085918592D8CB8593859485958596859785988599DBCC859A859B859C859DC8A5859E859F85A0CFD885A1C8FEB2CE85A285A385A485A585A6D3D6B2E6BCB0D3D1CBABB7B485A785A885A9B7A285AA85ABCAE585ACC8A1CADCB1E4D0F085ADC5D185AE85AF85B0DBC5B5FE85B185B2BFDAB9C5BEE4C1ED85B3DFB6DFB5D6BBBDD0D5D9B0C8B6A3BFC9CCA8DFB3CAB7D3D285B4D8CFD2B6BAC5CBBECCBE85B5DFB7B5F0DFB485B685B785B8D3F585B9B3D4B8F785BADFBA85BBBACFBCAAB5F585BCCDACC3FBBAF3C0F4CDC2CFF2DFB8CFC585BDC2C0DFB9C2F085BE85BF85C0BEFD85C1C1DFCDCCD2F7B7CDDFC185C2DFC485C385C4B7F1B0C9B6D6B7D485C5BAACCCFDBFD4CBB1C6F485C6D6A8DFC585C7CEE2B3B385C885C9CEFCB4B585CACEC7BAF085CBCEE185CCD1BD85CD85CEDFC085CF85D0B4F485D1B3CA85D2B8E6DFBB85D385D485D585D6C4C585D7DFBCDFBDDFBEC5BBDFBFDFC2D4B1DFC385D8C7BACED885D985DA85DB85DC85DDC4D885DEDFCA85DFDFCF85E0D6DC85E185E285E385E485E585E685E785E8DFC9DFDACEB685E9BAC7DFCEDFC8C5DE85EA85EBC9EBBAF4C3FC85EC85EDBED785EEDFC685EFDFCD85F0C5D885F185F285F385F4D5A6BACD85F5BECCD3BDB8C085F6D6E485F7DFC7B9BEBFA785F885F9C1FCDFCBDFCC85FADFD085FB85FC85FD85FE8640DFDBDFE58641DFD7DFD6D7C9DFE3DFE4E5EBD2A7DFD28642BFA98643D4DB8644BFC8DFD4864586468647CFCC86488649DFDD864AD1CA864BDFDEB0A7C6B7DFD3864CBAE5864DB6DFCDDBB9FED4D5864E864FDFDFCFECB0A5DFE7DFD1D1C6DFD5DFD8DFD9DFDC8650BBA98651DFE0DFE18652DFE2DFE6DFE8D3B486538654865586568657B8E7C5B6DFEAC9DAC1A8C4C486588659BFDECFF8865A865B865CD5DCDFEE865D865E865F866086618662B2B88663BADFDFEC8664DBC18665D1E48666866786688669CBF4B4BD866AB0A6866B866C866D866E866FDFF1CCC6DFF286708671DFED867286738674867586768677DFE986788679867A867BDFEB867CDFEFDFF0BBBD867D867EDFF386808681DFF48682BBA38683CADBCEA8E0A7B3AA8684E0A6868586868687E0A186888689868A868BDFFE868CCDD9DFFC868DDFFA868EBFD0D7C4868FC9CC86908691DFF8B0A186928693869486958696DFFD869786988699869ADFFBE0A2869B869C869D869E869FE0A886A086A186A286A3B7C886A486A5C6A1C9B6C0B2DFF586A686A7C5BE86A8D8C4DFF9C4F686A986AA86AB86AC86AD86AEE0A3E0A4E0A5D0A586AF86B0E0B4CCE486B1E0B186B2BFA6E0AFCEB9E0ABC9C686B386B4C0AEE0AEBAEDBAB0E0A986B586B686B7DFF686B8E0B386B986BAE0B886BB86BC86BDB4ADE0B986BE86BFCFB2BAC886C0E0B086C186C286C386C486C586C686C7D0FA86C886C986CA86CB86CC86CD86CE86CF86D0E0AC86D1D4FB86D2DFF786D3C5E786D4E0AD86D5D3F786D6E0B6E0B786D786D886D986DA86DBE0C4D0E186DC86DD86DEE0BC86DF86E0E0C9E0CA86E186E286E3E0BEE0AAC9A4E0C186E4E0B286E586E686E786E886E9CAC8E0C386EAE0B586EBCECB86ECCBC3E0CDE0C6E0C286EDE0CB86EEE0BAE0BFE0C086EF86F0E0C586F186F2E0C7E0C886F3E0CC86F4E0BB86F586F686F786F886F9CBD4E0D586FAE0D6E0D286FB86FC86FD86FE87408741E0D0BCCE87428743E0D18744B8C2D8C587458746874787488749874A874B874CD0EA874D874EC2EF874F8750E0CFE0BD875187528753E0D4E0D387548755E0D78756875787588759E0DCE0D8875A875B875CD6F6B3B0875DD7EC875ECBBB875F8760E0DA8761CEFB876287638764BAD987658766876787688769876A876B876C876D876E876F8770E0E1E0DDD2AD87718772877387748775E0E287768777E0DBE0D9E0DF87788779E0E0877A877B877C877D877EE0DE8780E0E4878187828783C6F7D8ACD4EBE0E6CAC98784878587868787E0E587888789878A878BB8C1878C878D878E878FE0E7E0E887908791879287938794879587968797E0E9E0E387988799879A879B879C879D879EBABFCCE7879F87A087A1E0EA87A287A387A487A587A687A787A887A987AA87AB87AC87AD87AE87AF87B0CFF987B187B287B387B487B587B687B787B887B987BA87BBE0EB87BC87BD87BE87BF87C087C187C2C8C287C387C487C587C6BDC087C787C887C987CA87CB87CC87CD87CE87CF87D087D187D287D3C4D287D487D587D687D787D887D987DA87DB87DCE0EC87DD87DEE0ED87DF87E0C7F4CBC487E1E0EEBBD8D8B6D2F2E0EFCDC587E2B6DA87E387E487E587E687E787E8E0F187E9D4B087EA87EBC0A7B4D187EC87EDCEA7E0F087EE87EF87F0E0F2B9CC87F187F2B9FACDBCE0F387F387F487F5C6D4E0F487F6D4B287F7C8A6E0F6E0F587F887F987FA87FB87FC87FD87FE8840884188428843884488458846884788488849E0F7884A884BCDC1884C884D884ECAA5884F885088518852D4DADBD7DBD98853DBD8B9E7DBDCDBDDB5D888548855DBDA8856885788588859885ADBDBB3A1DBDF885B885CBBF8885DD6B7885EDBE0885F886088618862BEF988638864B7BB8865DBD0CCAEBFB2BBB5D7F8BFD38866886788688869886ABFE9886B886CBCE1CCB3DBDEB0D3CEEBB7D8D7B9C6C2886D886EC0A4886FCCB98870DBE7DBE1C6BADBE38871DBE88872C5F7887388748875DBEA88768877DBE9BFC088788879887ADBE6DBE5887B887C887D887E8880B4B9C0ACC2A2DBE2DBE48881888288838884D0CDDBED88858886888788888889C0DDDBF2888A888B888C888D888E888F8890B6E28891889288938894DBF3DBD2B9B8D4ABDBEC8895BFD1DBF08896DBD18897B5E68898DBEBBFE58899889A889BDBEE889CDBF1889D889E889FDBF988A088A188A288A388A488A588A688A788A8B9A1B0A388A988AA88AB88AC88AD88AE88AFC2F188B088B1B3C7DBEF88B288B3DBF888B4C6D2DBF488B588B6DBF5DBF7DBF688B788B8DBFE88B9D3F2B2BA88BA88BB88BCDBFD88BD88BE88BF88C088C188C288C388C4DCA488C5DBFB88C688C788C888C9DBFA88CA88CB88CCDBFCC5E0BBF988CD88CEDCA388CF88D0DCA588D1CCC388D288D388D4B6D1DDC088D588D688D7DCA188D8DCA288D988DA88DBC7B588DC88DD88DEB6E988DF88E088E1DCA788E288E388E488E5DCA688E6DCA9B1A488E788E8B5CC88E988EA88EB88EC88EDBFB088EE88EF88F088F188F2D1DF88F388F488F588F6B6C288F788F888F988FA88FB88FC88FD88FE894089418942894389448945DCA88946894789488949894A894B894CCBFAEBF3894D894E894FCBDC89508951CBFE895289538954CCC189558956895789588959C8FB895A895B895C895D895E895FDCAA89608961896289638964CCEEDCAB89658966896789688969896A896B896C896D896E896F897089718972897389748975DBD38976DCAFDCAC8977BEB38978CAFB8979897A897BDCAD897C897D897E89808981898289838984C9CAC4B989858986898789888989C7BDDCAE898A898B898CD4F6D0E6898D898E898F89908991899289938994C4ABB6D589958996899789988999899A899B899C899D899E899F89A089A189A289A389A489A589A6DBD489A789A889A989AAB1DA89AB89AC89ADDBD589AE89AF89B089B189B289B389B489B589B689B789B8DBD689B989BA89BBBABE89BC89BD89BE89BF89C089C189C289C389C489C589C689C789C889C9C8C089CA89CB89CC89CD89CE89CFCABFC8C989D0D7B389D1C9F989D289D3BFC789D489D5BAF889D689D7D2BC89D889D989DA89DB89DC89DD89DE89DFE2BA89E0B4A689E189E2B1B889E389E489E589E689E7B8B489E8CFC489E989EA89EB89ECD9E7CFA6CDE289ED89EED9EDB6E089EFD2B989F089F1B9BB89F289F389F489F5E2B9E2B789F6B4F389F7CCECCCABB7F289F8D8B2D1EBBABB89F9CAA789FA89FBCDB789FC89FDD2C4BFE4BCD0B6E189FEDEC58A408A418A428A43DEC6DBBC8A44D1D98A458A46C6E6C4CEB7EE8A47B7DC8A488A49BFFCD7E08A4AC6F58A4B8A4CB1BCDEC8BDB1CCD7DECA8A4DDEC98A4E8A4F8A508A518A52B5EC8A53C9DD8A548A55B0C28A568A578A588A598A5A8A5B8A5C8A5D8A5E8A5F8A608A618A62C5AEC5AB8A63C4CC8A64BCE9CBFD8A658A668A67BAC38A688A698A6AE5F9C8E7E5FACDFD8A6BD7B1B8BEC2E88A6CC8D18A6D8A6EE5FB8A6F8A708A718A72B6CABCCB8A738A74D1FDE6A18A75C3EE8A768A778A788A79E6A48A7A8A7B8A7C8A7DE5FEE6A5CDD78A7E8A80B7C1E5FCE5FDE6A38A818A82C4DDE6A88A838A84E6A78A858A868A878A888A898A8AC3C38A8BC6DE8A8C8A8DE6AA8A8E8A8F8A908A918A928A938A94C4B78A958A968A97E6A2CABC8A988A998A9A8A9BBDE3B9C3E6A6D0D5CEAF8A9C8A9DE6A9E6B08A9ED2A68A9FBDAAE6AD8AA08AA18AA28AA38AA4E6AF8AA5C0D18AA68AA7D2CC8AA88AA98AAABCA78AAB8AAC8AAD8AAE8AAF8AB08AB18AB28AB38AB48AB58AB6E6B18AB7D2F68AB88AB98ABAD7CB8ABBCDFE8ABCCDDEC2A6E6ABE6ACBDBFE6AEE6B38ABD8ABEE6B28ABF8AC08AC18AC2E6B68AC3E6B88AC48AC58AC68AC7C4EF8AC88AC98ACAC4C88ACB8ACCBEEAC9EF8ACD8ACEE6B78ACFB6F08AD08AD18AD2C3E48AD38AD48AD58AD68AD78AD88AD9D3E9E6B48ADAE6B58ADBC8A28ADC8ADD8ADE8ADF8AE0E6BD8AE18AE28AE3E6B98AE48AE58AE68AE78AE8C6C58AE98AEACDF1E6BB8AEB8AEC8AED8AEE8AEF8AF08AF18AF28AF38AF4E6BC8AF58AF68AF78AF8BBE98AF98AFA8AFB8AFC8AFD8AFE8B40E6BE8B418B428B438B44E6BA8B458B46C0B78B478B488B498B4A8B4B8B4C8B4D8B4E8B4FD3A4E6BFC9F4E6C38B508B51E6C48B528B538B548B55D0F68B568B578B588B598B5A8B5B8B5C8B5D8B5E8B5F8B608B618B628B638B648B658B668B67C3BD8B688B698B6A8B6B8B6C8B6D8B6EC3C4E6C28B6F8B708B718B728B738B748B758B768B778B788B798B7A8B7B8B7CE6C18B7D8B7E8B808B818B828B838B84E6C7CFB18B85EBF48B868B87E6CA8B888B898B8A8B8B8B8CE6C58B8D8B8EBCDEC9A98B8F8B908B918B928B938B94BCB58B958B96CFD38B978B988B998B9A8B9BE6C88B9CE6C98B9DE6CE8B9EE6D08B9F8BA08BA1E6D18BA28BA38BA4E6CBB5D58BA5E6CC8BA68BA7E6CF8BA88BA9C4DB8BAAE6C68BAB8BAC8BAD8BAE8BAFE6CD8BB08BB18BB28BB38BB48BB58BB68BB78BB88BB98BBA8BBB8BBC8BBD8BBE8BBF8BC08BC18BC28BC38BC48BC58BC6E6D28BC78BC88BC98BCA8BCB8BCC8BCD8BCE8BCF8BD08BD18BD2E6D4E6D38BD38BD48BD58BD68BD78BD88BD98BDA8BDB8BDC8BDD8BDE8BDF8BE08BE18BE28BE38BE48BE58BE68BE78BE88BE98BEA8BEB8BECE6D58BEDD9F88BEE8BEFE6D68BF08BF18BF28BF38BF48BF58BF68BF7E6D78BF88BF98BFA8BFB8BFC8BFD8BFE8C408C418C428C438C448C458C468C47D7D3E6DD8C48E6DEBFD7D4D08C49D7D6B4E6CBEFE6DAD8C3D7CED0A28C4AC3CF8C4B8C4CE6DFBCBEB9C2E6DBD1A78C4D8C4EBAA2C2CF8C4FD8AB8C508C518C52CAEBE5EE8C53E6DC8C54B7F58C558C568C578C58C8E68C598C5AC4F58C5B8C5CE5B2C4FE8C5DCBFCE5B3D5AC8C5ED3EECAD8B0B28C5FCBCECDEA8C608C61BAEA8C628C638C64E5B58C65E5B48C66D7DAB9D9D6E6B6A8CDF0D2CBB1A6CAB58C67B3E8C9F3BFCDD0FBCAD2E5B6BBC28C688C698C6ACFDCB9AC8C6B8C6C8C6D8C6ED4D78C6F8C70BAA6D1E7CFFCBCD28C71E5B7C8DD8C728C738C74BFEDB1F6CBDE8C758C76BCC58C77BCC4D2FAC3DCBFDC8C788C798C7A8C7BB8BB8C7C8C7D8C7EC3C28C80BAAED4A28C818C828C838C848C858C868C878C888C89C7DEC4AFB2EC8C8AB9D18C8B8C8CE5BBC1C88C8D8C8ED5AF8C8F8C908C918C928C93E5BC8C94E5BE8C958C968C978C988C998C9A8C9BB4E7B6D4CBC2D1B0B5BC8C9C8C9DCAD98C9EB7E28C9F8CA0C9E48CA1BDAB8CA28CA3CEBED7F08CA48CA58CA68CA7D0A18CA8C9D98CA98CAAB6FBE6D8BCE28CABB3BE8CACC9D08CADE6D9B3A28CAE8CAF8CB08CB1DECC8CB2D3C8DECD8CB3D2A28CB48CB58CB68CB7DECE8CB88CB98CBA8CBBBECD8CBC8CBDDECF8CBE8CBF8CC0CAACD2FCB3DFE5EAC4E1BEA1CEB2C4F2BED6C6A8B2E38CC18CC2BED38CC38CC4C7FCCCEBBDECCEDD8CC58CC6CABAC6C1E5ECD0BC8CC78CC88CC9D5B98CCA8CCB8CCCE5ED8CCD8CCE8CCF8CD0CAF48CD1CDC0C2C58CD2E5EF8CD3C2C4E5F08CD48CD58CD68CD78CD88CD98CDAE5F8CDCD8CDBC9BD8CDC8CDD8CDE8CDF8CE08CE18CE2D2D9E1A88CE38CE48CE58CE6D3EC8CE7CBEAC6F18CE88CE98CEA8CEB8CECE1AC8CED8CEE8CEFE1A7E1A98CF08CF1E1AAE1AF8CF28CF3B2ED8CF4E1ABB8DAE1ADE1AEE1B0B5BAE1B18CF58CF68CF78CF88CF9E1B3E1B88CFA8CFB8CFC8CFD8CFED1D28D40E1B6E1B5C1EB8D418D428D43E1B78D44D4C08D45E1B28D46E1BAB0B68D478D488D498D4AE1B48D4BBFF98D4CE1B98D4D8D4EE1BB8D4F8D508D518D528D538D54E1BE8D558D568D578D588D598D5AE1BC8D5B8D5C8D5D8D5E8D5F8D60D6C58D618D628D638D648D658D668D67CFBF8D688D69E1BDE1BFC2CD8D6AB6EB8D6BD3F88D6C8D6DC7CD8D6E8D6FB7E58D708D718D728D738D748D758D768D778D788D79BEFE8D7A8D7B8D7C8D7D8D7E8D80E1C0E1C18D818D82E1C7B3E78D838D848D858D868D878D88C6E98D898D8A8D8B8D8C8D8DB4DE8D8ED1C28D8F8D908D918D92E1C88D938D94E1C68D958D968D978D988D99E1C58D9AE1C3E1C28D9BB1C08D9C8D9D8D9ED5B8E1C48D9F8DA08DA18DA28DA3E1CB8DA48DA58DA68DA78DA88DA98DAA8DABE1CCE1CA8DAC8DAD8DAE8DAF8DB08DB18DB28DB3EFFA8DB48DB5E1D3E1D2C7B68DB68DB78DB88DB98DBA8DBB8DBC8DBD8DBE8DBF8DC0E1C98DC18DC2E1CE8DC3E1D08DC48DC58DC68DC78DC88DC98DCA8DCB8DCC8DCD8DCEE1D48DCFE1D1E1CD8DD08DD1E1CF8DD28DD38DD48DD5E1D58DD68DD78DD88DD98DDA8DDB8DDC8DDD8DDE8DDF8DE08DE18DE2E1D68DE38DE48DE58DE68DE78DE88DE98DEA8DEB8DEC8DED8DEE8DEF8DF08DF18DF28DF38DF48DF58DF68DF78DF8E1D78DF98DFA8DFBE1D88DFC8DFD8DFE8E408E418E428E438E448E458E468E478E488E498E4A8E4B8E4C8E4D8E4E8E4F8E508E518E528E538E548E55E1DA8E568E578E588E598E5A8E5B8E5C8E5D8E5E8E5F8E608E618E62E1DB8E638E648E658E668E678E688E69CEA18E6A8E6B8E6C8E6D8E6E8E6F8E708E718E728E738E748E758E76E7DD8E77B4A8D6DD8E788E79D1B2B3B28E7A8E7BB9A4D7F3C7C9BEDEB9AE8E7CCED78E7D8E7EB2EEDBCF8E80BCBAD2D1CBC8B0CD8E818E82CFEF8E838E848E858E868E87D9E3BDED8E888E89B1D2CAD0B2BC8E8ACBA7B7AB8E8BCAA68E8C8E8D8E8ECFA38E8F8E90E0F8D5CAE0FB8E918E92E0FAC5C1CCFB8E93C1B1E0F9D6E3B2AFD6C4B5DB8E948E958E968E978E988E998E9A8E9BB4F8D6A18E9C8E9D8E9E8E9F8EA0CFAFB0EF8EA18EA2E0FC8EA38EA48EA58EA68EA7E1A1B3A38EA88EA9E0FDE0FEC3B18EAA8EAB8EAC8EADC3DD8EAEE1A2B7F98EAF8EB08EB18EB28EB38EB4BBCF8EB58EB68EB78EB88EB98EBA8EBBE1A3C4BB8EBC8EBD8EBE8EBF8EC0E1A48EC18EC2E1A58EC38EC4E1A6B4B18EC58EC68EC78EC88EC98ECA8ECB8ECC8ECD8ECE8ECF8ED08ED18ED28ED3B8C9C6BDC4EA8ED4B2A28ED5D0D28ED6E7DBBBC3D3D7D3C48ED7B9E3E2CF8ED88ED98EDAD7AF8EDBC7ECB1D38EDC8EDDB4B2E2D18EDE8EDF8EE0D0F2C2AEE2D08EE1BFE2D3A6B5D7E2D2B5EA8EE2C3EDB8FD8EE3B8AE8EE4C5D3B7CFE2D48EE58EE68EE78EE8E2D3B6C8D7F98EE98EEA8EEB8EEC8EEDCDA58EEE8EEF8EF08EF18EF2E2D88EF3E2D6CAFCBFB5D3B9E2D58EF48EF58EF68EF7E2D78EF88EF98EFA8EFB8EFC8EFD8EFE8F408F418F42C1AEC0C88F438F448F458F468F478F48E2DBE2DAC0AA8F498F4AC1CE8F4B8F4C8F4D8F4EE2DC8F4F8F508F518F528F538F548F558F568F578F588F598F5AE2DD8F5BE2DE8F5C8F5D8F5E8F5F8F608F618F628F638F64DBC88F65D1D3CDA28F668F67BDA88F688F698F6ADEC3D8A5BFAADBCDD2ECC6FAC5AA8F6B8F6C8F6DDEC48F6EB1D7DFAE8F6F8F708F71CABD8F72DFB18F73B9AD8F74D2FD8F75B8A5BAEB8F768F77B3DA8F788F798F7AB5DCD5C58F7B8F7C8F7D8F7EC3D6CFD2BBA18F80E5F3E5F28F818F82E5F48F83CDE48F84C8F58F858F868F878F888F898F8A8F8BB5AFC7BF8F8CE5F68F8D8F8E8F8FECB08F908F918F928F938F948F958F968F978F988F998F9A8F9B8F9C8F9D8F9EE5E68F9FB9E9B5B18FA0C2BCE5E8E5E7E5E98FA18FA28FA38FA4D2CD8FA58FA68FA7E1EAD0CE8FA8CDAE8FA9D1E58FAA8FABB2CAB1EB8FACB1F2C5ED8FAD8FAED5C3D3B08FAFE1DC8FB08FB18FB2E1DD8FB3D2DB8FB4B3B9B1CB8FB58FB68FB7CDF9D5F7E1DE8FB8BEB6B4FD8FB9E1DFBADCE1E0BBB2C2C9E1E18FBA8FBB8FBCD0EC8FBDCDBD8FBE8FBFE1E28FC0B5C3C5C7E1E38FC18FC2E1E48FC38FC48FC58FC6D3F98FC78FC88FC98FCA8FCB8FCCE1E58FCDD1AD8FCE8FCFE1E6CEA28FD08FD18FD28FD38FD48FD5E1E78FD6B5C28FD78FD88FD98FDAE1E8BBD58FDB8FDC8FDD8FDE8FDFD0C4E2E0B1D8D2E48FE08FE1E2E18FE28FE3BCC9C8CC8FE4E2E3ECFEECFDDFAF8FE58FE68FE7E2E2D6BECDFCC3A68FE88FE98FEAE3C38FEB8FECD6D2E2E78FED8FEEE2E88FEF8FF0D3C78FF18FF2E2ECBFEC8FF3E2EDE2E58FF48FF5B3C08FF68FF78FF8C4EE8FF98FFAE2EE8FFB8FFCD0C38FFDBAF6E2E9B7DEBBB3CCACCBCBE2E4E2E6E2EAE2EB8FFE90409041E2F790429043E2F4D4F5E2F390449045C5AD9046D5FAC5C2B2C090479048E2EF9049E2F2C1AFCBBC904A904BB5A1E2F9904C904D904EBCB1E2F1D0D4D4B9E2F5B9D6E2F6904F90509051C7D390529053905490559056E2F0905790589059905A905BD7DCEDA1905C905DE2F8905EEDA5E2FECAD1905F906090619062906390649065C1B59066BBD090679068BFD69069BAE3906A906BCBA1906C906D906EEDA6EDA3906F9070EDA29071907290739074BBD6EDA7D0F490759076EDA4BADEB6F7E3A1B6B2CCF1B9A79077CFA2C7A190789079BFD2907A907BB6F1907CE2FAE2FBE2FDE2FCC4D5E3A2907DD3C1907E90809081E3A7C7C49082908390849085CFA490869087E3A9BAB790889089908A908BE3A8908CBBDA908DE3A3908E908F9090E3A4E3AA9091E3A69092CEF2D3C690939094BBBC90959096D4C39097C4FA90989099EDA8D0FCE3A5909AC3F5909BE3ADB1AF909CE3B2909D909E909FBCC290A090A1E3ACB5BF90A290A390A490A590A690A790A890A9C7E9E3B090AA90AB90ACBEAACDEF90AD90AE90AF90B090B1BBF390B290B390B4CCE890B590B6E3AF90B7E3B190B8CFA7E3AE90B9CEA9BBDD90BA90BB90BC90BD90BEB5EBBEE5B2D2B3CD90BFB1B9E3ABB2D1B5ACB9DFB6E890C090C1CFEBE3B790C2BBCC90C390C4C8C7D0CA90C590C690C790C890C9E3B8B3EE90CA90CB90CC90CDEDA990CED3FAD3E490CF90D090D1EDAAE3B9D2E290D290D390D490D590D6E3B590D790D890D990DAD3DE90DB90DC90DD90DEB8D0E3B390DF90E0E3B6B7DF90E1E3B4C0A290E290E390E4E3BA90E590E690E790E890E990EA90EB90EC90ED90EE90EF90F090F190F290F390F490F590F690F7D4B890F890F990FA90FB90FC90FD90FE9140B4C89141E3BB9142BBC59143C9F791449145C9E5914691479148C4BD9149914A914B914C914D914E914FEDAB9150915191529153C2FD9154915591569157BBDBBFAE91589159915A915B915C915D915ECEBF915F916091619162E3BC9163BFB6916491659166916791689169916A916B916C916D916E916F9170917191729173917491759176B1EF91779178D4F79179917A917B917C917DE3BE917E9180918191829183918491859186EDAD918791889189918A918B918C918D918E918FE3BFBAA9EDAC91909191E3BD91929193919491959196919791989199919A919BE3C0919C919D919E919F91A091A1BAB691A291A391A4B6AE91A591A691A791A891A9D0B891AAB0C3EDAE91AB91AC91AD91AE91AFEDAFC0C191B0E3C191B191B291B391B491B591B691B791B891B991BA91BB91BC91BD91BE91BF91C091C1C5B391C291C391C491C591C691C791C891C991CA91CB91CC91CD91CE91CFE3C291D091D191D291D391D491D591D691D791D8DCB291D991DA91DB91DC91DD91DEEDB091DFB8EA91E0CEECEAA7D0E7CAF9C8D6CFB7B3C9CED2BDE491E191E2E3DEBBF2EAA8D5BD91E3C6DDEAA991E491E591E6EAAA91E7EAACEAAB91E8EAAEEAAD91E991EA91EB91ECBDD891EDEAAF91EEC2BE91EF91F091F191F2B4C1B4F791F391F4BBA791F591F691F791F891F9ECE6ECE5B7BFCBF9B1E291FAECE791FB91FC91FDC9C8ECE8ECE991FECAD6DED0B2C5D4FA92409241C6CBB0C7B4F2C8D3924292439244CDD092459246BFB8924792489249924A924B924C924DBFDB924E924FC7A4D6B49250C0A9DED1C9A8D1EFC5A4B0E7B3B6C8C592519252B0E292539254B7F692559256C5FA92579258B6F39259D5D2B3D0BCBC925A925B925CB3AD925D925E925F9260BEF1B0D1926192629263926492659266D2D6CAE3D7A59267CDB6B6B6BFB9D5DB9268B8A7C5D79269926A926BDED2BFD9C2D5C7C0926CBBA4B1A8926D926EC5EA926F9270C5FBCCA79271927292739274B1A7927592769277B5D692789279927AC4A8927BDED3D1BAB3E9927CC3F2927D927EB7F79280D6F4B5A3B2F0C4B4C4E9C0ADDED49281B0E8C5C4C1E09282B9D59283BEDCCDD8B0CE9284CDCFDED6BED0D7BEDED5D5D0B0DD92859286C4E292879288C2A3BCF09289D3B5C0B9C5A1B2A6D4F1928A928BC0A8CAC3DED7D5FC928CB9B0928DC8ADCBA9928EDED9BFBD928F929092919292C6B4D7A7CAB0C4C39293B3D6B9D29294929592969297D6B8EAFCB0B492989299929A929BBFE6929C929DCCF4929E929F92A092A1CDDA92A292A392A4D6BFC2CE92A5CECECCA2D0AEC4D3B5B2DED8D5F5BCB7BBD392A692A7B0A492A8C5B2B4EC92A992AA92ABD5F192AC92ADEAFD92AE92AF92B092B192B292B3DEDACDA692B492B5CDEC92B692B792B892B9CEE6DEDC92BACDB1C0A692BB92BCD7BD92BDDEDBB0C6BAB4C9D3C4F3BEE892BE92BF92C092C1B2B692C292C392C492C592C692C792C892C9C0CCCBF092CABCF1BBBBB5B792CB92CC92CDC5F592CEDEE692CF92D092D1DEE3BEDD92D292D3DEDF92D492D592D692D7B4B7BDDD92D892D9DEE0C4ED92DA92DB92DC92DDCFC692DEB5E092DF92E092E192E2B6DECADAB5F4DEE592E3D5C692E4DEE1CCCDC6FE92E5C5C592E692E792E8D2B492E9BEF292EA92EB92EC92ED92EE92EF92F0C2D392F1CCBDB3B892F2BDD392F3BFD8CDC6D1DAB4EB92F4DEE4DEDDDEE792F5EAFE92F692F7C2B0DEE292F892F9D6C0B5A792FAB2F492FBDEE892FCDEF292FD92FE934093419342DEED9343DEF193449345C8E0934693479348D7E1DEEFC3E8CCE19349B2E5934A934B934CD2BE934D934E934F9350935193529353DEEE9354DEEBCED59355B4A79356935793589359935ABFABBEBE935B935CBDD2935D935E935F9360DEE99361D4AE9362DEDE9363DEEA9364936593669367C0BF9368DEECB2F3B8E9C2A79369936ABDC1936B936C936D936E936FDEF5DEF893709371B2ABB4A493729373B4EAC9A6937493759376937793789379DEF6CBD1937AB8E3937BDEF7DEFA937C937D937E9380DEF9938193829383CCC29384B0E1B4EE93859386938793889389938AE5BA938B938C938D938E938FD0AF93909391B2EB9392EBA19393DEF493949395C9E3DEF3B0DAD2A1B1F79396CCAF939793989399939A939B939C939DDEF0939ECBA4939F93A093A1D5AA93A293A393A493A593A6DEFB93A793A893A993AA93AB93AC93AD93AEB4DD93AFC4A693B093B193B2DEFD93B393B493B593B693B793B893B993BA93BB93BCC3FEC4A1DFA193BD93BE93BF93C093C193C293C3C1CC93C4DEFCBEEF93C5C6B293C693C793C893C993CA93CB93CC93CD93CEB3C5C8F693CF93D0CBBADEFE93D193D2DFA493D393D493D593D6D7B293D793D893D993DA93DBB3B793DC93DD93DE93DFC1C393E093E1C7CBB2A5B4E993E2D7AB93E393E493E593E6C4EC93E7DFA2DFA393E8DFA593E9BAB393EA93EB93ECDFA693EDC0DE93EE93EFC9C393F093F193F293F393F493F593F6B2D9C7E693F7DFA793F8C7DC93F993FA93FB93FCDFA8EBA293FD93FE944094419442CBD3944394449445DFAA9446DFA99447B2C194489449944A944B944C944D944E944F9450945194529453945494559456945794589459945A945B945C945D945E945F9460C5CA94619462946394649465946694679468DFAB9469946A946B946C946D946E946F9470D4DC94719472947394749475C8C19476947794789479947A947B947C947D947E948094819482DFAC94839484948594869487BEF094889489DFADD6A7948A948B948C948DEAB7EBB6CAD5948ED8FCB8C4948FB9A594909491B7C5D5FE94929493949494959496B9CA94979498D0A7F4CD9499949AB5D0949B949CC3F4949DBEC8949E949F94A0EBB7B0BD94A194A2BDCC94A3C1B294A4B1D6B3A894A594A694A7B8D2C9A294A894A9B6D894AA94AB94AC94ADEBB8BEB494AE94AF94B0CAFD94B1C7C394B2D5FB94B394B4B7F394B594B694B794B894B994BA94BB94BC94BD94BE94BF94C094C194C294C3CEC494C494C594C6D5ABB1F394C794C894C9ECB3B0DF94CAECB594CB94CC94CDB6B794CEC1CF94CFF5FAD0B194D094D1D5E594D2CED394D394D4BDEFB3E294D5B8AB94D6D5B694D7EDBD94D8B6CF94D9CBB9D0C294DA94DB94DC94DD94DE94DF94E094E1B7BD94E294E3ECB6CAA994E494E594E6C5D494E7ECB9ECB8C2C3ECB794E894E994EA94EBD0FDECBA94ECECBBD7E594ED94EEECBC94EF94F094F1ECBDC6EC94F294F394F494F594F694F794F894F9CEDE94FABCC894FB94FCC8D5B5A9BEC9D6BCD4E794FD94FED1AED0F1EAB8EAB9EABABAB59540954195429543CAB1BFF595449545CDFA9546954795489549954AEAC0954BB0BAEABE954C954DC0A5954E954F9550EABB9551B2FD9552C3F7BBE8955395549555D2D7CEF4EABF955695579558EABC9559955A955BEAC3955CD0C7D3B3955D955E955F9560B4BA9561C3C1D7F29562956395649565D5D19566CAC79567EAC595689569EAC4EAC7EAC6956A956B956C956D956ED6E7956FCFD495709571EACB9572BBCE9573957495759576957795789579BDFAC9CE957A957BEACC957C957DC9B9CFFEEACAD4CEEACDEACF957E9580CDED9581958295839584EAC99585EACE95869587CEEE9588BBDE9589B3BF958A958B958C958D958EC6D5BEB0CEFA958F95909591C7E79592BEA7EAD095939594D6C7959595969597C1C095989599959AD4DD959BEAD1959C959DCFBE959E959F95A095A1EAD295A295A395A495A5CAEE95A695A795A895A9C5AFB0B595AA95AB95AC95AD95AEEAD495AF95B095B195B295B395B495B595B695B7EAD3F4DF95B895B995BA95BB95BCC4BA95BD95BE95BF95C095C1B1A995C295C395C495C5E5DF95C695C795C895C9EAD595CA95CB95CC95CD95CE95CF95D095D195D295D395D495D595D695D795D895D995DA95DB95DC95DD95DE95DF95E095E195E295E3CAEF95E4EAD6EAD7C6D895E595E695E795E895E995EA95EB95ECEAD895ED95EEEAD995EF95F095F195F295F395F4D4BB95F5C7FAD2B7B8FC95F695F7EAC295F8B2DC95F995FAC2FC95FBD4F8CCE6D7EE95FC95FD95FE9640964196429643D4C2D3D0EBC3C5F39644B7FE96459646EBD4964796489649CBB7EBDE964AC0CA964B964C964DCDFB964EB3AF964FC6DA965096519652965396549655EBFC9656C4BE9657CEB4C4A9B1BED4FD9658CAF59659D6EC965A965BC6D3B6E4965C965D965E965FBBFA96609661D0E096629663C9B19664D4D3C8A896659666B8CB9667E8BEC9BC96689669E8BB966AC0EED0D3B2C4B4E5966BE8BC966C966DD5C8966E966F967096719672B6C59673E8BDCAF8B8DCCCF5967496759676C0B496779678D1EEE8BFE8C29679967ABABC967BB1ADBDDC967CEABDE8C3967DE8C6967EE8CB9680968196829683E8CC9684CBC9B0E59685BCAB96869687B9B996889689E8C1968ACDF7968BE8CA968C968D968E968FCEF69690969196929693D5ED9694C1D6E8C49695C3B69696B9FBD6A6E8C8969796989699CAE0D4E6969AE8C0969BE8C5E8C7969CC7B9B7E3969DE8C9969EBFDDE8D2969F96A0E8D796A1E8D5BCDCBCCFE8DB96A296A396A496A596A696A796A896A9E8DE96AAE8DAB1FA96AB96AC96AD96AE96AF96B096B196B296B396B4B0D8C4B3B8CCC6E2C8BEC8E196B596B696B7E8CFE8D4E8D696B8B9F1E8D8D7F596B9C4FB96BAE8DC96BB96BCB2E996BD96BE96BFE8D196C096C1BCED96C296C3BFC2E8CDD6F996C4C1F8B2F196C596C696C796C896C996CA96CB96CCE8DF96CDCAC1E8D996CE96CF96D096D1D5A496D2B1EAD5BBE8CEE8D0B6B0E8D396D3E8DDC0B896D4CAF796D5CBA896D696D7C6DCC0F596D896D996DA96DB96DCE8E996DD96DE96DFD0A396E096E196E296E396E496E596E6E8F2D6EA96E796E896E996EA96EB96EC96EDE8E0E8E196EE96EF96F0D1F9BACBB8F996F196F2B8F1D4D4E8EF96F3E8EEE8ECB9F0CCD2E8E6CEA6BFF296F4B0B8E8F1E8F096F5D7C096F6E8E496F7CDA9C9A396F8BBB8BDDBE8EA96F996FA96FB96FC96FD96FE9740974197429743E8E2E8E3E8E5B5B5E8E7C7C5E8EBE8EDBDB0D7AE9744E8F897459746974797489749974A974B974CE8F5974DCDB0E8F6974E974F9750975197529753975497559756C1BA9757E8E89758C3B7B0F09759975A975B975C975D975E975F9760E8F4976197629763E8F7976497659766B9A3976797689769976A976B976C976D976E976F9770C9D2977197729773C3CECEE0C0E69774977597769777CBF39778CCDDD0B59779977ACAE1977BE8F3977C977D977E9780978197829783978497859786BCEC9787E8F997889789978A978B978C978DC3DE978EC6E5978FB9F79790979197929793B0F497949795D7D897969797BCAC9798C5EF9799979A979B979C979DCCC4979E979FE9A697A097A197A297A397A497A597A697A797A897A9C9AD97AAE9A2C0E297AB97AC97ADBFC397AE97AF97B0E8FEB9D797B1E8FB97B297B397B497B5E9A497B697B797B8D2CE97B997BA97BB97BC97BDE9A397BED6B2D7B597BFE9A797C0BDB797C197C297C397C497C597C697C797C897C997CA97CB97CCE8FCE8FD97CD97CE97CFE9A197D097D197D297D397D497D597D697D7CDD697D897D9D2AC97DA97DB97DCE9B297DD97DE97DF97E0E9A997E197E297E3B4AA97E4B4BB97E597E6E9AB97E797E897E997EA97EB97EC97ED97EE97EF97F097F197F297F397F497F597F697F7D0A897F897F9E9A597FA97FBB3FE97FC97FDE9ACC0E397FEE9AA98409841E9B998429843E9B89844984598469847E9AE98489849E8FA984A984BE9A8984C984D984E984F9850BFACE9B1E9BA98519852C2A5985398549855E9AF9856B8C59857E9AD9858D3DCE9B4E9B5E9B79859985A985BE9C7985C985D985E985F98609861C0C6E9C598629863E9B098649865E9BBB0F19866986798689869986A986B986C986D986E986FE9BCD5A598709871E9BE9872E9BF987398749875E9C198769877C1F198789879C8B6987A987B987CE9BD987D987E988098819882E9C29883988498859886988798889889988AE9C3988BE9B3988CE9B6988DBBB1988E988F9890E9C0989198929893989498959896BCF7989798989899E9C4E9C6989A989B989C989D989E989F98A098A198A298A398A498A5E9CA98A698A798A898A9E9CE98AA98AB98AC98AD98AE98AF98B098B198B298B3B2DB98B4E9C898B598B698B798B898B998BA98BB98BC98BD98BEB7AE98BF98C098C198C298C398C498C598C698C798C898C998CAE9CBE9CC98CB98CC98CD98CE98CF98D0D5C198D1C4A398D298D398D498D598D698D7E9D898D8BAE198D998DA98DB98DCE9C998DDD3A398DE98DF98E0E9D498E198E298E398E498E598E698E7E9D7E9D098E898E998EA98EB98ECE9CF98ED98EEC7C198EF98F098F198F298F398F498F598F6E9D298F798F898F998FA98FB98FC98FDE9D9B3C898FEE9D399409941994299439944CFF0994599469947E9CD99489949994A994B994C994D994E994F995099519952B3F79953995499559956995799589959E9D6995A995BE9DA995C995D995ECCB4995F99609961CFAD99629963996499659966996799689969996AE9D5996BE9DCE9DB996C996D996E996F9970E9DE99719972997399749975997699779978E9D19979997A997B997C997D997E99809981E9DD9982E9DFC3CA9983998499859986998799889989998A998B998C998D998E998F9990999199929993999499959996999799989999999A999B999C999D999E999F99A099A199A299A399A499A599A699A799A899A999AA99AB99AC99AD99AE99AF99B099B199B299B399B499B599B699B799B899B999BA99BB99BC99BD99BE99BF99C099C199C299C399C499C599C699C799C899C999CA99CB99CC99CD99CE99CF99D099D199D299D399D499D599D699D799D899D999DA99DB99DC99DD99DE99DF99E099E199E299E399E499E599E699E799E899E999EA99EB99EC99ED99EE99EF99F099F199F299F399F499F5C7B7B4CEBBB6D0C0ECA399F699F7C5B799F899F999FA99FB99FC99FD99FE9A409A419A42D3FB9A439A449A459A46ECA49A47ECA5C6DB9A489A499A4ABFEE9A4B9A4C9A4D9A4EECA69A4F9A50ECA7D0AA9A51C7B89A529A53B8E89A549A559A569A579A589A599A5A9A5B9A5C9A5D9A5E9A5FECA89A609A619A629A639A649A659A669A67D6B9D5FDB4CBB2BDCEE4C6E79A689A69CDE19A6A9A6B9A6C9A6D9A6E9A6F9A709A719A729A739A749A759A769A77B4F59A78CBC0BCDF9A799A7A9A7B9A7CE9E2E9E3D1EAE9E59A7DB4F9E9E49A7ED1B3CAE2B2D09A80E9E89A819A829A839A84E9E6E9E79A859A86D6B39A879A889A89E9E9E9EA9A8A9A8B9A8C9A8D9A8EE9EB9A8F9A909A919A929A939A949A959A96E9EC9A979A989A999A9A9A9B9A9C9A9D9A9EECAFC5B9B6CE9A9FD2F39AA09AA19AA29AA39AA49AA59AA6B5EE9AA7BBD9ECB19AA89AA9D2E39AAA9AAB9AAC9AAD9AAECEE39AAFC4B89AB0C3BF9AB19AB2B6BED8B9B1C8B1CFB1D1C5FE9AB3B1D09AB4C3AB9AB59AB69AB79AB89AB9D5B19ABA9ABB9ABC9ABD9ABE9ABF9AC09AC1EBA4BAC19AC29AC39AC4CCBA9AC59AC69AC7EBA59AC8EBA79AC99ACA9ACBEBA89ACC9ACD9ACEEBA69ACF9AD09AD19AD29AD39AD49AD5EBA9EBABEBAA9AD69AD79AD89AD99ADAEBAC9ADBCACFD8B5C3F19ADCC3A5C6F8EBADC4CA9ADDEBAEEBAFEBB0B7D59ADE9ADF9AE0B7FA9AE1EBB1C7E29AE2EBB39AE3BAA4D1F5B0B1EBB2EBB49AE49AE59AE6B5AAC2C8C7E89AE7EBB59AE8CBAEE3DF9AE99AEAD3C09AEB9AEC9AED9AEED9DB9AEF9AF0CDA1D6ADC7F39AF19AF29AF3D9E0BBE39AF4BABAE3E29AF59AF69AF79AF89AF9CFAB9AFA9AFB9AFCE3E0C9C79AFDBAB99AFE9B409B41D1B4E3E1C8EAB9AFBDADB3D8CEDB9B429B43CCC09B449B459B46E3E8E3E9CDF49B479B489B499B4A9B4BCCAD9B4CBCB39B4DE3EA9B4EE3EB9B4F9B50D0DA9B519B529B53C6FBB7DA9B549B55C7DFD2CACED69B56E3E4E3EC9B57C9F2B3C19B589B59E3E79B5A9B5BC6E3E3E59B5C9B5DEDB3E3E69B5E9B5F9B609B61C9B39B62C5E69B639B649B65B9B59B66C3BB9B67E3E3C5BDC1A4C2D9B2D79B68E3EDBBA6C4AD9B69E3F0BEDA9B6A9B6BE3FBE3F5BAD39B6C9B6D9B6E9B6FB7D0D3CD9B70D6CED5D3B9C1D5B4D1D89B719B729B739B74D0B9C7F69B759B769B77C8AAB2B49B78C3DA9B799B7A9B7BE3EE9B7C9B7DE3FCE3EFB7A8E3F7E3F49B7E9B809B81B7BA9B829B83C5A29B84E3F6C5DDB2A8C6FC9B85C4E09B869B87D7A29B88C0E1E3F99B899B8AE3FAE3FDCCA9E3F39B8BD3BE9B8CB1C3EDB4E3F1E3F29B8DE3F8D0BAC6C3D4F3E3FE9B8E9B8FBDE09B909B91E4A79B929B93E4A69B949B959B96D1F3E4A39B97E4A99B989B999B9AC8F79B9B9B9C9B9D9B9ECFB49B9FE4A8E4AEC2E59BA09BA1B6B49BA29BA39BA49BA59BA69BA7BDF29BA8E4A29BA99BAABAE9E4AA9BAB9BACE4AC9BAD9BAEB6FDD6DEE4B29BAFE4AD9BB09BB19BB2E4A19BB3BBEECDDDC7A2C5C99BB49BB5C1F79BB6E4A49BB7C7B3BDACBDBDE4A59BB8D7C7B2E29BB9E4ABBCC3E4AF9BBABBEBE4B0C5A8E4B19BBB9BBC9BBD9BBED5E3BFA39BBFE4BA9BC0E4B79BC1E4BB9BC29BC3E4BD9BC49BC5C6D69BC69BC7BAC6C0CB9BC89BC99BCAB8A1E4B49BCB9BCC9BCD9BCED4A19BCF9BD0BAA3BDFE9BD19BD29BD3E4BC9BD49BD59BD69BD79BD8CDBF9BD99BDAC4F99BDB9BDCCFFBC9E69BDD9BDED3BF9BDFCFD19BE09BE1E4B39BE2E4B8E4B9CCE99BE39BE49BE59BE69BE7CCCE9BE8C0D4E4B5C1B0E4B6CED09BE9BBC1B5D39BEAC8F3BDA7D5C7C9ACB8A2E4CA9BEB9BECE4CCD1C49BED9BEED2BA9BEF9BF0BAAD9BF19BF2BAD49BF39BF49BF59BF69BF79BF8E4C3B5ED9BF99BFA9BFBD7CDE4C0CFFDE4BF9BFC9BFD9BFEC1DCCCCA9C409C419C429C43CAE79C449C459C469C47C4D79C48CCD4E4C89C499C4A9C4BE4C7E4C19C4CE4C4B5AD9C4D9C4ED3D99C4FE4C69C509C519C529C53D2F9B4E39C54BBB49C559C56C9EE9C57B4BE9C589C599C5ABBEC9C5BD1CD9C5CCCEDEDB59C5D9C5E9C5F9C609C619C629C639C64C7E59C659C669C679C68D4A89C69E4CBD7D5E4C29C6ABDA5E4C59C6B9C6CD3E69C6DE4C9C9F89C6E9C6FE4BE9C709C71D3E59C729C73C7FEB6C99C74D4FCB2B3E4D79C759C769C77CEC29C78E4CD9C79CEBC9C7AB8DB9C7B9C7CE4D69C7DBFCA9C7E9C809C81D3CE9C82C3EC9C839C849C859C869C879C889C899C8AC5C8E4D89C8B9C8C9C8D9C8E9C8F9C909C919C92CDC4E4CF9C939C949C959C96E4D4E4D59C97BAFE9C98CFE69C999C9AD5BF9C9B9C9C9C9DE4D29C9E9C9F9CA09CA19CA29CA39CA49CA59CA69CA79CA8E4D09CA99CAAE4CE9CAB9CAC9CAD9CAE9CAF9CB09CB19CB29CB39CB49CB59CB69CB79CB89CB9CDE5CAAA9CBA9CBB9CBCC0A39CBDBDA6E4D39CBE9CBFB8C89CC09CC19CC29CC39CC4E4E7D4B49CC59CC69CC79CC89CC99CCA9CCBE4DB9CCC9CCD9CCEC1EF9CCF9CD0E4E99CD19CD2D2E79CD39CD4E4DF9CD5E4E09CD69CD7CFAA9CD89CD99CDA9CDBCBDD9CDCE4DAE4D19CDDE4E59CDEC8DCE4E39CDF9CE0C4E7E4E29CE1E4E19CE29CE39CE4B3FCE4E89CE59CE69CE79CE8B5E19CE99CEA9CEBD7CC9CEC9CED9CEEE4E69CEFBBAC9CF0D7D2CCCFEBF89CF1E4E49CF29CF3B9F69CF49CF59CF6D6CDE4D9E4DCC2FAE4DE9CF7C2CBC0C4C2D09CF8B1F5CCB29CF99CFA9CFB9CFC9CFD9CFE9D409D419D429D43B5CE9D449D459D469D47E4EF9D489D499D4A9D4B9D4C9D4D9D4E9D4FC6AF9D509D519D52C6E19D539D54E4F59D559D569D579D589D59C2A99D5A9D5B9D5CC0ECD1DDE4EE9D5D9D5E9D5F9D609D619D629D639D649D659D66C4AE9D679D689D69E4ED9D6A9D6B9D6C9D6DE4F6E4F4C2FE9D6EE4DD9D6FE4F09D70CAFE9D71D5C49D729D73E4F19D749D759D769D779D789D799D7AD1FA9D7B9D7C9D7D9D7E9D809D819D82E4EBE4EC9D839D849D85E4F29D86CEAB9D879D889D899D8A9D8B9D8C9D8D9D8E9D8F9D90C5CB9D919D929D93C7B19D94C2BA9D959D969D97E4EA9D989D999D9AC1CA9D9B9D9C9D9D9D9E9D9F9DA0CCB6B3B19DA19DA29DA3E4FB9DA4E4F39DA59DA69DA7E4FA9DA8E4FD9DA9E4FC9DAA9DAB9DAC9DAD9DAE9DAF9DB0B3CE9DB19DB29DB3B3BAE4F79DB49DB5E4F9E4F8C5EC9DB69DB79DB89DB99DBA9DBB9DBC9DBD9DBE9DBF9DC09DC19DC2C0BD9DC39DC49DC59DC6D4E89DC79DC89DC99DCA9DCBE5A29DCC9DCD9DCE9DCF9DD09DD19DD29DD39DD49DD59DD6B0C49DD79DD8E5A49DD99DDAE5A39DDB9DDC9DDD9DDE9DDF9DE0BCA49DE1E5A59DE29DE39DE49DE59DE69DE7E5A19DE89DE99DEA9DEB9DEC9DED9DEEE4FEB1F49DEF9DF09DF19DF29DF39DF49DF59DF69DF79DF89DF9E5A89DFAE5A9E5A69DFB9DFC9DFD9DFE9E409E419E429E439E449E459E469E47E5A7E5AA9E489E499E4A9E4B9E4C9E4D9E4E9E4F9E509E519E529E539E549E559E569E579E589E599E5A9E5B9E5C9E5D9E5E9E5F9E609E619E629E639E649E659E669E679E68C6D99E699E6A9E6B9E6C9E6D9E6E9E6F9E70E5ABE5AD9E719E729E739E749E759E769E77E5AC9E789E799E7A9E7B9E7C9E7D9E7E9E809E819E829E839E849E859E869E879E889E89E5AF9E8A9E8B9E8CE5AE9E8D9E8E9E8F9E909E919E929E939E949E959E969E979E989E999E9A9E9B9E9C9E9D9E9EB9E09E9F9EA0E5B09EA19EA29EA39EA49EA59EA69EA79EA89EA99EAA9EAB9EAC9EAD9EAEE5B19EAF9EB09EB19EB29EB39EB49EB59EB69EB79EB89EB99EBABBF0ECE1C3F09EBBB5C6BBD29EBC9EBD9EBE9EBFC1E9D4EE9EC0BEC49EC19EC29EC3D7C69EC4D4D6B2D3ECBE9EC59EC69EC79EC8EAC19EC99ECA9ECBC2AFB4B69ECC9ECD9ECED1D79ECF9ED09ED1B3B49ED2C8B2BFBBECC09ED39ED4D6CB9ED59ED6ECBFECC19ED79ED89ED99EDA9EDB9EDC9EDD9EDE9EDF9EE09EE19EE29EE3ECC5BEE6CCBFC5DABEBC9EE4ECC69EE5B1FE9EE69EE79EE8ECC4D5A8B5E39EE9ECC2C1B6B3E39EEA9EEBECC3CBB8C0C3CCFE9EEC9EED9EEE9EEFC1D29EF0ECC89EF19EF29EF39EF49EF59EF69EF79EF89EF99EFA9EFB9EFC9EFDBAE6C0D39EFED6F29F409F419F42D1CC9F439F449F459F46BFBE9F47B7B3C9D5ECC7BBE29F48CCCCBDFDC8C89F49CFA99F4A9F4B9F4C9F4D9F4E9F4F9F50CDE99F51C5EB9F529F539F54B7E99F559F569F579F589F599F5A9F5B9F5C9F5D9F5E9F5FD1C9BAB89F609F619F629F639F64ECC99F659F66ECCA9F67BBC0ECCB9F68ECE2B1BAB7D99F699F6A9F6B9F6C9F6D9F6E9F6F9F709F719F729F73BDB99F749F759F769F779F789F799F7A9F7BECCCD1E6ECCD9F7C9F7D9F7E9F80C8BB9F819F829F839F849F859F869F879F889F899F8A9F8B9F8C9F8D9F8EECD19F8F9F909F919F92ECD39F93BBCD9F94BCE59F959F969F979F989F999F9A9F9B9F9C9F9D9F9E9F9F9FA09FA1ECCF9FA2C9B79FA39FA49FA59FA69FA7C3BA9FA8ECE3D5D5ECD09FA99FAA9FAB9FAC9FADD6F39FAE9FAF9FB0ECD2ECCE9FB19FB29FB39FB4ECD49FB5ECD59FB69FB7C9BF9FB89FB99FBA9FBB9FBC9FBDCFA89FBE9FBF9FC09FC19FC2D0DC9FC39FC49FC59FC6D1AC9FC79FC89FC99FCAC8DB9FCB9FCC9FCDECD6CEF59FCE9FCF9FD09FD19FD2CAECECDA9FD39FD49FD59FD69FD79FD89FD9ECD99FDA9FDB9FDCB0BE9FDD9FDE9FDF9FE09FE19FE2ECD79FE3ECD89FE49FE59FE6ECE49FE79FE89FE99FEA9FEB9FEC9FED9FEE9FEFC8BC9FF09FF19FF29FF39FF49FF59FF69FF79FF89FF9C1C79FFA9FFB9FFC9FFD9FFEECDCD1E0A040A041A042A043A044A045A046A047A048A049ECDBA04AA04BA04CA04DD4EFA04EECDDA04FA050A051A052A053A054DBC6A055A056A057A058A059A05AA05BA05CA05DA05EECDEA05FA060A061A062A063A064A065A066A067A068A069A06AB1ACA06BA06CA06DA06EA06FA070A071A072A073A074A075A076A077A078A079A07AA07BA07CA07DA07EA080A081ECDFA082A083A084A085A086A087A088A089A08AA08BECE0A08CD7A6A08DC5C0A08EA08FA090EBBCB0AEA091A092A093BEF4B8B8D2AFB0D6B5F9A094D8B3A095CBACA096E3DDA097A098A099A09AA09BA09CA09DC6ACB0E6A09EA09FA0A0C5C6EBB9A0A1A0A2A0A3A0A4EBBAA0A5A0A6A0A7EBBBA0A8A0A9D1C0A0AAC5A3A0ABEAF2A0ACC4B2A0ADC4B5C0CEA0AEA0AFA0B0EAF3C4C1A0B1CEEFA0B2A0B3A0B4A0B5EAF0EAF4A0B6A0B7C9FCA0B8A0B9C7A3A0BAA0BBA0BCCCD8CEFEA0BDA0BEA0BFEAF5EAF6CFACC0E7A0C0A0C1EAF7A0C2A0C3A0C4A0C5A0C6B6BFEAF8A0C7EAF9A0C8EAFAA0C9A0CAEAFBA0CBA0CCA0CDA0CEA0CFA0D0A0D1A0D2A0D3A0D4A0D5A0D6EAF1A0D7A0D8A0D9A0DAA0DBA0DCA0DDA0DEA0DFA0E0A0E1A0E2C8AEE1EBA0E3B7B8E1ECA0E4A0E5A0E6E1EDA0E7D7B4E1EEE1EFD3CCA0E8A0E9A0EAA0EBA0ECA0EDA0EEE1F1BFF1E1F0B5D2A0EFA0F0A0F1B1B7A0F2A0F3A0F4A0F5E1F3E1F2A0F6BAFCA0F7E1F4A0F8A0F9A0FAA0FBB9B7A0FCBED1A0FDA0FEAA40AA41C4FCAA42BADDBDC6AA43AA44AA45AA46AA47AA48E1F5E1F7AA49AA4AB6C0CFC1CAA8E1F6D5F8D3FCE1F8E1FCE1F9AA4BAA4CE1FAC0EAAA4DE1FEE2A1C0C7AA4EAA4FAA50AA51E1FBAA52E1FDAA53AA54AA55AA56AA57AA58E2A5AA59AA5AAA5BC1D4AA5CAA5DAA5EAA5FE2A3AA60E2A8B2FEE2A2AA61AA62AA63C3CDB2C2E2A7E2A6AA64AA65E2A4E2A9AA66AA67E2ABAA68AA69AA6AD0C9D6EDC3A8E2ACAA6BCFD7AA6CAA6DE2AEAA6EAA6FBAEFAA70AA71E9E0E2ADE2AAAA72AA73AA74AA75BBABD4B3AA76AA77AA78AA79AA7AAA7BAA7CAA7DAA7EAA80AA81AA82AA83E2B0AA84AA85E2AFAA86E9E1AA87AA88AA89AA8AE2B1AA8BAA8CAA8DAA8EAA8FAA90AA91AA92E2B2AA93AA94AA95AA96AA97AA98AA99AA9AAA9BAA9CAA9DE2B3CCA1AA9EE2B4AA9FAAA0AB40AB41AB42AB43AB44AB45AB46AB47AB48AB49AB4AAB4BE2B5AB4CAB4DAB4EAB4FAB50D0FEAB51AB52C2CAAB53D3F1AB54CDF5AB55AB56E7E0AB57AB58E7E1AB59AB5AAB5BAB5CBEC1AB5DAB5EAB5FAB60C2EAAB61AB62AB63E7E4AB64AB65E7E3AB66AB67AB68AB69AB6AAB6BCDE6AB6CC3B5AB6DAB6EE7E2BBB7CFD6AB6FC1E1E7E9AB70AB71AB72E7E8AB73AB74E7F4B2A3AB75AB76AB77AB78E7EAAB79E7E6AB7AAB7BAB7CAB7DAB7EE7ECE7EBC9BAAB80AB81D5E4AB82E7E5B7A9E7E7AB83AB84AB85AB86AB87AB88AB89E7EEAB8AAB8BAB8CAB8DE7F3AB8ED6E9AB8FAB90AB91AB92E7EDAB93E7F2AB94E7F1AB95AB96AB97B0E0AB98AB99AB9AAB9BE7F5AB9CAB9DAB9EAB9FABA0AC40AC41AC42AC43AC44AC45AC46AC47AC48AC49AC4AC7F2AC4BC0C5C0EDAC4CAC4DC1F0E7F0AC4EAC4FAC50AC51E7F6CBF6AC52AC53AC54AC55AC56AC57AC58AC59AC5AE8A2E8A1AC5BAC5CAC5DAC5EAC5FAC60D7C1AC61AC62E7FAE7F9AC63E7FBAC64E7F7AC65E7FEAC66E7FDAC67E7FCAC68AC69C1D5C7D9C5FDC5C3AC6AAC6BAC6CAC6DAC6EC7EDAC6FAC70AC71AC72E8A3AC73AC74AC75AC76AC77AC78AC79AC7AAC7BAC7CAC7DAC7EAC80AC81AC82AC83AC84AC85AC86E8A6AC87E8A5AC88E8A7BAF7E7F8E8A4AC89C8F0C9AAAC8AAC8BAC8CAC8DAC8EAC8FAC90AC91AC92AC93AC94AC95AC96E8A9AC97AC98B9E5AC99AC9AAC9BAC9CAC9DD1FEE8A8AC9EAC9FACA0AD40AD41AD42E8AAAD43E8ADE8AEAD44C1A7AD45AD46AD47E8AFAD48AD49AD4AE8B0AD4BAD4CE8ACAD4DE8B4AD4EAD4FAD50AD51AD52AD53AD54AD55AD56AD57AD58E8ABAD59E8B1AD5AAD5BAD5CAD5DAD5EAD5FAD60AD61E8B5E8B2E8B3AD62AD63AD64AD65AD66AD67AD68AD69AD6AAD6BAD6CAD6DAD6EAD6FAD70AD71E8B7AD72AD73AD74AD75AD76AD77AD78AD79AD7AAD7BAD7CAD7DAD7EAD80AD81AD82AD83AD84AD85AD86AD87AD88AD89E8B6AD8AAD8BAD8CAD8DAD8EAD8FAD90AD91AD92B9CFAD93F0ACAD94F0ADAD95C6B0B0EAC8BFAD96CDDFAD97AD98AD99AD9AAD9BAD9CAD9DCECDEAB1AD9EAD9FADA0AE40EAB2AE41C6BFB4C9AE42AE43AE44AE45AE46AE47AE48EAB3AE49AE4AAE4BAE4CD5E7AE4DAE4EAE4FAE50AE51AE52AE53AE54DDF9AE55EAB4AE56EAB5AE57EAB6AE58AE59AE5AAE5BB8CADFB0C9F5AE5CCCF0AE5DAE5EC9FAAE5FAE60AE61AE62AE63C9FBAE64AE65D3C3CBA6AE66B8A6F0AEB1C2AE67E5B8CCEFD3C9BCD7C9EAAE68B5E7AE69C4D0B5E9AE6AEEAEBBADAE6BAE6CE7DEAE6DEEAFAE6EAE6FAE70AE71B3A9AE72AE73EEB2AE74AE75EEB1BDE7AE76EEB0CEB7AE77AE78AE79AE7AC5CFAE7BAE7CAE7DAE7EC1F4DBCEEEB3D0F3AE80AE81AE82AE83AE84AE85AE86AE87C2D4C6E8AE88AE89AE8AB7ACAE8BAE8CAE8DAE8EAE8FAE90AE91EEB4AE92B3EBAE93AE94AE95BBFBEEB5AE96AE97AE98AE99AE9AE7DCAE9BAE9CAE9DEEB6AE9EAE9FBDAEAEA0AF40AF41AF42F1E2AF43AF44AF45CAE8AF46D2C9F0DAAF47F0DBAF48F0DCC1C6AF49B8EDBECEAF4AAF4BF0DEAF4CC5B1F0DDD1F1AF4DF0E0B0CCBDEAAF4EAF4FAF50AF51AF52D2DFF0DFAF53B4AFB7E8F0E6F0E5C6A3F0E1F0E2B4C3AF54AF55F0E3D5EEAF56AF57CCDBBED2BCB2AF58AF59AF5AF0E8F0E7F0E4B2A1AF5BD6A2D3B8BEB7C8ACAF5CAF5DF0EAAF5EAF5FAF60AF61D1F7AF62D6CCBADBF0E9AF63B6BBAF64AF65CDB4AF66AF67C6A6AF68AF69AF6AC1A1F0EBF0EEAF6BF0EDF0F0F0ECAF6CBBBEF0EFAF6DAF6EAF6FAF70CCB5F0F2AF71AF72B3D5AF73AF74AF75AF76B1D4AF77AF78F0F3AF79AF7AF0F4F0F6B4E1AF7BF0F1AF7CF0F7AF7DAF7EAF80AF81F0FAAF82F0F8AF83AF84AF85F0F5AF86AF87AF88AF89F0FDAF8AF0F9F0FCF0FEAF8BF1A1AF8CAF8DAF8ECEC1F1A4AF8FF1A3AF90C1F6F0FBCADDAF91AF92B4F1B1F1CCB1AF93F1A6AF94AF95F1A7AF96AF97F1ACD5CEF1A9AF98AF99C8B3AF9AAF9BAF9CF1A2AF9DF1ABF1A8F1A5AF9EAF9FF1AAAFA0B040B041B042B043B044B045B046B0A9F1ADB047B048B049B04AB04BB04CF1AFB04DF1B1B04EB04FB050B051B052F1B0B053F1AEB054B055B056B057D1A2B058B059B05AB05BB05CB05DB05EF1B2B05FB060B061F1B3B062B063B064B065B066B067B068B069B9EFB06AB06BB5C7B06CB0D7B0D9B06DB06EB06FD4EDB070B5C4B071BDD4BBCAF0A7B072B073B8DEB074B075F0A8B076B077B0A8B078F0A9B079B07ACDEEB07BB07CF0AAB07DB07EB080B081B082B083B084B085B086B087F0ABB088B089B08AB08BB08CB08DB08EB08FB090C6A4B091B092D6E5F1E4B093F1E5B094B095B096B097B098B099B09AB09BB09CB09DC3F3B09EB09FD3DBB0A0B140D6D1C5E8B141D3AFB142D2E6B143B144EEC1B0BBD5B5D1CEBCE0BAD0B145BFF8B146B8C7B5C1C5CCB147B148CAA2B149B14AB14BC3CBB14CB14DB14EB14FB150EEC2B151B152B153B154B155B156B157B158C4BFB6A2B159EDECC3A4B15AD6B1B15BB15CB15DCFE0EDEFB15EB15FC5CEB160B6DCB161B162CAA1B163B164EDEDB165B166EDF0EDF1C3BCB167BFB4B168EDEEB169B16AB16BB16CB16DB16EB16FB170B171B172B173EDF4EDF2B174B175B176B177D5E6C3DFB178EDF3B179B17AB17BEDF6B17CD5A3D1A3B17DB17EB180EDF5B181C3D0B182B183B184B185B186EDF7BFF4BEECEDF8B187CCF7B188D1DBB189B18AB18BD7C5D5F6B18CEDFCB18DB18EB18FEDFBB190B191B192B193B194B195B196B197EDF9EDFAB198B199B19AB19BB19CB19DB19EB19FEDFDBEA6B1A0B240B241B242B243CBAFEEA1B6BDB244EEA2C4C0B245EDFEB246B247BDDEB2C7B248B249B24AB24BB24CB24DB24EB24FB250B251B252B253B6C3B254B255B256EEA5D8BAEEA3EEA6B257B258B259C3E9B3F2B25AB25BB25CB25DB25EB25FEEA7EEA4CFB9B260B261EEA8C2F7B262B263B264B265B266B267B268B269B26AB26BB26CB26DEEA9EEAAB26EDEABB26FB270C6B3B271C7C6B272D6F5B5C9B273CBB2B274B275B276EEABB277B278CDABB279EEACB27AB27BB27CB27DB27ED5B0B280EEADB281F6C4B282B283B284B285B286B287B288B289B28AB28BB28CB28DB28EDBC7B28FB290B291B292B293B294B295B296B297B4A3B298B299B29AC3ACF1E6B29BB29CB29DB29EB29FCAB8D2D3B2A0D6AAB340EFF2B341BED8B342BDC3EFF3B6CCB0ABB343B344B345B346CAAFB347B348EDB6B349EDB7B34AB34BB34CB34DCEF9B7AFBFF3EDB8C2EBC9B0B34EB34FB350B351B352B353EDB9B354B355C6F6BFB3B356B357B358EDBCC5F8B359D1D0B35AD7A9EDBAEDBBB35BD1E2B35CEDBFEDC0B35DEDC4B35EB35FB360EDC8B361EDC6EDCED5E8B362EDC9B363B364EDC7EDBEB365B366C5E9B367B368B369C6C6B36AB36BC9E9D4D2EDC1EDC2EDC3EDC5B36CC0F9B36DB4A1B36EB36FB370B371B9E8B372EDD0B373B374B375B376EDD1B377EDCAB378EDCFB379CEF8B37AB37BCBB6EDCCEDCDB37CB37DB37EB380B381CFF5B382B383B384B385B386B387B388B389B38AB38BB38CB38DEDD2C1F2D3B2EDCBC8B7B38EB38FB390B391B392B393B394B395BCEFB396B397B398B399C5F0B39AB39BB39CB39DB39EB39FB3A0B440B441B442EDD6B443B5EFB444B445C2B5B0ADCBE9B446B447B1AEB448EDD4B449B44AB44BCDEBB5E2B44CEDD5EDD3EDD7B44DB44EB5FAB44FEDD8B450EDD9B451EDDCB452B1CCB453B454B455B456B457B458B459B45AC5F6BCEEEDDACCBCB2EAB45BB45CB45DB45EEDDBB45FB460B461B462C4EBB463B464B4C5B465B466B467B0F5B468B469B46AEDDFC0DAB4E8B46BB46CB46DB46EC5CDB46FB470B471EDDDBFC4B472B473B474EDDEB475B476B477B478B479B47AB47BB47CB47DB47EB480B481B482B483C4A5B484B485B486EDE0B487B488B489B48AB48BEDE1B48CEDE3B48DB48EC1D7B48FB490BBC7B491B492B493B494B495B496BDB8B497B498B499EDE2B49AB49BB49CB49DB49EB49FB4A0B540B541B542B543B544B545EDE4B546B547B548B549B54AB54BB54CB54DB54EB54FEDE6B550B551B552B553B554EDE5B555B556B557B558B559B55AB55BB55CB55DB55EB55FB560B561B562B563EDE7B564B565B566B567B568CABEECEAC0F1B569C9E7B56AECEBC6EEB56BB56CB56DB56EECECB56FC6EDECEDB570B571B572B573B574B575B576B577B578ECF0B579B57AD7E6ECF3B57BB57CECF1ECEEECEFD7A3C9F1CBEEECF4B57DECF2B57EB580CFE9B581ECF6C6B1B582B583B584B585BCC0B586ECF5B587B588B589B58AB58BB58CB58DB5BBBBF6B58EECF7B58FB590B591B592B593D9F7BDFBB594B595C2BBECF8B596B597B598B599ECF9B59AB59BB59CB59DB8A3B59EB59FB5A0B640B641B642B643B644B645B646ECFAB647B648B649B64AB64BB64CB64DB64EB64FB650B651B652ECFBB653B654B655B656B657B658B659B65AB65BB65CB65DECFCB65EB65FB660B661B662D3EDD8AEC0EBB663C7DDBACCB664D0E3CBBDB665CDBAB666B667B8D1B668B669B1FCB66AC7EFB66BD6D6B66CB66DB66EBFC6C3EBB66FB670EFF5B671B672C3D8B673B674B675B676B677B678D7E2B679B67AB67BEFF7B3D3B67CC7D8D1EDB67DD6C8B67EEFF8B680EFF6B681BBFDB3C6B682B683B684B685B686B687B688BDD5B689B68AD2C6B68BBBE0B68CB68DCFA1B68EEFFCEFFBB68FB690EFF9B691B692B693B694B3CCB695C9D4CBB0B696B697B698B699B69AEFFEB69BB69CB0DEB69DB69ED6C9B69FB6A0B740EFFDB741B3EDB742B743F6D5B744B745B746B747B748B749B74AB74BB74CB74DB74EB74FB750B751B752CEC8B753B754B755F0A2B756F0A1B757B5BEBCDABBFCB758B8E5B759B75AB75BB75CB75DB75EC4C2B75FB760B761B762B763B764B765B766B767B768F0A3B769B76AB76BB76CB76DCBEBB76EB76FB770B771B772B773B774B775B776B777B778B779B77AB77BB77CB77DB77EB780B781B782B783B784B785B786F0A6B787B788B789D1A8B78ABEBFC7EEF1B6F1B7BFD5B78BB78CB78DB78EB4A9F1B8CDBBB78FC7D4D5ADB790F1B9B791F1BAB792B793B794B795C7CFB796B797B798D2A4D6CFB799B79AF1BBBDD1B4B0BEBDB79BB79CB79DB4DCCED1B79EBFDFF1BDB79FB7A0B840B841BFFAF1BCB842F1BFB843B844B845F1BEF1C0B846B847B848B849B84AF1C1B84BB84CB84DB84EB84FB850B851B852B853B854B855C1FEB856B857B858B859B85AB85BB85CB85DB85EB85FB860C1A2B861B862B863B864B865B866B867B868B869B86ACAFAB86BB86CD5BEB86DB86EB86FB870BEBABEB9D5C2B871B872BFA2B873CDAFF1B5B874B875B876B877B878B879BDDFB87AB6CBB87BB87CB87DB87EB880B881B882B883B884D6F1F3C3B885B886F3C4B887B8CDB888B889B88AF3C6F3C7B88BB0CAB88CF3C5B88DF3C9CBF1B88EB88FB890F3CBB891D0A6B892B893B1CAF3C8B894B895B896F3CFB897B5D1B898B899F3D7B89AF3D2B89BB89CB89DF3D4F3D3B7FBB89EB1BFB89FF3CEF3CAB5DAB8A0F3D0B940B941F3D1B942F3D5B943B944B945B946F3CDB947BCE3B948C1FDB949F3D6B94AB94BB94CB94DB94EB94FF3DAB950F3CCB951B5C8B952BDEEF3DCB953B954B7A4BFF0D6FECDB2B955B4F0B956B2DFB957F3D8B958F3D9C9B8B959F3DDB95AB95BF3DEB95CF3E1B95DB95EB95FB960B961B962B963B964B965B966B967F3DFB968B969F3E3F3E2B96AB96BF3DBB96CBFEAB96DB3EFB96EF3E0B96FB970C7A9B971BCF2B972B973B974B975F3EBB976B977B978B979B97AB97BB97CB9BFB97DB97EF3E4B980B981B982B2ADBBFEB983CBE3B984B985B986B987F3EDF3E9B988B989B98AB9DCF3EEB98BB98CB98DF3E5F3E6F3EAC2E1F3ECF3EFF3E8BCFDB98EB98FB990CFE4B991B992F3F0B993B994B995F3E7B996B997B998B999B99AB99BB99CB99DF3F2B99EB99FB9A0BA40D7ADC6AABA41BA42BA43BA44F3F3BA45BA46BA47BA48F3F1BA49C2A8BA4ABA4BBA4CBA4DBA4EB8DDF3F5BA4FBA50F3F4BA51BA52BA53B4DBBA54BA55BA56F3F6F3F7BA57BA58BA59F3F8BA5ABA5BBA5CC0BABA5DBA5EC0E9BA5FBA60BA61BA62BA63C5F1BA64BA65BA66BA67F3FBBA68F3FABA69BA6ABA6BBA6CBA6DBA6EBA6FBA70B4D8BA71BA72BA73F3FEF3F9BA74BA75F3FCBA76BA77BA78BA79BA7ABA7BF3FDBA7CBA7DBA7EBA80BA81BA82BA83BA84F4A1BA85BA86BA87BA88BA89BA8AF4A3BBC9BA8BBA8CF4A2BA8DBA8EBA8FBA90BA91BA92BA93BA94BA95BA96BA97BA98BA99F4A4BA9ABA9BBA9CBA9DBA9EBA9FB2BEF4A6F4A5BAA0BB40BB41BB42BB43BB44BB45BB46BB47BB48BB49BCAEBB4ABB4BBB4CBB4DBB4EBB4FBB50BB51BB52BB53BB54BB55BB56BB57BB58BB59BB5ABB5BBB5CBB5DBB5EBB5FBB60BB61BB62BB63BB64BB65BB66BB67BB68BB69BB6ABB6BBB6CBB6DBB6EC3D7D9E1BB6FBB70BB71BB72BB73BB74C0E0F4CCD7D1BB75BB76BB77BB78BB79BB7ABB7BBB7CBB7DBB7EBB80B7DBBB81BB82BB83BB84BB85BB86BB87F4CEC1A3BB88BB89C6C9BB8AB4D6D5B3BB8BBB8CBB8DF4D0F4CFF4D1CBDABB8EBB8FF4D2BB90D4C1D6E0BB91BB92BB93BB94B7E0BB95BB96BB97C1B8BB98BB99C1BBF4D3BEACBB9ABB9BBB9CBB9DBB9EB4E2BB9FBBA0F4D4F4D5BEABBC40BC41F4D6BC42BC43BC44F4DBBC45F4D7F4DABC46BAFDBC47F4D8F4D9BC48BC49BC4ABC4BBC4CBC4DBC4EB8E2CCC7F4DCBC4FB2DABC50BC51C3D3BC52BC53D4E3BFB7BC54BC55BC56BC57BC58BC59BC5AF4DDBC5BBC5CBC5DBC5EBC5FBC60C5B4BC61BC62BC63BC64BC65BC66BC67BC68F4E9BC69BC6ACFB5BC6BBC6CBC6DBC6EBC6FBC70BC71BC72BC73BC74BC75BC76BC77BC78CEC9BC79BC7ABC7BBC7CBC7DBC7EBC80BC81BC82BC83BC84BC85BC86BC87BC88BC89BC8ABC8BBC8CBC8DBC8ECBD8BC8FCBF7BC90BC91BC92BC93BDF4BC94BC95BC96D7CFBC97BC98BC99C0DBBC9ABC9BBC9CBC9DBC9EBC9FBCA0BD40BD41BD42BD43BD44BD45BD46BD47BD48BD49BD4ABD4BBD4CBD4DBD4EBD4FBD50BD51BD52BD53BD54BD55BD56BD57BD58BD59BD5ABD5BBD5CBD5DBD5EBD5FBD60BD61BD62BD63BD64BD65BD66BD67BD68BD69BD6ABD6BBD6CBD6DBD6EBD6FBD70BD71BD72BD73BD74BD75BD76D0F5BD77BD78BD79BD7ABD7BBD7CBD7DBD7EF4EABD80BD81BD82BD83BD84BD85BD86BD87BD88BD89BD8ABD8BBD8CBD8DBD8EBD8FBD90BD91BD92BD93BD94BD95BD96BD97BD98BD99BD9ABD9BBD9CBD9DBD9EBD9FBDA0BE40BE41BE42BE43BE44BE45BE46BE47BE48BE49BE4ABE4BBE4CF4EBBE4DBE4EBE4FBE50BE51BE52BE53F4ECBE54BE55BE56BE57BE58BE59BE5ABE5BBE5CBE5DBE5EBE5FBE60BE61BE62BE63BE64BE65BE66BE67BE68BE69BE6ABE6BBE6CBE6DBE6EBE6FBE70BE71BE72BE73BE74BE75BE76BE77BE78BE79BE7ABE7BBE7CBE7DBE7EBE80BE81BE82BE83BE84BE85BE86BE87BE88BE89BE8ABE8BBE8CBE8DBE8EBE8FBE90BE91BE92BE93BE94BE95BE96BE97BE98BE99BE9ABE9BBE9CBE9DBE9EBE9FBEA0BF40BF41BF42BF43BF44BF45BF46BF47BF48BF49BF4ABF4BBF4CBF4DBF4EBF4FBF50BF51BF52BF53BF54BF55BF56BF57BF58BF59BF5ABF5BBF5CBF5DBF5EBF5FBF60BF61BF62BF63BF64BF65BF66BF67BF68BF69BF6ABF6BBF6CBF6DBF6EBF6FBF70BF71BF72BF73BF74BF75BF76BF77BF78BF79BF7ABF7BBF7CBF7DBF7EBF80F7E3BF81BF82BF83BF84BF85B7B1BF86BF87BF88BF89BF8AF4EDBF8BBF8CBF8DBF8EBF8FBF90BF91BF92BF93BF94BF95BF96BF97BF98BF99BF9ABF9BBF9CBF9DBF9EBF9FBFA0C040C041C042C043C044C045C046C047C048C049C04AC04BC04CC04DC04EC04FC050C051C052C053C054C055C056C057C058C059C05AC05BC05CC05DC05EC05FC060C061C062C063D7EBC064C065C066C067C068C069C06AC06BC06CC06DC06EC06FC070C071C072C073C074C075C076C077C078C079C07AC07BF4EEC07CC07DC07EE6F9BEC0E6FABAECE6FBCFCBE6FCD4BCBCB6E6FDE6FEBCCDC8D2CEB3E7A1C080B4BFE7A2C9B4B8D9C4C9C081D7DDC2DAB7D7D6BDCEC6B7C4C082C083C5A6E7A3CFDFE7A4E7A5E7A6C1B7D7E9C9F0CFB8D6AFD6D5E7A7B0EDE7A8E7A9C9DCD2EFBEADE7AAB0F3C8DEBDE1E7ABC8C6C084E7ACBBE6B8F8D1A4E7ADC2E7BEF8BDCACDB3E7AEE7AFBEEED0E5C085CBE7CCD0BCCCE7B0BCA8D0F7E7B1C086D0F8E7B2E7B3B4C2E7B4E7B5C9FECEACC3E0E7B7B1C1B3F1C087E7B8E7B9D7DBD5C0E7BAC2CCD7BAE7BBE7BCE7BDBCEAC3E5C0C2E7BEE7BFBCA9C088E7C0E7C1E7B6B6D0E7C2C089E7C3E7C4BBBAB5DEC2C6B1E0E7C5D4B5E7C6B8BFE7C8E7C7B7ECC08AE7C9B2F8E7CAE7CBE7CCE7CDE7CEE7CFE7D0D3A7CBF5E7D1E7D2E7D3E7D4C9C9E7D5E7D6E7D7E7D8E7D9BDC9E7DAF3BEC08BB8D7C08CC8B1C08DC08EC08FC090C091C092C093F3BFC094F3C0F3C1C095C096C097C098C099C09AC09BC09CC09DC09EB9DECDF8C09FC0A0D8E8BAB1C140C2DEEEB7C141B7A3C142C143C144C145EEB9C146EEB8B0D5C147C148C149C14AC14BEEBBD5D6D7EFC14CC14DC14ED6C3C14FC150EEBDCAF0C151EEBCC152C153C154C155EEBEC156C157C158C159EEC0C15AC15BEEBFC15CC15DC15EC15FC160C161C162C163D1F2C164C7BCC165C3C0C166C167C168C169C16AB8E1C16BC16CC16DC16EC16FC1E7C170C171F4C6D0DFF4C7C172CFDBC173C174C8BAC175C176F4C8C177C178C179C17AC17BC17CC17DF4C9F4CAC17EF4CBC180C181C182C183C184D9FAB8FEC185C186E5F1D3F0C187F4E0C188CECCC189C18AC18BB3E1C18CC18DC18EC18FF1B4C190D2EEC191F4E1C192C193C194C195C196CFE8F4E2C197C198C7CCC199C19AC19BC19CC19DC19EB5D4B4E4F4E4C19FC1A0C240F4E3F4E5C241C242F4E6C243C244C245C246F4E7C247BAB2B0BFC248F4E8C249C24AC24BC24CC24DC24EC24FB7ADD2EDC250C251C252D2ABC0CFC253BFBCEBA3D5DFEAC8C254C255C256C257F1F3B6F8CBA3C258C259C4CDC25AF1E7C25BF1E8B8FBF1E9BAC4D4C5B0D2C25CC25DF1EAC25EC25FC260F1EBC261F1ECC262C263F1EDF1EEF1EFF1F1F1F0C5D5C264C265C266C267C268C269F1F2C26AB6FAC26BF1F4D2AEDEC7CBCAC26CC26DB3DCC26EB5A2C26FB9A2C270C271C4F4F1F5C272C273F1F6C274C275C276C1C4C1FBD6B0F1F7C277C278C279C27AF1F8C27BC1AAC27CC27DC27EC6B8C280BEDBC281C282C283C284C285C286C287C288C289C28AC28BC28CC28DC28EF1F9B4CFC28FC290C291C292C293C294F1FAC295C296C297C298C299C29AC29BC29CC29DC29EC29FC2A0C340EDB2EDB1C341C342CBE0D2DEC343CBC1D5D8C344C8E2C345C0DFBCA1C346C347C348C349C34AC34BEBC1C34CC34DD0A4C34ED6E2C34FB6C7B8D8EBC0B8CEC350EBBFB3A6B9C9D6ABC351B7F4B7CAC352C353C354BCE7B7BEEBC6C355EBC7B0B9BFCFC356EBC5D3FDC357EBC8C358C359EBC9C35AC35BB7CEC35CEBC2EBC4C9F6D6D7D5CDD0B2EBCFCEB8EBD0C35DB5A8C35EC35FC360C361C362B1B3EBD2CCA5C363C364C365C366C367C368C369C5D6EBD3C36AEBD1C5DFEBCECAA4EBD5B0FBC36BC36CBAFAC36DC36ED8B7F1E3C36FEBCAEBCBEBCCEBCDEBD6E6C0EBD9C370BFE8D2C8EBD7EBDCB8ECEBD8C371BDBAC372D0D8C373B0B7C374EBDDC4DCC375C376C377C378D6ACC379C37AC37BB4E0C37CC37DC2F6BCB9C37EC380EBDAEBDBD4E0C6EAC4D4EBDFC5A7D9F5C381B2B1C382EBE4C383BDC5C384C385C386EBE2C387C388C389C38AC38BC38CC38DC38EC38FC390C391C392C393EBE3C394C395B8ACC396CDD1EBE5C397C398C399EBE1C39AC1B3C39BC39CC39DC39EC39FC6A2C3A0C440C441C442C443C444C445CCF3C446EBE6C447C0B0D2B8EBE7C448C449C44AB8AFB8ADC44BEBE8C7BBCDF3C44CC44DC44EEBEAEBEBC44FC450C451C452C453EBEDC454C455C456C457D0C8C458EBF2C459EBEEC45AC45BC45CEBF1C8F9C45DD1FCEBECC45EC45FEBE9C460C461C462C463B8B9CFD9C4E5EBEFEBF0CCDACDC8B0F2C464EBF6C465C466C467C468C469EBF5C46AB2B2C46BC46CC46DC46EB8E0C46FEBF7C470C471C472C473C474C475B1ECC476C477CCC5C4A4CFA5C478C479C47AC47BC47CEBF9C47DC47EECA2C480C5F2C481EBFAC482C483C484C485C486C487C488C489C9C5C48AC48BC48CC48DC48EC48FE2DFEBFEC490C491C492C493CDCEECA1B1DBD3B7C494C495D2DCC496C497C498EBFDC499EBFBC49AC49BC49CC49DC49EC49FC4A0C540C541C542C543C544C545C546C547C548C549C54AC54BC54CC54DC54EB3BCC54FC550C551EAB0C552C553D7D4C554F4ABB3F4C555C556C557C558C559D6C1D6C2C55AC55BC55CC55DC55EC55FD5E9BECAC560F4A7C561D2A8F4A8F4A9C562F4AABECBD3DFC563C564C565C566C567C9E0C9E1C568C569F3C2C56ACAE6C56BCCF2C56CC56DC56EC56FC570C571E2B6CBB4C572CEE8D6DBC573F4ADF4AEF4AFC574C575C576C577F4B2C578BABDF4B3B0E3F4B0C579F4B1BDA2B2D5C57AF4B6F4B7B6E6B2B0CFCFF4B4B4ACC57BF4B5C57CC57DF4B8C57EC580C581C582C583F4B9C584C585CDA7C586F4BAC587F4BBC588C589C58AF4BCC58BC58CC58DC58EC58FC590C591C592CBD2C593F4BDC594C595C596C597F4BEC598C599C59AC59BC59CC59DC59EC59FF4BFC5A0C640C641C642C643F4DEC1BCBCE8C644C9ABD1DEE5F5C645C646C647C648DCB3D2D5C649C64ADCB4B0ACDCB5C64BC64CBDDAC64DDCB9C64EC64FC650D8C2C651DCB7D3F3C652C9D6DCBADCB6C653DCBBC3A2C654C655C656C657DCBCDCC5DCBDC658C659CEDFD6A5C65ADCCFC65BDCCDC65CC65DDCD2BDE6C2ABC65EDCB8DCCBDCCEDCBEB7D2B0C5DCC7D0BEDCC1BBA8C65FB7BCDCCCC660C661DCC6DCBFC7DBC662C663C664D1BFDCC0C665C666DCCAC667C668DCD0C669C66ACEADDCC2C66BDCC3DCC8DCC9B2D4DCD1CBD5C66CD4B7DCDBDCDFCCA6DCE6C66DC3E7DCDCC66EC66FBFC1DCD9C670B0FAB9B6DCE5DCD3C671DCC4DCD6C8F4BFE0C672C673C674C675C9BBC676C677C678B1BDC679D3A2C67AC67BDCDAC67CC67DDCD5C67EC6BBC680DCDEC681C682C683C684C685D7C2C3AFB7B6C7D1C3A9DCE2DCD8DCEBDCD4C686C687DCDDC688BEA5DCD7C689DCE0C68AC68BDCE3DCE4C68CDCF8C68DC68EDCE1DDA2DCE7C68FC690C691C692C693C694C695C696C697C698BCEBB4C4C699C69AC3A3B2E7DCFAC69BDCF2C69CDCEFC69DDCFCDCEED2F0B2E8C69EC8D7C8E3DCFBC69FDCEDC6A0C740C741DCF7C742C743DCF5C744C745BEA3DCF4C746B2DDC747C748C749C74AC74BDCF3BCF6DCE8BBC4C74CC0F3C74DC74EC74FC750C751BCD4DCE9DCEAC752DCF1DCF6DCF9B5B4C753C8D9BBE7DCFEDCFDD3ABDDA1DDA3DDA5D2F1DDA4DDA6DDA7D2A9C754C755C756C757C758C759C75ABAC9DDA9C75BC75CDDB6DDB1DDB4C75DC75EC75FC760C761C762C763DDB0C6CEC764C765C0F2C766C767C768C769C9AFC76AC76BC76CDCECDDAEC76DC76EC76FC770DDB7C771C772DCF0DDAFC773DDB8C774DDACC775C776C777C778C779C77AC77BDDB9DDB3DDADC4AAC77CC77DC77EC780DDA8C0B3C1ABDDAADDABC781DDB2BBF1DDB5D3A8DDBAC782DDBBC3A7C783C784DDD2DDBCC785C786C787DDD1C788B9BDC789C78ABED5C78BBEFAC78CC78DBACAC78EC78FC790C791DDCAC792DDC5C793DDBFC794C795C796B2CBDDC3C797DDCBB2A4DDD5C798C799C79ADDBEC79BC79CC79DC6D0DDD0C79EC79FC7A0C840C841DDD4C1E2B7C6C842C843C844C845C846DDCEDDCFC847C848C849DDC4C84AC84BC84CDDBDC84DDDCDCCD1C84EDDC9C84FC850C851C852DDC2C3C8C6BCCEAEDDCCC853DDC8C854C855C856C857C858C859DDC1C85AC85BC85CDDC6C2DCC85DC85EC85FC860C861C862D3A9D3AADDD3CFF4C8F8C863C864C865C866C867C868C869C86ADDE6C86BC86CC86DC86EC86FC870DDC7C871C872C873DDE0C2E4C874C875C876C877C878C879C87AC87BDDE1C87CC87DC87EC880C881C882C883C884C885C886DDD7C887C888C889C88AC88BD6F8C88CDDD9DDD8B8F0DDD6C88DC88EC88FC890C6CFC891B6ADC892C893C894C895C896DDE2C897BAF9D4E1DDE7C898C899C89AB4D0C89BDDDAC89CBFFBDDE3C89DDDDFC89EDDDDC89FC8A0C940C941C942C943C944B5D9C945C946C947C948DDDBDDDCDDDEC949BDAFDDE4C94ADDE5C94BC94CC94DC94EC94FC950C951C952DDF5C953C3C9C954C955CBE2C956C957C958C959DDF2C95AC95BC95CC95DC95EC95FC960C961C962C963C964C965C966D8E1C967C968C6D1C969DDF4C96AC96BC96CD5F4DDF3DDF0C96DC96EDDECC96FDDEFC970DDE8C971C972D0EEC973C974C975C976C8D8DDEEC977C978DDE9C979C97ADDEACBF2C97BDDEDC97CC97DB1CDC97EC980C981C982C983C984C0B6C985BCBBDDF1C986C987DDF7C988DDF6DDEBC989C98AC98BC98CC98DC5EEC98EC98FC990DDFBC991C992C993C994C995C996C997C998C999C99AC99BDEA4C99CC99DDEA3C99EC99FC9A0CA40CA41CA42CA43CA44CA45CA46CA47CA48DDF8CA49CA4ACA4BCA4CC3EFCA4DC2FBCA4ECA4FCA50D5E1CA51CA52CEB5CA53CA54CA55CA56DDFDCA57B2CCCA58CA59CA5ACA5BCA5CCA5DCA5ECA5FCA60C4E8CADFCA61CA62CA63CA64CA65CA66CA67CA68CA69CA6AC7BEDDFADDFCDDFEDEA2B0AAB1CECA6BCA6CCA6DCA6ECA6FDEACCA70CA71CA72CA73DEA6BDB6C8EFCA74CA75CA76CA77CA78CA79CA7ACA7BCA7CCA7DCA7EDEA1CA80CA81DEA5CA82CA83CA84CA85DEA9CA86CA87CA88CA89CA8ADEA8CA8BCA8CCA8DDEA7CA8ECA8FCA90CA91CA92CA93CA94CA95CA96DEADCA97D4CCCA98CA99CA9ACA9BDEB3DEAADEAECA9CCA9DC0D9CA9ECA9FCAA0CB40CB41B1A1DEB6CB42DEB1CB43CB44CB45CB46CB47CB48CB49DEB2CB4ACB4BCB4CCB4DCB4ECB4FCB50CB51CB52CB53CB54D1A6DEB5CB55CB56CB57CB58CB59CB5ACB5BDEAFCB5CCB5DCB5EDEB0CB5FD0BDCB60CB61CB62DEB4CAEDDEB9CB63CB64CB65CB66CB67CB68DEB8CB69DEB7CB6ACB6BCB6CCB6DCB6ECB6FCB70DEBBCB71CB72CB73CB74CB75CB76CB77BDE5CB78CB79CB7ACB7BCB7CB2D8C3EACB7DCB7EDEBACB80C5BACB81CB82CB83CB84CB85CB86DEBCCB87CB88CB89CB8ACB8BCB8CCB8DCCD9CB8ECB8FCB90CB91B7AACB92CB93CB94CB95CB96CB97CB98CB99CB9ACB9BCB9CCB9DCB9ECB9FCBA0CC40CC41D4E5CC42CC43CC44DEBDCC45CC46CC47CC48CC49DEBFCC4ACC4BCC4CCC4DCC4ECC4FCC50CC51CC52CC53CC54C4A2CC55CC56CC57CC58DEC1CC59CC5ACC5BCC5CCC5DCC5ECC5FCC60CC61CC62CC63CC64CC65CC66CC67CC68DEBECC69DEC0CC6ACC6BCC6CCC6DCC6ECC6FCC70CC71CC72CC73CC74CC75CC76CC77D5BACC78CC79CC7ADEC2CC7BCC7CCC7DCC7ECC80CC81CC82CC83CC84CC85CC86CC87CC88CC89CC8ACC8BF2AEBBA2C2B2C5B0C2C7CC8CCC8DF2AFCC8ECC8FCC90CC91CC92D0E9CC93CC94CC95D3DDCC96CC97CC98EBBDCC99CC9ACC9BCC9CCC9DCC9ECC9FCCA0B3E6F2B0CD40F2B1CD41CD42CAADCD43CD44CD45CD46CD47CD48CD49BAE7F2B3F2B5F2B4CBE4CFBAF2B2CAB4D2CFC2ECCD4ACD4BCD4CCD4DCD4ECD4FCD50CEC3F2B8B0F6F2B7CD51CD52CD53CD54CD55F2BECD56B2CFCD57CD58CD59CD5ACD5BCD5CD1C1F2BACD5DCD5ECD5FCD60CD61F2BCD4E9CD62CD63F2BBF2B6F2BFF2BDCD64F2B9CD65CD66F2C7F2C4F2C6CD67CD68F2CAF2C2F2C0CD69CD6ACD6BF2C5CD6CCD6DCD6ECD6FCD70D6FBCD71CD72CD73F2C1CD74C7F9C9DFCD75F2C8B9C6B5B0CD76CD77F2C3F2C9F2D0F2D6CD78CD79BBD7CD7ACD7BCD7CF2D5CDDCCD7DD6EBCD7ECD80F2D2F2D4CD81CD82CD83CD84B8F2CD85CD86CD87CD88F2CBCD89CD8ACD8BF2CEC2F9CD8CD5DDF2CCF2CDF2CFF2D3CD8DCD8ECD8FF2D9D3BCCD90CD91CD92CD93B6EACD94CAF1CD95B7E4F2D7CD96CD97CD98F2D8F2DAF2DDF2DBCD99CD9AF2DCCD9BCD9CCD9DCD9ED1D1F2D1CD9FCDC9CDA0CECFD6A9CE40F2E3CE41C3DBCE42F2E0CE43CE44C0AFF2ECF2DECE45F2E1CE46CE47CE48F2E8CE49CE4ACE4BCE4CF2E2CE4DCE4EF2E7CE4FCE50F2E6CE51CE52F2E9CE53CE54CE55F2DFCE56CE57F2E4F2EACE58CE59CE5ACE5BCE5CCE5DCE5ED3ACF2E5B2F5CE5FCE60F2F2CE61D0ABCE62CE63CE64CE65F2F5CE66CE67CE68BBC8CE69F2F9CE6ACE6BCE6CCE6DCE6ECE6FF2F0CE70CE71F2F6F2F8F2FACE72CE73CE74CE75CE76CE77CE78CE79F2F3CE7AF2F1CE7BCE7CCE7DBAFBCE7EB5FBCE80CE81CE82CE83F2EFF2F7F2EDF2EECE84CE85CE86F2EBF3A6CE87F3A3CE88CE89F3A2CE8ACE8BF2F4CE8CC8DACE8DCE8ECE8FCE90CE91F2FBCE92CE93CE94F3A5CE95CE96CE97CE98CE99CE9ACE9BC3F8CE9CCE9DCE9ECE9FCEA0CF40CF41CF42F2FDCF43CF44F3A7F3A9F3A4CF45F2FCCF46CF47CF48F3ABCF49F3AACF4ACF4BCF4CCF4DC2DDCF4ECF4FF3AECF50CF51F3B0CF52CF53CF54CF55CF56F3A1CF57CF58CF59F3B1F3ACCF5ACF5BCF5CCF5DCF5EF3AFF2FEF3ADCF5FCF60CF61CF62CF63CF64CF65F3B2CF66CF67CF68CF69F3B4CF6ACF6BCF6CCF6DF3A8CF6ECF6FCF70CF71F3B3CF72CF73CF74F3B5CF75CF76CF77CF78CF79CF7ACF7BCF7CCF7DCF7ED0B7CF80CF81CF82CF83F3B8CF84CF85CF86CF87D9F9CF88CF89CF8ACF8BCF8CCF8DF3B9CF8ECF8FCF90CF91CF92CF93CF94CF95F3B7CF96C8E4F3B6CF97CF98CF99CF9AF3BACF9BCF9CCF9DCF9ECF9FF3BBB4C0CFA0D040D041D042D043D044D045D046D047D048D049D04AD04BD04CD04DEEC3D04ED04FD050D051D052D053F3BCD054D055F3BDD056D057D058D1AAD059D05AD05BF4ACD0C6D05CD05DD05ED05FD060D061D0D0D1DCD062D063D064D065D066D067CFCED068D069BDD6D06AD1C3D06BD06CD06DD06ED06FD070D071BAE2E1E9D2C2F1C2B2B9D072D073B1EDF1C3D074C9C0B3C4D075D9F2D076CBA5D077F1C4D078D079D07AD07BD6D4D07CD07DD07ED080D081F1C5F4C0F1C6D082D4ACF1C7D083B0C0F4C1D084D085F4C2D086D087B4FCD088C5DBD089D08AD08BD08CCCBBD08DD08ED08FD0E4D090D091D092D093D094CDE0D095D096D097D098D099F1C8D09AD9F3D09BD09CD09DD09ED09FD0A0B1BBD140CFAED141D142D143B8A4D144D145D146D147D148F1CAD149D14AD14BD14CF1CBD14DD14ED14FD150B2C3C1D1D151D152D7B0F1C9D153D154F1CCD155D156D157D158F1CED159D15AD15BD9F6D15CD2E1D4A3D15DD15EF4C3C8B9D15FD160D161D162D163F4C4D164D165F1CDF1CFBFE3F1D0D166D167F1D4D168D169D16AD16BD16CD16DD16EF1D6F1D1D16FC9D1C5E1D170D171D172C2E3B9FCD173D174F1D3D175F1D5D176D177D178B9D3D179D17AD17BD17CD17DD17ED180F1DBD181D182D183D184D185BAD6D186B0FDF1D9D187D188D189D18AD18BF1D8F1D2F1DAD18CD18DD18ED18FD190F1D7D191D192D193C8ECD194D195D196D197CDCAF1DDD198D199D19AD19BE5BDD19CD19DD19EF1DCD19FF1DED1A0D240D241D242D243D244D245D246D247D248F1DFD249D24ACFE5D24BD24CD24DD24ED24FD250D251D252D253D254D255D256D257D258D259D25AD25BD25CD25DD25ED25FD260D261D262D263F4C5BDF3D264D265D266D267D268D269F1E0D26AD26BD26CD26DD26ED26FD270D271D272D273D274D275D276D277D278D279D27AD27BD27CD27DF1E1D27ED280D281CEF7D282D2AAD283F1FBD284D285B8B2D286D287D288D289D28AD28BD28CD28DD28ED28FD290D291D292D293D294D295D296D297D298D299D29AD29BD29CD29DD29ED29FD2A0D340D341D342D343D344D345D346D347D348D349D34AD34BD34CD34DD34ED34FD350D351D352D353D354D355D356D357D358D359D35AD35BD35CD35DD35EBCFBB9DBD35FB9E6C3D9CAD3EAE8C0C0BEF5EAE9EAEAEAEBD360EAECEAEDEAEEEAEFBDC7D361D362D363F5FBD364D365D366F5FDD367F5FED368F5FCD369D36AD36BD36CBDE2D36DF6A1B4A5D36ED36FD370D371F6A2D372D373D374F6A3D375D376D377ECB2D378D379D37AD37BD37CD37DD37ED380D381D382D383D384D1D4D385D386D387D388D389D38AD9EAD38BD38CD38DD38ED38FD390D391D392D393D394D395D396D397D398D399D39AD39BD39CD39DD39ED39FD3A0D440D441D442D443D444D445D446D447D448D449D44AD44BD44CD44DD44ED44FD450D451D452D453D454D455D456D457D458D459D45AD45BD45CD45DD45ED45FF6A4D460D461D462D463D464D465D466D467D468EEBAD469D46AD46BD46CD46DD46ED46FD470D471D472D473D474D475D476D477D478D479D47AD47BD47CD47DD47ED480D481D482D483D484D485D486D487D488D489D48AD48BD48CD48DD48ED48FD490D491D492D493D494D495D496D497D498D499D5B2D49AD49BD49CD49DD49ED49FD4A0D540D541D542D543D544D545D546D547D3FECCDCD548D549D54AD54BD54CD54DD54ED54FCAC4D550D551D552D553D554D555D556D557D558D559D55AD55BD55CD55DD55ED55FD560D561D562D563D564D565D566D567D568D569D56AD56BD56CD56DD56ED56FD570D571D572D573D574D575D576D577D578D579D57AD57BD57CD57DD57ED580D581D582D583D584D585D586D587D588D589D58AD58BD58CD58DD58ED58FD590D591D592D593D594D595D596D597D598D599D59AD59BD59CD59DD59ED59FD5A0D640D641D642D643D644D645D646D647D648D649D64AD64BD64CD64DD64ED64FD650D651D652D653D654D655D656D657D658D659D65AD65BD65CD65DD65ED65FD660D661D662E5C0D663D664D665D666D667D668D669D66AD66BD66CD66DD66ED66FD670D671D672D673D674D675D676D677D678D679D67AD67BD67CD67DD67ED680D681F6A5D682D683D684D685D686D687D688D689D68AD68BD68CD68DD68ED68FD690D691D692D693D694D695D696D697D698D699D69AD69BD69CD69DD69ED69FD6A0D740D741D742D743D744D745D746D747D748D749D74AD74BD74CD74DD74ED74FD750D751D752D753D754D755D756D757D758D759D75AD75BD75CD75DD75ED75FBEAFD760D761D762D763D764C6A9D765D766D767D768D769D76AD76BD76CD76DD76ED76FD770D771D772D773D774D775D776D777D778D779D77AD77BD77CD77DD77ED780D781D782D783D784D785D786D787D788D789D78AD78BD78CD78DD78ED78FD790D791D792D793D794D795D796D797D798DAA5BCC6B6A9B8BCC8CFBCA5DAA6DAA7CCD6C8C3DAA8C6FDD799D1B5D2E9D1B6BCC7D79ABDB2BBE4DAA9DAAAD1C8DAABD0EDB6EFC2DBD79BCBCFB7EDC9E8B7C3BEF7D6A4DAACDAADC6C0D7E7CAB6D79CD5A9CBDFD5EFDAAED6DFB4CADAB0DAAFD79DD2EBDAB1DAB2DAB3CAD4DAB4CAABDAB5DAB6B3CFD6EFDAB7BBB0B5AEDAB8DAB9B9EED1AFD2E8DABAB8C3CFEAB2EFDABBDABCD79EBDEBCEDCD3EFDABDCEF3DABED3D5BBE5DABFCBB5CBD0DAC0C7EBD6EEDAC1C5B5B6C1DAC2B7CCBFCEDAC3DAC4CBADDAC5B5F7DAC6C1C2D7BBDAC7CCB8D79FD2EAC4B1DAC8B5FDBBD1DAC9D0B3DACADACBCEBDDACCDACDDACEB2F7DAD1DACFD1E8DAD0C3D5DAD2D7A0DAD3DAD4DAD5D0BBD2A5B0F9DAD6C7ABDAD7BDF7C3A1DAD8DAD9C3FDCCB7DADADADBC0BEC6D7DADCDADDC7B4DADEDADFB9C8D840D841D842D843D844D845D846D847D848BBEDD849D84AD84BD84CB6B9F4F8D84DF4F9D84ED84FCDE3D850D851D852D853D854D855D856D857F5B9D858D859D85AD85BEBE0D85CD85DD85ED85FD860D861CFF3BBBFD862D863D864D865D866D867D868BAC0D4A5D869D86AD86BD86CD86DD86ED86FE1D9D870D871D872D873F5F4B1AAB2F2D874D875D876D877D878D879D87AF5F5D87BD87CF5F7D87DD87ED880BAD1F5F6D881C3B2D882D883D884D885D886D887D888F5F9D889D88AD88BF5F8D88CD88DD88ED88FD890D891D892D893D894D895D896D897D898D899D89AD89BD89CD89DD89ED89FD8A0D940D941D942D943D944D945D946D947D948D949D94AD94BD94CD94DD94ED94FD950D951D952D953D954D955D956D957D958D959D95AD95BD95CD95DD95ED95FD960D961D962D963D964D965D966D967D968D969D96AD96BD96CD96DD96ED96FD970D971D972D973D974D975D976D977D978D979D97AD97BD97CD97DD97ED980D981D982D983D984D985D986D987D988D989D98AD98BD98CD98DD98ED98FD990D991D992D993D994D995D996D997D998D999D99AD99BD99CD99DD99ED99FD9A0DA40DA41DA42DA43DA44DA45DA46DA47DA48DA49DA4ADA4BDA4CDA4DDA4EB1B4D5EAB8BADA4FB9B1B2C6D4F0CFCDB0DCD5CBBBF5D6CAB7B7CCB0C6B6B1E1B9BAD6FCB9E1B7A1BCFAEADAEADBCCF9B9F3EADCB4FBC3B3B7D1BAD8EADDD4F4EADEBCD6BBDFEADFC1DEC2B8D4DFD7CAEAE0EAE1EAE4EAE2EAE3C9DEB8B3B6C4EAE5CAEAC9CDB4CDDA50DA51E2D9C5E2EAE6C0B5DA52D7B8EAE7D7ACC8FCD8D3D8CDD4DEDA53D4F9C9C4D3AEB8D3B3E0DA54C9E2F4F6DA55DA56DA57BAD5DA58F4F7DA59DA5AD7DFDA5BDA5CF4F1B8B0D5D4B8CFC6F0DA5DDA5EDA5FDA60DA61DA62DA63DA64DA65B3C3DA66DA67F4F2B3ACDA68DA69DA6ADA6BD4BDC7F7DA6CDA6DDA6EDA6FDA70F4F4DA71DA72F4F3DA73DA74DA75DA76DA77DA78DA79DA7ADA7BDA7CCCCBDA7DDA7EDA80C8A4DA81DA82DA83DA84DA85DA86DA87DA88DA89DA8ADA8BDA8CDA8DF4F5DA8ED7E3C5BFF5C0DA8FDA90F5BBDA91F5C3DA92F5C2DA93D6BAF5C1DA94DA95DA96D4BEF5C4DA97F5CCDA98DA99DA9ADA9BB0CFB5F8DA9CF5C9F5CADA9DC5DCDA9EDA9FDAA0DB40F5C5F5C6DB41DB42F5C7F5CBDB43BEE0F5C8B8FADB44DB45DB46F5D0F5D3DB47DB48DB49BFE7DB4AB9F2F5BCF5CDDB4BDB4CC2B7DB4DDB4EDB4FCCF8DB50BCF9DB51F5CEF5CFF5D1B6E5F5D2DB52F5D5DB53DB54DB55DB56DB57DB58DB59F5BDDB5ADB5BDB5CF5D4D3BBDB5DB3ECDB5EDB5FCCA4DB60DB61DB62DB63F5D6DB64DB65DB66DB67DB68DB69DB6ADB6BF5D7BEE1F5D8DB6CDB6DCCDFF5DBDB6EDB6FDB70DB71DB72B2C8D7D9DB73F5D9DB74F5DAF5DCDB75F5E2DB76DB77DB78F5E0DB79DB7ADB7BF5DFF5DDDB7CDB7DF5E1DB7EDB80F5DEF5E4F5E5DB81CCE3DB82DB83E5BFB5B8F5E3F5E8CCA3DB84DB85DB86DB87DB88F5E6F5E7DB89DB8ADB8BDB8CDB8DDB8EF5BEDB8FDB90DB91DB92DB93DB94DB95DB96DB97DB98DB99DB9AB1C4DB9BDB9CF5BFDB9DDB9EB5C5B2E4DB9FF5ECF5E9DBA0B6D7DC40F5EDDC41F5EADC42DC43DC44DC45DC46F5EBDC47DC48B4DADC49D4EADC4ADC4BDC4CF5EEDC4DB3F9DC4EDC4FDC50DC51DC52DC53DC54F5EFF5F1DC55DC56DC57F5F0DC58DC59DC5ADC5BDC5CDC5DDC5EF5F2DC5FF5F3DC60DC61DC62DC63DC64DC65DC66DC67DC68DC69DC6ADC6BC9EDB9AADC6CDC6DC7FBDC6EDC6FB6E3DC70DC71DC72DC73DC74DC75DC76CCC9DC77DC78DC79DC7ADC7BDC7CDC7DDC7EDC80DC81DC82DC83DC84DC85DC86DC87DC88DC89DC8AEAA6DC8BDC8CDC8DDC8EDC8FDC90DC91DC92DC93DC94DC95DC96DC97DC98DC99DC9ADC9BDC9CDC9DDC9EDC9FDCA0DD40DD41DD42DD43DD44DD45DD46DD47DD48DD49DD4ADD4BDD4CDD4DDD4EDD4FDD50DD51DD52DD53DD54DD55DD56DD57DD58DD59DD5ADD5BDD5CDD5DDD5EDD5FDD60DD61DD62DD63DD64DD65DD66DD67DD68DD69DD6ADD6BDD6CDD6DDD6EDD6FDD70DD71DD72DD73DD74DD75DD76DD77DD78DD79DD7ADD7BDD7CDD7DDD7EDD80DD81DD82DD83DD84DD85DD86DD87DD88DD89DD8ADD8BDD8CDD8DDD8EDD8FDD90DD91DD92DD93DD94DD95DD96DD97DD98DD99DD9ADD9BDD9CDD9DDD9EDD9FDDA0DE40DE41DE42DE43DE44DE45DE46DE47DE48DE49DE4ADE4BDE4CDE4DDE4EDE4FDE50DE51DE52DE53DE54DE55DE56DE57DE58DE59DE5ADE5BDE5CDE5DDE5EDE5FDE60B3B5D4FEB9ECD0F9DE61E9EDD7AAE9EEC2D6C8EDBAE4E9EFE9F0E9F1D6E1E9F2E9F3E9F5E9F4E9F6E9F7C7E1E9F8D4D8E9F9BDCEDE62E9FAE9FBBDCFE9FCB8A8C1BEE9FDB1B2BBD4B9F5E9FEDE63EAA1EAA2EAA3B7F8BCADDE64CAE4E0CED4AFCFBDD5B7EAA4D5DEEAA5D0C1B9BCDE65B4C7B1D9DE66DE67DE68C0B1DE69DE6ADE6BDE6CB1E6B1E7DE6DB1E8DE6EDE6FDE70DE71B3BDC8E8DE72DE73DE74DE75E5C1DE76DE77B1DFDE78DE79DE7AC1C9B4EFDE7BDE7CC7A8D3D8DE7DC6F9D1B8DE7EB9FDC2F5DE80DE81DE82DE83DE84D3ADDE85D4CBBDFCDE86E5C2B7B5E5C3DE87DE88BBB9D5E2DE89BDF8D4B6CEA5C1ACB3D9DE8ADE8BCCF6DE8CE5C6E5C4E5C8DE8DE5CAE5C7B5CFC6C8DE8EB5FCE5C5DE8FCAF6DE90DE91E5C9DE92DE93DE94C3D4B1C5BCA3DE95DE96DE97D7B7DE98DE99CDCBCBCDCACACCD3E5CCE5CBC4E6DE9ADE9BD1A1D1B7E5CDDE9CE5D0DE9DCDB8D6F0E5CFB5DDDE9ECDBEDE9FE5D1B6BADEA0DF40CDA8B9E4DF41CAC5B3D1CBD9D4ECE5D2B7EADF42DF43DF44E5CEDF45DF46DF47DF48DF49DF4AE5D5B4FEE5D6DF4BDF4CDF4DDF4EDF4FE5D3E5D4DF50D2DDDF51DF52C2DFB1C6DF53D3E2DF54DF55B6DDCBECDF56E5D7DF57DF58D3F6DF59DF5ADF5BDF5CDF5DB1E9DF5EB6F4E5DAE5D8E5D9B5C0DF5FDF60DF61D2C5E5DCDF62DF63E5DEDF64DF65DF66DF67DF68DF69E5DDC7B2DF6AD2A3DF6BDF6CE5DBDF6DDF6EDF6FDF70D4E2D5DADF71DF72DF73DF74DF75E5E0D7F1DF76DF77DF78DF79DF7ADF7BDF7CE5E1DF7DB1DCD1FBDF7EE5E2E5E4DF80DF81DF82DF83E5E3DF84DF85E5E5DF86DF87DF88DF89DF8AD2D8DF8BB5CBDF8CE7DFDF8DDAF5DF8EDAF8DF8FDAF6DF90DAF7DF91DF92DF93DAFAD0CFC4C7DF94DF95B0EEDF96DF97DF98D0B0DF99DAF9DF9AD3CABAAADBA2C7F1DF9BDAFCDAFBC9DBDAFDDF9CDBA1D7DEDAFEC1DADF9DDF9EDBA5DF9FDFA0D3F4E040E041DBA7DBA4E042DBA8E043E044BDBCE045E046E047C0C9DBA3DBA6D6A3E048DBA9E049E04AE04BDBADE04CE04DE04EDBAEDBACBAC2E04FE050E051BFA4DBABE052E053E054DBAAD4C7B2BFE055E056DBAFE057B9F9E058DBB0E059E05AE05BE05CB3BBE05DE05EE05FB5A6E060E061E062E063B6BCDBB1E064E065E066B6F5E067DBB2E068E069E06AE06BE06CE06DE06EE06FE070E071E072E073E074E075E076E077E078E079E07AE07BB1C9E07CE07DE07EE080DBB4E081E082E083DBB3DBB5E084E085E086E087E088E089E08AE08BE08CE08DE08EDBB7E08FDBB6E090E091E092E093E094E095E096DBB8E097E098E099E09AE09BE09CE09DE09EE09FDBB9E0A0E140DBBAE141E142D3CFF4FAC7F5D7C3C5E4F4FCF4FDF4FBE143BEC6E144E145E146E147D0EFE148E149B7D3E14AE14BD4CDCCAAE14CE14DF5A2F5A1BAA8F4FECBD6E14EE14FE150F5A4C0D2E151B3EAE152CDAAF5A5F5A3BDB4F5A8E153F5A9BDCDC3B8BFE1CBE1F5AAE154E155E156F5A6F5A7C4F0E157E158E159E15AE15BF5ACE15CB4BCE15DD7EDE15EB4D7F5ABF5AEE15FE160F5ADF5AFD0D1E161E162E163E164E165E166E167C3D1C8A9E168E169E16AE16BE16CE16DF5B0F5B1E16EE16FE170E171E172E173F5B2E174E175F5B3F5B4F5B5E176E177E178E179F5B7F5B6E17AE17BE17CE17DF5B8E17EE180E181E182E183E184E185E186E187E188E189E18AB2C9E18BD3D4CACDE18CC0EFD6D8D2B0C1BFE18DBDF0E18EE18FE190E191E192E193E194E195E196E197B8AAE198E199E19AE19BE19CE19DE19EE19FE1A0E240E241E242E243E244E245E246E247E248E249E24AE24BE24CE24DE24EE24FE250E251E252E253E254E255E256E257E258E259E25AE25BE25CE25DE25EE25FE260E261E262E263E264E265E266E267E268E269E26AE26BE26CE26DE26EE26FE270E271E272E273E274E275E276E277E278E279E27AE27BE27CE27DE27EE280E281E282E283E284E285E286E287E288E289E28AE28BE28CE28DE28EE28FE290E291E292E293E294E295E296E297E298E299E29AE29BE29CE29DE29EE29FE2A0E340E341E342E343E344E345E346E347E348E349E34AE34BE34CE34DE34EE34FE350E351E352E353E354E355E356E357E358E359E35AE35BE35CE35DE35EE35FE360E361E362E363E364E365E366E367E368E369E36AE36BE36CE36DBCF8E36EE36FE370E371E372E373E374E375E376E377E378E379E37AE37BE37CE37DE37EE380E381E382E383E384E385E386E387F6C6E388E389E38AE38BE38CE38DE38EE38FE390E391E392E393E394E395E396E397E398E399E39AE39BE39CE39DE39EE39FE3A0E440E441E442E443E444E445F6C7E446E447E448E449E44AE44BE44CE44DE44EE44FE450E451E452E453E454E455E456E457E458E459E45AE45BE45CE45DE45EF6C8E45FE460E461E462E463E464E465E466E467E468E469E46AE46BE46CE46DE46EE46FE470E471E472E473E474E475E476E477E478E479E47AE47BE47CE47DE47EE480E481E482E483E484E485E486E487E488E489E48AE48BE48CE48DE48EE48FE490E491E492E493E494E495E496E497E498E499E49AE49BE49CE49DE49EE49FE4A0E540E541E542E543E544E545E546E547E548E549E54AE54BE54CE54DE54EE54FE550E551E552E553E554E555E556E557E558E559E55AE55BE55CE55DE55EE55FE560E561E562E563E564E565E566E567E568E569E56AE56BE56CE56DE56EE56FE570E571E572E573F6C9E574E575E576E577E578E579E57AE57BE57CE57DE57EE580E581E582E583E584E585E586E587E588E589E58AE58BE58CE58DE58EE58FE590E591E592E593E594E595E596E597E598E599E59AE59BE59CE59DE59EE59FF6CAE5A0E640E641E642E643E644E645E646E647E648E649E64AE64BE64CE64DE64EE64FE650E651E652E653E654E655E656E657E658E659E65AE65BE65CE65DE65EE65FE660E661E662F6CCE663E664E665E666E667E668E669E66AE66BE66CE66DE66EE66FE670E671E672E673E674E675E676E677E678E679E67AE67BE67CE67DE67EE680E681E682E683E684E685E686E687E688E689E68AE68BE68CE68DE68EE68FE690E691E692E693E694E695E696E697E698E699E69AE69BE69CE69DF6CBE69EE69FE6A0E740E741E742E743E744E745E746E747F7E9E748E749E74AE74BE74CE74DE74EE74FE750E751E752E753E754E755E756E757E758E759E75AE75BE75CE75DE75EE75FE760E761E762E763E764E765E766E767E768E769E76AE76BE76CE76DE76EE76FE770E771E772E773E774E775E776E777E778E779E77AE77BE77CE77DE77EE780E781E782E783E784E785E786E787E788E789E78AE78BE78CE78DE78EE78FE790E791E792E793E794E795E796E797E798E799E79AE79BE79CE79DE79EE79FE7A0E840E841E842E843E844E845E846E847E848E849E84AE84BE84CE84DE84EF6CDE84FE850E851E852E853E854E855E856E857E858E859E85AE85BE85CE85DE85EE85FE860E861E862E863E864E865E866E867E868E869E86AE86BE86CE86DE86EE86FE870E871E872E873E874E875E876E877E878E879E87AF6CEE87BE87CE87DE87EE880E881E882E883E884E885E886E887E888E889E88AE88BE88CE88DE88EE88FE890E891E892E893E894EEC4EEC5EEC6D5EBB6A4EEC8EEC7EEC9EECAC7A5EECBEECCE895B7B0B5F6EECDEECFE896EECEE897B8C6EED0EED1EED2B6DBB3AED6D3C4C6B1B5B8D6EED3EED4D4BFC7D5BEFBCED9B9B3EED6EED5EED8EED7C5A5EED9EEDAC7AEEEDBC7AFEEDCB2A7EEDDEEDEEEDFEEE0EEE1D7EAEEE2EEE3BCD8EEE4D3CBCCFAB2ACC1E5EEE5C7A6C3ADE898EEE6EEE7EEE8EEE9EEEAEEEBEEECE899EEEDEEEEEEEFE89AE89BEEF0EEF1EEF2EEF4EEF3E89CEEF5CDADC2C1EEF6EEF7EEF8D5A1EEF9CFB3EEFAEEFBE89DEEFCEEFDEFA1EEFEEFA2B8F5C3FAEFA3EFA4BDC2D2BFB2F9EFA5EFA6EFA7D2F8EFA8D6FDEFA9C6CCE89EEFAAEFABC1B4EFACCFFACBF8EFAEEFADB3FAB9F8EFAFEFB0D0E2EFB1EFB2B7E6D0BFEFB3EFB4EFB5C8F1CCE0EFB6EFB7EFB8EFB9EFBAD5E0EFBBB4EDC3AAEFBCE89FEFBDEFBEEFBFE8A0CEFDEFC0C2E0B4B8D7B6BDF5E940CFC7EFC3EFC1EFC2EFC4B6A7BCFCBEE2C3CCEFC5EFC6E941EFC7EFCFEFC8EFC9EFCAC7C2EFF1B6CDEFCBE942EFCCEFCDB6C6C3BEEFCEE943EFD0EFD1EFD2D5F2E944EFD3C4F7E945EFD4C4F8EFD5EFD6B8E4B0F7EFD7EFD8EFD9E946EFDAEFDBEFDCEFDDE947EFDEBEB5EFE1EFDFEFE0E948EFE2EFE3C1CDEFE4EFE5EFE6EFE7EFE8EFE9EFEAEFEBEFECC0D8E949EFEDC1ADEFEEEFEFEFF0E94AE94BCFE2E94CE94DE94EE94FE950E951E952E953B3A4E954E955E956E957E958E959E95AE95BE95CE95DE95EE95FE960E961E962E963E964E965E966E967E968E969E96AE96BE96CE96DE96EE96FE970E971E972E973E974E975E976E977E978E979E97AE97BE97CE97DE97EE980E981E982E983E984E985E986E987E988E989E98AE98BE98CE98DE98EE98FE990E991E992E993E994E995E996E997E998E999E99AE99BE99CE99DE99EE99FE9A0EA40EA41EA42EA43EA44EA45EA46EA47EA48EA49EA4AEA4BEA4CEA4DEA4EEA4FEA50EA51EA52EA53EA54EA55EA56EA57EA58EA59EA5AEA5BC3C5E3C5C9C1E3C6EA5CB1D5CECAB4B3C8F2E3C7CFD0E3C8BCE4E3C9E3CAC3C6D5A2C4D6B9EBCEC5E3CBC3F6E3CCEA5DB7A7B8F3BAD2E3CDE3CED4C4E3CFEA5EE3D0D1CBE3D1E3D2E3D3E3D4D1D6E3D5B2FBC0BBE3D6EA5FC0ABE3D7E3D8E3D9EA60E3DAE3DBEA61B8B7DAE2EA62B6D3EA63DAE4DAE3EA64EA65EA66EA67EA68EA69EA6ADAE6EA6BEA6CEA6DC8EEEA6EEA6FDAE5B7C0D1F4D2F5D5F3BDD7EA70EA71EA72EA73D7E8DAE8DAE7EA74B0A2CDD3EA75DAE9EA76B8BDBCCAC2BDC2A4B3C2DAEAEA77C2AAC4B0BDB5EA78EA79CFDEEA7AEA7BEA7CDAEBC9C2EA7DEA7EEA80EA81EA82B1DDEA83EA84EA85DAECEA86B6B8D4BAEA87B3FDEA88EA89DAEDD4C9CFD5C5E3EA8ADAEEEA8BEA8CEA8DEA8EEA8FDAEFEA90DAF0C1EACCD5CFDDEA91EA92EA93EA94EA95EA96EA97EA98EA99EA9AEA9BEA9CEA9DD3E7C2A1EA9EDAF1EA9FEAA0CBE5EB40DAF2EB41CBE6D2FEEB42EB43EB44B8F4EB45EB46DAF3B0AFCFB6EB47EB48D5CFEB49EB4AEB4BEB4CEB4DEB4EEB4FEB50EB51EB52CBEDEB53EB54EB55EB56EB57EB58EB59EB5ADAF4EB5BEB5CE3C4EB5DEB5EC1A5EB5FEB60F6BFEB61EB62F6C0F6C1C4D1EB63C8B8D1E3EB64EB65D0DBD1C5BCAFB9CDEB66EFF4EB67EB68B4C6D3BAF6C2B3FBEB69EB6AF6C3EB6BEB6CB5F1EB6DEB6EEB6FEB70EB71EB72EB73EB74EB75EB76F6C5EB77EB78EB79EB7AEB7BEB7CEB7DD3EAF6A7D1A9EB7EEB80EB81EB82F6A9EB83EB84EB85F6A8EB86EB87C1E3C0D7EB88B1A2EB89EB8AEB8BEB8CCEEDEB8DD0E8F6ABEB8EEB8FCFF6EB90F6AAD5F0F6ACC3B9EB91EB92EB93BBF4F6AEF6ADEB94EB95EB96C4DEEB97EB98C1D8EB99EB9AEB9BEB9CEB9DCBAAEB9ECFBCEB9FEBA0EC40EC41EC42EC43EC44EC45EC46EC47EC48F6AFEC49EC4AF6B0EC4BEC4CF6B1EC4DC2B6EC4EEC4FEC50EC51EC52B0D4C5F9EC53EC54EC55EC56F6B2EC57EC58EC59EC5AEC5BEC5CEC5DEC5EEC5FEC60EC61EC62EC63EC64EC65EC66EC67EC68EC69C7E0F6A6EC6AEC6BBEB8EC6CEC6DBEB2EC6EB5E5EC6FEC70B7C7EC71BFBFC3D2C3E6EC72EC73D8CCEC74EC75EC76B8EFEC77EC78EC79EC7AEC7BEC7CEC7DEC7EEC80BDF9D1A5EC81B0D0EC82EC83EC84EC85EC86F7B0EC87EC88EC89EC8AEC8BEC8CEC8DEC8EF7B1EC8FEC90EC91EC92EC93D0ACEC94B0B0EC95EC96EC97F7B2F7B3EC98F7B4EC99EC9AEC9BC7CAEC9CEC9DEC9EEC9FECA0ED40ED41BECFED42ED43F7B7ED44ED45ED46ED47ED48ED49ED4AF7B6ED4BB1DEED4CF7B5ED4DED4EF7B8ED4FF7B9ED50ED51ED52ED53ED54ED55ED56ED57ED58ED59ED5AED5BED5CED5DED5EED5FED60ED61ED62ED63ED64ED65ED66ED67ED68ED69ED6AED6BED6CED6DED6EED6FED70ED71ED72ED73ED74ED75ED76ED77ED78ED79ED7AED7BED7CED7DED7EED80ED81CEA4C8CDED82BAABE8B8E8B9E8BABEC2ED83ED84ED85ED86ED87D2F4ED88D4CFC9D8ED89ED8AED8BED8CED8DED8EED8FED90ED91ED92ED93ED94ED95ED96ED97ED98ED99ED9AED9BED9CED9DED9EED9FEDA0EE40EE41EE42EE43EE44EE45EE46EE47EE48EE49EE4AEE4BEE4CEE4DEE4EEE4FEE50EE51EE52EE53EE54EE55EE56EE57EE58EE59EE5AEE5BEE5CEE5DEE5EEE5FEE60EE61EE62EE63EE64EE65EE66EE67EE68EE69EE6AEE6BEE6CEE6DEE6EEE6FEE70EE71EE72EE73EE74EE75EE76EE77EE78EE79EE7AEE7BEE7CEE7DEE7EEE80EE81EE82EE83EE84EE85EE86EE87EE88EE89EE8AEE8BEE8CEE8DEE8EEE8FEE90EE91EE92EE93EE94EE95EE96EE97EE98EE99EE9AEE9BEE9CEE9DEE9EEE9FEEA0EF40EF41EF42EF43EF44EF45D2B3B6A5C7EAF1FCCFEECBB3D0EBE7EFCDE7B9CBB6D9F1FDB0E4CBCCF1FED4A4C2ADC1ECC6C4BEB1F2A1BCD5EF46F2A2F2A3EF47F2A4D2C3C6B5EF48CDC7F2A5EF49D3B1BFC5CCE2EF4AF2A6F2A7D1D5B6EEF2A8F2A9B5DFF2AAF2ABEF4BB2FCF2ACF2ADC8A7EF4CEF4DEF4EEF4FEF50EF51EF52EF53EF54EF55EF56EF57EF58EF59EF5AEF5BEF5CEF5DEF5EEF5FEF60EF61EF62EF63EF64EF65EF66EF67EF68EF69EF6AEF6BEF6CEF6DEF6EEF6FEF70EF71B7E7EF72EF73ECA9ECAAECABEF74ECACEF75EF76C6AEECADECAEEF77EF78EF79B7C9CAB3EF7AEF7BEF7CEF7DEF7EEF80EF81E2B8F7CFEF82EF83EF84EF85EF86EF87EF88EF89EF8AEF8BEF8CEF8DEF8EEF8FEF90EF91EF92EF93EF94EF95EF96EF97EF98EF99EF9AEF9BEF9CEF9DEF9EEF9FEFA0F040F041F042F043F044F7D0F045F046B2CDF047F048F049F04AF04BF04CF04DF04EF04FF050F051F052F053F054F055F056F057F058F059F05AF05BF05CF05DF05EF05FF060F061F062F063F7D1F064F065F066F067F068F069F06AF06BF06CF06DF06EF06FF070F071F072F073F074F075F076F077F078F079F07AF07BF07CF07DF07EF080F081F082F083F084F085F086F087F088F089F7D3F7D2F08AF08BF08CF08DF08EF08FF090F091F092F093F094F095F096E2BBF097BCA2F098E2BCE2BDE2BEE2BFE2C0E2C1B7B9D2FBBDA4CACEB1A5CBC7F099E2C2B6FCC8C4E2C3F09AF09BBDC8F09CB1FDE2C4F09DB6F6E2C5C4D9F09EF09FE2C6CFDAB9DDE2C7C0A1F0A0E2C8B2F6F140E2C9F141C1F3E2CAE2CBC2F8E2CCE2CDE2CECAD7D8B8D9E5CFE3F142F143F144F145F146F147F148F149F14AF14BF14CF0A5F14DF14EDCB0F14FF150F151F152F153F154F155F156F157F158F159F15AF15BF15CF15DF15EF15FF160F161F162F163F164F165F166F167F168F169F16AF16BF16CF16DF16EF16FF170F171F172F173F174F175F176F177F178F179F17AF17BF17CF17DF17EF180F181F182F183F184F185F186F187F188F189F18AF18BF18CF18DF18EF18FF190F191F192F193F194F195F196F197F198F199F19AF19BF19CF19DF19EF19FF1A0F240F241F242F243F244F245F246F247F248F249F24AF24BF24CF24DF24EF24FF250F251F252F253F254F255F256F257F258F259F25AF25BF25CF25DF25EF25FF260F261F262F263F264F265F266F267F268F269F26AF26BF26CF26DF26EF26FF270F271F272F273F274F275F276F277F278F279F27AF27BF27CF27DF27EF280F281F282F283F284F285F286F287F288F289F28AF28BF28CF28DF28EF28FF290F291F292F293F294F295F296F297F298F299F29AF29BF29CF29DF29EF29FF2A0F340F341F342F343F344F345F346F347F348F349F34AF34BF34CF34DF34EF34FF350F351C2EDD4A6CDD4D1B1B3DBC7FDF352B2B5C2BFE6E0CABBE6E1E6E2BED4E6E3D7A4CDD5E6E5BCDDE6E4E6E6E6E7C2EEF353BDBEE6E8C2E6BAA7E6E9F354E6EAB3D2D1E9F355F356BFA5E6EBC6EFE6ECE6EDF357F358E6EEC6ADE6EFF359C9A7E6F0E6F1E6F2E5B9E6F3E6F4C2E2E6F5E6F6D6E8E6F7F35AE6F8B9C7F35BF35CF35DF35EF35FF360F361F7BBF7BAF362F363F364F365F7BEF7BCBAA1F366F7BFF367F7C0F368F369F36AF7C2F7C1F7C4F36BF36CF7C3F36DF36EF36FF370F371F7C5F7C6F372F373F374F375F7C7F376CBE8F377F378F379F37AB8DFF37BF37CF37DF37EF380F381F7D4F382F7D5F383F384F385F386F7D6F387F388F389F38AF7D8F38BF7DAF38CF7D7F38DF38EF38FF390F391F392F393F394F395F7DBF396F7D9F397F398F399F39AF39BF39CF39DD7D7F39EF39FF3A0F440F7DCF441F442F443F444F445F446F7DDF447F448F449F7DEF44AF44BF44CF44DF44EF44FF450F451F452F453F454F7DFF455F456F457F7E0F458F459F45AF45BF45CF45DF45EF45FF460F461F462DBCBF463F464D8AAF465F466F467F468F469F46AF46BF46CE5F7B9EDF46DF46EF46FF470BFFDBBEAF7C9C6C7F7C8F471F7CAF7CCF7CBF472F473F474F7CDF475CEBAF476F7CEF477F478C4A7F479F47AF47BF47CF47DF47EF480F481F482F483F484F485F486F487F488F489F48AF48BF48CF48DF48EF48FF490F491F492F493F494F495F496F497F498F499F49AF49BF49CF49DF49EF49FF4A0F540F541F542F543F544F545F546F547F548F549F54AF54BF54CF54DF54EF54FF550F551F552F553F554F555F556F557F558F559F55AF55BF55CF55DF55EF55FF560F561F562F563F564F565F566F567F568F569F56AF56BF56CF56DF56EF56FF570F571F572F573F574F575F576F577F578F579F57AF57BF57CF57DF57EF580F581F582F583F584F585F586F587F588F589F58AF58BF58CF58DF58EF58FF590F591F592F593F594F595F596F597F598F599F59AF59BF59CF59DF59EF59FF5A0F640F641F642F643F644F645F646F647F648F649F64AF64BF64CF64DF64EF64FF650F651F652F653F654F655F656F657F658F659F65AF65BF65CF65DF65EF65FF660F661F662F663F664F665F666F667F668F669F66AF66BF66CF66DF66EF66FF670F671F672F673F674F675F676F677F678F679F67AF67BF67CF67DF67EF680F681F682F683F684F685F686F687F688F689F68AF68BF68CF68DF68EF68FF690F691F692F693F694F695F696F697F698F699F69AF69BF69CF69DF69EF69FF6A0F740F741F742F743F744F745F746F747F748F749F74AF74BF74CF74DF74EF74FF750F751F752F753F754F755F756F757F758F759F75AF75BF75CF75DF75EF75FF760F761F762F763F764F765F766F767F768F769F76AF76BF76CF76DF76EF76FF770F771F772F773F774F775F776F777F778F779F77AF77BF77CF77DF77EF780D3E3F781F782F6CFF783C2B3F6D0F784F785F6D1F6D2F6D3F6D4F786F787F6D6F788B1ABF6D7F789F6D8F6D9F6DAF78AF6DBF6DCF78BF78CF78DF78EF6DDF6DECFCAF78FF6DFF6E0F6E1F6E2F6E3F6E4C0F0F6E5F6E6F6E7F6E8F6E9F790F6EAF791F6EBF6ECF792F6EDF6EEF6EFF6F0F6F1F6F2F6F3F6F4BEA8F793F6F5F6F6F6F7F6F8F794F795F796F797F798C8FAF6F9F6FAF6FBF6FCF799F79AF6FDF6FEF7A1F7A2F7A3F7A4F7A5F79BF79CF7A6F7A7F7A8B1EEF7A9F7AAF7ABF79DF79EF7ACF7ADC1DBF7AEF79FF7A0F7AFF840F841F842F843F844F845F846F847F848F849F84AF84BF84CF84DF84EF84FF850F851F852F853F854F855F856F857F858F859F85AF85BF85CF85DF85EF85FF860F861F862F863F864F865F866F867F868F869F86AF86BF86CF86DF86EF86FF870F871F872F873F874F875F876F877F878F879F87AF87BF87CF87DF87EF880F881F882F883F884F885F886F887F888F889F88AF88BF88CF88DF88EF88FF890F891F892F893F894F895F896F897F898F899F89AF89BF89CF89DF89EF89FF8A0F940F941F942F943F944F945F946F947F948F949F94AF94BF94CF94DF94EF94FF950F951F952F953F954F955F956F957F958F959F95AF95BF95CF95DF95EF95FF960F961F962F963F964F965F966F967F968F969F96AF96BF96CF96DF96EF96FF970F971F972F973F974F975F976F977F978F979F97AF97BF97CF97DF97EF980F981F982F983F984F985F986F987F988F989F98AF98BF98CF98DF98EF98FF990F991F992F993F994F995F996F997F998F999F99AF99BF99CF99DF99EF99FF9A0FA40FA41FA42FA43FA44FA45FA46FA47FA48FA49FA4AFA4BFA4CFA4DFA4EFA4FFA50FA51FA52FA53FA54FA55FA56FA57FA58FA59FA5AFA5BFA5CFA5DFA5EFA5FFA60FA61FA62FA63FA64FA65FA66FA67FA68FA69FA6AFA6BFA6CFA6DFA6EFA6FFA70FA71FA72FA73FA74FA75FA76FA77FA78FA79FA7AFA7BFA7CFA7DFA7EFA80FA81FA82FA83FA84FA85FA86FA87FA88FA89FA8AFA8BFA8CFA8DFA8EFA8FFA90FA91FA92FA93FA94FA95FA96FA97FA98FA99FA9AFA9BFA9CFA9DFA9EFA9FFAA0FB40FB41FB42FB43FB44FB45FB46FB47FB48FB49FB4AFB4BFB4CFB4DFB4EFB4FFB50FB51FB52FB53FB54FB55FB56FB57FB58FB59FB5AFB5BC4F1F0AFBCA6F0B0C3F9FB5CC5B8D1BBFB5DF0B1F0B2F0B3F0B4F0B5D1BCFB5ED1ECFB5FF0B7F0B6D4A7FB60CDD2F0B8F0BAF0B9F0BBF0BCFB61FB62B8EBF0BDBAE8FB63F0BEF0BFBEE9F0C0B6ECF0C1F0C2F0C3F0C4C8B5F0C5F0C6FB64F0C7C5F4FB65F0C8FB66FB67FB68F0C9FB69F0CAF7BDFB6AF0CBF0CCF0CDFB6BF0CEFB6CFB6DFB6EFB6FF0CFBAD7FB70F0D0F0D1F0D2F0D3F0D4F0D5F0D6F0D8FB71FB72D3A5F0D7FB73F0D9FB74FB75FB76FB77FB78FB79FB7AFB7BFB7CFB7DF5BAC2B9FB7EFB80F7E4FB81FB82FB83FB84F7E5F7E6FB85FB86F7E7FB87FB88FB89FB8AFB8BFB8CF7E8C2B4FB8DFB8EFB8FFB90FB91FB92FB93FB94FB95F7EAFB96F7EBFB97FB98FB99FB9AFB9BFB9CC2F3FB9DFB9EFB9FFBA0FC40FC41FC42FC43FC44FC45FC46FC47FC48F4F0FC49FC4AFC4BF4EFFC4CFC4DC2E9FC4EF7E1F7E2FC4FFC50FC51FC52FC53BBC6FC54FC55FC56FC57D9E4FC58FC59FC5ACAF2C0E8F0A4FC5BBADAFC5CFC5DC7ADFC5EFC5FFC60C4ACFC61FC62F7ECF7EDF7EEFC63F7F0F7EFFC64F7F1FC65FC66F7F4FC67F7F3FC68F7F2F7F5FC69FC6AFC6BFC6CF7F6FC6DFC6EFC6FFC70FC71FC72FC73FC74FC75EDE9FC76EDEAEDEBFC77F6BCFC78FC79FC7AFC7BFC7CFC7DFC7EFC80FC81FC82FC83FC84F6BDFC85F6BEB6A6FC86D8BEFC87FC88B9C4FC89FC8AFC8BD8BBFC8CDCB1FC8DFC8EFC8FFC90FC91FC92CAF3FC93F7F7FC94FC95FC96FC97FC98FC99FC9AFC9BFC9CF7F8FC9DFC9EF7F9FC9FFCA0FD40FD41FD42FD43FD44F7FBFD45F7FAFD46B1C7FD47F7FCF7FDFD48FD49FD4AFD4BFD4CF7FEFD4DFD4EFD4FFD50FD51FD52FD53FD54FD55FD56FD57C6EBECB4FD58FD59FD5AFD5BFD5CFD5DFD5EFD5FFD60FD61FD62FD63FD64FD65FD66FD67FD68FD69FD6AFD6BFD6CFD6DFD6EFD6FFD70FD71FD72FD73FD74FD75FD76FD77FD78FD79FD7AFD7BFD7CFD7DFD7EFD80FD81FD82FD83FD84FD85B3DDF6B3FD86FD87F6B4C1E4F6B5F6B6F6B7F6B8F6B9F6BAC8A3F6BBFD88FD89FD8AFD8BFD8CFD8DFD8EFD8FFD90FD91FD92FD93C1FAB9A8EDE8FD94FD95FD96B9EAD9DFFD97FD98FD99FD9AFD9';\r\n\r\n            for (var i = 0; i < str.length; i++) {\r\n                var c = str.charAt(i),\r\n                    code = str.charCodeAt(i);\r\n                if (c == \" \") strOut += \"+\";\r\n                else if (code >= 19968 && code <= 40869) {\r\n                    var index = code - 19968;\r\n                    strOut += \"%\" + z.substr(index * 4, 2) + \"%\" + z.substr(index * 4 + 2, 2);\r\n                } else {\r\n                    strOut += \"%\" + str.charCodeAt(i).toString(16);\r\n                }\r\n            }\r\n            return strOut;\r\n        },\r\n        /* 改变图片大小 */\r\n        scale: function (img, w, h) {\r\n            var ow = img.width,\r\n                oh = img.height;\r\n\r\n            if (ow >= oh) {\r\n                img.width = w * ow / oh;\r\n                img.height = h;\r\n                img.style.marginLeft = '-' + parseInt((img.width - w) / 2) + 'px';\r\n            } else {\r\n                img.width = w;\r\n                img.height = h * oh / ow;\r\n                img.style.marginTop = '-' + parseInt((img.height - h) / 2) + 'px';\r\n            }\r\n        },\r\n        getImageData: function(){\r\n            var _this = this,\r\n                key = $G('searchTxt').value,\r\n                type = $G('searchType').value,\r\n                keepOriginName = editor.options.keepOriginName ? \"1\" : \"0\",\r\n                url = \"http://image.baidu.com/i?ct=201326592&cl=2&lm=-1&st=-1&tn=baiduimagejson&istype=2&rn=32&fm=index&pv=&word=\" + _this.encodeToGb2312(key) + type + \"&keeporiginname=\" + keepOriginName + \"&\" + +new Date;\r\n\r\n            $G('searchListUl').innerHTML = lang.searchLoading;\r\n            ajax.request(url, {\r\n                'dataType': 'jsonp',\r\n                'charset': 'GB18030',\r\n                'onsuccess':function(json){\r\n                    var list = [];\r\n                    if(json && json.data) {\r\n                        for(var i = 0; i < json.data.length; i++) {\r\n                            if(json.data[i].objURL) {\r\n                                list.push({\r\n                                    title: json.data[i].fromPageTitleEnc,\r\n                                    src: json.data[i].objURL,\r\n                                    url: json.data[i].fromURL\r\n                                });\r\n                            }\r\n                        }\r\n                    }\r\n                    _this.setList(list);\r\n                },\r\n                'onerror':function(){\r\n                    $G('searchListUl').innerHTML = lang.searchRetry;\r\n                }\r\n            });\r\n        },\r\n        /* 添加图片到列表界面上 */\r\n        setList: function (list) {\r\n            var i, item, p, img, link, _this = this,\r\n                listUl = $G('searchListUl');\r\n\r\n            listUl.innerHTML = '';\r\n            if(list.length) {\r\n                for (i = 0; i < list.length; i++) {\r\n                    item = document.createElement('li');\r\n                    p = document.createElement('p');\r\n                    img = document.createElement('img');\r\n                    link = document.createElement('a');\r\n\r\n                    img.onload = function () {\r\n                        _this.scale(this, 113, 113);\r\n                    };\r\n                    img.width = 113;\r\n                    img.setAttribute('src', list[i].src);\r\n\r\n                    link.href = list[i].url;\r\n                    link.target = '_blank';\r\n                    link.title = list[i].title;\r\n                    link.innerHTML = list[i].title;\r\n\r\n                    p.appendChild(img);\r\n                    item.appendChild(p);\r\n                    item.appendChild(link);\r\n                    listUl.appendChild(item);\r\n                }\r\n            } else {\r\n                listUl.innerHTML = lang.searchRetry;\r\n            }\r\n        },\r\n        getInsertList: function () {\r\n            var child,\r\n                src,\r\n                align = getAlign(),\r\n                list = [],\r\n                items = $G('searchListUl').children;\r\n            for(var i = 0; i < items.length; i++) {\r\n                child = items[i].firstChild && items[i].firstChild.firstChild;\r\n                if(child.tagName && child.tagName.toLowerCase() == 'img' && domUtils.hasClass(items[i], 'selected')) {\r\n                    src = child.src;\r\n                    list.push({\r\n                        src: src,\r\n                        _src: src,\r\n                        alt: src.substr(src.lastIndexOf('/') + 1),\r\n                        floatStyle: align\r\n                    });\r\n                }\r\n            }\r\n            return list;\r\n        }\r\n    };\r\n\r\n})();\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/insertframe/insertframe.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\r\n    <title></title>\r\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n    <style type=\"text/css\">\r\n        .warp {width: 320px;height: 153px;margin-left:5px;padding: 20px 0 0 15px;position: relative;}\r\n        #url {width: 290px; margin-bottom: 2px; margin-left: -6px; margin-left: -2px\\9;*margin-left:0;_margin-left:0; }\r\n        .format span{display: inline-block; width: 58px;text-align: center; zoom:1;}\r\n        table td{padding:5px 0;}\r\n        #align{width: 65px;height: 23px;line-height: 22px;}\r\n    </style>\r\n</head>\r\n<body>\r\n<div class=\"warp\">\r\n        <table width=\"300\" cellpadding=\"0\" cellspacing=\"0\">\r\n            <tr>\r\n                <td colspan=\"2\" class=\"format\">\r\n                    <span><var id=\"lang_input_address\"></var></span>\r\n                    <input style=\"width:200px\" id=\"url\" type=\"text\" value=\"\"/>\r\n                </td>\r\n            </tr>\r\n            <tr>\r\n                <td colspan=\"2\" class=\"format\"><span><var id=\"lang_input_width\"></var></span><input style=\"width:200px\" type=\"text\" id=\"width\"/> px</td>\r\n\r\n            </tr>\r\n            <tr>\r\n                <td colspan=\"2\" class=\"format\"><span><var id=\"lang_input_height\"></var></span><input style=\"width:200px\" type=\"text\" id=\"height\"/> px</td>\r\n            </tr>\r\n            <tr>\r\n                <td><span><var id=\"lang_input_isScroll\"></var></span><input type=\"checkbox\" id=\"scroll\"/> </td>\r\n                <td><span><var id=\"lang_input_frameborder\"></var></span><input type=\"checkbox\" id=\"frameborder\"/> </td>\r\n            </tr>\r\n\r\n            <tr>\r\n                <td colspan=\"2\"><span><var id=\"lang_input_alignMode\"></var></span>\r\n                    <select id=\"align\">\r\n                        <option value=\"\"></option>\r\n                        <option value=\"left\"></option>\r\n                        <option value=\"right\"></option>\r\n                    </select>\r\n                </td>\r\n            </tr>\r\n        </table>\r\n</div>\r\n<script type=\"text/javascript\">\r\n    var iframe = editor._iframe;\r\n    if(iframe){\r\n        $G(\"url\").value = iframe.getAttribute(\"src\")||\"\";\r\n        $G(\"width\").value = iframe.getAttribute(\"width\")||iframe.style.width.replace(\"px\",\"\")||\"\";\r\n        $G(\"height\").value = iframe.getAttribute(\"height\") || iframe.style.height.replace(\"px\",\"\") ||\"\";\r\n        $G(\"scroll\").checked = (iframe.getAttribute(\"scrolling\") == \"yes\") ? true : false;\r\n        $G(\"frameborder\").checked = (iframe.getAttribute(\"frameborder\") == \"1\") ? true : false;\r\n        $G(\"align\").value = iframe.align ? iframe.align : \"\";\r\n    }\r\n    function queding(){\r\n        var  url = $G(\"url\").value.replace(/^\\s*|\\s*$/ig,\"\"),\r\n                width = $G(\"width\").value,\r\n                height = $G(\"height\").value,\r\n                scroll = $G(\"scroll\"),\r\n                frameborder = $G(\"frameborder\"),\r\n                float = $G(\"align\").value,\r\n                newIframe = editor.document.createElement(\"iframe\"),\r\n                div;\r\n        if(!url){\r\n            alert(lang.enterAddress);\r\n            return false;\r\n        }\r\n        newIframe.setAttribute(\"src\",/http:\\/\\/|https:\\/\\//ig.test(url) ? url : \"http://\"+url);\r\n        /^[1-9]+[.]?\\d*$/g.test( width ) ? newIframe.setAttribute(\"width\",width) : \"\";\r\n        /^[1-9]+[.]?\\d*$/g.test( height ) ? newIframe.setAttribute(\"height\",height) : \"\";\r\n        scroll.checked ?  newIframe.setAttribute(\"scrolling\",\"yes\") : newIframe.setAttribute(\"scrolling\",\"no\");\r\n        frameborder.checked ?  newIframe.setAttribute(\"frameborder\",\"1\",0) : newIframe.setAttribute(\"frameborder\",\"0\",0);\r\n        float ? newIframe.setAttribute(\"align\",float) :  newIframe.setAttribute(\"align\",\"\");\r\n        if(iframe){\r\n            iframe.parentNode.insertBefore(newIframe,iframe);\r\n            domUtils.remove(iframe);\r\n        }else{\r\n            div = editor.document.createElement(\"div\");\r\n            div.appendChild(newIframe);\r\n            editor.execCommand(\"inserthtml\",div.innerHTML);\r\n        }\r\n        editor._iframe = null;\r\n        dialog.close();\r\n    }\r\n    dialog.onok = queding;\r\n    $G(\"url\").onkeydown = function(evt){\r\n        evt = evt || event;\r\n        if(evt.keyCode == 13){\r\n            queding();\r\n        }\r\n    };\r\n    $focus($G( \"url\" ));\r\n\r\n</script>\r\n</body>\r\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/internal.js",
    "content": "(function () {\r\n    /* eslint-disable */\r\n    if (window.frameElement.id) {\r\n        let parent = window.parent,\r\n\r\n            dialog = parent.$EDITORUI[window.frameElement.id.replace(/_iframe$/, '')],\r\n\r\n            editor = dialog.editor,\r\n\r\n            UE = parent.UE,\r\n\r\n            domUtils = UE.dom.domUtils,\r\n\r\n            utils = UE.utils,\r\n\r\n            browser = UE.browser,\r\n            /* eslint-disable */\r\n            ajax = UE.ajax,\r\n\r\n            $G = function (id) {\r\n                return document.getElementById(id)\r\n            },\r\n            $focus = function (node) {\r\n                setTimeout(function () {\r\n                    if (browser.ie) {\r\n                        var r = node.createTextRange();\r\n                        r.collapse(false);\r\n                        r.select();\r\n                    } else {\r\n                        node.focus()\r\n                    }\r\n                }, 0)\r\n            };\r\n        window.nowEditor = {editor: editor, dialog: dialog};\r\n        utils.loadFile(document, {\r\n            href: editor.options.themePath + editor.options.theme + '/dialogbase.css?cache=' + Math.random(),\r\n            tag: 'link',\r\n            type: 'text/css',\r\n            rel: 'stylesheet'\r\n        });\r\n        var lang = editor.getLang(dialog.className.split('-')[2]);\r\n        if (lang) {\r\n            domUtils.on(window, 'load', function () {\r\n                var langImgPath = editor.options.langPath + editor.options.lang + '/images/';\r\n                // 针对静态资源\r\n                for (var i in lang['static']) {\r\n                    var dom = $G(i);\r\n                    if (!dom) continue;\r\n                    let tagName = dom.tagName,\r\n                        content = lang['static'][i];\r\n                    if (content.src) {\r\n                        // clone\r\n                        content = utils.extend({}, content, false);\r\n                        content.src = langImgPath + content.src;\r\n                    }\r\n                    if (content.style) {\r\n                        content = utils.extend({}, content, false);\r\n                        content.style = content.style.replace(/url\\s*\\(/g, 'url(' + langImgPath)\r\n                    }\r\n                    switch (tagName.toLowerCase()) {\r\n                        case 'var':\r\n                            dom.parentNode.replaceChild(document.createTextNode(content), dom);\r\n                            break;\r\n                        case 'select':\r\n                            var ops = dom.options;\r\n                            for (var j = 0, oj; oj = ops[j];) {\r\n                                oj.innerHTML = content.options[j++];\r\n                            }\r\n                            for (var p in content) {\r\n                                p != 'options' && dom.setAttribute(p, content[p]);\r\n                            }\r\n                            break;\r\n                        default :\r\n                            domUtils.setAttributes(dom, content);\r\n                    }\r\n                }\r\n            });\r\n        }\r\n    }\r\n})();\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/link/link.html",
    "content": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\r\n        \"http://www.w3.org/TR/html4/loose.dtd\">\r\n<html>\r\n<head>\r\n    <title></title>\r\n    <meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"/>\r\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n    <style type=\"text/css\">\r\n        *{margin:0;padding:0;color: #838383;}\r\n        table{font-size: 12px;margin: 10px;line-height: 30px}\r\n        .txt{width:300px;height:21px;line-height:21px;border:1px solid #d7d7d7;}\r\n    </style>\r\n</head>\r\n<body>\r\n    <table>\r\n        <tr>\r\n            <td><label for=\"text\"> <var id=\"lang_input_text\"></var></label></td>\r\n            <td><input class=\"txt\" id=\"text\" type=\"text\" disabled=\"true\"/></td>\r\n        </tr>\r\n        <tr>\r\n            <td><label for=\"href\"> <var id=\"lang_input_url\"></var></label></td>\r\n            <td><input class=\"txt\" id=\"href\" type=\"text\" /></td>\r\n        </tr>\r\n        <tr>\r\n            <td><label for=\"title\"> <var id=\"lang_input_title\"></var></label></td>\r\n            <td><input class=\"txt\" id=\"title\" type=\"text\"/></td>\r\n        </tr>\r\n        <tr>\r\n             <td colspan=\"2\">\r\n                 <label for=\"target\"><var id=\"lang_input_target\"></var></label>\r\n                 <input id=\"target\" type=\"checkbox\"/>\r\n             </td>\r\n        </tr>\r\n        <tr>\r\n            <td colspan=\"2\" id=\"msg\"></td>\r\n        </tr>\r\n    </table>\r\n<script type=\"text/javascript\">\r\n    var range = editor.selection.getRange(),\r\n        link = range.collapsed ? editor.queryCommandValue( \"link\" ) : editor.selection.getStart(),\r\n        url,\r\n        text = $G('text'),\r\n        rangeLink = domUtils.findParentByTagName(range.getCommonAncestor(),'a',true),\r\n        orgText;\r\n    link = domUtils.findParentByTagName( link, \"a\", true );\r\n    if(link){\r\n        url = utils.html(link.getAttribute( '_href' ) || link.getAttribute( 'href', 2 ));\r\n\r\n        if(rangeLink === link && !link.getElementsByTagName('img').length){\r\n            text.removeAttribute('disabled');\r\n            orgText = text.value = link[browser.ie ? 'innerText':'textContent'];\r\n        }else{\r\n            text.setAttribute('disabled','true');\r\n            text.value = lang.validLink;\r\n        }\r\n\r\n    }else{\r\n        if(range.collapsed){\r\n            text.removeAttribute('disabled');\r\n            text.value = '';\r\n        }else{\r\n            text.setAttribute('disabled','true');\r\n            text.value = lang.validLink;\r\n        }\r\n\r\n    }\r\n    $G(\"title\").value = url ? link.title : \"\";\r\n    $G(\"href\").value = url ? url: '';\r\n    $G(\"target\").checked = url && link.target == \"_blank\" ? true :  false;\r\n    $focus($G(\"href\"));\r\n\r\n    function handleDialogOk(){\r\n        var href =$G('href').value.replace(/^\\s+|\\s+$/g, '');\r\n        if(href){\r\n            if(!hrefStartWith(href,[\"http\",\"/\",\"ftp://\",'#'])) {\r\n                href  = \"http://\" + href;\r\n            }\r\n            var obj = {\r\n                'href' : href,\r\n                'target' : $G(\"target\").checked ? \"_blank\" : '_self',\r\n                'title' : $G(\"title\").value.replace(/^\\s+|\\s+$/g, ''),\r\n                '_href':href\r\n            };\r\n            //修改链接内容的情况太特殊了，所以先做到这里了\r\n            //todo:情况多的时候，做到command里\r\n            if(orgText && text.value != orgText){\r\n                link[browser.ie ? 'innerText' : 'textContent'] =  obj.textValue = text.value;\r\n                range.selectNode(link).select()\r\n            }\r\n            if(range.collapsed){\r\n                obj.textValue = text.value;\r\n            }\r\n            editor.execCommand('link',utils.clearEmptyAttrs(obj) );\r\n            dialog.close();\r\n        }\r\n    }\r\n    dialog.onok = handleDialogOk;\r\n    $G('href').onkeydown = $G('title').onkeydown = function(evt){\r\n        evt = evt || window.event;\r\n        if (evt.keyCode == 13) {\r\n            handleDialogOk();\r\n            return false;\r\n        }\r\n    };\r\n    $G('href').onblur = function(){\r\n        if(!hrefStartWith(this.value,[\"http\",\"/\",\"ftp://\",'#'])){\r\n            $G(\"msg\").innerHTML = \"<span style='color: red'>\"+lang.httpPrompt+\"</span>\";\r\n        }else{\r\n            $G(\"msg\").innerHTML = \"\";\r\n        }\r\n    };\r\n\r\n    function hrefStartWith(href,arr){\r\n        href = href.replace(/^\\s+|\\s+$/g, '');\r\n        for(var i=0,ai;ai=arr[i++];){\r\n            if(href.indexOf(ai)==0){\r\n                return true;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/map/map.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\r\n    <title></title>\r\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n    <script type=\"text/javascript\" src=\"http://api.map.baidu.com/api?v=1.1&services=true\"></script>\r\n    <style type=\"text/css\">\r\n        .content{width:530px; height: 350px;margin: 10px auto;}\r\n        .content table{width: 100%}\r\n        .content table td{vertical-align: middle;}\r\n        #city,#address{height:21px;background: #FFF;border:1px solid #d7d7d7; line-height: 21px;}\r\n        #city{width:60px}\r\n        #address{width:130px}\r\n        #is_dynamic_label span{vertical-align:middle;margin: 3px 0px 3px 3px;}\r\n        #is_dynamic_label input{vertical-align:middle;margin: 3px 3px 3px 50px;}\r\n    </style>\r\n</head>\r\n<body>\r\n<div class=\"content\">\r\n    <table>\r\n        <tr>\r\n            <td><var id=\"lang_city\"></var>:</td>\r\n            <td><input id=\"city\" type=\"text\" /></td>\r\n            <td><var id=\"lang_address\"></var>:</td>\r\n            <td><input id=\"address\" type=\"text\" value=\"\" /></td>\r\n            <td><a href=\"javascript:doSearch()\" class=\"button\"><var id=\"lang_search\"></var></a></td>\r\n            <td><label id=\"is_dynamic_label\" for=\"is_dynamic\"><input id=\"is_dynamic\" type=\"checkbox\" name=\"is_dynamic\" /><span><var id=\"lang_dynamicmap\"></var></span></label></td>\r\n        </tr>\r\n    </table>\r\n    <div style=\"width:100%;height:340px;margin:5px auto;border:1px solid gray\" id=\"container\"></div>\r\n\r\n</div>\r\n<script type=\"text/javascript\">\r\n    var map = new BMap.Map(\"container\"),marker,point,styleStr;\r\n    map.enableScrollWheelZoom();\r\n    map.enableContinuousZoom();\r\n    function doSearch(){\r\n        if (!document.getElementById('city').value) {\r\n            alert(lang.cityMsg);\r\n            return;\r\n        }\r\n        var search = new BMap.LocalSearch(document.getElementById('city').value, {\r\n            onSearchComplete: function (results){\r\n                if (results && results.getNumPois()) {\r\n                    var points = [];\r\n                    for (var i=0; i<results.getCurrentNumPois(); i++) {\r\n                        points.push(results.getPoi(i).point);\r\n                    }\r\n                    if (points.length > 1) {\r\n                        map.setViewport(points);\r\n                    } else {\r\n                        map.centerAndZoom(points[0], 13);\r\n                    }\r\n                    point = map.getCenter();\r\n                    marker.setPoint(point);\r\n                } else {\r\n                    alert(lang.errorMsg);\r\n                }\r\n            }\r\n        });\r\n        search.search(document.getElementById('address').value || document.getElementById('city').value);\r\n    }\r\n    //获得参数\r\n    function getPars(str,par){\r\n        var reg = new RegExp(par+\"=((\\\\d+|[.,])*)\",\"g\");\r\n        return reg.exec(str)[1];\r\n    }\r\n    function init(){\r\n        var mapNode = editor.selection.getRange().getClosedNode(),\r\n            isMapImg = mapNode && /api[.]map[.]baidu[.]com/ig.test(mapNode.getAttribute(\"src\")),\r\n            isMapIframe = mapNode && domUtils.hasClass(mapNode, 'ueditor_baidumap');\r\n        if(isMapImg || isMapIframe){\r\n            var url, centerPos, markerPos;\r\n            if(isMapIframe) {\r\n                url = decodeURIComponent(mapNode.getAttribute(\"src\"));\r\n                $G('is_dynamic').checked = true;\r\n                styleStr = mapNode.style.cssText;\r\n            } else {\r\n                url = mapNode.getAttribute(\"src\");\r\n                styleStr = mapNode.style.cssText;\r\n            }\r\n\r\n            centerPos = getPars(url,\"center\").split(\",\");\r\n            markerPos = getPars(url, \"markers\").split(\",\");\r\n            point = new BMap.Point(Number(centerPos[0]),Number(centerPos[1]));\r\n            marker = new BMap.Marker(new BMap.Point(Number(markerPos[0]), Number(markerPos[1])));\r\n            map.addControl(new BMap.NavigationControl());\r\n            map.centerAndZoom(point, Number(getPars(url,\"zoom\")));\r\n        }else{\r\n            point = new BMap.Point(116.404, 39.915);    // 创建点坐标\r\n            marker = new BMap.Marker(point);\r\n            map.addControl(new BMap.NavigationControl());\r\n            map.centerAndZoom(point, 10);                     // 初始化地图,设置中心点坐标和地图级别。\r\n        }\r\n        marker.enableDragging();\r\n        map.addOverlay(marker);\r\n    }\r\n    init();\r\n    document.getElementById('address').onkeydown = function (evt){\r\n        evt = evt || event;\r\n        if (evt.keyCode == 13) {\r\n            doSearch();\r\n        }\r\n    };\r\n    dialog.onok = function (){\r\n        var center = map.getCenter();\r\n        var zoom = map.zoomLevel;\r\n        var size = map.getSize();\r\n        var mapWidth = size.width;\r\n        var mapHeight = size.height;\r\n        var point = marker.getPoint();\r\n\r\n        if($G('is_dynamic').checked) {\r\n            var URL = editor.options.UEDITOR_HOME_URL,\r\n                url = [URL + (/\\/$/.test(URL) ? '':'/') + \"dialogs/map/show.html\" +\r\n                    '#center=' + center.lng + ',' + center.lat,\r\n                    '&zoom=' + zoom,\r\n                    '&width=' + mapWidth,\r\n                    '&height=' + mapHeight,\r\n                    '&markers=' + point.lng + ',' + point.lat,\r\n                    '&markerStyles=' + 'l,A'].join('');\r\n            editor.execCommand('inserthtml', '<iframe class=\"ueditor_baidumap\" src=\"' + url + '\"' + (styleStr ? ' style=\"' + styleStr + '\"' :'') + ' frameborder=\"0\" width=\"' + (mapWidth+4) + '\" height=\"' + (mapHeight+4) + '\"></iframe>');\r\n        } else {\r\n            var url = \"http://api.map.baidu.com/staticimage?center=\" + center.lng + ',' + center.lat +\r\n                    \"&zoom=\" + zoom + \"&width=\" + size.width + '&height=' + size.height + \"&markers=\" + point.lng + ',' + point.lat;\r\n            editor.execCommand('inserthtml', '<img width=\"'+ size.width +'\"height=\"'+ size.height +'\" src=\"' + url + '\"' + (styleStr ? ' style=\"' + styleStr + '\"' :'') + '/>');\r\n        }\r\n    };\r\n    document.getElementById(\"address\").focus();\r\n</script>\r\n\r\n\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/map/show.html",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\r\n        \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\r\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\r\n<head>\r\n    <meta charset=\"utf-8\"/>\r\n    <meta name=\"keywords\" content=\"百度地图,百度地图API，百度地图自定义工具，百度地图所见即所得工具\"/>\r\n    <meta name=\"description\" content=\"百度地图API自定义地图，帮助用户在可视化操作下生成百度地图\"/>\r\n    <title>百度地图API自定义地图</title>\r\n    <!--引用百度地图API-->\r\n    <style type=\"text/css\">\r\n        html, body {\r\n            margin: 0;\r\n            padding: 0;\r\n            overflow: hidden;\r\n        }\r\n    </style>\r\n    <script type=\"text/javascript\" src=\"http://api.map.baidu.com/api?key=&v=1.1&services=true\"></script>\r\n</head>\r\n\r\n<body onload=\"initMap();\">\r\n<!--百度地图容器-->\r\n<div style=\"width:697px;height:550px;border:#ccc solid 1px;\" id=\"dituContent\"></div>\r\n</body>\r\n<script type=\"text/javascript\">\r\n    function getParam(name) {\r\n        return location.href.match(new RegExp('[?#&]' + name + '=([^?#&]+)', 'i')) ? RegExp.$1 : '';\r\n    }\r\n    var map, marker;\r\n    var centerParam = getParam('center');\r\n    var zoomParam = getParam('zoom');\r\n    var widthParam = getParam('width');\r\n    var heightParam = getParam('height');\r\n    var markersParam = getParam('markers');\r\n    var markerStylesParam = getParam('markerStyles');\r\n\r\n    //创建和初始化地图函数：\r\n    function initMap() {\r\n        // [FF]切换模式后报错\r\n        if (!window.BMap) {\r\n            return;\r\n        }\r\n        var dituContent = document.getElementById('dituContent');\r\n        dituContent.style.width = widthParam + 'px';\r\n        dituContent.style.height = heightParam + 'px';\r\n\r\n        createMap();//创建地图\r\n        setMapEvent();//设置地图事件\r\n        addMapControl();//向地图添加控件\r\n\r\n        // 创建标注\r\n        var markersArr = markersParam.split(',');\r\n        var point = new BMap.Point(markersArr[0], markersArr[1]);\r\n        marker = new BMap.Marker(point);\r\n        marker.enableDragging();\r\n        map.addOverlay(marker); // 将标注添加到地图中\r\n\r\n        if(parent.editor && parent.document.body.contentEditable==\"true\") { //在编辑状态下\r\n            setMapListener();//地图改变修改外层的iframe标签src属性\r\n        }\r\n    }\r\n\r\n    //创建地图函数：\r\n    function createMap() {\r\n        map = new BMap.Map(\"dituContent\");//在百度地图容器中创建一个地图\r\n        var centerArr = centerParam.split(',');\r\n        var point = new BMap.Point(parseFloat(centerArr[0]), parseFloat(centerArr[1]));//定义一个中心点坐标\r\n        map.centerAndZoom(point, parseInt(zoomParam));//设定地图的中心点和坐标并将地图显示在地图容器中\r\n    }\r\n\r\n    //地图事件设置函数：\r\n    function setMapEvent() {\r\n        map.enableDragging();//启用地图拖拽事件，默认启用(可不写)\r\n        map.enableScrollWheelZoom();//启用地图滚轮放大缩小\r\n        map.enableDoubleClickZoom();//启用鼠标双击放大，默认启用(可不写)\r\n        map.enableKeyboard();//启用键盘上下左右键移动地图\r\n    }\r\n\r\n    //地图控件添加函数：\r\n    function addMapControl() {\r\n        //向地图中添加缩放控件\r\n        var ctrl_nav = new BMap.NavigationControl({anchor: BMAP_ANCHOR_TOP_LEFT, type: BMAP_NAVIGATION_CONTROL_LARGE});\r\n        map.addControl(ctrl_nav);\r\n        //向地图中添加缩略图控件\r\n        var ctrl_ove = new BMap.OverviewMapControl({anchor: BMAP_ANCHOR_BOTTOM_RIGHT, isOpen: 1});\r\n        map.addControl(ctrl_ove);\r\n        //向地图中添加比例尺控件\r\n        var ctrl_sca = new BMap.ScaleControl({anchor: BMAP_ANCHOR_BOTTOM_LEFT});\r\n        map.addControl(ctrl_sca);\r\n    }\r\n\r\n    function setMapListener() {\r\n        var editor = parent.editor, containerIframe,\r\n            iframes = parent.document.getElementsByTagName('iframe');\r\n        for (var key in iframes) {\r\n            if (iframes[key].contentWindow == window) {\r\n                containerIframe = iframes[key];\r\n                break;\r\n            }\r\n        }\r\n        if (containerIframe) {\r\n            map.addEventListener('moveend', mapListenerHandler);\r\n            map.addEventListener('zoomend', mapListenerHandler);\r\n            marker.addEventListener('dragend', mapListenerHandler);\r\n        }\r\n\r\n        function mapListenerHandler() {\r\n            var zoom = map.getZoom(),\r\n                center = map.getCenter(),\r\n                marker = window.marker.getPoint();\r\n            containerIframe.src = containerIframe.src.\r\n                replace(new RegExp('([?#&])center=([^?#&]+)', 'i'), '$1center=' + center.lng + ',' + center.lat).\r\n                replace(new RegExp('([?#&])markers=([^?#&]+)', 'i'), '$1markers=' + marker.lng + ',' + marker.lat).\r\n                replace(new RegExp('([?#&])zoom=([^?#&]+)', 'i'), '$1zoom=' + zoom);\r\n            editor.fireEvent('saveScene');\r\n        }\r\n    }\r\n</script>\r\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/music/music.css",
    "content": ".wrapper{margin: 5px 10px;}\r\n\r\n.searchBar{height:30px;padding:7px 0 3px;text-align:center;}\r\n.searchBtn{font-size:13px;height:24px;}\r\n\r\n.resultBar{width:460px;margin:5px auto;border: 1px solid #CCC;border-radius: 5px;box-shadow: 2px 2px 5px #D3D6DA;overflow: hidden;}\r\n\r\n.listPanel{overflow: hidden;}\r\n.panelon{display:block;}\r\n.paneloff{display:none}\r\n\r\n.page{width:220px;margin:20px auto;overflow: hidden;}\r\n.pageon{float:right;width:24px;line-height:24px;height:24px;margin-right: 5px;background: none;border: none;color: #000;font-weight: bold;text-align:center}\r\n.pageoff{float:right;width:24px;line-height:24px;height:24px;cursor:pointer;background-color: #fff;\r\n   border: 1px solid #E7ECF0;color: #2D64B3;margin-right: 5px;text-decoration: none;text-align:center;}\r\n\r\n.m-box{width:460px;}\r\n.m-m{float: left;line-height: 20px;height: 20px;}\r\n.m-h{height:24px;line-height:24px;padding-left: 46px;background-color:#FAFAFA;border-bottom: 1px solid #DAD8D8;font-weight: bold;font-size: 12px;color: #333;}\r\n.m-l{float:left;width:40px; }\r\n.m-t{float:left;width:140px;}\r\n.m-s{float:left;width:110px;}\r\n.m-z{float:left;width:100px;}\r\n.m-try-t{float: left;width: 60px;;}\r\n\r\n.m-try{float:left;width:20px;height:20px;background:url('http://static.tieba.baidu.com/tb/editor/images/try_music.gif') no-repeat ;}\r\n.m-trying{float:left;width:20px;height:20px;background:url('http://static.tieba.baidu.com/tb/editor/images/stop_music.gif') no-repeat ;}\r\n\r\n.loading{width:95px;height:7px;font-size:7px;margin:60px auto;background:url(http://static.tieba.baidu.com/tb/editor/images/loading.gif) no-repeat}\r\n.empty{width:300px;height:40px;padding:2px;margin:50px auto;line-height:40px; color:#006699;text-align:center;}"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/music/music.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n    <meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">\r\n    <title>插入音乐</title>\r\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"music.css\">\r\n</head>\r\n<body>\r\n<div class=\"wrapper\">\r\n    <div class=\"searchBar\">\r\n        <input id=\"J_searchName\" type=\"text\"/>\r\n        <input type=\"button\" class=\"searchBtn\" id=\"J_searchBtn\">\r\n    </div>\r\n    <div class=\"resultBar\" id=\"J_resultBar\">\r\n        <div class=\"loading\" style=\"display:none\"></div>\r\n        <div class=\"empty\"><var id=\"lang_input_tips\"></var></div>\r\n    </div>\r\n    <div id=\"J_preview\"></div>\r\n</div>\r\n<script type=\"text/javascript\" src=\"music.js\"></script>\r\n<script type=\"text/javascript\">\r\n    var music = new Music;\r\n    dialog.onok = function () {\r\n        music.exec();\r\n    };\r\n    dialog.oncancel = function () {\r\n        $G('J_preview').innerHTML = \"\";\r\n    };\r\n</script>\r\n</body>\r\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/music/music.js",
    "content": "function Music() {\r\n    this.init();\r\n}\r\n(function () {\r\n    var pages = [],\r\n        panels = [],\r\n        selectedItem = null;\r\n    Music.prototype = {\r\n        total:70,\r\n        pageSize:10,\r\n        dataUrl:\"http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.search.common\",\r\n        playerUrl:\"http://box.baidu.com/widget/flash/bdspacesong.swf\",\r\n\r\n        init:function () {\r\n            var me = this;\r\n            domUtils.on($G(\"J_searchName\"), \"keyup\", function (event) {\r\n                var e = window.event || event;\r\n                if (e.keyCode == 13) {\r\n                    me.dosearch();\r\n                }\r\n            });\r\n            domUtils.on($G(\"J_searchBtn\"), \"click\", function () {\r\n                me.dosearch();\r\n            });\r\n        },\r\n        callback:function (data) {\r\n            var me = this;\r\n            me.data = data.song_list;\r\n            setTimeout(function () {\r\n                $G('J_resultBar').innerHTML = me._renderTemplate(data.song_list);\r\n            }, 300);\r\n        },\r\n        dosearch:function () {\r\n            var me = this;\r\n            selectedItem = null;\r\n            var key = $G('J_searchName').value;\r\n            if (utils.trim(key) == \"\")return false;\r\n            key = encodeURIComponent(key);\r\n            me._sent(key);\r\n        },\r\n        doselect:function (i) {\r\n            var me = this;\r\n            if (typeof i == 'object') {\r\n                selectedItem = i;\r\n            } else if (typeof i == 'number') {\r\n                selectedItem = me.data[i];\r\n            }\r\n        },\r\n        onpageclick:function (id) {\r\n            var me = this;\r\n            for (var i = 0; i < pages.length; i++) {\r\n                $G(pages[i]).className = 'pageoff';\r\n                $G(panels[i]).className = 'paneloff';\r\n            }\r\n            $G('page' + id).className = 'pageon';\r\n            $G('panel' + id).className = 'panelon';\r\n        },\r\n        listenTest:function (elem) {\r\n            var me = this,\r\n                view = $G('J_preview'),\r\n                is_play_action = (elem.className == 'm-try'),\r\n                old_trying = me._getTryingElem();\r\n\r\n            if (old_trying) {\r\n                old_trying.className = 'm-try';\r\n                view.innerHTML = '';\r\n            }\r\n            if (is_play_action) {\r\n                elem.className = 'm-trying';\r\n                view.innerHTML = me._buildMusicHtml(me._getUrl(true));\r\n            }\r\n        },\r\n        _sent:function (param) {\r\n            var me = this;\r\n            $G('J_resultBar').innerHTML = '<div class=\"loading\"></div>';\r\n\r\n            utils.loadFile(document, {\r\n                src:me.dataUrl + '&query=' + param + '&page_size=' + me.total + '&callback=music.callback&.r=' + Math.random(),\r\n                tag:\"script\",\r\n                type:\"text/javascript\",\r\n                defer:\"defer\"\r\n            });\r\n        },\r\n        _removeHtml:function (str) {\r\n            var reg = /<\\s*\\/?\\s*[^>]*\\s*>/gi;\r\n            return str.replace(reg, \"\");\r\n        },\r\n        _getUrl:function (isTryListen) {\r\n            var me = this;\r\n            var param = 'from=tiebasongwidget&url=&name=' + encodeURIComponent(me._removeHtml(selectedItem.title)) + '&artist='\r\n                + encodeURIComponent(me._removeHtml(selectedItem.author)) + '&extra='\r\n                + encodeURIComponent(me._removeHtml(selectedItem.album_title))\r\n                + '&autoPlay='+isTryListen+'' + '&loop=true';\r\n            return  me.playerUrl + \"?\" + param;\r\n        },\r\n        _getTryingElem:function () {\r\n            var s = $G('J_listPanel').getElementsByTagName('span');\r\n\r\n            for (var i = 0; i < s.length; i++) {\r\n                if (s[i].className == 'm-trying')\r\n                    return s[i];\r\n            }\r\n            return null;\r\n        },\r\n        _buildMusicHtml:function (playerUrl) {\r\n            var html = '<embed class=\"BDE_try_Music\" allowfullscreen=\"false\" pluginspage=\"http://www.macromedia.com/go/getflashplayer\"';\r\n            html += ' src=\"' + playerUrl + '\"';\r\n            html += ' width=\"1\" height=\"1\" style=\"position:absolute;left:-2000px;\"';\r\n            html += ' type=\"application/x-shockwave-flash\" wmode=\"transparent\" play=\"true\" loop=\"false\"';\r\n            html += ' menu=\"false\" allowscriptaccess=\"never\" scale=\"noborder\">';\r\n            return html;\r\n        },\r\n        _byteLength:function (str) {\r\n            return str.replace(/[^\\u0000-\\u007f]/g, \"\\u0061\\u0061\").length;\r\n        },\r\n        _getMaxText:function (s) {\r\n            var me = this;\r\n            s = me._removeHtml(s);\r\n            if (me._byteLength(s) > 12)\r\n                return s.substring(0, 5) + '...';\r\n            if (!s) s = \"&nbsp;\";\r\n            return s;\r\n        },\r\n        _rebuildData:function (data) {\r\n            var me = this,\r\n                newData = [],\r\n                d = me.pageSize,\r\n                itembox;\r\n            for (var i = 0; i < data.length; i++) {\r\n                if ((i + d) % d == 0) {\r\n                    itembox = [];\r\n                    newData.push(itembox)\r\n                }\r\n                itembox.push(data[i]);\r\n            }\r\n            return newData;\r\n        },\r\n        _renderTemplate:function (data) {\r\n            var me = this;\r\n            if (data.length == 0)return '<div class=\"empty\">' + lang.emptyTxt + '</div>';\r\n            data = me._rebuildData(data);\r\n            var s = [], p = [], t = [];\r\n            s.push('<div id=\"J_listPanel\" class=\"listPanel\">');\r\n            p.push('<div class=\"page\">');\r\n            for (var i = 0, tmpList; tmpList = data[i++];) {\r\n                panels.push('panel' + i);\r\n                pages.push('page' + i);\r\n                if (i == 1) {\r\n                    s.push('<div id=\"panel' + i + '\" class=\"panelon\">');\r\n                    if (data.length != 1) {\r\n                        t.push('<div id=\"page' + i + '\" onclick=\"music.onpageclick(' + i + ')\" class=\"pageon\">' + (i ) + '</div>');\r\n                    }\r\n                } else {\r\n                    s.push('<div id=\"panel' + i + '\" class=\"paneloff\">');\r\n                    t.push('<div id=\"page' + i + '\" onclick=\"music.onpageclick(' + i + ')\" class=\"pageoff\">' + (i ) + '</div>');\r\n                }\r\n                s.push('<div class=\"m-box\">');\r\n                s.push('<div class=\"m-h\"><span class=\"m-t\">' + lang.chapter + '</span><span class=\"m-s\">' + lang.singer\r\n                    + '</span><span class=\"m-z\">' + lang.special + '</span><span class=\"m-try-t\">' + lang.listenTest + '</span></div>');\r\n                for (var j = 0, tmpObj; tmpObj = tmpList[j++];) {\r\n                    s.push('<label for=\"radio-' + i + '-' + j + '\" class=\"m-m\">');\r\n                    s.push('<input type=\"radio\" id=\"radio-' + i + '-' + j + '\" name=\"musicId\" class=\"m-l\" onclick=\"music.doselect(' + (me.pageSize * (i-1) + (j-1)) + ')\"/>');\r\n                    s.push('<span class=\"m-t\">' + me._getMaxText(tmpObj.title) + '</span>');\r\n                    s.push('<span class=\"m-s\">' + me._getMaxText(tmpObj.author) + '</span>');\r\n                    s.push('<span class=\"m-z\">' + me._getMaxText(tmpObj.album_title) + '</span>');\r\n                    s.push('<span class=\"m-try\" onclick=\"music.doselect(' + (me.pageSize * (i-1) + (j-1)) + ');music.listenTest(this)\"></span>');\r\n                    s.push('</label>');\r\n                }\r\n                s.push('</div>');\r\n                s.push('</div>');\r\n            }\r\n            t.reverse();\r\n            p.push(t.join(''));\r\n            s.push('</div>');\r\n            p.push('</div>');\r\n            return s.join('') + p.join('');\r\n        },\r\n        exec:function () {\r\n            var me = this;\r\n            if (selectedItem == null)   return;\r\n            $G('J_preview').innerHTML = \"\";\r\n            editor.execCommand('music', {\r\n                url:me._getUrl(false),\r\n                width:400,\r\n                height:95\r\n            });\r\n        }\r\n    };\r\n})();\r\n\r\n\r\n\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/preview/preview.html",
    "content": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\r\n    \"http://www.w3.org/TR/html4/loose.dtd\">\r\n<html>\r\n    <head>\r\n        <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\r\n        <style>\r\n            html,body{\r\n                height:100%;\r\n                width:100%;\r\n                padding:0;\r\n                margin:0;\r\n            }\r\n            #preview{\r\n                width:100%;\r\n                height:100%;\r\n                padding:0;\r\n                margin:0;\r\n            }\r\n            #preview *{font-family:sans-serif;font-size:16px;}\r\n        </style>\r\n        <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n        <script src=\"../../ueditor.parse.js\"></script>\r\n        <title></title>\r\n    </head>\r\n    <body class=\"view\">\r\n        <div id=\"preview\" style=\"margin:8px\">\r\n\r\n        </div>\r\n    </body>\r\n    <script>\r\n        document.getElementById('preview').innerHTML = editor.getContent();\r\n        uParse('#preview',{\r\n            rootPath : '../../',\r\n            chartContainerHeight:500\r\n        })\r\n        dialog.oncancel = function(){\r\n            document.getElementById('preview').innerHTML = '';\r\n        }\r\n    </script>\r\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/scrawl/scrawl.css",
    "content": "/*common\r\n*/\r\nbody{margin: 0;}\r\ntable{width:100%;}\r\ntable td{padding:2px 4px;vertical-align: middle;}\r\na{text-decoration: none;}\r\nem{font-style: normal;}\r\n.border_style1{border: 1px solid #ccc;border-radius: 5px;box-shadow:2px 2px 5px #d3d6da;}\r\n/*module\r\n*/\r\n.main{margin: 8px;overflow: hidden;}\r\n\r\n.hot{float:left;height:335px;}\r\n.drawBoard{position: relative; cursor: crosshair;}\r\n.brushBorad{position: absolute;left:0;top:0;z-index: 998;}\r\n.picBoard{border: none;text-align: center;line-height: 300px;cursor: default;}\r\n.operateBar{margin-top:10px;font-size:12px;text-align: center;}\r\n.operateBar span{margin-left: 10px;}\r\n\r\n.drawToolbar{float:right;width:110px;height:300px;overflow: hidden;}\r\n.colorBar{margin-top:10px;font-size: 12px;text-align: center;}\r\n.colorBar a{display:block;width: 10px;height: 10px;border:1px solid #1006F1;border-radius: 3px; box-shadow:2px 2px 5px #d3d6da;opacity: 0.3}\r\n.sectionBar{margin-top:15px;font-size: 12px;text-align: center;}\r\n.sectionBar a{display:inline-block;width:10px;height:12px;color: #888;text-indent: -999px;opacity: 0.3}\r\n.size1{background: url('images/size.png') 1px center no-repeat ;}\r\n.size2{background: url('images/size.png') -10px center no-repeat;}\r\n.size3{background: url('images/size.png') -22px center no-repeat;}\r\n.size4{background: url('images/size.png') -35px center no-repeat;}\r\n\r\n.addImgH{position: relative;}\r\n.addImgH_form{position: absolute;left: 18px;top: -1px;width: 75px;height: 21px;opacity: 0;cursor: pointer;}\r\n.addImgH_form input{width: 100%;}\r\n/*scrawl遮罩层\r\n*/\r\n.maskLayerNull{display: none;}\r\n.maskLayer{position: absolute;top:0;left:0;width: 100%; height: 100%;opacity: 0.7;\r\n    background-color: #fff;text-align:center;font-weight:bold;line-height:300px;z-index: 1000;}\r\n/*btn state\r\n*/\r\n.previousStepH .icon{display: inline-block;width:16px;height:16px;background-image: url('images/undoH.png');cursor: pointer;}\r\n.previousStepH .text{color:#888;cursor:pointer;}\r\n.previousStep .icon{display: inline-block;width:16px;height:16px;background-image: url('images/undo.png');cursor:default;}\r\n.previousStep .text{color:#ccc;cursor:default;}\r\n\r\n.nextStepH .icon{display: inline-block;width:16px;height:16px;background-image: url('images/redoH.png');cursor: pointer;}\r\n.nextStepH .text{color:#888;cursor:pointer;}\r\n.nextStep .icon{display: inline-block;width:16px;height:16px;background-image: url('images/redo.png');cursor:default;}\r\n.nextStep .text{color:#ccc;cursor:default;}\r\n\r\n.clearBoardH .icon{display: inline-block;width:16px;height:16px;background-image: url('images/emptyH.png');cursor: pointer;}\r\n.clearBoardH .text{color:#888;cursor:pointer;}\r\n.clearBoard .icon{display: inline-block;width:16px;height:16px;background-image: url('images/empty.png');cursor:default;}\r\n.clearBoard .text{color:#ccc;cursor:default;}\r\n\r\n.scaleBoardH .icon{display: inline-block;width:16px;height:16px;background-image: url('images/scaleH.png');cursor: pointer;}\r\n.scaleBoardH .text{color:#888;cursor:pointer;}\r\n.scaleBoard .icon{display: inline-block;width:16px;height:16px;background-image: url('images/scale.png');cursor:default;}\r\n.scaleBoard .text{color:#ccc;cursor:default;}\r\n\r\n.removeImgH .icon{display: inline-block;width:16px;height:16px;background-image: url('images/delimgH.png');cursor: pointer;}\r\n.removeImgH .text{color:#888;cursor:pointer;}\r\n.removeImg .icon{display: inline-block;width:16px;height:16px;background-image: url('images/delimg.png');cursor:default;}\r\n.removeImg .text{color:#ccc;cursor:default;}\r\n\r\n.addImgH .icon{vertical-align:top;display: inline-block;width:16px;height:16px;background-image: url('images/addimg.png')}\r\n.addImgH .text{color:#888;cursor:pointer;}\r\n/*icon\r\n*/\r\n.brushIcon{display: inline-block;width:16px;height:16px;background-image: url('images/brush.png')}\r\n.eraserIcon{display: inline-block;width:16px;height:16px;background-image: url('images/eraser.png')}\r\n\r\n\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/scrawl/scrawl.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n    <title></title>\r\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\r\n    <meta name=\"robots\" content=\"noindex, nofollow\"/>\r\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"scrawl.css\">\r\n</head>\r\n<body>\r\n<div class=\"main\" id=\"J_wrap\">\r\n    <div class=\"hot\">\r\n        <div class=\"drawBoard border_style1\">\r\n            <canvas id=\"J_brushBoard\" class=\"brushBorad\" width=\"360\" height=\"300\"></canvas>\r\n            <div id=\"J_picBoard\" class=\"picBoard\" style=\"width: 360px;height: 300px\"></div>\r\n        </div>\r\n        <div id=\"J_operateBar\" class=\"operateBar\">\r\n            <span id=\"J_previousStep\" class=\"previousStep\">\r\n                <em class=\"icon\"></em>\r\n                <em class=\"text\"><var id=\"lang_input_previousStep\"></var></em>\r\n            </span>\r\n            <span id=\"J_nextStep\" class=\"nextStep\">\r\n                <em class=\"icon\"></em>\r\n                <em class=\"text\"><var id=\"lang_input_nextsStep\"></var></em>\r\n            </span>\r\n            <span id=\"J_clearBoard\" class=\"clearBoard\">\r\n                <em class=\"icon\"></em>\r\n                <em class=\"text\"><var id=\"lang_input_clear\"></var></em>\r\n            </span>\r\n            <span id=\"J_sacleBoard\" class=\"scaleBoard\">\r\n                <em class=\"icon\"></em>\r\n                <em class=\"text\"><var id=\"lang_input_ScalePic\"></var></em>\r\n            </span>\r\n        </div>\r\n    </div>\r\n    <div class=\"drawToolbar border_style1\">\r\n        <div id=\"J_colorBar\" class=\"colorBar\"></div>\r\n        <div id=\"J_brushBar\" class=\"sectionBar\">\r\n            <em class=\"brushIcon\"></em>\r\n            <a href=\"javascript:void(0)\" class=\"size1\">1</a>\r\n            <a href=\"javascript:void(0)\" class=\"size2\">3</a>\r\n            <a href=\"javascript:void(0)\" class=\"size3\">5</a>\r\n            <a href=\"javascript:void(0)\" class=\"size4\">7</a>\r\n        </div>\r\n        <div id=\"J_eraserBar\" class=\"sectionBar\">\r\n            <em class=\"eraserIcon\"></em>\r\n            <a href=\"javascript:void(0)\" class=\"size1\">1</a>\r\n            <a href=\"javascript:void(0)\" class=\"size2\">3</a>\r\n            <a href=\"javascript:void(0)\" class=\"size3\">5</a>\r\n            <a href=\"javascript:void(0)\" class=\"size4\">7</a>\r\n        </div>\r\n        <div class=\"sectionBar\">\r\n            <div id=\"J_addImg\" class=\"addImgH\">\r\n                <em class=\"icon\"></em>\r\n                <em class=\"text\"><var id=\"lang_input_addPic\"></var></em>\r\n                <form method=\"post\" id=\"fileForm\" enctype=\"multipart/form-data\" class=\"addImgH_form\" target=\"up\">\r\n                    <input type=\"file\" name=\"upfile\" id=\"J_imgTxt\"\r\n                           accept=\"image/gif,image/jpeg,image/png,image/jpg,image/bmp\"/>\r\n                </form>\r\n                <iframe name=\"up\" style=\"display: none\"></iframe>\r\n            </div>\r\n        </div>\r\n        <div class=\"sectionBar\">\r\n            <span id=\"J_removeImg\" class=\"removeImg\">\r\n                <em class=\"icon\"></em>\r\n                <em class=\"text\"><var id=\"lang_input_removePic\"></var></em>\r\n            </span>\r\n        </div>\r\n    </div>\r\n</div>\r\n<div id=\"J_maskLayer\" class=\"maskLayerNull\"></div>\r\n\r\n<script type=\"text/javascript\" src=\"scrawl.js\"></script>\r\n<script type=\"text/javascript\">\r\n    var settings = {\r\n        drawBrushSize:3, //画笔初始大小\r\n        drawBrushColor:\"#4bacc6\", //画笔初始颜色\r\n        colorList:['c00000', 'ff0000', 'ffc000', 'ffff00', '92d050', '00b050', '00b0f0', '0070c0', '002060', '7030a0', 'ffffff',\r\n            '000000', 'eeece1', '1f497d', '4f81bd', 'c0504d', '9bbb59', '8064a2', '4bacc6', 'f79646'], //画笔选择颜色\r\n        saveNum:10  //撤销次数\r\n    };\r\n\r\n    var scrawlObj = new scrawl( settings );\r\n    scrawlObj.isCancelScrawl = false;\r\n\r\n    dialog.onok = function () {\r\n        exec( scrawlObj );\r\n        return false;\r\n    };\r\n    dialog.oncancel = function () {\r\n        scrawlObj.isCancelScrawl = true;\r\n    };\r\n</script>\r\n</body>\r\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/scrawl/scrawl.js",
    "content": "/**\r\n * Created with JetBrains PhpStorm.\r\n * User: xuheng\r\n * Date: 12-5-22\r\n * Time: 上午11:38\r\n * To change this template use File | Settings | File Templates.\r\n */\r\nvar scrawl = function (options) {\r\n    options && this.initOptions(options);\r\n};\r\n(function () {\r\n    var canvas = $G(\"J_brushBoard\"),\r\n        context = canvas.getContext('2d'),\r\n        drawStep = [], //undo redo存储\r\n        drawStepIndex = 0; //undo redo指针\r\n\r\n    scrawl.prototype = {\r\n        isScrawl:false, //是否涂鸦\r\n        brushWidth:-1, //画笔粗细\r\n        brushColor:\"\", //画笔颜色\r\n\r\n        initOptions:function (options) {\r\n            var me = this;\r\n            me.originalState(options);//初始页面状态\r\n            me._buildToolbarColor(options.colorList);//动态生成颜色选择集合\r\n\r\n            me._addBoardListener(options.saveNum);//添加画板处理\r\n            me._addOPerateListener(options.saveNum);//添加undo redo clearBoard处理\r\n            me._addColorBarListener();//添加颜色选择处理\r\n            me._addBrushBarListener();//添加画笔大小处理\r\n            me._addEraserBarListener();//添加橡皮大小处理\r\n            me._addAddImgListener();//添加增添背景图片处理\r\n            me._addRemoveImgListenter();//删除背景图片处理\r\n            me._addScalePicListenter();//添加缩放处理\r\n            me._addClearSelectionListenter();//添加清楚选中状态处理\r\n\r\n            me._originalColorSelect(options.drawBrushColor);//初始化颜色选中\r\n            me._originalBrushSelect(options.drawBrushSize);//初始化画笔选中\r\n            me._clearSelection();//清楚选中状态\r\n        },\r\n\r\n        originalState:function (options) {\r\n            var me = this;\r\n\r\n            me.brushWidth = options.drawBrushSize;//同步画笔粗细\r\n            me.brushColor = options.drawBrushColor;//同步画笔颜色\r\n\r\n            context.lineWidth = me.brushWidth;//初始画笔大小\r\n            context.strokeStyle = me.brushColor;//初始画笔颜色\r\n            context.fillStyle = \"transparent\";//初始画布背景颜色\r\n            context.lineCap = \"round\";//去除锯齿\r\n            context.fill();\r\n        },\r\n        _buildToolbarColor:function (colorList) {\r\n            var tmp = null, arr = [];\r\n            arr.push(\"<table id='J_colorList'>\");\r\n            for (var i = 0, color; color = colorList[i++];) {\r\n                if ((i - 1) % 5 == 0) {\r\n                    if (i != 1) {\r\n                        arr.push(\"</tr>\");\r\n                    }\r\n                    arr.push(\"<tr>\");\r\n                }\r\n                tmp = '#' + color;\r\n                arr.push(\"<td><a title='\" + tmp + \"' href='javascript:void(0)' style='background-color:\" + tmp + \"'></a></td>\");\r\n            }\r\n            arr.push(\"</tr></table>\");\r\n            $G(\"J_colorBar\").innerHTML = arr.join(\"\");\r\n        },\r\n\r\n        _addBoardListener:function (saveNum) {\r\n            var me = this,\r\n                margin = 0,\r\n                startX = -1,\r\n                startY = -1,\r\n                isMouseDown = false,\r\n                isMouseMove = false,\r\n                isMouseUp = false,\r\n                buttonPress = 0, button, flag = '';\r\n\r\n            margin = parseInt(domUtils.getComputedStyle($G(\"J_wrap\"), \"margin-left\"));\r\n            drawStep.push(context.getImageData(0, 0, context.canvas.width, context.canvas.height));\r\n            drawStepIndex += 1;\r\n\r\n            domUtils.on(canvas, [\"mousedown\", \"mousemove\", \"mouseup\", \"mouseout\"], function (e) {\r\n                button = browser.webkit ? e.which : buttonPress;\r\n                switch (e.type) {\r\n                    case 'mousedown':\r\n                        buttonPress = 1;\r\n                        flag = 1;\r\n                        isMouseDown = true;\r\n                        isMouseUp = false;\r\n                        isMouseMove = false;\r\n                        me.isScrawl = true;\r\n                        startX = e.clientX - margin;//10为外边距总和\r\n                        startY = e.clientY - margin;\r\n                        context.beginPath();\r\n                        break;\r\n                    case 'mousemove' :\r\n                        if (!flag && button == 0) {\r\n                            return;\r\n                        }\r\n                        if (!flag && button) {\r\n                            startX = e.clientX - margin;//10为外边距总和\r\n                            startY = e.clientY - margin;\r\n                            context.beginPath();\r\n                            flag = 1;\r\n                        }\r\n                        if (isMouseUp || !isMouseDown) {\r\n                            return;\r\n                        }\r\n                        var endX = e.clientX - margin,\r\n                            endY = e.clientY - margin;\r\n\r\n                        context.moveTo(startX, startY);\r\n                        context.lineTo(endX, endY);\r\n                        context.stroke();\r\n                        startX = endX;\r\n                        startY = endY;\r\n                        isMouseMove = true;\r\n                        break;\r\n                    case 'mouseup':\r\n                        buttonPress = 0;\r\n                        if (!isMouseDown)return;\r\n                        if (!isMouseMove) {\r\n                            context.arc(startX, startY, context.lineWidth, 0, Math.PI * 2, false);\r\n                            context.fillStyle = context.strokeStyle;\r\n                            context.fill();\r\n                        }\r\n                        context.closePath();\r\n                        me._saveOPerate(saveNum);\r\n                        isMouseDown = false;\r\n                        isMouseMove = false;\r\n                        isMouseUp = true;\r\n                        startX = -1;\r\n                        startY = -1;\r\n                        break;\r\n                    case 'mouseout':\r\n                        flag = '';\r\n                        buttonPress = 0;\r\n                        if (button == 1) return;\r\n                        context.closePath();\r\n                        break;\r\n                }\r\n            });\r\n        },\r\n        _addOPerateListener:function (saveNum) {\r\n            var me = this;\r\n            domUtils.on($G(\"J_previousStep\"), \"click\", function () {\r\n                if (drawStepIndex > 1) {\r\n                    drawStepIndex -= 1;\r\n                    context.clearRect(0, 0, context.canvas.width, context.canvas.height);\r\n                    context.putImageData(drawStep[drawStepIndex - 1], 0, 0);\r\n                    me.btn2Highlight(\"J_nextStep\");\r\n                    drawStepIndex == 1 && me.btn2disable(\"J_previousStep\");\r\n                }\r\n            });\r\n            domUtils.on($G(\"J_nextStep\"), \"click\", function () {\r\n                if (drawStepIndex > 0 && drawStepIndex < drawStep.length) {\r\n                    context.clearRect(0, 0, context.canvas.width, context.canvas.height);\r\n                    context.putImageData(drawStep[drawStepIndex], 0, 0);\r\n                    drawStepIndex += 1;\r\n                    me.btn2Highlight(\"J_previousStep\");\r\n                    drawStepIndex == drawStep.length && me.btn2disable(\"J_nextStep\");\r\n                }\r\n            });\r\n            domUtils.on($G(\"J_clearBoard\"), \"click\", function () {\r\n                context.clearRect(0, 0, context.canvas.width, context.canvas.height);\r\n                drawStep = [];\r\n                me._saveOPerate(saveNum);\r\n                drawStepIndex = 1;\r\n                me.isScrawl = false;\r\n                me.btn2disable(\"J_previousStep\");\r\n                me.btn2disable(\"J_nextStep\");\r\n                me.btn2disable(\"J_clearBoard\");\r\n            });\r\n        },\r\n        _addColorBarListener:function () {\r\n            var me = this;\r\n            domUtils.on($G(\"J_colorBar\"), \"click\", function (e) {\r\n                var target = me.getTarget(e),\r\n                    color = target.title;\r\n                if (!!color) {\r\n                    me._addColorSelect(target);\r\n\r\n                    me.brushColor = color;\r\n                    context.globalCompositeOperation = \"source-over\";\r\n                    context.lineWidth = me.brushWidth;\r\n                    context.strokeStyle = color;\r\n                }\r\n            });\r\n        },\r\n        _addBrushBarListener:function () {\r\n            var me = this;\r\n            domUtils.on($G(\"J_brushBar\"), \"click\", function (e) {\r\n                var target = me.getTarget(e),\r\n                    size = browser.ie ? target.innerText : target.text;\r\n                if (!!size) {\r\n                    me._addBESelect(target);\r\n\r\n                    context.globalCompositeOperation = \"source-over\";\r\n                    context.lineWidth = parseInt(size);\r\n                    context.strokeStyle = me.brushColor;\r\n                    me.brushWidth = context.lineWidth;\r\n                }\r\n            });\r\n        },\r\n        _addEraserBarListener:function () {\r\n            var me = this;\r\n            domUtils.on($G(\"J_eraserBar\"), \"click\", function (e) {\r\n                var target = me.getTarget(e),\r\n                    size = browser.ie ? target.innerText : target.text;\r\n                if (!!size) {\r\n                    me._addBESelect(target);\r\n\r\n                    context.lineWidth = parseInt(size);\r\n                    context.globalCompositeOperation = \"destination-out\";\r\n                    context.strokeStyle = \"#FFF\";\r\n                }\r\n            });\r\n        },\r\n        _addAddImgListener:function () {\r\n            var file = $G(\"J_imgTxt\");\r\n            if (!window.FileReader) {\r\n                $G(\"J_addImg\").style.display = 'none';\r\n                $G(\"J_removeImg\").style.display = 'none';\r\n                $G(\"J_sacleBoard\").style.display = 'none';\r\n            }\r\n            domUtils.on(file, \"change\", function (e) {\r\n                var frm = file.parentNode;\r\n                addMaskLayer(lang.backgroundUploading);\r\n\r\n                var target = e.target || e.srcElement,\r\n                    reader = new FileReader();\r\n                reader.onload = function(evt){\r\n                    var target = evt.target || evt.srcElement;\r\n                    ue_callback(target.result, 'SUCCESS');\r\n                };\r\n                reader.readAsDataURL(target.files[0]);\r\n                frm.reset();\r\n            });\r\n        },\r\n        _addRemoveImgListenter:function () {\r\n            var me = this;\r\n            domUtils.on($G(\"J_removeImg\"), \"click\", function () {\r\n                $G(\"J_picBoard\").innerHTML = \"\";\r\n                me.btn2disable(\"J_removeImg\");\r\n                me.btn2disable(\"J_sacleBoard\");\r\n            });\r\n        },\r\n        _addScalePicListenter:function () {\r\n            domUtils.on($G(\"J_sacleBoard\"), \"click\", function () {\r\n                var picBoard = $G(\"J_picBoard\"),\r\n                    scaleCon = $G(\"J_scaleCon\"),\r\n                    img = picBoard.children[0];\r\n\r\n                if (img) {\r\n                    if (!scaleCon) {\r\n                        picBoard.style.cssText = \"position:relative;z-index:1;\"+picBoard.style.cssText;\r\n                        img.style.cssText = \"position: absolute;top:\" + (canvas.height - img.height) / 2 + \"px;left:\" + (canvas.width - img.width) / 2 + \"px;\";\r\n                        var scale = new ScaleBoy();\r\n                        picBoard.appendChild(scale.init());\r\n                        scale.startScale(img);\r\n                    } else {\r\n                        if (scaleCon.style.visibility == \"visible\") {\r\n                            scaleCon.style.visibility = \"hidden\";\r\n                            picBoard.style.position = \"\";\r\n                            picBoard.style.zIndex = \"\";\r\n                        } else {\r\n                            scaleCon.style.visibility = \"visible\";\r\n                            picBoard.style.cssText += \"position:relative;z-index:1\";\r\n                        }\r\n                    }\r\n                }\r\n            });\r\n        },\r\n        _addClearSelectionListenter:function () {\r\n            var doc = document;\r\n            domUtils.on(doc, 'mousemove', function (e) {\r\n                if (browser.ie && browser.version < 11)\r\n                    doc.selection.clear();\r\n                else\r\n                    window.getSelection().removeAllRanges();\r\n            });\r\n        },\r\n        _clearSelection:function () {\r\n            var list = [\"J_operateBar\", \"J_colorBar\", \"J_brushBar\", \"J_eraserBar\", \"J_picBoard\"];\r\n            for (var i = 0, group; group = list[i++];) {\r\n                domUtils.unSelectable($G(group));\r\n            }\r\n        },\r\n\r\n        _saveOPerate:function (saveNum) {\r\n            var me = this;\r\n            if (drawStep.length <= saveNum) {\r\n                if(drawStepIndex<drawStep.length){\r\n                    me.btn2disable(\"J_nextStep\");\r\n                    drawStep.splice(drawStepIndex);\r\n                }\r\n                drawStep.push(context.getImageData(0, 0, context.canvas.width, context.canvas.height));\r\n                drawStepIndex = drawStep.length;\r\n            } else {\r\n                drawStep.shift();\r\n                drawStep.push(context.getImageData(0, 0, context.canvas.width, context.canvas.height));\r\n                drawStepIndex = drawStep.length;\r\n            }\r\n            me.btn2Highlight(\"J_previousStep\");\r\n            me.btn2Highlight(\"J_clearBoard\");\r\n        },\r\n\r\n        _originalColorSelect:function (title) {\r\n            var colorList = $G(\"J_colorList\").getElementsByTagName(\"td\");\r\n            for (var j = 0, cell; cell = colorList[j++];) {\r\n                if (cell.children[0].title.toLowerCase() == title) {\r\n                    cell.children[0].style.opacity = 1;\r\n                }\r\n            }\r\n        },\r\n        _originalBrushSelect:function (text) {\r\n            var brushList = $G(\"J_brushBar\").children;\r\n            for (var i = 0, ele; ele = brushList[i++];) {\r\n                if (ele.tagName.toLowerCase() == \"a\") {\r\n                    var size = browser.ie ? ele.innerText : ele.text;\r\n                    if (size.toLowerCase() == text) {\r\n                        ele.style.opacity = 1;\r\n                    }\r\n                }\r\n            }\r\n        },\r\n        _addColorSelect:function (target) {\r\n            var me = this,\r\n                colorList = $G(\"J_colorList\").getElementsByTagName(\"td\"),\r\n                eraserList = $G(\"J_eraserBar\").children,\r\n                brushList = $G(\"J_brushBar\").children;\r\n\r\n            for (var i = 0, cell; cell = colorList[i++];) {\r\n                cell.children[0].style.opacity = 0.3;\r\n            }\r\n            for (var k = 0, ele; ele = brushList[k++];) {\r\n                if (ele.tagName.toLowerCase() == \"a\") {\r\n                    ele.style.opacity = 0.3;\r\n                    var size = browser.ie ? ele.innerText : ele.text;\r\n                    if (size.toLowerCase() == this.brushWidth) {\r\n                        ele.style.opacity = 1;\r\n                    }\r\n                }\r\n            }\r\n            for (var j = 0, node; node = eraserList[j++];) {\r\n                if (node.tagName.toLowerCase() == \"a\") {\r\n                    node.style.opacity = 0.3;\r\n                }\r\n            }\r\n\r\n            target.style.opacity = 1;\r\n            target.blur();\r\n        },\r\n        _addBESelect:function (target) {\r\n            var brushList = $G(\"J_brushBar\").children;\r\n            var eraserList = $G(\"J_eraserBar\").children;\r\n\r\n            for (var i = 0, ele; ele = brushList[i++];) {\r\n                if (ele.tagName.toLowerCase() == \"a\") {\r\n                    ele.style.opacity = 0.3;\r\n                }\r\n            }\r\n            for (var j = 0, node; node = eraserList[j++];) {\r\n                if (node.tagName.toLowerCase() == \"a\") {\r\n                    node.style.opacity = 0.3;\r\n                }\r\n            }\r\n\r\n            target.style.opacity = 1;\r\n            target.blur();\r\n        },\r\n        getCanvasData:function () {\r\n            var picContainer = $G(\"J_picBoard\"),\r\n                img = picContainer.children[0];\r\n            if (img) {\r\n                var x, y;\r\n                if (img.style.position == \"absolute\") {\r\n                    x = parseInt(img.style.left);\r\n                    y = parseInt(img.style.top);\r\n                } else {\r\n                    x = (picContainer.offsetWidth - img.width) / 2;\r\n                    y = (picContainer.offsetHeight - img.height) / 2;\r\n                }\r\n                context.globalCompositeOperation = \"destination-over\";\r\n                context.drawImage(img, x, y, img.width, img.height);\r\n            } else {\r\n                context.globalCompositeOperation = \"destination-atop\";\r\n                context.fillStyle = \"#fff\";//重置画布背景白色\r\n                context.fillRect(0, 0, canvas.width, canvas.height);\r\n            }\r\n            try {\r\n                return canvas.toDataURL(\"image/png\").substring(22);\r\n            } catch (e) {\r\n                return \"\";\r\n            }\r\n        },\r\n        btn2Highlight:function (id) {\r\n            var cur = $G(id);\r\n            cur.className.indexOf(\"H\") == -1 && (cur.className += \"H\");\r\n        },\r\n        btn2disable:function (id) {\r\n            var cur = $G(id);\r\n            cur.className.indexOf(\"H\") != -1 && (cur.className = cur.className.replace(\"H\", \"\"));\r\n        },\r\n        getTarget:function (evt) {\r\n            return evt.target || evt.srcElement;\r\n        }\r\n    };\r\n})();\r\n\r\nvar ScaleBoy = function () {\r\n    this.dom = null;\r\n    this.scalingElement = null;\r\n};\r\n(function () {\r\n    function _appendStyle() {\r\n        var doc = document,\r\n            head = doc.getElementsByTagName('head')[0],\r\n            style = doc.createElement('style'),\r\n            cssText = '.scale{visibility:hidden;cursor:move;position:absolute;left:0;top:0;width:100px;height:50px;background-color:#fff;font-size:0;line-height:0;opacity:.4;filter:Alpha(opacity=40);}'\r\n                + '.scale span{position:absolute;left:0;top:0;width:6px;height:6px;background-color:#006DAE;}'\r\n                + '.scale .hand0, .scale .hand7{cursor:nw-resize;}'\r\n                + '.scale .hand1, .scale .hand6{left:50%;margin-left:-3px;cursor:n-resize;}'\r\n                + '.scale .hand2, .scale .hand4, .scale .hand7{left:100%;margin-left:-6px;}'\r\n                + '.scale .hand3, .scale .hand4{top:50%;margin-top:-3px;cursor:w-resize;}'\r\n                + '.scale .hand5, .scale .hand6, .scale .hand7{margin-top:-6px;top:100%;}'\r\n                + '.scale .hand2, .scale .hand5{cursor:ne-resize;}';\r\n        style.type = 'text/css';\r\n\r\n        try {\r\n            style.appendChild(doc.createTextNode(cssText));\r\n        } catch (e) {\r\n            style.styleSheet.cssText = cssText;\r\n        }\r\n        head.appendChild(style);\r\n    }\r\n\r\n    function _getDom() {\r\n        var doc = document,\r\n            hand,\r\n            arr = [],\r\n            scale = doc.createElement('div');\r\n\r\n        scale.id = 'J_scaleCon';\r\n        scale.className = 'scale';\r\n        for (var i = 0; i < 8; i++) {\r\n            arr.push(\"<span class='hand\" + i + \"'></span>\");\r\n        }\r\n        scale.innerHTML = arr.join(\"\");\r\n        return scale;\r\n    }\r\n\r\n    var rect = [\r\n        //[left, top, width, height]\r\n        [1, 1, -1, -1],\r\n        [0, 1, 0, -1],\r\n        [0, 1, 1, -1],\r\n        [1, 0, -1, 0],\r\n        [0, 0, 1, 0],\r\n        [1, 0, -1, 1],\r\n        [0, 0, 0, 1],\r\n        [0, 0, 1, 1]\r\n    ];\r\n    ScaleBoy.prototype = {\r\n        init:function () {\r\n            _appendStyle();\r\n            var me = this,\r\n                scale = me.dom = _getDom();\r\n\r\n            me.scaleMousemove.fp = me;\r\n            domUtils.on(scale, 'mousedown', function (e) {\r\n                var target = e.target || e.srcElement;\r\n                me.start = {x:e.clientX, y:e.clientY};\r\n                if (target.className.indexOf('hand') != -1) {\r\n                    me.dir = target.className.replace('hand', '');\r\n                }\r\n                domUtils.on(document.body, 'mousemove', me.scaleMousemove);\r\n                e.stopPropagation ? e.stopPropagation() : e.cancelBubble = true;\r\n            });\r\n            domUtils.on(document.body, 'mouseup', function (e) {\r\n                if (me.start) {\r\n                    domUtils.un(document.body, 'mousemove', me.scaleMousemove);\r\n                    if (me.moved) {\r\n                        me.updateScaledElement({position:{x:scale.style.left, y:scale.style.top}, size:{w:scale.style.width, h:scale.style.height}});\r\n                    }\r\n                    delete me.start;\r\n                    delete me.moved;\r\n                    delete me.dir;\r\n                }\r\n            });\r\n            return scale;\r\n        },\r\n        startScale:function (objElement) {\r\n            var me = this, Idom = me.dom;\r\n\r\n            Idom.style.cssText = 'visibility:visible;top:' + objElement.style.top + ';left:' + objElement.style.left + ';width:' + objElement.offsetWidth + 'px;height:' + objElement.offsetHeight + 'px;';\r\n            me.scalingElement = objElement;\r\n        },\r\n        updateScaledElement:function (objStyle) {\r\n            var cur = this.scalingElement,\r\n                pos = objStyle.position,\r\n                size = objStyle.size;\r\n            if (pos) {\r\n                typeof pos.x != 'undefined' && (cur.style.left = pos.x);\r\n                typeof pos.y != 'undefined' && (cur.style.top = pos.y);\r\n            }\r\n            if (size) {\r\n                size.w && (cur.style.width = size.w);\r\n                size.h && (cur.style.height = size.h);\r\n            }\r\n        },\r\n        updateStyleByDir:function (dir, offset) {\r\n            var me = this,\r\n                dom = me.dom, tmp;\r\n\r\n            rect['def'] = [1, 1, 0, 0];\r\n            if (rect[dir][0] != 0) {\r\n                tmp = parseInt(dom.style.left) + offset.x;\r\n                dom.style.left = me._validScaledProp('left', tmp) + 'px';\r\n            }\r\n            if (rect[dir][1] != 0) {\r\n                tmp = parseInt(dom.style.top) + offset.y;\r\n                dom.style.top = me._validScaledProp('top', tmp) + 'px';\r\n            }\r\n            if (rect[dir][2] != 0) {\r\n                tmp = dom.clientWidth + rect[dir][2] * offset.x;\r\n                dom.style.width = me._validScaledProp('width', tmp) + 'px';\r\n            }\r\n            if (rect[dir][3] != 0) {\r\n                tmp = dom.clientHeight + rect[dir][3] * offset.y;\r\n                dom.style.height = me._validScaledProp('height', tmp) + 'px';\r\n            }\r\n            if (dir === 'def') {\r\n                me.updateScaledElement({position:{x:dom.style.left, y:dom.style.top}});\r\n            }\r\n        },\r\n        scaleMousemove:function (e) {\r\n            var me = arguments.callee.fp,\r\n                start = me.start,\r\n                dir = me.dir || 'def',\r\n                offset = {x:e.clientX - start.x, y:e.clientY - start.y};\r\n\r\n            me.updateStyleByDir(dir, offset);\r\n            arguments.callee.fp.start = {x:e.clientX, y:e.clientY};\r\n            arguments.callee.fp.moved = 1;\r\n        },\r\n        _validScaledProp:function (prop, value) {\r\n            var ele = this.dom,\r\n                wrap = $G(\"J_picBoard\");\r\n\r\n            value = isNaN(value) ? 0 : value;\r\n            switch (prop) {\r\n                case 'left':\r\n                    return value < 0 ? 0 : (value + ele.clientWidth) > wrap.clientWidth ? wrap.clientWidth - ele.clientWidth : value;\r\n                case 'top':\r\n                    return value < 0 ? 0 : (value + ele.clientHeight) > wrap.clientHeight ? wrap.clientHeight - ele.clientHeight : value;\r\n                case 'width':\r\n                    return value <= 0 ? 1 : (value + ele.offsetLeft) > wrap.clientWidth ? wrap.clientWidth - ele.offsetLeft : value;\r\n                case 'height':\r\n                    return value <= 0 ? 1 : (value + ele.offsetTop) > wrap.clientHeight ? wrap.clientHeight - ele.offsetTop : value;\r\n            }\r\n        }\r\n    };\r\n})();\r\n\r\n//后台回调\r\nfunction ue_callback(url, state) {\r\n    var doc = document,\r\n        picBorard = $G(\"J_picBoard\"),\r\n        img = doc.createElement(\"img\");\r\n\r\n    //图片缩放\r\n    function scale(img, max, oWidth, oHeight) {\r\n        var width = 0, height = 0, percent, ow = img.width || oWidth, oh = img.height || oHeight;\r\n        if (ow > max || oh > max) {\r\n            if (ow >= oh) {\r\n                if (width = ow - max) {\r\n                    percent = (width / ow).toFixed(2);\r\n                    img.height = oh - oh * percent;\r\n                    img.width = max;\r\n                }\r\n            } else {\r\n                if (height = oh - max) {\r\n                    percent = (height / oh).toFixed(2);\r\n                    img.width = ow - ow * percent;\r\n                    img.height = max;\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    //移除遮罩层\r\n    removeMaskLayer();\r\n    //状态响应\r\n    if (state == \"SUCCESS\") {\r\n        picBorard.innerHTML = \"\";\r\n        img.onload = function () {\r\n            scale(this, 300);\r\n            picBorard.appendChild(img);\r\n\r\n            var obj = new scrawl();\r\n            obj.btn2Highlight(\"J_removeImg\");\r\n            //trace 2457\r\n            obj.btn2Highlight(\"J_sacleBoard\");\r\n        };\r\n        img.src = url;\r\n    } else {\r\n        alert(state);\r\n    }\r\n}\r\n//去掉遮罩层\r\nfunction removeMaskLayer() {\r\n    var maskLayer = $G(\"J_maskLayer\");\r\n    maskLayer.className = \"maskLayerNull\";\r\n    maskLayer.innerHTML = \"\";\r\n    dialog.buttons[0].setDisabled(false);\r\n}\r\n//添加遮罩层\r\nfunction addMaskLayer(html) {\r\n    var maskLayer = $G(\"J_maskLayer\");\r\n    dialog.buttons[0].setDisabled(true);\r\n    maskLayer.className = \"maskLayer\";\r\n    maskLayer.innerHTML = html;\r\n}\r\n//执行确认按钮方法\r\nfunction exec(scrawlObj) {\r\n    if (scrawlObj.isScrawl) {\r\n        addMaskLayer(lang.scrawlUpLoading);\r\n        var base64 = scrawlObj.getCanvasData();\r\n        if (!!base64) {\r\n            var options = {\r\n                timeout:100000,\r\n                onsuccess:function (xhr) {\r\n                    if (!scrawlObj.isCancelScrawl) {\r\n                        var responseObj;\r\n                        responseObj = eval(\"(\" + xhr.responseText + \")\");\r\n                        if (responseObj.state == \"SUCCESS\") {\r\n                            var imgObj = {},\r\n                                url = editor.options.scrawlUrlPrefix + responseObj.url;\r\n                            imgObj.src = url;\r\n                            imgObj._src = url;\r\n                            imgObj.alt = responseObj.original || '';\r\n                            imgObj.title = responseObj.title || '';\r\n                            editor.execCommand(\"insertImage\", imgObj);\r\n                            dialog.close();\r\n                        } else {\r\n                            alert(responseObj.state);\r\n                        }\r\n\r\n                    }\r\n                },\r\n                onerror:function () {\r\n                    alert(lang.imageError);\r\n                    dialog.close();\r\n                }\r\n            };\r\n            options[editor.getOpt('scrawlFieldName')] = base64;\r\n\r\n            var actionUrl = editor.getActionUrl(editor.getOpt('scrawlActionName')),\r\n                params = utils.serializeParam(editor.queryCommandValue('serverparam')) || '',\r\n                url = utils.formatUrl(actionUrl + (actionUrl.indexOf('?') == -1 ? '?':'&') + params);\r\n            ajax.request(url, options);\r\n        }\r\n    } else {\r\n        addMaskLayer(lang.noScarwl + \"&nbsp;&nbsp;&nbsp;<input type='button' value='\" + lang.continueBtn + \"'  onclick='removeMaskLayer()'/>\");\r\n    }\r\n}\r\n\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/searchreplace/searchreplace.html",
    "content": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\r\n        \"http://www.w3.org/TR/html4/loose.dtd\">\r\n<html>\r\n<head>\r\n    <title></title>\r\n    <meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\"/>\r\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n    <style type=\"text/css\">\r\n        .warpper{ position:relative;width: 380px; height: 100%; margin: 10px auto;}\r\n        .tabbody{height: 160px;}\r\n        .tabbody table{width:100%;border-collapse: separate;border-spacing: 3px;}\r\n        .tabbody .panel{width:373px;height:100%;padding-left: 5px;position: absolute;background-color: #fff;}\r\n        .tabbody input.int{ width:190px;height:21px;border:1px solid #d7d7d7;line-height:21px;}\r\n        .tabbody input.btn{padding: 0 5px; text-align:center;line-height:24px; text-decoration: none;height:24px;background:url(\"../../themes/default/images/dialog-title-bg.png\") repeat-x;border:1px solid #ccc; }\r\n    </style>\r\n</head>\r\n<body>\r\n<div class=\"warpper\" id=\"searchtab\">\r\n    <div id=\"head\" class=\"tabhead\">\r\n        <span  tabsrc=\"find\" class=\"focus\"><var id=\"lang_tab_search\"></var></span>\r\n        <span  tabsrc=\"replace\" ><var id=\"lang_tab_replace\"></var></span>\r\n    </div>\r\n    <div class=\"tabbody\">\r\n        <div class=\"panel\" id=\"find\">\r\n            <table>\r\n                <tr>\r\n                    <td width=\"80\"><var id=\"lang_search1\"></var>: </td>\r\n                    <td><input id=\"findtxt\" type=\"text\" class=\"int\" /></td>\r\n                </tr>\r\n                <!--<tr>-->\r\n\r\n                    <!--<td colspan=\"2\"><span style=\"color:red\"><var id=\"lang_searchReg\"></var></span></td>-->\r\n                <!--</tr>-->\r\n                <tr>\r\n                    <td><var id=\"lang_case_sensitive1\"></var></td>\r\n                    <td>\r\n                        <input id=\"matchCase\" type=\"checkbox\" />\r\n                    </td>\r\n                </tr>\r\n                <tr>\r\n                    <td colspan=\"2\">\r\n                        <input id=\"nextFindBtn\" type=\"button\" class=\"btn\" />\r\n                        <input id=\"preFindBtn\" type=\"button\" class=\"btn\" />\r\n                    </td>\r\n                </tr>\r\n                <tr>\r\n                    <td colspan=\"2\">\r\n                        &nbsp;\r\n                    </td>\r\n                </tr>\r\n                <tr>\r\n                    <td colspan=\"2\">\r\n                        <span id=\"search-msg\" style=\"color:red\"></span>\r\n                    </td>\r\n                </tr>\r\n            </table>\r\n        </div>\r\n        <div class=\"panel\" id=\"replace\">\r\n            <table>\r\n                <tr>\r\n                    <td width=\"80\"><var id=\"lang_search2\"></var>: </td>\r\n                    <td><input id=\"findtxt1\" type=\"text\" class=\"int\"  /></td>\r\n                </tr>\r\n                <!--<tr>-->\r\n\r\n                    <!--<td colspan=\"2\"><span style=\"color:red\"><var id=\"lang_searchReg1\"></var></span></td>-->\r\n                <!--</tr>-->\r\n                <tr>\r\n                    <td><var id=\"lang_replace\"></var>: </td>\r\n                    <td><input id=\"replacetxt\" type=\"text\" class=\"int\" /></td>\r\n                </tr>\r\n                <tr>\r\n                    <td><var id=\"lang_case_sensitive2\"></var></td>\r\n                    <td>\r\n                        <input id=\"matchCase1\" type=\"checkbox\" />\r\n                    </td>\r\n                </tr>\r\n                <tr>\r\n                    <td colspan=\"2\">\r\n                        <input id=\"nextReplaceBtn\" type=\"button\" class=\"btn\" />\r\n                        <input id=\"preReplaceBtn\" type=\"button\" class=\"btn\" />\r\n                        <input id=\"repalceBtn\" type=\"button\" class=\"btn\" />\r\n                        <input id=\"repalceAllBtn\" type=\"button\" class=\"btn\" />\r\n                    </td>\r\n                </tr>\r\n                <tr>\r\n                    <td colspan=\"2\">\r\n                        &nbsp;\r\n                    </td>\r\n                </tr>\r\n                <tr>\r\n                    <td colspan=\"2\">\r\n                        <span id=\"replace-msg\" style=\"color:red\"></span>\r\n                    </td>\r\n                </tr>\r\n            </table>\r\n        </div>\r\n    </div>\r\n</div>\r\n<script type=\"text/javascript\" src=\"searchreplace.js\"></script>\r\n</body>\r\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/searchreplace/searchreplace.js",
    "content": "/**\r\n * Created with JetBrains PhpStorm.\r\n * User: xuheng\r\n * Date: 12-9-26\r\n * Time: 下午12:29\r\n * To change this template use File | Settings | File Templates.\r\n */\r\n\r\n//清空上次查选的痕迹\r\neditor.firstForSR = 0;\r\neditor.currentRangeForSR = null;\r\n//给tab注册切换事件\r\n/**\r\n * tab点击处理事件\r\n * @param tabHeads\r\n * @param tabBodys\r\n * @param obj\r\n */\r\nfunction clickHandler( tabHeads,tabBodys,obj ) {\r\n    //head样式更改\r\n    for ( var k = 0, len = tabHeads.length; k < len; k++ ) {\r\n        tabHeads[k].className = \"\";\r\n    }\r\n    obj.className = \"focus\";\r\n    //body显隐\r\n    var tabSrc = obj.getAttribute( \"tabSrc\" );\r\n    for ( var j = 0, length = tabBodys.length; j < length; j++ ) {\r\n        var body = tabBodys[j],\r\n            id = body.getAttribute( \"id\" );\r\n        if ( id != tabSrc ) {\r\n            body.style.zIndex = 1;\r\n        } else {\r\n            body.style.zIndex = 200;\r\n        }\r\n    }\r\n\r\n}\r\n\r\n/**\r\n * TAB切换\r\n * @param tabParentId  tab的父节点ID或者对象本身\r\n */\r\nfunction switchTab( tabParentId ) {\r\n    var tabElements = $G( tabParentId ).children,\r\n        tabHeads = tabElements[0].children,\r\n        tabBodys = tabElements[1].children;\r\n\r\n    for ( var i = 0, length = tabHeads.length; i < length; i++ ) {\r\n        var head = tabHeads[i];\r\n        if ( head.className === \"focus\" )clickHandler(tabHeads,tabBodys, head );\r\n        head.onclick = function () {\r\n            clickHandler(tabHeads,tabBodys,this);\r\n        }\r\n    }\r\n}\r\n$G('searchtab').onmousedown = function(){\r\n    $G('search-msg').innerHTML = '';\r\n    $G('replace-msg').innerHTML = ''\r\n}\r\n//是否区分大小写\r\nfunction getMatchCase(id) {\r\n    return $G(id).checked ? true : false;\r\n}\r\n//查找\r\n$G(\"nextFindBtn\").onclick = function (txt, dir, mcase) {\r\n    var findtxt = $G(\"findtxt\").value, obj;\r\n    if (!findtxt) {\r\n        return false;\r\n    }\r\n    obj = {\r\n        searchStr:findtxt,\r\n        dir:1,\r\n        casesensitive:getMatchCase(\"matchCase\")\r\n    };\r\n    if (!frCommond(obj)) {\r\n        var bk = editor.selection.getRange().createBookmark();\r\n        $G('search-msg').innerHTML = lang.getEnd;\r\n        editor.selection.getRange().moveToBookmark(bk).select();\r\n\r\n\r\n    }\r\n};\r\n$G(\"nextReplaceBtn\").onclick = function (txt, dir, mcase) {\r\n    var findtxt = $G(\"findtxt1\").value, obj;\r\n    if (!findtxt) {\r\n        return false;\r\n    }\r\n    obj = {\r\n        searchStr:findtxt,\r\n        dir:1,\r\n        casesensitive:getMatchCase(\"matchCase1\")\r\n    };\r\n    frCommond(obj);\r\n};\r\n$G(\"preFindBtn\").onclick = function (txt, dir, mcase) {\r\n    var findtxt = $G(\"findtxt\").value, obj;\r\n    if (!findtxt) {\r\n        return false;\r\n    }\r\n    obj = {\r\n        searchStr:findtxt,\r\n        dir:-1,\r\n        casesensitive:getMatchCase(\"matchCase\")\r\n    };\r\n    if (!frCommond(obj)) {\r\n        $G('search-msg').innerHTML = lang.getStart;\r\n    }\r\n};\r\n$G(\"preReplaceBtn\").onclick = function (txt, dir, mcase) {\r\n    var findtxt = $G(\"findtxt1\").value, obj;\r\n    if (!findtxt) {\r\n        return false;\r\n    }\r\n    obj = {\r\n        searchStr:findtxt,\r\n        dir:-1,\r\n        casesensitive:getMatchCase(\"matchCase1\")\r\n    };\r\n    frCommond(obj);\r\n};\r\n//替换\r\n$G(\"repalceBtn\").onclick = function () {\r\n    var findtxt = $G(\"findtxt1\").value.replace(/^\\s|\\s$/g, \"\"), obj,\r\n        replacetxt = $G(\"replacetxt\").value.replace(/^\\s|\\s$/g, \"\");\r\n    if (!findtxt) {\r\n        return false;\r\n    }\r\n    if (findtxt == replacetxt || (!getMatchCase(\"matchCase1\") && findtxt.toLowerCase() == replacetxt.toLowerCase())) {\r\n        return false;\r\n    }\r\n    obj = {\r\n        searchStr:findtxt,\r\n        dir:1,\r\n        casesensitive:getMatchCase(\"matchCase1\"),\r\n        replaceStr:replacetxt\r\n    };\r\n    frCommond(obj);\r\n};\r\n//全部替换\r\n$G(\"repalceAllBtn\").onclick = function () {\r\n    var findtxt = $G(\"findtxt1\").value.replace(/^\\s|\\s$/g, \"\"), obj,\r\n        replacetxt = $G(\"replacetxt\").value.replace(/^\\s|\\s$/g, \"\");\r\n    if (!findtxt) {\r\n        return false;\r\n    }\r\n    if (findtxt == replacetxt || (!getMatchCase(\"matchCase1\") && findtxt.toLowerCase() == replacetxt.toLowerCase())) {\r\n        return false;\r\n    }\r\n    obj = {\r\n        searchStr:findtxt,\r\n        casesensitive:getMatchCase(\"matchCase1\"),\r\n        replaceStr:replacetxt,\r\n        all:true\r\n    };\r\n    var num = frCommond(obj);\r\n    if (num) {\r\n        $G('replace-msg').innerHTML = lang.countMsg.replace(\"{#count}\", num);\r\n    }\r\n};\r\n//执行\r\nvar frCommond = function (obj) {\r\n    return editor.execCommand(\"searchreplace\", obj);\r\n};\r\nswitchTab(\"searchtab\");"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/snapscreen/snapscreen.html",
    "content": "<!DOCTYPE HTML>\r\n<html>\r\n    <head>\r\n        <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\r\n        <title></title>\r\n        <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n        <style type=\"text/css\">\r\n            *{color: #838383}\r\n            html,body {\r\n                font-size: 12px;\r\n                width:100%;\r\n                height:100%;\r\n                overflow: hidden;\r\n                margin:0px;\r\n                padding:0px;\r\n            }\r\n            h2 { font-size: 16px; margin: 20px auto;}\r\n            .content{\r\n                padding:5px 15px 0 15px;\r\n                height:100%;\r\n            }\r\n            dt,dd { margin-left: 0; padding-left: 0;}\r\n            dt a { display: block;\r\n                    height: 30px;\r\n                    line-height: 30px;\r\n                    width: 55px;\r\n                    background: #EFEFEF;\r\n                    border: 1px solid #CCC;\r\n                    padding: 0 10px;\r\n                    text-decoration: none;\r\n            }\r\n            dt a:hover{\r\n                background: #e0e0e0;\r\n                border-color: #999\r\n            }\r\n            dt a:active{\r\n                background: #ccc;\r\n                border-color: #999;\r\n                color: #666;\r\n            }\r\n            dd { line-height:20px;margin-top: 10px;}\r\n            span{ padding-right:4px;}\r\n            input{width:210px;height:21px;background: #FFF;border:1px solid #d7d7d7;padding: 0px; margin: 0px; }\r\n\r\n\r\n        </style>\r\n    </head>\r\n    <body>\r\n        <div class=\"content\">\r\n            <h2><var id=\"lang_showMsg\"></var></h2>\r\n            <dl>\r\n                <dt><a href=\"../../third-party/snapscreen/UEditorSnapscreen.exe\" target=\"_blank\" id=\"downlink\"><var id=\"lang_download\"></var></a></dt>\r\n                <dd><var id=\"lang_step1\"></var></dd>\r\n                <dd><var id=\"lang_step2\"></var></dd>\r\n            </dl>\r\n        </div>\r\n    </body>\r\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/spechars/spechars.html",
    "content": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\r\n        \"http://www.w3.org/TR/html4/loose.dtd\">\r\n<html>\r\n<head>\r\n    <title></title>\r\n    <meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"/>\r\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n    <style type=\"text/css\">\r\n        html,body{overflow:hidden;}\r\n        #specharsTab{width: 97%;margin: 10px auto; zoom:1;position: relative}\r\n        .tabbody {height:447px;}\r\n        .tabbody span{ margin: 5px 3px;text-align: center;display:inline-block;width: 40px;height:16px;line-height: 16px;cursor: pointer; }\r\n    </style>\r\n</head>\r\n<body>\r\n    <div id=\"specharsTab\">\r\n        <div id=\"tabHeads\" class=\"tabhead\"></div><div id=\"tabBodys\" class=\"tabbody\"></div>\r\n    </div>\r\n<script type=\"text/javascript\" src=\"spechars.js\"></script>\r\n</body>\r\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/spechars/spechars.js",
    "content": "/**\r\n * Created with JetBrains PhpStorm.\r\n * User: xuheng\r\n * Date: 12-9-26\r\n * Time: 下午1:09\r\n * To change this template use File | Settings | File Templates.\r\n */\r\nvar charsContent = [\r\n    { name:\"tsfh\", title:lang.tsfh, content:toArray(\"、,。,·,ˉ,ˇ,¨,〃,々,—,～,‖,…,‘,’,“,”,〔,〕,〈,〉,《,》,「,」,『,』,〖,〗,【,】,±,×,÷,∶,∧,∨,∑,∏,∪,∩,∈,∷,√,⊥,∥,∠,⌒,⊙,∫,∮,≡,≌,≈,∽,∝,≠,≮,≯,≤,≥,∞,∵,∴,♂,♀,°,′,″,℃,＄,¤,￠,￡,‰,§,№,☆,★,○,●,◎,◇,◆,□,■,△,▲,※,→,←,↑,↓,〓,〡,〢,〣,〤,〥,〦,〧,〨,〩,㊣,㎎,㎏,㎜,㎝,㎞,㎡,㏄,㏎,㏑,㏒,㏕,︰,￢,￤,℡,ˊ,ˋ,˙,–,―,‥,‵,℅,℉,↖,↗,↘,↙,∕,∟,∣,≒,≦,≧,⊿,═,║,╒,╓,╔,╕,╖,╗,╘,╙,╚,╛,╜,╝,╞,╟,╠,╡,╢,╣,╤,╥,╦,╧,╨,╩,╪,╫,╬,╭,╮,╯,╰,╱,╲,╳,▁,▂,▃,▄,▅,▆,▇,�,█,▉,▊,▋,▌,▍,▎,▏,▓,▔,▕,▼,▽,◢,◣,◤,◥,☉,⊕,〒,〝,〞\")},\r\n    { name:\"lmsz\", title:lang.lmsz, content:toArray(\"ⅰ,ⅱ,ⅲ,ⅳ,ⅴ,ⅵ,ⅶ,ⅷ,ⅸ,ⅹ,Ⅰ,Ⅱ,Ⅲ,Ⅳ,Ⅴ,Ⅵ,Ⅶ,Ⅷ,Ⅸ,Ⅹ,Ⅺ,Ⅻ\")},\r\n    { name:\"szfh\", title:lang.szfh, content:toArray(\"⒈,⒉,⒊,⒋,⒌,⒍,⒎,⒏,⒐,⒑,⒒,⒓,⒔,⒕,⒖,⒗,⒘,⒙,⒚,⒛,⑴,⑵,⑶,⑷,⑸,⑹,⑺,⑻,⑼,⑽,⑾,⑿,⒀,⒁,⒂,⒃,⒄,⒅,⒆,⒇,①,②,③,④,⑤,⑥,⑦,⑧,⑨,⑩,㈠,㈡,㈢,㈣,㈤,㈥,㈦,㈧,㈨,㈩\")},\r\n    { name:\"rwfh\", title:lang.rwfh, content:toArray(\"ぁ,あ,ぃ,い,ぅ,う,ぇ,え,ぉ,お,か,が,き,ぎ,く,ぐ,け,げ,こ,ご,さ,ざ,し,じ,す,ず,せ,ぜ,そ,ぞ,た,だ,ち,ぢ,っ,つ,づ,て,で,と,ど,な,に,ぬ,ね,の,は,ば,ぱ,ひ,び,ぴ,ふ,ぶ,ぷ,へ,べ,ぺ,ほ,ぼ,ぽ,ま,み,む,め,も,ゃ,や,ゅ,ゆ,ょ,よ,ら,り,る,れ,ろ,ゎ,わ,ゐ,ゑ,を,ん,ァ,ア,ィ,イ,ゥ,ウ,ェ,エ,ォ,オ,カ,ガ,キ,ギ,ク,グ,ケ,ゲ,コ,ゴ,サ,ザ,シ,ジ,ス,ズ,セ,ゼ,ソ,ゾ,タ,ダ,チ,ヂ,ッ,ツ,ヅ,テ,デ,ト,ド,ナ,ニ,ヌ,ネ,ノ,ハ,バ,パ,ヒ,ビ,ピ,フ,ブ,プ,ヘ,ベ,ペ,ホ,ボ,ポ,マ,ミ,ム,メ,モ,ャ,ヤ,ュ,ユ,ョ,ヨ,ラ,リ,ル,レ,ロ,ヮ,ワ,ヰ,ヱ,ヲ,ン,ヴ,ヵ,ヶ\")},\r\n    { name:\"xlzm\", title:lang.xlzm, content:toArray(\"Α,Β,Γ,Δ,Ε,Ζ,Η,Θ,Ι,Κ,Λ,Μ,Ν,Ξ,Ο,Π,Ρ,Σ,Τ,Υ,Φ,Χ,Ψ,Ω,α,β,γ,δ,ε,ζ,η,θ,ι,κ,λ,μ,ν,ξ,ο,π,ρ,σ,τ,υ,φ,χ,ψ,ω\")},\r\n    { name:\"ewzm\", title:lang.ewzm, content:toArray(\"А,Б,В,Г,Д,Е,Ё,Ж,З,И,Й,К,Л,М,Н,О,П,Р,С,Т,У,Ф,Х,Ц,Ч,Ш,Щ,Ъ,Ы,Ь,Э,Ю,Я,а,б,в,г,д,е,ё,ж,з,и,й,к,л,м,н,о,п,р,с,т,у,ф,х,ц,ч,ш,щ,ъ,ы,ь,э,ю,я\")},\r\n    { name:\"pyzm\", title:lang.pyzm, content:toArray(\"ā,á,ǎ,à,ē,é,ě,è,ī,í,ǐ,ì,ō,ó,ǒ,ò,ū,ú,ǔ,ù,ǖ,ǘ,ǚ,ǜ,ü\")},\r\n    { name:\"yyyb\", title:lang.yyyb, content:toArray(\"i:,i,e,æ,ʌ,ə:,ə,u:,u,ɔ:,ɔ,a:,ei,ai,ɔi,əu,au,iə,εə,uə,p,t,k,b,d,g,f,s,ʃ,θ,h,v,z,ʒ,ð,tʃ,tr,ts,dʒ,dr,dz,m,n,ŋ,l,r,w,j,\")},\r\n    { name:\"zyzf\", title:lang.zyzf, content:toArray(\"ㄅ,ㄆ,ㄇ,ㄈ,ㄉ,ㄊ,ㄋ,ㄌ,ㄍ,ㄎ,ㄏ,ㄐ,ㄑ,ㄒ,ㄓ,ㄔ,ㄕ,ㄖ,ㄗ,ㄘ,ㄙ,ㄚ,ㄛ,ㄜ,ㄝ,ㄞ,ㄟ,ㄠ,ㄡ,ㄢ,ㄣ,ㄤ,ㄥ,ㄦ,ㄧ,ㄨ\")}\r\n];\r\n(function createTab(content) {\r\n    for (var i = 0, ci; ci = content[i++];) {\r\n        var span = document.createElement(\"span\");\r\n        span.setAttribute(\"tabSrc\", ci.name);\r\n        span.innerHTML = ci.title;\r\n        if (i == 1)span.className = \"focus\";\r\n        domUtils.on(span, \"click\", function () {\r\n            var tmps = $G(\"tabHeads\").children;\r\n            for (var k = 0, sk; sk = tmps[k++];) {\r\n                sk.className = \"\";\r\n            }\r\n            tmps = $G(\"tabBodys\").children;\r\n            for (var k = 0, sk; sk = tmps[k++];) {\r\n                sk.style.display = \"none\";\r\n            }\r\n            this.className = \"focus\";\r\n            $G(this.getAttribute(\"tabSrc\")).style.display = \"\";\r\n        });\r\n        $G(\"tabHeads\").appendChild(span);\r\n        domUtils.insertAfter(span, document.createTextNode(\"\\n\"));\r\n        var div = document.createElement(\"div\");\r\n        div.id = ci.name;\r\n        div.style.display = (i == 1) ? \"\" : \"none\";\r\n        var cons = ci.content;\r\n        for (var j = 0, con; con = cons[j++];) {\r\n            var charSpan = document.createElement(\"span\");\r\n            charSpan.innerHTML = con;\r\n            domUtils.on(charSpan, \"click\", function () {\r\n                editor.execCommand(\"insertHTML\", this.innerHTML);\r\n                dialog.close();\r\n            });\r\n            div.appendChild(charSpan);\r\n        }\r\n        $G(\"tabBodys\").appendChild(div);\r\n    }\r\n})(charsContent);\r\nfunction toArray(str) {\r\n    return str.split(\",\");\r\n}\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/table/edittable.css",
    "content": "body{\r\n    overflow: hidden;\r\n    width: 540px;\r\n}\r\n.wrapper {\r\n    margin: 10px auto 0;\r\n    font-size: 12px;\r\n    overflow: hidden;\r\n    width: 520px;\r\n    height: 315px;\r\n}\r\n\r\n.clear {\r\n    clear: both;\r\n}\r\n\r\n.wrapper .left {\r\n    float: left;\r\n    margin-left: 10px;;\r\n}\r\n\r\n.wrapper .right {\r\n    float: right;\r\n    border-left: 2px dotted #EDEDED;\r\n    padding-left: 15px;\r\n}\r\n\r\n.section {\r\n    margin-bottom: 15px;\r\n    width: 240px;\r\n    overflow: hidden;\r\n}\r\n\r\n.section h3 {\r\n    font-weight: bold;\r\n    padding: 5px 0;\r\n    margin-bottom: 10px;\r\n    border-bottom: 1px solid #EDEDED;\r\n    font-size: 12px;\r\n}\r\n\r\n.section ul {\r\n    list-style: none;\r\n    overflow: hidden;\r\n    clear: both;\r\n\r\n}\r\n\r\n.section li {\r\n    float: left;\r\n    width: 120px;;\r\n}\r\n\r\n.section .tone {\r\n    width: 80px;;\r\n}\r\n\r\n.section .preview {\r\n    width: 220px;\r\n}\r\n\r\n.section .preview table {\r\n    text-align: center;\r\n    vertical-align: middle;\r\n    color: #666;\r\n}\r\n\r\n.section .preview caption {\r\n    font-weight: bold;\r\n}\r\n\r\n.section .preview td {\r\n    border-width: 1px;\r\n    border-style: solid;\r\n    height: 22px;\r\n}\r\n\r\n.section .preview th {\r\n    border-style: solid;\r\n    border-color: #DDD;\r\n    border-width: 2px 1px 1px 1px;\r\n    height: 22px;\r\n    background-color: #F7F7F7;\r\n}"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/table/edittable.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n    <title></title>\r\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"edittable.css\">\r\n</head>\r\n<body>\r\n<div class=\"wrapper\">\r\n    <div class=\"left\">\r\n        <div class=\"section\">\r\n            <h3><var id=\"lang_tableStyle\"></var></h3>\r\n            <ul>\r\n                <li>\r\n                    <label onselectstart=\"return false\"><input type=\"checkbox\" id=\"J_title\" name=\"style\"/><var id=\"lang_insertTitle\"></var></label>\r\n                </li>\r\n                <li>\r\n                    <label onselectstart=\"return false\"><input type=\"checkbox\" id=\"J_titleCol\" name=\"style\"/><var id=\"lang_insertTitleCol\"></var></label>\r\n                </li>\r\n            </ul>\r\n            <ul>\r\n                <li>\r\n                    <label onselectstart=\"return false\"><input type=\"checkbox\" id=\"J_caption\" name=\"style\"/><var id=\"lang_insertCaption\"></var></label>\r\n                </li>\r\n                <li>\r\n                    <label onselectstart=\"return false\"><input type=\"checkbox\" id=\"J_sorttable\" name=\"style\"/><var id=\"lang_orderbycontent\"></var></label>\r\n                </li>\r\n            </ul>\r\n            <div class=\"clear\"></div>\r\n        </div>\r\n        <div class=\"section\">\r\n            <h3><var id=\"lang_tableSize\"></var></h3>\r\n            <ul>\r\n                <li>\r\n                    <label><input type=\"radio\" id=\"J_autoSizeContent\" name=\"size\"/><var id=\"lang_autoSizeContent\"></var></label>\r\n                </li>\r\n                <li>\r\n                    <label><input type=\"radio\" id=\"J_autoSizePage\" name=\"size\"/><var id=\"lang_autoSizePage\"></var></label>\r\n                </li>\r\n            </ul>\r\n            <div class=\"clear\"></div>\r\n        </div>\r\n        <div class=\"section\">\r\n            <h3><var id=\"lang_borderStyle\"></var></h3>\r\n            <ul>\r\n                <li>\r\n                    <span><var id=\"lang_color\"></var></span>\r\n                    <input type=\"text\" class=\"tone\" id=\"J_tone\" readonly='readonly' />\r\n                </li>\r\n            </ul>\r\n            <div class=\"clear\"></div>\r\n        </div>\r\n    </div>\r\n    <div class=\"right\">\r\n        <div class=\"section\">\r\n            <h3><var id=\"lang_example\"></var></h3>\r\n            <div class=\"preview\" id=\"J_preview\">\r\n            </div>\r\n        </div>\r\n    </div>\r\n</div>\r\n<script type=\"text/javascript\" src=\"edittable.js\"></script>\r\n</body>\r\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/table/edittable.js",
    "content": "/**\r\n * Created with JetBrains PhpStorm.\r\n * User: xuheng\r\n * Date: 12-12-19\r\n * Time: 下午4:55\r\n * To change this template use File | Settings | File Templates.\r\n */\r\n(function () {\r\n    var title = $G(\"J_title\"),\r\n        titleCol = $G(\"J_titleCol\"),\r\n        caption = $G(\"J_caption\"),\r\n        sorttable = $G(\"J_sorttable\"),\r\n        autoSizeContent = $G(\"J_autoSizeContent\"),\r\n        autoSizePage = $G(\"J_autoSizePage\"),\r\n        tone = $G(\"J_tone\"),\r\n        me,\r\n        preview = $G(\"J_preview\");\r\n\r\n    var editTable = function () {\r\n        me = this;\r\n        me.init();\r\n    };\r\n    editTable.prototype = {\r\n        init:function () {\r\n            var colorPiker = new UE.ui.ColorPicker({\r\n                    editor:editor\r\n                }),\r\n                colorPop = new UE.ui.Popup({\r\n                    editor:editor,\r\n                    content:colorPiker\r\n                });\r\n\r\n            title.checked = editor.queryCommandState(\"inserttitle\") == -1;\r\n            titleCol.checked = editor.queryCommandState(\"inserttitlecol\") == -1;\r\n            caption.checked = editor.queryCommandState(\"insertcaption\") == -1;\r\n            sorttable.checked = editor.queryCommandState(\"enablesort\") == 1;\r\n\r\n            var enablesortState = editor.queryCommandState(\"enablesort\"),\r\n                disablesortState = editor.queryCommandState(\"disablesort\");\r\n\r\n            sorttable.checked = !!(enablesortState < 0 && disablesortState >=0);\r\n            sorttable.disabled = !!(enablesortState < 0 && disablesortState < 0);\r\n            sorttable.title = enablesortState < 0 && disablesortState < 0 ? lang.errorMsg:'';\r\n\r\n            me.createTable(title.checked, titleCol.checked, caption.checked);\r\n            me.setAutoSize();\r\n            me.setColor(me.getColor());\r\n\r\n            domUtils.on(title, \"click\", me.titleHanler);\r\n            domUtils.on(titleCol, \"click\", me.titleColHanler);\r\n            domUtils.on(caption, \"click\", me.captionHanler);\r\n            domUtils.on(sorttable, \"click\", me.sorttableHanler);\r\n            domUtils.on(autoSizeContent, \"click\", me.autoSizeContentHanler);\r\n            domUtils.on(autoSizePage, \"click\", me.autoSizePageHanler);\r\n\r\n            domUtils.on(tone, \"click\", function () {\r\n                colorPop.showAnchor(tone);\r\n            });\r\n            domUtils.on(document, 'mousedown', function () {\r\n                colorPop.hide();\r\n            });\r\n            colorPiker.addListener(\"pickcolor\", function () {\r\n                me.setColor(arguments[1]);\r\n                colorPop.hide();\r\n            });\r\n            colorPiker.addListener(\"picknocolor\", function () {\r\n                me.setColor(\"\");\r\n                colorPop.hide();\r\n            });\r\n        },\r\n\r\n        createTable:function (hasTitle, hasTitleCol, hasCaption) {\r\n            var arr = [],\r\n                sortSpan = '<span>^</span>';\r\n            arr.push(\"<table id='J_example'>\");\r\n            if (hasCaption) {\r\n                arr.push(\"<caption>\" + lang.captionName + \"</caption>\")\r\n            }\r\n            if (hasTitle) {\r\n                arr.push(\"<tr>\");\r\n                if(hasTitleCol) { arr.push(\"<th>\" + lang.titleName + \"</th>\"); }\r\n                for (var j = 0; j < 5; j++) {\r\n                    arr.push(\"<th>\" + lang.titleName + \"</th>\");\r\n                }\r\n                arr.push(\"</tr>\");\r\n            }\r\n            for (var i = 0; i < 6; i++) {\r\n                arr.push(\"<tr>\");\r\n                if(hasTitleCol) { arr.push(\"<th>\" + lang.titleName + \"</th>\") }\r\n                for (var k = 0; k < 5; k++) {\r\n                    arr.push(\"<td>\" + lang.cellsName + \"</td>\")\r\n                }\r\n                arr.push(\"</tr>\");\r\n            }\r\n            arr.push(\"</table>\");\r\n            preview.innerHTML = arr.join(\"\");\r\n            this.updateSortSpan();\r\n        },\r\n        titleHanler:function () {\r\n            var example = $G(\"J_example\"),\r\n                frg=document.createDocumentFragment(),\r\n                color = domUtils.getComputedStyle(domUtils.getElementsByTagName(example, \"td\")[0], \"border-color\"),\r\n                colCount = example.rows[0].children.length;\r\n\r\n            if (title.checked) {\r\n                example.insertRow(0);\r\n                for (var i = 0, node; i < colCount; i++) {\r\n                    node = document.createElement(\"th\");\r\n                    node.innerHTML = lang.titleName;\r\n                    frg.appendChild(node);\r\n                }\r\n                example.rows[0].appendChild(frg);\r\n\r\n            } else {\r\n                domUtils.remove(example.rows[0]);\r\n            }\r\n            me.setColor(color);\r\n            me.updateSortSpan();\r\n        },\r\n        titleColHanler:function () {\r\n            var example = $G(\"J_example\"),\r\n                color = domUtils.getComputedStyle(domUtils.getElementsByTagName(example, \"td\")[0], \"border-color\"),\r\n                colArr = example.rows,\r\n                colCount = colArr.length;\r\n\r\n            if (titleCol.checked) {\r\n                for (var i = 0, node; i < colCount; i++) {\r\n                    node = document.createElement(\"th\");\r\n                    node.innerHTML = lang.titleName;\r\n                    colArr[i].insertBefore(node, colArr[i].children[0]);\r\n                }\r\n            } else {\r\n                for (var i = 0; i < colCount; i++) {\r\n                    domUtils.remove(colArr[i].children[0]);\r\n                }\r\n            }\r\n            me.setColor(color);\r\n            me.updateSortSpan();\r\n        },\r\n        captionHanler:function () {\r\n            var example = $G(\"J_example\");\r\n            if (caption.checked) {\r\n                var row = document.createElement('caption');\r\n                row.innerHTML = lang.captionName;\r\n                example.insertBefore(row, example.firstChild);\r\n            } else {\r\n                domUtils.remove(domUtils.getElementsByTagName(example, 'caption')[0]);\r\n            }\r\n        },\r\n        sorttableHanler:function(){\r\n            me.updateSortSpan();\r\n        },\r\n        autoSizeContentHanler:function () {\r\n            var example = $G(\"J_example\");\r\n            example.removeAttribute(\"width\");\r\n        },\r\n        autoSizePageHanler:function () {\r\n            var example = $G(\"J_example\");\r\n            var tds = example.getElementsByTagName(example, \"td\");\r\n            utils.each(tds, function (td) {\r\n                td.removeAttribute(\"width\");\r\n            });\r\n            example.setAttribute('width', '100%');\r\n        },\r\n        updateSortSpan: function(){\r\n            var example = $G(\"J_example\"),\r\n                row = example.rows[0];\r\n\r\n            var spans = domUtils.getElementsByTagName(example,\"span\");\r\n            utils.each(spans,function(span){\r\n                span.parentNode.removeChild(span);\r\n            });\r\n            if (sorttable.checked) {\r\n                utils.each(row.cells, function(cell, i){\r\n                    var span = document.createElement(\"span\");\r\n                    span.innerHTML = \"^\";\r\n                    cell.appendChild(span);\r\n                });\r\n            }\r\n        },\r\n        getColor:function () {\r\n            var start = editor.selection.getStart(), color,\r\n                cell = domUtils.findParentByTagName(start, [\"td\", \"th\", \"caption\"], true);\r\n            color = cell && domUtils.getComputedStyle(cell, \"border-color\");\r\n            if (!color)  color = \"#DDDDDD\";\r\n            return color;\r\n        },\r\n        setColor:function (color) {\r\n            var example = $G(\"J_example\"),\r\n                arr = domUtils.getElementsByTagName(example, \"td\").concat(\r\n                    domUtils.getElementsByTagName(example, \"th\"),\r\n                    domUtils.getElementsByTagName(example, \"caption\")\r\n                );\r\n\r\n            tone.value = color;\r\n            utils.each(arr, function (node) {\r\n                node.style.borderColor = color;\r\n            });\r\n\r\n        },\r\n        setAutoSize:function () {\r\n            var me = this;\r\n            autoSizePage.checked = true;\r\n            me.autoSizePageHanler();\r\n        }\r\n    };\r\n\r\n    new editTable;\r\n\r\n    dialog.onok = function () {\r\n        editor.__hasEnterExecCommand = true;\r\n\r\n        var checks = {\r\n            title:\"inserttitle deletetitle\",\r\n            titleCol:\"inserttitlecol deletetitlecol\",\r\n            caption:\"insertcaption deletecaption\",\r\n            sorttable:\"enablesort disablesort\"\r\n        };\r\n        editor.fireEvent('saveScene');\r\n        for(var i in checks){\r\n            var cmds = checks[i].split(\" \"),\r\n                input = $G(\"J_\" + i);\r\n            if(input[\"checked\"]){\r\n                editor.queryCommandState(cmds[0])!=-1 &&editor.execCommand(cmds[0]);\r\n            }else{\r\n                editor.queryCommandState(cmds[1])!=-1 &&editor.execCommand(cmds[1]);\r\n            }\r\n        }\r\n\r\n        editor.execCommand(\"edittable\", tone.value);\r\n        autoSizeContent.checked ?editor.execCommand('adaptbytext') : \"\";\r\n        autoSizePage.checked ? editor.execCommand(\"adaptbywindow\") : \"\";\r\n        editor.fireEvent('saveScene');\r\n\r\n        editor.__hasEnterExecCommand = false;\r\n    };\r\n})();"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/table/edittd.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n    <title></title>\r\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n    <style type=\"text/css\">\r\n        .section {\r\n            text-align: center;\r\n            margin-top: 10px;\r\n        }\r\n        .section input {\r\n            margin-left: 5px;\r\n            width: 70px;\r\n        }\r\n    </style>\r\n</head>\r\n<body>\r\n<div class=\"section\">\r\n    <span><var id=\"lang_tdBkColor\"></var></span>\r\n    <input type=\"text\" id=\"J_tone\"/>\r\n</div>\r\n<script type=\"text/javascript\">\r\n    var tone = $G(\"J_tone\"),\r\n            colorPiker = new UE.ui.ColorPicker({\r\n                editor:editor\r\n            }),\r\n            colorPop = new UE.ui.Popup({\r\n                editor:editor,\r\n                content:colorPiker\r\n            });\r\n    domUtils.on(tone, \"click\", function () {\r\n        colorPop.showAnchor(tone);\r\n    });\r\n    domUtils.on(document, 'mousedown', function () {\r\n        colorPop.hide();\r\n    });\r\n    colorPiker.addListener(\"pickcolor\", function () {\r\n        tone.value = arguments[1];\r\n        colorPop.hide();\r\n    });\r\n    colorPiker.addListener(\"picknocolor\", function () {\r\n        tone.value=\"\";\r\n        colorPop.hide();\r\n    });\r\n    dialog.onok=function(){\r\n        editor.execCommand(\"edittd\",tone.value);\r\n    };\r\n\r\n    var start = editor.selection.getStart(),\r\n        cell = start && domUtils.findParentByTagName(start, [\"td\", \"th\"], true);\r\n    if(cell){\r\n        var color = domUtils.getComputedStyle(cell,'background-color');\r\n        if(/^#/.test(color)){\r\n            tone.value = color\r\n        }\r\n\r\n    }\r\n\r\n</script>\r\n</body>\r\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/table/edittip.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n    <title>表格删除提示</title>\r\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n    <style type=\"text/css\">\r\n        .section {\r\n            width: 200px;\r\n            margin: 10px auto 0;\r\n            font-size: 14px;\r\n        }\r\n\r\n        .item {\r\n            text-align: center;\r\n        }\r\n    </style>\r\n</head>\r\n<body>\r\n<div class=\"section\">\r\n    <div class=\"item\">\r\n        <label><input type=\"radio\" id=\"J_delRow\" name=\"cmd\" checked/><var id=\"lang_delRow\"></var></label>\r\n    </div>\r\n    <div class=\"item\">\r\n        <label><input type=\"radio\" id=\"J_delCol\" name=\"cmd\"/><var id=\"lang_delCol\"></var></label>\r\n    </div>\r\n</div>\r\n<script type=\"text/javascript\">\r\n    dialog.onok = function () {\r\n        $G(\"J_delRow\").checked ? editor.execCommand(\"deleterow\") : editor.execCommand(\"deletecol\");\r\n    };\r\n</script>\r\n</body>\r\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/template/config.js",
    "content": "/**\r\n * Created with JetBrains PhpStorm.\r\n * User: xuheng\r\n * Date: 12-8-8\r\n * Time: 下午2:00\r\n * To change this template use File | Settings | File Templates.\r\n */\r\nvar templates = [\r\n    {\r\n        \"pre\":\"pre0.png\",\r\n        'title':lang.blank,\r\n        'preHtml':'<p class=\"ue_t\">&nbsp;欢迎使用UEditor！</p>',\r\n        \"html\":'<p class=\"ue_t\">欢迎使用UEditor！</p>'\r\n\r\n    },\r\n    {\r\n        \"pre\":\"pre1.png\",\r\n        'title':lang.blog,\r\n        'preHtml':'<h1 label=\"Title center\" name=\"tc\" style=\"border-bottom-color:#cccccc;border-bottom-width:2px;border-bottom-style:solid;padding:0px 4px 0px 0px;text-align:center;margin:0px 0px 20px;\"><span style=\"color:#c0504d;\">深入理解Range</span></h1><p style=\"text-align:center;\"><strong class=\" \">UEditor二次开发</strong></p><h3><span class=\" \" style=\"font-family:幼圆\">什么是Range</span></h3><p style=\"text-indent:2em;\">对于“插入”选项卡上的库，在设计时都充分考虑了其中的项与文档整体外观的协调性。 </p><br /><h3><span class=\" \" style=\"font-family:幼圆\">Range能干什么</span></h3><p style=\"text-indent:2em;\">在“开始”选项卡上，通过从快速样式库中为所选文本选择一种外观，您可以方便地更改文档中所选文本的格式。</p>',\r\n        \"html\":'<h1 class=\"ue_t\" label=\"Title center\" name=\"tc\" style=\"border-bottom-color:#cccccc;border-bottom-width:2px;border-bottom-style:solid;padding:0px 4px 0px 0px;text-align:center;margin:0px 0px 20px;\"><span style=\"color:#c0504d;\">[键入文档标题]</span></h1><p style=\"text-align:center;\"><strong class=\"ue_t\">[键入文档副标题]</strong></p><h3><span class=\"ue_t\" style=\"font-family:幼圆\">[标题 1]</span></h3><p class=\"ue_t\"  style=\"text-indent:2em;\">对于“插入”选项卡上的库，在设计时都充分考虑了其中的项与文档整体外观的协调性。 您可以使用这些库来插入表格、页眉、页脚、列表、封面以及其他文档构建基块。 您创建的图片、图表或关系图也将与当前的文档外观协调一致。</p><h3><span class=\"ue_t\" style=\"font-family:幼圆\">[标题 2]</span></h3><p class=\"ue_t\"  style=\"text-indent:2em;\">在“开始”选项卡上，通过从快速样式库中为所选文本选择一种外观，您可以方便地更改文档中所选文本的格式。 您还可以使用“开始”选项卡上的其他控件来直接设置文本格式。大多数控件都允许您选择是使用当前主题外观，还是使用某种直接指定的格式。 </p><h3><span class=\"ue_t\" style=\"font-family:幼圆\">[标题 3]</span></h3><p class=\"ue_t\">对于“插入”选项卡上的库，在设计时都充分考虑了其中的项与文档整体外观的协调性。 您可以使用这些库来插入表格、页眉、页脚、列表、封面以及其他文档构建基块。 您创建的图片、图表或关系图也将与当前的文档外观协调一致。</p><p class=\"ue_t\"><br /></p>'\r\n\r\n    },\r\n    {\r\n        \"pre\":\"pre2.png\",\r\n        'title':lang.resume,\r\n        'preHtml':'<h1 label=\"Title left\" name=\"tl\" style=\"border-bottom-color:#cccccc;border-bottom-width:2px;border-bottom-style:solid;padding:0px 4px 0px 0px;margin:0px 0px 10px;\"><span style=\"color:#e36c09;\" class=\" \">WEB前端开发简历</span></h1><table width=\"100%\" border=\"1\" bordercolor=\"#95B3D7\" style=\"border-collapse:collapse;\"><tbody><tr><td width=\"100\" style=\"text-align:center;\"><p><span style=\"background-color:transparent;\">插</span><br /></p><p>入</p><p>照</p><p>片</p></td><td><p><span style=\"background-color:transparent;\"> 联系电话：</span><span class=\"ue_t\" style=\"background-color:transparent;\">[键入您的电话]</span><br /></p><p><span style=\"background-color:transparent;\"> 电子邮件：</span><span class=\"ue_t\" style=\"background-color:transparent;\">[键入您的电子邮件地址]</span><br /></p><p><span style=\"background-color:transparent;\"> 家庭住址：</span><span class=\"ue_t\" style=\"background-color:transparent;\">[键入您的地址]</span><br /></p></td></tr></tbody></table><h3><span style=\"color:#E36C09;font-size:20px;\">目标职位</span></h3><p style=\"text-indent:2em;\" class=\" \">WEB前端研发工程师</p><h3><span style=\"color:#e36c09;font-size:20px;\">学历</span></h3><p><span style=\"display:none;line-height:0px;\" id=\"_baidu_bookmark_start_26\">﻿</span></p><ol style=\"list-style-type:decimal;\"><li><p><span class=\"ue_t\">[起止时间]</span> <span class=\"ue_t\">[学校名称] </span> <span class=\"ue_t\">[所学专业]</span> <span class=\"ue_t\">[所获学位]</span></p></li></ol><h3><span style=\"color:#e36c09;font-size:20px;\" class=\"ue_t\">工作经验</span></h3><p><br /></p>',\r\n        \"html\":'<h1 label=\"Title left\" name=\"tl\" style=\"border-bottom-color:#cccccc;border-bottom-width:2px;border-bottom-style:solid;padding:0px 4px 0px 0px;margin:0px 0px 10px;\"><span style=\"color:#e36c09;\" class=\"ue_t\">[此处键入简历标题]</span></h1><p><span style=\"color:#e36c09;\"><br /></span></p><table width=\"100%\" border=\"1\" bordercolor=\"#95B3D7\" style=\"border-collapse:collapse;\"><tbody><tr><td width=\"200\" style=\"text-align:center;\" class=\"ue_t\">【此处插入照片】</td><td><p><br /></p><p> 联系电话：<span class=\"ue_t\">[键入您的电话]</span></p><p><br /></p><p> 电子邮件：<span class=\"ue_t\">[键入您的电子邮件地址]</span></p><p><br /></p><p> 家庭住址：<span class=\"ue_t\">[键入您的地址]</span></p><p><br /></p></td></tr></tbody></table><h3><span style=\"color:#e36c09;font-size:20px;\">目标职位</span></h3><p style=\"text-indent:2em;\" class=\"ue_t\">[此处键入您的期望职位]</p><h3><span style=\"color:#e36c09;font-size:20px;\">学历</span></h3><p><span style=\"display:none;line-height:0px;\" id=\"_baidu_bookmark_start_26\">﻿</span></p><ol style=\"list-style-type:decimal;\"><li><p><span class=\"ue_t\">[键入起止时间]</span> <span class=\"ue_t\">[键入学校名称] </span> <span class=\"ue_t\">[键入所学专业]</span> <span class=\"ue_t\">[键入所获学位]</span></p></li><li><p><span class=\"ue_t\">[键入起止时间]</span> <span class=\"ue_t\">[键入学校名称]</span> <span class=\"ue_t\">[键入所学专业]</span> <span class=\"ue_t\">[键入所获学位]</span></p></li></ol><h3><span style=\"color:#e36c09;font-size:20px;\" class=\"ue_t\">工作经验</span></h3><ol style=\"list-style-type:decimal;\"><li><p><span class=\"ue_t\">[键入起止时间]</span> <span class=\"ue_t\">[键入公司名称]</span> <span class=\"ue_t\">[键入职位名称]</span> </p></li><ol style=\"list-style-type:lower-alpha;\"><li><p><span class=\"ue_t\">[键入负责项目]</span> <span class=\"ue_t\">[键入项目简介]</span></p></li><li><p><span class=\"ue_t\">[键入负责项目]</span> <span class=\"ue_t\">[键入项目简介]</span></p></li></ol><li><p><span class=\"ue_t\">[键入起止时间]</span> <span class=\"ue_t\">[键入公司名称]</span> <span class=\"ue_t\">[键入职位名称]</span> </p></li><ol style=\"list-style-type:lower-alpha;\"><li><p><span class=\"ue_t\">[键入负责项目]</span> <span class=\"ue_t\">[键入项目简介]</span></p></li></ol></ol><p><span style=\"color:#e36c09;font-size:20px;\">掌握技能</span></p><p style=\"text-indent:2em;\"> &nbsp;<span class=\"ue_t\">[这里可以键入您所掌握的技能]</span><br /></p>'\r\n\r\n    },\r\n    {\r\n        \"pre\":\"pre3.png\",\r\n        'title':lang.richText,\r\n        'preHtml':'<h1 label=\"Title center\" name=\"tc\" style=\"border-bottom-color:#cccccc;border-bottom-width:2px;border-bottom-style:solid;padding:0px 4px 0px 0px;text-align:center;margin:0px 0px 20px;\" class=\"ue_t\">[此处键入文章标题]</h1><p><img src=\"http://img.baidu.com/hi/youa/y_0034.gif\" width=\"150\" height=\"100\" border=\"0\" hspace=\"0\" vspace=\"0\" style=\"width:150px;height:100px;float:left;\" />图文混排方法</p><p>图片居左，文字围绕图片排版</p><p>方法：在文字前面插入图片，设置居左对齐，然后即可在右边输入多行文</p><p><br /></p><p><img src=\"http://img.baidu.com/hi/youa/y_0040.gif\" width=\"100\" height=\"100\" border=\"0\" hspace=\"0\" vspace=\"0\" style=\"width:100px;height:100px;float:right;\" /></p><p>还有没有什么其他的环绕方式呢？这里是居右环绕</p><p><br /></p><p>欢迎大家多多尝试，为UEditor提供更多高质量模板！</p>',\r\n        \"html\":'<p><br /></p><h1 label=\"Title center\" name=\"tc\" style=\"border-bottom-color:#cccccc;border-bottom-width:2px;border-bottom-style:solid;padding:0px 4px 0px 0px;text-align:center;margin:0px 0px 20px;\" class=\"ue_t\">[此处键入文章标题]</h1><p><img src=\"http://img.baidu.com/hi/youa/y_0034.gif\" width=\"300\" height=\"200\" border=\"0\" hspace=\"0\" vspace=\"0\" style=\"width:300px;height:200px;float:left;\" />图文混排方法</p><p>1. 图片居左，文字围绕图片排版</p><p>方法：在文字前面插入图片，设置居左对齐，然后即可在右边输入多行文本</p><p><br /></p><p>2. 图片居右，文字围绕图片排版</p><p>方法：在文字前面插入图片，设置居右对齐，然后即可在左边输入多行文本</p><p><br /></p><p>3. 图片居中环绕排版</p><p>方法：亲，这个真心没有办法。。。</p><p><br /></p><p><br /></p><p><img src=\"http://img.baidu.com/hi/youa/y_0040.gif\" width=\"300\" height=\"300\" border=\"0\" hspace=\"0\" vspace=\"0\" style=\"width:300px;height:300px;float:right;\" /></p><p>还有没有什么其他的环绕方式呢？这里是居右环绕</p><p><br /></p><p>欢迎大家多多尝试，为UEditor提供更多高质量模板！</p><p><br /></p><p>占位</p><p><br /></p><p>占位</p><p><br /></p><p>占位</p><p><br /></p><p>占位</p><p><br /></p><p>占位</p><p><br /></p><p><br /></p>'\r\n    },\r\n    {\r\n        \"pre\":\"pre4.png\",\r\n        'title':lang.sciPapers,\r\n        'preHtml':'<h2 style=\"border-bottom-color:#cccccc;border-bottom-width:2px;border-bottom-style:solid;padding:0px 4px 0px 0px;margin:0px 0px 10px;text-align:center;\" class=\"ue_t\">[键入文章标题]</h2><p><strong><span style=\"font-size:12px;\">摘要</span></strong><span style=\"font-size:12px;\" class=\"ue_t\">：这里可以输入很长很长很长很长很长很长很长很长很差的摘要</span></p><p style=\"line-height:1.5em;\"><strong>标题 1</strong></p><p style=\"text-indent:2em;\"><span style=\"font-size:14px;\" class=\"ue_t\">这里可以输入很多内容，可以图文混排，可以有列表等。</span></p><p style=\"line-height:1.5em;\"><strong>标题 2</strong></p><ol style=\"list-style-type:lower-alpha;\"><li><p class=\"ue_t\">列表 1</p></li><li><p class=\"ue_t\">列表 2</p></li><ol style=\"list-style-type:lower-roman;\"><li><p class=\"ue_t\">多级列表 1</p></li><li><p class=\"ue_t\">多级列表 2</p></li></ol><li><p class=\"ue_t\">列表 3<br /></p></li></ol><p style=\"line-height:1.5em;\"><strong>标题 3</strong></p><p style=\"text-indent:2em;\"><span style=\"font-size:14px;\" class=\"ue_t\">来个文字图文混排的</span></p><p style=\"text-indent:2em;\"><br /></p>',\r\n        'html':'<h2 style=\"border-bottom-color:#cccccc;border-bottom-width:2px;border-bottom-style:solid;padding:0px 4px 0px 0px;margin:0px 0px 10px;text-align:center;\" class=\"ue_t\">[键入文章标题]</h2><p><strong><span style=\"font-size:12px;\">摘要</span></strong><span style=\"font-size:12px;\" class=\"ue_t\">：这里可以输入很长很长很长很长很长很长很长很长很差的摘要</span></p><p style=\"line-height:1.5em;\"><strong>标题 1</strong></p><p style=\"text-indent:2em;\"><span style=\"font-size:14px;\" class=\"ue_t\">这里可以输入很多内容，可以图文混排，可以有列表等。</span></p><p style=\"line-height:1.5em;\"><strong>标题 2</strong></p><p style=\"text-indent:2em;\"><span style=\"font-size:14px;\" class=\"ue_t\">来个列表瞅瞅：</span></p><ol style=\"list-style-type:lower-alpha;\"><li><p class=\"ue_t\">列表 1</p></li><li><p class=\"ue_t\">列表 2</p></li><ol style=\"list-style-type:lower-roman;\"><li><p class=\"ue_t\">多级列表 1</p></li><li><p class=\"ue_t\">多级列表 2</p></li></ol><li><p class=\"ue_t\">列表 3<br /></p></li></ol><p style=\"line-height:1.5em;\"><strong>标题 3</strong></p><p style=\"text-indent:2em;\"><span style=\"font-size:14px;\" class=\"ue_t\">来个文字图文混排的</span></p><p style=\"text-indent:2em;\"><span style=\"font-size:14px;\" class=\"ue_t\">这里可以多行</span></p><p style=\"text-indent:2em;\"><span style=\"font-size:14px;\" class=\"ue_t\">右边是图片</span></p><p style=\"text-indent:2em;\"><span style=\"font-size:14px;\" class=\"ue_t\">绝对没有问题的，不信你也可以试试看</span></p><p><br /></p>'\r\n    }\r\n];"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/template/template.css",
    "content": ".wrap{ padding: 5px;font-size: 14px;}\r\n.left{width:425px;float: left;}\r\n.right{width:160px;border: 1px solid #ccc;float: right;padding: 5px;margin-right: 5px;}\r\n.right .pre{height: 332px;overflow-y: auto;}\r\n.right .preitem{border: white 1px solid;margin: 5px 0;padding: 2px 0;}\r\n.right .preitem:hover{background-color: lemonChiffon;cursor: pointer;border: #ccc 1px solid;}\r\n.right .preitem img{display: block;margin: 0 auto;width:100px;}\r\n.clear{clear: both;}\r\n.top{height:26px;line-height: 26px;padding: 5px;}\r\n.bottom{height:320px;width:100%;margin: 0 auto;}\r\n.transparent{ background: url(\"images/bg.gif\") repeat;}\r\n.bottom table tr td{border:1px dashed #ccc;}\r\n#colorPicker{width: 17px;height: 17px;border: 1px solid #CCC;display: inline-block;border-radius: 3px;box-shadow: 2px 2px 5px #D3D6DA;}\r\n.border_style1{padding:2px;border: 1px solid #ccc;border-radius: 5px;box-shadow:2px 2px 5px #d3d6da;}\r\np{margin: 5px 0}\r\ntable{clear:both;margin-bottom:10px;border-collapse:collapse;word-break:break-all;}\r\nli{clear:both}\r\nol{padding-left:40px; }"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/template/template.html",
    "content": "<!DOCTYPE HTML>\r\n<html>\r\n<head>\r\n    <title></title>\r\n    <meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"/>\r\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"template.css\">\r\n</head>\r\n<body>\r\n    <div class=\"wrap\">\r\n        <div class=\"left\">\r\n            <div class=\"top\">\r\n                <label><var id=\"lang_template_clear\"></var>：<input id=\"issave\" type=\"checkbox\"></label>\r\n            </div>\r\n            <div class=\"bottom border_style1\" id=\"preview\"></div>\r\n        </div>\r\n        <fieldset  class=\"right border_style1\">\r\n            <legend><var id=\"lang_template_select\"></var></legend>\r\n            <div class=\"pre\" id=\"preitem\"></div>\r\n        </fieldset>\r\n        <div class=\"clear\"></div>\r\n    </div>\r\n    <script type=\"text/javascript\" src=\"config.js\"></script>\r\n    <script type=\"text/javascript\" src=\"template.js\"></script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/template/template.js",
    "content": "/**\r\n * Created with JetBrains PhpStorm.\r\n * User: xuheng\r\n * Date: 12-8-8\r\n * Time: 下午2:09\r\n * To change this template use File | Settings | File Templates.\r\n */\r\n(function () {\r\n    var me = editor,\r\n            preview = $G( \"preview\" ),\r\n            preitem = $G( \"preitem\" ),\r\n            tmps = templates,\r\n            currentTmp;\r\n    var initPre = function () {\r\n        var str = \"\";\r\n        for ( var i = 0, tmp; tmp = tmps[i++]; ) {\r\n            str += '<div class=\"preitem\" onclick=\"pre(' + i + ')\"><img src=\"' + \"images/\" + tmp.pre + '\" ' + (tmp.title ? \"alt=\" + tmp.title + \" title=\" + tmp.title + \"\" : \"\") + '></div>';\r\n        }\r\n        preitem.innerHTML = str;\r\n    };\r\n    var pre = function ( n ) {\r\n        var tmp = tmps[n - 1];\r\n        currentTmp = tmp;\r\n        clearItem();\r\n        domUtils.setStyles( preitem.childNodes[n - 1], {\r\n            \"background-color\":\"lemonChiffon\",\r\n            \"border\":\"#ccc 1px solid\"\r\n        } );\r\n        preview.innerHTML = tmp.preHtml ? tmp.preHtml : \"\";\r\n    };\r\n    var clearItem = function () {\r\n        var items = preitem.children;\r\n        for ( var i = 0, item; item = items[i++]; ) {\r\n            domUtils.setStyles( item, {\r\n                \"background-color\":\"\",\r\n                \"border\":\"white 1px solid\"\r\n            } );\r\n        }\r\n    };\r\n    dialog.onok = function () {\r\n        if ( !$G( \"issave\" ).checked ){\r\n            me.execCommand( \"cleardoc\" );\r\n        }\r\n        var obj = {\r\n            html:currentTmp && currentTmp.html\r\n        };\r\n        me.execCommand( \"template\", obj );\r\n    };\r\n    initPre();\r\n    window.pre = pre;\r\n    pre(2)\r\n\r\n})();"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/video/video.css",
    "content": "@charset \"utf-8\";\r\n.wrapper{ width: 570px;_width:575px;margin: 10px auto; zoom:1;position: relative}\r\n.tabbody{height: 335px;}\r\n.tabbody .panel {\r\n    position: absolute;\r\n    width: 0;\r\n    height: 0;\r\n    background: #fff;\r\n    overflow: hidden;\r\n    display: none;\r\n}\r\n.tabbody .panel.focus {\r\n    width: 100%;\r\n    height: 335px;\r\n    display: block;\r\n}\r\n\r\n.tabbody .panel table td{vertical-align: middle;}\r\n#videoUrl {\r\n    width: 490px;\r\n    height: 21px;\r\n    line-height: 21px;\r\n    margin: 8px 5px;\r\n    background: #FFF;\r\n    border: 1px solid #d7d7d7;\r\n}\r\n#videoSearchTxt{margin-left:15px;background: #FFF;width:200px;height:21px;line-height:21px;border: 1px solid #d7d7d7;}\r\n#searchList{width: 570px;overflow: auto;zoom:1;height: 270px;}\r\n#searchList div{float: left;width: 120px;height: 135px;margin: 5px 15px;}\r\n#searchList img{margin: 2px 8px;cursor: pointer;border: 2px solid #fff} /*不用缩略图*/\r\n#searchList p{margin-left: 10px;}\r\n#videoType{\r\n    width: 65px;\r\n    height: 23px;\r\n    line-height: 22px;\r\n    border: 1px solid #d7d7d7;\r\n}\r\n#videoSearchBtn,#videoSearchReset{\r\n    /*width: 80px;*/\r\n    height: 25px;\r\n    line-height: 25px;\r\n    background: #eee;\r\n    border: 1px solid #d7d7d7;\r\n    cursor: pointer;\r\n    padding: 0 5px;\r\n}\r\n\r\n\r\n\r\n#preview{position: relative;width: 420px;padding:0;overflow: hidden; margin-left: 10px; _margin-left:5px; height: 280px;background-color: #ddd;float: left}\r\n#preview .previewMsg {position:absolute;top:0;margin:0;padding:0;height:280px;width:100%;background-color: #666;}\r\n#preview .previewMsg span{display:block;margin: 125px auto 0 auto;text-align:center;font-size:18px;color:#fff;}\r\n#preview .previewVideo {position:absolute;top:0;margin:0;padding:0;height:280px;width:100%;}\r\n.edui-video-wrapper fieldset{\r\n    border: 1px solid #ddd;\r\n    padding-left: 5px;\r\n    margin-bottom: 20px;\r\n    padding-bottom: 5px;\r\n    width: 115px;\r\n}\r\n\r\n#videoInfo {width: 120px;float: left;margin-left: 10px;_margin-left:7px;}\r\nfieldset{\r\n    border: 1px solid #ddd;\r\n    padding-left: 5px;\r\n    margin-bottom: 20px;\r\n    padding-bottom: 5px;\r\n    width: 115px;\r\n}\r\nfieldset legend{font-weight: bold;}\r\nfieldset p{line-height: 30px;}\r\nfieldset input.txt{\r\n    width: 65px;\r\n    height: 21px;\r\n    line-height: 21px;\r\n    margin: 8px 5px;\r\n    background: #FFF;\r\n    border: 1px solid #d7d7d7;\r\n}\r\nlabel.url{font-weight: bold;margin-left: 5px;color: #06c;}\r\n#videoFloat div{cursor:pointer;opacity: 0.5;filter: alpha(opacity = 50);margin:9px;_margin:5px;width:38px;height:36px;float:left;}\r\n#videoFloat .focus{opacity: 1;filter: alpha(opacity = 100)}\r\nspan.view{display: inline-block;width: 30px;float: right;cursor: pointer;color: blue}\r\n\r\n\r\n\r\n\r\n/* upload video */\r\n.tabbody #upload.panel {\r\n    width: 0;\r\n    height: 0;\r\n    overflow: hidden;\r\n    position: absolute !important;\r\n    clip: rect(1px, 1px, 1px, 1px);\r\n    background: #fff;\r\n    display: block;\r\n}\r\n.tabbody #upload.panel.focus {\r\n    width: 100%;\r\n    height: 335px;\r\n    display: block;\r\n    clip: auto;\r\n}\r\n#upload_alignment div{cursor:pointer;opacity: 0.5;filter: alpha(opacity = 50);margin:9px;_margin:5px;width:38px;height:36px;float:left;}\r\n#upload_alignment .focus{opacity: 1;filter: alpha(opacity = 100)}\r\n#upload_left { width:427px; float:left; }\r\n#upload_left .controller { height: 30px; clear: both; }\r\n#uploadVideoInfo{margin-top:10px;float:right;padding-right:8px;}\r\n\r\n#upload .queueList {\r\n    margin: 0;\r\n}\r\n\r\n#upload p {\r\n    margin: 0;\r\n}\r\n\r\n.element-invisible {\r\n    width: 0 !important;\r\n    height: 0 !important;\r\n    border: 0;\r\n    padding: 0;\r\n    margin: 0;\r\n    overflow: hidden;\r\n    position: absolute !important;\r\n    clip: rect(1px, 1px, 1px, 1px);\r\n}\r\n\r\n#upload .placeholder {\r\n    margin: 10px;\r\n    margin-right:0;\r\n    border: 2px dashed #e6e6e6;\r\n    *border: 0px dashed #e6e6e6;\r\n    height: 161px;\r\n    padding-top: 150px;\r\n    text-align: center;\r\n    width: 97%;\r\n    float: left;\r\n    background: url(./images/image.png) center 70px no-repeat;\r\n    color: #cccccc;\r\n    font-size: 18px;\r\n    position: relative;\r\n    top:0;\r\n    *margin-left: 0;\r\n    *left: 10px;\r\n}\r\n\r\n#upload .placeholder .webuploader-pick {\r\n    font-size: 18px;\r\n    background: #00b7ee;\r\n    border-radius: 3px;\r\n    line-height: 44px;\r\n    padding: 0 30px;\r\n    *width: 120px;\r\n    color: #fff;\r\n    display: inline-block;\r\n    margin: 0 auto 20px auto;\r\n    cursor: pointer;\r\n    box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);\r\n}\r\n\r\n#upload .placeholder .webuploader-pick-hover {\r\n    background: #00a2d4;\r\n}\r\n\r\n\r\n#filePickerContainer {\r\n    text-align: center;\r\n}\r\n\r\n#upload .placeholder .flashTip {\r\n    color: #666666;\r\n    font-size: 12px;\r\n    position: absolute;\r\n    width: 100%;\r\n    text-align: center;\r\n    bottom: 20px;\r\n}\r\n\r\n#upload .placeholder .flashTip a {\r\n    color: #0785d1;\r\n    text-decoration: none;\r\n}\r\n\r\n#upload .placeholder .flashTip a:hover {\r\n    text-decoration: underline;\r\n}\r\n\r\n#upload .placeholder.webuploader-dnd-over {\r\n    border-color: #999999;\r\n}\r\n\r\n#upload .filelist {\r\n    list-style: none;\r\n    margin: 0;\r\n    padding: 0;\r\n    overflow-x: hidden;\r\n    overflow-y: auto;\r\n    position: relative;\r\n    height: 285px;\r\n}\r\n\r\n#upload .filelist:after {\r\n    content: '';\r\n    display: block;\r\n    width: 0;\r\n    height: 0;\r\n    overflow: hidden;\r\n    clear: both;\r\n}\r\n\r\n#upload .filelist li {\r\n    width: 113px;\r\n    height: 113px;\r\n    background: url(./images/bg.png);\r\n    text-align: center;\r\n    margin: 15px 0 0 20px;\r\n    *margin: 15px 0 0 15px;\r\n    position: relative;\r\n    display: block;\r\n    float: left;\r\n    overflow: hidden;\r\n    font-size: 12px;\r\n}\r\n\r\n#upload .filelist li p.log {\r\n    position: relative;\r\n    top: -45px;\r\n}\r\n\r\n#upload .filelist li p.title {\r\n    position: absolute;\r\n    top: 0;\r\n    left: 0;\r\n    width: 100%;\r\n    overflow: hidden;\r\n    white-space: nowrap;\r\n    text-overflow: ellipsis;\r\n    top: 5px;\r\n    text-indent: 5px;\r\n    text-align: left;\r\n}\r\n\r\n#upload .filelist li p.progress {\r\n    position: absolute;\r\n    width: 100%;\r\n    bottom: 0;\r\n    left: 0;\r\n    height: 8px;\r\n    overflow: hidden;\r\n    z-index: 50;\r\n    margin: 0;\r\n    border-radius: 0;\r\n    background: none;\r\n    -webkit-box-shadow: 0 0 0;\r\n}\r\n\r\n#upload .filelist li p.progress span {\r\n    display: none;\r\n    overflow: hidden;\r\n    width: 0;\r\n    height: 100%;\r\n    background: #1483d8 url(./images/progress.png) repeat-x;\r\n\r\n    -webit-transition: width 200ms linear;\r\n    -moz-transition: width 200ms linear;\r\n    -o-transition: width 200ms linear;\r\n    -ms-transition: width 200ms linear;\r\n    transition: width 200ms linear;\r\n\r\n    -webkit-animation: progressmove 2s linear infinite;\r\n    -moz-animation: progressmove 2s linear infinite;\r\n    -o-animation: progressmove 2s linear infinite;\r\n    -ms-animation: progressmove 2s linear infinite;\r\n    animation: progressmove 2s linear infinite;\r\n\r\n    -webkit-transform: translateZ(0);\r\n}\r\n\r\n@-webkit-keyframes progressmove {\r\n    0% {\r\n        background-position: 0 0;\r\n    }\r\n    100% {\r\n        background-position: 17px 0;\r\n    }\r\n}\r\n\r\n@-moz-keyframes progressmove {\r\n    0% {\r\n        background-position: 0 0;\r\n    }\r\n    100% {\r\n        background-position: 17px 0;\r\n    }\r\n}\r\n\r\n@keyframes progressmove {\r\n    0% {\r\n        background-position: 0 0;\r\n    }\r\n    100% {\r\n        background-position: 17px 0;\r\n    }\r\n}\r\n\r\n#upload .filelist li p.imgWrap {\r\n    position: relative;\r\n    z-index: 2;\r\n    line-height: 113px;\r\n    vertical-align: middle;\r\n    overflow: hidden;\r\n    width: 113px;\r\n    height: 113px;\r\n\r\n    -webkit-transform-origin: 50% 50%;\r\n    -moz-transform-origin: 50% 50%;\r\n    -o-transform-origin: 50% 50%;\r\n    -ms-transform-origin: 50% 50%;\r\n    transform-origin: 50% 50%;\r\n\r\n    -webit-transition: 200ms ease-out;\r\n    -moz-transition: 200ms ease-out;\r\n    -o-transition: 200ms ease-out;\r\n    -ms-transition: 200ms ease-out;\r\n    transition: 200ms ease-out;\r\n}\r\n#upload .filelist li p.imgWrap.notimage {\r\n    margin-top: 0;\r\n    width: 111px;\r\n    height: 111px;\r\n    border: 1px #eeeeee solid;\r\n}\r\n#upload .filelist li p.imgWrap.notimage i.file-preview {\r\n    margin-top: 15px;\r\n}\r\n\r\n#upload .filelist li img {\r\n    width: 100%;\r\n}\r\n\r\n#upload .filelist li p.error {\r\n    background: #f43838;\r\n    color: #fff;\r\n    position: absolute;\r\n    bottom: 0;\r\n    left: 0;\r\n    height: 28px;\r\n    line-height: 28px;\r\n    width: 100%;\r\n    z-index: 100;\r\n    display:none;\r\n}\r\n\r\n#upload .filelist li .success {\r\n    display: block;\r\n    position: absolute;\r\n    left: 0;\r\n    bottom: 0;\r\n    height: 40px;\r\n    width: 100%;\r\n    z-index: 200;\r\n    background: url(./images/success.png) no-repeat right bottom;\r\n    background-image: url(./images/success.gif) \\9;\r\n}\r\n\r\n#upload .filelist li.filePickerBlock {\r\n    width: 113px;\r\n    height: 113px;\r\n    background: url(./images/image.png) no-repeat center 12px;\r\n    border: 1px solid #eeeeee;\r\n    border-radius: 0;\r\n}\r\n#upload .filelist li.filePickerBlock div.webuploader-pick  {\r\n    width: 100%;\r\n    height: 100%;\r\n    margin: 0;\r\n    padding: 0;\r\n    opacity: 0;\r\n    background: none;\r\n    font-size: 0;\r\n}\r\n\r\n#upload .filelist div.file-panel {\r\n    position: absolute;\r\n    height: 0;\r\n    filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr='#80000000', endColorstr='#80000000') \\0;\r\n    background: rgba(0, 0, 0, 0.5);\r\n    width: 100%;\r\n    top: 0;\r\n    left: 0;\r\n    overflow: hidden;\r\n    z-index: 300;\r\n}\r\n\r\n#upload .filelist div.file-panel span {\r\n    width: 24px;\r\n    height: 24px;\r\n    display: inline;\r\n    float: right;\r\n    text-indent: -9999px;\r\n    overflow: hidden;\r\n    background: url(./images/icons.png) no-repeat;\r\n    background: url(./images/icons.gif) no-repeat \\9;\r\n    margin: 5px 1px 1px;\r\n    cursor: pointer;\r\n    -webkit-tap-highlight-color: rgba(0,0,0,0);\r\n    -webkit-user-select: none;\r\n    -moz-user-select: none;\r\n    -ms-user-select: none;\r\n    user-select: none;\r\n}\r\n\r\n#upload .filelist div.file-panel span.rotateLeft {\r\n    display:none;\r\n    background-position: 0 -24px;\r\n}\r\n\r\n#upload .filelist div.file-panel span.rotateLeft:hover {\r\n    background-position: 0 0;\r\n}\r\n\r\n#upload .filelist div.file-panel span.rotateRight {\r\n    display:none;\r\n    background-position: -24px -24px;\r\n}\r\n\r\n#upload .filelist div.file-panel span.rotateRight:hover {\r\n    background-position: -24px 0;\r\n}\r\n\r\n#upload .filelist div.file-panel span.cancel {\r\n    background-position: -48px -24px;\r\n}\r\n\r\n#upload .filelist div.file-panel span.cancel:hover {\r\n    background-position: -48px 0;\r\n}\r\n\r\n#upload .statusBar {\r\n    height: 45px;\r\n    border-bottom: 1px solid #dadada;\r\n    margin: 0 10px;\r\n    padding: 0;\r\n    line-height: 45px;\r\n    vertical-align: middle;\r\n    position: relative;\r\n}\r\n\r\n#upload .statusBar .progress {\r\n    border: 1px solid #1483d8;\r\n    width: 198px;\r\n    background: #fff;\r\n    height: 18px;\r\n    position: absolute;\r\n    top: 12px;\r\n    display: none;\r\n    text-align: center;\r\n    line-height: 18px;\r\n    color: #6dbfff;\r\n    margin: 0 10px 0 0;\r\n}\r\n#upload .statusBar .progress span.percentage {\r\n    width: 0;\r\n    height: 100%;\r\n    left: 0;\r\n    top: 0;\r\n    background: #1483d8;\r\n    position: absolute;\r\n}\r\n#upload .statusBar .progress span.text {\r\n    position: relative;\r\n    z-index: 10;\r\n}\r\n\r\n#upload .statusBar .info {\r\n    display: inline-block;\r\n    font-size: 14px;\r\n    color: #666666;\r\n}\r\n\r\n#upload .statusBar .btns {\r\n    position: absolute;\r\n    top: 7px;\r\n    right: 0;\r\n    line-height: 30px;\r\n}\r\n\r\n#filePickerBtn {\r\n    display: inline-block;\r\n    float: left;\r\n}\r\n#upload .statusBar .btns .webuploader-pick,\r\n#upload .statusBar .btns .uploadBtn,\r\n#upload .statusBar .btns .uploadBtn.state-uploading,\r\n#upload .statusBar .btns .uploadBtn.state-paused {\r\n    background: #ffffff;\r\n    border: 1px solid #cfcfcf;\r\n    color: #565656;\r\n    padding: 0 18px;\r\n    display: inline-block;\r\n    border-radius: 3px;\r\n    margin-left: 10px;\r\n    cursor: pointer;\r\n    font-size: 14px;\r\n    float: left;\r\n    -webkit-user-select: none;\r\n    -moz-user-select: none;\r\n    -ms-user-select: none;\r\n    user-select: none;\r\n}\r\n#upload .statusBar .btns .webuploader-pick-hover,\r\n#upload .statusBar .btns .uploadBtn:hover,\r\n#upload .statusBar .btns .uploadBtn.state-uploading:hover,\r\n#upload .statusBar .btns .uploadBtn.state-paused:hover {\r\n    background: #f0f0f0;\r\n}\r\n\r\n#upload .statusBar .btns .uploadBtn,\r\n#upload .statusBar .btns .uploadBtn.state-paused{\r\n    background: #00b7ee;\r\n    color: #fff;\r\n    border-color: transparent;\r\n}\r\n#upload .statusBar .btns .uploadBtn:hover,\r\n#upload .statusBar .btns .uploadBtn.state-paused:hover{\r\n    background: #00a2d4;\r\n}\r\n\r\n#upload .statusBar .btns .uploadBtn.disabled {\r\n    pointer-events: none;\r\n    filter:alpha(opacity=60);\r\n    -moz-opacity:0.6;\r\n    -khtml-opacity: 0.6;\r\n    opacity: 0.6;\r\n}\r\n\r\n\r\n/* 在线文件的文件预览图标 */\r\ni.file-preview {\r\n    display: block;\r\n    margin: 10px auto;\r\n    width: 70px;\r\n    height: 70px;\r\n    background-image: url(\"./images/file-icons.png\");\r\n    background-image: url(\"./images/file-icons.gif\") \\9;\r\n    background-position: -140px center;\r\n    background-repeat: no-repeat;\r\n}\r\ni.file-preview.file-type-dir{\r\n    background-position: 0 center;\r\n}\r\ni.file-preview.file-type-file{\r\n    background-position: -140px center;\r\n}\r\ni.file-preview.file-type-filelist{\r\n    background-position: -210px center;\r\n}\r\ni.file-preview.file-type-zip,\r\ni.file-preview.file-type-rar,\r\ni.file-preview.file-type-7z,\r\ni.file-preview.file-type-tar,\r\ni.file-preview.file-type-gz,\r\ni.file-preview.file-type-bz2{\r\n    background-position: -280px center;\r\n}\r\ni.file-preview.file-type-xls,\r\ni.file-preview.file-type-xlsx{\r\n    background-position: -350px center;\r\n}\r\ni.file-preview.file-type-doc,\r\ni.file-preview.file-type-docx{\r\n    background-position: -420px center;\r\n}\r\ni.file-preview.file-type-ppt,\r\ni.file-preview.file-type-pptx{\r\n    background-position: -490px center;\r\n}\r\ni.file-preview.file-type-vsd{\r\n    background-position: -560px center;\r\n}\r\ni.file-preview.file-type-pdf{\r\n    background-position: -630px center;\r\n}\r\ni.file-preview.file-type-txt,\r\ni.file-preview.file-type-md,\r\ni.file-preview.file-type-json,\r\ni.file-preview.file-type-htm,\r\ni.file-preview.file-type-xml,\r\ni.file-preview.file-type-html,\r\ni.file-preview.file-type-js,\r\ni.file-preview.file-type-css,\r\ni.file-preview.file-type-php,\r\ni.file-preview.file-type-jsp,\r\ni.file-preview.file-type-asp{\r\n    background-position: -700px center;\r\n}\r\ni.file-preview.file-type-apk{\r\n    background-position: -770px center;\r\n}\r\ni.file-preview.file-type-exe{\r\n    background-position: -840px center;\r\n}\r\ni.file-preview.file-type-ipa{\r\n    background-position: -910px center;\r\n}\r\ni.file-preview.file-type-mp4,\r\ni.file-preview.file-type-swf,\r\ni.file-preview.file-type-mkv,\r\ni.file-preview.file-type-avi,\r\ni.file-preview.file-type-flv,\r\ni.file-preview.file-type-mov,\r\ni.file-preview.file-type-mpg,\r\ni.file-preview.file-type-mpeg,\r\ni.file-preview.file-type-ogv,\r\ni.file-preview.file-type-webm,\r\ni.file-preview.file-type-rm,\r\ni.file-preview.file-type-rmvb{\r\n    background-position: -980px center;\r\n}\r\ni.file-preview.file-type-ogg,\r\ni.file-preview.file-type-wav,\r\ni.file-preview.file-type-wmv,\r\ni.file-preview.file-type-mid,\r\ni.file-preview.file-type-mp3{\r\n    background-position: -1050px center;\r\n}\r\ni.file-preview.file-type-jpg,\r\ni.file-preview.file-type-jpeg,\r\ni.file-preview.file-type-gif,\r\ni.file-preview.file-type-bmp,\r\ni.file-preview.file-type-png,\r\ni.file-preview.file-type-psd{\r\n    background-position: -140px center;\r\n}"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/video/video.html",
    "content": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\r\n        \"http://www.w3.org/TR/html4/loose.dtd\">\r\n<html>\r\n<head>\r\n    <title></title>\r\n    <meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"/>\r\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"video.css\" />\r\n</head>\r\n<body>\r\n<div class=\"wrapper\">\r\n    <div id=\"videoTab\">\r\n        <div id=\"tabHeads\" class=\"tabhead\">\r\n            <span tabSrc=\"video\" class=\"focus\" data-content-id=\"video\"><var id=\"lang_tab_insertV\"></var></span>\r\n            <span tabSrc=\"upload\" data-content-id=\"upload\"><var id=\"lang_tab_uploadV\"></var></span>\r\n        </div>\r\n        <div id=\"tabBodys\" class=\"tabbody\">\r\n            <div id=\"video\" class=\"panel focus\">\r\n               <table><tr><td><label for=\"videoUrl\" class=\"url\"><var id=\"lang_video_url\"></var></label></td><td><input id=\"videoUrl\" type=\"text\"></td></tr></table>\r\n               <div id=\"preview\"></div>\r\n               <div id=\"videoInfo\">\r\n                   <fieldset>\r\n                       <legend><var id=\"lang_video_size\"></var></legend>\r\n                       <table>\r\n                           <tr><td><label for=\"videoWidth\"><var id=\"lang_videoW\"></var></label></td><td><input class=\"txt\" id=\"videoWidth\" type=\"text\"/></td></tr>\r\n                           <tr><td><label for=\"videoHeight\"><var id=\"lang_videoH\"></var></label></td><td><input class=\"txt\" id=\"videoHeight\" type=\"text\"/></td></tr>\r\n                       </table>\r\n                   </fieldset>\r\n                   <fieldset>\r\n                      <legend><var id=\"lang_alignment\"></var></legend>\r\n                      <div id=\"videoFloat\"></div>\r\n                  </fieldset>\r\n               </div>\r\n            </div>\r\n            <div id=\"upload\" class=\"panel\">\r\n                <div id=\"upload_left\">\r\n                    <div id=\"queueList\" class=\"queueList\">\r\n                        <div class=\"statusBar element-invisible\">\r\n                            <div class=\"progress\">\r\n                                <span class=\"text\">0%</span>\r\n                                <span class=\"percentage\"></span>\r\n                            </div><div class=\"info\"></div>\r\n                            <div class=\"btns\">\r\n                                <div id=\"filePickerBtn\"></div>\r\n                                <div class=\"uploadBtn\"><var id=\"lang_start_upload\"></var></div>\r\n                            </div>\r\n                        </div>\r\n                        <div id=\"dndArea\" class=\"placeholder\">\r\n                            <div class=\"filePickerContainer\">\r\n                                <div id=\"filePickerReady\"></div>\r\n                            </div>\r\n                        </div>\r\n                        <ul class=\"filelist element-invisible\">\r\n                            <li id=\"filePickerBlock\" class=\"filePickerBlock\"></li>\r\n                        </ul>\r\n                    </div>\r\n                </div>\r\n                <div id=\"uploadVideoInfo\">\r\n                    <fieldset>\r\n                        <legend><var id=\"lang_upload_size\"></var></legend>\r\n                        <table>\r\n                            <tr><td><label><var id=\"lang_upload_width\"></var></label></td><td><input class=\"txt\" id=\"upload_width\" type=\"text\"/></td></tr>\r\n                            <tr><td><label><var id=\"lang_upload_height\"></var></label></td><td><input class=\"txt\" id=\"upload_height\" type=\"text\"/></td></tr>\r\n                        </table>\r\n                    </fieldset>\r\n                    <fieldset>\r\n                        <legend><var id=\"lang_upload_alignment\"></var></legend>\r\n                        <div id=\"upload_alignment\"></div>\r\n                    </fieldset>\r\n                </div>\r\n            </div>\r\n        </div>\r\n    </div>\r\n</div>\r\n\r\n<!-- jquery -->\r\n<script type=\"text/javascript\" src=\"../../third-party/jquery-1.10.2.min.js\"></script>\r\n\r\n<!-- webuploader -->\r\n<script type=\"text/javascript\" src=\"../../third-party/webuploader/webuploader.min.js\"></script>\r\n<link rel=\"stylesheet\" type=\"text/css\" href=\"../../third-party/webuploader/webuploader.css\">\r\n\r\n<!-- video -->\r\n<script type=\"text/javascript\" src=\"video.js\"></script>\r\n</body>\r\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/video/video.js",
    "content": "/**\r\n * Created by JetBrains PhpStorm.\r\n * User: taoqili\r\n * Date: 12-2-20\r\n * Time: 上午11:19\r\n * To change this template use File | Settings | File Templates.\r\n */\r\n\r\n(function(){\r\n\r\n    var video = {},\r\n        uploadVideoList = [],\r\n        isModifyUploadVideo = false,\r\n        uploadFile;\r\n\r\n    window.onload = function(){\r\n        $focus($G(\"videoUrl\"));\r\n        initTabs();\r\n        initVideo();\r\n        initUpload();\r\n    };\r\n\r\n    /* 初始化tab标签 */\r\n    function initTabs(){\r\n        var tabs = $G('tabHeads').children;\r\n        for (var i = 0; i < tabs.length; i++) {\r\n            domUtils.on(tabs[i], \"click\", function (e) {\r\n                var j, bodyId, target = e.target || e.srcElement;\r\n                for (j = 0; j < tabs.length; j++) {\r\n                    bodyId = tabs[j].getAttribute('data-content-id');\r\n                    if(tabs[j] == target){\r\n                        domUtils.addClass(tabs[j], 'focus');\r\n                        domUtils.addClass($G(bodyId), 'focus');\r\n                    }else {\r\n                        domUtils.removeClasses(tabs[j], 'focus');\r\n                        domUtils.removeClasses($G(bodyId), 'focus');\r\n                    }\r\n                }\r\n            });\r\n        }\r\n    }\r\n\r\n    function initVideo(){\r\n        createAlignButton( [\"videoFloat\", \"upload_alignment\"] );\r\n        addUrlChangeListener($G(\"videoUrl\"));\r\n        addOkListener();\r\n\r\n        //编辑视频时初始化相关信息\r\n        (function(){\r\n            var img = editor.selection.getRange().getClosedNode(),url;\r\n            if(img && img.className){\r\n                var hasFakedClass = (img.className == \"edui-faked-video\"),\r\n                    hasUploadClass = img.className.indexOf(\"edui-upload-video\")!=-1;\r\n                if(hasFakedClass || hasUploadClass) {\r\n                    $G(\"videoUrl\").value = url = img.getAttribute(\"_url\");\r\n                    $G(\"videoWidth\").value = img.width;\r\n                    $G(\"videoHeight\").value = img.height;\r\n                    var align = domUtils.getComputedStyle(img,\"float\"),\r\n                        parentAlign = domUtils.getComputedStyle(img.parentNode,\"text-align\");\r\n                    updateAlignButton(parentAlign===\"center\"?\"center\":align);\r\n                }\r\n                if(hasUploadClass) {\r\n                    isModifyUploadVideo = true;\r\n                }\r\n            }\r\n            createPreviewVideo(url);\r\n        })();\r\n    }\r\n\r\n    /**\r\n     * 监听确认和取消两个按钮事件，用户执行插入或者清空正在播放的视频实例操作\r\n     */\r\n    function addOkListener(){\r\n        dialog.onok = function(){\r\n            $G(\"preview\").innerHTML = \"\";\r\n            var currentTab =  findFocus(\"tabHeads\",\"tabSrc\");\r\n            switch(currentTab){\r\n                case \"video\":\r\n                    return insertSingle();\r\n                    break;\r\n                case \"videoSearch\":\r\n                    return insertSearch(\"searchList\");\r\n                    break;\r\n                case \"upload\":\r\n                    return insertUpload();\r\n                    break;\r\n            }\r\n        };\r\n        dialog.oncancel = function(){\r\n            $G(\"preview\").innerHTML = \"\";\r\n        };\r\n    }\r\n\r\n    /**\r\n     * 依据传入的align值更新按钮信息\r\n     * @param align\r\n     */\r\n    function updateAlignButton( align ) {\r\n        var aligns = $G( \"videoFloat\" ).children;\r\n        for ( var i = 0, ci; ci = aligns[i++]; ) {\r\n            if ( ci.getAttribute( \"name\" ) == align ) {\r\n                if ( ci.className !=\"focus\" ) {\r\n                    ci.className = \"focus\";\r\n                }\r\n            } else {\r\n                if ( ci.className ==\"focus\" ) {\r\n                    ci.className = \"\";\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    /**\r\n     * 将单个视频信息插入编辑器中\r\n     */\r\n    function insertSingle(){\r\n        var width = $G(\"videoWidth\"),\r\n            height = $G(\"videoHeight\"),\r\n            url=$G('videoUrl').value,\r\n            align = findFocus(\"videoFloat\",\"name\");\r\n        if(!url) return false;\r\n        if ( !checkNum( [width, height] ) ) return false;\r\n        editor.execCommand('insertvideo', {\r\n            url: convert_url(url),\r\n            width: width.value,\r\n            height: height.value,\r\n            align: align\r\n        }, isModifyUploadVideo ? 'upload':null);\r\n    }\r\n\r\n    /**\r\n     * 将元素id下的所有代表视频的图片插入编辑器中\r\n     * @param id\r\n     */\r\n    function insertSearch(id){\r\n        var imgs = domUtils.getElementsByTagName($G(id),\"img\"),\r\n            videoObjs=[];\r\n        for(var i=0,img; img=imgs[i++];){\r\n            if(img.getAttribute(\"selected\")){\r\n                videoObjs.push({\r\n                    url:img.getAttribute(\"ue_video_url\"),\r\n                    width:420,\r\n                    height:280,\r\n                    align:\"none\"\r\n                });\r\n            }\r\n        }\r\n        editor.execCommand('insertvideo',videoObjs);\r\n    }\r\n\r\n    /**\r\n     * 找到id下具有focus类的节点并返回该节点下的某个属性\r\n     * @param id\r\n     * @param returnProperty\r\n     */\r\n    function findFocus( id, returnProperty ) {\r\n        var tabs = $G( id ).children,\r\n                property;\r\n        for ( var i = 0, ci; ci = tabs[i++]; ) {\r\n            if ( ci.className==\"focus\" ) {\r\n                property = ci.getAttribute( returnProperty );\r\n                break;\r\n            }\r\n        }\r\n        return property;\r\n    }\r\n    function convert_url(url){\r\n        if ( !url ) return '';\r\n        url = utils.trim(url)\r\n            .replace(/v\\.youku\\.com\\/v_show\\/id_([\\w\\-=]+)\\.html/i, 'player.youku.com/player.php/sid/$1/v.swf')\r\n            .replace(/(www\\.)?youtube\\.com\\/watch\\?v=([\\w\\-]+)/i, \"www.youtube.com/v/$2\")\r\n            .replace(/youtu.be\\/(\\w+)$/i, \"www.youtube.com/v/$1\")\r\n            .replace(/v\\.ku6\\.com\\/.+\\/([\\w\\.]+)\\.html.*$/i, \"player.ku6.com/refer/$1/v.swf\")\r\n            .replace(/www\\.56\\.com\\/u\\d+\\/v_([\\w\\-]+)\\.html/i, \"player.56.com/v_$1.swf\")\r\n            .replace(/www.56.com\\/w\\d+\\/play_album\\-aid\\-\\d+_vid\\-([^.]+)\\.html/i, \"player.56.com/v_$1.swf\")\r\n            .replace(/v\\.pps\\.tv\\/play_([\\w]+)\\.html.*$/i, \"player.pps.tv/player/sid/$1/v.swf\")\r\n            .replace(/www\\.letv\\.com\\/ptv\\/vplay\\/([\\d]+)\\.html.*$/i, \"i7.imgs.letv.com/player/swfPlayer.swf?id=$1&autoplay=0\")\r\n            .replace(/www\\.tudou\\.com\\/programs\\/view\\/([\\w\\-]+)\\/?/i, \"www.tudou.com/v/$1\")\r\n            .replace(/v\\.qq\\.com\\/cover\\/[\\w]+\\/[\\w]+\\/([\\w]+)\\.html/i, \"static.video.qq.com/TPout.swf?vid=$1\")\r\n            .replace(/v\\.qq\\.com\\/.+[\\?\\&]vid=([^&]+).*$/i, \"static.video.qq.com/TPout.swf?vid=$1\")\r\n            .replace(/my\\.tv\\.sohu\\.com\\/[\\w]+\\/[\\d]+\\/([\\d]+)\\.shtml.*$/i, \"share.vrs.sohu.com/my/v.swf&id=$1\");\r\n\r\n        return url;\r\n    }\r\n\r\n    /**\r\n      * 检测传入的所有input框中输入的长宽是否是正数\r\n      * @param nodes input框集合，\r\n      */\r\n     function checkNum( nodes ) {\r\n         for ( var i = 0, ci; ci = nodes[i++]; ) {\r\n             var value = ci.value;\r\n             if ( !isNumber( value ) && value) {\r\n                 alert( lang.numError );\r\n                 ci.value = \"\";\r\n                 ci.focus();\r\n                 return false;\r\n             }\r\n         }\r\n         return true;\r\n     }\r\n\r\n    /**\r\n     * 数字判断\r\n     * @param value\r\n     */\r\n    function isNumber( value ) {\r\n        return /(0|^[1-9]\\d*$)/.test( value );\r\n    }\r\n\r\n    /**\r\n      * 创建图片浮动选择按钮\r\n      * @param ids\r\n      */\r\n     function createAlignButton( ids ) {\r\n         for ( var i = 0, ci; ci = ids[i++]; ) {\r\n             var floatContainer = $G( ci ),\r\n                     nameMaps = {\"none\":lang['default'], \"left\":lang.floatLeft, \"right\":lang.floatRight, \"center\":lang.block};\r\n             for ( var j in nameMaps ) {\r\n                 var div = document.createElement( \"div\" );\r\n                 div.setAttribute( \"name\", j );\r\n                 if ( j == \"none\" ) div.className=\"focus\";\r\n                 div.style.cssText = \"background:url(images/\" + j + \"_focus.jpg);\";\r\n                 div.setAttribute( \"title\", nameMaps[j] );\r\n                 floatContainer.appendChild( div );\r\n             }\r\n             switchSelect( ci );\r\n         }\r\n     }\r\n\r\n    /**\r\n     * 选择切换\r\n     * @param selectParentId\r\n     */\r\n    function switchSelect( selectParentId ) {\r\n        var selects = $G( selectParentId ).children;\r\n        for ( var i = 0, ci; ci = selects[i++]; ) {\r\n            domUtils.on( ci, \"click\", function () {\r\n                for ( var j = 0, cj; cj = selects[j++]; ) {\r\n                    cj.className = \"\";\r\n                    cj.removeAttribute && cj.removeAttribute( \"class\" );\r\n                }\r\n                this.className = \"focus\";\r\n            } )\r\n        }\r\n    }\r\n\r\n    /**\r\n     * 监听url改变事件\r\n     * @param url\r\n     */\r\n    function addUrlChangeListener(url){\r\n        if (browser.ie) {\r\n            url.onpropertychange = function () {\r\n                createPreviewVideo( this.value );\r\n            }\r\n        } else {\r\n            url.addEventListener( \"input\", function () {\r\n                createPreviewVideo( this.value );\r\n            }, false );\r\n        }\r\n    }\r\n\r\n    /**\r\n     * 根据url生成视频预览\r\n     * @param url\r\n     */\r\n    function createPreviewVideo(url){\r\n        if ( !url )return;\r\n\r\n        var conUrl = convert_url(url);\r\n\r\n        conUrl = utils.unhtmlForUrl(conUrl);\r\n\r\n        $G(\"preview\").innerHTML = '<div class=\"previewMsg\"><span>'+lang.urlError+'</span></div>'+\r\n        '<embed class=\"previewVideo\" type=\"application/x-shockwave-flash\" pluginspage=\"http://www.macromedia.com/go/getflashplayer\"' +\r\n            ' src=\"' + conUrl + '\"' +\r\n            ' width=\"' + 420  + '\"' +\r\n            ' height=\"' + 280  + '\"' +\r\n            ' wmode=\"transparent\" play=\"true\" loop=\"false\" menu=\"false\" allowscriptaccess=\"never\" allowfullscreen=\"true\" >' +\r\n        '</embed>';\r\n    }\r\n\r\n\r\n    /* 插入上传视频 */\r\n    function insertUpload(){\r\n        var videoObjs=[],\r\n            uploadDir = editor.getOpt('videoUrlPrefix'),\r\n            width = parseInt($G('upload_width').value, 10) || 420,\r\n            height = parseInt($G('upload_height').value, 10) || 280,\r\n            align = findFocus(\"upload_alignment\",\"name\") || 'none';\r\n        for(var key in uploadVideoList) {\r\n            var file = uploadVideoList[key];\r\n            videoObjs.push({\r\n                url: uploadDir + file.url,\r\n                width:width,\r\n                height:height,\r\n                align:align\r\n            });\r\n        }\r\n\r\n        var count = uploadFile.getQueueCount();\r\n        if (count) {\r\n            $('.info', '#queueList').html('<span style=\"color:red;\">' + '还有2个未上传文件'.replace(/[\\d]/, count) + '</span>');\r\n            return false;\r\n        } else {\r\n            editor.execCommand('insertvideo', videoObjs, 'upload');\r\n        }\r\n    }\r\n\r\n    /*初始化上传标签*/\r\n    function initUpload(){\r\n        uploadFile = new UploadFile('queueList');\r\n    }\r\n\r\n\r\n    /* 上传附件 */\r\n    function UploadFile(target) {\r\n        this.$wrap = target.constructor == String ? $('#' + target) : $(target);\r\n        this.init();\r\n    }\r\n    UploadFile.prototype = {\r\n        init: function () {\r\n            this.fileList = [];\r\n            this.initContainer();\r\n            this.initUploader();\r\n        },\r\n        initContainer: function () {\r\n            this.$queue = this.$wrap.find('.filelist');\r\n        },\r\n        /* 初始化容器 */\r\n        initUploader: function () {\r\n            var _this = this,\r\n                $ = jQuery,    // just in case. Make sure it's not an other libaray.\r\n                $wrap = _this.$wrap,\r\n            // 图片容器\r\n                $queue = $wrap.find('.filelist'),\r\n            // 状态栏，包括进度和控制按钮\r\n                $statusBar = $wrap.find('.statusBar'),\r\n            // 文件总体选择信息。\r\n                $info = $statusBar.find('.info'),\r\n            // 上传按钮\r\n                $upload = $wrap.find('.uploadBtn'),\r\n            // 上传按钮\r\n                $filePickerBtn = $wrap.find('.filePickerBtn'),\r\n            // 上传按钮\r\n                $filePickerBlock = $wrap.find('.filePickerBlock'),\r\n            // 没选择文件之前的内容。\r\n                $placeHolder = $wrap.find('.placeholder'),\r\n            // 总体进度条\r\n                $progress = $statusBar.find('.progress').hide(),\r\n            // 添加的文件数量\r\n                fileCount = 0,\r\n            // 添加的文件总大小\r\n                fileSize = 0,\r\n            // 优化retina, 在retina下这个值是2\r\n                ratio = window.devicePixelRatio || 1,\r\n            // 缩略图大小\r\n                thumbnailWidth = 113 * ratio,\r\n                thumbnailHeight = 113 * ratio,\r\n            // 可能有pedding, ready, uploading, confirm, done.\r\n                state = '',\r\n            // 所有文件的进度信息，key为file id\r\n                percentages = {},\r\n                supportTransition = (function () {\r\n                    var s = document.createElement('p').style,\r\n                        r = 'transition' in s ||\r\n                            'WebkitTransition' in s ||\r\n                            'MozTransition' in s ||\r\n                            'msTransition' in s ||\r\n                            'OTransition' in s;\r\n                    s = null;\r\n                    return r;\r\n                })(),\r\n            // WebUploader实例\r\n                uploader,\r\n                actionUrl = editor.getActionUrl(editor.getOpt('videoActionName')),\r\n                fileMaxSize = editor.getOpt('videoMaxSize'),\r\n                acceptExtensions = (editor.getOpt('videoAllowFiles') || []).join('').replace(/\\./g, ',').replace(/^[,]/, '');;\r\n\r\n            if (!WebUploader.Uploader.support()) {\r\n                $('#filePickerReady').after($('<div>').html(lang.errorNotSupport)).hide();\r\n                return;\r\n            } else if (!editor.getOpt('videoActionName')) {\r\n                $('#filePickerReady').after($('<div>').html(lang.errorLoadConfig)).hide();\r\n                return;\r\n            }\r\n\r\n            uploader = _this.uploader = WebUploader.create({\r\n                pick: {\r\n                    id: '#filePickerReady',\r\n                    label: lang.uploadSelectFile\r\n                },\r\n                swf: '../../third-party/webuploader/Uploader.swf',\r\n                server: actionUrl,\r\n                fileVal: editor.getOpt('videoFieldName'),\r\n                duplicate: true,\r\n                fileSingleSizeLimit: fileMaxSize,\r\n                compress: false\r\n            });\r\n            uploader.addButton({\r\n                id: '#filePickerBlock'\r\n            });\r\n            uploader.addButton({\r\n                id: '#filePickerBtn',\r\n                label: lang.uploadAddFile\r\n            });\r\n\r\n            setState('pedding');\r\n\r\n            // 当有文件添加进来时执行，负责view的创建\r\n            function addFile(file) {\r\n                var $li = $('<li id=\"' + file.id + '\">' +\r\n                        '<p class=\"title\">' + file.name + '</p>' +\r\n                        '<p class=\"imgWrap\"></p>' +\r\n                        '<p class=\"progress\"><span></span></p>' +\r\n                        '</li>'),\r\n\r\n                    $btns = $('<div class=\"file-panel\">' +\r\n                        '<span class=\"cancel\">' + lang.uploadDelete + '</span>' +\r\n                        '<span class=\"rotateRight\">' + lang.uploadTurnRight + '</span>' +\r\n                        '<span class=\"rotateLeft\">' + lang.uploadTurnLeft + '</span></div>').appendTo($li),\r\n                    $prgress = $li.find('p.progress span'),\r\n                    $wrap = $li.find('p.imgWrap'),\r\n                    $info = $('<p class=\"error\"></p>').hide().appendTo($li),\r\n\r\n                    showError = function (code) {\r\n                        switch (code) {\r\n                            case 'exceed_size':\r\n                                text = lang.errorExceedSize;\r\n                                break;\r\n                            case 'interrupt':\r\n                                text = lang.errorInterrupt;\r\n                                break;\r\n                            case 'http':\r\n                                text = lang.errorHttp;\r\n                                break;\r\n                            case 'not_allow_type':\r\n                                text = lang.errorFileType;\r\n                                break;\r\n                            default:\r\n                                text = lang.errorUploadRetry;\r\n                                break;\r\n                        }\r\n                        $info.text(text).show();\r\n                    };\r\n\r\n                if (file.getStatus() === 'invalid') {\r\n                    showError(file.statusText);\r\n                } else {\r\n                    $wrap.text(lang.uploadPreview);\r\n                    if ('|png|jpg|jpeg|bmp|gif|'.indexOf('|'+file.ext.toLowerCase()+'|') == -1) {\r\n                        $wrap.empty().addClass('notimage').append('<i class=\"file-preview file-type-' + file.ext.toLowerCase() + '\"></i>' +\r\n                            '<span class=\"file-title\">' + file.name + '</span>');\r\n                    } else {\r\n                        if (browser.ie && browser.version <= 7) {\r\n                            $wrap.text(lang.uploadNoPreview);\r\n                        } else {\r\n                            uploader.makeThumb(file, function (error, src) {\r\n                                if (error || !src || (/^data:/.test(src) && browser.ie && browser.version <= 7)) {\r\n                                    $wrap.text(lang.uploadNoPreview);\r\n                                } else {\r\n                                    var $img = $('<img src=\"' + src + '\">');\r\n                                    $wrap.empty().append($img);\r\n                                    $img.on('error', function () {\r\n                                        $wrap.text(lang.uploadNoPreview);\r\n                                    });\r\n                                }\r\n                            }, thumbnailWidth, thumbnailHeight);\r\n                        }\r\n                    }\r\n                    percentages[ file.id ] = [ file.size, 0 ];\r\n                    file.rotation = 0;\r\n\r\n                    /* 检查文件格式 */\r\n                    if (!file.ext || acceptExtensions.indexOf(file.ext.toLowerCase()) == -1) {\r\n                        showError('not_allow_type');\r\n                        uploader.removeFile(file);\r\n                    }\r\n                }\r\n\r\n                file.on('statuschange', function (cur, prev) {\r\n                    if (prev === 'progress') {\r\n                        $prgress.hide().width(0);\r\n                    } else if (prev === 'queued') {\r\n                        $li.off('mouseenter mouseleave');\r\n                        $btns.remove();\r\n                    }\r\n                    // 成功\r\n                    if (cur === 'error' || cur === 'invalid') {\r\n                        showError(file.statusText);\r\n                        percentages[ file.id ][ 1 ] = 1;\r\n                    } else if (cur === 'interrupt') {\r\n                        showError('interrupt');\r\n                    } else if (cur === 'queued') {\r\n                        percentages[ file.id ][ 1 ] = 0;\r\n                    } else if (cur === 'progress') {\r\n                        $info.hide();\r\n                        $prgress.css('display', 'block');\r\n                    } else if (cur === 'complete') {\r\n                    }\r\n\r\n                    $li.removeClass('state-' + prev).addClass('state-' + cur);\r\n                });\r\n\r\n                $li.on('mouseenter', function () {\r\n                    $btns.stop().animate({height: 30});\r\n                });\r\n                $li.on('mouseleave', function () {\r\n                    $btns.stop().animate({height: 0});\r\n                });\r\n\r\n                $btns.on('click', 'span', function () {\r\n                    var index = $(this).index(),\r\n                        deg;\r\n\r\n                    switch (index) {\r\n                        case 0:\r\n                            uploader.removeFile(file);\r\n                            return;\r\n                        case 1:\r\n                            file.rotation += 90;\r\n                            break;\r\n                        case 2:\r\n                            file.rotation -= 90;\r\n                            break;\r\n                    }\r\n\r\n                    if (supportTransition) {\r\n                        deg = 'rotate(' + file.rotation + 'deg)';\r\n                        $wrap.css({\r\n                            '-webkit-transform': deg,\r\n                            '-mos-transform': deg,\r\n                            '-o-transform': deg,\r\n                            'transform': deg\r\n                        });\r\n                    } else {\r\n                        $wrap.css('filter', 'progid:DXImageTransform.Microsoft.BasicImage(rotation=' + (~~((file.rotation / 90) % 4 + 4) % 4) + ')');\r\n                    }\r\n\r\n                });\r\n\r\n                $li.insertBefore($filePickerBlock);\r\n            }\r\n\r\n            // 负责view的销毁\r\n            function removeFile(file) {\r\n                var $li = $('#' + file.id);\r\n                delete percentages[ file.id ];\r\n                updateTotalProgress();\r\n                $li.off().find('.file-panel').off().end().remove();\r\n            }\r\n\r\n            function updateTotalProgress() {\r\n                var loaded = 0,\r\n                    total = 0,\r\n                    spans = $progress.children(),\r\n                    percent;\r\n\r\n                $.each(percentages, function (k, v) {\r\n                    total += v[ 0 ];\r\n                    loaded += v[ 0 ] * v[ 1 ];\r\n                });\r\n\r\n                percent = total ? loaded / total : 0;\r\n\r\n                spans.eq(0).text(Math.round(percent * 100) + '%');\r\n                spans.eq(1).css('width', Math.round(percent * 100) + '%');\r\n                updateStatus();\r\n            }\r\n\r\n            function setState(val, files) {\r\n\r\n                if (val != state) {\r\n\r\n                    var stats = uploader.getStats();\r\n\r\n                    $upload.removeClass('state-' + state);\r\n                    $upload.addClass('state-' + val);\r\n\r\n                    switch (val) {\r\n\r\n                        /* 未选择文件 */\r\n                        case 'pedding':\r\n                            $queue.addClass('element-invisible');\r\n                            $statusBar.addClass('element-invisible');\r\n                            $placeHolder.removeClass('element-invisible');\r\n                            $progress.hide(); $info.hide();\r\n                            uploader.refresh();\r\n                            break;\r\n\r\n                        /* 可以开始上传 */\r\n                        case 'ready':\r\n                            $placeHolder.addClass('element-invisible');\r\n                            $queue.removeClass('element-invisible');\r\n                            $statusBar.removeClass('element-invisible');\r\n                            $progress.hide(); $info.show();\r\n                            $upload.text(lang.uploadStart);\r\n                            uploader.refresh();\r\n                            break;\r\n\r\n                        /* 上传中 */\r\n                        case 'uploading':\r\n                            $progress.show(); $info.hide();\r\n                            $upload.text(lang.uploadPause);\r\n                            break;\r\n\r\n                        /* 暂停上传 */\r\n                        case 'paused':\r\n                            $progress.show(); $info.hide();\r\n                            $upload.text(lang.uploadContinue);\r\n                            break;\r\n\r\n                        case 'confirm':\r\n                            $progress.show(); $info.hide();\r\n                            $upload.text(lang.uploadStart);\r\n\r\n                            stats = uploader.getStats();\r\n                            if (stats.successNum && !stats.uploadFailNum) {\r\n                                setState('finish');\r\n                                return;\r\n                            }\r\n                            break;\r\n\r\n                        case 'finish':\r\n                            $progress.hide(); $info.show();\r\n                            if (stats.uploadFailNum) {\r\n                                $upload.text(lang.uploadRetry);\r\n                            } else {\r\n                                $upload.text(lang.uploadStart);\r\n                            }\r\n                            break;\r\n                    }\r\n\r\n                    state = val;\r\n                    updateStatus();\r\n\r\n                }\r\n\r\n                if (!_this.getQueueCount()) {\r\n                    $upload.addClass('disabled')\r\n                } else {\r\n                    $upload.removeClass('disabled')\r\n                }\r\n\r\n            }\r\n\r\n            function updateStatus() {\r\n                var text = '', stats;\r\n\r\n                if (state === 'ready') {\r\n                    text = lang.updateStatusReady.replace('_', fileCount).replace('_KB', WebUploader.formatSize(fileSize));\r\n                } else if (state === 'confirm') {\r\n                    stats = uploader.getStats();\r\n                    if (stats.uploadFailNum) {\r\n                        text = lang.updateStatusConfirm.replace('_', stats.successNum).replace('_', stats.successNum);\r\n                    }\r\n                } else {\r\n                    stats = uploader.getStats();\r\n                    text = lang.updateStatusFinish.replace('_', fileCount).\r\n                        replace('_KB', WebUploader.formatSize(fileSize)).\r\n                        replace('_', stats.successNum);\r\n\r\n                    if (stats.uploadFailNum) {\r\n                        text += lang.updateStatusError.replace('_', stats.uploadFailNum);\r\n                    }\r\n                }\r\n\r\n                $info.html(text);\r\n            }\r\n\r\n            uploader.on('fileQueued', function (file) {\r\n                fileCount++;\r\n                fileSize += file.size;\r\n\r\n                if (fileCount === 1) {\r\n                    $placeHolder.addClass('element-invisible');\r\n                    $statusBar.show();\r\n                }\r\n\r\n                addFile(file);\r\n            });\r\n\r\n            uploader.on('fileDequeued', function (file) {\r\n                fileCount--;\r\n                fileSize -= file.size;\r\n\r\n                removeFile(file);\r\n                updateTotalProgress();\r\n            });\r\n\r\n            uploader.on('filesQueued', function (file) {\r\n                if (!uploader.isInProgress() && (state == 'pedding' || state == 'finish' || state == 'confirm' || state == 'ready')) {\r\n                    setState('ready');\r\n                }\r\n                updateTotalProgress();\r\n            });\r\n\r\n            uploader.on('all', function (type, files) {\r\n                switch (type) {\r\n                    case 'uploadFinished':\r\n                        setState('confirm', files);\r\n                        break;\r\n                    case 'startUpload':\r\n                        /* 添加额外的GET参数 */\r\n                        var params = utils.serializeParam(editor.queryCommandValue('serverparam')) || '',\r\n                            url = utils.formatUrl(actionUrl + (actionUrl.indexOf('?') == -1 ? '?':'&') + 'encode=utf-8&' + params);\r\n                        uploader.option('server', url);\r\n                        setState('uploading', files);\r\n                        break;\r\n                    case 'stopUpload':\r\n                        setState('paused', files);\r\n                        break;\r\n                }\r\n            });\r\n\r\n            uploader.on('uploadBeforeSend', function (file, data, header) {\r\n                //这里可以通过data对象添加POST参数\r\n                header['X_Requested_With'] = 'XMLHttpRequest';\r\n                // HaoChuan9421\r\n                if(editor.options.headers && Object.prototype.toString.apply(editor.options.headers) === \"[object Object]\"){\r\n                    for(var key in editor.options.headers){\r\n                        header[key] = editor.options.headers[key]\r\n                    }\r\n                }\r\n            });\r\n\r\n            uploader.on('uploadProgress', function (file, percentage) {\r\n                var $li = $('#' + file.id),\r\n                    $percent = $li.find('.progress span');\r\n\r\n                $percent.css('width', percentage * 100 + '%');\r\n                percentages[ file.id ][ 1 ] = percentage;\r\n                updateTotalProgress();\r\n            });\r\n\r\n            uploader.on('uploadSuccess', function (file, ret) {\r\n                var $file = $('#' + file.id);\r\n                try {\r\n                    var responseText = (ret._raw || ret),\r\n                        json = utils.str2json(responseText);\r\n                    if (json.state == 'SUCCESS') {\r\n                        uploadVideoList.push({\r\n                            'url': json.url,\r\n                            'type': json.type,\r\n                            'original':json.original\r\n                        });\r\n                        $file.append('<span class=\"success\"></span>');\r\n                    } else {\r\n                        $file.find('.error').text(json.state).show();\r\n                    }\r\n                } catch (e) {\r\n                    $file.find('.error').text(lang.errorServerUpload).show();\r\n                }\r\n            });\r\n\r\n            uploader.on('uploadError', function (file, code) {\r\n            });\r\n            uploader.on('error', function (code, file) {\r\n                if (code == 'Q_TYPE_DENIED' || code == 'F_EXCEED_SIZE') {\r\n                    addFile(file);\r\n                }\r\n            });\r\n            uploader.on('uploadComplete', function (file, ret) {\r\n            });\r\n\r\n            $upload.on('click', function () {\r\n                if ($(this).hasClass('disabled')) {\r\n                    return false;\r\n                }\r\n\r\n                if (state === 'ready') {\r\n                    uploader.upload();\r\n                } else if (state === 'paused') {\r\n                    uploader.upload();\r\n                } else if (state === 'uploading') {\r\n                    uploader.stop();\r\n                }\r\n            });\r\n\r\n            $upload.addClass('state-' + state);\r\n            updateTotalProgress();\r\n        },\r\n        getQueueCount: function () {\r\n            var file, i, status, readyFile = 0, files = this.uploader.getFiles();\r\n            for (i = 0; file = files[i++]; ) {\r\n                status = file.getStatus();\r\n                if (status == 'queued' || status == 'uploading' || status == 'progress') readyFile++;\r\n            }\r\n            return readyFile;\r\n        },\r\n        refresh: function(){\r\n            this.uploader.refresh();\r\n        }\r\n    };\r\n\r\n})();\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/webapp/webapp.html",
    "content": "<!DOCTYPE>\r\n<html>\r\n<head>\r\n    <title></title>\r\n    <meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"/>\r\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n    <style type=\"text/css\">\r\n        .wrapper{width: 540px; margin: 10px auto;}\r\n        #appShow {border: 1px solid #ddd;}\r\n        .errorMsg{font-size: 13px;margin: 10px;color: #dd0000}\r\n    </style>\r\n</head>\r\n<body>\r\n    <div class=\"wrapper\">\r\n        <div id=\"appShow\"></div>\r\n    </div>\r\n    <script type=\"text/javascript\">\r\n        //此处配置您在百度上申请到的appkey。\r\n        var apikey = editor.options.webAppKey;\r\n        if ( apikey && apikey.length == 24 ) {\r\n            var searchConfig = {\r\n                container:'appShow', //容器ID\r\n                tips:\"\", //该值用于自动清空\r\n                search:1, //是否显示搜索框\r\n                ps:12, //每页显示的条数\r\n                suggest:1, //是否开启搜索自动完成\r\n                limit:0, //搜索结果显示条数，0表示无限制\r\n                searchNow:0, //是否在初始化完成时立即搜索\r\n                apikey:apikey, //每人得\r\n                pager:1,\r\n                cid:7134562,\r\n                outputHTML:1\r\n            },baiduApp;\r\n\r\n            function clickCallback() {\r\n                baiduApp.addEventListener( 'getAppHTML', function ( e, data ) {\r\n                    var url = 'http://app.baidu.com/app/enter?appid='+data.data['app_id'] +'&tn=app_canvas&app_spce_id=1&apikey='+apikey+'&api_key=' + apikey;\r\n                    editor.execCommand( \"webapp\", {url:url,width:data.uniWidth,height:data.uniHeight+60,logo:data.data['app_logo'],title:data.data['app_name']});\r\n                    dialog.close();\r\n                } );\r\n            }\r\n\r\n            var script = document.createElement( \"script\" );\r\n            script.type = \"text/javascript\";\r\n            script.src = \"http://app.baidu.com/appweb/api/search?auto=yes&container=container&apikey=\" + apikey + \"&instanceName=baiduApp&callback=clickCallback&config=searchConfig\";\r\n            document.body.appendChild( script );\r\n        } else {\r\n            $G( \"appShow\" ).innerHTML = \"<p class='errorMsg'>\"+lang.tip1+\"<a title='\"+lang.anthorApi+\"' href='http://app.baidu.com/static/cms/getapikey.html' target='_blank'>\"+lang.applyFor+\"</a></p><p class='errorMsg'>\"+lang.tip2+\"</p>\" ;\r\n        }\r\n\r\n    </script>\r\n</body>\r\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/wordimage/tangram.js",
    "content": "// Copyright (c) 2009, Baidu Inc. All rights reserved.\r\n// \r\n// Licensed under the BSD License\r\n// you may not use this file except in compliance with the License.\r\n// You may obtain a copy of the License at\r\n// \r\n//      http:// tangram.baidu.com/license.html\r\n// \r\n// Unless required by applicable law or agreed to in writing, software\r\n// distributed under the License is distributed on an \"AS-IS\" BASIS,\r\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r\n// See the License for the specific language governing permissions and\r\n// limitations under the License.\r\n /**\r\n * @namespace T Tangram七巧板\r\n * @name T\r\n * @version 1.6.0\r\n*/\r\n\r\n/**\r\n * 声明baidu包\r\n * @author: allstar, erik, meizz, berg\r\n */\r\nvar T,\r\n    baidu = T = baidu || {version: \"1.5.0\"};\r\nbaidu.guid = \"$BAIDU$\";\r\nbaidu.$$ = window[baidu.guid] = window[baidu.guid] || {global:{}};\r\n\r\n/**\r\n * 使用flash资源封装的一些功能\r\n * @namespace baidu.flash\r\n */\r\nbaidu.flash = baidu.flash || {};\r\n\r\n/**\r\n * 操作dom的方法\r\n * @namespace baidu.dom \r\n */\r\nbaidu.dom = baidu.dom || {};\r\n\r\n\r\n/**\r\n * 从文档中获取指定的DOM元素\r\n * @name baidu.dom.g\r\n * @function\r\n * @grammar baidu.dom.g(id)\r\n * @param {string|HTMLElement} id 元素的id或DOM元素.\r\n * @shortcut g,T.G\r\n * @meta standard\r\n * @see baidu.dom.q\r\n *\r\n * @return {HTMLElement|null} 获取的元素，查找不到时返回null,如果参数不合法，直接返回参数.\r\n */\r\nbaidu.dom.g = function(id) {\r\n    if (!id) return null;\r\n    if ('string' == typeof id || id instanceof String) {\r\n        return document.getElementById(id);\r\n    } else if (id.nodeName && (id.nodeType == 1 || id.nodeType == 9)) {\r\n        return id;\r\n    }\r\n    return null;\r\n};\r\nbaidu.g = baidu.G = baidu.dom.g;\r\n\r\n\r\n/**\r\n * 操作数组的方法\r\n * @namespace baidu.array\r\n */\r\n\r\nbaidu.array = baidu.array || {};\r\n\r\n\r\n/**\r\n * 遍历数组中所有元素\r\n * @name baidu.array.each\r\n * @function\r\n * @grammar baidu.array.each(source, iterator[, thisObject])\r\n * @param {Array} source 需要遍历的数组\r\n * @param {Function} iterator 对每个数组元素进行调用的函数，该函数有两个参数，第一个为数组元素，第二个为数组索引值，function (item, index)。\r\n * @param {Object} [thisObject] 函数调用时的this指针，如果没有此参数，默认是当前遍历的数组\r\n * @remark\r\n * each方法不支持对Object的遍历,对Object的遍历使用baidu.object.each 。\r\n * @shortcut each\r\n * @meta standard\r\n *             \r\n * @returns {Array} 遍历的数组\r\n */\r\n \r\nbaidu.each = baidu.array.forEach = baidu.array.each = function (source, iterator, thisObject) {\r\n    var returnValue, item, i, len = source.length;\r\n    \r\n    if ('function' == typeof iterator) {\r\n        for (i = 0; i < len; i++) {\r\n            item = source[i];\r\n            returnValue = iterator.call(thisObject || source, item, i);\r\n    \r\n            if (returnValue === false) {\r\n                break;\r\n            }\r\n        }\r\n    }\r\n    return source;\r\n};\r\n\r\n/**\r\n * 对语言层面的封装，包括类型判断、模块扩展、继承基类以及对象自定义事件的支持。\r\n * @namespace baidu.lang\r\n */\r\nbaidu.lang = baidu.lang || {};\r\n\r\n\r\n/**\r\n * 判断目标参数是否为function或Function实例\r\n * @name baidu.lang.isFunction\r\n * @function\r\n * @grammar baidu.lang.isFunction(source)\r\n * @param {Any} source 目标参数\r\n * @version 1.2\r\n * @see baidu.lang.isString,baidu.lang.isObject,baidu.lang.isNumber,baidu.lang.isArray,baidu.lang.isElement,baidu.lang.isBoolean,baidu.lang.isDate\r\n * @meta standard\r\n * @returns {boolean} 类型判断结果\r\n */\r\nbaidu.lang.isFunction = function (source) {\r\n    return '[object Function]' == Object.prototype.toString.call(source);\r\n};\r\n\r\n/**\r\n * 判断目标参数是否string类型或String对象\r\n * @name baidu.lang.isString\r\n * @function\r\n * @grammar baidu.lang.isString(source)\r\n * @param {Any} source 目标参数\r\n * @shortcut isString\r\n * @meta standard\r\n * @see baidu.lang.isObject,baidu.lang.isNumber,baidu.lang.isArray,baidu.lang.isElement,baidu.lang.isBoolean,baidu.lang.isDate\r\n *             \r\n * @returns {boolean} 类型判断结果\r\n */\r\nbaidu.lang.isString = function (source) {\r\n    return '[object String]' == Object.prototype.toString.call(source);\r\n};\r\nbaidu.isString = baidu.lang.isString;\r\n\r\n\r\n/**\r\n * 判断浏览器类型和特性的属性\r\n * @namespace baidu.browser\r\n */\r\nbaidu.browser = baidu.browser || {};\r\n\r\n\r\n/**\r\n * 判断是否为opera浏览器\r\n * @property opera opera版本号\r\n * @grammar baidu.browser.opera\r\n * @meta standard\r\n * @see baidu.browser.ie,baidu.browser.firefox,baidu.browser.safari,baidu.browser.chrome\r\n * @returns {Number} opera版本号\r\n */\r\n\r\n/**\r\n * opera 从10开始不是用opera后面的字符串进行版本的判断\r\n * 在Browser identification最后添加Version + 数字进行版本标识\r\n * opera后面的数字保持在9.80不变\r\n */\r\nbaidu.browser.opera = /opera(\\/| )(\\d+(\\.\\d+)?)(.+?(version\\/(\\d+(\\.\\d+)?)))?/i.test(navigator.userAgent) ?  + ( RegExp[\"\\x246\"] || RegExp[\"\\x242\"] ) : undefined;\r\n\r\n\r\n/**\r\n * 在目标元素的指定位置插入HTML代码\r\n * @name baidu.dom.insertHTML\r\n * @function\r\n * @grammar baidu.dom.insertHTML(element, position, html)\r\n * @param {HTMLElement|string} element 目标元素或目标元素的id\r\n * @param {string} position 插入html的位置信息，取值为beforeBegin,afterBegin,beforeEnd,afterEnd\r\n * @param {string} html 要插入的html\r\n * @remark\r\n * \r\n * 对于position参数，大小写不敏感<br>\r\n * 参数的意思：beforeBegin&lt;span&gt;afterBegin   this is span! beforeEnd&lt;/span&gt; afterEnd <br />\r\n * 此外，如果使用本函数插入带有script标签的HTML字符串，script标签对应的脚本将不会被执行。\r\n * \r\n * @shortcut insertHTML\r\n * @meta standard\r\n *             \r\n * @returns {HTMLElement} 目标元素\r\n */\r\nbaidu.dom.insertHTML = function (element, position, html) {\r\n    element = baidu.dom.g(element);\r\n    var range,begin;\r\n    if (element.insertAdjacentHTML && !baidu.browser.opera) {\r\n        element.insertAdjacentHTML(position, html);\r\n    } else {\r\n        range = element.ownerDocument.createRange();\r\n        position = position.toUpperCase();\r\n        if (position == 'AFTERBEGIN' || position == 'BEFOREEND') {\r\n            range.selectNodeContents(element);\r\n            range.collapse(position == 'AFTERBEGIN');\r\n        } else {\r\n            begin = position == 'BEFOREBEGIN';\r\n            range[begin ? 'setStartBefore' : 'setEndAfter'](element);\r\n            range.collapse(begin);\r\n        }\r\n        range.insertNode(range.createContextualFragment(html));\r\n    }\r\n    return element;\r\n};\r\n\r\nbaidu.insertHTML = baidu.dom.insertHTML;\r\n\r\n/**\r\n * 操作flash对象的方法，包括创建flash对象、获取flash对象以及判断flash插件的版本号\r\n * @namespace baidu.swf\r\n */\r\nbaidu.swf = baidu.swf || {};\r\n\r\n\r\n/**\r\n * 浏览器支持的flash插件版本\r\n * @property version 浏览器支持的flash插件版本\r\n * @grammar baidu.swf.version\r\n * @return {String} 版本号\r\n * @meta standard\r\n */\r\nbaidu.swf.version = (function () {\r\n    var n = navigator;\r\n    if (n.plugins && n.mimeTypes.length) {\r\n        var plugin = n.plugins[\"Shockwave Flash\"];\r\n        if (plugin && plugin.description) {\r\n            return plugin.description\r\n                    .replace(/([a-zA-Z]|\\s)+/, \"\")\r\n                    .replace(/(\\s)+r/, \".\") + \".0\";\r\n        }\r\n    } else if (window.ActiveXObject && !window.opera) {\r\n        for (var i = 12; i >= 2; i--) {\r\n            try {\r\n                var c = new ActiveXObject('ShockwaveFlash.ShockwaveFlash.' + i);\r\n                if (c) {\r\n                    var version = c.GetVariable(\"$version\");\r\n                    return version.replace(/WIN/g,'').replace(/,/g,'.');\r\n                }\r\n            } catch(e) {}\r\n        }\r\n    }\r\n})();\r\n\r\n/**\r\n * 操作字符串的方法\r\n * @namespace baidu.string\r\n */\r\nbaidu.string = baidu.string || {};\r\n\r\n\r\n/**\r\n * 对目标字符串进行html编码\r\n * @name baidu.string.encodeHTML\r\n * @function\r\n * @grammar baidu.string.encodeHTML(source)\r\n * @param {string} source 目标字符串\r\n * @remark\r\n * 编码字符有5个：&<>\"'\r\n * @shortcut encodeHTML\r\n * @meta standard\r\n * @see baidu.string.decodeHTML\r\n *             \r\n * @returns {string} html编码后的字符串\r\n */\r\nbaidu.string.encodeHTML = function (source) {\r\n    return String(source)\r\n                .replace(/&/g,'&amp;')\r\n                .replace(/</g,'&lt;')\r\n                .replace(/>/g,'&gt;')\r\n                .replace(/\"/g, \"&quot;\")\r\n                .replace(/'/g, \"&#39;\");\r\n};\r\n\r\nbaidu.encodeHTML = baidu.string.encodeHTML;\r\n\r\n/**\r\n * 创建flash对象的html字符串\r\n * @name baidu.swf.createHTML\r\n * @function\r\n * @grammar baidu.swf.createHTML(options)\r\n * \r\n * @param {Object} \toptions \t\t\t\t\t创建flash的选项参数\r\n * @param {string} \toptions.id \t\t\t\t\t要创建的flash的标识\r\n * @param {string} \toptions.url \t\t\t\tflash文件的url\r\n * @param {String} \toptions.errorMessage \t\t未安装flash player或flash player版本号过低时的提示\r\n * @param {string} \toptions.ver \t\t\t\t最低需要的flash player版本号\r\n * @param {string} \toptions.width \t\t\t\tflash的宽度\r\n * @param {string} \toptions.height \t\t\t\tflash的高度\r\n * @param {string} \toptions.align \t\t\t\tflash的对齐方式，允许值：middle/left/right/top/bottom\r\n * @param {string} \toptions.base \t\t\t\t设置用于解析swf文件中的所有相对路径语句的基本目录或URL\r\n * @param {string} \toptions.bgcolor \t\t\tswf文件的背景色\r\n * @param {string} \toptions.salign \t\t\t\t设置缩放的swf文件在由width和height设置定义的区域内的位置。允许值：l/r/t/b/tl/tr/bl/br\r\n * @param {boolean} options.menu \t\t\t\t是否显示右键菜单，允许值：true/false\r\n * @param {boolean} options.loop \t\t\t\t播放到最后一帧时是否重新播放，允许值： true/false\r\n * @param {boolean} options.play \t\t\t\tflash是否在浏览器加载时就开始播放。允许值：true/false\r\n * @param {string} \toptions.quality \t\t\t设置flash播放的画质，允许值：low/medium/high/autolow/autohigh/best\r\n * @param {string} \toptions.scale \t\t\t\t设置flash内容如何缩放来适应设置的宽高。允许值：showall/noborder/exactfit\r\n * @param {string} \toptions.wmode \t\t\t\t设置flash的显示模式。允许值：window/opaque/transparent\r\n * @param {string} \toptions.allowscriptaccess \t设置flash与页面的通信权限。允许值：always/never/sameDomain\r\n * @param {string} \toptions.allownetworking \t设置swf文件中允许使用的网络API。允许值：all/internal/none\r\n * @param {boolean} options.allowfullscreen \t是否允许flash全屏。允许值：true/false\r\n * @param {boolean} options.seamlesstabbing \t允许设置执行无缝跳格，从而使用户能跳出flash应用程序。该参数只能在安装Flash7及更高版本的Windows中使用。允许值：true/false\r\n * @param {boolean} options.devicefont \t\t\t设置静态文本对象是否以设备字体呈现。允许值：true/false\r\n * @param {boolean} options.swliveconnect \t\t第一次加载flash时浏览器是否应启动Java。允许值：true/false\r\n * @param {Object} \toptions.vars \t\t\t\t要传递给flash的参数，支持JSON或string类型。\r\n * \r\n * @see baidu.swf.create\r\n * @meta standard\r\n * @returns {string} flash对象的html字符串\r\n */\r\nbaidu.swf.createHTML = function (options) {\r\n    options = options || {};\r\n    var version = baidu.swf.version, \r\n        needVersion = options['ver'] || '6.0.0', \r\n        vUnit1, vUnit2, i, k, len, item, tmpOpt = {},\r\n        encodeHTML = baidu.string.encodeHTML;\r\n    for (k in options) {\r\n        tmpOpt[k] = options[k];\r\n    }\r\n    options = tmpOpt;\r\n    if (version) {\r\n        version = version.split('.');\r\n        needVersion = needVersion.split('.');\r\n        for (i = 0; i < 3; i++) {\r\n            vUnit1 = parseInt(version[i], 10);\r\n            vUnit2 = parseInt(needVersion[i], 10);\r\n            if (vUnit2 < vUnit1) {\r\n                break;\r\n            } else if (vUnit2 > vUnit1) {\r\n                return '';\r\n            }\r\n        }\r\n    } else {\r\n        return '';\r\n    }\r\n    \r\n    var vars = options['vars'],\r\n        objProperties = ['classid', 'codebase', 'id', 'width', 'height', 'align'];\r\n    options['align'] = options['align'] || 'middle';\r\n    options['classid'] = 'clsid:d27cdb6e-ae6d-11cf-96b8-444553540000';\r\n    options['codebase'] = 'http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0';\r\n    options['movie'] = options['url'] || '';\r\n    delete options['vars'];\r\n    delete options['url'];\r\n    if ('string' == typeof vars) {\r\n        options['flashvars'] = vars;\r\n    } else {\r\n        var fvars = [];\r\n        for (k in vars) {\r\n            item = vars[k];\r\n            fvars.push(k + \"=\" + encodeURIComponent(item));\r\n        }\r\n        options['flashvars'] = fvars.join('&');\r\n    }\r\n    var str = ['<object '];\r\n    for (i = 0, len = objProperties.length; i < len; i++) {\r\n        item = objProperties[i];\r\n        str.push(' ', item, '=\"', encodeHTML(options[item]), '\"');\r\n    }\r\n    str.push('>');\r\n    var params = {\r\n        'wmode'             : 1,\r\n        'scale'             : 1,\r\n        'quality'           : 1,\r\n        'play'              : 1,\r\n        'loop'              : 1,\r\n        'menu'              : 1,\r\n        'salign'            : 1,\r\n        'bgcolor'           : 1,\r\n        'base'              : 1,\r\n        'allowscriptaccess' : 1,\r\n        'allownetworking'   : 1,\r\n        'allowfullscreen'   : 1,\r\n        'seamlesstabbing'   : 1,\r\n        'devicefont'        : 1,\r\n        'swliveconnect'     : 1,\r\n        'flashvars'         : 1,\r\n        'movie'             : 1\r\n    };\r\n    \r\n    for (k in options) {\r\n        item = options[k];\r\n        k = k.toLowerCase();\r\n        if (params[k] && (item || item === false || item === 0)) {\r\n            str.push('<param name=\"' + k + '\" value=\"' + encodeHTML(item) + '\" />');\r\n        }\r\n    }\r\n    options['src']  = options['movie'];\r\n    options['name'] = options['id'];\r\n    delete options['id'];\r\n    delete options['movie'];\r\n    delete options['classid'];\r\n    delete options['codebase'];\r\n    options['type'] = 'application/x-shockwave-flash';\r\n    options['pluginspage'] = 'http://www.macromedia.com/go/getflashplayer';\r\n    str.push('<embed');\r\n    var salign;\r\n    for (k in options) {\r\n        item = options[k];\r\n        if (item || item === false || item === 0) {\r\n            if ((new RegExp(\"^salign\\x24\", \"i\")).test(k)) {\r\n                salign = item;\r\n                continue;\r\n            }\r\n            \r\n            str.push(' ', k, '=\"', encodeHTML(item), '\"');\r\n        }\r\n    }\r\n    \r\n    if (salign) {\r\n        str.push(' salign=\"', encodeHTML(salign), '\"');\r\n    }\r\n    str.push('></embed></object>');\r\n    \r\n    return str.join('');\r\n};\r\n\r\n\r\n/**\r\n * 在页面中创建一个flash对象\r\n * @name baidu.swf.create\r\n * @function\r\n * @grammar baidu.swf.create(options[, container])\r\n * \r\n * @param {Object} \toptions \t\t\t\t\t创建flash的选项参数\r\n * @param {string} \toptions.id \t\t\t\t\t要创建的flash的标识\r\n * @param {string} \toptions.url \t\t\t\tflash文件的url\r\n * @param {String} \toptions.errorMessage \t\t未安装flash player或flash player版本号过低时的提示\r\n * @param {string} \toptions.ver \t\t\t\t最低需要的flash player版本号\r\n * @param {string} \toptions.width \t\t\t\tflash的宽度\r\n * @param {string} \toptions.height \t\t\t\tflash的高度\r\n * @param {string} \toptions.align \t\t\t\tflash的对齐方式，允许值：middle/left/right/top/bottom\r\n * @param {string} \toptions.base \t\t\t\t设置用于解析swf文件中的所有相对路径语句的基本目录或URL\r\n * @param {string} \toptions.bgcolor \t\t\tswf文件的背景色\r\n * @param {string} \toptions.salign \t\t\t\t设置缩放的swf文件在由width和height设置定义的区域内的位置。允许值：l/r/t/b/tl/tr/bl/br\r\n * @param {boolean} options.menu \t\t\t\t是否显示右键菜单，允许值：true/false\r\n * @param {boolean} options.loop \t\t\t\t播放到最后一帧时是否重新播放，允许值： true/false\r\n * @param {boolean} options.play \t\t\t\tflash是否在浏览器加载时就开始播放。允许值：true/false\r\n * @param {string} \toptions.quality \t\t\t设置flash播放的画质，允许值：low/medium/high/autolow/autohigh/best\r\n * @param {string} \toptions.scale \t\t\t\t设置flash内容如何缩放来适应设置的宽高。允许值：showall/noborder/exactfit\r\n * @param {string} \toptions.wmode \t\t\t\t设置flash的显示模式。允许值：window/opaque/transparent\r\n * @param {string} \toptions.allowscriptaccess \t设置flash与页面的通信权限。允许值：always/never/sameDomain\r\n * @param {string} \toptions.allownetworking \t设置swf文件中允许使用的网络API。允许值：all/internal/none\r\n * @param {boolean} options.allowfullscreen \t是否允许flash全屏。允许值：true/false\r\n * @param {boolean} options.seamlesstabbing \t允许设置执行无缝跳格，从而使用户能跳出flash应用程序。该参数只能在安装Flash7及更高版本的Windows中使用。允许值：true/false\r\n * @param {boolean} options.devicefont \t\t\t设置静态文本对象是否以设备字体呈现。允许值：true/false\r\n * @param {boolean} options.swliveconnect \t\t第一次加载flash时浏览器是否应启动Java。允许值：true/false\r\n * @param {Object} \toptions.vars \t\t\t\t要传递给flash的参数，支持JSON或string类型。\r\n * \r\n * @param {HTMLElement|string} [container] \t\tflash对象的父容器元素，不传递该参数时在当前代码位置创建flash对象。\r\n * @meta standard\r\n * @see baidu.swf.createHTML,baidu.swf.getMovie\r\n */\r\nbaidu.swf.create = function (options, target) {\r\n    options = options || {};\r\n    var html = baidu.swf.createHTML(options) \r\n               || options['errorMessage'] \r\n               || '';\r\n                \r\n    if (target && 'string' == typeof target) {\r\n        target = document.getElementById(target);\r\n    }\r\n    baidu.dom.insertHTML( target || document.body ,'beforeEnd',html );\r\n};\r\n/**\r\n * 判断是否为ie浏览器\r\n * @name baidu.browser.ie\r\n * @field\r\n * @grammar baidu.browser.ie\r\n * @returns {Number} IE版本号\r\n */\r\nbaidu.browser.ie = baidu.ie = /msie (\\d+\\.\\d+)/i.test(navigator.userAgent) ? (document.documentMode || + RegExp['\\x241']) : undefined;\r\n\r\n/**\r\n * 移除数组中的项\r\n * @name baidu.array.remove\r\n * @function\r\n * @grammar baidu.array.remove(source, match)\r\n * @param {Array} source 需要移除项的数组\r\n * @param {Any} match 要移除的项\r\n * @meta standard\r\n * @see baidu.array.removeAt\r\n *             \r\n * @returns {Array} 移除后的数组\r\n */\r\nbaidu.array.remove = function (source, match) {\r\n    var len = source.length;\r\n        \r\n    while (len--) {\r\n        if (len in source && source[len] === match) {\r\n            source.splice(len, 1);\r\n        }\r\n    }\r\n    return source;\r\n};\r\n\r\n/**\r\n * 判断目标参数是否Array对象\r\n * @name baidu.lang.isArray\r\n * @function\r\n * @grammar baidu.lang.isArray(source)\r\n * @param {Any} source 目标参数\r\n * @meta standard\r\n * @see baidu.lang.isString,baidu.lang.isObject,baidu.lang.isNumber,baidu.lang.isElement,baidu.lang.isBoolean,baidu.lang.isDate\r\n *             \r\n * @returns {boolean} 类型判断结果\r\n */\r\nbaidu.lang.isArray = function (source) {\r\n    return '[object Array]' == Object.prototype.toString.call(source);\r\n};\r\n\r\n\r\n\r\n/**\r\n * 将一个变量转换成array\r\n * @name baidu.lang.toArray\r\n * @function\r\n * @grammar baidu.lang.toArray(source)\r\n * @param {mix} source 需要转换成array的变量\r\n * @version 1.3\r\n * @meta standard\r\n * @returns {array} 转换后的array\r\n */\r\nbaidu.lang.toArray = function (source) {\r\n    if (source === null || source === undefined)\r\n        return [];\r\n    if (baidu.lang.isArray(source))\r\n        return source;\r\n    if (typeof source.length !== 'number' || typeof source === 'string' || baidu.lang.isFunction(source)) {\r\n        return [source];\r\n    }\r\n    if (source.item) {\r\n        var l = source.length, array = new Array(l);\r\n        while (l--)\r\n            array[l] = source[l];\r\n        return array;\r\n    }\r\n\r\n    return [].slice.call(source);\r\n};\r\n\r\n/**\r\n * 获得flash对象的实例\r\n * @name baidu.swf.getMovie\r\n * @function\r\n * @grammar baidu.swf.getMovie(name)\r\n * @param {string} name flash对象的名称\r\n * @see baidu.swf.create\r\n * @meta standard\r\n * @returns {HTMLElement} flash对象的实例\r\n */\r\nbaidu.swf.getMovie = function (name) {\r\n\tvar movie = document[name], ret;\r\n    return baidu.browser.ie == 9 ?\r\n    \tmovie && movie.length ? \r\n    \t\t(ret = baidu.array.remove(baidu.lang.toArray(movie),function(item){\r\n    \t\t\treturn item.tagName.toLowerCase() != \"embed\";\r\n    \t\t})).length == 1 ? ret[0] : ret\r\n    \t\t: movie\r\n    \t: movie || window[name];\r\n};\r\n\r\n\r\nbaidu.flash._Base = (function(){\r\n   \r\n    var prefix = 'bd__flash__';\r\n\r\n    /**\r\n     * 创建一个随机的字符串\r\n     * @private\r\n     * @return {String}\r\n     */\r\n    function _createString(){\r\n        return  prefix + Math.floor(Math.random() * 2147483648).toString(36);\r\n    };\r\n   \r\n    /**\r\n     * 检查flash状态\r\n     * @private\r\n     * @param {Object} target flash对象\r\n     * @return {Boolean}\r\n     */\r\n    function _checkReady(target){\r\n        if(typeof target !== 'undefined' && typeof target.flashInit !== 'undefined' && target.flashInit()){\r\n            return true;\r\n        }else{\r\n            return false;\r\n        }\r\n    };\r\n\r\n    /**\r\n     * 调用之前进行压栈的函数\r\n     * @private\r\n     * @param {Array} callQueue 调用队列\r\n     * @param {Object} target flash对象\r\n     * @return {Null}\r\n     */\r\n    function _callFn(callQueue, target){\r\n        var result = null;\r\n        \r\n        callQueue = callQueue.reverse();\r\n        baidu.each(callQueue, function(item){\r\n            result = target.call(item.fnName, item.params);\r\n            item.callBack(result);\r\n        });\r\n    };\r\n\r\n    /**\r\n     * 为传入的匿名函数创建函数名\r\n     * @private\r\n     * @param {String|Function} fun 传入的匿名函数或者函数名\r\n     * @return {String}\r\n     */\r\n    function _createFunName(fun){\r\n        var name = '';\r\n\r\n        if(baidu.lang.isFunction(fun)){\r\n            name = _createString();\r\n            window[name] = function(){\r\n                fun.apply(window, arguments);\r\n            };\r\n\r\n            return name;\r\n        }else if(baidu.lang.isString){\r\n            return fun;\r\n        }\r\n    };\r\n\r\n    /**\r\n     * 绘制flash\r\n     * @private\r\n     * @param {Object} options 创建参数\r\n     * @return {Object} \r\n     */\r\n    function _render(options){\r\n        if(!options.id){\r\n            options.id = _createString();\r\n        }\r\n        \r\n        var container = options.container || '';\r\n        delete(options.container);\r\n        \r\n        baidu.swf.create(options, container);\r\n        \r\n        return baidu.swf.getMovie(options.id);\r\n    };\r\n\r\n    return function(options, callBack){\r\n        var me = this,\r\n            autoRender = (typeof options.autoRender !== 'undefined' ? options.autoRender : true),\r\n            createOptions = options.createOptions || {},\r\n            target = null,\r\n            isReady = false,\r\n            callQueue = [],\r\n            timeHandle = null,\r\n            callBack = callBack || [];\r\n\r\n        /**\r\n         * 将flash文件绘制到页面上\r\n         * @public\r\n         * @return {Null}\r\n         */\r\n        me.render = function(){\r\n            target = _render(createOptions);\r\n            \r\n            if(callBack.length > 0){\r\n                baidu.each(callBack, function(funName, index){\r\n                    callBack[index] = _createFunName(options[funName] || new Function());\r\n                });    \r\n            }\r\n            me.call('setJSFuncName', [callBack]);\r\n        };\r\n\r\n        /**\r\n         * 返回flash状态\r\n         * @return {Boolean}\r\n         */\r\n        me.isReady = function(){\r\n            return isReady;\r\n        };\r\n\r\n        /**\r\n         * 调用flash接口的统一入口\r\n         * @param {String} fnName 调用的函数名\r\n         * @param {Array} params 传入的参数组成的数组,若不许要参数，需传入空数组\r\n         * @param {Function} [callBack] 异步调用后将返回值作为参数的调用回调函数，如无返回值，可以不传入此参数\r\n         * @return {Null}\r\n        */\r\n        me.call = function(fnName, params, callBack){\r\n            if(!fnName) return null;\r\n            callBack = callBack || new Function();\r\n\r\n            var result = null;\r\n    \r\n            if(isReady){\r\n                result = target.call(fnName, params);\r\n                callBack(result);\r\n            }else{\r\n                callQueue.push({\r\n                    fnName: fnName,\r\n                    params: params,\r\n                    callBack: callBack\r\n                });\r\n    \r\n                (!timeHandle) && (timeHandle = setInterval(_check, 200));\r\n            }\r\n        };\r\n    \r\n        /**\r\n         * 为传入的匿名函数创建函数名\r\n         * @public\r\n         * @param {String|Function} fun 传入的匿名函数或者函数名\r\n         * @return {String}\r\n         */\r\n        me.createFunName = function(fun){\r\n            return _createFunName(fun);    \r\n        };\r\n\r\n        /**\r\n         * 检查flash是否ready， 并进行调用\r\n         * @private\r\n         * @return {Null}\r\n         */\r\n        function _check(){\r\n            if(_checkReady(target)){\r\n                clearInterval(timeHandle);\r\n                timeHandle = null;\r\n                _call();\r\n\r\n                isReady = true;\r\n            }               \r\n        };\r\n\r\n        /**\r\n         * 调用之前进行压栈的函数\r\n         * @private\r\n         * @return {Null}\r\n         */\r\n        function _call(){\r\n            _callFn(callQueue, target);\r\n            callQueue = [];\r\n        }\r\n\r\n        autoRender && me.render(); \r\n    };\r\n})();\r\n\r\n\r\n\r\n/**\r\n * 创建flash based imageUploader\r\n * @class\r\n * @grammar baidu.flash.imageUploader(options)\r\n * @param {Object} createOptions 创建flash时需要的参数，请参照baidu.swf.create文档\r\n * @config {Object} vars 创建imageUploader时所需要的参数\r\n * @config {Number} vars.gridWidth 每一个预览图片所占的宽度，应该为flash寛的整除\r\n * @config {Number} vars.gridHeight 每一个预览图片所占的高度，应该为flash高的整除\r\n * @config {Number} vars.picWidth 单张预览图片的宽度\r\n * @config {Number} vars.picHeight 单张预览图片的高度\r\n * @config {String} vars.uploadDataFieldName POST请求中图片数据的key,默认值'picdata'\r\n * @config {String} vars.picDescFieldName POST请求中图片描述的key,默认值'picDesc'\r\n * @config {Number} vars.maxSize 文件的最大体积,单位'MB'\r\n * @config {Number} vars.compressSize 上传前如果图片体积超过该值，会先压缩\r\n * @config {Number} vars.maxNum:32 最大上传多少个文件\r\n * @config {Number} vars.compressLength 能接受的最大边长，超过该值会等比压缩\r\n * @config {String} vars.url 上传的url地址\r\n * @config {Number} vars.mode mode == 0时，是使用滚动条，mode == 1时，拉伸flash, 默认值为0\r\n * @see baidu.swf.createHTML\r\n * @param {String} backgroundUrl 背景图片路径\r\n * @param {String} listBacgroundkUrl 布局控件背景\r\n * @param {String} buttonUrl 按钮图片不背景\r\n * @param {String|Function} selectFileCallback 选择文件的回调\r\n * @param {String|Function} exceedFileCallback文件超出限制的最大体积时的回调\r\n * @param {String|Function} deleteFileCallback 删除文件的回调\r\n * @param {String|Function} startUploadCallback 开始上传某个文件时的回调\r\n * @param {String|Function} uploadCompleteCallback 某个文件上传完成的回调\r\n * @param {String|Function} uploadErrorCallback 某个文件上传失败的回调\r\n * @param {String|Function} allCompleteCallback 全部上传完成时的回调\r\n * @param {String|Function} changeFlashHeight 改变Flash的高度，mode==1的时候才有用\r\n */ \r\nbaidu.flash.imageUploader = baidu.flash.imageUploader || function(options){\r\n   \r\n    var me = this,\r\n        options = options || {},\r\n        _flash = new baidu.flash._Base(options, [\r\n            'selectFileCallback', \r\n            'exceedFileCallback', \r\n            'deleteFileCallback', \r\n            'startUploadCallback',\r\n            'uploadCompleteCallback',\r\n            'uploadErrorCallback',\r\n            'allCompleteCallback',\r\n            'changeFlashHeight'\r\n        ]);\r\n    /**\r\n     * 开始或回复上传图片\r\n     * @public\r\n     * @return {Null}\r\n     */\r\n    me.upload = function(){\r\n        _flash.call('upload');\r\n    };\r\n\r\n    /**\r\n     * 暂停上传图片\r\n     * @public\r\n     * @return {Null}\r\n     */\r\n    me.pause = function(){\r\n        _flash.call('pause');\r\n    };\r\n    me.addCustomizedParams = function(index,obj){\r\n        _flash.call('addCustomizedParams',[index,obj]);\r\n    }\r\n};\r\n\r\n/**\r\n * 操作原生对象的方法\r\n * @namespace baidu.object\r\n */\r\nbaidu.object = baidu.object || {};\r\n\r\n\r\n/**\r\n * 将源对象的所有属性拷贝到目标对象中\r\n * @author erik\r\n * @name baidu.object.extend\r\n * @function\r\n * @grammar baidu.object.extend(target, source)\r\n * @param {Object} target 目标对象\r\n * @param {Object} source 源对象\r\n * @see baidu.array.merge\r\n * @remark\r\n * \r\n1.目标对象中，与源对象key相同的成员将会被覆盖。<br>\r\n2.源对象的prototype成员不会拷贝。\r\n\t\t\r\n * @shortcut extend\r\n * @meta standard\r\n *             \r\n * @returns {Object} 目标对象\r\n */\r\nbaidu.extend =\r\nbaidu.object.extend = function (target, source) {\r\n    for (var p in source) {\r\n        if (source.hasOwnProperty(p)) {\r\n            target[p] = source[p];\r\n        }\r\n    }\r\n    \r\n    return target;\r\n};\r\n\r\n\r\n\r\n\r\n\r\n/**\r\n * 创建flash based fileUploader\r\n * @class\r\n * @grammar baidu.flash.fileUploader(options)\r\n * @param {Object} options\r\n * @config {Object} createOptions 创建flash时需要的参数，请参照baidu.swf.create文档\r\n * @config {String} createOptions.width\r\n * @config {String} createOptions.height\r\n * @config {Number} maxNum 最大可选文件数\r\n * @config {Function|String} selectFile\r\n * @config {Function|String} exceedMaxSize\r\n * @config {Function|String} deleteFile\r\n * @config {Function|String} uploadStart\r\n * @config {Function|String} uploadComplete\r\n * @config {Function|String} uploadError\r\n * @config {Function|String} uploadProgress\r\n */\r\nbaidu.flash.fileUploader = baidu.flash.fileUploader || function(options){\r\n    var me = this,\r\n        options = options || {};\r\n    \r\n    options.createOptions = baidu.extend({\r\n        wmod: 'transparent'\r\n    },options.createOptions || {});\r\n    \r\n    var _flash = new baidu.flash._Base(options, [\r\n        'selectFile',\r\n        'exceedMaxSize',\r\n        'deleteFile',\r\n        'uploadStart',\r\n        'uploadComplete',\r\n        'uploadError', \r\n        'uploadProgress'\r\n    ]);\r\n\r\n    _flash.call('setMaxNum', options.maxNum ? [options.maxNum] : [1]);\r\n\r\n    /**\r\n     * 设置当鼠标移动到flash上时，是否变成手型\r\n     * @public\r\n     * @param {Boolean} isCursor\r\n     * @return {Null}\r\n     */\r\n    me.setHandCursor = function(isCursor){\r\n        _flash.call('setHandCursor', [isCursor || false]);\r\n    };\r\n\r\n    /**\r\n     * 设置鼠标相应函数名\r\n     * @param {String|Function} fun\r\n     */\r\n    me.setMSFunName = function(fun){\r\n        _flash.call('setMSFunName',[_flash.createFunName(fun)]);\r\n    }; \r\n\r\n    /**\r\n     * 执行上传操作\r\n     * @param {String} url 上传的url\r\n     * @param {String} fieldName 上传的表单字段名\r\n     * @param {Object} postData 键值对，上传的POST数据\r\n     * @param {Number|Array|null|-1} [index]上传的文件序列\r\n     *                            Int值上传该文件\r\n     *                            Array一次串行上传该序列文件\r\n     *                            -1/null上传所有文件\r\n     * @return {Null}\r\n     */\r\n    me.upload = function(url, fieldName, postData, index){\r\n\r\n        if(typeof url !== 'string' || typeof fieldName !== 'string') return null;\r\n        if(typeof index === 'undefined') index = -1;\r\n\r\n        _flash.call('upload', [url, fieldName, postData, index]);\r\n    };\r\n\r\n    /**\r\n     * 取消上传操作\r\n     * @public\r\n     * @param {Number|-1} index\r\n     */\r\n    me.cancel = function(index){\r\n        if(typeof index === 'undefined') index = -1;\r\n        _flash.call('cancel', [index]);\r\n    };\r\n\r\n    /**\r\n     * 删除文件\r\n     * @public\r\n     * @param {Number|Array} [index] 要删除的index，不传则全部删除\r\n     * @param {Function} callBack\r\n     * */\r\n    me.deleteFile = function(index, callBack){\r\n\r\n        var callBackAll = function(list){\r\n                callBack && callBack(list);\r\n            };\r\n\r\n        if(typeof index === 'undefined'){\r\n            _flash.call('deleteFilesAll', [], callBackAll);\r\n            return;\r\n        };\r\n        \r\n        if(typeof index === 'Number') index = [index];\r\n        index.sort(function(a,b){\r\n            return b-a;\r\n        });\r\n        baidu.each(index, function(item){\r\n            _flash.call('deleteFileBy', item, callBackAll);\r\n        });\r\n    };\r\n\r\n    /**\r\n     * 添加文件类型，支持macType\r\n     * @public\r\n     * @param {Object|Array[Object]} type {description:String, extention:String}\r\n     * @return {Null};\r\n     */\r\n    me.addFileType = function(type){\r\n        var type = type || [[]];\r\n        \r\n        if(type instanceof Array) type = [type];\r\n        else type = [[type]];\r\n        _flash.call('addFileTypes', type);\r\n    };\r\n    \r\n    /**\r\n     * 设置文件类型，支持macType\r\n     * @public\r\n     * @param {Object|Array[Object]} type {description:String, extention:String}\r\n     * @return {Null};\r\n     */\r\n    me.setFileType = function(type){\r\n        var type = type || [[]];\r\n        \r\n        if(type instanceof Array) type = [type];\r\n        else type = [[type]];\r\n        _flash.call('setFileTypes', type);\r\n    };\r\n\r\n    /**\r\n     * 设置可选文件的数量限制\r\n     * @public\r\n     * @param {Number} num\r\n     * @return {Null}\r\n     */\r\n    me.setMaxNum = function(num){\r\n        _flash.call('setMaxNum', [num]);\r\n    };\r\n\r\n    /**\r\n     * 设置可选文件大小限制，以兆M为单位\r\n     * @public\r\n     * @param {Number} num,0为无限制\r\n     * @return {Null}\r\n     */\r\n    me.setMaxSize = function(num){\r\n        _flash.call('setMaxSize', [num]);\r\n    };\r\n\r\n    /**\r\n     * @public\r\n     */\r\n    me.getFileAll = function(callBack){\r\n        _flash.call('getFileAll', [], callBack);\r\n    };\r\n\r\n    /**\r\n     * @public\r\n     * @param {Number} index\r\n     * @param {Function} [callBack]\r\n     */\r\n    me.getFileByIndex = function(index, callBack){\r\n        _flash.call('getFileByIndex', [], callBack);\r\n    };\r\n\r\n    /**\r\n     * @public\r\n     * @param {Number} index\r\n     * @param {function} [callBack]\r\n     */\r\n    me.getStatusByIndex = function(index, callBack){\r\n        _flash.call('getStatusByIndex', [], callBack);\r\n    };\r\n};\r\n\r\n/**\r\n * 使用动态script标签请求服务器资源，包括由服务器端的回调和浏览器端的回调\r\n * @namespace baidu.sio\r\n */\r\nbaidu.sio = baidu.sio || {};\r\n\r\n/**\r\n * \r\n * @param {HTMLElement} src script节点\r\n * @param {String} url script节点的地址\r\n * @param {String} [charset] 编码\r\n */\r\nbaidu.sio._createScriptTag = function(scr, url, charset){\r\n    scr.setAttribute('type', 'text/javascript');\r\n    charset && scr.setAttribute('charset', charset);\r\n    scr.setAttribute('src', url);\r\n    document.getElementsByTagName('head')[0].appendChild(scr);\r\n};\r\n\r\n/**\r\n * 删除script的属性，再删除script标签，以解决修复内存泄漏的问题\r\n * \r\n * @param {HTMLElement} src script节点\r\n */\r\nbaidu.sio._removeScriptTag = function(scr){\r\n    if (scr.clearAttributes) {\r\n        scr.clearAttributes();\r\n    } else {\r\n        for (var attr in scr) {\r\n            if (scr.hasOwnProperty(attr)) {\r\n                delete scr[attr];\r\n            }\r\n        }\r\n    }\r\n    if(scr && scr.parentNode){\r\n        scr.parentNode.removeChild(scr);\r\n    }\r\n    scr = null;\r\n};\r\n\r\n\r\n/**\r\n * 通过script标签加载数据，加载完成由浏览器端触发回调\r\n * @name baidu.sio.callByBrowser\r\n * @function\r\n * @grammar baidu.sio.callByBrowser(url, opt_callback, opt_options)\r\n * @param {string} url 加载数据的url\r\n * @param {Function|string} opt_callback 数据加载结束时调用的函数或函数名\r\n * @param {Object} opt_options 其他可选项\r\n * @config {String} [charset] script的字符集\r\n * @config {Integer} [timeOut] 超时时间，超过这个时间将不再响应本请求，并触发onfailure函数\r\n * @config {Function} [onfailure] timeOut设定后才生效，到达超时时间时触发本函数\r\n * @remark\r\n * 1、与callByServer不同，callback参数只支持Function类型，不支持string。\r\n * 2、如果请求了一个不存在的页面，callback函数在IE/opera下也会被调用，因此使用者需要在onsuccess函数中判断数据是否正确加载。\r\n * @meta standard\r\n * @see baidu.sio.callByServer\r\n */\r\nbaidu.sio.callByBrowser = function (url, opt_callback, opt_options) {\r\n    var scr = document.createElement(\"SCRIPT\"),\r\n        scriptLoaded = 0,\r\n        options = opt_options || {},\r\n        charset = options['charset'],\r\n        callback = opt_callback || function(){},\r\n        timeOut = options['timeOut'] || 0,\r\n        timer;\r\n    scr.onload = scr.onreadystatechange = function () {\r\n        if (scriptLoaded) {\r\n            return;\r\n        }\r\n        \r\n        var readyState = scr.readyState;\r\n        if ('undefined' == typeof readyState\r\n            || readyState == \"loaded\"\r\n            || readyState == \"complete\") {\r\n            scriptLoaded = 1;\r\n            try {\r\n                callback();\r\n                clearTimeout(timer);\r\n            } finally {\r\n                scr.onload = scr.onreadystatechange = null;\r\n                baidu.sio._removeScriptTag(scr);\r\n            }\r\n        }\r\n    };\r\n\r\n    if( timeOut ){\r\n        timer = setTimeout(function(){\r\n            scr.onload = scr.onreadystatechange = null;\r\n            baidu.sio._removeScriptTag(scr);\r\n            options.onfailure && options.onfailure();\r\n        }, timeOut);\r\n    }\r\n    \r\n    baidu.sio._createScriptTag(scr, url, charset);\r\n};\r\n\r\n/**\r\n * 通过script标签加载数据，加载完成由服务器端触发回调\r\n * @name baidu.sio.callByServer\r\n * @function\r\n * @grammar baidu.sio.callByServer(url, callback[, opt_options])\r\n * @param {string} url 加载数据的url.\r\n * @param {Function|string} callback 服务器端调用的函数或函数名。如果没有指定本参数，将在URL中寻找options['queryField']做为callback的方法名.\r\n * @param {Object} opt_options 加载数据时的选项.\r\n * @config {string} [charset] script的字符集\r\n * @config {string} [queryField] 服务器端callback请求字段名，默认为callback\r\n * @config {Integer} [timeOut] 超时时间(单位：ms)，超过这个时间将不再响应本请求，并触发onfailure函数\r\n * @config {Function} [onfailure] timeOut设定后才生效，到达超时时间时触发本函数\r\n * @remark\r\n * 如果url中已经包含key为“options['queryField']”的query项，将会被替换成callback中参数传递或自动生成的函数名。\r\n * @meta standard\r\n * @see baidu.sio.callByBrowser\r\n */\r\nbaidu.sio.callByServer = /**@function*/function(url, callback, opt_options) {\r\n    var scr = document.createElement('SCRIPT'),\r\n        prefix = 'bd__cbs__',\r\n        callbackName,\r\n        callbackImpl,\r\n        options = opt_options || {},\r\n        charset = options['charset'],\r\n        queryField = options['queryField'] || 'callback',\r\n        timeOut = options['timeOut'] || 0,\r\n        timer,\r\n        reg = new RegExp('(\\\\?|&)' + queryField + '=([^&]*)'),\r\n        matches;\r\n\r\n    if (baidu.lang.isFunction(callback)) {\r\n        callbackName = prefix + Math.floor(Math.random() * 2147483648).toString(36);\r\n        window[callbackName] = getCallBack(0);\r\n    } else if(baidu.lang.isString(callback)){\r\n        callbackName = callback;\r\n    } else {\r\n        if (matches = reg.exec(url)) {\r\n            callbackName = matches[2];\r\n        }\r\n    }\r\n\r\n    if( timeOut ){\r\n        timer = setTimeout(getCallBack(1), timeOut);\r\n    }\r\n    url = url.replace(reg, '\\x241' + queryField + '=' + callbackName);\r\n    \r\n    if (url.search(reg) < 0) {\r\n        url += (url.indexOf('?') < 0 ? '?' : '&') + queryField + '=' + callbackName;\r\n    }\r\n    baidu.sio._createScriptTag(scr, url, charset);\r\n\r\n    /*\r\n     * 返回一个函数，用于立即（挂在window上）或者超时（挂在setTimeout中）时执行\r\n     */\r\n    function getCallBack(onTimeOut){\r\n        /*global callbackName, callback, scr, options;*/\r\n        return function(){\r\n            try {\r\n                if( onTimeOut ){\r\n                    options.onfailure && options.onfailure();\r\n                }else{\r\n                    callback.apply(window, arguments);\r\n                    clearTimeout(timer);\r\n                }\r\n                window[callbackName] = null;\r\n                delete window[callbackName];\r\n            } catch (exception) {\r\n            } finally {\r\n                baidu.sio._removeScriptTag(scr);\r\n            }\r\n        }\r\n    }\r\n};\r\n\r\n/**\r\n * 通过请求一个图片的方式令服务器存储一条日志\r\n * @function\r\n * @grammar baidu.sio.log(url)\r\n * @param {string} url 要发送的地址.\r\n * @author: int08h,leeight\r\n */\r\nbaidu.sio.log = function(url) {\r\n  var img = new Image(),\r\n      key = 'tangram_sio_log_' + Math.floor(Math.random() *\r\n            2147483648).toString(36);\r\n  window[key] = img;\r\n\r\n  img.onload = img.onerror = img.onabort = function() {\r\n    img.onload = img.onerror = img.onabort = null;\r\n\r\n    window[key] = null;\r\n    img = null;\r\n  };\r\n  img.src = url;\r\n};\r\n\r\n\r\n\r\n/*\r\n * Tangram\r\n * Copyright 2009 Baidu Inc. All rights reserved.\r\n * \r\n * path: baidu/json.js\r\n * author: erik\r\n * version: 1.1.0\r\n * date: 2009/12/02\r\n */\r\n\r\n\r\n/**\r\n * 操作json对象的方法\r\n * @namespace baidu.json\r\n */\r\nbaidu.json = baidu.json || {};\r\n/*\r\n * Tangram\r\n * Copyright 2009 Baidu Inc. All rights reserved.\r\n * \r\n * path: baidu/json/parse.js\r\n * author: erik, berg\r\n * version: 1.2\r\n * date: 2009/11/23\r\n */\r\n\r\n\r\n\r\n/**\r\n * 将字符串解析成json对象。注：不会自动祛除空格\r\n * @name baidu.json.parse\r\n * @function\r\n * @grammar baidu.json.parse(data)\r\n * @param {string} source 需要解析的字符串\r\n * @remark\r\n * 该方法的实现与ecma-262第五版中规定的JSON.parse不同，暂时只支持传入一个参数。后续会进行功能丰富。\r\n * @meta standard\r\n * @see baidu.json.stringify,baidu.json.decode\r\n *             \r\n * @returns {JSON} 解析结果json对象\r\n */\r\nbaidu.json.parse = function (data) {\r\n    //2010/12/09：更新至不使用原生parse，不检测用户输入是否正确\r\n    return (new Function(\"return (\" + data + \")\"))();\r\n};\r\n/*\r\n * Tangram\r\n * Copyright 2009 Baidu Inc. All rights reserved.\r\n * \r\n * path: baidu/json/decode.js\r\n * author: erik, cat\r\n * version: 1.3.4\r\n * date: 2010/12/23\r\n */\r\n\r\n\r\n\r\n/**\r\n * 将字符串解析成json对象，为过时接口，今后会被baidu.json.parse代替\r\n * @name baidu.json.decode\r\n * @function\r\n * @grammar baidu.json.decode(source)\r\n * @param {string} source 需要解析的字符串\r\n * @meta out\r\n * @see baidu.json.encode,baidu.json.parse\r\n *             \r\n * @returns {JSON} 解析结果json对象\r\n */\r\nbaidu.json.decode = baidu.json.parse;\r\n/*\r\n * Tangram\r\n * Copyright 2009 Baidu Inc. All rights reserved.\r\n * \r\n * path: baidu/json/stringify.js\r\n * author: erik\r\n * version: 1.1.0\r\n * date: 2010/01/11\r\n */\r\n\r\n\r\n\r\n/**\r\n * 将json对象序列化\r\n * @name baidu.json.stringify\r\n * @function\r\n * @grammar baidu.json.stringify(value)\r\n * @param {JSON} value 需要序列化的json对象\r\n * @remark\r\n * 该方法的实现与ecma-262第五版中规定的JSON.stringify不同，暂时只支持传入一个参数。后续会进行功能丰富。\r\n * @meta standard\r\n * @see baidu.json.parse,baidu.json.encode\r\n *             \r\n * @returns {string} 序列化后的字符串\r\n */\r\nbaidu.json.stringify = (function () {\r\n    /**\r\n     * 字符串处理时需要转义的字符表\r\n     * @private\r\n     */\r\n    var escapeMap = {\r\n        \"\\b\": '\\\\b',\r\n        \"\\t\": '\\\\t',\r\n        \"\\n\": '\\\\n',\r\n        \"\\f\": '\\\\f',\r\n        \"\\r\": '\\\\r',\r\n        '\"' : '\\\\\"',\r\n        \"\\\\\": '\\\\\\\\'\r\n    };\r\n    \r\n    /**\r\n     * 字符串序列化\r\n     * @private\r\n     */\r\n    function encodeString(source) {\r\n        if (/[\"\\\\\\x00-\\x1f]/.test(source)) {\r\n            source = source.replace(\r\n                /[\"\\\\\\x00-\\x1f]/g, \r\n                function (match) {\r\n                    var c = escapeMap[match];\r\n                    if (c) {\r\n                        return c;\r\n                    }\r\n                    c = match.charCodeAt();\r\n                    return \"\\\\u00\" \r\n                            + Math.floor(c / 16).toString(16) \r\n                            + (c % 16).toString(16);\r\n                });\r\n        }\r\n        return '\"' + source + '\"';\r\n    }\r\n    \r\n    /**\r\n     * 数组序列化\r\n     * @private\r\n     */\r\n    function encodeArray(source) {\r\n        var result = [\"[\"], \r\n            l = source.length,\r\n            preComma, i, item;\r\n            \r\n        for (i = 0; i < l; i++) {\r\n            item = source[i];\r\n            \r\n            switch (typeof item) {\r\n            case \"undefined\":\r\n            case \"function\":\r\n            case \"unknown\":\r\n                break;\r\n            default:\r\n                if(preComma) {\r\n                    result.push(',');\r\n                }\r\n                result.push(baidu.json.stringify(item));\r\n                preComma = 1;\r\n            }\r\n        }\r\n        result.push(\"]\");\r\n        return result.join(\"\");\r\n    }\r\n    \r\n    /**\r\n     * 处理日期序列化时的补零\r\n     * @private\r\n     */\r\n    function pad(source) {\r\n        return source < 10 ? '0' + source : source;\r\n    }\r\n    \r\n    /**\r\n     * 日期序列化\r\n     * @private\r\n     */\r\n    function encodeDate(source){\r\n        return '\"' + source.getFullYear() + \"-\" \r\n                + pad(source.getMonth() + 1) + \"-\" \r\n                + pad(source.getDate()) + \"T\" \r\n                + pad(source.getHours()) + \":\" \r\n                + pad(source.getMinutes()) + \":\" \r\n                + pad(source.getSeconds()) + '\"';\r\n    }\r\n    \r\n    return function (value) {\r\n        switch (typeof value) {\r\n        case 'undefined':\r\n            return 'undefined';\r\n            \r\n        case 'number':\r\n            return isFinite(value) ? String(value) : \"null\";\r\n            \r\n        case 'string':\r\n            return encodeString(value);\r\n            \r\n        case 'boolean':\r\n            return String(value);\r\n            \r\n        default:\r\n            if (value === null) {\r\n                return 'null';\r\n            } else if (value instanceof Array) {\r\n                return encodeArray(value);\r\n            } else if (value instanceof Date) {\r\n                return encodeDate(value);\r\n            } else {\r\n                var result = ['{'],\r\n                    encode = baidu.json.stringify,\r\n                    preComma,\r\n                    item;\r\n                    \r\n                for (var key in value) {\r\n                    if (Object.prototype.hasOwnProperty.call(value, key)) {\r\n                        item = value[key];\r\n                        switch (typeof item) {\r\n                        case 'undefined':\r\n                        case 'unknown':\r\n                        case 'function':\r\n                            break;\r\n                        default:\r\n                            if (preComma) {\r\n                                result.push(',');\r\n                            }\r\n                            preComma = 1;\r\n                            result.push(encode(key) + ':' + encode(item));\r\n                        }\r\n                    }\r\n                }\r\n                result.push('}');\r\n                return result.join('');\r\n            }\r\n        }\r\n    };\r\n})();\r\n/*\r\n * Tangram\r\n * Copyright 2009 Baidu Inc. All rights reserved.\r\n * \r\n * path: baidu/json/encode.js\r\n * author: erik, cat\r\n * version: 1.3.4\r\n * date: 2010/12/23\r\n */\r\n\r\n\r\n\r\n/**\r\n * 将json对象序列化，为过时接口，今后会被baidu.json.stringify代替\r\n * @name baidu.json.encode\r\n * @function\r\n * @grammar baidu.json.encode(value)\r\n * @param {JSON} value 需要序列化的json对象\r\n * @meta out\r\n * @see baidu.json.decode,baidu.json.stringify\r\n *             \r\n * @returns {string} 序列化后的字符串\r\n */\r\nbaidu.json.encode = baidu.json.stringify;\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/wordimage/wordimage.html",
    "content": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\r\n        \"http://www.w3.org/TR/html4/loose.dtd\">\r\n<html>\r\n<head>\r\n    <title></title>\r\n    <meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"/>\r\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n    <style type=\"text/css\">\r\n        .wrapper{width: 600px;padding: 10px;height: 352px;overflow: hidden;position: relative;border-bottom: 1px solid #d7d7d7}\r\n        .localPath input{float: left;width: 350px;line-height: 20px;height: 20px;}\r\n        #clipboard{float:left;width: 70px;height: 30px; }\r\n        .description{ color: #0066cc; margin-top: 2px; width: 450px; height: 45px;float: left;line-height: 22px}\r\n        #upload{width: 100px;height: 30px;float: right; margin:10px 2px 0 0;cursor: pointer;}\r\n        #msg{ width: 140px; height: 30px; line-height:25px;float: left;color: red}\r\n    </style>\r\n</head>\r\n<body>\r\n    <div class=\"wrapper\">\r\n        <div class=\"localPath\">\r\n            <input id=\"localPath\" type=\"text\" readonly />\r\n            <div id=\"clipboard\"></div>\r\n            <div id=\"msg\"></div>\r\n        </div>\r\n        <div id=\"flashContainer\"></div>\r\n        <div>\r\n            <div id=\"upload\" style=\"display: none\" ><img id=\"uploadBtn\"></div>\r\n            <div class=\"description\">\r\n                <span style=\"color: red\"><var id=\"lang_resave\"></var>: </span><var id=\"lang_step\"></var>\r\n            </div>\r\n          </div>\r\n    </div>\r\n    <script type=\"text/javascript\" src=\"tangram.js\"></script>\r\n    <script type=\"text/javascript\" src=\"wordimage.js\"></script>\r\n    <script type=\"text/javascript\">\r\n        editor.setOpt({\r\n            wordImageFieldName:\"upfile\",\r\n            compressSide:0,\r\n            maxImageSideLength:900\r\n        });\r\n\r\n            //全局变量\r\n        var imageUrls = [],          //用于保存从服务器返回的图片信息数组\r\n            selectedImageCount = 0,  //当前已选择的但未上传的图片数量\r\n            optImageUrl = editor.getActionUrl(editor.getOpt('imageActionName')),\r\n            optImageFieldName = editor.getOpt('imageFieldName'),\r\n            optImageCompressBorder = editor.getOpt('imageCompressEnable') ? editor.getOpt('imageCompressBorder'):null,\r\n            maxSize = editor.getOpt('imageMaxSize') / 1024,\r\n            extension = editor.getOpt('imageAllowFiles').join(';').replace(/\\./g, '*.');\r\n\r\n        /* 添加额外的GET参数 */\r\n        var params = utils.serializeParam(editor.queryCommandValue('serverparam')) || '',\r\n            urlWidthParams = optImageUrl + (optImageUrl.indexOf('?') == -1 ? '?':'&') + params;\r\n\r\n        utils.domReady(function(){\r\n            //创建Flash相关的参数集合\r\n            var flashOptions = {\r\n                container:\"flashContainer\",                                                    //flash容器id\r\n                url:urlWidthParams,                                           // 上传处理页面的url地址\r\n                ext:editor.queryCommandValue('serverParam') || {},                                 //可向服务器提交的自定义参数列表\r\n                fileType:'{\"description\":\"'+lang.fileType+'\", \"extension\":\"' + extension + '\"}',     //上传文件格式限制\r\n                flashUrl:'imageUploader.swf',                                                  //上传用的flash组件地址\r\n                width:600,          //flash的宽度\r\n                height:272,         //flash的高度\r\n                gridWidth:120,     // 每一个预览图片所占的宽度\r\n                gridHeight:120,    // 每一个预览图片所占的高度\r\n                picWidth:100,      // 单张预览图片的宽度\r\n                picHeight:100,     // 单张预览图片的高度\r\n                uploadDataFieldName: optImageFieldName,    // POST请求中图片数据的key\r\n                picDescFieldName:'pictitle',      // POST请求中图片描述的key\r\n                maxSize: maxSize,                         // 文件的最大体积,单位M\r\n                compressSize:1,                   // 上传前如果图片体积超过该值，会先压缩,单位M\r\n                maxNum:32,                         // 单次最大可上传多少个文件\r\n                compressSide: 0,                 //等比压缩的基准，0为按照最长边，1为按照宽度，2为按照高度\r\n                compressLength: optImageCompressBorder        //能接受的最大边长，超过该值Flash会自动等比压缩\r\n            };\r\n            //回调函数集合，支持传递函数名的字符串、函数句柄以及函数本身三种类型\r\n            var callbacks={\r\n                selectFileCallback: function(selectFiles){                // 选择文件的回调\r\n                    selectedImageCount += selectFiles.length;\r\n                    if(selectedImageCount) baidu.g(\"upload\").style.display = \"\";\r\n                    dialog.buttons[0].setDisabled(true); //初始化时置灰确定按钮\r\n                },\r\n                deleteFileCallback: function(delFiles){                 // 删除文件的回调\r\n                    selectedImageCount -= delFiles.length;\r\n                    if (!selectedImageCount) {\r\n                        baidu.g(\"upload\").style.display = \"none\";\r\n                        dialog.buttons[0].setDisabled(false);         //没有选择图片时重新点亮按钮\r\n                    }\r\n                },\r\n                uploadCompleteCallback: function(data){               // 单个文件上传完成的回调\r\n                    try{var info = eval(\"(\" + data.info + \")\");\r\n                    info && imageUrls.push(info);\r\n                    selectedImageCount--;\r\n                    }catch(e){}\r\n                },\r\n                uploadErrorCallback: function (data){         // 单个文件上传失败的回调,\r\n                    console && console.log(data);\r\n                },\r\n                allCompleteCallback: function(){              // 全部上传完成时的回调\r\n                    dialog.buttons[0].setDisabled(false);    //上传完毕后点亮按钮\r\n                }\r\n                //exceedFileCallback: 'exceedFileCallback',   // 文件超出限制的最大体积时的回调\r\n                //startUploadCallback: startUploadCallback    // 开始上传某个文件时的回调\r\n            };\r\n            wordImage.init(flashOptions,callbacks);\r\n        });\r\n\r\n    </script>\r\n\r\n</body>\r\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/dialogs/wordimage/wordimage.js",
    "content": "/**\r\n * Created by JetBrains PhpStorm.\r\n * User: taoqili\r\n * Date: 12-1-30\r\n * Time: 下午12:50\r\n * To change this template use File | Settings | File Templates.\r\n */\r\n\r\n\r\n\r\nvar wordImage = {};\r\n//(function(){\r\nvar g = baidu.g,\r\n\tflashObj,flashContainer;\r\n\r\nwordImage.init = function(opt, callbacks) {\r\n\tshowLocalPath(\"localPath\");\r\n\t//createCopyButton(\"clipboard\",\"localPath\");\r\n\tcreateFlashUploader(opt, callbacks);\r\n\taddUploadListener();\r\n\taddOkListener();\r\n};\r\n\r\nfunction hideFlash(){\r\n    flashObj = null;\r\n    flashContainer.innerHTML = \"\";\r\n}\r\nfunction addOkListener() {\r\n\tdialog.onok = function() {\r\n\t\tif (!imageUrls.length) return;\r\n\t\tvar urlPrefix = editor.getOpt('imageUrlPrefix'),\r\n            images = domUtils.getElementsByTagName(editor.document,\"img\");\r\n        editor.fireEvent('saveScene');\r\n\t\tfor (var i = 0,img; img = images[i++];) {\r\n\t\t\tvar src = img.getAttribute(\"word_img\");\r\n\t\t\tif (!src) continue;\r\n\t\t\tfor (var j = 0,url; url = imageUrls[j++];) {\r\n\t\t\t\tif (src.indexOf(url.original.replace(\" \",\"\")) != -1) {\r\n\t\t\t\t\timg.src = urlPrefix + url.url;\r\n\t\t\t\t\timg.setAttribute(\"_src\", urlPrefix + url.url);  //同时修改\"_src\"属性\r\n\t\t\t\t\timg.setAttribute(\"title\",url.title);\r\n                    domUtils.removeAttributes(img, [\"word_img\",\"style\",\"width\",\"height\"]);\r\n\t\t\t\t\teditor.fireEvent(\"selectionchange\");\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n        editor.fireEvent('saveScene');\r\n        hideFlash();\r\n\t};\r\n    dialog.oncancel = function(){\r\n        hideFlash();\r\n    }\r\n}\r\n\r\n/**\r\n * 绑定开始上传事件\r\n */\r\nfunction addUploadListener() {\r\n\tg(\"upload\").onclick = function () {\r\n\t\tflashObj.upload();\r\n\t\tthis.style.display = \"none\";\r\n\t};\r\n}\r\n\r\nfunction showLocalPath(id) {\r\n    //单张编辑\r\n    var img = editor.selection.getRange().getClosedNode();\r\n    var images = editor.execCommand('wordimage');\r\n    if(images.length==1 || img && img.tagName == 'IMG'){\r\n        g(id).value = images[0];\r\n        return;\r\n    }\r\n\tvar path = images[0];\r\n    var leftSlashIndex  = path.lastIndexOf(\"/\")||0,  //不同版本的doc和浏览器都可能影响到这个符号，故直接判断两种\r\n        rightSlashIndex = path.lastIndexOf(\"\\\\\")||0,\r\n        separater = leftSlashIndex > rightSlashIndex ? \"/\":\"\\\\\" ;\r\n\r\n\tpath = path.substring(0, path.lastIndexOf(separater)+1);\r\n\tg(id).value = path;\r\n}\r\n\r\nfunction createFlashUploader(opt, callbacks) {\r\n    //由于lang.flashI18n是静态属性，不可以直接进行修改，否则会影响到后续内容\r\n    var i18n = utils.extend({},lang.flashI18n);\r\n    //处理图片资源地址的编码，补全等问题\r\n    for(var i in i18n){\r\n        if(!(i in {\"lang\":1,\"uploadingTF\":1,\"imageTF\":1,\"textEncoding\":1}) && i18n[i]){\r\n            i18n[i] = encodeURIComponent(editor.options.langPath + editor.options.lang + \"/images/\" + i18n[i]);\r\n        }\r\n    }\r\n    opt = utils.extend(opt,i18n,false);\r\n\tvar option = {\r\n\t\tcreateOptions:{\r\n\t\t\tid:'flash',\r\n\t\t\turl:opt.flashUrl,\r\n\t\t\twidth:opt.width,\r\n\t\t\theight:opt.height,\r\n\t\t\terrorMessage:lang.flashError,\r\n\t\t\twmode:browser.safari ? 'transparent' : 'window',\r\n\t\t\tver:'10.0.0',\r\n\t\t\tvars:opt,\r\n\t\t\tcontainer:opt.container\r\n\t\t}\r\n\t};\r\n\r\n\toption = extendProperty(callbacks, option);\r\n\tflashObj = new baidu.flash.imageUploader(option);\r\n    flashContainer = $G(opt.container);\r\n}\r\n\r\nfunction extendProperty(fromObj, toObj) {\r\n\tfor (var i in fromObj) {\r\n\t\tif (!toObj[i]) {\r\n\t\t\ttoObj[i] = fromObj[i];\r\n\t\t}\r\n\t}\r\n\treturn toObj;\r\n}\r\n\r\n//})();\r\n\r\nfunction getPasteData(id) {\r\n\tbaidu.g(\"msg\").innerHTML = lang.copySuccess + \"</br>\";\r\n\tsetTimeout(function() {\r\n\t\tbaidu.g(\"msg\").innerHTML = \"\";\r\n\t}, 5000);\r\n\treturn baidu.g(id).value;\r\n}\r\n\r\nfunction createCopyButton(id, dataFrom) {\r\n\tbaidu.swf.create({\r\n\t\t\tid:\"copyFlash\",\r\n\t\t\turl:\"fClipboard_ueditor.swf\",\r\n\t\t\twidth:\"58\",\r\n\t\t\theight:\"25\",\r\n\t\t\terrorMessage:\"\",\r\n\t\t\tbgColor:\"#CBCBCB\",\r\n\t\t\twmode:\"transparent\",\r\n\t\t\tver:\"10.0.0\",\r\n\t\t\tvars:{\r\n\t\t\t\ttid:dataFrom\r\n\t\t\t}\r\n\t\t}, id\r\n\t);\r\n\r\n\tvar clipboard = baidu.swf.getMovie(\"copyFlash\");\r\n\tvar clipinterval = setInterval(function() {\r\n\t\tif (clipboard && clipboard.flashInit) {\r\n\t\t\tclearInterval(clipinterval);\r\n\t\t\tclipboard.setHandCursor(true);\r\n\t\t\tclipboard.setContentFuncName(\"getPasteData\");\r\n\t\t\t//clipboard.setMEFuncName(\"mouseEventHandler\");\r\n\t\t}\r\n\t}, 500);\r\n}\r\ncreateCopyButton(\"clipboard\", \"localPath\");"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/index.html",
    "content": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\r\n        \"http://www.w3.org/TR/html4/loose.dtd\">\r\n<html>\r\n<head>\r\n    <title>完整demo</title>\r\n    <meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"/>\r\n    <script type=\"text/javascript\" charset=\"utf-8\" src=\"ueditor.config.js\"></script>\r\n    <script type=\"text/javascript\" charset=\"utf-8\" src=\"ueditor.all.min.js\"> </script>\r\n    <!--建议手动加在语言，避免在ie下有时因为加载语言失败导致编辑器加载失败-->\r\n    <!--这里加载的语言文件会覆盖你在配置项目里添加的语言类型，比如你在配置项目里配置的是英文，这里加载的中文，那最后就是中文-->\r\n    <script type=\"text/javascript\" charset=\"utf-8\" src=\"lang/zh-cn/zh-cn.js\"></script>\r\n\r\n    <style type=\"text/css\">\r\n        div{\r\n            width:100%;\r\n        }\r\n    </style>\r\n</head>\r\n<body>\r\n<div>\r\n    <h1>完整demo</h1>\r\n    <script id=\"editor\" type=\"text/plain\" style=\"width:1024px;height:500px;\"></script>\r\n</div>\r\n<div id=\"btns\">\r\n    <div>\r\n        <button onclick=\"getAllHtml()\">获得整个html的内容</button>\r\n        <button onclick=\"getContent()\">获得内容</button>\r\n        <button onclick=\"setContent()\">写入内容</button>\r\n        <button onclick=\"setContent(true)\">追加内容</button>\r\n        <button onclick=\"getContentTxt()\">获得纯文本</button>\r\n        <button onclick=\"getPlainTxt()\">获得带格式的纯文本</button>\r\n        <button onclick=\"hasContent()\">判断是否有内容</button>\r\n        <button onclick=\"setFocus()\">使编辑器获得焦点</button>\r\n        <button onmousedown=\"isFocus(event)\">编辑器是否获得焦点</button>\r\n        <button onmousedown=\"setblur(event)\" >编辑器失去焦点</button>\r\n\r\n    </div>\r\n    <div>\r\n        <button onclick=\"getText()\">获得当前选中的文本</button>\r\n        <button onclick=\"insertHtml()\">插入给定的内容</button>\r\n        <button id=\"enable\" onclick=\"setEnabled()\">可以编辑</button>\r\n        <button onclick=\"setDisabled()\">不可编辑</button>\r\n        <button onclick=\" UE.getEditor('editor').setHide()\">隐藏编辑器</button>\r\n        <button onclick=\" UE.getEditor('editor').setShow()\">显示编辑器</button>\r\n        <button onclick=\" UE.getEditor('editor').setHeight(300)\">设置高度为300默认关闭了自动长高</button>\r\n    </div>\r\n\r\n    <div>\r\n        <button onclick=\"getLocalData()\" >获取草稿箱内容</button>\r\n        <button onclick=\"clearLocalData()\" >清空草稿箱</button>\r\n    </div>\r\n\r\n</div>\r\n<div>\r\n    <button onclick=\"createEditor()\">\r\n    创建编辑器</button>\r\n    <button onclick=\"deleteEditor()\">\r\n    删除编辑器</button>\r\n</div>\r\n\r\n<script type=\"text/javascript\">\r\n\r\n    //实例化编辑器\r\n    //建议使用工厂方法getEditor创建和引用编辑器实例，如果在某个闭包下引用该编辑器，直接调用UE.getEditor('editor')就能拿到相关的实例\r\n    var ue = UE.getEditor('editor');\r\n\r\n\r\n    function isFocus(e){\r\n        alert(UE.getEditor('editor').isFocus());\r\n        UE.dom.domUtils.preventDefault(e)\r\n    }\r\n    function setblur(e){\r\n        UE.getEditor('editor').blur();\r\n        UE.dom.domUtils.preventDefault(e)\r\n    }\r\n    function insertHtml() {\r\n        var value = prompt('插入html代码', '');\r\n        UE.getEditor('editor').execCommand('insertHtml', value)\r\n    }\r\n    function createEditor() {\r\n        enableBtn();\r\n        UE.getEditor('editor');\r\n    }\r\n    function getAllHtml() {\r\n        alert(UE.getEditor('editor').getAllHtml())\r\n    }\r\n    function getContent() {\r\n        var arr = [];\r\n        arr.push(\"使用editor.getContent()方法可以获得编辑器的内容\");\r\n        arr.push(\"内容为：\");\r\n        arr.push(UE.getEditor('editor').getContent());\r\n        alert(arr.join(\"\\n\"));\r\n    }\r\n    function getPlainTxt() {\r\n        var arr = [];\r\n        arr.push(\"使用editor.getPlainTxt()方法可以获得编辑器的带格式的纯文本内容\");\r\n        arr.push(\"内容为：\");\r\n        arr.push(UE.getEditor('editor').getPlainTxt());\r\n        alert(arr.join('\\n'))\r\n    }\r\n    function setContent(isAppendTo) {\r\n        var arr = [];\r\n        arr.push(\"使用editor.setContent('欢迎使用ueditor')方法可以设置编辑器的内容\");\r\n        UE.getEditor('editor').setContent('欢迎使用ueditor', isAppendTo);\r\n        alert(arr.join(\"\\n\"));\r\n    }\r\n    function setDisabled() {\r\n        UE.getEditor('editor').setDisabled('fullscreen');\r\n        disableBtn(\"enable\");\r\n    }\r\n\r\n    function setEnabled() {\r\n        UE.getEditor('editor').setEnabled();\r\n        enableBtn();\r\n    }\r\n\r\n    function getText() {\r\n        //当你点击按钮时编辑区域已经失去了焦点，如果直接用getText将不会得到内容，所以要在选回来，然后取得内容\r\n        var range = UE.getEditor('editor').selection.getRange();\r\n        range.select();\r\n        var txt = UE.getEditor('editor').selection.getText();\r\n        alert(txt)\r\n    }\r\n\r\n    function getContentTxt() {\r\n        var arr = [];\r\n        arr.push(\"使用editor.getContentTxt()方法可以获得编辑器的纯文本内容\");\r\n        arr.push(\"编辑器的纯文本内容为：\");\r\n        arr.push(UE.getEditor('editor').getContentTxt());\r\n        alert(arr.join(\"\\n\"));\r\n    }\r\n    function hasContent() {\r\n        var arr = [];\r\n        arr.push(\"使用editor.hasContents()方法判断编辑器里是否有内容\");\r\n        arr.push(\"判断结果为：\");\r\n        arr.push(UE.getEditor('editor').hasContents());\r\n        alert(arr.join(\"\\n\"));\r\n    }\r\n    function setFocus() {\r\n        UE.getEditor('editor').focus();\r\n    }\r\n    function deleteEditor() {\r\n        disableBtn();\r\n        UE.getEditor('editor').destroy();\r\n    }\r\n    function disableBtn(str) {\r\n        var div = document.getElementById('btns');\r\n        var btns = UE.dom.domUtils.getElementsByTagName(div, \"button\");\r\n        for (var i = 0, btn; btn = btns[i++];) {\r\n            if (btn.id == str) {\r\n                UE.dom.domUtils.removeAttributes(btn, [\"disabled\"]);\r\n            } else {\r\n                btn.setAttribute(\"disabled\", \"true\");\r\n            }\r\n        }\r\n    }\r\n    function enableBtn() {\r\n        var div = document.getElementById('btns');\r\n        var btns = UE.dom.domUtils.getElementsByTagName(div, \"button\");\r\n        for (var i = 0, btn; btn = btns[i++];) {\r\n            UE.dom.domUtils.removeAttributes(btn, [\"disabled\"]);\r\n        }\r\n    }\r\n\r\n    function getLocalData () {\r\n        alert(UE.getEditor('editor').execCommand( \"getlocaldata\" ));\r\n    }\r\n\r\n    function clearLocalData () {\r\n        UE.getEditor('editor').execCommand( \"clearlocaldata\" );\r\n        alert(\"已清空草稿箱\")\r\n    }\r\n</script>\r\n</body>\r\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/lang/en/en.js",
    "content": "/**\r\n * Created with JetBrains PhpStorm.\r\n * User: taoqili\r\n * Date: 12-6-12\r\n * Time: 下午6:57\r\n * To change this template use File | Settings | File Templates.\r\n */\r\nUE.I18N['en'] = {\r\n    'labelMap': {\r\n        'anchor': 'Anchor',\r\n        'undo': 'Undo',\r\n        'redo': 'Redo',\r\n        'bold': 'Bold',\r\n        'indent': 'Indent',\r\n        'snapscreen': 'SnapScreen',\r\n        'italic': 'Italic',\r\n        'underline': 'Underline',\r\n        'strikethrough': 'Strikethrough',\r\n        'subscript': 'SubScript',\r\n        'fontborder': 'text border',\r\n        'superscript': 'SuperScript',\r\n        'formatmatch': 'Format Match',\r\n        'source': 'Source',\r\n        'blockquote': 'BlockQuote',\r\n        'pasteplain': 'PastePlain',\r\n        'selectall': 'SelectAll',\r\n        'print': 'Print',\r\n        'preview': 'Preview',\r\n        'horizontal': 'Horizontal',\r\n        'removeformat': 'RemoveFormat',\r\n        'time': 'Time',\r\n        'date': 'Date',\r\n        'unlink': 'Unlink',\r\n        'insertrow': 'InsertRow',\r\n        'insertcol': 'InsertCol',\r\n        'mergeright': 'MergeRight',\r\n        'mergedown': 'MergeDown',\r\n        'deleterow': 'DeleteRow',\r\n        'deletecol': 'DeleteCol',\r\n        'splittorows': 'SplitToRows',\r\n        'insertcode': 'insert code',\r\n        'splittocols': 'SplitToCols',\r\n        'splittocells': 'SplitToCells',\r\n        'deletecaption': 'DeleteCaption',\r\n        'inserttitle': 'InsertTitle',\r\n        'mergecells': 'MergeCells',\r\n        'deletetable': 'DeleteTable',\r\n        'cleardoc': 'Clear',\r\n        'insertparagraphbeforetable': 'InsertParagraphBeforeTable',\r\n        'fontfamily': 'FontFamily',\r\n        'fontsize': 'FontSize',\r\n        'paragraph': 'Paragraph',\r\n        'simpleupload': 'Single Image',\r\n        'insertimage': 'Multi Image',\r\n        'edittable': 'Edit Table',\r\n        'edittd': 'Edit Td',\r\n        'link': 'Link',\r\n        'emotion': 'Emotion',\r\n        'spechars': 'Spechars',\r\n        'searchreplace': 'SearchReplace',\r\n        'map': 'BaiduMap',\r\n        'gmap': 'GoogleMap',\r\n        'insertvideo': 'Video',\r\n        'help': 'Help',\r\n        'justifyleft': 'JustifyLeft',\r\n        'justifyright': 'JustifyRight',\r\n        'justifycenter': 'JustifyCenter',\r\n        'justifyjustify': 'Justify',\r\n        'forecolor': 'FontColor',\r\n        'backcolor': 'BackColor',\r\n        'insertorderedlist': 'OL',\r\n        'insertunorderedlist': 'UL',\r\n        'fullscreen': 'FullScreen',\r\n        'directionalityltr': 'EnterFromLeft',\r\n        'directionalityrtl': 'EnterFromRight',\r\n        'rowspacingtop': 'RowSpacingTop',\r\n        'rowspacingbottom': 'RowSpacingBottom',\r\n        'pagebreak': 'PageBreak',\r\n        'insertframe': 'Iframe',\r\n        'imagenone': 'Default',\r\n        'imageleft': 'ImageLeft',\r\n        'imageright': 'ImageRight',\r\n        'attachment': 'Attachment',\r\n        'imagecenter': 'ImageCenter',\r\n        'wordimage': 'WordImage',\r\n        'lineheight': 'LineHeight',\r\n        'edittip': 'EditTip',\r\n        'customstyle': 'CustomStyle',\r\n        'scrawl': 'Scrawl',\r\n        'autotypeset': 'AutoTypeset',\r\n        'webapp': 'WebAPP',\r\n        'touppercase': 'UpperCase',\r\n        'tolowercase': 'LowerCase',\r\n        'template': 'Template',\r\n        'background': 'Background',\r\n        'inserttable': 'InsertTable',\r\n        'music': 'Music',\r\n        'charts': 'charts',\r\n        'drafts': 'Load from Drafts'\r\n    },\r\n    'insertorderedlist': {\r\n        'num': '1,2,3...',\r\n        'num1': '1),2),3)...',\r\n        'num2': '(1),(2),(3)...',\r\n        'cn': '一,二,三....',\r\n        'cn1': '一),二),三)....',\r\n        'cn2': '(一),(二),(三)....',\r\n        'decimal': '1,2,3...',\r\n        'lower-alpha': 'a,b,c...',\r\n        'lower-roman': 'i,ii,iii...',\r\n        'upper-alpha': 'A,B,C...',\r\n        'upper-roman': 'I,II,III...'\r\n    },\r\n    'insertunorderedlist': {\r\n        'circle': '○ Circle',\r\n        'disc': '● Circle dot',\r\n        'square': '■ Rectangle ',\r\n        'dash': '－ Dash',\r\n        'dot': '。dot'\r\n    },\r\n    'paragraph': { 'p': 'Paragraph', 'h1': 'Title 1', 'h2': 'Title 2', 'h3': 'Title 3', 'h4': 'Title 4', 'h5': 'Title 5', 'h6': 'Title 6' },\r\n    'fontfamily': {\r\n        'songti': 'Sim Sun',\r\n        'kaiti': 'Sim Kai',\r\n        'heiti': 'Sim Hei',\r\n        'lishu': 'Sim Li',\r\n        'yahei': 'Microsoft YaHei',\r\n        'andaleMono': 'Andale Mono',\r\n        'arial': 'Arial',\r\n        'arialBlack': 'Arial Black',\r\n        'comicSansMs': 'Comic Sans MS',\r\n        'impact': 'Impact',\r\n        'timesNewRoman': 'Times New Roman'\r\n    },\r\n    'customstyle': {\r\n        'tc': 'Title center',\r\n        'tl': 'Title left',\r\n        'im': 'Important',\r\n        'hi': 'Highlight'\r\n    },\r\n    'autoupload': {\r\n        'exceedSizeError': 'File Size Exceed',\r\n        'exceedTypeError': 'File Type Not Allow',\r\n        'jsonEncodeError': 'Server Return Format Error',\r\n        'loading': 'loading...',\r\n        'loadError': 'load error',\r\n        'errorLoadConfig': 'Server config not loaded, upload can not work.'\r\n    },\r\n    'simpleupload': {\r\n        'exceedSizeError': 'File Size Exceed',\r\n        'exceedTypeError': 'File Type Not Allow',\r\n        'jsonEncodeError': 'Server Return Format Error',\r\n        'loading': 'loading...',\r\n        'loadError': 'load error',\r\n        'errorLoadConfig': 'Server config not loaded, upload can not work.'\r\n    },\r\n    'elementPathTip': 'Path',\r\n    'wordCountTip': 'Word Count',\r\n    'wordCountMsg': '{#count} characters entered,{#leave} left. ',\r\n    'wordOverFlowMsg': '<span style=\"color:red;\">The number of characters has exceeded allowable maximum values, the server may refuse to save!</span>',\r\n    'ok': 'OK',\r\n    'cancel': 'Cancel',\r\n    'closeDialog': 'closeDialog',\r\n    'tableDrag': 'You must import the file uiUtils.js before drag! ',\r\n    'autofloatMsg': 'The plugin AutoFloat depends on EditorUI!',\r\n    'loadconfigError': 'Get server config error.',\r\n    'loadconfigFormatError': 'Server config format error.',\r\n    'loadconfigHttpError': 'Get server config http error.',\r\n    'snapScreen_plugin': {\r\n        'browserMsg': 'Only IE supported!',\r\n        'callBackErrorMsg': 'The callback data is wrong,please check the config!',\r\n        'uploadErrorMsg': 'Upload error,please check your server environment! '\r\n    },\r\n    'insertcode': {\r\n        'as3': 'ActionScript 3',\r\n        'bash': 'Bash/Shell',\r\n        'cpp': 'C/C++',\r\n        'css': 'CSS',\r\n        'cf': 'ColdFusion',\r\n        'c#': 'C#',\r\n        'delphi': 'Delphi',\r\n        'diff': 'Diff',\r\n        'erlang': 'Erlang',\r\n        'groovy': 'Groovy',\r\n        'html': 'HTML',\r\n        'java': 'Java',\r\n        'jfx': 'JavaFX',\r\n        'js': 'JavaScript',\r\n        'pl': 'Perl',\r\n        'php': 'PHP',\r\n        'plain': 'Plain Text',\r\n        'ps': 'PowerShell',\r\n        'python': 'Python',\r\n        'ruby': 'Ruby',\r\n        'scala': 'Scala',\r\n        'sql': 'SQL',\r\n        'vb': 'Visual Basic',\r\n        'xml': 'XML'\r\n    },\r\n    'confirmClear': 'Do you confirm to clear the Document?',\r\n    'contextMenu': {\r\n        'delete': 'Delete',\r\n        'selectall': 'Select all',\r\n        'deletecode': 'Delete Code',\r\n        'cleardoc': 'Clear Document',\r\n        'confirmclear': 'Do you confirm to clear the Document?',\r\n        'unlink': 'Unlink',\r\n        'paragraph': 'Paragraph',\r\n        'edittable': 'Table property',\r\n        'aligncell': 'Align cell',\r\n        'aligntable': 'Table alignment',\r\n        'tableleft': 'Left float',\r\n        'tablecenter': 'Center',\r\n        'tableright': 'Right float',\r\n        'aligntd': 'Cell alignment',\r\n        'edittd': 'Cell property',\r\n        'setbordervisible': 'set table edge visible',\r\n        'table': 'Table',\r\n        'justifyleft': 'Justify Left',\r\n        'justifyright': 'Justify Right',\r\n        'justifycenter': 'Justify Center',\r\n        'justifyjustify': 'Default',\r\n        'deletetable': 'Delete table',\r\n        'insertparagraphbefore': 'InsertedBeforeLine',\r\n        'insertparagraphafter': 'InsertedAfterLine',\r\n        'inserttable': 'Insert table',\r\n        'insertcaption': 'Insert caption',\r\n        'deletecaption': 'Delete Caption',\r\n        'inserttitle': 'Insert Title',\r\n        'deletetitle': 'Delete Title',\r\n        'inserttitlecol': 'Insert Title Col',\r\n        'deletetitlecol': 'Delete Title Col',\r\n        'averageDiseRow': 'AverageDise Row',\r\n        'averageDisCol': 'AverageDis Col',\r\n        'deleterow': 'Delete row',\r\n        'deletecol': 'Delete col',\r\n        'insertrow': 'Insert row',\r\n        'insertcol': 'Insert col',\r\n        'insertrownext': 'Insert Row Next',\r\n        'insertcolnext': 'Insert Col Next',\r\n        'mergeright': 'Merge right',\r\n        'mergeleft': 'Merge left',\r\n        'mergedown': 'Merge down',\r\n        'mergecells': 'Merge cells',\r\n        'splittocells': 'Split to cells',\r\n        'splittocols': 'Split to Cols',\r\n        'splittorows': 'Split to Rows',\r\n        'tablesort': 'Table sorting',\r\n        'enablesort': 'Sorting Enable',\r\n        'disablesort': 'Sorting Disable',\r\n        'reversecurrent': 'Reverse current',\r\n        'orderbyasc': 'Order By ASCII',\r\n        'reversebyasc': 'Reverse By ASCII',\r\n        'orderbynum': 'Order By Num',\r\n        'reversebynum': 'Reverse By Num',\r\n        'borderbk': 'Border shading',\r\n        'setcolor': 'interlaced color',\r\n        'unsetcolor': 'Cancel interlacedcolor',\r\n        'setbackground': 'Background interlaced',\r\n        'unsetbackground': 'Cancel Bk interlaced',\r\n        'redandblue': 'Blue and red',\r\n        'threecolorgradient': 'Three-color gradient',\r\n        'copy': 'Copy(Ctrl + c)',\r\n        'copymsg': \"Browser does not support. Please use 'Ctrl + c' instead!\",\r\n        'paste': 'Paste(Ctrl + v)',\r\n        'pastemsg': \"Browser does not support. Please use 'Ctrl + v' instead!\"\r\n    },\r\n    'copymsg': \"Browser does not support. Please use 'Ctrl + c' instead!\",\r\n    'pastemsg': \"Browser does not support. Please use 'Ctrl + v' instead!\",\r\n    'anthorMsg': 'Link',\r\n    'clearColor': 'Clear',\r\n    'standardColor': 'Standard color',\r\n    'themeColor': 'Theme color',\r\n    'property': 'Property',\r\n    'default': 'Default',\r\n    'modify': 'Modify',\r\n    'justifyleft': 'Justify Left',\r\n    'justifyright': 'Justify Right',\r\n    'justifycenter': 'Justify Center',\r\n    'justify': 'Default',\r\n    'clear': 'Clear',\r\n    'anchorMsg': 'Anchor',\r\n    'delete': 'Delete',\r\n    'clickToUpload': 'Click to upload',\r\n    'unset': 'Language hasn\\'t been set!',\r\n    't_row': 'row',\r\n    't_col': 'col',\r\n    'pasteOpt': 'Paste Option',\r\n    'pasteSourceFormat': 'Keep Source Formatting',\r\n    'tagFormat': 'Keep tag',\r\n    'pasteTextFormat': 'Keep Text only',\r\n    'more': 'More',\r\n    'autoTypeSet': {\r\n        'mergeLine': 'Merge empty line',\r\n        'delLine': 'Del empty line',\r\n        'removeFormat': 'Remove format',\r\n        'indent': 'Indent',\r\n        'alignment': 'Alignment',\r\n        'imageFloat': 'Image float',\r\n        'removeFontsize': 'Remove font size',\r\n        'removeFontFamily': 'Remove fontFamily',\r\n        'removeHtml': 'Remove redundant HTML code',\r\n        'pasteFilter': 'Paste filter',\r\n        'run': 'Done',\r\n        'symbol': 'Symbol Conversion',\r\n        'bdc2sb': 'Full-width to Half-width',\r\n        'tobdc': 'Half-width to Full-width'\r\n    },\r\n\r\n    'background': {\r\n        'static': {\r\n            'lang_background_normal': 'Normal',\r\n            'lang_background_local': 'Online',\r\n            'lang_background_set': 'Background Set',\r\n            'lang_background_none': 'No Background',\r\n            'lang_background_colored': 'Colored Background',\r\n            'lang_background_color': 'Color Set',\r\n            'lang_background_netimg': 'Net-Image',\r\n            'lang_background_align': 'Align Type',\r\n            'lang_background_position': 'Position',\r\n            'repeatType': { 'options': ['Center', 'Repeat-x', 'Repeat-y', 'Tile', 'Custom'] }\r\n        },\r\n        'noUploadImage': 'No pictures has been uploaded！',\r\n        'toggleSelect': 'Change the active state by click!\\n Image Size: '\r\n    },\r\n    //= ==============dialog i18N=======================\r\n    'insertimage': {\r\n        'static': {\r\n            'lang_tab_remote': 'Insert',\r\n            'lang_tab_upload': 'Local',\r\n            'lang_tab_online': 'Manager',\r\n            'lang_tab_search': 'Search',\r\n            'lang_input_url': 'Address:',\r\n            'lang_input_size': 'Size:',\r\n            'lang_input_width': 'Width',\r\n            'lang_input_height': 'Height',\r\n            'lang_input_border': 'Border:',\r\n            'lang_input_vhspace': 'Margins:',\r\n            'lang_input_title': 'Title:',\r\n            'lang_input_align': 'Image Float Style:',\r\n            'lang_imgLoading': 'Loading...',\r\n            'lang_start_upload': 'Start Upload',\r\n            'lock': { 'title': 'Lock rate' },\r\n            'searchType': { 'title': 'ImageType', 'options': ['News', 'Wallpaper', 'emotions', 'photo'] },\r\n            'searchTxt': { 'value': 'Enter the search keyword!' },\r\n            'searchBtn': { 'value': 'Search' },\r\n            'searchReset': { 'value': 'Clear' },\r\n            'noneAlign': { 'title': 'None Float' },\r\n            'leftAlign': { 'title': 'Left Float' },\r\n            'rightAlign': { 'title': 'Right Float' },\r\n            'centerAlign': { 'title': 'Center In A Line' }\r\n        },\r\n        'uploadSelectFile': 'Select File',\r\n        'uploadAddFile': 'Add File',\r\n        'uploadStart': 'Start Upload',\r\n        'uploadPause': 'Pause Upload',\r\n        'uploadContinue': 'Continue Upload',\r\n        'uploadRetry': 'Retry Upload',\r\n        'uploadDelete': 'Delete',\r\n        'uploadTurnLeft': 'Turn Left',\r\n        'uploadTurnRight': 'Turn Right',\r\n        'uploadPreview': 'Doing Preview',\r\n        'uploadNoPreview': 'Can Not Preview',\r\n        'updateStatusReady': 'Selected _ pictures, total _KB.',\r\n        'updateStatusConfirm': '_ uploaded successfully and _ upload failed',\r\n        'updateStatusFinish': 'Total _ pictures (_KB), _  uploaded successfully',\r\n        'updateStatusError': ' and _ upload failed',\r\n        'errorNotSupport': 'WebUploader does not support the browser you are using. Please upgrade your browser or flash player',\r\n        'errorLoadConfig': 'Server config not loaded, upload can not work.',\r\n        'errorExceedSize': 'File Size Exceed',\r\n        'errorFileType': 'File Type Not Allow',\r\n        'errorInterrupt': 'File Upload Interrupted',\r\n        'errorUploadRetry': 'Upload Error, Please Retry.',\r\n        'errorHttp': 'Http Error',\r\n        'errorServerUpload': 'Server Result Error.',\r\n        'remoteLockError': 'Cannot Lock the Proportion between width and height',\r\n        'numError': 'Please enter the correct Num. e.g 123,400',\r\n        'imageUrlError': 'The image format may be wrong!',\r\n        'imageLoadError': 'Error,please check the network or URL！',\r\n        'searchRemind': 'Enter the search keyword!',\r\n        'searchLoading': 'Image is loading,please wait...',\r\n        'searchRetry': \" Sorry,can't find the image,please try again!\"\r\n    },\r\n    'attachment': {\r\n        'static': {\r\n            'lang_tab_upload': 'Upload',\r\n            'lang_tab_online': 'Online',\r\n            'lang_start_upload': 'Start upload',\r\n            'lang_drop_remind': 'You can drop files here, a single maximum of 300 files'\r\n        },\r\n        'uploadSelectFile': 'Select File',\r\n        'uploadAddFile': 'Add File',\r\n        'uploadStart': 'Start Upload',\r\n        'uploadPause': 'Pause Upload',\r\n        'uploadContinue': 'Continue Upload',\r\n        'uploadRetry': 'Retry Upload',\r\n        'uploadDelete': 'Delete',\r\n        'uploadTurnLeft': 'Turn Left',\r\n        'uploadTurnRight': 'Turn Right',\r\n        'uploadPreview': 'Doing Preview',\r\n        'updateStatusReady': 'Selected _ files, total _KB.',\r\n        'updateStatusConfirm': '_ uploaded successfully and _ upload failed',\r\n        'updateStatusFinish': 'Total _ files (_KB), _  uploaded successfully',\r\n        'updateStatusError': ' and _ upload failed',\r\n        'errorNotSupport': 'WebUploader does not support the browser you are using. Please upgrade your browser or flash player',\r\n        'errorLoadConfig': 'Server config not loaded, upload can not work.',\r\n        'errorExceedSize': 'File Size Exceed',\r\n        'errorFileType': 'File Type Not Allow',\r\n        'errorInterrupt': 'File Upload Interrupted',\r\n        'errorUploadRetry': 'Upload Error, Please Retry.',\r\n        'errorHttp': 'Http Error',\r\n        'errorServerUpload': 'Server Result Error.'\r\n    },\r\n\r\n    'insertvideo': {\r\n        'static': {\r\n            'lang_tab_insertV': 'Video',\r\n            'lang_tab_searchV': 'Search',\r\n            'lang_tab_uploadV': 'Upload',\r\n            'lang_video_url': ' URL ',\r\n            'lang_video_size': 'Video Size',\r\n            'lang_videoW': 'Width',\r\n            'lang_videoH': 'Height',\r\n            'lang_alignment': 'Alignment',\r\n            'videoSearchTxt': { 'value': 'Enter the search keyword!' },\r\n            'videoType': { 'options': ['All', 'Hot', 'Entertainment', 'Funny', 'Sports', 'Science', 'variety'] },\r\n            'videoSearchBtn': { 'value': 'Search in Baidu' },\r\n            'videoSearchReset': { 'value': 'Clear result' },\r\n\r\n            'lang_input_fileStatus': ' No file uploaded!',\r\n            'startUpload': { 'style': 'background:url(upload.png) no-repeat;' },\r\n\r\n            'lang_upload_size': 'Video Size',\r\n            'lang_upload_width': 'Width',\r\n            'lang_upload_height': 'Height',\r\n            'lang_upload_alignment': 'Alignment',\r\n            'lang_format_advice': 'Recommends mp4 format.'\r\n        },\r\n        'numError': 'Please enter the correct Num. e.g 123,400',\r\n        'floatLeft': 'Float left',\r\n        'floatRight': 'Float right',\r\n        'default': 'Default',\r\n        'block': 'Display in block',\r\n        'urlError': 'The video url format may be wrong!',\r\n        'loading': ' &nbsp;The video is loading, please wait…',\r\n        'clickToSelect': 'Click to select',\r\n        'goToSource': 'Visit source video ',\r\n        'noVideo': \" &nbsp; &nbsp;Sorry,can't find the video,please try again!\",\r\n\r\n        'browseFiles': 'Open files',\r\n        'uploadSuccess': 'Upload Successful!',\r\n        'delSuccessFile': 'Remove from the success of the queue',\r\n        'delFailSaveFile': 'Remove the save failed file',\r\n        'statusPrompt': ' file(s) uploaded! ',\r\n        'flashVersionError': 'The current Flash version is too low, please update FlashPlayer,then try again!',\r\n        'flashLoadingError': 'The Flash failed loading! Please check the path or network state',\r\n        'fileUploadReady': 'Wait for uploading...',\r\n        'delUploadQueue': 'Remove from the uploading queue ',\r\n        'limitPrompt1': 'Can not choose more than single',\r\n        'limitPrompt2': 'file(s)！Please choose again！',\r\n        'delFailFile': 'Remove failure file',\r\n        'fileSizeLimit': 'File size exceeds the limit！',\r\n        'emptyFile': 'Can not upload an empty file！',\r\n        'fileTypeError': 'File type error！',\r\n        'unknownError': 'Unknown error！',\r\n        'fileUploading': 'Uploading,please wait...',\r\n        'cancelUpload': 'Cancel upload',\r\n        'netError': 'Network error',\r\n        'failUpload': 'Upload failed',\r\n        'serverIOError': 'Server IO error！',\r\n        'noAuthority': 'No Permission！',\r\n        'fileNumLimit': 'Upload limit to the number',\r\n        'failCheck': 'Authentication fails, the upload is skipped!',\r\n        'fileCanceling': 'Cancel, please wait...',\r\n        'stopUploading': 'Upload has stopped...',\r\n\r\n        'uploadSelectFile': 'Select File',\r\n        'uploadAddFile': 'Add File',\r\n        'uploadStart': 'Start Upload',\r\n        'uploadPause': 'Pause Upload',\r\n        'uploadContinue': 'Continue Upload',\r\n        'uploadRetry': 'Retry Upload',\r\n        'uploadDelete': 'Delete',\r\n        'uploadTurnLeft': 'Turn Left',\r\n        'uploadTurnRight': 'Turn Right',\r\n        'uploadPreview': 'Doing Preview',\r\n        'updateStatusReady': 'Selected _ files, total _KB.',\r\n        'updateStatusConfirm': '_ uploaded successfully and _ upload failed',\r\n        'updateStatusFinish': 'Total _ files (_KB), _  uploaded successfully',\r\n        'updateStatusError': ' and _ upload failed',\r\n        'errorNotSupport': 'WebUploader does not support the browser you are using. Please upgrade your browser or flash player',\r\n        'errorLoadConfig': 'Server config not loaded, upload can not work.',\r\n        'errorExceedSize': 'File Size Exceed',\r\n        'errorFileType': 'File Type Not Allow',\r\n        'errorInterrupt': 'File Upload Interrupted',\r\n        'errorUploadRetry': 'Upload Error, Please Retry.',\r\n        'errorHttp': 'Http Error',\r\n        'errorServerUpload': 'Server Result Error.'\r\n    },\r\n    'webapp': {\r\n        'tip1': 'This function provided by Baidu APP,please apply for baidu APPKey webmaster first!',\r\n        'tip2': 'And then open the file ueditor.config.js to set it! ',\r\n        'applyFor': 'APPLY FOR',\r\n        'anthorApi': 'Baidu API'\r\n    },\r\n    'template': {\r\n        'static': {\r\n            'lang_template_bkcolor': 'Background Color',\r\n            'lang_template_clear': 'Keep Content',\r\n            'lang_template_select': 'Select Template'\r\n        },\r\n        'blank': 'Blank',\r\n        'blog': 'Blog',\r\n        'resume': 'Resume',\r\n        'richText': 'Rich Text',\r\n        'scrPapers': 'Scientific Papers'\r\n    },\r\n    scrawl: {\r\n        'static': {\r\n            'lang_input_previousStep': 'Previous',\r\n            'lang_input_nextsStep': 'Next',\r\n            'lang_input_clear': 'Clear',\r\n            'lang_input_addPic': 'AddImage',\r\n            'lang_input_ScalePic': 'ScaleImage',\r\n            'lang_input_removePic': 'RemoveImage',\r\n            'J_imgTxt': { title: 'Add background image' }\r\n        },\r\n        'noScarwl': 'No paint, a white paper...',\r\n        'scrawlUpLoading': 'Image is uploading, please wait...',\r\n        'continueBtn': 'Try again',\r\n        'imageError': 'Image failed to load!',\r\n        'backgroundUploading': 'Image is uploading,please wait...'\r\n    },\r\n    'music': {\r\n        'static': {\r\n            'lang_input_tips': 'Input singer/song/album, search you interested in music!',\r\n            'J_searchBtn': { value: 'Search songs' }\r\n        },\r\n        'emptyTxt': 'Not search to the relevant music results, please change a keyword try.',\r\n        'chapter': 'Songs',\r\n        'singer': 'Singer',\r\n        'special': 'Album',\r\n        'listenTest': 'Audition'\r\n    },\r\n    anchor: {\r\n        'static': {\r\n            'lang_input_anchorName': 'Anchor Name:'\r\n        }\r\n    },\r\n    'charts': {\r\n        'static': {\r\n            'lang_data_source': 'Data source:',\r\n            'lang_chart_format': 'Chart format:',\r\n            'lang_data_align': 'Align',\r\n            'lang_chart_align_same': 'Consistent with the X-axis Y-axis',\r\n            'lang_chart_align_reverse': 'X-axis Y-axis opposite',\r\n            'lang_chart_title': 'Title',\r\n            'lang_chart_main_title': 'main title:',\r\n            'lang_chart_sub_title': 'sub title:',\r\n            'lang_chart_x_title': 'X-axis title:',\r\n            'lang_chart_y_title': 'Y-axis title:',\r\n            'lang_chart_tip': 'Prompt',\r\n            'lang_cahrt_tip_prefix': 'prefix:',\r\n            'lang_cahrt_tip_description': '仅饼图有效， 当鼠标移动到饼图中相应的块上时，提示框内的文字的前缀',\r\n            'lang_chart_data_unit': 'Unit',\r\n            'lang_chart_data_unit_title': 'unit:',\r\n            'lang_chart_data_unit_description': '显示在每个数据点上的数据的单位， 比如： 温度的单位 ℃',\r\n            'lang_chart_type': 'Chart type:',\r\n            'lang_prev_btn': 'Previous',\r\n            'lang_next_btn': 'Next'\r\n        }\r\n    },\r\n    emotion: {\r\n        'static': {\r\n            'lang_input_choice': 'Choice',\r\n            'lang_input_Tuzki': 'Tuzki',\r\n            'lang_input_lvdouwa': 'LvDouWa',\r\n            'lang_input_BOBO': 'BOBO',\r\n            'lang_input_babyCat': 'BabyCat',\r\n            'lang_input_bubble': 'Bubble',\r\n            'lang_input_youa': 'YouA'\r\n        }\r\n    },\r\n    gmap: {\r\n        'static': {\r\n            'lang_input_address': 'Address:',\r\n            'lang_input_search': 'Search',\r\n            'address': { value: 'Beijing' }\r\n        },\r\n        searchError: 'Unable to locate the address!'\r\n    },\r\n    help: {\r\n        'static': {\r\n            'lang_input_about': 'About',\r\n            'lang_input_shortcuts': 'Shortcuts',\r\n            'lang_input_introduction': 'UEditor is developed by Baidu Co.ltd.  It is lightweight, customizable , focusing on user experience and etc. , UEditor is based on open source BSD license , allowing free use and redistribution.',\r\n            'lang_Txt_shortcuts': 'Shortcuts',\r\n            'lang_Txt_func': 'Function',\r\n            'lang_Txt_bold': 'Bold',\r\n            'lang_Txt_copy': 'Copy',\r\n            'lang_Txt_cut': 'Cut',\r\n            'lang_Txt_Paste': 'Paste',\r\n            'lang_Txt_undo': 'Undo',\r\n            'lang_Txt_redo': 'Redo',\r\n            'lang_Txt_italic': 'Italic',\r\n            'lang_Txt_underline': 'Underline',\r\n            'lang_Txt_selectAll': 'Select All',\r\n            'lang_Txt_visualEnter': 'Submit',\r\n            'lang_Txt_fullscreen': 'Fullscreen'\r\n        }\r\n    },\r\n    insertframe: {\r\n        'static': {\r\n            'lang_input_address': 'Address：',\r\n            'lang_input_width': 'Width：',\r\n            'lang_input_height': 'height：',\r\n            'lang_input_isScroll': 'Enable scrollbars：',\r\n            'lang_input_frameborder': 'Show frame border：',\r\n            'lang_input_alignMode': 'Alignment：',\r\n            'align': { title: 'Alignment', options: ['Default', 'Left', 'Right', 'Center'] }\r\n        },\r\n        'enterAddress': 'Please enter an address!'\r\n    },\r\n    link: {\r\n        'static': {\r\n            'lang_input_text': 'Text：',\r\n            'lang_input_url': 'URL：',\r\n            'lang_input_title': 'Title：',\r\n            'lang_input_target': 'open in new window：'\r\n        },\r\n        'validLink': 'Supports only effective when a link is selected',\r\n        'httpPrompt': 'The hyperlink you enter should start with \"http|https|ftp://\"!'\r\n    },\r\n    map: {\r\n        'static': {\r\n            lang_city: 'City',\r\n            lang_address: 'Address',\r\n            city: { value: 'Beijing' },\r\n            lang_search: 'Search',\r\n            lang_dynamicmap: 'Dynamic map'\r\n        },\r\n        cityMsg: 'Please enter the city name!',\r\n        errorMsg: \"Can't find the place!\"\r\n    },\r\n    searchreplace: {\r\n        'static': {\r\n            lang_tab_search: 'Search',\r\n            lang_tab_replace: 'Replace',\r\n            lang_search1: 'Search',\r\n            lang_search2: 'Search',\r\n            lang_replace: 'Replace',\r\n            lang_searchReg: 'Support regular expression ,which starts and ends with a slash ,for example \"/expression/\"',\r\n            lang_searchReg1: 'Support regular expression ,which starts and ends with a slash ,for example \"/expression/\"',\r\n            lang_case_sensitive1: 'Case sense',\r\n            lang_case_sensitive2: 'Case sense',\r\n            nextFindBtn: { value: 'Next' },\r\n            preFindBtn: { value: 'Preview' },\r\n            nextReplaceBtn: { value: 'Next' },\r\n            preReplaceBtn: { value: 'Preview' },\r\n            repalceBtn: { value: 'Replace' },\r\n            repalceAllBtn: { value: 'Replace all' }\r\n        },\r\n        getEnd: 'Has the search to the bottom!',\r\n        getStart: 'Has the search to the top!',\r\n        countMsg: 'Altogether replaced {#count} character(s)!'\r\n    },\r\n    snapscreen: {\r\n        'static': {\r\n            lang_showMsg: 'You should install the UEditor screenshots program first!',\r\n            lang_download: 'Download!',\r\n            lang_step1: 'Step1:Download the program and then run it',\r\n            lang_step2: 'Step2:After complete install,try to click the button again'\r\n        }\r\n    },\r\n    spechars: {\r\n        'static': {},\r\n        tsfh: 'Special',\r\n        lmsz: 'Roman',\r\n        szfh: 'Numeral',\r\n        rwfh: 'Japanese',\r\n        xlzm: 'The Greek',\r\n        ewzm: 'Russian',\r\n        pyzm: 'Phonetic',\r\n        yyyb: 'English',\r\n        zyzf: 'Others'\r\n    },\r\n    'edittable': {\r\n        'static': {\r\n            'lang_tableStyle': 'Table style',\r\n            'lang_insertCaption': 'Add table header row',\r\n            'lang_insertTitle': 'Add table title row',\r\n            'lang_insertTitleCol': 'Add table title col',\r\n            'lang_tableSize': 'Automatically adjust table size',\r\n            'lang_autoSizeContent': 'Adaptive by form text',\r\n            'lang_orderbycontent': 'Table of contents sortable',\r\n            'lang_autoSizePage': 'Page width adaptive',\r\n            'lang_example': 'Example',\r\n            'lang_borderStyle': 'Table Border',\r\n            'lang_color': 'Color:'\r\n        },\r\n        captionName: 'Caption',\r\n        titleName: 'Title',\r\n        cellsName: 'text',\r\n        errorMsg: 'There are merged cells, can not sort.'\r\n    },\r\n    'edittip': {\r\n        'static': {\r\n            lang_delRow: 'Delete entire row',\r\n            lang_delCol: 'Delete entire col'\r\n        }\r\n    },\r\n    'edittd': {\r\n        'static': {\r\n            lang_tdBkColor: 'Background Color:'\r\n        }\r\n    },\r\n    'formula': {\r\n        'static': {\r\n        }\r\n    },\r\n    wordimage: {\r\n        'static': {\r\n            lang_resave: 'The re-save step',\r\n            uploadBtn: { src: 'upload.png', alt: 'Upload' },\r\n            clipboard: { style: 'background: url(copy.png) -153px -1px no-repeat;' },\r\n            lang_step: ' 1. Click top button to copy the url and then open the dialog to paste it. 2. Open after choose photos uploaded process.'\r\n        },\r\n        fileType: 'Image',\r\n        flashError: 'Flash initialization failed!',\r\n        netError: 'Network error! Please try again!',\r\n        copySuccess: 'URL has been copied!',\r\n\r\n        'flashI18n': {\r\n            lang: encodeURI('{\"UploadingState\":\"totalNum: ${a},uploadComplete: ${b}\", \"BeforeUpload\":\"waitingNum: ${a}\", \"ExceedSize\":\"Size exceed${a}\", \"ErrorInPreview\":\"Preview failed\", \"DefaultDescription\":\"Description\", \"LoadingImage\":\"Loading...\"}'),\r\n            uploadingTF: encodeURI('{\"font\":\"Arial\", \"size\":12, \"color\":\"0x000\", \"bold\":\"true\", \"italic\":\"false\", \"underline\":\"false\"}'),\r\n            imageTF: encodeURI('{\"font\":\"Arial\", \"size\":11, \"color\":\"red\", \"bold\":\"false\", \"italic\":\"false\", \"underline\":\"false\"}'),\r\n            textEncoding: 'utf-8',\r\n            addImageSkinURL: 'addImage.png',\r\n            allDeleteBtnUpSkinURL: 'allDeleteBtnUpSkin.png',\r\n            allDeleteBtnHoverSkinURL: 'allDeleteBtnHoverSkin.png',\r\n            rotateLeftBtnEnableSkinURL: 'rotateLeftEnable.png',\r\n            rotateLeftBtnDisableSkinURL: 'rotateLeftDisable.png',\r\n            rotateRightBtnEnableSkinURL: 'rotateRightEnable.png',\r\n            rotateRightBtnDisableSkinURL: 'rotateRightDisable.png',\r\n            deleteBtnEnableSkinURL: 'deleteEnable.png',\r\n            deleteBtnDisableSkinURL: 'deleteDisable.png',\r\n            backgroundURL: '',\r\n            listBackgroundURL: '',\r\n            buttonURL: 'button.png'\r\n        }\r\n    },\r\n    'autosave': {\r\n        'success': 'Local conservation success'\r\n    }\r\n};\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/lang/zh-cn/zh-cn.js",
    "content": "/**\r\n * Created with JetBrains PhpStorm.\r\n * User: taoqili\r\n * Date: 12-6-12\r\n * Time: 下午5:02\r\n * To change this template use File | Settings | File Templates.\r\n */\r\nUE.I18N['zh-cn'] = {\r\n    'labelMap':{\r\n        'anchor':'锚点', 'undo':'撤销', 'redo':'重做', 'bold':'加粗', 'indent':'首行缩进', 'snapscreen':'截图',\r\n        'italic':'斜体', 'underline':'下划线', 'strikethrough':'删除线', 'subscript':'下标','fontborder':'字符边框',\r\n        'superscript':'上标', 'formatmatch':'格式刷', 'source':'源代码', 'blockquote':'引用',\r\n        'pasteplain':'纯文本粘贴模式', 'selectall':'全选', 'print':'打印', 'preview':'预览',\r\n        'horizontal':'分隔线', 'removeformat':'清除格式', 'time':'时间', 'date':'日期',\r\n        'unlink':'取消链接', 'insertrow':'前插入行', 'insertcol':'前插入列', 'mergeright':'右合并单元格', 'mergedown':'下合并单元格',\r\n        'deleterow':'删除行', 'deletecol':'删除列', 'splittorows':'拆分成行',\r\n        'splittocols':'拆分成列', 'splittocells':'完全拆分单元格','deletecaption':'删除表格标题','inserttitle':'插入标题',\r\n        'mergecells':'合并多个单元格', 'deletetable':'删除表格', 'cleardoc':'清空文档','insertparagraphbeforetable':\"表格前插入行\",'insertcode':'代码语言',\r\n        'fontfamily':'字体', 'fontsize':'字号', 'paragraph':'段落格式', 'simpleupload':'单图上传', 'insertimage':'多图上传','edittable':'表格属性','edittd':'单元格属性', 'link':'超链接',\r\n        'emotion':'表情', 'spechars':'特殊字符', 'searchreplace':'查询替换', 'map':'Baidu地图', 'gmap':'Google地图',\r\n        'insertvideo':'视频', 'help':'帮助', 'justifyleft':'居左对齐', 'justifyright':'居右对齐', 'justifycenter':'居中对齐',\r\n        'justifyjustify':'两端对齐', 'forecolor':'字体颜色', 'backcolor':'背景色', 'insertorderedlist':'有序列表',\r\n        'insertunorderedlist':'无序列表', 'fullscreen':'全屏', 'directionalityltr':'从左向右输入', 'directionalityrtl':'从右向左输入',\r\n        'rowspacingtop':'段前距', 'rowspacingbottom':'段后距',  'pagebreak':'分页', 'insertframe':'插入Iframe', 'imagenone':'默认',\r\n        'imageleft':'左浮动', 'imageright':'右浮动', 'attachment':'附件', 'imagecenter':'居中', 'wordimage':'图片转存',\r\n        'lineheight':'行间距','edittip' :'编辑提示','customstyle':'自定义标题', 'autotypeset':'自动排版',\r\n        'webapp':'百度应用','touppercase':'字母大写', 'tolowercase':'字母小写','background':'背景','template':'模板','scrawl':'涂鸦',\r\n        'music':'音乐','inserttable':'插入表格','drafts': '从草稿箱加载', 'charts': '图表'\r\n    },\r\n    'insertorderedlist':{\r\n        'num':'1,2,3...',\r\n        'num1':'1),2),3)...',\r\n        'num2':'(1),(2),(3)...',\r\n        'cn':'一,二,三....',\r\n        'cn1':'一),二),三)....',\r\n        'cn2':'(一),(二),(三)....',\r\n        'decimal':'1,2,3...',\r\n        'lower-alpha':'a,b,c...',\r\n        'lower-roman':'i,ii,iii...',\r\n        'upper-alpha':'A,B,C...',\r\n        'upper-roman':'I,II,III...'\r\n    },\r\n    'insertunorderedlist':{\r\n        'circle':'○ 大圆圈',\r\n        'disc':'● 小黑点',\r\n        'square':'■ 小方块 ',\r\n        'dash' :'— 破折号',\r\n        'dot':' 。 小圆圈'\r\n    },\r\n    'paragraph':{'p':'段落', 'h1':'标题 1', 'h2':'标题 2', 'h3':'标题 3', 'h4':'标题 4', 'h5':'标题 5', 'h6':'标题 6'},\r\n    'fontfamily':{\r\n        'songti':'宋体',\r\n        'kaiti':'楷体',\r\n        'heiti':'黑体',\r\n        'lishu':'隶书',\r\n        'yahei':'微软雅黑',\r\n        'andaleMono':'andale mono',\r\n        'arial': 'arial',\r\n        'arialBlack':'arial black',\r\n        'comicSansMs':'comic sans ms',\r\n        'impact':'impact',\r\n        'timesNewRoman':'times new roman'\r\n    },\r\n    'customstyle':{\r\n        'tc':'标题居中',\r\n        'tl':'标题居左',\r\n        'im':'强调',\r\n        'hi':'明显强调'\r\n    },\r\n    'autoupload': {\r\n        'exceedSizeError': '文件大小超出限制',\r\n        'exceedTypeError': '文件格式不允许',\r\n        'jsonEncodeError': '服务器返回格式错误',\r\n        'loading':\"正在上传...\",\r\n        'loadError':\"上传错误\",\r\n        'errorLoadConfig': '后端配置项没有正常加载，上传插件不能正常使用！'\r\n    },\r\n    'simpleupload':{\r\n        'exceedSizeError': '文件大小超出限制',\r\n        'exceedTypeError': '文件格式不允许',\r\n        'jsonEncodeError': '服务器返回格式错误',\r\n        'loading':\"正在上传...\",\r\n        'loadError':\"上传错误\",\r\n        'errorLoadConfig': '后端配置项没有正常加载，上传插件不能正常使用！'\r\n    },\r\n    'elementPathTip':\"元素路径\",\r\n    'wordCountTip':\"字数统计\",\r\n    'wordCountMsg':'当前已输入{#count}个字符, 您还可以输入{#leave}个字符。 ',\r\n    'wordOverFlowMsg':'<span style=\"color:red;\">字数超出最大允许值，服务器可能拒绝保存！</span>',\r\n    'ok':\"确认\",\r\n    'cancel':\"取消\",\r\n    'closeDialog':\"关闭对话框\",\r\n    'tableDrag':\"表格拖动必须引入uiUtils.js文件！\",\r\n    'autofloatMsg':\"工具栏浮动依赖编辑器UI，您首先需要引入UI文件!\",\r\n    'loadconfigError': '获取后台配置项请求出错，上传功能将不能正常使用！',\r\n    'loadconfigFormatError': '后台配置项返回格式出错，上传功能将不能正常使用！',\r\n    'loadconfigHttpError': '请求后台配置项http错误，上传功能将不能正常使用！',\r\n    'snapScreen_plugin':{\r\n        'browserMsg':\"仅支持IE浏览器！\",\r\n        'callBackErrorMsg':\"服务器返回数据有误，请检查配置项之后重试。\",\r\n        'uploadErrorMsg':\"截图上传失败，请检查服务器端环境! \"\r\n    },\r\n    'insertcode':{\r\n        'as3':'ActionScript 3',\r\n        'bash':'Bash/Shell',\r\n        'cpp':'C/C++',\r\n        'css':'CSS',\r\n        'cf':'ColdFusion',\r\n        'c#':'C#',\r\n        'delphi':'Delphi',\r\n        'diff':'Diff',\r\n        'erlang':'Erlang',\r\n        'groovy':'Groovy',\r\n        'html':'HTML',\r\n        'java':'Java',\r\n        'jfx':'JavaFX',\r\n        'js':'JavaScript',\r\n        'pl':'Perl',\r\n        'php':'PHP',\r\n        'plain':'Plain Text',\r\n        'ps':'PowerShell',\r\n        'python':'Python',\r\n        'ruby':'Ruby',\r\n        'scala':'Scala',\r\n        'sql':'SQL',\r\n        'vb':'Visual Basic',\r\n        'xml':'XML'\r\n    },\r\n    'confirmClear':\"确定清空当前文档么？\",\r\n    'contextMenu':{\r\n        'delete':\"删除\",\r\n        'selectall':\"全选\",\r\n        'deletecode':\"删除代码\",\r\n        'cleardoc':\"清空文档\",\r\n        'confirmclear':\"确定清空当前文档么？\",\r\n        'unlink':\"删除超链接\",\r\n        'paragraph':\"段落格式\",\r\n        'edittable':\"表格属性\",\r\n        'aligntd':\"单元格对齐方式\",\r\n        'aligntable':'表格对齐方式',\r\n        'tableleft':'左浮动',\r\n        'tablecenter':'居中显示',\r\n        'tableright':'右浮动',\r\n        'edittd':\"单元格属性\",\r\n        'setbordervisible':'设置表格边线可见',\r\n        'justifyleft':'左对齐',\r\n        'justifyright':'右对齐',\r\n        'justifycenter':'居中对齐',\r\n        'justifyjustify':'两端对齐',\r\n        'table':\"表格\",\r\n        'inserttable':'插入表格',\r\n        'deletetable':\"删除表格\",\r\n        'insertparagraphbefore':\"前插入段落\",\r\n        'insertparagraphafter':'后插入段落',\r\n        'deleterow':\"删除当前行\",\r\n        'deletecol':\"删除当前列\",\r\n        'insertrow':\"前插入行\",\r\n        'insertcol':\"左插入列\",\r\n        'insertrownext':'后插入行',\r\n        'insertcolnext':'右插入列',\r\n        'insertcaption':'插入表格名称',\r\n        'deletecaption':'删除表格名称',\r\n        'inserttitle':'插入表格标题行',\r\n        'deletetitle':'删除表格标题行',\r\n        'inserttitlecol':'插入表格标题列',\r\n        'deletetitlecol':'删除表格标题列',\r\n        'averageDiseRow':'平均分布各行',\r\n        'averageDisCol':'平均分布各列',\r\n        'mergeright':\"向右合并\",\r\n        'mergeleft':\"向左合并\",\r\n        'mergedown':\"向下合并\",\r\n        'mergecells':\"合并单元格\",\r\n        'splittocells':\"完全拆分单元格\",\r\n        'splittocols':\"拆分成列\",\r\n        'splittorows':\"拆分成行\",\r\n        'tablesort':'表格排序',\r\n        'enablesort':'设置表格可排序',\r\n        'disablesort':'取消表格可排序',\r\n        'reversecurrent':'逆序当前',\r\n        'orderbyasc':'按ASCII字符升序',\r\n        'reversebyasc':'按ASCII字符降序',\r\n        'orderbynum':'按数值大小升序',\r\n        'reversebynum':'按数值大小降序',\r\n        'borderbk':'边框底纹',\r\n        'setcolor':'表格隔行变色',\r\n        'unsetcolor':'取消表格隔行变色',\r\n        'setbackground':'选区背景隔行',\r\n        'unsetbackground':'取消选区背景',\r\n        'redandblue':'红蓝相间',\r\n        'threecolorgradient':'三色渐变',\r\n        'copy':\"复制(Ctrl + c)\",\r\n        'copymsg': \"浏览器不支持,请使用 'Ctrl + c'\",\r\n        'paste':\"粘贴(Ctrl + v)\",\r\n         'pastemsg': \"浏览器不支持,请使用 'Ctrl + v'\"\r\n    },\r\n    'copymsg': \"浏览器不支持,请使用 'Ctrl + c'\",\r\n    'pastemsg': \"浏览器不支持,请使用 'Ctrl + v'\",\r\n    'anthorMsg':\"链接\",\r\n    'clearColor':'清空颜色',\r\n    'standardColor':'标准颜色',\r\n    'themeColor':'主题颜色',\r\n    'property':'属性',\r\n    'default':'默认',\r\n    'modify':'修改',\r\n    'justifyleft':'左对齐',\r\n    'justifyright':'右对齐',\r\n    'justifycenter':'居中',\r\n    'justify':'默认',\r\n    'clear':'清除',\r\n    'anchorMsg':'锚点',\r\n    'delete':'删除',\r\n    'clickToUpload':\"点击上传\",\r\n    'unset':'尚未设置语言文件',\r\n    't_row':'行',\r\n    't_col':'列',\r\n    'more':'更多',\r\n    'pasteOpt':'粘贴选项',\r\n    'pasteSourceFormat':\"保留源格式\",\r\n    'tagFormat':'只保留标签',\r\n    'pasteTextFormat':'只保留文本',\r\n    'autoTypeSet':{\r\n        'mergeLine':\"合并空行\",\r\n        'delLine':\"清除空行\",\r\n        'removeFormat':\"清除格式\",\r\n        'indent':\"首行缩进\",\r\n        'alignment':\"对齐方式\",\r\n        'imageFloat':\"图片浮动\",\r\n        'removeFontsize':\"清除字号\",\r\n        'removeFontFamily':\"清除字体\",\r\n        'removeHtml':\"清除冗余HTML代码\",\r\n        'pasteFilter':\"粘贴过滤\",\r\n        'run':\"执行\",\r\n        'symbol':'符号转换',\r\n        'bdc2sb':'全角转半角',\r\n        'tobdc':'半角转全角'\r\n    },\r\n\r\n    'background':{\r\n        'static':{\r\n            'lang_background_normal':'背景设置',\r\n            'lang_background_local':'在线图片',\r\n            'lang_background_set':'选项',\r\n            'lang_background_none':'无背景色',\r\n            'lang_background_colored':'有背景色',\r\n            'lang_background_color':'颜色设置',\r\n            'lang_background_netimg':'网络图片',\r\n            'lang_background_align':'对齐方式',\r\n            'lang_background_position':'精确定位',\r\n            'repeatType':{'options':[\"居中\", \"横向重复\", \"纵向重复\", \"平铺\",\"自定义\"]}\r\n\r\n        },\r\n        'noUploadImage':\"当前未上传过任何图片！\",\r\n        'toggleSelect':\"单击可切换选中状态\\n原图尺寸: \"\r\n    },\r\n    //===============dialog i18N=======================\r\n    'insertimage':{\r\n        'static':{\r\n            'lang_tab_remote':\"插入图片\", //节点\r\n            'lang_tab_upload':\"本地上传\",\r\n            'lang_tab_online':\"在线管理\",\r\n            'lang_tab_search':\"图片搜索\",\r\n            'lang_input_url':\"地 址：\",\r\n            'lang_input_size':\"大 小：\",\r\n            'lang_input_width':\"宽度\",\r\n            'lang_input_height':\"高度\",\r\n            'lang_input_border':\"边 框：\",\r\n            'lang_input_vhspace':\"边 距：\",\r\n            'lang_input_title':\"描 述：\",\r\n            'lang_input_align':'图片浮动方式：',\r\n            'lang_imgLoading':\"　图片加载中……\",\r\n            'lang_start_upload':\"开始上传\",\r\n            'lock':{'title':\"锁定宽高比例\"}, //属性\r\n            'searchType':{'title':\"图片类型\", 'options':[\"新闻\", \"壁纸\", \"表情\", \"头像\"]}, //select的option\r\n            'searchTxt':{'value':\"请输入搜索关键词\"},\r\n            'searchBtn':{'value':\"百度一下\"},\r\n            'searchReset':{'value':\"清空搜索\"},\r\n            'noneAlign':{'title':'无浮动'},\r\n            'leftAlign':{'title':'左浮动'},\r\n            'rightAlign':{'title':'右浮动'},\r\n            'centerAlign':{'title':'居中独占一行'}\r\n        },\r\n        'uploadSelectFile':'点击选择图片',\r\n        'uploadAddFile':'继续添加',\r\n        'uploadStart':'开始上传',\r\n        'uploadPause':'暂停上传',\r\n        'uploadContinue':'继续上传',\r\n        'uploadRetry':'重试上传',\r\n        'uploadDelete':'删除',\r\n        'uploadTurnLeft':'向左旋转',\r\n        'uploadTurnRight':'向右旋转',\r\n        'uploadPreview':'预览中',\r\n        'uploadNoPreview':'不能预览',\r\n        'updateStatusReady': '选中_张图片，共_KB。',\r\n        'updateStatusConfirm': '已成功上传_张照片，_张照片上传失败',\r\n        'updateStatusFinish': '共_张（_KB），_张上传成功',\r\n        'updateStatusError': '，_张上传失败。',\r\n        'errorNotSupport': 'WebUploader 不支持您的浏览器！如果你使用的是IE浏览器，请尝试升级 flash 播放器。',\r\n        'errorLoadConfig': '后端配置项没有正常加载，上传插件不能正常使用！',\r\n        'errorExceedSize':'文件大小超出',\r\n        'errorFileType':'文件格式不允许',\r\n        'errorInterrupt':'文件传输中断',\r\n        'errorUploadRetry':'上传失败，请重试',\r\n        'errorHttp':'http请求错误',\r\n        'errorServerUpload':'服务器返回出错',\r\n        'remoteLockError':\"宽高不正确,不能所定比例\",\r\n        'numError':\"请输入正确的长度或者宽度值！例如：123，400\",\r\n        'imageUrlError':\"不允许的图片格式或者图片域！\",\r\n        'imageLoadError':\"图片加载失败！请检查链接地址或网络状态！\",\r\n        'searchRemind':\"请输入搜索关键词\",\r\n        'searchLoading':\"图片加载中，请稍后……\",\r\n        'searchRetry':\" :( ，抱歉，没有找到图片！请重试一次！\"\r\n    },\r\n    'attachment':{\r\n        'static':{\r\n            'lang_tab_upload': '上传附件',\r\n            'lang_tab_online': '在线附件',\r\n            'lang_start_upload':\"开始上传\",\r\n            'lang_drop_remind':\"可以将文件拖到这里，单次最多可选100个文件\"\r\n        },\r\n        'uploadSelectFile':'点击选择文件',\r\n        'uploadAddFile':'继续添加',\r\n        'uploadStart':'开始上传',\r\n        'uploadPause':'暂停上传',\r\n        'uploadContinue':'继续上传',\r\n        'uploadRetry':'重试上传',\r\n        'uploadDelete':'删除',\r\n        'uploadTurnLeft':'向左旋转',\r\n        'uploadTurnRight':'向右旋转',\r\n        'uploadPreview':'预览中',\r\n        'updateStatusReady': '选中_个文件，共_KB。',\r\n        'updateStatusConfirm': '已成功上传_个文件，_个文件上传失败',\r\n        'updateStatusFinish': '共_个（_KB），_个上传成功',\r\n        'updateStatusError': '，_张上传失败。',\r\n        'errorNotSupport': 'WebUploader 不支持您的浏览器！如果你使用的是IE浏览器，请尝试升级 flash 播放器。',\r\n        'errorLoadConfig': '后端配置项没有正常加载，上传插件不能正常使用！',\r\n        'errorExceedSize':'文件大小超出',\r\n        'errorFileType':'文件格式不允许',\r\n        'errorInterrupt':'文件传输中断',\r\n        'errorUploadRetry':'上传失败，请重试',\r\n        'errorHttp':'http请求错误',\r\n        'errorServerUpload':'服务器返回出错'\r\n    },\r\n    'insertvideo':{\r\n        'static':{\r\n            'lang_tab_insertV':\"插入视频\",\r\n            'lang_tab_searchV':\"搜索视频\",\r\n            'lang_tab_uploadV':\"上传视频\",\r\n            'lang_video_url':\"视频网址\",\r\n            'lang_video_size':\"视频尺寸\",\r\n            'lang_videoW':\"宽度\",\r\n            'lang_videoH':\"高度\",\r\n            'lang_alignment':\"对齐方式\",\r\n            'videoSearchTxt':{'value':\"请输入搜索关键字！\"},\r\n            'videoType':{'options':[\"全部\", \"热门\", \"娱乐\", \"搞笑\", \"体育\", \"科技\", \"综艺\"]},\r\n            'videoSearchBtn':{'value':\"百度一下\"},\r\n            'videoSearchReset':{'value':\"清空结果\"},\r\n\r\n            'lang_input_fileStatus':' 当前未上传文件',\r\n            'startUpload':{'style':\"background:url(upload.png) no-repeat;\"},\r\n\r\n            'lang_upload_size':\"视频尺寸\",\r\n            'lang_upload_width':\"宽度\",\r\n            'lang_upload_height':\"高度\",\r\n            'lang_upload_alignment':\"对齐方式\",\r\n            'lang_format_advice':\"建议使用mp4格式.\"\r\n\r\n        },\r\n        'numError':\"请输入正确的数值，如123,400\",\r\n        'floatLeft':\"左浮动\",\r\n        'floatRight':\"右浮动\",\r\n        '\"default\"':\"默认\",\r\n        'block':\"独占一行\",\r\n        'urlError':\"输入的视频地址有误，请检查后再试！\",\r\n        'loading':\" &nbsp;视频加载中，请等待……\",\r\n        'clickToSelect':\"点击选中\",\r\n        'goToSource':'访问源视频',\r\n        'noVideo':\" &nbsp; &nbsp;抱歉，找不到对应的视频，请重试！\",\r\n\r\n        'browseFiles':'浏览文件',\r\n        'uploadSuccess':'上传成功!',\r\n        'delSuccessFile':'从成功队列中移除',\r\n        'delFailSaveFile':'移除保存失败文件',\r\n        'statusPrompt':' 个文件已上传！ ',\r\n        'flashVersionError':'当前Flash版本过低，请更新FlashPlayer后重试！',\r\n        'flashLoadingError':'Flash加载失败!请检查路径或网络状态',\r\n        'fileUploadReady':'等待上传……',\r\n        'delUploadQueue':'从上传队列中移除',\r\n        'limitPrompt1':'单次不能选择超过',\r\n        'limitPrompt2':'个文件！请重新选择！',\r\n        'delFailFile':'移除失败文件',\r\n        'fileSizeLimit':'文件大小超出限制！',\r\n        'emptyFile':'空文件无法上传！',\r\n        'fileTypeError':'文件类型不允许！',\r\n        'unknownError':'未知错误！',\r\n        'fileUploading':'上传中，请等待……',\r\n        'cancelUpload':'取消上传',\r\n        'netError':'网络错误',\r\n        'failUpload':'上传失败!',\r\n        'serverIOError':'服务器IO错误！',\r\n        'noAuthority':'无权限！',\r\n        'fileNumLimit':'上传个数限制',\r\n        'failCheck':'验证失败，本次上传被跳过！',\r\n        'fileCanceling':'取消中，请等待……',\r\n        'stopUploading':'上传已停止……',\r\n\r\n        'uploadSelectFile':'点击选择文件',\r\n        'uploadAddFile':'继续添加',\r\n        'uploadStart':'开始上传',\r\n        'uploadPause':'暂停上传',\r\n        'uploadContinue':'继续上传',\r\n        'uploadRetry':'重试上传',\r\n        'uploadDelete':'删除',\r\n        'uploadTurnLeft':'向左旋转',\r\n        'uploadTurnRight':'向右旋转',\r\n        'uploadPreview':'预览中',\r\n        'updateStatusReady': '选中_个文件，共_KB。',\r\n        'updateStatusConfirm': '成功上传_个，_个失败',\r\n        'updateStatusFinish': '共_个(_KB)，_个成功上传',\r\n        'updateStatusError': '，_张上传失败。',\r\n        'errorNotSupport': 'WebUploader 不支持您的浏览器！如果你使用的是IE浏览器，请尝试升级 flash 播放器。',\r\n        'errorLoadConfig': '后端配置项没有正常加载，上传插件不能正常使用！',\r\n        'errorExceedSize':'文件大小超出',\r\n        'errorFileType':'文件格式不允许',\r\n        'errorInterrupt':'文件传输中断',\r\n        'errorUploadRetry':'上传失败，请重试',\r\n        'errorHttp':'http请求错误',\r\n        'errorServerUpload':'服务器返回出错'\r\n    },\r\n    'webapp':{\r\n        'tip1':\"本功能由百度APP提供，如看到此页面，请各位站长首先申请百度APPKey!\",\r\n        'tip2':\"申请完成之后请至ueditor.config.js中配置获得的appkey! \",\r\n        'applyFor':\"点此申请\",\r\n        'anthorApi':\"百度API\"\r\n    },\r\n    'template':{\r\n        'static':{\r\n            'lang_template_bkcolor':'背景颜色',\r\n            'lang_template_clear' : '保留原有内容',\r\n            'lang_template_select' : '选择模板'\r\n        },\r\n        'blank':\"空白文档\",\r\n        'blog':\"博客文章\",\r\n        'resume':\"个人简历\",\r\n        'richText':\"图文混排\",\r\n        'sciPapers':\"科技论文\"\r\n\r\n\r\n    },\r\n    'scrawl':{\r\n        'static':{\r\n            'lang_input_previousStep':\"上一步\",\r\n            'lang_input_nextsStep':\"下一步\",\r\n            'lang_input_clear':'清空',\r\n            'lang_input_addPic':'添加背景',\r\n            'lang_input_ScalePic':'缩放背景',\r\n            'lang_input_removePic':'删除背景',\r\n            'J_imgTxt':{title:'添加背景图片'}\r\n        },\r\n        'noScarwl':\"尚未作画，白纸一张~\",\r\n        'scrawlUpLoading':\"涂鸦上传中,别急哦~\",\r\n        'continueBtn':\"继续\",\r\n        'imageError':\"糟糕，图片读取失败了！\",\r\n        'backgroundUploading':'背景图片上传中,别急哦~'\r\n    },\r\n    'music':{\r\n        'static':{\r\n            'lang_input_tips':\"输入歌手/歌曲/专辑，搜索您感兴趣的音乐！\",\r\n            'J_searchBtn':{value:'搜索歌曲'}\r\n        },\r\n        'emptyTxt':'未搜索到相关音乐结果，请换一个关键词试试。',\r\n        'chapter':'歌曲',\r\n        'singer':'歌手',\r\n        'special':'专辑',\r\n        'listenTest':'试听'\r\n    },\r\n    'anchor':{\r\n        'static':{\r\n            'lang_input_anchorName':'锚点名字：'\r\n        }\r\n    },\r\n    'charts':{\r\n        'static':{\r\n            'lang_data_source':'数据源：',\r\n            'lang_chart_format': '图表格式：',\r\n            'lang_data_align': '数据对齐方式',\r\n            'lang_chart_align_same': '数据源与图表X轴Y轴一致',\r\n            'lang_chart_align_reverse': '数据源与图表X轴Y轴相反',\r\n            'lang_chart_title': '图表标题',\r\n            'lang_chart_main_title': '主标题：',\r\n            'lang_chart_sub_title': '子标题：',\r\n            'lang_chart_x_title': 'X轴标题：',\r\n            'lang_chart_y_title': 'Y轴标题：',\r\n            'lang_chart_tip': '提示文字',\r\n            'lang_cahrt_tip_prefix': '提示文字前缀：',\r\n            'lang_cahrt_tip_description': '仅饼图有效， 当鼠标移动到饼图中相应的块上时，提示框内的文字的前缀',\r\n            'lang_chart_data_unit': '数据单位',\r\n            'lang_chart_data_unit_title': '单位：',\r\n            'lang_chart_data_unit_description': '显示在每个数据点上的数据的单位， 比如： 温度的单位 ℃',\r\n            'lang_chart_type': '图表类型：',\r\n            'lang_prev_btn': '上一个',\r\n            'lang_next_btn': '下一个'\r\n        }\r\n    },\r\n    'emotion':{\r\n        'static':{\r\n            'lang_input_choice':'精选',\r\n            'lang_input_Tuzki':'兔斯基',\r\n            'lang_input_BOBO':'BOBO',\r\n            'lang_input_lvdouwa':'绿豆蛙',\r\n            'lang_input_babyCat':'baby猫',\r\n            'lang_input_bubble':'泡泡',\r\n            'lang_input_youa':'有啊'\r\n        }\r\n    },\r\n    'gmap':{\r\n        'static':{\r\n            'lang_input_address':'地址',\r\n            'lang_input_search':'搜索',\r\n            'address':{value:\"北京\"}\r\n        },\r\n        searchError:'无法定位到该地址!'\r\n    },\r\n    'help':{\r\n        'static':{\r\n            'lang_input_about':'关于UEditor',\r\n            'lang_input_shortcuts':'快捷键',\r\n            'lang_input_introduction':'UEditor是由百度web前端研发部开发的所见即所得富文本web编辑器，具有轻量，可定制，注重用户体验等特点。开源基于BSD协议，允许自由使用和修改代码。',\r\n            'lang_Txt_shortcuts':'快捷键',\r\n            'lang_Txt_func':'功能',\r\n            'lang_Txt_bold':'给选中字设置为加粗',\r\n            'lang_Txt_copy':'复制选中内容',\r\n            'lang_Txt_cut':'剪切选中内容',\r\n            'lang_Txt_Paste':'粘贴',\r\n            'lang_Txt_undo':'重新执行上次操作',\r\n            'lang_Txt_redo':'撤销上一次操作',\r\n            'lang_Txt_italic':'给选中字设置为斜体',\r\n            'lang_Txt_underline':'给选中字加下划线',\r\n            'lang_Txt_selectAll':'全部选中',\r\n            'lang_Txt_visualEnter':'软回车',\r\n            'lang_Txt_fullscreen':'全屏'\r\n        }\r\n    },\r\n    'insertframe':{\r\n        'static':{\r\n            'lang_input_address':'地址：',\r\n            'lang_input_width':'宽度：',\r\n            'lang_input_height':'高度：',\r\n            'lang_input_isScroll':'允许滚动条：',\r\n            'lang_input_frameborder':'显示框架边框：',\r\n            'lang_input_alignMode':'对齐方式：',\r\n            'align':{title:\"对齐方式\", options:[\"默认\", \"左对齐\", \"右对齐\", \"居中\"]}\r\n        },\r\n        'enterAddress':'请输入地址!'\r\n    },\r\n    'link':{\r\n        'static':{\r\n            'lang_input_text':'文本内容：',\r\n            'lang_input_url':'链接地址：',\r\n            'lang_input_title':'标题：',\r\n            'lang_input_target':'是否在新窗口打开：'\r\n        },\r\n        'validLink':'只支持选中一个链接时生效',\r\n        'httpPrompt':'您输入的超链接中不包含http等协议名称，默认将为您添加http://前缀'\r\n    },\r\n    'map':{\r\n        'static':{\r\n            lang_city:\"城市\",\r\n            lang_address:\"地址\",\r\n            city:{value:\"北京\"},\r\n            lang_search:\"搜索\",\r\n            lang_dynamicmap:\"插入动态地图\"\r\n        },\r\n        cityMsg:\"请选择城市\",\r\n        errorMsg:\"抱歉，找不到该位置！\"\r\n    },\r\n    'searchreplace':{\r\n        'static':{\r\n            lang_tab_search:\"查找\",\r\n            lang_tab_replace:\"替换\",\r\n            lang_search1:\"查找\",\r\n            lang_search2:\"查找\",\r\n            lang_replace:\"替换\",\r\n            lang_searchReg:'支持正则表达式，添加前后斜杠标示为正则表达式，例如“/表达式/”',\r\n            lang_searchReg1:'支持正则表达式，添加前后斜杠标示为正则表达式，例如“/表达式/”',\r\n            lang_case_sensitive1:\"区分大小写\",\r\n            lang_case_sensitive2:\"区分大小写\",\r\n            nextFindBtn:{value:\"下一个\"},\r\n            preFindBtn:{value:\"上一个\"},\r\n            nextReplaceBtn:{value:\"下一个\"},\r\n            preReplaceBtn:{value:\"上一个\"},\r\n            repalceBtn:{value:\"替换\"},\r\n            repalceAllBtn:{value:\"全部替换\"}\r\n        },\r\n        getEnd:\"已经搜索到文章末尾！\",\r\n        getStart:\"已经搜索到文章头部\",\r\n        countMsg:\"总共替换了{#count}处！\"\r\n    },\r\n    'snapscreen':{\r\n        'static':{\r\n            lang_showMsg:\"截图功能需要首先安装UEditor截图插件！ \",\r\n            lang_download:\"点此下载\",\r\n            lang_step1:\"第一步，下载UEditor截图插件并运行安装。\",\r\n            lang_step2:\"第二步，插件安装完成后即可使用，如不生效，请重启浏览器后再试！\"\r\n        }\r\n    },\r\n    'spechars':{\r\n        'static':{},\r\n        tsfh:\"特殊字符\",\r\n        lmsz:\"罗马字符\",\r\n        szfh:\"数学字符\",\r\n        rwfh:\"日文字符\",\r\n        xlzm:\"希腊字母\",\r\n        ewzm:\"俄文字符\",\r\n        pyzm:\"拼音字母\",\r\n        yyyb:\"英语音标\",\r\n        zyzf:\"其他\"\r\n    },\r\n    'edittable':{\r\n        'static':{\r\n            'lang_tableStyle':'表格样式',\r\n            'lang_insertCaption':'添加表格名称行',\r\n            'lang_insertTitle':'添加表格标题行',\r\n            'lang_insertTitleCol':'添加表格标题列',\r\n            'lang_orderbycontent':\"使表格内容可排序\",\r\n            'lang_tableSize':'自动调整表格尺寸',\r\n            'lang_autoSizeContent':'按表格文字自适应',\r\n            'lang_autoSizePage':'按页面宽度自适应',\r\n            'lang_example':'示例',\r\n            'lang_borderStyle':'表格边框',\r\n            'lang_color':'颜色:'\r\n        },\r\n        captionName:'表格名称',\r\n        titleName:'标题',\r\n        cellsName:'内容',\r\n        errorMsg:'有合并单元格，不可排序'\r\n    },\r\n    'edittip':{\r\n        'static':{\r\n            lang_delRow:'删除整行',\r\n            lang_delCol:'删除整列'\r\n        }\r\n    },\r\n    'edittd':{\r\n        'static':{\r\n            lang_tdBkColor:'背景颜色:'\r\n        }\r\n    },\r\n    'formula':{\r\n        'static':{\r\n        }\r\n    },\r\n    'wordimage':{\r\n        'static':{\r\n            lang_resave:\"转存步骤\",\r\n            uploadBtn:{src:\"upload.png\",alt:\"上传\"},\r\n            clipboard:{style:\"background: url(copy.png) -153px -1px no-repeat;\"},\r\n            lang_step:\"1、点击顶部复制按钮，将地址复制到剪贴板；2、点击添加照片按钮，在弹出的对话框中使用Ctrl+V粘贴地址；3、点击打开后选择图片上传流程。\"\r\n        },\r\n        'fileType':\"图片\",\r\n        'flashError':\"FLASH初始化失败，请检查FLASH插件是否正确安装！\",\r\n        'netError':\"网络连接错误，请重试！\",\r\n        'copySuccess':\"图片地址已经复制！\",\r\n        'flashI18n':{} //留空默认中文\r\n    },\r\n    'autosave': {\r\n        'saving':'保存中...',\r\n        'success':'本地保存成功'\r\n    }\r\n};\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/themes/default/css/ueditor.css",
    "content": "/*基础UI构建\r\n*/\r\n/* common layer */\r\n.edui-default .edui-box {\r\n    border: none;\r\n    padding: 0;\r\n    margin: 0;\r\n    overflow: hidden;\r\n}\r\n\r\n.edui-default a.edui-box {\r\n    display: block;\r\n    text-decoration: none;\r\n    color: black;\r\n}\r\n\r\n.edui-default a.edui-box:hover {\r\n    text-decoration: none;\r\n}\r\n\r\n.edui-default a.edui-box:active {\r\n    text-decoration: none;\r\n}\r\n\r\n.edui-default table.edui-box {\r\n    border-collapse: collapse;\r\n}\r\n\r\n.edui-default ul.edui-box {\r\n    list-style-type: none;\r\n}\r\n\r\ndiv.edui-box {\r\n    position: relative;\r\n    display: -moz-inline-box !important;\r\n    display: inline-block !important;\r\n    vertical-align: top;\r\n}\r\n\r\n.edui-default .edui-clearfix {\r\n    zoom: 1\r\n}\r\n\r\n.edui-default .edui-clearfix:after {\r\n    content: '\\20';\r\n    display: block;\r\n    clear: both;\r\n}\r\n\r\n * html div.edui-box {\r\n    display: inline !important;\r\n}\r\n\r\n*:first-child+html div.edui-box {\r\n    display: inline !important;\r\n}\r\n\r\n/* control layout */\r\n.edui-default .edui-button-body, .edui-splitbutton-body, .edui-menubutton-body, .edui-combox-body {\r\n    position: relative;\r\n}\r\n\r\n.edui-default .edui-popup {\r\n    position: absolute;\r\n    -webkit-user-select: none;\r\n    -moz-user-select: none;\r\n}\r\n\r\n.edui-default .edui-popup .edui-shadow {\r\n    position: absolute;\r\n    z-index: -1;\r\n}\r\n\r\n.edui-default .edui-popup .edui-bordereraser {\r\n    position: absolute;\r\n    overflow: hidden;\r\n}\r\n\r\n.edui-default .edui-tablepicker .edui-canvas {\r\n    position: relative;\r\n}\r\n\r\n.edui-default .edui-tablepicker .edui-canvas .edui-overlay {\r\n    position: absolute;\r\n}\r\n\r\n.edui-default .edui-dialog-modalmask, .edui-dialog-dragmask {\r\n    position: absolute;\r\n    left: 0;\r\n    top: 0;\r\n    width: 100%;\r\n    height: 100%;\r\n}\r\n\r\n.edui-default .edui-toolbar {\r\n    position: relative;\r\n}\r\n\r\n/*\r\n * default theme\r\n */\r\n.edui-default .edui-label {\r\n    cursor: default;\r\n}\r\n\r\n.edui-default span.edui-clickable {\r\n    color: blue;\r\n    cursor: pointer;\r\n    text-decoration: underline;\r\n}\r\n\r\n.edui-default span.edui-unclickable {\r\n    color: gray;\r\n    cursor: default;\r\n}\r\n/* 工具栏 */\r\n.edui-default .edui-toolbar {\r\n    cursor: default;\r\n    -webkit-user-select: none;\r\n    -moz-user-select: none;\r\n    padding: 1px;\r\n    overflow: hidden; /*全屏下单独一行不占位*/\r\n    zoom: 1;\r\n    width:auto;\r\n    height:auto;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-button,\r\n.edui-default .edui-toolbar .edui-splitbutton,\r\n.edui-default .edui-toolbar .edui-menubutton,\r\n.edui-default .edui-toolbar .edui-combox {\r\n    margin: 1px;\r\n}\r\n/*UI工具栏、编辑区域、底部*/\r\n.edui-default .edui-editor {\r\n    z-index: 1 !important;\r\n    border: 1px solid #d4d4d4;\r\n    background-color: white;\r\n    position: relative;\r\n    overflow: visible;\r\n    -webkit-border-radius: 4px;\r\n    -moz-border-radius: 4px;\r\n    border-radius: 4px;\r\n}\r\n.edui-editor div{\r\n    width:auto;\r\n    height:auto;\r\n}\r\n.edui-default .edui-editor-toolbarbox {\r\n    position: relative;\r\n    zoom: 1;\r\n    -webkit-box-shadow:0 1px 4px rgba(204, 204, 204, 0.6);\r\n    -moz-box-shadow:0 1px 4px rgba(204, 204, 204, 0.6);\r\n    box-shadow:0 1px 4px rgba(204, 204, 204, 0.6);\r\n    border-top-left-radius:2px;\r\n    border-top-right-radius:2px;\r\n}\r\n\r\n.edui-default .edui-editor-toolbarboxouter {\r\n    border-bottom: 1px solid #d4d4d4;\r\n    background-color: #fafafa;\r\n    background-image: -moz-linear-gradient(top, #ffffff, #f2f2f2);\r\n    background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#f2f2f2));\r\n    background-image: -webkit-linear-gradient(top, #ffffff, #f2f2f2);\r\n    background-image: -o-linear-gradient(top, #ffffff, #f2f2f2);\r\n    background-image: linear-gradient(to bottom, #ffffff, #f2f2f2);\r\n    background-repeat: repeat-x;\r\n    /*border: 1px solid #d4d4d4;*/\r\n    -webkit-border-radius: 4px 4px 0 0;\r\n    -moz-border-radius: 4px 4px 0 0;\r\n    border-radius: 4px 4px 0 0;\r\n    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff2f2f2', GradientType=0);\r\n    *zoom: 1;\r\n    -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);\r\n    -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);\r\n    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);\r\n}\r\n\r\n.edui-default .edui-editor-toolbarboxinner {\r\n    padding: 2px;\r\n}\r\n\r\n.edui-default .edui-editor-iframeholder {\r\n    position: relative;\r\n    /*for fix ie6 toolbarmsg under iframe bug. relative -> static */\r\n    /*_position: static !important;*\r\n}\r\n\r\n.edui-default .edui-editor-iframeholder textarea {\r\n    font-family: consolas, \"Courier New\", \"lucida console\", monospace;\r\n    font-size: 12px;\r\n    line-height: 18px;\r\n}\r\n\r\n.edui-default .edui-editor-bottombar {\r\n    /*border-top: 1px solid #ccc;*/\r\n    /*height: 20px;*/\r\n    /*width: 40%;*/\r\n    /*float: left;*/\r\n    /*overflow: hidden;*/\r\n}\r\n\r\n.edui-default .edui-editor-bottomContainer {\r\n    overflow: hidden;\r\n}\r\n\r\n.edui-default .edui-editor-bottomContainer table {\r\n    width: 100%;\r\n    height: 0;\r\n    overflow: hidden;\r\n    border-spacing: 0;\r\n}\r\n\r\n.edui-default .edui-editor-bottomContainer td {\r\n    white-space: nowrap;\r\n    border-top: 1px solid #ccc;\r\n    line-height: 20px;\r\n    font-size: 12px;\r\n    font-family: Arial, Helvetica, Tahoma, Verdana, Sans-Serif;\r\n}\r\n\r\n.edui-default .edui-editor-wordcount {\r\n    text-align: right;\r\n    margin-right: 5px;\r\n    color: #aaa;\r\n}\r\n.edui-default .edui-editor-scale {\r\n    width: 12px;\r\n}\r\n.edui-default .edui-editor-scale .edui-editor-icon {\r\n    float: right;\r\n    width: 100%;\r\n    height: 12px;\r\n    margin-top: 10px;\r\n    background: url(../images/scale.png) no-repeat;\r\n    cursor: se-resize;\r\n}\r\n.edui-default .edui-editor-breadcrumb {\r\n    margin: 2px 0 0 3px;\r\n}\r\n\r\n.edui-default .edui-editor-breadcrumb span {\r\n    cursor: pointer;\r\n    text-decoration: underline;\r\n    color: blue;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-for-fullscreen {\r\n    float: right;\r\n}\r\n\r\n.edui-default .edui-bubble .edui-popup-content {\r\n    border: 1px solid #DCAC6C;\r\n    background-color: #fff6d9;\r\n    padding: 5px;\r\n    font-size: 10pt;\r\n    font-family: \"宋体\";\r\n}\r\n\r\n.edui-default .edui-bubble .edui-shadow {\r\n    /*box-shadow: 1px 1px 3px #818181;*/\r\n    /*-webkit-box-shadow: 2px 2px 3px #818181;*/\r\n    /*-moz-box-shadow: 2px 2px 3px #818181;*/\r\n    /*filter: progid:DXImageTransform.Microsoft.Blur(PixelRadius = '2', MakeShadow = 'true', ShadowOpacity = '0.5');*/\r\n}\r\n\r\n.edui-default .edui-editor-toolbarmsg {\r\n    background-color: #FFF6D9;\r\n    border-bottom: 1px solid #ccc;\r\n    position: absolute;\r\n    bottom: -25px;\r\n    left: 0;\r\n    z-index: 1009;\r\n    width: 99.9%;\r\n}\r\n\r\n.edui-default .edui-editor-toolbarmsg-upload {\r\n    font-size: 14px;\r\n    color: blue;\r\n    width: 100px;\r\n    height: 16px;\r\n    line-height: 16px;\r\n    cursor: pointer;\r\n    position: absolute;\r\n    top: 5px;\r\n    left: 350px;\r\n}\r\n\r\n.edui-default .edui-editor-toolbarmsg-label {\r\n    font-size: 12px;\r\n    line-height: 16px;\r\n    padding: 4px;\r\n}\r\n\r\n.edui-default .edui-editor-toolbarmsg-close {\r\n    float: right;\r\n    width: 20px;\r\n    height: 16px;\r\n    line-height: 16px;\r\n    cursor: pointer;\r\n    color: red;\r\n}\r\n/*可选中菜单按钮*/\r\n.edui-default .edui-list .edui-bordereraser {\r\n    display: none;\r\n}\r\n\r\n.edui-default .edui-listitem {\r\n    padding: 1px;\r\n    white-space: nowrap;\r\n}\r\n\r\n.edui-default .edui-list .edui-state-hover {\r\n    position: relative;\r\n    background-color: #fff5d4;\r\n    border: 1px solid #dcac6c;\r\n    padding: 0;\r\n}\r\n\r\n.edui-default .edui-for-fontfamily .edui-listitem-label {\r\n    min-width: 130px;\r\n    _width: 120px;\r\n    font-size: 12px;\r\n    height: 22px;\r\n    line-height: 22px;\r\n    padding-left: 5px;\r\n}\r\n.edui-default .edui-for-insertcode .edui-listitem-label {\r\n    min-width: 120px;\r\n    _width: 120px;\r\n    font-size: 12px;\r\n    height: 22px;\r\n    line-height: 22px;\r\n    padding-left: 5px;\r\n}\r\n.edui-default .edui-for-underline .edui-listitem-label {\r\n    min-width: 120px;\r\n    _width: 120px;\r\n    padding: 3px 5px;\r\n    font-size: 12px;\r\n}\r\n\r\n.edui-default .edui-for-fontsize .edui-listitem-label {\r\n    min-width: 120px;\r\n    _width: 120px;\r\n    padding: 3px 5px;\r\n\r\n}\r\n\r\n.edui-default .edui-for-paragraph .edui-listitem-label {\r\n    min-width: 200px;\r\n    _width: 200px;\r\n    padding: 2px 5px;\r\n}\r\n\r\n.edui-default .edui-for-rowspacingtop .edui-listitem-label,\r\n.edui-default .edui-for-rowspacingbottom .edui-listitem-label {\r\n    min-width: 53px;\r\n    _width: 53px;\r\n    padding: 2px 5px;\r\n}\r\n\r\n.edui-default .edui-for-lineheight .edui-listitem-label {\r\n    min-width: 53px;\r\n    _width: 53px;\r\n    padding: 2px 5px;\r\n}\r\n\r\n.edui-default .edui-for-customstyle .edui-listitem-label {\r\n    min-width: 200px;\r\n    _width: 200px;\r\n    width: 200px !important;\r\n    padding: 2px 5px;\r\n}\r\n/* 可选中按钮弹出菜单*/\r\n.edui-default .edui-menu {\r\n    z-index: 3000;\r\n}\r\n\r\n.edui-default .edui-menu .edui-popup-content {\r\n    padding: 3px;\r\n}\r\n\r\n.edui-default .edui-menu-body {\r\n    _width: 150px;\r\n    min-width: 170px;\r\n    background: url(\"../images/sparator_v.png\") repeat-y 25px;\r\n}\r\n\r\n.edui-default .edui-menuitem-body {\r\n}\r\n\r\n.edui-default .edui-menuitem {\r\n    height: 20px;\r\n    cursor: default;\r\n    vertical-align: top;\r\n}\r\n\r\n.edui-default .edui-menuitem .edui-icon {\r\n    width: 20px !important;\r\n    height: 20px !important;\r\n    background: url(../images/icons.png) 0 -4000px;\r\n    background: url(../images/icons.gif) 0 -4000px\\9;\r\n}\r\n\r\n.edui-default .edui-menuitem .edui-label {\r\n    font-size: 12px;\r\n    line-height: 20px;\r\n    height: 20px;\r\n    padding-left: 10px;\r\n}\r\n\r\n.edui-default .edui-state-checked .edui-menuitem-body {\r\n    background: url(\"../images/icons-all.gif\") no-repeat 6px -205px;\r\n}\r\n\r\n.edui-default .edui-state-disabled .edui-menuitem-label {\r\n    color: gray;\r\n}\r\n\r\n\r\n/*不可选中菜单按钮 */\r\n.edui-default .edui-toolbar .edui-combox-body .edui-button-body {\r\n    width: 60px;\r\n    font-size: 12px;\r\n    height: 20px;\r\n    line-height: 20px;\r\n    padding-left: 5px;\r\n    white-space: nowrap;\r\n    margin: 0 3px 0 0;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-combox-body .edui-arrow {\r\n    background: url(../images/icons.png) -741px 0;\r\n    _background: url(../images/icons.gif) -741px 0;\r\n    height: 20px;\r\n    width: 9px;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-combox .edui-combox-body {\r\n    border: 1px solid #CCC;\r\n    background-color: white;\r\n    border-radius: 2px;\r\n    -webkit-border-radius: 2px;\r\n    -moz-border-radius: 2px;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-combox-body .edui-splitborder {\r\n    display: none;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-combox-body .edui-arrow {\r\n    border-left: 1px solid #CCC;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-state-hover .edui-combox-body {\r\n    background-color: #fff5d4;\r\n    border: 1px solid #dcac6c;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-state-hover .edui-combox-body .edui-arrow {\r\n    border-left: 1px solid #dcac6c;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-state-checked .edui-combox-body {\r\n    background-color: #FFE69F;\r\n    border: 1px solid #DCAC6C;\r\n}\r\n\r\n.edui-toolbar .edui-state-checked .edui-combox-body .edui-arrow {\r\n    border-left: 1px solid #DCAC6C;\r\n}\r\n\r\n.edui-toolbar .edui-state-disabled .edui-combox-body {\r\n    background-color: #F0F0EE;\r\n    opacity: 0.3;\r\n    filter: alpha(opacity = 30);\r\n}\r\n\r\n.edui-toolbar .edui-state-opened .edui-combox-body {\r\n    background-color: white;\r\n    border: 1px solid gray;\r\n}\r\n/*普通按钮样式及状态*/\r\n.edui-default .edui-toolbar .edui-button .edui-icon,\r\n.edui-default .edui-toolbar .edui-menubutton .edui-icon,\r\n.edui-default .edui-toolbar .edui-splitbutton .edui-icon {\r\n    height: 20px !important;\r\n    width: 20px !important;\r\n    background-image: url(../images/icons.png);\r\n    background-image: url(../images/icons.gif) \\9;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-button .edui-button-wrap {\r\n    padding: 1px;\r\n    position: relative;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-button .edui-state-hover .edui-button-wrap {\r\n    background-color: #fff5d4;\r\n    padding: 0;\r\n    border: 1px solid #dcac6c;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-button .edui-state-checked .edui-button-wrap {\r\n    background-color: #ffe69f;\r\n    padding: 0;\r\n    border: 1px solid #dcac6c;\r\n    border-radius: 2px;\r\n    -webkit-border-radius: 2px;\r\n    -moz-border-radius: 2px;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-button .edui-state-active .edui-button-wrap {\r\n    background-color: #ffffff;\r\n    padding: 0;\r\n    border: 1px solid gray;\r\n}\r\n.edui-default .edui-toolbar .edui-state-disabled .edui-label {\r\n    color: #ccc;\r\n}\r\n.edui-default .edui-toolbar .edui-state-disabled .edui-icon {\r\n    opacity: 0.3;\r\n    filter: alpha(opacity = 30);\r\n}\r\n\r\n/* toolbar icons */\r\n.edui-default .edui-for-undo .edui-icon {\r\n    background-position: -160px 0;\r\n}\r\n\r\n.edui-default  .edui-for-redo .edui-icon {\r\n    background-position: -100px 0;\r\n}\r\n\r\n.edui-default  .edui-for-bold .edui-icon {\r\n    background-position: 0 0;\r\n}\r\n\r\n.edui-default  .edui-for-italic .edui-icon {\r\n    background-position: -60px 0;\r\n}\r\n\r\n.edui-default  .edui-for-fontborder .edui-icon {\r\n    background-position:-160px -40px;\r\n}\r\n.edui-default  .edui-for-underline .edui-icon {\r\n    background-position: -140px 0;\r\n}\r\n\r\n.edui-default  .edui-for-strikethrough .edui-icon {\r\n    background-position: -120px 0;\r\n}\r\n\r\n.edui-default  .edui-for-subscript .edui-icon {\r\n    background-position: -600px 0;\r\n}\r\n\r\n.edui-default  .edui-for-superscript .edui-icon {\r\n    background-position: -620px 0;\r\n}\r\n\r\n.edui-default  .edui-for-blockquote .edui-icon {\r\n    background-position: -220px 0;\r\n}\r\n\r\n.edui-default  .edui-for-forecolor .edui-icon {\r\n    background-position: -720px 0;\r\n}\r\n\r\n.edui-default  .edui-for-backcolor .edui-icon {\r\n    background-position: -760px 0;\r\n}\r\n\r\n.edui-default  .edui-for-inserttable .edui-icon {\r\n    background-position: -580px -20px;\r\n}\r\n\r\n.edui-default  .edui-for-autotypeset .edui-icon {\r\n    background-position: -640px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-justifyleft .edui-icon {\r\n    background-position: -460px 0;\r\n}\r\n\r\n.edui-default  .edui-for-justifycenter .edui-icon {\r\n    background-position: -420px 0;\r\n}\r\n\r\n.edui-default  .edui-for-justifyright .edui-icon {\r\n    background-position: -480px 0;\r\n}\r\n\r\n.edui-default  .edui-for-justifyjustify .edui-icon {\r\n    background-position: -440px 0;\r\n}\r\n\r\n.edui-default  .edui-for-insertorderedlist .edui-icon {\r\n    background-position: -80px 0;\r\n}\r\n\r\n.edui-default  .edui-for-insertunorderedlist .edui-icon {\r\n    background-position: -20px 0;\r\n}\r\n\r\n.edui-default  .edui-for-lineheight .edui-icon {\r\n    background-position: -725px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-rowspacingbottom .edui-icon {\r\n    background-position: -745px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-rowspacingtop .edui-icon {\r\n    background-position: -765px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-horizontal .edui-icon {\r\n    background-position: -360px 0;\r\n}\r\n\r\n.edui-default  .edui-for-link .edui-icon {\r\n    background-position: -500px 0;\r\n}\r\n\r\n.edui-default  .edui-for-code .edui-icon {\r\n    background-position: -440px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-insertimage .edui-icon {\r\n    background-position: -726px -77px;\r\n}\r\n\r\n.edui-default  .edui-for-insertframe .edui-icon {\r\n    background-position: -240px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-emoticon .edui-icon {\r\n    background-position: -60px -20px;\r\n}\r\n\r\n.edui-default  .edui-for-spechars .edui-icon {\r\n    background-position: -240px 0;\r\n}\r\n\r\n.edui-default  .edui-for-help .edui-icon {\r\n    background-position: -340px 0;\r\n}\r\n\r\n.edui-default  .edui-for-print .edui-icon {\r\n    background-position: -440px -20px;\r\n}\r\n\r\n.edui-default  .edui-for-preview .edui-icon {\r\n    background-position: -420px -20px;\r\n}\r\n\r\n.edui-default  .edui-for-selectall .edui-icon {\r\n    background-position: -400px -20px;\r\n}\r\n\r\n.edui-default  .edui-for-searchreplace .edui-icon {\r\n    background-position: -520px -20px;\r\n}\r\n\r\n.edui-default  .edui-for-map .edui-icon {\r\n    background-position: -40px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-gmap .edui-icon {\r\n    background-position: -260px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-insertvideo .edui-icon {\r\n    background-position: -320px -20px;\r\n}\r\n\r\n.edui-default  .edui-for-time .edui-icon {\r\n    background-position: -160px -20px;\r\n}\r\n\r\n.edui-default  .edui-for-date .edui-icon {\r\n    background-position: -140px -20px;\r\n}\r\n\r\n.edui-default  .edui-for-cut .edui-icon {\r\n    background-position: -680px 0;\r\n}\r\n\r\n.edui-default  .edui-for-copy .edui-icon {\r\n    background-position: -700px 0;\r\n}\r\n\r\n.edui-default  .edui-for-paste .edui-icon {\r\n    background-position: -560px 0;\r\n}\r\n\r\n.edui-default  .edui-for-formatmatch .edui-icon {\r\n    background-position: -40px 0;\r\n}\r\n\r\n.edui-default  .edui-for-pasteplain .edui-icon {\r\n    background-position: -360px -20px;\r\n}\r\n\r\n.edui-default  .edui-for-directionalityltr .edui-icon {\r\n    background-position: -20px -20px;\r\n}\r\n\r\n.edui-default  .edui-for-directionalityrtl .edui-icon {\r\n    background-position: -40px -20px;\r\n}\r\n\r\n.edui-default  .edui-for-source .edui-icon {\r\n    background-position: -261px -0px;\r\n}\r\n\r\n.edui-default  .edui-for-removeformat .edui-icon {\r\n    background-position: -580px 0;\r\n}\r\n\r\n.edui-default  .edui-for-unlink .edui-icon {\r\n    background-position: -640px 0;\r\n}\r\n\r\n.edui-default  .edui-for-touppercase .edui-icon {\r\n    background-position: -786px 0;\r\n}\r\n\r\n.edui-default  .edui-for-tolowercase .edui-icon {\r\n    background-position: -806px 0;\r\n}\r\n\r\n.edui-default  .edui-for-insertrow .edui-icon {\r\n    background-position: -478px -76px;\r\n}\r\n\r\n.edui-default  .edui-for-insertrownext .edui-icon {\r\n    background-position: -498px -76px;\r\n}\r\n\r\n.edui-default  .edui-for-insertcol .edui-icon {\r\n    background-position: -455px -76px;\r\n}\r\n\r\n.edui-default  .edui-for-insertcolnext  .edui-icon {\r\n    background-position: -429px -76px;\r\n}\r\n\r\n.edui-default  .edui-for-mergeright .edui-icon {\r\n    background-position: -60px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-mergedown .edui-icon {\r\n    background-position: -80px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-splittorows .edui-icon {\r\n    background-position: -100px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-splittocols .edui-icon {\r\n    background-position: -120px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-insertparagraphbeforetable .edui-icon {\r\n    background-position: -140px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-deleterow .edui-icon {\r\n    background-position: -660px -20px;\r\n}\r\n\r\n.edui-default  .edui-for-deletecol .edui-icon {\r\n    background-position: -640px -20px;\r\n}\r\n\r\n.edui-default  .edui-for-splittocells .edui-icon {\r\n    background-position: -800px -20px;\r\n}\r\n\r\n.edui-default  .edui-for-mergecells .edui-icon {\r\n    background-position: -760px -20px;\r\n}\r\n\r\n.edui-default  .edui-for-deletetable .edui-icon {\r\n    background-position: -620px -20px;\r\n}\r\n\r\n.edui-default  .edui-for-cleardoc .edui-icon {\r\n    background-position: -520px 0;\r\n}\r\n\r\n.edui-default  .edui-for-fullscreen .edui-icon {\r\n    background-position: -100px -20px;\r\n}\r\n\r\n.edui-default  .edui-for-anchor .edui-icon {\r\n    background-position: -200px 0;\r\n}\r\n\r\n.edui-default  .edui-for-pagebreak .edui-icon {\r\n    background-position: -460px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-imagenone .edui-icon {\r\n    background-position: -480px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-imageleft .edui-icon {\r\n    background-position: -500px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-wordimage .edui-icon {\r\n    background-position: -660px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-imageright .edui-icon {\r\n    background-position: -520px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-imagecenter .edui-icon {\r\n    background-position: -540px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-indent .edui-icon {\r\n    background-position: -400px 0;\r\n}\r\n\r\n.edui-default  .edui-for-outdent .edui-icon {\r\n    background-position: -540px 0;\r\n}\r\n\r\n.edui-default  .edui-for-webapp .edui-icon {\r\n    background-position: -601px -40px\r\n}\r\n\r\n.edui-default  .edui-for-table .edui-icon {\r\n    background-position: -580px -20px;\r\n}\r\n\r\n.edui-default  .edui-for-edittable .edui-icon {\r\n    background-position: -420px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-template .edui-icon {\r\n    background-position: -339px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-delete .edui-icon {\r\n    background-position: -360px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-attachment .edui-icon {\r\n    background-position: -620px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-edittd .edui-icon {\r\n    background-position: -700px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-snapscreen .edui-icon {\r\n    background-position: -581px -40px\r\n}\r\n\r\n.edui-default  .edui-for-scrawl .edui-icon {\r\n    background-position: -801px -41px\r\n}\r\n\r\n.edui-default  .edui-for-background .edui-icon {\r\n    background-position: -680px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-music .edui-icon {\r\n    background-position: -18px -40px\r\n}\r\n\r\n.edui-default  .edui-for-formula .edui-icon {\r\n    background-position: -200px -40px\r\n}\r\n\r\n.edui-default  .edui-for-aligntd  .edui-icon {\r\n    background-position: -236px -76px;\r\n}\r\n\r\n.edui-default  .edui-for-insertparagraphtrue  .edui-icon {\r\n    background-position: -625px -76px;\r\n}\r\n\r\n.edui-default  .edui-for-insertparagraph  .edui-icon {\r\n    background-position: -602px -76px;\r\n}\r\n\r\n.edui-default  .edui-for-insertcaption  .edui-icon {\r\n    background-position: -336px -76px;\r\n}\r\n\r\n.edui-default  .edui-for-deletecaption  .edui-icon {\r\n    background-position: -362px -76px;\r\n}\r\n\r\n.edui-default  .edui-for-inserttitle  .edui-icon {\r\n    background-position: -286px -76px;\r\n}\r\n\r\n.edui-default  .edui-for-deletetitle  .edui-icon {\r\n    background-position: -311px -76px;\r\n}\r\n\r\n.edui-default  .edui-for-aligntable  .edui-icon {\r\n    background-position: -440px 0;\r\n}\r\n\r\n.edui-default  .edui-for-tablealignment-left  .edui-icon {\r\n    background-position: -460px 0;\r\n}\r\n\r\n.edui-default  .edui-for-tablealignment-center  .edui-icon {\r\n    background-position: -420px 0;\r\n}\r\n\r\n.edui-default  .edui-for-tablealignment-right  .edui-icon {\r\n    background-position: -480px 0;\r\n}\r\n\r\n.edui-default  .edui-for-drafts  .edui-icon {\r\n    background-position: -560px 0;\r\n}\r\n\r\n.edui-default  .edui-for-charts  .edui-icon {\r\n    background: url( ../images/charts.png ) no-repeat 2px 3px!important;\r\n}\r\n\r\n.edui-default  .edui-for-inserttitlecol  .edui-icon {\r\n    background-position: -673px -76px;\r\n}\r\n\r\n.edui-default  .edui-for-deletetitlecol  .edui-icon {\r\n    background-position: -698px -76px;\r\n}\r\n\r\n.edui-default  .edui-for-simpleupload  .edui-icon {\r\n    background-position: -380px 0px;\r\n}\r\n/*splitbutton*/\r\n.edui-default .edui-toolbar .edui-splitbutton-body .edui-arrow,\r\n.edui-default .edui-toolbar .edui-menubutton-body .edui-arrow {\r\n    background: url(../images/icons.png) -741px 0;\r\n    _background: url(../images/icons.gif) -741px 0;\r\n    height: 20px;\r\n    width: 9px;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-splitbutton .edui-splitbutton-body,\r\n.edui-default .edui-toolbar .edui-menubutton .edui-menubutton-body {\r\n    padding: 1px;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-splitborder {\r\n    width: 1px;\r\n    height: 20px;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-state-hover .edui-splitborder {\r\n    width: 1px;\r\n    border-left: 0px solid #dcac6c;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-state-active .edui-splitborder {\r\n    width: 0;\r\n    border-left: 1px solid gray;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-state-opened .edui-splitborder {\r\n    width: 1px;\r\n    border: 0;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-splitbutton .edui-state-hover .edui-splitbutton-body,\r\n.edui-default .edui-toolbar .edui-menubutton .edui-state-hover .edui-menubutton-body {\r\n    background-color: #fff5d4;\r\n    border: 1px solid #dcac6c;\r\n    padding: 0;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-splitbutton .edui-state-checked .edui-splitbutton-body,\r\n.edui-default .edui-toolbar .edui-menubutton .edui-state-checked .edui-menubutton-body {\r\n    background-color: #FFE69F;\r\n    border: 1px solid #DCAC6C;\r\n    padding: 0;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-splitbutton .edui-state-active .edui-splitbutton-body,\r\n.edui-default .edui-toolbar .edui-menubutton .edui-state-active .edui-menubutton-body {\r\n    background-color: #ffffff;\r\n    border: 1px solid gray;\r\n    padding: 0;\r\n}\r\n\r\n.edui-default .edui-state-disabled .edui-arrow {\r\n    opacity: 0.3;\r\n    _filter: alpha(opacity = 30);\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-splitbutton .edui-state-opened .edui-splitbutton-body,\r\n.edui-default .edui-toolbar .edui-menubutton .edui-state-opened .edui-menubutton-body {\r\n    background-color: white;\r\n    border: 1px solid gray;\r\n    padding: 0;\r\n}\r\n\r\n.edui-default .edui-for-insertorderedlist .edui-bordereraser,\r\n.edui-default .edui-for-lineheight .edui-bordereraser,\r\n.edui-default .edui-for-rowspacingtop .edui-bordereraser,\r\n.edui-default .edui-for-rowspacingbottom .edui-bordereraser,\r\n.edui-default .edui-for-insertunorderedlist .edui-bordereraser {\r\n    background-color: white;\r\n}\r\n\r\n/* 解决嵌套导致的图标问题 */\r\n.edui-default .edui-for-insertorderedlist .edui-popup-body .edui-icon,\r\n.edui-default .edui-for-lineheight .edui-popup-body .edui-icon,\r\n.edui-default .edui-for-rowspacingtop .edui-popup-body .edui-icon,\r\n.edui-default .edui-for-rowspacingbottom .edui-popup-body .edui-icon,\r\n.edui-default .edui-for-insertunorderedlist .edui-popup-body .edui-icon {\r\n    /*background-position: 0 -40px;*/\r\n    background-image: none  ;\r\n}\r\n\r\n/* 弹出菜单 */\r\n.edui-default .edui-popup {\r\n    z-index: 3000;\r\n    background-color: #ffffff;\r\n    width:auto;\r\n    height:auto;\r\n\r\n}\r\n\r\n.edui-default .edui-popup .edui-shadow {\r\n    left: 0;\r\n    top: 0;\r\n    width: 100%;\r\n    height: 100%;\r\n}\r\n\r\n.edui-default .edui-popup-content {\r\n    border:1px solid #ccc;\r\n    border: 1px solid rgba(0, 0, 0, 0.2);\r\n    *border-right-width: 2px;\r\n    *border-bottom-width: 2px;\r\n    -webkit-border-radius: 6px;\r\n    -moz-border-radius: 6px;\r\n    border-radius: 6px;\r\n    -webkit-box-shadow: 0 3px 4px rgba(0, 0, 0, 0.2);\r\n    -moz-box-shadow: 0 3px 4px rgba(0, 0, 0, 0.2);\r\n    box-shadow: 0 3px 4px rgba(0, 0, 0, 0.2);\r\n    -webkit-background-clip: padding-box;\r\n    -moz-background-clip: padding;\r\n    background-clip: padding-box;\r\n    padding: 5px;\r\n    background:#ffffff;\r\n}\r\n\r\n.edui-default .edui-popup .edui-bordereraser {\r\n    background-color: white;\r\n    height: 3px;\r\n}\r\n\r\n.edui-default .edui-menu .edui-bordereraser {\r\n    height: 3px;\r\n}\r\n\r\n.edui-default .edui-anchor-topleft .edui-bordereraser {\r\n    left: 1px;\r\n    top: -2px;\r\n}\r\n\r\n.edui-default .edui-anchor-topright .edui-bordereraser {\r\n    right: 1px;\r\n    top: -2px;\r\n}\r\n\r\n.edui-default .edui-anchor-bottomleft .edui-bordereraser {\r\n    left: 0;\r\n    bottom: -6px;\r\n    height: 7px;\r\n    border-left: 1px solid gray;\r\n    border-right: 1px solid gray;\r\n}\r\n\r\n.edui-default .edui-anchor-bottomright .edui-bordereraser {\r\n    right: 0;\r\n    bottom: -6px;\r\n    height: 7px;\r\n    border-left: 1px solid gray;\r\n    border-right: 1px solid gray;\r\n}\r\n\r\n.edui-popup div{\r\n    width:auto;\r\n    height:auto;\r\n}\r\n.edui-default .edui-editor-messageholder {\r\n    display: block;\r\n    width: 150px;\r\n    height: auto;\r\n    border: 0;\r\n    margin: 0;\r\n    padding: 0;\r\n    position: absolute;\r\n    top: 28px;\r\n    right: 3px;\r\n}\r\n\r\n.edui-default .edui-message{\r\n    min-height: 10px;\r\n    text-shadow: 0 1px 0 rgba(255,255,255,0.5);\r\n    padding: 0;\r\n    margin-bottom: 3px;\r\n    position: relative;\r\n}\r\n.edui-default .edui-message-body{\r\n    border-radius: 3px;\r\n    padding: 8px 15px 8px 8px;\r\n    color: #c09853;\r\n    background-color: #fcf8e3;\r\n    border: 1px solid #fbeed5;\r\n}\r\n.edui-default .edui-message-type-info{\r\n    color: #3a87ad;\r\n    background-color: #d9edf7;\r\n    border-color: #bce8f1\r\n}\r\n.edui-default .edui-message-type-success{\r\n    color: #468847;\r\n    background-color: #dff0d8;\r\n    border-color: #d6e9c6\r\n}\r\n.edui-default .edui-message-type-danger,\r\n.edui-default .edui-message-type-error{\r\n    color: #b94a48;\r\n    background-color: #f2dede;\r\n    border-color: #eed3d7\r\n}\r\n.edui-default .edui-message .edui-message-closer {\r\n    display: block;\r\n    width: 16px;\r\n    height: 16px;\r\n    line-height: 16px;\r\n    position: absolute;\r\n    top: 0;\r\n    right: 0;\r\n    padding: 0;\r\n    cursor: pointer;\r\n    background: transparent;\r\n    border: 0;\r\n    float: right;\r\n    font-size: 20px;\r\n    font-weight: bold;\r\n    color: #999;\r\n    text-shadow: 0 1px 0 #fff;\r\n    font-family: \"Helvetica Neue\",Helvetica,Arial,sans-serif;\r\n}\r\n.edui-default .edui-message .edui-message-content {\r\n    font-size: 10pt;\r\n    word-wrap: break-word;\r\n    word-break: normal;\r\n}\r\n/* 弹出对话框按钮和对话框大小 */\r\n.edui-default .edui-dialog {\r\n    z-index: 2000;\r\n    position: absolute;\r\n\r\n}\r\n\r\n.edui-dialog div{\r\n    width:auto;\r\n}\r\n\r\n.edui-default .edui-dialog-wrap {\r\n    margin-right: 6px;\r\n    margin-bottom: 6px;\r\n}\r\n\r\n.edui-default .edui-dialog-fullscreen-flag {\r\n    margin-right: 0;\r\n    margin-bottom: 0;\r\n}\r\n\r\n.edui-default .edui-dialog-body {\r\n    position: relative;\r\n    padding:2px 0 0 2px;\r\n    _zoom: 1;\r\n}\r\n\r\n.edui-default .edui-dialog-fullscreen-flag .edui-dialog-body {\r\n    padding: 0;\r\n}\r\n\r\n.edui-default .edui-dialog-shadow {\r\n    position: absolute;\r\n    z-index: -1;\r\n    left: 0;\r\n    top: 0;\r\n    width: 100%;\r\n    height: 100%;\r\n    background-color: #ffffff;\r\n    border: 1px solid #ccc;\r\n    border: 1px solid rgba(0, 0, 0, 0.2);\r\n    *border-right-width: 2px;\r\n    *border-bottom-width: 2px;\r\n    -webkit-border-radius: 6px;\r\n    -moz-border-radius: 6px;\r\n    border-radius: 6px;\r\n    -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\r\n    -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\r\n    box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\r\n    -webkit-background-clip: padding-box;\r\n    -moz-background-clip: padding;\r\n    background-clip: padding-box;\r\n}\r\n\r\n.edui-default .edui-dialog-foot {\r\n    background-color: white;\r\n}\r\n\r\n.edui-default .edui-dialog-titlebar {\r\n    height: 26px;\r\n    border-bottom: 1px solid #c6c6c6;\r\n    background: url(../images/dialog-title-bg.png) repeat-x bottom;\r\n    position: relative;\r\n    cursor: move;\r\n}\r\n.edui-default .edui-dialog-caption {\r\n    font-weight: bold;\r\n    font-size: 12px;\r\n    line-height: 26px;\r\n    padding-left: 5px;\r\n}\r\n\r\n.edui-default .edui-dialog-draghandle {\r\n    height: 26px;\r\n}\r\n\r\n.edui-default .edui-dialog-closebutton {\r\n    position: absolute !important;\r\n    right: 5px;\r\n    top: 3px;\r\n}\r\n\r\n.edui-default .edui-dialog-closebutton .edui-button-body {\r\n    height: 20px;\r\n    width: 20px;\r\n    cursor: pointer;\r\n    background: url(\"../images/icons-all.gif\") no-repeat 0 -59px;\r\n}\r\n\r\n.edui-default .edui-dialog-closebutton .edui-state-hover .edui-button-body {\r\n    background: url(\"../images/icons-all.gif\") no-repeat 0 -89px;\r\n}\r\n\r\n.edui-default .edui-dialog-foot {\r\n    height: 40px;\r\n}\r\n\r\n.edui-default .edui-dialog-buttons {\r\n    position: absolute;\r\n    right: 0;\r\n}\r\n\r\n.edui-default .edui-dialog-buttons .edui-button {\r\n    margin-right: 10px;\r\n}\r\n\r\n.edui-default .edui-dialog-buttons .edui-button .edui-button-body {\r\n    background: url(\"../images/icons-all.gif\") no-repeat;\r\n    height: 24px;\r\n    width: 96px;\r\n    font-size: 12px;\r\n    line-height: 24px;\r\n    text-align: center;\r\n    cursor: default;\r\n}\r\n\r\n.edui-default .edui-dialog-buttons .edui-button .edui-state-hover .edui-button-body {\r\n    background: url(\"../images/icons-all.gif\") no-repeat 0 -30px;\r\n}\r\n\r\n.edui-default .edui-dialog iframe {\r\n    border: 0;\r\n    padding: 0;\r\n    margin: 0;\r\n    vertical-align: top;\r\n}\r\n\r\n.edui-default .edui-dialog-modalmask {\r\n    opacity: 0.3;\r\n    filter: alpha(opacity = 30);\r\n    background-color: #ccc;\r\n    position: absolute;\r\n    /*z-index: 1999;*/\r\n}\r\n\r\n.edui-default .edui-dialog-dragmask {\r\n    position: absolute;\r\n    /*z-index: 2001;*/\r\n    background-color: transparent;\r\n    cursor: move;\r\n}\r\n\r\n.edui-default .edui-dialog-content {\r\n    position: relative;\r\n}\r\n\r\n.edui-default .dialogcontmask {\r\n    cursor: move;\r\n    visibility: hidden;\r\n    display: block;\r\n    position: absolute;\r\n    width: 100%;\r\n    height: 100%;\r\n    opacity: 0;\r\n    filter: alpha(opacity = 0);\r\n}\r\n\r\n/*link-dialog*/\r\n.edui-default .edui-for-link .edui-dialog-content {\r\n    width: 420px;\r\n    height: 200px;\r\n    overflow: hidden;\r\n}\r\n/*background-dialog*/\r\n.edui-default .edui-for-background .edui-dialog-content {\r\n    width: 440px;\r\n    height: 280px;\r\n    overflow: hidden;\r\n}\r\n\r\n/*template-dialog*/\r\n.edui-default .edui-for-template .edui-dialog-content {\r\n    width: 630px;\r\n    height: 390px;\r\n    overflow: hidden;\r\n}\r\n\r\n/*scrawl-dialog*/\r\n.edui-default .edui-for-scrawl .edui-dialog-content {\r\n    width: 515px;\r\n    *width: 506px;\r\n    height: 360px;\r\n}\r\n\r\n/*spechars-dialog*/\r\n.edui-default .edui-for-spechars .edui-dialog-content {\r\n    width: 620px;\r\n    height: 500px;\r\n    *width: 630px;\r\n    *height: 570px;\r\n}\r\n\r\n/*image-dialog*/\r\n.edui-default .edui-for-insertimage .edui-dialog-content {\r\n    width: 650px;\r\n    height: 400px;\r\n    overflow: hidden;\r\n}\r\n/*webapp-dialog*/\r\n.edui-default .edui-for-webapp .edui-dialog-content {\r\n    width: 560px;\r\n    _width: 565px;\r\n    height: 450px;\r\n    overflow: hidden;\r\n}\r\n\r\n/*image-insertframe*/\r\n.edui-default .edui-for-insertframe .edui-dialog-content {\r\n    width: 350px;\r\n    height: 200px;\r\n    overflow: hidden;\r\n}\r\n\r\n/*wordImage-dialog*/\r\n.edui-default .edui-for-wordimage .edui-dialog-content {\r\n    width: 620px;\r\n    height: 380px;\r\n    overflow: hidden;\r\n}\r\n\r\n/*attachment-dialog*/\r\n.edui-default .edui-for-attachment .edui-dialog-content {\r\n    width: 650px;\r\n    height: 400px;\r\n    overflow: hidden;\r\n}\r\n\r\n\r\n/*map-dialog*/\r\n.edui-default .edui-for-map .edui-dialog-content {\r\n    width: 550px;\r\n    height: 400px;\r\n}\r\n\r\n/*gmap-dialog*/\r\n.edui-default .edui-for-gmap .edui-dialog-content {\r\n    width: 550px;\r\n    height: 400px;\r\n}\r\n\r\n/*video-dialog*/\r\n.edui-default .edui-for-insertvideo .edui-dialog-content {\r\n    width: 590px;\r\n    height: 390px;\r\n}\r\n\r\n/*anchor-dialog*/\r\n.edui-default .edui-for-anchor .edui-dialog-content {\r\n    width: 320px;\r\n    height: 60px;\r\n    overflow: hidden;\r\n}\r\n\r\n/*searchreplace-dialog*/\r\n.edui-default .edui-for-searchreplace .edui-dialog-content {\r\n    width: 400px;\r\n    height: 220px;\r\n}\r\n\r\n/*help-dialog*/\r\n.edui-default .edui-for-help .edui-dialog-content {\r\n    width: 400px;\r\n    height: 420px;\r\n}\r\n\r\n/*edittable-dialog*/\r\n.edui-default .edui-for-edittable .edui-dialog-content {\r\n    width: 540px;\r\n    _width:590px;\r\n    height: 335px;\r\n}\r\n\r\n/*edittip-dialog*/\r\n.edui-default .edui-for-edittip .edui-dialog-content {\r\n    width: 225px;\r\n    height: 60px;\r\n}\r\n\r\n/*edittd-dialog*/\r\n.edui-default .edui-for-edittd .edui-dialog-content {\r\n    width: 240px;\r\n    height: 50px;\r\n}\r\n/*snapscreen-dialog*/\r\n.edui-default .edui-for-snapscreen .edui-dialog-content {\r\n    width: 400px;\r\n    height: 220px;\r\n}\r\n\r\n/*music-dialog*/\r\n.edui-default .edui-for-music .edui-dialog-content {\r\n    width: 515px;\r\n    height: 360px;\r\n}\r\n\r\n/*段落弹出菜单*/\r\n.edui-default .edui-for-paragraph .edui-listitem-label {\r\n    font-family: Tahoma, Verdana, Arial, Helvetica;\r\n}\r\n\r\n.edui-default .edui-for-paragraph .edui-listitem-label .edui-for-p {\r\n    font-size: 22px;\r\n    line-height: 27px;\r\n}\r\n\r\n.edui-default .edui-for-paragraph .edui-listitem-label .edui-for-h1 {\r\n    font-weight: bolder;\r\n    font-size: 32px;\r\n    line-height: 36px;\r\n}\r\n\r\n.edui-default .edui-for-paragraph .edui-listitem-label .edui-for-h2 {\r\n    font-weight: bolder;\r\n    font-size: 27px;\r\n    line-height: 29px;\r\n}\r\n\r\n.edui-default .edui-for-paragraph .edui-listitem-label .edui-for-h3 {\r\n    font-weight: bolder;\r\n    font-size: 19px;\r\n    line-height: 23px;\r\n}\r\n\r\n.edui-default .edui-for-paragraph .edui-listitem-label .edui-for-h4 {\r\n    font-weight: bolder;\r\n    font-size: 16px;\r\n    line-height: 19px\r\n}\r\n\r\n.edui-default .edui-for-paragraph .edui-listitem-label .edui-for-h5 {\r\n    font-weight: bolder;\r\n    font-size: 13px;\r\n    line-height: 16px;\r\n}\r\n\r\n.edui-default .edui-for-paragraph .edui-listitem-label .edui-for-h6 {\r\n    font-weight: bolder;\r\n    font-size: 12px;\r\n    line-height: 14px;\r\n}\r\n/* 表格弹出菜单 */\r\n.edui-default .edui-for-inserttable .edui-splitborder {\r\n    display: none\r\n}\r\n.edui-default .edui-for-inserttable  .edui-splitbutton-body .edui-arrow {\r\n    width: 0\r\n}\r\n.edui-default .edui-toolbar .edui-for-inserttable  .edui-state-active .edui-splitborder{\r\n    border-left: 1px solid transparent;\r\n}\r\n.edui-default .edui-tablepicker .edui-infoarea {\r\n    height: 14px;\r\n    line-height: 14px;\r\n    font-size: 12px;\r\n    width: 220px;\r\n    margin-bottom: 3px;\r\n    clear: both;\r\n}\r\n\r\n.edui-default .edui-tablepicker .edui-infoarea .edui-label {\r\n    float: left;\r\n}\r\n\r\n.edui-default .edui-dialog-buttons .edui-label {\r\n    line-height: 24px;\r\n}\r\n\r\n.edui-default .edui-tablepicker .edui-infoarea .edui-clickable {\r\n    float: right;\r\n}\r\n\r\n.edui-default .edui-tablepicker .edui-pickarea {\r\n    background: url(\"../images/unhighlighted.gif\") repeat;\r\n    height: 220px;\r\n    width: 220px;\r\n}\r\n\r\n.edui-default .edui-tablepicker .edui-pickarea .edui-overlay {\r\n    background: url(\"../images/highlighted.gif\") repeat;\r\n}\r\n\r\n/* 颜色弹出菜单 */\r\n.edui-default .edui-colorpicker-topbar {\r\n    height: 27px;\r\n    width: 200px;\r\n    /*border-bottom: 1px gray dashed;*/\r\n}\r\n\r\n.edui-default .edui-colorpicker-preview {\r\n    height: 20px;\r\n    border: 1px inset black;\r\n    margin-left: 1px;\r\n    width: 128px;\r\n    float: left;\r\n}\r\n\r\n.edui-default .edui-colorpicker-nocolor {\r\n    float: right;\r\n    margin-right: 1px;\r\n    font-size: 12px;\r\n    line-height: 14px;\r\n    height: 14px;\r\n    border: 1px solid #333;\r\n    padding: 3px 5px;\r\n    cursor: pointer;\r\n}\r\n\r\n.edui-default .edui-colorpicker-tablefirstrow {\r\n    height: 30px;\r\n}\r\n\r\n.edui-default .edui-colorpicker-colorcell {\r\n    width: 14px;\r\n    height: 14px;\r\n    display: block;\r\n    margin: 0;\r\n    cursor: pointer;\r\n}\r\n\r\n.edui-default .edui-colorpicker-colorcell:hover {\r\n    width: 14px;\r\n    height: 14px;\r\n    margin: 0;\r\n}\r\n.edui-default .edui-colorpicker-advbtn{\r\n    display: block;\r\n    text-align: center;\r\n    cursor: pointer;\r\n    height:20px;\r\n}\r\n.arrow_down{\r\n    background: white url('../images/arrow_down.png') no-repeat center;\r\n}\r\n.arrow_up{\r\n    background: white url('../images/arrow_up.png') no-repeat center;\r\n}\r\n/*高级的样式*/\r\n.edui-colorpicker-adv{\r\n    position: relative;\r\n    overflow: hidden;\r\n    height: 180px;\r\n    display: none;\r\n}\r\n.edui-colorpicker-plant, .edui-colorpicker-hue {\r\n    border: solid 1px #666;\r\n}\r\n.edui-colorpicker-pad {\r\n    width: 150px;\r\n    height: 150px;\r\n    left: 14px;\r\n    top: 13px;\r\n    position: absolute;\r\n    background: red;\r\n    overflow: hidden;\r\n    cursor: crosshair;\r\n}\r\n.edui-colorpicker-cover{\r\n    position: absolute;\r\n    top: 0;\r\n    left: 0;\r\n    width: 150px;\r\n    height: 150px;\r\n    background: url(\"../images/tangram-colorpicker.png\") -160px -200px;\r\n}\r\n.edui-colorpicker-padDot{\r\n    position: absolute;\r\n    top: 0;\r\n    left: 0;\r\n    width: 11px;\r\n    height: 11px;\r\n    overflow: hidden;\r\n    background: url(../images/tangram-colorpicker.png) 0px -200px repeat-x;\r\n    z-index: 1000;\r\n\r\n}\r\n.edui-colorpicker-sliderMain {\r\n    position: absolute;\r\n    left: 171px;\r\n    top: 13px;\r\n    width: 19px;\r\n    height: 152px;\r\n    background: url(../images/tangram-colorpicker.png) -179px -12px no-repeat;\r\n\r\n}\r\n.edui-colorpicker-slider {\r\n    width: 100%;\r\n    height: 100%;\r\n    cursor: pointer;\r\n}\r\n.edui-colorpicker-thumb{\r\n    position: absolute;\r\n    top: 0;\r\n    cursor: pointer;\r\n    height: 3px;\r\n    left: -1px;\r\n    right: -1px;\r\n    border: 1px solid black;\r\n    background: white;\r\n    opacity: .8;\r\n}\r\n/*自动排版弹出菜单*/\r\n.edui-default .edui-autotypesetpicker .edui-autotypesetpicker-body {\r\n    font-size: 12px;\r\n    margin-bottom: 3px;\r\n    clear: both;\r\n}\r\n\r\n.edui-default .edui-autotypesetpicker-body table {\r\n    border-collapse: separate;\r\n    border-spacing: 2px;\r\n}\r\n\r\n.edui-default .edui-autotypesetpicker-body td {\r\n    font-size: 12px;\r\n    word-wrap:break-word;\r\n}\r\n\r\n.edui-default .edui-autotypesetpicker-body td input {\r\n    margin: 3px 3px 3px 4px;\r\n    *margin: 1px 0 0 0;\r\n}\r\n/*自动排版弹出菜单*/\r\n.edui-default .edui-cellalignpicker .edui-cellalignpicker-body {\r\n    width: 70px;\r\n    font-size: 12px;\r\n    cursor: default;\r\n}\r\n\r\n.edui-default .edui-cellalignpicker-body table {\r\n    border-collapse: separate;\r\n    border-spacing: 0;\r\n}\r\n.edui-default .edui-cellalignpicker-body td{\r\n    padding: 1px;\r\n}\r\n.edui-default .edui-cellalignpicker-body .edui-icon{\r\n    height: 20px;\r\n    width: 20px;\r\n    padding: 1px;\r\n    background-image: url(../images/table-cell-align.png);\r\n}\r\n\r\n.edui-default .edui-cellalignpicker-body .edui-left{\r\n    background-position: 0 0;\r\n}\r\n\r\n.edui-default .edui-cellalignpicker-body .edui-center{\r\n    background-position: -25px 0;\r\n}\r\n.edui-default .edui-cellalignpicker-body .edui-right{\r\n    background-position: -51px 0;\r\n}\r\n\r\n.edui-default .edui-cellalignpicker-body td.edui-state-hover .edui-left{\r\n    background-position: -73px 0;\r\n}\r\n\r\n.edui-default .edui-cellalignpicker-body td.edui-state-hover .edui-center{\r\n    background-position: -98px 0;\r\n}\r\n\r\n.edui-default .edui-cellalignpicker-body td.edui-state-hover .edui-right{\r\n    background-position: -124px 0;\r\n}\r\n\r\n.edui-default .edui-cellalignpicker-body td.edui-cellalign-selected .edui-left {\r\n    background-position: -146px 0;\r\n    background-color: #f1f4f5;\r\n}\r\n\r\n.edui-default .edui-cellalignpicker-body td.edui-cellalign-selected .edui-center {\r\n    background-position: -245px 0;\r\n}\r\n\r\n.edui-default .edui-cellalignpicker-body td.edui-cellalign-selected .edui-right {\r\n    background-position: -271px 0;\r\n}\r\n/*分隔线*/\r\n.edui-default .edui-toolbar .edui-separator {\r\n    width: 2px;\r\n    height: 20px;\r\n    margin: 2px 4px 2px 3px;\r\n    background: url(../images/icons.png) -181px 0;\r\n    background: url(../images/icons.gif) -181px 0 \\9;\r\n}\r\n\r\n/*颜色按钮 */\r\n.edui-default .edui-toolbar .edui-colorbutton .edui-colorlump {\r\n    position: absolute;\r\n    overflow: hidden;\r\n    bottom: 1px;\r\n    left: 1px;\r\n    width: 18px;\r\n    height: 4px;\r\n}\r\n/*表情按钮及弹出菜单*/\r\n/*去除了表情的下拉箭头*/\r\n.edui-default .edui-for-emotion .edui-icon {\r\n    background-position: -60px -20px;\r\n}\r\n.edui-default .edui-for-emotion .edui-popup-content iframe\r\n{\r\n    width: 514px;\r\n    height: 380px;\r\n    overflow: hidden;\r\n}\r\n.edui-default .edui-for-emotion .edui-popup-content\r\n{\r\n    position: relative;\r\n    z-index: 555\r\n}\r\n\r\n.edui-default .edui-for-emotion .edui-splitborder {\r\n    display: none\r\n}\r\n\r\n.edui-default .edui-for-emotion .edui-splitbutton-body .edui-arrow\r\n{\r\n    width: 0\r\n}\r\n.edui-default .edui-toolbar .edui-for-emotion  .edui-state-active .edui-splitborder\r\n{\r\n    border-left: 1px solid transparent;\r\n}\r\n/*contextmenu*/\r\n.edui-default .edui-hassubmenu .edui-arrow {\r\n    height: 20px;\r\n    width: 20px;\r\n    float: right;\r\n    background: url(\"../images/icons-all.gif\") no-repeat 10px -233px;\r\n}\r\n\r\n.edui-default .edui-menu-body .edui-menuitem {\r\n    padding: 1px;\r\n}\r\n\r\n.edui-default .edui-menuseparator {\r\n    margin: 2px 0;\r\n    height: 1px;\r\n    overflow: hidden;\r\n}\r\n\r\n.edui-default .edui-menuseparator-inner {\r\n    border-bottom: 1px solid #e2e3e3;\r\n    margin-left: 29px;\r\n    margin-right: 1px;\r\n}\r\n\r\n.edui-default .edui-menu-body .edui-state-hover {\r\n    padding: 0 !important;\r\n    background-color: #fff5d4;\r\n    border: 1px solid #dcac6c;\r\n}\r\n/*弹出菜单*/\r\n.edui-default .edui-shortcutmenu {\r\n    padding: 2px;\r\n    width: 190px;\r\n    height: 50px;\r\n    background-color: #fff;\r\n    border: 1px solid #ccc;\r\n    border-radius: 5px;\r\n}\r\n\r\n/*粘贴弹出菜单*/\r\n.edui-default .edui-wordpastepop .edui-popup-content{\r\n    border: none;\r\n    padding: 0;\r\n    width: 54px;\r\n    height: 21px;\r\n}\r\n.edui-default  .edui-pasteicon {\r\n    width: 100%;\r\n    height: 100%;\r\n    background-image: url('../images/wordpaste.png');\r\n    background-position: 0 0;\r\n}\r\n\r\n.edui-default  .edui-pasteicon.edui-state-opened {\r\n    background-position: 0 -34px;\r\n}\r\n\r\n.edui-default  .edui-pastecontainer {\r\n    position: relative;\r\n    visibility: hidden;\r\n    width: 97px;\r\n    background: #fff;\r\n    border: 1px solid #ccc;\r\n}\r\n\r\n.edui-default  .edui-pastecontainer .edui-title {\r\n    font-weight: bold;\r\n    background: #F8F8FF;\r\n    height: 25px;\r\n    line-height: 25px;\r\n    font-size: 12px;\r\n    padding-left: 5px;\r\n}\r\n\r\n.edui-default  .edui-pastecontainer .edui-button {\r\n    overflow: hidden;\r\n    margin: 3px 0;\r\n}\r\n\r\n.edui-default  .edui-pastecontainer .edui-button .edui-richtxticon,\r\n.edui-default  .edui-pastecontainer .edui-button .edui-tagicon,\r\n.edui-default  .edui-pastecontainer .edui-button .edui-plaintxticon{\r\n    float: left;\r\n    cursor: pointer;\r\n    width: 29px;\r\n    height: 29px;\r\n    margin-left: 5px;\r\n    background-image: url('../images/wordpaste.png');\r\n    background-repeat: no-repeat;\r\n}\r\n.edui-default  .edui-pastecontainer .edui-button .edui-richtxticon {\r\n    margin-left: 0;\r\n    background-position: -109px 0;\r\n}\r\n.edui-default  .edui-pastecontainer .edui-button .edui-tagicon {\r\n    background-position: -148px 1px;\r\n}\r\n\r\n.edui-default  .edui-pastecontainer .edui-button .edui-plaintxticon {\r\n    background-position: -72px 0;\r\n}\r\n\r\n.edui-default  .edui-pastecontainer .edui-button .edui-state-hover .edui-richtxticon {\r\n    background-position: -109px -34px;\r\n}\r\n.edui-default  .edui-pastecontainer .edui-button .edui-state-hover .edui-tagicon{\r\n    background-position: -148px -34px;\r\n}\r\n.edui-default  .edui-pastecontainer .edui-button  .edui-state-hover .edui-plaintxticon{\r\n    background-position: -72px -34px;\r\n}"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/themes/default/dialogbase.css",
    "content": "/*弹出对话框页面样式组件\r\n*/\r\n\r\n/*reset\r\n*/\r\nhtml, body, div, span, applet, object, iframe,\r\nh1, h2, h3, h4, h5, h6, p, blockquote, pre,\r\na, abbr, acronym, address, big, cite, code,\r\ndel, dfn, em, font, img, ins, kbd, q, s, samp,\r\nsmall, strike, strong, sub, sup, tt, var,\r\nb, u, i, center,\r\ndl, dt, dd, ol, ul, li,\r\nfieldset, form, label, legend,\r\ntable, caption, tbody, tfoot, thead, tr, th, td {\r\n    margin: 0;\r\n    padding: 0;\r\n    outline: 0;\r\n    font-size: 100%;\r\n}\r\n\r\nbody {\r\n    line-height: 1;\r\n}\r\n\r\nol, ul {\r\n    list-style: none;\r\n}\r\n\r\nblockquote, q {\r\n    quotes: none;\r\n}\r\n\r\nins {\r\n    text-decoration: none;\r\n}\r\n\r\ndel {\r\n    text-decoration: line-through;\r\n}\r\n\r\ntable {\r\n    border-collapse: collapse;\r\n    border-spacing: 0;\r\n}\r\n\r\n/*module\r\n*/\r\nbody {\r\n    background-color: #fff;\r\n    font: 12px/1.5 sans-serif, \"宋体\", \"Arial Narrow\", HELVETICA;\r\n    color: #646464;\r\n}\r\n\r\n/*tab*/\r\n.tabhead {\r\n    position: relative;\r\n    z-index: 10;\r\n}\r\n\r\n.tabhead span {\r\n    display: inline-block;\r\n    padding: 0 5px;\r\n    height: 30px;\r\n    border: 1px solid #ccc;\r\n    background: url(\"images/dialog-title-bg.png\") repeat-x;\r\n    text-align: center;\r\n    line-height: 30px;\r\n    cursor: pointer;\r\n    *margin-right: 5px;\r\n}\r\n\r\n.tabhead span.focus {\r\n    height: 31px;\r\n    border-bottom: none;\r\n    background: #fff;\r\n}\r\n\r\n.tabbody {\r\n    position: relative;\r\n    top: -1px;\r\n    margin: 0 auto;\r\n    border: 1px solid #ccc;\r\n}\r\n\r\n/*button*/\r\na.button {\r\n    display: block;\r\n    text-align: center;\r\n    line-height: 24px;\r\n    text-decoration: none;\r\n    height: 24px;\r\n    width: 95px;\r\n    border: 0;\r\n    color: #838383;\r\n    background: url(../../themes/default/images/icons-all.gif) no-repeat;\r\n}\r\n\r\na.button:hover {\r\n    background-position: 0 -30px;\r\n}"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/themes/iframe.css",
    "content": "/*可以在这里添加你自己的css*/\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/SyntaxHighlighter/shCore.js",
    "content": "// XRegExp 1.5.1\r\n// (c) 2007-2012 Steven Levithan\r\n// MIT License\r\n// <http://xregexp.com>\r\n// Provides an augmented, extensible, cross-browser implementation of regular expressions,\r\n// including support for additional syntax, flags, and methods\r\n\r\nvar XRegExp;\r\n\r\nif (XRegExp) {\r\n    // Avoid running twice, since that would break references to native globals\r\n    throw Error(\"can't load XRegExp twice in the same frame\");\r\n}\r\n\r\n// Run within an anonymous function to protect variables and avoid new globals\r\n(function (undefined) {\r\n\r\n    //---------------------------------\r\n    //  Constructor\r\n    //---------------------------------\r\n\r\n    // Accepts a pattern and flags; returns a new, extended `RegExp` object. Differs from a native\r\n    // regular expression in that additional syntax and flags are supported and cross-browser\r\n    // syntax inconsistencies are ameliorated. `XRegExp(/regex/)` clones an existing regex and\r\n    // converts to type XRegExp\r\n    XRegExp = function (pattern, flags) {\r\n        var output = [],\r\n            currScope = XRegExp.OUTSIDE_CLASS,\r\n            pos = 0,\r\n            context, tokenResult, match, chr, regex;\r\n\r\n        if (XRegExp.isRegExp(pattern)) {\r\n            if (flags !== undefined)\r\n                throw TypeError(\"can't supply flags when constructing one RegExp from another\");\r\n            return clone(pattern);\r\n        }\r\n        // Tokens become part of the regex construction process, so protect against infinite\r\n        // recursion when an XRegExp is constructed within a token handler or trigger\r\n        if (isInsideConstructor)\r\n            throw Error(\"can't call the XRegExp constructor within token definition functions\");\r\n\r\n        flags = flags || \"\";\r\n        context = { // `this` object for custom tokens\r\n            hasNamedCapture: false,\r\n            captureNames: [],\r\n            hasFlag: function (flag) {return flags.indexOf(flag) > -1;},\r\n            setFlag: function (flag) {flags += flag;}\r\n        };\r\n\r\n        while (pos < pattern.length) {\r\n            // Check for custom tokens at the current position\r\n            tokenResult = runTokens(pattern, pos, currScope, context);\r\n\r\n            if (tokenResult) {\r\n                output.push(tokenResult.output);\r\n                pos += (tokenResult.match[0].length || 1);\r\n            } else {\r\n                // Check for native multicharacter metasequences (excluding character classes) at\r\n                // the current position\r\n                if (match = nativ.exec.call(nativeTokens[currScope], pattern.slice(pos))) {\r\n                    output.push(match[0]);\r\n                    pos += match[0].length;\r\n                } else {\r\n                    chr = pattern.charAt(pos);\r\n                    if (chr === \"[\")\r\n                        currScope = XRegExp.INSIDE_CLASS;\r\n                    else if (chr === \"]\")\r\n                        currScope = XRegExp.OUTSIDE_CLASS;\r\n                    // Advance position one character\r\n                    output.push(chr);\r\n                    pos++;\r\n                }\r\n            }\r\n        }\r\n\r\n        regex = RegExp(output.join(\"\"), nativ.replace.call(flags, flagClip, \"\"));\r\n        regex._xregexp = {\r\n            source: pattern,\r\n            captureNames: context.hasNamedCapture ? context.captureNames : null\r\n        };\r\n        return regex;\r\n    };\r\n\r\n\r\n    //---------------------------------\r\n    //  Public properties\r\n    //---------------------------------\r\n\r\n    XRegExp.version = \"1.5.1\";\r\n\r\n    // Token scope bitflags\r\n    XRegExp.INSIDE_CLASS = 1;\r\n    XRegExp.OUTSIDE_CLASS = 2;\r\n\r\n\r\n    //---------------------------------\r\n    //  Private variables\r\n    //---------------------------------\r\n\r\n    var replacementToken = /\\$(?:(\\d\\d?|[$&`'])|{([$\\w]+)})/g,\r\n        flagClip = /[^gimy]+|([\\s\\S])(?=[\\s\\S]*\\1)/g, // Nonnative and duplicate flags\r\n        quantifier = /^(?:[?*+]|{\\d+(?:,\\d*)?})\\??/,\r\n        isInsideConstructor = false,\r\n        tokens = [],\r\n    // Copy native globals for reference (\"native\" is an ES3 reserved keyword)\r\n        nativ = {\r\n            exec: RegExp.prototype.exec,\r\n            test: RegExp.prototype.test,\r\n            match: String.prototype.match,\r\n            replace: String.prototype.replace,\r\n            split: String.prototype.split\r\n        },\r\n        compliantExecNpcg = nativ.exec.call(/()??/, \"\")[1] === undefined, // check `exec` handling of nonparticipating capturing groups\r\n        compliantLastIndexIncrement = function () {\r\n            var x = /^/g;\r\n            nativ.test.call(x, \"\");\r\n            return !x.lastIndex;\r\n        }(),\r\n        hasNativeY = RegExp.prototype.sticky !== undefined,\r\n        nativeTokens = {};\r\n\r\n    // `nativeTokens` match native multicharacter metasequences only (including deprecated octals,\r\n    // excluding character classes)\r\n    nativeTokens[XRegExp.INSIDE_CLASS] = /^(?:\\\\(?:[0-3][0-7]{0,2}|[4-7][0-7]?|x[\\dA-Fa-f]{2}|u[\\dA-Fa-f]{4}|c[A-Za-z]|[\\s\\S]))/;\r\n    nativeTokens[XRegExp.OUTSIDE_CLASS] = /^(?:\\\\(?:0(?:[0-3][0-7]{0,2}|[4-7][0-7]?)?|[1-9]\\d*|x[\\dA-Fa-f]{2}|u[\\dA-Fa-f]{4}|c[A-Za-z]|[\\s\\S])|\\(\\?[:=!]|[?*+]\\?|{\\d+(?:,\\d*)?}\\??)/;\r\n\r\n\r\n    //---------------------------------\r\n    //  Public methods\r\n    //---------------------------------\r\n\r\n    // Lets you extend or change XRegExp syntax and create custom flags. This is used internally by\r\n    // the XRegExp library and can be used to create XRegExp plugins. This function is intended for\r\n    // users with advanced knowledge of JavaScript's regular expression syntax and behavior. It can\r\n    // be disabled by `XRegExp.freezeTokens`\r\n    XRegExp.addToken = function (regex, handler, scope, trigger) {\r\n        tokens.push({\r\n            pattern: clone(regex, \"g\" + (hasNativeY ? \"y\" : \"\")),\r\n            handler: handler,\r\n            scope: scope || XRegExp.OUTSIDE_CLASS,\r\n            trigger: trigger || null\r\n        });\r\n    };\r\n\r\n    // Accepts a pattern and flags; returns an extended `RegExp` object. If the pattern and flag\r\n    // combination has previously been cached, the cached copy is returned; otherwise the newly\r\n    // created regex is cached\r\n    XRegExp.cache = function (pattern, flags) {\r\n        var key = pattern + \"/\" + (flags || \"\");\r\n        return XRegExp.cache[key] || (XRegExp.cache[key] = XRegExp(pattern, flags));\r\n    };\r\n\r\n    // Accepts a `RegExp` instance; returns a copy with the `/g` flag set. The copy has a fresh\r\n    // `lastIndex` (set to zero). If you want to copy a regex without forcing the `global`\r\n    // property, use `XRegExp(regex)`. Do not use `RegExp(regex)` because it will not preserve\r\n    // special properties required for named capture\r\n    XRegExp.copyAsGlobal = function (regex) {\r\n        return clone(regex, \"g\");\r\n    };\r\n\r\n    // Accepts a string; returns the string with regex metacharacters escaped. The returned string\r\n    // can safely be used at any point within a regex to match the provided literal string. Escaped\r\n    // characters are [ ] { } ( ) * + ? - . , \\ ^ $ | # and whitespace\r\n    XRegExp.escape = function (str) {\r\n        return str.replace(/[-[\\]{}()*+?.,\\\\^$|#\\s]/g, \"\\\\$&\");\r\n    };\r\n\r\n    // Accepts a string to search, regex to search with, position to start the search within the\r\n    // string (default: 0), and an optional Boolean indicating whether matches must start at-or-\r\n    // after the position or at the specified position only. This function ignores the `lastIndex`\r\n    // of the provided regex in its own handling, but updates the property for compatibility\r\n    XRegExp.execAt = function (str, regex, pos, anchored) {\r\n        var r2 = clone(regex, \"g\" + ((anchored && hasNativeY) ? \"y\" : \"\")),\r\n            match;\r\n        r2.lastIndex = pos = pos || 0;\r\n        match = r2.exec(str); // Run the altered `exec` (required for `lastIndex` fix, etc.)\r\n        if (anchored && match && match.index !== pos)\r\n            match = null;\r\n        if (regex.global)\r\n            regex.lastIndex = match ? r2.lastIndex : 0;\r\n        return match;\r\n    };\r\n\r\n    // Breaks the unrestorable link to XRegExp's private list of tokens, thereby preventing\r\n    // syntax and flag changes. Should be run after XRegExp and any plugins are loaded\r\n    XRegExp.freezeTokens = function () {\r\n        XRegExp.addToken = function () {\r\n            throw Error(\"can't run addToken after freezeTokens\");\r\n        };\r\n    };\r\n\r\n    // Accepts any value; returns a Boolean indicating whether the argument is a `RegExp` object.\r\n    // Note that this is also `true` for regex literals and regexes created by the `XRegExp`\r\n    // constructor. This works correctly for variables created in another frame, when `instanceof`\r\n    // and `constructor` checks would fail to work as intended\r\n    XRegExp.isRegExp = function (o) {\r\n        return Object.prototype.toString.call(o) === \"[object RegExp]\";\r\n    };\r\n\r\n    // Executes `callback` once per match within `str`. Provides a simpler and cleaner way to\r\n    // iterate over regex matches compared to the traditional approaches of subverting\r\n    // `String.prototype.replace` or repeatedly calling `exec` within a `while` loop\r\n    XRegExp.iterate = function (str, regex, callback, context) {\r\n        var r2 = clone(regex, \"g\"),\r\n            i = -1, match;\r\n        while (match = r2.exec(str)) { // Run the altered `exec` (required for `lastIndex` fix, etc.)\r\n            if (regex.global)\r\n                regex.lastIndex = r2.lastIndex; // Doing this to follow expectations if `lastIndex` is checked within `callback`\r\n            callback.call(context, match, ++i, str, regex);\r\n            if (r2.lastIndex === match.index)\r\n                r2.lastIndex++;\r\n        }\r\n        if (regex.global)\r\n            regex.lastIndex = 0;\r\n    };\r\n\r\n    // Accepts a string and an array of regexes; returns the result of using each successive regex\r\n    // to search within the matches of the previous regex. The array of regexes can also contain\r\n    // objects with `regex` and `backref` properties, in which case the named or numbered back-\r\n    // references specified are passed forward to the next regex or returned. E.g.:\r\n    // var xregexpImgFileNames = XRegExp.matchChain(html, [\r\n    //     {regex: /<img\\b([^>]+)>/i, backref: 1}, // <img> tag attributes\r\n    //     {regex: XRegExp('(?ix) \\\\s src=\" (?<src> [^\"]+ )'), backref: \"src\"}, // src attribute values\r\n    //     {regex: XRegExp(\"^http://xregexp\\\\.com(/[^#?]+)\", \"i\"), backref: 1}, // xregexp.com paths\r\n    //     /[^\\/]+$/ // filenames (strip directory paths)\r\n    // ]);\r\n    XRegExp.matchChain = function (str, chain) {\r\n        return function recurseChain (values, level) {\r\n            var item = chain[level].regex ? chain[level] : {regex: chain[level]},\r\n                regex = clone(item.regex, \"g\"),\r\n                matches = [], i;\r\n            for (i = 0; i < values.length; i++) {\r\n                XRegExp.iterate(values[i], regex, function (match) {\r\n                    matches.push(item.backref ? (match[item.backref] || \"\") : match[0]);\r\n                });\r\n            }\r\n            return ((level === chain.length - 1) || !matches.length) ?\r\n                matches : recurseChain(matches, level + 1);\r\n        }([str], 0);\r\n    };\r\n\r\n\r\n    //---------------------------------\r\n    //  New RegExp prototype methods\r\n    //---------------------------------\r\n\r\n    // Accepts a context object and arguments array; returns the result of calling `exec` with the\r\n    // first value in the arguments array. the context is ignored but is accepted for congruity\r\n    // with `Function.prototype.apply`\r\n    RegExp.prototype.apply = function (context, args) {\r\n        return this.exec(args[0]);\r\n    };\r\n\r\n    // Accepts a context object and string; returns the result of calling `exec` with the provided\r\n    // string. the context is ignored but is accepted for congruity with `Function.prototype.call`\r\n    RegExp.prototype.call = function (context, str) {\r\n        return this.exec(str);\r\n    };\r\n\r\n\r\n    //---------------------------------\r\n    //  Overriden native methods\r\n    //---------------------------------\r\n\r\n    // Adds named capture support (with backreferences returned as `result.name`), and fixes two\r\n    // cross-browser issues per ES3:\r\n    // - Captured values for nonparticipating capturing groups should be returned as `undefined`,\r\n    //   rather than the empty string.\r\n    // - `lastIndex` should not be incremented after zero-length matches.\r\n    RegExp.prototype.exec = function (str) {\r\n        var match, name, r2, origLastIndex;\r\n        if (!this.global)\r\n            origLastIndex = this.lastIndex;\r\n        match = nativ.exec.apply(this, arguments);\r\n        if (match) {\r\n            // Fix browsers whose `exec` methods don't consistently return `undefined` for\r\n            // nonparticipating capturing groups\r\n            if (!compliantExecNpcg && match.length > 1 && indexOf(match, \"\") > -1) {\r\n                r2 = RegExp(this.source, nativ.replace.call(getNativeFlags(this), \"g\", \"\"));\r\n                // Using `str.slice(match.index)` rather than `match[0]` in case lookahead allowed\r\n                // matching due to characters outside the match\r\n                nativ.replace.call((str + \"\").slice(match.index), r2, function () {\r\n                    for (var i = 1; i < arguments.length - 2; i++) {\r\n                        if (arguments[i] === undefined)\r\n                            match[i] = undefined;\r\n                    }\r\n                });\r\n            }\r\n            // Attach named capture properties\r\n            if (this._xregexp && this._xregexp.captureNames) {\r\n                for (var i = 1; i < match.length; i++) {\r\n                    name = this._xregexp.captureNames[i - 1];\r\n                    if (name)\r\n                        match[name] = match[i];\r\n                }\r\n            }\r\n            // Fix browsers that increment `lastIndex` after zero-length matches\r\n            if (!compliantLastIndexIncrement && this.global && !match[0].length && (this.lastIndex > match.index))\r\n                this.lastIndex--;\r\n        }\r\n        if (!this.global)\r\n            this.lastIndex = origLastIndex; // Fix IE, Opera bug (last tested IE 9.0.5, Opera 11.61 on Windows)\r\n        return match;\r\n    };\r\n\r\n    // Fix browser bugs in native method\r\n    RegExp.prototype.test = function (str) {\r\n        // Use the native `exec` to skip some processing overhead, even though the altered\r\n        // `exec` would take care of the `lastIndex` fixes\r\n        var match, origLastIndex;\r\n        if (!this.global)\r\n            origLastIndex = this.lastIndex;\r\n        match = nativ.exec.call(this, str);\r\n        // Fix browsers that increment `lastIndex` after zero-length matches\r\n        if (match && !compliantLastIndexIncrement && this.global && !match[0].length && (this.lastIndex > match.index))\r\n            this.lastIndex--;\r\n        if (!this.global)\r\n            this.lastIndex = origLastIndex; // Fix IE, Opera bug (last tested IE 9.0.5, Opera 11.61 on Windows)\r\n        return !!match;\r\n    };\r\n\r\n    // Adds named capture support and fixes browser bugs in native method\r\n    String.prototype.match = function (regex) {\r\n        if (!XRegExp.isRegExp(regex))\r\n            regex = RegExp(regex); // Native `RegExp`\r\n        if (regex.global) {\r\n            var result = nativ.match.apply(this, arguments);\r\n            regex.lastIndex = 0; // Fix IE bug\r\n            return result;\r\n        }\r\n        return regex.exec(this); // Run the altered `exec`\r\n    };\r\n\r\n    // Adds support for `${n}` tokens for named and numbered backreferences in replacement text,\r\n    // and provides named backreferences to replacement functions as `arguments[0].name`. Also\r\n    // fixes cross-browser differences in replacement text syntax when performing a replacement\r\n    // using a nonregex search value, and the value of replacement regexes' `lastIndex` property\r\n    // during replacement iterations. Note that this doesn't support SpiderMonkey's proprietary\r\n    // third (`flags`) parameter\r\n    String.prototype.replace = function (search, replacement) {\r\n        var isRegex = XRegExp.isRegExp(search),\r\n            captureNames, result, str, origLastIndex;\r\n\r\n        // There are too many combinations of search/replacement types/values and browser bugs that\r\n        // preclude passing to native `replace`, so don't try\r\n        //if (...)\r\n        //    return nativ.replace.apply(this, arguments);\r\n\r\n        if (isRegex) {\r\n            if (search._xregexp)\r\n                captureNames = search._xregexp.captureNames; // Array or `null`\r\n            if (!search.global)\r\n                origLastIndex = search.lastIndex;\r\n        } else {\r\n            search = search + \"\"; // Type conversion\r\n        }\r\n\r\n        if (Object.prototype.toString.call(replacement) === \"[object Function]\") {\r\n            result = nativ.replace.call(this + \"\", search, function () {\r\n                if (captureNames) {\r\n                    // Change the `arguments[0]` string primitive to a String object which can store properties\r\n                    arguments[0] = new String(arguments[0]);\r\n                    // Store named backreferences on `arguments[0]`\r\n                    for (var i = 0; i < captureNames.length; i++) {\r\n                        if (captureNames[i])\r\n                            arguments[0][captureNames[i]] = arguments[i + 1];\r\n                    }\r\n                }\r\n                // Update `lastIndex` before calling `replacement` (fix browsers)\r\n                if (isRegex && search.global)\r\n                    search.lastIndex = arguments[arguments.length - 2] + arguments[0].length;\r\n                return replacement.apply(null, arguments);\r\n            });\r\n        } else {\r\n            str = this + \"\"; // Type conversion, so `args[args.length - 1]` will be a string (given nonstring `this`)\r\n            result = nativ.replace.call(str, search, function () {\r\n                var args = arguments; // Keep this function's `arguments` available through closure\r\n                return nativ.replace.call(replacement + \"\", replacementToken, function ($0, $1, $2) {\r\n                    // Numbered backreference (without delimiters) or special variable\r\n                    if ($1) {\r\n                        switch ($1) {\r\n                            case \"$\": return \"$\";\r\n                            case \"&\": return args[0];\r\n                            case \"`\": return args[args.length - 1].slice(0, args[args.length - 2]);\r\n                            case \"'\": return args[args.length - 1].slice(args[args.length - 2] + args[0].length);\r\n                            // Numbered backreference\r\n                            default:\r\n                                // What does \"$10\" mean?\r\n                                // - Backreference 10, if 10 or more capturing groups exist\r\n                                // - Backreference 1 followed by \"0\", if 1-9 capturing groups exist\r\n                                // - Otherwise, it's the string \"$10\"\r\n                                // Also note:\r\n                                // - Backreferences cannot be more than two digits (enforced by `replacementToken`)\r\n                                // - \"$01\" is equivalent to \"$1\" if a capturing group exists, otherwise it's the string \"$01\"\r\n                                // - There is no \"$0\" token (\"$&\" is the entire match)\r\n                                var literalNumbers = \"\";\r\n                                $1 = +$1; // Type conversion; drop leading zero\r\n                                if (!$1) // `$1` was \"0\" or \"00\"\r\n                                    return $0;\r\n                                while ($1 > args.length - 3) {\r\n                                    literalNumbers = String.prototype.slice.call($1, -1) + literalNumbers;\r\n                                    $1 = Math.floor($1 / 10); // Drop the last digit\r\n                                }\r\n                                return ($1 ? args[$1] || \"\" : \"$\") + literalNumbers;\r\n                        }\r\n                        // Named backreference or delimited numbered backreference\r\n                    } else {\r\n                        // What does \"${n}\" mean?\r\n                        // - Backreference to numbered capture n. Two differences from \"$n\":\r\n                        //   - n can be more than two digits\r\n                        //   - Backreference 0 is allowed, and is the entire match\r\n                        // - Backreference to named capture n, if it exists and is not a number overridden by numbered capture\r\n                        // - Otherwise, it's the string \"${n}\"\r\n                        var n = +$2; // Type conversion; drop leading zeros\r\n                        if (n <= args.length - 3)\r\n                            return args[n];\r\n                        n = captureNames ? indexOf(captureNames, $2) : -1;\r\n                        return n > -1 ? args[n + 1] : $0;\r\n                    }\r\n                });\r\n            });\r\n        }\r\n\r\n        if (isRegex) {\r\n            if (search.global)\r\n                search.lastIndex = 0; // Fix IE, Safari bug (last tested IE 9.0.5, Safari 5.1.2 on Windows)\r\n            else\r\n                search.lastIndex = origLastIndex; // Fix IE, Opera bug (last tested IE 9.0.5, Opera 11.61 on Windows)\r\n        }\r\n\r\n        return result;\r\n    };\r\n\r\n    // A consistent cross-browser, ES3 compliant `split`\r\n    String.prototype.split = function (s /* separator */, limit) {\r\n        // If separator `s` is not a regex, use the native `split`\r\n        if (!XRegExp.isRegExp(s))\r\n            return nativ.split.apply(this, arguments);\r\n\r\n        var str = this + \"\", // Type conversion\r\n            output = [],\r\n            lastLastIndex = 0,\r\n            match, lastLength;\r\n\r\n        // Behavior for `limit`: if it's...\r\n        // - `undefined`: No limit\r\n        // - `NaN` or zero: Return an empty array\r\n        // - A positive number: Use `Math.floor(limit)`\r\n        // - A negative number: No limit\r\n        // - Other: Type-convert, then use the above rules\r\n        if (limit === undefined || +limit < 0) {\r\n            limit = Infinity;\r\n        } else {\r\n            limit = Math.floor(+limit);\r\n            if (!limit)\r\n                return [];\r\n        }\r\n\r\n        // This is required if not `s.global`, and it avoids needing to set `s.lastIndex` to zero\r\n        // and restore it to its original value when we're done using the regex\r\n        s = XRegExp.copyAsGlobal(s);\r\n\r\n        while (match = s.exec(str)) { // Run the altered `exec` (required for `lastIndex` fix, etc.)\r\n            if (s.lastIndex > lastLastIndex) {\r\n                output.push(str.slice(lastLastIndex, match.index));\r\n\r\n                if (match.length > 1 && match.index < str.length)\r\n                    Array.prototype.push.apply(output, match.slice(1));\r\n\r\n                lastLength = match[0].length;\r\n                lastLastIndex = s.lastIndex;\r\n\r\n                if (output.length >= limit)\r\n                    break;\r\n            }\r\n\r\n            if (s.lastIndex === match.index)\r\n                s.lastIndex++;\r\n        }\r\n\r\n        if (lastLastIndex === str.length) {\r\n            if (!nativ.test.call(s, \"\") || lastLength)\r\n                output.push(\"\");\r\n        } else {\r\n            output.push(str.slice(lastLastIndex));\r\n        }\r\n\r\n        return output.length > limit ? output.slice(0, limit) : output;\r\n    };\r\n\r\n\r\n    //---------------------------------\r\n    //  Private helper functions\r\n    //---------------------------------\r\n\r\n    // Supporting function for `XRegExp`, `XRegExp.copyAsGlobal`, etc. Returns a copy of a `RegExp`\r\n    // instance with a fresh `lastIndex` (set to zero), preserving properties required for named\r\n    // capture. Also allows adding new flags in the process of copying the regex\r\n    function clone (regex, additionalFlags) {\r\n        if (!XRegExp.isRegExp(regex))\r\n            throw TypeError(\"type RegExp expected\");\r\n        var x = regex._xregexp;\r\n        regex = XRegExp(regex.source, getNativeFlags(regex) + (additionalFlags || \"\"));\r\n        if (x) {\r\n            regex._xregexp = {\r\n                source: x.source,\r\n                captureNames: x.captureNames ? x.captureNames.slice(0) : null\r\n            };\r\n        }\r\n        return regex;\r\n    }\r\n\r\n    function getNativeFlags (regex) {\r\n        return (regex.global     ? \"g\" : \"\") +\r\n            (regex.ignoreCase ? \"i\" : \"\") +\r\n            (regex.multiline  ? \"m\" : \"\") +\r\n            (regex.extended   ? \"x\" : \"\") + // Proposed for ES4; included in AS3\r\n            (regex.sticky     ? \"y\" : \"\");\r\n    }\r\n\r\n    function runTokens (pattern, index, scope, context) {\r\n        var i = tokens.length,\r\n            result, match, t;\r\n        // Protect against constructing XRegExps within token handler and trigger functions\r\n        isInsideConstructor = true;\r\n        // Must reset `isInsideConstructor`, even if a `trigger` or `handler` throws\r\n        try {\r\n            while (i--) { // Run in reverse order\r\n                t = tokens[i];\r\n                if ((scope & t.scope) && (!t.trigger || t.trigger.call(context))) {\r\n                    t.pattern.lastIndex = index;\r\n                    match = t.pattern.exec(pattern); // Running the altered `exec` here allows use of named backreferences, etc.\r\n                    if (match && match.index === index) {\r\n                        result = {\r\n                            output: t.handler.call(context, match, scope),\r\n                            match: match\r\n                        };\r\n                        break;\r\n                    }\r\n                }\r\n            }\r\n        } catch (err) {\r\n            throw err;\r\n        } finally {\r\n            isInsideConstructor = false;\r\n        }\r\n        return result;\r\n    }\r\n\r\n    function indexOf (array, item, from) {\r\n        if (Array.prototype.indexOf) // Use the native array method if available\r\n            return array.indexOf(item, from);\r\n        for (var i = from || 0; i < array.length; i++) {\r\n            if (array[i] === item)\r\n                return i;\r\n        }\r\n        return -1;\r\n    }\r\n\r\n\r\n    //---------------------------------\r\n    //  Built-in tokens\r\n    //---------------------------------\r\n\r\n    // Augment XRegExp's regular expression syntax and flags. Note that when adding tokens, the\r\n    // third (`scope`) argument defaults to `XRegExp.OUTSIDE_CLASS`\r\n\r\n    // Comment pattern: (?# )\r\n    XRegExp.addToken(\r\n        /\\(\\?#[^)]*\\)/,\r\n        function (match) {\r\n            // Keep tokens separated unless the following token is a quantifier\r\n            return nativ.test.call(quantifier, match.input.slice(match.index + match[0].length)) ? \"\" : \"(?:)\";\r\n        }\r\n    );\r\n\r\n    // Capturing group (match the opening parenthesis only).\r\n    // Required for support of named capturing groups\r\n    XRegExp.addToken(\r\n        /\\((?!\\?)/,\r\n        function () {\r\n            this.captureNames.push(null);\r\n            return \"(\";\r\n        }\r\n    );\r\n\r\n    // Named capturing group (match the opening delimiter only): (?<name>\r\n    XRegExp.addToken(\r\n        /\\(\\?<([$\\w]+)>/,\r\n        function (match) {\r\n            this.captureNames.push(match[1]);\r\n            this.hasNamedCapture = true;\r\n            return \"(\";\r\n        }\r\n    );\r\n\r\n    // Named backreference: \\k<name>\r\n    XRegExp.addToken(\r\n        /\\\\k<([\\w$]+)>/,\r\n        function (match) {\r\n            var index = indexOf(this.captureNames, match[1]);\r\n            // Keep backreferences separate from subsequent literal numbers. Preserve back-\r\n            // references to named groups that are undefined at this point as literal strings\r\n            return index > -1 ?\r\n                \"\\\\\" + (index + 1) + (isNaN(match.input.charAt(match.index + match[0].length)) ? \"\" : \"(?:)\") :\r\n                match[0];\r\n        }\r\n    );\r\n\r\n    // Empty character class: [] or [^]\r\n    XRegExp.addToken(\r\n        /\\[\\^?]/,\r\n        function (match) {\r\n            // For cross-browser compatibility with ES3, convert [] to \\b\\B and [^] to [\\s\\S].\r\n            // (?!) should work like \\b\\B, but is unreliable in Firefox\r\n            return match[0] === \"[]\" ? \"\\\\b\\\\B\" : \"[\\\\s\\\\S]\";\r\n        }\r\n    );\r\n\r\n    // Mode modifier at the start of the pattern only, with any combination of flags imsx: (?imsx)\r\n    // Does not support x(?i), (?-i), (?i-m), (?i: ), (?i)(?m), etc.\r\n    XRegExp.addToken(\r\n        /^\\(\\?([imsx]+)\\)/,\r\n        function (match) {\r\n            this.setFlag(match[1]);\r\n            return \"\";\r\n        }\r\n    );\r\n\r\n    // Whitespace and comments, in free-spacing (aka extended) mode only\r\n    XRegExp.addToken(\r\n        /(?:\\s+|#.*)+/,\r\n        function (match) {\r\n            // Keep tokens separated unless the following token is a quantifier\r\n            return nativ.test.call(quantifier, match.input.slice(match.index + match[0].length)) ? \"\" : \"(?:)\";\r\n        },\r\n        XRegExp.OUTSIDE_CLASS,\r\n        function () {return this.hasFlag(\"x\");}\r\n    );\r\n\r\n    // Dot, in dotall (aka singleline) mode only\r\n    XRegExp.addToken(\r\n        /\\./,\r\n        function () {return \"[\\\\s\\\\S]\";},\r\n        XRegExp.OUTSIDE_CLASS,\r\n        function () {return this.hasFlag(\"s\");}\r\n    );\r\n\r\n\r\n    //---------------------------------\r\n    //  Backward compatibility\r\n    //---------------------------------\r\n\r\n    // Uncomment the following block for compatibility with XRegExp 1.0-1.2:\r\n    /*\r\n     XRegExp.matchWithinChain = XRegExp.matchChain;\r\n     RegExp.prototype.addFlags = function (s) {return clone(this, s);};\r\n     RegExp.prototype.execAll = function (s) {var r = []; XRegExp.iterate(s, this, function (m) {r.push(m);}); return r;};\r\n     RegExp.prototype.forEachExec = function (s, f, c) {return XRegExp.iterate(s, this, f, c);};\r\n     RegExp.prototype.validate = function (s) {var r = RegExp(\"^(?:\" + this.source + \")$(?!\\\\s)\", getNativeFlags(this)); if (this.global) this.lastIndex = 0; return s.search(r) === 0;};\r\n     */\r\n\r\n})();\r\n\r\n//\r\n// Begin anonymous function. This is used to contain local scope variables without polutting global scope.\r\n//\r\nif (typeof(SyntaxHighlighter) == 'undefined') var SyntaxHighlighter = function() {\r\n\r\n// CommonJS\r\n    if (typeof(require) != 'undefined' && typeof(XRegExp) == 'undefined')\r\n    {\r\n        XRegExp = require('XRegExp').XRegExp;\r\n    }\r\n\r\n// Shortcut object which will be assigned to the SyntaxHighlighter variable.\r\n// This is a shorthand for local reference in order to avoid long namespace\r\n// references to SyntaxHighlighter.whatever...\r\n    var sh = {\r\n        defaults : {\r\n            /** Additional CSS class names to be added to highlighter elements. */\r\n            'class-name' : '',\r\n\r\n            /** First line number. */\r\n            'first-line' : 1,\r\n\r\n            /**\r\n             * Pads line numbers. Possible values are:\r\n             *\r\n             *   false - don't pad line numbers.\r\n             *   true  - automaticaly pad numbers with minimum required number of leading zeroes.\r\n             *   [int] - length up to which pad line numbers.\r\n             */\r\n            'pad-line-numbers' : false,\r\n\r\n            /** Lines to highlight. */\r\n            'highlight' : false,\r\n\r\n            /** Title to be displayed above the code block. */\r\n            'title' : null,\r\n\r\n            /** Enables or disables smart tabs. */\r\n            'smart-tabs' : true,\r\n\r\n            /** Gets or sets tab size. */\r\n            'tab-size' : 4,\r\n\r\n            /** Enables or disables gutter. */\r\n            'gutter' : true,\r\n\r\n            /** Enables or disables toolbar. */\r\n            'toolbar' : true,\r\n\r\n            /** Enables quick code copy and paste from double click. */\r\n            'quick-code' : true,\r\n\r\n            /** Forces code view to be collapsed. */\r\n            'collapse' : false,\r\n\r\n            /** Enables or disables automatic links. */\r\n            'auto-links' : false,\r\n\r\n            /** Gets or sets light mode. Equavalent to turning off gutter and toolbar. */\r\n            'light' : false,\r\n\r\n            'unindent' : true,\r\n\r\n            'html-script' : false\r\n        },\r\n\r\n        config : {\r\n            space : '&nbsp;',\r\n\r\n            /** Enables use of <SCRIPT type=\"syntaxhighlighter\" /> tags. */\r\n            useScriptTags : true,\r\n\r\n            /** Blogger mode flag. */\r\n            bloggerMode : false,\r\n\r\n            stripBrs : false,\r\n\r\n            /** Name of the tag that SyntaxHighlighter will automatically look for. */\r\n            tagName : 'pre',\r\n\r\n            strings : {\r\n                expandSource : 'expand source',\r\n                help : '?',\r\n                alert: 'SyntaxHighlighter\\n\\n',\r\n                noBrush : 'Can\\'t find brush for: ',\r\n                brushNotHtmlScript : 'Brush wasn\\'t configured for html-script option: ',\r\n\r\n                // this is populated by the build script\r\n                aboutDialog : '@ABOUT@'\r\n            }\r\n        },\r\n\r\n        /** Internal 'global' variables. */\r\n        vars : {\r\n            discoveredBrushes : null,\r\n            highlighters : {}\r\n        },\r\n\r\n        /** This object is populated by user included external brush files. */\r\n        brushes : {},\r\n\r\n        /** Common regular expressions. */\r\n        regexLib : {\r\n            multiLineCComments\t\t\t: /\\/\\*[\\s\\S]*?\\*\\//gm,\r\n            singleLineCComments\t\t\t: /\\/\\/.*$/gm,\r\n            singleLinePerlComments\t\t: /#.*$/gm,\r\n            doubleQuotedString\t\t\t: /\"([^\\\\\"\\n]|\\\\.)*\"/g,\r\n            singleQuotedString\t\t\t: /'([^\\\\'\\n]|\\\\.)*'/g,\r\n            multiLineDoubleQuotedString\t: new XRegExp('\"([^\\\\\\\\\"]|\\\\\\\\.)*\"', 'gs'),\r\n            multiLineSingleQuotedString\t: new XRegExp(\"'([^\\\\\\\\']|\\\\\\\\.)*'\", 'gs'),\r\n            xmlComments\t\t\t\t\t: /(&lt;|<)!--[\\s\\S]*?--(&gt;|>)/gm,\r\n            url\t\t\t\t\t\t\t: /\\w+:\\/\\/[\\w-.\\/?%&=:@;#]*/g,\r\n\r\n            /** <?= ?> tags. */\r\n            phpScriptTags \t\t\t\t: { left: /(&lt;|<)\\?(?:=|php)?/g, right: /\\?(&gt;|>)/g, 'eof' : true },\r\n\r\n            /** <%= %> tags. */\r\n            aspScriptTags\t\t\t\t: { left: /(&lt;|<)%=?/g, right: /%(&gt;|>)/g },\r\n\r\n            /** <script> tags. */\r\n            scriptScriptTags\t\t\t: { left: /(&lt;|<)\\s*script.*?(&gt;|>)/gi, right: /(&lt;|<)\\/\\s*script\\s*(&gt;|>)/gi }\r\n        },\r\n\r\n        toolbar: {\r\n            /**\r\n             * Generates HTML markup for the toolbar.\r\n             * @param {Highlighter} highlighter Highlighter instance.\r\n             * @return {String} Returns HTML markup.\r\n             */\r\n            getHtml: function(highlighter)\r\n            {\r\n                var html = '<div class=\"toolbar\">',\r\n                    items = sh.toolbar.items,\r\n                    list = items.list\r\n                    ;\r\n\r\n                function defaultGetHtml(highlighter, name)\r\n                {\r\n                    return sh.toolbar.getButtonHtml(highlighter, name, sh.config.strings[name]);\r\n                };\r\n\r\n                for (var i = 0; i < list.length; i++)\r\n                    html += (items[list[i]].getHtml || defaultGetHtml)(highlighter, list[i]);\r\n\r\n                html += '</div>';\r\n\r\n                return html;\r\n            },\r\n\r\n            /**\r\n             * Generates HTML markup for a regular button in the toolbar.\r\n             * @param {Highlighter} highlighter Highlighter instance.\r\n             * @param {String} commandName\t\tCommand name that would be executed.\r\n             * @param {String} label\t\t\tLabel text to display.\r\n             * @return {String}\t\t\t\t\tReturns HTML markup.\r\n             */\r\n            getButtonHtml: function(highlighter, commandName, label)\r\n            {\r\n                return '<span><a href=\"#\" class=\"toolbar_item'\r\n                    + ' command_' + commandName\r\n                    + ' ' + commandName\r\n                    + '\">' + label + '</a></span>'\r\n                    ;\r\n            },\r\n\r\n            /**\r\n             * Event handler for a toolbar anchor.\r\n             */\r\n            handler: function(e)\r\n            {\r\n                var target = e.target,\r\n                    className = target.className || ''\r\n                    ;\r\n\r\n                function getValue(name)\r\n                {\r\n                    var r = new RegExp(name + '_(\\\\w+)'),\r\n                        match = r.exec(className)\r\n                        ;\r\n\r\n                    return match ? match[1] : null;\r\n                };\r\n\r\n                var highlighter = getHighlighterById(findParentElement(target, '.syntaxhighlighter').id),\r\n                    commandName = getValue('command')\r\n                    ;\r\n\r\n                // execute the toolbar command\r\n                if (highlighter && commandName)\r\n                    sh.toolbar.items[commandName].execute(highlighter);\r\n\r\n                // disable default A click behaviour\r\n                e.preventDefault();\r\n            },\r\n\r\n            /** Collection of toolbar items. */\r\n            items : {\r\n                // Ordered lis of items in the toolbar. Can't expect `for (var n in items)` to be consistent.\r\n                list: ['expandSource', 'help'],\r\n\r\n                expandSource: {\r\n                    getHtml: function(highlighter)\r\n                    {\r\n                        if (highlighter.getParam('collapse') != true)\r\n                            return '';\r\n\r\n                        var title = highlighter.getParam('title');\r\n                        return sh.toolbar.getButtonHtml(highlighter, 'expandSource', title ? title : sh.config.strings.expandSource);\r\n                    },\r\n\r\n                    execute: function(highlighter)\r\n                    {\r\n                        var div = getHighlighterDivById(highlighter.id);\r\n                        removeClass(div, 'collapsed');\r\n                    }\r\n                },\r\n\r\n                /** Command to display the about dialog window. */\r\n                help: {\r\n                    execute: function(highlighter)\r\n                    {\r\n                        var wnd = popup('', '_blank', 500, 250, 'scrollbars=0'),\r\n                            doc = wnd.document\r\n                            ;\r\n\r\n                        doc.write(sh.config.strings.aboutDialog);\r\n                        doc.close();\r\n                        wnd.focus();\r\n                    }\r\n                }\r\n            }\r\n        },\r\n\r\n        /**\r\n         * Finds all elements on the page which should be processes by SyntaxHighlighter.\r\n         *\r\n         * @param {Object} globalParams\t\tOptional parameters which override element's\r\n         * \t\t\t\t\t\t\t\t\tparameters. Only used if element is specified.\r\n         *\r\n         * @param {Object} element\tOptional element to highlight. If none is\r\n         * \t\t\t\t\t\t\tprovided, all elements in the current document\r\n         * \t\t\t\t\t\t\tare returned which qualify.\r\n         *\r\n         * @return {Array}\tReturns list of <code>{ target: DOMElement, params: Object }</code> objects.\r\n         */\r\n        findElements: function(globalParams, element)\r\n        {\r\n            var elements = element ? [element] : toArray(document.getElementsByTagName(sh.config.tagName)),\r\n                conf = sh.config,\r\n                result = []\r\n                ;\r\n\r\n            // support for <SCRIPT TYPE=\"syntaxhighlighter\" /> feature\r\n            if (conf.useScriptTags)\r\n                elements = elements.concat(getSyntaxHighlighterScriptTags());\r\n\r\n            if (elements.length === 0)\r\n                return result;\r\n\r\n            for (var i = 0; i < elements.length; i++)\r\n            {\r\n                var item = {\r\n                    target: elements[i],\r\n                    // local params take precedence over globals\r\n                    params: merge(globalParams, parseParams(elements[i].className))\r\n                };\r\n\r\n                if (item.params['brush'] == null)\r\n                    continue;\r\n\r\n                result.push(item);\r\n            }\r\n\r\n            return result;\r\n        },\r\n\r\n        /**\r\n         * Shorthand to highlight all elements on the page that are marked as\r\n         * SyntaxHighlighter source code.\r\n         *\r\n         * @param {Object} globalParams\t\tOptional parameters which override element's\r\n         * \t\t\t\t\t\t\t\t\tparameters. Only used if element is specified.\r\n         *\r\n         * @param {Object} element\tOptional element to highlight. If none is\r\n         * \t\t\t\t\t\t\tprovided, all elements in the current document\r\n         * \t\t\t\t\t\t\tare highlighted.\r\n         */\r\n        highlight: function(globalParams, element)\r\n        {\r\n            var elements = this.findElements(globalParams, element),\r\n                propertyName = 'innerHTML',\r\n                highlighter = null,\r\n                conf = sh.config\r\n                ;\r\n\r\n            if (elements.length === 0)\r\n                return;\r\n\r\n            for (var i = 0; i < elements.length; i++)\r\n            {\r\n                var element = elements[i],\r\n                    target = element.target,\r\n                    params = element.params,\r\n                    brushName = params.brush,\r\n                    code\r\n                    ;\r\n\r\n                if (brushName == null)\r\n                    continue;\r\n\r\n                // Instantiate a brush\r\n                if (params['html-script'] == 'true' || sh.defaults['html-script'] == true)\r\n                {\r\n                    highlighter = new sh.HtmlScript(brushName);\r\n                    brushName = 'htmlscript';\r\n                }\r\n                else\r\n                {\r\n                    var brush = findBrush(brushName);\r\n\r\n                    if (brush)\r\n                        highlighter = new brush();\r\n                    else\r\n                        continue;\r\n                }\r\n\r\n                code = target[propertyName];\r\n\r\n                // remove CDATA from <SCRIPT/> tags if it's present\r\n                if (conf.useScriptTags)\r\n                    code = stripCData(code);\r\n\r\n                // Inject title if the attribute is present\r\n                if ((target.title || '') != '')\r\n                    params.title = target.title;\r\n\r\n                params['brush'] = brushName;\r\n                highlighter.init(params);\r\n                element = highlighter.getDiv(code);\r\n\r\n                // carry over ID\r\n                if ((target.id || '') != '')\r\n                    element.id = target.id;\r\n                //by zhanyi 去掉多余的外围div\r\n                var tmp = element.firstChild.firstChild;\r\n                tmp.className = element.firstChild.className;\r\n\r\n                target.parentNode.replaceChild(tmp, target);\r\n            }\r\n        },\r\n\r\n        /**\r\n         * Main entry point for the SyntaxHighlighter.\r\n         * @param {Object} params Optional params to apply to all highlighted elements.\r\n         */\r\n        all: function(params)\r\n        {\r\n            attachEvent(\r\n                window,\r\n                'load',\r\n                function() { sh.highlight(params); }\r\n            );\r\n        }\r\n    }; // end of sh\r\n\r\n    /**\r\n     * Checks if target DOM elements has specified CSS class.\r\n     * @param {DOMElement} target Target DOM element to check.\r\n     * @param {String} className Name of the CSS class to check for.\r\n     * @return {Boolean} Returns true if class name is present, false otherwise.\r\n     */\r\n    function hasClass(target, className)\r\n    {\r\n        return target.className.indexOf(className) != -1;\r\n    };\r\n\r\n    /**\r\n     * Adds CSS class name to the target DOM element.\r\n     * @param {DOMElement} target Target DOM element.\r\n     * @param {String} className New CSS class to add.\r\n     */\r\n    function addClass(target, className)\r\n    {\r\n        if (!hasClass(target, className))\r\n            target.className += ' ' + className;\r\n    };\r\n\r\n    /**\r\n     * Removes CSS class name from the target DOM element.\r\n     * @param {DOMElement} target Target DOM element.\r\n     * @param {String} className CSS class to remove.\r\n     */\r\n    function removeClass(target, className)\r\n    {\r\n        target.className = target.className.replace(className, '');\r\n    };\r\n\r\n    /**\r\n     * Converts the source to array object. Mostly used for function arguments and\r\n     * lists returned by getElementsByTagName() which aren't Array objects.\r\n     * @param {List} source Source list.\r\n     * @return {Array} Returns array.\r\n     */\r\n    function toArray(source)\r\n    {\r\n        var result = [];\r\n\r\n        for (var i = 0; i < source.length; i++)\r\n            result.push(source[i]);\r\n\r\n        return result;\r\n    };\r\n\r\n    /**\r\n     * Splits block of text into lines.\r\n     * @param {String} block Block of text.\r\n     * @return {Array} Returns array of lines.\r\n     */\r\n    function splitLines(block)\r\n    {\r\n        return block.split(/\\r?\\n/);\r\n    }\r\n\r\n    /**\r\n     * Generates HTML ID for the highlighter.\r\n     * @param {String} highlighterId Highlighter ID.\r\n     * @return {String} Returns HTML ID.\r\n     */\r\n    function getHighlighterId(id)\r\n    {\r\n        var prefix = 'highlighter_';\r\n        return id.indexOf(prefix) == 0 ? id : prefix + id;\r\n    };\r\n\r\n    /**\r\n     * Finds Highlighter instance by ID.\r\n     * @param {String} highlighterId Highlighter ID.\r\n     * @return {Highlighter} Returns instance of the highlighter.\r\n     */\r\n    function getHighlighterById(id)\r\n    {\r\n        return sh.vars.highlighters[getHighlighterId(id)];\r\n    };\r\n\r\n    /**\r\n     * Finds highlighter's DIV container.\r\n     * @param {String} highlighterId Highlighter ID.\r\n     * @return {Element} Returns highlighter's DIV element.\r\n     */\r\n    function getHighlighterDivById(id)\r\n    {\r\n        return document.getElementById(getHighlighterId(id));\r\n    };\r\n\r\n    /**\r\n     * Stores highlighter so that getHighlighterById() can do its thing. Each\r\n     * highlighter must call this method to preserve itself.\r\n     * @param {Highilghter} highlighter Highlighter instance.\r\n     */\r\n    function storeHighlighter(highlighter)\r\n    {\r\n        sh.vars.highlighters[getHighlighterId(highlighter.id)] = highlighter;\r\n    };\r\n\r\n    /**\r\n     * Looks for a child or parent node which has specified classname.\r\n     * Equivalent to jQuery's $(container).find(\".className\")\r\n     * @param {Element} target Target element.\r\n     * @param {String} search Class name or node name to look for.\r\n     * @param {Boolean} reverse If set to true, will go up the node tree instead of down.\r\n     * @return {Element} Returns found child or parent element on null.\r\n     */\r\n    function findElement(target, search, reverse /* optional */)\r\n    {\r\n        if (target == null)\r\n            return null;\r\n\r\n        var nodes\t\t\t= reverse != true ? target.childNodes : [ target.parentNode ],\r\n            propertyToFind\t= { '#' : 'id', '.' : 'className' }[search.substr(0, 1)] || 'nodeName',\r\n            expectedValue,\r\n            found\r\n            ;\r\n\r\n        expectedValue = propertyToFind != 'nodeName'\r\n            ? search.substr(1)\r\n            : search.toUpperCase()\r\n        ;\r\n\r\n        // main return of the found node\r\n        if ((target[propertyToFind] || '').indexOf(expectedValue) != -1)\r\n            return target;\r\n\r\n        for (var i = 0; nodes && i < nodes.length && found == null; i++)\r\n            found = findElement(nodes[i], search, reverse);\r\n\r\n        return found;\r\n    };\r\n\r\n    /**\r\n     * Looks for a parent node which has specified classname.\r\n     * This is an alias to <code>findElement(container, className, true)</code>.\r\n     * @param {Element} target Target element.\r\n     * @param {String} className Class name to look for.\r\n     * @return {Element} Returns found parent element on null.\r\n     */\r\n    function findParentElement(target, className)\r\n    {\r\n        return findElement(target, className, true);\r\n    };\r\n\r\n    /**\r\n     * Finds an index of element in the array.\r\n     * @ignore\r\n     * @param {Object} searchElement\r\n     * @param {Number} fromIndex\r\n     * @return {Number} Returns index of element if found; -1 otherwise.\r\n     */\r\n    function indexOf(array, searchElement, fromIndex)\r\n    {\r\n        fromIndex = Math.max(fromIndex || 0, 0);\r\n\r\n        for (var i = fromIndex; i < array.length; i++)\r\n            if(array[i] == searchElement)\r\n                return i;\r\n\r\n        return -1;\r\n    };\r\n\r\n    /**\r\n     * Generates a unique element ID.\r\n     */\r\n    function guid(prefix)\r\n    {\r\n        return (prefix || '') + Math.round(Math.random() * 1000000).toString();\r\n    };\r\n\r\n    /**\r\n     * Merges two objects. Values from obj2 override values in obj1.\r\n     * Function is NOT recursive and works only for one dimensional objects.\r\n     * @param {Object} obj1 First object.\r\n     * @param {Object} obj2 Second object.\r\n     * @return {Object} Returns combination of both objects.\r\n     */\r\n    function merge(obj1, obj2)\r\n    {\r\n        var result = {}, name;\r\n\r\n        for (name in obj1)\r\n            result[name] = obj1[name];\r\n\r\n        for (name in obj2)\r\n            result[name] = obj2[name];\r\n\r\n        return result;\r\n    };\r\n\r\n    /**\r\n     * Attempts to convert string to boolean.\r\n     * @param {String} value Input string.\r\n     * @return {Boolean} Returns true if input was \"true\", false if input was \"false\" and value otherwise.\r\n     */\r\n    function toBoolean(value)\r\n    {\r\n        var result = { \"true\" : true, \"false\" : false }[value];\r\n        return result == null ? value : result;\r\n    };\r\n\r\n    /**\r\n     * Opens up a centered popup window.\r\n     * @param {String} url\t\tURL to open in the window.\r\n     * @param {String} name\t\tPopup name.\r\n     * @param {int} width\t\tPopup width.\r\n     * @param {int} height\t\tPopup height.\r\n     * @param {String} options\twindow.open() options.\r\n     * @return {Window}\t\t\tReturns window instance.\r\n     */\r\n    function popup(url, name, width, height, options)\r\n    {\r\n        var x = (screen.width - width) / 2,\r\n            y = (screen.height - height) / 2\r\n            ;\r\n\r\n        options +=\t', left=' + x +\r\n            ', top=' + y +\r\n            ', width=' + width +\r\n            ', height=' + height\r\n        ;\r\n        options = options.replace(/^,/, '');\r\n\r\n        var win = window.open(url, name, options);\r\n        win.focus();\r\n        return win;\r\n    };\r\n\r\n    /**\r\n     * Adds event handler to the target object.\r\n     * @param {Object} obj\t\tTarget object.\r\n     * @param {String} type\t\tName of the event.\r\n     * @param {Function} func\tHandling function.\r\n     */\r\n    function attachEvent(obj, type, func, scope)\r\n    {\r\n        function handler(e)\r\n        {\r\n            e = e || window.event;\r\n\r\n            if (!e.target)\r\n            {\r\n                e.target = e.srcElement;\r\n                e.preventDefault = function()\r\n                {\r\n                    this.returnValue = false;\r\n                };\r\n            }\r\n\r\n            func.call(scope || window, e);\r\n        };\r\n\r\n        if (obj.attachEvent)\r\n        {\r\n            obj.attachEvent('on' + type, handler);\r\n        }\r\n        else\r\n        {\r\n            obj.addEventListener(type, handler, false);\r\n        }\r\n    };\r\n\r\n    /**\r\n     * Displays an alert.\r\n     * @param {String} str String to display.\r\n     */\r\n    function alert(str)\r\n    {\r\n        window.alert(sh.config.strings.alert + str);\r\n    };\r\n\r\n    /**\r\n     * Finds a brush by its alias.\r\n     *\r\n     * @param {String} alias\t\tBrush alias.\r\n     * @param {Boolean} showAlert\tSuppresses the alert if false.\r\n     * @return {Brush}\t\t\t\tReturns bursh constructor if found, null otherwise.\r\n     */\r\n    function findBrush(alias, showAlert)\r\n    {\r\n        var brushes = sh.vars.discoveredBrushes,\r\n            result = null\r\n            ;\r\n\r\n        if (brushes == null)\r\n        {\r\n            brushes = {};\r\n\r\n            // Find all brushes\r\n            for (var brush in sh.brushes)\r\n            {\r\n                var info = sh.brushes[brush],\r\n                    aliases = info.aliases\r\n                    ;\r\n\r\n                if (aliases == null)\r\n                    continue;\r\n\r\n                // keep the brush name\r\n                info.brushName = brush.toLowerCase();\r\n\r\n                for (var i = 0; i < aliases.length; i++)\r\n                    brushes[aliases[i]] = brush;\r\n            }\r\n\r\n            sh.vars.discoveredBrushes = brushes;\r\n        }\r\n\r\n        result = sh.brushes[brushes[alias]];\r\n\r\n        if (result == null && showAlert)\r\n            alert(sh.config.strings.noBrush + alias);\r\n\r\n        return result;\r\n    };\r\n\r\n    /**\r\n     * Executes a callback on each line and replaces each line with result from the callback.\r\n     * @param {Object} str\t\t\tInput string.\r\n     * @param {Object} callback\t\tCallback function taking one string argument and returning a string.\r\n     */\r\n    function eachLine(str, callback)\r\n    {\r\n        var lines = splitLines(str);\r\n\r\n        for (var i = 0; i < lines.length; i++)\r\n            lines[i] = callback(lines[i], i);\r\n\r\n        // include \\r to enable copy-paste on windows (ie8) without getting everything on one line\r\n        return lines.join('\\r\\n');\r\n    };\r\n\r\n    /**\r\n     * This is a special trim which only removes first and last empty lines\r\n     * and doesn't affect valid leading space on the first line.\r\n     *\r\n     * @param {String} str   Input string\r\n     * @return {String}      Returns string without empty first and last lines.\r\n     */\r\n    function trimFirstAndLastLines(str)\r\n    {\r\n        return str.replace(/^[ ]*[\\n]+|[\\n]*[ ]*$/g, '');\r\n    };\r\n\r\n    /**\r\n     * Parses key/value pairs into hash object.\r\n     *\r\n     * Understands the following formats:\r\n     * - name: word;\r\n     * - name: [word, word];\r\n     * - name: \"string\";\r\n     * - name: 'string';\r\n     *\r\n     * For example:\r\n     *   name1: value; name2: [value, value]; name3: 'value'\r\n     *\r\n     * @param {String} str    Input string.\r\n     * @return {Object}       Returns deserialized object.\r\n     */\r\n    function parseParams(str)\r\n    {\r\n        var match,\r\n            result = {},\r\n            arrayRegex = new XRegExp(\"^\\\\[(?<values>(.*?))\\\\]$\"),\r\n            regex = new XRegExp(\r\n                \"(?<name>[\\\\w-]+)\" +\r\n                    \"\\\\s*:\\\\s*\" +\r\n                    \"(?<value>\" +\r\n                    \"[\\\\w-%#]+|\" +\t\t// word\r\n                    \"\\\\[.*?\\\\]|\" +\t\t// [] array\r\n                    '\".*?\"|' +\t\t\t// \"\" string\r\n                    \"'.*?'\" +\t\t\t// '' string\r\n                    \")\\\\s*;?\",\r\n                \"g\"\r\n            )\r\n            ;\r\n\r\n        while ((match = regex.exec(str)) != null)\r\n        {\r\n            var value = match.value\r\n                    .replace(/^['\"]|['\"]$/g, '') // strip quotes from end of strings\r\n                ;\r\n\r\n            // try to parse array value\r\n            if (value != null && arrayRegex.test(value))\r\n            {\r\n                var m = arrayRegex.exec(value);\r\n                value = m.values.length > 0 ? m.values.split(/\\s*,\\s*/) : [];\r\n            }\r\n\r\n            result[match.name] = value;\r\n        }\r\n\r\n        return result;\r\n    };\r\n\r\n    /**\r\n     * Wraps each line of the string into <code/> tag with given style applied to it.\r\n     *\r\n     * @param {String} str   Input string.\r\n     * @param {String} css   Style name to apply to the string.\r\n     * @return {String}      Returns input string with each line surrounded by <span/> tag.\r\n     */\r\n    function wrapLinesWithCode(str, css)\r\n    {\r\n        if (str == null || str.length == 0 || str == '\\n')\r\n            return str;\r\n\r\n        str = str.replace(/</g, '&lt;');\r\n\r\n        // Replace two or more sequential spaces with &nbsp; leaving last space untouched.\r\n        str = str.replace(/ {2,}/g, function(m)\r\n        {\r\n            var spaces = '';\r\n\r\n            for (var i = 0; i < m.length - 1; i++)\r\n                spaces += sh.config.space;\r\n\r\n            return spaces + ' ';\r\n        });\r\n\r\n        // Split each line and apply <span class=\"...\">...</span> to them so that\r\n        // leading spaces aren't included.\r\n        if (css != null)\r\n            str = eachLine(str, function(line)\r\n            {\r\n                if (line.length == 0)\r\n                    return '';\r\n\r\n                var spaces = '';\r\n\r\n                line = line.replace(/^(&nbsp;| )+/, function(s)\r\n                {\r\n                    spaces = s;\r\n                    return '';\r\n                });\r\n\r\n                if (line.length == 0)\r\n                    return spaces;\r\n\r\n                return spaces + '<code class=\"' + css + '\">' + line + '</code>';\r\n            });\r\n\r\n        return str;\r\n    };\r\n\r\n    /**\r\n     * Pads number with zeros until it's length is the same as given length.\r\n     *\r\n     * @param {Number} number\tNumber to pad.\r\n     * @param {Number} length\tMax string length with.\r\n     * @return {String}\t\t\tReturns a string padded with proper amount of '0'.\r\n     */\r\n    function padNumber(number, length)\r\n    {\r\n        var result = number.toString();\r\n\r\n        while (result.length < length)\r\n            result = '0' + result;\r\n\r\n        return result;\r\n    };\r\n\r\n    /**\r\n     * Replaces tabs with spaces.\r\n     *\r\n     * @param {String} code\t\tSource code.\r\n     * @param {Number} tabSize\tSize of the tab.\r\n     * @return {String}\t\t\tReturns code with all tabs replaces by spaces.\r\n     */\r\n    function processTabs(code, tabSize)\r\n    {\r\n        var tab = '';\r\n\r\n        for (var i = 0; i < tabSize; i++)\r\n            tab += ' ';\r\n\r\n        return code.replace(/\\t/g, tab);\r\n    };\r\n\r\n    /**\r\n     * Replaces tabs with smart spaces.\r\n     *\r\n     * @param {String} code    Code to fix the tabs in.\r\n     * @param {Number} tabSize Number of spaces in a column.\r\n     * @return {String}        Returns code with all tabs replaces with roper amount of spaces.\r\n     */\r\n    function processSmartTabs(code, tabSize)\r\n    {\r\n        var lines = splitLines(code),\r\n            tab = '\\t',\r\n            spaces = ''\r\n            ;\r\n\r\n        // Create a string with 1000 spaces to copy spaces from...\r\n        // It's assumed that there would be no indentation longer than that.\r\n        for (var i = 0; i < 50; i++)\r\n            spaces += '                    '; // 20 spaces * 50\r\n\r\n        // This function inserts specified amount of spaces in the string\r\n        // where a tab is while removing that given tab.\r\n        function insertSpaces(line, pos, count)\r\n        {\r\n            return line.substr(0, pos)\r\n                + spaces.substr(0, count)\r\n                + line.substr(pos + 1, line.length) // pos + 1 will get rid of the tab\r\n                ;\r\n        };\r\n\r\n        // Go through all the lines and do the 'smart tabs' magic.\r\n        code = eachLine(code, function(line)\r\n        {\r\n            if (line.indexOf(tab) == -1)\r\n                return line;\r\n\r\n            var pos = 0;\r\n\r\n            while ((pos = line.indexOf(tab)) != -1)\r\n            {\r\n                // This is pretty much all there is to the 'smart tabs' logic.\r\n                // Based on the position within the line and size of a tab,\r\n                // calculate the amount of spaces we need to insert.\r\n                var spaces = tabSize - pos % tabSize;\r\n                line = insertSpaces(line, pos, spaces);\r\n            }\r\n\r\n            return line;\r\n        });\r\n\r\n        return code;\r\n    };\r\n\r\n    /**\r\n     * Performs various string fixes based on configuration.\r\n     */\r\n    function fixInputString(str)\r\n    {\r\n        var br = /<br\\s*\\/?>|&lt;br\\s*\\/?&gt;/gi;\r\n\r\n        if (sh.config.bloggerMode == true)\r\n            str = str.replace(br, '\\n');\r\n\r\n        if (sh.config.stripBrs == true)\r\n            str = str.replace(br, '');\r\n\r\n        return str;\r\n    };\r\n\r\n    /**\r\n     * Removes all white space at the begining and end of a string.\r\n     *\r\n     * @param {String} str   String to trim.\r\n     * @return {String}      Returns string without leading and following white space characters.\r\n     */\r\n    function trim(str)\r\n    {\r\n        return str.replace(/^\\s+|\\s+$/g, '');\r\n    };\r\n\r\n    /**\r\n     * Unindents a block of text by the lowest common indent amount.\r\n     * @param {String} str   Text to unindent.\r\n     * @return {String}      Returns unindented text block.\r\n     */\r\n    function unindent(str)\r\n    {\r\n        var lines = splitLines(fixInputString(str)),\r\n            indents = new Array(),\r\n            regex = /^\\s*/,\r\n            min = 1000\r\n            ;\r\n\r\n        // go through every line and check for common number of indents\r\n        for (var i = 0; i < lines.length && min > 0; i++)\r\n        {\r\n            var line = lines[i];\r\n\r\n            if (trim(line).length == 0)\r\n                continue;\r\n\r\n            var matches = regex.exec(line);\r\n\r\n            // In the event that just one line doesn't have leading white space\r\n            // we can't unindent anything, so bail completely.\r\n            if (matches == null)\r\n                return str;\r\n\r\n            min = Math.min(matches[0].length, min);\r\n        }\r\n\r\n        // trim minimum common number of white space from the begining of every line\r\n        if (min > 0)\r\n            for (var i = 0; i < lines.length; i++)\r\n                lines[i] = lines[i].substr(min);\r\n\r\n        return lines.join('\\n');\r\n    };\r\n\r\n    /**\r\n     * Callback method for Array.sort() which sorts matches by\r\n     * index position and then by length.\r\n     *\r\n     * @param {Match} m1\tLeft object.\r\n     * @param {Match} m2    Right object.\r\n     * @return {Number}     Returns -1, 0 or -1 as a comparison result.\r\n     */\r\n    function matchesSortCallback(m1, m2)\r\n    {\r\n        // sort matches by index first\r\n        if(m1.index < m2.index)\r\n            return -1;\r\n        else if(m1.index > m2.index)\r\n            return 1;\r\n        else\r\n        {\r\n            // if index is the same, sort by length\r\n            if(m1.length < m2.length)\r\n                return -1;\r\n            else if(m1.length > m2.length)\r\n                return 1;\r\n        }\r\n\r\n        return 0;\r\n    };\r\n\r\n    /**\r\n     * Executes given regular expression on provided code and returns all\r\n     * matches that are found.\r\n     *\r\n     * @param {String} code    Code to execute regular expression on.\r\n     * @param {Object} regex   Regular expression item info from <code>regexList</code> collection.\r\n     * @return {Array}         Returns a list of Match objects.\r\n     */\r\n    function getMatches(code, regexInfo)\r\n    {\r\n        function defaultAdd(match, regexInfo)\r\n        {\r\n            return match[0];\r\n        };\r\n\r\n        var index = 0,\r\n            match = null,\r\n            matches = [],\r\n            func = regexInfo.func ? regexInfo.func : defaultAdd\r\n            ;\r\n\r\n        while((match = regexInfo.regex.exec(code)) != null)\r\n        {\r\n            var resultMatch = func(match, regexInfo);\r\n\r\n            if (typeof(resultMatch) == 'string')\r\n                resultMatch = [new sh.Match(resultMatch, match.index, regexInfo.css)];\r\n\r\n            matches = matches.concat(resultMatch);\r\n        }\r\n\r\n        return matches;\r\n    };\r\n\r\n    /**\r\n     * Turns all URLs in the code into <a/> tags.\r\n     * @param {String} code Input code.\r\n     * @return {String} Returns code with </a> tags.\r\n     */\r\n    function processUrls(code)\r\n    {\r\n        var gt = /(.*)((&gt;|&lt;).*)/;\r\n\r\n        return code.replace(sh.regexLib.url, function(m)\r\n        {\r\n            var suffix = '',\r\n                match = null\r\n                ;\r\n\r\n            // We include &lt; and &gt; in the URL for the common cases like <http://google.com>\r\n            // The problem is that they get transformed into &lt;http://google.com&gt;\r\n            // Where as &gt; easily looks like part of the URL string.\r\n\r\n            if (match = gt.exec(m))\r\n            {\r\n                m = match[1];\r\n                suffix = match[2];\r\n            }\r\n\r\n            return '<a href=\"' + m + '\">' + m + '</a>' + suffix;\r\n        });\r\n    };\r\n\r\n    /**\r\n     * Finds all <SCRIPT TYPE=\"syntaxhighlighter\" /> elementss.\r\n     * @return {Array} Returns array of all found SyntaxHighlighter tags.\r\n     */\r\n    function getSyntaxHighlighterScriptTags()\r\n    {\r\n        var tags = document.getElementsByTagName('script'),\r\n            result = []\r\n            ;\r\n\r\n        for (var i = 0; i < tags.length; i++)\r\n            if (tags[i].type == 'syntaxhighlighter')\r\n                result.push(tags[i]);\r\n\r\n        return result;\r\n    };\r\n\r\n    /**\r\n     * Strips <![CDATA[]]> from <SCRIPT /> content because it should be used\r\n     * there in most cases for XHTML compliance.\r\n     * @param {String} original\tInput code.\r\n     * @return {String} Returns code without leading <![CDATA[]]> tags.\r\n     */\r\n    function stripCData(original)\r\n    {\r\n        var left = '<![CDATA[',\r\n            right = ']]>',\r\n        // for some reason IE inserts some leading blanks here\r\n            copy = trim(original),\r\n            changed = false,\r\n            leftLength = left.length,\r\n            rightLength = right.length\r\n            ;\r\n\r\n        if (copy.indexOf(left) == 0)\r\n        {\r\n            copy = copy.substring(leftLength);\r\n            changed = true;\r\n        }\r\n\r\n        var copyLength = copy.length;\r\n\r\n        if (copy.indexOf(right) == copyLength - rightLength)\r\n        {\r\n            copy = copy.substring(0, copyLength - rightLength);\r\n            changed = true;\r\n        }\r\n\r\n        return changed ? copy : original;\r\n    };\r\n\r\n\r\n    /**\r\n     * Quick code mouse double click handler.\r\n     */\r\n    function quickCodeHandler(e)\r\n    {\r\n        var target = e.target,\r\n            highlighterDiv = findParentElement(target, '.syntaxhighlighter'),\r\n            container = findParentElement(target, '.container'),\r\n            textarea = document.createElement('textarea'),\r\n            highlighter\r\n            ;\r\n\r\n        if (!container || !highlighterDiv || findElement(container, 'textarea'))\r\n            return;\r\n\r\n        highlighter = getHighlighterById(highlighterDiv.id);\r\n\r\n        // add source class name\r\n        addClass(highlighterDiv, 'source');\r\n\r\n        // Have to go over each line and grab it's text, can't just do it on the\r\n        // container because Firefox loses all \\n where as Webkit doesn't.\r\n        var lines = container.childNodes,\r\n            code = []\r\n            ;\r\n\r\n        for (var i = 0; i < lines.length; i++)\r\n            code.push(lines[i].innerText || lines[i].textContent);\r\n\r\n        // using \\r instead of \\r or \\r\\n makes this work equally well on IE, FF and Webkit\r\n        code = code.join('\\r');\r\n\r\n        // For Webkit browsers, replace nbsp with a breaking space\r\n        code = code.replace(/\\u00a0/g, \" \");\r\n\r\n        // inject <textarea/> tag\r\n        textarea.appendChild(document.createTextNode(code));\r\n        container.appendChild(textarea);\r\n\r\n        // preselect all text\r\n        textarea.focus();\r\n        textarea.select();\r\n\r\n        // set up handler for lost focus\r\n        attachEvent(textarea, 'blur', function(e)\r\n        {\r\n            textarea.parentNode.removeChild(textarea);\r\n            removeClass(highlighterDiv, 'source');\r\n        });\r\n    };\r\n\r\n    /**\r\n     * Match object.\r\n     */\r\n    sh.Match = function(value, index, css)\r\n    {\r\n        this.value = value;\r\n        this.index = index;\r\n        this.length = value.length;\r\n        this.css = css;\r\n        this.brushName = null;\r\n    };\r\n\r\n    sh.Match.prototype.toString = function()\r\n    {\r\n        return this.value;\r\n    };\r\n\r\n    /**\r\n     * Simulates HTML code with a scripting language embedded.\r\n     *\r\n     * @param {String} scriptBrushName Brush name of the scripting language.\r\n     */\r\n    sh.HtmlScript = function(scriptBrushName)\r\n    {\r\n        var brushClass = findBrush(scriptBrushName),\r\n            scriptBrush,\r\n            xmlBrush = new sh.brushes.Xml(),\r\n            bracketsRegex = null,\r\n            ref = this,\r\n            methodsToExpose = 'getDiv getHtml init'.split(' ')\r\n            ;\r\n\r\n        if (brushClass == null)\r\n            return;\r\n\r\n        scriptBrush = new brushClass();\r\n\r\n        for(var i = 0; i < methodsToExpose.length; i++)\r\n            // make a closure so we don't lose the name after i changes\r\n            (function() {\r\n                var name = methodsToExpose[i];\r\n\r\n                ref[name] = function()\r\n                {\r\n                    return xmlBrush[name].apply(xmlBrush, arguments);\r\n                };\r\n            })();\r\n\r\n        if (scriptBrush.htmlScript == null)\r\n        {\r\n            alert(sh.config.strings.brushNotHtmlScript + scriptBrushName);\r\n            return;\r\n        }\r\n\r\n        xmlBrush.regexList.push(\r\n            { regex: scriptBrush.htmlScript.code, func: process }\r\n        );\r\n\r\n        function offsetMatches(matches, offset)\r\n        {\r\n            for (var j = 0; j < matches.length; j++)\r\n                matches[j].index += offset;\r\n        }\r\n\r\n        function process(match, info)\r\n        {\r\n            var code = match.code,\r\n                matches = [],\r\n                regexList = scriptBrush.regexList,\r\n                offset = match.index + match.left.length,\r\n                htmlScript = scriptBrush.htmlScript,\r\n                result\r\n                ;\r\n\r\n            // add all matches from the code\r\n            for (var i = 0; i < regexList.length; i++)\r\n            {\r\n                result = getMatches(code, regexList[i]);\r\n                offsetMatches(result, offset);\r\n                matches = matches.concat(result);\r\n            }\r\n\r\n            // add left script bracket\r\n            if (htmlScript.left != null && match.left != null)\r\n            {\r\n                result = getMatches(match.left, htmlScript.left);\r\n                offsetMatches(result, match.index);\r\n                matches = matches.concat(result);\r\n            }\r\n\r\n            // add right script bracket\r\n            if (htmlScript.right != null && match.right != null)\r\n            {\r\n                result = getMatches(match.right, htmlScript.right);\r\n                offsetMatches(result, match.index + match[0].lastIndexOf(match.right));\r\n                matches = matches.concat(result);\r\n            }\r\n\r\n            for (var j = 0; j < matches.length; j++)\r\n                matches[j].brushName = brushClass.brushName;\r\n\r\n            return matches;\r\n        }\r\n    };\r\n\r\n    /**\r\n     * Main Highlither class.\r\n     * @constructor\r\n     */\r\n    sh.Highlighter = function()\r\n    {\r\n        // not putting any code in here because of the prototype inheritance\r\n    };\r\n\r\n    sh.Highlighter.prototype = {\r\n        /**\r\n         * Returns value of the parameter passed to the highlighter.\r\n         * @param {String} name\t\t\t\tName of the parameter.\r\n         * @param {Object} defaultValue\t\tDefault value.\r\n         * @return {Object}\t\t\t\t\tReturns found value or default value otherwise.\r\n         */\r\n        getParam: function(name, defaultValue)\r\n        {\r\n            var result = this.params[name];\r\n            return toBoolean(result == null ? defaultValue : result);\r\n        },\r\n\r\n        /**\r\n         * Shortcut to document.createElement().\r\n         * @param {String} name\t\tName of the element to create (DIV, A, etc).\r\n         * @return {HTMLElement}\tReturns new HTML element.\r\n         */\r\n        create: function(name)\r\n        {\r\n            return document.createElement(name);\r\n        },\r\n\r\n        /**\r\n         * Applies all regular expression to the code and stores all found\r\n         * matches in the `this.matches` array.\r\n         * @param {Array} regexList\t\tList of regular expressions.\r\n         * @param {String} code\t\t\tSource code.\r\n         * @return {Array}\t\t\t\tReturns list of matches.\r\n         */\r\n        findMatches: function(regexList, code)\r\n        {\r\n            var result = [];\r\n\r\n            if (regexList != null)\r\n                for (var i = 0; i < regexList.length; i++)\r\n                    // BUG: length returns len+1 for array if methods added to prototype chain (oising@gmail.com)\r\n                    if (typeof (regexList[i]) == \"object\")\r\n                        result = result.concat(getMatches(code, regexList[i]));\r\n\r\n            // sort and remove nested the matches\r\n            return this.removeNestedMatches(result.sort(matchesSortCallback));\r\n        },\r\n\r\n        /**\r\n         * Checks to see if any of the matches are inside of other matches.\r\n         * This process would get rid of highligted strings inside comments,\r\n         * keywords inside strings and so on.\r\n         */\r\n        removeNestedMatches: function(matches)\r\n        {\r\n            // Optimized by Jose Prado (http://joseprado.com)\r\n            for (var i = 0; i < matches.length; i++)\r\n            {\r\n                if (matches[i] === null)\r\n                    continue;\r\n\r\n                var itemI = matches[i],\r\n                    itemIEndPos = itemI.index + itemI.length\r\n                    ;\r\n\r\n                for (var j = i + 1; j < matches.length && matches[i] !== null; j++)\r\n                {\r\n                    var itemJ = matches[j];\r\n\r\n                    if (itemJ === null)\r\n                        continue;\r\n                    else if (itemJ.index > itemIEndPos)\r\n                        break;\r\n                    else if (itemJ.index == itemI.index && itemJ.length > itemI.length)\r\n                        matches[i] = null;\r\n                    else if (itemJ.index >= itemI.index && itemJ.index < itemIEndPos)\r\n                        matches[j] = null;\r\n                }\r\n            }\r\n\r\n            return matches;\r\n        },\r\n\r\n        /**\r\n         * Creates an array containing integer line numbers starting from the 'first-line' param.\r\n         * @return {Array} Returns array of integers.\r\n         */\r\n        figureOutLineNumbers: function(code)\r\n        {\r\n            var lines = [],\r\n                firstLine = parseInt(this.getParam('first-line'))\r\n                ;\r\n\r\n            eachLine(code, function(line, index)\r\n            {\r\n                lines.push(index + firstLine);\r\n            });\r\n\r\n            return lines;\r\n        },\r\n\r\n        /**\r\n         * Determines if specified line number is in the highlighted list.\r\n         */\r\n        isLineHighlighted: function(lineNumber)\r\n        {\r\n            var list = this.getParam('highlight', []);\r\n\r\n            if (typeof(list) != 'object' && list.push == null)\r\n                list = [ list ];\r\n\r\n            return indexOf(list, lineNumber.toString()) != -1;\r\n        },\r\n\r\n        /**\r\n         * Generates HTML markup for a single line of code while determining alternating line style.\r\n         * @param {Integer} lineNumber\tLine number.\r\n         * @param {String} code Line\tHTML markup.\r\n         * @return {String}\t\t\t\tReturns HTML markup.\r\n         */\r\n        getLineHtml: function(lineIndex, lineNumber, code)\r\n        {\r\n            var classes = [\r\n                'line',\r\n                'number' + lineNumber,\r\n                'index' + lineIndex,\r\n                'alt' + (lineNumber % 2 == 0 ? 1 : 2).toString()\r\n            ];\r\n\r\n            if (this.isLineHighlighted(lineNumber))\r\n                classes.push('highlighted');\r\n\r\n            if (lineNumber == 0)\r\n                classes.push('break');\r\n\r\n            return '<div class=\"' + classes.join(' ') + '\">' + code + '</div>';\r\n        },\r\n\r\n        /**\r\n         * Generates HTML markup for line number column.\r\n         * @param {String} code\t\t\tComplete code HTML markup.\r\n         * @param {Array} lineNumbers\tCalculated line numbers.\r\n         * @return {String}\t\t\t\tReturns HTML markup.\r\n         */\r\n        getLineNumbersHtml: function(code, lineNumbers)\r\n        {\r\n            var html = '',\r\n                count = splitLines(code).length,\r\n                firstLine = parseInt(this.getParam('first-line')),\r\n                pad = this.getParam('pad-line-numbers')\r\n                ;\r\n\r\n            if (pad == true)\r\n                pad = (firstLine + count - 1).toString().length;\r\n            else if (isNaN(pad) == true)\r\n                pad = 0;\r\n\r\n            for (var i = 0; i < count; i++)\r\n            {\r\n                var lineNumber = lineNumbers ? lineNumbers[i] : firstLine + i,\r\n                    code = lineNumber == 0 ? sh.config.space : padNumber(lineNumber, pad)\r\n                    ;\r\n\r\n                html += this.getLineHtml(i, lineNumber, code);\r\n            }\r\n\r\n            return html;\r\n        },\r\n\r\n        /**\r\n         * Splits block of text into individual DIV lines.\r\n         * @param {String} code\t\t\tCode to highlight.\r\n         * @param {Array} lineNumbers\tCalculated line numbers.\r\n         * @return {String}\t\t\t\tReturns highlighted code in HTML form.\r\n         */\r\n        getCodeLinesHtml: function(html, lineNumbers)\r\n        {\r\n            html = trim(html);\r\n\r\n            var lines = splitLines(html),\r\n                padLength = this.getParam('pad-line-numbers'),\r\n                firstLine = parseInt(this.getParam('first-line')),\r\n                html = '',\r\n                brushName = this.getParam('brush')\r\n                ;\r\n\r\n            for (var i = 0; i < lines.length; i++)\r\n            {\r\n                var line = lines[i],\r\n                    indent = /^(&nbsp;|\\s)+/.exec(line),\r\n                    spaces = null,\r\n                    lineNumber = lineNumbers ? lineNumbers[i] : firstLine + i;\r\n                ;\r\n\r\n                if (indent != null)\r\n                {\r\n                    spaces = indent[0].toString();\r\n                    line = line.substr(spaces.length);\r\n                    spaces = spaces.replace(' ', sh.config.space);\r\n                }\r\n\r\n                line = trim(line);\r\n\r\n                if (line.length == 0)\r\n                    line = sh.config.space;\r\n\r\n                html += this.getLineHtml(\r\n                    i,\r\n                    lineNumber,\r\n                    (spaces != null ? '<code class=\"' + brushName + ' spaces\">' + spaces + '</code>' : '') + line\r\n                );\r\n            }\r\n\r\n            return html;\r\n        },\r\n\r\n        /**\r\n         * Returns HTML for the table title or empty string if title is null.\r\n         */\r\n        getTitleHtml: function(title)\r\n        {\r\n            return title ? '<caption>' + title + '</caption>' : '';\r\n        },\r\n\r\n        /**\r\n         * Finds all matches in the source code.\r\n         * @param {String} code\t\tSource code to process matches in.\r\n         * @param {Array} matches\tDiscovered regex matches.\r\n         * @return {String} Returns formatted HTML with processed mathes.\r\n         */\r\n        getMatchesHtml: function(code, matches)\r\n        {\r\n            var pos = 0,\r\n                result = '',\r\n                brushName = this.getParam('brush', '')\r\n                ;\r\n\r\n            function getBrushNameCss(match)\r\n            {\r\n                var result = match ? (match.brushName || brushName) : brushName;\r\n                return result ? result + ' ' : '';\r\n            };\r\n\r\n            // Finally, go through the final list of matches and pull the all\r\n            // together adding everything in between that isn't a match.\r\n            for (var i = 0; i < matches.length; i++)\r\n            {\r\n                var match = matches[i],\r\n                    matchBrushName\r\n                    ;\r\n\r\n                if (match === null || match.length === 0)\r\n                    continue;\r\n\r\n                matchBrushName = getBrushNameCss(match);\r\n\r\n                result += wrapLinesWithCode(code.substr(pos, match.index - pos), matchBrushName + 'plain')\r\n                    + wrapLinesWithCode(match.value, matchBrushName + match.css)\r\n                ;\r\n\r\n                pos = match.index + match.length + (match.offset || 0);\r\n            }\r\n\r\n            // don't forget to add whatever's remaining in the string\r\n            result += wrapLinesWithCode(code.substr(pos), getBrushNameCss() + 'plain');\r\n\r\n            return result;\r\n        },\r\n\r\n        /**\r\n         * Generates HTML markup for the whole syntax highlighter.\r\n         * @param {String} code Source code.\r\n         * @return {String} Returns HTML markup.\r\n         */\r\n        getHtml: function(code)\r\n        {\r\n            var html = '',\r\n                classes = [ 'syntaxhighlighter' ],\r\n                tabSize,\r\n                matches,\r\n                lineNumbers\r\n                ;\r\n\r\n            // process light mode\r\n            if (this.getParam('light') == true)\r\n                this.params.toolbar = this.params.gutter = false;\r\n\r\n            className = 'syntaxhighlighter';\r\n\r\n            if (this.getParam('collapse') == true)\r\n                classes.push('collapsed');\r\n\r\n            if ((gutter = this.getParam('gutter')) == false)\r\n                classes.push('nogutter');\r\n\r\n            // add custom user style name\r\n            classes.push(this.getParam('class-name'));\r\n\r\n            // add brush alias to the class name for custom CSS\r\n            classes.push(this.getParam('brush'));\r\n\r\n            code = trimFirstAndLastLines(code)\r\n                .replace(/\\r/g, ' ') // IE lets these buggers through\r\n            ;\r\n\r\n            tabSize = this.getParam('tab-size');\r\n\r\n            // replace tabs with spaces\r\n            code = this.getParam('smart-tabs') == true\r\n                ? processSmartTabs(code, tabSize)\r\n                : processTabs(code, tabSize)\r\n            ;\r\n\r\n            // unindent code by the common indentation\r\n            if (this.getParam('unindent'))\r\n                code = unindent(code);\r\n\r\n            if (gutter)\r\n                lineNumbers = this.figureOutLineNumbers(code);\r\n\r\n            // find matches in the code using brushes regex list\r\n            matches = this.findMatches(this.regexList, code);\r\n            // processes found matches into the html\r\n            html = this.getMatchesHtml(code, matches);\r\n            // finally, split all lines so that they wrap well\r\n            html = this.getCodeLinesHtml(html, lineNumbers);\r\n\r\n            // finally, process the links\r\n            if (this.getParam('auto-links'))\r\n                html = processUrls(html);\r\n\r\n            if (typeof(navigator) != 'undefined' && navigator.userAgent && navigator.userAgent.match(/MSIE/))\r\n                classes.push('ie');\r\n\r\n            html =\r\n                '<div id=\"' + getHighlighterId(this.id) + '\" class=\"' + classes.join(' ') + '\">'\r\n                    + (this.getParam('toolbar') ? sh.toolbar.getHtml(this) : '')\r\n                    + '<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\">'\r\n                    + this.getTitleHtml(this.getParam('title'))\r\n                    + '<tbody>'\r\n                    + '<tr>'\r\n                    + (gutter ? '<td class=\"gutter\">' + this.getLineNumbersHtml(code) + '</td>' : '')\r\n                    + '<td class=\"code\">'\r\n                    + '<div class=\"container\">'\r\n                    + html\r\n                    + '</div>'\r\n                    + '</td>'\r\n                    + '</tr>'\r\n                    + '</tbody>'\r\n                    + '</table>'\r\n                    + '</div>'\r\n            ;\r\n\r\n            return html;\r\n        },\r\n\r\n        /**\r\n         * Highlights the code and returns complete HTML.\r\n         * @param {String} code     Code to highlight.\r\n         * @return {Element}        Returns container DIV element with all markup.\r\n         */\r\n        getDiv: function(code)\r\n        {\r\n            if (code === null)\r\n                code = '';\r\n\r\n            this.code = code;\r\n\r\n            var div = this.create('div');\r\n\r\n            // create main HTML\r\n            div.innerHTML = this.getHtml(code);\r\n\r\n            // set up click handlers\r\n            if (this.getParam('toolbar'))\r\n                attachEvent(findElement(div, '.toolbar'), 'click', sh.toolbar.handler);\r\n\r\n            if (this.getParam('quick-code'))\r\n                attachEvent(findElement(div, '.code'), 'dblclick', quickCodeHandler);\r\n\r\n            return div;\r\n        },\r\n\r\n        /**\r\n         * Initializes the highlighter/brush.\r\n         *\r\n         * Constructor isn't used for initialization so that nothing executes during necessary\r\n         * `new SyntaxHighlighter.Highlighter()` call when setting up brush inheritence.\r\n         *\r\n         * @param {Hash} params Highlighter parameters.\r\n         */\r\n        init: function(params)\r\n        {\r\n            this.id = guid();\r\n\r\n            // register this instance in the highlighters list\r\n            storeHighlighter(this);\r\n\r\n            // local params take precedence over defaults\r\n            this.params = merge(sh.defaults, params || {})\r\n\r\n            // process light mode\r\n            if (this.getParam('light') == true)\r\n                this.params.toolbar = this.params.gutter = false;\r\n        },\r\n\r\n        /**\r\n         * Converts space separated list of keywords into a regular expression string.\r\n         * @param {String} str    Space separated keywords.\r\n         * @return {String}       Returns regular expression string.\r\n         */\r\n        getKeywords: function(str)\r\n        {\r\n            str = str\r\n                .replace(/^\\s+|\\s+$/g, '')\r\n                .replace(/\\s+/g, '|')\r\n            ;\r\n\r\n            return '\\\\b(?:' + str + ')\\\\b';\r\n        },\r\n\r\n        /**\r\n         * Makes a brush compatible with the `html-script` functionality.\r\n         * @param {Object} regexGroup Object containing `left` and `right` regular expressions.\r\n         */\r\n        forHtmlScript: function(regexGroup)\r\n        {\r\n            var regex = { 'end' : regexGroup.right.source };\r\n\r\n            if(regexGroup.eof)\r\n                regex.end = \"(?:(?:\" + regex.end + \")|$)\";\r\n\r\n            this.htmlScript = {\r\n                left : { regex: regexGroup.left, css: 'script' },\r\n                right : { regex: regexGroup.right, css: 'script' },\r\n                code : new XRegExp(\r\n                    \"(?<left>\" + regexGroup.left.source + \")\" +\r\n                        \"(?<code>.*?)\" +\r\n                        \"(?<right>\" + regex.end + \")\",\r\n                    \"sgi\"\r\n                )\r\n            };\r\n        }\r\n    }; // end of Highlighter\r\n\r\n    return sh;\r\n}(); // end of anonymous function\r\n\r\n// CommonJS\r\ntypeof(exports) != 'undefined' ? exports.SyntaxHighlighter = SyntaxHighlighter : null;\r\n\r\n;(function()\r\n{\r\n    // CommonJS\r\n    SyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\r\n\r\n    function Brush()\r\n    {\r\n        // Created by Peter Atoria @ http://iAtoria.com\r\n\r\n        var inits \t =  'class interface function package';\r\n\r\n        var keywords =\t'-Infinity ...rest Array as AS3 Boolean break case catch const continue Date decodeURI ' +\r\n                'decodeURIComponent default delete do dynamic each else encodeURI encodeURIComponent escape ' +\r\n                'extends false final finally flash_proxy for get if implements import in include Infinity ' +\r\n                'instanceof int internal is isFinite isNaN isXMLName label namespace NaN native new null ' +\r\n                'Null Number Object object_proxy override parseFloat parseInt private protected public ' +\r\n                'return set static String super switch this throw true try typeof uint undefined unescape ' +\r\n                'use void while with'\r\n            ;\r\n\r\n        this.regexList = [\r\n            { regex: SyntaxHighlighter.regexLib.singleLineCComments,\tcss: 'comments' },\t\t// one line comments\r\n            { regex: SyntaxHighlighter.regexLib.multiLineCComments,\t\tcss: 'comments' },\t\t// multiline comments\r\n            { regex: SyntaxHighlighter.regexLib.doubleQuotedString,\t\tcss: 'string' },\t\t// double quoted strings\r\n            { regex: SyntaxHighlighter.regexLib.singleQuotedString,\t\tcss: 'string' },\t\t// single quoted strings\r\n            { regex: /\\b([\\d]+(\\.[\\d]+)?|0x[a-f0-9]+)\\b/gi,\t\t\t\tcss: 'value' },\t\t\t// numbers\r\n            { regex: new RegExp(this.getKeywords(inits), 'gm'),\t\t\tcss: 'color3' },\t\t// initializations\r\n            { regex: new RegExp(this.getKeywords(keywords), 'gm'),\t\tcss: 'keyword' },\t\t// keywords\r\n            { regex: new RegExp('var', 'gm'),\t\t\t\t\t\t\tcss: 'variable' },\t\t// variable\r\n            { regex: new RegExp('trace', 'gm'),\t\t\t\t\t\t\tcss: 'color1' }\t\t\t// trace\r\n        ];\r\n\r\n        this.forHtmlScript(SyntaxHighlighter.regexLib.scriptScriptTags);\r\n    };\r\n\r\n    Brush.prototype\t= new SyntaxHighlighter.Highlighter();\r\n    Brush.aliases\t= ['actionscript3', 'as3'];\r\n\r\n    SyntaxHighlighter.brushes.AS3 = Brush;\r\n\r\n    // CommonJS\r\n    typeof(exports) != 'undefined' ? exports.Brush = Brush : null;\r\n})();\r\n\r\n;(function()\r\n{\r\n    // CommonJS\r\n    SyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\r\n\r\n    function Brush()\r\n    {\r\n        // AppleScript brush by David Chambers\r\n        // http://davidchambersdesign.com/\r\n        var keywords   = 'after before beginning continue copy each end every from return get global in local named of set some that the then times to where whose with without';\r\n        var ordinals   = 'first second third fourth fifth sixth seventh eighth ninth tenth last front back middle';\r\n        var specials   = 'activate add alias AppleScript ask attachment boolean class constant delete duplicate empty exists false id integer list make message modal modified new no paragraph pi properties quit real record remove rest result reveal reverse run running save string true word yes';\r\n\r\n        this.regexList = [\r\n\r\n            { regex: /(--|#).*$/gm,\r\n                css: 'comments' },\r\n\r\n            { regex: /\\(\\*(?:[\\s\\S]*?\\(\\*[\\s\\S]*?\\*\\))*[\\s\\S]*?\\*\\)/gm, // support nested comments\r\n                css: 'comments' },\r\n\r\n            { regex: /\"[\\s\\S]*?\"/gm,\r\n                css: 'string' },\r\n\r\n            { regex: /(?:,|:|¬|'s\\b|\\(|\\)|\\{|\\}|«|\\b\\w*»)/g,\r\n                css: 'color1' },\r\n\r\n            { regex: /(-)?(\\d)+(\\.(\\d)?)?(E\\+(\\d)+)?/g, // numbers\r\n                css: 'color1' },\r\n\r\n            { regex: /(?:&(amp;|gt;|lt;)?|=|� |>|<|≥|>=|≤|<=|\\*|\\+|-|\\/|÷|\\^)/g,\r\n                css: 'color2' },\r\n\r\n            { regex: /\\b(?:and|as|div|mod|not|or|return(?!\\s&)(ing)?|equals|(is(n't| not)? )?equal( to)?|does(n't| not) equal|(is(n't| not)? )?(greater|less) than( or equal( to)?)?|(comes|does(n't| not) come) (after|before)|is(n't| not)?( in)? (back|front) of|is(n't| not)? behind|is(n't| not)?( (in|contained by))?|does(n't| not) contain|contain(s)?|(start|begin|end)(s)? with|((but|end) )?(consider|ignor)ing|prop(erty)?|(a )?ref(erence)?( to)?|repeat (until|while|with)|((end|exit) )?repeat|((else|end) )?if|else|(end )?(script|tell|try)|(on )?error|(put )?into|(of )?(it|me)|its|my|with (timeout( of)?|transaction)|end (timeout|transaction))\\b/g,\r\n                css: 'keyword' },\r\n\r\n            { regex: /\\b\\d+(st|nd|rd|th)\\b/g, // ordinals\r\n                css: 'keyword' },\r\n\r\n            { regex: /\\b(?:about|above|against|around|at|below|beneath|beside|between|by|(apart|aside) from|(instead|out) of|into|on(to)?|over|since|thr(ough|u)|under)\\b/g,\r\n                css: 'color3' },\r\n\r\n            { regex: /\\b(?:adding folder items to|after receiving|choose( ((remote )?application|color|folder|from list|URL))?|clipboard info|set the clipboard to|(the )?clipboard|entire contents|display(ing| (alert|dialog|mode))?|document( (edited|file|nib name))?|file( (name|type))?|(info )?for|giving up after|(name )?extension|quoted form|return(ed)?|second(?! item)(s)?|list (disks|folder)|text item(s| delimiters)?|(Unicode )?text|(disk )?item(s)?|((current|list) )?view|((container|key) )?window|with (data|icon( (caution|note|stop))?|parameter(s)?|prompt|properties|seed|title)|case|diacriticals|hyphens|numeric strings|punctuation|white space|folder creation|application(s( folder)?| (processes|scripts position|support))?|((desktop )?(pictures )?|(documents|downloads|favorites|home|keychain|library|movies|music|public|scripts|sites|system|users|utilities|workflows) )folder|desktop|Folder Action scripts|font(s| panel)?|help|internet plugins|modem scripts|(system )?preferences|printer descriptions|scripting (additions|components)|shared (documents|libraries)|startup (disk|items)|temporary items|trash|on server|in AppleTalk zone|((as|long|short) )?user name|user (ID|locale)|(with )?password|in (bundle( with identifier)?|directory)|(close|open for) access|read|write( permission)?|(g|s)et eof|using( delimiters)?|starting at|default (answer|button|color|country code|entr(y|ies)|identifiers|items|name|location|script editor)|hidden( answer)?|open(ed| (location|untitled))?|error (handling|reporting)|(do( shell)?|load|run|store) script|administrator privileges|altering line endings|get volume settings|(alert|boot|input|mount|output|set) volume|output muted|(fax|random )?number|round(ing)?|up|down|toward zero|to nearest|as taught in school|system (attribute|info)|((AppleScript( Studio)?|system) )?version|(home )?directory|(IPv4|primary Ethernet) address|CPU (type|speed)|physical memory|time (stamp|to GMT)|replacing|ASCII (character|number)|localized string|from table|offset|summarize|beep|delay|say|(empty|multiple) selections allowed|(of|preferred) type|invisibles|showing( package contents)?|editable URL|(File|FTP|News|Media|Web) [Ss]ervers|Telnet hosts|Directory services|Remote applications|waiting until completion|saving( (in|to))?|path (for|to( (((current|frontmost) )?application|resource))?)|POSIX (file|path)|(background|RGB) color|(OK|cancel) button name|cancel button|button(s)?|cubic ((centi)?met(re|er)s|yards|feet|inches)|square ((kilo)?met(re|er)s|miles|yards|feet)|(centi|kilo)?met(re|er)s|miles|yards|feet|inches|lit(re|er)s|gallons|quarts|(kilo)?grams|ounces|pounds|degrees (Celsius|Fahrenheit|Kelvin)|print( (dialog|settings))?|clos(e(able)?|ing)|(de)?miniaturized|miniaturizable|zoom(ed|able)|attribute run|action (method|property|title)|phone|email|((start|end)ing|home) page|((birth|creation|current|custom|modification) )?date|((((phonetic )?(first|last|middle))|computer|host|maiden|related) |nick)?name|aim|icq|jabber|msn|yahoo|address(es)?|save addressbook|should enable action|city|country( code)?|formatte(r|d address)|(palette )?label|state|street|zip|AIM [Hh]andle(s)?|my card|select(ion| all)?|unsaved|(alpha )?value|entr(y|ies)|group|(ICQ|Jabber|MSN) handle|person|people|company|department|icon image|job title|note|organization|suffix|vcard|url|copies|collating|pages (across|down)|request print time|target( printer)?|((GUI Scripting|Script menu) )?enabled|show Computer scripts|(de)?activated|awake from nib|became (key|main)|call method|of (class|object)|center|clicked toolbar item|closed|for document|exposed|(can )?hide|idle|keyboard (down|up)|event( (number|type))?|launch(ed)?|load (image|movie|nib|sound)|owner|log|mouse (down|dragged|entered|exited|moved|up)|move|column|localization|resource|script|register|drag (info|types)|resigned (active|key|main)|resiz(e(d)?|able)|right mouse (down|dragged|up)|scroll wheel|(at )?index|should (close|open( untitled)?|quit( after last window closed)?|zoom)|((proposed|screen) )?bounds|show(n)?|behind|in front of|size (mode|to fit)|update(d| toolbar item)?|was (hidden|miniaturized)|will (become active|close|finish launching|hide|miniaturize|move|open|quit|(resign )?active|((maximum|minimum|proposed) )?size|show|zoom)|bundle|data source|movie|pasteboard|sound|tool(bar| tip)|(color|open|save) panel|coordinate system|frontmost|main( (bundle|menu|window))?|((services|(excluded from )?windows) )?menu|((executable|frameworks|resource|scripts|shared (frameworks|support)) )?path|(selected item )?identifier|data|content(s| view)?|character(s)?|click count|(command|control|option|shift) key down|context|delta (x|y|z)|key( code)?|location|pressure|unmodified characters|types|(first )?responder|playing|(allowed|selectable) identifiers|allows customization|(auto saves )?configuration|visible|image( name)?|menu form representation|tag|user(-| )defaults|associated file name|(auto|needs) display|current field editor|floating|has (resize indicator|shadow)|hides when deactivated|level|minimized (image|title)|opaque|position|release when closed|sheet|title(d)?)\\b/g,\r\n                css: 'color3' },\r\n\r\n            { regex: new RegExp(this.getKeywords(specials), 'gm'), css: 'color3' },\r\n            { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' },\r\n            { regex: new RegExp(this.getKeywords(ordinals), 'gm'), css: 'keyword' }\r\n        ];\r\n    };\r\n\r\n    Brush.prototype = new SyntaxHighlighter.Highlighter();\r\n    Brush.aliases = ['applescript'];\r\n\r\n    SyntaxHighlighter.brushes.AppleScript = Brush;\r\n\r\n    // CommonJS\r\n    typeof(exports) != 'undefined' ? exports.Brush = Brush : null;\r\n})();\r\n;(function()\r\n{\r\n\t// CommonJS\r\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\r\n\r\n\tfunction Brush()\r\n\t{\r\n\t\tvar keywords =\t'if fi then elif else for do done until while break continue case esac function return in eq ne ge le';\r\n\t\tvar commands =  'alias apropos awk basename bash bc bg builtin bzip2 cal cat cd cfdisk chgrp chmod chown chroot' +\r\n\t\t\t\t\t\t'cksum clear cmp comm command cp cron crontab csplit cut date dc dd ddrescue declare df ' +\r\n\t\t\t\t\t\t'diff diff3 dig dir dircolors dirname dirs du echo egrep eject enable env ethtool eval ' +\r\n\t\t\t\t\t\t'exec exit expand export expr false fdformat fdisk fg fgrep file find fmt fold format ' +\r\n\t\t\t\t\t\t'free fsck ftp gawk getopts grep groups gzip hash head history hostname id ifconfig ' +\r\n\t\t\t\t\t\t'import install join kill less let ln local locate logname logout look lpc lpr lprint ' +\r\n\t\t\t\t\t\t'lprintd lprintq lprm ls lsof make man mkdir mkfifo mkisofs mknod more mount mtools ' +\r\n\t\t\t\t\t\t'mv netstat nice nl nohup nslookup open op passwd paste pathchk ping popd pr printcap ' +\r\n\t\t\t\t\t\t'printenv printf ps pushd pwd quota quotacheck quotactl ram rcp read readonly renice ' +\r\n\t\t\t\t\t\t'remsync rm rmdir rsync screen scp sdiff sed select seq set sftp shift shopt shutdown ' +\r\n\t\t\t\t\t\t'sleep sort source split ssh strace su sudo sum symlink sync tail tar tee test time ' +\r\n\t\t\t\t\t\t'times touch top traceroute trap tr true tsort tty type ulimit umask umount unalias ' +\r\n\t\t\t\t\t\t'uname unexpand uniq units unset unshar useradd usermod users uuencode uudecode v vdir ' +\r\n\t\t\t\t\t\t'vi watch wc whereis which who whoami Wget xargs yes'\r\n\t\t\t\t\t\t;\r\n\r\n\t\tthis.regexList = [\r\n\t\t\t{ regex: /^#!.*$/gm,\t\t\t\t\t\t\t\t\t\t\tcss: 'preprocessor bold' },\r\n\t\t\t{ regex: /\\/[\\w-\\/]+/gm,\t\t\t\t\t\t\t\t\t\tcss: 'plain' },\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleLinePerlComments,\t\tcss: 'comments' },\t\t// one line comments\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.doubleQuotedString,\t\t\tcss: 'string' },\t\t// double quoted strings\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleQuotedString,\t\t\tcss: 'string' },\t\t// single quoted strings\r\n\t\t\t{ regex: new RegExp(this.getKeywords(keywords), 'gm'),\t\t\tcss: 'keyword' },\t\t// keywords\r\n\t\t\t{ regex: new RegExp(this.getKeywords(commands), 'gm'),\t\t\tcss: 'functions' }\t\t// commands\r\n\t\t\t];\r\n\t}\r\n\r\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\r\n\tBrush.aliases\t= ['bash', 'shell', 'sh'];\r\n\r\n\tSyntaxHighlighter.brushes.Bash = Brush;\r\n\r\n\t// CommonJS\r\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\r\n})();\r\n;(function()\r\n{\r\n\t// CommonJS\r\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\r\n\r\n\tfunction Brush()\r\n\t{\r\n\t\t// Contributed by Jen\r\n\t\t// http://www.jensbits.com/2009/05/14/coldfusion-brush-for-syntaxhighlighter-plus\r\n\t\r\n\t\tvar funcs\t=\t'Abs ACos AddSOAPRequestHeader AddSOAPResponseHeader AjaxLink AjaxOnLoad ArrayAppend ArrayAvg ArrayClear ArrayDeleteAt ' + \r\n\t\t\t\t\t\t'ArrayInsertAt ArrayIsDefined ArrayIsEmpty ArrayLen ArrayMax ArrayMin ArraySet ArraySort ArraySum ArraySwap ArrayToList ' + \r\n\t\t\t\t\t\t'Asc ASin Atn BinaryDecode BinaryEncode BitAnd BitMaskClear BitMaskRead BitMaskSet BitNot BitOr BitSHLN BitSHRN BitXor ' + \r\n\t\t\t\t\t\t'Ceiling CharsetDecode CharsetEncode Chr CJustify Compare CompareNoCase Cos CreateDate CreateDateTime CreateObject ' + \r\n\t\t\t\t\t\t'CreateODBCDate CreateODBCDateTime CreateODBCTime CreateTime CreateTimeSpan CreateUUID DateAdd DateCompare DateConvert ' + \r\n\t\t\t\t\t\t'DateDiff DateFormat DatePart Day DayOfWeek DayOfWeekAsString DayOfYear DaysInMonth DaysInYear DE DecimalFormat DecrementValue ' + \r\n\t\t\t\t\t\t'Decrypt DecryptBinary DeleteClientVariable DeserializeJSON DirectoryExists DollarFormat DotNetToCFType Duplicate Encrypt ' + \r\n\t\t\t\t\t\t'EncryptBinary Evaluate Exp ExpandPath FileClose FileCopy FileDelete FileExists FileIsEOF FileMove FileOpen FileRead ' + \r\n\t\t\t\t\t\t'FileReadBinary FileReadLine FileSetAccessMode FileSetAttribute FileSetLastModified FileWrite Find FindNoCase FindOneOf ' + \r\n\t\t\t\t\t\t'FirstDayOfMonth Fix FormatBaseN GenerateSecretKey GetAuthUser GetBaseTagData GetBaseTagList GetBaseTemplatePath ' + \r\n\t\t\t\t\t\t'GetClientVariablesList GetComponentMetaData GetContextRoot GetCurrentTemplatePath GetDirectoryFromPath GetEncoding ' + \r\n\t\t\t\t\t\t'GetException GetFileFromPath GetFileInfo GetFunctionList GetGatewayHelper GetHttpRequestData GetHttpTimeString ' + \r\n\t\t\t\t\t\t'GetK2ServerDocCount GetK2ServerDocCountLimit GetLocale GetLocaleDisplayName GetLocalHostIP GetMetaData GetMetricData ' + \r\n\t\t\t\t\t\t'GetPageContext GetPrinterInfo GetProfileSections GetProfileString GetReadableImageFormats GetSOAPRequest GetSOAPRequestHeader ' + \r\n\t\t\t\t\t\t'GetSOAPResponse GetSOAPResponseHeader GetTempDirectory GetTempFile GetTemplatePath GetTickCount GetTimeZoneInfo GetToken ' + \r\n\t\t\t\t\t\t'GetUserRoles GetWriteableImageFormats Hash Hour HTMLCodeFormat HTMLEditFormat IIf ImageAddBorder ImageBlur ImageClearRect ' + \r\n\t\t\t\t\t\t'ImageCopy ImageCrop ImageDrawArc ImageDrawBeveledRect ImageDrawCubicCurve ImageDrawLine ImageDrawLines ImageDrawOval ' + \r\n\t\t\t\t\t\t'ImageDrawPoint ImageDrawQuadraticCurve ImageDrawRect ImageDrawRoundRect ImageDrawText ImageFlip ImageGetBlob ImageGetBufferedImage ' + \r\n\t\t\t\t\t\t'ImageGetEXIFTag ImageGetHeight ImageGetIPTCTag ImageGetWidth ImageGrayscale ImageInfo ImageNegative ImageNew ImageOverlay ImagePaste ' + \r\n\t\t\t\t\t\t'ImageRead ImageReadBase64 ImageResize ImageRotate ImageRotateDrawingAxis ImageScaleToFit ImageSetAntialiasing ImageSetBackgroundColor ' + \r\n\t\t\t\t\t\t'ImageSetDrawingColor ImageSetDrawingStroke ImageSetDrawingTransparency ImageSharpen ImageShear ImageShearDrawingAxis ImageTranslate ' + \r\n\t\t\t\t\t\t'ImageTranslateDrawingAxis ImageWrite ImageWriteBase64 ImageXORDrawingMode IncrementValue InputBaseN Insert Int IsArray IsBinary ' + \r\n\t\t\t\t\t\t'IsBoolean IsCustomFunction IsDate IsDDX IsDebugMode IsDefined IsImage IsImageFile IsInstanceOf IsJSON IsLeapYear IsLocalHost ' + \r\n\t\t\t\t\t\t'IsNumeric IsNumericDate IsObject IsPDFFile IsPDFObject IsQuery IsSimpleValue IsSOAPRequest IsStruct IsUserInAnyRole IsUserInRole ' + \r\n\t\t\t\t\t\t'IsUserLoggedIn IsValid IsWDDX IsXML IsXmlAttribute IsXmlDoc IsXmlElem IsXmlNode IsXmlRoot JavaCast JSStringFormat LCase Left Len ' + \r\n\t\t\t\t\t\t'ListAppend ListChangeDelims ListContains ListContainsNoCase ListDeleteAt ListFind ListFindNoCase ListFirst ListGetAt ListInsertAt ' + \r\n\t\t\t\t\t\t'ListLast ListLen ListPrepend ListQualify ListRest ListSetAt ListSort ListToArray ListValueCount ListValueCountNoCase LJustify Log ' + \r\n\t\t\t\t\t\t'Log10 LSCurrencyFormat LSDateFormat LSEuroCurrencyFormat LSIsCurrency LSIsDate LSIsNumeric LSNumberFormat LSParseCurrency LSParseDateTime ' + \r\n\t\t\t\t\t\t'LSParseEuroCurrency LSParseNumber LSTimeFormat LTrim Max Mid Min Minute Month MonthAsString Now NumberFormat ParagraphFormat ParseDateTime ' + \r\n\t\t\t\t\t\t'Pi PrecisionEvaluate PreserveSingleQuotes Quarter QueryAddColumn QueryAddRow QueryConvertForGrid QueryNew QuerySetCell QuotedValueList Rand ' + \r\n\t\t\t\t\t\t'Randomize RandRange REFind REFindNoCase ReleaseComObject REMatch REMatchNoCase RemoveChars RepeatString Replace ReplaceList ReplaceNoCase ' + \r\n\t\t\t\t\t\t'REReplace REReplaceNoCase Reverse Right RJustify Round RTrim Second SendGatewayMessage SerializeJSON SetEncoding SetLocale SetProfileString ' + \r\n\t\t\t\t\t\t'SetVariable Sgn Sin Sleep SpanExcluding SpanIncluding Sqr StripCR StructAppend StructClear StructCopy StructCount StructDelete StructFind ' + \r\n\t\t\t\t\t\t'StructFindKey StructFindValue StructGet StructInsert StructIsEmpty StructKeyArray StructKeyExists StructKeyList StructKeyList StructNew ' + \r\n\t\t\t\t\t\t'StructSort StructUpdate Tan TimeFormat ToBase64 ToBinary ToScript ToString Trim UCase URLDecode URLEncodedFormat URLSessionFormat Val ' + \r\n\t\t\t\t\t\t'ValueList VerifyClient Week Wrap Wrap WriteOutput XmlChildPos XmlElemNew XmlFormat XmlGetNodeType XmlNew XmlParse XmlSearch XmlTransform ' + \r\n\t\t\t\t\t\t'XmlValidate Year YesNoFormat';\r\n\r\n\t\tvar keywords =\t'cfabort cfajaximport cfajaxproxy cfapplet cfapplication cfargument cfassociate cfbreak cfcache cfcalendar ' + \r\n\t\t\t\t\t\t'cfcase cfcatch cfchart cfchartdata cfchartseries cfcol cfcollection cfcomponent cfcontent cfcookie cfdbinfo ' + \r\n\t\t\t\t\t\t'cfdefaultcase cfdirectory cfdiv cfdocument cfdocumentitem cfdocumentsection cfdump cfelse cfelseif cferror ' + \r\n\t\t\t\t\t\t'cfexchangecalendar cfexchangeconnection cfexchangecontact cfexchangefilter cfexchangemail cfexchangetask ' + \r\n\t\t\t\t\t\t'cfexecute cfexit cffeed cffile cfflush cfform cfformgroup cfformitem cfftp cffunction cfgrid cfgridcolumn ' + \r\n\t\t\t\t\t\t'cfgridrow cfgridupdate cfheader cfhtmlhead cfhttp cfhttpparam cfif cfimage cfimport cfinclude cfindex ' + \r\n\t\t\t\t\t\t'cfinput cfinsert cfinterface cfinvoke cfinvokeargument cflayout cflayoutarea cfldap cflocation cflock cflog ' + \r\n\t\t\t\t\t\t'cflogin cfloginuser cflogout cfloop cfmail cfmailparam cfmailpart cfmenu cfmenuitem cfmodule cfNTauthenticate ' + \r\n\t\t\t\t\t\t'cfobject cfobjectcache cfoutput cfparam cfpdf cfpdfform cfpdfformparam cfpdfparam cfpdfsubform cfpod cfpop ' + \r\n\t\t\t\t\t\t'cfpresentation cfpresentationslide cfpresenter cfprint cfprocessingdirective cfprocparam cfprocresult ' + \r\n\t\t\t\t\t\t'cfproperty cfquery cfqueryparam cfregistry cfreport cfreportparam cfrethrow cfreturn cfsavecontent cfschedule ' + \r\n\t\t\t\t\t\t'cfscript cfsearch cfselect cfset cfsetting cfsilent cfslider cfsprydataset cfstoredproc cfswitch cftable ' + \r\n\t\t\t\t\t\t'cftextarea cfthread cfthrow cftimer cftooltip cftrace cftransaction cftree cftreeitem cftry cfupdate cfwddx ' + \r\n\t\t\t\t\t\t'cfwindow cfxml cfzip cfzipparam';\r\n\r\n\t\tvar operators =\t'all and any between cross in join like not null or outer some';\r\n\r\n\t\tthis.regexList = [\r\n\t\t\t{ regex: new RegExp('--(.*)$', 'gm'),\t\t\t\t\t\tcss: 'comments' },  // one line and multiline comments\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.xmlComments,\t\t\tcss: 'comments' },    // single quoted strings\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.doubleQuotedString,\t\tcss: 'string' },    // double quoted strings\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleQuotedString,\t\tcss: 'string' },    // single quoted strings\r\n\t\t\t{ regex: new RegExp(this.getKeywords(funcs), 'gmi'),\t\tcss: 'functions' }, // functions\r\n\t\t\t{ regex: new RegExp(this.getKeywords(operators), 'gmi'),\tcss: 'color1' },    // operators and such\r\n\t\t\t{ regex: new RegExp(this.getKeywords(keywords), 'gmi'),\t\tcss: 'keyword' }    // keyword\r\n\t\t\t];\r\n\t}\r\n\r\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\r\n\tBrush.aliases\t= ['coldfusion','cf'];\r\n\t\r\n\tSyntaxHighlighter.brushes.ColdFusion = Brush;\r\n\r\n\t// CommonJS\r\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\r\n})();\r\n;(function()\r\n{\r\n\t// CommonJS\r\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\r\n\r\n\tfunction Brush()\r\n\t{\r\n\t\t// Copyright 2006 Shin, YoungJin\r\n\t\r\n\t\tvar datatypes =\t'ATOM BOOL BOOLEAN BYTE CHAR COLORREF DWORD DWORDLONG DWORD_PTR ' +\r\n\t\t\t\t\t\t'DWORD32 DWORD64 FLOAT HACCEL HALF_PTR HANDLE HBITMAP HBRUSH ' +\r\n\t\t\t\t\t\t'HCOLORSPACE HCONV HCONVLIST HCURSOR HDC HDDEDATA HDESK HDROP HDWP ' +\r\n\t\t\t\t\t\t'HENHMETAFILE HFILE HFONT HGDIOBJ HGLOBAL HHOOK HICON HINSTANCE HKEY ' +\r\n\t\t\t\t\t\t'HKL HLOCAL HMENU HMETAFILE HMODULE HMONITOR HPALETTE HPEN HRESULT ' +\r\n\t\t\t\t\t\t'HRGN HRSRC HSZ HWINSTA HWND INT INT_PTR INT32 INT64 LANGID LCID LCTYPE ' +\r\n\t\t\t\t\t\t'LGRPID LONG LONGLONG LONG_PTR LONG32 LONG64 LPARAM LPBOOL LPBYTE LPCOLORREF ' +\r\n\t\t\t\t\t\t'LPCSTR LPCTSTR LPCVOID LPCWSTR LPDWORD LPHANDLE LPINT LPLONG LPSTR LPTSTR ' +\r\n\t\t\t\t\t\t'LPVOID LPWORD LPWSTR LRESULT PBOOL PBOOLEAN PBYTE PCHAR PCSTR PCTSTR PCWSTR ' +\r\n\t\t\t\t\t\t'PDWORDLONG PDWORD_PTR PDWORD32 PDWORD64 PFLOAT PHALF_PTR PHANDLE PHKEY PINT ' +\r\n\t\t\t\t\t\t'PINT_PTR PINT32 PINT64 PLCID PLONG PLONGLONG PLONG_PTR PLONG32 PLONG64 POINTER_32 ' +\r\n\t\t\t\t\t\t'POINTER_64 PSHORT PSIZE_T PSSIZE_T PSTR PTBYTE PTCHAR PTSTR PUCHAR PUHALF_PTR ' +\r\n\t\t\t\t\t\t'PUINT PUINT_PTR PUINT32 PUINT64 PULONG PULONGLONG PULONG_PTR PULONG32 PULONG64 ' +\r\n\t\t\t\t\t\t'PUSHORT PVOID PWCHAR PWORD PWSTR SC_HANDLE SC_LOCK SERVICE_STATUS_HANDLE SHORT ' +\r\n\t\t\t\t\t\t'SIZE_T SSIZE_T TBYTE TCHAR UCHAR UHALF_PTR UINT UINT_PTR UINT32 UINT64 ULONG ' +\r\n\t\t\t\t\t\t'ULONGLONG ULONG_PTR ULONG32 ULONG64 USHORT USN VOID WCHAR WORD WPARAM WPARAM WPARAM ' +\r\n\t\t\t\t\t\t'char bool short int __int32 __int64 __int8 __int16 long float double __wchar_t ' +\r\n\t\t\t\t\t\t'clock_t _complex _dev_t _diskfree_t div_t ldiv_t _exception _EXCEPTION_POINTERS ' +\r\n\t\t\t\t\t\t'FILE _finddata_t _finddatai64_t _wfinddata_t _wfinddatai64_t __finddata64_t ' +\r\n\t\t\t\t\t\t'__wfinddata64_t _FPIEEE_RECORD fpos_t _HEAPINFO _HFILE lconv intptr_t ' +\r\n\t\t\t\t\t\t'jmp_buf mbstate_t _off_t _onexit_t _PNH ptrdiff_t _purecall_handler ' +\r\n\t\t\t\t\t\t'sig_atomic_t size_t _stat __stat64 _stati64 terminate_function ' +\r\n\t\t\t\t\t\t'time_t __time64_t _timeb __timeb64 tm uintptr_t _utimbuf ' +\r\n\t\t\t\t\t\t'va_list wchar_t wctrans_t wctype_t wint_t signed';\r\n\r\n\t\tvar keywords =\t'auto break case catch class const decltype __finally __exception __try ' +\r\n\t\t\t\t\t\t'const_cast continue private public protected __declspec ' +\r\n\t\t\t\t\t\t'default delete deprecated dllexport dllimport do dynamic_cast ' +\r\n\t\t\t\t\t\t'else enum explicit extern if for friend goto inline ' +\r\n\t\t\t\t\t\t'mutable naked namespace new noinline noreturn nothrow ' +\r\n\t\t\t\t\t\t'register reinterpret_cast return selectany ' +\r\n\t\t\t\t\t\t'sizeof static static_cast struct switch template this ' +\r\n\t\t\t\t\t\t'thread throw true false try typedef typeid typename union ' +\r\n\t\t\t\t\t\t'using uuid virtual void volatile whcar_t while';\r\n\t\t\t\t\t\r\n\t\tvar functions =\t'assert isalnum isalpha iscntrl isdigit isgraph islower isprint' +\r\n\t\t\t\t\t\t'ispunct isspace isupper isxdigit tolower toupper errno localeconv ' +\r\n\t\t\t\t\t\t'setlocale acos asin atan atan2 ceil cos cosh exp fabs floor fmod ' +\r\n\t\t\t\t\t\t'frexp ldexp log log10 modf pow sin sinh sqrt tan tanh jmp_buf ' +\r\n\t\t\t\t\t\t'longjmp setjmp raise signal sig_atomic_t va_arg va_end va_start ' +\r\n\t\t\t\t\t\t'clearerr fclose feof ferror fflush fgetc fgetpos fgets fopen ' +\r\n\t\t\t\t\t\t'fprintf fputc fputs fread freopen fscanf fseek fsetpos ftell ' +\r\n\t\t\t\t\t\t'fwrite getc getchar gets perror printf putc putchar puts remove ' +\r\n\t\t\t\t\t\t'rename rewind scanf setbuf setvbuf sprintf sscanf tmpfile tmpnam ' +\r\n\t\t\t\t\t\t'ungetc vfprintf vprintf vsprintf abort abs atexit atof atoi atol ' +\r\n\t\t\t\t\t\t'bsearch calloc div exit free getenv labs ldiv malloc mblen mbstowcs ' +\r\n\t\t\t\t\t\t'mbtowc qsort rand realloc srand strtod strtol strtoul system ' +\r\n\t\t\t\t\t\t'wcstombs wctomb memchr memcmp memcpy memmove memset strcat strchr ' +\r\n\t\t\t\t\t\t'strcmp strcoll strcpy strcspn strerror strlen strncat strncmp ' +\r\n\t\t\t\t\t\t'strncpy strpbrk strrchr strspn strstr strtok strxfrm asctime ' +\r\n\t\t\t\t\t\t'clock ctime difftime gmtime localtime mktime strftime time';\r\n\r\n\t\tthis.regexList = [\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleLineCComments,\tcss: 'comments' },\t\t\t// one line comments\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.multiLineCComments,\t\tcss: 'comments' },\t\t\t// multiline comments\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.doubleQuotedString,\t\tcss: 'string' },\t\t\t// strings\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleQuotedString,\t\tcss: 'string' },\t\t\t// strings\r\n\t\t\t{ regex: /^ *#.*/gm,\t\t\t\t\t\t\t\t\t\tcss: 'preprocessor' },\r\n\t\t\t{ regex: new RegExp(this.getKeywords(datatypes), 'gm'),\t\tcss: 'color1 bold' },\r\n\t\t\t{ regex: new RegExp(this.getKeywords(functions), 'gm'),\t\tcss: 'functions bold' },\r\n\t\t\t{ regex: new RegExp(this.getKeywords(keywords), 'gm'),\t\tcss: 'keyword bold' }\r\n\t\t\t];\r\n\t};\r\n\r\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\r\n\tBrush.aliases\t= ['cpp', 'c'];\r\n\r\n\tSyntaxHighlighter.brushes.Cpp = Brush;\r\n\r\n\t// CommonJS\r\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\r\n})();\r\n;(function()\r\n{\r\n\t// CommonJS\r\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\r\n\r\n\tfunction Brush()\r\n\t{\r\n\t\tvar keywords =\t'abstract as base bool break byte case catch char checked class const ' +\r\n\t\t\t\t\t\t'continue decimal default delegate do double else enum event explicit volatile ' +\r\n\t\t\t\t\t\t'extern false finally fixed float for foreach get goto if implicit in int ' +\r\n\t\t\t\t\t\t'interface internal is lock long namespace new null object operator out ' +\r\n\t\t\t\t\t\t'override params private protected public readonly ref return sbyte sealed set ' +\r\n\t\t\t\t\t\t'short sizeof stackalloc static string struct switch this throw true try ' +\r\n\t\t\t\t\t\t'typeof uint ulong unchecked unsafe ushort using virtual void while var ' +\r\n\t\t\t\t\t\t'from group by into select let where orderby join on equals ascending descending';\r\n\r\n\t\tfunction fixComments(match, regexInfo)\r\n\t\t{\r\n\t\t\tvar css = (match[0].indexOf(\"///\") == 0)\r\n\t\t\t\t? 'color1'\r\n\t\t\t\t: 'comments'\r\n\t\t\t\t;\r\n\t\t\t\r\n\t\t\treturn [new SyntaxHighlighter.Match(match[0], match.index, css)];\r\n\t\t}\r\n\r\n\t\tthis.regexList = [\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleLineCComments,\tfunc : fixComments },\t\t// one line comments\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.multiLineCComments,\t\tcss: 'comments' },\t\t\t// multiline comments\r\n\t\t\t{ regex: /@\"(?:[^\"]|\"\")*\"/g,\t\t\t\t\t\t\t\tcss: 'string' },\t\t\t// @-quoted strings\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.doubleQuotedString,\t\tcss: 'string' },\t\t\t// strings\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleQuotedString,\t\tcss: 'string' },\t\t\t// strings\r\n\t\t\t{ regex: /^\\s*#.*/gm,\t\t\t\t\t\t\t\t\t\tcss: 'preprocessor' },\t\t// preprocessor tags like #region and #endregion\r\n\t\t\t{ regex: new RegExp(this.getKeywords(keywords), 'gm'),\t\tcss: 'keyword' },\t\t\t// c# keyword\r\n\t\t\t{ regex: /\\bpartial(?=\\s+(?:class|interface|struct)\\b)/g,\tcss: 'keyword' },\t\t\t// contextual keyword: 'partial'\r\n\t\t\t{ regex: /\\byield(?=\\s+(?:return|break)\\b)/g,\t\t\t\tcss: 'keyword' }\t\t\t// contextual keyword: 'yield'\r\n\t\t\t];\r\n\t\t\r\n\t\tthis.forHtmlScript(SyntaxHighlighter.regexLib.aspScriptTags);\r\n\t};\r\n\r\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\r\n\tBrush.aliases\t= ['c#', 'c-sharp', 'csharp'];\r\n\r\n\tSyntaxHighlighter.brushes.CSharp = Brush;\r\n\r\n\t// CommonJS\r\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\r\n})();\r\n;(function()\r\n{\r\n\t// CommonJS\r\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\r\n\r\n\tfunction Brush()\r\n\t{\r\n\t\tfunction getKeywordsCSS(str)\r\n\t\t{\r\n\t\t\treturn '\\\\b([a-z_]|)' + str.replace(/ /g, '(?=:)\\\\b|\\\\b([a-z_\\\\*]|\\\\*|)') + '(?=:)\\\\b';\r\n\t\t};\r\n\t\r\n\t\tfunction getValuesCSS(str)\r\n\t\t{\r\n\t\t\treturn '\\\\b' + str.replace(/ /g, '(?!-)(?!:)\\\\b|\\\\b()') + '\\:\\\\b';\r\n\t\t};\r\n\r\n\t\tvar keywords =\t'ascent azimuth background-attachment background-color background-image background-position ' +\r\n\t\t\t\t\t\t'background-repeat background baseline bbox border-collapse border-color border-spacing border-style border-top ' +\r\n\t\t\t\t\t\t'border-right border-bottom border-left border-top-color border-right-color border-bottom-color border-left-color ' +\r\n\t\t\t\t\t\t'border-top-style border-right-style border-bottom-style border-left-style border-top-width border-right-width ' +\r\n\t\t\t\t\t\t'border-bottom-width border-left-width border-width border bottom cap-height caption-side centerline clear clip color ' +\r\n\t\t\t\t\t\t'content counter-increment counter-reset cue-after cue-before cue cursor definition-src descent direction display ' +\r\n\t\t\t\t\t\t'elevation empty-cells float font-size-adjust font-family font-size font-stretch font-style font-variant font-weight font ' +\r\n\t\t\t\t\t\t'height left letter-spacing line-height list-style-image list-style-position list-style-type list-style margin-top ' +\r\n\t\t\t\t\t\t'margin-right margin-bottom margin-left margin marker-offset marks mathline max-height max-width min-height min-width orphans ' +\r\n\t\t\t\t\t\t'outline-color outline-style outline-width outline overflow padding-top padding-right padding-bottom padding-left padding page ' +\r\n\t\t\t\t\t\t'page-break-after page-break-before page-break-inside pause pause-after pause-before pitch pitch-range play-during position ' +\r\n\t\t\t\t\t\t'quotes right richness size slope src speak-header speak-numeral speak-punctuation speak speech-rate stemh stemv stress ' +\r\n\t\t\t\t\t\t'table-layout text-align top text-decoration text-indent text-shadow text-transform unicode-bidi unicode-range units-per-em ' +\r\n\t\t\t\t\t\t'vertical-align visibility voice-family volume white-space widows width widths word-spacing x-height z-index';\r\n\r\n\t\tvar values =\t'above absolute all always aqua armenian attr aural auto avoid baseline behind below bidi-override black blink block blue bold bolder '+\r\n\t\t\t\t\t\t'both bottom braille capitalize caption center center-left center-right circle close-quote code collapse compact condensed '+\r\n\t\t\t\t\t\t'continuous counter counters crop cross crosshair cursive dashed decimal decimal-leading-zero default digits disc dotted double '+\r\n\t\t\t\t\t\t'embed embossed e-resize expanded extra-condensed extra-expanded fantasy far-left far-right fast faster fixed format fuchsia '+\r\n\t\t\t\t\t\t'gray green groove handheld hebrew help hidden hide high higher icon inline-table inline inset inside invert italic '+\r\n\t\t\t\t\t\t'justify landscape large larger left-side left leftwards level lighter lime line-through list-item local loud lower-alpha '+\r\n\t\t\t\t\t\t'lowercase lower-greek lower-latin lower-roman lower low ltr marker maroon medium message-box middle mix move narrower '+\r\n\t\t\t\t\t\t'navy ne-resize no-close-quote none no-open-quote no-repeat normal nowrap n-resize nw-resize oblique olive once open-quote outset '+\r\n\t\t\t\t\t\t'outside overline pointer portrait pre print projection purple red relative repeat repeat-x repeat-y rgb ridge right right-side '+\r\n\t\t\t\t\t\t'rightwards rtl run-in screen scroll semi-condensed semi-expanded separate se-resize show silent silver slower slow '+\r\n\t\t\t\t\t\t'small small-caps small-caption smaller soft solid speech spell-out square s-resize static status-bar sub super sw-resize '+\r\n\t\t\t\t\t\t'table-caption table-cell table-column table-column-group table-footer-group table-header-group table-row table-row-group teal '+\r\n\t\t\t\t\t\t'text-bottom text-top thick thin top transparent tty tv ultra-condensed ultra-expanded underline upper-alpha uppercase upper-latin '+\r\n\t\t\t\t\t\t'upper-roman url visible wait white wider w-resize x-fast x-high x-large x-loud x-low x-slow x-small x-soft xx-large xx-small yellow';\r\n\r\n\t\tvar fonts =\t\t'[mM]onospace [tT]ahoma [vV]erdana [aA]rial [hH]elvetica [sS]ans-serif [sS]erif [cC]ourier mono sans serif';\r\n\t\r\n\t\tthis.regexList = [\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.multiLineCComments,\t\tcss: 'comments' },\t// multiline comments\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.doubleQuotedString,\t\tcss: 'string' },\t// double quoted strings\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleQuotedString,\t\tcss: 'string' },\t// single quoted strings\r\n\t\t\t{ regex: /\\#[a-fA-F0-9]{3,6}/g,\t\t\t\t\t\t\t\tcss: 'value' },\t\t// html colors\r\n\t\t\t{ regex: /(-?\\d+)(\\.\\d+)?(px|em|pt|\\:|\\%|)/g,\t\t\t\tcss: 'value' },\t\t// sizes\r\n\t\t\t{ regex: /!important/g,\t\t\t\t\t\t\t\t\t\tcss: 'color3' },\t// !important\r\n\t\t\t{ regex: new RegExp(getKeywordsCSS(keywords), 'gm'),\t\tcss: 'keyword' },\t// keywords\r\n\t\t\t{ regex: new RegExp(getValuesCSS(values), 'g'),\t\t\t\tcss: 'value' },\t\t// values\r\n\t\t\t{ regex: new RegExp(this.getKeywords(fonts), 'g'),\t\t\tcss: 'color1' }\t\t// fonts\r\n\t\t\t];\r\n\r\n\t\tthis.forHtmlScript({ \r\n\t\t\tleft: /(&lt;|<)\\s*style.*?(&gt;|>)/gi, \r\n\t\t\tright: /(&lt;|<)\\/\\s*style\\s*(&gt;|>)/gi \r\n\t\t\t});\r\n\t};\r\n\r\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\r\n\tBrush.aliases\t= ['css'];\r\n\r\n\tSyntaxHighlighter.brushes.CSS = Brush;\r\n\r\n\t// CommonJS\r\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\r\n})();\r\n;(function()\r\n{\r\n\t// CommonJS\r\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\r\n\r\n\tfunction Brush()\r\n\t{\r\n\t\tvar keywords =\t'abs addr and ansichar ansistring array as asm begin boolean byte cardinal ' +\r\n\t\t\t\t\t\t'case char class comp const constructor currency destructor div do double ' +\r\n\t\t\t\t\t\t'downto else end except exports extended false file finalization finally ' +\r\n\t\t\t\t\t\t'for function goto if implementation in inherited int64 initialization ' +\r\n\t\t\t\t\t\t'integer interface is label library longint longword mod nil not object ' +\r\n\t\t\t\t\t\t'of on or packed pansichar pansistring pchar pcurrency pdatetime pextended ' +\r\n\t\t\t\t\t\t'pint64 pointer private procedure program property pshortstring pstring ' +\r\n\t\t\t\t\t\t'pvariant pwidechar pwidestring protected public published raise real real48 ' +\r\n\t\t\t\t\t\t'record repeat set shl shortint shortstring shr single smallint string then ' +\r\n\t\t\t\t\t\t'threadvar to true try type unit until uses val var varirnt while widechar ' +\r\n\t\t\t\t\t\t'widestring with word write writeln xor';\r\n\r\n\t\tthis.regexList = [\r\n\t\t\t{ regex: /\\(\\*[\\s\\S]*?\\*\\)/gm,\t\t\t\t\t\t\t\tcss: 'comments' },  \t// multiline comments (* *)\r\n\t\t\t{ regex: /{(?!\\$)[\\s\\S]*?}/gm,\t\t\t\t\t\t\t\tcss: 'comments' },  \t// multiline comments { }\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleLineCComments,\tcss: 'comments' },  \t// one line\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleQuotedString,\t\tcss: 'string' },\t\t// strings\r\n\t\t\t{ regex: /\\{\\$[a-zA-Z]+ .+\\}/g,\t\t\t\t\t\t\t\tcss: 'color1' },\t\t// compiler Directives and Region tags\r\n\t\t\t{ regex: /\\b[\\d\\.]+\\b/g,\t\t\t\t\t\t\t\t\tcss: 'value' },\t\t\t// numbers 12345\r\n\t\t\t{ regex: /\\$[a-zA-Z0-9]+\\b/g,\t\t\t\t\t\t\t\tcss: 'value' },\t\t\t// numbers $F5D3\r\n\t\t\t{ regex: new RegExp(this.getKeywords(keywords), 'gmi'),\t\tcss: 'keyword' }\t\t// keyword\r\n\t\t\t];\r\n\t};\r\n\r\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\r\n\tBrush.aliases\t= ['delphi', 'pascal', 'pas'];\r\n\r\n\tSyntaxHighlighter.brushes.Delphi = Brush;\r\n\r\n\t// CommonJS\r\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\r\n})();\r\n;(function()\r\n{\r\n\t// CommonJS\r\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\r\n\r\n\tfunction Brush()\r\n\t{\r\n\t\tthis.regexList = [\r\n\t\t\t{ regex: /^\\+\\+\\+ .*$/gm,\tcss: 'color2' },\t// new file\r\n\t\t\t{ regex: /^\\-\\-\\- .*$/gm,\tcss: 'color2' },\t// old file\r\n\t\t\t{ regex: /^\\s.*$/gm,\t\tcss: 'color1' },\t// unchanged\r\n\t\t\t{ regex: /^@@.*@@.*$/gm,\tcss: 'variable' },\t// location\r\n\t\t\t{ regex: /^\\+.*$/gm,\t\tcss: 'string' },\t// additions\r\n\t\t\t{ regex: /^\\-.*$/gm,\t\tcss: 'color3' }\t\t// deletions\r\n\t\t\t];\r\n\t};\r\n\r\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\r\n\tBrush.aliases\t= ['diff', 'patch'];\r\n\r\n\tSyntaxHighlighter.brushes.Diff = Brush;\r\n\r\n\t// CommonJS\r\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\r\n})();\r\n;(function()\r\n{\r\n\t// CommonJS\r\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\r\n\r\n\tfunction Brush()\r\n\t{\r\n\t\t// Contributed by Jean-Lou Dupont\r\n\t\t// http://jldupont.blogspot.com/2009/06/erlang-syntax-highlighter.html  \r\n\r\n\t\t// According to: http://erlang.org/doc/reference_manual/introduction.html#1.5\r\n\t\tvar keywords = 'after and andalso band begin bnot bor bsl bsr bxor '+\r\n\t\t\t'case catch cond div end fun if let not of or orelse '+\r\n\t\t\t'query receive rem try when xor'+\r\n\t\t\t// additional\r\n\t\t\t' module export import define';\r\n\r\n\t\tthis.regexList = [\r\n\t\t\t{ regex: new RegExp(\"[A-Z][A-Za-z0-9_]+\", 'g'), \t\t\tcss: 'constants' },\r\n\t\t\t{ regex: new RegExp(\"\\\\%.+\", 'gm'), \t\t\t\t\t\tcss: 'comments' },\r\n\t\t\t{ regex: new RegExp(\"\\\\?[A-Za-z0-9_]+\", 'g'), \t\t\t\tcss: 'preprocessor' },\r\n\t\t\t{ regex: new RegExp(\"[a-z0-9_]+:[a-z0-9_]+\", 'g'), \t\t\tcss: 'functions' },\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.doubleQuotedString,\t\tcss: 'string' },\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleQuotedString,\t\tcss: 'string' },\r\n\t\t\t{ regex: new RegExp(this.getKeywords(keywords),\t'gm'),\t\tcss: 'keyword' }\r\n\t\t\t];\r\n\t};\r\n\r\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\r\n\tBrush.aliases\t= ['erl', 'erlang'];\r\n\r\n\tSyntaxHighlighter.brushes.Erland = Brush;\r\n\r\n\t// CommonJS\r\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\r\n})();\r\n;(function()\r\n{\r\n\t// CommonJS\r\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\r\n\r\n\tfunction Brush()\r\n\t{\r\n\t\t// Contributed by Andres Almiray\r\n\t\t// http://jroller.com/aalmiray/entry/nice_source_code_syntax_highlighter\r\n\r\n\t\tvar keywords =\t'as assert break case catch class continue def default do else extends finally ' +\r\n\t\t\t\t\t\t'if in implements import instanceof interface new package property return switch ' +\r\n\t\t\t\t\t\t'throw throws try while public protected private static';\r\n\t\tvar types    =  'void boolean byte char short int long float double';\r\n\t\tvar constants = 'null';\r\n\t\tvar methods   = 'allProperties count get size '+\r\n\t\t\t\t\t\t'collect each eachProperty eachPropertyName eachWithIndex find findAll ' +\r\n\t\t\t\t\t\t'findIndexOf grep inject max min reverseEach sort ' +\r\n\t\t\t\t\t\t'asImmutable asSynchronized flatten intersect join pop reverse subMap toList ' +\r\n\t\t\t\t\t\t'padRight padLeft contains eachMatch toCharacter toLong toUrl tokenize ' +\r\n\t\t\t\t\t\t'eachFile eachFileRecurse eachB yte eachLine readBytes readLine getText ' +\r\n\t\t\t\t\t\t'splitEachLine withReader append encodeBase64 decodeBase64 filterLine ' +\r\n\t\t\t\t\t\t'transformChar transformLine withOutputStream withPrintWriter withStream ' +\r\n\t\t\t\t\t\t'withStreams withWriter withWriterAppend write writeLine '+\r\n\t\t\t\t\t\t'dump inspect invokeMethod print println step times upto use waitForOrKill '+\r\n\t\t\t\t\t\t'getText';\r\n\r\n\t\tthis.regexList = [\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleLineCComments,\t\t\t\tcss: 'comments' },\t\t// one line comments\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.multiLineCComments,\t\t\t\t\tcss: 'comments' },\t\t// multiline comments\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.doubleQuotedString,\t\t\t\t\tcss: 'string' },\t\t// strings\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleQuotedString,\t\t\t\t\tcss: 'string' },\t\t// strings\r\n\t\t\t{ regex: /\"\"\".*\"\"\"/g,\t\t\t\t\t\t\t\t\t\t\t\t\tcss: 'string' },\t\t// GStrings\r\n\t\t\t{ regex: new RegExp('\\\\b([\\\\d]+(\\\\.[\\\\d]+)?|0x[a-f0-9]+)\\\\b', 'gi'),\tcss: 'value' },\t\t\t// numbers\r\n\t\t\t{ regex: new RegExp(this.getKeywords(keywords), 'gm'),\t\t\t\t\tcss: 'keyword' },\t\t// goovy keyword\r\n\t\t\t{ regex: new RegExp(this.getKeywords(types), 'gm'),\t\t\t\t\t\tcss: 'color1' },\t\t// goovy/java type\r\n\t\t\t{ regex: new RegExp(this.getKeywords(constants), 'gm'),\t\t\t\t\tcss: 'constants' },\t\t// constants\r\n\t\t\t{ regex: new RegExp(this.getKeywords(methods), 'gm'),\t\t\t\t\tcss: 'functions' }\t\t// methods\r\n\t\t\t];\r\n\r\n\t\tthis.forHtmlScript(SyntaxHighlighter.regexLib.aspScriptTags);\r\n\t}\r\n\r\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\r\n\tBrush.aliases\t= ['groovy'];\r\n\r\n\tSyntaxHighlighter.brushes.Groovy = Brush;\r\n\r\n\t// CommonJS\r\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\r\n})();\r\n;(function()\r\n{\r\n\t// CommonJS\r\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\r\n\r\n\tfunction Brush()\r\n\t{\r\n\t\tvar keywords =\t'abstract assert boolean break byte case catch char class const ' +\r\n\t\t\t\t\t\t'continue default do double else enum extends ' +\r\n\t\t\t\t\t\t'false final finally float for goto if implements import ' +\r\n\t\t\t\t\t\t'instanceof int interface long native new null ' +\r\n\t\t\t\t\t\t'package private protected public return ' +\r\n\t\t\t\t\t\t'short static strictfp super switch synchronized this throw throws true ' +\r\n\t\t\t\t\t\t'transient try void volatile while';\r\n\r\n\t\tthis.regexList = [\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleLineCComments,\tcss: 'comments' },\t\t// one line comments\r\n\t\t\t{ regex: /\\/\\*([^\\*][\\s\\S]*)?\\*\\//gm,\t\t\t\t\t\tcss: 'comments' },\t \t// multiline comments\r\n\t\t\t{ regex: /\\/\\*(?!\\*\\/)\\*[\\s\\S]*?\\*\\//gm,\t\t\t\t\tcss: 'preprocessor' },\t// documentation comments\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.doubleQuotedString,\t\tcss: 'string' },\t\t// strings\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleQuotedString,\t\tcss: 'string' },\t\t// strings\r\n\t\t\t{ regex: /\\b([\\d]+(\\.[\\d]+)?|0x[a-f0-9]+)\\b/gi,\t\t\t\tcss: 'value' },\t\t\t// numbers\r\n\t\t\t{ regex: /(?!\\@interface\\b)\\@[\\$\\w]+\\b/g,\t\t\t\t\tcss: 'color1' },\t\t// annotation @anno\r\n\t\t\t{ regex: /\\@interface\\b/g,\t\t\t\t\t\t\t\t\tcss: 'color2' },\t\t// @interface keyword\r\n\t\t\t{ regex: new RegExp(this.getKeywords(keywords), 'gm'),\t\tcss: 'keyword' }\t\t// java keyword\r\n\t\t\t];\r\n\r\n\t\tthis.forHtmlScript({\r\n\t\t\tleft\t: /(&lt;|<)%[@!=]?/g, \r\n\t\t\tright\t: /%(&gt;|>)/g \r\n\t\t});\r\n\t};\r\n\r\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\r\n\tBrush.aliases\t= ['java'];\r\n\r\n\tSyntaxHighlighter.brushes.Java = Brush;\r\n\r\n\t// CommonJS\r\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\r\n})();\r\n;(function()\r\n{\r\n\t// CommonJS\r\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\r\n\r\n\tfunction Brush()\r\n\t{\r\n\t\t// Contributed by Patrick Webster\r\n\t\t// http://patrickwebster.blogspot.com/2009/04/javafx-brush-for-syntaxhighlighter.html\r\n\t\tvar datatypes =\t'Boolean Byte Character Double Duration '\r\n\t\t\t\t\t\t+ 'Float Integer Long Number Short String Void'\r\n\t\t\t\t\t\t;\r\n\r\n\t\tvar keywords = 'abstract after and as assert at before bind bound break catch class '\r\n\t\t\t\t\t\t+ 'continue def delete else exclusive extends false finally first for from '\r\n\t\t\t\t\t\t+ 'function if import in indexof init insert instanceof into inverse last '\r\n\t\t\t\t\t\t+ 'lazy mixin mod nativearray new not null on or override package postinit '\r\n\t\t\t\t\t\t+ 'protected public public-init public-read replace return reverse sizeof '\r\n\t\t\t\t\t\t+ 'step super then this throw true try tween typeof var where while with '\r\n\t\t\t\t\t\t+ 'attribute let private readonly static trigger'\r\n\t\t\t\t\t\t;\r\n\r\n\t\tthis.regexList = [\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleLineCComments,\tcss: 'comments' },\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.multiLineCComments,\t\tcss: 'comments' },\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleQuotedString,\t\tcss: 'string' },\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.doubleQuotedString,\t\tcss: 'string' },\r\n\t\t\t{ regex: /(-?\\.?)(\\b(\\d*\\.?\\d+|\\d+\\.?\\d*)(e[+-]?\\d+)?|0x[a-f\\d]+)\\b\\.?/gi, css: 'color2' },\t// numbers\r\n\t\t\t{ regex: new RegExp(this.getKeywords(datatypes), 'gm'),\t\tcss: 'variable' },\t// datatypes\r\n\t\t\t{ regex: new RegExp(this.getKeywords(keywords), 'gm'),\t\tcss: 'keyword' }\r\n\t\t];\r\n\t\tthis.forHtmlScript(SyntaxHighlighter.regexLib.aspScriptTags);\r\n\t};\r\n\r\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\r\n\tBrush.aliases\t= ['jfx', 'javafx'];\r\n\r\n\tSyntaxHighlighter.brushes.JavaFX = Brush;\r\n\r\n\t// CommonJS\r\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\r\n})();\r\n;(function()\r\n{\r\n\t// CommonJS\r\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\r\n\r\n\tfunction Brush()\r\n\t{\r\n\t\tvar keywords =\t'break case catch continue ' +\r\n\t\t\t\t\t\t'default delete do else false  ' +\r\n\t\t\t\t\t\t'for function if in instanceof ' +\r\n\t\t\t\t\t\t'new null return super switch ' +\r\n\t\t\t\t\t\t'this throw true try typeof var while with'\r\n\t\t\t\t\t\t;\r\n\r\n\t\tvar r = SyntaxHighlighter.regexLib;\r\n\t\t\r\n\t\tthis.regexList = [\r\n\t\t\t{ regex: r.multiLineDoubleQuotedString,\t\t\t\t\tcss: 'string' },\t\t\t// double quoted strings\r\n\t\t\t{ regex: r.multiLineSingleQuotedString,\t\t\t\t\tcss: 'string' },\t\t\t// single quoted strings\r\n\t\t\t{ regex: r.singleLineCComments,\t\t\t\t\t\t\tcss: 'comments' },\t\t\t// one line comments\r\n\t\t\t{ regex: r.multiLineCComments,\t\t\t\t\t\t\tcss: 'comments' },\t\t\t// multiline comments\r\n\t\t\t{ regex: /\\s*#.*/gm,\t\t\t\t\t\t\t\t\tcss: 'preprocessor' },\t\t// preprocessor tags like #region and #endregion\r\n\t\t\t{ regex: new RegExp(this.getKeywords(keywords), 'gm'),\tcss: 'keyword' }\t\t\t// keywords\r\n\t\t\t];\r\n\t\r\n\t\tthis.forHtmlScript(r.scriptScriptTags);\r\n\t};\r\n\r\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\r\n\tBrush.aliases\t= ['js', 'jscript', 'javascript'];\r\n\r\n\tSyntaxHighlighter.brushes.JScript = Brush;\r\n\r\n\t// CommonJS\r\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\r\n})();\r\n;(function()\r\n{\r\n\t// CommonJS\r\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\r\n\r\n\tfunction Brush()\r\n\t{\r\n\t\t// Contributed by David Simmons-Duffin and Marty Kube\r\n\t\r\n\t\tvar funcs = \r\n\t\t\t'abs accept alarm atan2 bind binmode chdir chmod chomp chop chown chr ' + \r\n\t\t\t'chroot close closedir connect cos crypt defined delete each endgrent ' + \r\n\t\t\t'endhostent endnetent endprotoent endpwent endservent eof exec exists ' + \r\n\t\t\t'exp fcntl fileno flock fork format formline getc getgrent getgrgid ' + \r\n\t\t\t'getgrnam gethostbyaddr gethostbyname gethostent getlogin getnetbyaddr ' + \r\n\t\t\t'getnetbyname getnetent getpeername getpgrp getppid getpriority ' + \r\n\t\t\t'getprotobyname getprotobynumber getprotoent getpwent getpwnam getpwuid ' + \r\n\t\t\t'getservbyname getservbyport getservent getsockname getsockopt glob ' + \r\n\t\t\t'gmtime grep hex index int ioctl join keys kill lc lcfirst length link ' + \r\n\t\t\t'listen localtime lock log lstat map mkdir msgctl msgget msgrcv msgsnd ' + \r\n\t\t\t'oct open opendir ord pack pipe pop pos print printf prototype push ' + \r\n\t\t\t'quotemeta rand read readdir readline readlink readpipe recv rename ' + \r\n\t\t\t'reset reverse rewinddir rindex rmdir scalar seek seekdir select semctl ' + \r\n\t\t\t'semget semop send setgrent sethostent setnetent setpgrp setpriority ' + \r\n\t\t\t'setprotoent setpwent setservent setsockopt shift shmctl shmget shmread ' + \r\n\t\t\t'shmwrite shutdown sin sleep socket socketpair sort splice split sprintf ' + \r\n\t\t\t'sqrt srand stat study substr symlink syscall sysopen sysread sysseek ' + \r\n\t\t\t'system syswrite tell telldir time times tr truncate uc ucfirst umask ' + \r\n\t\t\t'undef unlink unpack unshift utime values vec wait waitpid warn write ' +\r\n\t\t\t// feature\r\n\t\t\t'say';\r\n    \r\n\t\tvar keywords =  \r\n\t\t\t'bless caller continue dbmclose dbmopen die do dump else elsif eval exit ' +\r\n\t\t\t'for foreach goto if import last local my next no our package redo ref ' + \r\n\t\t\t'require return sub tie tied unless untie until use wantarray while ' +\r\n\t\t\t// feature\r\n\t\t\t'given when default ' +\r\n\t\t\t// Try::Tiny\r\n\t\t\t'try catch finally ' +\r\n\t\t\t// Moose\r\n\t\t\t'has extends with before after around override augment';\r\n    \r\n\t\tthis.regexList = [\r\n\t\t\t{ regex: /(<<|&lt;&lt;)((\\w+)|(['\"])(.+?)\\4)[\\s\\S]+?\\n\\3\\5\\n/g,\tcss: 'string' },\t// here doc (maybe html encoded)\r\n\t\t\t{ regex: /#.*$/gm,\t\t\t\t\t\t\t\t\t\tcss: 'comments' },\r\n\t\t\t{ regex: /^#!.*\\n/g,\t\t\t\t\t\t\t\t\tcss: 'preprocessor' },\t// shebang\r\n\t\t\t{ regex: /-?\\w+(?=\\s*=(>|&gt;))/g,\tcss: 'string' }, // fat comma\r\n\r\n\t\t\t// is this too much?\r\n\t\t\t{ regex: /\\bq[qwxr]?\\([\\s\\S]*?\\)/g,\tcss: 'string' }, // quote-like operators ()\r\n\t\t\t{ regex: /\\bq[qwxr]?\\{[\\s\\S]*?\\}/g,\tcss: 'string' }, // quote-like operators {}\r\n\t\t\t{ regex: /\\bq[qwxr]?\\[[\\s\\S]*?\\]/g,\tcss: 'string' }, // quote-like operators []\r\n\t\t\t{ regex: /\\bq[qwxr]?(<|&lt;)[\\s\\S]*?(>|&gt;)/g,\tcss: 'string' }, // quote-like operators <>\r\n\t\t\t{ regex: /\\bq[qwxr]?([^\\w({<[])[\\s\\S]*?\\1/g,\tcss: 'string' }, // quote-like operators non-paired\r\n\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.doubleQuotedString,\tcss: 'string' },\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleQuotedString,\tcss: 'string' },\r\n\t\t\t// currently ignoring single quote package separator and utf8 names\r\n\t\t\t{ regex: /(?:&amp;|[$@%*]|\\$#)[a-zA-Z_](\\w+|::)*/g,   \t\tcss: 'variable' },\r\n\t\t\t{ regex: /\\b__(?:END|DATA)__\\b[\\s\\S]*$/g,\t\t\t\tcss: 'comments' },\r\n\t\t\t{ regex: /(^|\\n)=\\w[\\s\\S]*?(\\n=cut\\s*\\n|$)/g,\t\t\t\tcss: 'comments' },\t\t// pod\r\n\t\t\t{ regex: new RegExp(this.getKeywords(funcs), 'gm'),\t\tcss: 'functions' },\r\n\t\t\t{ regex: new RegExp(this.getKeywords(keywords), 'gm'),\tcss: 'keyword' }\r\n\t\t];\r\n\r\n\t\tthis.forHtmlScript(SyntaxHighlighter.regexLib.phpScriptTags);\r\n\t}\r\n\r\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\r\n\tBrush.aliases\t\t= ['perl', 'Perl', 'pl'];\r\n\r\n\tSyntaxHighlighter.brushes.Perl = Brush;\r\n\r\n\t// CommonJS\r\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\r\n})();\r\n;(function()\r\n{\r\n\t// CommonJS\r\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\r\n\r\n\tfunction Brush()\r\n\t{\r\n\t\tvar funcs\t=\t'abs acos acosh addcslashes addslashes ' +\r\n\t\t\t\t\t\t'array_change_key_case array_chunk array_combine array_count_values array_diff '+\r\n\t\t\t\t\t\t'array_diff_assoc array_diff_key array_diff_uassoc array_diff_ukey array_fill '+\r\n\t\t\t\t\t\t'array_filter array_flip array_intersect array_intersect_assoc array_intersect_key '+\r\n\t\t\t\t\t\t'array_intersect_uassoc array_intersect_ukey array_key_exists array_keys array_map '+\r\n\t\t\t\t\t\t'array_merge array_merge_recursive array_multisort array_pad array_pop array_product '+\r\n\t\t\t\t\t\t'array_push array_rand array_reduce array_reverse array_search array_shift '+\r\n\t\t\t\t\t\t'array_slice array_splice array_sum array_udiff array_udiff_assoc '+\r\n\t\t\t\t\t\t'array_udiff_uassoc array_uintersect array_uintersect_assoc '+\r\n\t\t\t\t\t\t'array_uintersect_uassoc array_unique array_unshift array_values array_walk '+\r\n\t\t\t\t\t\t'array_walk_recursive atan atan2 atanh base64_decode base64_encode base_convert '+\r\n\t\t\t\t\t\t'basename bcadd bccomp bcdiv bcmod bcmul bindec bindtextdomain bzclose bzcompress '+\r\n\t\t\t\t\t\t'bzdecompress bzerrno bzerror bzerrstr bzflush bzopen bzread bzwrite ceil chdir '+\r\n\t\t\t\t\t\t'checkdate checkdnsrr chgrp chmod chop chown chr chroot chunk_split class_exists '+\r\n\t\t\t\t\t\t'closedir closelog copy cos cosh count count_chars date decbin dechex decoct '+\r\n\t\t\t\t\t\t'deg2rad delete ebcdic2ascii echo empty end ereg ereg_replace eregi eregi_replace error_log '+\r\n\t\t\t\t\t\t'error_reporting escapeshellarg escapeshellcmd eval exec exit exp explode extension_loaded '+\r\n\t\t\t\t\t\t'feof fflush fgetc fgetcsv fgets fgetss file_exists file_get_contents file_put_contents '+\r\n\t\t\t\t\t\t'fileatime filectime filegroup fileinode filemtime fileowner fileperms filesize filetype '+\r\n\t\t\t\t\t\t'floatval flock floor flush fmod fnmatch fopen fpassthru fprintf fputcsv fputs fread fscanf '+\r\n\t\t\t\t\t\t'fseek fsockopen fstat ftell ftok getallheaders getcwd getdate getenv gethostbyaddr gethostbyname '+\r\n\t\t\t\t\t\t'gethostbynamel getimagesize getlastmod getmxrr getmygid getmyinode getmypid getmyuid getopt '+\r\n\t\t\t\t\t\t'getprotobyname getprotobynumber getrandmax getrusage getservbyname getservbyport gettext '+\r\n\t\t\t\t\t\t'gettimeofday gettype glob gmdate gmmktime ini_alter ini_get ini_get_all ini_restore ini_set '+\r\n\t\t\t\t\t\t'interface_exists intval ip2long is_a is_array is_bool is_callable is_dir is_double '+\r\n\t\t\t\t\t\t'is_executable is_file is_finite is_float is_infinite is_int is_integer is_link is_long '+\r\n\t\t\t\t\t\t'is_nan is_null is_numeric is_object is_readable is_real is_resource is_scalar is_soap_fault '+\r\n\t\t\t\t\t\t'is_string is_subclass_of is_uploaded_file is_writable is_writeable mkdir mktime nl2br '+\r\n\t\t\t\t\t\t'parse_ini_file parse_str parse_url passthru pathinfo print readlink realpath rewind rewinddir rmdir '+\r\n\t\t\t\t\t\t'round str_ireplace str_pad str_repeat str_replace str_rot13 str_shuffle str_split '+\r\n\t\t\t\t\t\t'str_word_count strcasecmp strchr strcmp strcoll strcspn strftime strip_tags stripcslashes '+\r\n\t\t\t\t\t\t'stripos stripslashes stristr strlen strnatcasecmp strnatcmp strncasecmp strncmp strpbrk '+\r\n\t\t\t\t\t\t'strpos strptime strrchr strrev strripos strrpos strspn strstr strtok strtolower strtotime '+\r\n\t\t\t\t\t\t'strtoupper strtr strval substr substr_compare';\r\n\r\n\t\tvar keywords =\t'abstract and array as break case catch cfunction class clone const continue declare default die do ' +\r\n\t\t\t\t\t\t'else elseif enddeclare endfor endforeach endif endswitch endwhile extends final for foreach ' +\r\n\t\t\t\t\t\t'function global goto if implements include include_once interface instanceof insteadof namespace new ' +\r\n\t\t\t\t\t\t'old_function or private protected public return require require_once static switch ' +\r\n\t\t\t\t\t\t'trait throw try use var while xor ';\r\n\t\t\r\n\t\tvar constants\t= '__FILE__ __LINE__ __METHOD__ __FUNCTION__ __CLASS__';\r\n\r\n\t\tthis.regexList = [\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleLineCComments,\tcss: 'comments' },\t\t\t// one line comments\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.multiLineCComments,\t\tcss: 'comments' },\t\t\t// multiline comments\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.doubleQuotedString,\t\tcss: 'string' },\t\t\t// double quoted strings\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleQuotedString,\t\tcss: 'string' },\t\t\t// single quoted strings\r\n\t\t\t{ regex: /\\$\\w+/g,\t\t\t\t\t\t\t\t\t\t\tcss: 'variable' },\t\t\t// variables\r\n\t\t\t{ regex: new RegExp(this.getKeywords(funcs), 'gmi'),\t\tcss: 'functions' },\t\t\t// common functions\r\n\t\t\t{ regex: new RegExp(this.getKeywords(constants), 'gmi'),\tcss: 'constants' },\t\t\t// constants\r\n\t\t\t{ regex: new RegExp(this.getKeywords(keywords), 'gm'),\t\tcss: 'keyword' }\t\t\t// keyword\r\n\t\t\t];\r\n\r\n\t\tthis.forHtmlScript(SyntaxHighlighter.regexLib.phpScriptTags);\r\n\t};\r\n\r\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\r\n\tBrush.aliases\t= ['php'];\r\n\r\n\tSyntaxHighlighter.brushes.Php = Brush;\r\n\r\n\t// CommonJS\r\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\r\n})();\r\n;(function()\r\n{\r\n\t// CommonJS\r\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\r\n\r\n\tfunction Brush()\r\n\t{\r\n\t};\r\n\r\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\r\n\tBrush.aliases\t= ['text', 'plain'];\r\n\r\n\tSyntaxHighlighter.brushes.Plain = Brush;\r\n\r\n\t// CommonJS\r\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\r\n})();\r\n;(function()\r\n{\r\n\t// CommonJS\r\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\r\n\r\n\tfunction Brush()\r\n\t{\r\n\t\t// Contributed by Joel 'Jaykul' Bennett, http://PoshCode.org | http://HuddledMasses.org\r\n\t\tvar keywords =\t'while validateset validaterange validatepattern validatelength validatecount ' +\r\n\t\t\t\t\t\t'until trap switch return ref process param parameter in if global: '+\r\n\t\t\t\t\t\t'function foreach for finally filter end elseif else dynamicparam do default ' +\r\n\t\t\t\t\t\t'continue cmdletbinding break begin alias \\\\? % #script #private #local #global '+\r\n\t\t\t\t\t\t'mandatory parametersetname position valuefrompipeline ' +\r\n\t\t\t\t\t\t'valuefrompipelinebypropertyname valuefromremainingarguments helpmessage ';\r\n\r\n\t\tvar operators =\t' and as band bnot bor bxor casesensitive ccontains ceq cge cgt cle ' +\r\n\t\t\t\t\t\t'clike clt cmatch cne cnotcontains cnotlike cnotmatch contains ' +\r\n\t\t\t\t\t\t'creplace eq exact f file ge gt icontains ieq ige igt ile ilike ilt ' +\r\n\t\t\t\t\t\t'imatch ine inotcontains inotlike inotmatch ireplace is isnot le like ' +\r\n\t\t\t\t\t\t'lt match ne not notcontains notlike notmatch or regex replace wildcard';\r\n\t\t\t\t\t\t\r\n\t\tvar verbs =\t\t'write where wait use update unregister undo trace test tee take suspend ' +\r\n\t\t\t\t\t\t'stop start split sort skip show set send select scroll resume restore ' +\r\n\t\t\t\t\t\t'restart resolve resize reset rename remove register receive read push ' +\r\n\t\t\t\t\t\t'pop ping out new move measure limit join invoke import group get format ' +\r\n\t\t\t\t\t\t'foreach export expand exit enter enable disconnect disable debug cxnew ' +\r\n\t\t\t\t\t\t'copy convertto convertfrom convert connect complete compare clear ' +\r\n\t\t\t\t\t\t'checkpoint aggregate add';\r\n\r\n\t\t// I can't find a way to match the comment based help in multi-line comments, because SH won't highlight in highlights, and javascript doesn't support lookbehind\r\n\t\tvar commenthelp = ' component description example externalhelp forwardhelpcategory forwardhelptargetname forwardhelptargetname functionality inputs link notes outputs parameter remotehelprunspace role synopsis';\r\n\r\n\t\tthis.regexList = [\r\n\t\t\t{ regex: new RegExp('^\\\\s*#[#\\\\s]*\\\\.('+this.getKeywords(commenthelp)+').*$', 'gim'),\t\t\tcss: 'preprocessor help bold' },\t\t// comment-based help\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleLinePerlComments,\t\t\t\t\t\t\t\t\t\tcss: 'comments' },\t\t\t\t\t\t// one line comments\r\n\t\t\t{ regex: /(&lt;|<)#[\\s\\S]*?#(&gt;|>)/gm,\t\t\t\t\t\t\t\t\t\t\t\t\t\tcss: 'comments here' },\t\t\t\t\t// multi-line comments\r\n\t\t\t\r\n\t\t\t{ regex: new RegExp('@\"\\\\n[\\\\s\\\\S]*?\\\\n\"@', 'gm'),\t\t\t\t\t\t\t\t\t\t\t\tcss: 'script string here' },\t\t\t// double quoted here-strings\r\n\t\t\t{ regex: new RegExp(\"@'\\\\n[\\\\s\\\\S]*?\\\\n'@\", 'gm'),\t\t\t\t\t\t\t\t\t\t\t\tcss: 'script string single here' },\t\t// single quoted here-strings\r\n\t\t\t{ regex: new RegExp('\"(?:\\\\$\\\\([^\\\\)]*\\\\)|[^\"]|`\"|\"\")*[^`]\"','g'),\t\t\t\t\t\t\t\tcss: 'string' },\t\t\t\t\t\t// double quoted strings\r\n\t\t\t{ regex: new RegExp(\"'(?:[^']|'')*'\", 'g'),\t\t\t\t\t\t\t\t\t\t\t\t\t\tcss: 'string single' },\t\t\t\t\t// single quoted strings\r\n\t\t\t\r\n\t\t\t{ regex: new RegExp('[\\\\$|@|@@](?:(?:global|script|private|env):)?[A-Z0-9_]+', 'gi'),\t\t\tcss: 'variable' },\t\t\t\t\t\t// $variables\r\n\t\t\t{ regex: new RegExp('(?:\\\\b'+verbs.replace(/ /g, '\\\\b|\\\\b')+')-[a-zA-Z_][a-zA-Z0-9_]*', 'gmi'),\tcss: 'functions' },\t\t\t\t\t\t// functions and cmdlets\r\n\t\t\t{ regex: new RegExp(this.getKeywords(keywords), 'gmi'),\t\t\t\t\t\t\t\t\t\t\tcss: 'keyword' },\t\t\t\t\t\t// keywords\r\n\t\t\t{ regex: new RegExp('-'+this.getKeywords(operators), 'gmi'),\t\t\t\t\t\t\t\t\tcss: 'operator value' },\t\t\t\t// operators\r\n\t\t\t{ regex: new RegExp('\\\\[[A-Z_\\\\[][A-Z0-9_. `,\\\\[\\\\]]*\\\\]', 'gi'),\t\t\t\t\t\t\t\tcss: 'constants' },\t\t\t\t\t\t// .Net [Type]s\r\n\t\t\t{ regex: new RegExp('\\\\s+-(?!'+this.getKeywords(operators)+')[a-zA-Z_][a-zA-Z0-9_]*', 'gmi'),\tcss: 'color1' },\t\t\t\t\t\t// parameters\t  \r\n\t\t];\r\n\t};\r\n\r\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\r\n\tBrush.aliases\t= ['powershell', 'ps', 'posh'];\r\n\r\n\tSyntaxHighlighter.brushes.PowerShell = Brush;\r\n\r\n\t// CommonJS\r\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\r\n})();\r\n;(function()\r\n{\r\n\t// CommonJS\r\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\r\n\r\n\tfunction Brush()\r\n\t{\r\n\t\t// Contributed by Gheorghe Milas and Ahmad Sherif\r\n\t\r\n\t\tvar keywords =  'and assert break class continue def del elif else ' +\r\n\t\t\t\t\t\t'except exec finally for from global if import in is ' +\r\n\t\t\t\t\t\t'lambda not or pass print raise return try yield while';\r\n\r\n\t\tvar funcs = '__import__ abs all any apply basestring bin bool buffer callable ' +\r\n\t\t\t\t\t'chr classmethod cmp coerce compile complex delattr dict dir ' +\r\n\t\t\t\t\t'divmod enumerate eval execfile file filter float format frozenset ' +\r\n\t\t\t\t\t'getattr globals hasattr hash help hex id input int intern ' +\r\n\t\t\t\t\t'isinstance issubclass iter len list locals long map max min next ' +\r\n\t\t\t\t\t'object oct open ord pow print property range raw_input reduce ' +\r\n\t\t\t\t\t'reload repr reversed round set setattr slice sorted staticmethod ' +\r\n\t\t\t\t\t'str sum super tuple type type unichr unicode vars xrange zip';\r\n\r\n\t\tvar special =  'None True False self cls class_';\r\n\r\n\t\tthis.regexList = [\r\n\t\t\t\t{ regex: SyntaxHighlighter.regexLib.singleLinePerlComments, css: 'comments' },\r\n\t\t\t\t{ regex: /^\\s*@\\w+/gm, \t\t\t\t\t\t\t\t\t\tcss: 'decorator' },\r\n\t\t\t\t{ regex: /(['\\\"]{3})([^\\1])*?\\1/gm, \t\t\t\t\t\tcss: 'comments' },\r\n\t\t\t\t{ regex: /\"(?!\")(?:\\.|\\\\\\\"|[^\\\"\"\\n])*\"/gm, \t\t\t\t\tcss: 'string' },\r\n\t\t\t\t{ regex: /'(?!')(?:\\.|(\\\\\\')|[^\\''\\n])*'/gm, \t\t\t\tcss: 'string' },\r\n\t\t\t\t{ regex: /\\+|\\-|\\*|\\/|\\%|=|==/gm, \t\t\t\t\t\t\tcss: 'keyword' },\r\n\t\t\t\t{ regex: /\\b\\d+\\.?\\w*/g, \t\t\t\t\t\t\t\t\tcss: 'value' },\r\n\t\t\t\t{ regex: new RegExp(this.getKeywords(funcs), 'gmi'),\t\tcss: 'functions' },\r\n\t\t\t\t{ regex: new RegExp(this.getKeywords(keywords), 'gm'), \t\tcss: 'keyword' },\r\n\t\t\t\t{ regex: new RegExp(this.getKeywords(special), 'gm'), \t\tcss: 'color1' }\r\n\t\t\t\t];\r\n\t\t\t\r\n\t\tthis.forHtmlScript(SyntaxHighlighter.regexLib.aspScriptTags);\r\n\t};\r\n\r\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\r\n\tBrush.aliases\t= ['py', 'python'];\r\n\r\n\tSyntaxHighlighter.brushes.Python = Brush;\r\n\r\n\t// CommonJS\r\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\r\n})();\r\n;(function()\r\n{\r\n\t// CommonJS\r\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\r\n\r\n\tfunction Brush()\r\n\t{\r\n\t\t// Contributed by Erik Peterson.\r\n\t\r\n\t\tvar keywords =\t'alias and BEGIN begin break case class def define_method defined do each else elsif ' +\r\n\t\t\t\t\t\t'END end ensure false for if in module new next nil not or raise redo rescue retry return ' +\r\n\t\t\t\t\t\t'self super then throw true undef unless until when while yield';\r\n\r\n\t\tvar builtins =\t'Array Bignum Binding Class Continuation Dir Exception FalseClass File::Stat File Fixnum Fload ' +\r\n\t\t\t\t\t\t'Hash Integer IO MatchData Method Module NilClass Numeric Object Proc Range Regexp String Struct::TMS Symbol ' +\r\n\t\t\t\t\t\t'ThreadGroup Thread Time TrueClass';\r\n\r\n\t\tthis.regexList = [\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleLinePerlComments,\tcss: 'comments' },\t\t// one line comments\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.doubleQuotedString,\t\tcss: 'string' },\t\t// double quoted strings\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleQuotedString,\t\tcss: 'string' },\t\t// single quoted strings\r\n\t\t\t{ regex: /\\b[A-Z0-9_]+\\b/g,\t\t\t\t\t\t\t\t\tcss: 'constants' },\t\t// constants\r\n\t\t\t{ regex: /:[a-z][A-Za-z0-9_]*/g,\t\t\t\t\t\t\tcss: 'color2' },\t\t// symbols\r\n\t\t\t{ regex: /(\\$|@@|@)\\w+/g,\t\t\t\t\t\t\t\t\tcss: 'variable bold' },\t// $global, @instance, and @@class variables\r\n\t\t\t{ regex: new RegExp(this.getKeywords(keywords), 'gm'),\t\tcss: 'keyword' },\t\t// keywords\r\n\t\t\t{ regex: new RegExp(this.getKeywords(builtins), 'gm'),\t\tcss: 'color1' }\t\t\t// builtins\r\n\t\t\t];\r\n\r\n\t\tthis.forHtmlScript(SyntaxHighlighter.regexLib.aspScriptTags);\r\n\t};\r\n\r\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\r\n\tBrush.aliases\t= ['ruby', 'rails', 'ror', 'rb'];\r\n\r\n\tSyntaxHighlighter.brushes.Ruby = Brush;\r\n\r\n\t// CommonJS\r\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\r\n})();\r\n;(function()\r\n{\r\n\t// CommonJS\r\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\r\n\r\n\tfunction Brush()\r\n\t{\r\n\t\tfunction getKeywordsCSS(str)\r\n\t\t{\r\n\t\t\treturn '\\\\b([a-z_]|)' + str.replace(/ /g, '(?=:)\\\\b|\\\\b([a-z_\\\\*]|\\\\*|)') + '(?=:)\\\\b';\r\n\t\t};\r\n\t\r\n\t\tfunction getValuesCSS(str)\r\n\t\t{\r\n\t\t\treturn '\\\\b' + str.replace(/ /g, '(?!-)(?!:)\\\\b|\\\\b()') + '\\:\\\\b';\r\n\t\t};\r\n\r\n\t\tvar keywords =\t'ascent azimuth background-attachment background-color background-image background-position ' +\r\n\t\t\t\t\t\t'background-repeat background baseline bbox border-collapse border-color border-spacing border-style border-top ' +\r\n\t\t\t\t\t\t'border-right border-bottom border-left border-top-color border-right-color border-bottom-color border-left-color ' +\r\n\t\t\t\t\t\t'border-top-style border-right-style border-bottom-style border-left-style border-top-width border-right-width ' +\r\n\t\t\t\t\t\t'border-bottom-width border-left-width border-width border bottom cap-height caption-side centerline clear clip color ' +\r\n\t\t\t\t\t\t'content counter-increment counter-reset cue-after cue-before cue cursor definition-src descent direction display ' +\r\n\t\t\t\t\t\t'elevation empty-cells float font-size-adjust font-family font-size font-stretch font-style font-variant font-weight font ' +\r\n\t\t\t\t\t\t'height left letter-spacing line-height list-style-image list-style-position list-style-type list-style margin-top ' +\r\n\t\t\t\t\t\t'margin-right margin-bottom margin-left margin marker-offset marks mathline max-height max-width min-height min-width orphans ' +\r\n\t\t\t\t\t\t'outline-color outline-style outline-width outline overflow padding-top padding-right padding-bottom padding-left padding page ' +\r\n\t\t\t\t\t\t'page-break-after page-break-before page-break-inside pause pause-after pause-before pitch pitch-range play-during position ' +\r\n\t\t\t\t\t\t'quotes right richness size slope src speak-header speak-numeral speak-punctuation speak speech-rate stemh stemv stress ' +\r\n\t\t\t\t\t\t'table-layout text-align top text-decoration text-indent text-shadow text-transform unicode-bidi unicode-range units-per-em ' +\r\n\t\t\t\t\t\t'vertical-align visibility voice-family volume white-space widows width widths word-spacing x-height z-index';\r\n\t\t\r\n\t\tvar values =\t'above absolute all always aqua armenian attr aural auto avoid baseline behind below bidi-override black blink block blue bold bolder '+\r\n\t\t\t\t\t\t'both bottom braille capitalize caption center center-left center-right circle close-quote code collapse compact condensed '+\r\n\t\t\t\t\t\t'continuous counter counters crop cross crosshair cursive dashed decimal decimal-leading-zero digits disc dotted double '+\r\n\t\t\t\t\t\t'embed embossed e-resize expanded extra-condensed extra-expanded fantasy far-left far-right fast faster fixed format fuchsia '+\r\n\t\t\t\t\t\t'gray green groove handheld hebrew help hidden hide high higher icon inline-table inline inset inside invert italic '+\r\n\t\t\t\t\t\t'justify landscape large larger left-side left leftwards level lighter lime line-through list-item local loud lower-alpha '+\r\n\t\t\t\t\t\t'lowercase lower-greek lower-latin lower-roman lower low ltr marker maroon medium message-box middle mix move narrower '+\r\n\t\t\t\t\t\t'navy ne-resize no-close-quote none no-open-quote no-repeat normal nowrap n-resize nw-resize oblique olive once open-quote outset '+\r\n\t\t\t\t\t\t'outside overline pointer portrait pre print projection purple red relative repeat repeat-x repeat-y rgb ridge right right-side '+\r\n\t\t\t\t\t\t'rightwards rtl run-in screen scroll semi-condensed semi-expanded separate se-resize show silent silver slower slow '+\r\n\t\t\t\t\t\t'small small-caps small-caption smaller soft solid speech spell-out square s-resize static status-bar sub super sw-resize '+\r\n\t\t\t\t\t\t'table-caption table-cell table-column table-column-group table-footer-group table-header-group table-row table-row-group teal '+\r\n\t\t\t\t\t\t'text-bottom text-top thick thin top transparent tty tv ultra-condensed ultra-expanded underline upper-alpha uppercase upper-latin '+\r\n\t\t\t\t\t\t'upper-roman url visible wait white wider w-resize x-fast x-high x-large x-loud x-low x-slow x-small x-soft xx-large xx-small yellow';\r\n\t\t\r\n\t\tvar fonts =\t\t'[mM]onospace [tT]ahoma [vV]erdana [aA]rial [hH]elvetica [sS]ans-serif [sS]erif [cC]ourier mono sans serif';\r\n\t\t\r\n\t\tvar statements\t\t= '!important !default';\r\n\t\tvar preprocessor\t= '@import @extend @debug @warn @if @for @while @mixin @include';\r\n\t\t\r\n\t\tvar r = SyntaxHighlighter.regexLib;\r\n\t\t\r\n\t\tthis.regexList = [\r\n\t\t\t{ regex: r.multiLineCComments,\t\t\t\t\t\t\t\tcss: 'comments' },\t\t// multiline comments\r\n\t\t\t{ regex: r.singleLineCComments,\t\t\t\t\t\t\t\tcss: 'comments' },\t\t// singleline comments\r\n\t\t\t{ regex: r.doubleQuotedString,\t\t\t\t\t\t\t\tcss: 'string' },\t\t// double quoted strings\r\n\t\t\t{ regex: r.singleQuotedString,\t\t\t\t\t\t\t\tcss: 'string' },\t\t// single quoted strings\r\n\t\t\t{ regex: /\\#[a-fA-F0-9]{3,6}/g,\t\t\t\t\t\t\t\tcss: 'value' },\t\t\t// html colors\r\n\t\t\t{ regex: /\\b(-?\\d+)(\\.\\d+)?(px|em|pt|\\:|\\%|)\\b/g,\t\t\tcss: 'value' },\t\t\t// sizes\r\n\t\t\t{ regex: /\\$\\w+/g,\t\t\t\t\t\t\t\t\t\t\tcss: 'variable' },\t\t// variables\r\n\t\t\t{ regex: new RegExp(this.getKeywords(statements), 'g'),\t\tcss: 'color3' },\t\t// statements\r\n\t\t\t{ regex: new RegExp(this.getKeywords(preprocessor), 'g'),\tcss: 'preprocessor' },\t// preprocessor\r\n\t\t\t{ regex: new RegExp(getKeywordsCSS(keywords), 'gm'),\t\tcss: 'keyword' },\t\t// keywords\r\n\t\t\t{ regex: new RegExp(getValuesCSS(values), 'g'),\t\t\t\tcss: 'value' },\t\t\t// values\r\n\t\t\t{ regex: new RegExp(this.getKeywords(fonts), 'g'),\t\t\tcss: 'color1' }\t\t\t// fonts\r\n\t\t\t];\r\n\t};\r\n\r\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\r\n\tBrush.aliases\t= ['sass', 'scss'];\r\n\r\n\tSyntaxHighlighter.brushes.Sass = Brush;\r\n\r\n\t// CommonJS\r\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\r\n})();\r\n;(function()\r\n{\r\n\t// CommonJS\r\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\r\n\r\n\tfunction Brush()\r\n\t{\r\n\t\t// Contributed by Yegor Jbanov and David Bernard.\r\n\t\r\n\t\tvar keywords =\t'val sealed case def true trait implicit forSome import match object null finally super ' +\r\n\t\t\t\t\t\t'override try lazy for var catch throw type extends class while with new final yield abstract ' +\r\n\t\t\t\t\t\t'else do if return protected private this package false';\r\n\r\n\t\tvar keyops =\t'[_:=><%#@]+';\r\n\r\n\t\tthis.regexList = [\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleLineCComments,\t\t\tcss: 'comments' },\t// one line comments\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.multiLineCComments,\t\t\t\tcss: 'comments' },\t// multiline comments\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.multiLineSingleQuotedString,\tcss: 'string' },\t// multi-line strings\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.multiLineDoubleQuotedString,    css: 'string' },\t// double-quoted string\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleQuotedString,\t\t\t\tcss: 'string' },\t// strings\r\n\t\t\t{ regex: /0x[a-f0-9]+|\\d+(\\.\\d+)?/gi,\t\t\t\t\t\t\t\tcss: 'value' },\t\t// numbers\r\n\t\t\t{ regex: new RegExp(this.getKeywords(keywords), 'gm'),\t\t\t\tcss: 'keyword' },\t// keywords\r\n\t\t\t{ regex: new RegExp(keyops, 'gm'),\t\t\t\t\t\t\t\t\tcss: 'keyword' }\t// scala keyword\r\n\t\t\t];\r\n\t}\r\n\r\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\r\n\tBrush.aliases\t= ['scala'];\r\n\r\n\tSyntaxHighlighter.brushes.Scala = Brush;\r\n\r\n\t// CommonJS\r\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\r\n})();\r\n;(function()\r\n{\r\n\t// CommonJS\r\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\r\n\r\n\tfunction Brush()\r\n\t{\r\n\t\tvar funcs\t=\t'abs avg case cast coalesce convert count current_timestamp ' +\r\n\t\t\t\t\t\t'current_user day isnull left lower month nullif replace right ' +\r\n\t\t\t\t\t\t'session_user space substring sum system_user upper user year';\r\n\r\n\t\tvar keywords =\t'absolute action add after alter as asc at authorization begin bigint ' +\r\n\t\t\t\t\t\t'binary bit by cascade char character check checkpoint close collate ' +\r\n\t\t\t\t\t\t'column commit committed connect connection constraint contains continue ' +\r\n\t\t\t\t\t\t'create cube current current_date current_time cursor database date ' +\r\n\t\t\t\t\t\t'deallocate dec decimal declare default delete desc distinct double drop ' +\r\n\t\t\t\t\t\t'dynamic else end end-exec escape except exec execute false fetch first ' +\r\n\t\t\t\t\t\t'float for force foreign forward free from full function global goto grant ' +\r\n\t\t\t\t\t\t'group grouping having hour ignore index inner insensitive insert instead ' +\r\n\t\t\t\t\t\t'int integer intersect into is isolation key last level load local max min ' +\r\n\t\t\t\t\t\t'minute modify move name national nchar next no numeric of off on only ' +\r\n\t\t\t\t\t\t'open option order out output partial password precision prepare primary ' +\r\n\t\t\t\t\t\t'prior privileges procedure public read real references relative repeatable ' +\r\n\t\t\t\t\t\t'restrict return returns revoke rollback rollup rows rule schema scroll ' +\r\n\t\t\t\t\t\t'second section select sequence serializable set size smallint static ' +\r\n\t\t\t\t\t\t'statistics table temp temporary then time timestamp to top transaction ' +\r\n\t\t\t\t\t\t'translation trigger true truncate uncommitted union unique update values ' +\r\n\t\t\t\t\t\t'varchar varying view when where with work';\r\n\r\n\t\tvar operators =\t'all and any between cross in join like not null or outer some';\r\n\r\n\t\tthis.regexList = [\r\n\t\t\t{ regex: /--(.*)$/gm,\t\t\t\t\t\t\t\t\t\t\t\tcss: 'comments' },\t\t\t// one line and multiline comments\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.multiLineDoubleQuotedString,\tcss: 'string' },\t\t\t// double quoted strings\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.multiLineSingleQuotedString,\tcss: 'string' },\t\t\t// single quoted strings\r\n\t\t\t{ regex: new RegExp(this.getKeywords(funcs), 'gmi'),\t\t\t\tcss: 'color2' },\t\t\t// functions\r\n\t\t\t{ regex: new RegExp(this.getKeywords(operators), 'gmi'),\t\t\tcss: 'color1' },\t\t\t// operators and such\r\n\t\t\t{ regex: new RegExp(this.getKeywords(keywords), 'gmi'),\t\t\t\tcss: 'keyword' }\t\t\t// keyword\r\n\t\t\t];\r\n\t};\r\n\r\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\r\n\tBrush.aliases\t= ['sql'];\r\n\r\n\tSyntaxHighlighter.brushes.Sql = Brush;\r\n\r\n\t// CommonJS\r\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\r\n})();\r\n\r\n;(function()\r\n{\r\n\t// CommonJS\r\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\r\n\r\n\tfunction Brush()\r\n\t{\r\n\t\tvar keywords =\t'AddHandler AddressOf AndAlso Alias And Ansi As Assembly Auto ' +\r\n\t\t\t\t\t\t'Boolean ByRef Byte ByVal Call Case Catch CBool CByte CChar CDate ' +\r\n\t\t\t\t\t\t'CDec CDbl Char CInt Class CLng CObj Const CShort CSng CStr CType ' +\r\n\t\t\t\t\t\t'Date Decimal Declare Default Delegate Dim DirectCast Do Double Each ' +\r\n\t\t\t\t\t\t'Else ElseIf End Enum Erase Error Event Exit False Finally For Friend ' +\r\n\t\t\t\t\t\t'Function Get GetType GoSub GoTo Handles If Implements Imports In ' +\r\n\t\t\t\t\t\t'Inherits Integer Interface Is Let Lib Like Long Loop Me Mod Module ' +\r\n\t\t\t\t\t\t'MustInherit MustOverride MyBase MyClass Namespace New Next Not Nothing ' +\r\n\t\t\t\t\t\t'NotInheritable NotOverridable Object On Option Optional Or OrElse ' +\r\n\t\t\t\t\t\t'Overloads Overridable Overrides ParamArray Preserve Private Property ' +\r\n\t\t\t\t\t\t'Protected Public RaiseEvent ReadOnly ReDim REM RemoveHandler Resume ' +\r\n\t\t\t\t\t\t'Return Select Set Shadows Shared Short Single Static Step Stop String ' +\r\n\t\t\t\t\t\t'Structure Sub SyncLock Then Throw To True Try TypeOf Unicode Until ' +\r\n\t\t\t\t\t\t'Variant When While With WithEvents WriteOnly Xor';\r\n\r\n\t\tthis.regexList = [\r\n\t\t\t{ regex: /'.*$/gm,\t\t\t\t\t\t\t\t\t\tcss: 'comments' },\t\t\t// one line comments\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.doubleQuotedString,\tcss: 'string' },\t\t\t// strings\r\n\t\t\t{ regex: /^\\s*#.*$/gm,\t\t\t\t\t\t\t\t\tcss: 'preprocessor' },\t\t// preprocessor tags like #region and #endregion\r\n\t\t\t{ regex: new RegExp(this.getKeywords(keywords), 'gm'),\tcss: 'keyword' }\t\t\t// vb keyword\r\n\t\t\t];\r\n\r\n\t\tthis.forHtmlScript(SyntaxHighlighter.regexLib.aspScriptTags);\r\n\t};\r\n\r\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\r\n\tBrush.aliases\t= ['vb', 'vbnet'];\r\n\r\n\tSyntaxHighlighter.brushes.Vb = Brush;\r\n\r\n\t// CommonJS\r\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\r\n})();\r\n;(function()\r\n{\r\n\t// CommonJS\r\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\r\n\r\n\tfunction Brush()\r\n\t{\r\n\t\tfunction process(match, regexInfo)\r\n\t\t{\r\n\t\t\tvar constructor = SyntaxHighlighter.Match,\r\n\t\t\t\tcode = match[0],\r\n\t\t\t\ttag = new XRegExp('(&lt;|<)[\\\\s\\\\/\\\\?]*(?<name>[:\\\\w-\\\\.]+)', 'xg').exec(code),\r\n\t\t\t\tresult = []\r\n\t\t\t\t;\r\n\t\t\r\n\t\t\tif (match.attributes != null) \r\n\t\t\t{\r\n\t\t\t\tvar attributes,\r\n\t\t\t\t\tregex = new XRegExp('(?<name> [\\\\w:\\\\-\\\\.]+)' +\r\n\t\t\t\t\t\t\t\t\t\t'\\\\s*=\\\\s*' +\r\n\t\t\t\t\t\t\t\t\t\t'(?<value> \".*?\"|\\'.*?\\'|\\\\w+)',\r\n\t\t\t\t\t\t\t\t\t\t'xg');\r\n\r\n\t\t\t\twhile ((attributes = regex.exec(code)) != null) \r\n\t\t\t\t{\r\n\t\t\t\t\tresult.push(new constructor(attributes.name, match.index + attributes.index, 'color1'));\r\n\t\t\t\t\tresult.push(new constructor(attributes.value, match.index + attributes.index + attributes[0].indexOf(attributes.value), 'string'));\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif (tag != null)\r\n\t\t\t\tresult.push(\r\n\t\t\t\t\tnew constructor(tag.name, match.index + tag[0].indexOf(tag.name), 'keyword')\r\n\t\t\t\t);\r\n\r\n\t\t\treturn result;\r\n\t\t}\r\n\t\r\n\t\tthis.regexList = [\r\n\t\t\t{ regex: new XRegExp('(\\\\&lt;|<)\\\\!\\\\[[\\\\w\\\\s]*?\\\\[(.|\\\\s)*?\\\\]\\\\](\\\\&gt;|>)', 'gm'),\t\t\tcss: 'color2' },\t// <![ ... [ ... ]]>\r\n\t\t\t{ regex: SyntaxHighlighter.regexLib.xmlComments,\t\t\t\t\t\t\t\t\t\t\t\tcss: 'comments' },\t// <!-- ... -->\r\n\t\t\t{ regex: new XRegExp('(&lt;|<)[\\\\s\\\\/\\\\?]*(\\\\w+)(?<attributes>.*?)[\\\\s\\\\/\\\\?]*(&gt;|>)', 'sg'), func: process }\r\n\t\t];\r\n\t};\r\n\r\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\r\n\tBrush.aliases\t= ['xml', 'xhtml', 'xslt', 'html'];\r\n\r\n\tSyntaxHighlighter.brushes.Xml = Brush;\r\n\r\n\t// CommonJS\r\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\r\n})();\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/SyntaxHighlighter/shCoreDefault.css",
    "content": ".syntaxhighlighter a,.syntaxhighlighter div,.syntaxhighlighter code,.syntaxhighlighter,.syntaxhighlighter td,.syntaxhighlighter tr,.syntaxhighlighter tbody,.syntaxhighlighter thead,.syntaxhighlighter caption,.syntaxhighlighter textarea{-moz-border-radius:0 0 0 0!important;-webkit-border-radius:0 0 0 0!important;background:none!important;border:0!important;bottom:auto!important;float:none!important;left:auto!important;line-height:1.1em!important;margin:0!important;outline:0!important;overflow:visible!important;padding:0!important;position:static!important;right:auto!important;text-align:left!important;top:auto!important;vertical-align:baseline!important;width:auto!important;box-sizing:content-box!important;font-family:Monaco,Menlo,Consolas,\"Courier New\",monospace;font-weight:normal!important;font-style:normal!important;min-height:inherit!important;min-height:auto!important;font-size:13px!important}.syntaxhighlighter{width:100%!important;margin:.3em 0 .3em 0!important;position:relative!important;overflow:auto!important;background-color:#f5f5f5!important;border:1px solid #ccc!important;border-radius:4px!important;border-collapse:separate!important}.syntaxhighlighter.source{overflow:hidden!important}.syntaxhighlighter .bold{font-weight:bold!important}.syntaxhighlighter .italic{font-style:italic!important}.syntaxhighlighter .gutter div{white-space:pre!important;word-wrap:normal}.syntaxhighlighter caption{text-align:left!important;padding:.5em 0 .5em 1em!important}.syntaxhighlighter td.code{width:100%!important}.syntaxhighlighter td.code .container{position:relative!important}.syntaxhighlighter td.code .container textarea{box-sizing:border-box!important;position:absolute!important;left:0!important;top:0!important;width:100%!important;border:none!important;background:white!important;padding-left:1em!important;overflow:hidden!important;white-space:pre!important}.syntaxhighlighter td.gutter .line{text-align:right!important;padding:0 .5em 0 1em!important}.syntaxhighlighter td.code .line{padding:0 1em!important}.syntaxhighlighter.nogutter td.code .container textarea,.syntaxhighlighter.nogutter td.code .line{padding-left:0!important}.syntaxhighlighter.show{display:block!important}.syntaxhighlighter.collapsed table{display:none!important}.syntaxhighlighter.collapsed .toolbar{padding:.1em .8em 0 .8em!important;font-size:1em!important;position:static!important;width:auto!important}.syntaxhighlighter.collapsed .toolbar span{display:inline!important;margin-right:1em!important}.syntaxhighlighter.collapsed .toolbar span a{padding:0!important;display:none!important}.syntaxhighlighter.collapsed .toolbar span a.expandSource{display:inline!important}.syntaxhighlighter .toolbar{position:absolute!important;right:1px!important;top:1px!important;width:11px!important;height:11px!important;font-size:10px!important;z-index:10!important}.syntaxhighlighter .toolbar span.title{display:inline!important}.syntaxhighlighter .toolbar a{display:block!important;text-align:center!important;text-decoration:none!important;padding-top:1px!important}.syntaxhighlighter .toolbar a.expandSource{display:none!important}.syntaxhighlighter.ie{font-size:.9em!important;padding:1px 0 1px 0!important}.syntaxhighlighter.ie .toolbar{line-height:8px!important}.syntaxhighlighter.ie .toolbar a{padding-top:0!important}.syntaxhighlighter.printing .line.alt1 .content,.syntaxhighlighter.printing .line.alt2 .content,.syntaxhighlighter.printing .line.highlighted .number,.syntaxhighlighter.printing .line.highlighted.alt1 .content,.syntaxhighlighter.printing .line.highlighted.alt2 .content{background:none!important}.syntaxhighlighter.printing .line .number{color:#bbb!important}.syntaxhighlighter.printing .line .content{color:black!important}.syntaxhighlighter.printing .toolbar{display:none!important}.syntaxhighlighter.printing a{text-decoration:none!important}.syntaxhighlighter.printing .plain,.syntaxhighlighter.printing .plain a{color:black!important}.syntaxhighlighter.printing .comments,.syntaxhighlighter.printing .comments a{color:#008200!important}.syntaxhighlighter.printing .string,.syntaxhighlighter.printing .string a{color:blue!important}.syntaxhighlighter.printing .keyword{color:#ff7800!important;font-weight:bold!important}.syntaxhighlighter.printing .preprocessor{color:gray!important}.syntaxhighlighter.printing .variable{color:#a70!important}.syntaxhighlighter.printing .value{color:#090!important}.syntaxhighlighter.printing .functions{color:#ff1493!important}.syntaxhighlighter.printing .constants{color:#06c!important}.syntaxhighlighter.printing .script{font-weight:bold!important}.syntaxhighlighter.printing .color1,.syntaxhighlighter.printing .color1 a{color:gray!important}.syntaxhighlighter.printing .color2,.syntaxhighlighter.printing .color2 a{color:#ff1493!important}.syntaxhighlighter.printing .color3,.syntaxhighlighter.printing .color3 a{color:red!important}.syntaxhighlighter.printing .break,.syntaxhighlighter.printing .break a{color:black!important}.syntaxhighlighter{background-color:#f5f5f5!important}.syntaxhighlighter .line.highlighted.number{color:black!important}.syntaxhighlighter caption{color:black!important}.syntaxhighlighter .gutter{color:#afafaf!important;background-color:#f7f7f9!important;border-right:1px solid #e1e1e8!important;padding:9.5px 0 9.5px 9.5px!important;border-top-left-radius:4px!important;border-bottom-left-radius:4px!important;user-select:none!important;-moz-user-select:none!important;-webkit-user-select:none!important}.syntaxhighlighter .gutter .line.highlighted{background-color:#6ce26c!important;color:white!important}.syntaxhighlighter.printing .line .content{border:none!important}.syntaxhighlighter.collapsed{overflow:visible!important}.syntaxhighlighter.collapsed .toolbar{color:blue!important;background:white!important;border:1px solid #6ce26c!important}.syntaxhighlighter.collapsed .toolbar a{color:blue!important}.syntaxhighlighter.collapsed .toolbar a:hover{color:red!important}.syntaxhighlighter .toolbar{color:white!important;background:#6ce26c!important;border:none!important}.syntaxhighlighter .toolbar a{color:white!important}.syntaxhighlighter .toolbar a:hover{color:black!important}.syntaxhighlighter .plain,.syntaxhighlighter .plain a{color:black!important}.syntaxhighlighter .comments,.syntaxhighlighter .comments a{color:#008200!important}.syntaxhighlighter .string,.syntaxhighlighter .string a{color:blue!important}.syntaxhighlighter .keyword{color:#ff7800!important}.syntaxhighlighter .preprocessor{color:gray!important}.syntaxhighlighter .variable{color:#a70!important}.syntaxhighlighter .value{color:#090!important}.syntaxhighlighter .functions{color:#ff1493!important}.syntaxhighlighter .constants{color:#06c!important}.syntaxhighlighter .script{font-weight:bold!important;color:#ff7800!important;background-color:none!important}.syntaxhighlighter .color1,.syntaxhighlighter .color1 a{color:gray!important}.syntaxhighlighter .color2,.syntaxhighlighter .color2 a{color:#ff1493!important}.syntaxhighlighter .color3,.syntaxhighlighter .color3 a{color:red!important}.syntaxhighlighter .keyword{font-weight:bold!important}"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/codemirror/codemirror.css",
    "content": ".CodeMirror {\r\n    line-height: 1em;\r\n    font-family: monospace;\r\n}\r\n\r\n.CodeMirror-scroll {\r\n    overflow: auto;\r\n    height: 300px;\r\n    /* This is needed to prevent an IE[67] bug where the scrolled content\r\n       is visible outside of the scrolling box. */\r\n    position: relative;\r\n}\r\n\r\n.CodeMirror-gutter {\r\n    position: absolute; left: 0; top: 0;\r\n    z-index: 10;\r\n    background-color: #f7f7f7;\r\n    border-right: 1px solid #eee;\r\n    min-width: 2em;\r\n    height: 100%;\r\n}\r\n.CodeMirror-gutter-text {\r\n    color: #aaa;\r\n    text-align: right;\r\n    padding: .4em .2em .4em .4em;\r\n    white-space: pre !important;\r\n}\r\n.CodeMirror-lines {\r\n    padding: .4em;\r\n}\r\n\r\n.CodeMirror pre {\r\n    -moz-border-radius: 0;\r\n    -webkit-border-radius: 0;\r\n    -o-border-radius: 0;\r\n    border-radius: 0;\r\n    border-width: 0; margin: 0; padding: 0; background: transparent;\r\n    font-family: inherit;\r\n    font-size: inherit;\r\n    padding: 0; margin: 0;\r\n    white-space: pre;\r\n    word-wrap: normal;\r\n}\r\n\r\n.CodeMirror-wrap pre {\r\n    word-wrap: break-word;\r\n    white-space: pre-wrap;\r\n}\r\n.CodeMirror-wrap .CodeMirror-scroll {\r\n    overflow-x: hidden;\r\n}\r\n\r\n.CodeMirror textarea {\r\n    outline: none !important;\r\n}\r\n\r\n.CodeMirror pre.CodeMirror-cursor {\r\n    z-index: 10;\r\n    position: absolute;\r\n    visibility: hidden;\r\n    border-left: 1px solid black;\r\n}\r\n.CodeMirror-focused pre.CodeMirror-cursor {\r\n    visibility: visible;\r\n}\r\n\r\nspan.CodeMirror-selected { background: #d9d9d9; }\r\n.CodeMirror-focused span.CodeMirror-selected { background: #d2dcf8; }\r\n\r\n.CodeMirror-searching {background: #ffa;}\r\n\r\n/* Default theme */\r\n\r\n.cm-s-default span.cm-keyword {color: #708;}\r\n.cm-s-default span.cm-atom {color: #219;}\r\n.cm-s-default span.cm-number {color: #164;}\r\n.cm-s-default span.cm-def {color: #00f;}\r\n.cm-s-default span.cm-variable {color: black;}\r\n.cm-s-default span.cm-variable-2 {color: #05a;}\r\n.cm-s-default span.cm-variable-3 {color: #085;}\r\n.cm-s-default span.cm-property {color: black;}\r\n.cm-s-default span.cm-operator {color: black;}\r\n.cm-s-default span.cm-comment {color: #a50;}\r\n.cm-s-default span.cm-string {color: #a11;}\r\n.cm-s-default span.cm-string-2 {color: #f50;}\r\n.cm-s-default span.cm-meta {color: #555;}\r\n.cm-s-default span.cm-error {color: #f00;}\r\n.cm-s-default span.cm-qualifier {color: #555;}\r\n.cm-s-default span.cm-builtin {color: #30a;}\r\n.cm-s-default span.cm-bracket {color: #cc7;}\r\n.cm-s-default span.cm-tag {color: #170;}\r\n.cm-s-default span.cm-attribute {color: #00c;}\r\n.cm-s-default span.cm-header {color: #a0a;}\r\n.cm-s-default span.cm-quote {color: #090;}\r\n.cm-s-default span.cm-hr {color: #999;}\r\n.cm-s-default span.cm-link {color: #00c;}\r\n\r\nspan.cm-header, span.cm-strong {font-weight: bold;}\r\nspan.cm-em {font-style: italic;}\r\nspan.cm-emstrong {font-style: italic; font-weight: bold;}\r\nspan.cm-link {text-decoration: underline;}\r\n\r\ndiv.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}\r\ndiv.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/codemirror/codemirror.js",
    "content": "// CodeMirror version 2.2\r\n//\r\n// All functions that need access to the editor's state live inside\r\n// the CodeMirror function. Below that, at the bottom of the file,\r\n// some utilities are defined.\r\n\r\n// CodeMirror is the only global var we claim\r\nvar CodeMirror = (function() {\r\n    // This is the function that produces an editor instance. It's\r\n    // closure is used to store the editor state.\r\n    function CodeMirror(place, givenOptions) {\r\n        // Determine effective options based on given values and defaults.\r\n        var options = {}, defaults = CodeMirror.defaults;\r\n        for (var opt in defaults)\r\n            if (defaults.hasOwnProperty(opt))\r\n                options[opt] = (givenOptions && givenOptions.hasOwnProperty(opt) ? givenOptions : defaults)[opt];\r\n\r\n        var targetDocument = options[\"document\"];\r\n        // The element in which the editor lives.\r\n        var wrapper = targetDocument.createElement(\"div\");\r\n        wrapper.className = \"CodeMirror\" + (options.lineWrapping ? \" CodeMirror-wrap\" : \"\");\r\n        // This mess creates the base DOM structure for the editor.\r\n        wrapper.innerHTML =\r\n            '<div style=\"overflow: hidden; position: relative; width: 3px; height: 0px;\">' + // Wraps and hides input textarea\r\n                '<textarea style=\"position: absolute; padding: 0; width: 1px;\" wrap=\"off\" ' +\r\n                'autocorrect=\"off\" autocapitalize=\"off\"></textarea></div>' +\r\n                '<div class=\"CodeMirror-scroll\" tabindex=\"-1\">' +\r\n                '<div style=\"position: relative\">' + // Set to the height of the text, causes scrolling\r\n                '<div style=\"position: relative\">' + // Moved around its parent to cover visible view\r\n                '<div class=\"CodeMirror-gutter\"><div class=\"CodeMirror-gutter-text\"></div></div>' +\r\n                // Provides positioning relative to (visible) text origin\r\n                '<div class=\"CodeMirror-lines\"><div style=\"position: relative\">' +\r\n                '<div style=\"position: absolute; width: 100%; height: 0; overflow: hidden; visibility: hidden\"></div>' +\r\n                '<pre class=\"CodeMirror-cursor\">&#160;</pre>' + // Absolutely positioned blinky cursor\r\n                '<div></div>' + // This DIV contains the actual code\r\n                '</div></div></div></div></div>';\r\n        if (place.appendChild) place.appendChild(wrapper); else place(wrapper);\r\n        // I've never seen more elegant code in my life.\r\n        var inputDiv = wrapper.firstChild, input = inputDiv.firstChild,\r\n            scroller = wrapper.lastChild, code = scroller.firstChild,\r\n            mover = code.firstChild, gutter = mover.firstChild, gutterText = gutter.firstChild,\r\n            lineSpace = gutter.nextSibling.firstChild, measure = lineSpace.firstChild,\r\n            cursor = measure.nextSibling, lineDiv = cursor.nextSibling;\r\n        themeChanged();\r\n        // Needed to hide big blue blinking cursor on Mobile Safari\r\n        if (/AppleWebKit/.test(navigator.userAgent) && /Mobile\\/\\w+/.test(navigator.userAgent)) input.style.width = \"0px\";\r\n        if (!webkit) lineSpace.draggable = true;\r\n        if (options.tabindex != null) input.tabIndex = options.tabindex;\r\n        if (!options.gutter && !options.lineNumbers) gutter.style.display = \"none\";\r\n\r\n        // Check for problem with IE innerHTML not working when we have a\r\n        // P (or similar) parent node.\r\n        try { stringWidth(\"x\"); }\r\n        catch (e) {\r\n            if (e.message.match(/runtime/i))\r\n                e = new Error(\"A CodeMirror inside a P-style element does not work in Internet Explorer. (innerHTML bug)\");\r\n            throw e;\r\n        }\r\n\r\n        // Delayed object wrap timeouts, making sure only one is active. blinker holds an interval.\r\n        var poll = new Delayed(), highlight = new Delayed(), blinker;\r\n\r\n        // mode holds a mode API object. doc is the tree of Line objects,\r\n        // work an array of lines that should be parsed, and history the\r\n        // undo history (instance of History constructor).\r\n        var mode, doc = new BranchChunk([new LeafChunk([new Line(\"\")])]), work, focused;\r\n        loadMode();\r\n        // The selection. These are always maintained to point at valid\r\n        // positions. Inverted is used to remember that the user is\r\n        // selecting bottom-to-top.\r\n        var sel = {from: {line: 0, ch: 0}, to: {line: 0, ch: 0}, inverted: false};\r\n        // Selection-related flags. shiftSelecting obviously tracks\r\n        // whether the user is holding shift.\r\n        var shiftSelecting, lastClick, lastDoubleClick, draggingText, overwrite = false;\r\n        // Variables used by startOperation/endOperation to track what\r\n        // happened during the operation.\r\n        var updateInput, userSelChange, changes, textChanged, selectionChanged, leaveInputAlone,\r\n            gutterDirty, callbacks;\r\n        // Current visible range (may be bigger than the view window).\r\n        var displayOffset = 0, showingFrom = 0, showingTo = 0, lastSizeC = 0;\r\n        // bracketHighlighted is used to remember that a backet has been\r\n        // marked.\r\n        var bracketHighlighted;\r\n        // Tracks the maximum line length so that the horizontal scrollbar\r\n        // can be kept static when scrolling.\r\n        var maxLine = \"\", maxWidth, tabText = computeTabText();\r\n\r\n        // Initialize the content.\r\n        operation(function(){setValue(options.value || \"\"); updateInput = false;})();\r\n        var history = new History();\r\n\r\n        // Register our event handlers.\r\n        connect(scroller, \"mousedown\", operation(onMouseDown));\r\n        connect(scroller, \"dblclick\", operation(onDoubleClick));\r\n        connect(lineSpace, \"dragstart\", onDragStart);\r\n        connect(lineSpace, \"selectstart\", e_preventDefault);\r\n        // Gecko browsers fire contextmenu *after* opening the menu, at\r\n        // which point we can't mess with it anymore. Context menu is\r\n        // handled in onMouseDown for Gecko.\r\n        if (!gecko) connect(scroller, \"contextmenu\", onContextMenu);\r\n        connect(scroller, \"scroll\", function() {\r\n            updateDisplay([]);\r\n            if (options.fixedGutter) gutter.style.left = scroller.scrollLeft + \"px\";\r\n            if (options.onScroll) options.onScroll(instance);\r\n        });\r\n        connect(window, \"resize\", function() {updateDisplay(true);});\r\n        connect(input, \"keyup\", operation(onKeyUp));\r\n        connect(input, \"input\", fastPoll);\r\n        connect(input, \"keydown\", operation(onKeyDown));\r\n        connect(input, \"keypress\", operation(onKeyPress));\r\n        connect(input, \"focus\", onFocus);\r\n        connect(input, \"blur\", onBlur);\r\n\r\n        connect(scroller, \"dragenter\", e_stop);\r\n        connect(scroller, \"dragover\", e_stop);\r\n        connect(scroller, \"drop\", operation(onDrop));\r\n        connect(scroller, \"paste\", function(){focusInput(); fastPoll();});\r\n        connect(input, \"paste\", fastPoll);\r\n        connect(input, \"cut\", operation(function(){replaceSelection(\"\");}));\r\n\r\n        // IE throws unspecified error in certain cases, when\r\n        // trying to access activeElement before onload\r\n        var hasFocus; try { hasFocus = (targetDocument.activeElement == input); } catch(e) { }\r\n        if (hasFocus) setTimeout(onFocus, 20);\r\n        else onBlur();\r\n\r\n        function isLine(l) {return l >= 0 && l < doc.size;}\r\n        // The instance object that we'll return. Mostly calls out to\r\n        // local functions in the CodeMirror function. Some do some extra\r\n        // range checking and/or clipping. operation is used to wrap the\r\n        // call so that changes it makes are tracked, and the display is\r\n        // updated afterwards.\r\n        var instance = wrapper.CodeMirror = {\r\n            getValue: getValue,\r\n            setValue: operation(setValue),\r\n            getSelection: getSelection,\r\n            replaceSelection: operation(replaceSelection),\r\n            focus: function(){focusInput(); onFocus(); fastPoll();},\r\n            setOption: function(option, value) {\r\n                var oldVal = options[option];\r\n                options[option] = value;\r\n                if (option == \"mode\" || option == \"indentUnit\") loadMode();\r\n                else if (option == \"readOnly\" && value) {onBlur(); input.blur();}\r\n                else if (option == \"theme\") themeChanged();\r\n                else if (option == \"lineWrapping\" && oldVal != value) operation(wrappingChanged)();\r\n                else if (option == \"tabSize\") operation(tabsChanged)();\r\n                if (option == \"lineNumbers\" || option == \"gutter\" || option == \"firstLineNumber\" || option == \"theme\")\r\n                    operation(gutterChanged)();\r\n            },\r\n            getOption: function(option) {return options[option];},\r\n            undo: operation(undo),\r\n            redo: operation(redo),\r\n            indentLine: operation(function(n, dir) {\r\n                if (isLine(n)) indentLine(n, dir == null ? \"smart\" : dir ? \"add\" : \"subtract\");\r\n            }),\r\n            indentSelection: operation(indentSelected),\r\n            historySize: function() {return {undo: history.done.length, redo: history.undone.length};},\r\n            clearHistory: function() {history = new History();},\r\n            matchBrackets: operation(function(){matchBrackets(true);}),\r\n            getTokenAt: operation(function(pos) {\r\n                pos = clipPos(pos);\r\n                return getLine(pos.line).getTokenAt(mode, getStateBefore(pos.line), pos.ch);\r\n            }),\r\n            getStateAfter: function(line) {\r\n                line = clipLine(line == null ? doc.size - 1: line);\r\n                return getStateBefore(line + 1);\r\n            },\r\n            cursorCoords: function(start){\r\n                if (start == null) start = sel.inverted;\r\n                return pageCoords(start ? sel.from : sel.to);\r\n            },\r\n            charCoords: function(pos){return pageCoords(clipPos(pos));},\r\n            coordsChar: function(coords) {\r\n                var off = eltOffset(lineSpace);\r\n                return coordsChar(coords.x - off.left, coords.y - off.top);\r\n            },\r\n            markText: operation(markText),\r\n            setBookmark: setBookmark,\r\n            setMarker: operation(addGutterMarker),\r\n            clearMarker: operation(removeGutterMarker),\r\n            setLineClass: operation(setLineClass),\r\n            hideLine: operation(function(h) {return setLineHidden(h, true);}),\r\n            showLine: operation(function(h) {return setLineHidden(h, false);}),\r\n            onDeleteLine: function(line, f) {\r\n                if (typeof line == \"number\") {\r\n                    if (!isLine(line)) return null;\r\n                    line = getLine(line);\r\n                }\r\n                (line.handlers || (line.handlers = [])).push(f);\r\n                return line;\r\n            },\r\n            lineInfo: lineInfo,\r\n            addWidget: function(pos, node, scroll, vert, horiz) {\r\n                pos = localCoords(clipPos(pos));\r\n                var top = pos.yBot, left = pos.x;\r\n                node.style.position = \"absolute\";\r\n                code.appendChild(node);\r\n                if (vert == \"over\") top = pos.y;\r\n                else if (vert == \"near\") {\r\n                    var vspace = Math.max(scroller.offsetHeight, doc.height * textHeight()),\r\n                        hspace = Math.max(code.clientWidth, lineSpace.clientWidth) - paddingLeft();\r\n                    if (pos.yBot + node.offsetHeight > vspace && pos.y > node.offsetHeight)\r\n                        top = pos.y - node.offsetHeight;\r\n                    if (left + node.offsetWidth > hspace)\r\n                        left = hspace - node.offsetWidth;\r\n                }\r\n                node.style.top = (top + paddingTop()) + \"px\";\r\n                node.style.left = node.style.right = \"\";\r\n                if (horiz == \"right\") {\r\n                    left = code.clientWidth - node.offsetWidth;\r\n                    node.style.right = \"0px\";\r\n                } else {\r\n                    if (horiz == \"left\") left = 0;\r\n                    else if (horiz == \"middle\") left = (code.clientWidth - node.offsetWidth) / 2;\r\n                    node.style.left = (left + paddingLeft()) + \"px\";\r\n                }\r\n                if (scroll)\r\n                    scrollIntoView(left, top, left + node.offsetWidth, top + node.offsetHeight);\r\n            },\r\n\r\n            lineCount: function() {return doc.size;},\r\n            clipPos: clipPos,\r\n            getCursor: function(start) {\r\n                if (start == null) start = sel.inverted;\r\n                return copyPos(start ? sel.from : sel.to);\r\n            },\r\n            somethingSelected: function() {return !posEq(sel.from, sel.to);},\r\n            setCursor: operation(function(line, ch, user) {\r\n                if (ch == null && typeof line.line == \"number\") setCursor(line.line, line.ch, user);\r\n                else setCursor(line, ch, user);\r\n            }),\r\n            setSelection: operation(function(from, to, user) {\r\n                (user ? setSelectionUser : setSelection)(clipPos(from), clipPos(to || from));\r\n            }),\r\n            getLine: function(line) {if (isLine(line)) return getLine(line).text;},\r\n            getLineHandle: function(line) {if (isLine(line)) return getLine(line);},\r\n            setLine: operation(function(line, text) {\r\n                if (isLine(line)) replaceRange(text, {line: line, ch: 0}, {line: line, ch: getLine(line).text.length});\r\n            }),\r\n            removeLine: operation(function(line) {\r\n                if (isLine(line)) replaceRange(\"\", {line: line, ch: 0}, clipPos({line: line+1, ch: 0}));\r\n            }),\r\n            replaceRange: operation(replaceRange),\r\n            getRange: function(from, to) {return getRange(clipPos(from), clipPos(to));},\r\n\r\n            execCommand: function(cmd) {return commands[cmd](instance);},\r\n            // Stuff used by commands, probably not much use to outside code.\r\n            moveH: operation(moveH),\r\n            deleteH: operation(deleteH),\r\n            moveV: operation(moveV),\r\n            toggleOverwrite: function() {overwrite = !overwrite;},\r\n\r\n            posFromIndex: function(off) {\r\n                var lineNo = 0, ch;\r\n                doc.iter(0, doc.size, function(line) {\r\n                    var sz = line.text.length + 1;\r\n                    if (sz > off) { ch = off; return true; }\r\n                    off -= sz;\r\n                    ++lineNo;\r\n                });\r\n                return clipPos({line: lineNo, ch: ch});\r\n            },\r\n            indexFromPos: function (coords) {\r\n                if (coords.line < 0 || coords.ch < 0) return 0;\r\n                var index = coords.ch;\r\n                doc.iter(0, coords.line, function (line) {\r\n                    index += line.text.length + 1;\r\n                });\r\n                return index;\r\n            },\r\n\r\n            operation: function(f){return operation(f)();},\r\n            refresh: function(){updateDisplay(true);},\r\n            getInputField: function(){return input;},\r\n            getWrapperElement: function(){return wrapper;},\r\n            getScrollerElement: function(){return scroller;},\r\n            getGutterElement: function(){return gutter;}\r\n        };\r\n\r\n        function getLine(n) { return getLineAt(doc, n); }\r\n        function updateLineHeight(line, height) {\r\n            gutterDirty = true;\r\n            var diff = height - line.height;\r\n            for (var n = line; n; n = n.parent) n.height += diff;\r\n        }\r\n\r\n        function setValue(code) {\r\n            var top = {line: 0, ch: 0};\r\n            updateLines(top, {line: doc.size - 1, ch: getLine(doc.size-1).text.length},\r\n                splitLines(code), top, top);\r\n            updateInput = true;\r\n        }\r\n        function getValue(code) {\r\n            var text = [];\r\n            doc.iter(0, doc.size, function(line) { text.push(line.text); });\r\n            return text.join(\"\\n\");\r\n        }\r\n\r\n        function onMouseDown(e) {\r\n            setShift(e.shiftKey);\r\n            // Check whether this is a click in a widget\r\n            for (var n = e_target(e); n != wrapper; n = n.parentNode)\r\n                if (n.parentNode == code && n != mover) return;\r\n\r\n            // See if this is a click in the gutter\r\n            for (var n = e_target(e); n != wrapper; n = n.parentNode)\r\n                if (n.parentNode == gutterText) {\r\n                    if (options.onGutterClick)\r\n                        options.onGutterClick(instance, indexOf(gutterText.childNodes, n) + showingFrom, e);\r\n                    return e_preventDefault(e);\r\n                }\r\n\r\n            var start = posFromMouse(e);\r\n\r\n            switch (e_button(e)) {\r\n                case 3:\r\n                    if (gecko && !mac) onContextMenu(e);\r\n                    return;\r\n                case 2:\r\n                    if (start) setCursor(start.line, start.ch, true);\r\n                    return;\r\n            }\r\n            // For button 1, if it was clicked inside the editor\r\n            // (posFromMouse returning non-null), we have to adjust the\r\n            // selection.\r\n            if (!start) {if (e_target(e) == scroller) e_preventDefault(e); return;}\r\n\r\n            if (!focused) onFocus();\r\n\r\n            var now = +new Date;\r\n            if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) {\r\n                e_preventDefault(e);\r\n                setTimeout(focusInput, 20);\r\n                return selectLine(start.line);\r\n            } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) {\r\n                lastDoubleClick = {time: now, pos: start};\r\n                e_preventDefault(e);\r\n                return selectWordAt(start);\r\n            } else { lastClick = {time: now, pos: start}; }\r\n\r\n            var last = start, going;\r\n            if (dragAndDrop && !posEq(sel.from, sel.to) &&\r\n                !posLess(start, sel.from) && !posLess(sel.to, start)) {\r\n                // Let the drag handler handle this.\r\n                if (webkit) lineSpace.draggable = true;\r\n                var up = connect(targetDocument, \"mouseup\", operation(function(e2) {\r\n                    if (webkit) lineSpace.draggable = false;\r\n                    draggingText = false;\r\n                    up();\r\n                    if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {\r\n                        e_preventDefault(e2);\r\n                        setCursor(start.line, start.ch, true);\r\n                        focusInput();\r\n                    }\r\n                }), true);\r\n                draggingText = true;\r\n                return;\r\n            }\r\n            e_preventDefault(e);\r\n            setCursor(start.line, start.ch, true);\r\n\r\n            function extend(e) {\r\n                var cur = posFromMouse(e, true);\r\n                if (cur && !posEq(cur, last)) {\r\n                    if (!focused) onFocus();\r\n                    last = cur;\r\n                    setSelectionUser(start, cur);\r\n                    updateInput = false;\r\n                    var visible = visibleLines();\r\n                    if (cur.line >= visible.to || cur.line < visible.from)\r\n                        going = setTimeout(operation(function(){extend(e);}), 150);\r\n                }\r\n            }\r\n\r\n            var move = connect(targetDocument, \"mousemove\", operation(function(e) {\r\n                clearTimeout(going);\r\n                e_preventDefault(e);\r\n                extend(e);\r\n            }), true);\r\n            var up = connect(targetDocument, \"mouseup\", operation(function(e) {\r\n                clearTimeout(going);\r\n                var cur = posFromMouse(e);\r\n                if (cur) setSelectionUser(start, cur);\r\n                e_preventDefault(e);\r\n                focusInput();\r\n                updateInput = true;\r\n                move(); up();\r\n            }), true);\r\n        }\r\n        function onDoubleClick(e) {\r\n            for (var n = e_target(e); n != wrapper; n = n.parentNode)\r\n                if (n.parentNode == gutterText) return e_preventDefault(e);\r\n            var start = posFromMouse(e);\r\n            if (!start) return;\r\n            lastDoubleClick = {time: +new Date, pos: start};\r\n            e_preventDefault(e);\r\n            selectWordAt(start);\r\n        }\r\n        function onDrop(e) {\r\n            e.preventDefault();\r\n            var pos = posFromMouse(e, true), files = e.dataTransfer.files;\r\n            if (!pos || options.readOnly) return;\r\n            if (files && files.length && window.FileReader && window.File) {\r\n                function loadFile(file, i) {\r\n                    var reader = new FileReader;\r\n                    reader.onload = function() {\r\n                        text[i] = reader.result;\r\n                        if (++read == n) {\r\n                            pos = clipPos(pos);\r\n                            operation(function() {\r\n                                var end = replaceRange(text.join(\"\"), pos, pos);\r\n                                setSelectionUser(pos, end);\r\n                            })();\r\n                        }\r\n                    };\r\n                    reader.readAsText(file);\r\n                }\r\n                var n = files.length, text = Array(n), read = 0;\r\n                for (var i = 0; i < n; ++i) loadFile(files[i], i);\r\n            }\r\n            else {\r\n                try {\r\n                    var text = e.dataTransfer.getData(\"Text\");\r\n                    if (text) {\r\n                        var end = replaceRange(text, pos, pos);\r\n                        var curFrom = sel.from, curTo = sel.to;\r\n                        setSelectionUser(pos, end);\r\n                        if (draggingText) replaceRange(\"\", curFrom, curTo);\r\n                        focusInput();\r\n                    }\r\n                }\r\n                catch(e){}\r\n            }\r\n        }\r\n        function onDragStart(e) {\r\n            var txt = getSelection();\r\n            // This will reset escapeElement\r\n            htmlEscape(txt);\r\n            e.dataTransfer.setDragImage(escapeElement, 0, 0);\r\n            e.dataTransfer.setData(\"Text\", txt);\r\n        }\r\n        function handleKeyBinding(e) {\r\n            var name = keyNames[e.keyCode], next = keyMap[options.keyMap].auto, bound, dropShift;\r\n            if (name == null || e.altGraphKey) {\r\n                if (next) options.keyMap = next;\r\n                return null;\r\n            }\r\n            if (e.altKey) name = \"Alt-\" + name;\r\n            if (e.ctrlKey) name = \"Ctrl-\" + name;\r\n            if (e.metaKey) name = \"Cmd-\" + name;\r\n            if (e.shiftKey && (bound = lookupKey(\"Shift-\" + name, options.extraKeys, options.keyMap))) {\r\n                dropShift = true;\r\n            } else {\r\n                bound = lookupKey(name, options.extraKeys, options.keyMap);\r\n            }\r\n            if (typeof bound == \"string\") {\r\n                if (commands.propertyIsEnumerable(bound)) bound = commands[bound];\r\n                else bound = null;\r\n            }\r\n            if (next && (bound || !isModifierKey(e))) options.keyMap = next;\r\n            if (!bound) return false;\r\n            if (dropShift) {\r\n                var prevShift = shiftSelecting;\r\n                shiftSelecting = null;\r\n                bound(instance);\r\n                shiftSelecting = prevShift;\r\n            } else bound(instance);\r\n            e_preventDefault(e);\r\n            return true;\r\n        }\r\n        var lastStoppedKey = null;\r\n        function onKeyDown(e) {\r\n            if (!focused) onFocus();\r\n            var code = e.keyCode;\r\n            // IE does strange things with escape.\r\n            if (ie && code == 27) { e.returnValue = false; }\r\n            setShift(code == 16 || e.shiftKey);\r\n            // First give onKeyEvent option a chance to handle this.\r\n            if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;\r\n            var handled = handleKeyBinding(e);\r\n            if (window.opera) {\r\n                lastStoppedKey = handled ? e.keyCode : null;\r\n                // Opera has no cut event... we try to at least catch the key combo\r\n                if (!handled && (mac ? e.metaKey : e.ctrlKey) && e.keyCode == 88)\r\n                    replaceSelection(\"\");\r\n            }\r\n        }\r\n        function onKeyPress(e) {\r\n            if (window.opera && e.keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}\r\n            if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;\r\n            if (window.opera && !e.which && handleKeyBinding(e)) return;\r\n            if (options.electricChars && mode.electricChars) {\r\n                var ch = String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode);\r\n                if (mode.electricChars.indexOf(ch) > -1)\r\n                    setTimeout(operation(function() {indentLine(sel.to.line, \"smart\");}), 75);\r\n            }\r\n            fastPoll();\r\n        }\r\n        function onKeyUp(e) {\r\n            if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;\r\n            if (e.keyCode == 16) shiftSelecting = null;\r\n        }\r\n\r\n        function onFocus() {\r\n            if (options.readOnly) return;\r\n            if (!focused) {\r\n                if (options.onFocus) options.onFocus(instance);\r\n                focused = true;\r\n                if (wrapper.className.search(/\\bCodeMirror-focused\\b/) == -1)\r\n                    wrapper.className += \" CodeMirror-focused\";\r\n                if (!leaveInputAlone) resetInput(true);\r\n            }\r\n            slowPoll();\r\n            restartBlink();\r\n        }\r\n        function onBlur() {\r\n            if (focused) {\r\n                if (options.onBlur) options.onBlur(instance);\r\n                focused = false;\r\n                wrapper.className = wrapper.className.replace(\" CodeMirror-focused\", \"\");\r\n            }\r\n            clearInterval(blinker);\r\n            setTimeout(function() {if (!focused) shiftSelecting = null;}, 150);\r\n        }\r\n\r\n        // Replace the range from from to to by the strings in newText.\r\n        // Afterwards, set the selection to selFrom, selTo.\r\n        function updateLines(from, to, newText, selFrom, selTo) {\r\n            if (history) {\r\n                var old = [];\r\n                doc.iter(from.line, to.line + 1, function(line) { old.push(line.text); });\r\n                history.addChange(from.line, newText.length, old);\r\n                while (history.done.length > options.undoDepth) history.done.shift();\r\n            }\r\n            updateLinesNoUndo(from, to, newText, selFrom, selTo);\r\n        }\r\n        function unredoHelper(from, to) {\r\n            var change = from.pop();\r\n            if (change) {\r\n                var replaced = [], end = change.start + change.added;\r\n                doc.iter(change.start, end, function(line) { replaced.push(line.text); });\r\n                to.push({start: change.start, added: change.old.length, old: replaced});\r\n                var pos = clipPos({line: change.start + change.old.length - 1,\r\n                    ch: editEnd(replaced[replaced.length-1], change.old[change.old.length-1])});\r\n                updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: getLine(end-1).text.length}, change.old, pos, pos);\r\n                updateInput = true;\r\n            }\r\n        }\r\n        function undo() {unredoHelper(history.done, history.undone);}\r\n        function redo() {unredoHelper(history.undone, history.done);}\r\n\r\n        function updateLinesNoUndo(from, to, newText, selFrom, selTo) {\r\n            var recomputeMaxLength = false, maxLineLength = maxLine.length;\r\n            if (!options.lineWrapping)\r\n                doc.iter(from.line, to.line, function(line) {\r\n                    if (line.text.length == maxLineLength) {recomputeMaxLength = true; return true;}\r\n                });\r\n            if (from.line != to.line || newText.length > 1) gutterDirty = true;\r\n\r\n            var nlines = to.line - from.line, firstLine = getLine(from.line), lastLine = getLine(to.line);\r\n            // First adjust the line structure, taking some care to leave highlighting intact.\r\n            if (from.ch == 0 && to.ch == 0 && newText[newText.length - 1] == \"\") {\r\n                // This is a whole-line replace. Treated specially to make\r\n                // sure line objects move the way they are supposed to.\r\n                var added = [], prevLine = null;\r\n                if (from.line) {\r\n                    prevLine = getLine(from.line - 1);\r\n                    prevLine.fixMarkEnds(lastLine);\r\n                } else lastLine.fixMarkStarts();\r\n                for (var i = 0, e = newText.length - 1; i < e; ++i)\r\n                    added.push(Line.inheritMarks(newText[i], prevLine));\r\n                if (nlines) doc.remove(from.line, nlines, callbacks);\r\n                if (added.length) doc.insert(from.line, added);\r\n            } else if (firstLine == lastLine) {\r\n                if (newText.length == 1)\r\n                    firstLine.replace(from.ch, to.ch, newText[0]);\r\n                else {\r\n                    lastLine = firstLine.split(to.ch, newText[newText.length-1]);\r\n                    firstLine.replace(from.ch, null, newText[0]);\r\n                    firstLine.fixMarkEnds(lastLine);\r\n                    var added = [];\r\n                    for (var i = 1, e = newText.length - 1; i < e; ++i)\r\n                        added.push(Line.inheritMarks(newText[i], firstLine));\r\n                    added.push(lastLine);\r\n                    doc.insert(from.line + 1, added);\r\n                }\r\n            } else if (newText.length == 1) {\r\n                firstLine.replace(from.ch, null, newText[0]);\r\n                lastLine.replace(null, to.ch, \"\");\r\n                firstLine.append(lastLine);\r\n                doc.remove(from.line + 1, nlines, callbacks);\r\n            } else {\r\n                var added = [];\r\n                firstLine.replace(from.ch, null, newText[0]);\r\n                lastLine.replace(null, to.ch, newText[newText.length-1]);\r\n                firstLine.fixMarkEnds(lastLine);\r\n                for (var i = 1, e = newText.length - 1; i < e; ++i)\r\n                    added.push(Line.inheritMarks(newText[i], firstLine));\r\n                if (nlines > 1) doc.remove(from.line + 1, nlines - 1, callbacks);\r\n                doc.insert(from.line + 1, added);\r\n            }\r\n            if (options.lineWrapping) {\r\n                var perLine = scroller.clientWidth / charWidth() - 3;\r\n                doc.iter(from.line, from.line + newText.length, function(line) {\r\n                    if (line.hidden) return;\r\n                    var guess = Math.ceil(line.text.length / perLine) || 1;\r\n                    if (guess != line.height) updateLineHeight(line, guess);\r\n                });\r\n            } else {\r\n                doc.iter(from.line, i + newText.length, function(line) {\r\n                    var l = line.text;\r\n                    if (l.length > maxLineLength) {\r\n                        maxLine = l; maxLineLength = l.length; maxWidth = null;\r\n                        recomputeMaxLength = false;\r\n                    }\r\n                });\r\n                if (recomputeMaxLength) {\r\n                    maxLineLength = 0; maxLine = \"\"; maxWidth = null;\r\n                    doc.iter(0, doc.size, function(line) {\r\n                        var l = line.text;\r\n                        if (l.length > maxLineLength) {\r\n                            maxLineLength = l.length; maxLine = l;\r\n                        }\r\n                    });\r\n                }\r\n            }\r\n\r\n            // Add these lines to the work array, so that they will be\r\n            // highlighted. Adjust work lines if lines were added/removed.\r\n            var newWork = [], lendiff = newText.length - nlines - 1;\r\n            for (var i = 0, l = work.length; i < l; ++i) {\r\n                var task = work[i];\r\n                if (task < from.line) newWork.push(task);\r\n                else if (task > to.line) newWork.push(task + lendiff);\r\n            }\r\n            var hlEnd = from.line + Math.min(newText.length, 500);\r\n            highlightLines(from.line, hlEnd);\r\n            newWork.push(hlEnd);\r\n            work = newWork;\r\n            startWorker(100);\r\n            // Remember that these lines changed, for updating the display\r\n            changes.push({from: from.line, to: to.line + 1, diff: lendiff});\r\n            var changeObj = {from: from, to: to, text: newText};\r\n            if (textChanged) {\r\n                for (var cur = textChanged; cur.next; cur = cur.next) {}\r\n                cur.next = changeObj;\r\n            } else textChanged = changeObj;\r\n\r\n            // Update the selection\r\n            function updateLine(n) {return n <= Math.min(to.line, to.line + lendiff) ? n : n + lendiff;}\r\n            setSelection(selFrom, selTo, updateLine(sel.from.line), updateLine(sel.to.line));\r\n\r\n            // Make sure the scroll-size div has the correct height.\r\n            code.style.height = (doc.height * textHeight() + 2 * paddingTop()) + \"px\";\r\n        }\r\n\r\n        function replaceRange(code, from, to) {\r\n            from = clipPos(from);\r\n            if (!to) to = from; else to = clipPos(to);\r\n            code = splitLines(code);\r\n            function adjustPos(pos) {\r\n                if (posLess(pos, from)) return pos;\r\n                if (!posLess(to, pos)) return end;\r\n                var line = pos.line + code.length - (to.line - from.line) - 1;\r\n                var ch = pos.ch;\r\n                if (pos.line == to.line)\r\n                    ch += code[code.length-1].length - (to.ch - (to.line == from.line ? from.ch : 0));\r\n                return {line: line, ch: ch};\r\n            }\r\n            var end;\r\n            replaceRange1(code, from, to, function(end1) {\r\n                end = end1;\r\n                return {from: adjustPos(sel.from), to: adjustPos(sel.to)};\r\n            });\r\n            return end;\r\n        }\r\n        function replaceSelection(code, collapse) {\r\n            replaceRange1(splitLines(code), sel.from, sel.to, function(end) {\r\n                if (collapse == \"end\") return {from: end, to: end};\r\n                else if (collapse == \"start\") return {from: sel.from, to: sel.from};\r\n                else return {from: sel.from, to: end};\r\n            });\r\n        }\r\n        function replaceRange1(code, from, to, computeSel) {\r\n            var endch = code.length == 1 ? code[0].length + from.ch : code[code.length-1].length;\r\n            var newSel = computeSel({line: from.line + code.length - 1, ch: endch});\r\n            updateLines(from, to, code, newSel.from, newSel.to);\r\n        }\r\n\r\n        function getRange(from, to) {\r\n            var l1 = from.line, l2 = to.line;\r\n            if (l1 == l2) return getLine(l1).text.slice(from.ch, to.ch);\r\n            var code = [getLine(l1).text.slice(from.ch)];\r\n            doc.iter(l1 + 1, l2, function(line) { code.push(line.text); });\r\n            code.push(getLine(l2).text.slice(0, to.ch));\r\n            return code.join(\"\\n\");\r\n        }\r\n        function getSelection() {\r\n            return getRange(sel.from, sel.to);\r\n        }\r\n\r\n        var pollingFast = false; // Ensures slowPoll doesn't cancel fastPoll\r\n        function slowPoll() {\r\n            if (pollingFast) return;\r\n            poll.set(options.pollInterval, function() {\r\n                startOperation();\r\n                readInput();\r\n                if (focused) slowPoll();\r\n                endOperation();\r\n            });\r\n        }\r\n        function fastPoll() {\r\n            var missed = false;\r\n            pollingFast = true;\r\n            function p() {\r\n                startOperation();\r\n                var changed = readInput();\r\n                if (!changed && !missed) {missed = true; poll.set(60, p);}\r\n                else {pollingFast = false; slowPoll();}\r\n                endOperation();\r\n            }\r\n            poll.set(20, p);\r\n        }\r\n\r\n        // Previnput is a hack to work with IME. If we reset the textarea\r\n        // on every change, that breaks IME. So we look for changes\r\n        // compared to the previous content instead. (Modern browsers have\r\n        // events that indicate IME taking place, but these are not widely\r\n        // supported or compatible enough yet to rely on.)\r\n        var prevInput = \"\";\r\n        function readInput() {\r\n            if (leaveInputAlone || !focused || hasSelection(input)) return false;\r\n            var text = input.value;\r\n            if (text == prevInput) return false;\r\n            shiftSelecting = null;\r\n            var same = 0, l = Math.min(prevInput.length, text.length);\r\n            while (same < l && prevInput[same] == text[same]) ++same;\r\n            if (same < prevInput.length)\r\n                sel.from = {line: sel.from.line, ch: sel.from.ch - (prevInput.length - same)};\r\n            else if (overwrite && posEq(sel.from, sel.to))\r\n                sel.to = {line: sel.to.line, ch: Math.min(getLine(sel.to.line).text.length, sel.to.ch + (text.length - same))};\r\n            replaceSelection(text.slice(same), \"end\");\r\n            prevInput = text;\r\n            return true;\r\n        }\r\n        function resetInput(user) {\r\n            if (!posEq(sel.from, sel.to)) {\r\n                prevInput = \"\";\r\n                input.value = getSelection();\r\n                input.select();\r\n            } else if (user) prevInput = input.value = \"\";\r\n        }\r\n\r\n        function focusInput() {\r\n            if (!options.readOnly) input.focus();\r\n        }\r\n\r\n        function scrollEditorIntoView() {\r\n            if (!cursor.getBoundingClientRect) return;\r\n            var rect = cursor.getBoundingClientRect();\r\n            // IE returns bogus coordinates when the instance sits inside of an iframe and the cursor is hidden\r\n            if (ie && rect.top == rect.bottom) return;\r\n            var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight);\r\n            if (rect.top < 0 || rect.bottom > winH) cursor.scrollIntoView();\r\n        }\r\n        function scrollCursorIntoView() {\r\n            var cursor = localCoords(sel.inverted ? sel.from : sel.to);\r\n            var x = options.lineWrapping ? Math.min(cursor.x, lineSpace.offsetWidth) : cursor.x;\r\n            return scrollIntoView(x, cursor.y, x, cursor.yBot);\r\n        }\r\n        function scrollIntoView(x1, y1, x2, y2) {\r\n            var pl = paddingLeft(), pt = paddingTop(), lh = textHeight();\r\n            y1 += pt; y2 += pt; x1 += pl; x2 += pl;\r\n            var screen = scroller.clientHeight, screentop = scroller.scrollTop, scrolled = false, result = true;\r\n            if (y1 < screentop) {scroller.scrollTop = Math.max(0, y1 - 2*lh); scrolled = true;}\r\n            else if (y2 > screentop + screen) {scroller.scrollTop = y2 + lh - screen; scrolled = true;}\r\n\r\n            var screenw = scroller.clientWidth, screenleft = scroller.scrollLeft;\r\n            var gutterw = options.fixedGutter ? gutter.clientWidth : 0;\r\n            if (x1 < screenleft + gutterw) {\r\n                if (x1 < 50) x1 = 0;\r\n                scroller.scrollLeft = Math.max(0, x1 - 10 - gutterw);\r\n                scrolled = true;\r\n            }\r\n            else if (x2 > screenw + screenleft - 3) {\r\n                scroller.scrollLeft = x2 + 10 - screenw;\r\n                scrolled = true;\r\n                if (x2 > code.clientWidth) result = false;\r\n            }\r\n            if (scrolled && options.onScroll) options.onScroll(instance);\r\n            return result;\r\n        }\r\n\r\n        function visibleLines() {\r\n            var lh = textHeight(), top = scroller.scrollTop - paddingTop();\r\n            var from_height = Math.max(0, Math.floor(top / lh));\r\n            var to_height = Math.ceil((top + scroller.clientHeight) / lh);\r\n            return {from: lineAtHeight(doc, from_height),\r\n                to: lineAtHeight(doc, to_height)};\r\n        }\r\n        // Uses a set of changes plus the current scroll position to\r\n        // determine which DOM updates have to be made, and makes the\r\n        // updates.\r\n        function updateDisplay(changes, suppressCallback) {\r\n            if (!scroller.clientWidth) {\r\n                showingFrom = showingTo = displayOffset = 0;\r\n                return;\r\n            }\r\n            // Compute the new visible window\r\n            var visible = visibleLines();\r\n            // Bail out if the visible area is already rendered and nothing changed.\r\n            if (changes !== true && changes.length == 0 && visible.from >= showingFrom && visible.to <= showingTo) return;\r\n            var from = Math.max(visible.from - 100, 0), to = Math.min(doc.size, visible.to + 100);\r\n            if (showingFrom < from && from - showingFrom < 20) from = showingFrom;\r\n            if (showingTo > to && showingTo - to < 20) to = Math.min(doc.size, showingTo);\r\n\r\n            // Create a range of theoretically intact lines, and punch holes\r\n            // in that using the change info.\r\n            var intact = changes === true ? [] :\r\n                computeIntact([{from: showingFrom, to: showingTo, domStart: 0}], changes);\r\n            // Clip off the parts that won't be visible\r\n            var intactLines = 0;\r\n            for (var i = 0; i < intact.length; ++i) {\r\n                var range = intact[i];\r\n                if (range.from < from) {range.domStart += (from - range.from); range.from = from;}\r\n                if (range.to > to) range.to = to;\r\n                if (range.from >= range.to) intact.splice(i--, 1);\r\n                else intactLines += range.to - range.from;\r\n            }\r\n            if (intactLines == to - from) return;\r\n            intact.sort(function(a, b) {return a.domStart - b.domStart;});\r\n\r\n            var th = textHeight(), gutterDisplay = gutter.style.display;\r\n            lineDiv.style.display = gutter.style.display = \"none\";\r\n            patchDisplay(from, to, intact);\r\n            lineDiv.style.display = \"\";\r\n\r\n            // Position the mover div to align with the lines it's supposed\r\n            // to be showing (which will cover the visible display)\r\n            var different = from != showingFrom || to != showingTo || lastSizeC != scroller.clientHeight + th;\r\n            // This is just a bogus formula that detects when the editor is\r\n            // resized or the font size changes.\r\n            if (different) lastSizeC = scroller.clientHeight + th;\r\n            showingFrom = from; showingTo = to;\r\n            displayOffset = heightAtLine(doc, from);\r\n            mover.style.top = (displayOffset * th) + \"px\";\r\n            code.style.height = (doc.height * th + 2 * paddingTop()) + \"px\";\r\n\r\n            // Since this is all rather error prone, it is honoured with the\r\n            // only assertion in the whole file.\r\n            if (lineDiv.childNodes.length != showingTo - showingFrom)\r\n                throw new Error(\"BAD PATCH! \" + JSON.stringify(intact) + \" size=\" + (showingTo - showingFrom) +\r\n                    \" nodes=\" + lineDiv.childNodes.length);\r\n\r\n            if (options.lineWrapping) {\r\n                maxWidth = scroller.clientWidth;\r\n                var curNode = lineDiv.firstChild;\r\n                doc.iter(showingFrom, showingTo, function(line) {\r\n                    if (!line.hidden) {\r\n                        var height = Math.round(curNode.offsetHeight / th) || 1;\r\n                        if (line.height != height) {updateLineHeight(line, height); gutterDirty = true;}\r\n                    }\r\n                    curNode = curNode.nextSibling;\r\n                });\r\n            } else {\r\n                if (maxWidth == null) maxWidth = stringWidth(maxLine);\r\n                if (maxWidth > scroller.clientWidth) {\r\n                    lineSpace.style.width = maxWidth + \"px\";\r\n                    // Needed to prevent odd wrapping/hiding of widgets placed in here.\r\n                    code.style.width = \"\";\r\n                    code.style.width = scroller.scrollWidth + \"px\";\r\n                } else {\r\n                    lineSpace.style.width = code.style.width = \"\";\r\n                }\r\n            }\r\n            gutter.style.display = gutterDisplay;\r\n            if (different || gutterDirty) updateGutter();\r\n            updateCursor();\r\n            if (!suppressCallback && options.onUpdate) options.onUpdate(instance);\r\n            return true;\r\n        }\r\n\r\n        function computeIntact(intact, changes) {\r\n            for (var i = 0, l = changes.length || 0; i < l; ++i) {\r\n                var change = changes[i], intact2 = [], diff = change.diff || 0;\r\n                for (var j = 0, l2 = intact.length; j < l2; ++j) {\r\n                    var range = intact[j];\r\n                    if (change.to <= range.from && change.diff)\r\n                        intact2.push({from: range.from + diff, to: range.to + diff,\r\n                            domStart: range.domStart});\r\n                    else if (change.to <= range.from || change.from >= range.to)\r\n                        intact2.push(range);\r\n                    else {\r\n                        if (change.from > range.from)\r\n                            intact2.push({from: range.from, to: change.from, domStart: range.domStart});\r\n                        if (change.to < range.to)\r\n                            intact2.push({from: change.to + diff, to: range.to + diff,\r\n                                domStart: range.domStart + (change.to - range.from)});\r\n                    }\r\n                }\r\n                intact = intact2;\r\n            }\r\n            return intact;\r\n        }\r\n\r\n        function patchDisplay(from, to, intact) {\r\n            // The first pass removes the DOM nodes that aren't intact.\r\n            if (!intact.length) lineDiv.innerHTML = \"\";\r\n            else {\r\n                function killNode(node) {\r\n                    var tmp = node.nextSibling;\r\n                    node.parentNode.removeChild(node);\r\n                    return tmp;\r\n                }\r\n                var domPos = 0, curNode = lineDiv.firstChild, n;\r\n                for (var i = 0; i < intact.length; ++i) {\r\n                    var cur = intact[i];\r\n                    while (cur.domStart > domPos) {curNode = killNode(curNode); domPos++;}\r\n                    for (var j = 0, e = cur.to - cur.from; j < e; ++j) {curNode = curNode.nextSibling; domPos++;}\r\n                }\r\n                while (curNode) curNode = killNode(curNode);\r\n            }\r\n            // This pass fills in the lines that actually changed.\r\n            var nextIntact = intact.shift(), curNode = lineDiv.firstChild, j = from;\r\n            var sfrom = sel.from.line, sto = sel.to.line, inSel = sfrom < from && sto >= from;\r\n            var scratch = targetDocument.createElement(\"div\"), newElt;\r\n            doc.iter(from, to, function(line) {\r\n                var ch1 = null, ch2 = null;\r\n                if (inSel) {\r\n                    ch1 = 0;\r\n                    if (sto == j) {inSel = false; ch2 = sel.to.ch;}\r\n                } else if (sfrom == j) {\r\n                    if (sto == j) {ch1 = sel.from.ch; ch2 = sel.to.ch;}\r\n                    else {inSel = true; ch1 = sel.from.ch;}\r\n                }\r\n                if (nextIntact && nextIntact.to == j) nextIntact = intact.shift();\r\n                if (!nextIntact || nextIntact.from > j) {\r\n                    if (line.hidden) scratch.innerHTML = \"<pre></pre>\";\r\n                    else scratch.innerHTML = line.getHTML(ch1, ch2, true, tabText);\r\n                    lineDiv.insertBefore(scratch.firstChild, curNode);\r\n                } else {\r\n                    curNode = curNode.nextSibling;\r\n                }\r\n                ++j;\r\n            });\r\n        }\r\n\r\n        function updateGutter() {\r\n            if (!options.gutter && !options.lineNumbers) return;\r\n            var hText = mover.offsetHeight, hEditor = scroller.clientHeight;\r\n            gutter.style.height = (hText - hEditor < 2 ? hEditor : hText) + \"px\";\r\n            var html = [], i = showingFrom;\r\n            doc.iter(showingFrom, Math.max(showingTo, showingFrom + 1), function(line) {\r\n                if (line.hidden) {\r\n                    html.push(\"<pre></pre>\");\r\n                } else {\r\n                    var marker = line.gutterMarker;\r\n                    var text = options.lineNumbers ? i + options.firstLineNumber : null;\r\n                    if (marker && marker.text)\r\n                        text = marker.text.replace(\"%N%\", text != null ? text : \"\");\r\n                    else if (text == null)\r\n                        text = \"\\u00a0\";\r\n                    html.push((marker && marker.style ? '<pre class=\"' + marker.style + '\">' : \"<pre>\"), text);\r\n                    for (var j = 1; j < line.height; ++j) html.push(\"<br/>&#160;\");\r\n                    html.push(\"</pre>\");\r\n                }\r\n                ++i;\r\n            });\r\n            gutter.style.display = \"none\";\r\n            gutterText.innerHTML = html.join(\"\");\r\n            var minwidth = String(doc.size).length, firstNode = gutterText.firstChild, val = eltText(firstNode), pad = \"\";\r\n            while (val.length + pad.length < minwidth) pad += \"\\u00a0\";\r\n            if (pad) firstNode.insertBefore(targetDocument.createTextNode(pad), firstNode.firstChild);\r\n            gutter.style.display = \"\";\r\n            lineSpace.style.marginLeft = gutter.offsetWidth + \"px\";\r\n            gutterDirty = false;\r\n        }\r\n        function updateCursor() {\r\n            var head = sel.inverted ? sel.from : sel.to, lh = textHeight();\r\n            var pos = localCoords(head, true);\r\n            var wrapOff = eltOffset(wrapper), lineOff = eltOffset(lineDiv);\r\n            inputDiv.style.top = (pos.y + lineOff.top - wrapOff.top) + \"px\";\r\n            inputDiv.style.left = (pos.x + lineOff.left - wrapOff.left) + \"px\";\r\n            if (posEq(sel.from, sel.to)) {\r\n                cursor.style.top = pos.y + \"px\";\r\n                cursor.style.left = (options.lineWrapping ? Math.min(pos.x, lineSpace.offsetWidth) : pos.x) + \"px\";\r\n                cursor.style.display = \"\";\r\n            }\r\n            else cursor.style.display = \"none\";\r\n        }\r\n\r\n        function setShift(val) {\r\n            if (val) shiftSelecting = shiftSelecting || (sel.inverted ? sel.to : sel.from);\r\n            else shiftSelecting = null;\r\n        }\r\n        function setSelectionUser(from, to) {\r\n            var sh = shiftSelecting && clipPos(shiftSelecting);\r\n            if (sh) {\r\n                if (posLess(sh, from)) from = sh;\r\n                else if (posLess(to, sh)) to = sh;\r\n            }\r\n            setSelection(from, to);\r\n            userSelChange = true;\r\n        }\r\n        // Update the selection. Last two args are only used by\r\n        // updateLines, since they have to be expressed in the line\r\n        // numbers before the update.\r\n        function setSelection(from, to, oldFrom, oldTo) {\r\n            goalColumn = null;\r\n            if (oldFrom == null) {oldFrom = sel.from.line; oldTo = sel.to.line;}\r\n            if (posEq(sel.from, from) && posEq(sel.to, to)) return;\r\n            if (posLess(to, from)) {var tmp = to; to = from; from = tmp;}\r\n\r\n            // Skip over hidden lines.\r\n            if (from.line != oldFrom) from = skipHidden(from, oldFrom, sel.from.ch);\r\n            if (to.line != oldTo) to = skipHidden(to, oldTo, sel.to.ch);\r\n\r\n            if (posEq(from, to)) sel.inverted = false;\r\n            else if (posEq(from, sel.to)) sel.inverted = false;\r\n            else if (posEq(to, sel.from)) sel.inverted = true;\r\n\r\n            // Some ugly logic used to only mark the lines that actually did\r\n            // see a change in selection as changed, rather than the whole\r\n            // selected range.\r\n            if (posEq(from, to)) {\r\n                if (!posEq(sel.from, sel.to))\r\n                    changes.push({from: oldFrom, to: oldTo + 1});\r\n            }\r\n            else if (posEq(sel.from, sel.to)) {\r\n                changes.push({from: from.line, to: to.line + 1});\r\n            }\r\n            else {\r\n                if (!posEq(from, sel.from)) {\r\n                    if (from.line < oldFrom)\r\n                        changes.push({from: from.line, to: Math.min(to.line, oldFrom) + 1});\r\n                    else\r\n                        changes.push({from: oldFrom, to: Math.min(oldTo, from.line) + 1});\r\n                }\r\n                if (!posEq(to, sel.to)) {\r\n                    if (to.line < oldTo)\r\n                        changes.push({from: Math.max(oldFrom, from.line), to: oldTo + 1});\r\n                    else\r\n                        changes.push({from: Math.max(from.line, oldTo), to: to.line + 1});\r\n                }\r\n            }\r\n            sel.from = from; sel.to = to;\r\n            selectionChanged = true;\r\n        }\r\n        function skipHidden(pos, oldLine, oldCh) {\r\n            function getNonHidden(dir) {\r\n                var lNo = pos.line + dir, end = dir == 1 ? doc.size : -1;\r\n                while (lNo != end) {\r\n                    var line = getLine(lNo);\r\n                    if (!line.hidden) {\r\n                        var ch = pos.ch;\r\n                        if (ch > oldCh || ch > line.text.length) ch = line.text.length;\r\n                        return {line: lNo, ch: ch};\r\n                    }\r\n                    lNo += dir;\r\n                }\r\n            }\r\n            var line = getLine(pos.line);\r\n            if (!line.hidden) return pos;\r\n            if (pos.line >= oldLine) return getNonHidden(1) || getNonHidden(-1);\r\n            else return getNonHidden(-1) || getNonHidden(1);\r\n        }\r\n        function setCursor(line, ch, user) {\r\n            var pos = clipPos({line: line, ch: ch || 0});\r\n            (user ? setSelectionUser : setSelection)(pos, pos);\r\n        }\r\n\r\n        function clipLine(n) {return Math.max(0, Math.min(n, doc.size-1));}\r\n        function clipPos(pos) {\r\n            if (pos.line < 0) return {line: 0, ch: 0};\r\n            if (pos.line >= doc.size) return {line: doc.size-1, ch: getLine(doc.size-1).text.length};\r\n            var ch = pos.ch, linelen = getLine(pos.line).text.length;\r\n            if (ch == null || ch > linelen) return {line: pos.line, ch: linelen};\r\n            else if (ch < 0) return {line: pos.line, ch: 0};\r\n            else return pos;\r\n        }\r\n\r\n        function findPosH(dir, unit) {\r\n            var end = sel.inverted ? sel.from : sel.to, line = end.line, ch = end.ch;\r\n            var lineObj = getLine(line);\r\n            function findNextLine() {\r\n                for (var l = line + dir, e = dir < 0 ? -1 : doc.size; l != e; l += dir) {\r\n                    var lo = getLine(l);\r\n                    if (!lo.hidden) { line = l; lineObj = lo; return true; }\r\n                }\r\n            }\r\n            function moveOnce(boundToLine) {\r\n                if (ch == (dir < 0 ? 0 : lineObj.text.length)) {\r\n                    if (!boundToLine && findNextLine()) ch = dir < 0 ? lineObj.text.length : 0;\r\n                    else return false;\r\n                } else ch += dir;\r\n                return true;\r\n            }\r\n            if (unit == \"char\") moveOnce();\r\n            else if (unit == \"column\") moveOnce(true);\r\n            else if (unit == \"word\") {\r\n                var sawWord = false;\r\n                for (;;) {\r\n                    if (dir < 0) if (!moveOnce()) break;\r\n                    if (isWordChar(lineObj.text.charAt(ch))) sawWord = true;\r\n                    else if (sawWord) {if (dir < 0) {dir = 1; moveOnce();} break;}\r\n                    if (dir > 0) if (!moveOnce()) break;\r\n                }\r\n            }\r\n            return {line: line, ch: ch};\r\n        }\r\n        function moveH(dir, unit) {\r\n            var pos = dir < 0 ? sel.from : sel.to;\r\n            if (shiftSelecting || posEq(sel.from, sel.to)) pos = findPosH(dir, unit);\r\n            setCursor(pos.line, pos.ch, true);\r\n        }\r\n        function deleteH(dir, unit) {\r\n            if (!posEq(sel.from, sel.to)) replaceRange(\"\", sel.from, sel.to);\r\n            else if (dir < 0) replaceRange(\"\", findPosH(dir, unit), sel.to);\r\n            else replaceRange(\"\", sel.from, findPosH(dir, unit));\r\n            userSelChange = true;\r\n        }\r\n        var goalColumn = null;\r\n        function moveV(dir, unit) {\r\n            var dist = 0, pos = localCoords(sel.inverted ? sel.from : sel.to, true);\r\n            if (goalColumn != null) pos.x = goalColumn;\r\n            if (unit == \"page\") dist = scroller.clientHeight;\r\n            else if (unit == \"line\") dist = textHeight();\r\n            var target = coordsChar(pos.x, pos.y + dist * dir + 2);\r\n            setCursor(target.line, target.ch, true);\r\n            goalColumn = pos.x;\r\n        }\r\n\r\n        function selectWordAt(pos) {\r\n            var line = getLine(pos.line).text;\r\n            var start = pos.ch, end = pos.ch;\r\n            while (start > 0 && isWordChar(line.charAt(start - 1))) --start;\r\n            while (end < line.length && isWordChar(line.charAt(end))) ++end;\r\n            setSelectionUser({line: pos.line, ch: start}, {line: pos.line, ch: end});\r\n        }\r\n        function selectLine(line) {\r\n            setSelectionUser({line: line, ch: 0}, {line: line, ch: getLine(line).text.length});\r\n        }\r\n        function indentSelected(mode) {\r\n            if (posEq(sel.from, sel.to)) return indentLine(sel.from.line, mode);\r\n            var e = sel.to.line - (sel.to.ch ? 0 : 1);\r\n            for (var i = sel.from.line; i <= e; ++i) indentLine(i, mode);\r\n        }\r\n\r\n        function indentLine(n, how) {\r\n            if (!how) how = \"add\";\r\n            if (how == \"smart\") {\r\n                if (!mode.indent) how = \"prev\";\r\n                else var state = getStateBefore(n);\r\n            }\r\n\r\n            var line = getLine(n), curSpace = line.indentation(options.tabSize),\r\n                curSpaceString = line.text.match(/^\\s*/)[0], indentation;\r\n            if (how == \"prev\") {\r\n                if (n) indentation = getLine(n-1).indentation(options.tabSize);\r\n                else indentation = 0;\r\n            }\r\n            else if (how == \"smart\") indentation = mode.indent(state, line.text.slice(curSpaceString.length), line.text);\r\n            else if (how == \"add\") indentation = curSpace + options.indentUnit;\r\n            else if (how == \"subtract\") indentation = curSpace - options.indentUnit;\r\n            indentation = Math.max(0, indentation);\r\n            var diff = indentation - curSpace;\r\n\r\n            if (!diff) {\r\n                if (sel.from.line != n && sel.to.line != n) return;\r\n                var indentString = curSpaceString;\r\n            }\r\n            else {\r\n                var indentString = \"\", pos = 0;\r\n                if (options.indentWithTabs)\r\n                    for (var i = Math.floor(indentation / options.tabSize); i; --i) {pos += options.tabSize; indentString += \"\\t\";}\r\n                while (pos < indentation) {++pos; indentString += \" \";}\r\n            }\r\n\r\n            replaceRange(indentString, {line: n, ch: 0}, {line: n, ch: curSpaceString.length});\r\n        }\r\n\r\n        function loadMode() {\r\n            mode = CodeMirror.getMode(options, options.mode);\r\n            doc.iter(0, doc.size, function(line) { line.stateAfter = null; });\r\n            work = [0];\r\n            startWorker();\r\n        }\r\n        function gutterChanged() {\r\n            var visible = options.gutter || options.lineNumbers;\r\n            gutter.style.display = visible ? \"\" : \"none\";\r\n            if (visible) gutterDirty = true;\r\n            else lineDiv.parentNode.style.marginLeft = 0;\r\n        }\r\n        function wrappingChanged(from, to) {\r\n            if (options.lineWrapping) {\r\n                wrapper.className += \" CodeMirror-wrap\";\r\n                var perLine = scroller.clientWidth / charWidth() - 3;\r\n                doc.iter(0, doc.size, function(line) {\r\n                    if (line.hidden) return;\r\n                    var guess = Math.ceil(line.text.length / perLine) || 1;\r\n                    if (guess != 1) updateLineHeight(line, guess);\r\n                });\r\n                lineSpace.style.width = code.style.width = \"\";\r\n            } else {\r\n                wrapper.className = wrapper.className.replace(\" CodeMirror-wrap\", \"\");\r\n                maxWidth = null; maxLine = \"\";\r\n                doc.iter(0, doc.size, function(line) {\r\n                    if (line.height != 1 && !line.hidden) updateLineHeight(line, 1);\r\n                    if (line.text.length > maxLine.length) maxLine = line.text;\r\n                });\r\n            }\r\n            changes.push({from: 0, to: doc.size});\r\n        }\r\n        function computeTabText() {\r\n            for (var str = '<span class=\"cm-tab\">', i = 0; i < options.tabSize; ++i) str += \" \";\r\n            return str + \"</span>\";\r\n        }\r\n        function tabsChanged() {\r\n            tabText = computeTabText();\r\n            updateDisplay(true);\r\n        }\r\n        function themeChanged() {\r\n            scroller.className = scroller.className.replace(/\\s*cm-s-\\w+/g, \"\") +\r\n                options.theme.replace(/(^|\\s)\\s*/g, \" cm-s-\");\r\n        }\r\n\r\n        function TextMarker() { this.set = []; }\r\n        TextMarker.prototype.clear = operation(function() {\r\n            var min = Infinity, max = -Infinity;\r\n            for (var i = 0, e = this.set.length; i < e; ++i) {\r\n                var line = this.set[i], mk = line.marked;\r\n                if (!mk || !line.parent) continue;\r\n                var lineN = lineNo(line);\r\n                min = Math.min(min, lineN); max = Math.max(max, lineN);\r\n                for (var j = 0; j < mk.length; ++j)\r\n                    if (mk[j].set == this.set) mk.splice(j--, 1);\r\n            }\r\n            if (min != Infinity)\r\n                changes.push({from: min, to: max + 1});\r\n        });\r\n        TextMarker.prototype.find = function() {\r\n            var from, to;\r\n            for (var i = 0, e = this.set.length; i < e; ++i) {\r\n                var line = this.set[i], mk = line.marked;\r\n                for (var j = 0; j < mk.length; ++j) {\r\n                    var mark = mk[j];\r\n                    if (mark.set == this.set) {\r\n                        if (mark.from != null || mark.to != null) {\r\n                            var found = lineNo(line);\r\n                            if (found != null) {\r\n                                if (mark.from != null) from = {line: found, ch: mark.from};\r\n                                if (mark.to != null) to = {line: found, ch: mark.to};\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n            return {from: from, to: to};\r\n        };\r\n\r\n        function markText(from, to, className) {\r\n            from = clipPos(from); to = clipPos(to);\r\n            var tm = new TextMarker();\r\n            function add(line, from, to, className) {\r\n                getLine(line).addMark(new MarkedText(from, to, className, tm.set));\r\n            }\r\n            if (from.line == to.line) add(from.line, from.ch, to.ch, className);\r\n            else {\r\n                add(from.line, from.ch, null, className);\r\n                for (var i = from.line + 1, e = to.line; i < e; ++i)\r\n                    add(i, null, null, className);\r\n                add(to.line, null, to.ch, className);\r\n            }\r\n            changes.push({from: from.line, to: to.line + 1});\r\n            return tm;\r\n        }\r\n\r\n        function setBookmark(pos) {\r\n            pos = clipPos(pos);\r\n            var bm = new Bookmark(pos.ch);\r\n            getLine(pos.line).addMark(bm);\r\n            return bm;\r\n        }\r\n\r\n        function addGutterMarker(line, text, className) {\r\n            if (typeof line == \"number\") line = getLine(clipLine(line));\r\n            line.gutterMarker = {text: text, style: className};\r\n            gutterDirty = true;\r\n            return line;\r\n        }\r\n        function removeGutterMarker(line) {\r\n            if (typeof line == \"number\") line = getLine(clipLine(line));\r\n            line.gutterMarker = null;\r\n            gutterDirty = true;\r\n        }\r\n\r\n        function changeLine(handle, op) {\r\n            var no = handle, line = handle;\r\n            if (typeof handle == \"number\") line = getLine(clipLine(handle));\r\n            else no = lineNo(handle);\r\n            if (no == null) return null;\r\n            if (op(line, no)) changes.push({from: no, to: no + 1});\r\n            else return null;\r\n            return line;\r\n        }\r\n        function setLineClass(handle, className) {\r\n            return changeLine(handle, function(line) {\r\n                if (line.className != className) {\r\n                    line.className = className;\r\n                    return true;\r\n                }\r\n            });\r\n        }\r\n        function setLineHidden(handle, hidden) {\r\n            return changeLine(handle, function(line, no) {\r\n                if (line.hidden != hidden) {\r\n                    line.hidden = hidden;\r\n                    updateLineHeight(line, hidden ? 0 : 1);\r\n                    if (hidden && (sel.from.line == no || sel.to.line == no))\r\n                        setSelection(skipHidden(sel.from, sel.from.line, sel.from.ch),\r\n                            skipHidden(sel.to, sel.to.line, sel.to.ch));\r\n                    return (gutterDirty = true);\r\n                }\r\n            });\r\n        }\r\n\r\n        function lineInfo(line) {\r\n            if (typeof line == \"number\") {\r\n                if (!isLine(line)) return null;\r\n                var n = line;\r\n                line = getLine(line);\r\n                if (!line) return null;\r\n            }\r\n            else {\r\n                var n = lineNo(line);\r\n                if (n == null) return null;\r\n            }\r\n            var marker = line.gutterMarker;\r\n            return {line: n, handle: line, text: line.text, markerText: marker && marker.text,\r\n                markerClass: marker && marker.style, lineClass: line.className};\r\n        }\r\n\r\n        function stringWidth(str) {\r\n            measure.innerHTML = \"<pre><span>x</span></pre>\";\r\n            measure.firstChild.firstChild.firstChild.nodeValue = str;\r\n            return measure.firstChild.firstChild.offsetWidth || 10;\r\n        }\r\n        // These are used to go from pixel positions to character\r\n        // positions, taking varying character widths into account.\r\n        function charFromX(line, x) {\r\n            if (x <= 0) return 0;\r\n            var lineObj = getLine(line), text = lineObj.text;\r\n            function getX(len) {\r\n                measure.innerHTML = \"<pre><span>\" + lineObj.getHTML(null, null, false, tabText, len) + \"</span></pre>\";\r\n                return measure.firstChild.firstChild.offsetWidth;\r\n            }\r\n            var from = 0, fromX = 0, to = text.length, toX;\r\n            // Guess a suitable upper bound for our search.\r\n            var estimated = Math.min(to, Math.ceil(x / charWidth()));\r\n            for (;;) {\r\n                var estX = getX(estimated);\r\n                if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2));\r\n                else {toX = estX; to = estimated; break;}\r\n            }\r\n            if (x > toX) return to;\r\n            // Try to guess a suitable lower bound as well.\r\n            estimated = Math.floor(to * 0.8); estX = getX(estimated);\r\n            if (estX < x) {from = estimated; fromX = estX;}\r\n            // Do a binary search between these bounds.\r\n            for (;;) {\r\n                if (to - from <= 1) return (toX - x > x - fromX) ? from : to;\r\n                var middle = Math.ceil((from + to) / 2), middleX = getX(middle);\r\n                if (middleX > x) {to = middle; toX = middleX;}\r\n                else {from = middle; fromX = middleX;}\r\n            }\r\n        }\r\n\r\n        var tempId = Math.floor(Math.random() * 0xffffff).toString(16);\r\n        function measureLine(line, ch) {\r\n            var extra = \"\";\r\n            // Include extra text at the end to make sure the measured line is wrapped in the right way.\r\n            if (options.lineWrapping) {\r\n                var end = line.text.indexOf(\" \", ch + 2);\r\n                extra = htmlEscape(line.text.slice(ch + 1, end < 0 ? line.text.length : end + (ie ? 5 : 0)));\r\n            }\r\n            measure.innerHTML = \"<pre>\" + line.getHTML(null, null, false, tabText, ch) +\r\n                '<span id=\"CodeMirror-temp-' + tempId + '\">' + htmlEscape(line.text.charAt(ch) || \" \") + \"</span>\" +\r\n                extra + \"</pre>\";\r\n            var elt = document.getElementById(\"CodeMirror-temp-\" + tempId);\r\n            var top = elt.offsetTop, left = elt.offsetLeft;\r\n            // Older IEs report zero offsets for spans directly after a wrap\r\n            if (ie && ch && top == 0 && left == 0) {\r\n                var backup = document.createElement(\"span\");\r\n                backup.innerHTML = \"x\";\r\n                elt.parentNode.insertBefore(backup, elt.nextSibling);\r\n                top = backup.offsetTop;\r\n            }\r\n            return {top: top, left: left};\r\n        }\r\n        function localCoords(pos, inLineWrap) {\r\n            var x, lh = textHeight(), y = lh * (heightAtLine(doc, pos.line) - (inLineWrap ? displayOffset : 0));\r\n            if (pos.ch == 0) x = 0;\r\n            else {\r\n                var sp = measureLine(getLine(pos.line), pos.ch);\r\n                x = sp.left;\r\n                if (options.lineWrapping) y += Math.max(0, sp.top);\r\n            }\r\n            return {x: x, y: y, yBot: y + lh};\r\n        }\r\n        // Coords must be lineSpace-local\r\n        function coordsChar(x, y) {\r\n            if (y < 0) y = 0;\r\n            var th = textHeight(), cw = charWidth(), heightPos = displayOffset + Math.floor(y / th);\r\n            var lineNo = lineAtHeight(doc, heightPos);\r\n            if (lineNo >= doc.size) return {line: doc.size - 1, ch: getLine(doc.size - 1).text.length};\r\n            var lineObj = getLine(lineNo), text = lineObj.text;\r\n            var tw = options.lineWrapping, innerOff = tw ? heightPos - heightAtLine(doc, lineNo) : 0;\r\n            if (x <= 0 && innerOff == 0) return {line: lineNo, ch: 0};\r\n            function getX(len) {\r\n                var sp = measureLine(lineObj, len);\r\n                if (tw) {\r\n                    var off = Math.round(sp.top / th);\r\n                    return Math.max(0, sp.left + (off - innerOff) * scroller.clientWidth);\r\n                }\r\n                return sp.left;\r\n            }\r\n            var from = 0, fromX = 0, to = text.length, toX;\r\n            // Guess a suitable upper bound for our search.\r\n            var estimated = Math.min(to, Math.ceil((x + innerOff * scroller.clientWidth * .9) / cw));\r\n            for (;;) {\r\n                var estX = getX(estimated);\r\n                if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2));\r\n                else {toX = estX; to = estimated; break;}\r\n            }\r\n            if (x > toX) return {line: lineNo, ch: to};\r\n            // Try to guess a suitable lower bound as well.\r\n            estimated = Math.floor(to * 0.8); estX = getX(estimated);\r\n            if (estX < x) {from = estimated; fromX = estX;}\r\n            // Do a binary search between these bounds.\r\n            for (;;) {\r\n                if (to - from <= 1) return {line: lineNo, ch: (toX - x > x - fromX) ? from : to};\r\n                var middle = Math.ceil((from + to) / 2), middleX = getX(middle);\r\n                if (middleX > x) {to = middle; toX = middleX;}\r\n                else {from = middle; fromX = middleX;}\r\n            }\r\n        }\r\n        function pageCoords(pos) {\r\n            var local = localCoords(pos, true), off = eltOffset(lineSpace);\r\n            return {x: off.left + local.x, y: off.top + local.y, yBot: off.top + local.yBot};\r\n        }\r\n\r\n        var cachedHeight, cachedHeightFor, measureText;\r\n        function textHeight() {\r\n            if (measureText == null) {\r\n                measureText = \"<pre>\";\r\n                for (var i = 0; i < 49; ++i) measureText += \"x<br/>\";\r\n                measureText += \"x</pre>\";\r\n            }\r\n            var offsetHeight = lineDiv.clientHeight;\r\n            if (offsetHeight == cachedHeightFor) return cachedHeight;\r\n            cachedHeightFor = offsetHeight;\r\n            measure.innerHTML = measureText;\r\n            cachedHeight = measure.firstChild.offsetHeight / 50 || 1;\r\n            measure.innerHTML = \"\";\r\n            return cachedHeight;\r\n        }\r\n        var cachedWidth, cachedWidthFor = 0;\r\n        function charWidth() {\r\n            if (scroller.clientWidth == cachedWidthFor) return cachedWidth;\r\n            cachedWidthFor = scroller.clientWidth;\r\n            return (cachedWidth = stringWidth(\"x\"));\r\n        }\r\n        function paddingTop() {return lineSpace.offsetTop;}\r\n        function paddingLeft() {return lineSpace.offsetLeft;}\r\n\r\n        function posFromMouse(e, liberal) {\r\n            var offW = eltOffset(scroller, true), x, y;\r\n            // Fails unpredictably on IE[67] when mouse is dragged around quickly.\r\n            try { x = e.clientX; y = e.clientY; } catch (e) { return null; }\r\n            // This is a mess of a heuristic to try and determine whether a\r\n            // scroll-bar was clicked or not, and to return null if one was\r\n            // (and !liberal).\r\n            if (!liberal && (x - offW.left > scroller.clientWidth || y - offW.top > scroller.clientHeight))\r\n                return null;\r\n            var offL = eltOffset(lineSpace, true);\r\n            return coordsChar(x - offL.left, y - offL.top);\r\n        }\r\n        function onContextMenu(e) {\r\n            var pos = posFromMouse(e);\r\n            if (!pos || window.opera) return; // Opera is difficult.\r\n            if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to))\r\n                operation(setCursor)(pos.line, pos.ch);\r\n\r\n            var oldCSS = input.style.cssText;\r\n            inputDiv.style.position = \"absolute\";\r\n            input.style.cssText = \"position: fixed; width: 30px; height: 30px; top: \" + (e.clientY - 5) +\r\n                \"px; left: \" + (e.clientX - 5) + \"px; z-index: 1000; background: white; \" +\r\n                \"border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);\";\r\n            leaveInputAlone = true;\r\n            var val = input.value = getSelection();\r\n            focusInput();\r\n            input.select();\r\n            function rehide() {\r\n                var newVal = splitLines(input.value).join(\"\\n\");\r\n                if (newVal != val) operation(replaceSelection)(newVal, \"end\");\r\n                inputDiv.style.position = \"relative\";\r\n                input.style.cssText = oldCSS;\r\n                leaveInputAlone = false;\r\n                resetInput(true);\r\n                slowPoll();\r\n            }\r\n\r\n            if (gecko) {\r\n                e_stop(e);\r\n                var mouseup = connect(window, \"mouseup\", function() {\r\n                    mouseup();\r\n                    setTimeout(rehide, 20);\r\n                }, true);\r\n            }\r\n            else {\r\n                setTimeout(rehide, 50);\r\n            }\r\n        }\r\n\r\n        // Cursor-blinking\r\n        function restartBlink() {\r\n            clearInterval(blinker);\r\n            var on = true;\r\n            cursor.style.visibility = \"\";\r\n            blinker = setInterval(function() {\r\n                cursor.style.visibility = (on = !on) ? \"\" : \"hidden\";\r\n            }, 650);\r\n        }\r\n\r\n        var matching = {\"(\": \")>\", \")\": \"(<\", \"[\": \"]>\", \"]\": \"[<\", \"{\": \"}>\", \"}\": \"{<\"};\r\n        function matchBrackets(autoclear) {\r\n            var head = sel.inverted ? sel.from : sel.to, line = getLine(head.line), pos = head.ch - 1;\r\n            var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)];\r\n            if (!match) return;\r\n            var ch = match.charAt(0), forward = match.charAt(1) == \">\", d = forward ? 1 : -1, st = line.styles;\r\n            for (var off = pos + 1, i = 0, e = st.length; i < e; i+=2)\r\n                if ((off -= st[i].length) <= 0) {var style = st[i+1]; break;}\r\n\r\n            var stack = [line.text.charAt(pos)], re = /[(){}[\\]]/;\r\n            function scan(line, from, to) {\r\n                if (!line.text) return;\r\n                var st = line.styles, pos = forward ? 0 : line.text.length - 1, cur;\r\n                for (var i = forward ? 0 : st.length - 2, e = forward ? st.length : -2; i != e; i += 2*d) {\r\n                    var text = st[i];\r\n                    if (st[i+1] != null && st[i+1] != style) {pos += d * text.length; continue;}\r\n                    for (var j = forward ? 0 : text.length - 1, te = forward ? text.length : -1; j != te; j += d, pos+=d) {\r\n                        if (pos >= from && pos < to && re.test(cur = text.charAt(j))) {\r\n                            var match = matching[cur];\r\n                            if (match.charAt(1) == \">\" == forward) stack.push(cur);\r\n                            else if (stack.pop() != match.charAt(0)) return {pos: pos, match: false};\r\n                            else if (!stack.length) return {pos: pos, match: true};\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n            for (var i = head.line, e = forward ? Math.min(i + 100, doc.size) : Math.max(-1, i - 100); i != e; i+=d) {\r\n                var line = getLine(i), first = i == head.line;\r\n                var found = scan(line, first && forward ? pos + 1 : 0, first && !forward ? pos : line.text.length);\r\n                if (found) break;\r\n            }\r\n            if (!found) found = {pos: null, match: false};\r\n            var style = found.match ? \"CodeMirror-matchingbracket\" : \"CodeMirror-nonmatchingbracket\";\r\n            var one = markText({line: head.line, ch: pos}, {line: head.line, ch: pos+1}, style),\r\n                two = found.pos != null && markText({line: i, ch: found.pos}, {line: i, ch: found.pos + 1}, style);\r\n            var clear = operation(function(){one.clear(); two && two.clear();});\r\n            if (autoclear) setTimeout(clear, 800);\r\n            else bracketHighlighted = clear;\r\n        }\r\n\r\n        // Finds the line to start with when starting a parse. Tries to\r\n        // find a line with a stateAfter, so that it can start with a\r\n        // valid state. If that fails, it returns the line with the\r\n        // smallest indentation, which tends to need the least context to\r\n        // parse correctly.\r\n        function findStartLine(n) {\r\n            var minindent, minline;\r\n            for (var search = n, lim = n - 40; search > lim; --search) {\r\n                if (search == 0) return 0;\r\n                var line = getLine(search-1);\r\n                if (line.stateAfter) return search;\r\n                var indented = line.indentation(options.tabSize);\r\n                if (minline == null || minindent > indented) {\r\n                    minline = search - 1;\r\n                    minindent = indented;\r\n                }\r\n            }\r\n            return minline;\r\n        }\r\n        function getStateBefore(n) {\r\n            var start = findStartLine(n), state = start && getLine(start-1).stateAfter;\r\n            if (!state) state = startState(mode);\r\n            else state = copyState(mode, state);\r\n            doc.iter(start, n, function(line) {\r\n                line.highlight(mode, state, options.tabSize);\r\n                line.stateAfter = copyState(mode, state);\r\n            });\r\n            if (start < n) changes.push({from: start, to: n});\r\n            if (n < doc.size && !getLine(n).stateAfter) work.push(n);\r\n            return state;\r\n        }\r\n        function highlightLines(start, end) {\r\n            var state = getStateBefore(start);\r\n            doc.iter(start, end, function(line) {\r\n                line.highlight(mode, state, options.tabSize);\r\n                line.stateAfter = copyState(mode, state);\r\n            });\r\n        }\r\n        function highlightWorker() {\r\n            var end = +new Date + options.workTime;\r\n            var foundWork = work.length;\r\n            while (work.length) {\r\n                if (!getLine(showingFrom).stateAfter) var task = showingFrom;\r\n                else var task = work.pop();\r\n                if (task >= doc.size) continue;\r\n                var start = findStartLine(task), state = start && getLine(start-1).stateAfter;\r\n                if (state) state = copyState(mode, state);\r\n                else state = startState(mode);\r\n\r\n                var unchanged = 0, compare = mode.compareStates, realChange = false,\r\n                    i = start, bail = false;\r\n                doc.iter(i, doc.size, function(line) {\r\n                    var hadState = line.stateAfter;\r\n                    if (+new Date > end) {\r\n                        work.push(i);\r\n                        startWorker(options.workDelay);\r\n                        if (realChange) changes.push({from: task, to: i + 1});\r\n                        return (bail = true);\r\n                    }\r\n                    var changed = line.highlight(mode, state, options.tabSize);\r\n                    if (changed) realChange = true;\r\n                    line.stateAfter = copyState(mode, state);\r\n                    if (compare) {\r\n                        if (hadState && compare(hadState, state)) return true;\r\n                    } else {\r\n                        if (changed !== false || !hadState) unchanged = 0;\r\n                        else if (++unchanged > 3 && (!mode.indent || mode.indent(hadState, \"\") == mode.indent(state, \"\")))\r\n                            return true;\r\n                    }\r\n                    ++i;\r\n                });\r\n                if (bail) return;\r\n                if (realChange) changes.push({from: task, to: i + 1});\r\n            }\r\n            if (foundWork && options.onHighlightComplete)\r\n                options.onHighlightComplete(instance);\r\n        }\r\n        function startWorker(time) {\r\n            if (!work.length) return;\r\n            highlight.set(time, operation(highlightWorker));\r\n        }\r\n\r\n        // Operations are used to wrap changes in such a way that each\r\n        // change won't have to update the cursor and display (which would\r\n        // be awkward, slow, and error-prone), but instead updates are\r\n        // batched and then all combined and executed at once.\r\n        function startOperation() {\r\n            updateInput = userSelChange = textChanged = null;\r\n            changes = []; selectionChanged = false; callbacks = [];\r\n        }\r\n        function endOperation() {\r\n            var reScroll = false, updated;\r\n            if (selectionChanged) reScroll = !scrollCursorIntoView();\r\n            if (changes.length) updated = updateDisplay(changes, true);\r\n            else {\r\n                if (selectionChanged) updateCursor();\r\n                if (gutterDirty) updateGutter();\r\n            }\r\n            if (reScroll) scrollCursorIntoView();\r\n            if (selectionChanged) {scrollEditorIntoView(); restartBlink();}\r\n\r\n            if (focused && !leaveInputAlone &&\r\n                (updateInput === true || (updateInput !== false && selectionChanged)))\r\n                resetInput(userSelChange);\r\n\r\n            if (selectionChanged && options.matchBrackets)\r\n                setTimeout(operation(function() {\r\n                    if (bracketHighlighted) {bracketHighlighted(); bracketHighlighted = null;}\r\n                    if (posEq(sel.from, sel.to)) matchBrackets(false);\r\n                }), 20);\r\n            var tc = textChanged, cbs = callbacks; // these can be reset by callbacks\r\n            if (selectionChanged && options.onCursorActivity)\r\n                options.onCursorActivity(instance);\r\n            if (tc && options.onChange && instance)\r\n                options.onChange(instance, tc);\r\n            for (var i = 0; i < cbs.length; ++i) cbs[i](instance);\r\n            if (updated && options.onUpdate) options.onUpdate(instance);\r\n        }\r\n        var nestedOperation = 0;\r\n        function operation(f) {\r\n            return function() {\r\n                if (!nestedOperation++) startOperation();\r\n                try {var result = f.apply(this, arguments);}\r\n                finally {if (!--nestedOperation) endOperation();}\r\n                return result;\r\n            };\r\n        }\r\n\r\n        for (var ext in extensions)\r\n            if (extensions.propertyIsEnumerable(ext) &&\r\n                !instance.propertyIsEnumerable(ext))\r\n                instance[ext] = extensions[ext];\r\n        return instance;\r\n    } // (end of function CodeMirror)\r\n\r\n    // The default configuration options.\r\n    CodeMirror.defaults = {\r\n        value: \"\",\r\n        mode: null,\r\n        theme: \"default\",\r\n        indentUnit: 2,\r\n        indentWithTabs: false,\r\n        tabSize: 4,\r\n        keyMap: \"default\",\r\n        extraKeys: null,\r\n        electricChars: true,\r\n        onKeyEvent: null,\r\n        lineWrapping: false,\r\n        lineNumbers: false,\r\n        gutter: false,\r\n        fixedGutter: false,\r\n        firstLineNumber: 1,\r\n        readOnly: false,\r\n        onChange: null,\r\n        onCursorActivity: null,\r\n        onGutterClick: null,\r\n        onHighlightComplete: null,\r\n        onUpdate: null,\r\n        onFocus: null, onBlur: null, onScroll: null,\r\n        matchBrackets: false,\r\n        workTime: 100,\r\n        workDelay: 200,\r\n        pollInterval: 100,\r\n        undoDepth: 40,\r\n        tabindex: null,\r\n        document: window.document\r\n    };\r\n\r\n    var mac = /Mac/.test(navigator.platform);\r\n    var win = /Win/.test(navigator.platform);\r\n\r\n    // Known modes, by name and by MIME\r\n    var modes = {}, mimeModes = {};\r\n    CodeMirror.defineMode = function(name, mode) {\r\n        if (!CodeMirror.defaults.mode && name != \"null\") CodeMirror.defaults.mode = name;\r\n        modes[name] = mode;\r\n    };\r\n    CodeMirror.defineMIME = function(mime, spec) {\r\n        mimeModes[mime] = spec;\r\n    };\r\n    CodeMirror.getMode = function(options, spec) {\r\n        if (typeof spec == \"string\" && mimeModes.hasOwnProperty(spec))\r\n            spec = mimeModes[spec];\r\n        if (typeof spec == \"string\")\r\n            var mname = spec, config = {};\r\n        else if (spec != null)\r\n            var mname = spec.name, config = spec;\r\n        var mfactory = modes[mname];\r\n        if (!mfactory) {\r\n            if (window.console) console.warn(\"No mode \" + mname + \" found, falling back to plain text.\");\r\n            return CodeMirror.getMode(options, \"text/plain\");\r\n        }\r\n        return mfactory(options, config || {});\r\n    };\r\n    CodeMirror.listModes = function() {\r\n        var list = [];\r\n        for (var m in modes)\r\n            if (modes.propertyIsEnumerable(m)) list.push(m);\r\n        return list;\r\n    };\r\n    CodeMirror.listMIMEs = function() {\r\n        var list = [];\r\n        for (var m in mimeModes)\r\n            if (mimeModes.propertyIsEnumerable(m)) list.push({mime: m, mode: mimeModes[m]});\r\n        return list;\r\n    };\r\n\r\n    var extensions = CodeMirror.extensions = {};\r\n    CodeMirror.defineExtension = function(name, func) {\r\n        extensions[name] = func;\r\n    };\r\n\r\n    var commands = CodeMirror.commands = {\r\n        selectAll: function(cm) {cm.setSelection({line: 0, ch: 0}, {line: cm.lineCount() - 1});},\r\n        killLine: function(cm) {\r\n            var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);\r\n            if (!sel && cm.getLine(from.line).length == from.ch) cm.replaceRange(\"\", from, {line: from.line + 1, ch: 0});\r\n            else cm.replaceRange(\"\", from, sel ? to : {line: from.line});\r\n        },\r\n        deleteLine: function(cm) {var l = cm.getCursor().line; cm.replaceRange(\"\", {line: l, ch: 0}, {line: l});},\r\n        undo: function(cm) {cm.undo();},\r\n        redo: function(cm) {cm.redo();},\r\n        goDocStart: function(cm) {cm.setCursor(0, 0, true);},\r\n        goDocEnd: function(cm) {cm.setSelection({line: cm.lineCount() - 1}, null, true);},\r\n        goLineStart: function(cm) {cm.setCursor(cm.getCursor().line, 0, true);},\r\n        goLineStartSmart: function(cm) {\r\n            var cur = cm.getCursor();\r\n            var text = cm.getLine(cur.line), firstNonWS = Math.max(0, text.search(/\\S/));\r\n            cm.setCursor(cur.line, cur.ch <= firstNonWS && cur.ch ? 0 : firstNonWS, true);\r\n        },\r\n        goLineEnd: function(cm) {cm.setSelection({line: cm.getCursor().line}, null, true);},\r\n        goLineUp: function(cm) {cm.moveV(-1, \"line\");},\r\n        goLineDown: function(cm) {cm.moveV(1, \"line\");},\r\n        goPageUp: function(cm) {cm.moveV(-1, \"page\");},\r\n        goPageDown: function(cm) {cm.moveV(1, \"page\");},\r\n        goCharLeft: function(cm) {cm.moveH(-1, \"char\");},\r\n        goCharRight: function(cm) {cm.moveH(1, \"char\");},\r\n        goColumnLeft: function(cm) {cm.moveH(-1, \"column\");},\r\n        goColumnRight: function(cm) {cm.moveH(1, \"column\");},\r\n        goWordLeft: function(cm) {cm.moveH(-1, \"word\");},\r\n        goWordRight: function(cm) {cm.moveH(1, \"word\");},\r\n        delCharLeft: function(cm) {cm.deleteH(-1, \"char\");},\r\n        delCharRight: function(cm) {cm.deleteH(1, \"char\");},\r\n        delWordLeft: function(cm) {cm.deleteH(-1, \"word\");},\r\n        delWordRight: function(cm) {cm.deleteH(1, \"word\");},\r\n        indentAuto: function(cm) {cm.indentSelection(\"smart\");},\r\n        indentMore: function(cm) {cm.indentSelection(\"add\");},\r\n        indentLess: function(cm) {cm.indentSelection(\"subtract\");},\r\n        insertTab: function(cm) {cm.replaceSelection(\"\\t\", \"end\");},\r\n        transposeChars: function(cm) {\r\n            var cur = cm.getCursor(), line = cm.getLine(cur.line);\r\n            if (cur.ch > 0 && cur.ch < line.length - 1)\r\n                cm.replaceRange(line.charAt(cur.ch) + line.charAt(cur.ch - 1),\r\n                    {line: cur.line, ch: cur.ch - 1}, {line: cur.line, ch: cur.ch + 1});\r\n        },\r\n        newlineAndIndent: function(cm) {\r\n            cm.replaceSelection(\"\\n\", \"end\");\r\n            cm.indentLine(cm.getCursor().line);\r\n        },\r\n        toggleOverwrite: function(cm) {cm.toggleOverwrite();}\r\n    };\r\n\r\n    var keyMap = CodeMirror.keyMap = {};\r\n    keyMap.basic = {\r\n        \"Left\": \"goCharLeft\", \"Right\": \"goCharRight\", \"Up\": \"goLineUp\", \"Down\": \"goLineDown\",\r\n        \"End\": \"goLineEnd\", \"Home\": \"goLineStartSmart\", \"PageUp\": \"goPageUp\", \"PageDown\": \"goPageDown\",\r\n        \"Delete\": \"delCharRight\", \"Backspace\": \"delCharLeft\", \"Tab\": \"indentMore\", \"Shift-Tab\": \"indentLess\",\r\n        \"Enter\": \"newlineAndIndent\", \"Insert\": \"toggleOverwrite\"\r\n    };\r\n    // Note that the save and find-related commands aren't defined by\r\n    // default. Unknown commands are simply ignored.\r\n    keyMap.pcDefault = {\r\n        \"Ctrl-A\": \"selectAll\", \"Ctrl-D\": \"deleteLine\", \"Ctrl-Z\": \"undo\", \"Shift-Ctrl-Z\": \"redo\", \"Ctrl-Y\": \"redo\",\r\n        \"Ctrl-Home\": \"goDocStart\", \"Alt-Up\": \"goDocStart\", \"Ctrl-End\": \"goDocEnd\", \"Ctrl-Down\": \"goDocEnd\",\r\n        \"Ctrl-Left\": \"goWordLeft\", \"Ctrl-Right\": \"goWordRight\", \"Alt-Left\": \"goLineStart\", \"Alt-Right\": \"goLineEnd\",\r\n        \"Ctrl-Backspace\": \"delWordLeft\", \"Ctrl-Delete\": \"delWordRight\", \"Ctrl-S\": \"save\", \"Ctrl-F\": \"find\",\r\n        \"Ctrl-G\": \"findNext\", \"Shift-Ctrl-G\": \"findPrev\", \"Shift-Ctrl-F\": \"replace\", \"Shift-Ctrl-R\": \"replaceAll\",\r\n        fallthrough: \"basic\"\r\n    };\r\n    keyMap.macDefault = {\r\n        \"Cmd-A\": \"selectAll\", \"Cmd-D\": \"deleteLine\", \"Cmd-Z\": \"undo\", \"Shift-Cmd-Z\": \"redo\", \"Cmd-Y\": \"redo\",\r\n        \"Cmd-Up\": \"goDocStart\", \"Cmd-End\": \"goDocEnd\", \"Cmd-Down\": \"goDocEnd\", \"Alt-Left\": \"goWordLeft\",\r\n        \"Alt-Right\": \"goWordRight\", \"Cmd-Left\": \"goLineStart\", \"Cmd-Right\": \"goLineEnd\", \"Alt-Backspace\": \"delWordLeft\",\r\n        \"Ctrl-Alt-Backspace\": \"delWordRight\", \"Alt-Delete\": \"delWordRight\", \"Cmd-S\": \"save\", \"Cmd-F\": \"find\",\r\n        \"Cmd-G\": \"findNext\", \"Shift-Cmd-G\": \"findPrev\", \"Cmd-Alt-F\": \"replace\", \"Shift-Cmd-Alt-F\": \"replaceAll\",\r\n        fallthrough: [\"basic\", \"emacsy\"]\r\n    };\r\n    keyMap[\"default\"] = mac ? keyMap.macDefault : keyMap.pcDefault;\r\n    keyMap.emacsy = {\r\n        \"Ctrl-F\": \"goCharRight\", \"Ctrl-B\": \"goCharLeft\", \"Ctrl-P\": \"goLineUp\", \"Ctrl-N\": \"goLineDown\",\r\n        \"Alt-F\": \"goWordRight\", \"Alt-B\": \"goWordLeft\", \"Ctrl-A\": \"goLineStart\", \"Ctrl-E\": \"goLineEnd\",\r\n        \"Ctrl-V\": \"goPageUp\", \"Shift-Ctrl-V\": \"goPageDown\", \"Ctrl-D\": \"delCharRight\", \"Ctrl-H\": \"delCharLeft\",\r\n        \"Alt-D\": \"delWordRight\", \"Alt-Backspace\": \"delWordLeft\", \"Ctrl-K\": \"killLine\", \"Ctrl-T\": \"transposeChars\"\r\n    };\r\n\r\n    function lookupKey(name, extraMap, map) {\r\n        function lookup(name, map, ft) {\r\n            var found = map[name];\r\n            if (found != null) return found;\r\n            if (ft == null) ft = map.fallthrough;\r\n            if (ft == null) return map.catchall;\r\n            if (typeof ft == \"string\") return lookup(name, keyMap[ft]);\r\n            for (var i = 0, e = ft.length; i < e; ++i) {\r\n                found = lookup(name, keyMap[ft[i]]);\r\n                if (found != null) return found;\r\n            }\r\n            return null;\r\n        }\r\n        return extraMap ? lookup(name, extraMap, map) : lookup(name, keyMap[map]);\r\n    }\r\n    function isModifierKey(event) {\r\n        var name = keyNames[event.keyCode];\r\n        return name == \"Ctrl\" || name == \"Alt\" || name == \"Shift\" || name == \"Mod\";\r\n    }\r\n\r\n    CodeMirror.fromTextArea = function(textarea, options) {\r\n        if (!options) options = {};\r\n        options.value = textarea.value;\r\n        if (!options.tabindex && textarea.tabindex)\r\n            options.tabindex = textarea.tabindex;\r\n\r\n        function save() {textarea.value = instance.getValue();}\r\n        if (textarea.form) {\r\n            // Deplorable hack to make the submit method do the right thing.\r\n            var rmSubmit = connect(textarea.form, \"submit\", save, true);\r\n            if (typeof textarea.form.submit == \"function\") {\r\n                var realSubmit = textarea.form.submit;\r\n                function wrappedSubmit() {\r\n                    save();\r\n                    textarea.form.submit = realSubmit;\r\n                    textarea.form.submit();\r\n                    textarea.form.submit = wrappedSubmit;\r\n                }\r\n                textarea.form.submit = wrappedSubmit;\r\n            }\r\n        }\r\n\r\n        textarea.style.display = \"none\";\r\n        var instance = CodeMirror(function(node) {\r\n            textarea.parentNode.insertBefore(node, textarea.nextSibling);\r\n        }, options);\r\n        instance.save = save;\r\n        instance.getTextArea = function() { return textarea; };\r\n        instance.toTextArea = function() {\r\n            save();\r\n            textarea.parentNode.removeChild(instance.getWrapperElement());\r\n            textarea.style.display = \"\";\r\n            if (textarea.form) {\r\n                rmSubmit();\r\n                if (typeof textarea.form.submit == \"function\")\r\n                    textarea.form.submit = realSubmit;\r\n            }\r\n        };\r\n        return instance;\r\n    };\r\n\r\n    // Utility functions for working with state. Exported because modes\r\n    // sometimes need to do this.\r\n    function copyState(mode, state) {\r\n        if (state === true) return state;\r\n        if (mode.copyState) return mode.copyState(state);\r\n        var nstate = {};\r\n        for (var n in state) {\r\n            var val = state[n];\r\n            if (val instanceof Array) val = val.concat([]);\r\n            nstate[n] = val;\r\n        }\r\n        return nstate;\r\n    }\r\n    CodeMirror.copyState = copyState;\r\n    function startState(mode, a1, a2) {\r\n        return mode.startState ? mode.startState(a1, a2) : true;\r\n    }\r\n    CodeMirror.startState = startState;\r\n\r\n    // The character stream used by a mode's parser.\r\n    function StringStream(string, tabSize) {\r\n        this.pos = this.start = 0;\r\n        this.string = string;\r\n        this.tabSize = tabSize || 8;\r\n    }\r\n    StringStream.prototype = {\r\n        eol: function() {return this.pos >= this.string.length;},\r\n        sol: function() {return this.pos == 0;},\r\n        peek: function() {return this.string.charAt(this.pos);},\r\n        next: function() {\r\n            if (this.pos < this.string.length)\r\n                return this.string.charAt(this.pos++);\r\n        },\r\n        eat: function(match) {\r\n            var ch = this.string.charAt(this.pos);\r\n            if (typeof match == \"string\") var ok = ch == match;\r\n            else var ok = ch && (match.test ? match.test(ch) : match(ch));\r\n            if (ok) {++this.pos; return ch;}\r\n        },\r\n        eatWhile: function(match) {\r\n            var start = this.pos;\r\n            while (this.eat(match)){}\r\n            return this.pos > start;\r\n        },\r\n        eatSpace: function() {\r\n            var start = this.pos;\r\n            while (/[\\s\\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos;\r\n            return this.pos > start;\r\n        },\r\n        skipToEnd: function() {this.pos = this.string.length;},\r\n        skipTo: function(ch) {\r\n            var found = this.string.indexOf(ch, this.pos);\r\n            if (found > -1) {this.pos = found; return true;}\r\n        },\r\n        backUp: function(n) {this.pos -= n;},\r\n        column: function() {return countColumn(this.string, this.start, this.tabSize);},\r\n        indentation: function() {return countColumn(this.string, null, this.tabSize);},\r\n        match: function(pattern, consume, caseInsensitive) {\r\n            if (typeof pattern == \"string\") {\r\n                function cased(str) {return caseInsensitive ? str.toLowerCase() : str;}\r\n                if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) {\r\n                    if (consume !== false) this.pos += pattern.length;\r\n                    return true;\r\n                }\r\n            }\r\n            else {\r\n                var match = this.string.slice(this.pos).match(pattern);\r\n                if (match && consume !== false) this.pos += match[0].length;\r\n                return match;\r\n            }\r\n        },\r\n        current: function(){return this.string.slice(this.start, this.pos);}\r\n    };\r\n    CodeMirror.StringStream = StringStream;\r\n\r\n    function MarkedText(from, to, className, set) {\r\n        this.from = from; this.to = to; this.style = className; this.set = set;\r\n    }\r\n    MarkedText.prototype = {\r\n        attach: function(line) { this.set.push(line); },\r\n        detach: function(line) {\r\n            var ix = indexOf(this.set, line);\r\n            if (ix > -1) this.set.splice(ix, 1);\r\n        },\r\n        split: function(pos, lenBefore) {\r\n            if (this.to <= pos && this.to != null) return null;\r\n            var from = this.from < pos || this.from == null ? null : this.from - pos + lenBefore;\r\n            var to = this.to == null ? null : this.to - pos + lenBefore;\r\n            return new MarkedText(from, to, this.style, this.set);\r\n        },\r\n        dup: function() { return new MarkedText(null, null, this.style, this.set); },\r\n        clipTo: function(fromOpen, from, toOpen, to, diff) {\r\n            if (this.from != null && this.from >= from)\r\n                this.from = Math.max(to, this.from) + diff;\r\n            if (this.to != null && this.to > from)\r\n                this.to = to < this.to ? this.to + diff : from;\r\n            if (fromOpen && to > this.from && (to < this.to || this.to == null))\r\n                this.from = null;\r\n            if (toOpen && (from < this.to || this.to == null) && (from > this.from || this.from == null))\r\n                this.to = null;\r\n        },\r\n        isDead: function() { return this.from != null && this.to != null && this.from >= this.to; },\r\n        sameSet: function(x) { return this.set == x.set; }\r\n    };\r\n\r\n    function Bookmark(pos) {\r\n        this.from = pos; this.to = pos; this.line = null;\r\n    }\r\n    Bookmark.prototype = {\r\n        attach: function(line) { this.line = line; },\r\n        detach: function(line) { if (this.line == line) this.line = null; },\r\n        split: function(pos, lenBefore) {\r\n            if (pos < this.from) {\r\n                this.from = this.to = (this.from - pos) + lenBefore;\r\n                return this;\r\n            }\r\n        },\r\n        isDead: function() { return this.from > this.to; },\r\n        clipTo: function(fromOpen, from, toOpen, to, diff) {\r\n            if ((fromOpen || from < this.from) && (toOpen || to > this.to)) {\r\n                this.from = 0; this.to = -1;\r\n            } else if (this.from > from) {\r\n                this.from = this.to = Math.max(to, this.from) + diff;\r\n            }\r\n        },\r\n        sameSet: function(x) { return false; },\r\n        find: function() {\r\n            if (!this.line || !this.line.parent) return null;\r\n            return {line: lineNo(this.line), ch: this.from};\r\n        },\r\n        clear: function() {\r\n            if (this.line) {\r\n                var found = indexOf(this.line.marked, this);\r\n                if (found != -1) this.line.marked.splice(found, 1);\r\n                this.line = null;\r\n            }\r\n        }\r\n    };\r\n\r\n    // Line objects. These hold state related to a line, including\r\n    // highlighting info (the styles array).\r\n    function Line(text, styles) {\r\n        this.styles = styles || [text, null];\r\n        this.text = text;\r\n        this.height = 1;\r\n        this.marked = this.gutterMarker = this.className = this.handlers = null;\r\n        this.stateAfter = this.parent = this.hidden = null;\r\n    }\r\n    Line.inheritMarks = function(text, orig) {\r\n        var ln = new Line(text), mk = orig && orig.marked;\r\n        if (mk) {\r\n            for (var i = 0; i < mk.length; ++i) {\r\n                if (mk[i].to == null && mk[i].style) {\r\n                    var newmk = ln.marked || (ln.marked = []), mark = mk[i];\r\n                    var nmark = mark.dup(); newmk.push(nmark); nmark.attach(ln);\r\n                }\r\n            }\r\n        }\r\n        return ln;\r\n    }\r\n    Line.prototype = {\r\n        // Replace a piece of a line, keeping the styles around it intact.\r\n        replace: function(from, to_, text) {\r\n            var st = [], mk = this.marked, to = to_ == null ? this.text.length : to_;\r\n            copyStyles(0, from, this.styles, st);\r\n            if (text) st.push(text, null);\r\n            copyStyles(to, this.text.length, this.styles, st);\r\n            this.styles = st;\r\n            this.text = this.text.slice(0, from) + text + this.text.slice(to);\r\n            this.stateAfter = null;\r\n            if (mk) {\r\n                var diff = text.length - (to - from);\r\n                for (var i = 0, mark = mk[i]; i < mk.length; ++i) {\r\n                    mark.clipTo(from == null, from || 0, to_ == null, to, diff);\r\n                    if (mark.isDead()) {mark.detach(this); mk.splice(i--, 1);}\r\n                }\r\n            }\r\n        },\r\n        // Split a part off a line, keeping styles and markers intact.\r\n        split: function(pos, textBefore) {\r\n            var st = [textBefore, null], mk = this.marked;\r\n            copyStyles(pos, this.text.length, this.styles, st);\r\n            var taken = new Line(textBefore + this.text.slice(pos), st);\r\n            if (mk) {\r\n                for (var i = 0; i < mk.length; ++i) {\r\n                    var mark = mk[i];\r\n                    var newmark = mark.split(pos, textBefore.length);\r\n                    if (newmark) {\r\n                        if (!taken.marked) taken.marked = [];\r\n                        taken.marked.push(newmark); newmark.attach(taken);\r\n                    }\r\n                }\r\n            }\r\n            return taken;\r\n        },\r\n        append: function(line) {\r\n            var mylen = this.text.length, mk = line.marked, mymk = this.marked;\r\n            this.text += line.text;\r\n            copyStyles(0, line.text.length, line.styles, this.styles);\r\n            if (mymk) {\r\n                for (var i = 0; i < mymk.length; ++i)\r\n                    if (mymk[i].to == null) mymk[i].to = mylen;\r\n            }\r\n            if (mk && mk.length) {\r\n                if (!mymk) this.marked = mymk = [];\r\n                outer: for (var i = 0; i < mk.length; ++i) {\r\n                    var mark = mk[i];\r\n                    if (!mark.from) {\r\n                        for (var j = 0; j < mymk.length; ++j) {\r\n                            var mymark = mymk[j];\r\n                            if (mymark.to == mylen && mymark.sameSet(mark)) {\r\n                                mymark.to = mark.to == null ? null : mark.to + mylen;\r\n                                if (mymark.isDead()) {\r\n                                    mymark.detach(this);\r\n                                    mk.splice(i--, 1);\r\n                                }\r\n                                continue outer;\r\n                            }\r\n                        }\r\n                    }\r\n                    mymk.push(mark);\r\n                    mark.attach(this);\r\n                    mark.from += mylen;\r\n                    if (mark.to != null) mark.to += mylen;\r\n                }\r\n            }\r\n        },\r\n        fixMarkEnds: function(other) {\r\n            var mk = this.marked, omk = other.marked;\r\n            if (!mk) return;\r\n            for (var i = 0; i < mk.length; ++i) {\r\n                var mark = mk[i], close = mark.to == null;\r\n                if (close && omk) {\r\n                    for (var j = 0; j < omk.length; ++j)\r\n                        if (omk[j].sameSet(mark)) {close = false; break;}\r\n                }\r\n                if (close) mark.to = this.text.length;\r\n            }\r\n        },\r\n        fixMarkStarts: function() {\r\n            var mk = this.marked;\r\n            if (!mk) return;\r\n            for (var i = 0; i < mk.length; ++i)\r\n                if (mk[i].from == null) mk[i].from = 0;\r\n        },\r\n        addMark: function(mark) {\r\n            mark.attach(this);\r\n            if (this.marked == null) this.marked = [];\r\n            this.marked.push(mark);\r\n            this.marked.sort(function(a, b){return (a.from || 0) - (b.from || 0);});\r\n        },\r\n        // Run the given mode's parser over a line, update the styles\r\n        // array, which contains alternating fragments of text and CSS\r\n        // classes.\r\n        highlight: function(mode, state, tabSize) {\r\n            var stream = new StringStream(this.text, tabSize), st = this.styles, pos = 0;\r\n            var changed = false, curWord = st[0], prevWord;\r\n            if (this.text == \"\" && mode.blankLine) mode.blankLine(state);\r\n            while (!stream.eol()) {\r\n                var style = mode.token(stream, state);\r\n                var substr = this.text.slice(stream.start, stream.pos);\r\n                stream.start = stream.pos;\r\n                if (pos && st[pos-1] == style)\r\n                    st[pos-2] += substr;\r\n                else if (substr) {\r\n                    if (!changed && (st[pos+1] != style || (pos && st[pos-2] != prevWord))) changed = true;\r\n                    st[pos++] = substr; st[pos++] = style;\r\n                    prevWord = curWord; curWord = st[pos];\r\n                }\r\n                // Give up when line is ridiculously long\r\n                if (stream.pos > 5000) {\r\n                    st[pos++] = this.text.slice(stream.pos); st[pos++] = null;\r\n                    break;\r\n                }\r\n            }\r\n            if (st.length != pos) {st.length = pos; changed = true;}\r\n            if (pos && st[pos-2] != prevWord) changed = true;\r\n            // Short lines with simple highlights return null, and are\r\n            // counted as changed by the driver because they are likely to\r\n            // highlight the same way in various contexts.\r\n            return changed || (st.length < 5 && this.text.length < 10 ? null : false);\r\n        },\r\n        // Fetch the parser token for a given character. Useful for hacks\r\n        // that want to inspect the mode state (say, for completion).\r\n        getTokenAt: function(mode, state, ch) {\r\n            var txt = this.text, stream = new StringStream(txt);\r\n            while (stream.pos < ch && !stream.eol()) {\r\n                stream.start = stream.pos;\r\n                var style = mode.token(stream, state);\r\n            }\r\n            return {start: stream.start,\r\n                end: stream.pos,\r\n                string: stream.current(),\r\n                className: style || null,\r\n                state: state};\r\n        },\r\n        indentation: function(tabSize) {return countColumn(this.text, null, tabSize);},\r\n        // Produces an HTML fragment for the line, taking selection,\r\n        // marking, and highlighting into account.\r\n        getHTML: function(sfrom, sto, includePre, tabText, endAt) {\r\n            var html = [], first = true;\r\n            if (includePre)\r\n                html.push(this.className ? '<pre class=\"' + this.className + '\">': \"<pre>\");\r\n            function span(text, style) {\r\n                if (!text) return;\r\n                // Work around a bug where, in some compat modes, IE ignores leading spaces\r\n                if (first && ie && text.charAt(0) == \" \") text = \"\\u00a0\" + text.slice(1);\r\n                first = false;\r\n                if (style) html.push('<span class=\"', style, '\">', htmlEscape(text).replace(/\\t/g, tabText), \"</span>\");\r\n                else html.push(htmlEscape(text).replace(/\\t/g, tabText));\r\n            }\r\n            var st = this.styles, allText = this.text, marked = this.marked;\r\n            if (sfrom == sto) sfrom = null;\r\n            var len = allText.length;\r\n            if (endAt != null) len = Math.min(endAt, len);\r\n\r\n            if (!allText && endAt == null)\r\n                span(\" \", sfrom != null && sto == null ? \"CodeMirror-selected\" : null);\r\n            else if (!marked && sfrom == null)\r\n                for (var i = 0, ch = 0; ch < len; i+=2) {\r\n                    var str = st[i], style = st[i+1], l = str.length;\r\n                    if (ch + l > len) str = str.slice(0, len - ch);\r\n                    ch += l;\r\n                    span(str, style && \"cm-\" + style);\r\n                }\r\n            else {\r\n                var pos = 0, i = 0, text = \"\", style, sg = 0;\r\n                var markpos = -1, mark = null;\r\n                function nextMark() {\r\n                    if (marked) {\r\n                        markpos += 1;\r\n                        mark = (markpos < marked.length) ? marked[markpos] : null;\r\n                    }\r\n                }\r\n                nextMark();\r\n                while (pos < len) {\r\n                    var upto = len;\r\n                    var extraStyle = \"\";\r\n                    if (sfrom != null) {\r\n                        if (sfrom > pos) upto = sfrom;\r\n                        else if (sto == null || sto > pos) {\r\n                            extraStyle = \" CodeMirror-selected\";\r\n                            if (sto != null) upto = Math.min(upto, sto);\r\n                        }\r\n                    }\r\n                    while (mark && mark.to != null && mark.to <= pos) nextMark();\r\n                    if (mark) {\r\n                        if (mark.from > pos) upto = Math.min(upto, mark.from);\r\n                        else {\r\n                            extraStyle += \" \" + mark.style;\r\n                            if (mark.to != null) upto = Math.min(upto, mark.to);\r\n                        }\r\n                    }\r\n                    for (;;) {\r\n                        var end = pos + text.length;\r\n                        var appliedStyle = style;\r\n                        if (extraStyle) appliedStyle = style ? style + extraStyle : extraStyle;\r\n                        span(end > upto ? text.slice(0, upto - pos) : text, appliedStyle);\r\n                        if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;}\r\n                        pos = end;\r\n                        text = st[i++]; style = \"cm-\" + st[i++];\r\n                    }\r\n                }\r\n                if (sfrom != null && sto == null) span(\" \", \"CodeMirror-selected\");\r\n            }\r\n            if (includePre) html.push(\"</pre>\");\r\n            return html.join(\"\");\r\n        },\r\n        cleanUp: function() {\r\n            this.parent = null;\r\n            if (this.marked)\r\n                for (var i = 0, e = this.marked.length; i < e; ++i) this.marked[i].detach(this);\r\n        }\r\n    };\r\n    // Utility used by replace and split above\r\n    function copyStyles(from, to, source, dest) {\r\n        for (var i = 0, pos = 0, state = 0; pos < to; i+=2) {\r\n            var part = source[i], end = pos + part.length;\r\n            if (state == 0) {\r\n                if (end > from) dest.push(part.slice(from - pos, Math.min(part.length, to - pos)), source[i+1]);\r\n                if (end >= from) state = 1;\r\n            }\r\n            else if (state == 1) {\r\n                if (end > to) dest.push(part.slice(0, to - pos), source[i+1]);\r\n                else dest.push(part, source[i+1]);\r\n            }\r\n            pos = end;\r\n        }\r\n    }\r\n\r\n    // Data structure that holds the sequence of lines.\r\n    function LeafChunk(lines) {\r\n        this.lines = lines;\r\n        this.parent = null;\r\n        for (var i = 0, e = lines.length, height = 0; i < e; ++i) {\r\n            lines[i].parent = this;\r\n            height += lines[i].height;\r\n        }\r\n        this.height = height;\r\n    }\r\n    LeafChunk.prototype = {\r\n        chunkSize: function() { return this.lines.length; },\r\n        remove: function(at, n, callbacks) {\r\n            for (var i = at, e = at + n; i < e; ++i) {\r\n                var line = this.lines[i];\r\n                this.height -= line.height;\r\n                line.cleanUp();\r\n                if (line.handlers)\r\n                    for (var j = 0; j < line.handlers.length; ++j) callbacks.push(line.handlers[j]);\r\n            }\r\n            this.lines.splice(at, n);\r\n        },\r\n        collapse: function(lines) {\r\n            lines.splice.apply(lines, [lines.length, 0].concat(this.lines));\r\n        },\r\n        insertHeight: function(at, lines, height) {\r\n            this.height += height;\r\n            this.lines.splice.apply(this.lines, [at, 0].concat(lines));\r\n            for (var i = 0, e = lines.length; i < e; ++i) lines[i].parent = this;\r\n        },\r\n        iterN: function(at, n, op) {\r\n            for (var e = at + n; at < e; ++at)\r\n                if (op(this.lines[at])) return true;\r\n        }\r\n    };\r\n    function BranchChunk(children) {\r\n        this.children = children;\r\n        var size = 0, height = 0;\r\n        for (var i = 0, e = children.length; i < e; ++i) {\r\n            var ch = children[i];\r\n            size += ch.chunkSize(); height += ch.height;\r\n            ch.parent = this;\r\n        }\r\n        this.size = size;\r\n        this.height = height;\r\n        this.parent = null;\r\n    }\r\n    BranchChunk.prototype = {\r\n        chunkSize: function() { return this.size; },\r\n        remove: function(at, n, callbacks) {\r\n            this.size -= n;\r\n            for (var i = 0; i < this.children.length; ++i) {\r\n                var child = this.children[i], sz = child.chunkSize();\r\n                if (at < sz) {\r\n                    var rm = Math.min(n, sz - at), oldHeight = child.height;\r\n                    child.remove(at, rm, callbacks);\r\n                    this.height -= oldHeight - child.height;\r\n                    if (sz == rm) { this.children.splice(i--, 1); child.parent = null; }\r\n                    if ((n -= rm) == 0) break;\r\n                    at = 0;\r\n                } else at -= sz;\r\n            }\r\n            if (this.size - n < 25) {\r\n                var lines = [];\r\n                this.collapse(lines);\r\n                this.children = [new LeafChunk(lines)];\r\n            }\r\n        },\r\n        collapse: function(lines) {\r\n            for (var i = 0, e = this.children.length; i < e; ++i) this.children[i].collapse(lines);\r\n        },\r\n        insert: function(at, lines) {\r\n            var height = 0;\r\n            for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height;\r\n            this.insertHeight(at, lines, height);\r\n        },\r\n        insertHeight: function(at, lines, height) {\r\n            this.size += lines.length;\r\n            this.height += height;\r\n            for (var i = 0, e = this.children.length; i < e; ++i) {\r\n                var child = this.children[i], sz = child.chunkSize();\r\n                if (at <= sz) {\r\n                    child.insertHeight(at, lines, height);\r\n                    if (child.lines && child.lines.length > 50) {\r\n                        while (child.lines.length > 50) {\r\n                            var spilled = child.lines.splice(child.lines.length - 25, 25);\r\n                            var newleaf = new LeafChunk(spilled);\r\n                            child.height -= newleaf.height;\r\n                            this.children.splice(i + 1, 0, newleaf);\r\n                            newleaf.parent = this;\r\n                        }\r\n                        this.maybeSpill();\r\n                    }\r\n                    break;\r\n                }\r\n                at -= sz;\r\n            }\r\n        },\r\n        maybeSpill: function() {\r\n            if (this.children.length <= 10) return;\r\n            var me = this;\r\n            do {\r\n                var spilled = me.children.splice(me.children.length - 5, 5);\r\n                var sibling = new BranchChunk(spilled);\r\n                if (!me.parent) { // Become the parent node\r\n                    var copy = new BranchChunk(me.children);\r\n                    copy.parent = me;\r\n                    me.children = [copy, sibling];\r\n                    me = copy;\r\n                } else {\r\n                    me.size -= sibling.size;\r\n                    me.height -= sibling.height;\r\n                    var myIndex = indexOf(me.parent.children, me);\r\n                    me.parent.children.splice(myIndex + 1, 0, sibling);\r\n                }\r\n                sibling.parent = me.parent;\r\n            } while (me.children.length > 10);\r\n            me.parent.maybeSpill();\r\n        },\r\n        iter: function(from, to, op) { this.iterN(from, to - from, op); },\r\n        iterN: function(at, n, op) {\r\n            for (var i = 0, e = this.children.length; i < e; ++i) {\r\n                var child = this.children[i], sz = child.chunkSize();\r\n                if (at < sz) {\r\n                    var used = Math.min(n, sz - at);\r\n                    if (child.iterN(at, used, op)) return true;\r\n                    if ((n -= used) == 0) break;\r\n                    at = 0;\r\n                } else at -= sz;\r\n            }\r\n        }\r\n    };\r\n\r\n    function getLineAt(chunk, n) {\r\n        while (!chunk.lines) {\r\n            for (var i = 0;; ++i) {\r\n                var child = chunk.children[i], sz = child.chunkSize();\r\n                if (n < sz) { chunk = child; break; }\r\n                n -= sz;\r\n            }\r\n        }\r\n        return chunk.lines[n];\r\n    }\r\n    function lineNo(line) {\r\n        if (line.parent == null) return null;\r\n        var cur = line.parent, no = indexOf(cur.lines, line);\r\n        for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) {\r\n            for (var i = 0, e = chunk.children.length; ; ++i) {\r\n                if (chunk.children[i] == cur) break;\r\n                no += chunk.children[i].chunkSize();\r\n            }\r\n        }\r\n        return no;\r\n    }\r\n    function lineAtHeight(chunk, h) {\r\n        var n = 0;\r\n        outer: do {\r\n            for (var i = 0, e = chunk.children.length; i < e; ++i) {\r\n                var child = chunk.children[i], ch = child.height;\r\n                if (h < ch) { chunk = child; continue outer; }\r\n                h -= ch;\r\n                n += child.chunkSize();\r\n            }\r\n            return n;\r\n        } while (!chunk.lines);\r\n        for (var i = 0, e = chunk.lines.length; i < e; ++i) {\r\n            var line = chunk.lines[i], lh = line.height;\r\n            if (h < lh) break;\r\n            h -= lh;\r\n        }\r\n        return n + i;\r\n    }\r\n    function heightAtLine(chunk, n) {\r\n        var h = 0;\r\n        outer: do {\r\n            for (var i = 0, e = chunk.children.length; i < e; ++i) {\r\n                var child = chunk.children[i], sz = child.chunkSize();\r\n                if (n < sz) { chunk = child; continue outer; }\r\n                n -= sz;\r\n                h += child.height;\r\n            }\r\n            return h;\r\n        } while (!chunk.lines);\r\n        for (var i = 0; i < n; ++i) h += chunk.lines[i].height;\r\n        return h;\r\n    }\r\n\r\n    // The history object 'chunks' changes that are made close together\r\n    // and at almost the same time into bigger undoable units.\r\n    function History() {\r\n        this.time = 0;\r\n        this.done = []; this.undone = [];\r\n    }\r\n    History.prototype = {\r\n        addChange: function(start, added, old) {\r\n            this.undone.length = 0;\r\n            var time = +new Date, last = this.done[this.done.length - 1];\r\n            if (time - this.time > 400 || !last ||\r\n                last.start > start + added || last.start + last.added < start - last.added + last.old.length)\r\n                this.done.push({start: start, added: added, old: old});\r\n            else {\r\n                var oldoff = 0;\r\n                if (start < last.start) {\r\n                    for (var i = last.start - start - 1; i >= 0; --i)\r\n                        last.old.unshift(old[i]);\r\n                    last.added += last.start - start;\r\n                    last.start = start;\r\n                }\r\n                else if (last.start < start) {\r\n                    oldoff = start - last.start;\r\n                    added += oldoff;\r\n                }\r\n                for (var i = last.added - oldoff, e = old.length; i < e; ++i)\r\n                    last.old.push(old[i]);\r\n                if (last.added < added) last.added = added;\r\n            }\r\n            this.time = time;\r\n        }\r\n    };\r\n\r\n    function stopMethod() {e_stop(this);}\r\n    // Ensure an event has a stop method.\r\n    function addStop(event) {\r\n        if (!event.stop) event.stop = stopMethod;\r\n        return event;\r\n    }\r\n\r\n    function e_preventDefault(e) {\r\n        if (e.preventDefault) e.preventDefault();\r\n        else e.returnValue = false;\r\n    }\r\n    function e_stopPropagation(e) {\r\n        if (e.stopPropagation) e.stopPropagation();\r\n        else e.cancelBubble = true;\r\n    }\r\n    function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);}\r\n    CodeMirror.e_stop = e_stop;\r\n    CodeMirror.e_preventDefault = e_preventDefault;\r\n    CodeMirror.e_stopPropagation = e_stopPropagation;\r\n\r\n    function e_target(e) {return e.target || e.srcElement;}\r\n    function e_button(e) {\r\n        if (e.which) return e.which;\r\n        else if (e.button & 1) return 1;\r\n        else if (e.button & 2) return 3;\r\n        else if (e.button & 4) return 2;\r\n    }\r\n\r\n    // Event handler registration. If disconnect is true, it'll return a\r\n    // function that unregisters the handler.\r\n    function connect(node, type, handler, disconnect) {\r\n        if (typeof node.addEventListener == \"function\") {\r\n            node.addEventListener(type, handler, false);\r\n            if (disconnect) return function() {node.removeEventListener(type, handler, false);};\r\n        }\r\n        else {\r\n            var wrapHandler = function(event) {handler(event || window.event);};\r\n            node.attachEvent(\"on\" + type, wrapHandler);\r\n            if (disconnect) return function() {node.detachEvent(\"on\" + type, wrapHandler);};\r\n        }\r\n    }\r\n    CodeMirror.connect = connect;\r\n\r\n    function Delayed() {this.id = null;}\r\n    Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}};\r\n\r\n    // Detect drag-and-drop\r\n    var dragAndDrop = function() {\r\n        // IE8 has ondragstart and ondrop properties, but doesn't seem to\r\n        // actually support ondragstart the way it's supposed to work.\r\n        if (/MSIE [1-8]\\b/.test(navigator.userAgent)) return false;\r\n        var div = document.createElement('div');\r\n        return \"draggable\" in div;\r\n    }();\r\n\r\n    var gecko = /gecko\\/\\d{7}/i.test(navigator.userAgent);\r\n    var ie = /MSIE \\d/.test(navigator.userAgent);\r\n    var webkit = /WebKit\\//.test(navigator.userAgent);\r\n\r\n    var lineSep = \"\\n\";\r\n    // Feature-detect whether newlines in textareas are converted to \\r\\n\r\n    (function () {\r\n        var te = document.createElement(\"textarea\");\r\n        te.value = \"foo\\nbar\";\r\n        if (te.value.indexOf(\"\\r\") > -1) lineSep = \"\\r\\n\";\r\n    }());\r\n\r\n    // Counts the column offset in a string, taking tabs into account.\r\n    // Used mostly to find indentation.\r\n    function countColumn(string, end, tabSize) {\r\n        if (end == null) {\r\n            end = string.search(/[^\\s\\u00a0]/);\r\n            if (end == -1) end = string.length;\r\n        }\r\n        for (var i = 0, n = 0; i < end; ++i) {\r\n            if (string.charAt(i) == \"\\t\") n += tabSize - (n % tabSize);\r\n            else ++n;\r\n        }\r\n        return n;\r\n    }\r\n\r\n    function computedStyle(elt) {\r\n        if (elt.currentStyle) return elt.currentStyle;\r\n        return window.getComputedStyle(elt, null);\r\n    }\r\n\r\n    // Find the position of an element by following the offsetParent chain.\r\n    // If screen==true, it returns screen (rather than page) coordinates.\r\n    function eltOffset(node, screen) {\r\n        var bod = node.ownerDocument.body;\r\n        var x = 0, y = 0, skipBody = false;\r\n        for (var n = node; n; n = n.offsetParent) {\r\n            var ol = n.offsetLeft, ot = n.offsetTop;\r\n            // Firefox reports weird inverted offsets when the body has a border.\r\n            if (n == bod) { x += Math.abs(ol); y += Math.abs(ot); }\r\n            else { x += ol, y += ot; }\r\n            if (screen && computedStyle(n).position == \"fixed\")\r\n                skipBody = true;\r\n        }\r\n        var e = screen && !skipBody ? null : bod;\r\n        for (var n = node.parentNode; n != e; n = n.parentNode)\r\n            if (n.scrollLeft != null) { x -= n.scrollLeft; y -= n.scrollTop;}\r\n        return {left: x, top: y};\r\n    }\r\n    // Use the faster and saner getBoundingClientRect method when possible.\r\n    if (document.documentElement.getBoundingClientRect != null) eltOffset = function(node, screen) {\r\n        // Take the parts of bounding client rect that we are interested in so we are able to edit if need be,\r\n        // since the returned value cannot be changed externally (they are kept in sync as the element moves within the page)\r\n        try { var box = node.getBoundingClientRect(); box = { top: box.top, left: box.left }; }\r\n        catch(e) { box = {top: 0, left: 0}; }\r\n        if (!screen) {\r\n            // Get the toplevel scroll, working around browser differences.\r\n            if (window.pageYOffset == null) {\r\n                var t = document.documentElement || document.body.parentNode;\r\n                if (t.scrollTop == null) t = document.body;\r\n                box.top += t.scrollTop; box.left += t.scrollLeft;\r\n            } else {\r\n                box.top += window.pageYOffset; box.left += window.pageXOffset;\r\n            }\r\n        }\r\n        return box;\r\n    };\r\n\r\n    // Get a node's text content.\r\n    function eltText(node) {\r\n        return node.textContent || node.innerText || node.nodeValue || \"\";\r\n    }\r\n\r\n    // Operations on {line, ch} objects.\r\n    function posEq(a, b) {return a.line == b.line && a.ch == b.ch;}\r\n    function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);}\r\n    function copyPos(x) {return {line: x.line, ch: x.ch};}\r\n\r\n    var escapeElement = document.createElement(\"pre\");\r\n    function htmlEscape(str) {\r\n        escapeElement.textContent = str;\r\n        return escapeElement.innerHTML;\r\n    }\r\n    // Recent (late 2011) Opera betas insert bogus newlines at the start\r\n    // of the textContent, so we strip those.\r\n    if (htmlEscape(\"a\") == \"\\na\")\r\n        htmlEscape = function(str) {\r\n            escapeElement.textContent = str;\r\n            return escapeElement.innerHTML.slice(1);\r\n        };\r\n    // Some IEs don't preserve tabs through innerHTML\r\n    else if (htmlEscape(\"\\t\") != \"\\t\")\r\n        htmlEscape = function(str) {\r\n            escapeElement.innerHTML = \"\";\r\n            escapeElement.appendChild(document.createTextNode(str));\r\n            return escapeElement.innerHTML;\r\n        };\r\n    CodeMirror.htmlEscape = htmlEscape;\r\n\r\n    // Used to position the cursor after an undo/redo by finding the\r\n    // last edited character.\r\n    function editEnd(from, to) {\r\n        if (!to) return from ? from.length : 0;\r\n        if (!from) return to.length;\r\n        for (var i = from.length, j = to.length; i >= 0 && j >= 0; --i, --j)\r\n            if (from.charAt(i) != to.charAt(j)) break;\r\n        return j + 1;\r\n    }\r\n\r\n    function indexOf(collection, elt) {\r\n        if (collection.indexOf) return collection.indexOf(elt);\r\n        for (var i = 0, e = collection.length; i < e; ++i)\r\n            if (collection[i] == elt) return i;\r\n        return -1;\r\n    }\r\n    function isWordChar(ch) {\r\n        return /\\w/.test(ch) || ch.toUpperCase() != ch.toLowerCase();\r\n    }\r\n\r\n    // See if \"\".split is the broken IE version, if so, provide an\r\n    // alternative way to split lines.\r\n    var splitLines = \"\\n\\nb\".split(/\\n/).length != 3 ? function(string) {\r\n        var pos = 0, nl, result = [];\r\n        while ((nl = string.indexOf(\"\\n\", pos)) > -1) {\r\n            result.push(string.slice(pos, string.charAt(nl-1) == \"\\r\" ? nl - 1 : nl));\r\n            pos = nl + 1;\r\n        }\r\n        result.push(string.slice(pos));\r\n        return result;\r\n    } : function(string){return string.split(/\\r?\\n/);};\r\n    CodeMirror.splitLines = splitLines;\r\n\r\n    var hasSelection = window.getSelection ? function(te) {\r\n        try { return te.selectionStart != te.selectionEnd; }\r\n        catch(e) { return false; }\r\n    } : function(te) {\r\n        try {var range = te.ownerDocument.selection.createRange();}\r\n        catch(e) {}\r\n        if (!range || range.parentElement() != te) return false;\r\n        return range.compareEndPoints(\"StartToEnd\", range) != 0;\r\n    };\r\n\r\n    CodeMirror.defineMode(\"null\", function() {\r\n        return {token: function(stream) {stream.skipToEnd();}};\r\n    });\r\n    CodeMirror.defineMIME(\"text/plain\", \"null\");\r\n\r\n    var keyNames = {3: \"Enter\", 8: \"Backspace\", 9: \"Tab\", 13: \"Enter\", 16: \"Shift\", 17: \"Ctrl\", 18: \"Alt\",\r\n        19: \"Pause\", 20: \"CapsLock\", 27: \"Esc\", 32: \"Space\", 33: \"PageUp\", 34: \"PageDown\", 35: \"End\",\r\n        36: \"Home\", 37: \"Left\", 38: \"Up\", 39: \"Right\", 40: \"Down\", 44: \"PrintScrn\", 45: \"Insert\",\r\n        46: \"Delete\", 59: \";\", 91: \"Mod\", 92: \"Mod\", 93: \"Mod\", 186: \";\", 187: \"=\", 188: \",\",\r\n        189: \"-\", 190: \".\", 191: \"/\", 192: \"`\", 219: \"[\", 220: \"\\\\\", 221: \"]\", 222: \"'\", 63276: \"PageUp\",\r\n        63277: \"PageDown\", 63275: \"End\", 63273: \"Home\", 63234: \"Left\", 63232: \"Up\", 63235: \"Right\",\r\n        63233: \"Down\", 63302: \"Insert\", 63272: \"Delete\"};\r\n    CodeMirror.keyNames = keyNames;\r\n    (function() {\r\n        // Number keys\r\n        for (var i = 0; i < 10; i++) keyNames[i + 48] = String(i);\r\n        // Alphabetic keys\r\n        for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i);\r\n        // Function keys\r\n        for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = \"F\" + i;\r\n    })();\r\n\r\n    return CodeMirror;\r\n})();\r\nCodeMirror.defineMode(\"xml\", function(config, parserConfig) {\r\n    var indentUnit = config.indentUnit;\r\n    var Kludges = parserConfig.htmlMode ? {\r\n        autoSelfClosers: {\"br\": true, \"img\": true, \"hr\": true, \"link\": true, \"input\": true,\r\n            \"meta\": true, \"col\": true, \"frame\": true, \"base\": true, \"area\": true},\r\n        doNotIndent: {\"pre\": true},\r\n        allowUnquoted: true\r\n    } : {autoSelfClosers: {}, doNotIndent: {}, allowUnquoted: false};\r\n    var alignCDATA = parserConfig.alignCDATA;\r\n\r\n    // Return variables for tokenizers\r\n    var tagName, type;\r\n\r\n    function inText(stream, state) {\r\n        function chain(parser) {\r\n            state.tokenize = parser;\r\n            return parser(stream, state);\r\n        }\r\n\r\n        var ch = stream.next();\r\n        if (ch == \"<\") {\r\n            if (stream.eat(\"!\")) {\r\n                if (stream.eat(\"[\")) {\r\n                    if (stream.match(\"CDATA[\")) return chain(inBlock(\"atom\", \"]]>\"));\r\n                    else return null;\r\n                }\r\n                else if (stream.match(\"--\")) return chain(inBlock(\"comment\", \"-->\"));\r\n                else if (stream.match(\"DOCTYPE\", true, true)) {\r\n                    stream.eatWhile(/[\\w\\._\\-]/);\r\n                    return chain(doctype(1));\r\n                }\r\n                else return null;\r\n            }\r\n            else if (stream.eat(\"?\")) {\r\n                stream.eatWhile(/[\\w\\._\\-]/);\r\n                state.tokenize = inBlock(\"meta\", \"?>\");\r\n                return \"meta\";\r\n            }\r\n            else {\r\n                type = stream.eat(\"/\") ? \"closeTag\" : \"openTag\";\r\n                stream.eatSpace();\r\n                tagName = \"\";\r\n                var c;\r\n                while ((c = stream.eat(/[^\\s\\u00a0=<>\\\"\\'\\/?]/))) tagName += c;\r\n                state.tokenize = inTag;\r\n                return \"tag\";\r\n            }\r\n        }\r\n        else if (ch == \"&\") {\r\n            stream.eatWhile(/[^;]/);\r\n            stream.eat(\";\");\r\n            return \"atom\";\r\n        }\r\n        else {\r\n            stream.eatWhile(/[^&<]/);\r\n            return null;\r\n        }\r\n    }\r\n\r\n    function inTag(stream, state) {\r\n        var ch = stream.next();\r\n        if (ch == \">\" || (ch == \"/\" && stream.eat(\">\"))) {\r\n            state.tokenize = inText;\r\n            type = ch == \">\" ? \"endTag\" : \"selfcloseTag\";\r\n            return \"tag\";\r\n        }\r\n        else if (ch == \"=\") {\r\n            type = \"equals\";\r\n            return null;\r\n        }\r\n        else if (/[\\'\\\"]/.test(ch)) {\r\n            state.tokenize = inAttribute(ch);\r\n            return state.tokenize(stream, state);\r\n        }\r\n        else {\r\n            stream.eatWhile(/[^\\s\\u00a0=<>\\\"\\'\\/?]/);\r\n            return \"word\";\r\n        }\r\n    }\r\n\r\n    function inAttribute(quote) {\r\n        return function(stream, state) {\r\n            while (!stream.eol()) {\r\n                if (stream.next() == quote) {\r\n                    state.tokenize = inTag;\r\n                    break;\r\n                }\r\n            }\r\n            return \"string\";\r\n        };\r\n    }\r\n\r\n    function inBlock(style, terminator) {\r\n        return function(stream, state) {\r\n            while (!stream.eol()) {\r\n                if (stream.match(terminator)) {\r\n                    state.tokenize = inText;\r\n                    break;\r\n                }\r\n                stream.next();\r\n            }\r\n            return style;\r\n        };\r\n    }\r\n    function doctype(depth) {\r\n        return function(stream, state) {\r\n            var ch;\r\n            while ((ch = stream.next()) != null) {\r\n                if (ch == \"<\") {\r\n                    state.tokenize = doctype(depth + 1);\r\n                    return state.tokenize(stream, state);\r\n                } else if (ch == \">\") {\r\n                    if (depth == 1) {\r\n                        state.tokenize = inText;\r\n                        break;\r\n                    } else {\r\n                        state.tokenize = doctype(depth - 1);\r\n                        return state.tokenize(stream, state);\r\n                    }\r\n                }\r\n            }\r\n            return \"meta\";\r\n        };\r\n    }\r\n\r\n    var curState, setStyle;\r\n    function pass() {\r\n        for (var i = arguments.length - 1; i >= 0; i--) curState.cc.push(arguments[i]);\r\n    }\r\n    function cont() {\r\n        pass.apply(null, arguments);\r\n        return true;\r\n    }\r\n\r\n    function pushContext(tagName, startOfLine) {\r\n        var noIndent = Kludges.doNotIndent.hasOwnProperty(tagName) || (curState.context && curState.context.noIndent);\r\n        curState.context = {\r\n            prev: curState.context,\r\n            tagName: tagName,\r\n            indent: curState.indented,\r\n            startOfLine: startOfLine,\r\n            noIndent: noIndent\r\n        };\r\n    }\r\n    function popContext() {\r\n        if (curState.context) curState.context = curState.context.prev;\r\n    }\r\n\r\n    function element(type) {\r\n        if (type == \"openTag\") {\r\n            curState.tagName = tagName;\r\n            return cont(attributes, endtag(curState.startOfLine));\r\n        } else if (type == \"closeTag\") {\r\n            var err = false;\r\n            if (curState.context) {\r\n                err = curState.context.tagName != tagName;\r\n            } else {\r\n                err = true;\r\n            }\r\n            if (err) setStyle = \"error\";\r\n            return cont(endclosetag(err));\r\n        }\r\n        return cont();\r\n    }\r\n    function endtag(startOfLine) {\r\n        return function(type) {\r\n            if (type == \"selfcloseTag\" ||\r\n                (type == \"endTag\" && Kludges.autoSelfClosers.hasOwnProperty(curState.tagName.toLowerCase())))\r\n                return cont();\r\n            if (type == \"endTag\") {pushContext(curState.tagName, startOfLine); return cont();}\r\n            return cont();\r\n        };\r\n    }\r\n    function endclosetag(err) {\r\n        return function(type) {\r\n            if (err) setStyle = \"error\";\r\n            if (type == \"endTag\") { popContext(); return cont(); }\r\n            setStyle = \"error\";\r\n            return cont(arguments.callee);\r\n        }\r\n    }\r\n\r\n    function attributes(type) {\r\n        if (type == \"word\") {setStyle = \"attribute\"; return cont(attributes);}\r\n        if (type == \"equals\") return cont(attvalue, attributes);\r\n        if (type == \"string\") {setStyle = \"error\"; return cont(attributes);}\r\n        return pass();\r\n    }\r\n    function attvalue(type) {\r\n        if (type == \"word\" && Kludges.allowUnquoted) {setStyle = \"string\"; return cont();}\r\n        if (type == \"string\") return cont(attvaluemaybe);\r\n        return pass();\r\n    }\r\n    function attvaluemaybe(type) {\r\n        if (type == \"string\") return cont(attvaluemaybe);\r\n        else return pass();\r\n    }\r\n\r\n    return {\r\n        startState: function() {\r\n            return {tokenize: inText, cc: [], indented: 0, startOfLine: true, tagName: null, context: null};\r\n        },\r\n\r\n        token: function(stream, state) {\r\n            if (stream.sol()) {\r\n                state.startOfLine = true;\r\n                state.indented = stream.indentation();\r\n            }\r\n            if (stream.eatSpace()) return null;\r\n\r\n            setStyle = type = tagName = null;\r\n            var style = state.tokenize(stream, state);\r\n            state.type = type;\r\n            if ((style || type) && style != \"comment\") {\r\n                curState = state;\r\n                while (true) {\r\n                    var comb = state.cc.pop() || element;\r\n                    if (comb(type || style)) break;\r\n                }\r\n            }\r\n            state.startOfLine = false;\r\n            return setStyle || style;\r\n        },\r\n\r\n        indent: function(state, textAfter, fullLine) {\r\n            var context = state.context;\r\n            if ((state.tokenize != inTag && state.tokenize != inText) ||\r\n                context && context.noIndent)\r\n                return fullLine ? fullLine.match(/^(\\s*)/)[0].length : 0;\r\n            if (alignCDATA && /<!\\[CDATA\\[/.test(textAfter)) return 0;\r\n            if (context && /^<\\//.test(textAfter))\r\n                context = context.prev;\r\n            while (context && !context.startOfLine)\r\n                context = context.prev;\r\n            if (context) return context.indent + indentUnit;\r\n            else return 0;\r\n        },\r\n\r\n        compareStates: function(a, b) {\r\n            if (a.indented != b.indented || a.tokenize != b.tokenize) return false;\r\n            for (var ca = a.context, cb = b.context; ; ca = ca.prev, cb = cb.prev) {\r\n                if (!ca || !cb) return ca == cb;\r\n                if (ca.tagName != cb.tagName) return false;\r\n            }\r\n        },\r\n\r\n        electricChars: \"/\"\r\n    };\r\n});\r\n\r\nCodeMirror.defineMIME(\"application/xml\", \"xml\");\r\nCodeMirror.defineMIME(\"text/html\", {name: \"xml\", htmlMode: true});\r\nCodeMirror.defineMode(\"javascript\", function(config, parserConfig) {\r\n    var indentUnit = config.indentUnit;\r\n    var jsonMode = parserConfig.json;\r\n\r\n    // Tokenizer\r\n\r\n    var keywords = function(){\r\n        function kw(type) {return {type: type, style: \"keyword\"};}\r\n        var A = kw(\"keyword a\"), B = kw(\"keyword b\"), C = kw(\"keyword c\");\r\n        var operator = kw(\"operator\"), atom = {type: \"atom\", style: \"atom\"};\r\n        return {\r\n            \"if\": A, \"while\": A, \"with\": A, \"else\": B, \"do\": B, \"try\": B, \"finally\": B,\r\n            \"return\": C, \"break\": C, \"continue\": C, \"new\": C, \"delete\": C, \"throw\": C,\r\n            \"var\": kw(\"var\"), \"const\": kw(\"var\"), \"let\": kw(\"var\"),\r\n            \"function\": kw(\"function\"), \"catch\": kw(\"catch\"),\r\n            \"for\": kw(\"for\"), \"switch\": kw(\"switch\"), \"case\": kw(\"case\"), \"default\": kw(\"default\"),\r\n            \"in\": operator, \"typeof\": operator, \"instanceof\": operator,\r\n            \"true\": atom, \"false\": atom, \"null\": atom, \"undefined\": atom, \"NaN\": atom, \"Infinity\": atom\r\n        };\r\n    }();\r\n\r\n    var isOperatorChar = /[+\\-*&%=<>!?|]/;\r\n\r\n    function chain(stream, state, f) {\r\n        state.tokenize = f;\r\n        return f(stream, state);\r\n    }\r\n\r\n    function nextUntilUnescaped(stream, end) {\r\n        var escaped = false, next;\r\n        while ((next = stream.next()) != null) {\r\n            if (next == end && !escaped)\r\n                return false;\r\n            escaped = !escaped && next == \"\\\\\";\r\n        }\r\n        return escaped;\r\n    }\r\n\r\n    // Used as scratch variables to communicate multiple values without\r\n    // consing up tons of objects.\r\n    var type, content;\r\n    function ret(tp, style, cont) {\r\n        type = tp; content = cont;\r\n        return style;\r\n    }\r\n\r\n    function jsTokenBase(stream, state) {\r\n        var ch = stream.next();\r\n        if (ch == '\"' || ch == \"'\")\r\n            return chain(stream, state, jsTokenString(ch));\r\n        else if (/[\\[\\]{}\\(\\),;\\:\\.]/.test(ch))\r\n            return ret(ch);\r\n        else if (ch == \"0\" && stream.eat(/x/i)) {\r\n            stream.eatWhile(/[\\da-f]/i);\r\n            return ret(\"number\", \"number\");\r\n        }\r\n        else if (/\\d/.test(ch)) {\r\n            stream.match(/^\\d*(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?/);\r\n            return ret(\"number\", \"number\");\r\n        }\r\n        else if (ch == \"/\") {\r\n            if (stream.eat(\"*\")) {\r\n                return chain(stream, state, jsTokenComment);\r\n            }\r\n            else if (stream.eat(\"/\")) {\r\n                stream.skipToEnd();\r\n                return ret(\"comment\", \"comment\");\r\n            }\r\n            else if (state.reAllowed) {\r\n                nextUntilUnescaped(stream, \"/\");\r\n                stream.eatWhile(/[gimy]/); // 'y' is \"sticky\" option in Mozilla\r\n                return ret(\"regexp\", \"string\");\r\n            }\r\n            else {\r\n                stream.eatWhile(isOperatorChar);\r\n                return ret(\"operator\", null, stream.current());\r\n            }\r\n        }\r\n        else if (ch == \"#\") {\r\n            stream.skipToEnd();\r\n            return ret(\"error\", \"error\");\r\n        }\r\n        else if (isOperatorChar.test(ch)) {\r\n            stream.eatWhile(isOperatorChar);\r\n            return ret(\"operator\", null, stream.current());\r\n        }\r\n        else {\r\n            stream.eatWhile(/[\\w\\$_]/);\r\n            var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];\r\n            return (known && state.kwAllowed) ? ret(known.type, known.style, word) :\r\n                ret(\"variable\", \"variable\", word);\r\n        }\r\n    }\r\n\r\n    function jsTokenString(quote) {\r\n        return function(stream, state) {\r\n            if (!nextUntilUnescaped(stream, quote))\r\n                state.tokenize = jsTokenBase;\r\n            return ret(\"string\", \"string\");\r\n        };\r\n    }\r\n\r\n    function jsTokenComment(stream, state) {\r\n        var maybeEnd = false, ch;\r\n        while (ch = stream.next()) {\r\n            if (ch == \"/\" && maybeEnd) {\r\n                state.tokenize = jsTokenBase;\r\n                break;\r\n            }\r\n            maybeEnd = (ch == \"*\");\r\n        }\r\n        return ret(\"comment\", \"comment\");\r\n    }\r\n\r\n    // Parser\r\n\r\n    var atomicTypes = {\"atom\": true, \"number\": true, \"variable\": true, \"string\": true, \"regexp\": true};\r\n\r\n    function JSLexical(indented, column, type, align, prev, info) {\r\n        this.indented = indented;\r\n        this.column = column;\r\n        this.type = type;\r\n        this.prev = prev;\r\n        this.info = info;\r\n        if (align != null) this.align = align;\r\n    }\r\n\r\n    function inScope(state, varname) {\r\n        for (var v = state.localVars; v; v = v.next)\r\n            if (v.name == varname) return true;\r\n    }\r\n\r\n    function parseJS(state, style, type, content, stream) {\r\n        var cc = state.cc;\r\n        // Communicate our context to the combinators.\r\n        // (Less wasteful than consing up a hundred closures on every call.)\r\n        cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc;\r\n\r\n        if (!state.lexical.hasOwnProperty(\"align\"))\r\n            state.lexical.align = true;\r\n\r\n        while(true) {\r\n            var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;\r\n            if (combinator(type, content)) {\r\n                while(cc.length && cc[cc.length - 1].lex)\r\n                    cc.pop()();\r\n                if (cx.marked) return cx.marked;\r\n                if (type == \"variable\" && inScope(state, content)) return \"variable-2\";\r\n                return style;\r\n            }\r\n        }\r\n    }\r\n\r\n    // Combinator utils\r\n\r\n    var cx = {state: null, column: null, marked: null, cc: null};\r\n    function pass() {\r\n        for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);\r\n    }\r\n    function cont() {\r\n        pass.apply(null, arguments);\r\n        return true;\r\n    }\r\n    function register(varname) {\r\n        var state = cx.state;\r\n        if (state.context) {\r\n            cx.marked = \"def\";\r\n            for (var v = state.localVars; v; v = v.next)\r\n                if (v.name == varname) return;\r\n            state.localVars = {name: varname, next: state.localVars};\r\n        }\r\n    }\r\n\r\n    // Combinators\r\n\r\n    var defaultVars = {name: \"this\", next: {name: \"arguments\"}};\r\n    function pushcontext() {\r\n        if (!cx.state.context) cx.state.localVars = defaultVars;\r\n        cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};\r\n    }\r\n    function popcontext() {\r\n        cx.state.localVars = cx.state.context.vars;\r\n        cx.state.context = cx.state.context.prev;\r\n    }\r\n    function pushlex(type, info) {\r\n        var result = function() {\r\n            var state = cx.state;\r\n            state.lexical = new JSLexical(state.indented, cx.stream.column(), type, null, state.lexical, info)\r\n        };\r\n        result.lex = true;\r\n        return result;\r\n    }\r\n    function poplex() {\r\n        var state = cx.state;\r\n        if (state.lexical.prev) {\r\n            if (state.lexical.type == \")\")\r\n                state.indented = state.lexical.indented;\r\n            state.lexical = state.lexical.prev;\r\n        }\r\n    }\r\n    poplex.lex = true;\r\n\r\n    function expect(wanted) {\r\n        return function expecting(type) {\r\n            if (type == wanted) return cont();\r\n            else if (wanted == \";\") return pass();\r\n            else return cont(arguments.callee);\r\n        };\r\n    }\r\n\r\n    function statement(type) {\r\n        if (type == \"var\") return cont(pushlex(\"vardef\"), vardef1, expect(\";\"), poplex);\r\n        if (type == \"keyword a\") return cont(pushlex(\"form\"), expression, statement, poplex);\r\n        if (type == \"keyword b\") return cont(pushlex(\"form\"), statement, poplex);\r\n        if (type == \"{\") return cont(pushlex(\"}\"), block, poplex);\r\n        if (type == \";\") return cont();\r\n        if (type == \"function\") return cont(functiondef);\r\n        if (type == \"for\") return cont(pushlex(\"form\"), expect(\"(\"), pushlex(\")\"), forspec1, expect(\")\"),\r\n            poplex, statement, poplex);\r\n        if (type == \"variable\") return cont(pushlex(\"stat\"), maybelabel);\r\n        if (type == \"switch\") return cont(pushlex(\"form\"), expression, pushlex(\"}\", \"switch\"), expect(\"{\"),\r\n            block, poplex, poplex);\r\n        if (type == \"case\") return cont(expression, expect(\":\"));\r\n        if (type == \"default\") return cont(expect(\":\"));\r\n        if (type == \"catch\") return cont(pushlex(\"form\"), pushcontext, expect(\"(\"), funarg, expect(\")\"),\r\n            statement, poplex, popcontext);\r\n        return pass(pushlex(\"stat\"), expression, expect(\";\"), poplex);\r\n    }\r\n    function expression(type) {\r\n        if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator);\r\n        if (type == \"function\") return cont(functiondef);\r\n        if (type == \"keyword c\") return cont(maybeexpression);\r\n        if (type == \"(\") return cont(pushlex(\")\"), expression, expect(\")\"), poplex, maybeoperator);\r\n        if (type == \"operator\") return cont(expression);\r\n        if (type == \"[\") return cont(pushlex(\"]\"), commasep(expression, \"]\"), poplex, maybeoperator);\r\n        if (type == \"{\") return cont(pushlex(\"}\"), commasep(objprop, \"}\"), poplex, maybeoperator);\r\n        return cont();\r\n    }\r\n    function maybeexpression(type) {\r\n        if (type.match(/[;\\}\\)\\],]/)) return pass();\r\n        return pass(expression);\r\n    }\r\n\r\n    function maybeoperator(type, value) {\r\n        if (type == \"operator\" && /\\+\\+|--/.test(value)) return cont(maybeoperator);\r\n        if (type == \"operator\") return cont(expression);\r\n        if (type == \";\") return;\r\n        if (type == \"(\") return cont(pushlex(\")\"), commasep(expression, \")\"), poplex, maybeoperator);\r\n        if (type == \".\") return cont(property, maybeoperator);\r\n        if (type == \"[\") return cont(pushlex(\"]\"), expression, expect(\"]\"), poplex, maybeoperator);\r\n    }\r\n    function maybelabel(type) {\r\n        if (type == \":\") return cont(poplex, statement);\r\n        return pass(maybeoperator, expect(\";\"), poplex);\r\n    }\r\n    function property(type) {\r\n        if (type == \"variable\") {cx.marked = \"property\"; return cont();}\r\n    }\r\n    function objprop(type) {\r\n        if (type == \"variable\") cx.marked = \"property\";\r\n        if (atomicTypes.hasOwnProperty(type)) return cont(expect(\":\"), expression);\r\n    }\r\n    function commasep(what, end) {\r\n        function proceed(type) {\r\n            if (type == \",\") return cont(what, proceed);\r\n            if (type == end) return cont();\r\n            return cont(expect(end));\r\n        }\r\n        return function commaSeparated(type) {\r\n            if (type == end) return cont();\r\n            else return pass(what, proceed);\r\n        };\r\n    }\r\n    function block(type) {\r\n        if (type == \"}\") return cont();\r\n        return pass(statement, block);\r\n    }\r\n    function vardef1(type, value) {\r\n        if (type == \"variable\"){register(value); return cont(vardef2);}\r\n        return cont();\r\n    }\r\n    function vardef2(type, value) {\r\n        if (value == \"=\") return cont(expression, vardef2);\r\n        if (type == \",\") return cont(vardef1);\r\n    }\r\n    function forspec1(type) {\r\n        if (type == \"var\") return cont(vardef1, forspec2);\r\n        if (type == \";\") return pass(forspec2);\r\n        if (type == \"variable\") return cont(formaybein);\r\n        return pass(forspec2);\r\n    }\r\n    function formaybein(type, value) {\r\n        if (value == \"in\") return cont(expression);\r\n        return cont(maybeoperator, forspec2);\r\n    }\r\n    function forspec2(type, value) {\r\n        if (type == \";\") return cont(forspec3);\r\n        if (value == \"in\") return cont(expression);\r\n        return cont(expression, expect(\";\"), forspec3);\r\n    }\r\n    function forspec3(type) {\r\n        if (type != \")\") cont(expression);\r\n    }\r\n    function functiondef(type, value) {\r\n        if (type == \"variable\") {register(value); return cont(functiondef);}\r\n        if (type == \"(\") return cont(pushlex(\")\"), pushcontext, commasep(funarg, \")\"), poplex, statement, popcontext);\r\n    }\r\n    function funarg(type, value) {\r\n        if (type == \"variable\") {register(value); return cont();}\r\n    }\r\n\r\n    // Interface\r\n\r\n    return {\r\n        startState: function(basecolumn) {\r\n            return {\r\n                tokenize: jsTokenBase,\r\n                reAllowed: true,\r\n                kwAllowed: true,\r\n                cc: [],\r\n                lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, \"block\", false),\r\n                localVars: null,\r\n                context: null,\r\n                indented: 0\r\n            };\r\n        },\r\n\r\n        token: function(stream, state) {\r\n            if (stream.sol()) {\r\n                if (!state.lexical.hasOwnProperty(\"align\"))\r\n                    state.lexical.align = false;\r\n                state.indented = stream.indentation();\r\n            }\r\n            if (stream.eatSpace()) return null;\r\n            var style = state.tokenize(stream, state);\r\n            if (type == \"comment\") return style;\r\n            state.reAllowed = type == \"operator\" || type == \"keyword c\" || type.match(/^[\\[{}\\(,;:]$/);\r\n            state.kwAllowed = type != '.';\r\n            return parseJS(state, style, type, content, stream);\r\n        },\r\n\r\n        indent: function(state, textAfter) {\r\n            if (state.tokenize != jsTokenBase) return 0;\r\n            var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical,\r\n                type = lexical.type, closing = firstChar == type;\r\n            if (type == \"vardef\") return lexical.indented + 4;\r\n            else if (type == \"form\" && firstChar == \"{\") return lexical.indented;\r\n            else if (type == \"stat\" || type == \"form\") return lexical.indented + indentUnit;\r\n            else if (lexical.info == \"switch\" && !closing)\r\n                return lexical.indented + (/^(?:case|default)\\b/.test(textAfter) ? indentUnit : 2 * indentUnit);\r\n            else if (lexical.align) return lexical.column + (closing ? 0 : 1);\r\n            else return lexical.indented + (closing ? 0 : indentUnit);\r\n        },\r\n\r\n        electricChars: \":{}\"\r\n    };\r\n});\r\n\r\nCodeMirror.defineMIME(\"text/javascript\", \"javascript\");\r\nCodeMirror.defineMIME(\"application/json\", {name: \"javascript\", json: true});\r\n\r\nCodeMirror.defineMode(\"css\", function(config) {\r\n    var indentUnit = config.indentUnit, type;\r\n    function ret(style, tp) {type = tp; return style;}\r\n\r\n    function tokenBase(stream, state) {\r\n        var ch = stream.next();\r\n        if (ch == \"@\") {stream.eatWhile(/[\\w\\\\\\-]/); return ret(\"meta\", stream.current());}\r\n        else if (ch == \"/\" && stream.eat(\"*\")) {\r\n            state.tokenize = tokenCComment;\r\n            return tokenCComment(stream, state);\r\n        }\r\n        else if (ch == \"<\" && stream.eat(\"!\")) {\r\n            state.tokenize = tokenSGMLComment;\r\n            return tokenSGMLComment(stream, state);\r\n        }\r\n        else if (ch == \"=\") ret(null, \"compare\");\r\n        else if ((ch == \"~\" || ch == \"|\") && stream.eat(\"=\")) return ret(null, \"compare\");\r\n        else if (ch == \"\\\"\" || ch == \"'\") {\r\n            state.tokenize = tokenString(ch);\r\n            return state.tokenize(stream, state);\r\n        }\r\n        else if (ch == \"#\") {\r\n            stream.eatWhile(/[\\w\\\\\\-]/);\r\n            return ret(\"atom\", \"hash\");\r\n        }\r\n        else if (ch == \"!\") {\r\n            stream.match(/^\\s*\\w*/);\r\n            return ret(\"keyword\", \"important\");\r\n        }\r\n        else if (/\\d/.test(ch)) {\r\n            stream.eatWhile(/[\\w.%]/);\r\n            return ret(\"number\", \"unit\");\r\n        }\r\n        else if (/[,.+>*\\/]/.test(ch)) {\r\n            return ret(null, \"select-op\");\r\n        }\r\n        else if (/[;{}:\\[\\]]/.test(ch)) {\r\n            return ret(null, ch);\r\n        }\r\n        else {\r\n            stream.eatWhile(/[\\w\\\\\\-]/);\r\n            return ret(\"variable\", \"variable\");\r\n        }\r\n    }\r\n\r\n    function tokenCComment(stream, state) {\r\n        var maybeEnd = false, ch;\r\n        while ((ch = stream.next()) != null) {\r\n            if (maybeEnd && ch == \"/\") {\r\n                state.tokenize = tokenBase;\r\n                break;\r\n            }\r\n            maybeEnd = (ch == \"*\");\r\n        }\r\n        return ret(\"comment\", \"comment\");\r\n    }\r\n\r\n    function tokenSGMLComment(stream, state) {\r\n        var dashes = 0, ch;\r\n        while ((ch = stream.next()) != null) {\r\n            if (dashes >= 2 && ch == \">\") {\r\n                state.tokenize = tokenBase;\r\n                break;\r\n            }\r\n            dashes = (ch == \"-\") ? dashes + 1 : 0;\r\n        }\r\n        return ret(\"comment\", \"comment\");\r\n    }\r\n\r\n    function tokenString(quote) {\r\n        return function(stream, state) {\r\n            var escaped = false, ch;\r\n            while ((ch = stream.next()) != null) {\r\n                if (ch == quote && !escaped)\r\n                    break;\r\n                escaped = !escaped && ch == \"\\\\\";\r\n            }\r\n            if (!escaped) state.tokenize = tokenBase;\r\n            return ret(\"string\", \"string\");\r\n        };\r\n    }\r\n\r\n    return {\r\n        startState: function(base) {\r\n            return {tokenize: tokenBase,\r\n                baseIndent: base || 0,\r\n                stack: []};\r\n        },\r\n\r\n        token: function(stream, state) {\r\n            if (stream.eatSpace()) return null;\r\n            var style = state.tokenize(stream, state);\r\n\r\n            var context = state.stack[state.stack.length-1];\r\n            if (type == \"hash\" && context == \"rule\") style = \"atom\";\r\n            else if (style == \"variable\") {\r\n                if (context == \"rule\") style = \"number\";\r\n                else if (!context || context == \"@media{\") style = \"tag\";\r\n            }\r\n\r\n            if (context == \"rule\" && /^[\\{\\};]$/.test(type))\r\n                state.stack.pop();\r\n            if (type == \"{\") {\r\n                if (context == \"@media\") state.stack[state.stack.length-1] = \"@media{\";\r\n                else state.stack.push(\"{\");\r\n            }\r\n            else if (type == \"}\") state.stack.pop();\r\n            else if (type == \"@media\") state.stack.push(\"@media\");\r\n            else if (context == \"{\" && type != \"comment\") state.stack.push(\"rule\");\r\n            return style;\r\n        },\r\n\r\n        indent: function(state, textAfter) {\r\n            var n = state.stack.length;\r\n            if (/^\\}/.test(textAfter))\r\n                n -= state.stack[state.stack.length-1] == \"rule\" ? 2 : 1;\r\n            return state.baseIndent + n * indentUnit;\r\n        },\r\n\r\n        electricChars: \"}\"\r\n    };\r\n});\r\n\r\nCodeMirror.defineMIME(\"text/css\", \"css\");\r\nCodeMirror.defineMode(\"htmlmixed\", function(config, parserConfig) {\r\n    var htmlMode = CodeMirror.getMode(config, {name: \"xml\", htmlMode: true});\r\n    var jsMode = CodeMirror.getMode(config, \"javascript\");\r\n    var cssMode = CodeMirror.getMode(config, \"css\");\r\n\r\n    function html(stream, state) {\r\n        var style = htmlMode.token(stream, state.htmlState);\r\n        if (style == \"tag\" && stream.current() == \">\" && state.htmlState.context) {\r\n            if (/^script$/i.test(state.htmlState.context.tagName)) {\r\n                state.token = javascript;\r\n                state.localState = jsMode.startState(htmlMode.indent(state.htmlState, \"\"));\r\n                state.mode = \"javascript\";\r\n            }\r\n            else if (/^style$/i.test(state.htmlState.context.tagName)) {\r\n                state.token = css;\r\n                state.localState = cssMode.startState(htmlMode.indent(state.htmlState, \"\"));\r\n                state.mode = \"css\";\r\n            }\r\n        }\r\n        return style;\r\n    }\r\n    function maybeBackup(stream, pat, style) {\r\n        var cur = stream.current();\r\n        var close = cur.search(pat);\r\n        if (close > -1) stream.backUp(cur.length - close);\r\n        return style;\r\n    }\r\n    function javascript(stream, state) {\r\n        if (stream.match(/^<\\/\\s*script\\s*>/i, false)) {\r\n            state.token = html;\r\n            state.curState = null;\r\n            state.mode = \"html\";\r\n            return html(stream, state);\r\n        }\r\n        return maybeBackup(stream, /<\\/\\s*script\\s*>/,\r\n            jsMode.token(stream, state.localState));\r\n    }\r\n    function css(stream, state) {\r\n        if (stream.match(/^<\\/\\s*style\\s*>/i, false)) {\r\n            state.token = html;\r\n            state.localState = null;\r\n            state.mode = \"html\";\r\n            return html(stream, state);\r\n        }\r\n        return maybeBackup(stream, /<\\/\\s*style\\s*>/,\r\n            cssMode.token(stream, state.localState));\r\n    }\r\n\r\n    return {\r\n        startState: function() {\r\n            var state = htmlMode.startState();\r\n            return {token: html, localState: null, mode: \"html\", htmlState: state};\r\n        },\r\n\r\n        copyState: function(state) {\r\n            if (state.localState)\r\n                var local = CodeMirror.copyState(state.token == css ? cssMode : jsMode, state.localState);\r\n            return {token: state.token, localState: local, mode: state.mode,\r\n                htmlState: CodeMirror.copyState(htmlMode, state.htmlState)};\r\n        },\r\n\r\n        token: function(stream, state) {\r\n            return state.token(stream, state);\r\n        },\r\n\r\n        indent: function(state, textAfter) {\r\n            if (state.token == html || /^\\s*<\\//.test(textAfter))\r\n                return htmlMode.indent(state.htmlState, textAfter);\r\n            else if (state.token == javascript)\r\n                return jsMode.indent(state.localState, textAfter);\r\n            else\r\n                return cssMode.indent(state.localState, textAfter);\r\n        },\r\n\r\n        compareStates: function(a, b) {\r\n            return htmlMode.compareStates(a.htmlState, b.htmlState);\r\n        },\r\n\r\n        electricChars: \"/{}:\"\r\n    }\r\n});\r\n\r\nCodeMirror.defineMIME(\"text/html\", \"htmlmixed\");\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/highcharts/adapters/mootools-adapter.js",
    "content": "/*\r\n Highcharts JS v3.0.6 (2013-10-04)\r\n MooTools adapter\r\n\r\n (c) 2010-2013 Torstein Hønsi\r\n\r\n License: www.highcharts.com/license\r\n*/\r\n(function(){var e=window,h=document,f=e.MooTools.version.substring(0,3),i=f===\"1.2\"||f===\"1.1\",j=i||f===\"1.3\",g=e.$extend||function(){return Object.append.apply(Object,arguments)};e.HighchartsAdapter={init:function(a){var b=Fx.prototype,c=b.start,d=Fx.Morph.prototype,e=d.compute;b.start=function(b,d){var e=this.element;if(b.d)this.paths=a.init(e,e.d,this.toD);c.apply(this,arguments);return this};d.compute=function(b,c,d){var f=this.paths;if(f)this.element.attr(\"d\",a.step(f[0],f[1],d,this.toD));else return e.apply(this,\r\narguments)}},adapterRun:function(a,b){if(b===\"width\"||b===\"height\")return parseInt($(a).getStyle(b),10)},getScript:function(a,b){var c=h.getElementsByTagName(\"head\")[0],d=h.createElement(\"script\");d.type=\"text/javascript\";d.src=a;d.onload=b;c.appendChild(d)},animate:function(a,b,c){var d=a.attr,f=c&&c.complete;if(d&&!a.setStyle)a.getStyle=a.attr,a.setStyle=function(){var a=arguments;this.attr.call(this,a[0],a[1][0])},a.$family=function(){return!0};e.HighchartsAdapter.stop(a);c=new Fx.Morph(d?a:$(a),\r\ng({transition:Fx.Transitions.Quad.easeInOut},c));if(d)c.element=a;if(b.d)c.toD=b.d;f&&c.addEvent(\"complete\",f);c.start(b);a.fx=c},each:function(a,b){return i?$each(a,b):Array.each(a,b)},map:function(a,b){return a.map(b)},grep:function(a,b){return a.filter(b)},inArray:function(a,b,c){return b?b.indexOf(a,c):-1},offset:function(a){a=a.getPosition();return{left:a.x,top:a.y}},extendWithEvents:function(a){a.addEvent||(a.nodeName?$(a):g(a,new Events))},addEvent:function(a,b,c){typeof b===\"string\"&&(b===\r\n\"unload\"&&(b=\"beforeunload\"),e.HighchartsAdapter.extendWithEvents(a),a.addEvent(b,c))},removeEvent:function(a,b,c){typeof a!==\"string\"&&a.addEvent&&(b?(b===\"unload\"&&(b=\"beforeunload\"),c?a.removeEvent(b,c):a.removeEvents&&a.removeEvents(b)):a.removeEvents())},fireEvent:function(a,b,c,d){b={type:b,target:a};b=j?new Event(b):new DOMEvent(b);b=g(b,c);if(!b.target&&b.event)b.target=b.event.target;b.preventDefault=function(){d=null};a.fireEvent&&a.fireEvent(b.type,b);d&&d(b)},washMouseEvent:function(a){if(a.page)a.pageX=\r\na.page.x,a.pageY=a.page.y;return a},stop:function(a){a.fx&&a.fx.cancel()}}})();\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/highcharts/adapters/mootools-adapter.src.js",
    "content": "/**\r\n * @license Highcharts JS v3.0.6 (2013-10-04)\r\n * MooTools adapter\r\n *\r\n * (c) 2010-2013 Torstein Hønsi\r\n *\r\n * License: www.highcharts.com/license\r\n */\r\n\r\n// JSLint options:\r\n/*global Fx, $, $extend, $each, $merge, Events, Event, DOMEvent */\r\n\r\n(function () {\r\n\r\nvar win = window,\r\n\tdoc = document,\r\n\tmooVersion = win.MooTools.version.substring(0, 3), // Get the first three characters of the version number\r\n\tlegacy = mooVersion === '1.2' || mooVersion === '1.1', // 1.1 && 1.2 considered legacy, 1.3 is not.\r\n\tlegacyEvent = legacy || mooVersion === '1.3', // In versions 1.1 - 1.3 the event class is named Event, in newer versions it is named DOMEvent.\r\n\t$extend = win.$extend || function () {\r\n\t\treturn Object.append.apply(Object, arguments);\r\n\t};\r\n\r\nwin.HighchartsAdapter = {\r\n\t/**\r\n\t * Initialize the adapter. This is run once as Highcharts is first run.\r\n\t * @param {Object} pathAnim The helper object to do animations across adapters.\r\n\t */\r\n\tinit: function (pathAnim) {\r\n\t\tvar fxProto = Fx.prototype,\r\n\t\t\tfxStart = fxProto.start,\r\n\t\t\tmorphProto = Fx.Morph.prototype,\r\n\t\t\tmorphCompute = morphProto.compute;\r\n\r\n\t\t// override Fx.start to allow animation of SVG element wrappers\r\n\t\t/*jslint unparam: true*//* allow unused parameters in fx functions */\r\n\t\tfxProto.start = function (from, to) {\r\n\t\t\tvar fx = this,\r\n\t\t\t\telem = fx.element;\r\n\r\n\t\t\t// special for animating paths\r\n\t\t\tif (from.d) {\r\n\t\t\t\t//this.fromD = this.element.d.split(' ');\r\n\t\t\t\tfx.paths = pathAnim.init(\r\n\t\t\t\t\telem,\r\n\t\t\t\t\telem.d,\r\n\t\t\t\t\tfx.toD\r\n\t\t\t\t);\r\n\t\t\t}\r\n\t\t\tfxStart.apply(fx, arguments);\r\n\r\n\t\t\treturn this; // chainable\r\n\t\t};\r\n\r\n\t\t// override Fx.step to allow animation of SVG element wrappers\r\n\t\tmorphProto.compute = function (from, to, delta) {\r\n\t\t\tvar fx = this,\r\n\t\t\t\tpaths = fx.paths;\r\n\r\n\t\t\tif (paths) {\r\n\t\t\t\tfx.element.attr(\r\n\t\t\t\t\t'd',\r\n\t\t\t\t\tpathAnim.step(paths[0], paths[1], delta, fx.toD)\r\n\t\t\t\t);\r\n\t\t\t} else {\r\n\t\t\t\treturn morphCompute.apply(fx, arguments);\r\n\t\t\t}\r\n\t\t};\r\n\t\t/*jslint unparam: false*/\r\n\t},\r\n\t\r\n\t/**\r\n\t * Run a general method on the framework, following jQuery syntax\r\n\t * @param {Object} el The HTML element\r\n\t * @param {String} method Which method to run on the wrapped element\r\n\t */\r\n\tadapterRun: function (el, method) {\r\n\t\t\r\n\t\t// This currently works for getting inner width and height. If adding\r\n\t\t// more methods later, we need a conditional implementation for each.\r\n\t\tif (method === 'width' || method === 'height') {\r\n\t\t\treturn parseInt($(el).getStyle(method), 10);\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Downloads a script and executes a callback when done.\r\n\t * @param {String} scriptLocation\r\n\t * @param {Function} callback\r\n\t */\r\n\tgetScript: function (scriptLocation, callback) {\r\n\t\t// We cannot assume that Assets class from mootools-more is available so instead insert a script tag to download script.\r\n\t\tvar head = doc.getElementsByTagName('head')[0];\r\n\t\tvar script = doc.createElement('script');\r\n\r\n\t\tscript.type = 'text/javascript';\r\n\t\tscript.src = scriptLocation;\r\n\t\tscript.onload = callback;\r\n\r\n\t\thead.appendChild(script);\r\n\t},\r\n\r\n\t/**\r\n\t * Animate a HTML element or SVG element wrapper\r\n\t * @param {Object} el\r\n\t * @param {Object} params\r\n\t * @param {Object} options jQuery-like animation options: duration, easing, callback\r\n\t */\r\n\tanimate: function (el, params, options) {\r\n\t\tvar isSVGElement = el.attr,\r\n\t\t\teffect,\r\n\t\t\tcomplete = options && options.complete;\r\n\r\n\t\tif (isSVGElement && !el.setStyle) {\r\n\t\t\t// add setStyle and getStyle methods for internal use in Moo\r\n\t\t\tel.getStyle = el.attr;\r\n\t\t\tel.setStyle = function () { // property value is given as array in Moo - break it down\r\n\t\t\t\tvar args = arguments;\r\n\t\t\t\tthis.attr.call(this, args[0], args[1][0]);\r\n\t\t\t};\r\n\t\t\t// dirty hack to trick Moo into handling el as an element wrapper\r\n\t\t\tel.$family = function () { return true; };\r\n\t\t}\r\n\r\n\t\t// stop running animations\r\n\t\twin.HighchartsAdapter.stop(el);\r\n\r\n\t\t// define and run the effect\r\n\t\teffect = new Fx.Morph(\r\n\t\t\tisSVGElement ? el : $(el),\r\n\t\t\t$extend({\r\n\t\t\t\ttransition: Fx.Transitions.Quad.easeInOut\r\n\t\t\t}, options)\r\n\t\t);\r\n\r\n\t\t// Make sure that the element reference is set when animating svg elements\r\n\t\tif (isSVGElement) {\r\n\t\t\teffect.element = el;\r\n\t\t}\r\n\r\n\t\t// special treatment for paths\r\n\t\tif (params.d) {\r\n\t\t\teffect.toD = params.d;\r\n\t\t}\r\n\r\n\t\t// jQuery-like events\r\n\t\tif (complete) {\r\n\t\t\teffect.addEvent('complete', complete);\r\n\t\t}\r\n\r\n\t\t// run\r\n\t\teffect.start(params);\r\n\r\n\t\t// record for use in stop method\r\n\t\tel.fx = effect;\r\n\t},\r\n\r\n\t/**\r\n\t * MooTool's each function\r\n\t *\r\n\t */\r\n\teach: function (arr, fn) {\r\n\t\treturn legacy ?\r\n\t\t\t$each(arr, fn) :\r\n\t\t\tArray.each(arr, fn);\r\n\t},\r\n\r\n\t/**\r\n\t * Map an array\r\n\t * @param {Array} arr\r\n\t * @param {Function} fn\r\n\t */\r\n\tmap: function (arr, fn) {\r\n\t\treturn arr.map(fn);\r\n\t},\r\n\r\n\t/**\r\n\t * Grep or filter an array\r\n\t * @param {Array} arr\r\n\t * @param {Function} fn\r\n\t */\r\n\tgrep: function (arr, fn) {\r\n\t\treturn arr.filter(fn);\r\n\t},\r\n\t\r\n\t/**\r\n\t * Return the index of an item in an array, or -1 if not matched\r\n\t */\r\n\tinArray: function (item, arr, from) {\r\n\t\treturn arr ? arr.indexOf(item, from) : -1;\r\n\t},\r\n\r\n\t/**\r\n\t * Get the offset of an element relative to the top left corner of the web page\r\n\t */\r\n\toffset: function (el) {\r\n\t\tvar offsets = el.getPosition(); // #1496\r\n\t\treturn {\r\n\t\t\tleft: offsets.x,\r\n\t\t\ttop: offsets.y\r\n\t\t};\r\n\t},\r\n\r\n\t/**\r\n\t * Extends an object with Events, if its not done\r\n\t */\r\n\textendWithEvents: function (el) {\r\n\t\t// if the addEvent method is not defined, el is a custom Highcharts object\r\n\t\t// like series or point\r\n\t\tif (!el.addEvent) {\r\n\t\t\tif (el.nodeName) {\r\n\t\t\t\tel = $(el); // a dynamically generated node\r\n\t\t\t} else {\r\n\t\t\t\t$extend(el, new Events()); // a custom object\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Add an event listener\r\n\t * @param {Object} el HTML element or custom object\r\n\t * @param {String} type Event type\r\n\t * @param {Function} fn Event handler\r\n\t */\r\n\taddEvent: function (el, type, fn) {\r\n\t\tif (typeof type === 'string') { // chart broke due to el being string, type function\r\n\r\n\t\t\tif (type === 'unload') { // Moo self destructs before custom unload events\r\n\t\t\t\ttype = 'beforeunload';\r\n\t\t\t}\r\n\r\n\t\t\twin.HighchartsAdapter.extendWithEvents(el);\r\n\r\n\t\t\tel.addEvent(type, fn);\r\n\t\t}\r\n\t},\r\n\r\n\tremoveEvent: function (el, type, fn) {\r\n\t\tif (typeof el === 'string') {\r\n\t\t\t// el.removeEvents below apperantly calls this method again. Do not quite understand why, so for now just bail out.\r\n\t\t\treturn;\r\n\t\t}\r\n\t\t\r\n\t\tif (el.addEvent) { // If el doesn't have an addEvent method, there are no events to remove\r\n\t\t\tif (type) {\r\n\t\t\t\tif (type === 'unload') { // Moo self destructs before custom unload events\r\n\t\t\t\t\ttype = 'beforeunload';\r\n\t\t\t\t}\r\n\t\r\n\t\t\t\tif (fn) {\r\n\t\t\t\t\tel.removeEvent(type, fn);\r\n\t\t\t\t} else if (el.removeEvents) { // #958\r\n\t\t\t\t\tel.removeEvents(type);\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\tel.removeEvents();\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\r\n\tfireEvent: function (el, event, eventArguments, defaultFunction) {\r\n\t\tvar eventArgs = {\r\n\t\t\ttype: event,\r\n\t\t\ttarget: el\r\n\t\t};\r\n\t\t// create an event object that keeps all functions\r\n\t\tevent = legacyEvent ? new Event(eventArgs) : new DOMEvent(eventArgs);\r\n\t\tevent = $extend(event, eventArguments);\r\n\r\n\t\t// When running an event on the Chart.prototype, MooTools nests the target in event.event\r\n\t\tif (!event.target && event.event) {\r\n\t\t\tevent.target = event.event.target;\r\n\t\t}\r\n\r\n\t\t// override the preventDefault function to be able to use\r\n\t\t// this for custom events\r\n\t\tevent.preventDefault = function () {\r\n\t\t\tdefaultFunction = null;\r\n\t\t};\r\n\t\t// if fireEvent is not available on the object, there hasn't been added\r\n\t\t// any events to it above\r\n\t\tif (el.fireEvent) {\r\n\t\t\tel.fireEvent(event.type, event);\r\n\t\t}\r\n\r\n\t\t// fire the default if it is passed and it is not prevented above\r\n\t\tif (defaultFunction) {\r\n\t\t\tdefaultFunction(event);\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Set back e.pageX and e.pageY that MooTools has abstracted away. #1165, #1346.\r\n\t */\r\n\twashMouseEvent: function (e) {\r\n\t\tif (e.page) {\r\n\t\t\te.pageX = e.page.x;\r\n\t\t\te.pageY = e.page.y;\r\n\t\t}\r\n\t\treturn e;\r\n\t},\r\n\r\n\t/**\r\n\t * Stop running animations on the object\r\n\t */\r\n\tstop: function (el) {\r\n\t\tif (el.fx) {\r\n\t\t\tel.fx.cancel();\r\n\t\t}\r\n\t}\r\n};\r\n\r\n}());\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/highcharts/adapters/prototype-adapter.js",
    "content": "/*\r\n Highcharts JS v3.0.6 (2013-10-04)\r\n Prototype adapter\r\n\r\n @author Michael Nelson, Torstein Hønsi.\r\n\r\n Feel free to use and modify this script.\r\n Highcharts license: www.highcharts.com/license.\r\n*/\r\nvar HighchartsAdapter=function(){var f=typeof Effect!==\"undefined\";return{init:function(a){if(f)Effect.HighchartsTransition=Class.create(Effect.Base,{initialize:function(b,c,d,g){var e;this.element=b;this.key=c;e=b.attr?b.attr(c):$(b).getStyle(c);if(c===\"d\")this.paths=a.init(b,b.d,d),this.toD=d,e=0,d=1;this.start(Object.extend(g||{},{from:e,to:d,attribute:c}))},setup:function(){HighchartsAdapter._extend(this.element);if(!this.element._highchart_animation)this.element._highchart_animation={};this.element._highchart_animation[this.key]=\r\nthis},update:function(b){var c=this.paths,d=this.element;c&&(b=a.step(c[0],c[1],b,this.toD));d.attr?d.element&&d.attr(this.options.attribute,b):(c={},c[this.options.attribute]=b,$(d).setStyle(c))},finish:function(){this.element&&this.element._highchart_animation&&delete this.element._highchart_animation[this.key]}})},adapterRun:function(a,b){return parseInt($(a).getStyle(b),10)},getScript:function(a,b){var c=$$(\"head\")[0];c&&c.appendChild((new Element(\"script\",{type:\"text/javascript\",src:a})).observe(\"load\",\r\nb))},addNS:function(a){var b=/^(?:click|mouse(?:down|up|over|move|out))$/;return/^(?:load|unload|abort|error|select|change|submit|reset|focus|blur|resize|scroll)$/.test(a)||b.test(a)?a:\"h:\"+a},addEvent:function(a,b,c){a.addEventListener||a.attachEvent?Event.observe($(a),HighchartsAdapter.addNS(b),c):(HighchartsAdapter._extend(a),a._highcharts_observe(b,c))},animate:function(a,b,c){var d,c=c||{};c.delay=0;c.duration=(c.duration||500)/1E3;c.afterFinish=c.complete;if(f)for(d in b)new Effect.HighchartsTransition($(a),\r\nd,b[d],c);else{if(a.attr)for(d in b)a.attr(d,b[d]);c.complete&&c.complete()}a.attr||$(a).setStyle(b)},stop:function(a){var b;if(a._highcharts_extended&&a._highchart_animation)for(b in a._highchart_animation)a._highchart_animation[b].cancel()},each:function(a,b){$A(a).each(b)},inArray:function(a,b,c){return b?b.indexOf(a,c):-1},offset:function(a){return $(a).cumulativeOffset()},fireEvent:function(a,b,c,d){a.fire?a.fire(HighchartsAdapter.addNS(b),c):a._highcharts_extended&&(c=c||{},a._highcharts_fire(b,\r\nc));c&&c.defaultPrevented&&(d=null);d&&d(c)},removeEvent:function(a,b,c){$(a).stopObserving&&(b&&(b=HighchartsAdapter.addNS(b)),$(a).stopObserving(b,c));window===a?Event.stopObserving(a,b,c):(HighchartsAdapter._extend(a),a._highcharts_stop_observing(b,c))},washMouseEvent:function(a){return a},grep:function(a,b){return a.findAll(b)},map:function(a,b){return a.map(b)},_extend:function(a){a._highcharts_extended||Object.extend(a,{_highchart_events:{},_highchart_animation:null,_highcharts_extended:!0,\r\n_highcharts_observe:function(b,a){this._highchart_events[b]=[this._highchart_events[b],a].compact().flatten()},_highcharts_stop_observing:function(b,a){b?a?this._highchart_events[b]=[this._highchart_events[b]].compact().flatten().without(a):delete this._highchart_events[b]:this._highchart_events={}},_highcharts_fire:function(a,c){var d=this;(this._highchart_events[a]||[]).each(function(a){if(!c.stopped)c.preventDefault=function(){c.defaultPrevented=!0},c.target=d,a.bind(this)(c)===!1&&c.preventDefault()}.bind(this))}})}}}();\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/highcharts/adapters/prototype-adapter.src.js",
    "content": "/**\r\n * @license Highcharts JS v3.0.6 (2013-10-04)\r\n * Prototype adapter\r\n *\r\n * @author Michael Nelson, Torstein Hønsi.\r\n *\r\n * Feel free to use and modify this script.\r\n * Highcharts license: www.highcharts.com/license.\r\n */\r\n\r\n// JSLint options:\r\n/*global Effect, Class, Event, Element, $, $$, $A */\r\n\r\n// Adapter interface between prototype and the Highcharts charting library\r\nvar HighchartsAdapter = (function () {\r\n\r\nvar hasEffect = typeof Effect !== 'undefined';\r\n\r\nreturn {\r\n\r\n\t/**\r\n\t * Initialize the adapter. This is run once as Highcharts is first run.\r\n\t * @param {Object} pathAnim The helper object to do animations across adapters.\r\n\t */\r\n\tinit: function (pathAnim) {\r\n\t\tif (hasEffect) {\r\n\t\t\t/**\r\n\t\t\t * Animation for Highcharts SVG element wrappers only\r\n\t\t\t * @param {Object} element\r\n\t\t\t * @param {Object} attribute\r\n\t\t\t * @param {Object} to\r\n\t\t\t * @param {Object} options\r\n\t\t\t */\r\n\t\t\tEffect.HighchartsTransition = Class.create(Effect.Base, {\r\n\t\t\t\tinitialize: function (element, attr, to, options) {\r\n\t\t\t\t\tvar from,\r\n\t\t\t\t\t\topts;\r\n\r\n\t\t\t\t\tthis.element = element;\r\n\t\t\t\t\tthis.key = attr;\r\n\t\t\t\t\tfrom = element.attr ? element.attr(attr) : $(element).getStyle(attr);\r\n\r\n\t\t\t\t\t// special treatment for paths\r\n\t\t\t\t\tif (attr === 'd') {\r\n\t\t\t\t\t\tthis.paths = pathAnim.init(\r\n\t\t\t\t\t\t\telement,\r\n\t\t\t\t\t\t\telement.d,\r\n\t\t\t\t\t\t\tto\r\n\t\t\t\t\t\t);\r\n\t\t\t\t\t\tthis.toD = to;\r\n\r\n\r\n\t\t\t\t\t\t// fake values in order to read relative position as a float in update\r\n\t\t\t\t\t\tfrom = 0;\r\n\t\t\t\t\t\tto = 1;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\topts = Object.extend((options || {}), {\r\n\t\t\t\t\t\tfrom: from,\r\n\t\t\t\t\t\tto: to,\r\n\t\t\t\t\t\tattribute: attr\r\n\t\t\t\t\t});\r\n\t\t\t\t\tthis.start(opts);\r\n\t\t\t\t},\r\n\t\t\t\tsetup: function () {\r\n\t\t\t\t\tHighchartsAdapter._extend(this.element);\r\n\t\t\t\t\t// If this is the first animation on this object, create the _highcharts_animation helper that\r\n\t\t\t\t\t// contain pointers to the animation objects.\r\n\t\t\t\t\tif (!this.element._highchart_animation) {\r\n\t\t\t\t\t\tthis.element._highchart_animation = {};\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t// Store a reference to this animation instance.\r\n\t\t\t\t\tthis.element._highchart_animation[this.key] = this;\r\n\t\t\t\t},\r\n\t\t\t\tupdate: function (position) {\r\n\t\t\t\t\tvar paths = this.paths,\r\n\t\t\t\t\t\telement = this.element,\r\n\t\t\t\t\t\tobj;\r\n\r\n\t\t\t\t\tif (paths) {\r\n\t\t\t\t\t\tposition = pathAnim.step(paths[0], paths[1], position, this.toD);\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tif (element.attr) { // SVGElement\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tif (element.element) { // If not, it has been destroyed (#1405)\r\n\t\t\t\t\t\t\telement.attr(this.options.attribute, position);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\t} else { // HTML, #409\r\n\t\t\t\t\t\tobj = {};\r\n\t\t\t\t\t\tobj[this.options.attribute] = position;\r\n\t\t\t\t\t\t$(element).setStyle(obj);\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t},\r\n\t\t\t\tfinish: function () {\r\n\t\t\t\t\t// Delete the property that holds this animation now that it is finished.\r\n\t\t\t\t\t// Both canceled animations and complete ones gets a 'finish' call.\r\n\t\t\t\t\tif (this.element && this.element._highchart_animation) { // #1405\r\n\t\t\t\t\t\tdelete this.element._highchart_animation[this.key];\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Run a general method on the framework, following jQuery syntax\r\n\t * @param {Object} el The HTML element\r\n\t * @param {String} method Which method to run on the wrapped element\r\n\t */\r\n\tadapterRun: function (el, method) {\r\n\t\t\r\n\t\t// This currently works for getting inner width and height. If adding\r\n\t\t// more methods later, we need a conditional implementation for each.\r\n\t\treturn parseInt($(el).getStyle(method), 10);\r\n\t\t\r\n\t},\r\n\r\n\t/**\r\n\t * Downloads a script and executes a callback when done.\r\n\t * @param {String} scriptLocation\r\n\t * @param {Function} callback\r\n\t */\r\n\tgetScript: function (scriptLocation, callback) {\r\n\t\tvar head = $$('head')[0]; // Returns an array, so pick the first element.\r\n\t\tif (head) {\r\n\t\t\t// Append a new 'script' element, set its type and src attributes, add a 'load' handler that calls the callback\r\n\t\t\thead.appendChild(new Element('script', { type: 'text/javascript', src: scriptLocation}).observe('load', callback));\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Custom events in prototype needs to be namespaced. This method adds a namespace 'h:' in front of\r\n\t * events that are not recognized as native.\r\n\t */\r\n\taddNS: function (eventName) {\r\n\t\tvar HTMLEvents = /^(?:load|unload|abort|error|select|change|submit|reset|focus|blur|resize|scroll)$/,\r\n\t\t\tMouseEvents = /^(?:click|mouse(?:down|up|over|move|out))$/;\r\n\t\treturn (HTMLEvents.test(eventName) || MouseEvents.test(eventName)) ?\r\n\t\t\teventName :\r\n\t\t\t'h:' + eventName;\r\n\t},\r\n\r\n\t// el needs an event to be attached. el is not necessarily a dom element\r\n\taddEvent: function (el, event, fn) {\r\n\t\tif (el.addEventListener || el.attachEvent) {\r\n\t\t\tEvent.observe($(el), HighchartsAdapter.addNS(event), fn);\r\n\r\n\t\t} else {\r\n\t\t\tHighchartsAdapter._extend(el);\r\n\t\t\tel._highcharts_observe(event, fn);\r\n\t\t}\r\n\t},\r\n\r\n\t// motion makes things pretty. use it if effects is loaded, if not... still get to the end result.\r\n\tanimate: function (el, params, options) {\r\n\t\tvar key,\r\n\t\t\tfx;\r\n\r\n\t\t// default options\r\n\t\toptions = options || {};\r\n\t\toptions.delay = 0;\r\n\t\toptions.duration = (options.duration || 500) / 1000;\r\n\t\toptions.afterFinish = options.complete;\r\n\r\n\t\t// animate wrappers and DOM elements\r\n\t\tif (hasEffect) {\r\n\t\t\tfor (key in params) {\r\n\t\t\t\t// The fx variable is seemingly thrown away here, but the Effect.setup will add itself to the _highcharts_animation object\r\n\t\t\t\t// on the element itself so its not really lost.\r\n\t\t\t\tfx = new Effect.HighchartsTransition($(el), key, params[key], options);\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tif (el.attr) { // #409 without effects\r\n\t\t\t\tfor (key in params) {\r\n\t\t\t\t\tel.attr(key, params[key]);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tif (options.complete) {\r\n\t\t\t\toptions.complete();\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (!el.attr) { // HTML element, #409\r\n\t\t\t$(el).setStyle(params);\r\n\t\t}\r\n\t},\r\n\r\n\t// this only occurs in higcharts 2.0+\r\n\tstop: function (el) {\r\n\t\tvar key;\r\n\t\tif (el._highcharts_extended && el._highchart_animation) {\r\n\t\t\tfor (key in el._highchart_animation) {\r\n\t\t\t\t// Cancel the animation\r\n\t\t\t\t// The 'finish' function in the Effect object will remove the reference\r\n\t\t\t\tel._highchart_animation[key].cancel();\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\r\n\t// um.. each\r\n\teach: function (arr, fn) {\r\n\t\t$A(arr).each(fn);\r\n\t},\r\n\t\r\n\tinArray: function (item, arr, from) {\r\n\t\treturn arr ? arr.indexOf(item, from) : -1;\r\n\t},\r\n\r\n\t/**\r\n\t * Get the cumulative offset relative to the top left of the page. This method, unlike its\r\n\t * jQuery and MooTools counterpart, still suffers from issue #208 regarding the position\r\n\t * of a chart within a fixed container.\r\n\t */\r\n\toffset: function (el) {\r\n\t\treturn $(el).cumulativeOffset();\r\n\t},\r\n\r\n\t// fire an event based on an event name (event) and an object (el).\r\n\t// again, el may not be a dom element\r\n\tfireEvent: function (el, event, eventArguments, defaultFunction) {\r\n\t\tif (el.fire) {\r\n\t\t\tel.fire(HighchartsAdapter.addNS(event), eventArguments);\r\n\t\t} else if (el._highcharts_extended) {\r\n\t\t\teventArguments = eventArguments || {};\r\n\t\t\tel._highcharts_fire(event, eventArguments);\r\n\t\t}\r\n\r\n\t\tif (eventArguments && eventArguments.defaultPrevented) {\r\n\t\t\tdefaultFunction = null;\r\n\t\t}\r\n\r\n\t\tif (defaultFunction) {\r\n\t\t\tdefaultFunction(eventArguments);\r\n\t\t}\r\n\t},\r\n\r\n\tremoveEvent: function (el, event, handler) {\r\n\t\tif ($(el).stopObserving) {\r\n\t\t\tif (event) {\r\n\t\t\t\tevent = HighchartsAdapter.addNS(event);\r\n\t\t\t}\r\n\t\t\t$(el).stopObserving(event, handler);\r\n\t\t} if (window === el) {\r\n\t\t\tEvent.stopObserving(el, event, handler);\r\n\t\t} else {\r\n\t\t\tHighchartsAdapter._extend(el);\r\n\t\t\tel._highcharts_stop_observing(event, handler);\r\n\t\t}\r\n\t},\r\n\t\r\n\twashMouseEvent: function (e) {\r\n\t\treturn e;\r\n\t},\r\n\r\n\t// um, grep\r\n\tgrep: function (arr, fn) {\r\n\t\treturn arr.findAll(fn);\r\n\t},\r\n\r\n\t// um, map\r\n\tmap: function (arr, fn) {\r\n\t\treturn arr.map(fn);\r\n\t},\r\n\r\n\t// extend an object to handle highchart events (highchart objects, not svg elements).\r\n\t// this is a very simple way of handling events but whatever, it works (i think)\r\n\t_extend: function (object) {\r\n\t\tif (!object._highcharts_extended) {\r\n\t\t\tObject.extend(object, {\r\n\t\t\t\t_highchart_events: {},\r\n\t\t\t\t_highchart_animation: null,\r\n\t\t\t\t_highcharts_extended: true,\r\n\t\t\t\t_highcharts_observe: function (name, fn) {\r\n\t\t\t\t\tthis._highchart_events[name] = [this._highchart_events[name], fn].compact().flatten();\r\n\t\t\t\t},\r\n\t\t\t\t_highcharts_stop_observing: function (name, fn) {\r\n\t\t\t\t\tif (name) {\r\n\t\t\t\t\t\tif (fn) {\r\n\t\t\t\t\t\t\tthis._highchart_events[name] = [this._highchart_events[name]].compact().flatten().without(fn);\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\tdelete this._highchart_events[name];\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tthis._highchart_events = {};\r\n\t\t\t\t\t}\r\n\t\t\t\t},\r\n\t\t\t\t_highcharts_fire: function (name, args) {\r\n\t\t\t\t\tvar target = this;\r\n\t\t\t\t\t(this._highchart_events[name] || []).each(function (fn) {\r\n\t\t\t\t\t\t// args is never null here\r\n\t\t\t\t\t\tif (args.stopped) {\r\n\t\t\t\t\t\t\treturn; // \"throw $break\" wasn't working. i think because of the scope of 'this'.\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t// Attach a simple preventDefault function to skip default handler if called\r\n\t\t\t\t\t\targs.preventDefault = function () {\r\n\t\t\t\t\t\t\targs.defaultPrevented = true;\r\n\t\t\t\t\t\t};\r\n\t\t\t\t\t\targs.target = target;\r\n\r\n\t\t\t\t\t\t// If the event handler return false, prevent the default handler from executing\r\n\t\t\t\t\t\tif (fn.bind(this)(args) === false) {\r\n\t\t\t\t\t\t\targs.preventDefault();\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n.bind(this));\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t}\r\n\t}\r\n};\r\n}());\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/highcharts/adapters/standalone-framework.js",
    "content": "/*\r\n Highcharts JS v3.0.6 (2013-10-04)\r\n\r\n Standalone Highcharts Framework\r\n\r\n License: MIT License\r\n*/\r\nvar HighchartsAdapter=function(){function o(c){function a(a,b,d){a.removeEventListener(b,d,!1)}function d(a,b,d){d=a.HCProxiedMethods[d.toString()];a.detachEvent(\"on\"+b,d)}function b(b,c){var f=b.HCEvents,i,g,k,j;if(b.removeEventListener)i=a;else if(b.attachEvent)i=d;else return;c?(g={},g[c]=!0):g=f;for(j in g)if(f[j])for(k=f[j].length;k--;)i(b,j,f[j][k])}c.HCExtended||Highcharts.extend(c,{HCExtended:!0,HCEvents:{},bind:function(b,a){var d=this,c=this.HCEvents,g;if(d.addEventListener)d.addEventListener(b,\r\na,!1);else if(d.attachEvent){g=function(b){a.call(d,b)};if(!d.HCProxiedMethods)d.HCProxiedMethods={};d.HCProxiedMethods[a.toString()]=g;d.attachEvent(\"on\"+b,g)}c[b]===r&&(c[b]=[]);c[b].push(a)},unbind:function(c,h){var f,i;c?(f=this.HCEvents[c]||[],h?(i=HighchartsAdapter.inArray(h,f),i>-1&&(f.splice(i,1),this.HCEvents[c]=f),this.removeEventListener?a(this,c,h):this.attachEvent&&d(this,c,h)):(b(this,c),this.HCEvents[c]=[])):(b(this),this.HCEvents={})},trigger:function(b,a){var d=this.HCEvents[b]||\r\n[],c=d.length,g,k,j;k=function(){a.defaultPrevented=!0};for(g=0;g<c;g++){j=d[g];if(a.stopped)break;a.preventDefault=k;a.target=this;a.type=b;j.call(this,a)===!1&&a.preventDefault()}}});return c}var r,l=document,p=[],m=[],q,n;Math.easeInOutSine=function(c,a,d,b){return-d/2*(Math.cos(Math.PI*c/b)-1)+a};return{init:function(c){if(!l.defaultView)this._getStyle=function(a,d){var b;return a.style[d]?a.style[d]:(d===\"opacity\"&&(d=\"filter\"),b=a.currentStyle[d.replace(/\\-(\\w)/g,function(a,b){return b.toUpperCase()})],\r\nd===\"filter\"&&(b=b.replace(/alpha\\(opacity=([0-9]+)\\)/,function(b,a){return a/100})),b===\"\"?1:b)},this.adapterRun=function(a,d){var b={width:\"clientWidth\",height:\"clientHeight\"}[d];if(b)return a.style.zoom=1,a[b]-2*parseInt(HighchartsAdapter._getStyle(a,\"padding\"),10)};if(!Array.prototype.forEach)this.each=function(a,d){for(var b=0,c=a.length;b<c;b++)if(d.call(a[b],a[b],b,a)===!1)return b};if(!Array.prototype.indexOf)this.inArray=function(a,d){var b,c=0;if(d)for(b=d.length;c<b;c++)if(d[c]===a)return c;\r\nreturn-1};if(!Array.prototype.filter)this.grep=function(a,d){for(var b=[],c=0,h=a.length;c<h;c++)d(a[c],c)&&b.push(a[c]);return b};n=function(a,c,b){this.options=c;this.elem=a;this.prop=b};n.prototype={update:function(){var a;a=this.paths;var d=this.elem,b=d.element;a&&b?d.attr(\"d\",c.step(a[0],a[1],this.now,this.toD)):d.attr?b&&d.attr(this.prop,this.now):(a={},a[d]=this.now+this.unit,Highcharts.css(d,a));this.options.step&&this.options.step.call(this.elem,this.now,this)},custom:function(a,c,b){var e=\r\nthis,h=function(a){return e.step(a)},f;this.startTime=+new Date;this.start=a;this.end=c;this.unit=b;this.now=this.start;this.pos=this.state=0;h.elem=this.elem;h()&&m.push(h)===1&&(q=setInterval(function(){for(f=0;f<m.length;f++)m[f]()||m.splice(f--,1);m.length||clearInterval(q)},13))},step:function(a){var c=+new Date,b;b=this.options;var e;if(this.elem.stopAnimation)b=!1;else if(a||c>=b.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();a=this.options.curAnim[this.prop]=\r\n!0;for(e in b.curAnim)b.curAnim[e]!==!0&&(a=!1);a&&b.complete&&b.complete.call(this.elem);b=!1}else e=c-this.startTime,this.state=e/b.duration,this.pos=b.easing(e,0,1,b.duration),this.now=this.start+(this.end-this.start)*this.pos,this.update(),b=!0;return b}};this.animate=function(a,d,b){var e,h=\"\",f,i,g;a.stopAnimation=!1;if(typeof b!==\"object\"||b===null)e=arguments,b={duration:e[2],easing:e[3],complete:e[4]};if(typeof b.duration!==\"number\")b.duration=400;b.easing=Math[b.easing]||Math.easeInOutSine;\r\nb.curAnim=Highcharts.extend({},d);for(g in d)i=new n(a,b,g),f=null,g===\"d\"?(i.paths=c.init(a,a.d,d.d),i.toD=d.d,e=0,f=1):a.attr?e=a.attr(g):(e=parseFloat(HighchartsAdapter._getStyle(a,g))||0,g!==\"opacity\"&&(h=\"px\")),f||(f=parseFloat(d[g])),i.custom(e,f,h)}},_getStyle:function(c,a){return window.getComputedStyle(c).getPropertyValue(a)},getScript:function(c,a){var d=l.getElementsByTagName(\"head\")[0],b=l.createElement(\"script\");b.type=\"text/javascript\";b.src=c;b.onload=a;d.appendChild(b)},inArray:function(c,\r\na){return a.indexOf?a.indexOf(c):p.indexOf.call(a,c)},adapterRun:function(c,a){return parseInt(HighchartsAdapter._getStyle(c,a),10)},grep:function(c,a){return p.filter.call(c,a)},map:function(c,a){for(var d=[],b=0,e=c.length;b<e;b++)d[b]=a.call(c[b],c[b],b,c);return d},offset:function(c){for(var a=0,d=0;c;)a+=c.offsetLeft,d+=c.offsetTop,c=c.offsetParent;return{left:a,top:d}},addEvent:function(c,a,d){o(c).bind(a,d)},removeEvent:function(c,a,d){o(c).unbind(a,d)},fireEvent:function(c,a,d,b){var e;l.createEvent&&\r\n(c.dispatchEvent||c.fireEvent)?(e=l.createEvent(\"Events\"),e.initEvent(a,!0,!0),e.target=c,Highcharts.extend(e,d),c.dispatchEvent?c.dispatchEvent(e):c.fireEvent(a,e)):c.HCExtended===!0&&(d=d||{},c.trigger(a,d));d&&d.defaultPrevented&&(b=null);b&&b(d)},washMouseEvent:function(c){return c},stop:function(c){c.stopAnimation=!0},each:function(c,a){return Array.prototype.forEach.call(c,a)}}}();\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/highcharts/adapters/standalone-framework.src.js",
    "content": "/**\r\n * @license Highcharts JS v3.0.6 (2013-10-04)\r\n *\r\n * Standalone Highcharts Framework\r\n *\r\n * License: MIT License\r\n */\r\n\r\n\r\n/*global Highcharts */\r\nvar HighchartsAdapter = (function () {\r\n\r\nvar UNDEFINED,\r\n\tdoc = document,\r\n\temptyArray = [],\r\n\ttimers = [],\r\n\ttimerId,\r\n\tFx;\r\n\r\nMath.easeInOutSine = function (t, b, c, d) {\r\n\treturn -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b;\r\n};\r\n\r\n\r\n\r\n/**\r\n * Extend given object with custom events\r\n */\r\nfunction augment(obj) {\r\n\tfunction removeOneEvent(el, type, fn) {\r\n\t\tel.removeEventListener(type, fn, false);\r\n\t}\r\n\r\n\tfunction IERemoveOneEvent(el, type, fn) {\r\n\t\tfn = el.HCProxiedMethods[fn.toString()];\r\n\t\tel.detachEvent('on' + type, fn);\r\n\t}\r\n\r\n\tfunction removeAllEvents(el, type) {\r\n\t\tvar events = el.HCEvents,\r\n\t\t\tremove,\r\n\t\t\ttypes,\r\n\t\t\tlen,\r\n\t\t\tn;\r\n\r\n\t\tif (el.removeEventListener) {\r\n\t\t\tremove = removeOneEvent;\r\n\t\t} else if (el.attachEvent) {\r\n\t\t\tremove = IERemoveOneEvent;\r\n\t\t} else {\r\n\t\t\treturn; // break on non-DOM events\r\n\t\t}\r\n\r\n\r\n\t\tif (type) {\r\n\t\t\ttypes = {};\r\n\t\t\ttypes[type] = true;\r\n\t\t} else {\r\n\t\t\ttypes = events;\r\n\t\t}\r\n\r\n\t\tfor (n in types) {\r\n\t\t\tif (events[n]) {\r\n\t\t\t\tlen = events[n].length;\r\n\t\t\t\twhile (len--) {\r\n\t\t\t\t\tremove(el, n, events[n][len]);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tif (!obj.HCExtended) {\r\n\t\tHighcharts.extend(obj, {\r\n\t\t\tHCExtended: true,\r\n\r\n\t\t\tHCEvents: {},\r\n\r\n\t\t\tbind: function (name, fn) {\r\n\t\t\t\tvar el = this,\r\n\t\t\t\t\tevents = this.HCEvents,\r\n\t\t\t\t\twrappedFn;\r\n\r\n\t\t\t\t// handle DOM events in modern browsers\r\n\t\t\t\tif (el.addEventListener) {\r\n\t\t\t\t\tel.addEventListener(name, fn, false);\r\n\r\n\t\t\t\t// handle old IE implementation\r\n\t\t\t\t} else if (el.attachEvent) {\r\n\t\t\t\t\t\r\n\t\t\t\t\twrappedFn = function (e) {\r\n\t\t\t\t\t\tfn.call(el, e);\r\n\t\t\t\t\t};\r\n\r\n\t\t\t\t\tif (!el.HCProxiedMethods) {\r\n\t\t\t\t\t\tel.HCProxiedMethods = {};\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t// link wrapped fn with original fn, so we can get this in removeEvent\r\n\t\t\t\t\tel.HCProxiedMethods[fn.toString()] = wrappedFn;\r\n\r\n\t\t\t\t\tel.attachEvent('on' + name, wrappedFn);\r\n\t\t\t\t}\r\n\r\n\r\n\t\t\t\tif (events[name] === UNDEFINED) {\r\n\t\t\t\t\tevents[name] = [];\r\n\t\t\t\t}\r\n\r\n\t\t\t\tevents[name].push(fn);\r\n\t\t\t},\r\n\r\n\t\t\tunbind: function (name, fn) {\r\n\t\t\t\tvar events,\r\n\t\t\t\t\tindex;\r\n\r\n\t\t\t\tif (name) {\r\n\t\t\t\t\tevents = this.HCEvents[name] || [];\r\n\t\t\t\t\tif (fn) {\r\n\t\t\t\t\t\tindex = HighchartsAdapter.inArray(fn, events);\r\n\t\t\t\t\t\tif (index > -1) {\r\n\t\t\t\t\t\t\tevents.splice(index, 1);\r\n\t\t\t\t\t\t\tthis.HCEvents[name] = events;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tif (this.removeEventListener) {\r\n\t\t\t\t\t\t\tremoveOneEvent(this, name, fn);\r\n\t\t\t\t\t\t} else if (this.attachEvent) {\r\n\t\t\t\t\t\t\tIERemoveOneEvent(this, name, fn);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tremoveAllEvents(this, name);\r\n\t\t\t\t\t\tthis.HCEvents[name] = [];\r\n\t\t\t\t\t}\r\n\t\t\t\t} else {\r\n\t\t\t\t\tremoveAllEvents(this);\r\n\t\t\t\t\tthis.HCEvents = {};\r\n\t\t\t\t}\r\n\t\t\t},\r\n\r\n\t\t\ttrigger: function (name, args) {\r\n\t\t\t\tvar events = this.HCEvents[name] || [],\r\n\t\t\t\t\ttarget = this,\r\n\t\t\t\t\tlen = events.length,\r\n\t\t\t\t\ti,\r\n\t\t\t\t\tpreventDefault,\r\n\t\t\t\t\tfn;\r\n\r\n\t\t\t\t// Attach a simple preventDefault function to skip default handler if called\r\n\t\t\t\tpreventDefault = function () {\r\n\t\t\t\t\targs.defaultPrevented = true;\r\n\t\t\t\t};\r\n\t\t\t\t\r\n\t\t\t\tfor (i = 0; i < len; i++) {\r\n\t\t\t\t\tfn = events[i];\r\n\r\n\t\t\t\t\t// args is never null here\r\n\t\t\t\t\tif (args.stopped) {\r\n\t\t\t\t\t\treturn;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\targs.preventDefault = preventDefault;\r\n\t\t\t\t\targs.target = target;\r\n\t\t\t\t\targs.type = name; // #2297\t\r\n\t\t\t\t\t\r\n\t\t\t\t\t// If the event handler return false, prevent the default handler from executing\r\n\t\t\t\t\tif (fn.call(this, args) === false) {\r\n\t\t\t\t\t\targs.preventDefault();\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t});\r\n\t}\r\n\r\n\treturn obj;\r\n}\r\n\r\n\r\nreturn {\r\n\t/**\r\n\t * Initialize the adapter. This is run once as Highcharts is first run.\r\n\t */\r\n\tinit: function (pathAnim) {\r\n\r\n\t\t/**\r\n\t\t * Compatibility section to add support for legacy IE. This can be removed if old IE \r\n\t\t * support is not needed.\r\n\t\t */\r\n\t\tif (!doc.defaultView) {\r\n\t\t\tthis._getStyle = function (el, prop) {\r\n\t\t\t\tvar val;\r\n\t\t\t\tif (el.style[prop]) {\r\n\t\t\t\t\treturn el.style[prop];\r\n\t\t\t\t} else {\r\n\t\t\t\t\tif (prop === 'opacity') {\r\n\t\t\t\t\t\tprop = 'filter';\r\n\t\t\t\t\t}\r\n\t\t\t\t\t/*jslint unparam: true*/\r\n\t\t\t\t\tval = el.currentStyle[prop.replace(/\\-(\\w)/g, function (a, b) { return b.toUpperCase(); })];\r\n\t\t\t\t\tif (prop === 'filter') {\r\n\t\t\t\t\t\tval = val.replace(\r\n\t\t\t\t\t\t\t/alpha\\(opacity=([0-9]+)\\)/, \r\n\t\t\t\t\t\t\tfunction (a, b) { \r\n\t\t\t\t\t\t\t\treturn b / 100; \r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t);\r\n\t\t\t\t\t}\r\n\t\t\t\t\t/*jslint unparam: false*/\r\n\t\t\t\t\treturn val === '' ? 1 : val;\r\n\t\t\t\t} \r\n\t\t\t};\r\n\t\t\tthis.adapterRun = function (elem, method) {\r\n\t\t\t\tvar alias = { width: 'clientWidth', height: 'clientHeight' }[method];\r\n\r\n\t\t\t\tif (alias) {\r\n\t\t\t\t\telem.style.zoom = 1;\r\n\t\t\t\t\treturn elem[alias] - 2 * parseInt(HighchartsAdapter._getStyle(elem, 'padding'), 10);\r\n\t\t\t\t}\r\n\t\t\t};\r\n\t\t}\r\n\r\n\t\tif (!Array.prototype.forEach) {\r\n\t\t\tthis.each = function (arr, fn) { // legacy\r\n\t\t\t\tvar i = 0, \r\n\t\t\t\t\tlen = arr.length;\r\n\t\t\t\tfor (; i < len; i++) {\r\n\t\t\t\t\tif (fn.call(arr[i], arr[i], i, arr) === false) {\r\n\t\t\t\t\t\treturn i;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t};\r\n\t\t}\r\n\r\n\t\tif (!Array.prototype.indexOf) {\r\n\t\t\tthis.inArray = function (item, arr) {\r\n\t\t\t\tvar len, \r\n\t\t\t\t\ti = 0;\r\n\r\n\t\t\t\tif (arr) {\r\n\t\t\t\t\tlen = arr.length;\r\n\t\t\t\t\t\r\n\t\t\t\t\tfor (; i < len; i++) {\r\n\t\t\t\t\t\tif (arr[i] === item) {\r\n\t\t\t\t\t\t\treturn i;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\treturn -1;\r\n\t\t\t};\r\n\t\t}\r\n\r\n\t\tif (!Array.prototype.filter) {\r\n\t\t\tthis.grep = function (elements, callback) {\r\n\t\t\t\tvar ret = [],\r\n\t\t\t\t\ti = 0,\r\n\t\t\t\t\tlength = elements.length;\r\n\r\n\t\t\t\tfor (; i < length; i++) {\r\n\t\t\t\t\tif (!!callback(elements[i], i)) {\r\n\t\t\t\t\t\tret.push(elements[i]);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\treturn ret;\r\n\t\t\t};\r\n\t\t}\r\n\r\n\t\t//--- End compatibility section ---\r\n\r\n\r\n\t\t/**\r\n\t\t * Start of animation specific code\r\n\t\t */\r\n\t\tFx = function (elem, options, prop) {\r\n\t\t\tthis.options = options;\r\n\t\t\tthis.elem = elem;\r\n\t\t\tthis.prop = prop;\r\n\t\t};\r\n\t\tFx.prototype = {\r\n\t\t\t\r\n\t\t\tupdate: function () {\r\n\t\t\t\tvar styles,\r\n\t\t\t\t\tpaths = this.paths,\r\n\t\t\t\t\telem = this.elem,\r\n\t\t\t\t\telemelem = elem.element; // if destroyed, it is null\r\n\r\n\t\t\t\t// Animating a path definition on SVGElement\r\n\t\t\t\tif (paths && elemelem) {\r\n\t\t\t\t\telem.attr('d', pathAnim.step(paths[0], paths[1], this.now, this.toD));\r\n\t\t\t\t\r\n\t\t\t\t// Other animations on SVGElement\r\n\t\t\t\t} else if (elem.attr) {\r\n\t\t\t\t\tif (elemelem) {\r\n\t\t\t\t\t\telem.attr(this.prop, this.now);\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t// HTML styles\r\n\t\t\t\t} else {\r\n\t\t\t\t\tstyles = {};\r\n\t\t\t\t\tstyles[elem] = this.now + this.unit;\r\n\t\t\t\t\tHighcharts.css(elem, styles);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tif (this.options.step) {\r\n\t\t\t\t\tthis.options.step.call(this.elem, this.now, this);\r\n\t\t\t\t}\r\n\r\n\t\t\t},\r\n\t\t\tcustom: function (from, to, unit) {\r\n\t\t\t\tvar self = this,\r\n\t\t\t\t\tt = function (gotoEnd) {\r\n\t\t\t\t\t\treturn self.step(gotoEnd);\r\n\t\t\t\t\t},\r\n\t\t\t\t\ti;\r\n\r\n\t\t\t\tthis.startTime = +new Date();\r\n\t\t\t\tthis.start = from;\r\n\t\t\t\tthis.end = to;\r\n\t\t\t\tthis.unit = unit;\r\n\t\t\t\tthis.now = this.start;\r\n\t\t\t\tthis.pos = this.state = 0;\r\n\r\n\t\t\t\tt.elem = this.elem;\r\n\r\n\t\t\t\tif (t() && timers.push(t) === 1) {\r\n\t\t\t\t\ttimerId = setInterval(function () {\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tfor (i = 0; i < timers.length; i++) {\r\n\t\t\t\t\t\t\tif (!timers[i]()) {\r\n\t\t\t\t\t\t\t\ttimers.splice(i--, 1);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\tif (!timers.length) {\r\n\t\t\t\t\t\t\tclearInterval(timerId);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}, 13);\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\t\r\n\t\t\tstep: function (gotoEnd) {\r\n\t\t\t\tvar t = +new Date(),\r\n\t\t\t\t\tret,\r\n\t\t\t\t\tdone,\r\n\t\t\t\t\toptions = this.options,\r\n\t\t\t\t\ti;\r\n\r\n\t\t\t\tif (this.elem.stopAnimation) {\r\n\t\t\t\t\tret = false;\r\n\r\n\t\t\t\t} else if (gotoEnd || t >= options.duration + this.startTime) {\r\n\t\t\t\t\tthis.now = this.end;\r\n\t\t\t\t\tthis.pos = this.state = 1;\r\n\t\t\t\t\tthis.update();\r\n\r\n\t\t\t\t\tthis.options.curAnim[this.prop] = true;\r\n\r\n\t\t\t\t\tdone = true;\r\n\t\t\t\t\tfor (i in options.curAnim) {\r\n\t\t\t\t\t\tif (options.curAnim[i] !== true) {\r\n\t\t\t\t\t\t\tdone = false;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tif (done) {\r\n\t\t\t\t\t\tif (options.complete) {\r\n\t\t\t\t\t\t\toptions.complete.call(this.elem);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\tret = false;\r\n\r\n\t\t\t\t} else {\r\n\t\t\t\t\tvar n = t - this.startTime;\r\n\t\t\t\t\tthis.state = n / options.duration;\r\n\t\t\t\t\tthis.pos = options.easing(n, 0, 1, options.duration);\r\n\t\t\t\t\tthis.now = this.start + ((this.end - this.start) * this.pos);\r\n\t\t\t\t\tthis.update();\r\n\t\t\t\t\tret = true;\r\n\t\t\t\t}\r\n\t\t\t\treturn ret;\r\n\t\t\t}\r\n\t\t};\r\n\r\n\t\t/**\r\n\t\t * The adapter animate method\r\n\t\t */\r\n\t\tthis.animate = function (el, prop, opt) {\r\n\t\t\tvar start,\r\n\t\t\t\tunit = '',\r\n\t\t\t\tend,\r\n\t\t\t\tfx,\r\n\t\t\t\targs,\r\n\t\t\t\tname;\r\n\r\n\t\t\tel.stopAnimation = false; // ready for new\r\n\r\n\t\t\tif (typeof opt !== 'object' || opt === null) {\r\n\t\t\t\targs = arguments;\r\n\t\t\t\topt = {\r\n\t\t\t\t\tduration: args[2],\r\n\t\t\t\t\teasing: args[3],\r\n\t\t\t\t\tcomplete: args[4]\r\n\t\t\t\t};\r\n\t\t\t}\r\n\t\t\tif (typeof opt.duration !== 'number') {\r\n\t\t\t\topt.duration = 400;\r\n\t\t\t}\r\n\t\t\topt.easing = Math[opt.easing] || Math.easeInOutSine;\r\n\t\t\topt.curAnim = Highcharts.extend({}, prop);\r\n\t\t\t\r\n\t\t\tfor (name in prop) {\r\n\t\t\t\tfx = new Fx(el, opt, name);\r\n\t\t\t\tend = null;\r\n\t\t\t\t\r\n\t\t\t\tif (name === 'd') {\r\n\t\t\t\t\tfx.paths = pathAnim.init(\r\n\t\t\t\t\t\tel,\r\n\t\t\t\t\t\tel.d,\r\n\t\t\t\t\t\tprop.d\r\n\t\t\t\t\t);\r\n\t\t\t\t\tfx.toD = prop.d;\r\n\t\t\t\t\tstart = 0;\r\n\t\t\t\t\tend = 1;\r\n\t\t\t\t} else if (el.attr) {\r\n\t\t\t\t\tstart = el.attr(name);\r\n\t\t\t\t} else {\r\n\t\t\t\t\tstart = parseFloat(HighchartsAdapter._getStyle(el, name)) || 0;\r\n\t\t\t\t\tif (name !== 'opacity') {\r\n\t\t\t\t\t\tunit = 'px';\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\r\n\t\t\t\tif (!end) {\r\n\t\t\t\t\tend = parseFloat(prop[name]);\r\n\t\t\t\t}\r\n\t\t\t\tfx.custom(start, end, unit);\r\n\t\t\t}\t\r\n\t\t};\r\n\t},\r\n\r\n\t/**\r\n\t * Internal method to return CSS value for given element and property\r\n\t */\r\n\t_getStyle: function (el, prop) {\r\n\t\treturn window.getComputedStyle(el).getPropertyValue(prop);\r\n\t},\r\n\r\n\t/**\r\n\t * Downloads a script and executes a callback when done.\r\n\t * @param {String} scriptLocation\r\n\t * @param {Function} callback\r\n\t */\r\n\tgetScript: function (scriptLocation, callback) {\r\n\t\t// We cannot assume that Assets class from mootools-more is available so instead insert a script tag to download script.\r\n\t\tvar head = doc.getElementsByTagName('head')[0],\r\n\t\t\tscript = doc.createElement('script');\r\n\r\n\t\tscript.type = 'text/javascript';\r\n\t\tscript.src = scriptLocation;\r\n\t\tscript.onload = callback;\r\n\r\n\t\thead.appendChild(script);\r\n\t},\r\n\r\n\t/**\r\n\t * Return the index of an item in an array, or -1 if not found\r\n\t */\r\n\tinArray: function (item, arr) {\r\n\t\treturn arr.indexOf ? arr.indexOf(item) : emptyArray.indexOf.call(arr, item);\r\n\t},\r\n\r\n\r\n\t/**\r\n\t * A direct link to adapter methods\r\n\t */\r\n\tadapterRun: function (elem, method) {\r\n\t\treturn parseInt(HighchartsAdapter._getStyle(elem, method), 10);\r\n\t},\r\n\r\n\t/**\r\n\t * Filter an array\r\n\t */\r\n\tgrep: function (elements, callback) {\r\n\t\treturn emptyArray.filter.call(elements, callback);\r\n\t},\r\n\r\n\t/**\r\n\t * Map an array\r\n\t */\r\n\tmap: function (arr, fn) {\r\n\t\tvar results = [], i = 0, len = arr.length;\r\n\r\n\t\tfor (; i < len; i++) {\r\n\t\t\tresults[i] = fn.call(arr[i], arr[i], i, arr);\r\n\t\t}\r\n\r\n\t\treturn results;\r\n\t},\r\n\r\n\toffset: function (el) {\r\n\t\tvar left = 0,\r\n\t\t\ttop = 0;\r\n\r\n\t\twhile (el) {\r\n\t\t\tleft += el.offsetLeft;\r\n\t\t\ttop += el.offsetTop;\r\n\t\t\tel = el.offsetParent;\r\n\t\t}\r\n\r\n\t\treturn {\r\n\t\t\tleft: left,\r\n\t\t\ttop: top\r\n\t\t};\r\n\t},\r\n\r\n\t/**\r\n\t * Add an event listener\r\n\t */\r\n\taddEvent: function (el, type, fn) {\r\n\t\taugment(el).bind(type, fn);\r\n\t},\r\n\r\n\t/**\r\n\t * Remove event added with addEvent\r\n\t */\r\n\tremoveEvent: function (el, type, fn) {\r\n\t\taugment(el).unbind(type, fn);\r\n\t},\r\n\r\n\t/**\r\n\t * Fire an event on a custom object\r\n\t */\r\n\tfireEvent: function (el, type, eventArguments, defaultFunction) {\r\n\t\tvar e;\r\n\r\n\t\tif (doc.createEvent && (el.dispatchEvent || el.fireEvent)) {\r\n\t\t\te = doc.createEvent('Events');\r\n\t\t\te.initEvent(type, true, true);\r\n\t\t\te.target = el;\r\n\r\n\t\t\tHighcharts.extend(e, eventArguments);\r\n\r\n\t\t\tif (el.dispatchEvent) {\r\n\t\t\t\tel.dispatchEvent(e);\r\n\t\t\t} else {\r\n\t\t\t\tel.fireEvent(type, e);\r\n\t\t\t}\r\n\r\n\t\t} else if (el.HCExtended === true) {\r\n\t\t\teventArguments = eventArguments || {};\r\n\t\t\tel.trigger(type, eventArguments);\r\n\t\t}\r\n\r\n\t\tif (eventArguments && eventArguments.defaultPrevented) {\r\n\t\t\tdefaultFunction = null;\r\n\t\t}\r\n\r\n\t\tif (defaultFunction) {\r\n\t\t\tdefaultFunction(eventArguments);\r\n\t\t}\r\n\t},\r\n\r\n\twashMouseEvent: function (e) {\r\n\t\treturn e;\r\n\t},\r\n\r\n\r\n\t/**\r\n\t * Stop running animation\r\n\t */\r\n\tstop: function (el) {\r\n\t\tel.stopAnimation = true;\r\n\t},\r\n\r\n\t/**\r\n\t * Utility for iterating over an array. Parameters are reversed compared to jQuery.\r\n\t * @param {Array} arr\r\n\t * @param {Function} fn\r\n\t */\r\n\teach: function (arr, fn) { // modern browsers\r\n\t\treturn Array.prototype.forEach.call(arr, fn);\r\n\t}\r\n};\r\n}());\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/highcharts/highcharts-more.js",
    "content": "/*\r\n Highcharts JS v3.0.6 (2013-10-04)\r\n\r\n (c) 2009-2013 Torstein Hønsi\r\n\r\n License: www.highcharts.com/license\r\n*/\r\n(function(j,C){function J(a,b,c){this.init.call(this,a,b,c)}function K(a,b,c){a.call(this,b,c);if(this.chart.polar)this.closeSegment=function(a){var c=this.xAxis.center;a.push(\"L\",c[0],c[1])},this.closedStacks=!0}function L(a,b){var c=this.chart,d=this.options.animation,g=this.group,f=this.markerGroup,e=this.xAxis.center,i=c.plotLeft,n=c.plotTop;if(c.polar){if(c.renderer.isSVG)if(d===!0&&(d={}),b){if(c={translateX:e[0]+i,translateY:e[1]+n,scaleX:0.001,scaleY:0.001},g.attr(c),f)f.attrSetters=g.attrSetters,\r\nf.attr(c)}else c={translateX:i,translateY:n,scaleX:1,scaleY:1},g.animate(c,d),f&&f.animate(c,d),this.animate=null}else a.call(this,b)}var P=j.arrayMin,Q=j.arrayMax,s=j.each,F=j.extend,p=j.merge,R=j.map,r=j.pick,v=j.pInt,m=j.getOptions().plotOptions,h=j.seriesTypes,x=j.extendClass,M=j.splat,o=j.wrap,N=j.Axis,u=j.Tick,z=j.Series,q=h.column.prototype,t=Math,D=t.round,A=t.floor,S=t.max,w=function(){};F(J.prototype,{init:function(a,b,c){var d=this,g=d.defaultOptions;d.chart=b;if(b.angular)g.background=\r\n{};d.options=a=p(g,a);(a=a.background)&&s([].concat(M(a)).reverse(),function(a){var b=a.backgroundColor,a=p(d.defaultBackgroundOptions,a);if(b)a.backgroundColor=b;a.color=a.backgroundColor;c.options.plotBands.unshift(a)})},defaultOptions:{center:[\"50%\",\"50%\"],size:\"85%\",startAngle:0},defaultBackgroundOptions:{shape:\"circle\",borderWidth:1,borderColor:\"silver\",backgroundColor:{linearGradient:{x1:0,y1:0,x2:0,y2:1},stops:[[0,\"#FFF\"],[1,\"#DDD\"]]},from:Number.MIN_VALUE,innerRadius:0,to:Number.MAX_VALUE,\r\nouterRadius:\"105%\"}});var G=N.prototype,u=u.prototype,T={getOffset:w,redraw:function(){this.isDirty=!1},render:function(){this.isDirty=!1},setScale:w,setCategories:w,setTitle:w},O={isRadial:!0,defaultRadialGaugeOptions:{labels:{align:\"center\",x:0,y:null},minorGridLineWidth:0,minorTickInterval:\"auto\",minorTickLength:10,minorTickPosition:\"inside\",minorTickWidth:1,plotBands:[],tickLength:10,tickPosition:\"inside\",tickWidth:2,title:{rotation:0},zIndex:2},defaultRadialXOptions:{gridLineWidth:1,labels:{align:null,\r\ndistance:15,x:0,y:null},maxPadding:0,minPadding:0,plotBands:[],showLastLabel:!1,tickLength:0},defaultRadialYOptions:{gridLineInterpolation:\"circle\",labels:{align:\"right\",x:-3,y:-2},plotBands:[],showLastLabel:!1,title:{x:4,text:null,rotation:90}},setOptions:function(a){this.options=p(this.defaultOptions,this.defaultRadialOptions,a)},getOffset:function(){G.getOffset.call(this);this.chart.axisOffset[this.side]=0},getLinePath:function(a,b){var c=this.center,b=r(b,c[2]/2-this.offset);return this.chart.renderer.symbols.arc(this.left+\r\nc[0],this.top+c[1],b,b,{start:this.startAngleRad,end:this.endAngleRad,open:!0,innerR:0})},setAxisTranslation:function(){G.setAxisTranslation.call(this);if(this.center&&(this.transA=this.isCircular?(this.endAngleRad-this.startAngleRad)/(this.max-this.min||1):this.center[2]/2/(this.max-this.min||1),this.isXAxis))this.minPixelPadding=this.transA*this.minPointOffset+(this.reversed?(this.endAngleRad-this.startAngleRad)/4:0)},beforeSetTickPositions:function(){this.autoConnect&&(this.max+=this.categories&&\r\n1||this.pointRange||this.closestPointRange||0)},setAxisSize:function(){G.setAxisSize.call(this);if(this.isRadial)this.center=this.pane.center=h.pie.prototype.getCenter.call(this.pane),this.len=this.width=this.height=this.isCircular?this.center[2]*(this.endAngleRad-this.startAngleRad)/2:this.center[2]/2},getPosition:function(a,b){if(!this.isCircular)b=this.translate(a),a=this.min;return this.postTranslate(this.translate(a),r(b,this.center[2]/2)-this.offset)},postTranslate:function(a,b){var c=this.chart,\r\nd=this.center,a=this.startAngleRad+a;return{x:c.plotLeft+d[0]+Math.cos(a)*b,y:c.plotTop+d[1]+Math.sin(a)*b}},getPlotBandPath:function(a,b,c){var d=this.center,g=this.startAngleRad,f=d[2]/2,e=[r(c.outerRadius,\"100%\"),c.innerRadius,r(c.thickness,10)],i=/%$/,n,l=this.isCircular;this.options.gridLineInterpolation===\"polygon\"?d=this.getPlotLinePath(a).concat(this.getPlotLinePath(b,!0)):(l||(e[0]=this.translate(a),e[1]=this.translate(b)),e=R(e,function(a){i.test(a)&&(a=v(a,10)*f/100);return a}),c.shape===\r\n\"circle\"||!l?(a=-Math.PI/2,b=Math.PI*1.5,n=!0):(a=g+this.translate(a),b=g+this.translate(b)),d=this.chart.renderer.symbols.arc(this.left+d[0],this.top+d[1],e[0],e[0],{start:a,end:b,innerR:r(e[1],e[0]-e[2]),open:n}));return d},getPlotLinePath:function(a,b){var c=this.center,d=this.chart,g=this.getPosition(a),f,e,i;this.isCircular?i=[\"M\",c[0]+d.plotLeft,c[1]+d.plotTop,\"L\",g.x,g.y]:this.options.gridLineInterpolation===\"circle\"?(a=this.translate(a))&&(i=this.getLinePath(0,a)):(f=d.xAxis[0],i=[],a=this.translate(a),\r\nc=f.tickPositions,f.autoConnect&&(c=c.concat([c[0]])),b&&(c=[].concat(c).reverse()),s(c,function(c,b){e=f.getPosition(c,a);i.push(b?\"L\":\"M\",e.x,e.y)}));return i},getTitlePosition:function(){var a=this.center,b=this.chart,c=this.options.title;return{x:b.plotLeft+a[0]+(c.x||0),y:b.plotTop+a[1]-{high:0.5,middle:0.25,low:0}[c.align]*a[2]+(c.y||0)}}};o(G,\"init\",function(a,b,c){var k;var d=b.angular,g=b.polar,f=c.isX,e=d&&f,i,n;n=b.options;var l=c.pane||0;if(d){if(F(this,e?T:O),i=!f)this.defaultRadialOptions=\r\nthis.defaultRadialGaugeOptions}else if(g)F(this,O),this.defaultRadialOptions=(i=f)?this.defaultRadialXOptions:p(this.defaultYAxisOptions,this.defaultRadialYOptions);a.call(this,b,c);if(!e&&(d||g)){a=this.options;if(!b.panes)b.panes=[];this.pane=(k=b.panes[l]=b.panes[l]||new J(M(n.pane)[l],b,this),l=k);l=l.options;b.inverted=!1;n.chart.zoomType=null;this.startAngleRad=b=(l.startAngle-90)*Math.PI/180;this.endAngleRad=n=(r(l.endAngle,l.startAngle+360)-90)*Math.PI/180;this.offset=a.offset||0;if((this.isCircular=\r\ni)&&c.max===C&&n-b===2*Math.PI)this.autoConnect=!0}});o(u,\"getPosition\",function(a,b,c,d,g){var f=this.axis;return f.getPosition?f.getPosition(c):a.call(this,b,c,d,g)});o(u,\"getLabelPosition\",function(a,b,c,d,g,f,e,i,n){var l=this.axis,k=f.y,h=f.align,j=(l.translate(this.pos)+l.startAngleRad+Math.PI/2)/Math.PI*180%360;l.isRadial?(a=l.getPosition(this.pos,l.center[2]/2+r(f.distance,-25)),f.rotation===\"auto\"?d.attr({rotation:j}):k===null&&(k=v(d.styles.lineHeight)*0.9-d.getBBox().height/2),h===null&&\r\n(h=l.isCircular?j>20&&j<160?\"left\":j>200&&j<340?\"right\":\"center\":\"center\",d.attr({align:h})),a.x+=f.x,a.y+=k):a=a.call(this,b,c,d,g,f,e,i,n);return a});o(u,\"getMarkPath\",function(a,b,c,d,g,f,e){var i=this.axis;i.isRadial?(a=i.getPosition(this.pos,i.center[2]/2+d),b=[\"M\",b,c,\"L\",a.x,a.y]):b=a.call(this,b,c,d,g,f,e);return b});m.arearange=p(m.area,{lineWidth:1,marker:null,threshold:null,tooltip:{pointFormat:'<span style=\"color:{series.color}\">{series.name}</span>: <b>{point.low}</b> - <b>{point.high}</b><br/>'},\r\ntrackByArea:!0,dataLabels:{verticalAlign:null,xLow:0,xHigh:0,yLow:0,yHigh:0}});h.arearange=j.extendClass(h.area,{type:\"arearange\",pointArrayMap:[\"low\",\"high\"],toYData:function(a){return[a.low,a.high]},pointValKey:\"low\",getSegments:function(){var a=this;s(a.points,function(b){if(!a.options.connectNulls&&(b.low===null||b.high===null))b.y=null;else if(b.low===null&&b.high!==null)b.y=b.high});z.prototype.getSegments.call(this)},translate:function(){var a=this.yAxis;h.area.prototype.translate.apply(this);\r\ns(this.points,function(b){var c=b.low,d=b.high,g=b.plotY;d===null&&c===null?b.y=null:c===null?(b.plotLow=b.plotY=null,b.plotHigh=a.translate(d,0,1,0,1)):d===null?(b.plotLow=g,b.plotHigh=null):(b.plotLow=g,b.plotHigh=a.translate(d,0,1,0,1))})},getSegmentPath:function(a){var b,c=[],d=a.length,g=z.prototype.getSegmentPath,f,e;e=this.options;var i=e.step;for(b=HighchartsAdapter.grep(a,function(a){return a.plotLow!==null});d--;)f=a[d],f.plotHigh!==null&&c.push({plotX:f.plotX,plotY:f.plotHigh});a=g.call(this,\r\nb);if(i)i===!0&&(i=\"left\"),e.step={left:\"right\",center:\"center\",right:\"left\"}[i];c=g.call(this,c);e.step=i;e=[].concat(a,c);c[0]=\"L\";this.areaPath=this.areaPath.concat(a,c);return e},drawDataLabels:function(){var a=this.data,b=a.length,c,d=[],g=z.prototype,f=this.options.dataLabels,e,i=this.chart.inverted;if(f.enabled||this._hasPointLabels){for(c=b;c--;)e=a[c],e.y=e.high,e.plotY=e.plotHigh,d[c]=e.dataLabel,e.dataLabel=e.dataLabelUpper,e.below=!1,i?(f.align=\"left\",f.x=f.xHigh):f.y=f.yHigh;g.drawDataLabels.apply(this,\r\narguments);for(c=b;c--;)e=a[c],e.dataLabelUpper=e.dataLabel,e.dataLabel=d[c],e.y=e.low,e.plotY=e.plotLow,e.below=!0,i?(f.align=\"right\",f.x=f.xLow):f.y=f.yLow;g.drawDataLabels.apply(this,arguments)}},alignDataLabel:h.column.prototype.alignDataLabel,getSymbol:h.column.prototype.getSymbol,drawPoints:w});m.areasplinerange=p(m.arearange);h.areasplinerange=x(h.arearange,{type:\"areasplinerange\",getPointSpline:h.spline.prototype.getPointSpline});m.columnrange=p(m.column,m.arearange,{lineWidth:1,pointRange:null});\r\nh.columnrange=x(h.arearange,{type:\"columnrange\",translate:function(){var a=this,b=a.yAxis,c;q.translate.apply(a);s(a.points,function(d){var g=d.shapeArgs,f=a.options.minPointLength,e;d.plotHigh=c=b.translate(d.high,0,1,0,1);d.plotLow=d.plotY;e=c;d=d.plotY-c;d<f&&(f-=d,d+=f,e-=f/2);g.height=d;g.y=e})},trackerGroups:[\"group\",\"dataLabels\"],drawGraph:w,pointAttrToOptions:q.pointAttrToOptions,drawPoints:q.drawPoints,drawTracker:q.drawTracker,animate:q.animate,getColumnMetrics:q.getColumnMetrics});m.gauge=\r\np(m.line,{dataLabels:{enabled:!0,y:15,borderWidth:1,borderColor:\"silver\",borderRadius:3,style:{fontWeight:\"bold\"},verticalAlign:\"top\",zIndex:2},dial:{},pivot:{},tooltip:{headerFormat:\"\"},showInLegend:!1});u={type:\"gauge\",pointClass:j.extendClass(j.Point,{setState:function(a){this.state=a}}),angular:!0,drawGraph:w,fixedBox:!0,trackerGroups:[\"group\",\"dataLabels\"],translate:function(){var a=this.yAxis,b=this.options,c=a.center;this.generatePoints();s(this.points,function(d){var g=p(b.dial,d.dial),f=\r\nv(r(g.radius,80))*c[2]/200,e=v(r(g.baseLength,70))*f/100,i=v(r(g.rearLength,10))*f/100,n=g.baseWidth||3,l=g.topWidth||1,k=a.startAngleRad+a.translate(d.y,null,null,null,!0);b.wrap===!1&&(k=Math.max(a.startAngleRad,Math.min(a.endAngleRad,k)));k=k*180/Math.PI;d.shapeType=\"path\";d.shapeArgs={d:g.path||[\"M\",-i,-n/2,\"L\",e,-n/2,f,-l/2,f,l/2,e,n/2,-i,n/2,\"z\"],translateX:c[0],translateY:c[1],rotation:k};d.plotX=c[0];d.plotY=c[1]})},drawPoints:function(){var a=this,b=a.yAxis.center,c=a.pivot,d=a.options,g=\r\nd.pivot,f=a.chart.renderer;s(a.points,function(c){var b=c.graphic,g=c.shapeArgs,l=g.d,k=p(d.dial,c.dial);b?(b.animate(g),g.d=l):c.graphic=f[c.shapeType](g).attr({stroke:k.borderColor||\"none\",\"stroke-width\":k.borderWidth||0,fill:k.backgroundColor||\"black\",rotation:g.rotation}).add(a.group)});c?c.animate({translateX:b[0],translateY:b[1]}):a.pivot=f.circle(0,0,r(g.radius,5)).attr({\"stroke-width\":g.borderWidth||0,stroke:g.borderColor||\"silver\",fill:g.backgroundColor||\"black\"}).translate(b[0],b[1]).add(a.group)},\r\nanimate:function(a){var b=this;if(!a)s(b.points,function(a){var d=a.graphic;d&&(d.attr({rotation:b.yAxis.startAngleRad*180/Math.PI}),d.animate({rotation:a.shapeArgs.rotation},b.options.animation))}),b.animate=null},render:function(){this.group=this.plotGroup(\"group\",\"series\",this.visible?\"visible\":\"hidden\",this.options.zIndex,this.chart.seriesGroup);h.pie.prototype.render.call(this);this.group.clip(this.chart.clipRect)},setData:h.pie.prototype.setData,drawTracker:h.column.prototype.drawTracker};h.gauge=\r\nj.extendClass(h.line,u);m.boxplot=p(m.column,{fillColor:\"#FFFFFF\",lineWidth:1,medianWidth:2,states:{hover:{brightness:-0.3}},threshold:null,tooltip:{pointFormat:'<span style=\"color:{series.color};font-weight:bold\">{series.name}</span><br/>Maximum: {point.high}<br/>Upper quartile: {point.q3}<br/>Median: {point.median}<br/>Lower quartile: {point.q1}<br/>Minimum: {point.low}<br/>'},whiskerLength:\"50%\",whiskerWidth:2});h.boxplot=x(h.column,{type:\"boxplot\",pointArrayMap:[\"low\",\"q1\",\"median\",\"q3\",\"high\"],\r\ntoYData:function(a){return[a.low,a.q1,a.median,a.q3,a.high]},pointValKey:\"high\",pointAttrToOptions:{fill:\"fillColor\",stroke:\"color\",\"stroke-width\":\"lineWidth\"},drawDataLabels:w,translate:function(){var a=this.yAxis,b=this.pointArrayMap;h.column.prototype.translate.apply(this);s(this.points,function(c){s(b,function(b){c[b]!==null&&(c[b+\"Plot\"]=a.translate(c[b],0,1,0,1))})})},drawPoints:function(){var a=this,b=a.points,c=a.options,d=a.chart.renderer,g,f,e,i,n,l,k,h,j,m,o,H,p,E,I,q,w,t,v,u,z,y,x=a.doQuartiles!==\r\n!1,B=parseInt(a.options.whiskerLength,10)/100;s(b,function(b){j=b.graphic;z=b.shapeArgs;o={};E={};q={};y=b.color||a.color;if(b.plotY!==C)if(g=b.pointAttr[b.selected?\"selected\":\"\"],w=z.width,t=A(z.x),v=t+w,u=D(w/2),f=A(x?b.q1Plot:b.lowPlot),e=A(x?b.q3Plot:b.lowPlot),i=A(b.highPlot),n=A(b.lowPlot),o.stroke=b.stemColor||c.stemColor||y,o[\"stroke-width\"]=r(b.stemWidth,c.stemWidth,c.lineWidth),o.dashstyle=b.stemDashStyle||c.stemDashStyle,E.stroke=b.whiskerColor||c.whiskerColor||y,E[\"stroke-width\"]=r(b.whiskerWidth,\r\nc.whiskerWidth,c.lineWidth),q.stroke=b.medianColor||c.medianColor||y,q[\"stroke-width\"]=r(b.medianWidth,c.medianWidth,c.lineWidth),k=o[\"stroke-width\"]%2/2,h=t+u+k,m=[\"M\",h,e,\"L\",h,i,\"M\",h,f,\"L\",h,n,\"z\"],x&&(k=g[\"stroke-width\"]%2/2,h=A(h)+k,f=A(f)+k,e=A(e)+k,t+=k,v+=k,H=[\"M\",t,e,\"L\",t,f,\"L\",v,f,\"L\",v,e,\"L\",t,e,\"z\"]),B&&(k=E[\"stroke-width\"]%2/2,i+=k,n+=k,p=[\"M\",h-u*B,i,\"L\",h+u*B,i,\"M\",h-u*B,n,\"L\",h+u*B,n]),k=q[\"stroke-width\"]%2/2,l=D(b.medianPlot)+k,I=[\"M\",t,l,\"L\",v,l,\"z\"],j)b.stem.animate({d:m}),B&&\r\nb.whiskers.animate({d:p}),x&&b.box.animate({d:H}),b.medianShape.animate({d:I});else{b.graphic=j=d.g().add(a.group);b.stem=d.path(m).attr(o).add(j);if(B)b.whiskers=d.path(p).attr(E).add(j);if(x)b.box=d.path(H).attr(g).add(j);b.medianShape=d.path(I).attr(q).add(j)}})}});m.errorbar=p(m.boxplot,{color:\"#000000\",grouping:!1,linkedTo:\":previous\",tooltip:{pointFormat:m.arearange.tooltip.pointFormat},whiskerWidth:null});h.errorbar=x(h.boxplot,{type:\"errorbar\",pointArrayMap:[\"low\",\"high\"],toYData:function(a){return[a.low,\r\na.high]},pointValKey:\"high\",doQuartiles:!1,getColumnMetrics:function(){return this.linkedParent&&this.linkedParent.columnMetrics||h.column.prototype.getColumnMetrics.call(this)}});m.waterfall=p(m.column,{lineWidth:1,lineColor:\"#333\",dashStyle:\"dot\",borderColor:\"#333\"});h.waterfall=x(h.column,{type:\"waterfall\",upColorProp:\"fill\",pointArrayMap:[\"low\",\"y\"],pointValKey:\"y\",init:function(a,b){b.stacking=!0;h.column.prototype.init.call(this,a,b)},translate:function(){var a=this.options,b=this.yAxis,c,d,\r\ng,f,e,i,n,l,k;c=a.threshold;a=a.borderWidth%2/2;h.column.prototype.translate.apply(this);l=c;g=this.points;for(d=0,c=g.length;d<c;d++){f=g[d];e=f.shapeArgs;i=this.getStack(d);k=i.points[this.index];if(isNaN(f.y))f.y=this.yData[d];n=S(l,l+f.y)+k[0];e.y=b.translate(n,0,1);f.isSum||f.isIntermediateSum?(e.y=b.translate(k[1],0,1),e.height=b.translate(k[0],0,1)-e.y):l+=i.total;e.height<0&&(e.y+=e.height,e.height*=-1);f.plotY=e.y=D(e.y)-a;e.height=D(e.height);f.yBottom=e.y+e.height}},processData:function(a){var b=\r\nthis.yData,c=this.points,d,g=b.length,f=this.options.threshold||0,e,i,h,l,k,j;i=e=h=l=f;for(j=0;j<g;j++)k=b[j],d=c&&c[j]?c[j]:{},k===\"sum\"||d.isSum?b[j]=i:k===\"intermediateSum\"||d.isIntermediateSum?(b[j]=e,e=f):(i+=k,e+=k),h=Math.min(i,h),l=Math.max(i,l);z.prototype.processData.call(this,a);this.dataMin=h;this.dataMax=l},toYData:function(a){if(a.isSum)return\"sum\";else if(a.isIntermediateSum)return\"intermediateSum\";return a.y},getAttribs:function(){h.column.prototype.getAttribs.apply(this,arguments);\r\nvar a=this.options,b=a.states,c=a.upColor||this.color,a=j.Color(c).brighten(0.1).get(),d=p(this.pointAttr),g=this.upColorProp;d[\"\"][g]=c;d.hover[g]=b.hover.upColor||a;d.select[g]=b.select.upColor||c;s(this.points,function(a){if(a.y>0&&!a.color)a.pointAttr=d,a.color=c})},getGraphPath:function(){var a=this.data,b=a.length,c=D(this.options.lineWidth+this.options.borderWidth)%2/2,d=[],g,f,e;for(e=1;e<b;e++)f=a[e].shapeArgs,g=a[e-1].shapeArgs,f=[\"M\",g.x+g.width,g.y+c,\"L\",f.x,g.y+c],a[e-1].y<0&&(f[2]+=\r\ng.height,f[5]+=g.height),d=d.concat(f);return d},getExtremes:w,getStack:function(a){var b=this.yAxis.stacks,c=this.stackKey;this.processedYData[a]<this.options.threshold&&(c=\"-\"+c);return b[c][a]},drawGraph:z.prototype.drawGraph});m.bubble=p(m.scatter,{dataLabels:{inside:!0,style:{color:\"white\",textShadow:\"0px 0px 3px black\"},verticalAlign:\"middle\"},marker:{lineColor:null,lineWidth:1},minSize:8,maxSize:\"20%\",tooltip:{pointFormat:\"({point.x}, {point.y}), Size: {point.z}\"},turboThreshold:0,zThreshold:0});\r\nh.bubble=x(h.scatter,{type:\"bubble\",pointArrayMap:[\"y\",\"z\"],trackerGroups:[\"group\",\"dataLabelsGroup\"],pointAttrToOptions:{stroke:\"lineColor\",\"stroke-width\":\"lineWidth\",fill:\"fillColor\"},applyOpacity:function(a){var b=this.options.marker,c=r(b.fillOpacity,0.5),a=a||b.fillColor||this.color;c!==1&&(a=j.Color(a).setOpacity(c).get(\"rgba\"));return a},convertAttribs:function(){var a=z.prototype.convertAttribs.apply(this,arguments);a.fill=this.applyOpacity(a.fill);return a},getRadii:function(a,b,c,d){var g,\r\nf,e,i=this.zData,h=[];for(f=0,g=i.length;f<g;f++)e=b-a,e=e>0?(i[f]-a)/(b-a):0.5,h.push(t.ceil(c+e*(d-c))/2);this.radii=h},animate:function(a){var b=this.options.animation;if(!a)s(this.points,function(a){var d=a.graphic,a=a.shapeArgs;d&&a&&(d.attr(\"r\",1),d.animate({r:a.r},b))}),this.animate=null},translate:function(){var a,b=this.data,c,d,g=this.radii;h.scatter.prototype.translate.call(this);for(a=b.length;a--;)c=b[a],d=g?g[a]:0,c.negative=c.z<(this.options.zThreshold||0),d>=this.minPxSize/2?(c.shapeType=\r\n\"circle\",c.shapeArgs={x:c.plotX,y:c.plotY,r:d},c.dlBox={x:c.plotX-d,y:c.plotY-d,width:2*d,height:2*d}):c.shapeArgs=c.plotY=c.dlBox=C},drawLegendSymbol:function(a,b){var c=v(a.itemStyle.fontSize)/2;b.legendSymbol=this.chart.renderer.circle(c,a.baseline-c,c).attr({zIndex:3}).add(b.legendGroup);b.legendSymbol.isMarker=!0},drawPoints:h.column.prototype.drawPoints,alignDataLabel:h.column.prototype.alignDataLabel});N.prototype.beforePadding=function(){var a=this,b=this.len,c=this.chart,d=0,g=b,f=this.isXAxis,\r\ne=f?\"xData\":\"yData\",i=this.min,h={},j=t.min(c.plotWidth,c.plotHeight),k=Number.MAX_VALUE,m=-Number.MAX_VALUE,o=this.max-i,p=b/o,q=[];this.tickPositions&&(s(this.series,function(b){var c=b.options;if(b.type===\"bubble\"&&b.visible&&(a.allowZoomOutside=!0,q.push(b),f))s([\"minSize\",\"maxSize\"],function(a){var b=c[a],d=/%$/.test(b),b=v(b);h[a]=d?j*b/100:b}),b.minPxSize=h.minSize,b=b.zData,b.length&&(k=t.min(k,t.max(P(b),c.displayNegative===!1?c.zThreshold:-Number.MAX_VALUE)),m=t.max(m,Q(b)))}),s(q,function(a){var b=\r\na[e],c=b.length,j;f&&a.getRadii(k,m,h.minSize,h.maxSize);if(o>0)for(;c--;)j=a.radii[c],d=Math.min((b[c]-i)*p-j,d),g=Math.max((b[c]-i)*p+j,g)}),q.length&&o>0&&r(this.options.min,this.userMin)===C&&r(this.options.max,this.userMax)===C&&(g-=b,p*=(b+d-g)/b,this.min+=d/p,this.max+=g/p))};var y=z.prototype,m=j.Pointer.prototype;y.toXY=function(a){var b,c=this.chart;b=a.plotX;var d=a.plotY;a.rectPlotX=b;a.rectPlotY=d;a.clientX=(b/Math.PI*180+this.xAxis.pane.options.startAngle)%360;b=this.xAxis.postTranslate(a.plotX,\r\nthis.yAxis.len-d);a.plotX=a.polarPlotX=b.x-c.plotLeft;a.plotY=a.polarPlotY=b.y-c.plotTop};y.orderTooltipPoints=function(a){if(this.chart.polar&&(a.sort(function(a,c){return a.clientX-c.clientX}),a[0]))a[0].wrappedClientX=a[0].clientX+360,a.push(a[0])};o(h.area.prototype,\"init\",K);o(h.areaspline.prototype,\"init\",K);o(h.spline.prototype,\"getPointSpline\",function(a,b,c,d){var g,f,e,i,h,j,k;if(this.chart.polar){g=c.plotX;f=c.plotY;a=b[d-1];e=b[d+1];this.connectEnds&&(a||(a=b[b.length-2]),e||(e=b[1]));\r\nif(a&&e)i=a.plotX,h=a.plotY,b=e.plotX,j=e.plotY,i=(1.5*g+i)/2.5,h=(1.5*f+h)/2.5,e=(1.5*g+b)/2.5,k=(1.5*f+j)/2.5,b=Math.sqrt(Math.pow(i-g,2)+Math.pow(h-f,2)),j=Math.sqrt(Math.pow(e-g,2)+Math.pow(k-f,2)),i=Math.atan2(h-f,i-g),h=Math.atan2(k-f,e-g),k=Math.PI/2+(i+h)/2,Math.abs(i-k)>Math.PI/2&&(k-=Math.PI),i=g+Math.cos(k)*b,h=f+Math.sin(k)*b,e=g+Math.cos(Math.PI+k)*j,k=f+Math.sin(Math.PI+k)*j,c.rightContX=e,c.rightContY=k;d?(c=[\"C\",a.rightContX||a.plotX,a.rightContY||a.plotY,i||g,h||f,g,f],a.rightContX=\r\na.rightContY=null):c=[\"M\",g,f]}else c=a.call(this,b,c,d);return c});o(y,\"translate\",function(a){a.call(this);if(this.chart.polar&&!this.preventPostTranslate)for(var a=this.points,b=a.length;b--;)this.toXY(a[b])});o(y,\"getSegmentPath\",function(a,b){var c=this.points;if(this.chart.polar&&this.options.connectEnds!==!1&&b[b.length-1]===c[c.length-1]&&c[0].y!==null)this.connectEnds=!0,b=[].concat(b,[c[0]]);return a.call(this,b)});o(y,\"animate\",L);o(q,\"animate\",L);o(y,\"setTooltipPoints\",function(a,b){this.chart.polar&&\r\nF(this.xAxis,{tooltipLen:360});return a.call(this,b)});o(q,\"translate\",function(a){var b=this.xAxis,c=this.yAxis.len,d=b.center,g=b.startAngleRad,f=this.chart.renderer,e,h;this.preventPostTranslate=!0;a.call(this);if(b.isRadial){b=this.points;for(h=b.length;h--;)e=b[h],a=e.barX+g,e.shapeType=\"path\",e.shapeArgs={d:f.symbols.arc(d[0],d[1],c-e.plotY,null,{start:a,end:a+e.pointWidth,innerR:c-r(e.yBottom,c)})},this.toXY(e)}});o(q,\"alignDataLabel\",function(a,b,c,d,g,f){if(this.chart.polar){a=b.rectPlotX/\r\nMath.PI*180;if(d.align===null)d.align=a>20&&a<160?\"left\":a>200&&a<340?\"right\":\"center\";if(d.verticalAlign===null)d.verticalAlign=a<45||a>315?\"bottom\":a>135&&a<225?\"top\":\"middle\";y.alignDataLabel.call(this,b,c,d,g,f)}else a.call(this,b,c,d,g,f)});o(m,\"getIndex\",function(a,b){var c,d=this.chart,g;d.polar?(g=d.xAxis[0].center,c=b.chartX-g[0]-d.plotLeft,d=b.chartY-g[1]-d.plotTop,c=180-Math.round(Math.atan2(c,d)/Math.PI*180)):c=a.call(this,b);return c});o(m,\"getCoordinates\",function(a,b){var c=this.chart,\r\nd={xAxis:[],yAxis:[]};c.polar?s(c.axes,function(a){var f=a.isXAxis,e=a.center,h=b.chartX-e[0]-c.plotLeft,e=b.chartY-e[1]-c.plotTop;d[f?\"xAxis\":\"yAxis\"].push({axis:a,value:a.translate(f?Math.PI-Math.atan2(h,e):Math.sqrt(Math.pow(h,2)+Math.pow(e,2)),!0)})}):d=a.call(this,b);return d})})(Highcharts);\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/highcharts/highcharts-more.src.js",
    "content": "// ==ClosureCompiler==\r\n// @compilation_level SIMPLE_OPTIMIZATIONS\r\n\r\n/**\r\n * @license Highcharts JS v3.0.6 (2013-10-04)\r\n *\r\n * (c) 2009-2013 Torstein Hønsi\r\n *\r\n * License: www.highcharts.com/license\r\n */\r\n\r\n// JSLint options:\r\n/*global Highcharts, HighchartsAdapter, document, window, navigator, setInterval, clearInterval, clearTimeout, setTimeout, location, jQuery, $, console */\r\n\r\n(function (Highcharts, UNDEFINED) {\r\nvar arrayMin = Highcharts.arrayMin,\r\n\tarrayMax = Highcharts.arrayMax,\r\n\teach = Highcharts.each,\r\n\textend = Highcharts.extend,\r\n\tmerge = Highcharts.merge,\r\n\tmap = Highcharts.map,\r\n\tpick = Highcharts.pick,\r\n\tpInt = Highcharts.pInt,\r\n\tdefaultPlotOptions = Highcharts.getOptions().plotOptions,\r\n\tseriesTypes = Highcharts.seriesTypes,\r\n\textendClass = Highcharts.extendClass,\r\n\tsplat = Highcharts.splat,\r\n\twrap = Highcharts.wrap,\r\n\tAxis = Highcharts.Axis,\r\n\tTick = Highcharts.Tick,\r\n\tSeries = Highcharts.Series,\r\n\tcolProto = seriesTypes.column.prototype,\r\n\tmath = Math,\r\n\tmathRound = math.round,\r\n\tmathFloor = math.floor,\r\n\tmathMax = math.max,\r\n\tnoop = function () {};/**\r\n * The Pane object allows options that are common to a set of X and Y axes.\r\n * \r\n * In the future, this can be extended to basic Highcharts and Highstock.\r\n */\r\nfunction Pane(options, chart, firstAxis) {\r\n\tthis.init.call(this, options, chart, firstAxis);\r\n}\r\n\r\n// Extend the Pane prototype\r\nextend(Pane.prototype, {\r\n\t\r\n\t/**\r\n\t * Initiate the Pane object\r\n\t */\r\n\tinit: function (options, chart, firstAxis) {\r\n\t\tvar pane = this,\r\n\t\t\tbackgroundOption,\r\n\t\t\tdefaultOptions = pane.defaultOptions;\r\n\t\t\r\n\t\tpane.chart = chart;\r\n\t\t\r\n\t\t// Set options\r\n\t\tif (chart.angular) { // gauges\r\n\t\t\tdefaultOptions.background = {}; // gets extended by this.defaultBackgroundOptions\r\n\t\t}\r\n\t\tpane.options = options = merge(defaultOptions, options);\r\n\t\t\r\n\t\tbackgroundOption = options.background;\r\n\t\t\r\n\t\t// To avoid having weighty logic to place, update and remove the backgrounds,\r\n\t\t// push them to the first axis' plot bands and borrow the existing logic there.\r\n\t\tif (backgroundOption) {\r\n\t\t\teach([].concat(splat(backgroundOption)).reverse(), function (config) {\r\n\t\t\t\tvar backgroundColor = config.backgroundColor; // if defined, replace the old one (specific for gradients)\r\n\t\t\t\tconfig = merge(pane.defaultBackgroundOptions, config);\r\n\t\t\t\tif (backgroundColor) {\r\n\t\t\t\t\tconfig.backgroundColor = backgroundColor;\r\n\t\t\t\t}\r\n\t\t\t\tconfig.color = config.backgroundColor; // due to naming in plotBands\r\n\t\t\t\tfirstAxis.options.plotBands.unshift(config);\r\n\t\t\t});\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * The default options object\r\n\t */\r\n\tdefaultOptions: {\r\n\t\t// background: {conditional},\r\n\t\tcenter: ['50%', '50%'],\r\n\t\tsize: '85%',\r\n\t\tstartAngle: 0\r\n\t\t//endAngle: startAngle + 360\r\n\t},\t\r\n\t\r\n\t/**\r\n\t * The default background options\r\n\t */\r\n\tdefaultBackgroundOptions: {\r\n\t\tshape: 'circle',\r\n\t\tborderWidth: 1,\r\n\t\tborderColor: 'silver',\r\n\t\tbackgroundColor: {\r\n\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\r\n\t\t\tstops: [\r\n\t\t\t\t[0, '#FFF'],\r\n\t\t\t\t[1, '#DDD']\r\n\t\t\t]\r\n\t\t},\r\n\t\tfrom: Number.MIN_VALUE, // corrected to axis min\r\n\t\tinnerRadius: 0,\r\n\t\tto: Number.MAX_VALUE, // corrected to axis max\r\n\t\touterRadius: '105%'\r\n\t}\r\n\t\r\n});\r\nvar axisProto = Axis.prototype,\r\n\ttickProto = Tick.prototype;\r\n\t\r\n/**\r\n * Augmented methods for the x axis in order to hide it completely, used for the X axis in gauges\r\n */\r\nvar hiddenAxisMixin = {\r\n\tgetOffset: noop,\r\n\tredraw: function () {\r\n\t\tthis.isDirty = false; // prevent setting Y axis dirty\r\n\t},\r\n\trender: function () {\r\n\t\tthis.isDirty = false; // prevent setting Y axis dirty\r\n\t},\r\n\tsetScale: noop,\r\n\tsetCategories: noop,\r\n\tsetTitle: noop\r\n};\r\n\r\n/**\r\n * Augmented methods for the value axis\r\n */\r\n/*jslint unparam: true*/\r\nvar radialAxisMixin = {\r\n\tisRadial: true,\r\n\t\r\n\t/**\r\n\t * The default options extend defaultYAxisOptions\r\n\t */\r\n\tdefaultRadialGaugeOptions: {\r\n\t\tlabels: {\r\n\t\t\talign: 'center',\r\n\t\t\tx: 0,\r\n\t\t\ty: null // auto\r\n\t\t},\r\n\t\tminorGridLineWidth: 0,\r\n\t\tminorTickInterval: 'auto',\r\n\t\tminorTickLength: 10,\r\n\t\tminorTickPosition: 'inside',\r\n\t\tminorTickWidth: 1,\r\n\t\tplotBands: [],\r\n\t\ttickLength: 10,\r\n\t\ttickPosition: 'inside',\r\n\t\ttickWidth: 2,\r\n\t\ttitle: {\r\n\t\t\trotation: 0\r\n\t\t},\r\n\t\tzIndex: 2 // behind dials, points in the series group\r\n\t},\r\n\t\r\n\t// Circular axis around the perimeter of a polar chart\r\n\tdefaultRadialXOptions: {\r\n\t\tgridLineWidth: 1, // spokes\r\n\t\tlabels: {\r\n\t\t\talign: null, // auto\r\n\t\t\tdistance: 15,\r\n\t\t\tx: 0,\r\n\t\t\ty: null // auto\r\n\t\t},\r\n\t\tmaxPadding: 0,\r\n\t\tminPadding: 0,\r\n\t\tplotBands: [],\r\n\t\tshowLastLabel: false, \r\n\t\ttickLength: 0\r\n\t},\r\n\t\r\n\t// Radial axis, like a spoke in a polar chart\r\n\tdefaultRadialYOptions: {\r\n\t\tgridLineInterpolation: 'circle',\r\n\t\tlabels: {\r\n\t\t\talign: 'right',\r\n\t\t\tx: -3,\r\n\t\t\ty: -2\r\n\t\t},\r\n\t\tplotBands: [],\r\n\t\tshowLastLabel: false,\r\n\t\ttitle: {\r\n\t\t\tx: 4,\r\n\t\t\ttext: null,\r\n\t\t\trotation: 90\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Merge and set options\r\n\t */\r\n\tsetOptions: function (userOptions) {\r\n\t\t\r\n\t\tthis.options = merge(\r\n\t\t\tthis.defaultOptions,\r\n\t\t\tthis.defaultRadialOptions,\r\n\t\t\tuserOptions\r\n\t\t);\r\n\t\t\r\n\t},\r\n\t\r\n\t/**\r\n\t * Wrap the getOffset method to return zero offset for title or labels in a radial \r\n\t * axis\r\n\t */\r\n\tgetOffset: function () {\r\n\t\t// Call the Axis prototype method (the method we're in now is on the instance)\r\n\t\taxisProto.getOffset.call(this);\r\n\t\t\r\n\t\t// Title or label offsets are not counted\r\n\t\tthis.chart.axisOffset[this.side] = 0;\r\n\t},\r\n\r\n\r\n\t/**\r\n\t * Get the path for the axis line. This method is also referenced in the getPlotLinePath\r\n\t * method.\r\n\t */\r\n\tgetLinePath: function (lineWidth, radius) {\r\n\t\tvar center = this.center;\r\n\t\tradius = pick(radius, center[2] / 2 - this.offset);\r\n\t\t\r\n\t\treturn this.chart.renderer.symbols.arc(\r\n\t\t\tthis.left + center[0],\r\n\t\t\tthis.top + center[1],\r\n\t\t\tradius,\r\n\t\t\tradius, \r\n\t\t\t{\r\n\t\t\t\tstart: this.startAngleRad,\r\n\t\t\t\tend: this.endAngleRad,\r\n\t\t\t\topen: true,\r\n\t\t\t\tinnerR: 0\r\n\t\t\t}\r\n\t\t);\r\n\t},\r\n\r\n\t/**\r\n\t * Override setAxisTranslation by setting the translation to the difference\r\n\t * in rotation. This allows the translate method to return angle for \r\n\t * any given value.\r\n\t */\r\n\tsetAxisTranslation: function () {\r\n\t\t\r\n\t\t// Call uber method\t\t\r\n\t\taxisProto.setAxisTranslation.call(this);\r\n\t\t\t\r\n\t\t// Set transA and minPixelPadding\r\n\t\tif (this.center) { // it's not defined the first time\r\n\t\t\tif (this.isCircular) {\r\n\t\t\t\t\r\n\t\t\t\tthis.transA = (this.endAngleRad - this.startAngleRad) / \r\n\t\t\t\t\t((this.max - this.min) || 1);\r\n\t\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t} else { \r\n\t\t\t\tthis.transA = (this.center[2] / 2) / ((this.max - this.min) || 1);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (this.isXAxis) {\r\n\t\t\t\tthis.minPixelPadding = this.transA * this.minPointOffset +\r\n\t\t\t\t\t(this.reversed ? (this.endAngleRad - this.startAngleRad) / 4 : 0); // ???\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * In case of auto connect, add one closestPointRange to the max value right before\r\n\t * tickPositions are computed, so that ticks will extend passed the real max.\r\n\t */\r\n\tbeforeSetTickPositions: function () {\r\n\t\tif (this.autoConnect) {\r\n\t\t\tthis.max += (this.categories && 1) || this.pointRange || this.closestPointRange || 0; // #1197, #2260\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Override the setAxisSize method to use the arc's circumference as length. This\r\n\t * allows tickPixelInterval to apply to pixel lengths along the perimeter\r\n\t */\r\n\tsetAxisSize: function () {\r\n\t\t\r\n\t\taxisProto.setAxisSize.call(this);\r\n\r\n\t\tif (this.isRadial) {\r\n\r\n\t\t\t// Set the center array\r\n\t\t\tthis.center = this.pane.center = seriesTypes.pie.prototype.getCenter.call(this.pane);\r\n\t\t\t\r\n\t\t\tthis.len = this.width = this.height = this.isCircular ?\r\n\t\t\t\tthis.center[2] * (this.endAngleRad - this.startAngleRad) / 2 :\r\n\t\t\t\tthis.center[2] / 2;\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Returns the x, y coordinate of a point given by a value and a pixel distance\r\n\t * from center\r\n\t */\r\n\tgetPosition: function (value, length) {\r\n\t\tif (!this.isCircular) {\r\n\t\t\tlength = this.translate(value);\r\n\t\t\tvalue = this.min;\t\r\n\t\t}\r\n\t\t\r\n\t\treturn this.postTranslate(\r\n\t\t\tthis.translate(value),\r\n\t\t\tpick(length, this.center[2] / 2) - this.offset\r\n\t\t);\t\t\r\n\t},\r\n\t\r\n\t/**\r\n\t * Translate from intermediate plotX (angle), plotY (axis.len - radius) to final chart coordinates. \r\n\t */\r\n\tpostTranslate: function (angle, radius) {\r\n\t\t\r\n\t\tvar chart = this.chart,\r\n\t\t\tcenter = this.center;\r\n\t\t\t\r\n\t\tangle = this.startAngleRad + angle;\r\n\t\t\r\n\t\treturn {\r\n\t\t\tx: chart.plotLeft + center[0] + Math.cos(angle) * radius,\r\n\t\t\ty: chart.plotTop + center[1] + Math.sin(angle) * radius\r\n\t\t}; \r\n\t\t\r\n\t},\r\n\t\r\n\t/**\r\n\t * Find the path for plot bands along the radial axis\r\n\t */\r\n\tgetPlotBandPath: function (from, to, options) {\r\n\t\tvar center = this.center,\r\n\t\t\tstartAngleRad = this.startAngleRad,\r\n\t\t\tfullRadius = center[2] / 2,\r\n\t\t\tradii = [\r\n\t\t\t\tpick(options.outerRadius, '100%'),\r\n\t\t\t\toptions.innerRadius,\r\n\t\t\t\tpick(options.thickness, 10)\r\n\t\t\t],\r\n\t\t\tpercentRegex = /%$/,\r\n\t\t\tstart,\r\n\t\t\tend,\r\n\t\t\topen,\r\n\t\t\tisCircular = this.isCircular, // X axis in a polar chart\r\n\t\t\tret;\r\n\t\t\t\r\n\t\t// Polygonal plot bands\r\n\t\tif (this.options.gridLineInterpolation === 'polygon') {\r\n\t\t\tret = this.getPlotLinePath(from).concat(this.getPlotLinePath(to, true));\r\n\t\t\r\n\t\t// Circular grid bands\r\n\t\t} else {\r\n\t\t\t\r\n\t\t\t// Plot bands on Y axis (radial axis) - inner and outer radius depend on to and from\r\n\t\t\tif (!isCircular) {\r\n\t\t\t\tradii[0] = this.translate(from);\r\n\t\t\t\tradii[1] = this.translate(to);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// Convert percentages to pixel values\r\n\t\t\tradii = map(radii, function (radius) {\r\n\t\t\t\tif (percentRegex.test(radius)) {\r\n\t\t\t\t\tradius = (pInt(radius, 10) * fullRadius) / 100;\r\n\t\t\t\t}\r\n\t\t\t\treturn radius;\r\n\t\t\t});\r\n\t\t\t\r\n\t\t\t// Handle full circle\r\n\t\t\tif (options.shape === 'circle' || !isCircular) {\r\n\t\t\t\tstart = -Math.PI / 2;\r\n\t\t\t\tend = Math.PI * 1.5;\r\n\t\t\t\topen = true;\r\n\t\t\t} else {\r\n\t\t\t\tstart = startAngleRad + this.translate(from);\r\n\t\t\t\tend = startAngleRad + this.translate(to);\r\n\t\t\t}\r\n\t\t\r\n\t\t\r\n\t\t\tret = this.chart.renderer.symbols.arc(\r\n\t\t\t\tthis.left + center[0],\r\n\t\t\t\tthis.top + center[1],\r\n\t\t\t\tradii[0],\r\n\t\t\t\tradii[0],\r\n\t\t\t\t{\r\n\t\t\t\t\tstart: start,\r\n\t\t\t\t\tend: end,\r\n\t\t\t\t\tinnerR: pick(radii[1], radii[0] - radii[2]),\r\n\t\t\t\t\topen: open\r\n\t\t\t\t}\r\n\t\t\t);\r\n\t\t}\r\n\t\t \r\n\t\treturn ret;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Find the path for plot lines perpendicular to the radial axis.\r\n\t */\r\n\tgetPlotLinePath: function (value, reverse) {\r\n\t\tvar axis = this,\r\n\t\t\tcenter = axis.center,\r\n\t\t\tchart = axis.chart,\r\n\t\t\tend = axis.getPosition(value),\r\n\t\t\txAxis,\r\n\t\t\txy,\r\n\t\t\ttickPositions,\r\n\t\t\tret;\r\n\t\t\r\n\t\t// Spokes\r\n\t\tif (axis.isCircular) {\r\n\t\t\tret = ['M', center[0] + chart.plotLeft, center[1] + chart.plotTop, 'L', end.x, end.y];\r\n\t\t\r\n\t\t// Concentric circles\t\t\t\r\n\t\t} else if (axis.options.gridLineInterpolation === 'circle') {\r\n\t\t\tvalue = axis.translate(value);\r\n\t\t\tif (value) { // a value of 0 is in the center\r\n\t\t\t\tret = axis.getLinePath(0, value);\r\n\t\t\t}\r\n\t\t// Concentric polygons \r\n\t\t} else {\r\n\t\t\txAxis = chart.xAxis[0];\r\n\t\t\tret = [];\r\n\t\t\tvalue = axis.translate(value);\r\n\t\t\ttickPositions = xAxis.tickPositions;\r\n\t\t\tif (xAxis.autoConnect) {\r\n\t\t\t\ttickPositions = tickPositions.concat([tickPositions[0]]);\r\n\t\t\t}\r\n\t\t\t// Reverse the positions for concatenation of polygonal plot bands\r\n\t\t\tif (reverse) {\r\n\t\t\t\ttickPositions = [].concat(tickPositions).reverse();\r\n\t\t\t}\r\n\t\t\t\t\r\n\t\t\teach(tickPositions, function (pos, i) {\r\n\t\t\t\txy = xAxis.getPosition(pos, value);\r\n\t\t\t\tret.push(i ? 'L' : 'M', xy.x, xy.y);\r\n\t\t\t});\r\n\t\t\t\r\n\t\t}\r\n\t\treturn ret;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Find the position for the axis title, by default inside the gauge\r\n\t */\r\n\tgetTitlePosition: function () {\r\n\t\tvar center = this.center,\r\n\t\t\tchart = this.chart,\r\n\t\t\ttitleOptions = this.options.title;\r\n\t\t\r\n\t\treturn { \r\n\t\t\tx: chart.plotLeft + center[0] + (titleOptions.x || 0), \r\n\t\t\ty: chart.plotTop + center[1] - ({ high: 0.5, middle: 0.25, low: 0 }[titleOptions.align] * \r\n\t\t\t\tcenter[2]) + (titleOptions.y || 0)  \r\n\t\t};\r\n\t}\r\n\t\r\n};\r\n/*jslint unparam: false*/\r\n\r\n/**\r\n * Override axisProto.init to mix in special axis instance functions and function overrides\r\n */\r\nwrap(axisProto, 'init', function (proceed, chart, userOptions) {\r\n\tvar axis = this,\r\n\t\tangular = chart.angular,\r\n\t\tpolar = chart.polar,\r\n\t\tisX = userOptions.isX,\r\n\t\tisHidden = angular && isX,\r\n\t\tisCircular,\r\n\t\tstartAngleRad,\r\n\t\tendAngleRad,\r\n\t\toptions,\r\n\t\tchartOptions = chart.options,\r\n\t\tpaneIndex = userOptions.pane || 0,\r\n\t\tpane,\r\n\t\tpaneOptions;\r\n\t\t\r\n\t// Before prototype.init\r\n\tif (angular) {\r\n\t\textend(this, isHidden ? hiddenAxisMixin : radialAxisMixin);\r\n\t\tisCircular =  !isX;\r\n\t\tif (isCircular) {\r\n\t\t\tthis.defaultRadialOptions = this.defaultRadialGaugeOptions;\r\n\t\t}\r\n\t\t\r\n\t} else if (polar) {\r\n\t\t//extend(this, userOptions.isX ? radialAxisMixin : radialAxisMixin);\r\n\t\textend(this, radialAxisMixin);\r\n\t\tisCircular = isX;\r\n\t\tthis.defaultRadialOptions = isX ? this.defaultRadialXOptions : merge(this.defaultYAxisOptions, this.defaultRadialYOptions);\r\n\t\t\r\n\t}\r\n\t\r\n\t// Run prototype.init\r\n\tproceed.call(this, chart, userOptions);\r\n\t\r\n\tif (!isHidden && (angular || polar)) {\r\n\t\toptions = this.options;\r\n\t\t\r\n\t\t// Create the pane and set the pane options.\r\n\t\tif (!chart.panes) {\r\n\t\t\tchart.panes = [];\r\n\t\t}\r\n\t\tthis.pane = pane = chart.panes[paneIndex] = chart.panes[paneIndex] || new Pane(\r\n\t\t\tsplat(chartOptions.pane)[paneIndex],\r\n\t\t\tchart,\r\n\t\t\taxis\r\n\t\t);\r\n\t\tpaneOptions = pane.options;\r\n\t\t\r\n\t\t\t\r\n\t\t// Disable certain features on angular and polar axes\r\n\t\tchart.inverted = false;\r\n\t\tchartOptions.chart.zoomType = null;\r\n\t\t\r\n\t\t// Start and end angle options are\r\n\t\t// given in degrees relative to top, while internal computations are\r\n\t\t// in radians relative to right (like SVG).\r\n\t\tthis.startAngleRad = startAngleRad = (paneOptions.startAngle - 90) * Math.PI / 180;\r\n\t\tthis.endAngleRad = endAngleRad = (pick(paneOptions.endAngle, paneOptions.startAngle + 360)  - 90) * Math.PI / 180;\r\n\t\tthis.offset = options.offset || 0;\r\n\t\t\r\n\t\tthis.isCircular = isCircular;\r\n\t\t\r\n\t\t// Automatically connect grid lines?\r\n\t\tif (isCircular && userOptions.max === UNDEFINED && endAngleRad - startAngleRad === 2 * Math.PI) {\r\n\t\t\tthis.autoConnect = true;\r\n\t\t}\r\n\t}\r\n\t\r\n});\r\n\r\n/**\r\n * Add special cases within the Tick class' methods for radial axes.\r\n */\t\r\nwrap(tickProto, 'getPosition', function (proceed, horiz, pos, tickmarkOffset, old) {\r\n\tvar axis = this.axis;\r\n\t\r\n\treturn axis.getPosition ? \r\n\t\taxis.getPosition(pos) :\r\n\t\tproceed.call(this, horiz, pos, tickmarkOffset, old);\t\r\n});\r\n\r\n/**\r\n * Wrap the getLabelPosition function to find the center position of the label\r\n * based on the distance option\r\n */\t\r\nwrap(tickProto, 'getLabelPosition', function (proceed, x, y, label, horiz, labelOptions, tickmarkOffset, index, step) {\r\n\tvar axis = this.axis,\r\n\t\toptionsY = labelOptions.y,\r\n\t\tret,\r\n\t\talign = labelOptions.align,\r\n\t\tangle = ((axis.translate(this.pos) + axis.startAngleRad + Math.PI / 2) / Math.PI * 180) % 360;\r\n\t\r\n\tif (axis.isRadial) {\r\n\t\tret = axis.getPosition(this.pos, (axis.center[2] / 2) + pick(labelOptions.distance, -25));\r\n\t\t\r\n\t\t// Automatically rotated\r\n\t\tif (labelOptions.rotation === 'auto') {\r\n\t\t\tlabel.attr({ \r\n\t\t\t\trotation: angle\r\n\t\t\t});\r\n\t\t\r\n\t\t// Vertically centered\r\n\t\t} else if (optionsY === null) {\r\n\t\t\toptionsY = pInt(label.styles.lineHeight) * 0.9 - label.getBBox().height / 2;\r\n\t\t\r\n\t\t}\r\n\t\t\r\n\t\t// Automatic alignment\r\n\t\tif (align === null) {\r\n\t\t\tif (axis.isCircular) {\r\n\t\t\t\tif (angle > 20 && angle < 160) {\r\n\t\t\t\t\talign = 'left'; // right hemisphere\r\n\t\t\t\t} else if (angle > 200 && angle < 340) {\r\n\t\t\t\t\talign = 'right'; // left hemisphere\r\n\t\t\t\t} else {\r\n\t\t\t\t\talign = 'center'; // top or bottom\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\talign = 'center';\r\n\t\t\t}\r\n\t\t\tlabel.attr({\r\n\t\t\t\talign: align\r\n\t\t\t});\r\n\t\t}\r\n\t\t\r\n\t\tret.x += labelOptions.x;\r\n\t\tret.y += optionsY;\r\n\t\t\r\n\t} else {\r\n\t\tret = proceed.call(this, x, y, label, horiz, labelOptions, tickmarkOffset, index, step);\r\n\t}\r\n\treturn ret;\r\n});\r\n\r\n/**\r\n * Wrap the getMarkPath function to return the path of the radial marker\r\n */\r\nwrap(tickProto, 'getMarkPath', function (proceed, x, y, tickLength, tickWidth, horiz, renderer) {\r\n\tvar axis = this.axis,\r\n\t\tendPoint,\r\n\t\tret;\r\n\t\t\r\n\tif (axis.isRadial) {\r\n\t\tendPoint = axis.getPosition(this.pos, axis.center[2] / 2 + tickLength);\r\n\t\tret = [\r\n\t\t\t'M',\r\n\t\t\tx,\r\n\t\t\ty,\r\n\t\t\t'L',\r\n\t\t\tendPoint.x,\r\n\t\t\tendPoint.y\r\n\t\t];\r\n\t} else {\r\n\t\tret = proceed.call(this, x, y, tickLength, tickWidth, horiz, renderer);\r\n\t}\r\n\treturn ret;\r\n});/* \r\n * The AreaRangeSeries class\r\n * \r\n */\r\n\r\n/**\r\n * Extend the default options with map options\r\n */\r\ndefaultPlotOptions.arearange = merge(defaultPlotOptions.area, {\r\n\tlineWidth: 1,\r\n\tmarker: null,\r\n\tthreshold: null,\r\n\ttooltip: {\r\n\t\tpointFormat: '<span style=\"color:{series.color}\">{series.name}</span>: <b>{point.low}</b> - <b>{point.high}</b><br/>' \r\n\t},\r\n\ttrackByArea: true,\r\n\tdataLabels: {\r\n\t\tverticalAlign: null,\r\n\t\txLow: 0,\r\n\t\txHigh: 0,\r\n\t\tyLow: 0,\r\n\t\tyHigh: 0\t\r\n\t}\r\n});\r\n\r\n/**\r\n * Add the series type\r\n */\r\nseriesTypes.arearange = Highcharts.extendClass(seriesTypes.area, {\r\n\ttype: 'arearange',\r\n\tpointArrayMap: ['low', 'high'],\r\n\ttoYData: function (point) {\r\n\t\treturn [point.low, point.high];\r\n\t},\r\n\tpointValKey: 'low',\r\n\t\r\n\t/**\r\n\t * Extend getSegments to force null points if the higher value is null. #1703.\r\n\t */\r\n\tgetSegments: function () {\r\n\t\tvar series = this;\r\n\r\n\t\teach(series.points, function (point) {\r\n\t\t\tif (!series.options.connectNulls && (point.low === null || point.high === null)) {\r\n\t\t\t\tpoint.y = null;\r\n\t\t\t} else if (point.low === null && point.high !== null) {\r\n\t\t\t\tpoint.y = point.high;\r\n\t\t\t}\r\n\t\t});\r\n\t\tSeries.prototype.getSegments.call(this);\r\n\t},\r\n\t\r\n\t/**\r\n\t * Translate data points from raw values x and y to plotX and plotY\r\n\t */\r\n\ttranslate: function () {\r\n\t\tvar series = this,\r\n\t\t\tyAxis = series.yAxis;\r\n\r\n\t\tseriesTypes.area.prototype.translate.apply(series);\r\n\r\n\t\t// Set plotLow and plotHigh\r\n\t\teach(series.points, function (point) {\r\n\r\n\t\t\tvar low = point.low,\r\n\t\t\t\thigh = point.high,\r\n\t\t\t\tplotY = point.plotY;\r\n\r\n\t\t\tif (high === null && low === null) {\r\n\t\t\t\tpoint.y = null;\r\n\t\t\t} else if (low === null) {\r\n\t\t\t\tpoint.plotLow = point.plotY = null;\r\n\t\t\t\tpoint.plotHigh = yAxis.translate(high, 0, 1, 0, 1);\r\n\t\t\t} else if (high === null) {\r\n\t\t\t\tpoint.plotLow = plotY;\r\n\t\t\t\tpoint.plotHigh = null;\r\n\t\t\t} else {\r\n\t\t\t\tpoint.plotLow = plotY;\r\n\t\t\t\tpoint.plotHigh = yAxis.translate(high, 0, 1, 0, 1);\r\n\t\t\t}\r\n\t\t});\r\n\t},\r\n\t\r\n\t/**\r\n\t * Extend the line series' getSegmentPath method by applying the segment\r\n\t * path to both lower and higher values of the range\r\n\t */\r\n\tgetSegmentPath: function (segment) {\r\n\t\t\r\n\t\tvar lowSegment,\r\n\t\t\thighSegment = [],\r\n\t\t\ti = segment.length,\r\n\t\t\tbaseGetSegmentPath = Series.prototype.getSegmentPath,\r\n\t\t\tpoint,\r\n\t\t\tlinePath,\r\n\t\t\tlowerPath,\r\n\t\t\toptions = this.options,\r\n\t\t\tstep = options.step,\r\n\t\t\thigherPath;\r\n\t\t\t\r\n\t\t// Remove nulls from low segment\r\n\t\tlowSegment = HighchartsAdapter.grep(segment, function (point) {\r\n\t\t\treturn point.plotLow !== null;\r\n\t\t});\r\n\t\t\r\n\t\t// Make a segment with plotX and plotY for the top values\r\n\t\twhile (i--) {\r\n\t\t\tpoint = segment[i];\r\n\t\t\tif (point.plotHigh !== null) {\r\n\t\t\t\thighSegment.push({\r\n\t\t\t\t\tplotX: point.plotX,\r\n\t\t\t\t\tplotY: point.plotHigh\r\n\t\t\t\t});\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// Get the paths\r\n\t\tlowerPath = baseGetSegmentPath.call(this, lowSegment);\r\n\t\tif (step) {\r\n\t\t\tif (step === true) {\r\n\t\t\t\tstep = 'left';\r\n\t\t\t}\r\n\t\t\toptions.step = { left: 'right', center: 'center', right: 'left' }[step]; // swap for reading in getSegmentPath\r\n\t\t}\r\n\t\thigherPath = baseGetSegmentPath.call(this, highSegment);\r\n\t\toptions.step = step;\r\n\t\t\r\n\t\t// Create a line on both top and bottom of the range\r\n\t\tlinePath = [].concat(lowerPath, higherPath);\r\n\t\t\r\n\t\t// For the area path, we need to change the 'move' statement into 'lineTo' or 'curveTo'\r\n\t\thigherPath[0] = 'L'; // this probably doesn't work for spline\t\t\t\r\n\t\tthis.areaPath = this.areaPath.concat(lowerPath, higherPath);\r\n\t\t\r\n\t\treturn linePath;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Extend the basic drawDataLabels method by running it for both lower and higher\r\n\t * values.\r\n\t */\r\n\tdrawDataLabels: function () {\r\n\t\t\r\n\t\tvar data = this.data,\r\n\t\t\tlength = data.length,\r\n\t\t\ti,\r\n\t\t\toriginalDataLabels = [],\r\n\t\t\tseriesProto = Series.prototype,\r\n\t\t\tdataLabelOptions = this.options.dataLabels,\r\n\t\t\tpoint,\r\n\t\t\tinverted = this.chart.inverted;\r\n\t\t\t\r\n\t\tif (dataLabelOptions.enabled || this._hasPointLabels) {\r\n\t\t\t\r\n\t\t\t// Step 1: set preliminary values for plotY and dataLabel and draw the upper labels\r\n\t\t\ti = length;\r\n\t\t\twhile (i--) {\r\n\t\t\t\tpoint = data[i];\r\n\t\t\t\t\r\n\t\t\t\t// Set preliminary values\r\n\t\t\t\tpoint.y = point.high;\r\n\t\t\t\tpoint.plotY = point.plotHigh;\r\n\t\t\t\t\r\n\t\t\t\t// Store original data labels and set preliminary label objects to be picked up \r\n\t\t\t\t// in the uber method\r\n\t\t\t\toriginalDataLabels[i] = point.dataLabel;\r\n\t\t\t\tpoint.dataLabel = point.dataLabelUpper;\r\n\t\t\t\t\r\n\t\t\t\t// Set the default offset\r\n\t\t\t\tpoint.below = false;\r\n\t\t\t\tif (inverted) {\r\n\t\t\t\t\tdataLabelOptions.align = 'left';\r\n\t\t\t\t\tdataLabelOptions.x = dataLabelOptions.xHigh;\t\t\t\t\t\t\t\t\r\n\t\t\t\t} else {\r\n\t\t\t\t\tdataLabelOptions.y = dataLabelOptions.yHigh;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tseriesProto.drawDataLabels.apply(this, arguments); // #1209\r\n\t\t\t\r\n\t\t\t// Step 2: reorganize and handle data labels for the lower values\r\n\t\t\ti = length;\r\n\t\t\twhile (i--) {\r\n\t\t\t\tpoint = data[i];\r\n\t\t\t\t\r\n\t\t\t\t// Move the generated labels from step 1, and reassign the original data labels\r\n\t\t\t\tpoint.dataLabelUpper = point.dataLabel;\r\n\t\t\t\tpoint.dataLabel = originalDataLabels[i];\r\n\t\t\t\t\r\n\t\t\t\t// Reset values\r\n\t\t\t\tpoint.y = point.low;\r\n\t\t\t\tpoint.plotY = point.plotLow;\r\n\t\t\t\t\r\n\t\t\t\t// Set the default offset\r\n\t\t\t\tpoint.below = true;\r\n\t\t\t\tif (inverted) {\r\n\t\t\t\t\tdataLabelOptions.align = 'right';\r\n\t\t\t\t\tdataLabelOptions.x = dataLabelOptions.xLow;\r\n\t\t\t\t} else {\r\n\t\t\t\t\tdataLabelOptions.y = dataLabelOptions.yLow;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tseriesProto.drawDataLabels.apply(this, arguments);\r\n\t\t}\r\n\t\r\n\t},\r\n\t\r\n\talignDataLabel: seriesTypes.column.prototype.alignDataLabel,\r\n\t\r\n\tgetSymbol: seriesTypes.column.prototype.getSymbol,\r\n\t\r\n\tdrawPoints: noop\r\n});/**\r\n * The AreaSplineRangeSeries class\r\n */\r\n\r\ndefaultPlotOptions.areasplinerange = merge(defaultPlotOptions.arearange);\r\n\r\n/**\r\n * AreaSplineRangeSeries object\r\n */\r\nseriesTypes.areasplinerange = extendClass(seriesTypes.arearange, {\r\n\ttype: 'areasplinerange',\r\n\tgetPointSpline: seriesTypes.spline.prototype.getPointSpline\r\n});/**\r\n * The ColumnRangeSeries class\r\n */\r\ndefaultPlotOptions.columnrange = merge(defaultPlotOptions.column, defaultPlotOptions.arearange, {\r\n\tlineWidth: 1,\r\n\tpointRange: null\r\n});\r\n\r\n/**\r\n * ColumnRangeSeries object\r\n */\r\nseriesTypes.columnrange = extendClass(seriesTypes.arearange, {\r\n\ttype: 'columnrange',\r\n\t/**\r\n\t * Translate data points from raw values x and y to plotX and plotY\r\n\t */\r\n\ttranslate: function () {\r\n\t\tvar series = this,\r\n\t\t\tyAxis = series.yAxis,\r\n\t\t\tplotHigh;\r\n\r\n\t\tcolProto.translate.apply(series);\r\n\r\n\t\t// Set plotLow and plotHigh\r\n\t\teach(series.points, function (point) {\r\n\t\t\tvar shapeArgs = point.shapeArgs,\r\n\t\t\t\tminPointLength = series.options.minPointLength,\r\n\t\t\t\theightDifference,\r\n\t\t\t\theight,\r\n\t\t\t\ty;\r\n\r\n\t\t\tpoint.plotHigh = plotHigh = yAxis.translate(point.high, 0, 1, 0, 1);\r\n\t\t\tpoint.plotLow = point.plotY;\r\n\r\n\t\t\t// adjust shape\r\n\t\t\ty = plotHigh;\r\n\t\t\theight = point.plotY - plotHigh;\r\n\r\n\t\t\tif (height < minPointLength) {\r\n\t\t\t\theightDifference = (minPointLength - height);\r\n\t\t\t\theight += heightDifference;\r\n\t\t\t\ty -= heightDifference / 2;\r\n\t\t\t}\r\n\t\t\tshapeArgs.height = height;\r\n\t\t\tshapeArgs.y = y;\r\n\t\t});\r\n\t},\r\n\ttrackerGroups: ['group', 'dataLabels'],\r\n\tdrawGraph: noop,\r\n\tpointAttrToOptions: colProto.pointAttrToOptions,\r\n\tdrawPoints: colProto.drawPoints,\r\n\tdrawTracker: colProto.drawTracker,\r\n\tanimate: colProto.animate,\r\n\tgetColumnMetrics: colProto.getColumnMetrics\r\n});\r\n/* \r\n * The GaugeSeries class\r\n */\r\n\r\n\r\n\r\n/**\r\n * Extend the default options\r\n */\r\ndefaultPlotOptions.gauge = merge(defaultPlotOptions.line, {\r\n\tdataLabels: {\r\n\t\tenabled: true,\r\n\t\ty: 15,\r\n\t\tborderWidth: 1,\r\n\t\tborderColor: 'silver',\r\n\t\tborderRadius: 3,\r\n\t\tstyle: {\r\n\t\t\tfontWeight: 'bold'\r\n\t\t},\r\n\t\tverticalAlign: 'top',\r\n\t\tzIndex: 2\r\n\t},\r\n\tdial: {\r\n\t\t// radius: '80%',\r\n\t\t// backgroundColor: 'black',\r\n\t\t// borderColor: 'silver',\r\n\t\t// borderWidth: 0,\r\n\t\t// baseWidth: 3,\r\n\t\t// topWidth: 1,\r\n\t\t// baseLength: '70%' // of radius\r\n\t\t// rearLength: '10%'\r\n\t},\r\n\tpivot: {\r\n\t\t//radius: 5,\r\n\t\t//borderWidth: 0\r\n\t\t//borderColor: 'silver',\r\n\t\t//backgroundColor: 'black'\r\n\t},\r\n\ttooltip: {\r\n\t\theaderFormat: ''\r\n\t},\r\n\tshowInLegend: false\r\n});\r\n\r\n/**\r\n * Extend the point object\r\n */\r\nvar GaugePoint = Highcharts.extendClass(Highcharts.Point, {\r\n\t/**\r\n\t * Don't do any hover colors or anything\r\n\t */\r\n\tsetState: function (state) {\r\n\t\tthis.state = state;\r\n\t}\r\n});\r\n\r\n\r\n/**\r\n * Add the series type\r\n */\r\nvar GaugeSeries = {\r\n\ttype: 'gauge',\r\n\tpointClass: GaugePoint,\r\n\t\r\n\t// chart.angular will be set to true when a gauge series is present, and this will\r\n\t// be used on the axes\r\n\tangular: true, \r\n\tdrawGraph: noop,\r\n\tfixedBox: true,\r\n\ttrackerGroups: ['group', 'dataLabels'],\r\n\t\r\n\t/**\r\n\t * Calculate paths etc\r\n\t */\r\n\ttranslate: function () {\r\n\t\t\r\n\t\tvar series = this,\r\n\t\t\tyAxis = series.yAxis,\r\n\t\t\toptions = series.options,\r\n\t\t\tcenter = yAxis.center;\r\n\t\t\t\r\n\t\tseries.generatePoints();\r\n\t\t\r\n\t\teach(series.points, function (point) {\r\n\t\t\t\r\n\t\t\tvar dialOptions = merge(options.dial, point.dial),\r\n\t\t\t\tradius = (pInt(pick(dialOptions.radius, 80)) * center[2]) / 200,\r\n\t\t\t\tbaseLength = (pInt(pick(dialOptions.baseLength, 70)) * radius) / 100,\r\n\t\t\t\trearLength = (pInt(pick(dialOptions.rearLength, 10)) * radius) / 100,\r\n\t\t\t\tbaseWidth = dialOptions.baseWidth || 3,\r\n\t\t\t\ttopWidth = dialOptions.topWidth || 1,\r\n\t\t\t\trotation = yAxis.startAngleRad + yAxis.translate(point.y, null, null, null, true);\r\n\r\n\t\t\t// Handle the wrap option\r\n\t\t\tif (options.wrap === false) {\r\n\t\t\t\trotation = Math.max(yAxis.startAngleRad, Math.min(yAxis.endAngleRad, rotation));\r\n\t\t\t}\r\n\t\t\trotation = rotation * 180 / Math.PI;\r\n\t\t\t\t\r\n\t\t\tpoint.shapeType = 'path';\r\n\t\t\tpoint.shapeArgs = {\r\n\t\t\t\td: dialOptions.path || [\r\n\t\t\t\t\t'M', \r\n\t\t\t\t\t-rearLength, -baseWidth / 2, \r\n\t\t\t\t\t'L', \r\n\t\t\t\t\tbaseLength, -baseWidth / 2,\r\n\t\t\t\t\tradius, -topWidth / 2,\r\n\t\t\t\t\tradius, topWidth / 2,\r\n\t\t\t\t\tbaseLength, baseWidth / 2,\r\n\t\t\t\t\t-rearLength, baseWidth / 2,\r\n\t\t\t\t\t'z'\r\n\t\t\t\t],\r\n\t\t\t\ttranslateX: center[0],\r\n\t\t\t\ttranslateY: center[1],\r\n\t\t\t\trotation: rotation\r\n\t\t\t};\r\n\t\t\t\r\n\t\t\t// Positions for data label\r\n\t\t\tpoint.plotX = center[0];\r\n\t\t\tpoint.plotY = center[1];\r\n\t\t});\r\n\t},\r\n\t\r\n\t/**\r\n\t * Draw the points where each point is one needle\r\n\t */\r\n\tdrawPoints: function () {\r\n\t\t\r\n\t\tvar series = this,\r\n\t\t\tcenter = series.yAxis.center,\r\n\t\t\tpivot = series.pivot,\r\n\t\t\toptions = series.options,\r\n\t\t\tpivotOptions = options.pivot,\r\n\t\t\trenderer = series.chart.renderer;\r\n\t\t\r\n\t\teach(series.points, function (point) {\r\n\t\t\t\r\n\t\t\tvar graphic = point.graphic,\r\n\t\t\t\tshapeArgs = point.shapeArgs,\r\n\t\t\t\td = shapeArgs.d,\r\n\t\t\t\tdialOptions = merge(options.dial, point.dial); // #1233\r\n\t\t\t\r\n\t\t\tif (graphic) {\r\n\t\t\t\tgraphic.animate(shapeArgs);\r\n\t\t\t\tshapeArgs.d = d; // animate alters it\r\n\t\t\t} else {\r\n\t\t\t\tpoint.graphic = renderer[point.shapeType](shapeArgs)\r\n\t\t\t\t\t.attr({\r\n\t\t\t\t\t\tstroke: dialOptions.borderColor || 'none',\r\n\t\t\t\t\t\t'stroke-width': dialOptions.borderWidth || 0,\r\n\t\t\t\t\t\tfill: dialOptions.backgroundColor || 'black',\r\n\t\t\t\t\t\trotation: shapeArgs.rotation // required by VML when animation is false\r\n\t\t\t\t\t})\r\n\t\t\t\t\t.add(series.group);\r\n\t\t\t}\r\n\t\t});\r\n\t\t\r\n\t\t// Add or move the pivot\r\n\t\tif (pivot) {\r\n\t\t\tpivot.animate({ // #1235\r\n\t\t\t\ttranslateX: center[0],\r\n\t\t\t\ttranslateY: center[1]\r\n\t\t\t});\r\n\t\t} else {\r\n\t\t\tseries.pivot = renderer.circle(0, 0, pick(pivotOptions.radius, 5))\r\n\t\t\t\t.attr({\r\n\t\t\t\t\t'stroke-width': pivotOptions.borderWidth || 0,\r\n\t\t\t\t\tstroke: pivotOptions.borderColor || 'silver',\r\n\t\t\t\t\tfill: pivotOptions.backgroundColor || 'black'\r\n\t\t\t\t})\r\n\t\t\t\t.translate(center[0], center[1])\r\n\t\t\t\t.add(series.group);\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Animate the arrow up from startAngle\r\n\t */\r\n\tanimate: function (init) {\r\n\t\tvar series = this;\r\n\r\n\t\tif (!init) {\r\n\t\t\teach(series.points, function (point) {\r\n\t\t\t\tvar graphic = point.graphic;\r\n\r\n\t\t\t\tif (graphic) {\r\n\t\t\t\t\t// start value\r\n\t\t\t\t\tgraphic.attr({\r\n\t\t\t\t\t\trotation: series.yAxis.startAngleRad * 180 / Math.PI\r\n\t\t\t\t\t});\r\n\r\n\t\t\t\t\t// animate\r\n\t\t\t\t\tgraphic.animate({\r\n\t\t\t\t\t\trotation: point.shapeArgs.rotation\r\n\t\t\t\t\t}, series.options.animation);\r\n\t\t\t\t}\r\n\t\t\t});\r\n\r\n\t\t\t// delete this function to allow it only once\r\n\t\t\tseries.animate = null;\r\n\t\t}\r\n\t},\r\n\t\r\n\trender: function () {\r\n\t\tthis.group = this.plotGroup(\r\n\t\t\t'group', \r\n\t\t\t'series', \r\n\t\t\tthis.visible ? 'visible' : 'hidden', \r\n\t\t\tthis.options.zIndex, \r\n\t\t\tthis.chart.seriesGroup\r\n\t\t);\r\n\t\tseriesTypes.pie.prototype.render.call(this);\r\n\t\tthis.group.clip(this.chart.clipRect);\r\n\t},\r\n\t\r\n\tsetData: seriesTypes.pie.prototype.setData,\r\n\tdrawTracker: seriesTypes.column.prototype.drawTracker\r\n};\r\nseriesTypes.gauge = Highcharts.extendClass(seriesTypes.line, GaugeSeries);/* ****************************************************************************\r\n * Start Box plot series code\t\t\t\t\t\t\t\t\t\t\t      *\r\n *****************************************************************************/\r\n\r\n// Set default options\r\ndefaultPlotOptions.boxplot = merge(defaultPlotOptions.column, {\r\n\tfillColor: '#FFFFFF',\r\n\tlineWidth: 1,\r\n\t//medianColor: null,\r\n\tmedianWidth: 2,\r\n\tstates: {\r\n\t\thover: {\r\n\t\t\tbrightness: -0.3\r\n\t\t}\r\n\t},\r\n\t//stemColor: null,\r\n\t//stemDashStyle: 'solid'\r\n\t//stemWidth: null,\r\n\tthreshold: null,\r\n\ttooltip: {\r\n\t\tpointFormat: '<span style=\"color:{series.color};font-weight:bold\">{series.name}</span><br/>' +\r\n\t\t\t'Maximum: {point.high}<br/>' +\r\n\t\t\t'Upper quartile: {point.q3}<br/>' +\r\n\t\t\t'Median: {point.median}<br/>' +\r\n\t\t\t'Lower quartile: {point.q1}<br/>' +\r\n\t\t\t'Minimum: {point.low}<br/>'\r\n\t\t\t\r\n\t},\r\n\t//whiskerColor: null,\r\n\twhiskerLength: '50%',\r\n\twhiskerWidth: 2\r\n});\r\n\r\n// Create the series object\r\nseriesTypes.boxplot = extendClass(seriesTypes.column, {\r\n\ttype: 'boxplot',\r\n\tpointArrayMap: ['low', 'q1', 'median', 'q3', 'high'], // array point configs are mapped to this\r\n\ttoYData: function (point) { // return a plain array for speedy calculation\r\n\t\treturn [point.low, point.q1, point.median, point.q3, point.high];\r\n\t},\r\n\tpointValKey: 'high', // defines the top of the tracker\r\n\t\r\n\t/**\r\n\t * One-to-one mapping from options to SVG attributes\r\n\t */\r\n\tpointAttrToOptions: { // mapping between SVG attributes and the corresponding options\r\n\t\tfill: 'fillColor',\r\n\t\tstroke: 'color',\r\n\t\t'stroke-width': 'lineWidth'\r\n\t},\r\n\t\r\n\t/**\r\n\t * Disable data labels for box plot\r\n\t */\r\n\tdrawDataLabels: noop,\r\n\r\n\t/**\r\n\t * Translate data points from raw values x and y to plotX and plotY\r\n\t */\r\n\ttranslate: function () {\r\n\t\tvar series = this,\r\n\t\t\tyAxis = series.yAxis,\r\n\t\t\tpointArrayMap = series.pointArrayMap;\r\n\r\n\t\tseriesTypes.column.prototype.translate.apply(series);\r\n\r\n\t\t// do the translation on each point dimension\r\n\t\teach(series.points, function (point) {\r\n\t\t\teach(pointArrayMap, function (key) {\r\n\t\t\t\tif (point[key] !== null) {\r\n\t\t\t\t\tpoint[key + 'Plot'] = yAxis.translate(point[key], 0, 1, 0, 1);\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t});\r\n\t},\r\n\r\n\t/**\r\n\t * Draw the data points\r\n\t */\r\n\tdrawPoints: function () {\r\n\t\tvar series = this,  //state = series.state,\r\n\t\t\tpoints = series.points,\r\n\t\t\toptions = series.options,\r\n\t\t\tchart = series.chart,\r\n\t\t\trenderer = chart.renderer,\r\n\t\t\tpointAttr,\r\n\t\t\tq1Plot,\r\n\t\t\tq3Plot,\r\n\t\t\thighPlot,\r\n\t\t\tlowPlot,\r\n\t\t\tmedianPlot,\r\n\t\t\tcrispCorr,\r\n\t\t\tcrispX,\r\n\t\t\tgraphic,\r\n\t\t\tstemPath,\r\n\t\t\tstemAttr,\r\n\t\t\tboxPath,\r\n\t\t\twhiskersPath,\r\n\t\t\twhiskersAttr,\r\n\t\t\tmedianPath,\r\n\t\t\tmedianAttr,\r\n\t\t\twidth,\r\n\t\t\tleft,\r\n\t\t\tright,\r\n\t\t\thalfWidth,\r\n\t\t\tshapeArgs,\r\n\t\t\tcolor,\r\n\t\t\tdoQuartiles = series.doQuartiles !== false, // error bar inherits this series type but doesn't do quartiles\r\n\t\t\twhiskerLength = parseInt(series.options.whiskerLength, 10) / 100;\r\n\r\n\r\n\t\teach(points, function (point) {\r\n\r\n\t\t\tgraphic = point.graphic;\r\n\t\t\tshapeArgs = point.shapeArgs; // the box\r\n\t\t\tstemAttr = {};\r\n\t\t\twhiskersAttr = {};\r\n\t\t\tmedianAttr = {};\r\n\t\t\tcolor = point.color || series.color;\r\n\t\t\t\r\n\t\t\tif (point.plotY !== UNDEFINED) {\r\n\r\n\t\t\t\tpointAttr = point.pointAttr[point.selected ? 'selected' : ''];\r\n\r\n\t\t\t\t// crisp vector coordinates\r\n\t\t\t\twidth = shapeArgs.width;\r\n\t\t\t\tleft = mathFloor(shapeArgs.x);\r\n\t\t\t\tright = left + width;\r\n\t\t\t\thalfWidth = mathRound(width / 2);\r\n\t\t\t\t//crispX = mathRound(left + halfWidth) + crispCorr;\r\n\t\t\t\tq1Plot = mathFloor(doQuartiles ? point.q1Plot : point.lowPlot);// + crispCorr;\r\n\t\t\t\tq3Plot = mathFloor(doQuartiles ? point.q3Plot : point.lowPlot);// + crispCorr;\r\n\t\t\t\thighPlot = mathFloor(point.highPlot);// + crispCorr;\r\n\t\t\t\tlowPlot = mathFloor(point.lowPlot);// + crispCorr;\r\n\t\t\t\t\r\n\t\t\t\t// Stem attributes\r\n\t\t\t\tstemAttr.stroke = point.stemColor || options.stemColor || color;\r\n\t\t\t\tstemAttr['stroke-width'] = pick(point.stemWidth, options.stemWidth, options.lineWidth);\r\n\t\t\t\tstemAttr.dashstyle = point.stemDashStyle || options.stemDashStyle;\r\n\t\t\t\t\r\n\t\t\t\t// Whiskers attributes\r\n\t\t\t\twhiskersAttr.stroke = point.whiskerColor || options.whiskerColor || color;\r\n\t\t\t\twhiskersAttr['stroke-width'] = pick(point.whiskerWidth, options.whiskerWidth, options.lineWidth);\r\n\t\t\t\t\r\n\t\t\t\t// Median attributes\r\n\t\t\t\tmedianAttr.stroke = point.medianColor || options.medianColor || color;\r\n\t\t\t\tmedianAttr['stroke-width'] = pick(point.medianWidth, options.medianWidth, options.lineWidth);\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t// The stem\r\n\t\t\t\tcrispCorr = (stemAttr['stroke-width'] % 2) / 2;\r\n\t\t\t\tcrispX = left + halfWidth + crispCorr;\t\t\t\t\r\n\t\t\t\tstemPath = [\r\n\t\t\t\t\t// stem up\r\n\t\t\t\t\t'M',\r\n\t\t\t\t\tcrispX, q3Plot,\r\n\t\t\t\t\t'L',\r\n\t\t\t\t\tcrispX, highPlot,\r\n\t\t\t\t\t\r\n\t\t\t\t\t// stem down\r\n\t\t\t\t\t'M',\r\n\t\t\t\t\tcrispX, q1Plot,\r\n\t\t\t\t\t'L',\r\n\t\t\t\t\tcrispX, lowPlot,\r\n\t\t\t\t\t'z'\r\n\t\t\t\t];\r\n\t\t\t\t\r\n\t\t\t\t// The box\r\n\t\t\t\tif (doQuartiles) {\r\n\t\t\t\t\tcrispCorr = (pointAttr['stroke-width'] % 2) / 2;\r\n\t\t\t\t\tcrispX = mathFloor(crispX) + crispCorr;\r\n\t\t\t\t\tq1Plot = mathFloor(q1Plot) + crispCorr;\r\n\t\t\t\t\tq3Plot = mathFloor(q3Plot) + crispCorr;\r\n\t\t\t\t\tleft += crispCorr;\r\n\t\t\t\t\tright += crispCorr;\r\n\t\t\t\t\tboxPath = [\r\n\t\t\t\t\t\t'M',\r\n\t\t\t\t\t\tleft, q3Plot,\r\n\t\t\t\t\t\t'L',\r\n\t\t\t\t\t\tleft, q1Plot,\r\n\t\t\t\t\t\t'L',\r\n\t\t\t\t\t\tright, q1Plot,\r\n\t\t\t\t\t\t'L',\r\n\t\t\t\t\t\tright, q3Plot,\r\n\t\t\t\t\t\t'L',\r\n\t\t\t\t\t\tleft, q3Plot,\r\n\t\t\t\t\t\t'z'\r\n\t\t\t\t\t];\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// The whiskers\r\n\t\t\t\tif (whiskerLength) {\r\n\t\t\t\t\tcrispCorr = (whiskersAttr['stroke-width'] % 2) / 2;\r\n\t\t\t\t\thighPlot = highPlot + crispCorr;\r\n\t\t\t\t\tlowPlot = lowPlot + crispCorr;\r\n\t\t\t\t\twhiskersPath = [\r\n\t\t\t\t\t\t// High whisker\r\n\t\t\t\t\t\t'M',\r\n\t\t\t\t\t\tcrispX - halfWidth * whiskerLength, \r\n\t\t\t\t\t\thighPlot,\r\n\t\t\t\t\t\t'L',\r\n\t\t\t\t\t\tcrispX + halfWidth * whiskerLength, \r\n\t\t\t\t\t\thighPlot,\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t// Low whisker\r\n\t\t\t\t\t\t'M',\r\n\t\t\t\t\t\tcrispX - halfWidth * whiskerLength, \r\n\t\t\t\t\t\tlowPlot,\r\n\t\t\t\t\t\t'L',\r\n\t\t\t\t\t\tcrispX + halfWidth * whiskerLength, \r\n\t\t\t\t\t\tlowPlot\r\n\t\t\t\t\t];\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// The median\r\n\t\t\t\tcrispCorr = (medianAttr['stroke-width'] % 2) / 2;\t\t\t\t\r\n\t\t\t\tmedianPlot = mathRound(point.medianPlot) + crispCorr;\r\n\t\t\t\tmedianPath = [\r\n\t\t\t\t\t'M',\r\n\t\t\t\t\tleft, \r\n\t\t\t\t\tmedianPlot,\r\n\t\t\t\t\t'L',\r\n\t\t\t\t\tright, \r\n\t\t\t\t\tmedianPlot,\r\n\t\t\t\t\t'z'\r\n\t\t\t\t];\r\n\t\t\t\t\r\n\t\t\t\t// Create or update the graphics\r\n\t\t\t\tif (graphic) { // update\r\n\t\t\t\t\t\r\n\t\t\t\t\tpoint.stem.animate({ d: stemPath });\r\n\t\t\t\t\tif (whiskerLength) {\r\n\t\t\t\t\t\tpoint.whiskers.animate({ d: whiskersPath });\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (doQuartiles) {\r\n\t\t\t\t\t\tpoint.box.animate({ d: boxPath });\r\n\t\t\t\t\t}\r\n\t\t\t\t\tpoint.medianShape.animate({ d: medianPath });\r\n\t\t\t\t\t\r\n\t\t\t\t} else { // create new\r\n\t\t\t\t\tpoint.graphic = graphic = renderer.g()\r\n\t\t\t\t\t\t.add(series.group);\r\n\t\t\t\t\t\r\n\t\t\t\t\tpoint.stem = renderer.path(stemPath)\r\n\t\t\t\t\t\t.attr(stemAttr)\r\n\t\t\t\t\t\t.add(graphic);\r\n\t\t\t\t\t\t\r\n\t\t\t\t\tif (whiskerLength) {\r\n\t\t\t\t\t\tpoint.whiskers = renderer.path(whiskersPath) \r\n\t\t\t\t\t\t\t.attr(whiskersAttr)\r\n\t\t\t\t\t\t\t.add(graphic);\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (doQuartiles) {\r\n\t\t\t\t\t\tpoint.box = renderer.path(boxPath)\r\n\t\t\t\t\t\t\t.attr(pointAttr)\r\n\t\t\t\t\t\t\t.add(graphic);\r\n\t\t\t\t\t}\t\r\n\t\t\t\t\tpoint.medianShape = renderer.path(medianPath)\r\n\t\t\t\t\t\t.attr(medianAttr)\r\n\t\t\t\t\t\t.add(graphic);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t}\r\n\r\n\r\n});\r\n\r\n/* ****************************************************************************\r\n * End Box plot series code\t\t\t\t\t\t\t\t\t\t\t\t*\r\n *****************************************************************************/\r\n/* ****************************************************************************\r\n * Start error bar series code                                                *\r\n *****************************************************************************/\r\n\r\n// 1 - set default options\r\ndefaultPlotOptions.errorbar = merge(defaultPlotOptions.boxplot, {\r\n\tcolor: '#000000',\r\n\tgrouping: false,\r\n\tlinkedTo: ':previous',\r\n\ttooltip: {\r\n\t\tpointFormat: defaultPlotOptions.arearange.tooltip.pointFormat\r\n\t},\r\n\twhiskerWidth: null\r\n});\r\n\r\n// 2 - Create the series object\r\nseriesTypes.errorbar = extendClass(seriesTypes.boxplot, {\r\n\ttype: 'errorbar',\r\n\tpointArrayMap: ['low', 'high'], // array point configs are mapped to this\r\n\ttoYData: function (point) { // return a plain array for speedy calculation\r\n\t\treturn [point.low, point.high];\r\n\t},\r\n\tpointValKey: 'high', // defines the top of the tracker\r\n\tdoQuartiles: false,\r\n\r\n\t/**\r\n\t * Get the width and X offset, either on top of the linked series column\r\n\t * or standalone\r\n\t */\r\n\tgetColumnMetrics: function () {\r\n\t\treturn (this.linkedParent && this.linkedParent.columnMetrics) || \r\n\t\t\tseriesTypes.column.prototype.getColumnMetrics.call(this);\r\n\t}\r\n});\r\n\r\n/* ****************************************************************************\r\n * End error bar series code                                                  *\r\n *****************************************************************************/\r\n/* ****************************************************************************\r\n * Start Waterfall series code                                                *\r\n *****************************************************************************/\r\n\r\n// 1 - set default options\r\ndefaultPlotOptions.waterfall = merge(defaultPlotOptions.column, {\r\n\tlineWidth: 1,\r\n\tlineColor: '#333',\r\n\tdashStyle: 'dot',\r\n\tborderColor: '#333'\r\n});\r\n\r\n\r\n// 2 - Create the series object\r\nseriesTypes.waterfall = extendClass(seriesTypes.column, {\r\n\ttype: 'waterfall',\r\n\r\n\tupColorProp: 'fill',\r\n\r\n\tpointArrayMap: ['low', 'y'],\r\n\r\n\tpointValKey: 'y',\r\n\r\n\t/**\r\n\t * Init waterfall series, force stacking\r\n\t */\r\n\tinit: function (chart, options) {\r\n\t\t// force stacking\r\n\t\toptions.stacking = true;\r\n\r\n\t\tseriesTypes.column.prototype.init.call(this, chart, options);\r\n\t},\r\n\r\n\r\n\t/**\r\n\t * Translate data points from raw values\r\n\t */\r\n\ttranslate: function () {\r\n\t\tvar series = this,\r\n\t\t\toptions = series.options,\r\n\t\t\taxis = series.yAxis,\r\n\t\t\tlen,\r\n\t\t\ti,\r\n\t\t\tpoints,\r\n\t\t\tpoint,\r\n\t\t\tshapeArgs,\r\n\t\t\tstack,\r\n\t\t\ty,\r\n\t\t\tpreviousY,\r\n\t\t\tstackPoint,\r\n\t\t\tthreshold = options.threshold,\r\n\t\t\tcrispCorr = (options.borderWidth % 2) / 2;\r\n\r\n\t\t// run column series translate\r\n\t\tseriesTypes.column.prototype.translate.apply(this);\r\n\r\n\t\tpreviousY = threshold;\r\n\t\tpoints = series.points;\r\n\r\n\t\tfor (i = 0, len = points.length; i < len; i++) {\r\n\t\t\t// cache current point object\r\n\t\t\tpoint = points[i];\r\n\t\t\tshapeArgs = point.shapeArgs;\r\n\r\n\t\t\t// get current stack\r\n\t\t\tstack = series.getStack(i);\r\n\t\t\tstackPoint = stack.points[series.index];\r\n\r\n\t\t\t// override point value for sums\r\n\t\t\tif (isNaN(point.y)) {\r\n\t\t\t\tpoint.y = series.yData[i];\r\n\t\t\t}\r\n\r\n\t\t\t// up points\r\n\t\t\ty = mathMax(previousY, previousY + point.y) + stackPoint[0];\r\n\t\t\tshapeArgs.y = axis.translate(y, 0, 1);\r\n\r\n\r\n\t\t\t// sum points\r\n\t\t\tif (point.isSum || point.isIntermediateSum) {\r\n\t\t\t\tshapeArgs.y = axis.translate(stackPoint[1], 0, 1);\r\n\t\t\t\tshapeArgs.height = axis.translate(stackPoint[0], 0, 1) - shapeArgs.y;\r\n\r\n\t\t\t// if it's not the sum point, update previous stack end position\r\n\t\t\t} else {\r\n\t\t\t\tpreviousY += stack.total;\r\n\t\t\t}\r\n\r\n\t\t\t// negative points\r\n\t\t\tif (shapeArgs.height < 0) {\r\n\t\t\t\tshapeArgs.y += shapeArgs.height;\r\n\t\t\t\tshapeArgs.height *= -1;\r\n\t\t\t}\r\n\r\n\t\t\tpoint.plotY = shapeArgs.y = mathRound(shapeArgs.y) - crispCorr;\r\n\t\t\tshapeArgs.height = mathRound(shapeArgs.height);\r\n\t\t\tpoint.yBottom = shapeArgs.y + shapeArgs.height;\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Call default processData then override yData to reflect waterfall's extremes on yAxis\r\n\t */\r\n\tprocessData: function (force) {\r\n\t\tvar series = this,\r\n\t\t\toptions = series.options,\r\n\t\t\tyData = series.yData,\r\n\t\t\tpoints = series.points,\r\n\t\t\tpoint,\r\n\t\t\tdataLength = yData.length,\r\n\t\t\tthreshold = options.threshold || 0,\r\n\t\t\tsubSum,\r\n\t\t\tsum,\r\n\t\t\tdataMin,\r\n\t\t\tdataMax,\r\n\t\t\ty,\r\n\t\t\ti;\r\n\r\n\t\tsum = subSum = dataMin = dataMax = threshold;\r\n\r\n\t\tfor (i = 0; i < dataLength; i++) {\r\n\t\t\ty = yData[i];\r\n\t\t\tpoint = points && points[i] ? points[i] : {};\r\n\r\n\t\t\tif (y === \"sum\" || point.isSum) {\r\n\t\t\t\tyData[i] = sum;\r\n\t\t\t} else if (y === \"intermediateSum\" || point.isIntermediateSum) {\r\n\t\t\t\tyData[i] = subSum;\r\n\t\t\t\tsubSum = threshold;\r\n\t\t\t} else {\r\n\t\t\t\tsum += y;\r\n\t\t\t\tsubSum += y;\r\n\t\t\t}\r\n\t\t\tdataMin = Math.min(sum, dataMin);\r\n\t\t\tdataMax = Math.max(sum, dataMax);\r\n\t\t}\r\n\r\n\t\tSeries.prototype.processData.call(this, force);\r\n\r\n\t\t// Record extremes\r\n\t\tseries.dataMin = dataMin;\r\n\t\tseries.dataMax = dataMax;\r\n\t},\r\n\r\n\t/**\r\n\t * Return y value or string if point is sum\r\n\t */\r\n\ttoYData: function (pt) {\r\n\t\tif (pt.isSum) {\r\n\t\t\treturn \"sum\";\r\n\t\t} else if (pt.isIntermediateSum) {\r\n\t\t\treturn \"intermediateSum\";\r\n\t\t}\r\n\r\n\t\treturn pt.y;\r\n\t},\r\n\r\n\t/**\r\n\t * Postprocess mapping between options and SVG attributes\r\n\t */\r\n\tgetAttribs: function () {\r\n\t\tseriesTypes.column.prototype.getAttribs.apply(this, arguments);\r\n\r\n\t\tvar series = this,\r\n\t\t\toptions = series.options,\r\n\t\t\tstateOptions = options.states,\r\n\t\t\tupColor = options.upColor || series.color,\r\n\t\t\thoverColor = Highcharts.Color(upColor).brighten(0.1).get(),\r\n\t\t\tseriesDownPointAttr = merge(series.pointAttr),\r\n\t\t\tupColorProp = series.upColorProp;\r\n\r\n\t\tseriesDownPointAttr[''][upColorProp] = upColor;\r\n\t\tseriesDownPointAttr.hover[upColorProp] = stateOptions.hover.upColor || hoverColor;\r\n\t\tseriesDownPointAttr.select[upColorProp] = stateOptions.select.upColor || upColor;\r\n\r\n\t\teach(series.points, function (point) {\r\n\t\t\tif (point.y > 0 && !point.color) {\r\n\t\t\t\tpoint.pointAttr = seriesDownPointAttr;\r\n\t\t\t\tpoint.color = upColor;\r\n\t\t\t}\r\n\t\t});\r\n\t},\r\n\r\n\t/**\r\n\t * Draw columns' connector lines\r\n\t */\r\n\tgetGraphPath: function () {\r\n\r\n\t\tvar data = this.data,\r\n\t\t\tlength = data.length,\r\n\t\t\tlineWidth = this.options.lineWidth + this.options.borderWidth,\r\n\t\t\tnormalizer = mathRound(lineWidth) % 2 / 2,\r\n\t\t\tpath = [],\r\n\t\t\tM = 'M',\r\n\t\t\tL = 'L',\r\n\t\t\tprevArgs,\r\n\t\t\tpointArgs,\r\n\t\t\ti,\r\n\t\t\td;\r\n\r\n\t\tfor (i = 1; i < length; i++) {\r\n\t\t\tpointArgs = data[i].shapeArgs;\r\n\t\t\tprevArgs = data[i - 1].shapeArgs;\r\n\r\n\t\t\td = [\r\n\t\t\t\tM,\r\n\t\t\t\tprevArgs.x + prevArgs.width, prevArgs.y + normalizer,\r\n\t\t\t\tL,\r\n\t\t\t\tpointArgs.x, prevArgs.y + normalizer\r\n\t\t\t];\r\n\r\n\t\t\tif (data[i - 1].y < 0) {\r\n\t\t\t\td[2] += prevArgs.height;\r\n\t\t\t\td[5] += prevArgs.height;\r\n\t\t\t}\r\n\r\n\t\t\tpath = path.concat(d);\r\n\t\t}\r\n\r\n\t\treturn path;\r\n\t},\r\n\r\n\t/**\r\n\t * Extremes are recorded in processData\r\n\t */\r\n\tgetExtremes: noop,\r\n\r\n\t/**\r\n\t * Return stack for given index\r\n\t */\r\n\tgetStack: function (i) {\r\n\t\tvar axis = this.yAxis,\r\n\t\t\tstacks = axis.stacks,\r\n\t\t\tkey = this.stackKey;\r\n\r\n\t\tif (this.processedYData[i] < this.options.threshold) {\r\n\t\t\tkey = '-' + key;\r\n\t\t}\r\n\r\n\t\treturn stacks[key][i];\r\n\t},\r\n\r\n\tdrawGraph: Series.prototype.drawGraph\r\n});\r\n\r\n/* ****************************************************************************\r\n * End Waterfall series code                                                  *\r\n *****************************************************************************/\r\n/* ****************************************************************************\r\n * Start Bubble series code\t\t\t\t\t\t\t\t\t\t\t          *\r\n *****************************************************************************/\r\n\r\n// 1 - set default options\r\ndefaultPlotOptions.bubble = merge(defaultPlotOptions.scatter, {\r\n\tdataLabels: {\r\n\t\tinside: true,\r\n\t\tstyle: {\r\n\t\t\tcolor: 'white',\r\n\t\t\ttextShadow: '0px 0px 3px black'\r\n\t\t},\r\n\t\tverticalAlign: 'middle'\r\n\t},\r\n\t// displayNegative: true,\r\n\tmarker: {\r\n\t\t// fillOpacity: 0.5,\r\n\t\tlineColor: null, // inherit from series.color\r\n\t\tlineWidth: 1\r\n\t},\r\n\tminSize: 8,\r\n\tmaxSize: '20%',\r\n\t// negativeColor: null,\r\n\ttooltip: {\r\n\t\tpointFormat: '({point.x}, {point.y}), Size: {point.z}'\r\n\t},\r\n\tturboThreshold: 0,\r\n\tzThreshold: 0\r\n});\r\n\r\n// 2 - Create the series object\r\nseriesTypes.bubble = extendClass(seriesTypes.scatter, {\r\n\ttype: 'bubble',\r\n\tpointArrayMap: ['y', 'z'],\r\n\ttrackerGroups: ['group', 'dataLabelsGroup'],\r\n\t\r\n\t/**\r\n\t * Mapping between SVG attributes and the corresponding options\r\n\t */\r\n\tpointAttrToOptions: { \r\n\t\tstroke: 'lineColor',\r\n\t\t'stroke-width': 'lineWidth',\r\n\t\tfill: 'fillColor'\r\n\t},\r\n\t\r\n\t/**\r\n\t * Apply the fillOpacity to all fill positions\r\n\t */\r\n\tapplyOpacity: function (fill) {\r\n\t\tvar markerOptions = this.options.marker,\r\n\t\t\tfillOpacity = pick(markerOptions.fillOpacity, 0.5);\r\n\t\t\r\n\t\t// When called from Legend.colorizeItem, the fill isn't predefined\r\n\t\tfill = fill || markerOptions.fillColor || this.color; \r\n\t\t\r\n\t\tif (fillOpacity !== 1) {\r\n\t\t\tfill = Highcharts.Color(fill).setOpacity(fillOpacity).get('rgba');\r\n\t\t}\r\n\t\treturn fill;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Extend the convertAttribs method by applying opacity to the fill\r\n\t */\r\n\tconvertAttribs: function () {\r\n\t\tvar obj = Series.prototype.convertAttribs.apply(this, arguments);\r\n\t\t\r\n\t\tobj.fill = this.applyOpacity(obj.fill);\r\n\t\t\r\n\t\treturn obj;\r\n\t},\r\n\r\n\t/**\r\n\t * Get the radius for each point based on the minSize, maxSize and each point's Z value. This\r\n\t * must be done prior to Series.translate because the axis needs to add padding in \r\n\t * accordance with the point sizes.\r\n\t */\r\n\tgetRadii: function (zMin, zMax, minSize, maxSize) {\r\n\t\tvar len,\r\n\t\t\ti,\r\n\t\t\tpos,\r\n\t\t\tzData = this.zData,\r\n\t\t\tradii = [],\r\n\t\t\tzRange;\r\n\t\t\r\n\t\t// Set the shape type and arguments to be picked up in drawPoints\r\n\t\tfor (i = 0, len = zData.length; i < len; i++) {\r\n\t\t\tzRange = zMax - zMin;\r\n\t\t\tpos = zRange > 0 ? // relative size, a number between 0 and 1\r\n\t\t\t\t(zData[i] - zMin) / (zMax - zMin) : \r\n\t\t\t\t0.5;\r\n\t\t\tradii.push(math.ceil(minSize + pos * (maxSize - minSize)) / 2);\r\n\t\t}\r\n\t\tthis.radii = radii;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Perform animation on the bubbles\r\n\t */\r\n\tanimate: function (init) {\r\n\t\tvar animation = this.options.animation;\r\n\t\t\r\n\t\tif (!init) { // run the animation\r\n\t\t\teach(this.points, function (point) {\r\n\t\t\t\tvar graphic = point.graphic,\r\n\t\t\t\t\tshapeArgs = point.shapeArgs;\r\n\r\n\t\t\t\tif (graphic && shapeArgs) {\r\n\t\t\t\t\t// start values\r\n\t\t\t\t\tgraphic.attr('r', 1);\r\n\r\n\t\t\t\t\t// animate\r\n\t\t\t\t\tgraphic.animate({\r\n\t\t\t\t\t\tr: shapeArgs.r\r\n\t\t\t\t\t}, animation);\r\n\t\t\t\t}\r\n\t\t\t});\r\n\r\n\t\t\t// delete this function to allow it only once\r\n\t\t\tthis.animate = null;\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Extend the base translate method to handle bubble size\r\n\t */\r\n\ttranslate: function () {\r\n\t\t\r\n\t\tvar i,\r\n\t\t\tdata = this.data,\r\n\t\t\tpoint,\r\n\t\t\tradius,\r\n\t\t\tradii = this.radii;\r\n\t\t\r\n\t\t// Run the parent method\r\n\t\tseriesTypes.scatter.prototype.translate.call(this);\r\n\t\t\r\n\t\t// Set the shape type and arguments to be picked up in drawPoints\r\n\t\ti = data.length;\r\n\t\t\r\n\t\twhile (i--) {\r\n\t\t\tpoint = data[i];\r\n\t\t\tradius = radii ? radii[i] : 0; // #1737\r\n\r\n\t\t\t// Flag for negativeColor to be applied in Series.js\r\n\t\t\tpoint.negative = point.z < (this.options.zThreshold || 0);\r\n\t\t\t\r\n\t\t\tif (radius >= this.minPxSize / 2) {\r\n\t\t\t\t// Shape arguments\r\n\t\t\t\tpoint.shapeType = 'circle';\r\n\t\t\t\tpoint.shapeArgs = {\r\n\t\t\t\t\tx: point.plotX,\r\n\t\t\t\t\ty: point.plotY,\r\n\t\t\t\t\tr: radius\r\n\t\t\t\t};\r\n\t\t\t\t\r\n\t\t\t\t// Alignment box for the data label\r\n\t\t\t\tpoint.dlBox = {\r\n\t\t\t\t\tx: point.plotX - radius,\r\n\t\t\t\t\ty: point.plotY - radius,\r\n\t\t\t\t\twidth: 2 * radius,\r\n\t\t\t\t\theight: 2 * radius\r\n\t\t\t\t};\r\n\t\t\t} else { // below zThreshold\r\n\t\t\t\tpoint.shapeArgs = point.plotY = point.dlBox = UNDEFINED; // #1691\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Get the series' symbol in the legend\r\n\t * \r\n\t * @param {Object} legend The legend object\r\n\t * @param {Object} item The series (this) or point\r\n\t */\r\n\tdrawLegendSymbol: function (legend, item) {\r\n\t\tvar radius = pInt(legend.itemStyle.fontSize) / 2;\r\n\t\t\r\n\t\titem.legendSymbol = this.chart.renderer.circle(\r\n\t\t\tradius,\r\n\t\t\tlegend.baseline - radius,\r\n\t\t\tradius\r\n\t\t).attr({\r\n\t\t\tzIndex: 3\r\n\t\t}).add(item.legendGroup);\r\n\t\titem.legendSymbol.isMarker = true;\t\r\n\t\t\r\n\t},\r\n\t\r\n\tdrawPoints: seriesTypes.column.prototype.drawPoints,\r\n\talignDataLabel: seriesTypes.column.prototype.alignDataLabel\r\n});\r\n\r\n/**\r\n * Add logic to pad each axis with the amount of pixels\r\n * necessary to avoid the bubbles to overflow.\r\n */\r\nAxis.prototype.beforePadding = function () {\r\n\tvar axis = this,\r\n\t\taxisLength = this.len,\r\n\t\tchart = this.chart,\r\n\t\tpxMin = 0, \r\n\t\tpxMax = axisLength,\r\n\t\tisXAxis = this.isXAxis,\r\n\t\tdataKey = isXAxis ? 'xData' : 'yData',\r\n\t\tmin = this.min,\r\n\t\textremes = {},\r\n\t\tsmallestSize = math.min(chart.plotWidth, chart.plotHeight),\r\n\t\tzMin = Number.MAX_VALUE,\r\n\t\tzMax = -Number.MAX_VALUE,\r\n\t\trange = this.max - min,\r\n\t\ttransA = axisLength / range,\r\n\t\tactiveSeries = [];\r\n\r\n\t// Handle padding on the second pass, or on redraw\r\n\tif (this.tickPositions) {\r\n\t\teach(this.series, function (series) {\r\n\r\n\t\t\tvar seriesOptions = series.options,\r\n\t\t\t\tzData;\r\n\r\n\t\t\tif (series.type === 'bubble' && series.visible) {\r\n\r\n\t\t\t\t// Correction for #1673\r\n\t\t\t\taxis.allowZoomOutside = true;\r\n\r\n\t\t\t\t// Cache it\r\n\t\t\t\tactiveSeries.push(series);\r\n\r\n\t\t\t\tif (isXAxis) { // because X axis is evaluated first\r\n\t\t\t\t\r\n\t\t\t\t\t// For each series, translate the size extremes to pixel values\r\n\t\t\t\t\teach(['minSize', 'maxSize'], function (prop) {\r\n\t\t\t\t\t\tvar length = seriesOptions[prop],\r\n\t\t\t\t\t\t\tisPercent = /%$/.test(length);\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tlength = pInt(length);\r\n\t\t\t\t\t\textremes[prop] = isPercent ?\r\n\t\t\t\t\t\t\tsmallestSize * length / 100 :\r\n\t\t\t\t\t\t\tlength;\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t});\r\n\t\t\t\t\tseries.minPxSize = extremes.minSize;\r\n\t\t\t\t\t\r\n\t\t\t\t\t// Find the min and max Z\r\n\t\t\t\t\tzData = series.zData;\r\n\t\t\t\t\tif (zData.length) { // #1735\r\n\t\t\t\t\t\tzMin = math.min(\r\n\t\t\t\t\t\t\tzMin,\r\n\t\t\t\t\t\t\tmath.max(\r\n\t\t\t\t\t\t\t\tarrayMin(zData), \r\n\t\t\t\t\t\t\t\tseriesOptions.displayNegative === false ? seriesOptions.zThreshold : -Number.MAX_VALUE\r\n\t\t\t\t\t\t\t)\r\n\t\t\t\t\t\t);\r\n\t\t\t\t\t\tzMax = math.max(zMax, arrayMax(zData));\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\teach(activeSeries, function (series) {\r\n\r\n\t\t\tvar data = series[dataKey],\r\n\t\t\t\ti = data.length,\r\n\t\t\t\tradius;\r\n\r\n\t\t\tif (isXAxis) {\r\n\t\t\t\tseries.getRadii(zMin, zMax, extremes.minSize, extremes.maxSize);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (range > 0) {\r\n\t\t\t\twhile (i--) {\r\n\t\t\t\t\tradius = series.radii[i];\r\n\t\t\t\t\tpxMin = Math.min(((data[i] - min) * transA) - radius, pxMin);\r\n\t\t\t\t\tpxMax = Math.max(((data[i] - min) * transA) + radius, pxMax);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t});\r\n\t\t\r\n\t\tif (activeSeries.length && range > 0 && pick(this.options.min, this.userMin) === UNDEFINED && pick(this.options.max, this.userMax) === UNDEFINED) {\r\n\t\t\tpxMax -= axisLength;\r\n\t\t\ttransA *= (axisLength + pxMin - pxMax) / axisLength;\r\n\t\t\tthis.min += pxMin / transA;\r\n\t\t\tthis.max += pxMax / transA;\r\n\t\t}\r\n\t}\r\n};\r\n\r\n/* ****************************************************************************\r\n * End Bubble series code                                                     *\r\n *****************************************************************************/\r\n/**\r\n * Extensions for polar charts. Additionally, much of the geometry required for polar charts is\r\n * gathered in RadialAxes.js.\r\n * \r\n */\r\n\r\nvar seriesProto = Series.prototype,\r\n\tpointerProto = Highcharts.Pointer.prototype;\r\n\r\n\r\n\r\n/**\r\n * Translate a point's plotX and plotY from the internal angle and radius measures to \r\n * true plotX, plotY coordinates\r\n */\r\nseriesProto.toXY = function (point) {\r\n\tvar xy,\r\n\t\tchart = this.chart,\r\n\t\tplotX = point.plotX,\r\n\t\tplotY = point.plotY;\r\n\t\r\n\t// Save rectangular plotX, plotY for later computation\r\n\tpoint.rectPlotX = plotX;\r\n\tpoint.rectPlotY = plotY;\r\n\t\r\n\t// Record the angle in degrees for use in tooltip\r\n\tpoint.clientX = ((plotX / Math.PI * 180) + this.xAxis.pane.options.startAngle) % 360;\r\n\t\r\n\t// Find the polar plotX and plotY\r\n\txy = this.xAxis.postTranslate(point.plotX, this.yAxis.len - plotY);\r\n\tpoint.plotX = point.polarPlotX = xy.x - chart.plotLeft;\r\n\tpoint.plotY = point.polarPlotY = xy.y - chart.plotTop;\r\n};\r\n\r\n/** \r\n * Order the tooltip points to get the mouse capture ranges correct. #1915. \r\n */\r\nseriesProto.orderTooltipPoints = function (points) {\r\n\tif (this.chart.polar) {\r\n\t\tpoints.sort(function (a, b) {\r\n\t\t\treturn a.clientX - b.clientX;\r\n\t\t});\r\n\r\n\t\t// Wrap mouse tracking around to capture movement on the segment to the left\r\n\t\t// of the north point (#1469, #2093).\r\n\t\tif (points[0]) {\r\n\t\t\tpoints[0].wrappedClientX = points[0].clientX + 360;\r\n\t\t\tpoints.push(points[0]);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n\r\n/**\r\n * Add some special init logic to areas and areasplines\r\n */\r\nfunction initArea(proceed, chart, options) {\r\n\tproceed.call(this, chart, options);\r\n\tif (this.chart.polar) {\r\n\t\t\r\n\t\t/**\r\n\t\t * Overridden method to close a segment path. While in a cartesian plane the area \r\n\t\t * goes down to the threshold, in the polar chart it goes to the center.\r\n\t\t */\r\n\t\tthis.closeSegment = function (path) {\r\n\t\t\tvar center = this.xAxis.center;\r\n\t\t\tpath.push(\r\n\t\t\t\t'L',\r\n\t\t\t\tcenter[0],\r\n\t\t\t\tcenter[1]\r\n\t\t\t);\t\t\t\r\n\t\t};\r\n\t\t\r\n\t\t// Instead of complicated logic to draw an area around the inner area in a stack,\r\n\t\t// just draw it behind\r\n\t\tthis.closedStacks = true;\r\n\t}\r\n}\r\nwrap(seriesTypes.area.prototype, 'init', initArea);\r\nwrap(seriesTypes.areaspline.prototype, 'init', initArea);\r\n\t\t\r\n\r\n/**\r\n * Overridden method for calculating a spline from one point to the next\r\n */\r\nwrap(seriesTypes.spline.prototype, 'getPointSpline', function (proceed, segment, point, i) {\r\n\t\r\n\tvar ret,\r\n\t\tsmoothing = 1.5, // 1 means control points midway between points, 2 means 1/3 from the point, 3 is 1/4 etc;\r\n\t\tdenom = smoothing + 1,\r\n\t\tplotX, \r\n\t\tplotY,\r\n\t\tlastPoint,\r\n\t\tnextPoint,\r\n\t\tlastX,\r\n\t\tlastY,\r\n\t\tnextX,\r\n\t\tnextY,\r\n\t\tleftContX,\r\n\t\tleftContY,\r\n\t\trightContX,\r\n\t\trightContY,\r\n\t\tdistanceLeftControlPoint,\r\n\t\tdistanceRightControlPoint,\r\n\t\tleftContAngle,\r\n\t\trightContAngle,\r\n\t\tjointAngle;\r\n\t\t\r\n\t\t\r\n\tif (this.chart.polar) {\r\n\t\t\r\n\t\tplotX = point.plotX;\r\n\t\tplotY = point.plotY;\r\n\t\tlastPoint = segment[i - 1];\r\n\t\tnextPoint = segment[i + 1];\r\n\t\t\t\r\n\t\t// Connect ends\r\n\t\tif (this.connectEnds) {\r\n\t\t\tif (!lastPoint) {\r\n\t\t\t\tlastPoint = segment[segment.length - 2]; // not the last but the second last, because the segment is already connected\r\n\t\t\t}\r\n\t\t\tif (!nextPoint) {\r\n\t\t\t\tnextPoint = segment[1];\r\n\t\t\t}\t\r\n\t\t}\r\n\r\n\t\t// find control points\r\n\t\tif (lastPoint && nextPoint) {\r\n\t\t\r\n\t\t\tlastX = lastPoint.plotX;\r\n\t\t\tlastY = lastPoint.plotY;\r\n\t\t\tnextX = nextPoint.plotX;\r\n\t\t\tnextY = nextPoint.plotY;\r\n\t\t\tleftContX = (smoothing * plotX + lastX) / denom;\r\n\t\t\tleftContY = (smoothing * plotY + lastY) / denom;\r\n\t\t\trightContX = (smoothing * plotX + nextX) / denom;\r\n\t\t\trightContY = (smoothing * plotY + nextY) / denom;\r\n\t\t\tdistanceLeftControlPoint = Math.sqrt(Math.pow(leftContX - plotX, 2) + Math.pow(leftContY - plotY, 2));\r\n\t\t\tdistanceRightControlPoint = Math.sqrt(Math.pow(rightContX - plotX, 2) + Math.pow(rightContY - plotY, 2));\r\n\t\t\tleftContAngle = Math.atan2(leftContY - plotY, leftContX - plotX);\r\n\t\t\trightContAngle = Math.atan2(rightContY - plotY, rightContX - plotX);\r\n\t\t\tjointAngle = (Math.PI / 2) + ((leftContAngle + rightContAngle) / 2);\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t// Ensure the right direction, jointAngle should be in the same quadrant as leftContAngle\r\n\t\t\tif (Math.abs(leftContAngle - jointAngle) > Math.PI / 2) {\r\n\t\t\t\tjointAngle -= Math.PI;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// Find the corrected control points for a spline straight through the point\r\n\t\t\tleftContX = plotX + Math.cos(jointAngle) * distanceLeftControlPoint;\r\n\t\t\tleftContY = plotY + Math.sin(jointAngle) * distanceLeftControlPoint;\r\n\t\t\trightContX = plotX + Math.cos(Math.PI + jointAngle) * distanceRightControlPoint;\r\n\t\t\trightContY = plotY + Math.sin(Math.PI + jointAngle) * distanceRightControlPoint;\r\n\t\t\t\r\n\t\t\t// Record for drawing in next point\r\n\t\t\tpoint.rightContX = rightContX;\r\n\t\t\tpoint.rightContY = rightContY;\r\n\r\n\t\t}\r\n\t\t\r\n\t\t\r\n\t\t// moveTo or lineTo\r\n\t\tif (!i) {\r\n\t\t\tret = ['M', plotX, plotY];\r\n\t\t} else { // curve from last point to this\r\n\t\t\tret = [\r\n\t\t\t\t'C',\r\n\t\t\t\tlastPoint.rightContX || lastPoint.plotX,\r\n\t\t\t\tlastPoint.rightContY || lastPoint.plotY,\r\n\t\t\t\tleftContX || plotX,\r\n\t\t\t\tleftContY || plotY,\r\n\t\t\t\tplotX,\r\n\t\t\t\tplotY\r\n\t\t\t];\r\n\t\t\tlastPoint.rightContX = lastPoint.rightContY = null; // reset for updating series later\r\n\t\t}\r\n\t\t\r\n\t\t\r\n\t} else {\r\n\t\tret = proceed.call(this, segment, point, i);\r\n\t}\r\n\treturn ret;\r\n});\r\n\r\n/**\r\n * Extend translate. The plotX and plotY values are computed as if the polar chart were a\r\n * cartesian plane, where plotX denotes the angle in radians and (yAxis.len - plotY) is the pixel distance from\r\n * center. \r\n */\r\nwrap(seriesProto, 'translate', function (proceed) {\r\n\t\t\r\n\t// Run uber method\r\n\tproceed.call(this);\r\n\t\r\n\t// Postprocess plot coordinates\r\n\tif (this.chart.polar && !this.preventPostTranslate) {\r\n\t\tvar points = this.points,\r\n\t\t\ti = points.length;\r\n\t\twhile (i--) {\r\n\t\t\t// Translate plotX, plotY from angle and radius to true plot coordinates\r\n\t\t\tthis.toXY(points[i]);\r\n\t\t}\r\n\t}\r\n});\r\n\r\n/** \r\n * Extend getSegmentPath to allow connecting ends across 0 to provide a closed circle in \r\n * line-like series.\r\n */\r\nwrap(seriesProto, 'getSegmentPath', function (proceed, segment) {\r\n\t\t\r\n\tvar points = this.points;\r\n\t\r\n\t// Connect the path\r\n\tif (this.chart.polar && this.options.connectEnds !== false && \r\n\t\t\tsegment[segment.length - 1] === points[points.length - 1] && points[0].y !== null) {\r\n\t\tthis.connectEnds = true; // re-used in splines\r\n\t\tsegment = [].concat(segment, [points[0]]);\r\n\t}\r\n\t\r\n\t// Run uber method\r\n\treturn proceed.call(this, segment);\r\n\t\r\n});\r\n\r\n\r\nfunction polarAnimate(proceed, init) {\r\n\tvar chart = this.chart,\r\n\t\tanimation = this.options.animation,\r\n\t\tgroup = this.group,\r\n\t\tmarkerGroup = this.markerGroup,\r\n\t\tcenter = this.xAxis.center,\r\n\t\tplotLeft = chart.plotLeft,\r\n\t\tplotTop = chart.plotTop,\r\n\t\tattribs;\r\n\r\n\t// Specific animation for polar charts\r\n\tif (chart.polar) {\r\n\t\t\r\n\t\t// Enable animation on polar charts only in SVG. In VML, the scaling is different, plus animation\r\n\t\t// would be so slow it would't matter.\r\n\t\tif (chart.renderer.isSVG) {\r\n\r\n\t\t\tif (animation === true) {\r\n\t\t\t\tanimation = {};\r\n\t\t\t}\r\n\t\r\n\t\t\t// Initialize the animation\r\n\t\t\tif (init) {\r\n\t\t\t\t\r\n\t\t\t\t// Scale down the group and place it in the center\r\n\t\t\t\tattribs = {\r\n\t\t\t\t\ttranslateX: center[0] + plotLeft,\r\n\t\t\t\t\ttranslateY: center[1] + plotTop,\r\n\t\t\t\t\tscaleX: 0.001, // #1499\r\n\t\t\t\t\tscaleY: 0.001\r\n\t\t\t\t};\r\n\t\t\t\t\t\r\n\t\t\t\tgroup.attr(attribs);\r\n\t\t\t\tif (markerGroup) {\r\n\t\t\t\t\tmarkerGroup.attrSetters = group.attrSetters;\r\n\t\t\t\t\tmarkerGroup.attr(attribs);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t// Run the animation\r\n\t\t\t} else {\r\n\t\t\t\tattribs = {\r\n\t\t\t\t\ttranslateX: plotLeft,\r\n\t\t\t\t\ttranslateY: plotTop,\r\n\t\t\t\t\tscaleX: 1,\r\n\t\t\t\t\tscaleY: 1\r\n\t\t\t\t};\r\n\t\t\t\tgroup.animate(attribs, animation);\r\n\t\t\t\tif (markerGroup) {\r\n\t\t\t\t\tmarkerGroup.animate(attribs, animation);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// Delete this function to allow it only once\r\n\t\t\t\tthis.animate = null;\r\n\t\t\t}\r\n\t\t}\r\n\t\r\n\t// For non-polar charts, revert to the basic animation\r\n\t} else {\r\n\t\tproceed.call(this, init);\r\n\t} \r\n}\r\n\r\n// Define the animate method for both regular series and column series and their derivatives\r\nwrap(seriesProto, 'animate', polarAnimate);\r\nwrap(colProto, 'animate', polarAnimate);\r\n\r\n\r\n/**\r\n * Throw in a couple of properties to let setTooltipPoints know we're indexing the points\r\n * in degrees (0-360), not plot pixel width.\r\n */\r\nwrap(seriesProto, 'setTooltipPoints', function (proceed, renew) {\r\n\t\t\r\n\tif (this.chart.polar) {\r\n\t\textend(this.xAxis, {\r\n\t\t\ttooltipLen: 360 // degrees are the resolution unit of the tooltipPoints array\r\n\t\t});\t\r\n\t}\r\n\t\r\n\t// Run uber method\r\n\treturn proceed.call(this, renew);\r\n});\r\n\r\n\r\n/**\r\n * Extend the column prototype's translate method\r\n */\r\nwrap(colProto, 'translate', function (proceed) {\r\n\t\t\r\n\tvar xAxis = this.xAxis,\r\n\t\tlen = this.yAxis.len,\r\n\t\tcenter = xAxis.center,\r\n\t\tstartAngleRad = xAxis.startAngleRad,\r\n\t\trenderer = this.chart.renderer,\r\n\t\tstart,\r\n\t\tpoints,\r\n\t\tpoint,\r\n\t\ti;\r\n\t\r\n\tthis.preventPostTranslate = true;\r\n\t\r\n\t// Run uber method\r\n\tproceed.call(this);\r\n\t\r\n\t// Postprocess plot coordinates\r\n\tif (xAxis.isRadial) {\r\n\t\tpoints = this.points;\r\n\t\ti = points.length;\r\n\t\twhile (i--) {\r\n\t\t\tpoint = points[i];\r\n\t\t\tstart = point.barX + startAngleRad;\r\n\t\t\tpoint.shapeType = 'path';\r\n\t\t\tpoint.shapeArgs = {\r\n\t\t\t\td: renderer.symbols.arc(\r\n\t\t\t\t\tcenter[0],\r\n\t\t\t\t\tcenter[1],\r\n\t\t\t\t\tlen - point.plotY,\r\n\t\t\t\t\tnull, \r\n\t\t\t\t\t{\r\n\t\t\t\t\t\tstart: start,\r\n\t\t\t\t\t\tend: start + point.pointWidth,\r\n\t\t\t\t\t\tinnerR: len - pick(point.yBottom, len)\r\n\t\t\t\t\t}\r\n\t\t\t\t)\r\n\t\t\t};\r\n\t\t\tthis.toXY(point); // provide correct plotX, plotY for tooltip\r\n\t\t}\r\n\t}\r\n});\r\n\r\n\r\n/**\r\n * Align column data labels outside the columns. #1199.\r\n */\r\nwrap(colProto, 'alignDataLabel', function (proceed, point, dataLabel, options, alignTo, isNew) {\r\n\t\r\n\tif (this.chart.polar) {\r\n\t\tvar angle = point.rectPlotX / Math.PI * 180,\r\n\t\t\talign,\r\n\t\t\tverticalAlign;\r\n\t\t\r\n\t\t// Align nicely outside the perimeter of the columns\r\n\t\tif (options.align === null) {\r\n\t\t\tif (angle > 20 && angle < 160) {\r\n\t\t\t\talign = 'left'; // right hemisphere\r\n\t\t\t} else if (angle > 200 && angle < 340) {\r\n\t\t\t\talign = 'right'; // left hemisphere\r\n\t\t\t} else {\r\n\t\t\t\talign = 'center'; // top or bottom\r\n\t\t\t}\r\n\t\t\toptions.align = align;\r\n\t\t}\r\n\t\tif (options.verticalAlign === null) {\r\n\t\t\tif (angle < 45 || angle > 315) {\r\n\t\t\t\tverticalAlign = 'bottom'; // top part\r\n\t\t\t} else if (angle > 135 && angle < 225) {\r\n\t\t\t\tverticalAlign = 'top'; // bottom part\r\n\t\t\t} else {\r\n\t\t\t\tverticalAlign = 'middle'; // left or right\r\n\t\t\t}\r\n\t\t\toptions.verticalAlign = verticalAlign;\r\n\t\t}\r\n\t\t\r\n\t\tseriesProto.alignDataLabel.call(this, point, dataLabel, options, alignTo, isNew);\r\n\t} else {\r\n\t\tproceed.call(this, point, dataLabel, options, alignTo, isNew);\r\n\t}\r\n\t\r\n});\r\n\r\n/**\r\n * Extend the mouse tracker to return the tooltip position index in terms of\r\n * degrees rather than pixels\r\n */\r\nwrap(pointerProto, 'getIndex', function (proceed, e) {\r\n\tvar ret,\r\n\t\tchart = this.chart,\r\n\t\tcenter,\r\n\t\tx,\r\n\t\ty;\r\n\t\r\n\tif (chart.polar) {\r\n\t\tcenter = chart.xAxis[0].center;\r\n\t\tx = e.chartX - center[0] - chart.plotLeft;\r\n\t\ty = e.chartY - center[1] - chart.plotTop;\r\n\t\t\r\n\t\tret = 180 - Math.round(Math.atan2(x, y) / Math.PI * 180);\r\n\t\r\n\t} else {\r\n\t\r\n\t\t// Run uber method\r\n\t\tret = proceed.call(this, e);\r\n\t}\r\n\treturn ret;\r\n});\r\n\r\n/**\r\n * Extend getCoordinates to prepare for polar axis values\r\n */\r\nwrap(pointerProto, 'getCoordinates', function (proceed, e) {\r\n\tvar chart = this.chart,\r\n\t\tret = {\r\n\t\t\txAxis: [],\r\n\t\t\tyAxis: []\r\n\t\t};\r\n\t\r\n\tif (chart.polar) {\t\r\n\r\n\t\teach(chart.axes, function (axis) {\r\n\t\t\tvar isXAxis = axis.isXAxis,\r\n\t\t\t\tcenter = axis.center,\r\n\t\t\t\tx = e.chartX - center[0] - chart.plotLeft,\r\n\t\t\t\ty = e.chartY - center[1] - chart.plotTop;\r\n\t\t\t\r\n\t\t\tret[isXAxis ? 'xAxis' : 'yAxis'].push({\r\n\t\t\t\taxis: axis,\r\n\t\t\t\tvalue: axis.translate(\r\n\t\t\t\t\tisXAxis ?\r\n\t\t\t\t\t\tMath.PI - Math.atan2(x, y) : // angle \r\n\t\t\t\t\t\tMath.sqrt(Math.pow(x, 2) + Math.pow(y, 2)), // distance from center\r\n\t\t\t\t\ttrue\r\n\t\t\t\t)\r\n\t\t\t});\r\n\t\t});\r\n\t\t\r\n\t} else {\r\n\t\tret = proceed.call(this, e);\r\n\t}\r\n\t\r\n\treturn ret;\r\n});\r\n}(Highcharts));\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/highcharts/highcharts.js",
    "content": "/*\r\n Highcharts JS v3.0.6 (2013-10-04)\r\n\r\n (c) 2009-2013 Torstein Hønsi\r\n\r\n License: www.highcharts.com/license\r\n*/\r\n(function(){function r(a,b){var c;a||(a={});for(c in b)a[c]=b[c];return a}function x(){var a,b=arguments.length,c={},d=function(a,b){var c,h;typeof a!==\"object\"&&(a={});for(h in b)b.hasOwnProperty(h)&&(c=b[h],a[h]=c&&typeof c===\"object\"&&Object.prototype.toString.call(c)!==\"[object Array]\"&&typeof c.nodeType!==\"number\"?d(a[h]||{},c):b[h]);return a};for(a=0;a<b;a++)c=d(c,arguments[a]);return c}function C(a,b){return parseInt(a,b||10)}function ea(a){return typeof a===\"string\"}function T(a){return typeof a===\r\n\"object\"}function Ia(a){return Object.prototype.toString.call(a)===\"[object Array]\"}function sa(a){return typeof a===\"number\"}function na(a){return R.log(a)/R.LN10}function fa(a){return R.pow(10,a)}function ga(a,b){for(var c=a.length;c--;)if(a[c]===b){a.splice(c,1);break}}function u(a){return a!==w&&a!==null}function v(a,b,c){var d,e;if(ea(b))u(c)?a.setAttribute(b,c):a&&a.getAttribute&&(e=a.getAttribute(b));else if(u(b)&&T(b))for(d in b)a.setAttribute(d,b[d]);return e}function ja(a){return Ia(a)?\r\na:[a]}function o(){var a=arguments,b,c,d=a.length;for(b=0;b<d;b++)if(c=a[b],typeof c!==\"undefined\"&&c!==null)return c}function K(a,b){if(ta&&b&&b.opacity!==w)b.filter=\"alpha(opacity=\"+b.opacity*100+\")\";r(a.style,b)}function U(a,b,c,d,e){a=y.createElement(a);b&&r(a,b);e&&K(a,{padding:0,border:S,margin:0});c&&K(a,c);d&&d.appendChild(a);return a}function ha(a,b){var c=function(){};c.prototype=new a;r(c.prototype,b);return c}function Aa(a,b,c,d){var e=M.lang,a=+a||0,f=b===-1?(a.toString().split(\".\")[1]||\r\n\"\").length:isNaN(b=N(b))?2:b,b=c===void 0?e.decimalPoint:c,d=d===void 0?e.thousandsSep:d,e=a<0?\"-\":\"\",c=String(C(a=N(a).toFixed(f))),g=c.length>3?c.length%3:0;return e+(g?c.substr(0,g)+d:\"\")+c.substr(g).replace(/(\\d{3})(?=\\d)/g,\"$1\"+d)+(f?b+N(a-c).toFixed(f).slice(2):\"\")}function Ba(a,b){return Array((b||2)+1-String(a).length).join(0)+a}function mb(a,b,c){var d=a[b];a[b]=function(){var a=Array.prototype.slice.call(arguments);a.unshift(d);return c.apply(this,a)}}function Ca(a,b){for(var c=\"{\",d=!1,\r\ne,f,g,h,i,j=[];(c=a.indexOf(c))!==-1;){e=a.slice(0,c);if(d){f=e.split(\":\");g=f.shift().split(\".\");i=g.length;e=b;for(h=0;h<i;h++)e=e[g[h]];if(f.length)f=f.join(\":\"),g=/\\.([0-9])/,h=M.lang,i=void 0,/f$/.test(f)?(i=(i=f.match(g))?i[1]:-1,e=Aa(e,i,h.decimalPoint,f.indexOf(\",\")>-1?h.thousandsSep:\"\")):e=Xa(f,e)}j.push(e);a=a.slice(c+1);c=(d=!d)?\"}\":\"{\"}j.push(a);return j.join(\"\")}function nb(a){return R.pow(10,P(R.log(a)/R.LN10))}function ob(a,b,c,d){var e,c=o(c,1);e=a/c;b||(b=[1,2,2.5,5,10],d&&d.allowDecimals===\r\n!1&&(c===1?b=[1,2,5,10]:c<=0.1&&(b=[1/c])));for(d=0;d<b.length;d++)if(a=b[d],e<=(b[d]+(b[d+1]||b[d]))/2)break;a*=c;return a}function Cb(a,b){var c=b||[[Db,[1,2,5,10,20,25,50,100,200,500]],[pb,[1,2,5,10,15,30]],[Ya,[1,2,5,10,15,30]],[Qa,[1,2,3,4,6,8,12]],[ua,[1,2]],[Za,[1,2]],[Ra,[1,2,3,4,6]],[Da,null]],d=c[c.length-1],e=D[d[0]],f=d[1],g;for(g=0;g<c.length;g++)if(d=c[g],e=D[d[0]],f=d[1],c[g+1]&&a<=(e*f[f.length-1]+D[c[g+1][0]])/2)break;e===D[Da]&&a<5*e&&(f=[1,2,5]);c=ob(a/e,f,d[0]===Da?nb(a/e):1);\r\nreturn{unitRange:e,count:c,unitName:d[0]}}function Eb(a,b,c,d){var e=[],f={},g=M.global.useUTC,h,i=new Date(b),j=a.unitRange,k=a.count;if(u(b)){j>=D[pb]&&(i.setMilliseconds(0),i.setSeconds(j>=D[Ya]?0:k*P(i.getSeconds()/k)));if(j>=D[Ya])i[Fb](j>=D[Qa]?0:k*P(i[qb]()/k));if(j>=D[Qa])i[Gb](j>=D[ua]?0:k*P(i[rb]()/k));if(j>=D[ua])i[sb](j>=D[Ra]?1:k*P(i[Sa]()/k));j>=D[Ra]&&(i[Hb](j>=D[Da]?0:k*P(i[$a]()/k)),h=i[ab]());j>=D[Da]&&(h-=h%k,i[Ib](h));if(j===D[Za])i[sb](i[Sa]()-i[tb]()+o(d,1));b=1;h=i[ab]();for(var d=\r\ni.getTime(),l=i[$a](),m=i[Sa](),p=g?0:(864E5+i.getTimezoneOffset()*6E4)%864E5;d<c;)e.push(d),j===D[Da]?d=bb(h+b*k,0):j===D[Ra]?d=bb(h,l+b*k):!g&&(j===D[ua]||j===D[Za])?d=bb(h,l,m+b*k*(j===D[ua]?1:7)):d+=j*k,b++;e.push(d);n(ub(e,function(a){return j<=D[Qa]&&a%D[ua]===p}),function(a){f[a]=ua})}e.info=r(a,{higherRanks:f,totalRange:j*k});return e}function Jb(){this.symbol=this.color=0}function Kb(a,b){var c=a.length,d,e;for(e=0;e<c;e++)a[e].ss_i=e;a.sort(function(a,c){d=b(a,c);return d===0?a.ss_i-c.ss_i:\r\nd});for(e=0;e<c;e++)delete a[e].ss_i}function Ja(a){for(var b=a.length,c=a[0];b--;)a[b]<c&&(c=a[b]);return c}function va(a){for(var b=a.length,c=a[0];b--;)a[b]>c&&(c=a[b]);return c}function Ka(a,b){for(var c in a)a[c]&&a[c]!==b&&a[c].destroy&&a[c].destroy(),delete a[c]}function Ta(a){cb||(cb=U(Ea));a&&cb.appendChild(a);cb.innerHTML=\"\"}function ka(a,b){var c=\"Highcharts error #\"+a+\": www.highcharts.com/errors/\"+a;if(b)throw c;else O.console&&console.log(c)}function ia(a){return parseFloat(a.toPrecision(14))}\r\nfunction La(a,b){Fa=o(a,b.animation)}function Lb(){var a=M.global.useUTC,b=a?\"getUTC\":\"get\",c=a?\"setUTC\":\"set\";bb=a?Date.UTC:function(a,b,c,g,h,i){return(new Date(a,b,o(c,1),o(g,0),o(h,0),o(i,0))).getTime()};qb=b+\"Minutes\";rb=b+\"Hours\";tb=b+\"Day\";Sa=b+\"Date\";$a=b+\"Month\";ab=b+\"FullYear\";Fb=c+\"Minutes\";Gb=c+\"Hours\";sb=c+\"Date\";Hb=c+\"Month\";Ib=c+\"FullYear\"}function wa(){}function Ma(a,b,c,d){this.axis=a;this.pos=b;this.type=c||\"\";this.isNew=!0;!c&&!d&&this.addLabel()}function vb(a,b){this.axis=a;if(b)this.options=\r\nb,this.id=b.id}function Mb(a,b,c,d,e,f){var g=a.chart.inverted;this.axis=a;this.isNegative=c;this.options=b;this.x=d;this.total=null;this.points={};this.stack=e;this.percent=f===\"percent\";this.alignOptions={align:b.align||(g?c?\"left\":\"right\":\"center\"),verticalAlign:b.verticalAlign||(g?\"middle\":c?\"bottom\":\"top\"),y:o(b.y,g?4:c?14:-6),x:o(b.x,g?c?-6:6:0)};this.textAlign=b.textAlign||(g?c?\"right\":\"left\":\"center\")}function db(){this.init.apply(this,arguments)}function wb(){this.init.apply(this,arguments)}\r\nfunction xb(a,b){this.init(a,b)}function eb(a,b){this.init(a,b)}function yb(){this.init.apply(this,arguments)}var w,y=document,O=window,R=Math,t=R.round,P=R.floor,xa=R.ceil,s=R.max,I=R.min,N=R.abs,V=R.cos,ca=R.sin,ya=R.PI,Ua=ya*2/360,oa=navigator.userAgent,Nb=O.opera,ta=/msie/i.test(oa)&&!Nb,fb=y.documentMode===8,gb=/AppleWebKit/.test(oa),hb=/Firefox/.test(oa),Ob=/(Mobile|Android|Windows Phone)/.test(oa),za=\"http://www.w3.org/2000/svg\",Z=!!y.createElementNS&&!!y.createElementNS(za,\"svg\").createSVGRect,\r\nUb=hb&&parseInt(oa.split(\"Firefox/\")[1],10)<4,$=!Z&&!ta&&!!y.createElement(\"canvas\").getContext,Va,ib=y.documentElement.ontouchstart!==w,Pb={},zb=0,cb,M,Xa,Fa,Ab,D,pa=function(){},Ga=[],Ea=\"div\",S=\"none\",Qb=\"rgba(192,192,192,\"+(Z?1.0E-4:0.002)+\")\",Db=\"millisecond\",pb=\"second\",Ya=\"minute\",Qa=\"hour\",ua=\"day\",Za=\"week\",Ra=\"month\",Da=\"year\",Rb=\"stroke-width\",bb,qb,rb,tb,Sa,$a,ab,Fb,Gb,sb,Hb,Ib,W={};O.Highcharts=O.Highcharts?ka(16,!0):{};Xa=function(a,b,c){if(!u(b)||isNaN(b))return\"Invalid date\";var a=\r\no(a,\"%Y-%m-%d %H:%M:%S\"),d=new Date(b),e,f=d[rb](),g=d[tb](),h=d[Sa](),i=d[$a](),j=d[ab](),k=M.lang,l=k.weekdays,d=r({a:l[g].substr(0,3),A:l[g],d:Ba(h),e:h,b:k.shortMonths[i],B:k.months[i],m:Ba(i+1),y:j.toString().substr(2,2),Y:j,H:Ba(f),I:Ba(f%12||12),l:f%12||12,M:Ba(d[qb]()),p:f<12?\"AM\":\"PM\",P:f<12?\"am\":\"pm\",S:Ba(d.getSeconds()),L:Ba(t(b%1E3),3)},Highcharts.dateFormats);for(e in d)for(;a.indexOf(\"%\"+e)!==-1;)a=a.replace(\"%\"+e,typeof d[e]===\"function\"?d[e](b):d[e]);return c?a.substr(0,1).toUpperCase()+\r\na.substr(1):a};Jb.prototype={wrapColor:function(a){if(this.color>=a)this.color=0},wrapSymbol:function(a){if(this.symbol>=a)this.symbol=0}};D=function(){for(var a=0,b=arguments,c=b.length,d={};a<c;a++)d[b[a++]]=b[a];return d}(Db,1,pb,1E3,Ya,6E4,Qa,36E5,ua,864E5,Za,6048E5,Ra,26784E5,Da,31556952E3);Ab={init:function(a,b,c){var b=b||\"\",d=a.shift,e=b.indexOf(\"C\")>-1,f=e?7:3,g,b=b.split(\" \"),c=[].concat(c),h,i,j=function(a){for(g=a.length;g--;)a[g]===\"M\"&&a.splice(g+1,0,a[g+1],a[g+2],a[g+1],a[g+2])};e&&\r\n(j(b),j(c));a.isArea&&(h=b.splice(b.length-6,6),i=c.splice(c.length-6,6));if(d<=c.length/f&&b.length===c.length)for(;d--;)c=[].concat(c).splice(0,f).concat(c);a.shift=0;if(b.length)for(a=c.length;b.length<a;)d=[].concat(b).splice(b.length-f,f),e&&(d[f-6]=d[f-2],d[f-5]=d[f-1]),b=b.concat(d);h&&(b=b.concat(h),c=c.concat(i));return[b,c]},step:function(a,b,c,d){var e=[],f=a.length;if(c===1)e=d;else if(f===b.length&&c<1)for(;f--;)d=parseFloat(a[f]),e[f]=isNaN(d)?a[f]:c*parseFloat(b[f]-d)+d;else e=b;return e}};\r\n(function(a){O.HighchartsAdapter=O.HighchartsAdapter||a&&{init:function(b){var c=a.fx,d=c.step,e,f=a.Tween,g=f&&f.propHooks;e=a.cssHooks.opacity;a.extend(a.easing,{easeOutQuad:function(a,b,c,d,e){return-d*(b/=e)*(b-2)+c}});a.each([\"cur\",\"_default\",\"width\",\"height\",\"opacity\"],function(a,b){var e=d,k,l;b===\"cur\"?e=c.prototype:b===\"_default\"&&f&&(e=g[b],b=\"set\");(k=e[b])&&(e[b]=function(c){c=a?c:this;if(c.prop!==\"align\")return l=c.elem,l.attr?l.attr(c.prop,b===\"cur\"?w:c.now):k.apply(this,arguments)})});\r\nmb(e,\"get\",function(a,b,c){return b.attr?b.opacity||0:a.call(this,b,c)});e=function(a){var c=a.elem,d;if(!a.started)d=b.init(c,c.d,c.toD),a.start=d[0],a.end=d[1],a.started=!0;c.attr(\"d\",b.step(a.start,a.end,a.pos,c.toD))};f?g.d={set:e}:d.d=e;this.each=Array.prototype.forEach?function(a,b){return Array.prototype.forEach.call(a,b)}:function(a,b){for(var c=0,d=a.length;c<d;c++)if(b.call(a[c],a[c],c,a)===!1)return c};a.fn.highcharts=function(){var a=\"Chart\",b=arguments,c,d;ea(b[0])&&(a=b[0],b=Array.prototype.slice.call(b,\r\n1));c=b[0];if(c!==w)c.chart=c.chart||{},c.chart.renderTo=this[0],new Highcharts[a](c,b[1]),d=this;c===w&&(d=Ga[v(this[0],\"data-highcharts-chart\")]);return d}},getScript:a.getScript,inArray:a.inArray,adapterRun:function(b,c){return a(b)[c]()},grep:a.grep,map:function(a,c){for(var d=[],e=0,f=a.length;e<f;e++)d[e]=c.call(a[e],a[e],e,a);return d},offset:function(b){return a(b).offset()},addEvent:function(b,c,d){a(b).bind(c,d)},removeEvent:function(b,c,d){var e=y.removeEventListener?\"removeEventListener\":\r\n\"detachEvent\";y[e]&&b&&!b[e]&&(b[e]=function(){});a(b).unbind(c,d)},fireEvent:function(b,c,d,e){var f=a.Event(c),g=\"detached\"+c,h;!ta&&d&&(delete d.layerX,delete d.layerY);r(f,d);b[c]&&(b[g]=b[c],b[c]=null);a.each([\"preventDefault\",\"stopPropagation\"],function(a,b){var c=f[b];f[b]=function(){try{c.call(f)}catch(a){b===\"preventDefault\"&&(h=!0)}}});a(b).trigger(f);b[g]&&(b[c]=b[g],b[g]=null);e&&!f.isDefaultPrevented()&&!h&&e(f)},washMouseEvent:function(a){var c=a.originalEvent||a;if(c.pageX===w)c.pageX=\r\na.pageX,c.pageY=a.pageY;return c},animate:function(b,c,d){var e=a(b);if(!b.style)b.style={};if(c.d)b.toD=c.d,c.d=1;e.stop();c.opacity!==w&&b.attr&&(c.opacity+=\"px\");e.animate(c,d)},stop:function(b){a(b).stop()}}})(O.jQuery);var X=O.HighchartsAdapter,G=X||{};X&&X.init.call(X,Ab);var jb=G.adapterRun,Vb=G.getScript,qa=G.inArray,n=G.each,ub=G.grep,Wb=G.offset,Na=G.map,J=G.addEvent,aa=G.removeEvent,z=G.fireEvent,Xb=G.washMouseEvent,Bb=G.animate,Wa=G.stop,G={enabled:!0,x:0,y:15,style:{color:\"#666\",cursor:\"default\",\r\nfontSize:\"11px\",lineHeight:\"14px\"}};M={colors:\"#2f7ed8,#0d233a,#8bbc21,#910000,#1aadce,#492970,#f28f43,#77a1e5,#c42525,#a6c96a\".split(\",\"),symbols:[\"circle\",\"diamond\",\"square\",\"triangle\",\"triangle-down\"],lang:{loading:\"Loading...\",months:\"January,February,March,April,May,June,July,August,September,October,November,December\".split(\",\"),shortMonths:\"Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec\".split(\",\"),weekdays:\"Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday\".split(\",\"),decimalPoint:\".\",\r\nnumericSymbols:\"k,M,G,T,P,E\".split(\",\"),resetZoom:\"Reset zoom\",resetZoomTitle:\"Reset zoom level 1:1\",thousandsSep:\",\"},global:{useUTC:!0,canvasToolsURL:\"http://code.highcharts.com/3.0.6/modules/canvas-tools.js\",VMLRadialGradientURL:\"http://code.highcharts.com/3.0.6/gfx/vml-radial-gradient.png\"},chart:{borderColor:\"#4572A7\",borderRadius:5,defaultSeriesType:\"line\",ignoreHiddenSeries:!0,spacing:[10,10,15,10],style:{fontFamily:'\"Lucida Grande\", \"Lucida Sans Unicode\", Verdana, Arial, Helvetica, sans-serif',\r\nfontSize:\"12px\"},backgroundColor:\"#FFFFFF\",plotBorderColor:\"#C0C0C0\",resetZoomButton:{theme:{zIndex:20},position:{align:\"right\",x:-10,y:10}}},title:{text:\"Chart title\",align:\"center\",margin:15,style:{color:\"#274b6d\",fontSize:\"16px\"}},subtitle:{text:\"\",align:\"center\",style:{color:\"#4d759e\"}},plotOptions:{line:{allowPointSelect:!1,showCheckbox:!1,animation:{duration:1E3},events:{},lineWidth:2,marker:{enabled:!0,lineWidth:0,radius:4,lineColor:\"#FFFFFF\",states:{hover:{enabled:!0},select:{fillColor:\"#FFFFFF\",\r\nlineColor:\"#000000\",lineWidth:2}}},point:{events:{}},dataLabels:x(G,{align:\"center\",enabled:!1,formatter:function(){return this.y===null?\"\":Aa(this.y,-1)},verticalAlign:\"bottom\",y:0}),cropThreshold:300,pointRange:0,showInLegend:!0,states:{hover:{marker:{}},select:{marker:{}}},stickyTracking:!0}},labels:{style:{position:\"absolute\",color:\"#3E576F\"}},legend:{enabled:!0,align:\"center\",layout:\"horizontal\",labelFormatter:function(){return this.name},borderWidth:1,borderColor:\"#909090\",borderRadius:5,navigation:{activeColor:\"#274b6d\",\r\ninactiveColor:\"#CCC\"},shadow:!1,itemStyle:{cursor:\"pointer\",color:\"#274b6d\",fontSize:\"12px\"},itemHoverStyle:{color:\"#000\"},itemHiddenStyle:{color:\"#CCC\"},itemCheckboxStyle:{position:\"absolute\",width:\"13px\",height:\"13px\"},symbolWidth:16,symbolPadding:5,verticalAlign:\"bottom\",x:0,y:0,title:{style:{fontWeight:\"bold\"}}},loading:{labelStyle:{fontWeight:\"bold\",position:\"relative\",top:\"1em\"},style:{position:\"absolute\",backgroundColor:\"white\",opacity:0.5,textAlign:\"center\"}},tooltip:{enabled:!0,animation:Z,\r\nbackgroundColor:\"rgba(255, 255, 255, .85)\",borderWidth:1,borderRadius:3,dateTimeLabelFormats:{millisecond:\"%A, %b %e, %H:%M:%S.%L\",second:\"%A, %b %e, %H:%M:%S\",minute:\"%A, %b %e, %H:%M\",hour:\"%A, %b %e, %H:%M\",day:\"%A, %b %e, %Y\",week:\"Week from %A, %b %e, %Y\",month:\"%B %Y\",year:\"%Y\"},headerFormat:'<span style=\"font-size: 10px\">{point.key}</span><br/>',pointFormat:'<span style=\"color:{series.color}\">{series.name}</span>: <b>{point.y}</b><br/>',shadow:!0,snap:Ob?25:10,style:{color:\"#333333\",cursor:\"default\",\r\nfontSize:\"12px\",padding:\"8px\",whiteSpace:\"nowrap\"}},credits:{enabled:!0,text:\"Highcharts.com\",href:\"http://www.highcharts.com\",position:{align:\"right\",x:-10,verticalAlign:\"bottom\",y:-5},style:{cursor:\"pointer\",color:\"#909090\",fontSize:\"9px\"}}};var Y=M.plotOptions,X=Y.line;Lb();var ra=function(a){var b=[],c,d;(function(a){a&&a.stops?d=Na(a.stops,function(a){return ra(a[1])}):(c=/rgba\\(\\s*([0-9]{1,3})\\s*,\\s*([0-9]{1,3})\\s*,\\s*([0-9]{1,3})\\s*,\\s*([0-9]?(?:\\.[0-9]+)?)\\s*\\)/.exec(a))?b=[C(c[1]),C(c[2]),\r\nC(c[3]),parseFloat(c[4],10)]:(c=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(a))?b=[C(c[1],16),C(c[2],16),C(c[3],16),1]:(c=/rgb\\(\\s*([0-9]{1,3})\\s*,\\s*([0-9]{1,3})\\s*,\\s*([0-9]{1,3})\\s*\\)/.exec(a))&&(b=[C(c[1]),C(c[2]),C(c[3]),1])})(a);return{get:function(c){var f;d?(f=x(a),f.stops=[].concat(f.stops),n(d,function(a,b){f.stops[b]=[f.stops[b][0],a.get(c)]})):f=b&&!isNaN(b[0])?c===\"rgb\"?\"rgb(\"+b[0]+\",\"+b[1]+\",\"+b[2]+\")\":c===\"a\"?b[3]:\"rgba(\"+b.join(\",\")+\")\":a;return f},brighten:function(a){if(d)n(d,\r\nfunction(b){b.brighten(a)});else if(sa(a)&&a!==0){var c;for(c=0;c<3;c++)b[c]+=C(a*255),b[c]<0&&(b[c]=0),b[c]>255&&(b[c]=255)}return this},rgba:b,setOpacity:function(a){b[3]=a;return this}}};wa.prototype={init:function(a,b){this.element=b===\"span\"?U(b):y.createElementNS(za,b);this.renderer=a;this.attrSetters={}},opacity:1,animate:function(a,b,c){b=o(b,Fa,!0);Wa(this);if(b){b=x(b);if(c)b.complete=c;Bb(this,a,b)}else this.attr(a),c&&c()},attr:function(a,b){var c,d,e,f,g=this.element,h=g.nodeName.toLowerCase(),\r\ni=this.renderer,j,k=this.attrSetters,l=this.shadows,m,p,q=this;ea(a)&&u(b)&&(c=a,a={},a[c]=b);if(ea(a))c=a,h===\"circle\"?c={x:\"cx\",y:\"cy\"}[c]||c:c===\"strokeWidth\"&&(c=\"stroke-width\"),q=v(g,c)||this[c]||0,c!==\"d\"&&c!==\"visibility\"&&c!==\"fill\"&&(q=parseFloat(q));else{for(c in a)if(j=!1,d=a[c],e=k[c]&&k[c].call(this,d,c),e!==!1){e!==w&&(d=e);if(c===\"d\")d&&d.join&&(d=d.join(\" \")),/(NaN| {2}|^$)/.test(d)&&(d=\"M 0 0\");else if(c===\"x\"&&h===\"text\")for(e=0;e<g.childNodes.length;e++)f=g.childNodes[e],v(f,\"x\")===\r\nv(g,\"x\")&&v(f,\"x\",d);else if(this.rotation&&(c===\"x\"||c===\"y\"))p=!0;else if(c===\"fill\")d=i.color(d,g,c);else if(h===\"circle\"&&(c===\"x\"||c===\"y\"))c={x:\"cx\",y:\"cy\"}[c]||c;else if(h===\"rect\"&&c===\"r\")v(g,{rx:d,ry:d}),j=!0;else if(c===\"translateX\"||c===\"translateY\"||c===\"rotation\"||c===\"verticalAlign\"||c===\"scaleX\"||c===\"scaleY\")j=p=!0;else if(c===\"stroke\")d=i.color(d,g,c);else if(c===\"dashstyle\")if(c=\"stroke-dasharray\",d=d&&d.toLowerCase(),d===\"solid\")d=S;else{if(d){d=d.replace(\"shortdashdotdot\",\"3,1,1,1,1,1,\").replace(\"shortdashdot\",\r\n\"3,1,1,1\").replace(\"shortdot\",\"1,1,\").replace(\"shortdash\",\"3,1,\").replace(\"longdash\",\"8,3,\").replace(/dot/g,\"1,3,\").replace(\"dash\",\"4,3,\").replace(/,$/,\"\").split(\",\");for(e=d.length;e--;)d[e]=C(d[e])*o(a[\"stroke-width\"],this[\"stroke-width\"]);d=d.join(\",\")}}else if(c===\"width\")d=C(d);else if(c===\"align\")c=\"text-anchor\",d={left:\"start\",center:\"middle\",right:\"end\"}[d];else if(c===\"title\")e=g.getElementsByTagName(\"title\")[0],e||(e=y.createElementNS(za,\"title\"),g.appendChild(e)),e.textContent=d;c===\"strokeWidth\"&&\r\n(c=\"stroke-width\");if(c===\"stroke-width\"||c===\"stroke\"){this[c]=d;if(this.stroke&&this[\"stroke-width\"])v(g,\"stroke\",this.stroke),v(g,\"stroke-width\",this[\"stroke-width\"]),this.hasStroke=!0;else if(c===\"stroke-width\"&&d===0&&this.hasStroke)g.removeAttribute(\"stroke\"),this.hasStroke=!1;j=!0}this.symbolName&&/^(x|y|width|height|r|start|end|innerR|anchorX|anchorY)/.test(c)&&(m||(this.symbolAttr(a),m=!0),j=!0);if(l&&/^(width|height|visibility|x|y|d|transform|cx|cy|r)$/.test(c))for(e=l.length;e--;)v(l[e],\r\nc,c===\"height\"?s(d-(l[e].cutHeight||0),0):d);if((c===\"width\"||c===\"height\")&&h===\"rect\"&&d<0)d=0;this[c]=d;c===\"text\"?(d!==this.textStr&&delete this.bBox,this.textStr=d,this.added&&i.buildText(this)):j||v(g,c,d)}p&&this.updateTransform()}return q},addClass:function(a){var b=this.element,c=v(b,\"class\")||\"\";c.indexOf(a)===-1&&v(b,\"class\",c+\" \"+a);return this},symbolAttr:function(a){var b=this;n(\"x,y,r,start,end,width,height,innerR,anchorX,anchorY\".split(\",\"),function(c){b[c]=o(a[c],b[c])});b.attr({d:b.renderer.symbols[b.symbolName](b.x,\r\nb.y,b.width,b.height,b)})},clip:function(a){return this.attr(\"clip-path\",a?\"url(\"+this.renderer.url+\"#\"+a.id+\")\":S)},crisp:function(a,b,c,d,e){var f,g={},h={},i,a=a||this.strokeWidth||this.attr&&this.attr(\"stroke-width\")||0;i=t(a)%2/2;h.x=P(b||this.x||0)+i;h.y=P(c||this.y||0)+i;h.width=P((d||this.width||0)-2*i);h.height=P((e||this.height||0)-2*i);h.strokeWidth=a;for(f in h)this[f]!==h[f]&&(this[f]=g[f]=h[f]);return g},css:function(a){var b=this.element,c=a&&a.width&&b.nodeName.toLowerCase()===\"text\",\r\nd,e=\"\",f=function(a,b){return\"-\"+b.toLowerCase()};if(a&&a.color)a.fill=a.color;this.styles=a=r(this.styles,a);$&&c&&delete a.width;if(ta&&!Z)c&&delete a.width,K(this.element,a);else{for(d in a)e+=d.replace(/([A-Z])/g,f)+\":\"+a[d]+\";\";v(b,\"style\",e)}c&&this.added&&this.renderer.buildText(this);return this},on:function(a,b){var c=this,d=c.element;ib&&a===\"click\"?(d.ontouchstart=function(a){c.touchEventFired=Date.now();a.preventDefault();b.call(d,a)},d.onclick=function(a){(oa.indexOf(\"Android\")===-1||\r\nDate.now()-(c.touchEventFired||0)>1100)&&b.call(d,a)}):d[\"on\"+a]=b;return this},setRadialReference:function(a){this.element.radialReference=a;return this},translate:function(a,b){return this.attr({translateX:a,translateY:b})},invert:function(){this.inverted=!0;this.updateTransform();return this},htmlCss:function(a){var b=this.element;if(b=a&&b.tagName===\"SPAN\"&&a.width)delete a.width,this.textWidth=b,this.updateTransform();this.styles=r(this.styles,a);K(this.element,a);return this},htmlGetBBox:function(){var a=\r\nthis.element,b=this.bBox;if(!b){if(a.nodeName===\"text\")a.style.position=\"absolute\";b=this.bBox={x:a.offsetLeft,y:a.offsetTop,width:a.offsetWidth,height:a.offsetHeight}}return b},htmlUpdateTransform:function(){if(this.added){var a=this.renderer,b=this.element,c=this.translateX||0,d=this.translateY||0,e=this.x||0,f=this.y||0,g=this.textAlign||\"left\",h={left:0,center:0.5,right:1}[g],i=g&&g!==\"left\",j=this.shadows;K(b,{marginLeft:c,marginTop:d});j&&n(j,function(a){K(a,{marginLeft:c+1,marginTop:d+1})});\r\nthis.inverted&&n(b.childNodes,function(c){a.invertChild(c,b)});if(b.tagName===\"SPAN\"){var k,l,j=this.rotation,m;k=0;var p=1,q=0,ba;m=C(this.textWidth);var A=this.xCorr||0,L=this.yCorr||0,Sb=[j,g,b.innerHTML,this.textWidth].join(\",\");if(Sb!==this.cTT){u(j)&&(k=j*Ua,p=V(k),q=ca(k),this.setSpanRotation(j,q,p));k=o(this.elemWidth,b.offsetWidth);l=o(this.elemHeight,b.offsetHeight);if(k>m&&/[ \\-]/.test(b.textContent||b.innerText))K(b,{width:m+\"px\",display:\"block\",whiteSpace:\"normal\"}),k=m;m=a.fontMetrics(b.style.fontSize).b;\r\nA=p<0&&-k;L=q<0&&-l;ba=p*q<0;A+=q*m*(ba?1-h:h);L-=p*m*(j?ba?h:1-h:1);i&&(A-=k*h*(p<0?-1:1),j&&(L-=l*h*(q<0?-1:1)),K(b,{textAlign:g}));this.xCorr=A;this.yCorr=L}K(b,{left:e+A+\"px\",top:f+L+\"px\"});if(gb)l=b.offsetHeight;this.cTT=Sb}}else this.alignOnAdd=!0},setSpanRotation:function(a){var b={};b[ta?\"-ms-transform\":gb?\"-webkit-transform\":hb?\"MozTransform\":Nb?\"-o-transform\":\"\"]=b.transform=\"rotate(\"+a+\"deg)\";K(this.element,b)},updateTransform:function(){var a=this.translateX||0,b=this.translateY||0,c=\r\nthis.scaleX,d=this.scaleY,e=this.inverted,f=this.rotation;e&&(a+=this.attr(\"width\"),b+=this.attr(\"height\"));a=[\"translate(\"+a+\",\"+b+\")\"];e?a.push(\"rotate(90) scale(-1,1)\"):f&&a.push(\"rotate(\"+f+\" \"+(this.x||0)+\" \"+(this.y||0)+\")\");(u(c)||u(d))&&a.push(\"scale(\"+o(c,1)+\" \"+o(d,1)+\")\");a.length&&v(this.element,\"transform\",a.join(\" \"))},toFront:function(){var a=this.element;a.parentNode.appendChild(a);return this},align:function(a,b,c){var d,e,f,g,h={};e=this.renderer;f=e.alignedObjects;if(a){if(this.alignOptions=\r\na,this.alignByTranslate=b,!c||ea(c))this.alignTo=d=c||\"renderer\",ga(f,this),f.push(this),c=null}else a=this.alignOptions,b=this.alignByTranslate,d=this.alignTo;c=o(c,e[d],e);d=a.align;e=a.verticalAlign;f=(c.x||0)+(a.x||0);g=(c.y||0)+(a.y||0);if(d===\"right\"||d===\"center\")f+=(c.width-(a.width||0))/{right:1,center:2}[d];h[b?\"translateX\":\"x\"]=t(f);if(e===\"bottom\"||e===\"middle\")g+=(c.height-(a.height||0))/({bottom:1,middle:2}[e]||1);h[b?\"translateY\":\"y\"]=t(g);this[this.placed?\"animate\":\"attr\"](h);this.placed=\r\n!0;this.alignAttr=h;return this},getBBox:function(){var a=this.bBox,b=this.renderer,c,d=this.rotation;c=this.element;var e=this.styles,f=d*Ua;if(!a){if(c.namespaceURI===za||b.forExport){try{a=c.getBBox?r({},c.getBBox()):{width:c.offsetWidth,height:c.offsetHeight}}catch(g){}if(!a||a.width<0)a={width:0,height:0}}else a=this.htmlGetBBox();if(b.isSVG){b=a.width;c=a.height;if(ta&&e&&e.fontSize===\"11px\"&&c.toPrecision(3)===\"22.7\")a.height=c=14;if(d)a.width=N(c*ca(f))+N(b*V(f)),a.height=N(c*V(f))+N(b*ca(f))}this.bBox=\r\na}return a},show:function(){return this.attr({visibility:\"visible\"})},hide:function(){return this.attr({visibility:\"hidden\"})},fadeOut:function(a){var b=this;b.animate({opacity:0},{duration:a||150,complete:function(){b.hide()}})},add:function(a){var b=this.renderer,c=a||b,d=c.element||b.box,e=d.childNodes,f=this.element,g=v(f,\"zIndex\"),h;if(a)this.parentGroup=a;this.parentInverted=a&&a.inverted;this.textStr!==void 0&&b.buildText(this);if(g)c.handleZ=!0,g=C(g);if(c.handleZ)for(c=0;c<e.length;c++)if(a=\r\ne[c],b=v(a,\"zIndex\"),a!==f&&(C(b)>g||!u(g)&&u(b))){d.insertBefore(f,a);h=!0;break}h||d.appendChild(f);this.added=!0;z(this,\"add\");return this},safeRemoveChild:function(a){var b=a.parentNode;b&&b.removeChild(a)},destroy:function(){var a=this,b=a.element||{},c=a.shadows,d=a.renderer.isSVG&&b.nodeName===\"SPAN\"&&b.parentNode,e,f;b.onclick=b.onmouseout=b.onmouseover=b.onmousemove=b.point=null;Wa(a);if(a.clipPath)a.clipPath=a.clipPath.destroy();if(a.stops){for(f=0;f<a.stops.length;f++)a.stops[f]=a.stops[f].destroy();\r\na.stops=null}a.safeRemoveChild(b);for(c&&n(c,function(b){a.safeRemoveChild(b)});d&&d.childNodes.length===0;)b=d.parentNode,a.safeRemoveChild(d),d=b;a.alignTo&&ga(a.renderer.alignedObjects,a);for(e in a)delete a[e];return null},shadow:function(a,b,c){var d=[],e,f,g=this.element,h,i,j,k;if(a){i=o(a.width,3);j=(a.opacity||0.15)/i;k=this.parentInverted?\"(-1,-1)\":\"(\"+o(a.offsetX,1)+\", \"+o(a.offsetY,1)+\")\";for(e=1;e<=i;e++){f=g.cloneNode(0);h=i*2+1-2*e;v(f,{isShadow:\"true\",stroke:a.color||\"black\",\"stroke-opacity\":j*\r\ne,\"stroke-width\":h,transform:\"translate\"+k,fill:S});if(c)v(f,\"height\",s(v(f,\"height\")-h,0)),f.cutHeight=h;b?b.element.appendChild(f):g.parentNode.insertBefore(f,g);d.push(f)}this.shadows=d}return this}};var Ha=function(){this.init.apply(this,arguments)};Ha.prototype={Element:wa,init:function(a,b,c,d){var e=location,f,g;f=this.createElement(\"svg\").attr({version:\"1.1\"});g=f.element;a.appendChild(g);a.innerHTML.indexOf(\"xmlns\")===-1&&v(g,\"xmlns\",za);this.isSVG=!0;this.box=g;this.boxWrapper=f;this.alignedObjects=\r\n[];this.url=(hb||gb)&&y.getElementsByTagName(\"base\").length?e.href.replace(/#.*?$/,\"\").replace(/([\\('\\)])/g,\"\\\\$1\").replace(/ /g,\"%20\"):\"\";this.createElement(\"desc\").add().element.appendChild(y.createTextNode(\"Created with Highcharts 3.0.6\"));this.defs=this.createElement(\"defs\").add();this.forExport=d;this.gradients={};this.setSize(b,c,!1);var h;if(hb&&a.getBoundingClientRect)this.subPixelFix=b=function(){K(a,{left:0,top:0});h=a.getBoundingClientRect();K(a,{left:xa(h.left)-h.left+\"px\",top:xa(h.top)-\r\nh.top+\"px\"})},b(),J(O,\"resize\",b)},isHidden:function(){return!this.boxWrapper.getBBox().width},destroy:function(){var a=this.defs;this.box=null;this.boxWrapper=this.boxWrapper.destroy();Ka(this.gradients||{});this.gradients=null;if(a)this.defs=a.destroy();this.subPixelFix&&aa(O,\"resize\",this.subPixelFix);return this.alignedObjects=null},createElement:function(a){var b=new this.Element;b.init(this,a);return b},draw:function(){},buildText:function(a){for(var b=a.element,c=this,d=c.forExport,e=o(a.textStr,\r\n\"\").toString().replace(/<(b|strong)>/g,'<span style=\"font-weight:bold\">').replace(/<(i|em)>/g,'<span style=\"font-style:italic\">').replace(/<a/g,\"<span\").replace(/<\\/(b|strong|i|em|a)>/g,\"</span>\").split(/<br.*?>/g),f=b.childNodes,g=/style=\"([^\"]+)\"/,h=/href=\"(http[^\"]+)\"/,i=v(b,\"x\"),j=a.styles,k=j&&j.width&&C(j.width),l=j&&j.lineHeight,m=f.length;m--;)b.removeChild(f[m]);k&&!a.added&&this.box.appendChild(b);e[e.length-1]===\"\"&&e.pop();n(e,function(e,f){var m,o=0,e=e.replace(/<span/g,\"|||<span\").replace(/<\\/span>/g,\r\n\"</span>|||\");m=e.split(\"|||\");n(m,function(e){if(e!==\"\"||m.length===1){var p={},n=y.createElementNS(za,\"tspan\"),s;g.test(e)&&(s=e.match(g)[1].replace(/(;| |^)color([ :])/,\"$1fill$2\"),v(n,\"style\",s));h.test(e)&&!d&&(v(n,\"onclick\",'location.href=\"'+e.match(h)[1]+'\"'),K(n,{cursor:\"pointer\"}));e=(e.replace(/<(.|\\n)*?>/g,\"\")||\" \").replace(/&lt;/g,\"<\").replace(/&gt;/g,\">\");if(e!==\" \"&&(n.appendChild(y.createTextNode(e)),o?p.dx=0:p.x=i,v(n,p),!o&&f&&(!Z&&d&&K(n,{display:\"block\"}),v(n,\"dy\",l||c.fontMetrics(/px$/.test(n.style.fontSize)?\r\nn.style.fontSize:j.fontSize).h,gb&&n.offsetHeight)),b.appendChild(n),o++,k))for(var e=e.replace(/([^\\^])-/g,\"$1- \").split(\" \"),u,t,p=a._clipHeight,E=[],w=C(l||16),B=1;e.length||E.length;)delete a.bBox,u=a.getBBox(),t=u.width,u=t>k,!u||e.length===1?(e=E,E=[],e.length&&(B++,p&&B*w>p?(e=[\"...\"],a.attr(\"title\",a.textStr)):(n=y.createElementNS(za,\"tspan\"),v(n,{dy:w,x:i}),s&&v(n,\"style\",s),b.appendChild(n),t>k&&(k=t)))):(n.removeChild(n.firstChild),E.unshift(e.pop())),e.length&&n.appendChild(y.createTextNode(e.join(\" \").replace(/- /g,\r\n\"-\")))}})})},button:function(a,b,c,d,e,f,g,h){var i=this.label(a,b,c,null,null,null,null,null,\"button\"),j=0,k,l,m,p,q,n,a={x1:0,y1:0,x2:0,y2:1},e=x({\"stroke-width\":1,stroke:\"#CCCCCC\",fill:{linearGradient:a,stops:[[0,\"#FEFEFE\"],[1,\"#F6F6F6\"]]},r:2,padding:5,style:{color:\"black\"}},e);m=e.style;delete e.style;f=x(e,{stroke:\"#68A\",fill:{linearGradient:a,stops:[[0,\"#FFF\"],[1,\"#ACF\"]]}},f);p=f.style;delete f.style;g=x(e,{stroke:\"#68A\",fill:{linearGradient:a,stops:[[0,\"#9BD\"],[1,\"#CDF\"]]}},g);q=g.style;\r\ndelete g.style;h=x(e,{style:{color:\"#CCC\"}},h);n=h.style;delete h.style;J(i.element,ta?\"mouseover\":\"mouseenter\",function(){j!==3&&i.attr(f).css(p)});J(i.element,ta?\"mouseout\":\"mouseleave\",function(){j!==3&&(k=[e,f,g][j],l=[m,p,q][j],i.attr(k).css(l))});i.setState=function(a){(i.state=j=a)?a===2?i.attr(g).css(q):a===3&&i.attr(h).css(n):i.attr(e).css(m)};return i.on(\"click\",function(){j!==3&&d.call(i)}).attr(e).css(r({cursor:\"default\"},m))},crispLine:function(a,b){a[1]===a[4]&&(a[1]=a[4]=t(a[1])-b%\r\n2/2);a[2]===a[5]&&(a[2]=a[5]=t(a[2])+b%2/2);return a},path:function(a){var b={fill:S};Ia(a)?b.d=a:T(a)&&r(b,a);return this.createElement(\"path\").attr(b)},circle:function(a,b,c){a=T(a)?a:{x:a,y:b,r:c};return this.createElement(\"circle\").attr(a)},arc:function(a,b,c,d,e,f){if(T(a))b=a.y,c=a.r,d=a.innerR,e=a.start,f=a.end,a=a.x;a=this.symbol(\"arc\",a||0,b||0,c||0,c||0,{innerR:d||0,start:e||0,end:f||0});a.r=c;return a},rect:function(a,b,c,d,e,f){e=T(a)?a.r:e;e=this.createElement(\"rect\").attr({rx:e,ry:e,\r\nfill:S});return e.attr(T(a)?a:e.crisp(f,a,b,s(c,0),s(d,0)))},setSize:function(a,b,c){var d=this.alignedObjects,e=d.length;this.width=a;this.height=b;for(this.boxWrapper[o(c,!0)?\"animate\":\"attr\"]({width:a,height:b});e--;)d[e].align()},g:function(a){var b=this.createElement(\"g\");return u(a)?b.attr({\"class\":\"highcharts-\"+a}):b},image:function(a,b,c,d,e){var f={preserveAspectRatio:S};arguments.length>1&&r(f,{x:b,y:c,width:d,height:e});f=this.createElement(\"image\").attr(f);f.element.setAttributeNS?f.element.setAttributeNS(\"http://www.w3.org/1999/xlink\",\r\n\"href\",a):f.element.setAttribute(\"hc-svg-href\",a);return f},symbol:function(a,b,c,d,e,f){var g,h=this.symbols[a],h=h&&h(t(b),t(c),d,e,f),i=/^url\\((.*?)\\)$/,j,k;if(h)g=this.path(h),r(g,{symbolName:a,x:b,y:c,width:d,height:e}),f&&r(g,f);else if(i.test(a))k=function(a,b){a.element&&(a.attr({width:b[0],height:b[1]}),a.alignByTranslate||a.translate(t((d-b[0])/2),t((e-b[1])/2)))},j=a.match(i)[1],a=Pb[j],g=this.image(j).attr({x:b,y:c}),g.isImg=!0,a?k(g,a):(g.attr({width:0,height:0}),U(\"img\",{onload:function(){k(g,\r\nPb[j]=[this.width,this.height])},src:j}));return g},symbols:{circle:function(a,b,c,d){var e=0.166*c;return[\"M\",a+c/2,b,\"C\",a+c+e,b,a+c+e,b+d,a+c/2,b+d,\"C\",a-e,b+d,a-e,b,a+c/2,b,\"Z\"]},square:function(a,b,c,d){return[\"M\",a,b,\"L\",a+c,b,a+c,b+d,a,b+d,\"Z\"]},triangle:function(a,b,c,d){return[\"M\",a+c/2,b,\"L\",a+c,b+d,a,b+d,\"Z\"]},\"triangle-down\":function(a,b,c,d){return[\"M\",a,b,\"L\",a+c,b,a+c/2,b+d,\"Z\"]},diamond:function(a,b,c,d){return[\"M\",a+c/2,b,\"L\",a+c,b+d/2,a+c/2,b+d,a,b+d/2,\"Z\"]},arc:function(a,b,c,d,\r\ne){var f=e.start,c=e.r||c||d,g=e.end-0.001,d=e.innerR,h=e.open,i=V(f),j=ca(f),k=V(g),g=ca(g),e=e.end-f<ya?0:1;return[\"M\",a+c*i,b+c*j,\"A\",c,c,0,e,1,a+c*k,b+c*g,h?\"M\":\"L\",a+d*k,b+d*g,\"A\",d,d,0,e,0,a+d*i,b+d*j,h?\"\":\"Z\"]}},clipRect:function(a,b,c,d){var e=\"highcharts-\"+zb++,f=this.createElement(\"clipPath\").attr({id:e}).add(this.defs),a=this.rect(a,b,c,d,0).add(f);a.id=e;a.clipPath=f;return a},color:function(a,b,c){var d=this,e,f=/^rgba/,g,h,i,j,k,l,m,p=[];a&&a.linearGradient?g=\"linearGradient\":a&&a.radialGradient&&\r\n(g=\"radialGradient\");if(g){c=a[g];h=d.gradients;j=a.stops;b=b.radialReference;Ia(c)&&(a[g]=c={x1:c[0],y1:c[1],x2:c[2],y2:c[3],gradientUnits:\"userSpaceOnUse\"});g===\"radialGradient\"&&b&&!u(c.gradientUnits)&&(c=x(c,{cx:b[0]-b[2]/2+c.cx*b[2],cy:b[1]-b[2]/2+c.cy*b[2],r:c.r*b[2],gradientUnits:\"userSpaceOnUse\"}));for(m in c)m!==\"id\"&&p.push(m,c[m]);for(m in j)p.push(j[m]);p=p.join(\",\");h[p]?a=h[p].id:(c.id=a=\"highcharts-\"+zb++,h[p]=i=d.createElement(g).attr(c).add(d.defs),i.stops=[],n(j,function(a){f.test(a[1])?\r\n(e=ra(a[1]),k=e.get(\"rgb\"),l=e.get(\"a\")):(k=a[1],l=1);a=d.createElement(\"stop\").attr({offset:a[0],\"stop-color\":k,\"stop-opacity\":l}).add(i);i.stops.push(a)}));return\"url(\"+d.url+\"#\"+a+\")\"}else return f.test(a)?(e=ra(a),v(b,c+\"-opacity\",e.get(\"a\")),e.get(\"rgb\")):(b.removeAttribute(c+\"-opacity\"),a)},text:function(a,b,c,d){var e=M.chart.style,f=$||!Z&&this.forExport;if(d&&!this.forExport)return this.html(a,b,c);b=t(o(b,0));c=t(o(c,0));a=this.createElement(\"text\").attr({x:b,y:c,text:a}).css({fontFamily:e.fontFamily,\r\nfontSize:e.fontSize});f&&a.css({position:\"absolute\"});a.x=b;a.y=c;return a},html:function(a,b,c){var d=M.chart.style,e=this.createElement(\"span\"),f=e.attrSetters,g=e.element,h=e.renderer;f.text=function(a){a!==g.innerHTML&&delete this.bBox;g.innerHTML=a;return!1};f.x=f.y=f.align=function(a,b){b===\"align\"&&(b=\"textAlign\");e[b]=a;e.htmlUpdateTransform();return!1};e.attr({text:a,x:t(b),y:t(c)}).css({position:\"absolute\",whiteSpace:\"nowrap\",fontFamily:d.fontFamily,fontSize:d.fontSize});e.css=e.htmlCss;\r\nif(h.isSVG)e.add=function(a){var b,c=h.box.parentNode,d=[];if(a){if(b=a.div,!b){for(;a;)d.push(a),a=a.parentGroup;n(d.reverse(),function(a){var d;b=a.div=a.div||U(Ea,{className:v(a.element,\"class\")},{position:\"absolute\",left:(a.translateX||0)+\"px\",top:(a.translateY||0)+\"px\"},b||c);d=b.style;r(a.attrSetters,{translateX:function(a){d.left=a+\"px\"},translateY:function(a){d.top=a+\"px\"},visibility:function(a,b){d[b]=a}})})}}else b=c;b.appendChild(g);e.added=!0;e.alignOnAdd&&e.htmlUpdateTransform();return e};\r\nreturn e},fontMetrics:function(a){var a=C(a||11),a=a<24?a+4:t(a*1.2),b=t(a*0.8);return{h:a,b:b}},label:function(a,b,c,d,e,f,g,h,i){function j(){var a,b;a=o.element.style;L=(Oa===void 0||la===void 0||q.styles.textAlign)&&o.getBBox();q.width=(Oa||L.width||0)+2*da+kb;q.height=(la||L.height||0)+2*da;v=da+p.fontMetrics(a&&a.fontSize).b;if(C){if(!A)a=t(-s*da),b=h?-v:0,q.box=A=d?p.symbol(d,a,b,q.width,q.height):p.rect(a,b,q.width,q.height,0,lb[Rb]),A.add(q);A.isImg||A.attr(x({width:q.width,height:q.height},\r\nlb));lb=null}}function k(){var a=q.styles,a=a&&a.textAlign,b=kb+da*(1-s),c;c=h?0:v;if(u(Oa)&&(a===\"center\"||a===\"right\"))b+={center:0.5,right:1}[a]*(Oa-L.width);(b!==o.x||c!==o.y)&&o.attr({x:b,y:c});o.x=b;o.y=c}function l(a,b){A?A.attr(a,b):lb[a]=b}function m(){o.add(q);q.attr({text:a,x:b,y:c});A&&u(e)&&q.attr({anchorX:e,anchorY:f})}var p=this,q=p.g(i),o=p.text(\"\",0,0,g).attr({zIndex:1}),A,L,s=0,da=3,kb=0,Oa,la,E,H,B=0,lb={},v,g=q.attrSetters,C;J(q,\"add\",m);g.width=function(a){Oa=a;return!1};g.height=\r\nfunction(a){la=a;return!1};g.padding=function(a){u(a)&&a!==da&&(da=a,k());return!1};g.paddingLeft=function(a){u(a)&&a!==kb&&(kb=a,k());return!1};g.align=function(a){s={left:0,center:0.5,right:1}[a];return!1};g.text=function(a,b){o.attr(b,a);j();k();return!1};g[Rb]=function(a,b){C=!0;B=a%2/2;l(b,a);return!1};g.stroke=g.fill=g.r=function(a,b){b===\"fill\"&&(C=!0);l(b,a);return!1};g.anchorX=function(a,b){e=a;l(b,a+B-E);return!1};g.anchorY=function(a,b){f=a;l(b,a-H);return!1};g.x=function(a){q.x=a;a-=s*\r\n((Oa||L.width)+da);E=t(a);q.attr(\"translateX\",E);return!1};g.y=function(a){H=q.y=t(a);q.attr(\"translateY\",H);return!1};var y=q.css;return r(q,{css:function(a){if(a){var b={},a=x(a);n(\"fontSize,fontWeight,fontFamily,color,lineHeight,width,textDecoration,textShadow\".split(\",\"),function(c){a[c]!==w&&(b[c]=a[c],delete a[c])});o.css(b)}return y.call(q,a)},getBBox:function(){return{width:L.width+2*da,height:L.height+2*da,x:L.x-da,y:L.y-da}},shadow:function(a){A&&A.shadow(a);return q},destroy:function(){aa(q,\r\n\"add\",m);aa(q.element,\"mouseenter\");aa(q.element,\"mouseleave\");o&&(o=o.destroy());A&&(A=A.destroy());wa.prototype.destroy.call(q);q=p=j=k=l=m=null}})}};Va=Ha;var F;if(!Z&&!$){Highcharts.VMLElement=F={init:function(a,b){var c=[\"<\",b,' filled=\"f\" stroked=\"f\"'],d=[\"position: \",\"absolute\",\";\"],e=b===Ea;(b===\"shape\"||e)&&d.push(\"left:0;top:0;width:1px;height:1px;\");d.push(\"visibility: \",e?\"hidden\":\"visible\");c.push(' style=\"',d.join(\"\"),'\"/>');if(b)c=e||b===\"span\"||b===\"img\"?c.join(\"\"):a.prepVML(c),this.element=\r\nU(c);this.renderer=a;this.attrSetters={}},add:function(a){var b=this.renderer,c=this.element,d=b.box,d=a?a.element||a:d;a&&a.inverted&&b.invertChild(c,d);d.appendChild(c);this.added=!0;this.alignOnAdd&&!this.deferUpdateTransform&&this.updateTransform();z(this,\"add\");return this},updateTransform:wa.prototype.htmlUpdateTransform,setSpanRotation:function(a,b,c){K(this.element,{filter:a?[\"progid:DXImageTransform.Microsoft.Matrix(M11=\",c,\", M12=\",-b,\", M21=\",b,\", M22=\",c,\", sizingMethod='auto expand')\"].join(\"\"):\r\nS})},pathToVML:function(a){for(var b=a.length,c=[],d;b--;)if(sa(a[b]))c[b]=t(a[b]*10)-5;else if(a[b]===\"Z\")c[b]=\"x\";else if(c[b]=a[b],a.isArc&&(a[b]===\"wa\"||a[b]===\"at\"))d=a[b]===\"wa\"?1:-1,c[b+5]===c[b+7]&&(c[b+7]-=d),c[b+6]===c[b+8]&&(c[b+8]-=d);return c.join(\" \")||\"x\"},attr:function(a,b){var c,d,e,f=this.element||{},g=f.style,h=f.nodeName,i=this.renderer,j=this.symbolName,k,l=this.shadows,m,p=this.attrSetters,q=this;ea(a)&&u(b)&&(c=a,a={},a[c]=b);if(ea(a))c=a,q=c===\"strokeWidth\"||c===\"stroke-width\"?\r\nthis.strokeweight:this[c];else for(c in a)if(d=a[c],m=!1,e=p[c]&&p[c].call(this,d,c),e!==!1&&d!==null){e!==w&&(d=e);if(j&&/^(x|y|r|start|end|width|height|innerR|anchorX|anchorY)/.test(c))k||(this.symbolAttr(a),k=!0),m=!0;else if(c===\"d\"){d=d||[];this.d=d.join(\" \");f.path=d=this.pathToVML(d);if(l)for(e=l.length;e--;)l[e].path=l[e].cutOff?this.cutOffPath(d,l[e].cutOff):d;m=!0}else if(c===\"visibility\"){if(l)for(e=l.length;e--;)l[e].style[c]=d;h===\"DIV\"&&(d=d===\"hidden\"?\"-999em\":0,fb||(g[c]=d?\"visible\":\r\n\"hidden\"),c=\"top\");g[c]=d;m=!0}else if(c===\"zIndex\")d&&(g[c]=d),m=!0;else if(qa(c,[\"x\",\"y\",\"width\",\"height\"])!==-1)this[c]=d,c===\"x\"||c===\"y\"?c={x:\"left\",y:\"top\"}[c]:d=s(0,d),this.updateClipping?(this[c]=d,this.updateClipping()):g[c]=d,m=!0;else if(c===\"class\"&&h===\"DIV\")f.className=d;else if(c===\"stroke\")d=i.color(d,f,c),c=\"strokecolor\";else if(c===\"stroke-width\"||c===\"strokeWidth\")f.stroked=d?!0:!1,c=\"strokeweight\",this[c]=d,sa(d)&&(d+=\"px\");else if(c===\"dashstyle\")(f.getElementsByTagName(\"stroke\")[0]||\r\nU(i.prepVML([\"<stroke/>\"]),null,null,f))[c]=d||\"solid\",this.dashstyle=d,m=!0;else if(c===\"fill\")if(h===\"SPAN\")g.color=d;else{if(h!==\"IMG\")f.filled=d!==S?!0:!1,d=i.color(d,f,c,this),c=\"fillcolor\"}else if(c===\"opacity\")m=!0;else if(h===\"shape\"&&c===\"rotation\")this[c]=f.style[c]=d,f.style.left=-t(ca(d*Ua)+1)+\"px\",f.style.top=t(V(d*Ua))+\"px\";else if(c===\"translateX\"||c===\"translateY\"||c===\"rotation\")this[c]=d,this.updateTransform(),m=!0;else if(c===\"text\")this.bBox=null,f.innerHTML=d,m=!0;m||(fb?f[c]=\r\nd:v(f,c,d))}return q},clip:function(a){var b=this,c;a?(c=a.members,ga(c,b),c.push(b),b.destroyClip=function(){ga(c,b)},a=a.getCSS(b)):(b.destroyClip&&b.destroyClip(),a={clip:fb?\"inherit\":\"rect(auto)\"});return b.css(a)},css:wa.prototype.htmlCss,safeRemoveChild:function(a){a.parentNode&&Ta(a)},destroy:function(){this.destroyClip&&this.destroyClip();return wa.prototype.destroy.apply(this)},on:function(a,b){this.element[\"on\"+a]=function(){var a=O.event;a.target=a.srcElement;b(a)};return this},cutOffPath:function(a,\r\nb){var c,a=a.split(/[ ,]/);c=a.length;if(c===9||c===11)a[c-4]=a[c-2]=C(a[c-2])-10*b;return a.join(\" \")},shadow:function(a,b,c){var d=[],e,f=this.element,g=this.renderer,h,i=f.style,j,k=f.path,l,m,p,q;k&&typeof k.value!==\"string\"&&(k=\"x\");m=k;if(a){p=o(a.width,3);q=(a.opacity||0.15)/p;for(e=1;e<=3;e++){l=p*2+1-2*e;c&&(m=this.cutOffPath(k.value,l+0.5));j=['<shape isShadow=\"true\" strokeweight=\"',l,'\" filled=\"false\" path=\"',m,'\" coordsize=\"10 10\" style=\"',f.style.cssText,'\" />'];h=U(g.prepVML(j),null,\r\n{left:C(i.left)+o(a.offsetX,1),top:C(i.top)+o(a.offsetY,1)});if(c)h.cutOff=l+1;j=['<stroke color=\"',a.color||\"black\",'\" opacity=\"',q*e,'\"/>'];U(g.prepVML(j),null,null,h);b?b.element.appendChild(h):f.parentNode.insertBefore(h,f);d.push(h)}this.shadows=d}return this}};F=ha(wa,F);var ma={Element:F,isIE8:oa.indexOf(\"MSIE 8.0\")>-1,init:function(a,b,c){var d,e;this.alignedObjects=[];d=this.createElement(Ea);e=d.element;e.style.position=\"relative\";a.appendChild(d.element);this.isVML=!0;this.box=e;this.boxWrapper=\r\nd;this.setSize(b,c,!1);y.namespaces.hcv||(y.namespaces.add(\"hcv\",\"urn:schemas-microsoft-com:vml\"),(y.styleSheets.length?y.styleSheets[0]:y.createStyleSheet()).cssText+=\"hcv\\\\:fill, hcv\\\\:path, hcv\\\\:shape, hcv\\\\:stroke{ behavior:url(#default#VML); display: inline-block; } \")},isHidden:function(){return!this.box.offsetWidth},clipRect:function(a,b,c,d){var e=this.createElement(),f=T(a);return r(e,{members:[],left:(f?a.x:a)+1,top:(f?a.y:b)+1,width:(f?a.width:c)-1,height:(f?a.height:d)-1,getCSS:function(a){var b=\r\na.element,c=b.nodeName,a=a.inverted,d=this.top-(c===\"shape\"?b.offsetTop:0),e=this.left,b=e+this.width,f=d+this.height,d={clip:\"rect(\"+t(a?e:d)+\"px,\"+t(a?f:b)+\"px,\"+t(a?b:f)+\"px,\"+t(a?d:e)+\"px)\"};!a&&fb&&c===\"DIV\"&&r(d,{width:b+\"px\",height:f+\"px\"});return d},updateClipping:function(){n(e.members,function(a){a.css(e.getCSS(a))})}})},color:function(a,b,c,d){var e=this,f,g=/^rgba/,h,i,j=S;a&&a.linearGradient?i=\"gradient\":a&&a.radialGradient&&(i=\"pattern\");if(i){var k,l,m=a.linearGradient||a.radialGradient,\r\np,q,o,A,L,s=\"\",a=a.stops,u,t=[],w=function(){h=['<fill colors=\"'+t.join(\",\")+'\" opacity=\"',o,'\" o:opacity2=\"',q,'\" type=\"',i,'\" ',s,'focus=\"100%\" method=\"any\" />'];U(e.prepVML(h),null,null,b)};p=a[0];u=a[a.length-1];p[0]>0&&a.unshift([0,p[1]]);u[0]<1&&a.push([1,u[1]]);n(a,function(a,b){g.test(a[1])?(f=ra(a[1]),k=f.get(\"rgb\"),l=f.get(\"a\")):(k=a[1],l=1);t.push(a[0]*100+\"% \"+k);b?(o=l,A=k):(q=l,L=k)});if(c===\"fill\")if(i===\"gradient\")c=m.x1||m[0]||0,a=m.y1||m[1]||0,p=m.x2||m[2]||0,m=m.y2||m[3]||0,s='angle=\"'+\r\n(90-R.atan((m-a)/(p-c))*180/ya)+'\"',w();else{var j=m.r,r=j*2,E=j*2,H=m.cx,B=m.cy,x=b.radialReference,v,j=function(){x&&(v=d.getBBox(),H+=(x[0]-v.x)/v.width-0.5,B+=(x[1]-v.y)/v.height-0.5,r*=x[2]/v.width,E*=x[2]/v.height);s='src=\"'+M.global.VMLRadialGradientURL+'\" size=\"'+r+\",\"+E+'\" origin=\"0.5,0.5\" position=\"'+H+\",\"+B+'\" color2=\"'+L+'\" ';w()};d.added?j():J(d,\"add\",j);j=A}else j=k}else if(g.test(a)&&b.tagName!==\"IMG\")f=ra(a),h=[\"<\",c,' opacity=\"',f.get(\"a\"),'\"/>'],U(this.prepVML(h),null,null,b),j=\r\nf.get(\"rgb\");else{j=b.getElementsByTagName(c);if(j.length)j[0].opacity=1,j[0].type=\"solid\";j=a}return j},prepVML:function(a){var b=this.isIE8,a=a.join(\"\");b?(a=a.replace(\"/>\",' xmlns=\"urn:schemas-microsoft-com:vml\" />'),a=a.indexOf('style=\"')===-1?a.replace(\"/>\",' style=\"display:inline-block;behavior:url(#default#VML);\" />'):a.replace('style=\"','style=\"display:inline-block;behavior:url(#default#VML);')):a=a.replace(\"<\",\"<hcv:\");return a},text:Ha.prototype.html,path:function(a){var b={coordsize:\"10 10\"};\r\nIa(a)?b.d=a:T(a)&&r(b,a);return this.createElement(\"shape\").attr(b)},circle:function(a,b,c){var d=this.symbol(\"circle\");if(T(a))c=a.r,b=a.y,a=a.x;d.isCircle=!0;d.r=c;return d.attr({x:a,y:b})},g:function(a){var b;a&&(b={className:\"highcharts-\"+a,\"class\":\"highcharts-\"+a});return this.createElement(Ea).attr(b)},image:function(a,b,c,d,e){var f=this.createElement(\"img\").attr({src:a});arguments.length>1&&f.attr({x:b,y:c,width:d,height:e});return f},rect:function(a,b,c,d,e,f){var g=this.symbol(\"rect\");g.r=\r\nT(a)?a.r:e;return g.attr(T(a)?a:g.crisp(f,a,b,s(c,0),s(d,0)))},invertChild:function(a,b){var c=b.style;K(a,{flip:\"x\",left:C(c.width)-1,top:C(c.height)-1,rotation:-90})},symbols:{arc:function(a,b,c,d,e){var f=e.start,g=e.end,h=e.r||c||d,c=e.innerR,d=V(f),i=ca(f),j=V(g),k=ca(g);if(g-f===0)return[\"x\"];f=[\"wa\",a-h,b-h,a+h,b+h,a+h*d,b+h*i,a+h*j,b+h*k];e.open&&!c&&f.push(\"e\",\"M\",a,b);f.push(\"at\",a-c,b-c,a+c,b+c,a+c*j,b+c*k,a+c*d,b+c*i,\"x\",\"e\");f.isArc=!0;return f},circle:function(a,b,c,d,e){e&&(c=d=2*e.r);\r\ne&&e.isCircle&&(a-=c/2,b-=d/2);return[\"wa\",a,b,a+c,b+d,a+c,b+d/2,a+c,b+d/2,\"e\"]},rect:function(a,b,c,d,e){var f=a+c,g=b+d,h;!u(e)||!e.r?f=Ha.prototype.symbols.square.apply(0,arguments):(h=I(e.r,c,d),f=[\"M\",a+h,b,\"L\",f-h,b,\"wa\",f-2*h,b,f,b+2*h,f-h,b,f,b+h,\"L\",f,g-h,\"wa\",f-2*h,g-2*h,f,g,f,g-h,f-h,g,\"L\",a+h,g,\"wa\",a,g-2*h,a+2*h,g,a+h,g,a,g-h,\"L\",a,b+h,\"wa\",a,b,a+2*h,b+2*h,a,b+h,a+h,b,\"x\",\"e\"]);return f}}};Highcharts.VMLRenderer=F=function(){this.init.apply(this,arguments)};F.prototype=x(Ha.prototype,\r\nma);Va=F}var Tb;if($)Highcharts.CanVGRenderer=F=function(){za=\"http://www.w3.org/1999/xhtml\"},F.prototype.symbols={},Tb=function(){function a(){var a=b.length,d;for(d=0;d<a;d++)b[d]();b=[]}var b=[];return{push:function(c,d){b.length===0&&Vb(d,a);b.push(c)}}}(),Va=F;Ma.prototype={addLabel:function(){var a=this.axis,b=a.options,c=a.chart,d=a.horiz,e=a.categories,f=a.series[0]&&a.series[0].names,g=this.pos,h=b.labels,i=a.tickPositions,d=d&&e&&!h.step&&!h.staggerLines&&!h.rotation&&c.plotWidth/i.length||\r\n!d&&(c.margin[3]||c.chartWidth*0.33),j=g===i[0],k=g===i[i.length-1],l,f=e?o(e[g],f&&f[g],g):g,e=this.label,m=i.info;a.isDatetimeAxis&&m&&(l=b.dateTimeLabelFormats[m.higherRanks[g]||m.unitName]);this.isFirst=j;this.isLast=k;b=a.labelFormatter.call({axis:a,chart:c,isFirst:j,isLast:k,dateTimeLabelFormat:l,value:a.isLog?ia(fa(f)):f});g=d&&{width:s(1,t(d-2*(h.padding||10)))+\"px\"};g=r(g,h.style);if(u(e))e&&e.attr({text:b}).css(g);else{l={align:a.labelAlign};if(sa(h.rotation))l.rotation=h.rotation;if(d&&\r\nh.ellipsis)l._clipHeight=a.len/i.length;this.label=u(b)&&h.enabled?c.renderer.text(b,0,0,h.useHTML).attr(l).css(g).add(a.labelGroup):null}},getLabelSize:function(){var a=this.label,b=this.axis;return a?(this.labelBBox=a.getBBox())[b.horiz?\"height\":\"width\"]:0},getLabelSides:function(){var a=this.axis,b=this.labelBBox.width,a=b*{left:0,center:0.5,right:1}[a.labelAlign]-a.options.labels.x;return[-a,b-a]},handleOverflow:function(a,b){var c=!0,d=this.axis,e=d.chart,f=this.isFirst,g=this.isLast,h=b.x,i=\r\nd.reversed,j=d.tickPositions;if(f||g){var k=this.getLabelSides(),l=k[0],k=k[1],e=e.plotLeft,m=e+d.len,j=(d=d.ticks[j[a+(f?1:-1)]])&&d.label.xy&&d.label.xy.x+d.getLabelSides()[f?0:1];f&&!i||g&&i?h+l<e&&(h=e-l,d&&h+k>j&&(c=!1)):h+k>m&&(h=m-k,d&&h+l<j&&(c=!1));b.x=h}return c},getPosition:function(a,b,c,d){var e=this.axis,f=e.chart,g=d&&f.oldChartHeight||f.chartHeight;return{x:a?e.translate(b+c,null,null,d)+e.transB:e.left+e.offset+(e.opposite?(d&&f.oldChartWidth||f.chartWidth)-e.right-e.left:0),y:a?\r\ng-e.bottom+e.offset-(e.opposite?e.height:0):g-e.translate(b+c,null,null,d)-e.transB}},getLabelPosition:function(a,b,c,d,e,f,g,h){var i=this.axis,j=i.transA,k=i.reversed,l=i.staggerLines,m=i.chart.renderer.fontMetrics(e.style.fontSize).b,p=e.rotation,a=a+e.x-(f&&d?f*j*(k?-1:1):0),b=b+e.y-(f&&!d?f*j*(k?1:-1):0);p&&i.side===2&&(b-=m-m*V(p*Ua));!u(e.y)&&!p&&(b+=m-c.getBBox().height/2);l&&(b+=g/(h||1)%l*(i.labelOffset/l));return{x:a,y:b}},getMarkPath:function(a,b,c,d,e,f){return f.crispLine([\"M\",a,b,\"L\",\r\na+(e?0:-c),b+(e?c:0)],d)},render:function(a,b,c){var d=this.axis,e=d.options,f=d.chart.renderer,g=d.horiz,h=this.type,i=this.label,j=this.pos,k=e.labels,l=this.gridLine,m=h?h+\"Grid\":\"grid\",p=h?h+\"Tick\":\"tick\",q=e[m+\"LineWidth\"],n=e[m+\"LineColor\"],A=e[m+\"LineDashStyle\"],s=e[p+\"Length\"],m=e[p+\"Width\"]||0,u=e[p+\"Color\"],t=e[p+\"Position\"],p=this.mark,r=k.step,v=!0,x=d.tickmarkOffset,E=this.getPosition(g,j,x,b),H=E.x,E=E.y,B=g&&H===d.pos+d.len||!g&&E===d.pos?-1:1,C=d.staggerLines;this.isActive=!0;if(q){j=\r\nd.getPlotLinePath(j+x,q*B,b,!0);if(l===w){l={stroke:n,\"stroke-width\":q};if(A)l.dashstyle=A;if(!h)l.zIndex=1;if(b)l.opacity=0;this.gridLine=l=q?f.path(j).attr(l).add(d.gridGroup):null}if(!b&&l&&j)l[this.isNew?\"attr\":\"animate\"]({d:j,opacity:c})}if(m&&s)t===\"inside\"&&(s=-s),d.opposite&&(s=-s),b=this.getMarkPath(H,E,s,m*B,g,f),p?p.animate({d:b,opacity:c}):this.mark=f.path(b).attr({stroke:u,\"stroke-width\":m,opacity:c}).add(d.axisGroup);if(i&&!isNaN(H))i.xy=E=this.getLabelPosition(H,E,i,g,k,x,a,r),this.isFirst&&\r\n!this.isLast&&!o(e.showFirstLabel,1)||this.isLast&&!this.isFirst&&!o(e.showLastLabel,1)?v=!1:!C&&g&&k.overflow===\"justify\"&&!this.handleOverflow(a,E)&&(v=!1),r&&a%r&&(v=!1),v&&!isNaN(E.y)?(E.opacity=c,i[this.isNew?\"attr\":\"animate\"](E),this.isNew=!1):i.attr(\"y\",-9999)},destroy:function(){Ka(this,this.axis)}};vb.prototype={render:function(){var a=this,b=a.axis,c=b.horiz,d=(b.pointRange||0)/2,e=a.options,f=e.label,g=a.label,h=e.width,i=e.to,j=e.from,k=u(j)&&u(i),l=e.value,m=e.dashStyle,p=a.svgElem,q=\r\n[],n,A=e.color,L=e.zIndex,t=e.events,w=b.chart.renderer;b.isLog&&(j=na(j),i=na(i),l=na(l));if(h){if(q=b.getPlotLinePath(l,h),d={stroke:A,\"stroke-width\":h},m)d.dashstyle=m}else if(k){if(j=s(j,b.min-d),i=I(i,b.max+d),q=b.getPlotBandPath(j,i,e),d={fill:A},e.borderWidth)d.stroke=e.borderColor,d[\"stroke-width\"]=e.borderWidth}else return;if(u(L))d.zIndex=L;if(p)q?p.animate({d:q},null,p.onGetPath):(p.hide(),p.onGetPath=function(){p.show()});else if(q&&q.length&&(a.svgElem=p=w.path(q).attr(d).add(),t))for(n in e=\r\nfunction(b){p.on(b,function(c){t[b].apply(a,[c])})},t)e(n);if(f&&u(f.text)&&q&&q.length&&b.width>0&&b.height>0){f=x({align:c&&k&&\"center\",x:c?!k&&4:10,verticalAlign:!c&&k&&\"middle\",y:c?k?16:10:k?6:-4,rotation:c&&!k&&90},f);if(!g)a.label=g=w.text(f.text,0,0,f.useHTML).attr({align:f.textAlign||f.align,rotation:f.rotation,zIndex:L}).css(f.style).add();b=[q[1],q[4],o(q[6],q[1])];q=[q[2],q[5],o(q[7],q[2])];c=Ja(b);k=Ja(q);g.align(f,!1,{x:c,y:k,width:va(b)-c,height:va(q)-k});g.show()}else g&&g.hide();return a},\r\ndestroy:function(){ga(this.axis.plotLinesAndBands,this);delete this.axis;Ka(this)}};Mb.prototype={destroy:function(){Ka(this,this.axis)},render:function(a){var b=this.options,c=b.format,c=c?Ca(c,this):b.formatter.call(this);this.label?this.label.attr({text:c,visibility:\"hidden\"}):this.label=this.axis.chart.renderer.text(c,0,0,b.useHTML).css(b.style).attr({align:this.textAlign,rotation:b.rotation,visibility:\"hidden\"}).add(a)},setOffset:function(a,b){var c=this.axis,d=c.chart,e=d.inverted,f=this.isNegative,\r\ng=c.translate(this.percent?100:this.total,0,0,0,1),c=c.translate(0),c=N(g-c),h=d.xAxis[0].translate(this.x)+a,i=d.plotHeight,f={x:e?f?g:g-c:h,y:e?i-h-b:f?i-g-c:i-g,width:e?c:b,height:e?b:c};if(e=this.label)e.align(this.alignOptions,null,f),f=e.alignAttr,e.attr({visibility:this.options.crop===!1||d.isInsidePlot(f.x,f.y)?Z?\"inherit\":\"visible\":\"hidden\"})}};db.prototype={defaultOptions:{dateTimeLabelFormats:{millisecond:\"%H:%M:%S.%L\",second:\"%H:%M:%S\",minute:\"%H:%M\",hour:\"%H:%M\",day:\"%e. %b\",week:\"%e. %b\",\r\nmonth:\"%b '%y\",year:\"%Y\"},endOnTick:!1,gridLineColor:\"#C0C0C0\",labels:G,lineColor:\"#C0D0E0\",lineWidth:1,minPadding:0.01,maxPadding:0.01,minorGridLineColor:\"#E0E0E0\",minorGridLineWidth:1,minorTickColor:\"#A0A0A0\",minorTickLength:2,minorTickPosition:\"outside\",startOfWeek:1,startOnTick:!1,tickColor:\"#C0D0E0\",tickLength:5,tickmarkPlacement:\"between\",tickPixelInterval:100,tickPosition:\"outside\",tickWidth:1,title:{align:\"middle\",style:{color:\"#4d759e\",fontWeight:\"bold\"}},type:\"linear\"},defaultYAxisOptions:{endOnTick:!0,\r\ngridLineWidth:1,tickPixelInterval:72,showLastLabel:!0,labels:{x:-8,y:3},lineWidth:0,maxPadding:0.05,minPadding:0.05,startOnTick:!0,tickWidth:0,title:{rotation:270,text:\"Values\"},stackLabels:{enabled:!1,formatter:function(){return Aa(this.total,-1)},style:G.style}},defaultLeftAxisOptions:{labels:{x:-8,y:null},title:{rotation:270}},defaultRightAxisOptions:{labels:{x:8,y:null},title:{rotation:90}},defaultBottomAxisOptions:{labels:{x:0,y:14},title:{rotation:0}},defaultTopAxisOptions:{labels:{x:0,y:-5},\r\ntitle:{rotation:0}},init:function(a,b){var c=b.isX;this.horiz=a.inverted?!c:c;this.xOrY=(this.isXAxis=c)?\"x\":\"y\";this.opposite=b.opposite;this.side=this.horiz?this.opposite?0:2:this.opposite?1:3;this.setOptions(b);var d=this.options,e=d.type;this.labelFormatter=d.labels.formatter||this.defaultLabelFormatter;this.userOptions=b;this.minPixelPadding=0;this.chart=a;this.reversed=d.reversed;this.zoomEnabled=d.zoomEnabled!==!1;this.categories=d.categories||e===\"category\";this.isLog=e===\"logarithmic\";this.isDatetimeAxis=\r\ne===\"datetime\";this.isLinked=u(d.linkedTo);this.tickmarkOffset=this.categories&&d.tickmarkPlacement===\"between\"?0.5:0;this.ticks={};this.minorTicks={};this.plotLinesAndBands=[];this.alternateBands={};this.len=0;this.minRange=this.userMinRange=d.minRange||d.maxZoom;this.range=d.range;this.offset=d.offset||0;this.stacks={};this.oldStacks={};this.stackExtremes={};this.min=this.max=null;var f,d=this.options.events;qa(this,a.axes)===-1&&(a.axes.push(this),a[c?\"xAxis\":\"yAxis\"].push(this));this.series=this.series||\r\n[];if(a.inverted&&c&&this.reversed===w)this.reversed=!0;this.removePlotLine=this.removePlotBand=this.removePlotBandOrLine;for(f in d)J(this,f,d[f]);if(this.isLog)this.val2lin=na,this.lin2val=fa},setOptions:function(a){this.options=x(this.defaultOptions,this.isXAxis?{}:this.defaultYAxisOptions,[this.defaultTopAxisOptions,this.defaultRightAxisOptions,this.defaultBottomAxisOptions,this.defaultLeftAxisOptions][this.side],x(M[this.isXAxis?\"xAxis\":\"yAxis\"],a))},update:function(a,b){var c=this.chart,a=c.options[this.xOrY+\r\n\"Axis\"][this.options.index]=x(this.userOptions,a);this.destroy(!0);this._addedPlotLB=this.userMin=this.userMax=w;this.init(c,r(a,{events:w}));c.isDirtyBox=!0;o(b,!0)&&c.redraw()},remove:function(a){var b=this.chart,c=this.xOrY+\"Axis\";n(this.series,function(a){a.remove(!1)});ga(b.axes,this);ga(b[c],this);b.options[c].splice(this.options.index,1);n(b[c],function(a,b){a.options.index=b});this.destroy();b.isDirtyBox=!0;o(a,!0)&&b.redraw()},defaultLabelFormatter:function(){var a=this.axis,b=this.value,\r\nc=a.categories,d=this.dateTimeLabelFormat,e=M.lang.numericSymbols,f=e&&e.length,g,h=a.options.labels.format,a=a.isLog?b:a.tickInterval;if(h)g=Ca(h,this);else if(c)g=b;else if(d)g=Xa(d,b);else if(f&&a>=1E3)for(;f--&&g===w;)c=Math.pow(1E3,f+1),a>=c&&e[f]!==null&&(g=Aa(b/c,-1)+e[f]);g===w&&(g=b>=1E3?Aa(b,0):Aa(b,-1));return g},getSeriesExtremes:function(){var a=this,b=a.chart;a.hasVisibleSeries=!1;a.dataMin=a.dataMax=null;a.stackExtremes={};a.buildStacks();n(a.series,function(c){if(c.visible||!b.options.chart.ignoreHiddenSeries){var d;\r\nd=c.options.threshold;var e;a.hasVisibleSeries=!0;a.isLog&&d<=0&&(d=null);if(a.isXAxis){if(d=c.xData,d.length)a.dataMin=I(o(a.dataMin,d[0]),Ja(d)),a.dataMax=s(o(a.dataMax,d[0]),va(d))}else{c.getExtremes();e=c.dataMax;c=c.dataMin;if(u(c)&&u(e))a.dataMin=I(o(a.dataMin,c),c),a.dataMax=s(o(a.dataMax,e),e);if(u(d))if(a.dataMin>=d)a.dataMin=d,a.ignoreMinPadding=!0;else if(a.dataMax<d)a.dataMax=d,a.ignoreMaxPadding=!0}}})},translate:function(a,b,c,d,e,f){var g=this.len,h=1,i=0,j=d?this.oldTransA:this.transA,\r\nd=d?this.oldMin:this.min,k=this.minPixelPadding,e=(this.options.ordinal||this.isLog&&e)&&this.lin2val;if(!j)j=this.transA;c&&(h*=-1,i=g);this.reversed&&(h*=-1,i-=h*g);b?(a=a*h+i,a-=k,a=a/j+d,e&&(a=this.lin2val(a))):(e&&(a=this.val2lin(a)),f===\"between\"&&(f=0.5),a=h*(a-d)*j+i+h*k+(sa(f)?j*f*this.pointRange:0));return a},toPixels:function(a,b){return this.translate(a,!1,!this.horiz,null,!0)+(b?0:this.pos)},toValue:function(a,b){return this.translate(a-(b?0:this.pos),!0,!this.horiz,null,!0)},getPlotLinePath:function(a,\r\nb,c,d){var e=this.chart,f=this.left,g=this.top,h,i,j,a=this.translate(a,null,null,c),k=c&&e.oldChartHeight||e.chartHeight,l=c&&e.oldChartWidth||e.chartWidth,m;h=this.transB;c=i=t(a+h);h=j=t(k-a-h);if(isNaN(a))m=!0;else if(this.horiz){if(h=g,j=k-this.bottom,c<f||c>f+this.width)m=!0}else if(c=f,i=l-this.right,h<g||h>g+this.height)m=!0;return m&&!d?null:e.renderer.crispLine([\"M\",c,h,\"L\",i,j],b||0)},getPlotBandPath:function(a,b){var c=this.getPlotLinePath(b),d=this.getPlotLinePath(a);d&&c?d.push(c[4],\r\nc[5],c[1],c[2]):d=null;return d},getLinearTickPositions:function(a,b,c){for(var d,b=ia(P(b/a)*a),c=ia(xa(c/a)*a),e=[];b<=c;){e.push(b);b=ia(b+a);if(b===d)break;d=b}return e},getLogTickPositions:function(a,b,c,d){var e=this.options,f=this.len,g=[];if(!d)this._minorAutoInterval=null;if(a>=0.5)a=t(a),g=this.getLinearTickPositions(a,b,c);else if(a>=0.08)for(var f=P(b),h,i,j,k,l,e=a>0.3?[1,2,4]:a>0.15?[1,2,4,6,8]:[1,2,3,4,5,6,7,8,9];f<c+1&&!l;f++){i=e.length;for(h=0;h<i&&!l;h++)j=na(fa(f)*e[h]),j>b&&(!d||\r\nk<=c)&&g.push(k),k>c&&(l=!0),k=j}else if(b=fa(b),c=fa(c),a=e[d?\"minorTickInterval\":\"tickInterval\"],a=o(a===\"auto\"?null:a,this._minorAutoInterval,(c-b)*(e.tickPixelInterval/(d?5:1))/((d?f/this.tickPositions.length:f)||1)),a=ob(a,null,nb(a)),g=Na(this.getLinearTickPositions(a,b,c),na),!d)this._minorAutoInterval=a/5;if(!d)this.tickInterval=a;return g},getMinorTickPositions:function(){var a=this.options,b=this.tickPositions,c=this.minorTickInterval,d=[],e;if(this.isLog){e=b.length;for(a=1;a<e;a++)d=d.concat(this.getLogTickPositions(c,\r\nb[a-1],b[a],!0))}else if(this.isDatetimeAxis&&a.minorTickInterval===\"auto\")d=d.concat(Eb(Cb(c),this.min,this.max,a.startOfWeek)),d[0]<this.min&&d.shift();else for(b=this.min+(b[0]-this.min)%c;b<=this.max;b+=c)d.push(b);return d},adjustForMinRange:function(){var a=this.options,b=this.min,c=this.max,d,e=this.dataMax-this.dataMin>=this.minRange,f,g,h,i,j;if(this.isXAxis&&this.minRange===w&&!this.isLog)u(a.min)||u(a.max)?this.minRange=null:(n(this.series,function(a){i=a.xData;for(g=j=a.xIncrement?1:i.length-\r\n1;g>0;g--)if(h=i[g]-i[g-1],f===w||h<f)f=h}),this.minRange=I(f*5,this.dataMax-this.dataMin));if(c-b<this.minRange){var k=this.minRange;d=(k-c+b)/2;d=[b-d,o(a.min,b-d)];if(e)d[2]=this.dataMin;b=va(d);c=[b+k,o(a.max,b+k)];if(e)c[2]=this.dataMax;c=Ja(c);c-b<k&&(d[0]=c-k,d[1]=o(a.min,c-k),b=va(d))}this.min=b;this.max=c},setAxisTranslation:function(a){var b=this.max-this.min,c=0,d,e=0,f=0,g=this.linkedParent,h=this.transA;if(this.isXAxis)g?(e=g.minPointOffset,f=g.pointRangePadding):n(this.series,function(a){var g=\r\na.pointRange,h=a.options.pointPlacement,l=a.closestPointRange;g>b&&(g=0);c=s(c,g);e=s(e,ea(h)?0:g/2);f=s(f,h===\"on\"?0:g);!a.noSharedTooltip&&u(l)&&(d=u(d)?I(d,l):l)}),g=this.ordinalSlope&&d?this.ordinalSlope/d:1,this.minPointOffset=e*=g,this.pointRangePadding=f*=g,this.pointRange=I(c,b),this.closestPointRange=d;if(a)this.oldTransA=h;this.translationSlope=this.transA=h=this.len/(b+f||1);this.transB=this.horiz?this.left:this.bottom;this.minPixelPadding=h*e},setTickPositions:function(a){var b=this,c=\r\nb.chart,d=b.options,e=b.isLog,f=b.isDatetimeAxis,g=b.isXAxis,h=b.isLinked,i=b.options.tickPositioner,j=d.maxPadding,k=d.minPadding,l=d.tickInterval,m=d.minTickInterval,p=d.tickPixelInterval,q,ba=b.categories;h?(b.linkedParent=c[g?\"xAxis\":\"yAxis\"][d.linkedTo],c=b.linkedParent.getExtremes(),b.min=o(c.min,c.dataMin),b.max=o(c.max,c.dataMax),d.type!==b.linkedParent.options.type&&ka(11,1)):(b.min=o(b.userMin,d.min,b.dataMin),b.max=o(b.userMax,d.max,b.dataMax));if(e)!a&&I(b.min,o(b.dataMin,b.min))<=0&&\r\nka(10,1),b.min=ia(na(b.min)),b.max=ia(na(b.max));if(b.range&&(b.userMin=b.min=s(b.min,b.max-b.range),b.userMax=b.max,a))b.range=null;b.beforePadding&&b.beforePadding();b.adjustForMinRange();if(!ba&&!b.usePercentage&&!h&&u(b.min)&&u(b.max)&&(c=b.max-b.min)){if(!u(d.min)&&!u(b.userMin)&&k&&(b.dataMin<0||!b.ignoreMinPadding))b.min-=c*k;if(!u(d.max)&&!u(b.userMax)&&j&&(b.dataMax>0||!b.ignoreMaxPadding))b.max+=c*j}b.min===b.max||b.min===void 0||b.max===void 0?b.tickInterval=1:h&&!l&&p===b.linkedParent.options.tickPixelInterval?\r\nb.tickInterval=b.linkedParent.tickInterval:(b.tickInterval=o(l,ba?1:(b.max-b.min)*p/s(b.len,p)),!u(l)&&b.len<p&&!this.isRadial&&(q=!0,b.tickInterval/=4));g&&!a&&n(b.series,function(a){a.processData(b.min!==b.oldMin||b.max!==b.oldMax)});b.setAxisTranslation(!0);b.beforeSetTickPositions&&b.beforeSetTickPositions();if(b.postProcessTickInterval)b.tickInterval=b.postProcessTickInterval(b.tickInterval);if(b.pointRange)b.tickInterval=s(b.pointRange,b.tickInterval);if(!l&&b.tickInterval<m)b.tickInterval=\r\nm;if(!f&&!e&&!l)b.tickInterval=ob(b.tickInterval,null,nb(b.tickInterval),d);b.minorTickInterval=d.minorTickInterval===\"auto\"&&b.tickInterval?b.tickInterval/5:d.minorTickInterval;b.tickPositions=a=d.tickPositions?[].concat(d.tickPositions):i&&i.apply(b,[b.min,b.max]);if(!a)!b.ordinalPositions&&(b.max-b.min)/b.tickInterval>s(2*b.len,200)&&ka(19,!0),a=f?(b.getNonLinearTimeTicks||Eb)(Cb(b.tickInterval,d.units),b.min,b.max,d.startOfWeek,b.ordinalPositions,b.closestPointRange,!0):e?b.getLogTickPositions(b.tickInterval,\r\nb.min,b.max):b.getLinearTickPositions(b.tickInterval,b.min,b.max),q&&a.splice(1,a.length-2),b.tickPositions=a;if(!h)e=a[0],f=a[a.length-1],h=b.minPointOffset||0,d.startOnTick?b.min=e:b.min-h>e&&a.shift(),d.endOnTick?b.max=f:b.max+h<f&&a.pop(),a.length===1&&(b.min-=0.001,b.max+=0.001)},setMaxTicks:function(){var a=this.chart,b=a.maxTicks||{},c=this.tickPositions,d=this._maxTicksKey=[this.xOrY,this.pos,this.len].join(\"-\");if(!this.isLinked&&!this.isDatetimeAxis&&c&&c.length>(b[d]||0)&&this.options.alignTicks!==\r\n!1)b[d]=c.length;a.maxTicks=b},adjustTickAmount:function(){var a=this._maxTicksKey,b=this.tickPositions,c=this.chart.maxTicks;if(c&&c[a]&&!this.isDatetimeAxis&&!this.categories&&!this.isLinked&&this.options.alignTicks!==!1){var d=this.tickAmount,e=b.length;this.tickAmount=a=c[a];if(e<a){for(;b.length<a;)b.push(ia(b[b.length-1]+this.tickInterval));this.transA*=(e-1)/(a-1);this.max=b[b.length-1]}if(u(d)&&a!==d)this.isDirty=!0}},setScale:function(){var a=this.stacks,b,c,d,e;this.oldMin=this.min;this.oldMax=\r\nthis.max;this.oldAxisLength=this.len;this.setAxisSize();e=this.len!==this.oldAxisLength;n(this.series,function(a){if(a.isDirtyData||a.isDirty||a.xAxis.isDirty)d=!0});if(e||d||this.isLinked||this.forceRedraw||this.userMin!==this.oldUserMin||this.userMax!==this.oldUserMax){if(!this.isXAxis)for(b in a)delete a[b];this.forceRedraw=!1;this.getSeriesExtremes();this.setTickPositions();this.oldUserMin=this.userMin;this.oldUserMax=this.userMax;if(!this.isDirty)this.isDirty=e||this.min!==this.oldMin||this.max!==\r\nthis.oldMax}else if(!this.isXAxis){if(this.oldStacks)a=this.stacks=this.oldStacks;for(b in a)for(c in a[b])a[b][c].cum=a[b][c].total}this.setMaxTicks()},setExtremes:function(a,b,c,d,e){var f=this,g=f.chart,c=o(c,!0),e=r(e,{min:a,max:b});z(f,\"setExtremes\",e,function(){f.userMin=a;f.userMax=b;f.eventArgs=e;f.isDirtyExtremes=!0;c&&g.redraw(d)})},zoom:function(a,b){this.allowZoomOutside||(u(this.dataMin)&&a<=this.dataMin&&(a=w),u(this.dataMax)&&b>=this.dataMax&&(b=w));this.displayBtn=a!==w||b!==w;this.setExtremes(a,\r\nb,!1,w,{trigger:\"zoom\"});return!0},setAxisSize:function(){var a=this.chart,b=this.options,c=b.offsetLeft||0,d=b.offsetRight||0,e=this.horiz,f,g;this.left=g=o(b.left,a.plotLeft+c);this.top=f=o(b.top,a.plotTop);this.width=c=o(b.width,a.plotWidth-c+d);this.height=b=o(b.height,a.plotHeight);this.bottom=a.chartHeight-b-f;this.right=a.chartWidth-c-g;this.len=s(e?c:b,0);this.pos=e?g:f},getExtremes:function(){var a=this.isLog;return{min:a?ia(fa(this.min)):this.min,max:a?ia(fa(this.max)):this.max,dataMin:this.dataMin,\r\ndataMax:this.dataMax,userMin:this.userMin,userMax:this.userMax}},getThreshold:function(a){var b=this.isLog,c=b?fa(this.min):this.min,b=b?fa(this.max):this.max;c>a||a===null?a=c:b<a&&(a=b);return this.translate(a,0,1,0,1)},addPlotBand:function(a){this.addPlotBandOrLine(a,\"plotBands\")},addPlotLine:function(a){this.addPlotBandOrLine(a,\"plotLines\")},addPlotBandOrLine:function(a,b){var c=(new vb(this,a)).render(),d=this.userOptions;c&&(b&&(d[b]=d[b]||[],d[b].push(a)),this.plotLinesAndBands.push(c));return c},\r\nautoLabelAlign:function(a){a=(o(a,0)-this.side*90+720)%360;return a>15&&a<165?\"right\":a>195&&a<345?\"left\":\"center\"},getOffset:function(){var a=this,b=a.chart,c=b.renderer,d=a.options,e=a.tickPositions,f=a.ticks,g=a.horiz,h=a.side,i=b.inverted?[1,0,3,2][h]:h,j,k=0,l,m=0,p=d.title,q=d.labels,ba=0,A=b.axisOffset,L=b.clipOffset,t=[-1,1,1,-1][h],r,v=1,x=o(q.maxStaggerLines,5),la,E,H,B;a.hasData=j=a.hasVisibleSeries||u(a.min)&&u(a.max)&&!!e;a.showAxis=b=j||o(d.showEmpty,!0);a.staggerLines=a.horiz&&q.staggerLines;\r\nif(!a.axisGroup)a.gridGroup=c.g(\"grid\").attr({zIndex:d.gridZIndex||1}).add(),a.axisGroup=c.g(\"axis\").attr({zIndex:d.zIndex||2}).add(),a.labelGroup=c.g(\"axis-labels\").attr({zIndex:q.zIndex||7}).add();if(j||a.isLinked){a.labelAlign=o(q.align||a.autoLabelAlign(q.rotation));n(e,function(b){f[b]?f[b].addLabel():f[b]=new Ma(a,b)});if(a.horiz&&!a.staggerLines&&x&&!q.rotation){for(r=a.reversed?[].concat(e).reverse():e;v<x;){j=[];la=!1;for(q=0;q<r.length;q++)E=r[q],H=(H=f[E].label&&f[E].label.getBBox())?H.width:\r\n0,B=q%v,H&&(E=a.translate(E),j[B]!==w&&E<j[B]&&(la=!0),j[B]=E+H);if(la)v++;else break}if(v>1)a.staggerLines=v}n(e,function(b){if(h===0||h===2||{1:\"left\",3:\"right\"}[h]===a.labelAlign)ba=s(f[b].getLabelSize(),ba)});if(a.staggerLines)ba*=a.staggerLines,a.labelOffset=ba}else for(r in f)f[r].destroy(),delete f[r];if(p&&p.text&&p.enabled!==!1){if(!a.axisTitle)a.axisTitle=c.text(p.text,0,0,p.useHTML).attr({zIndex:7,rotation:p.rotation||0,align:p.textAlign||{low:\"left\",middle:\"center\",high:\"right\"}[p.align]}).css(p.style).add(a.axisGroup),\r\na.axisTitle.isNew=!0;if(b)k=a.axisTitle.getBBox()[g?\"height\":\"width\"],m=o(p.margin,g?5:10),l=p.offset;a.axisTitle[b?\"show\":\"hide\"]()}a.offset=t*o(d.offset,A[h]);a.axisTitleMargin=o(l,ba+m+(h!==2&&ba&&t*d.labels[g?\"y\":\"x\"]));A[h]=s(A[h],a.axisTitleMargin+k+t*a.offset);L[i]=s(L[i],P(d.lineWidth/2)*2)},getLinePath:function(a){var b=this.chart,c=this.opposite,d=this.offset,e=this.horiz,f=this.left+(c?this.width:0)+d,d=b.chartHeight-this.bottom-(c?this.height:0)+d;c&&(a*=-1);return b.renderer.crispLine([\"M\",\r\ne?this.left:f,e?d:this.top,\"L\",e?b.chartWidth-this.right:f,e?d:b.chartHeight-this.bottom],a)},getTitlePosition:function(){var a=this.horiz,b=this.left,c=this.top,d=this.len,e=this.options.title,f=a?b:c,g=this.opposite,h=this.offset,i=C(e.style.fontSize||12),d={low:f+(a?0:d),middle:f+d/2,high:f+(a?d:0)}[e.align],b=(a?c+this.height:b)+(a?1:-1)*(g?-1:1)*this.axisTitleMargin+(this.side===2?i:0);return{x:a?d:b+(g?this.width:0)+h+(e.x||0),y:a?b-(g?this.height:0)+h:d+(e.y||0)}},render:function(){var a=this,\r\nb=a.chart,c=b.renderer,d=a.options,e=a.isLog,f=a.isLinked,g=a.tickPositions,h=a.axisTitle,i=a.stacks,j=a.ticks,k=a.minorTicks,l=a.alternateBands,m=d.stackLabels,p=d.alternateGridColor,q=a.tickmarkOffset,o=d.lineWidth,A,s=b.hasRendered&&u(a.oldMin)&&!isNaN(a.oldMin);A=a.hasData;var t=a.showAxis,r,v;n([j,k,l],function(a){for(var b in a)a[b].isActive=!1});if(A||f)if(a.minorTickInterval&&!a.categories&&n(a.getMinorTickPositions(),function(b){k[b]||(k[b]=new Ma(a,b,\"minor\"));s&&k[b].isNew&&k[b].render(null,\r\n!0);k[b].render(null,!1,1)}),g.length&&(n(g.slice(1).concat([g[0]]),function(b,c){c=c===g.length-1?0:c+1;if(!f||b>=a.min&&b<=a.max)j[b]||(j[b]=new Ma(a,b)),s&&j[b].isNew&&j[b].render(c,!0),j[b].render(c,!1,1)}),q&&a.min===0&&(j[-1]||(j[-1]=new Ma(a,-1,null,!0)),j[-1].render(-1))),p&&n(g,function(b,c){if(c%2===0&&b<a.max)l[b]||(l[b]=new vb(a)),r=b+q,v=g[c+1]!==w?g[c+1]+q:a.max,l[b].options={from:e?fa(r):r,to:e?fa(v):v,color:p},l[b].render(),l[b].isActive=!0}),!a._addedPlotLB)n((d.plotLines||[]).concat(d.plotBands||\r\n[]),function(b){a.addPlotBandOrLine(b)}),a._addedPlotLB=!0;n([j,k,l],function(a){var c,d,e=[],f=Fa?Fa.duration||500:0,g=function(){for(d=e.length;d--;)a[e[d]]&&!a[e[d]].isActive&&(a[e[d]].destroy(),delete a[e[d]])};for(c in a)if(!a[c].isActive)a[c].render(c,!1,0),a[c].isActive=!1,e.push(c);a===l||!b.hasRendered||!f?g():f&&setTimeout(g,f)});if(o)A=a.getLinePath(o),a.axisLine?a.axisLine.animate({d:A}):a.axisLine=c.path(A).attr({stroke:d.lineColor,\"stroke-width\":o,zIndex:7}).add(a.axisGroup),a.axisLine[t?\r\n\"show\":\"hide\"]();if(h&&t)h[h.isNew?\"attr\":\"animate\"](a.getTitlePosition()),h.isNew=!1;if(m&&m.enabled){var x,la,d=a.stackTotalGroup;if(!d)a.stackTotalGroup=d=c.g(\"stack-labels\").attr({visibility:\"visible\",zIndex:6}).add();d.translate(b.plotLeft,b.plotTop);for(x in i)for(la in c=i[x],c)c[la].render(d)}a.isDirty=!1},removePlotBandOrLine:function(a){for(var b=this.plotLinesAndBands,c=this.options,d=this.userOptions,e=b.length;e--;)b[e].id===a&&b[e].destroy();n([c.plotLines||[],d.plotLines||[],c.plotBands||\r\n[],d.plotBands||[]],function(b){for(e=b.length;e--;)b[e].id===a&&ga(b,b[e])})},setTitle:function(a,b){this.update({title:a},b)},redraw:function(){var a=this.chart.pointer;a.reset&&a.reset(!0);this.render();n(this.plotLinesAndBands,function(a){a.render()});n(this.series,function(a){a.isDirty=!0})},buildStacks:function(){var a=this.series,b=a.length;if(!this.isXAxis){for(;b--;)a[b].setStackedPoints();if(this.usePercentage)for(b=0;b<a.length;b++)a[b].setPercentStacks()}},setCategories:function(a,b){this.update({categories:a},\r\nb)},destroy:function(a){var b=this,c=b.stacks,d,e=b.plotLinesAndBands;a||aa(b);for(d in c)Ka(c[d]),c[d]=null;n([b.ticks,b.minorTicks,b.alternateBands],function(a){Ka(a)});for(a=e.length;a--;)e[a].destroy();n(\"stackTotalGroup,axisLine,axisGroup,gridGroup,labelGroup,axisTitle\".split(\",\"),function(a){b[a]&&(b[a]=b[a].destroy())})}};wb.prototype={init:function(a,b){var c=b.borderWidth,d=b.style,e=C(d.padding);this.chart=a;this.options=b;this.crosshairs=[];this.now={x:0,y:0};this.isHidden=!0;this.label=\r\na.renderer.label(\"\",0,0,b.shape,null,null,b.useHTML,null,\"tooltip\").attr({padding:e,fill:b.backgroundColor,\"stroke-width\":c,r:b.borderRadius,zIndex:8}).css(d).css({padding:0}).add().attr({y:-999});$||this.label.shadow(b.shadow);this.shared=b.shared},destroy:function(){n(this.crosshairs,function(a){a&&a.destroy()});if(this.label)this.label=this.label.destroy();clearTimeout(this.hideTimer);clearTimeout(this.tooltipTimeout)},move:function(a,b,c,d){var e=this,f=e.now,g=e.options.animation!==!1&&!e.isHidden;\r\nr(f,{x:g?(2*f.x+a)/3:a,y:g?(f.y+b)/2:b,anchorX:g?(2*f.anchorX+c)/3:c,anchorY:g?(f.anchorY+d)/2:d});e.label.attr(f);if(g&&(N(a-f.x)>1||N(b-f.y)>1))clearTimeout(this.tooltipTimeout),this.tooltipTimeout=setTimeout(function(){e&&e.move(a,b,c,d)},32)},hide:function(){var a=this,b;clearTimeout(this.hideTimer);if(!this.isHidden)b=this.chart.hoverPoints,this.hideTimer=setTimeout(function(){a.label.fadeOut();a.isHidden=!0},o(this.options.hideDelay,500)),b&&n(b,function(a){a.setState()}),this.chart.hoverPoints=\r\nnull},hideCrosshairs:function(){n(this.crosshairs,function(a){a&&a.hide()})},getAnchor:function(a,b){var c,d=this.chart,e=d.inverted,f=d.plotTop,g=0,h=0,i,a=ja(a);c=a[0].tooltipPos;this.followPointer&&b&&(b.chartX===w&&(b=d.pointer.normalize(b)),c=[b.chartX-d.plotLeft,b.chartY-f]);c||(n(a,function(a){i=a.series.yAxis;g+=a.plotX;h+=(a.plotLow?(a.plotLow+a.plotHigh)/2:a.plotY)+(!e&&i?i.top-f:0)}),g/=a.length,h/=a.length,c=[e?d.plotWidth-h:g,this.shared&&!e&&a.length>1&&b?b.chartY-f:e?d.plotHeight-g:\r\nh]);return Na(c,t)},getPosition:function(a,b,c){var d=this.chart,e=d.plotLeft,f=d.plotTop,g=d.plotWidth,h=d.plotHeight,i=o(this.options.distance,12),j=c.plotX,c=c.plotY,d=j+e+(d.inverted?i:-a-i),k=c-b+f+15,l;d<7&&(d=e+s(j,0)+i);d+a>e+g&&(d-=d+a-(e+g),k=c-b+f-i,l=!0);k<f+5&&(k=f+5,l&&c>=k&&c<=k+b&&(k=c+f+i));k+b>f+h&&(k=s(f,f+h-b-i));return{x:d,y:k}},defaultFormatter:function(a){var b=this.points||ja(this),c=b[0].series,d;d=[c.tooltipHeaderFormatter(b[0])];n(b,function(a){c=a.series;d.push(c.tooltipFormatter&&\r\nc.tooltipFormatter(a)||a.point.tooltipFormatter(c.tooltipOptions.pointFormat))});d.push(a.options.footerFormat||\"\");return d.join(\"\")},refresh:function(a,b){var c=this.chart,d=this.label,e=this.options,f,g,h={},i,j=[];i=e.formatter||this.defaultFormatter;var h=c.hoverPoints,k,l=e.crosshairs,m=this.shared;clearTimeout(this.hideTimer);this.followPointer=ja(a)[0].series.tooltipOptions.followPointer;g=this.getAnchor(a,b);f=g[0];g=g[1];m&&(!a.series||!a.series.noSharedTooltip)?(c.hoverPoints=a,h&&n(h,\r\nfunction(a){a.setState()}),n(a,function(a){a.setState(\"hover\");j.push(a.getLabelConfig())}),h={x:a[0].category,y:a[0].y},h.points=j,a=a[0]):h=a.getLabelConfig();i=i.call(h,this);h=a.series;i===!1?this.hide():(this.isHidden&&(Wa(d),d.attr(\"opacity\",1).show()),d.attr({text:i}),k=e.borderColor||a.color||h.color||\"#606060\",d.attr({stroke:k}),this.updatePosition({plotX:f,plotY:g}),this.isHidden=!1);if(l){l=ja(l);for(d=l.length;d--;)if(m=a.series,e=m[d?\"yAxis\":\"xAxis\"],l[d]&&e)if(h=d?o(a.stackY,a.y):a.x,\r\ne.isLog&&(h=na(h)),d===1&&m.modifyValue&&(h=m.modifyValue(h)),e=e.getPlotLinePath(h,1),this.crosshairs[d])this.crosshairs[d].attr({d:e,visibility:\"visible\"});else{h={\"stroke-width\":l[d].width||1,stroke:l[d].color||\"#C0C0C0\",zIndex:l[d].zIndex||2};if(l[d].dashStyle)h.dashstyle=l[d].dashStyle;this.crosshairs[d]=c.renderer.path(e).attr(h).add()}}z(c,\"tooltipRefresh\",{text:i,x:f+c.plotLeft,y:g+c.plotTop,borderColor:k})},updatePosition:function(a){var b=this.chart,c=this.label,c=(this.options.positioner||\r\nthis.getPosition).call(this,c.width,c.height,a);this.move(t(c.x),t(c.y),a.plotX+b.plotLeft,a.plotY+b.plotTop)}};xb.prototype={init:function(a,b){var c=b.chart,d=c.events,e=$?\"\":c.zoomType,c=a.inverted,f;this.options=b;this.chart=a;this.zoomX=f=/x/.test(e);this.zoomY=e=/y/.test(e);this.zoomHor=f&&!c||e&&c;this.zoomVert=e&&!c||f&&c;this.runChartClick=d&&!!d.click;this.pinchDown=[];this.lastValidTouch={};if(b.tooltip.enabled)a.tooltip=new wb(a,b.tooltip);this.setDOMEvents()},normalize:function(a,b){var c,\r\nd,a=a||O.event;if(!a.target)a.target=a.srcElement;a=Xb(a);d=a.touches?a.touches.item(0):a;if(!b)this.chartPosition=b=Wb(this.chart.container);d.pageX===w?(c=s(a.x,a.clientX-b.left),d=a.y):(c=d.pageX-b.left,d=d.pageY-b.top);return r(a,{chartX:t(c),chartY:t(d)})},getCoordinates:function(a){var b={xAxis:[],yAxis:[]};n(this.chart.axes,function(c){b[c.isXAxis?\"xAxis\":\"yAxis\"].push({axis:c,value:c.toValue(a[c.horiz?\"chartX\":\"chartY\"])})});return b},getIndex:function(a){var b=this.chart;return b.inverted?\r\nb.plotHeight+b.plotTop-a.chartY:a.chartX-b.plotLeft},runPointActions:function(a){var b=this.chart,c=b.series,d=b.tooltip,e,f=b.hoverPoint,g=b.hoverSeries,h,i,j=b.chartWidth,k=this.getIndex(a);if(d&&this.options.tooltip.shared&&(!g||!g.noSharedTooltip)){e=[];h=c.length;for(i=0;i<h;i++)if(c[i].visible&&c[i].options.enableMouseTracking!==!1&&!c[i].noSharedTooltip&&c[i].tooltipPoints.length&&(b=c[i].tooltipPoints[k])&&b.series)b._dist=N(k-b.clientX),j=I(j,b._dist),e.push(b);for(h=e.length;h--;)e[h]._dist>\r\nj&&e.splice(h,1);if(e.length&&e[0].clientX!==this.hoverX)d.refresh(e,a),this.hoverX=e[0].clientX}if(g&&g.tracker){if((b=g.tooltipPoints[k])&&b!==f)b.onMouseOver(a)}else d&&d.followPointer&&!d.isHidden&&(a=d.getAnchor([{}],a),d.updatePosition({plotX:a[0],plotY:a[1]}))},reset:function(a){var b=this.chart,c=b.hoverSeries,d=b.hoverPoint,e=b.tooltip,b=e&&e.shared?b.hoverPoints:d;(a=a&&e&&b)&&ja(b)[0].plotX===w&&(a=!1);if(a)e.refresh(b);else{if(d)d.onMouseOut();if(c)c.onMouseOut();e&&(e.hide(),e.hideCrosshairs());\r\nthis.hoverX=null}},scaleGroups:function(a,b){var c=this.chart,d;n(c.series,function(e){d=a||e.getPlotBox();e.xAxis&&e.xAxis.zoomEnabled&&(e.group.attr(d),e.markerGroup&&(e.markerGroup.attr(d),e.markerGroup.clip(b?c.clipRect:null)),e.dataLabelsGroup&&e.dataLabelsGroup.attr(d))});c.clipRect.attr(b||c.clipBox)},pinchTranslateDirection:function(a,b,c,d,e,f,g){var h=this.chart,i=a?\"x\":\"y\",j=a?\"X\":\"Y\",k=\"chart\"+j,l=a?\"width\":\"height\",m=h[\"plot\"+(a?\"Left\":\"Top\")],p,q,o=1,n=h.inverted,s=h.bounds[a?\"h\":\"v\"],\r\nt=b.length===1,u=b[0][k],r=c[0][k],w=!t&&b[1][k],v=!t&&c[1][k],x,c=function(){!t&&N(u-w)>20&&(o=N(r-v)/N(u-w));q=(m-r)/o+u;p=h[\"plot\"+(a?\"Width\":\"Height\")]/o};c();b=q;b<s.min?(b=s.min,x=!0):b+p>s.max&&(b=s.max-p,x=!0);x?(r-=0.8*(r-g[i][0]),t||(v-=0.8*(v-g[i][1])),c()):g[i]=[r,v];n||(f[i]=q-m,f[l]=p);f=n?1/o:o;e[l]=p;e[i]=b;d[n?a?\"scaleY\":\"scaleX\":\"scale\"+j]=o;d[\"translate\"+j]=f*m+(r-f*u)},pinch:function(a){var b=this,c=b.chart,d=b.pinchDown,e=c.tooltip&&c.tooltip.options.followTouchMove,f=a.touches,\r\ng=f.length,h=b.lastValidTouch,i=b.zoomHor||b.pinchHor,j=b.zoomVert||b.pinchVert,k=i||j,l=b.selectionMarker,m={},p=g===1&&(b.inClass(a.target,\"highcharts-tracker\")&&c.runTrackerClick||c.runChartClick),q={};(k||e)&&!p&&a.preventDefault();Na(f,function(a){return b.normalize(a)});if(a.type===\"touchstart\")n(f,function(a,b){d[b]={chartX:a.chartX,chartY:a.chartY}}),h.x=[d[0].chartX,d[1]&&d[1].chartX],h.y=[d[0].chartY,d[1]&&d[1].chartY],n(c.axes,function(a){if(a.zoomEnabled){var b=c.bounds[a.horiz?\"h\":\"v\"],\r\nd=a.minPixelPadding,e=a.toPixels(a.dataMin),f=a.toPixels(a.dataMax),g=I(e,f),e=s(e,f);b.min=I(a.pos,g-d);b.max=s(a.pos+a.len,e+d)}});else if(d.length){if(!l)b.selectionMarker=l=r({destroy:pa},c.plotBox);i&&b.pinchTranslateDirection(!0,d,f,m,l,q,h);j&&b.pinchTranslateDirection(!1,d,f,m,l,q,h);b.hasPinched=k;b.scaleGroups(m,q);!k&&e&&g===1&&this.runPointActions(b.normalize(a))}},dragStart:function(a){var b=this.chart;b.mouseIsDown=a.type;b.cancelClick=!1;b.mouseDownX=this.mouseDownX=a.chartX;b.mouseDownY=\r\nthis.mouseDownY=a.chartY},drag:function(a){var b=this.chart,c=b.options.chart,d=a.chartX,e=a.chartY,f=this.zoomHor,g=this.zoomVert,h=b.plotLeft,i=b.plotTop,j=b.plotWidth,k=b.plotHeight,l,m=this.mouseDownX,p=this.mouseDownY;d<h?d=h:d>h+j&&(d=h+j);e<i?e=i:e>i+k&&(e=i+k);this.hasDragged=Math.sqrt(Math.pow(m-d,2)+Math.pow(p-e,2));if(this.hasDragged>10){l=b.isInsidePlot(m-h,p-i);if(b.hasCartesianSeries&&(this.zoomX||this.zoomY)&&l&&!this.selectionMarker)this.selectionMarker=b.renderer.rect(h,i,f?1:j,g?\r\n1:k,0).attr({fill:c.selectionMarkerFill||\"rgba(69,114,167,0.25)\",zIndex:7}).add();this.selectionMarker&&f&&(d-=m,this.selectionMarker.attr({width:N(d),x:(d>0?0:d)+m}));this.selectionMarker&&g&&(d=e-p,this.selectionMarker.attr({height:N(d),y:(d>0?0:d)+p}));l&&!this.selectionMarker&&c.panning&&b.pan(a,c.panning)}},drop:function(a){var b=this.chart,c=this.hasPinched;if(this.selectionMarker){var d={xAxis:[],yAxis:[],originalEvent:a.originalEvent||a},e=this.selectionMarker,f=e.x,g=e.y,h;if(this.hasDragged||\r\nc)n(b.axes,function(a){if(a.zoomEnabled){var b=a.horiz,c=a.toValue(b?f:g),b=a.toValue(b?f+e.width:g+e.height);!isNaN(c)&&!isNaN(b)&&(d[a.xOrY+\"Axis\"].push({axis:a,min:I(c,b),max:s(c,b)}),h=!0)}}),h&&z(b,\"selection\",d,function(a){b.zoom(r(a,c?{animation:!1}:null))});this.selectionMarker=this.selectionMarker.destroy();c&&this.scaleGroups()}if(b)K(b.container,{cursor:b._cursor}),b.cancelClick=this.hasDragged>10,b.mouseIsDown=this.hasDragged=this.hasPinched=!1,this.pinchDown=[]},onContainerMouseDown:function(a){a=\r\nthis.normalize(a);a.preventDefault&&a.preventDefault();this.dragStart(a)},onDocumentMouseUp:function(a){this.drop(a)},onDocumentMouseMove:function(a){var b=this.chart,c=this.chartPosition,d=b.hoverSeries,a=this.normalize(a,c);c&&d&&!this.inClass(a.target,\"highcharts-tracker\")&&!b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-b.plotTop)&&this.reset()},onContainerMouseLeave:function(){this.reset();this.chartPosition=null},onContainerMouseMove:function(a){var b=this.chart,a=this.normalize(a);a.returnValue=\r\n!1;b.mouseIsDown===\"mousedown\"&&this.drag(a);(this.inClass(a.target,\"highcharts-tracker\")||b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-b.plotTop))&&!b.openMenu&&this.runPointActions(a)},inClass:function(a,b){for(var c;a;){if(c=v(a,\"class\"))if(c.indexOf(b)!==-1)return!0;else if(c.indexOf(\"highcharts-container\")!==-1)return!1;a=a.parentNode}},onTrackerMouseOut:function(a){var b=this.chart.hoverSeries;if(b&&!b.options.stickyTracking&&!this.inClass(a.toElement||a.relatedTarget,\"highcharts-tooltip\"))b.onMouseOut()},\r\nonContainerClick:function(a){var b=this.chart,c=b.hoverPoint,d=b.plotLeft,e=b.plotTop,f=b.inverted,g,h,i,a=this.normalize(a);a.cancelBubble=!0;if(!b.cancelClick)c&&this.inClass(a.target,\"highcharts-tracker\")?(g=this.chartPosition,h=c.plotX,i=c.plotY,r(c,{pageX:g.left+d+(f?b.plotWidth-i:h),pageY:g.top+e+(f?b.plotHeight-h:i)}),z(c.series,\"click\",r(a,{point:c})),b.hoverPoint&&c.firePointEvent(\"click\",a)):(r(a,this.getCoordinates(a)),b.isInsidePlot(a.chartX-d,a.chartY-e)&&z(b,\"click\",a))},onContainerTouchStart:function(a){var b=\r\nthis.chart;a.touches.length===1?(a=this.normalize(a),b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-b.plotTop)?(this.runPointActions(a),this.pinch(a)):this.reset()):a.touches.length===2&&this.pinch(a)},onContainerTouchMove:function(a){(a.touches.length===1||a.touches.length===2)&&this.pinch(a)},onDocumentTouchEnd:function(a){this.drop(a)},setDOMEvents:function(){var a=this,b=a.chart.container,c;this._events=c=[[b,\"onmousedown\",\"onContainerMouseDown\"],[b,\"onmousemove\",\"onContainerMouseMove\"],[b,\"onclick\",\r\n\"onContainerClick\"],[b,\"mouseleave\",\"onContainerMouseLeave\"],[y,\"mousemove\",\"onDocumentMouseMove\"],[y,\"mouseup\",\"onDocumentMouseUp\"]];ib&&c.push([b,\"ontouchstart\",\"onContainerTouchStart\"],[b,\"ontouchmove\",\"onContainerTouchMove\"],[y,\"touchend\",\"onDocumentTouchEnd\"]);n(c,function(b){a[\"_\"+b[2]]=function(c){a[b[2]](c)};b[1].indexOf(\"on\")===0?b[0][b[1]]=a[\"_\"+b[2]]:J(b[0],b[1],a[\"_\"+b[2]])})},destroy:function(){var a=this;n(a._events,function(b){b[1].indexOf(\"on\")===0?b[0][b[1]]=null:aa(b[0],b[1],a[\"_\"+\r\nb[2]])});delete a._events;clearInterval(a.tooltipTimeout)}};eb.prototype={init:function(a,b){var c=this,d=b.itemStyle,e=o(b.padding,8),f=b.itemMarginTop||0;this.options=b;if(b.enabled)c.baseline=C(d.fontSize)+3+f,c.itemStyle=d,c.itemHiddenStyle=x(d,b.itemHiddenStyle),c.itemMarginTop=f,c.padding=e,c.initialItemX=e,c.initialItemY=e-5,c.maxItemWidth=0,c.chart=a,c.itemHeight=0,c.lastLineHeight=0,c.render(),J(c.chart,\"endResize\",function(){c.positionCheckboxes()})},colorizeItem:function(a,b){var c=this.options,\r\nd=a.legendItem,e=a.legendLine,f=a.legendSymbol,g=this.itemHiddenStyle.color,c=b?c.itemStyle.color:g,h=b?a.color:g,g=a.options&&a.options.marker,i={stroke:h,fill:h},j;d&&d.css({fill:c,color:c});e&&e.attr({stroke:h});if(f){if(g&&f.isMarker)for(j in g=a.convertAttribs(g),g)d=g[j],d!==w&&(i[j]=d);f.attr(i)}},positionItem:function(a){var b=this.options,c=b.symbolPadding,b=!b.rtl,d=a._legendItemPos,e=d[0],d=d[1],f=a.checkbox;a.legendGroup&&a.legendGroup.translate(b?e:this.legendWidth-e-2*c-4,d);if(f)f.x=\r\ne,f.y=d},destroyItem:function(a){var b=a.checkbox;n([\"legendItem\",\"legendLine\",\"legendSymbol\",\"legendGroup\"],function(b){a[b]&&(a[b]=a[b].destroy())});b&&Ta(a.checkbox)},destroy:function(){var a=this.group,b=this.box;if(b)this.box=b.destroy();if(a)this.group=a.destroy()},positionCheckboxes:function(a){var b=this.group.alignAttr,c,d=this.clipHeight||this.legendHeight;if(b)c=b.translateY,n(this.allItems,function(e){var f=e.checkbox,g;f&&(g=c+f.y+(a||0)+3,K(f,{left:b.translateX+e.legendItemWidth+f.x-\r\n20+\"px\",top:g+\"px\",display:g>c-6&&g<c+d-6?\"\":S}))})},renderTitle:function(){var a=this.padding,b=this.options.title,c=0;if(b.text){if(!this.title)this.title=this.chart.renderer.label(b.text,a-3,a-4,null,null,null,null,null,\"legend-title\").attr({zIndex:1}).css(b.style).add(this.group);a=this.title.getBBox();c=a.height;this.offsetWidth=a.width;this.contentGroup.attr({translateY:c})}this.titleHeight=c},renderItem:function(a){var B;var b=this,c=b.chart,d=c.renderer,e=b.options,f=e.layout===\"horizontal\",\r\ng=e.symbolWidth,h=e.symbolPadding,i=b.itemStyle,j=b.itemHiddenStyle,k=b.padding,l=f?o(e.itemDistance,8):0,m=!e.rtl,p=e.width,q=e.itemMarginBottom||0,n=b.itemMarginTop,A=b.initialItemX,t=a.legendItem,u=a.series||a,r=u.options,w=r.showCheckbox,v=e.useHTML;if(!t&&(a.legendGroup=d.g(\"legend-item\").attr({zIndex:1}).add(b.scrollGroup),u.drawLegendSymbol(b,a),a.legendItem=t=d.text(e.labelFormat?Ca(e.labelFormat,a):e.labelFormatter.call(a),m?g+h:-h,b.baseline,v).css(x(a.visible?i:j)).attr({align:m?\"left\":\r\n\"right\",zIndex:2}).add(a.legendGroup),(v?t:a.legendGroup).on(\"mouseover\",function(){a.setState(\"hover\");t.css(b.options.itemHoverStyle)}).on(\"mouseout\",function(){t.css(a.visible?i:j);a.setState()}).on(\"click\",function(b){var c=function(){a.setVisible()},b={browserEvent:b};a.firePointEvent?a.firePointEvent(\"legendItemClick\",b,c):z(a,\"legendItemClick\",b,c)}),b.colorizeItem(a,a.visible),r&&w))a.checkbox=U(\"input\",{type:\"checkbox\",checked:a.selected,defaultChecked:a.selected},e.itemCheckboxStyle,c.container),\r\nJ(a.checkbox,\"click\",function(b){z(a,\"checkboxClick\",{checked:b.target.checked},function(){a.select()})});d=t.getBBox();B=a.legendItemWidth=e.itemWidth||g+h+d.width+l+(w?20:0),e=B;b.itemHeight=g=d.height;if(f&&b.itemX-A+e>(p||c.chartWidth-2*k-A))b.itemX=A,b.itemY+=n+b.lastLineHeight+q,b.lastLineHeight=0;b.maxItemWidth=s(b.maxItemWidth,e);b.lastItemY=n+b.itemY+q;b.lastLineHeight=s(g,b.lastLineHeight);a._legendItemPos=[b.itemX,b.itemY];f?b.itemX+=e:(b.itemY+=n+g+q,b.lastLineHeight=g);b.offsetWidth=\r\np||s((f?b.itemX-A-l:e)+k,b.offsetWidth)},render:function(){var a=this,b=a.chart,c=b.renderer,d=a.group,e,f,g,h,i=a.box,j=a.options,k=a.padding,l=j.borderWidth,m=j.backgroundColor;a.itemX=a.initialItemX;a.itemY=a.initialItemY;a.offsetWidth=0;a.lastItemY=0;if(!d)a.group=d=c.g(\"legend\").attr({zIndex:7}).add(),a.contentGroup=c.g().attr({zIndex:1}).add(d),a.scrollGroup=c.g().add(a.contentGroup);a.renderTitle();e=[];n(b.series,function(a){var b=a.options;b.showInLegend&&!u(b.linkedTo)&&(e=e.concat(a.legendItems||\r\n(b.legendType===\"point\"?a.data:a)))});Kb(e,function(a,b){return(a.options&&a.options.legendIndex||0)-(b.options&&b.options.legendIndex||0)});j.reversed&&e.reverse();a.allItems=e;a.display=f=!!e.length;n(e,function(b){a.renderItem(b)});g=j.width||a.offsetWidth;h=a.lastItemY+a.lastLineHeight+a.titleHeight;h=a.handleOverflow(h);if(l||m){g+=k;h+=k;if(i){if(g>0&&h>0)i[i.isNew?\"attr\":\"animate\"](i.crisp(null,null,null,g,h)),i.isNew=!1}else a.box=i=c.rect(0,0,g,h,j.borderRadius,l||0).attr({stroke:j.borderColor,\r\n\"stroke-width\":l||0,fill:m||S}).add(d).shadow(j.shadow),i.isNew=!0;i[f?\"show\":\"hide\"]()}a.legendWidth=g;a.legendHeight=h;n(e,function(b){a.positionItem(b)});f&&d.align(r({width:g,height:h},j),!0,\"spacingBox\");b.isResizing||this.positionCheckboxes()},handleOverflow:function(a){var b=this,c=this.chart,d=c.renderer,e=this.options,f=e.y,f=c.spacingBox.height+(e.verticalAlign===\"top\"?-f:f)-this.padding,g=e.maxHeight,h=this.clipRect,i=e.navigation,j=o(i.animation,!0),k=i.arrowSize||12,l=this.nav;e.layout===\r\n\"horizontal\"&&(f/=2);g&&(f=I(f,g));if(a>f&&!e.useHTML){this.clipHeight=c=f-20-this.titleHeight;this.pageCount=xa(a/c);this.currentPage=o(this.currentPage,1);this.fullHeight=a;if(!h)h=b.clipRect=d.clipRect(0,0,9999,0),b.contentGroup.clip(h);h.attr({height:c});if(!l)this.nav=l=d.g().attr({zIndex:1}).add(this.group),this.up=d.symbol(\"triangle\",0,0,k,k).on(\"click\",function(){b.scroll(-1,j)}).add(l),this.pager=d.text(\"\",15,10).css(i.style).add(l),this.down=d.symbol(\"triangle-down\",0,0,k,k).on(\"click\",\r\nfunction(){b.scroll(1,j)}).add(l);b.scroll(0);a=f}else if(l)h.attr({height:c.chartHeight}),l.hide(),this.scrollGroup.attr({translateY:1}),this.clipHeight=0;return a},scroll:function(a,b){var c=this.pageCount,d=this.currentPage+a,e=this.clipHeight,f=this.options.navigation,g=f.activeColor,h=f.inactiveColor,f=this.pager,i=this.padding;d>c&&(d=c);if(d>0)b!==w&&La(b,this.chart),this.nav.attr({translateX:i,translateY:e+7+this.titleHeight,visibility:\"visible\"}),this.up.attr({fill:d===1?h:g}).css({cursor:d===\r\n1?\"default\":\"pointer\"}),f.attr({text:d+\"/\"+this.pageCount}),this.down.attr({x:18+this.pager.getBBox().width,fill:d===c?h:g}).css({cursor:d===c?\"default\":\"pointer\"}),e=-I(e*(d-1),this.fullHeight-e+i)+1,this.scrollGroup.animate({translateY:e}),f.attr({text:d+\"/\"+c}),this.currentPage=d,this.positionCheckboxes(e)}};/Trident.*?11\\.0/.test(oa)&&mb(eb.prototype,\"positionItem\",function(a,b){var c=this;setTimeout(function(){a.call(c,b)})});yb.prototype={init:function(a,b){var c,d=a.series;a.series=null;c=\r\nx(M,a);c.series=a.series=d;d=c.chart;this.margin=this.splashArray(\"margin\",d);this.spacing=this.splashArray(\"spacing\",d);var e=d.events;this.bounds={h:{},v:{}};this.callback=b;this.isResizing=0;this.options=c;this.axes=[];this.series=[];this.hasCartesianSeries=d.showAxes;var f=this,g;f.index=Ga.length;Ga.push(f);d.reflow!==!1&&J(f,\"load\",function(){f.initReflow()});if(e)for(g in e)J(f,g,e[g]);f.xAxis=[];f.yAxis=[];f.animation=$?!1:o(d.animation,!0);f.pointCount=0;f.counters=new Jb;f.firstRender()},\r\ninitSeries:function(a){var b=this.options.chart;(b=W[a.type||b.type||b.defaultSeriesType])||ka(17,!0);b=new b;b.init(this,a);return b},addSeries:function(a,b,c){var d,e=this;a&&(b=o(b,!0),z(e,\"addSeries\",{options:a},function(){d=e.initSeries(a);e.isDirtyLegend=!0;e.linkSeries();b&&e.redraw(c)}));return d},addAxis:function(a,b,c,d){var e=b?\"xAxis\":\"yAxis\",f=this.options;new db(this,x(a,{index:this[e].length,isX:b}));f[e]=ja(f[e]||{});f[e].push(a);o(c,!0)&&this.redraw(d)},isInsidePlot:function(a,b,\r\nc){var d=c?b:a,a=c?a:b;return d>=0&&d<=this.plotWidth&&a>=0&&a<=this.plotHeight},adjustTickAmounts:function(){this.options.chart.alignTicks!==!1&&n(this.axes,function(a){a.adjustTickAmount()});this.maxTicks=null},redraw:function(a){var b=this.axes,c=this.series,d=this.pointer,e=this.legend,f=this.isDirtyLegend,g,h,i=this.isDirtyBox,j=c.length,k=j,l=this.renderer,m=l.isHidden(),p=[];La(a,this);m&&this.cloneRenderTo();for(this.layOutTitles();k--;)if(a=c[k],a.options.stacking&&(g=!0,a.isDirty)){h=!0;\r\nbreak}if(h)for(k=j;k--;)if(a=c[k],a.options.stacking)a.isDirty=!0;n(c,function(a){a.isDirty&&a.options.legendType===\"point\"&&(f=!0)});if(f&&e.options.enabled)e.render(),this.isDirtyLegend=!1;g&&this.getStacks();if(this.hasCartesianSeries){if(!this.isResizing)this.maxTicks=null,n(b,function(a){a.setScale()});this.adjustTickAmounts();this.getMargins();n(b,function(a){a.isDirty&&(i=!0)});n(b,function(a){if(a.isDirtyExtremes)a.isDirtyExtremes=!1,p.push(function(){z(a,\"afterSetExtremes\",r(a.eventArgs,\r\na.getExtremes()));delete a.eventArgs});(i||g)&&a.redraw()})}i&&this.drawChartBox();n(c,function(a){a.isDirty&&a.visible&&(!a.isCartesian||a.xAxis)&&a.redraw()});d&&d.reset&&d.reset(!0);l.draw();z(this,\"redraw\");m&&this.cloneRenderTo(!0);n(p,function(a){a.call()})},showLoading:function(a){var b=this.options,c=this.loadingDiv,d=b.loading;if(!c)this.loadingDiv=c=U(Ea,{className:\"highcharts-loading\"},r(d.style,{zIndex:10,display:S}),this.container),this.loadingSpan=U(\"span\",null,d.labelStyle,c);this.loadingSpan.innerHTML=\r\na||b.lang.loading;if(!this.loadingShown)K(c,{opacity:0,display:\"\",left:this.plotLeft+\"px\",top:this.plotTop+\"px\",width:this.plotWidth+\"px\",height:this.plotHeight+\"px\"}),Bb(c,{opacity:d.style.opacity},{duration:d.showDuration||0}),this.loadingShown=!0},hideLoading:function(){var a=this.options,b=this.loadingDiv;b&&Bb(b,{opacity:0},{duration:a.loading.hideDuration||100,complete:function(){K(b,{display:S})}});this.loadingShown=!1},get:function(a){var b=this.axes,c=this.series,d,e;for(d=0;d<b.length;d++)if(b[d].options.id===\r\na)return b[d];for(d=0;d<c.length;d++)if(c[d].options.id===a)return c[d];for(d=0;d<c.length;d++){e=c[d].points||[];for(b=0;b<e.length;b++)if(e[b].id===a)return e[b]}return null},getAxes:function(){var a=this,b=this.options,c=b.xAxis=ja(b.xAxis||{}),b=b.yAxis=ja(b.yAxis||{});n(c,function(a,b){a.index=b;a.isX=!0});n(b,function(a,b){a.index=b});c=c.concat(b);n(c,function(b){new db(a,b)});a.adjustTickAmounts()},getSelectedPoints:function(){var a=[];n(this.series,function(b){a=a.concat(ub(b.points||[],\r\nfunction(a){return a.selected}))});return a},getSelectedSeries:function(){return ub(this.series,function(a){return a.selected})},getStacks:function(){var a=this;n(a.yAxis,function(a){if(a.stacks&&a.hasVisibleSeries)a.oldStacks=a.stacks});n(a.series,function(b){if(b.options.stacking&&(b.visible===!0||a.options.chart.ignoreHiddenSeries===!1))b.stackKey=b.type+o(b.options.stack,\"\")})},showResetZoom:function(){var a=this,b=M.lang,c=a.options.chart.resetZoomButton,d=c.theme,e=d.states,f=c.relativeTo===\r\n\"chart\"?null:\"plotBox\";this.resetZoomButton=a.renderer.button(b.resetZoom,null,null,function(){a.zoomOut()},d,e&&e.hover).attr({align:c.position.align,title:b.resetZoomTitle}).add().align(c.position,!1,f)},zoomOut:function(){var a=this;z(a,\"selection\",{resetSelection:!0},function(){a.zoom()})},zoom:function(a){var b,c=this.pointer,d=!1,e;!a||a.resetSelection?n(this.axes,function(a){b=a.zoom()}):n(a.xAxis.concat(a.yAxis),function(a){var e=a.axis,h=e.isXAxis;if(c[h?\"zoomX\":\"zoomY\"]||c[h?\"pinchX\":\"pinchY\"])b=\r\ne.zoom(a.min,a.max),e.displayBtn&&(d=!0)});e=this.resetZoomButton;if(d&&!e)this.showResetZoom();else if(!d&&T(e))this.resetZoomButton=e.destroy();b&&this.redraw(o(this.options.chart.animation,a&&a.animation,this.pointCount<100))},pan:function(a,b){var c=this,d=c.hoverPoints,e;d&&n(d,function(a){a.setState()});n(b===\"xy\"?[1,0]:[1],function(b){var d=a[b?\"chartX\":\"chartY\"],h=c[b?\"xAxis\":\"yAxis\"][0],i=c[b?\"mouseDownX\":\"mouseDownY\"],j=(h.pointRange||0)/2,k=h.getExtremes(),l=h.toValue(i-d,!0)+j,i=h.toValue(i+\r\nc[b?\"plotWidth\":\"plotHeight\"]-d,!0)-j;h.series.length&&l>I(k.dataMin,k.min)&&i<s(k.dataMax,k.max)&&(h.setExtremes(l,i,!1,!1,{trigger:\"pan\"}),e=!0);c[b?\"mouseDownX\":\"mouseDownY\"]=d});e&&c.redraw(!1);K(c.container,{cursor:\"move\"})},setTitle:function(a,b){var f;var c=this,d=c.options,e;e=d.title=x(d.title,a);f=d.subtitle=x(d.subtitle,b),d=f;n([[\"title\",a,e],[\"subtitle\",b,d]],function(a){var b=a[0],d=c[b],e=a[1],a=a[2];d&&e&&(c[b]=d=d.destroy());a&&a.text&&!d&&(c[b]=c.renderer.text(a.text,0,0,a.useHTML).attr({align:a.align,\r\n\"class\":\"highcharts-\"+b,zIndex:a.zIndex||4}).css(a.style).add())});c.layOutTitles()},layOutTitles:function(){var a=0,b=this.title,c=this.subtitle,d=this.options,e=d.title,d=d.subtitle,f=this.spacingBox.width-44;if(b&&(b.css({width:(e.width||f)+\"px\"}).align(r({y:15},e),!1,\"spacingBox\"),!e.floating&&!e.verticalAlign))a=b.getBBox().height,a>=18&&a<=25&&(a=15);c&&(c.css({width:(d.width||f)+\"px\"}).align(r({y:a+e.margin},d),!1,\"spacingBox\"),!d.floating&&!d.verticalAlign&&(a=xa(a+c.getBBox().height)));this.titleOffset=\r\na},getChartSize:function(){var a=this.options.chart,b=this.renderToClone||this.renderTo;this.containerWidth=jb(b,\"width\");this.containerHeight=jb(b,\"height\");this.chartWidth=s(0,a.width||this.containerWidth||600);this.chartHeight=s(0,o(a.height,this.containerHeight>19?this.containerHeight:400))},cloneRenderTo:function(a){var b=this.renderToClone,c=this.container;a?b&&(this.renderTo.appendChild(c),Ta(b),delete this.renderToClone):(c&&c.parentNode===this.renderTo&&this.renderTo.removeChild(c),this.renderToClone=\r\nb=this.renderTo.cloneNode(0),K(b,{position:\"absolute\",top:\"-9999px\",display:\"block\"}),y.body.appendChild(b),c&&b.appendChild(c))},getContainer:function(){var a,b=this.options.chart,c,d,e;this.renderTo=a=b.renderTo;e=\"highcharts-\"+zb++;if(ea(a))this.renderTo=a=y.getElementById(a);a||ka(13,!0);c=C(v(a,\"data-highcharts-chart\"));!isNaN(c)&&Ga[c]&&Ga[c].destroy();v(a,\"data-highcharts-chart\",this.index);a.innerHTML=\"\";a.offsetWidth||this.cloneRenderTo();this.getChartSize();c=this.chartWidth;d=this.chartHeight;\r\nthis.container=a=U(Ea,{className:\"highcharts-container\"+(b.className?\" \"+b.className:\"\"),id:e},r({position:\"relative\",overflow:\"hidden\",width:c+\"px\",height:d+\"px\",textAlign:\"left\",lineHeight:\"normal\",zIndex:0,\"-webkit-tap-highlight-color\":\"rgba(0,0,0,0)\"},b.style),this.renderToClone||a);this._cursor=a.style.cursor;this.renderer=b.forExport?new Ha(a,c,d,!0):new Va(a,c,d);$&&this.renderer.create(this,a,c,d)},getMargins:function(){var a=this.spacing,b,c=this.legend,d=this.margin,e=this.options.legend,\r\nf=o(e.margin,10),g=e.x,h=e.y,i=e.align,j=e.verticalAlign,k=this.titleOffset;this.resetMargins();b=this.axisOffset;if(k&&!u(d[0]))this.plotTop=s(this.plotTop,k+this.options.title.margin+a[0]);if(c.display&&!e.floating)if(i===\"right\"){if(!u(d[1]))this.marginRight=s(this.marginRight,c.legendWidth-g+f+a[1])}else if(i===\"left\"){if(!u(d[3]))this.plotLeft=s(this.plotLeft,c.legendWidth+g+f+a[3])}else if(j===\"top\"){if(!u(d[0]))this.plotTop=s(this.plotTop,c.legendHeight+h+f+a[0])}else if(j===\"bottom\"&&!u(d[2]))this.marginBottom=\r\ns(this.marginBottom,c.legendHeight-h+f+a[2]);this.extraBottomMargin&&(this.marginBottom+=this.extraBottomMargin);this.extraTopMargin&&(this.plotTop+=this.extraTopMargin);this.hasCartesianSeries&&n(this.axes,function(a){a.getOffset()});u(d[3])||(this.plotLeft+=b[3]);u(d[0])||(this.plotTop+=b[0]);u(d[2])||(this.marginBottom+=b[2]);u(d[1])||(this.marginRight+=b[1]);this.setChartSize()},initReflow:function(){function a(a){var g=c.width||jb(d,\"width\"),h=c.height||jb(d,\"height\"),a=a?a.target:O;if(!b.hasUserSize&&\r\ng&&h&&(a===O||a===y)){if(g!==b.containerWidth||h!==b.containerHeight)clearTimeout(e),b.reflowTimeout=e=setTimeout(function(){if(b.container)b.setSize(g,h,!1),b.hasUserSize=null},100);b.containerWidth=g;b.containerHeight=h}}var b=this,c=b.options.chart,d=b.renderTo,e;b.reflow=a;J(O,\"resize\",a);J(b,\"destroy\",function(){aa(O,\"resize\",a)})},setSize:function(a,b,c){var d=this,e,f,g;d.isResizing+=1;g=function(){d&&z(d,\"endResize\",null,function(){d.isResizing-=1})};La(c,d);d.oldChartHeight=d.chartHeight;\r\nd.oldChartWidth=d.chartWidth;if(u(a))d.chartWidth=e=s(0,t(a)),d.hasUserSize=!!e;if(u(b))d.chartHeight=f=s(0,t(b));K(d.container,{width:e+\"px\",height:f+\"px\"});d.setChartSize(!0);d.renderer.setSize(e,f,c);d.maxTicks=null;n(d.axes,function(a){a.isDirty=!0;a.setScale()});n(d.series,function(a){a.isDirty=!0});d.isDirtyLegend=!0;d.isDirtyBox=!0;d.getMargins();d.redraw(c);d.oldChartHeight=null;z(d,\"resize\");Fa===!1?g():setTimeout(g,Fa&&Fa.duration||500)},setChartSize:function(a){var b=this.inverted,c=this.renderer,\r\nd=this.chartWidth,e=this.chartHeight,f=this.options.chart,g=this.spacing,h=this.clipOffset,i,j,k,l;this.plotLeft=i=t(this.plotLeft);this.plotTop=j=t(this.plotTop);this.plotWidth=k=s(0,t(d-i-this.marginRight));this.plotHeight=l=s(0,t(e-j-this.marginBottom));this.plotSizeX=b?l:k;this.plotSizeY=b?k:l;this.plotBorderWidth=f.plotBorderWidth||0;this.spacingBox=c.spacingBox={x:g[3],y:g[0],width:d-g[3]-g[1],height:e-g[0]-g[2]};this.plotBox=c.plotBox={x:i,y:j,width:k,height:l};d=2*P(this.plotBorderWidth/2);\r\nb=xa(s(d,h[3])/2);c=xa(s(d,h[0])/2);this.clipBox={x:b,y:c,width:P(this.plotSizeX-s(d,h[1])/2-b),height:P(this.plotSizeY-s(d,h[2])/2-c)};a||n(this.axes,function(a){a.setAxisSize();a.setAxisTranslation()})},resetMargins:function(){var a=this.spacing,b=this.margin;this.plotTop=o(b[0],a[0]);this.marginRight=o(b[1],a[1]);this.marginBottom=o(b[2],a[2]);this.plotLeft=o(b[3],a[3]);this.axisOffset=[0,0,0,0];this.clipOffset=[0,0,0,0]},drawChartBox:function(){var a=this.options.chart,b=this.renderer,c=this.chartWidth,\r\nd=this.chartHeight,e=this.chartBackground,f=this.plotBackground,g=this.plotBorder,h=this.plotBGImage,i=a.borderWidth||0,j=a.backgroundColor,k=a.plotBackgroundColor,l=a.plotBackgroundImage,m=a.plotBorderWidth||0,p,q=this.plotLeft,o=this.plotTop,n=this.plotWidth,s=this.plotHeight,t=this.plotBox,u=this.clipRect,r=this.clipBox;p=i+(a.shadow?8:0);if(i||j)if(e)e.animate(e.crisp(null,null,null,c-p,d-p));else{e={fill:j||S};if(i)e.stroke=a.borderColor,e[\"stroke-width\"]=i;this.chartBackground=b.rect(p/2,p/\r\n2,c-p,d-p,a.borderRadius,i).attr(e).add().shadow(a.shadow)}if(k)f?f.animate(t):this.plotBackground=b.rect(q,o,n,s,0).attr({fill:k}).add().shadow(a.plotShadow);if(l)h?h.animate(t):this.plotBGImage=b.image(l,q,o,n,s).add();u?u.animate({width:r.width,height:r.height}):this.clipRect=b.clipRect(r);if(m)g?g.animate(g.crisp(null,q,o,n,s)):this.plotBorder=b.rect(q,o,n,s,0,-m).attr({stroke:a.plotBorderColor,\"stroke-width\":m,zIndex:1}).add();this.isDirtyBox=!1},propFromSeries:function(){var a=this,b=a.options.chart,\r\nc,d=a.options.series,e,f;n([\"inverted\",\"angular\",\"polar\"],function(g){c=W[b.type||b.defaultSeriesType];f=a[g]||b[g]||c&&c.prototype[g];for(e=d&&d.length;!f&&e--;)(c=W[d[e].type])&&c.prototype[g]&&(f=!0);a[g]=f})},linkSeries:function(){var a=this,b=a.series;n(b,function(a){a.linkedSeries.length=0});n(b,function(b){var d=b.options.linkedTo;if(ea(d)&&(d=d===\":previous\"?a.series[b.index-1]:a.get(d)))d.linkedSeries.push(b),b.linkedParent=d})},render:function(){var a=this,b=a.axes,c=a.renderer,d=a.options,\r\ne=d.labels,f=d.credits,g;a.setTitle();a.legend=new eb(a,d.legend);a.getStacks();n(b,function(a){a.setScale()});a.getMargins();a.maxTicks=null;n(b,function(a){a.setTickPositions(!0);a.setMaxTicks()});a.adjustTickAmounts();a.getMargins();a.drawChartBox();a.hasCartesianSeries&&n(b,function(a){a.render()});if(!a.seriesGroup)a.seriesGroup=c.g(\"series-group\").attr({zIndex:3}).add();n(a.series,function(a){a.translate();a.setTooltipPoints();a.render()});e.items&&n(e.items,function(b){var d=r(e.style,b.style),\r\nf=C(d.left)+a.plotLeft,g=C(d.top)+a.plotTop+12;delete d.left;delete d.top;c.text(b.html,f,g).attr({zIndex:2}).css(d).add()});if(f.enabled&&!a.credits)g=f.href,a.credits=c.text(f.text,0,0).on(\"click\",function(){if(g)location.href=g}).attr({align:f.position.align,zIndex:8}).css(f.style).add().align(f.position);a.hasRendered=!0},destroy:function(){var a=this,b=a.axes,c=a.series,d=a.container,e,f=d&&d.parentNode;z(a,\"destroy\");Ga[a.index]=w;a.renderTo.removeAttribute(\"data-highcharts-chart\");aa(a);for(e=\r\nb.length;e--;)b[e]=b[e].destroy();for(e=c.length;e--;)c[e]=c[e].destroy();n(\"title,subtitle,chartBackground,plotBackground,plotBGImage,plotBorder,seriesGroup,clipRect,credits,pointer,scroller,rangeSelector,legend,resetZoomButton,tooltip,renderer\".split(\",\"),function(b){var c=a[b];c&&c.destroy&&(a[b]=c.destroy())});if(d)d.innerHTML=\"\",aa(d),f&&Ta(d);for(e in a)delete a[e]},isReadyToRender:function(){var a=this;return!Z&&O==O.top&&y.readyState!==\"complete\"||$&&!O.canvg?($?Tb.push(function(){a.firstRender()},\r\na.options.global.canvasToolsURL):y.attachEvent(\"onreadystatechange\",function(){y.detachEvent(\"onreadystatechange\",a.firstRender);y.readyState===\"complete\"&&a.firstRender()}),!1):!0},firstRender:function(){var a=this,b=a.options,c=a.callback;if(a.isReadyToRender())a.getContainer(),z(a,\"init\"),a.resetMargins(),a.setChartSize(),a.propFromSeries(),a.getAxes(),n(b.series||[],function(b){a.initSeries(b)}),a.linkSeries(),z(a,\"beforeRender\"),a.pointer=new xb(a,b),a.render(),a.renderer.draw(),c&&c.apply(a,\r\n[a]),n(a.callbacks,function(b){b.apply(a,[a])}),a.cloneRenderTo(!0),z(a,\"load\")},splashArray:function(a,b){var c=b[a],c=T(c)?c:[c,c,c,c];return[o(b[a+\"Top\"],c[0]),o(b[a+\"Right\"],c[1]),o(b[a+\"Bottom\"],c[2]),o(b[a+\"Left\"],c[3])]}};yb.prototype.callbacks=[];var Pa=function(){};Pa.prototype={init:function(a,b,c){this.series=a;this.applyOptions(b,c);this.pointAttr={};if(a.options.colorByPoint&&(b=a.options.colors||a.chart.options.colors,this.color=this.color||b[a.colorCounter++],a.colorCounter===b.length))a.colorCounter=\r\n0;a.chart.pointCount++;return this},applyOptions:function(a,b){var c=this.series,d=c.pointValKey,a=Pa.prototype.optionsToObject.call(this,a);r(this,a);this.options=this.options?r(this.options,a):a;if(d)this.y=this[d];if(this.x===w&&c)this.x=b===w?c.autoIncrement():b;return this},optionsToObject:function(a){var b,c=this.series,d=c.pointArrayMap||[\"y\"],e=d.length,f=0,g=0;if(typeof a===\"number\"||a===null)b={y:a};else if(Ia(a)){b={};if(a.length>e){c=typeof a[0];if(c===\"string\")b.name=a[0];else if(c===\r\n\"number\")b.x=a[0];f++}for(;g<e;)b[d[g++]]=a[f++]}else if(typeof a===\"object\"){b=a;if(a.dataLabels)c._hasPointLabels=!0;if(a.marker)c._hasPointMarkers=!0}return b},destroy:function(){var a=this.series.chart,b=a.hoverPoints,c;a.pointCount--;if(b&&(this.setState(),ga(b,this),!b.length))a.hoverPoints=null;if(this===a.hoverPoint)this.onMouseOut();if(this.graphic||this.dataLabel)aa(this),this.destroyElements();this.legendItem&&a.legend.destroyItem(this);for(c in this)this[c]=null},destroyElements:function(){for(var a=\r\n\"graphic,dataLabel,dataLabelUpper,group,connector,shadowGroup\".split(\",\"),b,c=6;c--;)b=a[c],this[b]&&(this[b]=this[b].destroy())},getLabelConfig:function(){return{x:this.category,y:this.y,key:this.name||this.category,series:this.series,point:this,percentage:this.percentage,total:this.total||this.stackTotal}},select:function(a,b){var c=this,d=c.series,e=d.chart,a=o(a,!c.selected);c.firePointEvent(a?\"select\":\"unselect\",{accumulate:b},function(){c.selected=c.options.selected=a;d.options.data[qa(c,d.data)]=\r\nc.options;c.setState(a&&\"select\");b||n(e.getSelectedPoints(),function(a){if(a.selected&&a!==c)a.selected=a.options.selected=!1,d.options.data[qa(a,d.data)]=a.options,a.setState(\"\"),a.firePointEvent(\"unselect\")})})},onMouseOver:function(a){var b=this.series,c=b.chart,d=c.tooltip,e=c.hoverPoint;if(e&&e!==this)e.onMouseOut();this.firePointEvent(\"mouseOver\");d&&(!d.shared||b.noSharedTooltip)&&d.refresh(this,a);this.setState(\"hover\");c.hoverPoint=this},onMouseOut:function(){var a=this.series.chart,b=a.hoverPoints;\r\nif(!b||qa(this,b)===-1)this.firePointEvent(\"mouseOut\"),this.setState(),a.hoverPoint=null},tooltipFormatter:function(a){var b=this.series,c=b.tooltipOptions,d=o(c.valueDecimals,\"\"),e=c.valuePrefix||\"\",f=c.valueSuffix||\"\";n(b.pointArrayMap||[\"y\"],function(b){b=\"{point.\"+b;if(e||f)a=a.replace(b+\"}\",e+b+\"}\"+f);a=a.replace(b+\"}\",b+\":,.\"+d+\"f}\")});return Ca(a,{point:this,series:this.series})},update:function(a,b,c){var d=this,e=d.series,f=d.graphic,g,h=e.data,i=e.chart,j=e.options,b=o(b,!0);d.firePointEvent(\"update\",\r\n{options:a},function(){d.applyOptions(a);if(T(a)&&(e.getAttribs(),f))a.marker&&a.marker.symbol?d.graphic=f.destroy():f.attr(d.pointAttr[d.state||\"\"]);g=qa(d,h);e.xData[g]=d.x;e.yData[g]=e.toYData?e.toYData(d):d.y;e.zData[g]=d.z;j.data[g]=d.options;e.isDirty=e.isDirtyData=!0;if(!e.fixedBox&&e.hasCartesianSeries)i.isDirtyBox=!0;j.legendType===\"point\"&&i.legend.destroyItem(d);b&&i.redraw(c)})},remove:function(a,b){var c=this,d=c.series,e=d.points,f=d.chart,g,h=d.data;La(b,f);a=o(a,!0);c.firePointEvent(\"remove\",\r\nnull,function(){g=qa(c,h);h.length===e.length&&e.splice(g,1);h.splice(g,1);d.options.data.splice(g,1);d.xData.splice(g,1);d.yData.splice(g,1);d.zData.splice(g,1);c.destroy();d.isDirty=!0;d.isDirtyData=!0;a&&f.redraw()})},firePointEvent:function(a,b,c){var d=this,e=this.series.options;(e.point.events[a]||d.options&&d.options.events&&d.options.events[a])&&this.importEvents();a===\"click\"&&e.allowPointSelect&&(c=function(a){d.select(null,a.ctrlKey||a.metaKey||a.shiftKey)});z(this,a,b,c)},importEvents:function(){if(!this.hasImportedEvents){var a=\r\nx(this.series.options.point,this.options).events,b;this.events=a;for(b in a)J(this,b,a[b]);this.hasImportedEvents=!0}},setState:function(a){var b=this.plotX,c=this.plotY,d=this.series,e=d.options.states,f=Y[d.type].marker&&d.options.marker,g=f&&!f.enabled,h=f&&f.states[a],i=h&&h.enabled===!1,j=d.stateMarkerGraphic,k=this.marker||{},l=d.chart,m=this.pointAttr,a=a||\"\";if(!(a===this.state||this.selected&&a!==\"select\"||e[a]&&e[a].enabled===!1||a&&(i||g&&!h.enabled))){if(this.graphic)e=f&&this.graphic.symbolName&&\r\nm[a].r,this.graphic.attr(x(m[a],e?{x:b-e,y:c-e,width:2*e,height:2*e}:{}));else{if(a&&h)e=h.radius,k=k.symbol||d.symbol,j&&j.currentSymbol!==k&&(j=j.destroy()),j?j.attr({x:b-e,y:c-e}):(d.stateMarkerGraphic=j=l.renderer.symbol(k,b-e,c-e,2*e,2*e).attr(m[a]).add(d.markerGroup),j.currentSymbol=k);if(j)j[a&&l.isInsidePlot(b,c)?\"show\":\"hide\"]()}this.state=a}}};var Q=function(){};Q.prototype={isCartesian:!0,type:\"line\",pointClass:Pa,sorted:!0,requireSorting:!0,pointAttrToOptions:{stroke:\"lineColor\",\"stroke-width\":\"lineWidth\",\r\nfill:\"fillColor\",r:\"radius\"},colorCounter:0,init:function(a,b){var c,d,e=a.series;this.chart=a;this.options=b=this.setOptions(b);this.linkedSeries=[];this.bindAxes();r(this,{name:b.name,state:\"\",pointAttr:{},visible:b.visible!==!1,selected:b.selected===!0});if($)b.animation=!1;d=b.events;for(c in d)J(this,c,d[c]);if(d&&d.click||b.point&&b.point.events&&b.point.events.click||b.allowPointSelect)a.runTrackerClick=!0;this.getColor();this.getSymbol();this.setData(b.data,!1);if(this.isCartesian)a.hasCartesianSeries=\r\n!0;e.push(this);this._i=e.length-1;Kb(e,function(a,b){return o(a.options.index,a._i)-o(b.options.index,a._i)});n(e,function(a,b){a.index=b;a.name=a.name||\"Series \"+(b+1)})},bindAxes:function(){var a=this,b=a.options,c=a.chart,d;a.isCartesian&&n([\"xAxis\",\"yAxis\"],function(e){n(c[e],function(c){d=c.options;if(b[e]===d.index||b[e]!==w&&b[e]===d.id||b[e]===w&&d.index===0)c.series.push(a),a[e]=c,c.isDirty=!0});a[e]||ka(18,!0)})},autoIncrement:function(){var a=this.options,b=this.xIncrement,b=o(b,a.pointStart,\r\n0);this.pointInterval=o(this.pointInterval,a.pointInterval,1);this.xIncrement=b+this.pointInterval;return b},getSegments:function(){var a=-1,b=[],c,d=this.points,e=d.length;if(e)if(this.options.connectNulls){for(c=e;c--;)d[c].y===null&&d.splice(c,1);d.length&&(b=[d])}else n(d,function(c,g){c.y===null?(g>a+1&&b.push(d.slice(a+1,g)),a=g):g===e-1&&b.push(d.slice(a+1,g+1))});this.segments=b},setOptions:function(a){var b=this.chart.options,c=b.plotOptions,d=c[this.type];this.userOptions=a;a=x(d,c.series,\r\na);this.tooltipOptions=x(b.tooltip,a.tooltip);d.marker===null&&delete a.marker;return a},getColor:function(){var a=this.options,b=this.userOptions,c=this.chart.options.colors,d=this.chart.counters,e;e=a.color||Y[this.type].color;if(!e&&!a.colorByPoint)u(b._colorIndex)?a=b._colorIndex:(b._colorIndex=d.color,a=d.color++),e=c[a];this.color=e;d.wrapColor(c.length)},getSymbol:function(){var a=this.userOptions,b=this.options.marker,c=this.chart,d=c.options.symbols,c=c.counters;this.symbol=b.symbol;if(!this.symbol)u(a._symbolIndex)?\r\na=a._symbolIndex:(a._symbolIndex=c.symbol,a=c.symbol++),this.symbol=d[a];if(/^url/.test(this.symbol))b.radius=0;c.wrapSymbol(d.length)},drawLegendSymbol:function(a){var b=this.options,c=b.marker,d=a.options,e;e=d.symbolWidth;var f=this.chart.renderer,g=this.legendGroup,a=a.baseline-t(f.fontMetrics(d.itemStyle.fontSize).b*0.3);if(b.lineWidth){d={\"stroke-width\":b.lineWidth};if(b.dashStyle)d.dashstyle=b.dashStyle;this.legendLine=f.path([\"M\",0,a,\"L\",e,a]).attr(d).add(g)}if(c&&c.enabled)b=c.radius,this.legendSymbol=\r\ne=f.symbol(this.symbol,e/2-b,a-b,2*b,2*b).add(g),e.isMarker=!0},addPoint:function(a,b,c,d){var e=this.options,f=this.data,g=this.graph,h=this.area,i=this.chart,j=this.xData,k=this.yData,l=this.zData,m=this.names,p=g&&g.shift||0,q=e.data,s;La(d,i);c&&n([g,h,this.graphNeg,this.areaNeg],function(a){if(a)a.shift=p+1});if(h)h.isArea=!0;b=o(b,!0);d={series:this};this.pointClass.prototype.applyOptions.apply(d,[a]);g=d.x;h=j.length;if(this.requireSorting&&g<j[h-1])for(s=!0;h&&j[h-1]>g;)h--;j.splice(h,0,g);\r\nk.splice(h,0,this.toYData?this.toYData(d):d.y);l.splice(h,0,d.z);if(m)m[g]=d.name;q.splice(h,0,a);s&&(this.data.splice(h,0,null),this.processData());e.legendType===\"point\"&&this.generatePoints();c&&(f[0]&&f[0].remove?f[0].remove(!1):(f.shift(),j.shift(),k.shift(),l.shift(),q.shift()));this.isDirtyData=this.isDirty=!0;b&&(this.getAttribs(),i.redraw())},setData:function(a,b){var c=this.points,d=this.options,e=this.chart,f=null,g=this.xAxis,h=g&&g.categories&&!g.categories.length?[]:null,i;this.xIncrement=\r\nnull;this.pointRange=g&&g.categories?1:d.pointRange;this.colorCounter=0;var j=[],k=[],l=[],m=a?a.length:[];i=o(d.turboThreshold,1E3);var p=this.pointArrayMap,p=p&&p.length,q=!!this.toYData;if(i&&m>i){for(i=0;f===null&&i<m;)f=a[i],i++;if(sa(f)){f=o(d.pointStart,0);d=o(d.pointInterval,1);for(i=0;i<m;i++)j[i]=f,k[i]=a[i],f+=d;this.xIncrement=f}else if(Ia(f))if(p)for(i=0;i<m;i++)d=a[i],j[i]=d[0],k[i]=d.slice(1,p+1);else for(i=0;i<m;i++)d=a[i],j[i]=d[0],k[i]=d[1];else ka(12)}else for(i=0;i<m;i++)if(a[i]!==\r\nw&&(d={series:this},this.pointClass.prototype.applyOptions.apply(d,[a[i]]),j[i]=d.x,k[i]=q?this.toYData(d):d.y,l[i]=d.z,h&&d.name))h[d.x]=d.name;ea(k[0])&&ka(14,!0);this.data=[];this.options.data=a;this.xData=j;this.yData=k;this.zData=l;this.names=h;for(i=c&&c.length||0;i--;)c[i]&&c[i].destroy&&c[i].destroy();if(g)g.minRange=g.userMinRange;this.isDirty=this.isDirtyData=e.isDirtyBox=!0;o(b,!0)&&e.redraw(!1)},remove:function(a,b){var c=this,d=c.chart,a=o(a,!0);if(!c.isRemoving)c.isRemoving=!0,z(c,\"remove\",\r\nnull,function(){c.destroy();d.isDirtyLegend=d.isDirtyBox=!0;d.linkSeries();a&&d.redraw(b)});c.isRemoving=!1},processData:function(a){var b=this.xData,c=this.yData,d=b.length,e;e=0;var f,g,h=this.xAxis,i=this.options,j=i.cropThreshold,k=this.isCartesian;if(k&&!this.isDirty&&!h.isDirty&&!this.yAxis.isDirty&&!a)return!1;if(k&&this.sorted&&(!j||d>j||this.forceCrop))if(a=h.min,h=h.max,b[d-1]<a||b[0]>h)b=[],c=[];else if(b[0]<a||b[d-1]>h)e=this.cropData(this.xData,this.yData,a,h),b=e.xData,c=e.yData,e=e.start,\r\nf=!0;for(h=b.length-1;h>=0;h--)d=b[h]-b[h-1],d>0&&(g===w||d<g)?g=d:d<0&&this.requireSorting&&ka(15);this.cropped=f;this.cropStart=e;this.processedXData=b;this.processedYData=c;if(i.pointRange===null)this.pointRange=g||1;this.closestPointRange=g},cropData:function(a,b,c,d){var e=a.length,f=0,g=e,h=o(this.cropShoulder,1),i;for(i=0;i<e;i++)if(a[i]>=c){f=s(0,i-h);break}for(;i<e;i++)if(a[i]>d){g=i+h;break}return{xData:a.slice(f,g),yData:b.slice(f,g),start:f,end:g}},generatePoints:function(){var a=this.options.data,\r\nb=this.data,c,d=this.processedXData,e=this.processedYData,f=this.pointClass,g=d.length,h=this.cropStart||0,i,j=this.hasGroupedData,k,l=[],m;if(!b&&!j)b=[],b.length=a.length,b=this.data=b;for(m=0;m<g;m++)i=h+m,j?l[m]=(new f).init(this,[d[m]].concat(ja(e[m]))):(b[i]?k=b[i]:a[i]!==w&&(b[i]=k=(new f).init(this,a[i],d[m])),l[m]=k);if(b&&(g!==(c=b.length)||j))for(m=0;m<c;m++)if(m===h&&!j&&(m+=g),b[m])b[m].destroyElements(),b[m].plotX=w;this.data=b;this.points=l},setStackedPoints:function(){if(this.options.stacking&&\r\n!(this.visible!==!0&&this.chart.options.chart.ignoreHiddenSeries!==!1)){var a=this.processedXData,b=this.processedYData,c=[],d=b.length,e=this.options,f=e.threshold,g=e.stack,e=e.stacking,h=this.stackKey,i=\"-\"+h,j=this.negStacks,k=this.yAxis,l=k.stacks,m=k.oldStacks,p,q,o,n,t;for(o=0;o<d;o++){n=a[o];t=b[o];q=(p=j&&t<f)?i:h;l[q]||(l[q]={});if(!l[q][n])m[q]&&m[q][n]?(l[q][n]=m[q][n],l[q][n].total=null):l[q][n]=new Mb(k,k.options.stackLabels,p,n,g,e);q=l[q][n];q.points[this.index]=[q.cum||0];e===\"percent\"?\r\n(p=p?h:i,j&&l[p]&&l[p][n]?(p=l[p][n],q.total=p.total=s(p.total,q.total)+N(t)||0):q.total+=N(t)||0):q.total+=t||0;q.cum=(q.cum||0)+(t||0);q.points[this.index].push(q.cum);c[o]=q.cum}if(e===\"percent\")k.usePercentage=!0;this.stackedYData=c;k.oldStacks={}}},setPercentStacks:function(){var a=this,b=a.stackKey,c=a.yAxis.stacks;n([b,\"-\"+b],function(b){var d;for(var e=a.xData.length,f,g;e--;)if(f=a.xData[e],d=(g=c[b]&&c[b][f])&&g.points[a.index],f=d)g=g.total?100/g.total:0,f[0]=ia(f[0]*g),f[1]=ia(f[1]*g),\r\na.stackedYData[e]=f[1]})},getExtremes:function(){var a=this.yAxis,b=this.processedXData,c=this.stackedYData||this.processedYData,d=c.length,e=[],f=0,g=this.xAxis.getExtremes(),h=g.min,g=g.max,i,j,k,l;for(l=0;l<d;l++)if(j=b[l],k=c[l],i=k!==null&&k!==w&&(!a.isLog||k.length||k>0),j=this.getExtremesFromAll||this.cropped||(b[l+1]||j)>=h&&(b[l-1]||j)<=g,i&&j)if(i=k.length)for(;i--;)k[i]!==null&&(e[f++]=k[i]);else e[f++]=k;this.dataMin=o(void 0,Ja(e));this.dataMax=o(void 0,va(e))},translate:function(){this.processedXData||\r\nthis.processData();this.generatePoints();for(var a=this.options,b=a.stacking,c=this.xAxis,d=c.categories,e=this.yAxis,f=this.points,g=f.length,h=!!this.modifyValue,i=a.pointPlacement,j=i===\"between\"||sa(i),k=a.threshold,a=0;a<g;a++){var l=f[a],m=l.x,p=l.y,q=l.low,n=e.stacks[(this.negStacks&&p<k?\"-\":\"\")+this.stackKey];if(e.isLog&&p<=0)l.y=p=null;l.plotX=c.translate(m,0,0,0,1,i,this.type===\"flags\");if(b&&this.visible&&n&&n[m])n=n[m],p=n.points[this.index],q=p[0],p=p[1],q===0&&(q=o(k,e.min)),e.isLog&&\r\nq<=0&&(q=null),l.percentage=b===\"percent\"&&p,l.total=l.stackTotal=n.total,l.stackY=p,n.setOffset(this.pointXOffset||0,this.barW||0);l.yBottom=u(q)?e.translate(q,0,1,0,1):null;h&&(p=this.modifyValue(p,l));l.plotY=typeof p===\"number\"&&p!==Infinity?e.translate(p,0,1,0,1):w;l.clientX=j?c.translate(m,0,0,0,1):l.plotX;l.negative=l.y<(k||0);l.category=d&&d[l.x]!==w?d[l.x]:l.x}this.getSegments()},setTooltipPoints:function(a){var b=[],c,d,e=this.xAxis,f=e&&e.getExtremes(),g=e?e.tooltipLen||e.len:this.chart.plotSizeX,\r\nh,i,j=[];if(this.options.enableMouseTracking!==!1){if(a)this.tooltipPoints=null;n(this.segments||this.points,function(a){b=b.concat(a)});e&&e.reversed&&(b=b.reverse());this.orderTooltipPoints&&this.orderTooltipPoints(b);a=b.length;for(i=0;i<a;i++)if(e=b[i],c=e.x,c>=f.min&&c<=f.max){h=b[i+1];c=d===w?0:d+1;for(d=b[i+1]?I(s(0,P((e.clientX+(h?h.wrappedClientX||h.clientX:g))/2)),g):g;c>=0&&c<=d;)j[c++]=e}this.tooltipPoints=j}},tooltipHeaderFormatter:function(a){var b=this.tooltipOptions,c=b.xDateFormat,\r\nd=b.dateTimeLabelFormats,e=this.xAxis,f=e&&e.options.type===\"datetime\",b=b.headerFormat,e=e&&e.closestPointRange,g;if(f&&!c)if(e)for(g in D){if(D[g]>=e){c=d[g];break}}else c=d.day;f&&c&&sa(a.key)&&(b=b.replace(\"{point.key}\",\"{point.key:\"+c+\"}\"));return Ca(b,{point:a,series:this})},onMouseOver:function(){var a=this.chart,b=a.hoverSeries;if(b&&b!==this)b.onMouseOut();this.options.events.mouseOver&&z(this,\"mouseOver\");this.setState(\"hover\");a.hoverSeries=this},onMouseOut:function(){var a=this.options,\r\nb=this.chart,c=b.tooltip,d=b.hoverPoint;if(d)d.onMouseOut();this&&a.events.mouseOut&&z(this,\"mouseOut\");c&&!a.stickyTracking&&(!c.shared||this.noSharedTooltip)&&c.hide();this.setState();b.hoverSeries=null},animate:function(a){var b=this,c=b.chart,d=c.renderer,e;e=b.options.animation;var f=c.clipBox,g=c.inverted,h;if(e&&!T(e))e=Y[b.type].animation;h=\"_sharedClip\"+e.duration+e.easing;if(a)a=c[h],e=c[h+\"m\"],a||(c[h]=a=d.clipRect(r(f,{width:0})),c[h+\"m\"]=e=d.clipRect(-99,g?-c.plotLeft:-c.plotTop,99,g?\r\nc.chartWidth:c.chartHeight)),b.group.clip(a),b.markerGroup.clip(e),b.sharedClipKey=h;else{if(a=c[h])a.animate({width:c.plotSizeX},e),c[h+\"m\"].animate({width:c.plotSizeX+99},e);b.animate=null;b.animationTimeout=setTimeout(function(){b.afterAnimate()},e.duration)}},afterAnimate:function(){var a=this.chart,b=this.sharedClipKey,c=this.group;c&&this.options.clip!==!1&&(c.clip(a.clipRect),this.markerGroup.clip());setTimeout(function(){b&&a[b]&&(a[b]=a[b].destroy(),a[b+\"m\"]=a[b+\"m\"].destroy())},100)},drawPoints:function(){var a,\r\nb=this.points,c=this.chart,d,e,f,g,h,i,j,k,l=this.options.marker,m,p=this.markerGroup;if(l.enabled||this._hasPointMarkers)for(f=b.length;f--;)if(g=b[f],d=P(g.plotX),e=g.plotY,k=g.graphic,i=g.marker||{},a=l.enabled&&i.enabled===w||i.enabled,m=c.isInsidePlot(t(d),e,c.inverted),a&&e!==w&&!isNaN(e)&&g.y!==null)if(a=g.pointAttr[g.selected?\"select\":\"\"],h=a.r,i=o(i.symbol,this.symbol),j=i.indexOf(\"url\")===0,k)k.attr({visibility:m?Z?\"inherit\":\"visible\":\"hidden\"}).animate(r({x:d-h,y:e-h},k.symbolName?{width:2*\r\nh,height:2*h}:{}));else{if(m&&(h>0||j))g.graphic=c.renderer.symbol(i,d-h,e-h,2*h,2*h).attr(a).add(p)}else if(k)g.graphic=k.destroy()},convertAttribs:function(a,b,c,d){var e=this.pointAttrToOptions,f,g,h={},a=a||{},b=b||{},c=c||{},d=d||{};for(f in e)g=e[f],h[f]=o(a[g],b[f],c[f],d[f]);return h},getAttribs:function(){var a=this,b=a.options,c=Y[a.type].marker?b.marker:b,d=c.states,e=d.hover,f,g=a.color,h={stroke:g,fill:g},i=a.points||[],j=[],k,l=a.pointAttrToOptions,m=b.negativeColor,p=c.lineColor,q;\r\nb.marker?(e.radius=e.radius||c.radius+2,e.lineWidth=e.lineWidth||c.lineWidth+1):e.color=e.color||ra(e.color||g).brighten(e.brightness).get();j[\"\"]=a.convertAttribs(c,h);n([\"hover\",\"select\"],function(b){j[b]=a.convertAttribs(d[b],j[\"\"])});a.pointAttr=j;for(g=i.length;g--;){h=i[g];if((c=h.options&&h.options.marker||h.options)&&c.enabled===!1)c.radius=0;if(h.negative&&m)h.color=h.fillColor=m;f=b.colorByPoint||h.color;if(h.options)for(q in l)u(c[l[q]])&&(f=!0);if(f){c=c||{};k=[];d=c.states||{};f=d.hover=\r\nd.hover||{};if(!b.marker)f.color=ra(f.color||h.color).brighten(f.brightness||e.brightness).get();k[\"\"]=a.convertAttribs(r({color:h.color,fillColor:h.color,lineColor:p===null?h.color:w},c),j[\"\"]);k.hover=a.convertAttribs(d.hover,j.hover,k[\"\"]);k.select=a.convertAttribs(d.select,j.select,k[\"\"])}else k=j;h.pointAttr=k}},update:function(a,b){var c=this.chart,d=this.type,e=W[d].prototype,f,a=x(this.userOptions,{animation:!1,index:this.index,pointStart:this.xData[0]},{data:this.options.data},a);this.remove(!1);\r\nfor(f in e)e.hasOwnProperty(f)&&(this[f]=w);r(this,W[a.type||d].prototype);this.init(c,a);o(b,!0)&&c.redraw(!1)},destroy:function(){var a=this,b=a.chart,c=/AppleWebKit\\/533/.test(oa),d,e,f=a.data||[],g,h,i;z(a,\"destroy\");aa(a);n([\"xAxis\",\"yAxis\"],function(b){if(i=a[b])ga(i.series,a),i.isDirty=i.forceRedraw=!0,i.stacks={}});a.legendItem&&a.chart.legend.destroyItem(a);for(e=f.length;e--;)(g=f[e])&&g.destroy&&g.destroy();a.points=null;clearTimeout(a.animationTimeout);n(\"area,graph,dataLabelsGroup,group,markerGroup,tracker,graphNeg,areaNeg,posClip,negClip\".split(\",\"),\r\nfunction(b){a[b]&&(d=c&&b===\"group\"?\"hide\":\"destroy\",a[b][d]())});if(b.hoverSeries===a)b.hoverSeries=null;ga(b.series,a);for(h in a)delete a[h]},drawDataLabels:function(){var a=this,b=a.options.dataLabels,c=a.points,d,e,f,g;if(b.enabled||a._hasPointLabels)a.dlProcessOptions&&a.dlProcessOptions(b),g=a.plotGroup(\"dataLabelsGroup\",\"data-labels\",a.visible?\"visible\":\"hidden\",b.zIndex||6),e=b,n(c,function(c){var i,j=c.dataLabel,k,l,m=c.connector,p=!0;d=c.options&&c.options.dataLabels;i=o(d&&d.enabled,e.enabled);\r\nif(j&&!i)c.dataLabel=j.destroy();else if(i){b=x(e,d);i=b.rotation;k=c.getLabelConfig();f=b.format?Ca(b.format,k):b.formatter.call(k,b);b.style.color=o(b.color,b.style.color,a.color,\"black\");if(j)if(u(f))j.attr({text:f}),p=!1;else{if(c.dataLabel=j=j.destroy(),m)c.connector=m.destroy()}else if(u(f)){j={fill:b.backgroundColor,stroke:b.borderColor,\"stroke-width\":b.borderWidth,r:b.borderRadius||0,rotation:i,padding:b.padding,zIndex:1};for(l in j)j[l]===w&&delete j[l];j=c.dataLabel=a.chart.renderer[i?\"text\":\r\n\"label\"](f,0,-999,null,null,null,b.useHTML).attr(j).css(b.style).add(g).shadow(b.shadow)}j&&a.alignDataLabel(c,j,b,null,p)}})},alignDataLabel:function(a,b,c,d,e){var f=this.chart,g=f.inverted,h=o(a.plotX,-999),i=o(a.plotY,-999),j=b.getBBox();if(a=this.visible&&f.isInsidePlot(a.plotX,a.plotY,g))d=r({x:g?f.plotWidth-i:h,y:t(g?f.plotHeight-h:i),width:0,height:0},d),r(c,{width:j.width,height:j.height}),c.rotation?(g={align:c.align,x:d.x+c.x+d.width/2,y:d.y+c.y+d.height/2},b[e?\"attr\":\"animate\"](g)):(b.align(c,\r\nnull,d),g=b.alignAttr,o(c.overflow,\"justify\")===\"justify\"?this.justifyDataLabel(b,c,g,j,d,e):o(c.crop,!0)&&(a=f.isInsidePlot(g.x,g.y)&&f.isInsidePlot(g.x+j.width,g.y+j.height)));a||b.attr({y:-999})},justifyDataLabel:function(a,b,c,d,e,f){var g=this.chart,h=b.align,i=b.verticalAlign,j,k;j=c.x;if(j<0)h===\"right\"?b.align=\"left\":b.x=-j,k=!0;j=c.x+d.width;if(j>g.plotWidth)h===\"left\"?b.align=\"right\":b.x=g.plotWidth-j,k=!0;j=c.y;if(j<0)i===\"bottom\"?b.verticalAlign=\"top\":b.y=-j,k=!0;j=c.y+d.height;if(j>g.plotHeight)i===\r\n\"top\"?b.verticalAlign=\"bottom\":b.y=g.plotHeight-j,k=!0;if(k)a.placed=!f,a.align(b,null,e)},getSegmentPath:function(a){var b=this,c=[],d=b.options.step;n(a,function(e,f){var g=e.plotX,h=e.plotY,i;b.getPointSpline?c.push.apply(c,b.getPointSpline(a,e,f)):(c.push(f?\"L\":\"M\"),d&&f&&(i=a[f-1],d===\"right\"?c.push(i.plotX,h):d===\"center\"?c.push((i.plotX+g)/2,i.plotY,(i.plotX+g)/2,h):c.push(g,i.plotY)),c.push(e.plotX,e.plotY))});return c},getGraphPath:function(){var a=this,b=[],c,d=[];n(a.segments,function(e){c=\r\na.getSegmentPath(e);e.length>1?b=b.concat(c):d.push(e[0])});a.singlePoints=d;return a.graphPath=b},drawGraph:function(){var a=this,b=this.options,c=[[\"graph\",b.lineColor||this.color]],d=b.lineWidth,e=b.dashStyle,f=this.getGraphPath(),g=b.negativeColor;g&&c.push([\"graphNeg\",g]);n(c,function(c,g){var j=c[0],k=a[j];if(k)Wa(k),k.animate({d:f});else if(d&&f.length)k={stroke:c[1],\"stroke-width\":d,zIndex:1},e?k.dashstyle=e:k[\"stroke-linecap\"]=k[\"stroke-linejoin\"]=\"round\",a[j]=a.chart.renderer.path(f).attr(k).add(a.group).shadow(!g&&\r\nb.shadow)})},clipNeg:function(){var a=this.options,b=this.chart,c=b.renderer,d=a.negativeColor||a.negativeFillColor,e,f=this.graph,g=this.area,h=this.posClip,i=this.negClip;e=b.chartWidth;var j=b.chartHeight,k=s(e,j),l=this.yAxis;if(d&&(f||g)){d=t(l.toPixels(a.threshold||0,!0));a={x:0,y:0,width:k,height:d};k={x:0,y:d,width:k,height:k};if(b.inverted)a.height=k.y=b.plotWidth-d,c.isVML&&(a={x:b.plotWidth-d-b.plotLeft,y:0,width:e,height:j},k={x:d+b.plotLeft-e,y:0,width:b.plotLeft+d,height:e});l.reversed?\r\n(b=k,e=a):(b=a,e=k);h?(h.animate(b),i.animate(e)):(this.posClip=h=c.clipRect(b),this.negClip=i=c.clipRect(e),f&&this.graphNeg&&(f.clip(h),this.graphNeg.clip(i)),g&&(g.clip(h),this.areaNeg.clip(i)))}},invertGroups:function(){function a(){var a={width:b.yAxis.len,height:b.xAxis.len};n([\"group\",\"markerGroup\"],function(c){b[c]&&b[c].attr(a).invert()})}var b=this,c=b.chart;if(b.xAxis)J(c,\"resize\",a),J(b,\"destroy\",function(){aa(c,\"resize\",a)}),a(),b.invertGroups=a},plotGroup:function(a,b,c,d,e){var f=this[a],\r\ng=!f;g&&(this[a]=f=this.chart.renderer.g(b).attr({visibility:c,zIndex:d||0.1}).add(e));f[g?\"attr\":\"animate\"](this.getPlotBox());return f},getPlotBox:function(){return{translateX:this.xAxis?this.xAxis.left:this.chart.plotLeft,translateY:this.yAxis?this.yAxis.top:this.chart.plotTop,scaleX:1,scaleY:1}},render:function(){var a=this.chart,b,c=this.options,d=c.animation&&!!this.animate&&a.renderer.isSVG,e=this.visible?\"visible\":\"hidden\",f=c.zIndex,g=this.hasRendered,h=a.seriesGroup;b=this.plotGroup(\"group\",\r\n\"series\",e,f,h);this.markerGroup=this.plotGroup(\"markerGroup\",\"markers\",e,f,h);d&&this.animate(!0);this.getAttribs();b.inverted=this.isCartesian?a.inverted:!1;this.drawGraph&&(this.drawGraph(),this.clipNeg());this.drawDataLabels();this.drawPoints();this.options.enableMouseTracking!==!1&&this.drawTracker();a.inverted&&this.invertGroups();c.clip!==!1&&!this.sharedClipKey&&!g&&b.clip(a.clipRect);d?this.animate():g||this.afterAnimate();this.isDirty=this.isDirtyData=!1;this.hasRendered=!0},redraw:function(){var a=\r\nthis.chart,b=this.isDirtyData,c=this.group,d=this.xAxis,e=this.yAxis;c&&(a.inverted&&c.attr({width:a.plotWidth,height:a.plotHeight}),c.animate({translateX:o(d&&d.left,a.plotLeft),translateY:o(e&&e.top,a.plotTop)}));this.translate();this.setTooltipPoints(!0);this.render();b&&z(this,\"updatedData\")},setState:function(a){var b=this.options,c=this.graph,d=this.graphNeg,e=b.states,b=b.lineWidth,a=a||\"\";if(this.state!==a)this.state=a,e[a]&&e[a].enabled===!1||(a&&(b=e[a].lineWidth||b+1),c&&!c.dashstyle&&\r\n(a={\"stroke-width\":b},c.attr(a),d&&d.attr(a)))},setVisible:function(a,b){var c=this,d=c.chart,e=c.legendItem,f,g=d.options.chart.ignoreHiddenSeries,h=c.visible;f=(c.visible=a=c.userOptions.visible=a===w?!h:a)?\"show\":\"hide\";n([\"group\",\"dataLabelsGroup\",\"markerGroup\",\"tracker\"],function(a){if(c[a])c[a][f]()});if(d.hoverSeries===c)c.onMouseOut();e&&d.legend.colorizeItem(c,a);c.isDirty=!0;c.options.stacking&&n(d.series,function(a){if(a.options.stacking&&a.visible)a.isDirty=!0});n(c.linkedSeries,function(b){b.setVisible(a,\r\n!1)});if(g)d.isDirtyBox=!0;b!==!1&&d.redraw();z(c,f)},show:function(){this.setVisible(!0)},hide:function(){this.setVisible(!1)},select:function(a){this.selected=a=a===w?!this.selected:a;if(this.checkbox)this.checkbox.checked=a;z(this,a?\"select\":\"unselect\")},drawTracker:function(){var a=this,b=a.options,c=b.trackByArea,d=[].concat(c?a.areaPath:a.graphPath),e=d.length,f=a.chart,g=f.pointer,h=f.renderer,i=f.options.tooltip.snap,j=a.tracker,k=b.cursor,l=k&&{cursor:k},k=a.singlePoints,m,p=function(){if(f.hoverSeries!==\r\na)a.onMouseOver()};if(e&&!c)for(m=e+1;m--;)d[m]===\"M\"&&d.splice(m+1,0,d[m+1]-i,d[m+2],\"L\"),(m&&d[m]===\"M\"||m===e)&&d.splice(m,0,\"L\",d[m-2]+i,d[m-1]);for(m=0;m<k.length;m++)e=k[m],d.push(\"M\",e.plotX-i,e.plotY,\"L\",e.plotX+i,e.plotY);j?j.attr({d:d}):(a.tracker=h.path(d).attr({\"stroke-linejoin\":\"round\",visibility:a.visible?\"visible\":\"hidden\",stroke:Qb,fill:c?Qb:S,\"stroke-width\":b.lineWidth+(c?0:2*i),zIndex:2}).add(a.group),n([a.tracker,a.markerGroup],function(a){a.addClass(\"highcharts-tracker\").on(\"mouseover\",\r\np).on(\"mouseout\",function(a){g.onTrackerMouseOut(a)}).css(l);if(ib)a.on(\"touchstart\",p)}))}};G=ha(Q);W.line=G;Y.area=x(X,{threshold:0});G=ha(Q,{type:\"area\",getSegments:function(){var a=[],b=[],c=[],d=this.xAxis,e=this.yAxis,f=e.stacks[this.stackKey],g={},h,i,j=this.points,k=this.options.connectNulls,l,m,p;if(this.options.stacking&&!this.cropped){for(m=0;m<j.length;m++)g[j[m].x]=j[m];for(p in f)c.push(+p);c.sort(function(a,b){return a-b});n(c,function(a){if(!k||g[a]&&g[a].y!==null)g[a]?b.push(g[a]):\r\n(h=d.translate(a),l=f[a].percent?f[a].total?f[a].cum*100/f[a].total:0:f[a].cum,i=e.toPixels(l,!0),b.push({y:null,plotX:h,clientX:h,plotY:i,yBottom:i,onMouseOver:pa}))});b.length&&a.push(b)}else Q.prototype.getSegments.call(this),a=this.segments;this.segments=a},getSegmentPath:function(a){var b=Q.prototype.getSegmentPath.call(this,a),c=[].concat(b),d,e=this.options;d=b.length;var f=this.yAxis.getThreshold(e.threshold),g;d===3&&c.push(\"L\",b[1],b[2]);if(e.stacking&&!this.closedStacks)for(d=a.length-\r\n1;d>=0;d--)g=o(a[d].yBottom,f),d<a.length-1&&e.step&&c.push(a[d+1].plotX,g),c.push(a[d].plotX,g);else this.closeSegment(c,a,f);this.areaPath=this.areaPath.concat(c);return b},closeSegment:function(a,b,c){a.push(\"L\",b[b.length-1].plotX,c,\"L\",b[0].plotX,c)},drawGraph:function(){this.areaPath=[];Q.prototype.drawGraph.apply(this);var a=this,b=this.areaPath,c=this.options,d=c.negativeColor,e=c.negativeFillColor,f=[[\"area\",this.color,c.fillColor]];(d||e)&&f.push([\"areaNeg\",d,e]);n(f,function(d){var e=d[0],\r\nf=a[e];f?f.animate({d:b}):a[e]=a.chart.renderer.path(b).attr({fill:o(d[2],ra(d[1]).setOpacity(o(c.fillOpacity,0.75)).get()),zIndex:0}).add(a.group)})},drawLegendSymbol:function(a,b){b.legendSymbol=this.chart.renderer.rect(0,a.baseline-11,a.options.symbolWidth,12,2).attr({zIndex:3}).add(b.legendGroup)}});W.area=G;Y.spline=x(X);F=ha(Q,{type:\"spline\",getPointSpline:function(a,b,c){var d=b.plotX,e=b.plotY,f=a[c-1],g=a[c+1],h,i,j,k;if(f&&g){a=f.plotY;j=g.plotX;var g=g.plotY,l;h=(1.5*d+f.plotX)/2.5;i=(1.5*\r\ne+a)/2.5;j=(1.5*d+j)/2.5;k=(1.5*e+g)/2.5;l=(k-i)*(j-d)/(j-h)+e-k;i+=l;k+=l;i>a&&i>e?(i=s(a,e),k=2*e-i):i<a&&i<e&&(i=I(a,e),k=2*e-i);k>g&&k>e?(k=s(g,e),i=2*e-k):k<g&&k<e&&(k=I(g,e),i=2*e-k);b.rightContX=j;b.rightContY=k}c?(b=[\"C\",f.rightContX||f.plotX,f.rightContY||f.plotY,h||d,i||e,d,e],f.rightContX=f.rightContY=null):b=[\"M\",d,e];return b}});W.spline=F;Y.areaspline=x(Y.area);ma=G.prototype;F=ha(F,{type:\"areaspline\",closedStacks:!0,getSegmentPath:ma.getSegmentPath,closeSegment:ma.closeSegment,drawGraph:ma.drawGraph,\r\ndrawLegendSymbol:ma.drawLegendSymbol});W.areaspline=F;Y.column=x(X,{borderColor:\"#FFFFFF\",borderWidth:1,borderRadius:0,groupPadding:0.2,marker:null,pointPadding:0.1,minPointLength:0,cropThreshold:50,pointRange:null,states:{hover:{brightness:0.1,shadow:!1},select:{color:\"#C0C0C0\",borderColor:\"#000000\",shadow:!1}},dataLabels:{align:null,verticalAlign:null,y:null},stickyTracking:!1,threshold:0});F=ha(Q,{type:\"column\",pointAttrToOptions:{stroke:\"borderColor\",\"stroke-width\":\"borderWidth\",fill:\"color\",\r\nr:\"borderRadius\"},cropShoulder:0,trackerGroups:[\"group\",\"dataLabelsGroup\"],negStacks:!0,init:function(){Q.prototype.init.apply(this,arguments);var a=this,b=a.chart;b.hasRendered&&n(b.series,function(b){if(b.type===a.type)b.isDirty=!0})},getColumnMetrics:function(){var a=this,b=a.options,c=a.xAxis,d=a.yAxis,e=c.reversed,f,g={},h,i=0;b.grouping===!1?i=1:n(a.chart.series,function(b){var c=b.options,e=b.yAxis;if(b.type===a.type&&b.visible&&d.len===e.len&&d.pos===e.pos)c.stacking?(f=b.stackKey,g[f]===\r\nw&&(g[f]=i++),h=g[f]):c.grouping!==!1&&(h=i++),b.columnIndex=h});var c=I(N(c.transA)*(c.ordinalSlope||b.pointRange||c.closestPointRange||1),c.len),j=c*b.groupPadding,k=(c-2*j)/i,l=b.pointWidth,b=u(l)?(k-l)/2:k*b.pointPadding,l=o(l,k-2*b);return a.columnMetrics={width:l,offset:b+(j+((e?i-(a.columnIndex||0):a.columnIndex)||0)*k-c/2)*(e?-1:1)}},translate:function(){var a=this.chart,b=this.options,c=b.borderWidth,d=this.yAxis,e=this.translatedThreshold=d.getThreshold(b.threshold),f=o(b.minPointLength,\r\n5),b=this.getColumnMetrics(),g=b.width,h=this.barW=xa(s(g,1+2*c)),i=this.pointXOffset=b.offset,j=-(c%2?0.5:0),k=c%2?0.5:1;a.renderer.isVML&&a.inverted&&(k+=1);Q.prototype.translate.apply(this);n(this.points,function(a){var b=o(a.yBottom,e),c=I(s(-999-b,a.plotY),d.len+999+b),n=a.plotX+i,u=h,r=I(c,b),w,c=s(c,b)-r;N(c)<f&&f&&(c=f,r=t(N(r-e)>f?b-f:e-(d.translate(a.y,0,1,0,1)<=e?f:0)));a.barX=n;a.pointWidth=g;b=N(n)<0.5;u=t(n+u)+j;n=t(n)+j;u-=n;w=N(r)<0.5;c=t(r+c)+k;r=t(r)+k;c-=r;b&&(n+=1,u-=1);w&&(r-=\r\n1,c+=1);a.shapeType=\"rect\";a.shapeArgs={x:n,y:r,width:u,height:c}})},getSymbol:pa,drawLegendSymbol:G.prototype.drawLegendSymbol,drawGraph:pa,drawPoints:function(){var a=this,b=a.options,c=a.chart.renderer,d;n(a.points,function(e){var f=e.plotY,g=e.graphic;if(f!==w&&!isNaN(f)&&e.y!==null)d=e.shapeArgs,g?(Wa(g),g.animate(x(d))):e.graphic=c[e.shapeType](d).attr(e.pointAttr[e.selected?\"select\":\"\"]).add(a.group).shadow(b.shadow,null,b.stacking&&!b.borderRadius);else if(g)e.graphic=g.destroy()})},drawTracker:function(){var a=\r\nthis,b=a.chart,c=b.pointer,d=a.options.cursor,e=d&&{cursor:d},f=function(c){var d=c.target,e;if(b.hoverSeries!==a)a.onMouseOver();for(;d&&!e;)e=d.point,d=d.parentNode;if(e!==w&&e!==b.hoverPoint)e.onMouseOver(c)};n(a.points,function(a){if(a.graphic)a.graphic.element.point=a;if(a.dataLabel)a.dataLabel.element.point=a});if(!a._hasTracking)n(a.trackerGroups,function(b){if(a[b]&&(a[b].addClass(\"highcharts-tracker\").on(\"mouseover\",f).on(\"mouseout\",function(a){c.onTrackerMouseOut(a)}).css(e),ib))a[b].on(\"touchstart\",\r\nf)}),a._hasTracking=!0},alignDataLabel:function(a,b,c,d,e){var f=this.chart,g=f.inverted,h=a.dlBox||a.shapeArgs,i=a.below||a.plotY>o(this.translatedThreshold,f.plotSizeY),j=o(c.inside,!!this.options.stacking);if(h&&(d=x(h),g&&(d={x:f.plotWidth-d.y-d.height,y:f.plotHeight-d.x-d.width,width:d.height,height:d.width}),!j))g?(d.x+=i?0:d.width,d.width=0):(d.y+=i?d.height:0,d.height=0);c.align=o(c.align,!g||j?\"center\":i?\"right\":\"left\");c.verticalAlign=o(c.verticalAlign,g||j?\"middle\":i?\"top\":\"bottom\");Q.prototype.alignDataLabel.call(this,\r\na,b,c,d,e)},animate:function(a){var b=this.yAxis,c=this.options,d=this.chart.inverted,e={};if(Z)a?(e.scaleY=0.001,a=I(b.pos+b.len,s(b.pos,b.toPixels(c.threshold))),d?e.translateX=a-b.len:e.translateY=a,this.group.attr(e)):(e.scaleY=1,e[d?\"translateX\":\"translateY\"]=b.pos,this.group.animate(e,this.options.animation),this.animate=null)},remove:function(){var a=this,b=a.chart;b.hasRendered&&n(b.series,function(b){if(b.type===a.type)b.isDirty=!0});Q.prototype.remove.apply(a,arguments)}});W.column=F;Y.bar=\r\nx(Y.column);ma=ha(F,{type:\"bar\",inverted:!0});W.bar=ma;Y.scatter=x(X,{lineWidth:0,tooltip:{headerFormat:'<span style=\"font-size: 10px; color:{series.color}\">{series.name}</span><br/>',pointFormat:\"x: <b>{point.x}</b><br/>y: <b>{point.y}</b><br/>\",followPointer:!0},stickyTracking:!1});ma=ha(Q,{type:\"scatter\",sorted:!1,requireSorting:!1,noSharedTooltip:!0,trackerGroups:[\"markerGroup\"],drawTracker:F.prototype.drawTracker,setTooltipPoints:pa});W.scatter=ma;Y.pie=x(X,{borderColor:\"#FFFFFF\",borderWidth:1,\r\ncenter:[null,null],clip:!1,colorByPoint:!0,dataLabels:{distance:30,enabled:!0,formatter:function(){return this.point.name}},ignoreHiddenPoint:!0,legendType:\"point\",marker:null,size:null,showInLegend:!1,slicedOffset:10,states:{hover:{brightness:0.1,shadow:!1}},stickyTracking:!1,tooltip:{followPointer:!0}});X={type:\"pie\",isCartesian:!1,pointClass:ha(Pa,{init:function(){Pa.prototype.init.apply(this,arguments);var a=this,b;if(a.y<0)a.y=null;r(a,{visible:a.visible!==!1,name:o(a.name,\"Slice\")});b=function(b){a.slice(b.type===\r\n\"select\")};J(a,\"select\",b);J(a,\"unselect\",b);return a},setVisible:function(a){var b=this,c=b.series,d=c.chart,e;b.visible=b.options.visible=a=a===w?!b.visible:a;c.options.data[qa(b,c.data)]=b.options;e=a?\"show\":\"hide\";n([\"graphic\",\"dataLabel\",\"connector\",\"shadowGroup\"],function(a){if(b[a])b[a][e]()});b.legendItem&&d.legend.colorizeItem(b,a);if(!c.isDirty&&c.options.ignoreHiddenPoint)c.isDirty=!0,d.redraw()},slice:function(a,b,c){var d=this.series;La(c,d.chart);o(b,!0);this.sliced=this.options.sliced=\r\na=u(a)?a:!this.sliced;d.options.data[qa(this,d.data)]=this.options;a=a?this.slicedTranslation:{translateX:0,translateY:0};this.graphic.animate(a);this.shadowGroup&&this.shadowGroup.animate(a)}}),requireSorting:!1,noSharedTooltip:!0,trackerGroups:[\"group\",\"dataLabelsGroup\"],pointAttrToOptions:{stroke:\"borderColor\",\"stroke-width\":\"borderWidth\",fill:\"color\"},getColor:pa,animate:function(a){var b=this,c=b.points,d=b.startAngleRad;if(!a)n(c,function(a){var c=a.graphic,a=a.shapeArgs;c&&(c.attr({r:b.center[3]/\r\n2,start:d,end:d}),c.animate({r:a.r,start:a.start,end:a.end},b.options.animation))}),b.animate=null},setData:function(a,b){Q.prototype.setData.call(this,a,!1);this.processData();this.generatePoints();o(b,!0)&&this.chart.redraw()},generatePoints:function(){var a,b=0,c,d,e,f=this.options.ignoreHiddenPoint;Q.prototype.generatePoints.call(this);c=this.points;d=c.length;for(a=0;a<d;a++)e=c[a],b+=f&&!e.visible?0:e.y;this.total=b;for(a=0;a<d;a++)e=c[a],e.percentage=b>0?e.y/b*100:0,e.total=b},getCenter:function(){var a=\r\nthis.options,b=this.chart,c=2*(a.slicedOffset||0),d,e=b.plotWidth-2*c,f=b.plotHeight-2*c,b=a.center,a=[o(b[0],\"50%\"),o(b[1],\"50%\"),a.size||\"100%\",a.innerSize||0],g=I(e,f),h;return Na(a,function(a,b){h=/%$/.test(a);d=b<2||b===2&&h;return(h?[e,f,g,g][b]*C(a)/100:a)+(d?c:0)})},translate:function(a){this.generatePoints();var b=0,c=this.options,d=c.slicedOffset,e=d+c.borderWidth,f,g,h,i=c.startAngle||0,j=this.startAngleRad=ya/180*(i-90),i=(this.endAngleRad=ya/180*((c.endAngle||i+360)-90))-j,k=this.points,\r\nl=c.dataLabels.distance,c=c.ignoreHiddenPoint,m,n=k.length,o;if(!a)this.center=a=this.getCenter();this.getX=function(b,c){h=R.asin((b-a[1])/(a[2]/2+l));return a[0]+(c?-1:1)*V(h)*(a[2]/2+l)};for(m=0;m<n;m++){o=k[m];f=j+b*i;if(!c||o.visible)b+=o.percentage/100;g=j+b*i;o.shapeType=\"arc\";o.shapeArgs={x:a[0],y:a[1],r:a[2]/2,innerR:a[3]/2,start:t(f*1E3)/1E3,end:t(g*1E3)/1E3};h=(g+f)/2;h>0.75*i&&(h-=2*ya);o.slicedTranslation={translateX:t(V(h)*d),translateY:t(ca(h)*d)};f=V(h)*a[2]/2;g=ca(h)*a[2]/2;o.tooltipPos=\r\n[a[0]+f*0.7,a[1]+g*0.7];o.half=h<-ya/2||h>ya/2?1:0;o.angle=h;e=I(e,l/2);o.labelPos=[a[0]+f+V(h)*l,a[1]+g+ca(h)*l,a[0]+f+V(h)*e,a[1]+g+ca(h)*e,a[0]+f,a[1]+g,l<0?\"center\":o.half?\"right\":\"left\",h]}},setTooltipPoints:pa,drawGraph:null,drawPoints:function(){var a=this,b=a.chart.renderer,c,d,e=a.options.shadow,f,g;if(e&&!a.shadowGroup)a.shadowGroup=b.g(\"shadow\").add(a.group);n(a.points,function(h){d=h.graphic;g=h.shapeArgs;f=h.shadowGroup;if(e&&!f)f=h.shadowGroup=b.g(\"shadow\").add(a.shadowGroup);c=h.sliced?\r\nh.slicedTranslation:{translateX:0,translateY:0};f&&f.attr(c);d?d.animate(r(g,c)):h.graphic=d=b.arc(g).setRadialReference(a.center).attr(h.pointAttr[h.selected?\"select\":\"\"]).attr({\"stroke-linejoin\":\"round\"}).attr(c).add(a.group).shadow(e,f);h.visible===!1&&h.setVisible(!1)})},sortByAngle:function(a,b){a.sort(function(a,d){return a.angle!==void 0&&(d.angle-a.angle)*b})},drawDataLabels:function(){var a=this,b=a.data,c,d=a.chart,e=a.options.dataLabels,f=o(e.connectorPadding,10),g=o(e.connectorWidth,1),\r\nh=d.plotWidth,d=d.plotHeight,i,j,k=o(e.softConnector,!0),l=e.distance,m=a.center,p=m[2]/2,q=m[1],u=l>0,r,w,v,x,C=[[],[]],y,z,E,H,B,D=[0,0,0,0],I=function(a,b){return b.y-a.y};if(a.visible&&(e.enabled||a._hasPointLabels)){Q.prototype.drawDataLabels.apply(a);n(b,function(a){a.dataLabel&&C[a.half].push(a)});for(H=0;!x&&b[H];)x=b[H]&&b[H].dataLabel&&(b[H].dataLabel.getBBox().height||21),H++;for(H=2;H--;){var b=[],K=[],G=C[H],J=G.length,F;a.sortByAngle(G,H-0.5);if(l>0){for(B=q-p-l;B<=q+p+l;B+=x)b.push(B);\r\nw=b.length;if(J>w){c=[].concat(G);c.sort(I);for(B=J;B--;)c[B].rank=B;for(B=J;B--;)G[B].rank>=w&&G.splice(B,1);J=G.length}for(B=0;B<J;B++){c=G[B];v=c.labelPos;c=9999;var O,M;for(M=0;M<w;M++)O=N(b[M]-v[1]),O<c&&(c=O,F=M);if(F<B&&b[B]!==null)F=B;else for(w<J-B+F&&b[B]!==null&&(F=w-J+B);b[F]===null;)F++;K.push({i:F,y:b[F]});b[F]=null}K.sort(I)}for(B=0;B<J;B++){c=G[B];v=c.labelPos;r=c.dataLabel;E=c.visible===!1?\"hidden\":\"visible\";c=v[1];if(l>0){if(w=K.pop(),F=w.i,z=w.y,c>z&&b[F+1]!==null||c<z&&b[F-1]!==\r\nnull)z=c}else z=c;y=e.justify?m[0]+(H?-1:1)*(p+l):a.getX(F===0||F===b.length-1?c:z,H);r._attr={visibility:E,align:v[6]};r._pos={x:y+e.x+({left:f,right:-f}[v[6]]||0),y:z+e.y-10};r.connX=y;r.connY=z;if(this.options.size===null)w=r.width,y-w<f?D[3]=s(t(w-y+f),D[3]):y+w>h-f&&(D[1]=s(t(y+w-h+f),D[1])),z-x/2<0?D[0]=s(t(-z+x/2),D[0]):z+x/2>d&&(D[2]=s(t(z+x/2-d),D[2]))}}if(va(D)===0||this.verifyDataLabelOverflow(D))this.placeDataLabels(),u&&g&&n(this.points,function(b){i=b.connector;v=b.labelPos;if((r=b.dataLabel)&&\r\nr._pos)E=r._attr.visibility,y=r.connX,z=r.connY,j=k?[\"M\",y+(v[6]===\"left\"?5:-5),z,\"C\",y,z,2*v[2]-v[4],2*v[3]-v[5],v[2],v[3],\"L\",v[4],v[5]]:[\"M\",y+(v[6]===\"left\"?5:-5),z,\"L\",v[2],v[3],\"L\",v[4],v[5]],i?(i.animate({d:j}),i.attr(\"visibility\",E)):b.connector=i=a.chart.renderer.path(j).attr({\"stroke-width\":g,stroke:e.connectorColor||b.color||\"#606060\",visibility:E}).add(a.group);else if(i)b.connector=i.destroy()})}},verifyDataLabelOverflow:function(a){var b=this.center,c=this.options,d=c.center,e=c=c.minSize||\r\n80,f;d[0]!==null?e=s(b[2]-s(a[1],a[3]),c):(e=s(b[2]-a[1]-a[3],c),b[0]+=(a[3]-a[1])/2);d[1]!==null?e=s(I(e,b[2]-s(a[0],a[2])),c):(e=s(I(e,b[2]-a[0]-a[2]),c),b[1]+=(a[0]-a[2])/2);e<b[2]?(b[2]=e,this.translate(b),n(this.points,function(a){if(a.dataLabel)a.dataLabel._pos=null}),this.drawDataLabels()):f=!0;return f},placeDataLabels:function(){n(this.points,function(a){var a=a.dataLabel,b;if(a)(b=a._pos)?(a.attr(a._attr),a[a.moved?\"animate\":\"attr\"](b),a.moved=!0):a&&a.attr({y:-999})})},alignDataLabel:pa,\r\ndrawTracker:F.prototype.drawTracker,drawLegendSymbol:G.prototype.drawLegendSymbol,getSymbol:pa};X=ha(Q,X);W.pie=X;r(Highcharts,{Axis:db,Chart:yb,Color:ra,Legend:eb,Pointer:xb,Point:Pa,Tick:Ma,Tooltip:wb,Renderer:Va,Series:Q,SVGElement:wa,SVGRenderer:Ha,arrayMin:Ja,arrayMax:va,charts:Ga,dateFormat:Xa,format:Ca,pathAnim:Ab,getOptions:function(){return M},hasBidiBug:Ub,isTouchDevice:Ob,numberFormat:Aa,seriesTypes:W,setOptions:function(a){M=x(M,a);Lb();return M},addEvent:J,removeEvent:aa,createElement:U,\r\ndiscardElement:Ta,css:K,each:n,extend:r,map:Na,merge:x,pick:o,splat:ja,extendClass:ha,pInt:C,wrap:mb,svg:Z,canvas:$,vml:!Z&&!$,product:\"Highcharts\",version:\"3.0.6\"})})();\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/highcharts/highcharts.src.js",
    "content": "// ==ClosureCompiler==\r\n// @compilation_level SIMPLE_OPTIMIZATIONS\r\n\r\n/**\r\n * @license Highcharts JS v3.0.6 (2013-10-04)\r\n *\r\n * (c) 2009-2013 Torstein Hønsi\r\n *\r\n * License: www.highcharts.com/license\r\n */\r\n\r\n// JSLint options:\r\n/*global Highcharts, document, window, navigator, setInterval, clearInterval, clearTimeout, setTimeout, location, jQuery, $, console, each, grep */\r\n\r\n(function () {\r\n// encapsulated variables\r\nvar UNDEFINED,\r\n\tdoc = document,\r\n\twin = window,\r\n\tmath = Math,\r\n\tmathRound = math.round,\r\n\tmathFloor = math.floor,\r\n\tmathCeil = math.ceil,\r\n\tmathMax = math.max,\r\n\tmathMin = math.min,\r\n\tmathAbs = math.abs,\r\n\tmathCos = math.cos,\r\n\tmathSin = math.sin,\r\n\tmathPI = math.PI,\r\n\tdeg2rad = mathPI * 2 / 360,\r\n\r\n\r\n\t// some variables\r\n\tuserAgent = navigator.userAgent,\r\n\tisOpera = win.opera,\r\n\tisIE = /msie/i.test(userAgent) && !isOpera,\r\n\tdocMode8 = doc.documentMode === 8,\r\n\tisWebKit = /AppleWebKit/.test(userAgent),\r\n\tisFirefox = /Firefox/.test(userAgent),\r\n\tisTouchDevice = /(Mobile|Android|Windows Phone)/.test(userAgent),\r\n\tSVG_NS = 'http://www.w3.org/2000/svg',\r\n\thasSVG = !!doc.createElementNS && !!doc.createElementNS(SVG_NS, 'svg').createSVGRect,\r\n\thasBidiBug = isFirefox && parseInt(userAgent.split('Firefox/')[1], 10) < 4, // issue #38\r\n\tuseCanVG = !hasSVG && !isIE && !!doc.createElement('canvas').getContext,\r\n\tRenderer,\r\n\thasTouch = doc.documentElement.ontouchstart !== UNDEFINED,\r\n\tsymbolSizes = {},\r\n\tidCounter = 0,\r\n\tgarbageBin,\r\n\tdefaultOptions,\r\n\tdateFormat, // function\r\n\tglobalAnimation,\r\n\tpathAnim,\r\n\ttimeUnits,\r\n\tnoop = function () {},\r\n\tcharts = [],\r\n\tPRODUCT = 'Highcharts',\r\n\tVERSION = '3.0.6',\r\n\r\n\t// some constants for frequently used strings\r\n\tDIV = 'div',\r\n\tABSOLUTE = 'absolute',\r\n\tRELATIVE = 'relative',\r\n\tHIDDEN = 'hidden',\r\n\tPREFIX = 'highcharts-',\r\n\tVISIBLE = 'visible',\r\n\tPX = 'px',\r\n\tNONE = 'none',\r\n\tM = 'M',\r\n\tL = 'L',\r\n\t/*\r\n\t * Empirical lowest possible opacities for TRACKER_FILL\r\n\t * IE6: 0.002\r\n\t * IE7: 0.002\r\n\t * IE8: 0.002\r\n\t * IE9: 0.00000000001 (unlimited)\r\n\t * IE10: 0.0001 (exporting only)\r\n\t * FF: 0.00000000001 (unlimited)\r\n\t * Chrome: 0.000001\r\n\t * Safari: 0.000001\r\n\t * Opera: 0.00000000001 (unlimited)\r\n\t */\r\n\tTRACKER_FILL = 'rgba(192,192,192,' + (hasSVG ? 0.0001 : 0.002) + ')', // invisible but clickable\r\n\t//TRACKER_FILL = 'rgba(192,192,192,0.5)',\r\n\tNORMAL_STATE = '',\r\n\tHOVER_STATE = 'hover',\r\n\tSELECT_STATE = 'select',\r\n\tMILLISECOND = 'millisecond',\r\n\tSECOND = 'second',\r\n\tMINUTE = 'minute',\r\n\tHOUR = 'hour',\r\n\tDAY = 'day',\r\n\tWEEK = 'week',\r\n\tMONTH = 'month',\r\n\tYEAR = 'year',\r\n\r\n\t// constants for attributes\r\n\tLINEAR_GRADIENT = 'linearGradient',\r\n\tSTOPS = 'stops',\r\n\tSTROKE_WIDTH = 'stroke-width',\r\n\r\n\t// time methods, changed based on whether or not UTC is used\r\n\tmakeTime,\r\n\tgetMinutes,\r\n\tgetHours,\r\n\tgetDay,\r\n\tgetDate,\r\n\tgetMonth,\r\n\tgetFullYear,\r\n\tsetMinutes,\r\n\tsetHours,\r\n\tsetDate,\r\n\tsetMonth,\r\n\tsetFullYear,\r\n\r\n\r\n\t// lookup over the types and the associated classes\r\n\tseriesTypes = {};\r\n\r\n// The Highcharts namespace\r\nwin.Highcharts = win.Highcharts ? error(16, true) : {};\r\n\r\n/**\r\n * Extend an object with the members of another\r\n * @param {Object} a The object to be extended\r\n * @param {Object} b The object to add to the first one\r\n */\r\nfunction extend(a, b) {\r\n\tvar n;\r\n\tif (!a) {\r\n\t\ta = {};\r\n\t}\r\n\tfor (n in b) {\r\n\t\ta[n] = b[n];\r\n\t}\r\n\treturn a;\r\n}\r\n\t\r\n/**\r\n * Deep merge two or more objects and return a third object.\r\n * Previously this function redirected to jQuery.extend(true), but this had two limitations.\r\n * First, it deep merged arrays, which lead to workarounds in Highcharts. Second,\r\n * it copied properties from extended prototypes. \r\n */\r\nfunction merge() {\r\n\tvar i,\r\n\t\tlen = arguments.length,\r\n\t\tret = {},\r\n\t\tdoCopy = function (copy, original) {\r\n\t\t\tvar value, key;\r\n\r\n\t\t\t// An object is replacing a primitive\r\n\t\t\tif (typeof copy !== 'object') {\r\n\t\t\t\tcopy = {};\r\n\t\t\t}\r\n\r\n\t\t\tfor (key in original) {\r\n\t\t\t\tif (original.hasOwnProperty(key)) {\r\n\t\t\t\t\tvalue = original[key];\r\n\r\n\t\t\t\t\t// Copy the contents of objects, but not arrays or DOM nodes\r\n\t\t\t\t\tif (value && typeof value === 'object' && Object.prototype.toString.call(value) !== '[object Array]'\r\n\t\t\t\t\t\t\t&& typeof value.nodeType !== 'number') {\r\n\t\t\t\t\t\tcopy[key] = doCopy(copy[key] || {}, value);\r\n\t\t\t\t\r\n\t\t\t\t\t// Primitives and arrays are copied over directly\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tcopy[key] = original[key];\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\treturn copy;\r\n\t\t};\r\n\r\n\t// For each argument, extend the return\r\n\tfor (i = 0; i < len; i++) {\r\n\t\tret = doCopy(ret, arguments[i]);\r\n\t}\r\n\r\n\treturn ret;\r\n}\r\n\r\n/**\r\n * Take an array and turn into a hash with even number arguments as keys and odd numbers as\r\n * values. Allows creating constants for commonly used style properties, attributes etc.\r\n * Avoid it in performance critical situations like looping\r\n */\r\nfunction hash() {\r\n\tvar i = 0,\r\n\t\targs = arguments,\r\n\t\tlength = args.length,\r\n\t\tobj = {};\r\n\tfor (; i < length; i++) {\r\n\t\tobj[args[i++]] = args[i];\r\n\t}\r\n\treturn obj;\r\n}\r\n\r\n/**\r\n * Shortcut for parseInt\r\n * @param {Object} s\r\n * @param {Number} mag Magnitude\r\n */\r\nfunction pInt(s, mag) {\r\n\treturn parseInt(s, mag || 10);\r\n}\r\n\r\n/**\r\n * Check for string\r\n * @param {Object} s\r\n */\r\nfunction isString(s) {\r\n\treturn typeof s === 'string';\r\n}\r\n\r\n/**\r\n * Check for object\r\n * @param {Object} obj\r\n */\r\nfunction isObject(obj) {\r\n\treturn typeof obj === 'object';\r\n}\r\n\r\n/**\r\n * Check for array\r\n * @param {Object} obj\r\n */\r\nfunction isArray(obj) {\r\n\treturn Object.prototype.toString.call(obj) === '[object Array]';\r\n}\r\n\r\n/**\r\n * Check for number\r\n * @param {Object} n\r\n */\r\nfunction isNumber(n) {\r\n\treturn typeof n === 'number';\r\n}\r\n\r\nfunction log2lin(num) {\r\n\treturn math.log(num) / math.LN10;\r\n}\r\nfunction lin2log(num) {\r\n\treturn math.pow(10, num);\r\n}\r\n\r\n/**\r\n * Remove last occurence of an item from an array\r\n * @param {Array} arr\r\n * @param {Mixed} item\r\n */\r\nfunction erase(arr, item) {\r\n\tvar i = arr.length;\r\n\twhile (i--) {\r\n\t\tif (arr[i] === item) {\r\n\t\t\tarr.splice(i, 1);\r\n\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n\t//return arr;\r\n}\r\n\r\n/**\r\n * Returns true if the object is not null or undefined. Like MooTools' $.defined.\r\n * @param {Object} obj\r\n */\r\nfunction defined(obj) {\r\n\treturn obj !== UNDEFINED && obj !== null;\r\n}\r\n\r\n/**\r\n * Set or get an attribute or an object of attributes. Can't use jQuery attr because\r\n * it attempts to set expando properties on the SVG element, which is not allowed.\r\n *\r\n * @param {Object} elem The DOM element to receive the attribute(s)\r\n * @param {String|Object} prop The property or an abject of key-value pairs\r\n * @param {String} value The value if a single property is set\r\n */\r\nfunction attr(elem, prop, value) {\r\n\tvar key,\r\n\t\tsetAttribute = 'setAttribute',\r\n\t\tret;\r\n\r\n\t// if the prop is a string\r\n\tif (isString(prop)) {\r\n\t\t// set the value\r\n\t\tif (defined(value)) {\r\n\r\n\t\t\telem[setAttribute](prop, value);\r\n\r\n\t\t// get the value\r\n\t\t} else if (elem && elem.getAttribute) { // elem not defined when printing pie demo...\r\n\t\t\tret = elem.getAttribute(prop);\r\n\t\t}\r\n\r\n\t// else if prop is defined, it is a hash of key/value pairs\r\n\t} else if (defined(prop) && isObject(prop)) {\r\n\t\tfor (key in prop) {\r\n\t\t\telem[setAttribute](key, prop[key]);\r\n\t\t}\r\n\t}\r\n\treturn ret;\r\n}\r\n/**\r\n * Check if an element is an array, and if not, make it into an array. Like\r\n * MooTools' $.splat.\r\n */\r\nfunction splat(obj) {\r\n\treturn isArray(obj) ? obj : [obj];\r\n}\r\n\r\n\r\n/**\r\n * Return the first value that is defined. Like MooTools' $.pick.\r\n */\r\nfunction pick() {\r\n\tvar args = arguments,\r\n\t\ti,\r\n\t\targ,\r\n\t\tlength = args.length;\r\n\tfor (i = 0; i < length; i++) {\r\n\t\targ = args[i];\r\n\t\tif (typeof arg !== 'undefined' && arg !== null) {\r\n\t\t\treturn arg;\r\n\t\t}\r\n\t}\r\n}\r\n\r\n/**\r\n * Set CSS on a given element\r\n * @param {Object} el\r\n * @param {Object} styles Style object with camel case property names\r\n */\r\nfunction css(el, styles) {\r\n\tif (isIE) {\r\n\t\tif (styles && styles.opacity !== UNDEFINED) {\r\n\t\t\tstyles.filter = 'alpha(opacity=' + (styles.opacity * 100) + ')';\r\n\t\t}\r\n\t}\r\n\textend(el.style, styles);\r\n}\r\n\r\n/**\r\n * Utility function to create element with attributes and styles\r\n * @param {Object} tag\r\n * @param {Object} attribs\r\n * @param {Object} styles\r\n * @param {Object} parent\r\n * @param {Object} nopad\r\n */\r\nfunction createElement(tag, attribs, styles, parent, nopad) {\r\n\tvar el = doc.createElement(tag);\r\n\tif (attribs) {\r\n\t\textend(el, attribs);\r\n\t}\r\n\tif (nopad) {\r\n\t\tcss(el, {padding: 0, border: NONE, margin: 0});\r\n\t}\r\n\tif (styles) {\r\n\t\tcss(el, styles);\r\n\t}\r\n\tif (parent) {\r\n\t\tparent.appendChild(el);\r\n\t}\r\n\treturn el;\r\n}\r\n\r\n/**\r\n * Extend a prototyped class by new members\r\n * @param {Object} parent\r\n * @param {Object} members\r\n */\r\nfunction extendClass(parent, members) {\r\n\tvar object = function () {};\r\n\tobject.prototype = new parent();\r\n\textend(object.prototype, members);\r\n\treturn object;\r\n}\r\n\r\n/**\r\n * Format a number and return a string based on input settings\r\n * @param {Number} number The input number to format\r\n * @param {Number} decimals The amount of decimals\r\n * @param {String} decPoint The decimal point, defaults to the one given in the lang options\r\n * @param {String} thousandsSep The thousands separator, defaults to the one given in the lang options\r\n */\r\nfunction numberFormat(number, decimals, decPoint, thousandsSep) {\r\n\tvar lang = defaultOptions.lang,\r\n\t\t// http://kevin.vanzonneveld.net/techblog/article/javascript_equivalent_for_phps_number_format/\r\n\t\tn = +number || 0,\r\n\t\tc = decimals === -1 ?\r\n\t\t\t(n.toString().split('.')[1] || '').length : // preserve decimals\r\n\t\t\t(isNaN(decimals = mathAbs(decimals)) ? 2 : decimals),\r\n\t\td = decPoint === undefined ? lang.decimalPoint : decPoint,\r\n\t\tt = thousandsSep === undefined ? lang.thousandsSep : thousandsSep,\r\n\t\ts = n < 0 ? \"-\" : \"\",\r\n\t\ti = String(pInt(n = mathAbs(n).toFixed(c))),\r\n\t\tj = i.length > 3 ? i.length % 3 : 0;\r\n\r\n\treturn s + (j ? i.substr(0, j) + t : \"\") + i.substr(j).replace(/(\\d{3})(?=\\d)/g, \"$1\" + t) +\r\n\t\t(c ? d + mathAbs(n - i).toFixed(c).slice(2) : \"\");\r\n}\r\n\r\n/**\r\n * Pad a string to a given length by adding 0 to the beginning\r\n * @param {Number} number\r\n * @param {Number} length\r\n */\r\nfunction pad(number, length) {\r\n\t// Create an array of the remaining length +1 and join it with 0's\r\n\treturn new Array((length || 2) + 1 - String(number).length).join(0) + number;\r\n}\r\n\r\n/**\r\n * Wrap a method with extended functionality, preserving the original function\r\n * @param {Object} obj The context object that the method belongs to \r\n * @param {String} method The name of the method to extend\r\n * @param {Function} func A wrapper function callback. This function is called with the same arguments\r\n * as the original function, except that the original function is unshifted and passed as the first \r\n * argument. \r\n */\r\nfunction wrap(obj, method, func) {\r\n\tvar proceed = obj[method];\r\n\tobj[method] = function () {\r\n\t\tvar args = Array.prototype.slice.call(arguments);\r\n\t\targs.unshift(proceed);\r\n\t\treturn func.apply(this, args);\r\n\t};\r\n}\r\n\r\n/**\r\n * Based on http://www.php.net/manual/en/function.strftime.php\r\n * @param {String} format\r\n * @param {Number} timestamp\r\n * @param {Boolean} capitalize\r\n */\r\ndateFormat = function (format, timestamp, capitalize) {\r\n\tif (!defined(timestamp) || isNaN(timestamp)) {\r\n\t\treturn 'Invalid date';\r\n\t}\r\n\tformat = pick(format, '%Y-%m-%d %H:%M:%S');\r\n\r\n\tvar date = new Date(timestamp),\r\n\t\tkey, // used in for constuct below\r\n\t\t// get the basic time values\r\n\t\thours = date[getHours](),\r\n\t\tday = date[getDay](),\r\n\t\tdayOfMonth = date[getDate](),\r\n\t\tmonth = date[getMonth](),\r\n\t\tfullYear = date[getFullYear](),\r\n\t\tlang = defaultOptions.lang,\r\n\t\tlangWeekdays = lang.weekdays,\r\n\r\n\t\t// List all format keys. Custom formats can be added from the outside. \r\n\t\treplacements = extend({\r\n\r\n\t\t\t// Day\r\n\t\t\t'a': langWeekdays[day].substr(0, 3), // Short weekday, like 'Mon'\r\n\t\t\t'A': langWeekdays[day], // Long weekday, like 'Monday'\r\n\t\t\t'd': pad(dayOfMonth), // Two digit day of the month, 01 to 31\r\n\t\t\t'e': dayOfMonth, // Day of the month, 1 through 31\r\n\r\n\t\t\t// Week (none implemented)\r\n\t\t\t//'W': weekNumber(),\r\n\r\n\t\t\t// Month\r\n\t\t\t'b': lang.shortMonths[month], // Short month, like 'Jan'\r\n\t\t\t'B': lang.months[month], // Long month, like 'January'\r\n\t\t\t'm': pad(month + 1), // Two digit month number, 01 through 12\r\n\r\n\t\t\t// Year\r\n\t\t\t'y': fullYear.toString().substr(2, 2), // Two digits year, like 09 for 2009\r\n\t\t\t'Y': fullYear, // Four digits year, like 2009\r\n\r\n\t\t\t// Time\r\n\t\t\t'H': pad(hours), // Two digits hours in 24h format, 00 through 23\r\n\t\t\t'I': pad((hours % 12) || 12), // Two digits hours in 12h format, 00 through 11\r\n\t\t\t'l': (hours % 12) || 12, // Hours in 12h format, 1 through 12\r\n\t\t\t'M': pad(date[getMinutes]()), // Two digits minutes, 00 through 59\r\n\t\t\t'p': hours < 12 ? 'AM' : 'PM', // Upper case AM or PM\r\n\t\t\t'P': hours < 12 ? 'am' : 'pm', // Lower case AM or PM\r\n\t\t\t'S': pad(date.getSeconds()), // Two digits seconds, 00 through  59\r\n\t\t\t'L': pad(mathRound(timestamp % 1000), 3) // Milliseconds (naming from Ruby)\r\n\t\t}, Highcharts.dateFormats);\r\n\r\n\r\n\t// do the replaces\r\n\tfor (key in replacements) {\r\n\t\twhile (format.indexOf('%' + key) !== -1) { // regex would do it in one line, but this is faster\r\n\t\t\tformat = format.replace('%' + key, typeof replacements[key] === 'function' ? replacements[key](timestamp) : replacements[key]);\r\n\t\t}\r\n\t}\r\n\r\n\t// Optionally capitalize the string and return\r\n\treturn capitalize ? format.substr(0, 1).toUpperCase() + format.substr(1) : format;\r\n};\r\n\r\n/** \r\n * Format a single variable. Similar to sprintf, without the % prefix.\r\n */\r\nfunction formatSingle(format, val) {\r\n\tvar floatRegex = /f$/,\r\n\t\tdecRegex = /\\.([0-9])/,\r\n\t\tlang = defaultOptions.lang,\r\n\t\tdecimals;\r\n\r\n\tif (floatRegex.test(format)) { // float\r\n\t\tdecimals = format.match(decRegex);\r\n\t\tdecimals = decimals ? decimals[1] : -1;\r\n\t\tval = numberFormat(\r\n\t\t\tval,\r\n\t\t\tdecimals,\r\n\t\t\tlang.decimalPoint,\r\n\t\t\tformat.indexOf(',') > -1 ? lang.thousandsSep : ''\r\n\t\t);\r\n\t} else {\r\n\t\tval = dateFormat(format, val);\r\n\t}\r\n\treturn val;\r\n}\r\n\r\n/**\r\n * Format a string according to a subset of the rules of Python's String.format method.\r\n */\r\nfunction format(str, ctx) {\r\n\tvar splitter = '{',\r\n\t\tisInside = false,\r\n\t\tsegment,\r\n\t\tvalueAndFormat,\r\n\t\tpath,\r\n\t\ti,\r\n\t\tlen,\r\n\t\tret = [],\r\n\t\tval,\r\n\t\tindex;\r\n\t\r\n\twhile ((index = str.indexOf(splitter)) !== -1) {\r\n\t\t\r\n\t\tsegment = str.slice(0, index);\r\n\t\tif (isInside) { // we're on the closing bracket looking back\r\n\t\t\t\r\n\t\t\tvalueAndFormat = segment.split(':');\r\n\t\t\tpath = valueAndFormat.shift().split('.'); // get first and leave format\r\n\t\t\tlen = path.length;\r\n\t\t\tval = ctx;\r\n\r\n\t\t\t// Assign deeper paths\r\n\t\t\tfor (i = 0; i < len; i++) {\r\n\t\t\t\tval = val[path[i]];\r\n\t\t\t}\r\n\r\n\t\t\t// Format the replacement\r\n\t\t\tif (valueAndFormat.length) {\r\n\t\t\t\tval = formatSingle(valueAndFormat.join(':'), val);\r\n\t\t\t}\r\n\r\n\t\t\t// Push the result and advance the cursor\r\n\t\t\tret.push(val);\r\n\t\t\t\r\n\t\t} else {\r\n\t\t\tret.push(segment);\r\n\t\t\t\r\n\t\t}\r\n\t\tstr = str.slice(index + 1); // the rest\r\n\t\tisInside = !isInside; // toggle\r\n\t\tsplitter = isInside ? '}' : '{'; // now look for next matching bracket\r\n\t}\r\n\tret.push(str);\r\n\treturn ret.join('');\r\n}\r\n\r\n/**\r\n * Get the magnitude of a number\r\n */\r\nfunction getMagnitude(num) {\r\n\treturn math.pow(10, mathFloor(math.log(num) / math.LN10));\r\n}\r\n\r\n/**\r\n * Take an interval and normalize it to multiples of 1, 2, 2.5 and 5\r\n * @param {Number} interval\r\n * @param {Array} multiples\r\n * @param {Number} magnitude\r\n * @param {Object} options\r\n */\r\nfunction normalizeTickInterval(interval, multiples, magnitude, options) {\r\n\tvar normalized, i;\r\n\r\n\t// round to a tenfold of 1, 2, 2.5 or 5\r\n\tmagnitude = pick(magnitude, 1);\r\n\tnormalized = interval / magnitude;\r\n\r\n\t// multiples for a linear scale\r\n\tif (!multiples) {\r\n\t\tmultiples = [1, 2, 2.5, 5, 10];\r\n\r\n\t\t// the allowDecimals option\r\n\t\tif (options && options.allowDecimals === false) {\r\n\t\t\tif (magnitude === 1) {\r\n\t\t\t\tmultiples = [1, 2, 5, 10];\r\n\t\t\t} else if (magnitude <= 0.1) {\r\n\t\t\t\tmultiples = [1 / magnitude];\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t// normalize the interval to the nearest multiple\r\n\tfor (i = 0; i < multiples.length; i++) {\r\n\t\tinterval = multiples[i];\r\n\t\tif (normalized <= (multiples[i] + (multiples[i + 1] || multiples[i])) / 2) {\r\n\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n\r\n\t// multiply back to the correct magnitude\r\n\tinterval *= magnitude;\r\n\r\n\treturn interval;\r\n}\r\n\r\n/**\r\n * Get a normalized tick interval for dates. Returns a configuration object with\r\n * unit range (interval), count and name. Used to prepare data for getTimeTicks. \r\n * Previously this logic was part of getTimeTicks, but as getTimeTicks now runs\r\n * of segments in stock charts, the normalizing logic was extracted in order to \r\n * prevent it for running over again for each segment having the same interval. \r\n * #662, #697.\r\n */\r\nfunction normalizeTimeTickInterval(tickInterval, unitsOption) {\r\n\tvar units = unitsOption || [[\r\n\t\t\t\tMILLISECOND, // unit name\r\n\t\t\t\t[1, 2, 5, 10, 20, 25, 50, 100, 200, 500] // allowed multiples\r\n\t\t\t], [\r\n\t\t\t\tSECOND,\r\n\t\t\t\t[1, 2, 5, 10, 15, 30]\r\n\t\t\t], [\r\n\t\t\t\tMINUTE,\r\n\t\t\t\t[1, 2, 5, 10, 15, 30]\r\n\t\t\t], [\r\n\t\t\t\tHOUR,\r\n\t\t\t\t[1, 2, 3, 4, 6, 8, 12]\r\n\t\t\t], [\r\n\t\t\t\tDAY,\r\n\t\t\t\t[1, 2]\r\n\t\t\t], [\r\n\t\t\t\tWEEK,\r\n\t\t\t\t[1, 2]\r\n\t\t\t], [\r\n\t\t\t\tMONTH,\r\n\t\t\t\t[1, 2, 3, 4, 6]\r\n\t\t\t], [\r\n\t\t\t\tYEAR,\r\n\t\t\t\tnull\r\n\t\t\t]],\r\n\t\tunit = units[units.length - 1], // default unit is years\r\n\t\tinterval = timeUnits[unit[0]],\r\n\t\tmultiples = unit[1],\r\n\t\tcount,\r\n\t\ti;\r\n\t\t\r\n\t// loop through the units to find the one that best fits the tickInterval\r\n\tfor (i = 0; i < units.length; i++) {\r\n\t\tunit = units[i];\r\n\t\tinterval = timeUnits[unit[0]];\r\n\t\tmultiples = unit[1];\r\n\r\n\r\n\t\tif (units[i + 1]) {\r\n\t\t\t// lessThan is in the middle between the highest multiple and the next unit.\r\n\t\t\tvar lessThan = (interval * multiples[multiples.length - 1] +\r\n\t\t\t\t\t\ttimeUnits[units[i + 1][0]]) / 2;\r\n\r\n\t\t\t// break and keep the current unit\r\n\t\t\tif (tickInterval <= lessThan) {\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t// prevent 2.5 years intervals, though 25, 250 etc. are allowed\r\n\tif (interval === timeUnits[YEAR] && tickInterval < 5 * interval) {\r\n\t\tmultiples = [1, 2, 5];\r\n\t}\r\n\r\n\t// get the count\r\n\tcount = normalizeTickInterval(\r\n\t\ttickInterval / interval, \r\n\t\tmultiples,\r\n\t\tunit[0] === YEAR ? getMagnitude(tickInterval / interval) : 1 // #1913\r\n\t);\r\n\t\r\n\treturn {\r\n\t\tunitRange: interval,\r\n\t\tcount: count,\r\n\t\tunitName: unit[0]\r\n\t};\r\n}\r\n\r\n/**\r\n * Set the tick positions to a time unit that makes sense, for example\r\n * on the first of each month or on every Monday. Return an array\r\n * with the time positions. Used in datetime axes as well as for grouping\r\n * data on a datetime axis.\r\n *\r\n * @param {Object} normalizedInterval The interval in axis values (ms) and the count\r\n * @param {Number} min The minimum in axis values\r\n * @param {Number} max The maximum in axis values\r\n * @param {Number} startOfWeek\r\n */\r\nfunction getTimeTicks(normalizedInterval, min, max, startOfWeek) {\r\n\tvar tickPositions = [],\r\n\t\ti,\r\n\t\thigherRanks = {},\r\n\t\tuseUTC = defaultOptions.global.useUTC,\r\n\t\tminYear, // used in months and years as a basis for Date.UTC()\r\n\t\tminDate = new Date(min),\r\n\t\tinterval = normalizedInterval.unitRange,\r\n\t\tcount = normalizedInterval.count;\r\n\r\n\tif (defined(min)) { // #1300\r\n\t\tif (interval >= timeUnits[SECOND]) { // second\r\n\t\t\tminDate.setMilliseconds(0);\r\n\t\t\tminDate.setSeconds(interval >= timeUnits[MINUTE] ? 0 :\r\n\t\t\t\tcount * mathFloor(minDate.getSeconds() / count));\r\n\t\t}\r\n\t\r\n\t\tif (interval >= timeUnits[MINUTE]) { // minute\r\n\t\t\tminDate[setMinutes](interval >= timeUnits[HOUR] ? 0 :\r\n\t\t\t\tcount * mathFloor(minDate[getMinutes]() / count));\r\n\t\t}\r\n\t\r\n\t\tif (interval >= timeUnits[HOUR]) { // hour\r\n\t\t\tminDate[setHours](interval >= timeUnits[DAY] ? 0 :\r\n\t\t\t\tcount * mathFloor(minDate[getHours]() / count));\r\n\t\t}\r\n\t\r\n\t\tif (interval >= timeUnits[DAY]) { // day\r\n\t\t\tminDate[setDate](interval >= timeUnits[MONTH] ? 1 :\r\n\t\t\t\tcount * mathFloor(minDate[getDate]() / count));\r\n\t\t}\r\n\t\r\n\t\tif (interval >= timeUnits[MONTH]) { // month\r\n\t\t\tminDate[setMonth](interval >= timeUnits[YEAR] ? 0 :\r\n\t\t\t\tcount * mathFloor(minDate[getMonth]() / count));\r\n\t\t\tminYear = minDate[getFullYear]();\r\n\t\t}\r\n\t\r\n\t\tif (interval >= timeUnits[YEAR]) { // year\r\n\t\t\tminYear -= minYear % count;\r\n\t\t\tminDate[setFullYear](minYear);\r\n\t\t}\r\n\t\r\n\t\t// week is a special case that runs outside the hierarchy\r\n\t\tif (interval === timeUnits[WEEK]) {\r\n\t\t\t// get start of current week, independent of count\r\n\t\t\tminDate[setDate](minDate[getDate]() - minDate[getDay]() +\r\n\t\t\t\tpick(startOfWeek, 1));\r\n\t\t}\r\n\t\r\n\t\r\n\t\t// get tick positions\r\n\t\ti = 1;\r\n\t\tminYear = minDate[getFullYear]();\r\n\t\tvar time = minDate.getTime(),\r\n\t\t\tminMonth = minDate[getMonth](),\r\n\t\t\tminDateDate = minDate[getDate](),\r\n\t\t\ttimezoneOffset = useUTC ? \r\n\t\t\t\t0 : \r\n\t\t\t\t(24 * 3600 * 1000 + minDate.getTimezoneOffset() * 60 * 1000) % (24 * 3600 * 1000); // #950\r\n\t\r\n\t\t// iterate and add tick positions at appropriate values\r\n\t\twhile (time < max) {\r\n\t\t\ttickPositions.push(time);\r\n\t\r\n\t\t\t// if the interval is years, use Date.UTC to increase years\r\n\t\t\tif (interval === timeUnits[YEAR]) {\r\n\t\t\t\ttime = makeTime(minYear + i * count, 0);\r\n\t\r\n\t\t\t// if the interval is months, use Date.UTC to increase months\r\n\t\t\t} else if (interval === timeUnits[MONTH]) {\r\n\t\t\t\ttime = makeTime(minYear, minMonth + i * count);\r\n\t\r\n\t\t\t// if we're using global time, the interval is not fixed as it jumps\r\n\t\t\t// one hour at the DST crossover\r\n\t\t\t} else if (!useUTC && (interval === timeUnits[DAY] || interval === timeUnits[WEEK])) {\r\n\t\t\t\ttime = makeTime(minYear, minMonth, minDateDate +\r\n\t\t\t\t\ti * count * (interval === timeUnits[DAY] ? 1 : 7));\r\n\t\r\n\t\t\t// else, the interval is fixed and we use simple addition\r\n\t\t\t} else {\r\n\t\t\t\ttime += interval * count;\r\n\t\t\t}\r\n\t\r\n\t\t\ti++;\r\n\t\t}\r\n\t\r\n\t\t// push the last time\r\n\t\ttickPositions.push(time);\r\n\r\n\r\n\t\t// mark new days if the time is dividible by day (#1649, #1760)\r\n\t\teach(grep(tickPositions, function (time) {\r\n\t\t\treturn interval <= timeUnits[HOUR] && time % timeUnits[DAY] === timezoneOffset;\r\n\t\t}), function (time) {\r\n\t\t\thigherRanks[time] = DAY;\r\n\t\t});\r\n\t}\r\n\r\n\r\n\t// record information on the chosen unit - for dynamic label formatter\r\n\ttickPositions.info = extend(normalizedInterval, {\r\n\t\thigherRanks: higherRanks,\r\n\t\ttotalRange: interval * count\r\n\t});\r\n\r\n\treturn tickPositions;\r\n}\r\n\r\n/**\r\n * Helper class that contains variuos counters that are local to the chart.\r\n */\r\nfunction ChartCounters() {\r\n\tthis.color = 0;\r\n\tthis.symbol = 0;\r\n}\r\n\r\nChartCounters.prototype =  {\r\n\t/**\r\n\t * Wraps the color counter if it reaches the specified length.\r\n\t */\r\n\twrapColor: function (length) {\r\n\t\tif (this.color >= length) {\r\n\t\t\tthis.color = 0;\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Wraps the symbol counter if it reaches the specified length.\r\n\t */\r\n\twrapSymbol: function (length) {\r\n\t\tif (this.symbol >= length) {\r\n\t\t\tthis.symbol = 0;\r\n\t\t}\r\n\t}\r\n};\r\n\r\n\r\n/**\r\n * Utility method that sorts an object array and keeping the order of equal items.\r\n * ECMA script standard does not specify the behaviour when items are equal.\r\n */\r\nfunction stableSort(arr, sortFunction) {\r\n\tvar length = arr.length,\r\n\t\tsortValue,\r\n\t\ti;\r\n\r\n\t// Add index to each item\r\n\tfor (i = 0; i < length; i++) {\r\n\t\tarr[i].ss_i = i; // stable sort index\r\n\t}\r\n\r\n\tarr.sort(function (a, b) {\r\n\t\tsortValue = sortFunction(a, b);\r\n\t\treturn sortValue === 0 ? a.ss_i - b.ss_i : sortValue;\r\n\t});\r\n\r\n\t// Remove index from items\r\n\tfor (i = 0; i < length; i++) {\r\n\t\tdelete arr[i].ss_i; // stable sort index\r\n\t}\r\n}\r\n\r\n/**\r\n * Non-recursive method to find the lowest member of an array. Math.min raises a maximum\r\n * call stack size exceeded error in Chrome when trying to apply more than 150.000 points. This\r\n * method is slightly slower, but safe.\r\n */\r\nfunction arrayMin(data) {\r\n\tvar i = data.length,\r\n\t\tmin = data[0];\r\n\r\n\twhile (i--) {\r\n\t\tif (data[i] < min) {\r\n\t\t\tmin = data[i];\r\n\t\t}\r\n\t}\r\n\treturn min;\r\n}\r\n\r\n/**\r\n * Non-recursive method to find the lowest member of an array. Math.min raises a maximum\r\n * call stack size exceeded error in Chrome when trying to apply more than 150.000 points. This\r\n * method is slightly slower, but safe.\r\n */\r\nfunction arrayMax(data) {\r\n\tvar i = data.length,\r\n\t\tmax = data[0];\r\n\r\n\twhile (i--) {\r\n\t\tif (data[i] > max) {\r\n\t\t\tmax = data[i];\r\n\t\t}\r\n\t}\r\n\treturn max;\r\n}\r\n\r\n/**\r\n * Utility method that destroys any SVGElement or VMLElement that are properties on the given object.\r\n * It loops all properties and invokes destroy if there is a destroy method. The property is\r\n * then delete'ed.\r\n * @param {Object} The object to destroy properties on\r\n * @param {Object} Exception, do not destroy this property, only delete it.\r\n */\r\nfunction destroyObjectProperties(obj, except) {\r\n\tvar n;\r\n\tfor (n in obj) {\r\n\t\t// If the object is non-null and destroy is defined\r\n\t\tif (obj[n] && obj[n] !== except && obj[n].destroy) {\r\n\t\t\t// Invoke the destroy\r\n\t\t\tobj[n].destroy();\r\n\t\t}\r\n\r\n\t\t// Delete the property from the object.\r\n\t\tdelete obj[n];\r\n\t}\r\n}\r\n\r\n\r\n/**\r\n * Discard an element by moving it to the bin and delete\r\n * @param {Object} The HTML node to discard\r\n */\r\nfunction discardElement(element) {\r\n\t// create a garbage bin element, not part of the DOM\r\n\tif (!garbageBin) {\r\n\t\tgarbageBin = createElement(DIV);\r\n\t}\r\n\r\n\t// move the node and empty bin\r\n\tif (element) {\r\n\t\tgarbageBin.appendChild(element);\r\n\t}\r\n\tgarbageBin.innerHTML = '';\r\n}\r\n\r\n/**\r\n * Provide error messages for debugging, with links to online explanation \r\n */\r\nfunction error(code, stop) {\r\n\tvar msg = 'Highcharts error #' + code + ': www.highcharts.com/errors/' + code;\r\n\tif (stop) {\r\n\t\tthrow msg;\r\n\t} else if (win.console) {\r\n\t\tconsole.log(msg);\r\n\t}\r\n}\r\n\r\n/**\r\n * Fix JS round off float errors\r\n * @param {Number} num\r\n */\r\nfunction correctFloat(num) {\r\n\treturn parseFloat(\r\n\t\tnum.toPrecision(14)\r\n\t);\r\n}\r\n\r\n/**\r\n * Set the global animation to either a given value, or fall back to the\r\n * given chart's animation option\r\n * @param {Object} animation\r\n * @param {Object} chart\r\n */\r\nfunction setAnimation(animation, chart) {\r\n\tglobalAnimation = pick(animation, chart.animation);\r\n}\r\n\r\n/**\r\n * The time unit lookup\r\n */\r\n/*jslint white: true*/\r\ntimeUnits = hash(\r\n\tMILLISECOND, 1,\r\n\tSECOND, 1000,\r\n\tMINUTE, 60000,\r\n\tHOUR, 3600000,\r\n\tDAY, 24 * 3600000,\r\n\tWEEK, 7 * 24 * 3600000,\r\n\tMONTH, 31 * 24 * 3600000,\r\n\tYEAR, 31556952000\r\n);\r\n/*jslint white: false*/\r\n/**\r\n * Path interpolation algorithm used across adapters\r\n */\r\npathAnim = {\r\n\t/**\r\n\t * Prepare start and end values so that the path can be animated one to one\r\n\t */\r\n\tinit: function (elem, fromD, toD) {\r\n\t\tfromD = fromD || '';\r\n\t\tvar shift = elem.shift,\r\n\t\t\tbezier = fromD.indexOf('C') > -1,\r\n\t\t\tnumParams = bezier ? 7 : 3,\r\n\t\t\tendLength,\r\n\t\t\tslice,\r\n\t\t\ti,\r\n\t\t\tstart = fromD.split(' '),\r\n\t\t\tend = [].concat(toD), // copy\r\n\t\t\tstartBaseLine,\r\n\t\t\tendBaseLine,\r\n\t\t\tsixify = function (arr) { // in splines make move points have six parameters like bezier curves\r\n\t\t\t\ti = arr.length;\r\n\t\t\t\twhile (i--) {\r\n\t\t\t\t\tif (arr[i] === M) {\r\n\t\t\t\t\t\tarr.splice(i + 1, 0, arr[i + 1], arr[i + 2], arr[i + 1], arr[i + 2]);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t};\r\n\r\n\t\tif (bezier) {\r\n\t\t\tsixify(start);\r\n\t\t\tsixify(end);\r\n\t\t}\r\n\r\n\t\t// pull out the base lines before padding\r\n\t\tif (elem.isArea) {\r\n\t\t\tstartBaseLine = start.splice(start.length - 6, 6);\r\n\t\t\tendBaseLine = end.splice(end.length - 6, 6);\r\n\t\t}\r\n\r\n\t\t// if shifting points, prepend a dummy point to the end path\r\n\t\tif (shift <= end.length / numParams && start.length === end.length) {\r\n\t\t\twhile (shift--) {\r\n\t\t\t\tend = [].concat(end).splice(0, numParams).concat(end);\r\n\t\t\t}\r\n\t\t}\r\n\t\telem.shift = 0; // reset for following animations\r\n\r\n\t\t// copy and append last point until the length matches the end length\r\n\t\tif (start.length) {\r\n\t\t\tendLength = end.length;\r\n\t\t\twhile (start.length < endLength) {\r\n\r\n\t\t\t\t//bezier && sixify(start);\r\n\t\t\t\tslice = [].concat(start).splice(start.length - numParams, numParams);\r\n\t\t\t\tif (bezier) { // disable first control point\r\n\t\t\t\t\tslice[numParams - 6] = slice[numParams - 2];\r\n\t\t\t\t\tslice[numParams - 5] = slice[numParams - 1];\r\n\t\t\t\t}\r\n\t\t\t\tstart = start.concat(slice);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (startBaseLine) { // append the base lines for areas\r\n\t\t\tstart = start.concat(startBaseLine);\r\n\t\t\tend = end.concat(endBaseLine);\r\n\t\t}\r\n\t\treturn [start, end];\r\n\t},\r\n\r\n\t/**\r\n\t * Interpolate each value of the path and return the array\r\n\t */\r\n\tstep: function (start, end, pos, complete) {\r\n\t\tvar ret = [],\r\n\t\t\ti = start.length,\r\n\t\t\tstartVal;\r\n\r\n\t\tif (pos === 1) { // land on the final path without adjustment points appended in the ends\r\n\t\t\tret = complete;\r\n\r\n\t\t} else if (i === end.length && pos < 1) {\r\n\t\t\twhile (i--) {\r\n\t\t\t\tstartVal = parseFloat(start[i]);\r\n\t\t\t\tret[i] =\r\n\t\t\t\t\tisNaN(startVal) ? // a letter instruction like M or L\r\n\t\t\t\t\t\tstart[i] :\r\n\t\t\t\t\t\tpos * (parseFloat(end[i] - startVal)) + startVal;\r\n\r\n\t\t\t}\r\n\t\t} else { // if animation is finished or length not matching, land on right value\r\n\t\t\tret = end;\r\n\t\t}\r\n\t\treturn ret;\r\n\t}\r\n};\r\n\r\n(function ($) {\r\n\t/**\r\n\t * The default HighchartsAdapter for jQuery\r\n\t */\r\n\twin.HighchartsAdapter = win.HighchartsAdapter || ($ && {\r\n\t\t\r\n\t\t/**\r\n\t\t * Initialize the adapter by applying some extensions to jQuery\r\n\t\t */\r\n\t\tinit: function (pathAnim) {\r\n\t\t\t\r\n\t\t\t// extend the animate function to allow SVG animations\r\n\t\t\tvar Fx = $.fx,\r\n\t\t\t\tStep = Fx.step,\r\n\t\t\t\tdSetter,\r\n\t\t\t\tTween = $.Tween,\r\n\t\t\t\tpropHooks = Tween && Tween.propHooks,\r\n\t\t\t\topacityHook = $.cssHooks.opacity;\r\n\t\t\t\r\n\t\t\t/*jslint unparam: true*//* allow unused param x in this function */\r\n\t\t\t$.extend($.easing, {\r\n\t\t\t\teaseOutQuad: function (x, t, b, c, d) {\r\n\t\t\t\t\treturn -c * (t /= d) * (t - 2) + b;\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t\t/*jslint unparam: false*/\r\n\t\t\r\n\t\t\t// extend some methods to check for elem.attr, which means it is a Highcharts SVG object\r\n\t\t\t$.each(['cur', '_default', 'width', 'height', 'opacity'], function (i, fn) {\r\n\t\t\t\tvar obj = Step,\r\n\t\t\t\t\tbase,\r\n\t\t\t\t\telem;\r\n\t\t\t\t\t\r\n\t\t\t\t// Handle different parent objects\r\n\t\t\t\tif (fn === 'cur') {\r\n\t\t\t\t\tobj = Fx.prototype; // 'cur', the getter, relates to Fx.prototype\r\n\t\t\t\t\r\n\t\t\t\t} else if (fn === '_default' && Tween) { // jQuery 1.8 model\r\n\t\t\t\t\tobj = propHooks[fn];\r\n\t\t\t\t\tfn = 'set';\r\n\t\t\t\t}\r\n\t\t\r\n\t\t\t\t// Overwrite the method\r\n\t\t\t\tbase = obj[fn];\r\n\t\t\t\tif (base) { // step.width and step.height don't exist in jQuery < 1.7\r\n\t\t\r\n\t\t\t\t\t// create the extended function replacement\r\n\t\t\t\t\tobj[fn] = function (fx) {\r\n\t\t\r\n\t\t\t\t\t\t// Fx.prototype.cur does not use fx argument\r\n\t\t\t\t\t\tfx = i ? fx : this;\r\n\r\n\t\t\t\t\t\t// Don't run animations on textual properties like align (#1821)\r\n\t\t\t\t\t\tif (fx.prop === 'align') {\r\n\t\t\t\t\t\t\treturn;\r\n\t\t\t\t\t\t}\r\n\t\t\r\n\t\t\t\t\t\t// shortcut\r\n\t\t\t\t\t\telem = fx.elem;\r\n\t\t\r\n\t\t\t\t\t\t// Fx.prototype.cur returns the current value. The other ones are setters\r\n\t\t\t\t\t\t// and returning a value has no effect.\r\n\t\t\t\t\t\treturn elem.attr ? // is SVG element wrapper\r\n\t\t\t\t\t\t\telem.attr(fx.prop, fn === 'cur' ? UNDEFINED : fx.now) : // apply the SVG wrapper's method\r\n\t\t\t\t\t\t\tbase.apply(this, arguments); // use jQuery's built-in method\r\n\t\t\t\t\t};\r\n\t\t\t\t}\r\n\t\t\t});\r\n\r\n\t\t\t// Extend the opacity getter, needed for fading opacity with IE9 and jQuery 1.10+\r\n\t\t\twrap(opacityHook, 'get', function (proceed, elem, computed) {\r\n\t\t\t\treturn elem.attr ? (elem.opacity || 0) : proceed.call(this, elem, computed);\r\n\t\t\t});\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t// Define the setter function for d (path definitions)\r\n\t\t\tdSetter = function (fx) {\r\n\t\t\t\tvar elem = fx.elem,\r\n\t\t\t\t\tends;\r\n\t\t\r\n\t\t\t\t// Normally start and end should be set in state == 0, but sometimes,\r\n\t\t\t\t// for reasons unknown, this doesn't happen. Perhaps state == 0 is skipped\r\n\t\t\t\t// in these cases\r\n\t\t\t\tif (!fx.started) {\r\n\t\t\t\t\tends = pathAnim.init(elem, elem.d, elem.toD);\r\n\t\t\t\t\tfx.start = ends[0];\r\n\t\t\t\t\tfx.end = ends[1];\r\n\t\t\t\t\tfx.started = true;\r\n\t\t\t\t}\r\n\t\t\r\n\t\t\r\n\t\t\t\t// interpolate each value of the path\r\n\t\t\t\telem.attr('d', pathAnim.step(fx.start, fx.end, fx.pos, elem.toD));\r\n\t\t\t};\r\n\t\t\t\r\n\t\t\t// jQuery 1.8 style\r\n\t\t\tif (Tween) {\r\n\t\t\t\tpropHooks.d = {\r\n\t\t\t\t\tset: dSetter\r\n\t\t\t\t};\r\n\t\t\t// pre 1.8\r\n\t\t\t} else {\r\n\t\t\t\t// animate paths\r\n\t\t\t\tStep.d = dSetter;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t/**\r\n\t\t\t * Utility for iterating over an array. Parameters are reversed compared to jQuery.\r\n\t\t\t * @param {Array} arr\r\n\t\t\t * @param {Function} fn\r\n\t\t\t */\r\n\t\t\tthis.each = Array.prototype.forEach ?\r\n\t\t\t\tfunction (arr, fn) { // modern browsers\r\n\t\t\t\t\treturn Array.prototype.forEach.call(arr, fn);\r\n\t\t\t\t\t\r\n\t\t\t\t} : \r\n\t\t\t\tfunction (arr, fn) { // legacy\r\n\t\t\t\t\tvar i = 0, \r\n\t\t\t\t\t\tlen = arr.length;\r\n\t\t\t\t\tfor (; i < len; i++) {\r\n\t\t\t\t\t\tif (fn.call(arr[i], arr[i], i, arr) === false) {\r\n\t\t\t\t\t\t\treturn i;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t};\r\n\t\t\t\r\n\t\t\t/**\r\n\t\t\t * Register Highcharts as a plugin in the respective framework\r\n\t\t\t */\r\n\t\t\t$.fn.highcharts = function () {\r\n\t\t\t\tvar constr = 'Chart', // default constructor\r\n\t\t\t\t\targs = arguments,\r\n\t\t\t\t\toptions,\r\n\t\t\t\t\tret,\r\n\t\t\t\t\tchart;\r\n\r\n\t\t\t\tif (isString(args[0])) {\r\n\t\t\t\t\tconstr = args[0];\r\n\t\t\t\t\targs = Array.prototype.slice.call(args, 1); \r\n\t\t\t\t}\r\n\t\t\t\toptions = args[0];\r\n\r\n\t\t\t\t// Create the chart\r\n\t\t\t\tif (options !== UNDEFINED) {\r\n\t\t\t\t\t/*jslint unused:false*/\r\n\t\t\t\t\toptions.chart = options.chart || {};\r\n\t\t\t\t\toptions.chart.renderTo = this[0];\r\n\t\t\t\t\tchart = new Highcharts[constr](options, args[1]);\r\n\t\t\t\t\tret = this;\r\n\t\t\t\t\t/*jslint unused:true*/\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// When called without parameters or with the return argument, get a predefined chart\r\n\t\t\t\tif (options === UNDEFINED) {\r\n\t\t\t\t\tret = charts[attr(this[0], 'data-highcharts-chart')];\r\n\t\t\t\t}\t\r\n\r\n\t\t\t\treturn ret;\r\n\t\t\t};\r\n\r\n\t\t},\r\n\r\n\t\t\r\n\t\t/**\r\n\t\t * Downloads a script and executes a callback when done.\r\n\t\t * @param {String} scriptLocation\r\n\t\t * @param {Function} callback\r\n\t\t */\r\n\t\tgetScript: $.getScript,\r\n\t\t\r\n\t\t/**\r\n\t\t * Return the index of an item in an array, or -1 if not found\r\n\t\t */\r\n\t\tinArray: $.inArray,\r\n\t\t\r\n\t\t/**\r\n\t\t * A direct link to jQuery methods. MooTools and Prototype adapters must be implemented for each case of method.\r\n\t\t * @param {Object} elem The HTML element\r\n\t\t * @param {String} method Which method to run on the wrapped element\r\n\t\t */\r\n\t\tadapterRun: function (elem, method) {\r\n\t\t\treturn $(elem)[method]();\r\n\t\t},\r\n\t\r\n\t\t/**\r\n\t\t * Filter an array\r\n\t\t */\r\n\t\tgrep: $.grep,\r\n\t\r\n\t\t/**\r\n\t\t * Map an array\r\n\t\t * @param {Array} arr\r\n\t\t * @param {Function} fn\r\n\t\t */\r\n\t\tmap: function (arr, fn) {\r\n\t\t\t//return jQuery.map(arr, fn);\r\n\t\t\tvar results = [],\r\n\t\t\t\ti = 0,\r\n\t\t\t\tlen = arr.length;\r\n\t\t\tfor (; i < len; i++) {\r\n\t\t\t\tresults[i] = fn.call(arr[i], arr[i], i, arr);\r\n\t\t\t}\r\n\t\t\treturn results;\r\n\t\r\n\t\t},\r\n\t\r\n\t\t/**\r\n\t\t * Get the position of an element relative to the top left of the page\r\n\t\t */\r\n\t\toffset: function (el) {\r\n\t\t\treturn $(el).offset();\r\n\t\t},\r\n\t\r\n\t\t/**\r\n\t\t * Add an event listener\r\n\t\t * @param {Object} el A HTML element or custom object\r\n\t\t * @param {String} event The event type\r\n\t\t * @param {Function} fn The event handler\r\n\t\t */\r\n\t\taddEvent: function (el, event, fn) {\r\n\t\t\t$(el).bind(event, fn);\r\n\t\t},\r\n\t\r\n\t\t/**\r\n\t\t * Remove event added with addEvent\r\n\t\t * @param {Object} el The object\r\n\t\t * @param {String} eventType The event type. Leave blank to remove all events.\r\n\t\t * @param {Function} handler The function to remove\r\n\t\t */\r\n\t\tremoveEvent: function (el, eventType, handler) {\r\n\t\t\t// workaround for jQuery issue with unbinding custom events:\r\n\t\t\t// http://forum.jQuery.com/topic/javascript-error-when-unbinding-a-custom-event-using-jQuery-1-4-2\r\n\t\t\tvar func = doc.removeEventListener ? 'removeEventListener' : 'detachEvent';\r\n\t\t\tif (doc[func] && el && !el[func]) {\r\n\t\t\t\tel[func] = function () {};\r\n\t\t\t}\r\n\t\r\n\t\t\t$(el).unbind(eventType, handler);\r\n\t\t},\r\n\t\r\n\t\t/**\r\n\t\t * Fire an event on a custom object\r\n\t\t * @param {Object} el\r\n\t\t * @param {String} type\r\n\t\t * @param {Object} eventArguments\r\n\t\t * @param {Function} defaultFunction\r\n\t\t */\r\n\t\tfireEvent: function (el, type, eventArguments, defaultFunction) {\r\n\t\t\tvar event = $.Event(type),\r\n\t\t\t\tdetachedType = 'detached' + type,\r\n\t\t\t\tdefaultPrevented;\r\n\t\r\n\t\t\t// Remove warnings in Chrome when accessing layerX and layerY. Although Highcharts\r\n\t\t\t// never uses these properties, Chrome includes them in the default click event and\r\n\t\t\t// raises the warning when they are copied over in the extend statement below.\r\n\t\t\t//\r\n\t\t\t// To avoid problems in IE (see #1010) where we cannot delete the properties and avoid\r\n\t\t\t// testing if they are there (warning in chrome) the only option is to test if running IE.\r\n\t\t\tif (!isIE && eventArguments) {\r\n\t\t\t\tdelete eventArguments.layerX;\r\n\t\t\t\tdelete eventArguments.layerY;\r\n\t\t\t}\r\n\t\r\n\t\t\textend(event, eventArguments);\r\n\t\r\n\t\t\t// Prevent jQuery from triggering the object method that is named the\r\n\t\t\t// same as the event. For example, if the event is 'select', jQuery\r\n\t\t\t// attempts calling el.select and it goes into a loop.\r\n\t\t\tif (el[type]) {\r\n\t\t\t\tel[detachedType] = el[type];\r\n\t\t\t\tel[type] = null;\r\n\t\t\t}\r\n\t\r\n\t\t\t// Wrap preventDefault and stopPropagation in try/catch blocks in\r\n\t\t\t// order to prevent JS errors when cancelling events on non-DOM\r\n\t\t\t// objects. #615.\r\n\t\t\t/*jslint unparam: true*/\r\n\t\t\t$.each(['preventDefault', 'stopPropagation'], function (i, fn) {\r\n\t\t\t\tvar base = event[fn];\r\n\t\t\t\tevent[fn] = function () {\r\n\t\t\t\t\ttry {\r\n\t\t\t\t\t\tbase.call(event);\r\n\t\t\t\t\t} catch (e) {\r\n\t\t\t\t\t\tif (fn === 'preventDefault') {\r\n\t\t\t\t\t\t\tdefaultPrevented = true;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t};\r\n\t\t\t});\r\n\t\t\t/*jslint unparam: false*/\r\n\t\r\n\t\t\t// trigger it\r\n\t\t\t$(el).trigger(event);\r\n\t\r\n\t\t\t// attach the method\r\n\t\t\tif (el[detachedType]) {\r\n\t\t\t\tel[type] = el[detachedType];\r\n\t\t\t\tel[detachedType] = null;\r\n\t\t\t}\r\n\t\r\n\t\t\tif (defaultFunction && !event.isDefaultPrevented() && !defaultPrevented) {\r\n\t\t\t\tdefaultFunction(event);\r\n\t\t\t}\r\n\t\t},\r\n\t\t\r\n\t\t/**\r\n\t\t * Extension method needed for MooTools\r\n\t\t */\r\n\t\twashMouseEvent: function (e) {\r\n\t\t\tvar ret = e.originalEvent || e;\r\n\t\t\t\r\n\t\t\t// computed by jQuery, needed by IE8\r\n\t\t\tif (ret.pageX === UNDEFINED) { // #1236\r\n\t\t\t\tret.pageX = e.pageX;\r\n\t\t\t\tret.pageY = e.pageY;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\treturn ret;\r\n\t\t},\r\n\t\r\n\t\t/**\r\n\t\t * Animate a HTML element or SVG element wrapper\r\n\t\t * @param {Object} el\r\n\t\t * @param {Object} params\r\n\t\t * @param {Object} options jQuery-like animation options: duration, easing, callback\r\n\t\t */\r\n\t\tanimate: function (el, params, options) {\r\n\t\t\tvar $el = $(el);\r\n\t\t\tif (!el.style) {\r\n\t\t\t\tel.style = {}; // #1881\r\n\t\t\t}\r\n\t\t\tif (params.d) {\r\n\t\t\t\tel.toD = params.d; // keep the array form for paths, used in $.fx.step.d\r\n\t\t\t\tparams.d = 1; // because in jQuery, animating to an array has a different meaning\r\n\t\t\t}\r\n\t\r\n\t\t\t$el.stop();\r\n\t\t\tif (params.opacity !== UNDEFINED && el.attr) {\r\n\t\t\t\tparams.opacity += 'px'; // force jQuery to use same logic as width and height (#2161)\r\n\t\t\t}\r\n\t\t\t$el.animate(params, options);\r\n\t\r\n\t\t},\r\n\t\t/**\r\n\t\t * Stop running animation\r\n\t\t */\r\n\t\tstop: function (el) {\r\n\t\t\t$(el).stop();\r\n\t\t}\r\n\t});\r\n}(win.jQuery));\r\n\r\n\r\n// check for a custom HighchartsAdapter defined prior to this file\r\nvar globalAdapter = win.HighchartsAdapter,\r\n\tadapter = globalAdapter || {};\r\n\t\r\n// Initialize the adapter\r\nif (globalAdapter) {\r\n\tglobalAdapter.init.call(globalAdapter, pathAnim);\r\n}\r\n\r\n\r\n// Utility functions. If the HighchartsAdapter is not defined, adapter is an empty object\r\n// and all the utility functions will be null. In that case they are populated by the\r\n// default adapters below.\r\nvar adapterRun = adapter.adapterRun,\r\n\tgetScript = adapter.getScript,\r\n\tinArray = adapter.inArray,\r\n\teach = adapter.each,\r\n\tgrep = adapter.grep,\r\n\toffset = adapter.offset,\r\n\tmap = adapter.map,\r\n\taddEvent = adapter.addEvent,\r\n\tremoveEvent = adapter.removeEvent,\r\n\tfireEvent = adapter.fireEvent,\r\n\twashMouseEvent = adapter.washMouseEvent,\r\n\tanimate = adapter.animate,\r\n\tstop = adapter.stop;\r\n\r\n\r\n\r\n/* ****************************************************************************\r\n * Handle the options                                                         *\r\n *****************************************************************************/\r\nvar\r\n\r\ndefaultLabelOptions = {\r\n\tenabled: true,\r\n\t// rotation: 0,\r\n\t// align: 'center',\r\n\tx: 0,\r\n\ty: 15,\r\n\t/*formatter: function () {\r\n\t\treturn this.value;\r\n\t},*/\r\n\tstyle: {\r\n\t\tcolor: '#666',\r\n\t\tcursor: 'default',\r\n\t\tfontSize: '11px',\r\n\t\tlineHeight: '14px'\r\n\t}\r\n};\r\n\r\ndefaultOptions = {\r\n\tcolors: ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970',\r\n\t\t'#f28f43', '#77a1e5', '#c42525', '#a6c96a'],\r\n\tsymbols: ['circle', 'diamond', 'square', 'triangle', 'triangle-down'],\r\n\tlang: {\r\n\t\tloading: 'Loading...',\r\n\t\tmonths: ['January', 'February', 'March', 'April', 'May', 'June', 'July',\r\n\t\t\t\t'August', 'September', 'October', 'November', 'December'],\r\n\t\tshortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],\r\n\t\tweekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],\r\n\t\tdecimalPoint: '.',\r\n\t\tnumericSymbols: ['k', 'M', 'G', 'T', 'P', 'E'], // SI prefixes used in axis labels\r\n\t\tresetZoom: 'Reset zoom',\r\n\t\tresetZoomTitle: 'Reset zoom level 1:1',\r\n\t\tthousandsSep: ','\r\n\t},\r\n\tglobal: {\r\n\t\tuseUTC: true,\r\n\t\tcanvasToolsURL: 'http://code.highcharts.com/3.0.6/modules/canvas-tools.js',\r\n\t\tVMLRadialGradientURL: 'http://code.highcharts.com/3.0.6/gfx/vml-radial-gradient.png'\r\n\t},\r\n\tchart: {\r\n\t\t//animation: true,\r\n\t\t//alignTicks: false,\r\n\t\t//reflow: true,\r\n\t\t//className: null,\r\n\t\t//events: { load, selection },\r\n\t\t//margin: [null],\r\n\t\t//marginTop: null,\r\n\t\t//marginRight: null,\r\n\t\t//marginBottom: null,\r\n\t\t//marginLeft: null,\r\n\t\tborderColor: '#4572A7',\r\n\t\t//borderWidth: 0,\r\n\t\tborderRadius: 5,\r\n\t\tdefaultSeriesType: 'line',\r\n\t\tignoreHiddenSeries: true,\r\n\t\t//inverted: false,\r\n\t\t//shadow: false,\r\n\t\tspacing: [10, 10, 15, 10],\r\n\t\t//spacingTop: 10,\r\n\t\t//spacingRight: 10,\r\n\t\t//spacingBottom: 15,\r\n\t\t//spacingLeft: 10,\r\n\t\tstyle: {\r\n\t\t\tfontFamily: '\"Lucida Grande\", \"Lucida Sans Unicode\", Verdana, Arial, Helvetica, sans-serif', // default font\r\n\t\t\tfontSize: '12px'\r\n\t\t},\r\n\t\tbackgroundColor: '#FFFFFF',\r\n\t\t//plotBackgroundColor: null,\r\n\t\tplotBorderColor: '#C0C0C0',\r\n\t\t//plotBorderWidth: 0,\r\n\t\t//plotShadow: false,\r\n\t\t//zoomType: ''\r\n\t\tresetZoomButton: {\r\n\t\t\ttheme: {\r\n\t\t\t\tzIndex: 20\r\n\t\t\t},\r\n\t\t\tposition: {\r\n\t\t\t\talign: 'right',\r\n\t\t\t\tx: -10,\r\n\t\t\t\t//verticalAlign: 'top',\r\n\t\t\t\ty: 10\r\n\t\t\t}\r\n\t\t\t// relativeTo: 'plot'\r\n\t\t}\r\n\t},\r\n\ttitle: {\r\n\t\ttext: 'Chart title',\r\n\t\talign: 'center',\r\n\t\t// floating: false,\r\n\t\tmargin: 15,\r\n\t\t// x: 0,\r\n\t\t// verticalAlign: 'top',\r\n\t\t// y: null,\r\n\t\tstyle: {\r\n\t\t\tcolor: '#274b6d',//#3E576F',\r\n\t\t\tfontSize: '16px'\r\n\t\t}\r\n\r\n\t},\r\n\tsubtitle: {\r\n\t\ttext: '',\r\n\t\talign: 'center',\r\n\t\t// floating: false\r\n\t\t// x: 0,\r\n\t\t// verticalAlign: 'top',\r\n\t\t// y: null,\r\n\t\tstyle: {\r\n\t\t\tcolor: '#4d759e'\r\n\t\t}\r\n\t},\r\n\r\n\tplotOptions: {\r\n\t\tline: { // base series options\r\n\t\t\tallowPointSelect: false,\r\n\t\t\tshowCheckbox: false,\r\n\t\t\tanimation: {\r\n\t\t\t\tduration: 1000\r\n\t\t\t},\r\n\t\t\t//connectNulls: false,\r\n\t\t\t//cursor: 'default',\r\n\t\t\t//clip: true,\r\n\t\t\t//dashStyle: null,\r\n\t\t\t//enableMouseTracking: true,\r\n\t\t\tevents: {},\r\n\t\t\t//legendIndex: 0,\r\n\t\t\tlineWidth: 2,\r\n\t\t\t//shadow: false,\r\n\t\t\t// stacking: null,\r\n\t\t\tmarker: {\r\n\t\t\t\tenabled: true,\r\n\t\t\t\t//symbol: null,\r\n\t\t\t\tlineWidth: 0,\r\n\t\t\t\tradius: 4,\r\n\t\t\t\tlineColor: '#FFFFFF',\r\n\t\t\t\t//fillColor: null,\r\n\t\t\t\tstates: { // states for a single point\r\n\t\t\t\t\thover: {\r\n\t\t\t\t\t\tenabled: true\r\n\t\t\t\t\t\t//radius: base + 2\r\n\t\t\t\t\t},\r\n\t\t\t\t\tselect: {\r\n\t\t\t\t\t\tfillColor: '#FFFFFF',\r\n\t\t\t\t\t\tlineColor: '#000000',\r\n\t\t\t\t\t\tlineWidth: 2\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tpoint: {\r\n\t\t\t\tevents: {}\r\n\t\t\t},\r\n\t\t\tdataLabels: merge(defaultLabelOptions, {\r\n\t\t\t\talign: 'center',\r\n\t\t\t\tenabled: false,\r\n\t\t\t\tformatter: function () {\r\n\t\t\t\t\treturn this.y === null ? '' : numberFormat(this.y, -1);\r\n\t\t\t\t},\r\n\t\t\t\tverticalAlign: 'bottom', // above singular point\r\n\t\t\t\ty: 0\r\n\t\t\t\t// backgroundColor: undefined,\r\n\t\t\t\t// borderColor: undefined,\r\n\t\t\t\t// borderRadius: undefined,\r\n\t\t\t\t// borderWidth: undefined,\r\n\t\t\t\t// padding: 3,\r\n\t\t\t\t// shadow: false\r\n\t\t\t}),\r\n\t\t\tcropThreshold: 300, // draw points outside the plot area when the number of points is less than this\r\n\t\t\tpointRange: 0,\r\n\t\t\t//pointStart: 0,\r\n\t\t\t//pointInterval: 1,\r\n\t\t\tshowInLegend: true,\r\n\t\t\tstates: { // states for the entire series\r\n\t\t\t\thover: {\r\n\t\t\t\t\t//enabled: false,\r\n\t\t\t\t\t//lineWidth: base + 1,\r\n\t\t\t\t\tmarker: {\r\n\t\t\t\t\t\t// lineWidth: base + 1,\r\n\t\t\t\t\t\t// radius: base + 1\r\n\t\t\t\t\t}\r\n\t\t\t\t},\r\n\t\t\t\tselect: {\r\n\t\t\t\t\tmarker: {}\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tstickyTracking: true\r\n\t\t\t//tooltip: {\r\n\t\t\t\t//pointFormat: '<span style=\"color:{series.color}\">{series.name}</span>: <b>{point.y}</b>'\r\n\t\t\t\t//valueDecimals: null,\r\n\t\t\t\t//xDateFormat: '%A, %b %e, %Y',\r\n\t\t\t\t//valuePrefix: '',\r\n\t\t\t\t//ySuffix: ''\t\t\t\t\r\n\t\t\t//}\r\n\t\t\t// turboThreshold: 1000\r\n\t\t\t// zIndex: null\r\n\t\t}\r\n\t},\r\n\tlabels: {\r\n\t\t//items: [],\r\n\t\tstyle: {\r\n\t\t\t//font: defaultFont,\r\n\t\t\tposition: ABSOLUTE,\r\n\t\t\tcolor: '#3E576F'\r\n\t\t}\r\n\t},\r\n\tlegend: {\r\n\t\tenabled: true,\r\n\t\talign: 'center',\r\n\t\t//floating: false,\r\n\t\tlayout: 'horizontal',\r\n\t\tlabelFormatter: function () {\r\n\t\t\treturn this.name;\r\n\t\t},\r\n\t\tborderWidth: 1,\r\n\t\tborderColor: '#909090',\r\n\t\tborderRadius: 5,\r\n\t\tnavigation: {\r\n\t\t\t// animation: true,\r\n\t\t\tactiveColor: '#274b6d',\r\n\t\t\t// arrowSize: 12\r\n\t\t\tinactiveColor: '#CCC'\r\n\t\t\t// style: {} // text styles\r\n\t\t},\r\n\t\t// margin: 10,\r\n\t\t// reversed: false,\r\n\t\tshadow: false,\r\n\t\t// backgroundColor: null,\r\n\t\t/*style: {\r\n\t\t\tpadding: '5px'\r\n\t\t},*/\r\n\t\titemStyle: {\r\n\t\t\tcursor: 'pointer',\r\n\t\t\tcolor: '#274b6d',\r\n\t\t\tfontSize: '12px'\r\n\t\t},\r\n\t\titemHoverStyle: {\r\n\t\t\t//cursor: 'pointer', removed as of #601\r\n\t\t\tcolor: '#000'\r\n\t\t},\r\n\t\titemHiddenStyle: {\r\n\t\t\tcolor: '#CCC'\r\n\t\t},\r\n\t\titemCheckboxStyle: {\r\n\t\t\tposition: ABSOLUTE,\r\n\t\t\twidth: '13px', // for IE precision\r\n\t\t\theight: '13px'\r\n\t\t},\r\n\t\t// itemWidth: undefined,\r\n\t\tsymbolWidth: 16,\r\n\t\tsymbolPadding: 5,\r\n\t\tverticalAlign: 'bottom',\r\n\t\t// width: undefined,\r\n\t\tx: 0,\r\n\t\ty: 0,\r\n\t\ttitle: {\r\n\t\t\t//text: null,\r\n\t\t\tstyle: {\r\n\t\t\t\tfontWeight: 'bold'\r\n\t\t\t}\r\n\t\t}\t\t\t\r\n\t},\r\n\r\n\tloading: {\r\n\t\t// hideDuration: 100,\r\n\t\tlabelStyle: {\r\n\t\t\tfontWeight: 'bold',\r\n\t\t\tposition: RELATIVE,\r\n\t\t\ttop: '1em'\r\n\t\t},\r\n\t\t// showDuration: 0,\r\n\t\tstyle: {\r\n\t\t\tposition: ABSOLUTE,\r\n\t\t\tbackgroundColor: 'white',\r\n\t\t\topacity: 0.5,\r\n\t\t\ttextAlign: 'center'\r\n\t\t}\r\n\t},\r\n\r\n\ttooltip: {\r\n\t\tenabled: true,\r\n\t\tanimation: hasSVG,\r\n\t\t//crosshairs: null,\r\n\t\tbackgroundColor: 'rgba(255, 255, 255, .85)',\r\n\t\tborderWidth: 1,\r\n\t\tborderRadius: 3,\r\n\t\tdateTimeLabelFormats: { \r\n\t\t\tmillisecond: '%A, %b %e, %H:%M:%S.%L',\r\n\t\t\tsecond: '%A, %b %e, %H:%M:%S',\r\n\t\t\tminute: '%A, %b %e, %H:%M',\r\n\t\t\thour: '%A, %b %e, %H:%M',\r\n\t\t\tday: '%A, %b %e, %Y',\r\n\t\t\tweek: 'Week from %A, %b %e, %Y',\r\n\t\t\tmonth: '%B %Y',\r\n\t\t\tyear: '%Y'\r\n\t\t},\r\n\t\t//formatter: defaultFormatter,\r\n\t\theaderFormat: '<span style=\"font-size: 10px\">{point.key}</span><br/>',\r\n\t\tpointFormat: '<span style=\"color:{series.color}\">{series.name}</span>: <b>{point.y}</b><br/>',\r\n\t\tshadow: true,\r\n\t\t//shared: false,\r\n\t\tsnap: isTouchDevice ? 25 : 10,\r\n\t\tstyle: {\r\n\t\t\tcolor: '#333333',\r\n\t\t\tcursor: 'default',\r\n\t\t\tfontSize: '12px',\r\n\t\t\tpadding: '8px',\r\n\t\t\twhiteSpace: 'nowrap'\r\n\t\t}\r\n\t\t//xDateFormat: '%A, %b %e, %Y',\r\n\t\t//valueDecimals: null,\r\n\t\t//valuePrefix: '',\r\n\t\t//valueSuffix: ''\r\n\t},\r\n\r\n\tcredits: {\r\n\t\tenabled: true,\r\n\t\ttext: 'Highcharts.com',\r\n\t\thref: 'http://www.highcharts.com',\r\n\t\tposition: {\r\n\t\t\talign: 'right',\r\n\t\t\tx: -10,\r\n\t\t\tverticalAlign: 'bottom',\r\n\t\t\ty: -5\r\n\t\t},\r\n\t\tstyle: {\r\n\t\t\tcursor: 'pointer',\r\n\t\t\tcolor: '#909090',\r\n\t\t\tfontSize: '9px'\r\n\t\t}\r\n\t}\r\n};\r\n\r\n\r\n\r\n\r\n// Series defaults\r\nvar defaultPlotOptions = defaultOptions.plotOptions,\r\n\tdefaultSeriesOptions = defaultPlotOptions.line;\r\n\r\n// set the default time methods\r\nsetTimeMethods();\r\n\r\n\r\n\r\n/**\r\n * Set the time methods globally based on the useUTC option. Time method can be either\r\n * local time or UTC (default).\r\n */\r\nfunction setTimeMethods() {\r\n\tvar useUTC = defaultOptions.global.useUTC,\r\n\t\tGET = useUTC ? 'getUTC' : 'get',\r\n\t\tSET = useUTC ? 'setUTC' : 'set';\r\n\r\n\tmakeTime = useUTC ? Date.UTC : function (year, month, date, hours, minutes, seconds) {\r\n\t\treturn new Date(\r\n\t\t\tyear,\r\n\t\t\tmonth,\r\n\t\t\tpick(date, 1),\r\n\t\t\tpick(hours, 0),\r\n\t\t\tpick(minutes, 0),\r\n\t\t\tpick(seconds, 0)\r\n\t\t).getTime();\r\n\t};\r\n\tgetMinutes =  GET + 'Minutes';\r\n\tgetHours =    GET + 'Hours';\r\n\tgetDay =      GET + 'Day';\r\n\tgetDate =     GET + 'Date';\r\n\tgetMonth =    GET + 'Month';\r\n\tgetFullYear = GET + 'FullYear';\r\n\tsetMinutes =  SET + 'Minutes';\r\n\tsetHours =    SET + 'Hours';\r\n\tsetDate =     SET + 'Date';\r\n\tsetMonth =    SET + 'Month';\r\n\tsetFullYear = SET + 'FullYear';\r\n\r\n}\r\n\r\n/**\r\n * Merge the default options with custom options and return the new options structure\r\n * @param {Object} options The new custom options\r\n */\r\nfunction setOptions(options) {\r\n\t\r\n\t// Pull out axis options and apply them to the respective default axis options \r\n\t/*defaultXAxisOptions = merge(defaultXAxisOptions, options.xAxis);\r\n\tdefaultYAxisOptions = merge(defaultYAxisOptions, options.yAxis);\r\n\toptions.xAxis = options.yAxis = UNDEFINED;*/\r\n\t\r\n\t// Merge in the default options\r\n\tdefaultOptions = merge(defaultOptions, options);\r\n\t\r\n\t// Apply UTC\r\n\tsetTimeMethods();\r\n\r\n\treturn defaultOptions;\r\n}\r\n\r\n/**\r\n * Get the updated default options. Merely exposing defaultOptions for outside modules\r\n * isn't enough because the setOptions method creates a new object.\r\n */\r\nfunction getOptions() {\r\n\treturn defaultOptions;\r\n}\r\n\r\n\r\n/**\r\n * Handle color operations. The object methods are chainable.\r\n * @param {String} input The input color in either rbga or hex format\r\n */\r\nvar Color = function (input) {\r\n\t// declare variables\r\n\tvar rgba = [], result, stops;\r\n\r\n\t/**\r\n\t * Parse the input color to rgba array\r\n\t * @param {String} input\r\n\t */\r\n\tfunction init(input) {\r\n\r\n\t\t// Gradients\r\n\t\tif (input && input.stops) {\r\n\t\t\tstops = map(input.stops, function (stop) {\r\n\t\t\t\treturn Color(stop[1]);\r\n\t\t\t});\r\n\r\n\t\t// Solid colors\r\n\t\t} else {\r\n\t\t\t// rgba\r\n\t\t\tresult = /rgba\\(\\s*([0-9]{1,3})\\s*,\\s*([0-9]{1,3})\\s*,\\s*([0-9]{1,3})\\s*,\\s*([0-9]?(?:\\.[0-9]+)?)\\s*\\)/.exec(input);\r\n\t\t\tif (result) {\r\n\t\t\t\trgba = [pInt(result[1]), pInt(result[2]), pInt(result[3]), parseFloat(result[4], 10)];\r\n\t\t\t} else { \r\n\t\t\t\t// hex\r\n\t\t\t\tresult = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(input);\r\n\t\t\t\tif (result) {\r\n\t\t\t\t\trgba = [pInt(result[1], 16), pInt(result[2], 16), pInt(result[3], 16), 1];\r\n\t\t\t\t} else {\r\n\t\t\t\t\t// rgb\r\n\t\t\t\t\tresult = /rgb\\(\\s*([0-9]{1,3})\\s*,\\s*([0-9]{1,3})\\s*,\\s*([0-9]{1,3})\\s*\\)/.exec(input);\r\n\t\t\t\t\tif (result) {\r\n\t\t\t\t\t\trgba = [pInt(result[1]), pInt(result[2]), pInt(result[3]), 1];\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\t\t\r\n\r\n\t}\r\n\t/**\r\n\t * Return the color a specified format\r\n\t * @param {String} format\r\n\t */\r\n\tfunction get(format) {\r\n\t\tvar ret;\r\n\r\n\t\tif (stops) {\r\n\t\t\tret = merge(input);\r\n\t\t\tret.stops = [].concat(ret.stops);\r\n\t\t\teach(stops, function (stop, i) {\r\n\t\t\t\tret.stops[i] = [ret.stops[i][0], stop.get(format)];\r\n\t\t\t});\r\n\r\n\t\t// it's NaN if gradient colors on a column chart\r\n\t\t} else if (rgba && !isNaN(rgba[0])) {\r\n\t\t\tif (format === 'rgb') {\r\n\t\t\t\tret = 'rgb(' + rgba[0] + ',' + rgba[1] + ',' + rgba[2] + ')';\r\n\t\t\t} else if (format === 'a') {\r\n\t\t\t\tret = rgba[3];\r\n\t\t\t} else {\r\n\t\t\t\tret = 'rgba(' + rgba.join(',') + ')';\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tret = input;\r\n\t\t}\r\n\t\treturn ret;\r\n\t}\r\n\r\n\t/**\r\n\t * Brighten the color\r\n\t * @param {Number} alpha\r\n\t */\r\n\tfunction brighten(alpha) {\r\n\t\tif (stops) {\r\n\t\t\teach(stops, function (stop) {\r\n\t\t\t\tstop.brighten(alpha);\r\n\t\t\t});\r\n\t\t\r\n\t\t} else if (isNumber(alpha) && alpha !== 0) {\r\n\t\t\tvar i;\r\n\t\t\tfor (i = 0; i < 3; i++) {\r\n\t\t\t\trgba[i] += pInt(alpha * 255);\r\n\r\n\t\t\t\tif (rgba[i] < 0) {\r\n\t\t\t\t\trgba[i] = 0;\r\n\t\t\t\t}\r\n\t\t\t\tif (rgba[i] > 255) {\r\n\t\t\t\t\trgba[i] = 255;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn this;\r\n\t}\r\n\t/**\r\n\t * Set the color's opacity to a given alpha value\r\n\t * @param {Number} alpha\r\n\t */\r\n\tfunction setOpacity(alpha) {\r\n\t\trgba[3] = alpha;\r\n\t\treturn this;\r\n\t}\r\n\r\n\t// initialize: parse the input\r\n\tinit(input);\r\n\r\n\t// public methods\r\n\treturn {\r\n\t\tget: get,\r\n\t\tbrighten: brighten,\r\n\t\trgba: rgba,\r\n\t\tsetOpacity: setOpacity\r\n\t};\r\n};\r\n\r\n\r\n/**\r\n * A wrapper object for SVG elements\r\n */\r\nfunction SVGElement() {}\r\n\r\nSVGElement.prototype = {\r\n\t/**\r\n\t * Initialize the SVG renderer\r\n\t * @param {Object} renderer\r\n\t * @param {String} nodeName\r\n\t */\r\n\tinit: function (renderer, nodeName) {\r\n\t\tvar wrapper = this;\r\n\t\twrapper.element = nodeName === 'span' ?\r\n\t\t\tcreateElement(nodeName) :\r\n\t\t\tdoc.createElementNS(SVG_NS, nodeName);\r\n\t\twrapper.renderer = renderer;\r\n\t\t/**\r\n\t\t * A collection of attribute setters. These methods, if defined, are called right before a certain\r\n\t\t * attribute is set on an element wrapper. Returning false prevents the default attribute\r\n\t\t * setter to run. Returning a value causes the default setter to set that value. Used in\r\n\t\t * Renderer.label.\r\n\t\t */\r\n\t\twrapper.attrSetters = {};\r\n\t},\r\n\t/**\r\n\t * Default base for animation\r\n\t */\r\n\topacity: 1,\r\n\t/**\r\n\t * Animate a given attribute\r\n\t * @param {Object} params\r\n\t * @param {Number} options The same options as in jQuery animation\r\n\t * @param {Function} complete Function to perform at the end of animation\r\n\t */\r\n\tanimate: function (params, options, complete) {\r\n\t\tvar animOptions = pick(options, globalAnimation, true);\r\n\t\tstop(this); // stop regardless of animation actually running, or reverting to .attr (#607)\r\n\t\tif (animOptions) {\r\n\t\t\tanimOptions = merge(animOptions);\r\n\t\t\tif (complete) { // allows using a callback with the global animation without overwriting it\r\n\t\t\t\tanimOptions.complete = complete;\r\n\t\t\t}\r\n\t\t\tanimate(this, params, animOptions);\r\n\t\t} else {\r\n\t\t\tthis.attr(params);\r\n\t\t\tif (complete) {\r\n\t\t\t\tcomplete();\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\t/**\r\n\t * Set or get a given attribute\r\n\t * @param {Object|String} hash\r\n\t * @param {Mixed|Undefined} val\r\n\t */\r\n\tattr: function (hash, val) {\r\n\t\tvar wrapper = this,\r\n\t\t\tkey,\r\n\t\t\tvalue,\r\n\t\t\tresult,\r\n\t\t\ti,\r\n\t\t\tchild,\r\n\t\t\telement = wrapper.element,\r\n\t\t\tnodeName = element.nodeName.toLowerCase(), // Android2 requires lower for \"text\"\r\n\t\t\trenderer = wrapper.renderer,\r\n\t\t\tskipAttr,\r\n\t\t\ttitleNode,\r\n\t\t\tattrSetters = wrapper.attrSetters,\r\n\t\t\tshadows = wrapper.shadows,\r\n\t\t\thasSetSymbolSize,\r\n\t\t\tdoTransform,\r\n\t\t\tret = wrapper;\r\n\r\n\t\t// single key-value pair\r\n\t\tif (isString(hash) && defined(val)) {\r\n\t\t\tkey = hash;\r\n\t\t\thash = {};\r\n\t\t\thash[key] = val;\r\n\t\t}\r\n\r\n\t\t// used as a getter: first argument is a string, second is undefined\r\n\t\tif (isString(hash)) {\r\n\t\t\tkey = hash;\r\n\t\t\tif (nodeName === 'circle') {\r\n\t\t\t\tkey = { x: 'cx', y: 'cy' }[key] || key;\r\n\t\t\t} else if (key === 'strokeWidth') {\r\n\t\t\t\tkey = 'stroke-width';\r\n\t\t\t}\r\n\t\t\tret = attr(element, key) || wrapper[key] || 0;\r\n\t\t\tif (key !== 'd' && key !== 'visibility' && key !== 'fill') { // 'd' is string in animation step\r\n\t\t\t\tret = parseFloat(ret);\r\n\t\t\t}\r\n\r\n\t\t// setter\r\n\t\t} else {\r\n\r\n\t\t\tfor (key in hash) {\r\n\t\t\t\tskipAttr = false; // reset\r\n\t\t\t\tvalue = hash[key];\r\n\r\n\t\t\t\t// check for a specific attribute setter\r\n\t\t\t\tresult = attrSetters[key] && attrSetters[key].call(wrapper, value, key);\r\n\r\n\t\t\t\tif (result !== false) {\r\n\t\t\t\t\tif (result !== UNDEFINED) {\r\n\t\t\t\t\t\tvalue = result; // the attribute setter has returned a new value to set\r\n\t\t\t\t\t}\r\n\r\n\r\n\t\t\t\t\t// paths\r\n\t\t\t\t\tif (key === 'd') {\r\n\t\t\t\t\t\tif (value && value.join) { // join path\r\n\t\t\t\t\t\t\tvalue = value.join(' ');\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tif (/(NaN| {2}|^$)/.test(value)) {\r\n\t\t\t\t\t\t\tvalue = 'M 0 0';\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t//wrapper.d = value; // shortcut for animations\r\n\r\n\t\t\t\t\t// update child tspans x values\r\n\t\t\t\t\t} else if (key === 'x' && nodeName === 'text') {\r\n\t\t\t\t\t\tfor (i = 0; i < element.childNodes.length; i++) {\r\n\t\t\t\t\t\t\tchild = element.childNodes[i];\r\n\t\t\t\t\t\t\t// if the x values are equal, the tspan represents a linebreak\r\n\t\t\t\t\t\t\tif (attr(child, 'x') === attr(element, 'x')) {\r\n\t\t\t\t\t\t\t\t//child.setAttribute('x', value);\r\n\t\t\t\t\t\t\t\tattr(child, 'x', value);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t} else if (wrapper.rotation && (key === 'x' || key === 'y')) {\r\n\t\t\t\t\t\tdoTransform = true;\r\n\r\n\t\t\t\t\t// apply gradients\r\n\t\t\t\t\t} else if (key === 'fill') {\r\n\t\t\t\t\t\tvalue = renderer.color(value, element, key);\r\n\r\n\t\t\t\t\t// circle x and y\r\n\t\t\t\t\t} else if (nodeName === 'circle' && (key === 'x' || key === 'y')) {\r\n\t\t\t\t\t\tkey = { x: 'cx', y: 'cy' }[key] || key;\r\n\r\n\t\t\t\t\t// rectangle border radius\r\n\t\t\t\t\t} else if (nodeName === 'rect' && key === 'r') {\r\n\t\t\t\t\t\tattr(element, {\r\n\t\t\t\t\t\t\trx: value,\r\n\t\t\t\t\t\t\try: value\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t\tskipAttr = true;\r\n\r\n\t\t\t\t\t// translation and text rotation\r\n\t\t\t\t\t} else if (key === 'translateX' || key === 'translateY' || key === 'rotation' ||\r\n\t\t\t\t\t\t\tkey === 'verticalAlign' || key === 'scaleX' || key === 'scaleY') {\r\n\t\t\t\t\t\tdoTransform = true;\r\n\t\t\t\t\t\tskipAttr = true;\r\n\r\n\t\t\t\t\t// apply opacity as subnode (required by legacy WebKit and Batik)\r\n\t\t\t\t\t} else if (key === 'stroke') {\r\n\t\t\t\t\t\tvalue = renderer.color(value, element, key);\r\n\r\n\t\t\t\t\t// emulate VML's dashstyle implementation\r\n\t\t\t\t\t} else if (key === 'dashstyle') {\r\n\t\t\t\t\t\tkey = 'stroke-dasharray';\r\n\t\t\t\t\t\tvalue = value && value.toLowerCase();\r\n\t\t\t\t\t\tif (value === 'solid') {\r\n\t\t\t\t\t\t\tvalue = NONE;\r\n\t\t\t\t\t\t} else if (value) {\r\n\t\t\t\t\t\t\tvalue = value\r\n\t\t\t\t\t\t\t\t.replace('shortdashdotdot', '3,1,1,1,1,1,')\r\n\t\t\t\t\t\t\t\t.replace('shortdashdot', '3,1,1,1')\r\n\t\t\t\t\t\t\t\t.replace('shortdot', '1,1,')\r\n\t\t\t\t\t\t\t\t.replace('shortdash', '3,1,')\r\n\t\t\t\t\t\t\t\t.replace('longdash', '8,3,')\r\n\t\t\t\t\t\t\t\t.replace(/dot/g, '1,3,')\r\n\t\t\t\t\t\t\t\t.replace('dash', '4,3,')\r\n\t\t\t\t\t\t\t\t.replace(/,$/, '')\r\n\t\t\t\t\t\t\t\t.split(','); // ending comma\r\n\r\n\t\t\t\t\t\t\ti = value.length;\r\n\t\t\t\t\t\t\twhile (i--) {\r\n\t\t\t\t\t\t\t\tvalue[i] = pInt(value[i]) * pick(hash['stroke-width'], wrapper['stroke-width']);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\tvalue = value.join(',');\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t// IE9/MooTools combo: MooTools returns objects instead of numbers and IE9 Beta 2\r\n\t\t\t\t\t// is unable to cast them. Test again with final IE9.\r\n\t\t\t\t\t} else if (key === 'width') {\r\n\t\t\t\t\t\tvalue = pInt(value);\r\n\r\n\t\t\t\t\t// Text alignment\r\n\t\t\t\t\t} else if (key === 'align') {\r\n\t\t\t\t\t\tkey = 'text-anchor';\r\n\t\t\t\t\t\tvalue = { left: 'start', center: 'middle', right: 'end' }[value];\r\n\r\n\t\t\t\t\t// Title requires a subnode, #431\r\n\t\t\t\t\t} else if (key === 'title') {\r\n\t\t\t\t\t\ttitleNode = element.getElementsByTagName('title')[0];\r\n\t\t\t\t\t\tif (!titleNode) {\r\n\t\t\t\t\t\t\ttitleNode = doc.createElementNS(SVG_NS, 'title');\r\n\t\t\t\t\t\t\telement.appendChild(titleNode);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\ttitleNode.textContent = value;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t// jQuery animate changes case\r\n\t\t\t\t\tif (key === 'strokeWidth') {\r\n\t\t\t\t\t\tkey = 'stroke-width';\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t// In Chrome/Win < 6 as well as Batik, the stroke attribute can't be set when the stroke-\r\n\t\t\t\t\t// width is 0. #1369\r\n\t\t\t\t\tif (key === 'stroke-width' || key === 'stroke') {\r\n\t\t\t\t\t\twrapper[key] = value;\r\n\t\t\t\t\t\t// Only apply the stroke attribute if the stroke width is defined and larger than 0\r\n\t\t\t\t\t\tif (wrapper.stroke && wrapper['stroke-width']) {\r\n\t\t\t\t\t\t\tattr(element, 'stroke', wrapper.stroke);\r\n\t\t\t\t\t\t\tattr(element, 'stroke-width', wrapper['stroke-width']);\r\n\t\t\t\t\t\t\twrapper.hasStroke = true;\r\n\t\t\t\t\t\t} else if (key === 'stroke-width' && value === 0 && wrapper.hasStroke) {\r\n\t\t\t\t\t\t\telement.removeAttribute('stroke');\r\n\t\t\t\t\t\t\twrapper.hasStroke = false;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tskipAttr = true;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t// symbols\r\n\t\t\t\t\tif (wrapper.symbolName && /^(x|y|width|height|r|start|end|innerR|anchorX|anchorY)/.test(key)) {\r\n\r\n\r\n\t\t\t\t\t\tif (!hasSetSymbolSize) {\r\n\t\t\t\t\t\t\twrapper.symbolAttr(hash);\r\n\t\t\t\t\t\t\thasSetSymbolSize = true;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tskipAttr = true;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t// let the shadow follow the main element\r\n\t\t\t\t\tif (shadows && /^(width|height|visibility|x|y|d|transform|cx|cy|r)$/.test(key)) {\r\n\t\t\t\t\t\ti = shadows.length;\r\n\t\t\t\t\t\twhile (i--) {\r\n\t\t\t\t\t\t\tattr(\r\n\t\t\t\t\t\t\t\tshadows[i],\r\n\t\t\t\t\t\t\t\tkey,\r\n\t\t\t\t\t\t\t\tkey === 'height' ?\r\n\t\t\t\t\t\t\t\t\tmathMax(value - (shadows[i].cutHeight || 0), 0) :\r\n\t\t\t\t\t\t\t\t\tvalue\r\n\t\t\t\t\t\t\t);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t// validate heights\r\n\t\t\t\t\tif ((key === 'width' || key === 'height') && nodeName === 'rect' && value < 0) {\r\n\t\t\t\t\t\tvalue = 0;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t// Record for animation and quick access without polling the DOM\r\n\t\t\t\t\twrapper[key] = value;\r\n\r\n\r\n\t\t\t\t\tif (key === 'text') {\r\n\t\t\t\t\t\t// Delete bBox memo when the text changes\r\n\t\t\t\t\t\tif (value !== wrapper.textStr) {\r\n\t\t\t\t\t\t\tdelete wrapper.bBox;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\twrapper.textStr = value;\r\n\t\t\t\t\t\tif (wrapper.added) {\r\n\t\t\t\t\t\t\trenderer.buildText(wrapper);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t} else if (!skipAttr) {\r\n\t\t\t\t\t\tattr(element, key, value);\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t}\r\n\r\n\t\t\t}\r\n\r\n\t\t\t// Update transform. Do this outside the loop to prevent redundant updating for batch setting\r\n\t\t\t// of attributes.\r\n\t\t\tif (doTransform) {\r\n\t\t\t\twrapper.updateTransform();\r\n\t\t\t}\r\n\r\n\t\t}\r\n\r\n\t\treturn ret;\r\n\t},\r\n\r\n\r\n\t/**\r\n\t * Add a class name to an element\r\n\t */\r\n\taddClass: function (className) {\r\n\t\tvar element = this.element,\r\n\t\t\tcurrentClassName = attr(element, 'class') || '';\r\n\r\n\t\tif (currentClassName.indexOf(className) === -1) {\r\n\t\t\tattr(element, 'class', currentClassName + ' ' + className);\r\n\t\t}\r\n\t\treturn this;\r\n\t},\r\n\t/* hasClass and removeClass are not (yet) needed\r\n\thasClass: function (className) {\r\n\t\treturn attr(this.element, 'class').indexOf(className) !== -1;\r\n\t},\r\n\tremoveClass: function (className) {\r\n\t\tattr(this.element, 'class', attr(this.element, 'class').replace(className, ''));\r\n\t\treturn this;\r\n\t},\r\n\t*/\r\n\r\n\t/**\r\n\t * If one of the symbol size affecting parameters are changed,\r\n\t * check all the others only once for each call to an element's\r\n\t * .attr() method\r\n\t * @param {Object} hash\r\n\t */\r\n\tsymbolAttr: function (hash) {\r\n\t\tvar wrapper = this;\r\n\r\n\t\teach(['x', 'y', 'r', 'start', 'end', 'width', 'height', 'innerR', 'anchorX', 'anchorY'], function (key) {\r\n\t\t\twrapper[key] = pick(hash[key], wrapper[key]);\r\n\t\t});\r\n\r\n\t\twrapper.attr({\r\n\t\t\td: wrapper.renderer.symbols[wrapper.symbolName](\r\n\t\t\t\twrapper.x,\r\n\t\t\t\twrapper.y,\r\n\t\t\t\twrapper.width,\r\n\t\t\t\twrapper.height,\r\n\t\t\t\twrapper\r\n\t\t\t)\r\n\t\t});\r\n\t},\r\n\r\n\t/**\r\n\t * Apply a clipping path to this object\r\n\t * @param {String} id\r\n\t */\r\n\tclip: function (clipRect) {\r\n\t\treturn this.attr('clip-path', clipRect ? 'url(' + this.renderer.url + '#' + clipRect.id + ')' : NONE);\r\n\t},\r\n\r\n\t/**\r\n\t * Calculate the coordinates needed for drawing a rectangle crisply and return the\r\n\t * calculated attributes\r\n\t * @param {Number} strokeWidth\r\n\t * @param {Number} x\r\n\t * @param {Number} y\r\n\t * @param {Number} width\r\n\t * @param {Number} height\r\n\t */\r\n\tcrisp: function (strokeWidth, x, y, width, height) {\r\n\r\n\t\tvar wrapper = this,\r\n\t\t\tkey,\r\n\t\t\tattribs = {},\r\n\t\t\tvalues = {},\r\n\t\t\tnormalizer;\r\n\r\n\t\tstrokeWidth = strokeWidth || wrapper.strokeWidth || (wrapper.attr && wrapper.attr('stroke-width')) || 0;\r\n\t\tnormalizer = mathRound(strokeWidth) % 2 / 2; // mathRound because strokeWidth can sometimes have roundoff errors\r\n\r\n\t\t// normalize for crisp edges\r\n\t\tvalues.x = mathFloor(x || wrapper.x || 0) + normalizer;\r\n\t\tvalues.y = mathFloor(y || wrapper.y || 0) + normalizer;\r\n\t\tvalues.width = mathFloor((width || wrapper.width || 0) - 2 * normalizer);\r\n\t\tvalues.height = mathFloor((height || wrapper.height || 0) - 2 * normalizer);\r\n\t\tvalues.strokeWidth = strokeWidth;\r\n\r\n\t\tfor (key in values) {\r\n\t\t\tif (wrapper[key] !== values[key]) { // only set attribute if changed\r\n\t\t\t\twrapper[key] = attribs[key] = values[key];\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn attribs;\r\n\t},\r\n\r\n\t/**\r\n\t * Set styles for the element\r\n\t * @param {Object} styles\r\n\t */\r\n\tcss: function (styles) {\r\n\t\t/*jslint unparam: true*//* allow unused param a in the regexp function below */\r\n\t\tvar elemWrapper = this,\r\n\t\t\telem = elemWrapper.element,\r\n\t\t\ttextWidth = styles && styles.width && elem.nodeName.toLowerCase() === 'text',\r\n\t\t\tn,\r\n\t\t\tserializedCss = '',\r\n\t\t\thyphenate = function (a, b) { return '-' + b.toLowerCase(); };\r\n\t\t/*jslint unparam: false*/\r\n\r\n\t\t// convert legacy\r\n\t\tif (styles && styles.color) {\r\n\t\t\tstyles.fill = styles.color;\r\n\t\t}\r\n\r\n\t\t// Merge the new styles with the old ones\r\n\t\tstyles = extend(\r\n\t\t\telemWrapper.styles,\r\n\t\t\tstyles\r\n\t\t);\r\n\r\n\t\t// store object\r\n\t\telemWrapper.styles = styles;\r\n\r\n\r\n\t\t// Don't handle line wrap on canvas\r\n\t\tif (useCanVG && textWidth) {\r\n\t\t\tdelete styles.width;\r\n\t\t}\r\n\r\n\t\t// serialize and set style attribute\r\n\t\tif (isIE && !hasSVG) { // legacy IE doesn't support setting style attribute\r\n\t\t\tif (textWidth) {\r\n\t\t\t\tdelete styles.width;\r\n\t\t\t}\r\n\t\t\tcss(elemWrapper.element, styles);\r\n\t\t} else {\r\n\t\t\tfor (n in styles) {\r\n\t\t\t\tserializedCss += n.replace(/([A-Z])/g, hyphenate) + ':' + styles[n] + ';';\r\n\t\t\t}\r\n\t\t\tattr(elem, 'style', serializedCss); // #1881\r\n\t\t}\r\n\r\n\r\n\t\t// re-build text\r\n\t\tif (textWidth && elemWrapper.added) {\r\n\t\t\telemWrapper.renderer.buildText(elemWrapper);\r\n\t\t}\r\n\r\n\t\treturn elemWrapper;\r\n\t},\r\n\r\n\t/**\r\n\t * Add an event listener\r\n\t * @param {String} eventType\r\n\t * @param {Function} handler\r\n\t */\r\n\ton: function (eventType, handler) {\r\n\t\tvar svgElement = this,\r\n\t\t\telement = svgElement.element;\r\n\t\t\r\n\t\t// touch\r\n\t\tif (hasTouch && eventType === 'click') {\r\n\t\t\telement.ontouchstart = function (e) {\t\t\t\r\n\t\t\t\tsvgElement.touchEventFired = Date.now();\t\t\t\t\r\n\t\t\t\te.preventDefault();\r\n\t\t\t\thandler.call(element, e);\r\n\t\t\t};\r\n\t\t\telement.onclick = function (e) {\t\t\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\tif (userAgent.indexOf('Android') === -1 || Date.now() - (svgElement.touchEventFired || 0) > 1100) { // #2269\r\n\t\t\t\t\thandler.call(element, e);\r\n\t\t\t\t}\r\n\t\t\t};\t\t\t\r\n\t\t} else {\r\n\t\t\t// simplest possible event model for internal use\r\n\t\t\telement['on' + eventType] = handler;\r\n\t\t}\r\n\t\treturn this;\r\n\t},\r\n\r\n\t/**\r\n\t * Set the coordinates needed to draw a consistent radial gradient across\r\n\t * pie slices regardless of positioning inside the chart. The format is\r\n\t * [centerX, centerY, diameter] in pixels.\r\n\t */\r\n\tsetRadialReference: function (coordinates) {\r\n\t\tthis.element.radialReference = coordinates;\r\n\t\treturn this;\r\n\t},\r\n\r\n\t/**\r\n\t * Move an object and its children by x and y values\r\n\t * @param {Number} x\r\n\t * @param {Number} y\r\n\t */\r\n\ttranslate: function (x, y) {\r\n\t\treturn this.attr({\r\n\t\t\ttranslateX: x,\r\n\t\t\ttranslateY: y\r\n\t\t});\r\n\t},\r\n\r\n\t/**\r\n\t * Invert a group, rotate and flip\r\n\t */\r\n\tinvert: function () {\r\n\t\tvar wrapper = this;\r\n\t\twrapper.inverted = true;\r\n\t\twrapper.updateTransform();\r\n\t\treturn wrapper;\r\n\t},\r\n\r\n\t/**\r\n\t * Apply CSS to HTML elements. This is used in text within SVG rendering and\r\n\t * by the VML renderer\r\n\t */\r\n\thtmlCss: function (styles) {\r\n\t\tvar wrapper = this,\r\n\t\t\telement = wrapper.element,\r\n\t\t\ttextWidth = styles && element.tagName === 'SPAN' && styles.width;\r\n\r\n\t\tif (textWidth) {\r\n\t\t\tdelete styles.width;\r\n\t\t\twrapper.textWidth = textWidth;\r\n\t\t\twrapper.updateTransform();\r\n\t\t}\r\n\r\n\t\twrapper.styles = extend(wrapper.styles, styles);\r\n\t\tcss(wrapper.element, styles);\r\n\r\n\t\treturn wrapper;\r\n\t},\r\n\r\n\r\n\r\n\t/**\r\n\t * VML and useHTML method for calculating the bounding box based on offsets\r\n\t * @param {Boolean} refresh Whether to force a fresh value from the DOM or to\r\n\t * use the cached value\r\n\t *\r\n\t * @return {Object} A hash containing values for x, y, width and height\r\n\t */\r\n\r\n\thtmlGetBBox: function () {\r\n\t\tvar wrapper = this,\r\n\t\t\telement = wrapper.element,\r\n\t\t\tbBox = wrapper.bBox;\r\n\r\n\t\t// faking getBBox in exported SVG in legacy IE\r\n\t\tif (!bBox) {\r\n\t\t\t// faking getBBox in exported SVG in legacy IE (is this a duplicate of the fix for #1079?)\r\n\t\t\tif (element.nodeName === 'text') {\r\n\t\t\t\telement.style.position = ABSOLUTE;\r\n\t\t\t}\r\n\r\n\t\t\tbBox = wrapper.bBox = {\r\n\t\t\t\tx: element.offsetLeft,\r\n\t\t\t\ty: element.offsetTop,\r\n\t\t\t\twidth: element.offsetWidth,\r\n\t\t\t\theight: element.offsetHeight\r\n\t\t\t};\r\n\t\t}\r\n\r\n\t\treturn bBox;\r\n\t},\r\n\r\n\t/**\r\n\t * VML override private method to update elements based on internal\r\n\t * properties based on SVG transform\r\n\t */\r\n\thtmlUpdateTransform: function () {\r\n\t\t// aligning non added elements is expensive\r\n\t\tif (!this.added) {\r\n\t\t\tthis.alignOnAdd = true;\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tvar wrapper = this,\r\n\t\t\trenderer = wrapper.renderer,\r\n\t\t\telem = wrapper.element,\r\n\t\t\ttranslateX = wrapper.translateX || 0,\r\n\t\t\ttranslateY = wrapper.translateY || 0,\r\n\t\t\tx = wrapper.x || 0,\r\n\t\t\ty = wrapper.y || 0,\r\n\t\t\talign = wrapper.textAlign || 'left',\r\n\t\t\talignCorrection = { left: 0, center: 0.5, right: 1 }[align],\r\n\t\t\tnonLeft = align && align !== 'left',\r\n\t\t\tshadows = wrapper.shadows;\r\n\r\n\t\t// apply translate\r\n\t\tcss(elem, {\r\n\t\t\tmarginLeft: translateX,\r\n\t\t\tmarginTop: translateY\r\n\t\t});\r\n\t\tif (shadows) { // used in labels/tooltip\r\n\t\t\teach(shadows, function (shadow) {\r\n\t\t\t\tcss(shadow, {\r\n\t\t\t\t\tmarginLeft: translateX + 1,\r\n\t\t\t\t\tmarginTop: translateY + 1\r\n\t\t\t\t});\r\n\t\t\t});\r\n\t\t}\r\n\r\n\t\t// apply inversion\r\n\t\tif (wrapper.inverted) { // wrapper is a group\r\n\t\t\teach(elem.childNodes, function (child) {\r\n\t\t\t\trenderer.invertChild(child, elem);\r\n\t\t\t});\r\n\t\t}\r\n\r\n\t\tif (elem.tagName === 'SPAN') {\r\n\r\n\t\t\tvar width, height,\r\n\t\t\t\trotation = wrapper.rotation,\r\n\t\t\t\tbaseline,\r\n\t\t\t\tradians = 0,\r\n\t\t\t\tcostheta = 1,\r\n\t\t\t\tsintheta = 0,\r\n\t\t\t\tquad,\r\n\t\t\t\ttextWidth = pInt(wrapper.textWidth),\r\n\t\t\t\txCorr = wrapper.xCorr || 0,\r\n\t\t\t\tyCorr = wrapper.yCorr || 0,\r\n\t\t\t\tcurrentTextTransform = [rotation, align, elem.innerHTML, wrapper.textWidth].join(',');\r\n\r\n\t\t\tif (currentTextTransform !== wrapper.cTT) { // do the calculations and DOM access only if properties changed\r\n\r\n\t\t\t\tif (defined(rotation)) {\r\n\r\n\t\t\t\t\tradians = rotation * deg2rad; // deg to rad\r\n\t\t\t\t\tcostheta = mathCos(radians);\r\n\t\t\t\t\tsintheta = mathSin(radians);\r\n\r\n\t\t\t\t\twrapper.setSpanRotation(rotation, sintheta, costheta);\r\n\r\n\t\t\t\t}\r\n\r\n\t\t\t\twidth = pick(wrapper.elemWidth, elem.offsetWidth);\r\n\t\t\t\theight = pick(wrapper.elemHeight, elem.offsetHeight);\r\n\r\n\t\t\t\t// update textWidth\r\n\t\t\t\tif (width > textWidth && /[ \\-]/.test(elem.textContent || elem.innerText)) { // #983, #1254\r\n\t\t\t\t\tcss(elem, {\r\n\t\t\t\t\t\twidth: textWidth + PX,\r\n\t\t\t\t\t\tdisplay: 'block',\r\n\t\t\t\t\t\twhiteSpace: 'normal'\r\n\t\t\t\t\t});\r\n\t\t\t\t\twidth = textWidth;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// correct x and y\r\n\t\t\t\tbaseline = renderer.fontMetrics(elem.style.fontSize).b;\r\n\t\t\t\txCorr = costheta < 0 && -width;\r\n\t\t\t\tyCorr = sintheta < 0 && -height;\r\n\r\n\t\t\t\t// correct for baseline and corners spilling out after rotation\r\n\t\t\t\tquad = costheta * sintheta < 0;\r\n\t\t\t\txCorr += sintheta * baseline * (quad ? 1 - alignCorrection : alignCorrection);\r\n\t\t\t\tyCorr -= costheta * baseline * (rotation ? (quad ? alignCorrection : 1 - alignCorrection) : 1);\r\n\r\n\t\t\t\t// correct for the length/height of the text\r\n\t\t\t\tif (nonLeft) {\r\n\t\t\t\t\txCorr -= width * alignCorrection * (costheta < 0 ? -1 : 1);\r\n\t\t\t\t\tif (rotation) {\r\n\t\t\t\t\t\tyCorr -= height * alignCorrection * (sintheta < 0 ? -1 : 1);\r\n\t\t\t\t\t}\r\n\t\t\t\t\tcss(elem, {\r\n\t\t\t\t\t\ttextAlign: align\r\n\t\t\t\t\t});\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// record correction\r\n\t\t\t\twrapper.xCorr = xCorr;\r\n\t\t\t\twrapper.yCorr = yCorr;\r\n\t\t\t}\r\n\r\n\t\t\t// apply position with correction\r\n\t\t\tcss(elem, {\r\n\t\t\t\tleft: (x + xCorr) + PX,\r\n\t\t\t\ttop: (y + yCorr) + PX\r\n\t\t\t});\r\n\r\n\t\t\t// force reflow in webkit to apply the left and top on useHTML element (#1249)\r\n\t\t\tif (isWebKit) {\r\n\t\t\t\theight = elem.offsetHeight; // assigned to height for JSLint purpose\r\n\t\t\t}\r\n\r\n\t\t\t// record current text transform\r\n\t\t\twrapper.cTT = currentTextTransform;\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Set the rotation of an individual HTML span\r\n\t */\r\n\tsetSpanRotation: function (rotation) {\r\n\t\tvar rotationStyle = {},\r\n\t\t\tcssTransformKey = isIE ? '-ms-transform' : isWebKit ? '-webkit-transform' : isFirefox ? 'MozTransform' : isOpera ? '-o-transform' : '';\r\n\r\n\t\trotationStyle[cssTransformKey] = rotationStyle.transform = 'rotate(' + rotation + 'deg)';\r\n\t\tcss(this.element, rotationStyle);\r\n\t},\r\n\r\n\t/**\r\n\t * Private method to update the transform attribute based on internal\r\n\t * properties\r\n\t */\r\n\tupdateTransform: function () {\r\n\t\tvar wrapper = this,\r\n\t\t\ttranslateX = wrapper.translateX || 0,\r\n\t\t\ttranslateY = wrapper.translateY || 0,\r\n\t\t\tscaleX = wrapper.scaleX,\r\n\t\t\tscaleY = wrapper.scaleY,\r\n\t\t\tinverted = wrapper.inverted,\r\n\t\t\trotation = wrapper.rotation,\r\n\t\t\ttransform;\r\n\r\n\t\t// flipping affects translate as adjustment for flipping around the group's axis\r\n\t\tif (inverted) {\r\n\t\t\ttranslateX += wrapper.attr('width');\r\n\t\t\ttranslateY += wrapper.attr('height');\r\n\t\t}\r\n\r\n\t\t// Apply translate. Nearly all transformed elements have translation, so instead\r\n\t\t// of checking for translate = 0, do it always (#1767, #1846).\r\n\t\ttransform = ['translate(' + translateX + ',' + translateY + ')'];\r\n\r\n\t\t// apply rotation\r\n\t\tif (inverted) {\r\n\t\t\ttransform.push('rotate(90) scale(-1,1)');\r\n\t\t} else if (rotation) { // text rotation\r\n\t\t\ttransform.push('rotate(' + rotation + ' ' + (wrapper.x || 0) + ' ' + (wrapper.y || 0) + ')');\r\n\t\t}\r\n\r\n\t\t// apply scale\r\n\t\tif (defined(scaleX) || defined(scaleY)) {\r\n\t\t\ttransform.push('scale(' + pick(scaleX, 1) + ' ' + pick(scaleY, 1) + ')');\r\n\t\t}\r\n\r\n\t\tif (transform.length) {\r\n\t\t\tattr(wrapper.element, 'transform', transform.join(' '));\r\n\t\t}\r\n\t},\r\n\t/**\r\n\t * Bring the element to the front\r\n\t */\r\n\ttoFront: function () {\r\n\t\tvar element = this.element;\r\n\t\telement.parentNode.appendChild(element);\r\n\t\treturn this;\r\n\t},\r\n\r\n\r\n\t/**\r\n\t * Break down alignment options like align, verticalAlign, x and y\r\n\t * to x and y relative to the chart.\r\n\t *\r\n\t * @param {Object} alignOptions\r\n\t * @param {Boolean} alignByTranslate\r\n\t * @param {String[Object} box The box to align to, needs a width and height. When the\r\n\t *        box is a string, it refers to an object in the Renderer. For example, when\r\n\t *        box is 'spacingBox', it refers to Renderer.spacingBox which holds width, height\r\n\t *        x and y properties.\r\n\t *\r\n\t */\r\n\talign: function (alignOptions, alignByTranslate, box) {\r\n\t\tvar align,\r\n\t\t\tvAlign,\r\n\t\t\tx,\r\n\t\t\ty,\r\n\t\t\tattribs = {},\r\n\t\t\talignTo,\r\n\t\t\trenderer = this.renderer,\r\n\t\t\talignedObjects = renderer.alignedObjects;\r\n\r\n\t\t// First call on instanciate\r\n\t\tif (alignOptions) {\r\n\t\t\tthis.alignOptions = alignOptions;\r\n\t\t\tthis.alignByTranslate = alignByTranslate;\r\n\t\t\tif (!box || isString(box)) { // boxes other than renderer handle this internally\r\n\t\t\t\tthis.alignTo = alignTo = box || 'renderer';\r\n\t\t\t\terase(alignedObjects, this); // prevent duplicates, like legendGroup after resize\r\n\t\t\t\talignedObjects.push(this);\r\n\t\t\t\tbox = null; // reassign it below\r\n\t\t\t}\r\n\r\n\t\t// When called on resize, no arguments are supplied\r\n\t\t} else {\r\n\t\t\talignOptions = this.alignOptions;\r\n\t\t\talignByTranslate = this.alignByTranslate;\r\n\t\t\talignTo = this.alignTo;\r\n\t\t}\r\n\r\n\t\tbox = pick(box, renderer[alignTo], renderer);\r\n\r\n\t\t// Assign variables\r\n\t\talign = alignOptions.align;\r\n\t\tvAlign = alignOptions.verticalAlign;\r\n\t\tx = (box.x || 0) + (alignOptions.x || 0); // default: left align\r\n\t\ty = (box.y || 0) + (alignOptions.y || 0); // default: top align\r\n\r\n\t\t// Align\r\n\t\tif (align === 'right' || align === 'center') {\r\n\t\t\tx += (box.width - (alignOptions.width || 0)) /\r\n\t\t\t\t\t{ right: 1, center: 2 }[align];\r\n\t\t}\r\n\t\tattribs[alignByTranslate ? 'translateX' : 'x'] = mathRound(x);\r\n\r\n\r\n\t\t// Vertical align\r\n\t\tif (vAlign === 'bottom' || vAlign === 'middle') {\r\n\t\t\ty += (box.height - (alignOptions.height || 0)) /\r\n\t\t\t\t\t({ bottom: 1, middle: 2 }[vAlign] || 1);\r\n\r\n\t\t}\r\n\t\tattribs[alignByTranslate ? 'translateY' : 'y'] = mathRound(y);\r\n\r\n\t\t// Animate only if already placed\r\n\t\tthis[this.placed ? 'animate' : 'attr'](attribs);\r\n\t\tthis.placed = true;\r\n\t\tthis.alignAttr = attribs;\r\n\r\n\t\treturn this;\r\n\t},\r\n\r\n\t/**\r\n\t * Get the bounding box (width, height, x and y) for the element\r\n\t */\r\n\tgetBBox: function () {\r\n\t\tvar wrapper = this,\r\n\t\t\tbBox = wrapper.bBox,\r\n\t\t\trenderer = wrapper.renderer,\r\n\t\t\twidth,\r\n\t\t\theight,\r\n\t\t\trotation = wrapper.rotation,\r\n\t\t\telement = wrapper.element,\r\n\t\t\tstyles = wrapper.styles,\r\n\t\t\trad = rotation * deg2rad;\r\n\r\n\t\tif (!bBox) {\r\n\t\t\t// SVG elements\r\n\t\t\tif (element.namespaceURI === SVG_NS || renderer.forExport) {\r\n\t\t\t\ttry { // Fails in Firefox if the container has display: none.\r\n\r\n\t\t\t\t\tbBox = element.getBBox ?\r\n\t\t\t\t\t\t// SVG: use extend because IE9 is not allowed to change width and height in case\r\n\t\t\t\t\t\t// of rotation (below)\r\n\t\t\t\t\t\textend({}, element.getBBox()) :\r\n\t\t\t\t\t\t// Canvas renderer and legacy IE in export mode\r\n\t\t\t\t\t\t{\r\n\t\t\t\t\t\t\twidth: element.offsetWidth,\r\n\t\t\t\t\t\t\theight: element.offsetHeight\r\n\t\t\t\t\t\t};\r\n\t\t\t\t} catch (e) {}\r\n\r\n\t\t\t\t// If the bBox is not set, the try-catch block above failed. The other condition\r\n\t\t\t\t// is for Opera that returns a width of -Infinity on hidden elements.\r\n\t\t\t\tif (!bBox || bBox.width < 0) {\r\n\t\t\t\t\tbBox = { width: 0, height: 0 };\r\n\t\t\t\t}\r\n\r\n\r\n\t\t\t// VML Renderer or useHTML within SVG\r\n\t\t\t} else {\r\n\r\n\t\t\t\tbBox = wrapper.htmlGetBBox();\r\n\r\n\t\t\t}\r\n\r\n\t\t\t// True SVG elements as well as HTML elements in modern browsers using the .useHTML option\r\n\t\t\t// need to compensated for rotation\r\n\t\t\tif (renderer.isSVG) {\r\n\t\t\t\twidth = bBox.width;\r\n\t\t\t\theight = bBox.height;\r\n\r\n\t\t\t\t// Workaround for wrong bounding box in IE9 and IE10 (#1101, #1505, #1669)\r\n\t\t\t\tif (isIE && styles && styles.fontSize === '11px' && height.toPrecision(3) === '22.7') {\r\n\t\t\t\t\tbBox.height = height = 14;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Adjust for rotated text\r\n\t\t\t\tif (rotation) {\r\n\t\t\t\t\tbBox.width = mathAbs(height * mathSin(rad)) + mathAbs(width * mathCos(rad));\r\n\t\t\t\t\tbBox.height = mathAbs(height * mathCos(rad)) + mathAbs(width * mathSin(rad));\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\twrapper.bBox = bBox;\r\n\t\t}\r\n\t\treturn bBox;\r\n\t},\r\n\r\n\t/**\r\n\t * Show the element\r\n\t */\r\n\tshow: function () {\r\n\t\treturn this.attr({ visibility: VISIBLE });\r\n\t},\r\n\r\n\t/**\r\n\t * Hide the element\r\n\t */\r\n\thide: function () {\r\n\t\treturn this.attr({ visibility: HIDDEN });\r\n\t},\r\n\r\n\tfadeOut: function (duration) {\r\n\t\tvar elemWrapper = this;\r\n\t\telemWrapper.animate({\r\n\t\t\topacity: 0\r\n\t\t}, {\r\n\t\t\tduration: duration || 150,\r\n\t\t\tcomplete: function () {\r\n\t\t\t\telemWrapper.hide();\r\n\t\t\t}\r\n\t\t});\r\n\t},\r\n\r\n\t/**\r\n\t * Add the element\r\n\t * @param {Object|Undefined} parent Can be an element, an element wrapper or undefined\r\n\t *    to append the element to the renderer.box.\r\n\t */\r\n\tadd: function (parent) {\r\n\r\n\t\tvar renderer = this.renderer,\r\n\t\t\tparentWrapper = parent || renderer,\r\n\t\t\tparentNode = parentWrapper.element || renderer.box,\r\n\t\t\tchildNodes = parentNode.childNodes,\r\n\t\t\telement = this.element,\r\n\t\t\tzIndex = attr(element, 'zIndex'),\r\n\t\t\totherElement,\r\n\t\t\totherZIndex,\r\n\t\t\ti,\r\n\t\t\tinserted;\r\n\r\n\t\tif (parent) {\r\n\t\t\tthis.parentGroup = parent;\r\n\t\t}\r\n\r\n\t\t// mark as inverted\r\n\t\tthis.parentInverted = parent && parent.inverted;\r\n\r\n\t\t// build formatted text\r\n\t\tif (this.textStr !== undefined) {\r\n\t\t\trenderer.buildText(this);\r\n\t\t}\r\n\r\n\t\t// mark the container as having z indexed children\r\n\t\tif (zIndex) {\r\n\t\t\tparentWrapper.handleZ = true;\r\n\t\t\tzIndex = pInt(zIndex);\r\n\t\t}\r\n\r\n\t\t// insert according to this and other elements' zIndex\r\n\t\tif (parentWrapper.handleZ) { // this element or any of its siblings has a z index\r\n\t\t\tfor (i = 0; i < childNodes.length; i++) {\r\n\t\t\t\totherElement = childNodes[i];\r\n\t\t\t\totherZIndex = attr(otherElement, 'zIndex');\r\n\t\t\t\tif (otherElement !== element && (\r\n\t\t\t\t\t\t// insert before the first element with a higher zIndex\r\n\t\t\t\t\t\tpInt(otherZIndex) > zIndex ||\r\n\t\t\t\t\t\t// if no zIndex given, insert before the first element with a zIndex\r\n\t\t\t\t\t\t(!defined(zIndex) && defined(otherZIndex))\r\n\r\n\t\t\t\t\t\t)) {\r\n\t\t\t\t\tparentNode.insertBefore(element, otherElement);\r\n\t\t\t\t\tinserted = true;\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// default: append at the end\r\n\t\tif (!inserted) {\r\n\t\t\tparentNode.appendChild(element);\r\n\t\t}\r\n\r\n\t\t// mark as added\r\n\t\tthis.added = true;\r\n\r\n\t\t// fire an event for internal hooks\r\n\t\tfireEvent(this, 'add');\r\n\r\n\t\treturn this;\r\n\t},\r\n\r\n\t/**\r\n\t * Removes a child either by removeChild or move to garbageBin.\r\n\t * Issue 490; in VML removeChild results in Orphaned nodes according to sIEve, discardElement does not.\r\n\t */\r\n\tsafeRemoveChild: function (element) {\r\n\t\tvar parentNode = element.parentNode;\r\n\t\tif (parentNode) {\r\n\t\t\tparentNode.removeChild(element);\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Destroy the element and element wrapper\r\n\t */\r\n\tdestroy: function () {\r\n\t\tvar wrapper = this,\r\n\t\t\telement = wrapper.element || {},\r\n\t\t\tshadows = wrapper.shadows,\r\n\t\t\tparentToClean = wrapper.renderer.isSVG && element.nodeName === 'SPAN' && element.parentNode,\r\n\t\t\tgrandParent,\r\n\t\t\tkey,\r\n\t\t\ti;\r\n\r\n\t\t// remove events\r\n\t\telement.onclick = element.onmouseout = element.onmouseover = element.onmousemove = element.point = null;\r\n\t\tstop(wrapper); // stop running animations\r\n\r\n\t\tif (wrapper.clipPath) {\r\n\t\t\twrapper.clipPath = wrapper.clipPath.destroy();\r\n\t\t}\r\n\r\n\t\t// Destroy stops in case this is a gradient object\r\n\t\tif (wrapper.stops) {\r\n\t\t\tfor (i = 0; i < wrapper.stops.length; i++) {\r\n\t\t\t\twrapper.stops[i] = wrapper.stops[i].destroy();\r\n\t\t\t}\r\n\t\t\twrapper.stops = null;\r\n\t\t}\r\n\r\n\t\t// remove element\r\n\t\twrapper.safeRemoveChild(element);\r\n\r\n\t\t// destroy shadows\r\n\t\tif (shadows) {\r\n\t\t\teach(shadows, function (shadow) {\r\n\t\t\t\twrapper.safeRemoveChild(shadow);\r\n\t\t\t});\r\n\t\t}\r\n\r\n\t\t// In case of useHTML, clean up empty containers emulating SVG groups (#1960).\r\n\t\twhile (parentToClean && parentToClean.childNodes.length === 0) {\r\n\t\t\tgrandParent = parentToClean.parentNode;\r\n\t\t\twrapper.safeRemoveChild(parentToClean);\r\n\t\t\tparentToClean = grandParent;\r\n\t\t}\r\n\r\n\t\t// remove from alignObjects\r\n\t\tif (wrapper.alignTo) {\r\n\t\t\terase(wrapper.renderer.alignedObjects, wrapper);\r\n\t\t}\r\n\r\n\t\tfor (key in wrapper) {\r\n\t\t\tdelete wrapper[key];\r\n\t\t}\r\n\r\n\t\treturn null;\r\n\t},\r\n\r\n\t/**\r\n\t * Add a shadow to the element. Must be done after the element is added to the DOM\r\n\t * @param {Boolean|Object} shadowOptions\r\n\t */\r\n\tshadow: function (shadowOptions, group, cutOff) {\r\n\t\tvar shadows = [],\r\n\t\t\ti,\r\n\t\t\tshadow,\r\n\t\t\telement = this.element,\r\n\t\t\tstrokeWidth,\r\n\t\t\tshadowWidth,\r\n\t\t\tshadowElementOpacity,\r\n\r\n\t\t\t// compensate for inverted plot area\r\n\t\t\ttransform;\r\n\r\n\r\n\t\tif (shadowOptions) {\r\n\t\t\tshadowWidth = pick(shadowOptions.width, 3);\r\n\t\t\tshadowElementOpacity = (shadowOptions.opacity || 0.15) / shadowWidth;\r\n\t\t\ttransform = this.parentInverted ?\r\n\t\t\t\t'(-1,-1)' :\r\n\t\t\t\t'(' + pick(shadowOptions.offsetX, 1) + ', ' + pick(shadowOptions.offsetY, 1) + ')';\r\n\t\t\tfor (i = 1; i <= shadowWidth; i++) {\r\n\t\t\t\tshadow = element.cloneNode(0);\r\n\t\t\t\tstrokeWidth = (shadowWidth * 2) + 1 - (2 * i);\r\n\t\t\t\tattr(shadow, {\r\n\t\t\t\t\t'isShadow': 'true',\r\n\t\t\t\t\t'stroke': shadowOptions.color || 'black',\r\n\t\t\t\t\t'stroke-opacity': shadowElementOpacity * i,\r\n\t\t\t\t\t'stroke-width': strokeWidth,\r\n\t\t\t\t\t'transform': 'translate' + transform,\r\n\t\t\t\t\t'fill': NONE\r\n\t\t\t\t});\r\n\t\t\t\tif (cutOff) {\r\n\t\t\t\t\tattr(shadow, 'height', mathMax(attr(shadow, 'height') - strokeWidth, 0));\r\n\t\t\t\t\tshadow.cutHeight = strokeWidth;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (group) {\r\n\t\t\t\t\tgroup.element.appendChild(shadow);\r\n\t\t\t\t} else {\r\n\t\t\t\t\telement.parentNode.insertBefore(shadow, element);\r\n\t\t\t\t}\r\n\r\n\t\t\t\tshadows.push(shadow);\r\n\t\t\t}\r\n\r\n\t\t\tthis.shadows = shadows;\r\n\t\t}\r\n\t\treturn this;\r\n\r\n\t}\r\n};\r\n\r\n\r\n/**\r\n * The default SVG renderer\r\n */\r\nvar SVGRenderer = function () {\r\n\tthis.init.apply(this, arguments);\r\n};\r\nSVGRenderer.prototype = {\r\n\tElement: SVGElement,\r\n\r\n\t/**\r\n\t * Initialize the SVGRenderer\r\n\t * @param {Object} container\r\n\t * @param {Number} width\r\n\t * @param {Number} height\r\n\t * @param {Boolean} forExport\r\n\t */\r\n\tinit: function (container, width, height, forExport) {\r\n\t\tvar renderer = this,\r\n\t\t\tloc = location,\r\n\t\t\tboxWrapper,\r\n\t\t\telement,\r\n\t\t\tdesc;\r\n\r\n\t\tboxWrapper = renderer.createElement('svg')\r\n\t\t\t.attr({\r\n\t\t\t\tversion: '1.1'\r\n\t\t\t});\r\n\t\telement = boxWrapper.element;\r\n\t\tcontainer.appendChild(element);\r\n\r\n\t\t// For browsers other than IE, add the namespace attribute (#1978)\r\n\t\tif (container.innerHTML.indexOf('xmlns') === -1) {\r\n\t\t\tattr(element, 'xmlns', SVG_NS);\r\n\t\t}\r\n\r\n\t\t// object properties\r\n\t\trenderer.isSVG = true;\r\n\t\trenderer.box = element;\r\n\t\trenderer.boxWrapper = boxWrapper;\r\n\t\trenderer.alignedObjects = [];\r\n\r\n\t\t// Page url used for internal references. #24, #672, #1070\r\n\t\trenderer.url = (isFirefox || isWebKit) && doc.getElementsByTagName('base').length ?\r\n\t\t\tloc.href\r\n\t\t\t\t.replace(/#.*?$/, '') // remove the hash\r\n\t\t\t\t.replace(/([\\('\\)])/g, '\\\\$1') // escape parantheses and quotes\r\n\t\t\t\t.replace(/ /g, '%20') : // replace spaces (needed for Safari only)\r\n\t\t\t'';\r\n\r\n\t\t// Add description\r\n\t\tdesc = this.createElement('desc').add();\r\n\t\tdesc.element.appendChild(doc.createTextNode('Created with ' + PRODUCT + ' ' + VERSION));\r\n\r\n\r\n\t\trenderer.defs = this.createElement('defs').add();\r\n\t\trenderer.forExport = forExport;\r\n\t\trenderer.gradients = {}; // Object where gradient SvgElements are stored\r\n\r\n\t\trenderer.setSize(width, height, false);\r\n\r\n\r\n\r\n\t\t// Issue 110 workaround:\r\n\t\t// In Firefox, if a div is positioned by percentage, its pixel position may land\r\n\t\t// between pixels. The container itself doesn't display this, but an SVG element\r\n\t\t// inside this container will be drawn at subpixel precision. In order to draw\r\n\t\t// sharp lines, this must be compensated for. This doesn't seem to work inside\r\n\t\t// iframes though (like in jsFiddle).\r\n\t\tvar subPixelFix, rect;\r\n\t\tif (isFirefox && container.getBoundingClientRect) {\r\n\t\t\trenderer.subPixelFix = subPixelFix = function () {\r\n\t\t\t\tcss(container, { left: 0, top: 0 });\r\n\t\t\t\trect = container.getBoundingClientRect();\r\n\t\t\t\tcss(container, {\r\n\t\t\t\t\tleft: (mathCeil(rect.left) - rect.left) + PX,\r\n\t\t\t\t\ttop: (mathCeil(rect.top) - rect.top) + PX\r\n\t\t\t\t});\r\n\t\t\t};\r\n\r\n\t\t\t// run the fix now\r\n\t\t\tsubPixelFix();\r\n\r\n\t\t\t// run it on resize\r\n\t\t\taddEvent(win, 'resize', subPixelFix);\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Detect whether the renderer is hidden. This happens when one of the parent elements\r\n\t * has display: none. #608.\r\n\t */\r\n\tisHidden: function () {\r\n\t\treturn !this.boxWrapper.getBBox().width;\r\n\t},\r\n\r\n\t/**\r\n\t * Destroys the renderer and its allocated members.\r\n\t */\r\n\tdestroy: function () {\r\n\t\tvar renderer = this,\r\n\t\t\trendererDefs = renderer.defs;\r\n\t\trenderer.box = null;\r\n\t\trenderer.boxWrapper = renderer.boxWrapper.destroy();\r\n\r\n\t\t// Call destroy on all gradient elements\r\n\t\tdestroyObjectProperties(renderer.gradients || {});\r\n\t\trenderer.gradients = null;\r\n\r\n\t\t// Defs are null in VMLRenderer\r\n\t\t// Otherwise, destroy them here.\r\n\t\tif (rendererDefs) {\r\n\t\t\trenderer.defs = rendererDefs.destroy();\r\n\t\t}\r\n\r\n\t\t// Remove sub pixel fix handler\r\n\t\t// We need to check that there is a handler, otherwise all functions that are registered for event 'resize' are removed\r\n\t\t// See issue #982\r\n\t\tif (renderer.subPixelFix) {\r\n\t\t\tremoveEvent(win, 'resize', renderer.subPixelFix);\r\n\t\t}\r\n\r\n\t\trenderer.alignedObjects = null;\r\n\r\n\t\treturn null;\r\n\t},\r\n\r\n\t/**\r\n\t * Create a wrapper for an SVG element\r\n\t * @param {Object} nodeName\r\n\t */\r\n\tcreateElement: function (nodeName) {\r\n\t\tvar wrapper = new this.Element();\r\n\t\twrapper.init(this, nodeName);\r\n\t\treturn wrapper;\r\n\t},\r\n\r\n\t/**\r\n\t * Dummy function for use in canvas renderer\r\n\t */\r\n\tdraw: function () {},\r\n\r\n\t/**\r\n\t * Parse a simple HTML string into SVG tspans\r\n\t *\r\n\t * @param {Object} textNode The parent text SVG node\r\n\t */\r\n\tbuildText: function (wrapper) {\r\n\t\tvar textNode = wrapper.element,\r\n\t\t\trenderer = this,\r\n\t\t\tforExport = renderer.forExport,\r\n\t\t\tlines = pick(wrapper.textStr, '').toString()\r\n\t\t\t\t.replace(/<(b|strong)>/g, '<span style=\"font-weight:bold\">')\r\n\t\t\t\t.replace(/<(i|em)>/g, '<span style=\"font-style:italic\">')\r\n\t\t\t\t.replace(/<a/g, '<span')\r\n\t\t\t\t.replace(/<\\/(b|strong|i|em|a)>/g, '</span>')\r\n\t\t\t\t.split(/<br.*?>/g),\r\n\t\t\tchildNodes = textNode.childNodes,\r\n\t\t\tstyleRegex = /style=\"([^\"]+)\"/,\r\n\t\t\threfRegex = /href=\"(http[^\"]+)\"/,\r\n\t\t\tparentX = attr(textNode, 'x'),\r\n\t\t\ttextStyles = wrapper.styles,\r\n\t\t\twidth = textStyles && textStyles.width && pInt(textStyles.width),\r\n\t\t\ttextLineHeight = textStyles && textStyles.lineHeight,\r\n\t\t\ti = childNodes.length;\r\n\r\n\t\t/// remove old text\r\n\t\twhile (i--) {\r\n\t\t\ttextNode.removeChild(childNodes[i]);\r\n\t\t}\r\n\r\n\t\tif (width && !wrapper.added) {\r\n\t\t\tthis.box.appendChild(textNode); // attach it to the DOM to read offset width\r\n\t\t}\r\n\r\n\t\t// remove empty line at end\r\n\t\tif (lines[lines.length - 1] === '') {\r\n\t\t\tlines.pop();\r\n\t\t}\r\n\r\n\t\t// build the lines\r\n\t\teach(lines, function (line, lineNo) {\r\n\t\t\tvar spans, spanNo = 0;\r\n\r\n\t\t\tline = line.replace(/<span/g, '|||<span').replace(/<\\/span>/g, '</span>|||');\r\n\t\t\tspans = line.split('|||');\r\n\r\n\t\t\teach(spans, function (span) {\r\n\t\t\t\tif (span !== '' || spans.length === 1) {\r\n\t\t\t\t\tvar attributes = {},\r\n\t\t\t\t\t\ttspan = doc.createElementNS(SVG_NS, 'tspan'),\r\n\t\t\t\t\t\tspanStyle; // #390\r\n\t\t\t\t\tif (styleRegex.test(span)) {\r\n\t\t\t\t\t\tspanStyle = span.match(styleRegex)[1].replace(/(;| |^)color([ :])/, '$1fill$2');\r\n\t\t\t\t\t\tattr(tspan, 'style', spanStyle);\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (hrefRegex.test(span) && !forExport) { // Not for export - #1529\r\n\t\t\t\t\t\tattr(tspan, 'onclick', 'location.href=\\\"' + span.match(hrefRegex)[1] + '\\\"');\r\n\t\t\t\t\t\tcss(tspan, { cursor: 'pointer' });\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tspan = (span.replace(/<(.|\\n)*?>/g, '') || ' ')\r\n\t\t\t\t\t\t.replace(/&lt;/g, '<')\r\n\t\t\t\t\t\t.replace(/&gt;/g, '>');\r\n\r\n\t\t\t\t\t// Nested tags aren't supported, and cause crash in Safari (#1596)\r\n\t\t\t\t\tif (span !== ' ') {\r\n\r\n\t\t\t\t\t\t// add the text node\r\n\t\t\t\t\t\ttspan.appendChild(doc.createTextNode(span));\r\n\r\n\t\t\t\t\t\tif (!spanNo) { // first span in a line, align it to the left\r\n\t\t\t\t\t\t\tattributes.x = parentX;\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\tattributes.dx = 0; // #16\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t// add attributes\r\n\t\t\t\t\t\tattr(tspan, attributes);\r\n\r\n\t\t\t\t\t\t// first span on subsequent line, add the line height\r\n\t\t\t\t\t\tif (!spanNo && lineNo) {\r\n\r\n\t\t\t\t\t\t\t// allow getting the right offset height in exporting in IE\r\n\t\t\t\t\t\t\tif (!hasSVG && forExport) {\r\n\t\t\t\t\t\t\t\tcss(tspan, { display: 'block' });\r\n\t\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t\t// Set the line height based on the font size of either\r\n\t\t\t\t\t\t\t// the text element or the tspan element\r\n\t\t\t\t\t\t\tattr(\r\n\t\t\t\t\t\t\t\ttspan,\r\n\t\t\t\t\t\t\t\t'dy',\r\n\t\t\t\t\t\t\t\ttextLineHeight || renderer.fontMetrics(\r\n\t\t\t\t\t\t\t\t\t/px$/.test(tspan.style.fontSize) ?\r\n\t\t\t\t\t\t\t\t\t\ttspan.style.fontSize :\r\n\t\t\t\t\t\t\t\t\t\ttextStyles.fontSize\r\n\t\t\t\t\t\t\t\t).h,\r\n\t\t\t\t\t\t\t\t// Safari 6.0.2 - too optimized for its own good (#1539)\r\n\t\t\t\t\t\t\t\t// TODO: revisit this with future versions of Safari\r\n\t\t\t\t\t\t\t\tisWebKit && tspan.offsetHeight\r\n\t\t\t\t\t\t\t);\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t// Append it\r\n\t\t\t\t\t\ttextNode.appendChild(tspan);\r\n\r\n\t\t\t\t\t\tspanNo++;\r\n\r\n\t\t\t\t\t\t// check width and apply soft breaks\r\n\t\t\t\t\t\tif (width) {\r\n\t\t\t\t\t\t\tvar words = span.replace(/([^\\^])-/g, '$1- ').split(' '), // #1273\r\n\t\t\t\t\t\t\t\ttooLong,\r\n\t\t\t\t\t\t\t\tactualWidth,\r\n\t\t\t\t\t\t\t\tclipHeight = wrapper._clipHeight,\r\n\t\t\t\t\t\t\t\trest = [],\r\n\t\t\t\t\t\t\t\tdy = pInt(textLineHeight || 16),\r\n\t\t\t\t\t\t\t\tsoftLineNo = 1,\r\n\t\t\t\t\t\t\t\tbBox;\r\n\r\n\t\t\t\t\t\t\twhile (words.length || rest.length) {\r\n\t\t\t\t\t\t\t\tdelete wrapper.bBox; // delete cache\r\n\t\t\t\t\t\t\t\tbBox = wrapper.getBBox();\r\n\t\t\t\t\t\t\t\tactualWidth = bBox.width;\r\n\t\t\t\t\t\t\t\ttooLong = actualWidth > width;\r\n\t\t\t\t\t\t\t\tif (!tooLong || words.length === 1) { // new line needed\r\n\t\t\t\t\t\t\t\t\twords = rest;\r\n\t\t\t\t\t\t\t\t\trest = [];\r\n\t\t\t\t\t\t\t\t\tif (words.length) {\r\n\t\t\t\t\t\t\t\t\t\tsoftLineNo++;\r\n\r\n\t\t\t\t\t\t\t\t\t\tif (clipHeight && softLineNo * dy > clipHeight) {\r\n\t\t\t\t\t\t\t\t\t\t\twords = ['...'];\r\n\t\t\t\t\t\t\t\t\t\t\twrapper.attr('title', wrapper.textStr);\r\n\t\t\t\t\t\t\t\t\t\t} else {\r\n\r\n\t\t\t\t\t\t\t\t\t\t\ttspan = doc.createElementNS(SVG_NS, 'tspan');\r\n\t\t\t\t\t\t\t\t\t\t\tattr(tspan, {\r\n\t\t\t\t\t\t\t\t\t\t\t\tdy: dy,\r\n\t\t\t\t\t\t\t\t\t\t\t\tx: parentX\r\n\t\t\t\t\t\t\t\t\t\t\t});\r\n\t\t\t\t\t\t\t\t\t\t\tif (spanStyle) { // #390\r\n\t\t\t\t\t\t\t\t\t\t\t\tattr(tspan, 'style', spanStyle);\r\n\t\t\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\t\t\ttextNode.appendChild(tspan);\r\n\r\n\t\t\t\t\t\t\t\t\t\t\tif (actualWidth > width) { // a single word is pressing it out\r\n\t\t\t\t\t\t\t\t\t\t\t\twidth = actualWidth;\r\n\t\t\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t} else { // append to existing line tspan\r\n\t\t\t\t\t\t\t\t\ttspan.removeChild(tspan.firstChild);\r\n\t\t\t\t\t\t\t\t\trest.unshift(words.pop());\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\tif (words.length) {\r\n\t\t\t\t\t\t\t\t\ttspan.appendChild(doc.createTextNode(words.join(' ').replace(/- /g, '-')));\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t});\r\n\t},\r\n\r\n\t/**\r\n\t * Create a button with preset states\r\n\t * @param {String} text\r\n\t * @param {Number} x\r\n\t * @param {Number} y\r\n\t * @param {Function} callback\r\n\t * @param {Object} normalState\r\n\t * @param {Object} hoverState\r\n\t * @param {Object} pressedState\r\n\t */\r\n\tbutton: function (text, x, y, callback, normalState, hoverState, pressedState, disabledState) {\r\n\t\tvar label = this.label(text, x, y, null, null, null, null, null, 'button'),\r\n\t\t\tcurState = 0,\r\n\t\t\tstateOptions,\r\n\t\t\tstateStyle,\r\n\t\t\tnormalStyle,\r\n\t\t\thoverStyle,\r\n\t\t\tpressedStyle,\r\n\t\t\tdisabledStyle,\r\n\t\t\tSTYLE = 'style',\r\n\t\t\tverticalGradient = { x1: 0, y1: 0, x2: 0, y2: 1 };\r\n\r\n\t\t// Normal state - prepare the attributes\r\n\t\tnormalState = merge({\r\n\t\t\t'stroke-width': 1,\r\n\t\t\tstroke: '#CCCCCC',\r\n\t\t\tfill: {\r\n\t\t\t\tlinearGradient: verticalGradient,\r\n\t\t\t\tstops: [\r\n\t\t\t\t\t[0, '#FEFEFE'],\r\n\t\t\t\t\t[1, '#F6F6F6']\r\n\t\t\t\t]\r\n\t\t\t},\r\n\t\t\tr: 2,\r\n\t\t\tpadding: 5,\r\n\t\t\tstyle: {\r\n\t\t\t\tcolor: 'black'\r\n\t\t\t}\r\n\t\t}, normalState);\r\n\t\tnormalStyle = normalState[STYLE];\r\n\t\tdelete normalState[STYLE];\r\n\r\n\t\t// Hover state\r\n\t\thoverState = merge(normalState, {\r\n\t\t\tstroke: '#68A',\r\n\t\t\tfill: {\r\n\t\t\t\tlinearGradient: verticalGradient,\r\n\t\t\t\tstops: [\r\n\t\t\t\t\t[0, '#FFF'],\r\n\t\t\t\t\t[1, '#ACF']\r\n\t\t\t\t]\r\n\t\t\t}\r\n\t\t}, hoverState);\r\n\t\thoverStyle = hoverState[STYLE];\r\n\t\tdelete hoverState[STYLE];\r\n\r\n\t\t// Pressed state\r\n\t\tpressedState = merge(normalState, {\r\n\t\t\tstroke: '#68A',\r\n\t\t\tfill: {\r\n\t\t\t\tlinearGradient: verticalGradient,\r\n\t\t\t\tstops: [\r\n\t\t\t\t\t[0, '#9BD'],\r\n\t\t\t\t\t[1, '#CDF']\r\n\t\t\t\t]\r\n\t\t\t}\r\n\t\t}, pressedState);\r\n\t\tpressedStyle = pressedState[STYLE];\r\n\t\tdelete pressedState[STYLE];\r\n\r\n\t\t// Disabled state\r\n\t\tdisabledState = merge(normalState, {\r\n\t\t\tstyle: {\r\n\t\t\t\tcolor: '#CCC'\r\n\t\t\t}\r\n\t\t}, disabledState);\r\n\t\tdisabledStyle = disabledState[STYLE];\r\n\t\tdelete disabledState[STYLE];\r\n\r\n\t\t// Add the events. IE9 and IE10 need mouseover and mouseout to funciton (#667).\r\n\t\taddEvent(label.element, isIE ? 'mouseover' : 'mouseenter', function () {\r\n\t\t\tif (curState !== 3) {\r\n\t\t\t\tlabel.attr(hoverState)\r\n\t\t\t\t\t.css(hoverStyle);\r\n\t\t\t}\r\n\t\t});\r\n\t\taddEvent(label.element, isIE ? 'mouseout' : 'mouseleave', function () {\r\n\t\t\tif (curState !== 3) {\r\n\t\t\t\tstateOptions = [normalState, hoverState, pressedState][curState];\r\n\t\t\t\tstateStyle = [normalStyle, hoverStyle, pressedStyle][curState];\r\n\t\t\t\tlabel.attr(stateOptions)\r\n\t\t\t\t\t.css(stateStyle);\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\tlabel.setState = function (state) {\r\n\t\t\tlabel.state = curState = state;\r\n\t\t\tif (!state) {\r\n\t\t\t\tlabel.attr(normalState)\r\n\t\t\t\t\t.css(normalStyle);\r\n\t\t\t} else if (state === 2) {\r\n\t\t\t\tlabel.attr(pressedState)\r\n\t\t\t\t\t.css(pressedStyle);\r\n\t\t\t} else if (state === 3) {\r\n\t\t\t\tlabel.attr(disabledState)\r\n\t\t\t\t\t.css(disabledStyle);\r\n\t\t\t}\r\n\t\t};\r\n\r\n\t\treturn label\r\n\t\t\t.on('click', function () {\r\n\t\t\t\tif (curState !== 3) {\r\n\t\t\t\t\tcallback.call(label);\r\n\t\t\t\t}\r\n\t\t\t})\r\n\t\t\t.attr(normalState)\r\n\t\t\t.css(extend({ cursor: 'default' }, normalStyle));\r\n\t},\r\n\r\n\t/**\r\n\t * Make a straight line crisper by not spilling out to neighbour pixels\r\n\t * @param {Array} points\r\n\t * @param {Number} width\r\n\t */\r\n\tcrispLine: function (points, width) {\r\n\t\t// points format: [M, 0, 0, L, 100, 0]\r\n\t\t// normalize to a crisp line\r\n\t\tif (points[1] === points[4]) {\r\n\t\t\t// Substract due to #1129. Now bottom and left axis gridlines behave the same.\r\n\t\t\tpoints[1] = points[4] = mathRound(points[1]) - (width % 2 / 2);\r\n\t\t}\r\n\t\tif (points[2] === points[5]) {\r\n\t\t\tpoints[2] = points[5] = mathRound(points[2]) + (width % 2 / 2);\r\n\t\t}\r\n\t\treturn points;\r\n\t},\r\n\r\n\r\n\t/**\r\n\t * Draw a path\r\n\t * @param {Array} path An SVG path in array form\r\n\t */\r\n\tpath: function (path) {\r\n\t\tvar attr = {\r\n\t\t\tfill: NONE\r\n\t\t};\r\n\t\tif (isArray(path)) {\r\n\t\t\tattr.d = path;\r\n\t\t} else if (isObject(path)) { // attributes\r\n\t\t\textend(attr, path);\r\n\t\t}\r\n\t\treturn this.createElement('path').attr(attr);\r\n\t},\r\n\r\n\t/**\r\n\t * Draw and return an SVG circle\r\n\t * @param {Number} x The x position\r\n\t * @param {Number} y The y position\r\n\t * @param {Number} r The radius\r\n\t */\r\n\tcircle: function (x, y, r) {\r\n\t\tvar attr = isObject(x) ?\r\n\t\t\tx :\r\n\t\t\t{\r\n\t\t\t\tx: x,\r\n\t\t\t\ty: y,\r\n\t\t\t\tr: r\r\n\t\t\t};\r\n\r\n\t\treturn this.createElement('circle').attr(attr);\r\n\t},\r\n\r\n\t/**\r\n\t * Draw and return an arc\r\n\t * @param {Number} x X position\r\n\t * @param {Number} y Y position\r\n\t * @param {Number} r Radius\r\n\t * @param {Number} innerR Inner radius like used in donut charts\r\n\t * @param {Number} start Starting angle\r\n\t * @param {Number} end Ending angle\r\n\t */\r\n\tarc: function (x, y, r, innerR, start, end) {\r\n\t\tvar arc;\r\n\r\n\t\tif (isObject(x)) {\r\n\t\t\ty = x.y;\r\n\t\t\tr = x.r;\r\n\t\t\tinnerR = x.innerR;\r\n\t\t\tstart = x.start;\r\n\t\t\tend = x.end;\r\n\t\t\tx = x.x;\r\n\t\t}\r\n\r\n\t\t// Arcs are defined as symbols for the ability to set\r\n\t\t// attributes in attr and animate\r\n\t\tarc = this.symbol('arc', x || 0, y || 0, r || 0, r || 0, {\r\n\t\t\tinnerR: innerR || 0,\r\n\t\t\tstart: start || 0,\r\n\t\t\tend: end || 0\r\n\t\t});\r\n\t\tarc.r = r; // #959\r\n\t\treturn arc;\r\n\t},\r\n\r\n\t/**\r\n\t * Draw and return a rectangle\r\n\t * @param {Number} x Left position\r\n\t * @param {Number} y Top position\r\n\t * @param {Number} width\r\n\t * @param {Number} height\r\n\t * @param {Number} r Border corner radius\r\n\t * @param {Number} strokeWidth A stroke width can be supplied to allow crisp drawing\r\n\t */\r\n\trect: function (x, y, width, height, r, strokeWidth) {\r\n\r\n\t\tr = isObject(x) ? x.r : r;\r\n\r\n\t\tvar wrapper = this.createElement('rect').attr({\r\n\t\t\t\trx: r,\r\n\t\t\t\try: r,\r\n\t\t\t\tfill: NONE\r\n\t\t\t});\r\n\t\treturn wrapper.attr(\r\n\t\t\t\tisObject(x) ?\r\n\t\t\t\t\tx :\r\n\t\t\t\t\t// do not crispify when an object is passed in (as in column charts)\r\n\t\t\t\t\twrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0))\r\n\t\t\t);\r\n\t},\r\n\r\n\t/**\r\n\t * Resize the box and re-align all aligned elements\r\n\t * @param {Object} width\r\n\t * @param {Object} height\r\n\t * @param {Boolean} animate\r\n\t *\r\n\t */\r\n\tsetSize: function (width, height, animate) {\r\n\t\tvar renderer = this,\r\n\t\t\talignedObjects = renderer.alignedObjects,\r\n\t\t\ti = alignedObjects.length;\r\n\r\n\t\trenderer.width = width;\r\n\t\trenderer.height = height;\r\n\r\n\t\trenderer.boxWrapper[pick(animate, true) ? 'animate' : 'attr']({\r\n\t\t\twidth: width,\r\n\t\t\theight: height\r\n\t\t});\r\n\r\n\t\twhile (i--) {\r\n\t\t\talignedObjects[i].align();\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Create a group\r\n\t * @param {String} name The group will be given a class name of 'highcharts-{name}'.\r\n\t *     This can be used for styling and scripting.\r\n\t */\r\n\tg: function (name) {\r\n\t\tvar elem = this.createElement('g');\r\n\t\treturn defined(name) ? elem.attr({ 'class': PREFIX + name }) : elem;\r\n\t},\r\n\r\n\t/**\r\n\t * Display an image\r\n\t * @param {String} src\r\n\t * @param {Number} x\r\n\t * @param {Number} y\r\n\t * @param {Number} width\r\n\t * @param {Number} height\r\n\t */\r\n\timage: function (src, x, y, width, height) {\r\n\t\tvar attribs = {\r\n\t\t\t\tpreserveAspectRatio: NONE\r\n\t\t\t},\r\n\t\t\telemWrapper;\r\n\r\n\t\t// optional properties\r\n\t\tif (arguments.length > 1) {\r\n\t\t\textend(attribs, {\r\n\t\t\t\tx: x,\r\n\t\t\t\ty: y,\r\n\t\t\t\twidth: width,\r\n\t\t\t\theight: height\r\n\t\t\t});\r\n\t\t}\r\n\r\n\t\telemWrapper = this.createElement('image').attr(attribs);\r\n\r\n\t\t// set the href in the xlink namespace\r\n\t\tif (elemWrapper.element.setAttributeNS) {\r\n\t\t\telemWrapper.element.setAttributeNS('http://www.w3.org/1999/xlink',\r\n\t\t\t\t'href', src);\r\n\t\t} else {\r\n\t\t\t// could be exporting in IE\r\n\t\t\t// using href throws \"not supported\" in ie7 and under, requries regex shim to fix later\r\n\t\t\telemWrapper.element.setAttribute('hc-svg-href', src);\r\n\t}\r\n\r\n\t\treturn elemWrapper;\r\n\t},\r\n\r\n\t/**\r\n\t * Draw a symbol out of pre-defined shape paths from the namespace 'symbol' object.\r\n\t *\r\n\t * @param {Object} symbol\r\n\t * @param {Object} x\r\n\t * @param {Object} y\r\n\t * @param {Object} radius\r\n\t * @param {Object} options\r\n\t */\r\n\tsymbol: function (symbol, x, y, width, height, options) {\r\n\r\n\t\tvar obj,\r\n\r\n\t\t\t// get the symbol definition function\r\n\t\t\tsymbolFn = this.symbols[symbol],\r\n\r\n\t\t\t// check if there's a path defined for this symbol\r\n\t\t\tpath = symbolFn && symbolFn(\r\n\t\t\t\tmathRound(x),\r\n\t\t\t\tmathRound(y),\r\n\t\t\t\twidth,\r\n\t\t\t\theight,\r\n\t\t\t\toptions\r\n\t\t\t),\r\n\r\n\t\t\timageElement,\r\n\t\t\timageRegex = /^url\\((.*?)\\)$/,\r\n\t\t\timageSrc,\r\n\t\t\timageSize,\r\n\t\t\tcenterImage;\r\n\r\n\t\tif (path) {\r\n\r\n\t\t\tobj = this.path(path);\r\n\t\t\t// expando properties for use in animate and attr\r\n\t\t\textend(obj, {\r\n\t\t\t\tsymbolName: symbol,\r\n\t\t\t\tx: x,\r\n\t\t\t\ty: y,\r\n\t\t\t\twidth: width,\r\n\t\t\t\theight: height\r\n\t\t\t});\r\n\t\t\tif (options) {\r\n\t\t\t\textend(obj, options);\r\n\t\t\t}\r\n\r\n\r\n\t\t// image symbols\r\n\t\t} else if (imageRegex.test(symbol)) {\r\n\r\n\t\t\t// On image load, set the size and position\r\n\t\t\tcenterImage = function (img, size) {\r\n\t\t\t\tif (img.element) { // it may be destroyed in the meantime (#1390)\r\n\t\t\t\t\timg.attr({\r\n\t\t\t\t\t\twidth: size[0],\r\n\t\t\t\t\t\theight: size[1]\r\n\t\t\t\t\t});\r\n\r\n\t\t\t\t\tif (!img.alignByTranslate) { // #185\r\n\t\t\t\t\t\timg.translate(\r\n\t\t\t\t\t\t\tmathRound((width - size[0]) / 2), // #1378\r\n\t\t\t\t\t\t\tmathRound((height - size[1]) / 2)\r\n\t\t\t\t\t\t);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t};\r\n\r\n\t\t\timageSrc = symbol.match(imageRegex)[1];\r\n\t\t\timageSize = symbolSizes[imageSrc];\r\n\r\n\t\t\t// Ireate the image synchronously, add attribs async\r\n\t\t\tobj = this.image(imageSrc)\r\n\t\t\t\t.attr({\r\n\t\t\t\t\tx: x,\r\n\t\t\t\t\ty: y\r\n\t\t\t\t});\r\n\t\t\tobj.isImg = true;\r\n\r\n\t\t\tif (imageSize) {\r\n\t\t\t\tcenterImage(obj, imageSize);\r\n\t\t\t} else {\r\n\t\t\t\t// Initialize image to be 0 size so export will still function if there's no cached sizes.\r\n\t\t\t\t//\r\n\t\t\t\tobj.attr({ width: 0, height: 0 });\r\n\r\n\t\t\t\t// Create a dummy JavaScript image to get the width and height. Due to a bug in IE < 8,\r\n\t\t\t\t// the created element must be assigned to a variable in order to load (#292).\r\n\t\t\t\timageElement = createElement('img', {\r\n\t\t\t\t\tonload: function () {\r\n\t\t\t\t\t\tcenterImage(obj, symbolSizes[imageSrc] = [this.width, this.height]);\r\n\t\t\t\t\t},\r\n\t\t\t\t\tsrc: imageSrc\r\n\t\t\t\t});\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn obj;\r\n\t},\r\n\r\n\t/**\r\n\t * An extendable collection of functions for defining symbol paths.\r\n\t */\r\n\tsymbols: {\r\n\t\t'circle': function (x, y, w, h) {\r\n\t\t\tvar cpw = 0.166 * w;\r\n\t\t\treturn [\r\n\t\t\t\tM, x + w / 2, y,\r\n\t\t\t\t'C', x + w + cpw, y, x + w + cpw, y + h, x + w / 2, y + h,\r\n\t\t\t\t'C', x - cpw, y + h, x - cpw, y, x + w / 2, y,\r\n\t\t\t\t'Z'\r\n\t\t\t];\r\n\t\t},\r\n\r\n\t\t'square': function (x, y, w, h) {\r\n\t\t\treturn [\r\n\t\t\t\tM, x, y,\r\n\t\t\t\tL, x + w, y,\r\n\t\t\t\tx + w, y + h,\r\n\t\t\t\tx, y + h,\r\n\t\t\t\t'Z'\r\n\t\t\t];\r\n\t\t},\r\n\r\n\t\t'triangle': function (x, y, w, h) {\r\n\t\t\treturn [\r\n\t\t\t\tM, x + w / 2, y,\r\n\t\t\t\tL, x + w, y + h,\r\n\t\t\t\tx, y + h,\r\n\t\t\t\t'Z'\r\n\t\t\t];\r\n\t\t},\r\n\r\n\t\t'triangle-down': function (x, y, w, h) {\r\n\t\t\treturn [\r\n\t\t\t\tM, x, y,\r\n\t\t\t\tL, x + w, y,\r\n\t\t\t\tx + w / 2, y + h,\r\n\t\t\t\t'Z'\r\n\t\t\t];\r\n\t\t},\r\n\t\t'diamond': function (x, y, w, h) {\r\n\t\t\treturn [\r\n\t\t\t\tM, x + w / 2, y,\r\n\t\t\t\tL, x + w, y + h / 2,\r\n\t\t\t\tx + w / 2, y + h,\r\n\t\t\t\tx, y + h / 2,\r\n\t\t\t\t'Z'\r\n\t\t\t];\r\n\t\t},\r\n\t\t'arc': function (x, y, w, h, options) {\r\n\t\t\tvar start = options.start,\r\n\t\t\t\tradius = options.r || w || h,\r\n\t\t\t\tend = options.end - 0.001, // to prevent cos and sin of start and end from becoming equal on 360 arcs (related: #1561)\r\n\t\t\t\tinnerRadius = options.innerR,\r\n\t\t\t\topen = options.open,\r\n\t\t\t\tcosStart = mathCos(start),\r\n\t\t\t\tsinStart = mathSin(start),\r\n\t\t\t\tcosEnd = mathCos(end),\r\n\t\t\t\tsinEnd = mathSin(end),\r\n\t\t\t\tlongArc = options.end - start < mathPI ? 0 : 1;\r\n\r\n\t\t\treturn [\r\n\t\t\t\tM,\r\n\t\t\t\tx + radius * cosStart,\r\n\t\t\t\ty + radius * sinStart,\r\n\t\t\t\t'A', // arcTo\r\n\t\t\t\tradius, // x radius\r\n\t\t\t\tradius, // y radius\r\n\t\t\t\t0, // slanting\r\n\t\t\t\tlongArc, // long or short arc\r\n\t\t\t\t1, // clockwise\r\n\t\t\t\tx + radius * cosEnd,\r\n\t\t\t\ty + radius * sinEnd,\r\n\t\t\t\topen ? M : L,\r\n\t\t\t\tx + innerRadius * cosEnd,\r\n\t\t\t\ty + innerRadius * sinEnd,\r\n\t\t\t\t'A', // arcTo\r\n\t\t\t\tinnerRadius, // x radius\r\n\t\t\t\tinnerRadius, // y radius\r\n\t\t\t\t0, // slanting\r\n\t\t\t\tlongArc, // long or short arc\r\n\t\t\t\t0, // clockwise\r\n\t\t\t\tx + innerRadius * cosStart,\r\n\t\t\t\ty + innerRadius * sinStart,\r\n\r\n\t\t\t\topen ? '' : 'Z' // close\r\n\t\t\t];\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Define a clipping rectangle\r\n\t * @param {String} id\r\n\t * @param {Number} x\r\n\t * @param {Number} y\r\n\t * @param {Number} width\r\n\t * @param {Number} height\r\n\t */\r\n\tclipRect: function (x, y, width, height) {\r\n\t\tvar wrapper,\r\n\t\t\tid = PREFIX + idCounter++,\r\n\r\n\t\t\tclipPath = this.createElement('clipPath').attr({\r\n\t\t\t\tid: id\r\n\t\t\t}).add(this.defs);\r\n\r\n\t\twrapper = this.rect(x, y, width, height, 0).add(clipPath);\r\n\t\twrapper.id = id;\r\n\t\twrapper.clipPath = clipPath;\r\n\r\n\t\treturn wrapper;\r\n\t},\r\n\r\n\r\n\t/**\r\n\t * Take a color and return it if it's a string, make it a gradient if it's a\r\n\t * gradient configuration object. Prior to Highstock, an array was used to define\r\n\t * a linear gradient with pixel positions relative to the SVG. In newer versions\r\n\t * we change the coordinates to apply relative to the shape, using coordinates\r\n\t * 0-1 within the shape. To preserve backwards compatibility, linearGradient\r\n\t * in this definition is an object of x1, y1, x2 and y2.\r\n\t *\r\n\t * @param {Object} color The color or config object\r\n\t */\r\n\tcolor: function (color, elem, prop) {\r\n\t\tvar renderer = this,\r\n\t\t\tcolorObject,\r\n\t\t\tregexRgba = /^rgba/,\r\n\t\t\tgradName,\r\n\t\t\tgradAttr,\r\n\t\t\tgradients,\r\n\t\t\tgradientObject,\r\n\t\t\tstops,\r\n\t\t\tstopColor,\r\n\t\t\tstopOpacity,\r\n\t\t\tradialReference,\r\n\t\t\tn,\r\n\t\t\tid,\r\n\t\t\tkey = [];\r\n\r\n\t\t// Apply linear or radial gradients\r\n\t\tif (color && color.linearGradient) {\r\n\t\t\tgradName = 'linearGradient';\r\n\t\t} else if (color && color.radialGradient) {\r\n\t\t\tgradName = 'radialGradient';\r\n\t\t}\r\n\r\n\t\tif (gradName) {\r\n\t\t\tgradAttr = color[gradName];\r\n\t\t\tgradients = renderer.gradients;\r\n\t\t\tstops = color.stops;\r\n\t\t\tradialReference = elem.radialReference;\r\n\r\n\t\t\t// Keep < 2.2 kompatibility\r\n\t\t\tif (isArray(gradAttr)) {\r\n\t\t\t\tcolor[gradName] = gradAttr = {\r\n\t\t\t\t\tx1: gradAttr[0],\r\n\t\t\t\t\ty1: gradAttr[1],\r\n\t\t\t\t\tx2: gradAttr[2],\r\n\t\t\t\t\ty2: gradAttr[3],\r\n\t\t\t\t\tgradientUnits: 'userSpaceOnUse'\r\n\t\t\t\t};\r\n\t\t\t}\r\n\r\n\t\t\t// Correct the radial gradient for the radial reference system\r\n\t\t\tif (gradName === 'radialGradient' && radialReference && !defined(gradAttr.gradientUnits)) {\r\n\t\t\t\tgradAttr = merge(gradAttr, {\r\n\t\t\t\t\tcx: (radialReference[0] - radialReference[2] / 2) + gradAttr.cx * radialReference[2],\r\n\t\t\t\t\tcy: (radialReference[1] - radialReference[2] / 2) + gradAttr.cy * radialReference[2],\r\n\t\t\t\t\tr: gradAttr.r * radialReference[2],\r\n\t\t\t\t\tgradientUnits: 'userSpaceOnUse'\r\n\t\t\t\t});\r\n\t\t\t}\r\n\r\n\t\t\t// Build the unique key to detect whether we need to create a new element (#1282)\r\n\t\t\tfor (n in gradAttr) {\r\n\t\t\t\tif (n !== 'id') {\r\n\t\t\t\t\tkey.push(n, gradAttr[n]);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tfor (n in stops) {\r\n\t\t\t\tkey.push(stops[n]);\r\n\t\t\t}\r\n\t\t\tkey = key.join(',');\r\n\r\n\t\t\t// Check if a gradient object with the same config object is created within this renderer\r\n\t\t\tif (gradients[key]) {\r\n\t\t\t\tid = gradients[key].id;\r\n\r\n\t\t\t} else {\r\n\r\n\t\t\t\t// Set the id and create the element\r\n\t\t\t\tgradAttr.id = id = PREFIX + idCounter++;\r\n\t\t\t\tgradients[key] = gradientObject = renderer.createElement(gradName)\r\n\t\t\t\t\t.attr(gradAttr)\r\n\t\t\t\t\t.add(renderer.defs);\r\n\r\n\r\n\t\t\t\t// The gradient needs to keep a list of stops to be able to destroy them\r\n\t\t\t\tgradientObject.stops = [];\r\n\t\t\t\teach(stops, function (stop) {\r\n\t\t\t\t\tvar stopObject;\r\n\t\t\t\t\tif (regexRgba.test(stop[1])) {\r\n\t\t\t\t\t\tcolorObject = Color(stop[1]);\r\n\t\t\t\t\t\tstopColor = colorObject.get('rgb');\r\n\t\t\t\t\t\tstopOpacity = colorObject.get('a');\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tstopColor = stop[1];\r\n\t\t\t\t\t\tstopOpacity = 1;\r\n\t\t\t\t\t}\r\n\t\t\t\t\tstopObject = renderer.createElement('stop').attr({\r\n\t\t\t\t\t\toffset: stop[0],\r\n\t\t\t\t\t\t'stop-color': stopColor,\r\n\t\t\t\t\t\t'stop-opacity': stopOpacity\r\n\t\t\t\t\t}).add(gradientObject);\r\n\r\n\t\t\t\t\t// Add the stop element to the gradient\r\n\t\t\t\t\tgradientObject.stops.push(stopObject);\r\n\t\t\t\t});\r\n\t\t\t}\r\n\r\n\t\t\t// Return the reference to the gradient object\r\n\t\t\treturn 'url(' + renderer.url + '#' + id + ')';\r\n\r\n\t\t// Webkit and Batik can't show rgba.\r\n\t\t} else if (regexRgba.test(color)) {\r\n\t\t\tcolorObject = Color(color);\r\n\t\t\tattr(elem, prop + '-opacity', colorObject.get('a'));\r\n\r\n\t\t\treturn colorObject.get('rgb');\r\n\r\n\r\n\t\t} else {\r\n\t\t\t// Remove the opacity attribute added above. Does not throw if the attribute is not there.\r\n\t\t\telem.removeAttribute(prop + '-opacity');\r\n\r\n\t\t\treturn color;\r\n\t\t}\r\n\r\n\t},\r\n\r\n\r\n\t/**\r\n\t * Add text to the SVG object\r\n\t * @param {String} str\r\n\t * @param {Number} x Left position\r\n\t * @param {Number} y Top position\r\n\t * @param {Boolean} useHTML Use HTML to render the text\r\n\t */\r\n\ttext: function (str, x, y, useHTML) {\r\n\r\n\t\t// declare variables\r\n\t\tvar renderer = this,\r\n\t\t\tdefaultChartStyle = defaultOptions.chart.style,\r\n\t\t\tfakeSVG = useCanVG || (!hasSVG && renderer.forExport),\r\n\t\t\twrapper;\r\n\r\n\t\tif (useHTML && !renderer.forExport) {\r\n\t\t\treturn renderer.html(str, x, y);\r\n\t\t}\r\n\r\n\t\tx = mathRound(pick(x, 0));\r\n\t\ty = mathRound(pick(y, 0));\r\n\r\n\t\twrapper = renderer.createElement('text')\r\n\t\t\t.attr({\r\n\t\t\t\tx: x,\r\n\t\t\t\ty: y,\r\n\t\t\t\ttext: str\r\n\t\t\t})\r\n\t\t\t.css({\r\n\t\t\t\tfontFamily: defaultChartStyle.fontFamily,\r\n\t\t\t\tfontSize: defaultChartStyle.fontSize\r\n\t\t\t});\r\n\r\n\t\t// Prevent wrapping from creating false offsetWidths in export in legacy IE (#1079, #1063)\r\n\t\tif (fakeSVG) {\r\n\t\t\twrapper.css({\r\n\t\t\t\tposition: ABSOLUTE\r\n\t\t\t});\r\n\t\t}\r\n\r\n\t\twrapper.x = x;\r\n\t\twrapper.y = y;\r\n\t\treturn wrapper;\r\n\t},\r\n\r\n\r\n\t/**\r\n\t * Create HTML text node. This is used by the VML renderer as well as the SVG\r\n\t * renderer through the useHTML option.\r\n\t *\r\n\t * @param {String} str\r\n\t * @param {Number} x\r\n\t * @param {Number} y\r\n\t */\r\n\thtml: function (str, x, y) {\r\n\t\tvar defaultChartStyle = defaultOptions.chart.style,\r\n\t\t\twrapper = this.createElement('span'),\r\n\t\t\tattrSetters = wrapper.attrSetters,\r\n\t\t\telement = wrapper.element,\r\n\t\t\trenderer = wrapper.renderer;\r\n\r\n\t\t// Text setter\r\n\t\tattrSetters.text = function (value) {\r\n\t\t\tif (value !== element.innerHTML) {\r\n\t\t\t\tdelete this.bBox;\r\n\t\t\t}\r\n\t\t\telement.innerHTML = value;\r\n\t\t\treturn false;\r\n\t\t};\r\n\r\n\t\t// Various setters which rely on update transform\r\n\t\tattrSetters.x = attrSetters.y = attrSetters.align = function (value, key) {\r\n\t\t\tif (key === 'align') {\r\n\t\t\t\tkey = 'textAlign'; // Do not overwrite the SVGElement.align method. Same as VML.\r\n\t\t\t}\r\n\t\t\twrapper[key] = value;\r\n\t\t\twrapper.htmlUpdateTransform();\r\n\t\t\treturn false;\r\n\t\t};\r\n\r\n\t\t// Set the default attributes\r\n\t\twrapper.attr({\r\n\t\t\t\ttext: str,\r\n\t\t\t\tx: mathRound(x),\r\n\t\t\t\ty: mathRound(y)\r\n\t\t\t})\r\n\t\t\t.css({\r\n\t\t\t\tposition: ABSOLUTE,\r\n\t\t\t\twhiteSpace: 'nowrap',\r\n\t\t\t\tfontFamily: defaultChartStyle.fontFamily,\r\n\t\t\t\tfontSize: defaultChartStyle.fontSize\r\n\t\t\t});\r\n\r\n\t\t// Use the HTML specific .css method\r\n\t\twrapper.css = wrapper.htmlCss;\r\n\r\n\t\t// This is specific for HTML within SVG\r\n\t\tif (renderer.isSVG) {\r\n\t\t\twrapper.add = function (svgGroupWrapper) {\r\n\r\n\t\t\t\tvar htmlGroup,\r\n\t\t\t\t\tcontainer = renderer.box.parentNode,\r\n\t\t\t\t\tparentGroup,\r\n\t\t\t\t\tparents = [];\r\n\r\n\t\t\t\t// Create a mock group to hold the HTML elements\r\n\t\t\t\tif (svgGroupWrapper) {\r\n\t\t\t\t\thtmlGroup = svgGroupWrapper.div;\r\n\t\t\t\t\tif (!htmlGroup) {\r\n\r\n\t\t\t\t\t\t// Read the parent chain into an array and read from top down\r\n\t\t\t\t\t\tparentGroup = svgGroupWrapper;\r\n\t\t\t\t\t\twhile (parentGroup) {\r\n\r\n\t\t\t\t\t\t\tparents.push(parentGroup);\r\n\r\n\t\t\t\t\t\t\t// Move up to the next parent group\r\n\t\t\t\t\t\t\tparentGroup = parentGroup.parentGroup;\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t// Ensure dynamically updating position when any parent is translated\r\n\t\t\t\t\t\teach(parents.reverse(), function (parentGroup) {\r\n\t\t\t\t\t\t\tvar htmlGroupStyle;\r\n\r\n\t\t\t\t\t\t\t// Create a HTML div and append it to the parent div to emulate\r\n\t\t\t\t\t\t\t// the SVG group structure\r\n\t\t\t\t\t\t\thtmlGroup = parentGroup.div = parentGroup.div || createElement(DIV, {\r\n\t\t\t\t\t\t\t\tclassName: attr(parentGroup.element, 'class')\r\n\t\t\t\t\t\t\t}, {\r\n\t\t\t\t\t\t\t\tposition: ABSOLUTE,\r\n\t\t\t\t\t\t\t\tleft: (parentGroup.translateX || 0) + PX,\r\n\t\t\t\t\t\t\t\ttop: (parentGroup.translateY || 0) + PX\r\n\t\t\t\t\t\t\t}, htmlGroup || container); // the top group is appended to container\r\n\r\n\t\t\t\t\t\t\t// Shortcut\r\n\t\t\t\t\t\t\thtmlGroupStyle = htmlGroup.style;\r\n\r\n\t\t\t\t\t\t\t// Set listeners to update the HTML div's position whenever the SVG group\r\n\t\t\t\t\t\t\t// position is changed\r\n\t\t\t\t\t\t\textend(parentGroup.attrSetters, {\r\n\t\t\t\t\t\t\t\ttranslateX: function (value) {\r\n\t\t\t\t\t\t\t\t\thtmlGroupStyle.left = value + PX;\r\n\t\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t\t\ttranslateY: function (value) {\r\n\t\t\t\t\t\t\t\t\thtmlGroupStyle.top = value + PX;\r\n\t\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t\t\tvisibility: function (value, key) {\r\n\t\t\t\t\t\t\t\t\thtmlGroupStyle[key] = value;\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t});\r\n\t\t\t\t\t\t});\r\n\r\n\t\t\t\t\t}\r\n\t\t\t\t} else {\r\n\t\t\t\t\thtmlGroup = container;\r\n\t\t\t\t}\r\n\r\n\t\t\t\thtmlGroup.appendChild(element);\r\n\r\n\t\t\t\t// Shared with VML:\r\n\t\t\t\twrapper.added = true;\r\n\t\t\t\tif (wrapper.alignOnAdd) {\r\n\t\t\t\t\twrapper.htmlUpdateTransform();\r\n\t\t\t\t}\r\n\r\n\t\t\t\treturn wrapper;\r\n\t\t\t};\r\n\t\t}\r\n\t\treturn wrapper;\r\n\t},\r\n\r\n\t/**\r\n\t * Utility to return the baseline offset and total line height from the font size\r\n\t */\r\n\tfontMetrics: function (fontSize) {\r\n\t\tfontSize = pInt(fontSize || 11);\r\n\r\n\t\t// Empirical values found by comparing font size and bounding box height.\r\n\t\t// Applies to the default font family. http://jsfiddle.net/highcharts/7xvn7/\r\n\t\tvar lineHeight = fontSize < 24 ? fontSize + 4 : mathRound(fontSize * 1.2),\r\n\t\t\tbaseline = mathRound(lineHeight * 0.8);\r\n\r\n\t\treturn {\r\n\t\t\th: lineHeight,\r\n\t\t\tb: baseline\r\n\t\t};\r\n\t},\r\n\r\n\t/**\r\n\t * Add a label, a text item that can hold a colored or gradient background\r\n\t * as well as a border and shadow.\r\n\t * @param {string} str\r\n\t * @param {Number} x\r\n\t * @param {Number} y\r\n\t * @param {String} shape\r\n\t * @param {Number} anchorX In case the shape has a pointer, like a flag, this is the\r\n\t *    coordinates it should be pinned to\r\n\t * @param {Number} anchorY\r\n\t * @param {Boolean} baseline Whether to position the label relative to the text baseline,\r\n\t *    like renderer.text, or to the upper border of the rectangle.\r\n\t * @param {String} className Class name for the group\r\n\t */\r\n\tlabel: function (str, x, y, shape, anchorX, anchorY, useHTML, baseline, className) {\r\n\r\n\t\tvar renderer = this,\r\n\t\t\twrapper = renderer.g(className),\r\n\t\t\ttext = renderer.text('', 0, 0, useHTML)\r\n\t\t\t\t.attr({\r\n\t\t\t\t\tzIndex: 1\r\n\t\t\t\t}),\r\n\t\t\t\t//.add(wrapper),\r\n\t\t\tbox,\r\n\t\t\tbBox,\r\n\t\t\talignFactor = 0,\r\n\t\t\tpadding = 3,\r\n\t\t\tpaddingLeft = 0,\r\n\t\t\twidth,\r\n\t\t\theight,\r\n\t\t\twrapperX,\r\n\t\t\twrapperY,\r\n\t\t\tcrispAdjust = 0,\r\n\t\t\tdeferredAttr = {},\r\n\t\t\tbaselineOffset,\r\n\t\t\tattrSetters = wrapper.attrSetters,\r\n\t\t\tneedsBox;\r\n\r\n\t\t/**\r\n\t\t * This function runs after the label is added to the DOM (when the bounding box is\r\n\t\t * available), and after the text of the label is updated to detect the new bounding\r\n\t\t * box and reflect it in the border box.\r\n\t\t */\r\n\t\tfunction updateBoxSize() {\r\n\t\t\tvar boxX,\r\n\t\t\t\tboxY,\r\n\t\t\t\tstyle = text.element.style;\r\n\r\n\t\t\tbBox = (width === undefined || height === undefined || wrapper.styles.textAlign) &&\r\n\t\t\t\ttext.getBBox();\r\n\t\t\twrapper.width = (width || bBox.width || 0) + 2 * padding + paddingLeft;\r\n\t\t\twrapper.height = (height || bBox.height || 0) + 2 * padding;\r\n\r\n\t\t\t// update the label-scoped y offset\r\n\t\t\tbaselineOffset = padding + renderer.fontMetrics(style && style.fontSize).b;\r\n\r\n\t\t\tif (needsBox) {\r\n\r\n\t\t\t\t// create the border box if it is not already present\r\n\t\t\t\tif (!box) {\r\n\t\t\t\t\tboxX = mathRound(-alignFactor * padding);\r\n\t\t\t\t\tboxY = baseline ? -baselineOffset : 0;\r\n\r\n\t\t\t\t\twrapper.box = box = shape ?\r\n\t\t\t\t\t\trenderer.symbol(shape, boxX, boxY, wrapper.width, wrapper.height) :\r\n\t\t\t\t\t\trenderer.rect(boxX, boxY, wrapper.width, wrapper.height, 0, deferredAttr[STROKE_WIDTH]);\r\n\t\t\t\t\tbox.add(wrapper);\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// apply the box attributes\r\n\t\t\t\tif (!box.isImg) { // #1630\r\n\t\t\t\t\tbox.attr(merge({\r\n\t\t\t\t\t\twidth: wrapper.width,\r\n\t\t\t\t\t\theight: wrapper.height\r\n\t\t\t\t\t}, deferredAttr));\r\n\t\t\t\t}\r\n\t\t\t\tdeferredAttr = null;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t/**\r\n\t\t * This function runs after setting text or padding, but only if padding is changed\r\n\t\t */\r\n\t\tfunction updateTextPadding() {\r\n\t\t\tvar styles = wrapper.styles,\r\n\t\t\t\ttextAlign = styles && styles.textAlign,\r\n\t\t\t\tx = paddingLeft + padding * (1 - alignFactor),\r\n\t\t\t\ty;\r\n\r\n\t\t\t// determin y based on the baseline\r\n\t\t\ty = baseline ? 0 : baselineOffset;\r\n\r\n\t\t\t// compensate for alignment\r\n\t\t\tif (defined(width) && (textAlign === 'center' || textAlign === 'right')) {\r\n\t\t\t\tx += { center: 0.5, right: 1 }[textAlign] * (width - bBox.width);\r\n\t\t\t}\r\n\r\n\t\t\t// update if anything changed\r\n\t\t\tif (x !== text.x || y !== text.y) {\r\n\t\t\t\ttext.attr({\r\n\t\t\t\t\tx: x,\r\n\t\t\t\t\ty: y\r\n\t\t\t\t});\r\n\t\t\t}\r\n\r\n\t\t\t// record current values\r\n\t\t\ttext.x = x;\r\n\t\t\ttext.y = y;\r\n\t\t}\r\n\r\n\t\t/**\r\n\t\t * Set a box attribute, or defer it if the box is not yet created\r\n\t\t * @param {Object} key\r\n\t\t * @param {Object} value\r\n\t\t */\r\n\t\tfunction boxAttr(key, value) {\r\n\t\t\tif (box) {\r\n\t\t\t\tbox.attr(key, value);\r\n\t\t\t} else {\r\n\t\t\t\tdeferredAttr[key] = value;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tfunction getSizeAfterAdd() {\r\n\t\t\ttext.add(wrapper);\r\n\t\t\twrapper.attr({\r\n\t\t\t\ttext: str, // alignment is available now\r\n\t\t\t\tx: x,\r\n\t\t\t\ty: y\r\n\t\t\t});\r\n\r\n\t\t\tif (box && defined(anchorX)) {\r\n\t\t\t\twrapper.attr({\r\n\t\t\t\t\tanchorX: anchorX,\r\n\t\t\t\t\tanchorY: anchorY\r\n\t\t\t\t});\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t/**\r\n\t\t * After the text element is added, get the desired size of the border box\r\n\t\t * and add it before the text in the DOM.\r\n\t\t */\r\n\t\taddEvent(wrapper, 'add', getSizeAfterAdd);\r\n\r\n\t\t/*\r\n\t\t * Add specific attribute setters.\r\n\t\t */\r\n\r\n\t\t// only change local variables\r\n\t\tattrSetters.width = function (value) {\r\n\t\t\twidth = value;\r\n\t\t\treturn false;\r\n\t\t};\r\n\t\tattrSetters.height = function (value) {\r\n\t\t\theight = value;\r\n\t\t\treturn false;\r\n\t\t};\r\n\t\tattrSetters.padding =  function (value) {\r\n\t\t\tif (defined(value) && value !== padding) {\r\n\t\t\t\tpadding = value;\r\n\t\t\t\tupdateTextPadding();\r\n\t\t\t}\r\n\t\t\treturn false;\r\n\t\t};\r\n\t\tattrSetters.paddingLeft =  function (value) {\r\n\t\t\tif (defined(value) && value !== paddingLeft) {\r\n\t\t\t\tpaddingLeft = value;\r\n\t\t\t\tupdateTextPadding();\r\n\t\t\t}\r\n\t\t\treturn false;\r\n\t\t};\r\n\r\n\r\n\t\t// change local variable and set attribue as well\r\n\t\tattrSetters.align = function (value) {\r\n\t\t\talignFactor = { left: 0, center: 0.5, right: 1 }[value];\r\n\t\t\treturn false; // prevent setting text-anchor on the group\r\n\t\t};\r\n\r\n\t\t// apply these to the box and the text alike\r\n\t\tattrSetters.text = function (value, key) {\r\n\t\t\ttext.attr(key, value);\r\n\t\t\tupdateBoxSize();\r\n\t\t\tupdateTextPadding();\r\n\t\t\treturn false;\r\n\t\t};\r\n\r\n\t\t// apply these to the box but not to the text\r\n\t\tattrSetters[STROKE_WIDTH] = function (value, key) {\r\n\t\t\tneedsBox = true;\r\n\t\t\tcrispAdjust = value % 2 / 2;\r\n\t\t\tboxAttr(key, value);\r\n\t\t\treturn false;\r\n\t\t};\r\n\t\tattrSetters.stroke = attrSetters.fill = attrSetters.r = function (value, key) {\r\n\t\t\tif (key === 'fill') {\r\n\t\t\t\tneedsBox = true;\r\n\t\t\t}\r\n\t\t\tboxAttr(key, value);\r\n\t\t\treturn false;\r\n\t\t};\r\n\t\tattrSetters.anchorX = function (value, key) {\r\n\t\t\tanchorX = value;\r\n\t\t\tboxAttr(key, value + crispAdjust - wrapperX);\r\n\t\t\treturn false;\r\n\t\t};\r\n\t\tattrSetters.anchorY = function (value, key) {\r\n\t\t\tanchorY = value;\r\n\t\t\tboxAttr(key, value - wrapperY);\r\n\t\t\treturn false;\r\n\t\t};\r\n\r\n\t\t// rename attributes\r\n\t\tattrSetters.x = function (value) {\r\n\t\t\twrapper.x = value; // for animation getter\r\n\t\t\tvalue -= alignFactor * ((width || bBox.width) + padding);\r\n\t\t\twrapperX = mathRound(value);\r\n\r\n\t\t\twrapper.attr('translateX', wrapperX);\r\n\t\t\treturn false;\r\n\t\t};\r\n\t\tattrSetters.y = function (value) {\r\n\t\t\twrapperY = wrapper.y = mathRound(value);\r\n\t\t\twrapper.attr('translateY', wrapperY);\r\n\t\t\treturn false;\r\n\t\t};\r\n\r\n\t\t// Redirect certain methods to either the box or the text\r\n\t\tvar baseCss = wrapper.css;\r\n\t\treturn extend(wrapper, {\r\n\t\t\t/**\r\n\t\t\t * Pick up some properties and apply them to the text instead of the wrapper\r\n\t\t\t */\r\n\t\t\tcss: function (styles) {\r\n\t\t\t\tif (styles) {\r\n\t\t\t\t\tvar textStyles = {};\r\n\t\t\t\t\tstyles = merge(styles); // create a copy to avoid altering the original object (#537)\r\n\t\t\t\t\teach(['fontSize', 'fontWeight', 'fontFamily', 'color', 'lineHeight', 'width', 'textDecoration', 'textShadow'], function (prop) {\r\n\t\t\t\t\t\tif (styles[prop] !== UNDEFINED) {\r\n\t\t\t\t\t\t\ttextStyles[prop] = styles[prop];\r\n\t\t\t\t\t\t\tdelete styles[prop];\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t});\r\n\t\t\t\t\ttext.css(textStyles);\r\n\t\t\t\t}\r\n\t\t\t\treturn baseCss.call(wrapper, styles);\r\n\t\t\t},\r\n\t\t\t/**\r\n\t\t\t * Return the bounding box of the box, not the group\r\n\t\t\t */\r\n\t\t\tgetBBox: function () {\r\n\t\t\t\treturn {\r\n\t\t\t\t\twidth: bBox.width + 2 * padding,\r\n\t\t\t\t\theight: bBox.height + 2 * padding,\r\n\t\t\t\t\tx: bBox.x - padding,\r\n\t\t\t\t\ty: bBox.y - padding\r\n\t\t\t\t};\r\n\t\t\t},\r\n\t\t\t/**\r\n\t\t\t * Apply the shadow to the box\r\n\t\t\t */\r\n\t\t\tshadow: function (b) {\r\n\t\t\t\tif (box) {\r\n\t\t\t\t\tbox.shadow(b);\r\n\t\t\t\t}\r\n\t\t\t\treturn wrapper;\r\n\t\t\t},\r\n\t\t\t/**\r\n\t\t\t * Destroy and release memory.\r\n\t\t\t */\r\n\t\t\tdestroy: function () {\r\n\t\t\t\tremoveEvent(wrapper, 'add', getSizeAfterAdd);\r\n\r\n\t\t\t\t// Added by button implementation\r\n\t\t\t\tremoveEvent(wrapper.element, 'mouseenter');\r\n\t\t\t\tremoveEvent(wrapper.element, 'mouseleave');\r\n\r\n\t\t\t\tif (text) {\r\n\t\t\t\t\ttext = text.destroy();\r\n\t\t\t\t}\r\n\t\t\t\tif (box) {\r\n\t\t\t\t\tbox = box.destroy();\r\n\t\t\t\t}\r\n\t\t\t\t// Call base implementation to destroy the rest\r\n\t\t\t\tSVGElement.prototype.destroy.call(wrapper);\r\n\r\n\t\t\t\t// Release local pointers (#1298)\r\n\t\t\t\twrapper = renderer = updateBoxSize = updateTextPadding = boxAttr = getSizeAfterAdd = null;\r\n\t\t\t}\r\n\t\t});\r\n\t}\r\n}; // end SVGRenderer\r\n\r\n\r\n// general renderer\r\nRenderer = SVGRenderer;\r\n\r\n\r\n/* ****************************************************************************\r\n *                                                                            *\r\n * START OF INTERNET EXPLORER <= 8 SPECIFIC CODE                              *\r\n *                                                                            *\r\n * For applications and websites that don't need IE support, like platform    *\r\n * targeted mobile apps and web apps, this code can be removed.               *\r\n *                                                                            *\r\n *****************************************************************************/\r\n\r\n/**\r\n * @constructor\r\n */\r\nvar VMLRenderer, VMLElement;\r\nif (!hasSVG && !useCanVG) {\r\n\r\n/**\r\n * The VML element wrapper.\r\n */\r\nHighcharts.VMLElement = VMLElement = {\r\n\r\n\t/**\r\n\t * Initialize a new VML element wrapper. It builds the markup as a string\r\n\t * to minimize DOM traffic.\r\n\t * @param {Object} renderer\r\n\t * @param {Object} nodeName\r\n\t */\r\n\tinit: function (renderer, nodeName) {\r\n\t\tvar wrapper = this,\r\n\t\t\tmarkup =  ['<', nodeName, ' filled=\"f\" stroked=\"f\"'],\r\n\t\t\tstyle = ['position: ', ABSOLUTE, ';'],\r\n\t\t\tisDiv = nodeName === DIV;\r\n\r\n\t\t// divs and shapes need size\r\n\t\tif (nodeName === 'shape' || isDiv) {\r\n\t\t\tstyle.push('left:0;top:0;width:1px;height:1px;');\r\n\t\t}\r\n\t\tstyle.push('visibility: ', isDiv ? HIDDEN : VISIBLE);\r\n\r\n\t\tmarkup.push(' style=\"', style.join(''), '\"/>');\r\n\r\n\t\t// create element with default attributes and style\r\n\t\tif (nodeName) {\r\n\t\t\tmarkup = isDiv || nodeName === 'span' || nodeName === 'img' ?\r\n\t\t\t\tmarkup.join('')\r\n\t\t\t\t: renderer.prepVML(markup);\r\n\t\t\twrapper.element = createElement(markup);\r\n\t\t}\r\n\r\n\t\twrapper.renderer = renderer;\r\n\t\twrapper.attrSetters = {};\r\n\t},\r\n\r\n\t/**\r\n\t * Add the node to the given parent\r\n\t * @param {Object} parent\r\n\t */\r\n\tadd: function (parent) {\r\n\t\tvar wrapper = this,\r\n\t\t\trenderer = wrapper.renderer,\r\n\t\t\telement = wrapper.element,\r\n\t\t\tbox = renderer.box,\r\n\t\t\tinverted = parent && parent.inverted,\r\n\r\n\t\t\t// get the parent node\r\n\t\t\tparentNode = parent ?\r\n\t\t\t\tparent.element || parent :\r\n\t\t\t\tbox;\r\n\r\n\r\n\t\t// if the parent group is inverted, apply inversion on all children\r\n\t\tif (inverted) { // only on groups\r\n\t\t\trenderer.invertChild(element, parentNode);\r\n\t\t}\r\n\r\n\t\t// append it\r\n\t\tparentNode.appendChild(element);\r\n\r\n\t\t// align text after adding to be able to read offset\r\n\t\twrapper.added = true;\r\n\t\tif (wrapper.alignOnAdd && !wrapper.deferUpdateTransform) {\r\n\t\t\twrapper.updateTransform();\r\n\t\t}\r\n\r\n\t\t// fire an event for internal hooks\r\n\t\tfireEvent(wrapper, 'add');\r\n\r\n\t\treturn wrapper;\r\n\t},\r\n\r\n\t/**\r\n\t * VML always uses htmlUpdateTransform\r\n\t */\r\n\tupdateTransform: SVGElement.prototype.htmlUpdateTransform,\r\n\r\n\t/**\r\n\t * Set the rotation of a span with oldIE's filter\r\n\t */\r\n\tsetSpanRotation: function (rotation, sintheta, costheta) {\r\n\t\t// Adjust for alignment and rotation. Rotation of useHTML content is not yet implemented\r\n\t\t// but it can probably be implemented for Firefox 3.5+ on user request. FF3.5+\r\n\t\t// has support for CSS3 transform. The getBBox method also needs to be updated\r\n\t\t// to compensate for the rotation, like it currently does for SVG.\r\n\t\t// Test case: http://highcharts.com/tests/?file=text-rotation\r\n\t\tcss(this.element, {\r\n\t\t\tfilter: rotation ? ['progid:DXImageTransform.Microsoft.Matrix(M11=', costheta,\r\n\t\t\t\t', M12=', -sintheta, ', M21=', sintheta, ', M22=', costheta,\r\n\t\t\t\t', sizingMethod=\\'auto expand\\')'].join('') : NONE\r\n\t\t});\r\n\t},\r\n\r\n\t/**\r\n\t * Converts a subset of an SVG path definition to its VML counterpart. Takes an array\r\n\t * as the parameter and returns a string.\r\n\t */\r\n\tpathToVML: function (value) {\r\n\t\t// convert paths\r\n\t\tvar i = value.length,\r\n\t\t\tpath = [],\r\n\t\t\tclockwise;\r\n\r\n\t\twhile (i--) {\r\n\r\n\t\t\t// Multiply by 10 to allow subpixel precision.\r\n\t\t\t// Substracting half a pixel seems to make the coordinates\r\n\t\t\t// align with SVG, but this hasn't been tested thoroughly\r\n\t\t\tif (isNumber(value[i])) {\r\n\t\t\t\tpath[i] = mathRound(value[i] * 10) - 5;\r\n\t\t\t} else if (value[i] === 'Z') { // close the path\r\n\t\t\t\tpath[i] = 'x';\r\n\t\t\t} else {\r\n\t\t\t\tpath[i] = value[i];\r\n\r\n\t\t\t\t// When the start X and end X coordinates of an arc are too close,\r\n\t\t\t\t// they are rounded to the same value above. In this case, substract 1 from the end X\r\n\t\t\t\t// position. #760, #1371.\r\n\t\t\t\tif (value.isArc && (value[i] === 'wa' || value[i] === 'at')) {\r\n\t\t\t\t\tclockwise = value[i] === 'wa' ? 1 : -1; // #1642\r\n\t\t\t\t\tif (path[i + 5] === path[i + 7]) {\r\n\t\t\t\t\t\tpath[i + 7] -= clockwise;\r\n\t\t\t\t\t}\r\n\t\t\t\t\t// Start and end Y (#1410)\r\n\t\t\t\t\tif (path[i + 6] === path[i + 8]) {\r\n\t\t\t\t\t\tpath[i + 8] -= clockwise;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t// Loop up again to handle path shortcuts (#2132)\r\n\t\t/*while (i++ < path.length) {\r\n\t\t\tif (path[i] === 'H') { // horizontal line to\r\n\t\t\t\tpath[i] = 'L';\r\n\t\t\t\tpath.splice(i + 2, 0, path[i - 1]);\r\n\t\t\t} else if (path[i] === 'V') { // vertical line to\r\n\t\t\t\tpath[i] = 'L';\r\n\t\t\t\tpath.splice(i + 1, 0, path[i - 2]);\r\n\t\t\t}\r\n\t\t}*/\r\n\t\treturn path.join(' ') || 'x';\r\n\t},\r\n\r\n\t/**\r\n\t * Get or set attributes\r\n\t */\r\n\tattr: function (hash, val) {\r\n\t\tvar wrapper = this,\r\n\t\t\tkey,\r\n\t\t\tvalue,\r\n\t\t\ti,\r\n\t\t\tresult,\r\n\t\t\telement = wrapper.element || {},\r\n\t\t\telemStyle = element.style,\r\n\t\t\tnodeName = element.nodeName,\r\n\t\t\trenderer = wrapper.renderer,\r\n\t\t\tsymbolName = wrapper.symbolName,\r\n\t\t\thasSetSymbolSize,\r\n\t\t\tshadows = wrapper.shadows,\r\n\t\t\tskipAttr,\r\n\t\t\tattrSetters = wrapper.attrSetters,\r\n\t\t\tret = wrapper;\r\n\r\n\t\t// single key-value pair\r\n\t\tif (isString(hash) && defined(val)) {\r\n\t\t\tkey = hash;\r\n\t\t\thash = {};\r\n\t\t\thash[key] = val;\r\n\t\t}\r\n\r\n\t\t// used as a getter, val is undefined\r\n\t\tif (isString(hash)) {\r\n\t\t\tkey = hash;\r\n\t\t\tif (key === 'strokeWidth' || key === 'stroke-width') {\r\n\t\t\t\tret = wrapper.strokeweight;\r\n\t\t\t} else {\r\n\t\t\t\tret = wrapper[key];\r\n\t\t\t}\r\n\r\n\t\t// setter\r\n\t\t} else {\r\n\t\t\tfor (key in hash) {\r\n\t\t\t\tvalue = hash[key];\r\n\t\t\t\tskipAttr = false;\r\n\r\n\t\t\t\t// check for a specific attribute setter\r\n\t\t\t\tresult = attrSetters[key] && attrSetters[key].call(wrapper, value, key);\r\n\r\n\t\t\t\tif (result !== false && value !== null) { // #620\r\n\r\n\t\t\t\t\tif (result !== UNDEFINED) {\r\n\t\t\t\t\t\tvalue = result; // the attribute setter has returned a new value to set\r\n\t\t\t\t\t}\r\n\r\n\r\n\t\t\t\t\t// prepare paths\r\n\t\t\t\t\t// symbols\r\n\t\t\t\t\tif (symbolName && /^(x|y|r|start|end|width|height|innerR|anchorX|anchorY)/.test(key)) {\r\n\t\t\t\t\t\t// if one of the symbol size affecting parameters are changed,\r\n\t\t\t\t\t\t// check all the others only once for each call to an element's\r\n\t\t\t\t\t\t// .attr() method\r\n\t\t\t\t\t\tif (!hasSetSymbolSize) {\r\n\t\t\t\t\t\t\twrapper.symbolAttr(hash);\r\n\r\n\t\t\t\t\t\t\thasSetSymbolSize = true;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tskipAttr = true;\r\n\r\n\t\t\t\t\t} else if (key === 'd') {\r\n\t\t\t\t\t\tvalue = value || [];\r\n\t\t\t\t\t\twrapper.d = value.join(' '); // used in getter for animation\r\n\r\n\t\t\t\t\t\telement.path = value = wrapper.pathToVML(value);\r\n\r\n\t\t\t\t\t\t// update shadows\r\n\t\t\t\t\t\tif (shadows) {\r\n\t\t\t\t\t\t\ti = shadows.length;\r\n\t\t\t\t\t\t\twhile (i--) {\r\n\t\t\t\t\t\t\t\tshadows[i].path = shadows[i].cutOff ? this.cutOffPath(value, shadows[i].cutOff) : value;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tskipAttr = true;\r\n\r\n\t\t\t\t\t// handle visibility\r\n\t\t\t\t\t} else if (key === 'visibility') {\r\n\r\n\t\t\t\t\t\t// let the shadow follow the main element\r\n\t\t\t\t\t\tif (shadows) {\r\n\t\t\t\t\t\t\ti = shadows.length;\r\n\t\t\t\t\t\t\twhile (i--) {\r\n\t\t\t\t\t\t\t\tshadows[i].style[key] = value;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t// Instead of toggling the visibility CSS property, move the div out of the viewport.\r\n\t\t\t\t\t\t// This works around #61 and #586\r\n\t\t\t\t\t\tif (nodeName === 'DIV') {\r\n\t\t\t\t\t\t\tvalue = value === HIDDEN ? '-999em' : 0;\r\n\r\n\t\t\t\t\t\t\t// In order to redraw, IE7 needs the div to be visible when tucked away\r\n\t\t\t\t\t\t\t// outside the viewport. So the visibility is actually opposite of\r\n\t\t\t\t\t\t\t// the expected value. This applies to the tooltip only.\r\n\t\t\t\t\t\t\tif (!docMode8) {\r\n\t\t\t\t\t\t\t\telemStyle[key] = value ? VISIBLE : HIDDEN;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\tkey = 'top';\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\telemStyle[key] = value;\r\n\t\t\t\t\t\tskipAttr = true;\r\n\r\n\t\t\t\t\t// directly mapped to css\r\n\t\t\t\t\t} else if (key === 'zIndex') {\r\n\r\n\t\t\t\t\t\tif (value) {\r\n\t\t\t\t\t\t\telemStyle[key] = value;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tskipAttr = true;\r\n\r\n\t\t\t\t\t// x, y, width, height\r\n\t\t\t\t\t} else if (inArray(key, ['x', 'y', 'width', 'height']) !== -1) {\r\n\r\n\t\t\t\t\t\twrapper[key] = value; // used in getter\r\n\r\n\t\t\t\t\t\tif (key === 'x' || key === 'y') {\r\n\t\t\t\t\t\t\tkey = { x: 'left', y: 'top' }[key];\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\tvalue = mathMax(0, value); // don't set width or height below zero (#311)\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t// clipping rectangle special\r\n\t\t\t\t\t\tif (wrapper.updateClipping) {\r\n\t\t\t\t\t\t\twrapper[key] = value; // the key is now 'left' or 'top' for 'x' and 'y'\r\n\t\t\t\t\t\t\twrapper.updateClipping();\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t// normal\r\n\t\t\t\t\t\t\telemStyle[key] = value;\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\tskipAttr = true;\r\n\r\n\t\t\t\t\t// class name\r\n\t\t\t\t\t} else if (key === 'class' && nodeName === 'DIV') {\r\n\t\t\t\t\t\t// IE8 Standards mode has problems retrieving the className\r\n\t\t\t\t\t\telement.className = value;\r\n\r\n\t\t\t\t\t// stroke\r\n\t\t\t\t\t} else if (key === 'stroke') {\r\n\r\n\t\t\t\t\t\tvalue = renderer.color(value, element, key);\r\n\r\n\t\t\t\t\t\tkey = 'strokecolor';\r\n\r\n\t\t\t\t\t// stroke width\r\n\t\t\t\t\t} else if (key === 'stroke-width' || key === 'strokeWidth') {\r\n\t\t\t\t\t\telement.stroked = value ? true : false;\r\n\t\t\t\t\t\tkey = 'strokeweight';\r\n\t\t\t\t\t\twrapper[key] = value; // used in getter, issue #113\r\n\t\t\t\t\t\tif (isNumber(value)) {\r\n\t\t\t\t\t\t\tvalue += PX;\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t// dashStyle\r\n\t\t\t\t\t} else if (key === 'dashstyle') {\r\n\t\t\t\t\t\tvar strokeElem = element.getElementsByTagName('stroke')[0] ||\r\n\t\t\t\t\t\t\tcreateElement(renderer.prepVML(['<stroke/>']), null, null, element);\r\n\t\t\t\t\t\tstrokeElem[key] = value || 'solid';\r\n\t\t\t\t\t\twrapper.dashstyle = value; /* because changing stroke-width will change the dash length\r\n\t\t\t\t\t\t\tand cause an epileptic effect */\r\n\t\t\t\t\t\tskipAttr = true;\r\n\r\n\t\t\t\t\t// fill\r\n\t\t\t\t\t} else if (key === 'fill') {\r\n\r\n\t\t\t\t\t\tif (nodeName === 'SPAN') { // text color\r\n\t\t\t\t\t\t\telemStyle.color = value;\r\n\t\t\t\t\t\t} else if (nodeName !== 'IMG') { // #1336\r\n\t\t\t\t\t\t\telement.filled = value !== NONE ? true : false;\r\n\r\n\t\t\t\t\t\t\tvalue = renderer.color(value, element, key, wrapper);\r\n\r\n\t\t\t\t\t\t\tkey = 'fillcolor';\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t// opacity: don't bother - animation is too slow and filters introduce artifacts\r\n\t\t\t\t\t} else if (key === 'opacity') {\r\n\t\t\t\t\t\t/*css(element, {\r\n\t\t\t\t\t\t\topacity: value\r\n\t\t\t\t\t\t});*/\r\n\t\t\t\t\t\tskipAttr = true;\r\n\r\n\t\t\t\t\t// rotation on VML elements\r\n\t\t\t\t\t} else if (nodeName === 'shape' && key === 'rotation') {\r\n\r\n\t\t\t\t\t\twrapper[key] = element.style[key] = value; // style is for #1873\r\n\r\n\t\t\t\t\t\t// Correction for the 1x1 size of the shape container. Used in gauge needles.\r\n\t\t\t\t\t\telement.style.left = -mathRound(mathSin(value * deg2rad) + 1) + PX;\r\n\t\t\t\t\t\telement.style.top = mathRound(mathCos(value * deg2rad)) + PX;\r\n\r\n\t\t\t\t\t// translation for animation\r\n\t\t\t\t\t} else if (key === 'translateX' || key === 'translateY' || key === 'rotation') {\r\n\t\t\t\t\t\twrapper[key] = value;\r\n\t\t\t\t\t\twrapper.updateTransform();\r\n\r\n\t\t\t\t\t\tskipAttr = true;\r\n\r\n\t\t\t\t\t// text for rotated and non-rotated elements\r\n\t\t\t\t\t} else if (key === 'text') {\r\n\t\t\t\t\t\tthis.bBox = null;\r\n\t\t\t\t\t\telement.innerHTML = value;\r\n\t\t\t\t\t\tskipAttr = true;\r\n\t\t\t\t\t}\r\n\r\n\r\n\t\t\t\t\tif (!skipAttr) {\r\n\t\t\t\t\t\tif (docMode8) { // IE8 setAttribute bug\r\n\t\t\t\t\t\t\telement[key] = value;\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\tattr(element, key, value);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn ret;\r\n\t},\r\n\r\n\t/**\r\n\t * Set the element's clipping to a predefined rectangle\r\n\t *\r\n\t * @param {String} id The id of the clip rectangle\r\n\t */\r\n\tclip: function (clipRect) {\r\n\t\tvar wrapper = this,\r\n\t\t\tclipMembers,\r\n\t\t\tcssRet;\r\n\r\n\t\tif (clipRect) {\r\n\t\t\tclipMembers = clipRect.members;\r\n\t\t\terase(clipMembers, wrapper); // Ensure unique list of elements (#1258)\r\n\t\t\tclipMembers.push(wrapper);\r\n\t\t\twrapper.destroyClip = function () {\r\n\t\t\t\terase(clipMembers, wrapper);\r\n\t\t\t};\r\n\t\t\tcssRet = clipRect.getCSS(wrapper);\r\n\r\n\t\t} else {\r\n\t\t\tif (wrapper.destroyClip) {\r\n\t\t\t\twrapper.destroyClip();\r\n\t\t\t}\r\n\t\t\tcssRet = { clip: docMode8 ? 'inherit' : 'rect(auto)' }; // #1214\r\n\t\t}\r\n\r\n\t\treturn wrapper.css(cssRet);\r\n\r\n\t},\r\n\r\n\t/**\r\n\t * Set styles for the element\r\n\t * @param {Object} styles\r\n\t */\r\n\tcss: SVGElement.prototype.htmlCss,\r\n\r\n\t/**\r\n\t * Removes a child either by removeChild or move to garbageBin.\r\n\t * Issue 490; in VML removeChild results in Orphaned nodes according to sIEve, discardElement does not.\r\n\t */\r\n\tsafeRemoveChild: function (element) {\r\n\t\t// discardElement will detach the node from its parent before attaching it\r\n\t\t// to the garbage bin. Therefore it is important that the node is attached and have parent.\r\n\t\tif (element.parentNode) {\r\n\t\t\tdiscardElement(element);\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Extend element.destroy by removing it from the clip members array\r\n\t */\r\n\tdestroy: function () {\r\n\t\tif (this.destroyClip) {\r\n\t\t\tthis.destroyClip();\r\n\t\t}\r\n\r\n\t\treturn SVGElement.prototype.destroy.apply(this);\r\n\t},\r\n\r\n\t/**\r\n\t * Add an event listener. VML override for normalizing event parameters.\r\n\t * @param {String} eventType\r\n\t * @param {Function} handler\r\n\t */\r\n\ton: function (eventType, handler) {\r\n\t\t// simplest possible event model for internal use\r\n\t\tthis.element['on' + eventType] = function () {\r\n\t\t\tvar evt = win.event;\r\n\t\t\tevt.target = evt.srcElement;\r\n\t\t\thandler(evt);\r\n\t\t};\r\n\t\treturn this;\r\n\t},\r\n\r\n\t/**\r\n\t * In stacked columns, cut off the shadows so that they don't overlap\r\n\t */\r\n\tcutOffPath: function (path, length) {\r\n\r\n\t\tvar len;\r\n\r\n\t\tpath = path.split(/[ ,]/);\r\n\t\tlen = path.length;\r\n\r\n\t\tif (len === 9 || len === 11) {\r\n\t\t\tpath[len - 4] = path[len - 2] = pInt(path[len - 2]) - 10 * length;\r\n\t\t}\r\n\t\treturn path.join(' ');\r\n\t},\r\n\r\n\t/**\r\n\t * Apply a drop shadow by copying elements and giving them different strokes\r\n\t * @param {Boolean|Object} shadowOptions\r\n\t */\r\n\tshadow: function (shadowOptions, group, cutOff) {\r\n\t\tvar shadows = [],\r\n\t\t\ti,\r\n\t\t\telement = this.element,\r\n\t\t\trenderer = this.renderer,\r\n\t\t\tshadow,\r\n\t\t\telemStyle = element.style,\r\n\t\t\tmarkup,\r\n\t\t\tpath = element.path,\r\n\t\t\tstrokeWidth,\r\n\t\t\tmodifiedPath,\r\n\t\t\tshadowWidth,\r\n\t\t\tshadowElementOpacity;\r\n\r\n\t\t// some times empty paths are not strings\r\n\t\tif (path && typeof path.value !== 'string') {\r\n\t\t\tpath = 'x';\r\n\t\t}\r\n\t\tmodifiedPath = path;\r\n\r\n\t\tif (shadowOptions) {\r\n\t\t\tshadowWidth = pick(shadowOptions.width, 3);\r\n\t\t\tshadowElementOpacity = (shadowOptions.opacity || 0.15) / shadowWidth;\r\n\t\t\tfor (i = 1; i <= 3; i++) {\r\n\r\n\t\t\t\tstrokeWidth = (shadowWidth * 2) + 1 - (2 * i);\r\n\r\n\t\t\t\t// Cut off shadows for stacked column items\r\n\t\t\t\tif (cutOff) {\r\n\t\t\t\t\tmodifiedPath = this.cutOffPath(path.value, strokeWidth + 0.5);\r\n\t\t\t\t}\r\n\r\n\t\t\t\tmarkup = ['<shape isShadow=\"true\" strokeweight=\"', strokeWidth,\r\n\t\t\t\t\t'\" filled=\"false\" path=\"', modifiedPath,\r\n\t\t\t\t\t'\" coordsize=\"10 10\" style=\"', element.style.cssText, '\" />'];\r\n\r\n\t\t\t\tshadow = createElement(renderer.prepVML(markup),\r\n\t\t\t\t\tnull, {\r\n\t\t\t\t\t\tleft: pInt(elemStyle.left) + pick(shadowOptions.offsetX, 1),\r\n\t\t\t\t\t\ttop: pInt(elemStyle.top) + pick(shadowOptions.offsetY, 1)\r\n\t\t\t\t\t}\r\n\t\t\t\t);\r\n\t\t\t\tif (cutOff) {\r\n\t\t\t\t\tshadow.cutOff = strokeWidth + 1;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// apply the opacity\r\n\t\t\t\tmarkup = ['<stroke color=\"', shadowOptions.color || 'black', '\" opacity=\"', shadowElementOpacity * i, '\"/>'];\r\n\t\t\t\tcreateElement(renderer.prepVML(markup), null, null, shadow);\r\n\r\n\r\n\t\t\t\t// insert it\r\n\t\t\t\tif (group) {\r\n\t\t\t\t\tgroup.element.appendChild(shadow);\r\n\t\t\t\t} else {\r\n\t\t\t\t\telement.parentNode.insertBefore(shadow, element);\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// record it\r\n\t\t\t\tshadows.push(shadow);\r\n\r\n\t\t\t}\r\n\r\n\t\t\tthis.shadows = shadows;\r\n\t\t}\r\n\t\treturn this;\r\n\r\n\t}\r\n};\r\nVMLElement = extendClass(SVGElement, VMLElement);\r\n\r\n/**\r\n * The VML renderer\r\n */\r\nvar VMLRendererExtension = { // inherit SVGRenderer\r\n\r\n\tElement: VMLElement,\r\n\tisIE8: userAgent.indexOf('MSIE 8.0') > -1,\r\n\r\n\r\n\t/**\r\n\t * Initialize the VMLRenderer\r\n\t * @param {Object} container\r\n\t * @param {Number} width\r\n\t * @param {Number} height\r\n\t */\r\n\tinit: function (container, width, height) {\r\n\t\tvar renderer = this,\r\n\t\t\tboxWrapper,\r\n\t\t\tbox;\r\n\r\n\t\trenderer.alignedObjects = [];\r\n\r\n\t\tboxWrapper = renderer.createElement(DIV);\r\n\t\tbox = boxWrapper.element;\r\n\t\tbox.style.position = RELATIVE; // for freeform drawing using renderer directly\r\n\t\tcontainer.appendChild(boxWrapper.element);\r\n\r\n\r\n\t\t// generate the containing box\r\n\t\trenderer.isVML = true;\r\n\t\trenderer.box = box;\r\n\t\trenderer.boxWrapper = boxWrapper;\r\n\r\n\r\n\t\trenderer.setSize(width, height, false);\r\n\r\n\t\t// The only way to make IE6 and IE7 print is to use a global namespace. However,\r\n\t\t// with IE8 the only way to make the dynamic shapes visible in screen and print mode\r\n\t\t// seems to be to add the xmlns attribute and the behaviour style inline.\r\n\t\tif (!doc.namespaces.hcv) {\r\n\r\n\t\t\tdoc.namespaces.add('hcv', 'urn:schemas-microsoft-com:vml');\r\n\r\n\t\t\t// Setup default CSS (#2153)\r\n\t\t\t(doc.styleSheets.length ? doc.styleSheets[0] : doc.createStyleSheet()).cssText +=\r\n\t\t\t\t'hcv\\\\:fill, hcv\\\\:path, hcv\\\\:shape, hcv\\\\:stroke' +\r\n\t\t\t\t'{ behavior:url(#default#VML); display: inline-block; } ';\r\n\r\n\t\t}\r\n\t},\r\n\r\n\r\n\t/**\r\n\t * Detect whether the renderer is hidden. This happens when one of the parent elements\r\n\t * has display: none\r\n\t */\r\n\tisHidden: function () {\r\n\t\treturn !this.box.offsetWidth;\r\n\t},\r\n\r\n\t/**\r\n\t * Define a clipping rectangle. In VML it is accomplished by storing the values\r\n\t * for setting the CSS style to all associated members.\r\n\t *\r\n\t * @param {Number} x\r\n\t * @param {Number} y\r\n\t * @param {Number} width\r\n\t * @param {Number} height\r\n\t */\r\n\tclipRect: function (x, y, width, height) {\r\n\r\n\t\t// create a dummy element\r\n\t\tvar clipRect = this.createElement(),\r\n\t\t\tisObj = isObject(x);\r\n\r\n\t\t// mimic a rectangle with its style object for automatic updating in attr\r\n\t\treturn extend(clipRect, {\r\n\t\t\tmembers: [],\r\n\t\t\tleft: (isObj ? x.x : x) + 1,\r\n\t\t\ttop: (isObj ? x.y : y) + 1,\r\n\t\t\twidth: (isObj ? x.width : width) - 1,\r\n\t\t\theight: (isObj ? x.height : height) - 1,\r\n\t\t\tgetCSS: function (wrapper) {\r\n\t\t\t\tvar element = wrapper.element,\r\n\t\t\t\t\tnodeName = element.nodeName,\r\n\t\t\t\t\tisShape = nodeName === 'shape',\r\n\t\t\t\t\tinverted = wrapper.inverted,\r\n\t\t\t\t\trect = this,\r\n\t\t\t\t\ttop = rect.top - (isShape ? element.offsetTop : 0),\r\n\t\t\t\t\tleft = rect.left,\r\n\t\t\t\t\tright = left + rect.width,\r\n\t\t\t\t\tbottom = top + rect.height,\r\n\t\t\t\t\tret = {\r\n\t\t\t\t\t\tclip: 'rect(' +\r\n\t\t\t\t\t\t\tmathRound(inverted ? left : top) + 'px,' +\r\n\t\t\t\t\t\t\tmathRound(inverted ? bottom : right) + 'px,' +\r\n\t\t\t\t\t\t\tmathRound(inverted ? right : bottom) + 'px,' +\r\n\t\t\t\t\t\t\tmathRound(inverted ? top : left) + 'px)'\r\n\t\t\t\t\t};\r\n\r\n\t\t\t\t// issue 74 workaround\r\n\t\t\t\tif (!inverted && docMode8 && nodeName === 'DIV') {\r\n\t\t\t\t\textend(ret, {\r\n\t\t\t\t\t\twidth: right + PX,\r\n\t\t\t\t\t\theight: bottom + PX\r\n\t\t\t\t\t});\r\n\t\t\t\t}\r\n\t\t\t\treturn ret;\r\n\t\t\t},\r\n\r\n\t\t\t// used in attr and animation to update the clipping of all members\r\n\t\t\tupdateClipping: function () {\r\n\t\t\t\teach(clipRect.members, function (member) {\r\n\t\t\t\t\tmember.css(clipRect.getCSS(member));\r\n\t\t\t\t});\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t},\r\n\r\n\r\n\t/**\r\n\t * Take a color and return it if it's a string, make it a gradient if it's a\r\n\t * gradient configuration object, and apply opacity.\r\n\t *\r\n\t * @param {Object} color The color or config object\r\n\t */\r\n\tcolor: function (color, elem, prop, wrapper) {\r\n\t\tvar renderer = this,\r\n\t\t\tcolorObject,\r\n\t\t\tregexRgba = /^rgba/,\r\n\t\t\tmarkup,\r\n\t\t\tfillType,\r\n\t\t\tret = NONE;\r\n\r\n\t\t// Check for linear or radial gradient\r\n\t\tif (color && color.linearGradient) {\r\n\t\t\tfillType = 'gradient';\r\n\t\t} else if (color && color.radialGradient) {\r\n\t\t\tfillType = 'pattern';\r\n\t\t}\r\n\r\n\r\n\t\tif (fillType) {\r\n\r\n\t\t\tvar stopColor,\r\n\t\t\t\tstopOpacity,\r\n\t\t\t\tgradient = color.linearGradient || color.radialGradient,\r\n\t\t\t\tx1,\r\n\t\t\t\ty1,\r\n\t\t\t\tx2,\r\n\t\t\t\ty2,\r\n\t\t\t\topacity1,\r\n\t\t\t\topacity2,\r\n\t\t\t\tcolor1,\r\n\t\t\t\tcolor2,\r\n\t\t\t\tfillAttr = '',\r\n\t\t\t\tstops = color.stops,\r\n\t\t\t\tfirstStop,\r\n\t\t\t\tlastStop,\r\n\t\t\t\tcolors = [],\r\n\t\t\t\taddFillNode = function () {\r\n\t\t\t\t\t// Add the fill subnode. When colors attribute is used, the meanings of opacity and o:opacity2\r\n\t\t\t\t\t// are reversed.\r\n\t\t\t\t\tmarkup = ['<fill colors=\"' + colors.join(',') + '\" opacity=\"', opacity2, '\" o:opacity2=\"', opacity1,\r\n\t\t\t\t\t\t'\" type=\"', fillType, '\" ', fillAttr, 'focus=\"100%\" method=\"any\" />'];\r\n\t\t\t\t\tcreateElement(renderer.prepVML(markup), null, null, elem);\r\n\t\t\t\t};\r\n\r\n\t\t\t// Extend from 0 to 1\r\n\t\t\tfirstStop = stops[0];\r\n\t\t\tlastStop = stops[stops.length - 1];\r\n\t\t\tif (firstStop[0] > 0) {\r\n\t\t\t\tstops.unshift([\r\n\t\t\t\t\t0,\r\n\t\t\t\t\tfirstStop[1]\r\n\t\t\t\t]);\r\n\t\t\t}\r\n\t\t\tif (lastStop[0] < 1) {\r\n\t\t\t\tstops.push([\r\n\t\t\t\t\t1,\r\n\t\t\t\t\tlastStop[1]\r\n\t\t\t\t]);\r\n\t\t\t}\r\n\r\n\t\t\t// Compute the stops\r\n\t\t\teach(stops, function (stop, i) {\r\n\t\t\t\tif (regexRgba.test(stop[1])) {\r\n\t\t\t\t\tcolorObject = Color(stop[1]);\r\n\t\t\t\t\tstopColor = colorObject.get('rgb');\r\n\t\t\t\t\tstopOpacity = colorObject.get('a');\r\n\t\t\t\t} else {\r\n\t\t\t\t\tstopColor = stop[1];\r\n\t\t\t\t\tstopOpacity = 1;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Build the color attribute\r\n\t\t\t\tcolors.push((stop[0] * 100) + '% ' + stopColor);\r\n\r\n\t\t\t\t// Only start and end opacities are allowed, so we use the first and the last\r\n\t\t\t\tif (!i) {\r\n\t\t\t\t\topacity1 = stopOpacity;\r\n\t\t\t\t\tcolor2 = stopColor;\r\n\t\t\t\t} else {\r\n\t\t\t\t\topacity2 = stopOpacity;\r\n\t\t\t\t\tcolor1 = stopColor;\r\n\t\t\t\t}\r\n\t\t\t});\r\n\r\n\t\t\t// Apply the gradient to fills only.\r\n\t\t\tif (prop === 'fill') {\r\n\r\n\t\t\t\t// Handle linear gradient angle\r\n\t\t\t\tif (fillType === 'gradient') {\r\n\t\t\t\t\tx1 = gradient.x1 || gradient[0] || 0;\r\n\t\t\t\t\ty1 = gradient.y1 || gradient[1] || 0;\r\n\t\t\t\t\tx2 = gradient.x2 || gradient[2] || 0;\r\n\t\t\t\t\ty2 = gradient.y2 || gradient[3] || 0;\r\n\t\t\t\t\tfillAttr = 'angle=\"' + (90  - math.atan(\r\n\t\t\t\t\t\t(y2 - y1) / // y vector\r\n\t\t\t\t\t\t(x2 - x1) // x vector\r\n\t\t\t\t\t\t) * 180 / mathPI) + '\"';\r\n\r\n\t\t\t\t\taddFillNode();\r\n\r\n\t\t\t\t// Radial (circular) gradient\r\n\t\t\t\t} else {\r\n\r\n\t\t\t\t\tvar r = gradient.r,\r\n\t\t\t\t\t\tsizex = r * 2,\r\n\t\t\t\t\t\tsizey = r * 2,\r\n\t\t\t\t\t\tcx = gradient.cx,\r\n\t\t\t\t\t\tcy = gradient.cy,\r\n\t\t\t\t\t\tradialReference = elem.radialReference,\r\n\t\t\t\t\t\tbBox,\r\n\t\t\t\t\t\tapplyRadialGradient = function () {\r\n\t\t\t\t\t\t\tif (radialReference) {\r\n\t\t\t\t\t\t\t\tbBox = wrapper.getBBox();\r\n\t\t\t\t\t\t\t\tcx += (radialReference[0] - bBox.x) / bBox.width - 0.5;\r\n\t\t\t\t\t\t\t\tcy += (radialReference[1] - bBox.y) / bBox.height - 0.5;\r\n\t\t\t\t\t\t\t\tsizex *= radialReference[2] / bBox.width;\r\n\t\t\t\t\t\t\t\tsizey *= radialReference[2] / bBox.height;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\tfillAttr = 'src=\"' + defaultOptions.global.VMLRadialGradientURL + '\" ' +\r\n\t\t\t\t\t\t\t\t'size=\"' + sizex + ',' + sizey + '\" ' +\r\n\t\t\t\t\t\t\t\t'origin=\"0.5,0.5\" ' +\r\n\t\t\t\t\t\t\t\t'position=\"' + cx + ',' + cy + '\" ' +\r\n\t\t\t\t\t\t\t\t'color2=\"' + color2 + '\" ';\r\n\r\n\t\t\t\t\t\t\taddFillNode();\r\n\t\t\t\t\t\t};\r\n\r\n\t\t\t\t\t// Apply radial gradient\r\n\t\t\t\t\tif (wrapper.added) {\r\n\t\t\t\t\t\tapplyRadialGradient();\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\t// We need to know the bounding box to get the size and position right\r\n\t\t\t\t\t\taddEvent(wrapper, 'add', applyRadialGradient);\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t// The fill element's color attribute is broken in IE8 standards mode, so we\r\n\t\t\t\t\t// need to set the parent shape's fillcolor attribute instead.\r\n\t\t\t\t\tret = color1;\r\n\t\t\t\t}\r\n\r\n\t\t\t// Gradients are not supported for VML stroke, return the first color. #722.\r\n\t\t\t} else {\r\n\t\t\t\tret = stopColor;\r\n\t\t\t}\r\n\r\n\t\t// if the color is an rgba color, split it and add a fill node\r\n\t\t// to hold the opacity component\r\n\t\t} else if (regexRgba.test(color) && elem.tagName !== 'IMG') {\r\n\r\n\t\t\tcolorObject = Color(color);\r\n\r\n\t\t\tmarkup = ['<', prop, ' opacity=\"', colorObject.get('a'), '\"/>'];\r\n\t\t\tcreateElement(this.prepVML(markup), null, null, elem);\r\n\r\n\t\t\tret = colorObject.get('rgb');\r\n\r\n\r\n\t\t} else {\r\n\t\t\tvar propNodes = elem.getElementsByTagName(prop); // 'stroke' or 'fill' node\r\n\t\t\tif (propNodes.length) {\r\n\t\t\t\tpropNodes[0].opacity = 1;\r\n\t\t\t\tpropNodes[0].type = 'solid';\r\n\t\t\t}\r\n\t\t\tret = color;\r\n\t\t}\r\n\r\n\t\treturn ret;\r\n\t},\r\n\r\n\t/**\r\n\t * Take a VML string and prepare it for either IE8 or IE6/IE7.\r\n\t * @param {Array} markup A string array of the VML markup to prepare\r\n\t */\r\n\tprepVML: function (markup) {\r\n\t\tvar vmlStyle = 'display:inline-block;behavior:url(#default#VML);',\r\n\t\t\tisIE8 = this.isIE8;\r\n\r\n\t\tmarkup = markup.join('');\r\n\r\n\t\tif (isIE8) { // add xmlns and style inline\r\n\t\t\tmarkup = markup.replace('/>', ' xmlns=\"urn:schemas-microsoft-com:vml\" />');\r\n\t\t\tif (markup.indexOf('style=\"') === -1) {\r\n\t\t\t\tmarkup = markup.replace('/>', ' style=\"' + vmlStyle + '\" />');\r\n\t\t\t} else {\r\n\t\t\t\tmarkup = markup.replace('style=\"', 'style=\"' + vmlStyle);\r\n\t\t\t}\r\n\r\n\t\t} else { // add namespace\r\n\t\t\tmarkup = markup.replace('<', '<hcv:');\r\n\t\t}\r\n\r\n\t\treturn markup;\r\n\t},\r\n\r\n\t/**\r\n\t * Create rotated and aligned text\r\n\t * @param {String} str\r\n\t * @param {Number} x\r\n\t * @param {Number} y\r\n\t */\r\n\ttext: SVGRenderer.prototype.html,\r\n\r\n\t/**\r\n\t * Create and return a path element\r\n\t * @param {Array} path\r\n\t */\r\n\tpath: function (path) {\r\n\t\tvar attr = {\r\n\t\t\t// subpixel precision down to 0.1 (width and height = 1px)\r\n\t\t\tcoordsize: '10 10'\r\n\t\t};\r\n\t\tif (isArray(path)) {\r\n\t\t\tattr.d = path;\r\n\t\t} else if (isObject(path)) { // attributes\r\n\t\t\textend(attr, path);\r\n\t\t}\r\n\t\t// create the shape\r\n\t\treturn this.createElement('shape').attr(attr);\r\n\t},\r\n\r\n\t/**\r\n\t * Create and return a circle element. In VML circles are implemented as\r\n\t * shapes, which is faster than v:oval\r\n\t * @param {Number} x\r\n\t * @param {Number} y\r\n\t * @param {Number} r\r\n\t */\r\n\tcircle: function (x, y, r) {\r\n\t\tvar circle = this.symbol('circle');\r\n\t\tif (isObject(x)) {\r\n\t\t\tr = x.r;\r\n\t\t\ty = x.y;\r\n\t\t\tx = x.x;\r\n\t\t}\r\n\t\tcircle.isCircle = true; // Causes x and y to mean center (#1682)\r\n\t\tcircle.r = r;\r\n\t\treturn circle.attr({ x: x, y: y });\r\n\t},\r\n\r\n\t/**\r\n\t * Create a group using an outer div and an inner v:group to allow rotating\r\n\t * and flipping. A simple v:group would have problems with positioning\r\n\t * child HTML elements and CSS clip.\r\n\t *\r\n\t * @param {String} name The name of the group\r\n\t */\r\n\tg: function (name) {\r\n\t\tvar wrapper,\r\n\t\t\tattribs;\r\n\r\n\t\t// set the class name\r\n\t\tif (name) {\r\n\t\t\tattribs = { 'className': PREFIX + name, 'class': PREFIX + name };\r\n\t\t}\r\n\r\n\t\t// the div to hold HTML and clipping\r\n\t\twrapper = this.createElement(DIV).attr(attribs);\r\n\r\n\t\treturn wrapper;\r\n\t},\r\n\r\n\t/**\r\n\t * VML override to create a regular HTML image\r\n\t * @param {String} src\r\n\t * @param {Number} x\r\n\t * @param {Number} y\r\n\t * @param {Number} width\r\n\t * @param {Number} height\r\n\t */\r\n\timage: function (src, x, y, width, height) {\r\n\t\tvar obj = this.createElement('img')\r\n\t\t\t.attr({ src: src });\r\n\r\n\t\tif (arguments.length > 1) {\r\n\t\t\tobj.attr({\r\n\t\t\t\tx: x,\r\n\t\t\t\ty: y,\r\n\t\t\t\twidth: width,\r\n\t\t\t\theight: height\r\n\t\t\t});\r\n\t\t}\r\n\t\treturn obj;\r\n\t},\r\n\r\n\t/**\r\n\t * VML uses a shape for rect to overcome bugs and rotation problems\r\n\t */\r\n\trect: function (x, y, width, height, r, strokeWidth) {\r\n\r\n\t\tvar wrapper = this.symbol('rect');\r\n\t\twrapper.r = isObject(x) ? x.r : r;\r\n\r\n\t\t//return wrapper.attr(wrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0)));\r\n\t\treturn wrapper.attr(\r\n\t\t\t\tisObject(x) ?\r\n\t\t\t\t\tx :\r\n\t\t\t\t\t// do not crispify when an object is passed in (as in column charts)\r\n\t\t\t\t\twrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0))\r\n\t\t\t);\r\n\t},\r\n\r\n\t/**\r\n\t * In the VML renderer, each child of an inverted div (group) is inverted\r\n\t * @param {Object} element\r\n\t * @param {Object} parentNode\r\n\t */\r\n\tinvertChild: function (element, parentNode) {\r\n\t\tvar parentStyle = parentNode.style;\r\n\t\tcss(element, {\r\n\t\t\tflip: 'x',\r\n\t\t\tleft: pInt(parentStyle.width) - 1,\r\n\t\t\ttop: pInt(parentStyle.height) - 1,\r\n\t\t\trotation: -90\r\n\t\t});\r\n\t},\r\n\r\n\t/**\r\n\t * Symbol definitions that override the parent SVG renderer's symbols\r\n\t *\r\n\t */\r\n\tsymbols: {\r\n\t\t// VML specific arc function\r\n\t\tarc: function (x, y, w, h, options) {\r\n\t\t\tvar start = options.start,\r\n\t\t\t\tend = options.end,\r\n\t\t\t\tradius = options.r || w || h,\r\n\t\t\t\tinnerRadius = options.innerR,\r\n\t\t\t\tcosStart = mathCos(start),\r\n\t\t\t\tsinStart = mathSin(start),\r\n\t\t\t\tcosEnd = mathCos(end),\r\n\t\t\t\tsinEnd = mathSin(end),\r\n\t\t\t\tret;\r\n\r\n\t\t\tif (end - start === 0) { // no angle, don't show it.\r\n\t\t\t\treturn ['x'];\r\n\t\t\t}\r\n\r\n\t\t\tret = [\r\n\t\t\t\t'wa', // clockwise arc to\r\n\t\t\t\tx - radius, // left\r\n\t\t\t\ty - radius, // top\r\n\t\t\t\tx + radius, // right\r\n\t\t\t\ty + radius, // bottom\r\n\t\t\t\tx + radius * cosStart, // start x\r\n\t\t\t\ty + radius * sinStart, // start y\r\n\t\t\t\tx + radius * cosEnd, // end x\r\n\t\t\t\ty + radius * sinEnd  // end y\r\n\t\t\t];\r\n\r\n\t\t\tif (options.open && !innerRadius) {\r\n\t\t\t\tret.push(\r\n\t\t\t\t\t'e',\r\n\t\t\t\t\tM,\r\n\t\t\t\t\tx,// - innerRadius,\r\n\t\t\t\t\ty// - innerRadius\r\n\t\t\t\t);\r\n\t\t\t}\r\n\r\n\t\t\tret.push(\r\n\t\t\t\t'at', // anti clockwise arc to\r\n\t\t\t\tx - innerRadius, // left\r\n\t\t\t\ty - innerRadius, // top\r\n\t\t\t\tx + innerRadius, // right\r\n\t\t\t\ty + innerRadius, // bottom\r\n\t\t\t\tx + innerRadius * cosEnd, // start x\r\n\t\t\t\ty + innerRadius * sinEnd, // start y\r\n\t\t\t\tx + innerRadius * cosStart, // end x\r\n\t\t\t\ty + innerRadius * sinStart, // end y\r\n\t\t\t\t'x', // finish path\r\n\t\t\t\t'e' // close\r\n\t\t\t);\r\n\r\n\t\t\tret.isArc = true;\r\n\t\t\treturn ret;\r\n\r\n\t\t},\r\n\t\t// Add circle symbol path. This performs significantly faster than v:oval.\r\n\t\tcircle: function (x, y, w, h, wrapper) {\r\n\r\n\t\t\tif (wrapper) {\r\n\t\t\t\tw = h = 2 * wrapper.r;\r\n\t\t\t}\r\n\r\n\t\t\t// Center correction, #1682\r\n\t\t\tif (wrapper && wrapper.isCircle) {\r\n\t\t\t\tx -= w / 2;\r\n\t\t\t\ty -= h / 2;\r\n\t\t\t}\r\n\r\n\t\t\t// Return the path\r\n\t\t\treturn [\r\n\t\t\t\t'wa', // clockwisearcto\r\n\t\t\t\tx, // left\r\n\t\t\t\ty, // top\r\n\t\t\t\tx + w, // right\r\n\t\t\t\ty + h, // bottom\r\n\t\t\t\tx + w, // start x\r\n\t\t\t\ty + h / 2,     // start y\r\n\t\t\t\tx + w, // end x\r\n\t\t\t\ty + h / 2,     // end y\r\n\t\t\t\t//'x', // finish path\r\n\t\t\t\t'e' // close\r\n\t\t\t];\r\n\t\t},\r\n\t\t/**\r\n\t\t * Add rectangle symbol path which eases rotation and omits arcsize problems\r\n\t\t * compared to the built-in VML roundrect shape\r\n\t\t *\r\n\t\t * @param {Number} left Left position\r\n\t\t * @param {Number} top Top position\r\n\t\t * @param {Number} r Border radius\r\n\t\t * @param {Object} options Width and height\r\n\t\t */\r\n\r\n\t\trect: function (left, top, width, height, options) {\r\n\r\n\t\t\tvar right = left + width,\r\n\t\t\t\tbottom = top + height,\r\n\t\t\t\tret,\r\n\t\t\t\tr;\r\n\r\n\t\t\t// No radius, return the more lightweight square\r\n\t\t\tif (!defined(options) || !options.r) {\r\n\t\t\t\tret = SVGRenderer.prototype.symbols.square.apply(0, arguments);\r\n\r\n\t\t\t// Has radius add arcs for the corners\r\n\t\t\t} else {\r\n\r\n\t\t\t\tr = mathMin(options.r, width, height);\r\n\t\t\t\tret = [\r\n\t\t\t\t\tM,\r\n\t\t\t\t\tleft + r, top,\r\n\r\n\t\t\t\t\tL,\r\n\t\t\t\t\tright - r, top,\r\n\t\t\t\t\t'wa',\r\n\t\t\t\t\tright - 2 * r, top,\r\n\t\t\t\t\tright, top + 2 * r,\r\n\t\t\t\t\tright - r, top,\r\n\t\t\t\t\tright, top + r,\r\n\r\n\t\t\t\t\tL,\r\n\t\t\t\t\tright, bottom - r,\r\n\t\t\t\t\t'wa',\r\n\t\t\t\t\tright - 2 * r, bottom - 2 * r,\r\n\t\t\t\t\tright, bottom,\r\n\t\t\t\t\tright, bottom - r,\r\n\t\t\t\t\tright - r, bottom,\r\n\r\n\t\t\t\t\tL,\r\n\t\t\t\t\tleft + r, bottom,\r\n\t\t\t\t\t'wa',\r\n\t\t\t\t\tleft, bottom - 2 * r,\r\n\t\t\t\t\tleft + 2 * r, bottom,\r\n\t\t\t\t\tleft + r, bottom,\r\n\t\t\t\t\tleft, bottom - r,\r\n\r\n\t\t\t\t\tL,\r\n\t\t\t\t\tleft, top + r,\r\n\t\t\t\t\t'wa',\r\n\t\t\t\t\tleft, top,\r\n\t\t\t\t\tleft + 2 * r, top + 2 * r,\r\n\t\t\t\t\tleft, top + r,\r\n\t\t\t\t\tleft + r, top,\r\n\r\n\r\n\t\t\t\t\t'x',\r\n\t\t\t\t\t'e'\r\n\t\t\t\t];\r\n\t\t\t}\r\n\t\t\treturn ret;\r\n\t\t}\r\n\t}\r\n};\r\nHighcharts.VMLRenderer = VMLRenderer = function () {\r\n\tthis.init.apply(this, arguments);\r\n};\r\nVMLRenderer.prototype = merge(SVGRenderer.prototype, VMLRendererExtension);\r\n\r\n\t// general renderer\r\n\tRenderer = VMLRenderer;\r\n}\r\n\r\n/* ****************************************************************************\r\n *                                                                            *\r\n * END OF INTERNET EXPLORER <= 8 SPECIFIC CODE                                *\r\n *                                                                            *\r\n *****************************************************************************/\r\n/* ****************************************************************************\r\n *                                                                            *\r\n * START OF ANDROID < 3 SPECIFIC CODE. THIS CAN BE REMOVED IF YOU'RE NOT      *\r\n * TARGETING THAT SYSTEM.                                                     *\r\n *                                                                            *\r\n *****************************************************************************/\r\nvar CanVGRenderer,\r\n\tCanVGController;\r\n\r\nif (useCanVG) {\r\n\t/**\r\n\t * The CanVGRenderer is empty from start to keep the source footprint small.\r\n\t * When requested, the CanVGController downloads the rest of the source packaged\r\n\t * together with the canvg library.\r\n\t */\r\n\tHighcharts.CanVGRenderer = CanVGRenderer = function () {\r\n\t\t// Override the global SVG namespace to fake SVG/HTML that accepts CSS\r\n\t\tSVG_NS = 'http://www.w3.org/1999/xhtml';\r\n\t};\r\n\r\n\t/**\r\n\t * Start with an empty symbols object. This is needed when exporting is used (exporting.src.js will add a few symbols), but \r\n\t * the implementation from SvgRenderer will not be merged in until first render.\r\n\t */\r\n\tCanVGRenderer.prototype.symbols = {};\r\n\r\n\t/**\r\n\t * Handles on demand download of canvg rendering support.\r\n\t */\r\n\tCanVGController = (function () {\r\n\t\t// List of renderering calls\r\n\t\tvar deferredRenderCalls = [];\r\n\r\n\t\t/**\r\n\t\t * When downloaded, we are ready to draw deferred charts.\r\n\t\t */\r\n\t\tfunction drawDeferred() {\r\n\t\t\tvar callLength = deferredRenderCalls.length,\r\n\t\t\t\tcallIndex;\r\n\r\n\t\t\t// Draw all pending render calls\r\n\t\t\tfor (callIndex = 0; callIndex < callLength; callIndex++) {\r\n\t\t\t\tdeferredRenderCalls[callIndex]();\r\n\t\t\t}\r\n\t\t\t// Clear the list\r\n\t\t\tdeferredRenderCalls = [];\r\n\t\t}\r\n\r\n\t\treturn {\r\n\t\t\tpush: function (func, scriptLocation) {\r\n\t\t\t\t// Only get the script once\r\n\t\t\t\tif (deferredRenderCalls.length === 0) {\r\n\t\t\t\t\tgetScript(scriptLocation, drawDeferred);\r\n\t\t\t\t}\r\n\t\t\t\t// Register render call\r\n\t\t\t\tdeferredRenderCalls.push(func);\r\n\t\t\t}\r\n\t\t};\r\n\t}());\r\n\r\n\tRenderer = CanVGRenderer;\r\n} // end CanVGRenderer\r\n\r\n/* ****************************************************************************\r\n *                                                                            *\r\n * END OF ANDROID < 3 SPECIFIC CODE                                           *\r\n *                                                                            *\r\n *****************************************************************************/\r\n\r\n/**\r\n * The Tick class\r\n */\r\nfunction Tick(axis, pos, type, noLabel) {\r\n\tthis.axis = axis;\r\n\tthis.pos = pos;\r\n\tthis.type = type || '';\r\n\tthis.isNew = true;\r\n\r\n\tif (!type && !noLabel) {\r\n\t\tthis.addLabel();\r\n\t}\r\n}\r\n\r\nTick.prototype = {\r\n\t/**\r\n\t * Write the tick label\r\n\t */\r\n\taddLabel: function () {\r\n\t\tvar tick = this,\r\n\t\t\taxis = tick.axis,\r\n\t\t\toptions = axis.options,\r\n\t\t\tchart = axis.chart,\r\n\t\t\thoriz = axis.horiz,\r\n\t\t\tcategories = axis.categories,\r\n\t\t\tnames = axis.series[0] && axis.series[0].names,\r\n\t\t\tpos = tick.pos,\r\n\t\t\tlabelOptions = options.labels,\r\n\t\t\tstr,\r\n\t\t\ttickPositions = axis.tickPositions,\r\n\t\t\twidth = (horiz && categories &&\r\n\t\t\t\t!labelOptions.step && !labelOptions.staggerLines &&\r\n\t\t\t\t!labelOptions.rotation &&\r\n\t\t\t\tchart.plotWidth / tickPositions.length) ||\r\n\t\t\t\t(!horiz && (chart.margin[3] || chart.chartWidth * 0.33)), // #1580, #1931\r\n\t\t\tisFirst = pos === tickPositions[0],\r\n\t\t\tisLast = pos === tickPositions[tickPositions.length - 1],\r\n\t\t\tcss,\r\n\t\t\tattr,\r\n\t\t\tvalue = categories ?\r\n\t\t\t\tpick(categories[pos], names && names[pos], pos) : \r\n\t\t\t\tpos,\r\n\t\t\tlabel = tick.label,\r\n\t\t\ttickPositionInfo = tickPositions.info,\r\n\t\t\tdateTimeLabelFormat;\r\n\r\n\t\t// Set the datetime label format. If a higher rank is set for this position, use that. If not,\r\n\t\t// use the general format.\r\n\t\tif (axis.isDatetimeAxis && tickPositionInfo) {\r\n\t\t\tdateTimeLabelFormat = options.dateTimeLabelFormats[tickPositionInfo.higherRanks[pos] || tickPositionInfo.unitName];\r\n\t\t}\r\n\r\n\t\t// set properties for access in render method\r\n\t\ttick.isFirst = isFirst;\r\n\t\ttick.isLast = isLast;\r\n\r\n\t\t// get the string\r\n\t\tstr = axis.labelFormatter.call({\r\n\t\t\taxis: axis,\r\n\t\t\tchart: chart,\r\n\t\t\tisFirst: isFirst,\r\n\t\t\tisLast: isLast,\r\n\t\t\tdateTimeLabelFormat: dateTimeLabelFormat,\r\n\t\t\tvalue: axis.isLog ? correctFloat(lin2log(value)) : value\r\n\t\t});\r\n\r\n\t\t// prepare CSS\r\n\t\tcss = width && { width: mathMax(1, mathRound(width - 2 * (labelOptions.padding || 10))) + PX };\r\n\t\tcss = extend(css, labelOptions.style);\r\n\r\n\t\t// first call\r\n\t\tif (!defined(label)) {\r\n\t\t\tattr = {\r\n\t\t\t\talign: axis.labelAlign\r\n\t\t\t};\r\n\t\t\tif (isNumber(labelOptions.rotation)) {\r\n\t\t\t\tattr.rotation = labelOptions.rotation;\r\n\t\t\t}\r\n\t\t\tif (width && labelOptions.ellipsis) {\r\n\t\t\t\tattr._clipHeight = axis.len / tickPositions.length;\r\n\t\t\t}\r\n\r\n\t\t\ttick.label =\r\n\t\t\t\tdefined(str) && labelOptions.enabled ?\r\n\t\t\t\t\tchart.renderer.text(\r\n\t\t\t\t\t\t\tstr,\r\n\t\t\t\t\t\t\t0,\r\n\t\t\t\t\t\t\t0,\r\n\t\t\t\t\t\t\tlabelOptions.useHTML\r\n\t\t\t\t\t\t)\r\n\t\t\t\t\t\t.attr(attr)\r\n\t\t\t\t\t\t// without position absolute, IE export sometimes is wrong\r\n\t\t\t\t\t\t.css(css)\r\n\t\t\t\t\t\t.add(axis.labelGroup) :\r\n\t\t\t\t\tnull;\r\n\r\n\t\t// update\r\n\t\t} else if (label) {\r\n\t\t\tlabel.attr({\r\n\t\t\t\t\ttext: str\r\n\t\t\t\t})\r\n\t\t\t\t.css(css);\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Get the offset height or width of the label\r\n\t */\r\n\tgetLabelSize: function () {\r\n\t\tvar label = this.label,\r\n\t\t\taxis = this.axis;\r\n\t\treturn label ?\r\n\t\t\t((this.labelBBox = label.getBBox()))[axis.horiz ? 'height' : 'width'] :\r\n\t\t\t0;\r\n\t},\r\n\r\n\t/**\r\n\t * Find how far the labels extend to the right and left of the tick's x position. Used for anti-collision\r\n\t * detection with overflow logic.\r\n\t */\r\n\tgetLabelSides: function () {\r\n\t\tvar bBox = this.labelBBox, // assume getLabelSize has run at this point\r\n\t\t\taxis = this.axis,\r\n\t\t\toptions = axis.options,\r\n\t\t\tlabelOptions = options.labels,\r\n\t\t\twidth = bBox.width,\r\n\t\t\tleftSide = width * { left: 0, center: 0.5, right: 1 }[axis.labelAlign] - labelOptions.x;\r\n\r\n\t\treturn [-leftSide, width - leftSide];\r\n\t},\r\n\r\n\t/**\r\n\t * Handle the label overflow by adjusting the labels to the left and right edge, or\r\n\t * hide them if they collide into the neighbour label.\r\n\t */\r\n\thandleOverflow: function (index, xy) {\r\n\t\tvar show = true,\r\n\t\t\taxis = this.axis,\r\n\t\t\tchart = axis.chart,\r\n\t\t\tisFirst = this.isFirst,\r\n\t\t\tisLast = this.isLast,\r\n\t\t\tx = xy.x,\r\n\t\t\treversed = axis.reversed,\r\n\t\t\ttickPositions = axis.tickPositions;\r\n\r\n\t\tif (isFirst || isLast) {\r\n\r\n\t\t\tvar sides = this.getLabelSides(),\r\n\t\t\t\tleftSide = sides[0],\r\n\t\t\t\trightSide = sides[1],\r\n\t\t\t\tplotLeft = chart.plotLeft,\r\n\t\t\t\tplotRight = plotLeft + axis.len,\r\n\t\t\t\tneighbour = axis.ticks[tickPositions[index + (isFirst ? 1 : -1)]],\r\n\t\t\t\tneighbourEdge = neighbour && neighbour.label.xy && neighbour.label.xy.x + neighbour.getLabelSides()[isFirst ? 0 : 1];\r\n\r\n\t\t\tif ((isFirst && !reversed) || (isLast && reversed)) {\r\n\t\t\t\t// Is the label spilling out to the left of the plot area?\r\n\t\t\t\tif (x + leftSide < plotLeft) {\r\n\r\n\t\t\t\t\t// Align it to plot left\r\n\t\t\t\t\tx = plotLeft - leftSide;\r\n\r\n\t\t\t\t\t// Hide it if it now overlaps the neighbour label\r\n\t\t\t\t\tif (neighbour && x + rightSide > neighbourEdge) {\r\n\t\t\t\t\t\tshow = false;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t} else {\r\n\t\t\t\t// Is the label spilling out to the right of the plot area?\r\n\t\t\t\tif (x + rightSide > plotRight) {\r\n\r\n\t\t\t\t\t// Align it to plot right\r\n\t\t\t\t\tx = plotRight - rightSide;\r\n\r\n\t\t\t\t\t// Hide it if it now overlaps the neighbour label\r\n\t\t\t\t\tif (neighbour && x + leftSide < neighbourEdge) {\r\n\t\t\t\t\t\tshow = false;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// Set the modified x position of the label\r\n\t\t\txy.x = x;\r\n\t\t}\r\n\t\treturn show;\r\n\t},\r\n\r\n\t/**\r\n\t * Get the x and y position for ticks and labels\r\n\t */\r\n\tgetPosition: function (horiz, pos, tickmarkOffset, old) {\r\n\t\tvar axis = this.axis,\r\n\t\t\tchart = axis.chart,\r\n\t\t\tcHeight = (old && chart.oldChartHeight) || chart.chartHeight;\r\n\t\t\r\n\t\treturn {\r\n\t\t\tx: horiz ?\r\n\t\t\t\taxis.translate(pos + tickmarkOffset, null, null, old) + axis.transB :\r\n\t\t\t\taxis.left + axis.offset + (axis.opposite ? ((old && chart.oldChartWidth) || chart.chartWidth) - axis.right - axis.left : 0),\r\n\r\n\t\t\ty: horiz ?\r\n\t\t\t\tcHeight - axis.bottom + axis.offset - (axis.opposite ? axis.height : 0) :\r\n\t\t\t\tcHeight - axis.translate(pos + tickmarkOffset, null, null, old) - axis.transB\r\n\t\t};\r\n\t\t\r\n\t},\r\n\t\r\n\t/**\r\n\t * Get the x, y position of the tick label\r\n\t */\r\n\tgetLabelPosition: function (x, y, label, horiz, labelOptions, tickmarkOffset, index, step) {\r\n\t\tvar axis = this.axis,\r\n\t\t\ttransA = axis.transA,\r\n\t\t\treversed = axis.reversed,\r\n\t\t\tstaggerLines = axis.staggerLines,\r\n\t\t\tbaseline = axis.chart.renderer.fontMetrics(labelOptions.style.fontSize).b,\r\n\t\t\trotation = labelOptions.rotation;\r\n\t\t\t\r\n\t\tx = x + labelOptions.x - (tickmarkOffset && horiz ?\r\n\t\t\ttickmarkOffset * transA * (reversed ? -1 : 1) : 0);\r\n\t\ty = y + labelOptions.y - (tickmarkOffset && !horiz ?\r\n\t\t\ttickmarkOffset * transA * (reversed ? 1 : -1) : 0);\r\n\r\n\t\t// Correct for rotation (#1764)\r\n\t\tif (rotation && axis.side === 2) {\r\n\t\t\ty -= baseline - baseline * mathCos(rotation * deg2rad);\r\n\t\t}\r\n\t\t\r\n\t\t// Vertically centered\r\n\t\tif (!defined(labelOptions.y) && !rotation) { // #1951\r\n\t\t\ty += baseline - label.getBBox().height / 2;\r\n\t\t}\r\n\t\t\r\n\t\t// Correct for staggered labels\r\n\t\tif (staggerLines) {\r\n\t\t\ty += (index / (step || 1) % staggerLines) * (axis.labelOffset / staggerLines);\r\n\t\t}\r\n\t\t\r\n\t\treturn {\r\n\t\t\tx: x,\r\n\t\t\ty: y\r\n\t\t};\r\n\t},\r\n\t\r\n\t/**\r\n\t * Extendible method to return the path of the marker\r\n\t */\r\n\tgetMarkPath: function (x, y, tickLength, tickWidth, horiz, renderer) {\r\n\t\treturn renderer.crispLine([\r\n\t\t\t\tM,\r\n\t\t\t\tx,\r\n\t\t\t\ty,\r\n\t\t\t\tL,\r\n\t\t\t\tx + (horiz ? 0 : -tickLength),\r\n\t\t\t\ty + (horiz ? tickLength : 0)\r\n\t\t\t], tickWidth);\r\n\t},\r\n\r\n\t/**\r\n\t * Put everything in place\r\n\t *\r\n\t * @param index {Number}\r\n\t * @param old {Boolean} Use old coordinates to prepare an animation into new position\r\n\t */\r\n\trender: function (index, old, opacity) {\r\n\t\tvar tick = this,\r\n\t\t\taxis = tick.axis,\r\n\t\t\toptions = axis.options,\r\n\t\t\tchart = axis.chart,\r\n\t\t\trenderer = chart.renderer,\r\n\t\t\thoriz = axis.horiz,\r\n\t\t\ttype = tick.type,\r\n\t\t\tlabel = tick.label,\r\n\t\t\tpos = tick.pos,\r\n\t\t\tlabelOptions = options.labels,\r\n\t\t\tgridLine = tick.gridLine,\r\n\t\t\tgridPrefix = type ? type + 'Grid' : 'grid',\r\n\t\t\ttickPrefix = type ? type + 'Tick' : 'tick',\r\n\t\t\tgridLineWidth = options[gridPrefix + 'LineWidth'],\r\n\t\t\tgridLineColor = options[gridPrefix + 'LineColor'],\r\n\t\t\tdashStyle = options[gridPrefix + 'LineDashStyle'],\r\n\t\t\ttickLength = options[tickPrefix + 'Length'],\r\n\t\t\ttickWidth = options[tickPrefix + 'Width'] || 0,\r\n\t\t\ttickColor = options[tickPrefix + 'Color'],\r\n\t\t\ttickPosition = options[tickPrefix + 'Position'],\r\n\t\t\tgridLinePath,\r\n\t\t\tmark = tick.mark,\r\n\t\t\tmarkPath,\r\n\t\t\tstep = labelOptions.step,\r\n\t\t\tattribs,\r\n\t\t\tshow = true,\r\n\t\t\ttickmarkOffset = axis.tickmarkOffset,\r\n\t\t\txy = tick.getPosition(horiz, pos, tickmarkOffset, old),\r\n\t\t\tx = xy.x,\r\n\t\t\ty = xy.y,\r\n\t\t\treverseCrisp = ((horiz && x === axis.pos + axis.len) || (!horiz && y === axis.pos)) ? -1 : 1, // #1480, #1687\r\n\t\t\tstaggerLines = axis.staggerLines;\r\n\r\n\t\tthis.isActive = true;\r\n\t\t\r\n\t\t// create the grid line\r\n\t\tif (gridLineWidth) {\r\n\t\t\tgridLinePath = axis.getPlotLinePath(pos + tickmarkOffset, gridLineWidth * reverseCrisp, old, true);\r\n\r\n\t\t\tif (gridLine === UNDEFINED) {\r\n\t\t\t\tattribs = {\r\n\t\t\t\t\tstroke: gridLineColor,\r\n\t\t\t\t\t'stroke-width': gridLineWidth\r\n\t\t\t\t};\r\n\t\t\t\tif (dashStyle) {\r\n\t\t\t\t\tattribs.dashstyle = dashStyle;\r\n\t\t\t\t}\r\n\t\t\t\tif (!type) {\r\n\t\t\t\t\tattribs.zIndex = 1;\r\n\t\t\t\t}\r\n\t\t\t\tif (old) {\r\n\t\t\t\t\tattribs.opacity = 0;\r\n\t\t\t\t}\r\n\t\t\t\ttick.gridLine = gridLine =\r\n\t\t\t\t\tgridLineWidth ?\r\n\t\t\t\t\t\trenderer.path(gridLinePath)\r\n\t\t\t\t\t\t\t.attr(attribs).add(axis.gridGroup) :\r\n\t\t\t\t\t\tnull;\r\n\t\t\t}\r\n\r\n\t\t\t// If the parameter 'old' is set, the current call will be followed\r\n\t\t\t// by another call, therefore do not do any animations this time\r\n\t\t\tif (!old && gridLine && gridLinePath) {\r\n\t\t\t\tgridLine[tick.isNew ? 'attr' : 'animate']({\r\n\t\t\t\t\td: gridLinePath,\r\n\t\t\t\t\topacity: opacity\r\n\t\t\t\t});\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// create the tick mark\r\n\t\tif (tickWidth && tickLength) {\r\n\r\n\t\t\t// negate the length\r\n\t\t\tif (tickPosition === 'inside') {\r\n\t\t\t\ttickLength = -tickLength;\r\n\t\t\t}\r\n\t\t\tif (axis.opposite) {\r\n\t\t\t\ttickLength = -tickLength;\r\n\t\t\t}\r\n\r\n\t\t\tmarkPath = tick.getMarkPath(x, y, tickLength, tickWidth * reverseCrisp, horiz, renderer);\r\n\r\n\t\t\tif (mark) { // updating\r\n\t\t\t\tmark.animate({\r\n\t\t\t\t\td: markPath,\r\n\t\t\t\t\topacity: opacity\r\n\t\t\t\t});\r\n\t\t\t} else { // first time\r\n\t\t\t\ttick.mark = renderer.path(\r\n\t\t\t\t\tmarkPath\r\n\t\t\t\t).attr({\r\n\t\t\t\t\tstroke: tickColor,\r\n\t\t\t\t\t'stroke-width': tickWidth,\r\n\t\t\t\t\topacity: opacity\r\n\t\t\t\t}).add(axis.axisGroup);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// the label is created on init - now move it into place\r\n\t\tif (label && !isNaN(x)) {\r\n\t\t\tlabel.xy = xy = tick.getLabelPosition(x, y, label, horiz, labelOptions, tickmarkOffset, index, step);\r\n\r\n\t\t\t// Apply show first and show last. If the tick is both first and last, it is \r\n\t\t\t// a single centered tick, in which case we show the label anyway (#2100).\r\n\t\t\tif ((tick.isFirst && !tick.isLast && !pick(options.showFirstLabel, 1)) ||\r\n\t\t\t\t\t(tick.isLast && !tick.isFirst && !pick(options.showLastLabel, 1))) {\r\n\t\t\t\tshow = false;\r\n\r\n\t\t\t// Handle label overflow and show or hide accordingly\r\n\t\t\t} else if (!staggerLines && horiz && labelOptions.overflow === 'justify' && !tick.handleOverflow(index, xy)) {\r\n\t\t\t\tshow = false;\r\n\t\t\t}\r\n\r\n\t\t\t// apply step\r\n\t\t\tif (step && index % step) {\r\n\t\t\t\t// show those indices dividable by step\r\n\t\t\t\tshow = false;\r\n\t\t\t}\r\n\r\n\t\t\t// Set the new position, and show or hide\r\n\t\t\tif (show && !isNaN(xy.y)) {\r\n\t\t\t\txy.opacity = opacity;\r\n\t\t\t\tlabel[tick.isNew ? 'attr' : 'animate'](xy);\r\n\t\t\t\ttick.isNew = false;\r\n\t\t\t} else {\r\n\t\t\t\tlabel.attr('y', -9999); // #1338\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Destructor for the tick prototype\r\n\t */\r\n\tdestroy: function () {\r\n\t\tdestroyObjectProperties(this, this.axis);\r\n\t}\r\n};\r\n\r\n/**\r\n * The object wrapper for plot lines and plot bands\r\n * @param {Object} options\r\n */\r\nfunction PlotLineOrBand(axis, options) {\r\n\tthis.axis = axis;\r\n\r\n\tif (options) {\r\n\t\tthis.options = options;\r\n\t\tthis.id = options.id;\r\n\t}\r\n}\r\n\r\nPlotLineOrBand.prototype = {\r\n\t\r\n\t/**\r\n\t * Render the plot line or plot band. If it is already existing,\r\n\t * move it.\r\n\t */\r\n\trender: function () {\r\n\t\tvar plotLine = this,\r\n\t\t\taxis = plotLine.axis,\r\n\t\t\thoriz = axis.horiz,\r\n\t\t\thalfPointRange = (axis.pointRange || 0) / 2,\r\n\t\t\toptions = plotLine.options,\r\n\t\t\toptionsLabel = options.label,\r\n\t\t\tlabel = plotLine.label,\r\n\t\t\twidth = options.width,\r\n\t\t\tto = options.to,\r\n\t\t\tfrom = options.from,\r\n\t\t\tisBand = defined(from) && defined(to),\r\n\t\t\tvalue = options.value,\r\n\t\t\tdashStyle = options.dashStyle,\r\n\t\t\tsvgElem = plotLine.svgElem,\r\n\t\t\tpath = [],\r\n\t\t\taddEvent,\r\n\t\t\teventType,\r\n\t\t\txs,\r\n\t\t\tys,\r\n\t\t\tx,\r\n\t\t\ty,\r\n\t\t\tcolor = options.color,\r\n\t\t\tzIndex = options.zIndex,\r\n\t\t\tevents = options.events,\r\n\t\t\tattribs,\r\n\t\t\trenderer = axis.chart.renderer;\r\n\r\n\t\t// logarithmic conversion\r\n\t\tif (axis.isLog) {\r\n\t\t\tfrom = log2lin(from);\r\n\t\t\tto = log2lin(to);\r\n\t\t\tvalue = log2lin(value);\r\n\t\t}\r\n\r\n\t\t// plot line\r\n\t\tif (width) {\r\n\t\t\tpath = axis.getPlotLinePath(value, width);\r\n\t\t\tattribs = {\r\n\t\t\t\tstroke: color,\r\n\t\t\t\t'stroke-width': width\r\n\t\t\t};\r\n\t\t\tif (dashStyle) {\r\n\t\t\t\tattribs.dashstyle = dashStyle;\r\n\t\t\t}\r\n\t\t} else if (isBand) { // plot band\r\n\t\t\t\r\n\t\t\t// keep within plot area\r\n\t\t\tfrom = mathMax(from, axis.min - halfPointRange);\r\n\t\t\tto = mathMin(to, axis.max + halfPointRange);\r\n\t\t\t\r\n\t\t\tpath = axis.getPlotBandPath(from, to, options);\r\n\t\t\tattribs = {\r\n\t\t\t\tfill: color\r\n\t\t\t};\r\n\t\t\tif (options.borderWidth) {\r\n\t\t\t\tattribs.stroke = options.borderColor;\r\n\t\t\t\tattribs['stroke-width'] = options.borderWidth;\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\t// zIndex\r\n\t\tif (defined(zIndex)) {\r\n\t\t\tattribs.zIndex = zIndex;\r\n\t\t}\r\n\r\n\t\t// common for lines and bands\r\n\t\tif (svgElem) {\r\n\t\t\tif (path) {\r\n\t\t\t\tsvgElem.animate({\r\n\t\t\t\t\td: path\r\n\t\t\t\t}, null, svgElem.onGetPath);\r\n\t\t\t} else {\r\n\t\t\t\tsvgElem.hide();\r\n\t\t\t\tsvgElem.onGetPath = function () {\r\n\t\t\t\t\tsvgElem.show();\r\n\t\t\t\t};\r\n\t\t\t}\r\n\t\t} else if (path && path.length) {\r\n\t\t\tplotLine.svgElem = svgElem = renderer.path(path)\r\n\t\t\t\t.attr(attribs).add();\r\n\r\n\t\t\t// events\r\n\t\t\tif (events) {\r\n\t\t\t\taddEvent = function (eventType) {\r\n\t\t\t\t\tsvgElem.on(eventType, function (e) {\r\n\t\t\t\t\t\tevents[eventType].apply(plotLine, [e]);\r\n\t\t\t\t\t});\r\n\t\t\t\t};\r\n\t\t\t\tfor (eventType in events) {\r\n\t\t\t\t\taddEvent(eventType);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// the plot band/line label\r\n\t\tif (optionsLabel && defined(optionsLabel.text) && path && path.length && axis.width > 0 && axis.height > 0) {\r\n\t\t\t// apply defaults\r\n\t\t\toptionsLabel = merge({\r\n\t\t\t\talign: horiz && isBand && 'center',\r\n\t\t\t\tx: horiz ? !isBand && 4 : 10,\r\n\t\t\t\tverticalAlign : !horiz && isBand && 'middle',\r\n\t\t\t\ty: horiz ? isBand ? 16 : 10 : isBand ? 6 : -4,\r\n\t\t\t\trotation: horiz && !isBand && 90\r\n\t\t\t}, optionsLabel);\r\n\r\n\t\t\t// add the SVG element\r\n\t\t\tif (!label) {\r\n\t\t\t\tplotLine.label = label = renderer.text(\r\n\t\t\t\t\t\toptionsLabel.text,\r\n\t\t\t\t\t\t0,\r\n\t\t\t\t\t\t0,\r\n\t\t\t\t\t\toptionsLabel.useHTML\r\n\t\t\t\t\t)\r\n\t\t\t\t\t.attr({\r\n\t\t\t\t\t\talign: optionsLabel.textAlign || optionsLabel.align,\r\n\t\t\t\t\t\trotation: optionsLabel.rotation,\r\n\t\t\t\t\t\tzIndex: zIndex\r\n\t\t\t\t\t})\r\n\t\t\t\t\t.css(optionsLabel.style)\r\n\t\t\t\t\t.add();\r\n\t\t\t}\r\n\r\n\t\t\t// get the bounding box and align the label\r\n\t\t\txs = [path[1], path[4], pick(path[6], path[1])];\r\n\t\t\tys = [path[2], path[5], pick(path[7], path[2])];\r\n\t\t\tx = arrayMin(xs);\r\n\t\t\ty = arrayMin(ys);\r\n\r\n\t\t\tlabel.align(optionsLabel, false, {\r\n\t\t\t\tx: x,\r\n\t\t\t\ty: y,\r\n\t\t\t\twidth: arrayMax(xs) - x,\r\n\t\t\t\theight: arrayMax(ys) - y\r\n\t\t\t});\r\n\t\t\tlabel.show();\r\n\r\n\t\t} else if (label) { // move out of sight\r\n\t\t\tlabel.hide();\r\n\t\t}\r\n\r\n\t\t// chainable\r\n\t\treturn plotLine;\r\n\t},\r\n\r\n\t/**\r\n\t * Remove the plot line or band\r\n\t */\r\n\tdestroy: function () {\r\n\t\t// remove it from the lookup\r\n\t\terase(this.axis.plotLinesAndBands, this);\r\n\t\t\r\n\t\tdelete this.axis;\r\n\t\tdestroyObjectProperties(this);\r\n\t}\r\n};\r\n/**\r\n * The class for stack items\r\n */\r\nfunction StackItem(axis, options, isNegative, x, stackOption, stacking) {\r\n\t\r\n\tvar inverted = axis.chart.inverted;\r\n\r\n\tthis.axis = axis;\r\n\r\n\t// Tells if the stack is negative\r\n\tthis.isNegative = isNegative;\r\n\r\n\t// Save the options to be able to style the label\r\n\tthis.options = options;\r\n\r\n\t// Save the x value to be able to position the label later\r\n\tthis.x = x;\r\n\r\n\t// Initialize total value\r\n\tthis.total = null;\r\n\r\n\t// This will keep each points' extremes stored by series.index\r\n\tthis.points = {};\r\n\r\n\t// Save the stack option on the series configuration object, and whether to treat it as percent\r\n\tthis.stack = stackOption;\r\n\tthis.percent = stacking === 'percent';\r\n\r\n\t// The align options and text align varies on whether the stack is negative and\r\n\t// if the chart is inverted or not.\r\n\t// First test the user supplied value, then use the dynamic.\r\n\tthis.alignOptions = {\r\n\t\talign: options.align || (inverted ? (isNegative ? 'left' : 'right') : 'center'),\r\n\t\tverticalAlign: options.verticalAlign || (inverted ? 'middle' : (isNegative ? 'bottom' : 'top')),\r\n\t\ty: pick(options.y, inverted ? 4 : (isNegative ? 14 : -6)),\r\n\t\tx: pick(options.x, inverted ? (isNegative ? -6 : 6) : 0)\r\n\t};\r\n\r\n\tthis.textAlign = options.textAlign || (inverted ? (isNegative ? 'right' : 'left') : 'center');\r\n}\r\n\r\nStackItem.prototype = {\r\n\tdestroy: function () {\r\n\t\tdestroyObjectProperties(this, this.axis);\r\n\t},\r\n\r\n\t/**\r\n\t * Renders the stack total label and adds it to the stack label group.\r\n\t */\r\n\trender: function (group) {\r\n\t\tvar options = this.options,\r\n\t\t\tformatOption = options.format,\r\n\t\t\tstr = formatOption ?\r\n\t\t\t\tformat(formatOption, this) : \r\n\t\t\t\toptions.formatter.call(this);  // format the text in the label\r\n\r\n\t\t// Change the text to reflect the new total and set visibility to hidden in case the serie is hidden\r\n\t\tif (this.label) {\r\n\t\t\tthis.label.attr({text: str, visibility: HIDDEN});\r\n\t\t// Create new label\r\n\t\t} else {\r\n\t\t\tthis.label =\r\n\t\t\t\tthis.axis.chart.renderer.text(str, 0, 0, options.useHTML)\t\t// dummy positions, actual position updated with setOffset method in columnseries\r\n\t\t\t\t\t.css(options.style)\t\t\t\t// apply style\r\n\t\t\t\t\t.attr({\r\n\t\t\t\t\t\talign: this.textAlign,\t\t\t\t// fix the text-anchor\r\n\t\t\t\t\t\trotation: options.rotation,\t// rotation\r\n\t\t\t\t\t\tvisibility: HIDDEN\t\t\t\t\t// hidden until setOffset is called\r\n\t\t\t\t\t})\t\t\t\t\r\n\t\t\t\t\t.add(group);\t\t\t\t\t\t\t// add to the labels-group\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Sets the offset that the stack has from the x value and repositions the label.\r\n\t */\r\n\tsetOffset: function (xOffset, xWidth) {\r\n\t\tvar stackItem = this,\r\n\t\t\taxis = stackItem.axis,\r\n\t\t\tchart = axis.chart,\r\n\t\t\tinverted = chart.inverted,\r\n\t\t\tneg = this.isNegative,\t\t\t\t\t\t\t// special treatment is needed for negative stacks\r\n\t\t\ty = axis.translate(this.percent ? 100 : this.total, 0, 0, 0, 1), // stack value translated mapped to chart coordinates\r\n\t\t\tyZero = axis.translate(0),\t\t\t\t\t\t// stack origin\r\n\t\t\th = mathAbs(y - yZero),\t\t\t\t\t\t\t// stack height\r\n\t\t\tx = chart.xAxis[0].translate(this.x) + xOffset,\t// stack x position\r\n\t\t\tplotHeight = chart.plotHeight,\r\n\t\t\tstackBox = {\t// this is the box for the complete stack\r\n\t\t\t\tx: inverted ? (neg ? y : y - h) : x,\r\n\t\t\t\ty: inverted ? plotHeight - x - xWidth : (neg ? (plotHeight - y - h) : plotHeight - y),\r\n\t\t\t\twidth: inverted ? h : xWidth,\r\n\t\t\t\theight: inverted ? xWidth : h\r\n\t\t\t},\r\n\t\t\tlabel = this.label,\r\n\t\t\talignAttr;\r\n\t\t\r\n\t\tif (label) {\r\n\t\t\tlabel.align(this.alignOptions, null, stackBox);\t// align the label to the box\r\n\t\t\t\t\r\n\t\t\t// Set visibility (#678)\r\n\t\t\talignAttr = label.alignAttr;\r\n\t\t\tlabel.attr({ \r\n\t\t\t\tvisibility: this.options.crop === false || chart.isInsidePlot(alignAttr.x, alignAttr.y) ? \r\n\t\t\t\t\t(hasSVG ? 'inherit' : VISIBLE) : \r\n\t\t\t\t\tHIDDEN\r\n\t\t\t});\r\n\t\t}\r\n\t}\r\n};\r\n/**\r\n * Create a new axis object\r\n * @param {Object} chart\r\n * @param {Object} options\r\n */\r\nfunction Axis() {\r\n\tthis.init.apply(this, arguments);\r\n}\r\n\r\nAxis.prototype = {\r\n\t\r\n\t/**\r\n\t * Default options for the X axis - the Y axis has extended defaults \r\n\t */\r\n\tdefaultOptions: {\r\n\t\t// allowDecimals: null,\r\n\t\t// alternateGridColor: null,\r\n\t\t// categories: [],\r\n\t\tdateTimeLabelFormats: {\r\n\t\t\tmillisecond: '%H:%M:%S.%L',\r\n\t\t\tsecond: '%H:%M:%S',\r\n\t\t\tminute: '%H:%M',\r\n\t\t\thour: '%H:%M',\r\n\t\t\tday: '%e. %b',\r\n\t\t\tweek: '%e. %b',\r\n\t\t\tmonth: '%b \\'%y',\r\n\t\t\tyear: '%Y'\r\n\t\t},\r\n\t\tendOnTick: false,\r\n\t\tgridLineColor: '#C0C0C0',\r\n\t\t// gridLineDashStyle: 'solid',\r\n\t\t// gridLineWidth: 0,\r\n\t\t// reversed: false,\r\n\t\r\n\t\tlabels: defaultLabelOptions,\r\n\t\t\t// { step: null },\r\n\t\tlineColor: '#C0D0E0',\r\n\t\tlineWidth: 1,\r\n\t\t//linkedTo: null,\r\n\t\t//max: undefined,\r\n\t\t//min: undefined,\r\n\t\tminPadding: 0.01,\r\n\t\tmaxPadding: 0.01,\r\n\t\t//minRange: null,\r\n\t\tminorGridLineColor: '#E0E0E0',\r\n\t\t// minorGridLineDashStyle: null,\r\n\t\tminorGridLineWidth: 1,\r\n\t\tminorTickColor: '#A0A0A0',\r\n\t\t//minorTickInterval: null,\r\n\t\tminorTickLength: 2,\r\n\t\tminorTickPosition: 'outside', // inside or outside\r\n\t\t//minorTickWidth: 0,\r\n\t\t//opposite: false,\r\n\t\t//offset: 0,\r\n\t\t//plotBands: [{\r\n\t\t//\tevents: {},\r\n\t\t//\tzIndex: 1,\r\n\t\t//\tlabels: { align, x, verticalAlign, y, style, rotation, textAlign }\r\n\t\t//}],\r\n\t\t//plotLines: [{\r\n\t\t//\tevents: {}\r\n\t\t//  dashStyle: {}\r\n\t\t//\tzIndex:\r\n\t\t//\tlabels: { align, x, verticalAlign, y, style, rotation, textAlign }\r\n\t\t//}],\r\n\t\t//reversed: false,\r\n\t\t// showFirstLabel: true,\r\n\t\t// showLastLabel: true,\r\n\t\tstartOfWeek: 1,\r\n\t\tstartOnTick: false,\r\n\t\ttickColor: '#C0D0E0',\r\n\t\t//tickInterval: null,\r\n\t\ttickLength: 5,\r\n\t\ttickmarkPlacement: 'between', // on or between\r\n\t\ttickPixelInterval: 100,\r\n\t\ttickPosition: 'outside',\r\n\t\ttickWidth: 1,\r\n\t\ttitle: {\r\n\t\t\t//text: null,\r\n\t\t\talign: 'middle', // low, middle or high\r\n\t\t\t//margin: 0 for horizontal, 10 for vertical axes,\r\n\t\t\t//rotation: 0,\r\n\t\t\t//side: 'outside',\r\n\t\t\tstyle: {\r\n\t\t\t\tcolor: '#4d759e',\r\n\t\t\t\t//font: defaultFont.replace('normal', 'bold')\r\n\t\t\t\tfontWeight: 'bold'\r\n\t\t\t}\r\n\t\t\t//x: 0,\r\n\t\t\t//y: 0\r\n\t\t},\r\n\t\ttype: 'linear' // linear, logarithmic or datetime\r\n\t},\r\n\t\r\n\t/**\r\n\t * This options set extends the defaultOptions for Y axes\r\n\t */\r\n\tdefaultYAxisOptions: {\r\n\t\tendOnTick: true,\r\n\t\tgridLineWidth: 1,\r\n\t\ttickPixelInterval: 72,\r\n\t\tshowLastLabel: true,\r\n\t\tlabels: {\r\n\t\t\tx: -8,\r\n\t\t\ty: 3\r\n\t\t},\r\n\t\tlineWidth: 0,\r\n\t\tmaxPadding: 0.05,\r\n\t\tminPadding: 0.05,\r\n\t\tstartOnTick: true,\r\n\t\ttickWidth: 0,\r\n\t\ttitle: {\r\n\t\t\trotation: 270,\r\n\t\t\ttext: 'Values'\r\n\t\t},\r\n\t\tstackLabels: {\r\n\t\t\tenabled: false,\r\n\t\t\t//align: dynamic,\r\n\t\t\t//y: dynamic,\r\n\t\t\t//x: dynamic,\r\n\t\t\t//verticalAlign: dynamic,\r\n\t\t\t//textAlign: dynamic,\r\n\t\t\t//rotation: 0,\r\n\t\t\tformatter: function () {\r\n\t\t\t\treturn numberFormat(this.total, -1);\r\n\t\t\t},\r\n\t\t\tstyle: defaultLabelOptions.style\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * These options extend the defaultOptions for left axes\r\n\t */\r\n\tdefaultLeftAxisOptions: {\r\n\t\tlabels: {\r\n\t\t\tx: -8,\r\n\t\t\ty: null\r\n\t\t},\r\n\t\ttitle: {\r\n\t\t\trotation: 270\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * These options extend the defaultOptions for right axes\r\n\t */\r\n\tdefaultRightAxisOptions: {\r\n\t\tlabels: {\r\n\t\t\tx: 8,\r\n\t\t\ty: null\r\n\t\t},\r\n\t\ttitle: {\r\n\t\t\trotation: 90\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * These options extend the defaultOptions for bottom axes\r\n\t */\r\n\tdefaultBottomAxisOptions: {\r\n\t\tlabels: {\r\n\t\t\tx: 0,\r\n\t\t\ty: 14\r\n\t\t\t// overflow: undefined,\r\n\t\t\t// staggerLines: null\r\n\t\t},\r\n\t\ttitle: {\r\n\t\t\trotation: 0\r\n\t\t}\r\n\t},\r\n\t/**\r\n\t * These options extend the defaultOptions for left axes\r\n\t */\r\n\tdefaultTopAxisOptions: {\r\n\t\tlabels: {\r\n\t\t\tx: 0,\r\n\t\t\ty: -5\r\n\t\t\t// overflow: undefined\r\n\t\t\t// staggerLines: null\r\n\t\t},\r\n\t\ttitle: {\r\n\t\t\trotation: 0\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Initialize the axis\r\n\t */\r\n\tinit: function (chart, userOptions) {\r\n\t\t\t\r\n\t\t\r\n\t\tvar isXAxis = userOptions.isX,\r\n\t\t\taxis = this;\r\n\t\r\n\t\t// Flag, is the axis horizontal\r\n\t\taxis.horiz = chart.inverted ? !isXAxis : isXAxis;\r\n\t\t\r\n\t\t// Flag, isXAxis\r\n\t\taxis.isXAxis = isXAxis;\r\n\t\taxis.xOrY = isXAxis ? 'x' : 'y';\r\n\t\r\n\t\r\n\t\taxis.opposite = userOptions.opposite; // needed in setOptions\r\n\t\taxis.side = axis.horiz ?\r\n\t\t\t\t(axis.opposite ? 0 : 2) : // top : bottom\r\n\t\t\t\t(axis.opposite ? 1 : 3);  // right : left\r\n\t\r\n\t\taxis.setOptions(userOptions);\r\n\t\t\r\n\t\r\n\t\tvar options = this.options,\r\n\t\t\ttype = options.type,\r\n\t\t\tisDatetimeAxis = type === 'datetime';\r\n\t\r\n\t\taxis.labelFormatter = options.labels.formatter || axis.defaultLabelFormatter; // can be overwritten by dynamic format\r\n\t\r\n\t\r\n\t\t// Flag, stagger lines or not\r\n\t\taxis.userOptions = userOptions;\r\n\t\r\n\t\t//axis.axisTitleMargin = UNDEFINED,// = options.title.margin,\r\n\t\taxis.minPixelPadding = 0;\r\n\t\t//axis.ignoreMinPadding = UNDEFINED; // can be set to true by a column or bar series\r\n\t\t//axis.ignoreMaxPadding = UNDEFINED;\r\n\t\r\n\t\taxis.chart = chart;\r\n\t\taxis.reversed = options.reversed;\r\n\t\taxis.zoomEnabled = options.zoomEnabled !== false;\r\n\t\r\n\t\t// Initial categories\r\n\t\taxis.categories = options.categories || type === 'category';\r\n\t\r\n\t\t// Elements\r\n\t\t//axis.axisGroup = UNDEFINED;\r\n\t\t//axis.gridGroup = UNDEFINED;\r\n\t\t//axis.axisTitle = UNDEFINED;\r\n\t\t//axis.axisLine = UNDEFINED;\r\n\t\r\n\t\t// Shorthand types\r\n\t\taxis.isLog = type === 'logarithmic';\r\n\t\taxis.isDatetimeAxis = isDatetimeAxis;\r\n\t\r\n\t\t// Flag, if axis is linked to another axis\r\n\t\taxis.isLinked = defined(options.linkedTo);\r\n\t\t// Linked axis.\r\n\t\t//axis.linkedParent = UNDEFINED;\t\r\n\t\t\r\n\t\t// Tick positions\r\n\t\t//axis.tickPositions = UNDEFINED; // array containing predefined positions\r\n\t\t// Tick intervals\r\n\t\t//axis.tickInterval = UNDEFINED;\r\n\t\t//axis.minorTickInterval = UNDEFINED;\r\n\t\t\r\n\t\taxis.tickmarkOffset = (axis.categories && options.tickmarkPlacement === 'between') ? 0.5 : 0;\r\n\t\r\n\t\t// Major ticks\r\n\t\taxis.ticks = {};\r\n\t\t// Minor ticks\r\n\t\taxis.minorTicks = {};\r\n\t\t//axis.tickAmount = UNDEFINED;\r\n\t\r\n\t\t// List of plotLines/Bands\r\n\t\taxis.plotLinesAndBands = [];\r\n\t\r\n\t\t// Alternate bands\r\n\t\taxis.alternateBands = {};\r\n\t\r\n\t\t// Axis metrics\r\n\t\t//axis.left = UNDEFINED;\r\n\t\t//axis.top = UNDEFINED;\r\n\t\t//axis.width = UNDEFINED;\r\n\t\t//axis.height = UNDEFINED;\r\n\t\t//axis.bottom = UNDEFINED;\r\n\t\t//axis.right = UNDEFINED;\r\n\t\t//axis.transA = UNDEFINED;\r\n\t\t//axis.transB = UNDEFINED;\r\n\t\t//axis.oldTransA = UNDEFINED;\r\n\t\taxis.len = 0;\r\n\t\t//axis.oldMin = UNDEFINED;\r\n\t\t//axis.oldMax = UNDEFINED;\r\n\t\t//axis.oldUserMin = UNDEFINED;\r\n\t\t//axis.oldUserMax = UNDEFINED;\r\n\t\t//axis.oldAxisLength = UNDEFINED;\r\n\t\taxis.minRange = axis.userMinRange = options.minRange || options.maxZoom;\r\n\t\taxis.range = options.range;\r\n\t\taxis.offset = options.offset || 0;\r\n\t\r\n\t\r\n\t\t// Dictionary for stacks\r\n\t\taxis.stacks = {};\r\n\t\taxis.oldStacks = {};\r\n\r\n\t\t// Dictionary for stacks max values\r\n\t\taxis.stackExtremes = {};\r\n\r\n\t\t// Min and max in the data\r\n\t\t//axis.dataMin = UNDEFINED,\r\n\t\t//axis.dataMax = UNDEFINED,\r\n\t\r\n\t\t// The axis range\r\n\t\taxis.max = null;\r\n\t\taxis.min = null;\r\n\t\r\n\t\t// User set min and max\r\n\t\t//axis.userMin = UNDEFINED,\r\n\t\t//axis.userMax = UNDEFINED,\r\n\r\n\t\t// Run Axis\r\n\t\t\r\n\t\tvar eventType,\r\n\t\t\tevents = axis.options.events;\r\n\r\n\t\t// Register\r\n\t\tif (inArray(axis, chart.axes) === -1) { // don't add it again on Axis.update()\r\n\t\t\tchart.axes.push(axis);\r\n\t\t\tchart[isXAxis ? 'xAxis' : 'yAxis'].push(axis);\r\n\t\t}\r\n\r\n\t\taxis.series = axis.series || []; // populated by Series\r\n\r\n\t\t// inverted charts have reversed xAxes as default\r\n\t\tif (chart.inverted && isXAxis && axis.reversed === UNDEFINED) {\r\n\t\t\taxis.reversed = true;\r\n\t\t}\r\n\r\n\t\taxis.removePlotBand = axis.removePlotBandOrLine;\r\n\t\taxis.removePlotLine = axis.removePlotBandOrLine;\r\n\r\n\r\n\t\t// register event listeners\r\n\t\tfor (eventType in events) {\r\n\t\t\taddEvent(axis, eventType, events[eventType]);\r\n\t\t}\r\n\r\n\t\t// extend logarithmic axis\r\n\t\tif (axis.isLog) {\r\n\t\t\taxis.val2lin = log2lin;\r\n\t\t\taxis.lin2val = lin2log;\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Merge and set options\r\n\t */\r\n\tsetOptions: function (userOptions) {\r\n\t\tthis.options = merge(\r\n\t\t\tthis.defaultOptions,\r\n\t\t\tthis.isXAxis ? {} : this.defaultYAxisOptions,\r\n\t\t\t[this.defaultTopAxisOptions, this.defaultRightAxisOptions,\r\n\t\t\t\tthis.defaultBottomAxisOptions, this.defaultLeftAxisOptions][this.side],\r\n\t\t\tmerge(\r\n\t\t\t\tdefaultOptions[this.isXAxis ? 'xAxis' : 'yAxis'], // if set in setOptions (#1053)\r\n\t\t\t\tuserOptions\r\n\t\t\t)\r\n\t\t);\r\n\t},\r\n\r\n\t/**\r\n\t * Update the axis with a new options structure\r\n\t */\r\n\tupdate: function (newOptions, redraw) {\r\n\t\tvar chart = this.chart;\r\n\r\n\t\tnewOptions = chart.options[this.xOrY + 'Axis'][this.options.index] = merge(this.userOptions, newOptions);\r\n\r\n\t\tthis.destroy(true);\r\n\t\tthis._addedPlotLB = this.userMin = this.userMax = UNDEFINED; // #1611, #2306\r\n\r\n\t\tthis.init(chart, extend(newOptions, { events: UNDEFINED }));\r\n\r\n\t\tchart.isDirtyBox = true;\r\n\t\tif (pick(redraw, true)) {\r\n\t\t\tchart.redraw();\r\n\t\t}\r\n\t},\t\r\n\t\r\n\t/**\r\n     * Remove the axis from the chart\r\n     */\r\n\tremove: function (redraw) {\r\n\t\tvar chart = this.chart,\r\n\t\t\tkey = this.xOrY + 'Axis'; // xAxis or yAxis\r\n\r\n\t\t// Remove associated series\r\n\t\teach(this.series, function (series) {\r\n\t\t\tseries.remove(false);\r\n\t\t});\r\n\r\n\t\t// Remove the axis\r\n\t\terase(chart.axes, this);\r\n\t\terase(chart[key], this);\r\n\t\tchart.options[key].splice(this.options.index, 1);\r\n\t\teach(chart[key], function (axis, i) { // Re-index, #1706\r\n\t\t\taxis.options.index = i;\r\n\t\t});\r\n\t\tthis.destroy();\r\n\t\tchart.isDirtyBox = true;\r\n\r\n\t\tif (pick(redraw, true)) {\r\n\t\t\tchart.redraw();\r\n\t\t}\r\n\t},\r\n\t\r\n\t/** \r\n\t * The default label formatter. The context is a special config object for the label.\r\n\t */\r\n\tdefaultLabelFormatter: function () {\r\n\t\tvar axis = this.axis,\r\n\t\t\tvalue = this.value,\r\n\t\t\tcategories = axis.categories, \r\n\t\t\tdateTimeLabelFormat = this.dateTimeLabelFormat,\r\n\t\t\tnumericSymbols = defaultOptions.lang.numericSymbols,\r\n\t\t\ti = numericSymbols && numericSymbols.length,\r\n\t\t\tmulti,\r\n\t\t\tret,\r\n\t\t\tformatOption = axis.options.labels.format,\r\n\t\t\t\r\n\t\t\t// make sure the same symbol is added for all labels on a linear axis\r\n\t\t\tnumericSymbolDetector = axis.isLog ? value : axis.tickInterval;\r\n\r\n\t\tif (formatOption) {\r\n\t\t\tret = format(formatOption, this);\r\n\t\t\r\n\t\t} else if (categories) {\r\n\t\t\tret = value;\r\n\t\t\r\n\t\t} else if (dateTimeLabelFormat) { // datetime axis\r\n\t\t\tret = dateFormat(dateTimeLabelFormat, value);\r\n\t\t\r\n\t\t} else if (i && numericSymbolDetector >= 1000) {\r\n\t\t\t// Decide whether we should add a numeric symbol like k (thousands) or M (millions).\r\n\t\t\t// If we are to enable this in tooltip or other places as well, we can move this\r\n\t\t\t// logic to the numberFormatter and enable it by a parameter.\r\n\t\t\twhile (i-- && ret === UNDEFINED) {\r\n\t\t\t\tmulti = Math.pow(1000, i + 1);\r\n\t\t\t\tif (numericSymbolDetector >= multi && numericSymbols[i] !== null) {\r\n\t\t\t\t\tret = numberFormat(value / multi, -1) + numericSymbols[i];\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tif (ret === UNDEFINED) {\r\n\t\t\tif (value >= 1000) { // add thousands separators\r\n\t\t\t\tret = numberFormat(value, 0);\r\n\r\n\t\t\t} else { // small numbers\r\n\t\t\t\tret = numberFormat(value, -1);\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\treturn ret;\r\n\t},\r\n\r\n\t/**\r\n\t * Get the minimum and maximum for the series of each axis\r\n\t */\r\n\tgetSeriesExtremes: function () {\r\n\t\tvar axis = this,\r\n\t\t\tchart = axis.chart;\r\n\r\n\t\taxis.hasVisibleSeries = false;\r\n\r\n\t\t// reset dataMin and dataMax in case we're redrawing\r\n\t\taxis.dataMin = axis.dataMax = null;\r\n\r\n\t\t// reset cached stacking extremes\r\n\t\taxis.stackExtremes = {};\r\n\r\n\t\taxis.buildStacks();\r\n\r\n\t\t// loop through this axis' series\r\n\t\teach(axis.series, function (series) {\r\n\r\n\t\t\tif (series.visible || !chart.options.chart.ignoreHiddenSeries) {\r\n\r\n\t\t\t\tvar seriesOptions = series.options,\r\n\t\t\t\t\txData,\r\n\t\t\t\t\tthreshold = seriesOptions.threshold,\r\n\t\t\t\t\tseriesDataMin,\r\n\t\t\t\t\tseriesDataMax;\r\n\r\n\t\t\t\taxis.hasVisibleSeries = true;\r\n\r\n\t\t\t\t// Validate threshold in logarithmic axes\r\n\t\t\t\tif (axis.isLog && threshold <= 0) {\r\n\t\t\t\t\tthreshold = null;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Get dataMin and dataMax for X axes\r\n\t\t\t\tif (axis.isXAxis) {\r\n\t\t\t\t\txData = series.xData;\r\n\t\t\t\t\tif (xData.length) {\r\n\t\t\t\t\t\taxis.dataMin = mathMin(pick(axis.dataMin, xData[0]), arrayMin(xData));\r\n\t\t\t\t\t\taxis.dataMax = mathMax(pick(axis.dataMax, xData[0]), arrayMax(xData));\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t// Get dataMin and dataMax for Y axes, as well as handle stacking and processed data\r\n\t\t\t\t} else {\r\n\r\n\t\t\t\t\t// Get this particular series extremes\r\n\t\t\t\t\tseries.getExtremes();\r\n\t\t\t\t\tseriesDataMax = series.dataMax;\r\n\t\t\t\t\tseriesDataMin = series.dataMin;\r\n\r\n\t\t\t\t\t// Get the dataMin and dataMax so far. If percentage is used, the min and max are\r\n\t\t\t\t\t// always 0 and 100. If seriesDataMin and seriesDataMax is null, then series\r\n\t\t\t\t\t// doesn't have active y data, we continue with nulls\r\n\t\t\t\t\tif (defined(seriesDataMin) && defined(seriesDataMax)) {\r\n\t\t\t\t\t\taxis.dataMin = mathMin(pick(axis.dataMin, seriesDataMin), seriesDataMin);\r\n\t\t\t\t\t\taxis.dataMax = mathMax(pick(axis.dataMax, seriesDataMax), seriesDataMax);\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t// Adjust to threshold\r\n\t\t\t\t\tif (defined(threshold)) {\r\n\t\t\t\t\t\tif (axis.dataMin >= threshold) {\r\n\t\t\t\t\t\t\taxis.dataMin = threshold;\r\n\t\t\t\t\t\t\taxis.ignoreMinPadding = true;\r\n\t\t\t\t\t\t} else if (axis.dataMax < threshold) {\r\n\t\t\t\t\t\t\taxis.dataMax = threshold;\r\n\t\t\t\t\t\t\taxis.ignoreMaxPadding = true;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t});\r\n\t},\r\n\r\n\t/**\r\n\t * Translate from axis value to pixel position on the chart, or back\r\n\t *\r\n\t */\r\n\ttranslate: function (val, backwards, cvsCoord, old, handleLog, pointPlacement) {\r\n\t\tvar axis = this,\r\n\t\t\taxisLength = axis.len,\r\n\t\t\tsign = 1,\r\n\t\t\tcvsOffset = 0,\r\n\t\t\tlocalA = old ? axis.oldTransA : axis.transA,\r\n\t\t\tlocalMin = old ? axis.oldMin : axis.min,\r\n\t\t\treturnValue,\r\n\t\t\tminPixelPadding = axis.minPixelPadding,\r\n\t\t\tpostTranslate = (axis.options.ordinal || (axis.isLog && handleLog)) && axis.lin2val;\r\n\r\n\t\tif (!localA) {\r\n\t\t\tlocalA = axis.transA;\r\n\t\t}\r\n\r\n\t\t// In vertical axes, the canvas coordinates start from 0 at the top like in \r\n\t\t// SVG. \r\n\t\tif (cvsCoord) {\r\n\t\t\tsign *= -1; // canvas coordinates inverts the value\r\n\t\t\tcvsOffset = axisLength;\r\n\t\t}\r\n\r\n\t\t// Handle reversed axis\r\n\t\tif (axis.reversed) { \r\n\t\t\tsign *= -1;\r\n\t\t\tcvsOffset -= sign * axisLength;\r\n\t\t}\r\n\r\n\t\t// From pixels to value\r\n\t\tif (backwards) { // reverse translation\r\n\t\t\t\r\n\t\t\tval = val * sign + cvsOffset;\r\n\t\t\tval -= minPixelPadding;\r\n\t\t\treturnValue = val / localA + localMin; // from chart pixel to value\r\n\t\t\tif (postTranslate) { // log and ordinal axes\r\n\t\t\t\treturnValue = axis.lin2val(returnValue);\r\n\t\t\t}\r\n\r\n\t\t// From value to pixels\r\n\t\t} else {\r\n\t\t\tif (postTranslate) { // log and ordinal axes\r\n\t\t\t\tval = axis.val2lin(val);\r\n\t\t\t}\r\n\t\t\tif (pointPlacement === 'between') {\r\n\t\t\t\tpointPlacement = 0.5;\r\n\t\t\t}\r\n\t\t\treturnValue = sign * (val - localMin) * localA + cvsOffset + (sign * minPixelPadding) +\r\n\t\t\t\t(isNumber(pointPlacement) ? localA * pointPlacement * axis.pointRange : 0);\r\n\t\t}\r\n\r\n\t\treturn returnValue;\r\n\t},\r\n\r\n\t/**\r\n\t * Utility method to translate an axis value to pixel position. \r\n\t * @param {Number} value A value in terms of axis units\r\n\t * @param {Boolean} paneCoordinates Whether to return the pixel coordinate relative to the chart\r\n\t *        or just the axis/pane itself.\r\n\t */\r\n\ttoPixels: function (value, paneCoordinates) {\r\n\t\treturn this.translate(value, false, !this.horiz, null, true) + (paneCoordinates ? 0 : this.pos);\r\n\t},\r\n\r\n\t/*\r\n\t * Utility method to translate a pixel position in to an axis value\r\n\t * @param {Number} pixel The pixel value coordinate\r\n\t * @param {Boolean} paneCoordiantes Whether the input pixel is relative to the chart or just the\r\n\t *        axis/pane itself.\r\n\t */\r\n\ttoValue: function (pixel, paneCoordinates) {\r\n\t\treturn this.translate(pixel - (paneCoordinates ? 0 : this.pos), true, !this.horiz, null, true);\r\n\t},\r\n\r\n\t/**\r\n\t * Create the path for a plot line that goes from the given value on\r\n\t * this axis, across the plot to the opposite side\r\n\t * @param {Number} value\r\n\t * @param {Number} lineWidth Used for calculation crisp line\r\n\t * @param {Number] old Use old coordinates (for resizing and rescaling)\r\n\t */\r\n\tgetPlotLinePath: function (value, lineWidth, old, force) {\r\n\t\tvar axis = this,\r\n\t\t\tchart = axis.chart,\r\n\t\t\taxisLeft = axis.left,\r\n\t\t\taxisTop = axis.top,\r\n\t\t\tx1,\r\n\t\t\ty1,\r\n\t\t\tx2,\r\n\t\t\ty2,\r\n\t\t\ttranslatedValue = axis.translate(value, null, null, old),\r\n\t\t\tcHeight = (old && chart.oldChartHeight) || chart.chartHeight,\r\n\t\t\tcWidth = (old && chart.oldChartWidth) || chart.chartWidth,\r\n\t\t\tskip,\r\n\t\t\ttransB = axis.transB;\r\n\r\n\t\tx1 = x2 = mathRound(translatedValue + transB);\r\n\t\ty1 = y2 = mathRound(cHeight - translatedValue - transB);\r\n\r\n\t\tif (isNaN(translatedValue)) { // no min or max\r\n\t\t\tskip = true;\r\n\r\n\t\t} else if (axis.horiz) {\r\n\t\t\ty1 = axisTop;\r\n\t\t\ty2 = cHeight - axis.bottom;\r\n\t\t\tif (x1 < axisLeft || x1 > axisLeft + axis.width) {\r\n\t\t\t\tskip = true;\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tx1 = axisLeft;\r\n\t\t\tx2 = cWidth - axis.right;\r\n\r\n\t\t\tif (y1 < axisTop || y1 > axisTop + axis.height) {\r\n\t\t\t\tskip = true;\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn skip && !force ?\r\n\t\t\tnull :\r\n\t\t\tchart.renderer.crispLine([M, x1, y1, L, x2, y2], lineWidth || 0);\r\n\t},\r\n\t\r\n\t/**\r\n\t * Create the path for a plot band\r\n\t */\r\n\tgetPlotBandPath: function (from, to) {\r\n\r\n\t\tvar toPath = this.getPlotLinePath(to),\r\n\t\t\tpath = this.getPlotLinePath(from);\r\n\t\t\t\r\n\t\tif (path && toPath) {\r\n\t\t\tpath.push(\r\n\t\t\t\ttoPath[4],\r\n\t\t\t\ttoPath[5],\r\n\t\t\t\ttoPath[1],\r\n\t\t\t\ttoPath[2]\r\n\t\t\t);\r\n\t\t} else { // outside the axis area\r\n\t\t\tpath = null;\r\n\t\t}\r\n\t\t\r\n\t\treturn path;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Set the tick positions of a linear axis to round values like whole tens or every five.\r\n\t */\r\n\tgetLinearTickPositions: function (tickInterval, min, max) {\r\n\t\tvar pos,\r\n\t\t\tlastPos,\r\n\t\t\troundedMin = correctFloat(mathFloor(min / tickInterval) * tickInterval),\r\n\t\t\troundedMax = correctFloat(mathCeil(max / tickInterval) * tickInterval),\r\n\t\t\ttickPositions = [];\r\n\r\n\t\t// Populate the intermediate values\r\n\t\tpos = roundedMin;\r\n\t\twhile (pos <= roundedMax) {\r\n\r\n\t\t\t// Place the tick on the rounded value\r\n\t\t\ttickPositions.push(pos);\r\n\r\n\t\t\t// Always add the raw tickInterval, not the corrected one.\r\n\t\t\tpos = correctFloat(pos + tickInterval);\r\n\r\n\t\t\t// If the interval is not big enough in the current min - max range to actually increase\r\n\t\t\t// the loop variable, we need to break out to prevent endless loop. Issue #619\r\n\t\t\tif (pos === lastPos) {\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\r\n\t\t\t// Record the last value\r\n\t\t\tlastPos = pos;\r\n\t\t}\r\n\t\treturn tickPositions;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Set the tick positions of a logarithmic axis\r\n\t */\r\n\tgetLogTickPositions: function (interval, min, max, minor) {\r\n\t\tvar axis = this,\r\n\t\t\toptions = axis.options,\r\n\t\t\taxisLength = axis.len,\r\n\t\t\t// Since we use this method for both major and minor ticks,\r\n\t\t\t// use a local variable and return the result\r\n\t\t\tpositions = []; \r\n\t\t\r\n\t\t// Reset\r\n\t\tif (!minor) {\r\n\t\t\taxis._minorAutoInterval = null;\r\n\t\t}\r\n\t\t\r\n\t\t// First case: All ticks fall on whole logarithms: 1, 10, 100 etc.\r\n\t\tif (interval >= 0.5) {\r\n\t\t\tinterval = mathRound(interval);\r\n\t\t\tpositions = axis.getLinearTickPositions(interval, min, max);\r\n\t\t\t\r\n\t\t// Second case: We need intermediary ticks. For example \r\n\t\t// 1, 2, 4, 6, 8, 10, 20, 40 etc. \r\n\t\t} else if (interval >= 0.08) {\r\n\t\t\tvar roundedMin = mathFloor(min),\r\n\t\t\t\tintermediate,\r\n\t\t\t\ti,\r\n\t\t\t\tj,\r\n\t\t\t\tlen,\r\n\t\t\t\tpos,\r\n\t\t\t\tlastPos,\r\n\t\t\t\tbreak2;\r\n\t\t\t\t\r\n\t\t\tif (interval > 0.3) {\r\n\t\t\t\tintermediate = [1, 2, 4];\r\n\t\t\t} else if (interval > 0.15) { // 0.2 equals five minor ticks per 1, 10, 100 etc\r\n\t\t\t\tintermediate = [1, 2, 4, 6, 8];\r\n\t\t\t} else { // 0.1 equals ten minor ticks per 1, 10, 100 etc\r\n\t\t\t\tintermediate = [1, 2, 3, 4, 5, 6, 7, 8, 9];\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tfor (i = roundedMin; i < max + 1 && !break2; i++) {\r\n\t\t\t\tlen = intermediate.length;\r\n\t\t\t\tfor (j = 0; j < len && !break2; j++) {\r\n\t\t\t\t\tpos = log2lin(lin2log(i) * intermediate[j]);\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (pos > min && (!minor || lastPos <= max)) { // #1670\r\n\t\t\t\t\t\tpositions.push(lastPos);\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (lastPos > max) {\r\n\t\t\t\t\t\tbreak2 = true;\r\n\t\t\t\t\t}\r\n\t\t\t\t\tlastPos = pos;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t// Third case: We are so deep in between whole logarithmic values that\r\n\t\t// we might as well handle the tick positions like a linear axis. For\r\n\t\t// example 1.01, 1.02, 1.03, 1.04.\r\n\t\t} else {\r\n\t\t\tvar realMin = lin2log(min),\r\n\t\t\t\trealMax = lin2log(max),\r\n\t\t\t\ttickIntervalOption = options[minor ? 'minorTickInterval' : 'tickInterval'],\r\n\t\t\t\tfilteredTickIntervalOption = tickIntervalOption === 'auto' ? null : tickIntervalOption,\r\n\t\t\t\ttickPixelIntervalOption = options.tickPixelInterval / (minor ? 5 : 1),\r\n\t\t\t\ttotalPixelLength = minor ? axisLength / axis.tickPositions.length : axisLength;\r\n\t\t\t\r\n\t\t\tinterval = pick(\r\n\t\t\t\tfilteredTickIntervalOption,\r\n\t\t\t\taxis._minorAutoInterval,\r\n\t\t\t\t(realMax - realMin) * tickPixelIntervalOption / (totalPixelLength || 1)\r\n\t\t\t);\r\n\t\t\t\r\n\t\t\tinterval = normalizeTickInterval(\r\n\t\t\t\tinterval, \r\n\t\t\t\tnull, \r\n\t\t\t\tgetMagnitude(interval)\r\n\t\t\t);\r\n\t\t\t\r\n\t\t\tpositions = map(axis.getLinearTickPositions(\r\n\t\t\t\tinterval, \r\n\t\t\t\trealMin,\r\n\t\t\t\trealMax\t\r\n\t\t\t), log2lin);\r\n\t\t\t\r\n\t\t\tif (!minor) {\r\n\t\t\t\taxis._minorAutoInterval = interval / 5;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// Set the axis-level tickInterval variable \r\n\t\tif (!minor) {\r\n\t\t\taxis.tickInterval = interval;\r\n\t\t}\r\n\t\treturn positions;\r\n\t},\r\n\r\n\t/**\r\n\t * Return the minor tick positions. For logarithmic axes, reuse the same logic\r\n\t * as for major ticks.\r\n\t */\r\n\tgetMinorTickPositions: function () {\r\n\t\tvar axis = this,\r\n\t\t\toptions = axis.options,\r\n\t\t\ttickPositions = axis.tickPositions,\r\n\t\t\tminorTickInterval = axis.minorTickInterval,\r\n\t\t\tminorTickPositions = [],\r\n\t\t\tpos,\r\n\t\t\ti,\r\n\t\t\tlen;\r\n\t\t\r\n\t\tif (axis.isLog) {\r\n\t\t\tlen = tickPositions.length;\r\n\t\t\tfor (i = 1; i < len; i++) {\r\n\t\t\t\tminorTickPositions = minorTickPositions.concat(\r\n\t\t\t\t\taxis.getLogTickPositions(minorTickInterval, tickPositions[i - 1], tickPositions[i], true)\r\n\t\t\t\t);\t\r\n\t\t\t}\r\n\t\t} else if (axis.isDatetimeAxis && options.minorTickInterval === 'auto') { // #1314\r\n\t\t\tminorTickPositions = minorTickPositions.concat(\r\n\t\t\t\tgetTimeTicks(\r\n\t\t\t\t\tnormalizeTimeTickInterval(minorTickInterval),\r\n\t\t\t\t\taxis.min,\r\n\t\t\t\t\taxis.max,\r\n\t\t\t\t\toptions.startOfWeek\r\n\t\t\t\t)\r\n\t\t\t);\r\n\t\t\tif (minorTickPositions[0] < axis.min) {\r\n\t\t\t\tminorTickPositions.shift();\r\n\t\t\t}\r\n\t\t} else {\t\t\t\r\n\t\t\tfor (pos = axis.min + (tickPositions[0] - axis.min) % minorTickInterval; pos <= axis.max; pos += minorTickInterval) {\r\n\t\t\t\tminorTickPositions.push(pos);\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn minorTickPositions;\r\n\t},\r\n\r\n\t/**\r\n\t * Adjust the min and max for the minimum range. Keep in mind that the series data is \r\n\t * not yet processed, so we don't have information on data cropping and grouping, or \r\n\t * updated axis.pointRange or series.pointRange. The data can't be processed until\r\n\t * we have finally established min and max.\r\n\t */\r\n\tadjustForMinRange: function () {\r\n\t\tvar axis = this,\r\n\t\t\toptions = axis.options,\r\n\t\t\tmin = axis.min,\r\n\t\t\tmax = axis.max,\r\n\t\t\tzoomOffset,\r\n\t\t\tspaceAvailable = axis.dataMax - axis.dataMin >= axis.minRange,\r\n\t\t\tclosestDataRange,\r\n\t\t\ti,\r\n\t\t\tdistance,\r\n\t\t\txData,\r\n\t\t\tloopLength,\r\n\t\t\tminArgs,\r\n\t\t\tmaxArgs;\r\n\r\n\t\t// Set the automatic minimum range based on the closest point distance\r\n\t\tif (axis.isXAxis && axis.minRange === UNDEFINED && !axis.isLog) {\r\n\r\n\t\t\tif (defined(options.min) || defined(options.max)) {\r\n\t\t\t\taxis.minRange = null; // don't do this again\r\n\r\n\t\t\t} else {\r\n\r\n\t\t\t\t// Find the closest distance between raw data points, as opposed to\r\n\t\t\t\t// closestPointRange that applies to processed points (cropped and grouped)\r\n\t\t\t\teach(axis.series, function (series) {\r\n\t\t\t\t\txData = series.xData;\r\n\t\t\t\t\tloopLength = series.xIncrement ? 1 : xData.length - 1;\r\n\t\t\t\t\tfor (i = loopLength; i > 0; i--) {\r\n\t\t\t\t\t\tdistance = xData[i] - xData[i - 1];\r\n\t\t\t\t\t\tif (closestDataRange === UNDEFINED || distance < closestDataRange) {\r\n\t\t\t\t\t\t\tclosestDataRange = distance;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t});\r\n\t\t\t\taxis.minRange = mathMin(closestDataRange * 5, axis.dataMax - axis.dataMin);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// if minRange is exceeded, adjust\r\n\t\tif (max - min < axis.minRange) {\r\n\t\t\tvar minRange = axis.minRange;\r\n\t\t\tzoomOffset = (minRange - max + min) / 2;\r\n\r\n\t\t\t// if min and max options have been set, don't go beyond it\r\n\t\t\tminArgs = [min - zoomOffset, pick(options.min, min - zoomOffset)];\r\n\t\t\tif (spaceAvailable) { // if space is available, stay within the data range\r\n\t\t\t\tminArgs[2] = axis.dataMin;\r\n\t\t\t}\r\n\t\t\tmin = arrayMax(minArgs);\r\n\r\n\t\t\tmaxArgs = [min + minRange, pick(options.max, min + minRange)];\r\n\t\t\tif (spaceAvailable) { // if space is availabe, stay within the data range\r\n\t\t\t\tmaxArgs[2] = axis.dataMax;\r\n\t\t\t}\r\n\r\n\t\t\tmax = arrayMin(maxArgs);\r\n\r\n\t\t\t// now if the max is adjusted, adjust the min back\r\n\t\t\tif (max - min < minRange) {\r\n\t\t\t\tminArgs[0] = max - minRange;\r\n\t\t\t\tminArgs[1] = pick(options.min, max - minRange);\r\n\t\t\t\tmin = arrayMax(minArgs);\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// Record modified extremes\r\n\t\taxis.min = min;\r\n\t\taxis.max = max;\r\n\t},\r\n\r\n\t/**\r\n\t * Update translation information\r\n\t */\r\n\tsetAxisTranslation: function (saveOld) {\r\n\t\tvar axis = this,\r\n\t\t\trange = axis.max - axis.min,\r\n\t\t\tpointRange = 0,\r\n\t\t\tclosestPointRange,\r\n\t\t\tminPointOffset = 0,\r\n\t\t\tpointRangePadding = 0,\r\n\t\t\tlinkedParent = axis.linkedParent,\r\n\t\t\tordinalCorrection,\r\n\t\t\ttransA = axis.transA;\r\n\r\n\t\t// adjust translation for padding\r\n\t\tif (axis.isXAxis) {\r\n\t\t\tif (linkedParent) {\r\n\t\t\t\tminPointOffset = linkedParent.minPointOffset;\r\n\t\t\t\tpointRangePadding = linkedParent.pointRangePadding;\r\n\t\t\t\t\r\n\t\t\t} else {\r\n\t\t\t\teach(axis.series, function (series) {\r\n\t\t\t\t\tvar seriesPointRange = series.pointRange,\r\n\t\t\t\t\t\tpointPlacement = series.options.pointPlacement,\r\n\t\t\t\t\t\tseriesClosestPointRange = series.closestPointRange;\r\n\r\n\t\t\t\t\tif (seriesPointRange > range) { // #1446\r\n\t\t\t\t\t\tseriesPointRange = 0;\r\n\t\t\t\t\t}\r\n\t\t\t\t\tpointRange = mathMax(pointRange, seriesPointRange);\r\n\t\t\t\t\t\r\n\t\t\t\t\t// minPointOffset is the value padding to the left of the axis in order to make\r\n\t\t\t\t\t// room for points with a pointRange, typically columns. When the pointPlacement option\r\n\t\t\t\t\t// is 'between' or 'on', this padding does not apply.\r\n\t\t\t\t\tminPointOffset = mathMax(\r\n\t\t\t\t\t\tminPointOffset, \r\n\t\t\t\t\t\tisString(pointPlacement) ? 0 : seriesPointRange / 2\r\n\t\t\t\t\t);\r\n\t\t\t\t\t\r\n\t\t\t\t\t// Determine the total padding needed to the length of the axis to make room for the \r\n\t\t\t\t\t// pointRange. If the series' pointPlacement is 'on', no padding is added.\r\n\t\t\t\t\tpointRangePadding = mathMax(\r\n\t\t\t\t\t\tpointRangePadding,\r\n\t\t\t\t\t\tpointPlacement === 'on' ? 0 : seriesPointRange\r\n\t\t\t\t\t);\r\n\r\n\t\t\t\t\t// Set the closestPointRange\r\n\t\t\t\t\tif (!series.noSharedTooltip && defined(seriesClosestPointRange)) {\r\n\t\t\t\t\t\tclosestPointRange = defined(closestPointRange) ?\r\n\t\t\t\t\t\t\tmathMin(closestPointRange, seriesClosestPointRange) :\r\n\t\t\t\t\t\t\tseriesClosestPointRange;\r\n\t\t\t\t\t}\r\n\t\t\t\t});\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// Record minPointOffset and pointRangePadding\r\n\t\t\tordinalCorrection = axis.ordinalSlope && closestPointRange ? axis.ordinalSlope / closestPointRange : 1; // #988, #1853\r\n\t\t\taxis.minPointOffset = minPointOffset = minPointOffset * ordinalCorrection;\r\n\t\t\taxis.pointRangePadding = pointRangePadding = pointRangePadding * ordinalCorrection;\r\n\r\n\t\t\t// pointRange means the width reserved for each point, like in a column chart\r\n\t\t\taxis.pointRange = mathMin(pointRange, range);\r\n\r\n\t\t\t// closestPointRange means the closest distance between points. In columns\r\n\t\t\t// it is mostly equal to pointRange, but in lines pointRange is 0 while closestPointRange\r\n\t\t\t// is some other value\r\n\t\t\taxis.closestPointRange = closestPointRange;\r\n\t\t}\r\n\r\n\t\t// Secondary values\r\n\t\tif (saveOld) {\r\n\t\t\taxis.oldTransA = transA;\r\n\t\t}\r\n\t\taxis.translationSlope = axis.transA = transA = axis.len / ((range + pointRangePadding) || 1);\r\n\t\taxis.transB = axis.horiz ? axis.left : axis.bottom; // translation addend\r\n\t\taxis.minPixelPadding = transA * minPointOffset;\r\n\t},\r\n\r\n\t/**\r\n\t * Set the tick positions to round values and optionally extend the extremes\r\n\t * to the nearest tick\r\n\t */\r\n\tsetTickPositions: function (secondPass) {\r\n\t\tvar axis = this,\r\n\t\t\tchart = axis.chart,\r\n\t\t\toptions = axis.options,\r\n\t\t\tisLog = axis.isLog,\r\n\t\t\tisDatetimeAxis = axis.isDatetimeAxis,\r\n\t\t\tisXAxis = axis.isXAxis,\r\n\t\t\tisLinked = axis.isLinked,\r\n\t\t\ttickPositioner = axis.options.tickPositioner,\r\n\t\t\tmaxPadding = options.maxPadding,\r\n\t\t\tminPadding = options.minPadding,\r\n\t\t\tlength,\r\n\t\t\tlinkedParentExtremes,\r\n\t\t\ttickIntervalOption = options.tickInterval,\r\n\t\t\tminTickIntervalOption = options.minTickInterval,\r\n\t\t\ttickPixelIntervalOption = options.tickPixelInterval,\r\n\t\t\ttickPositions,\r\n\t\t\tkeepTwoTicksOnly,\r\n\t\t\tcategories = axis.categories;\r\n\r\n\t\t// linked axis gets the extremes from the parent axis\r\n\t\tif (isLinked) {\r\n\t\t\taxis.linkedParent = chart[isXAxis ? 'xAxis' : 'yAxis'][options.linkedTo];\r\n\t\t\tlinkedParentExtremes = axis.linkedParent.getExtremes();\r\n\t\t\taxis.min = pick(linkedParentExtremes.min, linkedParentExtremes.dataMin);\r\n\t\t\taxis.max = pick(linkedParentExtremes.max, linkedParentExtremes.dataMax);\r\n\t\t\tif (options.type !== axis.linkedParent.options.type) {\r\n\t\t\t\terror(11, 1); // Can't link axes of different type\r\n\t\t\t}\r\n\t\t} else { // initial min and max from the extreme data values\r\n\t\t\taxis.min = pick(axis.userMin, options.min, axis.dataMin);\r\n\t\t\taxis.max = pick(axis.userMax, options.max, axis.dataMax);\r\n\t\t}\r\n\r\n\t\tif (isLog) {\r\n\t\t\tif (!secondPass && mathMin(axis.min, pick(axis.dataMin, axis.min)) <= 0) { // #978\r\n\t\t\t\terror(10, 1); // Can't plot negative values on log axis\r\n\t\t\t}\r\n\t\t\taxis.min = correctFloat(log2lin(axis.min)); // correctFloat cures #934\r\n\t\t\taxis.max = correctFloat(log2lin(axis.max));\r\n\t\t}\r\n\r\n\t\t// handle zoomed range\r\n\t\tif (axis.range) {\r\n\t\t\taxis.userMin = axis.min = mathMax(axis.min, axis.max - axis.range); // #618\r\n\t\t\taxis.userMax = axis.max;\r\n\t\t\tif (secondPass) {\r\n\t\t\t\taxis.range = null;  // don't use it when running setExtremes\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// Hook for adjusting this.min and this.max. Used by bubble series.\r\n\t\tif (axis.beforePadding) {\r\n\t\t\taxis.beforePadding();\r\n\t\t}\r\n\r\n\t\t// adjust min and max for the minimum range\r\n\t\taxis.adjustForMinRange();\r\n\t\t\r\n\t\t// Pad the values to get clear of the chart's edges. To avoid tickInterval taking the padding\r\n\t\t// into account, we do this after computing tick interval (#1337).\r\n\t\tif (!categories && !axis.usePercentage && !isLinked && defined(axis.min) && defined(axis.max)) {\r\n\t\t\tlength = axis.max - axis.min;\r\n\t\t\tif (length) {\r\n\t\t\t\tif (!defined(options.min) && !defined(axis.userMin) && minPadding && (axis.dataMin < 0 || !axis.ignoreMinPadding)) {\r\n\t\t\t\t\taxis.min -= length * minPadding;\r\n\t\t\t\t}\r\n\t\t\t\tif (!defined(options.max) && !defined(axis.userMax)  && maxPadding && (axis.dataMax > 0 || !axis.ignoreMaxPadding)) {\r\n\t\t\t\t\taxis.max += length * maxPadding;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// get tickInterval\r\n\t\tif (axis.min === axis.max || axis.min === undefined || axis.max === undefined) {\r\n\t\t\taxis.tickInterval = 1;\r\n\t\t} else if (isLinked && !tickIntervalOption &&\r\n\t\t\t\ttickPixelIntervalOption === axis.linkedParent.options.tickPixelInterval) {\r\n\t\t\taxis.tickInterval = axis.linkedParent.tickInterval;\r\n\t\t} else {\r\n\t\t\taxis.tickInterval = pick(\r\n\t\t\t\ttickIntervalOption,\r\n\t\t\t\tcategories ? // for categoried axis, 1 is default, for linear axis use tickPix\r\n\t\t\t\t\t1 :\r\n\t\t\t\t\t// don't let it be more than the data range\r\n\t\t\t\t\t(axis.max - axis.min) * tickPixelIntervalOption / mathMax(axis.len, tickPixelIntervalOption)\r\n\t\t\t);\r\n\t\t\t// For squished axes, set only two ticks\r\n\t\t\tif (!defined(tickIntervalOption) && axis.len < tickPixelIntervalOption && !this.isRadial) {\r\n\t\t\t\tkeepTwoTicksOnly = true;\r\n\t\t\t\taxis.tickInterval /= 4; // tick extremes closer to the real values\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Now we're finished detecting min and max, crop and group series data. This\r\n\t\t// is in turn needed in order to find tick positions in ordinal axes. \r\n\t\tif (isXAxis && !secondPass) {\r\n\t\t\teach(axis.series, function (series) {\r\n\t\t\t\tseries.processData(axis.min !== axis.oldMin || axis.max !== axis.oldMax);\r\n\t\t\t});\r\n\t\t}\r\n\r\n\t\t// set the translation factor used in translate function\r\n\t\taxis.setAxisTranslation(true);\r\n\r\n\t\t// hook for ordinal axes and radial axes\r\n\t\tif (axis.beforeSetTickPositions) {\r\n\t\t\taxis.beforeSetTickPositions();\r\n\t\t}\r\n\t\t\r\n\t\t// hook for extensions, used in Highstock ordinal axes\r\n\t\tif (axis.postProcessTickInterval) {\r\n\t\t\taxis.tickInterval = axis.postProcessTickInterval(axis.tickInterval);\r\n\t\t}\r\n\r\n\t\t// In column-like charts, don't cramp in more ticks than there are points (#1943)\r\n\t\tif (axis.pointRange) {\r\n\t\t\taxis.tickInterval = mathMax(axis.pointRange, axis.tickInterval);\r\n\t\t}\r\n\t\t\r\n\t\t// Before normalizing the tick interval, handle minimum tick interval. This applies only if tickInterval is not defined.\r\n\t\tif (!tickIntervalOption && axis.tickInterval < minTickIntervalOption) {\r\n\t\t\taxis.tickInterval = minTickIntervalOption;\r\n\t\t}\r\n\r\n\t\t// for linear axes, get magnitude and normalize the interval\r\n\t\tif (!isDatetimeAxis && !isLog) { // linear\r\n\t\t\tif (!tickIntervalOption) {\r\n\t\t\t\taxis.tickInterval = normalizeTickInterval(axis.tickInterval, null, getMagnitude(axis.tickInterval), options);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// get minorTickInterval\r\n\t\taxis.minorTickInterval = options.minorTickInterval === 'auto' && axis.tickInterval ?\r\n\t\t\t\taxis.tickInterval / 5 : options.minorTickInterval;\r\n\r\n\t\t// find the tick positions\r\n\t\taxis.tickPositions = tickPositions = options.tickPositions ?\r\n\t\t\t[].concat(options.tickPositions) : // Work on a copy (#1565)\r\n\t\t\t(tickPositioner && tickPositioner.apply(axis, [axis.min, axis.max]));\r\n\t\tif (!tickPositions) {\r\n\t\t\t\r\n\t\t\t// Too many ticks\r\n\t\t\tif (!axis.ordinalPositions && (axis.max - axis.min) / axis.tickInterval > mathMax(2 * axis.len, 200)) {\r\n\t\t\t\terror(19, true);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (isDatetimeAxis) {\r\n\t\t\t\ttickPositions = (axis.getNonLinearTimeTicks || getTimeTicks)(\r\n\t\t\t\t\tnormalizeTimeTickInterval(axis.tickInterval, options.units),\r\n\t\t\t\t\taxis.min,\r\n\t\t\t\t\taxis.max,\r\n\t\t\t\t\toptions.startOfWeek,\r\n\t\t\t\t\taxis.ordinalPositions,\r\n\t\t\t\t\taxis.closestPointRange,\r\n\t\t\t\t\ttrue\r\n\t\t\t\t);\r\n\t\t\t} else if (isLog) {\r\n\t\t\t\ttickPositions = axis.getLogTickPositions(axis.tickInterval, axis.min, axis.max);\r\n\t\t\t} else {\r\n\t\t\t\ttickPositions = axis.getLinearTickPositions(axis.tickInterval, axis.min, axis.max);\r\n\t\t\t}\r\n\t\t\tif (keepTwoTicksOnly) {\r\n\t\t\t\ttickPositions.splice(1, tickPositions.length - 2);\r\n\t\t\t}\r\n\r\n\t\t\taxis.tickPositions = tickPositions;\r\n\t\t}\r\n\r\n\t\tif (!isLinked) {\r\n\r\n\t\t\t// reset min/max or remove extremes based on start/end on tick\r\n\t\t\tvar roundedMin = tickPositions[0],\r\n\t\t\t\troundedMax = tickPositions[tickPositions.length - 1],\r\n\t\t\t\tminPointOffset = axis.minPointOffset || 0,\r\n\t\t\t\tsinglePad;\r\n\r\n\t\t\tif (options.startOnTick) {\r\n\t\t\t\taxis.min = roundedMin;\r\n\t\t\t} else if (axis.min - minPointOffset > roundedMin) {\r\n\t\t\t\ttickPositions.shift();\r\n\t\t\t}\r\n\r\n\t\t\tif (options.endOnTick) {\r\n\t\t\t\taxis.max = roundedMax;\r\n\t\t\t} else if (axis.max + minPointOffset < roundedMax) {\r\n\t\t\t\ttickPositions.pop();\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// When there is only one point, or all points have the same value on this axis, then min\r\n\t\t\t// and max are equal and tickPositions.length is 1. In this case, add some padding\r\n\t\t\t// in order to center the point, but leave it with one tick. #1337.\r\n\t\t\tif (tickPositions.length === 1) {\r\n\t\t\t\tsinglePad = 0.001; // The lowest possible number to avoid extra padding on columns\r\n\t\t\t\taxis.min -= singlePad;\r\n\t\t\t\taxis.max += singlePad;\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Set the max ticks of either the x and y axis collection\r\n\t */\r\n\tsetMaxTicks: function () {\r\n\t\t\r\n\t\tvar chart = this.chart,\r\n\t\t\tmaxTicks = chart.maxTicks || {},\r\n\t\t\ttickPositions = this.tickPositions,\r\n\t\t\tkey = this._maxTicksKey = [this.xOrY, this.pos, this.len].join('-');\r\n\t\t\r\n\t\tif (!this.isLinked && !this.isDatetimeAxis && tickPositions && tickPositions.length > (maxTicks[key] || 0) && this.options.alignTicks !== false) {\r\n\t\t\tmaxTicks[key] = tickPositions.length;\r\n\t\t}\r\n\t\tchart.maxTicks = maxTicks;\r\n\t},\r\n\r\n\t/**\r\n\t * When using multiple axes, adjust the number of ticks to match the highest\r\n\t * number of ticks in that group\r\n\t */\r\n\tadjustTickAmount: function () {\r\n\t\tvar axis = this,\r\n\t\t\tchart = axis.chart,\r\n\t\t\tkey = axis._maxTicksKey,\r\n\t\t\ttickPositions = axis.tickPositions,\r\n\t\t\tmaxTicks = chart.maxTicks;\r\n\r\n\t\tif (maxTicks && maxTicks[key] && !axis.isDatetimeAxis && !axis.categories && !axis.isLinked && axis.options.alignTicks !== false) { // only apply to linear scale\r\n\t\t\tvar oldTickAmount = axis.tickAmount,\r\n\t\t\t\tcalculatedTickAmount = tickPositions.length,\r\n\t\t\t\ttickAmount;\r\n\r\n\t\t\t// set the axis-level tickAmount to use below\r\n\t\t\taxis.tickAmount = tickAmount = maxTicks[key];\r\n\r\n\t\t\tif (calculatedTickAmount < tickAmount) {\r\n\t\t\t\twhile (tickPositions.length < tickAmount) {\r\n\t\t\t\t\ttickPositions.push(correctFloat(\r\n\t\t\t\t\t\ttickPositions[tickPositions.length - 1] + axis.tickInterval\r\n\t\t\t\t\t));\r\n\t\t\t\t}\r\n\t\t\t\taxis.transA *= (calculatedTickAmount - 1) / (tickAmount - 1);\r\n\t\t\t\taxis.max = tickPositions[tickPositions.length - 1];\r\n\r\n\t\t\t}\r\n\t\t\tif (defined(oldTickAmount) && tickAmount !== oldTickAmount) {\r\n\t\t\t\taxis.isDirty = true;\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Set the scale based on data min and max, user set min and max or options\r\n\t *\r\n\t */\r\n\tsetScale: function () {\r\n\t\tvar axis = this,\r\n\t\t\tstacks = axis.stacks,\r\n\t\t\ttype,\r\n\t\t\ti,\r\n\t\t\tisDirtyData,\r\n\t\t\tisDirtyAxisLength;\r\n\r\n\t\taxis.oldMin = axis.min;\r\n\t\taxis.oldMax = axis.max;\r\n\t\taxis.oldAxisLength = axis.len;\r\n\r\n\t\t// set the new axisLength\r\n\t\taxis.setAxisSize();\r\n\t\t//axisLength = horiz ? axisWidth : axisHeight;\r\n\t\tisDirtyAxisLength = axis.len !== axis.oldAxisLength;\r\n\r\n\t\t// is there new data?\r\n\t\teach(axis.series, function (series) {\r\n\t\t\tif (series.isDirtyData || series.isDirty ||\r\n\t\t\t\t\tseries.xAxis.isDirty) { // when x axis is dirty, we need new data extremes for y as well\r\n\t\t\t\tisDirtyData = true;\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\t// do we really need to go through all this?\r\n\t\tif (isDirtyAxisLength || isDirtyData || axis.isLinked || axis.forceRedraw ||\r\n\t\t\taxis.userMin !== axis.oldUserMin || axis.userMax !== axis.oldUserMax) {\r\n\t\t\t\r\n\t\t\t// reset stacks\r\n\t\t\tif (!axis.isXAxis) {\r\n\t\t\t\tfor (type in stacks) {\r\n\t\t\t\t\tdelete stacks[type];\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\taxis.forceRedraw = false;\r\n\r\n\t\t\t// get data extremes if needed\r\n\t\t\taxis.getSeriesExtremes();\r\n\r\n\t\t\t// get fixed positions based on tickInterval\r\n\t\t\taxis.setTickPositions();\r\n\r\n\t\t\t// record old values to decide whether a rescale is necessary later on (#540)\r\n\t\t\taxis.oldUserMin = axis.userMin;\r\n\t\t\taxis.oldUserMax = axis.userMax;\r\n\r\n\t\t\t// Mark as dirty if it is not already set to dirty and extremes have changed. #595.\r\n\t\t\tif (!axis.isDirty) {\r\n\t\t\t\taxis.isDirty = isDirtyAxisLength || axis.min !== axis.oldMin || axis.max !== axis.oldMax;\r\n\t\t\t}\r\n\t\t} else if (!axis.isXAxis) {\r\n\t\t\tif (axis.oldStacks) {\r\n\t\t\t\tstacks = axis.stacks = axis.oldStacks;\r\n\t\t\t}\r\n\r\n\t\t\t// reset stacks\r\n\t\t\tfor (type in stacks) {\r\n\t\t\t\tfor (i in stacks[type]) {\r\n\t\t\t\t\tstacks[type][i].cum = stacks[type][i].total;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// Set the maximum tick amount\r\n\t\taxis.setMaxTicks();\r\n\t},\r\n\r\n\t/**\r\n\t * Set the extremes and optionally redraw\r\n\t * @param {Number} newMin\r\n\t * @param {Number} newMax\r\n\t * @param {Boolean} redraw\r\n\t * @param {Boolean|Object} animation Whether to apply animation, and optionally animation\r\n\t *    configuration\r\n\t * @param {Object} eventArguments \r\n\t *\r\n\t */\r\n\tsetExtremes: function (newMin, newMax, redraw, animation, eventArguments) {\r\n\t\tvar axis = this,\r\n\t\t\tchart = axis.chart;\r\n\r\n\t\tredraw = pick(redraw, true); // defaults to true\r\n\r\n\t\t// Extend the arguments with min and max\r\n\t\teventArguments = extend(eventArguments, {\r\n\t\t\tmin: newMin,\r\n\t\t\tmax: newMax\r\n\t\t});\r\n\r\n\t\t// Fire the event\r\n\t\tfireEvent(axis, 'setExtremes', eventArguments, function () { // the default event handler\r\n\r\n\t\t\taxis.userMin = newMin;\r\n\t\t\taxis.userMax = newMax;\r\n\t\t\taxis.eventArgs = eventArguments;\r\n\r\n\t\t\t// Mark for running afterSetExtremes\r\n\t\t\taxis.isDirtyExtremes = true;\r\n\r\n\t\t\t// redraw\r\n\t\t\tif (redraw) {\r\n\t\t\t\tchart.redraw(animation);\r\n\t\t\t}\r\n\t\t});\r\n\t},\r\n\t\r\n\t/**\r\n\t * Overridable method for zooming chart. Pulled out in a separate method to allow overriding\r\n\t * in stock charts.\r\n\t */\r\n\tzoom: function (newMin, newMax) {\r\n\r\n\t\t// Prevent pinch zooming out of range. Check for defined is for #1946.\r\n\t\tif (!this.allowZoomOutside) {\r\n\t\t\tif (defined(this.dataMin) && newMin <= this.dataMin) {\r\n\t\t\t\tnewMin = UNDEFINED;\r\n\t\t\t}\r\n\t\t\tif (defined(this.dataMax) && newMax >= this.dataMax) {\r\n\t\t\t\tnewMax = UNDEFINED;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// In full view, displaying the reset zoom button is not required\r\n\t\tthis.displayBtn = newMin !== UNDEFINED || newMax !== UNDEFINED;\r\n\t\t\r\n\t\t// Do it\r\n\t\tthis.setExtremes(\r\n\t\t\tnewMin,\r\n\t\t\tnewMax,\r\n\t\t\tfalse, \r\n\t\t\tUNDEFINED, \r\n\t\t\t{ trigger: 'zoom' }\r\n\t\t);\r\n\t\treturn true;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Update the axis metrics\r\n\t */\r\n\tsetAxisSize: function () {\r\n\t\tvar chart = this.chart,\r\n\t\t\toptions = this.options,\r\n\t\t\toffsetLeft = options.offsetLeft || 0,\r\n\t\t\toffsetRight = options.offsetRight || 0,\r\n\t\t\thoriz = this.horiz,\r\n\t\t\twidth,\r\n\t\t\theight,\r\n\t\t\ttop,\r\n\t\t\tleft;\r\n\r\n\t\t// Expose basic values to use in Series object and navigator\r\n\t\tthis.left = left = pick(options.left, chart.plotLeft + offsetLeft);\r\n\t\tthis.top = top = pick(options.top, chart.plotTop);\r\n\t\tthis.width = width = pick(options.width, chart.plotWidth - offsetLeft + offsetRight);\r\n\t\tthis.height = height = pick(options.height, chart.plotHeight);\r\n\t\tthis.bottom = chart.chartHeight - height - top;\r\n\t\tthis.right = chart.chartWidth - width - left;\r\n\r\n\t\t// Direction agnostic properties\r\n\t\tthis.len = mathMax(horiz ? width : height, 0); // mathMax fixes #905\r\n\t\tthis.pos = horiz ? left : top; // distance from SVG origin\r\n\t},\r\n\r\n\t/**\r\n\t * Get the actual axis extremes\r\n\t */\r\n\tgetExtremes: function () {\r\n\t\tvar axis = this,\r\n\t\t\tisLog = axis.isLog;\r\n\r\n\t\treturn {\r\n\t\t\tmin: isLog ? correctFloat(lin2log(axis.min)) : axis.min,\r\n\t\t\tmax: isLog ? correctFloat(lin2log(axis.max)) : axis.max,\r\n\t\t\tdataMin: axis.dataMin,\r\n\t\t\tdataMax: axis.dataMax,\r\n\t\t\tuserMin: axis.userMin,\r\n\t\t\tuserMax: axis.userMax\r\n\t\t};\r\n\t},\r\n\r\n\t/**\r\n\t * Get the zero plane either based on zero or on the min or max value.\r\n\t * Used in bar and area plots\r\n\t */\r\n\tgetThreshold: function (threshold) {\r\n\t\tvar axis = this,\r\n\t\t\tisLog = axis.isLog;\r\n\r\n\t\tvar realMin = isLog ? lin2log(axis.min) : axis.min,\r\n\t\t\trealMax = isLog ? lin2log(axis.max) : axis.max;\r\n\t\t\r\n\t\tif (realMin > threshold || threshold === null) {\r\n\t\t\tthreshold = realMin;\r\n\t\t} else if (realMax < threshold) {\r\n\t\t\tthreshold = realMax;\r\n\t\t}\r\n\r\n\t\treturn axis.translate(threshold, 0, 1, 0, 1);\r\n\t},\r\n\r\n\taddPlotBand: function (options) {\r\n\t\tthis.addPlotBandOrLine(options, 'plotBands');\r\n\t},\r\n\t\r\n\taddPlotLine: function (options) {\r\n\t\tthis.addPlotBandOrLine(options, 'plotLines');\r\n\t},\r\n\r\n\t/**\r\n\t * Add a plot band or plot line after render time\r\n\t *\r\n\t * @param options {Object} The plotBand or plotLine configuration object\r\n\t */\r\n\taddPlotBandOrLine: function (options, coll) {\r\n\t\tvar obj = new PlotLineOrBand(this, options).render(),\r\n\t\t\tuserOptions = this.userOptions;\r\n\r\n\t\tif (obj) { // #2189\r\n\t\t\t// Add it to the user options for exporting and Axis.update\r\n\t\t\tif (coll) {\r\n\t\t\t\tuserOptions[coll] = userOptions[coll] || [];\r\n\t\t\t\tuserOptions[coll].push(options); \r\n\t\t\t}\r\n\t\t\tthis.plotLinesAndBands.push(obj); \r\n\t\t}\r\n\t\t\r\n\t\treturn obj;\r\n\t},\r\n\r\n\t/**\r\n\t * Compute auto alignment for the axis label based on which side the axis is on \r\n\t * and the given rotation for the label\r\n\t */\r\n\tautoLabelAlign: function (rotation) {\r\n\t\tvar ret, \r\n\t\t\tangle = (pick(rotation, 0) - (this.side * 90) + 720) % 360;\r\n\r\n\t\tif (angle > 15 && angle < 165) {\r\n\t\t\tret = 'right';\r\n\t\t} else if (angle > 195 && angle < 345) {\r\n\t\t\tret = 'left';\r\n\t\t} else {\r\n\t\t\tret = 'center';\r\n\t\t}\r\n\t\treturn ret;\r\n\t},\r\n\r\n\t/**\r\n\t * Render the tick labels to a preliminary position to get their sizes\r\n\t */\r\n\tgetOffset: function () {\r\n\t\tvar axis = this,\r\n\t\t\tchart = axis.chart,\r\n\t\t\trenderer = chart.renderer,\r\n\t\t\toptions = axis.options,\r\n\t\t\ttickPositions = axis.tickPositions,\r\n\t\t\tticks = axis.ticks,\r\n\t\t\thoriz = axis.horiz,\r\n\t\t\tside = axis.side,\r\n\t\t\tinvertedSide = chart.inverted ? [1, 0, 3, 2][side] : side,\r\n\t\t\thasData,\r\n\t\t\tshowAxis,\r\n\t\t\ttitleOffset = 0,\r\n\t\t\ttitleOffsetOption,\r\n\t\t\ttitleMargin = 0,\r\n\t\t\taxisTitleOptions = options.title,\r\n\t\t\tlabelOptions = options.labels,\r\n\t\t\tlabelOffset = 0, // reset\r\n\t\t\taxisOffset = chart.axisOffset,\r\n\t\t\tclipOffset = chart.clipOffset,\r\n\t\t\tdirectionFactor = [-1, 1, 1, -1][side],\r\n\t\t\tn,\r\n\t\t\ti,\r\n\t\t\tautoStaggerLines = 1,\r\n\t\t\tmaxStaggerLines = pick(labelOptions.maxStaggerLines, 5),\r\n\t\t\tsortedPositions,\r\n\t\t\tlastRight,\r\n\t\t\toverlap,\r\n\t\t\tpos,\r\n\t\t\tbBox,\r\n\t\t\tx,\r\n\t\t\tw,\r\n\t\t\tlineNo;\r\n\t\t\t\r\n\t\t// For reuse in Axis.render\r\n\t\taxis.hasData = hasData = (axis.hasVisibleSeries || (defined(axis.min) && defined(axis.max) && !!tickPositions));\r\n\t\taxis.showAxis = showAxis = hasData || pick(options.showEmpty, true);\r\n\r\n\t\t// Set/reset staggerLines\r\n\t\taxis.staggerLines = axis.horiz && labelOptions.staggerLines;\r\n\t\t\r\n\t\t// Create the axisGroup and gridGroup elements on first iteration\r\n\t\tif (!axis.axisGroup) {\r\n\t\t\taxis.gridGroup = renderer.g('grid')\r\n\t\t\t\t.attr({ zIndex: options.gridZIndex || 1 })\r\n\t\t\t\t.add();\r\n\t\t\taxis.axisGroup = renderer.g('axis')\r\n\t\t\t\t.attr({ zIndex: options.zIndex || 2 })\r\n\t\t\t\t.add();\r\n\t\t\taxis.labelGroup = renderer.g('axis-labels')\r\n\t\t\t\t.attr({ zIndex: labelOptions.zIndex || 7 })\r\n\t\t\t\t.add();\r\n\t\t}\r\n\r\n\t\tif (hasData || axis.isLinked) {\r\n\t\t\t\r\n\t\t\t// Set the explicit or automatic label alignment\r\n\t\t\taxis.labelAlign = pick(labelOptions.align || axis.autoLabelAlign(labelOptions.rotation));\r\n\r\n\t\t\teach(tickPositions, function (pos) {\r\n\t\t\t\tif (!ticks[pos]) {\r\n\t\t\t\t\tticks[pos] = new Tick(axis, pos);\r\n\t\t\t\t} else {\r\n\t\t\t\t\tticks[pos].addLabel(); // update labels depending on tick interval\r\n\t\t\t\t}\r\n\t\t\t});\r\n\r\n\t\t\t// Handle automatic stagger lines\r\n\t\t\tif (axis.horiz && !axis.staggerLines && maxStaggerLines && !labelOptions.rotation) {\r\n\t\t\t\tsortedPositions = axis.reversed ? [].concat(tickPositions).reverse() : tickPositions;\r\n\t\t\t\twhile (autoStaggerLines < maxStaggerLines) {\r\n\t\t\t\t\tlastRight = [];\r\n\t\t\t\t\toverlap = false;\r\n\t\t\t\t\t\r\n\t\t\t\t\tfor (i = 0; i < sortedPositions.length; i++) {\r\n\t\t\t\t\t\tpos = sortedPositions[i];\r\n\t\t\t\t\t\tbBox = ticks[pos].label && ticks[pos].label.getBBox();\r\n\t\t\t\t\t\tw = bBox ? bBox.width : 0;\r\n\t\t\t\t\t\tlineNo = i % autoStaggerLines;\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tif (w) {\r\n\t\t\t\t\t\t\tx = axis.translate(pos); // don't handle log\r\n\t\t\t\t\t\t\tif (lastRight[lineNo] !== UNDEFINED && x < lastRight[lineNo]) {\r\n\t\t\t\t\t\t\t\toverlap = true;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\tlastRight[lineNo] = x + w;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (overlap) {\r\n\t\t\t\t\t\tautoStaggerLines++;\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (autoStaggerLines > 1) {\r\n\t\t\t\t\taxis.staggerLines = autoStaggerLines;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\r\n\t\t\teach(tickPositions, function (pos) {\r\n\t\t\t\t// left side must be align: right and right side must have align: left for labels\r\n\t\t\t\tif (side === 0 || side === 2 || { 1: 'left', 3: 'right' }[side] === axis.labelAlign) {\r\n\r\n\t\t\t\t\t// get the highest offset\r\n\t\t\t\t\tlabelOffset = mathMax(\r\n\t\t\t\t\t\tticks[pos].getLabelSize(),\r\n\t\t\t\t\t\tlabelOffset\r\n\t\t\t\t\t);\r\n\t\t\t\t}\r\n\r\n\t\t\t});\r\n\t\t\tif (axis.staggerLines) {\r\n\t\t\t\tlabelOffset *= axis.staggerLines;\r\n\t\t\t\taxis.labelOffset = labelOffset;\r\n\t\t\t}\r\n\t\t\t\r\n\r\n\t\t} else { // doesn't have data\r\n\t\t\tfor (n in ticks) {\r\n\t\t\t\tticks[n].destroy();\r\n\t\t\t\tdelete ticks[n];\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif (axisTitleOptions && axisTitleOptions.text && axisTitleOptions.enabled !== false) { \r\n\t\t\tif (!axis.axisTitle) {\r\n\t\t\t\taxis.axisTitle = renderer.text(\r\n\t\t\t\t\taxisTitleOptions.text,\r\n\t\t\t\t\t0,\r\n\t\t\t\t\t0,\r\n\t\t\t\t\taxisTitleOptions.useHTML\r\n\t\t\t\t)\r\n\t\t\t\t.attr({\r\n\t\t\t\t\tzIndex: 7,\r\n\t\t\t\t\trotation: axisTitleOptions.rotation || 0,\r\n\t\t\t\t\talign:\r\n\t\t\t\t\t\taxisTitleOptions.textAlign ||\r\n\t\t\t\t\t\t{ low: 'left', middle: 'center', high: 'right' }[axisTitleOptions.align]\r\n\t\t\t\t})\r\n\t\t\t\t.css(axisTitleOptions.style)\r\n\t\t\t\t.add(axis.axisGroup);\r\n\t\t\t\taxis.axisTitle.isNew = true;\r\n\t\t\t}\r\n\r\n\t\t\tif (showAxis) {\r\n\t\t\t\ttitleOffset = axis.axisTitle.getBBox()[horiz ? 'height' : 'width'];\r\n\t\t\t\ttitleMargin = pick(axisTitleOptions.margin, horiz ? 5 : 10);\r\n\t\t\t\ttitleOffsetOption = axisTitleOptions.offset;\r\n\t\t\t}\r\n\r\n\t\t\t// hide or show the title depending on whether showEmpty is set\r\n\t\t\taxis.axisTitle[showAxis ? 'show' : 'hide']();\r\n\t\t}\r\n\t\t\r\n\t\t// handle automatic or user set offset\r\n\t\taxis.offset = directionFactor * pick(options.offset, axisOffset[side]);\r\n\t\t\r\n\t\taxis.axisTitleMargin =\r\n\t\t\tpick(titleOffsetOption,\r\n\t\t\t\tlabelOffset + titleMargin +\r\n\t\t\t\t(side !== 2 && labelOffset && directionFactor * options.labels[horiz ? 'y' : 'x'])\r\n\t\t\t);\r\n\r\n\t\taxisOffset[side] = mathMax(\r\n\t\t\taxisOffset[side],\r\n\t\t\taxis.axisTitleMargin + titleOffset + directionFactor * axis.offset\r\n\t\t);\r\n\t\tclipOffset[invertedSide] = mathMax(clipOffset[invertedSide], mathFloor(options.lineWidth / 2) * 2);\r\n\t},\r\n\t\r\n\t/**\r\n\t * Get the path for the axis line\r\n\t */\r\n\tgetLinePath: function (lineWidth) {\r\n\t\tvar chart = this.chart,\r\n\t\t\topposite = this.opposite,\r\n\t\t\toffset = this.offset,\r\n\t\t\thoriz = this.horiz,\r\n\t\t\tlineLeft = this.left + (opposite ? this.width : 0) + offset,\r\n\t\t\tlineTop = chart.chartHeight - this.bottom - (opposite ? this.height : 0) + offset;\r\n\t\t\t\r\n\t\tif (opposite) {\r\n\t\t\tlineWidth *= -1; // crispify the other way - #1480, #1687\r\n\t\t}\r\n\r\n\t\treturn chart.renderer.crispLine([\r\n\t\t\t\tM,\r\n\t\t\t\thoriz ?\r\n\t\t\t\t\tthis.left :\r\n\t\t\t\t\tlineLeft,\r\n\t\t\t\thoriz ?\r\n\t\t\t\t\tlineTop :\r\n\t\t\t\t\tthis.top,\r\n\t\t\t\tL,\r\n\t\t\t\thoriz ?\r\n\t\t\t\t\tchart.chartWidth - this.right :\r\n\t\t\t\t\tlineLeft,\r\n\t\t\t\thoriz ?\r\n\t\t\t\t\tlineTop :\r\n\t\t\t\t\tchart.chartHeight - this.bottom\r\n\t\t\t], lineWidth);\r\n\t},\r\n\t\r\n\t/**\r\n\t * Position the title\r\n\t */\r\n\tgetTitlePosition: function () {\r\n\t\t// compute anchor points for each of the title align options\r\n\t\tvar horiz = this.horiz,\r\n\t\t\taxisLeft = this.left,\r\n\t\t\taxisTop = this.top,\r\n\t\t\taxisLength = this.len,\r\n\t\t\taxisTitleOptions = this.options.title,\t\t\t\r\n\t\t\tmargin = horiz ? axisLeft : axisTop,\r\n\t\t\topposite = this.opposite,\r\n\t\t\toffset = this.offset,\r\n\t\t\tfontSize = pInt(axisTitleOptions.style.fontSize || 12),\r\n\t\t\t\r\n\t\t\t// the position in the length direction of the axis\r\n\t\t\talongAxis = {\r\n\t\t\t\tlow: margin + (horiz ? 0 : axisLength),\r\n\t\t\t\tmiddle: margin + axisLength / 2,\r\n\t\t\t\thigh: margin + (horiz ? axisLength : 0)\r\n\t\t\t}[axisTitleOptions.align],\r\n\t\r\n\t\t\t// the position in the perpendicular direction of the axis\r\n\t\t\toffAxis = (horiz ? axisTop + this.height : axisLeft) +\r\n\t\t\t\t(horiz ? 1 : -1) * // horizontal axis reverses the margin\r\n\t\t\t\t(opposite ? -1 : 1) * // so does opposite axes\r\n\t\t\t\tthis.axisTitleMargin +\r\n\t\t\t\t(this.side === 2 ? fontSize : 0);\r\n\r\n\t\treturn {\r\n\t\t\tx: horiz ?\r\n\t\t\t\talongAxis :\r\n\t\t\t\toffAxis + (opposite ? this.width : 0) + offset +\r\n\t\t\t\t\t(axisTitleOptions.x || 0), // x\r\n\t\t\ty: horiz ?\r\n\t\t\t\toffAxis - (opposite ? this.height : 0) + offset :\r\n\t\t\t\talongAxis + (axisTitleOptions.y || 0) // y\r\n\t\t};\r\n\t},\r\n\t\r\n\t/**\r\n\t * Render the axis\r\n\t */\r\n\trender: function () {\r\n\t\tvar axis = this,\r\n\t\t\tchart = axis.chart,\r\n\t\t\trenderer = chart.renderer,\r\n\t\t\toptions = axis.options,\r\n\t\t\tisLog = axis.isLog,\r\n\t\t\tisLinked = axis.isLinked,\r\n\t\t\ttickPositions = axis.tickPositions,\r\n\t\t\taxisTitle = axis.axisTitle,\r\n\t\t\tstacks = axis.stacks,\r\n\t\t\tticks = axis.ticks,\r\n\t\t\tminorTicks = axis.minorTicks,\r\n\t\t\talternateBands = axis.alternateBands,\r\n\t\t\tstackLabelOptions = options.stackLabels,\r\n\t\t\talternateGridColor = options.alternateGridColor,\r\n\t\t\ttickmarkOffset = axis.tickmarkOffset,\r\n\t\t\tlineWidth = options.lineWidth,\r\n\t\t\tlinePath,\r\n\t\t\thasRendered = chart.hasRendered,\r\n\t\t\tslideInTicks = hasRendered && defined(axis.oldMin) && !isNaN(axis.oldMin),\r\n\t\t\thasData = axis.hasData,\r\n\t\t\tshowAxis = axis.showAxis,\r\n\t\t\tfrom,\r\n\t\t\tto;\r\n\r\n\t\t// Mark all elements inActive before we go over and mark the active ones\r\n\t\teach([ticks, minorTicks, alternateBands], function (coll) {\r\n\t\t\tvar pos;\r\n\t\t\tfor (pos in coll) {\r\n\t\t\t\tcoll[pos].isActive = false;\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\t// If the series has data draw the ticks. Else only the line and title\r\n\t\tif (hasData || isLinked) {\r\n\r\n\t\t\t// minor ticks\r\n\t\t\tif (axis.minorTickInterval && !axis.categories) {\r\n\t\t\t\teach(axis.getMinorTickPositions(), function (pos) {\r\n\t\t\t\t\tif (!minorTicks[pos]) {\r\n\t\t\t\t\t\tminorTicks[pos] = new Tick(axis, pos, 'minor');\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t// render new ticks in old position\r\n\t\t\t\t\tif (slideInTicks && minorTicks[pos].isNew) {\r\n\t\t\t\t\t\tminorTicks[pos].render(null, true);\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tminorTicks[pos].render(null, false, 1);\r\n\t\t\t\t});\r\n\t\t\t}\r\n\r\n\t\t\t// Major ticks. Pull out the first item and render it last so that\r\n\t\t\t// we can get the position of the neighbour label. #808.\r\n\t\t\tif (tickPositions.length) { // #1300\r\n\t\t\t\teach(tickPositions.slice(1).concat([tickPositions[0]]), function (pos, i) {\r\n\t\r\n\t\t\t\t\t// Reorganize the indices\r\n\t\t\t\t\ti = (i === tickPositions.length - 1) ? 0 : i + 1;\r\n\t\r\n\t\t\t\t\t// linked axes need an extra check to find out if\r\n\t\t\t\t\tif (!isLinked || (pos >= axis.min && pos <= axis.max)) {\r\n\t\r\n\t\t\t\t\t\tif (!ticks[pos]) {\r\n\t\t\t\t\t\t\tticks[pos] = new Tick(axis, pos);\r\n\t\t\t\t\t\t}\r\n\t\r\n\t\t\t\t\t\t// render new ticks in old position\r\n\t\t\t\t\t\tif (slideInTicks && ticks[pos].isNew) {\r\n\t\t\t\t\t\t\tticks[pos].render(i, true);\r\n\t\t\t\t\t\t}\r\n\t\r\n\t\t\t\t\t\tticks[pos].render(i, false, 1);\r\n\t\t\t\t\t}\r\n\t\r\n\t\t\t\t});\r\n\t\t\t\t// In a categorized axis, the tick marks are displayed between labels. So\r\n\t\t\t\t// we need to add a tick mark and grid line at the left edge of the X axis.\r\n\t\t\t\tif (tickmarkOffset && axis.min === 0) {\r\n\t\t\t\t\tif (!ticks[-1]) {\r\n\t\t\t\t\t\tticks[-1] = new Tick(axis, -1, null, true);\r\n\t\t\t\t\t}\r\n\t\t\t\t\tticks[-1].render(-1);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t}\r\n\r\n\t\t\t// alternate grid color\r\n\t\t\tif (alternateGridColor) {\r\n\t\t\t\teach(tickPositions, function (pos, i) {\r\n\t\t\t\t\tif (i % 2 === 0 && pos < axis.max) {\r\n\t\t\t\t\t\tif (!alternateBands[pos]) {\r\n\t\t\t\t\t\t\talternateBands[pos] = new PlotLineOrBand(axis);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tfrom = pos + tickmarkOffset; // #949\r\n\t\t\t\t\t\tto = tickPositions[i + 1] !== UNDEFINED ? tickPositions[i + 1] + tickmarkOffset : axis.max;\r\n\t\t\t\t\t\talternateBands[pos].options = {\r\n\t\t\t\t\t\t\tfrom: isLog ? lin2log(from) : from,\r\n\t\t\t\t\t\t\tto: isLog ? lin2log(to) : to,\r\n\t\t\t\t\t\t\tcolor: alternateGridColor\r\n\t\t\t\t\t\t};\r\n\t\t\t\t\t\talternateBands[pos].render();\r\n\t\t\t\t\t\talternateBands[pos].isActive = true;\r\n\t\t\t\t\t}\r\n\t\t\t\t});\r\n\t\t\t}\r\n\r\n\t\t\t// custom plot lines and bands\r\n\t\t\tif (!axis._addedPlotLB) { // only first time\r\n\t\t\t\teach((options.plotLines || []).concat(options.plotBands || []), function (plotLineOptions) {\r\n\t\t\t\t\taxis.addPlotBandOrLine(plotLineOptions);\r\n\t\t\t\t});\r\n\t\t\t\taxis._addedPlotLB = true;\r\n\t\t\t}\r\n\r\n\t\t} // end if hasData\r\n\r\n\t\t// Remove inactive ticks\r\n\t\teach([ticks, minorTicks, alternateBands], function (coll) {\r\n\t\t\tvar pos, \r\n\t\t\t\ti,\r\n\t\t\t\tforDestruction = [],\r\n\t\t\t\tdelay = globalAnimation ? globalAnimation.duration || 500 : 0,\r\n\t\t\t\tdestroyInactiveItems = function () {\r\n\t\t\t\t\ti = forDestruction.length;\r\n\t\t\t\t\twhile (i--) {\r\n\t\t\t\t\t\t// When resizing rapidly, the same items may be destroyed in different timeouts,\r\n\t\t\t\t\t\t// or the may be reactivated\r\n\t\t\t\t\t\tif (coll[forDestruction[i]] && !coll[forDestruction[i]].isActive) {\r\n\t\t\t\t\t\t\tcoll[forDestruction[i]].destroy();\r\n\t\t\t\t\t\t\tdelete coll[forDestruction[i]];\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t};\r\n\r\n\t\t\tfor (pos in coll) {\r\n\r\n\t\t\t\tif (!coll[pos].isActive) {\r\n\t\t\t\t\t// Render to zero opacity\r\n\t\t\t\t\tcoll[pos].render(pos, false, 0);\r\n\t\t\t\t\tcoll[pos].isActive = false;\r\n\t\t\t\t\tforDestruction.push(pos);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// When the objects are finished fading out, destroy them\r\n\t\t\tif (coll === alternateBands || !chart.hasRendered || !delay) {\r\n\t\t\t\tdestroyInactiveItems();\r\n\t\t\t} else if (delay) {\r\n\t\t\t\tsetTimeout(destroyInactiveItems, delay);\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\t// Static items. As the axis group is cleared on subsequent calls\r\n\t\t// to render, these items are added outside the group.\r\n\t\t// axis line\r\n\t\tif (lineWidth) {\r\n\t\t\tlinePath = axis.getLinePath(lineWidth);\r\n\t\t\tif (!axis.axisLine) {\r\n\t\t\t\taxis.axisLine = renderer.path(linePath)\r\n\t\t\t\t\t.attr({\r\n\t\t\t\t\t\tstroke: options.lineColor,\r\n\t\t\t\t\t\t'stroke-width': lineWidth,\r\n\t\t\t\t\t\tzIndex: 7\r\n\t\t\t\t\t})\r\n\t\t\t\t\t.add(axis.axisGroup);\r\n\t\t\t} else {\r\n\t\t\t\taxis.axisLine.animate({ d: linePath });\r\n\t\t\t}\r\n\r\n\t\t\t// show or hide the line depending on options.showEmpty\r\n\t\t\taxis.axisLine[showAxis ? 'show' : 'hide']();\r\n\t\t}\r\n\r\n\t\tif (axisTitle && showAxis) {\r\n\t\t\t\r\n\t\t\taxisTitle[axisTitle.isNew ? 'attr' : 'animate'](\r\n\t\t\t\taxis.getTitlePosition()\r\n\t\t\t);\r\n\t\t\taxisTitle.isNew = false;\r\n\t\t}\r\n\r\n\t\t// Stacked totals:\r\n\t\tif (stackLabelOptions && stackLabelOptions.enabled) {\r\n\t\t\tvar stackKey, oneStack, stackCategory,\r\n\t\t\t\tstackTotalGroup = axis.stackTotalGroup;\r\n\r\n\t\t\t// Create a separate group for the stack total labels\r\n\t\t\tif (!stackTotalGroup) {\r\n\t\t\t\taxis.stackTotalGroup = stackTotalGroup =\r\n\t\t\t\t\trenderer.g('stack-labels')\r\n\t\t\t\t\t\t.attr({\r\n\t\t\t\t\t\t\tvisibility: VISIBLE,\r\n\t\t\t\t\t\t\tzIndex: 6\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t\t.add();\r\n\t\t\t}\r\n\r\n\t\t\t// plotLeft/Top will change when y axis gets wider so we need to translate the\r\n\t\t\t// stackTotalGroup at every render call. See bug #506 and #516\r\n\t\t\tstackTotalGroup.translate(chart.plotLeft, chart.plotTop);\r\n\r\n\t\t\t// Render each stack total\r\n\t\t\tfor (stackKey in stacks) {\r\n\t\t\t\toneStack = stacks[stackKey];\r\n\t\t\t\tfor (stackCategory in oneStack) {\r\n\t\t\t\t\toneStack[stackCategory].render(stackTotalGroup);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t// End stacked totals\r\n\r\n\t\taxis.isDirty = false;\r\n\t},\r\n\r\n\t/**\r\n\t * Remove a plot band or plot line from the chart by id\r\n\t * @param {Object} id\r\n\t */\r\n\tremovePlotBandOrLine: function (id) {\r\n\t\tvar plotLinesAndBands = this.plotLinesAndBands,\r\n\t\t\toptions = this.options,\r\n\t\t\tuserOptions = this.userOptions,\r\n\t\t\ti = plotLinesAndBands.length;\r\n\t\twhile (i--) {\r\n\t\t\tif (plotLinesAndBands[i].id === id) {\r\n\t\t\t\tplotLinesAndBands[i].destroy();\r\n\t\t\t}\r\n\t\t}\r\n\t\teach([options.plotLines || [], userOptions.plotLines || [], options.plotBands || [], userOptions.plotBands || []], function (arr) {\r\n\t\t\ti = arr.length;\r\n\t\t\twhile (i--) {\r\n\t\t\t\tif (arr[i].id === id) {\r\n\t\t\t\t\terase(arr, arr[i]);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t},\r\n\r\n\t/**\r\n\t * Update the axis title by options\r\n\t */\r\n\tsetTitle: function (newTitleOptions, redraw) {\r\n\t\tthis.update({ title: newTitleOptions }, redraw);\r\n\t},\r\n\r\n\t/**\r\n\t * Redraw the axis to reflect changes in the data or axis extremes\r\n\t */\r\n\tredraw: function () {\r\n\t\tvar axis = this,\r\n\t\t\tchart = axis.chart,\r\n\t\t\tpointer = chart.pointer;\r\n\r\n\t\t// hide tooltip and hover states\r\n\t\tif (pointer.reset) {\r\n\t\t\tpointer.reset(true);\r\n\t\t}\r\n\r\n\t\t// render the axis\r\n\t\taxis.render();\r\n\r\n\t\t// move plot lines and bands\r\n\t\teach(axis.plotLinesAndBands, function (plotLine) {\r\n\t\t\tplotLine.render();\r\n\t\t});\r\n\r\n\t\t// mark associated series as dirty and ready for redraw\r\n\t\teach(axis.series, function (series) {\r\n\t\t\tseries.isDirty = true;\r\n\t\t});\r\n\r\n\t},\r\n\r\n\t/**\r\n\t * Build the stacks from top down\r\n\t */\r\n\tbuildStacks: function () {\r\n\t\tvar series = this.series,\r\n\t\t\ti = series.length;\r\n\t\tif (!this.isXAxis) {\r\n\t\t\twhile (i--) {\r\n\t\t\t\tseries[i].setStackedPoints();\r\n\t\t\t}\r\n\t\t\t// Loop up again to compute percent stack\r\n\t\t\tif (this.usePercentage) {\r\n\t\t\t\tfor (i = 0; i < series.length; i++) {\r\n\t\t\t\t\tseries[i].setPercentStacks();\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Set new axis categories and optionally redraw\r\n\t * @param {Array} categories\r\n\t * @param {Boolean} redraw\r\n\t */\r\n\tsetCategories: function (categories, redraw) {\r\n\t\tthis.update({ categories: categories }, redraw);\r\n\t},\r\n\r\n\t/**\r\n\t * Destroys an Axis instance.\r\n\t */\r\n\tdestroy: function (keepEvents) {\r\n\t\tvar axis = this,\r\n\t\t\tstacks = axis.stacks,\r\n\t\t\tstackKey,\r\n\t\t\tplotLinesAndBands = axis.plotLinesAndBands,\r\n\t\t\ti;\r\n\r\n\t\t// Remove the events\r\n\t\tif (!keepEvents) {\r\n\t\t\tremoveEvent(axis);\r\n\t\t}\r\n\r\n\t\t// Destroy each stack total\r\n\t\tfor (stackKey in stacks) {\r\n\t\t\tdestroyObjectProperties(stacks[stackKey]);\r\n\r\n\t\t\tstacks[stackKey] = null;\r\n\t\t}\r\n\r\n\t\t// Destroy collections\r\n\t\teach([axis.ticks, axis.minorTicks, axis.alternateBands], function (coll) {\r\n\t\t\tdestroyObjectProperties(coll);\r\n\t\t});\r\n\t\ti = plotLinesAndBands.length;\r\n\t\twhile (i--) { // #1975\r\n\t\t\tplotLinesAndBands[i].destroy();\r\n\t\t}\r\n\r\n\t\t// Destroy local variables\r\n\t\teach(['stackTotalGroup', 'axisLine', 'axisGroup', 'gridGroup', 'labelGroup', 'axisTitle'], function (prop) {\r\n\t\t\tif (axis[prop]) {\r\n\t\t\t\taxis[prop] = axis[prop].destroy();\r\n\t\t\t}\r\n\t\t});\r\n\t}\r\n\r\n\t\r\n}; // end Axis\r\n\r\n/**\r\n * The tooltip object\r\n * @param {Object} chart The chart instance\r\n * @param {Object} options Tooltip options\r\n */\r\nfunction Tooltip() {\r\n\tthis.init.apply(this, arguments);\r\n}\r\n\r\nTooltip.prototype = {\r\n\r\n\tinit: function (chart, options) {\r\n\r\n\t\tvar borderWidth = options.borderWidth,\r\n\t\t\tstyle = options.style,\r\n\t\t\tpadding = pInt(style.padding);\r\n\r\n\t\t// Save the chart and options\r\n\t\tthis.chart = chart;\r\n\t\tthis.options = options;\r\n\r\n\t\t// Keep track of the current series\r\n\t\t//this.currentSeries = UNDEFINED;\r\n\r\n\t\t// List of crosshairs\r\n\t\tthis.crosshairs = [];\r\n\r\n\t\t// Current values of x and y when animating\r\n\t\tthis.now = { x: 0, y: 0 };\r\n\r\n\t\t// The tooltip is initially hidden\r\n\t\tthis.isHidden = true;\r\n\r\n\r\n\t\t// create the label\r\n\t\tthis.label = chart.renderer.label('', 0, 0, options.shape, null, null, options.useHTML, null, 'tooltip')\r\n\t\t\t.attr({\r\n\t\t\t\tpadding: padding,\r\n\t\t\t\tfill: options.backgroundColor,\r\n\t\t\t\t'stroke-width': borderWidth,\r\n\t\t\t\tr: options.borderRadius,\r\n\t\t\t\tzIndex: 8\r\n\t\t\t})\r\n\t\t\t.css(style)\r\n\t\t\t.css({ padding: 0 }) // Remove it from VML, the padding is applied as an attribute instead (#1117)\r\n\t\t\t.add()\r\n\t\t\t.attr({ y: -999 }); // #2301\r\n\r\n\t\t// When using canVG the shadow shows up as a gray circle\r\n\t\t// even if the tooltip is hidden.\r\n\t\tif (!useCanVG) {\r\n\t\t\tthis.label.shadow(options.shadow);\r\n\t\t}\r\n\r\n\t\t// Public property for getting the shared state.\r\n\t\tthis.shared = options.shared;\r\n\t},\r\n\r\n\t/**\r\n\t * Destroy the tooltip and its elements.\r\n\t */\r\n\tdestroy: function () {\r\n\t\teach(this.crosshairs, function (crosshair) {\r\n\t\t\tif (crosshair) {\r\n\t\t\t\tcrosshair.destroy();\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\t// Destroy and clear local variables\r\n\t\tif (this.label) {\r\n\t\t\tthis.label = this.label.destroy();\r\n\t\t}\r\n\t\tclearTimeout(this.hideTimer);\r\n\t\tclearTimeout(this.tooltipTimeout);\r\n\t},\r\n\r\n\t/**\r\n\t * Provide a soft movement for the tooltip\r\n\t *\r\n\t * @param {Number} x\r\n\t * @param {Number} y\r\n\t * @private\r\n\t */\r\n\tmove: function (x, y, anchorX, anchorY) {\r\n\t\tvar tooltip = this,\r\n\t\t\tnow = tooltip.now,\r\n\t\t\tanimate = tooltip.options.animation !== false && !tooltip.isHidden;\r\n\r\n\t\t// get intermediate values for animation\r\n\t\textend(now, {\r\n\t\t\tx: animate ? (2 * now.x + x) / 3 : x,\r\n\t\t\ty: animate ? (now.y + y) / 2 : y,\r\n\t\t\tanchorX: animate ? (2 * now.anchorX + anchorX) / 3 : anchorX,\r\n\t\t\tanchorY: animate ? (now.anchorY + anchorY) / 2 : anchorY\r\n\t\t});\r\n\r\n\t\t// move to the intermediate value\r\n\t\ttooltip.label.attr(now);\r\n\r\n\t\t\r\n\t\t// run on next tick of the mouse tracker\r\n\t\tif (animate && (mathAbs(x - now.x) > 1 || mathAbs(y - now.y) > 1)) {\r\n\t\t\r\n\t\t\t// never allow two timeouts\r\n\t\t\tclearTimeout(this.tooltipTimeout);\r\n\t\t\t\r\n\t\t\t// set the fixed interval ticking for the smooth tooltip\r\n\t\t\tthis.tooltipTimeout = setTimeout(function () {\r\n\t\t\t\t// The interval function may still be running during destroy, so check that the chart is really there before calling.\r\n\t\t\t\tif (tooltip) {\r\n\t\t\t\t\ttooltip.move(x, y, anchorX, anchorY);\r\n\t\t\t\t}\r\n\t\t\t}, 32);\r\n\t\t\t\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Hide the tooltip\r\n\t */\r\n\thide: function () {\r\n\t\tvar tooltip = this,\r\n\t\t\thoverPoints;\r\n\t\t\r\n\t\tclearTimeout(this.hideTimer); // disallow duplicate timers (#1728, #1766)\r\n\t\tif (!this.isHidden) {\r\n\t\t\thoverPoints = this.chart.hoverPoints;\r\n\r\n\t\t\tthis.hideTimer = setTimeout(function () {\r\n\t\t\t\ttooltip.label.fadeOut();\r\n\t\t\t\ttooltip.isHidden = true;\r\n\t\t\t}, pick(this.options.hideDelay, 500));\r\n\r\n\t\t\t// hide previous hoverPoints and set new\r\n\t\t\tif (hoverPoints) {\r\n\t\t\t\teach(hoverPoints, function (point) {\r\n\t\t\t\t\tpoint.setState();\r\n\t\t\t\t});\r\n\t\t\t}\r\n\r\n\t\t\tthis.chart.hoverPoints = null;\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Hide the crosshairs\r\n\t */\r\n\thideCrosshairs: function () {\r\n\t\teach(this.crosshairs, function (crosshair) {\r\n\t\t\tif (crosshair) {\r\n\t\t\t\tcrosshair.hide();\r\n\t\t\t}\r\n\t\t});\r\n\t},\r\n\t\r\n\t/** \r\n\t * Extendable method to get the anchor position of the tooltip\r\n\t * from a point or set of points\r\n\t */\r\n\tgetAnchor: function (points, mouseEvent) {\r\n\t\tvar ret,\r\n\t\t\tchart = this.chart,\r\n\t\t\tinverted = chart.inverted,\r\n\t\t\tplotTop = chart.plotTop,\r\n\t\t\tplotX = 0,\r\n\t\t\tplotY = 0,\r\n\t\t\tyAxis;\r\n\t\t\r\n\t\tpoints = splat(points);\r\n\t\t\r\n\t\t// Pie uses a special tooltipPos\r\n\t\tret = points[0].tooltipPos;\r\n\t\t\r\n\t\t// When tooltip follows mouse, relate the position to the mouse\r\n\t\tif (this.followPointer && mouseEvent) {\r\n\t\t\tif (mouseEvent.chartX === UNDEFINED) {\r\n\t\t\t\tmouseEvent = chart.pointer.normalize(mouseEvent);\r\n\t\t\t}\r\n\t\t\tret = [\r\n\t\t\t\tmouseEvent.chartX - chart.plotLeft,\r\n\t\t\t\tmouseEvent.chartY - plotTop\r\n\t\t\t];\r\n\t\t}\r\n\t\t// When shared, use the average position\r\n\t\tif (!ret) {\r\n\t\t\teach(points, function (point) {\r\n\t\t\t\tyAxis = point.series.yAxis;\r\n\t\t\t\tplotX += point.plotX;\r\n\t\t\t\tplotY += (point.plotLow ? (point.plotLow + point.plotHigh) / 2 : point.plotY) +\r\n\t\t\t\t\t(!inverted && yAxis ? yAxis.top - plotTop : 0); // #1151\r\n\t\t\t});\r\n\t\t\t\r\n\t\t\tplotX /= points.length;\r\n\t\t\tplotY /= points.length;\r\n\t\t\t\r\n\t\t\tret = [\r\n\t\t\t\tinverted ? chart.plotWidth - plotY : plotX,\r\n\t\t\t\tthis.shared && !inverted && points.length > 1 && mouseEvent ? \r\n\t\t\t\t\tmouseEvent.chartY - plotTop : // place shared tooltip next to the mouse (#424)\r\n\t\t\t\t\tinverted ? chart.plotHeight - plotX : plotY\r\n\t\t\t];\r\n\t\t}\r\n\r\n\t\treturn map(ret, mathRound);\r\n\t},\r\n\t\r\n\t/**\r\n\t * Place the tooltip in a chart without spilling over\r\n\t * and not covering the point it self.\r\n\t */\r\n\tgetPosition: function (boxWidth, boxHeight, point) {\r\n\t\t\r\n\t\t// Set up the variables\r\n\t\tvar chart = this.chart,\r\n\t\t\tplotLeft = chart.plotLeft,\r\n\t\t\tplotTop = chart.plotTop,\r\n\t\t\tplotWidth = chart.plotWidth,\r\n\t\t\tplotHeight = chart.plotHeight,\r\n\t\t\tdistance = pick(this.options.distance, 12),\r\n\t\t\tpointX = point.plotX,\r\n\t\t\tpointY = point.plotY,\r\n\t\t\tx = pointX + plotLeft + (chart.inverted ? distance : -boxWidth - distance),\r\n\t\t\ty = pointY - boxHeight + plotTop + 15, // 15 means the point is 15 pixels up from the bottom of the tooltip\r\n\t\t\talignedRight;\r\n\t\r\n\t\t// It is too far to the left, adjust it\r\n\t\tif (x < 7) {\r\n\t\t\tx = plotLeft + mathMax(pointX, 0) + distance;\r\n\t\t}\r\n\t\r\n\t\t// Test to see if the tooltip is too far to the right,\r\n\t\t// if it is, move it back to be inside and then up to not cover the point.\r\n\t\tif ((x + boxWidth) > (plotLeft + plotWidth)) {\r\n\t\t\tx -= (x + boxWidth) - (plotLeft + plotWidth);\r\n\t\t\ty = pointY - boxHeight + plotTop - distance;\r\n\t\t\talignedRight = true;\r\n\t\t}\r\n\t\r\n\t\t// If it is now above the plot area, align it to the top of the plot area\r\n\t\tif (y < plotTop + 5) {\r\n\t\t\ty = plotTop + 5;\r\n\t\r\n\t\t\t// If the tooltip is still covering the point, move it below instead\r\n\t\t\tif (alignedRight && pointY >= y && pointY <= (y + boxHeight)) {\r\n\t\t\t\ty = pointY + plotTop + distance; // below\r\n\t\t\t}\r\n\t\t} \r\n\t\r\n\t\t// Now if the tooltip is below the chart, move it up. It's better to cover the\r\n\t\t// point than to disappear outside the chart. #834.\r\n\t\tif (y + boxHeight > plotTop + plotHeight) {\r\n\t\t\ty = mathMax(plotTop, plotTop + plotHeight - boxHeight - distance); // below\r\n\t\t}\r\n\t\r\n\t\treturn {x: x, y: y};\r\n\t},\r\n\r\n\t/**\r\n\t * In case no user defined formatter is given, this will be used. Note that the context\r\n\t * here is an object holding point, series, x, y etc.\r\n\t */\r\n\tdefaultFormatter: function (tooltip) {\r\n\t\tvar items = this.points || splat(this),\r\n\t\t\tseries = items[0].series,\r\n\t\t\ts;\r\n\r\n\t\t// build the header\r\n\t\ts = [series.tooltipHeaderFormatter(items[0])];\r\n\r\n\t\t// build the values\r\n\t\teach(items, function (item) {\r\n\t\t\tseries = item.series;\r\n\t\t\ts.push((series.tooltipFormatter && series.tooltipFormatter(item)) ||\r\n\t\t\t\titem.point.tooltipFormatter(series.tooltipOptions.pointFormat));\r\n\t\t});\r\n\r\n\t\t// footer\r\n\t\ts.push(tooltip.options.footerFormat || '');\r\n\r\n\t\treturn s.join('');\r\n\t},\r\n\r\n\t/**\r\n\t * Refresh the tooltip's text and position.\r\n\t * @param {Object} point\r\n\t */\r\n\trefresh: function (point, mouseEvent) {\r\n\t\tvar tooltip = this,\r\n\t\t\tchart = tooltip.chart,\r\n\t\t\tlabel = tooltip.label,\r\n\t\t\toptions = tooltip.options,\r\n\t\t\tx,\r\n\t\t\ty,\r\n\t\t\tanchor,\r\n\t\t\ttextConfig = {},\r\n\t\t\ttext,\r\n\t\t\tpointConfig = [],\r\n\t\t\tformatter = options.formatter || tooltip.defaultFormatter,\r\n\t\t\thoverPoints = chart.hoverPoints,\r\n\t\t\tborderColor,\r\n\t\t\tcrosshairsOptions = options.crosshairs,\r\n\t\t\tshared = tooltip.shared,\r\n\t\t\tcurrentSeries;\r\n\t\t\t\r\n\t\tclearTimeout(this.hideTimer);\r\n\t\t\r\n\t\t// get the reference point coordinates (pie charts use tooltipPos)\r\n\t\ttooltip.followPointer = splat(point)[0].series.tooltipOptions.followPointer;\r\n\t\tanchor = tooltip.getAnchor(point, mouseEvent);\r\n\t\tx = anchor[0];\r\n\t\ty = anchor[1];\r\n\r\n\t\t// shared tooltip, array is sent over\r\n\t\tif (shared && !(point.series && point.series.noSharedTooltip)) {\r\n\t\t\t\r\n\t\t\t// hide previous hoverPoints and set new\r\n\t\t\t\r\n\t\t\tchart.hoverPoints = point;\r\n\t\t\tif (hoverPoints) {\r\n\t\t\t\teach(hoverPoints, function (point) {\r\n\t\t\t\t\tpoint.setState();\r\n\t\t\t\t});\r\n\t\t\t}\r\n\r\n\t\t\teach(point, function (item) {\r\n\t\t\t\titem.setState(HOVER_STATE);\r\n\r\n\t\t\t\tpointConfig.push(item.getLabelConfig());\r\n\t\t\t});\r\n\r\n\t\t\ttextConfig = {\r\n\t\t\t\tx: point[0].category,\r\n\t\t\t\ty: point[0].y\r\n\t\t\t};\r\n\t\t\ttextConfig.points = pointConfig;\r\n\t\t\tpoint = point[0];\r\n\r\n\t\t// single point tooltip\r\n\t\t} else {\r\n\t\t\ttextConfig = point.getLabelConfig();\r\n\t\t}\r\n\t\ttext = formatter.call(textConfig, tooltip);\r\n\r\n\t\t// register the current series\r\n\t\tcurrentSeries = point.series;\r\n\r\n\t\t// update the inner HTML\r\n\t\tif (text === false) {\r\n\t\t\tthis.hide();\r\n\t\t} else {\r\n\r\n\t\t\t// show it\r\n\t\t\tif (tooltip.isHidden) {\r\n\t\t\t\tstop(label);\r\n\t\t\t\tlabel.attr('opacity', 1).show();\r\n\t\t\t}\r\n\r\n\t\t\t// update text\r\n\t\t\tlabel.attr({\r\n\t\t\t\ttext: text\r\n\t\t\t});\r\n\r\n\t\t\t// set the stroke color of the box\r\n\t\t\tborderColor = options.borderColor || point.color || currentSeries.color || '#606060';\r\n\t\t\tlabel.attr({\r\n\t\t\t\tstroke: borderColor\r\n\t\t\t});\r\n\t\t\t\r\n\t\t\ttooltip.updatePosition({ plotX: x, plotY: y });\r\n\t\t\r\n\t\t\tthis.isHidden = false;\r\n\t\t}\r\n\r\n\t\t// crosshairs\r\n\t\tif (crosshairsOptions) {\r\n\t\t\tcrosshairsOptions = splat(crosshairsOptions); // [x, y]\r\n\r\n\t\t\tvar path,\r\n\t\t\t\ti = crosshairsOptions.length,\r\n\t\t\t\tattribs,\r\n\t\t\t\taxis,\r\n\t\t\t\tval,\r\n\t\t\t\tseries;\r\n\r\n\t\t\twhile (i--) {\r\n\t\t\t\tseries = point.series;\r\n\t\t\t\taxis = series[i ? 'yAxis' : 'xAxis'];\r\n\t\t\t\tif (crosshairsOptions[i] && axis) {\r\n\t\t\t\t\tval = i ? pick(point.stackY, point.y) : point.x; // #814\r\n\t\t\t\t\tif (axis.isLog) { // #1671\r\n\t\t\t\t\t\tval = log2lin(val);\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (i === 1 && series.modifyValue) { // #1205, #2316\r\n\t\t\t\t\t\tval = series.modifyValue(val);\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tpath = axis.getPlotLinePath(\r\n\t\t\t\t\t\tval,\r\n\t\t\t\t\t\t1\r\n\t\t\t\t\t);\r\n\r\n\t\t\t\t\tif (tooltip.crosshairs[i]) {\r\n\t\t\t\t\t\ttooltip.crosshairs[i].attr({ d: path, visibility: VISIBLE });\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tattribs = {\r\n\t\t\t\t\t\t\t'stroke-width': crosshairsOptions[i].width || 1,\r\n\t\t\t\t\t\t\tstroke: crosshairsOptions[i].color || '#C0C0C0',\r\n\t\t\t\t\t\t\tzIndex: crosshairsOptions[i].zIndex || 2\r\n\t\t\t\t\t\t};\r\n\t\t\t\t\t\tif (crosshairsOptions[i].dashStyle) {\r\n\t\t\t\t\t\t\tattribs.dashstyle = crosshairsOptions[i].dashStyle;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\ttooltip.crosshairs[i] = chart.renderer.path(path)\r\n\t\t\t\t\t\t\t.attr(attribs)\r\n\t\t\t\t\t\t\t.add();\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\tfireEvent(chart, 'tooltipRefresh', {\r\n\t\t\t\ttext: text,\r\n\t\t\t\tx: x + chart.plotLeft,\r\n\t\t\t\ty: y + chart.plotTop,\r\n\t\t\t\tborderColor: borderColor\r\n\t\t\t});\r\n\t},\r\n\t\r\n\t/**\r\n\t * Find the new position and perform the move\r\n\t */\r\n\tupdatePosition: function (point) {\r\n\t\tvar chart = this.chart,\r\n\t\t\tlabel = this.label, \r\n\t\t\tpos = (this.options.positioner || this.getPosition).call(\r\n\t\t\t\tthis,\r\n\t\t\t\tlabel.width,\r\n\t\t\t\tlabel.height,\r\n\t\t\t\tpoint\r\n\t\t\t);\r\n\r\n\t\t// do the move\r\n\t\tthis.move(\r\n\t\t\tmathRound(pos.x), \r\n\t\t\tmathRound(pos.y), \r\n\t\t\tpoint.plotX + chart.plotLeft, \r\n\t\t\tpoint.plotY + chart.plotTop\r\n\t\t);\r\n\t}\r\n};\r\n/**\r\n * The mouse tracker object. All methods starting with \"on\" are primary DOM event handlers. \r\n * Subsequent methods should be named differently from what they are doing.\r\n * @param {Object} chart The Chart instance\r\n * @param {Object} options The root options object\r\n */\r\nfunction Pointer(chart, options) {\r\n\tthis.init(chart, options);\r\n}\r\n\r\nPointer.prototype = {\r\n\t/**\r\n\t * Initialize Pointer\r\n\t */\r\n\tinit: function (chart, options) {\r\n\t\t\r\n\t\tvar chartOptions = options.chart,\r\n\t\t\tchartEvents = chartOptions.events,\r\n\t\t\tzoomType = useCanVG ? '' : chartOptions.zoomType,\r\n\t\t\tinverted = chart.inverted,\r\n\t\t\tzoomX,\r\n\t\t\tzoomY;\r\n\r\n\t\t// Store references\r\n\t\tthis.options = options;\r\n\t\tthis.chart = chart;\r\n\t\t\r\n\t\t// Zoom status\r\n\t\tthis.zoomX = zoomX = /x/.test(zoomType);\r\n\t\tthis.zoomY = zoomY = /y/.test(zoomType);\r\n\t\tthis.zoomHor = (zoomX && !inverted) || (zoomY && inverted);\r\n\t\tthis.zoomVert = (zoomY && !inverted) || (zoomX && inverted);\r\n\r\n\t\t// Do we need to handle click on a touch device?\r\n\t\tthis.runChartClick = chartEvents && !!chartEvents.click;\r\n\r\n\t\tthis.pinchDown = [];\r\n\t\tthis.lastValidTouch = {};\r\n\r\n\t\tif (options.tooltip.enabled) {\r\n\t\t\tchart.tooltip = new Tooltip(chart, options.tooltip);\r\n\t\t}\r\n\r\n\t\tthis.setDOMEvents();\r\n\t}, \r\n\r\n\t/**\r\n\t * Add crossbrowser support for chartX and chartY\r\n\t * @param {Object} e The event object in standard browsers\r\n\t */\r\n\tnormalize: function (e, chartPosition) {\r\n\t\tvar chartX,\r\n\t\t\tchartY,\r\n\t\t\tePos;\r\n\r\n\t\t// common IE normalizing\r\n\t\te = e || win.event;\r\n\t\tif (!e.target) {\r\n\t\t\te.target = e.srcElement;\r\n\t\t}\r\n\r\n\t\t// Framework specific normalizing (#1165)\r\n\t\te = washMouseEvent(e);\r\n\t\t\r\n\t\t// iOS\r\n\t\tePos = e.touches ? e.touches.item(0) : e;\r\n\r\n\t\t// Get mouse position\r\n\t\tif (!chartPosition) {\r\n\t\t\tthis.chartPosition = chartPosition = offset(this.chart.container);\r\n\t\t}\r\n\r\n\t\t// chartX and chartY\r\n\t\tif (ePos.pageX === UNDEFINED) { // IE < 9. #886.\r\n\t\t\tchartX = mathMax(e.x, e.clientX - chartPosition.left); // #2005, #2129: the second case is \r\n\t\t\t\t// for IE10 quirks mode within framesets\r\n\t\t\tchartY = e.y;\r\n\t\t} else {\r\n\t\t\tchartX = ePos.pageX - chartPosition.left;\r\n\t\t\tchartY = ePos.pageY - chartPosition.top;\r\n\t\t}\r\n\r\n\t\treturn extend(e, {\r\n\t\t\tchartX: mathRound(chartX),\r\n\t\t\tchartY: mathRound(chartY)\r\n\t\t});\r\n\t},\r\n\r\n\t/**\r\n\t * Get the click position in terms of axis values.\r\n\t *\r\n\t * @param {Object} e A pointer event\r\n\t */\r\n\tgetCoordinates: function (e) {\r\n\t\tvar coordinates = {\r\n\t\t\t\txAxis: [],\r\n\t\t\t\tyAxis: []\r\n\t\t\t};\r\n\r\n\t\teach(this.chart.axes, function (axis) {\r\n\t\t\tcoordinates[axis.isXAxis ? 'xAxis' : 'yAxis'].push({\r\n\t\t\t\taxis: axis,\r\n\t\t\t\tvalue: axis.toValue(e[axis.horiz ? 'chartX' : 'chartY'])\r\n\t\t\t});\r\n\t\t});\r\n\t\treturn coordinates;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Return the index in the tooltipPoints array, corresponding to pixel position in \r\n\t * the plot area.\r\n\t */\r\n\tgetIndex: function (e) {\r\n\t\tvar chart = this.chart;\r\n\t\treturn chart.inverted ? \r\n\t\t\tchart.plotHeight + chart.plotTop - e.chartY : \r\n\t\t\te.chartX - chart.plotLeft;\r\n\t},\r\n\r\n\t/**\r\n\t * With line type charts with a single tracker, get the point closest to the mouse.\r\n\t * Run Point.onMouseOver and display tooltip for the point or points.\r\n\t */\r\n\trunPointActions: function (e) {\r\n\t\tvar pointer = this,\r\n\t\t\tchart = pointer.chart,\r\n\t\t\tseries = chart.series,\r\n\t\t\ttooltip = chart.tooltip,\r\n\t\t\tpoint,\r\n\t\t\tpoints,\r\n\t\t\thoverPoint = chart.hoverPoint,\r\n\t\t\thoverSeries = chart.hoverSeries,\r\n\t\t\ti,\r\n\t\t\tj,\r\n\t\t\tdistance = chart.chartWidth,\r\n\t\t\tindex = pointer.getIndex(e),\r\n\t\t\tanchor;\r\n\r\n\t\t// shared tooltip\r\n\t\tif (tooltip && pointer.options.tooltip.shared && !(hoverSeries && hoverSeries.noSharedTooltip)) {\r\n\t\t\tpoints = [];\r\n\r\n\t\t\t// loop over all series and find the ones with points closest to the mouse\r\n\t\t\ti = series.length;\r\n\t\t\tfor (j = 0; j < i; j++) {\r\n\t\t\t\tif (series[j].visible &&\r\n\t\t\t\t\t\tseries[j].options.enableMouseTracking !== false &&\r\n\t\t\t\t\t\t!series[j].noSharedTooltip && series[j].tooltipPoints.length) {\r\n\t\t\t\t\tpoint = series[j].tooltipPoints[index];\r\n\t\t\t\t\tif (point && point.series) { // not a dummy point, #1544\r\n\t\t\t\t\t\tpoint._dist = mathAbs(index - point.clientX);\r\n\t\t\t\t\t\tdistance = mathMin(distance, point._dist);\r\n\t\t\t\t\t\tpoints.push(point);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t// remove furthest points\r\n\t\t\ti = points.length;\r\n\t\t\twhile (i--) {\r\n\t\t\t\tif (points[i]._dist > distance) {\r\n\t\t\t\t\tpoints.splice(i, 1);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t// refresh the tooltip if necessary\r\n\t\t\tif (points.length && (points[0].clientX !== pointer.hoverX)) {\r\n\t\t\t\ttooltip.refresh(points, e);\r\n\t\t\t\tpointer.hoverX = points[0].clientX;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// separate tooltip and general mouse events\r\n\t\tif (hoverSeries && hoverSeries.tracker) { // only use for line-type series with common tracker\r\n\r\n\t\t\t// get the point\r\n\t\t\tpoint = hoverSeries.tooltipPoints[index];\r\n\r\n\t\t\t// a new point is hovered, refresh the tooltip\r\n\t\t\tif (point && point !== hoverPoint) {\r\n\r\n\t\t\t\t// trigger the events\r\n\t\t\t\tpoint.onMouseOver(e);\r\n\r\n\t\t\t}\r\n\t\t\t\r\n\t\t} else if (tooltip && tooltip.followPointer && !tooltip.isHidden) {\r\n\t\t\tanchor = tooltip.getAnchor([{}], e);\r\n\t\t\ttooltip.updatePosition({ plotX: anchor[0], plotY: anchor[1] });\r\n\t\t}\r\n\t},\r\n\r\n\r\n\r\n\t/**\r\n\t * Reset the tracking by hiding the tooltip, the hover series state and the hover point\r\n\t * \r\n\t * @param allowMove {Boolean} Instead of destroying the tooltip altogether, allow moving it if possible\r\n\t */\r\n\treset: function (allowMove) {\r\n\t\tvar pointer = this,\r\n\t\t\tchart = pointer.chart,\r\n\t\t\thoverSeries = chart.hoverSeries,\r\n\t\t\thoverPoint = chart.hoverPoint,\r\n\t\t\ttooltip = chart.tooltip,\r\n\t\t\ttooltipPoints = tooltip && tooltip.shared ? chart.hoverPoints : hoverPoint;\r\n\t\t\t\r\n\t\t// Narrow in allowMove\r\n\t\tallowMove = allowMove && tooltip && tooltipPoints;\r\n\t\t\t\r\n\t\t// Check if the points have moved outside the plot area, #1003\r\n\t\tif (allowMove && splat(tooltipPoints)[0].plotX === UNDEFINED) {\r\n\t\t\tallowMove = false;\r\n\t\t}\t\r\n\r\n\t\t// Just move the tooltip, #349\r\n\t\tif (allowMove) {\r\n\t\t\ttooltip.refresh(tooltipPoints);\r\n\r\n\t\t// Full reset\r\n\t\t} else {\r\n\r\n\t\t\tif (hoverPoint) {\r\n\t\t\t\thoverPoint.onMouseOut();\r\n\t\t\t}\r\n\r\n\t\t\tif (hoverSeries) {\r\n\t\t\t\thoverSeries.onMouseOut();\r\n\t\t\t}\r\n\r\n\t\t\tif (tooltip) {\r\n\t\t\t\ttooltip.hide();\r\n\t\t\t\ttooltip.hideCrosshairs();\r\n\t\t\t}\r\n\r\n\t\t\tpointer.hoverX = null;\r\n\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Scale series groups to a certain scale and translation\r\n\t */\r\n\tscaleGroups: function (attribs, clip) {\r\n\r\n\t\tvar chart = this.chart,\r\n\t\t\tseriesAttribs;\r\n\r\n\t\t// Scale each series\r\n\t\teach(chart.series, function (series) {\r\n\t\t\tseriesAttribs = attribs || series.getPlotBox(); // #1701\r\n\t\t\tif (series.xAxis && series.xAxis.zoomEnabled) {\r\n\t\t\t\tseries.group.attr(seriesAttribs);\r\n\t\t\t\tif (series.markerGroup) {\r\n\t\t\t\t\tseries.markerGroup.attr(seriesAttribs);\r\n\t\t\t\t\tseries.markerGroup.clip(clip ? chart.clipRect : null);\r\n\t\t\t\t}\r\n\t\t\t\tif (series.dataLabelsGroup) {\r\n\t\t\t\t\tseries.dataLabelsGroup.attr(seriesAttribs);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t});\r\n\t\t\r\n\t\t// Clip\r\n\t\tchart.clipRect.attr(clip || chart.clipBox);\r\n\t},\r\n\r\n\t/**\r\n\t * Run translation operations for each direction (horizontal and vertical) independently\r\n\t */\r\n\tpinchTranslateDirection: function (horiz, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch) {\r\n\t\tvar chart = this.chart,\r\n\t\t\txy = horiz ? 'x' : 'y',\r\n\t\t\tXY = horiz ? 'X' : 'Y',\r\n\t\t\tsChartXY = 'chart' + XY,\r\n\t\t\twh = horiz ? 'width' : 'height',\r\n\t\t\tplotLeftTop = chart['plot' + (horiz ? 'Left' : 'Top')],\r\n\t\t\tselectionWH,\r\n\t\t\tselectionXY,\r\n\t\t\tclipXY,\r\n\t\t\tscale = 1,\r\n\t\t\tinverted = chart.inverted,\r\n\t\t\tbounds = chart.bounds[horiz ? 'h' : 'v'],\r\n\t\t\tsingleTouch = pinchDown.length === 1,\r\n\t\t\ttouch0Start = pinchDown[0][sChartXY],\r\n\t\t\ttouch0Now = touches[0][sChartXY],\r\n\t\t\ttouch1Start = !singleTouch && pinchDown[1][sChartXY],\r\n\t\t\ttouch1Now = !singleTouch && touches[1][sChartXY],\r\n\t\t\toutOfBounds,\r\n\t\t\ttransformScale,\r\n\t\t\tscaleKey,\r\n\t\t\tsetScale = function () {\r\n\t\t\t\tif (!singleTouch && mathAbs(touch0Start - touch1Start) > 20) { // Don't zoom if fingers are too close on this axis\r\n\t\t\t\t\tscale = mathAbs(touch0Now - touch1Now) / mathAbs(touch0Start - touch1Start);\t\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tclipXY = ((plotLeftTop - touch0Now) / scale) + touch0Start;\r\n\t\t\t\tselectionWH = chart['plot' + (horiz ? 'Width' : 'Height')] / scale;\r\n\t\t\t};\r\n\r\n\t\t// Set the scale, first pass\r\n\t\tsetScale();\r\n\r\n\t\tselectionXY = clipXY; // the clip position (x or y) is altered if out of bounds, the selection position is not\r\n\r\n\t\t// Out of bounds\r\n\t\tif (selectionXY < bounds.min) {\r\n\t\t\tselectionXY = bounds.min;\r\n\t\t\toutOfBounds = true;\r\n\t\t} else if (selectionXY + selectionWH > bounds.max) {\r\n\t\t\tselectionXY = bounds.max - selectionWH;\r\n\t\t\toutOfBounds = true;\r\n\t\t}\r\n\t\t\r\n\t\t// Is the chart dragged off its bounds, determined by dataMin and dataMax?\r\n\t\tif (outOfBounds) {\r\n\r\n\t\t\t// Modify the touchNow position in order to create an elastic drag movement. This indicates\r\n\t\t\t// to the user that the chart is responsive but can't be dragged further.\r\n\t\t\ttouch0Now -= 0.8 * (touch0Now - lastValidTouch[xy][0]);\r\n\t\t\tif (!singleTouch) {\r\n\t\t\t\ttouch1Now -= 0.8 * (touch1Now - lastValidTouch[xy][1]);\r\n\t\t\t}\r\n\r\n\t\t\t// Set the scale, second pass to adapt to the modified touchNow positions\r\n\t\t\tsetScale();\r\n\r\n\t\t} else {\r\n\t\t\tlastValidTouch[xy] = [touch0Now, touch1Now];\r\n\t\t}\r\n\r\n\t\t\r\n\t\t// Set geometry for clipping, selection and transformation\r\n\t\tif (!inverted) { // TODO: implement clipping for inverted charts\r\n\t\t\tclip[xy] = clipXY - plotLeftTop;\r\n\t\t\tclip[wh] = selectionWH;\r\n\t\t}\r\n\t\tscaleKey = inverted ? (horiz ? 'scaleY' : 'scaleX') : 'scale' + XY;\r\n\t\ttransformScale = inverted ? 1 / scale : scale;\r\n\r\n\t\tselectionMarker[wh] = selectionWH;\r\n\t\tselectionMarker[xy] = selectionXY;\r\n\t\ttransform[scaleKey] = scale;\r\n\t\ttransform['translate' + XY] = (transformScale * plotLeftTop) + (touch0Now - (transformScale * touch0Start));\r\n\t},\r\n\t\r\n\t/**\r\n\t * Handle touch events with two touches\r\n\t */\r\n\tpinch: function (e) {\r\n\r\n\t\tvar self = this,\r\n\t\t\tchart = self.chart,\r\n\t\t\tpinchDown = self.pinchDown,\r\n\t\t\tfollowTouchMove = chart.tooltip && chart.tooltip.options.followTouchMove,\r\n\t\t\ttouches = e.touches,\r\n\t\t\ttouchesLength = touches.length,\r\n\t\t\tlastValidTouch = self.lastValidTouch,\r\n\t\t\tzoomHor = self.zoomHor || self.pinchHor,\r\n\t\t\tzoomVert = self.zoomVert || self.pinchVert,\r\n\t\t\thasZoom = zoomHor || zoomVert,\r\n\t\t\tselectionMarker = self.selectionMarker,\r\n\t\t\ttransform = {},\r\n\t\t\tfireClickEvent = touchesLength === 1 && ((self.inClass(e.target, PREFIX + 'tracker') && \r\n\t\t\t\tchart.runTrackerClick) || chart.runChartClick),\r\n\t\t\tclip = {};\r\n\r\n\t\t// On touch devices, only proceed to trigger click if a handler is defined\r\n\t\tif ((hasZoom || followTouchMove) && !fireClickEvent) {\r\n\t\t\te.preventDefault();\r\n\t\t}\r\n\t\t\r\n\t\t// Normalize each touch\r\n\t\tmap(touches, function (e) {\r\n\t\t\treturn self.normalize(e);\r\n\t\t});\r\n\t\t\t\r\n\t\t// Register the touch start position\r\n\t\tif (e.type === 'touchstart') {\r\n\t\t\teach(touches, function (e, i) {\r\n\t\t\t\tpinchDown[i] = { chartX: e.chartX, chartY: e.chartY };\r\n\t\t\t});\r\n\t\t\tlastValidTouch.x = [pinchDown[0].chartX, pinchDown[1] && pinchDown[1].chartX];\r\n\t\t\tlastValidTouch.y = [pinchDown[0].chartY, pinchDown[1] && pinchDown[1].chartY];\r\n\r\n\t\t\t// Identify the data bounds in pixels\r\n\t\t\teach(chart.axes, function (axis) {\r\n\t\t\t\tif (axis.zoomEnabled) {\r\n\t\t\t\t\tvar bounds = chart.bounds[axis.horiz ? 'h' : 'v'],\r\n\t\t\t\t\t\tminPixelPadding = axis.minPixelPadding,\r\n\t\t\t\t\t\tmin = axis.toPixels(axis.dataMin),\r\n\t\t\t\t\t\tmax = axis.toPixels(axis.dataMax),\r\n\t\t\t\t\t\tabsMin = mathMin(min, max),\r\n\t\t\t\t\t\tabsMax = mathMax(min, max);\r\n\r\n\t\t\t\t\t// Store the bounds for use in the touchmove handler\r\n\t\t\t\t\tbounds.min = mathMin(axis.pos, absMin - minPixelPadding);\r\n\t\t\t\t\tbounds.max = mathMax(axis.pos + axis.len, absMax + minPixelPadding);\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t\r\n\t\t// Event type is touchmove, handle panning and pinching\r\n\t\t} else if (pinchDown.length) { // can be 0 when releasing, if touchend fires first\r\n\t\t\t\r\n\r\n\t\t\t// Set the marker\r\n\t\t\tif (!selectionMarker) {\r\n\t\t\t\tself.selectionMarker = selectionMarker = extend({\r\n\t\t\t\t\tdestroy: noop\r\n\t\t\t\t}, chart.plotBox);\r\n\t\t\t}\r\n\r\n\t\t\t\r\n\r\n\t\t\tif (zoomHor) {\r\n\t\t\t\tself.pinchTranslateDirection(true, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch);\r\n\t\t\t}\r\n\t\t\tif (zoomVert) {\r\n\t\t\t\tself.pinchTranslateDirection(false, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch);\r\n\t\t\t}\r\n\r\n\t\t\tself.hasPinched = hasZoom;\r\n\r\n\t\t\t// Scale and translate the groups to provide visual feedback during pinching\r\n\t\t\tself.scaleGroups(transform, clip);\r\n\t\t\t\r\n\t\t\t// Optionally move the tooltip on touchmove\r\n\t\t\tif (!hasZoom && followTouchMove && touchesLength === 1) {\r\n\t\t\t\tthis.runPointActions(self.normalize(e));\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Start a drag operation\r\n\t */\r\n\tdragStart: function (e) {\r\n\t\tvar chart = this.chart;\r\n\r\n\t\t// Record the start position\r\n\t\tchart.mouseIsDown = e.type;\r\n\t\tchart.cancelClick = false;\r\n\t\tchart.mouseDownX = this.mouseDownX = e.chartX;\r\n\t\tchart.mouseDownY = this.mouseDownY = e.chartY;\r\n\t},\r\n\r\n\t/**\r\n\t * Perform a drag operation in response to a mousemove event while the mouse is down\r\n\t */\r\n\tdrag: function (e) {\r\n\r\n\t\tvar chart = this.chart,\r\n\t\t\tchartOptions = chart.options.chart,\r\n\t\t\tchartX = e.chartX,\r\n\t\t\tchartY = e.chartY,\r\n\t\t\tzoomHor = this.zoomHor,\r\n\t\t\tzoomVert = this.zoomVert,\r\n\t\t\tplotLeft = chart.plotLeft,\r\n\t\t\tplotTop = chart.plotTop,\r\n\t\t\tplotWidth = chart.plotWidth,\r\n\t\t\tplotHeight = chart.plotHeight,\r\n\t\t\tclickedInside,\r\n\t\t\tsize,\r\n\t\t\tmouseDownX = this.mouseDownX,\r\n\t\t\tmouseDownY = this.mouseDownY;\r\n\r\n\t\t// If the mouse is outside the plot area, adjust to cooordinates\r\n\t\t// inside to prevent the selection marker from going outside\r\n\t\tif (chartX < plotLeft) {\r\n\t\t\tchartX = plotLeft;\r\n\t\t} else if (chartX > plotLeft + plotWidth) {\r\n\t\t\tchartX = plotLeft + plotWidth;\r\n\t\t}\r\n\r\n\t\tif (chartY < plotTop) {\r\n\t\t\tchartY = plotTop;\r\n\t\t} else if (chartY > plotTop + plotHeight) {\r\n\t\t\tchartY = plotTop + plotHeight;\r\n\t\t}\r\n\t\t\r\n\t\t// determine if the mouse has moved more than 10px\r\n\t\tthis.hasDragged = Math.sqrt(\r\n\t\t\tMath.pow(mouseDownX - chartX, 2) +\r\n\t\t\tMath.pow(mouseDownY - chartY, 2)\r\n\t\t);\r\n\t\tif (this.hasDragged > 10) {\r\n\t\t\tclickedInside = chart.isInsidePlot(mouseDownX - plotLeft, mouseDownY - plotTop);\r\n\r\n\t\t\t// make a selection\r\n\t\t\tif (chart.hasCartesianSeries && (this.zoomX || this.zoomY) && clickedInside) {\r\n\t\t\t\tif (!this.selectionMarker) {\r\n\t\t\t\t\tthis.selectionMarker = chart.renderer.rect(\r\n\t\t\t\t\t\tplotLeft,\r\n\t\t\t\t\t\tplotTop,\r\n\t\t\t\t\t\tzoomHor ? 1 : plotWidth,\r\n\t\t\t\t\t\tzoomVert ? 1 : plotHeight,\r\n\t\t\t\t\t\t0\r\n\t\t\t\t\t)\r\n\t\t\t\t\t.attr({\r\n\t\t\t\t\t\tfill: chartOptions.selectionMarkerFill || 'rgba(69,114,167,0.25)',\r\n\t\t\t\t\t\tzIndex: 7\r\n\t\t\t\t\t})\r\n\t\t\t\t\t.add();\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// adjust the width of the selection marker\r\n\t\t\tif (this.selectionMarker && zoomHor) {\r\n\t\t\t\tsize = chartX - mouseDownX;\r\n\t\t\t\tthis.selectionMarker.attr({\r\n\t\t\t\t\twidth: mathAbs(size),\r\n\t\t\t\t\tx: (size > 0 ? 0 : size) + mouseDownX\r\n\t\t\t\t});\r\n\t\t\t}\r\n\t\t\t// adjust the height of the selection marker\r\n\t\t\tif (this.selectionMarker && zoomVert) {\r\n\t\t\t\tsize = chartY - mouseDownY;\r\n\t\t\t\tthis.selectionMarker.attr({\r\n\t\t\t\t\theight: mathAbs(size),\r\n\t\t\t\t\ty: (size > 0 ? 0 : size) + mouseDownY\r\n\t\t\t\t});\r\n\t\t\t}\r\n\r\n\t\t\t// panning\r\n\t\t\tif (clickedInside && !this.selectionMarker && chartOptions.panning) {\r\n\t\t\t\tchart.pan(e, chartOptions.panning);\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * On mouse up or touch end across the entire document, drop the selection.\r\n\t */\r\n\tdrop: function (e) {\r\n\t\tvar chart = this.chart,\r\n\t\t\thasPinched = this.hasPinched;\r\n\r\n\t\tif (this.selectionMarker) {\r\n\t\t\tvar selectionData = {\r\n\t\t\t\t\txAxis: [],\r\n\t\t\t\t\tyAxis: [],\r\n\t\t\t\t\toriginalEvent: e.originalEvent || e\r\n\t\t\t\t},\r\n\t\t\t\tselectionBox = this.selectionMarker,\r\n\t\t\t\tselectionLeft = selectionBox.x,\r\n\t\t\t\tselectionTop = selectionBox.y,\r\n\t\t\t\trunZoom;\r\n\t\t\t// a selection has been made\r\n\t\t\tif (this.hasDragged || hasPinched) {\r\n\r\n\t\t\t\t// record each axis' min and max\r\n\t\t\t\teach(chart.axes, function (axis) {\r\n\t\t\t\t\tif (axis.zoomEnabled) {\r\n\t\t\t\t\t\tvar horiz = axis.horiz,\r\n\t\t\t\t\t\t\tselectionMin = axis.toValue((horiz ? selectionLeft : selectionTop)),\r\n\t\t\t\t\t\t\tselectionMax = axis.toValue((horiz ? selectionLeft + selectionBox.width : selectionTop + selectionBox.height));\r\n\r\n\t\t\t\t\t\tif (!isNaN(selectionMin) && !isNaN(selectionMax)) { // #859\r\n\t\t\t\t\t\t\tselectionData[axis.xOrY + 'Axis'].push({\r\n\t\t\t\t\t\t\t\taxis: axis,\r\n\t\t\t\t\t\t\t\tmin: mathMin(selectionMin, selectionMax), // for reversed axes,\r\n\t\t\t\t\t\t\t\tmax: mathMax(selectionMin, selectionMax)\r\n\t\t\t\t\t\t\t});\r\n\t\t\t\t\t\t\trunZoom = true;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t});\r\n\t\t\t\tif (runZoom) {\r\n\t\t\t\t\tfireEvent(chart, 'selection', selectionData, function (args) { \r\n\t\t\t\t\t\tchart.zoom(extend(args, hasPinched ? { animation: false } : null)); \r\n\t\t\t\t\t});\r\n\t\t\t\t}\r\n\r\n\t\t\t}\r\n\t\t\tthis.selectionMarker = this.selectionMarker.destroy();\r\n\r\n\t\t\t// Reset scaling preview\r\n\t\t\tif (hasPinched) {\r\n\t\t\t\tthis.scaleGroups();\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Reset all\r\n\t\tif (chart) { // it may be destroyed on mouse up - #877\r\n\t\t\tcss(chart.container, { cursor: chart._cursor });\r\n\t\t\tchart.cancelClick = this.hasDragged > 10; // #370\r\n\t\t\tchart.mouseIsDown = this.hasDragged = this.hasPinched = false;\r\n\t\t\tthis.pinchDown = [];\r\n\t\t}\r\n\t},\r\n\r\n\tonContainerMouseDown: function (e) {\r\n\r\n\t\te = this.normalize(e);\r\n\r\n\t\t// issue #295, dragging not always working in Firefox\r\n\t\tif (e.preventDefault) {\r\n\t\t\te.preventDefault();\r\n\t\t}\r\n\t\t\r\n\t\tthis.dragStart(e);\r\n\t},\r\n\r\n\t\r\n\r\n\tonDocumentMouseUp: function (e) {\r\n\t\tthis.drop(e);\r\n\t},\r\n\r\n\t/**\r\n\t * Special handler for mouse move that will hide the tooltip when the mouse leaves the plotarea.\r\n\t * Issue #149 workaround. The mouseleave event does not always fire. \r\n\t */\r\n\tonDocumentMouseMove: function (e) {\r\n\t\tvar chart = this.chart,\r\n\t\t\tchartPosition = this.chartPosition,\r\n\t\t\thoverSeries = chart.hoverSeries;\r\n\r\n\t\te = this.normalize(e, chartPosition);\r\n\r\n\t\t// If we're outside, hide the tooltip\r\n\t\tif (chartPosition && hoverSeries && !this.inClass(e.target, 'highcharts-tracker') &&\r\n\t\t\t\t!chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) {\r\n\t\t\tthis.reset();\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * When mouse leaves the container, hide the tooltip.\r\n\t */\r\n\tonContainerMouseLeave: function () {\r\n\t\tthis.reset();\r\n\t\tthis.chartPosition = null; // also reset the chart position, used in #149 fix\r\n\t},\r\n\r\n\t// The mousemove, touchmove and touchstart event handler\r\n\tonContainerMouseMove: function (e) {\r\n\r\n\t\tvar chart = this.chart;\r\n\r\n\t\t// normalize\r\n\t\te = this.normalize(e);\r\n\r\n\t\t// #295\r\n\t\te.returnValue = false;\r\n\t\t\r\n\t\t\r\n\t\tif (chart.mouseIsDown === 'mousedown') {\r\n\t\t\tthis.drag(e);\r\n\t\t} \r\n\t\t\r\n\t\t// Show the tooltip and run mouse over events (#977)\r\n\t\tif ((this.inClass(e.target, 'highcharts-tracker') || \r\n\t\t\t\tchart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) && !chart.openMenu) {\r\n\t\t\tthis.runPointActions(e);\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Utility to detect whether an element has, or has a parent with, a specific\r\n\t * class name. Used on detection of tracker objects and on deciding whether\r\n\t * hovering the tooltip should cause the active series to mouse out.\r\n\t */\r\n\tinClass: function (element, className) {\r\n\t\tvar elemClassName;\r\n\t\twhile (element) {\r\n\t\t\telemClassName = attr(element, 'class');\r\n\t\t\tif (elemClassName) {\r\n\t\t\t\tif (elemClassName.indexOf(className) !== -1) {\r\n\t\t\t\t\treturn true;\r\n\t\t\t\t} else if (elemClassName.indexOf(PREFIX + 'container') !== -1) {\r\n\t\t\t\t\treturn false;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\telement = element.parentNode;\r\n\t\t}\t\t\r\n\t},\r\n\r\n\tonTrackerMouseOut: function (e) {\r\n\t\tvar series = this.chart.hoverSeries;\r\n\t\tif (series && !series.options.stickyTracking && !this.inClass(e.toElement || e.relatedTarget, PREFIX + 'tooltip')) {\r\n\t\t\tseries.onMouseOut();\r\n\t\t}\r\n\t},\r\n\r\n\tonContainerClick: function (e) {\r\n\t\tvar chart = this.chart,\r\n\t\t\thoverPoint = chart.hoverPoint, \r\n\t\t\tplotLeft = chart.plotLeft,\r\n\t\t\tplotTop = chart.plotTop,\r\n\t\t\tinverted = chart.inverted,\r\n\t\t\tchartPosition,\r\n\t\t\tplotX,\r\n\t\t\tplotY;\r\n\t\t\r\n\t\te = this.normalize(e);\r\n\t\te.cancelBubble = true; // IE specific\r\n\r\n\t\tif (!chart.cancelClick) {\r\n\t\t\t\r\n\t\t\t// On tracker click, fire the series and point events. #783, #1583\r\n\t\t\tif (hoverPoint && this.inClass(e.target, PREFIX + 'tracker')) {\r\n\t\t\t\tchartPosition = this.chartPosition;\r\n\t\t\t\tplotX = hoverPoint.plotX;\r\n\t\t\t\tplotY = hoverPoint.plotY;\r\n\r\n\t\t\t\t// add page position info\r\n\t\t\t\textend(hoverPoint, {\r\n\t\t\t\t\tpageX: chartPosition.left + plotLeft +\r\n\t\t\t\t\t\t(inverted ? chart.plotWidth - plotY : plotX),\r\n\t\t\t\t\tpageY: chartPosition.top + plotTop +\r\n\t\t\t\t\t\t(inverted ? chart.plotHeight - plotX : plotY)\r\n\t\t\t\t});\r\n\t\t\t\r\n\t\t\t\t// the series click event\r\n\t\t\t\tfireEvent(hoverPoint.series, 'click', extend(e, {\r\n\t\t\t\t\tpoint: hoverPoint\r\n\t\t\t\t}));\r\n\r\n\t\t\t\t// the point click event\r\n\t\t\t\tif (chart.hoverPoint) { // it may be destroyed (#1844)\r\n\t\t\t\t\thoverPoint.firePointEvent('click', e);\r\n\t\t\t\t}\r\n\r\n\t\t\t// When clicking outside a tracker, fire a chart event\r\n\t\t\t} else {\r\n\t\t\t\textend(e, this.getCoordinates(e));\r\n\r\n\t\t\t\t// fire a click event in the chart\r\n\t\t\t\tif (chart.isInsidePlot(e.chartX - plotLeft, e.chartY - plotTop)) {\r\n\t\t\t\t\tfireEvent(chart, 'click', e);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\r\n\t\t}\r\n\t},\r\n\r\n\tonContainerTouchStart: function (e) {\r\n\t\tvar chart = this.chart;\r\n\r\n\t\tif (e.touches.length === 1) {\r\n\r\n\t\t\te = this.normalize(e);\r\n\r\n\t\t\tif (chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) {\r\n\r\n\t\t\t\t// Prevent the click pseudo event from firing unless it is set in the options\r\n\t\t\t\t/*if (!chart.runChartClick) {\r\n\t\t\t\t\te.preventDefault();\r\n\t\t\t\t}*/\r\n\t\t\t\r\n\t\t\t\t// Run mouse events and display tooltip etc\r\n\t\t\t\tthis.runPointActions(e);\r\n\r\n\t\t\t\tthis.pinch(e);\r\n\r\n\t\t\t} else {\r\n\t\t\t\t// Hide the tooltip on touching outside the plot area (#1203)\r\n\t\t\t\tthis.reset();\r\n\t\t\t}\r\n\r\n\t\t} else if (e.touches.length === 2) {\r\n\t\t\tthis.pinch(e);\r\n\t\t}\t\t\r\n\t},\r\n\r\n\tonContainerTouchMove: function (e) {\r\n\t\tif (e.touches.length === 1 || e.touches.length === 2) {\r\n\t\t\tthis.pinch(e);\r\n\t\t}\r\n\t},\r\n\r\n\tonDocumentTouchEnd: function (e) {\r\n\t\tthis.drop(e);\r\n\t},\r\n\r\n\t/**\r\n\t * Set the JS DOM events on the container and document. This method should contain\r\n\t * a one-to-one assignment between methods and their handlers. Any advanced logic should\r\n\t * be moved to the handler reflecting the event's name.\r\n\t */\r\n\tsetDOMEvents: function () {\r\n\r\n\t\tvar pointer = this,\r\n\t\t\tcontainer = pointer.chart.container,\r\n\t\t\tevents;\r\n\r\n\t\tthis._events = events = [\r\n\t\t\t[container, 'onmousedown', 'onContainerMouseDown'],\r\n\t\t\t[container, 'onmousemove', 'onContainerMouseMove'],\r\n\t\t\t[container, 'onclick', 'onContainerClick'],\r\n\t\t\t[container, 'mouseleave', 'onContainerMouseLeave'],\r\n\t\t\t[doc, 'mousemove', 'onDocumentMouseMove'],\r\n\t\t\t[doc, 'mouseup', 'onDocumentMouseUp']\r\n\t\t];\r\n\r\n\t\tif (hasTouch) {\r\n\t\t\tevents.push(\r\n\t\t\t\t[container, 'ontouchstart', 'onContainerTouchStart'],\r\n\t\t\t\t[container, 'ontouchmove', 'onContainerTouchMove'],\r\n\t\t\t\t[doc, 'touchend', 'onDocumentTouchEnd']\r\n\t\t\t);\r\n\t\t}\r\n\r\n\t\teach(events, function (eventConfig) {\r\n\r\n\t\t\t// First, create the callback function that in turn calls the method on Pointer\r\n\t\t\tpointer['_' + eventConfig[2]] = function (e) {\r\n\t\t\t\tpointer[eventConfig[2]](e);\r\n\t\t\t};\r\n\r\n\t\t\t// Now attach the function, either as a direct property or through addEvent\r\n\t\t\tif (eventConfig[1].indexOf('on') === 0) {\r\n\t\t\t\teventConfig[0][eventConfig[1]] = pointer['_' + eventConfig[2]];\r\n\t\t\t} else {\r\n\t\t\t\taddEvent(eventConfig[0], eventConfig[1], pointer['_' + eventConfig[2]]);\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\t\r\n\t},\r\n\r\n\t/**\r\n\t * Destroys the Pointer object and disconnects DOM events.\r\n\t */\r\n\tdestroy: function () {\r\n\t\tvar pointer = this;\r\n\r\n\t\t// Release all DOM events\r\n\t\teach(pointer._events, function (eventConfig) {\t\r\n\t\t\tif (eventConfig[1].indexOf('on') === 0) {\r\n\t\t\t\teventConfig[0][eventConfig[1]] = null; // delete breaks oldIE\r\n\t\t\t} else {\t\t\r\n\t\t\t\tremoveEvent(eventConfig[0], eventConfig[1], pointer['_' + eventConfig[2]]);\r\n\t\t\t}\r\n\t\t});\r\n\t\tdelete pointer._events;\r\n\r\n\t\t// memory and CPU leak\r\n\t\tclearInterval(pointer.tooltipTimeout);\r\n\t}\r\n};\r\n/**\r\n * The overview of the chart's series\r\n */\r\nfunction Legend(chart, options) {\r\n\tthis.init(chart, options);\r\n}\r\n\r\nLegend.prototype = {\r\n\t\r\n\t/**\r\n\t * Initialize the legend\r\n\t */\r\n\tinit: function (chart, options) {\r\n\t\t\r\n\t\tvar legend = this,\r\n\t\t\titemStyle = options.itemStyle,\r\n\t\t\tpadding = pick(options.padding, 8),\r\n\t\t\titemMarginTop = options.itemMarginTop || 0;\r\n\t\r\n\t\tthis.options = options;\r\n\r\n\t\tif (!options.enabled) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\r\n\t\tlegend.baseline = pInt(itemStyle.fontSize) + 3 + itemMarginTop; // used in Series prototype\r\n\t\tlegend.itemStyle = itemStyle;\r\n\t\tlegend.itemHiddenStyle = merge(itemStyle, options.itemHiddenStyle);\r\n\t\tlegend.itemMarginTop = itemMarginTop;\r\n\t\tlegend.padding = padding;\r\n\t\tlegend.initialItemX = padding;\r\n\t\tlegend.initialItemY = padding - 5; // 5 is the number of pixels above the text\r\n\t\tlegend.maxItemWidth = 0;\r\n\t\tlegend.chart = chart;\r\n\t\tlegend.itemHeight = 0;\r\n\t\tlegend.lastLineHeight = 0;\r\n\r\n\t\t// Render it\r\n\t\tlegend.render();\r\n\r\n\t\t// move checkboxes\r\n\t\taddEvent(legend.chart, 'endResize', function () { \r\n\t\t\tlegend.positionCheckboxes();\r\n\t\t});\r\n\r\n\t},\r\n\r\n\t/**\r\n\t * Set the colors for the legend item\r\n\t * @param {Object} item A Series or Point instance\r\n\t * @param {Object} visible Dimmed or colored\r\n\t */\r\n\tcolorizeItem: function (item, visible) {\r\n\t\tvar legend = this,\r\n\t\t\toptions = legend.options,\r\n\t\t\tlegendItem = item.legendItem,\r\n\t\t\tlegendLine = item.legendLine,\r\n\t\t\tlegendSymbol = item.legendSymbol,\r\n\t\t\thiddenColor = legend.itemHiddenStyle.color,\r\n\t\t\ttextColor = visible ? options.itemStyle.color : hiddenColor,\r\n\t\t\tsymbolColor = visible ? item.color : hiddenColor,\r\n\t\t\tmarkerOptions = item.options && item.options.marker,\r\n\t\t\tsymbolAttr = {\r\n\t\t\t\tstroke: symbolColor,\r\n\t\t\t\tfill: symbolColor\r\n\t\t\t},\r\n\t\t\tkey,\r\n\t\t\tval;\r\n\t\t\r\n\t\tif (legendItem) {\r\n\t\t\tlegendItem.css({ fill: textColor, color: textColor }); // color for #1553, oldIE\r\n\t\t}\r\n\t\tif (legendLine) {\r\n\t\t\tlegendLine.attr({ stroke: symbolColor });\r\n\t\t}\r\n\t\t\r\n\t\tif (legendSymbol) {\r\n\t\t\t\r\n\t\t\t// Apply marker options\r\n\t\t\tif (markerOptions && legendSymbol.isMarker) { // #585\r\n\t\t\t\tmarkerOptions = item.convertAttribs(markerOptions);\r\n\t\t\t\tfor (key in markerOptions) {\r\n\t\t\t\t\tval = markerOptions[key];\r\n\t\t\t\t\tif (val !== UNDEFINED) {\r\n\t\t\t\t\t\tsymbolAttr[key] = val;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tlegendSymbol.attr(symbolAttr);\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Position the legend item\r\n\t * @param {Object} item A Series or Point instance\r\n\t */\r\n\tpositionItem: function (item) {\r\n\t\tvar legend = this,\r\n\t\t\toptions = legend.options,\r\n\t\t\tsymbolPadding = options.symbolPadding,\r\n\t\t\tltr = !options.rtl,\r\n\t\t\tlegendItemPos = item._legendItemPos,\r\n\t\t\titemX = legendItemPos[0],\r\n\t\t\titemY = legendItemPos[1],\r\n\t\t\tcheckbox = item.checkbox;\r\n\r\n\t\tif (item.legendGroup) {\r\n\t\t\titem.legendGroup.translate(\r\n\t\t\t\tltr ? itemX : legend.legendWidth - itemX - 2 * symbolPadding - 4,\r\n\t\t\t\titemY\r\n\t\t\t);\r\n\t\t}\r\n\r\n\t\tif (checkbox) {\r\n\t\t\tcheckbox.x = itemX;\r\n\t\t\tcheckbox.y = itemY;\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Destroy a single legend item\r\n\t * @param {Object} item The series or point\r\n\t */\r\n\tdestroyItem: function (item) {\r\n\t\tvar checkbox = item.checkbox;\r\n\r\n\t\t// destroy SVG elements\r\n\t\teach(['legendItem', 'legendLine', 'legendSymbol', 'legendGroup'], function (key) {\r\n\t\t\tif (item[key]) {\r\n\t\t\t\titem[key] = item[key].destroy();\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\tif (checkbox) {\r\n\t\t\tdiscardElement(item.checkbox);\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Destroys the legend.\r\n\t */\r\n\tdestroy: function () {\r\n\t\tvar legend = this,\r\n\t\t\tlegendGroup = legend.group,\r\n\t\t\tbox = legend.box;\r\n\r\n\t\tif (box) {\r\n\t\t\tlegend.box = box.destroy();\r\n\t\t}\r\n\r\n\t\tif (legendGroup) {\r\n\t\t\tlegend.group = legendGroup.destroy();\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Position the checkboxes after the width is determined\r\n\t */\r\n\tpositionCheckboxes: function (scrollOffset) {\r\n\t\tvar alignAttr = this.group.alignAttr,\r\n\t\t\ttranslateY,\r\n\t\t\tclipHeight = this.clipHeight || this.legendHeight;\r\n\r\n\t\tif (alignAttr) {\r\n\t\t\ttranslateY = alignAttr.translateY;\r\n\t\t\teach(this.allItems, function (item) {\r\n\t\t\t\tvar checkbox = item.checkbox,\r\n\t\t\t\t\ttop;\r\n\t\t\t\t\r\n\t\t\t\tif (checkbox) {\r\n\t\t\t\t\ttop = (translateY + checkbox.y + (scrollOffset || 0) + 3);\r\n\t\t\t\t\tcss(checkbox, {\r\n\t\t\t\t\t\tleft: (alignAttr.translateX + item.legendItemWidth + checkbox.x - 20) + PX,\r\n\t\t\t\t\t\ttop: top + PX,\r\n\t\t\t\t\t\tdisplay: top > translateY - 6 && top < translateY + clipHeight - 6 ? '' : NONE\r\n\t\t\t\t\t});\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Render the legend title on top of the legend\r\n\t */\r\n\trenderTitle: function () {\r\n\t\tvar options = this.options,\r\n\t\t\tpadding = this.padding,\r\n\t\t\ttitleOptions = options.title,\r\n\t\t\ttitleHeight = 0,\r\n\t\t\tbBox;\r\n\t\t\r\n\t\tif (titleOptions.text) {\r\n\t\t\tif (!this.title) {\r\n\t\t\t\tthis.title = this.chart.renderer.label(titleOptions.text, padding - 3, padding - 4, null, null, null, null, null, 'legend-title')\r\n\t\t\t\t\t.attr({ zIndex: 1 })\r\n\t\t\t\t\t.css(titleOptions.style)\r\n\t\t\t\t\t.add(this.group);\r\n\t\t\t}\r\n\t\t\tbBox = this.title.getBBox();\r\n\t\t\ttitleHeight = bBox.height;\r\n\t\t\tthis.offsetWidth = bBox.width; // #1717\r\n\t\t\tthis.contentGroup.attr({ translateY: titleHeight });\r\n\t\t}\r\n\t\tthis.titleHeight = titleHeight;\r\n\t},\r\n\r\n\t/**\r\n\t * Render a single specific legend item\r\n\t * @param {Object} item A series or point\r\n\t */\r\n\trenderItem: function (item) {\r\n\t\tvar legend = this,\r\n\t\t\tchart = legend.chart,\r\n\t\t\trenderer = chart.renderer,\r\n\t\t\toptions = legend.options,\r\n\t\t\thorizontal = options.layout === 'horizontal',\r\n\t\t\tsymbolWidth = options.symbolWidth,\r\n\t\t\tsymbolPadding = options.symbolPadding,\r\n\t\t\titemStyle = legend.itemStyle,\r\n\t\t\titemHiddenStyle = legend.itemHiddenStyle,\r\n\t\t\tpadding = legend.padding,\r\n\t\t\titemDistance = horizontal ? pick(options.itemDistance, 8) : 0,\r\n\t\t\tltr = !options.rtl,\r\n\t\t\titemHeight,\r\n\t\t\twidthOption = options.width,\r\n\t\t\titemMarginBottom = options.itemMarginBottom || 0,\r\n\t\t\titemMarginTop = legend.itemMarginTop,\r\n\t\t\tinitialItemX = legend.initialItemX,\r\n\t\t\tbBox,\r\n\t\t\titemWidth,\r\n\t\t\tli = item.legendItem,\r\n\t\t\tseries = item.series || item,\r\n\t\t\titemOptions = series.options,\r\n\t\t\tshowCheckbox = itemOptions.showCheckbox,\r\n\t\t\tuseHTML = options.useHTML;\r\n\r\n\t\tif (!li) { // generate it once, later move it\r\n\r\n\t\t\t// Generate the group box\r\n\t\t\t// A group to hold the symbol and text. Text is to be appended in Legend class.\r\n\t\t\titem.legendGroup = renderer.g('legend-item')\r\n\t\t\t\t.attr({ zIndex: 1 })\r\n\t\t\t\t.add(legend.scrollGroup);\r\n\r\n\t\t\t// Draw the legend symbol inside the group box\r\n\t\t\tseries.drawLegendSymbol(legend, item);\r\n\r\n\t\t\t// Generate the list item text and add it to the group\r\n\t\t\titem.legendItem = li = renderer.text(\r\n\t\t\t\t\toptions.labelFormat ? format(options.labelFormat, item) : options.labelFormatter.call(item),\r\n\t\t\t\t\tltr ? symbolWidth + symbolPadding : -symbolPadding,\r\n\t\t\t\t\tlegend.baseline,\r\n\t\t\t\t\tuseHTML\r\n\t\t\t\t)\r\n\t\t\t\t.css(merge(item.visible ? itemStyle : itemHiddenStyle)) // merge to prevent modifying original (#1021)\r\n\t\t\t\t.attr({\r\n\t\t\t\t\talign: ltr ? 'left' : 'right',\r\n\t\t\t\t\tzIndex: 2\r\n\t\t\t\t})\r\n\t\t\t\t.add(item.legendGroup);\r\n\r\n\t\t\t// Set the events on the item group, or in case of useHTML, the item itself (#1249)\r\n\t\t\t(useHTML ? li : item.legendGroup).on('mouseover', function () {\r\n\t\t\t\t\titem.setState(HOVER_STATE);\r\n\t\t\t\t\tli.css(legend.options.itemHoverStyle);\r\n\t\t\t\t})\r\n\t\t\t\t.on('mouseout', function () {\r\n\t\t\t\t\tli.css(item.visible ? itemStyle : itemHiddenStyle);\r\n\t\t\t\t\titem.setState();\r\n\t\t\t\t})\r\n\t\t\t\t.on('click', function (event) {\r\n\t\t\t\t\tvar strLegendItemClick = 'legendItemClick',\r\n\t\t\t\t\t\tfnLegendItemClick = function () {\r\n\t\t\t\t\t\t\titem.setVisible();\r\n\t\t\t\t\t\t};\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t// Pass over the click/touch event. #4.\r\n\t\t\t\t\tevent = {\r\n\t\t\t\t\t\tbrowserEvent: event\r\n\t\t\t\t\t};\r\n\r\n\t\t\t\t\t// click the name or symbol\r\n\t\t\t\t\tif (item.firePointEvent) { // point\r\n\t\t\t\t\t\titem.firePointEvent(strLegendItemClick, event, fnLegendItemClick);\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tfireEvent(item, strLegendItemClick, event, fnLegendItemClick);\r\n\t\t\t\t\t}\r\n\t\t\t\t});\r\n\r\n\t\t\t// Colorize the items\r\n\t\t\tlegend.colorizeItem(item, item.visible);\r\n\r\n\t\t\t// add the HTML checkbox on top\r\n\t\t\tif (itemOptions && showCheckbox) {\r\n\t\t\t\titem.checkbox = createElement('input', {\r\n\t\t\t\t\ttype: 'checkbox',\r\n\t\t\t\t\tchecked: item.selected,\r\n\t\t\t\t\tdefaultChecked: item.selected // required by IE7\r\n\t\t\t\t}, options.itemCheckboxStyle, chart.container);\r\n\r\n\t\t\t\taddEvent(item.checkbox, 'click', function (event) {\r\n\t\t\t\t\tvar target = event.target;\r\n\t\t\t\t\tfireEvent(item, 'checkboxClick', {\r\n\t\t\t\t\t\t\tchecked: target.checked\r\n\t\t\t\t\t\t},\r\n\t\t\t\t\t\tfunction () {\r\n\t\t\t\t\t\t\titem.select();\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t);\r\n\t\t\t\t});\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// calculate the positions for the next line\r\n\t\tbBox = li.getBBox();\r\n\r\n\t\titemWidth = item.legendItemWidth =\r\n\t\t\toptions.itemWidth || symbolWidth + symbolPadding + bBox.width + itemDistance +\r\n\t\t\t(showCheckbox ? 20 : 0);\r\n\t\tlegend.itemHeight = itemHeight = bBox.height;\r\n\r\n\t\t// if the item exceeds the width, start a new line\r\n\t\tif (horizontal && legend.itemX - initialItemX + itemWidth >\r\n\t\t\t\t(widthOption || (chart.chartWidth - 2 * padding - initialItemX))) {\r\n\t\t\tlegend.itemX = initialItemX;\r\n\t\t\tlegend.itemY += itemMarginTop + legend.lastLineHeight + itemMarginBottom;\r\n\t\t\tlegend.lastLineHeight = 0; // reset for next line\r\n\t\t}\r\n\r\n\t\t// If the item exceeds the height, start a new column\r\n\t\t/*if (!horizontal && legend.itemY + options.y + itemHeight > chart.chartHeight - spacingTop - spacingBottom) {\r\n\t\t\tlegend.itemY = legend.initialItemY;\r\n\t\t\tlegend.itemX += legend.maxItemWidth;\r\n\t\t\tlegend.maxItemWidth = 0;\r\n\t\t}*/\r\n\r\n\t\t// Set the edge positions\r\n\t\tlegend.maxItemWidth = mathMax(legend.maxItemWidth, itemWidth);\r\n\t\tlegend.lastItemY = itemMarginTop + legend.itemY + itemMarginBottom;\r\n\t\tlegend.lastLineHeight = mathMax(itemHeight, legend.lastLineHeight); // #915\r\n\r\n\t\t// cache the position of the newly generated or reordered items\r\n\t\titem._legendItemPos = [legend.itemX, legend.itemY];\r\n\r\n\t\t// advance\r\n\t\tif (horizontal) {\r\n\t\t\tlegend.itemX += itemWidth;\r\n\r\n\t\t} else {\r\n\t\t\tlegend.itemY += itemMarginTop + itemHeight + itemMarginBottom;\r\n\t\t\tlegend.lastLineHeight = itemHeight;\r\n\t\t}\r\n\r\n\t\t// the width of the widest item\r\n\t\tlegend.offsetWidth = widthOption || mathMax(\r\n\t\t\t(horizontal ? legend.itemX - initialItemX - itemDistance : itemWidth) + padding,\r\n\t\t\tlegend.offsetWidth\r\n\t\t);\r\n\t},\r\n\r\n\t/**\r\n\t * Render the legend. This method can be called both before and after\r\n\t * chart.render. If called after, it will only rearrange items instead\r\n\t * of creating new ones.\r\n\t */\r\n\trender: function () {\r\n\t\tvar legend = this,\r\n\t\t\tchart = legend.chart,\r\n\t\t\trenderer = chart.renderer,\r\n\t\t\tlegendGroup = legend.group,\r\n\t\t\tallItems,\r\n\t\t\tdisplay,\r\n\t\t\tlegendWidth,\r\n\t\t\tlegendHeight,\r\n\t\t\tbox = legend.box,\r\n\t\t\toptions = legend.options,\r\n\t\t\tpadding = legend.padding,\r\n\t\t\tlegendBorderWidth = options.borderWidth,\r\n\t\t\tlegendBackgroundColor = options.backgroundColor;\r\n\r\n\t\tlegend.itemX = legend.initialItemX;\r\n\t\tlegend.itemY = legend.initialItemY;\r\n\t\tlegend.offsetWidth = 0;\r\n\t\tlegend.lastItemY = 0;\r\n\r\n\t\tif (!legendGroup) {\r\n\t\t\tlegend.group = legendGroup = renderer.g('legend')\r\n\t\t\t\t.attr({ zIndex: 7 }) \r\n\t\t\t\t.add();\r\n\t\t\tlegend.contentGroup = renderer.g()\r\n\t\t\t\t.attr({ zIndex: 1 }) // above background\r\n\t\t\t\t.add(legendGroup);\r\n\t\t\tlegend.scrollGroup = renderer.g()\r\n\t\t\t\t.add(legend.contentGroup);\r\n\t\t}\r\n\t\t\r\n\t\tlegend.renderTitle();\r\n\r\n\t\t// add each series or point\r\n\t\tallItems = [];\r\n\t\teach(chart.series, function (serie) {\r\n\t\t\tvar seriesOptions = serie.options;\r\n\r\n\t\t\tif (!seriesOptions.showInLegend || defined(seriesOptions.linkedTo)) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\t// use points or series for the legend item depending on legendType\r\n\t\t\tallItems = allItems.concat(\r\n\t\t\t\t\tserie.legendItems ||\r\n\t\t\t\t\t(seriesOptions.legendType === 'point' ?\r\n\t\t\t\t\t\t\tserie.data :\r\n\t\t\t\t\t\t\tserie)\r\n\t\t\t);\r\n\t\t});\r\n\r\n\t\t// sort by legendIndex\r\n\t\tstableSort(allItems, function (a, b) {\r\n\t\t\treturn ((a.options && a.options.legendIndex) || 0) - ((b.options && b.options.legendIndex) || 0);\r\n\t\t});\r\n\r\n\t\t// reversed legend\r\n\t\tif (options.reversed) {\r\n\t\t\tallItems.reverse();\r\n\t\t}\r\n\r\n\t\tlegend.allItems = allItems;\r\n\t\tlegend.display = display = !!allItems.length;\r\n\r\n\t\t// render the items\r\n\t\teach(allItems, function (item) {\r\n\t\t\tlegend.renderItem(item); \r\n\t\t});\r\n\r\n\t\t// Draw the border\r\n\t\tlegendWidth = options.width || legend.offsetWidth;\r\n\t\tlegendHeight = legend.lastItemY + legend.lastLineHeight + legend.titleHeight;\r\n\t\t\r\n\t\t\r\n\t\tlegendHeight = legend.handleOverflow(legendHeight);\r\n\r\n\t\tif (legendBorderWidth || legendBackgroundColor) {\r\n\t\t\tlegendWidth += padding;\r\n\t\t\tlegendHeight += padding;\r\n\r\n\t\t\tif (!box) {\r\n\t\t\t\tlegend.box = box = renderer.rect(\r\n\t\t\t\t\t0,\r\n\t\t\t\t\t0,\r\n\t\t\t\t\tlegendWidth,\r\n\t\t\t\t\tlegendHeight,\r\n\t\t\t\t\toptions.borderRadius,\r\n\t\t\t\t\tlegendBorderWidth || 0\r\n\t\t\t\t).attr({\r\n\t\t\t\t\tstroke: options.borderColor,\r\n\t\t\t\t\t'stroke-width': legendBorderWidth || 0,\r\n\t\t\t\t\tfill: legendBackgroundColor || NONE\r\n\t\t\t\t})\r\n\t\t\t\t.add(legendGroup)\r\n\t\t\t\t.shadow(options.shadow);\r\n\t\t\t\tbox.isNew = true;\r\n\r\n\t\t\t} else if (legendWidth > 0 && legendHeight > 0) {\r\n\t\t\t\tbox[box.isNew ? 'attr' : 'animate'](\r\n\t\t\t\t\tbox.crisp(null, null, null, legendWidth, legendHeight)\r\n\t\t\t\t);\r\n\t\t\t\tbox.isNew = false;\r\n\t\t\t}\r\n\r\n\t\t\t// hide the border if no items\r\n\t\t\tbox[display ? 'show' : 'hide']();\r\n\t\t}\r\n\t\t\r\n\t\tlegend.legendWidth = legendWidth;\r\n\t\tlegend.legendHeight = legendHeight;\r\n\r\n\t\t// Now that the legend width and height are established, put the items in the \r\n\t\t// final position\r\n\t\teach(allItems, function (item) {\r\n\t\t\tlegend.positionItem(item);\r\n\t\t});\r\n\r\n\t\t// 1.x compatibility: positioning based on style\r\n\t\t/*var props = ['left', 'right', 'top', 'bottom'],\r\n\t\t\tprop,\r\n\t\t\ti = 4;\r\n\t\twhile (i--) {\r\n\t\t\tprop = props[i];\r\n\t\t\tif (options.style[prop] && options.style[prop] !== 'auto') {\r\n\t\t\t\toptions[i < 2 ? 'align' : 'verticalAlign'] = prop;\r\n\t\t\t\toptions[i < 2 ? 'x' : 'y'] = pInt(options.style[prop]) * (i % 2 ? -1 : 1);\r\n\t\t\t}\r\n\t\t}*/\r\n\r\n\t\tif (display) {\r\n\t\t\tlegendGroup.align(extend({\r\n\t\t\t\twidth: legendWidth,\r\n\t\t\t\theight: legendHeight\r\n\t\t\t}, options), true, 'spacingBox');\r\n\t\t}\r\n\r\n\t\tif (!chart.isResizing) {\r\n\t\t\tthis.positionCheckboxes();\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Set up the overflow handling by adding navigation with up and down arrows below the\r\n\t * legend.\r\n\t */\r\n\thandleOverflow: function (legendHeight) {\r\n\t\tvar legend = this,\r\n\t\t\tchart = this.chart,\r\n\t\t\trenderer = chart.renderer,\r\n\t\t\tpageCount,\r\n\t\t\toptions = this.options,\r\n\t\t\toptionsY = options.y,\r\n\t\t\talignTop = options.verticalAlign === 'top',\r\n\t\t\tspaceHeight = chart.spacingBox.height + (alignTop ? -optionsY : optionsY) - this.padding,\r\n\t\t\tmaxHeight = options.maxHeight,\r\n\t\t\tclipHeight,\r\n\t\t\tclipRect = this.clipRect,\r\n\t\t\tnavOptions = options.navigation,\r\n\t\t\tanimation = pick(navOptions.animation, true),\r\n\t\t\tarrowSize = navOptions.arrowSize || 12,\r\n\t\t\tnav = this.nav;\r\n\t\t\t\r\n\t\t// Adjust the height\r\n\t\tif (options.layout === 'horizontal') {\r\n\t\t\tspaceHeight /= 2;\r\n\t\t}\r\n\t\tif (maxHeight) {\r\n\t\t\tspaceHeight = mathMin(spaceHeight, maxHeight);\r\n\t\t}\r\n\t\t\r\n\t\t// Reset the legend height and adjust the clipping rectangle\r\n\t\tif (legendHeight > spaceHeight && !options.useHTML) {\r\n\r\n\t\t\tthis.clipHeight = clipHeight = spaceHeight - 20 - this.titleHeight;\r\n\t\t\tthis.pageCount = pageCount = mathCeil(legendHeight / clipHeight);\r\n\t\t\tthis.currentPage = pick(this.currentPage, 1);\r\n\t\t\tthis.fullHeight = legendHeight;\r\n\t\t\t\r\n\t\t\t// Only apply clipping if needed. Clipping causes blurred legend in PDF export (#1787)\r\n\t\t\tif (!clipRect) {\r\n\t\t\t\tclipRect = legend.clipRect = renderer.clipRect(0, 0, 9999, 0);\r\n\t\t\t\tlegend.contentGroup.clip(clipRect);\r\n\t\t\t}\r\n\t\t\tclipRect.attr({\r\n\t\t\t\theight: clipHeight\r\n\t\t\t});\r\n\t\t\t\r\n\t\t\t// Add navigation elements\r\n\t\t\tif (!nav) {\r\n\t\t\t\tthis.nav = nav = renderer.g().attr({ zIndex: 1 }).add(this.group);\r\n\t\t\t\tthis.up = renderer.symbol('triangle', 0, 0, arrowSize, arrowSize)\r\n\t\t\t\t\t.on('click', function () {\r\n\t\t\t\t\t\tlegend.scroll(-1, animation);\r\n\t\t\t\t\t})\r\n\t\t\t\t\t.add(nav);\r\n\t\t\t\tthis.pager = renderer.text('', 15, 10)\r\n\t\t\t\t\t.css(navOptions.style)\r\n\t\t\t\t\t.add(nav);\r\n\t\t\t\tthis.down = renderer.symbol('triangle-down', 0, 0, arrowSize, arrowSize)\r\n\t\t\t\t\t.on('click', function () {\r\n\t\t\t\t\t\tlegend.scroll(1, animation);\r\n\t\t\t\t\t})\r\n\t\t\t\t\t.add(nav);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// Set initial position\r\n\t\t\tlegend.scroll(0);\r\n\t\t\t\r\n\t\t\tlegendHeight = spaceHeight;\r\n\t\t\t\r\n\t\t} else if (nav) {\r\n\t\t\tclipRect.attr({\r\n\t\t\t\theight: chart.chartHeight\r\n\t\t\t});\r\n\t\t\tnav.hide();\r\n\t\t\tthis.scrollGroup.attr({\r\n\t\t\t\ttranslateY: 1\r\n\t\t\t});\r\n\t\t\tthis.clipHeight = 0; // #1379\r\n\t\t}\r\n\t\t\r\n\t\treturn legendHeight;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Scroll the legend by a number of pages\r\n\t * @param {Object} scrollBy\r\n\t * @param {Object} animation\r\n\t */\r\n\tscroll: function (scrollBy, animation) {\r\n\t\tvar pageCount = this.pageCount,\r\n\t\t\tcurrentPage = this.currentPage + scrollBy,\r\n\t\t\tclipHeight = this.clipHeight,\r\n\t\t\tnavOptions = this.options.navigation,\r\n\t\t\tactiveColor = navOptions.activeColor,\r\n\t\t\tinactiveColor = navOptions.inactiveColor,\r\n\t\t\tpager = this.pager,\r\n\t\t\tpadding = this.padding,\r\n\t\t\tscrollOffset;\r\n\t\t\r\n\t\t// When resizing while looking at the last page\r\n\t\tif (currentPage > pageCount) {\r\n\t\t\tcurrentPage = pageCount;\r\n\t\t}\r\n\t\t\r\n\t\tif (currentPage > 0) {\r\n\t\t\t\r\n\t\t\tif (animation !== UNDEFINED) {\r\n\t\t\t\tsetAnimation(animation, this.chart);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.nav.attr({\r\n\t\t\t\ttranslateX: padding,\r\n\t\t\t\ttranslateY: clipHeight + 7 + this.titleHeight,\r\n\t\t\t\tvisibility: VISIBLE\r\n\t\t\t});\r\n\t\t\tthis.up.attr({\r\n\t\t\t\t\tfill: currentPage === 1 ? inactiveColor : activeColor\r\n\t\t\t\t})\r\n\t\t\t\t.css({\r\n\t\t\t\t\tcursor: currentPage === 1 ? 'default' : 'pointer'\r\n\t\t\t\t});\r\n\t\t\tpager.attr({\r\n\t\t\t\ttext: currentPage + '/' + this.pageCount\r\n\t\t\t});\r\n\t\t\tthis.down.attr({\r\n\t\t\t\t\tx: 18 + this.pager.getBBox().width, // adjust to text width\r\n\t\t\t\t\tfill: currentPage === pageCount ? inactiveColor : activeColor\r\n\t\t\t\t})\r\n\t\t\t\t.css({\r\n\t\t\t\t\tcursor: currentPage === pageCount ? 'default' : 'pointer'\r\n\t\t\t\t});\r\n\t\t\t\r\n\t\t\tscrollOffset = -mathMin(clipHeight * (currentPage - 1), this.fullHeight - clipHeight + padding) + 1;\r\n\t\t\tthis.scrollGroup.animate({\r\n\t\t\t\ttranslateY: scrollOffset\r\n\t\t\t});\r\n\t\t\tpager.attr({\r\n\t\t\t\ttext: currentPage + '/' + pageCount\r\n\t\t\t});\r\n\t\t\t\r\n\t\t\t\r\n\t\t\tthis.currentPage = currentPage;\r\n\t\t\tthis.positionCheckboxes(scrollOffset);\r\n\t\t}\r\n\t\t\t\r\n\t}\r\n\t\r\n};\r\n\r\n// Workaround for #2030, horizontal legend items not displaying in IE11 Preview.\r\n// TODO: When IE11 is released, check again for this bug, and remove the fix\r\n// or make a better one.\r\nif (/Trident.*?11\\.0/.test(userAgent)) {\r\n\twrap(Legend.prototype, 'positionItem', function (proceed, item) {\r\n\t\tvar legend = this;\r\n\t\tsetTimeout(function () {\r\n\t\t\tproceed.call(legend, item);\r\n\t\t});\r\n\t});\r\n}\r\n\r\n/**\r\n * The chart class\r\n * @param {Object} options\r\n * @param {Function} callback Function to run when the chart has loaded\r\n */\r\nfunction Chart() {\r\n\tthis.init.apply(this, arguments);\r\n}\r\n\r\nChart.prototype = {\r\n\r\n\t/**\r\n\t * Initialize the chart\r\n\t */\r\n\tinit: function (userOptions, callback) {\r\n\r\n\t\t// Handle regular options\r\n\t\tvar options,\r\n\t\t\tseriesOptions = userOptions.series; // skip merging data points to increase performance\r\n\r\n\t\tuserOptions.series = null;\r\n\t\toptions = merge(defaultOptions, userOptions); // do the merge\r\n\t\toptions.series = userOptions.series = seriesOptions; // set back the series data\r\n\r\n\t\tvar optionsChart = options.chart;\r\n\t\t\r\n\t\t// Create margin & spacing array\r\n\t\tthis.margin = this.splashArray('margin', optionsChart);\r\n\t\tthis.spacing = this.splashArray('spacing', optionsChart);\r\n\r\n\t\tvar chartEvents = optionsChart.events;\r\n\r\n\t\t//this.runChartClick = chartEvents && !!chartEvents.click;\r\n\t\tthis.bounds = { h: {}, v: {} }; // Pixel data bounds for touch zoom\r\n\r\n\t\tthis.callback = callback;\r\n\t\tthis.isResizing = 0;\r\n\t\tthis.options = options;\r\n\t\t//chartTitleOptions = UNDEFINED;\r\n\t\t//chartSubtitleOptions = UNDEFINED;\r\n\r\n\t\tthis.axes = [];\r\n\t\tthis.series = [];\r\n\t\tthis.hasCartesianSeries = optionsChart.showAxes;\r\n\t\t//this.axisOffset = UNDEFINED;\r\n\t\t//this.maxTicks = UNDEFINED; // handle the greatest amount of ticks on grouped axes\r\n\t\t//this.inverted = UNDEFINED;\r\n\t\t//this.loadingShown = UNDEFINED;\r\n\t\t//this.container = UNDEFINED;\r\n\t\t//this.chartWidth = UNDEFINED;\r\n\t\t//this.chartHeight = UNDEFINED;\r\n\t\t//this.marginRight = UNDEFINED;\r\n\t\t//this.marginBottom = UNDEFINED;\r\n\t\t//this.containerWidth = UNDEFINED;\r\n\t\t//this.containerHeight = UNDEFINED;\r\n\t\t//this.oldChartWidth = UNDEFINED;\r\n\t\t//this.oldChartHeight = UNDEFINED;\r\n\r\n\t\t//this.renderTo = UNDEFINED;\r\n\t\t//this.renderToClone = UNDEFINED;\r\n\r\n\t\t//this.spacingBox = UNDEFINED\r\n\r\n\t\t//this.legend = UNDEFINED;\r\n\r\n\t\t// Elements\r\n\t\t//this.chartBackground = UNDEFINED;\r\n\t\t//this.plotBackground = UNDEFINED;\r\n\t\t//this.plotBGImage = UNDEFINED;\r\n\t\t//this.plotBorder = UNDEFINED;\r\n\t\t//this.loadingDiv = UNDEFINED;\r\n\t\t//this.loadingSpan = UNDEFINED;\r\n\r\n\t\tvar chart = this,\r\n\t\t\teventType;\r\n\r\n\t\t// Add the chart to the global lookup\r\n\t\tchart.index = charts.length;\r\n\t\tcharts.push(chart);\r\n\r\n\t\t// Set up auto resize\r\n\t\tif (optionsChart.reflow !== false) {\r\n\t\t\taddEvent(chart, 'load', function () {\r\n\t\t\t\tchart.initReflow();\r\n\t\t\t});\r\n\t\t}\r\n\r\n\t\t// Chart event handlers\r\n\t\tif (chartEvents) {\r\n\t\t\tfor (eventType in chartEvents) {\r\n\t\t\t\taddEvent(chart, eventType, chartEvents[eventType]);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tchart.xAxis = [];\r\n\t\tchart.yAxis = [];\r\n\r\n\t\t// Expose methods and variables\r\n\t\tchart.animation = useCanVG ? false : pick(optionsChart.animation, true);\r\n\t\tchart.pointCount = 0;\r\n\t\tchart.counters = new ChartCounters();\r\n\r\n\t\tchart.firstRender();\r\n\t},\r\n\r\n\t/**\r\n\t * Initialize an individual series, called internally before render time\r\n\t */\r\n\tinitSeries: function (options) {\r\n\t\tvar chart = this,\r\n\t\t\toptionsChart = chart.options.chart,\r\n\t\t\ttype = options.type || optionsChart.type || optionsChart.defaultSeriesType,\r\n\t\t\tseries,\r\n\t\t\tconstr = seriesTypes[type];\r\n\r\n\t\t// No such series type\r\n\t\tif (!constr) {\r\n\t\t\terror(17, true);\r\n\t\t}\r\n\r\n\t\tseries = new constr();\r\n\t\tseries.init(this, options);\r\n\t\treturn series;\r\n\t},\r\n\r\n\t/**\r\n\t * Add a series dynamically after  time\r\n\t *\r\n\t * @param {Object} options The config options\r\n\t * @param {Boolean} redraw Whether to redraw the chart after adding. Defaults to true.\r\n\t * @param {Boolean|Object} animation Whether to apply animation, and optionally animation\r\n\t *    configuration\r\n\t *\r\n\t * @return {Object} series The newly created series object\r\n\t */\r\n\taddSeries: function (options, redraw, animation) {\r\n\t\tvar series,\r\n\t\t\tchart = this;\r\n\r\n\t\tif (options) {\r\n\t\t\tredraw = pick(redraw, true); // defaults to true\r\n\r\n\t\t\tfireEvent(chart, 'addSeries', { options: options }, function () {\r\n\t\t\t\tseries = chart.initSeries(options);\r\n\t\t\t\t\r\n\t\t\t\tchart.isDirtyLegend = true; // the series array is out of sync with the display\r\n\t\t\t\tchart.linkSeries();\r\n\t\t\t\tif (redraw) {\r\n\t\t\t\t\tchart.redraw(animation);\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t}\r\n\r\n\t\treturn series;\r\n\t},\r\n\r\n\t/**\r\n     * Add an axis to the chart\r\n     * @param {Object} options The axis option\r\n     * @param {Boolean} isX Whether it is an X axis or a value axis\r\n     */\r\n\taddAxis: function (options, isX, redraw, animation) {\r\n\t\tvar key = isX ? 'xAxis' : 'yAxis',\r\n\t\t\tchartOptions = this.options,\r\n\t\t\taxis;\r\n\r\n\t\t/*jslint unused: false*/\r\n\t\taxis = new Axis(this, merge(options, {\r\n\t\t\tindex: this[key].length,\r\n\t\t\tisX: isX\r\n\t\t}));\r\n\t\t/*jslint unused: true*/\r\n\r\n\t\t// Push the new axis options to the chart options\r\n\t\tchartOptions[key] = splat(chartOptions[key] || {});\r\n\t\tchartOptions[key].push(options);\r\n\r\n\t\tif (pick(redraw, true)) {\r\n\t\t\tthis.redraw(animation);\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Check whether a given point is within the plot area\r\n\t *\r\n\t * @param {Number} plotX Pixel x relative to the plot area\r\n\t * @param {Number} plotY Pixel y relative to the plot area\r\n\t * @param {Boolean} inverted Whether the chart is inverted\r\n\t */\r\n\tisInsidePlot: function (plotX, plotY, inverted) {\r\n\t\tvar x = inverted ? plotY : plotX,\r\n\t\t\ty = inverted ? plotX : plotY;\r\n\t\t\t\r\n\t\treturn x >= 0 &&\r\n\t\t\tx <= this.plotWidth &&\r\n\t\t\ty >= 0 &&\r\n\t\t\ty <= this.plotHeight;\r\n\t},\r\n\r\n\t/**\r\n\t * Adjust all axes tick amounts\r\n\t */\r\n\tadjustTickAmounts: function () {\r\n\t\tif (this.options.chart.alignTicks !== false) {\r\n\t\t\teach(this.axes, function (axis) {\r\n\t\t\t\taxis.adjustTickAmount();\r\n\t\t\t});\r\n\t\t}\r\n\t\tthis.maxTicks = null;\r\n\t},\r\n\r\n\t/**\r\n\t * Redraw legend, axes or series based on updated data\r\n\t *\r\n\t * @param {Boolean|Object} animation Whether to apply animation, and optionally animation\r\n\t *    configuration\r\n\t */\r\n\tredraw: function (animation) {\r\n\t\tvar chart = this,\r\n\t\t\taxes = chart.axes,\r\n\t\t\tseries = chart.series,\r\n\t\t\tpointer = chart.pointer,\r\n\t\t\tlegend = chart.legend,\r\n\t\t\tredrawLegend = chart.isDirtyLegend,\r\n\t\t\thasStackedSeries,\r\n\t\t\thasDirtyStacks,\r\n\t\t\tisDirtyBox = chart.isDirtyBox, // todo: check if it has actually changed?\r\n\t\t\tseriesLength = series.length,\r\n\t\t\ti = seriesLength,\r\n\t\t\tserie,\r\n\t\t\trenderer = chart.renderer,\r\n\t\t\tisHiddenChart = renderer.isHidden(),\r\n\t\t\tafterRedraw = [];\r\n\t\t\t\r\n\t\tsetAnimation(animation, chart);\r\n\t\t\r\n\t\tif (isHiddenChart) {\r\n\t\t\tchart.cloneRenderTo();\r\n\t\t}\r\n\r\n\t\t// Adjust title layout (reflow multiline text)\r\n\t\tchart.layOutTitles();\r\n\r\n\t\t// link stacked series\r\n\t\twhile (i--) {\r\n\t\t\tserie = series[i];\r\n\r\n\t\t\tif (serie.options.stacking) {\r\n\t\t\t\thasStackedSeries = true;\r\n\t\t\t\t\r\n\t\t\t\tif (serie.isDirty) {\r\n\t\t\t\t\thasDirtyStacks = true;\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (hasDirtyStacks) { // mark others as dirty\r\n\t\t\ti = seriesLength;\r\n\t\t\twhile (i--) {\r\n\t\t\t\tserie = series[i];\r\n\t\t\t\tif (serie.options.stacking) {\r\n\t\t\t\t\tserie.isDirty = true;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// handle updated data in the series\r\n\t\teach(series, function (serie) {\r\n\t\t\tif (serie.isDirty) { // prepare the data so axis can read it\r\n\t\t\t\tif (serie.options.legendType === 'point') {\r\n\t\t\t\t\tredrawLegend = true;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\t// handle added or removed series\r\n\t\tif (redrawLegend && legend.options.enabled) { // series or pie points are added or removed\r\n\t\t\t// draw legend graphics\r\n\t\t\tlegend.render();\r\n\r\n\t\t\tchart.isDirtyLegend = false;\r\n\t\t}\r\n\r\n\t\t// reset stacks\r\n\t\tif (hasStackedSeries) {\r\n\t\t\tchart.getStacks();\r\n\t\t}\r\n\r\n\r\n\t\tif (chart.hasCartesianSeries) {\r\n\t\t\tif (!chart.isResizing) {\r\n\r\n\t\t\t\t// reset maxTicks\r\n\t\t\t\tchart.maxTicks = null;\r\n\r\n\t\t\t\t// set axes scales\r\n\t\t\t\teach(axes, function (axis) {\r\n\t\t\t\t\taxis.setScale();\r\n\t\t\t\t});\r\n\t\t\t}\r\n\r\n\t\t\tchart.adjustTickAmounts();\r\n\t\t\tchart.getMargins();\r\n\r\n\t\t\t// If one axis is dirty, all axes must be redrawn (#792, #2169)\r\n\t\t\teach(axes, function (axis) {\r\n\t\t\t\tif (axis.isDirty) {\r\n\t\t\t\t\tisDirtyBox = true;\r\n\t\t\t\t}\r\n\t\t\t});\r\n\r\n\t\t\t// redraw axes\r\n\t\t\teach(axes, function (axis) {\r\n\t\t\t\t\r\n\t\t\t\t// Fire 'afterSetExtremes' only if extremes are set\r\n\t\t\t\tif (axis.isDirtyExtremes) { // #821\r\n\t\t\t\t\taxis.isDirtyExtremes = false;\r\n\t\t\t\t\tafterRedraw.push(function () { // prevent a recursive call to chart.redraw() (#1119)\r\n\t\t\t\t\t\tfireEvent(axis, 'afterSetExtremes', extend(axis.eventArgs, axis.getExtremes())); // #747, #751\r\n\t\t\t\t\t\tdelete axis.eventArgs;\r\n\t\t\t\t\t});\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tif (isDirtyBox || hasStackedSeries) {\r\n\t\t\t\t\taxis.redraw();\r\n\t\t\t\t}\r\n\t\t\t});\r\n\r\n\r\n\t\t}\r\n\t\t// the plot areas size has changed\r\n\t\tif (isDirtyBox) {\r\n\t\t\tchart.drawChartBox();\r\n\t\t}\r\n\r\n\r\n\t\t// redraw affected series\r\n\t\teach(series, function (serie) {\r\n\t\t\tif (serie.isDirty && serie.visible &&\r\n\t\t\t\t\t(!serie.isCartesian || serie.xAxis)) { // issue #153\r\n\t\t\t\tserie.redraw();\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\t// move tooltip or reset\r\n\t\tif (pointer && pointer.reset) {\r\n\t\t\tpointer.reset(true);\r\n\t\t}\r\n\r\n\t\t// redraw if canvas\r\n\t\trenderer.draw();\r\n\r\n\t\t// fire the event\r\n\t\tfireEvent(chart, 'redraw'); // jQuery breaks this when calling it from addEvent. Overwrites chart.redraw\r\n\t\t\r\n\t\tif (isHiddenChart) {\r\n\t\t\tchart.cloneRenderTo(true);\r\n\t\t}\r\n\t\t\r\n\t\t// Fire callbacks that are put on hold until after the redraw\r\n\t\teach(afterRedraw, function (callback) {\r\n\t\t\tcallback.call();\r\n\t\t});\r\n\t},\r\n\r\n\r\n\r\n\t/**\r\n\t * Dim the chart and show a loading text or symbol\r\n\t * @param {String} str An optional text to show in the loading label instead of the default one\r\n\t */\r\n\tshowLoading: function (str) {\r\n\t\tvar chart = this,\r\n\t\t\toptions = chart.options,\r\n\t\t\tloadingDiv = chart.loadingDiv;\r\n\r\n\t\tvar loadingOptions = options.loading;\r\n\r\n\t\t// create the layer at the first call\r\n\t\tif (!loadingDiv) {\r\n\t\t\tchart.loadingDiv = loadingDiv = createElement(DIV, {\r\n\t\t\t\tclassName: PREFIX + 'loading'\r\n\t\t\t}, extend(loadingOptions.style, {\r\n\t\t\t\tzIndex: 10,\r\n\t\t\t\tdisplay: NONE\r\n\t\t\t}), chart.container);\r\n\r\n\t\t\tchart.loadingSpan = createElement(\r\n\t\t\t\t'span',\r\n\t\t\t\tnull,\r\n\t\t\t\tloadingOptions.labelStyle,\r\n\t\t\t\tloadingDiv\r\n\t\t\t);\r\n\r\n\t\t}\r\n\r\n\t\t// update text\r\n\t\tchart.loadingSpan.innerHTML = str || options.lang.loading;\r\n\r\n\t\t// show it\r\n\t\tif (!chart.loadingShown) {\r\n\t\t\tcss(loadingDiv, { \r\n\t\t\t\topacity: 0, \r\n\t\t\t\tdisplay: '',\r\n\t\t\t\tleft: chart.plotLeft + PX,\r\n\t\t\t\ttop: chart.plotTop + PX,\r\n\t\t\t\twidth: chart.plotWidth + PX,\r\n\t\t\t\theight: chart.plotHeight + PX\r\n\t\t\t});\r\n\t\t\tanimate(loadingDiv, {\r\n\t\t\t\topacity: loadingOptions.style.opacity\r\n\t\t\t}, {\r\n\t\t\t\tduration: loadingOptions.showDuration || 0\r\n\t\t\t});\r\n\t\t\tchart.loadingShown = true;\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Hide the loading layer\r\n\t */\r\n\thideLoading: function () {\r\n\t\tvar options = this.options,\r\n\t\t\tloadingDiv = this.loadingDiv;\r\n\r\n\t\tif (loadingDiv) {\r\n\t\t\tanimate(loadingDiv, {\r\n\t\t\t\topacity: 0\r\n\t\t\t}, {\r\n\t\t\t\tduration: options.loading.hideDuration || 100,\r\n\t\t\t\tcomplete: function () {\r\n\t\t\t\t\tcss(loadingDiv, { display: NONE });\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t}\r\n\t\tthis.loadingShown = false;\r\n\t},\r\n\r\n\t/**\r\n\t * Get an axis, series or point object by id.\r\n\t * @param id {String} The id as given in the configuration options\r\n\t */\r\n\tget: function (id) {\r\n\t\tvar chart = this,\r\n\t\t\taxes = chart.axes,\r\n\t\t\tseries = chart.series;\r\n\r\n\t\tvar i,\r\n\t\t\tj,\r\n\t\t\tpoints;\r\n\r\n\t\t// search axes\r\n\t\tfor (i = 0; i < axes.length; i++) {\r\n\t\t\tif (axes[i].options.id === id) {\r\n\t\t\t\treturn axes[i];\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// search series\r\n\t\tfor (i = 0; i < series.length; i++) {\r\n\t\t\tif (series[i].options.id === id) {\r\n\t\t\t\treturn series[i];\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// search points\r\n\t\tfor (i = 0; i < series.length; i++) {\r\n\t\t\tpoints = series[i].points || [];\r\n\t\t\tfor (j = 0; j < points.length; j++) {\r\n\t\t\t\tif (points[j].id === id) {\r\n\t\t\t\t\treturn points[j];\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn null;\r\n\t},\r\n\r\n\t/**\r\n\t * Create the Axis instances based on the config options\r\n\t */\r\n\tgetAxes: function () {\r\n\t\tvar chart = this,\r\n\t\t\toptions = this.options,\r\n\t\t\txAxisOptions = options.xAxis = splat(options.xAxis || {}),\r\n\t\t\tyAxisOptions = options.yAxis = splat(options.yAxis || {}),\r\n\t\t\toptionsArray,\r\n\t\t\taxis;\r\n\r\n\t\t// make sure the options are arrays and add some members\r\n\t\teach(xAxisOptions, function (axis, i) {\r\n\t\t\taxis.index = i;\r\n\t\t\taxis.isX = true;\r\n\t\t});\r\n\r\n\t\teach(yAxisOptions, function (axis, i) {\r\n\t\t\taxis.index = i;\r\n\t\t});\r\n\r\n\t\t// concatenate all axis options into one array\r\n\t\toptionsArray = xAxisOptions.concat(yAxisOptions);\r\n\r\n\t\teach(optionsArray, function (axisOptions) {\r\n\t\t\taxis = new Axis(chart, axisOptions);\r\n\t\t});\r\n\r\n\t\tchart.adjustTickAmounts();\r\n\t},\r\n\r\n\r\n\t/**\r\n\t * Get the currently selected points from all series\r\n\t */\r\n\tgetSelectedPoints: function () {\r\n\t\tvar points = [];\r\n\t\teach(this.series, function (serie) {\r\n\t\t\tpoints = points.concat(grep(serie.points || [], function (point) {\r\n\t\t\t\treturn point.selected;\r\n\t\t\t}));\r\n\t\t});\r\n\t\treturn points;\r\n\t},\r\n\r\n\t/**\r\n\t * Get the currently selected series\r\n\t */\r\n\tgetSelectedSeries: function () {\r\n\t\treturn grep(this.series, function (serie) {\r\n\t\t\treturn serie.selected;\r\n\t\t});\r\n\t},\r\n\r\n\t/**\r\n\t * Generate stacks for each series and calculate stacks total values\r\n\t */\r\n\tgetStacks: function () {\r\n\t\tvar chart = this;\r\n\r\n\t\t// reset stacks for each yAxis\r\n\t\teach(chart.yAxis, function (axis) {\r\n\t\t\tif (axis.stacks && axis.hasVisibleSeries) {\r\n\t\t\t\taxis.oldStacks = axis.stacks;\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\teach(chart.series, function (series) {\r\n\t\t\tif (series.options.stacking && (series.visible === true || chart.options.chart.ignoreHiddenSeries === false)) {\r\n\t\t\t\tseries.stackKey = series.type + pick(series.options.stack, '');\r\n\t\t\t}\r\n\t\t});\r\n\t},\r\n\r\n\t/**\r\n\t * Display the zoom button\r\n\t */\r\n\tshowResetZoom: function () {\r\n\t\tvar chart = this,\r\n\t\t\tlang = defaultOptions.lang,\r\n\t\t\tbtnOptions = chart.options.chart.resetZoomButton,\r\n\t\t\ttheme = btnOptions.theme,\r\n\t\t\tstates = theme.states,\r\n\t\t\talignTo = btnOptions.relativeTo === 'chart' ? null : 'plotBox';\r\n\t\t\t\r\n\t\tthis.resetZoomButton = chart.renderer.button(lang.resetZoom, null, null, function () { chart.zoomOut(); }, theme, states && states.hover)\r\n\t\t\t.attr({\r\n\t\t\t\talign: btnOptions.position.align,\r\n\t\t\t\ttitle: lang.resetZoomTitle\r\n\t\t\t})\r\n\t\t\t.add()\r\n\t\t\t.align(btnOptions.position, false, alignTo);\r\n\t\t\t\r\n\t},\r\n\r\n\t/**\r\n\t * Zoom out to 1:1\r\n\t */\r\n\tzoomOut: function () {\r\n\t\tvar chart = this;\r\n\t\tfireEvent(chart, 'selection', { resetSelection: true }, function () { \r\n\t\t\tchart.zoom();\r\n\t\t});\r\n\t},\r\n\r\n\t/**\r\n\t * Zoom into a given portion of the chart given by axis coordinates\r\n\t * @param {Object} event\r\n\t */\r\n\tzoom: function (event) {\r\n\t\tvar chart = this,\r\n\t\t\thasZoomed,\r\n\t\t\tpointer = chart.pointer,\r\n\t\t\tdisplayButton = false,\r\n\t\t\tresetZoomButton;\r\n\r\n\t\t// If zoom is called with no arguments, reset the axes\r\n\t\tif (!event || event.resetSelection) {\r\n\t\t\teach(chart.axes, function (axis) {\r\n\t\t\t\thasZoomed = axis.zoom();\r\n\t\t\t});\r\n\t\t} else { // else, zoom in on all axes\r\n\t\t\teach(event.xAxis.concat(event.yAxis), function (axisData) {\r\n\t\t\t\tvar axis = axisData.axis,\r\n\t\t\t\t\tisXAxis = axis.isXAxis;\r\n\r\n\t\t\t\t// don't zoom more than minRange\r\n\t\t\t\tif (pointer[isXAxis ? 'zoomX' : 'zoomY'] || pointer[isXAxis ? 'pinchX' : 'pinchY']) {\r\n\t\t\t\t\thasZoomed = axis.zoom(axisData.min, axisData.max);\r\n\t\t\t\t\tif (axis.displayBtn) {\r\n\t\t\t\t\t\tdisplayButton = true;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t}\r\n\t\t\r\n\t\t// Show or hide the Reset zoom button\r\n\t\tresetZoomButton = chart.resetZoomButton;\r\n\t\tif (displayButton && !resetZoomButton) {\r\n\t\t\tchart.showResetZoom();\r\n\t\t} else if (!displayButton && isObject(resetZoomButton)) {\r\n\t\t\tchart.resetZoomButton = resetZoomButton.destroy();\r\n\t\t}\r\n\t\t\r\n\r\n\t\t// Redraw\r\n\t\tif (hasZoomed) {\r\n\t\t\tchart.redraw(\r\n\t\t\t\tpick(chart.options.chart.animation, event && event.animation, chart.pointCount < 100) // animation\r\n\t\t\t);\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Pan the chart by dragging the mouse across the pane. This function is called\r\n\t * on mouse move, and the distance to pan is computed from chartX compared to\r\n\t * the first chartX position in the dragging operation.\r\n\t */\r\n\tpan: function (e, panning) {\r\n\r\n\t\tvar chart = this,\r\n\t\t\thoverPoints = chart.hoverPoints,\r\n\t\t\tdoRedraw;\r\n\r\n\t\t// remove active points for shared tooltip\r\n\t\tif (hoverPoints) {\r\n\t\t\teach(hoverPoints, function (point) {\r\n\t\t\t\tpoint.setState();\r\n\t\t\t});\r\n\t\t}\r\n\r\n\t\teach(panning === 'xy' ? [1, 0] : [1], function (isX) { // xy is used in maps\r\n\t\t\tvar mousePos = e[isX ? 'chartX' : 'chartY'],\r\n\t\t\t\taxis = chart[isX ? 'xAxis' : 'yAxis'][0],\r\n\t\t\t\tstartPos = chart[isX ? 'mouseDownX' : 'mouseDownY'],\r\n\t\t\t\thalfPointRange = (axis.pointRange || 0) / 2,\r\n\t\t\t\textremes = axis.getExtremes(),\r\n\t\t\t\tnewMin = axis.toValue(startPos - mousePos, true) + halfPointRange,\r\n\t\t\t\tnewMax = axis.toValue(startPos + chart[isX ? 'plotWidth' : 'plotHeight'] - mousePos, true) - halfPointRange;\r\n\r\n\t\t\tif (axis.series.length && newMin > mathMin(extremes.dataMin, extremes.min) && newMax < mathMax(extremes.dataMax, extremes.max)) {\r\n\t\t\t\taxis.setExtremes(newMin, newMax, false, false, { trigger: 'pan' });\r\n\t\t\t\tdoRedraw = true;\r\n\t\t\t}\r\n\r\n\t\t\tchart[isX ? 'mouseDownX' : 'mouseDownY'] = mousePos; // set new reference for next run\r\n\t\t});\r\n\r\n\t\tif (doRedraw) {\r\n\t\t\tchart.redraw(false);\r\n\t\t}\r\n\t\tcss(chart.container, { cursor: 'move' });\r\n\t},\r\n\r\n\t/**\r\n\t * Show the title and subtitle of the chart\r\n\t *\r\n\t * @param titleOptions {Object} New title options\r\n\t * @param subtitleOptions {Object} New subtitle options\r\n\t *\r\n\t */\r\n\tsetTitle: function (titleOptions, subtitleOptions) {\r\n\t\tvar chart = this,\r\n\t\t\toptions = chart.options,\r\n\t\t\tchartTitleOptions,\r\n\t\t\tchartSubtitleOptions;\r\n\r\n\t\tchartTitleOptions = options.title = merge(options.title, titleOptions);\r\n\t\tchartSubtitleOptions = options.subtitle = merge(options.subtitle, subtitleOptions);\r\n\r\n\t\t// add title and subtitle\r\n\t\teach([\r\n\t\t\t['title', titleOptions, chartTitleOptions],\r\n\t\t\t['subtitle', subtitleOptions, chartSubtitleOptions]\r\n\t\t], function (arr) {\r\n\t\t\tvar name = arr[0],\r\n\t\t\t\ttitle = chart[name],\r\n\t\t\t\ttitleOptions = arr[1],\r\n\t\t\t\tchartTitleOptions = arr[2];\r\n\r\n\t\t\tif (title && titleOptions) {\r\n\t\t\t\tchart[name] = title = title.destroy(); // remove old\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (chartTitleOptions && chartTitleOptions.text && !title) {\r\n\t\t\t\tchart[name] = chart.renderer.text(\r\n\t\t\t\t\tchartTitleOptions.text,\r\n\t\t\t\t\t0,\r\n\t\t\t\t\t0,\r\n\t\t\t\t\tchartTitleOptions.useHTML\r\n\t\t\t\t)\r\n\t\t\t\t.attr({\r\n\t\t\t\t\talign: chartTitleOptions.align,\r\n\t\t\t\t\t'class': PREFIX + name,\r\n\t\t\t\t\tzIndex: chartTitleOptions.zIndex || 4\r\n\t\t\t\t})\r\n\t\t\t\t.css(chartTitleOptions.style)\r\n\t\t\t\t.add();\r\n\t\t\t}\t\r\n\t\t});\r\n\t\tchart.layOutTitles();\r\n\t},\r\n\r\n\t/**\r\n\t * Lay out the chart titles and cache the full offset height for use in getMargins\r\n\t */\r\n\tlayOutTitles: function () {\r\n\t\tvar titleOffset = 0,\r\n\t\t\ttitle = this.title,\r\n\t\t\tsubtitle = this.subtitle,\r\n\t\t\toptions = this.options,\r\n\t\t\ttitleOptions = options.title,\r\n\t\t\tsubtitleOptions = options.subtitle,\r\n\t\t\tautoWidth = this.spacingBox.width - 44; // 44 makes room for default context button\r\n\r\n\t\tif (title) {\r\n\t\t\ttitle\r\n\t\t\t\t.css({ width: (titleOptions.width || autoWidth) + PX })\r\n\t\t\t\t.align(extend({ y: 15 }, titleOptions), false, 'spacingBox');\r\n\t\t\t\r\n\t\t\tif (!titleOptions.floating && !titleOptions.verticalAlign) {\r\n\t\t\t\ttitleOffset = title.getBBox().height;\r\n\r\n\t\t\t\t// Adjust for browser consistency + backwards compat after #776 fix\r\n\t\t\t\tif (titleOffset >= 18 && titleOffset <= 25) {\r\n\t\t\t\t\ttitleOffset = 15; \r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (subtitle) {\r\n\t\t\tsubtitle\r\n\t\t\t\t.css({ width: (subtitleOptions.width || autoWidth) + PX })\r\n\t\t\t\t.align(extend({ y: titleOffset + titleOptions.margin }, subtitleOptions), false, 'spacingBox');\r\n\t\t\t\r\n\t\t\tif (!subtitleOptions.floating && !subtitleOptions.verticalAlign) {\r\n\t\t\t\ttitleOffset = mathCeil(titleOffset + subtitle.getBBox().height);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tthis.titleOffset = titleOffset; // used in getMargins\r\n\t},\r\n\r\n\t/**\r\n\t * Get chart width and height according to options and container size\r\n\t */\r\n\tgetChartSize: function () {\r\n\t\tvar chart = this,\r\n\t\t\toptionsChart = chart.options.chart,\r\n\t\t\trenderTo = chart.renderToClone || chart.renderTo;\r\n\r\n\t\t// get inner width and height from jQuery (#824)\r\n\t\tchart.containerWidth = adapterRun(renderTo, 'width');\r\n\t\tchart.containerHeight = adapterRun(renderTo, 'height');\r\n\t\t\r\n\t\tchart.chartWidth = mathMax(0, optionsChart.width || chart.containerWidth || 600); // #1393, 1460\r\n\t\tchart.chartHeight = mathMax(0, pick(optionsChart.height,\r\n\t\t\t// the offsetHeight of an empty container is 0 in standard browsers, but 19 in IE7:\r\n\t\t\tchart.containerHeight > 19 ? chart.containerHeight : 400));\r\n\t},\r\n\r\n\t/**\r\n\t * Create a clone of the chart's renderTo div and place it outside the viewport to allow\r\n\t * size computation on chart.render and chart.redraw\r\n\t */\r\n\tcloneRenderTo: function (revert) {\r\n\t\tvar clone = this.renderToClone,\r\n\t\t\tcontainer = this.container;\r\n\t\t\r\n\t\t// Destroy the clone and bring the container back to the real renderTo div\r\n\t\tif (revert) {\r\n\t\t\tif (clone) {\r\n\t\t\t\tthis.renderTo.appendChild(container);\r\n\t\t\t\tdiscardElement(clone);\r\n\t\t\t\tdelete this.renderToClone;\r\n\t\t\t}\r\n\t\t\r\n\t\t// Set up the clone\r\n\t\t} else {\r\n\t\t\tif (container && container.parentNode === this.renderTo) {\r\n\t\t\t\tthis.renderTo.removeChild(container); // do not clone this\r\n\t\t\t}\r\n\t\t\tthis.renderToClone = clone = this.renderTo.cloneNode(0);\r\n\t\t\tcss(clone, {\r\n\t\t\t\tposition: ABSOLUTE,\r\n\t\t\t\ttop: '-9999px',\r\n\t\t\t\tdisplay: 'block' // #833\r\n\t\t\t});\r\n\t\t\tdoc.body.appendChild(clone);\r\n\t\t\tif (container) {\r\n\t\t\t\tclone.appendChild(container);\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Get the containing element, determine the size and create the inner container\r\n\t * div to hold the chart\r\n\t */\r\n\tgetContainer: function () {\r\n\t\tvar chart = this,\r\n\t\t\tcontainer,\r\n\t\t\toptionsChart = chart.options.chart,\r\n\t\t\tchartWidth,\r\n\t\t\tchartHeight,\r\n\t\t\trenderTo,\r\n\t\t\tindexAttrName = 'data-highcharts-chart',\r\n\t\t\toldChartIndex,\r\n\t\t\tcontainerId;\r\n\r\n\t\tchart.renderTo = renderTo = optionsChart.renderTo;\r\n\t\tcontainerId = PREFIX + idCounter++;\r\n\r\n\t\tif (isString(renderTo)) {\r\n\t\t\tchart.renderTo = renderTo = doc.getElementById(renderTo);\r\n\t\t}\r\n\t\t\r\n\t\t// Display an error if the renderTo is wrong\r\n\t\tif (!renderTo) {\r\n\t\t\terror(13, true);\r\n\t\t}\r\n\t\t\r\n\t\t// If the container already holds a chart, destroy it\r\n\t\toldChartIndex = pInt(attr(renderTo, indexAttrName));\r\n\t\tif (!isNaN(oldChartIndex) && charts[oldChartIndex]) {\r\n\t\t\tcharts[oldChartIndex].destroy();\r\n\t\t}\t\t\r\n\t\t\r\n\t\t// Make a reference to the chart from the div\r\n\t\tattr(renderTo, indexAttrName, chart.index);\r\n\r\n\t\t// remove previous chart\r\n\t\trenderTo.innerHTML = '';\r\n\r\n\t\t// If the container doesn't have an offsetWidth, it has or is a child of a node\r\n\t\t// that has display:none. We need to temporarily move it out to a visible\r\n\t\t// state to determine the size, else the legend and tooltips won't render\r\n\t\t// properly\r\n\t\tif (!renderTo.offsetWidth) {\r\n\t\t\tchart.cloneRenderTo();\r\n\t\t}\r\n\r\n\t\t// get the width and height\r\n\t\tchart.getChartSize();\r\n\t\tchartWidth = chart.chartWidth;\r\n\t\tchartHeight = chart.chartHeight;\r\n\r\n\t\t// create the inner container\r\n\t\tchart.container = container = createElement(DIV, {\r\n\t\t\t\tclassName: PREFIX + 'container' +\r\n\t\t\t\t\t(optionsChart.className ? ' ' + optionsChart.className : ''),\r\n\t\t\t\tid: containerId\r\n\t\t\t}, extend({\r\n\t\t\t\tposition: RELATIVE,\r\n\t\t\t\toverflow: HIDDEN, // needed for context menu (avoid scrollbars) and\r\n\t\t\t\t\t// content overflow in IE\r\n\t\t\t\twidth: chartWidth + PX,\r\n\t\t\t\theight: chartHeight + PX,\r\n\t\t\t\ttextAlign: 'left',\r\n\t\t\t\tlineHeight: 'normal', // #427\r\n\t\t\t\tzIndex: 0, // #1072\r\n\t\t\t\t'-webkit-tap-highlight-color': 'rgba(0,0,0,0)'\r\n\t\t\t}, optionsChart.style),\r\n\t\t\tchart.renderToClone || renderTo\r\n\t\t);\r\n\r\n\t\t// cache the cursor (#1650)\r\n\t\tchart._cursor = container.style.cursor;\r\n\r\n\t\tchart.renderer =\r\n\t\t\toptionsChart.forExport ? // force SVG, used for SVG export\r\n\t\t\t\tnew SVGRenderer(container, chartWidth, chartHeight, true) :\r\n\t\t\t\tnew Renderer(container, chartWidth, chartHeight);\r\n\r\n\t\tif (useCanVG) {\r\n\t\t\t// If we need canvg library, extend and configure the renderer\r\n\t\t\t// to get the tracker for translating mouse events\r\n\t\t\tchart.renderer.create(chart, container, chartWidth, chartHeight);\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Calculate margins by rendering axis labels in a preliminary position. Title,\r\n\t * subtitle and legend have already been rendered at this stage, but will be\r\n\t * moved into their final positions\r\n\t */\r\n\tgetMargins: function () {\r\n\t\tvar chart = this,\r\n\t\t\tspacing = chart.spacing,\r\n\t\t\taxisOffset,\r\n\t\t\tlegend = chart.legend,\r\n\t\t\tmargin = chart.margin,\r\n\t\t\tlegendOptions = chart.options.legend,\r\n\t\t\tlegendMargin = pick(legendOptions.margin, 10),\r\n\t\t\tlegendX = legendOptions.x,\r\n\t\t\tlegendY = legendOptions.y,\r\n\t\t\talign = legendOptions.align,\r\n\t\t\tverticalAlign = legendOptions.verticalAlign,\r\n\t\t\ttitleOffset = chart.titleOffset;\r\n\r\n\t\tchart.resetMargins();\r\n\t\taxisOffset = chart.axisOffset;\r\n\r\n\t\t// Adjust for title and subtitle\r\n\t\tif (titleOffset && !defined(margin[0])) {\r\n\t\t\tchart.plotTop = mathMax(chart.plotTop, titleOffset + chart.options.title.margin + spacing[0]);\r\n\t\t}\r\n\t\t\r\n\t\t// Adjust for legend\r\n\t\tif (legend.display && !legendOptions.floating) {\r\n\t\t\tif (align === 'right') { // horizontal alignment handled first\r\n\t\t\t\tif (!defined(margin[1])) {\r\n\t\t\t\t\tchart.marginRight = mathMax(\r\n\t\t\t\t\t\tchart.marginRight,\r\n\t\t\t\t\t\tlegend.legendWidth - legendX + legendMargin + spacing[1]\r\n\t\t\t\t\t);\r\n\t\t\t\t}\r\n\t\t\t} else if (align === 'left') {\r\n\t\t\t\tif (!defined(margin[3])) {\r\n\t\t\t\t\tchart.plotLeft = mathMax(\r\n\t\t\t\t\t\tchart.plotLeft,\r\n\t\t\t\t\t\tlegend.legendWidth + legendX + legendMargin + spacing[3]\r\n\t\t\t\t\t);\r\n\t\t\t\t}\r\n\r\n\t\t\t} else if (verticalAlign === 'top') {\r\n\t\t\t\tif (!defined(margin[0])) {\r\n\t\t\t\t\tchart.plotTop = mathMax(\r\n\t\t\t\t\t\tchart.plotTop,\r\n\t\t\t\t\t\tlegend.legendHeight + legendY + legendMargin + spacing[0]\r\n\t\t\t\t\t);\r\n\t\t\t\t}\r\n\r\n\t\t\t} else if (verticalAlign === 'bottom') {\r\n\t\t\t\tif (!defined(margin[2])) {\r\n\t\t\t\t\tchart.marginBottom = mathMax(\r\n\t\t\t\t\t\tchart.marginBottom,\r\n\t\t\t\t\t\tlegend.legendHeight - legendY + legendMargin + spacing[2]\r\n\t\t\t\t\t);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// adjust for scroller\r\n\t\tif (chart.extraBottomMargin) {\r\n\t\t\tchart.marginBottom += chart.extraBottomMargin;\r\n\t\t}\r\n\t\tif (chart.extraTopMargin) {\r\n\t\t\tchart.plotTop += chart.extraTopMargin;\r\n\t\t}\r\n\r\n\t\t// pre-render axes to get labels offset width\r\n\t\tif (chart.hasCartesianSeries) {\r\n\t\t\teach(chart.axes, function (axis) {\r\n\t\t\t\taxis.getOffset();\r\n\t\t\t});\r\n\t\t}\r\n\t\t\r\n\t\tif (!defined(margin[3])) {\r\n\t\t\tchart.plotLeft += axisOffset[3];\r\n\t\t}\r\n\t\tif (!defined(margin[0])) {\r\n\t\t\tchart.plotTop += axisOffset[0];\r\n\t\t}\r\n\t\tif (!defined(margin[2])) {\r\n\t\t\tchart.marginBottom += axisOffset[2];\r\n\t\t}\r\n\t\tif (!defined(margin[1])) {\r\n\t\t\tchart.marginRight += axisOffset[1];\r\n\t\t}\r\n\r\n\t\tchart.setChartSize();\r\n\r\n\t},\r\n\r\n\t/**\r\n\t * Add the event handlers necessary for auto resizing\r\n\t *\r\n\t */\r\n\tinitReflow: function () {\r\n\t\tvar chart = this,\r\n\t\t\toptionsChart = chart.options.chart,\r\n\t\t\trenderTo = chart.renderTo,\r\n\t\t\treflowTimeout;\r\n\t\t\t\r\n\t\tfunction reflow(e) {\r\n\t\t\tvar width = optionsChart.width || adapterRun(renderTo, 'width'),\r\n\t\t\t\theight = optionsChart.height || adapterRun(renderTo, 'height'),\r\n\t\t\t\ttarget = e ? e.target : win; // #805 - MooTools doesn't supply e\r\n\t\t\t\t\r\n\t\t\t// Width and height checks for display:none. Target is doc in IE8 and Opera,\r\n\t\t\t// win in Firefox, Chrome and IE9.\r\n\t\t\tif (!chart.hasUserSize && width && height && (target === win || target === doc)) {\r\n\t\t\t\t\r\n\t\t\t\tif (width !== chart.containerWidth || height !== chart.containerHeight) {\r\n\t\t\t\t\tclearTimeout(reflowTimeout);\r\n\t\t\t\t\tchart.reflowTimeout = reflowTimeout = setTimeout(function () {\r\n\t\t\t\t\t\tif (chart.container) { // It may have been destroyed in the meantime (#1257)\r\n\t\t\t\t\t\t\tchart.setSize(width, height, false);\r\n\t\t\t\t\t\t\tchart.hasUserSize = null;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}, 100);\r\n\t\t\t\t}\r\n\t\t\t\tchart.containerWidth = width;\r\n\t\t\t\tchart.containerHeight = height;\r\n\t\t\t}\r\n\t\t}\r\n\t\tchart.reflow = reflow;\r\n\t\taddEvent(win, 'resize', reflow);\r\n\t\taddEvent(chart, 'destroy', function () {\r\n\t\t\tremoveEvent(win, 'resize', reflow);\r\n\t\t});\r\n\t},\r\n\r\n\t/**\r\n\t * Resize the chart to a given width and height\r\n\t * @param {Number} width\r\n\t * @param {Number} height\r\n\t * @param {Object|Boolean} animation\r\n\t */\r\n\tsetSize: function (width, height, animation) {\r\n\t\tvar chart = this,\r\n\t\t\tchartWidth,\r\n\t\t\tchartHeight,\r\n\t\t\tfireEndResize;\r\n\r\n\t\t// Handle the isResizing counter\r\n\t\tchart.isResizing += 1;\r\n\t\tfireEndResize = function () {\r\n\t\t\tif (chart) {\r\n\t\t\t\tfireEvent(chart, 'endResize', null, function () {\r\n\t\t\t\t\tchart.isResizing -= 1;\r\n\t\t\t\t});\r\n\t\t\t}\r\n\t\t};\r\n\r\n\t\t// set the animation for the current process\r\n\t\tsetAnimation(animation, chart);\r\n\r\n\t\tchart.oldChartHeight = chart.chartHeight;\r\n\t\tchart.oldChartWidth = chart.chartWidth;\r\n\t\tif (defined(width)) {\r\n\t\t\tchart.chartWidth = chartWidth = mathMax(0, mathRound(width));\r\n\t\t\tchart.hasUserSize = !!chartWidth;\r\n\t\t}\r\n\t\tif (defined(height)) {\r\n\t\t\tchart.chartHeight = chartHeight = mathMax(0, mathRound(height));\r\n\t\t}\r\n\r\n\t\tcss(chart.container, {\r\n\t\t\twidth: chartWidth + PX,\r\n\t\t\theight: chartHeight + PX\r\n\t\t});\r\n\t\tchart.setChartSize(true);\r\n\t\tchart.renderer.setSize(chartWidth, chartHeight, animation);\r\n\r\n\t\t// handle axes\r\n\t\tchart.maxTicks = null;\r\n\t\teach(chart.axes, function (axis) {\r\n\t\t\taxis.isDirty = true;\r\n\t\t\taxis.setScale();\r\n\t\t});\r\n\r\n\t\t// make sure non-cartesian series are also handled\r\n\t\teach(chart.series, function (serie) {\r\n\t\t\tserie.isDirty = true;\r\n\t\t});\r\n\r\n\t\tchart.isDirtyLegend = true; // force legend redraw\r\n\t\tchart.isDirtyBox = true; // force redraw of plot and chart border\r\n\r\n\t\tchart.getMargins();\r\n\r\n\t\tchart.redraw(animation);\r\n\r\n\r\n\t\tchart.oldChartHeight = null;\r\n\t\tfireEvent(chart, 'resize');\r\n\r\n\t\t// fire endResize and set isResizing back\r\n\t\t// If animation is disabled, fire without delay\r\n\t\tif (globalAnimation === false) {\r\n\t\t\tfireEndResize();\r\n\t\t} else { // else set a timeout with the animation duration\r\n\t\t\tsetTimeout(fireEndResize, (globalAnimation && globalAnimation.duration) || 500);\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Set the public chart properties. This is done before and after the pre-render\r\n\t * to determine margin sizes\r\n\t */\r\n\tsetChartSize: function (skipAxes) {\r\n\t\tvar chart = this,\r\n\t\t\tinverted = chart.inverted,\r\n\t\t\trenderer = chart.renderer,\r\n\t\t\tchartWidth = chart.chartWidth,\r\n\t\t\tchartHeight = chart.chartHeight,\r\n\t\t\toptionsChart = chart.options.chart,\r\n\t\t\tspacing = chart.spacing,\r\n\t\t\tclipOffset = chart.clipOffset,\r\n\t\t\tclipX,\r\n\t\t\tclipY,\r\n\t\t\tplotLeft,\r\n\t\t\tplotTop,\r\n\t\t\tplotWidth,\r\n\t\t\tplotHeight,\r\n\t\t\tplotBorderWidth;\r\n\r\n\t\tchart.plotLeft = plotLeft = mathRound(chart.plotLeft);\r\n\t\tchart.plotTop = plotTop = mathRound(chart.plotTop);\r\n\t\tchart.plotWidth = plotWidth = mathMax(0, mathRound(chartWidth - plotLeft - chart.marginRight));\r\n\t\tchart.plotHeight = plotHeight = mathMax(0, mathRound(chartHeight - plotTop - chart.marginBottom));\r\n\r\n\t\tchart.plotSizeX = inverted ? plotHeight : plotWidth;\r\n\t\tchart.plotSizeY = inverted ? plotWidth : plotHeight;\r\n\t\t\r\n\t\tchart.plotBorderWidth = optionsChart.plotBorderWidth || 0;\r\n\r\n\t\t// Set boxes used for alignment\r\n\t\tchart.spacingBox = renderer.spacingBox = {\r\n\t\t\tx: spacing[3],\r\n\t\t\ty: spacing[0],\r\n\t\t\twidth: chartWidth - spacing[3] - spacing[1],\r\n\t\t\theight: chartHeight - spacing[0] - spacing[2]\r\n\t\t};\r\n\t\tchart.plotBox = renderer.plotBox = {\r\n\t\t\tx: plotLeft,\r\n\t\t\ty: plotTop,\r\n\t\t\twidth: plotWidth,\r\n\t\t\theight: plotHeight\r\n\t\t};\r\n\r\n\t\tplotBorderWidth = 2 * mathFloor(chart.plotBorderWidth / 2);\r\n\t\tclipX = mathCeil(mathMax(plotBorderWidth, clipOffset[3]) / 2);\r\n\t\tclipY = mathCeil(mathMax(plotBorderWidth, clipOffset[0]) / 2);\r\n\t\tchart.clipBox = {\r\n\t\t\tx: clipX, \r\n\t\t\ty: clipY, \r\n\t\t\twidth: mathFloor(chart.plotSizeX - mathMax(plotBorderWidth, clipOffset[1]) / 2 - clipX), \r\n\t\t\theight: mathFloor(chart.plotSizeY - mathMax(plotBorderWidth, clipOffset[2]) / 2 - clipY)\r\n\t\t};\r\n\r\n\t\tif (!skipAxes) {\r\n\t\t\teach(chart.axes, function (axis) {\r\n\t\t\t\taxis.setAxisSize();\r\n\t\t\t\taxis.setAxisTranslation();\r\n\t\t\t});\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Initial margins before auto size margins are applied\r\n\t */\r\n\tresetMargins: function () {\r\n\t\tvar chart = this,\r\n\t\t\tspacing = chart.spacing,\r\n\t\t\tmargin = chart.margin;\r\n\r\n\t\tchart.plotTop = pick(margin[0], spacing[0]);\r\n\t\tchart.marginRight = pick(margin[1], spacing[1]);\r\n\t\tchart.marginBottom = pick(margin[2], spacing[2]);\r\n\t\tchart.plotLeft = pick(margin[3], spacing[3]);\r\n\t\tchart.axisOffset = [0, 0, 0, 0]; // top, right, bottom, left\r\n\t\tchart.clipOffset = [0, 0, 0, 0];\r\n\t},\r\n\r\n\t/**\r\n\t * Draw the borders and backgrounds for chart and plot area\r\n\t */\r\n\tdrawChartBox: function () {\r\n\t\tvar chart = this,\r\n\t\t\toptionsChart = chart.options.chart,\r\n\t\t\trenderer = chart.renderer,\r\n\t\t\tchartWidth = chart.chartWidth,\r\n\t\t\tchartHeight = chart.chartHeight,\r\n\t\t\tchartBackground = chart.chartBackground,\r\n\t\t\tplotBackground = chart.plotBackground,\r\n\t\t\tplotBorder = chart.plotBorder,\r\n\t\t\tplotBGImage = chart.plotBGImage,\r\n\t\t\tchartBorderWidth = optionsChart.borderWidth || 0,\r\n\t\t\tchartBackgroundColor = optionsChart.backgroundColor,\r\n\t\t\tplotBackgroundColor = optionsChart.plotBackgroundColor,\r\n\t\t\tplotBackgroundImage = optionsChart.plotBackgroundImage,\r\n\t\t\tplotBorderWidth = optionsChart.plotBorderWidth || 0,\r\n\t\t\tmgn,\r\n\t\t\tbgAttr,\r\n\t\t\tplotLeft = chart.plotLeft,\r\n\t\t\tplotTop = chart.plotTop,\r\n\t\t\tplotWidth = chart.plotWidth,\r\n\t\t\tplotHeight = chart.plotHeight,\r\n\t\t\tplotBox = chart.plotBox,\r\n\t\t\tclipRect = chart.clipRect,\r\n\t\t\tclipBox = chart.clipBox;\r\n\r\n\t\t// Chart area\r\n\t\tmgn = chartBorderWidth + (optionsChart.shadow ? 8 : 0);\r\n\r\n\t\tif (chartBorderWidth || chartBackgroundColor) {\r\n\t\t\tif (!chartBackground) {\r\n\t\t\t\t\r\n\t\t\t\tbgAttr = {\r\n\t\t\t\t\tfill: chartBackgroundColor || NONE\r\n\t\t\t\t};\r\n\t\t\t\tif (chartBorderWidth) { // #980\r\n\t\t\t\t\tbgAttr.stroke = optionsChart.borderColor;\r\n\t\t\t\t\tbgAttr['stroke-width'] = chartBorderWidth;\r\n\t\t\t\t}\r\n\t\t\t\tchart.chartBackground = renderer.rect(mgn / 2, mgn / 2, chartWidth - mgn, chartHeight - mgn,\r\n\t\t\t\t\t\toptionsChart.borderRadius, chartBorderWidth)\r\n\t\t\t\t\t.attr(bgAttr)\r\n\t\t\t\t\t.add()\r\n\t\t\t\t\t.shadow(optionsChart.shadow);\r\n\r\n\t\t\t} else { // resize\r\n\t\t\t\tchartBackground.animate(\r\n\t\t\t\t\tchartBackground.crisp(null, null, null, chartWidth - mgn, chartHeight - mgn)\r\n\t\t\t\t);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\r\n\t\t// Plot background\r\n\t\tif (plotBackgroundColor) {\r\n\t\t\tif (!plotBackground) {\r\n\t\t\t\tchart.plotBackground = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0)\r\n\t\t\t\t\t.attr({\r\n\t\t\t\t\t\tfill: plotBackgroundColor\r\n\t\t\t\t\t})\r\n\t\t\t\t\t.add()\r\n\t\t\t\t\t.shadow(optionsChart.plotShadow);\r\n\t\t\t} else {\r\n\t\t\t\tplotBackground.animate(plotBox);\r\n\t\t\t}\r\n\t\t}\r\n\t\tif (plotBackgroundImage) {\r\n\t\t\tif (!plotBGImage) {\r\n\t\t\t\tchart.plotBGImage = renderer.image(plotBackgroundImage, plotLeft, plotTop, plotWidth, plotHeight)\r\n\t\t\t\t\t.add();\r\n\t\t\t} else {\r\n\t\t\t\tplotBGImage.animate(plotBox);\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// Plot clip\r\n\t\tif (!clipRect) {\r\n\t\t\tchart.clipRect = renderer.clipRect(clipBox);\r\n\t\t} else {\r\n\t\t\tclipRect.animate({\r\n\t\t\t\twidth: clipBox.width,\r\n\t\t\t\theight: clipBox.height\r\n\t\t\t});\r\n\t\t}\r\n\r\n\t\t// Plot area border\r\n\t\tif (plotBorderWidth) {\r\n\t\t\tif (!plotBorder) {\r\n\t\t\t\tchart.plotBorder = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0, -plotBorderWidth)\r\n\t\t\t\t\t.attr({\r\n\t\t\t\t\t\tstroke: optionsChart.plotBorderColor,\r\n\t\t\t\t\t\t'stroke-width': plotBorderWidth,\r\n\t\t\t\t\t\tzIndex: 1\r\n\t\t\t\t\t})\r\n\t\t\t\t\t.add();\r\n\t\t\t} else {\r\n\t\t\t\tplotBorder.animate(\r\n\t\t\t\t\tplotBorder.crisp(null, plotLeft, plotTop, plotWidth, plotHeight)\r\n\t\t\t\t);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// reset\r\n\t\tchart.isDirtyBox = false;\r\n\t},\r\n\r\n\t/**\r\n\t * Detect whether a certain chart property is needed based on inspecting its options\r\n\t * and series. This mainly applies to the chart.invert property, and in extensions to \r\n\t * the chart.angular and chart.polar properties.\r\n\t */\r\n\tpropFromSeries: function () {\r\n\t\tvar chart = this,\r\n\t\t\toptionsChart = chart.options.chart,\r\n\t\t\tklass,\r\n\t\t\tseriesOptions = chart.options.series,\r\n\t\t\ti,\r\n\t\t\tvalue;\r\n\t\t\t\r\n\t\t\t\r\n\t\teach(['inverted', 'angular', 'polar'], function (key) {\r\n\t\t\t\r\n\t\t\t// The default series type's class\r\n\t\t\tklass = seriesTypes[optionsChart.type || optionsChart.defaultSeriesType];\r\n\t\t\t\r\n\t\t\t// Get the value from available chart-wide properties\r\n\t\t\tvalue = (\r\n\t\t\t\tchart[key] || // 1. it is set before\r\n\t\t\t\toptionsChart[key] || // 2. it is set in the options\r\n\t\t\t\t(klass && klass.prototype[key]) // 3. it's default series class requires it\r\n\t\t\t);\r\n\t\r\n\t\t\t// 4. Check if any the chart's series require it\r\n\t\t\ti = seriesOptions && seriesOptions.length;\r\n\t\t\twhile (!value && i--) {\r\n\t\t\t\tklass = seriesTypes[seriesOptions[i].type];\r\n\t\t\t\tif (klass && klass.prototype[key]) {\r\n\t\t\t\t\tvalue = true;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\r\n\t\t\t// Set the chart property\r\n\t\t\tchart[key] = value;\t\r\n\t\t});\r\n\t\t\r\n\t},\r\n\r\n\t/**\r\n\t * Link two or more series together. This is done initially from Chart.render,\r\n\t * and after Chart.addSeries and Series.remove.\r\n\t */\r\n\tlinkSeries: function () {\r\n\t\tvar chart = this,\r\n\t\t\tchartSeries = chart.series;\r\n\r\n\t\t// Reset links\r\n\t\teach(chartSeries, function (series) {\r\n\t\t\tseries.linkedSeries.length = 0;\r\n\t\t});\r\n\r\n\t\t// Apply new links\r\n\t\teach(chartSeries, function (series) {\r\n\t\t\tvar linkedTo = series.options.linkedTo;\r\n\t\t\tif (isString(linkedTo)) {\r\n\t\t\t\tif (linkedTo === ':previous') {\r\n\t\t\t\t\tlinkedTo = chart.series[series.index - 1];\r\n\t\t\t\t} else {\r\n\t\t\t\t\tlinkedTo = chart.get(linkedTo);\r\n\t\t\t\t}\r\n\t\t\t\tif (linkedTo) {\r\n\t\t\t\t\tlinkedTo.linkedSeries.push(series);\r\n\t\t\t\t\tseries.linkedParent = linkedTo;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t});\r\n\t},\r\n\r\n\t/**\r\n\t * Render all graphics for the chart\r\n\t */\r\n\trender: function () {\r\n\t\tvar chart = this,\r\n\t\t\taxes = chart.axes,\r\n\t\t\trenderer = chart.renderer,\r\n\t\t\toptions = chart.options;\r\n\r\n\t\tvar labels = options.labels,\r\n\t\t\tcredits = options.credits,\r\n\t\t\tcreditsHref;\r\n\r\n\t\t// Title\r\n\t\tchart.setTitle();\r\n\r\n\r\n\t\t// Legend\r\n\t\tchart.legend = new Legend(chart, options.legend);\r\n\r\n\t\tchart.getStacks(); // render stacks\r\n\r\n\t\t// Get margins by pre-rendering axes\r\n\t\t// set axes scales\r\n\t\teach(axes, function (axis) {\r\n\t\t\taxis.setScale();\r\n\t\t});\r\n\r\n\t\tchart.getMargins();\r\n\r\n\t\tchart.maxTicks = null; // reset for second pass\r\n\t\teach(axes, function (axis) {\r\n\t\t\taxis.setTickPositions(true); // update to reflect the new margins\r\n\t\t\taxis.setMaxTicks();\r\n\t\t});\r\n\t\tchart.adjustTickAmounts();\r\n\t\tchart.getMargins(); // second pass to check for new labels\r\n\r\n\r\n\t\t// Draw the borders and backgrounds\r\n\t\tchart.drawChartBox();\t\t\r\n\r\n\r\n\t\t// Axes\r\n\t\tif (chart.hasCartesianSeries) {\r\n\t\t\teach(axes, function (axis) {\r\n\t\t\t\taxis.render();\r\n\t\t\t});\r\n\t\t}\r\n\r\n\t\t// The series\r\n\t\tif (!chart.seriesGroup) {\r\n\t\t\tchart.seriesGroup = renderer.g('series-group')\r\n\t\t\t\t.attr({ zIndex: 3 })\r\n\t\t\t\t.add();\r\n\t\t}\r\n\t\teach(chart.series, function (serie) {\r\n\t\t\tserie.translate();\r\n\t\t\tserie.setTooltipPoints();\r\n\t\t\tserie.render();\r\n\t\t});\r\n\r\n\t\t// Labels\r\n\t\tif (labels.items) {\r\n\t\t\teach(labels.items, function (label) {\r\n\t\t\t\tvar style = extend(labels.style, label.style),\r\n\t\t\t\t\tx = pInt(style.left) + chart.plotLeft,\r\n\t\t\t\t\ty = pInt(style.top) + chart.plotTop + 12;\r\n\r\n\t\t\t\t// delete to prevent rewriting in IE\r\n\t\t\t\tdelete style.left;\r\n\t\t\t\tdelete style.top;\r\n\r\n\t\t\t\trenderer.text(\r\n\t\t\t\t\tlabel.html,\r\n\t\t\t\t\tx,\r\n\t\t\t\t\ty\r\n\t\t\t\t)\r\n\t\t\t\t.attr({ zIndex: 2 })\r\n\t\t\t\t.css(style)\r\n\t\t\t\t.add();\r\n\r\n\t\t\t});\r\n\t\t}\r\n\r\n\t\t// Credits\r\n\t\tif (credits.enabled && !chart.credits) {\r\n\t\t\tcreditsHref = credits.href;\r\n\t\t\tchart.credits = renderer.text(\r\n\t\t\t\tcredits.text,\r\n\t\t\t\t0,\r\n\t\t\t\t0\r\n\t\t\t)\r\n\t\t\t.on('click', function () {\r\n\t\t\t\tif (creditsHref) {\r\n\t\t\t\t\tlocation.href = creditsHref;\r\n\t\t\t\t}\r\n\t\t\t})\r\n\t\t\t.attr({\r\n\t\t\t\talign: credits.position.align,\r\n\t\t\t\tzIndex: 8\r\n\t\t\t})\r\n\t\t\t.css(credits.style)\r\n\t\t\t.add()\r\n\t\t\t.align(credits.position);\r\n\t\t}\r\n\r\n\t\t// Set flag\r\n\t\tchart.hasRendered = true;\r\n\r\n\t},\r\n\r\n\t/**\r\n\t * Clean up memory usage\r\n\t */\r\n\tdestroy: function () {\r\n\t\tvar chart = this,\r\n\t\t\taxes = chart.axes,\r\n\t\t\tseries = chart.series,\r\n\t\t\tcontainer = chart.container,\r\n\t\t\ti,\r\n\t\t\tparentNode = container && container.parentNode;\r\n\t\t\t\r\n\t\t// fire the chart.destoy event\r\n\t\tfireEvent(chart, 'destroy');\r\n\t\t\r\n\t\t// Delete the chart from charts lookup array\r\n\t\tcharts[chart.index] = UNDEFINED;\r\n\t\tchart.renderTo.removeAttribute('data-highcharts-chart');\r\n\r\n\t\t// remove events\r\n\t\tremoveEvent(chart);\r\n\r\n\t\t// ==== Destroy collections:\r\n\t\t// Destroy axes\r\n\t\ti = axes.length;\r\n\t\twhile (i--) {\r\n\t\t\taxes[i] = axes[i].destroy();\r\n\t\t}\r\n\r\n\t\t// Destroy each series\r\n\t\ti = series.length;\r\n\t\twhile (i--) {\r\n\t\t\tseries[i] = series[i].destroy();\r\n\t\t}\r\n\r\n\t\t// ==== Destroy chart properties:\r\n\t\teach(['title', 'subtitle', 'chartBackground', 'plotBackground', 'plotBGImage', \r\n\t\t\t\t'plotBorder', 'seriesGroup', 'clipRect', 'credits', 'pointer', 'scroller', \r\n\t\t\t\t'rangeSelector', 'legend', 'resetZoomButton', 'tooltip', 'renderer'], function (name) {\r\n\t\t\tvar prop = chart[name];\r\n\r\n\t\t\tif (prop && prop.destroy) {\r\n\t\t\t\tchart[name] = prop.destroy();\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\t// remove container and all SVG\r\n\t\tif (container) { // can break in IE when destroyed before finished loading\r\n\t\t\tcontainer.innerHTML = '';\r\n\t\t\tremoveEvent(container);\r\n\t\t\tif (parentNode) {\r\n\t\t\t\tdiscardElement(container);\r\n\t\t\t}\r\n\r\n\t\t}\r\n\r\n\t\t// clean it all up\r\n\t\tfor (i in chart) {\r\n\t\t\tdelete chart[i];\r\n\t\t}\r\n\r\n\t},\r\n\r\n\r\n\t/**\r\n\t * VML namespaces can't be added until after complete. Listening\r\n\t * for Perini's doScroll hack is not enough.\r\n\t */\r\n\tisReadyToRender: function () {\r\n\t\tvar chart = this;\r\n\r\n\t\t// Note: in spite of JSLint's complaints, win == win.top is required\r\n\t\t/*jslint eqeq: true*/\r\n\t\tif ((!hasSVG && (win == win.top && doc.readyState !== 'complete')) || (useCanVG && !win.canvg)) {\r\n\t\t/*jslint eqeq: false*/\r\n\t\t\tif (useCanVG) {\r\n\t\t\t\t// Delay rendering until canvg library is downloaded and ready\r\n\t\t\t\tCanVGController.push(function () { chart.firstRender(); }, chart.options.global.canvasToolsURL);\r\n\t\t\t} else {\r\n\t\t\t\tdoc.attachEvent('onreadystatechange', function () {\r\n\t\t\t\t\tdoc.detachEvent('onreadystatechange', chart.firstRender);\r\n\t\t\t\t\tif (doc.readyState === 'complete') {\r\n\t\t\t\t\t\tchart.firstRender();\r\n\t\t\t\t\t}\r\n\t\t\t\t});\r\n\t\t\t}\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\treturn true;\r\n\t},\r\n\r\n\t/**\r\n\t * Prepare for first rendering after all data are loaded\r\n\t */\r\n\tfirstRender: function () {\r\n\t\tvar chart = this,\r\n\t\t\toptions = chart.options,\r\n\t\t\tcallback = chart.callback;\r\n\r\n\t\t// Check whether the chart is ready to render\r\n\t\tif (!chart.isReadyToRender()) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t// Create the container\r\n\t\tchart.getContainer();\r\n\r\n\t\t// Run an early event after the container and renderer are established\r\n\t\tfireEvent(chart, 'init');\r\n\r\n\t\t\r\n\t\tchart.resetMargins();\r\n\t\tchart.setChartSize();\r\n\r\n\t\t// Set the common chart properties (mainly invert) from the given series\r\n\t\tchart.propFromSeries();\r\n\r\n\t\t// get axes\r\n\t\tchart.getAxes();\r\n\r\n\t\t// Initialize the series\r\n\t\teach(options.series || [], function (serieOptions) {\r\n\t\t\tchart.initSeries(serieOptions);\r\n\t\t});\r\n\r\n\t\tchart.linkSeries();\r\n\r\n\t\t// Run an event after axes and series are initialized, but before render. At this stage,\r\n\t\t// the series data is indexed and cached in the xData and yData arrays, so we can access\r\n\t\t// those before rendering. Used in Highstock. \r\n\t\tfireEvent(chart, 'beforeRender'); \r\n\r\n\t\t// depends on inverted and on margins being set\r\n\t\tchart.pointer = new Pointer(chart, options);\r\n\r\n\t\tchart.render();\r\n\r\n\t\t// add canvas\r\n\t\tchart.renderer.draw();\r\n\t\t// run callbacks\r\n\t\tif (callback) {\r\n\t\t\tcallback.apply(chart, [chart]);\r\n\t\t}\r\n\t\teach(chart.callbacks, function (fn) {\r\n\t\t\tfn.apply(chart, [chart]);\r\n\t\t});\r\n\t\t\r\n\t\t\r\n\t\t// If the chart was rendered outside the top container, put it back in\r\n\t\tchart.cloneRenderTo(true);\r\n\r\n\t\tfireEvent(chart, 'load');\r\n\r\n\t},\r\n\r\n\t/**\r\n\t* Creates arrays for spacing and margin from given options.\r\n\t*/\r\n\tsplashArray: function (target, options) {\r\n\t\tvar oVar = options[target],\r\n\t\t\ttArray = isObject(oVar) ? oVar : [oVar, oVar, oVar, oVar];\r\n\r\n\t\treturn [pick(options[target + 'Top'], tArray[0]),\r\n\t\t\t\tpick(options[target + 'Right'], tArray[1]),\r\n\t\t\t\tpick(options[target + 'Bottom'], tArray[2]),\r\n\t\t\t\tpick(options[target + 'Left'], tArray[3])];\r\n\t}\r\n}; // end Chart\r\n\r\n// Hook for exporting module\r\nChart.prototype.callbacks = [];\r\n/**\r\n * The Point object and prototype. Inheritable and used as base for PiePoint\r\n */\r\nvar Point = function () {};\r\nPoint.prototype = {\r\n\r\n\t/**\r\n\t * Initialize the point\r\n\t * @param {Object} series The series object containing this point\r\n\t * @param {Object} options The data in either number, array or object format\r\n\t */\r\n\tinit: function (series, options, x) {\r\n\r\n\t\tvar point = this,\r\n\t\t\tcolors;\r\n\t\tpoint.series = series;\r\n\t\tpoint.applyOptions(options, x);\r\n\t\tpoint.pointAttr = {};\r\n\r\n\t\tif (series.options.colorByPoint) {\r\n\t\t\tcolors = series.options.colors || series.chart.options.colors;\r\n\t\t\tpoint.color = point.color || colors[series.colorCounter++];\r\n\t\t\t// loop back to zero\r\n\t\t\tif (series.colorCounter === colors.length) {\r\n\t\t\t\tseries.colorCounter = 0;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tseries.chart.pointCount++;\r\n\t\treturn point;\r\n\t},\r\n\t/**\r\n\t * Apply the options containing the x and y data and possible some extra properties.\r\n\t * This is called on point init or from point.update.\r\n\t *\r\n\t * @param {Object} options\r\n\t */\r\n\tapplyOptions: function (options, x) {\r\n\t\tvar point = this,\r\n\t\t\tseries = point.series,\r\n\t\t\tpointValKey = series.pointValKey;\r\n\r\n\t\toptions = Point.prototype.optionsToObject.call(this, options);\r\n\r\n\t\t// copy options directly to point\r\n\t\textend(point, options);\r\n\t\tpoint.options = point.options ? extend(point.options, options) : options;\r\n\t\t\t\r\n\t\t// For higher dimension series types. For instance, for ranges, point.y is mapped to point.low.\r\n\t\tif (pointValKey) {\r\n\t\t\tpoint.y = point[pointValKey];\r\n\t\t}\r\n\t\t\r\n\t\t// If no x is set by now, get auto incremented value. All points must have an\r\n\t\t// x value, however the y value can be null to create a gap in the series\r\n\t\tif (point.x === UNDEFINED && series) {\r\n\t\t\tpoint.x = x === UNDEFINED ? series.autoIncrement() : x;\r\n\t\t}\r\n\t\t\r\n\t\treturn point;\r\n\t},\r\n\r\n\t/**\r\n\t * Transform number or array configs into objects\r\n\t */\r\n\toptionsToObject: function (options) {\r\n\t\tvar ret,\r\n\t\t\tseries = this.series,\r\n\t\t\tpointArrayMap = series.pointArrayMap || ['y'],\r\n\t\t\tvalueCount = pointArrayMap.length,\r\n\t\t\tfirstItemType,\r\n\t\t\ti = 0,\r\n\t\t\tj = 0;\r\n\r\n\t\tif (typeof options === 'number' || options === null) {\r\n\t\t\tret = { y: options };\r\n\r\n\t\t} else if (isArray(options)) {\r\n\t\t\tret = {};\r\n\t\t\t// with leading x value\r\n\t\t\tif (options.length > valueCount) {\r\n\t\t\t\tfirstItemType = typeof options[0];\r\n\t\t\t\tif (firstItemType === 'string') {\r\n\t\t\t\t\tret.name = options[0];\r\n\t\t\t\t} else if (firstItemType === 'number') {\r\n\t\t\t\t\tret.x = options[0];\r\n\t\t\t\t}\r\n\t\t\t\ti++;\r\n\t\t\t}\r\n\t\t\twhile (j < valueCount) {\r\n\t\t\t\tret[pointArrayMap[j++]] = options[i++];\r\n\t\t\t}\t\t\t\r\n\t\t} else if (typeof options === 'object') {\r\n\t\t\tret = options;\r\n\r\n\t\t\t// This is the fastest way to detect if there are individual point dataLabels that need \r\n\t\t\t// to be considered in drawDataLabels. These can only occur in object configs.\r\n\t\t\tif (options.dataLabels) {\r\n\t\t\t\tseries._hasPointLabels = true;\r\n\t\t\t}\r\n\r\n\t\t\t// Same approach as above for markers\r\n\t\t\tif (options.marker) {\r\n\t\t\t\tseries._hasPointMarkers = true;\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn ret;\r\n\t},\r\n\r\n\t/**\r\n\t * Destroy a point to clear memory. Its reference still stays in series.data.\r\n\t */\r\n\tdestroy: function () {\r\n\t\tvar point = this,\r\n\t\t\tseries = point.series,\r\n\t\t\tchart = series.chart,\r\n\t\t\thoverPoints = chart.hoverPoints,\r\n\t\t\tprop;\r\n\r\n\t\tchart.pointCount--;\r\n\r\n\t\tif (hoverPoints) {\r\n\t\t\tpoint.setState();\r\n\t\t\terase(hoverPoints, point);\r\n\t\t\tif (!hoverPoints.length) {\r\n\t\t\t\tchart.hoverPoints = null;\r\n\t\t\t}\r\n\r\n\t\t}\r\n\t\tif (point === chart.hoverPoint) {\r\n\t\t\tpoint.onMouseOut();\r\n\t\t}\r\n\t\t\r\n\t\t// remove all events\r\n\t\tif (point.graphic || point.dataLabel) { // removeEvent and destroyElements are performance expensive\r\n\t\t\tremoveEvent(point);\r\n\t\t\tpoint.destroyElements();\r\n\t\t}\r\n\r\n\t\tif (point.legendItem) { // pies have legend items\r\n\t\t\tchart.legend.destroyItem(point);\r\n\t\t}\r\n\r\n\t\tfor (prop in point) {\r\n\t\t\tpoint[prop] = null;\r\n\t\t}\r\n\r\n\r\n\t},\r\n\r\n\t/**\r\n\t * Destroy SVG elements associated with the point\r\n\t */\r\n\tdestroyElements: function () {\r\n\t\tvar point = this,\r\n\t\t\tprops = ['graphic', 'dataLabel', 'dataLabelUpper', 'group', 'connector', 'shadowGroup'],\r\n\t\t\tprop,\r\n\t\t\ti = 6;\r\n\t\twhile (i--) {\r\n\t\t\tprop = props[i];\r\n\t\t\tif (point[prop]) {\r\n\t\t\t\tpoint[prop] = point[prop].destroy();\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Return the configuration hash needed for the data label and tooltip formatters\r\n\t */\r\n\tgetLabelConfig: function () {\r\n\t\tvar point = this;\r\n\t\treturn {\r\n\t\t\tx: point.category,\r\n\t\t\ty: point.y,\r\n\t\t\tkey: point.name || point.category,\r\n\t\t\tseries: point.series,\r\n\t\t\tpoint: point,\r\n\t\t\tpercentage: point.percentage,\r\n\t\t\ttotal: point.total || point.stackTotal\r\n\t\t};\r\n\t},\r\n\r\n\t/**\r\n\t * Toggle the selection status of a point\r\n\t * @param {Boolean} selected Whether to select or unselect the point.\r\n\t * @param {Boolean} accumulate Whether to add to the previous selection. By default,\r\n\t *     this happens if the control key (Cmd on Mac) was pressed during clicking.\r\n\t */\r\n\tselect: function (selected, accumulate) {\r\n\t\tvar point = this,\r\n\t\t\tseries = point.series,\r\n\t\t\tchart = series.chart;\r\n\r\n\t\tselected = pick(selected, !point.selected);\r\n\r\n\t\t// fire the event with the defalut handler\r\n\t\tpoint.firePointEvent(selected ? 'select' : 'unselect', { accumulate: accumulate }, function () {\r\n\t\t\tpoint.selected = point.options.selected = selected;\r\n\t\t\tseries.options.data[inArray(point, series.data)] = point.options;\r\n\t\t\t\r\n\t\t\tpoint.setState(selected && SELECT_STATE);\r\n\r\n\t\t\t// unselect all other points unless Ctrl or Cmd + click\r\n\t\t\tif (!accumulate) {\r\n\t\t\t\teach(chart.getSelectedPoints(), function (loopPoint) {\r\n\t\t\t\t\tif (loopPoint.selected && loopPoint !== point) {\r\n\t\t\t\t\t\tloopPoint.selected = loopPoint.options.selected = false;\r\n\t\t\t\t\t\tseries.options.data[inArray(loopPoint, series.data)] = loopPoint.options;\r\n\t\t\t\t\t\tloopPoint.setState(NORMAL_STATE);\r\n\t\t\t\t\t\tloopPoint.firePointEvent('unselect');\r\n\t\t\t\t\t}\r\n\t\t\t\t});\r\n\t\t\t}\r\n\t\t});\r\n\t},\r\n\r\n\t/**\r\n\t * Runs on mouse over the point\r\n\t */\r\n\tonMouseOver: function (e) {\r\n\t\tvar point = this,\r\n\t\t\tseries = point.series,\r\n\t\t\tchart = series.chart,\r\n\t\t\ttooltip = chart.tooltip,\r\n\t\t\thoverPoint = chart.hoverPoint;\r\n\r\n\t\t// set normal state to previous series\r\n\t\tif (hoverPoint && hoverPoint !== point) {\r\n\t\t\thoverPoint.onMouseOut();\r\n\t\t}\r\n\r\n\t\t// trigger the event\r\n\t\tpoint.firePointEvent('mouseOver');\r\n\r\n\t\t// update the tooltip\r\n\t\tif (tooltip && (!tooltip.shared || series.noSharedTooltip)) {\r\n\t\t\ttooltip.refresh(point, e);\r\n\t\t}\r\n\r\n\t\t// hover this\r\n\t\tpoint.setState(HOVER_STATE);\r\n\t\tchart.hoverPoint = point;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Runs on mouse out from the point\r\n\t */\r\n\tonMouseOut: function () {\r\n\t\tvar chart = this.series.chart,\r\n\t\t\thoverPoints = chart.hoverPoints;\r\n\t\t\r\n\t\tif (!hoverPoints || inArray(this, hoverPoints) === -1) { // #887\r\n\t\t\tthis.firePointEvent('mouseOut');\r\n\t\r\n\t\t\tthis.setState();\r\n\t\t\tchart.hoverPoint = null;\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Extendable method for formatting each point's tooltip line\r\n\t *\r\n\t * @return {String} A string to be concatenated in to the common tooltip text\r\n\t */\r\n\ttooltipFormatter: function (pointFormat) {\r\n\t\t\r\n\t\t// Insert options for valueDecimals, valuePrefix, and valueSuffix\r\n\t\tvar series = this.series,\r\n\t\t\tseriesTooltipOptions = series.tooltipOptions,\r\n\t\t\tvalueDecimals = pick(seriesTooltipOptions.valueDecimals, ''),\r\n\t\t\tvaluePrefix = seriesTooltipOptions.valuePrefix || '',\r\n\t\t\tvalueSuffix = seriesTooltipOptions.valueSuffix || '';\r\n\t\t\t\r\n\t\t// Loop over the point array map and replace unformatted values with sprintf formatting markup\r\n\t\teach(series.pointArrayMap || ['y'], function (key) {\r\n\t\t\tkey = '{point.' + key; // without the closing bracket\r\n\t\t\tif (valuePrefix || valueSuffix) {\r\n\t\t\t\tpointFormat = pointFormat.replace(key + '}', valuePrefix + key + '}' + valueSuffix);\r\n\t\t\t}\r\n\t\t\tpointFormat = pointFormat.replace(key + '}', key + ':,.' + valueDecimals + 'f}');\r\n\t\t});\r\n\t\t\r\n\t\treturn format(pointFormat, {\r\n\t\t\tpoint: this,\r\n\t\t\tseries: this.series\r\n\t\t});\r\n\t},\r\n\r\n\t/**\r\n\t * Update the point with new options (typically x/y data) and optionally redraw the series.\r\n\t *\r\n\t * @param {Object} options Point options as defined in the series.data array\r\n\t * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call\r\n\t * @param {Boolean|Object} animation Whether to apply animation, and optionally animation\r\n\t *    configuration\r\n\t *\r\n\t */\r\n\tupdate: function (options, redraw, animation) {\r\n\t\tvar point = this,\r\n\t\t\tseries = point.series,\r\n\t\t\tgraphic = point.graphic,\r\n\t\t\ti,\r\n\t\t\tdata = series.data,\r\n\t\t\tchart = series.chart,\r\n\t\t\tseriesOptions = series.options;\r\n\r\n\t\tredraw = pick(redraw, true);\r\n\r\n\t\t// fire the event with a default handler of doing the update\r\n\t\tpoint.firePointEvent('update', { options: options }, function () {\r\n\r\n\t\t\tpoint.applyOptions(options);\r\n\r\n\t\t\t// update visuals\r\n\t\t\tif (isObject(options)) {\r\n\t\t\t\tseries.getAttribs();\r\n\t\t\t\tif (graphic) {\r\n\t\t\t\t\tif (options.marker && options.marker.symbol) {\r\n\t\t\t\t\t\tpoint.graphic = graphic.destroy();\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tgraphic.attr(point.pointAttr[point.state || '']);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// record changes in the parallel arrays\r\n\t\t\ti = inArray(point, data);\r\n\t\t\tseries.xData[i] = point.x;\r\n\t\t\tseries.yData[i] = series.toYData ? series.toYData(point) : point.y;\r\n\t\t\tseries.zData[i] = point.z;\r\n\t\t\tseriesOptions.data[i] = point.options;\r\n\r\n\t\t\t// redraw\r\n\t\t\tseries.isDirty = series.isDirtyData = true;\r\n\t\t\tif (!series.fixedBox && series.hasCartesianSeries) { // #1906, #2320\r\n\t\t\t\tchart.isDirtyBox = true;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (seriesOptions.legendType === 'point') { // #1831, #1885\r\n\t\t\t\tchart.legend.destroyItem(point);\r\n\t\t\t}\r\n\t\t\tif (redraw) {\r\n\t\t\t\tchart.redraw(animation);\r\n\t\t\t}\r\n\t\t});\r\n\t},\r\n\r\n\t/**\r\n\t * Remove a point and optionally redraw the series and if necessary the axes\r\n\t * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call\r\n\t * @param {Boolean|Object} animation Whether to apply animation, and optionally animation\r\n\t *    configuration\r\n\t */\r\n\tremove: function (redraw, animation) {\r\n\t\tvar point = this,\r\n\t\t\tseries = point.series,\r\n\t\t\tpoints = series.points,\r\n\t\t\tchart = series.chart,\r\n\t\t\ti,\r\n\t\t\tdata = series.data;\r\n\r\n\t\tsetAnimation(animation, chart);\r\n\t\tredraw = pick(redraw, true);\r\n\r\n\t\t// fire the event with a default handler of removing the point\r\n\t\tpoint.firePointEvent('remove', null, function () {\r\n\r\n\t\t\t// splice all the parallel arrays\r\n\t\t\ti = inArray(point, data);\r\n\t\t\tif (data.length === points.length) {\r\n\t\t\t\tpoints.splice(i, 1);\t\t\t\r\n\t\t\t}\r\n\t\t\tdata.splice(i, 1);\r\n\t\t\tseries.options.data.splice(i, 1);\r\n\t\t\tseries.xData.splice(i, 1);\r\n\t\t\tseries.yData.splice(i, 1);\r\n\t\t\tseries.zData.splice(i, 1);\r\n\r\n\t\t\tpoint.destroy();\r\n\r\n\r\n\t\t\t// redraw\r\n\t\t\tseries.isDirty = true;\r\n\t\t\tseries.isDirtyData = true;\r\n\t\t\tif (redraw) {\r\n\t\t\t\tchart.redraw();\r\n\t\t\t}\r\n\t\t});\r\n\r\n\r\n\t},\r\n\r\n\t/**\r\n\t * Fire an event on the Point object. Must not be renamed to fireEvent, as this\r\n\t * causes a name clash in MooTools\r\n\t * @param {String} eventType\r\n\t * @param {Object} eventArgs Additional event arguments\r\n\t * @param {Function} defaultFunction Default event handler\r\n\t */\r\n\tfirePointEvent: function (eventType, eventArgs, defaultFunction) {\r\n\t\tvar point = this,\r\n\t\t\tseries = this.series,\r\n\t\t\tseriesOptions = series.options;\r\n\r\n\t\t// load event handlers on demand to save time on mouseover/out\r\n\t\tif (seriesOptions.point.events[eventType] || (point.options && point.options.events && point.options.events[eventType])) {\r\n\t\t\tthis.importEvents();\r\n\t\t}\r\n\r\n\t\t// add default handler if in selection mode\r\n\t\tif (eventType === 'click' && seriesOptions.allowPointSelect) {\r\n\t\t\tdefaultFunction = function (event) {\r\n\t\t\t\t// Control key is for Windows, meta (= Cmd key) for Mac, Shift for Opera\r\n\t\t\t\tpoint.select(null, event.ctrlKey || event.metaKey || event.shiftKey);\r\n\t\t\t};\r\n\t\t}\r\n\r\n\t\tfireEvent(this, eventType, eventArgs, defaultFunction);\r\n\t},\r\n\t/**\r\n\t * Import events from the series' and point's options. Only do it on\r\n\t * demand, to save processing time on hovering.\r\n\t */\r\n\timportEvents: function () {\r\n\t\tif (!this.hasImportedEvents) {\r\n\t\t\tvar point = this,\r\n\t\t\t\toptions = merge(point.series.options.point, point.options),\r\n\t\t\t\tevents = options.events,\r\n\t\t\t\teventType;\r\n\r\n\t\t\tpoint.events = events;\r\n\r\n\t\t\tfor (eventType in events) {\r\n\t\t\t\taddEvent(point, eventType, events[eventType]);\r\n\t\t\t}\r\n\t\t\tthis.hasImportedEvents = true;\r\n\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Set the point's state\r\n\t * @param {String} state\r\n\t */\r\n\tsetState: function (state) {\r\n\t\tvar point = this,\r\n\t\t\tplotX = point.plotX,\r\n\t\t\tplotY = point.plotY,\r\n\t\t\tseries = point.series,\r\n\t\t\tstateOptions = series.options.states,\r\n\t\t\tmarkerOptions = defaultPlotOptions[series.type].marker && series.options.marker,\r\n\t\t\tnormalDisabled = markerOptions && !markerOptions.enabled,\r\n\t\t\tmarkerStateOptions = markerOptions && markerOptions.states[state],\r\n\t\t\tstateDisabled = markerStateOptions && markerStateOptions.enabled === false,\r\n\t\t\tstateMarkerGraphic = series.stateMarkerGraphic,\r\n\t\t\tpointMarker = point.marker || {},\r\n\t\t\tchart = series.chart,\r\n\t\t\tradius,\r\n\t\t\tnewSymbol,\r\n\t\t\tpointAttr = point.pointAttr;\r\n\r\n\t\tstate = state || NORMAL_STATE; // empty string\r\n\r\n\t\tif (\r\n\t\t\t\t// already has this state\r\n\t\t\t\tstate === point.state ||\r\n\t\t\t\t// selected points don't respond to hover\r\n\t\t\t\t(point.selected && state !== SELECT_STATE) ||\r\n\t\t\t\t// series' state options is disabled\r\n\t\t\t\t(stateOptions[state] && stateOptions[state].enabled === false) ||\r\n\t\t\t\t// point marker's state options is disabled\r\n\t\t\t\t(state && (stateDisabled || (normalDisabled && !markerStateOptions.enabled)))\r\n\r\n\t\t\t) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t// apply hover styles to the existing point\r\n\t\tif (point.graphic) {\r\n\t\t\tradius = markerOptions && point.graphic.symbolName && pointAttr[state].r;\r\n\t\t\tpoint.graphic.attr(merge(\r\n\t\t\t\tpointAttr[state],\r\n\t\t\t\tradius ? { // new symbol attributes (#507, #612)\r\n\t\t\t\t\tx: plotX - radius,\r\n\t\t\t\t\ty: plotY - radius,\r\n\t\t\t\t\twidth: 2 * radius,\r\n\t\t\t\t\theight: 2 * radius\r\n\t\t\t\t} : {}\r\n\t\t\t));\r\n\t\t} else {\r\n\t\t\t// if a graphic is not applied to each point in the normal state, create a shared\r\n\t\t\t// graphic for the hover state\r\n\t\t\tif (state && markerStateOptions) {\r\n\t\t\t\tradius = markerStateOptions.radius;\r\n\t\t\t\tnewSymbol = pointMarker.symbol || series.symbol;\r\n\r\n\t\t\t\t// If the point has another symbol than the previous one, throw away the \r\n\t\t\t\t// state marker graphic and force a new one (#1459)\r\n\t\t\t\tif (stateMarkerGraphic && stateMarkerGraphic.currentSymbol !== newSymbol) {\t\t\t\t\r\n\t\t\t\t\tstateMarkerGraphic = stateMarkerGraphic.destroy();\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Add a new state marker graphic\r\n\t\t\t\tif (!stateMarkerGraphic) {\r\n\t\t\t\t\tseries.stateMarkerGraphic = stateMarkerGraphic = chart.renderer.symbol(\r\n\t\t\t\t\t\tnewSymbol,\r\n\t\t\t\t\t\tplotX - radius,\r\n\t\t\t\t\t\tplotY - radius,\r\n\t\t\t\t\t\t2 * radius,\r\n\t\t\t\t\t\t2 * radius\r\n\t\t\t\t\t)\r\n\t\t\t\t\t.attr(pointAttr[state])\r\n\t\t\t\t\t.add(series.markerGroup);\r\n\t\t\t\t\tstateMarkerGraphic.currentSymbol = newSymbol;\r\n\t\t\t\t\r\n\t\t\t\t// Move the existing graphic\r\n\t\t\t\t} else {\r\n\t\t\t\t\tstateMarkerGraphic.attr({ // #1054\r\n\t\t\t\t\t\tx: plotX - radius,\r\n\t\t\t\t\t\ty: plotY - radius\r\n\t\t\t\t\t});\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif (stateMarkerGraphic) {\r\n\t\t\t\tstateMarkerGraphic[state && chart.isInsidePlot(plotX, plotY) ? 'show' : 'hide']();\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tpoint.state = state;\r\n\t}\r\n};\r\n\r\n/**\r\n * @classDescription The base function which all other series types inherit from. The data in the series is stored\r\n * in various arrays.\r\n *\r\n * - First, series.options.data contains all the original config options for\r\n * each point whether added by options or methods like series.addPoint.\r\n * - Next, series.data contains those values converted to points, but in case the series data length\r\n * exceeds the cropThreshold, or if the data is grouped, series.data doesn't contain all the points. It\r\n * only contains the points that have been created on demand.\r\n * - Then there's series.points that contains all currently visible point objects. In case of cropping,\r\n * the cropped-away points are not part of this array. The series.points array starts at series.cropStart\r\n * compared to series.data and series.options.data. If however the series data is grouped, these can't\r\n * be correlated one to one.\r\n * - series.xData and series.processedXData contain clean x values, equivalent to series.data and series.points.\r\n * - series.yData and series.processedYData contain clean x values, equivalent to series.data and series.points.\r\n *\r\n * @param {Object} chart\r\n * @param {Object} options\r\n */\r\nvar Series = function () {};\r\n\r\nSeries.prototype = {\r\n\r\n\tisCartesian: true,\r\n\ttype: 'line',\r\n\tpointClass: Point,\r\n\tsorted: true, // requires the data to be sorted\r\n\trequireSorting: true,\r\n\tpointAttrToOptions: { // mapping between SVG attributes and the corresponding options\r\n\t\tstroke: 'lineColor',\r\n\t\t'stroke-width': 'lineWidth',\r\n\t\tfill: 'fillColor',\r\n\t\tr: 'radius'\r\n\t},\r\n\tcolorCounter: 0,\r\n\tinit: function (chart, options) {\r\n\t\tvar series = this,\r\n\t\t\teventType,\r\n\t\t\tevents,\r\n\t\t\tchartSeries = chart.series;\r\n\r\n\t\tseries.chart = chart;\r\n\t\tseries.options = options = series.setOptions(options); // merge with plotOptions\r\n\t\tseries.linkedSeries = [];\r\n\r\n\t\t// bind the axes\r\n\t\tseries.bindAxes();\r\n\r\n\t\t// set some variables\r\n\t\textend(series, {\r\n\t\t\tname: options.name,\r\n\t\t\tstate: NORMAL_STATE,\r\n\t\t\tpointAttr: {},\r\n\t\t\tvisible: options.visible !== false, // true by default\r\n\t\t\tselected: options.selected === true // false by default\r\n\t\t});\r\n\t\t\r\n\t\t// special\r\n\t\tif (useCanVG) {\r\n\t\t\toptions.animation = false;\r\n\t\t}\r\n\r\n\t\t// register event listeners\r\n\t\tevents = options.events;\r\n\t\tfor (eventType in events) {\r\n\t\t\taddEvent(series, eventType, events[eventType]);\r\n\t\t}\r\n\t\tif (\r\n\t\t\t(events && events.click) ||\r\n\t\t\t(options.point && options.point.events && options.point.events.click) ||\r\n\t\t\toptions.allowPointSelect\r\n\t\t) {\r\n\t\t\tchart.runTrackerClick = true;\r\n\t\t}\r\n\r\n\t\tseries.getColor();\r\n\t\tseries.getSymbol();\r\n\r\n\t\t// set the data\r\n\t\tseries.setData(options.data, false);\r\n\t\t\r\n\t\t// Mark cartesian\r\n\t\tif (series.isCartesian) {\r\n\t\t\tchart.hasCartesianSeries = true;\r\n\t\t}\r\n\r\n\t\t// Register it in the chart\r\n\t\tchartSeries.push(series);\r\n\t\tseries._i = chartSeries.length - 1;\r\n\t\t\r\n\t\t// Sort series according to index option (#248, #1123)\r\n\t\tstableSort(chartSeries, function (a, b) {\r\n\t\t\treturn pick(a.options.index, a._i) - pick(b.options.index, a._i);\r\n\t\t});\r\n\t\teach(chartSeries, function (series, i) {\r\n\t\t\tseries.index = i;\r\n\t\t\tseries.name = series.name || 'Series ' + (i + 1);\r\n\t\t});\r\n\r\n\t},\r\n\t\r\n\t/**\r\n\t * Set the xAxis and yAxis properties of cartesian series, and register the series\r\n\t * in the axis.series array\r\n\t */\r\n\tbindAxes: function () {\r\n\t\tvar series = this,\r\n\t\t\tseriesOptions = series.options,\r\n\t\t\tchart = series.chart,\r\n\t\t\taxisOptions;\r\n\t\t\t\r\n\t\tif (series.isCartesian) {\r\n\t\t\t\r\n\t\t\teach(['xAxis', 'yAxis'], function (AXIS) { // repeat for xAxis and yAxis\r\n\t\t\t\t\r\n\t\t\t\teach(chart[AXIS], function (axis) { // loop through the chart's axis objects\r\n\t\t\t\t\t\r\n\t\t\t\t\taxisOptions = axis.options;\r\n\t\t\t\t\t\r\n\t\t\t\t\t// apply if the series xAxis or yAxis option mathches the number of the \r\n\t\t\t\t\t// axis, or if undefined, use the first axis\r\n\t\t\t\t\tif ((seriesOptions[AXIS] === axisOptions.index) ||\r\n\t\t\t\t\t\t\t(seriesOptions[AXIS] !== UNDEFINED && seriesOptions[AXIS] === axisOptions.id) ||\r\n\t\t\t\t\t\t\t(seriesOptions[AXIS] === UNDEFINED && axisOptions.index === 0)) {\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t// register this series in the axis.series lookup\r\n\t\t\t\t\t\taxis.series.push(series);\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t// set this series.xAxis or series.yAxis reference\r\n\t\t\t\t\t\tseries[AXIS] = axis;\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t// mark dirty for redraw\r\n\t\t\t\t\t\taxis.isDirty = true;\r\n\t\t\t\t\t}\r\n\t\t\t\t});\r\n\r\n\t\t\t\t// The series needs an X and an Y axis\r\n\t\t\t\tif (!series[AXIS]) {\r\n\t\t\t\t\terror(18, true);\r\n\t\t\t\t}\r\n\r\n\t\t\t});\r\n\t\t}\r\n\t},\r\n\r\n\r\n\t/**\r\n\t * Return an auto incremented x value based on the pointStart and pointInterval options.\r\n\t * This is only used if an x value is not given for the point that calls autoIncrement.\r\n\t */\r\n\tautoIncrement: function () {\r\n\t\tvar series = this,\r\n\t\t\toptions = series.options,\r\n\t\t\txIncrement = series.xIncrement;\r\n\r\n\t\txIncrement = pick(xIncrement, options.pointStart, 0);\r\n\r\n\t\tseries.pointInterval = pick(series.pointInterval, options.pointInterval, 1);\r\n\r\n\t\tseries.xIncrement = xIncrement + series.pointInterval;\r\n\t\treturn xIncrement;\r\n\t},\r\n\r\n\t/**\r\n\t * Divide the series data into segments divided by null values.\r\n\t */\r\n\tgetSegments: function () {\r\n\t\tvar series = this,\r\n\t\t\tlastNull = -1,\r\n\t\t\tsegments = [],\r\n\t\t\ti,\r\n\t\t\tpoints = series.points,\r\n\t\t\tpointsLength = points.length;\r\n\r\n\t\tif (pointsLength) { // no action required for []\r\n\t\t\t\r\n\t\t\t// if connect nulls, just remove null points\r\n\t\t\tif (series.options.connectNulls) {\r\n\t\t\t\ti = pointsLength;\r\n\t\t\t\twhile (i--) {\r\n\t\t\t\t\tif (points[i].y === null) {\r\n\t\t\t\t\t\tpoints.splice(i, 1);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tif (points.length) {\r\n\t\t\t\t\tsegments = [points];\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t// else, split on null points\r\n\t\t\t} else {\r\n\t\t\t\teach(points, function (point, i) {\r\n\t\t\t\t\tif (point.y === null) {\r\n\t\t\t\t\t\tif (i > lastNull + 1) {\r\n\t\t\t\t\t\t\tsegments.push(points.slice(lastNull + 1, i));\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tlastNull = i;\r\n\t\t\t\t\t} else if (i === pointsLength - 1) { // last value\r\n\t\t\t\t\t\tsegments.push(points.slice(lastNull + 1, i + 1));\r\n\t\t\t\t\t}\r\n\t\t\t\t});\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// register it\r\n\t\tseries.segments = segments;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Set the series options by merging from the options tree\r\n\t * @param {Object} itemOptions\r\n\t */\r\n\tsetOptions: function (itemOptions) {\r\n\t\tvar chart = this.chart,\r\n\t\t\tchartOptions = chart.options,\r\n\t\t\tplotOptions = chartOptions.plotOptions,\r\n\t\t\ttypeOptions = plotOptions[this.type],\r\n\t\t\toptions;\r\n\r\n\t\tthis.userOptions = itemOptions;\r\n\r\n\t\toptions = merge(\r\n\t\t\ttypeOptions,\r\n\t\t\tplotOptions.series,\r\n\t\t\titemOptions\r\n\t\t);\r\n\t\t\r\n\t\t// the tooltip options are merged between global and series specific options\r\n\t\tthis.tooltipOptions = merge(chartOptions.tooltip, options.tooltip);\r\n\t\t\r\n\t\t// Delte marker object if not allowed (#1125)\r\n\t\tif (typeOptions.marker === null) {\r\n\t\t\tdelete options.marker;\r\n\t\t}\r\n\t\t\r\n\t\treturn options;\r\n\r\n\t},\r\n\t/**\r\n\t * Get the series' color\r\n\t */\r\n\tgetColor: function () {\r\n\t\tvar options = this.options,\r\n\t\t\tuserOptions = this.userOptions,\r\n\t\t\tdefaultColors = this.chart.options.colors,\r\n\t\t\tcounters = this.chart.counters,\r\n\t\t\tcolor,\r\n\t\t\tcolorIndex;\r\n\r\n\t\tcolor = options.color || defaultPlotOptions[this.type].color;\r\n\r\n\t\tif (!color && !options.colorByPoint) {\r\n\t\t\tif (defined(userOptions._colorIndex)) { // after Series.update()\r\n\t\t\t\tcolorIndex = userOptions._colorIndex;\r\n\t\t\t} else {\r\n\t\t\t\tuserOptions._colorIndex = counters.color;\r\n\t\t\t\tcolorIndex = counters.color++;\r\n\t\t\t}\r\n\t\t\tcolor = defaultColors[colorIndex];\r\n\t\t}\r\n\t\t\r\n\t\tthis.color = color;\r\n\t\tcounters.wrapColor(defaultColors.length);\r\n\t},\r\n\t/**\r\n\t * Get the series' symbol\r\n\t */\r\n\tgetSymbol: function () {\r\n\t\tvar series = this,\r\n\t\t\tuserOptions = series.userOptions,\r\n\t\t\tseriesMarkerOption = series.options.marker,\r\n\t\t\tchart = series.chart,\r\n\t\t\tdefaultSymbols = chart.options.symbols,\r\n\t\t\tcounters = chart.counters,\r\n\t\t\tsymbolIndex;\r\n\r\n\t\tseries.symbol = seriesMarkerOption.symbol;\r\n\t\tif (!series.symbol) {\r\n\t\t\tif (defined(userOptions._symbolIndex)) { // after Series.update()\r\n\t\t\t\tsymbolIndex = userOptions._symbolIndex;\r\n\t\t\t} else {\r\n\t\t\t\tuserOptions._symbolIndex = counters.symbol;\r\n\t\t\t\tsymbolIndex = counters.symbol++;\r\n\t\t\t}\r\n\t\t\tseries.symbol = defaultSymbols[symbolIndex];\r\n\t\t}\r\n\r\n\t\t// don't substract radius in image symbols (#604)\r\n\t\tif (/^url/.test(series.symbol)) {\r\n\t\t\tseriesMarkerOption.radius = 0;\r\n\t\t}\r\n\t\tcounters.wrapSymbol(defaultSymbols.length);\r\n\t},\r\n\r\n\t/**\r\n\t * Get the series' symbol in the legend. This method should be overridable to create custom \r\n\t * symbols through Highcharts.seriesTypes[type].prototype.drawLegendSymbols.\r\n\t * \r\n\t * @param {Object} legend The legend object\r\n\t */\r\n\tdrawLegendSymbol: function (legend) {\r\n\t\t\r\n\t\tvar options = this.options,\r\n\t\t\tmarkerOptions = options.marker,\r\n\t\t\tradius,\r\n\t\t\tlegendOptions = legend.options,\r\n\t\t\tlegendSymbol,\r\n\t\t\tsymbolWidth = legendOptions.symbolWidth,\r\n\t\t\trenderer = this.chart.renderer,\r\n\t\t\tlegendItemGroup = this.legendGroup,\r\n\t\t\tverticalCenter = legend.baseline - mathRound(renderer.fontMetrics(legendOptions.itemStyle.fontSize).b * 0.3),\r\n\t\t\tattr;\r\n\t\t\t\r\n\t\t// Draw the line\r\n\t\tif (options.lineWidth) {\r\n\t\t\tattr = {\r\n\t\t\t\t'stroke-width': options.lineWidth\r\n\t\t\t};\r\n\t\t\tif (options.dashStyle) {\r\n\t\t\t\tattr.dashstyle = options.dashStyle;\r\n\t\t\t}\r\n\t\t\tthis.legendLine = renderer.path([\r\n\t\t\t\tM,\r\n\t\t\t\t0,\r\n\t\t\t\tverticalCenter,\r\n\t\t\t\tL,\r\n\t\t\t\tsymbolWidth,\r\n\t\t\t\tverticalCenter\r\n\t\t\t])\r\n\t\t\t.attr(attr)\r\n\t\t\t.add(legendItemGroup);\r\n\t\t}\r\n\t\t\r\n\t\t// Draw the marker\r\n\t\tif (markerOptions && markerOptions.enabled) {\r\n\t\t\tradius = markerOptions.radius;\r\n\t\t\tthis.legendSymbol = legendSymbol = renderer.symbol(\r\n\t\t\t\tthis.symbol,\r\n\t\t\t\t(symbolWidth / 2) - radius,\r\n\t\t\t\tverticalCenter - radius,\r\n\t\t\t\t2 * radius,\r\n\t\t\t\t2 * radius\r\n\t\t\t)\r\n\t\t\t.add(legendItemGroup);\r\n\t\t\tlegendSymbol.isMarker = true;\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Add a point dynamically after chart load time\r\n\t * @param {Object} options Point options as given in series.data\r\n\t * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call\r\n\t * @param {Boolean} shift If shift is true, a point is shifted off the start\r\n\t *    of the series as one is appended to the end.\r\n\t * @param {Boolean|Object} animation Whether to apply animation, and optionally animation\r\n\t *    configuration\r\n\t */\r\n\taddPoint: function (options, redraw, shift, animation) {\r\n\t\tvar series = this,\r\n\t\t\tseriesOptions = series.options,\r\n\t\t\tdata = series.data,\r\n\t\t\tgraph = series.graph,\r\n\t\t\tarea = series.area,\r\n\t\t\tchart = series.chart,\r\n\t\t\txData = series.xData,\r\n\t\t\tyData = series.yData,\r\n\t\t\tzData = series.zData,\r\n\t\t\tnames = series.names,\r\n\t\t\tcurrentShift = (graph && graph.shift) || 0,\r\n\t\t\tdataOptions = seriesOptions.data,\r\n\t\t\tpoint,\r\n\t\t\tisInTheMiddle,\r\n\t\t\tx,\r\n\t\t\ti;\r\n\r\n\t\tsetAnimation(animation, chart);\r\n\r\n\t\t// Make graph animate sideways\r\n\t\tif (shift) {\r\n\t\t\teach([graph, area, series.graphNeg, series.areaNeg], function (shape) {\r\n\t\t\t\tif (shape) {\r\n\t\t\t\t\tshape.shift = currentShift + 1;\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t}\r\n\t\tif (area) {\r\n\t\t\tarea.isArea = true; // needed in animation, both with and without shift\r\n\t\t}\r\n\t\t\r\n\t\t// Optional redraw, defaults to true\r\n\t\tredraw = pick(redraw, true);\r\n\r\n\t\t// Get options and push the point to xData, yData and series.options. In series.generatePoints\r\n\t\t// the Point instance will be created on demand and pushed to the series.data array.\r\n\t\tpoint = { series: series };\r\n\t\tseries.pointClass.prototype.applyOptions.apply(point, [options]);\r\n\t\tx = point.x;\r\n\r\n\t\t// Get the insertion point\r\n\t\ti = xData.length;\r\n\t\tif (series.requireSorting && x < xData[i - 1]) {\r\n\t\t\tisInTheMiddle = true;\r\n\t\t\twhile (i && xData[i - 1] > x) {\r\n\t\t\t\ti--;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\txData.splice(i, 0, x);\r\n\t\tyData.splice(i, 0, series.toYData ? series.toYData(point) : point.y);\r\n\t\tzData.splice(i, 0, point.z);\r\n\t\tif (names) {\r\n\t\t\tnames[x] = point.name;\r\n\t\t}\r\n\t\tdataOptions.splice(i, 0, options);\r\n\r\n\t\tif (isInTheMiddle) {\r\n\t\t\tseries.data.splice(i, 0, null);\r\n\t\t\tseries.processData();\r\n\t\t}\r\n\t\t\r\n\t\t// Generate points to be added to the legend (#1329) \r\n\t\tif (seriesOptions.legendType === 'point') {\r\n\t\t\tseries.generatePoints();\r\n\t\t}\r\n\r\n\t\t// Shift the first point off the parallel arrays\r\n\t\t// todo: consider series.removePoint(i) method\r\n\t\tif (shift) {\r\n\t\t\tif (data[0] && data[0].remove) {\r\n\t\t\t\tdata[0].remove(false);\r\n\t\t\t} else {\r\n\t\t\t\tdata.shift();\r\n\t\t\t\txData.shift();\r\n\t\t\t\tyData.shift();\r\n\t\t\t\tzData.shift();\r\n\t\t\t\tdataOptions.shift();\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// redraw\r\n\t\tseries.isDirty = true;\r\n\t\tseries.isDirtyData = true;\r\n\t\tif (redraw) {\r\n\t\t\tseries.getAttribs(); // #1937\r\n\t\t\tchart.redraw();\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Replace the series data with a new set of data\r\n\t * @param {Object} data\r\n\t * @param {Object} redraw\r\n\t */\r\n\tsetData: function (data, redraw) {\r\n\t\tvar series = this,\r\n\t\t\toldData = series.points,\r\n\t\t\toptions = series.options,\r\n\t\t\tchart = series.chart,\r\n\t\t\tfirstPoint = null,\r\n\t\t\txAxis = series.xAxis,\r\n\t\t\tnames = xAxis && xAxis.categories && !xAxis.categories.length ? [] : null,\r\n\t\t\ti;\r\n\r\n\t\t// reset properties\r\n\t\tseries.xIncrement = null;\r\n\t\tseries.pointRange = xAxis && xAxis.categories ? 1 : options.pointRange;\r\n\r\n\t\tseries.colorCounter = 0; // for series with colorByPoint (#1547)\r\n\t\t\r\n\t\t// parallel arrays\r\n\t\tvar xData = [],\r\n\t\t\tyData = [],\r\n\t\t\tzData = [],\r\n\t\t\tdataLength = data ? data.length : [],\r\n\t\t\tturboThreshold = pick(options.turboThreshold, 1000),\r\n\t\t\tpt,\r\n\t\t\tpointArrayMap = series.pointArrayMap,\r\n\t\t\tvalueCount = pointArrayMap && pointArrayMap.length,\r\n\t\t\thasToYData = !!series.toYData;\r\n\r\n\t\t// In turbo mode, only one- or twodimensional arrays of numbers are allowed. The\r\n\t\t// first value is tested, and we assume that all the rest are defined the same\r\n\t\t// way. Although the 'for' loops are similar, they are repeated inside each\r\n\t\t// if-else conditional for max performance.\r\n\t\tif (turboThreshold && dataLength > turboThreshold) { \r\n\t\t\t\r\n\t\t\t// find the first non-null point\r\n\t\t\ti = 0;\r\n\t\t\twhile (firstPoint === null && i < dataLength) {\r\n\t\t\t\tfirstPoint = data[i];\r\n\t\t\t\ti++;\r\n\t\t\t}\r\n\t\t\r\n\t\t\r\n\t\t\tif (isNumber(firstPoint)) { // assume all points are numbers\r\n\t\t\t\tvar x = pick(options.pointStart, 0),\r\n\t\t\t\t\tpointInterval = pick(options.pointInterval, 1);\r\n\r\n\t\t\t\tfor (i = 0; i < dataLength; i++) {\r\n\t\t\t\t\txData[i] = x;\r\n\t\t\t\t\tyData[i] = data[i];\r\n\t\t\t\t\tx += pointInterval;\r\n\t\t\t\t}\r\n\t\t\t\tseries.xIncrement = x;\r\n\t\t\t} else if (isArray(firstPoint)) { // assume all points are arrays\r\n\t\t\t\tif (valueCount) { // [x, low, high] or [x, o, h, l, c]\r\n\t\t\t\t\tfor (i = 0; i < dataLength; i++) {\r\n\t\t\t\t\t\tpt = data[i];\r\n\t\t\t\t\t\txData[i] = pt[0];\r\n\t\t\t\t\t\tyData[i] = pt.slice(1, valueCount + 1);\r\n\t\t\t\t\t}\r\n\t\t\t\t} else { // [x, y]\r\n\t\t\t\t\tfor (i = 0; i < dataLength; i++) {\r\n\t\t\t\t\t\tpt = data[i];\r\n\t\t\t\t\t\txData[i] = pt[0];\r\n\t\t\t\t\t\tyData[i] = pt[1];\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\terror(12); // Highcharts expects configs to be numbers or arrays in turbo mode\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tfor (i = 0; i < dataLength; i++) {\r\n\t\t\t\tif (data[i] !== UNDEFINED) { // stray commas in oldIE\r\n\t\t\t\t\tpt = { series: series };\r\n\t\t\t\t\tseries.pointClass.prototype.applyOptions.apply(pt, [data[i]]);\r\n\t\t\t\t\txData[i] = pt.x;\r\n\t\t\t\t\tyData[i] = hasToYData ? series.toYData(pt) : pt.y;\r\n\t\t\t\t\tzData[i] = pt.z;\r\n\t\t\t\t\tif (names && pt.name) {\r\n\t\t\t\t\t\tnames[pt.x] = pt.name; // #2046\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// Forgetting to cast strings to numbers is a common caveat when handling CSV or JSON\t\t\r\n\t\tif (isString(yData[0])) {\r\n\t\t\terror(14, true);\r\n\t\t} \r\n\r\n\t\tseries.data = [];\r\n\t\tseries.options.data = data;\r\n\t\tseries.xData = xData;\r\n\t\tseries.yData = yData;\r\n\t\tseries.zData = zData;\r\n\t\tseries.names = names;\r\n\r\n\t\t// destroy old points\r\n\t\ti = (oldData && oldData.length) || 0;\r\n\t\twhile (i--) {\r\n\t\t\tif (oldData[i] && oldData[i].destroy) {\r\n\t\t\t\toldData[i].destroy();\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// reset minRange (#878)\r\n\t\tif (xAxis) {\r\n\t\t\txAxis.minRange = xAxis.userMinRange;\r\n\t\t}\r\n\r\n\t\t// redraw\r\n\t\tseries.isDirty = series.isDirtyData = chart.isDirtyBox = true;\r\n\t\tif (pick(redraw, true)) {\r\n\t\t\tchart.redraw(false);\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Remove a series and optionally redraw the chart\r\n\t *\r\n\t * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call\r\n\t * @param {Boolean|Object} animation Whether to apply animation, and optionally animation\r\n\t *    configuration\r\n\t */\r\n\r\n\tremove: function (redraw, animation) {\r\n\t\tvar series = this,\r\n\t\t\tchart = series.chart;\r\n\t\tredraw = pick(redraw, true);\r\n\r\n\t\tif (!series.isRemoving) {  /* prevent triggering native event in jQuery\r\n\t\t\t\t(calling the remove function from the remove event) */\r\n\t\t\tseries.isRemoving = true;\r\n\r\n\t\t\t// fire the event with a default handler of removing the point\r\n\t\t\tfireEvent(series, 'remove', null, function () {\r\n\r\n\r\n\t\t\t\t// destroy elements\r\n\t\t\t\tseries.destroy();\r\n\r\n\r\n\t\t\t\t// redraw\r\n\t\t\t\tchart.isDirtyLegend = chart.isDirtyBox = true;\r\n\t\t\t\tchart.linkSeries();\r\n\t\t\t\t\r\n\t\t\t\tif (redraw) {\r\n\t\t\t\t\tchart.redraw(animation);\r\n\t\t\t\t}\r\n\t\t\t});\r\n\r\n\t\t}\r\n\t\tseries.isRemoving = false;\r\n\t},\r\n\r\n\t/**\r\n\t * Process the data by cropping away unused data points if the series is longer\r\n\t * than the crop threshold. This saves computing time for lage series.\r\n\t */\r\n\tprocessData: function (force) {\r\n\t\tvar series = this,\r\n\t\t\tprocessedXData = series.xData, // copied during slice operation below\r\n\t\t\tprocessedYData = series.yData,\r\n\t\t\tdataLength = processedXData.length,\r\n\t\t\tcroppedData,\r\n\t\t\tcropStart = 0,\r\n\t\t\tcropped,\r\n\t\t\tdistance,\r\n\t\t\tclosestPointRange,\r\n\t\t\txAxis = series.xAxis,\r\n\t\t\ti, // loop variable\r\n\t\t\toptions = series.options,\r\n\t\t\tcropThreshold = options.cropThreshold,\r\n\t\t\tisCartesian = series.isCartesian;\r\n\r\n\t\t// If the series data or axes haven't changed, don't go through this. Return false to pass\r\n\t\t// the message on to override methods like in data grouping. \r\n\t\tif (isCartesian && !series.isDirty && !xAxis.isDirty && !series.yAxis.isDirty && !force) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\t\r\n\r\n\t\t// optionally filter out points outside the plot area\r\n\t\tif (isCartesian && series.sorted && (!cropThreshold || dataLength > cropThreshold || series.forceCrop)) {\r\n\t\t\tvar min = xAxis.min,\r\n\t\t\t\tmax = xAxis.max;\r\n\r\n\t\t\t// it's outside current extremes\r\n\t\t\tif (processedXData[dataLength - 1] < min || processedXData[0] > max) {\r\n\t\t\t\tprocessedXData = [];\r\n\t\t\t\tprocessedYData = [];\r\n\t\t\t\r\n\t\t\t// only crop if it's actually spilling out\r\n\t\t\t} else if (processedXData[0] < min || processedXData[dataLength - 1] > max) {\r\n\t\t\t\tcroppedData = this.cropData(series.xData, series.yData, min, max);\r\n\t\t\t\tprocessedXData = croppedData.xData;\r\n\t\t\t\tprocessedYData = croppedData.yData;\r\n\t\t\t\tcropStart = croppedData.start;\r\n\t\t\t\tcropped = true;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t\r\n\t\t// Find the closest distance between processed points\r\n\t\tfor (i = processedXData.length - 1; i >= 0; i--) {\r\n\t\t\tdistance = processedXData[i] - processedXData[i - 1];\r\n\t\t\tif (distance > 0 && (closestPointRange === UNDEFINED || distance < closestPointRange)) {\r\n\t\t\t\tclosestPointRange = distance;\r\n\r\n\t\t\t// Unsorted data is not supported by the line tooltip, as well as data grouping and \r\n\t\t\t// navigation in Stock charts (#725) and width calculation of columns (#1900)\r\n\t\t\t} else if (distance < 0 && series.requireSorting) {\r\n\t\t\t\terror(15);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Record the properties\r\n\t\tseries.cropped = cropped; // undefined or true\r\n\t\tseries.cropStart = cropStart;\r\n\t\tseries.processedXData = processedXData;\r\n\t\tseries.processedYData = processedYData;\r\n\r\n\t\tif (options.pointRange === null) { // null means auto, as for columns, candlesticks and OHLC\r\n\t\t\tseries.pointRange = closestPointRange || 1;\r\n\t\t}\r\n\t\tseries.closestPointRange = closestPointRange;\r\n\t\t\r\n\t},\r\n\r\n\t/**\r\n\t * Iterate over xData and crop values between min and max. Returns object containing crop start/end\r\n\t * cropped xData with corresponding part of yData, dataMin and dataMax within the cropped range\r\n\t */\r\n\tcropData: function (xData, yData, min, max) {\r\n\t\tvar dataLength = xData.length,\r\n\t\t\tcropStart = 0,\r\n\t\t\tcropEnd = dataLength,\r\n\t\t\tcropShoulder = pick(this.cropShoulder, 1), // line-type series need one point outside\r\n\t\t\ti;\r\n\r\n\t\t// iterate up to find slice start\r\n\t\tfor (i = 0; i < dataLength; i++) {\r\n\t\t\tif (xData[i] >= min) {\r\n\t\t\t\tcropStart = mathMax(0, i - cropShoulder);\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// proceed to find slice end\r\n\t\tfor (; i < dataLength; i++) {\r\n\t\t\tif (xData[i] > max) {\r\n\t\t\t\tcropEnd = i + cropShoulder;\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn {\r\n\t\t\txData: xData.slice(cropStart, cropEnd),\r\n\t\t\tyData: yData.slice(cropStart, cropEnd),\r\n\t\t\tstart: cropStart,\r\n\t\t\tend: cropEnd\r\n\t\t};\r\n\t},\r\n\r\n\r\n\t/**\r\n\t * Generate the data point after the data has been processed by cropping away\r\n\t * unused points and optionally grouped in Highcharts Stock.\r\n\t */\r\n\tgeneratePoints: function () {\r\n\t\tvar series = this,\r\n\t\t\toptions = series.options,\r\n\t\t\tdataOptions = options.data,\r\n\t\t\tdata = series.data,\r\n\t\t\tdataLength,\r\n\t\t\tprocessedXData = series.processedXData,\r\n\t\t\tprocessedYData = series.processedYData,\r\n\t\t\tpointClass = series.pointClass,\r\n\t\t\tprocessedDataLength = processedXData.length,\r\n\t\t\tcropStart = series.cropStart || 0,\r\n\t\t\tcursor,\r\n\t\t\thasGroupedData = series.hasGroupedData,\r\n\t\t\tpoint,\r\n\t\t\tpoints = [],\r\n\t\t\ti;\r\n\r\n\t\tif (!data && !hasGroupedData) {\r\n\t\t\tvar arr = [];\r\n\t\t\tarr.length = dataOptions.length;\r\n\t\t\tdata = series.data = arr;\r\n\t\t}\r\n\r\n\t\tfor (i = 0; i < processedDataLength; i++) {\r\n\t\t\tcursor = cropStart + i;\r\n\t\t\tif (!hasGroupedData) {\r\n\t\t\t\tif (data[cursor]) {\r\n\t\t\t\t\tpoint = data[cursor];\r\n\t\t\t\t} else if (dataOptions[cursor] !== UNDEFINED) { // #970\r\n\t\t\t\t\tdata[cursor] = point = (new pointClass()).init(series, dataOptions[cursor], processedXData[i]);\r\n\t\t\t\t}\r\n\t\t\t\tpoints[i] = point;\r\n\t\t\t} else {\r\n\t\t\t\t// splat the y data in case of ohlc data array\r\n\t\t\t\tpoints[i] = (new pointClass()).init(series, [processedXData[i]].concat(splat(processedYData[i])));\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Hide cropped-away points - this only runs when the number of points is above cropThreshold, or when\r\n\t\t// swithching view from non-grouped data to grouped data (#637)\t\r\n\t\tif (data && (processedDataLength !== (dataLength = data.length) || hasGroupedData)) {\r\n\t\t\tfor (i = 0; i < dataLength; i++) {\r\n\t\t\t\tif (i === cropStart && !hasGroupedData) { // when has grouped data, clear all points\r\n\t\t\t\t\ti += processedDataLength;\r\n\t\t\t\t}\r\n\t\t\t\tif (data[i]) {\r\n\t\t\t\t\tdata[i].destroyElements();\r\n\t\t\t\t\tdata[i].plotX = UNDEFINED; // #1003\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tseries.data = data;\r\n\t\tseries.points = points;\r\n\t},\r\n\r\n\t/**\r\n\t * Adds series' points value to corresponding stack\r\n\t */\r\n\tsetStackedPoints: function () {\r\n\t\tif (!this.options.stacking || (this.visible !== true && this.chart.options.chart.ignoreHiddenSeries !== false)) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tvar series = this,\r\n\t\t\txData = series.processedXData,\r\n\t\t\tyData = series.processedYData,\r\n\t\t\tstackedYData = [],\r\n\t\t\tyDataLength = yData.length,\r\n\t\t\tseriesOptions = series.options,\r\n\t\t\tthreshold = seriesOptions.threshold,\r\n\t\t\tstackOption = seriesOptions.stack,\r\n\t\t\tstacking = seriesOptions.stacking,\r\n\t\t\tstackKey = series.stackKey,\r\n\t\t\tnegKey = '-' + stackKey,\r\n\t\t\tnegStacks = series.negStacks,\r\n\t\t\tyAxis = series.yAxis,\r\n\t\t\tstacks = yAxis.stacks,\r\n\t\t\toldStacks = yAxis.oldStacks,\r\n\t\t\tisNegative,\r\n\t\t\tstack,\r\n\t\t\tother,\r\n\t\t\tkey,\r\n\t\t\ti,\r\n\t\t\tx,\r\n\t\t\ty;\r\n\r\n\t\t// loop over the non-null y values and read them into a local array\r\n\t\tfor (i = 0; i < yDataLength; i++) {\r\n\t\t\tx = xData[i];\r\n\t\t\ty = yData[i];\r\n\r\n\t\t\t// Read stacked values into a stack based on the x value,\r\n\t\t\t// the sign of y and the stack key. Stacking is also handled for null values (#739)\r\n\t\t\tisNegative = negStacks && y < threshold;\r\n\t\t\tkey = isNegative ? negKey : stackKey;\r\n\r\n\t\t\t// Create empty object for this stack if it doesn't exist yet\r\n\t\t\tif (!stacks[key]) {\r\n\t\t\t\tstacks[key] = {};\r\n\t\t\t}\r\n\r\n\t\t\t// Initialize StackItem for this x\r\n\t\t\tif (!stacks[key][x]) {\r\n\t\t\t\tif (oldStacks[key] && oldStacks[key][x]) {\r\n\t\t\t\t\tstacks[key][x] = oldStacks[key][x];\r\n\t\t\t\t\tstacks[key][x].total = null;\r\n\t\t\t\t} else {\r\n\t\t\t\t\tstacks[key][x] = new StackItem(yAxis, yAxis.options.stackLabels, isNegative, x, stackOption, stacking);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// If the StackItem doesn't exist, create it first\r\n\t\t\tstack = stacks[key][x];\r\n\t\t\tstack.points[series.index] = [stack.cum || 0];\r\n\r\n\t\t\t// Add value to the stack total\r\n\t\t\tif (stacking === 'percent') {\r\n\t\t\t\t\r\n\t\t\t\t// Percent stacked column, totals are the same for the positive and negative stacks\r\n\t\t\t\tother = isNegative ? stackKey : negKey;\r\n\t\t\t\tif (negStacks && stacks[other] && stacks[other][x]) {\r\n\t\t\t\t\tother = stacks[other][x];\r\n\t\t\t\t\tstack.total = other.total = mathMax(other.total, stack.total) + mathAbs(y) || 0;\r\n\r\n\t\t\t\t// Percent stacked areas\t\t\t\t\t\r\n\t\t\t\t} else {\r\n\t\t\t\t\tstack.total += mathAbs(y) || 0;\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\tstack.total += y || 0;\r\n\t\t\t}\r\n\r\n\t\t\tstack.cum = (stack.cum || 0) + (y || 0);\r\n\r\n\t\t\tstack.points[series.index].push(stack.cum);\r\n\t\t\tstackedYData[i] = stack.cum;\r\n\r\n\t\t}\r\n\r\n\t\tif (stacking === 'percent') {\r\n\t\t\tyAxis.usePercentage = true;\r\n\t\t}\r\n\r\n\t\tthis.stackedYData = stackedYData; // To be used in getExtremes\r\n\t\t\r\n\t\t// Reset old stacks\r\n\t\tyAxis.oldStacks = {};\r\n\t},\r\n\r\n\t/**\r\n\t * Iterate over all stacks and compute the absolute values to percent\r\n\t */\r\n\tsetPercentStacks: function () {\r\n\t\tvar series = this,\r\n\t\t\tstackKey = series.stackKey,\r\n\t\t\tstacks = series.yAxis.stacks;\r\n\t\t\r\n\t\teach([stackKey, '-' + stackKey], function (key) {\r\n\t\t\tvar i = series.xData.length,\r\n\t\t\t\tx,\r\n\t\t\t\tstack,\r\n\t\t\t\tpointExtremes,\r\n\t\t\t\ttotalFactor;\r\n\r\n\t\t\twhile (i--) {\r\n\t\t\t\tx = series.xData[i];\r\n\t\t\t\tstack = stacks[key] && stacks[key][x];\r\n\t\t\t\tpointExtremes = stack && stack.points[series.index];\r\n\t\t\t\tif (pointExtremes) {\r\n\t\t\t\t\ttotalFactor = stack.total ? 100 / stack.total : 0;\r\n\t\t\t\t\tpointExtremes[0] = correctFloat(pointExtremes[0] * totalFactor); // Y bottom value\r\n\t\t\t\t\tpointExtremes[1] = correctFloat(pointExtremes[1] * totalFactor); // Y value\r\n\t\t\t\t\tseries.stackedYData[i] = pointExtremes[1];\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t});\r\n\t},\r\n\r\n\t/**\r\n\t * Calculate Y extremes for visible data\r\n\t */\r\n\tgetExtremes: function () {\r\n\t\tvar xAxis = this.xAxis,\r\n\t\t\tyAxis = this.yAxis,\r\n\t\t\txData = this.processedXData,\r\n\t\t\tyData = this.stackedYData || this.processedYData,\r\n\t\t\tyDataLength = yData.length,\r\n\t\t\tactiveYData = [],\r\n\t\t\tactiveCounter = 0,\r\n\t\t\txExtremes = xAxis.getExtremes(), // #2117, need to compensate for log X axis\r\n\t\t\txMin = xExtremes.min,\r\n\t\t\txMax = xExtremes.max,\r\n\t\t\tvalidValue,\r\n\t\t\twithinRange,\r\n\t\t\tdataMin,\r\n\t\t\tdataMax,\r\n\t\t\tx,\r\n\t\t\ty,\r\n\t\t\ti,\r\n\t\t\tj;\r\n\r\n\t\tfor (i = 0; i < yDataLength; i++) {\r\n\t\t\t\r\n\t\t\tx = xData[i];\r\n\t\t\ty = yData[i];\r\n\r\n\t\t\t// For points within the visible range, including the first point outside the\r\n\t\t\t// visible range, consider y extremes\r\n\t\t\tvalidValue = y !== null && y !== UNDEFINED && (!yAxis.isLog || (y.length || y > 0));\r\n\t\t\twithinRange = this.getExtremesFromAll || this.cropped || ((xData[i + 1] || x) >= xMin && \r\n\t\t\t\t(xData[i - 1] || x) <= xMax);\r\n\r\n\t\t\tif (validValue && withinRange) {\r\n\r\n\t\t\t\tj = y.length;\r\n\t\t\t\tif (j) { // array, like ohlc or range data\r\n\t\t\t\t\twhile (j--) {\r\n\t\t\t\t\t\tif (y[j] !== null) {\r\n\t\t\t\t\t\t\tactiveYData[activeCounter++] = y[j];\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t} else {\r\n\t\t\t\t\tactiveYData[activeCounter++] = y;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\tthis.dataMin = pick(dataMin, arrayMin(activeYData));\r\n\t\tthis.dataMax = pick(dataMax, arrayMax(activeYData));\r\n\t},\r\n\r\n\t/**\r\n\t * Translate data points from raw data values to chart specific positioning data\r\n\t * needed later in drawPoints, drawGraph and drawTracker.\r\n\t */\r\n\ttranslate: function () {\r\n\t\tif (!this.processedXData) { // hidden series\r\n\t\t\tthis.processData();\r\n\t\t}\r\n\t\tthis.generatePoints();\r\n\t\tvar series = this,\r\n\t\t\toptions = series.options,\r\n\t\t\tstacking = options.stacking,\r\n\t\t\txAxis = series.xAxis,\r\n\t\t\tcategories = xAxis.categories,\r\n\t\t\tyAxis = series.yAxis,\r\n\t\t\tpoints = series.points,\r\n\t\t\tdataLength = points.length,\r\n\t\t\thasModifyValue = !!series.modifyValue,\r\n\t\t\ti,\r\n\t\t\tpointPlacement = options.pointPlacement,\r\n\t\t\tdynamicallyPlaced = pointPlacement === 'between' || isNumber(pointPlacement),\r\n\t\t\tthreshold = options.threshold;\r\n\r\n\t\t\r\n\t\t// Translate each point\r\n\t\tfor (i = 0; i < dataLength; i++) {\r\n\t\t\tvar point = points[i],\r\n\t\t\t\txValue = point.x,\r\n\t\t\t\tyValue = point.y,\r\n\t\t\t\tyBottom = point.low,\r\n\t\t\t\tstack = yAxis.stacks[(series.negStacks && yValue < threshold ? '-' : '') + series.stackKey],\r\n\t\t\t\tpointStack,\r\n\t\t\t\tstackValues;\r\n\r\n\t\t\t// Discard disallowed y values for log axes\r\n\t\t\tif (yAxis.isLog && yValue <= 0) {\r\n\t\t\t\tpoint.y = yValue = null;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// Get the plotX translation\r\n\t\t\tpoint.plotX = xAxis.translate(xValue, 0, 0, 0, 1, pointPlacement, this.type === 'flags'); // Math.round fixes #591\r\n\t\t\t\r\n\r\n\t\t\t// Calculate the bottom y value for stacked series\r\n\t\t\tif (stacking && series.visible && stack && stack[xValue]) {\r\n\r\n\t\t\t\tpointStack = stack[xValue];\r\n\t\t\t\tstackValues = pointStack.points[series.index];\r\n\t\t\t\tyBottom = stackValues[0];\r\n\t\t\t\tyValue = stackValues[1];\r\n\r\n\t\t\t\tif (yBottom === 0) {\r\n\t\t\t\t\tyBottom = pick(threshold, yAxis.min);\r\n\t\t\t\t}\r\n\t\t\t\tif (yAxis.isLog && yBottom <= 0) { // #1200, #1232\r\n\t\t\t\t\tyBottom = null;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tpoint.percentage = stacking === 'percent' && yValue;\r\n\t\t\t\tpoint.total = point.stackTotal = pointStack.total;\r\n\t\t\t\tpoint.stackY = yValue;\r\n\r\n\t\t\t\t// Place the stack label\r\n\t\t\t\tpointStack.setOffset(series.pointXOffset || 0, series.barW || 0);\r\n\t\t\t\t\r\n\t\t\t}\r\n\r\n\t\t\t// Set translated yBottom or remove it\r\n\t\t\tpoint.yBottom = defined(yBottom) ? \r\n\t\t\t\tyAxis.translate(yBottom, 0, 1, 0, 1) :\r\n\t\t\t\tnull;\r\n\t\t\t\t\r\n\t\t\t// general hook, used for Highstock compare mode\r\n\t\t\tif (hasModifyValue) {\r\n\t\t\t\tyValue = series.modifyValue(yValue, point);\r\n\t\t\t}\r\n\r\n\t\t\t// Set the the plotY value, reset it for redraws\r\n\t\t\tpoint.plotY = (typeof yValue === 'number' && yValue !== Infinity) ? \r\n\t\t\t\t//mathRound(yAxis.translate(yValue, 0, 1, 0, 1) * 10) / 10 : // Math.round fixes #591\r\n\t\t\t\tyAxis.translate(yValue, 0, 1, 0, 1) : \r\n\t\t\t\tUNDEFINED;\r\n\t\t\t\r\n\t\t\t// Set client related positions for mouse tracking\r\n\t\t\tpoint.clientX = dynamicallyPlaced ? xAxis.translate(xValue, 0, 0, 0, 1) : point.plotX; // #1514\r\n\t\t\t\t\r\n\t\t\tpoint.negative = point.y < (threshold || 0);\r\n\r\n\t\t\t// some API data\r\n\t\t\tpoint.category = categories && categories[point.x] !== UNDEFINED ?\r\n\t\t\t\tcategories[point.x] : point.x;\r\n\r\n\r\n\t\t}\r\n\r\n\t\t// now that we have the cropped data, build the segments\r\n\t\tseries.getSegments();\r\n\t},\r\n\t/**\r\n\t * Memoize tooltip texts and positions\r\n\t */\r\n\tsetTooltipPoints: function (renew) {\r\n\t\tvar series = this,\r\n\t\t\tpoints = [],\r\n\t\t\tpointsLength,\r\n\t\t\tlow,\r\n\t\t\thigh,\r\n\t\t\txAxis = series.xAxis,\r\n\t\t\txExtremes = xAxis && xAxis.getExtremes(),\r\n\t\t\taxisLength = xAxis ? (xAxis.tooltipLen || xAxis.len) : series.chart.plotSizeX, // tooltipLen and tooltipPosName used in polar\r\n\t\t\tpoint,\r\n\t\t\tpointX,\r\n\t\t\tnextPoint,\r\n\t\t\ti,\r\n\t\t\ttooltipPoints = []; // a lookup array for each pixel in the x dimension\r\n\r\n\t\t// don't waste resources if tracker is disabled\r\n\t\tif (series.options.enableMouseTracking === false) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t// renew\r\n\t\tif (renew) {\r\n\t\t\tseries.tooltipPoints = null;\r\n\t\t}\r\n\r\n\t\t// concat segments to overcome null values\r\n\t\teach(series.segments || series.points, function (segment) {\r\n\t\t\tpoints = points.concat(segment);\r\n\t\t});\r\n\r\n\t\t// Reverse the points in case the X axis is reversed\r\n\t\tif (xAxis && xAxis.reversed) {\r\n\t\t\tpoints = points.reverse();\r\n\t\t}\r\n\r\n\t\t// Polar needs additional shaping\r\n\t\tif (series.orderTooltipPoints) {\r\n\t\t\tseries.orderTooltipPoints(points);\r\n\t\t}\r\n\r\n\t\t// Assign each pixel position to the nearest point\r\n\t\tpointsLength = points.length;\r\n\t\tfor (i = 0; i < pointsLength; i++) {\r\n\t\t\tpoint = points[i];\r\n\t\t\tpointX = point.x;\r\n\t\t\tif (pointX >= xExtremes.min && pointX <= xExtremes.max) { // #1149\r\n\t\t\t\tnextPoint = points[i + 1];\r\n\t\t\t\t\r\n\t\t\t\t// Set this range's low to the last range's high plus one\r\n\t\t\t\tlow = high === UNDEFINED ? 0 : high + 1;\r\n\t\t\t\t// Now find the new high\r\n\t\t\t\thigh = points[i + 1] ?\r\n\t\t\t\t\tmathMin(mathMax(0, mathFloor( // #2070\r\n\t\t\t\t\t\t(point.clientX + (nextPoint ? (nextPoint.wrappedClientX || nextPoint.clientX) : axisLength)) / 2\r\n\t\t\t\t\t)), axisLength) :\r\n\t\t\t\t\taxisLength;\r\n\r\n\t\t\t\twhile (low >= 0 && low <= high) {\r\n\t\t\t\t\ttooltipPoints[low++] = point;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\tseries.tooltipPoints = tooltipPoints;\r\n\t},\r\n\r\n\t/**\r\n\t * Format the header of the tooltip\r\n\t */\r\n\ttooltipHeaderFormatter: function (point) {\r\n\t\tvar series = this,\r\n\t\t\ttooltipOptions = series.tooltipOptions,\r\n\t\t\txDateFormat = tooltipOptions.xDateFormat,\r\n\t\t\tdateTimeLabelFormats = tooltipOptions.dateTimeLabelFormats,\r\n\t\t\txAxis = series.xAxis,\r\n\t\t\tisDateTime = xAxis && xAxis.options.type === 'datetime',\r\n\t\t\theaderFormat = tooltipOptions.headerFormat,\r\n\t\t\tclosestPointRange = xAxis && xAxis.closestPointRange,\r\n\t\t\tn;\r\n\t\t\t\r\n\t\t// Guess the best date format based on the closest point distance (#568)\r\n\t\tif (isDateTime && !xDateFormat) {\r\n\t\t\tif (closestPointRange) {\r\n\t\t\t\tfor (n in timeUnits) {\r\n\t\t\t\t\tif (timeUnits[n] >= closestPointRange) {\r\n\t\t\t\t\t\txDateFormat = dateTimeLabelFormats[n];\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\txDateFormat = dateTimeLabelFormats.day;\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// Insert the header date format if any\r\n\t\tif (isDateTime && xDateFormat && isNumber(point.key)) {\r\n\t\t\theaderFormat = headerFormat.replace('{point.key}', '{point.key:' + xDateFormat + '}');\r\n\t\t}\r\n\t\t\r\n\t\treturn format(headerFormat, {\r\n\t\t\tpoint: point,\r\n\t\t\tseries: series\r\n\t\t});\r\n\t},\r\n\r\n\t/**\r\n\t * Series mouse over handler\r\n\t */\r\n\tonMouseOver: function () {\r\n\t\tvar series = this,\r\n\t\t\tchart = series.chart,\r\n\t\t\thoverSeries = chart.hoverSeries;\r\n\r\n\t\t// set normal state to previous series\r\n\t\tif (hoverSeries && hoverSeries !== series) {\r\n\t\t\thoverSeries.onMouseOut();\r\n\t\t}\r\n\r\n\t\t// trigger the event, but to save processing time,\r\n\t\t// only if defined\r\n\t\tif (series.options.events.mouseOver) {\r\n\t\t\tfireEvent(series, 'mouseOver');\r\n\t\t}\r\n\r\n\t\t// hover this\r\n\t\tseries.setState(HOVER_STATE);\r\n\t\tchart.hoverSeries = series;\r\n\t},\r\n\r\n\t/**\r\n\t * Series mouse out handler\r\n\t */\r\n\tonMouseOut: function () {\r\n\t\t// trigger the event only if listeners exist\r\n\t\tvar series = this,\r\n\t\t\toptions = series.options,\r\n\t\t\tchart = series.chart,\r\n\t\t\ttooltip = chart.tooltip,\r\n\t\t\thoverPoint = chart.hoverPoint;\r\n\r\n\t\t// trigger mouse out on the point, which must be in this series\r\n\t\tif (hoverPoint) {\r\n\t\t\thoverPoint.onMouseOut();\r\n\t\t}\r\n\r\n\t\t// fire the mouse out event\r\n\t\tif (series && options.events.mouseOut) {\r\n\t\t\tfireEvent(series, 'mouseOut');\r\n\t\t}\r\n\r\n\r\n\t\t// hide the tooltip\r\n\t\tif (tooltip && !options.stickyTracking && (!tooltip.shared || series.noSharedTooltip)) {\r\n\t\t\ttooltip.hide();\r\n\t\t}\r\n\r\n\t\t// set normal state\r\n\t\tseries.setState();\r\n\t\tchart.hoverSeries = null;\r\n\t},\r\n\r\n\t/**\r\n\t * Animate in the series\r\n\t */\r\n\tanimate: function (init) {\r\n\t\tvar series = this,\r\n\t\t\tchart = series.chart,\r\n\t\t\trenderer = chart.renderer,\r\n\t\t\tclipRect,\r\n\t\t\tmarkerClipRect,\r\n\t\t\tanimation = series.options.animation,\r\n\t\t\tclipBox = chart.clipBox,\r\n\t\t\tinverted = chart.inverted,\r\n\t\t\tsharedClipKey;\r\n\r\n\t\t// Animation option is set to true\r\n\t\tif (animation && !isObject(animation)) {\r\n\t\t\tanimation = defaultPlotOptions[series.type].animation;\r\n\t\t}\r\n\t\tsharedClipKey = '_sharedClip' + animation.duration + animation.easing;\r\n\r\n\t\t// Initialize the animation. Set up the clipping rectangle.\r\n\t\tif (init) { \r\n\t\t\t\r\n\t\t\t// If a clipping rectangle with the same properties is currently present in the chart, use that. \r\n\t\t\tclipRect = chart[sharedClipKey];\r\n\t\t\tmarkerClipRect = chart[sharedClipKey + 'm'];\r\n\t\t\tif (!clipRect) {\r\n\t\t\t\tchart[sharedClipKey] = clipRect = renderer.clipRect(\r\n\t\t\t\t\textend(clipBox, { width: 0 })\r\n\t\t\t\t);\r\n\t\t\t\t\r\n\t\t\t\tchart[sharedClipKey + 'm'] = markerClipRect = renderer.clipRect(\r\n\t\t\t\t\t-99, // include the width of the first marker\r\n\t\t\t\t\tinverted ? -chart.plotLeft : -chart.plotTop, \r\n\t\t\t\t\t99,\r\n\t\t\t\t\tinverted ? chart.chartWidth : chart.chartHeight\r\n\t\t\t\t);\r\n\t\t\t}\r\n\t\t\tseries.group.clip(clipRect);\r\n\t\t\tseries.markerGroup.clip(markerClipRect);\r\n\t\t\tseries.sharedClipKey = sharedClipKey;\r\n\r\n\t\t// Run the animation\r\n\t\t} else { \r\n\t\t\tclipRect = chart[sharedClipKey];\r\n\t\t\tif (clipRect) {\r\n\t\t\t\tclipRect.animate({\r\n\t\t\t\t\twidth: chart.plotSizeX\r\n\t\t\t\t}, animation);\r\n\t\t\t\tchart[sharedClipKey + 'm'].animate({\r\n\t\t\t\t\twidth: chart.plotSizeX + 99\r\n\t\t\t\t}, animation);\r\n\t\t\t}\r\n\r\n\t\t\t// Delete this function to allow it only once\r\n\t\t\tseries.animate = null;\r\n\t\t\t\r\n\t\t\t// Call the afterAnimate function on animation complete (but don't overwrite the animation.complete option\r\n\t\t\t// which should be available to the user).\r\n\t\t\tseries.animationTimeout = setTimeout(function () {\r\n\t\t\t\tseries.afterAnimate();\r\n\t\t\t}, animation.duration);\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * This runs after animation to land on the final plot clipping\r\n\t */\r\n\tafterAnimate: function () {\r\n\t\tvar chart = this.chart,\r\n\t\t\tsharedClipKey = this.sharedClipKey,\r\n\t\t\tgroup = this.group;\r\n\t\t\t\r\n\t\tif (group && this.options.clip !== false) {\r\n\t\t\tgroup.clip(chart.clipRect);\r\n\t\t\tthis.markerGroup.clip(); // no clip\r\n\t\t}\r\n\t\t\r\n\t\t// Remove the shared clipping rectancgle when all series are shown\t\t\r\n\t\tsetTimeout(function () {\r\n\t\t\tif (sharedClipKey && chart[sharedClipKey]) {\r\n\t\t\t\tchart[sharedClipKey] = chart[sharedClipKey].destroy();\r\n\t\t\t\tchart[sharedClipKey + 'm'] = chart[sharedClipKey + 'm'].destroy();\r\n\t\t\t}\r\n\t\t}, 100);\r\n\t},\r\n\r\n\t/**\r\n\t * Draw the markers\r\n\t */\r\n\tdrawPoints: function () {\r\n\t\tvar series = this,\r\n\t\t\tpointAttr,\r\n\t\t\tpoints = series.points,\r\n\t\t\tchart = series.chart,\r\n\t\t\tplotX,\r\n\t\t\tplotY,\r\n\t\t\ti,\r\n\t\t\tpoint,\r\n\t\t\tradius,\r\n\t\t\tsymbol,\r\n\t\t\tisImage,\r\n\t\t\tgraphic,\r\n\t\t\toptions = series.options,\r\n\t\t\tseriesMarkerOptions = options.marker,\r\n\t\t\tpointMarkerOptions,\r\n\t\t\tenabled,\r\n\t\t\tisInside,\r\n\t\t\tmarkerGroup = series.markerGroup;\r\n\r\n\t\tif (seriesMarkerOptions.enabled || series._hasPointMarkers) {\r\n\t\t\t\r\n\t\t\ti = points.length;\r\n\t\t\twhile (i--) {\r\n\t\t\t\tpoint = points[i];\r\n\t\t\t\tplotX = mathFloor(point.plotX); // #1843\r\n\t\t\t\tplotY = point.plotY;\r\n\t\t\t\tgraphic = point.graphic;\r\n\t\t\t\tpointMarkerOptions = point.marker || {};\r\n\t\t\t\tenabled = (seriesMarkerOptions.enabled && pointMarkerOptions.enabled === UNDEFINED) || pointMarkerOptions.enabled;\r\n\t\t\t\tisInside = chart.isInsidePlot(mathRound(plotX), plotY, chart.inverted); // #1858\r\n\t\t\t\t\r\n\t\t\t\t// only draw the point if y is defined\r\n\t\t\t\tif (enabled && plotY !== UNDEFINED && !isNaN(plotY) && point.y !== null) {\r\n\r\n\t\t\t\t\t// shortcuts\r\n\t\t\t\t\tpointAttr = point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE];\r\n\t\t\t\t\tradius = pointAttr.r;\r\n\t\t\t\t\tsymbol = pick(pointMarkerOptions.symbol, series.symbol);\r\n\t\t\t\t\tisImage = symbol.indexOf('url') === 0;\r\n\r\n\t\t\t\t\tif (graphic) { // update\r\n\t\t\t\t\t\tgraphic\r\n\t\t\t\t\t\t\t.attr({ // Since the marker group isn't clipped, each individual marker must be toggled\r\n\t\t\t\t\t\t\t\tvisibility: isInside ? (hasSVG ? 'inherit' : VISIBLE) : HIDDEN\r\n\t\t\t\t\t\t\t})\r\n\t\t\t\t\t\t\t.animate(extend({\r\n\t\t\t\t\t\t\t\tx: plotX - radius,\r\n\t\t\t\t\t\t\t\ty: plotY - radius\r\n\t\t\t\t\t\t\t}, graphic.symbolName ? { // don't apply to image symbols #507\r\n\t\t\t\t\t\t\t\twidth: 2 * radius,\r\n\t\t\t\t\t\t\t\theight: 2 * radius\r\n\t\t\t\t\t\t\t} : {}));\r\n\t\t\t\t\t} else if (isInside && (radius > 0 || isImage)) {\r\n\t\t\t\t\t\tpoint.graphic = graphic = chart.renderer.symbol(\r\n\t\t\t\t\t\t\tsymbol,\r\n\t\t\t\t\t\t\tplotX - radius,\r\n\t\t\t\t\t\t\tplotY - radius,\r\n\t\t\t\t\t\t\t2 * radius,\r\n\t\t\t\t\t\t\t2 * radius\r\n\t\t\t\t\t\t)\r\n\t\t\t\t\t\t.attr(pointAttr)\r\n\t\t\t\t\t\t.add(markerGroup);\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t} else if (graphic) {\r\n\t\t\t\t\tpoint.graphic = graphic.destroy(); // #1269\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t},\r\n\r\n\t/**\r\n\t * Convert state properties from API naming conventions to SVG attributes\r\n\t *\r\n\t * @param {Object} options API options object\r\n\t * @param {Object} base1 SVG attribute object to inherit from\r\n\t * @param {Object} base2 Second level SVG attribute object to inherit from\r\n\t */\r\n\tconvertAttribs: function (options, base1, base2, base3) {\r\n\t\tvar conversion = this.pointAttrToOptions,\r\n\t\t\tattr,\r\n\t\t\toption,\r\n\t\t\tobj = {};\r\n\r\n\t\toptions = options || {};\r\n\t\tbase1 = base1 || {};\r\n\t\tbase2 = base2 || {};\r\n\t\tbase3 = base3 || {};\r\n\r\n\t\tfor (attr in conversion) {\r\n\t\t\toption = conversion[attr];\r\n\t\t\tobj[attr] = pick(options[option], base1[attr], base2[attr], base3[attr]);\r\n\t\t}\r\n\t\treturn obj;\r\n\t},\r\n\r\n\t/**\r\n\t * Get the state attributes. Each series type has its own set of attributes\r\n\t * that are allowed to change on a point's state change. Series wide attributes are stored for\r\n\t * all series, and additionally point specific attributes are stored for all\r\n\t * points with individual marker options. If such options are not defined for the point,\r\n\t * a reference to the series wide attributes is stored in point.pointAttr.\r\n\t */\r\n\tgetAttribs: function () {\r\n\t\tvar series = this,\r\n\t\t\tseriesOptions = series.options,\r\n\t\t\tnormalOptions = defaultPlotOptions[series.type].marker ? seriesOptions.marker : seriesOptions,\r\n\t\t\tstateOptions = normalOptions.states,\r\n\t\t\tstateOptionsHover = stateOptions[HOVER_STATE],\r\n\t\t\tpointStateOptionsHover,\r\n\t\t\tseriesColor = series.color,\r\n\t\t\tnormalDefaults = {\r\n\t\t\t\tstroke: seriesColor,\r\n\t\t\t\tfill: seriesColor\r\n\t\t\t},\r\n\t\t\tpoints = series.points || [], // #927\r\n\t\t\ti,\r\n\t\t\tpoint,\r\n\t\t\tseriesPointAttr = [],\r\n\t\t\tpointAttr,\r\n\t\t\tpointAttrToOptions = series.pointAttrToOptions,\r\n\t\t\thasPointSpecificOptions,\r\n\t\t\tnegativeColor = seriesOptions.negativeColor,\r\n\t\t\tdefaultLineColor = normalOptions.lineColor,\r\n\t\t\tkey;\r\n\r\n\t\t// series type specific modifications\r\n\t\tif (seriesOptions.marker) { // line, spline, area, areaspline, scatter\r\n\r\n\t\t\t// if no hover radius is given, default to normal radius + 2\r\n\t\t\tstateOptionsHover.radius = stateOptionsHover.radius || normalOptions.radius + 2;\r\n\t\t\tstateOptionsHover.lineWidth = stateOptionsHover.lineWidth || normalOptions.lineWidth + 1;\r\n\t\t\t\r\n\t\t} else { // column, bar, pie\r\n\r\n\t\t\t// if no hover color is given, brighten the normal color\r\n\t\t\tstateOptionsHover.color = stateOptionsHover.color ||\r\n\t\t\t\tColor(stateOptionsHover.color || seriesColor)\r\n\t\t\t\t\t.brighten(stateOptionsHover.brightness).get();\r\n\t\t}\r\n\r\n\t\t// general point attributes for the series normal state\r\n\t\tseriesPointAttr[NORMAL_STATE] = series.convertAttribs(normalOptions, normalDefaults);\r\n\r\n\t\t// HOVER_STATE and SELECT_STATE states inherit from normal state except the default radius\r\n\t\teach([HOVER_STATE, SELECT_STATE], function (state) {\r\n\t\t\tseriesPointAttr[state] =\r\n\t\t\t\t\tseries.convertAttribs(stateOptions[state], seriesPointAttr[NORMAL_STATE]);\r\n\t\t});\r\n\r\n\t\t// set it\r\n\t\tseries.pointAttr = seriesPointAttr;\r\n\r\n\r\n\t\t// Generate the point-specific attribute collections if specific point\r\n\t\t// options are given. If not, create a referance to the series wide point\r\n\t\t// attributes\r\n\t\ti = points.length;\r\n\t\twhile (i--) {\r\n\t\t\tpoint = points[i];\r\n\t\t\tnormalOptions = (point.options && point.options.marker) || point.options;\r\n\t\t\tif (normalOptions && normalOptions.enabled === false) {\r\n\t\t\t\tnormalOptions.radius = 0;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (point.negative && negativeColor) {\r\n\t\t\t\tpoint.color = point.fillColor = negativeColor;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\thasPointSpecificOptions = seriesOptions.colorByPoint || point.color; // #868\r\n\r\n\t\t\t// check if the point has specific visual options\r\n\t\t\tif (point.options) {\r\n\t\t\t\tfor (key in pointAttrToOptions) {\r\n\t\t\t\t\tif (defined(normalOptions[pointAttrToOptions[key]])) {\r\n\t\t\t\t\t\thasPointSpecificOptions = true;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// a specific marker config object is defined for the individual point:\r\n\t\t\t// create it's own attribute collection\r\n\t\t\tif (hasPointSpecificOptions) {\r\n\t\t\t\tnormalOptions = normalOptions || {};\r\n\t\t\t\tpointAttr = [];\r\n\t\t\t\tstateOptions = normalOptions.states || {}; // reassign for individual point\r\n\t\t\t\tpointStateOptionsHover = stateOptions[HOVER_STATE] = stateOptions[HOVER_STATE] || {};\r\n\r\n\t\t\t\t// Handle colors for column and pies\r\n\t\t\t\tif (!seriesOptions.marker) { // column, bar, point\r\n\t\t\t\t\t// if no hover color is given, brighten the normal color\r\n\t\t\t\t\tpointStateOptionsHover.color =\r\n\t\t\t\t\t\tColor(pointStateOptionsHover.color || point.color)\r\n\t\t\t\t\t\t\t.brighten(pointStateOptionsHover.brightness ||\r\n\t\t\t\t\t\t\t\tstateOptionsHover.brightness).get();\r\n\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// normal point state inherits series wide normal state\r\n\t\t\t\tpointAttr[NORMAL_STATE] = series.convertAttribs(extend({\r\n\t\t\t\t\tcolor: point.color, // #868\r\n\t\t\t\t\tfillColor: point.color, // Individual point color or negative color markers (#2219)\r\n\t\t\t\t\tlineColor: defaultLineColor === null ? point.color : UNDEFINED // Bubbles take point color, line markers use white\r\n\t\t\t\t}, normalOptions), seriesPointAttr[NORMAL_STATE]);\r\n\r\n\t\t\t\t// inherit from point normal and series hover\r\n\t\t\t\tpointAttr[HOVER_STATE] = series.convertAttribs(\r\n\t\t\t\t\tstateOptions[HOVER_STATE],\r\n\t\t\t\t\tseriesPointAttr[HOVER_STATE],\r\n\t\t\t\t\tpointAttr[NORMAL_STATE]\r\n\t\t\t\t);\r\n\t\t\t\t\r\n\t\t\t\t// inherit from point normal and series hover\r\n\t\t\t\tpointAttr[SELECT_STATE] = series.convertAttribs(\r\n\t\t\t\t\tstateOptions[SELECT_STATE],\r\n\t\t\t\t\tseriesPointAttr[SELECT_STATE],\r\n\t\t\t\t\tpointAttr[NORMAL_STATE]\r\n\t\t\t\t);\r\n\r\n\r\n\t\t\t// no marker config object is created: copy a reference to the series-wide\r\n\t\t\t// attribute collection\r\n\t\t\t} else {\r\n\t\t\t\tpointAttr = seriesPointAttr;\r\n\t\t\t}\r\n\r\n\t\t\tpoint.pointAttr = pointAttr;\r\n\r\n\t\t}\r\n\r\n\t},\r\n\t/**\r\n\t * Update the series with a new set of options\r\n\t */\r\n\tupdate: function (newOptions, redraw) {\r\n\t\tvar chart = this.chart,\r\n\t\t\t// must use user options when changing type because this.options is merged\r\n\t\t\t// in with type specific plotOptions\r\n\t\t\toldOptions = this.userOptions,\r\n\t\t\toldType = this.type,\r\n\t\t\tproto = seriesTypes[oldType].prototype,\r\n\t\t\tn;\r\n\r\n\t\t// Do the merge, with some forced options\r\n\t\tnewOptions = merge(oldOptions, {\r\n\t\t\tanimation: false,\r\n\t\t\tindex: this.index,\r\n\t\t\tpointStart: this.xData[0] // when updating after addPoint\r\n\t\t}, { data: this.options.data }, newOptions);\r\n\r\n\t\t// Destroy the series and reinsert methods from the type prototype\r\n\t\tthis.remove(false);\r\n\t\tfor (n in proto) { // Overwrite series-type specific methods (#2270)\r\n\t\t\tif (proto.hasOwnProperty(n)) {\r\n\t\t\t\tthis[n] = UNDEFINED;\r\n\t\t\t}\r\n\t\t}\r\n\t\textend(this, seriesTypes[newOptions.type || oldType].prototype);\r\n\t\t\r\n\r\n\t\tthis.init(chart, newOptions);\r\n\t\tif (pick(redraw, true)) {\r\n\t\t\tchart.redraw(false);\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Clear DOM objects and free up memory\r\n\t */\r\n\tdestroy: function () {\r\n\t\tvar series = this,\r\n\t\t\tchart = series.chart,\r\n\t\t\tissue134 = /AppleWebKit\\/533/.test(userAgent),\r\n\t\t\tdestroy,\r\n\t\t\ti,\r\n\t\t\tdata = series.data || [],\r\n\t\t\tpoint,\r\n\t\t\tprop,\r\n\t\t\taxis;\r\n\r\n\t\t// add event hook\r\n\t\tfireEvent(series, 'destroy');\r\n\r\n\t\t// remove all events\r\n\t\tremoveEvent(series);\r\n\t\t\r\n\t\t// erase from axes\r\n\t\teach(['xAxis', 'yAxis'], function (AXIS) {\r\n\t\t\taxis = series[AXIS];\r\n\t\t\tif (axis) {\r\n\t\t\t\terase(axis.series, series);\r\n\t\t\t\taxis.isDirty = axis.forceRedraw = true;\r\n\t\t\t\taxis.stacks = {}; // Rebuild stacks when updating (#2229)\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\t// remove legend items\r\n\t\tif (series.legendItem) {\r\n\t\t\tseries.chart.legend.destroyItem(series);\r\n\t\t}\r\n\r\n\t\t// destroy all points with their elements\r\n\t\ti = data.length;\r\n\t\twhile (i--) {\r\n\t\t\tpoint = data[i];\r\n\t\t\tif (point && point.destroy) {\r\n\t\t\t\tpoint.destroy();\r\n\t\t\t}\r\n\t\t}\r\n\t\tseries.points = null;\r\n\r\n\t\t// Clear the animation timeout if we are destroying the series during initial animation\r\n\t\tclearTimeout(series.animationTimeout);\r\n\r\n\t\t// destroy all SVGElements associated to the series\r\n\t\teach(['area', 'graph', 'dataLabelsGroup', 'group', 'markerGroup', 'tracker',\r\n\t\t\t\t'graphNeg', 'areaNeg', 'posClip', 'negClip'], function (prop) {\r\n\t\t\tif (series[prop]) {\r\n\r\n\t\t\t\t// issue 134 workaround\r\n\t\t\t\tdestroy = issue134 && prop === 'group' ?\r\n\t\t\t\t\t'hide' :\r\n\t\t\t\t\t'destroy';\r\n\r\n\t\t\t\tseries[prop][destroy]();\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\t// remove from hoverSeries\r\n\t\tif (chart.hoverSeries === series) {\r\n\t\t\tchart.hoverSeries = null;\r\n\t\t}\r\n\t\terase(chart.series, series);\r\n\r\n\t\t// clear all members\r\n\t\tfor (prop in series) {\r\n\t\t\tdelete series[prop];\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Draw the data labels\r\n\t */\r\n\tdrawDataLabels: function () {\r\n\t\t\r\n\t\tvar series = this,\r\n\t\t\tseriesOptions = series.options,\r\n\t\t\toptions = seriesOptions.dataLabels,\r\n\t\t\tpoints = series.points,\r\n\t\t\tpointOptions,\r\n\t\t\tgeneralOptions,\r\n\t\t\tstr,\r\n\t\t\tdataLabelsGroup;\r\n\t\t\r\n\t\tif (options.enabled || series._hasPointLabels) {\r\n\t\t\t\t\t\t\r\n\t\t\t// Process default alignment of data labels for columns\r\n\t\t\tif (series.dlProcessOptions) {\r\n\t\t\t\tseries.dlProcessOptions(options);\r\n\t\t\t}\r\n\r\n\t\t\t// Create a separate group for the data labels to avoid rotation\r\n\t\t\tdataLabelsGroup = series.plotGroup(\r\n\t\t\t\t'dataLabelsGroup', \r\n\t\t\t\t'data-labels', \r\n\t\t\t\tseries.visible ? VISIBLE : HIDDEN, \r\n\t\t\t\toptions.zIndex || 6\r\n\t\t\t);\r\n\t\t\t\r\n\t\t\t// Make the labels for each point\r\n\t\t\tgeneralOptions = options;\r\n\t\t\teach(points, function (point) {\r\n\t\t\t\t\r\n\t\t\t\tvar enabled,\r\n\t\t\t\t\tdataLabel = point.dataLabel,\r\n\t\t\t\t\tlabelConfig,\r\n\t\t\t\t\tattr,\r\n\t\t\t\t\tname,\r\n\t\t\t\t\trotation,\r\n\t\t\t\t\tconnector = point.connector,\r\n\t\t\t\t\tisNew = true;\r\n\t\t\t\t\r\n\t\t\t\t// Determine if each data label is enabled\r\n\t\t\t\tpointOptions = point.options && point.options.dataLabels;\r\n\t\t\t\tenabled = pick(pointOptions && pointOptions.enabled, generalOptions.enabled); // #2282\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t// If the point is outside the plot area, destroy it. #678, #820\r\n\t\t\t\tif (dataLabel && !enabled) {\r\n\t\t\t\t\tpoint.dataLabel = dataLabel.destroy();\r\n\t\t\t\t\r\n\t\t\t\t// Individual labels are disabled if the are explicitly disabled \r\n\t\t\t\t// in the point options, or if they fall outside the plot area.\r\n\t\t\t\t} else if (enabled) {\r\n\t\t\t\t\t\r\n\t\t\t\t\t// Create individual options structure that can be extended without \r\n\t\t\t\t\t// affecting others\r\n\t\t\t\t\toptions = merge(generalOptions, pointOptions);\r\n\r\n\t\t\t\t\trotation = options.rotation;\r\n\t\t\t\t\t\r\n\t\t\t\t\t// Get the string\r\n\t\t\t\t\tlabelConfig = point.getLabelConfig();\r\n\t\t\t\t\tstr = options.format ?\r\n\t\t\t\t\t\tformat(options.format, labelConfig) : \r\n\t\t\t\t\t\toptions.formatter.call(labelConfig, options);\r\n\t\t\t\t\t\r\n\t\t\t\t\t// Determine the color\r\n\t\t\t\t\toptions.style.color = pick(options.color, options.style.color, series.color, 'black');\r\n\t\r\n\t\t\t\t\t\r\n\t\t\t\t\t// update existing label\r\n\t\t\t\t\tif (dataLabel) {\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tif (defined(str)) {\r\n\t\t\t\t\t\t\tdataLabel\r\n\t\t\t\t\t\t\t\t.attr({\r\n\t\t\t\t\t\t\t\t\ttext: str\r\n\t\t\t\t\t\t\t\t});\r\n\t\t\t\t\t\t\tisNew = false;\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\t} else { // #1437 - the label is shown conditionally\r\n\t\t\t\t\t\t\tpoint.dataLabel = dataLabel = dataLabel.destroy();\r\n\t\t\t\t\t\t\tif (connector) {\r\n\t\t\t\t\t\t\t\tpoint.connector = connector.destroy();\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t// create new label\r\n\t\t\t\t\t} else if (defined(str)) {\r\n\t\t\t\t\t\tattr = {\r\n\t\t\t\t\t\t\t//align: align,\r\n\t\t\t\t\t\t\tfill: options.backgroundColor,\r\n\t\t\t\t\t\t\tstroke: options.borderColor,\r\n\t\t\t\t\t\t\t'stroke-width': options.borderWidth,\r\n\t\t\t\t\t\t\tr: options.borderRadius || 0,\r\n\t\t\t\t\t\t\trotation: rotation,\r\n\t\t\t\t\t\t\tpadding: options.padding,\r\n\t\t\t\t\t\t\tzIndex: 1\r\n\t\t\t\t\t\t};\r\n\t\t\t\t\t\t// Remove unused attributes (#947)\r\n\t\t\t\t\t\tfor (name in attr) {\r\n\t\t\t\t\t\t\tif (attr[name] === UNDEFINED) {\r\n\t\t\t\t\t\t\t\tdelete attr[name];\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tdataLabel = point.dataLabel = series.chart.renderer[rotation ? 'text' : 'label']( // labels don't support rotation\r\n\t\t\t\t\t\t\tstr,\r\n\t\t\t\t\t\t\t0,\r\n\t\t\t\t\t\t\t-999,\r\n\t\t\t\t\t\t\tnull,\r\n\t\t\t\t\t\t\tnull,\r\n\t\t\t\t\t\t\tnull,\r\n\t\t\t\t\t\t\toptions.useHTML\r\n\t\t\t\t\t\t)\r\n\t\t\t\t\t\t.attr(attr)\r\n\t\t\t\t\t\t.css(options.style)\r\n\t\t\t\t\t\t.add(dataLabelsGroup)\r\n\t\t\t\t\t\t.shadow(options.shadow);\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (dataLabel) {\r\n\t\t\t\t\t\t// Now the data label is created and placed at 0,0, so we need to align it\r\n\t\t\t\t\t\tseries.alignDataLabel(point, dataLabel, options, null, isNew);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Align each individual data label\r\n\t */\r\n\talignDataLabel: function (point, dataLabel, options, alignTo, isNew) {\r\n\t\tvar chart = this.chart,\r\n\t\t\tinverted = chart.inverted,\r\n\t\t\tplotX = pick(point.plotX, -999),\r\n\t\t\tplotY = pick(point.plotY, -999),\r\n\t\t\tbBox = dataLabel.getBBox(),\r\n\t\t\tvisible = this.visible && chart.isInsidePlot(point.plotX, point.plotY, inverted),\r\n\t\t\talignAttr; // the final position;\r\n\t\t\t\t\r\n\t\tif (visible) {\r\n\r\n\t\t\t// The alignment box is a singular point\r\n\t\t\talignTo = extend({\r\n\t\t\t\tx: inverted ? chart.plotWidth - plotY : plotX,\r\n\t\t\t\ty: mathRound(inverted ? chart.plotHeight - plotX : plotY),\r\n\t\t\t\twidth: 0,\r\n\t\t\t\theight: 0\r\n\t\t\t}, alignTo);\r\n\t\t\t\r\n\t\t\t// Add the text size for alignment calculation\r\n\t\t\textend(options, {\r\n\t\t\t\twidth: bBox.width,\r\n\t\t\t\theight: bBox.height\r\n\t\t\t});\r\n\r\n\t\t\t// Allow a hook for changing alignment in the last moment, then do the alignment\r\n\t\t\tif (options.rotation) { // Fancy box alignment isn't supported for rotated text\r\n\t\t\t\talignAttr = {\r\n\t\t\t\t\talign: options.align,\r\n\t\t\t\t\tx: alignTo.x + options.x + alignTo.width / 2,\r\n\t\t\t\t\ty: alignTo.y + options.y + alignTo.height / 2\r\n\t\t\t\t};\r\n\t\t\t\tdataLabel[isNew ? 'attr' : 'animate'](alignAttr);\r\n\t\t\t} else {\r\n\t\t\t\tdataLabel.align(options, null, alignTo);\r\n\t\t\t\talignAttr = dataLabel.alignAttr;\r\n\r\n\t\t\t\t// Handle justify or crop\r\n\t\t\t\tif (pick(options.overflow, 'justify') === 'justify') { // docs: overflow: justify, also crop only applies when not justify\r\n\t\t\t\t\tthis.justifyDataLabel(dataLabel, options, alignAttr, bBox, alignTo, isNew);\r\n\t\t\t\t\r\n\t\t\t\t} else if (pick(options.crop, true)) {\r\n\t\t\t\t\t// Now check that the data label is within the plot area\r\n\t\t\t\t\tvisible = chart.isInsidePlot(alignAttr.x, alignAttr.y) && chart.isInsidePlot(alignAttr.x + bBox.width, alignAttr.y + bBox.height);\r\n\t\t\t\t\r\n\t\t\t\t}\r\n\t\t\t}\t\t\r\n\t\t}\r\n\r\n\t\t// Show or hide based on the final aligned position\r\n\t\tif (!visible) {\r\n\t\t\tdataLabel.attr({ y: -999 });\r\n\t\t}\r\n\t\t\t\t\r\n\t},\r\n\t\r\n\t/**\r\n\t * If data labels fall partly outside the plot area, align them back in, in a way that\r\n\t * doesn't hide the point.\r\n\t */\r\n\tjustifyDataLabel: function (dataLabel, options, alignAttr, bBox, alignTo, isNew) {\r\n\t\tvar chart = this.chart,\r\n\t\t\talign = options.align,\r\n\t\t\tverticalAlign = options.verticalAlign,\r\n\t\t\toff,\r\n\t\t\tjustified;\r\n\r\n\t\t// Off left\r\n\t\toff = alignAttr.x;\r\n\t\tif (off < 0) {\r\n\t\t\tif (align === 'right') {\r\n\t\t\t\toptions.align = 'left';\r\n\t\t\t} else {\r\n\t\t\t\toptions.x = -off;\r\n\t\t\t}\r\n\t\t\tjustified = true;\r\n\t\t}\r\n\r\n\t\t// Off right\r\n\t\toff = alignAttr.x + bBox.width;\r\n\t\tif (off > chart.plotWidth) {\r\n\t\t\tif (align === 'left') {\r\n\t\t\t\toptions.align = 'right';\r\n\t\t\t} else {\r\n\t\t\t\toptions.x = chart.plotWidth - off;\r\n\t\t\t}\r\n\t\t\tjustified = true;\r\n\t\t}\r\n\r\n\t\t// Off top\r\n\t\toff = alignAttr.y;\r\n\t\tif (off < 0) {\r\n\t\t\tif (verticalAlign === 'bottom') {\r\n\t\t\t\toptions.verticalAlign = 'top';\r\n\t\t\t} else {\r\n\t\t\t\toptions.y = -off;\r\n\t\t\t}\r\n\t\t\tjustified = true;\r\n\t\t}\r\n\r\n\t\t// Off bottom\r\n\t\toff = alignAttr.y + bBox.height;\r\n\t\tif (off > chart.plotHeight) {\r\n\t\t\tif (verticalAlign === 'top') {\r\n\t\t\t\toptions.verticalAlign = 'bottom';\r\n\t\t\t} else {\r\n\t\t\t\toptions.y = chart.plotHeight - off;\r\n\t\t\t}\r\n\t\t\tjustified = true;\r\n\t\t}\r\n\t\t\r\n\t\tif (justified) {\r\n\t\t\tdataLabel.placed = !isNew;\r\n\t\t\tdataLabel.align(options, null, alignTo);\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Return the graph path of a segment\r\n\t */\r\n\tgetSegmentPath: function (segment) {\t\t\r\n\t\tvar series = this,\r\n\t\t\tsegmentPath = [],\r\n\t\t\tstep = series.options.step;\r\n\t\t\t\r\n\t\t// build the segment line\r\n\t\teach(segment, function (point, i) {\r\n\t\t\t\r\n\t\t\tvar plotX = point.plotX,\r\n\t\t\t\tplotY = point.plotY,\r\n\t\t\t\tlastPoint;\r\n\r\n\t\t\tif (series.getPointSpline) { // generate the spline as defined in the SplineSeries object\r\n\t\t\t\tsegmentPath.push.apply(segmentPath, series.getPointSpline(segment, point, i));\r\n\r\n\t\t\t} else {\r\n\r\n\t\t\t\t// moveTo or lineTo\r\n\t\t\t\tsegmentPath.push(i ? L : M);\r\n\r\n\t\t\t\t// step line?\r\n\t\t\t\tif (step && i) {\r\n\t\t\t\t\tlastPoint = segment[i - 1];\r\n\t\t\t\t\tif (step === 'right') {\r\n\t\t\t\t\t\tsegmentPath.push(\r\n\t\t\t\t\t\t\tlastPoint.plotX,\r\n\t\t\t\t\t\t\tplotY\r\n\t\t\t\t\t\t);\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t} else if (step === 'center') {\r\n\t\t\t\t\t\tsegmentPath.push(\r\n\t\t\t\t\t\t\t(lastPoint.plotX + plotX) / 2,\r\n\t\t\t\t\t\t\tlastPoint.plotY,\r\n\t\t\t\t\t\t\t(lastPoint.plotX + plotX) / 2,\r\n\t\t\t\t\t\t\tplotY\r\n\t\t\t\t\t\t);\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tsegmentPath.push(\r\n\t\t\t\t\t\t\tplotX,\r\n\t\t\t\t\t\t\tlastPoint.plotY\r\n\t\t\t\t\t\t);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// normal line to next point\r\n\t\t\t\tsegmentPath.push(\r\n\t\t\t\t\tpoint.plotX,\r\n\t\t\t\t\tpoint.plotY\r\n\t\t\t\t);\r\n\t\t\t}\r\n\t\t});\r\n\t\t\r\n\t\treturn segmentPath;\r\n\t},\r\n\r\n\t/**\r\n\t * Get the graph path\r\n\t */\r\n\tgetGraphPath: function () {\r\n\t\tvar series = this,\r\n\t\t\tgraphPath = [],\r\n\t\t\tsegmentPath,\r\n\t\t\tsinglePoints = []; // used in drawTracker\r\n\r\n\t\t// Divide into segments and build graph and area paths\r\n\t\teach(series.segments, function (segment) {\r\n\t\t\t\r\n\t\t\tsegmentPath = series.getSegmentPath(segment);\r\n\t\t\t\r\n\t\t\t// add the segment to the graph, or a single point for tracking\r\n\t\t\tif (segment.length > 1) {\r\n\t\t\t\tgraphPath = graphPath.concat(segmentPath);\r\n\t\t\t} else {\r\n\t\t\t\tsinglePoints.push(segment[0]);\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\t// Record it for use in drawGraph and drawTracker, and return graphPath\r\n\t\tseries.singlePoints = singlePoints;\r\n\t\tseries.graphPath = graphPath;\r\n\t\t\r\n\t\treturn graphPath;\r\n\t\t\r\n\t},\r\n\t\r\n\t/**\r\n\t * Draw the actual graph\r\n\t */\r\n\tdrawGraph: function () {\r\n\t\tvar series = this,\r\n\t\t\toptions = this.options,\r\n\t\t\tprops = [['graph', options.lineColor || this.color]],\r\n\t\t\tlineWidth = options.lineWidth,\r\n\t\t\tdashStyle =  options.dashStyle,\r\n\t\t\tgraphPath = this.getGraphPath(),\r\n\t\t\tnegativeColor = options.negativeColor;\r\n\t\t\t\r\n\t\tif (negativeColor) {\r\n\t\t\tprops.push(['graphNeg', negativeColor]);\r\n\t\t}\r\n\t\t\r\n\t\t// draw the graph\r\n\t\teach(props, function (prop, i) {\r\n\t\t\tvar graphKey = prop[0],\r\n\t\t\t\tgraph = series[graphKey],\r\n\t\t\t\tattribs;\r\n\t\t\t\r\n\t\t\tif (graph) {\r\n\t\t\t\tstop(graph); // cancel running animations, #459\r\n\t\t\t\tgraph.animate({ d: graphPath });\r\n\t\r\n\t\t\t} else if (lineWidth && graphPath.length) { // #1487\r\n\t\t\t\tattribs = {\r\n\t\t\t\t\tstroke: prop[1],\r\n\t\t\t\t\t'stroke-width': lineWidth,\r\n\t\t\t\t\tzIndex: 1 // #1069\r\n\t\t\t\t};\r\n\t\t\t\tif (dashStyle) {\r\n\t\t\t\t\tattribs.dashstyle = dashStyle;\r\n\t\t\t\t} else {\r\n\t\t\t\t\tattribs['stroke-linecap'] = attribs['stroke-linejoin'] = 'round';\r\n\t\t\t\t}\r\n\r\n\t\t\t\tseries[graphKey] = series.chart.renderer.path(graphPath)\r\n\t\t\t\t\t.attr(attribs)\r\n\t\t\t\t\t.add(series.group)\r\n\t\t\t\t\t.shadow(!i && options.shadow);\r\n\t\t\t}\r\n\t\t});\r\n\t},\r\n\t\r\n\t/**\r\n\t * Clip the graphs into the positive and negative coloured graphs\r\n\t */\r\n\tclipNeg: function () {\r\n\t\tvar options = this.options,\r\n\t\t\tchart = this.chart,\r\n\t\t\trenderer = chart.renderer,\r\n\t\t\tnegativeColor = options.negativeColor || options.negativeFillColor,\r\n\t\t\ttranslatedThreshold,\r\n\t\t\tposAttr,\r\n\t\t\tnegAttr,\r\n\t\t\tgraph = this.graph,\r\n\t\t\tarea = this.area,\r\n\t\t\tposClip = this.posClip,\r\n\t\t\tnegClip = this.negClip,\r\n\t\t\tchartWidth = chart.chartWidth,\r\n\t\t\tchartHeight = chart.chartHeight,\r\n\t\t\tchartSizeMax = mathMax(chartWidth, chartHeight),\r\n\t\t\tyAxis = this.yAxis,\r\n\t\t\tabove,\r\n\t\t\tbelow;\r\n\t\t\r\n\t\tif (negativeColor && (graph || area)) {\r\n\t\t\ttranslatedThreshold = mathRound(yAxis.toPixels(options.threshold || 0, true));\r\n\t\t\tabove = {\r\n\t\t\t\tx: 0,\r\n\t\t\t\ty: 0,\r\n\t\t\t\twidth: chartSizeMax,\r\n\t\t\t\theight: translatedThreshold\r\n\t\t\t};\r\n\t\t\tbelow = {\r\n\t\t\t\tx: 0,\r\n\t\t\t\ty: translatedThreshold,\r\n\t\t\t\twidth: chartSizeMax,\r\n\t\t\t\theight: chartSizeMax\r\n\t\t\t};\r\n\t\t\t\r\n\t\t\tif (chart.inverted) {\r\n\r\n\t\t\t\tabove.height = below.y = chart.plotWidth - translatedThreshold;\r\n\t\t\t\tif (renderer.isVML) {\r\n\t\t\t\t\tabove = {\r\n\t\t\t\t\t\tx: chart.plotWidth - translatedThreshold - chart.plotLeft,\r\n\t\t\t\t\t\ty: 0,\r\n\t\t\t\t\t\twidth: chartWidth,\r\n\t\t\t\t\t\theight: chartHeight\r\n\t\t\t\t\t};\r\n\t\t\t\t\tbelow = {\r\n\t\t\t\t\t\tx: translatedThreshold + chart.plotLeft - chartWidth,\r\n\t\t\t\t\t\ty: 0,\r\n\t\t\t\t\t\twidth: chart.plotLeft + translatedThreshold,\r\n\t\t\t\t\t\theight: chartWidth\r\n\t\t\t\t\t};\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tif (yAxis.reversed) {\r\n\t\t\t\tposAttr = below;\r\n\t\t\t\tnegAttr = above;\r\n\t\t\t} else {\r\n\t\t\t\tposAttr = above;\r\n\t\t\t\tnegAttr = below;\r\n\t\t\t}\r\n\t\t\r\n\t\t\tif (posClip) { // update\r\n\t\t\t\tposClip.animate(posAttr);\r\n\t\t\t\tnegClip.animate(negAttr);\r\n\t\t\t} else {\r\n\t\t\t\t\r\n\t\t\t\tthis.posClip = posClip = renderer.clipRect(posAttr);\r\n\t\t\t\tthis.negClip = negClip = renderer.clipRect(negAttr);\r\n\t\t\t\t\r\n\t\t\t\tif (graph && this.graphNeg) {\r\n\t\t\t\t\tgraph.clip(posClip);\r\n\t\t\t\t\tthis.graphNeg.clip(negClip);\t\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tif (area) {\r\n\t\t\t\t\tarea.clip(posClip);\r\n\t\t\t\t\tthis.areaNeg.clip(negClip);\r\n\t\t\t\t} \r\n\t\t\t} \r\n\t\t}\t\r\n\t},\r\n\r\n\t/**\r\n\t * Initialize and perform group inversion on series.group and series.markerGroup\r\n\t */\r\n\tinvertGroups: function () {\r\n\t\tvar series = this,\r\n\t\t\tchart = series.chart;\r\n\r\n\t\t// Pie, go away (#1736)\r\n\t\tif (!series.xAxis) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\t\r\n\t\t// A fixed size is needed for inversion to work\r\n\t\tfunction setInvert() {\t\t\t\r\n\t\t\tvar size = {\r\n\t\t\t\twidth: series.yAxis.len,\r\n\t\t\t\theight: series.xAxis.len\r\n\t\t\t};\r\n\t\t\t\r\n\t\t\teach(['group', 'markerGroup'], function (groupName) {\r\n\t\t\t\tif (series[groupName]) {\r\n\t\t\t\t\tseries[groupName].attr(size).invert();\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t}\r\n\r\n\t\taddEvent(chart, 'resize', setInvert); // do it on resize\r\n\t\taddEvent(series, 'destroy', function () {\r\n\t\t\tremoveEvent(chart, 'resize', setInvert);\r\n\t\t});\r\n\r\n\t\t// Do it now\r\n\t\tsetInvert(); // do it now\r\n\t\t\r\n\t\t// On subsequent render and redraw, just do setInvert without setting up events again\r\n\t\tseries.invertGroups = setInvert;\r\n\t},\r\n\t\r\n\t/**\r\n\t * General abstraction for creating plot groups like series.group, series.dataLabelsGroup and \r\n\t * series.markerGroup. On subsequent calls, the group will only be adjusted to the updated plot size.\r\n\t */\r\n\tplotGroup: function (prop, name, visibility, zIndex, parent) {\r\n\t\tvar group = this[prop],\r\n\t\t\tisNew = !group;\r\n\t\t\r\n\t\t// Generate it on first call\r\n\t\tif (isNew) {\t\r\n\t\t\tthis[prop] = group = this.chart.renderer.g(name)\r\n\t\t\t\t.attr({\r\n\t\t\t\t\tvisibility: visibility,\r\n\t\t\t\t\tzIndex: zIndex || 0.1 // IE8 needs this\r\n\t\t\t\t})\r\n\t\t\t\t.add(parent);\r\n\t\t}\r\n\t\t// Place it on first and subsequent (redraw) calls\r\n\t\tgroup[isNew ? 'attr' : 'animate'](this.getPlotBox());\r\n\t\treturn group;\t\t\r\n\t},\r\n\r\n\t/**\r\n\t * Get the translation and scale for the plot area of this series\r\n\t */\r\n\tgetPlotBox: function () {\r\n\t\treturn {\r\n\t\t\ttranslateX: this.xAxis ? this.xAxis.left : this.chart.plotLeft, \r\n\t\t\ttranslateY: this.yAxis ? this.yAxis.top : this.chart.plotTop,\r\n\t\t\tscaleX: 1, // #1623\r\n\t\t\tscaleY: 1\r\n\t\t};\r\n\t},\r\n\t\r\n\t/**\r\n\t * Render the graph and markers\r\n\t */\r\n\trender: function () {\r\n\t\tvar series = this,\r\n\t\t\tchart = series.chart,\r\n\t\t\tgroup,\r\n\t\t\toptions = series.options,\r\n\t\t\tanimation = options.animation,\r\n\t\t\tdoAnimation = animation && !!series.animate && \r\n\t\t\t\tchart.renderer.isSVG, // this animation doesn't work in IE8 quirks when the group div is hidden,\r\n\t\t\t\t// and looks bad in other oldIE\r\n\t\t\tvisibility = series.visible ? VISIBLE : HIDDEN,\r\n\t\t\tzIndex = options.zIndex,\r\n\t\t\thasRendered = series.hasRendered,\r\n\t\t\tchartSeriesGroup = chart.seriesGroup;\r\n\t\t\r\n\t\t// the group\r\n\t\tgroup = series.plotGroup(\r\n\t\t\t'group', \r\n\t\t\t'series', \r\n\t\t\tvisibility, \r\n\t\t\tzIndex, \r\n\t\t\tchartSeriesGroup\r\n\t\t);\r\n\t\t\r\n\t\tseries.markerGroup = series.plotGroup(\r\n\t\t\t'markerGroup', \r\n\t\t\t'markers', \r\n\t\t\tvisibility, \r\n\t\t\tzIndex, \r\n\t\t\tchartSeriesGroup\r\n\t\t);\r\n\t\t\r\n\t\t// initiate the animation\r\n\t\tif (doAnimation) {\r\n\t\t\tseries.animate(true);\r\n\t\t}\r\n\r\n\t\t// cache attributes for shapes\r\n\t\tseries.getAttribs();\r\n\r\n\t\t// SVGRenderer needs to know this before drawing elements (#1089, #1795)\r\n\t\tgroup.inverted = series.isCartesian ? chart.inverted : false;\r\n\t\t\r\n\t\t// draw the graph if any\r\n\t\tif (series.drawGraph) {\r\n\t\t\tseries.drawGraph();\r\n\t\t\tseries.clipNeg();\r\n\t\t}\r\n\r\n\t\t// draw the data labels (inn pies they go before the points)\r\n\t\tseries.drawDataLabels();\r\n\t\t\r\n\t\t// draw the points\r\n\t\tseries.drawPoints();\r\n\r\n\r\n\t\t// draw the mouse tracking area\r\n\t\tif (series.options.enableMouseTracking !== false) {\r\n\t\t\tseries.drawTracker();\r\n\t\t}\r\n\t\t\r\n\t\t// Handle inverted series and tracker groups\r\n\t\tif (chart.inverted) {\r\n\t\t\tseries.invertGroups();\r\n\t\t}\r\n\t\t\r\n\t\t// Initial clipping, must be defined after inverting groups for VML\r\n\t\tif (options.clip !== false && !series.sharedClipKey && !hasRendered) {\r\n\t\t\tgroup.clip(chart.clipRect);\r\n\t\t}\r\n\r\n\t\t// Run the animation\r\n\t\tif (doAnimation) {\r\n\t\t\tseries.animate();\r\n\t\t} else if (!hasRendered) {\r\n\t\t\tseries.afterAnimate();\r\n\t\t}\r\n\r\n\t\tseries.isDirty = series.isDirtyData = false; // means data is in accordance with what you see\r\n\t\t// (See #322) series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see\r\n\t\tseries.hasRendered = true;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Redraw the series after an update in the axes.\r\n\t */\r\n\tredraw: function () {\r\n\t\tvar series = this,\r\n\t\t\tchart = series.chart,\r\n\t\t\twasDirtyData = series.isDirtyData, // cache it here as it is set to false in render, but used after\r\n\t\t\tgroup = series.group,\r\n\t\t\txAxis = series.xAxis,\r\n\t\t\tyAxis = series.yAxis;\r\n\r\n\t\t// reposition on resize\r\n\t\tif (group) {\r\n\t\t\tif (chart.inverted) {\r\n\t\t\t\tgroup.attr({\r\n\t\t\t\t\twidth: chart.plotWidth,\r\n\t\t\t\t\theight: chart.plotHeight\r\n\t\t\t\t});\r\n\t\t\t}\r\n\r\n\t\t\tgroup.animate({\r\n\t\t\t\ttranslateX: pick(xAxis && xAxis.left, chart.plotLeft),\r\n\t\t\t\ttranslateY: pick(yAxis && yAxis.top, chart.plotTop)\r\n\t\t\t});\r\n\t\t}\r\n\r\n\t\tseries.translate();\r\n\t\tseries.setTooltipPoints(true);\r\n\r\n\t\tseries.render();\r\n\t\tif (wasDirtyData) {\r\n\t\t\tfireEvent(series, 'updatedData');\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Set the state of the graph\r\n\t */\r\n\tsetState: function (state) {\r\n\t\tvar series = this,\r\n\t\t\toptions = series.options,\r\n\t\t\tgraph = series.graph,\r\n\t\t\tgraphNeg = series.graphNeg,\r\n\t\t\tstateOptions = options.states,\r\n\t\t\tlineWidth = options.lineWidth,\r\n\t\t\tattribs;\r\n\r\n\t\tstate = state || NORMAL_STATE;\r\n\r\n\t\tif (series.state !== state) {\r\n\t\t\tseries.state = state;\r\n\r\n\t\t\tif (stateOptions[state] && stateOptions[state].enabled === false) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tif (state) {\r\n\t\t\t\tlineWidth = stateOptions[state].lineWidth || lineWidth + 1;\r\n\t\t\t}\r\n\r\n\t\t\tif (graph && !graph.dashstyle) { // hover is turned off for dashed lines in VML\r\n\t\t\t\tattribs = {\r\n\t\t\t\t\t'stroke-width': lineWidth\r\n\t\t\t\t};\r\n\t\t\t\t// use attr because animate will cause any other animation on the graph to stop\r\n\t\t\t\tgraph.attr(attribs);\r\n\t\t\t\tif (graphNeg) {\r\n\t\t\t\t\tgraphNeg.attr(attribs);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Set the visibility of the graph\r\n\t *\r\n\t * @param vis {Boolean} True to show the series, false to hide. If UNDEFINED,\r\n\t *        the visibility is toggled.\r\n\t */\r\n\tsetVisible: function (vis, redraw) {\r\n\t\tvar series = this,\r\n\t\t\tchart = series.chart,\r\n\t\t\tlegendItem = series.legendItem,\r\n\t\t\tshowOrHide,\r\n\t\t\tignoreHiddenSeries = chart.options.chart.ignoreHiddenSeries,\r\n\t\t\toldVisibility = series.visible;\r\n\r\n\t\t// if called without an argument, toggle visibility\r\n\t\tseries.visible = vis = series.userOptions.visible = vis === UNDEFINED ? !oldVisibility : vis;\r\n\t\tshowOrHide = vis ? 'show' : 'hide';\r\n\r\n\t\t// show or hide elements\r\n\t\teach(['group', 'dataLabelsGroup', 'markerGroup', 'tracker'], function (key) {\r\n\t\t\tif (series[key]) {\r\n\t\t\t\tseries[key][showOrHide]();\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\t\r\n\t\t// hide tooltip (#1361)\r\n\t\tif (chart.hoverSeries === series) {\r\n\t\t\tseries.onMouseOut();\r\n\t\t}\r\n\r\n\r\n\t\tif (legendItem) {\r\n\t\t\tchart.legend.colorizeItem(series, vis);\r\n\t\t}\r\n\r\n\r\n\t\t// rescale or adapt to resized chart\r\n\t\tseries.isDirty = true;\r\n\t\t// in a stack, all other series are affected\r\n\t\tif (series.options.stacking) {\r\n\t\t\teach(chart.series, function (otherSeries) {\r\n\t\t\t\tif (otherSeries.options.stacking && otherSeries.visible) {\r\n\t\t\t\t\totherSeries.isDirty = true;\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t}\r\n\r\n\t\t// show or hide linked series\r\n\t\teach(series.linkedSeries, function (otherSeries) {\r\n\t\t\totherSeries.setVisible(vis, false);\r\n\t\t});\r\n\r\n\t\tif (ignoreHiddenSeries) {\r\n\t\t\tchart.isDirtyBox = true;\r\n\t\t}\r\n\t\tif (redraw !== false) {\r\n\t\t\tchart.redraw();\r\n\t\t}\r\n\r\n\t\tfireEvent(series, showOrHide);\r\n\t},\r\n\r\n\t/**\r\n\t * Show the graph\r\n\t */\r\n\tshow: function () {\r\n\t\tthis.setVisible(true);\r\n\t},\r\n\r\n\t/**\r\n\t * Hide the graph\r\n\t */\r\n\thide: function () {\r\n\t\tthis.setVisible(false);\r\n\t},\r\n\r\n\r\n\t/**\r\n\t * Set the selected state of the graph\r\n\t *\r\n\t * @param selected {Boolean} True to select the series, false to unselect. If\r\n\t *        UNDEFINED, the selection state is toggled.\r\n\t */\r\n\tselect: function (selected) {\r\n\t\tvar series = this;\r\n\t\t// if called without an argument, toggle\r\n\t\tseries.selected = selected = (selected === UNDEFINED) ? !series.selected : selected;\r\n\r\n\t\tif (series.checkbox) {\r\n\t\t\tseries.checkbox.checked = selected;\r\n\t\t}\r\n\r\n\t\tfireEvent(series, selected ? 'select' : 'unselect');\r\n\t},\r\n\r\n\t/**\r\n\t * Draw the tracker object that sits above all data labels and markers to\r\n\t * track mouse events on the graph or points. For the line type charts\r\n\t * the tracker uses the same graphPath, but with a greater stroke width\r\n\t * for better control.\r\n\t */\r\n\tdrawTracker: function () {\r\n\t\tvar series = this,\r\n\t\t\toptions = series.options,\r\n\t\t\ttrackByArea = options.trackByArea,\r\n\t\t\ttrackerPath = [].concat(trackByArea ? series.areaPath : series.graphPath),\r\n\t\t\ttrackerPathLength = trackerPath.length,\r\n\t\t\tchart = series.chart,\r\n\t\t\tpointer = chart.pointer,\r\n\t\t\trenderer = chart.renderer,\r\n\t\t\tsnap = chart.options.tooltip.snap,\r\n\t\t\ttracker = series.tracker,\r\n\t\t\tcursor = options.cursor,\r\n\t\t\tcss = cursor && { cursor: cursor },\r\n\t\t\tsinglePoints = series.singlePoints,\r\n\t\t\tsinglePoint,\r\n\t\t\ti,\r\n\t\t\tonMouseOver = function () {\r\n\t\t\t\tif (chart.hoverSeries !== series) {\r\n\t\t\t\t\tseries.onMouseOver();\r\n\t\t\t\t}\r\n\t\t\t};\r\n\r\n\t\t// Extend end points. A better way would be to use round linecaps,\r\n\t\t// but those are not clickable in VML.\r\n\t\tif (trackerPathLength && !trackByArea) {\r\n\t\t\ti = trackerPathLength + 1;\r\n\t\t\twhile (i--) {\r\n\t\t\t\tif (trackerPath[i] === M) { // extend left side\r\n\t\t\t\t\ttrackerPath.splice(i + 1, 0, trackerPath[i + 1] - snap, trackerPath[i + 2], L);\r\n\t\t\t\t}\r\n\t\t\t\tif ((i && trackerPath[i] === M) || i === trackerPathLength) { // extend right side\r\n\t\t\t\t\ttrackerPath.splice(i, 0, L, trackerPath[i - 2] + snap, trackerPath[i - 1]);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// handle single points\r\n\t\tfor (i = 0; i < singlePoints.length; i++) {\r\n\t\t\tsinglePoint = singlePoints[i];\r\n\t\t\ttrackerPath.push(M, singlePoint.plotX - snap, singlePoint.plotY,\r\n\t\t\t\tL, singlePoint.plotX + snap, singlePoint.plotY);\r\n\t\t}\r\n\t\t\r\n\t\t\r\n\r\n\t\t// draw the tracker\r\n\t\tif (tracker) {\r\n\t\t\ttracker.attr({ d: trackerPath });\r\n\r\n\t\t} else { // create\r\n\t\t\t\t\r\n\t\t\tseries.tracker = renderer.path(trackerPath)\r\n\t\t\t\t.attr({\r\n\t\t\t\t\t'stroke-linejoin': 'round', // #1225\r\n\t\t\t\t\tvisibility: series.visible ? VISIBLE : HIDDEN,\r\n\t\t\t\t\tstroke: TRACKER_FILL,\r\n\t\t\t\t\tfill: trackByArea ? TRACKER_FILL : NONE,\r\n\t\t\t\t\t'stroke-width' : options.lineWidth + (trackByArea ? 0 : 2 * snap),\r\n\t\t\t\t\tzIndex: 2\r\n\t\t\t\t})\r\n\t\t\t\t.add(series.group);\r\n\t\t\t\t\r\n\t\t\t// The tracker is added to the series group, which is clipped, but is covered \r\n\t\t\t// by the marker group. So the marker group also needs to capture events.\r\n\t\t\teach([series.tracker, series.markerGroup], function (tracker) {\r\n\t\t\t\ttracker.addClass(PREFIX + 'tracker')\r\n\t\t\t\t\t.on('mouseover', onMouseOver)\r\n\t\t\t\t\t.on('mouseout', function (e) { pointer.onTrackerMouseOut(e); })\r\n\t\t\t\t\t.css(css);\r\n\r\n\t\t\t\tif (hasTouch) {\r\n\t\t\t\t\ttracker.on('touchstart', onMouseOver);\r\n\t\t\t\t} \r\n\t\t\t});\r\n\t\t}\r\n\r\n\t}\r\n\r\n}; // end Series prototype\r\n\r\n\r\n/**\r\n * LineSeries object\r\n */\r\nvar LineSeries = extendClass(Series);\r\nseriesTypes.line = LineSeries;\r\n\r\n/**\r\n * Set the default options for area\r\n */\r\ndefaultPlotOptions.area = merge(defaultSeriesOptions, {\r\n\tthreshold: 0\r\n\t// trackByArea: false,\r\n\t// lineColor: null, // overrides color, but lets fillColor be unaltered\r\n\t// fillOpacity: 0.75,\r\n\t// fillColor: null\r\n});\r\n\r\n/**\r\n * AreaSeries object\r\n */\r\nvar AreaSeries = extendClass(Series, {\r\n\ttype: 'area',\r\n\t\r\n\t/**\r\n\t * For stacks, don't split segments on null values. Instead, draw null values with \r\n\t * no marker. Also insert dummy points for any X position that exists in other series\r\n\t * in the stack.\r\n\t */ \r\n\tgetSegments: function () {\r\n\t\tvar segments = [],\r\n\t\t\tsegment = [],\r\n\t\t\tkeys = [],\r\n\t\t\txAxis = this.xAxis,\r\n\t\t\tyAxis = this.yAxis,\r\n\t\t\tstack = yAxis.stacks[this.stackKey],\r\n\t\t\tpointMap = {},\r\n\t\t\tplotX,\r\n\t\t\tplotY,\r\n\t\t\tpoints = this.points,\r\n\t\t\tconnectNulls = this.options.connectNulls,\r\n\t\t\tval,\r\n\t\t\ti,\r\n\t\t\tx;\r\n\r\n\t\tif (this.options.stacking && !this.cropped) { // cropped causes artefacts in Stock, and perf issue\r\n\t\t\t// Create a map where we can quickly look up the points by their X value.\r\n\t\t\tfor (i = 0; i < points.length; i++) {\r\n\t\t\t\tpointMap[points[i].x] = points[i];\r\n\t\t\t}\r\n\r\n\t\t\t// Sort the keys (#1651)\r\n\t\t\tfor (x in stack) {\r\n\t\t\t\tkeys.push(+x);\r\n\t\t\t}\r\n\t\t\tkeys.sort(function (a, b) {\r\n\t\t\t\treturn a - b;\r\n\t\t\t});\r\n\r\n\t\t\teach(keys, function (x) {\r\n\t\t\t\tif (connectNulls && (!pointMap[x] || pointMap[x].y === null)) { // #1836\r\n\t\t\t\t\treturn;\r\n\r\n\t\t\t\t// The point exists, push it to the segment\r\n\t\t\t\t} else if (pointMap[x]) {\r\n\t\t\t\t\tsegment.push(pointMap[x]);\r\n\r\n\t\t\t\t// There is no point for this X value in this series, so we \r\n\t\t\t\t// insert a dummy point in order for the areas to be drawn\r\n\t\t\t\t// correctly.\r\n\t\t\t\t} else {\r\n\t\t\t\t\tplotX = xAxis.translate(x);\r\n\t\t\t\t\tval = stack[x].percent ? (stack[x].total ? stack[x].cum * 100 / stack[x].total : 0) : stack[x].cum; // #1991\r\n\t\t\t\t\tplotY = yAxis.toPixels(val, true);\r\n\t\t\t\t\tsegment.push({ \r\n\t\t\t\t\t\ty: null, \r\n\t\t\t\t\t\tplotX: plotX,\r\n\t\t\t\t\t\tclientX: plotX, \r\n\t\t\t\t\t\tplotY: plotY, \r\n\t\t\t\t\t\tyBottom: plotY,\r\n\t\t\t\t\t\tonMouseOver: noop\r\n\t\t\t\t\t});\r\n\t\t\t\t}\r\n\t\t\t});\r\n\r\n\t\t\tif (segment.length) {\r\n\t\t\t\tsegments.push(segment);\r\n\t\t\t}\r\n\r\n\t\t} else {\r\n\t\t\tSeries.prototype.getSegments.call(this);\r\n\t\t\tsegments = this.segments;\r\n\t\t}\r\n\r\n\t\tthis.segments = segments;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Extend the base Series getSegmentPath method by adding the path for the area.\r\n\t * This path is pushed to the series.areaPath property.\r\n\t */\r\n\tgetSegmentPath: function (segment) {\r\n\t\t\r\n\t\tvar segmentPath = Series.prototype.getSegmentPath.call(this, segment), // call base method\r\n\t\t\tareaSegmentPath = [].concat(segmentPath), // work on a copy for the area path\r\n\t\t\ti,\r\n\t\t\toptions = this.options,\r\n\t\t\tsegLength = segmentPath.length,\r\n\t\t\ttranslatedThreshold = this.yAxis.getThreshold(options.threshold), // #2181\r\n\t\t\tyBottom;\r\n\t\t\r\n\t\tif (segLength === 3) { // for animation from 1 to two points\r\n\t\t\tareaSegmentPath.push(L, segmentPath[1], segmentPath[2]);\r\n\t\t}\r\n\t\tif (options.stacking && !this.closedStacks) {\r\n\t\t\t\r\n\t\t\t// Follow stack back. Todo: implement areaspline. A general solution could be to \r\n\t\t\t// reverse the entire graphPath of the previous series, though may be hard with\r\n\t\t\t// splines and with series with different extremes\r\n\t\t\tfor (i = segment.length - 1; i >= 0; i--) {\r\n\r\n\t\t\t\tyBottom = pick(segment[i].yBottom, translatedThreshold);\r\n\t\t\t\r\n\t\t\t\t// step line?\r\n\t\t\t\tif (i < segment.length - 1 && options.step) {\r\n\t\t\t\t\tareaSegmentPath.push(segment[i + 1].plotX, yBottom);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tareaSegmentPath.push(segment[i].plotX, yBottom);\r\n\t\t\t}\r\n\r\n\t\t} else { // follow zero line back\r\n\t\t\tthis.closeSegment(areaSegmentPath, segment, translatedThreshold);\r\n\t\t}\r\n\t\tthis.areaPath = this.areaPath.concat(areaSegmentPath);\r\n\t\treturn segmentPath;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Extendable method to close the segment path of an area. This is overridden in polar \r\n\t * charts.\r\n\t */\r\n\tcloseSegment: function (path, segment, translatedThreshold) {\r\n\t\tpath.push(\r\n\t\t\tL,\r\n\t\t\tsegment[segment.length - 1].plotX,\r\n\t\t\ttranslatedThreshold,\r\n\t\t\tL,\r\n\t\t\tsegment[0].plotX,\r\n\t\t\ttranslatedThreshold\r\n\t\t);\r\n\t},\r\n\t\r\n\t/**\r\n\t * Draw the graph and the underlying area. This method calls the Series base\r\n\t * function and adds the area. The areaPath is calculated in the getSegmentPath\r\n\t * method called from Series.prototype.drawGraph.\r\n\t */\r\n\tdrawGraph: function () {\r\n\t\t\r\n\t\t// Define or reset areaPath\r\n\t\tthis.areaPath = [];\r\n\t\t\r\n\t\t// Call the base method\r\n\t\tSeries.prototype.drawGraph.apply(this);\r\n\t\t\r\n\t\t// Define local variables\r\n\t\tvar series = this,\r\n\t\t\tareaPath = this.areaPath,\r\n\t\t\toptions = this.options,\r\n\t\t\tnegativeColor = options.negativeColor,\r\n\t\t\tnegativeFillColor = options.negativeFillColor,\r\n\t\t\tprops = [['area', this.color, options.fillColor]]; // area name, main color, fill color\r\n\t\t\r\n\t\tif (negativeColor || negativeFillColor) {\r\n\t\t\tprops.push(['areaNeg', negativeColor, negativeFillColor]);\r\n\t\t}\r\n\t\t\r\n\t\teach(props, function (prop) {\r\n\t\t\tvar areaKey = prop[0],\r\n\t\t\t\tarea = series[areaKey];\r\n\t\t\t\t\r\n\t\t\t// Create or update the area\r\n\t\t\tif (area) { // update\r\n\t\t\t\tarea.animate({ d: areaPath });\r\n\t\r\n\t\t\t} else { // create\r\n\t\t\t\tseries[areaKey] = series.chart.renderer.path(areaPath)\r\n\t\t\t\t\t.attr({\r\n\t\t\t\t\t\tfill: pick(\r\n\t\t\t\t\t\t\tprop[2],\r\n\t\t\t\t\t\t\tColor(prop[1]).setOpacity(pick(options.fillOpacity, 0.75)).get()\r\n\t\t\t\t\t\t),\r\n\t\t\t\t\t\tzIndex: 0 // #1069\r\n\t\t\t\t\t}).add(series.group);\r\n\t\t\t}\r\n\t\t});\r\n\t},\r\n\t\r\n\t/**\r\n\t * Get the series' symbol in the legend\r\n\t * \r\n\t * @param {Object} legend The legend object\r\n\t * @param {Object} item The series (this) or point\r\n\t */\r\n\tdrawLegendSymbol: function (legend, item) {\r\n\t\t\r\n\t\titem.legendSymbol = this.chart.renderer.rect(\r\n\t\t\t0,\r\n\t\t\tlegend.baseline - 11,\r\n\t\t\tlegend.options.symbolWidth,\r\n\t\t\t12,\r\n\t\t\t2\r\n\t\t).attr({\r\n\t\t\tzIndex: 3\r\n\t\t}).add(item.legendGroup);\t\t\r\n\t\t\r\n\t}\r\n});\r\n\r\nseriesTypes.area = AreaSeries;/**\r\n * Set the default options for spline\r\n */\r\ndefaultPlotOptions.spline = merge(defaultSeriesOptions);\r\n\r\n/**\r\n * SplineSeries object\r\n */\r\nvar SplineSeries = extendClass(Series, {\r\n\ttype: 'spline',\r\n\r\n\t/**\r\n\t * Get the spline segment from a given point's previous neighbour to the given point\r\n\t */\r\n\tgetPointSpline: function (segment, point, i) {\r\n\t\tvar smoothing = 1.5, // 1 means control points midway between points, 2 means 1/3 from the point, 3 is 1/4 etc\r\n\t\t\tdenom = smoothing + 1,\r\n\t\t\tplotX = point.plotX,\r\n\t\t\tplotY = point.plotY,\r\n\t\t\tlastPoint = segment[i - 1],\r\n\t\t\tnextPoint = segment[i + 1],\r\n\t\t\tleftContX,\r\n\t\t\tleftContY,\r\n\t\t\trightContX,\r\n\t\t\trightContY,\r\n\t\t\tret;\r\n\r\n\t\t// find control points\r\n\t\tif (lastPoint && nextPoint) {\r\n\t\t\r\n\t\t\tvar lastX = lastPoint.plotX,\r\n\t\t\t\tlastY = lastPoint.plotY,\r\n\t\t\t\tnextX = nextPoint.plotX,\r\n\t\t\t\tnextY = nextPoint.plotY,\r\n\t\t\t\tcorrection;\r\n\r\n\t\t\tleftContX = (smoothing * plotX + lastX) / denom;\r\n\t\t\tleftContY = (smoothing * plotY + lastY) / denom;\r\n\t\t\trightContX = (smoothing * plotX + nextX) / denom;\r\n\t\t\trightContY = (smoothing * plotY + nextY) / denom;\r\n\r\n\t\t\t// have the two control points make a straight line through main point\r\n\t\t\tcorrection = ((rightContY - leftContY) * (rightContX - plotX)) /\r\n\t\t\t\t(rightContX - leftContX) + plotY - rightContY;\r\n\r\n\t\t\tleftContY += correction;\r\n\t\t\trightContY += correction;\r\n\r\n\t\t\t// to prevent false extremes, check that control points are between\r\n\t\t\t// neighbouring points' y values\r\n\t\t\tif (leftContY > lastY && leftContY > plotY) {\r\n\t\t\t\tleftContY = mathMax(lastY, plotY);\r\n\t\t\t\trightContY = 2 * plotY - leftContY; // mirror of left control point\r\n\t\t\t} else if (leftContY < lastY && leftContY < plotY) {\r\n\t\t\t\tleftContY = mathMin(lastY, plotY);\r\n\t\t\t\trightContY = 2 * plotY - leftContY;\r\n\t\t\t}\r\n\t\t\tif (rightContY > nextY && rightContY > plotY) {\r\n\t\t\t\trightContY = mathMax(nextY, plotY);\r\n\t\t\t\tleftContY = 2 * plotY - rightContY;\r\n\t\t\t} else if (rightContY < nextY && rightContY < plotY) {\r\n\t\t\t\trightContY = mathMin(nextY, plotY);\r\n\t\t\t\tleftContY = 2 * plotY - rightContY;\r\n\t\t\t}\r\n\r\n\t\t\t// record for drawing in next point\r\n\t\t\tpoint.rightContX = rightContX;\r\n\t\t\tpoint.rightContY = rightContY;\r\n\r\n\t\t}\r\n\t\t\r\n\t\t// Visualize control points for debugging\r\n\t\t/*\r\n\t\tif (leftContX) {\r\n\t\t\tthis.chart.renderer.circle(leftContX + this.chart.plotLeft, leftContY + this.chart.plotTop, 2)\r\n\t\t\t\t.attr({\r\n\t\t\t\t\tstroke: 'red',\r\n\t\t\t\t\t'stroke-width': 1,\r\n\t\t\t\t\tfill: 'none'\r\n\t\t\t\t})\r\n\t\t\t\t.add();\r\n\t\t\tthis.chart.renderer.path(['M', leftContX + this.chart.plotLeft, leftContY + this.chart.plotTop,\r\n\t\t\t\t'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop])\r\n\t\t\t\t.attr({\r\n\t\t\t\t\tstroke: 'red',\r\n\t\t\t\t\t'stroke-width': 1\r\n\t\t\t\t})\r\n\t\t\t\t.add();\r\n\t\t\tthis.chart.renderer.circle(rightContX + this.chart.plotLeft, rightContY + this.chart.plotTop, 2)\r\n\t\t\t\t.attr({\r\n\t\t\t\t\tstroke: 'green',\r\n\t\t\t\t\t'stroke-width': 1,\r\n\t\t\t\t\tfill: 'none'\r\n\t\t\t\t})\r\n\t\t\t\t.add();\r\n\t\t\tthis.chart.renderer.path(['M', rightContX + this.chart.plotLeft, rightContY + this.chart.plotTop,\r\n\t\t\t\t'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop])\r\n\t\t\t\t.attr({\r\n\t\t\t\t\tstroke: 'green',\r\n\t\t\t\t\t'stroke-width': 1\r\n\t\t\t\t})\r\n\t\t\t\t.add();\r\n\t\t}\r\n\t\t*/\r\n\r\n\t\t// moveTo or lineTo\r\n\t\tif (!i) {\r\n\t\t\tret = [M, plotX, plotY];\r\n\t\t} else { // curve from last point to this\r\n\t\t\tret = [\r\n\t\t\t\t'C',\r\n\t\t\t\tlastPoint.rightContX || lastPoint.plotX,\r\n\t\t\t\tlastPoint.rightContY || lastPoint.plotY,\r\n\t\t\t\tleftContX || plotX,\r\n\t\t\t\tleftContY || plotY,\r\n\t\t\t\tplotX,\r\n\t\t\t\tplotY\r\n\t\t\t];\r\n\t\t\tlastPoint.rightContX = lastPoint.rightContY = null; // reset for updating series later\r\n\t\t}\r\n\t\treturn ret;\r\n\t}\r\n});\r\nseriesTypes.spline = SplineSeries;\r\n\r\n/**\r\n * Set the default options for areaspline\r\n */\r\ndefaultPlotOptions.areaspline = merge(defaultPlotOptions.area);\r\n\r\n/**\r\n * AreaSplineSeries object\r\n */\r\nvar areaProto = AreaSeries.prototype,\r\n\tAreaSplineSeries = extendClass(SplineSeries, {\r\n\t\ttype: 'areaspline',\r\n\t\tclosedStacks: true, // instead of following the previous graph back, follow the threshold back\r\n\t\t\r\n\t\t// Mix in methods from the area series\r\n\t\tgetSegmentPath: areaProto.getSegmentPath,\r\n\t\tcloseSegment: areaProto.closeSegment,\r\n\t\tdrawGraph: areaProto.drawGraph,\r\n\t\tdrawLegendSymbol: areaProto.drawLegendSymbol\r\n\t});\r\nseriesTypes.areaspline = AreaSplineSeries;\r\n\r\n/**\r\n * Set the default options for column\r\n */\r\ndefaultPlotOptions.column = merge(defaultSeriesOptions, {\r\n\tborderColor: '#FFFFFF',\r\n\tborderWidth: 1,\r\n\tborderRadius: 0,\r\n\t//colorByPoint: undefined,\r\n\tgroupPadding: 0.2,\r\n\t//grouping: true,\r\n\tmarker: null, // point options are specified in the base options\r\n\tpointPadding: 0.1,\r\n\t//pointWidth: null,\r\n\tminPointLength: 0,\r\n\tcropThreshold: 50, // when there are more points, they will not animate out of the chart on xAxis.setExtremes\r\n\tpointRange: null, // null means auto, meaning 1 in a categorized axis and least distance between points if not categories\r\n\tstates: {\r\n\t\thover: {\r\n\t\t\tbrightness: 0.1,\r\n\t\t\tshadow: false\r\n\t\t},\r\n\t\tselect: {\r\n\t\t\tcolor: '#C0C0C0',\r\n\t\t\tborderColor: '#000000',\r\n\t\t\tshadow: false\r\n\t\t}\r\n\t},\r\n\tdataLabels: {\r\n\t\talign: null, // auto\r\n\t\tverticalAlign: null, // auto\r\n\t\ty: null\r\n\t},\r\n\tstickyTracking: false,\r\n\tthreshold: 0\r\n});\r\n\r\n/**\r\n * ColumnSeries object\r\n */\r\nvar ColumnSeries = extendClass(Series, {\r\n\ttype: 'column',\r\n\tpointAttrToOptions: { // mapping between SVG attributes and the corresponding options\r\n\t\tstroke: 'borderColor',\r\n\t\t'stroke-width': 'borderWidth',\r\n\t\tfill: 'color',\r\n\t\tr: 'borderRadius'\r\n\t},\r\n\tcropShoulder: 0,\r\n\ttrackerGroups: ['group', 'dataLabelsGroup'],\r\n\tnegStacks: true, // use separate negative stacks, unlike area stacks where a negative \r\n\t\t// point is substracted from previous (#1910)\r\n\t\r\n\t/**\r\n\t * Initialize the series\r\n\t */\r\n\tinit: function () {\r\n\t\tSeries.prototype.init.apply(this, arguments);\r\n\r\n\t\tvar series = this,\r\n\t\t\tchart = series.chart;\r\n\r\n\t\t// if the series is added dynamically, force redraw of other\r\n\t\t// series affected by a new column\r\n\t\tif (chart.hasRendered) {\r\n\t\t\teach(chart.series, function (otherSeries) {\r\n\t\t\t\tif (otherSeries.type === series.type) {\r\n\t\t\t\t\totherSeries.isDirty = true;\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Return the width and x offset of the columns adjusted for grouping, groupPadding, pointPadding,\r\n\t * pointWidth etc. \r\n\t */\r\n\tgetColumnMetrics: function () {\r\n\r\n\t\tvar series = this,\r\n\t\t\toptions = series.options,\r\n\t\t\txAxis = series.xAxis,\r\n\t\t\tyAxis = series.yAxis,\r\n\t\t\treversedXAxis = xAxis.reversed,\r\n\t\t\tstackKey,\r\n\t\t\tstackGroups = {},\r\n\t\t\tcolumnIndex,\r\n\t\t\tcolumnCount = 0;\r\n\r\n\t\t// Get the total number of column type series.\r\n\t\t// This is called on every series. Consider moving this logic to a\r\n\t\t// chart.orderStacks() function and call it on init, addSeries and removeSeries\r\n\t\tif (options.grouping === false) {\r\n\t\t\tcolumnCount = 1;\r\n\t\t} else {\r\n\t\t\teach(series.chart.series, function (otherSeries) {\r\n\t\t\t\tvar otherOptions = otherSeries.options,\r\n\t\t\t\t\totherYAxis = otherSeries.yAxis;\r\n\t\t\t\tif (otherSeries.type === series.type && otherSeries.visible &&\r\n\t\t\t\t\t\tyAxis.len === otherYAxis.len && yAxis.pos === otherYAxis.pos) {  // #642, #2086\r\n\t\t\t\t\tif (otherOptions.stacking) {\r\n\t\t\t\t\t\tstackKey = otherSeries.stackKey;\r\n\t\t\t\t\t\tif (stackGroups[stackKey] === UNDEFINED) {\r\n\t\t\t\t\t\t\tstackGroups[stackKey] = columnCount++;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tcolumnIndex = stackGroups[stackKey];\r\n\t\t\t\t\t} else if (otherOptions.grouping !== false) { // #1162\r\n\t\t\t\t\t\tcolumnIndex = columnCount++;\r\n\t\t\t\t\t}\r\n\t\t\t\t\totherSeries.columnIndex = columnIndex;\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t}\r\n\r\n\t\tvar categoryWidth = mathMin(\r\n\t\t\t\tmathAbs(xAxis.transA) * (xAxis.ordinalSlope || options.pointRange || xAxis.closestPointRange || 1), \r\n\t\t\t\txAxis.len // #1535\r\n\t\t\t),\r\n\t\t\tgroupPadding = categoryWidth * options.groupPadding,\r\n\t\t\tgroupWidth = categoryWidth - 2 * groupPadding,\r\n\t\t\tpointOffsetWidth = groupWidth / columnCount,\r\n\t\t\toptionPointWidth = options.pointWidth,\r\n\t\t\tpointPadding = defined(optionPointWidth) ? (pointOffsetWidth - optionPointWidth) / 2 :\r\n\t\t\t\tpointOffsetWidth * options.pointPadding,\r\n\t\t\tpointWidth = pick(optionPointWidth, pointOffsetWidth - 2 * pointPadding), // exact point width, used in polar charts\r\n\t\t\tcolIndex = (reversedXAxis ? \r\n\t\t\t\tcolumnCount - (series.columnIndex || 0) : // #1251\r\n\t\t\t\tseries.columnIndex) || 0,\r\n\t\t\tpointXOffset = pointPadding + (groupPadding + colIndex *\r\n\t\t\t\tpointOffsetWidth - (categoryWidth / 2)) *\r\n\t\t\t\t(reversedXAxis ? -1 : 1);\r\n\r\n\t\t// Save it for reading in linked series (Error bars particularly)\r\n\t\treturn (series.columnMetrics = { \r\n\t\t\twidth: pointWidth, \r\n\t\t\toffset: pointXOffset \r\n\t\t});\r\n\t\t\t\r\n\t},\r\n\r\n\t/**\r\n\t * Translate each point to the plot area coordinate system and find shape positions\r\n\t */\r\n\ttranslate: function () {\r\n\t\tvar series = this,\r\n\t\t\tchart = series.chart,\r\n\t\t\toptions = series.options,\r\n\t\t\tborderWidth = options.borderWidth,\r\n\t\t\tyAxis = series.yAxis,\r\n\t\t\tthreshold = options.threshold,\r\n\t\t\ttranslatedThreshold = series.translatedThreshold = yAxis.getThreshold(threshold),\r\n\t\t\tminPointLength = pick(options.minPointLength, 5),\r\n\t\t\tmetrics = series.getColumnMetrics(),\r\n\t\t\tpointWidth = metrics.width,\r\n\t\t\tseriesBarW = series.barW = mathCeil(mathMax(pointWidth, 1 + 2 * borderWidth)), // rounded and postprocessed for border width\r\n\t\t\tpointXOffset = series.pointXOffset = metrics.offset,\r\n\t\t\txCrisp = -(borderWidth % 2 ? 0.5 : 0),\r\n\t\t\tyCrisp = borderWidth % 2 ? 0.5 : 1;\r\n\r\n\t\tif (chart.renderer.isVML && chart.inverted) {\r\n\t\t\tyCrisp += 1;\r\n\t\t}\r\n\r\n\t\tSeries.prototype.translate.apply(series);\r\n\r\n\t\t// record the new values\r\n\t\teach(series.points, function (point) {\r\n\t\t\tvar yBottom = pick(point.yBottom, translatedThreshold),\r\n\t\t\t\tplotY = mathMin(mathMax(-999 - yBottom, point.plotY), yAxis.len + 999 + yBottom), // Don't draw too far outside plot area (#1303, #2241)\r\n\t\t\t\tbarX = point.plotX + pointXOffset,\r\n\t\t\t\tbarW = seriesBarW,\r\n\t\t\t\tbarY = mathMin(plotY, yBottom),\r\n\t\t\t\tright,\r\n\t\t\t\tbottom,\r\n\t\t\t\tfromTop,\r\n\t\t\t\tfromLeft,\r\n\t\t\t\tbarH = mathMax(plotY, yBottom) - barY;\r\n\r\n\t\t\t// Handle options.minPointLength\r\n\t\t\tif (mathAbs(barH) < minPointLength) {\r\n\t\t\t\tif (minPointLength) {\r\n\t\t\t\t\tbarH = minPointLength;\r\n\t\t\t\t\tbarY =\r\n\t\t\t\t\t\tmathRound(mathAbs(barY - translatedThreshold) > minPointLength ? // stacked\r\n\t\t\t\t\t\t\tyBottom - minPointLength : // keep position\r\n\t\t\t\t\t\t\ttranslatedThreshold - (yAxis.translate(point.y, 0, 1, 0, 1) <= translatedThreshold ? minPointLength : 0)); // use exact yAxis.translation (#1485)\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// Cache for access in polar\r\n\t\t\tpoint.barX = barX;\r\n\t\t\tpoint.pointWidth = pointWidth;\r\n\r\n\r\n\t\t\t// Round off to obtain crisp edges\r\n\t\t\tfromLeft = mathAbs(barX) < 0.5;\r\n\t\t\tright = mathRound(barX + barW) + xCrisp;\r\n\t\t\tbarX = mathRound(barX) + xCrisp;\r\n\t\t\tbarW = right - barX;\r\n\r\n\t\t\tfromTop = mathAbs(barY) < 0.5;\r\n\t\t\tbottom = mathRound(barY + barH) + yCrisp;\r\n\t\t\tbarY = mathRound(barY) + yCrisp;\r\n\t\t\tbarH = bottom - barY;\r\n\r\n\t\t\t// Top and left edges are exceptions\r\n\t\t\tif (fromLeft) {\r\n\t\t\t\tbarX += 1;\r\n\t\t\t\tbarW -= 1;\r\n\t\t\t}\r\n\t\t\tif (fromTop) {\r\n\t\t\t\tbarY -= 1;\r\n\t\t\t\tbarH += 1;\r\n\t\t\t}\r\n\r\n\t\t\t// Register shape type and arguments to be used in drawPoints\r\n\t\t\tpoint.shapeType = 'rect';\r\n\t\t\tpoint.shapeArgs = {\r\n\t\t\t\tx: barX,\r\n\t\t\t\ty: barY,\r\n\t\t\t\twidth: barW,\r\n\t\t\t\theight: barH\r\n\t\t\t};\r\n\t\t});\r\n\r\n\t},\r\n\r\n\tgetSymbol: noop,\r\n\t\r\n\t/**\r\n\t * Use a solid rectangle like the area series types\r\n\t */\r\n\tdrawLegendSymbol: AreaSeries.prototype.drawLegendSymbol,\r\n\t\r\n\t\r\n\t/**\r\n\t * Columns have no graph\r\n\t */\r\n\tdrawGraph: noop,\r\n\r\n\t/**\r\n\t * Draw the columns. For bars, the series.group is rotated, so the same coordinates\r\n\t * apply for columns and bars. This method is inherited by scatter series.\r\n\t *\r\n\t */\r\n\tdrawPoints: function () {\r\n\t\tvar series = this,\r\n\t\t\toptions = series.options,\r\n\t\t\trenderer = series.chart.renderer,\r\n\t\t\tshapeArgs;\r\n\r\n\r\n\t\t// draw the columns\r\n\t\teach(series.points, function (point) {\r\n\t\t\tvar plotY = point.plotY,\r\n\t\t\t\tgraphic = point.graphic;\r\n\r\n\t\t\tif (plotY !== UNDEFINED && !isNaN(plotY) && point.y !== null) {\r\n\t\t\t\tshapeArgs = point.shapeArgs;\r\n\t\t\t\t\r\n\t\t\t\tif (graphic) { // update\r\n\t\t\t\t\tstop(graphic);\r\n\t\t\t\t\tgraphic.animate(merge(shapeArgs));\r\n\r\n\t\t\t\t} else {\r\n\t\t\t\t\tpoint.graphic = graphic = renderer[point.shapeType](shapeArgs)\r\n\t\t\t\t\t\t.attr(point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE])\r\n\t\t\t\t\t\t.add(series.group)\r\n\t\t\t\t\t\t.shadow(options.shadow, null, options.stacking && !options.borderRadius);\r\n\t\t\t\t}\r\n\r\n\t\t\t} else if (graphic) {\r\n\t\t\t\tpoint.graphic = graphic.destroy(); // #1269\r\n\t\t\t}\r\n\t\t});\r\n\t},\r\n\r\n\t/**\r\n\t * Add tracking event listener to the series group, so the point graphics\r\n\t * themselves act as trackers\r\n\t */\r\n\tdrawTracker: function () {\r\n\t\tvar series = this,\r\n\t\t\tchart = series.chart,\r\n\t\t\tpointer = chart.pointer,\r\n\t\t\tcursor = series.options.cursor,\r\n\t\t\tcss = cursor && { cursor: cursor },\r\n\t\t\tonMouseOver = function (e) {\r\n\t\t\t\tvar target = e.target,\r\n\t\t\t\t\tpoint;\r\n\r\n\t\t\t\tif (chart.hoverSeries !== series) {\r\n\t\t\t\t\tseries.onMouseOver();\r\n\t\t\t\t}\r\n\t\t\t\twhile (target && !point) {\r\n\t\t\t\t\tpoint = target.point;\r\n\t\t\t\t\ttarget = target.parentNode;\r\n\t\t\t\t}\r\n\t\t\t\tif (point !== UNDEFINED && point !== chart.hoverPoint) { // undefined on graph in scatterchart\r\n\t\t\t\t\tpoint.onMouseOver(e);\r\n\t\t\t\t}\r\n\t\t\t};\r\n\r\n\t\t// Add reference to the point\r\n\t\teach(series.points, function (point) {\r\n\t\t\tif (point.graphic) {\r\n\t\t\t\tpoint.graphic.element.point = point;\r\n\t\t\t}\r\n\t\t\tif (point.dataLabel) {\r\n\t\t\t\tpoint.dataLabel.element.point = point;\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\t// Add the event listeners, we need to do this only once\r\n\t\tif (!series._hasTracking) {\r\n\t\t\teach(series.trackerGroups, function (key) {\r\n\t\t\t\tif (series[key]) { // we don't always have dataLabelsGroup\r\n\t\t\t\t\tseries[key]\r\n\t\t\t\t\t\t.addClass(PREFIX + 'tracker')\r\n\t\t\t\t\t\t.on('mouseover', onMouseOver)\r\n\t\t\t\t\t\t.on('mouseout', function (e) { pointer.onTrackerMouseOut(e); })\r\n\t\t\t\t\t\t.css(css);\r\n\t\t\t\t\tif (hasTouch) {\r\n\t\t\t\t\t\tseries[key].on('touchstart', onMouseOver);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t\tseries._hasTracking = true;\r\n\t\t}\r\n\t},\r\n\t\r\n\t/** \r\n\t * Override the basic data label alignment by adjusting for the position of the column\r\n\t */\r\n\talignDataLabel: function (point, dataLabel, options,  alignTo, isNew) {\r\n\t\tvar chart = this.chart,\r\n\t\t\tinverted = chart.inverted,\r\n\t\t\tdlBox = point.dlBox || point.shapeArgs, // data label box for alignment\r\n\t\t\tbelow = point.below || (point.plotY > pick(this.translatedThreshold, chart.plotSizeY)),\r\n\t\t\tinside = pick(options.inside, !!this.options.stacking); // draw it inside the box?\r\n\t\t\r\n\t\t// Align to the column itself, or the top of it\r\n\t\tif (dlBox) { // Area range uses this method but not alignTo\r\n\t\t\talignTo = merge(dlBox);\r\n\t\t\tif (inverted) {\r\n\t\t\t\talignTo = {\r\n\t\t\t\t\tx: chart.plotWidth - alignTo.y - alignTo.height,\r\n\t\t\t\t\ty: chart.plotHeight - alignTo.x - alignTo.width,\r\n\t\t\t\t\twidth: alignTo.height,\r\n\t\t\t\t\theight: alignTo.width\r\n\t\t\t\t};\r\n\t\t\t}\r\n\t\t\t\t\r\n\t\t\t// Compute the alignment box\r\n\t\t\tif (!inside) {\r\n\t\t\t\tif (inverted) {\r\n\t\t\t\t\talignTo.x += below ? 0 : alignTo.width;\r\n\t\t\t\t\talignTo.width = 0;\r\n\t\t\t\t} else {\r\n\t\t\t\t\talignTo.y += below ? alignTo.height : 0;\r\n\t\t\t\t\talignTo.height = 0;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// When alignment is undefined (typically columns and bars), display the individual \r\n\t\t// point below or above the point depending on the threshold\r\n\t\toptions.align = pick(\r\n\t\t\toptions.align, \r\n\t\t\t!inverted || inside ? 'center' : below ? 'right' : 'left'\r\n\t\t);\r\n\t\toptions.verticalAlign = pick(\r\n\t\t\toptions.verticalAlign, \r\n\t\t\tinverted || inside ? 'middle' : below ? 'top' : 'bottom'\r\n\t\t);\r\n\t\t\r\n\t\t// Call the parent method\r\n\t\tSeries.prototype.alignDataLabel.call(this, point, dataLabel, options, alignTo, isNew);\r\n\t},\r\n\r\n\r\n\t/**\r\n\t * Animate the column heights one by one from zero\r\n\t * @param {Boolean} init Whether to initialize the animation or run it\r\n\t */\r\n\tanimate: function (init) {\r\n\t\tvar series = this,\r\n\t\t\tyAxis = this.yAxis,\r\n\t\t\toptions = series.options,\r\n\t\t\tinverted = this.chart.inverted,\r\n\t\t\tattr = {},\r\n\t\t\ttranslatedThreshold;\r\n\r\n\t\tif (hasSVG) { // VML is too slow anyway\r\n\t\t\tif (init) {\r\n\t\t\t\tattr.scaleY = 0.001;\r\n\t\t\t\ttranslatedThreshold = mathMin(yAxis.pos + yAxis.len, mathMax(yAxis.pos, yAxis.toPixels(options.threshold)));\r\n\t\t\t\tif (inverted) {\r\n\t\t\t\t\tattr.translateX = translatedThreshold - yAxis.len;\r\n\t\t\t\t} else {\r\n\t\t\t\t\tattr.translateY = translatedThreshold;\r\n\t\t\t\t}\r\n\t\t\t\tseries.group.attr(attr);\r\n\r\n\t\t\t} else { // run the animation\r\n\t\t\t\t\r\n\t\t\t\tattr.scaleY = 1;\r\n\t\t\t\tattr[inverted ? 'translateX' : 'translateY'] = yAxis.pos;\r\n\t\t\t\tseries.group.animate(attr, series.options.animation);\r\n\r\n\t\t\t\t// delete this function to allow it only once\r\n\t\t\t\tseries.animate = null;\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Remove this series from the chart\r\n\t */\r\n\tremove: function () {\r\n\t\tvar series = this,\r\n\t\t\tchart = series.chart;\r\n\r\n\t\t// column and bar series affects other series of the same type\r\n\t\t// as they are either stacked or grouped\r\n\t\tif (chart.hasRendered) {\r\n\t\t\teach(chart.series, function (otherSeries) {\r\n\t\t\t\tif (otherSeries.type === series.type) {\r\n\t\t\t\t\totherSeries.isDirty = true;\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t}\r\n\r\n\t\tSeries.prototype.remove.apply(series, arguments);\r\n\t}\r\n});\r\nseriesTypes.column = ColumnSeries;\r\n/**\r\n * Set the default options for bar\r\n */\r\ndefaultPlotOptions.bar = merge(defaultPlotOptions.column);\r\n/**\r\n * The Bar series class\r\n */\r\nvar BarSeries = extendClass(ColumnSeries, {\r\n\ttype: 'bar',\r\n\tinverted: true\r\n});\r\nseriesTypes.bar = BarSeries;\r\n\r\n/**\r\n * Set the default options for scatter\r\n */\r\ndefaultPlotOptions.scatter = merge(defaultSeriesOptions, {\r\n\tlineWidth: 0,\r\n\ttooltip: {\r\n\t\theaderFormat: '<span style=\"font-size: 10px; color:{series.color}\">{series.name}</span><br/>',\r\n\t\tpointFormat: 'x: <b>{point.x}</b><br/>y: <b>{point.y}</b><br/>',\r\n\t\tfollowPointer: true\r\n\t},\r\n\tstickyTracking: false\r\n});\r\n\r\n/**\r\n * The scatter series class\r\n */\r\nvar ScatterSeries = extendClass(Series, {\r\n\ttype: 'scatter',\r\n\tsorted: false,\r\n\trequireSorting: false,\r\n\tnoSharedTooltip: true,\r\n\ttrackerGroups: ['markerGroup'],\r\n\r\n\tdrawTracker: ColumnSeries.prototype.drawTracker,\r\n\t\r\n\tsetTooltipPoints: noop\r\n});\r\nseriesTypes.scatter = ScatterSeries;\r\n\r\n/**\r\n * Set the default options for pie\r\n */\r\ndefaultPlotOptions.pie = merge(defaultSeriesOptions, {\r\n\tborderColor: '#FFFFFF',\r\n\tborderWidth: 1,\r\n\tcenter: [null, null],\r\n\tclip: false,\r\n\tcolorByPoint: true, // always true for pies\r\n\tdataLabels: {\r\n\t\t// align: null,\r\n\t\t// connectorWidth: 1,\r\n\t\t// connectorColor: point.color,\r\n\t\t// connectorPadding: 5,\r\n\t\tdistance: 30,\r\n\t\tenabled: true,\r\n\t\tformatter: function () {\r\n\t\t\treturn this.point.name;\r\n\t\t}\r\n\t\t// softConnector: true,\r\n\t\t//y: 0\r\n\t},\r\n\tignoreHiddenPoint: true,\r\n\t//innerSize: 0,\r\n\tlegendType: 'point',\r\n\tmarker: null, // point options are specified in the base options\r\n\tsize: null,\r\n\tshowInLegend: false,\r\n\tslicedOffset: 10,\r\n\tstates: {\r\n\t\thover: {\r\n\t\t\tbrightness: 0.1,\r\n\t\t\tshadow: false\r\n\t\t}\r\n\t},\r\n\tstickyTracking: false,\r\n\ttooltip: {\r\n\t\tfollowPointer: true\r\n\t}\r\n});\r\n\r\n/**\r\n * Extended point object for pies\r\n */\r\nvar PiePoint = extendClass(Point, {\r\n\t/**\r\n\t * Initiate the pie slice\r\n\t */\r\n\tinit: function () {\r\n\r\n\t\tPoint.prototype.init.apply(this, arguments);\r\n\r\n\t\tvar point = this,\r\n\t\t\ttoggleSlice;\r\n\r\n\t\t// Disallow negative values (#1530)\r\n\t\tif (point.y < 0) {\r\n\t\t\tpoint.y = null;\r\n\t\t}\r\n\r\n\t\t//visible: options.visible !== false,\r\n\t\textend(point, {\r\n\t\t\tvisible: point.visible !== false,\r\n\t\t\tname: pick(point.name, 'Slice')\r\n\t\t});\r\n\r\n\t\t// add event listener for select\r\n\t\ttoggleSlice = function (e) {\r\n\t\t\tpoint.slice(e.type === 'select');\r\n\t\t};\r\n\t\taddEvent(point, 'select', toggleSlice);\r\n\t\taddEvent(point, 'unselect', toggleSlice);\r\n\r\n\t\treturn point;\r\n\t},\r\n\r\n\t/**\r\n\t * Toggle the visibility of the pie slice\r\n\t * @param {Boolean} vis Whether to show the slice or not. If undefined, the\r\n\t *    visibility is toggled\r\n\t */\r\n\tsetVisible: function (vis) {\r\n\t\tvar point = this,\r\n\t\t\tseries = point.series,\r\n\t\t\tchart = series.chart,\r\n\t\t\tmethod;\r\n\r\n\t\t// if called without an argument, toggle visibility\r\n\t\tpoint.visible = point.options.visible = vis = vis === UNDEFINED ? !point.visible : vis;\r\n\t\tseries.options.data[inArray(point, series.data)] = point.options; // update userOptions.data\r\n\t\t\r\n\t\tmethod = vis ? 'show' : 'hide';\r\n\r\n\t\t// Show and hide associated elements\r\n\t\teach(['graphic', 'dataLabel', 'connector', 'shadowGroup'], function (key) {\r\n\t\t\tif (point[key]) {\r\n\t\t\t\tpoint[key][method]();\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\tif (point.legendItem) {\r\n\t\t\tchart.legend.colorizeItem(point, vis);\r\n\t\t}\r\n\t\t\r\n\t\t// Handle ignore hidden slices\r\n\t\tif (!series.isDirty && series.options.ignoreHiddenPoint) {\r\n\t\t\tseries.isDirty = true;\r\n\t\t\tchart.redraw();\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Set or toggle whether the slice is cut out from the pie\r\n\t * @param {Boolean} sliced When undefined, the slice state is toggled\r\n\t * @param {Boolean} redraw Whether to redraw the chart. True by default.\r\n\t */\r\n\tslice: function (sliced, redraw, animation) {\r\n\t\tvar point = this,\r\n\t\t\tseries = point.series,\r\n\t\t\tchart = series.chart,\r\n\t\t\ttranslation;\r\n\r\n\t\tsetAnimation(animation, chart);\r\n\r\n\t\t// redraw is true by default\r\n\t\tredraw = pick(redraw, true);\r\n\r\n\t\t// if called without an argument, toggle\r\n\t\tpoint.sliced = point.options.sliced = sliced = defined(sliced) ? sliced : !point.sliced;\r\n\t\tseries.options.data[inArray(point, series.data)] = point.options; // update userOptions.data\r\n\r\n\t\ttranslation = sliced ? point.slicedTranslation : {\r\n\t\t\ttranslateX: 0,\r\n\t\t\ttranslateY: 0\r\n\t\t};\r\n\r\n\t\tpoint.graphic.animate(translation);\r\n\t\t\r\n\t\tif (point.shadowGroup) {\r\n\t\t\tpoint.shadowGroup.animate(translation);\r\n\t\t}\r\n\r\n\t}\r\n});\r\n\r\n/**\r\n * The Pie series class\r\n */\r\nvar PieSeries = {\r\n\ttype: 'pie',\r\n\tisCartesian: false,\r\n\tpointClass: PiePoint,\r\n\trequireSorting: false,\r\n\tnoSharedTooltip: true,\r\n\ttrackerGroups: ['group', 'dataLabelsGroup'],\r\n\tpointAttrToOptions: { // mapping between SVG attributes and the corresponding options\r\n\t\tstroke: 'borderColor',\r\n\t\t'stroke-width': 'borderWidth',\r\n\t\tfill: 'color'\r\n\t},\r\n\r\n\t/**\r\n\t * Pies have one color each point\r\n\t */\r\n\tgetColor: noop,\r\n\r\n\t/**\r\n\t * Animate the pies in\r\n\t */\r\n\tanimate: function (init) {\r\n\t\tvar series = this,\r\n\t\t\tpoints = series.points,\r\n\t\t\tstartAngleRad = series.startAngleRad;\r\n\r\n\t\tif (!init) {\r\n\t\t\teach(points, function (point) {\r\n\t\t\t\tvar graphic = point.graphic,\r\n\t\t\t\t\targs = point.shapeArgs;\r\n\r\n\t\t\t\tif (graphic) {\r\n\t\t\t\t\t// start values\r\n\t\t\t\t\tgraphic.attr({\r\n\t\t\t\t\t\tr: series.center[3] / 2, // animate from inner radius (#779)\r\n\t\t\t\t\t\tstart: startAngleRad,\r\n\t\t\t\t\t\tend: startAngleRad\r\n\t\t\t\t\t});\r\n\r\n\t\t\t\t\t// animate\r\n\t\t\t\t\tgraphic.animate({\r\n\t\t\t\t\t\tr: args.r,\r\n\t\t\t\t\t\tstart: args.start,\r\n\t\t\t\t\t\tend: args.end\r\n\t\t\t\t\t}, series.options.animation);\r\n\t\t\t\t}\r\n\t\t\t});\r\n\r\n\t\t\t// delete this function to allow it only once\r\n\t\t\tseries.animate = null;\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * Extend the basic setData method by running processData and generatePoints immediately,\r\n\t * in order to access the points from the legend.\r\n\t */\r\n\tsetData: function (data, redraw) {\r\n\t\tSeries.prototype.setData.call(this, data, false);\r\n\t\tthis.processData();\r\n\t\tthis.generatePoints();\r\n\t\tif (pick(redraw, true)) {\r\n\t\t\tthis.chart.redraw();\r\n\t\t} \r\n\t},\r\n\r\n\t/**\r\n\t * Extend the generatePoints method by adding total and percentage properties to each point\r\n\t */\r\n\tgeneratePoints: function () {\r\n\t\tvar i,\r\n\t\t\ttotal = 0,\r\n\t\t\tpoints,\r\n\t\t\tlen,\r\n\t\t\tpoint,\r\n\t\t\tignoreHiddenPoint = this.options.ignoreHiddenPoint;\r\n\r\n\t\tSeries.prototype.generatePoints.call(this);\r\n\r\n\t\t// Populate local vars\r\n\t\tpoints = this.points;\r\n\t\tlen = points.length;\r\n\t\t\r\n\t\t// Get the total sum\r\n\t\tfor (i = 0; i < len; i++) {\r\n\t\t\tpoint = points[i];\r\n\t\t\ttotal += (ignoreHiddenPoint && !point.visible) ? 0 : point.y;\r\n\t\t}\r\n\t\tthis.total = total;\r\n\r\n\t\t// Set each point's properties\r\n\t\tfor (i = 0; i < len; i++) {\r\n\t\t\tpoint = points[i];\r\n\t\t\tpoint.percentage = total > 0 ? (point.y / total) * 100 : 0;\r\n\t\t\tpoint.total = total;\r\n\t\t}\r\n\t\t\r\n\t},\r\n\t\r\n\t/**\r\n\t * Get the center of the pie based on the size and center options relative to the  \r\n\t * plot area. Borrowed by the polar and gauge series types.\r\n\t */\r\n\tgetCenter: function () {\r\n\t\t\r\n\t\tvar options = this.options,\r\n\t\t\tchart = this.chart,\r\n\t\t\tslicingRoom = 2 * (options.slicedOffset || 0),\r\n\t\t\thandleSlicingRoom,\r\n\t\t\tplotWidth = chart.plotWidth - 2 * slicingRoom,\r\n\t\t\tplotHeight = chart.plotHeight - 2 * slicingRoom,\r\n\t\t\tcenterOption = options.center,\r\n\t\t\tpositions = [pick(centerOption[0], '50%'), pick(centerOption[1], '50%'), options.size || '100%', options.innerSize || 0],\r\n\t\t\tsmallestSize = mathMin(plotWidth, plotHeight),\r\n\t\t\tisPercent;\r\n\t\t\r\n\t\treturn map(positions, function (length, i) {\r\n\t\t\tisPercent = /%$/.test(length);\r\n\t\t\thandleSlicingRoom = i < 2 || (i === 2 && isPercent);\r\n\t\t\treturn (isPercent ?\r\n\t\t\t\t// i == 0: centerX, relative to width\r\n\t\t\t\t// i == 1: centerY, relative to height\r\n\t\t\t\t// i == 2: size, relative to smallestSize\r\n\t\t\t\t// i == 4: innerSize, relative to smallestSize\r\n\t\t\t\t[plotWidth, plotHeight, smallestSize, smallestSize][i] *\r\n\t\t\t\t\tpInt(length) / 100 :\r\n\t\t\t\tlength) + (handleSlicingRoom ? slicingRoom : 0);\r\n\t\t});\r\n\t},\r\n\t\r\n\t/**\r\n\t * Do translation for pie slices\r\n\t */\r\n\ttranslate: function (positions) {\r\n\t\tthis.generatePoints();\r\n\t\t\r\n\t\tvar series = this,\r\n\t\t\tcumulative = 0,\r\n\t\t\tprecision = 1000, // issue #172\r\n\t\t\toptions = series.options,\r\n\t\t\tslicedOffset = options.slicedOffset,\r\n\t\t\tconnectorOffset = slicedOffset + options.borderWidth,\r\n\t\t\tstart,\r\n\t\t\tend,\r\n\t\t\tangle,\r\n\t\t\tstartAngle = options.startAngle || 0,\r\n\t\t\tstartAngleRad = series.startAngleRad = mathPI / 180 * (startAngle - 90),\r\n\t\t\tendAngleRad = series.endAngleRad = mathPI / 180 * ((options.endAngle || (startAngle + 360)) - 90), // docs\r\n\t\t\tcirc = endAngleRad - startAngleRad, //2 * mathPI,\r\n\t\t\tpoints = series.points,\r\n\t\t\tradiusX, // the x component of the radius vector for a given point\r\n\t\t\tradiusY,\r\n\t\t\tlabelDistance = options.dataLabels.distance,\r\n\t\t\tignoreHiddenPoint = options.ignoreHiddenPoint,\r\n\t\t\ti,\r\n\t\t\tlen = points.length,\r\n\t\t\tpoint;\r\n\r\n\t\t// Get positions - either an integer or a percentage string must be given.\r\n\t\t// If positions are passed as a parameter, we're in a recursive loop for adjusting\r\n\t\t// space for data labels.\r\n\t\tif (!positions) {\r\n\t\t\tseries.center = positions = series.getCenter();\r\n\t\t}\r\n\r\n\t\t// utility for getting the x value from a given y, used for anticollision logic in data labels\r\n\t\tseries.getX = function (y, left) {\r\n\r\n\t\t\tangle = math.asin((y - positions[1]) / (positions[2] / 2 + labelDistance));\r\n\r\n\t\t\treturn positions[0] +\r\n\t\t\t\t(left ? -1 : 1) *\r\n\t\t\t\t(mathCos(angle) * (positions[2] / 2 + labelDistance));\r\n\t\t};\r\n\r\n\t\t// Calculate the geometry for each point\r\n\t\tfor (i = 0; i < len; i++) {\r\n\t\t\t\r\n\t\t\tpoint = points[i];\r\n\t\t\t\r\n\t\t\t// set start and end angle\r\n\t\t\tstart = startAngleRad + (cumulative * circ);\r\n\t\t\tif (!ignoreHiddenPoint || point.visible) {\r\n\t\t\t\tcumulative += point.percentage / 100;\r\n\t\t\t}\r\n\t\t\tend = startAngleRad + (cumulative * circ);\r\n\r\n\t\t\t// set the shape\r\n\t\t\tpoint.shapeType = 'arc';\r\n\t\t\tpoint.shapeArgs = {\r\n\t\t\t\tx: positions[0],\r\n\t\t\t\ty: positions[1],\r\n\t\t\t\tr: positions[2] / 2,\r\n\t\t\t\tinnerR: positions[3] / 2,\r\n\t\t\t\tstart: mathRound(start * precision) / precision,\r\n\t\t\t\tend: mathRound(end * precision) / precision\r\n\t\t\t};\r\n\r\n\t\t\t// center for the sliced out slice\r\n\t\t\tangle = (end + start) / 2;\r\n\t\t\tif (angle > 0.75 * circ) {\r\n\t\t\t\tangle -= 2 * mathPI;\r\n\t\t\t}\r\n\t\t\tpoint.slicedTranslation = {\r\n\t\t\t\ttranslateX: mathRound(mathCos(angle) * slicedOffset),\r\n\t\t\t\ttranslateY: mathRound(mathSin(angle) * slicedOffset)\r\n\t\t\t};\r\n\r\n\t\t\t// set the anchor point for tooltips\r\n\t\t\tradiusX = mathCos(angle) * positions[2] / 2;\r\n\t\t\tradiusY = mathSin(angle) * positions[2] / 2;\r\n\t\t\tpoint.tooltipPos = [\r\n\t\t\t\tpositions[0] + radiusX * 0.7,\r\n\t\t\t\tpositions[1] + radiusY * 0.7\r\n\t\t\t];\r\n\t\t\t\r\n\t\t\tpoint.half = angle < -mathPI / 2 || angle > mathPI / 2 ? 1 : 0;\r\n\t\t\tpoint.angle = angle;\r\n\r\n\t\t\t// set the anchor point for data labels\r\n\t\t\tconnectorOffset = mathMin(connectorOffset, labelDistance / 2); // #1678\r\n\t\t\tpoint.labelPos = [\r\n\t\t\t\tpositions[0] + radiusX + mathCos(angle) * labelDistance, // first break of connector\r\n\t\t\t\tpositions[1] + radiusY + mathSin(angle) * labelDistance, // a/a\r\n\t\t\t\tpositions[0] + radiusX + mathCos(angle) * connectorOffset, // second break, right outside pie\r\n\t\t\t\tpositions[1] + radiusY + mathSin(angle) * connectorOffset, // a/a\r\n\t\t\t\tpositions[0] + radiusX, // landing point for connector\r\n\t\t\t\tpositions[1] + radiusY, // a/a\r\n\t\t\t\tlabelDistance < 0 ? // alignment\r\n\t\t\t\t\t'center' :\r\n\t\t\t\t\tpoint.half ? 'right' : 'left', // alignment\r\n\t\t\t\tangle // center angle\r\n\t\t\t];\r\n\r\n\t\t}\r\n\t},\r\n\r\n\tsetTooltipPoints: noop,\r\n\tdrawGraph: null,\r\n\r\n\t/**\r\n\t * Draw the data points\r\n\t */\r\n\tdrawPoints: function () {\r\n\t\tvar series = this,\r\n\t\t\tchart = series.chart,\r\n\t\t\trenderer = chart.renderer,\r\n\t\t\tgroupTranslation,\r\n\t\t\t//center,\r\n\t\t\tgraphic,\r\n\t\t\t//group,\r\n\t\t\tshadow = series.options.shadow,\r\n\t\t\tshadowGroup,\r\n\t\t\tshapeArgs;\r\n\r\n\t\tif (shadow && !series.shadowGroup) {\r\n\t\t\tseries.shadowGroup = renderer.g('shadow')\r\n\t\t\t\t.add(series.group);\r\n\t\t}\r\n\r\n\t\t// draw the slices\r\n\t\teach(series.points, function (point) {\r\n\t\t\tgraphic = point.graphic;\r\n\t\t\tshapeArgs = point.shapeArgs;\r\n\t\t\tshadowGroup = point.shadowGroup;\r\n\r\n\t\t\t// put the shadow behind all points\r\n\t\t\tif (shadow && !shadowGroup) {\r\n\t\t\t\tshadowGroup = point.shadowGroup = renderer.g('shadow')\r\n\t\t\t\t\t.add(series.shadowGroup);\r\n\t\t\t}\r\n\r\n\t\t\t// if the point is sliced, use special translation, else use plot area traslation\r\n\t\t\tgroupTranslation = point.sliced ? point.slicedTranslation : {\r\n\t\t\t\ttranslateX: 0,\r\n\t\t\t\ttranslateY: 0\r\n\t\t\t};\r\n\r\n\t\t\t//group.translate(groupTranslation[0], groupTranslation[1]);\r\n\t\t\tif (shadowGroup) {\r\n\t\t\t\tshadowGroup.attr(groupTranslation);\r\n\t\t\t}\r\n\r\n\t\t\t// draw the slice\r\n\t\t\tif (graphic) {\r\n\t\t\t\tgraphic.animate(extend(shapeArgs, groupTranslation));\r\n\t\t\t} else {\r\n\t\t\t\tpoint.graphic = graphic = renderer.arc(shapeArgs)\r\n\t\t\t\t\t.setRadialReference(series.center)\r\n\t\t\t\t\t.attr(\r\n\t\t\t\t\t\tpoint.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE]\r\n\t\t\t\t\t)\r\n\t\t\t\t\t.attr({ 'stroke-linejoin': 'round' })\r\n\t\t\t\t\t.attr(groupTranslation)\r\n\t\t\t\t\t.add(series.group)\r\n\t\t\t\t\t.shadow(shadow, shadowGroup);\t\r\n\t\t\t}\r\n\r\n\t\t\t// detect point specific visibility\r\n\t\t\tif (point.visible === false) {\r\n\t\t\t\tpoint.setVisible(false);\r\n\t\t\t}\r\n\r\n\t\t});\r\n\r\n\t},\r\n\r\n\t/**\r\n\t * Utility for sorting data labels\r\n\t */\r\n\tsortByAngle: function (points, sign) {\r\n\t\tpoints.sort(function (a, b) {\r\n\t\t\treturn a.angle !== undefined && (b.angle - a.angle) * sign;\r\n\t\t});\r\n\t},\r\n\r\n\t/**\r\n\t * Override the base drawDataLabels method by pie specific functionality\r\n\t */\r\n\tdrawDataLabels: function () {\r\n\t\tvar series = this,\r\n\t\t\tdata = series.data,\r\n\t\t\tpoint,\r\n\t\t\tchart = series.chart,\r\n\t\t\toptions = series.options.dataLabels,\r\n\t\t\tconnectorPadding = pick(options.connectorPadding, 10),\r\n\t\t\tconnectorWidth = pick(options.connectorWidth, 1),\r\n\t\t\tplotWidth = chart.plotWidth,\r\n\t\t\tplotHeight = chart.plotHeight,\r\n\t\t\tconnector,\r\n\t\t\tconnectorPath,\r\n\t\t\tsoftConnector = pick(options.softConnector, true),\r\n\t\t\tdistanceOption = options.distance,\r\n\t\t\tseriesCenter = series.center,\r\n\t\t\tradius = seriesCenter[2] / 2,\r\n\t\t\tcenterY = seriesCenter[1],\r\n\t\t\toutside = distanceOption > 0,\r\n\t\t\tdataLabel,\r\n\t\t\tdataLabelWidth,\r\n\t\t\tlabelPos,\r\n\t\t\tlabelHeight,\r\n\t\t\thalves = [// divide the points into right and left halves for anti collision\r\n\t\t\t\t[], // right\r\n\t\t\t\t[]  // left\r\n\t\t\t],\r\n\t\t\tx,\r\n\t\t\ty,\r\n\t\t\tvisibility,\r\n\t\t\trankArr,\r\n\t\t\ti,\r\n\t\t\tj,\r\n\t\t\toverflow = [0, 0, 0, 0], // top, right, bottom, left\r\n\t\t\tsort = function (a, b) {\r\n\t\t\t\treturn b.y - a.y;\r\n\t\t\t};\r\n\r\n\t\t// get out if not enabled\r\n\t\tif (!series.visible || (!options.enabled && !series._hasPointLabels)) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t// run parent method\r\n\t\tSeries.prototype.drawDataLabels.apply(series);\r\n\r\n\t\t// arrange points for detection collision\r\n\t\teach(data, function (point) {\r\n\t\t\tif (point.dataLabel) { // it may have been cancelled in the base method (#407)\r\n\t\t\t\thalves[point.half].push(point);\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\t// assume equal label heights\r\n\t\ti = 0;\r\n\t\twhile (!labelHeight && data[i]) { // #1569\r\n\t\t\tlabelHeight = data[i] && data[i].dataLabel && (data[i].dataLabel.getBBox().height || 21); // 21 is for #968\r\n\t\t\ti++;\r\n\t\t}\r\n\r\n\t\t/* Loop over the points in each half, starting from the top and bottom\r\n\t\t * of the pie to detect overlapping labels.\r\n\t\t */\r\n\t\ti = 2;\r\n\t\twhile (i--) {\r\n\r\n\t\t\tvar slots = [],\r\n\t\t\t\tslotsLength,\r\n\t\t\t\tusedSlots = [],\r\n\t\t\t\tpoints = halves[i],\r\n\t\t\t\tpos,\r\n\t\t\t\tlength = points.length,\r\n\t\t\t\tslotIndex;\r\n\t\t\t\t\r\n\t\t\t// Sort by angle\r\n\t\t\tseries.sortByAngle(points, i - 0.5);\r\n\r\n\t\t\t// Only do anti-collision when we are outside the pie and have connectors (#856)\r\n\t\t\tif (distanceOption > 0) {\r\n\t\t\t\t\r\n\t\t\t\t// build the slots\r\n\t\t\t\tfor (pos = centerY - radius - distanceOption; pos <= centerY + radius + distanceOption; pos += labelHeight) {\r\n\t\t\t\t\tslots.push(pos);\r\n\t\t\t\t\t\r\n\t\t\t\t\t// visualize the slot\r\n\t\t\t\t\t/*\r\n\t\t\t\t\tvar slotX = series.getX(pos, i) + chart.plotLeft - (i ? 100 : 0),\r\n\t\t\t\t\t\tslotY = pos + chart.plotTop;\r\n\t\t\t\t\tif (!isNaN(slotX)) {\r\n\t\t\t\t\t\tchart.renderer.rect(slotX, slotY - 7, 100, labelHeight, 1)\r\n\t\t\t\t\t\t\t.attr({\r\n\t\t\t\t\t\t\t\t'stroke-width': 1,\r\n\t\t\t\t\t\t\t\tstroke: 'silver'\r\n\t\t\t\t\t\t\t})\r\n\t\t\t\t\t\t\t.add();\r\n\t\t\t\t\t\tchart.renderer.text('Slot '+ (slots.length - 1), slotX, slotY + 4)\r\n\t\t\t\t\t\t\t.attr({\r\n\t\t\t\t\t\t\t\tfill: 'silver'\r\n\t\t\t\t\t\t\t}).add();\r\n\t\t\t\t\t}\r\n\t\t\t\t\t*/\r\n\t\t\t\t}\r\n\t\t\t\tslotsLength = slots.length;\r\n\t\r\n\t\t\t\t// if there are more values than available slots, remove lowest values\r\n\t\t\t\tif (length > slotsLength) {\r\n\t\t\t\t\t// create an array for sorting and ranking the points within each quarter\r\n\t\t\t\t\trankArr = [].concat(points);\r\n\t\t\t\t\trankArr.sort(sort);\r\n\t\t\t\t\tj = length;\r\n\t\t\t\t\twhile (j--) {\r\n\t\t\t\t\t\trankArr[j].rank = j;\r\n\t\t\t\t\t}\r\n\t\t\t\t\tj = length;\r\n\t\t\t\t\twhile (j--) {\r\n\t\t\t\t\t\tif (points[j].rank >= slotsLength) {\r\n\t\t\t\t\t\t\tpoints.splice(j, 1);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\tlength = points.length;\r\n\t\t\t\t}\r\n\t\r\n\t\t\t\t// The label goes to the nearest open slot, but not closer to the edge than\r\n\t\t\t\t// the label's index.\r\n\t\t\t\tfor (j = 0; j < length; j++) {\r\n\t\r\n\t\t\t\t\tpoint = points[j];\r\n\t\t\t\t\tlabelPos = point.labelPos;\r\n\t\r\n\t\t\t\t\tvar closest = 9999,\r\n\t\t\t\t\t\tdistance,\r\n\t\t\t\t\t\tslotI;\r\n\t\r\n\t\t\t\t\t// find the closest slot index\r\n\t\t\t\t\tfor (slotI = 0; slotI < slotsLength; slotI++) {\r\n\t\t\t\t\t\tdistance = mathAbs(slots[slotI] - labelPos[1]);\r\n\t\t\t\t\t\tif (distance < closest) {\r\n\t\t\t\t\t\t\tclosest = distance;\r\n\t\t\t\t\t\t\tslotIndex = slotI;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\r\n\t\t\t\t\t// if that slot index is closer to the edges of the slots, move it\r\n\t\t\t\t\t// to the closest appropriate slot\r\n\t\t\t\t\tif (slotIndex < j && slots[j] !== null) { // cluster at the top\r\n\t\t\t\t\t\tslotIndex = j;\r\n\t\t\t\t\t} else if (slotsLength  < length - j + slotIndex && slots[j] !== null) { // cluster at the bottom\r\n\t\t\t\t\t\tslotIndex = slotsLength - length + j;\r\n\t\t\t\t\t\twhile (slots[slotIndex] === null) { // make sure it is not taken\r\n\t\t\t\t\t\t\tslotIndex++;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\t// Slot is taken, find next free slot below. In the next run, the next slice will find the\r\n\t\t\t\t\t\t// slot above these, because it is the closest one\r\n\t\t\t\t\t\twhile (slots[slotIndex] === null) { // make sure it is not taken\r\n\t\t\t\t\t\t\tslotIndex++;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\r\n\t\t\t\t\tusedSlots.push({ i: slotIndex, y: slots[slotIndex] });\r\n\t\t\t\t\tslots[slotIndex] = null; // mark as taken\r\n\t\t\t\t}\r\n\t\t\t\t// sort them in order to fill in from the top\r\n\t\t\t\tusedSlots.sort(sort);\r\n\t\t\t}\r\n\r\n\t\t\t// now the used slots are sorted, fill them up sequentially\r\n\t\t\tfor (j = 0; j < length; j++) {\r\n\t\t\t\t\r\n\t\t\t\tvar slot, naturalY;\r\n\r\n\t\t\t\tpoint = points[j];\r\n\t\t\t\tlabelPos = point.labelPos;\r\n\t\t\t\tdataLabel = point.dataLabel;\r\n\t\t\t\tvisibility = point.visible === false ? HIDDEN : VISIBLE;\r\n\t\t\t\tnaturalY = labelPos[1];\r\n\t\t\t\t\r\n\t\t\t\tif (distanceOption > 0) {\r\n\t\t\t\t\tslot = usedSlots.pop();\r\n\t\t\t\t\tslotIndex = slot.i;\r\n\r\n\t\t\t\t\t// if the slot next to currrent slot is free, the y value is allowed\r\n\t\t\t\t\t// to fall back to the natural position\r\n\t\t\t\t\ty = slot.y;\r\n\t\t\t\t\tif ((naturalY > y && slots[slotIndex + 1] !== null) ||\r\n\t\t\t\t\t\t\t(naturalY < y &&  slots[slotIndex - 1] !== null)) {\r\n\t\t\t\t\t\ty = naturalY;\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t} else {\r\n\t\t\t\t\ty = naturalY;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// get the x - use the natural x position for first and last slot, to prevent the top\r\n\t\t\t\t// and botton slice connectors from touching each other on either side\r\n\t\t\t\tx = options.justify ? \r\n\t\t\t\t\tseriesCenter[0] + (i ? -1 : 1) * (radius + distanceOption) :\r\n\t\t\t\t\tseries.getX(slotIndex === 0 || slotIndex === slots.length - 1 ? naturalY : y, i);\r\n\t\t\t\t\r\n\t\t\t\r\n\t\t\t\t// Record the placement and visibility\r\n\t\t\t\tdataLabel._attr = {\r\n\t\t\t\t\tvisibility: visibility,\r\n\t\t\t\t\talign: labelPos[6]\r\n\t\t\t\t};\r\n\t\t\t\tdataLabel._pos = {\r\n\t\t\t\t\tx: x + options.x +\r\n\t\t\t\t\t\t({ left: connectorPadding, right: -connectorPadding }[labelPos[6]] || 0),\r\n\t\t\t\t\ty: y + options.y - 10 // 10 is for the baseline (label vs text)\r\n\t\t\t\t};\r\n\t\t\t\tdataLabel.connX = x;\r\n\t\t\t\tdataLabel.connY = y;\r\n\t\t\t\t\r\n\t\t\t\t\t\t\r\n\t\t\t\t// Detect overflowing data labels\r\n\t\t\t\tif (this.options.size === null) {\r\n\t\t\t\t\tdataLabelWidth = dataLabel.width;\r\n\t\t\t\t\t// Overflow left\r\n\t\t\t\t\tif (x - dataLabelWidth < connectorPadding) {\r\n\t\t\t\t\t\toverflow[3] = mathMax(mathRound(dataLabelWidth - x + connectorPadding), overflow[3]);\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t// Overflow right\r\n\t\t\t\t\t} else if (x + dataLabelWidth > plotWidth - connectorPadding) {\r\n\t\t\t\t\t\toverflow[1] = mathMax(mathRound(x + dataLabelWidth - plotWidth + connectorPadding), overflow[1]);\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\t// Overflow top\r\n\t\t\t\t\tif (y - labelHeight / 2 < 0) {\r\n\t\t\t\t\t\toverflow[0] = mathMax(mathRound(-y + labelHeight / 2), overflow[0]);\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t// Overflow left\r\n\t\t\t\t\t} else if (y + labelHeight / 2 > plotHeight) {\r\n\t\t\t\t\t\toverflow[2] = mathMax(mathRound(y + labelHeight / 2 - plotHeight), overflow[2]);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t} // for each point\r\n\t\t} // for each half\r\n\t\t\r\n\t\t// Do not apply the final placement and draw the connectors until we have verified\r\n\t\t// that labels are not spilling over. \r\n\t\tif (arrayMax(overflow) === 0 || this.verifyDataLabelOverflow(overflow)) {\r\n\t\t\t\r\n\t\t\t// Place the labels in the final position\r\n\t\t\tthis.placeDataLabels();\r\n\t\t\t\r\n\t\t\t// Draw the connectors\r\n\t\t\tif (outside && connectorWidth) {\r\n\t\t\t\teach(this.points, function (point) {\r\n\t\t\t\t\tconnector = point.connector;\r\n\t\t\t\t\tlabelPos = point.labelPos;\r\n\t\t\t\t\tdataLabel = point.dataLabel;\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (dataLabel && dataLabel._pos) {\r\n\t\t\t\t\t\tvisibility = dataLabel._attr.visibility;\r\n\t\t\t\t\t\tx = dataLabel.connX;\r\n\t\t\t\t\t\ty = dataLabel.connY;\r\n\t\t\t\t\t\tconnectorPath = softConnector ? [\r\n\t\t\t\t\t\t\tM,\r\n\t\t\t\t\t\t\tx + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label\r\n\t\t\t\t\t\t\t'C',\r\n\t\t\t\t\t\t\tx, y, // first break, next to the label\r\n\t\t\t\t\t\t\t2 * labelPos[2] - labelPos[4], 2 * labelPos[3] - labelPos[5],\r\n\t\t\t\t\t\t\tlabelPos[2], labelPos[3], // second break\r\n\t\t\t\t\t\t\tL,\r\n\t\t\t\t\t\t\tlabelPos[4], labelPos[5] // base\r\n\t\t\t\t\t\t] : [\r\n\t\t\t\t\t\t\tM,\r\n\t\t\t\t\t\t\tx + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label\r\n\t\t\t\t\t\t\tL,\r\n\t\t\t\t\t\t\tlabelPos[2], labelPos[3], // second break\r\n\t\t\t\t\t\t\tL,\r\n\t\t\t\t\t\t\tlabelPos[4], labelPos[5] // base\r\n\t\t\t\t\t\t];\r\n\t\t\r\n\t\t\t\t\t\tif (connector) {\r\n\t\t\t\t\t\t\tconnector.animate({ d: connectorPath });\r\n\t\t\t\t\t\t\tconnector.attr('visibility', visibility);\r\n\t\t\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\tpoint.connector = connector = series.chart.renderer.path(connectorPath).attr({\r\n\t\t\t\t\t\t\t\t'stroke-width': connectorWidth,\r\n\t\t\t\t\t\t\t\tstroke: options.connectorColor || point.color || '#606060',\r\n\t\t\t\t\t\t\t\tvisibility: visibility\r\n\t\t\t\t\t\t\t})\r\n\t\t\t\t\t\t\t.add(series.group);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t} else if (connector) {\r\n\t\t\t\t\t\tpoint.connector = connector.destroy();\r\n\t\t\t\t\t}\r\n\t\t\t\t});\r\n\t\t\t}\t\t\t\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Verify whether the data labels are allowed to draw, or we should run more translation and data\r\n\t * label positioning to keep them inside the plot area. Returns true when data labels are ready \r\n\t * to draw.\r\n\t */\r\n\tverifyDataLabelOverflow: function (overflow) {\r\n\t\t\r\n\t\tvar center = this.center,\r\n\t\t\toptions = this.options,\r\n\t\t\tcenterOption = options.center,\r\n\t\t\tminSize = options.minSize || 80,\r\n\t\t\tnewSize = minSize,\r\n\t\t\tret;\r\n\t\t\t\r\n\t\t// Handle horizontal size and center\r\n\t\tif (centerOption[0] !== null) { // Fixed center\r\n\t\t\tnewSize = mathMax(center[2] - mathMax(overflow[1], overflow[3]), minSize);\r\n\t\t\t\r\n\t\t} else { // Auto center\r\n\t\t\tnewSize = mathMax(\r\n\t\t\t\tcenter[2] - overflow[1] - overflow[3], // horizontal overflow\t\t\t\t\t\r\n\t\t\t\tminSize\r\n\t\t\t);\r\n\t\t\tcenter[0] += (overflow[3] - overflow[1]) / 2; // horizontal center\r\n\t\t}\r\n\t\t\r\n\t\t// Handle vertical size and center\r\n\t\tif (centerOption[1] !== null) { // Fixed center\r\n\t\t\tnewSize = mathMax(mathMin(newSize, center[2] - mathMax(overflow[0], overflow[2])), minSize);\r\n\t\t\t\r\n\t\t} else { // Auto center\r\n\t\t\tnewSize = mathMax(\r\n\t\t\t\tmathMin(\r\n\t\t\t\t\tnewSize,\t\t\r\n\t\t\t\t\tcenter[2] - overflow[0] - overflow[2] // vertical overflow\r\n\t\t\t\t),\r\n\t\t\t\tminSize\r\n\t\t\t);\r\n\t\t\tcenter[1] += (overflow[0] - overflow[2]) / 2; // vertical center\r\n\t\t}\r\n\t\t\r\n\t\t// If the size must be decreased, we need to run translate and drawDataLabels again\r\n\t\tif (newSize < center[2]) {\r\n\t\t\tcenter[2] = newSize;\r\n\t\t\tthis.translate(center);\r\n\t\t\teach(this.points, function (point) {\r\n\t\t\t\tif (point.dataLabel) {\r\n\t\t\t\t\tpoint.dataLabel._pos = null; // reset\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t\tthis.drawDataLabels();\r\n\t\t\t\r\n\t\t// Else, return true to indicate that the pie and its labels is within the plot area\r\n\t\t} else {\r\n\t\t\tret = true;\r\n\t\t}\r\n\t\treturn ret;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Perform the final placement of the data labels after we have verified that they\r\n\t * fall within the plot area.\r\n\t */\r\n\tplaceDataLabels: function () {\r\n\t\teach(this.points, function (point) {\r\n\t\t\tvar dataLabel = point.dataLabel,\r\n\t\t\t\t_pos;\r\n\t\t\t\r\n\t\t\tif (dataLabel) {\r\n\t\t\t\t_pos = dataLabel._pos;\r\n\t\t\t\tif (_pos) {\r\n\t\t\t\t\tdataLabel.attr(dataLabel._attr);\t\t\t\r\n\t\t\t\t\tdataLabel[dataLabel.moved ? 'animate' : 'attr'](_pos);\r\n\t\t\t\t\tdataLabel.moved = true;\r\n\t\t\t\t} else if (dataLabel) {\r\n\t\t\t\t\tdataLabel.attr({ y: -999 });\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t});\r\n\t},\r\n\t\r\n\talignDataLabel: noop,\r\n\r\n\t/**\r\n\t * Draw point specific tracker objects. Inherit directly from column series.\r\n\t */\r\n\tdrawTracker: ColumnSeries.prototype.drawTracker,\r\n\r\n\t/**\r\n\t * Use a simple symbol from column prototype\r\n\t */\r\n\tdrawLegendSymbol: AreaSeries.prototype.drawLegendSymbol,\r\n\r\n\t/**\r\n\t * Pies don't have point marker symbols\r\n\t */\r\n\tgetSymbol: noop\r\n\r\n};\r\nPieSeries = extendClass(Series, PieSeries);\r\nseriesTypes.pie = PieSeries;\r\n\r\n\r\n// global variables\r\nextend(Highcharts, {\r\n\t\r\n\t// Constructors\r\n\tAxis: Axis,\r\n\tChart: Chart,\r\n\tColor: Color,\r\n\tLegend: Legend,\r\n\tPointer: Pointer,\r\n\tPoint: Point,\r\n\tTick: Tick,\r\n\tTooltip: Tooltip,\r\n\tRenderer: Renderer,\r\n\tSeries: Series,\r\n\tSVGElement: SVGElement,\r\n\tSVGRenderer: SVGRenderer,\r\n\t\r\n\t// Various\r\n\tarrayMin: arrayMin,\r\n\tarrayMax: arrayMax,\r\n\tcharts: charts,\r\n\tdateFormat: dateFormat,\r\n\tformat: format,\r\n\tpathAnim: pathAnim,\r\n\tgetOptions: getOptions,\r\n\thasBidiBug: hasBidiBug,\r\n\tisTouchDevice: isTouchDevice,\r\n\tnumberFormat: numberFormat,\r\n\tseriesTypes: seriesTypes,\r\n\tsetOptions: setOptions,\r\n\taddEvent: addEvent,\r\n\tremoveEvent: removeEvent,\r\n\tcreateElement: createElement,\r\n\tdiscardElement: discardElement,\r\n\tcss: css,\r\n\teach: each,\r\n\textend: extend,\r\n\tmap: map,\r\n\tmerge: merge,\r\n\tpick: pick,\r\n\tsplat: splat,\r\n\textendClass: extendClass,\r\n\tpInt: pInt,\r\n\twrap: wrap,\r\n\tsvg: hasSVG,\r\n\tcanvas: useCanVG,\r\n\tvml: !hasSVG && !useCanVG,\r\n\tproduct: PRODUCT,\r\n\tversion: VERSION\r\n});\r\n}());\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/highcharts/modules/annotations.js",
    "content": "(function(i,C){function m(a){return typeof a===\"number\"}function n(a){return a!==D&&a!==null}var D,p,r,s=i.Chart,t=i.extend,z=i.each;r=[\"path\",\"rect\",\"circle\"];p={top:0,left:0,center:0.5,middle:0.5,bottom:1,right:1};var u=C.inArray,A=i.merge,B=function(){this.init.apply(this,arguments)};B.prototype={init:function(a,d){var c=d.shape&&d.shape.type;this.chart=a;var b,f;f={xAxis:0,yAxis:0,title:{style:{},text:\"\",x:0,y:0},shape:{params:{stroke:\"#000000\",fill:\"transparent\",strokeWidth:2}}};b={circle:{params:{x:0,\r\ny:0}}};if(b[c])f.shape=A(f.shape,b[c]);this.options=A({},f,d)},render:function(a){var d=this.chart,c=this.chart.renderer,b=this.group,f=this.title,e=this.shape,h=this.options,i=h.title,l=h.shape;if(!b)b=this.group=c.g();if(!e&&l&&u(l.type,r)!==-1)e=this.shape=c[h.shape.type](l.params),e.add(b);if(!f&&i)f=this.title=c.label(i),f.add(b);b.add(d.annotations.group);this.linkObjects();a!==!1&&this.redraw()},redraw:function(){var a=this.options,d=this.chart,c=this.group,b=this.title,f=this.shape,e=this.linkedObject,\r\nh=d.xAxis[a.xAxis],v=d.yAxis[a.yAxis],l=a.width,w=a.height,x=p[a.anchorY],y=p[a.anchorX],j,o,g,q;if(e)j=e instanceof i.Point?\"point\":e instanceof i.Series?\"series\":null,j===\"point\"?(a.xValue=e.x,a.yValue=e.y,o=e.series):j===\"series\"&&(o=e),c.visibility!==o.group.visibility&&c.attr({visibility:o.group.visibility});e=n(a.xValue)?h.toPixels(a.xValue+h.minPointOffset)-h.minPixelPadding:a.x;j=n(a.yValue)?v.toPixels(a.yValue):a.y;if(!isNaN(e)&&!isNaN(j)&&m(e)&&m(j)){b&&(b.attr(a.title),b.css(a.title.style));\r\nif(f){b=t({},a.shape.params);if(a.units===\"values\"){for(g in b)u(g,[\"width\",\"x\"])>-1?b[g]=h.translate(b[g]):u(g,[\"height\",\"y\"])>-1&&(b[g]=v.translate(b[g]));b.width&&(b.width-=h.toPixels(0)-h.left);b.x&&(b.x+=h.minPixelPadding);if(a.shape.type===\"path\"){g=b.d;o=e;for(var r=j,s=g.length,k=0;k<s;)typeof g[k]===\"number\"&&typeof g[k+1]===\"number\"?(g[k]=h.toPixels(g[k])-o,g[k+1]=v.toPixels(g[k+1])-r,k+=2):k+=1}}a.shape.type===\"circle\"&&(b.x+=b.r,b.y+=b.r);f.attr(b)}c.bBox=null;if(!m(l))q=c.getBBox(),l=\r\nq.width;if(!m(w))q||(q=c.getBBox()),w=q.height;if(!m(y))y=p.center;if(!m(x))x=p.center;e-=l*y;j-=w*x;d.animation&&n(c.translateX)&&n(c.translateY)?c.animate({translateX:e,translateY:j}):c.translate(e,j)}},destroy:function(){var a=this,d=this.chart.annotations.allItems,c=d.indexOf(a);c>-1&&d.splice(c,1);z([\"title\",\"shape\",\"group\"],function(b){a[b]&&(a[b].destroy(),a[b]=null)});a.group=a.title=a.shape=a.chart=a.options=null},update:function(a,d){t(this.options,a);this.linkObjects();this.render(d)},\r\nlinkObjects:function(){var a=this.chart,d=this.linkedObject,c=d&&(d.id||d.options.id),b=this.options.linkedTo;if(n(b)){if(!n(d)||b!==c)this.linkedObject=a.get(b)}else this.linkedObject=null}};t(s.prototype,{annotations:{add:function(a,d){var c=this.allItems,b=this.chart,f,e;Object.prototype.toString.call(a)===\"[object Array]\"||(a=[a]);for(e=a.length;e--;)f=new B(b,a[e]),c.push(f),f.render(d)},redraw:function(){z(this.allItems,function(a){a.redraw()})}}});s.prototype.callbacks.push(function(a){var d=\r\na.options.annotations,c;c=a.renderer.g(\"annotations\");c.attr({zIndex:7});c.add();a.annotations.allItems=[];a.annotations.chart=a;a.annotations.group=c;Object.prototype.toString.call(d)===\"[object Array]\"&&d.length>0&&a.annotations.add(a.options.annotations);i.addEvent(a,\"redraw\",function(){a.annotations.redraw()})})})(Highcharts,HighchartsAdapter);\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/highcharts/modules/annotations.src.js",
    "content": "(function (Highcharts, HighchartsAdapter) {\r\n\r\nvar UNDEFINED,\r\n\tALIGN_FACTOR,\r\n\tALLOWED_SHAPES,\r\n\tChart = Highcharts.Chart,\r\n\textend = Highcharts.extend,\r\n\teach = Highcharts.each;\r\n\r\nALLOWED_SHAPES = [\"path\", \"rect\", \"circle\"];\r\n\r\nALIGN_FACTOR = {\r\n\ttop: 0,\r\n\tleft: 0,\r\n\tcenter: 0.5,\r\n\tmiddle: 0.5,\r\n\tbottom: 1,\r\n\tright: 1\r\n};\r\n\r\n\r\n// Highcharts helper methods\r\nvar inArray = HighchartsAdapter.inArray,\r\n\tmerge = Highcharts.merge;\r\n\r\nfunction defaultOptions(shapeType) {\r\n\tvar shapeOptions,\r\n\t\toptions;\r\n\r\n\toptions = {\r\n\t\txAxis: 0,\r\n\t\tyAxis: 0,\r\n\t\ttitle: {\r\n\t\t\tstyle: {},\r\n\t\t\ttext: \"\",\r\n\t\t\tx: 0,\r\n\t\t\ty: 0\r\n\t\t},\r\n\t\tshape: {\r\n\t\t\tparams: {\r\n\t\t\t\tstroke: \"#000000\",\r\n\t\t\t\tfill: \"transparent\",\r\n\t\t\t\tstrokeWidth: 2\r\n\t\t\t}\r\n\t\t}\r\n\t};\r\n\r\n\tshapeOptions = {\r\n\t\tcircle: {\r\n\t\t\tparams: {\r\n\t\t\t\tx: 0,\r\n\t\t\t\ty: 0\r\n\t\t\t}\r\n\t\t}\r\n\t};\r\n\r\n\tif (shapeOptions[shapeType]) {\r\n\t\toptions.shape = merge(options.shape, shapeOptions[shapeType]);\r\n\t}\r\n\r\n\treturn options;\r\n}\r\n\r\nfunction isArray(obj) {\r\n\treturn Object.prototype.toString.call(obj) === '[object Array]';\r\n}\r\n\r\nfunction isNumber(n) {\r\n\treturn typeof n === 'number';\r\n}\r\n\r\nfunction defined(obj) {\r\n\treturn obj !== UNDEFINED && obj !== null;\r\n}\r\n\r\nfunction translatePath(d, xAxis, yAxis, xOffset, yOffset) {\r\n\tvar len = d.length,\r\n\t\ti = 0;\r\n\r\n\twhile (i < len) {\r\n\t\tif (typeof d[i] === 'number' && typeof d[i + 1] === 'number') {\r\n\t\t\td[i] = xAxis.toPixels(d[i]) - xOffset;\r\n\t\t\td[i + 1] = yAxis.toPixels(d[i + 1]) - yOffset;\r\n\t\t\ti += 2;\r\n\t\t} else {\r\n\t\t\ti += 1;\r\n\t\t}\r\n\t}\r\n\r\n\treturn d;\r\n}\r\n\r\n\r\n// Define annotation prototype\r\nvar Annotation = function () {\r\n\tthis.init.apply(this, arguments);\r\n};\r\nAnnotation.prototype = {\r\n\t/* \r\n\t * Initialize the annotation\r\n\t */\r\n\tinit: function (chart, options) {\r\n\t\tvar shapeType = options.shape && options.shape.type;\r\n\r\n\t\tthis.chart = chart;\r\n\t\tthis.options = merge({}, defaultOptions(shapeType), options);\r\n\t},\r\n\r\n\t/*\r\n\t * Render the annotation\r\n\t */\r\n\trender: function (redraw) {\r\n\t\tvar annotation = this,\r\n\t\t\tchart = this.chart,\r\n\t\t\trenderer = annotation.chart.renderer,\r\n\t\t\tgroup = annotation.group,\r\n\t\t\ttitle = annotation.title,\r\n\t\t\tshape = annotation.shape,\r\n\t\t\toptions = annotation.options,\r\n\t\t\ttitleOptions = options.title,\r\n\t\t\tshapeOptions = options.shape;\r\n\r\n\t\tif (!group) {\r\n\t\t\tgroup = annotation.group = renderer.g();\r\n\t\t}\r\n\r\n\r\n\t\tif (!shape && shapeOptions && inArray(shapeOptions.type, ALLOWED_SHAPES) !== -1) {\r\n\t\t\tshape = annotation.shape = renderer[options.shape.type](shapeOptions.params);\r\n\t\t\tshape.add(group);\r\n\t\t}\r\n\r\n\t\tif (!title && titleOptions) {\r\n\t\t\ttitle = annotation.title = renderer.label(titleOptions);\r\n\t\t\ttitle.add(group);\r\n\t\t}\r\n\r\n\t\tgroup.add(chart.annotations.group);\r\n\r\n\t\t// link annotations to point or series\r\n\t\tannotation.linkObjects();\r\n\r\n\t\tif (redraw !== false) {\r\n\t\t\tannotation.redraw();\r\n\t\t}\r\n\t},\r\n\r\n\t/*\r\n\t * Redraw the annotation title or shape after options update\r\n\t */\r\n\tredraw: function () {\r\n\t\tvar options = this.options,\r\n\t\t\tchart = this.chart,\r\n\t\t\tgroup = this.group,\r\n\t\t\ttitle = this.title,\r\n\t\t\tshape = this.shape,\r\n\t\t\tlinkedTo = this.linkedObject,\r\n\t\t\txAxis = chart.xAxis[options.xAxis],\r\n\t\t\tyAxis = chart.yAxis[options.yAxis],\r\n\t\t\twidth = options.width,\r\n\t\t\theight = options.height,\r\n\t\t\tanchorY = ALIGN_FACTOR[options.anchorY],\r\n\t\t\tanchorX = ALIGN_FACTOR[options.anchorX],\r\n\t\t\tresetBBox = false,\r\n\t\t\tshapeParams,\r\n\t\t\tlinkType,\r\n\t\t\tseries,\r\n\t\t\tparam,\r\n\t\t\tbbox,\r\n\t\t\tx,\r\n\t\t\ty;\r\n\r\n\t\tif (linkedTo) {\r\n\t\t\tlinkType = (linkedTo instanceof Highcharts.Point) ? 'point' :\r\n\t\t\t\t\t\t(linkedTo instanceof Highcharts.Series) ? 'series' : null;\r\n\r\n\t\t\tif (linkType === 'point') {\r\n\t\t\t\toptions.xValue = linkedTo.x;\r\n\t\t\t\toptions.yValue = linkedTo.y;\r\n\t\t\t\tseries = linkedTo.series;\r\n\t\t\t} else if (linkType === 'series') {\r\n\t\t\t\tseries = linkedTo;\r\n\t\t\t}\r\n\r\n\t\t\tif (group.visibility !== series.group.visibility) {\r\n\t\t\t\tgroup.attr({\r\n\t\t\t\t\tvisibility: series.group.visibility\r\n\t\t\t\t});\r\n\t\t\t}\r\n\t\t}\r\n\r\n\r\n\t\t// Based on given options find annotation pixel position\r\n\t\tx = (defined(options.xValue) ? xAxis.toPixels(options.xValue + xAxis.minPointOffset) - xAxis.minPixelPadding : options.x);\r\n\t\ty = defined(options.yValue) ? yAxis.toPixels(options.yValue) : options.y;\r\n\r\n\t\tif (isNaN(x) || isNaN(y) || !isNumber(x) || !isNumber(y)) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\r\n\t\tif (title) {\r\n\t\t\ttitle.attr(options.title);\r\n\t\t\ttitle.css(options.title.style);\r\n\t\t\tresetBBox = true;\r\n\t\t}\r\n\r\n\t\tif (shape) {\r\n\t\t\tshapeParams = extend({}, options.shape.params);\r\n\r\n\t\t\tif (options.units === 'values') {\r\n\t\t\t\tfor (param in shapeParams) {\r\n\t\t\t\t\tif (inArray(param, ['width', 'x']) > -1) {\r\n\t\t\t\t\t\tshapeParams[param] = xAxis.translate(shapeParams[param]);\r\n\t\t\t\t\t} else if (inArray(param, ['height', 'y']) > -1) {\r\n\t\t\t\t\t\tshapeParams[param] = yAxis.translate(shapeParams[param]);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (shapeParams.width) {\r\n\t\t\t\t\tshapeParams.width -= xAxis.toPixels(0) - xAxis.left;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (shapeParams.x) {\r\n\t\t\t\t\tshapeParams.x += xAxis.minPixelPadding;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (options.shape.type === 'path') {\r\n\t\t\t\t\ttranslatePath(shapeParams.d, xAxis, yAxis, x, y);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// move the center of the circle to shape x/y\r\n\t\t\tif (options.shape.type === 'circle') {\r\n\t\t\t\tshapeParams.x += shapeParams.r;\r\n\t\t\t\tshapeParams.y += shapeParams.r;\r\n\t\t\t}\r\n\r\n\t\t\tresetBBox = true;\r\n\t\t\tshape.attr(shapeParams);\r\n\t\t}\r\n\r\n\t\tgroup.bBox = null;\r\n\r\n\t\t// If annotation width or height is not defined in options use bounding box size\r\n\t\tif (!isNumber(width)) {\r\n\t\t\tbbox = group.getBBox();\r\n\t\t\twidth = bbox.width;\r\n\t\t}\r\n\r\n\t\tif (!isNumber(height)) {\r\n\t\t\t// get bbox only if it wasn't set before\r\n\t\t\tif (!bbox) {\r\n\t\t\t\tbbox = group.getBBox();\r\n\t\t\t}\r\n\r\n\t\t\theight = bbox.height;\r\n\t\t}\r\n\r\n\t\t// Calculate anchor point\r\n\t\tif (!isNumber(anchorX)) {\r\n\t\t\tanchorX = ALIGN_FACTOR.center;\r\n\t\t}\r\n\r\n\t\tif (!isNumber(anchorY)) {\r\n\t\t\tanchorY = ALIGN_FACTOR.center;\r\n\t\t}\r\n\r\n\t\t// Translate group according to its dimension and anchor point\r\n\t\tx = x - width * anchorX;\r\n\t\ty = y - height * anchorY;\r\n\r\n\t\tif (chart.animation && defined(group.translateX) && defined(group.translateY)) {\r\n\t\t\tgroup.animate({\r\n\t\t\t\ttranslateX: x,\r\n\t\t\t\ttranslateY: y\r\n\t\t\t});\r\n\t\t} else {\r\n\t\t\tgroup.translate(x, y);\r\n\t\t}\r\n\t},\r\n\r\n\t/*\r\n\t * Destroy the annotation\r\n\t */\r\n\tdestroy: function () {\r\n\t\tvar annotation = this,\r\n\t\t\tchart = this.chart,\r\n\t\t\tallItems = chart.annotations.allItems,\r\n\t\t\tindex = allItems.indexOf(annotation);\r\n\r\n\t\tif (index > -1) {\r\n\t\t\tallItems.splice(index, 1);\r\n\t\t}\r\n\r\n\t\teach(['title', 'shape', 'group'], function (element) {\r\n\t\t\tif (annotation[element]) {\r\n\t\t\t\tannotation[element].destroy();\r\n\t\t\t\tannotation[element] = null;\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\tannotation.group = annotation.title = annotation.shape = annotation.chart = annotation.options = null;\r\n\t},\r\n\r\n\t/*\r\n\t * Update the annotation with a given options\r\n\t */\r\n\tupdate: function (options, redraw) {\r\n\t\textend(this.options, options);\r\n\r\n\t\t// update link to point or series\r\n\t\tthis.linkObjects();\r\n\r\n\t\tthis.render(redraw);\r\n\t},\r\n\r\n\tlinkObjects: function () {\r\n\t\tvar annotation = this,\r\n\t\t\tchart = annotation.chart,\r\n\t\t\tlinkedTo = annotation.linkedObject,\r\n\t\t\tlinkedId = linkedTo && (linkedTo.id || linkedTo.options.id),\r\n\t\t\toptions = annotation.options,\r\n\t\t\tid = options.linkedTo;\r\n\r\n\t\tif (!defined(id)) {\r\n\t\t\tannotation.linkedObject = null;\r\n\t\t} else if (!defined(linkedTo) || id !== linkedId) {\r\n\t\t\tannotation.linkedObject = chart.get(id);\r\n\t\t}\r\n\t}\r\n};\r\n\r\n\r\n// Add annotations methods to chart prototype\r\nextend(Chart.prototype, {\r\n\tannotations: {\r\n\t\t/*\r\n\t\t * Unified method for adding annotations to the chart\r\n\t\t */\r\n\t\tadd: function (options, redraw) {\r\n\t\t\tvar annotations = this.allItems,\r\n\t\t\t\tchart = this.chart,\r\n\t\t\t\titem,\r\n\t\t\t\tlen;\r\n\r\n\t\t\tif (!isArray(options)) {\r\n\t\t\t\toptions = [options];\r\n\t\t\t}\r\n\r\n\t\t\tlen = options.length;\r\n\r\n\t\t\twhile (len--) {\r\n\t\t\t\titem = new Annotation(chart, options[len]);\r\n\t\t\t\tannotations.push(item);\r\n\t\t\t\titem.render(redraw);\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\t/**\r\n\t\t * Redraw all annotations, method used in chart events\r\n\t\t */\r\n\t\tredraw: function () {\r\n\t\t\teach(this.allItems, function (annotation) {\r\n\t\t\t\tannotation.redraw();\r\n\t\t\t});\r\n\t\t}\r\n\t}\r\n});\r\n\r\n\r\n// Initialize on chart load\r\nChart.prototype.callbacks.push(function (chart) {\r\n\tvar options = chart.options.annotations,\r\n\t\tgroup;\r\n\r\n\tgroup = chart.renderer.g(\"annotations\");\r\n\tgroup.attr({\r\n\t\tzIndex: 7\r\n\t});\r\n\tgroup.add();\r\n\r\n\t// initialize empty array for annotations\r\n\tchart.annotations.allItems = [];\r\n\r\n\t// link chart object to annotations\r\n\tchart.annotations.chart = chart;\r\n\r\n\t// link annotations group element to the chart\r\n\tchart.annotations.group = group;\r\n\r\n\tif (isArray(options) && options.length > 0) {\r\n\t\tchart.annotations.add(chart.options.annotations);\r\n\t}\r\n\r\n\t// update annotations after chart redraw\r\n\tHighcharts.addEvent(chart, 'redraw', function () {\r\n\t\tchart.annotations.redraw();\r\n\t});\r\n});\r\n}(Highcharts, HighchartsAdapter));\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/highcharts/modules/canvas-tools.js",
    "content": "/*\r\n A class to parse color values\r\n @author Stoyan Stefanov <sstoo@gmail.com>\r\n @link   http://www.phpied.com/rgb-color-parser-in-javascript/\r\n Use it if you like it\r\n\r\n canvg.js - Javascript SVG parser and renderer on Canvas\r\n MIT Licensed \r\n Gabe Lerner (gabelerner@gmail.com)\r\n http://code.google.com/p/canvg/\r\n\r\n Requires: rgbcolor.js - http://www.phpied.com/rgb-color-parser-in-javascript/\r\n\r\n Highcharts JS v3.0.6 (2013-10-04)\r\n CanVGRenderer Extension module\r\n\r\n (c) 2011-2012 Torstein Hønsi, Erik Olsson\r\n\r\n License: www.highcharts.com/license\r\n*/\r\nfunction RGBColor(m){this.ok=!1;m.charAt(0)==\"#\"&&(m=m.substr(1,6));var m=m.replace(/ /g,\"\"),m=m.toLowerCase(),a={aliceblue:\"f0f8ff\",antiquewhite:\"faebd7\",aqua:\"00ffff\",aquamarine:\"7fffd4\",azure:\"f0ffff\",beige:\"f5f5dc\",bisque:\"ffe4c4\",black:\"000000\",blanchedalmond:\"ffebcd\",blue:\"0000ff\",blueviolet:\"8a2be2\",brown:\"a52a2a\",burlywood:\"deb887\",cadetblue:\"5f9ea0\",chartreuse:\"7fff00\",chocolate:\"d2691e\",coral:\"ff7f50\",cornflowerblue:\"6495ed\",cornsilk:\"fff8dc\",crimson:\"dc143c\",cyan:\"00ffff\",darkblue:\"00008b\",\r\ndarkcyan:\"008b8b\",darkgoldenrod:\"b8860b\",darkgray:\"a9a9a9\",darkgreen:\"006400\",darkkhaki:\"bdb76b\",darkmagenta:\"8b008b\",darkolivegreen:\"556b2f\",darkorange:\"ff8c00\",darkorchid:\"9932cc\",darkred:\"8b0000\",darksalmon:\"e9967a\",darkseagreen:\"8fbc8f\",darkslateblue:\"483d8b\",darkslategray:\"2f4f4f\",darkturquoise:\"00ced1\",darkviolet:\"9400d3\",deeppink:\"ff1493\",deepskyblue:\"00bfff\",dimgray:\"696969\",dodgerblue:\"1e90ff\",feldspar:\"d19275\",firebrick:\"b22222\",floralwhite:\"fffaf0\",forestgreen:\"228b22\",fuchsia:\"ff00ff\",\r\ngainsboro:\"dcdcdc\",ghostwhite:\"f8f8ff\",gold:\"ffd700\",goldenrod:\"daa520\",gray:\"808080\",green:\"008000\",greenyellow:\"adff2f\",honeydew:\"f0fff0\",hotpink:\"ff69b4\",indianred:\"cd5c5c\",indigo:\"4b0082\",ivory:\"fffff0\",khaki:\"f0e68c\",lavender:\"e6e6fa\",lavenderblush:\"fff0f5\",lawngreen:\"7cfc00\",lemonchiffon:\"fffacd\",lightblue:\"add8e6\",lightcoral:\"f08080\",lightcyan:\"e0ffff\",lightgoldenrodyellow:\"fafad2\",lightgrey:\"d3d3d3\",lightgreen:\"90ee90\",lightpink:\"ffb6c1\",lightsalmon:\"ffa07a\",lightseagreen:\"20b2aa\",lightskyblue:\"87cefa\",\r\nlightslateblue:\"8470ff\",lightslategray:\"778899\",lightsteelblue:\"b0c4de\",lightyellow:\"ffffe0\",lime:\"00ff00\",limegreen:\"32cd32\",linen:\"faf0e6\",magenta:\"ff00ff\",maroon:\"800000\",mediumaquamarine:\"66cdaa\",mediumblue:\"0000cd\",mediumorchid:\"ba55d3\",mediumpurple:\"9370d8\",mediumseagreen:\"3cb371\",mediumslateblue:\"7b68ee\",mediumspringgreen:\"00fa9a\",mediumturquoise:\"48d1cc\",mediumvioletred:\"c71585\",midnightblue:\"191970\",mintcream:\"f5fffa\",mistyrose:\"ffe4e1\",moccasin:\"ffe4b5\",navajowhite:\"ffdead\",navy:\"000080\",\r\noldlace:\"fdf5e6\",olive:\"808000\",olivedrab:\"6b8e23\",orange:\"ffa500\",orangered:\"ff4500\",orchid:\"da70d6\",palegoldenrod:\"eee8aa\",palegreen:\"98fb98\",paleturquoise:\"afeeee\",palevioletred:\"d87093\",papayawhip:\"ffefd5\",peachpuff:\"ffdab9\",peru:\"cd853f\",pink:\"ffc0cb\",plum:\"dda0dd\",powderblue:\"b0e0e6\",purple:\"800080\",red:\"ff0000\",rosybrown:\"bc8f8f\",royalblue:\"4169e1\",saddlebrown:\"8b4513\",salmon:\"fa8072\",sandybrown:\"f4a460\",seagreen:\"2e8b57\",seashell:\"fff5ee\",sienna:\"a0522d\",silver:\"c0c0c0\",skyblue:\"87ceeb\",slateblue:\"6a5acd\",\r\nslategray:\"708090\",snow:\"fffafa\",springgreen:\"00ff7f\",steelblue:\"4682b4\",tan:\"d2b48c\",teal:\"008080\",thistle:\"d8bfd8\",tomato:\"ff6347\",turquoise:\"40e0d0\",violet:\"ee82ee\",violetred:\"d02090\",wheat:\"f5deb3\",white:\"ffffff\",whitesmoke:\"f5f5f5\",yellow:\"ffff00\",yellowgreen:\"9acd32\"},c;for(c in a)m==c&&(m=a[c]);var d=[{re:/^rgb\\((\\d{1,3}),\\s*(\\d{1,3}),\\s*(\\d{1,3})\\)$/,example:[\"rgb(123, 234, 45)\",\"rgb(255,234,245)\"],process:function(b){return[parseInt(b[1]),parseInt(b[2]),parseInt(b[3])]}},{re:/^(\\w{2})(\\w{2})(\\w{2})$/,\r\nexample:[\"#00ff00\",\"336699\"],process:function(b){return[parseInt(b[1],16),parseInt(b[2],16),parseInt(b[3],16)]}},{re:/^(\\w{1})(\\w{1})(\\w{1})$/,example:[\"#fb0\",\"f0f\"],process:function(b){return[parseInt(b[1]+b[1],16),parseInt(b[2]+b[2],16),parseInt(b[3]+b[3],16)]}}];for(c=0;c<d.length;c++){var b=d[c].process,k=d[c].re.exec(m);if(k)channels=b(k),this.r=channels[0],this.g=channels[1],this.b=channels[2],this.ok=!0}this.r=this.r<0||isNaN(this.r)?0:this.r>255?255:this.r;this.g=this.g<0||isNaN(this.g)?0:\r\nthis.g>255?255:this.g;this.b=this.b<0||isNaN(this.b)?0:this.b>255?255:this.b;this.toRGB=function(){return\"rgb(\"+this.r+\", \"+this.g+\", \"+this.b+\")\"};this.toHex=function(){var b=this.r.toString(16),a=this.g.toString(16),d=this.b.toString(16);b.length==1&&(b=\"0\"+b);a.length==1&&(a=\"0\"+a);d.length==1&&(d=\"0\"+d);return\"#\"+b+a+d};this.getHelpXML=function(){for(var b=[],k=0;k<d.length;k++)for(var c=d[k].example,j=0;j<c.length;j++)b[b.length]=c[j];for(var h in a)b[b.length]=h;c=document.createElement(\"ul\");\r\nc.setAttribute(\"id\",\"rgbcolor-examples\");for(k=0;k<b.length;k++)try{var l=document.createElement(\"li\"),o=new RGBColor(b[k]),n=document.createElement(\"div\");n.style.cssText=\"margin: 3px; border: 1px solid black; background:\"+o.toHex()+\"; color:\"+o.toHex();n.appendChild(document.createTextNode(\"test\"));var q=document.createTextNode(\" \"+b[k]+\" -> \"+o.toRGB()+\" -> \"+o.toHex());l.appendChild(n);l.appendChild(q);c.appendChild(l)}catch(p){}return c}}\r\nif(!window.console)window.console={},window.console.log=function(){},window.console.dir=function(){};if(!Array.prototype.indexOf)Array.prototype.indexOf=function(m){for(var a=0;a<this.length;a++)if(this[a]==m)return a;return-1};\r\n(function(){function m(){var a={FRAMERATE:30,MAX_VIRTUAL_PIXELS:3E4};a.init=function(c){a.Definitions={};a.Styles={};a.Animations=[];a.Images=[];a.ctx=c;a.ViewPort=new function(){this.viewPorts=[];this.Clear=function(){this.viewPorts=[]};this.SetCurrent=function(a,b){this.viewPorts.push({width:a,height:b})};this.RemoveCurrent=function(){this.viewPorts.pop()};this.Current=function(){return this.viewPorts[this.viewPorts.length-1]};this.width=function(){return this.Current().width};this.height=function(){return this.Current().height};\r\nthis.ComputeSize=function(a){return a!=null&&typeof a==\"number\"?a:a==\"x\"?this.width():a==\"y\"?this.height():Math.sqrt(Math.pow(this.width(),2)+Math.pow(this.height(),2))/Math.sqrt(2)}}};a.init();a.ImagesLoaded=function(){for(var c=0;c<a.Images.length;c++)if(!a.Images[c].loaded)return!1;return!0};a.trim=function(a){return a.replace(/^\\s+|\\s+$/g,\"\")};a.compressSpaces=function(a){return a.replace(/[\\s\\r\\t\\n]+/gm,\" \")};a.ajax=function(a){var d;return(d=window.XMLHttpRequest?new XMLHttpRequest:new ActiveXObject(\"Microsoft.XMLHTTP\"))?\r\n(d.open(\"GET\",a,!1),d.send(null),d.responseText):null};a.parseXml=function(a){if(window.DOMParser)return(new DOMParser).parseFromString(a,\"text/xml\");else{var a=a.replace(/<!DOCTYPE svg[^>]*>/,\"\"),d=new ActiveXObject(\"Microsoft.XMLDOM\");d.async=\"false\";d.loadXML(a);return d}};a.Property=function(c,d){this.name=c;this.value=d;this.hasValue=function(){return this.value!=null&&this.value!==\"\"};this.numValue=function(){if(!this.hasValue())return 0;var b=parseFloat(this.value);(this.value+\"\").match(/%$/)&&\r\n(b/=100);return b};this.valueOrDefault=function(b){return this.hasValue()?this.value:b};this.numValueOrDefault=function(b){return this.hasValue()?this.numValue():b};var b=this;this.Color={addOpacity:function(d){var c=b.value;if(d!=null&&d!=\"\"){var f=new RGBColor(b.value);f.ok&&(c=\"rgba(\"+f.r+\", \"+f.g+\", \"+f.b+\", \"+d+\")\")}return new a.Property(b.name,c)}};this.Definition={getDefinition:function(){var d=b.value.replace(/^(url\\()?#([^\\)]+)\\)?$/,\"$2\");return a.Definitions[d]},isUrl:function(){return b.value.indexOf(\"url(\")==\r\n0},getFillStyle:function(b){var d=this.getDefinition();return d!=null&&d.createGradient?d.createGradient(a.ctx,b):d!=null&&d.createPattern?d.createPattern(a.ctx,b):null}};this.Length={DPI:function(){return 96},EM:function(b){var d=12,c=new a.Property(\"fontSize\",a.Font.Parse(a.ctx.font).fontSize);c.hasValue()&&(d=c.Length.toPixels(b));return d},toPixels:function(d){if(!b.hasValue())return 0;var c=b.value+\"\";return c.match(/em$/)?b.numValue()*this.EM(d):c.match(/ex$/)?b.numValue()*this.EM(d)/2:c.match(/px$/)?\r\nb.numValue():c.match(/pt$/)?b.numValue()*1.25:c.match(/pc$/)?b.numValue()*15:c.match(/cm$/)?b.numValue()*this.DPI(d)/2.54:c.match(/mm$/)?b.numValue()*this.DPI(d)/25.4:c.match(/in$/)?b.numValue()*this.DPI(d):c.match(/%$/)?b.numValue()*a.ViewPort.ComputeSize(d):b.numValue()}};this.Time={toMilliseconds:function(){if(!b.hasValue())return 0;var a=b.value+\"\";if(a.match(/s$/))return b.numValue()*1E3;a.match(/ms$/);return b.numValue()}};this.Angle={toRadians:function(){if(!b.hasValue())return 0;var a=b.value+\r\n\"\";return a.match(/deg$/)?b.numValue()*(Math.PI/180):a.match(/grad$/)?b.numValue()*(Math.PI/200):a.match(/rad$/)?b.numValue():b.numValue()*(Math.PI/180)}}};a.Font=new function(){this.Styles=[\"normal\",\"italic\",\"oblique\",\"inherit\"];this.Variants=[\"normal\",\"small-caps\",\"inherit\"];this.Weights=\"normal,bold,bolder,lighter,100,200,300,400,500,600,700,800,900,inherit\".split(\",\");this.CreateFont=function(d,b,c,e,f,g){g=g!=null?this.Parse(g):this.CreateFont(\"\",\"\",\"\",\"\",\"\",a.ctx.font);return{fontFamily:f||\r\ng.fontFamily,fontSize:e||g.fontSize,fontStyle:d||g.fontStyle,fontWeight:c||g.fontWeight,fontVariant:b||g.fontVariant,toString:function(){return[this.fontStyle,this.fontVariant,this.fontWeight,this.fontSize,this.fontFamily].join(\" \")}}};var c=this;this.Parse=function(d){for(var b={},d=a.trim(a.compressSpaces(d||\"\")).split(\" \"),k=!1,e=!1,f=!1,g=!1,j=\"\",h=0;h<d.length;h++)if(!e&&c.Styles.indexOf(d[h])!=-1){if(d[h]!=\"inherit\")b.fontStyle=d[h];e=!0}else if(!g&&c.Variants.indexOf(d[h])!=-1){if(d[h]!=\"inherit\")b.fontVariant=\r\nd[h];e=g=!0}else if(!f&&c.Weights.indexOf(d[h])!=-1){if(d[h]!=\"inherit\")b.fontWeight=d[h];e=g=f=!0}else if(k)d[h]!=\"inherit\"&&(j+=d[h]);else{if(d[h]!=\"inherit\")b.fontSize=d[h].split(\"/\")[0];e=g=f=k=!0}if(j!=\"\")b.fontFamily=j;return b}};a.ToNumberArray=function(c){for(var c=a.trim(a.compressSpaces((c||\"\").replace(/,/g,\" \"))).split(\" \"),d=0;d<c.length;d++)c[d]=parseFloat(c[d]);return c};a.Point=function(a,d){this.x=a;this.y=d;this.angleTo=function(b){return Math.atan2(b.y-this.y,b.x-this.x)};this.applyTransform=\r\nfunction(b){var a=this.x*b[1]+this.y*b[3]+b[5];this.x=this.x*b[0]+this.y*b[2]+b[4];this.y=a}};a.CreatePoint=function(c){c=a.ToNumberArray(c);return new a.Point(c[0],c[1])};a.CreatePath=function(c){for(var c=a.ToNumberArray(c),d=[],b=0;b<c.length;b+=2)d.push(new a.Point(c[b],c[b+1]));return d};a.BoundingBox=function(a,d,b,k){this.y2=this.x2=this.y1=this.x1=Number.NaN;this.x=function(){return this.x1};this.y=function(){return this.y1};this.width=function(){return this.x2-this.x1};this.height=function(){return this.y2-\r\nthis.y1};this.addPoint=function(b,a){if(b!=null){if(isNaN(this.x1)||isNaN(this.x2))this.x2=this.x1=b;if(b<this.x1)this.x1=b;if(b>this.x2)this.x2=b}if(a!=null){if(isNaN(this.y1)||isNaN(this.y2))this.y2=this.y1=a;if(a<this.y1)this.y1=a;if(a>this.y2)this.y2=a}};this.addX=function(b){this.addPoint(b,null)};this.addY=function(b){this.addPoint(null,b)};this.addBoundingBox=function(b){this.addPoint(b.x1,b.y1);this.addPoint(b.x2,b.y2)};this.addQuadraticCurve=function(b,a,d,c,k,l){d=b+2/3*(d-b);c=a+2/3*(c-\r\na);this.addBezierCurve(b,a,d,d+1/3*(k-b),c,c+1/3*(l-a),k,l)};this.addBezierCurve=function(b,a,d,c,k,l,o,n){var q=[b,a],p=[d,c],t=[k,l],m=[o,n];this.addPoint(q[0],q[1]);this.addPoint(m[0],m[1]);for(i=0;i<=1;i++)b=function(b){return Math.pow(1-b,3)*q[i]+3*Math.pow(1-b,2)*b*p[i]+3*(1-b)*Math.pow(b,2)*t[i]+Math.pow(b,3)*m[i]},a=6*q[i]-12*p[i]+6*t[i],d=-3*q[i]+9*p[i]-9*t[i]+3*m[i],c=3*p[i]-3*q[i],d==0?a!=0&&(a=-c/a,0<a&&a<1&&(i==0&&this.addX(b(a)),i==1&&this.addY(b(a)))):(c=Math.pow(a,2)-4*c*d,c<0||(k=\r\n(-a+Math.sqrt(c))/(2*d),0<k&&k<1&&(i==0&&this.addX(b(k)),i==1&&this.addY(b(k))),a=(-a-Math.sqrt(c))/(2*d),0<a&&a<1&&(i==0&&this.addX(b(a)),i==1&&this.addY(b(a)))))};this.isPointInBox=function(b,a){return this.x1<=b&&b<=this.x2&&this.y1<=a&&a<=this.y2};this.addPoint(a,d);this.addPoint(b,k)};a.Transform=function(c){var d=this;this.Type={};this.Type.translate=function(b){this.p=a.CreatePoint(b);this.apply=function(b){b.translate(this.p.x||0,this.p.y||0)};this.applyToPoint=function(b){b.applyTransform([1,\r\n0,0,1,this.p.x||0,this.p.y||0])}};this.Type.rotate=function(b){b=a.ToNumberArray(b);this.angle=new a.Property(\"angle\",b[0]);this.cx=b[1]||0;this.cy=b[2]||0;this.apply=function(b){b.translate(this.cx,this.cy);b.rotate(this.angle.Angle.toRadians());b.translate(-this.cx,-this.cy)};this.applyToPoint=function(b){var a=this.angle.Angle.toRadians();b.applyTransform([1,0,0,1,this.p.x||0,this.p.y||0]);b.applyTransform([Math.cos(a),Math.sin(a),-Math.sin(a),Math.cos(a),0,0]);b.applyTransform([1,0,0,1,-this.p.x||\r\n0,-this.p.y||0])}};this.Type.scale=function(b){this.p=a.CreatePoint(b);this.apply=function(b){b.scale(this.p.x||1,this.p.y||this.p.x||1)};this.applyToPoint=function(b){b.applyTransform([this.p.x||0,0,0,this.p.y||0,0,0])}};this.Type.matrix=function(b){this.m=a.ToNumberArray(b);this.apply=function(b){b.transform(this.m[0],this.m[1],this.m[2],this.m[3],this.m[4],this.m[5])};this.applyToPoint=function(b){b.applyTransform(this.m)}};this.Type.SkewBase=function(b){this.base=d.Type.matrix;this.base(b);this.angle=\r\nnew a.Property(\"angle\",b)};this.Type.SkewBase.prototype=new this.Type.matrix;this.Type.skewX=function(b){this.base=d.Type.SkewBase;this.base(b);this.m=[1,0,Math.tan(this.angle.Angle.toRadians()),1,0,0]};this.Type.skewX.prototype=new this.Type.SkewBase;this.Type.skewY=function(b){this.base=d.Type.SkewBase;this.base(b);this.m=[1,Math.tan(this.angle.Angle.toRadians()),0,1,0,0]};this.Type.skewY.prototype=new this.Type.SkewBase;this.transforms=[];this.apply=function(b){for(var a=0;a<this.transforms.length;a++)this.transforms[a].apply(b)};\r\nthis.applyToPoint=function(b){for(var a=0;a<this.transforms.length;a++)this.transforms[a].applyToPoint(b)};for(var c=a.trim(a.compressSpaces(c)).split(/\\s(?=[a-z])/),b=0;b<c.length;b++){var k=c[b].split(\"(\")[0],e=c[b].split(\"(\")[1].replace(\")\",\"\");this.transforms.push(new this.Type[k](e))}};a.AspectRatio=function(c,d,b,k,e,f,g,j,h,l){var d=a.compressSpaces(d),d=d.replace(/^defer\\s/,\"\"),o=d.split(\" \")[0]||\"xMidYMid\",d=d.split(\" \")[1]||\"meet\",n=b/k,q=e/f,p=Math.min(n,q),m=Math.max(n,q);d==\"meet\"&&(k*=\r\np,f*=p);d==\"slice\"&&(k*=m,f*=m);h=new a.Property(\"refX\",h);l=new a.Property(\"refY\",l);h.hasValue()&&l.hasValue()?c.translate(-p*h.Length.toPixels(\"x\"),-p*l.Length.toPixels(\"y\")):(o.match(/^xMid/)&&(d==\"meet\"&&p==q||d==\"slice\"&&m==q)&&c.translate(b/2-k/2,0),o.match(/YMid$/)&&(d==\"meet\"&&p==n||d==\"slice\"&&m==n)&&c.translate(0,e/2-f/2),o.match(/^xMax/)&&(d==\"meet\"&&p==q||d==\"slice\"&&m==q)&&c.translate(b-k,0),o.match(/YMax$/)&&(d==\"meet\"&&p==n||d==\"slice\"&&m==n)&&c.translate(0,e-f));o==\"none\"?c.scale(n,\r\nq):d==\"meet\"?c.scale(p,p):d==\"slice\"&&c.scale(m,m);c.translate(g==null?0:-g,j==null?0:-j)};a.Element={};a.Element.ElementBase=function(c){this.attributes={};this.styles={};this.children=[];this.attribute=function(b,d){var c=this.attributes[b];if(c!=null)return c;c=new a.Property(b,\"\");d==!0&&(this.attributes[b]=c);return c};this.style=function(b,d){var c=this.styles[b];if(c!=null)return c;c=this.attribute(b);if(c!=null&&c.hasValue())return c;c=this.parent;if(c!=null&&(c=c.style(b),c!=null&&c.hasValue()))return c;\r\nc=new a.Property(b,\"\");d==!0&&(this.styles[b]=c);return c};this.render=function(b){if(this.style(\"display\").value!=\"none\"&&this.attribute(\"visibility\").value!=\"hidden\"){b.save();this.setContext(b);if(this.attribute(\"mask\").hasValue()){var a=this.attribute(\"mask\").Definition.getDefinition();a!=null&&a.apply(b,this)}else this.style(\"filter\").hasValue()?(a=this.style(\"filter\").Definition.getDefinition(),a!=null&&a.apply(b,this)):this.renderChildren(b);this.clearContext(b);b.restore()}};this.setContext=\r\nfunction(){};this.clearContext=function(){};this.renderChildren=function(b){for(var a=0;a<this.children.length;a++)this.children[a].render(b)};this.addChild=function(b,d){var c=b;d&&(c=a.CreateElement(b));c.parent=this;this.children.push(c)};if(c!=null&&c.nodeType==1){for(var d=0;d<c.childNodes.length;d++){var b=c.childNodes[d];b.nodeType==1&&this.addChild(b,!0)}for(d=0;d<c.attributes.length;d++)b=c.attributes[d],this.attributes[b.nodeName]=new a.Property(b.nodeName,b.nodeValue);b=a.Styles[c.nodeName];\r\nif(b!=null)for(var k in b)this.styles[k]=b[k];if(this.attribute(\"class\").hasValue())for(var d=a.compressSpaces(this.attribute(\"class\").value).split(\" \"),e=0;e<d.length;e++){b=a.Styles[\".\"+d[e]];if(b!=null)for(k in b)this.styles[k]=b[k];b=a.Styles[c.nodeName+\".\"+d[e]];if(b!=null)for(k in b)this.styles[k]=b[k]}if(this.attribute(\"style\").hasValue()){b=this.attribute(\"style\").value.split(\";\");for(d=0;d<b.length;d++)a.trim(b[d])!=\"\"&&(c=b[d].split(\":\"),k=a.trim(c[0]),c=a.trim(c[1]),this.styles[k]=new a.Property(k,\r\nc))}this.attribute(\"id\").hasValue()&&a.Definitions[this.attribute(\"id\").value]==null&&(a.Definitions[this.attribute(\"id\").value]=this)}};a.Element.RenderedElementBase=function(c){this.base=a.Element.ElementBase;this.base(c);this.setContext=function(d){if(this.style(\"fill\").Definition.isUrl()){var b=this.style(\"fill\").Definition.getFillStyle(this);if(b!=null)d.fillStyle=b}else if(this.style(\"fill\").hasValue())b=this.style(\"fill\"),this.style(\"fill-opacity\").hasValue()&&(b=b.Color.addOpacity(this.style(\"fill-opacity\").value)),\r\nd.fillStyle=b.value==\"none\"?\"rgba(0,0,0,0)\":b.value;if(this.style(\"stroke\").Definition.isUrl()){if(b=this.style(\"stroke\").Definition.getFillStyle(this),b!=null)d.strokeStyle=b}else if(this.style(\"stroke\").hasValue())b=this.style(\"stroke\"),this.style(\"stroke-opacity\").hasValue()&&(b=b.Color.addOpacity(this.style(\"stroke-opacity\").value)),d.strokeStyle=b.value==\"none\"?\"rgba(0,0,0,0)\":b.value;if(this.style(\"stroke-width\").hasValue())d.lineWidth=this.style(\"stroke-width\").Length.toPixels();if(this.style(\"stroke-linecap\").hasValue())d.lineCap=\r\nthis.style(\"stroke-linecap\").value;if(this.style(\"stroke-linejoin\").hasValue())d.lineJoin=this.style(\"stroke-linejoin\").value;if(this.style(\"stroke-miterlimit\").hasValue())d.miterLimit=this.style(\"stroke-miterlimit\").value;if(typeof d.font!=\"undefined\")d.font=a.Font.CreateFont(this.style(\"font-style\").value,this.style(\"font-variant\").value,this.style(\"font-weight\").value,this.style(\"font-size\").hasValue()?this.style(\"font-size\").Length.toPixels()+\"px\":\"\",this.style(\"font-family\").value).toString();\r\nthis.attribute(\"transform\").hasValue()&&(new a.Transform(this.attribute(\"transform\").value)).apply(d);this.attribute(\"clip-path\").hasValue()&&(b=this.attribute(\"clip-path\").Definition.getDefinition(),b!=null&&b.apply(d));if(this.style(\"opacity\").hasValue())d.globalAlpha=this.style(\"opacity\").numValue()}};a.Element.RenderedElementBase.prototype=new a.Element.ElementBase;a.Element.PathElementBase=function(c){this.base=a.Element.RenderedElementBase;this.base(c);this.path=function(d){d!=null&&d.beginPath();\r\nreturn new a.BoundingBox};this.renderChildren=function(d){this.path(d);a.Mouse.checkPath(this,d);d.fillStyle!=\"\"&&d.fill();d.strokeStyle!=\"\"&&d.stroke();var b=this.getMarkers();if(b!=null){if(this.style(\"marker-start\").Definition.isUrl()){var c=this.style(\"marker-start\").Definition.getDefinition();c.render(d,b[0][0],b[0][1])}if(this.style(\"marker-mid\").Definition.isUrl())for(var c=this.style(\"marker-mid\").Definition.getDefinition(),e=1;e<b.length-1;e++)c.render(d,b[e][0],b[e][1]);this.style(\"marker-end\").Definition.isUrl()&&\r\n(c=this.style(\"marker-end\").Definition.getDefinition(),c.render(d,b[b.length-1][0],b[b.length-1][1]))}};this.getBoundingBox=function(){return this.path()};this.getMarkers=function(){return null}};a.Element.PathElementBase.prototype=new a.Element.RenderedElementBase;a.Element.svg=function(c){this.base=a.Element.RenderedElementBase;this.base(c);this.baseClearContext=this.clearContext;this.clearContext=function(d){this.baseClearContext(d);a.ViewPort.RemoveCurrent()};this.baseSetContext=this.setContext;\r\nthis.setContext=function(d){d.strokeStyle=\"rgba(0,0,0,0)\";d.lineCap=\"butt\";d.lineJoin=\"miter\";d.miterLimit=4;this.baseSetContext(d);this.attribute(\"x\").hasValue()&&this.attribute(\"y\").hasValue()&&d.translate(this.attribute(\"x\").Length.toPixels(\"x\"),this.attribute(\"y\").Length.toPixels(\"y\"));var b=a.ViewPort.width(),c=a.ViewPort.height();if(typeof this.root==\"undefined\"&&this.attribute(\"width\").hasValue()&&this.attribute(\"height\").hasValue()){var b=this.attribute(\"width\").Length.toPixels(\"x\"),c=this.attribute(\"height\").Length.toPixels(\"y\"),\r\ne=0,f=0;this.attribute(\"refX\").hasValue()&&this.attribute(\"refY\").hasValue()&&(e=-this.attribute(\"refX\").Length.toPixels(\"x\"),f=-this.attribute(\"refY\").Length.toPixels(\"y\"));d.beginPath();d.moveTo(e,f);d.lineTo(b,f);d.lineTo(b,c);d.lineTo(e,c);d.closePath();d.clip()}a.ViewPort.SetCurrent(b,c);if(this.attribute(\"viewBox\").hasValue()){var e=a.ToNumberArray(this.attribute(\"viewBox\").value),f=e[0],g=e[1],b=e[2],c=e[3];a.AspectRatio(d,this.attribute(\"preserveAspectRatio\").value,a.ViewPort.width(),b,a.ViewPort.height(),\r\nc,f,g,this.attribute(\"refX\").value,this.attribute(\"refY\").value);a.ViewPort.RemoveCurrent();a.ViewPort.SetCurrent(e[2],e[3])}}};a.Element.svg.prototype=new a.Element.RenderedElementBase;a.Element.rect=function(c){this.base=a.Element.PathElementBase;this.base(c);this.path=function(d){var b=this.attribute(\"x\").Length.toPixels(\"x\"),c=this.attribute(\"y\").Length.toPixels(\"y\"),e=this.attribute(\"width\").Length.toPixels(\"x\"),f=this.attribute(\"height\").Length.toPixels(\"y\"),g=this.attribute(\"rx\").Length.toPixels(\"x\"),\r\nj=this.attribute(\"ry\").Length.toPixels(\"y\");this.attribute(\"rx\").hasValue()&&!this.attribute(\"ry\").hasValue()&&(j=g);this.attribute(\"ry\").hasValue()&&!this.attribute(\"rx\").hasValue()&&(g=j);d!=null&&(d.beginPath(),d.moveTo(b+g,c),d.lineTo(b+e-g,c),d.quadraticCurveTo(b+e,c,b+e,c+j),d.lineTo(b+e,c+f-j),d.quadraticCurveTo(b+e,c+f,b+e-g,c+f),d.lineTo(b+g,c+f),d.quadraticCurveTo(b,c+f,b,c+f-j),d.lineTo(b,c+j),d.quadraticCurveTo(b,c,b+g,c),d.closePath());return new a.BoundingBox(b,c,b+e,c+f)}};a.Element.rect.prototype=\r\nnew a.Element.PathElementBase;a.Element.circle=function(c){this.base=a.Element.PathElementBase;this.base(c);this.path=function(d){var b=this.attribute(\"cx\").Length.toPixels(\"x\"),c=this.attribute(\"cy\").Length.toPixels(\"y\"),e=this.attribute(\"r\").Length.toPixels();d!=null&&(d.beginPath(),d.arc(b,c,e,0,Math.PI*2,!0),d.closePath());return new a.BoundingBox(b-e,c-e,b+e,c+e)}};a.Element.circle.prototype=new a.Element.PathElementBase;a.Element.ellipse=function(c){this.base=a.Element.PathElementBase;this.base(c);\r\nthis.path=function(d){var b=4*((Math.sqrt(2)-1)/3),c=this.attribute(\"rx\").Length.toPixels(\"x\"),e=this.attribute(\"ry\").Length.toPixels(\"y\"),f=this.attribute(\"cx\").Length.toPixels(\"x\"),g=this.attribute(\"cy\").Length.toPixels(\"y\");d!=null&&(d.beginPath(),d.moveTo(f,g-e),d.bezierCurveTo(f+b*c,g-e,f+c,g-b*e,f+c,g),d.bezierCurveTo(f+c,g+b*e,f+b*c,g+e,f,g+e),d.bezierCurveTo(f-b*c,g+e,f-c,g+b*e,f-c,g),d.bezierCurveTo(f-c,g-b*e,f-b*c,g-e,f,g-e),d.closePath());return new a.BoundingBox(f-c,g-e,f+c,g+e)}};a.Element.ellipse.prototype=\r\nnew a.Element.PathElementBase;a.Element.line=function(c){this.base=a.Element.PathElementBase;this.base(c);this.getPoints=function(){return[new a.Point(this.attribute(\"x1\").Length.toPixels(\"x\"),this.attribute(\"y1\").Length.toPixels(\"y\")),new a.Point(this.attribute(\"x2\").Length.toPixels(\"x\"),this.attribute(\"y2\").Length.toPixels(\"y\"))]};this.path=function(d){var b=this.getPoints();d!=null&&(d.beginPath(),d.moveTo(b[0].x,b[0].y),d.lineTo(b[1].x,b[1].y));return new a.BoundingBox(b[0].x,b[0].y,b[1].x,b[1].y)};\r\nthis.getMarkers=function(){var a=this.getPoints(),b=a[0].angleTo(a[1]);return[[a[0],b],[a[1],b]]}};a.Element.line.prototype=new a.Element.PathElementBase;a.Element.polyline=function(c){this.base=a.Element.PathElementBase;this.base(c);this.points=a.CreatePath(this.attribute(\"points\").value);this.path=function(d){var b=new a.BoundingBox(this.points[0].x,this.points[0].y);d!=null&&(d.beginPath(),d.moveTo(this.points[0].x,this.points[0].y));for(var c=1;c<this.points.length;c++)b.addPoint(this.points[c].x,\r\nthis.points[c].y),d!=null&&d.lineTo(this.points[c].x,this.points[c].y);return b};this.getMarkers=function(){for(var a=[],b=0;b<this.points.length-1;b++)a.push([this.points[b],this.points[b].angleTo(this.points[b+1])]);a.push([this.points[this.points.length-1],a[a.length-1][1]]);return a}};a.Element.polyline.prototype=new a.Element.PathElementBase;a.Element.polygon=function(c){this.base=a.Element.polyline;this.base(c);this.basePath=this.path;this.path=function(a){var b=this.basePath(a);a!=null&&(a.lineTo(this.points[0].x,\r\nthis.points[0].y),a.closePath());return b}};a.Element.polygon.prototype=new a.Element.polyline;a.Element.path=function(c){this.base=a.Element.PathElementBase;this.base(c);c=this.attribute(\"d\").value;c=c.replace(/,/gm,\" \");c=c.replace(/([MmZzLlHhVvCcSsQqTtAa])([MmZzLlHhVvCcSsQqTtAa])/gm,\"$1 $2\");c=c.replace(/([MmZzLlHhVvCcSsQqTtAa])([MmZzLlHhVvCcSsQqTtAa])/gm,\"$1 $2\");c=c.replace(/([MmZzLlHhVvCcSsQqTtAa])([^\\s])/gm,\"$1 $2\");c=c.replace(/([^\\s])([MmZzLlHhVvCcSsQqTtAa])/gm,\"$1 $2\");c=c.replace(/([0-9])([+\\-])/gm,\r\n\"$1 $2\");c=c.replace(/(\\.[0-9]*)(\\.)/gm,\"$1 $2\");c=c.replace(/([Aa](\\s+[0-9]+){3})\\s+([01])\\s*([01])/gm,\"$1 $3 $4 \");c=a.compressSpaces(c);c=a.trim(c);this.PathParser=new function(d){this.tokens=d.split(\" \");this.reset=function(){this.i=-1;this.previousCommand=this.command=\"\";this.start=new a.Point(0,0);this.control=new a.Point(0,0);this.current=new a.Point(0,0);this.points=[];this.angles=[]};this.isEnd=function(){return this.i>=this.tokens.length-1};this.isCommandOrEnd=function(){return this.isEnd()?\r\n!0:this.tokens[this.i+1].match(/^[A-Za-z]$/)!=null};this.isRelativeCommand=function(){return this.command==this.command.toLowerCase()};this.getToken=function(){this.i+=1;return this.tokens[this.i]};this.getScalar=function(){return parseFloat(this.getToken())};this.nextCommand=function(){this.previousCommand=this.command;this.command=this.getToken()};this.getPoint=function(){return this.makeAbsolute(new a.Point(this.getScalar(),this.getScalar()))};this.getAsControlPoint=function(){var b=this.getPoint();\r\nreturn this.control=b};this.getAsCurrentPoint=function(){var b=this.getPoint();return this.current=b};this.getReflectedControlPoint=function(){return this.previousCommand.toLowerCase()!=\"c\"&&this.previousCommand.toLowerCase()!=\"s\"?this.current:new a.Point(2*this.current.x-this.control.x,2*this.current.y-this.control.y)};this.makeAbsolute=function(b){if(this.isRelativeCommand())b.x=this.current.x+b.x,b.y=this.current.y+b.y;return b};this.addMarker=function(b,a,d){d!=null&&this.angles.length>0&&this.angles[this.angles.length-\r\n1]==null&&(this.angles[this.angles.length-1]=this.points[this.points.length-1].angleTo(d));this.addMarkerAngle(b,a==null?null:a.angleTo(b))};this.addMarkerAngle=function(b,a){this.points.push(b);this.angles.push(a)};this.getMarkerPoints=function(){return this.points};this.getMarkerAngles=function(){for(var b=0;b<this.angles.length;b++)if(this.angles[b]==null)for(var a=b+1;a<this.angles.length;a++)if(this.angles[a]!=null){this.angles[b]=this.angles[a];break}return this.angles}}(c);this.path=function(d){var b=\r\nthis.PathParser;b.reset();var c=new a.BoundingBox;for(d!=null&&d.beginPath();!b.isEnd();)switch(b.nextCommand(),b.command.toUpperCase()){case \"M\":var e=b.getAsCurrentPoint();b.addMarker(e);c.addPoint(e.x,e.y);d!=null&&d.moveTo(e.x,e.y);for(b.start=b.current;!b.isCommandOrEnd();)e=b.getAsCurrentPoint(),b.addMarker(e,b.start),c.addPoint(e.x,e.y),d!=null&&d.lineTo(e.x,e.y);break;case \"L\":for(;!b.isCommandOrEnd();){var f=b.current,e=b.getAsCurrentPoint();b.addMarker(e,f);c.addPoint(e.x,e.y);d!=null&&\r\nd.lineTo(e.x,e.y)}break;case \"H\":for(;!b.isCommandOrEnd();)e=new a.Point((b.isRelativeCommand()?b.current.x:0)+b.getScalar(),b.current.y),b.addMarker(e,b.current),b.current=e,c.addPoint(b.current.x,b.current.y),d!=null&&d.lineTo(b.current.x,b.current.y);break;case \"V\":for(;!b.isCommandOrEnd();)e=new a.Point(b.current.x,(b.isRelativeCommand()?b.current.y:0)+b.getScalar()),b.addMarker(e,b.current),b.current=e,c.addPoint(b.current.x,b.current.y),d!=null&&d.lineTo(b.current.x,b.current.y);break;case \"C\":for(;!b.isCommandOrEnd();){var g=\r\nb.current,f=b.getPoint(),j=b.getAsControlPoint(),e=b.getAsCurrentPoint();b.addMarker(e,j,f);c.addBezierCurve(g.x,g.y,f.x,f.y,j.x,j.y,e.x,e.y);d!=null&&d.bezierCurveTo(f.x,f.y,j.x,j.y,e.x,e.y)}break;case \"S\":for(;!b.isCommandOrEnd();)g=b.current,f=b.getReflectedControlPoint(),j=b.getAsControlPoint(),e=b.getAsCurrentPoint(),b.addMarker(e,j,f),c.addBezierCurve(g.x,g.y,f.x,f.y,j.x,j.y,e.x,e.y),d!=null&&d.bezierCurveTo(f.x,f.y,j.x,j.y,e.x,e.y);break;case \"Q\":for(;!b.isCommandOrEnd();)g=b.current,j=b.getAsControlPoint(),\r\ne=b.getAsCurrentPoint(),b.addMarker(e,j,j),c.addQuadraticCurve(g.x,g.y,j.x,j.y,e.x,e.y),d!=null&&d.quadraticCurveTo(j.x,j.y,e.x,e.y);break;case \"T\":for(;!b.isCommandOrEnd();)g=b.current,j=b.getReflectedControlPoint(),b.control=j,e=b.getAsCurrentPoint(),b.addMarker(e,j,j),c.addQuadraticCurve(g.x,g.y,j.x,j.y,e.x,e.y),d!=null&&d.quadraticCurveTo(j.x,j.y,e.x,e.y);break;case \"A\":for(;!b.isCommandOrEnd();){var g=b.current,h=b.getScalar(),l=b.getScalar(),f=b.getScalar()*(Math.PI/180),o=b.getScalar(),j=b.getScalar(),\r\ne=b.getAsCurrentPoint(),n=new a.Point(Math.cos(f)*(g.x-e.x)/2+Math.sin(f)*(g.y-e.y)/2,-Math.sin(f)*(g.x-e.x)/2+Math.cos(f)*(g.y-e.y)/2),q=Math.pow(n.x,2)/Math.pow(h,2)+Math.pow(n.y,2)/Math.pow(l,2);q>1&&(h*=Math.sqrt(q),l*=Math.sqrt(q));o=(o==j?-1:1)*Math.sqrt((Math.pow(h,2)*Math.pow(l,2)-Math.pow(h,2)*Math.pow(n.y,2)-Math.pow(l,2)*Math.pow(n.x,2))/(Math.pow(h,2)*Math.pow(n.y,2)+Math.pow(l,2)*Math.pow(n.x,2)));isNaN(o)&&(o=0);var p=new a.Point(o*h*n.y/l,o*-l*n.x/h),g=new a.Point((g.x+e.x)/2+Math.cos(f)*\r\np.x-Math.sin(f)*p.y,(g.y+e.y)/2+Math.sin(f)*p.x+Math.cos(f)*p.y),m=function(b,a){return(b[0]*a[0]+b[1]*a[1])/(Math.sqrt(Math.pow(b[0],2)+Math.pow(b[1],2))*Math.sqrt(Math.pow(a[0],2)+Math.pow(a[1],2)))},s=function(b,a){return(b[0]*a[1]<b[1]*a[0]?-1:1)*Math.acos(m(b,a))},o=s([1,0],[(n.x-p.x)/h,(n.y-p.y)/l]),q=[(n.x-p.x)/h,(n.y-p.y)/l],p=[(-n.x-p.x)/h,(-n.y-p.y)/l],n=s(q,p);if(m(q,p)<=-1)n=Math.PI;m(q,p)>=1&&(n=0);j==0&&n>0&&(n-=2*Math.PI);j==1&&n<0&&(n+=2*Math.PI);q=new a.Point(g.x-h*Math.cos((o+n)/\r\n2),g.y-l*Math.sin((o+n)/2));b.addMarkerAngle(q,(o+n)/2+(j==0?1:-1)*Math.PI/2);b.addMarkerAngle(e,n+(j==0?1:-1)*Math.PI/2);c.addPoint(e.x,e.y);d!=null&&(m=h>l?h:l,e=h>l?1:h/l,h=h>l?l/h:1,d.translate(g.x,g.y),d.rotate(f),d.scale(e,h),d.arc(0,0,m,o,o+n,1-j),d.scale(1/e,1/h),d.rotate(-f),d.translate(-g.x,-g.y))}break;case \"Z\":d!=null&&d.closePath(),b.current=b.start}return c};this.getMarkers=function(){for(var a=this.PathParser.getMarkerPoints(),b=this.PathParser.getMarkerAngles(),c=[],e=0;e<a.length;e++)c.push([a[e],\r\nb[e]]);return c}};a.Element.path.prototype=new a.Element.PathElementBase;a.Element.pattern=function(c){this.base=a.Element.ElementBase;this.base(c);this.createPattern=function(d){var b=new a.Element.svg;b.attributes.viewBox=new a.Property(\"viewBox\",this.attribute(\"viewBox\").value);b.attributes.x=new a.Property(\"x\",this.attribute(\"x\").value);b.attributes.y=new a.Property(\"y\",this.attribute(\"y\").value);b.attributes.width=new a.Property(\"width\",this.attribute(\"width\").value);b.attributes.height=new a.Property(\"height\",\r\nthis.attribute(\"height\").value);b.children=this.children;var c=document.createElement(\"canvas\");c.width=this.attribute(\"width\").Length.toPixels(\"x\");c.height=this.attribute(\"height\").Length.toPixels(\"y\");b.render(c.getContext(\"2d\"));return d.createPattern(c,\"repeat\")}};a.Element.pattern.prototype=new a.Element.ElementBase;a.Element.marker=function(c){this.base=a.Element.ElementBase;this.base(c);this.baseRender=this.render;this.render=function(d,b,c){d.translate(b.x,b.y);this.attribute(\"orient\").valueOrDefault(\"auto\")==\r\n\"auto\"&&d.rotate(c);this.attribute(\"markerUnits\").valueOrDefault(\"strokeWidth\")==\"strokeWidth\"&&d.scale(d.lineWidth,d.lineWidth);d.save();var e=new a.Element.svg;e.attributes.viewBox=new a.Property(\"viewBox\",this.attribute(\"viewBox\").value);e.attributes.refX=new a.Property(\"refX\",this.attribute(\"refX\").value);e.attributes.refY=new a.Property(\"refY\",this.attribute(\"refY\").value);e.attributes.width=new a.Property(\"width\",this.attribute(\"markerWidth\").value);e.attributes.height=new a.Property(\"height\",\r\nthis.attribute(\"markerHeight\").value);e.attributes.fill=new a.Property(\"fill\",this.attribute(\"fill\").valueOrDefault(\"black\"));e.attributes.stroke=new a.Property(\"stroke\",this.attribute(\"stroke\").valueOrDefault(\"none\"));e.children=this.children;e.render(d);d.restore();this.attribute(\"markerUnits\").valueOrDefault(\"strokeWidth\")==\"strokeWidth\"&&d.scale(1/d.lineWidth,1/d.lineWidth);this.attribute(\"orient\").valueOrDefault(\"auto\")==\"auto\"&&d.rotate(-c);d.translate(-b.x,-b.y)}};a.Element.marker.prototype=\r\nnew a.Element.ElementBase;a.Element.defs=function(c){this.base=a.Element.ElementBase;this.base(c);this.render=function(){}};a.Element.defs.prototype=new a.Element.ElementBase;a.Element.GradientBase=function(c){this.base=a.Element.ElementBase;this.base(c);this.gradientUnits=this.attribute(\"gradientUnits\").valueOrDefault(\"objectBoundingBox\");this.stops=[];for(c=0;c<this.children.length;c++)this.stops.push(this.children[c]);this.getGradient=function(){};this.createGradient=function(d,b){var c=this;this.attribute(\"xlink:href\").hasValue()&&\r\n(c=this.attribute(\"xlink:href\").Definition.getDefinition());for(var e=this.getGradient(d,b),f=0;f<c.stops.length;f++)e.addColorStop(c.stops[f].offset,c.stops[f].color);if(this.attribute(\"gradientTransform\").hasValue()){c=a.ViewPort.viewPorts[0];f=new a.Element.rect;f.attributes.x=new a.Property(\"x\",-a.MAX_VIRTUAL_PIXELS/3);f.attributes.y=new a.Property(\"y\",-a.MAX_VIRTUAL_PIXELS/3);f.attributes.width=new a.Property(\"width\",a.MAX_VIRTUAL_PIXELS);f.attributes.height=new a.Property(\"height\",a.MAX_VIRTUAL_PIXELS);\r\nvar g=new a.Element.g;g.attributes.transform=new a.Property(\"transform\",this.attribute(\"gradientTransform\").value);g.children=[f];f=new a.Element.svg;f.attributes.x=new a.Property(\"x\",0);f.attributes.y=new a.Property(\"y\",0);f.attributes.width=new a.Property(\"width\",c.width);f.attributes.height=new a.Property(\"height\",c.height);f.children=[g];g=document.createElement(\"canvas\");g.width=c.width;g.height=c.height;c=g.getContext(\"2d\");c.fillStyle=e;f.render(c);return c.createPattern(g,\"no-repeat\")}return e}};\r\na.Element.GradientBase.prototype=new a.Element.ElementBase;a.Element.linearGradient=function(c){this.base=a.Element.GradientBase;this.base(c);this.getGradient=function(a,b){var c=b.getBoundingBox(),e=this.gradientUnits==\"objectBoundingBox\"?c.x()+c.width()*this.attribute(\"x1\").numValue():this.attribute(\"x1\").Length.toPixels(\"x\"),f=this.gradientUnits==\"objectBoundingBox\"?c.y()+c.height()*this.attribute(\"y1\").numValue():this.attribute(\"y1\").Length.toPixels(\"y\"),g=this.gradientUnits==\"objectBoundingBox\"?\r\nc.x()+c.width()*this.attribute(\"x2\").numValue():this.attribute(\"x2\").Length.toPixels(\"x\"),c=this.gradientUnits==\"objectBoundingBox\"?c.y()+c.height()*this.attribute(\"y2\").numValue():this.attribute(\"y2\").Length.toPixels(\"y\");return a.createLinearGradient(e,f,g,c)}};a.Element.linearGradient.prototype=new a.Element.GradientBase;a.Element.radialGradient=function(c){this.base=a.Element.GradientBase;this.base(c);this.getGradient=function(a,b){var c=b.getBoundingBox(),e=this.gradientUnits==\"objectBoundingBox\"?\r\nc.x()+c.width()*this.attribute(\"cx\").numValue():this.attribute(\"cx\").Length.toPixels(\"x\"),f=this.gradientUnits==\"objectBoundingBox\"?c.y()+c.height()*this.attribute(\"cy\").numValue():this.attribute(\"cy\").Length.toPixels(\"y\"),g=e,j=f;this.attribute(\"fx\").hasValue()&&(g=this.gradientUnits==\"objectBoundingBox\"?c.x()+c.width()*this.attribute(\"fx\").numValue():this.attribute(\"fx\").Length.toPixels(\"x\"));this.attribute(\"fy\").hasValue()&&(j=this.gradientUnits==\"objectBoundingBox\"?c.y()+c.height()*this.attribute(\"fy\").numValue():\r\nthis.attribute(\"fy\").Length.toPixels(\"y\"));c=this.gradientUnits==\"objectBoundingBox\"?(c.width()+c.height())/2*this.attribute(\"r\").numValue():this.attribute(\"r\").Length.toPixels();return a.createRadialGradient(g,j,0,e,f,c)}};a.Element.radialGradient.prototype=new a.Element.GradientBase;a.Element.stop=function(c){this.base=a.Element.ElementBase;this.base(c);this.offset=this.attribute(\"offset\").numValue();c=this.style(\"stop-color\");this.style(\"stop-opacity\").hasValue()&&(c=c.Color.addOpacity(this.style(\"stop-opacity\").value));\r\nthis.color=c.value};a.Element.stop.prototype=new a.Element.ElementBase;a.Element.AnimateBase=function(c){this.base=a.Element.ElementBase;this.base(c);a.Animations.push(this);this.duration=0;this.begin=this.attribute(\"begin\").Time.toMilliseconds();this.maxDuration=this.begin+this.attribute(\"dur\").Time.toMilliseconds();this.getProperty=function(){var a=this.attribute(\"attributeType\").value,b=this.attribute(\"attributeName\").value;return a==\"CSS\"?this.parent.style(b,!0):this.parent.attribute(b,!0)};this.initialValue=\r\nnull;this.removed=!1;this.calcValue=function(){return\"\"};this.update=function(a){if(this.initialValue==null)this.initialValue=this.getProperty().value;if(this.duration>this.maxDuration)if(this.attribute(\"repeatCount\").value==\"indefinite\")this.duration=0;else return this.attribute(\"fill\").valueOrDefault(\"remove\")==\"remove\"&&!this.removed?(this.removed=!0,this.getProperty().value=this.initialValue,!0):!1;this.duration+=a;a=!1;if(this.begin<this.duration)a=this.calcValue(),this.attribute(\"type\").hasValue()&&\r\n(a=this.attribute(\"type\").value+\"(\"+a+\")\"),this.getProperty().value=a,a=!0;return a};this.progress=function(){return(this.duration-this.begin)/(this.maxDuration-this.begin)}};a.Element.AnimateBase.prototype=new a.Element.ElementBase;a.Element.animate=function(c){this.base=a.Element.AnimateBase;this.base(c);this.calcValue=function(){var a=this.attribute(\"from\").numValue(),b=this.attribute(\"to\").numValue();return a+(b-a)*this.progress()}};a.Element.animate.prototype=new a.Element.AnimateBase;a.Element.animateColor=\r\nfunction(c){this.base=a.Element.AnimateBase;this.base(c);this.calcValue=function(){var a=new RGBColor(this.attribute(\"from\").value),b=new RGBColor(this.attribute(\"to\").value);if(a.ok&&b.ok){var c=a.r+(b.r-a.r)*this.progress(),e=a.g+(b.g-a.g)*this.progress(),a=a.b+(b.b-a.b)*this.progress();return\"rgb(\"+parseInt(c,10)+\",\"+parseInt(e,10)+\",\"+parseInt(a,10)+\")\"}return this.attribute(\"from\").value}};a.Element.animateColor.prototype=new a.Element.AnimateBase;a.Element.animateTransform=function(c){this.base=\r\na.Element.animate;this.base(c)};a.Element.animateTransform.prototype=new a.Element.animate;a.Element.font=function(c){this.base=a.Element.ElementBase;this.base(c);this.horizAdvX=this.attribute(\"horiz-adv-x\").numValue();this.isArabic=this.isRTL=!1;this.missingGlyph=this.fontFace=null;this.glyphs=[];for(c=0;c<this.children.length;c++){var d=this.children[c];if(d.type==\"font-face\")this.fontFace=d,d.style(\"font-family\").hasValue()&&(a.Definitions[d.style(\"font-family\").value]=this);else if(d.type==\"missing-glyph\")this.missingGlyph=\r\nd;else if(d.type==\"glyph\")d.arabicForm!=\"\"?(this.isArabic=this.isRTL=!0,typeof this.glyphs[d.unicode]==\"undefined\"&&(this.glyphs[d.unicode]=[]),this.glyphs[d.unicode][d.arabicForm]=d):this.glyphs[d.unicode]=d}};a.Element.font.prototype=new a.Element.ElementBase;a.Element.fontface=function(c){this.base=a.Element.ElementBase;this.base(c);this.ascent=this.attribute(\"ascent\").value;this.descent=this.attribute(\"descent\").value;this.unitsPerEm=this.attribute(\"units-per-em\").numValue()};a.Element.fontface.prototype=\r\nnew a.Element.ElementBase;a.Element.missingglyph=function(c){this.base=a.Element.path;this.base(c);this.horizAdvX=0};a.Element.missingglyph.prototype=new a.Element.path;a.Element.glyph=function(c){this.base=a.Element.path;this.base(c);this.horizAdvX=this.attribute(\"horiz-adv-x\").numValue();this.unicode=this.attribute(\"unicode\").value;this.arabicForm=this.attribute(\"arabic-form\").value};a.Element.glyph.prototype=new a.Element.path;a.Element.text=function(c){this.base=a.Element.RenderedElementBase;\r\nthis.base(c);if(c!=null){this.children=[];for(var d=0;d<c.childNodes.length;d++){var b=c.childNodes[d];b.nodeType==1?this.addChild(b,!0):b.nodeType==3&&this.addChild(new a.Element.tspan(b),!1)}}this.baseSetContext=this.setContext;this.setContext=function(b){this.baseSetContext(b);if(this.style(\"dominant-baseline\").hasValue())b.textBaseline=this.style(\"dominant-baseline\").value;if(this.style(\"alignment-baseline\").hasValue())b.textBaseline=this.style(\"alignment-baseline\").value};this.renderChildren=\r\nfunction(b){for(var a=this.style(\"text-anchor\").valueOrDefault(\"start\"),c=this.attribute(\"x\").Length.toPixels(\"x\"),d=this.attribute(\"y\").Length.toPixels(\"y\"),j=0;j<this.children.length;j++){var h=this.children[j];h.attribute(\"x\").hasValue()?h.x=h.attribute(\"x\").Length.toPixels(\"x\"):(h.attribute(\"dx\").hasValue()&&(c+=h.attribute(\"dx\").Length.toPixels(\"x\")),h.x=c);c=h.measureText(b);if(a!=\"start\"&&(j==0||h.attribute(\"x\").hasValue())){for(var l=c,o=j+1;o<this.children.length;o++){var n=this.children[o];\r\nif(n.attribute(\"x\").hasValue())break;l+=n.measureText(b)}h.x-=a==\"end\"?l:l/2}c=h.x+c;h.attribute(\"y\").hasValue()?h.y=h.attribute(\"y\").Length.toPixels(\"y\"):(h.attribute(\"dy\").hasValue()&&(d+=h.attribute(\"dy\").Length.toPixels(\"y\")),h.y=d);d=h.y;h.render(b)}}};a.Element.text.prototype=new a.Element.RenderedElementBase;a.Element.TextElementBase=function(c){this.base=a.Element.RenderedElementBase;this.base(c);this.getGlyph=function(a,b,c){var e=b[c],f=null;if(a.isArabic){var g=\"isolated\";if((c==0||b[c-\r\n1]==\" \")&&c<b.length-2&&b[c+1]!=\" \")g=\"terminal\";c>0&&b[c-1]!=\" \"&&c<b.length-2&&b[c+1]!=\" \"&&(g=\"medial\");if(c>0&&b[c-1]!=\" \"&&(c==b.length-1||b[c+1]==\" \"))g=\"initial\";typeof a.glyphs[e]!=\"undefined\"&&(f=a.glyphs[e][g],f==null&&a.glyphs[e].type==\"glyph\"&&(f=a.glyphs[e]))}else f=a.glyphs[e];if(f==null)f=a.missingGlyph;return f};this.renderChildren=function(c){var b=this.parent.style(\"font-family\").Definition.getDefinition();if(b!=null){var k=this.parent.style(\"font-size\").numValueOrDefault(a.Font.Parse(a.ctx.font).fontSize),\r\ne=this.parent.style(\"font-style\").valueOrDefault(a.Font.Parse(a.ctx.font).fontStyle),f=this.getText();b.isRTL&&(f=f.split(\"\").reverse().join(\"\"));for(var g=a.ToNumberArray(this.parent.attribute(\"dx\").value),j=0;j<f.length;j++){var h=this.getGlyph(b,f,j),l=k/b.fontFace.unitsPerEm;c.translate(this.x,this.y);c.scale(l,-l);var o=c.lineWidth;c.lineWidth=c.lineWidth*b.fontFace.unitsPerEm/k;e==\"italic\"&&c.transform(1,0,0.4,1,0,0);h.render(c);e==\"italic\"&&c.transform(1,0,-0.4,1,0,0);c.lineWidth=o;c.scale(1/\r\nl,-1/l);c.translate(-this.x,-this.y);this.x+=k*(h.horizAdvX||b.horizAdvX)/b.fontFace.unitsPerEm;typeof g[j]!=\"undefined\"&&!isNaN(g[j])&&(this.x+=g[j])}}else c.strokeStyle!=\"\"&&c.strokeText(a.compressSpaces(this.getText()),this.x,this.y),c.fillStyle!=\"\"&&c.fillText(a.compressSpaces(this.getText()),this.x,this.y)};this.getText=function(){};this.measureText=function(c){var b=this.parent.style(\"font-family\").Definition.getDefinition();if(b!=null){var c=this.parent.style(\"font-size\").numValueOrDefault(a.Font.Parse(a.ctx.font).fontSize),\r\nk=0,e=this.getText();b.isRTL&&(e=e.split(\"\").reverse().join(\"\"));for(var f=a.ToNumberArray(this.parent.attribute(\"dx\").value),g=0;g<e.length;g++){var j=this.getGlyph(b,e,g);k+=(j.horizAdvX||b.horizAdvX)*c/b.fontFace.unitsPerEm;typeof f[g]!=\"undefined\"&&!isNaN(f[g])&&(k+=f[g])}return k}b=a.compressSpaces(this.getText());if(!c.measureText)return b.length*10;c.save();this.setContext(c);b=c.measureText(b).width;c.restore();return b}};a.Element.TextElementBase.prototype=new a.Element.RenderedElementBase;\r\na.Element.tspan=function(c){this.base=a.Element.TextElementBase;this.base(c);this.text=c.nodeType==3?c.nodeValue:c.childNodes.length>0?c.childNodes[0].nodeValue:c.text;this.getText=function(){return this.text}};a.Element.tspan.prototype=new a.Element.TextElementBase;a.Element.tref=function(c){this.base=a.Element.TextElementBase;this.base(c);this.getText=function(){var a=this.attribute(\"xlink:href\").Definition.getDefinition();if(a!=null)return a.children[0].getText()}};a.Element.tref.prototype=new a.Element.TextElementBase;\r\na.Element.a=function(c){this.base=a.Element.TextElementBase;this.base(c);this.hasText=!0;for(var d=0;d<c.childNodes.length;d++)if(c.childNodes[d].nodeType!=3)this.hasText=!1;this.text=this.hasText?c.childNodes[0].nodeValue:\"\";this.getText=function(){return this.text};this.baseRenderChildren=this.renderChildren;this.renderChildren=function(b){if(this.hasText){this.baseRenderChildren(b);var c=new a.Property(\"fontSize\",a.Font.Parse(a.ctx.font).fontSize);a.Mouse.checkBoundingBox(this,new a.BoundingBox(this.x,\r\nthis.y-c.Length.toPixels(\"y\"),this.x+this.measureText(b),this.y))}else c=new a.Element.g,c.children=this.children,c.parent=this,c.render(b)};this.onclick=function(){window.open(this.attribute(\"xlink:href\").value)};this.onmousemove=function(){a.ctx.canvas.style.cursor=\"pointer\"}};a.Element.a.prototype=new a.Element.TextElementBase;a.Element.image=function(c){this.base=a.Element.RenderedElementBase;this.base(c);a.Images.push(this);this.img=document.createElement(\"img\");this.loaded=!1;var d=this;this.img.onload=\r\nfunction(){d.loaded=!0};this.img.src=this.attribute(\"xlink:href\").value;this.renderChildren=function(b){var c=this.attribute(\"x\").Length.toPixels(\"x\"),d=this.attribute(\"y\").Length.toPixels(\"y\"),f=this.attribute(\"width\").Length.toPixels(\"x\"),g=this.attribute(\"height\").Length.toPixels(\"y\");f==0||g==0||(b.save(),b.translate(c,d),a.AspectRatio(b,this.attribute(\"preserveAspectRatio\").value,f,this.img.width,g,this.img.height,0,0),b.drawImage(this.img,0,0),b.restore())}};a.Element.image.prototype=new a.Element.RenderedElementBase;\r\na.Element.g=function(c){this.base=a.Element.RenderedElementBase;this.base(c);this.getBoundingBox=function(){for(var c=new a.BoundingBox,b=0;b<this.children.length;b++)c.addBoundingBox(this.children[b].getBoundingBox());return c}};a.Element.g.prototype=new a.Element.RenderedElementBase;a.Element.symbol=function(c){this.base=a.Element.RenderedElementBase;this.base(c);this.baseSetContext=this.setContext;this.setContext=function(c){this.baseSetContext(c);if(this.attribute(\"viewBox\").hasValue()){var b=\r\na.ToNumberArray(this.attribute(\"viewBox\").value),k=b[0],e=b[1];width=b[2];height=b[3];a.AspectRatio(c,this.attribute(\"preserveAspectRatio\").value,this.attribute(\"width\").Length.toPixels(\"x\"),width,this.attribute(\"height\").Length.toPixels(\"y\"),height,k,e);a.ViewPort.SetCurrent(b[2],b[3])}}};a.Element.symbol.prototype=new a.Element.RenderedElementBase;a.Element.style=function(c){this.base=a.Element.ElementBase;this.base(c);for(var c=c.childNodes[0].nodeValue+(c.childNodes.length>1?c.childNodes[1].nodeValue:\r\n\"\"),c=c.replace(/(\\/\\*([^*]|[\\r\\n]|(\\*+([^*\\/]|[\\r\\n])))*\\*+\\/)|(^[\\s]*\\/\\/.*)/gm,\"\"),c=a.compressSpaces(c),c=c.split(\"}\"),d=0;d<c.length;d++)if(a.trim(c[d])!=\"\")for(var b=c[d].split(\"{\"),k=b[0].split(\",\"),b=b[1].split(\";\"),e=0;e<k.length;e++){var f=a.trim(k[e]);if(f!=\"\"){for(var g={},j=0;j<b.length;j++){var h=b[j].indexOf(\":\"),l=b[j].substr(0,h),h=b[j].substr(h+1,b[j].length-h);l!=null&&h!=null&&(g[a.trim(l)]=new a.Property(a.trim(l),a.trim(h)))}a.Styles[f]=g;if(f==\"@font-face\"){f=g[\"font-family\"].value.replace(/\"/g,\r\n\"\");g=g.src.value.split(\",\");for(j=0;j<g.length;j++)if(g[j].indexOf('format(\"svg\")')>0){l=g[j].indexOf(\"url\");h=g[j].indexOf(\")\",l);l=g[j].substr(l+5,h-l-6);l=a.parseXml(a.ajax(l)).getElementsByTagName(\"font\");for(h=0;h<l.length;h++){var o=a.CreateElement(l[h]);a.Definitions[f]=o}}}}}};a.Element.style.prototype=new a.Element.ElementBase;a.Element.use=function(c){this.base=a.Element.RenderedElementBase;this.base(c);this.baseSetContext=this.setContext;this.setContext=function(a){this.baseSetContext(a);\r\nthis.attribute(\"x\").hasValue()&&a.translate(this.attribute(\"x\").Length.toPixels(\"x\"),0);this.attribute(\"y\").hasValue()&&a.translate(0,this.attribute(\"y\").Length.toPixels(\"y\"))};this.getDefinition=function(){var a=this.attribute(\"xlink:href\").Definition.getDefinition();if(this.attribute(\"width\").hasValue())a.attribute(\"width\",!0).value=this.attribute(\"width\").value;if(this.attribute(\"height\").hasValue())a.attribute(\"height\",!0).value=this.attribute(\"height\").value;return a};this.path=function(a){var b=\r\nthis.getDefinition();b!=null&&b.path(a)};this.renderChildren=function(a){var b=this.getDefinition();b!=null&&b.render(a)}};a.Element.use.prototype=new a.Element.RenderedElementBase;a.Element.mask=function(c){this.base=a.Element.ElementBase;this.base(c);this.apply=function(a,b){var c=this.attribute(\"x\").Length.toPixels(\"x\"),e=this.attribute(\"y\").Length.toPixels(\"y\"),f=this.attribute(\"width\").Length.toPixels(\"x\"),g=this.attribute(\"height\").Length.toPixels(\"y\"),j=b.attribute(\"mask\").value;b.attribute(\"mask\").value=\r\n\"\";var h=document.createElement(\"canvas\");h.width=c+f;h.height=e+g;var l=h.getContext(\"2d\");this.renderChildren(l);var o=document.createElement(\"canvas\");o.width=c+f;o.height=e+g;var n=o.getContext(\"2d\");b.render(n);n.globalCompositeOperation=\"destination-in\";n.fillStyle=l.createPattern(h,\"no-repeat\");n.fillRect(0,0,c+f,e+g);a.fillStyle=n.createPattern(o,\"no-repeat\");a.fillRect(0,0,c+f,e+g);b.attribute(\"mask\").value=j};this.render=function(){}};a.Element.mask.prototype=new a.Element.ElementBase;a.Element.clipPath=\r\nfunction(c){this.base=a.Element.ElementBase;this.base(c);this.apply=function(a){for(var b=0;b<this.children.length;b++)this.children[b].path&&(this.children[b].path(a),a.clip())};this.render=function(){}};a.Element.clipPath.prototype=new a.Element.ElementBase;a.Element.filter=function(c){this.base=a.Element.ElementBase;this.base(c);this.apply=function(a,b){var c=b.getBoundingBox(),e=this.attribute(\"x\").Length.toPixels(\"x\"),f=this.attribute(\"y\").Length.toPixels(\"y\");if(e==0||f==0)e=c.x1,f=c.y1;var g=\r\nthis.attribute(\"width\").Length.toPixels(\"x\"),j=this.attribute(\"height\").Length.toPixels(\"y\");if(g==0||j==0)g=c.width(),j=c.height();c=b.style(\"filter\").value;b.style(\"filter\").value=\"\";var h=0.2*g,l=0.2*j,o=document.createElement(\"canvas\");o.width=g+2*h;o.height=j+2*l;var n=o.getContext(\"2d\");n.translate(-e+h,-f+l);b.render(n);for(var q=0;q<this.children.length;q++)this.children[q].apply(n,0,0,g+2*h,j+2*l);a.drawImage(o,0,0,g+2*h,j+2*l,e-h,f-l,g+2*h,j+2*l);b.style(\"filter\",!0).value=c};this.render=\r\nfunction(){}};a.Element.filter.prototype=new a.Element.ElementBase;a.Element.feGaussianBlur=function(c){function d(a,c,d,f,g){for(var j=0;j<g;j++)for(var h=0;h<f;h++)for(var l=a[j*f*4+h*4+3]/255,o=0;o<4;o++){for(var n=d[0]*(l==0?255:a[j*f*4+h*4+o])*(l==0||o==3?1:l),q=1;q<d.length;q++){var p=Math.max(h-q,0),m=a[j*f*4+p*4+3]/255,p=Math.min(h+q,f-1),p=a[j*f*4+p*4+3]/255,s=d[q],r;m==0?r=255:(r=Math.max(h-q,0),r=a[j*f*4+r*4+o]);m=r*(m==0||o==3?1:m);p==0?r=255:(r=Math.min(h+q,f-1),r=a[j*f*4+r*4+o]);n+=\r\ns*(m+r*(p==0||o==3?1:p))}c[h*g*4+j*4+o]=n}}this.base=a.Element.ElementBase;this.base(c);this.apply=function(a,c,e,f,g){var e=this.attribute(\"stdDeviation\").numValue(),c=a.getImageData(0,0,f,g),e=Math.max(e,0.01),j=Math.ceil(e*4)+1;mask=[];for(var h=0;h<j;h++)mask[h]=Math.exp(-0.5*(h/e)*(h/e));e=mask;j=0;for(h=1;h<e.length;h++)j+=Math.abs(e[h]);j=2*j+Math.abs(e[0]);for(h=0;h<e.length;h++)e[h]/=j;tmp=[];d(c.data,tmp,e,f,g);d(tmp,c.data,e,g,f);a.clearRect(0,0,f,g);a.putImageData(c,0,0)}};a.Element.filter.prototype=\r\nnew a.Element.feGaussianBlur;a.Element.title=function(){};a.Element.title.prototype=new a.Element.ElementBase;a.Element.desc=function(){};a.Element.desc.prototype=new a.Element.ElementBase;a.Element.MISSING=function(a){console.log(\"ERROR: Element '\"+a.nodeName+\"' not yet implemented.\")};a.Element.MISSING.prototype=new a.Element.ElementBase;a.CreateElement=function(c){var d=c.nodeName.replace(/^[^:]+:/,\"\"),d=d.replace(/\\-/g,\"\"),b=null,b=typeof a.Element[d]!=\"undefined\"?new a.Element[d](c):new a.Element.MISSING(c);\r\nb.type=c.nodeName;return b};a.load=function(c,d){a.loadXml(c,a.ajax(d))};a.loadXml=function(c,d){a.loadXmlDoc(c,a.parseXml(d))};a.loadXmlDoc=function(c,d){a.init(c);var b=function(a){for(var b=c.canvas;b;)a.x-=b.offsetLeft,a.y-=b.offsetTop,b=b.offsetParent;window.scrollX&&(a.x+=window.scrollX);window.scrollY&&(a.y+=window.scrollY);return a};if(a.opts.ignoreMouse!=!0)c.canvas.onclick=function(c){c=b(new a.Point(c!=null?c.clientX:event.clientX,c!=null?c.clientY:event.clientY));a.Mouse.onclick(c.x,c.y)},\r\nc.canvas.onmousemove=function(c){c=b(new a.Point(c!=null?c.clientX:event.clientX,c!=null?c.clientY:event.clientY));a.Mouse.onmousemove(c.x,c.y)};var k=a.CreateElement(d.documentElement),e=k.root=!0,f=function(){a.ViewPort.Clear();c.canvas.parentNode&&a.ViewPort.SetCurrent(c.canvas.parentNode.clientWidth,c.canvas.parentNode.clientHeight);if(a.opts.ignoreDimensions!=!0){if(k.style(\"width\").hasValue())c.canvas.width=k.style(\"width\").Length.toPixels(\"x\"),c.canvas.style.width=c.canvas.width+\"px\";if(k.style(\"height\").hasValue())c.canvas.height=\r\nk.style(\"height\").Length.toPixels(\"y\"),c.canvas.style.height=c.canvas.height+\"px\"}var b=c.canvas.clientWidth||c.canvas.width,d=c.canvas.clientHeight||c.canvas.height;a.ViewPort.SetCurrent(b,d);if(a.opts!=null&&a.opts.offsetX!=null)k.attribute(\"x\",!0).value=a.opts.offsetX;if(a.opts!=null&&a.opts.offsetY!=null)k.attribute(\"y\",!0).value=a.opts.offsetY;if(a.opts!=null&&a.opts.scaleWidth!=null&&a.opts.scaleHeight!=null){var f=1,g=1;k.attribute(\"width\").hasValue()&&(f=k.attribute(\"width\").Length.toPixels(\"x\")/\r\na.opts.scaleWidth);k.attribute(\"height\").hasValue()&&(g=k.attribute(\"height\").Length.toPixels(\"y\")/a.opts.scaleHeight);k.attribute(\"width\",!0).value=a.opts.scaleWidth;k.attribute(\"height\",!0).value=a.opts.scaleHeight;k.attribute(\"viewBox\",!0).value=\"0 0 \"+b*f+\" \"+d*g;k.attribute(\"preserveAspectRatio\",!0).value=\"none\"}a.opts.ignoreClear!=!0&&c.clearRect(0,0,b,d);k.render(c);e&&(e=!1,a.opts!=null&&typeof a.opts.renderCallback==\"function\"&&a.opts.renderCallback())},g=!0;a.ImagesLoaded()&&(g=!1,f());\r\na.intervalID=setInterval(function(){var b=!1;g&&a.ImagesLoaded()&&(g=!1,b=!0);a.opts.ignoreMouse!=!0&&(b|=a.Mouse.hasEvents());if(a.opts.ignoreAnimation!=!0)for(var c=0;c<a.Animations.length;c++)b|=a.Animations[c].update(1E3/a.FRAMERATE);a.opts!=null&&typeof a.opts.forceRedraw==\"function\"&&a.opts.forceRedraw()==!0&&(b=!0);b&&(f(),a.Mouse.runEvents())},1E3/a.FRAMERATE)};a.stop=function(){a.intervalID&&clearInterval(a.intervalID)};a.Mouse=new function(){this.events=[];this.hasEvents=function(){return this.events.length!=\r\n0};this.onclick=function(a,d){this.events.push({type:\"onclick\",x:a,y:d,run:function(a){if(a.onclick)a.onclick()}})};this.onmousemove=function(a,d){this.events.push({type:\"onmousemove\",x:a,y:d,run:function(a){if(a.onmousemove)a.onmousemove()}})};this.eventElements=[];this.checkPath=function(a,d){for(var b=0;b<this.events.length;b++){var k=this.events[b];d.isPointInPath&&d.isPointInPath(k.x,k.y)&&(this.eventElements[b]=a)}};this.checkBoundingBox=function(a,d){for(var b=0;b<this.events.length;b++){var k=\r\nthis.events[b];d.isPointInBox(k.x,k.y)&&(this.eventElements[b]=a)}};this.runEvents=function(){a.ctx.canvas.style.cursor=\"\";for(var c=0;c<this.events.length;c++)for(var d=this.events[c],b=this.eventElements[c];b;)d.run(b),b=b.parent;this.events=[];this.eventElements=[]}};return a}this.canvg=function(a,c,d){if(a==null&&c==null&&d==null)for(var c=document.getElementsByTagName(\"svg\"),b=0;b<c.length;b++){a=c[b];d=document.createElement(\"canvas\");d.width=a.clientWidth;d.height=a.clientHeight;a.parentNode.insertBefore(d,\r\na);a.parentNode.removeChild(a);var k=document.createElement(\"div\");k.appendChild(a);canvg(d,k.innerHTML)}else d=d||{},typeof a==\"string\"&&(a=document.getElementById(a)),a.svg==null?(b=m(),a.svg=b):(b=a.svg,b.stop()),b.opts=d,a=a.getContext(\"2d\"),typeof c.documentElement!=\"undefined\"?b.loadXmlDoc(a,c):c.substr(0,1)==\"<\"?b.loadXml(a,c):b.load(a,c)}})();\r\nif(CanvasRenderingContext2D)CanvasRenderingContext2D.prototype.drawSvg=function(m,a,c,d,b){canvg(this.canvas,m,{ignoreMouse:!0,ignoreAnimation:!0,ignoreDimensions:!0,ignoreClear:!0,offsetX:a,offsetY:c,scaleWidth:d,scaleHeight:b})};\r\n(function(m){var a=m.css,c=m.CanVGRenderer,d=m.SVGRenderer,b=m.extend,k=m.merge,e=m.addEvent,f=m.createElement,g=m.discardElement;b(c.prototype,d.prototype);b(c.prototype,{create:function(a,b,c,d){this.setContainer(b,c,d);this.configure(a)},setContainer:function(a,b,c){var d=a.style,e=a.parentNode,g=d.left,d=d.top,k=a.offsetWidth,m=a.offsetHeight,s={visibility:\"hidden\",position:\"absolute\"};this.init.apply(this,[a,b,c]);this.canvas=f(\"canvas\",{width:k,height:m},{position:\"relative\",left:g,top:d},a);\r\nthis.ttLine=f(\"div\",null,s,e);this.ttDiv=f(\"div\",null,s,e);this.ttTimer=void 0;this.hiddenSvg=a=f(\"div\",{width:k,height:m},{visibility:\"hidden\",left:g,top:d},e);a.appendChild(this.box)},configure:function(b){var c=this,d=b.options.tooltip,f=d.borderWidth,g=c.ttDiv,m=d.style,p=c.ttLine,t=parseInt(m.padding,10),m=k(m,{padding:t+\"px\",\"background-color\":d.backgroundColor,\"border-style\":\"solid\",\"border-width\":f+\"px\",\"border-radius\":d.borderRadius+\"px\"});d.shadow&&(m=k(m,{\"box-shadow\":\"1px 1px 3px gray\",\r\n\"-webkit-box-shadow\":\"1px 1px 3px gray\"}));a(g,m);a(p,{\"border-left\":\"1px solid darkgray\"});e(b,\"tooltipRefresh\",function(d){var e=b.container,f=e.offsetLeft,e=e.offsetTop,k;g.innerHTML=d.text;k=b.tooltip.getPosition(g.offsetWidth,g.offsetHeight,{plotX:d.x,plotY:d.y});a(g,{visibility:\"visible\",left:k.x+\"px\",top:k.y+\"px\",\"border-color\":d.borderColor});a(p,{visibility:\"visible\",left:f+d.x+\"px\",top:e+b.plotTop+\"px\",height:b.plotHeight+\"px\"});c.ttTimer!==void 0&&clearTimeout(c.ttTimer);c.ttTimer=setTimeout(function(){a(g,\r\n{visibility:\"hidden\"});a(p,{visibility:\"hidden\"})},3E3)})},destroy:function(){g(this.canvas);this.ttTimer!==void 0&&clearTimeout(this.ttTimer);g(this.ttLine);g(this.ttDiv);g(this.hiddenSvg);return d.prototype.destroy.apply(this)},color:function(a,b,c){a&&a.linearGradient&&(a=a.stops[a.stops.length-1][1]);return d.prototype.color.call(this,a,b,c)},draw:function(){window.canvg(this.canvas,this.hiddenSvg.innerHTML)}})})(Highcharts);\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/highcharts/modules/canvas-tools.src.js",
    "content": "/**\r\n * @license A class to parse color values\r\n * @author Stoyan Stefanov <sstoo@gmail.com>\r\n * @link   http://www.phpied.com/rgb-color-parser-in-javascript/\r\n * Use it if you like it\r\n *\r\n */\r\nfunction RGBColor(color_string)\r\n{\r\n    this.ok = false;\r\n\r\n    // strip any leading #\r\n    if (color_string.charAt(0) == '#') { // remove # if any\r\n        color_string = color_string.substr(1,6);\r\n    }\r\n\r\n    color_string = color_string.replace(/ /g,'');\r\n    color_string = color_string.toLowerCase();\r\n\r\n    // before getting into regexps, try simple matches\r\n    // and overwrite the input\r\n    var simple_colors = {\r\n        aliceblue: 'f0f8ff',\r\n        antiquewhite: 'faebd7',\r\n        aqua: '00ffff',\r\n        aquamarine: '7fffd4',\r\n        azure: 'f0ffff',\r\n        beige: 'f5f5dc',\r\n        bisque: 'ffe4c4',\r\n        black: '000000',\r\n        blanchedalmond: 'ffebcd',\r\n        blue: '0000ff',\r\n        blueviolet: '8a2be2',\r\n        brown: 'a52a2a',\r\n        burlywood: 'deb887',\r\n        cadetblue: '5f9ea0',\r\n        chartreuse: '7fff00',\r\n        chocolate: 'd2691e',\r\n        coral: 'ff7f50',\r\n        cornflowerblue: '6495ed',\r\n        cornsilk: 'fff8dc',\r\n        crimson: 'dc143c',\r\n        cyan: '00ffff',\r\n        darkblue: '00008b',\r\n        darkcyan: '008b8b',\r\n        darkgoldenrod: 'b8860b',\r\n        darkgray: 'a9a9a9',\r\n        darkgreen: '006400',\r\n        darkkhaki: 'bdb76b',\r\n        darkmagenta: '8b008b',\r\n        darkolivegreen: '556b2f',\r\n        darkorange: 'ff8c00',\r\n        darkorchid: '9932cc',\r\n        darkred: '8b0000',\r\n        darksalmon: 'e9967a',\r\n        darkseagreen: '8fbc8f',\r\n        darkslateblue: '483d8b',\r\n        darkslategray: '2f4f4f',\r\n        darkturquoise: '00ced1',\r\n        darkviolet: '9400d3',\r\n        deeppink: 'ff1493',\r\n        deepskyblue: '00bfff',\r\n        dimgray: '696969',\r\n        dodgerblue: '1e90ff',\r\n        feldspar: 'd19275',\r\n        firebrick: 'b22222',\r\n        floralwhite: 'fffaf0',\r\n        forestgreen: '228b22',\r\n        fuchsia: 'ff00ff',\r\n        gainsboro: 'dcdcdc',\r\n        ghostwhite: 'f8f8ff',\r\n        gold: 'ffd700',\r\n        goldenrod: 'daa520',\r\n        gray: '808080',\r\n        green: '008000',\r\n        greenyellow: 'adff2f',\r\n        honeydew: 'f0fff0',\r\n        hotpink: 'ff69b4',\r\n        indianred : 'cd5c5c',\r\n        indigo : '4b0082',\r\n        ivory: 'fffff0',\r\n        khaki: 'f0e68c',\r\n        lavender: 'e6e6fa',\r\n        lavenderblush: 'fff0f5',\r\n        lawngreen: '7cfc00',\r\n        lemonchiffon: 'fffacd',\r\n        lightblue: 'add8e6',\r\n        lightcoral: 'f08080',\r\n        lightcyan: 'e0ffff',\r\n        lightgoldenrodyellow: 'fafad2',\r\n        lightgrey: 'd3d3d3',\r\n        lightgreen: '90ee90',\r\n        lightpink: 'ffb6c1',\r\n        lightsalmon: 'ffa07a',\r\n        lightseagreen: '20b2aa',\r\n        lightskyblue: '87cefa',\r\n        lightslateblue: '8470ff',\r\n        lightslategray: '778899',\r\n        lightsteelblue: 'b0c4de',\r\n        lightyellow: 'ffffe0',\r\n        lime: '00ff00',\r\n        limegreen: '32cd32',\r\n        linen: 'faf0e6',\r\n        magenta: 'ff00ff',\r\n        maroon: '800000',\r\n        mediumaquamarine: '66cdaa',\r\n        mediumblue: '0000cd',\r\n        mediumorchid: 'ba55d3',\r\n        mediumpurple: '9370d8',\r\n        mediumseagreen: '3cb371',\r\n        mediumslateblue: '7b68ee',\r\n        mediumspringgreen: '00fa9a',\r\n        mediumturquoise: '48d1cc',\r\n        mediumvioletred: 'c71585',\r\n        midnightblue: '191970',\r\n        mintcream: 'f5fffa',\r\n        mistyrose: 'ffe4e1',\r\n        moccasin: 'ffe4b5',\r\n        navajowhite: 'ffdead',\r\n        navy: '000080',\r\n        oldlace: 'fdf5e6',\r\n        olive: '808000',\r\n        olivedrab: '6b8e23',\r\n        orange: 'ffa500',\r\n        orangered: 'ff4500',\r\n        orchid: 'da70d6',\r\n        palegoldenrod: 'eee8aa',\r\n        palegreen: '98fb98',\r\n        paleturquoise: 'afeeee',\r\n        palevioletred: 'd87093',\r\n        papayawhip: 'ffefd5',\r\n        peachpuff: 'ffdab9',\r\n        peru: 'cd853f',\r\n        pink: 'ffc0cb',\r\n        plum: 'dda0dd',\r\n        powderblue: 'b0e0e6',\r\n        purple: '800080',\r\n        red: 'ff0000',\r\n        rosybrown: 'bc8f8f',\r\n        royalblue: '4169e1',\r\n        saddlebrown: '8b4513',\r\n        salmon: 'fa8072',\r\n        sandybrown: 'f4a460',\r\n        seagreen: '2e8b57',\r\n        seashell: 'fff5ee',\r\n        sienna: 'a0522d',\r\n        silver: 'c0c0c0',\r\n        skyblue: '87ceeb',\r\n        slateblue: '6a5acd',\r\n        slategray: '708090',\r\n        snow: 'fffafa',\r\n        springgreen: '00ff7f',\r\n        steelblue: '4682b4',\r\n        tan: 'd2b48c',\r\n        teal: '008080',\r\n        thistle: 'd8bfd8',\r\n        tomato: 'ff6347',\r\n        turquoise: '40e0d0',\r\n        violet: 'ee82ee',\r\n        violetred: 'd02090',\r\n        wheat: 'f5deb3',\r\n        white: 'ffffff',\r\n        whitesmoke: 'f5f5f5',\r\n        yellow: 'ffff00',\r\n        yellowgreen: '9acd32'\r\n    };\r\n    for (var key in simple_colors) {\r\n        if (color_string == key) {\r\n            color_string = simple_colors[key];\r\n        }\r\n    }\r\n    // emd of simple type-in colors\r\n\r\n    // array of color definition objects\r\n    var color_defs = [\r\n        {\r\n            re: /^rgb\\((\\d{1,3}),\\s*(\\d{1,3}),\\s*(\\d{1,3})\\)$/,\r\n            example: ['rgb(123, 234, 45)', 'rgb(255,234,245)'],\r\n            process: function (bits){\r\n                return [\r\n                    parseInt(bits[1]),\r\n                    parseInt(bits[2]),\r\n                    parseInt(bits[3])\r\n                ];\r\n            }\r\n        },\r\n        {\r\n            re: /^(\\w{2})(\\w{2})(\\w{2})$/,\r\n            example: ['#00ff00', '336699'],\r\n            process: function (bits){\r\n                return [\r\n                    parseInt(bits[1], 16),\r\n                    parseInt(bits[2], 16),\r\n                    parseInt(bits[3], 16)\r\n                ];\r\n            }\r\n        },\r\n        {\r\n            re: /^(\\w{1})(\\w{1})(\\w{1})$/,\r\n            example: ['#fb0', 'f0f'],\r\n            process: function (bits){\r\n                return [\r\n                    parseInt(bits[1] + bits[1], 16),\r\n                    parseInt(bits[2] + bits[2], 16),\r\n                    parseInt(bits[3] + bits[3], 16)\r\n                ];\r\n            }\r\n        }\r\n    ];\r\n\r\n    // search through the definitions to find a match\r\n    for (var i = 0; i < color_defs.length; i++) {\r\n        var re = color_defs[i].re;\r\n        var processor = color_defs[i].process;\r\n        var bits = re.exec(color_string);\r\n        if (bits) {\r\n            channels = processor(bits);\r\n            this.r = channels[0];\r\n            this.g = channels[1];\r\n            this.b = channels[2];\r\n            this.ok = true;\r\n        }\r\n\r\n    }\r\n\r\n    // validate/cleanup values\r\n    this.r = (this.r < 0 || isNaN(this.r)) ? 0 : ((this.r > 255) ? 255 : this.r);\r\n    this.g = (this.g < 0 || isNaN(this.g)) ? 0 : ((this.g > 255) ? 255 : this.g);\r\n    this.b = (this.b < 0 || isNaN(this.b)) ? 0 : ((this.b > 255) ? 255 : this.b);\r\n\r\n    // some getters\r\n    this.toRGB = function () {\r\n        return 'rgb(' + this.r + ', ' + this.g + ', ' + this.b + ')';\r\n    }\r\n    this.toHex = function () {\r\n        var r = this.r.toString(16);\r\n        var g = this.g.toString(16);\r\n        var b = this.b.toString(16);\r\n        if (r.length == 1) r = '0' + r;\r\n        if (g.length == 1) g = '0' + g;\r\n        if (b.length == 1) b = '0' + b;\r\n        return '#' + r + g + b;\r\n    }\r\n\r\n    // help\r\n    this.getHelpXML = function () {\r\n\r\n        var examples = new Array();\r\n        // add regexps\r\n        for (var i = 0; i < color_defs.length; i++) {\r\n            var example = color_defs[i].example;\r\n            for (var j = 0; j < example.length; j++) {\r\n                examples[examples.length] = example[j];\r\n            }\r\n        }\r\n        // add type-in colors\r\n        for (var sc in simple_colors) {\r\n            examples[examples.length] = sc;\r\n        }\r\n\r\n        var xml = document.createElement('ul');\r\n        xml.setAttribute('id', 'rgbcolor-examples');\r\n        for (var i = 0; i < examples.length; i++) {\r\n            try {\r\n                var list_item = document.createElement('li');\r\n                var list_color = new RGBColor(examples[i]);\r\n                var example_div = document.createElement('div');\r\n                example_div.style.cssText =\r\n                        'margin: 3px; '\r\n                        + 'border: 1px solid black; '\r\n                        + 'background:' + list_color.toHex() + '; '\r\n                        + 'color:' + list_color.toHex()\r\n                ;\r\n                example_div.appendChild(document.createTextNode('test'));\r\n                var list_item_value = document.createTextNode(\r\n                    ' ' + examples[i] + ' -> ' + list_color.toRGB() + ' -> ' + list_color.toHex()\r\n                );\r\n                list_item.appendChild(example_div);\r\n                list_item.appendChild(list_item_value);\r\n                xml.appendChild(list_item);\r\n\r\n            } catch(e){}\r\n        }\r\n        return xml;\r\n\r\n    }\r\n\r\n}\r\n\r\n/**\r\n * @license canvg.js - Javascript SVG parser and renderer on Canvas\r\n * MIT Licensed \r\n * Gabe Lerner (gabelerner@gmail.com)\r\n * http://code.google.com/p/canvg/\r\n *\r\n * Requires: rgbcolor.js - http://www.phpied.com/rgb-color-parser-in-javascript/\r\n *\r\n */\r\nif(!window.console) {\r\n\twindow.console = {};\r\n\twindow.console.log = function(str) {};\r\n\twindow.console.dir = function(str) {};\r\n}\r\n\r\nif(!Array.prototype.indexOf){\r\n\tArray.prototype.indexOf = function(obj){\r\n\t\tfor(var i=0; i<this.length; i++){\r\n\t\t\tif(this[i]==obj){\r\n\t\t\t\treturn i;\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn -1;\r\n\t}\r\n}\r\n\r\n(function(){\r\n\t// canvg(target, s)\r\n\t// empty parameters: replace all 'svg' elements on page with 'canvas' elements\r\n\t// target: canvas element or the id of a canvas element\r\n\t// s: svg string, url to svg file, or xml document\r\n\t// opts: optional hash of options\r\n\t//\t\t ignoreMouse: true => ignore mouse events\r\n\t//\t\t ignoreAnimation: true => ignore animations\r\n\t//\t\t ignoreDimensions: true => does not try to resize canvas\r\n\t//\t\t ignoreClear: true => does not clear canvas\r\n\t//\t\t offsetX: int => draws at a x offset\r\n\t//\t\t offsetY: int => draws at a y offset\r\n\t//\t\t scaleWidth: int => scales horizontally to width\r\n\t//\t\t scaleHeight: int => scales vertically to height\r\n\t//\t\t renderCallback: function => will call the function after the first render is completed\r\n\t//\t\t forceRedraw: function => will call the function on every frame, if it returns true, will redraw\r\n\tthis.canvg = function (target, s, opts) {\r\n\t\t// no parameters\r\n\t\tif (target == null && s == null && opts == null) {\r\n\t\t\tvar svgTags = document.getElementsByTagName('svg');\r\n\t\t\tfor (var i=0; i<svgTags.length; i++) {\r\n\t\t\t\tvar svgTag = svgTags[i];\r\n\t\t\t\tvar c = document.createElement('canvas');\r\n\t\t\t\tc.width = svgTag.clientWidth;\r\n\t\t\t\tc.height = svgTag.clientHeight;\r\n\t\t\t\tsvgTag.parentNode.insertBefore(c, svgTag);\r\n\t\t\t\tsvgTag.parentNode.removeChild(svgTag);\r\n\t\t\t\tvar div = document.createElement('div');\r\n\t\t\t\tdiv.appendChild(svgTag);\r\n\t\t\t\tcanvg(c, div.innerHTML);\r\n\t\t\t}\r\n\t\t\treturn;\r\n\t\t}\t\r\n\t\topts = opts || {};\r\n\t\r\n\t\tif (typeof target == 'string') {\r\n\t\t\ttarget = document.getElementById(target);\r\n\t\t}\r\n\t\t\r\n\t\t// reuse class per canvas\r\n\t\tvar svg;\r\n\t\tif (target.svg == null) {\r\n\t\t\tsvg = build();\r\n\t\t\ttarget.svg = svg;\r\n\t\t}\r\n\t\telse {\r\n\t\t\tsvg = target.svg;\r\n\t\t\tsvg.stop();\r\n\t\t}\r\n\t\tsvg.opts = opts;\r\n\t\t\r\n\t\tvar ctx = target.getContext('2d');\r\n\t\tif (typeof(s.documentElement) != 'undefined') {\r\n\t\t\t// load from xml doc\r\n\t\t\tsvg.loadXmlDoc(ctx, s);\r\n\t\t}\r\n\t\telse if (s.substr(0,1) == '<') {\r\n\t\t\t// load from xml string\r\n\t\t\tsvg.loadXml(ctx, s);\r\n\t\t}\r\n\t\telse {\r\n\t\t\t// load from url\r\n\t\t\tsvg.load(ctx, s);\r\n\t\t}\r\n\t}\r\n\r\n\tfunction build() {\r\n\t\tvar svg = { };\r\n\t\t\r\n\t\tsvg.FRAMERATE = 30;\r\n\t\tsvg.MAX_VIRTUAL_PIXELS = 30000;\r\n\t\t\r\n\t\t// globals\r\n\t\tsvg.init = function(ctx) {\r\n\t\t\tsvg.Definitions = {};\r\n\t\t\tsvg.Styles = {};\r\n\t\t\tsvg.Animations = [];\r\n\t\t\tsvg.Images = [];\r\n\t\t\tsvg.ctx = ctx;\r\n\t\t\tsvg.ViewPort = new (function () {\r\n\t\t\t\tthis.viewPorts = [];\r\n\t\t\t\tthis.Clear = function() { this.viewPorts = []; }\r\n\t\t\t\tthis.SetCurrent = function(width, height) { this.viewPorts.push({ width: width, height: height }); }\r\n\t\t\t\tthis.RemoveCurrent = function() { this.viewPorts.pop(); }\r\n\t\t\t\tthis.Current = function() { return this.viewPorts[this.viewPorts.length - 1]; }\r\n\t\t\t\tthis.width = function() { return this.Current().width; }\r\n\t\t\t\tthis.height = function() { return this.Current().height; }\r\n\t\t\t\tthis.ComputeSize = function(d) {\r\n\t\t\t\t\tif (d != null && typeof(d) == 'number') return d;\r\n\t\t\t\t\tif (d == 'x') return this.width();\r\n\t\t\t\t\tif (d == 'y') return this.height();\r\n\t\t\t\t\treturn Math.sqrt(Math.pow(this.width(), 2) + Math.pow(this.height(), 2)) / Math.sqrt(2);\t\t\t\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t}\r\n\t\tsvg.init();\r\n\t\t\r\n\t\t// images loaded\r\n\t\tsvg.ImagesLoaded = function() { \r\n\t\t\tfor (var i=0; i<svg.Images.length; i++) {\r\n\t\t\t\tif (!svg.Images[i].loaded) return false;\r\n\t\t\t}\r\n\t\t\treturn true;\r\n\t\t}\r\n\r\n\t\t// trim\r\n\t\tsvg.trim = function(s) { return s.replace(/^\\s+|\\s+$/g, ''); }\r\n\t\t\r\n\t\t// compress spaces\r\n\t\tsvg.compressSpaces = function(s) { return s.replace(/[\\s\\r\\t\\n]+/gm,' '); }\r\n\t\t\r\n\t\t// ajax\r\n\t\tsvg.ajax = function(url) {\r\n\t\t\tvar AJAX;\r\n\t\t\tif(window.XMLHttpRequest){AJAX=new XMLHttpRequest();}\r\n\t\t\telse{AJAX=new ActiveXObject('Microsoft.XMLHTTP');}\r\n\t\t\tif(AJAX){\r\n\t\t\t   AJAX.open('GET',url,false);\r\n\t\t\t   AJAX.send(null);\r\n\t\t\t   return AJAX.responseText;\r\n\t\t\t}\r\n\t\t\treturn null;\r\n\t\t} \r\n\t\t\r\n\t\t// parse xml\r\n\t\tsvg.parseXml = function(xml) {\r\n\t\t\tif (window.DOMParser)\r\n\t\t\t{\r\n\t\t\t\tvar parser = new DOMParser();\r\n\t\t\t\treturn parser.parseFromString(xml, 'text/xml');\r\n\t\t\t}\r\n\t\t\telse \r\n\t\t\t{\r\n\t\t\t\txml = xml.replace(/<!DOCTYPE svg[^>]*>/, '');\r\n\t\t\t\tvar xmlDoc = new ActiveXObject('Microsoft.XMLDOM');\r\n\t\t\t\txmlDoc.async = 'false';\r\n\t\t\t\txmlDoc.loadXML(xml); \r\n\t\t\t\treturn xmlDoc;\r\n\t\t\t}\t\t\r\n\t\t}\r\n\t\t\r\n\t\tsvg.Property = function(name, value) {\r\n\t\t\tthis.name = name;\r\n\t\t\tthis.value = value;\r\n\t\t\t\r\n\t\t\tthis.hasValue = function() {\r\n\t\t\t\treturn (this.value != null && this.value !== '');\r\n\t\t\t}\r\n\t\t\t\t\t\t\t\r\n\t\t\t// return the numerical value of the property\r\n\t\t\tthis.numValue = function() {\r\n\t\t\t\tif (!this.hasValue()) return 0;\r\n\t\t\t\t\r\n\t\t\t\tvar n = parseFloat(this.value);\r\n\t\t\t\tif ((this.value + '').match(/%$/)) {\r\n\t\t\t\t\tn = n / 100.0;\r\n\t\t\t\t}\r\n\t\t\t\treturn n;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.valueOrDefault = function(def) {\r\n\t\t\t\tif (this.hasValue()) return this.value;\r\n\t\t\t\treturn def;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.numValueOrDefault = function(def) {\r\n\t\t\t\tif (this.hasValue()) return this.numValue();\r\n\t\t\t\treturn def;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t/* EXTENSIONS */\r\n\t\t\tvar that = this;\r\n\t\t\t\r\n\t\t\t// color extensions\r\n\t\t\tthis.Color = {\r\n\t\t\t\t// augment the current color value with the opacity\r\n\t\t\t\taddOpacity: function(opacity) {\r\n\t\t\t\t\tvar newValue = that.value;\r\n\t\t\t\t\tif (opacity != null && opacity != '') {\r\n\t\t\t\t\t\tvar color = new RGBColor(that.value);\r\n\t\t\t\t\t\tif (color.ok) {\r\n\t\t\t\t\t\t\tnewValue = 'rgba(' + color.r + ', ' + color.g + ', ' + color.b + ', ' + opacity + ')';\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\treturn new svg.Property(that.name, newValue);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// definition extensions\r\n\t\t\tthis.Definition = {\r\n\t\t\t\t// get the definition from the definitions table\r\n\t\t\t\tgetDefinition: function() {\r\n\t\t\t\t\tvar name = that.value.replace(/^(url\\()?#([^\\)]+)\\)?$/, '$2');\r\n\t\t\t\t\treturn svg.Definitions[name];\r\n\t\t\t\t},\r\n\t\t\t\t\r\n\t\t\t\tisUrl: function() {\r\n\t\t\t\t\treturn that.value.indexOf('url(') == 0\r\n\t\t\t\t},\r\n\t\t\t\t\r\n\t\t\t\tgetFillStyle: function(e) {\r\n\t\t\t\t\tvar def = this.getDefinition();\r\n\t\t\t\t\t\r\n\t\t\t\t\t// gradient\r\n\t\t\t\t\tif (def != null && def.createGradient) {\r\n\t\t\t\t\t\treturn def.createGradient(svg.ctx, e);\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\t// pattern\r\n\t\t\t\t\tif (def != null && def.createPattern) {\r\n\t\t\t\t\t\treturn def.createPattern(svg.ctx, e);\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\treturn null;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// length extensions\r\n\t\t\tthis.Length = {\r\n\t\t\t\tDPI: function(viewPort) {\r\n\t\t\t\t\treturn 96.0; // TODO: compute?\r\n\t\t\t\t},\r\n\t\t\t\t\r\n\t\t\t\tEM: function(viewPort) {\r\n\t\t\t\t\tvar em = 12;\r\n\t\t\t\t\t\r\n\t\t\t\t\tvar fontSize = new svg.Property('fontSize', svg.Font.Parse(svg.ctx.font).fontSize);\r\n\t\t\t\t\tif (fontSize.hasValue()) em = fontSize.Length.toPixels(viewPort);\r\n\t\t\t\t\t\r\n\t\t\t\t\treturn em;\r\n\t\t\t\t},\r\n\t\t\t\r\n\t\t\t\t// get the length as pixels\r\n\t\t\t\ttoPixels: function(viewPort) {\r\n\t\t\t\t\tif (!that.hasValue()) return 0;\r\n\t\t\t\t\tvar s = that.value+'';\r\n\t\t\t\t\tif (s.match(/em$/)) return that.numValue() * this.EM(viewPort);\r\n\t\t\t\t\tif (s.match(/ex$/)) return that.numValue() * this.EM(viewPort) / 2.0;\r\n\t\t\t\t\tif (s.match(/px$/)) return that.numValue();\r\n\t\t\t\t\tif (s.match(/pt$/)) return that.numValue() * 1.25;\r\n\t\t\t\t\tif (s.match(/pc$/)) return that.numValue() * 15;\r\n\t\t\t\t\tif (s.match(/cm$/)) return that.numValue() * this.DPI(viewPort) / 2.54;\r\n\t\t\t\t\tif (s.match(/mm$/)) return that.numValue() * this.DPI(viewPort) / 25.4;\r\n\t\t\t\t\tif (s.match(/in$/)) return that.numValue() * this.DPI(viewPort);\r\n\t\t\t\t\tif (s.match(/%$/)) return that.numValue() * svg.ViewPort.ComputeSize(viewPort);\r\n\t\t\t\t\treturn that.numValue();\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// time extensions\r\n\t\t\tthis.Time = {\r\n\t\t\t\t// get the time as milliseconds\r\n\t\t\t\ttoMilliseconds: function() {\r\n\t\t\t\t\tif (!that.hasValue()) return 0;\r\n\t\t\t\t\tvar s = that.value+'';\r\n\t\t\t\t\tif (s.match(/s$/)) return that.numValue() * 1000;\r\n\t\t\t\t\tif (s.match(/ms$/)) return that.numValue();\r\n\t\t\t\t\treturn that.numValue();\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// angle extensions\r\n\t\t\tthis.Angle = {\r\n\t\t\t\t// get the angle as radians\r\n\t\t\t\ttoRadians: function() {\r\n\t\t\t\t\tif (!that.hasValue()) return 0;\r\n\t\t\t\t\tvar s = that.value+'';\r\n\t\t\t\t\tif (s.match(/deg$/)) return that.numValue() * (Math.PI / 180.0);\r\n\t\t\t\t\tif (s.match(/grad$/)) return that.numValue() * (Math.PI / 200.0);\r\n\t\t\t\t\tif (s.match(/rad$/)) return that.numValue();\r\n\t\t\t\t\treturn that.numValue() * (Math.PI / 180.0);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// fonts\r\n\t\tsvg.Font = new (function() {\r\n\t\t\tthis.Styles = ['normal','italic','oblique','inherit'];\r\n\t\t\tthis.Variants = ['normal','small-caps','inherit'];\r\n\t\t\tthis.Weights = ['normal','bold','bolder','lighter','100','200','300','400','500','600','700','800','900','inherit'];\r\n\t\t\t\r\n\t\t\tthis.CreateFont = function(fontStyle, fontVariant, fontWeight, fontSize, fontFamily, inherit) { \r\n\t\t\t\tvar f = inherit != null ? this.Parse(inherit) : this.CreateFont('', '', '', '', '', svg.ctx.font);\r\n\t\t\t\treturn { \r\n\t\t\t\t\tfontFamily: fontFamily || f.fontFamily, \r\n\t\t\t\t\tfontSize: fontSize || f.fontSize, \r\n\t\t\t\t\tfontStyle: fontStyle || f.fontStyle, \r\n\t\t\t\t\tfontWeight: fontWeight || f.fontWeight, \r\n\t\t\t\t\tfontVariant: fontVariant || f.fontVariant,\r\n\t\t\t\t\ttoString: function () { return [this.fontStyle, this.fontVariant, this.fontWeight, this.fontSize, this.fontFamily].join(' ') } \r\n\t\t\t\t} \r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tvar that = this;\r\n\t\t\tthis.Parse = function(s) {\r\n\t\t\t\tvar f = {};\r\n\t\t\t\tvar d = svg.trim(svg.compressSpaces(s || '')).split(' ');\r\n\t\t\t\tvar set = { fontSize: false, fontStyle: false, fontWeight: false, fontVariant: false }\r\n\t\t\t\tvar ff = '';\r\n\t\t\t\tfor (var i=0; i<d.length; i++) {\r\n\t\t\t\t\tif (!set.fontStyle && that.Styles.indexOf(d[i]) != -1) { if (d[i] != 'inherit') f.fontStyle = d[i]; set.fontStyle = true; }\r\n\t\t\t\t\telse if (!set.fontVariant && that.Variants.indexOf(d[i]) != -1) { if (d[i] != 'inherit') f.fontVariant = d[i]; set.fontStyle = set.fontVariant = true;\t}\r\n\t\t\t\t\telse if (!set.fontWeight && that.Weights.indexOf(d[i]) != -1) {\tif (d[i] != 'inherit') f.fontWeight = d[i]; set.fontStyle = set.fontVariant = set.fontWeight = true; }\r\n\t\t\t\t\telse if (!set.fontSize) { if (d[i] != 'inherit') f.fontSize = d[i].split('/')[0]; set.fontStyle = set.fontVariant = set.fontWeight = set.fontSize = true; }\r\n\t\t\t\t\telse { if (d[i] != 'inherit') ff += d[i]; }\r\n\t\t\t\t} if (ff != '') f.fontFamily = ff;\r\n\t\t\t\treturn f;\r\n\t\t\t}\r\n\t\t});\r\n\t\t\r\n\t\t// points and paths\r\n\t\tsvg.ToNumberArray = function(s) {\r\n\t\t\tvar a = svg.trim(svg.compressSpaces((s || '').replace(/,/g, ' '))).split(' ');\r\n\t\t\tfor (var i=0; i<a.length; i++) {\r\n\t\t\t\ta[i] = parseFloat(a[i]);\r\n\t\t\t}\r\n\t\t\treturn a;\r\n\t\t}\t\t\r\n\t\tsvg.Point = function(x, y) {\r\n\t\t\tthis.x = x;\r\n\t\t\tthis.y = y;\r\n\t\t\t\r\n\t\t\tthis.angleTo = function(p) {\r\n\t\t\t\treturn Math.atan2(p.y - this.y, p.x - this.x);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.applyTransform = function(v) {\r\n\t\t\t\tvar xp = this.x * v[0] + this.y * v[2] + v[4];\r\n\t\t\t\tvar yp = this.x * v[1] + this.y * v[3] + v[5];\r\n\t\t\t\tthis.x = xp;\r\n\t\t\t\tthis.y = yp;\r\n\t\t\t}\r\n\t\t}\r\n\t\tsvg.CreatePoint = function(s) {\r\n\t\t\tvar a = svg.ToNumberArray(s);\r\n\t\t\treturn new svg.Point(a[0], a[1]);\r\n\t\t}\r\n\t\tsvg.CreatePath = function(s) {\r\n\t\t\tvar a = svg.ToNumberArray(s);\r\n\t\t\tvar path = [];\r\n\t\t\tfor (var i=0; i<a.length; i+=2) {\r\n\t\t\t\tpath.push(new svg.Point(a[i], a[i+1]));\r\n\t\t\t}\r\n\t\t\treturn path;\r\n\t\t}\r\n\t\t\r\n\t\t// bounding box\r\n\t\tsvg.BoundingBox = function(x1, y1, x2, y2) { // pass in initial points if you want\r\n\t\t\tthis.x1 = Number.NaN;\r\n\t\t\tthis.y1 = Number.NaN;\r\n\t\t\tthis.x2 = Number.NaN;\r\n\t\t\tthis.y2 = Number.NaN;\r\n\t\t\t\r\n\t\t\tthis.x = function() { return this.x1; }\r\n\t\t\tthis.y = function() { return this.y1; }\r\n\t\t\tthis.width = function() { return this.x2 - this.x1; }\r\n\t\t\tthis.height = function() { return this.y2 - this.y1; }\r\n\t\t\t\r\n\t\t\tthis.addPoint = function(x, y) {\t\r\n\t\t\t\tif (x != null) {\r\n\t\t\t\t\tif (isNaN(this.x1) || isNaN(this.x2)) {\r\n\t\t\t\t\t\tthis.x1 = x;\r\n\t\t\t\t\t\tthis.x2 = x;\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (x < this.x1) this.x1 = x;\r\n\t\t\t\t\tif (x > this.x2) this.x2 = x;\r\n\t\t\t\t}\r\n\t\t\t\r\n\t\t\t\tif (y != null) {\r\n\t\t\t\t\tif (isNaN(this.y1) || isNaN(this.y2)) {\r\n\t\t\t\t\t\tthis.y1 = y;\r\n\t\t\t\t\t\tthis.y2 = y;\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (y < this.y1) this.y1 = y;\r\n\t\t\t\t\tif (y > this.y2) this.y2 = y;\r\n\t\t\t\t}\r\n\t\t\t}\t\t\t\r\n\t\t\tthis.addX = function(x) { this.addPoint(x, null); }\r\n\t\t\tthis.addY = function(y) { this.addPoint(null, y); }\r\n\t\t\t\r\n\t\t\tthis.addBoundingBox = function(bb) {\r\n\t\t\t\tthis.addPoint(bb.x1, bb.y1);\r\n\t\t\t\tthis.addPoint(bb.x2, bb.y2);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.addQuadraticCurve = function(p0x, p0y, p1x, p1y, p2x, p2y) {\r\n\t\t\t\tvar cp1x = p0x + 2/3 * (p1x - p0x); // CP1 = QP0 + 2/3 *(QP1-QP0)\r\n\t\t\t\tvar cp1y = p0y + 2/3 * (p1y - p0y); // CP1 = QP0 + 2/3 *(QP1-QP0)\r\n\t\t\t\tvar cp2x = cp1x + 1/3 * (p2x - p0x); // CP2 = CP1 + 1/3 *(QP2-QP0)\r\n\t\t\t\tvar cp2y = cp1y + 1/3 * (p2y - p0y); // CP2 = CP1 + 1/3 *(QP2-QP0)\r\n\t\t\t\tthis.addBezierCurve(p0x, p0y, cp1x, cp2x, cp1y,\tcp2y, p2x, p2y);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.addBezierCurve = function(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y) {\r\n\t\t\t\t// from http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html\r\n\t\t\t\tvar p0 = [p0x, p0y], p1 = [p1x, p1y], p2 = [p2x, p2y], p3 = [p3x, p3y];\r\n\t\t\t\tthis.addPoint(p0[0], p0[1]);\r\n\t\t\t\tthis.addPoint(p3[0], p3[1]);\r\n\t\t\t\t\r\n\t\t\t\tfor (i=0; i<=1; i++) {\r\n\t\t\t\t\tvar f = function(t) { \r\n\t\t\t\t\t\treturn Math.pow(1-t, 3) * p0[i]\r\n\t\t\t\t\t\t+ 3 * Math.pow(1-t, 2) * t * p1[i]\r\n\t\t\t\t\t\t+ 3 * (1-t) * Math.pow(t, 2) * p2[i]\r\n\t\t\t\t\t\t+ Math.pow(t, 3) * p3[i];\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tvar b = 6 * p0[i] - 12 * p1[i] + 6 * p2[i];\r\n\t\t\t\t\tvar a = -3 * p0[i] + 9 * p1[i] - 9 * p2[i] + 3 * p3[i];\r\n\t\t\t\t\tvar c = 3 * p1[i] - 3 * p0[i];\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (a == 0) {\r\n\t\t\t\t\t\tif (b == 0) continue;\r\n\t\t\t\t\t\tvar t = -c / b;\r\n\t\t\t\t\t\tif (0 < t && t < 1) {\r\n\t\t\t\t\t\t\tif (i == 0) this.addX(f(t));\r\n\t\t\t\t\t\t\tif (i == 1) this.addY(f(t));\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tcontinue;\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tvar b2ac = Math.pow(b, 2) - 4 * c * a;\r\n\t\t\t\t\tif (b2ac < 0) continue;\r\n\t\t\t\t\tvar t1 = (-b + Math.sqrt(b2ac)) / (2 * a);\r\n\t\t\t\t\tif (0 < t1 && t1 < 1) {\r\n\t\t\t\t\t\tif (i == 0) this.addX(f(t1));\r\n\t\t\t\t\t\tif (i == 1) this.addY(f(t1));\r\n\t\t\t\t\t}\r\n\t\t\t\t\tvar t2 = (-b - Math.sqrt(b2ac)) / (2 * a);\r\n\t\t\t\t\tif (0 < t2 && t2 < 1) {\r\n\t\t\t\t\t\tif (i == 0) this.addX(f(t2));\r\n\t\t\t\t\t\tif (i == 1) this.addY(f(t2));\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.isPointInBox = function(x, y) {\r\n\t\t\t\treturn (this.x1 <= x && x <= this.x2 && this.y1 <= y && y <= this.y2);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.addPoint(x1, y1);\r\n\t\t\tthis.addPoint(x2, y2);\r\n\t\t}\r\n\t\t\r\n\t\t// transforms\r\n\t\tsvg.Transform = function(v) {\t\r\n\t\t\tvar that = this;\r\n\t\t\tthis.Type = {}\r\n\t\t\r\n\t\t\t// translate\r\n\t\t\tthis.Type.translate = function(s) {\r\n\t\t\t\tthis.p = svg.CreatePoint(s);\t\t\t\r\n\t\t\t\tthis.apply = function(ctx) {\r\n\t\t\t\t\tctx.translate(this.p.x || 0.0, this.p.y || 0.0);\r\n\t\t\t\t}\r\n\t\t\t\tthis.applyToPoint = function(p) {\r\n\t\t\t\t\tp.applyTransform([1, 0, 0, 1, this.p.x || 0.0, this.p.y || 0.0]);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// rotate\r\n\t\t\tthis.Type.rotate = function(s) {\r\n\t\t\t\tvar a = svg.ToNumberArray(s);\r\n\t\t\t\tthis.angle = new svg.Property('angle', a[0]);\r\n\t\t\t\tthis.cx = a[1] || 0;\r\n\t\t\t\tthis.cy = a[2] || 0;\r\n\t\t\t\tthis.apply = function(ctx) {\r\n\t\t\t\t\tctx.translate(this.cx, this.cy);\r\n\t\t\t\t\tctx.rotate(this.angle.Angle.toRadians());\r\n\t\t\t\t\tctx.translate(-this.cx, -this.cy);\r\n\t\t\t\t}\r\n\t\t\t\tthis.applyToPoint = function(p) {\r\n\t\t\t\t\tvar a = this.angle.Angle.toRadians();\r\n\t\t\t\t\tp.applyTransform([1, 0, 0, 1, this.p.x || 0.0, this.p.y || 0.0]);\r\n\t\t\t\t\tp.applyTransform([Math.cos(a), Math.sin(a), -Math.sin(a), Math.cos(a), 0, 0]);\r\n\t\t\t\t\tp.applyTransform([1, 0, 0, 1, -this.p.x || 0.0, -this.p.y || 0.0]);\r\n\t\t\t\t}\t\t\t\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.Type.scale = function(s) {\r\n\t\t\t\tthis.p = svg.CreatePoint(s);\r\n\t\t\t\tthis.apply = function(ctx) {\r\n\t\t\t\t\tctx.scale(this.p.x || 1.0, this.p.y || this.p.x || 1.0);\r\n\t\t\t\t}\r\n\t\t\t\tthis.applyToPoint = function(p) {\r\n\t\t\t\t\tp.applyTransform([this.p.x || 0.0, 0, 0, this.p.y || 0.0, 0, 0]);\r\n\t\t\t\t}\t\t\t\t\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.Type.matrix = function(s) {\r\n\t\t\t\tthis.m = svg.ToNumberArray(s);\r\n\t\t\t\tthis.apply = function(ctx) {\r\n\t\t\t\t\tctx.transform(this.m[0], this.m[1], this.m[2], this.m[3], this.m[4], this.m[5]);\r\n\t\t\t\t}\r\n\t\t\t\tthis.applyToPoint = function(p) {\r\n\t\t\t\t\tp.applyTransform(this.m);\r\n\t\t\t\t}\t\t\t\t\t\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.Type.SkewBase = function(s) {\r\n\t\t\t\tthis.base = that.Type.matrix;\r\n\t\t\t\tthis.base(s);\r\n\t\t\t\tthis.angle = new svg.Property('angle', s);\r\n\t\t\t}\r\n\t\t\tthis.Type.SkewBase.prototype = new this.Type.matrix;\r\n\t\t\t\r\n\t\t\tthis.Type.skewX = function(s) {\r\n\t\t\t\tthis.base = that.Type.SkewBase;\r\n\t\t\t\tthis.base(s);\r\n\t\t\t\tthis.m = [1, 0, Math.tan(this.angle.Angle.toRadians()), 1, 0, 0];\r\n\t\t\t}\r\n\t\t\tthis.Type.skewX.prototype = new this.Type.SkewBase;\r\n\t\t\t\r\n\t\t\tthis.Type.skewY = function(s) {\r\n\t\t\t\tthis.base = that.Type.SkewBase;\r\n\t\t\t\tthis.base(s);\r\n\t\t\t\tthis.m = [1, Math.tan(this.angle.Angle.toRadians()), 0, 1, 0, 0];\r\n\t\t\t}\r\n\t\t\tthis.Type.skewY.prototype = new this.Type.SkewBase;\r\n\t\t\r\n\t\t\tthis.transforms = [];\r\n\t\t\t\r\n\t\t\tthis.apply = function(ctx) {\r\n\t\t\t\tfor (var i=0; i<this.transforms.length; i++) {\r\n\t\t\t\t\tthis.transforms[i].apply(ctx);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.applyToPoint = function(p) {\r\n\t\t\t\tfor (var i=0; i<this.transforms.length; i++) {\r\n\t\t\t\t\tthis.transforms[i].applyToPoint(p);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tvar data = svg.trim(svg.compressSpaces(v)).split(/\\s(?=[a-z])/);\r\n\t\t\tfor (var i=0; i<data.length; i++) {\r\n\t\t\t\tvar type = data[i].split('(')[0];\r\n\t\t\t\tvar s = data[i].split('(')[1].replace(')','');\r\n\t\t\t\tvar transform = new this.Type[type](s);\r\n\t\t\t\tthis.transforms.push(transform);\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\t// aspect ratio\r\n\t\tsvg.AspectRatio = function(ctx, aspectRatio, width, desiredWidth, height, desiredHeight, minX, minY, refX, refY) {\r\n\t\t\t// aspect ratio - http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute\r\n\t\t\taspectRatio = svg.compressSpaces(aspectRatio);\r\n\t\t\taspectRatio = aspectRatio.replace(/^defer\\s/,''); // ignore defer\r\n\t\t\tvar align = aspectRatio.split(' ')[0] || 'xMidYMid';\r\n\t\t\tvar meetOrSlice = aspectRatio.split(' ')[1] || 'meet';\t\t\t\t\t\r\n\t\r\n\t\t\t// calculate scale\r\n\t\t\tvar scaleX = width / desiredWidth;\r\n\t\t\tvar scaleY = height / desiredHeight;\r\n\t\t\tvar scaleMin = Math.min(scaleX, scaleY);\r\n\t\t\tvar scaleMax = Math.max(scaleX, scaleY);\r\n\t\t\tif (meetOrSlice == 'meet') { desiredWidth *= scaleMin; desiredHeight *= scaleMin; }\r\n\t\t\tif (meetOrSlice == 'slice') { desiredWidth *= scaleMax; desiredHeight *= scaleMax; }\t\r\n\t\t\t\r\n\t\t\trefX = new svg.Property('refX', refX);\r\n\t\t\trefY = new svg.Property('refY', refY);\r\n\t\t\tif (refX.hasValue() && refY.hasValue()) {\t\t\t\t\r\n\t\t\t\tctx.translate(-scaleMin * refX.Length.toPixels('x'), -scaleMin * refY.Length.toPixels('y'));\r\n\t\t\t} \r\n\t\t\telse {\t\t\t\t\t\r\n\t\t\t\t// align\r\n\t\t\t\tif (align.match(/^xMid/) && ((meetOrSlice == 'meet' && scaleMin == scaleY) || (meetOrSlice == 'slice' && scaleMax == scaleY))) ctx.translate(width / 2.0 - desiredWidth / 2.0, 0); \r\n\t\t\t\tif (align.match(/YMid$/) && ((meetOrSlice == 'meet' && scaleMin == scaleX) || (meetOrSlice == 'slice' && scaleMax == scaleX))) ctx.translate(0, height / 2.0 - desiredHeight / 2.0); \r\n\t\t\t\tif (align.match(/^xMax/) && ((meetOrSlice == 'meet' && scaleMin == scaleY) || (meetOrSlice == 'slice' && scaleMax == scaleY))) ctx.translate(width - desiredWidth, 0); \r\n\t\t\t\tif (align.match(/YMax$/) && ((meetOrSlice == 'meet' && scaleMin == scaleX) || (meetOrSlice == 'slice' && scaleMax == scaleX))) ctx.translate(0, height - desiredHeight); \r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// scale\r\n\t\t\tif (align == 'none') ctx.scale(scaleX, scaleY);\r\n\t\t\telse if (meetOrSlice == 'meet') ctx.scale(scaleMin, scaleMin); \r\n\t\t\telse if (meetOrSlice == 'slice') ctx.scale(scaleMax, scaleMax); \t\r\n\t\t\t\r\n\t\t\t// translate\r\n\t\t\tctx.translate(minX == null ? 0 : -minX, minY == null ? 0 : -minY);\t\t\t\r\n\t\t}\r\n\t\t\r\n\t\t// elements\r\n\t\tsvg.Element = {}\r\n\t\t\r\n\t\tsvg.Element.ElementBase = function(node) {\t\r\n\t\t\tthis.attributes = {};\r\n\t\t\tthis.styles = {};\r\n\t\t\tthis.children = [];\r\n\t\t\t\r\n\t\t\t// get or create attribute\r\n\t\t\tthis.attribute = function(name, createIfNotExists) {\r\n\t\t\t\tvar a = this.attributes[name];\r\n\t\t\t\tif (a != null) return a;\r\n\t\t\t\t\t\t\t\r\n\t\t\t\ta = new svg.Property(name, '');\r\n\t\t\t\tif (createIfNotExists == true) this.attributes[name] = a;\r\n\t\t\t\treturn a;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// get or create style, crawls up node tree\r\n\t\t\tthis.style = function(name, createIfNotExists) {\r\n\t\t\t\tvar s = this.styles[name];\r\n\t\t\t\tif (s != null) return s;\r\n\t\t\t\t\r\n\t\t\t\tvar a = this.attribute(name);\r\n\t\t\t\tif (a != null && a.hasValue()) {\r\n\t\t\t\t\treturn a;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tvar p = this.parent;\r\n\t\t\t\tif (p != null) {\r\n\t\t\t\t\tvar ps = p.style(name);\r\n\t\t\t\t\tif (ps != null && ps.hasValue()) {\r\n\t\t\t\t\t\treturn ps;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\ts = new svg.Property(name, '');\r\n\t\t\t\tif (createIfNotExists == true) this.styles[name] = s;\r\n\t\t\t\treturn s;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// base render\r\n\t\t\tthis.render = function(ctx) {\r\n\t\t\t\t// don't render display=none\r\n\t\t\t\tif (this.style('display').value == 'none') return;\r\n\t\t\t\t\r\n\t\t\t\t// don't render visibility=hidden\r\n\t\t\t\tif (this.attribute('visibility').value == 'hidden') return;\r\n\t\t\t\r\n\t\t\t\tctx.save();\r\n\t\t\t\t\tthis.setContext(ctx);\r\n\t\t\t\t\t\t// mask\r\n\t\t\t\t\t\tif (this.attribute('mask').hasValue()) {\r\n\t\t\t\t\t\t\tvar mask = this.attribute('mask').Definition.getDefinition();\r\n\t\t\t\t\t\t\tif (mask != null) mask.apply(ctx, this);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\telse if (this.style('filter').hasValue()) {\r\n\t\t\t\t\t\t\tvar filter = this.style('filter').Definition.getDefinition();\r\n\t\t\t\t\t\t\tif (filter != null) filter.apply(ctx, this);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\telse this.renderChildren(ctx);\t\t\t\t\r\n\t\t\t\t\tthis.clearContext(ctx);\r\n\t\t\t\tctx.restore();\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// base set context\r\n\t\t\tthis.setContext = function(ctx) {\r\n\t\t\t\t// OVERRIDE ME!\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// base clear context\r\n\t\t\tthis.clearContext = function(ctx) {\r\n\t\t\t\t// OVERRIDE ME!\r\n\t\t\t}\t\t\t\r\n\t\t\t\r\n\t\t\t// base render children\r\n\t\t\tthis.renderChildren = function(ctx) {\r\n\t\t\t\tfor (var i=0; i<this.children.length; i++) {\r\n\t\t\t\t\tthis.children[i].render(ctx);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.addChild = function(childNode, create) {\r\n\t\t\t\tvar child = childNode;\r\n\t\t\t\tif (create) child = svg.CreateElement(childNode);\r\n\t\t\t\tchild.parent = this;\r\n\t\t\t\tthis.children.push(child);\t\t\t\r\n\t\t\t}\r\n\t\t\t\t\r\n\t\t\tif (node != null && node.nodeType == 1) { //ELEMENT_NODE\r\n\t\t\t\t// add children\r\n\t\t\t\tfor (var i=0; i<node.childNodes.length; i++) {\r\n\t\t\t\t\tvar childNode = node.childNodes[i];\r\n\t\t\t\t\tif (childNode.nodeType == 1) this.addChild(childNode, true); //ELEMENT_NODE\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// add attributes\r\n\t\t\t\tfor (var i=0; i<node.attributes.length; i++) {\r\n\t\t\t\t\tvar attribute = node.attributes[i];\r\n\t\t\t\t\tthis.attributes[attribute.nodeName] = new svg.Property(attribute.nodeName, attribute.nodeValue);\r\n\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t// add tag styles\r\n\t\t\t\tvar styles = svg.Styles[node.nodeName];\r\n\t\t\t\tif (styles != null) {\r\n\t\t\t\t\tfor (var name in styles) {\r\n\t\t\t\t\t\tthis.styles[name] = styles[name];\r\n\t\t\t\t\t}\r\n\t\t\t\t}\t\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t// add class styles\r\n\t\t\t\tif (this.attribute('class').hasValue()) {\r\n\t\t\t\t\tvar classes = svg.compressSpaces(this.attribute('class').value).split(' ');\r\n\t\t\t\t\tfor (var j=0; j<classes.length; j++) {\r\n\t\t\t\t\t\tstyles = svg.Styles['.'+classes[j]];\r\n\t\t\t\t\t\tif (styles != null) {\r\n\t\t\t\t\t\t\tfor (var name in styles) {\r\n\t\t\t\t\t\t\t\tthis.styles[name] = styles[name];\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tstyles = svg.Styles[node.nodeName+'.'+classes[j]];\r\n\t\t\t\t\t\tif (styles != null) {\r\n\t\t\t\t\t\t\tfor (var name in styles) {\r\n\t\t\t\t\t\t\t\tthis.styles[name] = styles[name];\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// add inline styles\r\n\t\t\t\tif (this.attribute('style').hasValue()) {\r\n\t\t\t\t\tvar styles = this.attribute('style').value.split(';');\r\n\t\t\t\t\tfor (var i=0; i<styles.length; i++) {\r\n\t\t\t\t\t\tif (svg.trim(styles[i]) != '') {\r\n\t\t\t\t\t\t\tvar style = styles[i].split(':');\r\n\t\t\t\t\t\t\tvar name = svg.trim(style[0]);\r\n\t\t\t\t\t\t\tvar value = svg.trim(style[1]);\r\n\t\t\t\t\t\t\tthis.styles[name] = new svg.Property(name, value);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\t\r\n\r\n\t\t\t\t// add id\r\n\t\t\t\tif (this.attribute('id').hasValue()) {\r\n\t\t\t\t\tif (svg.Definitions[this.attribute('id').value] == null) {\r\n\t\t\t\t\t\tsvg.Definitions[this.attribute('id').value] = this;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tsvg.Element.RenderedElementBase = function(node) {\r\n\t\t\tthis.base = svg.Element.ElementBase;\r\n\t\t\tthis.base(node);\r\n\t\t\t\r\n\t\t\tthis.setContext = function(ctx) {\r\n\t\t\t\t// fill\r\n\t\t\t\tif (this.style('fill').Definition.isUrl()) {\r\n\t\t\t\t\tvar fs = this.style('fill').Definition.getFillStyle(this);\r\n\t\t\t\t\tif (fs != null) ctx.fillStyle = fs;\r\n\t\t\t\t}\r\n\t\t\t\telse if (this.style('fill').hasValue()) {\r\n\t\t\t\t\tvar fillStyle = this.style('fill');\r\n\t\t\t\t\tif (this.style('fill-opacity').hasValue()) fillStyle = fillStyle.Color.addOpacity(this.style('fill-opacity').value);\r\n\t\t\t\t\tctx.fillStyle = (fillStyle.value == 'none' ? 'rgba(0,0,0,0)' : fillStyle.value);\r\n\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t// stroke\r\n\t\t\t\tif (this.style('stroke').Definition.isUrl()) {\r\n\t\t\t\t\tvar fs = this.style('stroke').Definition.getFillStyle(this);\r\n\t\t\t\t\tif (fs != null) ctx.strokeStyle = fs;\r\n\t\t\t\t}\r\n\t\t\t\telse if (this.style('stroke').hasValue()) {\r\n\t\t\t\t\tvar strokeStyle = this.style('stroke');\r\n\t\t\t\t\tif (this.style('stroke-opacity').hasValue()) strokeStyle = strokeStyle.Color.addOpacity(this.style('stroke-opacity').value);\r\n\t\t\t\t\tctx.strokeStyle = (strokeStyle.value == 'none' ? 'rgba(0,0,0,0)' : strokeStyle.value);\r\n\t\t\t\t}\r\n\t\t\t\tif (this.style('stroke-width').hasValue()) ctx.lineWidth = this.style('stroke-width').Length.toPixels();\r\n\t\t\t\tif (this.style('stroke-linecap').hasValue()) ctx.lineCap = this.style('stroke-linecap').value;\r\n\t\t\t\tif (this.style('stroke-linejoin').hasValue()) ctx.lineJoin = this.style('stroke-linejoin').value;\r\n\t\t\t\tif (this.style('stroke-miterlimit').hasValue()) ctx.miterLimit = this.style('stroke-miterlimit').value;\r\n\r\n\t\t\t\t// font\r\n\t\t\t\tif (typeof(ctx.font) != 'undefined') {\r\n\t\t\t\t\tctx.font = svg.Font.CreateFont( \r\n\t\t\t\t\t\tthis.style('font-style').value, \r\n\t\t\t\t\t\tthis.style('font-variant').value, \r\n\t\t\t\t\t\tthis.style('font-weight').value, \r\n\t\t\t\t\t\tthis.style('font-size').hasValue() ? this.style('font-size').Length.toPixels() + 'px' : '', \r\n\t\t\t\t\t\tthis.style('font-family').value).toString();\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// transform\r\n\t\t\t\tif (this.attribute('transform').hasValue()) { \r\n\t\t\t\t\tvar transform = new svg.Transform(this.attribute('transform').value);\r\n\t\t\t\t\ttransform.apply(ctx);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// clip\r\n\t\t\t\tif (this.attribute('clip-path').hasValue()) {\r\n\t\t\t\t\tvar clip = this.attribute('clip-path').Definition.getDefinition();\r\n\t\t\t\t\tif (clip != null) clip.apply(ctx);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// opacity\r\n\t\t\t\tif (this.style('opacity').hasValue()) {\r\n\t\t\t\t\tctx.globalAlpha = this.style('opacity').numValue();\r\n\t\t\t\t}\r\n\t\t\t}\t\t\r\n\t\t}\r\n\t\tsvg.Element.RenderedElementBase.prototype = new svg.Element.ElementBase;\r\n\t\t\r\n\t\tsvg.Element.PathElementBase = function(node) {\r\n\t\t\tthis.base = svg.Element.RenderedElementBase;\r\n\t\t\tthis.base(node);\r\n\t\t\t\r\n\t\t\tthis.path = function(ctx) {\r\n\t\t\t\tif (ctx != null) ctx.beginPath();\r\n\t\t\t\treturn new svg.BoundingBox();\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.renderChildren = function(ctx) {\r\n\t\t\t\tthis.path(ctx);\r\n\t\t\t\tsvg.Mouse.checkPath(this, ctx);\r\n\t\t\t\tif (ctx.fillStyle != '') ctx.fill();\r\n\t\t\t\tif (ctx.strokeStyle != '') ctx.stroke();\r\n\t\t\t\t\r\n\t\t\t\tvar markers = this.getMarkers();\r\n\t\t\t\tif (markers != null) {\r\n\t\t\t\t\tif (this.style('marker-start').Definition.isUrl()) {\r\n\t\t\t\t\t\tvar marker = this.style('marker-start').Definition.getDefinition();\r\n\t\t\t\t\t\tmarker.render(ctx, markers[0][0], markers[0][1]);\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (this.style('marker-mid').Definition.isUrl()) {\r\n\t\t\t\t\t\tvar marker = this.style('marker-mid').Definition.getDefinition();\r\n\t\t\t\t\t\tfor (var i=1;i<markers.length-1;i++) {\r\n\t\t\t\t\t\t\tmarker.render(ctx, markers[i][0], markers[i][1]);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (this.style('marker-end').Definition.isUrl()) {\r\n\t\t\t\t\t\tvar marker = this.style('marker-end').Definition.getDefinition();\r\n\t\t\t\t\t\tmarker.render(ctx, markers[markers.length-1][0], markers[markers.length-1][1]);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\t\t\t\t\t\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.getBoundingBox = function() {\r\n\t\t\t\treturn this.path();\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.getMarkers = function() {\r\n\t\t\t\treturn null;\r\n\t\t\t}\r\n\t\t}\r\n\t\tsvg.Element.PathElementBase.prototype = new svg.Element.RenderedElementBase;\r\n\t\t\r\n\t\t// svg element\r\n\t\tsvg.Element.svg = function(node) {\r\n\t\t\tthis.base = svg.Element.RenderedElementBase;\r\n\t\t\tthis.base(node);\r\n\t\t\t\r\n\t\t\tthis.baseClearContext = this.clearContext;\r\n\t\t\tthis.clearContext = function(ctx) {\r\n\t\t\t\tthis.baseClearContext(ctx);\r\n\t\t\t\tsvg.ViewPort.RemoveCurrent();\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.baseSetContext = this.setContext;\r\n\t\t\tthis.setContext = function(ctx) {\r\n\t\t\t\t// initial values\r\n\t\t\t\tctx.strokeStyle = 'rgba(0,0,0,0)';\r\n\t\t\t\tctx.lineCap = 'butt';\r\n\t\t\t\tctx.lineJoin = 'miter';\r\n\t\t\t\tctx.miterLimit = 4;\t\t\t\r\n\t\t\t\r\n\t\t\t\tthis.baseSetContext(ctx);\r\n\t\t\t\t\r\n\t\t\t\t// create new view port\r\n\t\t\t\tif (this.attribute('x').hasValue() && this.attribute('y').hasValue()) {\r\n\t\t\t\t\tctx.translate(this.attribute('x').Length.toPixels('x'), this.attribute('y').Length.toPixels('y'));\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tvar width = svg.ViewPort.width();\r\n\t\t\t\tvar height = svg.ViewPort.height();\r\n\t\t\t\tif (typeof(this.root) == 'undefined' && this.attribute('width').hasValue() && this.attribute('height').hasValue()) {\r\n\t\t\t\t\twidth = this.attribute('width').Length.toPixels('x');\r\n\t\t\t\t\theight = this.attribute('height').Length.toPixels('y');\r\n\t\t\t\t\t\r\n\t\t\t\t\tvar x = 0;\r\n\t\t\t\t\tvar y = 0;\r\n\t\t\t\t\tif (this.attribute('refX').hasValue() && this.attribute('refY').hasValue()) {\r\n\t\t\t\t\t\tx = -this.attribute('refX').Length.toPixels('x');\r\n\t\t\t\t\t\ty = -this.attribute('refY').Length.toPixels('y');\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tctx.beginPath();\r\n\t\t\t\t\tctx.moveTo(x, y);\r\n\t\t\t\t\tctx.lineTo(width, y);\r\n\t\t\t\t\tctx.lineTo(width, height);\r\n\t\t\t\t\tctx.lineTo(x, height);\r\n\t\t\t\t\tctx.closePath();\r\n\t\t\t\t\tctx.clip();\r\n\t\t\t\t}\r\n\t\t\t\tsvg.ViewPort.SetCurrent(width, height);\t\r\n\t\t\t\t\t\t\r\n\t\t\t\t// viewbox\r\n\t\t\t\tif (this.attribute('viewBox').hasValue()) {\t\t\t\t\r\n\t\t\t\t\tvar viewBox = svg.ToNumberArray(this.attribute('viewBox').value);\r\n\t\t\t\t\tvar minX = viewBox[0];\r\n\t\t\t\t\tvar minY = viewBox[1];\r\n\t\t\t\t\twidth = viewBox[2];\r\n\t\t\t\t\theight = viewBox[3];\r\n\t\t\t\t\t\r\n\t\t\t\t\tsvg.AspectRatio(ctx,\r\n\t\t\t\t\t\t\t\t\tthis.attribute('preserveAspectRatio').value, \r\n\t\t\t\t\t\t\t\t\tsvg.ViewPort.width(), \r\n\t\t\t\t\t\t\t\t\twidth,\r\n\t\t\t\t\t\t\t\t\tsvg.ViewPort.height(),\r\n\t\t\t\t\t\t\t\t\theight,\r\n\t\t\t\t\t\t\t\t\tminX,\r\n\t\t\t\t\t\t\t\t\tminY,\r\n\t\t\t\t\t\t\t\t\tthis.attribute('refX').value,\r\n\t\t\t\t\t\t\t\t\tthis.attribute('refY').value);\r\n\t\t\t\t\t\t\t\t\t\t\r\n\t\t\t\t\tsvg.ViewPort.RemoveCurrent();\t\r\n\t\t\t\t\tsvg.ViewPort.SetCurrent(viewBox[2], viewBox[3]);\t\t\t\t\t\t\r\n\t\t\t\t}\t\t\t\t\r\n\t\t\t}\r\n\t\t}\r\n\t\tsvg.Element.svg.prototype = new svg.Element.RenderedElementBase;\r\n\r\n\t\t// rect element\r\n\t\tsvg.Element.rect = function(node) {\r\n\t\t\tthis.base = svg.Element.PathElementBase;\r\n\t\t\tthis.base(node);\r\n\t\t\t\r\n\t\t\tthis.path = function(ctx) {\r\n\t\t\t\tvar x = this.attribute('x').Length.toPixels('x');\r\n\t\t\t\tvar y = this.attribute('y').Length.toPixels('y');\r\n\t\t\t\tvar width = this.attribute('width').Length.toPixels('x');\r\n\t\t\t\tvar height = this.attribute('height').Length.toPixels('y');\r\n\t\t\t\tvar rx = this.attribute('rx').Length.toPixels('x');\r\n\t\t\t\tvar ry = this.attribute('ry').Length.toPixels('y');\r\n\t\t\t\tif (this.attribute('rx').hasValue() && !this.attribute('ry').hasValue()) ry = rx;\r\n\t\t\t\tif (this.attribute('ry').hasValue() && !this.attribute('rx').hasValue()) rx = ry;\r\n\t\t\t\t\r\n\t\t\t\tif (ctx != null) {\r\n\t\t\t\t\tctx.beginPath();\r\n\t\t\t\t\tctx.moveTo(x + rx, y);\r\n\t\t\t\t\tctx.lineTo(x + width - rx, y);\r\n\t\t\t\t\tctx.quadraticCurveTo(x + width, y, x + width, y + ry)\r\n\t\t\t\t\tctx.lineTo(x + width, y + height - ry);\r\n\t\t\t\t\tctx.quadraticCurveTo(x + width, y + height, x + width - rx, y + height)\r\n\t\t\t\t\tctx.lineTo(x + rx, y + height);\r\n\t\t\t\t\tctx.quadraticCurveTo(x, y + height, x, y + height - ry)\r\n\t\t\t\t\tctx.lineTo(x, y + ry);\r\n\t\t\t\t\tctx.quadraticCurveTo(x, y, x + rx, y)\r\n\t\t\t\t\tctx.closePath();\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\treturn new svg.BoundingBox(x, y, x + width, y + height);\r\n\t\t\t}\r\n\t\t}\r\n\t\tsvg.Element.rect.prototype = new svg.Element.PathElementBase;\r\n\t\t\r\n\t\t// circle element\r\n\t\tsvg.Element.circle = function(node) {\r\n\t\t\tthis.base = svg.Element.PathElementBase;\r\n\t\t\tthis.base(node);\r\n\t\t\t\r\n\t\t\tthis.path = function(ctx) {\r\n\t\t\t\tvar cx = this.attribute('cx').Length.toPixels('x');\r\n\t\t\t\tvar cy = this.attribute('cy').Length.toPixels('y');\r\n\t\t\t\tvar r = this.attribute('r').Length.toPixels();\r\n\t\t\t\r\n\t\t\t\tif (ctx != null) {\r\n\t\t\t\t\tctx.beginPath();\r\n\t\t\t\t\tctx.arc(cx, cy, r, 0, Math.PI * 2, true); \r\n\t\t\t\t\tctx.closePath();\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\treturn new svg.BoundingBox(cx - r, cy - r, cx + r, cy + r);\r\n\t\t\t}\r\n\t\t}\r\n\t\tsvg.Element.circle.prototype = new svg.Element.PathElementBase;\t\r\n\r\n\t\t// ellipse element\r\n\t\tsvg.Element.ellipse = function(node) {\r\n\t\t\tthis.base = svg.Element.PathElementBase;\r\n\t\t\tthis.base(node);\r\n\t\t\t\r\n\t\t\tthis.path = function(ctx) {\r\n\t\t\t\tvar KAPPA = 4 * ((Math.sqrt(2) - 1) / 3);\r\n\t\t\t\tvar rx = this.attribute('rx').Length.toPixels('x');\r\n\t\t\t\tvar ry = this.attribute('ry').Length.toPixels('y');\r\n\t\t\t\tvar cx = this.attribute('cx').Length.toPixels('x');\r\n\t\t\t\tvar cy = this.attribute('cy').Length.toPixels('y');\r\n\t\t\t\t\r\n\t\t\t\tif (ctx != null) {\r\n\t\t\t\t\tctx.beginPath();\r\n\t\t\t\t\tctx.moveTo(cx, cy - ry);\r\n\t\t\t\t\tctx.bezierCurveTo(cx + (KAPPA * rx), cy - ry,  cx + rx, cy - (KAPPA * ry), cx + rx, cy);\r\n\t\t\t\t\tctx.bezierCurveTo(cx + rx, cy + (KAPPA * ry), cx + (KAPPA * rx), cy + ry, cx, cy + ry);\r\n\t\t\t\t\tctx.bezierCurveTo(cx - (KAPPA * rx), cy + ry, cx - rx, cy + (KAPPA * ry), cx - rx, cy);\r\n\t\t\t\t\tctx.bezierCurveTo(cx - rx, cy - (KAPPA * ry), cx - (KAPPA * rx), cy - ry, cx, cy - ry);\r\n\t\t\t\t\tctx.closePath();\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\treturn new svg.BoundingBox(cx - rx, cy - ry, cx + rx, cy + ry);\r\n\t\t\t}\r\n\t\t}\r\n\t\tsvg.Element.ellipse.prototype = new svg.Element.PathElementBase;\t\t\t\r\n\t\t\r\n\t\t// line element\r\n\t\tsvg.Element.line = function(node) {\r\n\t\t\tthis.base = svg.Element.PathElementBase;\r\n\t\t\tthis.base(node);\r\n\t\t\t\r\n\t\t\tthis.getPoints = function() {\r\n\t\t\t\treturn [\r\n\t\t\t\t\tnew svg.Point(this.attribute('x1').Length.toPixels('x'), this.attribute('y1').Length.toPixels('y')),\r\n\t\t\t\t\tnew svg.Point(this.attribute('x2').Length.toPixels('x'), this.attribute('y2').Length.toPixels('y'))];\r\n\t\t\t}\r\n\t\t\t\t\t\t\t\t\r\n\t\t\tthis.path = function(ctx) {\r\n\t\t\t\tvar points = this.getPoints();\r\n\t\t\t\t\r\n\t\t\t\tif (ctx != null) {\r\n\t\t\t\t\tctx.beginPath();\r\n\t\t\t\t\tctx.moveTo(points[0].x, points[0].y);\r\n\t\t\t\t\tctx.lineTo(points[1].x, points[1].y);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\treturn new svg.BoundingBox(points[0].x, points[0].y, points[1].x, points[1].y);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.getMarkers = function() {\r\n\t\t\t\tvar points = this.getPoints();\t\r\n\t\t\t\tvar a = points[0].angleTo(points[1]);\r\n\t\t\t\treturn [[points[0], a], [points[1], a]];\r\n\t\t\t}\r\n\t\t}\r\n\t\tsvg.Element.line.prototype = new svg.Element.PathElementBase;\t\t\r\n\t\t\t\t\r\n\t\t// polyline element\r\n\t\tsvg.Element.polyline = function(node) {\r\n\t\t\tthis.base = svg.Element.PathElementBase;\r\n\t\t\tthis.base(node);\r\n\t\t\t\r\n\t\t\tthis.points = svg.CreatePath(this.attribute('points').value);\r\n\t\t\tthis.path = function(ctx) {\r\n\t\t\t\tvar bb = new svg.BoundingBox(this.points[0].x, this.points[0].y);\r\n\t\t\t\tif (ctx != null) {\r\n\t\t\t\t\tctx.beginPath();\r\n\t\t\t\t\tctx.moveTo(this.points[0].x, this.points[0].y);\r\n\t\t\t\t}\r\n\t\t\t\tfor (var i=1; i<this.points.length; i++) {\r\n\t\t\t\t\tbb.addPoint(this.points[i].x, this.points[i].y);\r\n\t\t\t\t\tif (ctx != null) ctx.lineTo(this.points[i].x, this.points[i].y);\r\n\t\t\t\t}\r\n\t\t\t\treturn bb;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.getMarkers = function() {\r\n\t\t\t\tvar markers = [];\r\n\t\t\t\tfor (var i=0; i<this.points.length - 1; i++) {\r\n\t\t\t\t\tmarkers.push([this.points[i], this.points[i].angleTo(this.points[i+1])]);\r\n\t\t\t\t}\r\n\t\t\t\tmarkers.push([this.points[this.points.length-1], markers[markers.length-1][1]]);\r\n\t\t\t\treturn markers;\r\n\t\t\t}\t\t\t\r\n\t\t}\r\n\t\tsvg.Element.polyline.prototype = new svg.Element.PathElementBase;\t\t\t\t\r\n\t\t\t\t\r\n\t\t// polygon element\r\n\t\tsvg.Element.polygon = function(node) {\r\n\t\t\tthis.base = svg.Element.polyline;\r\n\t\t\tthis.base(node);\r\n\t\t\t\r\n\t\t\tthis.basePath = this.path;\r\n\t\t\tthis.path = function(ctx) {\r\n\t\t\t\tvar bb = this.basePath(ctx);\r\n\t\t\t\tif (ctx != null) {\r\n\t\t\t\t\tctx.lineTo(this.points[0].x, this.points[0].y);\r\n\t\t\t\t\tctx.closePath();\r\n\t\t\t\t}\r\n\t\t\t\treturn bb;\r\n\t\t\t}\r\n\t\t}\r\n\t\tsvg.Element.polygon.prototype = new svg.Element.polyline;\r\n\r\n\t\t// path element\r\n\t\tsvg.Element.path = function(node) {\r\n\t\t\tthis.base = svg.Element.PathElementBase;\r\n\t\t\tthis.base(node);\r\n\t\t\t\t\t\r\n\t\t\tvar d = this.attribute('d').value;\r\n\t\t\t// TODO: convert to real lexer based on http://www.w3.org/TR/SVG11/paths.html#PathDataBNF\r\n\t\t\td = d.replace(/,/gm,' '); // get rid of all commas\r\n\t\t\td = d.replace(/([MmZzLlHhVvCcSsQqTtAa])([MmZzLlHhVvCcSsQqTtAa])/gm,'$1 $2'); // separate commands from commands\r\n\t\t\td = d.replace(/([MmZzLlHhVvCcSsQqTtAa])([MmZzLlHhVvCcSsQqTtAa])/gm,'$1 $2'); // separate commands from commands\r\n\t\t\td = d.replace(/([MmZzLlHhVvCcSsQqTtAa])([^\\s])/gm,'$1 $2'); // separate commands from points\r\n\t\t\td = d.replace(/([^\\s])([MmZzLlHhVvCcSsQqTtAa])/gm,'$1 $2'); // separate commands from points\r\n\t\t\td = d.replace(/([0-9])([+\\-])/gm,'$1 $2'); // separate digits when no comma\r\n\t\t\td = d.replace(/(\\.[0-9]*)(\\.)/gm,'$1 $2'); // separate digits when no comma\r\n\t\t\td = d.replace(/([Aa](\\s+[0-9]+){3})\\s+([01])\\s*([01])/gm,'$1 $3 $4 '); // shorthand elliptical arc path syntax\r\n\t\t\td = svg.compressSpaces(d); // compress multiple spaces\r\n\t\t\td = svg.trim(d);\r\n\t\t\tthis.PathParser = new (function(d) {\r\n\t\t\t\tthis.tokens = d.split(' ');\r\n\t\t\t\t\r\n\t\t\t\tthis.reset = function() {\r\n\t\t\t\t\tthis.i = -1;\r\n\t\t\t\t\tthis.command = '';\r\n\t\t\t\t\tthis.previousCommand = '';\r\n\t\t\t\t\tthis.start = new svg.Point(0, 0);\r\n\t\t\t\t\tthis.control = new svg.Point(0, 0);\r\n\t\t\t\t\tthis.current = new svg.Point(0, 0);\r\n\t\t\t\t\tthis.points = [];\r\n\t\t\t\t\tthis.angles = [];\r\n\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\r\n\t\t\t\tthis.isEnd = function() {\r\n\t\t\t\t\treturn this.i >= this.tokens.length - 1;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tthis.isCommandOrEnd = function() {\r\n\t\t\t\t\tif (this.isEnd()) return true;\r\n\t\t\t\t\treturn this.tokens[this.i + 1].match(/^[A-Za-z]$/) != null;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tthis.isRelativeCommand = function() {\r\n\t\t\t\t\treturn this.command == this.command.toLowerCase();\r\n\t\t\t\t}\r\n\t\t\t\t\t\t\t\r\n\t\t\t\tthis.getToken = function() {\r\n\t\t\t\t\tthis.i = this.i + 1;\r\n\t\t\t\t\treturn this.tokens[this.i];\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tthis.getScalar = function() {\r\n\t\t\t\t\treturn parseFloat(this.getToken());\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tthis.nextCommand = function() {\r\n\t\t\t\t\tthis.previousCommand = this.command;\r\n\t\t\t\t\tthis.command = this.getToken();\r\n\t\t\t\t}\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\tthis.getPoint = function() {\r\n\t\t\t\t\tvar p = new svg.Point(this.getScalar(), this.getScalar());\r\n\t\t\t\t\treturn this.makeAbsolute(p);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tthis.getAsControlPoint = function() {\r\n\t\t\t\t\tvar p = this.getPoint();\r\n\t\t\t\t\tthis.control = p;\r\n\t\t\t\t\treturn p;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tthis.getAsCurrentPoint = function() {\r\n\t\t\t\t\tvar p = this.getPoint();\r\n\t\t\t\t\tthis.current = p;\r\n\t\t\t\t\treturn p;\t\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tthis.getReflectedControlPoint = function() {\r\n\t\t\t\t\tif (this.previousCommand.toLowerCase() != 'c' && this.previousCommand.toLowerCase() != 's') {\r\n\t\t\t\t\t\treturn this.current;\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\t// reflect point\r\n\t\t\t\t\tvar p = new svg.Point(2 * this.current.x - this.control.x, 2 * this.current.y - this.control.y);\t\t\t\t\t\r\n\t\t\t\t\treturn p;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tthis.makeAbsolute = function(p) {\r\n\t\t\t\t\tif (this.isRelativeCommand()) {\r\n\t\t\t\t\t\tp.x = this.current.x + p.x;\r\n\t\t\t\t\t\tp.y = this.current.y + p.y;\r\n\t\t\t\t\t}\r\n\t\t\t\t\treturn p;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tthis.addMarker = function(p, from, priorTo) {\r\n\t\t\t\t\t// if the last angle isn't filled in because we didn't have this point yet ...\r\n\t\t\t\t\tif (priorTo != null && this.angles.length > 0 && this.angles[this.angles.length-1] == null) {\r\n\t\t\t\t\t\tthis.angles[this.angles.length-1] = this.points[this.points.length-1].angleTo(priorTo);\r\n\t\t\t\t\t}\r\n\t\t\t\t\tthis.addMarkerAngle(p, from == null ? null : from.angleTo(p));\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tthis.addMarkerAngle = function(p, a) {\r\n\t\t\t\t\tthis.points.push(p);\r\n\t\t\t\t\tthis.angles.push(a);\r\n\t\t\t\t}\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\tthis.getMarkerPoints = function() { return this.points; }\r\n\t\t\t\tthis.getMarkerAngles = function() {\r\n\t\t\t\t\tfor (var i=0; i<this.angles.length; i++) {\r\n\t\t\t\t\t\tif (this.angles[i] == null) {\r\n\t\t\t\t\t\t\tfor (var j=i+1; j<this.angles.length; j++) {\r\n\t\t\t\t\t\t\t\tif (this.angles[j] != null) {\r\n\t\t\t\t\t\t\t\t\tthis.angles[i] = this.angles[j];\r\n\t\t\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\treturn this.angles;\r\n\t\t\t\t}\r\n\t\t\t})(d);\r\n\r\n\t\t\tthis.path = function(ctx) {\r\n\t\t\t\tvar pp = this.PathParser;\r\n\t\t\t\tpp.reset();\r\n\r\n\t\t\t\tvar bb = new svg.BoundingBox();\r\n\t\t\t\tif (ctx != null) ctx.beginPath();\r\n\t\t\t\twhile (!pp.isEnd()) {\r\n\t\t\t\t\tpp.nextCommand();\r\n\t\t\t\t\tswitch (pp.command.toUpperCase()) {\r\n\t\t\t\t\tcase 'M':\r\n\t\t\t\t\t\tvar p = pp.getAsCurrentPoint();\r\n\t\t\t\t\t\tpp.addMarker(p);\r\n\t\t\t\t\t\tbb.addPoint(p.x, p.y);\r\n\t\t\t\t\t\tif (ctx != null) ctx.moveTo(p.x, p.y);\r\n\t\t\t\t\t\tpp.start = pp.current;\r\n\t\t\t\t\t\twhile (!pp.isCommandOrEnd()) {\r\n\t\t\t\t\t\t\tvar p = pp.getAsCurrentPoint();\r\n\t\t\t\t\t\t\tpp.addMarker(p, pp.start);\r\n\t\t\t\t\t\t\tbb.addPoint(p.x, p.y);\r\n\t\t\t\t\t\t\tif (ctx != null) ctx.lineTo(p.x, p.y);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase 'L':\r\n\t\t\t\t\t\twhile (!pp.isCommandOrEnd()) {\r\n\t\t\t\t\t\t\tvar c = pp.current;\r\n\t\t\t\t\t\t\tvar p = pp.getAsCurrentPoint();\r\n\t\t\t\t\t\t\tpp.addMarker(p, c);\r\n\t\t\t\t\t\t\tbb.addPoint(p.x, p.y);\r\n\t\t\t\t\t\t\tif (ctx != null) ctx.lineTo(p.x, p.y);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase 'H':\r\n\t\t\t\t\t\twhile (!pp.isCommandOrEnd()) {\r\n\t\t\t\t\t\t\tvar newP = new svg.Point((pp.isRelativeCommand() ? pp.current.x : 0) + pp.getScalar(), pp.current.y);\r\n\t\t\t\t\t\t\tpp.addMarker(newP, pp.current);\r\n\t\t\t\t\t\t\tpp.current = newP;\r\n\t\t\t\t\t\t\tbb.addPoint(pp.current.x, pp.current.y);\r\n\t\t\t\t\t\t\tif (ctx != null) ctx.lineTo(pp.current.x, pp.current.y);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase 'V':\r\n\t\t\t\t\t\twhile (!pp.isCommandOrEnd()) {\r\n\t\t\t\t\t\t\tvar newP = new svg.Point(pp.current.x, (pp.isRelativeCommand() ? pp.current.y : 0) + pp.getScalar());\r\n\t\t\t\t\t\t\tpp.addMarker(newP, pp.current);\r\n\t\t\t\t\t\t\tpp.current = newP;\r\n\t\t\t\t\t\t\tbb.addPoint(pp.current.x, pp.current.y);\r\n\t\t\t\t\t\t\tif (ctx != null) ctx.lineTo(pp.current.x, pp.current.y);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase 'C':\r\n\t\t\t\t\t\twhile (!pp.isCommandOrEnd()) {\r\n\t\t\t\t\t\t\tvar curr = pp.current;\r\n\t\t\t\t\t\t\tvar p1 = pp.getPoint();\r\n\t\t\t\t\t\t\tvar cntrl = pp.getAsControlPoint();\r\n\t\t\t\t\t\t\tvar cp = pp.getAsCurrentPoint();\r\n\t\t\t\t\t\t\tpp.addMarker(cp, cntrl, p1);\r\n\t\t\t\t\t\t\tbb.addBezierCurve(curr.x, curr.y, p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);\r\n\t\t\t\t\t\t\tif (ctx != null) ctx.bezierCurveTo(p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase 'S':\r\n\t\t\t\t\t\twhile (!pp.isCommandOrEnd()) {\r\n\t\t\t\t\t\t\tvar curr = pp.current;\r\n\t\t\t\t\t\t\tvar p1 = pp.getReflectedControlPoint();\r\n\t\t\t\t\t\t\tvar cntrl = pp.getAsControlPoint();\r\n\t\t\t\t\t\t\tvar cp = pp.getAsCurrentPoint();\r\n\t\t\t\t\t\t\tpp.addMarker(cp, cntrl, p1);\r\n\t\t\t\t\t\t\tbb.addBezierCurve(curr.x, curr.y, p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);\r\n\t\t\t\t\t\t\tif (ctx != null) ctx.bezierCurveTo(p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase 'Q':\r\n\t\t\t\t\t\twhile (!pp.isCommandOrEnd()) {\r\n\t\t\t\t\t\t\tvar curr = pp.current;\r\n\t\t\t\t\t\t\tvar cntrl = pp.getAsControlPoint();\r\n\t\t\t\t\t\t\tvar cp = pp.getAsCurrentPoint();\r\n\t\t\t\t\t\t\tpp.addMarker(cp, cntrl, cntrl);\r\n\t\t\t\t\t\t\tbb.addQuadraticCurve(curr.x, curr.y, cntrl.x, cntrl.y, cp.x, cp.y);\r\n\t\t\t\t\t\t\tif (ctx != null) ctx.quadraticCurveTo(cntrl.x, cntrl.y, cp.x, cp.y);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase 'T':\r\n\t\t\t\t\t\twhile (!pp.isCommandOrEnd()) {\r\n\t\t\t\t\t\t\tvar curr = pp.current;\r\n\t\t\t\t\t\t\tvar cntrl = pp.getReflectedControlPoint();\r\n\t\t\t\t\t\t\tpp.control = cntrl;\r\n\t\t\t\t\t\t\tvar cp = pp.getAsCurrentPoint();\r\n\t\t\t\t\t\t\tpp.addMarker(cp, cntrl, cntrl);\r\n\t\t\t\t\t\t\tbb.addQuadraticCurve(curr.x, curr.y, cntrl.x, cntrl.y, cp.x, cp.y);\r\n\t\t\t\t\t\t\tif (ctx != null) ctx.quadraticCurveTo(cntrl.x, cntrl.y, cp.x, cp.y);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase 'A':\r\n\t\t\t\t\t\twhile (!pp.isCommandOrEnd()) {\r\n\t\t\t\t\t\t    var curr = pp.current;\r\n\t\t\t\t\t\t\tvar rx = pp.getScalar();\r\n\t\t\t\t\t\t\tvar ry = pp.getScalar();\r\n\t\t\t\t\t\t\tvar xAxisRotation = pp.getScalar() * (Math.PI / 180.0);\r\n\t\t\t\t\t\t\tvar largeArcFlag = pp.getScalar();\r\n\t\t\t\t\t\t\tvar sweepFlag = pp.getScalar();\r\n\t\t\t\t\t\t\tvar cp = pp.getAsCurrentPoint();\r\n\r\n\t\t\t\t\t\t\t// Conversion from endpoint to center parameterization\r\n\t\t\t\t\t\t\t// http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes\r\n\t\t\t\t\t\t\t// x1', y1'\r\n\t\t\t\t\t\t\tvar currp = new svg.Point(\r\n\t\t\t\t\t\t\t\tMath.cos(xAxisRotation) * (curr.x - cp.x) / 2.0 + Math.sin(xAxisRotation) * (curr.y - cp.y) / 2.0,\r\n\t\t\t\t\t\t\t\t-Math.sin(xAxisRotation) * (curr.x - cp.x) / 2.0 + Math.cos(xAxisRotation) * (curr.y - cp.y) / 2.0\r\n\t\t\t\t\t\t\t);\r\n\t\t\t\t\t\t\t// adjust radii\r\n\t\t\t\t\t\t\tvar l = Math.pow(currp.x,2)/Math.pow(rx,2)+Math.pow(currp.y,2)/Math.pow(ry,2);\r\n\t\t\t\t\t\t\tif (l > 1) {\r\n\t\t\t\t\t\t\t\trx *= Math.sqrt(l);\r\n\t\t\t\t\t\t\t\try *= Math.sqrt(l);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t// cx', cy'\r\n\t\t\t\t\t\t\tvar s = (largeArcFlag == sweepFlag ? -1 : 1) * Math.sqrt(\r\n\t\t\t\t\t\t\t\t((Math.pow(rx,2)*Math.pow(ry,2))-(Math.pow(rx,2)*Math.pow(currp.y,2))-(Math.pow(ry,2)*Math.pow(currp.x,2))) /\r\n\t\t\t\t\t\t\t\t(Math.pow(rx,2)*Math.pow(currp.y,2)+Math.pow(ry,2)*Math.pow(currp.x,2))\r\n\t\t\t\t\t\t\t);\r\n\t\t\t\t\t\t\tif (isNaN(s)) s = 0;\r\n\t\t\t\t\t\t\tvar cpp = new svg.Point(s * rx * currp.y / ry, s * -ry * currp.x / rx);\r\n\t\t\t\t\t\t\t// cx, cy\r\n\t\t\t\t\t\t\tvar centp = new svg.Point(\r\n\t\t\t\t\t\t\t\t(curr.x + cp.x) / 2.0 + Math.cos(xAxisRotation) * cpp.x - Math.sin(xAxisRotation) * cpp.y,\r\n\t\t\t\t\t\t\t\t(curr.y + cp.y) / 2.0 + Math.sin(xAxisRotation) * cpp.x + Math.cos(xAxisRotation) * cpp.y\r\n\t\t\t\t\t\t\t);\r\n\t\t\t\t\t\t\t// vector magnitude\r\n\t\t\t\t\t\t\tvar m = function(v) { return Math.sqrt(Math.pow(v[0],2) + Math.pow(v[1],2)); }\r\n\t\t\t\t\t\t\t// ratio between two vectors\r\n\t\t\t\t\t\t\tvar r = function(u, v) { return (u[0]*v[0]+u[1]*v[1]) / (m(u)*m(v)) }\r\n\t\t\t\t\t\t\t// angle between two vectors\r\n\t\t\t\t\t\t\tvar a = function(u, v) { return (u[0]*v[1] < u[1]*v[0] ? -1 : 1) * Math.acos(r(u,v)); }\r\n\t\t\t\t\t\t\t// initial angle\r\n\t\t\t\t\t\t\tvar a1 = a([1,0], [(currp.x-cpp.x)/rx,(currp.y-cpp.y)/ry]);\r\n\t\t\t\t\t\t\t// angle delta\r\n\t\t\t\t\t\t\tvar u = [(currp.x-cpp.x)/rx,(currp.y-cpp.y)/ry];\r\n\t\t\t\t\t\t\tvar v = [(-currp.x-cpp.x)/rx,(-currp.y-cpp.y)/ry];\r\n\t\t\t\t\t\t\tvar ad = a(u, v);\r\n\t\t\t\t\t\t\tif (r(u,v) <= -1) ad = Math.PI;\r\n\t\t\t\t\t\t\tif (r(u,v) >= 1) ad = 0;\r\n\r\n\t\t\t\t\t\t\tif (sweepFlag == 0 && ad > 0) ad = ad - 2 * Math.PI;\r\n\t\t\t\t\t\t\tif (sweepFlag == 1 && ad < 0) ad = ad + 2 * Math.PI;\r\n\r\n\t\t\t\t\t\t\t// for markers\r\n\t\t\t\t\t\t\tvar halfWay = new svg.Point(\r\n\t\t\t\t\t\t\t\tcentp.x - rx * Math.cos((a1 + ad) / 2),\r\n\t\t\t\t\t\t\t\tcentp.y - ry * Math.sin((a1 + ad) / 2)\r\n\t\t\t\t\t\t\t);\r\n\t\t\t\t\t\t\tpp.addMarkerAngle(halfWay, (a1 + ad) / 2 + (sweepFlag == 0 ? 1 : -1) * Math.PI / 2);\r\n\t\t\t\t\t\t\tpp.addMarkerAngle(cp, ad + (sweepFlag == 0 ? 1 : -1) * Math.PI / 2);\r\n\r\n\t\t\t\t\t\t\tbb.addPoint(cp.x, cp.y); // TODO: this is too naive, make it better\r\n\t\t\t\t\t\t\tif (ctx != null) {\r\n\t\t\t\t\t\t\t\tvar r = rx > ry ? rx : ry;\r\n\t\t\t\t\t\t\t\tvar sx = rx > ry ? 1 : rx / ry;\r\n\t\t\t\t\t\t\t\tvar sy = rx > ry ? ry / rx : 1;\r\n\r\n\t\t\t\t\t\t\t\tctx.translate(centp.x, centp.y);\r\n\t\t\t\t\t\t\t\tctx.rotate(xAxisRotation);\r\n\t\t\t\t\t\t\t\tctx.scale(sx, sy);\r\n\t\t\t\t\t\t\t\tctx.arc(0, 0, r, a1, a1 + ad, 1 - sweepFlag);\r\n\t\t\t\t\t\t\t\tctx.scale(1/sx, 1/sy);\r\n\t\t\t\t\t\t\t\tctx.rotate(-xAxisRotation);\r\n\t\t\t\t\t\t\t\tctx.translate(-centp.x, -centp.y);\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\tcase 'Z':\r\n\t\t\t\t\t\tif (ctx != null) ctx.closePath();\r\n\t\t\t\t\t\tpp.current = pp.start;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\treturn bb;\r\n\t\t\t}\r\n\r\n\t\t\tthis.getMarkers = function() {\r\n\t\t\t\tvar points = this.PathParser.getMarkerPoints();\r\n\t\t\t\tvar angles = this.PathParser.getMarkerAngles();\r\n\t\t\t\t\r\n\t\t\t\tvar markers = [];\r\n\t\t\t\tfor (var i=0; i<points.length; i++) {\r\n\t\t\t\t\tmarkers.push([points[i], angles[i]]);\r\n\t\t\t\t}\r\n\t\t\t\treturn markers;\r\n\t\t\t}\r\n\t\t}\r\n\t\tsvg.Element.path.prototype = new svg.Element.PathElementBase;\r\n\t\t\r\n\t\t// pattern element\r\n\t\tsvg.Element.pattern = function(node) {\r\n\t\t\tthis.base = svg.Element.ElementBase;\r\n\t\t\tthis.base(node);\r\n\t\t\t\r\n\t\t\tthis.createPattern = function(ctx, element) {\r\n\t\t\t\t// render me using a temporary svg element\r\n\t\t\t\tvar tempSvg = new svg.Element.svg();\r\n\t\t\t\ttempSvg.attributes['viewBox'] = new svg.Property('viewBox', this.attribute('viewBox').value);\r\n\t\t\t\ttempSvg.attributes['x'] = new svg.Property('x', this.attribute('x').value);\r\n\t\t\t\ttempSvg.attributes['y'] = new svg.Property('y', this.attribute('y').value);\r\n\t\t\t\ttempSvg.attributes['width'] = new svg.Property('width', this.attribute('width').value);\r\n\t\t\t\ttempSvg.attributes['height'] = new svg.Property('height', this.attribute('height').value);\r\n\t\t\t\ttempSvg.children = this.children;\r\n\t\t\t\t\r\n\t\t\t\tvar c = document.createElement('canvas');\r\n\t\t\t\tc.width = this.attribute('width').Length.toPixels('x');\r\n\t\t\t\tc.height = this.attribute('height').Length.toPixels('y');\r\n\t\t\t\ttempSvg.render(c.getContext('2d'));\t\t\r\n\t\t\t\treturn ctx.createPattern(c, 'repeat');\r\n\t\t\t}\r\n\t\t}\r\n\t\tsvg.Element.pattern.prototype = new svg.Element.ElementBase;\r\n\t\t\r\n\t\t// marker element\r\n\t\tsvg.Element.marker = function(node) {\r\n\t\t\tthis.base = svg.Element.ElementBase;\r\n\t\t\tthis.base(node);\r\n\t\t\t\r\n\t\t\tthis.baseRender = this.render;\r\n\t\t\tthis.render = function(ctx, point, angle) {\r\n\t\t\t\tctx.translate(point.x, point.y);\r\n\t\t\t\tif (this.attribute('orient').valueOrDefault('auto') == 'auto') ctx.rotate(angle);\r\n\t\t\t\tif (this.attribute('markerUnits').valueOrDefault('strokeWidth') == 'strokeWidth') ctx.scale(ctx.lineWidth, ctx.lineWidth);\r\n\t\t\t\tctx.save();\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t// render me using a temporary svg element\r\n\t\t\t\tvar tempSvg = new svg.Element.svg();\r\n\t\t\t\ttempSvg.attributes['viewBox'] = new svg.Property('viewBox', this.attribute('viewBox').value);\r\n\t\t\t\ttempSvg.attributes['refX'] = new svg.Property('refX', this.attribute('refX').value);\r\n\t\t\t\ttempSvg.attributes['refY'] = new svg.Property('refY', this.attribute('refY').value);\r\n\t\t\t\ttempSvg.attributes['width'] = new svg.Property('width', this.attribute('markerWidth').value);\r\n\t\t\t\ttempSvg.attributes['height'] = new svg.Property('height', this.attribute('markerHeight').value);\r\n\t\t\t\ttempSvg.attributes['fill'] = new svg.Property('fill', this.attribute('fill').valueOrDefault('black'));\r\n\t\t\t\ttempSvg.attributes['stroke'] = new svg.Property('stroke', this.attribute('stroke').valueOrDefault('none'));\r\n\t\t\t\ttempSvg.children = this.children;\r\n\t\t\t\ttempSvg.render(ctx);\r\n\t\t\t\t\r\n\t\t\t\tctx.restore();\r\n\t\t\t\tif (this.attribute('markerUnits').valueOrDefault('strokeWidth') == 'strokeWidth') ctx.scale(1/ctx.lineWidth, 1/ctx.lineWidth);\r\n\t\t\t\tif (this.attribute('orient').valueOrDefault('auto') == 'auto') ctx.rotate(-angle);\r\n\t\t\t\tctx.translate(-point.x, -point.y);\r\n\t\t\t}\r\n\t\t}\r\n\t\tsvg.Element.marker.prototype = new svg.Element.ElementBase;\r\n\t\t\r\n\t\t// definitions element\r\n\t\tsvg.Element.defs = function(node) {\r\n\t\t\tthis.base = svg.Element.ElementBase;\r\n\t\t\tthis.base(node);\t\r\n\t\t\t\r\n\t\t\tthis.render = function(ctx) {\r\n\t\t\t\t// NOOP\r\n\t\t\t}\r\n\t\t}\r\n\t\tsvg.Element.defs.prototype = new svg.Element.ElementBase;\r\n\t\t\r\n\t\t// base for gradients\r\n\t\tsvg.Element.GradientBase = function(node) {\r\n\t\t\tthis.base = svg.Element.ElementBase;\r\n\t\t\tthis.base(node);\r\n\t\t\t\r\n\t\t\tthis.gradientUnits = this.attribute('gradientUnits').valueOrDefault('objectBoundingBox');\r\n\t\t\t\r\n\t\t\tthis.stops = [];\t\t\t\r\n\t\t\tfor (var i=0; i<this.children.length; i++) {\r\n\t\t\t\tvar child = this.children[i];\r\n\t\t\t\tthis.stops.push(child);\r\n\t\t\t}\t\r\n\t\t\t\r\n\t\t\tthis.getGradient = function() {\r\n\t\t\t\t// OVERRIDE ME!\r\n\t\t\t}\t\t\t\r\n\r\n\t\t\tthis.createGradient = function(ctx, element) {\r\n\t\t\t\tvar stopsContainer = this;\r\n\t\t\t\tif (this.attribute('xlink:href').hasValue()) {\r\n\t\t\t\t\tstopsContainer = this.attribute('xlink:href').Definition.getDefinition();\r\n\t\t\t\t}\r\n\t\t\t\r\n\t\t\t\tvar g = this.getGradient(ctx, element);\r\n\t\t\t\tfor (var i=0; i<stopsContainer.stops.length; i++) {\r\n\t\t\t\t\tg.addColorStop(stopsContainer.stops[i].offset, stopsContainer.stops[i].color);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tif (this.attribute('gradientTransform').hasValue()) {\r\n\t\t\t\t\t// render as transformed pattern on temporary canvas\r\n\t\t\t\t\tvar rootView = svg.ViewPort.viewPorts[0];\r\n\t\t\t\t\t\r\n\t\t\t\t\tvar rect = new svg.Element.rect();\r\n\t\t\t\t\trect.attributes['x'] = new svg.Property('x', -svg.MAX_VIRTUAL_PIXELS/3.0);\r\n\t\t\t\t\trect.attributes['y'] = new svg.Property('y', -svg.MAX_VIRTUAL_PIXELS/3.0);\r\n\t\t\t\t\trect.attributes['width'] = new svg.Property('width', svg.MAX_VIRTUAL_PIXELS);\r\n\t\t\t\t\trect.attributes['height'] = new svg.Property('height', svg.MAX_VIRTUAL_PIXELS);\r\n\t\t\t\t\t\r\n\t\t\t\t\tvar group = new svg.Element.g();\r\n\t\t\t\t\tgroup.attributes['transform'] = new svg.Property('transform', this.attribute('gradientTransform').value);\r\n\t\t\t\t\tgroup.children = [ rect ];\r\n\t\t\t\t\t\r\n\t\t\t\t\tvar tempSvg = new svg.Element.svg();\r\n\t\t\t\t\ttempSvg.attributes['x'] = new svg.Property('x', 0);\r\n\t\t\t\t\ttempSvg.attributes['y'] = new svg.Property('y', 0);\r\n\t\t\t\t\ttempSvg.attributes['width'] = new svg.Property('width', rootView.width);\r\n\t\t\t\t\ttempSvg.attributes['height'] = new svg.Property('height', rootView.height);\r\n\t\t\t\t\ttempSvg.children = [ group ];\r\n\t\t\t\t\t\r\n\t\t\t\t\tvar c = document.createElement('canvas');\r\n\t\t\t\t\tc.width = rootView.width;\r\n\t\t\t\t\tc.height = rootView.height;\r\n\t\t\t\t\tvar tempCtx = c.getContext('2d');\r\n\t\t\t\t\ttempCtx.fillStyle = g;\r\n\t\t\t\t\ttempSvg.render(tempCtx);\t\t\r\n\t\t\t\t\treturn tempCtx.createPattern(c, 'no-repeat');\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\treturn g;\t\t\t\t\r\n\t\t\t}\r\n\t\t}\r\n\t\tsvg.Element.GradientBase.prototype = new svg.Element.ElementBase;\r\n\t\t\r\n\t\t// linear gradient element\r\n\t\tsvg.Element.linearGradient = function(node) {\r\n\t\t\tthis.base = svg.Element.GradientBase;\r\n\t\t\tthis.base(node);\r\n\t\t\t\r\n\t\t\tthis.getGradient = function(ctx, element) {\r\n\t\t\t\tvar bb = element.getBoundingBox();\r\n\t\t\t\t\r\n\t\t\t\tvar x1 = (this.gradientUnits == 'objectBoundingBox' \r\n\t\t\t\t\t? bb.x() + bb.width() * this.attribute('x1').numValue() \r\n\t\t\t\t\t: this.attribute('x1').Length.toPixels('x'));\r\n\t\t\t\tvar y1 = (this.gradientUnits == 'objectBoundingBox' \r\n\t\t\t\t\t? bb.y() + bb.height() * this.attribute('y1').numValue()\r\n\t\t\t\t\t: this.attribute('y1').Length.toPixels('y'));\r\n\t\t\t\tvar x2 = (this.gradientUnits == 'objectBoundingBox' \r\n\t\t\t\t\t? bb.x() + bb.width() * this.attribute('x2').numValue()\r\n\t\t\t\t\t: this.attribute('x2').Length.toPixels('x'));\r\n\t\t\t\tvar y2 = (this.gradientUnits == 'objectBoundingBox' \r\n\t\t\t\t\t? bb.y() + bb.height() * this.attribute('y2').numValue()\r\n\t\t\t\t\t: this.attribute('y2').Length.toPixels('y'));\r\n\r\n\t\t\t\treturn ctx.createLinearGradient(x1, y1, x2, y2);\r\n\t\t\t}\r\n\t\t}\r\n\t\tsvg.Element.linearGradient.prototype = new svg.Element.GradientBase;\r\n\t\t\r\n\t\t// radial gradient element\r\n\t\tsvg.Element.radialGradient = function(node) {\r\n\t\t\tthis.base = svg.Element.GradientBase;\r\n\t\t\tthis.base(node);\r\n\t\t\t\r\n\t\t\tthis.getGradient = function(ctx, element) {\r\n\t\t\t\tvar bb = element.getBoundingBox();\r\n\t\t\t\t\r\n\t\t\t\tvar cx = (this.gradientUnits == 'objectBoundingBox' \r\n\t\t\t\t\t? bb.x() + bb.width() * this.attribute('cx').numValue() \r\n\t\t\t\t\t: this.attribute('cx').Length.toPixels('x'));\r\n\t\t\t\tvar cy = (this.gradientUnits == 'objectBoundingBox' \r\n\t\t\t\t\t? bb.y() + bb.height() * this.attribute('cy').numValue() \r\n\t\t\t\t\t: this.attribute('cy').Length.toPixels('y'));\r\n\t\t\t\t\r\n\t\t\t\tvar fx = cx;\r\n\t\t\t\tvar fy = cy;\r\n\t\t\t\tif (this.attribute('fx').hasValue()) {\r\n\t\t\t\t\tfx = (this.gradientUnits == 'objectBoundingBox' \r\n\t\t\t\t\t? bb.x() + bb.width() * this.attribute('fx').numValue() \r\n\t\t\t\t\t: this.attribute('fx').Length.toPixels('x'));\r\n\t\t\t\t}\r\n\t\t\t\tif (this.attribute('fy').hasValue()) {\r\n\t\t\t\t\tfy = (this.gradientUnits == 'objectBoundingBox' \r\n\t\t\t\t\t? bb.y() + bb.height() * this.attribute('fy').numValue() \r\n\t\t\t\t\t: this.attribute('fy').Length.toPixels('y'));\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\tvar r = (this.gradientUnits == 'objectBoundingBox' \r\n\t\t\t\t\t? (bb.width() + bb.height()) / 2.0 * this.attribute('r').numValue()\r\n\t\t\t\t\t: this.attribute('r').Length.toPixels());\r\n\t\t\t\t\r\n\t\t\t\treturn ctx.createRadialGradient(fx, fy, 0, cx, cy, r);\r\n\t\t\t}\r\n\t\t}\r\n\t\tsvg.Element.radialGradient.prototype = new svg.Element.GradientBase;\r\n\t\t\r\n\t\t// gradient stop element\r\n\t\tsvg.Element.stop = function(node) {\r\n\t\t\tthis.base = svg.Element.ElementBase;\r\n\t\t\tthis.base(node);\r\n\t\t\t\r\n\t\t\tthis.offset = this.attribute('offset').numValue();\r\n\t\t\t\r\n\t\t\tvar stopColor = this.style('stop-color');\r\n\t\t\tif (this.style('stop-opacity').hasValue()) stopColor = stopColor.Color.addOpacity(this.style('stop-opacity').value);\r\n\t\t\tthis.color = stopColor.value;\r\n\t\t}\r\n\t\tsvg.Element.stop.prototype = new svg.Element.ElementBase;\r\n\t\t\r\n\t\t// animation base element\r\n\t\tsvg.Element.AnimateBase = function(node) {\r\n\t\t\tthis.base = svg.Element.ElementBase;\r\n\t\t\tthis.base(node);\r\n\t\t\t\r\n\t\t\tsvg.Animations.push(this);\r\n\t\t\t\r\n\t\t\tthis.duration = 0.0;\r\n\t\t\tthis.begin = this.attribute('begin').Time.toMilliseconds();\r\n\t\t\tthis.maxDuration = this.begin + this.attribute('dur').Time.toMilliseconds();\r\n\t\t\t\r\n\t\t\tthis.getProperty = function() {\r\n\t\t\t\tvar attributeType = this.attribute('attributeType').value;\r\n\t\t\t\tvar attributeName = this.attribute('attributeName').value;\r\n\t\t\t\t\r\n\t\t\t\tif (attributeType == 'CSS') {\r\n\t\t\t\t\treturn this.parent.style(attributeName, true);\r\n\t\t\t\t}\r\n\t\t\t\treturn this.parent.attribute(attributeName, true);\t\t\t\r\n\t\t\t};\r\n\t\t\t\r\n\t\t\tthis.initialValue = null;\r\n\t\t\tthis.removed = false;\t\t\t\r\n\r\n\t\t\tthis.calcValue = function() {\r\n\t\t\t\t// OVERRIDE ME!\r\n\t\t\t\treturn '';\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.update = function(delta) {\t\r\n\t\t\t\t// set initial value\r\n\t\t\t\tif (this.initialValue == null) {\r\n\t\t\t\t\tthis.initialValue = this.getProperty().value;\r\n\t\t\t\t}\r\n\t\t\t\r\n\t\t\t\t// if we're past the end time\r\n\t\t\t\tif (this.duration > this.maxDuration) {\r\n\t\t\t\t\t// loop for indefinitely repeating animations\r\n\t\t\t\t\tif (this.attribute('repeatCount').value == 'indefinite') {\r\n\t\t\t\t\t\tthis.duration = 0.0\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if (this.attribute('fill').valueOrDefault('remove') == 'remove' && !this.removed) {\r\n\t\t\t\t\t\tthis.removed = true;\r\n\t\t\t\t\t\tthis.getProperty().value = this.initialValue;\r\n\t\t\t\t\t\treturn true;\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse {\r\n\t\t\t\t\t\treturn false; // no updates made\r\n\t\t\t\t\t}\r\n\t\t\t\t}\t\t\t\r\n\t\t\t\tthis.duration = this.duration + delta;\r\n\t\t\t\r\n\t\t\t\t// if we're past the begin time\r\n\t\t\t\tvar updated = false;\r\n\t\t\t\tif (this.begin < this.duration) {\r\n\t\t\t\t\tvar newValue = this.calcValue(); // tween\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (this.attribute('type').hasValue()) {\r\n\t\t\t\t\t\t// for transform, etc.\r\n\t\t\t\t\t\tvar type = this.attribute('type').value;\r\n\t\t\t\t\t\tnewValue = type + '(' + newValue + ')';\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tthis.getProperty().value = newValue;\r\n\t\t\t\t\tupdated = true;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\treturn updated;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// fraction of duration we've covered\r\n\t\t\tthis.progress = function() {\r\n\t\t\t\treturn ((this.duration - this.begin) / (this.maxDuration - this.begin));\r\n\t\t\t}\t\t\t\r\n\t\t}\r\n\t\tsvg.Element.AnimateBase.prototype = new svg.Element.ElementBase;\r\n\t\t\r\n\t\t// animate element\r\n\t\tsvg.Element.animate = function(node) {\r\n\t\t\tthis.base = svg.Element.AnimateBase;\r\n\t\t\tthis.base(node);\r\n\t\t\t\r\n\t\t\tthis.calcValue = function() {\r\n\t\t\t\tvar from = this.attribute('from').numValue();\r\n\t\t\t\tvar to = this.attribute('to').numValue();\r\n\t\t\t\t\r\n\t\t\t\t// tween value linearly\r\n\t\t\t\treturn from + (to - from) * this.progress(); \r\n\t\t\t};\r\n\t\t}\r\n\t\tsvg.Element.animate.prototype = new svg.Element.AnimateBase;\r\n\t\t\t\r\n\t\t// animate color element\r\n\t\tsvg.Element.animateColor = function(node) {\r\n\t\t\tthis.base = svg.Element.AnimateBase;\r\n\t\t\tthis.base(node);\r\n\r\n\t\t\tthis.calcValue = function() {\r\n\t\t\t\tvar from = new RGBColor(this.attribute('from').value);\r\n\t\t\t\tvar to = new RGBColor(this.attribute('to').value);\r\n\t\t\t\t\r\n\t\t\t\tif (from.ok && to.ok) {\r\n\t\t\t\t\t// tween color linearly\r\n\t\t\t\t\tvar r = from.r + (to.r - from.r) * this.progress();\r\n\t\t\t\t\tvar g = from.g + (to.g - from.g) * this.progress();\r\n\t\t\t\t\tvar b = from.b + (to.b - from.b) * this.progress();\r\n\t\t\t\t\treturn 'rgb('+parseInt(r,10)+','+parseInt(g,10)+','+parseInt(b,10)+')';\r\n\t\t\t\t}\r\n\t\t\t\treturn this.attribute('from').value;\r\n\t\t\t};\r\n\t\t}\r\n\t\tsvg.Element.animateColor.prototype = new svg.Element.AnimateBase;\r\n\t\t\r\n\t\t// animate transform element\r\n\t\tsvg.Element.animateTransform = function(node) {\r\n\t\t\tthis.base = svg.Element.animate;\r\n\t\t\tthis.base(node);\r\n\t\t}\r\n\t\tsvg.Element.animateTransform.prototype = new svg.Element.animate;\r\n\t\t\r\n\t\t// font element\r\n\t\tsvg.Element.font = function(node) {\r\n\t\t\tthis.base = svg.Element.ElementBase;\r\n\t\t\tthis.base(node);\r\n\r\n\t\t\tthis.horizAdvX = this.attribute('horiz-adv-x').numValue();\t\t\t\r\n\t\t\t\r\n\t\t\tthis.isRTL = false;\r\n\t\t\tthis.isArabic = false;\r\n\t\t\tthis.fontFace = null;\r\n\t\t\tthis.missingGlyph = null;\r\n\t\t\tthis.glyphs = [];\t\t\t\r\n\t\t\tfor (var i=0; i<this.children.length; i++) {\r\n\t\t\t\tvar child = this.children[i];\r\n\t\t\t\tif (child.type == 'font-face') {\r\n\t\t\t\t\tthis.fontFace = child;\r\n\t\t\t\t\tif (child.style('font-family').hasValue()) {\r\n\t\t\t\t\t\tsvg.Definitions[child.style('font-family').value] = this;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse if (child.type == 'missing-glyph') this.missingGlyph = child;\r\n\t\t\t\telse if (child.type == 'glyph') {\r\n\t\t\t\t\tif (child.arabicForm != '') {\r\n\t\t\t\t\t\tthis.isRTL = true;\r\n\t\t\t\t\t\tthis.isArabic = true;\r\n\t\t\t\t\t\tif (typeof(this.glyphs[child.unicode]) == 'undefined') this.glyphs[child.unicode] = [];\r\n\t\t\t\t\t\tthis.glyphs[child.unicode][child.arabicForm] = child;\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse {\r\n\t\t\t\t\t\tthis.glyphs[child.unicode] = child;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\t\r\n\t\t}\r\n\t\tsvg.Element.font.prototype = new svg.Element.ElementBase;\r\n\t\t\r\n\t\t// font-face element\r\n\t\tsvg.Element.fontface = function(node) {\r\n\t\t\tthis.base = svg.Element.ElementBase;\r\n\t\t\tthis.base(node);\t\r\n\t\t\t\r\n\t\t\tthis.ascent = this.attribute('ascent').value;\r\n\t\t\tthis.descent = this.attribute('descent').value;\r\n\t\t\tthis.unitsPerEm = this.attribute('units-per-em').numValue();\t\t\t\t\r\n\t\t}\r\n\t\tsvg.Element.fontface.prototype = new svg.Element.ElementBase;\r\n\t\t\r\n\t\t// missing-glyph element\r\n\t\tsvg.Element.missingglyph = function(node) {\r\n\t\t\tthis.base = svg.Element.path;\r\n\t\t\tthis.base(node);\t\r\n\t\t\t\r\n\t\t\tthis.horizAdvX = 0;\r\n\t\t}\r\n\t\tsvg.Element.missingglyph.prototype = new svg.Element.path;\r\n\t\t\r\n\t\t// glyph element\r\n\t\tsvg.Element.glyph = function(node) {\r\n\t\t\tthis.base = svg.Element.path;\r\n\t\t\tthis.base(node);\t\r\n\t\t\t\r\n\t\t\tthis.horizAdvX = this.attribute('horiz-adv-x').numValue();\r\n\t\t\tthis.unicode = this.attribute('unicode').value;\r\n\t\t\tthis.arabicForm = this.attribute('arabic-form').value;\r\n\t\t}\r\n\t\tsvg.Element.glyph.prototype = new svg.Element.path;\r\n\t\t\r\n\t\t// text element\r\n\t\tsvg.Element.text = function(node) {\r\n\t\t\tthis.base = svg.Element.RenderedElementBase;\r\n\t\t\tthis.base(node);\r\n\t\t\t\r\n\t\t\tif (node != null) {\r\n\t\t\t\t// add children\r\n\t\t\t\tthis.children = [];\r\n\t\t\t\tfor (var i=0; i<node.childNodes.length; i++) {\r\n\t\t\t\t\tvar childNode = node.childNodes[i];\r\n\t\t\t\t\tif (childNode.nodeType == 1) { // capture tspan and tref nodes\r\n\t\t\t\t\t\tthis.addChild(childNode, true);\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse if (childNode.nodeType == 3) { // capture text\r\n\t\t\t\t\t\tthis.addChild(new svg.Element.tspan(childNode), false);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.baseSetContext = this.setContext;\r\n\t\t\tthis.setContext = function(ctx) {\r\n\t\t\t\tthis.baseSetContext(ctx);\r\n\t\t\t\tif (this.style('dominant-baseline').hasValue()) ctx.textBaseline = this.style('dominant-baseline').value;\r\n\t\t\t\tif (this.style('alignment-baseline').hasValue()) ctx.textBaseline = this.style('alignment-baseline').value;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.renderChildren = function(ctx) {\r\n\t\t\t\tvar textAnchor = this.style('text-anchor').valueOrDefault('start');\r\n\t\t\t\tvar x = this.attribute('x').Length.toPixels('x');\r\n\t\t\t\tvar y = this.attribute('y').Length.toPixels('y');\r\n\t\t\t\tfor (var i=0; i<this.children.length; i++) {\r\n\t\t\t\t\tvar child = this.children[i];\r\n\t\t\t\t\r\n\t\t\t\t\tif (child.attribute('x').hasValue()) {\r\n\t\t\t\t\t\tchild.x = child.attribute('x').Length.toPixels('x');\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse {\r\n\t\t\t\t\t\tif (child.attribute('dx').hasValue()) x += child.attribute('dx').Length.toPixels('x');\r\n\t\t\t\t\t\tchild.x = x;\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\tvar childLength = child.measureText(ctx);\r\n\t\t\t\t\tif (textAnchor != 'start' && (i==0 || child.attribute('x').hasValue())) { // new group?\r\n\t\t\t\t\t\t// loop through rest of children\r\n\t\t\t\t\t\tvar groupLength = childLength;\r\n\t\t\t\t\t\tfor (var j=i+1; j<this.children.length; j++) {\r\n\t\t\t\t\t\t\tvar childInGroup = this.children[j];\r\n\t\t\t\t\t\t\tif (childInGroup.attribute('x').hasValue()) break; // new group\r\n\t\t\t\t\t\t\tgroupLength += childInGroup.measureText(ctx);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tchild.x -= (textAnchor == 'end' ? groupLength : groupLength / 2.0);\r\n\t\t\t\t\t}\r\n\t\t\t\t\tx = child.x + childLength;\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (child.attribute('y').hasValue()) {\r\n\t\t\t\t\t\tchild.y = child.attribute('y').Length.toPixels('y');\r\n\t\t\t\t\t}\r\n\t\t\t\t\telse {\r\n\t\t\t\t\t\tif (child.attribute('dy').hasValue()) y += child.attribute('dy').Length.toPixels('y');\r\n\t\t\t\t\t\tchild.y = y;\r\n\t\t\t\t\t}\t\r\n\t\t\t\t\ty = child.y;\r\n\t\t\t\t\t\r\n\t\t\t\t\tchild.render(ctx);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\tsvg.Element.text.prototype = new svg.Element.RenderedElementBase;\r\n\t\t\r\n\t\t// text base\r\n\t\tsvg.Element.TextElementBase = function(node) {\r\n\t\t\tthis.base = svg.Element.RenderedElementBase;\r\n\t\t\tthis.base(node);\r\n\t\t\t\r\n\t\t\tthis.getGlyph = function(font, text, i) {\r\n\t\t\t\tvar c = text[i];\r\n\t\t\t\tvar glyph = null;\r\n\t\t\t\tif (font.isArabic) {\r\n\t\t\t\t\tvar arabicForm = 'isolated';\r\n\t\t\t\t\tif ((i==0 || text[i-1]==' ') && i<text.length-2 && text[i+1]!=' ') arabicForm = 'terminal'; \r\n\t\t\t\t\tif (i>0 && text[i-1]!=' ' && i<text.length-2 && text[i+1]!=' ') arabicForm = 'medial';\r\n\t\t\t\t\tif (i>0 && text[i-1]!=' ' && (i == text.length-1 || text[i+1]==' ')) arabicForm = 'initial';\r\n\t\t\t\t\tif (typeof(font.glyphs[c]) != 'undefined') {\r\n\t\t\t\t\t\tglyph = font.glyphs[c][arabicForm];\r\n\t\t\t\t\t\tif (glyph == null && font.glyphs[c].type == 'glyph') glyph = font.glyphs[c];\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\telse {\r\n\t\t\t\t\tglyph = font.glyphs[c];\r\n\t\t\t\t}\r\n\t\t\t\tif (glyph == null) glyph = font.missingGlyph;\r\n\t\t\t\treturn glyph;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.renderChildren = function(ctx) {\r\n\t\t\t\tvar customFont = this.parent.style('font-family').Definition.getDefinition();\r\n\t\t\t\tif (customFont != null) {\r\n\t\t\t\t\tvar fontSize = this.parent.style('font-size').numValueOrDefault(svg.Font.Parse(svg.ctx.font).fontSize);\r\n\t\t\t\t\tvar fontStyle = this.parent.style('font-style').valueOrDefault(svg.Font.Parse(svg.ctx.font).fontStyle);\r\n\t\t\t\t\tvar text = this.getText();\r\n\t\t\t\t\tif (customFont.isRTL) text = text.split(\"\").reverse().join(\"\");\r\n\t\t\t\t\t\r\n\t\t\t\t\tvar dx = svg.ToNumberArray(this.parent.attribute('dx').value);\r\n\t\t\t\t\tfor (var i=0; i<text.length; i++) {\r\n\t\t\t\t\t\tvar glyph = this.getGlyph(customFont, text, i);\r\n\t\t\t\t\t\tvar scale = fontSize / customFont.fontFace.unitsPerEm;\r\n\t\t\t\t\t\tctx.translate(this.x, this.y);\r\n\t\t\t\t\t\tctx.scale(scale, -scale);\r\n\t\t\t\t\t\tvar lw = ctx.lineWidth;\r\n\t\t\t\t\t\tctx.lineWidth = ctx.lineWidth * customFont.fontFace.unitsPerEm / fontSize;\r\n\t\t\t\t\t\tif (fontStyle == 'italic') ctx.transform(1, 0, .4, 1, 0, 0);\r\n\t\t\t\t\t\tglyph.render(ctx);\r\n\t\t\t\t\t\tif (fontStyle == 'italic') ctx.transform(1, 0, -.4, 1, 0, 0);\r\n\t\t\t\t\t\tctx.lineWidth = lw;\r\n\t\t\t\t\t\tctx.scale(1/scale, -1/scale);\r\n\t\t\t\t\t\tctx.translate(-this.x, -this.y);\t\r\n\t\t\t\t\t\t\r\n\t\t\t\t\t\tthis.x += fontSize * (glyph.horizAdvX || customFont.horizAdvX) / customFont.fontFace.unitsPerEm;\r\n\t\t\t\t\t\tif (typeof(dx[i]) != 'undefined' && !isNaN(dx[i])) {\r\n\t\t\t\t\t\t\tthis.x += dx[i];\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\t\t\t\r\n\t\t\t\tif (ctx.strokeStyle != '') ctx.strokeText(svg.compressSpaces(this.getText()), this.x, this.y);\r\n\t\t\t\tif (ctx.fillStyle != '') ctx.fillText(svg.compressSpaces(this.getText()), this.x, this.y);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.getText = function() {\r\n\t\t\t\t// OVERRIDE ME\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.measureText = function(ctx) {\r\n\t\t\t\tvar customFont = this.parent.style('font-family').Definition.getDefinition();\r\n\t\t\t\tif (customFont != null) {\r\n\t\t\t\t\tvar fontSize = this.parent.style('font-size').numValueOrDefault(svg.Font.Parse(svg.ctx.font).fontSize);\r\n\t\t\t\t\tvar measure = 0;\r\n\t\t\t\t\tvar text = this.getText();\r\n\t\t\t\t\tif (customFont.isRTL) text = text.split(\"\").reverse().join(\"\");\r\n\t\t\t\t\tvar dx = svg.ToNumberArray(this.parent.attribute('dx').value);\r\n\t\t\t\t\tfor (var i=0; i<text.length; i++) {\r\n\t\t\t\t\t\tvar glyph = this.getGlyph(customFont, text, i);\r\n\t\t\t\t\t\tmeasure += (glyph.horizAdvX || customFont.horizAdvX) * fontSize / customFont.fontFace.unitsPerEm;\r\n\t\t\t\t\t\tif (typeof(dx[i]) != 'undefined' && !isNaN(dx[i])) {\r\n\t\t\t\t\t\t\tmeasure += dx[i];\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\treturn measure;\r\n\t\t\t\t}\r\n\t\t\t\r\n\t\t\t\tvar textToMeasure = svg.compressSpaces(this.getText());\r\n\t\t\t\tif (!ctx.measureText) return textToMeasure.length * 10;\r\n\t\t\t\t\r\n\t\t\t\tctx.save();\r\n\t\t\t\tthis.setContext(ctx);\r\n\t\t\t\tvar width = ctx.measureText(textToMeasure).width;\r\n\t\t\t\tctx.restore();\r\n\t\t\t\treturn width;\r\n\t\t\t}\r\n\t\t}\r\n\t\tsvg.Element.TextElementBase.prototype = new svg.Element.RenderedElementBase;\r\n\t\t\r\n\t\t// tspan \r\n\t\tsvg.Element.tspan = function(node) {\r\n\t\t\tthis.base = svg.Element.TextElementBase;\r\n\t\t\tthis.base(node);\r\n\t\t\t\r\n\t\t\tthis.text = node.nodeType == 3 ? node.nodeValue : // text\r\n\t\t\t\t\t\tnode.childNodes.length > 0 ? node.childNodes[0].nodeValue : // element\r\n\t\t\t\t\t\tnode.text;\r\n\t\t\tthis.getText = function() {\r\n\t\t\t\treturn this.text;\r\n\t\t\t}\r\n\t\t}\r\n\t\tsvg.Element.tspan.prototype = new svg.Element.TextElementBase;\r\n\t\t\r\n\t\t// tref\r\n\t\tsvg.Element.tref = function(node) {\r\n\t\t\tthis.base = svg.Element.TextElementBase;\r\n\t\t\tthis.base(node);\r\n\t\t\t\r\n\t\t\tthis.getText = function() {\r\n\t\t\t\tvar element = this.attribute('xlink:href').Definition.getDefinition();\r\n\t\t\t\tif (element != null) return element.children[0].getText();\r\n\t\t\t}\r\n\t\t}\r\n\t\tsvg.Element.tref.prototype = new svg.Element.TextElementBase;\t\t\r\n\t\t\r\n\t\t// a element\r\n\t\tsvg.Element.a = function(node) {\r\n\t\t\tthis.base = svg.Element.TextElementBase;\r\n\t\t\tthis.base(node);\r\n\t\t\t\r\n\t\t\tthis.hasText = true;\r\n\t\t\tfor (var i=0; i<node.childNodes.length; i++) {\r\n\t\t\t\tif (node.childNodes[i].nodeType != 3) this.hasText = false;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// this might contain text\r\n\t\t\tthis.text = this.hasText ? node.childNodes[0].nodeValue : '';\r\n\t\t\tthis.getText = function() {\r\n\t\t\t\treturn this.text;\r\n\t\t\t}\t\t\r\n\r\n\t\t\tthis.baseRenderChildren = this.renderChildren;\r\n\t\t\tthis.renderChildren = function(ctx) {\r\n\t\t\t\tif (this.hasText) {\r\n\t\t\t\t\t// render as text element\r\n\t\t\t\t\tthis.baseRenderChildren(ctx);\r\n\t\t\t\t\tvar fontSize = new svg.Property('fontSize', svg.Font.Parse(svg.ctx.font).fontSize);\r\n\t\t\t\t\tsvg.Mouse.checkBoundingBox(this, new svg.BoundingBox(this.x, this.y - fontSize.Length.toPixels('y'), this.x + this.measureText(ctx), this.y));\t\t\t\t\t\r\n\t\t\t\t}\r\n\t\t\t\telse {\r\n\t\t\t\t\t// render as temporary group\r\n\t\t\t\t\tvar g = new svg.Element.g();\r\n\t\t\t\t\tg.children = this.children;\r\n\t\t\t\t\tg.parent = this;\r\n\t\t\t\t\tg.render(ctx);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.onclick = function() {\r\n\t\t\t\twindow.open(this.attribute('xlink:href').value);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.onmousemove = function() {\r\n\t\t\t\tsvg.ctx.canvas.style.cursor = 'pointer';\r\n\t\t\t}\r\n\t\t}\r\n\t\tsvg.Element.a.prototype = new svg.Element.TextElementBase;\t\t\r\n\t\t\r\n\t\t// image element\r\n\t\tsvg.Element.image = function(node) {\r\n\t\t\tthis.base = svg.Element.RenderedElementBase;\r\n\t\t\tthis.base(node);\r\n\t\t\t\r\n\t\t\tsvg.Images.push(this);\r\n\t\t\tthis.img = document.createElement('img');\r\n\t\t\tthis.loaded = false;\r\n\t\t\tvar that = this;\r\n\t\t\tthis.img.onload = function() { that.loaded = true; }\r\n\t\t\tthis.img.src = this.attribute('xlink:href').value;\r\n\t\t\t\r\n\t\t\tthis.renderChildren = function(ctx) {\r\n\t\t\t\tvar x = this.attribute('x').Length.toPixels('x');\r\n\t\t\t\tvar y = this.attribute('y').Length.toPixels('y');\r\n\t\t\t\t\r\n\t\t\t\tvar width = this.attribute('width').Length.toPixels('x');\r\n\t\t\t\tvar height = this.attribute('height').Length.toPixels('y');\t\t\t\r\n\t\t\t\tif (width == 0 || height == 0) return;\r\n\t\t\t\r\n\t\t\t\tctx.save();\r\n\t\t\t\tctx.translate(x, y);\r\n\t\t\t\tsvg.AspectRatio(ctx,\r\n\t\t\t\t\t\t\t\tthis.attribute('preserveAspectRatio').value,\r\n\t\t\t\t\t\t\t\twidth,\r\n\t\t\t\t\t\t\t\tthis.img.width,\r\n\t\t\t\t\t\t\t\theight,\r\n\t\t\t\t\t\t\t\tthis.img.height,\r\n\t\t\t\t\t\t\t\t0,\r\n\t\t\t\t\t\t\t\t0);\t\r\n\t\t\t\tctx.drawImage(this.img, 0, 0);\t\t\t\r\n\t\t\t\tctx.restore();\r\n\t\t\t}\r\n\t\t}\r\n\t\tsvg.Element.image.prototype = new svg.Element.RenderedElementBase;\r\n\t\t\r\n\t\t// group element\r\n\t\tsvg.Element.g = function(node) {\r\n\t\t\tthis.base = svg.Element.RenderedElementBase;\r\n\t\t\tthis.base(node);\r\n\t\t\t\r\n\t\t\tthis.getBoundingBox = function() {\r\n\t\t\t\tvar bb = new svg.BoundingBox();\r\n\t\t\t\tfor (var i=0; i<this.children.length; i++) {\r\n\t\t\t\t\tbb.addBoundingBox(this.children[i].getBoundingBox());\r\n\t\t\t\t}\r\n\t\t\t\treturn bb;\r\n\t\t\t};\r\n\t\t}\r\n\t\tsvg.Element.g.prototype = new svg.Element.RenderedElementBase;\r\n\r\n\t\t// symbol element\r\n\t\tsvg.Element.symbol = function(node) {\r\n\t\t\tthis.base = svg.Element.RenderedElementBase;\r\n\t\t\tthis.base(node);\r\n\t\t\t\r\n\t\t\tthis.baseSetContext = this.setContext;\r\n\t\t\tthis.setContext = function(ctx) {\t\t\r\n\t\t\t\tthis.baseSetContext(ctx);\r\n\t\t\t\t\r\n\t\t\t\t// viewbox\r\n\t\t\t\tif (this.attribute('viewBox').hasValue()) {\t\t\t\t\r\n\t\t\t\t\tvar viewBox = svg.ToNumberArray(this.attribute('viewBox').value);\r\n\t\t\t\t\tvar minX = viewBox[0];\r\n\t\t\t\t\tvar minY = viewBox[1];\r\n\t\t\t\t\twidth = viewBox[2];\r\n\t\t\t\t\theight = viewBox[3];\r\n\t\t\t\t\t\r\n\t\t\t\t\tsvg.AspectRatio(ctx,\r\n\t\t\t\t\t\t\t\t\tthis.attribute('preserveAspectRatio').value, \r\n\t\t\t\t\t\t\t\t\tthis.attribute('width').Length.toPixels('x'),\r\n\t\t\t\t\t\t\t\t\twidth,\r\n\t\t\t\t\t\t\t\t\tthis.attribute('height').Length.toPixels('y'),\r\n\t\t\t\t\t\t\t\t\theight,\r\n\t\t\t\t\t\t\t\t\tminX,\r\n\t\t\t\t\t\t\t\t\tminY);\r\n\r\n\t\t\t\t\tsvg.ViewPort.SetCurrent(viewBox[2], viewBox[3]);\t\t\t\t\t\t\r\n\t\t\t\t}\r\n\t\t\t}\t\t\t\r\n\t\t}\r\n\t\tsvg.Element.symbol.prototype = new svg.Element.RenderedElementBase;\t\t\r\n\t\t\t\r\n\t\t// style element\r\n\t\tsvg.Element.style = function(node) { \r\n\t\t\tthis.base = svg.Element.ElementBase;\r\n\t\t\tthis.base(node);\r\n\t\t\t\r\n\t\t\t// text, or spaces then CDATA\r\n\t\t\tvar css = node.childNodes[0].nodeValue + (node.childNodes.length > 1 ? node.childNodes[1].nodeValue : '');\r\n\t\t\tcss = css.replace(/(\\/\\*([^*]|[\\r\\n]|(\\*+([^*\\/]|[\\r\\n])))*\\*+\\/)|(^[\\s]*\\/\\/.*)/gm, ''); // remove comments\r\n\t\t\tcss = svg.compressSpaces(css); // replace whitespace\r\n\t\t\tvar cssDefs = css.split('}');\r\n\t\t\tfor (var i=0; i<cssDefs.length; i++) {\r\n\t\t\t\tif (svg.trim(cssDefs[i]) != '') {\r\n\t\t\t\t\tvar cssDef = cssDefs[i].split('{');\r\n\t\t\t\t\tvar cssClasses = cssDef[0].split(',');\r\n\t\t\t\t\tvar cssProps = cssDef[1].split(';');\r\n\t\t\t\t\tfor (var j=0; j<cssClasses.length; j++) {\r\n\t\t\t\t\t\tvar cssClass = svg.trim(cssClasses[j]);\r\n\t\t\t\t\t\tif (cssClass != '') {\r\n\t\t\t\t\t\t\tvar props = {};\r\n\t\t\t\t\t\t\tfor (var k=0; k<cssProps.length; k++) {\r\n\t\t\t\t\t\t\t\tvar prop = cssProps[k].indexOf(':');\r\n\t\t\t\t\t\t\t\tvar name = cssProps[k].substr(0, prop);\r\n\t\t\t\t\t\t\t\tvar value = cssProps[k].substr(prop + 1, cssProps[k].length - prop);\r\n\t\t\t\t\t\t\t\tif (name != null && value != null) {\r\n\t\t\t\t\t\t\t\t\tprops[svg.trim(name)] = new svg.Property(svg.trim(name), svg.trim(value));\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\tsvg.Styles[cssClass] = props;\r\n\t\t\t\t\t\t\tif (cssClass == '@font-face') {\r\n\t\t\t\t\t\t\t\tvar fontFamily = props['font-family'].value.replace(/\"/g,'');\r\n\t\t\t\t\t\t\t\tvar srcs = props['src'].value.split(',');\r\n\t\t\t\t\t\t\t\tfor (var s=0; s<srcs.length; s++) {\r\n\t\t\t\t\t\t\t\t\tif (srcs[s].indexOf('format(\"svg\")') > 0) {\r\n\t\t\t\t\t\t\t\t\t\tvar urlStart = srcs[s].indexOf('url');\r\n\t\t\t\t\t\t\t\t\t\tvar urlEnd = srcs[s].indexOf(')', urlStart);\r\n\t\t\t\t\t\t\t\t\t\tvar url = srcs[s].substr(urlStart + 5, urlEnd - urlStart - 6);\r\n\t\t\t\t\t\t\t\t\t\tvar doc = svg.parseXml(svg.ajax(url));\r\n\t\t\t\t\t\t\t\t\t\tvar fonts = doc.getElementsByTagName('font');\r\n\t\t\t\t\t\t\t\t\t\tfor (var f=0; f<fonts.length; f++) {\r\n\t\t\t\t\t\t\t\t\t\t\tvar font = svg.CreateElement(fonts[f]);\r\n\t\t\t\t\t\t\t\t\t\t\tsvg.Definitions[fontFamily] = font;\r\n\t\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\tsvg.Element.style.prototype = new svg.Element.ElementBase;\r\n\t\t\r\n\t\t// use element \r\n\t\tsvg.Element.use = function(node) {\r\n\t\t\tthis.base = svg.Element.RenderedElementBase;\r\n\t\t\tthis.base(node);\r\n\t\t\t\r\n\t\t\tthis.baseSetContext = this.setContext;\r\n\t\t\tthis.setContext = function(ctx) {\r\n\t\t\t\tthis.baseSetContext(ctx);\r\n\t\t\t\tif (this.attribute('x').hasValue()) ctx.translate(this.attribute('x').Length.toPixels('x'), 0);\r\n\t\t\t\tif (this.attribute('y').hasValue()) ctx.translate(0, this.attribute('y').Length.toPixels('y'));\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.getDefinition = function() {\r\n\t\t\t\tvar element = this.attribute('xlink:href').Definition.getDefinition();\r\n\t\t\t\tif (this.attribute('width').hasValue()) element.attribute('width', true).value = this.attribute('width').value;\r\n\t\t\t\tif (this.attribute('height').hasValue()) element.attribute('height', true).value = this.attribute('height').value;\r\n\t\t\t\treturn element;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.path = function(ctx) {\r\n\t\t\t\tvar element = this.getDefinition();\r\n\t\t\t\tif (element != null) element.path(ctx);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.renderChildren = function(ctx) {\r\n\t\t\t\tvar element = this.getDefinition();\r\n\t\t\t\tif (element != null) element.render(ctx);\r\n\t\t\t}\r\n\t\t}\r\n\t\tsvg.Element.use.prototype = new svg.Element.RenderedElementBase;\r\n\t\t\r\n\t\t// mask element\r\n\t\tsvg.Element.mask = function(node) {\r\n\t\t\tthis.base = svg.Element.ElementBase;\r\n\t\t\tthis.base(node);\r\n\t\t\t\t\t\t\r\n\t\t\tthis.apply = function(ctx, element) {\r\n\t\t\t\t// render as temp svg\t\r\n\t\t\t\tvar x = this.attribute('x').Length.toPixels('x');\r\n\t\t\t\tvar y = this.attribute('y').Length.toPixels('y');\r\n\t\t\t\tvar width = this.attribute('width').Length.toPixels('x');\r\n\t\t\t\tvar height = this.attribute('height').Length.toPixels('y');\r\n\t\t\t\t\r\n\t\t\t\t// temporarily remove mask to avoid recursion\r\n\t\t\t\tvar mask = element.attribute('mask').value;\r\n\t\t\t\telement.attribute('mask').value = '';\r\n\t\t\t\t\r\n\t\t\t\t\tvar cMask = document.createElement('canvas');\r\n\t\t\t\t\tcMask.width = x + width;\r\n\t\t\t\t\tcMask.height = y + height;\r\n\t\t\t\t\tvar maskCtx = cMask.getContext('2d');\r\n\t\t\t\t\tthis.renderChildren(maskCtx);\r\n\t\t\t\t\r\n\t\t\t\t\tvar c = document.createElement('canvas');\r\n\t\t\t\t\tc.width = x + width;\r\n\t\t\t\t\tc.height = y + height;\r\n\t\t\t\t\tvar tempCtx = c.getContext('2d');\r\n\t\t\t\t\telement.render(tempCtx);\r\n\t\t\t\t\ttempCtx.globalCompositeOperation = 'destination-in';\r\n\t\t\t\t\ttempCtx.fillStyle = maskCtx.createPattern(cMask, 'no-repeat');\r\n\t\t\t\t\ttempCtx.fillRect(0, 0, x + width, y + height);\r\n\t\t\t\t\t\r\n\t\t\t\t\tctx.fillStyle = tempCtx.createPattern(c, 'no-repeat');\r\n\t\t\t\t\tctx.fillRect(0, 0, x + width, y + height);\r\n\t\t\t\t\t\r\n\t\t\t\t// reassign mask\r\n\t\t\t\telement.attribute('mask').value = mask;\t\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.render = function(ctx) {\r\n\t\t\t\t// NO RENDER\r\n\t\t\t}\r\n\t\t}\r\n\t\tsvg.Element.mask.prototype = new svg.Element.ElementBase;\r\n\t\t\r\n\t\t// clip element\r\n\t\tsvg.Element.clipPath = function(node) {\r\n\t\t\tthis.base = svg.Element.ElementBase;\r\n\t\t\tthis.base(node);\r\n\t\t\t\r\n\t\t\tthis.apply = function(ctx) {\r\n\t\t\t\tfor (var i=0; i<this.children.length; i++) {\r\n\t\t\t\t\tif (this.children[i].path) {\r\n\t\t\t\t\t\tthis.children[i].path(ctx);\r\n\t\t\t\t\t\tctx.clip();\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.render = function(ctx) {\r\n\t\t\t\t// NO RENDER\r\n\t\t\t}\r\n\t\t}\r\n\t\tsvg.Element.clipPath.prototype = new svg.Element.ElementBase;\r\n\r\n\t\t// filters\r\n\t\tsvg.Element.filter = function(node) {\r\n\t\t\tthis.base = svg.Element.ElementBase;\r\n\t\t\tthis.base(node);\r\n\t\t\t\t\t\t\r\n\t\t\tthis.apply = function(ctx, element) {\r\n\t\t\t\t// render as temp svg\t\r\n\t\t\t\tvar bb = element.getBoundingBox();\r\n\t\t\t\tvar x = this.attribute('x').Length.toPixels('x');\r\n\t\t\t\tvar y = this.attribute('y').Length.toPixels('y');\r\n\t\t\t\tif (x == 0 || y == 0) {\r\n\t\t\t\t\tx = bb.x1;\r\n\t\t\t\t\ty = bb.y1;\r\n\t\t\t\t}\r\n\t\t\t\tvar width = this.attribute('width').Length.toPixels('x');\r\n\t\t\t\tvar height = this.attribute('height').Length.toPixels('y');\r\n\t\t\t\tif (width == 0 || height == 0) {\r\n\t\t\t\t\twidth = bb.width();\r\n\t\t\t\t\theight = bb.height();\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// temporarily remove filter to avoid recursion\r\n\t\t\t\tvar filter = element.style('filter').value;\r\n\t\t\t\telement.style('filter').value = '';\r\n\t\t\t\t\r\n\t\t\t\t// max filter distance\r\n\t\t\t\tvar extraPercent = .20;\r\n\t\t\t\tvar px = extraPercent * width;\r\n\t\t\t\tvar py = extraPercent * height;\r\n\t\t\t\t\r\n\t\t\t\tvar c = document.createElement('canvas');\r\n\t\t\t\tc.width = width + 2*px;\r\n\t\t\t\tc.height = height + 2*py;\r\n\t\t\t\tvar tempCtx = c.getContext('2d');\r\n\t\t\t\ttempCtx.translate(-x + px, -y + py);\r\n\t\t\t\telement.render(tempCtx);\r\n\t\t\t\r\n\t\t\t\t// apply filters\r\n\t\t\t\tfor (var i=0; i<this.children.length; i++) {\r\n\t\t\t\t\tthis.children[i].apply(tempCtx, 0, 0, width + 2*px, height + 2*py);\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// render on me\r\n\t\t\t\tctx.drawImage(c, 0, 0, width + 2*px, height + 2*py, x - px, y - py, width + 2*px, height + 2*py);\r\n\t\t\t\t\r\n\t\t\t\t// reassign filter\r\n\t\t\t\telement.style('filter', true).value = filter;\t\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.render = function(ctx) {\r\n\t\t\t\t// NO RENDER\r\n\t\t\t}\t\t\r\n\t\t}\r\n\t\tsvg.Element.filter.prototype = new svg.Element.ElementBase;\r\n\t\t\r\n\t\tsvg.Element.feGaussianBlur = function(node) {\r\n\t\t\tthis.base = svg.Element.ElementBase;\r\n\t\t\tthis.base(node);\t\r\n\t\t\t\r\n\t\t\tfunction make_fgauss(sigma) {\r\n\t\t\t\tsigma = Math.max(sigma, 0.01);\t\t\t      \r\n\t\t\t\tvar len = Math.ceil(sigma * 4.0) + 1;                     \r\n\t\t\t\tmask = [];                               \r\n\t\t\t\tfor (var i = 0; i < len; i++) {                             \r\n\t\t\t\t\tmask[i] = Math.exp(-0.5 * (i / sigma) * (i / sigma));                                           \r\n\t\t\t\t}                                                           \r\n\t\t\t\treturn mask; \r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tfunction normalize(mask) {\r\n\t\t\t\tvar sum = 0;\r\n\t\t\t\tfor (var i = 1; i < mask.length; i++) {\r\n\t\t\t\t\tsum += Math.abs(mask[i]);\r\n\t\t\t\t}\r\n\t\t\t\tsum = 2 * sum + Math.abs(mask[0]);\r\n\t\t\t\tfor (var i = 0; i < mask.length; i++) {\r\n\t\t\t\t\tmask[i] /= sum;\r\n\t\t\t\t}\r\n\t\t\t\treturn mask;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tfunction convolve_even(src, dst, mask, width, height) {\r\n\t\t\t  for (var y = 0; y < height; y++) {\r\n\t\t\t\tfor (var x = 0; x < width; x++) {\r\n\t\t\t\t  var a = imGet(src, x, y, width, height, 3)/255;\r\n\t\t\t\t  for (var rgba = 0; rgba < 4; rgba++) {\t\t\t\t\t  \r\n\t\t\t\t\t  var sum = mask[0] * (a==0?255:imGet(src, x, y, width, height, rgba)) * (a==0||rgba==3?1:a);\r\n\t\t\t\t\t  for (var i = 1; i < mask.length; i++) {\r\n\t\t\t\t\t\tvar a1 = imGet(src, Math.max(x-i,0), y, width, height, 3)/255;\r\n\t\t\t\t\t    var a2 = imGet(src, Math.min(x+i, width-1), y, width, height, 3)/255;\r\n\t\t\t\t\t\tsum += mask[i] * \r\n\t\t\t\t\t\t  ((a1==0?255:imGet(src, Math.max(x-i,0), y, width, height, rgba)) * (a1==0||rgba==3?1:a1) + \r\n\t\t\t\t\t\t   (a2==0?255:imGet(src, Math.min(x+i, width-1), y, width, height, rgba)) * (a2==0||rgba==3?1:a2));\r\n\t\t\t\t\t  }\r\n\t\t\t\t\t  imSet(dst, y, x, height, width, rgba, sum);\r\n\t\t\t\t  }\t\t\t  \r\n\t\t\t\t}\r\n\t\t\t  }\r\n\t\t\t}\t\t\r\n\r\n\t\t\tfunction imGet(img, x, y, width, height, rgba) {\r\n\t\t\t\treturn img[y*width*4 + x*4 + rgba];\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tfunction imSet(img, x, y, width, height, rgba, val) {\r\n\t\t\t\timg[y*width*4 + x*4 + rgba] = val;\r\n\t\t\t}\r\n\t\t\t\t\t\t\r\n\t\t\tfunction blur(ctx, width, height, sigma)\r\n\t\t\t{\r\n\t\t\t\tvar srcData = ctx.getImageData(0, 0, width, height);\r\n\t\t\t\tvar mask = make_fgauss(sigma);\r\n\t\t\t\tmask = normalize(mask);\r\n\t\t\t\ttmp = [];\r\n\t\t\t\tconvolve_even(srcData.data, tmp, mask, width, height);\r\n\t\t\t\tconvolve_even(tmp, srcData.data, mask, height, width);\r\n\t\t\t\tctx.clearRect(0, 0, width, height);\r\n\t\t\t\tctx.putImageData(srcData, 0, 0);\r\n\t\t\t}\t\t\t\r\n\t\t\r\n\t\t\tthis.apply = function(ctx, x, y, width, height) {\r\n\t\t\t\t// assuming x==0 && y==0 for now\r\n\t\t\t\tblur(ctx, width, height, this.attribute('stdDeviation').numValue());\r\n\t\t\t}\r\n\t\t}\r\n\t\tsvg.Element.filter.prototype = new svg.Element.feGaussianBlur;\r\n\t\t\r\n\t\t// title element, do nothing\r\n\t\tsvg.Element.title = function(node) {\r\n\t\t}\r\n\t\tsvg.Element.title.prototype = new svg.Element.ElementBase;\r\n\r\n\t\t// desc element, do nothing\r\n\t\tsvg.Element.desc = function(node) {\r\n\t\t}\r\n\t\tsvg.Element.desc.prototype = new svg.Element.ElementBase;\t\t\r\n\t\t\r\n\t\tsvg.Element.MISSING = function(node) {\r\n\t\t\tconsole.log('ERROR: Element \\'' + node.nodeName + '\\' not yet implemented.');\r\n\t\t}\r\n\t\tsvg.Element.MISSING.prototype = new svg.Element.ElementBase;\r\n\t\t\r\n\t\t// element factory\r\n\t\tsvg.CreateElement = function(node) {\t\r\n\t\t\tvar className = node.nodeName.replace(/^[^:]+:/,''); // remove namespace\r\n\t\t\tclassName = className.replace(/\\-/g,''); // remove dashes\r\n\t\t\tvar e = null;\r\n\t\t\tif (typeof(svg.Element[className]) != 'undefined') {\r\n\t\t\t\te = new svg.Element[className](node);\r\n\t\t\t}\r\n\t\t\telse {\r\n\t\t\t\te = new svg.Element.MISSING(node);\r\n\t\t\t}\r\n\r\n\t\t\te.type = node.nodeName;\r\n\t\t\treturn e;\r\n\t\t}\r\n\t\t\t\t\r\n\t\t// load from url\r\n\t\tsvg.load = function(ctx, url) {\r\n\t\t\tsvg.loadXml(ctx, svg.ajax(url));\r\n\t\t}\r\n\t\t\r\n\t\t// load from xml\r\n\t\tsvg.loadXml = function(ctx, xml) {\r\n\t\t\tsvg.loadXmlDoc(ctx, svg.parseXml(xml));\r\n\t\t}\r\n\t\t\r\n\t\tsvg.loadXmlDoc = function(ctx, dom) {\r\n\t\t\tsvg.init(ctx);\r\n\t\t\t\r\n\t\t\tvar mapXY = function(p) {\r\n\t\t\t\tvar e = ctx.canvas;\r\n\t\t\t\twhile (e) {\r\n\t\t\t\t\tp.x -= e.offsetLeft;\r\n\t\t\t\t\tp.y -= e.offsetTop;\r\n\t\t\t\t\te = e.offsetParent;\r\n\t\t\t\t}\r\n\t\t\t\tif (window.scrollX) p.x += window.scrollX;\r\n\t\t\t\tif (window.scrollY) p.y += window.scrollY;\r\n\t\t\t\treturn p;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// bind mouse\r\n\t\t\tif (svg.opts['ignoreMouse'] != true) {\r\n\t\t\t\tctx.canvas.onclick = function(e) {\r\n\t\t\t\t\tvar p = mapXY(new svg.Point(e != null ? e.clientX : event.clientX, e != null ? e.clientY : event.clientY));\r\n\t\t\t\t\tsvg.Mouse.onclick(p.x, p.y);\r\n\t\t\t\t};\r\n\t\t\t\tctx.canvas.onmousemove = function(e) {\r\n\t\t\t\t\tvar p = mapXY(new svg.Point(e != null ? e.clientX : event.clientX, e != null ? e.clientY : event.clientY));\r\n\t\t\t\t\tsvg.Mouse.onmousemove(p.x, p.y);\r\n\t\t\t\t};\r\n\t\t\t}\r\n\t\t\r\n\t\t\tvar e = svg.CreateElement(dom.documentElement);\r\n\t\t\te.root = true;\r\n\t\t\t\t\t\r\n\t\t\t// render loop\r\n\t\t\tvar isFirstRender = true;\r\n\t\t\tvar draw = function() {\r\n\t\t\t\tsvg.ViewPort.Clear();\r\n\t\t\t\tif (ctx.canvas.parentNode) svg.ViewPort.SetCurrent(ctx.canvas.parentNode.clientWidth, ctx.canvas.parentNode.clientHeight);\r\n\t\t\t\r\n\t\t\t\tif (svg.opts['ignoreDimensions'] != true) {\r\n\t\t\t\t\t// set canvas size\r\n\t\t\t\t\tif (e.style('width').hasValue()) {\r\n\t\t\t\t\t\tctx.canvas.width = e.style('width').Length.toPixels('x');\r\n\t\t\t\t\t\tctx.canvas.style.width = ctx.canvas.width + 'px';\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (e.style('height').hasValue()) {\r\n\t\t\t\t\t\tctx.canvas.height = e.style('height').Length.toPixels('y');\r\n\t\t\t\t\t\tctx.canvas.style.height = ctx.canvas.height + 'px';\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tvar cWidth = ctx.canvas.clientWidth || ctx.canvas.width;\r\n\t\t\t\tvar cHeight = ctx.canvas.clientHeight || ctx.canvas.height;\r\n\t\t\t\tsvg.ViewPort.SetCurrent(cWidth, cHeight);\t\t\r\n\t\t\t\t\r\n\t\t\t\tif (svg.opts != null && svg.opts['offsetX'] != null) e.attribute('x', true).value = svg.opts['offsetX'];\r\n\t\t\t\tif (svg.opts != null && svg.opts['offsetY'] != null) e.attribute('y', true).value = svg.opts['offsetY'];\r\n\t\t\t\tif (svg.opts != null && svg.opts['scaleWidth'] != null && svg.opts['scaleHeight'] != null) {\r\n\t\t\t\t\tvar xRatio = 1, yRatio = 1;\r\n\t\t\t\t\tif (e.attribute('width').hasValue()) xRatio = e.attribute('width').Length.toPixels('x') / svg.opts['scaleWidth'];\r\n\t\t\t\t\tif (e.attribute('height').hasValue()) yRatio = e.attribute('height').Length.toPixels('y') / svg.opts['scaleHeight'];\r\n\t\t\t\t\r\n\t\t\t\t\te.attribute('width', true).value = svg.opts['scaleWidth'];\r\n\t\t\t\t\te.attribute('height', true).value = svg.opts['scaleHeight'];\t\t\t\r\n\t\t\t\t\te.attribute('viewBox', true).value = '0 0 ' + (cWidth * xRatio) + ' ' + (cHeight * yRatio);\r\n\t\t\t\t\te.attribute('preserveAspectRatio', true).value = 'none';\r\n\t\t\t\t}\r\n\t\t\t\r\n\t\t\t\t// clear and render\r\n\t\t\t\tif (svg.opts['ignoreClear'] != true) {\r\n\t\t\t\t\tctx.clearRect(0, 0, cWidth, cHeight);\r\n\t\t\t\t}\r\n\t\t\t\te.render(ctx);\r\n\t\t\t\tif (isFirstRender) {\r\n\t\t\t\t\tisFirstRender = false;\r\n\t\t\t\t\tif (svg.opts != null && typeof(svg.opts['renderCallback']) == 'function') svg.opts['renderCallback']();\r\n\t\t\t\t}\t\t\t\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tvar waitingForImages = true;\r\n\t\t\tif (svg.ImagesLoaded()) {\r\n\t\t\t\twaitingForImages = false;\r\n\t\t\t\tdraw();\r\n\t\t\t}\r\n\t\t\tsvg.intervalID = setInterval(function() { \r\n\t\t\t\tvar needUpdate = false;\r\n\t\t\t\t\r\n\t\t\t\tif (waitingForImages && svg.ImagesLoaded()) {\r\n\t\t\t\t\twaitingForImages = false;\r\n\t\t\t\t\tneedUpdate = true;\r\n\t\t\t\t}\r\n\t\t\t\r\n\t\t\t\t// need update from mouse events?\r\n\t\t\t\tif (svg.opts['ignoreMouse'] != true) {\r\n\t\t\t\t\tneedUpdate = needUpdate | svg.Mouse.hasEvents();\r\n\t\t\t\t}\r\n\t\t\t\r\n\t\t\t\t// need update from animations?\r\n\t\t\t\tif (svg.opts['ignoreAnimation'] != true) {\r\n\t\t\t\t\tfor (var i=0; i<svg.Animations.length; i++) {\r\n\t\t\t\t\t\tneedUpdate = needUpdate | svg.Animations[i].update(1000 / svg.FRAMERATE);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// need update from redraw?\r\n\t\t\t\tif (svg.opts != null && typeof(svg.opts['forceRedraw']) == 'function') {\r\n\t\t\t\t\tif (svg.opts['forceRedraw']() == true) needUpdate = true;\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// render if needed\r\n\t\t\t\tif (needUpdate) {\r\n\t\t\t\t\tdraw();\t\t\t\t\r\n\t\t\t\t\tsvg.Mouse.runEvents(); // run and clear our events\r\n\t\t\t\t}\r\n\t\t\t}, 1000 / svg.FRAMERATE);\r\n\t\t}\r\n\t\t\r\n\t\tsvg.stop = function() {\r\n\t\t\tif (svg.intervalID) {\r\n\t\t\t\tclearInterval(svg.intervalID);\r\n\t\t\t}\r\n\t\t}\r\n\t\t\r\n\t\tsvg.Mouse = new (function() {\r\n\t\t\tthis.events = [];\r\n\t\t\tthis.hasEvents = function() { return this.events.length != 0; }\r\n\t\t\r\n\t\t\tthis.onclick = function(x, y) {\r\n\t\t\t\tthis.events.push({ type: 'onclick', x: x, y: y, \r\n\t\t\t\t\trun: function(e) { if (e.onclick) e.onclick(); }\r\n\t\t\t\t});\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.onmousemove = function(x, y) {\r\n\t\t\t\tthis.events.push({ type: 'onmousemove', x: x, y: y,\r\n\t\t\t\t\trun: function(e) { if (e.onmousemove) e.onmousemove(); }\r\n\t\t\t\t});\r\n\t\t\t}\t\t\t\r\n\t\t\t\r\n\t\t\tthis.eventElements = [];\r\n\t\t\t\r\n\t\t\tthis.checkPath = function(element, ctx) {\r\n\t\t\t\tfor (var i=0; i<this.events.length; i++) {\r\n\t\t\t\t\tvar e = this.events[i];\r\n\t\t\t\t\tif (ctx.isPointInPath && ctx.isPointInPath(e.x, e.y)) this.eventElements[i] = element;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.checkBoundingBox = function(element, bb) {\r\n\t\t\t\tfor (var i=0; i<this.events.length; i++) {\r\n\t\t\t\t\tvar e = this.events[i];\r\n\t\t\t\t\tif (bb.isPointInBox(e.x, e.y)) this.eventElements[i] = element;\r\n\t\t\t\t}\t\t\t\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\tthis.runEvents = function() {\r\n\t\t\t\tsvg.ctx.canvas.style.cursor = '';\r\n\t\t\t\t\r\n\t\t\t\tfor (var i=0; i<this.events.length; i++) {\r\n\t\t\t\t\tvar e = this.events[i];\r\n\t\t\t\t\tvar element = this.eventElements[i];\r\n\t\t\t\t\twhile (element) {\r\n\t\t\t\t\t\te.run(element);\r\n\t\t\t\t\t\telement = element.parent;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\t\t\r\n\t\t\t\r\n\t\t\t\t// done running, clear\r\n\t\t\t\tthis.events = []; \r\n\t\t\t\tthis.eventElements = [];\r\n\t\t\t}\r\n\t\t});\r\n\t\t\r\n\t\treturn svg;\r\n\t}\r\n})();\r\n\r\nif (CanvasRenderingContext2D) {\r\n\tCanvasRenderingContext2D.prototype.drawSvg = function(s, dx, dy, dw, dh) {\r\n\t\tcanvg(this.canvas, s, { \r\n\t\t\tignoreMouse: true, \r\n\t\t\tignoreAnimation: true, \r\n\t\t\tignoreDimensions: true, \r\n\t\t\tignoreClear: true, \r\n\t\t\toffsetX: dx, \r\n\t\t\toffsetY: dy, \r\n\t\t\tscaleWidth: dw, \r\n\t\t\tscaleHeight: dh\r\n\t\t});\r\n\t}\r\n}/**\r\n * @license Highcharts JS v3.0.6 (2013-10-04)\r\n * CanVGRenderer Extension module\r\n *\r\n * (c) 2011-2012 Torstein Hønsi, Erik Olsson\r\n *\r\n * License: www.highcharts.com/license\r\n */\r\n\r\n// JSLint options:\r\n/*global Highcharts */\r\n\r\n(function (Highcharts) { // encapsulate\r\n\tvar UNDEFINED,\r\n\t\tDIV = 'div',\r\n\t\tABSOLUTE = 'absolute',\r\n\t\tRELATIVE = 'relative',\r\n\t\tHIDDEN = 'hidden',\r\n\t\tVISIBLE = 'visible',\r\n\t\tPX = 'px',\r\n\t\tcss = Highcharts.css,\r\n\t\tCanVGRenderer = Highcharts.CanVGRenderer,\r\n\t\tSVGRenderer = Highcharts.SVGRenderer,\r\n\t\textend = Highcharts.extend,\r\n\t\tmerge = Highcharts.merge,\r\n\t\taddEvent = Highcharts.addEvent,\r\n\t\tcreateElement = Highcharts.createElement,\r\n\t\tdiscardElement = Highcharts.discardElement;\r\n\r\n\t// Extend CanVG renderer on demand, inherit from SVGRenderer\r\n\textend(CanVGRenderer.prototype, SVGRenderer.prototype);\r\n\r\n\t// Add additional functionality:\r\n\textend(CanVGRenderer.prototype, {\r\n\t\tcreate: function (chart, container, chartWidth, chartHeight) {\r\n\t\t\tthis.setContainer(container, chartWidth, chartHeight);\r\n\t\t\tthis.configure(chart);\r\n\t\t},\r\n\t\tsetContainer: function (container, chartWidth, chartHeight) {\r\n\t\t\tvar containerStyle = container.style,\r\n\t\t\t\tcontainerParent = container.parentNode,\r\n\t\t\t\tcontainerLeft = containerStyle.left,\r\n\t\t\t\tcontainerTop = containerStyle.top,\r\n\t\t\t\tcontainerOffsetWidth = container.offsetWidth,\r\n\t\t\t\tcontainerOffsetHeight = container.offsetHeight,\r\n\t\t\t\tcanvas,\r\n\t\t\t\tinitialHiddenStyle = { visibility: HIDDEN, position: ABSOLUTE };\r\n\r\n\t\t\tthis.init.apply(this, [container, chartWidth, chartHeight]);\r\n\r\n\t\t\t// add the canvas above it\r\n\t\t\tcanvas = createElement('canvas', {\r\n\t\t\t\twidth: containerOffsetWidth,\r\n\t\t\t\theight: containerOffsetHeight\r\n\t\t\t}, {\r\n\t\t\t\tposition: RELATIVE,\r\n\t\t\t\tleft: containerLeft,\r\n\t\t\t\ttop: containerTop\r\n\t\t\t}, container);\r\n\t\t\tthis.canvas = canvas;\r\n\r\n\t\t\t// Create the tooltip line and div, they are placed as siblings to\r\n\t\t\t// the container (and as direct childs to the div specified in the html page)\r\n\t\t\tthis.ttLine = createElement(DIV, null, initialHiddenStyle, containerParent);\r\n\t\t\tthis.ttDiv = createElement(DIV, null, initialHiddenStyle, containerParent);\r\n\t\t\tthis.ttTimer = UNDEFINED;\r\n\r\n\t\t\t// Move away the svg node to a new div inside the container's parent so we can hide it.\r\n\t\t\tvar hiddenSvg = createElement(DIV, {\r\n\t\t\t\twidth: containerOffsetWidth,\r\n\t\t\t\theight: containerOffsetHeight\r\n\t\t\t}, {\r\n\t\t\t\tvisibility: HIDDEN,\r\n\t\t\t\tleft: containerLeft,\r\n\t\t\t\ttop: containerTop\r\n\t\t\t}, containerParent);\r\n\t\t\tthis.hiddenSvg = hiddenSvg;\r\n\t\t\thiddenSvg.appendChild(this.box);\r\n\t\t},\r\n\r\n\t\t/**\r\n\t\t * Configures the renderer with the chart. Attach a listener to the event tooltipRefresh.\r\n\t\t **/\r\n\t\tconfigure: function (chart) {\r\n\t\t\tvar renderer = this,\r\n\t\t\t\toptions = chart.options.tooltip,\r\n\t\t\t\tborderWidth = options.borderWidth,\r\n\t\t\t\ttooltipDiv = renderer.ttDiv,\r\n\t\t\t\ttooltipDivStyle = options.style,\r\n\t\t\t\ttooltipLine = renderer.ttLine,\r\n\t\t\t\tpadding = parseInt(tooltipDivStyle.padding, 10);\r\n\r\n\t\t\t// Add border styling from options to the style\r\n\t\t\ttooltipDivStyle = merge(tooltipDivStyle, {\r\n\t\t\t\tpadding: padding + PX,\r\n\t\t\t\t'background-color': options.backgroundColor,\r\n\t\t\t\t'border-style': 'solid',\r\n\t\t\t\t'border-width': borderWidth + PX,\r\n\t\t\t\t'border-radius': options.borderRadius + PX\r\n\t\t\t});\r\n\r\n\t\t\t// Optionally add shadow\r\n\t\t\tif (options.shadow) {\r\n\t\t\t\ttooltipDivStyle = merge(tooltipDivStyle, {\r\n\t\t\t\t\t'box-shadow': '1px 1px 3px gray', // w3c\r\n\t\t\t\t\t'-webkit-box-shadow': '1px 1px 3px gray' // webkit\r\n\t\t\t\t});\r\n\t\t\t}\r\n\t\t\tcss(tooltipDiv, tooltipDivStyle);\r\n\r\n\t\t\t// Set simple style on the line\r\n\t\t\tcss(tooltipLine, {\r\n\t\t\t\t'border-left': '1px solid darkgray'\r\n\t\t\t});\r\n\r\n\t\t\t// This event is triggered when a new tooltip should be shown\r\n\t\t\taddEvent(chart, 'tooltipRefresh', function (args) {\r\n\t\t\t\tvar chartContainer = chart.container,\r\n\t\t\t\t\toffsetLeft = chartContainer.offsetLeft,\r\n\t\t\t\t\toffsetTop = chartContainer.offsetTop,\r\n\t\t\t\t\tposition;\r\n\r\n\t\t\t\t// Set the content of the tooltip\r\n\t\t\t\ttooltipDiv.innerHTML = args.text;\r\n\r\n\t\t\t\t// Compute the best position for the tooltip based on the divs size and container size.\r\n\t\t\t\tposition = chart.tooltip.getPosition(tooltipDiv.offsetWidth, tooltipDiv.offsetHeight, {plotX: args.x, plotY: args.y});\r\n\r\n\t\t\t\tcss(tooltipDiv, {\r\n\t\t\t\t\tvisibility: VISIBLE,\r\n\t\t\t\t\tleft: position.x + PX,\r\n\t\t\t\t\ttop: position.y + PX,\r\n\t\t\t\t\t'border-color': args.borderColor\r\n\t\t\t\t});\r\n\r\n\t\t\t\t// Position the tooltip line\r\n\t\t\t\tcss(tooltipLine, {\r\n\t\t\t\t\tvisibility: VISIBLE,\r\n\t\t\t\t\tleft: offsetLeft + args.x + PX,\r\n\t\t\t\t\ttop: offsetTop + chart.plotTop + PX,\r\n\t\t\t\t\theight: chart.plotHeight  + PX\r\n\t\t\t\t});\r\n\r\n\t\t\t\t// This timeout hides the tooltip after 3 seconds\r\n\t\t\t\t// First clear any existing timer\r\n\t\t\t\tif (renderer.ttTimer !== UNDEFINED) {\r\n\t\t\t\t\tclearTimeout(renderer.ttTimer);\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Start a new timer that hides tooltip and line\r\n\t\t\t\trenderer.ttTimer = setTimeout(function () {\r\n\t\t\t\t\tcss(tooltipDiv, { visibility: HIDDEN });\r\n\t\t\t\t\tcss(tooltipLine, { visibility: HIDDEN });\r\n\t\t\t\t}, 3000);\r\n\t\t\t});\r\n\t\t},\r\n\r\n\t\t/**\r\n\t\t * Extend SVGRenderer.destroy to also destroy the elements added by CanVGRenderer.\r\n\t\t */\r\n\t\tdestroy: function () {\r\n\t\t\tvar renderer = this;\r\n\r\n\t\t\t// Remove the canvas\r\n\t\t\tdiscardElement(renderer.canvas);\r\n\r\n\t\t\t// Kill the timer\r\n\t\t\tif (renderer.ttTimer !== UNDEFINED) {\r\n\t\t\t\tclearTimeout(renderer.ttTimer);\r\n\t\t\t}\r\n\r\n\t\t\t// Remove the divs for tooltip and line\r\n\t\t\tdiscardElement(renderer.ttLine);\r\n\t\t\tdiscardElement(renderer.ttDiv);\r\n\t\t\tdiscardElement(renderer.hiddenSvg);\r\n\r\n\t\t\t// Continue with base class\r\n\t\t\treturn SVGRenderer.prototype.destroy.apply(renderer);\r\n\t\t},\r\n\r\n\t\t/**\r\n\t\t * Take a color and return it if it's a string, do not make it a gradient even if it is a\r\n\t\t * gradient. Currently canvg cannot render gradients (turns out black),\r\n\t\t * see: http://code.google.com/p/canvg/issues/detail?id=104\r\n\t\t *\r\n\t\t * @param {Object} color The color or config object\r\n\t\t */\r\n\t\tcolor: function (color, elem, prop) {\r\n\t\t\tif (color && color.linearGradient) {\r\n\t\t\t\t// Pick the end color and forward to base implementation\r\n\t\t\t\tcolor = color.stops[color.stops.length - 1][1];\r\n\t\t\t}\r\n\t\t\treturn SVGRenderer.prototype.color.call(this, color, elem, prop);\r\n\t\t},\r\n\r\n\t\t/**\r\n\t\t * Draws the SVG on the canvas or adds a draw invokation to the deferred list.\r\n\t\t */\r\n\t\tdraw: function () {\r\n\t\t\tvar renderer = this;\r\n\t\t\twindow.canvg(renderer.canvas, renderer.hiddenSvg.innerHTML);\r\n\t\t}\r\n\t});\r\n}(Highcharts));\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/highcharts/modules/data.js",
    "content": "/*\r\n Data plugin for Highcharts\r\n\r\n (c) 2012-2013 Torstein Hønsi\r\n Last revision 2013-06-07\r\n\r\n License: www.highcharts.com/license\r\n*/\r\n(function(h){var k=h.each,m=function(b,a){this.init(b,a)};h.extend(m.prototype,{init:function(b,a){this.options=b;this.chartOptions=a;this.columns=b.columns||this.rowsToColumns(b.rows)||[];this.columns.length?this.dataFound():(this.parseCSV(),this.parseTable(),this.parseGoogleSpreadsheet())},getColumnDistribution:function(){var b=this.chartOptions,a=b&&b.chart&&b.chart.type,c=[];k(b&&b.series||[],function(b){c.push((h.seriesTypes[b.type||a||\"line\"].prototype.pointArrayMap||[0]).length)});this.valueCount=\r\n{global:(h.seriesTypes[a||\"line\"].prototype.pointArrayMap||[0]).length,individual:c}},dataFound:function(){this.parseTypes();this.findHeaderRow();this.parsed();this.complete()},parseCSV:function(){var b=this,a=this.options,c=a.csv,d=this.columns,f=a.startRow||0,i=a.endRow||Number.MAX_VALUE,j=a.startColumn||0,e=a.endColumn||Number.MAX_VALUE,g=0;c&&(c=c.replace(/\\r\\n/g,\"\\n\").replace(/\\r/g,\"\\n\").split(a.lineDelimiter||\"\\n\"),k(c,function(c,h){var n=b.trim(c),p=n.indexOf(\"#\")===0;h>=f&&h<=i&&!p&&n!==\"\"&&\r\n(n=c.split(a.itemDelimiter||\",\"),k(n,function(b,a){a>=j&&a<=e&&(d[a-j]||(d[a-j]=[]),d[a-j][g]=b)}),g+=1)}),this.dataFound())},parseTable:function(){var b=this.options,a=b.table,c=this.columns,d=b.startRow||0,f=b.endRow||Number.MAX_VALUE,i=b.startColumn||0,j=b.endColumn||Number.MAX_VALUE,e;a&&(typeof a===\"string\"&&(a=document.getElementById(a)),k(a.getElementsByTagName(\"tr\"),function(a,b){e=0;b>=d&&b<=f&&k(a.childNodes,function(a){if((a.tagName===\"TD\"||a.tagName===\"TH\")&&e>=i&&e<=j)c[e]||(c[e]=[]),\r\nc[e][b-d]=a.innerHTML,e+=1})}),this.dataFound())},parseGoogleSpreadsheet:function(){var b=this,a=this.options,c=a.googleSpreadsheetKey,d=this.columns,f=a.startRow||0,i=a.endRow||Number.MAX_VALUE,j=a.startColumn||0,e=a.endColumn||Number.MAX_VALUE,g,h;c&&jQuery.getJSON(\"https://spreadsheets.google.com/feeds/cells/\"+c+\"/\"+(a.googleSpreadsheetWorksheet||\"od6\")+\"/public/values?alt=json-in-script&callback=?\",function(a){var a=a.feed.entry,c,k=a.length,m=0,o=0,l;for(l=0;l<k;l++)c=a[l],m=Math.max(m,c.gs$cell.col),\r\no=Math.max(o,c.gs$cell.row);for(l=0;l<m;l++)if(l>=j&&l<=e)d[l-j]=[],d[l-j].length=Math.min(o,i-f);for(l=0;l<k;l++)if(c=a[l],g=c.gs$cell.row-1,h=c.gs$cell.col-1,h>=j&&h<=e&&g>=f&&g<=i)d[h-j][g-f]=c.content.$t;b.dataFound()})},findHeaderRow:function(){k(this.columns,function(){});this.headerRow=0},trim:function(b){return typeof b===\"string\"?b.replace(/^\\s+|\\s+$/g,\"\"):b},parseTypes:function(){for(var b=this.columns,a=b.length,c,d,f,i;a--;)for(c=b[a].length;c--;)d=b[a][c],f=parseFloat(d),i=this.trim(d),\r\ni==f?(b[a][c]=f,f>31536E6?b[a].isDatetime=!0:b[a].isNumeric=!0):(d=this.parseDate(d),a===0&&typeof d===\"number\"&&!isNaN(d)?(b[a][c]=d,b[a].isDatetime=!0):b[a][c]=i===\"\"?null:i)},dateFormats:{\"YYYY-mm-dd\":{regex:\"^([0-9]{4})-([0-9]{2})-([0-9]{2})$\",parser:function(b){return Date.UTC(+b[1],b[2]-1,+b[3])}}},parseDate:function(b){var a=this.options.parseDate,c,d,f;a&&(c=a(b));if(typeof b===\"string\")for(d in this.dateFormats)a=this.dateFormats[d],(f=b.match(a.regex))&&(c=a.parser(f));return c},rowsToColumns:function(b){var a,\r\nc,d,f,i;if(b){i=[];c=b.length;for(a=0;a<c;a++){f=b[a].length;for(d=0;d<f;d++)i[d]||(i[d]=[]),i[d][a]=b[a][d]}}return i},parsed:function(){this.options.parsed&&this.options.parsed.call(this,this.columns)},complete:function(){var b=this.columns,a,c,d=this.options,f,i,j,e,g,k;if(d.complete){this.getColumnDistribution();b.length>1&&(a=b.shift(),this.headerRow===0&&a.shift(),a.isDatetime?c=\"datetime\":a.isNumeric||(c=\"category\"));for(e=0;e<b.length;e++)if(this.headerRow===0)b[e].name=b[e].shift();i=[];\r\nfor(e=0,k=0;e<b.length;k++){f=h.pick(this.valueCount.individual[k],this.valueCount.global);j=[];for(g=0;g<b[e].length;g++)j[g]=[a[g],b[e][g]!==void 0?b[e][g]:null],f>1&&j[g].push(b[e+1][g]!==void 0?b[e+1][g]:null),f>2&&j[g].push(b[e+2][g]!==void 0?b[e+2][g]:null),f>3&&j[g].push(b[e+3][g]!==void 0?b[e+3][g]:null),f>4&&j[g].push(b[e+4][g]!==void 0?b[e+4][g]:null);i[k]={name:b[e].name,data:j};e+=f}d.complete({xAxis:{type:c},series:i})}}});h.Data=m;h.data=function(b,a){return new m(b,a)};h.wrap(h.Chart.prototype,\r\n\"init\",function(b,a,c){var d=this;a&&a.data?h.data(h.extend(a.data,{complete:function(f){a.series&&k(a.series,function(b,c){a.series[c]=h.merge(b,f.series[c])});a=h.merge(f,a);b.call(d,a,c)}}),a):b.call(d,a,c)})})(Highcharts);\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/highcharts/modules/data.src.js",
    "content": "/**\r\n * @license Data plugin for Highcharts\r\n *\r\n * (c) 2012-2013 Torstein Hønsi\r\n * Last revision 2013-06-07\r\n *\r\n * License: www.highcharts.com/license\r\n */\r\n\r\n/*\r\n * The Highcharts Data plugin is a utility to ease parsing of input sources like\r\n * CSV, HTML tables or grid views into basic configuration options for use \r\n * directly in the Highcharts constructor.\r\n *\r\n * Demo: http://jsfiddle.net/highcharts/SnLFj/\r\n *\r\n * --- OPTIONS ---\r\n *\r\n * - columns : Array<Array<Mixed>>\r\n * A two-dimensional array representing the input data on tabular form. This input can\r\n * be used when the data is already parsed, for example from a grid view component.\r\n * Each cell can be a string or number. If not switchRowsAndColumns is set, the columns\r\n * are interpreted as series. See also the rows option.\r\n *\r\n * - complete : Function(chartOptions)\r\n * The callback that is evaluated when the data is finished loading, optionally from an \r\n * external source, and parsed. The first argument passed is a finished chart options\r\n * object, containing series and an xAxis with categories if applicable. Thise options\r\n * can be extended with additional options and passed directly to the chart constructor.\r\n *\r\n * - csv : String\r\n * A comma delimited string to be parsed. Related options are startRow, endRow, startColumn\r\n * and endColumn to delimit what part of the table is used. The lineDelimiter and \r\n * itemDelimiter options define the CSV delimiter formats.\r\n * \r\n * - endColumn : Integer\r\n * In tabular input data, the first row (indexed by 0) to use. Defaults to the last \r\n * column containing data.\r\n *\r\n * - endRow : Integer\r\n * In tabular input data, the last row (indexed by 0) to use. Defaults to the last row\r\n * containing data.\r\n *\r\n * - googleSpreadsheetKey : String \r\n * A Google Spreadsheet key. See https://developers.google.com/gdata/samples/spreadsheet_sample\r\n * for general information on GS.\r\n *\r\n * - googleSpreadsheetWorksheet : String \r\n * The Google Spreadsheet worksheet. The available id's can be read from \r\n * https://spreadsheets.google.com/feeds/worksheets/{key}/public/basic\r\n *\r\n * - itemDelimiter : String\r\n * Item or cell delimiter for parsing CSV. Defaults to \",\".\r\n *\r\n * - lineDelimiter : String\r\n * Line delimiter for parsing CSV. Defaults to \"\\n\".\r\n *\r\n * - parsed : Function\r\n * A callback function to access the parsed columns, the two-dimentional input data\r\n * array directly, before they are interpreted into series data and categories.\r\n *\r\n * - parseDate : Function\r\n * A callback function to parse string representations of dates into JavaScript timestamps.\r\n * Return an integer on success.\r\n *\r\n * - rows : Array<Array<Mixed>>\r\n * The same as the columns input option, but defining rows intead of columns.\r\n *\r\n * - startColumn : Integer\r\n * In tabular input data, the first column (indexed by 0) to use. \r\n *\r\n * - startRow : Integer\r\n * In tabular input data, the first row (indexed by 0) to use.\r\n *\r\n * - table : String|HTMLElement\r\n * A HTML table or the id of such to be parsed as input data. Related options ara startRow,\r\n * endRow, startColumn and endColumn to delimit what part of the table is used.\r\n */\r\n\r\n// JSLint options:\r\n/*global jQuery */\r\n\r\n(function (Highcharts) {\t\r\n\t\r\n\t// Utilities\r\n\tvar each = Highcharts.each;\r\n\t\r\n\t\r\n\t// The Data constructor\r\n\tvar Data = function (dataOptions, chartOptions) {\r\n\t\tthis.init(dataOptions, chartOptions);\r\n\t};\r\n\t\r\n\t// Set the prototype properties\r\n\tHighcharts.extend(Data.prototype, {\r\n\t\t\r\n\t/**\r\n\t * Initialize the Data object with the given options\r\n\t */\r\n\tinit: function (options, chartOptions) {\r\n\t\tthis.options = options;\r\n\t\tthis.chartOptions = chartOptions;\r\n\t\tthis.columns = options.columns || this.rowsToColumns(options.rows) || [];\r\n\r\n\t\t// No need to parse or interpret anything\r\n\t\tif (this.columns.length) {\r\n\t\t\tthis.dataFound();\r\n\r\n\t\t// Parse and interpret\r\n\t\t} else {\r\n\r\n\t\t\t// Parse a CSV string if options.csv is given\r\n\t\t\tthis.parseCSV();\r\n\t\t\t\r\n\t\t\t// Parse a HTML table if options.table is given\r\n\t\t\tthis.parseTable();\r\n\r\n\t\t\t// Parse a Google Spreadsheet \r\n\t\t\tthis.parseGoogleSpreadsheet();\t\r\n\t\t}\r\n\r\n\t},\r\n\r\n\t/**\r\n\t * Get the column distribution. For example, a line series takes a single column for \r\n\t * Y values. A range series takes two columns for low and high values respectively,\r\n\t * and an OHLC series takes four columns.\r\n\t */\r\n\tgetColumnDistribution: function () {\r\n\t\tvar chartOptions = this.chartOptions,\r\n\t\t\tgetValueCount = function (type) {\r\n\t\t\t\treturn (Highcharts.seriesTypes[type || 'line'].prototype.pointArrayMap || [0]).length;\r\n\t\t\t},\r\n\t\t\tglobalType = chartOptions && chartOptions.chart && chartOptions.chart.type,\r\n\t\t\tindividualCounts = [];\r\n\r\n\t\teach((chartOptions && chartOptions.series) || [], function (series) {\r\n\t\t\tindividualCounts.push(getValueCount(series.type || globalType));\r\n\t\t});\r\n\r\n\t\tthis.valueCount = {\r\n\t\t\tglobal: getValueCount(globalType),\r\n\t\t\tindividual: individualCounts\r\n\t\t};\r\n\t},\r\n\r\n\r\n\tdataFound: function () {\r\n\t\t// Interpret the values into right types\r\n\t\tthis.parseTypes();\r\n\t\t\r\n\t\t// Use first row for series names?\r\n\t\tthis.findHeaderRow();\r\n\t\t\r\n\t\t// Handle columns if a handleColumns callback is given\r\n\t\tthis.parsed();\r\n\t\t\r\n\t\t// Complete if a complete callback is given\r\n\t\tthis.complete();\r\n\t\t\r\n\t},\r\n\t\r\n\t/**\r\n\t * Parse a CSV input string\r\n\t */\r\n\tparseCSV: function () {\r\n\t\tvar self = this,\r\n\t\t\toptions = this.options,\r\n\t\t\tcsv = options.csv,\r\n\t\t\tcolumns = this.columns,\r\n\t\t\tstartRow = options.startRow || 0,\r\n\t\t\tendRow = options.endRow || Number.MAX_VALUE,\r\n\t\t\tstartColumn = options.startColumn || 0,\r\n\t\t\tendColumn = options.endColumn || Number.MAX_VALUE,\r\n\t\t\tlines,\r\n\t\t\tactiveRowNo = 0;\r\n\t\t\t\r\n\t\tif (csv) {\r\n\t\t\t\r\n\t\t\tlines = csv\r\n\t\t\t\t.replace(/\\r\\n/g, \"\\n\") // Unix\r\n\t\t\t\t.replace(/\\r/g, \"\\n\") // Mac\r\n\t\t\t\t.split(options.lineDelimiter || \"\\n\");\r\n\t\t\t\r\n\t\t\teach(lines, function (line, rowNo) {\r\n\t\t\t\tvar trimmed = self.trim(line),\r\n\t\t\t\t\tisComment = trimmed.indexOf('#') === 0,\r\n\t\t\t\t\tisBlank = trimmed === '',\r\n\t\t\t\t\titems;\r\n\t\t\t\t\r\n\t\t\t\tif (rowNo >= startRow && rowNo <= endRow && !isComment && !isBlank) {\r\n\t\t\t\t\titems = line.split(options.itemDelimiter || ',');\r\n\t\t\t\t\teach(items, function (item, colNo) {\r\n\t\t\t\t\t\tif (colNo >= startColumn && colNo <= endColumn) {\r\n\t\t\t\t\t\t\tif (!columns[colNo - startColumn]) {\r\n\t\t\t\t\t\t\t\tcolumns[colNo - startColumn] = [];\t\t\t\t\t\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\tcolumns[colNo - startColumn][activeRowNo] = item;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t});\r\n\t\t\t\t\tactiveRowNo += 1;\r\n\t\t\t\t}\r\n\t\t\t});\r\n\r\n\t\t\tthis.dataFound();\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Parse a HTML table\r\n\t */\r\n\tparseTable: function () {\r\n\t\tvar options = this.options,\r\n\t\t\ttable = options.table,\r\n\t\t\tcolumns = this.columns,\r\n\t\t\tstartRow = options.startRow || 0,\r\n\t\t\tendRow = options.endRow || Number.MAX_VALUE,\r\n\t\t\tstartColumn = options.startColumn || 0,\r\n\t\t\tendColumn = options.endColumn || Number.MAX_VALUE,\r\n\t\t\tcolNo;\r\n\t\t\t\r\n\t\tif (table) {\r\n\t\t\t\r\n\t\t\tif (typeof table === 'string') {\r\n\t\t\t\ttable = document.getElementById(table);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\teach(table.getElementsByTagName('tr'), function (tr, rowNo) {\r\n\t\t\t\tcolNo = 0; \r\n\t\t\t\tif (rowNo >= startRow && rowNo <= endRow) {\r\n\t\t\t\t\teach(tr.childNodes, function (item) {\r\n\t\t\t\t\t\tif ((item.tagName === 'TD' || item.tagName === 'TH') && colNo >= startColumn && colNo <= endColumn) {\r\n\t\t\t\t\t\t\tif (!columns[colNo]) {\r\n\t\t\t\t\t\t\t\tcolumns[colNo] = [];\t\t\t\t\t\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\tcolumns[colNo][rowNo - startRow] = item.innerHTML;\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t\t\tcolNo += 1;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t});\r\n\t\t\t\t}\r\n\t\t\t});\r\n\r\n\t\t\tthis.dataFound(); // continue\r\n\t\t}\r\n\t},\r\n\r\n\t/**\r\n\t * TODO: \r\n\t * - switchRowsAndColumns\r\n\t */\r\n\tparseGoogleSpreadsheet: function () {\r\n\t\tvar self = this,\r\n\t\t\toptions = this.options,\r\n\t\t\tgoogleSpreadsheetKey = options.googleSpreadsheetKey,\r\n\t\t\tcolumns = this.columns,\r\n\t\t\tstartRow = options.startRow || 0,\r\n\t\t\tendRow = options.endRow || Number.MAX_VALUE,\r\n\t\t\tstartColumn = options.startColumn || 0,\r\n\t\t\tendColumn = options.endColumn || Number.MAX_VALUE,\r\n\t\t\tgr, // google row\r\n\t\t\tgc; // google column\r\n\r\n\t\tif (googleSpreadsheetKey) {\r\n\t\t\tjQuery.getJSON('https://spreadsheets.google.com/feeds/cells/' + \r\n\t\t\t\t  googleSpreadsheetKey + '/' + (options.googleSpreadsheetWorksheet || 'od6') +\r\n\t\t\t\t\t  '/public/values?alt=json-in-script&callback=?',\r\n\t\t\t\t\t  function (json) {\r\n\t\t\t\t\t\r\n\t\t\t\t// Prepare the data from the spreadsheat\r\n\t\t\t\tvar cells = json.feed.entry,\r\n\t\t\t\t\tcell,\r\n\t\t\t\t\tcellCount = cells.length,\r\n\t\t\t\t\tcolCount = 0,\r\n\t\t\t\t\trowCount = 0,\r\n\t\t\t\t\ti;\r\n\t\t\t\r\n\t\t\t\t// First, find the total number of columns and rows that \r\n\t\t\t\t// are actually filled with data\r\n\t\t\t\tfor (i = 0; i < cellCount; i++) {\r\n\t\t\t\t\tcell = cells[i];\r\n\t\t\t\t\tcolCount = Math.max(colCount, cell.gs$cell.col);\r\n\t\t\t\t\trowCount = Math.max(rowCount, cell.gs$cell.row);\t\t\t\r\n\t\t\t\t}\r\n\t\t\t\r\n\t\t\t\t// Set up arrays containing the column data\r\n\t\t\t\tfor (i = 0; i < colCount; i++) {\r\n\t\t\t\t\tif (i >= startColumn && i <= endColumn) {\r\n\t\t\t\t\t\t// Create new columns with the length of either end-start or rowCount\r\n\t\t\t\t\t\tcolumns[i - startColumn] = [];\r\n\r\n\t\t\t\t\t\t// Setting the length to avoid jslint warning\r\n\t\t\t\t\t\tcolumns[i - startColumn].length = Math.min(rowCount, endRow - startRow);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t// Loop over the cells and assign the value to the right\r\n\t\t\t\t// place in the column arrays\r\n\t\t\t\tfor (i = 0; i < cellCount; i++) {\r\n\t\t\t\t\tcell = cells[i];\r\n\t\t\t\t\tgr = cell.gs$cell.row - 1; // rows start at 1\r\n\t\t\t\t\tgc = cell.gs$cell.col - 1; // columns start at 1\r\n\r\n\t\t\t\t\t// If both row and col falls inside start and end\r\n\t\t\t\t\t// set the transposed cell value in the newly created columns\r\n\t\t\t\t\tif (gc >= startColumn && gc <= endColumn &&\r\n\t\t\t\t\t\tgr >= startRow && gr <= endRow) {\r\n\t\t\t\t\t\tcolumns[gc - startColumn][gr - startRow] = cell.content.$t;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tself.dataFound();\r\n\t\t\t});\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * Find the header row. For now, we just check whether the first row contains\r\n\t * numbers or strings. Later we could loop down and find the first row with \r\n\t * numbers.\r\n\t */\r\n\tfindHeaderRow: function () {\r\n\t\tvar headerRow = 0;\r\n\t\teach(this.columns, function (column) {\r\n\t\t\tif (typeof column[0] !== 'string') {\r\n\t\t\t\theaderRow = null;\r\n\t\t\t}\r\n\t\t});\r\n\t\tthis.headerRow = 0;\t\t\t\r\n\t},\r\n\t\r\n\t/**\r\n\t * Trim a string from whitespace\r\n\t */\r\n\ttrim: function (str) {\r\n\t\treturn typeof str === 'string' ? str.replace(/^\\s+|\\s+$/g, '') : str;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Parse numeric cells in to number types and date types in to true dates.\r\n\t * @param {Object} columns\r\n\t */\r\n\tparseTypes: function () {\r\n\t\tvar columns = this.columns,\r\n\t\t\tcol = columns.length, \r\n\t\t\trow,\r\n\t\t\tval,\r\n\t\t\tfloatVal,\r\n\t\t\ttrimVal,\r\n\t\t\tdateVal;\r\n\t\t\t\r\n\t\twhile (col--) {\r\n\t\t\trow = columns[col].length;\r\n\t\t\twhile (row--) {\r\n\t\t\t\tval = columns[col][row];\r\n\t\t\t\tfloatVal = parseFloat(val);\r\n\t\t\t\ttrimVal = this.trim(val);\r\n\r\n\t\t\t\t/*jslint eqeq: true*/\r\n\t\t\t\tif (trimVal == floatVal) { // is numeric\r\n\t\t\t\t/*jslint eqeq: false*/\r\n\t\t\t\t\tcolumns[col][row] = floatVal;\r\n\t\t\t\t\t\r\n\t\t\t\t\t// If the number is greater than milliseconds in a year, assume datetime\r\n\t\t\t\t\tif (floatVal > 365 * 24 * 3600 * 1000) {\r\n\t\t\t\t\t\tcolumns[col].isDatetime = true;\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tcolumns[col].isNumeric = true;\r\n\t\t\t\t\t}\t\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t} else { // string, continue to determine if it is a date string or really a string\r\n\t\t\t\t\tdateVal = this.parseDate(val);\r\n\t\t\t\t\t\r\n\t\t\t\t\tif (col === 0 && typeof dateVal === 'number' && !isNaN(dateVal)) { // is date\r\n\t\t\t\t\t\tcolumns[col][row] = dateVal;\r\n\t\t\t\t\t\tcolumns[col].isDatetime = true;\r\n\t\t\t\t\t\r\n\t\t\t\t\t} else { // string\r\n\t\t\t\t\t\tcolumns[col][row] = trimVal === '' ? null : trimVal;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\t//*\r\n\tdateFormats: {\r\n\t\t'YYYY-mm-dd': {\r\n\t\t\tregex: '^([0-9]{4})-([0-9]{2})-([0-9]{2})$',\r\n\t\t\tparser: function (match) {\r\n\t\t\t\treturn Date.UTC(+match[1], match[2] - 1, +match[3]);\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\t// */\r\n\t/**\r\n\t * Parse a date and return it as a number. Overridable through options.parseDate.\r\n\t */\r\n\tparseDate: function (val) {\r\n\t\tvar parseDate = this.options.parseDate,\r\n\t\t\tret,\r\n\t\t\tkey,\r\n\t\t\tformat,\r\n\t\t\tmatch;\r\n\r\n\t\tif (parseDate) {\r\n\t\t\tret = parseDate(val);\r\n\t\t}\r\n\t\t\t\r\n\t\tif (typeof val === 'string') {\r\n\t\t\tfor (key in this.dateFormats) {\r\n\t\t\t\tformat = this.dateFormats[key];\r\n\t\t\t\tmatch = val.match(format.regex);\r\n\t\t\t\tif (match) {\r\n\t\t\t\t\tret = format.parser(match);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn ret;\r\n\t},\r\n\t\r\n\t/**\r\n\t * Reorganize rows into columns\r\n\t */\r\n\trowsToColumns: function (rows) {\r\n\t\tvar row,\r\n\t\t\trowsLength,\r\n\t\t\tcol,\r\n\t\t\tcolsLength,\r\n\t\t\tcolumns;\r\n\r\n\t\tif (rows) {\r\n\t\t\tcolumns = [];\r\n\t\t\trowsLength = rows.length;\r\n\t\t\tfor (row = 0; row < rowsLength; row++) {\r\n\t\t\t\tcolsLength = rows[row].length;\r\n\t\t\t\tfor (col = 0; col < colsLength; col++) {\r\n\t\t\t\t\tif (!columns[col]) {\r\n\t\t\t\t\t\tcolumns[col] = [];\r\n\t\t\t\t\t}\r\n\t\t\t\t\tcolumns[col][row] = rows[row][col];\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn columns;\r\n\t},\r\n\t\r\n\t/**\r\n\t * A hook for working directly on the parsed columns\r\n\t */\r\n\tparsed: function () {\r\n\t\tif (this.options.parsed) {\r\n\t\t\tthis.options.parsed.call(this, this.columns);\r\n\t\t}\r\n\t},\r\n\t\r\n\t/**\r\n\t * If a complete callback function is provided in the options, interpret the \r\n\t * columns into a Highcharts options object.\r\n\t */\r\n\tcomplete: function () {\r\n\t\t\r\n\t\tvar columns = this.columns,\r\n\t\t\tfirstCol,\r\n\t\t\ttype,\r\n\t\t\toptions = this.options,\r\n\t\t\tvalueCount,\r\n\t\t\tseries,\r\n\t\t\tdata,\r\n\t\t\ti,\r\n\t\t\tj,\r\n\t\t\tseriesIndex;\r\n\t\t\t\r\n\t\t\r\n\t\tif (options.complete) {\r\n\r\n\t\t\tthis.getColumnDistribution();\r\n\t\t\t\r\n\t\t\t// Use first column for X data or categories?\r\n\t\t\tif (columns.length > 1) {\r\n\t\t\t\tfirstCol = columns.shift();\r\n\t\t\t\tif (this.headerRow === 0) {\r\n\t\t\t\t\tfirstCol.shift(); // remove the first cell\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\tif (firstCol.isDatetime) {\r\n\t\t\t\t\ttype = 'datetime';\r\n\t\t\t\t} else if (!firstCol.isNumeric) {\r\n\t\t\t\t\ttype = 'category';\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// Get the names and shift the top row\r\n\t\t\tfor (i = 0; i < columns.length; i++) {\r\n\t\t\t\tif (this.headerRow === 0) {\r\n\t\t\t\t\tcolumns[i].name = columns[i].shift();\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// Use the next columns for series\r\n\t\t\tseries = [];\r\n\t\t\tfor (i = 0, seriesIndex = 0; i < columns.length; seriesIndex++) {\r\n\r\n\t\t\t\t// This series' value count\r\n\t\t\t\tvalueCount = Highcharts.pick(this.valueCount.individual[seriesIndex], this.valueCount.global);\r\n\t\t\t\t\r\n\t\t\t\t// Iterate down the cells of each column and add data to the series\r\n\t\t\t\tdata = [];\r\n\t\t\t\tfor (j = 0; j < columns[i].length; j++) {\r\n\t\t\t\t\tdata[j] = [\r\n\t\t\t\t\t\tfirstCol[j], \r\n\t\t\t\t\t\tcolumns[i][j] !== undefined ? columns[i][j] : null\r\n\t\t\t\t\t];\r\n\t\t\t\t\tif (valueCount > 1) {\r\n\t\t\t\t\t\tdata[j].push(columns[i + 1][j] !== undefined ? columns[i + 1][j] : null);\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (valueCount > 2) {\r\n\t\t\t\t\t\tdata[j].push(columns[i + 2][j] !== undefined ? columns[i + 2][j] : null);\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (valueCount > 3) {\r\n\t\t\t\t\t\tdata[j].push(columns[i + 3][j] !== undefined ? columns[i + 3][j] : null);\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (valueCount > 4) {\r\n\t\t\t\t\t\tdata[j].push(columns[i + 4][j] !== undefined ? columns[i + 4][j] : null);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Add the series\r\n\t\t\t\tseries[seriesIndex] = {\r\n\t\t\t\t\tname: columns[i].name,\r\n\t\t\t\t\tdata: data\r\n\t\t\t\t};\r\n\r\n\t\t\t\ti += valueCount;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\t// Do the callback\r\n\t\t\toptions.complete({\r\n\t\t\t\txAxis: {\r\n\t\t\t\t\ttype: type\r\n\t\t\t\t},\r\n\t\t\t\tseries: series\r\n\t\t\t});\r\n\t\t}\r\n\t}\r\n\t});\r\n\t\r\n\t// Register the Data prototype and data function on Highcharts\r\n\tHighcharts.Data = Data;\r\n\tHighcharts.data = function (options, chartOptions) {\r\n\t\treturn new Data(options, chartOptions);\r\n\t};\r\n\r\n\t// Extend Chart.init so that the Chart constructor accepts a new configuration\r\n\t// option group, data.\r\n\tHighcharts.wrap(Highcharts.Chart.prototype, 'init', function (proceed, userOptions, callback) {\r\n\t\tvar chart = this;\r\n\r\n\t\tif (userOptions && userOptions.data) {\r\n\t\t\tHighcharts.data(Highcharts.extend(userOptions.data, {\r\n\t\t\t\tcomplete: function (dataOptions) {\r\n\t\t\t\t\t\r\n\t\t\t\t\t// Merge series configs\r\n\t\t\t\t\tif (userOptions.series) {\r\n\t\t\t\t\t\teach(userOptions.series, function (series, i) {\r\n\t\t\t\t\t\t\tuserOptions.series[i] = Highcharts.merge(series, dataOptions.series[i]);\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t// Do the merge\r\n\t\t\t\t\tuserOptions = Highcharts.merge(dataOptions, userOptions);\r\n\r\n\t\t\t\t\tproceed.call(chart, userOptions, callback);\r\n\t\t\t\t}\r\n\t\t\t}), userOptions);\r\n\t\t} else {\r\n\t\t\tproceed.call(chart, userOptions, callback);\r\n\t\t}\r\n\t});\r\n\r\n}(Highcharts));\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/highcharts/modules/drilldown.js",
    "content": "(function(e){function q(b,a,c){return\"rgba(\"+[Math.round(b[0]+(a[0]-b[0])*c),Math.round(b[1]+(a[1]-b[1])*c),Math.round(b[2]+(a[2]-b[2])*c),b[3]+(a[3]-b[3])*c].join(\",\")+\")\"}var m=function(){},j=e.getOptions(),g=e.each,n=e.extend,o=e.wrap,h=e.Chart,i=e.seriesTypes,k=i.pie,l=i.column,r=HighchartsAdapter.fireEvent;n(j.lang,{drillUpText:\"◁ Back to {series.name}\"});j.drilldown={activeAxisLabelStyle:{cursor:\"pointer\",color:\"#039\",fontWeight:\"bold\",textDecoration:\"underline\"},activeDataLabelStyle:{cursor:\"pointer\",\r\ncolor:\"#039\",fontWeight:\"bold\",textDecoration:\"underline\"},animation:{duration:500},drillUpButton:{position:{align:\"right\",x:-10,y:10}}};e.SVGRenderer.prototype.Element.prototype.fadeIn=function(){this.attr({opacity:0.1,visibility:\"visible\"}).animate({opacity:1},{duration:250})};h.prototype.drilldownLevels=[];h.prototype.addSeriesAsDrilldown=function(b,a){var c=b.series,d=c.xAxis,f=c.yAxis,e;e=b.color||c.color;var g,a=n({color:e},a);g=HighchartsAdapter.inArray(this,c.points);this.drilldownLevels.push({seriesOptions:c.userOptions,\r\nshapeArgs:b.shapeArgs,bBox:b.graphic.getBBox(),color:e,newSeries:a,pointOptions:c.options.data[g],pointIndex:g,oldExtremes:{xMin:d&&d.userMin,xMax:d&&d.userMax,yMin:f&&f.userMin,yMax:f&&f.userMax}});e=this.addSeries(a,!1);if(d)d.oldPos=d.pos,d.userMin=d.userMax=null,f.userMin=f.userMax=null;if(c.type===e.type)e.animate=e.animateDrilldown||m,e.options.animation=!0;c.remove(!1);this.redraw();this.showDrillUpButton()};h.prototype.getDrilldownBackText=function(){return this.options.lang.drillUpText.replace(\"{series.name}\",\r\nthis.drilldownLevels[this.drilldownLevels.length-1].seriesOptions.name)};h.prototype.showDrillUpButton=function(){var b=this,a=this.getDrilldownBackText(),c=b.options.drilldown.drillUpButton;this.drillUpButton?this.drillUpButton.attr({text:a}).align():this.drillUpButton=this.renderer.button(a,null,null,function(){b.drillUp()}).attr(n({align:c.position.align,zIndex:9},c.theme)).add().align(c.position,!1,c.relativeTo||\"plotBox\")};h.prototype.drillUp=function(){var b=this.drilldownLevels.pop(),a=this.series[0],\r\nc=b.oldExtremes,d=this.addSeries(b.seriesOptions,!1);r(this,\"drillup\",{seriesOptions:b.seriesOptions});if(d.type===a.type)d.drilldownLevel=b,d.animate=d.animateDrillupTo||m,d.options.animation=!0,a.animateDrillupFrom&&a.animateDrillupFrom(b);a.remove(!1);d.xAxis&&(d.xAxis.setExtremes(c.xMin,c.xMax,!1),d.yAxis.setExtremes(c.yMin,c.yMax,!1));this.redraw();this.drilldownLevels.length===0?this.drillUpButton=this.drillUpButton.destroy():this.drillUpButton.attr({text:this.getDrilldownBackText()}).align()};\r\nk.prototype.animateDrilldown=function(b){var a=this.chart.drilldownLevels[this.chart.drilldownLevels.length-1],c=this.chart.options.drilldown.animation,d=a.shapeArgs,f=d.start,s=(d.end-f)/this.points.length,h=e.Color(a.color).rgba;b||g(this.points,function(a,b){var g=e.Color(a.color).rgba;a.graphic.attr(e.merge(d,{start:f+b*s,end:f+(b+1)*s})).animate(a.shapeArgs,e.merge(c,{step:function(a,d){d.prop===\"start\"&&this.attr({fill:q(h,g,d.pos)})}}))})};k.prototype.animateDrillupTo=l.prototype.animateDrillupTo=\r\nfunction(b){if(!b){var a=this,c=a.drilldownLevel;g(this.points,function(a){a.graphic.hide();a.dataLabel&&a.dataLabel.hide();a.connector&&a.connector.hide()});setTimeout(function(){g(a.points,function(a,b){var e=b===c.pointIndex?\"show\":\"fadeIn\";a.graphic[e]();if(a.dataLabel)a.dataLabel[e]();if(a.connector)a.connector[e]()})},Math.max(this.chart.options.drilldown.animation.duration-50,0));this.animate=m}};l.prototype.animateDrilldown=function(b){var a=this.chart.drilldownLevels[this.chart.drilldownLevels.length-\r\n1].shapeArgs,c=this.chart.options.drilldown.animation;b||(a.x+=this.xAxis.oldPos-this.xAxis.pos,g(this.points,function(b){b.graphic.attr(a).animate(b.shapeArgs,c)}))};l.prototype.animateDrillupFrom=k.prototype.animateDrillupFrom=function(b){var a=this.chart.options.drilldown.animation,c=this.group;delete this.group;g(this.points,function(d){var f=d.graphic,g=e.Color(d.color).rgba;delete d.graphic;f.animate(b.shapeArgs,e.merge(a,{step:function(a,c){c.prop===\"start\"&&this.attr({fill:q(g,e.Color(b.color).rgba,\r\nc.pos)})},complete:function(){f.destroy();c&&(c=c.destroy())}}))})};e.Point.prototype.doDrilldown=function(){for(var b=this.series.chart,a=b.options.drilldown,c=a.series.length,d;c--&&!d;)a.series[c].id===this.drilldown&&(d=a.series[c]);r(b,\"drilldown\",{point:this,seriesOptions:d});d&&b.addSeriesAsDrilldown(this,d)};o(e.Point.prototype,\"init\",function(b,a,c,d){var f=b.call(this,a,c,d),b=a.chart,a=(a=a.xAxis&&a.xAxis.ticks[d])&&a.label;if(f.drilldown){if(e.addEvent(f,\"click\",function(){f.doDrilldown()}),\r\na){if(!a._basicStyle)a._basicStyle=a.element.getAttribute(\"style\");a.addClass(\"highcharts-drilldown-axis-label\").css(b.options.drilldown.activeAxisLabelStyle).on(\"click\",function(){f.doDrilldown&&f.doDrilldown()})}}else a&&a._basicStyle&&a.element.setAttribute(\"style\",a._basicStyle);return f});o(e.Series.prototype,\"drawDataLabels\",function(b){var a=this.chart.options.drilldown.activeDataLabelStyle;b.call(this);g(this.points,function(b){if(b.drilldown&&b.dataLabel)b.dataLabel.attr({\"class\":\"highcharts-drilldown-data-label\"}).css(a).on(\"click\",\r\nfunction(){b.doDrilldown()})})});l.prototype.supportsDrilldown=!0;k.prototype.supportsDrilldown=!0;var p,j=function(b){b.call(this);g(this.points,function(a){a.drilldown&&a.graphic&&a.graphic.attr({\"class\":\"highcharts-drilldown-point\"}).css({cursor:\"pointer\"})})};for(p in i)i[p].prototype.supportsDrilldown&&o(i[p].prototype,\"drawTracker\",j)})(Highcharts);\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/highcharts/modules/drilldown.src.js",
    "content": "/**\r\n * Highcharts Drilldown plugin\r\n * \r\n * Author: Torstein Honsi\r\n * Last revision: 2013-02-18\r\n * License: MIT License\r\n *\r\n * Demo: http://jsfiddle.net/highcharts/Vf3yT/\r\n */\r\n\r\n/*global HighchartsAdapter*/\r\n(function (H) {\r\n\r\n\t\"use strict\";\r\n\r\n\tvar noop = function () {},\r\n\t\tdefaultOptions = H.getOptions(),\r\n\t\teach = H.each,\r\n\t\textend = H.extend,\r\n\t\twrap = H.wrap,\r\n\t\tChart = H.Chart,\r\n\t\tseriesTypes = H.seriesTypes,\r\n\t\tPieSeries = seriesTypes.pie,\r\n\t\tColumnSeries = seriesTypes.column,\r\n\t\tfireEvent = HighchartsAdapter.fireEvent;\r\n\r\n\t// Utilities\r\n\tfunction tweenColors(startColor, endColor, pos) {\r\n\t\tvar rgba = [\r\n\t\t\t\tMath.round(startColor[0] + (endColor[0] - startColor[0]) * pos),\r\n\t\t\t\tMath.round(startColor[1] + (endColor[1] - startColor[1]) * pos),\r\n\t\t\t\tMath.round(startColor[2] + (endColor[2] - startColor[2]) * pos),\r\n\t\t\t\tstartColor[3] + (endColor[3] - startColor[3]) * pos\r\n\t\t\t];\r\n\t\treturn 'rgba(' + rgba.join(',') + ')';\r\n\t}\r\n\r\n\t// Add language\r\n\textend(defaultOptions.lang, {\r\n\t\tdrillUpText: '◁ Back to {series.name}'\r\n\t});\r\n\tdefaultOptions.drilldown = {\r\n\t\tactiveAxisLabelStyle: {\r\n\t\t\tcursor: 'pointer',\r\n\t\t\tcolor: '#039',\r\n\t\t\tfontWeight: 'bold',\r\n\t\t\ttextDecoration: 'underline'\t\t\t\r\n\t\t},\r\n\t\tactiveDataLabelStyle: {\r\n\t\t\tcursor: 'pointer',\r\n\t\t\tcolor: '#039',\r\n\t\t\tfontWeight: 'bold',\r\n\t\t\ttextDecoration: 'underline'\t\t\t\r\n\t\t},\r\n\t\tanimation: {\r\n\t\t\tduration: 500\r\n\t\t},\r\n\t\tdrillUpButton: {\r\n\t\t\tposition: { \r\n\t\t\t\talign: 'right',\r\n\t\t\t\tx: -10,\r\n\t\t\t\ty: 10\r\n\t\t\t}\r\n\t\t\t// relativeTo: 'plotBox'\r\n\t\t\t// theme\r\n\t\t}\r\n\t};\t\r\n\r\n\t/**\r\n\t * A general fadeIn method\r\n\t */\r\n\tH.SVGRenderer.prototype.Element.prototype.fadeIn = function () {\r\n\t\tthis\r\n\t\t.attr({\r\n\t\t\topacity: 0.1,\r\n\t\t\tvisibility: 'visible'\r\n\t\t})\r\n\t\t.animate({\r\n\t\t\topacity: 1\r\n\t\t}, {\r\n\t\t\tduration: 250\r\n\t\t});\r\n\t};\r\n\r\n\t// Extend the Chart prototype\r\n\tChart.prototype.drilldownLevels = [];\r\n\r\n\tChart.prototype.addSeriesAsDrilldown = function (point, ddOptions) {\r\n\t\tvar oldSeries = point.series,\r\n\t\t\txAxis = oldSeries.xAxis,\r\n\t\t\tyAxis = oldSeries.yAxis,\r\n\t\t\tnewSeries,\r\n\t\t\tcolor = point.color || oldSeries.color,\r\n\t\t\tpointIndex,\r\n\t\t\tlevel;\r\n\t\t\t\r\n\t\tddOptions = extend({\r\n\t\t\tcolor: color\r\n\t\t}, ddOptions);\r\n\t\tpointIndex = HighchartsAdapter.inArray(this, oldSeries.points);\r\n\t\tlevel = {\r\n\t\t\tseriesOptions: oldSeries.userOptions,\r\n\t\t\tshapeArgs: point.shapeArgs,\r\n\t\t\tbBox: point.graphic.getBBox(),\r\n\t\t\tcolor: color,\r\n\t\t\tnewSeries: ddOptions,\r\n\t\t\tpointOptions: oldSeries.options.data[pointIndex],\r\n\t\t\tpointIndex: pointIndex,\r\n\t\t\toldExtremes: {\r\n\t\t\t\txMin: xAxis && xAxis.userMin,\r\n\t\t\t\txMax: xAxis && xAxis.userMax,\r\n\t\t\t\tyMin: yAxis && yAxis.userMin,\r\n\t\t\t\tyMax: yAxis && yAxis.userMax\r\n\t\t\t}\r\n\t\t};\r\n\r\n\t\tthis.drilldownLevels.push(level);\r\n\r\n\t\tnewSeries = this.addSeries(ddOptions, false);\r\n\t\tif (xAxis) {\r\n\t\t\txAxis.oldPos = xAxis.pos;\r\n\t\t\txAxis.userMin = xAxis.userMax = null;\r\n\t\t\tyAxis.userMin = yAxis.userMax = null;\r\n\t\t}\r\n\r\n\t\t// Run fancy cross-animation on supported and equal types\r\n\t\tif (oldSeries.type === newSeries.type) {\r\n\t\t\tnewSeries.animate = newSeries.animateDrilldown || noop;\r\n\t\t\tnewSeries.options.animation = true;\r\n\t\t}\r\n\t\t\r\n\t\toldSeries.remove(false);\r\n\t\t\r\n\t\tthis.redraw();\r\n\t\tthis.showDrillUpButton();\r\n\t};\r\n\r\n\tChart.prototype.getDrilldownBackText = function () {\r\n\t\tvar lastLevel = this.drilldownLevels[this.drilldownLevels.length - 1];\r\n\r\n\t\treturn this.options.lang.drillUpText.replace('{series.name}', lastLevel.seriesOptions.name);\r\n\r\n\t};\r\n\r\n\tChart.prototype.showDrillUpButton = function () {\r\n\t\tvar chart = this,\r\n\t\t\tbackText = this.getDrilldownBackText(),\r\n\t\t\tbuttonOptions = chart.options.drilldown.drillUpButton;\r\n\t\t\t\r\n\r\n\t\tif (!this.drillUpButton) {\r\n\t\t\tthis.drillUpButton = this.renderer.button(\r\n\t\t\t\tbackText,\r\n\t\t\t\tnull,\r\n\t\t\t\tnull,\r\n\t\t\t\tfunction () {\r\n\t\t\t\t\tchart.drillUp(); \r\n\t\t\t\t}\r\n\t\t\t)\r\n\t\t\t.attr(extend({\r\n\t\t\t\talign: buttonOptions.position.align,\r\n\t\t\t\tzIndex: 9\r\n\t\t\t}, buttonOptions.theme))\r\n\t\t\t.add()\r\n\t\t\t.align(buttonOptions.position, false, buttonOptions.relativeTo || 'plotBox');\r\n\t\t} else {\r\n\t\t\tthis.drillUpButton.attr({\r\n\t\t\t\ttext: backText\r\n\t\t\t})\r\n\t\t\t.align();\r\n\t\t}\r\n\t};\r\n\r\n\tChart.prototype.drillUp = function () {\r\n\t\tvar chart = this,\r\n\t\t\tlevel = chart.drilldownLevels.pop(),\r\n\t\t\toldSeries = chart.series[0],\r\n\t\t\toldExtremes = level.oldExtremes,\r\n\t\t\tnewSeries = chart.addSeries(level.seriesOptions, false);\r\n\t\t\r\n\t\tfireEvent(chart, 'drillup', { seriesOptions: level.seriesOptions });\r\n\r\n\t\tif (newSeries.type === oldSeries.type) {\r\n\t\t\tnewSeries.drilldownLevel = level;\r\n\t\t\tnewSeries.animate = newSeries.animateDrillupTo || noop;\r\n\t\t\tnewSeries.options.animation = true;\r\n\r\n\t\t\tif (oldSeries.animateDrillupFrom) {\r\n\t\t\t\toldSeries.animateDrillupFrom(level);\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\toldSeries.remove(false);\r\n\r\n\t\t// Reset the zoom level of the upper series\r\n\t\tif (newSeries.xAxis) {\r\n\t\t\tnewSeries.xAxis.setExtremes(oldExtremes.xMin, oldExtremes.xMax, false);\r\n\t\t\tnewSeries.yAxis.setExtremes(oldExtremes.yMin, oldExtremes.yMax, false);\r\n\t\t}\r\n\r\n\r\n\t\tthis.redraw();\r\n\r\n\t\tif (this.drilldownLevels.length === 0) {\r\n\t\t\tthis.drillUpButton = this.drillUpButton.destroy();\r\n\t\t} else {\r\n\t\t\tthis.drillUpButton.attr({\r\n\t\t\t\ttext: this.getDrilldownBackText()\r\n\t\t\t})\r\n\t\t\t.align();\r\n\t\t}\r\n\t};\r\n\r\n\tPieSeries.prototype.animateDrilldown = function (init) {\r\n\t\tvar level = this.chart.drilldownLevels[this.chart.drilldownLevels.length - 1],\r\n\t\t\tanimationOptions = this.chart.options.drilldown.animation,\r\n\t\t\tanimateFrom = level.shapeArgs,\r\n\t\t\tstart = animateFrom.start,\r\n\t\t\tangle = animateFrom.end - start,\r\n\t\t\tstartAngle = angle / this.points.length,\r\n\t\t\tstartColor = H.Color(level.color).rgba;\r\n\r\n\t\tif (!init) {\r\n\t\t\teach(this.points, function (point, i) {\r\n\t\t\t\tvar endColor = H.Color(point.color).rgba;\r\n\r\n\t\t\t\t/*jslint unparam: true*/\r\n\t\t\t\tpoint.graphic\r\n\t\t\t\t\t.attr(H.merge(animateFrom, {\r\n\t\t\t\t\t\tstart: start + i * startAngle,\r\n\t\t\t\t\t\tend: start + (i + 1) * startAngle\r\n\t\t\t\t\t}))\r\n\t\t\t\t\t.animate(point.shapeArgs, H.merge(animationOptions, {\r\n\t\t\t\t\t\tstep: function (val, fx) {\r\n\t\t\t\t\t\t\tif (fx.prop === 'start') {\r\n\t\t\t\t\t\t\t\tthis.attr({\r\n\t\t\t\t\t\t\t\t\tfill: tweenColors(startColor, endColor, fx.pos)\r\n\t\t\t\t\t\t\t\t});\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}));\r\n\t\t\t\t/*jslint unparam: false*/\r\n\t\t\t});\r\n\t\t}\r\n\t};\r\n\r\n\r\n\t/**\r\n\t * When drilling up, keep the upper series invisible until the lower series has\r\n\t * moved into place\r\n\t */\r\n\tPieSeries.prototype.animateDrillupTo = \r\n\t\t\tColumnSeries.prototype.animateDrillupTo = function (init) {\r\n\t\tif (!init) {\r\n\t\t\tvar newSeries = this,\r\n\t\t\t\tlevel = newSeries.drilldownLevel;\r\n\r\n\t\t\teach(this.points, function (point) {\r\n\t\t\t\tpoint.graphic.hide();\r\n\t\t\t\tif (point.dataLabel) {\r\n\t\t\t\t\tpoint.dataLabel.hide();\r\n\t\t\t\t}\r\n\t\t\t\tif (point.connector) {\r\n\t\t\t\t\tpoint.connector.hide();\r\n\t\t\t\t}\r\n\t\t\t});\r\n\r\n\r\n\t\t\t// Do dummy animation on first point to get to complete\r\n\t\t\tsetTimeout(function () {\r\n\t\t\t\teach(newSeries.points, function (point, i) {  \r\n\t\t\t\t\t// Fade in other points\t\t\t  \r\n\t\t\t\t\tvar verb = i === level.pointIndex ? 'show' : 'fadeIn';\r\n\t\t\t\t\tpoint.graphic[verb]();\r\n\t\t\t\t\tif (point.dataLabel) {\r\n\t\t\t\t\t\tpoint.dataLabel[verb]();\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (point.connector) {\r\n\t\t\t\t\t\tpoint.connector[verb]();\r\n\t\t\t\t\t}\r\n\t\t\t\t});\r\n\t\t\t}, Math.max(this.chart.options.drilldown.animation.duration - 50, 0));\r\n\r\n\t\t\t// Reset\r\n\t\t\tthis.animate = noop;\r\n\t\t}\r\n\r\n\t};\r\n\t\r\n\tColumnSeries.prototype.animateDrilldown = function (init) {\r\n\t\tvar animateFrom = this.chart.drilldownLevels[this.chart.drilldownLevels.length - 1].shapeArgs,\r\n\t\t\tanimationOptions = this.chart.options.drilldown.animation;\r\n\t\t\t\r\n\t\tif (!init) {\r\n\r\n\t\t\tanimateFrom.x += (this.xAxis.oldPos - this.xAxis.pos);\r\n\t\r\n\t\t\teach(this.points, function (point) {\r\n\t\t\t\tpoint.graphic\r\n\t\t\t\t\t.attr(animateFrom)\r\n\t\t\t\t\t.animate(point.shapeArgs, animationOptions);\r\n\t\t\t});\r\n\t\t}\r\n\t\t\r\n\t};\r\n\r\n\t/**\r\n\t * When drilling up, pull out the individual point graphics from the lower series\r\n\t * and animate them into the origin point in the upper series.\r\n\t */\r\n\tColumnSeries.prototype.animateDrillupFrom = \r\n\t\tPieSeries.prototype.animateDrillupFrom =\r\n\tfunction (level) {\r\n\t\tvar animationOptions = this.chart.options.drilldown.animation,\r\n\t\t\tgroup = this.group;\r\n\r\n\t\tdelete this.group;\r\n\t\teach(this.points, function (point) {\r\n\t\t\tvar graphic = point.graphic,\r\n\t\t\t\tstartColor = H.Color(point.color).rgba;\r\n\r\n\t\t\tdelete point.graphic;\r\n\r\n\t\t\t/*jslint unparam: true*/\r\n\t\t\tgraphic.animate(level.shapeArgs, H.merge(animationOptions, {\r\n\r\n\t\t\t\tstep: function (val, fx) {\r\n\t\t\t\t\tif (fx.prop === 'start') {\r\n\t\t\t\t\t\tthis.attr({\r\n\t\t\t\t\t\t\tfill: tweenColors(startColor, H.Color(level.color).rgba, fx.pos)\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t}\r\n\t\t\t\t},\r\n\t\t\t\tcomplete: function () {\r\n\t\t\t\t\tgraphic.destroy();\r\n\t\t\t\t\tif (group) {\r\n\t\t\t\t\t\tgroup = group.destroy();\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}));\r\n\t\t\t/*jslint unparam: false*/\r\n\t\t});\r\n\t};\r\n\t\r\n\tH.Point.prototype.doDrilldown = function () {\r\n\t\tvar series = this.series,\r\n\t\t\tchart = series.chart,\r\n\t\t\tdrilldown = chart.options.drilldown,\r\n\t\t\ti = drilldown.series.length,\r\n\t\t\tseriesOptions;\r\n\t\t\r\n\t\twhile (i-- && !seriesOptions) {\r\n\t\t\tif (drilldown.series[i].id === this.drilldown) {\r\n\t\t\t\tseriesOptions = drilldown.series[i];\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Fire the event. If seriesOptions is undefined, the implementer can check for \r\n\t\t// seriesOptions, and call addSeriesAsDrilldown async if necessary.\r\n\t\tfireEvent(chart, 'drilldown', { \r\n\t\t\tpoint: this,\r\n\t\t\tseriesOptions: seriesOptions\r\n\t\t});\r\n\t\t\r\n\t\tif (seriesOptions) {\r\n\t\t\tchart.addSeriesAsDrilldown(this, seriesOptions);\r\n\t\t}\r\n\r\n\t};\r\n\t\r\n\twrap(H.Point.prototype, 'init', function (proceed, series, options, x) {\r\n\t\tvar point = proceed.call(this, series, options, x),\r\n\t\t\tchart = series.chart,\r\n\t\t\ttick = series.xAxis && series.xAxis.ticks[x],\r\n\t\t\ttickLabel = tick && tick.label;\r\n\t\t\r\n\t\tif (point.drilldown) {\r\n\t\t\t\r\n\t\t\t// Add the click event to the point label\r\n\t\t\tH.addEvent(point, 'click', function () {\r\n\t\t\t\tpoint.doDrilldown();\r\n\t\t\t});\r\n\t\t\t\r\n\t\t\t// Make axis labels clickable\r\n\t\t\tif (tickLabel) {\r\n\t\t\t\tif (!tickLabel._basicStyle) {\r\n\t\t\t\t\ttickLabel._basicStyle = tickLabel.element.getAttribute('style');\r\n\t\t\t\t}\r\n\t\t\t\ttickLabel\r\n\t\t\t\t\t.addClass('highcharts-drilldown-axis-label')\r\n\t\t\t\t\t.css(chart.options.drilldown.activeAxisLabelStyle)\r\n\t\t\t\t\t.on('click', function () {\r\n\t\t\t\t\t\tif (point.doDrilldown) {\r\n\t\t\t\t\t\t\tpoint.doDrilldown();\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t});\r\n\t\t\t\t\t\r\n\t\t\t}\r\n\t\t} else if (tickLabel && tickLabel._basicStyle) {\r\n\t\t\ttickLabel.element.setAttribute('style', tickLabel._basicStyle);\r\n\t\t}\r\n\t\t\r\n\t\treturn point;\r\n\t});\r\n\r\n\twrap(H.Series.prototype, 'drawDataLabels', function (proceed) {\r\n\t\tvar css = this.chart.options.drilldown.activeDataLabelStyle;\r\n\r\n\t\tproceed.call(this);\r\n\r\n\t\teach(this.points, function (point) {\r\n\t\t\tif (point.drilldown && point.dataLabel) {\r\n\t\t\t\tpoint.dataLabel\r\n\t\t\t\t\t.attr({\r\n\t\t\t\t\t\t'class': 'highcharts-drilldown-data-label'\r\n\t\t\t\t\t})\r\n\t\t\t\t\t.css(css)\r\n\t\t\t\t\t.on('click', function () {\r\n\t\t\t\t\t\tpoint.doDrilldown();\r\n\t\t\t\t\t});\r\n\t\t\t}\r\n\t\t});\r\n\t});\r\n\r\n\t// Mark the trackers with a pointer \r\n\tColumnSeries.prototype.supportsDrilldown = true;\r\n\tPieSeries.prototype.supportsDrilldown = true;\r\n\tvar type, \r\n\t\tdrawTrackerWrapper = function (proceed) {\r\n\t\t\tproceed.call(this);\r\n\t\t\teach(this.points, function (point) {\r\n\t\t\t\tif (point.drilldown && point.graphic) {\r\n\t\t\t\t\tpoint.graphic\r\n\t\t\t\t\t\t.attr({\r\n\t\t\t\t\t\t\t'class': 'highcharts-drilldown-point'\r\n\t\t\t\t\t\t})\r\n\t\t\t\t\t\t.css({ cursor: 'pointer' });\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t};\r\n\tfor (type in seriesTypes) {\r\n\t\tif (seriesTypes[type].prototype.supportsDrilldown) {\r\n\t\t\twrap(seriesTypes[type].prototype, 'drawTracker', drawTrackerWrapper);\r\n\t\t}\r\n\t}\r\n\t\t\r\n}(Highcharts));\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/highcharts/modules/exporting.js",
    "content": "/*\r\n Highcharts JS v3.0.6 (2013-10-04)\r\n Exporting module\r\n\r\n (c) 2010-2013 Torstein Hønsi\r\n\r\n License: www.highcharts.com/license\r\n*/\r\n(function(f){var A=f.Chart,t=f.addEvent,C=f.removeEvent,k=f.createElement,n=f.discardElement,u=f.css,o=f.merge,r=f.each,p=f.extend,D=Math.max,j=document,B=window,E=f.isTouchDevice,F=f.Renderer.prototype.symbols,x=f.getOptions(),y;p(x.lang,{printChart:\"Print chart\",downloadPNG:\"Download PNG image\",downloadJPEG:\"Download JPEG image\",downloadPDF:\"Download PDF document\",downloadSVG:\"Download SVG vector image\",contextButtonTitle:\"Chart context menu\"});x.navigation={menuStyle:{border:\"1px solid #A0A0A0\",\r\nbackground:\"#FFFFFF\",padding:\"5px 0\"},menuItemStyle:{padding:\"0 10px\",background:\"none\",color:\"#303030\",fontSize:E?\"14px\":\"11px\"},menuItemHoverStyle:{background:\"#4572A5\",color:\"#FFFFFF\"},buttonOptions:{symbolFill:\"#E0E0E0\",symbolSize:14,symbolStroke:\"#666\",symbolStrokeWidth:3,symbolX:12.5,symbolY:10.5,align:\"right\",buttonSpacing:3,height:22,theme:{fill:\"white\",stroke:\"none\"},verticalAlign:\"top\",width:24}};x.exporting={type:\"image/png\",url:\"http://export.highcharts.com/\",buttons:{contextButton:{menuClassName:\"highcharts-contextmenu\",\r\nsymbol:\"menu\",_titleKey:\"contextButtonTitle\",menuItems:[{textKey:\"printChart\",onclick:function(){this.print()}},{separator:!0},{textKey:\"downloadPNG\",onclick:function(){this.exportChart()}},{textKey:\"downloadJPEG\",onclick:function(){this.exportChart({type:\"image/jpeg\"})}},{textKey:\"downloadPDF\",onclick:function(){this.exportChart({type:\"application/pdf\"})}},{textKey:\"downloadSVG\",onclick:function(){this.exportChart({type:\"image/svg+xml\"})}}]}}};f.post=function(c,a){var d,b;b=k(\"form\",{method:\"post\",\r\naction:c,enctype:\"multipart/form-data\"},{display:\"none\"},j.body);for(d in a)k(\"input\",{type:\"hidden\",name:d,value:a[d]},null,b);b.submit();n(b)};p(A.prototype,{getSVG:function(c){var a=this,d,b,z,h,g=o(a.options,c);if(!j.createElementNS)j.createElementNS=function(a,b){return j.createElement(b)};c=k(\"div\",null,{position:\"absolute\",top:\"-9999em\",width:a.chartWidth+\"px\",height:a.chartHeight+\"px\"},j.body);b=a.renderTo.style.width;h=a.renderTo.style.height;b=g.exporting.sourceWidth||g.chart.width||/px$/.test(b)&&\r\nparseInt(b,10)||600;h=g.exporting.sourceHeight||g.chart.height||/px$/.test(h)&&parseInt(h,10)||400;p(g.chart,{animation:!1,renderTo:c,forExport:!0,width:b,height:h});g.exporting.enabled=!1;g.series=[];r(a.series,function(a){z=o(a.options,{animation:!1,showCheckbox:!1,visible:a.visible});z.isInternal||g.series.push(z)});d=new f.Chart(g,a.callback);r([\"xAxis\",\"yAxis\"],function(b){r(a[b],function(a,c){var g=d[b][c],f=a.getExtremes(),h=f.userMin,f=f.userMax;g&&(h!==void 0||f!==void 0)&&g.setExtremes(h,\r\nf,!0,!1)})});b=d.container.innerHTML;g=null;d.destroy();n(c);b=b.replace(/zIndex=\"[^\"]+\"/g,\"\").replace(/isShadow=\"[^\"]+\"/g,\"\").replace(/symbolName=\"[^\"]+\"/g,\"\").replace(/jQuery[0-9]+=\"[^\"]+\"/g,\"\").replace(/url\\([^#]+#/g,\"url(#\").replace(/<svg /,'<svg xmlns:xlink=\"http://www.w3.org/1999/xlink\" ').replace(/ href=/g,\" xlink:href=\").replace(/\\n/,\" \").replace(/<\\/svg>.*?$/,\"</svg>\").replace(/&nbsp;/g,\" \").replace(/&shy;/g,\"­\").replace(/<IMG /g,\"<image \").replace(/height=([^\" ]+)/g,'height=\"$1\"').replace(/width=([^\" ]+)/g,\r\n'width=\"$1\"').replace(/hc-svg-href=\"([^\"]+)\">/g,'xlink:href=\"$1\"/>').replace(/id=([^\" >]+)/g,'id=\"$1\"').replace(/class=([^\" >]+)/g,'class=\"$1\"').replace(/ transform /g,\" \").replace(/:(path|rect)/g,\"$1\").replace(/style=\"([^\"]+)\"/g,function(a){return a.toLowerCase()});return b=b.replace(/(url\\(#highcharts-[0-9]+)&quot;/g,\"$1\").replace(/&quot;/g,\"'\")},exportChart:function(c,a){var c=c||{},d=this.options.exporting,d=this.getSVG(o({chart:{borderRadius:0}},d.chartOptions,a,{exporting:{sourceWidth:c.sourceWidth||\r\nd.sourceWidth,sourceHeight:c.sourceHeight||d.sourceHeight}})),c=o(this.options.exporting,c);f.post(c.url,{filename:c.filename||\"chart\",type:c.type,width:c.width||0,scale:c.scale||2,svg:d})},print:function(){var c=this,a=c.container,d=[],b=a.parentNode,f=j.body,h=f.childNodes;if(!c.isPrinting)c.isPrinting=!0,r(h,function(a,b){if(a.nodeType===1)d[b]=a.style.display,a.style.display=\"none\"}),f.appendChild(a),B.focus(),B.print(),setTimeout(function(){b.appendChild(a);r(h,function(a,b){if(a.nodeType===\r\n1)a.style.display=d[b]});c.isPrinting=!1},1E3)},contextMenu:function(c,a,d,b,f,h,g){var e=this,j=e.options.navigation,q=j.menuItemStyle,l=e.chartWidth,m=e.chartHeight,o=\"cache-\"+c,i=e[o],s=D(f,h),v,w,n;if(!i)e[o]=i=k(\"div\",{className:c},{position:\"absolute\",zIndex:1E3,padding:s+\"px\"},e.container),v=k(\"div\",null,p({MozBoxShadow:\"3px 3px 10px #888\",WebkitBoxShadow:\"3px 3px 10px #888\",boxShadow:\"3px 3px 10px #888\"},j.menuStyle),i),w=function(){u(i,{display:\"none\"});g&&g.setState(0);e.openMenu=!1},t(i,\r\n\"mouseleave\",function(){n=setTimeout(w,500)}),t(i,\"mouseenter\",function(){clearTimeout(n)}),t(document,\"mousedown\",function(a){e.pointer.inClass(a.target,c)||w()}),r(a,function(a){if(a){var b=a.separator?k(\"hr\",null,null,v):k(\"div\",{onmouseover:function(){u(this,j.menuItemHoverStyle)},onmouseout:function(){u(this,q)},onclick:function(){w();a.onclick.apply(e,arguments)},innerHTML:a.text||e.options.lang[a.textKey]},p({cursor:\"pointer\"},q),v);e.exportDivElements.push(b)}}),e.exportDivElements.push(v,\r\ni),e.exportMenuWidth=i.offsetWidth,e.exportMenuHeight=i.offsetHeight;a={display:\"block\"};d+e.exportMenuWidth>l?a.right=l-d-f-s+\"px\":a.left=d-s+\"px\";b+h+e.exportMenuHeight>m&&g.alignOptions.verticalAlign!==\"top\"?a.bottom=m-b-s+\"px\":a.top=b+h-s+\"px\";u(i,a);e.openMenu=!0},addButton:function(c){var a=this,d=a.renderer,b=o(a.options.navigation.buttonOptions,c),j=b.onclick,h=b.menuItems,g,e,k={stroke:b.symbolStroke,fill:b.symbolFill},q=b.symbolSize||12;if(!a.btnCount)a.btnCount=0;if(!a.exportDivElements)a.exportDivElements=\r\n[],a.exportSVGElements=[];if(b.enabled!==!1){var l=b.theme,m=l.states,n=m&&m.hover,m=m&&m.select,i;delete l.states;j?i=function(){j.apply(a,arguments)}:h&&(i=function(){a.contextMenu(e.menuClassName,h,e.translateX,e.translateY,e.width,e.height,e);e.setState(2)});b.text&&b.symbol?l.paddingLeft=f.pick(l.paddingLeft,25):b.text||p(l,{width:b.width,height:b.height,padding:0});e=d.button(b.text,0,0,i,l,n,m).attr({title:a.options.lang[b._titleKey],\"stroke-linecap\":\"round\"});e.menuClassName=c.menuClassName||\r\n\"highcharts-menu-\"+a.btnCount++;b.symbol&&(g=d.symbol(b.symbol,b.symbolX-q/2,b.symbolY-q/2,q,q).attr(p(k,{\"stroke-width\":b.symbolStrokeWidth||1,zIndex:1})).add(e));e.add().align(p(b,{width:e.width,x:f.pick(b.x,y)}),!0,\"spacingBox\");y+=(e.width+b.buttonSpacing)*(b.align===\"right\"?-1:1);a.exportSVGElements.push(e,g)}},destroyExport:function(c){var c=c.target,a,d;for(a=0;a<c.exportSVGElements.length;a++)if(d=c.exportSVGElements[a])d.onclick=d.ontouchstart=null,c.exportSVGElements[a]=d.destroy();for(a=\r\n0;a<c.exportDivElements.length;a++)d=c.exportDivElements[a],C(d,\"mouseleave\"),c.exportDivElements[a]=d.onmouseout=d.onmouseover=d.ontouchstart=d.onclick=null,n(d)}});F.menu=function(c,a,d,b){return[\"M\",c,a+2.5,\"L\",c+d,a+2.5,\"M\",c,a+b/2+0.5,\"L\",c+d,a+b/2+0.5,\"M\",c,a+b-1.5,\"L\",c+d,a+b-1.5]};A.prototype.callbacks.push(function(c){var a,d=c.options.exporting,b=d.buttons;y=0;if(d.enabled!==!1){for(a in b)c.addButton(b[a]);t(c,\"destroy\",c.destroyExport)}})})(Highcharts);\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/highcharts/modules/exporting.src.js",
    "content": "/**\r\n * @license Highcharts JS v3.0.6 (2013-10-04)\r\n * Exporting module\r\n *\r\n * (c) 2010-2013 Torstein Hønsi\r\n *\r\n * License: www.highcharts.com/license\r\n */\r\n\r\n// JSLint options:\r\n/*global Highcharts, document, window, Math, setTimeout */\r\n\r\n(function (Highcharts) { // encapsulate\r\n\r\n// create shortcuts\r\nvar Chart = Highcharts.Chart,\r\n\taddEvent = Highcharts.addEvent,\r\n\tremoveEvent = Highcharts.removeEvent,\r\n\tcreateElement = Highcharts.createElement,\r\n\tdiscardElement = Highcharts.discardElement,\r\n\tcss = Highcharts.css,\r\n\tmerge = Highcharts.merge,\r\n\teach = Highcharts.each,\r\n\textend = Highcharts.extend,\r\n\tmath = Math,\r\n\tmathMax = math.max,\r\n\tdoc = document,\r\n\twin = window,\r\n\tisTouchDevice = Highcharts.isTouchDevice,\r\n\tM = 'M',\r\n\tL = 'L',\r\n\tDIV = 'div',\r\n\tHIDDEN = 'hidden',\r\n\tNONE = 'none',\r\n\tPREFIX = 'highcharts-',\r\n\tABSOLUTE = 'absolute',\r\n\tPX = 'px',\r\n\tUNDEFINED,\r\n\tsymbols = Highcharts.Renderer.prototype.symbols,\r\n\tdefaultOptions = Highcharts.getOptions(),\r\n\tbuttonOffset;\r\n\r\n\t// Add language\r\n\textend(defaultOptions.lang, {\r\n\t\tprintChart: 'Print chart',\r\n\t\tdownloadPNG: 'Download PNG image',\r\n\t\tdownloadJPEG: 'Download JPEG image',\r\n\t\tdownloadPDF: 'Download PDF document',\r\n\t\tdownloadSVG: 'Download SVG vector image',\r\n\t\tcontextButtonTitle: 'Chart context menu'\r\n\t});\r\n\r\n// Buttons and menus are collected in a separate config option set called 'navigation'.\r\n// This can be extended later to add control buttons like zoom and pan right click menus.\r\ndefaultOptions.navigation = {\r\n\tmenuStyle: {\r\n\t\tborder: '1px solid #A0A0A0',\r\n\t\tbackground: '#FFFFFF',\r\n\t\tpadding: '5px 0'\r\n\t},\r\n\tmenuItemStyle: {\r\n\t\tpadding: '0 10px',\r\n\t\tbackground: NONE,\r\n\t\tcolor: '#303030',\r\n\t\tfontSize: isTouchDevice ? '14px' : '11px'\r\n\t},\r\n\tmenuItemHoverStyle: {\r\n\t\tbackground: '#4572A5',\r\n\t\tcolor: '#FFFFFF'\r\n\t},\r\n\r\n\tbuttonOptions: {\r\n\t\tsymbolFill: '#E0E0E0',\r\n\t\tsymbolSize: 14,\r\n\t\tsymbolStroke: '#666',\r\n\t\tsymbolStrokeWidth: 3,\r\n\t\tsymbolX: 12.5,\r\n\t\tsymbolY: 10.5,\r\n\t\talign: 'right',\r\n\t\tbuttonSpacing: 3, \r\n\t\theight: 22,\r\n\t\t// text: null,\r\n\t\ttheme: {\r\n\t\t\tfill: 'white', // capture hover\r\n\t\t\tstroke: 'none'\r\n\t\t},\r\n\t\tverticalAlign: 'top',\r\n\t\twidth: 24\r\n\t}\r\n};\r\n\r\n\r\n\r\n// Add the export related options\r\ndefaultOptions.exporting = {\r\n\t//enabled: true,\r\n\t//filename: 'chart',\r\n\ttype: 'image/png',\r\n\turl: 'http://export.highcharts.com/',\r\n\t//width: undefined,\r\n\t//scale: 2\r\n\tbuttons: {\r\n\t\tcontextButton: {\r\n\t\t\tmenuClassName: PREFIX + 'contextmenu',\r\n\t\t\t//x: -10,\r\n\t\t\tsymbol: 'menu',\r\n\t\t\t_titleKey: 'contextButtonTitle',\r\n\t\t\tmenuItems: [{\r\n\t\t\t\ttextKey: 'printChart',\r\n\t\t\t\tonclick: function () {\r\n\t\t\t\t\tthis.print();\r\n\t\t\t\t}\r\n\t\t\t}, {\r\n\t\t\t\tseparator: true\r\n\t\t\t}, {\r\n\t\t\t\ttextKey: 'downloadPNG',\r\n\t\t\t\tonclick: function () {\r\n\t\t\t\t\tthis.exportChart();\r\n\t\t\t\t}\r\n\t\t\t}, {\r\n\t\t\t\ttextKey: 'downloadJPEG',\r\n\t\t\t\tonclick: function () {\r\n\t\t\t\t\tthis.exportChart({\r\n\t\t\t\t\t\ttype: 'image/jpeg'\r\n\t\t\t\t\t});\r\n\t\t\t\t}\r\n\t\t\t}, {\r\n\t\t\t\ttextKey: 'downloadPDF',\r\n\t\t\t\tonclick: function () {\r\n\t\t\t\t\tthis.exportChart({\r\n\t\t\t\t\t\ttype: 'application/pdf'\r\n\t\t\t\t\t});\r\n\t\t\t\t}\r\n\t\t\t}, {\r\n\t\t\t\ttextKey: 'downloadSVG',\r\n\t\t\t\tonclick: function () {\r\n\t\t\t\t\tthis.exportChart({\r\n\t\t\t\t\t\ttype: 'image/svg+xml'\r\n\t\t\t\t\t});\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\t// Enable this block to add \"View SVG\" to the dropdown menu\r\n\t\t\t/*\r\n\t\t\t,{\r\n\r\n\t\t\t\ttext: 'View SVG',\r\n\t\t\t\tonclick: function () {\r\n\t\t\t\t\tvar svg = this.getSVG()\r\n\t\t\t\t\t\t.replace(/</g, '\\n&lt;')\r\n\t\t\t\t\t\t.replace(/>/g, '&gt;');\r\n\r\n\t\t\t\t\tdoc.body.innerHTML = '<pre>' + svg + '</pre>';\r\n\t\t\t\t}\r\n\t\t\t} // */\r\n\t\t\t]\r\n\t\t}\r\n\t}\r\n};\r\n\r\n// Add the Highcharts.post utility\r\nHighcharts.post = function (url, data) {\r\n\tvar name,\r\n\t\tform;\r\n\t\r\n\t// create the form\r\n\tform = createElement('form', {\r\n\t\tmethod: 'post',\r\n\t\taction: url,\r\n\t\tenctype: 'multipart/form-data'\r\n\t}, {\r\n\t\tdisplay: NONE\r\n\t}, doc.body);\r\n\r\n\t// add the data\r\n\tfor (name in data) {\r\n\t\tcreateElement('input', {\r\n\t\t\ttype: HIDDEN,\r\n\t\t\tname: name,\r\n\t\t\tvalue: data[name]\r\n\t\t}, null, form);\r\n\t}\r\n\r\n\t// submit\r\n\tform.submit();\r\n\r\n\t// clean up\r\n\tdiscardElement(form);\r\n};\r\n\r\nextend(Chart.prototype, {\r\n\r\n\t/**\r\n\t * Return an SVG representation of the chart\r\n\t *\r\n\t * @param additionalOptions {Object} Additional chart options for the generated SVG representation\r\n\t */\r\n\tgetSVG: function (additionalOptions) {\r\n\t\tvar chart = this,\r\n\t\t\tchartCopy,\r\n\t\t\tsandbox,\r\n\t\t\tsvg,\r\n\t\t\tseriesOptions,\r\n\t\t\tsourceWidth,\r\n\t\t\tsourceHeight,\r\n\t\t\tcssWidth,\r\n\t\t\tcssHeight,\r\n\t\t\toptions = merge(chart.options, additionalOptions); // copy the options and add extra options\r\n\r\n\t\t// IE compatibility hack for generating SVG content that it doesn't really understand\r\n\t\tif (!doc.createElementNS) {\r\n\t\t\t/*jslint unparam: true*//* allow unused parameter ns in function below */\r\n\t\t\tdoc.createElementNS = function (ns, tagName) {\r\n\t\t\t\treturn doc.createElement(tagName);\r\n\t\t\t};\r\n\t\t\t/*jslint unparam: false*/\r\n\t\t}\r\n\r\n\t\t// create a sandbox where a new chart will be generated\r\n\t\tsandbox = createElement(DIV, null, {\r\n\t\t\tposition: ABSOLUTE,\r\n\t\t\ttop: '-9999em',\r\n\t\t\twidth: chart.chartWidth + PX,\r\n\t\t\theight: chart.chartHeight + PX\r\n\t\t}, doc.body);\r\n\t\t\r\n\t\t// get the source size\r\n\t\tcssWidth = chart.renderTo.style.width;\r\n\t\tcssHeight = chart.renderTo.style.height;\r\n\t\tsourceWidth = options.exporting.sourceWidth ||\r\n\t\t\toptions.chart.width ||\r\n\t\t\t(/px$/.test(cssWidth) && parseInt(cssWidth, 10)) ||\r\n\t\t\t600;\r\n\t\tsourceHeight = options.exporting.sourceHeight ||\r\n\t\t\toptions.chart.height ||\r\n\t\t\t(/px$/.test(cssHeight) && parseInt(cssHeight, 10)) ||\r\n\t\t\t400;\r\n\r\n\t\t// override some options\r\n\t\textend(options.chart, {\r\n\t\t\tanimation: false,\r\n\t\t\trenderTo: sandbox,\r\n\t\t\tforExport: true,\r\n\t\t\twidth: sourceWidth,\r\n\t\t\theight: sourceHeight\r\n\t\t});\r\n\t\toptions.exporting.enabled = false; // hide buttons in print\r\n\t\t\r\n\t\t// prepare for replicating the chart\r\n\t\toptions.series = [];\r\n\t\teach(chart.series, function (serie) {\r\n\t\t\tseriesOptions = merge(serie.options, {\r\n\t\t\t\tanimation: false, // turn off animation\r\n\t\t\t\tshowCheckbox: false,\r\n\t\t\t\tvisible: serie.visible\r\n\t\t\t});\r\n\r\n\t\t\tif (!seriesOptions.isInternal) { // used for the navigator series that has its own option set\r\n\t\t\t\toptions.series.push(seriesOptions);\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\t// generate the chart copy\r\n\t\tchartCopy = new Highcharts.Chart(options, chart.callback);\r\n\r\n\t\t// reflect axis extremes in the export\r\n\t\teach(['xAxis', 'yAxis'], function (axisType) {\r\n\t\t\teach(chart[axisType], function (axis, i) {\r\n\t\t\t\tvar axisCopy = chartCopy[axisType][i],\r\n\t\t\t\t\textremes = axis.getExtremes(),\r\n\t\t\t\t\tuserMin = extremes.userMin,\r\n\t\t\t\t\tuserMax = extremes.userMax;\r\n\r\n\t\t\t\tif (axisCopy && (userMin !== UNDEFINED || userMax !== UNDEFINED)) {\r\n\t\t\t\t\taxisCopy.setExtremes(userMin, userMax, true, false);\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t});\r\n\r\n\t\t// get the SVG from the container's innerHTML\r\n\t\tsvg = chartCopy.container.innerHTML;\r\n\r\n\t\t// free up memory\r\n\t\toptions = null;\r\n\t\tchartCopy.destroy();\r\n\t\tdiscardElement(sandbox);\r\n\r\n\t\t// sanitize\r\n\t\tsvg = svg\r\n\t\t\t.replace(/zIndex=\"[^\"]+\"/g, '')\r\n\t\t\t.replace(/isShadow=\"[^\"]+\"/g, '')\r\n\t\t\t.replace(/symbolName=\"[^\"]+\"/g, '')\r\n\t\t\t.replace(/jQuery[0-9]+=\"[^\"]+\"/g, '')\r\n\t\t\t.replace(/url\\([^#]+#/g, 'url(#')\r\n\t\t\t.replace(/<svg /, '<svg xmlns:xlink=\"http://www.w3.org/1999/xlink\" ')\r\n\t\t\t.replace(/ href=/g, ' xlink:href=')\r\n\t\t\t.replace(/\\n/, ' ')\r\n\t\t\t.replace(/<\\/svg>.*?$/, '</svg>') // any HTML added to the container after the SVG (#894)\r\n\t\t\t/* This fails in IE < 8\r\n\t\t\t.replace(/([0-9]+)\\.([0-9]+)/g, function(s1, s2, s3) { // round off to save weight\r\n\t\t\t\treturn s2 +'.'+ s3[0];\r\n\t\t\t})*/\r\n\r\n\t\t\t// Replace HTML entities, issue #347\r\n\t\t\t.replace(/&nbsp;/g, '\\u00A0') // no-break space\r\n\t\t\t.replace(/&shy;/g,  '\\u00AD') // soft hyphen\r\n\r\n\t\t\t// IE specific\r\n\t\t\t.replace(/<IMG /g, '<image ')\r\n\t\t\t.replace(/height=([^\" ]+)/g, 'height=\"$1\"')\r\n\t\t\t.replace(/width=([^\" ]+)/g, 'width=\"$1\"')\r\n\t\t\t.replace(/hc-svg-href=\"([^\"]+)\">/g, 'xlink:href=\"$1\"/>')\r\n\t\t\t.replace(/id=([^\" >]+)/g, 'id=\"$1\"')\r\n\t\t\t.replace(/class=([^\" >]+)/g, 'class=\"$1\"')\r\n\t\t\t.replace(/ transform /g, ' ')\r\n\t\t\t.replace(/:(path|rect)/g, '$1')\r\n\t\t\t.replace(/style=\"([^\"]+)\"/g, function (s) {\r\n\t\t\t\treturn s.toLowerCase();\r\n\t\t\t});\r\n\r\n\t\t// IE9 beta bugs with innerHTML. Test again with final IE9.\r\n\t\tsvg = svg.replace(/(url\\(#highcharts-[0-9]+)&quot;/g, '$1')\r\n\t\t\t.replace(/&quot;/g, \"'\");\r\n\r\n\t\treturn svg;\r\n\t},\r\n\r\n\t/**\r\n\t * Submit the SVG representation of the chart to the server\r\n\t * @param {Object} options Exporting options. Possible members are url, type and width.\r\n\t * @param {Object} chartOptions Additional chart options for the SVG representation of the chart\r\n\t */\r\n\texportChart: function (options, chartOptions) {\r\n\t\toptions = options || {};\r\n\t\t\r\n\t\tvar chart = this,\r\n\t\t\tchartExportingOptions = chart.options.exporting,\r\n\t\t\tsvg = chart.getSVG(merge(\r\n\t\t\t\t{ chart: { borderRadius: 0 } },\r\n\t\t\t\tchartExportingOptions.chartOptions,\r\n\t\t\t\tchartOptions, \r\n\t\t\t\t{\r\n\t\t\t\t\texporting: {\r\n\t\t\t\t\t\tsourceWidth: options.sourceWidth || chartExportingOptions.sourceWidth,\r\n\t\t\t\t\t\tsourceHeight: options.sourceHeight || chartExportingOptions.sourceHeight\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t));\r\n\r\n\t\t// merge the options\r\n\t\toptions = merge(chart.options.exporting, options);\r\n\t\t\r\n\t\t// do the post\r\n\t\tHighcharts.post(options.url, {\r\n\t\t\tfilename: options.filename || 'chart',\r\n\t\t\ttype: options.type,\r\n\t\t\twidth: options.width || 0, // IE8 fails to post undefined correctly, so use 0\r\n\t\t\tscale: options.scale || 2,\r\n\t\t\tsvg: svg\r\n\t\t});\r\n\r\n\t},\r\n\t\r\n\t/**\r\n\t * Print the chart\r\n\t */\r\n\tprint: function () {\r\n\r\n\t\tvar chart = this,\r\n\t\t\tcontainer = chart.container,\r\n\t\t\torigDisplay = [],\r\n\t\t\torigParent = container.parentNode,\r\n\t\t\tbody = doc.body,\r\n\t\t\tchildNodes = body.childNodes;\r\n\r\n\t\tif (chart.isPrinting) { // block the button while in printing mode\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tchart.isPrinting = true;\r\n\r\n\t\t// hide all body content\r\n\t\teach(childNodes, function (node, i) {\r\n\t\t\tif (node.nodeType === 1) {\r\n\t\t\t\torigDisplay[i] = node.style.display;\r\n\t\t\t\tnode.style.display = NONE;\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\t// pull out the chart\r\n\t\tbody.appendChild(container);\r\n\r\n\t\t// print\r\n\t\twin.focus(); // #1510\r\n\t\twin.print();\r\n\r\n\t\t// allow the browser to prepare before reverting\r\n\t\tsetTimeout(function () {\r\n\r\n\t\t\t// put the chart back in\r\n\t\t\torigParent.appendChild(container);\r\n\r\n\t\t\t// restore all body content\r\n\t\t\teach(childNodes, function (node, i) {\r\n\t\t\t\tif (node.nodeType === 1) {\r\n\t\t\t\t\tnode.style.display = origDisplay[i];\r\n\t\t\t\t}\r\n\t\t\t});\r\n\r\n\t\t\tchart.isPrinting = false;\r\n\r\n\t\t}, 1000);\r\n\r\n\t},\r\n\r\n\t/**\r\n\t * Display a popup menu for choosing the export type\r\n\t *\r\n\t * @param {String} className An identifier for the menu\r\n\t * @param {Array} items A collection with text and onclicks for the items\r\n\t * @param {Number} x The x position of the opener button\r\n\t * @param {Number} y The y position of the opener button\r\n\t * @param {Number} width The width of the opener button\r\n\t * @param {Number} height The height of the opener button\r\n\t */\r\n\tcontextMenu: function (className, items, x, y, width, height, button) {\r\n\t\tvar chart = this,\r\n\t\t\tnavOptions = chart.options.navigation,\r\n\t\t\tmenuItemStyle = navOptions.menuItemStyle,\r\n\t\t\tchartWidth = chart.chartWidth,\r\n\t\t\tchartHeight = chart.chartHeight,\r\n\t\t\tcacheName = 'cache-' + className,\r\n\t\t\tmenu = chart[cacheName],\r\n\t\t\tmenuPadding = mathMax(width, height), // for mouse leave detection\r\n\t\t\tboxShadow = '3px 3px 10px #888',\r\n\t\t\tinnerMenu,\r\n\t\t\thide,\r\n\t\t\thideTimer,\r\n\t\t\tmenuStyle;\r\n\r\n\t\t// create the menu only the first time\r\n\t\tif (!menu) {\r\n\r\n\t\t\t// create a HTML element above the SVG\r\n\t\t\tchart[cacheName] = menu = createElement(DIV, {\r\n\t\t\t\tclassName: className\r\n\t\t\t}, {\r\n\t\t\t\tposition: ABSOLUTE,\r\n\t\t\t\tzIndex: 1000,\r\n\t\t\t\tpadding: menuPadding + PX\r\n\t\t\t}, chart.container);\r\n\r\n\t\t\tinnerMenu = createElement(DIV, null,\r\n\t\t\t\textend({\r\n\t\t\t\t\tMozBoxShadow: boxShadow,\r\n\t\t\t\t\tWebkitBoxShadow: boxShadow,\r\n\t\t\t\t\tboxShadow: boxShadow\r\n\t\t\t\t}, navOptions.menuStyle), menu);\r\n\r\n\t\t\t// hide on mouse out\r\n\t\t\thide = function () {\r\n\t\t\t\tcss(menu, { display: NONE });\r\n\t\t\t\tif (button) {\r\n\t\t\t\t\tbutton.setState(0);\r\n\t\t\t\t}\r\n\t\t\t\tchart.openMenu = false;\r\n\t\t\t};\r\n\r\n\t\t\t// Hide the menu some time after mouse leave (#1357)\r\n\t\t\taddEvent(menu, 'mouseleave', function () {\r\n\t\t\t\thideTimer = setTimeout(hide, 500);\r\n\t\t\t});\r\n\t\t\taddEvent(menu, 'mouseenter', function () {\r\n\t\t\t\tclearTimeout(hideTimer);\r\n\t\t\t});\r\n\t\t\t// Hide it on clicking or touching outside the menu (#2258)\r\n\t\t\taddEvent(document, 'mousedown', function (e) {\r\n\t\t\t\tif (!chart.pointer.inClass(e.target, className)) {\r\n\t\t\t\t\thide();\r\n\t\t\t\t}\r\n\t\t\t});\r\n\r\n\r\n\t\t\t// create the items\r\n\t\t\teach(items, function (item) {\r\n\t\t\t\tif (item) {\r\n\t\t\t\t\tvar element = item.separator ? \r\n\t\t\t\t\t\tcreateElement('hr', null, null, innerMenu) :\r\n\t\t\t\t\t\tcreateElement(DIV, {\r\n\t\t\t\t\t\t\tonmouseover: function () {\r\n\t\t\t\t\t\t\t\tcss(this, navOptions.menuItemHoverStyle);\r\n\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t\tonmouseout: function () {\r\n\t\t\t\t\t\t\t\tcss(this, menuItemStyle);\r\n\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t\tonclick: function () {\r\n\t\t\t\t\t\t\t\thide();\r\n\t\t\t\t\t\t\t\titem.onclick.apply(chart, arguments);\r\n\t\t\t\t\t\t\t},\r\n\t\t\t\t\t\t\tinnerHTML: item.text || chart.options.lang[item.textKey]\r\n\t\t\t\t\t\t}, extend({\r\n\t\t\t\t\t\t\tcursor: 'pointer'\r\n\t\t\t\t\t\t}, menuItemStyle), innerMenu);\r\n\r\n\r\n\t\t\t\t\t// Keep references to menu divs to be able to destroy them\r\n\t\t\t\t\tchart.exportDivElements.push(element);\r\n\t\t\t\t}\r\n\t\t\t});\r\n\r\n\t\t\t// Keep references to menu and innerMenu div to be able to destroy them\r\n\t\t\tchart.exportDivElements.push(innerMenu, menu);\r\n\r\n\t\t\tchart.exportMenuWidth = menu.offsetWidth;\r\n\t\t\tchart.exportMenuHeight = menu.offsetHeight;\r\n\t\t}\r\n\r\n\t\tmenuStyle = { display: 'block' };\r\n\r\n\t\t// if outside right, right align it\r\n\t\tif (x + chart.exportMenuWidth > chartWidth) {\r\n\t\t\tmenuStyle.right = (chartWidth - x - width - menuPadding) + PX;\r\n\t\t} else {\r\n\t\t\tmenuStyle.left = (x - menuPadding) + PX;\r\n\t\t}\r\n\t\t// if outside bottom, bottom align it\r\n\t\tif (y + height + chart.exportMenuHeight > chartHeight && button.alignOptions.verticalAlign !== 'top') {\r\n\t\t\tmenuStyle.bottom = (chartHeight - y - menuPadding)  + PX;\r\n\t\t} else {\r\n\t\t\tmenuStyle.top = (y + height - menuPadding) + PX;\r\n\t\t}\r\n\r\n\t\tcss(menu, menuStyle);\r\n\t\tchart.openMenu = true;\r\n\t},\r\n\r\n\t/**\r\n\t * Add the export button to the chart\r\n\t */\r\n\taddButton: function (options) {\r\n\t\tvar chart = this,\r\n\t\t\trenderer = chart.renderer,\r\n\t\t\tbtnOptions = merge(chart.options.navigation.buttonOptions, options),\r\n\t\t\tonclick = btnOptions.onclick,\r\n\t\t\tmenuItems = btnOptions.menuItems,\r\n\t\t\tsymbol,\r\n\t\t\tbutton,\r\n\t\t\tsymbolAttr = {\r\n\t\t\t\tstroke: btnOptions.symbolStroke,\r\n\t\t\t\tfill: btnOptions.symbolFill\r\n\t\t\t},\r\n\t\t\tsymbolSize = btnOptions.symbolSize || 12;\r\n\t\tif (!chart.btnCount) {\r\n\t\t\tchart.btnCount = 0;\r\n\t\t}\r\n\r\n\t\t// Keeps references to the button elements\r\n\t\tif (!chart.exportDivElements) {\r\n\t\t\tchart.exportDivElements = [];\r\n\t\t\tchart.exportSVGElements = [];\r\n\t\t}\r\n\r\n\t\tif (btnOptions.enabled === false) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\r\n\t\tvar attr = btnOptions.theme,\r\n\t\t\tstates = attr.states,\r\n\t\t\thover = states && states.hover,\r\n\t\t\tselect = states && states.select,\r\n\t\t\tcallback;\r\n\r\n\t\tdelete attr.states;\r\n\r\n\t\tif (onclick) {\r\n\t\t\tcallback = function () {\r\n\t\t\t\tonclick.apply(chart, arguments);\r\n\t\t\t};\r\n\r\n\t\t} else if (menuItems) {\r\n\t\t\tcallback = function () {\r\n\t\t\t\tchart.contextMenu(\r\n\t\t\t\t\tbutton.menuClassName, \r\n\t\t\t\t\tmenuItems, \r\n\t\t\t\t\tbutton.translateX, \r\n\t\t\t\t\tbutton.translateY, \r\n\t\t\t\t\tbutton.width, \r\n\t\t\t\t\tbutton.height,\r\n\t\t\t\t\tbutton\r\n\t\t\t\t);\r\n\t\t\t\tbutton.setState(2);\r\n\t\t\t};\r\n\t\t}\r\n\r\n\r\n\t\tif (btnOptions.text && btnOptions.symbol) {\r\n\t\t\tattr.paddingLeft = Highcharts.pick(attr.paddingLeft, 25);\r\n\t\t\r\n\t\t} else if (!btnOptions.text) {\r\n\t\t\textend(attr, {\r\n\t\t\t\twidth: btnOptions.width,\r\n\t\t\t\theight: btnOptions.height,\r\n\t\t\t\tpadding: 0\r\n\t\t\t});\r\n\t\t}\r\n\r\n\t\tbutton = renderer.button(btnOptions.text, 0, 0, callback, attr, hover, select)\r\n\t\t\t.attr({\r\n\t\t\t\ttitle: chart.options.lang[btnOptions._titleKey],\r\n\t\t\t\t'stroke-linecap': 'round'\r\n\t\t\t});\r\n\t\tbutton.menuClassName = options.menuClassName || PREFIX + 'menu-' + chart.btnCount++;\r\n\r\n\t\tif (btnOptions.symbol) {\r\n\t\t\tsymbol = renderer.symbol(\r\n\t\t\t\t\tbtnOptions.symbol,\r\n\t\t\t\t\tbtnOptions.symbolX - (symbolSize / 2),\r\n\t\t\t\t\tbtnOptions.symbolY - (symbolSize / 2),\r\n\t\t\t\t\tsymbolSize,\t\t\t\t\r\n\t\t\t\t\tsymbolSize\r\n\t\t\t\t)\r\n\t\t\t\t.attr(extend(symbolAttr, {\r\n\t\t\t\t\t'stroke-width': btnOptions.symbolStrokeWidth || 1,\r\n\t\t\t\t\tzIndex: 1\r\n\t\t\t\t})).add(button);\r\n\t\t}\r\n\r\n\t\tbutton.add()\r\n\t\t\t.align(extend(btnOptions, {\r\n\t\t\t\twidth: button.width,\r\n\t\t\t\tx: Highcharts.pick(btnOptions.x, buttonOffset) // #1654\r\n\t\t\t}), true, 'spacingBox');\r\n\r\n\t\tbuttonOffset += (button.width + btnOptions.buttonSpacing) * (btnOptions.align === 'right' ? -1 : 1);\r\n\r\n\t\tchart.exportSVGElements.push(button, symbol);\r\n\r\n\t},\r\n\r\n\t/**\r\n\t * Destroy the buttons.\r\n\t */\r\n\tdestroyExport: function (e) {\r\n\t\tvar chart = e.target,\r\n\t\t\ti,\r\n\t\t\telem;\r\n\r\n\t\t// Destroy the extra buttons added\r\n\t\tfor (i = 0; i < chart.exportSVGElements.length; i++) {\r\n\t\t\telem = chart.exportSVGElements[i];\r\n\t\t\t\r\n\t\t\t// Destroy and null the svg/vml elements\r\n\t\t\tif (elem) { // #1822\r\n\t\t\t\telem.onclick = elem.ontouchstart = null;\r\n\t\t\t\tchart.exportSVGElements[i] = elem.destroy();\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Destroy the divs for the menu\r\n\t\tfor (i = 0; i < chart.exportDivElements.length; i++) {\r\n\t\t\telem = chart.exportDivElements[i];\r\n\r\n\t\t\t// Remove the event handler\r\n\t\t\tremoveEvent(elem, 'mouseleave');\r\n\r\n\t\t\t// Remove inline events\r\n\t\t\tchart.exportDivElements[i] = elem.onmouseout = elem.onmouseover = elem.ontouchstart = elem.onclick = null;\r\n\r\n\t\t\t// Destroy the div by moving to garbage bin\r\n\t\t\tdiscardElement(elem);\r\n\t\t}\r\n\t}\r\n});\r\n\r\n\r\nsymbols.menu = function (x, y, width, height) {\r\n\tvar arr = [\r\n\t\tM, x, y + 2.5,\r\n\t\tL, x + width, y + 2.5,\r\n\t\tM, x, y + height / 2 + 0.5,\r\n\t\tL, x + width, y + height / 2 + 0.5,\r\n\t\tM, x, y + height - 1.5,\r\n\t\tL, x + width, y + height - 1.5\r\n\t];\r\n\treturn arr;\r\n};\r\n\r\n// Add the buttons on chart load\r\nChart.prototype.callbacks.push(function (chart) {\r\n\tvar n,\r\n\t\texportingOptions = chart.options.exporting,\r\n\t\tbuttons = exportingOptions.buttons;\r\n\r\n\tbuttonOffset = 0;\r\n\r\n\tif (exportingOptions.enabled !== false) {\r\n\r\n\t\tfor (n in buttons) {\r\n\t\t\tchart.addButton(buttons[n]);\r\n\t\t}\r\n\r\n\t\t// Destroy the export elements at chart destroy\r\n\t\taddEvent(chart, 'destroy', chart.destroyExport);\r\n\t}\r\n\r\n});\r\n\r\n\r\n}(Highcharts));\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/highcharts/modules/funnel.js",
    "content": "/*\r\n \r\n Highcharts funnel module, Beta\r\n\r\n (c) 2010-2012 Torstein Hønsi\r\n\r\n License: www.highcharts.com/license\r\n*/\r\n(function(d){var u=d.getOptions().plotOptions,p=d.seriesTypes,D=d.merge,z=function(){},A=d.each;u.funnel=D(u.pie,{center:[\"50%\",\"50%\"],width:\"90%\",neckWidth:\"30%\",height:\"100%\",neckHeight:\"25%\",dataLabels:{connectorWidth:1,connectorColor:\"#606060\"},size:!0,states:{select:{color:\"#C0C0C0\",borderColor:\"#000000\",shadow:!1}}});p.funnel=d.extendClass(p.pie,{type:\"funnel\",animate:z,translate:function(){var a=function(k,a){return/%$/.test(k)?a*parseInt(k,10)/100:parseInt(k,10)},g=0,e=this.chart,f=e.plotWidth,\r\ne=e.plotHeight,h=0,c=this.options,C=c.center,b=a(C[0],f),d=a(C[0],e),p=a(c.width,f),i,q,j=a(c.height,e),r=a(c.neckWidth,f),s=a(c.neckHeight,e),v=j-s,a=this.data,w,x,u=c.dataLabels.position===\"left\"?1:0,y,m,B,n,l,t,o;this.getWidthAt=q=function(k){return k>j-s||j===s?r:r+(p-r)*((j-s-k)/(j-s))};this.getX=function(k,a){return b+(a?-1:1)*(q(k)/2+c.dataLabels.distance)};this.center=[b,d,j];this.centerX=b;A(a,function(a){g+=a.y});A(a,function(a){o=null;x=g?a.y/g:0;m=d-j/2+h*j;l=m+x*j;i=q(m);y=b-i/2;B=y+\r\ni;i=q(l);n=b-i/2;t=n+i;m>v?(y=n=b-r/2,B=t=b+r/2):l>v&&(o=l,i=q(v),n=b-i/2,t=n+i,l=v);w=[\"M\",y,m,\"L\",B,m,t,l];o&&w.push(t,o,n,o);w.push(n,l,\"Z\");a.shapeType=\"path\";a.shapeArgs={d:w};a.percentage=x*100;a.plotX=b;a.plotY=(m+(o||l))/2;a.tooltipPos=[b,a.plotY];a.slice=z;a.half=u;h+=x});this.setTooltipPoints()},drawPoints:function(){var a=this,g=a.options,e=a.chart.renderer;A(a.data,function(f){var h=f.graphic,c=f.shapeArgs;h?h.animate(c):f.graphic=e.path(c).attr({fill:f.color,stroke:g.borderColor,\"stroke-width\":g.borderWidth}).add(a.group)})},\r\nsortByAngle:z,drawDataLabels:function(){var a=this.data,g=this.options.dataLabels.distance,e,f,h,c=a.length,d,b;for(this.center[2]-=2*g;c--;)h=a[c],f=(e=h.half)?1:-1,b=h.plotY,d=this.getX(b,e),h.labelPos=[0,b,d+(g-5)*f,b,d+g*f,b,e?\"right\":\"left\",0];p.pie.prototype.drawDataLabels.call(this)}})})(Highcharts);\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/highcharts/modules/funnel.src.js",
    "content": "/**\r\n * @license \r\n * Highcharts funnel module, Beta\r\n *\r\n * (c) 2010-2012 Torstein Hønsi\r\n *\r\n * License: www.highcharts.com/license\r\n */\r\n\r\n/*global Highcharts */\r\n(function (Highcharts) {\r\n\t\r\n'use strict';\r\n\r\n// create shortcuts\r\nvar defaultOptions = Highcharts.getOptions(),\r\n\tdefaultPlotOptions = defaultOptions.plotOptions,\r\n\tseriesTypes = Highcharts.seriesTypes,\r\n\tmerge = Highcharts.merge,\r\n\tnoop = function () {},\r\n\teach = Highcharts.each;\r\n\r\n// set default options\r\ndefaultPlotOptions.funnel = merge(defaultPlotOptions.pie, {\r\n\tcenter: ['50%', '50%'],\r\n\twidth: '90%',\r\n\tneckWidth: '30%',\r\n\theight: '100%',\r\n\tneckHeight: '25%',\r\n\r\n\tdataLabels: {\r\n\t\t//position: 'right',\r\n\t\tconnectorWidth: 1,\r\n\t\tconnectorColor: '#606060'\r\n\t},\r\n\tsize: true, // to avoid adapting to data label size in Pie.drawDataLabels\r\n\tstates: {\r\n\t\tselect: {\r\n\t\t\tcolor: '#C0C0C0',\r\n\t\t\tborderColor: '#000000',\r\n\t\t\tshadow: false\r\n\t\t}\r\n\t}\t\r\n});\r\n\r\n\r\nseriesTypes.funnel = Highcharts.extendClass(seriesTypes.pie, {\r\n\t\r\n\ttype: 'funnel',\r\n\tanimate: noop,\r\n\r\n\t/**\r\n\t * Overrides the pie translate method\r\n\t */\r\n\ttranslate: function () {\r\n\t\t\r\n\t\tvar \r\n\t\t\t// Get positions - either an integer or a percentage string must be given\r\n\t\t\tgetLength = function (length, relativeTo) {\r\n\t\t\t\treturn (/%$/).test(length) ?\r\n\t\t\t\t\trelativeTo * parseInt(length, 10) / 100 :\r\n\t\t\t\t\tparseInt(length, 10);\r\n\t\t\t},\r\n\t\t\t\r\n\t\t\tsum = 0,\r\n\t\t\tseries = this,\r\n\t\t\tchart = series.chart,\r\n\t\t\tplotWidth = chart.plotWidth,\r\n\t\t\tplotHeight = chart.plotHeight,\r\n\t\t\tcumulative = 0, // start at top\r\n\t\t\toptions = series.options,\r\n\t\t\tcenter = options.center,\r\n\t\t\tcenterX = getLength(center[0], plotWidth),\r\n\t\t\tcenterY = getLength(center[0], plotHeight),\r\n\t\t\twidth = getLength(options.width, plotWidth),\r\n\t\t\ttempWidth,\r\n\t\t\tgetWidthAt,\r\n\t\t\theight = getLength(options.height, plotHeight),\r\n\t\t\tneckWidth = getLength(options.neckWidth, plotWidth),\r\n\t\t\tneckHeight = getLength(options.neckHeight, plotHeight),\r\n\t\t\tneckY = height - neckHeight,\r\n\t\t\tdata = series.data,\r\n\t\t\tpath,\r\n\t\t\tfraction,\r\n\t\t\thalf = options.dataLabels.position === 'left' ? 1 : 0,\r\n\r\n\t\t\tx1, \r\n\t\t\ty1, \r\n\t\t\tx2, \r\n\t\t\tx3, \r\n\t\t\ty3, \r\n\t\t\tx4, \r\n\t\t\ty5;\r\n\r\n\t\t// Return the width at a specific y coordinate\r\n\t\tseries.getWidthAt = getWidthAt = function (y) {\r\n\t\t\treturn y > height - neckHeight || height === neckHeight ?\r\n\t\t\t\tneckWidth :\r\n\t\t\t\tneckWidth + (width - neckWidth) * ((height - neckHeight - y) / (height - neckHeight));\r\n\t\t};\r\n\t\tseries.getX = function (y, half) {\r\n\t\t\treturn centerX + (half ? -1 : 1) * ((getWidthAt(y) / 2) + options.dataLabels.distance);\r\n\t\t};\r\n\r\n\t\t// Expose\r\n\t\tseries.center = [centerX, centerY, height];\r\n\t\tseries.centerX = centerX;\r\n\r\n\t\t/*\r\n\t\t * Individual point coordinate naming:\r\n\t\t *\r\n\t\t * x1,y1 _________________ x2,y1\r\n\t\t *  \\                         /\r\n\t\t *   \\                       /\r\n\t\t *    \\                     /\r\n\t\t *     \\                   /\r\n\t\t *      \\                 /\r\n\t\t *     x3,y3 _________ x4,y3\r\n\t\t *\r\n\t\t * Additional for the base of the neck:\r\n\t\t *\r\n\t\t *       |               |\r\n\t\t *       |               |\r\n\t\t *       |               |\r\n\t\t *     x3,y5 _________ x4,y5\r\n\t\t */\r\n\r\n\r\n\r\n\r\n\t\t// get the total sum\r\n\t\teach(data, function (point) {\r\n\t\t\tsum += point.y;\r\n\t\t});\r\n\r\n\t\teach(data, function (point) {\r\n\t\t\t// set start and end positions\r\n\t\t\ty5 = null;\r\n\t\t\tfraction = sum ? point.y / sum : 0;\r\n\t\t\ty1 = centerY - height / 2 + cumulative * height;\r\n\t\t\ty3 = y1 + fraction * height;\r\n\t\t\t//tempWidth = neckWidth + (width - neckWidth) * ((height - neckHeight - y1) / (height - neckHeight));\r\n\t\t\ttempWidth = getWidthAt(y1);\r\n\t\t\tx1 = centerX - tempWidth / 2;\r\n\t\t\tx2 = x1 + tempWidth;\r\n\t\t\ttempWidth = getWidthAt(y3);\r\n\t\t\tx3 = centerX - tempWidth / 2;\r\n\t\t\tx4 = x3 + tempWidth;\r\n\r\n\t\t\t// the entire point is within the neck\r\n\t\t\tif (y1 > neckY) {\r\n\t\t\t\tx1 = x3 = centerX - neckWidth / 2;\r\n\t\t\t\tx2 = x4 = centerX + neckWidth / 2;\r\n\t\t\t\r\n\t\t\t// the base of the neck\r\n\t\t\t} else if (y3 > neckY) {\r\n\t\t\t\ty5 = y3;\r\n\r\n\t\t\t\ttempWidth = getWidthAt(neckY);\r\n\t\t\t\tx3 = centerX - tempWidth / 2;\r\n\t\t\t\tx4 = x3 + tempWidth;\r\n\r\n\t\t\t\ty3 = neckY;\r\n\t\t\t}\r\n\r\n\t\t\t// save the path\r\n\t\t\tpath = [\r\n\t\t\t\t'M',\r\n\t\t\t\tx1, y1,\r\n\t\t\t\t'L',\r\n\t\t\t\tx2, y1,\r\n\t\t\t\tx4, y3\r\n\t\t\t];\r\n\t\t\tif (y5) {\r\n\t\t\t\tpath.push(x4, y5, x3, y5);\r\n\t\t\t}\r\n\t\t\tpath.push(x3, y3, 'Z');\r\n\r\n\t\t\t// prepare for using shared dr\r\n\t\t\tpoint.shapeType = 'path';\r\n\t\t\tpoint.shapeArgs = { d: path };\r\n\r\n\r\n\t\t\t// for tooltips and data labels\r\n\t\t\tpoint.percentage = fraction * 100;\r\n\t\t\tpoint.plotX = centerX;\r\n\t\t\tpoint.plotY = (y1 + (y5 || y3)) / 2;\r\n\r\n\t\t\t// Placement of tooltips and data labels\r\n\t\t\tpoint.tooltipPos = [\r\n\t\t\t\tcenterX,\r\n\t\t\t\tpoint.plotY\r\n\t\t\t];\r\n\r\n\t\t\t// Slice is a noop on funnel points\r\n\t\t\tpoint.slice = noop;\r\n\t\t\t\r\n\t\t\t// Mimicking pie data label placement logic\r\n\t\t\tpoint.half = half;\r\n\r\n\t\t\tcumulative += fraction;\r\n\t\t});\r\n\r\n\r\n\t\tseries.setTooltipPoints();\r\n\t},\r\n\t/**\r\n\t * Draw a single point (wedge)\r\n\t * @param {Object} point The point object\r\n\t * @param {Object} color The color of the point\r\n\t * @param {Number} brightness The brightness relative to the color\r\n\t */\r\n\tdrawPoints: function () {\r\n\t\tvar series = this,\r\n\t\t\toptions = series.options,\r\n\t\t\tchart = series.chart,\r\n\t\t\trenderer = chart.renderer;\r\n\r\n\t\teach(series.data, function (point) {\r\n\t\t\t\r\n\t\t\tvar graphic = point.graphic,\r\n\t\t\t\tshapeArgs = point.shapeArgs;\r\n\r\n\t\t\tif (!graphic) { // Create the shapes\r\n\t\t\t\tpoint.graphic = renderer.path(shapeArgs).\r\n\t\t\t\t\tattr({\r\n\t\t\t\t\t\tfill: point.color,\r\n\t\t\t\t\t\tstroke: options.borderColor,\r\n\t\t\t\t\t\t'stroke-width': options.borderWidth\r\n\t\t\t\t\t}).\r\n\t\t\t\t\tadd(series.group);\r\n\t\t\t\t\t\r\n\t\t\t} else { // Update the shapes\r\n\t\t\t\tgraphic.animate(shapeArgs);\r\n\t\t\t}\r\n\t\t});\r\n\t},\r\n\r\n\t/**\r\n\t * Funnel items don't have angles (#2289)\r\n\t */\r\n\tsortByAngle: noop,\r\n\t\r\n\t/**\r\n\t * Extend the pie data label method\r\n\t */\r\n\tdrawDataLabels: function () {\r\n\t\tvar data = this.data,\r\n\t\t\tlabelDistance = this.options.dataLabels.distance,\r\n\t\t\tleftSide,\r\n\t\t\tsign,\r\n\t\t\tpoint,\r\n\t\t\ti = data.length,\r\n\t\t\tx,\r\n\t\t\ty;\r\n\t\t\r\n\t\t// In the original pie label anticollision logic, the slots are distributed\r\n\t\t// from one labelDistance above to one labelDistance below the pie. In funnels\r\n\t\t// we don't want this.\r\n\t\tthis.center[2] -= 2 * labelDistance;\r\n\t\t\r\n\t\t// Set the label position array for each point.\r\n\t\twhile (i--) {\r\n\t\t\tpoint = data[i];\r\n\t\t\tleftSide = point.half;\r\n\t\t\tsign = leftSide ? 1 : -1;\r\n\t\t\ty = point.plotY;\r\n\t\t\tx = this.getX(y, leftSide);\r\n\t\t\t\t\r\n\t\t\t// set the anchor point for data labels\r\n\t\t\tpoint.labelPos = [\r\n\t\t\t\t0, // first break of connector\r\n\t\t\t\ty, // a/a\r\n\t\t\t\tx + (labelDistance - 5) * sign, // second break, right outside point shape\r\n\t\t\t\ty, // a/a\r\n\t\t\t\tx + labelDistance * sign, // landing point for connector\r\n\t\t\t\ty, // a/a\r\n\t\t\t\tleftSide ? 'right' : 'left', // alignment\r\n\t\t\t\t0 // center angle\r\n\t\t\t];\r\n\t\t}\r\n\t\t\r\n\t\tseriesTypes.pie.prototype.drawDataLabels.call(this);\r\n\t}\r\n\r\n});\r\n\r\n\r\n}(Highcharts));\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/highcharts/modules/heatmap.js",
    "content": "(function(b){var k=b.seriesTypes,l=b.each;k.heatmap=b.extendClass(k.map,{colorKey:\"z\",useMapGeometry:!1,pointArrayMap:[\"y\",\"z\"],translate:function(){var c=this,b=c.options,i=Number.MAX_VALUE,j=Number.MIN_VALUE;c.generatePoints();l(c.data,function(a){var e=a.x,f=a.y,d=a.z,g=(b.colsize||1)/2,h=(b.rowsize||1)/2;a.path=[\"M\",e-g,f-h,\"L\",e+g,f-h,\"L\",e+g,f+h,\"L\",e-g,f+h,\"Z\"];a.shapeType=\"path\";a.shapeArgs={d:c.translatePath(a.path)};typeof d===\"number\"&&(d>j?j=d:d<i&&(i=d))});c.translateColors(i,j)},getBox:function(){}})})(Highcharts);\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/highcharts/modules/heatmap.src.js",
    "content": "(function (Highcharts) {\r\n\tvar seriesTypes = Highcharts.seriesTypes,\r\n\t\teach = Highcharts.each;\r\n\t\r\n\tseriesTypes.heatmap = Highcharts.extendClass(seriesTypes.map, {\r\n\t\tcolorKey: 'z',\r\n\t\tuseMapGeometry: false,\r\n\t\tpointArrayMap: ['y', 'z'],\r\n\t\ttranslate: function () {\r\n\t\t\tvar series = this,\r\n\t\t\t\toptions = series.options,\r\n\t\t\t\tdataMin = Number.MAX_VALUE,\r\n\t\t\t\tdataMax = Number.MIN_VALUE;\r\n\r\n\t\t\tseries.generatePoints();\r\n\t\r\n\t\t\teach(series.data, function (point) {\r\n\t\t\t\tvar x = point.x,\r\n\t\t\t\t\ty = point.y,\r\n\t\t\t\t\tvalue = point.z,\r\n\t\t\t\t\txPad = (options.colsize || 1) / 2,\r\n\t\t\t\t\tyPad = (options.rowsize || 1) / 2;\r\n\r\n\t\t\t\tpoint.path = [\r\n\t\t\t\t\t'M', x - xPad, y - yPad,\r\n\t\t\t\t\t'L', x + xPad, y - yPad,\r\n\t\t\t\t\t'L', x + xPad, y + yPad,\r\n\t\t\t\t\t'L', x - xPad, y + yPad,\r\n\t\t\t\t\t'Z'\r\n\t\t\t\t];\r\n\t\t\t\t\r\n\t\t\t\tpoint.shapeType = 'path';\r\n\t\t\t\tpoint.shapeArgs = {\r\n\t\t\t\t\td: series.translatePath(point.path)\r\n\t\t\t\t};\r\n\t\t\t\t\r\n\t\t\t\tif (typeof value === 'number') {\r\n\t\t\t\t\tif (value > dataMax) {\r\n\t\t\t\t\t\tdataMax = value;\r\n\t\t\t\t\t} else if (value < dataMin) {\r\n\t\t\t\t\t\tdataMin = value;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t\t\r\n\t\t\tseries.translateColors(dataMin, dataMax);\r\n\t\t},\r\n\t\t\r\n\t\tgetBox: function () {}\r\n\t\t\t\r\n\t});\r\n\t\r\n}(Highcharts));\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/highcharts/modules/map.js",
    "content": "/*\r\n Map plugin v0.1 for Highcharts\r\n\r\n (c) 2011-2013 Torstein Hønsi\r\n\r\n License: www.highcharts.com/license\r\n*/\r\n(function(g){function x(a,b,c){for(var d=4,e=[];d--;)e[d]=Math.round(b.rgba[d]+(a.rgba[d]-b.rgba[d])*(1-c));return\"rgba(\"+e.join(\",\")+\")\"}var r=g.Axis,y=g.Chart,s=g.Point,z=g.Pointer,l=g.each,v=g.extend,p=g.merge,n=g.pick,A=g.numberFormat,B=g.getOptions(),k=g.seriesTypes,q=B.plotOptions,t=g.wrap,u=g.Color,w=function(){};B.mapNavigation={buttonOptions:{align:\"right\",verticalAlign:\"bottom\",x:0,width:18,height:18,style:{fontSize:\"15px\",fontWeight:\"bold\",textAlign:\"center\"}},buttons:{zoomIn:{onclick:function(){this.mapZoom(0.5)},\r\ntext:\"+\",y:-32},zoomOut:{onclick:function(){this.mapZoom(2)},text:\"-\",y:0}}};g.splitPath=function(a){var b,a=a.replace(/([A-Za-z])/g,\" $1 \"),a=a.replace(/^\\s*/,\"\").replace(/\\s*$/,\"\"),a=a.split(/[ ,]+/);for(b=0;b<a.length;b++)/[a-zA-Z]/.test(a[b])||(a[b]=parseFloat(a[b]));return a};g.maps={};t(r.prototype,\"getSeriesExtremes\",function(a){var b=this.isXAxis,c,d,e=[];l(this.series,function(a,b){if(a.useMapGeometry)e[b]=a.xData,a.xData=[]});a.call(this);c=n(this.dataMin,Number.MAX_VALUE);d=n(this.dataMax,\r\nNumber.MIN_VALUE);l(this.series,function(a,i){if(a.useMapGeometry)c=Math.min(c,a[b?\"minX\":\"minY\"]),d=Math.max(d,a[b?\"maxX\":\"maxY\"]),a.xData=e[i]});this.dataMin=c;this.dataMax=d});t(r.prototype,\"setAxisTranslation\",function(a){var b=this.chart,c=b.plotWidth/b.plotHeight,d=this.isXAxis,e=b.xAxis[0];a.call(this);if(b.options.chart.type===\"map\"&&!d&&e.transA!==void 0)this.transA=e.transA=Math.min(this.transA,e.transA),a=(e.max-e.min)/(this.max-this.min),e=a>c?this:e,c=(e.max-e.min)*e.transA,e.minPixelPadding=\r\n(e.len-c)/2});t(y.prototype,\"render\",function(a){var b=this,c=b.options.mapNavigation;a.call(b);b.renderMapNavigation();c.zoomOnDoubleClick&&g.addEvent(b.container,\"dblclick\",function(a){b.pointer.onContainerDblClick(a)});c.zoomOnMouseWheel&&g.addEvent(b.container,document.onmousewheel===void 0?\"DOMMouseScroll\":\"mousewheel\",function(a){b.pointer.onContainerMouseWheel(a)})});v(z.prototype,{onContainerDblClick:function(a){var b=this.chart,a=this.normalize(a);b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-\r\nb.plotTop)&&b.mapZoom(0.5,b.xAxis[0].toValue(a.chartX),b.yAxis[0].toValue(a.chartY))},onContainerMouseWheel:function(a){var b=this.chart,c,a=this.normalize(a);c=a.detail||-(a.wheelDelta/120);b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-b.plotTop)&&b.mapZoom(c>0?2:0.5,b.xAxis[0].toValue(a.chartX),b.yAxis[0].toValue(a.chartY))}});t(z.prototype,\"init\",function(a,b,c){a.call(this,b,c);if(c.mapNavigation.enableTouchZoom)this.pinchX=this.pinchHor=this.pinchY=this.pinchVert=!0});v(y.prototype,{renderMapNavigation:function(){var a=\r\nthis,b=this.options.mapNavigation,c=b.buttons,d,e,f,i=function(){this.handler.call(a)};if(b.enableButtons)for(d in c)if(c.hasOwnProperty(d))f=p(b.buttonOptions,c[d]),e=a.renderer.button(f.text,0,0,i).attr({width:f.width,height:f.height}).css(f.style).add(),e.handler=f.onclick,e.align(v(f,{width:e.width,height:e.height}),null,\"spacingBox\")},fitToBox:function(a,b){l([[\"x\",\"width\"],[\"y\",\"height\"]],function(c){var d=c[0],c=c[1];a[d]+a[c]>b[d]+b[c]&&(a[c]>b[c]?(a[c]=b[c],a[d]=b[d]):a[d]=b[d]+b[c]-a[c]);\r\na[c]>b[c]&&(a[c]=b[c]);a[d]<b[d]&&(a[d]=b[d])});return a},mapZoom:function(a,b,c){if(!this.isMapZooming){var d=this,e=d.xAxis[0],f=e.max-e.min,i=n(b,e.min+f/2),b=f*a,f=d.yAxis[0],h=f.max-f.min,c=n(c,f.min+h/2);a*=h;i-=b/2;h=c-a/2;c=n(d.options.chart.animation,!0);b=d.fitToBox({x:i,y:h,width:b,height:a},{x:e.dataMin,y:f.dataMin,width:e.dataMax-e.dataMin,height:f.dataMax-f.dataMin});e.setExtremes(b.x,b.x+b.width,!1);f.setExtremes(b.y,b.y+b.height,!1);if(e=c?c.duration||500:0)d.isMapZooming=!0,setTimeout(function(){d.isMapZooming=\r\n!1},e);d.redraw()}}});q.map=p(q.scatter,{animation:!1,nullColor:\"#F8F8F8\",borderColor:\"silver\",borderWidth:1,marker:null,stickyTracking:!1,dataLabels:{verticalAlign:\"middle\"},turboThreshold:0,tooltip:{followPointer:!0,pointFormat:\"{point.name}: {point.y}<br/>\"},states:{normal:{animation:!0}}});r=g.extendClass(s,{applyOptions:function(a,b){var c=s.prototype.applyOptions.call(this,a,b);if(c.path&&typeof c.path===\"string\")c.path=c.options.path=g.splitPath(c.path);return c},onMouseOver:function(){clearTimeout(this.colorInterval);\r\ns.prototype.onMouseOver.call(this)},onMouseOut:function(){var a=this,b=+new Date,c=u(a.options.color),d=u(a.pointAttr.hover.fill),e=a.series.options.states.normal.animation,f=e&&(e.duration||500);if(f&&c.rgba.length===4&&d.rgba.length===4)delete a.pointAttr[\"\"].fill,clearTimeout(a.colorInterval),a.colorInterval=setInterval(function(){var e=(new Date-b)/f,h=a.graphic;e>1&&(e=1);h&&h.attr(\"fill\",x(d,c,e));e>=1&&clearTimeout(a.colorInterval)},13);s.prototype.onMouseOut.call(a)}});k.map=g.extendClass(k.scatter,\r\n{type:\"map\",pointAttrToOptions:{stroke:\"borderColor\",\"stroke-width\":\"borderWidth\",fill:\"color\"},colorKey:\"y\",pointClass:r,trackerGroups:[\"group\",\"markerGroup\",\"dataLabelsGroup\"],getSymbol:w,supportsDrilldown:!0,getExtremesFromAll:!0,useMapGeometry:!0,init:function(a){var b=this,c=a.options.legend.valueDecimals,d=[],e,f,i,h,j,o,m;o=a.options.legend.layout===\"horizontal\";g.Series.prototype.init.apply(this,arguments);j=b.options.colorRange;if(h=b.options.valueRanges)l(h,function(a){f=a.from;i=a.to;e=\r\n\"\";f===void 0?e=\"< \":i===void 0&&(e=\"> \");f!==void 0&&(e+=A(f,c));f!==void 0&&i!==void 0&&(e+=\" - \");i!==void 0&&(e+=A(i,c));d.push(g.extend({chart:b.chart,name:e,options:{},drawLegendSymbol:k.area.prototype.drawLegendSymbol,visible:!0,setState:function(){},setVisible:function(){}},a))}),b.legendItems=d;else if(j)f=j.from,i=j.to,h=j.fromLabel,j=j.toLabel,m=o?[0,0,1,0]:[0,1,0,0],o||(o=h,h=j,j=o),o={linearGradient:{x1:m[0],y1:m[1],x2:m[2],y2:m[3]},stops:[[0,f],[1,i]]},d=[{chart:b.chart,options:{},fromLabel:h,\r\ntoLabel:j,color:o,drawLegendSymbol:this.drawLegendSymbolGradient,visible:!0,setState:function(){},setVisible:function(){}}],b.legendItems=d},drawLegendSymbol:k.area.prototype.drawLegendSymbol,drawLegendSymbolGradient:function(a,b){var c=a.options.symbolPadding,d=n(a.options.padding,8),e,f,i=this.chart.renderer.fontMetrics(a.options.itemStyle.fontSize).h,h=a.options.layout===\"horizontal\",j;j=n(a.options.rectangleLength,200);h?(e=-(c/2),f=0):(e=-j+a.baseline-c/2,f=d+i);b.fromText=this.chart.renderer.text(b.fromLabel,\r\nf,e).attr({zIndex:2}).add(b.legendGroup);f=b.fromText.getBBox();b.legendSymbol=this.chart.renderer.rect(h?f.x+f.width+c:f.x-i-c,f.y,h?j:i,h?i:j,2).attr({zIndex:1}).add(b.legendGroup);j=b.legendSymbol.getBBox();b.toText=this.chart.renderer.text(b.toLabel,j.x+j.width+c,h?e:j.y+j.height-c).attr({zIndex:2}).add(b.legendGroup);e=b.toText.getBBox();h?(a.offsetWidth=f.width+j.width+e.width+c*2+d,a.itemY=i+d):(a.offsetWidth=Math.max(f.width,e.width)+c+j.width+d,a.itemY=j.height+d,a.itemX=c)},getBox:function(a){var b=\r\nNumber.MIN_VALUE,c=Number.MAX_VALUE,d=Number.MIN_VALUE,e=Number.MAX_VALUE;l(a||this.options.data,function(a){for(var i=a.path,h=i.length,j=!1,g=Number.MIN_VALUE,m=Number.MAX_VALUE,k=Number.MIN_VALUE,l=Number.MAX_VALUE;h--;)typeof i[h]===\"number\"&&!isNaN(i[h])&&(j?(g=Math.max(g,i[h]),m=Math.min(m,i[h])):(k=Math.max(k,i[h]),l=Math.min(l,i[h])),j=!j);a._maxX=g;a._minX=m;a._maxY=k;a._minY=l;b=Math.max(b,g);c=Math.min(c,m);d=Math.max(d,k);e=Math.min(e,l)});this.minY=e;this.maxY=d;this.minX=c;this.maxX=\r\nb},translatePath:function(a){var b=!1,c=this.xAxis,d=this.yAxis,e,a=[].concat(a);for(e=a.length;e--;)typeof a[e]===\"number\"&&(a[e]=b?Math.round(c.translate(a[e])):Math.round(d.len-d.translate(a[e])),b=!b);return a},setData:function(){g.Series.prototype.setData.apply(this,arguments);this.getBox()},translate:function(){var a=this,b=Number.MAX_VALUE,c=Number.MIN_VALUE;a.generatePoints();l(a.data,function(d){d.shapeType=\"path\";d.shapeArgs={d:a.translatePath(d.path)};if(typeof d.y===\"number\")if(d.y>c)c=\r\nd.y;else if(d.y<b)b=d.y});a.translateColors(b,c)},translateColors:function(a,b){var c=this.options,d=c.valueRanges,e=c.colorRange,f=this.colorKey,i,h;e&&(i=u(e.from),h=u(e.to));l(this.data,function(g){var k=g[f],m,l,n;if(d)for(n=d.length;n--;){if(m=d[n],i=m.from,h=m.to,(i===void 0||k>=i)&&(h===void 0||k<=h)){l=m.color;break}}else e&&k!==void 0&&(m=1-(b-k)/(b-a),l=k===null?c.nullColor:x(i,h,m));if(l)g.color=null,g.options.color=l})},drawGraph:w,drawDataLabels:w,drawPoints:function(){var a=this.xAxis,\r\nb=this.yAxis,c=this.colorKey;l(this.data,function(a){a.plotY=1;if(a[c]===null)a[c]=0,a.isNull=!0});k.column.prototype.drawPoints.apply(this);l(this.data,function(d){var e=d.dataLabels,f=a.toPixels(d._minX,!0),g=a.toPixels(d._maxX,!0),h=b.toPixels(d._minY,!0),j=b.toPixels(d._maxY,!0);d.plotX=Math.round(f+(g-f)*n(e&&e.anchorX,0.5));d.plotY=Math.round(h+(j-h)*n(e&&e.anchorY,0.5));d.isNull&&(d[c]=null)});g.Series.prototype.drawDataLabels.call(this)},animateDrilldown:function(a){var b=this.chart.plotBox,\r\nc=this.chart.drilldownLevels[this.chart.drilldownLevels.length-1],d=c.bBox,e=this.chart.options.drilldown.animation;if(!a)a=Math.min(d.width/b.width,d.height/b.height),c.shapeArgs={scaleX:a,scaleY:a,translateX:d.x,translateY:d.y},l(this.points,function(a){a.graphic.attr(c.shapeArgs).animate({scaleX:1,scaleY:1,translateX:0,translateY:0},e)}),delete this.animate},animateDrillupFrom:function(a){k.column.prototype.animateDrillupFrom.call(this,a)},animateDrillupTo:function(a){k.column.prototype.animateDrillupTo.call(this,\r\na)}});q.mapline=p(q.map,{lineWidth:1,backgroundColor:\"none\"});k.mapline=g.extendClass(k.map,{type:\"mapline\",pointAttrToOptions:{stroke:\"color\",\"stroke-width\":\"lineWidth\",fill:\"backgroundColor\"},drawLegendSymbol:k.line.prototype.drawLegendSymbol});q.mappoint=p(q.scatter,{dataLabels:{enabled:!0,format:\"{point.name}\",color:\"black\",style:{textShadow:\"0 0 5px white\"}}});k.mappoint=g.extendClass(k.scatter,{type:\"mappoint\"});g.Map=function(a,b){var c={endOnTick:!1,gridLineWidth:0,labels:{enabled:!1},lineWidth:0,\r\nminPadding:0,maxPadding:0,startOnTick:!1,tickWidth:0,title:null},d;d=a.series;a.series=null;a=p({chart:{type:\"map\",panning:\"xy\"},xAxis:c,yAxis:p(c,{reversed:!0})},a,{chart:{inverted:!1}});a.series=d;return new g.Chart(a,b)}})(Highcharts);\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/highcharts/modules/map.src.js",
    "content": "/**\r\n * @license Map plugin v0.1 for Highcharts\r\n *\r\n * (c) 2011-2013 Torstein Hønsi\r\n *\r\n * License: www.highcharts.com/license\r\n */\r\n\r\n/* \r\n * See www.highcharts.com/studies/world-map.htm for use case.\r\n *\r\n * To do:\r\n * - Optimize long variable names and alias adapter methods and Highcharts namespace variables\r\n * - Zoom and pan GUI\r\n */\r\n(function (Highcharts) {\r\n\tvar UNDEFINED,\r\n\t\tAxis = Highcharts.Axis,\r\n\t\tChart = Highcharts.Chart,\r\n\t\tPoint = Highcharts.Point,\r\n\t\tPointer = Highcharts.Pointer,\r\n\t\teach = Highcharts.each,\r\n\t\textend = Highcharts.extend,\r\n\t\tmerge = Highcharts.merge,\r\n\t\tpick = Highcharts.pick,\r\n\t\tnumberFormat = Highcharts.numberFormat,\r\n\t\tdefaultOptions = Highcharts.getOptions(),\r\n\t\tseriesTypes = Highcharts.seriesTypes,\r\n\t\tplotOptions = defaultOptions.plotOptions,\r\n\t\twrap = Highcharts.wrap,\r\n\t\tColor = Highcharts.Color,\r\n\t\tnoop = function () {};\r\n\r\n\t\r\n\r\n\t/*\r\n\t * Return an intermediate color between two colors, according to pos where 0\r\n\t * is the from color and 1 is the to color\r\n\t */\r\n\tfunction tweenColors(from, to, pos) {\r\n\t\tvar i = 4,\r\n\t\t\trgba = [];\r\n\r\n\t\twhile (i--) {\r\n\t\t\trgba[i] = Math.round(\r\n\t\t\t\tto.rgba[i] + (from.rgba[i] - to.rgba[i]) * (1 - pos)\r\n\t\t\t);\r\n\t\t}\r\n\t\treturn 'rgba(' + rgba.join(',') + ')';\r\n\t}\r\n\r\n\t// Set the default map navigation options\r\n\tdefaultOptions.mapNavigation = {\r\n\t\tbuttonOptions: {\r\n\t\t\talign: 'right',\r\n\t\t\tverticalAlign: 'bottom',\r\n\t\t\tx: 0,\r\n\t\t\twidth: 18,\r\n\t\t\theight: 18,\r\n\t\t\tstyle: {\r\n\t\t\t\tfontSize: '15px',\r\n\t\t\t\tfontWeight: 'bold',\r\n\t\t\t\ttextAlign: 'center'\r\n\t\t\t}\r\n\t\t},\r\n\t\tbuttons: {\r\n\t\t\tzoomIn: {\r\n\t\t\t\tonclick: function () {\r\n\t\t\t\t\tthis.mapZoom(0.5);\r\n\t\t\t\t},\r\n\t\t\t\ttext: '+',\r\n\t\t\t\ty: -32\r\n\t\t\t},\r\n\t\t\tzoomOut: {\r\n\t\t\t\tonclick: function () {\r\n\t\t\t\t\tthis.mapZoom(2);\r\n\t\t\t\t},\r\n\t\t\t\ttext: '-',\r\n\t\t\t\ty: 0\r\n\t\t\t}\r\n\t\t}\r\n\t\t// enableButtons: false,\r\n\t\t// enableTouchZoom: false,\r\n\t\t// zoomOnDoubleClick: false,\r\n\t\t// zoomOnMouseWheel: false\r\n\r\n\t};\r\n\t\r\n\t/**\r\n\t * Utility for reading SVG paths directly.\r\n\t */\r\n\tHighcharts.splitPath = function (path) {\r\n\t\tvar i;\r\n\r\n\t\t// Move letters apart\r\n\t\tpath = path.replace(/([A-Za-z])/g, ' $1 ');\r\n\t\t// Trim\r\n\t\tpath = path.replace(/^\\s*/, \"\").replace(/\\s*$/, \"\");\r\n\t\t\r\n\t\t// Split on spaces and commas\r\n\t\tpath = path.split(/[ ,]+/);\r\n\t\t\r\n\t\t// Parse numbers\r\n\t\tfor (i = 0; i < path.length; i++) {\r\n\t\t\tif (!/[a-zA-Z]/.test(path[i])) {\r\n\t\t\t\tpath[i] = parseFloat(path[i]);\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn path;\r\n\t};\r\n\r\n\t// A placeholder for map definitions\r\n\tHighcharts.maps = {};\r\n\t\r\n\t/**\r\n\t * Override to use the extreme coordinates from the SVG shape, not the\r\n\t * data values\r\n\t */\r\n\twrap(Axis.prototype, 'getSeriesExtremes', function (proceed) {\r\n\t\tvar isXAxis = this.isXAxis,\r\n\t\t\tdataMin,\r\n\t\t\tdataMax,\r\n\t\t\txData = [];\r\n\r\n\t\t// Remove the xData array and cache it locally so that the proceed method doesn't use it\r\n\t\teach(this.series, function (series, i) {\r\n\t\t\tif (series.useMapGeometry) {\r\n\t\t\t\txData[i] = series.xData;\r\n\t\t\t\tseries.xData = [];\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\t// Call base to reach normal cartesian series (like mappoint)\r\n\t\tproceed.call(this);\r\n\r\n\t\t// Run extremes logic for map and mapline\r\n\t\tdataMin = pick(this.dataMin, Number.MAX_VALUE);\r\n\t\tdataMax = pick(this.dataMax, Number.MIN_VALUE);\r\n\t\teach(this.series, function (series, i) {\r\n\t\t\tif (series.useMapGeometry) {\r\n\t\t\t\tdataMin = Math.min(dataMin, series[isXAxis ? 'minX' : 'minY']);\r\n\t\t\t\tdataMax = Math.max(dataMax, series[isXAxis ? 'maxX' : 'maxY']);\r\n\t\t\t\tseries.xData = xData[i]; // Reset xData array\r\n\t\t\t}\r\n\t\t});\r\n\t\t\r\n\t\tthis.dataMin = dataMin;\r\n\t\tthis.dataMax = dataMax;\r\n\t});\r\n\t\r\n\t/**\r\n\t * Override axis translation to make sure the aspect ratio is always kept\r\n\t */\r\n\twrap(Axis.prototype, 'setAxisTranslation', function (proceed) {\r\n\t\tvar chart = this.chart,\r\n\t\t\tmapRatio,\r\n\t\t\tplotRatio = chart.plotWidth / chart.plotHeight,\r\n\t\t\tisXAxis = this.isXAxis,\r\n\t\t\tadjustedAxisLength,\r\n\t\t\txAxis = chart.xAxis[0],\r\n\t\t\tpadAxis;\r\n\t\t\r\n\t\t// Run the parent method\r\n\t\tproceed.call(this);\r\n\t\t\r\n\t\t// On Y axis, handle both\r\n\t\tif (chart.options.chart.type === 'map' && !isXAxis && xAxis.transA !== UNDEFINED) {\r\n\t\t\t\r\n\t\t\t// Use the same translation for both axes\r\n\t\t\tthis.transA = xAxis.transA = Math.min(this.transA, xAxis.transA);\r\n\t\t\t\r\n\t\t\tmapRatio = (xAxis.max - xAxis.min) / (this.max - this.min);\r\n\t\t\t\r\n\t\t\t// What axis to pad to put the map in the middle\r\n\t\t\tpadAxis = mapRatio > plotRatio ? this : xAxis;\r\n\t\t\t\r\n\t\t\t// Pad it\r\n\t\t\tadjustedAxisLength = (padAxis.max - padAxis.min) * padAxis.transA;\r\n\t\t\tpadAxis.minPixelPadding = (padAxis.len - adjustedAxisLength) / 2;\r\n\t\t}\r\n\t});\r\n\r\n\r\n\t//--- Start zooming and panning features\r\n\r\n\twrap(Chart.prototype, 'render', function (proceed) {\r\n\t\tvar chart = this,\r\n\t\t\tmapNavigation = chart.options.mapNavigation;\r\n\r\n\t\tproceed.call(chart);\r\n\r\n\t\t// Render the plus and minus buttons\r\n\t\tchart.renderMapNavigation();\r\n\r\n\t\t// Add the double click event\r\n\t\tif (mapNavigation.zoomOnDoubleClick) {\r\n\t\t\tHighcharts.addEvent(chart.container, 'dblclick', function (e) {\r\n\t\t\t\tchart.pointer.onContainerDblClick(e);\r\n\t\t\t});\r\n\t\t}\r\n\r\n\t\t// Add the mousewheel event\r\n\t\tif (mapNavigation.zoomOnMouseWheel) {\r\n\t\t\tHighcharts.addEvent(chart.container, document.onmousewheel === undefined ? 'DOMMouseScroll' : 'mousewheel', function (e) {\r\n\t\t\t\tchart.pointer.onContainerMouseWheel(e);\r\n\t\t\t});\r\n\t\t}\r\n\t});\r\n\r\n\t// Extend the Pointer\r\n\textend(Pointer.prototype, {\r\n\r\n\t\t/**\r\n\t\t * The event handler for the doubleclick event\r\n\t\t */\r\n\t\tonContainerDblClick: function (e) {\r\n\t\t\tvar chart = this.chart;\r\n\r\n\t\t\te = this.normalize(e);\r\n\r\n\t\t\tif (chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) {\r\n\t\t\t\tchart.mapZoom(\r\n\t\t\t\t\t0.5,\r\n\t\t\t\t\tchart.xAxis[0].toValue(e.chartX),\r\n\t\t\t\t\tchart.yAxis[0].toValue(e.chartY)\r\n\t\t\t\t);\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\t/**\r\n\t\t * The event handler for the mouse scroll event\r\n\t\t */\r\n\t\tonContainerMouseWheel: function (e) {\r\n\t\t\tvar chart = this.chart,\r\n\t\t\t\tdelta;\r\n\r\n\t\t\te = this.normalize(e);\r\n\r\n\t\t\t// Firefox uses e.detail, WebKit and IE uses wheelDelta\r\n\t\t\tdelta = e.detail || -(e.wheelDelta / 120);\r\n\t\t\tif (chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) {\r\n\t\t\t\tchart.mapZoom(\r\n\t\t\t\t\tdelta > 0 ? 2 : 0.5,\r\n\t\t\t\t\tchart.xAxis[0].toValue(e.chartX),\r\n\t\t\t\t\tchart.yAxis[0].toValue(e.chartY)\r\n\t\t\t\t);\r\n\t\t\t}\r\n\t\t}\r\n\t});\r\n\t// Implement the pinchType option\r\n\twrap(Pointer.prototype, 'init', function (proceed, chart, options) {\r\n\r\n\t\tproceed.call(this, chart, options);\r\n\r\n\t\t// Pinch status\r\n\t\tif (options.mapNavigation.enableTouchZoom) {\r\n\t\t\tthis.pinchX = this.pinchHor = \r\n\t\t\t\tthis.pinchY = this.pinchVert = true;\r\n\t\t}\r\n\t});\r\n\r\n\t// Add events to the Chart object itself\r\n\textend(Chart.prototype, {\r\n\t\trenderMapNavigation: function () {\r\n\t\t\tvar chart = this,\r\n\t\t\t\toptions = this.options.mapNavigation,\r\n\t\t\t\tbuttons = options.buttons,\r\n\t\t\t\tn,\r\n\t\t\t\tbutton,\r\n\t\t\t\tbuttonOptions,\r\n\t\t\t\touterHandler = function () { \r\n\t\t\t\t\tthis.handler.call(chart); \r\n\t\t\t\t};\r\n\r\n\t\t\tif (options.enableButtons) {\r\n\t\t\t\tfor (n in buttons) {\r\n\t\t\t\t\tif (buttons.hasOwnProperty(n)) {\r\n\t\t\t\t\t\tbuttonOptions = merge(options.buttonOptions, buttons[n]);\r\n\r\n\t\t\t\t\t\tbutton = chart.renderer.button(buttonOptions.text, 0, 0, outerHandler)\r\n\t\t\t\t\t\t\t.attr({\r\n\t\t\t\t\t\t\t\twidth: buttonOptions.width,\r\n\t\t\t\t\t\t\t\theight: buttonOptions.height\r\n\t\t\t\t\t\t\t})\r\n\t\t\t\t\t\t\t.css(buttonOptions.style)\r\n\t\t\t\t\t\t\t.add();\r\n\t\t\t\t\t\tbutton.handler = buttonOptions.onclick;\r\n\t\t\t\t\t\tbutton.align(extend(buttonOptions, { width: button.width, height: button.height }), null, 'spacingBox');\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\t/**\r\n\t\t * Fit an inner box to an outer. If the inner box overflows left or right, align it to the sides of the\r\n\t\t * outer. If it overflows both sides, fit it within the outer. This is a pattern that occurs more places\r\n\t\t * in Highcharts, perhaps it should be elevated to a common utility function.\r\n\t\t */\r\n\t\tfitToBox: function (inner, outer) {\r\n\t\t\teach([['x', 'width'], ['y', 'height']], function (dim) {\r\n\t\t\t\tvar pos = dim[0],\r\n\t\t\t\t\tsize = dim[1];\r\n\t\t\t\tif (inner[pos] + inner[size] > outer[pos] + outer[size]) { // right overflow\r\n\t\t\t\t\tif (inner[size] > outer[size]) { // the general size is greater, fit fully to outer\r\n\t\t\t\t\t\tinner[size] = outer[size];\r\n\t\t\t\t\t\tinner[pos] = outer[pos];\r\n\t\t\t\t\t} else { // align right\r\n\t\t\t\t\t\tinner[pos] = outer[pos] + outer[size] - inner[size];\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\tif (inner[size] > outer[size]) {\r\n\t\t\t\t\tinner[size] = outer[size];\r\n\t\t\t\t}\r\n\t\t\t\tif (inner[pos] < outer[pos]) {\r\n\t\t\t\t\tinner[pos] = outer[pos];\r\n\t\t\t\t}\r\n\t\t\t\t\r\n\t\t\t});\r\n\r\n\t\t\treturn inner;\r\n\t\t},\r\n\r\n\t\t/**\r\n\t\t * Zoom the map in or out by a certain amount. Less than 1 zooms in, greater than 1 zooms out.\r\n\t\t */\r\n\t\tmapZoom: function (howMuch, centerXArg, centerYArg) {\r\n\r\n\t\t\tif (this.isMapZooming) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tvar chart = this,\r\n\t\t\t\txAxis = chart.xAxis[0],\r\n\t\t\t\txRange = xAxis.max - xAxis.min,\r\n\t\t\t\tcenterX = pick(centerXArg, xAxis.min + xRange / 2),\r\n\t\t\t\tnewXRange = xRange * howMuch,\r\n\t\t\t\tyAxis = chart.yAxis[0],\r\n\t\t\t\tyRange = yAxis.max - yAxis.min,\r\n\t\t\t\tcenterY = pick(centerYArg, yAxis.min + yRange / 2),\r\n\t\t\t\tnewYRange = yRange * howMuch,\r\n\t\t\t\tnewXMin = centerX - newXRange / 2,\r\n\t\t\t\tnewYMin = centerY - newYRange / 2,\r\n\t\t\t\tanimation = pick(chart.options.chart.animation, true),\r\n\t\t\t\tdelay,\r\n\t\t\t\tnewExt = chart.fitToBox({\r\n\t\t\t\t\tx: newXMin,\r\n\t\t\t\t\ty: newYMin,\r\n\t\t\t\t\twidth: newXRange,\r\n\t\t\t\t\theight: newYRange\r\n\t\t\t\t}, {\r\n\t\t\t\t\tx: xAxis.dataMin,\r\n\t\t\t\t\ty: yAxis.dataMin,\r\n\t\t\t\t\twidth: xAxis.dataMax - xAxis.dataMin,\r\n\t\t\t\t\theight: yAxis.dataMax - yAxis.dataMin\r\n\t\t\t\t});\r\n\r\n\t\t\txAxis.setExtremes(newExt.x, newExt.x + newExt.width, false);\r\n\t\t\tyAxis.setExtremes(newExt.y, newExt.y + newExt.height, false);\r\n\r\n\t\t\t// Prevent zooming until this one is finished animating\r\n\t\t\tdelay = animation ? animation.duration || 500 : 0;\r\n\t\t\tif (delay) {\r\n\t\t\t\tchart.isMapZooming = true;\r\n\t\t\t\tsetTimeout(function () {\r\n\t\t\t\t\tchart.isMapZooming = false;\r\n\t\t\t\t}, delay);\r\n\t\t\t}\r\n\r\n\t\t\tchart.redraw();\r\n\t\t}\r\n\t});\r\n\t\r\n\t/**\r\n\t * Extend the default options with map options\r\n\t */\r\n\tplotOptions.map = merge(plotOptions.scatter, {\r\n\t\tanimation: false, // makes the complex shapes slow\r\n\t\tnullColor: '#F8F8F8',\r\n\t\tborderColor: 'silver',\r\n\t\tborderWidth: 1,\r\n\t\tmarker: null,\r\n\t\tstickyTracking: false,\r\n\t\tdataLabels: {\r\n\t\t\tverticalAlign: 'middle'\r\n\t\t},\r\n\t\tturboThreshold: 0,\r\n\t\ttooltip: {\r\n\t\t\tfollowPointer: true,\r\n\t\t\tpointFormat: '{point.name}: {point.y}<br/>'\r\n\t\t},\r\n\t\tstates: {\r\n\t\t\tnormal: {\r\n\t\t\t\tanimation: true\r\n\t\t\t}\r\n\t\t}\r\n\t});\r\n\r\n\tvar MapAreaPoint = Highcharts.extendClass(Point, {\r\n\t\t/**\r\n\t\t * Extend the Point object to split paths\r\n\t\t */\r\n\t\tapplyOptions: function (options, x) {\r\n\r\n\t\t\tvar point = Point.prototype.applyOptions.call(this, options, x);\r\n\r\n\t\t\tif (point.path && typeof point.path === 'string') {\r\n\t\t\t\tpoint.path = point.options.path = Highcharts.splitPath(point.path);\r\n\t\t\t}\r\n\r\n\t\t\treturn point;\r\n\t\t},\r\n\t\t/**\r\n\t\t * Stop the fade-out \r\n\t\t */\r\n\t\tonMouseOver: function () {\r\n\t\t\tclearTimeout(this.colorInterval);\r\n\t\t\tPoint.prototype.onMouseOver.call(this);\r\n\t\t},\r\n\t\t/**\r\n\t\t * Custom animation for tweening out the colors. Animation reduces blinking when hovering\r\n\t\t * over islands and coast lines. We run a custom implementation of animation becuase we\r\n\t\t * need to be able to run this independently from other animations like zoom redraw. Also,\r\n\t\t * adding color animation to the adapters would introduce almost the same amount of code.\r\n\t\t */\r\n\t\tonMouseOut: function () {\r\n\t\t\tvar point = this,\r\n\t\t\t\tstart = +new Date(),\r\n\t\t\t\tnormalColor = Color(point.options.color),\r\n\t\t\t\thoverColor = Color(point.pointAttr.hover.fill),\r\n\t\t\t\tanimation = point.series.options.states.normal.animation,\r\n\t\t\t\tduration = animation && (animation.duration || 500);\r\n\r\n\t\t\tif (duration && normalColor.rgba.length === 4 && hoverColor.rgba.length === 4) {\r\n\t\t\t\tdelete point.pointAttr[''].fill; // avoid resetting it in Point.setState\r\n\r\n\t\t\t\tclearTimeout(point.colorInterval);\r\n\t\t\t\tpoint.colorInterval = setInterval(function () {\r\n\t\t\t\t\tvar pos = (new Date() - start) / duration,\r\n\t\t\t\t\t\tgraphic = point.graphic;\r\n\t\t\t\t\tif (pos > 1) {\r\n\t\t\t\t\t\tpos = 1;\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (graphic) {\r\n\t\t\t\t\t\tgraphic.attr('fill', tweenColors(hoverColor, normalColor, pos));\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (pos >= 1) {\r\n\t\t\t\t\t\tclearTimeout(point.colorInterval);\r\n\t\t\t\t\t}\r\n\t\t\t\t}, 13);\r\n\t\t\t}\r\n\t\t\tPoint.prototype.onMouseOut.call(point);\r\n\t\t}\r\n\t});\r\n\r\n\t/**\r\n\t * Add the series type\r\n\t */\r\n\tseriesTypes.map = Highcharts.extendClass(seriesTypes.scatter, {\r\n\t\ttype: 'map',\r\n\t\tpointAttrToOptions: { // mapping between SVG attributes and the corresponding options\r\n\t\t\tstroke: 'borderColor',\r\n\t\t\t'stroke-width': 'borderWidth',\r\n\t\t\tfill: 'color'\r\n\t\t},\r\n\t\tcolorKey: 'y',\r\n\t\tpointClass: MapAreaPoint,\r\n\t\ttrackerGroups: ['group', 'markerGroup', 'dataLabelsGroup'],\r\n\t\tgetSymbol: noop,\r\n\t\tsupportsDrilldown: true,\r\n\t\tgetExtremesFromAll: true,\r\n\t\tuseMapGeometry: true, // get axis extremes from paths, not values\r\n\t\tinit: function (chart) {\r\n\t\t\tvar series = this,\r\n\t\t\t\tvalueDecimals = chart.options.legend.valueDecimals,\r\n\t\t\t\tlegendItems = [],\r\n\t\t\t\tname,\r\n\t\t\t\tfrom,\r\n\t\t\t\tto,\r\n\t\t\t\tfromLabel,\r\n\t\t\t\ttoLabel,\r\n\t\t\t\tcolorRange,\r\n\t\t\t\tvalueRanges,\r\n\t\t\t\tgradientColor,\r\n\t\t\t\tgrad,\r\n\t\t\t\ttmpLabel,\r\n\t\t\t\thorizontal = chart.options.legend.layout === 'horizontal';\r\n\r\n\t\t\t\r\n\t\t\tHighcharts.Series.prototype.init.apply(this, arguments);\r\n\t\t\tcolorRange = series.options.colorRange;\r\n\t\t\tvalueRanges = series.options.valueRanges;\r\n\r\n\t\t\tif (valueRanges) {\r\n\t\t\t\teach(valueRanges, function (range) {\r\n\t\t\t\t\tfrom = range.from;\r\n\t\t\t\t\tto = range.to;\r\n\t\t\t\t\t\r\n\t\t\t\t\t// Assemble the default name. This can be overridden by legend.options.labelFormatter\r\n\t\t\t\t\tname = '';\r\n\t\t\t\t\tif (from === UNDEFINED) {\r\n\t\t\t\t\t\tname = '< ';\r\n\t\t\t\t\t} else if (to === UNDEFINED) {\r\n\t\t\t\t\t\tname = '> ';\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (from !== UNDEFINED) {\r\n\t\t\t\t\t\tname += numberFormat(from, valueDecimals);\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (from !== UNDEFINED && to !== UNDEFINED) {\r\n\t\t\t\t\t\tname += ' - ';\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif (to !== UNDEFINED) {\r\n\t\t\t\t\t\tname += numberFormat(to, valueDecimals);\r\n\t\t\t\t\t}\r\n\t\t\t\t\t\r\n\t\t\t\t\t// Add a mock object to the legend items\r\n\t\t\t\t\tlegendItems.push(Highcharts.extend({\r\n\t\t\t\t\t\tchart: series.chart,\r\n\t\t\t\t\t\tname: name,\r\n\t\t\t\t\t\toptions: {},\r\n\t\t\t\t\t\tdrawLegendSymbol: seriesTypes.area.prototype.drawLegendSymbol,\r\n\t\t\t\t\t\tvisible: true,\r\n\t\t\t\t\t\tsetState: function () {},\r\n\t\t\t\t\t\tsetVisible: function () {}\r\n\t\t\t\t\t}, range));\r\n\t\t\t\t});\r\n\t\t\t\tseries.legendItems = legendItems;\r\n\r\n\t\t\t} else if (colorRange) {\r\n\r\n\t\t\t\tfrom = colorRange.from;\r\n\t\t\t\tto = colorRange.to;\r\n\t\t\t\tfromLabel = colorRange.fromLabel;\r\n\t\t\t\ttoLabel = colorRange.toLabel;\r\n\r\n\t\t\t\t// Flips linearGradient variables and label text.\r\n\t\t\t\tgrad = horizontal ? [0, 0, 1, 0] : [0, 1, 0, 0]; \r\n\t\t\t\tif (!horizontal) {\r\n\t\t\t\t\ttmpLabel = fromLabel;\r\n\t\t\t\t\tfromLabel = toLabel;\r\n\t\t\t\t\ttoLabel = tmpLabel;\r\n\t\t\t\t} \r\n\r\n\t\t\t\t// Creates color gradient.\r\n\t\t\t\tgradientColor = {\r\n\t\t\t\t\tlinearGradient: { x1: grad[0], y1: grad[1], x2: grad[2], y2: grad[3] },\r\n\t\t\t\t\tstops: \r\n\t\t\t\t\t[\r\n\t\t\t\t\t\t[0, from],\r\n\t\t\t\t\t\t[1, to]\r\n\t\t\t\t\t]\r\n\t\t\t\t};\r\n\r\n\t\t\t\t// Add a mock object to the legend items.\r\n\t\t\t\tlegendItems = [{\r\n\t\t\t\t\tchart: series.chart,\r\n\t\t\t\t\toptions: {},\r\n\t\t\t\t\tfromLabel: fromLabel,\r\n\t\t\t\t\ttoLabel: toLabel,\r\n\t\t\t\t\tcolor: gradientColor,\r\n\t\t\t\t\tdrawLegendSymbol: this.drawLegendSymbolGradient,\r\n\t\t\t\t\tvisible: true,\r\n\t\t\t\t\tsetState: function () {},\r\n\t\t\t\t\tsetVisible: function () {}\r\n\t\t\t\t}];\r\n\r\n\t\t\t\tseries.legendItems = legendItems;\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\t/**\r\n\t\t * If neither valueRanges nor colorRanges are defined, use basic area symbol.\r\n\t\t */\r\n\t\tdrawLegendSymbol: seriesTypes.area.prototype.drawLegendSymbol,\r\n\r\n\t\t/**\r\n\t\t * Gets the series' symbol in the legend and extended legend with more information.\r\n\t\t * \r\n\t\t * @param {Object} legend The legend object\r\n\t\t * @param {Object} item The series (this) or point\r\n\t\t */\r\n\t\tdrawLegendSymbolGradient: function (legend, item) {\r\n\t\t\tvar spacing = legend.options.symbolPadding,\r\n\t\t\t\tpadding = pick(legend.options.padding, 8),\r\n\t\t\t\tpositionY,\r\n\t\t\t\tpositionX,\r\n\t\t\t\tgradientSize = this.chart.renderer.fontMetrics(legend.options.itemStyle.fontSize).h,\r\n\t\t\t\thorizontal = legend.options.layout === 'horizontal',\r\n\t\t\t\tbox1,\r\n\t\t\t\tbox2,\r\n\t\t\t\tbox3,\r\n\t\t\t\trectangleLength = pick(legend.options.rectangleLength, 200);\r\n\r\n\t\t\t// Set local variables based on option.\r\n\t\t\tif (horizontal) {\r\n\t\t\t\tpositionY = -(spacing / 2);\r\n\t\t\t\tpositionX = 0;\r\n\t\t\t} else {\r\n\t\t\t\tpositionY = -rectangleLength + legend.baseline - (spacing / 2);\r\n\t\t\t\tpositionX = padding + gradientSize;\r\n\t\t\t}\r\n\r\n\t\t\t// Creates the from text.\r\n\t\t\titem.fromText = this.chart.renderer.text(\r\n\t\t\t\t\titem.fromLabel,\t// Text.\r\n\t\t\t\t\tpositionX,\t\t// Lower left x.\r\n\t\t\t\t\tpositionY\t\t// Lower left y.\r\n\t\t\t\t).attr({\r\n\t\t\t\t\tzIndex: 2\r\n\t\t\t\t}).add(item.legendGroup);\r\n\t\t\tbox1 = item.fromText.getBBox();\r\n\r\n\t\t\t// Creates legend symbol.\r\n\t\t\t// Ternary changes variables based on option.\r\n\t\t\titem.legendSymbol = this.chart.renderer.rect(\r\n\t\t\t\thorizontal ? box1.x + box1.width + spacing : box1.x - gradientSize - spacing,\t\t// Upper left x.\r\n\t\t\t\tbox1.y,\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// Upper left y.\r\n\t\t\t\thorizontal ? rectangleLength : gradientSize,\t\t\t\t\t\t\t\t\t\t\t// Width.\r\n\t\t\t\thorizontal ? gradientSize : rectangleLength,\t\t\t\t\t\t\t\t\t\t// Height.\r\n\t\t\t\t2\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// Corner radius.\r\n\t\t\t).attr({\r\n\t\t\t\tzIndex: 1\r\n\t\t\t}).add(item.legendGroup);\r\n\t\t\tbox2 = item.legendSymbol.getBBox();\r\n\r\n\t\t\t// Creates the to text.\r\n\t\t\t// Vertical coordinate changed based on option.\r\n\t\t\titem.toText = this.chart.renderer.text(\r\n\t\t\t\t\titem.toLabel,\r\n\t\t\t\t\tbox2.x + box2.width + spacing,\r\n\t\t\t\t\thorizontal ? positionY : box2.y + box2.height - spacing\r\n\t\t\t\t).attr({\r\n\t\t\t\t\tzIndex: 2\r\n\t\t\t\t}).add(item.legendGroup);\r\n\t\t\tbox3 = item.toText.getBBox();\r\n\r\n\t\t\t// Changes legend box settings based on option.\r\n\t\t\tif (horizontal) {\r\n\t\t\t\tlegend.offsetWidth = box1.width + box2.width + box3.width + (spacing * 2) + padding;\r\n\t\t\t\tlegend.itemY = gradientSize + padding;\r\n\t\t\t} else {\r\n\t\t\t\tlegend.offsetWidth = Math.max(box1.width, box3.width) + (spacing) + box2.width + padding;\r\n\t\t\t\tlegend.itemY = box2.height + padding;\r\n\t\t\t\tlegend.itemX = spacing;\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\t/**\r\n\t\t * Get the bounding box of all paths in the map combined.\r\n\t\t */\r\n\t\tgetBox: function (paths) {\r\n\t\t\tvar maxX = Number.MIN_VALUE, \r\n\t\t\t\tminX =  Number.MAX_VALUE, \r\n\t\t\t\tmaxY = Number.MIN_VALUE, \r\n\t\t\t\tminY =  Number.MAX_VALUE;\r\n\t\t\t\r\n\t\t\t\r\n\t\t\t// Find the bounding box\r\n\t\t\teach(paths || this.options.data, function (point) {\r\n\t\t\t\tvar path = point.path,\r\n\t\t\t\t\ti = path.length,\r\n\t\t\t\t\teven = false, // while loop reads from the end\r\n\t\t\t\t\tpointMaxX = Number.MIN_VALUE, \r\n\t\t\t\t\tpointMinX =  Number.MAX_VALUE, \r\n\t\t\t\t\tpointMaxY = Number.MIN_VALUE, \r\n\t\t\t\t\tpointMinY =  Number.MAX_VALUE;\r\n\t\t\t\t\t\r\n\t\t\t\twhile (i--) {\r\n\t\t\t\t\tif (typeof path[i] === 'number' && !isNaN(path[i])) {\r\n\t\t\t\t\t\tif (even) { // even = x\r\n\t\t\t\t\t\t\tpointMaxX = Math.max(pointMaxX, path[i]);\r\n\t\t\t\t\t\t\tpointMinX = Math.min(pointMinX, path[i]);\r\n\t\t\t\t\t\t} else { // odd = Y\r\n\t\t\t\t\t\t\tpointMaxY = Math.max(pointMaxY, path[i]);\r\n\t\t\t\t\t\t\tpointMinY = Math.min(pointMinY, path[i]);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\teven = !even;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\t// Cache point bounding box for use to position data labels\r\n\t\t\t\tpoint._maxX = pointMaxX;\r\n\t\t\t\tpoint._minX = pointMinX;\r\n\t\t\t\tpoint._maxY = pointMaxY;\r\n\t\t\t\tpoint._minY = pointMinY;\r\n\r\n\t\t\t\tmaxX = Math.max(maxX, pointMaxX);\r\n\t\t\t\tminX = Math.min(minX, pointMinX);\r\n\t\t\t\tmaxY = Math.max(maxY, pointMaxY);\r\n\t\t\t\tminY = Math.min(minY, pointMinY);\r\n\t\t\t});\r\n\t\t\tthis.minY = minY;\r\n\t\t\tthis.maxY = maxY;\r\n\t\t\tthis.minX = minX;\r\n\t\t\tthis.maxX = maxX;\r\n\t\t\t\r\n\t\t},\r\n\t\t\r\n\t\t\r\n\t\t\r\n\t\t/**\r\n\t\t * Translate the path so that it automatically fits into the plot area box\r\n\t\t * @param {Object} path\r\n\t\t */\r\n\t\ttranslatePath: function (path) {\r\n\t\t\t\r\n\t\t\tvar series = this,\r\n\t\t\t\teven = false, // while loop reads from the end\r\n\t\t\t\txAxis = series.xAxis,\r\n\t\t\t\tyAxis = series.yAxis,\r\n\t\t\t\ti;\r\n\t\t\t\t\r\n\t\t\t// Preserve the original\r\n\t\t\tpath = [].concat(path);\r\n\t\t\t\t\r\n\t\t\t// Do the translation\r\n\t\t\ti = path.length;\r\n\t\t\twhile (i--) {\r\n\t\t\t\tif (typeof path[i] === 'number') {\r\n\t\t\t\t\tif (even) { // even = x\r\n\t\t\t\t\t\tpath[i] = Math.round(xAxis.translate(path[i]));\r\n\t\t\t\t\t} else { // odd = Y\r\n\t\t\t\t\t\tpath[i] = Math.round(yAxis.len - yAxis.translate(path[i]));\r\n\t\t\t\t\t}\r\n\t\t\t\t\teven = !even;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\treturn path;\r\n\t\t},\r\n\t\t\r\n\t\tsetData: function () {\r\n\t\t\tHighcharts.Series.prototype.setData.apply(this, arguments);\r\n\t\t\tthis.getBox();\r\n\t\t},\r\n\t\t\r\n\t\t/**\r\n\t\t * Add the path option for data points. Find the max value for color calculation.\r\n\t\t */\r\n\t\ttranslate: function () {\r\n\t\t\tvar series = this,\r\n\t\t\t\tdataMin = Number.MAX_VALUE,\r\n\t\t\t\tdataMax = Number.MIN_VALUE;\r\n\t\r\n\t\t\tseries.generatePoints();\r\n\t\r\n\t\t\teach(series.data, function (point) {\r\n\t\t\t\t\r\n\t\t\t\tpoint.shapeType = 'path';\r\n\t\t\t\tpoint.shapeArgs = {\r\n\t\t\t\t\td: series.translatePath(point.path)\r\n\t\t\t\t};\r\n\t\t\t\t\r\n\t\t\t\t// TODO: do point colors in drawPoints instead of point.init\r\n\t\t\t\tif (typeof point.y === 'number') {\r\n\t\t\t\t\tif (point.y > dataMax) {\r\n\t\t\t\t\t\tdataMax = point.y;\r\n\t\t\t\t\t} else if (point.y < dataMin) {\r\n\t\t\t\t\t\tdataMin = point.y;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t\t\r\n\t\t\tseries.translateColors(dataMin, dataMax);\r\n\t\t},\r\n\t\t\r\n\t\t/**\r\n\t\t * In choropleth maps, the color is a result of the value, so this needs translation too\r\n\t\t */\r\n\t\ttranslateColors: function (dataMin, dataMax) {\r\n\t\t\t\r\n\t\t\tvar seriesOptions = this.options,\r\n\t\t\t\tvalueRanges = seriesOptions.valueRanges,\r\n\t\t\t\tcolorRange = seriesOptions.colorRange,\r\n\t\t\t\tcolorKey = this.colorKey,\r\n\t\t\t\tfrom,\r\n\t\t\t\tto;\r\n\r\n\t\t\tif (colorRange) {\r\n\t\t\t\tfrom = Color(colorRange.from);\r\n\t\t\t\tto = Color(colorRange.to);\r\n\t\t\t}\r\n\t\t\t\r\n\t\t\teach(this.data, function (point) {\r\n\t\t\t\tvar value = point[colorKey],\r\n\t\t\t\t\trange,\r\n\t\t\t\t\tcolor,\r\n\t\t\t\t\ti,\r\n\t\t\t\t\tpos;\r\n\r\n\t\t\t\tif (valueRanges) {\r\n\t\t\t\t\ti = valueRanges.length;\r\n\t\t\t\t\twhile (i--) {\r\n\t\t\t\t\t\trange = valueRanges[i];\r\n\t\t\t\t\t\tfrom = range.from;\r\n\t\t\t\t\t\tto = range.to;\r\n\t\t\t\t\t\tif ((from === UNDEFINED || value >= from) && (to === UNDEFINED || value <= to)) {\r\n\t\t\t\t\t\t\tcolor = range.color;\r\n\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\r\n\t\t\t\t\t}\r\n\t\t\t\t} else if (colorRange && value !== undefined) {\r\n\r\n\t\t\t\t\tpos = 1 - ((dataMax - value) / (dataMax - dataMin));\r\n\t\t\t\t\tcolor = value === null ? seriesOptions.nullColor : tweenColors(from, to, pos);\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif (color) {\r\n\t\t\t\t\tpoint.color = null; // reset from previous drilldowns, use of the same data options\r\n\t\t\t\t\tpoint.options.color = color;\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t},\r\n\t\t\r\n\t\tdrawGraph: noop,\r\n\t\t\r\n\t\t/**\r\n\t\t * We need the points' bounding boxes in order to draw the data labels, so \r\n\t\t * we skip it now and call if from drawPoints instead.\r\n\t\t */\r\n\t\tdrawDataLabels: noop,\r\n\t\t\r\n\t\t/** \r\n\t\t * Use the drawPoints method of column, that is able to handle simple shapeArgs.\r\n\t\t * Extend it by assigning the tooltip position.\r\n\t\t */\r\n\t\tdrawPoints: function () {\r\n\t\t\tvar series = this,\r\n\t\t\t\txAxis = series.xAxis,\r\n\t\t\t\tyAxis = series.yAxis,\r\n\t\t\t\tcolorKey = series.colorKey;\r\n\t\t\t\r\n\t\t\t// Make points pass test in drawing\r\n\t\t\teach(series.data, function (point) {\r\n\t\t\t\tpoint.plotY = 1; // pass null test in column.drawPoints\r\n\t\t\t\tif (point[colorKey] === null) {\r\n\t\t\t\t\tpoint[colorKey] = 0;\r\n\t\t\t\t\tpoint.isNull = true;\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t\t\r\n\t\t\t// Draw them\r\n\t\t\tseriesTypes.column.prototype.drawPoints.apply(series);\r\n\t\t\t\r\n\t\t\teach(series.data, function (point) {\r\n\r\n\t\t\t\tvar dataLabels = point.dataLabels,\r\n\t\t\t\t\tminX = xAxis.toPixels(point._minX, true),\r\n\t\t\t\t\tmaxX = xAxis.toPixels(point._maxX, true),\r\n\t\t\t\t\tminY = yAxis.toPixels(point._minY, true),\r\n\t\t\t\t\tmaxY = yAxis.toPixels(point._maxY, true);\r\n\r\n\t\t\t\tpoint.plotX = Math.round(minX + (maxX - minX) * pick(dataLabels && dataLabels.anchorX, 0.5));\r\n\t\t\t\tpoint.plotY = Math.round(minY + (maxY - minY) * pick(dataLabels && dataLabels.anchorY, 0.5)); \r\n\t\t\t\t\r\n\t\t\t\t\r\n\t\t\t\t// Reset escaped null points\r\n\t\t\t\tif (point.isNull) {\r\n\t\t\t\t\tpoint[colorKey] = null;\r\n\t\t\t\t}\r\n\t\t\t});\r\n\r\n\t\t\t// Now draw the data labels\r\n\t\t\tHighcharts.Series.prototype.drawDataLabels.call(series);\r\n\t\t\t\r\n\t\t},\r\n\r\n\t\t/**\r\n\t\t * Animate in the new series from the clicked point in the old series.\r\n\t\t * Depends on the drilldown.js module\r\n\t\t */\r\n\t\tanimateDrilldown: function (init) {\r\n\t\t\tvar toBox = this.chart.plotBox,\r\n\t\t\t\tlevel = this.chart.drilldownLevels[this.chart.drilldownLevels.length - 1],\r\n\t\t\t\tfromBox = level.bBox,\r\n\t\t\t\tanimationOptions = this.chart.options.drilldown.animation,\r\n\t\t\t\tscale;\r\n\t\t\t\t\r\n\t\t\tif (!init) {\r\n\r\n\t\t\t\tscale = Math.min(fromBox.width / toBox.width, fromBox.height / toBox.height);\r\n\t\t\t\tlevel.shapeArgs = {\r\n\t\t\t\t\tscaleX: scale,\r\n\t\t\t\t\tscaleY: scale,\r\n\t\t\t\t\ttranslateX: fromBox.x,\r\n\t\t\t\t\ttranslateY: fromBox.y\r\n\t\t\t\t};\r\n\t\t\t\t\r\n\t\t\t\t// TODO: Animate this.group instead\r\n\t\t\t\teach(this.points, function (point) {\r\n\r\n\t\t\t\t\tpoint.graphic\r\n\t\t\t\t\t\t.attr(level.shapeArgs)\r\n\t\t\t\t\t\t.animate({\r\n\t\t\t\t\t\t\tscaleX: 1,\r\n\t\t\t\t\t\t\tscaleY: 1,\r\n\t\t\t\t\t\t\ttranslateX: 0,\r\n\t\t\t\t\t\t\ttranslateY: 0\r\n\t\t\t\t\t\t}, animationOptions);\r\n\r\n\t\t\t\t});\r\n\r\n\t\t\t\tdelete this.animate;\r\n\t\t\t}\r\n\t\t\t\r\n\t\t},\r\n\r\n\t\t/**\r\n\t\t * When drilling up, pull out the individual point graphics from the lower series\r\n\t\t * and animate them into the origin point in the upper series.\r\n\t\t */\r\n\t\tanimateDrillupFrom: function (level) {\r\n\t\t\tseriesTypes.column.prototype.animateDrillupFrom.call(this, level);\r\n\t\t},\r\n\r\n\r\n\t\t/**\r\n\t\t * When drilling up, keep the upper series invisible until the lower series has\r\n\t\t * moved into place\r\n\t\t */\r\n\t\tanimateDrillupTo: function (init) {\r\n\t\t\tseriesTypes.column.prototype.animateDrillupTo.call(this, init);\r\n\t\t}\r\n\t});\r\n\r\n\r\n\t// The mapline series type\r\n\tplotOptions.mapline = merge(plotOptions.map, {\r\n\t\tlineWidth: 1,\r\n\t\tbackgroundColor: 'none'\r\n\t});\r\n\tseriesTypes.mapline = Highcharts.extendClass(seriesTypes.map, {\r\n\t\ttype: 'mapline',\r\n\t\tpointAttrToOptions: { // mapping between SVG attributes and the corresponding options\r\n\t\t\tstroke: 'color',\r\n\t\t\t'stroke-width': 'lineWidth',\r\n\t\t\tfill: 'backgroundColor'\r\n\t\t},\r\n\t\tdrawLegendSymbol: seriesTypes.line.prototype.drawLegendSymbol\r\n\t});\r\n\r\n\t// The mappoint series type\r\n\tplotOptions.mappoint = merge(plotOptions.scatter, {\r\n\t\tdataLabels: {\r\n\t\t\tenabled: true,\r\n\t\t\tformat: '{point.name}',\r\n\t\t\tcolor: 'black',\r\n\t\t\tstyle: {\r\n\t\t\t\ttextShadow: '0 0 5px white'\r\n\t\t\t}\r\n\t\t}\r\n\t});\r\n\tseriesTypes.mappoint = Highcharts.extendClass(seriesTypes.scatter, {\r\n\t\ttype: 'mappoint'\r\n\t});\r\n\t\r\n\r\n\t\r\n\t/**\r\n\t * A wrapper for Chart with all the default values for a Map\r\n\t */\r\n\tHighcharts.Map = function (options, callback) {\r\n\t\t\r\n\t\tvar hiddenAxis = {\r\n\t\t\t\tendOnTick: false,\r\n\t\t\t\tgridLineWidth: 0,\r\n\t\t\t\tlabels: {\r\n\t\t\t\t\tenabled: false\r\n\t\t\t\t},\r\n\t\t\t\tlineWidth: 0,\r\n\t\t\t\tminPadding: 0,\r\n\t\t\t\tmaxPadding: 0,\r\n\t\t\t\tstartOnTick: false,\r\n\t\t\t\ttickWidth: 0,\r\n\t\t\t\ttitle: null\r\n\t\t\t},\r\n\t\t\tseriesOptions;\r\n\t\t\r\n\t\t// Don't merge the data\r\n\t\tseriesOptions = options.series;\r\n\t\toptions.series = null;\r\n\t\t\r\n\t\toptions = merge({\r\n\t\t\tchart: {\r\n\t\t\t\ttype: 'map',\r\n\t\t\t\tpanning: 'xy'\r\n\t\t\t},\r\n\t\t\txAxis: hiddenAxis,\r\n\t\t\tyAxis: merge(hiddenAxis, { reversed: true })\t\r\n\t\t},\r\n\t\toptions, // user's options\r\n\t\r\n\t\t{ // forced options\r\n\t\t\tchart: {\r\n\t\t\t\tinverted: false\r\n\t\t\t}\r\n\t\t});\r\n\t\r\n\t\toptions.series = seriesOptions;\r\n\t\r\n\t\r\n\t\treturn new Highcharts.Chart(options, callback);\r\n\t};\r\n}(Highcharts));\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/highcharts/modules/no-data-to-display.js",
    "content": "/*\r\n Highcharts JS v3.0.6 (2013-10-04)\r\n Plugin for displaying a message when there is no data visible in chart.\r\n\r\n (c) 2010-2013 Highsoft AS\r\n Author: Øystein Moseng\r\n\r\n License: www.highcharts.com/license\r\n*/\r\n(function(c){function f(){return!!this.points.length}function g(){this.hasData()?this.hideNoData():this.showNoData()}var d=c.seriesTypes,e=c.Chart.prototype,h=c.getOptions(),i=c.extend;i(h.lang,{noData:\"No data to display\"});h.noData={position:{x:0,y:0,align:\"center\",verticalAlign:\"middle\"},attr:{},style:{fontWeight:\"bold\",fontSize:\"12px\",color:\"#60606a\"}};d.pie.prototype.hasData=f;if(d.gauge)d.gauge.prototype.hasData=f;if(d.waterfall)d.waterfall.prototype.hasData=f;c.Series.prototype.hasData=function(){return this.dataMax!==\r\nvoid 0&&this.dataMin!==void 0};e.showNoData=function(a){var b=this.options,a=a||b.lang.noData,b=b.noData;if(!this.noDataLabel)this.noDataLabel=this.renderer.label(a,0,0,null,null,null,null,null,\"no-data\").attr(b.attr).css(b.style).add(),this.noDataLabel.align(i(this.noDataLabel.getBBox(),b.position),!1,\"plotBox\")};e.hideNoData=function(){if(this.noDataLabel)this.noDataLabel=this.noDataLabel.destroy()};e.hasData=function(){for(var a=this.series,b=a.length;b--;)if(a[b].hasData()&&!a[b].options.isInternal)return!0;\r\nreturn!1};e.callbacks.push(function(a){c.addEvent(a,\"load\",g);c.addEvent(a,\"redraw\",g)})})(Highcharts);\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/highcharts/modules/no-data-to-display.src.js",
    "content": "/**\r\n * @license Highcharts JS v3.0.6 (2013-10-04)\r\n * Plugin for displaying a message when there is no data visible in chart.\r\n *\r\n * (c) 2010-2013 Highsoft AS\r\n * Author: Øystein Moseng\r\n *\r\n * License: www.highcharts.com/license\r\n */\r\n\r\n(function (H) { // docs\r\n\t\r\n\tvar seriesTypes = H.seriesTypes,\r\n\t\tchartPrototype = H.Chart.prototype,\r\n\t\tdefaultOptions = H.getOptions(),\r\n\t\textend = H.extend;\r\n\r\n\t// Add language option\r\n\textend(defaultOptions.lang, {\r\n\t\tnoData: 'No data to display'\r\n\t});\r\n\t\r\n\t// Add default display options for message\r\n\tdefaultOptions.noData = {\r\n\t\tposition: {\r\n\t\t\tx: 0,\r\n\t\t\ty: 0,\t\t\t\r\n\t\t\talign: 'center',\r\n\t\t\tverticalAlign: 'middle'\r\n\t\t},\r\n\t\tattr: {\t\t\t\t\t\t\r\n\t\t},\r\n\t\tstyle: {\t\r\n\t\t\tfontWeight: 'bold',\t\t\r\n\t\t\tfontSize: '12px',\r\n\t\t\tcolor: '#60606a'\t\t\r\n\t\t}\r\n\t};\r\n\r\n\t/**\r\n\t * Define hasData functions for series. These return true if there are data points on this series within the plot area\r\n\t */\t\r\n\tfunction hasDataPie() {\r\n\t\treturn !!this.points.length; /* != 0 */\r\n\t}\r\n\r\n\tseriesTypes.pie.prototype.hasData = hasDataPie;\r\n\r\n\tif (seriesTypes.gauge) {\r\n\t\tseriesTypes.gauge.prototype.hasData = hasDataPie;\r\n\t}\r\n\r\n\tif (seriesTypes.waterfall) {\r\n\t\tseriesTypes.waterfall.prototype.hasData = hasDataPie;\r\n\t}\r\n\r\n\tH.Series.prototype.hasData = function () {\r\n\t\treturn this.dataMax !== undefined && this.dataMin !== undefined;\r\n\t};\r\n\t\r\n\t/**\r\n\t * Display a no-data message.\r\n\t *\r\n\t * @param {String} str An optional message to show in place of the default one \r\n\t */\r\n\tchartPrototype.showNoData = function (str) {\r\n\t\tvar chart = this,\r\n\t\t\toptions = chart.options,\r\n\t\t\ttext = str || options.lang.noData,\r\n\t\t\tnoDataOptions = options.noData;\r\n\r\n\t\tif (!chart.noDataLabel) {\r\n\t\t\tchart.noDataLabel = chart.renderer.label(text, 0, 0, null, null, null, null, null, 'no-data')\r\n\t\t\t\t.attr(noDataOptions.attr)\r\n\t\t\t\t.css(noDataOptions.style)\r\n\t\t\t\t.add();\r\n\t\t\tchart.noDataLabel.align(extend(chart.noDataLabel.getBBox(), noDataOptions.position), false, 'plotBox');\r\n\t\t}\r\n\t};\r\n\r\n\t/**\r\n\t * Hide no-data message\t\r\n\t */\t\r\n\tchartPrototype.hideNoData = function () {\r\n\t\tvar chart = this;\r\n\t\tif (chart.noDataLabel) {\r\n\t\t\tchart.noDataLabel = chart.noDataLabel.destroy();\r\n\t\t}\r\n\t};\r\n\r\n\t/**\r\n\t * Returns true if there are data points within the plot area now\r\n\t */\t\r\n\tchartPrototype.hasData = function () {\r\n\t\tvar chart = this,\r\n\t\t\tseries = chart.series,\r\n\t\t\ti = series.length;\r\n\r\n\t\twhile (i--) {\r\n\t\t\tif (series[i].hasData() && !series[i].options.isInternal) { \r\n\t\t\t\treturn true;\r\n\t\t\t}\t\r\n\t\t}\r\n\r\n\t\treturn false;\r\n\t};\r\n\r\n\t/**\r\n\t * Show no-data message if there is no data in sight. Otherwise, hide it.\r\n\t */\r\n\tfunction handleNoData() {\r\n\t\tvar chart = this;\r\n\t\tif (chart.hasData()) {\r\n\t\t\tchart.hideNoData();\r\n\t\t} else {\r\n\t\t\tchart.showNoData();\r\n\t\t}\r\n\t}\r\n\r\n\t/**\r\n\t * Add event listener to handle automatic display of no-data message\r\n\t */\r\n\tchartPrototype.callbacks.push(function (chart) {\r\n\t\tH.addEvent(chart, 'load', handleNoData);\r\n\t\tH.addEvent(chart, 'redraw', handleNoData);\r\n\t});\r\n\r\n}(Highcharts));\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/highcharts/themes/dark-blue.js",
    "content": "/**\r\n * Dark blue theme for Highcharts JS\r\n * @author Torstein Hønsi\r\n */\r\n\r\nHighcharts.theme = {\r\n\tcolors: [\"#DDDF0D\", \"#55BF3B\", \"#DF5353\", \"#7798BF\", \"#aaeeee\", \"#ff0066\", \"#eeaaee\",\r\n\t\t\"#55BF3B\", \"#DF5353\", \"#7798BF\", \"#aaeeee\"],\r\n\tchart: {\r\n\t\tbackgroundColor: {\r\n\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 1, y2: 1 },\r\n\t\t\tstops: [\r\n\t\t\t\t[0, 'rgb(48, 48, 96)'],\r\n\t\t\t\t[1, 'rgb(0, 0, 0)']\r\n\t\t\t]\r\n\t\t},\r\n\t\tborderColor: '#000000',\r\n\t\tborderWidth: 2,\r\n\t\tclassName: 'dark-container',\r\n\t\tplotBackgroundColor: 'rgba(255, 255, 255, .1)',\r\n\t\tplotBorderColor: '#CCCCCC',\r\n\t\tplotBorderWidth: 1\r\n\t},\r\n\ttitle: {\r\n\t\tstyle: {\r\n\t\t\tcolor: '#C0C0C0',\r\n\t\t\tfont: 'bold 16px \"Trebuchet MS\", Verdana, sans-serif'\r\n\t\t}\r\n\t},\r\n\tsubtitle: {\r\n\t\tstyle: {\r\n\t\t\tcolor: '#666666',\r\n\t\t\tfont: 'bold 12px \"Trebuchet MS\", Verdana, sans-serif'\r\n\t\t}\r\n\t},\r\n\txAxis: {\r\n\t\tgridLineColor: '#333333',\r\n\t\tgridLineWidth: 1,\r\n\t\tlabels: {\r\n\t\t\tstyle: {\r\n\t\t\t\tcolor: '#A0A0A0'\r\n\t\t\t}\r\n\t\t},\r\n\t\tlineColor: '#A0A0A0',\r\n\t\ttickColor: '#A0A0A0',\r\n\t\ttitle: {\r\n\t\t\tstyle: {\r\n\t\t\t\tcolor: '#CCC',\r\n\t\t\t\tfontWeight: 'bold',\r\n\t\t\t\tfontSize: '12px',\r\n\t\t\t\tfontFamily: 'Trebuchet MS, Verdana, sans-serif'\r\n\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\tyAxis: {\r\n\t\tgridLineColor: '#333333',\r\n\t\tlabels: {\r\n\t\t\tstyle: {\r\n\t\t\t\tcolor: '#A0A0A0'\r\n\t\t\t}\r\n\t\t},\r\n\t\tlineColor: '#A0A0A0',\r\n\t\tminorTickInterval: null,\r\n\t\ttickColor: '#A0A0A0',\r\n\t\ttickWidth: 1,\r\n\t\ttitle: {\r\n\t\t\tstyle: {\r\n\t\t\t\tcolor: '#CCC',\r\n\t\t\t\tfontWeight: 'bold',\r\n\t\t\t\tfontSize: '12px',\r\n\t\t\t\tfontFamily: 'Trebuchet MS, Verdana, sans-serif'\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\ttooltip: {\r\n\t\tbackgroundColor: 'rgba(0, 0, 0, 0.75)',\r\n\t\tstyle: {\r\n\t\t\tcolor: '#F0F0F0'\r\n\t\t}\r\n\t},\r\n\ttoolbar: {\r\n\t\titemStyle: {\r\n\t\t\tcolor: 'silver'\r\n\t\t}\r\n\t},\r\n\tplotOptions: {\r\n\t\tline: {\r\n\t\t\tdataLabels: {\r\n\t\t\t\tcolor: '#CCC'\r\n\t\t\t},\r\n\t\t\tmarker: {\r\n\t\t\t\tlineColor: '#333'\r\n\t\t\t}\r\n\t\t},\r\n\t\tspline: {\r\n\t\t\tmarker: {\r\n\t\t\t\tlineColor: '#333'\r\n\t\t\t}\r\n\t\t},\r\n\t\tscatter: {\r\n\t\t\tmarker: {\r\n\t\t\t\tlineColor: '#333'\r\n\t\t\t}\r\n\t\t},\r\n\t\tcandlestick: {\r\n\t\t\tlineColor: 'white'\r\n\t\t}\r\n\t},\r\n\tlegend: {\r\n\t\titemStyle: {\r\n\t\t\tfont: '9pt Trebuchet MS, Verdana, sans-serif',\r\n\t\t\tcolor: '#A0A0A0'\r\n\t\t},\r\n\t\titemHoverStyle: {\r\n\t\t\tcolor: '#FFF'\r\n\t\t},\r\n\t\titemHiddenStyle: {\r\n\t\t\tcolor: '#444'\r\n\t\t}\r\n\t},\r\n\tcredits: {\r\n\t\tstyle: {\r\n\t\t\tcolor: '#666'\r\n\t\t}\r\n\t},\r\n\tlabels: {\r\n\t\tstyle: {\r\n\t\t\tcolor: '#CCC'\r\n\t\t}\r\n\t},\r\n\r\n\tnavigation: {\r\n\t\tbuttonOptions: {\r\n\t\t\tsymbolStroke: '#DDDDDD',\r\n\t\t\thoverSymbolStroke: '#FFFFFF',\r\n\t\t\ttheme: {\r\n\t\t\t\tfill: {\r\n\t\t\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\r\n\t\t\t\t\tstops: [\r\n\t\t\t\t\t\t[0.4, '#606060'],\r\n\t\t\t\t\t\t[0.6, '#333333']\r\n\t\t\t\t\t]\r\n\t\t\t\t},\r\n\t\t\t\tstroke: '#000000'\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\r\n\t// scroll charts\r\n\trangeSelector: {\r\n\t\tbuttonTheme: {\r\n\t\t\tfill: {\r\n\t\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\r\n\t\t\t\tstops: [\r\n\t\t\t\t\t[0.4, '#888'],\r\n\t\t\t\t\t[0.6, '#555']\r\n\t\t\t\t]\r\n\t\t\t},\r\n\t\t\tstroke: '#000000',\r\n\t\t\tstyle: {\r\n\t\t\t\tcolor: '#CCC',\r\n\t\t\t\tfontWeight: 'bold'\r\n\t\t\t},\r\n\t\t\tstates: {\r\n\t\t\t\thover: {\r\n\t\t\t\t\tfill: {\r\n\t\t\t\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\r\n\t\t\t\t\t\tstops: [\r\n\t\t\t\t\t\t\t[0.4, '#BBB'],\r\n\t\t\t\t\t\t\t[0.6, '#888']\r\n\t\t\t\t\t\t]\r\n\t\t\t\t\t},\r\n\t\t\t\t\tstroke: '#000000',\r\n\t\t\t\t\tstyle: {\r\n\t\t\t\t\t\tcolor: 'white'\r\n\t\t\t\t\t}\r\n\t\t\t\t},\r\n\t\t\t\tselect: {\r\n\t\t\t\t\tfill: {\r\n\t\t\t\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\r\n\t\t\t\t\t\tstops: [\r\n\t\t\t\t\t\t\t[0.1, '#000'],\r\n\t\t\t\t\t\t\t[0.3, '#333']\r\n\t\t\t\t\t\t]\r\n\t\t\t\t\t},\r\n\t\t\t\t\tstroke: '#000000',\r\n\t\t\t\t\tstyle: {\r\n\t\t\t\t\t\tcolor: 'yellow'\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\tinputStyle: {\r\n\t\t\tbackgroundColor: '#333',\r\n\t\t\tcolor: 'silver'\r\n\t\t},\r\n\t\tlabelStyle: {\r\n\t\t\tcolor: 'silver'\r\n\t\t}\r\n\t},\r\n\r\n\tnavigator: {\r\n\t\thandles: {\r\n\t\t\tbackgroundColor: '#666',\r\n\t\t\tborderColor: '#AAA'\r\n\t\t},\r\n\t\toutlineColor: '#CCC',\r\n\t\tmaskFill: 'rgba(16, 16, 16, 0.5)',\r\n\t\tseries: {\r\n\t\t\tcolor: '#7798BF',\r\n\t\t\tlineColor: '#A6C7ED'\r\n\t\t}\r\n\t},\r\n\r\n\tscrollbar: {\r\n\t\tbarBackgroundColor: {\r\n\t\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\r\n\t\t\t\tstops: [\r\n\t\t\t\t\t[0.4, '#888'],\r\n\t\t\t\t\t[0.6, '#555']\r\n\t\t\t\t]\r\n\t\t\t},\r\n\t\tbarBorderColor: '#CCC',\r\n\t\tbuttonArrowColor: '#CCC',\r\n\t\tbuttonBackgroundColor: {\r\n\t\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\r\n\t\t\t\tstops: [\r\n\t\t\t\t\t[0.4, '#888'],\r\n\t\t\t\t\t[0.6, '#555']\r\n\t\t\t\t]\r\n\t\t\t},\r\n\t\tbuttonBorderColor: '#CCC',\r\n\t\trifleColor: '#FFF',\r\n\t\ttrackBackgroundColor: {\r\n\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\r\n\t\t\tstops: [\r\n\t\t\t\t[0, '#000'],\r\n\t\t\t\t[1, '#333']\r\n\t\t\t]\r\n\t\t},\r\n\t\ttrackBorderColor: '#666'\r\n\t},\r\n\r\n\t// special colors for some of the\r\n\tlegendBackgroundColor: 'rgba(0, 0, 0, 0.5)',\r\n\tlegendBackgroundColorSolid: 'rgb(35, 35, 70)',\r\n\tdataLabelsColor: '#444',\r\n\ttextColor: '#C0C0C0',\r\n\tmaskColor: 'rgba(255,255,255,0.3)'\r\n};\r\n\r\n// Apply the theme\r\nvar highchartsOptions = Highcharts.setOptions(Highcharts.theme);\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/highcharts/themes/dark-green.js",
    "content": "/**\r\n * Dark blue theme for Highcharts JS\r\n * @author Torstein Hønsi\r\n */\r\n\r\nHighcharts.theme = {\r\n\tcolors: [\"#DDDF0D\", \"#55BF3B\", \"#DF5353\", \"#7798BF\", \"#aaeeee\", \"#ff0066\", \"#eeaaee\",\r\n\t\t\"#55BF3B\", \"#DF5353\", \"#7798BF\", \"#aaeeee\"],\r\n\tchart: {\r\n\t\tbackgroundColor: {\r\n\t\t\tlinearGradient: [0, 0, 250, 500],\r\n\t\t\tstops: [\r\n\t\t\t\t[0, 'rgb(48, 96, 48)'],\r\n\t\t\t\t[1, 'rgb(0, 0, 0)']\r\n\t\t\t]\r\n\t\t},\r\n\t\tborderColor: '#000000',\r\n\t\tborderWidth: 2,\r\n\t\tclassName: 'dark-container',\r\n\t\tplotBackgroundColor: 'rgba(255, 255, 255, .1)',\r\n\t\tplotBorderColor: '#CCCCCC',\r\n\t\tplotBorderWidth: 1\r\n\t},\r\n\ttitle: {\r\n\t\tstyle: {\r\n\t\t\tcolor: '#C0C0C0',\r\n\t\t\tfont: 'bold 16px \"Trebuchet MS\", Verdana, sans-serif'\r\n\t\t}\r\n\t},\r\n\tsubtitle: {\r\n\t\tstyle: {\r\n\t\t\tcolor: '#666666',\r\n\t\t\tfont: 'bold 12px \"Trebuchet MS\", Verdana, sans-serif'\r\n\t\t}\r\n\t},\r\n\txAxis: {\r\n\t\tgridLineColor: '#333333',\r\n\t\tgridLineWidth: 1,\r\n\t\tlabels: {\r\n\t\t\tstyle: {\r\n\t\t\t\tcolor: '#A0A0A0'\r\n\t\t\t}\r\n\t\t},\r\n\t\tlineColor: '#A0A0A0',\r\n\t\ttickColor: '#A0A0A0',\r\n\t\ttitle: {\r\n\t\t\tstyle: {\r\n\t\t\t\tcolor: '#CCC',\r\n\t\t\t\tfontWeight: 'bold',\r\n\t\t\t\tfontSize: '12px',\r\n\t\t\t\tfontFamily: 'Trebuchet MS, Verdana, sans-serif'\r\n\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\tyAxis: {\r\n\t\tgridLineColor: '#333333',\r\n\t\tlabels: {\r\n\t\t\tstyle: {\r\n\t\t\t\tcolor: '#A0A0A0'\r\n\t\t\t}\r\n\t\t},\r\n\t\tlineColor: '#A0A0A0',\r\n\t\tminorTickInterval: null,\r\n\t\ttickColor: '#A0A0A0',\r\n\t\ttickWidth: 1,\r\n\t\ttitle: {\r\n\t\t\tstyle: {\r\n\t\t\t\tcolor: '#CCC',\r\n\t\t\t\tfontWeight: 'bold',\r\n\t\t\t\tfontSize: '12px',\r\n\t\t\t\tfontFamily: 'Trebuchet MS, Verdana, sans-serif'\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\ttooltip: {\r\n\t\tbackgroundColor: 'rgba(0, 0, 0, 0.75)',\r\n\t\tstyle: {\r\n\t\t\tcolor: '#F0F0F0'\r\n\t\t}\r\n\t},\r\n\ttoolbar: {\r\n\t\titemStyle: {\r\n\t\t\tcolor: 'silver'\r\n\t\t}\r\n\t},\r\n\tplotOptions: {\r\n\t\tline: {\r\n\t\t\tdataLabels: {\r\n\t\t\t\tcolor: '#CCC'\r\n\t\t\t},\r\n\t\t\tmarker: {\r\n\t\t\t\tlineColor: '#333'\r\n\t\t\t}\r\n\t\t},\r\n\t\tspline: {\r\n\t\t\tmarker: {\r\n\t\t\t\tlineColor: '#333'\r\n\t\t\t}\r\n\t\t},\r\n\t\tscatter: {\r\n\t\t\tmarker: {\r\n\t\t\t\tlineColor: '#333'\r\n\t\t\t}\r\n\t\t},\r\n\t\tcandlestick: {\r\n\t\t\tlineColor: 'white'\r\n\t\t}\r\n\t},\r\n\tlegend: {\r\n\t\titemStyle: {\r\n\t\t\tfont: '9pt Trebuchet MS, Verdana, sans-serif',\r\n\t\t\tcolor: '#A0A0A0'\r\n\t\t},\r\n\t\titemHoverStyle: {\r\n\t\t\tcolor: '#FFF'\r\n\t\t},\r\n\t\titemHiddenStyle: {\r\n\t\t\tcolor: '#444'\r\n\t\t}\r\n\t},\r\n\tcredits: {\r\n\t\tstyle: {\r\n\t\t\tcolor: '#666'\r\n\t\t}\r\n\t},\r\n\tlabels: {\r\n\t\tstyle: {\r\n\t\t\tcolor: '#CCC'\r\n\t\t}\r\n\t},\r\n\r\n\r\n\tnavigation: {\r\n\t\tbuttonOptions: {\r\n\t\t\tsymbolStroke: '#DDDDDD',\r\n\t\t\thoverSymbolStroke: '#FFFFFF',\r\n\t\t\ttheme: {\r\n\t\t\t\tfill: {\r\n\t\t\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\r\n\t\t\t\t\tstops: [\r\n\t\t\t\t\t\t[0.4, '#606060'],\r\n\t\t\t\t\t\t[0.6, '#333333']\r\n\t\t\t\t\t]\r\n\t\t\t\t},\r\n\t\t\t\tstroke: '#000000'\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\r\n\t// scroll charts\r\n\trangeSelector: {\r\n\t\tbuttonTheme: {\r\n\t\t\tfill: {\r\n\t\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\r\n\t\t\t\tstops: [\r\n\t\t\t\t\t[0.4, '#888'],\r\n\t\t\t\t\t[0.6, '#555']\r\n\t\t\t\t]\r\n\t\t\t},\r\n\t\t\tstroke: '#000000',\r\n\t\t\tstyle: {\r\n\t\t\t\tcolor: '#CCC',\r\n\t\t\t\tfontWeight: 'bold'\r\n\t\t\t},\r\n\t\t\tstates: {\r\n\t\t\t\thover: {\r\n\t\t\t\t\tfill: {\r\n\t\t\t\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\r\n\t\t\t\t\t\tstops: [\r\n\t\t\t\t\t\t\t[0.4, '#BBB'],\r\n\t\t\t\t\t\t\t[0.6, '#888']\r\n\t\t\t\t\t\t]\r\n\t\t\t\t\t},\r\n\t\t\t\t\tstroke: '#000000',\r\n\t\t\t\t\tstyle: {\r\n\t\t\t\t\t\tcolor: 'white'\r\n\t\t\t\t\t}\r\n\t\t\t\t},\r\n\t\t\t\tselect: {\r\n\t\t\t\t\tfill: {\r\n\t\t\t\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\r\n\t\t\t\t\t\tstops: [\r\n\t\t\t\t\t\t\t[0.1, '#000'],\r\n\t\t\t\t\t\t\t[0.3, '#333']\r\n\t\t\t\t\t\t]\r\n\t\t\t\t\t},\r\n\t\t\t\t\tstroke: '#000000',\r\n\t\t\t\t\tstyle: {\r\n\t\t\t\t\t\tcolor: 'yellow'\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\tinputStyle: {\r\n\t\t\tbackgroundColor: '#333',\r\n\t\t\tcolor: 'silver'\r\n\t\t},\r\n\t\tlabelStyle: {\r\n\t\t\tcolor: 'silver'\r\n\t\t}\r\n\t},\r\n\r\n\tnavigator: {\r\n\t\thandles: {\r\n\t\t\tbackgroundColor: '#666',\r\n\t\t\tborderColor: '#AAA'\r\n\t\t},\r\n\t\toutlineColor: '#CCC',\r\n\t\tmaskFill: 'rgba(16, 16, 16, 0.5)',\r\n\t\tseries: {\r\n\t\t\tcolor: '#7798BF',\r\n\t\t\tlineColor: '#A6C7ED'\r\n\t\t}\r\n\t},\r\n\r\n\tscrollbar: {\r\n\t\tbarBackgroundColor: {\r\n\t\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\r\n\t\t\t\tstops: [\r\n\t\t\t\t\t[0.4, '#888'],\r\n\t\t\t\t\t[0.6, '#555']\r\n\t\t\t\t]\r\n\t\t\t},\r\n\t\tbarBorderColor: '#CCC',\r\n\t\tbuttonArrowColor: '#CCC',\r\n\t\tbuttonBackgroundColor: {\r\n\t\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\r\n\t\t\t\tstops: [\r\n\t\t\t\t\t[0.4, '#888'],\r\n\t\t\t\t\t[0.6, '#555']\r\n\t\t\t\t]\r\n\t\t\t},\r\n\t\tbuttonBorderColor: '#CCC',\r\n\t\trifleColor: '#FFF',\r\n\t\ttrackBackgroundColor: {\r\n\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\r\n\t\t\tstops: [\r\n\t\t\t\t[0, '#000'],\r\n\t\t\t\t[1, '#333']\r\n\t\t\t]\r\n\t\t},\r\n\t\ttrackBorderColor: '#666'\r\n\t},\r\n\r\n\t// special colors for some of the\r\n\tlegendBackgroundColor: 'rgba(0, 0, 0, 0.5)',\r\n\tlegendBackgroundColorSolid: 'rgb(35, 35, 70)',\r\n\tdataLabelsColor: '#444',\r\n\ttextColor: '#C0C0C0',\r\n\tmaskColor: 'rgba(255,255,255,0.3)'\r\n};\r\n\r\n// Apply the theme\r\nvar highchartsOptions = Highcharts.setOptions(Highcharts.theme);\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/highcharts/themes/gray.js",
    "content": "/**\r\n * Gray theme for Highcharts JS\r\n * @author Torstein Hønsi\r\n */\r\n\r\nHighcharts.theme = {\r\n\tcolors: [\"#DDDF0D\", \"#7798BF\", \"#55BF3B\", \"#DF5353\", \"#aaeeee\", \"#ff0066\", \"#eeaaee\",\r\n\t\t\"#55BF3B\", \"#DF5353\", \"#7798BF\", \"#aaeeee\"],\r\n\tchart: {\r\n\t\tbackgroundColor: {\r\n\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\r\n\t\t\tstops: [\r\n\t\t\t\t[0, 'rgb(96, 96, 96)'],\r\n\t\t\t\t[1, 'rgb(16, 16, 16)']\r\n\t\t\t]\r\n\t\t},\r\n\t\tborderWidth: 0,\r\n\t\tborderRadius: 15,\r\n\t\tplotBackgroundColor: null,\r\n\t\tplotShadow: false,\r\n\t\tplotBorderWidth: 0\r\n\t},\r\n\ttitle: {\r\n\t\tstyle: {\r\n\t\t\tcolor: '#FFF',\r\n\t\t\tfont: '16px Lucida Grande, Lucida Sans Unicode, Verdana, Arial, Helvetica, sans-serif'\r\n\t\t}\r\n\t},\r\n\tsubtitle: {\r\n\t\tstyle: {\r\n\t\t\tcolor: '#DDD',\r\n\t\t\tfont: '12px Lucida Grande, Lucida Sans Unicode, Verdana, Arial, Helvetica, sans-serif'\r\n\t\t}\r\n\t},\r\n\txAxis: {\r\n\t\tgridLineWidth: 0,\r\n\t\tlineColor: '#999',\r\n\t\ttickColor: '#999',\r\n\t\tlabels: {\r\n\t\t\tstyle: {\r\n\t\t\t\tcolor: '#999',\r\n\t\t\t\tfontWeight: 'bold'\r\n\t\t\t}\r\n\t\t},\r\n\t\ttitle: {\r\n\t\t\tstyle: {\r\n\t\t\t\tcolor: '#AAA',\r\n\t\t\t\tfont: 'bold 12px Lucida Grande, Lucida Sans Unicode, Verdana, Arial, Helvetica, sans-serif'\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\tyAxis: {\r\n\t\talternateGridColor: null,\r\n\t\tminorTickInterval: null,\r\n\t\tgridLineColor: 'rgba(255, 255, 255, .1)',\r\n\t\tminorGridLineColor: 'rgba(255,255,255,0.07)',\r\n\t\tlineWidth: 0,\r\n\t\ttickWidth: 0,\r\n\t\tlabels: {\r\n\t\t\tstyle: {\r\n\t\t\t\tcolor: '#999',\r\n\t\t\t\tfontWeight: 'bold'\r\n\t\t\t}\r\n\t\t},\r\n\t\ttitle: {\r\n\t\t\tstyle: {\r\n\t\t\t\tcolor: '#AAA',\r\n\t\t\t\tfont: 'bold 12px Lucida Grande, Lucida Sans Unicode, Verdana, Arial, Helvetica, sans-serif'\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\tlegend: {\r\n\t\titemStyle: {\r\n\t\t\tcolor: '#CCC'\r\n\t\t},\r\n\t\titemHoverStyle: {\r\n\t\t\tcolor: '#FFF'\r\n\t\t},\r\n\t\titemHiddenStyle: {\r\n\t\t\tcolor: '#333'\r\n\t\t}\r\n\t},\r\n\tlabels: {\r\n\t\tstyle: {\r\n\t\t\tcolor: '#CCC'\r\n\t\t}\r\n\t},\r\n\ttooltip: {\r\n\t\tbackgroundColor: {\r\n\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\r\n\t\t\tstops: [\r\n\t\t\t\t[0, 'rgba(96, 96, 96, .8)'],\r\n\t\t\t\t[1, 'rgba(16, 16, 16, .8)']\r\n\t\t\t]\r\n\t\t},\r\n\t\tborderWidth: 0,\r\n\t\tstyle: {\r\n\t\t\tcolor: '#FFF'\r\n\t\t}\r\n\t},\r\n\r\n\r\n\tplotOptions: {\r\n\t\tseries: {\r\n\t\t\tshadow: true\r\n\t\t},\r\n\t\tline: {\r\n\t\t\tdataLabels: {\r\n\t\t\t\tcolor: '#CCC'\r\n\t\t\t},\r\n\t\t\tmarker: {\r\n\t\t\t\tlineColor: '#333'\r\n\t\t\t}\r\n\t\t},\r\n\t\tspline: {\r\n\t\t\tmarker: {\r\n\t\t\t\tlineColor: '#333'\r\n\t\t\t}\r\n\t\t},\r\n\t\tscatter: {\r\n\t\t\tmarker: {\r\n\t\t\t\tlineColor: '#333'\r\n\t\t\t}\r\n\t\t},\r\n\t\tcandlestick: {\r\n\t\t\tlineColor: 'white'\r\n\t\t}\r\n\t},\r\n\r\n\ttoolbar: {\r\n\t\titemStyle: {\r\n\t\t\tcolor: '#CCC'\r\n\t\t}\r\n\t},\r\n\r\n\tnavigation: {\r\n\t\tbuttonOptions: {\r\n\t\t\tsymbolStroke: '#DDDDDD',\r\n\t\t\thoverSymbolStroke: '#FFFFFF',\r\n\t\t\ttheme: {\r\n\t\t\t\tfill: {\r\n\t\t\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\r\n\t\t\t\t\tstops: [\r\n\t\t\t\t\t\t[0.4, '#606060'],\r\n\t\t\t\t\t\t[0.6, '#333333']\r\n\t\t\t\t\t]\r\n\t\t\t\t},\r\n\t\t\t\tstroke: '#000000'\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\r\n\t// scroll charts\r\n\trangeSelector: {\r\n\t\tbuttonTheme: {\r\n\t\t\tfill: {\r\n\t\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\r\n\t\t\t\tstops: [\r\n\t\t\t\t\t[0.4, '#888'],\r\n\t\t\t\t\t[0.6, '#555']\r\n\t\t\t\t]\r\n\t\t\t},\r\n\t\t\tstroke: '#000000',\r\n\t\t\tstyle: {\r\n\t\t\t\tcolor: '#CCC',\r\n\t\t\t\tfontWeight: 'bold'\r\n\t\t\t},\r\n\t\t\tstates: {\r\n\t\t\t\thover: {\r\n\t\t\t\t\tfill: {\r\n\t\t\t\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\r\n\t\t\t\t\t\tstops: [\r\n\t\t\t\t\t\t\t[0.4, '#BBB'],\r\n\t\t\t\t\t\t\t[0.6, '#888']\r\n\t\t\t\t\t\t]\r\n\t\t\t\t\t},\r\n\t\t\t\t\tstroke: '#000000',\r\n\t\t\t\t\tstyle: {\r\n\t\t\t\t\t\tcolor: 'white'\r\n\t\t\t\t\t}\r\n\t\t\t\t},\r\n\t\t\t\tselect: {\r\n\t\t\t\t\tfill: {\r\n\t\t\t\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\r\n\t\t\t\t\t\tstops: [\r\n\t\t\t\t\t\t\t[0.1, '#000'],\r\n\t\t\t\t\t\t\t[0.3, '#333']\r\n\t\t\t\t\t\t]\r\n\t\t\t\t\t},\r\n\t\t\t\t\tstroke: '#000000',\r\n\t\t\t\t\tstyle: {\r\n\t\t\t\t\t\tcolor: 'yellow'\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\tinputStyle: {\r\n\t\t\tbackgroundColor: '#333',\r\n\t\t\tcolor: 'silver'\r\n\t\t},\r\n\t\tlabelStyle: {\r\n\t\t\tcolor: 'silver'\r\n\t\t}\r\n\t},\r\n\r\n\tnavigator: {\r\n\t\thandles: {\r\n\t\t\tbackgroundColor: '#666',\r\n\t\t\tborderColor: '#AAA'\r\n\t\t},\r\n\t\toutlineColor: '#CCC',\r\n\t\tmaskFill: 'rgba(16, 16, 16, 0.5)',\r\n\t\tseries: {\r\n\t\t\tcolor: '#7798BF',\r\n\t\t\tlineColor: '#A6C7ED'\r\n\t\t}\r\n\t},\r\n\r\n\tscrollbar: {\r\n\t\tbarBackgroundColor: {\r\n\t\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\r\n\t\t\t\tstops: [\r\n\t\t\t\t\t[0.4, '#888'],\r\n\t\t\t\t\t[0.6, '#555']\r\n\t\t\t\t]\r\n\t\t\t},\r\n\t\tbarBorderColor: '#CCC',\r\n\t\tbuttonArrowColor: '#CCC',\r\n\t\tbuttonBackgroundColor: {\r\n\t\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\r\n\t\t\t\tstops: [\r\n\t\t\t\t\t[0.4, '#888'],\r\n\t\t\t\t\t[0.6, '#555']\r\n\t\t\t\t]\r\n\t\t\t},\r\n\t\tbuttonBorderColor: '#CCC',\r\n\t\trifleColor: '#FFF',\r\n\t\ttrackBackgroundColor: {\r\n\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\r\n\t\t\tstops: [\r\n\t\t\t\t[0, '#000'],\r\n\t\t\t\t[1, '#333']\r\n\t\t\t]\r\n\t\t},\r\n\t\ttrackBorderColor: '#666'\r\n\t},\r\n\r\n\t// special colors for some of the demo examples\r\n\tlegendBackgroundColor: 'rgba(48, 48, 48, 0.8)',\r\n\tlegendBackgroundColorSolid: 'rgb(70, 70, 70)',\r\n\tdataLabelsColor: '#444',\r\n\ttextColor: '#E0E0E0',\r\n\tmaskColor: 'rgba(255,255,255,0.3)'\r\n};\r\n\r\n// Apply the theme\r\nvar highchartsOptions = Highcharts.setOptions(Highcharts.theme);\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/highcharts/themes/grid.js",
    "content": "/**\r\n * Grid theme for Highcharts JS\r\n * @author Torstein Hønsi\r\n */\r\n\r\nHighcharts.theme = {\r\n\tcolors: ['#058DC7', '#50B432', '#ED561B', '#DDDF00', '#24CBE5', '#64E572', '#FF9655', '#FFF263', '#6AF9C4'],\r\n\tchart: {\r\n\t\tbackgroundColor: {\r\n\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 1, y2: 1 },\r\n\t\t\tstops: [\r\n\t\t\t\t[0, 'rgb(255, 255, 255)'],\r\n\t\t\t\t[1, 'rgb(240, 240, 255)']\r\n\t\t\t]\r\n\t\t},\r\n\t\tborderWidth: 2,\r\n\t\tplotBackgroundColor: 'rgba(255, 255, 255, .9)',\r\n\t\tplotShadow: true,\r\n\t\tplotBorderWidth: 1\r\n\t},\r\n\ttitle: {\r\n\t\tstyle: {\r\n\t\t\tcolor: '#000',\r\n\t\t\tfont: 'bold 16px \"Trebuchet MS\", Verdana, sans-serif'\r\n\t\t}\r\n\t},\r\n\tsubtitle: {\r\n\t\tstyle: {\r\n\t\t\tcolor: '#666666',\r\n\t\t\tfont: 'bold 12px \"Trebuchet MS\", Verdana, sans-serif'\r\n\t\t}\r\n\t},\r\n\txAxis: {\r\n\t\tgridLineWidth: 1,\r\n\t\tlineColor: '#000',\r\n\t\ttickColor: '#000',\r\n\t\tlabels: {\r\n\t\t\tstyle: {\r\n\t\t\t\tcolor: '#000',\r\n\t\t\t\tfont: '11px Trebuchet MS, Verdana, sans-serif'\r\n\t\t\t}\r\n\t\t},\r\n\t\ttitle: {\r\n\t\t\tstyle: {\r\n\t\t\t\tcolor: '#333',\r\n\t\t\t\tfontWeight: 'bold',\r\n\t\t\t\tfontSize: '12px',\r\n\t\t\t\tfontFamily: 'Trebuchet MS, Verdana, sans-serif'\r\n\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\tyAxis: {\r\n\t\tminorTickInterval: 'auto',\r\n\t\tlineColor: '#000',\r\n\t\tlineWidth: 1,\r\n\t\ttickWidth: 1,\r\n\t\ttickColor: '#000',\r\n\t\tlabels: {\r\n\t\t\tstyle: {\r\n\t\t\t\tcolor: '#000',\r\n\t\t\t\tfont: '11px Trebuchet MS, Verdana, sans-serif'\r\n\t\t\t}\r\n\t\t},\r\n\t\ttitle: {\r\n\t\t\tstyle: {\r\n\t\t\t\tcolor: '#333',\r\n\t\t\t\tfontWeight: 'bold',\r\n\t\t\t\tfontSize: '12px',\r\n\t\t\t\tfontFamily: 'Trebuchet MS, Verdana, sans-serif'\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\tlegend: {\r\n\t\titemStyle: {\r\n\t\t\tfont: '9pt Trebuchet MS, Verdana, sans-serif',\r\n\t\t\tcolor: 'black'\r\n\r\n\t\t},\r\n\t\titemHoverStyle: {\r\n\t\t\tcolor: '#039'\r\n\t\t},\r\n\t\titemHiddenStyle: {\r\n\t\t\tcolor: 'gray'\r\n\t\t}\r\n\t},\r\n\tlabels: {\r\n\t\tstyle: {\r\n\t\t\tcolor: '#99b'\r\n\t\t}\r\n\t},\r\n\r\n\tnavigation: {\r\n\t\tbuttonOptions: {\r\n\t\t\ttheme: {\r\n\t\t\t\tstroke: '#CCCCCC'\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n// Apply the theme\r\nvar highchartsOptions = Highcharts.setOptions(Highcharts.theme);\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/highcharts/themes/skies.js",
    "content": "/**\r\n * Skies theme for Highcharts JS\r\n * @author Torstein Hønsi\r\n */\r\n\r\nHighcharts.theme = {\r\n\tcolors: [\"#514F78\", \"#42A07B\", \"#9B5E4A\", \"#72727F\", \"#1F949A\", \"#82914E\", \"#86777F\", \"#42A07B\"],\r\n\tchart: {\r\n\t\tclassName: 'skies',\r\n\t\tborderWidth: 0,\r\n\t\tplotShadow: true,\r\n\t\tplotBackgroundImage: 'http://www.highcharts.com/demo/gfx/skies.jpg',\r\n\t\tplotBackgroundColor: {\r\n\t\t\tlinearGradient: [0, 0, 250, 500],\r\n\t\t\tstops: [\r\n\t\t\t\t[0, 'rgba(255, 255, 255, 1)'],\r\n\t\t\t\t[1, 'rgba(255, 255, 255, 0)']\r\n\t\t\t]\r\n\t\t},\r\n\t\tplotBorderWidth: 1\r\n\t},\r\n\ttitle: {\r\n\t\tstyle: {\r\n\t\t\tcolor: '#3E576F',\r\n\t\t\tfont: '16px Lucida Grande, Lucida Sans Unicode, Verdana, Arial, Helvetica, sans-serif'\r\n\t\t}\r\n\t},\r\n\tsubtitle: {\r\n\t\tstyle: {\r\n\t\t\tcolor: '#6D869F',\r\n\t\t\tfont: '12px Lucida Grande, Lucida Sans Unicode, Verdana, Arial, Helvetica, sans-serif'\r\n\t\t}\r\n\t},\r\n\txAxis: {\r\n\t\tgridLineWidth: 0,\r\n\t\tlineColor: '#C0D0E0',\r\n\t\ttickColor: '#C0D0E0',\r\n\t\tlabels: {\r\n\t\t\tstyle: {\r\n\t\t\t\tcolor: '#666',\r\n\t\t\t\tfontWeight: 'bold'\r\n\t\t\t}\r\n\t\t},\r\n\t\ttitle: {\r\n\t\t\tstyle: {\r\n\t\t\t\tcolor: '#666',\r\n\t\t\t\tfont: '12px Lucida Grande, Lucida Sans Unicode, Verdana, Arial, Helvetica, sans-serif'\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\tyAxis: {\r\n\t\talternateGridColor: 'rgba(255, 255, 255, .5)',\r\n\t\tlineColor: '#C0D0E0',\r\n\t\ttickColor: '#C0D0E0',\r\n\t\ttickWidth: 1,\r\n\t\tlabels: {\r\n\t\t\tstyle: {\r\n\t\t\t\tcolor: '#666',\r\n\t\t\t\tfontWeight: 'bold'\r\n\t\t\t}\r\n\t\t},\r\n\t\ttitle: {\r\n\t\t\tstyle: {\r\n\t\t\t\tcolor: '#666',\r\n\t\t\t\tfont: '12px Lucida Grande, Lucida Sans Unicode, Verdana, Arial, Helvetica, sans-serif'\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\tlegend: {\r\n\t\titemStyle: {\r\n\t\t\tfont: '9pt Trebuchet MS, Verdana, sans-serif',\r\n\t\t\tcolor: '#3E576F'\r\n\t\t},\r\n\t\titemHoverStyle: {\r\n\t\t\tcolor: 'black'\r\n\t\t},\r\n\t\titemHiddenStyle: {\r\n\t\t\tcolor: 'silver'\r\n\t\t}\r\n\t},\r\n\tlabels: {\r\n\t\tstyle: {\r\n\t\t\tcolor: '#3E576F'\r\n\t\t}\r\n\t}\r\n};\r\n\r\n// Apply the theme\r\nvar highchartsOptions = Highcharts.setOptions(Highcharts.theme);\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/jquery-1.10.2.js",
    "content": "/*!\r\n * jQuery JavaScript Library v1.10.2\r\n * http://jquery.com/\r\n *\r\n * Includes Sizzle.js\r\n * http://sizzlejs.com/\r\n *\r\n * Copyright 2005, 2013 jQuery Foundation, Inc. and other contributors\r\n * Released under the MIT license\r\n * http://jquery.org/license\r\n *\r\n * Date: 2013-07-03T13:48Z\r\n */\r\n(function( window, undefined ) {\r\n\r\n// Can't do this because several apps including ASP.NET trace\r\n// the stack via arguments.caller.callee and Firefox dies if\r\n// you try to trace through \"use strict\" call chains. (#13335)\r\n// Support: Firefox 18+\r\n//\"use strict\";\r\nvar\r\n\t// The deferred used on DOM ready\r\n\treadyList,\r\n\r\n\t// A central reference to the root jQuery(document)\r\n\trootjQuery,\r\n\r\n\t// Support: IE<10\r\n\t// For `typeof xmlNode.method` instead of `xmlNode.method !== undefined`\r\n\tcore_strundefined = typeof undefined,\r\n\r\n\t// Use the correct document accordingly with window argument (sandbox)\r\n\tlocation = window.location,\r\n\tdocument = window.document,\r\n\tdocElem = document.documentElement,\r\n\r\n\t// Map over jQuery in case of overwrite\r\n\t_jQuery = window.jQuery,\r\n\r\n\t// Map over the $ in case of overwrite\r\n\t_$ = window.$,\r\n\r\n\t// [[Class]] -> type pairs\r\n\tclass2type = {},\r\n\r\n\t// List of deleted data cache ids, so we can reuse them\r\n\tcore_deletedIds = [],\r\n\r\n\tcore_version = \"1.10.2\",\r\n\r\n\t// Save a reference to some core methods\r\n\tcore_concat = core_deletedIds.concat,\r\n\tcore_push = core_deletedIds.push,\r\n\tcore_slice = core_deletedIds.slice,\r\n\tcore_indexOf = core_deletedIds.indexOf,\r\n\tcore_toString = class2type.toString,\r\n\tcore_hasOwn = class2type.hasOwnProperty,\r\n\tcore_trim = core_version.trim,\r\n\r\n\t// Define a local copy of jQuery\r\n\tjQuery = function( selector, context ) {\r\n\t\t// The jQuery object is actually just the init constructor 'enhanced'\r\n\t\treturn new jQuery.fn.init( selector, context, rootjQuery );\r\n\t},\r\n\r\n\t// Used for matching numbers\r\n\tcore_pnum = /[+-]?(?:\\d*\\.|)\\d+(?:[eE][+-]?\\d+|)/.source,\r\n\r\n\t// Used for splitting on whitespace\r\n\tcore_rnotwhite = /\\S+/g,\r\n\r\n\t// Make sure we trim BOM and NBSP (here's looking at you, Safari 5.0 and IE)\r\n\trtrim = /^[\\s\\uFEFF\\xA0]+|[\\s\\uFEFF\\xA0]+$/g,\r\n\r\n\t// A simple way to check for HTML strings\r\n\t// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)\r\n\t// Strict HTML recognition (#11290: must start with <)\r\n\trquickExpr = /^(?:\\s*(<[\\w\\W]+>)[^>]*|#([\\w-]*))$/,\r\n\r\n\t// Match a standalone tag\r\n\trsingleTag = /^<(\\w+)\\s*\\/?>(?:<\\/\\1>|)$/,\r\n\r\n\t// JSON RegExp\r\n\trvalidchars = /^[\\],:{}\\s]*$/,\r\n\trvalidbraces = /(?:^|:|,)(?:\\s*\\[)+/g,\r\n\trvalidescape = /\\\\(?:[\"\\\\\\/bfnrt]|u[\\da-fA-F]{4})/g,\r\n\trvalidtokens = /\"[^\"\\\\\\r\\n]*\"|true|false|null|-?(?:\\d+\\.|)\\d+(?:[eE][+-]?\\d+|)/g,\r\n\r\n\t// Matches dashed string for camelizing\r\n\trmsPrefix = /^-ms-/,\r\n\trdashAlpha = /-([\\da-z])/gi,\r\n\r\n\t// Used by jQuery.camelCase as callback to replace()\r\n\tfcamelCase = function( all, letter ) {\r\n\t\treturn letter.toUpperCase();\r\n\t},\r\n\r\n\t// The ready event handler\r\n\tcompleted = function( event ) {\r\n\r\n\t\t// readyState === \"complete\" is good enough for us to call the dom ready in oldIE\r\n\t\tif ( document.addEventListener || event.type === \"load\" || document.readyState === \"complete\" ) {\r\n\t\t\tdetach();\r\n\t\t\tjQuery.ready();\r\n\t\t}\r\n\t},\r\n\t// Clean-up method for dom ready events\r\n\tdetach = function() {\r\n\t\tif ( document.addEventListener ) {\r\n\t\t\tdocument.removeEventListener( \"DOMContentLoaded\", completed, false );\r\n\t\t\twindow.removeEventListener( \"load\", completed, false );\r\n\r\n\t\t} else {\r\n\t\t\tdocument.detachEvent( \"onreadystatechange\", completed );\r\n\t\t\twindow.detachEvent( \"onload\", completed );\r\n\t\t}\r\n\t};\r\n\r\njQuery.fn = jQuery.prototype = {\r\n\t// The current version of jQuery being used\r\n\tjquery: core_version,\r\n\r\n\tconstructor: jQuery,\r\n\tinit: function( selector, context, rootjQuery ) {\r\n\t\tvar match, elem;\r\n\r\n\t\t// HANDLE: $(\"\"), $(null), $(undefined), $(false)\r\n\t\tif ( !selector ) {\r\n\t\t\treturn this;\r\n\t\t}\r\n\r\n\t\t// Handle HTML strings\r\n\t\tif ( typeof selector === \"string\" ) {\r\n\t\t\tif ( selector.charAt(0) === \"<\" && selector.charAt( selector.length - 1 ) === \">\" && selector.length >= 3 ) {\r\n\t\t\t\t// Assume that strings that start and end with <> are HTML and skip the regex check\r\n\t\t\t\tmatch = [ null, selector, null ];\r\n\r\n\t\t\t} else {\r\n\t\t\t\tmatch = rquickExpr.exec( selector );\r\n\t\t\t}\r\n\r\n\t\t\t// Match html or make sure no context is specified for #id\r\n\t\t\tif ( match && (match[1] || !context) ) {\r\n\r\n\t\t\t\t// HANDLE: $(html) -> $(array)\r\n\t\t\t\tif ( match[1] ) {\r\n\t\t\t\t\tcontext = context instanceof jQuery ? context[0] : context;\r\n\r\n\t\t\t\t\t// scripts is true for back-compat\r\n\t\t\t\t\tjQuery.merge( this, jQuery.parseHTML(\r\n\t\t\t\t\t\tmatch[1],\r\n\t\t\t\t\t\tcontext && context.nodeType ? context.ownerDocument || context : document,\r\n\t\t\t\t\t\ttrue\r\n\t\t\t\t\t) );\r\n\r\n\t\t\t\t\t// HANDLE: $(html, props)\r\n\t\t\t\t\tif ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {\r\n\t\t\t\t\t\tfor ( match in context ) {\r\n\t\t\t\t\t\t\t// Properties of context are called as methods if possible\r\n\t\t\t\t\t\t\tif ( jQuery.isFunction( this[ match ] ) ) {\r\n\t\t\t\t\t\t\t\tthis[ match ]( context[ match ] );\r\n\r\n\t\t\t\t\t\t\t// ...and otherwise set as attributes\r\n\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\tthis.attr( match, context[ match ] );\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\treturn this;\r\n\r\n\t\t\t\t// HANDLE: $(#id)\r\n\t\t\t\t} else {\r\n\t\t\t\t\telem = document.getElementById( match[2] );\r\n\r\n\t\t\t\t\t// Check parentNode to catch when Blackberry 4.6 returns\r\n\t\t\t\t\t// nodes that are no longer in the document #6963\r\n\t\t\t\t\tif ( elem && elem.parentNode ) {\r\n\t\t\t\t\t\t// Handle the case where IE and Opera return items\r\n\t\t\t\t\t\t// by name instead of ID\r\n\t\t\t\t\t\tif ( elem.id !== match[2] ) {\r\n\t\t\t\t\t\t\treturn rootjQuery.find( selector );\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t// Otherwise, we inject the element directly into the jQuery object\r\n\t\t\t\t\t\tthis.length = 1;\r\n\t\t\t\t\t\tthis[0] = elem;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tthis.context = document;\r\n\t\t\t\t\tthis.selector = selector;\r\n\t\t\t\t\treturn this;\r\n\t\t\t\t}\r\n\r\n\t\t\t// HANDLE: $(expr, $(...))\r\n\t\t\t} else if ( !context || context.jquery ) {\r\n\t\t\t\treturn ( context || rootjQuery ).find( selector );\r\n\r\n\t\t\t// HANDLE: $(expr, context)\r\n\t\t\t// (which is just equivalent to: $(context).find(expr)\r\n\t\t\t} else {\r\n\t\t\t\treturn this.constructor( context ).find( selector );\r\n\t\t\t}\r\n\r\n\t\t// HANDLE: $(DOMElement)\r\n\t\t} else if ( selector.nodeType ) {\r\n\t\t\tthis.context = this[0] = selector;\r\n\t\t\tthis.length = 1;\r\n\t\t\treturn this;\r\n\r\n\t\t// HANDLE: $(function)\r\n\t\t// Shortcut for document ready\r\n\t\t} else if ( jQuery.isFunction( selector ) ) {\r\n\t\t\treturn rootjQuery.ready( selector );\r\n\t\t}\r\n\r\n\t\tif ( selector.selector !== undefined ) {\r\n\t\t\tthis.selector = selector.selector;\r\n\t\t\tthis.context = selector.context;\r\n\t\t}\r\n\r\n\t\treturn jQuery.makeArray( selector, this );\r\n\t},\r\n\r\n\t// Start with an empty selector\r\n\tselector: \"\",\r\n\r\n\t// The default length of a jQuery object is 0\r\n\tlength: 0,\r\n\r\n\ttoArray: function() {\r\n\t\treturn core_slice.call( this );\r\n\t},\r\n\r\n\t// Get the Nth element in the matched element set OR\r\n\t// Get the whole matched element set as a clean array\r\n\tget: function( num ) {\r\n\t\treturn num == null ?\r\n\r\n\t\t\t// Return a 'clean' array\r\n\t\t\tthis.toArray() :\r\n\r\n\t\t\t// Return just the object\r\n\t\t\t( num < 0 ? this[ this.length + num ] : this[ num ] );\r\n\t},\r\n\r\n\t// Take an array of elements and push it onto the stack\r\n\t// (returning the new matched element set)\r\n\tpushStack: function( elems ) {\r\n\r\n\t\t// Build a new jQuery matched element set\r\n\t\tvar ret = jQuery.merge( this.constructor(), elems );\r\n\r\n\t\t// Add the old object onto the stack (as a reference)\r\n\t\tret.prevObject = this;\r\n\t\tret.context = this.context;\r\n\r\n\t\t// Return the newly-formed element set\r\n\t\treturn ret;\r\n\t},\r\n\r\n\t// Execute a callback for every element in the matched set.\r\n\t// (You can seed the arguments with an array of args, but this is\r\n\t// only used internally.)\r\n\teach: function( callback, args ) {\r\n\t\treturn jQuery.each( this, callback, args );\r\n\t},\r\n\r\n\tready: function( fn ) {\r\n\t\t// Add the callback\r\n\t\tjQuery.ready.promise().done( fn );\r\n\r\n\t\treturn this;\r\n\t},\r\n\r\n\tslice: function() {\r\n\t\treturn this.pushStack( core_slice.apply( this, arguments ) );\r\n\t},\r\n\r\n\tfirst: function() {\r\n\t\treturn this.eq( 0 );\r\n\t},\r\n\r\n\tlast: function() {\r\n\t\treturn this.eq( -1 );\r\n\t},\r\n\r\n\teq: function( i ) {\r\n\t\tvar len = this.length,\r\n\t\t\tj = +i + ( i < 0 ? len : 0 );\r\n\t\treturn this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );\r\n\t},\r\n\r\n\tmap: function( callback ) {\r\n\t\treturn this.pushStack( jQuery.map(this, function( elem, i ) {\r\n\t\t\treturn callback.call( elem, i, elem );\r\n\t\t}));\r\n\t},\r\n\r\n\tend: function() {\r\n\t\treturn this.prevObject || this.constructor(null);\r\n\t},\r\n\r\n\t// For internal use only.\r\n\t// Behaves like an Array's method, not like a jQuery method.\r\n\tpush: core_push,\r\n\tsort: [].sort,\r\n\tsplice: [].splice\r\n};\r\n\r\n// Give the init function the jQuery prototype for later instantiation\r\njQuery.fn.init.prototype = jQuery.fn;\r\n\r\njQuery.extend = jQuery.fn.extend = function() {\r\n\tvar src, copyIsArray, copy, name, options, clone,\r\n\t\ttarget = arguments[0] || {},\r\n\t\ti = 1,\r\n\t\tlength = arguments.length,\r\n\t\tdeep = false;\r\n\r\n\t// Handle a deep copy situation\r\n\tif ( typeof target === \"boolean\" ) {\r\n\t\tdeep = target;\r\n\t\ttarget = arguments[1] || {};\r\n\t\t// skip the boolean and the target\r\n\t\ti = 2;\r\n\t}\r\n\r\n\t// Handle case when target is a string or something (possible in deep copy)\r\n\tif ( typeof target !== \"object\" && !jQuery.isFunction(target) ) {\r\n\t\ttarget = {};\r\n\t}\r\n\r\n\t// extend jQuery itself if only one argument is passed\r\n\tif ( length === i ) {\r\n\t\ttarget = this;\r\n\t\t--i;\r\n\t}\r\n\r\n\tfor ( ; i < length; i++ ) {\r\n\t\t// Only deal with non-null/undefined values\r\n\t\tif ( (options = arguments[ i ]) != null ) {\r\n\t\t\t// Extend the base object\r\n\t\t\tfor ( name in options ) {\r\n\t\t\t\tsrc = target[ name ];\r\n\t\t\t\tcopy = options[ name ];\r\n\r\n\t\t\t\t// Prevent never-ending loop\r\n\t\t\t\tif ( target === copy ) {\r\n\t\t\t\t\tcontinue;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Recurse if we're merging plain objects or arrays\r\n\t\t\t\tif ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {\r\n\t\t\t\t\tif ( copyIsArray ) {\r\n\t\t\t\t\t\tcopyIsArray = false;\r\n\t\t\t\t\t\tclone = src && jQuery.isArray(src) ? src : [];\r\n\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tclone = src && jQuery.isPlainObject(src) ? src : {};\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t// Never move original objects, clone them\r\n\t\t\t\t\ttarget[ name ] = jQuery.extend( deep, clone, copy );\r\n\r\n\t\t\t\t// Don't bring in undefined values\r\n\t\t\t\t} else if ( copy !== undefined ) {\r\n\t\t\t\t\ttarget[ name ] = copy;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t// Return the modified object\r\n\treturn target;\r\n};\r\n\r\njQuery.extend({\r\n\t// Unique for each copy of jQuery on the page\r\n\t// Non-digits removed to match rinlinejQuery\r\n\texpando: \"jQuery\" + ( core_version + Math.random() ).replace( /\\D/g, \"\" ),\r\n\r\n\tnoConflict: function( deep ) {\r\n\t\tif ( window.$ === jQuery ) {\r\n\t\t\twindow.$ = _$;\r\n\t\t}\r\n\r\n\t\tif ( deep && window.jQuery === jQuery ) {\r\n\t\t\twindow.jQuery = _jQuery;\r\n\t\t}\r\n\r\n\t\treturn jQuery;\r\n\t},\r\n\r\n\t// Is the DOM ready to be used? Set to true once it occurs.\r\n\tisReady: false,\r\n\r\n\t// A counter to track how many items to wait for before\r\n\t// the ready event fires. See #6781\r\n\treadyWait: 1,\r\n\r\n\t// Hold (or release) the ready event\r\n\tholdReady: function( hold ) {\r\n\t\tif ( hold ) {\r\n\t\t\tjQuery.readyWait++;\r\n\t\t} else {\r\n\t\t\tjQuery.ready( true );\r\n\t\t}\r\n\t},\r\n\r\n\t// Handle when the DOM is ready\r\n\tready: function( wait ) {\r\n\r\n\t\t// Abort if there are pending holds or we're already ready\r\n\t\tif ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t// Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).\r\n\t\tif ( !document.body ) {\r\n\t\t\treturn setTimeout( jQuery.ready );\r\n\t\t}\r\n\r\n\t\t// Remember that the DOM is ready\r\n\t\tjQuery.isReady = true;\r\n\r\n\t\t// If a normal DOM Ready event fired, decrement, and wait if need be\r\n\t\tif ( wait !== true && --jQuery.readyWait > 0 ) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t// If there are functions bound, to execute\r\n\t\treadyList.resolveWith( document, [ jQuery ] );\r\n\r\n\t\t// Trigger any bound ready events\r\n\t\tif ( jQuery.fn.trigger ) {\r\n\t\t\tjQuery( document ).trigger(\"ready\").off(\"ready\");\r\n\t\t}\r\n\t},\r\n\r\n\t// See test/unit/core.js for details concerning isFunction.\r\n\t// Since version 1.3, DOM methods and functions like alert\r\n\t// aren't supported. They return false on IE (#2968).\r\n\tisFunction: function( obj ) {\r\n\t\treturn jQuery.type(obj) === \"function\";\r\n\t},\r\n\r\n\tisArray: Array.isArray || function( obj ) {\r\n\t\treturn jQuery.type(obj) === \"array\";\r\n\t},\r\n\r\n\tisWindow: function( obj ) {\r\n\t\t/* jshint eqeqeq: false */\r\n\t\treturn obj != null && obj == obj.window;\r\n\t},\r\n\r\n\tisNumeric: function( obj ) {\r\n\t\treturn !isNaN( parseFloat(obj) ) && isFinite( obj );\r\n\t},\r\n\r\n\ttype: function( obj ) {\r\n\t\tif ( obj == null ) {\r\n\t\t\treturn String( obj );\r\n\t\t}\r\n\t\treturn typeof obj === \"object\" || typeof obj === \"function\" ?\r\n\t\t\tclass2type[ core_toString.call(obj) ] || \"object\" :\r\n\t\t\ttypeof obj;\r\n\t},\r\n\r\n\tisPlainObject: function( obj ) {\r\n\t\tvar key;\r\n\r\n\t\t// Must be an Object.\r\n\t\t// Because of IE, we also have to check the presence of the constructor property.\r\n\t\t// Make sure that DOM nodes and window objects don't pass through, as well\r\n\t\tif ( !obj || jQuery.type(obj) !== \"object\" || obj.nodeType || jQuery.isWindow( obj ) ) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\ttry {\r\n\t\t\t// Not own constructor property must be Object\r\n\t\t\tif ( obj.constructor &&\r\n\t\t\t\t!core_hasOwn.call(obj, \"constructor\") &&\r\n\t\t\t\t!core_hasOwn.call(obj.constructor.prototype, \"isPrototypeOf\") ) {\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t} catch ( e ) {\r\n\t\t\t// IE8,9 Will throw exceptions on certain host objects #9897\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\t// Support: IE<9\r\n\t\t// Handle iteration over inherited properties before own properties.\r\n\t\tif ( jQuery.support.ownLast ) {\r\n\t\t\tfor ( key in obj ) {\r\n\t\t\t\treturn core_hasOwn.call( obj, key );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Own properties are enumerated firstly, so to speed up,\r\n\t\t// if last one is own, then all properties are own.\r\n\t\tfor ( key in obj ) {}\r\n\r\n\t\treturn key === undefined || core_hasOwn.call( obj, key );\r\n\t},\r\n\r\n\tisEmptyObject: function( obj ) {\r\n\t\tvar name;\r\n\t\tfor ( name in obj ) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t\treturn true;\r\n\t},\r\n\r\n\terror: function( msg ) {\r\n\t\tthrow new Error( msg );\r\n\t},\r\n\r\n\t// data: string of html\r\n\t// context (optional): If specified, the fragment will be created in this context, defaults to document\r\n\t// keepScripts (optional): If true, will include scripts passed in the html string\r\n\tparseHTML: function( data, context, keepScripts ) {\r\n\t\tif ( !data || typeof data !== \"string\" ) {\r\n\t\t\treturn null;\r\n\t\t}\r\n\t\tif ( typeof context === \"boolean\" ) {\r\n\t\t\tkeepScripts = context;\r\n\t\t\tcontext = false;\r\n\t\t}\r\n\t\tcontext = context || document;\r\n\r\n\t\tvar parsed = rsingleTag.exec( data ),\r\n\t\t\tscripts = !keepScripts && [];\r\n\r\n\t\t// Single tag\r\n\t\tif ( parsed ) {\r\n\t\t\treturn [ context.createElement( parsed[1] ) ];\r\n\t\t}\r\n\r\n\t\tparsed = jQuery.buildFragment( [ data ], context, scripts );\r\n\t\tif ( scripts ) {\r\n\t\t\tjQuery( scripts ).remove();\r\n\t\t}\r\n\t\treturn jQuery.merge( [], parsed.childNodes );\r\n\t},\r\n\r\n\tparseJSON: function( data ) {\r\n\t\t// Attempt to parse using the native JSON parser first\r\n\t\tif ( window.JSON && window.JSON.parse ) {\r\n\t\t\treturn window.JSON.parse( data );\r\n\t\t}\r\n\r\n\t\tif ( data === null ) {\r\n\t\t\treturn data;\r\n\t\t}\r\n\r\n\t\tif ( typeof data === \"string\" ) {\r\n\r\n\t\t\t// Make sure leading/trailing whitespace is removed (IE can't handle it)\r\n\t\t\tdata = jQuery.trim( data );\r\n\r\n\t\t\tif ( data ) {\r\n\t\t\t\t// Make sure the incoming data is actual JSON\r\n\t\t\t\t// Logic borrowed from http://json.org/json2.js\r\n\t\t\t\tif ( rvalidchars.test( data.replace( rvalidescape, \"@\" )\r\n\t\t\t\t\t.replace( rvalidtokens, \"]\" )\r\n\t\t\t\t\t.replace( rvalidbraces, \"\")) ) {\r\n\r\n\t\t\t\t\treturn ( new Function( \"return \" + data ) )();\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tjQuery.error( \"Invalid JSON: \" + data );\r\n\t},\r\n\r\n\t// Cross-browser xml parsing\r\n\tparseXML: function( data ) {\r\n\t\tvar xml, tmp;\r\n\t\tif ( !data || typeof data !== \"string\" ) {\r\n\t\t\treturn null;\r\n\t\t}\r\n\t\ttry {\r\n\t\t\tif ( window.DOMParser ) { // Standard\r\n\t\t\t\ttmp = new DOMParser();\r\n\t\t\t\txml = tmp.parseFromString( data , \"text/xml\" );\r\n\t\t\t} else { // IE\r\n\t\t\t\txml = new ActiveXObject( \"Microsoft.XMLDOM\" );\r\n\t\t\t\txml.async = \"false\";\r\n\t\t\t\txml.loadXML( data );\r\n\t\t\t}\r\n\t\t} catch( e ) {\r\n\t\t\txml = undefined;\r\n\t\t}\r\n\t\tif ( !xml || !xml.documentElement || xml.getElementsByTagName( \"parsererror\" ).length ) {\r\n\t\t\tjQuery.error( \"Invalid XML: \" + data );\r\n\t\t}\r\n\t\treturn xml;\r\n\t},\r\n\r\n\tnoop: function() {},\r\n\r\n\t// Evaluates a script in a global context\r\n\t// Workarounds based on findings by Jim Driscoll\r\n\t// http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context\r\n\tglobalEval: function( data ) {\r\n\t\tif ( data && jQuery.trim( data ) ) {\r\n\t\t\t// We use execScript on Internet Explorer\r\n\t\t\t// We use an anonymous function so that context is window\r\n\t\t\t// rather than jQuery in Firefox\r\n\t\t\t( window.execScript || function( data ) {\r\n\t\t\t\twindow[ \"eval\" ].call( window, data );\r\n\t\t\t} )( data );\r\n\t\t}\r\n\t},\r\n\r\n\t// Convert dashed to camelCase; used by the css and data modules\r\n\t// Microsoft forgot to hump their vendor prefix (#9572)\r\n\tcamelCase: function( string ) {\r\n\t\treturn string.replace( rmsPrefix, \"ms-\" ).replace( rdashAlpha, fcamelCase );\r\n\t},\r\n\r\n\tnodeName: function( elem, name ) {\r\n\t\treturn elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();\r\n\t},\r\n\r\n\t// args is for internal usage only\r\n\teach: function( obj, callback, args ) {\r\n\t\tvar value,\r\n\t\t\ti = 0,\r\n\t\t\tlength = obj.length,\r\n\t\t\tisArray = isArraylike( obj );\r\n\r\n\t\tif ( args ) {\r\n\t\t\tif ( isArray ) {\r\n\t\t\t\tfor ( ; i < length; i++ ) {\r\n\t\t\t\t\tvalue = callback.apply( obj[ i ], args );\r\n\r\n\t\t\t\t\tif ( value === false ) {\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\tfor ( i in obj ) {\r\n\t\t\t\t\tvalue = callback.apply( obj[ i ], args );\r\n\r\n\t\t\t\t\tif ( value === false ) {\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t// A special, fast, case for the most common use of each\r\n\t\t} else {\r\n\t\t\tif ( isArray ) {\r\n\t\t\t\tfor ( ; i < length; i++ ) {\r\n\t\t\t\t\tvalue = callback.call( obj[ i ], i, obj[ i ] );\r\n\r\n\t\t\t\t\tif ( value === false ) {\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\tfor ( i in obj ) {\r\n\t\t\t\t\tvalue = callback.call( obj[ i ], i, obj[ i ] );\r\n\r\n\t\t\t\t\tif ( value === false ) {\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn obj;\r\n\t},\r\n\r\n\t// Use native String.trim function wherever possible\r\n\ttrim: core_trim && !core_trim.call(\"\\uFEFF\\xA0\") ?\r\n\t\tfunction( text ) {\r\n\t\t\treturn text == null ?\r\n\t\t\t\t\"\" :\r\n\t\t\t\tcore_trim.call( text );\r\n\t\t} :\r\n\r\n\t\t// Otherwise use our own trimming functionality\r\n\t\tfunction( text ) {\r\n\t\t\treturn text == null ?\r\n\t\t\t\t\"\" :\r\n\t\t\t\t( text + \"\" ).replace( rtrim, \"\" );\r\n\t\t},\r\n\r\n\t// results is for internal usage only\r\n\tmakeArray: function( arr, results ) {\r\n\t\tvar ret = results || [];\r\n\r\n\t\tif ( arr != null ) {\r\n\t\t\tif ( isArraylike( Object(arr) ) ) {\r\n\t\t\t\tjQuery.merge( ret,\r\n\t\t\t\t\ttypeof arr === \"string\" ?\r\n\t\t\t\t\t[ arr ] : arr\r\n\t\t\t\t);\r\n\t\t\t} else {\r\n\t\t\t\tcore_push.call( ret, arr );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn ret;\r\n\t},\r\n\r\n\tinArray: function( elem, arr, i ) {\r\n\t\tvar len;\r\n\r\n\t\tif ( arr ) {\r\n\t\t\tif ( core_indexOf ) {\r\n\t\t\t\treturn core_indexOf.call( arr, elem, i );\r\n\t\t\t}\r\n\r\n\t\t\tlen = arr.length;\r\n\t\t\ti = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;\r\n\r\n\t\t\tfor ( ; i < len; i++ ) {\r\n\t\t\t\t// Skip accessing in sparse arrays\r\n\t\t\t\tif ( i in arr && arr[ i ] === elem ) {\r\n\t\t\t\t\treturn i;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn -1;\r\n\t},\r\n\r\n\tmerge: function( first, second ) {\r\n\t\tvar l = second.length,\r\n\t\t\ti = first.length,\r\n\t\t\tj = 0;\r\n\r\n\t\tif ( typeof l === \"number\" ) {\r\n\t\t\tfor ( ; j < l; j++ ) {\r\n\t\t\t\tfirst[ i++ ] = second[ j ];\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\twhile ( second[j] !== undefined ) {\r\n\t\t\t\tfirst[ i++ ] = second[ j++ ];\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tfirst.length = i;\r\n\r\n\t\treturn first;\r\n\t},\r\n\r\n\tgrep: function( elems, callback, inv ) {\r\n\t\tvar retVal,\r\n\t\t\tret = [],\r\n\t\t\ti = 0,\r\n\t\t\tlength = elems.length;\r\n\t\tinv = !!inv;\r\n\r\n\t\t// Go through the array, only saving the items\r\n\t\t// that pass the validator function\r\n\t\tfor ( ; i < length; i++ ) {\r\n\t\t\tretVal = !!callback( elems[ i ], i );\r\n\t\t\tif ( inv !== retVal ) {\r\n\t\t\t\tret.push( elems[ i ] );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn ret;\r\n\t},\r\n\r\n\t// arg is for internal usage only\r\n\tmap: function( elems, callback, arg ) {\r\n\t\tvar value,\r\n\t\t\ti = 0,\r\n\t\t\tlength = elems.length,\r\n\t\t\tisArray = isArraylike( elems ),\r\n\t\t\tret = [];\r\n\r\n\t\t// Go through the array, translating each of the items to their\r\n\t\tif ( isArray ) {\r\n\t\t\tfor ( ; i < length; i++ ) {\r\n\t\t\t\tvalue = callback( elems[ i ], i, arg );\r\n\r\n\t\t\t\tif ( value != null ) {\r\n\t\t\t\t\tret[ ret.length ] = value;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t// Go through every key on the object,\r\n\t\t} else {\r\n\t\t\tfor ( i in elems ) {\r\n\t\t\t\tvalue = callback( elems[ i ], i, arg );\r\n\r\n\t\t\t\tif ( value != null ) {\r\n\t\t\t\t\tret[ ret.length ] = value;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Flatten any nested arrays\r\n\t\treturn core_concat.apply( [], ret );\r\n\t},\r\n\r\n\t// A global GUID counter for objects\r\n\tguid: 1,\r\n\r\n\t// Bind a function to a context, optionally partially applying any\r\n\t// arguments.\r\n\tproxy: function( fn, context ) {\r\n\t\tvar args, proxy, tmp;\r\n\r\n\t\tif ( typeof context === \"string\" ) {\r\n\t\t\ttmp = fn[ context ];\r\n\t\t\tcontext = fn;\r\n\t\t\tfn = tmp;\r\n\t\t}\r\n\r\n\t\t// Quick check to determine if target is callable, in the spec\r\n\t\t// this throws a TypeError, but we will just return undefined.\r\n\t\tif ( !jQuery.isFunction( fn ) ) {\r\n\t\t\treturn undefined;\r\n\t\t}\r\n\r\n\t\t// Simulated bind\r\n\t\targs = core_slice.call( arguments, 2 );\r\n\t\tproxy = function() {\r\n\t\t\treturn fn.apply( context || this, args.concat( core_slice.call( arguments ) ) );\r\n\t\t};\r\n\r\n\t\t// Set the guid of unique handler to the same of original handler, so it can be removed\r\n\t\tproxy.guid = fn.guid = fn.guid || jQuery.guid++;\r\n\r\n\t\treturn proxy;\r\n\t},\r\n\r\n\t// Multifunctional method to get and set values of a collection\r\n\t// The value/s can optionally be executed if it's a function\r\n\taccess: function( elems, fn, key, value, chainable, emptyGet, raw ) {\r\n\t\tvar i = 0,\r\n\t\t\tlength = elems.length,\r\n\t\t\tbulk = key == null;\r\n\r\n\t\t// Sets many values\r\n\t\tif ( jQuery.type( key ) === \"object\" ) {\r\n\t\t\tchainable = true;\r\n\t\t\tfor ( i in key ) {\r\n\t\t\t\tjQuery.access( elems, fn, i, key[i], true, emptyGet, raw );\r\n\t\t\t}\r\n\r\n\t\t// Sets one value\r\n\t\t} else if ( value !== undefined ) {\r\n\t\t\tchainable = true;\r\n\r\n\t\t\tif ( !jQuery.isFunction( value ) ) {\r\n\t\t\t\traw = true;\r\n\t\t\t}\r\n\r\n\t\t\tif ( bulk ) {\r\n\t\t\t\t// Bulk operations run against the entire set\r\n\t\t\t\tif ( raw ) {\r\n\t\t\t\t\tfn.call( elems, value );\r\n\t\t\t\t\tfn = null;\r\n\r\n\t\t\t\t// ...except when executing function values\r\n\t\t\t\t} else {\r\n\t\t\t\t\tbulk = fn;\r\n\t\t\t\t\tfn = function( elem, key, value ) {\r\n\t\t\t\t\t\treturn bulk.call( jQuery( elem ), value );\r\n\t\t\t\t\t};\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif ( fn ) {\r\n\t\t\t\tfor ( ; i < length; i++ ) {\r\n\t\t\t\t\tfn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn chainable ?\r\n\t\t\telems :\r\n\r\n\t\t\t// Gets\r\n\t\t\tbulk ?\r\n\t\t\t\tfn.call( elems ) :\r\n\t\t\t\tlength ? fn( elems[0], key ) : emptyGet;\r\n\t},\r\n\r\n\tnow: function() {\r\n\t\treturn ( new Date() ).getTime();\r\n\t},\r\n\r\n\t// A method for quickly swapping in/out CSS properties to get correct calculations.\r\n\t// Note: this method belongs to the css module but it's needed here for the support module.\r\n\t// If support gets modularized, this method should be moved back to the css module.\r\n\tswap: function( elem, options, callback, args ) {\r\n\t\tvar ret, name,\r\n\t\t\told = {};\r\n\r\n\t\t// Remember the old values, and insert the new ones\r\n\t\tfor ( name in options ) {\r\n\t\t\told[ name ] = elem.style[ name ];\r\n\t\t\telem.style[ name ] = options[ name ];\r\n\t\t}\r\n\r\n\t\tret = callback.apply( elem, args || [] );\r\n\r\n\t\t// Revert the old values\r\n\t\tfor ( name in options ) {\r\n\t\t\telem.style[ name ] = old[ name ];\r\n\t\t}\r\n\r\n\t\treturn ret;\r\n\t}\r\n});\r\n\r\njQuery.ready.promise = function( obj ) {\r\n\tif ( !readyList ) {\r\n\r\n\t\treadyList = jQuery.Deferred();\r\n\r\n\t\t// Catch cases where $(document).ready() is called after the browser event has already occurred.\r\n\t\t// we once tried to use readyState \"interactive\" here, but it caused issues like the one\r\n\t\t// discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15\r\n\t\tif ( document.readyState === \"complete\" ) {\r\n\t\t\t// Handle it asynchronously to allow scripts the opportunity to delay ready\r\n\t\t\tsetTimeout( jQuery.ready );\r\n\r\n\t\t// Standards-based browsers support DOMContentLoaded\r\n\t\t} else if ( document.addEventListener ) {\r\n\t\t\t// Use the handy event callback\r\n\t\t\tdocument.addEventListener( \"DOMContentLoaded\", completed, false );\r\n\r\n\t\t\t// A fallback to window.onload, that will always work\r\n\t\t\twindow.addEventListener( \"load\", completed, false );\r\n\r\n\t\t// If IE event model is used\r\n\t\t} else {\r\n\t\t\t// Ensure firing before onload, maybe late but safe also for iframes\r\n\t\t\tdocument.attachEvent( \"onreadystatechange\", completed );\r\n\r\n\t\t\t// A fallback to window.onload, that will always work\r\n\t\t\twindow.attachEvent( \"onload\", completed );\r\n\r\n\t\t\t// If IE and not a frame\r\n\t\t\t// continually check to see if the document is ready\r\n\t\t\tvar top = false;\r\n\r\n\t\t\ttry {\r\n\t\t\t\ttop = window.frameElement == null && document.documentElement;\r\n\t\t\t} catch(e) {}\r\n\r\n\t\t\tif ( top && top.doScroll ) {\r\n\t\t\t\t(function doScrollCheck() {\r\n\t\t\t\t\tif ( !jQuery.isReady ) {\r\n\r\n\t\t\t\t\t\ttry {\r\n\t\t\t\t\t\t\t// Use the trick by Diego Perini\r\n\t\t\t\t\t\t\t// http://javascript.nwbox.com/IEContentLoaded/\r\n\t\t\t\t\t\t\ttop.doScroll(\"left\");\r\n\t\t\t\t\t\t} catch(e) {\r\n\t\t\t\t\t\t\treturn setTimeout( doScrollCheck, 50 );\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t// detach all dom ready events\r\n\t\t\t\t\t\tdetach();\r\n\r\n\t\t\t\t\t\t// and execute any waiting functions\r\n\t\t\t\t\t\tjQuery.ready();\r\n\t\t\t\t\t}\r\n\t\t\t\t})();\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\treturn readyList.promise( obj );\r\n};\r\n\r\n// Populate the class2type map\r\njQuery.each(\"Boolean Number String Function Array Date RegExp Object Error\".split(\" \"), function(i, name) {\r\n\tclass2type[ \"[object \" + name + \"]\" ] = name.toLowerCase();\r\n});\r\n\r\nfunction isArraylike( obj ) {\r\n\tvar length = obj.length,\r\n\t\ttype = jQuery.type( obj );\r\n\r\n\tif ( jQuery.isWindow( obj ) ) {\r\n\t\treturn false;\r\n\t}\r\n\r\n\tif ( obj.nodeType === 1 && length ) {\r\n\t\treturn true;\r\n\t}\r\n\r\n\treturn type === \"array\" || type !== \"function\" &&\r\n\t\t( length === 0 ||\r\n\t\ttypeof length === \"number\" && length > 0 && ( length - 1 ) in obj );\r\n}\r\n\r\n// All jQuery objects should point back to these\r\nrootjQuery = jQuery(document);\r\n/*!\r\n * Sizzle CSS Selector Engine v1.10.2\r\n * http://sizzlejs.com/\r\n *\r\n * Copyright 2013 jQuery Foundation, Inc. and other contributors\r\n * Released under the MIT license\r\n * http://jquery.org/license\r\n *\r\n * Date: 2013-07-03\r\n */\r\n(function( window, undefined ) {\r\n\r\nvar i,\r\n\tsupport,\r\n\tcachedruns,\r\n\tExpr,\r\n\tgetText,\r\n\tisXML,\r\n\tcompile,\r\n\toutermostContext,\r\n\tsortInput,\r\n\r\n\t// Local document vars\r\n\tsetDocument,\r\n\tdocument,\r\n\tdocElem,\r\n\tdocumentIsHTML,\r\n\trbuggyQSA,\r\n\trbuggyMatches,\r\n\tmatches,\r\n\tcontains,\r\n\r\n\t// Instance-specific data\r\n\texpando = \"sizzle\" + -(new Date()),\r\n\tpreferredDoc = window.document,\r\n\tdirruns = 0,\r\n\tdone = 0,\r\n\tclassCache = createCache(),\r\n\ttokenCache = createCache(),\r\n\tcompilerCache = createCache(),\r\n\thasDuplicate = false,\r\n\tsortOrder = function( a, b ) {\r\n\t\tif ( a === b ) {\r\n\t\t\thasDuplicate = true;\r\n\t\t\treturn 0;\r\n\t\t}\r\n\t\treturn 0;\r\n\t},\r\n\r\n\t// General-purpose constants\r\n\tstrundefined = typeof undefined,\r\n\tMAX_NEGATIVE = 1 << 31,\r\n\r\n\t// Instance methods\r\n\thasOwn = ({}).hasOwnProperty,\r\n\tarr = [],\r\n\tpop = arr.pop,\r\n\tpush_native = arr.push,\r\n\tpush = arr.push,\r\n\tslice = arr.slice,\r\n\t// Use a stripped-down indexOf if we can't use a native one\r\n\tindexOf = arr.indexOf || function( elem ) {\r\n\t\tvar i = 0,\r\n\t\t\tlen = this.length;\r\n\t\tfor ( ; i < len; i++ ) {\r\n\t\t\tif ( this[i] === elem ) {\r\n\t\t\t\treturn i;\r\n\t\t\t}\r\n\t\t}\r\n\t\treturn -1;\r\n\t},\r\n\r\n\tbooleans = \"checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped\",\r\n\r\n\t// Regular expressions\r\n\r\n\t// Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace\r\n\twhitespace = \"[\\\\x20\\\\t\\\\r\\\\n\\\\f]\",\r\n\t// http://www.w3.org/TR/css3-syntax/#characters\r\n\tcharacterEncoding = \"(?:\\\\\\\\.|[\\\\w-]|[^\\\\x00-\\\\xa0])+\",\r\n\r\n\t// Loosely modeled on CSS identifier characters\r\n\t// An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors\r\n\t// Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier\r\n\tidentifier = characterEncoding.replace( \"w\", \"w#\" ),\r\n\r\n\t// Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors\r\n\tattributes = \"\\\\[\" + whitespace + \"*(\" + characterEncoding + \")\" + whitespace +\r\n\t\t\"*(?:([*^$|!~]?=)\" + whitespace + \"*(?:(['\\\"])((?:\\\\\\\\.|[^\\\\\\\\])*?)\\\\3|(\" + identifier + \")|)|)\" + whitespace + \"*\\\\]\",\r\n\r\n\t// Prefer arguments quoted,\r\n\t//   then not containing pseudos/brackets,\r\n\t//   then attribute selectors/non-parenthetical expressions,\r\n\t//   then anything else\r\n\t// These preferences are here to reduce the number of selectors\r\n\t//   needing tokenize in the PSEUDO preFilter\r\n\tpseudos = \":(\" + characterEncoding + \")(?:\\\\(((['\\\"])((?:\\\\\\\\.|[^\\\\\\\\])*?)\\\\3|((?:\\\\\\\\.|[^\\\\\\\\()[\\\\]]|\" + attributes.replace( 3, 8 ) + \")*)|.*)\\\\)|)\",\r\n\r\n\t// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter\r\n\trtrim = new RegExp( \"^\" + whitespace + \"+|((?:^|[^\\\\\\\\])(?:\\\\\\\\.)*)\" + whitespace + \"+$\", \"g\" ),\r\n\r\n\trcomma = new RegExp( \"^\" + whitespace + \"*,\" + whitespace + \"*\" ),\r\n\trcombinators = new RegExp( \"^\" + whitespace + \"*([>+~]|\" + whitespace + \")\" + whitespace + \"*\" ),\r\n\r\n\trsibling = new RegExp( whitespace + \"*[+~]\" ),\r\n\trattributeQuotes = new RegExp( \"=\" + whitespace + \"*([^\\\\]'\\\"]*)\" + whitespace + \"*\\\\]\", \"g\" ),\r\n\r\n\trpseudo = new RegExp( pseudos ),\r\n\tridentifier = new RegExp( \"^\" + identifier + \"$\" ),\r\n\r\n\tmatchExpr = {\r\n\t\t\"ID\": new RegExp( \"^#(\" + characterEncoding + \")\" ),\r\n\t\t\"CLASS\": new RegExp( \"^\\\\.(\" + characterEncoding + \")\" ),\r\n\t\t\"TAG\": new RegExp( \"^(\" + characterEncoding.replace( \"w\", \"w*\" ) + \")\" ),\r\n\t\t\"ATTR\": new RegExp( \"^\" + attributes ),\r\n\t\t\"PSEUDO\": new RegExp( \"^\" + pseudos ),\r\n\t\t\"CHILD\": new RegExp( \"^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\\\(\" + whitespace +\r\n\t\t\t\"*(even|odd|(([+-]|)(\\\\d*)n|)\" + whitespace + \"*(?:([+-]|)\" + whitespace +\r\n\t\t\t\"*(\\\\d+)|))\" + whitespace + \"*\\\\)|)\", \"i\" ),\r\n\t\t\"bool\": new RegExp( \"^(?:\" + booleans + \")$\", \"i\" ),\r\n\t\t// For use in libraries implementing .is()\r\n\t\t// We use this for POS matching in `select`\r\n\t\t\"needsContext\": new RegExp( \"^\" + whitespace + \"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\\\(\" +\r\n\t\t\twhitespace + \"*((?:-\\\\d)?\\\\d*)\" + whitespace + \"*\\\\)|)(?=[^-]|$)\", \"i\" )\r\n\t},\r\n\r\n\trnative = /^[^{]+\\{\\s*\\[native \\w/,\r\n\r\n\t// Easily-parseable/retrievable ID or TAG or CLASS selectors\r\n\trquickExpr = /^(?:#([\\w-]+)|(\\w+)|\\.([\\w-]+))$/,\r\n\r\n\trinputs = /^(?:input|select|textarea|button)$/i,\r\n\trheader = /^h\\d$/i,\r\n\r\n\trescape = /'|\\\\/g,\r\n\r\n\t// CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters\r\n\trunescape = new RegExp( \"\\\\\\\\([\\\\da-f]{1,6}\" + whitespace + \"?|(\" + whitespace + \")|.)\", \"ig\" ),\r\n\tfunescape = function( _, escaped, escapedWhitespace ) {\r\n\t\tvar high = \"0x\" + escaped - 0x10000;\r\n\t\t// NaN means non-codepoint\r\n\t\t// Support: Firefox\r\n\t\t// Workaround erroneous numeric interpretation of +\"0x\"\r\n\t\treturn high !== high || escapedWhitespace ?\r\n\t\t\tescaped :\r\n\t\t\t// BMP codepoint\r\n\t\t\thigh < 0 ?\r\n\t\t\t\tString.fromCharCode( high + 0x10000 ) :\r\n\t\t\t\t// Supplemental Plane codepoint (surrogate pair)\r\n\t\t\t\tString.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );\r\n\t};\r\n\r\n// Optimize for push.apply( _, NodeList )\r\ntry {\r\n\tpush.apply(\r\n\t\t(arr = slice.call( preferredDoc.childNodes )),\r\n\t\tpreferredDoc.childNodes\r\n\t);\r\n\t// Support: Android<4.0\r\n\t// Detect silently failing push.apply\r\n\tarr[ preferredDoc.childNodes.length ].nodeType;\r\n} catch ( e ) {\r\n\tpush = { apply: arr.length ?\r\n\r\n\t\t// Leverage slice if possible\r\n\t\tfunction( target, els ) {\r\n\t\t\tpush_native.apply( target, slice.call(els) );\r\n\t\t} :\r\n\r\n\t\t// Support: IE<9\r\n\t\t// Otherwise append directly\r\n\t\tfunction( target, els ) {\r\n\t\t\tvar j = target.length,\r\n\t\t\t\ti = 0;\r\n\t\t\t// Can't trust NodeList.length\r\n\t\t\twhile ( (target[j++] = els[i++]) ) {}\r\n\t\t\ttarget.length = j - 1;\r\n\t\t}\r\n\t};\r\n}\r\n\r\nfunction Sizzle( selector, context, results, seed ) {\r\n\tvar match, elem, m, nodeType,\r\n\t\t// QSA vars\r\n\t\ti, groups, old, nid, newContext, newSelector;\r\n\r\n\tif ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {\r\n\t\tsetDocument( context );\r\n\t}\r\n\r\n\tcontext = context || document;\r\n\tresults = results || [];\r\n\r\n\tif ( !selector || typeof selector !== \"string\" ) {\r\n\t\treturn results;\r\n\t}\r\n\r\n\tif ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) {\r\n\t\treturn [];\r\n\t}\r\n\r\n\tif ( documentIsHTML && !seed ) {\r\n\r\n\t\t// Shortcuts\r\n\t\tif ( (match = rquickExpr.exec( selector )) ) {\r\n\t\t\t// Speed-up: Sizzle(\"#ID\")\r\n\t\t\tif ( (m = match[1]) ) {\r\n\t\t\t\tif ( nodeType === 9 ) {\r\n\t\t\t\t\telem = context.getElementById( m );\r\n\t\t\t\t\t// Check parentNode to catch when Blackberry 4.6 returns\r\n\t\t\t\t\t// nodes that are no longer in the document #6963\r\n\t\t\t\t\tif ( elem && elem.parentNode ) {\r\n\t\t\t\t\t\t// Handle the case where IE, Opera, and Webkit return items\r\n\t\t\t\t\t\t// by name instead of ID\r\n\t\t\t\t\t\tif ( elem.id === m ) {\r\n\t\t\t\t\t\t\tresults.push( elem );\r\n\t\t\t\t\t\t\treturn results;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\treturn results;\r\n\t\t\t\t\t}\r\n\t\t\t\t} else {\r\n\t\t\t\t\t// Context is not a document\r\n\t\t\t\t\tif ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&\r\n\t\t\t\t\t\tcontains( context, elem ) && elem.id === m ) {\r\n\t\t\t\t\t\tresults.push( elem );\r\n\t\t\t\t\t\treturn results;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t// Speed-up: Sizzle(\"TAG\")\r\n\t\t\t} else if ( match[2] ) {\r\n\t\t\t\tpush.apply( results, context.getElementsByTagName( selector ) );\r\n\t\t\t\treturn results;\r\n\r\n\t\t\t// Speed-up: Sizzle(\".CLASS\")\r\n\t\t\t} else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) {\r\n\t\t\t\tpush.apply( results, context.getElementsByClassName( m ) );\r\n\t\t\t\treturn results;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// QSA path\r\n\t\tif ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {\r\n\t\t\tnid = old = expando;\r\n\t\t\tnewContext = context;\r\n\t\t\tnewSelector = nodeType === 9 && selector;\r\n\r\n\t\t\t// qSA works strangely on Element-rooted queries\r\n\t\t\t// We can work around this by specifying an extra ID on the root\r\n\t\t\t// and working up from there (Thanks to Andrew Dupont for the technique)\r\n\t\t\t// IE 8 doesn't work on object elements\r\n\t\t\tif ( nodeType === 1 && context.nodeName.toLowerCase() !== \"object\" ) {\r\n\t\t\t\tgroups = tokenize( selector );\r\n\r\n\t\t\t\tif ( (old = context.getAttribute(\"id\")) ) {\r\n\t\t\t\t\tnid = old.replace( rescape, \"\\\\$&\" );\r\n\t\t\t\t} else {\r\n\t\t\t\t\tcontext.setAttribute( \"id\", nid );\r\n\t\t\t\t}\r\n\t\t\t\tnid = \"[id='\" + nid + \"'] \";\r\n\r\n\t\t\t\ti = groups.length;\r\n\t\t\t\twhile ( i-- ) {\r\n\t\t\t\t\tgroups[i] = nid + toSelector( groups[i] );\r\n\t\t\t\t}\r\n\t\t\t\tnewContext = rsibling.test( selector ) && context.parentNode || context;\r\n\t\t\t\tnewSelector = groups.join(\",\");\r\n\t\t\t}\r\n\r\n\t\t\tif ( newSelector ) {\r\n\t\t\t\ttry {\r\n\t\t\t\t\tpush.apply( results,\r\n\t\t\t\t\t\tnewContext.querySelectorAll( newSelector )\r\n\t\t\t\t\t);\r\n\t\t\t\t\treturn results;\r\n\t\t\t\t} catch(qsaError) {\r\n\t\t\t\t} finally {\r\n\t\t\t\t\tif ( !old ) {\r\n\t\t\t\t\t\tcontext.removeAttribute(\"id\");\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t// All others\r\n\treturn select( selector.replace( rtrim, \"$1\" ), context, results, seed );\r\n}\r\n\r\n/**\r\n * Create key-value caches of limited size\r\n * @returns {Function(string, Object)} Returns the Object data after storing it on itself with\r\n *\tproperty name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)\r\n *\tdeleting the oldest entry\r\n */\r\nfunction createCache() {\r\n\tvar keys = [];\r\n\r\n\tfunction cache( key, value ) {\r\n\t\t// Use (key + \" \") to avoid collision with native prototype properties (see Issue #157)\r\n\t\tif ( keys.push( key += \" \" ) > Expr.cacheLength ) {\r\n\t\t\t// Only keep the most recent entries\r\n\t\t\tdelete cache[ keys.shift() ];\r\n\t\t}\r\n\t\treturn (cache[ key ] = value);\r\n\t}\r\n\treturn cache;\r\n}\r\n\r\n/**\r\n * Mark a function for special use by Sizzle\r\n * @param {Function} fn The function to mark\r\n */\r\nfunction markFunction( fn ) {\r\n\tfn[ expando ] = true;\r\n\treturn fn;\r\n}\r\n\r\n/**\r\n * Support testing using an element\r\n * @param {Function} fn Passed the created div and expects a boolean result\r\n */\r\nfunction assert( fn ) {\r\n\tvar div = document.createElement(\"div\");\r\n\r\n\ttry {\r\n\t\treturn !!fn( div );\r\n\t} catch (e) {\r\n\t\treturn false;\r\n\t} finally {\r\n\t\t// Remove from its parent by default\r\n\t\tif ( div.parentNode ) {\r\n\t\t\tdiv.parentNode.removeChild( div );\r\n\t\t}\r\n\t\t// release memory in IE\r\n\t\tdiv = null;\r\n\t}\r\n}\r\n\r\n/**\r\n * Adds the same handler for all of the specified attrs\r\n * @param {String} attrs Pipe-separated list of attributes\r\n * @param {Function} handler The method that will be applied\r\n */\r\nfunction addHandle( attrs, handler ) {\r\n\tvar arr = attrs.split(\"|\"),\r\n\t\ti = attrs.length;\r\n\r\n\twhile ( i-- ) {\r\n\t\tExpr.attrHandle[ arr[i] ] = handler;\r\n\t}\r\n}\r\n\r\n/**\r\n * Checks document order of two siblings\r\n * @param {Element} a\r\n * @param {Element} b\r\n * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b\r\n */\r\nfunction siblingCheck( a, b ) {\r\n\tvar cur = b && a,\r\n\t\tdiff = cur && a.nodeType === 1 && b.nodeType === 1 &&\r\n\t\t\t( ~b.sourceIndex || MAX_NEGATIVE ) -\r\n\t\t\t( ~a.sourceIndex || MAX_NEGATIVE );\r\n\r\n\t// Use IE sourceIndex if available on both nodes\r\n\tif ( diff ) {\r\n\t\treturn diff;\r\n\t}\r\n\r\n\t// Check if b follows a\r\n\tif ( cur ) {\r\n\t\twhile ( (cur = cur.nextSibling) ) {\r\n\t\t\tif ( cur === b ) {\r\n\t\t\t\treturn -1;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\treturn a ? 1 : -1;\r\n}\r\n\r\n/**\r\n * Returns a function to use in pseudos for input types\r\n * @param {String} type\r\n */\r\nfunction createInputPseudo( type ) {\r\n\treturn function( elem ) {\r\n\t\tvar name = elem.nodeName.toLowerCase();\r\n\t\treturn name === \"input\" && elem.type === type;\r\n\t};\r\n}\r\n\r\n/**\r\n * Returns a function to use in pseudos for buttons\r\n * @param {String} type\r\n */\r\nfunction createButtonPseudo( type ) {\r\n\treturn function( elem ) {\r\n\t\tvar name = elem.nodeName.toLowerCase();\r\n\t\treturn (name === \"input\" || name === \"button\") && elem.type === type;\r\n\t};\r\n}\r\n\r\n/**\r\n * Returns a function to use in pseudos for positionals\r\n * @param {Function} fn\r\n */\r\nfunction createPositionalPseudo( fn ) {\r\n\treturn markFunction(function( argument ) {\r\n\t\targument = +argument;\r\n\t\treturn markFunction(function( seed, matches ) {\r\n\t\t\tvar j,\r\n\t\t\t\tmatchIndexes = fn( [], seed.length, argument ),\r\n\t\t\t\ti = matchIndexes.length;\r\n\r\n\t\t\t// Match elements found at the specified indexes\r\n\t\t\twhile ( i-- ) {\r\n\t\t\t\tif ( seed[ (j = matchIndexes[i]) ] ) {\r\n\t\t\t\t\tseed[j] = !(matches[j] = seed[j]);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t});\r\n\t});\r\n}\r\n\r\n/**\r\n * Detect xml\r\n * @param {Element|Object} elem An element or a document\r\n */\r\nisXML = Sizzle.isXML = function( elem ) {\r\n\t// documentElement is verified for cases where it doesn't yet exist\r\n\t// (such as loading iframes in IE - #4833)\r\n\tvar documentElement = elem && (elem.ownerDocument || elem).documentElement;\r\n\treturn documentElement ? documentElement.nodeName !== \"HTML\" : false;\r\n};\r\n\r\n// Expose support vars for convenience\r\nsupport = Sizzle.support = {};\r\n\r\n/**\r\n * Sets document-related variables once based on the current document\r\n * @param {Element|Object} [doc] An element or document object to use to set the document\r\n * @returns {Object} Returns the current document\r\n */\r\nsetDocument = Sizzle.setDocument = function( node ) {\r\n\tvar doc = node ? node.ownerDocument || node : preferredDoc,\r\n\t\tparent = doc.defaultView;\r\n\r\n\t// If no document and documentElement is available, return\r\n\tif ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {\r\n\t\treturn document;\r\n\t}\r\n\r\n\t// Set our document\r\n\tdocument = doc;\r\n\tdocElem = doc.documentElement;\r\n\r\n\t// Support tests\r\n\tdocumentIsHTML = !isXML( doc );\r\n\r\n\t// Support: IE>8\r\n\t// If iframe document is assigned to \"document\" variable and if iframe has been reloaded,\r\n\t// IE will throw \"permission denied\" error when accessing \"document\" variable, see jQuery #13936\r\n\t// IE6-8 do not support the defaultView property so parent will be undefined\r\n\tif ( parent && parent.attachEvent && parent !== parent.top ) {\r\n\t\tparent.attachEvent( \"onbeforeunload\", function() {\r\n\t\t\tsetDocument();\r\n\t\t});\r\n\t}\r\n\r\n\t/* Attributes\r\n\t---------------------------------------------------------------------- */\r\n\r\n\t// Support: IE<8\r\n\t// Verify that getAttribute really returns attributes and not properties (excepting IE8 booleans)\r\n\tsupport.attributes = assert(function( div ) {\r\n\t\tdiv.className = \"i\";\r\n\t\treturn !div.getAttribute(\"className\");\r\n\t});\r\n\r\n\t/* getElement(s)By*\r\n\t---------------------------------------------------------------------- */\r\n\r\n\t// Check if getElementsByTagName(\"*\") returns only elements\r\n\tsupport.getElementsByTagName = assert(function( div ) {\r\n\t\tdiv.appendChild( doc.createComment(\"\") );\r\n\t\treturn !div.getElementsByTagName(\"*\").length;\r\n\t});\r\n\r\n\t// Check if getElementsByClassName can be trusted\r\n\tsupport.getElementsByClassName = assert(function( div ) {\r\n\t\tdiv.innerHTML = \"<div class='a'></div><div class='a i'></div>\";\r\n\r\n\t\t// Support: Safari<4\r\n\t\t// Catch class over-caching\r\n\t\tdiv.firstChild.className = \"i\";\r\n\t\t// Support: Opera<10\r\n\t\t// Catch gEBCN failure to find non-leading classes\r\n\t\treturn div.getElementsByClassName(\"i\").length === 2;\r\n\t});\r\n\r\n\t// Support: IE<10\r\n\t// Check if getElementById returns elements by name\r\n\t// The broken getElementById methods don't pick up programatically-set names,\r\n\t// so use a roundabout getElementsByName test\r\n\tsupport.getById = assert(function( div ) {\r\n\t\tdocElem.appendChild( div ).id = expando;\r\n\t\treturn !doc.getElementsByName || !doc.getElementsByName( expando ).length;\r\n\t});\r\n\r\n\t// ID find and filter\r\n\tif ( support.getById ) {\r\n\t\tExpr.find[\"ID\"] = function( id, context ) {\r\n\t\t\tif ( typeof context.getElementById !== strundefined && documentIsHTML ) {\r\n\t\t\t\tvar m = context.getElementById( id );\r\n\t\t\t\t// Check parentNode to catch when Blackberry 4.6 returns\r\n\t\t\t\t// nodes that are no longer in the document #6963\r\n\t\t\t\treturn m && m.parentNode ? [m] : [];\r\n\t\t\t}\r\n\t\t};\r\n\t\tExpr.filter[\"ID\"] = function( id ) {\r\n\t\t\tvar attrId = id.replace( runescape, funescape );\r\n\t\t\treturn function( elem ) {\r\n\t\t\t\treturn elem.getAttribute(\"id\") === attrId;\r\n\t\t\t};\r\n\t\t};\r\n\t} else {\r\n\t\t// Support: IE6/7\r\n\t\t// getElementById is not reliable as a find shortcut\r\n\t\tdelete Expr.find[\"ID\"];\r\n\r\n\t\tExpr.filter[\"ID\"] =  function( id ) {\r\n\t\t\tvar attrId = id.replace( runescape, funescape );\r\n\t\t\treturn function( elem ) {\r\n\t\t\t\tvar node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode(\"id\");\r\n\t\t\t\treturn node && node.value === attrId;\r\n\t\t\t};\r\n\t\t};\r\n\t}\r\n\r\n\t// Tag\r\n\tExpr.find[\"TAG\"] = support.getElementsByTagName ?\r\n\t\tfunction( tag, context ) {\r\n\t\t\tif ( typeof context.getElementsByTagName !== strundefined ) {\r\n\t\t\t\treturn context.getElementsByTagName( tag );\r\n\t\t\t}\r\n\t\t} :\r\n\t\tfunction( tag, context ) {\r\n\t\t\tvar elem,\r\n\t\t\t\ttmp = [],\r\n\t\t\t\ti = 0,\r\n\t\t\t\tresults = context.getElementsByTagName( tag );\r\n\r\n\t\t\t// Filter out possible comments\r\n\t\t\tif ( tag === \"*\" ) {\r\n\t\t\t\twhile ( (elem = results[i++]) ) {\r\n\t\t\t\t\tif ( elem.nodeType === 1 ) {\r\n\t\t\t\t\t\ttmp.push( elem );\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\treturn tmp;\r\n\t\t\t}\r\n\t\t\treturn results;\r\n\t\t};\r\n\r\n\t// Class\r\n\tExpr.find[\"CLASS\"] = support.getElementsByClassName && function( className, context ) {\r\n\t\tif ( typeof context.getElementsByClassName !== strundefined && documentIsHTML ) {\r\n\t\t\treturn context.getElementsByClassName( className );\r\n\t\t}\r\n\t};\r\n\r\n\t/* QSA/matchesSelector\r\n\t---------------------------------------------------------------------- */\r\n\r\n\t// QSA and matchesSelector support\r\n\r\n\t// matchesSelector(:active) reports false when true (IE9/Opera 11.5)\r\n\trbuggyMatches = [];\r\n\r\n\t// qSa(:focus) reports false when true (Chrome 21)\r\n\t// We allow this because of a bug in IE8/9 that throws an error\r\n\t// whenever `document.activeElement` is accessed on an iframe\r\n\t// So, we allow :focus to pass through QSA all the time to avoid the IE error\r\n\t// See http://bugs.jquery.com/ticket/13378\r\n\trbuggyQSA = [];\r\n\r\n\tif ( (support.qsa = rnative.test( doc.querySelectorAll )) ) {\r\n\t\t// Build QSA regex\r\n\t\t// Regex strategy adopted from Diego Perini\r\n\t\tassert(function( div ) {\r\n\t\t\t// Select is set to empty string on purpose\r\n\t\t\t// This is to test IE's treatment of not explicitly\r\n\t\t\t// setting a boolean content attribute,\r\n\t\t\t// since its presence should be enough\r\n\t\t\t// http://bugs.jquery.com/ticket/12359\r\n\t\t\tdiv.innerHTML = \"<select><option selected=''></option></select>\";\r\n\r\n\t\t\t// Support: IE8\r\n\t\t\t// Boolean attributes and \"value\" are not treated correctly\r\n\t\t\tif ( !div.querySelectorAll(\"[selected]\").length ) {\r\n\t\t\t\trbuggyQSA.push( \"\\\\[\" + whitespace + \"*(?:value|\" + booleans + \")\" );\r\n\t\t\t}\r\n\r\n\t\t\t// Webkit/Opera - :checked should return selected option elements\r\n\t\t\t// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked\r\n\t\t\t// IE8 throws error here and will not see later tests\r\n\t\t\tif ( !div.querySelectorAll(\":checked\").length ) {\r\n\t\t\t\trbuggyQSA.push(\":checked\");\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t\tassert(function( div ) {\r\n\r\n\t\t\t// Support: Opera 10-12/IE8\r\n\t\t\t// ^= $= *= and empty values\r\n\t\t\t// Should not select anything\r\n\t\t\t// Support: Windows 8 Native Apps\r\n\t\t\t// The type attribute is restricted during .innerHTML assignment\r\n\t\t\tvar input = doc.createElement(\"input\");\r\n\t\t\tinput.setAttribute( \"type\", \"hidden\" );\r\n\t\t\tdiv.appendChild( input ).setAttribute( \"t\", \"\" );\r\n\r\n\t\t\tif ( div.querySelectorAll(\"[t^='']\").length ) {\r\n\t\t\t\trbuggyQSA.push( \"[*^$]=\" + whitespace + \"*(?:''|\\\"\\\")\" );\r\n\t\t\t}\r\n\r\n\t\t\t// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)\r\n\t\t\t// IE8 throws error here and will not see later tests\r\n\t\t\tif ( !div.querySelectorAll(\":enabled\").length ) {\r\n\t\t\t\trbuggyQSA.push( \":enabled\", \":disabled\" );\r\n\t\t\t}\r\n\r\n\t\t\t// Opera 10-11 does not throw on post-comma invalid pseudos\r\n\t\t\tdiv.querySelectorAll(\"*,:x\");\r\n\t\t\trbuggyQSA.push(\",.*:\");\r\n\t\t});\r\n\t}\r\n\r\n\tif ( (support.matchesSelector = rnative.test( (matches = docElem.webkitMatchesSelector ||\r\n\t\tdocElem.mozMatchesSelector ||\r\n\t\tdocElem.oMatchesSelector ||\r\n\t\tdocElem.msMatchesSelector) )) ) {\r\n\r\n\t\tassert(function( div ) {\r\n\t\t\t// Check to see if it's possible to do matchesSelector\r\n\t\t\t// on a disconnected node (IE 9)\r\n\t\t\tsupport.disconnectedMatch = matches.call( div, \"div\" );\r\n\r\n\t\t\t// This should fail with an exception\r\n\t\t\t// Gecko does not error, returns false instead\r\n\t\t\tmatches.call( div, \"[s!='']:x\" );\r\n\t\t\trbuggyMatches.push( \"!=\", pseudos );\r\n\t\t});\r\n\t}\r\n\r\n\trbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join(\"|\") );\r\n\trbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join(\"|\") );\r\n\r\n\t/* Contains\r\n\t---------------------------------------------------------------------- */\r\n\r\n\t// Element contains another\r\n\t// Purposefully does not implement inclusive descendent\r\n\t// As in, an element does not contain itself\r\n\tcontains = rnative.test( docElem.contains ) || docElem.compareDocumentPosition ?\r\n\t\tfunction( a, b ) {\r\n\t\t\tvar adown = a.nodeType === 9 ? a.documentElement : a,\r\n\t\t\t\tbup = b && b.parentNode;\r\n\t\t\treturn a === bup || !!( bup && bup.nodeType === 1 && (\r\n\t\t\t\tadown.contains ?\r\n\t\t\t\t\tadown.contains( bup ) :\r\n\t\t\t\t\ta.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16\r\n\t\t\t));\r\n\t\t} :\r\n\t\tfunction( a, b ) {\r\n\t\t\tif ( b ) {\r\n\t\t\t\twhile ( (b = b.parentNode) ) {\r\n\t\t\t\t\tif ( b === a ) {\r\n\t\t\t\t\t\treturn true;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\treturn false;\r\n\t\t};\r\n\r\n\t/* Sorting\r\n\t---------------------------------------------------------------------- */\r\n\r\n\t// Document order sorting\r\n\tsortOrder = docElem.compareDocumentPosition ?\r\n\tfunction( a, b ) {\r\n\r\n\t\t// Flag for duplicate removal\r\n\t\tif ( a === b ) {\r\n\t\t\thasDuplicate = true;\r\n\t\t\treturn 0;\r\n\t\t}\r\n\r\n\t\tvar compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition( b );\r\n\r\n\t\tif ( compare ) {\r\n\t\t\t// Disconnected nodes\r\n\t\t\tif ( compare & 1 ||\r\n\t\t\t\t(!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) {\r\n\r\n\t\t\t\t// Choose the first element that is related to our preferred document\r\n\t\t\t\tif ( a === doc || contains(preferredDoc, a) ) {\r\n\t\t\t\t\treturn -1;\r\n\t\t\t\t}\r\n\t\t\t\tif ( b === doc || contains(preferredDoc, b) ) {\r\n\t\t\t\t\treturn 1;\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Maintain original order\r\n\t\t\t\treturn sortInput ?\r\n\t\t\t\t\t( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :\r\n\t\t\t\t\t0;\r\n\t\t\t}\r\n\r\n\t\t\treturn compare & 4 ? -1 : 1;\r\n\t\t}\r\n\r\n\t\t// Not directly comparable, sort on existence of method\r\n\t\treturn a.compareDocumentPosition ? -1 : 1;\r\n\t} :\r\n\tfunction( a, b ) {\r\n\t\tvar cur,\r\n\t\t\ti = 0,\r\n\t\t\taup = a.parentNode,\r\n\t\t\tbup = b.parentNode,\r\n\t\t\tap = [ a ],\r\n\t\t\tbp = [ b ];\r\n\r\n\t\t// Exit early if the nodes are identical\r\n\t\tif ( a === b ) {\r\n\t\t\thasDuplicate = true;\r\n\t\t\treturn 0;\r\n\r\n\t\t// Parentless nodes are either documents or disconnected\r\n\t\t} else if ( !aup || !bup ) {\r\n\t\t\treturn a === doc ? -1 :\r\n\t\t\t\tb === doc ? 1 :\r\n\t\t\t\taup ? -1 :\r\n\t\t\t\tbup ? 1 :\r\n\t\t\t\tsortInput ?\r\n\t\t\t\t( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :\r\n\t\t\t\t0;\r\n\r\n\t\t// If the nodes are siblings, we can do a quick check\r\n\t\t} else if ( aup === bup ) {\r\n\t\t\treturn siblingCheck( a, b );\r\n\t\t}\r\n\r\n\t\t// Otherwise we need full lists of their ancestors for comparison\r\n\t\tcur = a;\r\n\t\twhile ( (cur = cur.parentNode) ) {\r\n\t\t\tap.unshift( cur );\r\n\t\t}\r\n\t\tcur = b;\r\n\t\twhile ( (cur = cur.parentNode) ) {\r\n\t\t\tbp.unshift( cur );\r\n\t\t}\r\n\r\n\t\t// Walk down the tree looking for a discrepancy\r\n\t\twhile ( ap[i] === bp[i] ) {\r\n\t\t\ti++;\r\n\t\t}\r\n\r\n\t\treturn i ?\r\n\t\t\t// Do a sibling check if the nodes have a common ancestor\r\n\t\t\tsiblingCheck( ap[i], bp[i] ) :\r\n\r\n\t\t\t// Otherwise nodes in our document sort first\r\n\t\t\tap[i] === preferredDoc ? -1 :\r\n\t\t\tbp[i] === preferredDoc ? 1 :\r\n\t\t\t0;\r\n\t};\r\n\r\n\treturn doc;\r\n};\r\n\r\nSizzle.matches = function( expr, elements ) {\r\n\treturn Sizzle( expr, null, null, elements );\r\n};\r\n\r\nSizzle.matchesSelector = function( elem, expr ) {\r\n\t// Set document vars if needed\r\n\tif ( ( elem.ownerDocument || elem ) !== document ) {\r\n\t\tsetDocument( elem );\r\n\t}\r\n\r\n\t// Make sure that attribute selectors are quoted\r\n\texpr = expr.replace( rattributeQuotes, \"='$1']\" );\r\n\r\n\tif ( support.matchesSelector && documentIsHTML &&\r\n\t\t( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&\r\n\t\t( !rbuggyQSA     || !rbuggyQSA.test( expr ) ) ) {\r\n\r\n\t\ttry {\r\n\t\t\tvar ret = matches.call( elem, expr );\r\n\r\n\t\t\t// IE 9's matchesSelector returns false on disconnected nodes\r\n\t\t\tif ( ret || support.disconnectedMatch ||\r\n\t\t\t\t\t// As well, disconnected nodes are said to be in a document\r\n\t\t\t\t\t// fragment in IE 9\r\n\t\t\t\t\telem.document && elem.document.nodeType !== 11 ) {\r\n\t\t\t\treturn ret;\r\n\t\t\t}\r\n\t\t} catch(e) {}\r\n\t}\r\n\r\n\treturn Sizzle( expr, document, null, [elem] ).length > 0;\r\n};\r\n\r\nSizzle.contains = function( context, elem ) {\r\n\t// Set document vars if needed\r\n\tif ( ( context.ownerDocument || context ) !== document ) {\r\n\t\tsetDocument( context );\r\n\t}\r\n\treturn contains( context, elem );\r\n};\r\n\r\nSizzle.attr = function( elem, name ) {\r\n\t// Set document vars if needed\r\n\tif ( ( elem.ownerDocument || elem ) !== document ) {\r\n\t\tsetDocument( elem );\r\n\t}\r\n\r\n\tvar fn = Expr.attrHandle[ name.toLowerCase() ],\r\n\t\t// Don't get fooled by Object.prototype properties (jQuery #13807)\r\n\t\tval = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?\r\n\t\t\tfn( elem, name, !documentIsHTML ) :\r\n\t\t\tundefined;\r\n\r\n\treturn val === undefined ?\r\n\t\tsupport.attributes || !documentIsHTML ?\r\n\t\t\telem.getAttribute( name ) :\r\n\t\t\t(val = elem.getAttributeNode(name)) && val.specified ?\r\n\t\t\t\tval.value :\r\n\t\t\t\tnull :\r\n\t\tval;\r\n};\r\n\r\nSizzle.error = function( msg ) {\r\n\tthrow new Error( \"Syntax error, unrecognized expression: \" + msg );\r\n};\r\n\r\n/**\r\n * Document sorting and removing duplicates\r\n * @param {ArrayLike} results\r\n */\r\nSizzle.uniqueSort = function( results ) {\r\n\tvar elem,\r\n\t\tduplicates = [],\r\n\t\tj = 0,\r\n\t\ti = 0;\r\n\r\n\t// Unless we *know* we can detect duplicates, assume their presence\r\n\thasDuplicate = !support.detectDuplicates;\r\n\tsortInput = !support.sortStable && results.slice( 0 );\r\n\tresults.sort( sortOrder );\r\n\r\n\tif ( hasDuplicate ) {\r\n\t\twhile ( (elem = results[i++]) ) {\r\n\t\t\tif ( elem === results[ i ] ) {\r\n\t\t\t\tj = duplicates.push( i );\r\n\t\t\t}\r\n\t\t}\r\n\t\twhile ( j-- ) {\r\n\t\t\tresults.splice( duplicates[ j ], 1 );\r\n\t\t}\r\n\t}\r\n\r\n\treturn results;\r\n};\r\n\r\n/**\r\n * Utility function for retrieving the text value of an array of DOM nodes\r\n * @param {Array|Element} elem\r\n */\r\ngetText = Sizzle.getText = function( elem ) {\r\n\tvar node,\r\n\t\tret = \"\",\r\n\t\ti = 0,\r\n\t\tnodeType = elem.nodeType;\r\n\r\n\tif ( !nodeType ) {\r\n\t\t// If no nodeType, this is expected to be an array\r\n\t\tfor ( ; (node = elem[i]); i++ ) {\r\n\t\t\t// Do not traverse comment nodes\r\n\t\t\tret += getText( node );\r\n\t\t}\r\n\t} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {\r\n\t\t// Use textContent for elements\r\n\t\t// innerText usage removed for consistency of new lines (see #11153)\r\n\t\tif ( typeof elem.textContent === \"string\" ) {\r\n\t\t\treturn elem.textContent;\r\n\t\t} else {\r\n\t\t\t// Traverse its children\r\n\t\t\tfor ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {\r\n\t\t\t\tret += getText( elem );\r\n\t\t\t}\r\n\t\t}\r\n\t} else if ( nodeType === 3 || nodeType === 4 ) {\r\n\t\treturn elem.nodeValue;\r\n\t}\r\n\t// Do not include comment or processing instruction nodes\r\n\r\n\treturn ret;\r\n};\r\n\r\nExpr = Sizzle.selectors = {\r\n\r\n\t// Can be adjusted by the user\r\n\tcacheLength: 50,\r\n\r\n\tcreatePseudo: markFunction,\r\n\r\n\tmatch: matchExpr,\r\n\r\n\tattrHandle: {},\r\n\r\n\tfind: {},\r\n\r\n\trelative: {\r\n\t\t\">\": { dir: \"parentNode\", first: true },\r\n\t\t\" \": { dir: \"parentNode\" },\r\n\t\t\"+\": { dir: \"previousSibling\", first: true },\r\n\t\t\"~\": { dir: \"previousSibling\" }\r\n\t},\r\n\r\n\tpreFilter: {\r\n\t\t\"ATTR\": function( match ) {\r\n\t\t\tmatch[1] = match[1].replace( runescape, funescape );\r\n\r\n\t\t\t// Move the given value to match[3] whether quoted or unquoted\r\n\t\t\tmatch[3] = ( match[4] || match[5] || \"\" ).replace( runescape, funescape );\r\n\r\n\t\t\tif ( match[2] === \"~=\" ) {\r\n\t\t\t\tmatch[3] = \" \" + match[3] + \" \";\r\n\t\t\t}\r\n\r\n\t\t\treturn match.slice( 0, 4 );\r\n\t\t},\r\n\r\n\t\t\"CHILD\": function( match ) {\r\n\t\t\t/* matches from matchExpr[\"CHILD\"]\r\n\t\t\t\t1 type (only|nth|...)\r\n\t\t\t\t2 what (child|of-type)\r\n\t\t\t\t3 argument (even|odd|\\d*|\\d*n([+-]\\d+)?|...)\r\n\t\t\t\t4 xn-component of xn+y argument ([+-]?\\d*n|)\r\n\t\t\t\t5 sign of xn-component\r\n\t\t\t\t6 x of xn-component\r\n\t\t\t\t7 sign of y-component\r\n\t\t\t\t8 y of y-component\r\n\t\t\t*/\r\n\t\t\tmatch[1] = match[1].toLowerCase();\r\n\r\n\t\t\tif ( match[1].slice( 0, 3 ) === \"nth\" ) {\r\n\t\t\t\t// nth-* requires argument\r\n\t\t\t\tif ( !match[3] ) {\r\n\t\t\t\t\tSizzle.error( match[0] );\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// numeric x and y parameters for Expr.filter.CHILD\r\n\t\t\t\t// remember that false/true cast respectively to 0/1\r\n\t\t\t\tmatch[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === \"even\" || match[3] === \"odd\" ) );\r\n\t\t\t\tmatch[5] = +( ( match[7] + match[8] ) || match[3] === \"odd\" );\r\n\r\n\t\t\t// other types prohibit arguments\r\n\t\t\t} else if ( match[3] ) {\r\n\t\t\t\tSizzle.error( match[0] );\r\n\t\t\t}\r\n\r\n\t\t\treturn match;\r\n\t\t},\r\n\r\n\t\t\"PSEUDO\": function( match ) {\r\n\t\t\tvar excess,\r\n\t\t\t\tunquoted = !match[5] && match[2];\r\n\r\n\t\t\tif ( matchExpr[\"CHILD\"].test( match[0] ) ) {\r\n\t\t\t\treturn null;\r\n\t\t\t}\r\n\r\n\t\t\t// Accept quoted arguments as-is\r\n\t\t\tif ( match[3] && match[4] !== undefined ) {\r\n\t\t\t\tmatch[2] = match[4];\r\n\r\n\t\t\t// Strip excess characters from unquoted arguments\r\n\t\t\t} else if ( unquoted && rpseudo.test( unquoted ) &&\r\n\t\t\t\t// Get excess from tokenize (recursively)\r\n\t\t\t\t(excess = tokenize( unquoted, true )) &&\r\n\t\t\t\t// advance to the next closing parenthesis\r\n\t\t\t\t(excess = unquoted.indexOf( \")\", unquoted.length - excess ) - unquoted.length) ) {\r\n\r\n\t\t\t\t// excess is a negative index\r\n\t\t\t\tmatch[0] = match[0].slice( 0, excess );\r\n\t\t\t\tmatch[2] = unquoted.slice( 0, excess );\r\n\t\t\t}\r\n\r\n\t\t\t// Return only captures needed by the pseudo filter method (type and argument)\r\n\t\t\treturn match.slice( 0, 3 );\r\n\t\t}\r\n\t},\r\n\r\n\tfilter: {\r\n\r\n\t\t\"TAG\": function( nodeNameSelector ) {\r\n\t\t\tvar nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();\r\n\t\t\treturn nodeNameSelector === \"*\" ?\r\n\t\t\t\tfunction() { return true; } :\r\n\t\t\t\tfunction( elem ) {\r\n\t\t\t\t\treturn elem.nodeName && elem.nodeName.toLowerCase() === nodeName;\r\n\t\t\t\t};\r\n\t\t},\r\n\r\n\t\t\"CLASS\": function( className ) {\r\n\t\t\tvar pattern = classCache[ className + \" \" ];\r\n\r\n\t\t\treturn pattern ||\r\n\t\t\t\t(pattern = new RegExp( \"(^|\" + whitespace + \")\" + className + \"(\" + whitespace + \"|$)\" )) &&\r\n\t\t\t\tclassCache( className, function( elem ) {\r\n\t\t\t\t\treturn pattern.test( typeof elem.className === \"string\" && elem.className || typeof elem.getAttribute !== strundefined && elem.getAttribute(\"class\") || \"\" );\r\n\t\t\t\t});\r\n\t\t},\r\n\r\n\t\t\"ATTR\": function( name, operator, check ) {\r\n\t\t\treturn function( elem ) {\r\n\t\t\t\tvar result = Sizzle.attr( elem, name );\r\n\r\n\t\t\t\tif ( result == null ) {\r\n\t\t\t\t\treturn operator === \"!=\";\r\n\t\t\t\t}\r\n\t\t\t\tif ( !operator ) {\r\n\t\t\t\t\treturn true;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tresult += \"\";\r\n\r\n\t\t\t\treturn operator === \"=\" ? result === check :\r\n\t\t\t\t\toperator === \"!=\" ? result !== check :\r\n\t\t\t\t\toperator === \"^=\" ? check && result.indexOf( check ) === 0 :\r\n\t\t\t\t\toperator === \"*=\" ? check && result.indexOf( check ) > -1 :\r\n\t\t\t\t\toperator === \"$=\" ? check && result.slice( -check.length ) === check :\r\n\t\t\t\t\toperator === \"~=\" ? ( \" \" + result + \" \" ).indexOf( check ) > -1 :\r\n\t\t\t\t\toperator === \"|=\" ? result === check || result.slice( 0, check.length + 1 ) === check + \"-\" :\r\n\t\t\t\t\tfalse;\r\n\t\t\t};\r\n\t\t},\r\n\r\n\t\t\"CHILD\": function( type, what, argument, first, last ) {\r\n\t\t\tvar simple = type.slice( 0, 3 ) !== \"nth\",\r\n\t\t\t\tforward = type.slice( -4 ) !== \"last\",\r\n\t\t\t\tofType = what === \"of-type\";\r\n\r\n\t\t\treturn first === 1 && last === 0 ?\r\n\r\n\t\t\t\t// Shortcut for :nth-*(n)\r\n\t\t\t\tfunction( elem ) {\r\n\t\t\t\t\treturn !!elem.parentNode;\r\n\t\t\t\t} :\r\n\r\n\t\t\t\tfunction( elem, context, xml ) {\r\n\t\t\t\t\tvar cache, outerCache, node, diff, nodeIndex, start,\r\n\t\t\t\t\t\tdir = simple !== forward ? \"nextSibling\" : \"previousSibling\",\r\n\t\t\t\t\t\tparent = elem.parentNode,\r\n\t\t\t\t\t\tname = ofType && elem.nodeName.toLowerCase(),\r\n\t\t\t\t\t\tuseCache = !xml && !ofType;\r\n\r\n\t\t\t\t\tif ( parent ) {\r\n\r\n\t\t\t\t\t\t// :(first|last|only)-(child|of-type)\r\n\t\t\t\t\t\tif ( simple ) {\r\n\t\t\t\t\t\t\twhile ( dir ) {\r\n\t\t\t\t\t\t\t\tnode = elem;\r\n\t\t\t\t\t\t\t\twhile ( (node = node[ dir ]) ) {\r\n\t\t\t\t\t\t\t\t\tif ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) {\r\n\t\t\t\t\t\t\t\t\t\treturn false;\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t// Reverse direction for :only-* (if we haven't yet done so)\r\n\t\t\t\t\t\t\t\tstart = dir = type === \"only\" && !start && \"nextSibling\";\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\treturn true;\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\tstart = [ forward ? parent.firstChild : parent.lastChild ];\r\n\r\n\t\t\t\t\t\t// non-xml :nth-child(...) stores cache data on `parent`\r\n\t\t\t\t\t\tif ( forward && useCache ) {\r\n\t\t\t\t\t\t\t// Seek `elem` from a previously-cached index\r\n\t\t\t\t\t\t\touterCache = parent[ expando ] || (parent[ expando ] = {});\r\n\t\t\t\t\t\t\tcache = outerCache[ type ] || [];\r\n\t\t\t\t\t\t\tnodeIndex = cache[0] === dirruns && cache[1];\r\n\t\t\t\t\t\t\tdiff = cache[0] === dirruns && cache[2];\r\n\t\t\t\t\t\t\tnode = nodeIndex && parent.childNodes[ nodeIndex ];\r\n\r\n\t\t\t\t\t\t\twhile ( (node = ++nodeIndex && node && node[ dir ] ||\r\n\r\n\t\t\t\t\t\t\t\t// Fallback to seeking `elem` from the start\r\n\t\t\t\t\t\t\t\t(diff = nodeIndex = 0) || start.pop()) ) {\r\n\r\n\t\t\t\t\t\t\t\t// When found, cache indexes on `parent` and break\r\n\t\t\t\t\t\t\t\tif ( node.nodeType === 1 && ++diff && node === elem ) {\r\n\t\t\t\t\t\t\t\t\touterCache[ type ] = [ dirruns, nodeIndex, diff ];\r\n\t\t\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t// Use previously-cached element index if available\r\n\t\t\t\t\t\t} else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) {\r\n\t\t\t\t\t\t\tdiff = cache[1];\r\n\r\n\t\t\t\t\t\t// xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...)\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t// Use the same loop as above to seek `elem` from the start\r\n\t\t\t\t\t\t\twhile ( (node = ++nodeIndex && node && node[ dir ] ||\r\n\t\t\t\t\t\t\t\t(diff = nodeIndex = 0) || start.pop()) ) {\r\n\r\n\t\t\t\t\t\t\t\tif ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) {\r\n\t\t\t\t\t\t\t\t\t// Cache the index of each encountered element\r\n\t\t\t\t\t\t\t\t\tif ( useCache ) {\r\n\t\t\t\t\t\t\t\t\t\t(node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ];\r\n\t\t\t\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t\t\t\tif ( node === elem ) {\r\n\t\t\t\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t// Incorporate the offset, then check against cycle size\r\n\t\t\t\t\t\tdiff -= last;\r\n\t\t\t\t\t\treturn diff === first || ( diff % first === 0 && diff / first >= 0 );\r\n\t\t\t\t\t}\r\n\t\t\t\t};\r\n\t\t},\r\n\r\n\t\t\"PSEUDO\": function( pseudo, argument ) {\r\n\t\t\t// pseudo-class names are case-insensitive\r\n\t\t\t// http://www.w3.org/TR/selectors/#pseudo-classes\r\n\t\t\t// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters\r\n\t\t\t// Remember that setFilters inherits from pseudos\r\n\t\t\tvar args,\r\n\t\t\t\tfn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||\r\n\t\t\t\t\tSizzle.error( \"unsupported pseudo: \" + pseudo );\r\n\r\n\t\t\t// The user may use createPseudo to indicate that\r\n\t\t\t// arguments are needed to create the filter function\r\n\t\t\t// just as Sizzle does\r\n\t\t\tif ( fn[ expando ] ) {\r\n\t\t\t\treturn fn( argument );\r\n\t\t\t}\r\n\r\n\t\t\t// But maintain support for old signatures\r\n\t\t\tif ( fn.length > 1 ) {\r\n\t\t\t\targs = [ pseudo, pseudo, \"\", argument ];\r\n\t\t\t\treturn Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?\r\n\t\t\t\t\tmarkFunction(function( seed, matches ) {\r\n\t\t\t\t\t\tvar idx,\r\n\t\t\t\t\t\t\tmatched = fn( seed, argument ),\r\n\t\t\t\t\t\t\ti = matched.length;\r\n\t\t\t\t\t\twhile ( i-- ) {\r\n\t\t\t\t\t\t\tidx = indexOf.call( seed, matched[i] );\r\n\t\t\t\t\t\t\tseed[ idx ] = !( matches[ idx ] = matched[i] );\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}) :\r\n\t\t\t\t\tfunction( elem ) {\r\n\t\t\t\t\t\treturn fn( elem, 0, args );\r\n\t\t\t\t\t};\r\n\t\t\t}\r\n\r\n\t\t\treturn fn;\r\n\t\t}\r\n\t},\r\n\r\n\tpseudos: {\r\n\t\t// Potentially complex pseudos\r\n\t\t\"not\": markFunction(function( selector ) {\r\n\t\t\t// Trim the selector passed to compile\r\n\t\t\t// to avoid treating leading and trailing\r\n\t\t\t// spaces as combinators\r\n\t\t\tvar input = [],\r\n\t\t\t\tresults = [],\r\n\t\t\t\tmatcher = compile( selector.replace( rtrim, \"$1\" ) );\r\n\r\n\t\t\treturn matcher[ expando ] ?\r\n\t\t\t\tmarkFunction(function( seed, matches, context, xml ) {\r\n\t\t\t\t\tvar elem,\r\n\t\t\t\t\t\tunmatched = matcher( seed, null, xml, [] ),\r\n\t\t\t\t\t\ti = seed.length;\r\n\r\n\t\t\t\t\t// Match elements unmatched by `matcher`\r\n\t\t\t\t\twhile ( i-- ) {\r\n\t\t\t\t\t\tif ( (elem = unmatched[i]) ) {\r\n\t\t\t\t\t\t\tseed[i] = !(matches[i] = elem);\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}) :\r\n\t\t\t\tfunction( elem, context, xml ) {\r\n\t\t\t\t\tinput[0] = elem;\r\n\t\t\t\t\tmatcher( input, null, xml, results );\r\n\t\t\t\t\treturn !results.pop();\r\n\t\t\t\t};\r\n\t\t}),\r\n\r\n\t\t\"has\": markFunction(function( selector ) {\r\n\t\t\treturn function( elem ) {\r\n\t\t\t\treturn Sizzle( selector, elem ).length > 0;\r\n\t\t\t};\r\n\t\t}),\r\n\r\n\t\t\"contains\": markFunction(function( text ) {\r\n\t\t\treturn function( elem ) {\r\n\t\t\t\treturn ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;\r\n\t\t\t};\r\n\t\t}),\r\n\r\n\t\t// \"Whether an element is represented by a :lang() selector\r\n\t\t// is based solely on the element's language value\r\n\t\t// being equal to the identifier C,\r\n\t\t// or beginning with the identifier C immediately followed by \"-\".\r\n\t\t// The matching of C against the element's language value is performed case-insensitively.\r\n\t\t// The identifier C does not have to be a valid language name.\"\r\n\t\t// http://www.w3.org/TR/selectors/#lang-pseudo\r\n\t\t\"lang\": markFunction( function( lang ) {\r\n\t\t\t// lang value must be a valid identifier\r\n\t\t\tif ( !ridentifier.test(lang || \"\") ) {\r\n\t\t\t\tSizzle.error( \"unsupported lang: \" + lang );\r\n\t\t\t}\r\n\t\t\tlang = lang.replace( runescape, funescape ).toLowerCase();\r\n\t\t\treturn function( elem ) {\r\n\t\t\t\tvar elemLang;\r\n\t\t\t\tdo {\r\n\t\t\t\t\tif ( (elemLang = documentIsHTML ?\r\n\t\t\t\t\t\telem.lang :\r\n\t\t\t\t\t\telem.getAttribute(\"xml:lang\") || elem.getAttribute(\"lang\")) ) {\r\n\r\n\t\t\t\t\t\telemLang = elemLang.toLowerCase();\r\n\t\t\t\t\t\treturn elemLang === lang || elemLang.indexOf( lang + \"-\" ) === 0;\r\n\t\t\t\t\t}\r\n\t\t\t\t} while ( (elem = elem.parentNode) && elem.nodeType === 1 );\r\n\t\t\t\treturn false;\r\n\t\t\t};\r\n\t\t}),\r\n\r\n\t\t// Miscellaneous\r\n\t\t\"target\": function( elem ) {\r\n\t\t\tvar hash = window.location && window.location.hash;\r\n\t\t\treturn hash && hash.slice( 1 ) === elem.id;\r\n\t\t},\r\n\r\n\t\t\"root\": function( elem ) {\r\n\t\t\treturn elem === docElem;\r\n\t\t},\r\n\r\n\t\t\"focus\": function( elem ) {\r\n\t\t\treturn elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);\r\n\t\t},\r\n\r\n\t\t// Boolean properties\r\n\t\t\"enabled\": function( elem ) {\r\n\t\t\treturn elem.disabled === false;\r\n\t\t},\r\n\r\n\t\t\"disabled\": function( elem ) {\r\n\t\t\treturn elem.disabled === true;\r\n\t\t},\r\n\r\n\t\t\"checked\": function( elem ) {\r\n\t\t\t// In CSS3, :checked should return both checked and selected elements\r\n\t\t\t// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked\r\n\t\t\tvar nodeName = elem.nodeName.toLowerCase();\r\n\t\t\treturn (nodeName === \"input\" && !!elem.checked) || (nodeName === \"option\" && !!elem.selected);\r\n\t\t},\r\n\r\n\t\t\"selected\": function( elem ) {\r\n\t\t\t// Accessing this property makes selected-by-default\r\n\t\t\t// options in Safari work properly\r\n\t\t\tif ( elem.parentNode ) {\r\n\t\t\t\telem.parentNode.selectedIndex;\r\n\t\t\t}\r\n\r\n\t\t\treturn elem.selected === true;\r\n\t\t},\r\n\r\n\t\t// Contents\r\n\t\t\"empty\": function( elem ) {\r\n\t\t\t// http://www.w3.org/TR/selectors/#empty-pseudo\r\n\t\t\t// :empty is only affected by element nodes and content nodes(including text(3), cdata(4)),\r\n\t\t\t//   not comment, processing instructions, or others\r\n\t\t\t// Thanks to Diego Perini for the nodeName shortcut\r\n\t\t\t//   Greater than \"@\" means alpha characters (specifically not starting with \"#\" or \"?\")\r\n\t\t\tfor ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {\r\n\t\t\t\tif ( elem.nodeName > \"@\" || elem.nodeType === 3 || elem.nodeType === 4 ) {\r\n\t\t\t\t\treturn false;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\treturn true;\r\n\t\t},\r\n\r\n\t\t\"parent\": function( elem ) {\r\n\t\t\treturn !Expr.pseudos[\"empty\"]( elem );\r\n\t\t},\r\n\r\n\t\t// Element/input types\r\n\t\t\"header\": function( elem ) {\r\n\t\t\treturn rheader.test( elem.nodeName );\r\n\t\t},\r\n\r\n\t\t\"input\": function( elem ) {\r\n\t\t\treturn rinputs.test( elem.nodeName );\r\n\t\t},\r\n\r\n\t\t\"button\": function( elem ) {\r\n\t\t\tvar name = elem.nodeName.toLowerCase();\r\n\t\t\treturn name === \"input\" && elem.type === \"button\" || name === \"button\";\r\n\t\t},\r\n\r\n\t\t\"text\": function( elem ) {\r\n\t\t\tvar attr;\r\n\t\t\t// IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)\r\n\t\t\t// use getAttribute instead to test this case\r\n\t\t\treturn elem.nodeName.toLowerCase() === \"input\" &&\r\n\t\t\t\telem.type === \"text\" &&\r\n\t\t\t\t( (attr = elem.getAttribute(\"type\")) == null || attr.toLowerCase() === elem.type );\r\n\t\t},\r\n\r\n\t\t// Position-in-collection\r\n\t\t\"first\": createPositionalPseudo(function() {\r\n\t\t\treturn [ 0 ];\r\n\t\t}),\r\n\r\n\t\t\"last\": createPositionalPseudo(function( matchIndexes, length ) {\r\n\t\t\treturn [ length - 1 ];\r\n\t\t}),\r\n\r\n\t\t\"eq\": createPositionalPseudo(function( matchIndexes, length, argument ) {\r\n\t\t\treturn [ argument < 0 ? argument + length : argument ];\r\n\t\t}),\r\n\r\n\t\t\"even\": createPositionalPseudo(function( matchIndexes, length ) {\r\n\t\t\tvar i = 0;\r\n\t\t\tfor ( ; i < length; i += 2 ) {\r\n\t\t\t\tmatchIndexes.push( i );\r\n\t\t\t}\r\n\t\t\treturn matchIndexes;\r\n\t\t}),\r\n\r\n\t\t\"odd\": createPositionalPseudo(function( matchIndexes, length ) {\r\n\t\t\tvar i = 1;\r\n\t\t\tfor ( ; i < length; i += 2 ) {\r\n\t\t\t\tmatchIndexes.push( i );\r\n\t\t\t}\r\n\t\t\treturn matchIndexes;\r\n\t\t}),\r\n\r\n\t\t\"lt\": createPositionalPseudo(function( matchIndexes, length, argument ) {\r\n\t\t\tvar i = argument < 0 ? argument + length : argument;\r\n\t\t\tfor ( ; --i >= 0; ) {\r\n\t\t\t\tmatchIndexes.push( i );\r\n\t\t\t}\r\n\t\t\treturn matchIndexes;\r\n\t\t}),\r\n\r\n\t\t\"gt\": createPositionalPseudo(function( matchIndexes, length, argument ) {\r\n\t\t\tvar i = argument < 0 ? argument + length : argument;\r\n\t\t\tfor ( ; ++i < length; ) {\r\n\t\t\t\tmatchIndexes.push( i );\r\n\t\t\t}\r\n\t\t\treturn matchIndexes;\r\n\t\t})\r\n\t}\r\n};\r\n\r\nExpr.pseudos[\"nth\"] = Expr.pseudos[\"eq\"];\r\n\r\n// Add button/input type pseudos\r\nfor ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {\r\n\tExpr.pseudos[ i ] = createInputPseudo( i );\r\n}\r\nfor ( i in { submit: true, reset: true } ) {\r\n\tExpr.pseudos[ i ] = createButtonPseudo( i );\r\n}\r\n\r\n// Easy API for creating new setFilters\r\nfunction setFilters() {}\r\nsetFilters.prototype = Expr.filters = Expr.pseudos;\r\nExpr.setFilters = new setFilters();\r\n\r\nfunction tokenize( selector, parseOnly ) {\r\n\tvar matched, match, tokens, type,\r\n\t\tsoFar, groups, preFilters,\r\n\t\tcached = tokenCache[ selector + \" \" ];\r\n\r\n\tif ( cached ) {\r\n\t\treturn parseOnly ? 0 : cached.slice( 0 );\r\n\t}\r\n\r\n\tsoFar = selector;\r\n\tgroups = [];\r\n\tpreFilters = Expr.preFilter;\r\n\r\n\twhile ( soFar ) {\r\n\r\n\t\t// Comma and first run\r\n\t\tif ( !matched || (match = rcomma.exec( soFar )) ) {\r\n\t\t\tif ( match ) {\r\n\t\t\t\t// Don't consume trailing commas as valid\r\n\t\t\t\tsoFar = soFar.slice( match[0].length ) || soFar;\r\n\t\t\t}\r\n\t\t\tgroups.push( tokens = [] );\r\n\t\t}\r\n\r\n\t\tmatched = false;\r\n\r\n\t\t// Combinators\r\n\t\tif ( (match = rcombinators.exec( soFar )) ) {\r\n\t\t\tmatched = match.shift();\r\n\t\t\ttokens.push({\r\n\t\t\t\tvalue: matched,\r\n\t\t\t\t// Cast descendant combinators to space\r\n\t\t\t\ttype: match[0].replace( rtrim, \" \" )\r\n\t\t\t});\r\n\t\t\tsoFar = soFar.slice( matched.length );\r\n\t\t}\r\n\r\n\t\t// Filters\r\n\t\tfor ( type in Expr.filter ) {\r\n\t\t\tif ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||\r\n\t\t\t\t(match = preFilters[ type ]( match ))) ) {\r\n\t\t\t\tmatched = match.shift();\r\n\t\t\t\ttokens.push({\r\n\t\t\t\t\tvalue: matched,\r\n\t\t\t\t\ttype: type,\r\n\t\t\t\t\tmatches: match\r\n\t\t\t\t});\r\n\t\t\t\tsoFar = soFar.slice( matched.length );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif ( !matched ) {\r\n\t\t\tbreak;\r\n\t\t}\r\n\t}\r\n\r\n\t// Return the length of the invalid excess\r\n\t// if we're just parsing\r\n\t// Otherwise, throw an error or return tokens\r\n\treturn parseOnly ?\r\n\t\tsoFar.length :\r\n\t\tsoFar ?\r\n\t\t\tSizzle.error( selector ) :\r\n\t\t\t// Cache the tokens\r\n\t\t\ttokenCache( selector, groups ).slice( 0 );\r\n}\r\n\r\nfunction toSelector( tokens ) {\r\n\tvar i = 0,\r\n\t\tlen = tokens.length,\r\n\t\tselector = \"\";\r\n\tfor ( ; i < len; i++ ) {\r\n\t\tselector += tokens[i].value;\r\n\t}\r\n\treturn selector;\r\n}\r\n\r\nfunction addCombinator( matcher, combinator, base ) {\r\n\tvar dir = combinator.dir,\r\n\t\tcheckNonElements = base && dir === \"parentNode\",\r\n\t\tdoneName = done++;\r\n\r\n\treturn combinator.first ?\r\n\t\t// Check against closest ancestor/preceding element\r\n\t\tfunction( elem, context, xml ) {\r\n\t\t\twhile ( (elem = elem[ dir ]) ) {\r\n\t\t\t\tif ( elem.nodeType === 1 || checkNonElements ) {\r\n\t\t\t\t\treturn matcher( elem, context, xml );\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t} :\r\n\r\n\t\t// Check against all ancestor/preceding elements\r\n\t\tfunction( elem, context, xml ) {\r\n\t\t\tvar data, cache, outerCache,\r\n\t\t\t\tdirkey = dirruns + \" \" + doneName;\r\n\r\n\t\t\t// We can't set arbitrary data on XML nodes, so they don't benefit from dir caching\r\n\t\t\tif ( xml ) {\r\n\t\t\t\twhile ( (elem = elem[ dir ]) ) {\r\n\t\t\t\t\tif ( elem.nodeType === 1 || checkNonElements ) {\r\n\t\t\t\t\t\tif ( matcher( elem, context, xml ) ) {\r\n\t\t\t\t\t\t\treturn true;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\twhile ( (elem = elem[ dir ]) ) {\r\n\t\t\t\t\tif ( elem.nodeType === 1 || checkNonElements ) {\r\n\t\t\t\t\t\touterCache = elem[ expando ] || (elem[ expando ] = {});\r\n\t\t\t\t\t\tif ( (cache = outerCache[ dir ]) && cache[0] === dirkey ) {\r\n\t\t\t\t\t\t\tif ( (data = cache[1]) === true || data === cachedruns ) {\r\n\t\t\t\t\t\t\t\treturn data === true;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\tcache = outerCache[ dir ] = [ dirkey ];\r\n\t\t\t\t\t\t\tcache[1] = matcher( elem, context, xml ) || cachedruns;\r\n\t\t\t\t\t\t\tif ( cache[1] === true ) {\r\n\t\t\t\t\t\t\t\treturn true;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t};\r\n}\r\n\r\nfunction elementMatcher( matchers ) {\r\n\treturn matchers.length > 1 ?\r\n\t\tfunction( elem, context, xml ) {\r\n\t\t\tvar i = matchers.length;\r\n\t\t\twhile ( i-- ) {\r\n\t\t\t\tif ( !matchers[i]( elem, context, xml ) ) {\r\n\t\t\t\t\treturn false;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\treturn true;\r\n\t\t} :\r\n\t\tmatchers[0];\r\n}\r\n\r\nfunction condense( unmatched, map, filter, context, xml ) {\r\n\tvar elem,\r\n\t\tnewUnmatched = [],\r\n\t\ti = 0,\r\n\t\tlen = unmatched.length,\r\n\t\tmapped = map != null;\r\n\r\n\tfor ( ; i < len; i++ ) {\r\n\t\tif ( (elem = unmatched[i]) ) {\r\n\t\t\tif ( !filter || filter( elem, context, xml ) ) {\r\n\t\t\t\tnewUnmatched.push( elem );\r\n\t\t\t\tif ( mapped ) {\r\n\t\t\t\t\tmap.push( i );\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\treturn newUnmatched;\r\n}\r\n\r\nfunction setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {\r\n\tif ( postFilter && !postFilter[ expando ] ) {\r\n\t\tpostFilter = setMatcher( postFilter );\r\n\t}\r\n\tif ( postFinder && !postFinder[ expando ] ) {\r\n\t\tpostFinder = setMatcher( postFinder, postSelector );\r\n\t}\r\n\treturn markFunction(function( seed, results, context, xml ) {\r\n\t\tvar temp, i, elem,\r\n\t\t\tpreMap = [],\r\n\t\t\tpostMap = [],\r\n\t\t\tpreexisting = results.length,\r\n\r\n\t\t\t// Get initial elements from seed or context\r\n\t\t\telems = seed || multipleContexts( selector || \"*\", context.nodeType ? [ context ] : context, [] ),\r\n\r\n\t\t\t// Prefilter to get matcher input, preserving a map for seed-results synchronization\r\n\t\t\tmatcherIn = preFilter && ( seed || !selector ) ?\r\n\t\t\t\tcondense( elems, preMap, preFilter, context, xml ) :\r\n\t\t\t\telems,\r\n\r\n\t\t\tmatcherOut = matcher ?\r\n\t\t\t\t// If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,\r\n\t\t\t\tpostFinder || ( seed ? preFilter : preexisting || postFilter ) ?\r\n\r\n\t\t\t\t\t// ...intermediate processing is necessary\r\n\t\t\t\t\t[] :\r\n\r\n\t\t\t\t\t// ...otherwise use results directly\r\n\t\t\t\t\tresults :\r\n\t\t\t\tmatcherIn;\r\n\r\n\t\t// Find primary matches\r\n\t\tif ( matcher ) {\r\n\t\t\tmatcher( matcherIn, matcherOut, context, xml );\r\n\t\t}\r\n\r\n\t\t// Apply postFilter\r\n\t\tif ( postFilter ) {\r\n\t\t\ttemp = condense( matcherOut, postMap );\r\n\t\t\tpostFilter( temp, [], context, xml );\r\n\r\n\t\t\t// Un-match failing elements by moving them back to matcherIn\r\n\t\t\ti = temp.length;\r\n\t\t\twhile ( i-- ) {\r\n\t\t\t\tif ( (elem = temp[i]) ) {\r\n\t\t\t\t\tmatcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tif ( seed ) {\r\n\t\t\tif ( postFinder || preFilter ) {\r\n\t\t\t\tif ( postFinder ) {\r\n\t\t\t\t\t// Get the final matcherOut by condensing this intermediate into postFinder contexts\r\n\t\t\t\t\ttemp = [];\r\n\t\t\t\t\ti = matcherOut.length;\r\n\t\t\t\t\twhile ( i-- ) {\r\n\t\t\t\t\t\tif ( (elem = matcherOut[i]) ) {\r\n\t\t\t\t\t\t\t// Restore matcherIn since elem is not yet a final match\r\n\t\t\t\t\t\t\ttemp.push( (matcherIn[i] = elem) );\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\tpostFinder( null, (matcherOut = []), temp, xml );\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Move matched elements from seed to results to keep them synchronized\r\n\t\t\t\ti = matcherOut.length;\r\n\t\t\t\twhile ( i-- ) {\r\n\t\t\t\t\tif ( (elem = matcherOut[i]) &&\r\n\t\t\t\t\t\t(temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) {\r\n\r\n\t\t\t\t\t\tseed[temp] = !(results[temp] = elem);\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t// Add elements to results, through postFinder if defined\r\n\t\t} else {\r\n\t\t\tmatcherOut = condense(\r\n\t\t\t\tmatcherOut === results ?\r\n\t\t\t\t\tmatcherOut.splice( preexisting, matcherOut.length ) :\r\n\t\t\t\t\tmatcherOut\r\n\t\t\t);\r\n\t\t\tif ( postFinder ) {\r\n\t\t\t\tpostFinder( null, results, matcherOut, xml );\r\n\t\t\t} else {\r\n\t\t\t\tpush.apply( results, matcherOut );\r\n\t\t\t}\r\n\t\t}\r\n\t});\r\n}\r\n\r\nfunction matcherFromTokens( tokens ) {\r\n\tvar checkContext, matcher, j,\r\n\t\tlen = tokens.length,\r\n\t\tleadingRelative = Expr.relative[ tokens[0].type ],\r\n\t\timplicitRelative = leadingRelative || Expr.relative[\" \"],\r\n\t\ti = leadingRelative ? 1 : 0,\r\n\r\n\t\t// The foundational matcher ensures that elements are reachable from top-level context(s)\r\n\t\tmatchContext = addCombinator( function( elem ) {\r\n\t\t\treturn elem === checkContext;\r\n\t\t}, implicitRelative, true ),\r\n\t\tmatchAnyContext = addCombinator( function( elem ) {\r\n\t\t\treturn indexOf.call( checkContext, elem ) > -1;\r\n\t\t}, implicitRelative, true ),\r\n\t\tmatchers = [ function( elem, context, xml ) {\r\n\t\t\treturn ( !leadingRelative && ( xml || context !== outermostContext ) ) || (\r\n\t\t\t\t(checkContext = context).nodeType ?\r\n\t\t\t\t\tmatchContext( elem, context, xml ) :\r\n\t\t\t\t\tmatchAnyContext( elem, context, xml ) );\r\n\t\t} ];\r\n\r\n\tfor ( ; i < len; i++ ) {\r\n\t\tif ( (matcher = Expr.relative[ tokens[i].type ]) ) {\r\n\t\t\tmatchers = [ addCombinator(elementMatcher( matchers ), matcher) ];\r\n\t\t} else {\r\n\t\t\tmatcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );\r\n\r\n\t\t\t// Return special upon seeing a positional matcher\r\n\t\t\tif ( matcher[ expando ] ) {\r\n\t\t\t\t// Find the next relative operator (if any) for proper handling\r\n\t\t\t\tj = ++i;\r\n\t\t\t\tfor ( ; j < len; j++ ) {\r\n\t\t\t\t\tif ( Expr.relative[ tokens[j].type ] ) {\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\treturn setMatcher(\r\n\t\t\t\t\ti > 1 && elementMatcher( matchers ),\r\n\t\t\t\t\ti > 1 && toSelector(\r\n\t\t\t\t\t\t// If the preceding token was a descendant combinator, insert an implicit any-element `*`\r\n\t\t\t\t\t\ttokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === \" \" ? \"*\" : \"\" })\r\n\t\t\t\t\t).replace( rtrim, \"$1\" ),\r\n\t\t\t\t\tmatcher,\r\n\t\t\t\t\ti < j && matcherFromTokens( tokens.slice( i, j ) ),\r\n\t\t\t\t\tj < len && matcherFromTokens( (tokens = tokens.slice( j )) ),\r\n\t\t\t\t\tj < len && toSelector( tokens )\r\n\t\t\t\t);\r\n\t\t\t}\r\n\t\t\tmatchers.push( matcher );\r\n\t\t}\r\n\t}\r\n\r\n\treturn elementMatcher( matchers );\r\n}\r\n\r\nfunction matcherFromGroupMatchers( elementMatchers, setMatchers ) {\r\n\t// A counter to specify which element is currently being matched\r\n\tvar matcherCachedRuns = 0,\r\n\t\tbySet = setMatchers.length > 0,\r\n\t\tbyElement = elementMatchers.length > 0,\r\n\t\tsuperMatcher = function( seed, context, xml, results, expandContext ) {\r\n\t\t\tvar elem, j, matcher,\r\n\t\t\t\tsetMatched = [],\r\n\t\t\t\tmatchedCount = 0,\r\n\t\t\t\ti = \"0\",\r\n\t\t\t\tunmatched = seed && [],\r\n\t\t\t\toutermost = expandContext != null,\r\n\t\t\t\tcontextBackup = outermostContext,\r\n\t\t\t\t// We must always have either seed elements or context\r\n\t\t\t\telems = seed || byElement && Expr.find[\"TAG\"]( \"*\", expandContext && context.parentNode || context ),\r\n\t\t\t\t// Use integer dirruns iff this is the outermost matcher\r\n\t\t\t\tdirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1);\r\n\r\n\t\t\tif ( outermost ) {\r\n\t\t\t\toutermostContext = context !== document && context;\r\n\t\t\t\tcachedruns = matcherCachedRuns;\r\n\t\t\t}\r\n\r\n\t\t\t// Add elements passing elementMatchers directly to results\r\n\t\t\t// Keep `i` a string if there are no elements so `matchedCount` will be \"00\" below\r\n\t\t\tfor ( ; (elem = elems[i]) != null; i++ ) {\r\n\t\t\t\tif ( byElement && elem ) {\r\n\t\t\t\t\tj = 0;\r\n\t\t\t\t\twhile ( (matcher = elementMatchers[j++]) ) {\r\n\t\t\t\t\t\tif ( matcher( elem, context, xml ) ) {\r\n\t\t\t\t\t\t\tresults.push( elem );\r\n\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif ( outermost ) {\r\n\t\t\t\t\t\tdirruns = dirrunsUnique;\r\n\t\t\t\t\t\tcachedruns = ++matcherCachedRuns;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Track unmatched elements for set filters\r\n\t\t\t\tif ( bySet ) {\r\n\t\t\t\t\t// They will have gone through all possible matchers\r\n\t\t\t\t\tif ( (elem = !matcher && elem) ) {\r\n\t\t\t\t\t\tmatchedCount--;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t// Lengthen the array for every element, matched or not\r\n\t\t\t\t\tif ( seed ) {\r\n\t\t\t\t\t\tunmatched.push( elem );\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// Apply set filters to unmatched elements\r\n\t\t\tmatchedCount += i;\r\n\t\t\tif ( bySet && i !== matchedCount ) {\r\n\t\t\t\tj = 0;\r\n\t\t\t\twhile ( (matcher = setMatchers[j++]) ) {\r\n\t\t\t\t\tmatcher( unmatched, setMatched, context, xml );\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif ( seed ) {\r\n\t\t\t\t\t// Reintegrate element matches to eliminate the need for sorting\r\n\t\t\t\t\tif ( matchedCount > 0 ) {\r\n\t\t\t\t\t\twhile ( i-- ) {\r\n\t\t\t\t\t\t\tif ( !(unmatched[i] || setMatched[i]) ) {\r\n\t\t\t\t\t\t\t\tsetMatched[i] = pop.call( results );\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t// Discard index placeholder values to get only actual matches\r\n\t\t\t\t\tsetMatched = condense( setMatched );\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Add matches to results\r\n\t\t\t\tpush.apply( results, setMatched );\r\n\r\n\t\t\t\t// Seedless set matches succeeding multiple successful matchers stipulate sorting\r\n\t\t\t\tif ( outermost && !seed && setMatched.length > 0 &&\r\n\t\t\t\t\t( matchedCount + setMatchers.length ) > 1 ) {\r\n\r\n\t\t\t\t\tSizzle.uniqueSort( results );\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// Override manipulation of globals by nested matchers\r\n\t\t\tif ( outermost ) {\r\n\t\t\t\tdirruns = dirrunsUnique;\r\n\t\t\t\toutermostContext = contextBackup;\r\n\t\t\t}\r\n\r\n\t\t\treturn unmatched;\r\n\t\t};\r\n\r\n\treturn bySet ?\r\n\t\tmarkFunction( superMatcher ) :\r\n\t\tsuperMatcher;\r\n}\r\n\r\ncompile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) {\r\n\tvar i,\r\n\t\tsetMatchers = [],\r\n\t\telementMatchers = [],\r\n\t\tcached = compilerCache[ selector + \" \" ];\r\n\r\n\tif ( !cached ) {\r\n\t\t// Generate a function of recursive functions that can be used to check each element\r\n\t\tif ( !group ) {\r\n\t\t\tgroup = tokenize( selector );\r\n\t\t}\r\n\t\ti = group.length;\r\n\t\twhile ( i-- ) {\r\n\t\t\tcached = matcherFromTokens( group[i] );\r\n\t\t\tif ( cached[ expando ] ) {\r\n\t\t\t\tsetMatchers.push( cached );\r\n\t\t\t} else {\r\n\t\t\t\telementMatchers.push( cached );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Cache the compiled function\r\n\t\tcached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );\r\n\t}\r\n\treturn cached;\r\n};\r\n\r\nfunction multipleContexts( selector, contexts, results ) {\r\n\tvar i = 0,\r\n\t\tlen = contexts.length;\r\n\tfor ( ; i < len; i++ ) {\r\n\t\tSizzle( selector, contexts[i], results );\r\n\t}\r\n\treturn results;\r\n}\r\n\r\nfunction select( selector, context, results, seed ) {\r\n\tvar i, tokens, token, type, find,\r\n\t\tmatch = tokenize( selector );\r\n\r\n\tif ( !seed ) {\r\n\t\t// Try to minimize operations if there is only one group\r\n\t\tif ( match.length === 1 ) {\r\n\r\n\t\t\t// Take a shortcut and set the context if the root selector is an ID\r\n\t\t\ttokens = match[0] = match[0].slice( 0 );\r\n\t\t\tif ( tokens.length > 2 && (token = tokens[0]).type === \"ID\" &&\r\n\t\t\t\t\tsupport.getById && context.nodeType === 9 && documentIsHTML &&\r\n\t\t\t\t\tExpr.relative[ tokens[1].type ] ) {\r\n\r\n\t\t\t\tcontext = ( Expr.find[\"ID\"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];\r\n\t\t\t\tif ( !context ) {\r\n\t\t\t\t\treturn results;\r\n\t\t\t\t}\r\n\t\t\t\tselector = selector.slice( tokens.shift().value.length );\r\n\t\t\t}\r\n\r\n\t\t\t// Fetch a seed set for right-to-left matching\r\n\t\t\ti = matchExpr[\"needsContext\"].test( selector ) ? 0 : tokens.length;\r\n\t\t\twhile ( i-- ) {\r\n\t\t\t\ttoken = tokens[i];\r\n\r\n\t\t\t\t// Abort if we hit a combinator\r\n\t\t\t\tif ( Expr.relative[ (type = token.type) ] ) {\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t\tif ( (find = Expr.find[ type ]) ) {\r\n\t\t\t\t\t// Search, expanding context for leading sibling combinators\r\n\t\t\t\t\tif ( (seed = find(\r\n\t\t\t\t\t\ttoken.matches[0].replace( runescape, funescape ),\r\n\t\t\t\t\t\trsibling.test( tokens[0].type ) && context.parentNode || context\r\n\t\t\t\t\t)) ) {\r\n\r\n\t\t\t\t\t\t// If seed is empty or no tokens remain, we can return early\r\n\t\t\t\t\t\ttokens.splice( i, 1 );\r\n\t\t\t\t\t\tselector = seed.length && toSelector( tokens );\r\n\t\t\t\t\t\tif ( !selector ) {\r\n\t\t\t\t\t\t\tpush.apply( results, seed );\r\n\t\t\t\t\t\t\treturn results;\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t// Compile and execute a filtering function\r\n\t// Provide `match` to avoid retokenization if we modified the selector above\r\n\tcompile( selector, match )(\r\n\t\tseed,\r\n\t\tcontext,\r\n\t\t!documentIsHTML,\r\n\t\tresults,\r\n\t\trsibling.test( selector )\r\n\t);\r\n\treturn results;\r\n}\r\n\r\n// One-time assignments\r\n\r\n// Sort stability\r\nsupport.sortStable = expando.split(\"\").sort( sortOrder ).join(\"\") === expando;\r\n\r\n// Support: Chrome<14\r\n// Always assume duplicates if they aren't passed to the comparison function\r\nsupport.detectDuplicates = hasDuplicate;\r\n\r\n// Initialize against the default document\r\nsetDocument();\r\n\r\n// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)\r\n// Detached nodes confoundingly follow *each other*\r\nsupport.sortDetached = assert(function( div1 ) {\r\n\t// Should return 1, but returns 4 (following)\r\n\treturn div1.compareDocumentPosition( document.createElement(\"div\") ) & 1;\r\n});\r\n\r\n// Support: IE<8\r\n// Prevent attribute/property \"interpolation\"\r\n// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx\r\nif ( !assert(function( div ) {\r\n\tdiv.innerHTML = \"<a href='#'></a>\";\r\n\treturn div.firstChild.getAttribute(\"href\") === \"#\" ;\r\n}) ) {\r\n\taddHandle( \"type|href|height|width\", function( elem, name, isXML ) {\r\n\t\tif ( !isXML ) {\r\n\t\t\treturn elem.getAttribute( name, name.toLowerCase() === \"type\" ? 1 : 2 );\r\n\t\t}\r\n\t});\r\n}\r\n\r\n// Support: IE<9\r\n// Use defaultValue in place of getAttribute(\"value\")\r\nif ( !support.attributes || !assert(function( div ) {\r\n\tdiv.innerHTML = \"<input/>\";\r\n\tdiv.firstChild.setAttribute( \"value\", \"\" );\r\n\treturn div.firstChild.getAttribute( \"value\" ) === \"\";\r\n}) ) {\r\n\taddHandle( \"value\", function( elem, name, isXML ) {\r\n\t\tif ( !isXML && elem.nodeName.toLowerCase() === \"input\" ) {\r\n\t\t\treturn elem.defaultValue;\r\n\t\t}\r\n\t});\r\n}\r\n\r\n// Support: IE<9\r\n// Use getAttributeNode to fetch booleans when getAttribute lies\r\nif ( !assert(function( div ) {\r\n\treturn div.getAttribute(\"disabled\") == null;\r\n}) ) {\r\n\taddHandle( booleans, function( elem, name, isXML ) {\r\n\t\tvar val;\r\n\t\tif ( !isXML ) {\r\n\t\t\treturn (val = elem.getAttributeNode( name )) && val.specified ?\r\n\t\t\t\tval.value :\r\n\t\t\t\telem[ name ] === true ? name.toLowerCase() : null;\r\n\t\t}\r\n\t});\r\n}\r\n\r\njQuery.find = Sizzle;\r\njQuery.expr = Sizzle.selectors;\r\njQuery.expr[\":\"] = jQuery.expr.pseudos;\r\njQuery.unique = Sizzle.uniqueSort;\r\njQuery.text = Sizzle.getText;\r\njQuery.isXMLDoc = Sizzle.isXML;\r\njQuery.contains = Sizzle.contains;\r\n\r\n\r\n})( window );\r\n// String to Object options format cache\r\nvar optionsCache = {};\r\n\r\n// Convert String-formatted options into Object-formatted ones and store in cache\r\nfunction createOptions( options ) {\r\n\tvar object = optionsCache[ options ] = {};\r\n\tjQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) {\r\n\t\tobject[ flag ] = true;\r\n\t});\r\n\treturn object;\r\n}\r\n\r\n/*\r\n * Create a callback list using the following parameters:\r\n *\r\n *\toptions: an optional list of space-separated options that will change how\r\n *\t\t\tthe callback list behaves or a more traditional option object\r\n *\r\n * By default a callback list will act like an event callback list and can be\r\n * \"fired\" multiple times.\r\n *\r\n * Possible options:\r\n *\r\n *\tonce:\t\t\twill ensure the callback list can only be fired once (like a Deferred)\r\n *\r\n *\tmemory:\t\t\twill keep track of previous values and will call any callback added\r\n *\t\t\t\t\tafter the list has been fired right away with the latest \"memorized\"\r\n *\t\t\t\t\tvalues (like a Deferred)\r\n *\r\n *\tunique:\t\t\twill ensure a callback can only be added once (no duplicate in the list)\r\n *\r\n *\tstopOnFalse:\tinterrupt callings when a callback returns false\r\n *\r\n */\r\njQuery.Callbacks = function( options ) {\r\n\r\n\t// Convert options from String-formatted to Object-formatted if needed\r\n\t// (we check in cache first)\r\n\toptions = typeof options === \"string\" ?\r\n\t\t( optionsCache[ options ] || createOptions( options ) ) :\r\n\t\tjQuery.extend( {}, options );\r\n\r\n\tvar // Flag to know if list is currently firing\r\n\t\tfiring,\r\n\t\t// Last fire value (for non-forgettable lists)\r\n\t\tmemory,\r\n\t\t// Flag to know if list was already fired\r\n\t\tfired,\r\n\t\t// End of the loop when firing\r\n\t\tfiringLength,\r\n\t\t// Index of currently firing callback (modified by remove if needed)\r\n\t\tfiringIndex,\r\n\t\t// First callback to fire (used internally by add and fireWith)\r\n\t\tfiringStart,\r\n\t\t// Actual callback list\r\n\t\tlist = [],\r\n\t\t// Stack of fire calls for repeatable lists\r\n\t\tstack = !options.once && [],\r\n\t\t// Fire callbacks\r\n\t\tfire = function( data ) {\r\n\t\t\tmemory = options.memory && data;\r\n\t\t\tfired = true;\r\n\t\t\tfiringIndex = firingStart || 0;\r\n\t\t\tfiringStart = 0;\r\n\t\t\tfiringLength = list.length;\r\n\t\t\tfiring = true;\r\n\t\t\tfor ( ; list && firingIndex < firingLength; firingIndex++ ) {\r\n\t\t\t\tif ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {\r\n\t\t\t\t\tmemory = false; // To prevent further calls using add\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\tfiring = false;\r\n\t\t\tif ( list ) {\r\n\t\t\t\tif ( stack ) {\r\n\t\t\t\t\tif ( stack.length ) {\r\n\t\t\t\t\t\tfire( stack.shift() );\r\n\t\t\t\t\t}\r\n\t\t\t\t} else if ( memory ) {\r\n\t\t\t\t\tlist = [];\r\n\t\t\t\t} else {\r\n\t\t\t\t\tself.disable();\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\t\t// Actual Callbacks object\r\n\t\tself = {\r\n\t\t\t// Add a callback or a collection of callbacks to the list\r\n\t\t\tadd: function() {\r\n\t\t\t\tif ( list ) {\r\n\t\t\t\t\t// First, we save the current length\r\n\t\t\t\t\tvar start = list.length;\r\n\t\t\t\t\t(function add( args ) {\r\n\t\t\t\t\t\tjQuery.each( args, function( _, arg ) {\r\n\t\t\t\t\t\t\tvar type = jQuery.type( arg );\r\n\t\t\t\t\t\t\tif ( type === \"function\" ) {\r\n\t\t\t\t\t\t\t\tif ( !options.unique || !self.has( arg ) ) {\r\n\t\t\t\t\t\t\t\t\tlist.push( arg );\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t} else if ( arg && arg.length && type !== \"string\" ) {\r\n\t\t\t\t\t\t\t\t// Inspect recursively\r\n\t\t\t\t\t\t\t\tadd( arg );\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t})( arguments );\r\n\t\t\t\t\t// Do we need to add the callbacks to the\r\n\t\t\t\t\t// current firing batch?\r\n\t\t\t\t\tif ( firing ) {\r\n\t\t\t\t\t\tfiringLength = list.length;\r\n\t\t\t\t\t// With memory, if we're not firing then\r\n\t\t\t\t\t// we should call right away\r\n\t\t\t\t\t} else if ( memory ) {\r\n\t\t\t\t\t\tfiringStart = start;\r\n\t\t\t\t\t\tfire( memory );\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\treturn this;\r\n\t\t\t},\r\n\t\t\t// Remove a callback from the list\r\n\t\t\tremove: function() {\r\n\t\t\t\tif ( list ) {\r\n\t\t\t\t\tjQuery.each( arguments, function( _, arg ) {\r\n\t\t\t\t\t\tvar index;\r\n\t\t\t\t\t\twhile( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {\r\n\t\t\t\t\t\t\tlist.splice( index, 1 );\r\n\t\t\t\t\t\t\t// Handle firing indexes\r\n\t\t\t\t\t\t\tif ( firing ) {\r\n\t\t\t\t\t\t\t\tif ( index <= firingLength ) {\r\n\t\t\t\t\t\t\t\t\tfiringLength--;\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\tif ( index <= firingIndex ) {\r\n\t\t\t\t\t\t\t\t\tfiringIndex--;\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t});\r\n\t\t\t\t}\r\n\t\t\t\treturn this;\r\n\t\t\t},\r\n\t\t\t// Check if a given callback is in the list.\r\n\t\t\t// If no argument is given, return whether or not list has callbacks attached.\r\n\t\t\thas: function( fn ) {\r\n\t\t\t\treturn fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );\r\n\t\t\t},\r\n\t\t\t// Remove all callbacks from the list\r\n\t\t\tempty: function() {\r\n\t\t\t\tlist = [];\r\n\t\t\t\tfiringLength = 0;\r\n\t\t\t\treturn this;\r\n\t\t\t},\r\n\t\t\t// Have the list do nothing anymore\r\n\t\t\tdisable: function() {\r\n\t\t\t\tlist = stack = memory = undefined;\r\n\t\t\t\treturn this;\r\n\t\t\t},\r\n\t\t\t// Is it disabled?\r\n\t\t\tdisabled: function() {\r\n\t\t\t\treturn !list;\r\n\t\t\t},\r\n\t\t\t// Lock the list in its current state\r\n\t\t\tlock: function() {\r\n\t\t\t\tstack = undefined;\r\n\t\t\t\tif ( !memory ) {\r\n\t\t\t\t\tself.disable();\r\n\t\t\t\t}\r\n\t\t\t\treturn this;\r\n\t\t\t},\r\n\t\t\t// Is it locked?\r\n\t\t\tlocked: function() {\r\n\t\t\t\treturn !stack;\r\n\t\t\t},\r\n\t\t\t// Call all callbacks with the given context and arguments\r\n\t\t\tfireWith: function( context, args ) {\r\n\t\t\t\tif ( list && ( !fired || stack ) ) {\r\n\t\t\t\t\targs = args || [];\r\n\t\t\t\t\targs = [ context, args.slice ? args.slice() : args ];\r\n\t\t\t\t\tif ( firing ) {\r\n\t\t\t\t\t\tstack.push( args );\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tfire( args );\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t\treturn this;\r\n\t\t\t},\r\n\t\t\t// Call all the callbacks with the given arguments\r\n\t\t\tfire: function() {\r\n\t\t\t\tself.fireWith( this, arguments );\r\n\t\t\t\treturn this;\r\n\t\t\t},\r\n\t\t\t// To know if the callbacks have already been called at least once\r\n\t\t\tfired: function() {\r\n\t\t\t\treturn !!fired;\r\n\t\t\t}\r\n\t\t};\r\n\r\n\treturn self;\r\n};\r\njQuery.extend({\r\n\r\n\tDeferred: function( func ) {\r\n\t\tvar tuples = [\r\n\t\t\t\t// action, add listener, listener list, final state\r\n\t\t\t\t[ \"resolve\", \"done\", jQuery.Callbacks(\"once memory\"), \"resolved\" ],\r\n\t\t\t\t[ \"reject\", \"fail\", jQuery.Callbacks(\"once memory\"), \"rejected\" ],\r\n\t\t\t\t[ \"notify\", \"progress\", jQuery.Callbacks(\"memory\") ]\r\n\t\t\t],\r\n\t\t\tstate = \"pending\",\r\n\t\t\tpromise = {\r\n\t\t\t\tstate: function() {\r\n\t\t\t\t\treturn state;\r\n\t\t\t\t},\r\n\t\t\t\talways: function() {\r\n\t\t\t\t\tdeferred.done( arguments ).fail( arguments );\r\n\t\t\t\t\treturn this;\r\n\t\t\t\t},\r\n\t\t\t\tthen: function( /* fnDone, fnFail, fnProgress */ ) {\r\n\t\t\t\t\tvar fns = arguments;\r\n\t\t\t\t\treturn jQuery.Deferred(function( newDefer ) {\r\n\t\t\t\t\t\tjQuery.each( tuples, function( i, tuple ) {\r\n\t\t\t\t\t\t\tvar action = tuple[ 0 ],\r\n\t\t\t\t\t\t\t\tfn = jQuery.isFunction( fns[ i ] ) && fns[ i ];\r\n\t\t\t\t\t\t\t// deferred[ done | fail | progress ] for forwarding actions to newDefer\r\n\t\t\t\t\t\t\tdeferred[ tuple[1] ](function() {\r\n\t\t\t\t\t\t\t\tvar returned = fn && fn.apply( this, arguments );\r\n\t\t\t\t\t\t\t\tif ( returned && jQuery.isFunction( returned.promise ) ) {\r\n\t\t\t\t\t\t\t\t\treturned.promise()\r\n\t\t\t\t\t\t\t\t\t\t.done( newDefer.resolve )\r\n\t\t\t\t\t\t\t\t\t\t.fail( newDefer.reject )\r\n\t\t\t\t\t\t\t\t\t\t.progress( newDefer.notify );\r\n\t\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\t\tnewDefer[ action + \"With\" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t});\r\n\t\t\t\t\t\t});\r\n\t\t\t\t\t\tfns = null;\r\n\t\t\t\t\t}).promise();\r\n\t\t\t\t},\r\n\t\t\t\t// Get a promise for this deferred\r\n\t\t\t\t// If obj is provided, the promise aspect is added to the object\r\n\t\t\t\tpromise: function( obj ) {\r\n\t\t\t\t\treturn obj != null ? jQuery.extend( obj, promise ) : promise;\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tdeferred = {};\r\n\r\n\t\t// Keep pipe for back-compat\r\n\t\tpromise.pipe = promise.then;\r\n\r\n\t\t// Add list-specific methods\r\n\t\tjQuery.each( tuples, function( i, tuple ) {\r\n\t\t\tvar list = tuple[ 2 ],\r\n\t\t\t\tstateString = tuple[ 3 ];\r\n\r\n\t\t\t// promise[ done | fail | progress ] = list.add\r\n\t\t\tpromise[ tuple[1] ] = list.add;\r\n\r\n\t\t\t// Handle state\r\n\t\t\tif ( stateString ) {\r\n\t\t\t\tlist.add(function() {\r\n\t\t\t\t\t// state = [ resolved | rejected ]\r\n\t\t\t\t\tstate = stateString;\r\n\r\n\t\t\t\t// [ reject_list | resolve_list ].disable; progress_list.lock\r\n\t\t\t\t}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );\r\n\t\t\t}\r\n\r\n\t\t\t// deferred[ resolve | reject | notify ]\r\n\t\t\tdeferred[ tuple[0] ] = function() {\r\n\t\t\t\tdeferred[ tuple[0] + \"With\" ]( this === deferred ? promise : this, arguments );\r\n\t\t\t\treturn this;\r\n\t\t\t};\r\n\t\t\tdeferred[ tuple[0] + \"With\" ] = list.fireWith;\r\n\t\t});\r\n\r\n\t\t// Make the deferred a promise\r\n\t\tpromise.promise( deferred );\r\n\r\n\t\t// Call given func if any\r\n\t\tif ( func ) {\r\n\t\t\tfunc.call( deferred, deferred );\r\n\t\t}\r\n\r\n\t\t// All done!\r\n\t\treturn deferred;\r\n\t},\r\n\r\n\t// Deferred helper\r\n\twhen: function( subordinate /* , ..., subordinateN */ ) {\r\n\t\tvar i = 0,\r\n\t\t\tresolveValues = core_slice.call( arguments ),\r\n\t\t\tlength = resolveValues.length,\r\n\r\n\t\t\t// the count of uncompleted subordinates\r\n\t\t\tremaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,\r\n\r\n\t\t\t// the master Deferred. If resolveValues consist of only a single Deferred, just use that.\r\n\t\t\tdeferred = remaining === 1 ? subordinate : jQuery.Deferred(),\r\n\r\n\t\t\t// Update function for both resolve and progress values\r\n\t\t\tupdateFunc = function( i, contexts, values ) {\r\n\t\t\t\treturn function( value ) {\r\n\t\t\t\t\tcontexts[ i ] = this;\r\n\t\t\t\t\tvalues[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;\r\n\t\t\t\t\tif( values === progressValues ) {\r\n\t\t\t\t\t\tdeferred.notifyWith( contexts, values );\r\n\t\t\t\t\t} else if ( !( --remaining ) ) {\r\n\t\t\t\t\t\tdeferred.resolveWith( contexts, values );\r\n\t\t\t\t\t}\r\n\t\t\t\t};\r\n\t\t\t},\r\n\r\n\t\t\tprogressValues, progressContexts, resolveContexts;\r\n\r\n\t\t// add listeners to Deferred subordinates; treat others as resolved\r\n\t\tif ( length > 1 ) {\r\n\t\t\tprogressValues = new Array( length );\r\n\t\t\tprogressContexts = new Array( length );\r\n\t\t\tresolveContexts = new Array( length );\r\n\t\t\tfor ( ; i < length; i++ ) {\r\n\t\t\t\tif ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {\r\n\t\t\t\t\tresolveValues[ i ].promise()\r\n\t\t\t\t\t\t.done( updateFunc( i, resolveContexts, resolveValues ) )\r\n\t\t\t\t\t\t.fail( deferred.reject )\r\n\t\t\t\t\t\t.progress( updateFunc( i, progressContexts, progressValues ) );\r\n\t\t\t\t} else {\r\n\t\t\t\t\t--remaining;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// if we're not waiting on anything, resolve the master\r\n\t\tif ( !remaining ) {\r\n\t\t\tdeferred.resolveWith( resolveContexts, resolveValues );\r\n\t\t}\r\n\r\n\t\treturn deferred.promise();\r\n\t}\r\n});\r\njQuery.support = (function( support ) {\r\n\r\n\tvar all, a, input, select, fragment, opt, eventName, isSupported, i,\r\n\t\tdiv = document.createElement(\"div\");\r\n\r\n\t// Setup\r\n\tdiv.setAttribute( \"className\", \"t\" );\r\n\tdiv.innerHTML = \"  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>\";\r\n\r\n\t// Finish early in limited (non-browser) environments\r\n\tall = div.getElementsByTagName(\"*\") || [];\r\n\ta = div.getElementsByTagName(\"a\")[ 0 ];\r\n\tif ( !a || !a.style || !all.length ) {\r\n\t\treturn support;\r\n\t}\r\n\r\n\t// First batch of tests\r\n\tselect = document.createElement(\"select\");\r\n\topt = select.appendChild( document.createElement(\"option\") );\r\n\tinput = div.getElementsByTagName(\"input\")[ 0 ];\r\n\r\n\ta.style.cssText = \"top:1px;float:left;opacity:.5\";\r\n\r\n\t// Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7)\r\n\tsupport.getSetAttribute = div.className !== \"t\";\r\n\r\n\t// IE strips leading whitespace when .innerHTML is used\r\n\tsupport.leadingWhitespace = div.firstChild.nodeType === 3;\r\n\r\n\t// Make sure that tbody elements aren't automatically inserted\r\n\t// IE will insert them into empty tables\r\n\tsupport.tbody = !div.getElementsByTagName(\"tbody\").length;\r\n\r\n\t// Make sure that link elements get serialized correctly by innerHTML\r\n\t// This requires a wrapper element in IE\r\n\tsupport.htmlSerialize = !!div.getElementsByTagName(\"link\").length;\r\n\r\n\t// Get the style information from getAttribute\r\n\t// (IE uses .cssText instead)\r\n\tsupport.style = /top/.test( a.getAttribute(\"style\") );\r\n\r\n\t// Make sure that URLs aren't manipulated\r\n\t// (IE normalizes it by default)\r\n\tsupport.hrefNormalized = a.getAttribute(\"href\") === \"/a\";\r\n\r\n\t// Make sure that element opacity exists\r\n\t// (IE uses filter instead)\r\n\t// Use a regex to work around a WebKit issue. See #5145\r\n\tsupport.opacity = /^0.5/.test( a.style.opacity );\r\n\r\n\t// Verify style float existence\r\n\t// (IE uses styleFloat instead of cssFloat)\r\n\tsupport.cssFloat = !!a.style.cssFloat;\r\n\r\n\t// Check the default checkbox/radio value (\"\" on WebKit; \"on\" elsewhere)\r\n\tsupport.checkOn = !!input.value;\r\n\r\n\t// Make sure that a selected-by-default option has a working selected property.\r\n\t// (WebKit defaults to false instead of true, IE too, if it's in an optgroup)\r\n\tsupport.optSelected = opt.selected;\r\n\r\n\t// Tests for enctype support on a form (#6743)\r\n\tsupport.enctype = !!document.createElement(\"form\").enctype;\r\n\r\n\t// Makes sure cloning an html5 element does not cause problems\r\n\t// Where outerHTML is undefined, this still works\r\n\tsupport.html5Clone = document.createElement(\"nav\").cloneNode( true ).outerHTML !== \"<:nav></:nav>\";\r\n\r\n\t// Will be defined later\r\n\tsupport.inlineBlockNeedsLayout = false;\r\n\tsupport.shrinkWrapBlocks = false;\r\n\tsupport.pixelPosition = false;\r\n\tsupport.deleteExpando = true;\r\n\tsupport.noCloneEvent = true;\r\n\tsupport.reliableMarginRight = true;\r\n\tsupport.boxSizingReliable = true;\r\n\r\n\t// Make sure checked status is properly cloned\r\n\tinput.checked = true;\r\n\tsupport.noCloneChecked = input.cloneNode( true ).checked;\r\n\r\n\t// Make sure that the options inside disabled selects aren't marked as disabled\r\n\t// (WebKit marks them as disabled)\r\n\tselect.disabled = true;\r\n\tsupport.optDisabled = !opt.disabled;\r\n\r\n\t// Support: IE<9\r\n\ttry {\r\n\t\tdelete div.test;\r\n\t} catch( e ) {\r\n\t\tsupport.deleteExpando = false;\r\n\t}\r\n\r\n\t// Check if we can trust getAttribute(\"value\")\r\n\tinput = document.createElement(\"input\");\r\n\tinput.setAttribute( \"value\", \"\" );\r\n\tsupport.input = input.getAttribute( \"value\" ) === \"\";\r\n\r\n\t// Check if an input maintains its value after becoming a radio\r\n\tinput.value = \"t\";\r\n\tinput.setAttribute( \"type\", \"radio\" );\r\n\tsupport.radioValue = input.value === \"t\";\r\n\r\n\t// #11217 - WebKit loses check when the name is after the checked attribute\r\n\tinput.setAttribute( \"checked\", \"t\" );\r\n\tinput.setAttribute( \"name\", \"t\" );\r\n\r\n\tfragment = document.createDocumentFragment();\r\n\tfragment.appendChild( input );\r\n\r\n\t// Check if a disconnected checkbox will retain its checked\r\n\t// value of true after appended to the DOM (IE6/7)\r\n\tsupport.appendChecked = input.checked;\r\n\r\n\t// WebKit doesn't clone checked state correctly in fragments\r\n\tsupport.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;\r\n\r\n\t// Support: IE<9\r\n\t// Opera does not clone events (and typeof div.attachEvent === undefined).\r\n\t// IE9-10 clones events bound via attachEvent, but they don't trigger with .click()\r\n\tif ( div.attachEvent ) {\r\n\t\tdiv.attachEvent( \"onclick\", function() {\r\n\t\t\tsupport.noCloneEvent = false;\r\n\t\t});\r\n\r\n\t\tdiv.cloneNode( true ).click();\r\n\t}\r\n\r\n\t// Support: IE<9 (lack submit/change bubble), Firefox 17+ (lack focusin event)\r\n\t// Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP)\r\n\tfor ( i in { submit: true, change: true, focusin: true }) {\r\n\t\tdiv.setAttribute( eventName = \"on\" + i, \"t\" );\r\n\r\n\t\tsupport[ i + \"Bubbles\" ] = eventName in window || div.attributes[ eventName ].expando === false;\r\n\t}\r\n\r\n\tdiv.style.backgroundClip = \"content-box\";\r\n\tdiv.cloneNode( true ).style.backgroundClip = \"\";\r\n\tsupport.clearCloneStyle = div.style.backgroundClip === \"content-box\";\r\n\r\n\t// Support: IE<9\r\n\t// Iteration over object's inherited properties before its own.\r\n\tfor ( i in jQuery( support ) ) {\r\n\t\tbreak;\r\n\t}\r\n\tsupport.ownLast = i !== \"0\";\r\n\r\n\t// Run tests that need a body at doc ready\r\n\tjQuery(function() {\r\n\t\tvar container, marginDiv, tds,\r\n\t\t\tdivReset = \"padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;\",\r\n\t\t\tbody = document.getElementsByTagName(\"body\")[0];\r\n\r\n\t\tif ( !body ) {\r\n\t\t\t// Return for frameset docs that don't have a body\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tcontainer = document.createElement(\"div\");\r\n\t\tcontainer.style.cssText = \"border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px\";\r\n\r\n\t\tbody.appendChild( container ).appendChild( div );\r\n\r\n\t\t// Support: IE8\r\n\t\t// Check if table cells still have offsetWidth/Height when they are set\r\n\t\t// to display:none and there are still other visible table cells in a\r\n\t\t// table row; if so, offsetWidth/Height are not reliable for use when\r\n\t\t// determining if an element has been hidden directly using\r\n\t\t// display:none (it is still safe to use offsets if a parent element is\r\n\t\t// hidden; don safety goggles and see bug #4512 for more information).\r\n\t\tdiv.innerHTML = \"<table><tr><td></td><td>t</td></tr></table>\";\r\n\t\ttds = div.getElementsByTagName(\"td\");\r\n\t\ttds[ 0 ].style.cssText = \"padding:0;margin:0;border:0;display:none\";\r\n\t\tisSupported = ( tds[ 0 ].offsetHeight === 0 );\r\n\r\n\t\ttds[ 0 ].style.display = \"\";\r\n\t\ttds[ 1 ].style.display = \"none\";\r\n\r\n\t\t// Support: IE8\r\n\t\t// Check if empty table cells still have offsetWidth/Height\r\n\t\tsupport.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 );\r\n\r\n\t\t// Check box-sizing and margin behavior.\r\n\t\tdiv.innerHTML = \"\";\r\n\t\tdiv.style.cssText = \"box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;\";\r\n\r\n\t\t// Workaround failing boxSizing test due to offsetWidth returning wrong value\r\n\t\t// with some non-1 values of body zoom, ticket #13543\r\n\t\tjQuery.swap( body, body.style.zoom != null ? { zoom: 1 } : {}, function() {\r\n\t\t\tsupport.boxSizing = div.offsetWidth === 4;\r\n\t\t});\r\n\r\n\t\t// Use window.getComputedStyle because jsdom on node.js will break without it.\r\n\t\tif ( window.getComputedStyle ) {\r\n\t\t\tsupport.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== \"1%\";\r\n\t\t\tsupport.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: \"4px\" } ).width === \"4px\";\r\n\r\n\t\t\t// Check if div with explicit width and no margin-right incorrectly\r\n\t\t\t// gets computed margin-right based on width of container. (#3333)\r\n\t\t\t// Fails in WebKit before Feb 2011 nightlies\r\n\t\t\t// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right\r\n\t\t\tmarginDiv = div.appendChild( document.createElement(\"div\") );\r\n\t\t\tmarginDiv.style.cssText = div.style.cssText = divReset;\r\n\t\t\tmarginDiv.style.marginRight = marginDiv.style.width = \"0\";\r\n\t\t\tdiv.style.width = \"1px\";\r\n\r\n\t\t\tsupport.reliableMarginRight =\r\n\t\t\t\t!parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight );\r\n\t\t}\r\n\r\n\t\tif ( typeof div.style.zoom !== core_strundefined ) {\r\n\t\t\t// Support: IE<8\r\n\t\t\t// Check if natively block-level elements act like inline-block\r\n\t\t\t// elements when setting their display to 'inline' and giving\r\n\t\t\t// them layout\r\n\t\t\tdiv.innerHTML = \"\";\r\n\t\t\tdiv.style.cssText = divReset + \"width:1px;padding:1px;display:inline;zoom:1\";\r\n\t\t\tsupport.inlineBlockNeedsLayout = ( div.offsetWidth === 3 );\r\n\r\n\t\t\t// Support: IE6\r\n\t\t\t// Check if elements with layout shrink-wrap their children\r\n\t\t\tdiv.style.display = \"block\";\r\n\t\t\tdiv.innerHTML = \"<div></div>\";\r\n\t\t\tdiv.firstChild.style.width = \"5px\";\r\n\t\t\tsupport.shrinkWrapBlocks = ( div.offsetWidth !== 3 );\r\n\r\n\t\t\tif ( support.inlineBlockNeedsLayout ) {\r\n\t\t\t\t// Prevent IE 6 from affecting layout for positioned elements #11048\r\n\t\t\t\t// Prevent IE from shrinking the body in IE 7 mode #12869\r\n\t\t\t\t// Support: IE<8\r\n\t\t\t\tbody.style.zoom = 1;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\tbody.removeChild( container );\r\n\r\n\t\t// Null elements to avoid leaks in IE\r\n\t\tcontainer = div = tds = marginDiv = null;\r\n\t});\r\n\r\n\t// Null elements to avoid leaks in IE\r\n\tall = select = fragment = opt = a = input = null;\r\n\r\n\treturn support;\r\n})({});\r\n\r\nvar rbrace = /(?:\\{[\\s\\S]*\\}|\\[[\\s\\S]*\\])$/,\r\n\trmultiDash = /([A-Z])/g;\r\n\r\nfunction internalData( elem, name, data, pvt /* Internal Use Only */ ){\r\n\tif ( !jQuery.acceptData( elem ) ) {\r\n\t\treturn;\r\n\t}\r\n\r\n\tvar ret, thisCache,\r\n\t\tinternalKey = jQuery.expando,\r\n\r\n\t\t// We have to handle DOM nodes and JS objects differently because IE6-7\r\n\t\t// can't GC object references properly across the DOM-JS boundary\r\n\t\tisNode = elem.nodeType,\r\n\r\n\t\t// Only DOM nodes need the global jQuery cache; JS object data is\r\n\t\t// attached directly to the object so GC can occur automatically\r\n\t\tcache = isNode ? jQuery.cache : elem,\r\n\r\n\t\t// Only defining an ID for JS objects if its cache already exists allows\r\n\t\t// the code to shortcut on the same path as a DOM node with no cache\r\n\t\tid = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;\r\n\r\n\t// Avoid doing any more work than we need to when trying to get data on an\r\n\t// object that has no data at all\r\n\tif ( (!id || !cache[id] || (!pvt && !cache[id].data)) && data === undefined && typeof name === \"string\" ) {\r\n\t\treturn;\r\n\t}\r\n\r\n\tif ( !id ) {\r\n\t\t// Only DOM nodes need a new unique ID for each element since their data\r\n\t\t// ends up in the global cache\r\n\t\tif ( isNode ) {\r\n\t\t\tid = elem[ internalKey ] = core_deletedIds.pop() || jQuery.guid++;\r\n\t\t} else {\r\n\t\t\tid = internalKey;\r\n\t\t}\r\n\t}\r\n\r\n\tif ( !cache[ id ] ) {\r\n\t\t// Avoid exposing jQuery metadata on plain JS objects when the object\r\n\t\t// is serialized using JSON.stringify\r\n\t\tcache[ id ] = isNode ? {} : { toJSON: jQuery.noop };\r\n\t}\r\n\r\n\t// An object can be passed to jQuery.data instead of a key/value pair; this gets\r\n\t// shallow copied over onto the existing cache\r\n\tif ( typeof name === \"object\" || typeof name === \"function\" ) {\r\n\t\tif ( pvt ) {\r\n\t\t\tcache[ id ] = jQuery.extend( cache[ id ], name );\r\n\t\t} else {\r\n\t\t\tcache[ id ].data = jQuery.extend( cache[ id ].data, name );\r\n\t\t}\r\n\t}\r\n\r\n\tthisCache = cache[ id ];\r\n\r\n\t// jQuery data() is stored in a separate object inside the object's internal data\r\n\t// cache in order to avoid key collisions between internal data and user-defined\r\n\t// data.\r\n\tif ( !pvt ) {\r\n\t\tif ( !thisCache.data ) {\r\n\t\t\tthisCache.data = {};\r\n\t\t}\r\n\r\n\t\tthisCache = thisCache.data;\r\n\t}\r\n\r\n\tif ( data !== undefined ) {\r\n\t\tthisCache[ jQuery.camelCase( name ) ] = data;\r\n\t}\r\n\r\n\t// Check for both converted-to-camel and non-converted data property names\r\n\t// If a data property was specified\r\n\tif ( typeof name === \"string\" ) {\r\n\r\n\t\t// First Try to find as-is property data\r\n\t\tret = thisCache[ name ];\r\n\r\n\t\t// Test for null|undefined property data\r\n\t\tif ( ret == null ) {\r\n\r\n\t\t\t// Try to find the camelCased property\r\n\t\t\tret = thisCache[ jQuery.camelCase( name ) ];\r\n\t\t}\r\n\t} else {\r\n\t\tret = thisCache;\r\n\t}\r\n\r\n\treturn ret;\r\n}\r\n\r\nfunction internalRemoveData( elem, name, pvt ) {\r\n\tif ( !jQuery.acceptData( elem ) ) {\r\n\t\treturn;\r\n\t}\r\n\r\n\tvar thisCache, i,\r\n\t\tisNode = elem.nodeType,\r\n\r\n\t\t// See jQuery.data for more information\r\n\t\tcache = isNode ? jQuery.cache : elem,\r\n\t\tid = isNode ? elem[ jQuery.expando ] : jQuery.expando;\r\n\r\n\t// If there is already no cache entry for this object, there is no\r\n\t// purpose in continuing\r\n\tif ( !cache[ id ] ) {\r\n\t\treturn;\r\n\t}\r\n\r\n\tif ( name ) {\r\n\r\n\t\tthisCache = pvt ? cache[ id ] : cache[ id ].data;\r\n\r\n\t\tif ( thisCache ) {\r\n\r\n\t\t\t// Support array or space separated string names for data keys\r\n\t\t\tif ( !jQuery.isArray( name ) ) {\r\n\r\n\t\t\t\t// try the string as a key before any manipulation\r\n\t\t\t\tif ( name in thisCache ) {\r\n\t\t\t\t\tname = [ name ];\r\n\t\t\t\t} else {\r\n\r\n\t\t\t\t\t// split the camel cased version by spaces unless a key with the spaces exists\r\n\t\t\t\t\tname = jQuery.camelCase( name );\r\n\t\t\t\t\tif ( name in thisCache ) {\r\n\t\t\t\t\t\tname = [ name ];\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tname = name.split(\" \");\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\t// If \"name\" is an array of keys...\r\n\t\t\t\t// When data is initially created, via (\"key\", \"val\") signature,\r\n\t\t\t\t// keys will be converted to camelCase.\r\n\t\t\t\t// Since there is no way to tell _how_ a key was added, remove\r\n\t\t\t\t// both plain key and camelCase key. #12786\r\n\t\t\t\t// This will only penalize the array argument path.\r\n\t\t\t\tname = name.concat( jQuery.map( name, jQuery.camelCase ) );\r\n\t\t\t}\r\n\r\n\t\t\ti = name.length;\r\n\t\t\twhile ( i-- ) {\r\n\t\t\t\tdelete thisCache[ name[i] ];\r\n\t\t\t}\r\n\r\n\t\t\t// If there is no data left in the cache, we want to continue\r\n\t\t\t// and let the cache object itself get destroyed\r\n\t\t\tif ( pvt ? !isEmptyDataObject(thisCache) : !jQuery.isEmptyObject(thisCache) ) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t// See jQuery.data for more information\r\n\tif ( !pvt ) {\r\n\t\tdelete cache[ id ].data;\r\n\r\n\t\t// Don't destroy the parent cache unless the internal data object\r\n\t\t// had been the only thing left in it\r\n\t\tif ( !isEmptyDataObject( cache[ id ] ) ) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t}\r\n\r\n\t// Destroy the cache\r\n\tif ( isNode ) {\r\n\t\tjQuery.cleanData( [ elem ], true );\r\n\r\n\t// Use delete when supported for expandos or `cache` is not a window per isWindow (#10080)\r\n\t/* jshint eqeqeq: false */\r\n\t} else if ( jQuery.support.deleteExpando || cache != cache.window ) {\r\n\t\t/* jshint eqeqeq: true */\r\n\t\tdelete cache[ id ];\r\n\r\n\t// When all else fails, null\r\n\t} else {\r\n\t\tcache[ id ] = null;\r\n\t}\r\n}\r\n\r\njQuery.extend({\r\n\tcache: {},\r\n\r\n\t// The following elements throw uncatchable exceptions if you\r\n\t// attempt to add expando properties to them.\r\n\tnoData: {\r\n\t\t\"applet\": true,\r\n\t\t\"embed\": true,\r\n\t\t// Ban all objects except for Flash (which handle expandos)\r\n\t\t\"object\": \"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000\"\r\n\t},\r\n\r\n\thasData: function( elem ) {\r\n\t\telem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];\r\n\t\treturn !!elem && !isEmptyDataObject( elem );\r\n\t},\r\n\r\n\tdata: function( elem, name, data ) {\r\n\t\treturn internalData( elem, name, data );\r\n\t},\r\n\r\n\tremoveData: function( elem, name ) {\r\n\t\treturn internalRemoveData( elem, name );\r\n\t},\r\n\r\n\t// For internal use only.\r\n\t_data: function( elem, name, data ) {\r\n\t\treturn internalData( elem, name, data, true );\r\n\t},\r\n\r\n\t_removeData: function( elem, name ) {\r\n\t\treturn internalRemoveData( elem, name, true );\r\n\t},\r\n\r\n\t// A method for determining if a DOM node can handle the data expando\r\n\tacceptData: function( elem ) {\r\n\t\t// Do not set data on non-element because it will not be cleared (#8335).\r\n\t\tif ( elem.nodeType && elem.nodeType !== 1 && elem.nodeType !== 9 ) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tvar noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ];\r\n\r\n\t\t// nodes accept data unless otherwise specified; rejection can be conditional\r\n\t\treturn !noData || noData !== true && elem.getAttribute(\"classid\") === noData;\r\n\t}\r\n});\r\n\r\njQuery.fn.extend({\r\n\tdata: function( key, value ) {\r\n\t\tvar attrs, name,\r\n\t\t\tdata = null,\r\n\t\t\ti = 0,\r\n\t\t\telem = this[0];\r\n\r\n\t\t// Special expections of .data basically thwart jQuery.access,\r\n\t\t// so implement the relevant behavior ourselves\r\n\r\n\t\t// Gets all values\r\n\t\tif ( key === undefined ) {\r\n\t\t\tif ( this.length ) {\r\n\t\t\t\tdata = jQuery.data( elem );\r\n\r\n\t\t\t\tif ( elem.nodeType === 1 && !jQuery._data( elem, \"parsedAttrs\" ) ) {\r\n\t\t\t\t\tattrs = elem.attributes;\r\n\t\t\t\t\tfor ( ; i < attrs.length; i++ ) {\r\n\t\t\t\t\t\tname = attrs[i].name;\r\n\r\n\t\t\t\t\t\tif ( name.indexOf(\"data-\") === 0 ) {\r\n\t\t\t\t\t\t\tname = jQuery.camelCase( name.slice(5) );\r\n\r\n\t\t\t\t\t\t\tdataAttr( elem, name, data[ name ] );\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\tjQuery._data( elem, \"parsedAttrs\", true );\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\treturn data;\r\n\t\t}\r\n\r\n\t\t// Sets multiple values\r\n\t\tif ( typeof key === \"object\" ) {\r\n\t\t\treturn this.each(function() {\r\n\t\t\t\tjQuery.data( this, key );\r\n\t\t\t});\r\n\t\t}\r\n\r\n\t\treturn arguments.length > 1 ?\r\n\r\n\t\t\t// Sets one value\r\n\t\t\tthis.each(function() {\r\n\t\t\t\tjQuery.data( this, key, value );\r\n\t\t\t}) :\r\n\r\n\t\t\t// Gets one value\r\n\t\t\t// Try to fetch any internally stored data first\r\n\t\t\telem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : null;\r\n\t},\r\n\r\n\tremoveData: function( key ) {\r\n\t\treturn this.each(function() {\r\n\t\t\tjQuery.removeData( this, key );\r\n\t\t});\r\n\t}\r\n});\r\n\r\nfunction dataAttr( elem, key, data ) {\r\n\t// If nothing was found internally, try to fetch any\r\n\t// data from the HTML5 data-* attribute\r\n\tif ( data === undefined && elem.nodeType === 1 ) {\r\n\r\n\t\tvar name = \"data-\" + key.replace( rmultiDash, \"-$1\" ).toLowerCase();\r\n\r\n\t\tdata = elem.getAttribute( name );\r\n\r\n\t\tif ( typeof data === \"string\" ) {\r\n\t\t\ttry {\r\n\t\t\t\tdata = data === \"true\" ? true :\r\n\t\t\t\t\tdata === \"false\" ? false :\r\n\t\t\t\t\tdata === \"null\" ? null :\r\n\t\t\t\t\t// Only convert to a number if it doesn't change the string\r\n\t\t\t\t\t+data + \"\" === data ? +data :\r\n\t\t\t\t\trbrace.test( data ) ? jQuery.parseJSON( data ) :\r\n\t\t\t\t\t\tdata;\r\n\t\t\t} catch( e ) {}\r\n\r\n\t\t\t// Make sure we set the data so it isn't changed later\r\n\t\t\tjQuery.data( elem, key, data );\r\n\r\n\t\t} else {\r\n\t\t\tdata = undefined;\r\n\t\t}\r\n\t}\r\n\r\n\treturn data;\r\n}\r\n\r\n// checks a cache object for emptiness\r\nfunction isEmptyDataObject( obj ) {\r\n\tvar name;\r\n\tfor ( name in obj ) {\r\n\r\n\t\t// if the public data object is empty, the private is still empty\r\n\t\tif ( name === \"data\" && jQuery.isEmptyObject( obj[name] ) ) {\r\n\t\t\tcontinue;\r\n\t\t}\r\n\t\tif ( name !== \"toJSON\" ) {\r\n\t\t\treturn false;\r\n\t\t}\r\n\t}\r\n\r\n\treturn true;\r\n}\r\njQuery.extend({\r\n\tqueue: function( elem, type, data ) {\r\n\t\tvar queue;\r\n\r\n\t\tif ( elem ) {\r\n\t\t\ttype = ( type || \"fx\" ) + \"queue\";\r\n\t\t\tqueue = jQuery._data( elem, type );\r\n\r\n\t\t\t// Speed up dequeue by getting out quickly if this is just a lookup\r\n\t\t\tif ( data ) {\r\n\t\t\t\tif ( !queue || jQuery.isArray(data) ) {\r\n\t\t\t\t\tqueue = jQuery._data( elem, type, jQuery.makeArray(data) );\r\n\t\t\t\t} else {\r\n\t\t\t\t\tqueue.push( data );\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\treturn queue || [];\r\n\t\t}\r\n\t},\r\n\r\n\tdequeue: function( elem, type ) {\r\n\t\ttype = type || \"fx\";\r\n\r\n\t\tvar queue = jQuery.queue( elem, type ),\r\n\t\t\tstartLength = queue.length,\r\n\t\t\tfn = queue.shift(),\r\n\t\t\thooks = jQuery._queueHooks( elem, type ),\r\n\t\t\tnext = function() {\r\n\t\t\t\tjQuery.dequeue( elem, type );\r\n\t\t\t};\r\n\r\n\t\t// If the fx queue is dequeued, always remove the progress sentinel\r\n\t\tif ( fn === \"inprogress\" ) {\r\n\t\t\tfn = queue.shift();\r\n\t\t\tstartLength--;\r\n\t\t}\r\n\r\n\t\tif ( fn ) {\r\n\r\n\t\t\t// Add a progress sentinel to prevent the fx queue from being\r\n\t\t\t// automatically dequeued\r\n\t\t\tif ( type === \"fx\" ) {\r\n\t\t\t\tqueue.unshift( \"inprogress\" );\r\n\t\t\t}\r\n\r\n\t\t\t// clear up the last queue stop function\r\n\t\t\tdelete hooks.stop;\r\n\t\t\tfn.call( elem, next, hooks );\r\n\t\t}\r\n\r\n\t\tif ( !startLength && hooks ) {\r\n\t\t\thooks.empty.fire();\r\n\t\t}\r\n\t},\r\n\r\n\t// not intended for public consumption - generates a queueHooks object, or returns the current one\r\n\t_queueHooks: function( elem, type ) {\r\n\t\tvar key = type + \"queueHooks\";\r\n\t\treturn jQuery._data( elem, key ) || jQuery._data( elem, key, {\r\n\t\t\tempty: jQuery.Callbacks(\"once memory\").add(function() {\r\n\t\t\t\tjQuery._removeData( elem, type + \"queue\" );\r\n\t\t\t\tjQuery._removeData( elem, key );\r\n\t\t\t})\r\n\t\t});\r\n\t}\r\n});\r\n\r\njQuery.fn.extend({\r\n\tqueue: function( type, data ) {\r\n\t\tvar setter = 2;\r\n\r\n\t\tif ( typeof type !== \"string\" ) {\r\n\t\t\tdata = type;\r\n\t\t\ttype = \"fx\";\r\n\t\t\tsetter--;\r\n\t\t}\r\n\r\n\t\tif ( arguments.length < setter ) {\r\n\t\t\treturn jQuery.queue( this[0], type );\r\n\t\t}\r\n\r\n\t\treturn data === undefined ?\r\n\t\t\tthis :\r\n\t\t\tthis.each(function() {\r\n\t\t\t\tvar queue = jQuery.queue( this, type, data );\r\n\r\n\t\t\t\t// ensure a hooks for this queue\r\n\t\t\t\tjQuery._queueHooks( this, type );\r\n\r\n\t\t\t\tif ( type === \"fx\" && queue[0] !== \"inprogress\" ) {\r\n\t\t\t\t\tjQuery.dequeue( this, type );\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t},\r\n\tdequeue: function( type ) {\r\n\t\treturn this.each(function() {\r\n\t\t\tjQuery.dequeue( this, type );\r\n\t\t});\r\n\t},\r\n\t// Based off of the plugin by Clint Helfers, with permission.\r\n\t// http://blindsignals.com/index.php/2009/07/jquery-delay/\r\n\tdelay: function( time, type ) {\r\n\t\ttime = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;\r\n\t\ttype = type || \"fx\";\r\n\r\n\t\treturn this.queue( type, function( next, hooks ) {\r\n\t\t\tvar timeout = setTimeout( next, time );\r\n\t\t\thooks.stop = function() {\r\n\t\t\t\tclearTimeout( timeout );\r\n\t\t\t};\r\n\t\t});\r\n\t},\r\n\tclearQueue: function( type ) {\r\n\t\treturn this.queue( type || \"fx\", [] );\r\n\t},\r\n\t// Get a promise resolved when queues of a certain type\r\n\t// are emptied (fx is the type by default)\r\n\tpromise: function( type, obj ) {\r\n\t\tvar tmp,\r\n\t\t\tcount = 1,\r\n\t\t\tdefer = jQuery.Deferred(),\r\n\t\t\telements = this,\r\n\t\t\ti = this.length,\r\n\t\t\tresolve = function() {\r\n\t\t\t\tif ( !( --count ) ) {\r\n\t\t\t\t\tdefer.resolveWith( elements, [ elements ] );\r\n\t\t\t\t}\r\n\t\t\t};\r\n\r\n\t\tif ( typeof type !== \"string\" ) {\r\n\t\t\tobj = type;\r\n\t\t\ttype = undefined;\r\n\t\t}\r\n\t\ttype = type || \"fx\";\r\n\r\n\t\twhile( i-- ) {\r\n\t\t\ttmp = jQuery._data( elements[ i ], type + \"queueHooks\" );\r\n\t\t\tif ( tmp && tmp.empty ) {\r\n\t\t\t\tcount++;\r\n\t\t\t\ttmp.empty.add( resolve );\r\n\t\t\t}\r\n\t\t}\r\n\t\tresolve();\r\n\t\treturn defer.promise( obj );\r\n\t}\r\n});\r\nvar nodeHook, boolHook,\r\n\trclass = /[\\t\\r\\n\\f]/g,\r\n\trreturn = /\\r/g,\r\n\trfocusable = /^(?:input|select|textarea|button|object)$/i,\r\n\trclickable = /^(?:a|area)$/i,\r\n\truseDefault = /^(?:checked|selected)$/i,\r\n\tgetSetAttribute = jQuery.support.getSetAttribute,\r\n\tgetSetInput = jQuery.support.input;\r\n\r\njQuery.fn.extend({\r\n\tattr: function( name, value ) {\r\n\t\treturn jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );\r\n\t},\r\n\r\n\tremoveAttr: function( name ) {\r\n\t\treturn this.each(function() {\r\n\t\t\tjQuery.removeAttr( this, name );\r\n\t\t});\r\n\t},\r\n\r\n\tprop: function( name, value ) {\r\n\t\treturn jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );\r\n\t},\r\n\r\n\tremoveProp: function( name ) {\r\n\t\tname = jQuery.propFix[ name ] || name;\r\n\t\treturn this.each(function() {\r\n\t\t\t// try/catch handles cases where IE balks (such as removing a property on window)\r\n\t\t\ttry {\r\n\t\t\t\tthis[ name ] = undefined;\r\n\t\t\t\tdelete this[ name ];\r\n\t\t\t} catch( e ) {}\r\n\t\t});\r\n\t},\r\n\r\n\taddClass: function( value ) {\r\n\t\tvar classes, elem, cur, clazz, j,\r\n\t\t\ti = 0,\r\n\t\t\tlen = this.length,\r\n\t\t\tproceed = typeof value === \"string\" && value;\r\n\r\n\t\tif ( jQuery.isFunction( value ) ) {\r\n\t\t\treturn this.each(function( j ) {\r\n\t\t\t\tjQuery( this ).addClass( value.call( this, j, this.className ) );\r\n\t\t\t});\r\n\t\t}\r\n\r\n\t\tif ( proceed ) {\r\n\t\t\t// The disjunction here is for better compressibility (see removeClass)\r\n\t\t\tclasses = ( value || \"\" ).match( core_rnotwhite ) || [];\r\n\r\n\t\t\tfor ( ; i < len; i++ ) {\r\n\t\t\t\telem = this[ i ];\r\n\t\t\t\tcur = elem.nodeType === 1 && ( elem.className ?\r\n\t\t\t\t\t( \" \" + elem.className + \" \" ).replace( rclass, \" \" ) :\r\n\t\t\t\t\t\" \"\r\n\t\t\t\t);\r\n\r\n\t\t\t\tif ( cur ) {\r\n\t\t\t\t\tj = 0;\r\n\t\t\t\t\twhile ( (clazz = classes[j++]) ) {\r\n\t\t\t\t\t\tif ( cur.indexOf( \" \" + clazz + \" \" ) < 0 ) {\r\n\t\t\t\t\t\t\tcur += clazz + \" \";\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\telem.className = jQuery.trim( cur );\r\n\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn this;\r\n\t},\r\n\r\n\tremoveClass: function( value ) {\r\n\t\tvar classes, elem, cur, clazz, j,\r\n\t\t\ti = 0,\r\n\t\t\tlen = this.length,\r\n\t\t\tproceed = arguments.length === 0 || typeof value === \"string\" && value;\r\n\r\n\t\tif ( jQuery.isFunction( value ) ) {\r\n\t\t\treturn this.each(function( j ) {\r\n\t\t\t\tjQuery( this ).removeClass( value.call( this, j, this.className ) );\r\n\t\t\t});\r\n\t\t}\r\n\t\tif ( proceed ) {\r\n\t\t\tclasses = ( value || \"\" ).match( core_rnotwhite ) || [];\r\n\r\n\t\t\tfor ( ; i < len; i++ ) {\r\n\t\t\t\telem = this[ i ];\r\n\t\t\t\t// This expression is here for better compressibility (see addClass)\r\n\t\t\t\tcur = elem.nodeType === 1 && ( elem.className ?\r\n\t\t\t\t\t( \" \" + elem.className + \" \" ).replace( rclass, \" \" ) :\r\n\t\t\t\t\t\"\"\r\n\t\t\t\t);\r\n\r\n\t\t\t\tif ( cur ) {\r\n\t\t\t\t\tj = 0;\r\n\t\t\t\t\twhile ( (clazz = classes[j++]) ) {\r\n\t\t\t\t\t\t// Remove *all* instances\r\n\t\t\t\t\t\twhile ( cur.indexOf( \" \" + clazz + \" \" ) >= 0 ) {\r\n\t\t\t\t\t\t\tcur = cur.replace( \" \" + clazz + \" \", \" \" );\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\telem.className = value ? jQuery.trim( cur ) : \"\";\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn this;\r\n\t},\r\n\r\n\ttoggleClass: function( value, stateVal ) {\r\n\t\tvar type = typeof value;\r\n\r\n\t\tif ( typeof stateVal === \"boolean\" && type === \"string\" ) {\r\n\t\t\treturn stateVal ? this.addClass( value ) : this.removeClass( value );\r\n\t\t}\r\n\r\n\t\tif ( jQuery.isFunction( value ) ) {\r\n\t\t\treturn this.each(function( i ) {\r\n\t\t\t\tjQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );\r\n\t\t\t});\r\n\t\t}\r\n\r\n\t\treturn this.each(function() {\r\n\t\t\tif ( type === \"string\" ) {\r\n\t\t\t\t// toggle individual class names\r\n\t\t\t\tvar className,\r\n\t\t\t\t\ti = 0,\r\n\t\t\t\t\tself = jQuery( this ),\r\n\t\t\t\t\tclassNames = value.match( core_rnotwhite ) || [];\r\n\r\n\t\t\t\twhile ( (className = classNames[ i++ ]) ) {\r\n\t\t\t\t\t// check each className given, space separated list\r\n\t\t\t\t\tif ( self.hasClass( className ) ) {\r\n\t\t\t\t\t\tself.removeClass( className );\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\tself.addClass( className );\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t// Toggle whole class name\r\n\t\t\t} else if ( type === core_strundefined || type === \"boolean\" ) {\r\n\t\t\t\tif ( this.className ) {\r\n\t\t\t\t\t// store className if set\r\n\t\t\t\t\tjQuery._data( this, \"__className__\", this.className );\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// If the element has a class name or if we're passed \"false\",\r\n\t\t\t\t// then remove the whole classname (if there was one, the above saved it).\r\n\t\t\t\t// Otherwise bring back whatever was previously saved (if anything),\r\n\t\t\t\t// falling back to the empty string if nothing was stored.\r\n\t\t\t\tthis.className = this.className || value === false ? \"\" : jQuery._data( this, \"__className__\" ) || \"\";\r\n\t\t\t}\r\n\t\t});\r\n\t},\r\n\r\n\thasClass: function( selector ) {\r\n\t\tvar className = \" \" + selector + \" \",\r\n\t\t\ti = 0,\r\n\t\t\tl = this.length;\r\n\t\tfor ( ; i < l; i++ ) {\r\n\t\t\tif ( this[i].nodeType === 1 && (\" \" + this[i].className + \" \").replace(rclass, \" \").indexOf( className ) >= 0 ) {\r\n\t\t\t\treturn true;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn false;\r\n\t},\r\n\r\n\tval: function( value ) {\r\n\t\tvar ret, hooks, isFunction,\r\n\t\t\telem = this[0];\r\n\r\n\t\tif ( !arguments.length ) {\r\n\t\t\tif ( elem ) {\r\n\t\t\t\thooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];\r\n\r\n\t\t\t\tif ( hooks && \"get\" in hooks && (ret = hooks.get( elem, \"value\" )) !== undefined ) {\r\n\t\t\t\t\treturn ret;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tret = elem.value;\r\n\r\n\t\t\t\treturn typeof ret === \"string\" ?\r\n\t\t\t\t\t// handle most common string cases\r\n\t\t\t\t\tret.replace(rreturn, \"\") :\r\n\t\t\t\t\t// handle cases where value is null/undef or number\r\n\t\t\t\t\tret == null ? \"\" : ret;\r\n\t\t\t}\r\n\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tisFunction = jQuery.isFunction( value );\r\n\r\n\t\treturn this.each(function( i ) {\r\n\t\t\tvar val;\r\n\r\n\t\t\tif ( this.nodeType !== 1 ) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\tif ( isFunction ) {\r\n\t\t\t\tval = value.call( this, i, jQuery( this ).val() );\r\n\t\t\t} else {\r\n\t\t\t\tval = value;\r\n\t\t\t}\r\n\r\n\t\t\t// Treat null/undefined as \"\"; convert numbers to string\r\n\t\t\tif ( val == null ) {\r\n\t\t\t\tval = \"\";\r\n\t\t\t} else if ( typeof val === \"number\" ) {\r\n\t\t\t\tval += \"\";\r\n\t\t\t} else if ( jQuery.isArray( val ) ) {\r\n\t\t\t\tval = jQuery.map(val, function ( value ) {\r\n\t\t\t\t\treturn value == null ? \"\" : value + \"\";\r\n\t\t\t\t});\r\n\t\t\t}\r\n\r\n\t\t\thooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];\r\n\r\n\t\t\t// If set returns undefined, fall back to normal setting\r\n\t\t\tif ( !hooks || !(\"set\" in hooks) || hooks.set( this, val, \"value\" ) === undefined ) {\r\n\t\t\t\tthis.value = val;\r\n\t\t\t}\r\n\t\t});\r\n\t}\r\n});\r\n\r\njQuery.extend({\r\n\tvalHooks: {\r\n\t\toption: {\r\n\t\t\tget: function( elem ) {\r\n\t\t\t\t// Use proper attribute retrieval(#6932, #12072)\r\n\t\t\t\tvar val = jQuery.find.attr( elem, \"value\" );\r\n\t\t\t\treturn val != null ?\r\n\t\t\t\t\tval :\r\n\t\t\t\t\telem.text;\r\n\t\t\t}\r\n\t\t},\r\n\t\tselect: {\r\n\t\t\tget: function( elem ) {\r\n\t\t\t\tvar value, option,\r\n\t\t\t\t\toptions = elem.options,\r\n\t\t\t\t\tindex = elem.selectedIndex,\r\n\t\t\t\t\tone = elem.type === \"select-one\" || index < 0,\r\n\t\t\t\t\tvalues = one ? null : [],\r\n\t\t\t\t\tmax = one ? index + 1 : options.length,\r\n\t\t\t\t\ti = index < 0 ?\r\n\t\t\t\t\t\tmax :\r\n\t\t\t\t\t\tone ? index : 0;\r\n\r\n\t\t\t\t// Loop through all the selected options\r\n\t\t\t\tfor ( ; i < max; i++ ) {\r\n\t\t\t\t\toption = options[ i ];\r\n\r\n\t\t\t\t\t// oldIE doesn't update selected after form reset (#2551)\r\n\t\t\t\t\tif ( ( option.selected || i === index ) &&\r\n\t\t\t\t\t\t\t// Don't return options that are disabled or in a disabled optgroup\r\n\t\t\t\t\t\t\t( jQuery.support.optDisabled ? !option.disabled : option.getAttribute(\"disabled\") === null ) &&\r\n\t\t\t\t\t\t\t( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, \"optgroup\" ) ) ) {\r\n\r\n\t\t\t\t\t\t// Get the specific value for the option\r\n\t\t\t\t\t\tvalue = jQuery( option ).val();\r\n\r\n\t\t\t\t\t\t// We don't need an array for one selects\r\n\t\t\t\t\t\tif ( one ) {\r\n\t\t\t\t\t\t\treturn value;\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t// Multi-Selects return an array\r\n\t\t\t\t\t\tvalues.push( value );\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\treturn values;\r\n\t\t\t},\r\n\r\n\t\t\tset: function( elem, value ) {\r\n\t\t\t\tvar optionSet, option,\r\n\t\t\t\t\toptions = elem.options,\r\n\t\t\t\t\tvalues = jQuery.makeArray( value ),\r\n\t\t\t\t\ti = options.length;\r\n\r\n\t\t\t\twhile ( i-- ) {\r\n\t\t\t\t\toption = options[ i ];\r\n\t\t\t\t\tif ( (option.selected = jQuery.inArray( jQuery(option).val(), values ) >= 0) ) {\r\n\t\t\t\t\t\toptionSet = true;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// force browsers to behave consistently when non-matching value is set\r\n\t\t\t\tif ( !optionSet ) {\r\n\t\t\t\t\telem.selectedIndex = -1;\r\n\t\t\t\t}\r\n\t\t\t\treturn values;\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\r\n\tattr: function( elem, name, value ) {\r\n\t\tvar hooks, ret,\r\n\t\t\tnType = elem.nodeType;\r\n\r\n\t\t// don't get/set attributes on text, comment and attribute nodes\r\n\t\tif ( !elem || nType === 3 || nType === 8 || nType === 2 ) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t// Fallback to prop when attributes are not supported\r\n\t\tif ( typeof elem.getAttribute === core_strundefined ) {\r\n\t\t\treturn jQuery.prop( elem, name, value );\r\n\t\t}\r\n\r\n\t\t// All attributes are lowercase\r\n\t\t// Grab necessary hook if one is defined\r\n\t\tif ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {\r\n\t\t\tname = name.toLowerCase();\r\n\t\t\thooks = jQuery.attrHooks[ name ] ||\r\n\t\t\t\t( jQuery.expr.match.bool.test( name ) ? boolHook : nodeHook );\r\n\t\t}\r\n\r\n\t\tif ( value !== undefined ) {\r\n\r\n\t\t\tif ( value === null ) {\r\n\t\t\t\tjQuery.removeAttr( elem, name );\r\n\r\n\t\t\t} else if ( hooks && \"set\" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {\r\n\t\t\t\treturn ret;\r\n\r\n\t\t\t} else {\r\n\t\t\t\telem.setAttribute( name, value + \"\" );\r\n\t\t\t\treturn value;\r\n\t\t\t}\r\n\r\n\t\t} else if ( hooks && \"get\" in hooks && (ret = hooks.get( elem, name )) !== null ) {\r\n\t\t\treturn ret;\r\n\r\n\t\t} else {\r\n\t\t\tret = jQuery.find.attr( elem, name );\r\n\r\n\t\t\t// Non-existent attributes return null, we normalize to undefined\r\n\t\t\treturn ret == null ?\r\n\t\t\t\tundefined :\r\n\t\t\t\tret;\r\n\t\t}\r\n\t},\r\n\r\n\tremoveAttr: function( elem, value ) {\r\n\t\tvar name, propName,\r\n\t\t\ti = 0,\r\n\t\t\tattrNames = value && value.match( core_rnotwhite );\r\n\r\n\t\tif ( attrNames && elem.nodeType === 1 ) {\r\n\t\t\twhile ( (name = attrNames[i++]) ) {\r\n\t\t\t\tpropName = jQuery.propFix[ name ] || name;\r\n\r\n\t\t\t\t// Boolean attributes get special treatment (#10870)\r\n\t\t\t\tif ( jQuery.expr.match.bool.test( name ) ) {\r\n\t\t\t\t\t// Set corresponding property to false\r\n\t\t\t\t\tif ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) {\r\n\t\t\t\t\t\telem[ propName ] = false;\r\n\t\t\t\t\t// Support: IE<9\r\n\t\t\t\t\t// Also clear defaultChecked/defaultSelected (if appropriate)\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\telem[ jQuery.camelCase( \"default-\" + name ) ] =\r\n\t\t\t\t\t\t\telem[ propName ] = false;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t// See #9699 for explanation of this approach (setting first, then removal)\r\n\t\t\t\t} else {\r\n\t\t\t\t\tjQuery.attr( elem, name, \"\" );\r\n\t\t\t\t}\r\n\r\n\t\t\t\telem.removeAttribute( getSetAttribute ? name : propName );\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\r\n\tattrHooks: {\r\n\t\ttype: {\r\n\t\t\tset: function( elem, value ) {\r\n\t\t\t\tif ( !jQuery.support.radioValue && value === \"radio\" && jQuery.nodeName(elem, \"input\") ) {\r\n\t\t\t\t\t// Setting the type on a radio button after the value resets the value in IE6-9\r\n\t\t\t\t\t// Reset value to default in case type is set after value during creation\r\n\t\t\t\t\tvar val = elem.value;\r\n\t\t\t\t\telem.setAttribute( \"type\", value );\r\n\t\t\t\t\tif ( val ) {\r\n\t\t\t\t\t\telem.value = val;\r\n\t\t\t\t\t}\r\n\t\t\t\t\treturn value;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\r\n\tpropFix: {\r\n\t\t\"for\": \"htmlFor\",\r\n\t\t\"class\": \"className\"\r\n\t},\r\n\r\n\tprop: function( elem, name, value ) {\r\n\t\tvar ret, hooks, notxml,\r\n\t\t\tnType = elem.nodeType;\r\n\r\n\t\t// don't get/set properties on text, comment and attribute nodes\r\n\t\tif ( !elem || nType === 3 || nType === 8 || nType === 2 ) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tnotxml = nType !== 1 || !jQuery.isXMLDoc( elem );\r\n\r\n\t\tif ( notxml ) {\r\n\t\t\t// Fix name and attach hooks\r\n\t\t\tname = jQuery.propFix[ name ] || name;\r\n\t\t\thooks = jQuery.propHooks[ name ];\r\n\t\t}\r\n\r\n\t\tif ( value !== undefined ) {\r\n\t\t\treturn hooks && \"set\" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ?\r\n\t\t\t\tret :\r\n\t\t\t\t( elem[ name ] = value );\r\n\r\n\t\t} else {\r\n\t\t\treturn hooks && \"get\" in hooks && (ret = hooks.get( elem, name )) !== null ?\r\n\t\t\t\tret :\r\n\t\t\t\telem[ name ];\r\n\t\t}\r\n\t},\r\n\r\n\tpropHooks: {\r\n\t\ttabIndex: {\r\n\t\t\tget: function( elem ) {\r\n\t\t\t\t// elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set\r\n\t\t\t\t// http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/\r\n\t\t\t\t// Use proper attribute retrieval(#12072)\r\n\t\t\t\tvar tabindex = jQuery.find.attr( elem, \"tabindex\" );\r\n\r\n\t\t\t\treturn tabindex ?\r\n\t\t\t\t\tparseInt( tabindex, 10 ) :\r\n\t\t\t\t\trfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?\r\n\t\t\t\t\t\t0 :\r\n\t\t\t\t\t\t-1;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n});\r\n\r\n// Hooks for boolean attributes\r\nboolHook = {\r\n\tset: function( elem, value, name ) {\r\n\t\tif ( value === false ) {\r\n\t\t\t// Remove boolean attributes when set to false\r\n\t\t\tjQuery.removeAttr( elem, name );\r\n\t\t} else if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) {\r\n\t\t\t// IE<8 needs the *property* name\r\n\t\t\telem.setAttribute( !getSetAttribute && jQuery.propFix[ name ] || name, name );\r\n\r\n\t\t// Use defaultChecked and defaultSelected for oldIE\r\n\t\t} else {\r\n\t\t\telem[ jQuery.camelCase( \"default-\" + name ) ] = elem[ name ] = true;\r\n\t\t}\r\n\r\n\t\treturn name;\r\n\t}\r\n};\r\njQuery.each( jQuery.expr.match.bool.source.match( /\\w+/g ), function( i, name ) {\r\n\tvar getter = jQuery.expr.attrHandle[ name ] || jQuery.find.attr;\r\n\r\n\tjQuery.expr.attrHandle[ name ] = getSetInput && getSetAttribute || !ruseDefault.test( name ) ?\r\n\t\tfunction( elem, name, isXML ) {\r\n\t\t\tvar fn = jQuery.expr.attrHandle[ name ],\r\n\t\t\t\tret = isXML ?\r\n\t\t\t\t\tundefined :\r\n\t\t\t\t\t/* jshint eqeqeq: false */\r\n\t\t\t\t\t(jQuery.expr.attrHandle[ name ] = undefined) !=\r\n\t\t\t\t\t\tgetter( elem, name, isXML ) ?\r\n\r\n\t\t\t\t\t\tname.toLowerCase() :\r\n\t\t\t\t\t\tnull;\r\n\t\t\tjQuery.expr.attrHandle[ name ] = fn;\r\n\t\t\treturn ret;\r\n\t\t} :\r\n\t\tfunction( elem, name, isXML ) {\r\n\t\t\treturn isXML ?\r\n\t\t\t\tundefined :\r\n\t\t\t\telem[ jQuery.camelCase( \"default-\" + name ) ] ?\r\n\t\t\t\t\tname.toLowerCase() :\r\n\t\t\t\t\tnull;\r\n\t\t};\r\n});\r\n\r\n// fix oldIE attroperties\r\nif ( !getSetInput || !getSetAttribute ) {\r\n\tjQuery.attrHooks.value = {\r\n\t\tset: function( elem, value, name ) {\r\n\t\t\tif ( jQuery.nodeName( elem, \"input\" ) ) {\r\n\t\t\t\t// Does not return so that setAttribute is also used\r\n\t\t\t\telem.defaultValue = value;\r\n\t\t\t} else {\r\n\t\t\t\t// Use nodeHook if defined (#1954); otherwise setAttribute is fine\r\n\t\t\t\treturn nodeHook && nodeHook.set( elem, value, name );\r\n\t\t\t}\r\n\t\t}\r\n\t};\r\n}\r\n\r\n// IE6/7 do not support getting/setting some attributes with get/setAttribute\r\nif ( !getSetAttribute ) {\r\n\r\n\t// Use this for any attribute in IE6/7\r\n\t// This fixes almost every IE6/7 issue\r\n\tnodeHook = {\r\n\t\tset: function( elem, value, name ) {\r\n\t\t\t// Set the existing or create a new attribute node\r\n\t\t\tvar ret = elem.getAttributeNode( name );\r\n\t\t\tif ( !ret ) {\r\n\t\t\t\telem.setAttributeNode(\r\n\t\t\t\t\t(ret = elem.ownerDocument.createAttribute( name ))\r\n\t\t\t\t);\r\n\t\t\t}\r\n\r\n\t\t\tret.value = value += \"\";\r\n\r\n\t\t\t// Break association with cloned elements by also using setAttribute (#9646)\r\n\t\t\treturn name === \"value\" || value === elem.getAttribute( name ) ?\r\n\t\t\t\tvalue :\r\n\t\t\t\tundefined;\r\n\t\t}\r\n\t};\r\n\tjQuery.expr.attrHandle.id = jQuery.expr.attrHandle.name = jQuery.expr.attrHandle.coords =\r\n\t\t// Some attributes are constructed with empty-string values when not defined\r\n\t\tfunction( elem, name, isXML ) {\r\n\t\t\tvar ret;\r\n\t\t\treturn isXML ?\r\n\t\t\t\tundefined :\r\n\t\t\t\t(ret = elem.getAttributeNode( name )) && ret.value !== \"\" ?\r\n\t\t\t\t\tret.value :\r\n\t\t\t\t\tnull;\r\n\t\t};\r\n\tjQuery.valHooks.button = {\r\n\t\tget: function( elem, name ) {\r\n\t\t\tvar ret = elem.getAttributeNode( name );\r\n\t\t\treturn ret && ret.specified ?\r\n\t\t\t\tret.value :\r\n\t\t\t\tundefined;\r\n\t\t},\r\n\t\tset: nodeHook.set\r\n\t};\r\n\r\n\t// Set contenteditable to false on removals(#10429)\r\n\t// Setting to empty string throws an error as an invalid value\r\n\tjQuery.attrHooks.contenteditable = {\r\n\t\tset: function( elem, value, name ) {\r\n\t\t\tnodeHook.set( elem, value === \"\" ? false : value, name );\r\n\t\t}\r\n\t};\r\n\r\n\t// Set width and height to auto instead of 0 on empty string( Bug #8150 )\r\n\t// This is for removals\r\n\tjQuery.each([ \"width\", \"height\" ], function( i, name ) {\r\n\t\tjQuery.attrHooks[ name ] = {\r\n\t\t\tset: function( elem, value ) {\r\n\t\t\t\tif ( value === \"\" ) {\r\n\t\t\t\t\telem.setAttribute( name, \"auto\" );\r\n\t\t\t\t\treturn value;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t};\r\n\t});\r\n}\r\n\r\n\r\n// Some attributes require a special call on IE\r\n// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx\r\nif ( !jQuery.support.hrefNormalized ) {\r\n\t// href/src property should get the full normalized URL (#10299/#12915)\r\n\tjQuery.each([ \"href\", \"src\" ], function( i, name ) {\r\n\t\tjQuery.propHooks[ name ] = {\r\n\t\t\tget: function( elem ) {\r\n\t\t\t\treturn elem.getAttribute( name, 4 );\r\n\t\t\t}\r\n\t\t};\r\n\t});\r\n}\r\n\r\nif ( !jQuery.support.style ) {\r\n\tjQuery.attrHooks.style = {\r\n\t\tget: function( elem ) {\r\n\t\t\t// Return undefined in the case of empty string\r\n\t\t\t// Note: IE uppercases css property names, but if we were to .toLowerCase()\r\n\t\t\t// .cssText, that would destroy case senstitivity in URL's, like in \"background\"\r\n\t\t\treturn elem.style.cssText || undefined;\r\n\t\t},\r\n\t\tset: function( elem, value ) {\r\n\t\t\treturn ( elem.style.cssText = value + \"\" );\r\n\t\t}\r\n\t};\r\n}\r\n\r\n// Safari mis-reports the default selected property of an option\r\n// Accessing the parent's selectedIndex property fixes it\r\nif ( !jQuery.support.optSelected ) {\r\n\tjQuery.propHooks.selected = {\r\n\t\tget: function( elem ) {\r\n\t\t\tvar parent = elem.parentNode;\r\n\r\n\t\t\tif ( parent ) {\r\n\t\t\t\tparent.selectedIndex;\r\n\r\n\t\t\t\t// Make sure that it also works with optgroups, see #5701\r\n\t\t\t\tif ( parent.parentNode ) {\r\n\t\t\t\t\tparent.parentNode.selectedIndex;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t\treturn null;\r\n\t\t}\r\n\t};\r\n}\r\n\r\njQuery.each([\r\n\t\"tabIndex\",\r\n\t\"readOnly\",\r\n\t\"maxLength\",\r\n\t\"cellSpacing\",\r\n\t\"cellPadding\",\r\n\t\"rowSpan\",\r\n\t\"colSpan\",\r\n\t\"useMap\",\r\n\t\"frameBorder\",\r\n\t\"contentEditable\"\r\n], function() {\r\n\tjQuery.propFix[ this.toLowerCase() ] = this;\r\n});\r\n\r\n// IE6/7 call enctype encoding\r\nif ( !jQuery.support.enctype ) {\r\n\tjQuery.propFix.enctype = \"encoding\";\r\n}\r\n\r\n// Radios and checkboxes getter/setter\r\njQuery.each([ \"radio\", \"checkbox\" ], function() {\r\n\tjQuery.valHooks[ this ] = {\r\n\t\tset: function( elem, value ) {\r\n\t\t\tif ( jQuery.isArray( value ) ) {\r\n\t\t\t\treturn ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );\r\n\t\t\t}\r\n\t\t}\r\n\t};\r\n\tif ( !jQuery.support.checkOn ) {\r\n\t\tjQuery.valHooks[ this ].get = function( elem ) {\r\n\t\t\t// Support: Webkit\r\n\t\t\t// \"\" is returned instead of \"on\" if a value isn't specified\r\n\t\t\treturn elem.getAttribute(\"value\") === null ? \"on\" : elem.value;\r\n\t\t};\r\n\t}\r\n});\r\nvar rformElems = /^(?:input|select|textarea)$/i,\r\n\trkeyEvent = /^key/,\r\n\trmouseEvent = /^(?:mouse|contextmenu)|click/,\r\n\trfocusMorph = /^(?:focusinfocus|focusoutblur)$/,\r\n\trtypenamespace = /^([^.]*)(?:\\.(.+)|)$/;\r\n\r\nfunction returnTrue() {\r\n\treturn true;\r\n}\r\n\r\nfunction returnFalse() {\r\n\treturn false;\r\n}\r\n\r\nfunction safeActiveElement() {\r\n\ttry {\r\n\t\treturn document.activeElement;\r\n\t} catch ( err ) { }\r\n}\r\n\r\n/*\r\n * Helper functions for managing events -- not part of the public interface.\r\n * Props to Dean Edwards' addEvent library for many of the ideas.\r\n */\r\njQuery.event = {\r\n\r\n\tglobal: {},\r\n\r\n\tadd: function( elem, types, handler, data, selector ) {\r\n\t\tvar tmp, events, t, handleObjIn,\r\n\t\t\tspecial, eventHandle, handleObj,\r\n\t\t\thandlers, type, namespaces, origType,\r\n\t\t\telemData = jQuery._data( elem );\r\n\r\n\t\t// Don't attach events to noData or text/comment nodes (but allow plain objects)\r\n\t\tif ( !elemData ) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t// Caller can pass in an object of custom data in lieu of the handler\r\n\t\tif ( handler.handler ) {\r\n\t\t\thandleObjIn = handler;\r\n\t\t\thandler = handleObjIn.handler;\r\n\t\t\tselector = handleObjIn.selector;\r\n\t\t}\r\n\r\n\t\t// Make sure that the handler has a unique ID, used to find/remove it later\r\n\t\tif ( !handler.guid ) {\r\n\t\t\thandler.guid = jQuery.guid++;\r\n\t\t}\r\n\r\n\t\t// Init the element's event structure and main handler, if this is the first\r\n\t\tif ( !(events = elemData.events) ) {\r\n\t\t\tevents = elemData.events = {};\r\n\t\t}\r\n\t\tif ( !(eventHandle = elemData.handle) ) {\r\n\t\t\teventHandle = elemData.handle = function( e ) {\r\n\t\t\t\t// Discard the second event of a jQuery.event.trigger() and\r\n\t\t\t\t// when an event is called after a page has unloaded\r\n\t\t\t\treturn typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ?\r\n\t\t\t\t\tjQuery.event.dispatch.apply( eventHandle.elem, arguments ) :\r\n\t\t\t\t\tundefined;\r\n\t\t\t};\r\n\t\t\t// Add elem as a property of the handle fn to prevent a memory leak with IE non-native events\r\n\t\t\teventHandle.elem = elem;\r\n\t\t}\r\n\r\n\t\t// Handle multiple events separated by a space\r\n\t\ttypes = ( types || \"\" ).match( core_rnotwhite ) || [\"\"];\r\n\t\tt = types.length;\r\n\t\twhile ( t-- ) {\r\n\t\t\ttmp = rtypenamespace.exec( types[t] ) || [];\r\n\t\t\ttype = origType = tmp[1];\r\n\t\t\tnamespaces = ( tmp[2] || \"\" ).split( \".\" ).sort();\r\n\r\n\t\t\t// There *must* be a type, no attaching namespace-only handlers\r\n\t\t\tif ( !type ) {\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\t// If event changes its type, use the special event handlers for the changed type\r\n\t\t\tspecial = jQuery.event.special[ type ] || {};\r\n\r\n\t\t\t// If selector defined, determine special event api type, otherwise given type\r\n\t\t\ttype = ( selector ? special.delegateType : special.bindType ) || type;\r\n\r\n\t\t\t// Update special based on newly reset type\r\n\t\t\tspecial = jQuery.event.special[ type ] || {};\r\n\r\n\t\t\t// handleObj is passed to all event handlers\r\n\t\t\thandleObj = jQuery.extend({\r\n\t\t\t\ttype: type,\r\n\t\t\t\torigType: origType,\r\n\t\t\t\tdata: data,\r\n\t\t\t\thandler: handler,\r\n\t\t\t\tguid: handler.guid,\r\n\t\t\t\tselector: selector,\r\n\t\t\t\tneedsContext: selector && jQuery.expr.match.needsContext.test( selector ),\r\n\t\t\t\tnamespace: namespaces.join(\".\")\r\n\t\t\t}, handleObjIn );\r\n\r\n\t\t\t// Init the event handler queue if we're the first\r\n\t\t\tif ( !(handlers = events[ type ]) ) {\r\n\t\t\t\thandlers = events[ type ] = [];\r\n\t\t\t\thandlers.delegateCount = 0;\r\n\r\n\t\t\t\t// Only use addEventListener/attachEvent if the special events handler returns false\r\n\t\t\t\tif ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {\r\n\t\t\t\t\t// Bind the global event handler to the element\r\n\t\t\t\t\tif ( elem.addEventListener ) {\r\n\t\t\t\t\t\telem.addEventListener( type, eventHandle, false );\r\n\r\n\t\t\t\t\t} else if ( elem.attachEvent ) {\r\n\t\t\t\t\t\telem.attachEvent( \"on\" + type, eventHandle );\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tif ( special.add ) {\r\n\t\t\t\tspecial.add.call( elem, handleObj );\r\n\r\n\t\t\t\tif ( !handleObj.handler.guid ) {\r\n\t\t\t\t\thandleObj.handler.guid = handler.guid;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// Add to the element's handler list, delegates in front\r\n\t\t\tif ( selector ) {\r\n\t\t\t\thandlers.splice( handlers.delegateCount++, 0, handleObj );\r\n\t\t\t} else {\r\n\t\t\t\thandlers.push( handleObj );\r\n\t\t\t}\r\n\r\n\t\t\t// Keep track of which events have ever been used, for event optimization\r\n\t\t\tjQuery.event.global[ type ] = true;\r\n\t\t}\r\n\r\n\t\t// Nullify elem to prevent memory leaks in IE\r\n\t\telem = null;\r\n\t},\r\n\r\n\t// Detach an event or set of events from an element\r\n\tremove: function( elem, types, handler, selector, mappedTypes ) {\r\n\t\tvar j, handleObj, tmp,\r\n\t\t\torigCount, t, events,\r\n\t\t\tspecial, handlers, type,\r\n\t\t\tnamespaces, origType,\r\n\t\t\telemData = jQuery.hasData( elem ) && jQuery._data( elem );\r\n\r\n\t\tif ( !elemData || !(events = elemData.events) ) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t// Once for each type.namespace in types; type may be omitted\r\n\t\ttypes = ( types || \"\" ).match( core_rnotwhite ) || [\"\"];\r\n\t\tt = types.length;\r\n\t\twhile ( t-- ) {\r\n\t\t\ttmp = rtypenamespace.exec( types[t] ) || [];\r\n\t\t\ttype = origType = tmp[1];\r\n\t\t\tnamespaces = ( tmp[2] || \"\" ).split( \".\" ).sort();\r\n\r\n\t\t\t// Unbind all events (on this namespace, if provided) for the element\r\n\t\t\tif ( !type ) {\r\n\t\t\t\tfor ( type in events ) {\r\n\t\t\t\t\tjQuery.event.remove( elem, type + types[ t ], handler, selector, true );\r\n\t\t\t\t}\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\tspecial = jQuery.event.special[ type ] || {};\r\n\t\t\ttype = ( selector ? special.delegateType : special.bindType ) || type;\r\n\t\t\thandlers = events[ type ] || [];\r\n\t\t\ttmp = tmp[2] && new RegExp( \"(^|\\\\.)\" + namespaces.join(\"\\\\.(?:.*\\\\.|)\") + \"(\\\\.|$)\" );\r\n\r\n\t\t\t// Remove matching events\r\n\t\t\torigCount = j = handlers.length;\r\n\t\t\twhile ( j-- ) {\r\n\t\t\t\thandleObj = handlers[ j ];\r\n\r\n\t\t\t\tif ( ( mappedTypes || origType === handleObj.origType ) &&\r\n\t\t\t\t\t( !handler || handler.guid === handleObj.guid ) &&\r\n\t\t\t\t\t( !tmp || tmp.test( handleObj.namespace ) ) &&\r\n\t\t\t\t\t( !selector || selector === handleObj.selector || selector === \"**\" && handleObj.selector ) ) {\r\n\t\t\t\t\thandlers.splice( j, 1 );\r\n\r\n\t\t\t\t\tif ( handleObj.selector ) {\r\n\t\t\t\t\t\thandlers.delegateCount--;\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif ( special.remove ) {\r\n\t\t\t\t\t\tspecial.remove.call( elem, handleObj );\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// Remove generic event handler if we removed something and no more handlers exist\r\n\t\t\t// (avoids potential for endless recursion during removal of special event handlers)\r\n\t\t\tif ( origCount && !handlers.length ) {\r\n\t\t\t\tif ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {\r\n\t\t\t\t\tjQuery.removeEvent( elem, type, elemData.handle );\r\n\t\t\t\t}\r\n\r\n\t\t\t\tdelete events[ type ];\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Remove the expando if it's no longer used\r\n\t\tif ( jQuery.isEmptyObject( events ) ) {\r\n\t\t\tdelete elemData.handle;\r\n\r\n\t\t\t// removeData also checks for emptiness and clears the expando if empty\r\n\t\t\t// so use it instead of delete\r\n\t\t\tjQuery._removeData( elem, \"events\" );\r\n\t\t}\r\n\t},\r\n\r\n\ttrigger: function( event, data, elem, onlyHandlers ) {\r\n\t\tvar handle, ontype, cur,\r\n\t\t\tbubbleType, special, tmp, i,\r\n\t\t\teventPath = [ elem || document ],\r\n\t\t\ttype = core_hasOwn.call( event, \"type\" ) ? event.type : event,\r\n\t\t\tnamespaces = core_hasOwn.call( event, \"namespace\" ) ? event.namespace.split(\".\") : [];\r\n\r\n\t\tcur = tmp = elem = elem || document;\r\n\r\n\t\t// Don't do events on text and comment nodes\r\n\t\tif ( elem.nodeType === 3 || elem.nodeType === 8 ) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t// focus/blur morphs to focusin/out; ensure we're not firing them right now\r\n\t\tif ( rfocusMorph.test( type + jQuery.event.triggered ) ) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tif ( type.indexOf(\".\") >= 0 ) {\r\n\t\t\t// Namespaced trigger; create a regexp to match event type in handle()\r\n\t\t\tnamespaces = type.split(\".\");\r\n\t\t\ttype = namespaces.shift();\r\n\t\t\tnamespaces.sort();\r\n\t\t}\r\n\t\tontype = type.indexOf(\":\") < 0 && \"on\" + type;\r\n\r\n\t\t// Caller can pass in a jQuery.Event object, Object, or just an event type string\r\n\t\tevent = event[ jQuery.expando ] ?\r\n\t\t\tevent :\r\n\t\t\tnew jQuery.Event( type, typeof event === \"object\" && event );\r\n\r\n\t\t// Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)\r\n\t\tevent.isTrigger = onlyHandlers ? 2 : 3;\r\n\t\tevent.namespace = namespaces.join(\".\");\r\n\t\tevent.namespace_re = event.namespace ?\r\n\t\t\tnew RegExp( \"(^|\\\\.)\" + namespaces.join(\"\\\\.(?:.*\\\\.|)\") + \"(\\\\.|$)\" ) :\r\n\t\t\tnull;\r\n\r\n\t\t// Clean up the event in case it is being reused\r\n\t\tevent.result = undefined;\r\n\t\tif ( !event.target ) {\r\n\t\t\tevent.target = elem;\r\n\t\t}\r\n\r\n\t\t// Clone any incoming data and prepend the event, creating the handler arg list\r\n\t\tdata = data == null ?\r\n\t\t\t[ event ] :\r\n\t\t\tjQuery.makeArray( data, [ event ] );\r\n\r\n\t\t// Allow special events to draw outside the lines\r\n\t\tspecial = jQuery.event.special[ type ] || {};\r\n\t\tif ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t// Determine event propagation path in advance, per W3C events spec (#9951)\r\n\t\t// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)\r\n\t\tif ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {\r\n\r\n\t\t\tbubbleType = special.delegateType || type;\r\n\t\t\tif ( !rfocusMorph.test( bubbleType + type ) ) {\r\n\t\t\t\tcur = cur.parentNode;\r\n\t\t\t}\r\n\t\t\tfor ( ; cur; cur = cur.parentNode ) {\r\n\t\t\t\teventPath.push( cur );\r\n\t\t\t\ttmp = cur;\r\n\t\t\t}\r\n\r\n\t\t\t// Only add window if we got to document (e.g., not plain obj or detached DOM)\r\n\t\t\tif ( tmp === (elem.ownerDocument || document) ) {\r\n\t\t\t\teventPath.push( tmp.defaultView || tmp.parentWindow || window );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Fire handlers on the event path\r\n\t\ti = 0;\r\n\t\twhile ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) {\r\n\r\n\t\t\tevent.type = i > 1 ?\r\n\t\t\t\tbubbleType :\r\n\t\t\t\tspecial.bindType || type;\r\n\r\n\t\t\t// jQuery handler\r\n\t\t\thandle = ( jQuery._data( cur, \"events\" ) || {} )[ event.type ] && jQuery._data( cur, \"handle\" );\r\n\t\t\tif ( handle ) {\r\n\t\t\t\thandle.apply( cur, data );\r\n\t\t\t}\r\n\r\n\t\t\t// Native handler\r\n\t\t\thandle = ontype && cur[ ontype ];\r\n\t\t\tif ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) {\r\n\t\t\t\tevent.preventDefault();\r\n\t\t\t}\r\n\t\t}\r\n\t\tevent.type = type;\r\n\r\n\t\t// If nobody prevented the default action, do it now\r\n\t\tif ( !onlyHandlers && !event.isDefaultPrevented() ) {\r\n\r\n\t\t\tif ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) &&\r\n\t\t\t\tjQuery.acceptData( elem ) ) {\r\n\r\n\t\t\t\t// Call a native DOM method on the target with the same name name as the event.\r\n\t\t\t\t// Can't use an .isFunction() check here because IE6/7 fails that test.\r\n\t\t\t\t// Don't do default actions on window, that's where global variables be (#6170)\r\n\t\t\t\tif ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) {\r\n\r\n\t\t\t\t\t// Don't re-trigger an onFOO event when we call its FOO() method\r\n\t\t\t\t\ttmp = elem[ ontype ];\r\n\r\n\t\t\t\t\tif ( tmp ) {\r\n\t\t\t\t\t\telem[ ontype ] = null;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t// Prevent re-triggering of the same event, since we already bubbled it above\r\n\t\t\t\t\tjQuery.event.triggered = type;\r\n\t\t\t\t\ttry {\r\n\t\t\t\t\t\telem[ type ]();\r\n\t\t\t\t\t} catch ( e ) {\r\n\t\t\t\t\t\t// IE<9 dies on focus/blur to hidden element (#1486,#12518)\r\n\t\t\t\t\t\t// only reproducible on winXP IE8 native, not IE9 in IE8 mode\r\n\t\t\t\t\t}\r\n\t\t\t\t\tjQuery.event.triggered = undefined;\r\n\r\n\t\t\t\t\tif ( tmp ) {\r\n\t\t\t\t\t\telem[ ontype ] = tmp;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn event.result;\r\n\t},\r\n\r\n\tdispatch: function( event ) {\r\n\r\n\t\t// Make a writable jQuery.Event from the native event object\r\n\t\tevent = jQuery.event.fix( event );\r\n\r\n\t\tvar i, ret, handleObj, matched, j,\r\n\t\t\thandlerQueue = [],\r\n\t\t\targs = core_slice.call( arguments ),\r\n\t\t\thandlers = ( jQuery._data( this, \"events\" ) || {} )[ event.type ] || [],\r\n\t\t\tspecial = jQuery.event.special[ event.type ] || {};\r\n\r\n\t\t// Use the fix-ed jQuery.Event rather than the (read-only) native event\r\n\t\targs[0] = event;\r\n\t\tevent.delegateTarget = this;\r\n\r\n\t\t// Call the preDispatch hook for the mapped type, and let it bail if desired\r\n\t\tif ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t// Determine handlers\r\n\t\thandlerQueue = jQuery.event.handlers.call( this, event, handlers );\r\n\r\n\t\t// Run delegates first; they may want to stop propagation beneath us\r\n\t\ti = 0;\r\n\t\twhile ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) {\r\n\t\t\tevent.currentTarget = matched.elem;\r\n\r\n\t\t\tj = 0;\r\n\t\t\twhile ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) {\r\n\r\n\t\t\t\t// Triggered event must either 1) have no namespace, or\r\n\t\t\t\t// 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).\r\n\t\t\t\tif ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) {\r\n\r\n\t\t\t\t\tevent.handleObj = handleObj;\r\n\t\t\t\t\tevent.data = handleObj.data;\r\n\r\n\t\t\t\t\tret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )\r\n\t\t\t\t\t\t\t.apply( matched.elem, args );\r\n\r\n\t\t\t\t\tif ( ret !== undefined ) {\r\n\t\t\t\t\t\tif ( (event.result = ret) === false ) {\r\n\t\t\t\t\t\t\tevent.preventDefault();\r\n\t\t\t\t\t\t\tevent.stopPropagation();\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Call the postDispatch hook for the mapped type\r\n\t\tif ( special.postDispatch ) {\r\n\t\t\tspecial.postDispatch.call( this, event );\r\n\t\t}\r\n\r\n\t\treturn event.result;\r\n\t},\r\n\r\n\thandlers: function( event, handlers ) {\r\n\t\tvar sel, handleObj, matches, i,\r\n\t\t\thandlerQueue = [],\r\n\t\t\tdelegateCount = handlers.delegateCount,\r\n\t\t\tcur = event.target;\r\n\r\n\t\t// Find delegate handlers\r\n\t\t// Black-hole SVG <use> instance trees (#13180)\r\n\t\t// Avoid non-left-click bubbling in Firefox (#3861)\r\n\t\tif ( delegateCount && cur.nodeType && (!event.button || event.type !== \"click\") ) {\r\n\r\n\t\t\t/* jshint eqeqeq: false */\r\n\t\t\tfor ( ; cur != this; cur = cur.parentNode || this ) {\r\n\t\t\t\t/* jshint eqeqeq: true */\r\n\r\n\t\t\t\t// Don't check non-elements (#13208)\r\n\t\t\t\t// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)\r\n\t\t\t\tif ( cur.nodeType === 1 && (cur.disabled !== true || event.type !== \"click\") ) {\r\n\t\t\t\t\tmatches = [];\r\n\t\t\t\t\tfor ( i = 0; i < delegateCount; i++ ) {\r\n\t\t\t\t\t\thandleObj = handlers[ i ];\r\n\r\n\t\t\t\t\t\t// Don't conflict with Object.prototype properties (#13203)\r\n\t\t\t\t\t\tsel = handleObj.selector + \" \";\r\n\r\n\t\t\t\t\t\tif ( matches[ sel ] === undefined ) {\r\n\t\t\t\t\t\t\tmatches[ sel ] = handleObj.needsContext ?\r\n\t\t\t\t\t\t\t\tjQuery( sel, this ).index( cur ) >= 0 :\r\n\t\t\t\t\t\t\t\tjQuery.find( sel, this, null, [ cur ] ).length;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tif ( matches[ sel ] ) {\r\n\t\t\t\t\t\t\tmatches.push( handleObj );\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\tif ( matches.length ) {\r\n\t\t\t\t\t\thandlerQueue.push({ elem: cur, handlers: matches });\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Add the remaining (directly-bound) handlers\r\n\t\tif ( delegateCount < handlers.length ) {\r\n\t\t\thandlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) });\r\n\t\t}\r\n\r\n\t\treturn handlerQueue;\r\n\t},\r\n\r\n\tfix: function( event ) {\r\n\t\tif ( event[ jQuery.expando ] ) {\r\n\t\t\treturn event;\r\n\t\t}\r\n\r\n\t\t// Create a writable copy of the event object and normalize some properties\r\n\t\tvar i, prop, copy,\r\n\t\t\ttype = event.type,\r\n\t\t\toriginalEvent = event,\r\n\t\t\tfixHook = this.fixHooks[ type ];\r\n\r\n\t\tif ( !fixHook ) {\r\n\t\t\tthis.fixHooks[ type ] = fixHook =\r\n\t\t\t\trmouseEvent.test( type ) ? this.mouseHooks :\r\n\t\t\t\trkeyEvent.test( type ) ? this.keyHooks :\r\n\t\t\t\t{};\r\n\t\t}\r\n\t\tcopy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;\r\n\r\n\t\tevent = new jQuery.Event( originalEvent );\r\n\r\n\t\ti = copy.length;\r\n\t\twhile ( i-- ) {\r\n\t\t\tprop = copy[ i ];\r\n\t\t\tevent[ prop ] = originalEvent[ prop ];\r\n\t\t}\r\n\r\n\t\t// Support: IE<9\r\n\t\t// Fix target property (#1925)\r\n\t\tif ( !event.target ) {\r\n\t\t\tevent.target = originalEvent.srcElement || document;\r\n\t\t}\r\n\r\n\t\t// Support: Chrome 23+, Safari?\r\n\t\t// Target should not be a text node (#504, #13143)\r\n\t\tif ( event.target.nodeType === 3 ) {\r\n\t\t\tevent.target = event.target.parentNode;\r\n\t\t}\r\n\r\n\t\t// Support: IE<9\r\n\t\t// For mouse/key events, metaKey==false if it's undefined (#3368, #11328)\r\n\t\tevent.metaKey = !!event.metaKey;\r\n\r\n\t\treturn fixHook.filter ? fixHook.filter( event, originalEvent ) : event;\r\n\t},\r\n\r\n\t// Includes some event props shared by KeyEvent and MouseEvent\r\n\tprops: \"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which\".split(\" \"),\r\n\r\n\tfixHooks: {},\r\n\r\n\tkeyHooks: {\r\n\t\tprops: \"char charCode key keyCode\".split(\" \"),\r\n\t\tfilter: function( event, original ) {\r\n\r\n\t\t\t// Add which for key events\r\n\t\t\tif ( event.which == null ) {\r\n\t\t\t\tevent.which = original.charCode != null ? original.charCode : original.keyCode;\r\n\t\t\t}\r\n\r\n\t\t\treturn event;\r\n\t\t}\r\n\t},\r\n\r\n\tmouseHooks: {\r\n\t\tprops: \"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement\".split(\" \"),\r\n\t\tfilter: function( event, original ) {\r\n\t\t\tvar body, eventDoc, doc,\r\n\t\t\t\tbutton = original.button,\r\n\t\t\t\tfromElement = original.fromElement;\r\n\r\n\t\t\t// Calculate pageX/Y if missing and clientX/Y available\r\n\t\t\tif ( event.pageX == null && original.clientX != null ) {\r\n\t\t\t\teventDoc = event.target.ownerDocument || document;\r\n\t\t\t\tdoc = eventDoc.documentElement;\r\n\t\t\t\tbody = eventDoc.body;\r\n\r\n\t\t\t\tevent.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );\r\n\t\t\t\tevent.pageY = original.clientY + ( doc && doc.scrollTop  || body && body.scrollTop  || 0 ) - ( doc && doc.clientTop  || body && body.clientTop  || 0 );\r\n\t\t\t}\r\n\r\n\t\t\t// Add relatedTarget, if necessary\r\n\t\t\tif ( !event.relatedTarget && fromElement ) {\r\n\t\t\t\tevent.relatedTarget = fromElement === event.target ? original.toElement : fromElement;\r\n\t\t\t}\r\n\r\n\t\t\t// Add which for click: 1 === left; 2 === middle; 3 === right\r\n\t\t\t// Note: button is not normalized, so don't use it\r\n\t\t\tif ( !event.which && button !== undefined ) {\r\n\t\t\t\tevent.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );\r\n\t\t\t}\r\n\r\n\t\t\treturn event;\r\n\t\t}\r\n\t},\r\n\r\n\tspecial: {\r\n\t\tload: {\r\n\t\t\t// Prevent triggered image.load events from bubbling to window.load\r\n\t\t\tnoBubble: true\r\n\t\t},\r\n\t\tfocus: {\r\n\t\t\t// Fire native event if possible so blur/focus sequence is correct\r\n\t\t\ttrigger: function() {\r\n\t\t\t\tif ( this !== safeActiveElement() && this.focus ) {\r\n\t\t\t\t\ttry {\r\n\t\t\t\t\t\tthis.focus();\r\n\t\t\t\t\t\treturn false;\r\n\t\t\t\t\t} catch ( e ) {\r\n\t\t\t\t\t\t// Support: IE<9\r\n\t\t\t\t\t\t// If we error on focus to hidden element (#1486, #12518),\r\n\t\t\t\t\t\t// let .trigger() run the handlers\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tdelegateType: \"focusin\"\r\n\t\t},\r\n\t\tblur: {\r\n\t\t\ttrigger: function() {\r\n\t\t\t\tif ( this === safeActiveElement() && this.blur ) {\r\n\t\t\t\t\tthis.blur();\r\n\t\t\t\t\treturn false;\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tdelegateType: \"focusout\"\r\n\t\t},\r\n\t\tclick: {\r\n\t\t\t// For checkbox, fire native event so checked state will be right\r\n\t\t\ttrigger: function() {\r\n\t\t\t\tif ( jQuery.nodeName( this, \"input\" ) && this.type === \"checkbox\" && this.click ) {\r\n\t\t\t\t\tthis.click();\r\n\t\t\t\t\treturn false;\r\n\t\t\t\t}\r\n\t\t\t},\r\n\r\n\t\t\t// For cross-browser consistency, don't fire native .click() on links\r\n\t\t\t_default: function( event ) {\r\n\t\t\t\treturn jQuery.nodeName( event.target, \"a\" );\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\tbeforeunload: {\r\n\t\t\tpostDispatch: function( event ) {\r\n\r\n\t\t\t\t// Even when returnValue equals to undefined Firefox will still show alert\r\n\t\t\t\tif ( event.result !== undefined ) {\r\n\t\t\t\t\tevent.originalEvent.returnValue = event.result;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\r\n\tsimulate: function( type, elem, event, bubble ) {\r\n\t\t// Piggyback on a donor event to simulate a different one.\r\n\t\t// Fake originalEvent to avoid donor's stopPropagation, but if the\r\n\t\t// simulated event prevents default then we do the same on the donor.\r\n\t\tvar e = jQuery.extend(\r\n\t\t\tnew jQuery.Event(),\r\n\t\t\tevent,\r\n\t\t\t{\r\n\t\t\t\ttype: type,\r\n\t\t\t\tisSimulated: true,\r\n\t\t\t\toriginalEvent: {}\r\n\t\t\t}\r\n\t\t);\r\n\t\tif ( bubble ) {\r\n\t\t\tjQuery.event.trigger( e, null, elem );\r\n\t\t} else {\r\n\t\t\tjQuery.event.dispatch.call( elem, e );\r\n\t\t}\r\n\t\tif ( e.isDefaultPrevented() ) {\r\n\t\t\tevent.preventDefault();\r\n\t\t}\r\n\t}\r\n};\r\n\r\njQuery.removeEvent = document.removeEventListener ?\r\n\tfunction( elem, type, handle ) {\r\n\t\tif ( elem.removeEventListener ) {\r\n\t\t\telem.removeEventListener( type, handle, false );\r\n\t\t}\r\n\t} :\r\n\tfunction( elem, type, handle ) {\r\n\t\tvar name = \"on\" + type;\r\n\r\n\t\tif ( elem.detachEvent ) {\r\n\r\n\t\t\t// #8545, #7054, preventing memory leaks for custom events in IE6-8\r\n\t\t\t// detachEvent needed property on element, by name of that event, to properly expose it to GC\r\n\t\t\tif ( typeof elem[ name ] === core_strundefined ) {\r\n\t\t\t\telem[ name ] = null;\r\n\t\t\t}\r\n\r\n\t\t\telem.detachEvent( name, handle );\r\n\t\t}\r\n\t};\r\n\r\njQuery.Event = function( src, props ) {\r\n\t// Allow instantiation without the 'new' keyword\r\n\tif ( !(this instanceof jQuery.Event) ) {\r\n\t\treturn new jQuery.Event( src, props );\r\n\t}\r\n\r\n\t// Event object\r\n\tif ( src && src.type ) {\r\n\t\tthis.originalEvent = src;\r\n\t\tthis.type = src.type;\r\n\r\n\t\t// Events bubbling up the document may have been marked as prevented\r\n\t\t// by a handler lower down the tree; reflect the correct value.\r\n\t\tthis.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false ||\r\n\t\t\tsrc.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse;\r\n\r\n\t// Event type\r\n\t} else {\r\n\t\tthis.type = src;\r\n\t}\r\n\r\n\t// Put explicitly provided properties onto the event object\r\n\tif ( props ) {\r\n\t\tjQuery.extend( this, props );\r\n\t}\r\n\r\n\t// Create a timestamp if incoming event doesn't have one\r\n\tthis.timeStamp = src && src.timeStamp || jQuery.now();\r\n\r\n\t// Mark it as fixed\r\n\tthis[ jQuery.expando ] = true;\r\n};\r\n\r\n// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding\r\n// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html\r\njQuery.Event.prototype = {\r\n\tisDefaultPrevented: returnFalse,\r\n\tisPropagationStopped: returnFalse,\r\n\tisImmediatePropagationStopped: returnFalse,\r\n\r\n\tpreventDefault: function() {\r\n\t\tvar e = this.originalEvent;\r\n\r\n\t\tthis.isDefaultPrevented = returnTrue;\r\n\t\tif ( !e ) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t// If preventDefault exists, run it on the original event\r\n\t\tif ( e.preventDefault ) {\r\n\t\t\te.preventDefault();\r\n\r\n\t\t// Support: IE\r\n\t\t// Otherwise set the returnValue property of the original event to false\r\n\t\t} else {\r\n\t\t\te.returnValue = false;\r\n\t\t}\r\n\t},\r\n\tstopPropagation: function() {\r\n\t\tvar e = this.originalEvent;\r\n\r\n\t\tthis.isPropagationStopped = returnTrue;\r\n\t\tif ( !e ) {\r\n\t\t\treturn;\r\n\t\t}\r\n\t\t// If stopPropagation exists, run it on the original event\r\n\t\tif ( e.stopPropagation ) {\r\n\t\t\te.stopPropagation();\r\n\t\t}\r\n\r\n\t\t// Support: IE\r\n\t\t// Set the cancelBubble property of the original event to true\r\n\t\te.cancelBubble = true;\r\n\t},\r\n\tstopImmediatePropagation: function() {\r\n\t\tthis.isImmediatePropagationStopped = returnTrue;\r\n\t\tthis.stopPropagation();\r\n\t}\r\n};\r\n\r\n// Create mouseenter/leave events using mouseover/out and event-time checks\r\njQuery.each({\r\n\tmouseenter: \"mouseover\",\r\n\tmouseleave: \"mouseout\"\r\n}, function( orig, fix ) {\r\n\tjQuery.event.special[ orig ] = {\r\n\t\tdelegateType: fix,\r\n\t\tbindType: fix,\r\n\r\n\t\thandle: function( event ) {\r\n\t\t\tvar ret,\r\n\t\t\t\ttarget = this,\r\n\t\t\t\trelated = event.relatedTarget,\r\n\t\t\t\thandleObj = event.handleObj;\r\n\r\n\t\t\t// For mousenter/leave call the handler if related is outside the target.\r\n\t\t\t// NB: No relatedTarget if the mouse left/entered the browser window\r\n\t\t\tif ( !related || (related !== target && !jQuery.contains( target, related )) ) {\r\n\t\t\t\tevent.type = handleObj.origType;\r\n\t\t\t\tret = handleObj.handler.apply( this, arguments );\r\n\t\t\t\tevent.type = fix;\r\n\t\t\t}\r\n\t\t\treturn ret;\r\n\t\t}\r\n\t};\r\n});\r\n\r\n// IE submit delegation\r\nif ( !jQuery.support.submitBubbles ) {\r\n\r\n\tjQuery.event.special.submit = {\r\n\t\tsetup: function() {\r\n\t\t\t// Only need this for delegated form submit events\r\n\t\t\tif ( jQuery.nodeName( this, \"form\" ) ) {\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\r\n\t\t\t// Lazy-add a submit handler when a descendant form may potentially be submitted\r\n\t\t\tjQuery.event.add( this, \"click._submit keypress._submit\", function( e ) {\r\n\t\t\t\t// Node name check avoids a VML-related crash in IE (#9807)\r\n\t\t\t\tvar elem = e.target,\r\n\t\t\t\t\tform = jQuery.nodeName( elem, \"input\" ) || jQuery.nodeName( elem, \"button\" ) ? elem.form : undefined;\r\n\t\t\t\tif ( form && !jQuery._data( form, \"submitBubbles\" ) ) {\r\n\t\t\t\t\tjQuery.event.add( form, \"submit._submit\", function( event ) {\r\n\t\t\t\t\t\tevent._submit_bubble = true;\r\n\t\t\t\t\t});\r\n\t\t\t\t\tjQuery._data( form, \"submitBubbles\", true );\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t\t// return undefined since we don't need an event listener\r\n\t\t},\r\n\r\n\t\tpostDispatch: function( event ) {\r\n\t\t\t// If form was submitted by the user, bubble the event up the tree\r\n\t\t\tif ( event._submit_bubble ) {\r\n\t\t\t\tdelete event._submit_bubble;\r\n\t\t\t\tif ( this.parentNode && !event.isTrigger ) {\r\n\t\t\t\t\tjQuery.event.simulate( \"submit\", this.parentNode, event, true );\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\tteardown: function() {\r\n\t\t\t// Only need this for delegated form submit events\r\n\t\t\tif ( jQuery.nodeName( this, \"form\" ) ) {\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\r\n\t\t\t// Remove delegated handlers; cleanData eventually reaps submit handlers attached above\r\n\t\t\tjQuery.event.remove( this, \"._submit\" );\r\n\t\t}\r\n\t};\r\n}\r\n\r\n// IE change delegation and checkbox/radio fix\r\nif ( !jQuery.support.changeBubbles ) {\r\n\r\n\tjQuery.event.special.change = {\r\n\r\n\t\tsetup: function() {\r\n\r\n\t\t\tif ( rformElems.test( this.nodeName ) ) {\r\n\t\t\t\t// IE doesn't fire change on a check/radio until blur; trigger it on click\r\n\t\t\t\t// after a propertychange. Eat the blur-change in special.change.handle.\r\n\t\t\t\t// This still fires onchange a second time for check/radio after blur.\r\n\t\t\t\tif ( this.type === \"checkbox\" || this.type === \"radio\" ) {\r\n\t\t\t\t\tjQuery.event.add( this, \"propertychange._change\", function( event ) {\r\n\t\t\t\t\t\tif ( event.originalEvent.propertyName === \"checked\" ) {\r\n\t\t\t\t\t\t\tthis._just_changed = true;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t});\r\n\t\t\t\t\tjQuery.event.add( this, \"click._change\", function( event ) {\r\n\t\t\t\t\t\tif ( this._just_changed && !event.isTrigger ) {\r\n\t\t\t\t\t\t\tthis._just_changed = false;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\t// Allow triggered, simulated change events (#11500)\r\n\t\t\t\t\t\tjQuery.event.simulate( \"change\", this, event, true );\r\n\t\t\t\t\t});\r\n\t\t\t\t}\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t\t// Delegated event; lazy-add a change handler on descendant inputs\r\n\t\t\tjQuery.event.add( this, \"beforeactivate._change\", function( e ) {\r\n\t\t\t\tvar elem = e.target;\r\n\r\n\t\t\t\tif ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, \"changeBubbles\" ) ) {\r\n\t\t\t\t\tjQuery.event.add( elem, \"change._change\", function( event ) {\r\n\t\t\t\t\t\tif ( this.parentNode && !event.isSimulated && !event.isTrigger ) {\r\n\t\t\t\t\t\t\tjQuery.event.simulate( \"change\", this.parentNode, event, true );\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t});\r\n\t\t\t\t\tjQuery._data( elem, \"changeBubbles\", true );\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t},\r\n\r\n\t\thandle: function( event ) {\r\n\t\t\tvar elem = event.target;\r\n\r\n\t\t\t// Swallow native change events from checkbox/radio, we already triggered them above\r\n\t\t\tif ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== \"radio\" && elem.type !== \"checkbox\") ) {\r\n\t\t\t\treturn event.handleObj.handler.apply( this, arguments );\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\tteardown: function() {\r\n\t\t\tjQuery.event.remove( this, \"._change\" );\r\n\r\n\t\t\treturn !rformElems.test( this.nodeName );\r\n\t\t}\r\n\t};\r\n}\r\n\r\n// Create \"bubbling\" focus and blur events\r\nif ( !jQuery.support.focusinBubbles ) {\r\n\tjQuery.each({ focus: \"focusin\", blur: \"focusout\" }, function( orig, fix ) {\r\n\r\n\t\t// Attach a single capturing handler while someone wants focusin/focusout\r\n\t\tvar attaches = 0,\r\n\t\t\thandler = function( event ) {\r\n\t\t\t\tjQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );\r\n\t\t\t};\r\n\r\n\t\tjQuery.event.special[ fix ] = {\r\n\t\t\tsetup: function() {\r\n\t\t\t\tif ( attaches++ === 0 ) {\r\n\t\t\t\t\tdocument.addEventListener( orig, handler, true );\r\n\t\t\t\t}\r\n\t\t\t},\r\n\t\t\tteardown: function() {\r\n\t\t\t\tif ( --attaches === 0 ) {\r\n\t\t\t\t\tdocument.removeEventListener( orig, handler, true );\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t};\r\n\t});\r\n}\r\n\r\njQuery.fn.extend({\r\n\r\n\ton: function( types, selector, data, fn, /*INTERNAL*/ one ) {\r\n\t\tvar type, origFn;\r\n\r\n\t\t// Types can be a map of types/handlers\r\n\t\tif ( typeof types === \"object\" ) {\r\n\t\t\t// ( types-Object, selector, data )\r\n\t\t\tif ( typeof selector !== \"string\" ) {\r\n\t\t\t\t// ( types-Object, data )\r\n\t\t\t\tdata = data || selector;\r\n\t\t\t\tselector = undefined;\r\n\t\t\t}\r\n\t\t\tfor ( type in types ) {\r\n\t\t\t\tthis.on( type, selector, data, types[ type ], one );\r\n\t\t\t}\r\n\t\t\treturn this;\r\n\t\t}\r\n\r\n\t\tif ( data == null && fn == null ) {\r\n\t\t\t// ( types, fn )\r\n\t\t\tfn = selector;\r\n\t\t\tdata = selector = undefined;\r\n\t\t} else if ( fn == null ) {\r\n\t\t\tif ( typeof selector === \"string\" ) {\r\n\t\t\t\t// ( types, selector, fn )\r\n\t\t\t\tfn = data;\r\n\t\t\t\tdata = undefined;\r\n\t\t\t} else {\r\n\t\t\t\t// ( types, data, fn )\r\n\t\t\t\tfn = data;\r\n\t\t\t\tdata = selector;\r\n\t\t\t\tselector = undefined;\r\n\t\t\t}\r\n\t\t}\r\n\t\tif ( fn === false ) {\r\n\t\t\tfn = returnFalse;\r\n\t\t} else if ( !fn ) {\r\n\t\t\treturn this;\r\n\t\t}\r\n\r\n\t\tif ( one === 1 ) {\r\n\t\t\torigFn = fn;\r\n\t\t\tfn = function( event ) {\r\n\t\t\t\t// Can use an empty set, since event contains the info\r\n\t\t\t\tjQuery().off( event );\r\n\t\t\t\treturn origFn.apply( this, arguments );\r\n\t\t\t};\r\n\t\t\t// Use same guid so caller can remove using origFn\r\n\t\t\tfn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );\r\n\t\t}\r\n\t\treturn this.each( function() {\r\n\t\t\tjQuery.event.add( this, types, fn, data, selector );\r\n\t\t});\r\n\t},\r\n\tone: function( types, selector, data, fn ) {\r\n\t\treturn this.on( types, selector, data, fn, 1 );\r\n\t},\r\n\toff: function( types, selector, fn ) {\r\n\t\tvar handleObj, type;\r\n\t\tif ( types && types.preventDefault && types.handleObj ) {\r\n\t\t\t// ( event )  dispatched jQuery.Event\r\n\t\t\thandleObj = types.handleObj;\r\n\t\t\tjQuery( types.delegateTarget ).off(\r\n\t\t\t\thandleObj.namespace ? handleObj.origType + \".\" + handleObj.namespace : handleObj.origType,\r\n\t\t\t\thandleObj.selector,\r\n\t\t\t\thandleObj.handler\r\n\t\t\t);\r\n\t\t\treturn this;\r\n\t\t}\r\n\t\tif ( typeof types === \"object\" ) {\r\n\t\t\t// ( types-object [, selector] )\r\n\t\t\tfor ( type in types ) {\r\n\t\t\t\tthis.off( type, selector, types[ type ] );\r\n\t\t\t}\r\n\t\t\treturn this;\r\n\t\t}\r\n\t\tif ( selector === false || typeof selector === \"function\" ) {\r\n\t\t\t// ( types [, fn] )\r\n\t\t\tfn = selector;\r\n\t\t\tselector = undefined;\r\n\t\t}\r\n\t\tif ( fn === false ) {\r\n\t\t\tfn = returnFalse;\r\n\t\t}\r\n\t\treturn this.each(function() {\r\n\t\t\tjQuery.event.remove( this, types, fn, selector );\r\n\t\t});\r\n\t},\r\n\r\n\ttrigger: function( type, data ) {\r\n\t\treturn this.each(function() {\r\n\t\t\tjQuery.event.trigger( type, data, this );\r\n\t\t});\r\n\t},\r\n\ttriggerHandler: function( type, data ) {\r\n\t\tvar elem = this[0];\r\n\t\tif ( elem ) {\r\n\t\t\treturn jQuery.event.trigger( type, data, elem, true );\r\n\t\t}\r\n\t}\r\n});\r\nvar isSimple = /^.[^:#\\[\\.,]*$/,\r\n\trparentsprev = /^(?:parents|prev(?:Until|All))/,\r\n\trneedsContext = jQuery.expr.match.needsContext,\r\n\t// methods guaranteed to produce a unique set when starting from a unique set\r\n\tguaranteedUnique = {\r\n\t\tchildren: true,\r\n\t\tcontents: true,\r\n\t\tnext: true,\r\n\t\tprev: true\r\n\t};\r\n\r\njQuery.fn.extend({\r\n\tfind: function( selector ) {\r\n\t\tvar i,\r\n\t\t\tret = [],\r\n\t\t\tself = this,\r\n\t\t\tlen = self.length;\r\n\r\n\t\tif ( typeof selector !== \"string\" ) {\r\n\t\t\treturn this.pushStack( jQuery( selector ).filter(function() {\r\n\t\t\t\tfor ( i = 0; i < len; i++ ) {\r\n\t\t\t\t\tif ( jQuery.contains( self[ i ], this ) ) {\r\n\t\t\t\t\t\treturn true;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}) );\r\n\t\t}\r\n\r\n\t\tfor ( i = 0; i < len; i++ ) {\r\n\t\t\tjQuery.find( selector, self[ i ], ret );\r\n\t\t}\r\n\r\n\t\t// Needed because $( selector, context ) becomes $( context ).find( selector )\r\n\t\tret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret );\r\n\t\tret.selector = this.selector ? this.selector + \" \" + selector : selector;\r\n\t\treturn ret;\r\n\t},\r\n\r\n\thas: function( target ) {\r\n\t\tvar i,\r\n\t\t\ttargets = jQuery( target, this ),\r\n\t\t\tlen = targets.length;\r\n\r\n\t\treturn this.filter(function() {\r\n\t\t\tfor ( i = 0; i < len; i++ ) {\r\n\t\t\t\tif ( jQuery.contains( this, targets[i] ) ) {\r\n\t\t\t\t\treturn true;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t});\r\n\t},\r\n\r\n\tnot: function( selector ) {\r\n\t\treturn this.pushStack( winnow(this, selector || [], true) );\r\n\t},\r\n\r\n\tfilter: function( selector ) {\r\n\t\treturn this.pushStack( winnow(this, selector || [], false) );\r\n\t},\r\n\r\n\tis: function( selector ) {\r\n\t\treturn !!winnow(\r\n\t\t\tthis,\r\n\r\n\t\t\t// If this is a positional/relative selector, check membership in the returned set\r\n\t\t\t// so $(\"p:first\").is(\"p:last\") won't return true for a doc with two \"p\".\r\n\t\t\ttypeof selector === \"string\" && rneedsContext.test( selector ) ?\r\n\t\t\t\tjQuery( selector ) :\r\n\t\t\t\tselector || [],\r\n\t\t\tfalse\r\n\t\t).length;\r\n\t},\r\n\r\n\tclosest: function( selectors, context ) {\r\n\t\tvar cur,\r\n\t\t\ti = 0,\r\n\t\t\tl = this.length,\r\n\t\t\tret = [],\r\n\t\t\tpos = rneedsContext.test( selectors ) || typeof selectors !== \"string\" ?\r\n\t\t\t\tjQuery( selectors, context || this.context ) :\r\n\t\t\t\t0;\r\n\r\n\t\tfor ( ; i < l; i++ ) {\r\n\t\t\tfor ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) {\r\n\t\t\t\t// Always skip document fragments\r\n\t\t\t\tif ( cur.nodeType < 11 && (pos ?\r\n\t\t\t\t\tpos.index(cur) > -1 :\r\n\r\n\t\t\t\t\t// Don't pass non-elements to Sizzle\r\n\t\t\t\t\tcur.nodeType === 1 &&\r\n\t\t\t\t\t\tjQuery.find.matchesSelector(cur, selectors)) ) {\r\n\r\n\t\t\t\t\tcur = ret.push( cur );\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn this.pushStack( ret.length > 1 ? jQuery.unique( ret ) : ret );\r\n\t},\r\n\r\n\t// Determine the position of an element within\r\n\t// the matched set of elements\r\n\tindex: function( elem ) {\r\n\r\n\t\t// No argument, return index in parent\r\n\t\tif ( !elem ) {\r\n\t\t\treturn ( this[0] && this[0].parentNode ) ? this.first().prevAll().length : -1;\r\n\t\t}\r\n\r\n\t\t// index in selector\r\n\t\tif ( typeof elem === \"string\" ) {\r\n\t\t\treturn jQuery.inArray( this[0], jQuery( elem ) );\r\n\t\t}\r\n\r\n\t\t// Locate the position of the desired element\r\n\t\treturn jQuery.inArray(\r\n\t\t\t// If it receives a jQuery object, the first element is used\r\n\t\t\telem.jquery ? elem[0] : elem, this );\r\n\t},\r\n\r\n\tadd: function( selector, context ) {\r\n\t\tvar set = typeof selector === \"string\" ?\r\n\t\t\t\tjQuery( selector, context ) :\r\n\t\t\t\tjQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ),\r\n\t\t\tall = jQuery.merge( this.get(), set );\r\n\r\n\t\treturn this.pushStack( jQuery.unique(all) );\r\n\t},\r\n\r\n\taddBack: function( selector ) {\r\n\t\treturn this.add( selector == null ?\r\n\t\t\tthis.prevObject : this.prevObject.filter(selector)\r\n\t\t);\r\n\t}\r\n});\r\n\r\nfunction sibling( cur, dir ) {\r\n\tdo {\r\n\t\tcur = cur[ dir ];\r\n\t} while ( cur && cur.nodeType !== 1 );\r\n\r\n\treturn cur;\r\n}\r\n\r\njQuery.each({\r\n\tparent: function( elem ) {\r\n\t\tvar parent = elem.parentNode;\r\n\t\treturn parent && parent.nodeType !== 11 ? parent : null;\r\n\t},\r\n\tparents: function( elem ) {\r\n\t\treturn jQuery.dir( elem, \"parentNode\" );\r\n\t},\r\n\tparentsUntil: function( elem, i, until ) {\r\n\t\treturn jQuery.dir( elem, \"parentNode\", until );\r\n\t},\r\n\tnext: function( elem ) {\r\n\t\treturn sibling( elem, \"nextSibling\" );\r\n\t},\r\n\tprev: function( elem ) {\r\n\t\treturn sibling( elem, \"previousSibling\" );\r\n\t},\r\n\tnextAll: function( elem ) {\r\n\t\treturn jQuery.dir( elem, \"nextSibling\" );\r\n\t},\r\n\tprevAll: function( elem ) {\r\n\t\treturn jQuery.dir( elem, \"previousSibling\" );\r\n\t},\r\n\tnextUntil: function( elem, i, until ) {\r\n\t\treturn jQuery.dir( elem, \"nextSibling\", until );\r\n\t},\r\n\tprevUntil: function( elem, i, until ) {\r\n\t\treturn jQuery.dir( elem, \"previousSibling\", until );\r\n\t},\r\n\tsiblings: function( elem ) {\r\n\t\treturn jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );\r\n\t},\r\n\tchildren: function( elem ) {\r\n\t\treturn jQuery.sibling( elem.firstChild );\r\n\t},\r\n\tcontents: function( elem ) {\r\n\t\treturn jQuery.nodeName( elem, \"iframe\" ) ?\r\n\t\t\telem.contentDocument || elem.contentWindow.document :\r\n\t\t\tjQuery.merge( [], elem.childNodes );\r\n\t}\r\n}, function( name, fn ) {\r\n\tjQuery.fn[ name ] = function( until, selector ) {\r\n\t\tvar ret = jQuery.map( this, fn, until );\r\n\r\n\t\tif ( name.slice( -5 ) !== \"Until\" ) {\r\n\t\t\tselector = until;\r\n\t\t}\r\n\r\n\t\tif ( selector && typeof selector === \"string\" ) {\r\n\t\t\tret = jQuery.filter( selector, ret );\r\n\t\t}\r\n\r\n\t\tif ( this.length > 1 ) {\r\n\t\t\t// Remove duplicates\r\n\t\t\tif ( !guaranteedUnique[ name ] ) {\r\n\t\t\t\tret = jQuery.unique( ret );\r\n\t\t\t}\r\n\r\n\t\t\t// Reverse order for parents* and prev-derivatives\r\n\t\t\tif ( rparentsprev.test( name ) ) {\r\n\t\t\t\tret = ret.reverse();\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn this.pushStack( ret );\r\n\t};\r\n});\r\n\r\njQuery.extend({\r\n\tfilter: function( expr, elems, not ) {\r\n\t\tvar elem = elems[ 0 ];\r\n\r\n\t\tif ( not ) {\r\n\t\t\texpr = \":not(\" + expr + \")\";\r\n\t\t}\r\n\r\n\t\treturn elems.length === 1 && elem.nodeType === 1 ?\r\n\t\t\tjQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] :\r\n\t\t\tjQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {\r\n\t\t\t\treturn elem.nodeType === 1;\r\n\t\t\t}));\r\n\t},\r\n\r\n\tdir: function( elem, dir, until ) {\r\n\t\tvar matched = [],\r\n\t\t\tcur = elem[ dir ];\r\n\r\n\t\twhile ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {\r\n\t\t\tif ( cur.nodeType === 1 ) {\r\n\t\t\t\tmatched.push( cur );\r\n\t\t\t}\r\n\t\t\tcur = cur[dir];\r\n\t\t}\r\n\t\treturn matched;\r\n\t},\r\n\r\n\tsibling: function( n, elem ) {\r\n\t\tvar r = [];\r\n\r\n\t\tfor ( ; n; n = n.nextSibling ) {\r\n\t\t\tif ( n.nodeType === 1 && n !== elem ) {\r\n\t\t\t\tr.push( n );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn r;\r\n\t}\r\n});\r\n\r\n// Implement the identical functionality for filter and not\r\nfunction winnow( elements, qualifier, not ) {\r\n\tif ( jQuery.isFunction( qualifier ) ) {\r\n\t\treturn jQuery.grep( elements, function( elem, i ) {\r\n\t\t\t/* jshint -W018 */\r\n\t\t\treturn !!qualifier.call( elem, i, elem ) !== not;\r\n\t\t});\r\n\r\n\t}\r\n\r\n\tif ( qualifier.nodeType ) {\r\n\t\treturn jQuery.grep( elements, function( elem ) {\r\n\t\t\treturn ( elem === qualifier ) !== not;\r\n\t\t});\r\n\r\n\t}\r\n\r\n\tif ( typeof qualifier === \"string\" ) {\r\n\t\tif ( isSimple.test( qualifier ) ) {\r\n\t\t\treturn jQuery.filter( qualifier, elements, not );\r\n\t\t}\r\n\r\n\t\tqualifier = jQuery.filter( qualifier, elements );\r\n\t}\r\n\r\n\treturn jQuery.grep( elements, function( elem ) {\r\n\t\treturn ( jQuery.inArray( elem, qualifier ) >= 0 ) !== not;\r\n\t});\r\n}\r\nfunction createSafeFragment( document ) {\r\n\tvar list = nodeNames.split( \"|\" ),\r\n\t\tsafeFrag = document.createDocumentFragment();\r\n\r\n\tif ( safeFrag.createElement ) {\r\n\t\twhile ( list.length ) {\r\n\t\t\tsafeFrag.createElement(\r\n\t\t\t\tlist.pop()\r\n\t\t\t);\r\n\t\t}\r\n\t}\r\n\treturn safeFrag;\r\n}\r\n\r\nvar nodeNames = \"abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|\" +\r\n\t\t\"header|hgroup|mark|meter|nav|output|progress|section|summary|time|video\",\r\n\trinlinejQuery = / jQuery\\d+=\"(?:null|\\d+)\"/g,\r\n\trnoshimcache = new RegExp(\"<(?:\" + nodeNames + \")[\\\\s/>]\", \"i\"),\r\n\trleadingWhitespace = /^\\s+/,\r\n\trxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\\w:]+)[^>]*)\\/>/gi,\r\n\trtagName = /<([\\w:]+)/,\r\n\trtbody = /<tbody/i,\r\n\trhtml = /<|&#?\\w+;/,\r\n\trnoInnerhtml = /<(?:script|style|link)/i,\r\n\tmanipulation_rcheckableType = /^(?:checkbox|radio)$/i,\r\n\t// checked=\"checked\" or checked\r\n\trchecked = /checked\\s*(?:[^=]|=\\s*.checked.)/i,\r\n\trscriptType = /^$|\\/(?:java|ecma)script/i,\r\n\trscriptTypeMasked = /^true\\/(.*)/,\r\n\trcleanScript = /^\\s*<!(?:\\[CDATA\\[|--)|(?:\\]\\]|--)>\\s*$/g,\r\n\r\n\t// We have to close these tags to support XHTML (#13200)\r\n\twrapMap = {\r\n\t\toption: [ 1, \"<select multiple='multiple'>\", \"</select>\" ],\r\n\t\tlegend: [ 1, \"<fieldset>\", \"</fieldset>\" ],\r\n\t\tarea: [ 1, \"<map>\", \"</map>\" ],\r\n\t\tparam: [ 1, \"<object>\", \"</object>\" ],\r\n\t\tthead: [ 1, \"<table>\", \"</table>\" ],\r\n\t\ttr: [ 2, \"<table><tbody>\", \"</tbody></table>\" ],\r\n\t\tcol: [ 2, \"<table><tbody></tbody><colgroup>\", \"</colgroup></table>\" ],\r\n\t\ttd: [ 3, \"<table><tbody><tr>\", \"</tr></tbody></table>\" ],\r\n\r\n\t\t// IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags,\r\n\t\t// unless wrapped in a div with non-breaking characters in front of it.\r\n\t\t_default: jQuery.support.htmlSerialize ? [ 0, \"\", \"\" ] : [ 1, \"X<div>\", \"</div>\"  ]\r\n\t},\r\n\tsafeFragment = createSafeFragment( document ),\r\n\tfragmentDiv = safeFragment.appendChild( document.createElement(\"div\") );\r\n\r\nwrapMap.optgroup = wrapMap.option;\r\nwrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;\r\nwrapMap.th = wrapMap.td;\r\n\r\njQuery.fn.extend({\r\n\ttext: function( value ) {\r\n\t\treturn jQuery.access( this, function( value ) {\r\n\t\t\treturn value === undefined ?\r\n\t\t\t\tjQuery.text( this ) :\r\n\t\t\t\tthis.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) );\r\n\t\t}, null, value, arguments.length );\r\n\t},\r\n\r\n\tappend: function() {\r\n\t\treturn this.domManip( arguments, function( elem ) {\r\n\t\t\tif ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {\r\n\t\t\t\tvar target = manipulationTarget( this, elem );\r\n\t\t\t\ttarget.appendChild( elem );\r\n\t\t\t}\r\n\t\t});\r\n\t},\r\n\r\n\tprepend: function() {\r\n\t\treturn this.domManip( arguments, function( elem ) {\r\n\t\t\tif ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {\r\n\t\t\t\tvar target = manipulationTarget( this, elem );\r\n\t\t\t\ttarget.insertBefore( elem, target.firstChild );\r\n\t\t\t}\r\n\t\t});\r\n\t},\r\n\r\n\tbefore: function() {\r\n\t\treturn this.domManip( arguments, function( elem ) {\r\n\t\t\tif ( this.parentNode ) {\r\n\t\t\t\tthis.parentNode.insertBefore( elem, this );\r\n\t\t\t}\r\n\t\t});\r\n\t},\r\n\r\n\tafter: function() {\r\n\t\treturn this.domManip( arguments, function( elem ) {\r\n\t\t\tif ( this.parentNode ) {\r\n\t\t\t\tthis.parentNode.insertBefore( elem, this.nextSibling );\r\n\t\t\t}\r\n\t\t});\r\n\t},\r\n\r\n\t// keepData is for internal use only--do not document\r\n\tremove: function( selector, keepData ) {\r\n\t\tvar elem,\r\n\t\t\telems = selector ? jQuery.filter( selector, this ) : this,\r\n\t\t\ti = 0;\r\n\r\n\t\tfor ( ; (elem = elems[i]) != null; i++ ) {\r\n\r\n\t\t\tif ( !keepData && elem.nodeType === 1 ) {\r\n\t\t\t\tjQuery.cleanData( getAll( elem ) );\r\n\t\t\t}\r\n\r\n\t\t\tif ( elem.parentNode ) {\r\n\t\t\t\tif ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) {\r\n\t\t\t\t\tsetGlobalEval( getAll( elem, \"script\" ) );\r\n\t\t\t\t}\r\n\t\t\t\telem.parentNode.removeChild( elem );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn this;\r\n\t},\r\n\r\n\tempty: function() {\r\n\t\tvar elem,\r\n\t\t\ti = 0;\r\n\r\n\t\tfor ( ; (elem = this[i]) != null; i++ ) {\r\n\t\t\t// Remove element nodes and prevent memory leaks\r\n\t\t\tif ( elem.nodeType === 1 ) {\r\n\t\t\t\tjQuery.cleanData( getAll( elem, false ) );\r\n\t\t\t}\r\n\r\n\t\t\t// Remove any remaining nodes\r\n\t\t\twhile ( elem.firstChild ) {\r\n\t\t\t\telem.removeChild( elem.firstChild );\r\n\t\t\t}\r\n\r\n\t\t\t// If this is a select, ensure that it displays empty (#12336)\r\n\t\t\t// Support: IE<9\r\n\t\t\tif ( elem.options && jQuery.nodeName( elem, \"select\" ) ) {\r\n\t\t\t\telem.options.length = 0;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn this;\r\n\t},\r\n\r\n\tclone: function( dataAndEvents, deepDataAndEvents ) {\r\n\t\tdataAndEvents = dataAndEvents == null ? false : dataAndEvents;\r\n\t\tdeepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;\r\n\r\n\t\treturn this.map( function () {\r\n\t\t\treturn jQuery.clone( this, dataAndEvents, deepDataAndEvents );\r\n\t\t});\r\n\t},\r\n\r\n\thtml: function( value ) {\r\n\t\treturn jQuery.access( this, function( value ) {\r\n\t\t\tvar elem = this[0] || {},\r\n\t\t\t\ti = 0,\r\n\t\t\t\tl = this.length;\r\n\r\n\t\t\tif ( value === undefined ) {\r\n\t\t\t\treturn elem.nodeType === 1 ?\r\n\t\t\t\t\telem.innerHTML.replace( rinlinejQuery, \"\" ) :\r\n\t\t\t\t\tundefined;\r\n\t\t\t}\r\n\r\n\t\t\t// See if we can take a shortcut and just use innerHTML\r\n\t\t\tif ( typeof value === \"string\" && !rnoInnerhtml.test( value ) &&\r\n\t\t\t\t( jQuery.support.htmlSerialize || !rnoshimcache.test( value )  ) &&\r\n\t\t\t\t( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) &&\r\n\t\t\t\t!wrapMap[ ( rtagName.exec( value ) || [\"\", \"\"] )[1].toLowerCase() ] ) {\r\n\r\n\t\t\t\tvalue = value.replace( rxhtmlTag, \"<$1></$2>\" );\r\n\r\n\t\t\t\ttry {\r\n\t\t\t\t\tfor (; i < l; i++ ) {\r\n\t\t\t\t\t\t// Remove element nodes and prevent memory leaks\r\n\t\t\t\t\t\telem = this[i] || {};\r\n\t\t\t\t\t\tif ( elem.nodeType === 1 ) {\r\n\t\t\t\t\t\t\tjQuery.cleanData( getAll( elem, false ) );\r\n\t\t\t\t\t\t\telem.innerHTML = value;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\telem = 0;\r\n\r\n\t\t\t\t// If using innerHTML throws an exception, use the fallback method\r\n\t\t\t\t} catch(e) {}\r\n\t\t\t}\r\n\r\n\t\t\tif ( elem ) {\r\n\t\t\t\tthis.empty().append( value );\r\n\t\t\t}\r\n\t\t}, null, value, arguments.length );\r\n\t},\r\n\r\n\treplaceWith: function() {\r\n\t\tvar\r\n\t\t\t// Snapshot the DOM in case .domManip sweeps something relevant into its fragment\r\n\t\t\targs = jQuery.map( this, function( elem ) {\r\n\t\t\t\treturn [ elem.nextSibling, elem.parentNode ];\r\n\t\t\t}),\r\n\t\t\ti = 0;\r\n\r\n\t\t// Make the changes, replacing each context element with the new content\r\n\t\tthis.domManip( arguments, function( elem ) {\r\n\t\t\tvar next = args[ i++ ],\r\n\t\t\t\tparent = args[ i++ ];\r\n\r\n\t\t\tif ( parent ) {\r\n\t\t\t\t// Don't use the snapshot next if it has moved (#13810)\r\n\t\t\t\tif ( next && next.parentNode !== parent ) {\r\n\t\t\t\t\tnext = this.nextSibling;\r\n\t\t\t\t}\r\n\t\t\t\tjQuery( this ).remove();\r\n\t\t\t\tparent.insertBefore( elem, next );\r\n\t\t\t}\r\n\t\t// Allow new content to include elements from the context set\r\n\t\t}, true );\r\n\r\n\t\t// Force removal if there was no new content (e.g., from empty arguments)\r\n\t\treturn i ? this : this.remove();\r\n\t},\r\n\r\n\tdetach: function( selector ) {\r\n\t\treturn this.remove( selector, true );\r\n\t},\r\n\r\n\tdomManip: function( args, callback, allowIntersection ) {\r\n\r\n\t\t// Flatten any nested arrays\r\n\t\targs = core_concat.apply( [], args );\r\n\r\n\t\tvar first, node, hasScripts,\r\n\t\t\tscripts, doc, fragment,\r\n\t\t\ti = 0,\r\n\t\t\tl = this.length,\r\n\t\t\tset = this,\r\n\t\t\tiNoClone = l - 1,\r\n\t\t\tvalue = args[0],\r\n\t\t\tisFunction = jQuery.isFunction( value );\r\n\r\n\t\t// We can't cloneNode fragments that contain checked, in WebKit\r\n\t\tif ( isFunction || !( l <= 1 || typeof value !== \"string\" || jQuery.support.checkClone || !rchecked.test( value ) ) ) {\r\n\t\t\treturn this.each(function( index ) {\r\n\t\t\t\tvar self = set.eq( index );\r\n\t\t\t\tif ( isFunction ) {\r\n\t\t\t\t\targs[0] = value.call( this, index, self.html() );\r\n\t\t\t\t}\r\n\t\t\t\tself.domManip( args, callback, allowIntersection );\r\n\t\t\t});\r\n\t\t}\r\n\r\n\t\tif ( l ) {\r\n\t\t\tfragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, !allowIntersection && this );\r\n\t\t\tfirst = fragment.firstChild;\r\n\r\n\t\t\tif ( fragment.childNodes.length === 1 ) {\r\n\t\t\t\tfragment = first;\r\n\t\t\t}\r\n\r\n\t\t\tif ( first ) {\r\n\t\t\t\tscripts = jQuery.map( getAll( fragment, \"script\" ), disableScript );\r\n\t\t\t\thasScripts = scripts.length;\r\n\r\n\t\t\t\t// Use the original fragment for the last item instead of the first because it can end up\r\n\t\t\t\t// being emptied incorrectly in certain situations (#8070).\r\n\t\t\t\tfor ( ; i < l; i++ ) {\r\n\t\t\t\t\tnode = fragment;\r\n\r\n\t\t\t\t\tif ( i !== iNoClone ) {\r\n\t\t\t\t\t\tnode = jQuery.clone( node, true, true );\r\n\r\n\t\t\t\t\t\t// Keep references to cloned scripts for later restoration\r\n\t\t\t\t\t\tif ( hasScripts ) {\r\n\t\t\t\t\t\t\tjQuery.merge( scripts, getAll( node, \"script\" ) );\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tcallback.call( this[i], node, i );\r\n\t\t\t\t}\r\n\r\n\t\t\t\tif ( hasScripts ) {\r\n\t\t\t\t\tdoc = scripts[ scripts.length - 1 ].ownerDocument;\r\n\r\n\t\t\t\t\t// Reenable scripts\r\n\t\t\t\t\tjQuery.map( scripts, restoreScript );\r\n\r\n\t\t\t\t\t// Evaluate executable scripts on first document insertion\r\n\t\t\t\t\tfor ( i = 0; i < hasScripts; i++ ) {\r\n\t\t\t\t\t\tnode = scripts[ i ];\r\n\t\t\t\t\t\tif ( rscriptType.test( node.type || \"\" ) &&\r\n\t\t\t\t\t\t\t!jQuery._data( node, \"globalEval\" ) && jQuery.contains( doc, node ) ) {\r\n\r\n\t\t\t\t\t\t\tif ( node.src ) {\r\n\t\t\t\t\t\t\t\t// Hope ajax is available...\r\n\t\t\t\t\t\t\t\tjQuery._evalUrl( node.src );\r\n\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\tjQuery.globalEval( ( node.text || node.textContent || node.innerHTML || \"\" ).replace( rcleanScript, \"\" ) );\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Fix #11809: Avoid leaking memory\r\n\t\t\t\tfragment = first = null;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn this;\r\n\t}\r\n});\r\n\r\n// Support: IE<8\r\n// Manipulating tables requires a tbody\r\nfunction manipulationTarget( elem, content ) {\r\n\treturn jQuery.nodeName( elem, \"table\" ) &&\r\n\t\tjQuery.nodeName( content.nodeType === 1 ? content : content.firstChild, \"tr\" ) ?\r\n\r\n\t\telem.getElementsByTagName(\"tbody\")[0] ||\r\n\t\t\telem.appendChild( elem.ownerDocument.createElement(\"tbody\") ) :\r\n\t\telem;\r\n}\r\n\r\n// Replace/restore the type attribute of script elements for safe DOM manipulation\r\nfunction disableScript( elem ) {\r\n\telem.type = (jQuery.find.attr( elem, \"type\" ) !== null) + \"/\" + elem.type;\r\n\treturn elem;\r\n}\r\nfunction restoreScript( elem ) {\r\n\tvar match = rscriptTypeMasked.exec( elem.type );\r\n\tif ( match ) {\r\n\t\telem.type = match[1];\r\n\t} else {\r\n\t\telem.removeAttribute(\"type\");\r\n\t}\r\n\treturn elem;\r\n}\r\n\r\n// Mark scripts as having already been evaluated\r\nfunction setGlobalEval( elems, refElements ) {\r\n\tvar elem,\r\n\t\ti = 0;\r\n\tfor ( ; (elem = elems[i]) != null; i++ ) {\r\n\t\tjQuery._data( elem, \"globalEval\", !refElements || jQuery._data( refElements[i], \"globalEval\" ) );\r\n\t}\r\n}\r\n\r\nfunction cloneCopyEvent( src, dest ) {\r\n\r\n\tif ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {\r\n\t\treturn;\r\n\t}\r\n\r\n\tvar type, i, l,\r\n\t\toldData = jQuery._data( src ),\r\n\t\tcurData = jQuery._data( dest, oldData ),\r\n\t\tevents = oldData.events;\r\n\r\n\tif ( events ) {\r\n\t\tdelete curData.handle;\r\n\t\tcurData.events = {};\r\n\r\n\t\tfor ( type in events ) {\r\n\t\t\tfor ( i = 0, l = events[ type ].length; i < l; i++ ) {\r\n\t\t\t\tjQuery.event.add( dest, type, events[ type ][ i ] );\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t// make the cloned public data object a copy from the original\r\n\tif ( curData.data ) {\r\n\t\tcurData.data = jQuery.extend( {}, curData.data );\r\n\t}\r\n}\r\n\r\nfunction fixCloneNodeIssues( src, dest ) {\r\n\tvar nodeName, e, data;\r\n\r\n\t// We do not need to do anything for non-Elements\r\n\tif ( dest.nodeType !== 1 ) {\r\n\t\treturn;\r\n\t}\r\n\r\n\tnodeName = dest.nodeName.toLowerCase();\r\n\r\n\t// IE6-8 copies events bound via attachEvent when using cloneNode.\r\n\tif ( !jQuery.support.noCloneEvent && dest[ jQuery.expando ] ) {\r\n\t\tdata = jQuery._data( dest );\r\n\r\n\t\tfor ( e in data.events ) {\r\n\t\t\tjQuery.removeEvent( dest, e, data.handle );\r\n\t\t}\r\n\r\n\t\t// Event data gets referenced instead of copied if the expando gets copied too\r\n\t\tdest.removeAttribute( jQuery.expando );\r\n\t}\r\n\r\n\t// IE blanks contents when cloning scripts, and tries to evaluate newly-set text\r\n\tif ( nodeName === \"script\" && dest.text !== src.text ) {\r\n\t\tdisableScript( dest ).text = src.text;\r\n\t\trestoreScript( dest );\r\n\r\n\t// IE6-10 improperly clones children of object elements using classid.\r\n\t// IE10 throws NoModificationAllowedError if parent is null, #12132.\r\n\t} else if ( nodeName === \"object\" ) {\r\n\t\tif ( dest.parentNode ) {\r\n\t\t\tdest.outerHTML = src.outerHTML;\r\n\t\t}\r\n\r\n\t\t// This path appears unavoidable for IE9. When cloning an object\r\n\t\t// element in IE9, the outerHTML strategy above is not sufficient.\r\n\t\t// If the src has innerHTML and the destination does not,\r\n\t\t// copy the src.innerHTML into the dest.innerHTML. #10324\r\n\t\tif ( jQuery.support.html5Clone && ( src.innerHTML && !jQuery.trim(dest.innerHTML) ) ) {\r\n\t\t\tdest.innerHTML = src.innerHTML;\r\n\t\t}\r\n\r\n\t} else if ( nodeName === \"input\" && manipulation_rcheckableType.test( src.type ) ) {\r\n\t\t// IE6-8 fails to persist the checked state of a cloned checkbox\r\n\t\t// or radio button. Worse, IE6-7 fail to give the cloned element\r\n\t\t// a checked appearance if the defaultChecked value isn't also set\r\n\r\n\t\tdest.defaultChecked = dest.checked = src.checked;\r\n\r\n\t\t// IE6-7 get confused and end up setting the value of a cloned\r\n\t\t// checkbox/radio button to an empty string instead of \"on\"\r\n\t\tif ( dest.value !== src.value ) {\r\n\t\t\tdest.value = src.value;\r\n\t\t}\r\n\r\n\t// IE6-8 fails to return the selected option to the default selected\r\n\t// state when cloning options\r\n\t} else if ( nodeName === \"option\" ) {\r\n\t\tdest.defaultSelected = dest.selected = src.defaultSelected;\r\n\r\n\t// IE6-8 fails to set the defaultValue to the correct value when\r\n\t// cloning other types of input fields\r\n\t} else if ( nodeName === \"input\" || nodeName === \"textarea\" ) {\r\n\t\tdest.defaultValue = src.defaultValue;\r\n\t}\r\n}\r\n\r\njQuery.each({\r\n\tappendTo: \"append\",\r\n\tprependTo: \"prepend\",\r\n\tinsertBefore: \"before\",\r\n\tinsertAfter: \"after\",\r\n\treplaceAll: \"replaceWith\"\r\n}, function( name, original ) {\r\n\tjQuery.fn[ name ] = function( selector ) {\r\n\t\tvar elems,\r\n\t\t\ti = 0,\r\n\t\t\tret = [],\r\n\t\t\tinsert = jQuery( selector ),\r\n\t\t\tlast = insert.length - 1;\r\n\r\n\t\tfor ( ; i <= last; i++ ) {\r\n\t\t\telems = i === last ? this : this.clone(true);\r\n\t\t\tjQuery( insert[i] )[ original ]( elems );\r\n\r\n\t\t\t// Modern browsers can apply jQuery collections as arrays, but oldIE needs a .get()\r\n\t\t\tcore_push.apply( ret, elems.get() );\r\n\t\t}\r\n\r\n\t\treturn this.pushStack( ret );\r\n\t};\r\n});\r\n\r\nfunction getAll( context, tag ) {\r\n\tvar elems, elem,\r\n\t\ti = 0,\r\n\t\tfound = typeof context.getElementsByTagName !== core_strundefined ? context.getElementsByTagName( tag || \"*\" ) :\r\n\t\t\ttypeof context.querySelectorAll !== core_strundefined ? context.querySelectorAll( tag || \"*\" ) :\r\n\t\t\tundefined;\r\n\r\n\tif ( !found ) {\r\n\t\tfor ( found = [], elems = context.childNodes || context; (elem = elems[i]) != null; i++ ) {\r\n\t\t\tif ( !tag || jQuery.nodeName( elem, tag ) ) {\r\n\t\t\t\tfound.push( elem );\r\n\t\t\t} else {\r\n\t\t\t\tjQuery.merge( found, getAll( elem, tag ) );\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\treturn tag === undefined || tag && jQuery.nodeName( context, tag ) ?\r\n\t\tjQuery.merge( [ context ], found ) :\r\n\t\tfound;\r\n}\r\n\r\n// Used in buildFragment, fixes the defaultChecked property\r\nfunction fixDefaultChecked( elem ) {\r\n\tif ( manipulation_rcheckableType.test( elem.type ) ) {\r\n\t\telem.defaultChecked = elem.checked;\r\n\t}\r\n}\r\n\r\njQuery.extend({\r\n\tclone: function( elem, dataAndEvents, deepDataAndEvents ) {\r\n\t\tvar destElements, node, clone, i, srcElements,\r\n\t\t\tinPage = jQuery.contains( elem.ownerDocument, elem );\r\n\r\n\t\tif ( jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( \"<\" + elem.nodeName + \">\" ) ) {\r\n\t\t\tclone = elem.cloneNode( true );\r\n\r\n\t\t// IE<=8 does not properly clone detached, unknown element nodes\r\n\t\t} else {\r\n\t\t\tfragmentDiv.innerHTML = elem.outerHTML;\r\n\t\t\tfragmentDiv.removeChild( clone = fragmentDiv.firstChild );\r\n\t\t}\r\n\r\n\t\tif ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) &&\r\n\t\t\t\t(elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {\r\n\r\n\t\t\t// We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2\r\n\t\t\tdestElements = getAll( clone );\r\n\t\t\tsrcElements = getAll( elem );\r\n\r\n\t\t\t// Fix all IE cloning issues\r\n\t\t\tfor ( i = 0; (node = srcElements[i]) != null; ++i ) {\r\n\t\t\t\t// Ensure that the destination node is not null; Fixes #9587\r\n\t\t\t\tif ( destElements[i] ) {\r\n\t\t\t\t\tfixCloneNodeIssues( node, destElements[i] );\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Copy the events from the original to the clone\r\n\t\tif ( dataAndEvents ) {\r\n\t\t\tif ( deepDataAndEvents ) {\r\n\t\t\t\tsrcElements = srcElements || getAll( elem );\r\n\t\t\t\tdestElements = destElements || getAll( clone );\r\n\r\n\t\t\t\tfor ( i = 0; (node = srcElements[i]) != null; i++ ) {\r\n\t\t\t\t\tcloneCopyEvent( node, destElements[i] );\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\tcloneCopyEvent( elem, clone );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Preserve script evaluation history\r\n\t\tdestElements = getAll( clone, \"script\" );\r\n\t\tif ( destElements.length > 0 ) {\r\n\t\t\tsetGlobalEval( destElements, !inPage && getAll( elem, \"script\" ) );\r\n\t\t}\r\n\r\n\t\tdestElements = srcElements = node = null;\r\n\r\n\t\t// Return the cloned set\r\n\t\treturn clone;\r\n\t},\r\n\r\n\tbuildFragment: function( elems, context, scripts, selection ) {\r\n\t\tvar j, elem, contains,\r\n\t\t\ttmp, tag, tbody, wrap,\r\n\t\t\tl = elems.length,\r\n\r\n\t\t\t// Ensure a safe fragment\r\n\t\t\tsafe = createSafeFragment( context ),\r\n\r\n\t\t\tnodes = [],\r\n\t\t\ti = 0;\r\n\r\n\t\tfor ( ; i < l; i++ ) {\r\n\t\t\telem = elems[ i ];\r\n\r\n\t\t\tif ( elem || elem === 0 ) {\r\n\r\n\t\t\t\t// Add nodes directly\r\n\t\t\t\tif ( jQuery.type( elem ) === \"object\" ) {\r\n\t\t\t\t\tjQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );\r\n\r\n\t\t\t\t// Convert non-html into a text node\r\n\t\t\t\t} else if ( !rhtml.test( elem ) ) {\r\n\t\t\t\t\tnodes.push( context.createTextNode( elem ) );\r\n\r\n\t\t\t\t// Convert html into DOM nodes\r\n\t\t\t\t} else {\r\n\t\t\t\t\ttmp = tmp || safe.appendChild( context.createElement(\"div\") );\r\n\r\n\t\t\t\t\t// Deserialize a standard representation\r\n\t\t\t\t\ttag = ( rtagName.exec( elem ) || [\"\", \"\"] )[1].toLowerCase();\r\n\t\t\t\t\twrap = wrapMap[ tag ] || wrapMap._default;\r\n\r\n\t\t\t\t\ttmp.innerHTML = wrap[1] + elem.replace( rxhtmlTag, \"<$1></$2>\" ) + wrap[2];\r\n\r\n\t\t\t\t\t// Descend through wrappers to the right content\r\n\t\t\t\t\tj = wrap[0];\r\n\t\t\t\t\twhile ( j-- ) {\r\n\t\t\t\t\t\ttmp = tmp.lastChild;\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t// Manually add leading whitespace removed by IE\r\n\t\t\t\t\tif ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {\r\n\t\t\t\t\t\tnodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[0] ) );\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t// Remove IE's autoinserted <tbody> from table fragments\r\n\t\t\t\t\tif ( !jQuery.support.tbody ) {\r\n\r\n\t\t\t\t\t\t// String was a <table>, *may* have spurious <tbody>\r\n\t\t\t\t\t\telem = tag === \"table\" && !rtbody.test( elem ) ?\r\n\t\t\t\t\t\t\ttmp.firstChild :\r\n\r\n\t\t\t\t\t\t\t// String was a bare <thead> or <tfoot>\r\n\t\t\t\t\t\t\twrap[1] === \"<table>\" && !rtbody.test( elem ) ?\r\n\t\t\t\t\t\t\t\ttmp :\r\n\t\t\t\t\t\t\t\t0;\r\n\r\n\t\t\t\t\t\tj = elem && elem.childNodes.length;\r\n\t\t\t\t\t\twhile ( j-- ) {\r\n\t\t\t\t\t\t\tif ( jQuery.nodeName( (tbody = elem.childNodes[j]), \"tbody\" ) && !tbody.childNodes.length ) {\r\n\t\t\t\t\t\t\t\telem.removeChild( tbody );\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\tjQuery.merge( nodes, tmp.childNodes );\r\n\r\n\t\t\t\t\t// Fix #12392 for WebKit and IE > 9\r\n\t\t\t\t\ttmp.textContent = \"\";\r\n\r\n\t\t\t\t\t// Fix #12392 for oldIE\r\n\t\t\t\t\twhile ( tmp.firstChild ) {\r\n\t\t\t\t\t\ttmp.removeChild( tmp.firstChild );\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t// Remember the top-level container for proper cleanup\r\n\t\t\t\t\ttmp = safe.lastChild;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Fix #11356: Clear elements from fragment\r\n\t\tif ( tmp ) {\r\n\t\t\tsafe.removeChild( tmp );\r\n\t\t}\r\n\r\n\t\t// Reset defaultChecked for any radios and checkboxes\r\n\t\t// about to be appended to the DOM in IE 6/7 (#8060)\r\n\t\tif ( !jQuery.support.appendChecked ) {\r\n\t\t\tjQuery.grep( getAll( nodes, \"input\" ), fixDefaultChecked );\r\n\t\t}\r\n\r\n\t\ti = 0;\r\n\t\twhile ( (elem = nodes[ i++ ]) ) {\r\n\r\n\t\t\t// #4087 - If origin and destination elements are the same, and this is\r\n\t\t\t// that element, do not do anything\r\n\t\t\tif ( selection && jQuery.inArray( elem, selection ) !== -1 ) {\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\r\n\t\t\tcontains = jQuery.contains( elem.ownerDocument, elem );\r\n\r\n\t\t\t// Append to fragment\r\n\t\t\ttmp = getAll( safe.appendChild( elem ), \"script\" );\r\n\r\n\t\t\t// Preserve script evaluation history\r\n\t\t\tif ( contains ) {\r\n\t\t\t\tsetGlobalEval( tmp );\r\n\t\t\t}\r\n\r\n\t\t\t// Capture executables\r\n\t\t\tif ( scripts ) {\r\n\t\t\t\tj = 0;\r\n\t\t\t\twhile ( (elem = tmp[ j++ ]) ) {\r\n\t\t\t\t\tif ( rscriptType.test( elem.type || \"\" ) ) {\r\n\t\t\t\t\t\tscripts.push( elem );\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\ttmp = null;\r\n\r\n\t\treturn safe;\r\n\t},\r\n\r\n\tcleanData: function( elems, /* internal */ acceptData ) {\r\n\t\tvar elem, type, id, data,\r\n\t\t\ti = 0,\r\n\t\t\tinternalKey = jQuery.expando,\r\n\t\t\tcache = jQuery.cache,\r\n\t\t\tdeleteExpando = jQuery.support.deleteExpando,\r\n\t\t\tspecial = jQuery.event.special;\r\n\r\n\t\tfor ( ; (elem = elems[i]) != null; i++ ) {\r\n\r\n\t\t\tif ( acceptData || jQuery.acceptData( elem ) ) {\r\n\r\n\t\t\t\tid = elem[ internalKey ];\r\n\t\t\t\tdata = id && cache[ id ];\r\n\r\n\t\t\t\tif ( data ) {\r\n\t\t\t\t\tif ( data.events ) {\r\n\t\t\t\t\t\tfor ( type in data.events ) {\r\n\t\t\t\t\t\t\tif ( special[ type ] ) {\r\n\t\t\t\t\t\t\t\tjQuery.event.remove( elem, type );\r\n\r\n\t\t\t\t\t\t\t// This is a shortcut to avoid jQuery.event.remove's overhead\r\n\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\tjQuery.removeEvent( elem, type, data.handle );\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t// Remove cache only if it was not already removed by jQuery.event.remove\r\n\t\t\t\t\tif ( cache[ id ] ) {\r\n\r\n\t\t\t\t\t\tdelete cache[ id ];\r\n\r\n\t\t\t\t\t\t// IE does not allow us to delete expando properties from nodes,\r\n\t\t\t\t\t\t// nor does it have a removeAttribute function on Document nodes;\r\n\t\t\t\t\t\t// we must handle all of these cases\r\n\t\t\t\t\t\tif ( deleteExpando ) {\r\n\t\t\t\t\t\t\tdelete elem[ internalKey ];\r\n\r\n\t\t\t\t\t\t} else if ( typeof elem.removeAttribute !== core_strundefined ) {\r\n\t\t\t\t\t\t\telem.removeAttribute( internalKey );\r\n\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\telem[ internalKey ] = null;\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\tcore_deletedIds.push( id );\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\r\n\t_evalUrl: function( url ) {\r\n\t\treturn jQuery.ajax({\r\n\t\t\turl: url,\r\n\t\t\ttype: \"GET\",\r\n\t\t\tdataType: \"script\",\r\n\t\t\tasync: false,\r\n\t\t\tglobal: false,\r\n\t\t\t\"throws\": true\r\n\t\t});\r\n\t}\r\n});\r\njQuery.fn.extend({\r\n\twrapAll: function( html ) {\r\n\t\tif ( jQuery.isFunction( html ) ) {\r\n\t\t\treturn this.each(function(i) {\r\n\t\t\t\tjQuery(this).wrapAll( html.call(this, i) );\r\n\t\t\t});\r\n\t\t}\r\n\r\n\t\tif ( this[0] ) {\r\n\t\t\t// The elements to wrap the target around\r\n\t\t\tvar wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);\r\n\r\n\t\t\tif ( this[0].parentNode ) {\r\n\t\t\t\twrap.insertBefore( this[0] );\r\n\t\t\t}\r\n\r\n\t\t\twrap.map(function() {\r\n\t\t\t\tvar elem = this;\r\n\r\n\t\t\t\twhile ( elem.firstChild && elem.firstChild.nodeType === 1 ) {\r\n\t\t\t\t\telem = elem.firstChild;\r\n\t\t\t\t}\r\n\r\n\t\t\t\treturn elem;\r\n\t\t\t}).append( this );\r\n\t\t}\r\n\r\n\t\treturn this;\r\n\t},\r\n\r\n\twrapInner: function( html ) {\r\n\t\tif ( jQuery.isFunction( html ) ) {\r\n\t\t\treturn this.each(function(i) {\r\n\t\t\t\tjQuery(this).wrapInner( html.call(this, i) );\r\n\t\t\t});\r\n\t\t}\r\n\r\n\t\treturn this.each(function() {\r\n\t\t\tvar self = jQuery( this ),\r\n\t\t\t\tcontents = self.contents();\r\n\r\n\t\t\tif ( contents.length ) {\r\n\t\t\t\tcontents.wrapAll( html );\r\n\r\n\t\t\t} else {\r\n\t\t\t\tself.append( html );\r\n\t\t\t}\r\n\t\t});\r\n\t},\r\n\r\n\twrap: function( html ) {\r\n\t\tvar isFunction = jQuery.isFunction( html );\r\n\r\n\t\treturn this.each(function(i) {\r\n\t\t\tjQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );\r\n\t\t});\r\n\t},\r\n\r\n\tunwrap: function() {\r\n\t\treturn this.parent().each(function() {\r\n\t\t\tif ( !jQuery.nodeName( this, \"body\" ) ) {\r\n\t\t\t\tjQuery( this ).replaceWith( this.childNodes );\r\n\t\t\t}\r\n\t\t}).end();\r\n\t}\r\n});\r\nvar iframe, getStyles, curCSS,\r\n\tralpha = /alpha\\([^)]*\\)/i,\r\n\tropacity = /opacity\\s*=\\s*([^)]*)/,\r\n\trposition = /^(top|right|bottom|left)$/,\r\n\t// swappable if display is none or starts with table except \"table\", \"table-cell\", or \"table-caption\"\r\n\t// see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display\r\n\trdisplayswap = /^(none|table(?!-c[ea]).+)/,\r\n\trmargin = /^margin/,\r\n\trnumsplit = new RegExp( \"^(\" + core_pnum + \")(.*)$\", \"i\" ),\r\n\trnumnonpx = new RegExp( \"^(\" + core_pnum + \")(?!px)[a-z%]+$\", \"i\" ),\r\n\trrelNum = new RegExp( \"^([+-])=(\" + core_pnum + \")\", \"i\" ),\r\n\telemdisplay = { BODY: \"block\" },\r\n\r\n\tcssShow = { position: \"absolute\", visibility: \"hidden\", display: \"block\" },\r\n\tcssNormalTransform = {\r\n\t\tletterSpacing: 0,\r\n\t\tfontWeight: 400\r\n\t},\r\n\r\n\tcssExpand = [ \"Top\", \"Right\", \"Bottom\", \"Left\" ],\r\n\tcssPrefixes = [ \"Webkit\", \"O\", \"Moz\", \"ms\" ];\r\n\r\n// return a css property mapped to a potentially vendor prefixed property\r\nfunction vendorPropName( style, name ) {\r\n\r\n\t// shortcut for names that are not vendor prefixed\r\n\tif ( name in style ) {\r\n\t\treturn name;\r\n\t}\r\n\r\n\t// check for vendor prefixed names\r\n\tvar capName = name.charAt(0).toUpperCase() + name.slice(1),\r\n\t\torigName = name,\r\n\t\ti = cssPrefixes.length;\r\n\r\n\twhile ( i-- ) {\r\n\t\tname = cssPrefixes[ i ] + capName;\r\n\t\tif ( name in style ) {\r\n\t\t\treturn name;\r\n\t\t}\r\n\t}\r\n\r\n\treturn origName;\r\n}\r\n\r\nfunction isHidden( elem, el ) {\r\n\t// isHidden might be called from jQuery#filter function;\r\n\t// in that case, element will be second argument\r\n\telem = el || elem;\r\n\treturn jQuery.css( elem, \"display\" ) === \"none\" || !jQuery.contains( elem.ownerDocument, elem );\r\n}\r\n\r\nfunction showHide( elements, show ) {\r\n\tvar display, elem, hidden,\r\n\t\tvalues = [],\r\n\t\tindex = 0,\r\n\t\tlength = elements.length;\r\n\r\n\tfor ( ; index < length; index++ ) {\r\n\t\telem = elements[ index ];\r\n\t\tif ( !elem.style ) {\r\n\t\t\tcontinue;\r\n\t\t}\r\n\r\n\t\tvalues[ index ] = jQuery._data( elem, \"olddisplay\" );\r\n\t\tdisplay = elem.style.display;\r\n\t\tif ( show ) {\r\n\t\t\t// Reset the inline display of this element to learn if it is\r\n\t\t\t// being hidden by cascaded rules or not\r\n\t\t\tif ( !values[ index ] && display === \"none\" ) {\r\n\t\t\t\telem.style.display = \"\";\r\n\t\t\t}\r\n\r\n\t\t\t// Set elements which have been overridden with display: none\r\n\t\t\t// in a stylesheet to whatever the default browser style is\r\n\t\t\t// for such an element\r\n\t\t\tif ( elem.style.display === \"\" && isHidden( elem ) ) {\r\n\t\t\t\tvalues[ index ] = jQuery._data( elem, \"olddisplay\", css_defaultDisplay(elem.nodeName) );\r\n\t\t\t}\r\n\t\t} else {\r\n\r\n\t\t\tif ( !values[ index ] ) {\r\n\t\t\t\thidden = isHidden( elem );\r\n\r\n\t\t\t\tif ( display && display !== \"none\" || !hidden ) {\r\n\t\t\t\t\tjQuery._data( elem, \"olddisplay\", hidden ? display : jQuery.css( elem, \"display\" ) );\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t// Set the display of most of the elements in a second loop\r\n\t// to avoid the constant reflow\r\n\tfor ( index = 0; index < length; index++ ) {\r\n\t\telem = elements[ index ];\r\n\t\tif ( !elem.style ) {\r\n\t\t\tcontinue;\r\n\t\t}\r\n\t\tif ( !show || elem.style.display === \"none\" || elem.style.display === \"\" ) {\r\n\t\t\telem.style.display = show ? values[ index ] || \"\" : \"none\";\r\n\t\t}\r\n\t}\r\n\r\n\treturn elements;\r\n}\r\n\r\njQuery.fn.extend({\r\n\tcss: function( name, value ) {\r\n\t\treturn jQuery.access( this, function( elem, name, value ) {\r\n\t\t\tvar len, styles,\r\n\t\t\t\tmap = {},\r\n\t\t\t\ti = 0;\r\n\r\n\t\t\tif ( jQuery.isArray( name ) ) {\r\n\t\t\t\tstyles = getStyles( elem );\r\n\t\t\t\tlen = name.length;\r\n\r\n\t\t\t\tfor ( ; i < len; i++ ) {\r\n\t\t\t\t\tmap[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );\r\n\t\t\t\t}\r\n\r\n\t\t\t\treturn map;\r\n\t\t\t}\r\n\r\n\t\t\treturn value !== undefined ?\r\n\t\t\t\tjQuery.style( elem, name, value ) :\r\n\t\t\t\tjQuery.css( elem, name );\r\n\t\t}, name, value, arguments.length > 1 );\r\n\t},\r\n\tshow: function() {\r\n\t\treturn showHide( this, true );\r\n\t},\r\n\thide: function() {\r\n\t\treturn showHide( this );\r\n\t},\r\n\ttoggle: function( state ) {\r\n\t\tif ( typeof state === \"boolean\" ) {\r\n\t\t\treturn state ? this.show() : this.hide();\r\n\t\t}\r\n\r\n\t\treturn this.each(function() {\r\n\t\t\tif ( isHidden( this ) ) {\r\n\t\t\t\tjQuery( this ).show();\r\n\t\t\t} else {\r\n\t\t\t\tjQuery( this ).hide();\r\n\t\t\t}\r\n\t\t});\r\n\t}\r\n});\r\n\r\njQuery.extend({\r\n\t// Add in style property hooks for overriding the default\r\n\t// behavior of getting and setting a style property\r\n\tcssHooks: {\r\n\t\topacity: {\r\n\t\t\tget: function( elem, computed ) {\r\n\t\t\t\tif ( computed ) {\r\n\t\t\t\t\t// We should always get a number back from opacity\r\n\t\t\t\t\tvar ret = curCSS( elem, \"opacity\" );\r\n\t\t\t\t\treturn ret === \"\" ? \"1\" : ret;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t},\r\n\r\n\t// Don't automatically add \"px\" to these possibly-unitless properties\r\n\tcssNumber: {\r\n\t\t\"columnCount\": true,\r\n\t\t\"fillOpacity\": true,\r\n\t\t\"fontWeight\": true,\r\n\t\t\"lineHeight\": true,\r\n\t\t\"opacity\": true,\r\n\t\t\"order\": true,\r\n\t\t\"orphans\": true,\r\n\t\t\"widows\": true,\r\n\t\t\"zIndex\": true,\r\n\t\t\"zoom\": true\r\n\t},\r\n\r\n\t// Add in properties whose names you wish to fix before\r\n\t// setting or getting the value\r\n\tcssProps: {\r\n\t\t// normalize float css property\r\n\t\t\"float\": jQuery.support.cssFloat ? \"cssFloat\" : \"styleFloat\"\r\n\t},\r\n\r\n\t// Get and set the style property on a DOM Node\r\n\tstyle: function( elem, name, value, extra ) {\r\n\t\t// Don't set styles on text and comment nodes\r\n\t\tif ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\t// Make sure that we're working with the right name\r\n\t\tvar ret, type, hooks,\r\n\t\t\torigName = jQuery.camelCase( name ),\r\n\t\t\tstyle = elem.style;\r\n\r\n\t\tname = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) );\r\n\r\n\t\t// gets hook for the prefixed version\r\n\t\t// followed by the unprefixed version\r\n\t\thooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];\r\n\r\n\t\t// Check if we're setting a value\r\n\t\tif ( value !== undefined ) {\r\n\t\t\ttype = typeof value;\r\n\r\n\t\t\t// convert relative number strings (+= or -=) to relative numbers. #7345\r\n\t\t\tif ( type === \"string\" && (ret = rrelNum.exec( value )) ) {\r\n\t\t\t\tvalue = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) );\r\n\t\t\t\t// Fixes bug #9237\r\n\t\t\t\ttype = \"number\";\r\n\t\t\t}\r\n\r\n\t\t\t// Make sure that NaN and null values aren't set. See: #7116\r\n\t\t\tif ( value == null || type === \"number\" && isNaN( value ) ) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\t// If a number was passed in, add 'px' to the (except for certain CSS properties)\r\n\t\t\tif ( type === \"number\" && !jQuery.cssNumber[ origName ] ) {\r\n\t\t\t\tvalue += \"px\";\r\n\t\t\t}\r\n\r\n\t\t\t// Fixes #8908, it can be done more correctly by specifing setters in cssHooks,\r\n\t\t\t// but it would mean to define eight (for every problematic property) identical functions\r\n\t\t\tif ( !jQuery.support.clearCloneStyle && value === \"\" && name.indexOf(\"background\") === 0 ) {\r\n\t\t\t\tstyle[ name ] = \"inherit\";\r\n\t\t\t}\r\n\r\n\t\t\t// If a hook was provided, use that value, otherwise just set the specified value\r\n\t\t\tif ( !hooks || !(\"set\" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) {\r\n\r\n\t\t\t\t// Wrapped to prevent IE from throwing errors when 'invalid' values are provided\r\n\t\t\t\t// Fixes bug #5509\r\n\t\t\t\ttry {\r\n\t\t\t\t\tstyle[ name ] = value;\r\n\t\t\t\t} catch(e) {}\r\n\t\t\t}\r\n\r\n\t\t} else {\r\n\t\t\t// If a hook was provided get the non-computed value from there\r\n\t\t\tif ( hooks && \"get\" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {\r\n\t\t\t\treturn ret;\r\n\t\t\t}\r\n\r\n\t\t\t// Otherwise just get the value from the style object\r\n\t\t\treturn style[ name ];\r\n\t\t}\r\n\t},\r\n\r\n\tcss: function( elem, name, extra, styles ) {\r\n\t\tvar num, val, hooks,\r\n\t\t\torigName = jQuery.camelCase( name );\r\n\r\n\t\t// Make sure that we're working with the right name\r\n\t\tname = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) );\r\n\r\n\t\t// gets hook for the prefixed version\r\n\t\t// followed by the unprefixed version\r\n\t\thooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];\r\n\r\n\t\t// If a hook was provided get the computed value from there\r\n\t\tif ( hooks && \"get\" in hooks ) {\r\n\t\t\tval = hooks.get( elem, true, extra );\r\n\t\t}\r\n\r\n\t\t// Otherwise, if a way to get the computed value exists, use that\r\n\t\tif ( val === undefined ) {\r\n\t\t\tval = curCSS( elem, name, styles );\r\n\t\t}\r\n\r\n\t\t//convert \"normal\" to computed value\r\n\t\tif ( val === \"normal\" && name in cssNormalTransform ) {\r\n\t\t\tval = cssNormalTransform[ name ];\r\n\t\t}\r\n\r\n\t\t// Return, converting to number if forced or a qualifier was provided and val looks numeric\r\n\t\tif ( extra === \"\" || extra ) {\r\n\t\t\tnum = parseFloat( val );\r\n\t\t\treturn extra === true || jQuery.isNumeric( num ) ? num || 0 : val;\r\n\t\t}\r\n\t\treturn val;\r\n\t}\r\n});\r\n\r\n// NOTE: we've included the \"window\" in window.getComputedStyle\r\n// because jsdom on node.js will break without it.\r\nif ( window.getComputedStyle ) {\r\n\tgetStyles = function( elem ) {\r\n\t\treturn window.getComputedStyle( elem, null );\r\n\t};\r\n\r\n\tcurCSS = function( elem, name, _computed ) {\r\n\t\tvar width, minWidth, maxWidth,\r\n\t\t\tcomputed = _computed || getStyles( elem ),\r\n\r\n\t\t\t// getPropertyValue is only needed for .css('filter') in IE9, see #12537\r\n\t\t\tret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined,\r\n\t\t\tstyle = elem.style;\r\n\r\n\t\tif ( computed ) {\r\n\r\n\t\t\tif ( ret === \"\" && !jQuery.contains( elem.ownerDocument, elem ) ) {\r\n\t\t\t\tret = jQuery.style( elem, name );\r\n\t\t\t}\r\n\r\n\t\t\t// A tribute to the \"awesome hack by Dean Edwards\"\r\n\t\t\t// Chrome < 17 and Safari 5.0 uses \"computed value\" instead of \"used value\" for margin-right\r\n\t\t\t// Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels\r\n\t\t\t// this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values\r\n\t\t\tif ( rnumnonpx.test( ret ) && rmargin.test( name ) ) {\r\n\r\n\t\t\t\t// Remember the original values\r\n\t\t\t\twidth = style.width;\r\n\t\t\t\tminWidth = style.minWidth;\r\n\t\t\t\tmaxWidth = style.maxWidth;\r\n\r\n\t\t\t\t// Put in the new values to get a computed value out\r\n\t\t\t\tstyle.minWidth = style.maxWidth = style.width = ret;\r\n\t\t\t\tret = computed.width;\r\n\r\n\t\t\t\t// Revert the changed values\r\n\t\t\t\tstyle.width = width;\r\n\t\t\t\tstyle.minWidth = minWidth;\r\n\t\t\t\tstyle.maxWidth = maxWidth;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn ret;\r\n\t};\r\n} else if ( document.documentElement.currentStyle ) {\r\n\tgetStyles = function( elem ) {\r\n\t\treturn elem.currentStyle;\r\n\t};\r\n\r\n\tcurCSS = function( elem, name, _computed ) {\r\n\t\tvar left, rs, rsLeft,\r\n\t\t\tcomputed = _computed || getStyles( elem ),\r\n\t\t\tret = computed ? computed[ name ] : undefined,\r\n\t\t\tstyle = elem.style;\r\n\r\n\t\t// Avoid setting ret to empty string here\r\n\t\t// so we don't default to auto\r\n\t\tif ( ret == null && style && style[ name ] ) {\r\n\t\t\tret = style[ name ];\r\n\t\t}\r\n\r\n\t\t// From the awesome hack by Dean Edwards\r\n\t\t// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291\r\n\r\n\t\t// If we're not dealing with a regular pixel number\r\n\t\t// but a number that has a weird ending, we need to convert it to pixels\r\n\t\t// but not position css attributes, as those are proportional to the parent element instead\r\n\t\t// and we can't measure the parent instead because it might trigger a \"stacking dolls\" problem\r\n\t\tif ( rnumnonpx.test( ret ) && !rposition.test( name ) ) {\r\n\r\n\t\t\t// Remember the original values\r\n\t\t\tleft = style.left;\r\n\t\t\trs = elem.runtimeStyle;\r\n\t\t\trsLeft = rs && rs.left;\r\n\r\n\t\t\t// Put in the new values to get a computed value out\r\n\t\t\tif ( rsLeft ) {\r\n\t\t\t\trs.left = elem.currentStyle.left;\r\n\t\t\t}\r\n\t\t\tstyle.left = name === \"fontSize\" ? \"1em\" : ret;\r\n\t\t\tret = style.pixelLeft + \"px\";\r\n\r\n\t\t\t// Revert the changed values\r\n\t\t\tstyle.left = left;\r\n\t\t\tif ( rsLeft ) {\r\n\t\t\t\trs.left = rsLeft;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn ret === \"\" ? \"auto\" : ret;\r\n\t};\r\n}\r\n\r\nfunction setPositiveNumber( elem, value, subtract ) {\r\n\tvar matches = rnumsplit.exec( value );\r\n\treturn matches ?\r\n\t\t// Guard against undefined \"subtract\", e.g., when used as in cssHooks\r\n\t\tMath.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || \"px\" ) :\r\n\t\tvalue;\r\n}\r\n\r\nfunction augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {\r\n\tvar i = extra === ( isBorderBox ? \"border\" : \"content\" ) ?\r\n\t\t// If we already have the right measurement, avoid augmentation\r\n\t\t4 :\r\n\t\t// Otherwise initialize for horizontal or vertical properties\r\n\t\tname === \"width\" ? 1 : 0,\r\n\r\n\t\tval = 0;\r\n\r\n\tfor ( ; i < 4; i += 2 ) {\r\n\t\t// both box models exclude margin, so add it if we want it\r\n\t\tif ( extra === \"margin\" ) {\r\n\t\t\tval += jQuery.css( elem, extra + cssExpand[ i ], true, styles );\r\n\t\t}\r\n\r\n\t\tif ( isBorderBox ) {\r\n\t\t\t// border-box includes padding, so remove it if we want content\r\n\t\t\tif ( extra === \"content\" ) {\r\n\t\t\t\tval -= jQuery.css( elem, \"padding\" + cssExpand[ i ], true, styles );\r\n\t\t\t}\r\n\r\n\t\t\t// at this point, extra isn't border nor margin, so remove border\r\n\t\t\tif ( extra !== \"margin\" ) {\r\n\t\t\t\tval -= jQuery.css( elem, \"border\" + cssExpand[ i ] + \"Width\", true, styles );\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\t// at this point, extra isn't content, so add padding\r\n\t\t\tval += jQuery.css( elem, \"padding\" + cssExpand[ i ], true, styles );\r\n\r\n\t\t\t// at this point, extra isn't content nor padding, so add border\r\n\t\t\tif ( extra !== \"padding\" ) {\r\n\t\t\t\tval += jQuery.css( elem, \"border\" + cssExpand[ i ] + \"Width\", true, styles );\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\treturn val;\r\n}\r\n\r\nfunction getWidthOrHeight( elem, name, extra ) {\r\n\r\n\t// Start with offset property, which is equivalent to the border-box value\r\n\tvar valueIsBorderBox = true,\r\n\t\tval = name === \"width\" ? elem.offsetWidth : elem.offsetHeight,\r\n\t\tstyles = getStyles( elem ),\r\n\t\tisBorderBox = jQuery.support.boxSizing && jQuery.css( elem, \"boxSizing\", false, styles ) === \"border-box\";\r\n\r\n\t// some non-html elements return undefined for offsetWidth, so check for null/undefined\r\n\t// svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285\r\n\t// MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668\r\n\tif ( val <= 0 || val == null ) {\r\n\t\t// Fall back to computed then uncomputed css if necessary\r\n\t\tval = curCSS( elem, name, styles );\r\n\t\tif ( val < 0 || val == null ) {\r\n\t\t\tval = elem.style[ name ];\r\n\t\t}\r\n\r\n\t\t// Computed unit is not pixels. Stop here and return.\r\n\t\tif ( rnumnonpx.test(val) ) {\r\n\t\t\treturn val;\r\n\t\t}\r\n\r\n\t\t// we need the check for style in case a browser which returns unreliable values\r\n\t\t// for getComputedStyle silently falls back to the reliable elem.style\r\n\t\tvalueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] );\r\n\r\n\t\t// Normalize \"\", auto, and prepare for extra\r\n\t\tval = parseFloat( val ) || 0;\r\n\t}\r\n\r\n\t// use the active box-sizing model to add/subtract irrelevant styles\r\n\treturn ( val +\r\n\t\taugmentWidthOrHeight(\r\n\t\t\telem,\r\n\t\t\tname,\r\n\t\t\textra || ( isBorderBox ? \"border\" : \"content\" ),\r\n\t\t\tvalueIsBorderBox,\r\n\t\t\tstyles\r\n\t\t)\r\n\t) + \"px\";\r\n}\r\n\r\n// Try to determine the default display value of an element\r\nfunction css_defaultDisplay( nodeName ) {\r\n\tvar doc = document,\r\n\t\tdisplay = elemdisplay[ nodeName ];\r\n\r\n\tif ( !display ) {\r\n\t\tdisplay = actualDisplay( nodeName, doc );\r\n\r\n\t\t// If the simple way fails, read from inside an iframe\r\n\t\tif ( display === \"none\" || !display ) {\r\n\t\t\t// Use the already-created iframe if possible\r\n\t\t\tiframe = ( iframe ||\r\n\t\t\t\tjQuery(\"<iframe frameborder='0' width='0' height='0'/>\")\r\n\t\t\t\t.css( \"cssText\", \"display:block !important\" )\r\n\t\t\t).appendTo( doc.documentElement );\r\n\r\n\t\t\t// Always write a new HTML skeleton so Webkit and Firefox don't choke on reuse\r\n\t\t\tdoc = ( iframe[0].contentWindow || iframe[0].contentDocument ).document;\r\n\t\t\tdoc.write(\"<!doctype html><html><body>\");\r\n\t\t\tdoc.close();\r\n\r\n\t\t\tdisplay = actualDisplay( nodeName, doc );\r\n\t\t\tiframe.detach();\r\n\t\t}\r\n\r\n\t\t// Store the correct default display\r\n\t\telemdisplay[ nodeName ] = display;\r\n\t}\r\n\r\n\treturn display;\r\n}\r\n\r\n// Called ONLY from within css_defaultDisplay\r\nfunction actualDisplay( name, doc ) {\r\n\tvar elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ),\r\n\t\tdisplay = jQuery.css( elem[0], \"display\" );\r\n\telem.remove();\r\n\treturn display;\r\n}\r\n\r\njQuery.each([ \"height\", \"width\" ], function( i, name ) {\r\n\tjQuery.cssHooks[ name ] = {\r\n\t\tget: function( elem, computed, extra ) {\r\n\t\t\tif ( computed ) {\r\n\t\t\t\t// certain elements can have dimension info if we invisibly show them\r\n\t\t\t\t// however, it must have a current display style that would benefit from this\r\n\t\t\t\treturn elem.offsetWidth === 0 && rdisplayswap.test( jQuery.css( elem, \"display\" ) ) ?\r\n\t\t\t\t\tjQuery.swap( elem, cssShow, function() {\r\n\t\t\t\t\t\treturn getWidthOrHeight( elem, name, extra );\r\n\t\t\t\t\t}) :\r\n\t\t\t\t\tgetWidthOrHeight( elem, name, extra );\r\n\t\t\t}\r\n\t\t},\r\n\r\n\t\tset: function( elem, value, extra ) {\r\n\t\t\tvar styles = extra && getStyles( elem );\r\n\t\t\treturn setPositiveNumber( elem, value, extra ?\r\n\t\t\t\taugmentWidthOrHeight(\r\n\t\t\t\t\telem,\r\n\t\t\t\t\tname,\r\n\t\t\t\t\textra,\r\n\t\t\t\t\tjQuery.support.boxSizing && jQuery.css( elem, \"boxSizing\", false, styles ) === \"border-box\",\r\n\t\t\t\t\tstyles\r\n\t\t\t\t) : 0\r\n\t\t\t);\r\n\t\t}\r\n\t};\r\n});\r\n\r\nif ( !jQuery.support.opacity ) {\r\n\tjQuery.cssHooks.opacity = {\r\n\t\tget: function( elem, computed ) {\r\n\t\t\t// IE uses filters for opacity\r\n\t\t\treturn ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || \"\" ) ?\r\n\t\t\t\t( 0.01 * parseFloat( RegExp.$1 ) ) + \"\" :\r\n\t\t\t\tcomputed ? \"1\" : \"\";\r\n\t\t},\r\n\r\n\t\tset: function( elem, value ) {\r\n\t\t\tvar style = elem.style,\r\n\t\t\t\tcurrentStyle = elem.currentStyle,\r\n\t\t\t\topacity = jQuery.isNumeric( value ) ? \"alpha(opacity=\" + value * 100 + \")\" : \"\",\r\n\t\t\t\tfilter = currentStyle && currentStyle.filter || style.filter || \"\";\r\n\r\n\t\t\t// IE has trouble with opacity if it does not have layout\r\n\t\t\t// Force it by setting the zoom level\r\n\t\t\tstyle.zoom = 1;\r\n\r\n\t\t\t// if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652\r\n\t\t\t// if value === \"\", then remove inline opacity #12685\r\n\t\t\tif ( ( value >= 1 || value === \"\" ) &&\r\n\t\t\t\t\tjQuery.trim( filter.replace( ralpha, \"\" ) ) === \"\" &&\r\n\t\t\t\t\tstyle.removeAttribute ) {\r\n\r\n\t\t\t\t// Setting style.filter to null, \"\" & \" \" still leave \"filter:\" in the cssText\r\n\t\t\t\t// if \"filter:\" is present at all, clearType is disabled, we want to avoid this\r\n\t\t\t\t// style.removeAttribute is IE Only, but so apparently is this code path...\r\n\t\t\t\tstyle.removeAttribute( \"filter\" );\r\n\r\n\t\t\t\t// if there is no filter style applied in a css rule or unset inline opacity, we are done\r\n\t\t\t\tif ( value === \"\" || currentStyle && !currentStyle.filter ) {\r\n\t\t\t\t\treturn;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// otherwise, set new filter values\r\n\t\t\tstyle.filter = ralpha.test( filter ) ?\r\n\t\t\t\tfilter.replace( ralpha, opacity ) :\r\n\t\t\t\tfilter + \" \" + opacity;\r\n\t\t}\r\n\t};\r\n}\r\n\r\n// These hooks cannot be added until DOM ready because the support test\r\n// for it is not run until after DOM ready\r\njQuery(function() {\r\n\tif ( !jQuery.support.reliableMarginRight ) {\r\n\t\tjQuery.cssHooks.marginRight = {\r\n\t\t\tget: function( elem, computed ) {\r\n\t\t\t\tif ( computed ) {\r\n\t\t\t\t\t// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right\r\n\t\t\t\t\t// Work around by temporarily setting element display to inline-block\r\n\t\t\t\t\treturn jQuery.swap( elem, { \"display\": \"inline-block\" },\r\n\t\t\t\t\t\tcurCSS, [ elem, \"marginRight\" ] );\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t};\r\n\t}\r\n\r\n\t// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084\r\n\t// getComputedStyle returns percent when specified for top/left/bottom/right\r\n\t// rather than make the css module depend on the offset module, we just check for it here\r\n\tif ( !jQuery.support.pixelPosition && jQuery.fn.position ) {\r\n\t\tjQuery.each( [ \"top\", \"left\" ], function( i, prop ) {\r\n\t\t\tjQuery.cssHooks[ prop ] = {\r\n\t\t\t\tget: function( elem, computed ) {\r\n\t\t\t\t\tif ( computed ) {\r\n\t\t\t\t\t\tcomputed = curCSS( elem, prop );\r\n\t\t\t\t\t\t// if curCSS returns percentage, fallback to offset\r\n\t\t\t\t\t\treturn rnumnonpx.test( computed ) ?\r\n\t\t\t\t\t\t\tjQuery( elem ).position()[ prop ] + \"px\" :\r\n\t\t\t\t\t\t\tcomputed;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t};\r\n\t\t});\r\n\t}\r\n\r\n});\r\n\r\nif ( jQuery.expr && jQuery.expr.filters ) {\r\n\tjQuery.expr.filters.hidden = function( elem ) {\r\n\t\t// Support: Opera <= 12.12\r\n\t\t// Opera reports offsetWidths and offsetHeights less than zero on some elements\r\n\t\treturn elem.offsetWidth <= 0 && elem.offsetHeight <= 0 ||\r\n\t\t\t(!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || jQuery.css( elem, \"display\" )) === \"none\");\r\n\t};\r\n\r\n\tjQuery.expr.filters.visible = function( elem ) {\r\n\t\treturn !jQuery.expr.filters.hidden( elem );\r\n\t};\r\n}\r\n\r\n// These hooks are used by animate to expand properties\r\njQuery.each({\r\n\tmargin: \"\",\r\n\tpadding: \"\",\r\n\tborder: \"Width\"\r\n}, function( prefix, suffix ) {\r\n\tjQuery.cssHooks[ prefix + suffix ] = {\r\n\t\texpand: function( value ) {\r\n\t\t\tvar i = 0,\r\n\t\t\t\texpanded = {},\r\n\r\n\t\t\t\t// assumes a single number if not a string\r\n\t\t\t\tparts = typeof value === \"string\" ? value.split(\" \") : [ value ];\r\n\r\n\t\t\tfor ( ; i < 4; i++ ) {\r\n\t\t\t\texpanded[ prefix + cssExpand[ i ] + suffix ] =\r\n\t\t\t\t\tparts[ i ] || parts[ i - 2 ] || parts[ 0 ];\r\n\t\t\t}\r\n\r\n\t\t\treturn expanded;\r\n\t\t}\r\n\t};\r\n\r\n\tif ( !rmargin.test( prefix ) ) {\r\n\t\tjQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;\r\n\t}\r\n});\r\nvar r20 = /%20/g,\r\n\trbracket = /\\[\\]$/,\r\n\trCRLF = /\\r?\\n/g,\r\n\trsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,\r\n\trsubmittable = /^(?:input|select|textarea|keygen)/i;\r\n\r\njQuery.fn.extend({\r\n\tserialize: function() {\r\n\t\treturn jQuery.param( this.serializeArray() );\r\n\t},\r\n\tserializeArray: function() {\r\n\t\treturn this.map(function(){\r\n\t\t\t// Can add propHook for \"elements\" to filter or add form elements\r\n\t\t\tvar elements = jQuery.prop( this, \"elements\" );\r\n\t\t\treturn elements ? jQuery.makeArray( elements ) : this;\r\n\t\t})\r\n\t\t.filter(function(){\r\n\t\t\tvar type = this.type;\r\n\t\t\t// Use .is(\":disabled\") so that fieldset[disabled] works\r\n\t\t\treturn this.name && !jQuery( this ).is( \":disabled\" ) &&\r\n\t\t\t\trsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&\r\n\t\t\t\t( this.checked || !manipulation_rcheckableType.test( type ) );\r\n\t\t})\r\n\t\t.map(function( i, elem ){\r\n\t\t\tvar val = jQuery( this ).val();\r\n\r\n\t\t\treturn val == null ?\r\n\t\t\t\tnull :\r\n\t\t\t\tjQuery.isArray( val ) ?\r\n\t\t\t\t\tjQuery.map( val, function( val ){\r\n\t\t\t\t\t\treturn { name: elem.name, value: val.replace( rCRLF, \"\\r\\n\" ) };\r\n\t\t\t\t\t}) :\r\n\t\t\t\t\t{ name: elem.name, value: val.replace( rCRLF, \"\\r\\n\" ) };\r\n\t\t}).get();\r\n\t}\r\n});\r\n\r\n//Serialize an array of form elements or a set of\r\n//key/values into a query string\r\njQuery.param = function( a, traditional ) {\r\n\tvar prefix,\r\n\t\ts = [],\r\n\t\tadd = function( key, value ) {\r\n\t\t\t// If value is a function, invoke it and return its value\r\n\t\t\tvalue = jQuery.isFunction( value ) ? value() : ( value == null ? \"\" : value );\r\n\t\t\ts[ s.length ] = encodeURIComponent( key ) + \"=\" + encodeURIComponent( value );\r\n\t\t};\r\n\r\n\t// Set traditional to true for jQuery <= 1.3.2 behavior.\r\n\tif ( traditional === undefined ) {\r\n\t\ttraditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;\r\n\t}\r\n\r\n\t// If an array was passed in, assume that it is an array of form elements.\r\n\tif ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {\r\n\t\t// Serialize the form elements\r\n\t\tjQuery.each( a, function() {\r\n\t\t\tadd( this.name, this.value );\r\n\t\t});\r\n\r\n\t} else {\r\n\t\t// If traditional, encode the \"old\" way (the way 1.3.2 or older\r\n\t\t// did it), otherwise encode params recursively.\r\n\t\tfor ( prefix in a ) {\r\n\t\t\tbuildParams( prefix, a[ prefix ], traditional, add );\r\n\t\t}\r\n\t}\r\n\r\n\t// Return the resulting serialization\r\n\treturn s.join( \"&\" ).replace( r20, \"+\" );\r\n};\r\n\r\nfunction buildParams( prefix, obj, traditional, add ) {\r\n\tvar name;\r\n\r\n\tif ( jQuery.isArray( obj ) ) {\r\n\t\t// Serialize array item.\r\n\t\tjQuery.each( obj, function( i, v ) {\r\n\t\t\tif ( traditional || rbracket.test( prefix ) ) {\r\n\t\t\t\t// Treat each array item as a scalar.\r\n\t\t\t\tadd( prefix, v );\r\n\r\n\t\t\t} else {\r\n\t\t\t\t// Item is non-scalar (array or object), encode its numeric index.\r\n\t\t\t\tbuildParams( prefix + \"[\" + ( typeof v === \"object\" ? i : \"\" ) + \"]\", v, traditional, add );\r\n\t\t\t}\r\n\t\t});\r\n\r\n\t} else if ( !traditional && jQuery.type( obj ) === \"object\" ) {\r\n\t\t// Serialize object item.\r\n\t\tfor ( name in obj ) {\r\n\t\t\tbuildParams( prefix + \"[\" + name + \"]\", obj[ name ], traditional, add );\r\n\t\t}\r\n\r\n\t} else {\r\n\t\t// Serialize scalar item.\r\n\t\tadd( prefix, obj );\r\n\t}\r\n}\r\njQuery.each( (\"blur focus focusin focusout load resize scroll unload click dblclick \" +\r\n\t\"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave \" +\r\n\t\"change select submit keydown keypress keyup error contextmenu\").split(\" \"), function( i, name ) {\r\n\r\n\t// Handle event binding\r\n\tjQuery.fn[ name ] = function( data, fn ) {\r\n\t\treturn arguments.length > 0 ?\r\n\t\t\tthis.on( name, null, data, fn ) :\r\n\t\t\tthis.trigger( name );\r\n\t};\r\n});\r\n\r\njQuery.fn.extend({\r\n\thover: function( fnOver, fnOut ) {\r\n\t\treturn this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );\r\n\t},\r\n\r\n\tbind: function( types, data, fn ) {\r\n\t\treturn this.on( types, null, data, fn );\r\n\t},\r\n\tunbind: function( types, fn ) {\r\n\t\treturn this.off( types, null, fn );\r\n\t},\r\n\r\n\tdelegate: function( selector, types, data, fn ) {\r\n\t\treturn this.on( types, selector, data, fn );\r\n\t},\r\n\tundelegate: function( selector, types, fn ) {\r\n\t\t// ( namespace ) or ( selector, types [, fn] )\r\n\t\treturn arguments.length === 1 ? this.off( selector, \"**\" ) : this.off( types, selector || \"**\", fn );\r\n\t}\r\n});\r\nvar\r\n\t// Document location\r\n\tajaxLocParts,\r\n\tajaxLocation,\r\n\tajax_nonce = jQuery.now(),\r\n\r\n\tajax_rquery = /\\?/,\r\n\trhash = /#.*$/,\r\n\trts = /([?&])_=[^&]*/,\r\n\trheaders = /^(.*?):[ \\t]*([^\\r\\n]*)\\r?$/mg, // IE leaves an \\r character at EOL\r\n\t// #7653, #8125, #8152: local protocol detection\r\n\trlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,\r\n\trnoContent = /^(?:GET|HEAD)$/,\r\n\trprotocol = /^\\/\\//,\r\n\trurl = /^([\\w.+-]+:)(?:\\/\\/([^\\/?#:]*)(?::(\\d+)|)|)/,\r\n\r\n\t// Keep a copy of the old load method\r\n\t_load = jQuery.fn.load,\r\n\r\n\t/* Prefilters\r\n\t * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)\r\n\t * 2) These are called:\r\n\t *    - BEFORE asking for a transport\r\n\t *    - AFTER param serialization (s.data is a string if s.processData is true)\r\n\t * 3) key is the dataType\r\n\t * 4) the catchall symbol \"*\" can be used\r\n\t * 5) execution will start with transport dataType and THEN continue down to \"*\" if needed\r\n\t */\r\n\tprefilters = {},\r\n\r\n\t/* Transports bindings\r\n\t * 1) key is the dataType\r\n\t * 2) the catchall symbol \"*\" can be used\r\n\t * 3) selection will start with transport dataType and THEN go to \"*\" if needed\r\n\t */\r\n\ttransports = {},\r\n\r\n\t// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression\r\n\tallTypes = \"*/\".concat(\"*\");\r\n\r\n// #8138, IE may throw an exception when accessing\r\n// a field from window.location if document.domain has been set\r\ntry {\r\n\tajaxLocation = location.href;\r\n} catch( e ) {\r\n\t// Use the href attribute of an A element\r\n\t// since IE will modify it given document.location\r\n\tajaxLocation = document.createElement( \"a\" );\r\n\tajaxLocation.href = \"\";\r\n\tajaxLocation = ajaxLocation.href;\r\n}\r\n\r\n// Segment location into parts\r\najaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];\r\n\r\n// Base \"constructor\" for jQuery.ajaxPrefilter and jQuery.ajaxTransport\r\nfunction addToPrefiltersOrTransports( structure ) {\r\n\r\n\t// dataTypeExpression is optional and defaults to \"*\"\r\n\treturn function( dataTypeExpression, func ) {\r\n\r\n\t\tif ( typeof dataTypeExpression !== \"string\" ) {\r\n\t\t\tfunc = dataTypeExpression;\r\n\t\t\tdataTypeExpression = \"*\";\r\n\t\t}\r\n\r\n\t\tvar dataType,\r\n\t\t\ti = 0,\r\n\t\t\tdataTypes = dataTypeExpression.toLowerCase().match( core_rnotwhite ) || [];\r\n\r\n\t\tif ( jQuery.isFunction( func ) ) {\r\n\t\t\t// For each dataType in the dataTypeExpression\r\n\t\t\twhile ( (dataType = dataTypes[i++]) ) {\r\n\t\t\t\t// Prepend if requested\r\n\t\t\t\tif ( dataType[0] === \"+\" ) {\r\n\t\t\t\t\tdataType = dataType.slice( 1 ) || \"*\";\r\n\t\t\t\t\t(structure[ dataType ] = structure[ dataType ] || []).unshift( func );\r\n\r\n\t\t\t\t// Otherwise append\r\n\t\t\t\t} else {\r\n\t\t\t\t\t(structure[ dataType ] = structure[ dataType ] || []).push( func );\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t};\r\n}\r\n\r\n// Base inspection function for prefilters and transports\r\nfunction inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {\r\n\r\n\tvar inspected = {},\r\n\t\tseekingTransport = ( structure === transports );\r\n\r\n\tfunction inspect( dataType ) {\r\n\t\tvar selected;\r\n\t\tinspected[ dataType ] = true;\r\n\t\tjQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {\r\n\t\t\tvar dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );\r\n\t\t\tif( typeof dataTypeOrTransport === \"string\" && !seekingTransport && !inspected[ dataTypeOrTransport ] ) {\r\n\t\t\t\toptions.dataTypes.unshift( dataTypeOrTransport );\r\n\t\t\t\tinspect( dataTypeOrTransport );\r\n\t\t\t\treturn false;\r\n\t\t\t} else if ( seekingTransport ) {\r\n\t\t\t\treturn !( selected = dataTypeOrTransport );\r\n\t\t\t}\r\n\t\t});\r\n\t\treturn selected;\r\n\t}\r\n\r\n\treturn inspect( options.dataTypes[ 0 ] ) || !inspected[ \"*\" ] && inspect( \"*\" );\r\n}\r\n\r\n// A special extend for ajax options\r\n// that takes \"flat\" options (not to be deep extended)\r\n// Fixes #9887\r\nfunction ajaxExtend( target, src ) {\r\n\tvar deep, key,\r\n\t\tflatOptions = jQuery.ajaxSettings.flatOptions || {};\r\n\r\n\tfor ( key in src ) {\r\n\t\tif ( src[ key ] !== undefined ) {\r\n\t\t\t( flatOptions[ key ] ? target : ( deep || (deep = {}) ) )[ key ] = src[ key ];\r\n\t\t}\r\n\t}\r\n\tif ( deep ) {\r\n\t\tjQuery.extend( true, target, deep );\r\n\t}\r\n\r\n\treturn target;\r\n}\r\n\r\njQuery.fn.load = function( url, params, callback ) {\r\n\tif ( typeof url !== \"string\" && _load ) {\r\n\t\treturn _load.apply( this, arguments );\r\n\t}\r\n\r\n\tvar selector, response, type,\r\n\t\tself = this,\r\n\t\toff = url.indexOf(\" \");\r\n\r\n\tif ( off >= 0 ) {\r\n\t\tselector = url.slice( off, url.length );\r\n\t\turl = url.slice( 0, off );\r\n\t}\r\n\r\n\t// If it's a function\r\n\tif ( jQuery.isFunction( params ) ) {\r\n\r\n\t\t// We assume that it's the callback\r\n\t\tcallback = params;\r\n\t\tparams = undefined;\r\n\r\n\t// Otherwise, build a param string\r\n\t} else if ( params && typeof params === \"object\" ) {\r\n\t\ttype = \"POST\";\r\n\t}\r\n\r\n\t// If we have elements to modify, make the request\r\n\tif ( self.length > 0 ) {\r\n\t\tjQuery.ajax({\r\n\t\t\turl: url,\r\n\r\n\t\t\t// if \"type\" variable is undefined, then \"GET\" method will be used\r\n\t\t\ttype: type,\r\n\t\t\tdataType: \"html\",\r\n\t\t\tdata: params\r\n\t\t}).done(function( responseText ) {\r\n\r\n\t\t\t// Save response for use in complete callback\r\n\t\t\tresponse = arguments;\r\n\r\n\t\t\tself.html( selector ?\r\n\r\n\t\t\t\t// If a selector was specified, locate the right elements in a dummy div\r\n\t\t\t\t// Exclude scripts to avoid IE 'Permission Denied' errors\r\n\t\t\t\tjQuery(\"<div>\").append( jQuery.parseHTML( responseText ) ).find( selector ) :\r\n\r\n\t\t\t\t// Otherwise use the full result\r\n\t\t\t\tresponseText );\r\n\r\n\t\t}).complete( callback && function( jqXHR, status ) {\r\n\t\t\tself.each( callback, response || [ jqXHR.responseText, status, jqXHR ] );\r\n\t\t});\r\n\t}\r\n\r\n\treturn this;\r\n};\r\n\r\n// Attach a bunch of functions for handling common AJAX events\r\njQuery.each( [ \"ajaxStart\", \"ajaxStop\", \"ajaxComplete\", \"ajaxError\", \"ajaxSuccess\", \"ajaxSend\" ], function( i, type ){\r\n\tjQuery.fn[ type ] = function( fn ){\r\n\t\treturn this.on( type, fn );\r\n\t};\r\n});\r\n\r\njQuery.extend({\r\n\r\n\t// Counter for holding the number of active queries\r\n\tactive: 0,\r\n\r\n\t// Last-Modified header cache for next request\r\n\tlastModified: {},\r\n\tetag: {},\r\n\r\n\tajaxSettings: {\r\n\t\turl: ajaxLocation,\r\n\t\ttype: \"GET\",\r\n\t\tisLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),\r\n\t\tglobal: true,\r\n\t\tprocessData: true,\r\n\t\tasync: true,\r\n\t\tcontentType: \"application/x-www-form-urlencoded; charset=UTF-8\",\r\n\t\t/*\r\n\t\ttimeout: 0,\r\n\t\tdata: null,\r\n\t\tdataType: null,\r\n\t\tusername: null,\r\n\t\tpassword: null,\r\n\t\tcache: null,\r\n\t\tthrows: false,\r\n\t\ttraditional: false,\r\n\t\theaders: {},\r\n\t\t*/\r\n\r\n\t\taccepts: {\r\n\t\t\t\"*\": allTypes,\r\n\t\t\ttext: \"text/plain\",\r\n\t\t\thtml: \"text/html\",\r\n\t\t\txml: \"application/xml, text/xml\",\r\n\t\t\tjson: \"application/json, text/javascript\"\r\n\t\t},\r\n\r\n\t\tcontents: {\r\n\t\t\txml: /xml/,\r\n\t\t\thtml: /html/,\r\n\t\t\tjson: /json/\r\n\t\t},\r\n\r\n\t\tresponseFields: {\r\n\t\t\txml: \"responseXML\",\r\n\t\t\ttext: \"responseText\",\r\n\t\t\tjson: \"responseJSON\"\r\n\t\t},\r\n\r\n\t\t// Data converters\r\n\t\t// Keys separate source (or catchall \"*\") and destination types with a single space\r\n\t\tconverters: {\r\n\r\n\t\t\t// Convert anything to text\r\n\t\t\t\"* text\": String,\r\n\r\n\t\t\t// Text to html (true = no transformation)\r\n\t\t\t\"text html\": true,\r\n\r\n\t\t\t// Evaluate text as a json expression\r\n\t\t\t\"text json\": jQuery.parseJSON,\r\n\r\n\t\t\t// Parse text as xml\r\n\t\t\t\"text xml\": jQuery.parseXML\r\n\t\t},\r\n\r\n\t\t// For options that shouldn't be deep extended:\r\n\t\t// you can add your own custom options here if\r\n\t\t// and when you create one that shouldn't be\r\n\t\t// deep extended (see ajaxExtend)\r\n\t\tflatOptions: {\r\n\t\t\turl: true,\r\n\t\t\tcontext: true\r\n\t\t}\r\n\t},\r\n\r\n\t// Creates a full fledged settings object into target\r\n\t// with both ajaxSettings and settings fields.\r\n\t// If target is omitted, writes into ajaxSettings.\r\n\tajaxSetup: function( target, settings ) {\r\n\t\treturn settings ?\r\n\r\n\t\t\t// Building a settings object\r\n\t\t\tajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :\r\n\r\n\t\t\t// Extending ajaxSettings\r\n\t\t\tajaxExtend( jQuery.ajaxSettings, target );\r\n\t},\r\n\r\n\tajaxPrefilter: addToPrefiltersOrTransports( prefilters ),\r\n\tajaxTransport: addToPrefiltersOrTransports( transports ),\r\n\r\n\t// Main method\r\n\tajax: function( url, options ) {\r\n\r\n\t\t// If url is an object, simulate pre-1.5 signature\r\n\t\tif ( typeof url === \"object\" ) {\r\n\t\t\toptions = url;\r\n\t\t\turl = undefined;\r\n\t\t}\r\n\r\n\t\t// Force options to be an object\r\n\t\toptions = options || {};\r\n\r\n\t\tvar // Cross-domain detection vars\r\n\t\t\tparts,\r\n\t\t\t// Loop variable\r\n\t\t\ti,\r\n\t\t\t// URL without anti-cache param\r\n\t\t\tcacheURL,\r\n\t\t\t// Response headers as string\r\n\t\t\tresponseHeadersString,\r\n\t\t\t// timeout handle\r\n\t\t\ttimeoutTimer,\r\n\r\n\t\t\t// To know if global events are to be dispatched\r\n\t\t\tfireGlobals,\r\n\r\n\t\t\ttransport,\r\n\t\t\t// Response headers\r\n\t\t\tresponseHeaders,\r\n\t\t\t// Create the final options object\r\n\t\t\ts = jQuery.ajaxSetup( {}, options ),\r\n\t\t\t// Callbacks context\r\n\t\t\tcallbackContext = s.context || s,\r\n\t\t\t// Context for global events is callbackContext if it is a DOM node or jQuery collection\r\n\t\t\tglobalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ?\r\n\t\t\t\tjQuery( callbackContext ) :\r\n\t\t\t\tjQuery.event,\r\n\t\t\t// Deferreds\r\n\t\t\tdeferred = jQuery.Deferred(),\r\n\t\t\tcompleteDeferred = jQuery.Callbacks(\"once memory\"),\r\n\t\t\t// Status-dependent callbacks\r\n\t\t\tstatusCode = s.statusCode || {},\r\n\t\t\t// Headers (they are sent all at once)\r\n\t\t\trequestHeaders = {},\r\n\t\t\trequestHeadersNames = {},\r\n\t\t\t// The jqXHR state\r\n\t\t\tstate = 0,\r\n\t\t\t// Default abort message\r\n\t\t\tstrAbort = \"canceled\",\r\n\t\t\t// Fake xhr\r\n\t\t\tjqXHR = {\r\n\t\t\t\treadyState: 0,\r\n\r\n\t\t\t\t// Builds headers hashtable if needed\r\n\t\t\t\tgetResponseHeader: function( key ) {\r\n\t\t\t\t\tvar match;\r\n\t\t\t\t\tif ( state === 2 ) {\r\n\t\t\t\t\t\tif ( !responseHeaders ) {\r\n\t\t\t\t\t\t\tresponseHeaders = {};\r\n\t\t\t\t\t\t\twhile ( (match = rheaders.exec( responseHeadersString )) ) {\r\n\t\t\t\t\t\t\t\tresponseHeaders[ match[1].toLowerCase() ] = match[ 2 ];\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\tmatch = responseHeaders[ key.toLowerCase() ];\r\n\t\t\t\t\t}\r\n\t\t\t\t\treturn match == null ? null : match;\r\n\t\t\t\t},\r\n\r\n\t\t\t\t// Raw string\r\n\t\t\t\tgetAllResponseHeaders: function() {\r\n\t\t\t\t\treturn state === 2 ? responseHeadersString : null;\r\n\t\t\t\t},\r\n\r\n\t\t\t\t// Caches the header\r\n\t\t\t\tsetRequestHeader: function( name, value ) {\r\n\t\t\t\t\tvar lname = name.toLowerCase();\r\n\t\t\t\t\tif ( !state ) {\r\n\t\t\t\t\t\tname = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;\r\n\t\t\t\t\t\trequestHeaders[ name ] = value;\r\n\t\t\t\t\t}\r\n\t\t\t\t\treturn this;\r\n\t\t\t\t},\r\n\r\n\t\t\t\t// Overrides response content-type header\r\n\t\t\t\toverrideMimeType: function( type ) {\r\n\t\t\t\t\tif ( !state ) {\r\n\t\t\t\t\t\ts.mimeType = type;\r\n\t\t\t\t\t}\r\n\t\t\t\t\treturn this;\r\n\t\t\t\t},\r\n\r\n\t\t\t\t// Status-dependent callbacks\r\n\t\t\t\tstatusCode: function( map ) {\r\n\t\t\t\t\tvar code;\r\n\t\t\t\t\tif ( map ) {\r\n\t\t\t\t\t\tif ( state < 2 ) {\r\n\t\t\t\t\t\t\tfor ( code in map ) {\r\n\t\t\t\t\t\t\t\t// Lazy-add the new callback in a way that preserves old ones\r\n\t\t\t\t\t\t\t\tstatusCode[ code ] = [ statusCode[ code ], map[ code ] ];\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t// Execute the appropriate callbacks\r\n\t\t\t\t\t\t\tjqXHR.always( map[ jqXHR.status ] );\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t\treturn this;\r\n\t\t\t\t},\r\n\r\n\t\t\t\t// Cancel the request\r\n\t\t\t\tabort: function( statusText ) {\r\n\t\t\t\t\tvar finalText = statusText || strAbort;\r\n\t\t\t\t\tif ( transport ) {\r\n\t\t\t\t\t\ttransport.abort( finalText );\r\n\t\t\t\t\t}\r\n\t\t\t\t\tdone( 0, finalText );\r\n\t\t\t\t\treturn this;\r\n\t\t\t\t}\r\n\t\t\t};\r\n\r\n\t\t// Attach deferreds\r\n\t\tdeferred.promise( jqXHR ).complete = completeDeferred.add;\r\n\t\tjqXHR.success = jqXHR.done;\r\n\t\tjqXHR.error = jqXHR.fail;\r\n\r\n\t\t// Remove hash character (#7531: and string promotion)\r\n\t\t// Add protocol if not provided (#5866: IE7 issue with protocol-less urls)\r\n\t\t// Handle falsy url in the settings object (#10093: consistency with old signature)\r\n\t\t// We also use the url parameter if available\r\n\t\ts.url = ( ( url || s.url || ajaxLocation ) + \"\" ).replace( rhash, \"\" ).replace( rprotocol, ajaxLocParts[ 1 ] + \"//\" );\r\n\r\n\t\t// Alias method option to type as per ticket #12004\r\n\t\ts.type = options.method || options.type || s.method || s.type;\r\n\r\n\t\t// Extract dataTypes list\r\n\t\ts.dataTypes = jQuery.trim( s.dataType || \"*\" ).toLowerCase().match( core_rnotwhite ) || [\"\"];\r\n\r\n\t\t// A cross-domain request is in order when we have a protocol:host:port mismatch\r\n\t\tif ( s.crossDomain == null ) {\r\n\t\t\tparts = rurl.exec( s.url.toLowerCase() );\r\n\t\t\ts.crossDomain = !!( parts &&\r\n\t\t\t\t( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] ||\r\n\t\t\t\t\t( parts[ 3 ] || ( parts[ 1 ] === \"http:\" ? \"80\" : \"443\" ) ) !==\r\n\t\t\t\t\t\t( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === \"http:\" ? \"80\" : \"443\" ) ) )\r\n\t\t\t);\r\n\t\t}\r\n\r\n\t\t// Convert data if not already a string\r\n\t\tif ( s.data && s.processData && typeof s.data !== \"string\" ) {\r\n\t\t\ts.data = jQuery.param( s.data, s.traditional );\r\n\t\t}\r\n\r\n\t\t// Apply prefilters\r\n\t\tinspectPrefiltersOrTransports( prefilters, s, options, jqXHR );\r\n\r\n\t\t// If request was aborted inside a prefilter, stop there\r\n\t\tif ( state === 2 ) {\r\n\t\t\treturn jqXHR;\r\n\t\t}\r\n\r\n\t\t// We can fire global events as of now if asked to\r\n\t\tfireGlobals = s.global;\r\n\r\n\t\t// Watch for a new set of requests\r\n\t\tif ( fireGlobals && jQuery.active++ === 0 ) {\r\n\t\t\tjQuery.event.trigger(\"ajaxStart\");\r\n\t\t}\r\n\r\n\t\t// Uppercase the type\r\n\t\ts.type = s.type.toUpperCase();\r\n\r\n\t\t// Determine if request has content\r\n\t\ts.hasContent = !rnoContent.test( s.type );\r\n\r\n\t\t// Save the URL in case we're toying with the If-Modified-Since\r\n\t\t// and/or If-None-Match header later on\r\n\t\tcacheURL = s.url;\r\n\r\n\t\t// More options handling for requests with no content\r\n\t\tif ( !s.hasContent ) {\r\n\r\n\t\t\t// If data is available, append data to url\r\n\t\t\tif ( s.data ) {\r\n\t\t\t\tcacheURL = ( s.url += ( ajax_rquery.test( cacheURL ) ? \"&\" : \"?\" ) + s.data );\r\n\t\t\t\t// #9682: remove data so that it's not used in an eventual retry\r\n\t\t\t\tdelete s.data;\r\n\t\t\t}\r\n\r\n\t\t\t// Add anti-cache in url if needed\r\n\t\t\tif ( s.cache === false ) {\r\n\t\t\t\ts.url = rts.test( cacheURL ) ?\r\n\r\n\t\t\t\t\t// If there is already a '_' parameter, set its value\r\n\t\t\t\t\tcacheURL.replace( rts, \"$1_=\" + ajax_nonce++ ) :\r\n\r\n\t\t\t\t\t// Otherwise add one to the end\r\n\t\t\t\t\tcacheURL + ( ajax_rquery.test( cacheURL ) ? \"&\" : \"?\" ) + \"_=\" + ajax_nonce++;\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.\r\n\t\tif ( s.ifModified ) {\r\n\t\t\tif ( jQuery.lastModified[ cacheURL ] ) {\r\n\t\t\t\tjqXHR.setRequestHeader( \"If-Modified-Since\", jQuery.lastModified[ cacheURL ] );\r\n\t\t\t}\r\n\t\t\tif ( jQuery.etag[ cacheURL ] ) {\r\n\t\t\t\tjqXHR.setRequestHeader( \"If-None-Match\", jQuery.etag[ cacheURL ] );\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Set the correct header, if data is being sent\r\n\t\tif ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {\r\n\t\t\tjqXHR.setRequestHeader( \"Content-Type\", s.contentType );\r\n\t\t}\r\n\r\n\t\t// Set the Accepts header for the server, depending on the dataType\r\n\t\tjqXHR.setRequestHeader(\r\n\t\t\t\"Accept\",\r\n\t\t\ts.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?\r\n\t\t\t\ts.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== \"*\" ? \", \" + allTypes + \"; q=0.01\" : \"\" ) :\r\n\t\t\t\ts.accepts[ \"*\" ]\r\n\t\t);\r\n\r\n\t\t// Check for headers option\r\n\t\tfor ( i in s.headers ) {\r\n\t\t\tjqXHR.setRequestHeader( i, s.headers[ i ] );\r\n\t\t}\r\n\r\n\t\t// Allow custom headers/mimetypes and early abort\r\n\t\tif ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {\r\n\t\t\t// Abort if not done already and return\r\n\t\t\treturn jqXHR.abort();\r\n\t\t}\r\n\r\n\t\t// aborting is no longer a cancellation\r\n\t\tstrAbort = \"abort\";\r\n\r\n\t\t// Install callbacks on deferreds\r\n\t\tfor ( i in { success: 1, error: 1, complete: 1 } ) {\r\n\t\t\tjqXHR[ i ]( s[ i ] );\r\n\t\t}\r\n\r\n\t\t// Get transport\r\n\t\ttransport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );\r\n\r\n\t\t// If no transport, we auto-abort\r\n\t\tif ( !transport ) {\r\n\t\t\tdone( -1, \"No Transport\" );\r\n\t\t} else {\r\n\t\t\tjqXHR.readyState = 1;\r\n\r\n\t\t\t// Send global event\r\n\t\t\tif ( fireGlobals ) {\r\n\t\t\t\tglobalEventContext.trigger( \"ajaxSend\", [ jqXHR, s ] );\r\n\t\t\t}\r\n\t\t\t// Timeout\r\n\t\t\tif ( s.async && s.timeout > 0 ) {\r\n\t\t\t\ttimeoutTimer = setTimeout(function() {\r\n\t\t\t\t\tjqXHR.abort(\"timeout\");\r\n\t\t\t\t}, s.timeout );\r\n\t\t\t}\r\n\r\n\t\t\ttry {\r\n\t\t\t\tstate = 1;\r\n\t\t\t\ttransport.send( requestHeaders, done );\r\n\t\t\t} catch ( e ) {\r\n\t\t\t\t// Propagate exception as error if not done\r\n\t\t\t\tif ( state < 2 ) {\r\n\t\t\t\t\tdone( -1, e );\r\n\t\t\t\t// Simply rethrow otherwise\r\n\t\t\t\t} else {\r\n\t\t\t\t\tthrow e;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\t// Callback for when everything is done\r\n\t\tfunction done( status, nativeStatusText, responses, headers ) {\r\n\t\t\tvar isSuccess, success, error, response, modified,\r\n\t\t\t\tstatusText = nativeStatusText;\r\n\r\n\t\t\t// Called once\r\n\t\t\tif ( state === 2 ) {\r\n\t\t\t\treturn;\r\n\t\t\t}\r\n\r\n\t\t\t// State is \"done\" now\r\n\t\t\tstate = 2;\r\n\r\n\t\t\t// Clear timeout if it exists\r\n\t\t\tif ( timeoutTimer ) {\r\n\t\t\t\tclearTimeout( timeoutTimer );\r\n\t\t\t}\r\n\r\n\t\t\t// Dereference transport for early garbage collection\r\n\t\t\t// (no matter how long the jqXHR object will be used)\r\n\t\t\ttransport = undefined;\r\n\r\n\t\t\t// Cache response headers\r\n\t\t\tresponseHeadersString = headers || \"\";\r\n\r\n\t\t\t// Set readyState\r\n\t\t\tjqXHR.readyState = status > 0 ? 4 : 0;\r\n\r\n\t\t\t// Determine if successful\r\n\t\t\tisSuccess = status >= 200 && status < 300 || status === 304;\r\n\r\n\t\t\t// Get response data\r\n\t\t\tif ( responses ) {\r\n\t\t\t\tresponse = ajaxHandleResponses( s, jqXHR, responses );\r\n\t\t\t}\r\n\r\n\t\t\t// Convert no matter what (that way responseXXX fields are always set)\r\n\t\t\tresponse = ajaxConvert( s, response, jqXHR, isSuccess );\r\n\r\n\t\t\t// If successful, handle type chaining\r\n\t\t\tif ( isSuccess ) {\r\n\r\n\t\t\t\t// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.\r\n\t\t\t\tif ( s.ifModified ) {\r\n\t\t\t\t\tmodified = jqXHR.getResponseHeader(\"Last-Modified\");\r\n\t\t\t\t\tif ( modified ) {\r\n\t\t\t\t\t\tjQuery.lastModified[ cacheURL ] = modified;\r\n\t\t\t\t\t}\r\n\t\t\t\t\tmodified = jqXHR.getResponseHeader(\"etag\");\r\n\t\t\t\t\tif ( modified ) {\r\n\t\t\t\t\t\tjQuery.etag[ cacheURL ] = modified;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// if no content\r\n\t\t\t\tif ( status === 204 || s.type === \"HEAD\" ) {\r\n\t\t\t\t\tstatusText = \"nocontent\";\r\n\r\n\t\t\t\t// if not modified\r\n\t\t\t\t} else if ( status === 304 ) {\r\n\t\t\t\t\tstatusText = \"notmodified\";\r\n\r\n\t\t\t\t// If we have data, let's convert it\r\n\t\t\t\t} else {\r\n\t\t\t\t\tstatusText = response.state;\r\n\t\t\t\t\tsuccess = response.data;\r\n\t\t\t\t\terror = response.error;\r\n\t\t\t\t\tisSuccess = !error;\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\t// We extract error from statusText\r\n\t\t\t\t// then normalize statusText and status for non-aborts\r\n\t\t\t\terror = statusText;\r\n\t\t\t\tif ( status || !statusText ) {\r\n\t\t\t\t\tstatusText = \"error\";\r\n\t\t\t\t\tif ( status < 0 ) {\r\n\t\t\t\t\t\tstatus = 0;\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// Set data for the fake xhr object\r\n\t\t\tjqXHR.status = status;\r\n\t\t\tjqXHR.statusText = ( nativeStatusText || statusText ) + \"\";\r\n\r\n\t\t\t// Success/Error\r\n\t\t\tif ( isSuccess ) {\r\n\t\t\t\tdeferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );\r\n\t\t\t} else {\r\n\t\t\t\tdeferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );\r\n\t\t\t}\r\n\r\n\t\t\t// Status-dependent callbacks\r\n\t\t\tjqXHR.statusCode( statusCode );\r\n\t\t\tstatusCode = undefined;\r\n\r\n\t\t\tif ( fireGlobals ) {\r\n\t\t\t\tglobalEventContext.trigger( isSuccess ? \"ajaxSuccess\" : \"ajaxError\",\r\n\t\t\t\t\t[ jqXHR, s, isSuccess ? success : error ] );\r\n\t\t\t}\r\n\r\n\t\t\t// Complete\r\n\t\t\tcompleteDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );\r\n\r\n\t\t\tif ( fireGlobals ) {\r\n\t\t\t\tglobalEventContext.trigger( \"ajaxComplete\", [ jqXHR, s ] );\r\n\t\t\t\t// Handle the global AJAX counter\r\n\t\t\t\tif ( !( --jQuery.active ) ) {\r\n\t\t\t\t\tjQuery.event.trigger(\"ajaxStop\");\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\r\n\t\treturn jqXHR;\r\n\t},\r\n\r\n\tgetJSON: function( url, data, callback ) {\r\n\t\treturn jQuery.get( url, data, callback, \"json\" );\r\n\t},\r\n\r\n\tgetScript: function( url, callback ) {\r\n\t\treturn jQuery.get( url, undefined, callback, \"script\" );\r\n\t}\r\n});\r\n\r\njQuery.each( [ \"get\", \"post\" ], function( i, method ) {\r\n\tjQuery[ method ] = function( url, data, callback, type ) {\r\n\t\t// shift arguments if data argument was omitted\r\n\t\tif ( jQuery.isFunction( data ) ) {\r\n\t\t\ttype = type || callback;\r\n\t\t\tcallback = data;\r\n\t\t\tdata = undefined;\r\n\t\t}\r\n\r\n\t\treturn jQuery.ajax({\r\n\t\t\turl: url,\r\n\t\t\ttype: method,\r\n\t\t\tdataType: type,\r\n\t\t\tdata: data,\r\n\t\t\tsuccess: callback\r\n\t\t});\r\n\t};\r\n});\r\n\r\n/* Handles responses to an ajax request:\r\n * - finds the right dataType (mediates between content-type and expected dataType)\r\n * - returns the corresponding response\r\n */\r\nfunction ajaxHandleResponses( s, jqXHR, responses ) {\r\n\tvar firstDataType, ct, finalDataType, type,\r\n\t\tcontents = s.contents,\r\n\t\tdataTypes = s.dataTypes;\r\n\r\n\t// Remove auto dataType and get content-type in the process\r\n\twhile( dataTypes[ 0 ] === \"*\" ) {\r\n\t\tdataTypes.shift();\r\n\t\tif ( ct === undefined ) {\r\n\t\t\tct = s.mimeType || jqXHR.getResponseHeader(\"Content-Type\");\r\n\t\t}\r\n\t}\r\n\r\n\t// Check if we're dealing with a known content-type\r\n\tif ( ct ) {\r\n\t\tfor ( type in contents ) {\r\n\t\t\tif ( contents[ type ] && contents[ type ].test( ct ) ) {\r\n\t\t\t\tdataTypes.unshift( type );\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\t// Check to see if we have a response for the expected dataType\r\n\tif ( dataTypes[ 0 ] in responses ) {\r\n\t\tfinalDataType = dataTypes[ 0 ];\r\n\t} else {\r\n\t\t// Try convertible dataTypes\r\n\t\tfor ( type in responses ) {\r\n\t\t\tif ( !dataTypes[ 0 ] || s.converters[ type + \" \" + dataTypes[0] ] ) {\r\n\t\t\t\tfinalDataType = type;\r\n\t\t\t\tbreak;\r\n\t\t\t}\r\n\t\t\tif ( !firstDataType ) {\r\n\t\t\t\tfirstDataType = type;\r\n\t\t\t}\r\n\t\t}\r\n\t\t// Or just use first one\r\n\t\tfinalDataType = finalDataType || firstDataType;\r\n\t}\r\n\r\n\t// If we found a dataType\r\n\t// We add the dataType to the list if needed\r\n\t// and return the corresponding response\r\n\tif ( finalDataType ) {\r\n\t\tif ( finalDataType !== dataTypes[ 0 ] ) {\r\n\t\t\tdataTypes.unshift( finalDataType );\r\n\t\t}\r\n\t\treturn responses[ finalDataType ];\r\n\t}\r\n}\r\n\r\n/* Chain conversions given the request and the original response\r\n * Also sets the responseXXX fields on the jqXHR instance\r\n */\r\nfunction ajaxConvert( s, response, jqXHR, isSuccess ) {\r\n\tvar conv2, current, conv, tmp, prev,\r\n\t\tconverters = {},\r\n\t\t// Work with a copy of dataTypes in case we need to modify it for conversion\r\n\t\tdataTypes = s.dataTypes.slice();\r\n\r\n\t// Create converters map with lowercased keys\r\n\tif ( dataTypes[ 1 ] ) {\r\n\t\tfor ( conv in s.converters ) {\r\n\t\t\tconverters[ conv.toLowerCase() ] = s.converters[ conv ];\r\n\t\t}\r\n\t}\r\n\r\n\tcurrent = dataTypes.shift();\r\n\r\n\t// Convert to each sequential dataType\r\n\twhile ( current ) {\r\n\r\n\t\tif ( s.responseFields[ current ] ) {\r\n\t\t\tjqXHR[ s.responseFields[ current ] ] = response;\r\n\t\t}\r\n\r\n\t\t// Apply the dataFilter if provided\r\n\t\tif ( !prev && isSuccess && s.dataFilter ) {\r\n\t\t\tresponse = s.dataFilter( response, s.dataType );\r\n\t\t}\r\n\r\n\t\tprev = current;\r\n\t\tcurrent = dataTypes.shift();\r\n\r\n\t\tif ( current ) {\r\n\r\n\t\t\t// There's only work to do if current dataType is non-auto\r\n\t\t\tif ( current === \"*\" ) {\r\n\r\n\t\t\t\tcurrent = prev;\r\n\r\n\t\t\t// Convert response if prev dataType is non-auto and differs from current\r\n\t\t\t} else if ( prev !== \"*\" && prev !== current ) {\r\n\r\n\t\t\t\t// Seek a direct converter\r\n\t\t\t\tconv = converters[ prev + \" \" + current ] || converters[ \"* \" + current ];\r\n\r\n\t\t\t\t// If none found, seek a pair\r\n\t\t\t\tif ( !conv ) {\r\n\t\t\t\t\tfor ( conv2 in converters ) {\r\n\r\n\t\t\t\t\t\t// If conv2 outputs current\r\n\t\t\t\t\t\ttmp = conv2.split( \" \" );\r\n\t\t\t\t\t\tif ( tmp[ 1 ] === current ) {\r\n\r\n\t\t\t\t\t\t\t// If prev can be converted to accepted input\r\n\t\t\t\t\t\t\tconv = converters[ prev + \" \" + tmp[ 0 ] ] ||\r\n\t\t\t\t\t\t\t\tconverters[ \"* \" + tmp[ 0 ] ];\r\n\t\t\t\t\t\t\tif ( conv ) {\r\n\t\t\t\t\t\t\t\t// Condense equivalence converters\r\n\t\t\t\t\t\t\t\tif ( conv === true ) {\r\n\t\t\t\t\t\t\t\t\tconv = converters[ conv2 ];\r\n\r\n\t\t\t\t\t\t\t\t// Otherwise, insert the intermediate dataType\r\n\t\t\t\t\t\t\t\t} else if ( converters[ conv2 ] !== true ) {\r\n\t\t\t\t\t\t\t\t\tcurrent = tmp[ 0 ];\r\n\t\t\t\t\t\t\t\t\tdataTypes.unshift( tmp[ 1 ] );\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\tbreak;\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Apply converter (if not an equivalence)\r\n\t\t\t\tif ( conv !== true ) {\r\n\r\n\t\t\t\t\t// Unless errors are allowed to bubble, catch and return them\r\n\t\t\t\t\tif ( conv && s[ \"throws\" ] ) {\r\n\t\t\t\t\t\tresponse = conv( response );\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\ttry {\r\n\t\t\t\t\t\t\tresponse = conv( response );\r\n\t\t\t\t\t\t} catch ( e ) {\r\n\t\t\t\t\t\t\treturn { state: \"parsererror\", error: conv ? e : \"No conversion from \" + prev + \" to \" + current };\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\treturn { state: \"success\", data: response };\r\n}\r\n// Install script dataType\r\njQuery.ajaxSetup({\r\n\taccepts: {\r\n\t\tscript: \"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript\"\r\n\t},\r\n\tcontents: {\r\n\t\tscript: /(?:java|ecma)script/\r\n\t},\r\n\tconverters: {\r\n\t\t\"text script\": function( text ) {\r\n\t\t\tjQuery.globalEval( text );\r\n\t\t\treturn text;\r\n\t\t}\r\n\t}\r\n});\r\n\r\n// Handle cache's special case and global\r\njQuery.ajaxPrefilter( \"script\", function( s ) {\r\n\tif ( s.cache === undefined ) {\r\n\t\ts.cache = false;\r\n\t}\r\n\tif ( s.crossDomain ) {\r\n\t\ts.type = \"GET\";\r\n\t\ts.global = false;\r\n\t}\r\n});\r\n\r\n// Bind script tag hack transport\r\njQuery.ajaxTransport( \"script\", function(s) {\r\n\r\n\t// This transport only deals with cross domain requests\r\n\tif ( s.crossDomain ) {\r\n\r\n\t\tvar script,\r\n\t\t\thead = document.head || jQuery(\"head\")[0] || document.documentElement;\r\n\r\n\t\treturn {\r\n\r\n\t\t\tsend: function( _, callback ) {\r\n\r\n\t\t\t\tscript = document.createElement(\"script\");\r\n\r\n\t\t\t\tscript.async = true;\r\n\r\n\t\t\t\tif ( s.scriptCharset ) {\r\n\t\t\t\t\tscript.charset = s.scriptCharset;\r\n\t\t\t\t}\r\n\r\n\t\t\t\tscript.src = s.url;\r\n\r\n\t\t\t\t// Attach handlers for all browsers\r\n\t\t\t\tscript.onload = script.onreadystatechange = function( _, isAbort ) {\r\n\r\n\t\t\t\t\tif ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {\r\n\r\n\t\t\t\t\t\t// Handle memory leak in IE\r\n\t\t\t\t\t\tscript.onload = script.onreadystatechange = null;\r\n\r\n\t\t\t\t\t\t// Remove the script\r\n\t\t\t\t\t\tif ( script.parentNode ) {\r\n\t\t\t\t\t\t\tscript.parentNode.removeChild( script );\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t// Dereference the script\r\n\t\t\t\t\t\tscript = null;\r\n\r\n\t\t\t\t\t\t// Callback if not abort\r\n\t\t\t\t\t\tif ( !isAbort ) {\r\n\t\t\t\t\t\t\tcallback( 200, \"success\" );\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\t\t\t\t};\r\n\r\n\t\t\t\t// Circumvent IE6 bugs with base elements (#2709 and #4378) by prepending\r\n\t\t\t\t// Use native DOM manipulation to avoid our domManip AJAX trickery\r\n\t\t\t\thead.insertBefore( script, head.firstChild );\r\n\t\t\t},\r\n\r\n\t\t\tabort: function() {\r\n\t\t\t\tif ( script ) {\r\n\t\t\t\t\tscript.onload( undefined, true );\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t};\r\n\t}\r\n});\r\nvar oldCallbacks = [],\r\n\trjsonp = /(=)\\?(?=&|$)|\\?\\?/;\r\n\r\n// Default jsonp settings\r\njQuery.ajaxSetup({\r\n\tjsonp: \"callback\",\r\n\tjsonpCallback: function() {\r\n\t\tvar callback = oldCallbacks.pop() || ( jQuery.expando + \"_\" + ( ajax_nonce++ ) );\r\n\t\tthis[ callback ] = true;\r\n\t\treturn callback;\r\n\t}\r\n});\r\n\r\n// Detect, normalize options and install callbacks for jsonp requests\r\njQuery.ajaxPrefilter( \"json jsonp\", function( s, originalSettings, jqXHR ) {\r\n\r\n\tvar callbackName, overwritten, responseContainer,\r\n\t\tjsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?\r\n\t\t\t\"url\" :\r\n\t\t\ttypeof s.data === \"string\" && !( s.contentType || \"\" ).indexOf(\"application/x-www-form-urlencoded\") && rjsonp.test( s.data ) && \"data\"\r\n\t\t);\r\n\r\n\t// Handle iff the expected data type is \"jsonp\" or we have a parameter to set\r\n\tif ( jsonProp || s.dataTypes[ 0 ] === \"jsonp\" ) {\r\n\r\n\t\t// Get callback name, remembering preexisting value associated with it\r\n\t\tcallbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?\r\n\t\t\ts.jsonpCallback() :\r\n\t\t\ts.jsonpCallback;\r\n\r\n\t\t// Insert callback into url or form data\r\n\t\tif ( jsonProp ) {\r\n\t\t\ts[ jsonProp ] = s[ jsonProp ].replace( rjsonp, \"$1\" + callbackName );\r\n\t\t} else if ( s.jsonp !== false ) {\r\n\t\t\ts.url += ( ajax_rquery.test( s.url ) ? \"&\" : \"?\" ) + s.jsonp + \"=\" + callbackName;\r\n\t\t}\r\n\r\n\t\t// Use data converter to retrieve json after script execution\r\n\t\ts.converters[\"script json\"] = function() {\r\n\t\t\tif ( !responseContainer ) {\r\n\t\t\t\tjQuery.error( callbackName + \" was not called\" );\r\n\t\t\t}\r\n\t\t\treturn responseContainer[ 0 ];\r\n\t\t};\r\n\r\n\t\t// force json dataType\r\n\t\ts.dataTypes[ 0 ] = \"json\";\r\n\r\n\t\t// Install callback\r\n\t\toverwritten = window[ callbackName ];\r\n\t\twindow[ callbackName ] = function() {\r\n\t\t\tresponseContainer = arguments;\r\n\t\t};\r\n\r\n\t\t// Clean-up function (fires after converters)\r\n\t\tjqXHR.always(function() {\r\n\t\t\t// Restore preexisting value\r\n\t\t\twindow[ callbackName ] = overwritten;\r\n\r\n\t\t\t// Save back as free\r\n\t\t\tif ( s[ callbackName ] ) {\r\n\t\t\t\t// make sure that re-using the options doesn't screw things around\r\n\t\t\t\ts.jsonpCallback = originalSettings.jsonpCallback;\r\n\r\n\t\t\t\t// save the callback name for future use\r\n\t\t\t\toldCallbacks.push( callbackName );\r\n\t\t\t}\r\n\r\n\t\t\t// Call if it was a function and we have a response\r\n\t\t\tif ( responseContainer && jQuery.isFunction( overwritten ) ) {\r\n\t\t\t\toverwritten( responseContainer[ 0 ] );\r\n\t\t\t}\r\n\r\n\t\t\tresponseContainer = overwritten = undefined;\r\n\t\t});\r\n\r\n\t\t// Delegate to script\r\n\t\treturn \"script\";\r\n\t}\r\n});\r\nvar xhrCallbacks, xhrSupported,\r\n\txhrId = 0,\r\n\t// #5280: Internet Explorer will keep connections alive if we don't abort on unload\r\n\txhrOnUnloadAbort = window.ActiveXObject && function() {\r\n\t\t// Abort all pending requests\r\n\t\tvar key;\r\n\t\tfor ( key in xhrCallbacks ) {\r\n\t\t\txhrCallbacks[ key ]( undefined, true );\r\n\t\t}\r\n\t};\r\n\r\n// Functions to create xhrs\r\nfunction createStandardXHR() {\r\n\ttry {\r\n\t\treturn new window.XMLHttpRequest();\r\n\t} catch( e ) {}\r\n}\r\n\r\nfunction createActiveXHR() {\r\n\ttry {\r\n\t\treturn new window.ActiveXObject(\"Microsoft.XMLHTTP\");\r\n\t} catch( e ) {}\r\n}\r\n\r\n// Create the request object\r\n// (This is still attached to ajaxSettings for backward compatibility)\r\njQuery.ajaxSettings.xhr = window.ActiveXObject ?\r\n\t/* Microsoft failed to properly\r\n\t * implement the XMLHttpRequest in IE7 (can't request local files),\r\n\t * so we use the ActiveXObject when it is available\r\n\t * Additionally XMLHttpRequest can be disabled in IE7/IE8 so\r\n\t * we need a fallback.\r\n\t */\r\n\tfunction() {\r\n\t\treturn !this.isLocal && createStandardXHR() || createActiveXHR();\r\n\t} :\r\n\t// For all other browsers, use the standard XMLHttpRequest object\r\n\tcreateStandardXHR;\r\n\r\n// Determine support properties\r\nxhrSupported = jQuery.ajaxSettings.xhr();\r\njQuery.support.cors = !!xhrSupported && ( \"withCredentials\" in xhrSupported );\r\nxhrSupported = jQuery.support.ajax = !!xhrSupported;\r\n\r\n// Create transport if the browser can provide an xhr\r\nif ( xhrSupported ) {\r\n\r\n\tjQuery.ajaxTransport(function( s ) {\r\n\t\t// Cross domain only allowed if supported through XMLHttpRequest\r\n\t\tif ( !s.crossDomain || jQuery.support.cors ) {\r\n\r\n\t\t\tvar callback;\r\n\r\n\t\t\treturn {\r\n\t\t\t\tsend: function( headers, complete ) {\r\n\r\n\t\t\t\t\t// Get a new xhr\r\n\t\t\t\t\tvar handle, i,\r\n\t\t\t\t\t\txhr = s.xhr();\r\n\r\n\t\t\t\t\t// Open the socket\r\n\t\t\t\t\t// Passing null username, generates a login popup on Opera (#2865)\r\n\t\t\t\t\tif ( s.username ) {\r\n\t\t\t\t\t\txhr.open( s.type, s.url, s.async, s.username, s.password );\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\txhr.open( s.type, s.url, s.async );\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t// Apply custom fields if provided\r\n\t\t\t\t\tif ( s.xhrFields ) {\r\n\t\t\t\t\t\tfor ( i in s.xhrFields ) {\r\n\t\t\t\t\t\t\txhr[ i ] = s.xhrFields[ i ];\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t// Override mime type if needed\r\n\t\t\t\t\tif ( s.mimeType && xhr.overrideMimeType ) {\r\n\t\t\t\t\t\txhr.overrideMimeType( s.mimeType );\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t// X-Requested-With header\r\n\t\t\t\t\t// For cross-domain requests, seeing as conditions for a preflight are\r\n\t\t\t\t\t// akin to a jigsaw puzzle, we simply never set it to be sure.\r\n\t\t\t\t\t// (it can always be set on a per-request basis or even using ajaxSetup)\r\n\t\t\t\t\t// For same-domain requests, won't change header if already provided.\r\n\t\t\t\t\tif ( !s.crossDomain && !headers[\"X-Requested-With\"] ) {\r\n\t\t\t\t\t\theaders[\"X-Requested-With\"] = \"XMLHttpRequest\";\r\n\t\t\t\t\t}\r\n\r\n\t\t\t\t\t// Need an extra try/catch for cross domain requests in Firefox 3\r\n\t\t\t\t\ttry {\r\n\t\t\t\t\t\tfor ( i in headers ) {\r\n\t\t\t\t\t\t\txhr.setRequestHeader( i, headers[ i ] );\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t} catch( err ) {}\r\n\r\n\t\t\t\t\t// Do send the request\r\n\t\t\t\t\t// This may raise an exception which is actually\r\n\t\t\t\t\t// handled in jQuery.ajax (so no try/catch here)\r\n\t\t\t\t\txhr.send( ( s.hasContent && s.data ) || null );\r\n\r\n\t\t\t\t\t// Listener\r\n\t\t\t\t\tcallback = function( _, isAbort ) {\r\n\t\t\t\t\t\tvar status, responseHeaders, statusText, responses;\r\n\r\n\t\t\t\t\t\t// Firefox throws exceptions when accessing properties\r\n\t\t\t\t\t\t// of an xhr when a network error occurred\r\n\t\t\t\t\t\t// http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE)\r\n\t\t\t\t\t\ttry {\r\n\r\n\t\t\t\t\t\t\t// Was never called and is aborted or complete\r\n\t\t\t\t\t\t\tif ( callback && ( isAbort || xhr.readyState === 4 ) ) {\r\n\r\n\t\t\t\t\t\t\t\t// Only called once\r\n\t\t\t\t\t\t\t\tcallback = undefined;\r\n\r\n\t\t\t\t\t\t\t\t// Do not keep as active anymore\r\n\t\t\t\t\t\t\t\tif ( handle ) {\r\n\t\t\t\t\t\t\t\t\txhr.onreadystatechange = jQuery.noop;\r\n\t\t\t\t\t\t\t\t\tif ( xhrOnUnloadAbort ) {\r\n\t\t\t\t\t\t\t\t\t\tdelete xhrCallbacks[ handle ];\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t\t\t// If it's an abort\r\n\t\t\t\t\t\t\t\tif ( isAbort ) {\r\n\t\t\t\t\t\t\t\t\t// Abort it manually if needed\r\n\t\t\t\t\t\t\t\t\tif ( xhr.readyState !== 4 ) {\r\n\t\t\t\t\t\t\t\t\t\txhr.abort();\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t} else {\r\n\t\t\t\t\t\t\t\t\tresponses = {};\r\n\t\t\t\t\t\t\t\t\tstatus = xhr.status;\r\n\t\t\t\t\t\t\t\t\tresponseHeaders = xhr.getAllResponseHeaders();\r\n\r\n\t\t\t\t\t\t\t\t\t// When requesting binary data, IE6-9 will throw an exception\r\n\t\t\t\t\t\t\t\t\t// on any attempt to access responseText (#11426)\r\n\t\t\t\t\t\t\t\t\tif ( typeof xhr.responseText === \"string\" ) {\r\n\t\t\t\t\t\t\t\t\t\tresponses.text = xhr.responseText;\r\n\t\t\t\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t\t\t\t// Firefox throws an exception when accessing\r\n\t\t\t\t\t\t\t\t\t// statusText for faulty cross-domain requests\r\n\t\t\t\t\t\t\t\t\ttry {\r\n\t\t\t\t\t\t\t\t\t\tstatusText = xhr.statusText;\r\n\t\t\t\t\t\t\t\t\t} catch( e ) {\r\n\t\t\t\t\t\t\t\t\t\t// We normalize with Webkit giving an empty statusText\r\n\t\t\t\t\t\t\t\t\t\tstatusText = \"\";\r\n\t\t\t\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t\t\t\t// Filter status for non standard behaviors\r\n\r\n\t\t\t\t\t\t\t\t\t// If the request is local and we have data: assume a success\r\n\t\t\t\t\t\t\t\t\t// (success with no data won't get notified, that's the best we\r\n\t\t\t\t\t\t\t\t\t// can do given current implementations)\r\n\t\t\t\t\t\t\t\t\tif ( !status && s.isLocal && !s.crossDomain ) {\r\n\t\t\t\t\t\t\t\t\t\tstatus = responses.text ? 200 : 404;\r\n\t\t\t\t\t\t\t\t\t// IE - #1450: sometimes returns 1223 when it should be 204\r\n\t\t\t\t\t\t\t\t\t} else if ( status === 1223 ) {\r\n\t\t\t\t\t\t\t\t\t\tstatus = 204;\r\n\t\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t} catch( firefoxAccessException ) {\r\n\t\t\t\t\t\t\tif ( !isAbort ) {\r\n\t\t\t\t\t\t\t\tcomplete( -1, firefoxAccessException );\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t}\r\n\r\n\t\t\t\t\t\t// Call complete if needed\r\n\t\t\t\t\t\tif ( responses ) {\r\n\t\t\t\t\t\t\tcomplete( status, statusText, responses, responseHeaders );\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t};\r\n\r\n\t\t\t\t\tif ( !s.async ) {\r\n\t\t\t\t\t\t// if we're in sync mode we fire the callback\r\n\t\t\t\t\t\tcallback();\r\n\t\t\t\t\t} else if ( xhr.readyState === 4 ) {\r\n\t\t\t\t\t\t// (IE6 & IE7) if it's in cache and has been\r\n\t\t\t\t\t\t// retrieved directly we need to fire the callback\r\n\t\t\t\t\t\tsetTimeout( callback );\r\n\t\t\t\t\t} else {\r\n\t\t\t\t\t\thandle = ++xhrId;\r\n\t\t\t\t\t\tif ( xhrOnUnloadAbort ) {\r\n\t\t\t\t\t\t\t// Create the active xhrs callbacks list if needed\r\n\t\t\t\t\t\t\t// and attach the unload handler\r\n\t\t\t\t\t\t\tif ( !xhrCallbacks ) {\r\n\t\t\t\t\t\t\t\txhrCallbacks = {};\r\n\t\t\t\t\t\t\t\tjQuery( window ).unload( xhrOnUnloadAbort );\r\n\t\t\t\t\t\t\t}\r\n\t\t\t\t\t\t\t// Add to list of active xhrs callbacks\r\n\t\t\t\t\t\t\txhrCallbacks[ handle ] = callback;\r\n\t\t\t\t\t\t}\r\n\t\t\t\t\t\txhr.onreadystatechange = callback;\r\n\t\t\t\t\t}\r\n\t\t\t\t},\r\n\r\n\t\t\t\tabort: function() {\r\n\t\t\t\t\tif ( callback ) {\r\n\t\t\t\t\t\tcallback( undefined, true );\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t};\r\n\t\t}\r\n\t});\r\n}\r\nvar fxNow, timerId,\r\n\trfxtypes = /^(?:toggle|show|hide)$/,\r\n\trfxnum = new RegExp( \"^(?:([+-])=|)(\" + core_pnum + \")([a-z%]*)$\", \"i\" ),\r\n\trrun = /queueHooks$/,\r\n\tanimationPrefilters = [ defaultPrefilter ],\r\n\ttweeners = {\r\n\t\t\"*\": [function( prop, value ) {\r\n\t\t\tvar tween = this.createTween( prop, value ),\r\n\t\t\t\ttarget = tween.cur(),\r\n\t\t\t\tparts = rfxnum.exec( value ),\r\n\t\t\t\tunit = parts && parts[ 3 ] || ( jQuery.cssNumber[ prop ] ? \"\" : \"px\" ),\r\n\r\n\t\t\t\t// Starting value computation is required for potential unit mismatches\r\n\t\t\t\tstart = ( jQuery.cssNumber[ prop ] || unit !== \"px\" && +target ) &&\r\n\t\t\t\t\trfxnum.exec( jQuery.css( tween.elem, prop ) ),\r\n\t\t\t\tscale = 1,\r\n\t\t\t\tmaxIterations = 20;\r\n\r\n\t\t\tif ( start && start[ 3 ] !== unit ) {\r\n\t\t\t\t// Trust units reported by jQuery.css\r\n\t\t\t\tunit = unit || start[ 3 ];\r\n\r\n\t\t\t\t// Make sure we update the tween properties later on\r\n\t\t\t\tparts = parts || [];\r\n\r\n\t\t\t\t// Iteratively approximate from a nonzero starting point\r\n\t\t\t\tstart = +target || 1;\r\n\r\n\t\t\t\tdo {\r\n\t\t\t\t\t// If previous iteration zeroed out, double until we get *something*\r\n\t\t\t\t\t// Use a string for doubling factor so we don't accidentally see scale as unchanged below\r\n\t\t\t\t\tscale = scale || \".5\";\r\n\r\n\t\t\t\t\t// Adjust and apply\r\n\t\t\t\t\tstart = start / scale;\r\n\t\t\t\t\tjQuery.style( tween.elem, prop, start + unit );\r\n\r\n\t\t\t\t// Update scale, tolerating zero or NaN from tween.cur()\r\n\t\t\t\t// And breaking the loop if scale is unchanged or perfect, or if we've just had enough\r\n\t\t\t\t} while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations );\r\n\t\t\t}\r\n\r\n\t\t\t// Update tween properties\r\n\t\t\tif ( parts ) {\r\n\t\t\t\tstart = tween.start = +start || +target || 0;\r\n\t\t\t\ttween.unit = unit;\r\n\t\t\t\t// If a +=/-= token was provided, we're doing a relative animation\r\n\t\t\t\ttween.end = parts[ 1 ] ?\r\n\t\t\t\t\tstart + ( parts[ 1 ] + 1 ) * parts[ 2 ] :\r\n\t\t\t\t\t+parts[ 2 ];\r\n\t\t\t}\r\n\r\n\t\t\treturn tween;\r\n\t\t}]\r\n\t};\r\n\r\n// Animations created synchronously will run synchronously\r\nfunction createFxNow() {\r\n\tsetTimeout(function() {\r\n\t\tfxNow = undefined;\r\n\t});\r\n\treturn ( fxNow = jQuery.now() );\r\n}\r\n\r\nfunction createTween( value, prop, animation ) {\r\n\tvar tween,\r\n\t\tcollection = ( tweeners[ prop ] || [] ).concat( tweeners[ \"*\" ] ),\r\n\t\tindex = 0,\r\n\t\tlength = collection.length;\r\n\tfor ( ; index < length; index++ ) {\r\n\t\tif ( (tween = collection[ index ].call( animation, prop, value )) ) {\r\n\r\n\t\t\t// we're done with this property\r\n\t\t\treturn tween;\r\n\t\t}\r\n\t}\r\n}\r\n\r\nfunction Animation( elem, properties, options ) {\r\n\tvar result,\r\n\t\tstopped,\r\n\t\tindex = 0,\r\n\t\tlength = animationPrefilters.length,\r\n\t\tdeferred = jQuery.Deferred().always( function() {\r\n\t\t\t// don't match elem in the :animated selector\r\n\t\t\tdelete tick.elem;\r\n\t\t}),\r\n\t\ttick = function() {\r\n\t\t\tif ( stopped ) {\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t\tvar currentTime = fxNow || createFxNow(),\r\n\t\t\t\tremaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),\r\n\t\t\t\t// archaic crash bug won't allow us to use 1 - ( 0.5 || 0 ) (#12497)\r\n\t\t\t\ttemp = remaining / animation.duration || 0,\r\n\t\t\t\tpercent = 1 - temp,\r\n\t\t\t\tindex = 0,\r\n\t\t\t\tlength = animation.tweens.length;\r\n\r\n\t\t\tfor ( ; index < length ; index++ ) {\r\n\t\t\t\tanimation.tweens[ index ].run( percent );\r\n\t\t\t}\r\n\r\n\t\t\tdeferred.notifyWith( elem, [ animation, percent, remaining ]);\r\n\r\n\t\t\tif ( percent < 1 && length ) {\r\n\t\t\t\treturn remaining;\r\n\t\t\t} else {\r\n\t\t\t\tdeferred.resolveWith( elem, [ animation ] );\r\n\t\t\t\treturn false;\r\n\t\t\t}\r\n\t\t},\r\n\t\tanimation = deferred.promise({\r\n\t\t\telem: elem,\r\n\t\t\tprops: jQuery.extend( {}, properties ),\r\n\t\t\topts: jQuery.extend( true, { specialEasing: {} }, options ),\r\n\t\t\toriginalProperties: properties,\r\n\t\t\toriginalOptions: options,\r\n\t\t\tstartTime: fxNow || createFxNow(),\r\n\t\t\tduration: options.duration,\r\n\t\t\ttweens: [],\r\n\t\t\tcreateTween: function( prop, end ) {\r\n\t\t\t\tvar tween = jQuery.Tween( elem, animation.opts, prop, end,\r\n\t\t\t\t\t\tanimation.opts.specialEasing[ prop ] || animation.opts.easing );\r\n\t\t\t\tanimation.tweens.push( tween );\r\n\t\t\t\treturn tween;\r\n\t\t\t},\r\n\t\t\tstop: function( gotoEnd ) {\r\n\t\t\t\tvar index = 0,\r\n\t\t\t\t\t// if we are going to the end, we want to run all the tweens\r\n\t\t\t\t\t// otherwise we skip this part\r\n\t\t\t\t\tlength = gotoEnd ? animation.tweens.length : 0;\r\n\t\t\t\tif ( stopped ) {\r\n\t\t\t\t\treturn this;\r\n\t\t\t\t}\r\n\t\t\t\tstopped = true;\r\n\t\t\t\tfor ( ; index < length ; index++ ) {\r\n\t\t\t\t\tanimation.tweens[ index ].run( 1 );\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// resolve when we played the last frame\r\n\t\t\t\t// otherwise, reject\r\n\t\t\t\tif ( gotoEnd ) {\r\n\t\t\t\t\tdeferred.resolveWith( elem, [ animation, gotoEnd ] );\r\n\t\t\t\t} else {\r\n\t\t\t\t\tdeferred.rejectWith( elem, [ animation, gotoEnd ] );\r\n\t\t\t\t}\r\n\t\t\t\treturn this;\r\n\t\t\t}\r\n\t\t}),\r\n\t\tprops = animation.props;\r\n\r\n\tpropFilter( props, animation.opts.specialEasing );\r\n\r\n\tfor ( ; index < length ; index++ ) {\r\n\t\tresult = animationPrefilters[ index ].call( animation, elem, props, animation.opts );\r\n\t\tif ( result ) {\r\n\t\t\treturn result;\r\n\t\t}\r\n\t}\r\n\r\n\tjQuery.map( props, createTween, animation );\r\n\r\n\tif ( jQuery.isFunction( animation.opts.start ) ) {\r\n\t\tanimation.opts.start.call( elem, animation );\r\n\t}\r\n\r\n\tjQuery.fx.timer(\r\n\t\tjQuery.extend( tick, {\r\n\t\t\telem: elem,\r\n\t\t\tanim: animation,\r\n\t\t\tqueue: animation.opts.queue\r\n\t\t})\r\n\t);\r\n\r\n\t// attach callbacks from options\r\n\treturn animation.progress( animation.opts.progress )\r\n\t\t.done( animation.opts.done, animation.opts.complete )\r\n\t\t.fail( animation.opts.fail )\r\n\t\t.always( animation.opts.always );\r\n}\r\n\r\nfunction propFilter( props, specialEasing ) {\r\n\tvar index, name, easing, value, hooks;\r\n\r\n\t// camelCase, specialEasing and expand cssHook pass\r\n\tfor ( index in props ) {\r\n\t\tname = jQuery.camelCase( index );\r\n\t\teasing = specialEasing[ name ];\r\n\t\tvalue = props[ index ];\r\n\t\tif ( jQuery.isArray( value ) ) {\r\n\t\t\teasing = value[ 1 ];\r\n\t\t\tvalue = props[ index ] = value[ 0 ];\r\n\t\t}\r\n\r\n\t\tif ( index !== name ) {\r\n\t\t\tprops[ name ] = value;\r\n\t\t\tdelete props[ index ];\r\n\t\t}\r\n\r\n\t\thooks = jQuery.cssHooks[ name ];\r\n\t\tif ( hooks && \"expand\" in hooks ) {\r\n\t\t\tvalue = hooks.expand( value );\r\n\t\t\tdelete props[ name ];\r\n\r\n\t\t\t// not quite $.extend, this wont overwrite keys already present.\r\n\t\t\t// also - reusing 'index' from above because we have the correct \"name\"\r\n\t\t\tfor ( index in value ) {\r\n\t\t\t\tif ( !( index in props ) ) {\r\n\t\t\t\t\tprops[ index ] = value[ index ];\r\n\t\t\t\t\tspecialEasing[ index ] = easing;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tspecialEasing[ name ] = easing;\r\n\t\t}\r\n\t}\r\n}\r\n\r\njQuery.Animation = jQuery.extend( Animation, {\r\n\r\n\ttweener: function( props, callback ) {\r\n\t\tif ( jQuery.isFunction( props ) ) {\r\n\t\t\tcallback = props;\r\n\t\t\tprops = [ \"*\" ];\r\n\t\t} else {\r\n\t\t\tprops = props.split(\" \");\r\n\t\t}\r\n\r\n\t\tvar prop,\r\n\t\t\tindex = 0,\r\n\t\t\tlength = props.length;\r\n\r\n\t\tfor ( ; index < length ; index++ ) {\r\n\t\t\tprop = props[ index ];\r\n\t\t\ttweeners[ prop ] = tweeners[ prop ] || [];\r\n\t\t\ttweeners[ prop ].unshift( callback );\r\n\t\t}\r\n\t},\r\n\r\n\tprefilter: function( callback, prepend ) {\r\n\t\tif ( prepend ) {\r\n\t\t\tanimationPrefilters.unshift( callback );\r\n\t\t} else {\r\n\t\t\tanimationPrefilters.push( callback );\r\n\t\t}\r\n\t}\r\n});\r\n\r\nfunction defaultPrefilter( elem, props, opts ) {\r\n\t/* jshint validthis: true */\r\n\tvar prop, value, toggle, tween, hooks, oldfire,\r\n\t\tanim = this,\r\n\t\torig = {},\r\n\t\tstyle = elem.style,\r\n\t\thidden = elem.nodeType && isHidden( elem ),\r\n\t\tdataShow = jQuery._data( elem, \"fxshow\" );\r\n\r\n\t// handle queue: false promises\r\n\tif ( !opts.queue ) {\r\n\t\thooks = jQuery._queueHooks( elem, \"fx\" );\r\n\t\tif ( hooks.unqueued == null ) {\r\n\t\t\thooks.unqueued = 0;\r\n\t\t\toldfire = hooks.empty.fire;\r\n\t\t\thooks.empty.fire = function() {\r\n\t\t\t\tif ( !hooks.unqueued ) {\r\n\t\t\t\t\toldfire();\r\n\t\t\t\t}\r\n\t\t\t};\r\n\t\t}\r\n\t\thooks.unqueued++;\r\n\r\n\t\tanim.always(function() {\r\n\t\t\t// doing this makes sure that the complete handler will be called\r\n\t\t\t// before this completes\r\n\t\t\tanim.always(function() {\r\n\t\t\t\thooks.unqueued--;\r\n\t\t\t\tif ( !jQuery.queue( elem, \"fx\" ).length ) {\r\n\t\t\t\t\thooks.empty.fire();\r\n\t\t\t\t}\r\n\t\t\t});\r\n\t\t});\r\n\t}\r\n\r\n\t// height/width overflow pass\r\n\tif ( elem.nodeType === 1 && ( \"height\" in props || \"width\" in props ) ) {\r\n\t\t// Make sure that nothing sneaks out\r\n\t\t// Record all 3 overflow attributes because IE does not\r\n\t\t// change the overflow attribute when overflowX and\r\n\t\t// overflowY are set to the same value\r\n\t\topts.overflow = [ style.overflow, style.overflowX, style.overflowY ];\r\n\r\n\t\t// Set display property to inline-block for height/width\r\n\t\t// animations on inline elements that are having width/height animated\r\n\t\tif ( jQuery.css( elem, \"display\" ) === \"inline\" &&\r\n\t\t\t\tjQuery.css( elem, \"float\" ) === \"none\" ) {\r\n\r\n\t\t\t// inline-level elements accept inline-block;\r\n\t\t\t// block-level elements need to be inline with layout\r\n\t\t\tif ( !jQuery.support.inlineBlockNeedsLayout || css_defaultDisplay( elem.nodeName ) === \"inline\" ) {\r\n\t\t\t\tstyle.display = \"inline-block\";\r\n\r\n\t\t\t} else {\r\n\t\t\t\tstyle.zoom = 1;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n\r\n\tif ( opts.overflow ) {\r\n\t\tstyle.overflow = \"hidden\";\r\n\t\tif ( !jQuery.support.shrinkWrapBlocks ) {\r\n\t\t\tanim.always(function() {\r\n\t\t\t\tstyle.overflow = opts.overflow[ 0 ];\r\n\t\t\t\tstyle.overflowX = opts.overflow[ 1 ];\r\n\t\t\t\tstyle.overflowY = opts.overflow[ 2 ];\r\n\t\t\t});\r\n\t\t}\r\n\t}\r\n\r\n\r\n\t// show/hide pass\r\n\tfor ( prop in props ) {\r\n\t\tvalue = props[ prop ];\r\n\t\tif ( rfxtypes.exec( value ) ) {\r\n\t\t\tdelete props[ prop ];\r\n\t\t\ttoggle = toggle || value === \"toggle\";\r\n\t\t\tif ( value === ( hidden ? \"hide\" : \"show\" ) ) {\r\n\t\t\t\tcontinue;\r\n\t\t\t}\r\n\t\t\torig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );\r\n\t\t}\r\n\t}\r\n\r\n\tif ( !jQuery.isEmptyObject( orig ) ) {\r\n\t\tif ( dataShow ) {\r\n\t\t\tif ( \"hidden\" in dataShow ) {\r\n\t\t\t\thidden = dataShow.hidden;\r\n\t\t\t}\r\n\t\t} else {\r\n\t\t\tdataShow = jQuery._data( elem, \"fxshow\", {} );\r\n\t\t}\r\n\r\n\t\t// store state if its toggle - enables .stop().toggle() to \"reverse\"\r\n\t\tif ( toggle ) {\r\n\t\t\tdataShow.hidden = !hidden;\r\n\t\t}\r\n\t\tif ( hidden ) {\r\n\t\t\tjQuery( elem ).show();\r\n\t\t} else {\r\n\t\t\tanim.done(function() {\r\n\t\t\t\tjQuery( elem ).hide();\r\n\t\t\t});\r\n\t\t}\r\n\t\tanim.done(function() {\r\n\t\t\tvar prop;\r\n\t\t\tjQuery._removeData( elem, \"fxshow\" );\r\n\t\t\tfor ( prop in orig ) {\r\n\t\t\t\tjQuery.style( elem, prop, orig[ prop ] );\r\n\t\t\t}\r\n\t\t});\r\n\t\tfor ( prop in orig ) {\r\n\t\t\ttween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );\r\n\r\n\t\t\tif ( !( prop in dataShow ) ) {\r\n\t\t\t\tdataShow[ prop ] = tween.start;\r\n\t\t\t\tif ( hidden ) {\r\n\t\t\t\t\ttween.end = tween.start;\r\n\t\t\t\t\ttween.start = prop === \"width\" || prop === \"height\" ? 1 : 0;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n}\r\n\r\nfunction Tween( elem, options, prop, end, easing ) {\r\n\treturn new Tween.prototype.init( elem, options, prop, end, easing );\r\n}\r\njQuery.Tween = Tween;\r\n\r\nTween.prototype = {\r\n\tconstructor: Tween,\r\n\tinit: function( elem, options, prop, end, easing, unit ) {\r\n\t\tthis.elem = elem;\r\n\t\tthis.prop = prop;\r\n\t\tthis.easing = easing || \"swing\";\r\n\t\tthis.options = options;\r\n\t\tthis.start = this.now = this.cur();\r\n\t\tthis.end = end;\r\n\t\tthis.unit = unit || ( jQuery.cssNumber[ prop ] ? \"\" : \"px\" );\r\n\t},\r\n\tcur: function() {\r\n\t\tvar hooks = Tween.propHooks[ this.prop ];\r\n\r\n\t\treturn hooks && hooks.get ?\r\n\t\t\thooks.get( this ) :\r\n\t\t\tTween.propHooks._default.get( this );\r\n\t},\r\n\trun: function( percent ) {\r\n\t\tvar eased,\r\n\t\t\thooks = Tween.propHooks[ this.prop ];\r\n\r\n\t\tif ( this.options.duration ) {\r\n\t\t\tthis.pos = eased = jQuery.easing[ this.easing ](\r\n\t\t\t\tpercent, this.options.duration * percent, 0, 1, this.options.duration\r\n\t\t\t);\r\n\t\t} else {\r\n\t\t\tthis.pos = eased = percent;\r\n\t\t}\r\n\t\tthis.now = ( this.end - this.start ) * eased + this.start;\r\n\r\n\t\tif ( this.options.step ) {\r\n\t\t\tthis.options.step.call( this.elem, this.now, this );\r\n\t\t}\r\n\r\n\t\tif ( hooks && hooks.set ) {\r\n\t\t\thooks.set( this );\r\n\t\t} else {\r\n\t\t\tTween.propHooks._default.set( this );\r\n\t\t}\r\n\t\treturn this;\r\n\t}\r\n};\r\n\r\nTween.prototype.init.prototype = Tween.prototype;\r\n\r\nTween.propHooks = {\r\n\t_default: {\r\n\t\tget: function( tween ) {\r\n\t\t\tvar result;\r\n\r\n\t\t\tif ( tween.elem[ tween.prop ] != null &&\r\n\t\t\t\t(!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) {\r\n\t\t\t\treturn tween.elem[ tween.prop ];\r\n\t\t\t}\r\n\r\n\t\t\t// passing an empty string as a 3rd parameter to .css will automatically\r\n\t\t\t// attempt a parseFloat and fallback to a string if the parse fails\r\n\t\t\t// so, simple values such as \"10px\" are parsed to Float.\r\n\t\t\t// complex values such as \"rotate(1rad)\" are returned as is.\r\n\t\t\tresult = jQuery.css( tween.elem, tween.prop, \"\" );\r\n\t\t\t// Empty strings, null, undefined and \"auto\" are converted to 0.\r\n\t\t\treturn !result || result === \"auto\" ? 0 : result;\r\n\t\t},\r\n\t\tset: function( tween ) {\r\n\t\t\t// use step hook for back compat - use cssHook if its there - use .style if its\r\n\t\t\t// available and use plain properties where available\r\n\t\t\tif ( jQuery.fx.step[ tween.prop ] ) {\r\n\t\t\t\tjQuery.fx.step[ tween.prop ]( tween );\r\n\t\t\t} else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) {\r\n\t\t\t\tjQuery.style( tween.elem, tween.prop, tween.now + tween.unit );\r\n\t\t\t} else {\r\n\t\t\t\ttween.elem[ tween.prop ] = tween.now;\r\n\t\t\t}\r\n\t\t}\r\n\t}\r\n};\r\n\r\n// Support: IE <=9\r\n// Panic based approach to setting things on disconnected nodes\r\n\r\nTween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {\r\n\tset: function( tween ) {\r\n\t\tif ( tween.elem.nodeType && tween.elem.parentNode ) {\r\n\t\t\ttween.elem[ tween.prop ] = tween.now;\r\n\t\t}\r\n\t}\r\n};\r\n\r\njQuery.each([ \"toggle\", \"show\", \"hide\" ], function( i, name ) {\r\n\tvar cssFn = jQuery.fn[ name ];\r\n\tjQuery.fn[ name ] = function( speed, easing, callback ) {\r\n\t\treturn speed == null || typeof speed === \"boolean\" ?\r\n\t\t\tcssFn.apply( this, arguments ) :\r\n\t\t\tthis.animate( genFx( name, true ), speed, easing, callback );\r\n\t};\r\n});\r\n\r\njQuery.fn.extend({\r\n\tfadeTo: function( speed, to, easing, callback ) {\r\n\r\n\t\t// show any hidden elements after setting opacity to 0\r\n\t\treturn this.filter( isHidden ).css( \"opacity\", 0 ).show()\r\n\r\n\t\t\t// animate to the value specified\r\n\t\t\t.end().animate({ opacity: to }, speed, easing, callback );\r\n\t},\r\n\tanimate: function( prop, speed, easing, callback ) {\r\n\t\tvar empty = jQuery.isEmptyObject( prop ),\r\n\t\t\toptall = jQuery.speed( speed, easing, callback ),\r\n\t\t\tdoAnimation = function() {\r\n\t\t\t\t// Operate on a copy of prop so per-property easing won't be lost\r\n\t\t\t\tvar anim = Animation( this, jQuery.extend( {}, prop ), optall );\r\n\r\n\t\t\t\t// Empty animations, or finishing resolves immediately\r\n\t\t\t\tif ( empty || jQuery._data( this, \"finish\" ) ) {\r\n\t\t\t\t\tanim.stop( true );\r\n\t\t\t\t}\r\n\t\t\t};\r\n\t\t\tdoAnimation.finish = doAnimation;\r\n\r\n\t\treturn empty || optall.queue === false ?\r\n\t\t\tthis.each( doAnimation ) :\r\n\t\t\tthis.queue( optall.queue, doAnimation );\r\n\t},\r\n\tstop: function( type, clearQueue, gotoEnd ) {\r\n\t\tvar stopQueue = function( hooks ) {\r\n\t\t\tvar stop = hooks.stop;\r\n\t\t\tdelete hooks.stop;\r\n\t\t\tstop( gotoEnd );\r\n\t\t};\r\n\r\n\t\tif ( typeof type !== \"string\" ) {\r\n\t\t\tgotoEnd = clearQueue;\r\n\t\t\tclearQueue = type;\r\n\t\t\ttype = undefined;\r\n\t\t}\r\n\t\tif ( clearQueue && type !== false ) {\r\n\t\t\tthis.queue( type || \"fx\", [] );\r\n\t\t}\r\n\r\n\t\treturn this.each(function() {\r\n\t\t\tvar dequeue = true,\r\n\t\t\t\tindex = type != null && type + \"queueHooks\",\r\n\t\t\t\ttimers = jQuery.timers,\r\n\t\t\t\tdata = jQuery._data( this );\r\n\r\n\t\t\tif ( index ) {\r\n\t\t\t\tif ( data[ index ] && data[ index ].stop ) {\r\n\t\t\t\t\tstopQueue( data[ index ] );\r\n\t\t\t\t}\r\n\t\t\t} else {\r\n\t\t\t\tfor ( index in data ) {\r\n\t\t\t\t\tif ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {\r\n\t\t\t\t\t\tstopQueue( data[ index ] );\r\n\t\t\t\t\t}\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\tfor ( index = timers.length; index--; ) {\r\n\t\t\t\tif ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {\r\n\t\t\t\t\ttimers[ index ].anim.stop( gotoEnd );\r\n\t\t\t\t\tdequeue = false;\r\n\t\t\t\t\ttimers.splice( index, 1 );\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// start the next in the queue if the last step wasn't forced\r\n\t\t\t// timers currently will call their complete callbacks, which will dequeue\r\n\t\t\t// but only if they were gotoEnd\r\n\t\t\tif ( dequeue || !gotoEnd ) {\r\n\t\t\t\tjQuery.dequeue( this, type );\r\n\t\t\t}\r\n\t\t});\r\n\t},\r\n\tfinish: function( type ) {\r\n\t\tif ( type !== false ) {\r\n\t\t\ttype = type || \"fx\";\r\n\t\t}\r\n\t\treturn this.each(function() {\r\n\t\t\tvar index,\r\n\t\t\t\tdata = jQuery._data( this ),\r\n\t\t\t\tqueue = data[ type + \"queue\" ],\r\n\t\t\t\thooks = data[ type + \"queueHooks\" ],\r\n\t\t\t\ttimers = jQuery.timers,\r\n\t\t\t\tlength = queue ? queue.length : 0;\r\n\r\n\t\t\t// enable finishing flag on private data\r\n\t\t\tdata.finish = true;\r\n\r\n\t\t\t// empty the queue first\r\n\t\t\tjQuery.queue( this, type, [] );\r\n\r\n\t\t\tif ( hooks && hooks.stop ) {\r\n\t\t\t\thooks.stop.call( this, true );\r\n\t\t\t}\r\n\r\n\t\t\t// look for any active animations, and finish them\r\n\t\t\tfor ( index = timers.length; index--; ) {\r\n\t\t\t\tif ( timers[ index ].elem === this && timers[ index ].queue === type ) {\r\n\t\t\t\t\ttimers[ index ].anim.stop( true );\r\n\t\t\t\t\ttimers.splice( index, 1 );\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// look for any animations in the old queue and finish them\r\n\t\t\tfor ( index = 0; index < length; index++ ) {\r\n\t\t\t\tif ( queue[ index ] && queue[ index ].finish ) {\r\n\t\t\t\t\tqueue[ index ].finish.call( this );\r\n\t\t\t\t}\r\n\t\t\t}\r\n\r\n\t\t\t// turn off finishing flag\r\n\t\t\tdelete data.finish;\r\n\t\t});\r\n\t}\r\n});\r\n\r\n// Generate parameters to create a standard animation\r\nfunction genFx( type, includeWidth ) {\r\n\tvar which,\r\n\t\tattrs = { height: type },\r\n\t\ti = 0;\r\n\r\n\t// if we include width, step value is 1 to do all cssExpand values,\r\n\t// if we don't include width, step value is 2 to skip over Left and Right\r\n\tincludeWidth = includeWidth? 1 : 0;\r\n\tfor( ; i < 4 ; i += 2 - includeWidth ) {\r\n\t\twhich = cssExpand[ i ];\r\n\t\tattrs[ \"margin\" + which ] = attrs[ \"padding\" + which ] = type;\r\n\t}\r\n\r\n\tif ( includeWidth ) {\r\n\t\tattrs.opacity = attrs.width = type;\r\n\t}\r\n\r\n\treturn attrs;\r\n}\r\n\r\n// Generate shortcuts for custom animations\r\njQuery.each({\r\n\tslideDown: genFx(\"show\"),\r\n\tslideUp: genFx(\"hide\"),\r\n\tslideToggle: genFx(\"toggle\"),\r\n\tfadeIn: { opacity: \"show\" },\r\n\tfadeOut: { opacity: \"hide\" },\r\n\tfadeToggle: { opacity: \"toggle\" }\r\n}, function( name, props ) {\r\n\tjQuery.fn[ name ] = function( speed, easing, callback ) {\r\n\t\treturn this.animate( props, speed, easing, callback );\r\n\t};\r\n});\r\n\r\njQuery.speed = function( speed, easing, fn ) {\r\n\tvar opt = speed && typeof speed === \"object\" ? jQuery.extend( {}, speed ) : {\r\n\t\tcomplete: fn || !fn && easing ||\r\n\t\t\tjQuery.isFunction( speed ) && speed,\r\n\t\tduration: speed,\r\n\t\teasing: fn && easing || easing && !jQuery.isFunction( easing ) && easing\r\n\t};\r\n\r\n\topt.duration = jQuery.fx.off ? 0 : typeof opt.duration === \"number\" ? opt.duration :\r\n\t\topt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;\r\n\r\n\t// normalize opt.queue - true/undefined/null -> \"fx\"\r\n\tif ( opt.queue == null || opt.queue === true ) {\r\n\t\topt.queue = \"fx\";\r\n\t}\r\n\r\n\t// Queueing\r\n\topt.old = opt.complete;\r\n\r\n\topt.complete = function() {\r\n\t\tif ( jQuery.isFunction( opt.old ) ) {\r\n\t\t\topt.old.call( this );\r\n\t\t}\r\n\r\n\t\tif ( opt.queue ) {\r\n\t\t\tjQuery.dequeue( this, opt.queue );\r\n\t\t}\r\n\t};\r\n\r\n\treturn opt;\r\n};\r\n\r\njQuery.easing = {\r\n\tlinear: function( p ) {\r\n\t\treturn p;\r\n\t},\r\n\tswing: function( p ) {\r\n\t\treturn 0.5 - Math.cos( p*Math.PI ) / 2;\r\n\t}\r\n};\r\n\r\njQuery.timers = [];\r\njQuery.fx = Tween.prototype.init;\r\njQuery.fx.tick = function() {\r\n\tvar timer,\r\n\t\ttimers = jQuery.timers,\r\n\t\ti = 0;\r\n\r\n\tfxNow = jQuery.now();\r\n\r\n\tfor ( ; i < timers.length; i++ ) {\r\n\t\ttimer = timers[ i ];\r\n\t\t// Checks the timer has not already been removed\r\n\t\tif ( !timer() && timers[ i ] === timer ) {\r\n\t\t\ttimers.splice( i--, 1 );\r\n\t\t}\r\n\t}\r\n\r\n\tif ( !timers.length ) {\r\n\t\tjQuery.fx.stop();\r\n\t}\r\n\tfxNow = undefined;\r\n};\r\n\r\njQuery.fx.timer = function( timer ) {\r\n\tif ( timer() && jQuery.timers.push( timer ) ) {\r\n\t\tjQuery.fx.start();\r\n\t}\r\n};\r\n\r\njQuery.fx.interval = 13;\r\n\r\njQuery.fx.start = function() {\r\n\tif ( !timerId ) {\r\n\t\ttimerId = setInterval( jQuery.fx.tick, jQuery.fx.interval );\r\n\t}\r\n};\r\n\r\njQuery.fx.stop = function() {\r\n\tclearInterval( timerId );\r\n\ttimerId = null;\r\n};\r\n\r\njQuery.fx.speeds = {\r\n\tslow: 600,\r\n\tfast: 200,\r\n\t// Default speed\r\n\t_default: 400\r\n};\r\n\r\n// Back Compat <1.8 extension point\r\njQuery.fx.step = {};\r\n\r\nif ( jQuery.expr && jQuery.expr.filters ) {\r\n\tjQuery.expr.filters.animated = function( elem ) {\r\n\t\treturn jQuery.grep(jQuery.timers, function( fn ) {\r\n\t\t\treturn elem === fn.elem;\r\n\t\t}).length;\r\n\t};\r\n}\r\njQuery.fn.offset = function( options ) {\r\n\tif ( arguments.length ) {\r\n\t\treturn options === undefined ?\r\n\t\t\tthis :\r\n\t\t\tthis.each(function( i ) {\r\n\t\t\t\tjQuery.offset.setOffset( this, options, i );\r\n\t\t\t});\r\n\t}\r\n\r\n\tvar docElem, win,\r\n\t\tbox = { top: 0, left: 0 },\r\n\t\telem = this[ 0 ],\r\n\t\tdoc = elem && elem.ownerDocument;\r\n\r\n\tif ( !doc ) {\r\n\t\treturn;\r\n\t}\r\n\r\n\tdocElem = doc.documentElement;\r\n\r\n\t// Make sure it's not a disconnected DOM node\r\n\tif ( !jQuery.contains( docElem, elem ) ) {\r\n\t\treturn box;\r\n\t}\r\n\r\n\t// If we don't have gBCR, just use 0,0 rather than error\r\n\t// BlackBerry 5, iOS 3 (original iPhone)\r\n\tif ( typeof elem.getBoundingClientRect !== core_strundefined ) {\r\n\t\tbox = elem.getBoundingClientRect();\r\n\t}\r\n\twin = getWindow( doc );\r\n\treturn {\r\n\t\ttop: box.top  + ( win.pageYOffset || docElem.scrollTop )  - ( docElem.clientTop  || 0 ),\r\n\t\tleft: box.left + ( win.pageXOffset || docElem.scrollLeft ) - ( docElem.clientLeft || 0 )\r\n\t};\r\n};\r\n\r\njQuery.offset = {\r\n\r\n\tsetOffset: function( elem, options, i ) {\r\n\t\tvar position = jQuery.css( elem, \"position\" );\r\n\r\n\t\t// set position first, in-case top/left are set even on static elem\r\n\t\tif ( position === \"static\" ) {\r\n\t\t\telem.style.position = \"relative\";\r\n\t\t}\r\n\r\n\t\tvar curElem = jQuery( elem ),\r\n\t\t\tcurOffset = curElem.offset(),\r\n\t\t\tcurCSSTop = jQuery.css( elem, \"top\" ),\r\n\t\t\tcurCSSLeft = jQuery.css( elem, \"left\" ),\r\n\t\t\tcalculatePosition = ( position === \"absolute\" || position === \"fixed\" ) && jQuery.inArray(\"auto\", [curCSSTop, curCSSLeft]) > -1,\r\n\t\t\tprops = {}, curPosition = {}, curTop, curLeft;\r\n\r\n\t\t// need to be able to calculate position if either top or left is auto and position is either absolute or fixed\r\n\t\tif ( calculatePosition ) {\r\n\t\t\tcurPosition = curElem.position();\r\n\t\t\tcurTop = curPosition.top;\r\n\t\t\tcurLeft = curPosition.left;\r\n\t\t} else {\r\n\t\t\tcurTop = parseFloat( curCSSTop ) || 0;\r\n\t\t\tcurLeft = parseFloat( curCSSLeft ) || 0;\r\n\t\t}\r\n\r\n\t\tif ( jQuery.isFunction( options ) ) {\r\n\t\t\toptions = options.call( elem, i, curOffset );\r\n\t\t}\r\n\r\n\t\tif ( options.top != null ) {\r\n\t\t\tprops.top = ( options.top - curOffset.top ) + curTop;\r\n\t\t}\r\n\t\tif ( options.left != null ) {\r\n\t\t\tprops.left = ( options.left - curOffset.left ) + curLeft;\r\n\t\t}\r\n\r\n\t\tif ( \"using\" in options ) {\r\n\t\t\toptions.using.call( elem, props );\r\n\t\t} else {\r\n\t\t\tcurElem.css( props );\r\n\t\t}\r\n\t}\r\n};\r\n\r\n\r\njQuery.fn.extend({\r\n\r\n\tposition: function() {\r\n\t\tif ( !this[ 0 ] ) {\r\n\t\t\treturn;\r\n\t\t}\r\n\r\n\t\tvar offsetParent, offset,\r\n\t\t\tparentOffset = { top: 0, left: 0 },\r\n\t\t\telem = this[ 0 ];\r\n\r\n\t\t// fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is it's only offset parent\r\n\t\tif ( jQuery.css( elem, \"position\" ) === \"fixed\" ) {\r\n\t\t\t// we assume that getBoundingClientRect is available when computed position is fixed\r\n\t\t\toffset = elem.getBoundingClientRect();\r\n\t\t} else {\r\n\t\t\t// Get *real* offsetParent\r\n\t\t\toffsetParent = this.offsetParent();\r\n\r\n\t\t\t// Get correct offsets\r\n\t\t\toffset = this.offset();\r\n\t\t\tif ( !jQuery.nodeName( offsetParent[ 0 ], \"html\" ) ) {\r\n\t\t\t\tparentOffset = offsetParent.offset();\r\n\t\t\t}\r\n\r\n\t\t\t// Add offsetParent borders\r\n\t\t\tparentOffset.top  += jQuery.css( offsetParent[ 0 ], \"borderTopWidth\", true );\r\n\t\t\tparentOffset.left += jQuery.css( offsetParent[ 0 ], \"borderLeftWidth\", true );\r\n\t\t}\r\n\r\n\t\t// Subtract parent offsets and element margins\r\n\t\t// note: when an element has margin: auto the offsetLeft and marginLeft\r\n\t\t// are the same in Safari causing offset.left to incorrectly be 0\r\n\t\treturn {\r\n\t\t\ttop:  offset.top  - parentOffset.top - jQuery.css( elem, \"marginTop\", true ),\r\n\t\t\tleft: offset.left - parentOffset.left - jQuery.css( elem, \"marginLeft\", true)\r\n\t\t};\r\n\t},\r\n\r\n\toffsetParent: function() {\r\n\t\treturn this.map(function() {\r\n\t\t\tvar offsetParent = this.offsetParent || docElem;\r\n\t\t\twhile ( offsetParent && ( !jQuery.nodeName( offsetParent, \"html\" ) && jQuery.css( offsetParent, \"position\") === \"static\" ) ) {\r\n\t\t\t\toffsetParent = offsetParent.offsetParent;\r\n\t\t\t}\r\n\t\t\treturn offsetParent || docElem;\r\n\t\t});\r\n\t}\r\n});\r\n\r\n\r\n// Create scrollLeft and scrollTop methods\r\njQuery.each( {scrollLeft: \"pageXOffset\", scrollTop: \"pageYOffset\"}, function( method, prop ) {\r\n\tvar top = /Y/.test( prop );\r\n\r\n\tjQuery.fn[ method ] = function( val ) {\r\n\t\treturn jQuery.access( this, function( elem, method, val ) {\r\n\t\t\tvar win = getWindow( elem );\r\n\r\n\t\t\tif ( val === undefined ) {\r\n\t\t\t\treturn win ? (prop in win) ? win[ prop ] :\r\n\t\t\t\t\twin.document.documentElement[ method ] :\r\n\t\t\t\t\telem[ method ];\r\n\t\t\t}\r\n\r\n\t\t\tif ( win ) {\r\n\t\t\t\twin.scrollTo(\r\n\t\t\t\t\t!top ? val : jQuery( win ).scrollLeft(),\r\n\t\t\t\t\ttop ? val : jQuery( win ).scrollTop()\r\n\t\t\t\t);\r\n\r\n\t\t\t} else {\r\n\t\t\t\telem[ method ] = val;\r\n\t\t\t}\r\n\t\t}, method, val, arguments.length, null );\r\n\t};\r\n});\r\n\r\nfunction getWindow( elem ) {\r\n\treturn jQuery.isWindow( elem ) ?\r\n\t\telem :\r\n\t\telem.nodeType === 9 ?\r\n\t\t\telem.defaultView || elem.parentWindow :\r\n\t\t\tfalse;\r\n}\r\n// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods\r\njQuery.each( { Height: \"height\", Width: \"width\" }, function( name, type ) {\r\n\tjQuery.each( { padding: \"inner\" + name, content: type, \"\": \"outer\" + name }, function( defaultExtra, funcName ) {\r\n\t\t// margin is only for outerHeight, outerWidth\r\n\t\tjQuery.fn[ funcName ] = function( margin, value ) {\r\n\t\t\tvar chainable = arguments.length && ( defaultExtra || typeof margin !== \"boolean\" ),\r\n\t\t\t\textra = defaultExtra || ( margin === true || value === true ? \"margin\" : \"border\" );\r\n\r\n\t\t\treturn jQuery.access( this, function( elem, type, value ) {\r\n\t\t\t\tvar doc;\r\n\r\n\t\t\t\tif ( jQuery.isWindow( elem ) ) {\r\n\t\t\t\t\t// As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there\r\n\t\t\t\t\t// isn't a whole lot we can do. See pull request at this URL for discussion:\r\n\t\t\t\t\t// https://github.com/jquery/jquery/pull/764\r\n\t\t\t\t\treturn elem.document.documentElement[ \"client\" + name ];\r\n\t\t\t\t}\r\n\r\n\t\t\t\t// Get document width or height\r\n\t\t\t\tif ( elem.nodeType === 9 ) {\r\n\t\t\t\t\tdoc = elem.documentElement;\r\n\r\n\t\t\t\t\t// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height], whichever is greatest\r\n\t\t\t\t\t// unfortunately, this causes bug #3838 in IE6/8 only, but there is currently no good, small way to fix it.\r\n\t\t\t\t\treturn Math.max(\r\n\t\t\t\t\t\telem.body[ \"scroll\" + name ], doc[ \"scroll\" + name ],\r\n\t\t\t\t\t\telem.body[ \"offset\" + name ], doc[ \"offset\" + name ],\r\n\t\t\t\t\t\tdoc[ \"client\" + name ]\r\n\t\t\t\t\t);\r\n\t\t\t\t}\r\n\r\n\t\t\t\treturn value === undefined ?\r\n\t\t\t\t\t// Get width or height on the element, requesting but not forcing parseFloat\r\n\t\t\t\t\tjQuery.css( elem, type, extra ) :\r\n\r\n\t\t\t\t\t// Set width or height on the element\r\n\t\t\t\t\tjQuery.style( elem, type, value, extra );\r\n\t\t\t}, type, chainable ? margin : undefined, chainable, null );\r\n\t\t};\r\n\t});\r\n});\r\n// Limit scope pollution from any deprecated API\r\n// (function() {\r\n\r\n// The number of elements contained in the matched element set\r\njQuery.fn.size = function() {\r\n\treturn this.length;\r\n};\r\n\r\njQuery.fn.andSelf = jQuery.fn.addBack;\r\n\r\n// })();\r\nif ( typeof module === \"object\" && module && typeof module.exports === \"object\" ) {\r\n\t// Expose jQuery as module.exports in loaders that implement the Node\r\n\t// module pattern (including browserify). Do not create the global, since\r\n\t// the user will be storing it themselves locally, and globals are frowned\r\n\t// upon in the Node module world.\r\n\tmodule.exports = jQuery;\r\n} else {\r\n\t// Otherwise expose jQuery to the global object as usual\r\n\twindow.jQuery = window.$ = jQuery;\r\n\r\n\t// Register as a named AMD module, since jQuery can be concatenated with other\r\n\t// files that may use define, but not via a proper concatenation script that\r\n\t// understands anonymous AMD modules. A named AMD is safest and most robust\r\n\t// way to register. Lowercase jquery is used because AMD module names are\r\n\t// derived from file names, and jQuery is normally delivered in a lowercase\r\n\t// file name. Do this after creating the global so that if an AMD module wants\r\n\t// to call noConflict to hide this version of jQuery, it will work.\r\n\tif ( typeof define === \"function\" && define.amd ) {\r\n\t\tdefine( \"jquery\", [], function () { return jQuery; } );\r\n\t}\r\n}\r\n\r\n})( window );\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/video-js/video-js.css",
    "content": "/*!\r\nVideo.js Default Styles (http://videojs.com)\r\nVersion 4.3.0\r\nCreate your own skin at http://designer.videojs.com\r\n*/\r\n/* SKIN\r\n================================================================================\r\nThe main class name for all skin-specific styles. To make your own skin,\r\nreplace all occurances of 'vjs-default-skin' with a new name. Then add your new\r\nskin name to your video tag instead of the default skin.\r\ne.g. <video class=\"video-js my-skin-name\">\r\n*/\r\n.vjs-default-skin {\r\n  color: #cccccc;\r\n}\r\n/* Custom Icon Font\r\n--------------------------------------------------------------------------------\r\nThe control icons are from a custom font. Each icon corresponds to a character\r\n(e.g. \"\\e001\"). Font icons allow for easy scaling and coloring of icons.\r\n*/\r\n@font-face {\r\n  font-family: 'VideoJS';\r\n  src: url('font/vjs.eot');\r\n  src: url('font/vjs.eot?#iefix') format('embedded-opentype'), url('font/vjs.woff') format('woff'), url('font/vjs.ttf') format('truetype');\r\n  font-weight: normal;\r\n  font-style: normal;\r\n}\r\n/* Base UI Component Classes\r\n--------------------------------------------------------------------------------\r\n*/\r\n/* Slider - used for Volume bar and Seek bar */\r\n.vjs-default-skin .vjs-slider {\r\n  /* Replace browser focus hightlight with handle highlight */\r\n  outline: 0;\r\n  position: relative;\r\n  cursor: pointer;\r\n  padding: 0;\r\n  /* background-color-with-alpha */\r\n  background-color: #333333;\r\n  background-color: rgba(51, 51, 51, 0.9);\r\n}\r\n.vjs-default-skin .vjs-slider:focus {\r\n  /* box-shadow */\r\n  -webkit-box-shadow: 0 0 2em #ffffff;\r\n  -moz-box-shadow: 0 0 2em #ffffff;\r\n  box-shadow: 0 0 2em #ffffff;\r\n}\r\n.vjs-default-skin .vjs-slider-handle {\r\n  position: absolute;\r\n  /* Needed for IE6 */\r\n  left: 0;\r\n  top: 0;\r\n}\r\n.vjs-default-skin .vjs-slider-handle:before {\r\n  content: \"\\e009\";\r\n  font-family: VideoJS;\r\n  font-size: 1em;\r\n  line-height: 1;\r\n  text-align: center;\r\n  text-shadow: 0em 0em 1em #fff;\r\n  position: absolute;\r\n  top: 0;\r\n  left: 0;\r\n  /* Rotate the square icon to make a diamond */\r\n  /* transform */\r\n  -webkit-transform: rotate(-45deg);\r\n  -moz-transform: rotate(-45deg);\r\n  -ms-transform: rotate(-45deg);\r\n  -o-transform: rotate(-45deg);\r\n  transform: rotate(-45deg);\r\n}\r\n/* Control Bar\r\n--------------------------------------------------------------------------------\r\nThe default control bar that is a container for most of the controls.\r\n*/\r\n.vjs-default-skin .vjs-control-bar {\r\n  /* Start hidden */\r\n  display: none;\r\n  position: absolute;\r\n  /* Place control bar at the bottom of the player box/video.\r\n     If you want more margin below the control bar, add more height. */\r\n  bottom: 0;\r\n  /* Use left/right to stretch to 100% width of player div */\r\n  left: 0;\r\n  right: 0;\r\n  /* Height includes any margin you want above or below control items */\r\n  height: 3.0em;\r\n  /* background-color-with-alpha */\r\n  background-color: #07141e;\r\n  background-color: rgba(7, 20, 30, 0.7);\r\n}\r\n/* Show the control bar only once the video has started playing */\r\n.vjs-default-skin.vjs-has-started .vjs-control-bar {\r\n  display: block;\r\n  /* Visibility needed to make sure things hide in older browsers too. */\r\n\r\n  visibility: visible;\r\n  opacity: 1;\r\n  /* transition */\r\n  -webkit-transition: visibility 0.1s, opacity 0.1s;\r\n  -moz-transition: visibility 0.1s, opacity 0.1s;\r\n  -o-transition: visibility 0.1s, opacity 0.1s;\r\n  transition: visibility 0.1s, opacity 0.1s;\r\n}\r\n/* Hide the control bar when the video is playing and the user is inactive  */\r\n.vjs-default-skin.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar {\r\n  display: block;\r\n  visibility: hidden;\r\n  opacity: 0;\r\n  /* transition */\r\n  -webkit-transition: visibility 1s, opacity 1s;\r\n  -moz-transition: visibility 1s, opacity 1s;\r\n  -o-transition: visibility 1s, opacity 1s;\r\n  transition: visibility 1s, opacity 1s;\r\n}\r\n.vjs-default-skin.vjs-controls-disabled .vjs-control-bar {\r\n  display: none;\r\n}\r\n.vjs-default-skin.vjs-using-native-controls .vjs-control-bar {\r\n  display: none;\r\n}\r\n/* IE8 is flakey with fonts, and you have to change the actual content to force\r\nfonts to show/hide properly.\r\n  - \"\\9\" IE8 hack didn't work for this\r\n  - Found in XP IE8 from http://modern.ie. Does not show up in \"IE8 mode\" in IE9\r\n*/\r\n@media \\0screen {\r\n  .vjs-default-skin.vjs-user-inactive.vjs-playing .vjs-control-bar :before {\r\n    content: \"\";\r\n  }\r\n}\r\n/* General styles for individual controls. */\r\n.vjs-default-skin .vjs-control {\r\n  outline: none;\r\n  position: relative;\r\n  float: left;\r\n  text-align: center;\r\n  margin: 0;\r\n  padding: 0;\r\n  height: 3.0em;\r\n  width: 4em;\r\n}\r\n/* FontAwsome button icons */\r\n.vjs-default-skin .vjs-control:before {\r\n  font-family: VideoJS;\r\n  font-size: 1.5em;\r\n  line-height: 2;\r\n  position: absolute;\r\n  top: 0;\r\n  left: 0;\r\n  width: 100%;\r\n  height: 100%;\r\n  text-align: center;\r\n  text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5);\r\n}\r\n/* Replacement for focus outline */\r\n.vjs-default-skin .vjs-control:focus:before,\r\n.vjs-default-skin .vjs-control:hover:before {\r\n  text-shadow: 0em 0em 1em #ffffff;\r\n}\r\n.vjs-default-skin .vjs-control:focus {\r\n  /*  outline: 0; */\r\n  /* keyboard-only users cannot see the focus on several of the UI elements when\r\n  this is set to 0 */\r\n\r\n}\r\n/* Hide control text visually, but have it available for screenreaders */\r\n.vjs-default-skin .vjs-control-text {\r\n  /* hide-visually */\r\n  border: 0;\r\n  clip: rect(0 0 0 0);\r\n  height: 1px;\r\n  margin: -1px;\r\n  overflow: hidden;\r\n  padding: 0;\r\n  position: absolute;\r\n  width: 1px;\r\n}\r\n/* Play/Pause\r\n--------------------------------------------------------------------------------\r\n*/\r\n.vjs-default-skin .vjs-play-control {\r\n  width: 5em;\r\n  cursor: pointer;\r\n}\r\n.vjs-default-skin .vjs-play-control:before {\r\n  content: \"\\e001\";\r\n}\r\n.vjs-default-skin.vjs-playing .vjs-play-control:before {\r\n  content: \"\\e002\";\r\n}\r\n/* Volume/Mute\r\n-------------------------------------------------------------------------------- */\r\n.vjs-default-skin .vjs-mute-control,\r\n.vjs-default-skin .vjs-volume-menu-button {\r\n  cursor: pointer;\r\n  float: right;\r\n}\r\n.vjs-default-skin .vjs-mute-control:before,\r\n.vjs-default-skin .vjs-volume-menu-button:before {\r\n  content: \"\\e006\";\r\n}\r\n.vjs-default-skin .vjs-mute-control.vjs-vol-0:before,\r\n.vjs-default-skin .vjs-volume-menu-button.vjs-vol-0:before {\r\n  content: \"\\e003\";\r\n}\r\n.vjs-default-skin .vjs-mute-control.vjs-vol-1:before,\r\n.vjs-default-skin .vjs-volume-menu-button.vjs-vol-1:before {\r\n  content: \"\\e004\";\r\n}\r\n.vjs-default-skin .vjs-mute-control.vjs-vol-2:before,\r\n.vjs-default-skin .vjs-volume-menu-button.vjs-vol-2:before {\r\n  content: \"\\e005\";\r\n}\r\n.vjs-default-skin .vjs-volume-control {\r\n  width: 5em;\r\n  float: right;\r\n}\r\n.vjs-default-skin .vjs-volume-bar {\r\n  width: 5em;\r\n  height: 0.6em;\r\n  margin: 1.1em auto 0;\r\n}\r\n.vjs-default-skin .vjs-volume-menu-button .vjs-menu-content {\r\n  height: 2.9em;\r\n}\r\n.vjs-default-skin .vjs-volume-level {\r\n  position: absolute;\r\n  top: 0;\r\n  left: 0;\r\n  height: 0.5em;\r\n  background: #66a8cc url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAP0lEQVQIHWWMAQoAIAgDR/QJ/Ub//04+w7ZICBwcOg5FZi5iBB82AGzixEglJrd4TVK5XUJpskSTEvpdFzX9AB2pGziSQcvAAAAAAElFTkSuQmCC) -50% 0 repeat;\r\n}\r\n.vjs-default-skin .vjs-volume-bar .vjs-volume-handle {\r\n  width: 0.5em;\r\n  height: 0.5em;\r\n}\r\n.vjs-default-skin .vjs-volume-handle:before {\r\n  font-size: 0.9em;\r\n  top: -0.2em;\r\n  left: -0.2em;\r\n  width: 1em;\r\n  height: 1em;\r\n}\r\n.vjs-default-skin .vjs-volume-menu-button .vjs-menu .vjs-menu-content {\r\n  width: 6em;\r\n  left: -4em;\r\n}\r\n/* Progress\r\n--------------------------------------------------------------------------------\r\n*/\r\n.vjs-default-skin .vjs-progress-control {\r\n  position: absolute;\r\n  left: 0;\r\n  right: 0;\r\n  width: auto;\r\n  font-size: 0.3em;\r\n  height: 1em;\r\n  /* Set above the rest of the controls. */\r\n  top: -1em;\r\n  /* Shrink the bar slower than it grows. */\r\n  /* transition */\r\n  -webkit-transition: all 0.4s;\r\n  -moz-transition: all 0.4s;\r\n  -o-transition: all 0.4s;\r\n  transition: all 0.4s;\r\n}\r\n/* On hover, make the progress bar grow to something that's more clickable.\r\n    This simply changes the overall font for the progress bar, and this\r\n    updates both the em-based widths and heights, as wells as the icon font */\r\n.vjs-default-skin:hover .vjs-progress-control {\r\n  font-size: .9em;\r\n  /* Even though we're not changing the top/height, we need to include them in\r\n      the transition so they're handled correctly. */\r\n\r\n  /* transition */\r\n  -webkit-transition: all 0.2s;\r\n  -moz-transition: all 0.2s;\r\n  -o-transition: all 0.2s;\r\n  transition: all 0.2s;\r\n}\r\n/* Box containing play and load progresses. Also acts as seek scrubber. */\r\n.vjs-default-skin .vjs-progress-holder {\r\n  height: 100%;\r\n}\r\n/* Progress Bars */\r\n.vjs-default-skin .vjs-progress-holder .vjs-play-progress,\r\n.vjs-default-skin .vjs-progress-holder .vjs-load-progress {\r\n  position: absolute;\r\n  display: block;\r\n  height: 100%;\r\n  margin: 0;\r\n  padding: 0;\r\n  /* Needed for IE6 */\r\n  left: 0;\r\n  top: 0;\r\n}\r\n.vjs-default-skin .vjs-play-progress {\r\n  /*\r\n    Using a data URI to create the white diagonal lines with a transparent\r\n      background. Surprisingly works in IE8.\r\n      Created using http://www.patternify.com\r\n    Changing the first color value will change the bar color.\r\n    Also using a paralax effect to make the lines move backwards.\r\n      The -50% left position makes that happen.\r\n  */\r\n\r\n  background: #66a8cc url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAP0lEQVQIHWWMAQoAIAgDR/QJ/Ub//04+w7ZICBwcOg5FZi5iBB82AGzixEglJrd4TVK5XUJpskSTEvpdFzX9AB2pGziSQcvAAAAAAElFTkSuQmCC) -50% 0 repeat;\r\n}\r\n.vjs-default-skin .vjs-load-progress {\r\n  background: #646464 /* IE8- Fallback */;\r\n  background: rgba(255, 255, 255, 0.4);\r\n}\r\n.vjs-default-skin .vjs-seek-handle {\r\n  width: 1.5em;\r\n  height: 100%;\r\n}\r\n.vjs-default-skin .vjs-seek-handle:before {\r\n  padding-top: 0.1em /* Minor adjustment */;\r\n}\r\n/* Time Display\r\n--------------------------------------------------------------------------------\r\n*/\r\n.vjs-default-skin .vjs-time-controls {\r\n  font-size: 1em;\r\n  /* Align vertically by making the line height the same as the control bar */\r\n  line-height: 3em;\r\n}\r\n.vjs-default-skin .vjs-current-time {\r\n  float: left;\r\n}\r\n.vjs-default-skin .vjs-duration {\r\n  float: left;\r\n}\r\n/* Remaining time is in the HTML, but not included in default design */\r\n.vjs-default-skin .vjs-remaining-time {\r\n  display: none;\r\n  float: left;\r\n}\r\n.vjs-time-divider {\r\n  float: left;\r\n  line-height: 3em;\r\n}\r\n/* Fullscreen\r\n--------------------------------------------------------------------------------\r\n*/\r\n.vjs-default-skin .vjs-fullscreen-control {\r\n  width: 3.8em;\r\n  cursor: pointer;\r\n  float: right;\r\n}\r\n.vjs-default-skin .vjs-fullscreen-control:before {\r\n  content: \"\\e000\";\r\n}\r\n/* Switch to the exit icon when the player is in fullscreen */\r\n.vjs-default-skin.vjs-fullscreen .vjs-fullscreen-control:before {\r\n  content: \"\\e00b\";\r\n}\r\n/* Big Play Button (play button at start)\r\n--------------------------------------------------------------------------------\r\nPositioning of the play button in the center or other corners can be done more\r\neasily in the skin designer. http://designer.videojs.com/\r\n*/\r\n.vjs-default-skin .vjs-big-play-button {\r\n  left: 0.5em;\r\n  top: 0.5em;\r\n  font-size: 3em;\r\n  display: block;\r\n  z-index: 2;\r\n  position: absolute;\r\n  width: 4em;\r\n  height: 2.6em;\r\n  text-align: center;\r\n  vertical-align: middle;\r\n  cursor: pointer;\r\n  opacity: 1;\r\n  /* Need a slightly gray bg so it can be seen on black backgrounds */\r\n  /* background-color-with-alpha */\r\n  background-color: #07141e;\r\n  background-color: rgba(7, 20, 30, 0.7);\r\n  border: 0.1em solid #3b4249;\r\n  /* border-radius */\r\n  -webkit-border-radius: 0.8em;\r\n  -moz-border-radius: 0.8em;\r\n  border-radius: 0.8em;\r\n  /* box-shadow */\r\n  -webkit-box-shadow: 0px 0px 1em rgba(255, 255, 255, 0.25);\r\n  -moz-box-shadow: 0px 0px 1em rgba(255, 255, 255, 0.25);\r\n  box-shadow: 0px 0px 1em rgba(255, 255, 255, 0.25);\r\n  /* transition */\r\n  -webkit-transition: all 0.4s;\r\n  -moz-transition: all 0.4s;\r\n  -o-transition: all 0.4s;\r\n  transition: all 0.4s;\r\n}\r\n/* Optionally center */\r\n.vjs-default-skin.vjs-big-play-centered .vjs-big-play-button {\r\n  /* Center it horizontally */\r\n  left: 50%;\r\n  margin-left: -2.1em;\r\n  /* Center it vertically */\r\n  top: 50%;\r\n  margin-top: -1.4000000000000001em;\r\n}\r\n/* Hide if controls are disabled */\r\n.vjs-default-skin.vjs-controls-disabled .vjs-big-play-button {\r\n  display: none;\r\n}\r\n/* Hide when video starts playing */\r\n.vjs-default-skin.vjs-has-started .vjs-big-play-button {\r\n  display: none;\r\n}\r\n/* Hide on mobile devices. Remove when we stop using native controls\r\n    by default on mobile  */\r\n.vjs-default-skin.vjs-using-native-controls .vjs-big-play-button {\r\n  display: none;\r\n}\r\n.vjs-default-skin:hover .vjs-big-play-button,\r\n.vjs-default-skin .vjs-big-play-button:focus {\r\n  outline: 0;\r\n  border-color: #fff;\r\n  /* IE8 needs a non-glow hover state */\r\n  background-color: #505050;\r\n  background-color: rgba(50, 50, 50, 0.75);\r\n  /* box-shadow */\r\n  -webkit-box-shadow: 0 0 3em #ffffff;\r\n  -moz-box-shadow: 0 0 3em #ffffff;\r\n  box-shadow: 0 0 3em #ffffff;\r\n  /* transition */\r\n  -webkit-transition: all 0s;\r\n  -moz-transition: all 0s;\r\n  -o-transition: all 0s;\r\n  transition: all 0s;\r\n}\r\n.vjs-default-skin .vjs-big-play-button:before {\r\n  content: \"\\e001\";\r\n  font-family: VideoJS;\r\n  /* In order to center the play icon vertically we need to set the line height\r\n     to the same as the button height */\r\n\r\n  line-height: 2.6em;\r\n  text-shadow: 0.05em 0.05em 0.1em #000;\r\n  text-align: center /* Needed for IE8 */;\r\n  position: absolute;\r\n  left: 0;\r\n  width: 100%;\r\n  height: 100%;\r\n}\r\n/* Loading Spinner\r\n--------------------------------------------------------------------------------\r\n*/\r\n.vjs-loading-spinner {\r\n  display: none;\r\n  position: absolute;\r\n  top: 50%;\r\n  left: 50%;\r\n  font-size: 4em;\r\n  line-height: 1;\r\n  width: 1em;\r\n  height: 1em;\r\n  margin-left: -0.5em;\r\n  margin-top: -0.5em;\r\n  opacity: 0.75;\r\n  /* animation */\r\n  -webkit-animation: spin 1.5s infinite linear;\r\n  -moz-animation: spin 1.5s infinite linear;\r\n  -o-animation: spin 1.5s infinite linear;\r\n  animation: spin 1.5s infinite linear;\r\n}\r\n.vjs-default-skin .vjs-loading-spinner:before {\r\n  content: \"\\e01e\";\r\n  font-family: VideoJS;\r\n  position: absolute;\r\n  top: 0;\r\n  left: 0;\r\n  width: 1em;\r\n  height: 1em;\r\n  text-align: center;\r\n  text-shadow: 0em 0em 0.1em #000;\r\n}\r\n@-moz-keyframes spin {\r\n  0% {\r\n    -moz-transform: rotate(0deg);\r\n  }\r\n  100% {\r\n    -moz-transform: rotate(359deg);\r\n  }\r\n}\r\n@-webkit-keyframes spin {\r\n  0% {\r\n    -webkit-transform: rotate(0deg);\r\n  }\r\n  100% {\r\n    -webkit-transform: rotate(359deg);\r\n  }\r\n}\r\n@-o-keyframes spin {\r\n  0% {\r\n    -o-transform: rotate(0deg);\r\n  }\r\n  100% {\r\n    -o-transform: rotate(359deg);\r\n  }\r\n}\r\n@keyframes spin {\r\n  0% {\r\n    transform: rotate(0deg);\r\n  }\r\n  100% {\r\n    transform: rotate(359deg);\r\n  }\r\n}\r\n/* Menu Buttons (Captions/Subtitles/etc.)\r\n--------------------------------------------------------------------------------\r\n*/\r\n.vjs-default-skin .vjs-menu-button {\r\n  float: right;\r\n  cursor: pointer;\r\n}\r\n.vjs-default-skin .vjs-menu {\r\n  display: none;\r\n  position: absolute;\r\n  bottom: 0;\r\n  left: 0em;\r\n  /* (Width of vjs-menu - width of button) / 2 */\r\n\r\n  width: 0em;\r\n  height: 0em;\r\n  margin-bottom: 3em;\r\n  border-left: 2em solid transparent;\r\n  border-right: 2em solid transparent;\r\n  border-top: 1.55em solid #000000;\r\n  /* Same width top as ul bottom */\r\n\r\n  border-top-color: rgba(7, 40, 50, 0.5);\r\n  /* Same as ul background */\r\n\r\n}\r\n/* Button Pop-up Menu */\r\n.vjs-default-skin .vjs-menu-button .vjs-menu .vjs-menu-content {\r\n  display: block;\r\n  padding: 0;\r\n  margin: 0;\r\n  position: absolute;\r\n  width: 10em;\r\n  bottom: 1.5em;\r\n  /* Same bottom as vjs-menu border-top */\r\n\r\n  max-height: 15em;\r\n  overflow: auto;\r\n  left: -5em;\r\n  /* Width of menu - width of button / 2 */\r\n\r\n  /* background-color-with-alpha */\r\n  background-color: #07141e;\r\n  background-color: rgba(7, 20, 30, 0.7);\r\n  /* box-shadow */\r\n  -webkit-box-shadow: -0.2em -0.2em 0.3em rgba(255, 255, 255, 0.2);\r\n  -moz-box-shadow: -0.2em -0.2em 0.3em rgba(255, 255, 255, 0.2);\r\n  box-shadow: -0.2em -0.2em 0.3em rgba(255, 255, 255, 0.2);\r\n}\r\n.vjs-default-skin .vjs-menu-button:hover .vjs-menu {\r\n  display: block;\r\n}\r\n.vjs-default-skin .vjs-menu-button ul li {\r\n  list-style: none;\r\n  margin: 0;\r\n  padding: 0.3em 0 0.3em 0;\r\n  line-height: 1.4em;\r\n  font-size: 1.2em;\r\n  text-align: center;\r\n  text-transform: lowercase;\r\n}\r\n.vjs-default-skin .vjs-menu-button ul li.vjs-selected {\r\n  background-color: #000;\r\n}\r\n.vjs-default-skin .vjs-menu-button ul li:focus,\r\n.vjs-default-skin .vjs-menu-button ul li:hover,\r\n.vjs-default-skin .vjs-menu-button ul li.vjs-selected:focus,\r\n.vjs-default-skin .vjs-menu-button ul li.vjs-selected:hover {\r\n  outline: 0;\r\n  color: #111;\r\n  /* background-color-with-alpha */\r\n  background-color: #ffffff;\r\n  background-color: rgba(255, 255, 255, 0.75);\r\n  /* box-shadow */\r\n  -webkit-box-shadow: 0 0 1em #ffffff;\r\n  -moz-box-shadow: 0 0 1em #ffffff;\r\n  box-shadow: 0 0 1em #ffffff;\r\n}\r\n.vjs-default-skin .vjs-menu-button ul li.vjs-menu-title {\r\n  text-align: center;\r\n  text-transform: uppercase;\r\n  font-size: 1em;\r\n  line-height: 2em;\r\n  padding: 0;\r\n  margin: 0 0 0.3em 0;\r\n  font-weight: bold;\r\n  cursor: default;\r\n}\r\n/* Subtitles Button */\r\n.vjs-default-skin .vjs-subtitles-button:before {\r\n  content: \"\\e00c\";\r\n}\r\n/* Captions Button */\r\n.vjs-default-skin .vjs-captions-button:before {\r\n  content: \"\\e008\";\r\n}\r\n/* Replacement for focus outline */\r\n.vjs-default-skin .vjs-captions-button:focus .vjs-control-content:before,\r\n.vjs-default-skin .vjs-captions-button:hover .vjs-control-content:before {\r\n  /* box-shadow */\r\n  -webkit-box-shadow: 0 0 1em #ffffff;\r\n  -moz-box-shadow: 0 0 1em #ffffff;\r\n  box-shadow: 0 0 1em #ffffff;\r\n}\r\n/*\r\nREQUIRED STYLES (be careful overriding)\r\n================================================================================\r\nWhen loading the player, the video tag is replaced with a DIV,\r\nthat will hold the video tag or object tag for other playback methods.\r\nThe div contains the video playback element (Flash or HTML5) and controls,\r\nand sets the width and height of the video.\r\n\r\n** If you want to add some kind of border/padding (e.g. a frame), or special\r\npositioning, use another containing element. Otherwise you risk messing up\r\ncontrol positioning and full window mode. **\r\n*/\r\n.video-js {\r\n  background-color: #000;\r\n  position: relative;\r\n  padding: 0;\r\n  /* Start with 10px for base font size so other dimensions can be em based and\r\n     easily calculable. */\r\n\r\n  font-size: 10px;\r\n  /* Allow poster to be vertially aligned. */\r\n\r\n  vertical-align: middle;\r\n  /*  display: table-cell; */\r\n  /*This works in Safari but not Firefox.*/\r\n\r\n  /* Provide some basic defaults for fonts */\r\n\r\n  font-weight: normal;\r\n  font-style: normal;\r\n  /* Avoiding helvetica: issue #376 */\r\n\r\n  font-family: Arial, sans-serif;\r\n  /* Turn off user selection (text highlighting) by default.\r\n     The majority of player components will not be text blocks.\r\n     Text areas will need to turn user selection back on. */\r\n\r\n  /* user-select */\r\n  -webkit-user-select: none;\r\n  -moz-user-select: none;\r\n  -ms-user-select: none;\r\n  user-select: none;\r\n}\r\n/* Playback technology elements expand to the width/height of the containing div\r\n    <video> or <object> */\r\n.video-js .vjs-tech {\r\n  position: absolute;\r\n  top: 0;\r\n  left: 0;\r\n  width: 100%;\r\n  height: 100%;\r\n}\r\n/* Fix for Firefox 9 fullscreen (only if it is enabled). Not needed when\r\n   checking fullScreenEnabled. */\r\n.video-js:-moz-full-screen {\r\n  position: absolute;\r\n}\r\n/* Fullscreen Styles */\r\nbody.vjs-full-window {\r\n  padding: 0;\r\n  margin: 0;\r\n  height: 100%;\r\n  /* Fix for IE6 full-window. http://www.cssplay.co.uk/layouts/fixed.html */\r\n  overflow-y: auto;\r\n}\r\n.video-js.vjs-fullscreen {\r\n  position: fixed;\r\n  overflow: hidden;\r\n  z-index: 1000;\r\n  left: 0;\r\n  top: 0;\r\n  bottom: 0;\r\n  right: 0;\r\n  width: 100% !important;\r\n  height: 100% !important;\r\n  /* IE6 full-window (underscore hack) */\r\n  _position: absolute;\r\n}\r\n.video-js:-webkit-full-screen {\r\n  width: 100% !important;\r\n  height: 100% !important;\r\n}\r\n.video-js.vjs-fullscreen.vjs-user-inactive {\r\n  cursor: none;\r\n}\r\n/* Poster Styles */\r\n.vjs-poster {\r\n  background-repeat: no-repeat;\r\n  background-position: 50% 50%;\r\n  background-size: contain;\r\n  cursor: pointer;\r\n  height: 100%;\r\n  margin: 0;\r\n  padding: 0;\r\n  position: relative;\r\n  width: 100%;\r\n}\r\n.vjs-poster img {\r\n  display: block;\r\n  margin: 0 auto;\r\n  max-height: 100%;\r\n  padding: 0;\r\n  width: 100%;\r\n}\r\n/* Hide the poster when native controls are used otherwise it covers them */\r\n.video-js.vjs-using-native-controls .vjs-poster {\r\n  display: none;\r\n}\r\n/* Text Track Styles */\r\n/* Overall track holder for both captions and subtitles */\r\n.video-js .vjs-text-track-display {\r\n  text-align: center;\r\n  position: absolute;\r\n  bottom: 4em;\r\n  /* Leave padding on left and right */\r\n  left: 1em;\r\n  right: 1em;\r\n}\r\n/* Individual tracks */\r\n.video-js .vjs-text-track {\r\n  display: none;\r\n  font-size: 1.4em;\r\n  text-align: center;\r\n  margin-bottom: 0.1em;\r\n  /* Transparent black background, or fallback to all black (oldIE) */\r\n  /* background-color-with-alpha */\r\n  background-color: #000000;\r\n  background-color: rgba(0, 0, 0, 0.5);\r\n}\r\n.video-js .vjs-subtitles {\r\n  color: #ffffff /* Subtitles are white */;\r\n}\r\n.video-js .vjs-captions {\r\n  color: #ffcc66 /* Captions are yellow */;\r\n}\r\n.vjs-tt-cue {\r\n  display: block;\r\n}\r\n/* Hide disabled or unsupported controls */\r\n.vjs-default-skin .vjs-hidden {\r\n  display: none;\r\n}\r\n.vjs-lock-showing {\r\n  display: block !important;\r\n  opacity: 1;\r\n  visibility: visible;\r\n}\r\n/* -----------------------------------------------------------------------------\r\nThe original source of this file lives at\r\nhttps://github.com/videojs/video.js/blob/master/src/css/video-js.less */\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/video-js/video.dev.js",
    "content": "/**\r\n * @fileoverview Main function src.\r\n */\r\n\r\n// HTML5 Shiv. Must be in <head> to support older browsers.\r\ndocument.createElement('video');\r\ndocument.createElement('audio');\r\ndocument.createElement('track');\r\n\r\n/**\r\n * Doubles as the main function for users to create a player instance and also\r\n * the main library object.\r\n *\r\n * **ALIASES** videojs, _V_ (deprecated)\r\n *\r\n * The `vjs` function can be used to initialize or retrieve a player.\r\n *\r\n *     var myPlayer = vjs('my_video_id');\r\n *\r\n * @param  {String|Element} id      Video element or video element ID\r\n * @param  {Object=} options        Optional options object for config/settings\r\n * @param  {Function=} ready        Optional ready callback\r\n * @return {vjs.Player}             A player instance\r\n * @namespace\r\n */\r\nvar vjs = function(id, options, ready){\r\n  var tag; // Element of ID\r\n\r\n  // Allow for element or ID to be passed in\r\n  // String ID\r\n  if (typeof id === 'string') {\r\n\r\n    // Adjust for jQuery ID syntax\r\n    if (id.indexOf('#') === 0) {\r\n      id = id.slice(1);\r\n    }\r\n\r\n    // If a player instance has already been created for this ID return it.\r\n    if (vjs.players[id]) {\r\n      return vjs.players[id];\r\n\r\n    // Otherwise get element for ID\r\n    } else {\r\n      tag = vjs.el(id);\r\n    }\r\n\r\n  // ID is a media element\r\n  } else {\r\n    tag = id;\r\n  }\r\n\r\n  // Check for a useable element\r\n  if (!tag || !tag.nodeName) { // re: nodeName, could be a box div also\r\n    throw new TypeError('The element or ID supplied is not valid. (videojs)'); // Returns\r\n  }\r\n\r\n  // Element may have a player attr referring to an already created player instance.\r\n  // If not, set up a new player and return the instance.\r\n  return tag['player'] || new vjs.Player(tag, options, ready);\r\n};\r\n\r\n// Extended name, also available externally, window.videojs\r\nvar videojs = vjs;\r\nwindow.videojs = window.vjs = vjs;\r\n\r\n// CDN Version. Used to target right flash swf.\r\nvjs.CDN_VERSION = '4.3';\r\nvjs.ACCESS_PROTOCOL = ('https:' == document.location.protocol ? 'https://' : 'http://');\r\n\r\n/**\r\n * Global Player instance options, surfaced from vjs.Player.prototype.options_\r\n * vjs.options = vjs.Player.prototype.options_\r\n * All options should use string keys so they avoid\r\n * renaming by closure compiler\r\n * @type {Object}\r\n */\r\nvjs.options = {\r\n  // Default order of fallback technology\r\n  'techOrder': ['html5','flash'],\r\n  // techOrder: ['flash','html5'],\r\n\r\n  'html5': {},\r\n  'flash': {},\r\n\r\n  // Default of web browser is 300x150. Should rely on source width/height.\r\n  'width': 300,\r\n  'height': 150,\r\n  // defaultVolume: 0.85,\r\n  'defaultVolume': 0.00, // The freakin seaguls are driving me crazy!\r\n\r\n  // Included control sets\r\n  'children': {\r\n    'mediaLoader': {},\r\n    'posterImage': {},\r\n    'textTrackDisplay': {},\r\n    'loadingSpinner': {},\r\n    'bigPlayButton': {},\r\n    'controlBar': {}\r\n  },\r\n\r\n  // Default message to show when a video cannot be played.\r\n  'notSupportedMessage': 'Sorry, no compatible source and playback ' +\r\n      'technology were found for this video. Try using another browser ' +\r\n      'like <a href=\"http://bit.ly/ccMUEC\">Chrome</a> or download the ' +\r\n      'latest <a href=\"http://adobe.ly/mwfN1\">Adobe Flash Player</a>.'\r\n};\r\n\r\n// Set CDN Version of swf\r\n// The added (+) blocks the replace from changing this 4.3 string\r\nif (vjs.CDN_VERSION !== 'GENERATED'+'_CDN_VSN') {\r\n  videojs.options['flash']['swf'] = vjs.ACCESS_PROTOCOL + 'vjs.zencdn.net/'+vjs.CDN_VERSION+'/video-js.swf';\r\n}\r\n\r\n/**\r\n * Global player list\r\n * @type {Object}\r\n */\r\nvjs.players = {};\r\n/**\r\n * Core Object/Class for objects that use inheritance + contstructors\r\n *\r\n * To create a class that can be subclassed itself, extend the CoreObject class.\r\n *\r\n *     var Animal = CoreObject.extend();\r\n *     var Horse = Animal.extend();\r\n *\r\n * The constructor can be defined through the init property of an object argument.\r\n *\r\n *     var Animal = CoreObject.extend({\r\n *       init: function(name, sound){\r\n *         this.name = name;\r\n *       }\r\n *     });\r\n *\r\n * Other methods and properties can be added the same way, or directly to the\r\n * prototype.\r\n *\r\n *    var Animal = CoreObject.extend({\r\n *       init: function(name){\r\n *         this.name = name;\r\n *       },\r\n *       getName: function(){\r\n *         return this.name;\r\n *       },\r\n *       sound: '...'\r\n *    });\r\n *\r\n *    Animal.prototype.makeSound = function(){\r\n *      alert(this.sound);\r\n *    };\r\n *\r\n * To create an instance of a class, use the create method.\r\n *\r\n *    var fluffy = Animal.create('Fluffy');\r\n *    fluffy.getName(); // -> Fluffy\r\n *\r\n * Methods and properties can be overridden in subclasses.\r\n *\r\n *     var Horse = Animal.extend({\r\n *       sound: 'Neighhhhh!'\r\n *     });\r\n *\r\n *     var horsey = Horse.create('Horsey');\r\n *     horsey.getName(); // -> Horsey\r\n *     horsey.makeSound(); // -> Alert: Neighhhhh!\r\n *\r\n * @class\r\n * @constructor\r\n */\r\nvjs.CoreObject = vjs['CoreObject'] = function(){};\r\n// Manually exporting vjs['CoreObject'] here for Closure Compiler\r\n// because of the use of the extend/create class methods\r\n// If we didn't do this, those functions would get flattend to something like\r\n// `a = ...` and `this.prototype` would refer to the global object instead of\r\n// CoreObject\r\n\r\n/**\r\n * Create a new object that inherits from this Object\r\n *\r\n *     var Animal = CoreObject.extend();\r\n *     var Horse = Animal.extend();\r\n *\r\n * @param {Object} props Functions and properties to be applied to the\r\n *                       new object's prototype\r\n * @return {vjs.CoreObject} An object that inherits from CoreObject\r\n * @this {*}\r\n */\r\nvjs.CoreObject.extend = function(props){\r\n  var init, subObj;\r\n\r\n  props = props || {};\r\n  // Set up the constructor using the supplied init method\r\n  // or using the init of the parent object\r\n  // Make sure to check the unobfuscated version for external libs\r\n  init = props['init'] || props.init || this.prototype['init'] || this.prototype.init || function(){};\r\n  // In Resig's simple class inheritance (previously used) the constructor\r\n  //  is a function that calls `this.init.apply(arguments)`\r\n  // However that would prevent us from using `ParentObject.call(this);`\r\n  //  in a Child constuctor because the `this` in `this.init`\r\n  //  would still refer to the Child and cause an inifinite loop.\r\n  // We would instead have to do\r\n  //    `ParentObject.prototype.init.apply(this, argumnents);`\r\n  //  Bleh. We're not creating a _super() function, so it's good to keep\r\n  //  the parent constructor reference simple.\r\n  subObj = function(){\r\n    init.apply(this, arguments);\r\n  };\r\n\r\n  // Inherit from this object's prototype\r\n  subObj.prototype = vjs.obj.create(this.prototype);\r\n  // Reset the constructor property for subObj otherwise\r\n  // instances of subObj would have the constructor of the parent Object\r\n  subObj.prototype.constructor = subObj;\r\n\r\n  // Make the class extendable\r\n  subObj.extend = vjs.CoreObject.extend;\r\n  // Make a function for creating instances\r\n  subObj.create = vjs.CoreObject.create;\r\n\r\n  // Extend subObj's prototype with functions and other properties from props\r\n  for (var name in props) {\r\n    if (props.hasOwnProperty(name)) {\r\n      subObj.prototype[name] = props[name];\r\n    }\r\n  }\r\n\r\n  return subObj;\r\n};\r\n\r\n/**\r\n * Create a new instace of this Object class\r\n *\r\n *     var myAnimal = Animal.create();\r\n *\r\n * @return {vjs.CoreObject} An instance of a CoreObject subclass\r\n * @this {*}\r\n */\r\nvjs.CoreObject.create = function(){\r\n  // Create a new object that inherits from this object's prototype\r\n  var inst = vjs.obj.create(this.prototype);\r\n\r\n  // Apply this constructor function to the new object\r\n  this.apply(inst, arguments);\r\n\r\n  // Return the new object\r\n  return inst;\r\n};\r\n/**\r\n * @fileoverview Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/)\r\n * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible)\r\n * This should work very similarly to jQuery's events, however it's based off the book version which isn't as\r\n * robust as jquery's, so there's probably some differences.\r\n */\r\n\r\n/**\r\n * Add an event listener to element\r\n * It stores the handler function in a separate cache object\r\n * and adds a generic handler to the element's event,\r\n * along with a unique id (guid) to the element.\r\n * @param  {Element|Object}   elem Element or object to bind listeners to\r\n * @param  {String}   type Type of event to bind to.\r\n * @param  {Function} fn   Event listener.\r\n * @private\r\n */\r\nvjs.on = function(elem, type, fn){\r\n  var data = vjs.getData(elem);\r\n\r\n  // We need a place to store all our handler data\r\n  if (!data.handlers) data.handlers = {};\r\n\r\n  if (!data.handlers[type]) data.handlers[type] = [];\r\n\r\n  if (!fn.guid) fn.guid = vjs.guid++;\r\n\r\n  data.handlers[type].push(fn);\r\n\r\n  if (!data.dispatcher) {\r\n    data.disabled = false;\r\n\r\n    data.dispatcher = function (event){\r\n\r\n      if (data.disabled) return;\r\n      event = vjs.fixEvent(event);\r\n\r\n      var handlers = data.handlers[event.type];\r\n\r\n      if (handlers) {\r\n        // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off.\r\n        var handlersCopy = handlers.slice(0);\r\n\r\n        for (var m = 0, n = handlersCopy.length; m < n; m++) {\r\n          if (event.isImmediatePropagationStopped()) {\r\n            break;\r\n          } else {\r\n            handlersCopy[m].call(elem, event);\r\n          }\r\n        }\r\n      }\r\n    };\r\n  }\r\n\r\n  if (data.handlers[type].length == 1) {\r\n    if (document.addEventListener) {\r\n      elem.addEventListener(type, data.dispatcher, false);\r\n    } else if (document.attachEvent) {\r\n      elem.attachEvent('on' + type, data.dispatcher);\r\n    }\r\n  }\r\n};\r\n\r\n/**\r\n * Removes event listeners from an element\r\n * @param  {Element|Object}   elem Object to remove listeners from\r\n * @param  {String=}   type Type of listener to remove. Don't include to remove all events from element.\r\n * @param  {Function} fn   Specific listener to remove. Don't incldue to remove listeners for an event type.\r\n * @private\r\n */\r\nvjs.off = function(elem, type, fn) {\r\n  // Don't want to add a cache object through getData if not needed\r\n  if (!vjs.hasData(elem)) return;\r\n\r\n  var data = vjs.getData(elem);\r\n\r\n  // If no events exist, nothing to unbind\r\n  if (!data.handlers) { return; }\r\n\r\n  // Utility function\r\n  var removeType = function(t){\r\n     data.handlers[t] = [];\r\n     vjs.cleanUpEvents(elem,t);\r\n  };\r\n\r\n  // Are we removing all bound events?\r\n  if (!type) {\r\n    for (var t in data.handlers) removeType(t);\r\n    return;\r\n  }\r\n\r\n  var handlers = data.handlers[type];\r\n\r\n  // If no handlers exist, nothing to unbind\r\n  if (!handlers) return;\r\n\r\n  // If no listener was provided, remove all listeners for type\r\n  if (!fn) {\r\n    removeType(type);\r\n    return;\r\n  }\r\n\r\n  // We're only removing a single handler\r\n  if (fn.guid) {\r\n    for (var n = 0; n < handlers.length; n++) {\r\n      if (handlers[n].guid === fn.guid) {\r\n        handlers.splice(n--, 1);\r\n      }\r\n    }\r\n  }\r\n\r\n  vjs.cleanUpEvents(elem, type);\r\n};\r\n\r\n/**\r\n * Clean up the listener cache and dispatchers\r\n * @param  {Element|Object} elem Element to clean up\r\n * @param  {String} type Type of event to clean up\r\n * @private\r\n */\r\nvjs.cleanUpEvents = function(elem, type) {\r\n  var data = vjs.getData(elem);\r\n\r\n  // Remove the events of a particular type if there are none left\r\n  if (data.handlers[type].length === 0) {\r\n    delete data.handlers[type];\r\n    // data.handlers[type] = null;\r\n    // Setting to null was causing an error with data.handlers\r\n\r\n    // Remove the meta-handler from the element\r\n    if (document.removeEventListener) {\r\n      elem.removeEventListener(type, data.dispatcher, false);\r\n    } else if (document.detachEvent) {\r\n      elem.detachEvent('on' + type, data.dispatcher);\r\n    }\r\n  }\r\n\r\n  // Remove the events object if there are no types left\r\n  if (vjs.isEmpty(data.handlers)) {\r\n    delete data.handlers;\r\n    delete data.dispatcher;\r\n    delete data.disabled;\r\n\r\n    // data.handlers = null;\r\n    // data.dispatcher = null;\r\n    // data.disabled = null;\r\n  }\r\n\r\n  // Finally remove the expando if there is no data left\r\n  if (vjs.isEmpty(data)) {\r\n    vjs.removeData(elem);\r\n  }\r\n};\r\n\r\n/**\r\n * Fix a native event to have standard property values\r\n * @param  {Object} event Event object to fix\r\n * @return {Object}\r\n * @private\r\n */\r\nvjs.fixEvent = function(event) {\r\n\r\n  function returnTrue() { return true; }\r\n  function returnFalse() { return false; }\r\n\r\n  // Test if fixing up is needed\r\n  // Used to check if !event.stopPropagation instead of isPropagationStopped\r\n  // But native events return true for stopPropagation, but don't have\r\n  // other expected methods like isPropagationStopped. Seems to be a problem\r\n  // with the Javascript Ninja code. So we're just overriding all events now.\r\n  if (!event || !event.isPropagationStopped) {\r\n    var old = event || window.event;\r\n\r\n    event = {};\r\n    // Clone the old object so that we can modify the values event = {};\r\n    // IE8 Doesn't like when you mess with native event properties\r\n    // Firefox returns false for event.hasOwnProperty('type') and other props\r\n    //  which makes copying more difficult.\r\n    // TODO: Probably best to create a whitelist of event props\r\n    for (var key in old) {\r\n      // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y\r\n      if (key !== 'layerX' && key !== 'layerY') {\r\n        event[key] = old[key];\r\n      }\r\n    }\r\n\r\n    // The event occurred on this element\r\n    if (!event.target) {\r\n      event.target = event.srcElement || document;\r\n    }\r\n\r\n    // Handle which other element the event is related to\r\n    event.relatedTarget = event.fromElement === event.target ?\r\n      event.toElement :\r\n      event.fromElement;\r\n\r\n    // Stop the default browser action\r\n    event.preventDefault = function () {\r\n      if (old.preventDefault) {\r\n        old.preventDefault();\r\n      }\r\n      event.returnValue = false;\r\n      event.isDefaultPrevented = returnTrue;\r\n    };\r\n\r\n    event.isDefaultPrevented = returnFalse;\r\n\r\n    // Stop the event from bubbling\r\n    event.stopPropagation = function () {\r\n      if (old.stopPropagation) {\r\n        old.stopPropagation();\r\n      }\r\n      event.cancelBubble = true;\r\n      event.isPropagationStopped = returnTrue;\r\n    };\r\n\r\n    event.isPropagationStopped = returnFalse;\r\n\r\n    // Stop the event from bubbling and executing other handlers\r\n    event.stopImmediatePropagation = function () {\r\n      if (old.stopImmediatePropagation) {\r\n        old.stopImmediatePropagation();\r\n      }\r\n      event.isImmediatePropagationStopped = returnTrue;\r\n      event.stopPropagation();\r\n    };\r\n\r\n    event.isImmediatePropagationStopped = returnFalse;\r\n\r\n    // Handle mouse position\r\n    if (event.clientX != null) {\r\n      var doc = document.documentElement, body = document.body;\r\n\r\n      event.pageX = event.clientX +\r\n        (doc && doc.scrollLeft || body && body.scrollLeft || 0) -\r\n        (doc && doc.clientLeft || body && body.clientLeft || 0);\r\n      event.pageY = event.clientY +\r\n        (doc && doc.scrollTop || body && body.scrollTop || 0) -\r\n        (doc && doc.clientTop || body && body.clientTop || 0);\r\n    }\r\n\r\n    // Handle key presses\r\n    event.which = event.charCode || event.keyCode;\r\n\r\n    // Fix button for mouse clicks:\r\n    // 0 == left; 1 == middle; 2 == right\r\n    if (event.button != null) {\r\n      event.button = (event.button & 1 ? 0 :\r\n        (event.button & 4 ? 1 :\r\n          (event.button & 2 ? 2 : 0)));\r\n    }\r\n  }\r\n\r\n  // Returns fixed-up instance\r\n  return event;\r\n};\r\n\r\n/**\r\n * Trigger an event for an element\r\n * @param  {Element|Object} elem  Element to trigger an event on\r\n * @param  {String} event Type of event to trigger\r\n * @private\r\n */\r\nvjs.trigger = function(elem, event) {\r\n  // Fetches element data and a reference to the parent (for bubbling).\r\n  // Don't want to add a data object to cache for every parent,\r\n  // so checking hasData first.\r\n  var elemData = (vjs.hasData(elem)) ? vjs.getData(elem) : {};\r\n  var parent = elem.parentNode || elem.ownerDocument;\r\n      // type = event.type || event,\r\n      // handler;\r\n\r\n  // If an event name was passed as a string, creates an event out of it\r\n  if (typeof event === 'string') {\r\n    event = { type:event, target:elem };\r\n  }\r\n  // Normalizes the event properties.\r\n  event = vjs.fixEvent(event);\r\n\r\n  // If the passed element has a dispatcher, executes the established handlers.\r\n  if (elemData.dispatcher) {\r\n    elemData.dispatcher.call(elem, event);\r\n  }\r\n\r\n  // Unless explicitly stopped or the event does not bubble (e.g. media events)\r\n    // recursively calls this function to bubble the event up the DOM.\r\n    if (parent && !event.isPropagationStopped() && event.bubbles !== false) {\r\n    vjs.trigger(parent, event);\r\n\r\n  // If at the top of the DOM, triggers the default action unless disabled.\r\n  } else if (!parent && !event.isDefaultPrevented()) {\r\n    var targetData = vjs.getData(event.target);\r\n\r\n    // Checks if the target has a default action for this event.\r\n    if (event.target[event.type]) {\r\n      // Temporarily disables event dispatching on the target as we have already executed the handler.\r\n      targetData.disabled = true;\r\n      // Executes the default action.\r\n      if (typeof event.target[event.type] === 'function') {\r\n        event.target[event.type]();\r\n      }\r\n      // Re-enables event dispatching.\r\n      targetData.disabled = false;\r\n    }\r\n  }\r\n\r\n  // Inform the triggerer if the default was prevented by returning false\r\n  return !event.isDefaultPrevented();\r\n  /* Original version of js ninja events wasn't complete.\r\n   * We've since updated to the latest version, but keeping this around\r\n   * for now just in case.\r\n   */\r\n  // // Added in attion to book. Book code was broke.\r\n  // event = typeof event === 'object' ?\r\n  //   event[vjs.expando] ?\r\n  //     event :\r\n  //     new vjs.Event(type, event) :\r\n  //   new vjs.Event(type);\r\n\r\n  // event.type = type;\r\n  // if (handler) {\r\n  //   handler.call(elem, event);\r\n  // }\r\n\r\n  // // Clean up the event in case it is being reused\r\n  // event.result = undefined;\r\n  // event.target = elem;\r\n};\r\n\r\n/**\r\n * Trigger a listener only once for an event\r\n * @param  {Element|Object}   elem Element or object to\r\n * @param  {String}   type\r\n * @param  {Function} fn\r\n * @private\r\n */\r\nvjs.one = function(elem, type, fn) {\r\n  var func = function(){\r\n    vjs.off(elem, type, func);\r\n    fn.apply(this, arguments);\r\n  };\r\n  func.guid = fn.guid = fn.guid || vjs.guid++;\r\n  vjs.on(elem, type, func);\r\n};\r\nvar hasOwnProp = Object.prototype.hasOwnProperty;\r\n\r\n/**\r\n * Creates an element and applies properties.\r\n * @param  {String=} tagName    Name of tag to be created.\r\n * @param  {Object=} properties Element properties to be applied.\r\n * @return {Element}\r\n * @private\r\n */\r\nvjs.createEl = function(tagName, properties){\r\n  var el, propName;\r\n\r\n  el = document.createElement(tagName || 'div');\r\n\r\n  for (propName in properties){\r\n    if (hasOwnProp.call(properties, propName)) {\r\n      //el[propName] = properties[propName];\r\n      // Not remembering why we were checking for dash\r\n      // but using setAttribute means you have to use getAttribute\r\n\r\n      // The check for dash checks for the aria-* attributes, like aria-label, aria-valuemin.\r\n      // The additional check for \"role\" is because the default method for adding attributes does not\r\n      // add the attribute \"role\". My guess is because it's not a valid attribute in some namespaces, although\r\n      // browsers handle the attribute just fine. The W3C allows for aria-* attributes to be used in pre-HTML5 docs.\r\n      // http://www.w3.org/TR/wai-aria-primer/#ariahtml. Using setAttribute gets around this problem.\r\n\r\n       if (propName.indexOf('aria-') !== -1 || propName=='role') {\r\n         el.setAttribute(propName, properties[propName]);\r\n       } else {\r\n         el[propName] = properties[propName];\r\n       }\r\n    }\r\n  }\r\n  return el;\r\n};\r\n\r\n/**\r\n * Uppercase the first letter of a string\r\n * @param  {String} string String to be uppercased\r\n * @return {String}\r\n * @private\r\n */\r\nvjs.capitalize = function(string){\r\n  return string.charAt(0).toUpperCase() + string.slice(1);\r\n};\r\n\r\n/**\r\n * Object functions container\r\n * @type {Object}\r\n * @private\r\n */\r\nvjs.obj = {};\r\n\r\n/**\r\n * Object.create shim for prototypal inheritance\r\n *\r\n * https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/create\r\n *\r\n * @function\r\n * @param  {Object}   obj Object to use as prototype\r\n * @private\r\n */\r\n vjs.obj.create = Object.create || function(obj){\r\n  //Create a new function called 'F' which is just an empty object.\r\n  function F() {}\r\n\r\n  //the prototype of the 'F' function should point to the\r\n  //parameter of the anonymous function.\r\n  F.prototype = obj;\r\n\r\n  //create a new constructor function based off of the 'F' function.\r\n  return new F();\r\n};\r\n\r\n/**\r\n * Loop through each property in an object and call a function\r\n * whose arguments are (key,value)\r\n * @param  {Object}   obj Object of properties\r\n * @param  {Function} fn  Function to be called on each property.\r\n * @this {*}\r\n * @private\r\n */\r\nvjs.obj.each = function(obj, fn, context){\r\n  for (var key in obj) {\r\n    if (hasOwnProp.call(obj, key)) {\r\n      fn.call(context || this, key, obj[key]);\r\n    }\r\n  }\r\n};\r\n\r\n/**\r\n * Merge two objects together and return the original.\r\n * @param  {Object} obj1\r\n * @param  {Object} obj2\r\n * @return {Object}\r\n * @private\r\n */\r\nvjs.obj.merge = function(obj1, obj2){\r\n  if (!obj2) { return obj1; }\r\n  for (var key in obj2){\r\n    if (hasOwnProp.call(obj2, key)) {\r\n      obj1[key] = obj2[key];\r\n    }\r\n  }\r\n  return obj1;\r\n};\r\n\r\n/**\r\n * Merge two objects, and merge any properties that are objects\r\n * instead of just overwriting one. Uses to merge options hashes\r\n * where deeper default settings are important.\r\n * @param  {Object} obj1 Object to override\r\n * @param  {Object} obj2 Overriding object\r\n * @return {Object}      New object. Obj1 and Obj2 will be untouched.\r\n * @private\r\n */\r\nvjs.obj.deepMerge = function(obj1, obj2){\r\n  var key, val1, val2;\r\n\r\n  // make a copy of obj1 so we're not ovewriting original values.\r\n  // like prototype.options_ and all sub options objects\r\n  obj1 = vjs.obj.copy(obj1);\r\n\r\n  for (key in obj2){\r\n    if (hasOwnProp.call(obj2, key)) {\r\n      val1 = obj1[key];\r\n      val2 = obj2[key];\r\n\r\n      // Check if both properties are pure objects and do a deep merge if so\r\n      if (vjs.obj.isPlain(val1) && vjs.obj.isPlain(val2)) {\r\n        obj1[key] = vjs.obj.deepMerge(val1, val2);\r\n      } else {\r\n        obj1[key] = obj2[key];\r\n      }\r\n    }\r\n  }\r\n  return obj1;\r\n};\r\n\r\n/**\r\n * Make a copy of the supplied object\r\n * @param  {Object} obj Object to copy\r\n * @return {Object}     Copy of object\r\n * @private\r\n */\r\nvjs.obj.copy = function(obj){\r\n  return vjs.obj.merge({}, obj);\r\n};\r\n\r\n/**\r\n * Check if an object is plain, and not a dom node or any object sub-instance\r\n * @param  {Object} obj Object to check\r\n * @return {Boolean}     True if plain, false otherwise\r\n * @private\r\n */\r\nvjs.obj.isPlain = function(obj){\r\n  return !!obj\r\n    && typeof obj === 'object'\r\n    && obj.toString() === '[object Object]'\r\n    && obj.constructor === Object;\r\n};\r\n\r\n/**\r\n * Bind (a.k.a proxy or Context). A simple method for changing the context of a function\r\n   It also stores a unique id on the function so it can be easily removed from events\r\n * @param  {*}   context The object to bind as scope\r\n * @param  {Function} fn      The function to be bound to a scope\r\n * @param  {Number=}   uid     An optional unique ID for the function to be set\r\n * @return {Function}\r\n * @private\r\n */\r\nvjs.bind = function(context, fn, uid) {\r\n  // Make sure the function has a unique ID\r\n  if (!fn.guid) { fn.guid = vjs.guid++; }\r\n\r\n  // Create the new function that changes the context\r\n  var ret = function() {\r\n    return fn.apply(context, arguments);\r\n  };\r\n\r\n  // Allow for the ability to individualize this function\r\n  // Needed in the case where multiple objects might share the same prototype\r\n  // IF both items add an event listener with the same function, then you try to remove just one\r\n  // it will remove both because they both have the same guid.\r\n  // when using this, you need to use the bind method when you remove the listener as well.\r\n  // currently used in text tracks\r\n  ret.guid = (uid) ? uid + '_' + fn.guid : fn.guid;\r\n\r\n  return ret;\r\n};\r\n\r\n/**\r\n * Element Data Store. Allows for binding data to an element without putting it directly on the element.\r\n * Ex. Event listneres are stored here.\r\n * (also from jsninja.com, slightly modified and updated for closure compiler)\r\n * @type {Object}\r\n * @private\r\n */\r\nvjs.cache = {};\r\n\r\n/**\r\n * Unique ID for an element or function\r\n * @type {Number}\r\n * @private\r\n */\r\nvjs.guid = 1;\r\n\r\n/**\r\n * Unique attribute name to store an element's guid in\r\n * @type {String}\r\n * @constant\r\n * @private\r\n */\r\nvjs.expando = 'vdata' + (new Date()).getTime();\r\n\r\n/**\r\n * Returns the cache object where data for an element is stored\r\n * @param  {Element} el Element to store data for.\r\n * @return {Object}\r\n * @private\r\n */\r\nvjs.getData = function(el){\r\n  var id = el[vjs.expando];\r\n  if (!id) {\r\n    id = el[vjs.expando] = vjs.guid++;\r\n    vjs.cache[id] = {};\r\n  }\r\n  return vjs.cache[id];\r\n};\r\n\r\n/**\r\n * Returns the cache object where data for an element is stored\r\n * @param  {Element} el Element to store data for.\r\n * @return {Object}\r\n * @private\r\n */\r\nvjs.hasData = function(el){\r\n  var id = el[vjs.expando];\r\n  return !(!id || vjs.isEmpty(vjs.cache[id]));\r\n};\r\n\r\n/**\r\n * Delete data for the element from the cache and the guid attr from getElementById\r\n * @param  {Element} el Remove data for an element\r\n * @private\r\n */\r\nvjs.removeData = function(el){\r\n  var id = el[vjs.expando];\r\n  if (!id) { return; }\r\n  // Remove all stored data\r\n  // Changed to = null\r\n  // http://coding.smashingmagazine.com/2012/11/05/writing-fast-memory-efficient-javascript/\r\n  // vjs.cache[id] = null;\r\n  delete vjs.cache[id];\r\n\r\n  // Remove the expando property from the DOM node\r\n  try {\r\n    delete el[vjs.expando];\r\n  } catch(e) {\r\n    if (el.removeAttribute) {\r\n      el.removeAttribute(vjs.expando);\r\n    } else {\r\n      // IE doesn't appear to support removeAttribute on the document element\r\n      el[vjs.expando] = null;\r\n    }\r\n  }\r\n};\r\n\r\n/**\r\n * Check if an object is empty\r\n * @param  {Object}  obj The object to check for emptiness\r\n * @return {Boolean}\r\n * @private\r\n */\r\nvjs.isEmpty = function(obj) {\r\n  for (var prop in obj) {\r\n    // Inlude null properties as empty.\r\n    if (obj[prop] !== null) {\r\n      return false;\r\n    }\r\n  }\r\n  return true;\r\n};\r\n\r\n/**\r\n * Add a CSS class name to an element\r\n * @param {Element} element    Element to add class name to\r\n * @param {String} classToAdd Classname to add\r\n * @private\r\n */\r\nvjs.addClass = function(element, classToAdd){\r\n  if ((' '+element.className+' ').indexOf(' '+classToAdd+' ') == -1) {\r\n    element.className = element.className === '' ? classToAdd : element.className + ' ' + classToAdd;\r\n  }\r\n};\r\n\r\n/**\r\n * Remove a CSS class name from an element\r\n * @param {Element} element    Element to remove from class name\r\n * @param {String} classToAdd Classname to remove\r\n * @private\r\n */\r\nvjs.removeClass = function(element, classToRemove){\r\n  var classNames, i;\r\n\r\n  if (element.className.indexOf(classToRemove) == -1) { return; }\r\n\r\n  classNames = element.className.split(' ');\r\n\r\n  // no arr.indexOf in ie8, and we don't want to add a big shim\r\n  for (i = classNames.length - 1; i >= 0; i--) {\r\n    if (classNames[i] === classToRemove) {\r\n      classNames.splice(i,1);\r\n    }\r\n  }\r\n\r\n  element.className = classNames.join(' ');\r\n};\r\n\r\n/**\r\n * Element for testing browser HTML5 video capabilities\r\n * @type {Element}\r\n * @constant\r\n * @private\r\n */\r\nvjs.TEST_VID = vjs.createEl('video');\r\n\r\n/**\r\n * Useragent for browser testing.\r\n * @type {String}\r\n * @constant\r\n * @private\r\n */\r\nvjs.USER_AGENT = navigator.userAgent;\r\n\r\n/**\r\n * Device is an iPhone\r\n * @type {Boolean}\r\n * @constant\r\n * @private\r\n */\r\nvjs.IS_IPHONE = (/iPhone/i).test(vjs.USER_AGENT);\r\nvjs.IS_IPAD = (/iPad/i).test(vjs.USER_AGENT);\r\nvjs.IS_IPOD = (/iPod/i).test(vjs.USER_AGENT);\r\nvjs.IS_IOS = vjs.IS_IPHONE || vjs.IS_IPAD || vjs.IS_IPOD;\r\n\r\nvjs.IOS_VERSION = (function(){\r\n  var match = vjs.USER_AGENT.match(/OS (\\d+)_/i);\r\n  if (match && match[1]) { return match[1]; }\r\n})();\r\n\r\nvjs.IS_ANDROID = (/Android/i).test(vjs.USER_AGENT);\r\nvjs.ANDROID_VERSION = (function() {\r\n  // This matches Android Major.Minor.Patch versions\r\n  // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned\r\n  var match = vjs.USER_AGENT.match(/Android (\\d+)(?:\\.(\\d+))?(?:\\.(\\d+))*/i),\r\n    major,\r\n    minor;\r\n\r\n  if (!match) {\r\n    return null;\r\n  }\r\n\r\n  major = match[1] && parseFloat(match[1]);\r\n  minor = match[2] && parseFloat(match[2]);\r\n\r\n  if (major && minor) {\r\n    return parseFloat(match[1] + '.' + match[2]);\r\n  } else if (major) {\r\n    return major;\r\n  } else {\r\n    return null;\r\n  }\r\n})();\r\n// Old Android is defined as Version older than 2.3, and requiring a webkit version of the android browser\r\nvjs.IS_OLD_ANDROID = vjs.IS_ANDROID && (/webkit/i).test(vjs.USER_AGENT) && vjs.ANDROID_VERSION < 2.3;\r\n\r\nvjs.IS_FIREFOX = (/Firefox/i).test(vjs.USER_AGENT);\r\nvjs.IS_CHROME = (/Chrome/i).test(vjs.USER_AGENT);\r\n\r\nvjs.TOUCH_ENABLED = !!(('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch);\r\n\r\n/**\r\n * Get an element's attribute values, as defined on the HTML tag\r\n * Attributs are not the same as properties. They're defined on the tag\r\n * or with setAttribute (which shouldn't be used with HTML)\r\n * This will return true or false for boolean attributes.\r\n * @param  {Element} tag Element from which to get tag attributes\r\n * @return {Object}\r\n * @private\r\n */\r\nvjs.getAttributeValues = function(tag){\r\n  var obj, knownBooleans, attrs, attrName, attrVal;\r\n\r\n  obj = {};\r\n\r\n  // known boolean attributes\r\n  // we can check for matching boolean properties, but older browsers\r\n  // won't know about HTML5 boolean attributes that we still read from\r\n  knownBooleans = ','+'autoplay,controls,loop,muted,default'+',';\r\n\r\n  if (tag && tag.attributes && tag.attributes.length > 0) {\r\n    attrs = tag.attributes;\r\n\r\n    for (var i = attrs.length - 1; i >= 0; i--) {\r\n      attrName = attrs[i].name;\r\n      attrVal = attrs[i].value;\r\n\r\n      // check for known booleans\r\n      // the matching element property will return a value for typeof\r\n      if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(','+attrName+',') !== -1) {\r\n        // the value of an included boolean attribute is typically an empty\r\n        // string ('') which would equal false if we just check for a false value.\r\n        // we also don't want support bad code like autoplay='false'\r\n        attrVal = (attrVal !== null) ? true : false;\r\n      }\r\n\r\n      obj[attrName] = attrVal;\r\n    }\r\n  }\r\n\r\n  return obj;\r\n};\r\n\r\n/**\r\n * Get the computed style value for an element\r\n * From http://robertnyman.com/2006/04/24/get-the-rendered-style-of-an-element/\r\n * @param  {Element} el        Element to get style value for\r\n * @param  {String} strCssRule Style name\r\n * @return {String}            Style value\r\n * @private\r\n */\r\nvjs.getComputedDimension = function(el, strCssRule){\r\n  var strValue = '';\r\n  if(document.defaultView && document.defaultView.getComputedStyle){\r\n    strValue = document.defaultView.getComputedStyle(el, '').getPropertyValue(strCssRule);\r\n\r\n  } else if(el.currentStyle){\r\n    // IE8 Width/Height support\r\n    strValue = el['client'+strCssRule.substr(0,1).toUpperCase() + strCssRule.substr(1)] + 'px';\r\n  }\r\n  return strValue;\r\n};\r\n\r\n/**\r\n * Insert an element as the first child node of another\r\n * @param  {Element} child   Element to insert\r\n * @param  {[type]} parent Element to insert child into\r\n * @private\r\n */\r\nvjs.insertFirst = function(child, parent){\r\n  if (parent.firstChild) {\r\n    parent.insertBefore(child, parent.firstChild);\r\n  } else {\r\n    parent.appendChild(child);\r\n  }\r\n};\r\n\r\n/**\r\n * Object to hold browser support information\r\n * @type {Object}\r\n * @private\r\n */\r\nvjs.support = {};\r\n\r\n/**\r\n * Shorthand for document.getElementById()\r\n * Also allows for CSS (jQuery) ID syntax. But nothing other than IDs.\r\n * @param  {String} id  Element ID\r\n * @return {Element}    Element with supplied ID\r\n * @private\r\n */\r\nvjs.el = function(id){\r\n  if (id.indexOf('#') === 0) {\r\n    id = id.slice(1);\r\n  }\r\n\r\n  return document.getElementById(id);\r\n};\r\n\r\n/**\r\n * Format seconds as a time string, H:MM:SS or M:SS\r\n * Supplying a guide (in seconds) will force a number of leading zeros\r\n * to cover the length of the guide\r\n * @param  {Number} seconds Number of seconds to be turned into a string\r\n * @param  {Number} guide   Number (in seconds) to model the string after\r\n * @return {String}         Time formatted as H:MM:SS or M:SS\r\n * @private\r\n */\r\nvjs.formatTime = function(seconds, guide) {\r\n  // Default to using seconds as guide\r\n  guide = guide || seconds;\r\n  var s = Math.floor(seconds % 60),\r\n      m = Math.floor(seconds / 60 % 60),\r\n      h = Math.floor(seconds / 3600),\r\n      gm = Math.floor(guide / 60 % 60),\r\n      gh = Math.floor(guide / 3600);\r\n\r\n  // handle invalid times\r\n  if (isNaN(seconds) || seconds === Infinity) {\r\n    // '-' is false for all relational operators (e.g. <, >=) so this setting\r\n    // will add the minimum number of fields specified by the guide\r\n    h = m = s = '-';\r\n  }\r\n\r\n  // Check if we need to show hours\r\n  h = (h > 0 || gh > 0) ? h + ':' : '';\r\n\r\n  // If hours are showing, we may need to add a leading zero.\r\n  // Always show at least one digit of minutes.\r\n  m = (((h || gm >= 10) && m < 10) ? '0' + m : m) + ':';\r\n\r\n  // Check if leading zero is need for seconds\r\n  s = (s < 10) ? '0' + s : s;\r\n\r\n  return h + m + s;\r\n};\r\n\r\n// Attempt to block the ability to select text while dragging controls\r\nvjs.blockTextSelection = function(){\r\n  document.body.focus();\r\n  document.onselectstart = function () { return false; };\r\n};\r\n// Turn off text selection blocking\r\nvjs.unblockTextSelection = function(){ document.onselectstart = function () { return true; }; };\r\n\r\n/**\r\n * Trim whitespace from the ends of a string.\r\n * @param  {String} string String to trim\r\n * @return {String}        Trimmed string\r\n * @private\r\n */\r\nvjs.trim = function(str){\r\n  return (str+'').replace(/^\\s+|\\s+$/g, '');\r\n};\r\n\r\n/**\r\n * Should round off a number to a decimal place\r\n * @param  {Number} num Number to round\r\n * @param  {Number} dec Number of decimal places to round to\r\n * @return {Number}     Rounded number\r\n * @private\r\n */\r\nvjs.round = function(num, dec) {\r\n  if (!dec) { dec = 0; }\r\n  return Math.round(num*Math.pow(10,dec))/Math.pow(10,dec);\r\n};\r\n\r\n/**\r\n * Should create a fake TimeRange object\r\n * Mimics an HTML5 time range instance, which has functions that\r\n * return the start and end times for a range\r\n * TimeRanges are returned by the buffered() method\r\n * @param  {Number} start Start time in seconds\r\n * @param  {Number} end   End time in seconds\r\n * @return {Object}       Fake TimeRange object\r\n * @private\r\n */\r\nvjs.createTimeRange = function(start, end){\r\n  return {\r\n    length: 1,\r\n    start: function() { return start; },\r\n    end: function() { return end; }\r\n  };\r\n};\r\n\r\n/**\r\n * Simple http request for retrieving external files (e.g. text tracks)\r\n * @param  {String} url           URL of resource\r\n * @param  {Function=} onSuccess  Success callback\r\n * @param  {Function=} onError    Error callback\r\n * @private\r\n */\r\nvjs.get = function(url, onSuccess, onError){\r\n  var local, request;\r\n\r\n  if (typeof XMLHttpRequest === 'undefined') {\r\n    window.XMLHttpRequest = function () {\r\n      try { return new window.ActiveXObject('Msxml2.XMLHTTP.6.0'); } catch (e) {}\r\n      try { return new window.ActiveXObject('Msxml2.XMLHTTP.3.0'); } catch (f) {}\r\n      try { return new window.ActiveXObject('Msxml2.XMLHTTP'); } catch (g) {}\r\n      throw new Error('This browser does not support XMLHttpRequest.');\r\n    };\r\n  }\r\n\r\n  request = new XMLHttpRequest();\r\n  try {\r\n    request.open('GET', url);\r\n  } catch(e) {\r\n    onError(e);\r\n  }\r\n\r\n  local = (url.indexOf('file:') === 0 || (window.location.href.indexOf('file:') === 0 && url.indexOf('http') === -1));\r\n\r\n  request.onreadystatechange = function() {\r\n    if (request.readyState === 4) {\r\n      if (request.status === 200 || local && request.status === 0) {\r\n        onSuccess(request.responseText);\r\n      } else {\r\n        if (onError) {\r\n          onError();\r\n        }\r\n      }\r\n    }\r\n  };\r\n\r\n  try {\r\n    request.send();\r\n  } catch(e) {\r\n    if (onError) {\r\n      onError(e);\r\n    }\r\n  }\r\n};\r\n\r\n/**\r\n * Add to local storage (may removeable)\r\n * @private\r\n */\r\nvjs.setLocalStorage = function(key, value){\r\n  try {\r\n    // IE was throwing errors referencing the var anywhere without this\r\n    var localStorage = window.localStorage || false;\r\n    if (!localStorage) { return; }\r\n    localStorage[key] = value;\r\n  } catch(e) {\r\n    if (e.code == 22 || e.code == 1014) { // Webkit == 22 / Firefox == 1014\r\n      vjs.log('LocalStorage Full (VideoJS)', e);\r\n    } else {\r\n      if (e.code == 18) {\r\n        vjs.log('LocalStorage not allowed (VideoJS)', e);\r\n      } else {\r\n        vjs.log('LocalStorage Error (VideoJS)', e);\r\n      }\r\n    }\r\n  }\r\n};\r\n\r\n/**\r\n * Get abosolute version of relative URL. Used to tell flash correct URL.\r\n * http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue\r\n * @param  {String} url URL to make absolute\r\n * @return {String}     Absolute URL\r\n * @private\r\n */\r\nvjs.getAbsoluteURL = function(url){\r\n\r\n  // Check if absolute URL\r\n  if (!url.match(/^https?:\\/\\//)) {\r\n    // Convert to absolute URL. Flash hosted off-site needs an absolute URL.\r\n    url = vjs.createEl('div', {\r\n      innerHTML: '<a href=\"'+url+'\">x</a>'\r\n    }).firstChild.href;\r\n  }\r\n\r\n  return url;\r\n};\r\n\r\n// usage: log('inside coolFunc',this,arguments);\r\n// http://paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/\r\nvjs.log = function(){\r\n  vjs.log.history = vjs.log.history || [];   // store logs to an array for reference\r\n  vjs.log.history.push(arguments);\r\n  if(window.console){\r\n    window.console.log(Array.prototype.slice.call(arguments));\r\n  }\r\n};\r\n\r\n// Offset Left\r\n// getBoundingClientRect technique from John Resig http://ejohn.org/blog/getboundingclientrect-is-awesome/\r\nvjs.findPosition = function(el) {\r\n    var box, docEl, body, clientLeft, scrollLeft, left, clientTop, scrollTop, top;\r\n\r\n    if (el.getBoundingClientRect && el.parentNode) {\r\n      box = el.getBoundingClientRect();\r\n    }\r\n\r\n    if (!box) {\r\n      return {\r\n        left: 0,\r\n        top: 0\r\n      };\r\n    }\r\n\r\n    docEl = document.documentElement;\r\n    body = document.body;\r\n\r\n    clientLeft = docEl.clientLeft || body.clientLeft || 0;\r\n    scrollLeft = window.pageXOffset || body.scrollLeft;\r\n    left = box.left + scrollLeft - clientLeft;\r\n\r\n    clientTop = docEl.clientTop || body.clientTop || 0;\r\n    scrollTop = window.pageYOffset || body.scrollTop;\r\n    top = box.top + scrollTop - clientTop;\r\n\r\n    return {\r\n      left: left,\r\n      top: top\r\n    };\r\n};\r\n/**\r\n * @fileoverview Player Component - Base class for all UI objects\r\n *\r\n */\r\n\r\n/**\r\n * Base UI Component class\r\n *\r\n * Components are embeddable UI objects that are represented by both a\r\n * javascript object and an element in the DOM. They can be children of other\r\n * components, and can have many children themselves.\r\n *\r\n *     // adding a button to the player\r\n *     var button = player.addChild('button');\r\n *     button.el(); // -> button element\r\n *\r\n *     <div class=\"video-js\">\r\n *       <div class=\"vjs-button\">Button</div>\r\n *     </div>\r\n *\r\n * Components are also event emitters.\r\n *\r\n *     button.on('click', function(){\r\n *       console.log('Button Clicked!');\r\n *     });\r\n *\r\n *     button.trigger('customevent');\r\n *\r\n * @param {Object} player  Main Player\r\n * @param {Object=} options\r\n * @class\r\n * @constructor\r\n * @extends vjs.CoreObject\r\n */\r\nvjs.Component = vjs.CoreObject.extend({\r\n  /**\r\n   * the constructor funciton for the class\r\n   *\r\n   * @constructor\r\n   */\r\n  init: function(player, options, ready){\r\n    this.player_ = player;\r\n\r\n    // Make a copy of prototype.options_ to protect against overriding global defaults\r\n    this.options_ = vjs.obj.copy(this.options_);\r\n\r\n    // Updated options with supplied options\r\n    options = this.options(options);\r\n\r\n    // Get ID from options, element, or create using player ID and unique ID\r\n    this.id_ = options['id'] || ((options['el'] && options['el']['id']) ? options['el']['id'] : player.id() + '_component_' + vjs.guid++ );\r\n\r\n    this.name_ = options['name'] || null;\r\n\r\n    // Create element if one wasn't provided in options\r\n    this.el_ = options['el'] || this.createEl();\r\n\r\n    this.children_ = [];\r\n    this.childIndex_ = {};\r\n    this.childNameIndex_ = {};\r\n\r\n    // Add any child components in options\r\n    this.initChildren();\r\n\r\n    this.ready(ready);\r\n    // Don't want to trigger ready here or it will before init is actually\r\n    // finished for all children that run this constructor\r\n  }\r\n});\r\n\r\n/**\r\n * Dispose of the component and all child components\r\n */\r\nvjs.Component.prototype.dispose = function(){\r\n  this.trigger('dispose');\r\n\r\n  // Dispose all children.\r\n  if (this.children_) {\r\n    for (var i = this.children_.length - 1; i >= 0; i--) {\r\n      if (this.children_[i].dispose) {\r\n        this.children_[i].dispose();\r\n      }\r\n    }\r\n  }\r\n\r\n  // Delete child references\r\n  this.children_ = null;\r\n  this.childIndex_ = null;\r\n  this.childNameIndex_ = null;\r\n\r\n  // Remove all event listeners.\r\n  this.off();\r\n\r\n  // Remove element from DOM\r\n  if (this.el_.parentNode) {\r\n    this.el_.parentNode.removeChild(this.el_);\r\n  }\r\n\r\n  vjs.removeData(this.el_);\r\n  this.el_ = null;\r\n};\r\n\r\n/**\r\n * Reference to main player instance\r\n *\r\n * @type {vjs.Player}\r\n * @private\r\n */\r\nvjs.Component.prototype.player_ = true;\r\n\r\n/**\r\n * Return the component's player\r\n *\r\n * @return {vjs.Player}\r\n */\r\nvjs.Component.prototype.player = function(){\r\n  return this.player_;\r\n};\r\n\r\n/**\r\n * The component's options object\r\n *\r\n * @type {Object}\r\n * @private\r\n */\r\nvjs.Component.prototype.options_;\r\n\r\n/**\r\n * Deep merge of options objects\r\n *\r\n * Whenever a property is an object on both options objects\r\n * the two properties will be merged using vjs.obj.deepMerge.\r\n *\r\n * This is used for merging options for child components. We\r\n * want it to be easy to override individual options on a child\r\n * component without having to rewrite all the other default options.\r\n *\r\n *     Parent.prototype.options_ = {\r\n *       children: {\r\n *         'childOne': { 'foo': 'bar', 'asdf': 'fdsa' },\r\n *         'childTwo': {},\r\n *         'childThree': {}\r\n *       }\r\n *     }\r\n *     newOptions = {\r\n *       children: {\r\n *         'childOne': { 'foo': 'baz', 'abc': '123' }\r\n *         'childTwo': null,\r\n *         'childFour': {}\r\n *       }\r\n *     }\r\n *\r\n *     this.options(newOptions);\r\n *\r\n * RESULT\r\n *\r\n *     {\r\n *       children: {\r\n *         'childOne': { 'foo': 'baz', 'asdf': 'fdsa', 'abc': '123' },\r\n *         'childTwo': null, // Disabled. Won't be initialized.\r\n *         'childThree': {},\r\n *         'childFour': {}\r\n *       }\r\n *     }\r\n *\r\n * @param  {Object} obj Object whose values will be overwritten\r\n * @return {Object}     NEW merged object. Does not return obj1.\r\n */\r\nvjs.Component.prototype.options = function(obj){\r\n  if (obj === undefined) return this.options_;\r\n\r\n  return this.options_ = vjs.obj.deepMerge(this.options_, obj);\r\n};\r\n\r\n/**\r\n * The DOM element for the component\r\n *\r\n * @type {Element}\r\n * @private\r\n */\r\nvjs.Component.prototype.el_;\r\n\r\n/**\r\n * Create the component's DOM element\r\n *\r\n * @param  {String=} tagName  Element's node type. e.g. 'div'\r\n * @param  {Object=} attributes An object of element attributes that should be set on the element\r\n * @return {Element}\r\n */\r\nvjs.Component.prototype.createEl = function(tagName, attributes){\r\n  return vjs.createEl(tagName, attributes);\r\n};\r\n\r\n/**\r\n * Get the component's DOM element\r\n *\r\n *     var domEl = myComponent.el();\r\n *\r\n * @return {Element}\r\n */\r\nvjs.Component.prototype.el = function(){\r\n  return this.el_;\r\n};\r\n\r\n/**\r\n * An optional element where, if defined, children will be inserted instead of\r\n * directly in `el_`\r\n *\r\n * @type {Element}\r\n * @private\r\n */\r\nvjs.Component.prototype.contentEl_;\r\n\r\n/**\r\n * Return the component's DOM element for embedding content.\r\n * Will either be el_ or a new element defined in createEl.\r\n *\r\n * @return {Element}\r\n */\r\nvjs.Component.prototype.contentEl = function(){\r\n  return this.contentEl_ || this.el_;\r\n};\r\n\r\n/**\r\n * The ID for the component\r\n *\r\n * @type {String}\r\n * @private\r\n */\r\nvjs.Component.prototype.id_;\r\n\r\n/**\r\n * Get the component's ID\r\n *\r\n *     var id = myComponent.id();\r\n *\r\n * @return {String}\r\n */\r\nvjs.Component.prototype.id = function(){\r\n  return this.id_;\r\n};\r\n\r\n/**\r\n * The name for the component. Often used to reference the component.\r\n *\r\n * @type {String}\r\n * @private\r\n */\r\nvjs.Component.prototype.name_;\r\n\r\n/**\r\n * Get the component's name. The name is often used to reference the component.\r\n *\r\n *     var name = myComponent.name();\r\n *\r\n * @return {String}\r\n */\r\nvjs.Component.prototype.name = function(){\r\n  return this.name_;\r\n};\r\n\r\n/**\r\n * Array of child components\r\n *\r\n * @type {Array}\r\n * @private\r\n */\r\nvjs.Component.prototype.children_;\r\n\r\n/**\r\n * Get an array of all child components\r\n *\r\n *     var kids = myComponent.children();\r\n *\r\n * @return {Array} The children\r\n */\r\nvjs.Component.prototype.children = function(){\r\n  return this.children_;\r\n};\r\n\r\n/**\r\n * Object of child components by ID\r\n *\r\n * @type {Object}\r\n * @private\r\n */\r\nvjs.Component.prototype.childIndex_;\r\n\r\n/**\r\n * Returns a child component with the provided ID\r\n *\r\n * @return {vjs.Component}\r\n */\r\nvjs.Component.prototype.getChildById = function(id){\r\n  return this.childIndex_[id];\r\n};\r\n\r\n/**\r\n * Object of child components by name\r\n *\r\n * @type {Object}\r\n * @private\r\n */\r\nvjs.Component.prototype.childNameIndex_;\r\n\r\n/**\r\n * Returns a child component with the provided ID\r\n *\r\n * @return {vjs.Component}\r\n */\r\nvjs.Component.prototype.getChild = function(name){\r\n  return this.childNameIndex_[name];\r\n};\r\n\r\n/**\r\n * Adds a child component inside this component\r\n *\r\n *     myComponent.el();\r\n *     // -> <div class='my-component'></div>\r\n *     myComonent.children();\r\n *     // [empty array]\r\n *\r\n *     var myButton = myComponent.addChild('MyButton');\r\n *     // -> <div class='my-component'><div class=\"my-button\">myButton<div></div>\r\n *     // -> myButton === myComonent.children()[0];\r\n *\r\n * Pass in options for child constructors and options for children of the child\r\n *\r\n *    var myButton = myComponent.addChild('MyButton', {\r\n *      text: 'Press Me',\r\n *      children: {\r\n *        buttonChildExample: {\r\n *          buttonChildOption: true\r\n *        }\r\n *      }\r\n *    });\r\n *\r\n * @param {String|vjs.Component} child The class name or instance of a child to add\r\n * @param {Object=} options Options, including options to be passed to children of the child.\r\n * @return {vjs.Component} The child component (created by this process if a string was used)\r\n * @suppress {accessControls|checkRegExp|checkTypes|checkVars|const|constantProperty|deprecated|duplicate|es5Strict|fileoverviewTags|globalThis|invalidCasts|missingProperties|nonStandardJsDocs|strictModuleDepCheck|undefinedNames|undefinedVars|unknownDefines|uselessCode|visibility}\r\n */\r\nvjs.Component.prototype.addChild = function(child, options){\r\n  var component, componentClass, componentName, componentId;\r\n\r\n  // If string, create new component with options\r\n  if (typeof child === 'string') {\r\n\r\n    componentName = child;\r\n\r\n    // Make sure options is at least an empty object to protect against errors\r\n    options = options || {};\r\n\r\n    // Assume name of set is a lowercased name of the UI Class (PlayButton, etc.)\r\n    componentClass = options['componentClass'] || vjs.capitalize(componentName);\r\n\r\n    // Set name through options\r\n    options['name'] = componentName;\r\n\r\n    // Create a new object & element for this controls set\r\n    // If there's no .player_, this is a player\r\n    // Closure Compiler throws an 'incomplete alias' warning if we use the vjs variable directly.\r\n    // Every class should be exported, so this should never be a problem here.\r\n    component = new window['videojs'][componentClass](this.player_ || this, options);\r\n\r\n  // child is a component instance\r\n  } else {\r\n    component = child;\r\n  }\r\n\r\n  this.children_.push(component);\r\n\r\n  if (typeof component.id === 'function') {\r\n    this.childIndex_[component.id()] = component;\r\n  }\r\n\r\n  // If a name wasn't used to create the component, check if we can use the\r\n  // name function of the component\r\n  componentName = componentName || (component.name && component.name());\r\n\r\n  if (componentName) {\r\n    this.childNameIndex_[componentName] = component;\r\n  }\r\n\r\n  // Add the UI object's element to the container div (box)\r\n  // Having an element is not required\r\n  if (typeof component['el'] === 'function' && component['el']()) {\r\n    this.contentEl().appendChild(component['el']());\r\n  }\r\n\r\n  // Return so it can stored on parent object if desired.\r\n  return component;\r\n};\r\n\r\n/**\r\n * Remove a child component from this component's list of children, and the\r\n * child component's element from this component's element\r\n *\r\n * @param  {vjs.Component} component Component to remove\r\n */\r\nvjs.Component.prototype.removeChild = function(component){\r\n  if (typeof component === 'string') {\r\n    component = this.getChild(component);\r\n  }\r\n\r\n  if (!component || !this.children_) return;\r\n\r\n  var childFound = false;\r\n  for (var i = this.children_.length - 1; i >= 0; i--) {\r\n    if (this.children_[i] === component) {\r\n      childFound = true;\r\n      this.children_.splice(i,1);\r\n      break;\r\n    }\r\n  }\r\n\r\n  if (!childFound) return;\r\n\r\n  this.childIndex_[component.id] = null;\r\n  this.childNameIndex_[component.name] = null;\r\n\r\n  var compEl = component.el();\r\n  if (compEl && compEl.parentNode === this.contentEl()) {\r\n    this.contentEl().removeChild(component.el());\r\n  }\r\n};\r\n\r\n/**\r\n * Add and initialize default child components from options\r\n *\r\n *     // when an instance of MyComponent is created, all children in options\r\n *     // will be added to the instance by their name strings and options\r\n *     MyComponent.prototype.options_.children = {\r\n *       myChildComponent: {\r\n *         myChildOption: true\r\n *       }\r\n *     }\r\n */\r\nvjs.Component.prototype.initChildren = function(){\r\n  var options = this.options_;\r\n\r\n  if (options && options['children']) {\r\n    var self = this;\r\n\r\n    // Loop through components and add them to the player\r\n    vjs.obj.each(options['children'], function(name, opts){\r\n      // Allow for disabling default components\r\n      // e.g. vjs.options['children']['posterImage'] = false\r\n      if (opts === false) return;\r\n\r\n      // Allow waiting to add components until a specific event is called\r\n      var tempAdd = function(){\r\n        // Set property name on player. Could cause conflicts with other prop names, but it's worth making refs easy.\r\n        self[name] = self.addChild(name, opts);\r\n      };\r\n\r\n      if (opts['loadEvent']) {\r\n        // this.one(opts.loadEvent, tempAdd)\r\n      } else {\r\n        tempAdd();\r\n      }\r\n    });\r\n  }\r\n};\r\n\r\n/**\r\n * Allows sub components to stack CSS class names\r\n *\r\n * @return {String} The constructed class name\r\n */\r\nvjs.Component.prototype.buildCSSClass = function(){\r\n    // Child classes can include a function that does:\r\n    // return 'CLASS NAME' + this._super();\r\n    return '';\r\n};\r\n\r\n/* Events\r\n============================================================================= */\r\n\r\n/**\r\n * Add an event listener to this component's element\r\n *\r\n *     var myFunc = function(){\r\n *       var myPlayer = this;\r\n *       // Do something when the event is fired\r\n *     };\r\n *\r\n *     myPlayer.on(\"eventName\", myFunc);\r\n *\r\n * The context will be the component.\r\n *\r\n * @param  {String}   type The event type e.g. 'click'\r\n * @param  {Function} fn   The event listener\r\n * @return {vjs.Component} self\r\n */\r\nvjs.Component.prototype.on = function(type, fn){\r\n  vjs.on(this.el_, type, vjs.bind(this, fn));\r\n  return this;\r\n};\r\n\r\n/**\r\n * Remove an event listener from the component's element\r\n *\r\n *     myComponent.off(\"eventName\", myFunc);\r\n *\r\n * @param  {String=}   type Event type. Without type it will remove all listeners.\r\n * @param  {Function=} fn   Event listener. Without fn it will remove all listeners for a type.\r\n * @return {vjs.Component}\r\n */\r\nvjs.Component.prototype.off = function(type, fn){\r\n  vjs.off(this.el_, type, fn);\r\n  return this;\r\n};\r\n\r\n/**\r\n * Add an event listener to be triggered only once and then removed\r\n *\r\n * @param  {String}   type Event type\r\n * @param  {Function} fn   Event listener\r\n * @return {vjs.Component}\r\n */\r\nvjs.Component.prototype.one = function(type, fn) {\r\n  vjs.one(this.el_, type, vjs.bind(this, fn));\r\n  return this;\r\n};\r\n\r\n/**\r\n * Trigger an event on an element\r\n *\r\n *     myComponent.trigger('eventName');\r\n *\r\n * @param  {String}       type  The event type to trigger, e.g. 'click'\r\n * @param  {Event|Object} event The event object to be passed to the listener\r\n * @return {vjs.Component}      self\r\n */\r\nvjs.Component.prototype.trigger = function(type, event){\r\n  vjs.trigger(this.el_, type, event);\r\n  return this;\r\n};\r\n\r\n/* Ready\r\n================================================================================ */\r\n/**\r\n * Is the component loaded\r\n * This can mean different things depending on the component.\r\n *\r\n * @private\r\n * @type {Boolean}\r\n */\r\nvjs.Component.prototype.isReady_;\r\n\r\n/**\r\n * Trigger ready as soon as initialization is finished\r\n *\r\n * Allows for delaying ready. Override on a sub class prototype.\r\n * If you set this.isReadyOnInitFinish_ it will affect all components.\r\n * Specially used when waiting for the Flash player to asynchrnously load.\r\n *\r\n * @type {Boolean}\r\n * @private\r\n */\r\nvjs.Component.prototype.isReadyOnInitFinish_ = true;\r\n\r\n/**\r\n * List of ready listeners\r\n *\r\n * @type {Array}\r\n * @private\r\n */\r\nvjs.Component.prototype.readyQueue_;\r\n\r\n/**\r\n * Bind a listener to the component's ready state\r\n *\r\n * Different from event listeners in that if the ready event has already happend\r\n * it will trigger the function immediately.\r\n *\r\n * @param  {Function} fn Ready listener\r\n * @return {vjs.Component}\r\n */\r\nvjs.Component.prototype.ready = function(fn){\r\n  if (fn) {\r\n    if (this.isReady_) {\r\n      fn.call(this);\r\n    } else {\r\n      if (this.readyQueue_ === undefined) {\r\n        this.readyQueue_ = [];\r\n      }\r\n      this.readyQueue_.push(fn);\r\n    }\r\n  }\r\n  return this;\r\n};\r\n\r\n/**\r\n * Trigger the ready listeners\r\n *\r\n * @return {vjs.Component}\r\n */\r\nvjs.Component.prototype.triggerReady = function(){\r\n  this.isReady_ = true;\r\n\r\n  var readyQueue = this.readyQueue_;\r\n\r\n  if (readyQueue && readyQueue.length > 0) {\r\n\r\n    for (var i = 0, j = readyQueue.length; i < j; i++) {\r\n      readyQueue[i].call(this);\r\n    }\r\n\r\n    // Reset Ready Queue\r\n    this.readyQueue_ = [];\r\n\r\n    // Allow for using event listeners also, in case you want to do something everytime a source is ready.\r\n    this.trigger('ready');\r\n  }\r\n};\r\n\r\n/* Display\r\n============================================================================= */\r\n\r\n/**\r\n * Add a CSS class name to the component's element\r\n *\r\n * @param {String} classToAdd Classname to add\r\n * @return {vjs.Component}\r\n */\r\nvjs.Component.prototype.addClass = function(classToAdd){\r\n  vjs.addClass(this.el_, classToAdd);\r\n  return this;\r\n};\r\n\r\n/**\r\n * Remove a CSS class name from the component's element\r\n *\r\n * @param {String} classToRemove Classname to remove\r\n * @return {vjs.Component}\r\n */\r\nvjs.Component.prototype.removeClass = function(classToRemove){\r\n  vjs.removeClass(this.el_, classToRemove);\r\n  return this;\r\n};\r\n\r\n/**\r\n * Show the component element if hidden\r\n *\r\n * @return {vjs.Component}\r\n */\r\nvjs.Component.prototype.show = function(){\r\n  this.el_.style.display = 'block';\r\n  return this;\r\n};\r\n\r\n/**\r\n * Hide the component element if hidden\r\n *\r\n * @return {vjs.Component}\r\n */\r\nvjs.Component.prototype.hide = function(){\r\n  this.el_.style.display = 'none';\r\n  return this;\r\n};\r\n\r\n/**\r\n * Lock an item in its visible state\r\n * To be used with fadeIn/fadeOut.\r\n *\r\n * @return {vjs.Component}\r\n * @private\r\n */\r\nvjs.Component.prototype.lockShowing = function(){\r\n  this.addClass('vjs-lock-showing');\r\n  return this;\r\n};\r\n\r\n/**\r\n * Unlock an item to be hidden\r\n * To be used with fadeIn/fadeOut.\r\n *\r\n * @return {vjs.Component}\r\n * @private\r\n */\r\nvjs.Component.prototype.unlockShowing = function(){\r\n  this.removeClass('vjs-lock-showing');\r\n  return this;\r\n};\r\n\r\n/**\r\n * Disable component by making it unshowable\r\n */\r\nvjs.Component.prototype.disable = function(){\r\n  this.hide();\r\n  this.show = function(){};\r\n};\r\n\r\n/**\r\n * Set or get the width of the component (CSS values)\r\n *\r\n * Video tag width/height only work in pixels. No percents.\r\n * But allowing limited percents use. e.g. width() will return number+%, not computed width\r\n *\r\n * @param  {Number|String=} num   Optional width number\r\n * @param  {Boolean} skipListeners Skip the 'resize' event trigger\r\n * @return {vjs.Component} Returns 'this' if width was set\r\n * @return {Number|String} Returns the width if nothing was set\r\n */\r\nvjs.Component.prototype.width = function(num, skipListeners){\r\n  return this.dimension('width', num, skipListeners);\r\n};\r\n\r\n/**\r\n * Get or set the height of the component (CSS values)\r\n *\r\n * @param  {Number|String=} num     New component height\r\n * @param  {Boolean=} skipListeners Skip the resize event trigger\r\n * @return {vjs.Component} The component if the height was set\r\n * @return {Number|String} The height if it wasn't set\r\n */\r\nvjs.Component.prototype.height = function(num, skipListeners){\r\n  return this.dimension('height', num, skipListeners);\r\n};\r\n\r\n/**\r\n * Set both width and height at the same time\r\n *\r\n * @param  {Number|String} width\r\n * @param  {Number|String} height\r\n * @return {vjs.Component} The component\r\n */\r\nvjs.Component.prototype.dimensions = function(width, height){\r\n  // Skip resize listeners on width for optimization\r\n  return this.width(width, true).height(height);\r\n};\r\n\r\n/**\r\n * Get or set width or height\r\n *\r\n * This is the shared code for the width() and height() methods.\r\n * All for an integer, integer + 'px' or integer + '%';\r\n *\r\n * Known issue: Hidden elements officially have a width of 0. We're defaulting\r\n * to the style.width value and falling back to computedStyle which has the\r\n * hidden element issue. Info, but probably not an efficient fix:\r\n * http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/\r\n *\r\n * @param  {String} widthOrHeight  'width' or 'height'\r\n * @param  {Number|String=} num     New dimension\r\n * @param  {Boolean=} skipListeners Skip resize event trigger\r\n * @return {vjs.Component} The component if a dimension was set\r\n * @return {Number|String} The dimension if nothing was set\r\n * @private\r\n */\r\nvjs.Component.prototype.dimension = function(widthOrHeight, num, skipListeners){\r\n  if (num !== undefined) {\r\n\r\n    // Check if using css width/height (% or px) and adjust\r\n    if ((''+num).indexOf('%') !== -1 || (''+num).indexOf('px') !== -1) {\r\n      this.el_.style[widthOrHeight] = num;\r\n    } else if (num === 'auto') {\r\n      this.el_.style[widthOrHeight] = '';\r\n    } else {\r\n      this.el_.style[widthOrHeight] = num+'px';\r\n    }\r\n\r\n    // skipListeners allows us to avoid triggering the resize event when setting both width and height\r\n    if (!skipListeners) { this.trigger('resize'); }\r\n\r\n    // Return component\r\n    return this;\r\n  }\r\n\r\n  // Not setting a value, so getting it\r\n  // Make sure element exists\r\n  if (!this.el_) return 0;\r\n\r\n  // Get dimension value from style\r\n  var val = this.el_.style[widthOrHeight];\r\n  var pxIndex = val.indexOf('px');\r\n  if (pxIndex !== -1) {\r\n    // Return the pixel value with no 'px'\r\n    return parseInt(val.slice(0,pxIndex), 10);\r\n\r\n  // No px so using % or no style was set, so falling back to offsetWidth/height\r\n  // If component has display:none, offset will return 0\r\n  // TODO: handle display:none and no dimension style using px\r\n  } else {\r\n\r\n    return parseInt(this.el_['offset'+vjs.capitalize(widthOrHeight)], 10);\r\n\r\n    // ComputedStyle version.\r\n    // Only difference is if the element is hidden it will return\r\n    // the percent value (e.g. '100%'')\r\n    // instead of zero like offsetWidth returns.\r\n    // var val = vjs.getComputedStyleValue(this.el_, widthOrHeight);\r\n    // var pxIndex = val.indexOf('px');\r\n\r\n    // if (pxIndex !== -1) {\r\n    //   return val.slice(0, pxIndex);\r\n    // } else {\r\n    //   return val;\r\n    // }\r\n  }\r\n};\r\n\r\n/**\r\n * Fired when the width and/or height of the component changes\r\n * @event resize\r\n */\r\nvjs.Component.prototype.onResize;\r\n\r\n/**\r\n * Emit 'tap' events when touch events are supported\r\n *\r\n * This is used to support toggling the controls through a tap on the video.\r\n *\r\n * We're requireing them to be enabled because otherwise every component would\r\n * have this extra overhead unnecessarily, on mobile devices where extra\r\n * overhead is especially bad.\r\n * @private\r\n */\r\nvjs.Component.prototype.emitTapEvents = function(){\r\n  var touchStart, touchTime, couldBeTap, noTap;\r\n\r\n  // Track the start time so we can determine how long the touch lasted\r\n  touchStart = 0;\r\n\r\n  this.on('touchstart', function(event) {\r\n    // Record start time so we can detect a tap vs. \"touch and hold\"\r\n    touchStart = new Date().getTime();\r\n    // Reset couldBeTap tracking\r\n    couldBeTap = true;\r\n  });\r\n\r\n  noTap = function(){\r\n    couldBeTap = false;\r\n  };\r\n  // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s\r\n  this.on('touchmove', noTap);\r\n  this.on('touchleave', noTap);\r\n  this.on('touchcancel', noTap);\r\n\r\n  // When the touch ends, measure how long it took and trigger the appropriate\r\n  // event\r\n  this.on('touchend', function() {\r\n    // Proceed only if the touchmove/leave/cancel event didn't happen\r\n    if (couldBeTap === true) {\r\n      // Measure how long the touch lasted\r\n      touchTime = new Date().getTime() - touchStart;\r\n      // The touch needs to be quick in order to consider it a tap\r\n      if (touchTime < 250) {\r\n        this.trigger('tap');\r\n        // It may be good to copy the touchend event object and change the\r\n        // type to tap, if the other event properties aren't exact after\r\n        // vjs.fixEvent runs (e.g. event.target)\r\n      }\r\n    }\r\n  });\r\n};\r\n/* Button - Base class for all buttons\r\n================================================================================ */\r\n/**\r\n * Base class for all buttons\r\n * @param {vjs.Player|Object} player\r\n * @param {Object=} options\r\n * @class\r\n * @constructor\r\n */\r\nvjs.Button = vjs.Component.extend({\r\n  /**\r\n   * @constructor\r\n   * @inheritDoc\r\n   */\r\n  init: function(player, options){\r\n    vjs.Component.call(this, player, options);\r\n\r\n    var touchstart = false;\r\n    this.on('touchstart', function(event) {\r\n      // Stop click and other mouse events from triggering also\r\n      event.preventDefault();\r\n      touchstart = true;\r\n    });\r\n    this.on('touchmove', function() {\r\n      touchstart = false;\r\n    });\r\n    var self = this;\r\n    this.on('touchend', function(event) {\r\n      if (touchstart) {\r\n        self.onClick(event);\r\n      }\r\n      event.preventDefault();\r\n    });\r\n\r\n    this.on('click', this.onClick);\r\n    this.on('focus', this.onFocus);\r\n    this.on('blur', this.onBlur);\r\n  }\r\n});\r\n\r\nvjs.Button.prototype.createEl = function(type, props){\r\n  // Add standard Aria and Tabindex info\r\n  props = vjs.obj.merge({\r\n    className: this.buildCSSClass(),\r\n    innerHTML: '<div class=\"vjs-control-content\"><span class=\"vjs-control-text\">' + (this.buttonText || 'Need Text') + '</span></div>',\r\n    role: 'button',\r\n    'aria-live': 'polite', // let the screen reader user know that the text of the button may change\r\n    tabIndex: 0\r\n  }, props);\r\n\r\n  return vjs.Component.prototype.createEl.call(this, type, props);\r\n};\r\n\r\nvjs.Button.prototype.buildCSSClass = function(){\r\n  // TODO: Change vjs-control to vjs-button?\r\n  return 'vjs-control ' + vjs.Component.prototype.buildCSSClass.call(this);\r\n};\r\n\r\n  // Click - Override with specific functionality for button\r\nvjs.Button.prototype.onClick = function(){};\r\n\r\n  // Focus - Add keyboard functionality to element\r\nvjs.Button.prototype.onFocus = function(){\r\n  vjs.on(document, 'keyup', vjs.bind(this, this.onKeyPress));\r\n};\r\n\r\n  // KeyPress (document level) - Trigger click when keys are pressed\r\nvjs.Button.prototype.onKeyPress = function(event){\r\n  // Check for space bar (32) or enter (13) keys\r\n  if (event.which == 32 || event.which == 13) {\r\n    event.preventDefault();\r\n    this.onClick();\r\n  }\r\n};\r\n\r\n// Blur - Remove keyboard triggers\r\nvjs.Button.prototype.onBlur = function(){\r\n  vjs.off(document, 'keyup', vjs.bind(this, this.onKeyPress));\r\n};\r\n/* Slider\r\n================================================================================ */\r\n/**\r\n * The base functionality for sliders like the volume bar and seek bar\r\n *\r\n * @param {vjs.Player|Object} player\r\n * @param {Object=} options\r\n * @constructor\r\n */\r\nvjs.Slider = vjs.Component.extend({\r\n  /** @constructor */\r\n  init: function(player, options){\r\n    vjs.Component.call(this, player, options);\r\n\r\n    // Set property names to bar and handle to match with the child Slider class is looking for\r\n    this.bar = this.getChild(this.options_['barName']);\r\n    this.handle = this.getChild(this.options_['handleName']);\r\n\r\n    player.on(this.playerEvent, vjs.bind(this, this.update));\r\n\r\n    this.on('mousedown', this.onMouseDown);\r\n    this.on('touchstart', this.onMouseDown);\r\n    this.on('focus', this.onFocus);\r\n    this.on('blur', this.onBlur);\r\n    this.on('click', this.onClick);\r\n\r\n    this.player_.on('controlsvisible', vjs.bind(this, this.update));\r\n\r\n    // This is actually to fix the volume handle position. http://twitter.com/#!/gerritvanaaken/status/159046254519787520\r\n    // this.player_.one('timeupdate', vjs.bind(this, this.update));\r\n\r\n    player.ready(vjs.bind(this, this.update));\r\n\r\n    this.boundEvents = {};\r\n  }\r\n});\r\n\r\nvjs.Slider.prototype.createEl = function(type, props) {\r\n  props = props || {};\r\n  // Add the slider element class to all sub classes\r\n  props.className = props.className + ' vjs-slider';\r\n  props = vjs.obj.merge({\r\n    role: 'slider',\r\n    'aria-valuenow': 0,\r\n    'aria-valuemin': 0,\r\n    'aria-valuemax': 100,\r\n    tabIndex: 0\r\n  }, props);\r\n\r\n  return vjs.Component.prototype.createEl.call(this, type, props);\r\n};\r\n\r\nvjs.Slider.prototype.onMouseDown = function(event){\r\n  event.preventDefault();\r\n  vjs.blockTextSelection();\r\n\r\n  this.boundEvents.move = vjs.bind(this, this.onMouseMove);\r\n  this.boundEvents.end = vjs.bind(this, this.onMouseUp);\r\n\r\n  vjs.on(document, 'mousemove', this.boundEvents.move);\r\n  vjs.on(document, 'mouseup', this.boundEvents.end);\r\n  vjs.on(document, 'touchmove', this.boundEvents.move);\r\n  vjs.on(document, 'touchend', this.boundEvents.end);\r\n\r\n  this.onMouseMove(event);\r\n};\r\n\r\nvjs.Slider.prototype.onMouseUp = function() {\r\n  vjs.unblockTextSelection();\r\n  vjs.off(document, 'mousemove', this.boundEvents.move, false);\r\n  vjs.off(document, 'mouseup', this.boundEvents.end, false);\r\n  vjs.off(document, 'touchmove', this.boundEvents.move, false);\r\n  vjs.off(document, 'touchend', this.boundEvents.end, false);\r\n\r\n  this.update();\r\n};\r\n\r\nvjs.Slider.prototype.update = function(){\r\n  // In VolumeBar init we have a setTimeout for update that pops and update to the end of the\r\n  // execution stack. The player is destroyed before then update will cause an error\r\n  if (!this.el_) return;\r\n\r\n  // If scrubbing, we could use a cached value to make the handle keep up with the user's mouse.\r\n  // On HTML5 browsers scrubbing is really smooth, but some flash players are slow, so we might want to utilize this later.\r\n  // var progress =  (this.player_.scrubbing) ? this.player_.getCache().currentTime / this.player_.duration() : this.player_.currentTime() / this.player_.duration();\r\n\r\n  var barProgress,\r\n      progress = this.getPercent(),\r\n      handle = this.handle,\r\n      bar = this.bar;\r\n\r\n  // Protect against no duration and other division issues\r\n  if (isNaN(progress)) { progress = 0; }\r\n\r\n  barProgress = progress;\r\n\r\n  // If there is a handle, we need to account for the handle in our calculation for progress bar\r\n  // so that it doesn't fall short of or extend past the handle.\r\n  if (handle) {\r\n\r\n    var box = this.el_,\r\n        boxWidth = box.offsetWidth,\r\n\r\n        handleWidth = handle.el().offsetWidth,\r\n\r\n        // The width of the handle in percent of the containing box\r\n        // In IE, widths may not be ready yet causing NaN\r\n        handlePercent = (handleWidth) ? handleWidth / boxWidth : 0,\r\n\r\n        // Get the adjusted size of the box, considering that the handle's center never touches the left or right side.\r\n        // There is a margin of half the handle's width on both sides.\r\n        boxAdjustedPercent = 1 - handlePercent,\r\n\r\n        // Adjust the progress that we'll use to set widths to the new adjusted box width\r\n        adjustedProgress = progress * boxAdjustedPercent;\r\n\r\n    // The bar does reach the left side, so we need to account for this in the bar's width\r\n    barProgress = adjustedProgress + (handlePercent / 2);\r\n\r\n    // Move the handle from the left based on the adjected progress\r\n    handle.el().style.left = vjs.round(adjustedProgress * 100, 2) + '%';\r\n  }\r\n\r\n  // Set the new bar width\r\n  bar.el().style.width = vjs.round(barProgress * 100, 2) + '%';\r\n};\r\n\r\nvjs.Slider.prototype.calculateDistance = function(event){\r\n  var el, box, boxX, boxY, boxW, boxH, handle, pageX, pageY;\r\n\r\n  el = this.el_;\r\n  box = vjs.findPosition(el);\r\n  boxW = boxH = el.offsetWidth;\r\n  handle = this.handle;\r\n\r\n  if (this.options_.vertical) {\r\n    boxY = box.top;\r\n\r\n    if (event.changedTouches) {\r\n      pageY = event.changedTouches[0].pageY;\r\n    } else {\r\n      pageY = event.pageY;\r\n    }\r\n\r\n    if (handle) {\r\n      var handleH = handle.el().offsetHeight;\r\n      // Adjusted X and Width, so handle doesn't go outside the bar\r\n      boxY = boxY + (handleH / 2);\r\n      boxH = boxH - handleH;\r\n    }\r\n\r\n    // Percent that the click is through the adjusted area\r\n    return Math.max(0, Math.min(1, ((boxY - pageY) + boxH) / boxH));\r\n\r\n  } else {\r\n    boxX = box.left;\r\n\r\n    if (event.changedTouches) {\r\n      pageX = event.changedTouches[0].pageX;\r\n    } else {\r\n      pageX = event.pageX;\r\n    }\r\n\r\n    if (handle) {\r\n      var handleW = handle.el().offsetWidth;\r\n\r\n      // Adjusted X and Width, so handle doesn't go outside the bar\r\n      boxX = boxX + (handleW / 2);\r\n      boxW = boxW - handleW;\r\n    }\r\n\r\n    // Percent that the click is through the adjusted area\r\n    return Math.max(0, Math.min(1, (pageX - boxX) / boxW));\r\n  }\r\n};\r\n\r\nvjs.Slider.prototype.onFocus = function(){\r\n  vjs.on(document, 'keyup', vjs.bind(this, this.onKeyPress));\r\n};\r\n\r\nvjs.Slider.prototype.onKeyPress = function(event){\r\n  if (event.which == 37) { // Left Arrow\r\n    event.preventDefault();\r\n    this.stepBack();\r\n  } else if (event.which == 39) { // Right Arrow\r\n    event.preventDefault();\r\n    this.stepForward();\r\n  }\r\n};\r\n\r\nvjs.Slider.prototype.onBlur = function(){\r\n  vjs.off(document, 'keyup', vjs.bind(this, this.onKeyPress));\r\n};\r\n\r\n/**\r\n * Listener for click events on slider, used to prevent clicks\r\n *   from bubbling up to parent elements like button menus.\r\n * @param  {Object} event Event object\r\n */\r\nvjs.Slider.prototype.onClick = function(event){\r\n  event.stopImmediatePropagation();\r\n  event.preventDefault();\r\n};\r\n\r\n/**\r\n * SeekBar Behavior includes play progress bar, and seek handle\r\n * Needed so it can determine seek position based on handle position/size\r\n * @param {vjs.Player|Object} player\r\n * @param {Object=} options\r\n * @constructor\r\n */\r\nvjs.SliderHandle = vjs.Component.extend();\r\n\r\n/**\r\n * Default value of the slider\r\n *\r\n * @type {Number}\r\n * @private\r\n */\r\nvjs.SliderHandle.prototype.defaultValue = 0;\r\n\r\n/** @inheritDoc */\r\nvjs.SliderHandle.prototype.createEl = function(type, props) {\r\n  props = props || {};\r\n  // Add the slider element class to all sub classes\r\n  props.className = props.className + ' vjs-slider-handle';\r\n  props = vjs.obj.merge({\r\n    innerHTML: '<span class=\"vjs-control-text\">'+this.defaultValue+'</span>'\r\n  }, props);\r\n\r\n  return vjs.Component.prototype.createEl.call(this, 'div', props);\r\n};\r\n/* Menu\r\n================================================================================ */\r\n/**\r\n * The Menu component is used to build pop up menus, including subtitle and\r\n * captions selection menus.\r\n *\r\n * @param {vjs.Player|Object} player\r\n * @param {Object=} options\r\n * @class\r\n * @constructor\r\n */\r\nvjs.Menu = vjs.Component.extend();\r\n\r\n/**\r\n * Add a menu item to the menu\r\n * @param {Object|String} component Component or component type to add\r\n */\r\nvjs.Menu.prototype.addItem = function(component){\r\n  this.addChild(component);\r\n  component.on('click', vjs.bind(this, function(){\r\n    this.unlockShowing();\r\n  }));\r\n};\r\n\r\n/** @inheritDoc */\r\nvjs.Menu.prototype.createEl = function(){\r\n  var contentElType = this.options().contentElType || 'ul';\r\n  this.contentEl_ = vjs.createEl(contentElType, {\r\n    className: 'vjs-menu-content'\r\n  });\r\n  var el = vjs.Component.prototype.createEl.call(this, 'div', {\r\n    append: this.contentEl_,\r\n    className: 'vjs-menu'\r\n  });\r\n  el.appendChild(this.contentEl_);\r\n\r\n  // Prevent clicks from bubbling up. Needed for Menu Buttons,\r\n  // where a click on the parent is significant\r\n  vjs.on(el, 'click', function(event){\r\n    event.preventDefault();\r\n    event.stopImmediatePropagation();\r\n  });\r\n\r\n  return el;\r\n};\r\n\r\n/**\r\n * The component for a menu item. `<li>`\r\n *\r\n * @param {vjs.Player|Object} player\r\n * @param {Object=} options\r\n * @class\r\n * @constructor\r\n */\r\nvjs.MenuItem = vjs.Button.extend({\r\n  /** @constructor */\r\n  init: function(player, options){\r\n    vjs.Button.call(this, player, options);\r\n    this.selected(options['selected']);\r\n  }\r\n});\r\n\r\n/** @inheritDoc */\r\nvjs.MenuItem.prototype.createEl = function(type, props){\r\n  return vjs.Button.prototype.createEl.call(this, 'li', vjs.obj.merge({\r\n    className: 'vjs-menu-item',\r\n    innerHTML: this.options_['label']\r\n  }, props));\r\n};\r\n\r\n/**\r\n * Handle a click on the menu item, and set it to selected\r\n */\r\nvjs.MenuItem.prototype.onClick = function(){\r\n  this.selected(true);\r\n};\r\n\r\n/**\r\n * Set this menu item as selected or not\r\n * @param  {Boolean} selected\r\n */\r\nvjs.MenuItem.prototype.selected = function(selected){\r\n  if (selected) {\r\n    this.addClass('vjs-selected');\r\n    this.el_.setAttribute('aria-selected',true);\r\n  } else {\r\n    this.removeClass('vjs-selected');\r\n    this.el_.setAttribute('aria-selected',false);\r\n  }\r\n};\r\n\r\n\r\n/**\r\n * A button class with a popup menu\r\n * @param {vjs.Player|Object} player\r\n * @param {Object=} options\r\n * @constructor\r\n */\r\nvjs.MenuButton = vjs.Button.extend({\r\n  /** @constructor */\r\n  init: function(player, options){\r\n    vjs.Button.call(this, player, options);\r\n\r\n    this.menu = this.createMenu();\r\n\r\n    // Add list to element\r\n    this.addChild(this.menu);\r\n\r\n    // Automatically hide empty menu buttons\r\n    if (this.items && this.items.length === 0) {\r\n      this.hide();\r\n    }\r\n\r\n    this.on('keyup', this.onKeyPress);\r\n    this.el_.setAttribute('aria-haspopup', true);\r\n    this.el_.setAttribute('role', 'button');\r\n  }\r\n});\r\n\r\n/**\r\n * Track the state of the menu button\r\n * @type {Boolean}\r\n * @private\r\n */\r\nvjs.MenuButton.prototype.buttonPressed_ = false;\r\n\r\nvjs.MenuButton.prototype.createMenu = function(){\r\n  var menu = new vjs.Menu(this.player_);\r\n\r\n  // Add a title list item to the top\r\n  if (this.options().title) {\r\n    menu.el().appendChild(vjs.createEl('li', {\r\n      className: 'vjs-menu-title',\r\n      innerHTML: vjs.capitalize(this.kind_),\r\n      tabindex: -1\r\n    }));\r\n  }\r\n\r\n  this.items = this['createItems']();\r\n\r\n  if (this.items) {\r\n    // Add menu items to the menu\r\n    for (var i = 0; i < this.items.length; i++) {\r\n      menu.addItem(this.items[i]);\r\n    }\r\n  }\r\n\r\n  return menu;\r\n};\r\n\r\n/**\r\n * Create the list of menu items. Specific to each subclass.\r\n */\r\nvjs.MenuButton.prototype.createItems = function(){};\r\n\r\n/** @inheritDoc */\r\nvjs.MenuButton.prototype.buildCSSClass = function(){\r\n  return this.className + ' vjs-menu-button ' + vjs.Button.prototype.buildCSSClass.call(this);\r\n};\r\n\r\n// Focus - Add keyboard functionality to element\r\n// This function is not needed anymore. Instead, the keyboard functionality is handled by\r\n// treating the button as triggering a submenu. When the button is pressed, the submenu\r\n// appears. Pressing the button again makes the submenu disappear.\r\nvjs.MenuButton.prototype.onFocus = function(){};\r\n// Can't turn off list display that we turned on with focus, because list would go away.\r\nvjs.MenuButton.prototype.onBlur = function(){};\r\n\r\nvjs.MenuButton.prototype.onClick = function(){\r\n  // When you click the button it adds focus, which will show the menu indefinitely.\r\n  // So we'll remove focus when the mouse leaves the button.\r\n  // Focus is needed for tab navigation.\r\n  this.one('mouseout', vjs.bind(this, function(){\r\n    this.menu.unlockShowing();\r\n    this.el_.blur();\r\n  }));\r\n  if (this.buttonPressed_){\r\n    this.unpressButton();\r\n  } else {\r\n    this.pressButton();\r\n  }\r\n};\r\n\r\nvjs.MenuButton.prototype.onKeyPress = function(event){\r\n  event.preventDefault();\r\n\r\n  // Check for space bar (32) or enter (13) keys\r\n  if (event.which == 32 || event.which == 13) {\r\n    if (this.buttonPressed_){\r\n      this.unpressButton();\r\n    } else {\r\n      this.pressButton();\r\n    }\r\n  // Check for escape (27) key\r\n  } else if (event.which == 27){\r\n    if (this.buttonPressed_){\r\n      this.unpressButton();\r\n    }\r\n  }\r\n};\r\n\r\nvjs.MenuButton.prototype.pressButton = function(){\r\n  this.buttonPressed_ = true;\r\n  this.menu.lockShowing();\r\n  this.el_.setAttribute('aria-pressed', true);\r\n  if (this.items && this.items.length > 0) {\r\n    this.items[0].el().focus(); // set the focus to the title of the submenu\r\n  }\r\n};\r\n\r\nvjs.MenuButton.prototype.unpressButton = function(){\r\n  this.buttonPressed_ = false;\r\n  this.menu.unlockShowing();\r\n  this.el_.setAttribute('aria-pressed', false);\r\n};\r\n\r\n/**\r\n * An instance of the `vjs.Player` class is created when any of the Video.js setup methods are used to initialize a video.\r\n *\r\n * ```js\r\n * var myPlayer = videojs('example_video_1');\r\n * ```\r\n *\r\n * In the follwing example, the `data-setup` attribute tells the Video.js library to create a player instance when the library is ready.\r\n *\r\n * ```html\r\n * <video id=\"example_video_1\" data-setup='{}' controls>\r\n *   <source src=\"my-source.mp4\" type=\"video/mp4\">\r\n * </video>\r\n * ```\r\n *\r\n * After an instance has been created it can be accessed globally using `Video('example_video_1')`.\r\n *\r\n * @class\r\n * @extends vjs.Component\r\n */\r\nvjs.Player = vjs.Component.extend({\r\n\r\n  /**\r\n   * player's constructor function\r\n   *\r\n   * @constructs\r\n   * @method init\r\n   * @param {Element} tag        The original video tag used for configuring options\r\n   * @param {Object=} options    Player options\r\n   * @param {Function=} ready    Ready callback function\r\n   */\r\n  init: function(tag, options, ready){\r\n    this.tag = tag; // Store the original tag used to set options\r\n\r\n    // Set Options\r\n    // The options argument overrides options set in the video tag\r\n    // which overrides globally set options.\r\n    // This latter part coincides with the load order\r\n    // (tag must exist before Player)\r\n    options = vjs.obj.merge(this.getTagSettings(tag), options);\r\n\r\n    // Cache for video property values.\r\n    this.cache_ = {};\r\n\r\n    // Set poster\r\n    this.poster_ = options['poster'];\r\n    // Set controls\r\n    this.controls_ = options['controls'];\r\n    // Original tag settings stored in options\r\n    // now remove immediately so native controls don't flash.\r\n    // May be turned back on by HTML5 tech if nativeControlsForTouch is true\r\n    tag.controls = false;\r\n\r\n    // Run base component initializing with new options.\r\n    // Builds the element through createEl()\r\n    // Inits and embeds any child components in opts\r\n    vjs.Component.call(this, this, options, ready);\r\n\r\n    // Update controls className. Can't do this when the controls are initially\r\n    // set because the element doesn't exist yet.\r\n    if (this.controls()) {\r\n      this.addClass('vjs-controls-enabled');\r\n    } else {\r\n      this.addClass('vjs-controls-disabled');\r\n    }\r\n\r\n    // TODO: Make this smarter. Toggle user state between touching/mousing\r\n    // using events, since devices can have both touch and mouse events.\r\n    // if (vjs.TOUCH_ENABLED) {\r\n    //   this.addClass('vjs-touch-enabled');\r\n    // }\r\n\r\n    // Firstplay event implimentation. Not sold on the event yet.\r\n    // Could probably just check currentTime==0?\r\n    this.one('play', function(e){\r\n      var fpEvent = { type: 'firstplay', target: this.el_ };\r\n      // Using vjs.trigger so we can check if default was prevented\r\n      var keepGoing = vjs.trigger(this.el_, fpEvent);\r\n\r\n      if (!keepGoing) {\r\n        e.preventDefault();\r\n        e.stopPropagation();\r\n        e.stopImmediatePropagation();\r\n      }\r\n    });\r\n\r\n    this.on('ended', this.onEnded);\r\n    this.on('play', this.onPlay);\r\n    this.on('firstplay', this.onFirstPlay);\r\n    this.on('pause', this.onPause);\r\n    this.on('progress', this.onProgress);\r\n    this.on('durationchange', this.onDurationChange);\r\n    this.on('error', this.onError);\r\n    this.on('fullscreenchange', this.onFullscreenChange);\r\n\r\n    // Make player easily findable by ID\r\n    vjs.players[this.id_] = this;\r\n\r\n    if (options['plugins']) {\r\n      vjs.obj.each(options['plugins'], function(key, val){\r\n        this[key](val);\r\n      }, this);\r\n    }\r\n\r\n    this.listenForUserActivity();\r\n  }\r\n});\r\n\r\n/**\r\n * Player instance options, surfaced using vjs.options\r\n * vjs.options = vjs.Player.prototype.options_\r\n * Make changes in vjs.options, not here.\r\n * All options should use string keys so they avoid\r\n * renaming by closure compiler\r\n * @type {Object}\r\n * @private\r\n */\r\nvjs.Player.prototype.options_ = vjs.options;\r\n\r\n/**\r\n * Destroys the video player and does any necessary cleanup\r\n *\r\n *     myPlayer.dispose();\r\n *\r\n * This is especially helpful if you are dynamically adding and removing videos\r\n * to/from the DOM.\r\n */\r\nvjs.Player.prototype.dispose = function(){\r\n  this.trigger('dispose');\r\n  // prevent dispose from being called twice\r\n  this.off('dispose');\r\n\r\n  // Kill reference to this player\r\n  vjs.players[this.id_] = null;\r\n  if (this.tag && this.tag['player']) { this.tag['player'] = null; }\r\n  if (this.el_ && this.el_['player']) { this.el_['player'] = null; }\r\n\r\n  // Ensure that tracking progress and time progress will stop and plater deleted\r\n  this.stopTrackingProgress();\r\n  this.stopTrackingCurrentTime();\r\n\r\n  if (this.tech) { this.tech.dispose(); }\r\n\r\n  // Component dispose\r\n  vjs.Component.prototype.dispose.call(this);\r\n};\r\n\r\nvjs.Player.prototype.getTagSettings = function(tag){\r\n  var options = {\r\n    'sources': [],\r\n    'tracks': []\r\n  };\r\n\r\n  vjs.obj.merge(options, vjs.getAttributeValues(tag));\r\n\r\n  // Get tag children settings\r\n  if (tag.hasChildNodes()) {\r\n    var children, child, childName, i, j;\r\n\r\n    children = tag.childNodes;\r\n\r\n    for (i=0,j=children.length; i<j; i++) {\r\n      child = children[i];\r\n      // Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/\r\n      childName = child.nodeName.toLowerCase();\r\n      if (childName === 'source') {\r\n        options['sources'].push(vjs.getAttributeValues(child));\r\n      } else if (childName === 'track') {\r\n        options['tracks'].push(vjs.getAttributeValues(child));\r\n      }\r\n    }\r\n  }\r\n\r\n  return options;\r\n};\r\n\r\nvjs.Player.prototype.createEl = function(){\r\n  var el = this.el_ = vjs.Component.prototype.createEl.call(this, 'div');\r\n  var tag = this.tag;\r\n\r\n  // Remove width/height attrs from tag so CSS can make it 100% width/height\r\n  tag.removeAttribute('width');\r\n  tag.removeAttribute('height');\r\n  // Empty video tag tracks so the built-in player doesn't use them also.\r\n  // This may not be fast enough to stop HTML5 browsers from reading the tags\r\n  // so we'll need to turn off any default tracks if we're manually doing\r\n  // captions and subtitles. videoElement.textTracks\r\n  if (tag.hasChildNodes()) {\r\n    var nodes, nodesLength, i, node, nodeName, removeNodes;\r\n\r\n    nodes = tag.childNodes;\r\n    nodesLength = nodes.length;\r\n    removeNodes = [];\r\n\r\n    while (nodesLength--) {\r\n      node = nodes[nodesLength];\r\n      nodeName = node.nodeName.toLowerCase();\r\n      if (nodeName === 'track') {\r\n        removeNodes.push(node);\r\n      }\r\n    }\r\n\r\n    for (i=0; i<removeNodes.length; i++) {\r\n      tag.removeChild(removeNodes[i]);\r\n    }\r\n  }\r\n\r\n  // Make sure tag ID exists\r\n  tag.id = tag.id || 'vjs_video_' + vjs.guid++;\r\n\r\n  // Give video tag ID and class to player div\r\n  // ID will now reference player box, not the video tag\r\n  el.id = tag.id;\r\n  el.className = tag.className;\r\n\r\n  // Update tag id/class for use as HTML5 playback tech\r\n  // Might think we should do this after embedding in container so .vjs-tech class\r\n  // doesn't flash 100% width/height, but class only applies with .video-js parent\r\n  tag.id += '_html5_api';\r\n  tag.className = 'vjs-tech';\r\n\r\n  // Make player findable on elements\r\n  tag['player'] = el['player'] = this;\r\n  // Default state of video is paused\r\n  this.addClass('vjs-paused');\r\n\r\n  // Make box use width/height of tag, or rely on default implementation\r\n  // Enforce with CSS since width/height attrs don't work on divs\r\n  this.width(this.options_['width'], true); // (true) Skip resize listener on load\r\n  this.height(this.options_['height'], true);\r\n\r\n  // Wrap video tag in div (el/box) container\r\n  if (tag.parentNode) {\r\n    tag.parentNode.insertBefore(el, tag);\r\n  }\r\n  vjs.insertFirst(tag, el); // Breaks iPhone, fixed in HTML5 setup.\r\n\r\n  return el;\r\n};\r\n\r\n// /* Media Technology (tech)\r\n// ================================================================================ */\r\n// Load/Create an instance of playback technlogy including element and API methods\r\n// And append playback element in player div.\r\nvjs.Player.prototype.loadTech = function(techName, source){\r\n\r\n  // Pause and remove current playback technology\r\n  if (this.tech) {\r\n    this.unloadTech();\r\n\r\n  // if this is the first time loading, HTML5 tag will exist but won't be initialized\r\n  // so we need to remove it if we're not loading HTML5\r\n  } else if (techName !== 'Html5' && this.tag) {\r\n    vjs.Html5.disposeMediaElement(this.tag);\r\n    this.tag = null;\r\n  }\r\n\r\n  this.techName = techName;\r\n\r\n  // Turn off API access because we're loading a new tech that might load asynchronously\r\n  this.isReady_ = false;\r\n\r\n  var techReady = function(){\r\n    this.player_.triggerReady();\r\n\r\n    // Manually track progress in cases where the browser/flash player doesn't report it.\r\n    if (!this.features['progressEvents']) {\r\n      this.player_.manualProgressOn();\r\n    }\r\n\r\n    // Manually track timeudpates in cases where the browser/flash player doesn't report it.\r\n    if (!this.features['timeupdateEvents']) {\r\n      this.player_.manualTimeUpdatesOn();\r\n    }\r\n  };\r\n\r\n  // Grab tech-specific options from player options and add source and parent element to use.\r\n  var techOptions = vjs.obj.merge({ 'source': source, 'parentEl': this.el_ }, this.options_[techName.toLowerCase()]);\r\n\r\n  if (source) {\r\n    if (source.src == this.cache_.src && this.cache_.currentTime > 0) {\r\n      techOptions['startTime'] = this.cache_.currentTime;\r\n    }\r\n\r\n    this.cache_.src = source.src;\r\n  }\r\n\r\n  // Initialize tech instance\r\n  this.tech = new window['videojs'][techName](this, techOptions);\r\n\r\n  this.tech.ready(techReady);\r\n};\r\n\r\nvjs.Player.prototype.unloadTech = function(){\r\n  this.isReady_ = false;\r\n  this.tech.dispose();\r\n\r\n  // Turn off any manual progress or timeupdate tracking\r\n  if (this.manualProgress) { this.manualProgressOff(); }\r\n\r\n  if (this.manualTimeUpdates) { this.manualTimeUpdatesOff(); }\r\n\r\n  this.tech = false;\r\n};\r\n\r\n// There's many issues around changing the size of a Flash (or other plugin) object.\r\n// First is a plugin reload issue in Firefox that has been around for 11 years: https://bugzilla.mozilla.org/show_bug.cgi?id=90268\r\n// Then with the new fullscreen API, Mozilla and webkit browsers will reload the flash object after going to fullscreen.\r\n// To get around this, we're unloading the tech, caching source and currentTime values, and reloading the tech once the plugin is resized.\r\n// reloadTech: function(betweenFn){\r\n//   vjs.log('unloadingTech')\r\n//   this.unloadTech();\r\n//   vjs.log('unloadedTech')\r\n//   if (betweenFn) { betweenFn.call(); }\r\n//   vjs.log('LoadingTech')\r\n//   this.loadTech(this.techName, { src: this.cache_.src })\r\n//   vjs.log('loadedTech')\r\n// },\r\n\r\n/* Fallbacks for unsupported event types\r\n================================================================================ */\r\n// Manually trigger progress events based on changes to the buffered amount\r\n// Many flash players and older HTML5 browsers don't send progress or progress-like events\r\nvjs.Player.prototype.manualProgressOn = function(){\r\n  this.manualProgress = true;\r\n\r\n  // Trigger progress watching when a source begins loading\r\n  this.trackProgress();\r\n\r\n  // Watch for a native progress event call on the tech element\r\n  // In HTML5, some older versions don't support the progress event\r\n  // So we're assuming they don't, and turning off manual progress if they do.\r\n  // As opposed to doing user agent detection\r\n  this.tech.one('progress', function(){\r\n\r\n    // Update known progress support for this playback technology\r\n    this.features['progressEvents'] = true;\r\n\r\n    // Turn off manual progress tracking\r\n    this.player_.manualProgressOff();\r\n  });\r\n};\r\n\r\nvjs.Player.prototype.manualProgressOff = function(){\r\n  this.manualProgress = false;\r\n  this.stopTrackingProgress();\r\n};\r\n\r\nvjs.Player.prototype.trackProgress = function(){\r\n\r\n  this.progressInterval = setInterval(vjs.bind(this, function(){\r\n    // Don't trigger unless buffered amount is greater than last time\r\n    // log(this.cache_.bufferEnd, this.buffered().end(0), this.duration())\r\n    /* TODO: update for multiple buffered regions */\r\n    if (this.cache_.bufferEnd < this.buffered().end(0)) {\r\n      this.trigger('progress');\r\n    } else if (this.bufferedPercent() == 1) {\r\n      this.stopTrackingProgress();\r\n      this.trigger('progress'); // Last update\r\n    }\r\n  }), 500);\r\n};\r\nvjs.Player.prototype.stopTrackingProgress = function(){ clearInterval(this.progressInterval); };\r\n\r\n/*! Time Tracking -------------------------------------------------------------- */\r\nvjs.Player.prototype.manualTimeUpdatesOn = function(){\r\n  this.manualTimeUpdates = true;\r\n\r\n  this.on('play', this.trackCurrentTime);\r\n  this.on('pause', this.stopTrackingCurrentTime);\r\n  // timeupdate is also called by .currentTime whenever current time is set\r\n\r\n  // Watch for native timeupdate event\r\n  this.tech.one('timeupdate', function(){\r\n    // Update known progress support for this playback technology\r\n    this.features['timeupdateEvents'] = true;\r\n    // Turn off manual progress tracking\r\n    this.player_.manualTimeUpdatesOff();\r\n  });\r\n};\r\n\r\nvjs.Player.prototype.manualTimeUpdatesOff = function(){\r\n  this.manualTimeUpdates = false;\r\n  this.stopTrackingCurrentTime();\r\n  this.off('play', this.trackCurrentTime);\r\n  this.off('pause', this.stopTrackingCurrentTime);\r\n};\r\n\r\nvjs.Player.prototype.trackCurrentTime = function(){\r\n  if (this.currentTimeInterval) { this.stopTrackingCurrentTime(); }\r\n  this.currentTimeInterval = setInterval(vjs.bind(this, function(){\r\n    this.trigger('timeupdate');\r\n  }), 250); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15\r\n};\r\n\r\n// Turn off play progress tracking (when paused or dragging)\r\nvjs.Player.prototype.stopTrackingCurrentTime = function(){ clearInterval(this.currentTimeInterval); };\r\n\r\n// /* Player event handlers (how the player reacts to certain events)\r\n// ================================================================================ */\r\n\r\n/**\r\n * Fired when the user agent begins looking for media data\r\n * @event loadstart\r\n */\r\nvjs.Player.prototype.onLoadStart;\r\n\r\n/**\r\n * Fired when the player has initial duration and dimension information\r\n * @event loadedmetadata\r\n */\r\nvjs.Player.prototype.onLoadedMetaData;\r\n\r\n/**\r\n * Fired when the player has downloaded data at the current playback position\r\n * @event loadeddata\r\n */\r\nvjs.Player.prototype.onLoadedData;\r\n\r\n/**\r\n * Fired when the player has finished downloading the source data\r\n * @event loadedalldata\r\n */\r\nvjs.Player.prototype.onLoadedAllData;\r\n\r\n/**\r\n * Fired whenever the media begins or resumes playback\r\n * @event play\r\n */\r\nvjs.Player.prototype.onPlay = function(){\r\n  vjs.removeClass(this.el_, 'vjs-paused');\r\n  vjs.addClass(this.el_, 'vjs-playing');\r\n};\r\n\r\n/**\r\n * Fired the first time a video is played\r\n *\r\n * Not part of the HLS spec, and we're not sure if this is the best\r\n * implementation yet, so use sparingly. If you don't have a reason to\r\n * prevent playback, use `myPlayer.one('play');` instead.\r\n *\r\n * @event firstplay\r\n */\r\nvjs.Player.prototype.onFirstPlay = function(){\r\n    //If the first starttime attribute is specified\r\n    //then we will start at the given offset in seconds\r\n    if(this.options_['starttime']){\r\n      this.currentTime(this.options_['starttime']);\r\n    }\r\n\r\n    this.addClass('vjs-has-started');\r\n};\r\n\r\n/**\r\n * Fired whenever the media has been paused\r\n * @event pause\r\n */\r\nvjs.Player.prototype.onPause = function(){\r\n  vjs.removeClass(this.el_, 'vjs-playing');\r\n  vjs.addClass(this.el_, 'vjs-paused');\r\n};\r\n\r\n/**\r\n * Fired when the current playback position has changed\r\n *\r\n * During playback this is fired every 15-250 milliseconds, depnding on the\r\n * playback technology in use.\r\n * @event timeupdate\r\n */\r\nvjs.Player.prototype.onTimeUpdate;\r\n\r\n/**\r\n * Fired while the user agent is downloading media data\r\n * @event progress\r\n */\r\nvjs.Player.prototype.onProgress = function(){\r\n  // Add custom event for when source is finished downloading.\r\n  if (this.bufferedPercent() == 1) {\r\n    this.trigger('loadedalldata');\r\n  }\r\n};\r\n\r\n/**\r\n * Fired when the end of the media resource is reached (currentTime == duration)\r\n * @event ended\r\n */\r\nvjs.Player.prototype.onEnded = function(){\r\n  if (this.options_['loop']) {\r\n    this.currentTime(0);\r\n    this.play();\r\n  }\r\n};\r\n\r\n/**\r\n * Fired when the duration of the media resource is first known or changed\r\n * @event durationchange\r\n */\r\nvjs.Player.prototype.onDurationChange = function(){\r\n  // Allows for cacheing value instead of asking player each time.\r\n  this.duration(this.techGet('duration'));\r\n};\r\n\r\n/**\r\n * Fired when the volume changes\r\n * @event volumechange\r\n */\r\nvjs.Player.prototype.onVolumeChange;\r\n\r\n/**\r\n * Fired when the player switches in or out of fullscreen mode\r\n * @event fullscreenchange\r\n */\r\nvjs.Player.prototype.onFullscreenChange = function() {\r\n  if (this.isFullScreen) {\r\n    this.addClass('vjs-fullscreen');\r\n  } else {\r\n    this.removeClass('vjs-fullscreen');\r\n  }\r\n};\r\n\r\n/**\r\n * Fired when there is an error in playback\r\n * @event error\r\n */\r\nvjs.Player.prototype.onError = function(e) {\r\n  vjs.log('Video Error', e);\r\n};\r\n\r\n// /* Player API\r\n// ================================================================================ */\r\n\r\n/**\r\n * Object for cached values.\r\n * @private\r\n */\r\nvjs.Player.prototype.cache_;\r\n\r\nvjs.Player.prototype.getCache = function(){\r\n  return this.cache_;\r\n};\r\n\r\n// Pass values to the playback tech\r\nvjs.Player.prototype.techCall = function(method, arg){\r\n  // If it's not ready yet, call method when it is\r\n  if (this.tech && !this.tech.isReady_) {\r\n    this.tech.ready(function(){\r\n      this[method](arg);\r\n    });\r\n\r\n  // Otherwise call method now\r\n  } else {\r\n    try {\r\n      this.tech[method](arg);\r\n    } catch(e) {\r\n      vjs.log(e);\r\n      throw e;\r\n    }\r\n  }\r\n};\r\n\r\n// Get calls can't wait for the tech, and sometimes don't need to.\r\nvjs.Player.prototype.techGet = function(method){\r\n\r\n  if (this.tech && this.tech.isReady_) {\r\n\r\n    // Flash likes to die and reload when you hide or reposition it.\r\n    // In these cases the object methods go away and we get errors.\r\n    // When that happens we'll catch the errors and inform tech that it's not ready any more.\r\n    try {\r\n      return this.tech[method]();\r\n    } catch(e) {\r\n      // When building additional tech libs, an expected method may not be defined yet\r\n      if (this.tech[method] === undefined) {\r\n        vjs.log('Video.js: ' + method + ' method not defined for '+this.techName+' playback technology.', e);\r\n      } else {\r\n        // When a method isn't available on the object it throws a TypeError\r\n        if (e.name == 'TypeError') {\r\n          vjs.log('Video.js: ' + method + ' unavailable on '+this.techName+' playback technology element.', e);\r\n          this.tech.isReady_ = false;\r\n        } else {\r\n          vjs.log(e);\r\n        }\r\n      }\r\n      throw e;\r\n    }\r\n  }\r\n\r\n  return;\r\n};\r\n\r\n/**\r\n * start media playback\r\n *\r\n *     myPlayer.play();\r\n *\r\n * @return {vjs.Player} self\r\n */\r\nvjs.Player.prototype.play = function(){\r\n  this.techCall('play');\r\n  return this;\r\n};\r\n\r\n/**\r\n * Pause the video playback\r\n *\r\n *     myPlayer.pause();\r\n *\r\n * @return {vjs.Player} self\r\n */\r\nvjs.Player.prototype.pause = function(){\r\n  this.techCall('pause');\r\n  return this;\r\n};\r\n\r\n/**\r\n * Check if the player is paused\r\n *\r\n *     var isPaused = myPlayer.paused();\r\n *     var isPlaying = !myPlayer.paused();\r\n *\r\n * @return {Boolean} false if the media is currently playing, or true otherwise\r\n */\r\nvjs.Player.prototype.paused = function(){\r\n  // The initial state of paused should be true (in Safari it's actually false)\r\n  return (this.techGet('paused') === false) ? false : true;\r\n};\r\n\r\n/**\r\n * Get or set the current time (in seconds)\r\n *\r\n *     // get\r\n *     var whereYouAt = myPlayer.currentTime();\r\n *\r\n *     // set\r\n *     myPlayer.currentTime(120); // 2 minutes into the video\r\n *\r\n * @param  {Number|String=} seconds The time to seek to\r\n * @return {Number}        The time in seconds, when not setting\r\n * @return {vjs.Player}    self, when the current time is set\r\n */\r\nvjs.Player.prototype.currentTime = function(seconds){\r\n  if (seconds !== undefined) {\r\n\r\n    // cache the last set value for smoother scrubbing\r\n    this.cache_.lastSetCurrentTime = seconds;\r\n\r\n    this.techCall('setCurrentTime', seconds);\r\n\r\n    // improve the accuracy of manual timeupdates\r\n    if (this.manualTimeUpdates) { this.trigger('timeupdate'); }\r\n\r\n    return this;\r\n  }\r\n\r\n  // cache last currentTime and return\r\n  // default to 0 seconds\r\n  return this.cache_.currentTime = (this.techGet('currentTime') || 0);\r\n};\r\n\r\n/**\r\n * Get the length in time of the video in seconds\r\n *\r\n *     var lengthOfVideo = myPlayer.duration();\r\n *\r\n * **NOTE**: The video must have started loading before the duration can be\r\n * known, and in the case of Flash, may not be known until the video starts\r\n * playing.\r\n *\r\n * @return {Number} The duration of the video in seconds\r\n */\r\nvjs.Player.prototype.duration = function(seconds){\r\n  if (seconds !== undefined) {\r\n\r\n    // cache the last set value for optimiized scrubbing (esp. Flash)\r\n    this.cache_.duration = parseFloat(seconds);\r\n\r\n    return this;\r\n  }\r\n\r\n  if (this.cache_.duration === undefined) {\r\n    this.onDurationChange();\r\n  }\r\n\r\n  return this.cache_.duration;\r\n};\r\n\r\n// Calculates how much time is left. Not in spec, but useful.\r\nvjs.Player.prototype.remainingTime = function(){\r\n  return this.duration() - this.currentTime();\r\n};\r\n\r\n// http://dev.w3.org/html5/spec/video.html#dom-media-buffered\r\n// Buffered returns a timerange object.\r\n// Kind of like an array of portions of the video that have been downloaded.\r\n// So far no browsers return more than one range (portion)\r\n\r\n/**\r\n * Get a TimeRange object with the times of the video that have been downloaded\r\n *\r\n * If you just want the percent of the video that's been downloaded,\r\n * use bufferedPercent.\r\n *\r\n *     // Number of different ranges of time have been buffered. Usually 1.\r\n *     numberOfRanges = bufferedTimeRange.length,\r\n *\r\n *     // Time in seconds when the first range starts. Usually 0.\r\n *     firstRangeStart = bufferedTimeRange.start(0),\r\n *\r\n *     // Time in seconds when the first range ends\r\n *     firstRangeEnd = bufferedTimeRange.end(0),\r\n *\r\n *     // Length in seconds of the first time range\r\n *     firstRangeLength = firstRangeEnd - firstRangeStart;\r\n *\r\n * @return {Object} A mock TimeRange object (following HTML spec)\r\n */\r\nvjs.Player.prototype.buffered = function(){\r\n  var buffered = this.techGet('buffered'),\r\n      start = 0,\r\n      buflast = buffered.length - 1,\r\n      // Default end to 0 and store in values\r\n      end = this.cache_.bufferEnd = this.cache_.bufferEnd || 0;\r\n\r\n  if (buffered && buflast >= 0 && buffered.end(buflast) !== end) {\r\n    end = buffered.end(buflast);\r\n    // Storing values allows them be overridden by setBufferedFromProgress\r\n    this.cache_.bufferEnd = end;\r\n  }\r\n\r\n  return vjs.createTimeRange(start, end);\r\n};\r\n\r\n/**\r\n * Get the percent (as a decimal) of the video that's been downloaded\r\n *\r\n *     var howMuchIsDownloaded = myPlayer.bufferedPercent();\r\n *\r\n * 0 means none, 1 means all.\r\n * (This method isn't in the HTML5 spec, but it's very convenient)\r\n *\r\n * @return {Number} A decimal between 0 and 1 representing the percent\r\n */\r\nvjs.Player.prototype.bufferedPercent = function(){\r\n  return (this.duration()) ? this.buffered().end(0) / this.duration() : 0;\r\n};\r\n\r\n/**\r\n * Get or set the current volume of the media\r\n *\r\n *     // get\r\n *     var howLoudIsIt = myPlayer.volume();\r\n *\r\n *     // set\r\n *     myPlayer.volume(0.5); // Set volume to half\r\n *\r\n * 0 is off (muted), 1.0 is all the way up, 0.5 is half way.\r\n *\r\n * @param  {Number} percentAsDecimal The new volume as a decimal percent\r\n * @return {Number}                  The current volume, when getting\r\n * @return {vjs.Player}              self, when setting\r\n */\r\nvjs.Player.prototype.volume = function(percentAsDecimal){\r\n  var vol;\r\n\r\n  if (percentAsDecimal !== undefined) {\r\n    vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal))); // Force value to between 0 and 1\r\n    this.cache_.volume = vol;\r\n    this.techCall('setVolume', vol);\r\n    vjs.setLocalStorage('volume', vol);\r\n    return this;\r\n  }\r\n\r\n  // Default to 1 when returning current volume.\r\n  vol = parseFloat(this.techGet('volume'));\r\n  return (isNaN(vol)) ? 1 : vol;\r\n};\r\n\r\n\r\n/**\r\n * Get the current muted state, or turn mute on or off\r\n *\r\n *     // get\r\n *     var isVolumeMuted = myPlayer.muted();\r\n *\r\n *     // set\r\n *     myPlayer.muted(true); // mute the volume\r\n *\r\n * @param  {Boolean=} muted True to mute, false to unmute\r\n * @return {Boolean} True if mute is on, false if not, when getting\r\n * @return {vjs.Player} self, when setting mute\r\n */\r\nvjs.Player.prototype.muted = function(muted){\r\n  if (muted !== undefined) {\r\n    this.techCall('setMuted', muted);\r\n    return this;\r\n  }\r\n  return this.techGet('muted') || false; // Default to false\r\n};\r\n\r\n// Check if current tech can support native fullscreen (e.g. with built in controls lik iOS, so not our flash swf)\r\nvjs.Player.prototype.supportsFullScreen = function(){ return this.techGet('supportsFullScreen') || false; };\r\n\r\n/**\r\n * Increase the size of the video to full screen\r\n *\r\n *     myPlayer.requestFullScreen();\r\n *\r\n * In some browsers, full screen is not supported natively, so it enters\r\n * \"full window mode\", where the video fills the browser window.\r\n * In browsers and devices that support native full screen, sometimes the\r\n * browser's default controls will be shown, and not the Video.js custom skin.\r\n * This includes most mobile devices (iOS, Android) and older versions of\r\n * Safari.\r\n *\r\n * @return {vjs.Player} self\r\n */\r\nvjs.Player.prototype.requestFullScreen = function(){\r\n  var requestFullScreen = vjs.support.requestFullScreen;\r\n  this.isFullScreen = true;\r\n\r\n  if (requestFullScreen) {\r\n    // the browser supports going fullscreen at the element level so we can\r\n    // take the controls fullscreen as well as the video\r\n\r\n    // Trigger fullscreenchange event after change\r\n    // We have to specifically add this each time, and remove\r\n    // when cancelling fullscreen. Otherwise if there's multiple\r\n    // players on a page, they would all be reacting to the same fullscreen\r\n    // events\r\n    vjs.on(document, requestFullScreen.eventName, vjs.bind(this, function(e){\r\n      this.isFullScreen = document[requestFullScreen.isFullScreen];\r\n\r\n      // If cancelling fullscreen, remove event listener.\r\n      if (this.isFullScreen === false) {\r\n        vjs.off(document, requestFullScreen.eventName, arguments.callee);\r\n      }\r\n\r\n      this.trigger('fullscreenchange');\r\n    }));\r\n\r\n    this.el_[requestFullScreen.requestFn]();\r\n\r\n  } else if (this.tech.supportsFullScreen()) {\r\n    // we can't take the video.js controls fullscreen but we can go fullscreen\r\n    // with native controls\r\n    this.techCall('enterFullScreen');\r\n  } else {\r\n    // fullscreen isn't supported so we'll just stretch the video element to\r\n    // fill the viewport\r\n    this.enterFullWindow();\r\n    this.trigger('fullscreenchange');\r\n  }\r\n\r\n  return this;\r\n};\r\n\r\n/**\r\n * Return the video to its normal size after having been in full screen mode\r\n *\r\n *     myPlayer.cancelFullScreen();\r\n *\r\n * @return {vjs.Player} self\r\n */\r\nvjs.Player.prototype.cancelFullScreen = function(){\r\n  var requestFullScreen = vjs.support.requestFullScreen;\r\n  this.isFullScreen = false;\r\n\r\n  // Check for browser element fullscreen support\r\n  if (requestFullScreen) {\r\n    document[requestFullScreen.cancelFn]();\r\n  } else if (this.tech.supportsFullScreen()) {\r\n   this.techCall('exitFullScreen');\r\n  } else {\r\n   this.exitFullWindow();\r\n   this.trigger('fullscreenchange');\r\n  }\r\n\r\n  return this;\r\n};\r\n\r\n// When fullscreen isn't supported we can stretch the video container to as wide as the browser will let us.\r\nvjs.Player.prototype.enterFullWindow = function(){\r\n  this.isFullWindow = true;\r\n\r\n  // Storing original doc overflow value to return to when fullscreen is off\r\n  this.docOrigOverflow = document.documentElement.style.overflow;\r\n\r\n  // Add listener for esc key to exit fullscreen\r\n  vjs.on(document, 'keydown', vjs.bind(this, this.fullWindowOnEscKey));\r\n\r\n  // Hide any scroll bars\r\n  document.documentElement.style.overflow = 'hidden';\r\n\r\n  // Apply fullscreen styles\r\n  vjs.addClass(document.body, 'vjs-full-window');\r\n\r\n  this.trigger('enterFullWindow');\r\n};\r\nvjs.Player.prototype.fullWindowOnEscKey = function(event){\r\n  if (event.keyCode === 27) {\r\n    if (this.isFullScreen === true) {\r\n      this.cancelFullScreen();\r\n    } else {\r\n      this.exitFullWindow();\r\n    }\r\n  }\r\n};\r\n\r\nvjs.Player.prototype.exitFullWindow = function(){\r\n  this.isFullWindow = false;\r\n  vjs.off(document, 'keydown', this.fullWindowOnEscKey);\r\n\r\n  // Unhide scroll bars.\r\n  document.documentElement.style.overflow = this.docOrigOverflow;\r\n\r\n  // Remove fullscreen styles\r\n  vjs.removeClass(document.body, 'vjs-full-window');\r\n\r\n  // Resize the box, controller, and poster to original sizes\r\n  // this.positionAll();\r\n  this.trigger('exitFullWindow');\r\n};\r\n\r\nvjs.Player.prototype.selectSource = function(sources){\r\n\r\n  // Loop through each playback technology in the options order\r\n  for (var i=0,j=this.options_['techOrder'];i<j.length;i++) {\r\n    var techName = vjs.capitalize(j[i]),\r\n        tech = window['videojs'][techName];\r\n\r\n    // Check if the browser supports this technology\r\n    if (tech.isSupported()) {\r\n      // Loop through each source object\r\n      for (var a=0,b=sources;a<b.length;a++) {\r\n        var source = b[a];\r\n\r\n        // Check if source can be played with this technology\r\n        if (tech['canPlaySource'](source)) {\r\n          return { source: source, tech: techName };\r\n        }\r\n      }\r\n    }\r\n  }\r\n\r\n  return false;\r\n};\r\n\r\n/**\r\n * The source function updates the video source\r\n *\r\n * There are three types of variables you can pass as the argument.\r\n *\r\n * **URL String**: A URL to the the video file. Use this method if you are sure\r\n * the current playback technology (HTML5/Flash) can support the source you\r\n * provide. Currently only MP4 files can be used in both HTML5 and Flash.\r\n *\r\n *     myPlayer.src(\"http://www.example.com/path/to/video.mp4\");\r\n *\r\n * **Source Object (or element):** A javascript object containing information\r\n * about the source file. Use this method if you want the player to determine if\r\n * it can support the file using the type information.\r\n *\r\n *     myPlayer.src({ type: \"video/mp4\", src: \"http://www.example.com/path/to/video.mp4\" });\r\n *\r\n * **Array of Source Objects:** To provide multiple versions of the source so\r\n * that it can be played using HTML5 across browsers you can use an array of\r\n * source objects. Video.js will detect which version is supported and load that\r\n * file.\r\n *\r\n *     myPlayer.src([\r\n *       { type: \"video/mp4\", src: \"http://www.example.com/path/to/video.mp4\" },\r\n *       { type: \"video/webm\", src: \"http://www.example.com/path/to/video.webm\" },\r\n *       { type: \"video/ogg\", src: \"http://www.example.com/path/to/video.ogv\" }\r\n *     ]);\r\n *\r\n * @param  {String|Object|Array=} source The source URL, object, or array of sources\r\n * @return {vjs.Player} self\r\n */\r\nvjs.Player.prototype.src = function(source){\r\n  // Case: Array of source objects to choose from and pick the best to play\r\n  if (source instanceof Array) {\r\n\r\n    var sourceTech = this.selectSource(source),\r\n        techName;\r\n\r\n    if (sourceTech) {\r\n        source = sourceTech.source;\r\n        techName = sourceTech.tech;\r\n\r\n      // If this technology is already loaded, set source\r\n      if (techName == this.techName) {\r\n        this.src(source); // Passing the source object\r\n      // Otherwise load this technology with chosen source\r\n      } else {\r\n        this.loadTech(techName, source);\r\n      }\r\n    } else {\r\n      this.el_.appendChild(vjs.createEl('p', {\r\n        innerHTML: this.options()['notSupportedMessage']\r\n      }));\r\n    }\r\n\r\n  // Case: Source object { src: '', type: '' ... }\r\n  } else if (source instanceof Object) {\r\n\r\n    if (window['videojs'][this.techName]['canPlaySource'](source)) {\r\n      this.src(source.src);\r\n    } else {\r\n      // Send through tech loop to check for a compatible technology.\r\n      this.src([source]);\r\n    }\r\n\r\n  // Case: URL String (http://myvideo...)\r\n  } else {\r\n    // Cache for getting last set source\r\n    this.cache_.src = source;\r\n\r\n    if (!this.isReady_) {\r\n      this.ready(function(){\r\n        this.src(source);\r\n      });\r\n    } else {\r\n      this.techCall('src', source);\r\n      if (this.options_['preload'] == 'auto') {\r\n        this.load();\r\n      }\r\n      if (this.options_['autoplay']) {\r\n        this.play();\r\n      }\r\n    }\r\n  }\r\n  return this;\r\n};\r\n\r\n// Begin loading the src data\r\n// http://dev.w3.org/html5/spec/video.html#dom-media-load\r\nvjs.Player.prototype.load = function(){\r\n  this.techCall('load');\r\n  return this;\r\n};\r\n\r\n// http://dev.w3.org/html5/spec/video.html#dom-media-currentsrc\r\nvjs.Player.prototype.currentSrc = function(){\r\n  return this.techGet('currentSrc') || this.cache_.src || '';\r\n};\r\n\r\n// Attributes/Options\r\nvjs.Player.prototype.preload = function(value){\r\n  if (value !== undefined) {\r\n    this.techCall('setPreload', value);\r\n    this.options_['preload'] = value;\r\n    return this;\r\n  }\r\n  return this.techGet('preload');\r\n};\r\nvjs.Player.prototype.autoplay = function(value){\r\n  if (value !== undefined) {\r\n    this.techCall('setAutoplay', value);\r\n    this.options_['autoplay'] = value;\r\n    return this;\r\n  }\r\n  return this.techGet('autoplay', value);\r\n};\r\nvjs.Player.prototype.loop = function(value){\r\n  if (value !== undefined) {\r\n    this.techCall('setLoop', value);\r\n    this.options_['loop'] = value;\r\n    return this;\r\n  }\r\n  return this.techGet('loop');\r\n};\r\n\r\n/**\r\n * the url of the poster image source\r\n * @type {String}\r\n * @private\r\n */\r\nvjs.Player.prototype.poster_;\r\n\r\n/**\r\n * get or set the poster image source url\r\n *\r\n * ##### EXAMPLE:\r\n *\r\n *     // getting\r\n *     var currentPoster = myPlayer.poster();\r\n *\r\n *     // setting\r\n *     myPlayer.poster('http://example.com/myImage.jpg');\r\n *\r\n * @param  {String=} [src] Poster image source URL\r\n * @return {String} poster URL when getting\r\n * @return {vjs.Player} self when setting\r\n */\r\nvjs.Player.prototype.poster = function(src){\r\n  if (src !== undefined) {\r\n    this.poster_ = src;\r\n    return this;\r\n  }\r\n  return this.poster_;\r\n};\r\n\r\n/**\r\n * Whether or not the controls are showing\r\n * @type {Boolean}\r\n * @private\r\n */\r\nvjs.Player.prototype.controls_;\r\n\r\n/**\r\n * Get or set whether or not the controls are showing.\r\n * @param  {Boolean} controls Set controls to showing or not\r\n * @return {Boolean}    Controls are showing\r\n */\r\nvjs.Player.prototype.controls = function(bool){\r\n  if (bool !== undefined) {\r\n    bool = !!bool; // force boolean\r\n    // Don't trigger a change event unless it actually changed\r\n    if (this.controls_ !== bool) {\r\n      this.controls_ = bool;\r\n      if (bool) {\r\n        this.removeClass('vjs-controls-disabled');\r\n        this.addClass('vjs-controls-enabled');\r\n        this.trigger('controlsenabled');\r\n      } else {\r\n        this.removeClass('vjs-controls-enabled');\r\n        this.addClass('vjs-controls-disabled');\r\n        this.trigger('controlsdisabled');\r\n      }\r\n    }\r\n    return this;\r\n  }\r\n  return this.controls_;\r\n};\r\n\r\nvjs.Player.prototype.usingNativeControls_;\r\n\r\n/**\r\n * Toggle native controls on/off. Native controls are the controls built into\r\n * devices (e.g. default iPhone controls), Flash, or other techs\r\n * (e.g. Vimeo Controls)\r\n *\r\n * **This should only be set by the current tech, because only the tech knows\r\n * if it can support native controls**\r\n *\r\n * @param  {Boolean} bool    True signals that native controls are on\r\n * @return {vjs.Player}      Returns the player\r\n * @private\r\n */\r\nvjs.Player.prototype.usingNativeControls = function(bool){\r\n  if (bool !== undefined) {\r\n    bool = !!bool; // force boolean\r\n    // Don't trigger a change event unless it actually changed\r\n    if (this.usingNativeControls_ !== bool) {\r\n      this.usingNativeControls_ = bool;\r\n      if (bool) {\r\n        this.addClass('vjs-using-native-controls');\r\n\r\n        /**\r\n         * player is using the native device controls\r\n         *\r\n         * @event usingnativecontrols\r\n         * @memberof vjs.Player\r\n         * @instance\r\n         * @private\r\n         */\r\n        this.trigger('usingnativecontrols');\r\n      } else {\r\n        this.removeClass('vjs-using-native-controls');\r\n\r\n        /**\r\n         * player is using the custom HTML controls\r\n         *\r\n         * @event usingcustomcontrols\r\n         * @memberof vjs.Player\r\n         * @instance\r\n         * @private\r\n         */\r\n        this.trigger('usingcustomcontrols');\r\n      }\r\n    }\r\n    return this;\r\n  }\r\n  return this.usingNativeControls_;\r\n};\r\n\r\nvjs.Player.prototype.error = function(){ return this.techGet('error'); };\r\nvjs.Player.prototype.ended = function(){ return this.techGet('ended'); };\r\nvjs.Player.prototype.seeking = function(){ return this.techGet('seeking'); };\r\n\r\n// When the player is first initialized, trigger activity so components\r\n// like the control bar show themselves if needed\r\nvjs.Player.prototype.userActivity_ = true;\r\nvjs.Player.prototype.reportUserActivity = function(event){\r\n  this.userActivity_ = true;\r\n};\r\n\r\nvjs.Player.prototype.userActive_ = true;\r\nvjs.Player.prototype.userActive = function(bool){\r\n  if (bool !== undefined) {\r\n    bool = !!bool;\r\n    if (bool !== this.userActive_) {\r\n      this.userActive_ = bool;\r\n      if (bool) {\r\n        // If the user was inactive and is now active we want to reset the\r\n        // inactivity timer\r\n        this.userActivity_ = true;\r\n        this.removeClass('vjs-user-inactive');\r\n        this.addClass('vjs-user-active');\r\n        this.trigger('useractive');\r\n      } else {\r\n        // We're switching the state to inactive manually, so erase any other\r\n        // activity\r\n        this.userActivity_ = false;\r\n\r\n        // Chrome/Safari/IE have bugs where when you change the cursor it can\r\n        // trigger a mousemove event. This causes an issue when you're hiding\r\n        // the cursor when the user is inactive, and a mousemove signals user\r\n        // activity. Making it impossible to go into inactive mode. Specifically\r\n        // this happens in fullscreen when we really need to hide the cursor.\r\n        //\r\n        // When this gets resolved in ALL browsers it can be removed\r\n        // https://code.google.com/p/chromium/issues/detail?id=103041\r\n        this.tech.one('mousemove', function(e){\r\n          e.stopPropagation();\r\n          e.preventDefault();\r\n        });\r\n        this.removeClass('vjs-user-active');\r\n        this.addClass('vjs-user-inactive');\r\n        this.trigger('userinactive');\r\n      }\r\n    }\r\n    return this;\r\n  }\r\n  return this.userActive_;\r\n};\r\n\r\nvjs.Player.prototype.listenForUserActivity = function(){\r\n  var onMouseActivity, onMouseDown, mouseInProgress, onMouseUp,\r\n      activityCheck, inactivityTimeout;\r\n\r\n  onMouseActivity = this.reportUserActivity;\r\n\r\n  onMouseDown = function() {\r\n    onMouseActivity();\r\n    // For as long as the they are touching the device or have their mouse down,\r\n    // we consider them active even if they're not moving their finger or mouse.\r\n    // So we want to continue to update that they are active\r\n    clearInterval(mouseInProgress);\r\n    // Setting userActivity=true now and setting the interval to the same time\r\n    // as the activityCheck interval (250) should ensure we never miss the\r\n    // next activityCheck\r\n    mouseInProgress = setInterval(vjs.bind(this, onMouseActivity), 250);\r\n  };\r\n\r\n  onMouseUp = function(event) {\r\n    onMouseActivity();\r\n    // Stop the interval that maintains activity if the mouse/touch is down\r\n    clearInterval(mouseInProgress);\r\n  };\r\n\r\n  // Any mouse movement will be considered user activity\r\n  this.on('mousedown', onMouseDown);\r\n  this.on('mousemove', onMouseActivity);\r\n  this.on('mouseup', onMouseUp);\r\n\r\n  // Listen for keyboard navigation\r\n  // Shouldn't need to use inProgress interval because of key repeat\r\n  this.on('keydown', onMouseActivity);\r\n  this.on('keyup', onMouseActivity);\r\n\r\n  // Consider any touch events that bubble up to be activity\r\n  // Certain touches on the tech will be blocked from bubbling because they\r\n  // toggle controls\r\n  this.on('touchstart', onMouseDown);\r\n  this.on('touchmove', onMouseActivity);\r\n  this.on('touchend', onMouseUp);\r\n  this.on('touchcancel', onMouseUp);\r\n\r\n  // Run an interval every 250 milliseconds instead of stuffing everything into\r\n  // the mousemove/touchmove function itself, to prevent performance degradation.\r\n  // `this.reportUserActivity` simply sets this.userActivity_ to true, which\r\n  // then gets picked up by this loop\r\n  // http://ejohn.org/blog/learning-from-twitter/\r\n  activityCheck = setInterval(vjs.bind(this, function() {\r\n    // Check to see if mouse/touch activity has happened\r\n    if (this.userActivity_) {\r\n      // Reset the activity tracker\r\n      this.userActivity_ = false;\r\n\r\n      // If the user state was inactive, set the state to active\r\n      this.userActive(true);\r\n\r\n      // Clear any existing inactivity timeout to start the timer over\r\n      clearTimeout(inactivityTimeout);\r\n\r\n      // In X seconds, if no more activity has occurred the user will be\r\n      // considered inactive\r\n      inactivityTimeout = setTimeout(vjs.bind(this, function() {\r\n        // Protect against the case where the inactivityTimeout can trigger just\r\n        // before the next user activity is picked up by the activityCheck loop\r\n        // causing a flicker\r\n        if (!this.userActivity_) {\r\n          this.userActive(false);\r\n        }\r\n      }), 2000);\r\n    }\r\n  }), 250);\r\n\r\n  // Clean up the intervals when we kill the player\r\n  this.on('dispose', function(){\r\n    clearInterval(activityCheck);\r\n    clearTimeout(inactivityTimeout);\r\n  });\r\n};\r\n\r\n// Methods to add support for\r\n// networkState: function(){ return this.techCall('networkState'); },\r\n// readyState: function(){ return this.techCall('readyState'); },\r\n// seeking: function(){ return this.techCall('seeking'); },\r\n// initialTime: function(){ return this.techCall('initialTime'); },\r\n// startOffsetTime: function(){ return this.techCall('startOffsetTime'); },\r\n// played: function(){ return this.techCall('played'); },\r\n// seekable: function(){ return this.techCall('seekable'); },\r\n// videoTracks: function(){ return this.techCall('videoTracks'); },\r\n// audioTracks: function(){ return this.techCall('audioTracks'); },\r\n// videoWidth: function(){ return this.techCall('videoWidth'); },\r\n// videoHeight: function(){ return this.techCall('videoHeight'); },\r\n// defaultPlaybackRate: function(){ return this.techCall('defaultPlaybackRate'); },\r\n// playbackRate: function(){ return this.techCall('playbackRate'); },\r\n// mediaGroup: function(){ return this.techCall('mediaGroup'); },\r\n// controller: function(){ return this.techCall('controller'); },\r\n// defaultMuted: function(){ return this.techCall('defaultMuted'); }\r\n\r\n// TODO\r\n// currentSrcList: the array of sources including other formats and bitrates\r\n// playList: array of source lists in order of playback\r\n\r\n// RequestFullscreen API\r\n(function(){\r\n  var prefix, requestFS, div;\r\n\r\n  div = document.createElement('div');\r\n\r\n  requestFS = {};\r\n\r\n  // Current W3C Spec\r\n  // http://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html#api\r\n  // Mozilla Draft: https://wiki.mozilla.org/Gecko:FullScreenAPI#fullscreenchange_event\r\n  // New: https://dvcs.w3.org/hg/fullscreen/raw-file/529a67b8d9f3/Overview.html\r\n  if (div.cancelFullscreen !== undefined) {\r\n    requestFS.requestFn = 'requestFullscreen';\r\n    requestFS.cancelFn = 'exitFullscreen';\r\n    requestFS.eventName = 'fullscreenchange';\r\n    requestFS.isFullScreen = 'fullScreen';\r\n\r\n  // Webkit (Chrome/Safari) and Mozilla (Firefox) have working implementations\r\n  // that use prefixes and vary slightly from the new W3C spec. Specifically,\r\n  // using 'exit' instead of 'cancel', and lowercasing the 'S' in Fullscreen.\r\n  // Other browsers don't have any hints of which version they might follow yet,\r\n  // so not going to try to predict by looping through all prefixes.\r\n  } else {\r\n\r\n    if (document.mozCancelFullScreen) {\r\n      prefix = 'moz';\r\n      requestFS.isFullScreen = prefix + 'FullScreen';\r\n    } else {\r\n      prefix = 'webkit';\r\n      requestFS.isFullScreen = prefix + 'IsFullScreen';\r\n    }\r\n\r\n    if (div[prefix + 'RequestFullScreen']) {\r\n      requestFS.requestFn = prefix + 'RequestFullScreen';\r\n      requestFS.cancelFn = prefix + 'CancelFullScreen';\r\n    }\r\n    requestFS.eventName = prefix + 'fullscreenchange';\r\n  }\r\n\r\n  if (document[requestFS.cancelFn]) {\r\n    vjs.support.requestFullScreen = requestFS;\r\n  }\r\n\r\n})();\r\n\r\n\r\n/**\r\n * Container of main controls\r\n * @param {vjs.Player|Object} player\r\n * @param {Object=} options\r\n * @class\r\n * @constructor\r\n * @extends vjs.Component\r\n */\r\nvjs.ControlBar = vjs.Component.extend();\r\n\r\nvjs.ControlBar.prototype.options_ = {\r\n  loadEvent: 'play',\r\n  children: {\r\n    'playToggle': {},\r\n    'currentTimeDisplay': {},\r\n    'timeDivider': {},\r\n    'durationDisplay': {},\r\n    'remainingTimeDisplay': {},\r\n    'progressControl': {},\r\n    'fullscreenToggle': {},\r\n    'volumeControl': {},\r\n    'muteToggle': {}\r\n    // 'volumeMenuButton': {}\r\n  }\r\n};\r\n\r\nvjs.ControlBar.prototype.createEl = function(){\r\n  return vjs.createEl('div', {\r\n    className: 'vjs-control-bar'\r\n  });\r\n};\r\n/**\r\n * Button to toggle between play and pause\r\n * @param {vjs.Player|Object} player\r\n * @param {Object=} options\r\n * @class\r\n * @constructor\r\n */\r\nvjs.PlayToggle = vjs.Button.extend({\r\n  /** @constructor */\r\n  init: function(player, options){\r\n    vjs.Button.call(this, player, options);\r\n\r\n    player.on('play', vjs.bind(this, this.onPlay));\r\n    player.on('pause', vjs.bind(this, this.onPause));\r\n  }\r\n});\r\n\r\nvjs.PlayToggle.prototype.buttonText = 'Play';\r\n\r\nvjs.PlayToggle.prototype.buildCSSClass = function(){\r\n  return 'vjs-play-control ' + vjs.Button.prototype.buildCSSClass.call(this);\r\n};\r\n\r\n// OnClick - Toggle between play and pause\r\nvjs.PlayToggle.prototype.onClick = function(){\r\n  if (this.player_.paused()) {\r\n    this.player_.play();\r\n  } else {\r\n    this.player_.pause();\r\n  }\r\n};\r\n\r\n  // OnPlay - Add the vjs-playing class to the element so it can change appearance\r\nvjs.PlayToggle.prototype.onPlay = function(){\r\n  vjs.removeClass(this.el_, 'vjs-paused');\r\n  vjs.addClass(this.el_, 'vjs-playing');\r\n  this.el_.children[0].children[0].innerHTML = 'Pause'; // change the button text to \"Pause\"\r\n};\r\n\r\n  // OnPause - Add the vjs-paused class to the element so it can change appearance\r\nvjs.PlayToggle.prototype.onPause = function(){\r\n  vjs.removeClass(this.el_, 'vjs-playing');\r\n  vjs.addClass(this.el_, 'vjs-paused');\r\n  this.el_.children[0].children[0].innerHTML = 'Play'; // change the button text to \"Play\"\r\n};\r\n/**\r\n * Displays the current time\r\n * @param {vjs.Player|Object} player\r\n * @param {Object=} options\r\n * @constructor\r\n */\r\nvjs.CurrentTimeDisplay = vjs.Component.extend({\r\n  /** @constructor */\r\n  init: function(player, options){\r\n    vjs.Component.call(this, player, options);\r\n\r\n    player.on('timeupdate', vjs.bind(this, this.updateContent));\r\n  }\r\n});\r\n\r\nvjs.CurrentTimeDisplay.prototype.createEl = function(){\r\n  var el = vjs.Component.prototype.createEl.call(this, 'div', {\r\n    className: 'vjs-current-time vjs-time-controls vjs-control'\r\n  });\r\n\r\n  this.content = vjs.createEl('div', {\r\n    className: 'vjs-current-time-display',\r\n    innerHTML: '<span class=\"vjs-control-text\">Current Time </span>' + '0:00', // label the current time for screen reader users\r\n    'aria-live': 'off' // tell screen readers not to automatically read the time as it changes\r\n  });\r\n\r\n  el.appendChild(vjs.createEl('div').appendChild(this.content));\r\n  return el;\r\n};\r\n\r\nvjs.CurrentTimeDisplay.prototype.updateContent = function(){\r\n  // Allows for smooth scrubbing, when player can't keep up.\r\n  var time = (this.player_.scrubbing) ? this.player_.getCache().currentTime : this.player_.currentTime();\r\n  this.content.innerHTML = '<span class=\"vjs-control-text\">Current Time </span>' + vjs.formatTime(time, this.player_.duration());\r\n};\r\n\r\n/**\r\n * Displays the duration\r\n * @param {vjs.Player|Object} player\r\n * @param {Object=} options\r\n * @constructor\r\n */\r\nvjs.DurationDisplay = vjs.Component.extend({\r\n  /** @constructor */\r\n  init: function(player, options){\r\n    vjs.Component.call(this, player, options);\r\n\r\n    player.on('timeupdate', vjs.bind(this, this.updateContent)); // this might need to be changes to 'durationchange' instead of 'timeupdate' eventually, however the durationchange event fires before this.player_.duration() is set, so the value cannot be written out using this method. Once the order of durationchange and this.player_.duration() being set is figured out, this can be updated.\r\n  }\r\n});\r\n\r\nvjs.DurationDisplay.prototype.createEl = function(){\r\n  var el = vjs.Component.prototype.createEl.call(this, 'div', {\r\n    className: 'vjs-duration vjs-time-controls vjs-control'\r\n  });\r\n\r\n  this.content = vjs.createEl('div', {\r\n    className: 'vjs-duration-display',\r\n    innerHTML: '<span class=\"vjs-control-text\">Duration Time </span>' + '0:00', // label the duration time for screen reader users\r\n    'aria-live': 'off' // tell screen readers not to automatically read the time as it changes\r\n  });\r\n\r\n  el.appendChild(vjs.createEl('div').appendChild(this.content));\r\n  return el;\r\n};\r\n\r\nvjs.DurationDisplay.prototype.updateContent = function(){\r\n  var duration = this.player_.duration();\r\n  if (duration) {\r\n      this.content.innerHTML = '<span class=\"vjs-control-text\">Duration Time </span>' + vjs.formatTime(duration); // label the duration time for screen reader users\r\n  }\r\n};\r\n\r\n/**\r\n * The separator between the current time and duration\r\n *\r\n * Can be hidden if it's not needed in the design.\r\n *\r\n * @param {vjs.Player|Object} player\r\n * @param {Object=} options\r\n * @constructor\r\n */\r\nvjs.TimeDivider = vjs.Component.extend({\r\n  /** @constructor */\r\n  init: function(player, options){\r\n    vjs.Component.call(this, player, options);\r\n  }\r\n});\r\n\r\nvjs.TimeDivider.prototype.createEl = function(){\r\n  return vjs.Component.prototype.createEl.call(this, 'div', {\r\n    className: 'vjs-time-divider',\r\n    innerHTML: '<div><span>/</span></div>'\r\n  });\r\n};\r\n\r\n/**\r\n * Displays the time left in the video\r\n * @param {vjs.Player|Object} player\r\n * @param {Object=} options\r\n * @constructor\r\n */\r\nvjs.RemainingTimeDisplay = vjs.Component.extend({\r\n  /** @constructor */\r\n  init: function(player, options){\r\n    vjs.Component.call(this, player, options);\r\n\r\n    player.on('timeupdate', vjs.bind(this, this.updateContent));\r\n  }\r\n});\r\n\r\nvjs.RemainingTimeDisplay.prototype.createEl = function(){\r\n  var el = vjs.Component.prototype.createEl.call(this, 'div', {\r\n    className: 'vjs-remaining-time vjs-time-controls vjs-control'\r\n  });\r\n\r\n  this.content = vjs.createEl('div', {\r\n    className: 'vjs-remaining-time-display',\r\n    innerHTML: '<span class=\"vjs-control-text\">Remaining Time </span>' + '-0:00', // label the remaining time for screen reader users\r\n    'aria-live': 'off' // tell screen readers not to automatically read the time as it changes\r\n  });\r\n\r\n  el.appendChild(vjs.createEl('div').appendChild(this.content));\r\n  return el;\r\n};\r\n\r\nvjs.RemainingTimeDisplay.prototype.updateContent = function(){\r\n  if (this.player_.duration()) {\r\n    this.content.innerHTML = '<span class=\"vjs-control-text\">Remaining Time </span>' + '-'+ vjs.formatTime(this.player_.remainingTime());\r\n  }\r\n\r\n  // Allows for smooth scrubbing, when player can't keep up.\r\n  // var time = (this.player_.scrubbing) ? this.player_.getCache().currentTime : this.player_.currentTime();\r\n  // this.content.innerHTML = vjs.formatTime(time, this.player_.duration());\r\n};\r\n/**\r\n * Toggle fullscreen video\r\n * @param {vjs.Player|Object} player\r\n * @param {Object=} options\r\n * @class\r\n * @extends vjs.Button\r\n */\r\nvjs.FullscreenToggle = vjs.Button.extend({\r\n  /**\r\n   * @constructor\r\n   * @memberof vjs.FullscreenToggle\r\n   * @instance\r\n   */\r\n  init: function(player, options){\r\n    vjs.Button.call(this, player, options);\r\n  }\r\n});\r\n\r\nvjs.FullscreenToggle.prototype.buttonText = 'Fullscreen';\r\n\r\nvjs.FullscreenToggle.prototype.buildCSSClass = function(){\r\n  return 'vjs-fullscreen-control ' + vjs.Button.prototype.buildCSSClass.call(this);\r\n};\r\n\r\nvjs.FullscreenToggle.prototype.onClick = function(){\r\n  if (!this.player_.isFullScreen) {\r\n    this.player_.requestFullScreen();\r\n    this.el_.children[0].children[0].innerHTML = 'Non-Fullscreen'; // change the button text to \"Non-Fullscreen\"\r\n  } else {\r\n    this.player_.cancelFullScreen();\r\n    this.el_.children[0].children[0].innerHTML = 'Fullscreen'; // change the button to \"Fullscreen\"\r\n  }\r\n};\r\n/**\r\n * The Progress Control component contains the seek bar, load progress,\r\n * and play progress\r\n *\r\n * @param {vjs.Player|Object} player\r\n * @param {Object=} options\r\n * @constructor\r\n */\r\nvjs.ProgressControl = vjs.Component.extend({\r\n  /** @constructor */\r\n  init: function(player, options){\r\n    vjs.Component.call(this, player, options);\r\n  }\r\n});\r\n\r\nvjs.ProgressControl.prototype.options_ = {\r\n  children: {\r\n    'seekBar': {}\r\n  }\r\n};\r\n\r\nvjs.ProgressControl.prototype.createEl = function(){\r\n  return vjs.Component.prototype.createEl.call(this, 'div', {\r\n    className: 'vjs-progress-control vjs-control'\r\n  });\r\n};\r\n\r\n/**\r\n * Seek Bar and holder for the progress bars\r\n *\r\n * @param {vjs.Player|Object} player\r\n * @param {Object=} options\r\n * @constructor\r\n */\r\nvjs.SeekBar = vjs.Slider.extend({\r\n  /** @constructor */\r\n  init: function(player, options){\r\n    vjs.Slider.call(this, player, options);\r\n    player.on('timeupdate', vjs.bind(this, this.updateARIAAttributes));\r\n    player.ready(vjs.bind(this, this.updateARIAAttributes));\r\n  }\r\n});\r\n\r\nvjs.SeekBar.prototype.options_ = {\r\n  children: {\r\n    'loadProgressBar': {},\r\n    'playProgressBar': {},\r\n    'seekHandle': {}\r\n  },\r\n  'barName': 'playProgressBar',\r\n  'handleName': 'seekHandle'\r\n};\r\n\r\nvjs.SeekBar.prototype.playerEvent = 'timeupdate';\r\n\r\nvjs.SeekBar.prototype.createEl = function(){\r\n  return vjs.Slider.prototype.createEl.call(this, 'div', {\r\n    className: 'vjs-progress-holder',\r\n    'aria-label': 'video progress bar'\r\n  });\r\n};\r\n\r\nvjs.SeekBar.prototype.updateARIAAttributes = function(){\r\n    // Allows for smooth scrubbing, when player can't keep up.\r\n    var time = (this.player_.scrubbing) ? this.player_.getCache().currentTime : this.player_.currentTime();\r\n    this.el_.setAttribute('aria-valuenow',vjs.round(this.getPercent()*100, 2)); // machine readable value of progress bar (percentage complete)\r\n    this.el_.setAttribute('aria-valuetext',vjs.formatTime(time, this.player_.duration())); // human readable value of progress bar (time complete)\r\n};\r\n\r\nvjs.SeekBar.prototype.getPercent = function(){\r\n  var currentTime;\r\n  // Flash RTMP provider will not report the correct time\r\n  // immediately after a seek. This isn't noticeable if you're\r\n  // seeking while the video is playing, but it is if you seek\r\n  // while the video is paused.\r\n  if (this.player_.techName === 'Flash' && this.player_.seeking()) {\r\n    var cache = this.player_.getCache();\r\n    if (cache.lastSetCurrentTime) {\r\n      currentTime = cache.lastSetCurrentTime;\r\n    }\r\n    else {\r\n      currentTime = this.player_.currentTime();\r\n    }\r\n  }\r\n  else {\r\n    currentTime = this.player_.currentTime();\r\n  }\r\n\r\n  return currentTime / this.player_.duration();\r\n};\r\n\r\nvjs.SeekBar.prototype.onMouseDown = function(event){\r\n  vjs.Slider.prototype.onMouseDown.call(this, event);\r\n\r\n  this.player_.scrubbing = true;\r\n\r\n  this.videoWasPlaying = !this.player_.paused();\r\n  this.player_.pause();\r\n};\r\n\r\nvjs.SeekBar.prototype.onMouseMove = function(event){\r\n  var newTime = this.calculateDistance(event) * this.player_.duration();\r\n\r\n  // Don't let video end while scrubbing.\r\n  if (newTime == this.player_.duration()) { newTime = newTime - 0.1; }\r\n\r\n  // Set new time (tell player to seek to new time)\r\n  this.player_.currentTime(newTime);\r\n};\r\n\r\nvjs.SeekBar.prototype.onMouseUp = function(event){\r\n    debugger\r\n  vjs.Slider.prototype.onMouseUp.call(this, event);\r\n\r\n  this.player_.scrubbing = false;\r\n  if (this.videoWasPlaying) {\r\n      debugger\r\n    this.player_.play();\r\n  }\r\n};\r\n\r\nvjs.SeekBar.prototype.stepForward = function(){\r\n  this.player_.currentTime(this.player_.currentTime() + 5); // more quickly fast forward for keyboard-only users\r\n};\r\n\r\nvjs.SeekBar.prototype.stepBack = function(){\r\n  this.player_.currentTime(this.player_.currentTime() - 5); // more quickly rewind for keyboard-only users\r\n};\r\n\r\n\r\n/**\r\n * Shows load progress\r\n *\r\n * @param {vjs.Player|Object} player\r\n * @param {Object=} options\r\n * @constructor\r\n */\r\nvjs.LoadProgressBar = vjs.Component.extend({\r\n  /** @constructor */\r\n  init: function(player, options){\r\n    vjs.Component.call(this, player, options);\r\n    player.on('progress', vjs.bind(this, this.update));\r\n  }\r\n});\r\n\r\nvjs.LoadProgressBar.prototype.createEl = function(){\r\n  return vjs.Component.prototype.createEl.call(this, 'div', {\r\n    className: 'vjs-load-progress',\r\n    innerHTML: '<span class=\"vjs-control-text\">Loaded: 0%</span>'\r\n  });\r\n};\r\n\r\nvjs.LoadProgressBar.prototype.update = function(){\r\n  if (this.el_.style) { this.el_.style.width = vjs.round(this.player_.bufferedPercent() * 100, 2) + '%'; }\r\n};\r\n\r\n\r\n/**\r\n * Shows play progress\r\n *\r\n * @param {vjs.Player|Object} player\r\n * @param {Object=} options\r\n * @constructor\r\n */\r\nvjs.PlayProgressBar = vjs.Component.extend({\r\n  /** @constructor */\r\n  init: function(player, options){\r\n    vjs.Component.call(this, player, options);\r\n  }\r\n});\r\n\r\nvjs.PlayProgressBar.prototype.createEl = function(){\r\n  return vjs.Component.prototype.createEl.call(this, 'div', {\r\n    className: 'vjs-play-progress',\r\n    innerHTML: '<span class=\"vjs-control-text\">Progress: 0%</span>'\r\n  });\r\n};\r\n\r\n/**\r\n * The Seek Handle shows the current position of the playhead during playback,\r\n * and can be dragged to adjust the playhead.\r\n *\r\n * @param {vjs.Player|Object} player\r\n * @param {Object=} options\r\n * @constructor\r\n */\r\nvjs.SeekHandle = vjs.SliderHandle.extend();\r\n\r\n/**\r\n * The default value for the handle content, which may be read by screen readers\r\n *\r\n * @type {String}\r\n * @private\r\n */\r\nvjs.SeekHandle.prototype.defaultValue = '00:00';\r\n\r\n/** @inheritDoc */\r\nvjs.SeekHandle.prototype.createEl = function(){\r\n  return vjs.SliderHandle.prototype.createEl.call(this, 'div', {\r\n    className: 'vjs-seek-handle'\r\n  });\r\n};\r\n/**\r\n * The component for controlling the volume level\r\n *\r\n * @param {vjs.Player|Object} player\r\n * @param {Object=} options\r\n * @constructor\r\n */\r\nvjs.VolumeControl = vjs.Component.extend({\r\n  /** @constructor */\r\n  init: function(player, options){\r\n    vjs.Component.call(this, player, options);\r\n\r\n    // hide volume controls when they're not supported by the current tech\r\n    if (player.tech && player.tech.features && player.tech.features['volumeControl'] === false) {\r\n      this.addClass('vjs-hidden');\r\n    }\r\n    player.on('loadstart', vjs.bind(this, function(){\r\n      if (player.tech.features && player.tech.features['volumeControl'] === false) {\r\n        this.addClass('vjs-hidden');\r\n      } else {\r\n        this.removeClass('vjs-hidden');\r\n      }\r\n    }));\r\n  }\r\n});\r\n\r\nvjs.VolumeControl.prototype.options_ = {\r\n  children: {\r\n    'volumeBar': {}\r\n  }\r\n};\r\n\r\nvjs.VolumeControl.prototype.createEl = function(){\r\n  return vjs.Component.prototype.createEl.call(this, 'div', {\r\n    className: 'vjs-volume-control vjs-control'\r\n  });\r\n};\r\n\r\n/**\r\n * The bar that contains the volume level and can be clicked on to adjust the level\r\n *\r\n * @param {vjs.Player|Object} player\r\n * @param {Object=} options\r\n * @constructor\r\n */\r\nvjs.VolumeBar = vjs.Slider.extend({\r\n  /** @constructor */\r\n  init: function(player, options){\r\n    vjs.Slider.call(this, player, options);\r\n    player.on('volumechange', vjs.bind(this, this.updateARIAAttributes));\r\n    player.ready(vjs.bind(this, this.updateARIAAttributes));\r\n    setTimeout(vjs.bind(this, this.update), 0); // update when elements is in DOM\r\n  }\r\n});\r\n\r\nvjs.VolumeBar.prototype.updateARIAAttributes = function(){\r\n  // Current value of volume bar as a percentage\r\n  this.el_.setAttribute('aria-valuenow',vjs.round(this.player_.volume()*100, 2));\r\n  this.el_.setAttribute('aria-valuetext',vjs.round(this.player_.volume()*100, 2)+'%');\r\n};\r\n\r\nvjs.VolumeBar.prototype.options_ = {\r\n  children: {\r\n    'volumeLevel': {},\r\n    'volumeHandle': {}\r\n  },\r\n  'barName': 'volumeLevel',\r\n  'handleName': 'volumeHandle'\r\n};\r\n\r\nvjs.VolumeBar.prototype.playerEvent = 'volumechange';\r\n\r\nvjs.VolumeBar.prototype.createEl = function(){\r\n  return vjs.Slider.prototype.createEl.call(this, 'div', {\r\n    className: 'vjs-volume-bar',\r\n    'aria-label': 'volume level'\r\n  });\r\n};\r\n\r\nvjs.VolumeBar.prototype.onMouseMove = function(event) {\r\n  if (this.player_.muted()) {\r\n    this.player_.muted(false);\r\n  }\r\n\r\n  this.player_.volume(this.calculateDistance(event));\r\n};\r\n\r\nvjs.VolumeBar.prototype.getPercent = function(){\r\n  if (this.player_.muted()) {\r\n    return 0;\r\n  } else {\r\n    return this.player_.volume();\r\n  }\r\n};\r\n\r\nvjs.VolumeBar.prototype.stepForward = function(){\r\n  this.player_.volume(this.player_.volume() + 0.1);\r\n};\r\n\r\nvjs.VolumeBar.prototype.stepBack = function(){\r\n  this.player_.volume(this.player_.volume() - 0.1);\r\n};\r\n\r\n/**\r\n * Shows volume level\r\n *\r\n * @param {vjs.Player|Object} player\r\n * @param {Object=} options\r\n * @constructor\r\n */\r\nvjs.VolumeLevel = vjs.Component.extend({\r\n  /** @constructor */\r\n  init: function(player, options){\r\n    vjs.Component.call(this, player, options);\r\n  }\r\n});\r\n\r\nvjs.VolumeLevel.prototype.createEl = function(){\r\n  return vjs.Component.prototype.createEl.call(this, 'div', {\r\n    className: 'vjs-volume-level',\r\n    innerHTML: '<span class=\"vjs-control-text\"></span>'\r\n  });\r\n};\r\n\r\n/**\r\n * The volume handle can be dragged to adjust the volume level\r\n *\r\n * @param {vjs.Player|Object} player\r\n * @param {Object=} options\r\n * @constructor\r\n */\r\n vjs.VolumeHandle = vjs.SliderHandle.extend();\r\n\r\n vjs.VolumeHandle.prototype.defaultValue = '00:00';\r\n\r\n /** @inheritDoc */\r\n vjs.VolumeHandle.prototype.createEl = function(){\r\n   return vjs.SliderHandle.prototype.createEl.call(this, 'div', {\r\n     className: 'vjs-volume-handle'\r\n   });\r\n };\r\n/**\r\n * A button component for muting the audio\r\n *\r\n * @param {vjs.Player|Object} player\r\n * @param {Object=} options\r\n * @constructor\r\n */\r\nvjs.MuteToggle = vjs.Button.extend({\r\n  /** @constructor */\r\n  init: function(player, options){\r\n    vjs.Button.call(this, player, options);\r\n\r\n    player.on('volumechange', vjs.bind(this, this.update));\r\n\r\n    // hide mute toggle if the current tech doesn't support volume control\r\n    if (player.tech && player.tech.features && player.tech.features['volumeControl'] === false) {\r\n      this.addClass('vjs-hidden');\r\n    }\r\n    player.on('loadstart', vjs.bind(this, function(){\r\n      if (player.tech.features && player.tech.features['volumeControl'] === false) {\r\n        this.addClass('vjs-hidden');\r\n      } else {\r\n        this.removeClass('vjs-hidden');\r\n      }\r\n    }));\r\n  }\r\n});\r\n\r\nvjs.MuteToggle.prototype.createEl = function(){\r\n  return vjs.Button.prototype.createEl.call(this, 'div', {\r\n    className: 'vjs-mute-control vjs-control',\r\n    innerHTML: '<div><span class=\"vjs-control-text\">Mute</span></div>'\r\n  });\r\n};\r\n\r\nvjs.MuteToggle.prototype.onClick = function(){\r\n  this.player_.muted( this.player_.muted() ? false : true );\r\n};\r\n\r\nvjs.MuteToggle.prototype.update = function(){\r\n  var vol = this.player_.volume(),\r\n      level = 3;\r\n\r\n  if (vol === 0 || this.player_.muted()) {\r\n    level = 0;\r\n  } else if (vol < 0.33) {\r\n    level = 1;\r\n  } else if (vol < 0.67) {\r\n    level = 2;\r\n  }\r\n\r\n  // Don't rewrite the button text if the actual text doesn't change.\r\n  // This causes unnecessary and confusing information for screen reader users.\r\n  // This check is needed because this function gets called every time the volume level is changed.\r\n  if(this.player_.muted()){\r\n      if(this.el_.children[0].children[0].innerHTML!='Unmute'){\r\n          this.el_.children[0].children[0].innerHTML = 'Unmute'; // change the button text to \"Unmute\"\r\n      }\r\n  } else {\r\n      if(this.el_.children[0].children[0].innerHTML!='Mute'){\r\n          this.el_.children[0].children[0].innerHTML = 'Mute'; // change the button text to \"Mute\"\r\n      }\r\n  }\r\n\r\n  /* TODO improve muted icon classes */\r\n  for (var i = 0; i < 4; i++) {\r\n    vjs.removeClass(this.el_, 'vjs-vol-'+i);\r\n  }\r\n  vjs.addClass(this.el_, 'vjs-vol-'+level);\r\n};\r\n/**\r\n * Menu button with a popup for showing the volume slider.\r\n * @constructor\r\n */\r\nvjs.VolumeMenuButton = vjs.MenuButton.extend({\r\n  /** @constructor */\r\n  init: function(player, options){\r\n    vjs.MenuButton.call(this, player, options);\r\n\r\n    // Same listeners as MuteToggle\r\n    player.on('volumechange', vjs.bind(this, this.update));\r\n\r\n    // hide mute toggle if the current tech doesn't support volume control\r\n    if (player.tech && player.tech.features && player.tech.features.volumeControl === false) {\r\n      this.addClass('vjs-hidden');\r\n    }\r\n    player.on('loadstart', vjs.bind(this, function(){\r\n      if (player.tech.features && player.tech.features.volumeControl === false) {\r\n        this.addClass('vjs-hidden');\r\n      } else {\r\n        this.removeClass('vjs-hidden');\r\n      }\r\n    }));\r\n    this.addClass('vjs-menu-button');\r\n  }\r\n});\r\n\r\nvjs.VolumeMenuButton.prototype.createMenu = function(){\r\n  var menu = new vjs.Menu(this.player_, {\r\n    contentElType: 'div'\r\n  });\r\n  var vc = new vjs.VolumeBar(this.player_, vjs.obj.merge({vertical: true}, this.options_.volumeBar));\r\n  menu.addChild(vc);\r\n  return menu;\r\n};\r\n\r\nvjs.VolumeMenuButton.prototype.onClick = function(){\r\n  vjs.MuteToggle.prototype.onClick.call(this);\r\n  vjs.MenuButton.prototype.onClick.call(this);\r\n};\r\n\r\nvjs.VolumeMenuButton.prototype.createEl = function(){\r\n  return vjs.Button.prototype.createEl.call(this, 'div', {\r\n    className: 'vjs-volume-menu-button vjs-menu-button vjs-control',\r\n    innerHTML: '<div><span class=\"vjs-control-text\">Mute</span></div>'\r\n  });\r\n};\r\nvjs.VolumeMenuButton.prototype.update = vjs.MuteToggle.prototype.update;\r\n/* Poster Image\r\n================================================================================ */\r\n/**\r\n * The component that handles showing the poster image.\r\n *\r\n * @param {vjs.Player|Object} player\r\n * @param {Object=} options\r\n * @constructor\r\n */\r\nvjs.PosterImage = vjs.Button.extend({\r\n  /** @constructor */\r\n  init: function(player, options){\r\n    vjs.Button.call(this, player, options);\r\n\r\n    if (!player.poster() || !player.controls()) {\r\n      this.hide();\r\n    }\r\n\r\n    player.on('play', vjs.bind(this, this.hide));\r\n  }\r\n});\r\n\r\nvjs.PosterImage.prototype.createEl = function(){\r\n  var el = vjs.createEl('div', {\r\n        className: 'vjs-poster',\r\n\r\n        // Don't want poster to be tabbable.\r\n        tabIndex: -1\r\n      }),\r\n      poster = this.player_.poster();\r\n\r\n  if (poster) {\r\n    if ('backgroundSize' in el.style) {\r\n      el.style.backgroundImage = 'url(\"' + poster + '\")';\r\n    } else {\r\n      el.appendChild(vjs.createEl('img', { src: poster }));\r\n    }\r\n  }\r\n\r\n  return el;\r\n};\r\n\r\nvjs.PosterImage.prototype.onClick = function(){\r\n  // Only accept clicks when controls are enabled\r\n  if (this.player().controls()) {\r\n    this.player_.play();\r\n  }\r\n};\r\n/* Loading Spinner\r\n================================================================================ */\r\n/**\r\n * Loading spinner for waiting events\r\n * @param {vjs.Player|Object} player\r\n * @param {Object=} options\r\n * @class\r\n * @constructor\r\n */\r\nvjs.LoadingSpinner = vjs.Component.extend({\r\n  /** @constructor */\r\n  init: function(player, options){\r\n    vjs.Component.call(this, player, options);\r\n\r\n    player.on('canplay', vjs.bind(this, this.hide));\r\n    player.on('canplaythrough', vjs.bind(this, this.hide));\r\n    player.on('playing', vjs.bind(this, this.hide));\r\n    player.on('seeked', vjs.bind(this, this.hide));\r\n\r\n    player.on('seeking', vjs.bind(this, this.show));\r\n\r\n    // in some browsers seeking does not trigger the 'playing' event,\r\n    // so we also need to trap 'seeked' if we are going to set a\r\n    // 'seeking' event\r\n    player.on('seeked', vjs.bind(this, this.hide));\r\n\r\n    player.on('error', vjs.bind(this, this.show));\r\n\r\n    // Not showing spinner on stalled any more. Browsers may stall and then not trigger any events that would remove the spinner.\r\n    // Checked in Chrome 16 and Safari 5.1.2. http://help.videojs.com/discussions/problems/883-why-is-the-download-progress-showing\r\n    // player.on('stalled', vjs.bind(this, this.show));\r\n\r\n    player.on('waiting', vjs.bind(this, this.show));\r\n  }\r\n});\r\n\r\nvjs.LoadingSpinner.prototype.createEl = function(){\r\n  return vjs.Component.prototype.createEl.call(this, 'div', {\r\n    className: 'vjs-loading-spinner'\r\n  });\r\n};\r\n/* Big Play Button\r\n================================================================================ */\r\n/**\r\n * Initial play button. Shows before the video has played. The hiding of the\r\n * big play button is done via CSS and player states.\r\n * @param {vjs.Player|Object} player\r\n * @param {Object=} options\r\n * @class\r\n * @constructor\r\n */\r\nvjs.BigPlayButton = vjs.Button.extend();\r\n\r\nvjs.BigPlayButton.prototype.createEl = function(){\r\n  return vjs.Button.prototype.createEl.call(this, 'div', {\r\n    className: 'vjs-big-play-button',\r\n    innerHTML: '<span aria-hidden=\"true\"></span>',\r\n    'aria-label': 'play video'\r\n  });\r\n};\r\n\r\nvjs.BigPlayButton.prototype.onClick = function(){\r\n  this.player_.play();\r\n};\r\n/**\r\n * @fileoverview Media Technology Controller - Base class for media playback\r\n * technology controllers like Flash and HTML5\r\n */\r\n\r\n/**\r\n * Base class for media (HTML5 Video, Flash) controllers\r\n * @param {vjs.Player|Object} player  Central player instance\r\n * @param {Object=} options Options object\r\n * @constructor\r\n */\r\nvjs.MediaTechController = vjs.Component.extend({\r\n  /** @constructor */\r\n  init: function(player, options, ready){\r\n    vjs.Component.call(this, player, options, ready);\r\n\r\n    this.initControlsListeners();\r\n  }\r\n});\r\n\r\n/**\r\n * Set up click and touch listeners for the playback element\r\n * On desktops, a click on the video itself will toggle playback,\r\n * on a mobile device a click on the video toggles controls.\r\n * (toggling controls is done by toggling the user state between active and\r\n * inactive)\r\n *\r\n * A tap can signal that a user has become active, or has become inactive\r\n * e.g. a quick tap on an iPhone movie should reveal the controls. Another\r\n * quick tap should hide them again (signaling the user is in an inactive\r\n * viewing state)\r\n *\r\n * In addition to this, we still want the user to be considered inactive after\r\n * a few seconds of inactivity.\r\n *\r\n * Note: the only part of iOS interaction we can't mimic with this setup\r\n * is a touch and hold on the video element counting as activity in order to\r\n * keep the controls showing, but that shouldn't be an issue. A touch and hold on\r\n * any controls will still keep the user active\r\n */\r\nvjs.MediaTechController.prototype.initControlsListeners = function(){\r\n  var player, tech, activateControls, deactivateControls;\r\n\r\n  tech = this;\r\n  player = this.player();\r\n\r\n  var activateControls = function(){\r\n    if (player.controls() && !player.usingNativeControls()) {\r\n      tech.addControlsListeners();\r\n    }\r\n  };\r\n\r\n  deactivateControls = vjs.bind(tech, tech.removeControlsListeners);\r\n\r\n  // Set up event listeners once the tech is ready and has an element to apply\r\n  // listeners to\r\n  this.ready(activateControls);\r\n  player.on('controlsenabled', activateControls);\r\n  player.on('controlsdisabled', deactivateControls);\r\n};\r\n\r\nvjs.MediaTechController.prototype.addControlsListeners = function(){\r\n  var preventBubble, userWasActive;\r\n\r\n  // Some browsers (Chrome & IE) don't trigger a click on a flash swf, but do\r\n  // trigger mousedown/up.\r\n  // http://stackoverflow.com/questions/1444562/javascript-onclick-event-over-flash-object\r\n  // Any touch events are set to block the mousedown event from happening\r\n  this.on('mousedown', this.onClick);\r\n\r\n  // We need to block touch events on the video element from bubbling up,\r\n  // otherwise they'll signal activity prematurely. The specific use case is\r\n  // when the video is playing and the controls have faded out. In this case\r\n  // only a tap (fast touch) should toggle the user active state and turn the\r\n  // controls back on. A touch and move or touch and hold should not trigger\r\n  // the controls (per iOS as an example at least)\r\n  //\r\n  // We always want to stop propagation on touchstart because touchstart\r\n  // at the player level starts the touchInProgress interval. We can still\r\n  // report activity on the other events, but won't let them bubble for\r\n  // consistency. We don't want to bubble a touchend without a touchstart.\r\n  this.on('touchstart', function(event) {\r\n    // Stop the mouse events from also happening\r\n    event.preventDefault();\r\n    event.stopPropagation();\r\n    // Record if the user was active now so we don't have to keep polling it\r\n    userWasActive = this.player_.userActive();\r\n  });\r\n\r\n  preventBubble = function(event){\r\n    event.stopPropagation();\r\n    if (userWasActive) {\r\n      this.player_.reportUserActivity();\r\n    }\r\n  };\r\n\r\n  // Treat all touch events the same for consistency\r\n  this.on('touchmove', preventBubble);\r\n  this.on('touchleave', preventBubble);\r\n  this.on('touchcancel', preventBubble);\r\n  this.on('touchend', preventBubble);\r\n\r\n  // Turn on component tap events\r\n  this.emitTapEvents();\r\n\r\n  // The tap listener needs to come after the touchend listener because the tap\r\n  // listener cancels out any reportedUserActivity when setting userActive(false)\r\n  this.on('tap', this.onTap);\r\n};\r\n\r\n/**\r\n * Remove the listeners used for click and tap controls. This is needed for\r\n * toggling to controls disabled, where a tap/touch should do nothing.\r\n */\r\nvjs.MediaTechController.prototype.removeControlsListeners = function(){\r\n  // We don't want to just use `this.off()` because there might be other needed\r\n  // listeners added by techs that extend this.\r\n  this.off('tap');\r\n  this.off('touchstart');\r\n  this.off('touchmove');\r\n  this.off('touchleave');\r\n  this.off('touchcancel');\r\n  this.off('touchend');\r\n  this.off('click');\r\n  this.off('mousedown');\r\n};\r\n\r\n/**\r\n * Handle a click on the media element. By default will play/pause the media.\r\n */\r\nvjs.MediaTechController.prototype.onClick = function(event){\r\n  // We're using mousedown to detect clicks thanks to Flash, but mousedown\r\n  // will also be triggered with right-clicks, so we need to prevent that\r\n  if (event.button !== 0) return;\r\n\r\n  // When controls are disabled a click should not toggle playback because\r\n  // the click is considered a control\r\n  if (this.player().controls()) {\r\n    if (this.player().paused()) {\r\n      this.player().play();\r\n    } else {\r\n      this.player().pause();\r\n    }\r\n  }\r\n};\r\n\r\n/**\r\n * Handle a tap on the media element. By default it will toggle the user\r\n * activity state, which hides and shows the controls.\r\n */\r\n\r\nvjs.MediaTechController.prototype.onTap = function(){\r\n  this.player().userActive(!this.player().userActive());\r\n};\r\n\r\nvjs.MediaTechController.prototype.features = {\r\n  'volumeControl': true,\r\n\r\n  // Resizing plugins using request fullscreen reloads the plugin\r\n  'fullscreenResize': false,\r\n\r\n  // Optional events that we can manually mimic with timers\r\n  // currently not triggered by video-js-swf\r\n  'progressEvents': false,\r\n  'timeupdateEvents': false\r\n};\r\n\r\nvjs.media = {};\r\n\r\n/**\r\n * List of default API methods for any MediaTechController\r\n * @type {String}\r\n */\r\nvjs.media.ApiMethods = 'play,pause,paused,currentTime,setCurrentTime,duration,buffered,volume,setVolume,muted,setMuted,width,height,supportsFullScreen,enterFullScreen,src,load,currentSrc,preload,setPreload,autoplay,setAutoplay,loop,setLoop,error,networkState,readyState,seeking,initialTime,startOffsetTime,played,seekable,ended,videoTracks,audioTracks,videoWidth,videoHeight,textTracks,defaultPlaybackRate,playbackRate,mediaGroup,controller,controls,defaultMuted'.split(',');\r\n// Create placeholder methods for each that warn when a method isn't supported by the current playback technology\r\n\r\nfunction createMethod(methodName){\r\n  return function(){\r\n    throw new Error('The \"'+methodName+'\" method is not available on the playback technology\\'s API');\r\n  };\r\n}\r\n\r\nfor (var i = vjs.media.ApiMethods.length - 1; i >= 0; i--) {\r\n  var methodName = vjs.media.ApiMethods[i];\r\n  vjs.MediaTechController.prototype[vjs.media.ApiMethods[i]] = createMethod(methodName);\r\n}\r\n/**\r\n * @fileoverview HTML5 Media Controller - Wrapper for HTML5 Media API\r\n */\r\n\r\n/**\r\n * HTML5 Media Controller - Wrapper for HTML5 Media API\r\n * @param {vjs.Player|Object} player\r\n * @param {Object=} options\r\n * @param {Function=} ready\r\n * @constructor\r\n */\r\nvjs.Html5 = vjs.MediaTechController.extend({\r\n  /** @constructor */\r\n  init: function(player, options, ready){\r\n    // volume cannot be changed from 1 on iOS\r\n    this.features['volumeControl'] = vjs.Html5.canControlVolume();\r\n\r\n    // In iOS, if you move a video element in the DOM, it breaks video playback.\r\n    this.features['movingMediaElementInDOM'] = !vjs.IS_IOS;\r\n\r\n    // HTML video is able to automatically resize when going to fullscreen\r\n    this.features['fullscreenResize'] = true;\r\n\r\n    vjs.MediaTechController.call(this, player, options, ready);\r\n\r\n    var source = options['source'];\r\n\r\n    // If the element source is already set, we may have missed the loadstart event, and want to trigger it.\r\n    // We don't want to set the source again and interrupt playback.\r\n    if (source && this.el_.currentSrc === source.src && this.el_.networkState > 0) {\r\n      player.trigger('loadstart');\r\n\r\n    // Otherwise set the source if one was provided.\r\n    } else if (source) {\r\n      this.el_.src = source.src;\r\n    }\r\n\r\n    // Determine if native controls should be used\r\n    // Our goal should be to get the custom controls on mobile solid everywhere\r\n    // so we can remove this all together. Right now this will block custom\r\n    // controls on touch enabled laptops like the Chrome Pixel\r\n    if (vjs.TOUCH_ENABLED && player.options()['nativeControlsForTouch'] !== false) {\r\n      this.useNativeControls();\r\n    }\r\n\r\n    // Chrome and Safari both have issues with autoplay.\r\n    // In Safari (5.1.1), when we move the video element into the container div, autoplay doesn't work.\r\n    // In Chrome (15), if you have autoplay + a poster + no controls, the video gets hidden (but audio plays)\r\n    // This fixes both issues. Need to wait for API, so it updates displays correctly\r\n    player.ready(function(){\r\n      if (this.tag && this.options_['autoplay'] && this.paused()) {\r\n        delete this.tag['poster']; // Chrome Fix. Fixed in Chrome v16.\r\n        this.play();\r\n      }\r\n    });\r\n\r\n    this.setupTriggers();\r\n    this.triggerReady();\r\n  }\r\n});\r\n\r\nvjs.Html5.prototype.dispose = function(){\r\n  vjs.MediaTechController.prototype.dispose.call(this);\r\n};\r\n\r\nvjs.Html5.prototype.createEl = function(){\r\n  var player = this.player_,\r\n      // If possible, reuse original tag for HTML5 playback technology element\r\n      el = player.tag,\r\n      newEl,\r\n      clone;\r\n\r\n  // Check if this browser supports moving the element into the box.\r\n  // On the iPhone video will break if you move the element,\r\n  // So we have to create a brand new element.\r\n  if (!el || this.features['movingMediaElementInDOM'] === false) {\r\n\r\n    // If the original tag is still there, clone and remove it.\r\n    if (el) {\r\n      clone = el.cloneNode(false);\r\n      vjs.Html5.disposeMediaElement(el);\r\n      el = clone;\r\n      player.tag = null;\r\n    } else {\r\n      el = vjs.createEl('video', {\r\n        id:player.id() + '_html5_api',\r\n        className:'vjs-tech'\r\n      });\r\n    }\r\n    // associate the player with the new tag\r\n    el['player'] = player;\r\n\r\n    vjs.insertFirst(el, player.el());\r\n  }\r\n\r\n  // Update specific tag settings, in case they were overridden\r\n  var attrs = ['autoplay','preload','loop','muted'];\r\n  for (var i = attrs.length - 1; i >= 0; i--) {\r\n    var attr = attrs[i];\r\n    if (player.options_[attr] !== null) {\r\n      el[attr] = player.options_[attr];\r\n    }\r\n  }\r\n\r\n  return el;\r\n  // jenniisawesome = true;\r\n};\r\n\r\n// Make video events trigger player events\r\n// May seem verbose here, but makes other APIs possible.\r\nvjs.Html5.prototype.setupTriggers = function(){\r\n  for (var i = vjs.Html5.Events.length - 1; i >= 0; i--) {\r\n    vjs.on(this.el_, vjs.Html5.Events[i], vjs.bind(this.player_, this.eventHandler));\r\n  }\r\n};\r\n// Triggers removed using this.off when disposed\r\n\r\nvjs.Html5.prototype.eventHandler = function(e){\r\n  this.trigger(e);\r\n\r\n  // No need for media events to bubble up.\r\n  e.stopPropagation();\r\n};\r\n\r\nvjs.Html5.prototype.useNativeControls = function(){\r\n  var tech, player, controlsOn, controlsOff, cleanUp;\r\n\r\n  tech = this;\r\n  player = this.player();\r\n\r\n  // If the player controls are enabled turn on the native controls\r\n  tech.setControls(player.controls());\r\n\r\n  // Update the native controls when player controls state is updated\r\n  controlsOn = function(){\r\n    tech.setControls(true);\r\n  };\r\n  controlsOff = function(){\r\n    tech.setControls(false);\r\n  };\r\n  player.on('controlsenabled', controlsOn);\r\n  player.on('controlsdisabled', controlsOff);\r\n\r\n  // Clean up when not using native controls anymore\r\n  cleanUp = function(){\r\n    player.off('controlsenabled', controlsOn);\r\n    player.off('controlsdisabled', controlsOff);\r\n  };\r\n  tech.on('dispose', cleanUp);\r\n  player.on('usingcustomcontrols', cleanUp);\r\n\r\n  // Update the state of the player to using native controls\r\n  player.usingNativeControls(true);\r\n};\r\n\r\n\r\nvjs.Html5.prototype.play = function(){ this.el_.play(); };\r\nvjs.Html5.prototype.pause = function(){ this.el_.pause(); };\r\nvjs.Html5.prototype.paused = function(){ return this.el_.paused; };\r\n\r\nvjs.Html5.prototype.currentTime = function(){ return this.el_.currentTime; };\r\nvjs.Html5.prototype.setCurrentTime = function(seconds){\r\n  try {\r\n    this.el_.currentTime = seconds;\r\n  } catch(e) {\r\n    vjs.log(e, 'Video is not ready. (Video.js)');\r\n    // this.warning(VideoJS.warnings.videoNotReady);\r\n  }\r\n};\r\n\r\nvjs.Html5.prototype.duration = function(){ return this.el_.duration || 0; };\r\nvjs.Html5.prototype.buffered = function(){ return this.el_.buffered; };\r\n\r\nvjs.Html5.prototype.volume = function(){ return this.el_.volume; };\r\nvjs.Html5.prototype.setVolume = function(percentAsDecimal){ this.el_.volume = percentAsDecimal; };\r\nvjs.Html5.prototype.muted = function(){ return this.el_.muted; };\r\nvjs.Html5.prototype.setMuted = function(muted){ this.el_.muted = muted; };\r\n\r\nvjs.Html5.prototype.width = function(){ return this.el_.offsetWidth; };\r\nvjs.Html5.prototype.height = function(){ return this.el_.offsetHeight; };\r\n\r\nvjs.Html5.prototype.supportsFullScreen = function(){\r\n  if (typeof this.el_.webkitEnterFullScreen == 'function') {\r\n\r\n    // Seems to be broken in Chromium/Chrome && Safari in Leopard\r\n    if (/Android/.test(vjs.USER_AGENT) || !/Chrome|Mac OS X 10.5/.test(vjs.USER_AGENT)) {\r\n      return true;\r\n    }\r\n  }\r\n  return false;\r\n};\r\n\r\nvjs.Html5.prototype.enterFullScreen = function(){\r\n  var video = this.el_;\r\n  if (video.paused && video.networkState <= video.HAVE_METADATA) {\r\n    // attempt to prime the video element for programmatic access\r\n    // this isn't necessary on the desktop but shouldn't hurt\r\n    this.el_.play();\r\n\r\n    // playing and pausing synchronously during the transition to fullscreen\r\n    // can get iOS ~6.1 devices into a play/pause loop\r\n    setTimeout(function(){\r\n      video.pause();\r\n      video.webkitEnterFullScreen();\r\n    }, 0);\r\n  } else {\r\n    video.webkitEnterFullScreen();\r\n  }\r\n};\r\nvjs.Html5.prototype.exitFullScreen = function(){\r\n  this.el_.webkitExitFullScreen();\r\n};\r\nvjs.Html5.prototype.src = function(src){ this.el_.src = src; };\r\nvjs.Html5.prototype.load = function(){ this.el_.load(); };\r\nvjs.Html5.prototype.currentSrc = function(){ return this.el_.currentSrc; };\r\n\r\nvjs.Html5.prototype.preload = function(){ return this.el_.preload; };\r\nvjs.Html5.prototype.setPreload = function(val){ this.el_.preload = val; };\r\n\r\nvjs.Html5.prototype.autoplay = function(){ return this.el_.autoplay; };\r\nvjs.Html5.prototype.setAutoplay = function(val){ this.el_.autoplay = val; };\r\n\r\nvjs.Html5.prototype.controls = function(){ return this.el_.controls; }\r\nvjs.Html5.prototype.setControls = function(val){ this.el_.controls = !!val; }\r\n\r\nvjs.Html5.prototype.loop = function(){ return this.el_.loop; };\r\nvjs.Html5.prototype.setLoop = function(val){ this.el_.loop = val; };\r\n\r\nvjs.Html5.prototype.error = function(){ return this.el_.error; };\r\nvjs.Html5.prototype.seeking = function(){ return this.el_.seeking; };\r\nvjs.Html5.prototype.ended = function(){ return this.el_.ended; };\r\nvjs.Html5.prototype.defaultMuted = function(){ return this.el_.defaultMuted; };\r\n\r\n/* HTML5 Support Testing ---------------------------------------------------- */\r\n\r\nvjs.Html5.isSupported = function(){\r\n  return !!vjs.TEST_VID.canPlayType;\r\n};\r\n\r\nvjs.Html5.canPlaySource = function(srcObj){\r\n  // IE9 on Windows 7 without MediaPlayer throws an error here\r\n  // https://github.com/videojs/video.js/issues/519\r\n  try {\r\n    return !!vjs.TEST_VID.canPlayType(srcObj.type);\r\n  } catch(e) {\r\n    return '';\r\n  }\r\n  // TODO: Check Type\r\n  // If no Type, check ext\r\n  // Check Media Type\r\n};\r\n\r\nvjs.Html5.canControlVolume = function(){\r\n  var volume =  vjs.TEST_VID.volume;\r\n  vjs.TEST_VID.volume = (volume / 2) + 0.1;\r\n  return volume !== vjs.TEST_VID.volume;\r\n};\r\n\r\n// List of all HTML5 events (various uses).\r\nvjs.Html5.Events = 'loadstart,suspend,abort,error,emptied,stalled,loadedmetadata,loadeddata,canplay,canplaythrough,playing,waiting,seeking,seeked,ended,durationchange,timeupdate,progress,play,pause,ratechange,volumechange'.split(',');\r\n\r\nvjs.Html5.disposeMediaElement = function(el){\r\n  if (!el) { return; }\r\n\r\n  el['player'] = null;\r\n\r\n  if (el.parentNode) {\r\n    el.parentNode.removeChild(el);\r\n  }\r\n\r\n  // remove any child track or source nodes to prevent their loading\r\n  while(el.hasChildNodes()) {\r\n    el.removeChild(el.firstChild);\r\n  }\r\n\r\n  // remove any src reference. not setting `src=''` because that causes a warning\r\n  // in firefox\r\n  el.removeAttribute('src');\r\n\r\n  // force the media element to update its loading state by calling load()\r\n  if (typeof el.load === 'function') {\r\n    el.load();\r\n  }\r\n};\r\n\r\n// HTML5 Feature detection and Device Fixes --------------------------------- //\r\n\r\n  // Override Android 2.2 and less canPlayType method which is broken\r\nif (vjs.IS_OLD_ANDROID) {\r\n  document.createElement('video').constructor.prototype.canPlayType = function(type){\r\n    return (type && type.toLowerCase().indexOf('video/mp4') != -1) ? 'maybe' : '';\r\n  };\r\n}\r\n/**\r\n * @fileoverview VideoJS-SWF - Custom Flash Player with HTML5-ish API\r\n * https://github.com/zencoder/video-js-swf\r\n * Not using setupTriggers. Using global onEvent func to distribute events\r\n */\r\n\r\n/**\r\n * Flash Media Controller - Wrapper for fallback SWF API\r\n *\r\n * @param {vjs.Player} player\r\n * @param {Object=} options\r\n * @param {Function=} ready\r\n * @constructor\r\n */\r\nvjs.Flash = vjs.MediaTechController.extend({\r\n  /** @constructor */\r\n  init: function(player, options, ready){\r\n    vjs.MediaTechController.call(this, player, options, ready);\r\n\r\n    var source = options['source'],\r\n\r\n        // Which element to embed in\r\n        parentEl = options['parentEl'],\r\n\r\n        // Create a temporary element to be replaced by swf object\r\n        placeHolder = this.el_ = vjs.createEl('div', { id: player.id() + '_temp_flash' }),\r\n\r\n        // Generate ID for swf object\r\n        objId = player.id()+'_flash_api',\r\n\r\n        // Store player options in local var for optimization\r\n        // TODO: switch to using player methods instead of options\r\n        // e.g. player.autoplay();\r\n        playerOptions = player.options_,\r\n\r\n        // Merge default flashvars with ones passed in to init\r\n        flashVars = vjs.obj.merge({\r\n\r\n          // SWF Callback Functions\r\n          'readyFunction': 'videojs.Flash.onReady',\r\n          'eventProxyFunction': 'videojs.Flash.onEvent',\r\n          'errorEventProxyFunction': 'videojs.Flash.onError',\r\n\r\n          // Player Settings\r\n          'autoplay': playerOptions.autoplay,\r\n          'preload': playerOptions.preload,\r\n          'loop': playerOptions.loop,\r\n          'muted': playerOptions.muted\r\n\r\n        }, options['flashVars']),\r\n\r\n        // Merge default parames with ones passed in\r\n        params = vjs.obj.merge({\r\n          'wmode': 'opaque', // Opaque is needed to overlay controls, but can affect playback performance\r\n          'bgcolor': '#000000' // Using bgcolor prevents a white flash when the object is loading\r\n        }, options['params']),\r\n\r\n        // Merge default attributes with ones passed in\r\n        attributes = vjs.obj.merge({\r\n          'id': objId,\r\n          'name': objId, // Both ID and Name needed or swf to identifty itself\r\n          'class': 'vjs-tech'\r\n        }, options['attributes'])\r\n    ;\r\n\r\n    // If source was supplied pass as a flash var.\r\n    if (source) {\r\n      if (source.type && vjs.Flash.isStreamingType(source.type)) {\r\n        var parts = vjs.Flash.streamToParts(source.src);\r\n        flashVars['rtmpConnection'] = encodeURIComponent(parts.connection);\r\n        flashVars['rtmpStream'] = encodeURIComponent(parts.stream);\r\n      }\r\n      else {\r\n        flashVars['src'] = encodeURIComponent(vjs.getAbsoluteURL(source.src));\r\n      }\r\n    }\r\n\r\n    // Add placeholder to player div\r\n    vjs.insertFirst(placeHolder, parentEl);\r\n\r\n    // Having issues with Flash reloading on certain page actions (hide/resize/fullscreen) in certain browsers\r\n    // This allows resetting the playhead when we catch the reload\r\n    if (options['startTime']) {\r\n      this.ready(function(){\r\n        this.load();\r\n        this.play();\r\n        this.currentTime(options['startTime']);\r\n      });\r\n    }\r\n\r\n    // Flash iFrame Mode\r\n    // In web browsers there are multiple instances where changing the parent element or visibility of a plugin causes the plugin to reload.\r\n    // - Firefox just about always. https://bugzilla.mozilla.org/show_bug.cgi?id=90268 (might be fixed by version 13)\r\n    // - Webkit when hiding the plugin\r\n    // - Webkit and Firefox when using requestFullScreen on a parent element\r\n    // Loading the flash plugin into a dynamically generated iFrame gets around most of these issues.\r\n    // Issues that remain include hiding the element and requestFullScreen in Firefox specifically\r\n\r\n    // There's on particularly annoying issue with this method which is that Firefox throws a security error on an offsite Flash object loaded into a dynamically created iFrame.\r\n    // Even though the iframe was inserted into a page on the web, Firefox + Flash considers it a local app trying to access an internet file.\r\n    // I tried mulitple ways of setting the iframe src attribute but couldn't find a src that worked well. Tried a real/fake source, in/out of domain.\r\n    // Also tried a method from stackoverflow that caused a security error in all browsers. http://stackoverflow.com/questions/2486901/how-to-set-document-domain-for-a-dynamically-generated-iframe\r\n    // In the end the solution I found to work was setting the iframe window.location.href right before doing a document.write of the Flash object.\r\n    // The only downside of this it seems to trigger another http request to the original page (no matter what's put in the href). Not sure why that is.\r\n\r\n    // NOTE (2012-01-29): Cannot get Firefox to load the remote hosted SWF into a dynamically created iFrame\r\n    // Firefox 9 throws a security error, unleess you call location.href right before doc.write.\r\n    //    Not sure why that even works, but it causes the browser to look like it's continuously trying to load the page.\r\n    // Firefox 3.6 keeps calling the iframe onload function anytime I write to it, causing an endless loop.\r\n\r\n    if (options['iFrameMode'] === true && !vjs.IS_FIREFOX) {\r\n\r\n      // Create iFrame with vjs-tech class so it's 100% width/height\r\n      var iFrm = vjs.createEl('iframe', {\r\n        'id': objId + '_iframe',\r\n        'name': objId + '_iframe',\r\n        'className': 'vjs-tech',\r\n        'scrolling': 'no',\r\n        'marginWidth': 0,\r\n        'marginHeight': 0,\r\n        'frameBorder': 0\r\n      });\r\n\r\n      // Update ready function names in flash vars for iframe window\r\n      flashVars['readyFunction'] = 'ready';\r\n      flashVars['eventProxyFunction'] = 'events';\r\n      flashVars['errorEventProxyFunction'] = 'errors';\r\n\r\n      // Tried multiple methods to get this to work in all browsers\r\n\r\n      // Tried embedding the flash object in the page first, and then adding a place holder to the iframe, then replacing the placeholder with the page object.\r\n      // The goal here was to try to load the swf URL in the parent page first and hope that got around the firefox security error\r\n      // var newObj = vjs.Flash.embed(options['swf'], placeHolder, flashVars, params, attributes);\r\n      // (in onload)\r\n      //  var temp = vjs.createEl('a', { id:'asdf', innerHTML: 'asdf' } );\r\n      //  iDoc.body.appendChild(temp);\r\n\r\n      // Tried embedding the flash object through javascript in the iframe source.\r\n      // This works in webkit but still triggers the firefox security error\r\n      // iFrm.src = 'javascript: document.write('\"+vjs.Flash.getEmbedCode(options['swf'], flashVars, params, attributes)+\"');\";\r\n\r\n      // Tried an actual local iframe just to make sure that works, but it kills the easiness of the CDN version if you require the user to host an iframe\r\n      // We should add an option to host the iframe locally though, because it could help a lot of issues.\r\n      // iFrm.src = \"iframe.html\";\r\n\r\n      // Wait until iFrame has loaded to write into it.\r\n      vjs.on(iFrm, 'load', vjs.bind(this, function(){\r\n\r\n        var iDoc,\r\n            iWin = iFrm.contentWindow;\r\n\r\n        // The one working method I found was to use the iframe's document.write() to create the swf object\r\n        // This got around the security issue in all browsers except firefox.\r\n        // I did find a hack where if I call the iframe's window.location.href='', it would get around the security error\r\n        // However, the main page would look like it was loading indefinitely (URL bar loading spinner would never stop)\r\n        // Plus Firefox 3.6 didn't work no matter what I tried.\r\n        // if (vjs.USER_AGENT.match('Firefox')) {\r\n        //   iWin.location.href = '';\r\n        // }\r\n\r\n        // Get the iFrame's document depending on what the browser supports\r\n        iDoc = iFrm.contentDocument ? iFrm.contentDocument : iFrm.contentWindow.document;\r\n\r\n        // Tried ensuring both document domains were the same, but they already were, so that wasn't the issue.\r\n        // Even tried adding /. that was mentioned in a browser security writeup\r\n        // document.domain = document.domain+'/.';\r\n        // iDoc.domain = document.domain+'/.';\r\n\r\n        // Tried adding the object to the iframe doc's innerHTML. Security error in all browsers.\r\n        // iDoc.body.innerHTML = swfObjectHTML;\r\n\r\n        // Tried appending the object to the iframe doc's body. Security error in all browsers.\r\n        // iDoc.body.appendChild(swfObject);\r\n\r\n        // Using document.write actually got around the security error that browsers were throwing.\r\n        // Again, it's a dynamically generated (same domain) iframe, loading an external Flash swf.\r\n        // Not sure why that's a security issue, but apparently it is.\r\n        iDoc.write(vjs.Flash.getEmbedCode(options['swf'], flashVars, params, attributes));\r\n\r\n        // Setting variables on the window needs to come after the doc write because otherwise they can get reset in some browsers\r\n        // So far no issues with swf ready event being called before it's set on the window.\r\n        iWin['player'] = this.player_;\r\n\r\n        // Create swf ready function for iFrame window\r\n        iWin['ready'] = vjs.bind(this.player_, function(currSwf){\r\n          var el = iDoc.getElementById(currSwf),\r\n              player = this,\r\n              tech = player.tech;\r\n\r\n          // Update reference to playback technology element\r\n          tech.el_ = el;\r\n\r\n          // Make sure swf is actually ready. Sometimes the API isn't actually yet.\r\n          vjs.Flash.checkReady(tech);\r\n        });\r\n\r\n        // Create event listener for all swf events\r\n        iWin['events'] = vjs.bind(this.player_, function(swfID, eventName){\r\n          var player = this;\r\n          if (player && player.techName === 'flash') {\r\n            player.trigger(eventName);\r\n          }\r\n        });\r\n\r\n        // Create error listener for all swf errors\r\n        iWin['errors'] = vjs.bind(this.player_, function(swfID, eventName){\r\n          vjs.log('Flash Error', eventName);\r\n        });\r\n\r\n      }));\r\n\r\n      // Replace placeholder with iFrame (it will load now)\r\n      placeHolder.parentNode.replaceChild(iFrm, placeHolder);\r\n\r\n    // If not using iFrame mode, embed as normal object\r\n    } else {\r\n      vjs.Flash.embed(options['swf'], placeHolder, flashVars, params, attributes);\r\n    }\r\n  }\r\n});\r\n\r\nvjs.Flash.prototype.dispose = function(){\r\n  vjs.MediaTechController.prototype.dispose.call(this);\r\n};\r\n\r\nvjs.Flash.prototype.play = function(){\r\n  this.el_.vjs_play();\r\n};\r\n\r\nvjs.Flash.prototype.pause = function(){\r\n  this.el_.vjs_pause();\r\n};\r\n\r\nvjs.Flash.prototype.src = function(src){\r\n  if (vjs.Flash.isStreamingSrc(src)) {\r\n    src = vjs.Flash.streamToParts(src);\r\n    this.setRtmpConnection(src.connection);\r\n    this.setRtmpStream(src.stream);\r\n  }\r\n  else {\r\n    // Make sure source URL is abosolute.\r\n    src = vjs.getAbsoluteURL(src);\r\n    this.el_.vjs_src(src);\r\n  }\r\n\r\n  // Currently the SWF doesn't autoplay if you load a source later.\r\n  // e.g. Load player w/ no source, wait 2s, set src.\r\n  if (this.player_.autoplay()) {\r\n    var tech = this;\r\n    setTimeout(function(){ tech.play(); }, 0);\r\n  }\r\n};\r\n\r\nvjs.Flash.prototype.currentSrc = function(){\r\n  var src = this.el_.vjs_getProperty('currentSrc');\r\n  // no src, check and see if RTMP\r\n  if (src == null) {\r\n    var connection = this.rtmpConnection(),\r\n        stream = this.rtmpStream();\r\n\r\n    if (connection && stream) {\r\n      src = vjs.Flash.streamFromParts(connection, stream);\r\n    }\r\n  }\r\n  return src;\r\n};\r\n\r\nvjs.Flash.prototype.load = function(){\r\n  this.el_.vjs_load();\r\n};\r\n\r\nvjs.Flash.prototype.poster = function(){\r\n  this.el_.vjs_getProperty('poster');\r\n};\r\n\r\nvjs.Flash.prototype.buffered = function(){\r\n  return vjs.createTimeRange(0, this.el_.vjs_getProperty('buffered'));\r\n};\r\n\r\nvjs.Flash.prototype.supportsFullScreen = function(){\r\n  return false; // Flash does not allow fullscreen through javascript\r\n};\r\n\r\nvjs.Flash.prototype.enterFullScreen = function(){\r\n  return false;\r\n};\r\n\r\n\r\n// Create setters and getters for attributes\r\nvar api = vjs.Flash.prototype,\r\n    readWrite = 'rtmpConnection,rtmpStream,preload,currentTime,defaultPlaybackRate,playbackRate,autoplay,loop,mediaGroup,controller,controls,volume,muted,defaultMuted'.split(','),\r\n    readOnly = 'error,currentSrc,networkState,readyState,seeking,initialTime,duration,startOffsetTime,paused,played,seekable,ended,videoTracks,audioTracks,videoWidth,videoHeight,textTracks'.split(',');\r\n    // Overridden: buffered\r\n\r\n/**\r\n * @this {*}\r\n * @private\r\n */\r\nvar createSetter = function(attr){\r\n  var attrUpper = attr.charAt(0).toUpperCase() + attr.slice(1);\r\n  api['set'+attrUpper] = function(val){ return this.el_.vjs_setProperty(attr, val); };\r\n};\r\n\r\n/**\r\n * @this {*}\r\n * @private\r\n */\r\nvar createGetter = function(attr){\r\n  api[attr] = function(){ return this.el_.vjs_getProperty(attr); };\r\n};\r\n\r\n(function(){\r\n  var i;\r\n  // Create getter and setters for all read/write attributes\r\n  for (i = 0; i < readWrite.length; i++) {\r\n    createGetter(readWrite[i]);\r\n    createSetter(readWrite[i]);\r\n  }\r\n\r\n  // Create getters for read-only attributes\r\n  for (i = 0; i < readOnly.length; i++) {\r\n    createGetter(readOnly[i]);\r\n  }\r\n})();\r\n\r\n/* Flash Support Testing -------------------------------------------------------- */\r\n\r\nvjs.Flash.isSupported = function(){\r\n  return vjs.Flash.version()[0] >= 10;\r\n  // return swfobject.hasFlashPlayerVersion('10');\r\n};\r\n\r\nvjs.Flash.canPlaySource = function(srcObj){\r\n  var type;\r\n\r\n  if (!srcObj.type) {\r\n    return '';\r\n  }\r\n\r\n  type = srcObj.type.replace(/;.*/,'').toLowerCase();\r\n  if (type in vjs.Flash.formats || type in vjs.Flash.streamingFormats) {\r\n    return 'maybe';\r\n  }\r\n};\r\n\r\nvjs.Flash.formats = {\r\n  'video/flv': 'FLV',\r\n  'video/x-flv': 'FLV',\r\n  'video/mp4': 'MP4',\r\n  'video/m4v': 'MP4'\r\n};\r\n\r\nvjs.Flash.streamingFormats = {\r\n  'rtmp/mp4': 'MP4',\r\n  'rtmp/flv': 'FLV'\r\n};\r\n\r\nvjs.Flash['onReady'] = function(currSwf){\r\n  var el = vjs.el(currSwf);\r\n\r\n  // Get player from box\r\n  // On firefox reloads, el might already have a player\r\n  var player = el['player'] || el.parentNode['player'],\r\n      tech = player.tech;\r\n\r\n  // Reference player on tech element\r\n  el['player'] = player;\r\n\r\n  // Update reference to playback technology element\r\n  tech.el_ = el;\r\n\r\n  vjs.Flash.checkReady(tech);\r\n};\r\n\r\n// The SWF isn't alwasy ready when it says it is. Sometimes the API functions still need to be added to the object.\r\n// If it's not ready, we set a timeout to check again shortly.\r\nvjs.Flash.checkReady = function(tech){\r\n\r\n  // Check if API property exists\r\n  if (tech.el().vjs_getProperty) {\r\n\r\n    // If so, tell tech it's ready\r\n    tech.triggerReady();\r\n\r\n  // Otherwise wait longer.\r\n  } else {\r\n\r\n    setTimeout(function(){\r\n      vjs.Flash.checkReady(tech);\r\n    }, 50);\r\n\r\n  }\r\n};\r\n\r\n// Trigger events from the swf on the player\r\nvjs.Flash['onEvent'] = function(swfID, eventName){\r\n  var player = vjs.el(swfID)['player'];\r\n  player.trigger(eventName);\r\n};\r\n\r\n// Log errors from the swf\r\nvjs.Flash['onError'] = function(swfID, err){\r\n  var player = vjs.el(swfID)['player'];\r\n  player.trigger('error');\r\n  vjs.log('Flash Error', err, swfID);\r\n};\r\n\r\n// Flash Version Check\r\nvjs.Flash.version = function(){\r\n  var version = '0,0,0';\r\n\r\n  // IE\r\n  try {\r\n    version = new window.ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version').replace(/\\D+/g, ',').match(/^,?(.+),?$/)[1];\r\n\r\n  // other browsers\r\n  } catch(e) {\r\n    try {\r\n      if (navigator.mimeTypes['application/x-shockwave-flash'].enabledPlugin){\r\n        version = (navigator.plugins['Shockwave Flash 2.0'] || navigator.plugins['Shockwave Flash']).description.replace(/\\D+/g, ',').match(/^,?(.+),?$/)[1];\r\n      }\r\n    } catch(err) {}\r\n  }\r\n  return version.split(',');\r\n};\r\n\r\n// Flash embedding method. Only used in non-iframe mode\r\nvjs.Flash.embed = function(swf, placeHolder, flashVars, params, attributes){\r\n  var code = vjs.Flash.getEmbedCode(swf, flashVars, params, attributes),\r\n\r\n      // Get element by embedding code and retrieving created element\r\n      obj = vjs.createEl('div', { innerHTML: code }).childNodes[0],\r\n\r\n      par = placeHolder.parentNode\r\n  ;\r\n\r\n  placeHolder.parentNode.replaceChild(obj, placeHolder);\r\n\r\n  // IE6 seems to have an issue where it won't initialize the swf object after injecting it.\r\n  // This is a dumb fix\r\n  var newObj = par.childNodes[0];\r\n  setTimeout(function(){\r\n    newObj.style.display = 'block';\r\n  }, 1000);\r\n\r\n  return obj;\r\n\r\n};\r\n\r\nvjs.Flash.getEmbedCode = function(swf, flashVars, params, attributes){\r\n\r\n  var objTag = '<object type=\"application/x-shockwave-flash\"',\r\n      flashVarsString = '',\r\n      paramsString = '',\r\n      attrsString = '';\r\n\r\n  // Convert flash vars to string\r\n  if (flashVars) {\r\n    vjs.obj.each(flashVars, function(key, val){\r\n      flashVarsString += (key + '=' + val + '&amp;');\r\n    });\r\n  }\r\n\r\n  // Add swf, flashVars, and other default params\r\n  params = vjs.obj.merge({\r\n    'movie': swf,\r\n    'flashvars': flashVarsString,\r\n    'allowScriptAccess': 'always', // Required to talk to swf\r\n    'allowNetworking': 'all' // All should be default, but having security issues.\r\n  }, params);\r\n\r\n  // Create param tags string\r\n  vjs.obj.each(params, function(key, val){\r\n    paramsString += '<param name=\"'+key+'\" value=\"'+val+'\" />';\r\n  });\r\n\r\n  attributes = vjs.obj.merge({\r\n    // Add swf to attributes (need both for IE and Others to work)\r\n    'data': swf,\r\n\r\n    // Default to 100% width/height\r\n    'width': '100%',\r\n    'height': '100%'\r\n\r\n  }, attributes);\r\n\r\n  // Create Attributes string\r\n  vjs.obj.each(attributes, function(key, val){\r\n    attrsString += (key + '=\"' + val + '\" ');\r\n  });\r\n\r\n  return objTag + attrsString + '>' + paramsString + '</object>';\r\n};\r\n\r\nvjs.Flash.streamFromParts = function(connection, stream) {\r\n  return connection + '&' + stream;\r\n};\r\n\r\nvjs.Flash.streamToParts = function(src) {\r\n  var parts = {\r\n    connection: '',\r\n    stream: ''\r\n  };\r\n\r\n  if (! src) {\r\n    return parts;\r\n  }\r\n\r\n  // Look for the normal URL separator we expect, '&'.\r\n  // If found, we split the URL into two pieces around the\r\n  // first '&'.\r\n  var connEnd = src.indexOf('&');\r\n  var streamBegin;\r\n  if (connEnd !== -1) {\r\n    streamBegin = connEnd + 1;\r\n  }\r\n  else {\r\n    // If there's not a '&', we use the last '/' as the delimiter.\r\n    connEnd = streamBegin = src.lastIndexOf('/') + 1;\r\n    if (connEnd === 0) {\r\n      // really, there's not a '/'?\r\n      connEnd = streamBegin = src.length;\r\n    }\r\n  }\r\n  parts.connection = src.substring(0, connEnd);\r\n  parts.stream = src.substring(streamBegin, src.length);\r\n\r\n  return parts;\r\n};\r\n\r\nvjs.Flash.isStreamingType = function(srcType) {\r\n  return srcType in vjs.Flash.streamingFormats;\r\n};\r\n\r\n// RTMP has four variations, any string starting\r\n// with one of these protocols should be valid\r\nvjs.Flash.RTMP_RE = /^rtmp[set]?:\\/\\//i;\r\n\r\nvjs.Flash.isStreamingSrc = function(src) {\r\n  return vjs.Flash.RTMP_RE.test(src);\r\n};\r\n/**\r\n * The Media Loader is the component that decides which playback technology to load\r\n * when the player is initialized.\r\n *\r\n * @constructor\r\n */\r\nvjs.MediaLoader = vjs.Component.extend({\r\n  /** @constructor */\r\n  init: function(player, options, ready){\r\n    vjs.Component.call(this, player, options, ready);\r\n\r\n    // If there are no sources when the player is initialized,\r\n    // load the first supported playback technology.\r\n    if (!player.options_['sources'] || player.options_['sources'].length === 0) {\r\n      for (var i=0,j=player.options_['techOrder']; i<j.length; i++) {\r\n        var techName = vjs.capitalize(j[i]),\r\n            tech = window['videojs'][techName];\r\n\r\n        // Check if the browser supports this technology\r\n        if (tech && tech.isSupported()) {\r\n          player.loadTech(techName);\r\n          break;\r\n        }\r\n      }\r\n    } else {\r\n      // // Loop through playback technologies (HTML5, Flash) and check for support.\r\n      // // Then load the best source.\r\n      // // A few assumptions here:\r\n      // //   All playback technologies respect preload false.\r\n      player.src(player.options_['sources']);\r\n    }\r\n  }\r\n});\r\n/**\r\n * @fileoverview Text Tracks\r\n * Text tracks are tracks of timed text events.\r\n * Captions - text displayed over the video for the hearing impared\r\n * Subtitles - text displayed over the video for those who don't understand langauge in the video\r\n * Chapters - text displayed in a menu allowing the user to jump to particular points (chapters) in the video\r\n * Descriptions (not supported yet) - audio descriptions that are read back to the user by a screen reading device\r\n */\r\n\r\n// Player Additions - Functions add to the player object for easier access to tracks\r\n\r\n/**\r\n * List of associated text tracks\r\n * @type {Array}\r\n * @private\r\n */\r\nvjs.Player.prototype.textTracks_;\r\n\r\n/**\r\n * Get an array of associated text tracks. captions, subtitles, chapters, descriptions\r\n * http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks\r\n * @return {Array}           Array of track objects\r\n * @private\r\n */\r\nvjs.Player.prototype.textTracks = function(){\r\n  this.textTracks_ = this.textTracks_ || [];\r\n  return this.textTracks_;\r\n};\r\n\r\n/**\r\n * Add a text track\r\n * In addition to the W3C settings we allow adding additional info through options.\r\n * http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack\r\n * @param {String}  kind        Captions, subtitles, chapters, descriptions, or metadata\r\n * @param {String=} label       Optional label\r\n * @param {String=} language    Optional language\r\n * @param {Object=} options     Additional track options, like src\r\n * @private\r\n */\r\nvjs.Player.prototype.addTextTrack = function(kind, label, language, options){\r\n  var tracks = this.textTracks_ = this.textTracks_ || [];\r\n  options = options || {};\r\n\r\n  options['kind'] = kind;\r\n  options['label'] = label;\r\n  options['language'] = language;\r\n\r\n  // HTML5 Spec says default to subtitles.\r\n  // Uppercase first letter to match class names\r\n  var Kind = vjs.capitalize(kind || 'subtitles');\r\n\r\n  // Create correct texttrack class. CaptionsTrack, etc.\r\n  var track = new window['videojs'][Kind + 'Track'](this, options);\r\n\r\n  tracks.push(track);\r\n\r\n  // If track.dflt() is set, start showing immediately\r\n  // TODO: Add a process to deterime the best track to show for the specific kind\r\n  // Incase there are mulitple defaulted tracks of the same kind\r\n  // Or the user has a set preference of a specific language that should override the default\r\n  // if (track.dflt()) {\r\n  //   this.ready(vjs.bind(track, track.show));\r\n  // }\r\n\r\n  return track;\r\n};\r\n\r\n/**\r\n * Add an array of text tracks. captions, subtitles, chapters, descriptions\r\n * Track objects will be stored in the player.textTracks() array\r\n * @param {Array} trackList Array of track elements or objects (fake track elements)\r\n * @private\r\n */\r\nvjs.Player.prototype.addTextTracks = function(trackList){\r\n  var trackObj;\r\n\r\n  for (var i = 0; i < trackList.length; i++) {\r\n    trackObj = trackList[i];\r\n    this.addTextTrack(trackObj['kind'], trackObj['label'], trackObj['language'], trackObj);\r\n  }\r\n\r\n  return this;\r\n};\r\n\r\n// Show a text track\r\n// disableSameKind: disable all other tracks of the same kind. Value should be a track kind (captions, etc.)\r\nvjs.Player.prototype.showTextTrack = function(id, disableSameKind){\r\n  var tracks = this.textTracks_,\r\n      i = 0,\r\n      j = tracks.length,\r\n      track, showTrack, kind;\r\n\r\n  // Find Track with same ID\r\n  for (;i<j;i++) {\r\n    track = tracks[i];\r\n    if (track.id() === id) {\r\n      track.show();\r\n      showTrack = track;\r\n\r\n    // Disable tracks of the same kind\r\n    } else if (disableSameKind && track.kind() == disableSameKind && track.mode() > 0) {\r\n      track.disable();\r\n    }\r\n  }\r\n\r\n  // Get track kind from shown track or disableSameKind\r\n  kind = (showTrack) ? showTrack.kind() : ((disableSameKind) ? disableSameKind : false);\r\n\r\n  // Trigger trackchange event, captionstrackchange, subtitlestrackchange, etc.\r\n  if (kind) {\r\n    this.trigger(kind+'trackchange');\r\n  }\r\n\r\n  return this;\r\n};\r\n\r\n/**\r\n * The base class for all text tracks\r\n *\r\n * Handles the parsing, hiding, and showing of text track cues\r\n *\r\n * @param {vjs.Player|Object} player\r\n * @param {Object=} options\r\n * @constructor\r\n */\r\nvjs.TextTrack = vjs.Component.extend({\r\n  /** @constructor */\r\n  init: function(player, options){\r\n    vjs.Component.call(this, player, options);\r\n\r\n    // Apply track info to track object\r\n    // Options will often be a track element\r\n\r\n    // Build ID if one doesn't exist\r\n    this.id_ = options['id'] || ('vjs_' + options['kind'] + '_' + options['language'] + '_' + vjs.guid++);\r\n    this.src_ = options['src'];\r\n    // 'default' is a reserved keyword in js so we use an abbreviated version\r\n    this.dflt_ = options['default'] || options['dflt'];\r\n    this.title_ = options['title'];\r\n    this.language_ = options['srclang'];\r\n    this.label_ = options['label'];\r\n    this.cues_ = [];\r\n    this.activeCues_ = [];\r\n    this.readyState_ = 0;\r\n    this.mode_ = 0;\r\n\r\n    this.player_.on('fullscreenchange', vjs.bind(this, this.adjustFontSize));\r\n  }\r\n});\r\n\r\n/**\r\n * Track kind value. Captions, subtitles, etc.\r\n * @private\r\n */\r\nvjs.TextTrack.prototype.kind_;\r\n\r\n/**\r\n * Get the track kind value\r\n * @return {String}\r\n */\r\nvjs.TextTrack.prototype.kind = function(){\r\n  return this.kind_;\r\n};\r\n\r\n/**\r\n * Track src value\r\n * @private\r\n */\r\nvjs.TextTrack.prototype.src_;\r\n\r\n/**\r\n * Get the track src value\r\n * @return {String}\r\n */\r\nvjs.TextTrack.prototype.src = function(){\r\n  return this.src_;\r\n};\r\n\r\n/**\r\n * Track default value\r\n * If default is used, subtitles/captions to start showing\r\n * @private\r\n */\r\nvjs.TextTrack.prototype.dflt_;\r\n\r\n/**\r\n * Get the track default value. ('default' is a reserved keyword)\r\n * @return {Boolean}\r\n */\r\nvjs.TextTrack.prototype.dflt = function(){\r\n  return this.dflt_;\r\n};\r\n\r\n/**\r\n * Track title value\r\n * @private\r\n */\r\nvjs.TextTrack.prototype.title_;\r\n\r\n/**\r\n * Get the track title value\r\n * @return {String}\r\n */\r\nvjs.TextTrack.prototype.title = function(){\r\n  return this.title_;\r\n};\r\n\r\n/**\r\n * Language - two letter string to represent track language, e.g. 'en' for English\r\n * Spec def: readonly attribute DOMString language;\r\n * @private\r\n */\r\nvjs.TextTrack.prototype.language_;\r\n\r\n/**\r\n * Get the track language value\r\n * @return {String}\r\n */\r\nvjs.TextTrack.prototype.language = function(){\r\n  return this.language_;\r\n};\r\n\r\n/**\r\n * Track label e.g. 'English'\r\n * Spec def: readonly attribute DOMString label;\r\n * @private\r\n */\r\nvjs.TextTrack.prototype.label_;\r\n\r\n/**\r\n * Get the track label value\r\n * @return {String}\r\n */\r\nvjs.TextTrack.prototype.label = function(){\r\n  return this.label_;\r\n};\r\n\r\n/**\r\n * All cues of the track. Cues have a startTime, endTime, text, and other properties.\r\n * Spec def: readonly attribute TextTrackCueList cues;\r\n * @private\r\n */\r\nvjs.TextTrack.prototype.cues_;\r\n\r\n/**\r\n * Get the track cues\r\n * @return {Array}\r\n */\r\nvjs.TextTrack.prototype.cues = function(){\r\n  return this.cues_;\r\n};\r\n\r\n/**\r\n * ActiveCues is all cues that are currently showing\r\n * Spec def: readonly attribute TextTrackCueList activeCues;\r\n * @private\r\n */\r\nvjs.TextTrack.prototype.activeCues_;\r\n\r\n/**\r\n * Get the track active cues\r\n * @return {Array}\r\n */\r\nvjs.TextTrack.prototype.activeCues = function(){\r\n  return this.activeCues_;\r\n};\r\n\r\n/**\r\n * ReadyState describes if the text file has been loaded\r\n * const unsigned short NONE = 0;\r\n * const unsigned short LOADING = 1;\r\n * const unsigned short LOADED = 2;\r\n * const unsigned short ERROR = 3;\r\n * readonly attribute unsigned short readyState;\r\n * @private\r\n */\r\nvjs.TextTrack.prototype.readyState_;\r\n\r\n/**\r\n * Get the track readyState\r\n * @return {Number}\r\n */\r\nvjs.TextTrack.prototype.readyState = function(){\r\n  return this.readyState_;\r\n};\r\n\r\n/**\r\n * Mode describes if the track is showing, hidden, or disabled\r\n * const unsigned short OFF = 0;\r\n * const unsigned short HIDDEN = 1; (still triggering cuechange events, but not visible)\r\n * const unsigned short SHOWING = 2;\r\n * attribute unsigned short mode;\r\n * @private\r\n */\r\nvjs.TextTrack.prototype.mode_;\r\n\r\n/**\r\n * Get the track mode\r\n * @return {Number}\r\n */\r\nvjs.TextTrack.prototype.mode = function(){\r\n  return this.mode_;\r\n};\r\n\r\n/**\r\n * Change the font size of the text track to make it larger when playing in fullscreen mode\r\n * and restore it to its normal size when not in fullscreen mode.\r\n */\r\nvjs.TextTrack.prototype.adjustFontSize = function(){\r\n    if (this.player_.isFullScreen) {\r\n        // Scale the font by the same factor as increasing the video width to the full screen window width.\r\n        // Additionally, multiply that factor by 1.4, which is the default font size for\r\n        // the caption track (from the CSS)\r\n        this.el_.style.fontSize = screen.width / this.player_.width() * 1.4 * 100 + '%';\r\n    } else {\r\n        // Change the font size of the text track back to its original non-fullscreen size\r\n        this.el_.style.fontSize = '';\r\n    }\r\n};\r\n\r\n/**\r\n * Create basic div to hold cue text\r\n * @return {Element}\r\n */\r\nvjs.TextTrack.prototype.createEl = function(){\r\n  return vjs.Component.prototype.createEl.call(this, 'div', {\r\n    className: 'vjs-' + this.kind_ + ' vjs-text-track'\r\n  });\r\n};\r\n\r\n/**\r\n * Show: Mode Showing (2)\r\n * Indicates that the text track is active. If no attempt has yet been made to obtain the track's cues, the user agent will perform such an attempt momentarily.\r\n * The user agent is maintaining a list of which cues are active, and events are being fired accordingly.\r\n * In addition, for text tracks whose kind is subtitles or captions, the cues are being displayed over the video as appropriate;\r\n * for text tracks whose kind is descriptions, the user agent is making the cues available to the user in a non-visual fashion;\r\n * and for text tracks whose kind is chapters, the user agent is making available to the user a mechanism by which the user can navigate to any point in the media resource by selecting a cue.\r\n * The showing by default state is used in conjunction with the default attribute on track elements to indicate that the text track was enabled due to that attribute.\r\n * This allows the user agent to override the state if a later track is discovered that is more appropriate per the user's preferences.\r\n */\r\nvjs.TextTrack.prototype.show = function(){\r\n  this.activate();\r\n\r\n  this.mode_ = 2;\r\n\r\n  // Show element.\r\n  vjs.Component.prototype.show.call(this);\r\n};\r\n\r\n/**\r\n * Hide: Mode Hidden (1)\r\n * Indicates that the text track is active, but that the user agent is not actively displaying the cues.\r\n * If no attempt has yet been made to obtain the track's cues, the user agent will perform such an attempt momentarily.\r\n * The user agent is maintaining a list of which cues are active, and events are being fired accordingly.\r\n */\r\nvjs.TextTrack.prototype.hide = function(){\r\n  // When hidden, cues are still triggered. Disable to stop triggering.\r\n  this.activate();\r\n\r\n  this.mode_ = 1;\r\n\r\n  // Hide element.\r\n  vjs.Component.prototype.hide.call(this);\r\n};\r\n\r\n/**\r\n * Disable: Mode Off/Disable (0)\r\n * Indicates that the text track is not active. Other than for the purposes of exposing the track in the DOM, the user agent is ignoring the text track.\r\n * No cues are active, no events are fired, and the user agent will not attempt to obtain the track's cues.\r\n */\r\nvjs.TextTrack.prototype.disable = function(){\r\n  // If showing, hide.\r\n  if (this.mode_ == 2) { this.hide(); }\r\n\r\n  // Stop triggering cues\r\n  this.deactivate();\r\n\r\n  // Switch Mode to Off\r\n  this.mode_ = 0;\r\n};\r\n\r\n/**\r\n * Turn on cue tracking. Tracks that are showing OR hidden are active.\r\n */\r\nvjs.TextTrack.prototype.activate = function(){\r\n  // Load text file if it hasn't been yet.\r\n  if (this.readyState_ === 0) { this.load(); }\r\n\r\n  // Only activate if not already active.\r\n  if (this.mode_ === 0) {\r\n    // Update current cue on timeupdate\r\n    // Using unique ID for bind function so other tracks don't remove listener\r\n    this.player_.on('timeupdate', vjs.bind(this, this.update, this.id_));\r\n\r\n    // Reset cue time on media end\r\n    this.player_.on('ended', vjs.bind(this, this.reset, this.id_));\r\n\r\n    // Add to display\r\n    if (this.kind_ === 'captions' || this.kind_ === 'subtitles') {\r\n      this.player_.getChild('textTrackDisplay').addChild(this);\r\n    }\r\n  }\r\n};\r\n\r\n/**\r\n * Turn off cue tracking.\r\n */\r\nvjs.TextTrack.prototype.deactivate = function(){\r\n  // Using unique ID for bind function so other tracks don't remove listener\r\n  this.player_.off('timeupdate', vjs.bind(this, this.update, this.id_));\r\n  this.player_.off('ended', vjs.bind(this, this.reset, this.id_));\r\n  this.reset(); // Reset\r\n\r\n  // Remove from display\r\n  this.player_.getChild('textTrackDisplay').removeChild(this);\r\n};\r\n\r\n// A readiness state\r\n// One of the following:\r\n//\r\n// Not loaded\r\n// Indicates that the text track is known to exist (e.g. it has been declared with a track element), but its cues have not been obtained.\r\n//\r\n// Loading\r\n// Indicates that the text track is loading and there have been no fatal errors encountered so far. Further cues might still be added to the track.\r\n//\r\n// Loaded\r\n// Indicates that the text track has been loaded with no fatal errors. No new cues will be added to the track except if the text track corresponds to a MutableTextTrack object.\r\n//\r\n// Failed to load\r\n// Indicates that the text track was enabled, but when the user agent attempted to obtain it, this failed in some way (e.g. URL could not be resolved, network error, unknown text track format). Some or all of the cues are likely missing and will not be obtained.\r\nvjs.TextTrack.prototype.load = function(){\r\n\r\n  // Only load if not loaded yet.\r\n  if (this.readyState_ === 0) {\r\n    this.readyState_ = 1;\r\n    vjs.get(this.src_, vjs.bind(this, this.parseCues), vjs.bind(this, this.onError));\r\n  }\r\n\r\n};\r\n\r\nvjs.TextTrack.prototype.onError = function(err){\r\n  this.error = err;\r\n  this.readyState_ = 3;\r\n  this.trigger('error');\r\n};\r\n\r\n// Parse the WebVTT text format for cue times.\r\n// TODO: Separate parser into own class so alternative timed text formats can be used. (TTML, DFXP)\r\nvjs.TextTrack.prototype.parseCues = function(srcContent) {\r\n  var cue, time, text,\r\n      lines = srcContent.split('\\n'),\r\n      line = '', id;\r\n\r\n  for (var i=1, j=lines.length; i<j; i++) {\r\n    // Line 0 should be 'WEBVTT', so skipping i=0\r\n\r\n    line = vjs.trim(lines[i]); // Trim whitespace and linebreaks\r\n\r\n    if (line) { // Loop until a line with content\r\n\r\n      // First line could be an optional cue ID\r\n      // Check if line has the time separator\r\n      if (line.indexOf('-->') == -1) {\r\n        id = line;\r\n        // Advance to next line for timing.\r\n        line = vjs.trim(lines[++i]);\r\n      } else {\r\n        id = this.cues_.length;\r\n      }\r\n\r\n      // First line - Number\r\n      cue = {\r\n        id: id, // Cue Number\r\n        index: this.cues_.length // Position in Array\r\n      };\r\n\r\n      // Timing line\r\n      time = line.split(' --> ');\r\n      cue.startTime = this.parseCueTime(time[0]);\r\n      cue.endTime = this.parseCueTime(time[1]);\r\n\r\n      // Additional lines - Cue Text\r\n      text = [];\r\n\r\n      // Loop until a blank line or end of lines\r\n      // Assumeing trim('') returns false for blank lines\r\n      while (lines[++i] && (line = vjs.trim(lines[i]))) {\r\n        text.push(line);\r\n      }\r\n\r\n      cue.text = text.join('<br/>');\r\n\r\n      // Add this cue\r\n      this.cues_.push(cue);\r\n    }\r\n  }\r\n\r\n  this.readyState_ = 2;\r\n  this.trigger('loaded');\r\n};\r\n\r\n\r\nvjs.TextTrack.prototype.parseCueTime = function(timeText) {\r\n  var parts = timeText.split(':'),\r\n      time = 0,\r\n      hours, minutes, other, seconds, ms;\r\n\r\n  // Check if optional hours place is included\r\n  // 00:00:00.000 vs. 00:00.000\r\n  if (parts.length == 3) {\r\n    hours = parts[0];\r\n    minutes = parts[1];\r\n    other = parts[2];\r\n  } else {\r\n    hours = 0;\r\n    minutes = parts[0];\r\n    other = parts[1];\r\n  }\r\n\r\n  // Break other (seconds, milliseconds, and flags) by spaces\r\n  // TODO: Make additional cue layout settings work with flags\r\n  other = other.split(/\\s+/);\r\n  // Remove seconds. Seconds is the first part before any spaces.\r\n  seconds = other.splice(0,1)[0];\r\n  // Could use either . or , for decimal\r\n  seconds = seconds.split(/\\.|,/);\r\n  // Get milliseconds\r\n  ms = parseFloat(seconds[1]);\r\n  seconds = seconds[0];\r\n\r\n  // hours => seconds\r\n  time += parseFloat(hours) * 3600;\r\n  // minutes => seconds\r\n  time += parseFloat(minutes) * 60;\r\n  // Add seconds\r\n  time += parseFloat(seconds);\r\n  // Add milliseconds\r\n  if (ms) { time += ms/1000; }\r\n\r\n  return time;\r\n};\r\n\r\n// Update active cues whenever timeupdate events are triggered on the player.\r\nvjs.TextTrack.prototype.update = function(){\r\n  if (this.cues_.length > 0) {\r\n\r\n    // Get curent player time\r\n    var time = this.player_.currentTime();\r\n\r\n    // Check if the new time is outside the time box created by the the last update.\r\n    if (this.prevChange === undefined || time < this.prevChange || this.nextChange <= time) {\r\n      var cues = this.cues_,\r\n\r\n          // Create a new time box for this state.\r\n          newNextChange = this.player_.duration(), // Start at beginning of the timeline\r\n          newPrevChange = 0, // Start at end\r\n\r\n          reverse = false, // Set the direction of the loop through the cues. Optimized the cue check.\r\n          newCues = [], // Store new active cues.\r\n\r\n          // Store where in the loop the current active cues are, to provide a smart starting point for the next loop.\r\n          firstActiveIndex, lastActiveIndex,\r\n          cue, i; // Loop vars\r\n\r\n      // Check if time is going forwards or backwards (scrubbing/rewinding)\r\n      // If we know the direction we can optimize the starting position and direction of the loop through the cues array.\r\n      if (time >= this.nextChange || this.nextChange === undefined) { // NextChange should happen\r\n        // Forwards, so start at the index of the first active cue and loop forward\r\n        i = (this.firstActiveIndex !== undefined) ? this.firstActiveIndex : 0;\r\n      } else {\r\n        // Backwards, so start at the index of the last active cue and loop backward\r\n        reverse = true;\r\n        i = (this.lastActiveIndex !== undefined) ? this.lastActiveIndex : cues.length - 1;\r\n      }\r\n\r\n      while (true) { // Loop until broken\r\n        cue = cues[i];\r\n\r\n        // Cue ended at this point\r\n        if (cue.endTime <= time) {\r\n          newPrevChange = Math.max(newPrevChange, cue.endTime);\r\n\r\n          if (cue.active) {\r\n            cue.active = false;\r\n          }\r\n\r\n          // No earlier cues should have an active start time.\r\n          // Nevermind. Assume first cue could have a duration the same as the video.\r\n          // In that case we need to loop all the way back to the beginning.\r\n          // if (reverse && cue.startTime) { break; }\r\n\r\n        // Cue hasn't started\r\n        } else if (time < cue.startTime) {\r\n          newNextChange = Math.min(newNextChange, cue.startTime);\r\n\r\n          if (cue.active) {\r\n            cue.active = false;\r\n          }\r\n\r\n          // No later cues should have an active start time.\r\n          if (!reverse) { break; }\r\n\r\n        // Cue is current\r\n        } else {\r\n\r\n          if (reverse) {\r\n            // Add cue to front of array to keep in time order\r\n            newCues.splice(0,0,cue);\r\n\r\n            // If in reverse, the first current cue is our lastActiveCue\r\n            if (lastActiveIndex === undefined) { lastActiveIndex = i; }\r\n            firstActiveIndex = i;\r\n          } else {\r\n            // Add cue to end of array\r\n            newCues.push(cue);\r\n\r\n            // If forward, the first current cue is our firstActiveIndex\r\n            if (firstActiveIndex === undefined) { firstActiveIndex = i; }\r\n            lastActiveIndex = i;\r\n          }\r\n\r\n          newNextChange = Math.min(newNextChange, cue.endTime);\r\n          newPrevChange = Math.max(newPrevChange, cue.startTime);\r\n\r\n          cue.active = true;\r\n        }\r\n\r\n        if (reverse) {\r\n          // Reverse down the array of cues, break if at first\r\n          if (i === 0) { break; } else { i--; }\r\n        } else {\r\n          // Walk up the array fo cues, break if at last\r\n          if (i === cues.length - 1) { break; } else { i++; }\r\n        }\r\n\r\n      }\r\n\r\n      this.activeCues_ = newCues;\r\n      this.nextChange = newNextChange;\r\n      this.prevChange = newPrevChange;\r\n      this.firstActiveIndex = firstActiveIndex;\r\n      this.lastActiveIndex = lastActiveIndex;\r\n\r\n      this.updateDisplay();\r\n\r\n      this.trigger('cuechange');\r\n    }\r\n  }\r\n};\r\n\r\n// Add cue HTML to display\r\nvjs.TextTrack.prototype.updateDisplay = function(){\r\n  var cues = this.activeCues_,\r\n      html = '',\r\n      i=0,j=cues.length;\r\n\r\n  for (;i<j;i++) {\r\n    html += '<span class=\"vjs-tt-cue\">'+cues[i].text+'</span>';\r\n  }\r\n\r\n  this.el_.innerHTML = html;\r\n};\r\n\r\n// Set all loop helper values back\r\nvjs.TextTrack.prototype.reset = function(){\r\n  this.nextChange = 0;\r\n  this.prevChange = this.player_.duration();\r\n  this.firstActiveIndex = 0;\r\n  this.lastActiveIndex = 0;\r\n};\r\n\r\n// Create specific track types\r\n/**\r\n * The track component for managing the hiding and showing of captions\r\n *\r\n * @constructor\r\n */\r\nvjs.CaptionsTrack = vjs.TextTrack.extend();\r\nvjs.CaptionsTrack.prototype.kind_ = 'captions';\r\n// Exporting here because Track creation requires the track kind\r\n// to be available on global object. e.g. new window['videojs'][Kind + 'Track']\r\n\r\n/**\r\n * The track component for managing the hiding and showing of subtitles\r\n *\r\n * @constructor\r\n */\r\nvjs.SubtitlesTrack = vjs.TextTrack.extend();\r\nvjs.SubtitlesTrack.prototype.kind_ = 'subtitles';\r\n\r\n/**\r\n * The track component for managing the hiding and showing of chapters\r\n *\r\n * @constructor\r\n */\r\nvjs.ChaptersTrack = vjs.TextTrack.extend();\r\nvjs.ChaptersTrack.prototype.kind_ = 'chapters';\r\n\r\n\r\n/* Text Track Display\r\n============================================================================= */\r\n// Global container for both subtitle and captions text. Simple div container.\r\n\r\n/**\r\n * The component for displaying text track cues\r\n *\r\n * @constructor\r\n */\r\nvjs.TextTrackDisplay = vjs.Component.extend({\r\n  /** @constructor */\r\n  init: function(player, options, ready){\r\n    vjs.Component.call(this, player, options, ready);\r\n\r\n    // This used to be called during player init, but was causing an error\r\n    // if a track should show by default and the display hadn't loaded yet.\r\n    // Should probably be moved to an external track loader when we support\r\n    // tracks that don't need a display.\r\n    if (player.options_['tracks'] && player.options_['tracks'].length > 0) {\r\n      this.player_.addTextTracks(player.options_['tracks']);\r\n    }\r\n  }\r\n});\r\n\r\nvjs.TextTrackDisplay.prototype.createEl = function(){\r\n  return vjs.Component.prototype.createEl.call(this, 'div', {\r\n    className: 'vjs-text-track-display'\r\n  });\r\n};\r\n\r\n\r\n/**\r\n * The specific menu item type for selecting a language within a text track kind\r\n *\r\n * @constructor\r\n */\r\nvjs.TextTrackMenuItem = vjs.MenuItem.extend({\r\n  /** @constructor */\r\n  init: function(player, options){\r\n    var track = this.track = options['track'];\r\n\r\n    // Modify options for parent MenuItem class's init.\r\n    options['label'] = track.label();\r\n    options['selected'] = track.dflt();\r\n    vjs.MenuItem.call(this, player, options);\r\n\r\n    this.player_.on(track.kind() + 'trackchange', vjs.bind(this, this.update));\r\n  }\r\n});\r\n\r\nvjs.TextTrackMenuItem.prototype.onClick = function(){\r\n  vjs.MenuItem.prototype.onClick.call(this);\r\n  this.player_.showTextTrack(this.track.id_, this.track.kind());\r\n};\r\n\r\nvjs.TextTrackMenuItem.prototype.update = function(){\r\n  this.selected(this.track.mode() == 2);\r\n};\r\n\r\n/**\r\n * A special menu item for turning of a specific type of text track\r\n *\r\n * @constructor\r\n */\r\nvjs.OffTextTrackMenuItem = vjs.TextTrackMenuItem.extend({\r\n  /** @constructor */\r\n  init: function(player, options){\r\n    // Create pseudo track info\r\n    // Requires options['kind']\r\n    options['track'] = {\r\n      kind: function() { return options['kind']; },\r\n      player: player,\r\n      label: function(){ return options['kind'] + ' off'; },\r\n      dflt: function(){ return false; },\r\n      mode: function(){ return false; }\r\n    };\r\n    vjs.TextTrackMenuItem.call(this, player, options);\r\n    this.selected(true);\r\n  }\r\n});\r\n\r\nvjs.OffTextTrackMenuItem.prototype.onClick = function(){\r\n  vjs.TextTrackMenuItem.prototype.onClick.call(this);\r\n  this.player_.showTextTrack(this.track.id_, this.track.kind());\r\n};\r\n\r\nvjs.OffTextTrackMenuItem.prototype.update = function(){\r\n  var tracks = this.player_.textTracks(),\r\n      i=0, j=tracks.length, track,\r\n      off = true;\r\n\r\n  for (;i<j;i++) {\r\n    track = tracks[i];\r\n    if (track.kind() == this.track.kind() && track.mode() == 2) {\r\n      off = false;\r\n    }\r\n  }\r\n\r\n  this.selected(off);\r\n};\r\n\r\n/**\r\n * The base class for buttons that toggle specific text track types (e.g. subtitles)\r\n *\r\n * @constructor\r\n */\r\nvjs.TextTrackButton = vjs.MenuButton.extend({\r\n  /** @constructor */\r\n  init: function(player, options){\r\n    vjs.MenuButton.call(this, player, options);\r\n\r\n    if (this.items.length <= 1) {\r\n      this.hide();\r\n    }\r\n  }\r\n});\r\n\r\n// vjs.TextTrackButton.prototype.buttonPressed = false;\r\n\r\n// vjs.TextTrackButton.prototype.createMenu = function(){\r\n//   var menu = new vjs.Menu(this.player_);\r\n\r\n//   // Add a title list item to the top\r\n//   // menu.el().appendChild(vjs.createEl('li', {\r\n//   //   className: 'vjs-menu-title',\r\n//   //   innerHTML: vjs.capitalize(this.kind_),\r\n//   //   tabindex: -1\r\n//   // }));\r\n\r\n//   this.items = this.createItems();\r\n\r\n//   // Add menu items to the menu\r\n//   for (var i = 0; i < this.items.length; i++) {\r\n//     menu.addItem(this.items[i]);\r\n//   }\r\n\r\n//   // Add list to element\r\n//   this.addChild(menu);\r\n\r\n//   return menu;\r\n// };\r\n\r\n// Create a menu item for each text track\r\nvjs.TextTrackButton.prototype.createItems = function(){\r\n  var items = [], track;\r\n\r\n  // Add an OFF menu item to turn all tracks off\r\n  items.push(new vjs.OffTextTrackMenuItem(this.player_, { 'kind': this.kind_ }));\r\n\r\n  for (var i = 0; i < this.player_.textTracks().length; i++) {\r\n    track = this.player_.textTracks()[i];\r\n    if (track.kind() === this.kind_) {\r\n      items.push(new vjs.TextTrackMenuItem(this.player_, {\r\n        'track': track\r\n      }));\r\n    }\r\n  }\r\n\r\n  return items;\r\n};\r\n\r\n/**\r\n * The button component for toggling and selecting captions\r\n *\r\n * @constructor\r\n */\r\nvjs.CaptionsButton = vjs.TextTrackButton.extend({\r\n  /** @constructor */\r\n  init: function(player, options, ready){\r\n    vjs.TextTrackButton.call(this, player, options, ready);\r\n    this.el_.setAttribute('aria-label','Captions Menu');\r\n  }\r\n});\r\nvjs.CaptionsButton.prototype.kind_ = 'captions';\r\nvjs.CaptionsButton.prototype.buttonText = 'Captions';\r\nvjs.CaptionsButton.prototype.className = 'vjs-captions-button';\r\n\r\n/**\r\n * The button component for toggling and selecting subtitles\r\n *\r\n * @constructor\r\n */\r\nvjs.SubtitlesButton = vjs.TextTrackButton.extend({\r\n  /** @constructor */\r\n  init: function(player, options, ready){\r\n    vjs.TextTrackButton.call(this, player, options, ready);\r\n    this.el_.setAttribute('aria-label','Subtitles Menu');\r\n  }\r\n});\r\nvjs.SubtitlesButton.prototype.kind_ = 'subtitles';\r\nvjs.SubtitlesButton.prototype.buttonText = 'Subtitles';\r\nvjs.SubtitlesButton.prototype.className = 'vjs-subtitles-button';\r\n\r\n// Chapters act much differently than other text tracks\r\n// Cues are navigation vs. other tracks of alternative languages\r\n/**\r\n * The button component for toggling and selecting chapters\r\n *\r\n * @constructor\r\n */\r\nvjs.ChaptersButton = vjs.TextTrackButton.extend({\r\n  /** @constructor */\r\n  init: function(player, options, ready){\r\n    vjs.TextTrackButton.call(this, player, options, ready);\r\n    this.el_.setAttribute('aria-label','Chapters Menu');\r\n  }\r\n});\r\nvjs.ChaptersButton.prototype.kind_ = 'chapters';\r\nvjs.ChaptersButton.prototype.buttonText = 'Chapters';\r\nvjs.ChaptersButton.prototype.className = 'vjs-chapters-button';\r\n\r\n// Create a menu item for each text track\r\nvjs.ChaptersButton.prototype.createItems = function(){\r\n  var items = [], track;\r\n\r\n  for (var i = 0; i < this.player_.textTracks().length; i++) {\r\n    track = this.player_.textTracks()[i];\r\n    if (track.kind() === this.kind_) {\r\n      items.push(new vjs.TextTrackMenuItem(this.player_, {\r\n        'track': track\r\n      }));\r\n    }\r\n  }\r\n\r\n  return items;\r\n};\r\n\r\nvjs.ChaptersButton.prototype.createMenu = function(){\r\n  var tracks = this.player_.textTracks(),\r\n      i = 0,\r\n      j = tracks.length,\r\n      track, chaptersTrack,\r\n      items = this.items = [];\r\n\r\n  for (;i<j;i++) {\r\n    track = tracks[i];\r\n    if (track.kind() == this.kind_ && track.dflt()) {\r\n      if (track.readyState() < 2) {\r\n        this.chaptersTrack = track;\r\n        track.on('loaded', vjs.bind(this, this.createMenu));\r\n        return;\r\n      } else {\r\n        chaptersTrack = track;\r\n        break;\r\n      }\r\n    }\r\n  }\r\n\r\n  var menu = this.menu = new vjs.Menu(this.player_);\r\n\r\n  menu.el_.appendChild(vjs.createEl('li', {\r\n    className: 'vjs-menu-title',\r\n    innerHTML: vjs.capitalize(this.kind_),\r\n    tabindex: -1\r\n  }));\r\n\r\n  if (chaptersTrack) {\r\n    var cues = chaptersTrack.cues_, cue, mi;\r\n    i = 0;\r\n    j = cues.length;\r\n\r\n    for (;i<j;i++) {\r\n      cue = cues[i];\r\n\r\n      mi = new vjs.ChaptersTrackMenuItem(this.player_, {\r\n        'track': chaptersTrack,\r\n        'cue': cue\r\n      });\r\n\r\n      items.push(mi);\r\n\r\n      menu.addChild(mi);\r\n    }\r\n  }\r\n\r\n  if (this.items.length > 0) {\r\n    this.show();\r\n  }\r\n\r\n  return menu;\r\n};\r\n\r\n\r\n/**\r\n * @constructor\r\n */\r\nvjs.ChaptersTrackMenuItem = vjs.MenuItem.extend({\r\n  /** @constructor */\r\n  init: function(player, options){\r\n    var track = this.track = options['track'],\r\n        cue = this.cue = options['cue'],\r\n        currentTime = player.currentTime();\r\n\r\n    // Modify options for parent MenuItem class's init.\r\n    options['label'] = cue.text;\r\n    options['selected'] = (cue.startTime <= currentTime && currentTime < cue.endTime);\r\n    vjs.MenuItem.call(this, player, options);\r\n\r\n    track.on('cuechange', vjs.bind(this, this.update));\r\n  }\r\n});\r\n\r\nvjs.ChaptersTrackMenuItem.prototype.onClick = function(){\r\n  vjs.MenuItem.prototype.onClick.call(this);\r\n  this.player_.currentTime(this.cue.startTime);\r\n  this.update(this.cue.startTime);\r\n};\r\n\r\nvjs.ChaptersTrackMenuItem.prototype.update = function(){\r\n  var cue = this.cue,\r\n      currentTime = this.player_.currentTime();\r\n\r\n  // vjs.log(currentTime, cue.startTime);\r\n  this.selected(cue.startTime <= currentTime && currentTime < cue.endTime);\r\n};\r\n\r\n// Add Buttons to controlBar\r\nvjs.obj.merge(vjs.ControlBar.prototype.options_['children'], {\r\n  'subtitlesButton': {},\r\n  'captionsButton': {},\r\n  'chaptersButton': {}\r\n});\r\n\r\n// vjs.Cue = vjs.Component.extend({\r\n//   /** @constructor */\r\n//   init: function(player, options){\r\n//     vjs.Component.call(this, player, options);\r\n//   }\r\n// });\r\n/**\r\n * @fileoverview Add JSON support\r\n * @suppress {undefinedVars}\r\n * (Compiler doesn't like JSON not being declared)\r\n */\r\n\r\n/**\r\n * Javascript JSON implementation\r\n * (Parse Method Only)\r\n * https://github.com/douglascrockford/JSON-js/blob/master/json2.js\r\n * Only using for parse method when parsing data-setup attribute JSON.\r\n * @suppress {undefinedVars}\r\n * @namespace\r\n * @private\r\n */\r\nvjs.JSON;\r\n\r\nif (typeof window.JSON !== 'undefined' && window.JSON.parse === 'function') {\r\n  vjs.JSON = window.JSON;\r\n\r\n} else {\r\n  vjs.JSON = {};\r\n\r\n  var cx = /[\\u0000\\u00ad\\u0600-\\u0604\\u070f\\u17b4\\u17b5\\u200c-\\u200f\\u2028-\\u202f\\u2060-\\u206f\\ufeff\\ufff0-\\uffff]/g;\r\n\r\n  /**\r\n   * parse the json\r\n   *\r\n   * @memberof vjs.JSON\r\n   * @return {Object|Array} The parsed JSON\r\n   */\r\n  vjs.JSON.parse = function (text, reviver) {\r\n      var j;\r\n\r\n      function walk(holder, key) {\r\n          var k, v, value = holder[key];\r\n          if (value && typeof value === 'object') {\r\n              for (k in value) {\r\n                  if (Object.prototype.hasOwnProperty.call(value, k)) {\r\n                      v = walk(value, k);\r\n                      if (v !== undefined) {\r\n                          value[k] = v;\r\n                      } else {\r\n                          delete value[k];\r\n                      }\r\n                  }\r\n              }\r\n          }\r\n          return reviver.call(holder, key, value);\r\n      }\r\n      text = String(text);\r\n      cx.lastIndex = 0;\r\n      if (cx.test(text)) {\r\n          text = text.replace(cx, function (a) {\r\n              return '\\\\u' +\r\n                  ('0000' + a.charCodeAt(0).toString(16)).slice(-4);\r\n          });\r\n      }\r\n\r\n      if (/^[\\],:{}\\s]*$/\r\n              .test(text.replace(/\\\\(?:[\"\\\\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')\r\n                  .replace(/\"[^\"\\\\\\n\\r]*\"|true|false|null|-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?/g, ']')\r\n                  .replace(/(?:^|:|,)(?:\\s*\\[)+/g, ''))) {\r\n\r\n          j = eval('(' + text + ')');\r\n\r\n          return typeof reviver === 'function' ?\r\n              walk({'': j}, '') : j;\r\n      }\r\n\r\n      throw new SyntaxError('JSON.parse(): invalid or malformed JSON data');\r\n  };\r\n}\r\n/**\r\n * @fileoverview Functions for automatically setting up a player\r\n * based on the data-setup attribute of the video tag\r\n */\r\n\r\n// Automatically set up any tags that have a data-setup attribute\r\nvjs.autoSetup = function(){\r\n  var options, vid, player,\r\n      vids = document.getElementsByTagName('video');\r\n\r\n  // Check if any media elements exist\r\n  if (vids && vids.length > 0) {\r\n\r\n    for (var i=0,j=vids.length; i<j; i++) {\r\n      vid = vids[i];\r\n\r\n      // Check if element exists, has getAttribute func.\r\n      // IE seems to consider typeof el.getAttribute == 'object' instead of 'function' like expected, at least when loading the player immediately.\r\n      if (vid && vid.getAttribute) {\r\n\r\n        // Make sure this player hasn't already been set up.\r\n        if (vid['player'] === undefined) {\r\n          options = vid.getAttribute('data-setup');\r\n\r\n          // Check if data-setup attr exists.\r\n          // We only auto-setup if they've added the data-setup attr.\r\n          if (options !== null) {\r\n\r\n            // Parse options JSON\r\n            // If empty string, make it a parsable json object.\r\n            options = vjs.JSON.parse(options || '{}');\r\n\r\n            // Create new video.js instance.\r\n            player = videojs(vid, options);\r\n          }\r\n        }\r\n\r\n      // If getAttribute isn't defined, we need to wait for the DOM.\r\n      } else {\r\n        vjs.autoSetupTimeout(1);\r\n        break;\r\n      }\r\n    }\r\n\r\n  // No videos were found, so keep looping unless page is finisehd loading.\r\n  } else if (!vjs.windowLoaded) {\r\n    vjs.autoSetupTimeout(1);\r\n  }\r\n};\r\n\r\n// Pause to let the DOM keep processing\r\nvjs.autoSetupTimeout = function(wait){\r\n  setTimeout(vjs.autoSetup, wait);\r\n};\r\n\r\nif (document.readyState === 'complete') {\r\n  vjs.windowLoaded = true;\r\n} else {\r\n  vjs.one(window, 'load', function(){\r\n    vjs.windowLoaded = true;\r\n  });\r\n}\r\n\r\n// Run Auto-load players\r\n// You have to wait at least once in case this script is loaded after your video in the DOM (weird behavior only with minified version)\r\nvjs.autoSetupTimeout(1);\r\n/**\r\n * the method for registering a video.js plugin\r\n *\r\n * @param  {String} name The name of the plugin\r\n * @param  {Function} init The function that is run when the player inits\r\n */\r\nvjs.plugin = function(name, init){\r\n  vjs.Player.prototype[name] = init;\r\n};\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/video-js/video.js",
    "content": "/*! Video.js v4.3.0 Copyright 2013 Brightcove, Inc. https://github.com/videojs/video.js/blob/master/LICENSE */ (function() {var b=void 0,f=!0,h=null,l=!1;function m(){return function(){}}function p(a){return function(){return this[a]}}function s(a){return function(){return a}}var t;document.createElement(\"video\");document.createElement(\"audio\");document.createElement(\"track\");function u(a,c,d){if(\"string\"===typeof a){0===a.indexOf(\"#\")&&(a=a.slice(1));if(u.xa[a])return u.xa[a];a=u.w(a)}if(!a||!a.nodeName)throw new TypeError(\"The element or ID supplied is not valid. (videojs)\");return a.player||new u.s(a,c,d)}var v=u;\r\nwindow.Td=window.Ud=u;u.Tb=\"4.3\";u.Fc=\"https:\"==document.location.protocol?\"https://\":\"http://\";u.options={techOrder:[\"html5\",\"flash\"],html5:{},flash:{},width:300,height:150,defaultVolume:0,children:{mediaLoader:{},posterImage:{},textTrackDisplay:{},loadingSpinner:{},bigPlayButton:{},controlBar:{}},notSupportedMessage:'Sorry, no compatible source and playback technology were found for this video. Try using another browser like <a href=\"http://bit.ly/ccMUEC\">Chrome</a> or download the latest <a href=\"http://adobe.ly/mwfN1\">Adobe Flash Player</a>.'};\r\n\"GENERATED_CDN_VSN\"!==u.Tb&&(v.options.flash.swf=u.Fc+\"vjs.zencdn.net/\"+u.Tb+\"/video-js.swf\");u.xa={};u.la=u.CoreObject=m();u.la.extend=function(a){var c,d;a=a||{};c=a.init||a.i||this.prototype.init||this.prototype.i||m();d=function(){c.apply(this,arguments)};d.prototype=u.k.create(this.prototype);d.prototype.constructor=d;d.extend=u.la.extend;d.create=u.la.create;for(var e in a)a.hasOwnProperty(e)&&(d.prototype[e]=a[e]);return d};\r\nu.la.create=function(){var a=u.k.create(this.prototype);this.apply(a,arguments);return a};u.d=function(a,c,d){var e=u.getData(a);e.z||(e.z={});e.z[c]||(e.z[c]=[]);d.t||(d.t=u.t++);e.z[c].push(d);e.W||(e.disabled=l,e.W=function(c){if(!e.disabled){c=u.kc(c);var d=e.z[c.type];if(d)for(var d=d.slice(0),k=0,q=d.length;k<q&&!c.pc();k++)d[k].call(a,c)}});1==e.z[c].length&&(document.addEventListener?a.addEventListener(c,e.W,l):document.attachEvent&&a.attachEvent(\"on\"+c,e.W))};\r\nu.o=function(a,c,d){if(u.oc(a)){var e=u.getData(a);if(e.z)if(c){var g=e.z[c];if(g){if(d){if(d.t)for(e=0;e<g.length;e++)g[e].t===d.t&&g.splice(e--,1)}else e.z[c]=[];u.gc(a,c)}}else for(g in e.z)c=g,e.z[c]=[],u.gc(a,c)}};u.gc=function(a,c){var d=u.getData(a);0===d.z[c].length&&(delete d.z[c],document.removeEventListener?a.removeEventListener(c,d.W,l):document.detachEvent&&a.detachEvent(\"on\"+c,d.W));u.Bb(d.z)&&(delete d.z,delete d.W,delete d.disabled);u.Bb(d)&&u.vc(a)};\r\nu.kc=function(a){function c(){return f}function d(){return l}if(!a||!a.Cb){var e=a||window.event;a={};for(var g in e)\"layerX\"!==g&&\"layerY\"!==g&&(a[g]=e[g]);a.target||(a.target=a.srcElement||document);a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement;a.preventDefault=function(){e.preventDefault&&e.preventDefault();a.returnValue=l;a.Ab=c};a.Ab=d;a.stopPropagation=function(){e.stopPropagation&&e.stopPropagation();a.cancelBubble=f;a.Cb=c};a.Cb=d;a.stopImmediatePropagation=function(){e.stopImmediatePropagation&&\r\ne.stopImmediatePropagation();a.pc=c;a.stopPropagation()};a.pc=d;if(a.clientX!=h){g=document.documentElement;var j=document.body;a.pageX=a.clientX+(g&&g.scrollLeft||j&&j.scrollLeft||0)-(g&&g.clientLeft||j&&j.clientLeft||0);a.pageY=a.clientY+(g&&g.scrollTop||j&&j.scrollTop||0)-(g&&g.clientTop||j&&j.clientTop||0)}a.which=a.charCode||a.keyCode;a.button!=h&&(a.button=a.button&1?0:a.button&4?1:a.button&2?2:0)}return a};\r\nu.j=function(a,c){var d=u.oc(a)?u.getData(a):{},e=a.parentNode||a.ownerDocument;\"string\"===typeof c&&(c={type:c,target:a});c=u.kc(c);d.W&&d.W.call(a,c);if(e&&!c.Cb()&&c.bubbles!==l)u.j(e,c);else if(!e&&!c.Ab()&&(d=u.getData(c.target),c.target[c.type])){d.disabled=f;if(\"function\"===typeof c.target[c.type])c.target[c.type]();d.disabled=l}return!c.Ab()};u.U=function(a,c,d){function e(){u.o(a,c,e);d.apply(this,arguments)}e.t=d.t=d.t||u.t++;u.d(a,c,e)};var w=Object.prototype.hasOwnProperty;\r\nu.e=function(a,c){var d,e;d=document.createElement(a||\"div\");for(e in c)w.call(c,e)&&(-1!==e.indexOf(\"aria-\")||\"role\"==e?d.setAttribute(e,c[e]):d[e]=c[e]);return d};u.$=function(a){return a.charAt(0).toUpperCase()+a.slice(1)};u.k={};u.k.create=Object.create||function(a){function c(){}c.prototype=a;return new c};u.k.ua=function(a,c,d){for(var e in a)w.call(a,e)&&c.call(d||this,e,a[e])};u.k.B=function(a,c){if(!c)return a;for(var d in c)w.call(c,d)&&(a[d]=c[d]);return a};\r\nu.k.ic=function(a,c){var d,e,g;a=u.k.copy(a);for(d in c)w.call(c,d)&&(e=a[d],g=c[d],a[d]=u.k.qc(e)&&u.k.qc(g)?u.k.ic(e,g):c[d]);return a};u.k.copy=function(a){return u.k.B({},a)};u.k.qc=function(a){return!!a&&\"object\"===typeof a&&\"[object Object]\"===a.toString()&&a.constructor===Object};u.bind=function(a,c,d){function e(){return c.apply(a,arguments)}c.t||(c.t=u.t++);e.t=d?d+\"_\"+c.t:c.t;return e};u.ra={};u.t=1;u.expando=\"vdata\"+(new Date).getTime();\r\nu.getData=function(a){var c=a[u.expando];c||(c=a[u.expando]=u.t++,u.ra[c]={});return u.ra[c]};u.oc=function(a){a=a[u.expando];return!(!a||u.Bb(u.ra[a]))};u.vc=function(a){var c=a[u.expando];if(c){delete u.ra[c];try{delete a[u.expando]}catch(d){a.removeAttribute?a.removeAttribute(u.expando):a[u.expando]=h}}};u.Bb=function(a){for(var c in a)if(a[c]!==h)return l;return f};u.n=function(a,c){-1==(\" \"+a.className+\" \").indexOf(\" \"+c+\" \")&&(a.className=\"\"===a.className?c:a.className+\" \"+c)};\r\nu.u=function(a,c){var d,e;if(-1!=a.className.indexOf(c)){d=a.className.split(\" \");for(e=d.length-1;0<=e;e--)d[e]===c&&d.splice(e,1);a.className=d.join(\" \")}};u.na=u.e(\"video\");u.F=navigator.userAgent;u.Mc=/iPhone/i.test(u.F);u.Lc=/iPad/i.test(u.F);u.Nc=/iPod/i.test(u.F);u.Kc=u.Mc||u.Lc||u.Nc;var aa=u,x;var y=u.F.match(/OS (\\d+)_/i);x=y&&y[1]?y[1]:b;aa.Fd=x;u.Ic=/Android/i.test(u.F);var ba=u,z;var A=u.F.match(/Android (\\d+)(?:\\.(\\d+))?(?:\\.(\\d+))*/i),B,C;\r\nA?(B=A[1]&&parseFloat(A[1]),C=A[2]&&parseFloat(A[2]),z=B&&C?parseFloat(A[1]+\".\"+A[2]):B?B:h):z=h;ba.Gc=z;u.Oc=u.Ic&&/webkit/i.test(u.F)&&2.3>u.Gc;u.Jc=/Firefox/i.test(u.F);u.Gd=/Chrome/i.test(u.F);u.ac=!!(\"ontouchstart\"in window||window.Hc&&document instanceof window.Hc);\r\nu.xb=function(a){var c,d,e,g;c={};if(a&&a.attributes&&0<a.attributes.length){d=a.attributes;for(var j=d.length-1;0<=j;j--){e=d[j].name;g=d[j].value;if(\"boolean\"===typeof a[e]||-1!==\",autoplay,controls,loop,muted,default,\".indexOf(\",\"+e+\",\"))g=g!==h?f:l;c[e]=g}}return c};\r\nu.Kd=function(a,c){var d=\"\";document.defaultView&&document.defaultView.getComputedStyle?d=document.defaultView.getComputedStyle(a,\"\").getPropertyValue(c):a.currentStyle&&(d=a[\"client\"+c.substr(0,1).toUpperCase()+c.substr(1)]+\"px\");return d};u.zb=function(a,c){c.firstChild?c.insertBefore(a,c.firstChild):c.appendChild(a)};u.Pb={};u.w=function(a){0===a.indexOf(\"#\")&&(a=a.slice(1));return document.getElementById(a)};\r\nu.La=function(a,c){c=c||a;var d=Math.floor(a%60),e=Math.floor(a/60%60),g=Math.floor(a/3600),j=Math.floor(c/60%60),k=Math.floor(c/3600);if(isNaN(a)||Infinity===a)g=e=d=\"-\";g=0<g||0<k?g+\":\":\"\";return g+(((g||10<=j)&&10>e?\"0\"+e:e)+\":\")+(10>d?\"0\"+d:d)};u.Tc=function(){document.body.focus();document.onselectstart=s(l)};u.Bd=function(){document.onselectstart=s(f)};u.trim=function(a){return(a+\"\").replace(/^\\s+|\\s+$/g,\"\")};u.round=function(a,c){c||(c=0);return Math.round(a*Math.pow(10,c))/Math.pow(10,c)};\r\nu.tb=function(a,c){return{length:1,start:function(){return a},end:function(){return c}}};\r\nu.get=function(a,c,d){var e,g;\"undefined\"===typeof XMLHttpRequest&&(window.XMLHttpRequest=function(){try{return new window.ActiveXObject(\"Msxml2.XMLHTTP.6.0\")}catch(a){}try{return new window.ActiveXObject(\"Msxml2.XMLHTTP.3.0\")}catch(c){}try{return new window.ActiveXObject(\"Msxml2.XMLHTTP\")}catch(d){}throw Error(\"This browser does not support XMLHttpRequest.\");});g=new XMLHttpRequest;try{g.open(\"GET\",a)}catch(j){d(j)}e=0===a.indexOf(\"file:\")||0===window.location.href.indexOf(\"file:\")&&-1===a.indexOf(\"http\");\r\ng.onreadystatechange=function(){4===g.readyState&&(200===g.status||e&&0===g.status?c(g.responseText):d&&d())};try{g.send()}catch(k){d&&d(k)}};u.td=function(a){try{var c=window.localStorage||l;c&&(c.volume=a)}catch(d){22==d.code||1014==d.code?u.log(\"LocalStorage Full (VideoJS)\",d):18==d.code?u.log(\"LocalStorage not allowed (VideoJS)\",d):u.log(\"LocalStorage Error (VideoJS)\",d)}};u.mc=function(a){a.match(/^https?:\\/\\//)||(a=u.e(\"div\",{innerHTML:'<a href=\"'+a+'\">x</a>'}).firstChild.href);return a};\r\nu.log=function(){u.log.history=u.log.history||[];u.log.history.push(arguments);window.console&&window.console.log(Array.prototype.slice.call(arguments))};u.ad=function(a){var c,d;a.getBoundingClientRect&&a.parentNode&&(c=a.getBoundingClientRect());if(!c)return{left:0,top:0};a=document.documentElement;d=document.body;return{left:c.left+(window.pageXOffset||d.scrollLeft)-(a.clientLeft||d.clientLeft||0),top:c.top+(window.pageYOffset||d.scrollTop)-(a.clientTop||d.clientTop||0)}};\r\nu.c=u.la.extend({i:function(a,c,d){this.b=a;this.g=u.k.copy(this.g);c=this.options(c);this.Q=c.id||(c.el&&c.el.id?c.el.id:a.id()+\"_component_\"+u.t++);this.gd=c.name||h;this.a=c.el||this.e();this.G=[];this.qb={};this.V={};if((a=this.g)&&a.children){var e=this;u.k.ua(a.children,function(a,c){c!==l&&!c.loadEvent&&(e[a]=e.Z(a,c))})}this.L(d)}});t=u.c.prototype;\r\nt.D=function(){this.j(\"dispose\");if(this.G)for(var a=this.G.length-1;0<=a;a--)this.G[a].D&&this.G[a].D();this.V=this.qb=this.G=h;this.o();this.a.parentNode&&this.a.parentNode.removeChild(this.a);u.vc(this.a);this.a=h};t.b=f;t.K=p(\"b\");t.options=function(a){return a===b?this.g:this.g=u.k.ic(this.g,a)};t.e=function(a,c){return u.e(a,c)};t.w=p(\"a\");t.id=p(\"Q\");t.name=p(\"gd\");t.children=p(\"G\");\r\nt.Z=function(a,c){var d,e;\"string\"===typeof a?(e=a,c=c||{},d=c.componentClass||u.$(e),c.name=e,d=new window.videojs[d](this.b||this,c)):d=a;this.G.push(d);\"function\"===typeof d.id&&(this.qb[d.id()]=d);(e=e||d.name&&d.name())&&(this.V[e]=d);\"function\"===typeof d.el&&d.el()&&(this.sa||this.a).appendChild(d.el());return d};\r\nt.removeChild=function(a){\"string\"===typeof a&&(a=this.V[a]);if(a&&this.G){for(var c=l,d=this.G.length-1;0<=d;d--)if(this.G[d]===a){c=f;this.G.splice(d,1);break}c&&(this.qb[a.id]=h,this.V[a.name]=h,(c=a.w())&&c.parentNode===(this.sa||this.a)&&(this.sa||this.a).removeChild(a.w()))}};t.T=s(\"\");t.d=function(a,c){u.d(this.a,a,u.bind(this,c));return this};t.o=function(a,c){u.o(this.a,a,c);return this};t.U=function(a,c){u.U(this.a,a,u.bind(this,c));return this};t.j=function(a,c){u.j(this.a,a,c);return this};\r\nt.L=function(a){a&&(this.aa?a.call(this):(this.Sa===b&&(this.Sa=[]),this.Sa.push(a)));return this};t.Ua=function(){this.aa=f;var a=this.Sa;if(a&&0<a.length){for(var c=0,d=a.length;c<d;c++)a[c].call(this);this.Sa=[];this.j(\"ready\")}};t.n=function(a){u.n(this.a,a);return this};t.u=function(a){u.u(this.a,a);return this};t.show=function(){this.a.style.display=\"block\";return this};t.C=function(){this.a.style.display=\"none\";return this};function D(a){a.u(\"vjs-lock-showing\")}\r\nt.disable=function(){this.C();this.show=m()};t.width=function(a,c){return E(this,\"width\",a,c)};t.height=function(a,c){return E(this,\"height\",a,c)};t.Xc=function(a,c){return this.width(a,f).height(c)};function E(a,c,d,e){if(d!==b)return a.a.style[c]=-1!==(\"\"+d).indexOf(\"%\")||-1!==(\"\"+d).indexOf(\"px\")?d:\"auto\"===d?\"\":d+\"px\",e||a.j(\"resize\"),a;if(!a.a)return 0;d=a.a.style[c];e=d.indexOf(\"px\");return-1!==e?parseInt(d.slice(0,e),10):parseInt(a.a[\"offset\"+u.$(c)],10)}\r\nu.q=u.c.extend({i:function(a,c){u.c.call(this,a,c);var d=l;this.d(\"touchstart\",function(a){a.preventDefault();d=f});this.d(\"touchmove\",function(){d=l});var e=this;this.d(\"touchend\",function(a){d&&e.p(a);a.preventDefault()});this.d(\"click\",this.p);this.d(\"focus\",this.Oa);this.d(\"blur\",this.Na)}});t=u.q.prototype;\r\nt.e=function(a,c){c=u.k.B({className:this.T(),innerHTML:'<div class=\"vjs-control-content\"><span class=\"vjs-control-text\">'+(this.qa||\"Need Text\")+\"</span></div>\",qd:\"button\",\"aria-live\":\"polite\",tabIndex:0},c);return u.c.prototype.e.call(this,a,c)};t.T=function(){return\"vjs-control \"+u.c.prototype.T.call(this)};t.p=m();t.Oa=function(){u.d(document,\"keyup\",u.bind(this,this.ba))};t.ba=function(a){if(32==a.which||13==a.which)a.preventDefault(),this.p()};\r\nt.Na=function(){u.o(document,\"keyup\",u.bind(this,this.ba))};u.O=u.c.extend({i:function(a,c){u.c.call(this,a,c);this.Sc=this.V[this.g.barName];this.handle=this.V[this.g.handleName];a.d(this.tc,u.bind(this,this.update));this.d(\"mousedown\",this.Pa);this.d(\"touchstart\",this.Pa);this.d(\"focus\",this.Oa);this.d(\"blur\",this.Na);this.d(\"click\",this.p);this.b.d(\"controlsvisible\",u.bind(this,this.update));a.L(u.bind(this,this.update));this.P={}}});t=u.O.prototype;\r\nt.e=function(a,c){c=c||{};c.className+=\" vjs-slider\";c=u.k.B({qd:\"slider\",\"aria-valuenow\":0,\"aria-valuemin\":0,\"aria-valuemax\":100,tabIndex:0},c);return u.c.prototype.e.call(this,a,c)};t.Pa=function(a){a.preventDefault();u.Tc();this.P.move=u.bind(this,this.Hb);this.P.end=u.bind(this,this.Ib);u.d(document,\"mousemove\",this.P.move);u.d(document,\"mouseup\",this.P.end);u.d(document,\"touchmove\",this.P.move);u.d(document,\"touchend\",this.P.end);this.Hb(a)};\r\nt.Ib=function(){u.Bd();u.o(document,\"mousemove\",this.P.move,l);u.o(document,\"mouseup\",this.P.end,l);u.o(document,\"touchmove\",this.P.move,l);u.o(document,\"touchend\",this.P.end,l);this.update()};t.update=function(){if(this.a){var a,c=this.yb(),d=this.handle,e=this.Sc;isNaN(c)&&(c=0);a=c;if(d){a=this.a.offsetWidth;var g=d.w().offsetWidth;a=g?g/a:0;c*=1-a;a=c+a/2;d.w().style.left=u.round(100*c,2)+\"%\"}e.w().style.width=u.round(100*a,2)+\"%\"}};\r\nfunction F(a,c){var d,e,g,j;d=a.a;e=u.ad(d);j=g=d.offsetWidth;d=a.handle;if(a.g.Cd)return j=e.top,e=c.changedTouches?c.changedTouches[0].pageY:c.pageY,d&&(d=d.w().offsetHeight,j+=d/2,g-=d),Math.max(0,Math.min(1,(j-e+g)/g));g=e.left;e=c.changedTouches?c.changedTouches[0].pageX:c.pageX;d&&(d=d.w().offsetWidth,g+=d/2,j-=d);return Math.max(0,Math.min(1,(e-g)/j))}t.Oa=function(){u.d(document,\"keyup\",u.bind(this,this.ba))};\r\nt.ba=function(a){37==a.which?(a.preventDefault(),this.yc()):39==a.which&&(a.preventDefault(),this.zc())};t.Na=function(){u.o(document,\"keyup\",u.bind(this,this.ba))};t.p=function(a){a.stopImmediatePropagation();a.preventDefault()};u.ea=u.c.extend();u.ea.prototype.defaultValue=0;u.ea.prototype.e=function(a,c){c=c||{};c.className+=\" vjs-slider-handle\";c=u.k.B({innerHTML:'<span class=\"vjs-control-text\">'+this.defaultValue+\"</span>\"},c);return u.c.prototype.e.call(this,\"div\",c)};u.ma=u.c.extend();\r\nfunction ca(a,c){a.Z(c);c.d(\"click\",u.bind(a,function(){D(this)}))}u.ma.prototype.e=function(){var a=this.options().Vc||\"ul\";this.sa=u.e(a,{className:\"vjs-menu-content\"});a=u.c.prototype.e.call(this,\"div\",{append:this.sa,className:\"vjs-menu\"});a.appendChild(this.sa);u.d(a,\"click\",function(a){a.preventDefault();a.stopImmediatePropagation()});return a};u.N=u.q.extend({i:function(a,c){u.q.call(this,a,c);this.selected(c.selected)}});\r\nu.N.prototype.e=function(a,c){return u.q.prototype.e.call(this,\"li\",u.k.B({className:\"vjs-menu-item\",innerHTML:this.g.label},c))};u.N.prototype.p=function(){this.selected(f)};u.N.prototype.selected=function(a){a?(this.n(\"vjs-selected\"),this.a.setAttribute(\"aria-selected\",f)):(this.u(\"vjs-selected\"),this.a.setAttribute(\"aria-selected\",l))};\r\nu.R=u.q.extend({i:function(a,c){u.q.call(this,a,c);this.wa=this.Ka();this.Z(this.wa);this.I&&0===this.I.length&&this.C();this.d(\"keyup\",this.ba);this.a.setAttribute(\"aria-haspopup\",f);this.a.setAttribute(\"role\",\"button\")}});t=u.R.prototype;t.pa=l;t.Ka=function(){var a=new u.ma(this.b);this.options().title&&a.w().appendChild(u.e(\"li\",{className:\"vjs-menu-title\",innerHTML:u.$(this.A),zd:-1}));if(this.I=this.createItems())for(var c=0;c<this.I.length;c++)ca(a,this.I[c]);return a};t.ta=m();\r\nt.T=function(){return this.className+\" vjs-menu-button \"+u.q.prototype.T.call(this)};t.Oa=m();t.Na=m();t.p=function(){this.U(\"mouseout\",u.bind(this,function(){D(this.wa);this.a.blur()}));this.pa?G(this):H(this)};t.ba=function(a){a.preventDefault();32==a.which||13==a.which?this.pa?G(this):H(this):27==a.which&&this.pa&&G(this)};function H(a){a.pa=f;a.wa.n(\"vjs-lock-showing\");a.a.setAttribute(\"aria-pressed\",f);a.I&&0<a.I.length&&a.I[0].w().focus()}\r\nfunction G(a){a.pa=l;D(a.wa);a.a.setAttribute(\"aria-pressed\",l)}\r\nu.s=u.c.extend({i:function(a,c,d){this.M=a;c=u.k.B(da(a),c);this.v={};this.uc=c.poster;this.sb=c.controls;a.controls=l;u.c.call(this,this,c,d);this.controls()?this.n(\"vjs-controls-enabled\"):this.n(\"vjs-controls-disabled\");this.U(\"play\",function(a){u.j(this.a,{type:\"firstplay\",target:this.a})||(a.preventDefault(),a.stopPropagation(),a.stopImmediatePropagation())});this.d(\"ended\",this.hd);this.d(\"play\",this.Kb);this.d(\"firstplay\",this.jd);this.d(\"pause\",this.Jb);this.d(\"progress\",this.ld);this.d(\"durationchange\",\r\nthis.sc);this.d(\"error\",this.Gb);this.d(\"fullscreenchange\",this.kd);u.xa[this.Q]=this;c.plugins&&u.k.ua(c.plugins,function(a,c){this[a](c)},this);var e,g,j,k;e=this.Mb;a=function(){e();clearInterval(g);g=setInterval(u.bind(this,e),250)};c=function(){e();clearInterval(g)};this.d(\"mousedown\",a);this.d(\"mousemove\",e);this.d(\"mouseup\",c);this.d(\"keydown\",e);this.d(\"keyup\",e);this.d(\"touchstart\",a);this.d(\"touchmove\",e);this.d(\"touchend\",c);this.d(\"touchcancel\",c);j=setInterval(u.bind(this,function(){this.ka&&\r\n(this.ka=l,this.ja(f),clearTimeout(k),k=setTimeout(u.bind(this,function(){this.ka||this.ja(l)}),2E3))}),250);this.d(\"dispose\",function(){clearInterval(j);clearTimeout(k)})}});t=u.s.prototype;t.g=u.options;t.D=function(){this.j(\"dispose\");this.o(\"dispose\");u.xa[this.Q]=h;this.M&&this.M.player&&(this.M.player=h);this.a&&this.a.player&&(this.a.player=h);clearInterval(this.Ra);this.za();this.h&&this.h.D();u.c.prototype.D.call(this)};\r\nfunction da(a){var c={sources:[],tracks:[]};u.k.B(c,u.xb(a));if(a.hasChildNodes()){var d,e,g,j;a=a.childNodes;g=0;for(j=a.length;g<j;g++)d=a[g],e=d.nodeName.toLowerCase(),\"source\"===e?c.sources.push(u.xb(d)):\"track\"===e&&c.tracks.push(u.xb(d))}return c}\r\nt.e=function(){var a=this.a=u.c.prototype.e.call(this,\"div\"),c=this.M;c.removeAttribute(\"width\");c.removeAttribute(\"height\");if(c.hasChildNodes()){var d,e,g,j,k;d=c.childNodes;e=d.length;for(k=[];e--;)g=d[e],j=g.nodeName.toLowerCase(),\"track\"===j&&k.push(g);for(d=0;d<k.length;d++)c.removeChild(k[d])}c.id=c.id||\"vjs_video_\"+u.t++;a.id=c.id;a.className=c.className;c.id+=\"_html5_api\";c.className=\"vjs-tech\";c.player=a.player=this;this.n(\"vjs-paused\");this.width(this.g.width,f);this.height(this.g.height,\r\nf);c.parentNode&&c.parentNode.insertBefore(a,c);u.zb(c,a);return a};\r\nfunction I(a,c,d){a.h?(a.aa=l,a.h.D(),a.Eb&&(a.Eb=l,clearInterval(a.Ra)),a.Fb&&J(a),a.h=l):\"Html5\"!==c&&a.M&&(u.l.jc(a.M),a.M=h);a.ia=c;a.aa=l;var e=u.k.B({source:d,parentEl:a.a},a.g[c.toLowerCase()]);d&&(d.src==a.v.src&&0<a.v.currentTime&&(e.startTime=a.v.currentTime),a.v.src=d.src);a.h=new window.videojs[c](a,e);a.h.L(function(){this.b.Ua();if(!this.m.progressEvents){var a=this.b;a.Eb=f;a.Ra=setInterval(u.bind(a,function(){this.v.lb<this.buffered().end(0)?this.j(\"progress\"):1==this.Ja()&&(clearInterval(this.Ra),\r\nthis.j(\"progress\"))}),500);a.h.U(\"progress\",function(){this.m.progressEvents=f;var a=this.b;a.Eb=l;clearInterval(a.Ra)})}this.m.timeupdateEvents||(a=this.b,a.Fb=f,a.d(\"play\",a.Cc),a.d(\"pause\",a.za),a.h.U(\"timeupdate\",function(){this.m.timeupdateEvents=f;J(this.b)}))})}function J(a){a.Fb=l;a.za();a.o(\"play\",a.Cc);a.o(\"pause\",a.za)}t.Cc=function(){this.hc&&this.za();this.hc=setInterval(u.bind(this,function(){this.j(\"timeupdate\")}),250)};t.za=function(){clearInterval(this.hc)};\r\nt.Kb=function(){u.u(this.a,\"vjs-paused\");u.n(this.a,\"vjs-playing\")};t.jd=function(){this.g.starttime&&this.currentTime(this.g.starttime);this.n(\"vjs-has-started\")};t.Jb=function(){u.u(this.a,\"vjs-playing\");u.n(this.a,\"vjs-paused\")};t.ld=function(){1==this.Ja()&&this.j(\"loadedalldata\")};t.hd=function(){this.g.loop&&(this.currentTime(0),this.play())};t.sc=function(){this.duration(K(this,\"duration\"))};t.kd=function(){this.H?this.n(\"vjs-fullscreen\"):this.u(\"vjs-fullscreen\")};\r\nt.Gb=function(a){u.log(\"Video Error\",a)};function L(a,c,d){if(a.h&&!a.h.aa)a.h.L(function(){this[c](d)});else try{a.h[c](d)}catch(e){throw u.log(e),e;}}function K(a,c){if(a.h&&a.h.aa)try{return a.h[c]()}catch(d){throw a.h[c]===b?u.log(\"Video.js: \"+c+\" method not defined for \"+a.ia+\" playback technology.\",d):\"TypeError\"==d.name?(u.log(\"Video.js: \"+c+\" unavailable on \"+a.ia+\" playback technology element.\",d),a.h.aa=l):u.log(d),d;}}t.play=function(){L(this,\"play\");return this};\r\nt.pause=function(){L(this,\"pause\");return this};t.paused=function(){return K(this,\"paused\")===l?l:f};t.currentTime=function(a){return a!==b?(this.v.rc=a,L(this,\"setCurrentTime\",a),this.Fb&&this.j(\"timeupdate\"),this):this.v.currentTime=K(this,\"currentTime\")||0};t.duration=function(a){if(a!==b)return this.v.duration=parseFloat(a),this;this.v.duration===b&&this.sc();return this.v.duration};\r\nt.buffered=function(){var a=K(this,\"buffered\"),c=a.length-1,d=this.v.lb=this.v.lb||0;a&&(0<=c&&a.end(c)!==d)&&(d=a.end(c),this.v.lb=d);return u.tb(0,d)};t.Ja=function(){return this.duration()?this.buffered().end(0)/this.duration():0};t.volume=function(a){if(a!==b)return a=Math.max(0,Math.min(1,parseFloat(a))),this.v.volume=a,L(this,\"setVolume\",a),u.td(a),this;a=parseFloat(K(this,\"volume\"));return isNaN(a)?1:a};t.muted=function(a){return a!==b?(L(this,\"setMuted\",a),this):K(this,\"muted\")||l};\r\nt.Ta=function(){return K(this,\"supportsFullScreen\")||l};\r\nt.ya=function(){var a=u.Pb.ya;this.H=f;a?(u.d(document,a.vb,u.bind(this,function(c){this.H=document[a.H];this.H===l&&u.o(document,a.vb,arguments.callee);this.j(\"fullscreenchange\")})),this.a[a.wc]()):this.h.Ta()?L(this,\"enterFullScreen\"):(this.cd=f,this.Yc=document.documentElement.style.overflow,u.d(document,\"keydown\",u.bind(this,this.lc)),document.documentElement.style.overflow=\"hidden\",u.n(document.body,\"vjs-full-window\"),this.j(\"enterFullWindow\"),this.j(\"fullscreenchange\"));return this};\r\nt.ob=function(){var a=u.Pb.ya;this.H=l;if(a)document[a.nb]();else this.h.Ta()?L(this,\"exitFullScreen\"):(M(this),this.j(\"fullscreenchange\"));return this};t.lc=function(a){27===a.keyCode&&(this.H===f?this.ob():M(this))};function M(a){a.cd=l;u.o(document,\"keydown\",a.lc);document.documentElement.style.overflow=a.Yc;u.u(document.body,\"vjs-full-window\");a.j(\"exitFullWindow\")}\r\nt.src=function(a){if(a instanceof Array){var c;a:{c=a;for(var d=0,e=this.g.techOrder;d<e.length;d++){var g=u.$(e[d]),j=window.videojs[g];if(j.isSupported())for(var k=0,q=c;k<q.length;k++){var n=q[k];if(j.canPlaySource(n)){c={source:n,h:g};break a}}}c=l}c?(a=c.source,c=c.h,c==this.ia?this.src(a):I(this,c,a)):this.a.appendChild(u.e(\"p\",{innerHTML:this.options().notSupportedMessage}))}else a instanceof Object?window.videojs[this.ia].canPlaySource(a)?this.src(a.src):this.src([a]):(this.v.src=a,this.aa?\r\n(L(this,\"src\",a),\"auto\"==this.g.preload&&this.load(),this.g.autoplay&&this.play()):this.L(function(){this.src(a)}));return this};t.load=function(){L(this,\"load\");return this};t.currentSrc=function(){return K(this,\"currentSrc\")||this.v.src||\"\"};t.Qa=function(a){return a!==b?(L(this,\"setPreload\",a),this.g.preload=a,this):K(this,\"preload\")};t.autoplay=function(a){return a!==b?(L(this,\"setAutoplay\",a),this.g.autoplay=a,this):K(this,\"autoplay\")};\r\nt.loop=function(a){return a!==b?(L(this,\"setLoop\",a),this.g.loop=a,this):K(this,\"loop\")};t.poster=function(a){return a!==b?(this.uc=a,this):this.uc};t.controls=function(a){return a!==b?(a=!!a,this.sb!==a&&((this.sb=a)?(this.u(\"vjs-controls-disabled\"),this.n(\"vjs-controls-enabled\"),this.j(\"controlsenabled\")):(this.u(\"vjs-controls-enabled\"),this.n(\"vjs-controls-disabled\"),this.j(\"controlsdisabled\"))),this):this.sb};u.s.prototype.Sb;t=u.s.prototype;\r\nt.Rb=function(a){return a!==b?(a=!!a,this.Sb!==a&&((this.Sb=a)?(this.n(\"vjs-using-native-controls\"),this.j(\"usingnativecontrols\")):(this.u(\"vjs-using-native-controls\"),this.j(\"usingcustomcontrols\"))),this):this.Sb};t.error=function(){return K(this,\"error\")};t.seeking=function(){return K(this,\"seeking\")};t.ka=f;t.Mb=function(){this.ka=f};t.Qb=f;\r\nt.ja=function(a){return a!==b?(a=!!a,a!==this.Qb&&((this.Qb=a)?(this.ka=f,this.u(\"vjs-user-inactive\"),this.n(\"vjs-user-active\"),this.j(\"useractive\")):(this.ka=l,this.h.U(\"mousemove\",function(a){a.stopPropagation();a.preventDefault()}),this.u(\"vjs-user-active\"),this.n(\"vjs-user-inactive\"),this.j(\"userinactive\"))),this):this.Qb};var N,O,P;P=document.createElement(\"div\");O={};\r\nP.Hd!==b?(O.wc=\"requestFullscreen\",O.nb=\"exitFullscreen\",O.vb=\"fullscreenchange\",O.H=\"fullScreen\"):(document.mozCancelFullScreen?(N=\"moz\",O.H=N+\"FullScreen\"):(N=\"webkit\",O.H=N+\"IsFullScreen\"),P[N+\"RequestFullScreen\"]&&(O.wc=N+\"RequestFullScreen\",O.nb=N+\"CancelFullScreen\"),O.vb=N+\"fullscreenchange\");document[O.nb]&&(u.Pb.ya=O);u.Fa=u.c.extend();\r\nu.Fa.prototype.g={Md:\"play\",children:{playToggle:{},currentTimeDisplay:{},timeDivider:{},durationDisplay:{},remainingTimeDisplay:{},progressControl:{},fullscreenToggle:{},volumeControl:{},muteToggle:{}}};u.Fa.prototype.e=function(){return u.e(\"div\",{className:\"vjs-control-bar\"})};u.Yb=u.q.extend({i:function(a,c){u.q.call(this,a,c);a.d(\"play\",u.bind(this,this.Kb));a.d(\"pause\",u.bind(this,this.Jb))}});t=u.Yb.prototype;t.qa=\"Play\";t.T=function(){return\"vjs-play-control \"+u.q.prototype.T.call(this)};\r\nt.p=function(){this.b.paused()?this.b.play():this.b.pause()};t.Kb=function(){u.u(this.a,\"vjs-paused\");u.n(this.a,\"vjs-playing\");this.a.children[0].children[0].innerHTML=\"Pause\"};t.Jb=function(){u.u(this.a,\"vjs-playing\");u.n(this.a,\"vjs-paused\");this.a.children[0].children[0].innerHTML=\"Play\"};u.Ya=u.c.extend({i:function(a,c){u.c.call(this,a,c);a.d(\"timeupdate\",u.bind(this,this.Ca))}});\r\nu.Ya.prototype.e=function(){var a=u.c.prototype.e.call(this,\"div\",{className:\"vjs-current-time vjs-time-controls vjs-control\"});this.content=u.e(\"div\",{className:\"vjs-current-time-display\",innerHTML:'<span class=\"vjs-control-text\">Current Time </span>0:00',\"aria-live\":\"off\"});a.appendChild(u.e(\"div\").appendChild(this.content));return a};\r\nu.Ya.prototype.Ca=function(){var a=this.b.Nb?this.b.v.currentTime:this.b.currentTime();this.content.innerHTML='<span class=\"vjs-control-text\">Current Time </span>'+u.La(a,this.b.duration())};u.Za=u.c.extend({i:function(a,c){u.c.call(this,a,c);a.d(\"timeupdate\",u.bind(this,this.Ca))}});\r\nu.Za.prototype.e=function(){var a=u.c.prototype.e.call(this,\"div\",{className:\"vjs-duration vjs-time-controls vjs-control\"});this.content=u.e(\"div\",{className:\"vjs-duration-display\",innerHTML:'<span class=\"vjs-control-text\">Duration Time </span>0:00',\"aria-live\":\"off\"});a.appendChild(u.e(\"div\").appendChild(this.content));return a};u.Za.prototype.Ca=function(){var a=this.b.duration();a&&(this.content.innerHTML='<span class=\"vjs-control-text\">Duration Time </span>'+u.La(a))};\r\nu.cc=u.c.extend({i:function(a,c){u.c.call(this,a,c)}});u.cc.prototype.e=function(){return u.c.prototype.e.call(this,\"div\",{className:\"vjs-time-divider\",innerHTML:\"<div><span>/</span></div>\"})};u.fb=u.c.extend({i:function(a,c){u.c.call(this,a,c);a.d(\"timeupdate\",u.bind(this,this.Ca))}});\r\nu.fb.prototype.e=function(){var a=u.c.prototype.e.call(this,\"div\",{className:\"vjs-remaining-time vjs-time-controls vjs-control\"});this.content=u.e(\"div\",{className:\"vjs-remaining-time-display\",innerHTML:'<span class=\"vjs-control-text\">Remaining Time </span>-0:00',\"aria-live\":\"off\"});a.appendChild(u.e(\"div\").appendChild(this.content));return a};u.fb.prototype.Ca=function(){this.b.duration()&&(this.content.innerHTML='<span class=\"vjs-control-text\">Remaining Time </span>-'+u.La(this.b.duration()-this.b.currentTime()))};\r\nu.Ga=u.q.extend({i:function(a,c){u.q.call(this,a,c)}});u.Ga.prototype.qa=\"Fullscreen\";u.Ga.prototype.T=function(){return\"vjs-fullscreen-control \"+u.q.prototype.T.call(this)};u.Ga.prototype.p=function(){this.b.H?(this.b.ob(),this.a.children[0].children[0].innerHTML=\"Fullscreen\"):(this.b.ya(),this.a.children[0].children[0].innerHTML=\"Non-Fullscreen\")};u.eb=u.c.extend({i:function(a,c){u.c.call(this,a,c)}});u.eb.prototype.g={children:{seekBar:{}}};\r\nu.eb.prototype.e=function(){return u.c.prototype.e.call(this,\"div\",{className:\"vjs-progress-control vjs-control\"})};u.Zb=u.O.extend({i:function(a,c){u.O.call(this,a,c);a.d(\"timeupdate\",u.bind(this,this.Ba));a.L(u.bind(this,this.Ba))}});t=u.Zb.prototype;t.g={children:{loadProgressBar:{},playProgressBar:{},seekHandle:{}},barName:\"playProgressBar\",handleName:\"seekHandle\"};t.tc=\"timeupdate\";t.e=function(){return u.O.prototype.e.call(this,\"div\",{className:\"vjs-progress-holder\",\"aria-label\":\"video progress bar\"})};\r\nt.Ba=function(){var a=this.b.Nb?this.b.v.currentTime:this.b.currentTime();this.a.setAttribute(\"aria-valuenow\",u.round(100*this.yb(),2));this.a.setAttribute(\"aria-valuetext\",u.La(a,this.b.duration()))};t.yb=function(){var a;\"Flash\"===this.b.ia&&this.b.seeking()?(a=this.b.v,a=a.rc?a.rc:this.b.currentTime()):a=this.b.currentTime();return a/this.b.duration()};t.Pa=function(a){u.O.prototype.Pa.call(this,a);this.b.Nb=f;this.Dd=!this.b.paused();this.b.pause()};\r\nt.Hb=function(a){a=F(this,a)*this.b.duration();a==this.b.duration()&&(a-=0.1);this.b.currentTime(a)};t.Ib=function(a){u.O.prototype.Ib.call(this,a);this.b.Nb=l;this.Dd&&this.b.play()};t.zc=function(){this.b.currentTime(this.b.currentTime()+5)};t.yc=function(){this.b.currentTime(this.b.currentTime()-5)};u.ab=u.c.extend({i:function(a,c){u.c.call(this,a,c);a.d(\"progress\",u.bind(this,this.update))}});u.ab.prototype.e=function(){return u.c.prototype.e.call(this,\"div\",{className:\"vjs-load-progress\",innerHTML:'<span class=\"vjs-control-text\">Loaded: 0%</span>'})};\r\nu.ab.prototype.update=function(){this.a.style&&(this.a.style.width=u.round(100*this.b.Ja(),2)+\"%\")};u.Xb=u.c.extend({i:function(a,c){u.c.call(this,a,c)}});u.Xb.prototype.e=function(){return u.c.prototype.e.call(this,\"div\",{className:\"vjs-play-progress\",innerHTML:'<span class=\"vjs-control-text\">Progress: 0%</span>'})};u.gb=u.ea.extend();u.gb.prototype.defaultValue=\"00:00\";u.gb.prototype.e=function(){return u.ea.prototype.e.call(this,\"div\",{className:\"vjs-seek-handle\"})};\r\nu.ib=u.c.extend({i:function(a,c){u.c.call(this,a,c);a.h&&(a.h.m&&a.h.m.volumeControl===l)&&this.n(\"vjs-hidden\");a.d(\"loadstart\",u.bind(this,function(){a.h.m&&a.h.m.volumeControl===l?this.n(\"vjs-hidden\"):this.u(\"vjs-hidden\")}))}});u.ib.prototype.g={children:{volumeBar:{}}};u.ib.prototype.e=function(){return u.c.prototype.e.call(this,\"div\",{className:\"vjs-volume-control vjs-control\"})};\r\nu.hb=u.O.extend({i:function(a,c){u.O.call(this,a,c);a.d(\"volumechange\",u.bind(this,this.Ba));a.L(u.bind(this,this.Ba));setTimeout(u.bind(this,this.update),0)}});t=u.hb.prototype;t.Ba=function(){this.a.setAttribute(\"aria-valuenow\",u.round(100*this.b.volume(),2));this.a.setAttribute(\"aria-valuetext\",u.round(100*this.b.volume(),2)+\"%\")};t.g={children:{volumeLevel:{},volumeHandle:{}},barName:\"volumeLevel\",handleName:\"volumeHandle\"};t.tc=\"volumechange\";\r\nt.e=function(){return u.O.prototype.e.call(this,\"div\",{className:\"vjs-volume-bar\",\"aria-label\":\"volume level\"})};t.Hb=function(a){this.b.muted()&&this.b.muted(l);this.b.volume(F(this,a))};t.yb=function(){return this.b.muted()?0:this.b.volume()};t.zc=function(){this.b.volume(this.b.volume()+0.1)};t.yc=function(){this.b.volume(this.b.volume()-0.1)};u.dc=u.c.extend({i:function(a,c){u.c.call(this,a,c)}});\r\nu.dc.prototype.e=function(){return u.c.prototype.e.call(this,\"div\",{className:\"vjs-volume-level\",innerHTML:'<span class=\"vjs-control-text\"></span>'})};u.jb=u.ea.extend();u.jb.prototype.defaultValue=\"00:00\";u.jb.prototype.e=function(){return u.ea.prototype.e.call(this,\"div\",{className:\"vjs-volume-handle\"})};\r\nu.da=u.q.extend({i:function(a,c){u.q.call(this,a,c);a.d(\"volumechange\",u.bind(this,this.update));a.h&&(a.h.m&&a.h.m.volumeControl===l)&&this.n(\"vjs-hidden\");a.d(\"loadstart\",u.bind(this,function(){a.h.m&&a.h.m.volumeControl===l?this.n(\"vjs-hidden\"):this.u(\"vjs-hidden\")}))}});u.da.prototype.e=function(){return u.q.prototype.e.call(this,\"div\",{className:\"vjs-mute-control vjs-control\",innerHTML:'<div><span class=\"vjs-control-text\">Mute</span></div>'})};\r\nu.da.prototype.p=function(){this.b.muted(this.b.muted()?l:f)};u.da.prototype.update=function(){var a=this.b.volume(),c=3;0===a||this.b.muted()?c=0:0.33>a?c=1:0.67>a&&(c=2);this.b.muted()?\"Unmute\"!=this.a.children[0].children[0].innerHTML&&(this.a.children[0].children[0].innerHTML=\"Unmute\"):\"Mute\"!=this.a.children[0].children[0].innerHTML&&(this.a.children[0].children[0].innerHTML=\"Mute\");for(a=0;4>a;a++)u.u(this.a,\"vjs-vol-\"+a);u.n(this.a,\"vjs-vol-\"+c)};\r\nu.oa=u.R.extend({i:function(a,c){u.R.call(this,a,c);a.d(\"volumechange\",u.bind(this,this.update));a.h&&(a.h.m&&a.h.m.Dc===l)&&this.n(\"vjs-hidden\");a.d(\"loadstart\",u.bind(this,function(){a.h.m&&a.h.m.Dc===l?this.n(\"vjs-hidden\"):this.u(\"vjs-hidden\")}));this.n(\"vjs-menu-button\")}});u.oa.prototype.Ka=function(){var a=new u.ma(this.b,{Vc:\"div\"}),c=new u.hb(this.b,u.k.B({Cd:f},this.g.Vd));a.Z(c);return a};u.oa.prototype.p=function(){u.da.prototype.p.call(this);u.R.prototype.p.call(this)};\r\nu.oa.prototype.e=function(){return u.q.prototype.e.call(this,\"div\",{className:\"vjs-volume-menu-button vjs-menu-button vjs-control\",innerHTML:'<div><span class=\"vjs-control-text\">Mute</span></div>'})};u.oa.prototype.update=u.da.prototype.update;u.cb=u.q.extend({i:function(a,c){u.q.call(this,a,c);(!a.poster()||!a.controls())&&this.C();a.d(\"play\",u.bind(this,this.C))}});\r\nu.cb.prototype.e=function(){var a=u.e(\"div\",{className:\"vjs-poster\",tabIndex:-1}),c=this.b.poster();c&&(\"backgroundSize\"in a.style?a.style.backgroundImage='url(\"'+c+'\")':a.appendChild(u.e(\"img\",{src:c})));return a};u.cb.prototype.p=function(){this.K().controls()&&this.b.play()};\r\nu.Wb=u.c.extend({i:function(a,c){u.c.call(this,a,c);a.d(\"canplay\",u.bind(this,this.C));a.d(\"canplaythrough\",u.bind(this,this.C));a.d(\"playing\",u.bind(this,this.C));a.d(\"seeked\",u.bind(this,this.C));a.d(\"seeking\",u.bind(this,this.show));a.d(\"seeked\",u.bind(this,this.C));a.d(\"error\",u.bind(this,this.show));a.d(\"waiting\",u.bind(this,this.show))}});u.Wb.prototype.e=function(){return u.c.prototype.e.call(this,\"div\",{className:\"vjs-loading-spinner\"})};u.Wa=u.q.extend();\r\nu.Wa.prototype.e=function(){return u.q.prototype.e.call(this,\"div\",{className:\"vjs-big-play-button\",innerHTML:'<span aria-hidden=\"true\"></span>',\"aria-label\":\"play video\"})};u.Wa.prototype.p=function(){this.b.play()};\r\nu.r=u.c.extend({i:function(a,c,d){u.c.call(this,a,c,d);var e,g;g=this;e=this.K();a=function(){if(e.controls()&&!e.Rb()){var a,c;g.d(\"mousedown\",g.p);g.d(\"touchstart\",function(a){a.preventDefault();a.stopPropagation();c=this.b.ja()});a=function(a){a.stopPropagation();c&&this.b.Mb()};g.d(\"touchmove\",a);g.d(\"touchleave\",a);g.d(\"touchcancel\",a);g.d(\"touchend\",a);var d,n,r;d=0;g.d(\"touchstart\",function(){d=(new Date).getTime();r=f});a=function(){r=l};g.d(\"touchmove\",a);g.d(\"touchleave\",a);g.d(\"touchcancel\",\r\na);g.d(\"touchend\",function(){r===f&&(n=(new Date).getTime()-d,250>n&&this.j(\"tap\"))});g.d(\"tap\",g.md)}};c=u.bind(g,g.pd);this.L(a);e.d(\"controlsenabled\",a);e.d(\"controlsdisabled\",c)}});u.r.prototype.pd=function(){this.o(\"tap\");this.o(\"touchstart\");this.o(\"touchmove\");this.o(\"touchleave\");this.o(\"touchcancel\");this.o(\"touchend\");this.o(\"click\");this.o(\"mousedown\")};u.r.prototype.p=function(a){0===a.button&&this.K().controls()&&(this.K().paused()?this.K().play():this.K().pause())};\r\nu.r.prototype.md=function(){this.K().ja(!this.K().ja())};u.r.prototype.m={volumeControl:f,fullscreenResize:l,progressEvents:l,timeupdateEvents:l};u.media={};u.media.Va=\"play pause paused currentTime setCurrentTime duration buffered volume setVolume muted setMuted width height supportsFullScreen enterFullScreen src load currentSrc preload setPreload autoplay setAutoplay loop setLoop error networkState readyState seeking initialTime startOffsetTime played seekable ended videoTracks audioTracks videoWidth videoHeight textTracks defaultPlaybackRate playbackRate mediaGroup controller controls defaultMuted\".split(\" \");\r\nfunction ea(){var a=u.media.Va[i];return function(){throw Error('The \"'+a+\"\\\" method is not available on the playback technology's API\");}}for(var i=u.media.Va.length-1;0<=i;i--)u.r.prototype[u.media.Va[i]]=ea();\r\nu.l=u.r.extend({i:function(a,c,d){this.m.volumeControl=u.l.Uc();this.m.movingMediaElementInDOM=!u.Kc;this.m.fullscreenResize=f;u.r.call(this,a,c,d);(c=c.source)&&this.a.currentSrc===c.src&&0<this.a.networkState?a.j(\"loadstart\"):c&&(this.a.src=c.src);if(u.ac&&a.options().nativeControlsForTouch!==l){var e,g,j,k;e=this;g=this.K();c=g.controls();e.a.controls=!!c;j=function(){e.a.controls=f};k=function(){e.a.controls=l};g.d(\"controlsenabled\",j);g.d(\"controlsdisabled\",k);c=function(){g.o(\"controlsenabled\",\r\nj);g.o(\"controlsdisabled\",k)};e.d(\"dispose\",c);g.d(\"usingcustomcontrols\",c);g.Rb(f)}a.L(function(){this.M&&(this.g.autoplay&&this.paused())&&(delete this.M.poster,this.play())});for(a=u.l.$a.length-1;0<=a;a--)u.d(this.a,u.l.$a[a],u.bind(this.b,this.$c));this.Ua()}});t=u.l.prototype;t.D=function(){u.r.prototype.D.call(this)};\r\nt.e=function(){var a=this.b,c=a.M,d;if(!c||this.m.movingMediaElementInDOM===l)c?(d=c.cloneNode(l),u.l.jc(c),c=d,a.M=h):c=u.e(\"video\",{id:a.id()+\"_html5_api\",className:\"vjs-tech\"}),c.player=a,u.zb(c,a.w());d=[\"autoplay\",\"preload\",\"loop\",\"muted\"];for(var e=d.length-1;0<=e;e--){var g=d[e];a.g[g]!==h&&(c[g]=a.g[g])}return c};t.$c=function(a){this.j(a);a.stopPropagation()};t.play=function(){this.a.play()};t.pause=function(){this.a.pause()};t.paused=function(){return this.a.paused};t.currentTime=function(){return this.a.currentTime};\r\nt.sd=function(a){try{this.a.currentTime=a}catch(c){u.log(c,\"Video is not ready. (Video.js)\")}};t.duration=function(){return this.a.duration||0};t.buffered=function(){return this.a.buffered};t.volume=function(){return this.a.volume};t.xd=function(a){this.a.volume=a};t.muted=function(){return this.a.muted};t.vd=function(a){this.a.muted=a};t.width=function(){return this.a.offsetWidth};t.height=function(){return this.a.offsetHeight};\r\nt.Ta=function(){return\"function\"==typeof this.a.webkitEnterFullScreen&&(/Android/.test(u.F)||!/Chrome|Mac OS X 10.5/.test(u.F))?f:l};t.src=function(a){this.a.src=a};t.load=function(){this.a.load()};t.currentSrc=function(){return this.a.currentSrc};t.Qa=function(){return this.a.Qa};t.wd=function(a){this.a.Qa=a};t.autoplay=function(){return this.a.autoplay};t.rd=function(a){this.a.autoplay=a};t.controls=function(){return this.a.controls};t.loop=function(){return this.a.loop};\r\nt.ud=function(a){this.a.loop=a};t.error=function(){return this.a.error};t.seeking=function(){return this.a.seeking};u.l.isSupported=function(){return!!u.na.canPlayType};u.l.mb=function(a){try{return!!u.na.canPlayType(a.type)}catch(c){return\"\"}};u.l.Uc=function(){var a=u.na.volume;u.na.volume=a/2+0.1;return a!==u.na.volume};u.l.$a=\"loadstart suspend abort error emptied stalled loadedmetadata loadeddata canplay canplaythrough playing waiting seeking seeked ended durationchange timeupdate progress play pause ratechange volumechange\".split(\" \");\r\nu.l.jc=function(a){if(a){a.player=h;for(a.parentNode&&a.parentNode.removeChild(a);a.hasChildNodes();)a.removeChild(a.firstChild);a.removeAttribute(\"src\");\"function\"===typeof a.load&&a.load()}};u.Oc&&(document.createElement(\"video\").constructor.prototype.canPlayType=function(a){return a&&-1!=a.toLowerCase().indexOf(\"video/mp4\")?\"maybe\":\"\"});\r\nu.f=u.r.extend({i:function(a,c,d){u.r.call(this,a,c,d);var e=c.source;d=c.parentEl;var g=this.a=u.e(\"div\",{id:a.id()+\"_temp_flash\"}),j=a.id()+\"_flash_api\";a=a.g;var k=u.k.B({readyFunction:\"videojs.Flash.onReady\",eventProxyFunction:\"videojs.Flash.onEvent\",errorEventProxyFunction:\"videojs.Flash.onError\",autoplay:a.autoplay,preload:a.Qa,loop:a.loop,muted:a.muted},c.flashVars),q=u.k.B({wmode:\"opaque\",bgcolor:\"#000000\"},c.params),n=u.k.B({id:j,name:j,\"class\":\"vjs-tech\"},c.attributes);e&&(e.type&&u.f.ed(e.type)?\r\n(a=u.f.Ac(e.src),k.rtmpConnection=encodeURIComponent(a.rb),k.rtmpStream=encodeURIComponent(a.Ob)):k.src=encodeURIComponent(u.mc(e.src)));u.zb(g,d);c.startTime&&this.L(function(){this.load();this.play();this.currentTime(c.startTime)});if(c.iFrameMode===f&&!u.Jc){var r=u.e(\"iframe\",{id:j+\"_iframe\",name:j+\"_iframe\",className:\"vjs-tech\",scrolling:\"no\",marginWidth:0,marginHeight:0,frameBorder:0});k.readyFunction=\"ready\";k.eventProxyFunction=\"events\";k.errorEventProxyFunction=\"errors\";u.d(r,\"load\",u.bind(this,\r\nfunction(){var a,d=r.contentWindow;a=r.contentDocument?r.contentDocument:r.contentWindow.document;a.write(u.f.nc(c.swf,k,q,n));d.player=this.b;d.ready=u.bind(this.b,function(c){var d=this.h;d.a=a.getElementById(c);u.f.pb(d)});d.events=u.bind(this.b,function(a,c){this&&\"flash\"===this.ia&&this.j(c)});d.errors=u.bind(this.b,function(a,c){u.log(\"Flash Error\",c)})}));g.parentNode.replaceChild(r,g)}else u.f.Zc(c.swf,g,k,q,n)}});t=u.f.prototype;t.D=function(){u.r.prototype.D.call(this)};t.play=function(){this.a.vjs_play()};\r\nt.pause=function(){this.a.vjs_pause()};t.src=function(a){u.f.dd(a)?(a=u.f.Ac(a),this.Qd(a.rb),this.Rd(a.Ob)):(a=u.mc(a),this.a.vjs_src(a));if(this.b.autoplay()){var c=this;setTimeout(function(){c.play()},0)}};t.currentSrc=function(){var a=this.a.vjs_getProperty(\"currentSrc\");if(a==h){var c=this.Od(),d=this.Pd();c&&d&&(a=u.f.yd(c,d))}return a};t.load=function(){this.a.vjs_load()};t.poster=function(){this.a.vjs_getProperty(\"poster\")};t.buffered=function(){return u.tb(0,this.a.vjs_getProperty(\"buffered\"))};\r\nt.Ta=s(l);var Q=u.f.prototype,R=\"rtmpConnection rtmpStream preload currentTime defaultPlaybackRate playbackRate autoplay loop mediaGroup controller controls volume muted defaultMuted\".split(\" \"),S=\"error currentSrc networkState readyState seeking initialTime duration startOffsetTime paused played seekable ended videoTracks audioTracks videoWidth videoHeight textTracks\".split(\" \");\r\nfunction fa(){var a=R[T],c=a.charAt(0).toUpperCase()+a.slice(1);Q[\"set\"+c]=function(c){return this.a.vjs_setProperty(a,c)}}function U(a){Q[a]=function(){return this.a.vjs_getProperty(a)}}var T;for(T=0;T<R.length;T++)U(R[T]),fa();for(T=0;T<S.length;T++)U(S[T]);u.f.isSupported=function(){return 10<=u.f.version()[0]};u.f.mb=function(a){if(!a.type)return\"\";a=a.type.replace(/;.*/,\"\").toLowerCase();if(a in u.f.bd||a in u.f.Bc)return\"maybe\"};\r\nu.f.bd={\"video/flv\":\"FLV\",\"video/x-flv\":\"FLV\",\"video/mp4\":\"MP4\",\"video/m4v\":\"MP4\"};u.f.Bc={\"rtmp/mp4\":\"MP4\",\"rtmp/flv\":\"FLV\"};u.f.onReady=function(a){a=u.w(a);var c=a.player||a.parentNode.player,d=c.h;a.player=c;d.a=a;u.f.pb(d)};u.f.pb=function(a){a.w().vjs_getProperty?a.Ua():setTimeout(function(){u.f.pb(a)},50)};u.f.onEvent=function(a,c){u.w(a).player.j(c)};u.f.onError=function(a,c){u.w(a).player.j(\"error\");u.log(\"Flash Error\",c,a)};\r\nu.f.version=function(){var a=\"0,0,0\";try{a=(new window.ActiveXObject(\"ShockwaveFlash.ShockwaveFlash\")).GetVariable(\"$version\").replace(/\\D+/g,\",\").match(/^,?(.+),?$/)[1]}catch(c){try{navigator.mimeTypes[\"application/x-shockwave-flash\"].enabledPlugin&&(a=(navigator.plugins[\"Shockwave Flash 2.0\"]||navigator.plugins[\"Shockwave Flash\"]).description.replace(/\\D+/g,\",\").match(/^,?(.+),?$/)[1])}catch(d){}}return a.split(\",\")};\r\nu.f.Zc=function(a,c,d,e,g){a=u.f.nc(a,d,e,g);a=u.e(\"div\",{innerHTML:a}).childNodes[0];d=c.parentNode;c.parentNode.replaceChild(a,c);var j=d.childNodes[0];setTimeout(function(){j.style.display=\"block\"},1E3)};\r\nu.f.nc=function(a,c,d,e){var g=\"\",j=\"\",k=\"\";c&&u.k.ua(c,function(a,c){g+=a+\"=\"+c+\"&amp;\"});d=u.k.B({movie:a,flashvars:g,allowScriptAccess:\"always\",allowNetworking:\"all\"},d);u.k.ua(d,function(a,c){j+='<param name=\"'+a+'\" value=\"'+c+'\" />'});e=u.k.B({data:a,width:\"100%\",height:\"100%\"},e);u.k.ua(e,function(a,c){k+=a+'=\"'+c+'\" '});return'<object type=\"application/x-shockwave-flash\"'+k+\">\"+j+\"</object>\"};u.f.yd=function(a,c){return a+\"&\"+c};\r\nu.f.Ac=function(a){var c={rb:\"\",Ob:\"\"};if(!a)return c;var d=a.indexOf(\"&\"),e;-1!==d?e=d+1:(d=e=a.lastIndexOf(\"/\")+1,0===d&&(d=e=a.length));c.rb=a.substring(0,d);c.Ob=a.substring(e,a.length);return c};u.f.ed=function(a){return a in u.f.Bc};u.f.Qc=/^rtmp[set]?:\\/\\//i;u.f.dd=function(a){return u.f.Qc.test(a)};\r\nu.Pc=u.c.extend({i:function(a,c,d){u.c.call(this,a,c,d);if(!a.g.sources||0===a.g.sources.length){c=0;for(d=a.g.techOrder;c<d.length;c++){var e=u.$(d[c]),g=window.videojs[e];if(g&&g.isSupported()){I(a,e);break}}}else a.src(a.g.sources)}});function V(a){a.Aa=a.Aa||[];return a.Aa}function W(a,c,d){for(var e=a.Aa,g=0,j=e.length,k,q;g<j;g++)k=e[g],k.id()===c?(k.show(),q=k):d&&(k.J()==d&&0<k.mode())&&k.disable();(c=q?q.J():d?d:l)&&a.j(c+\"trackchange\")}\r\nu.X=u.c.extend({i:function(a,c){u.c.call(this,a,c);this.Q=c.id||\"vjs_\"+c.kind+\"_\"+c.language+\"_\"+u.t++;this.xc=c.src;this.Wc=c[\"default\"]||c.dflt;this.Ad=c.title;this.Ld=c.srclang;this.fd=c.label;this.fa=[];this.ec=[];this.ga=this.ha=0;this.b.d(\"fullscreenchange\",u.bind(this,this.Rc))}});t=u.X.prototype;t.J=p(\"A\");t.src=p(\"xc\");t.ub=p(\"Wc\");t.title=p(\"Ad\");t.label=p(\"fd\");t.readyState=p(\"ha\");t.mode=p(\"ga\");t.Rc=function(){this.a.style.fontSize=this.b.H?140*(screen.width/this.b.width())+\"%\":\"\"};\r\nt.e=function(){return u.c.prototype.e.call(this,\"div\",{className:\"vjs-\"+this.A+\" vjs-text-track\"})};t.show=function(){X(this);this.ga=2;u.c.prototype.show.call(this)};t.C=function(){X(this);this.ga=1;u.c.prototype.C.call(this)};t.disable=function(){2==this.ga&&this.C();this.b.o(\"timeupdate\",u.bind(this,this.update,this.Q));this.b.o(\"ended\",u.bind(this,this.reset,this.Q));this.reset();this.b.V.textTrackDisplay.removeChild(this);this.ga=0};\r\nfunction X(a){0===a.ha&&a.load();0===a.ga&&(a.b.d(\"timeupdate\",u.bind(a,a.update,a.Q)),a.b.d(\"ended\",u.bind(a,a.reset,a.Q)),(\"captions\"===a.A||\"subtitles\"===a.A)&&a.b.V.textTrackDisplay.Z(a))}t.load=function(){0===this.ha&&(this.ha=1,u.get(this.xc,u.bind(this,this.nd),u.bind(this,this.Gb)))};t.Gb=function(a){this.error=a;this.ha=3;this.j(\"error\")};\r\nt.nd=function(a){var c,d;a=a.split(\"\\n\");for(var e=\"\",g=1,j=a.length;g<j;g++)if(e=u.trim(a[g])){-1==e.indexOf(\"--\\x3e\")?(c=e,e=u.trim(a[++g])):c=this.fa.length;c={id:c,index:this.fa.length};d=e.split(\" --\\x3e \");c.startTime=Y(d[0]);c.va=Y(d[1]);for(d=[];a[++g]&&(e=u.trim(a[g]));)d.push(e);c.text=d.join(\"<br/>\");this.fa.push(c)}this.ha=2;this.j(\"loaded\")};\r\nfunction Y(a){var c=a.split(\":\");a=0;var d,e,g;3==c.length?(d=c[0],e=c[1],c=c[2]):(d=0,e=c[0],c=c[1]);c=c.split(/\\s+/);c=c.splice(0,1)[0];c=c.split(/\\.|,/);g=parseFloat(c[1]);c=c[0];a+=3600*parseFloat(d);a+=60*parseFloat(e);a+=parseFloat(c);g&&(a+=g/1E3);return a}\r\nt.update=function(){if(0<this.fa.length){var a=this.b.currentTime();if(this.Lb===b||a<this.Lb||this.Ma<=a){var c=this.fa,d=this.b.duration(),e=0,g=l,j=[],k,q,n,r;a>=this.Ma||this.Ma===b?r=this.wb!==b?this.wb:0:(g=f,r=this.Db!==b?this.Db:c.length-1);for(;;){n=c[r];if(n.va<=a)e=Math.max(e,n.va),n.Ia&&(n.Ia=l);else if(a<n.startTime){if(d=Math.min(d,n.startTime),n.Ia&&(n.Ia=l),!g)break}else g?(j.splice(0,0,n),q===b&&(q=r),k=r):(j.push(n),k===b&&(k=r),q=r),d=Math.min(d,n.va),e=Math.max(e,n.startTime),\r\nn.Ia=f;if(g)if(0===r)break;else r--;else if(r===c.length-1)break;else r++}this.ec=j;this.Ma=d;this.Lb=e;this.wb=k;this.Db=q;a=this.ec;c=\"\";d=0;for(e=a.length;d<e;d++)c+='<span class=\"vjs-tt-cue\">'+a[d].text+\"</span>\";this.a.innerHTML=c;this.j(\"cuechange\")}}};t.reset=function(){this.Ma=0;this.Lb=this.b.duration();this.Db=this.wb=0};u.Ub=u.X.extend();u.Ub.prototype.A=\"captions\";u.$b=u.X.extend();u.$b.prototype.A=\"subtitles\";u.Vb=u.X.extend();u.Vb.prototype.A=\"chapters\";\r\nu.bc=u.c.extend({i:function(a,c,d){u.c.call(this,a,c,d);if(a.g.tracks&&0<a.g.tracks.length){c=this.b;a=a.g.tracks;var e;for(d=0;d<a.length;d++){e=a[d];var g=c,j=e.kind,k=e.label,q=e.language,n=e;e=g.Aa=g.Aa||[];n=n||{};n.kind=j;n.label=k;n.language=q;j=u.$(j||\"subtitles\");g=new window.videojs[j+\"Track\"](g,n);e.push(g)}}}});u.bc.prototype.e=function(){return u.c.prototype.e.call(this,\"div\",{className:\"vjs-text-track-display\"})};\r\nu.Y=u.N.extend({i:function(a,c){var d=this.ca=c.track;c.label=d.label();c.selected=d.ub();u.N.call(this,a,c);this.b.d(d.J()+\"trackchange\",u.bind(this,this.update))}});u.Y.prototype.p=function(){u.N.prototype.p.call(this);W(this.b,this.ca.Q,this.ca.J())};u.Y.prototype.update=function(){this.selected(2==this.ca.mode())};u.bb=u.Y.extend({i:function(a,c){c.track={J:function(){return c.kind},K:a,label:function(){return c.kind+\" off\"},ub:s(l),mode:s(l)};u.Y.call(this,a,c);this.selected(f)}});\r\nu.bb.prototype.p=function(){u.Y.prototype.p.call(this);W(this.b,this.ca.Q,this.ca.J())};u.bb.prototype.update=function(){for(var a=V(this.b),c=0,d=a.length,e,g=f;c<d;c++)e=a[c],e.J()==this.ca.J()&&2==e.mode()&&(g=l);this.selected(g)};u.S=u.R.extend({i:function(a,c){u.R.call(this,a,c);1>=this.I.length&&this.C()}});u.S.prototype.ta=function(){var a=[],c;a.push(new u.bb(this.b,{kind:this.A}));for(var d=0;d<V(this.b).length;d++)c=V(this.b)[d],c.J()===this.A&&a.push(new u.Y(this.b,{track:c}));return a};\r\nu.Da=u.S.extend({i:function(a,c,d){u.S.call(this,a,c,d);this.a.setAttribute(\"aria-label\",\"Captions Menu\")}});u.Da.prototype.A=\"captions\";u.Da.prototype.qa=\"Captions\";u.Da.prototype.className=\"vjs-captions-button\";u.Ha=u.S.extend({i:function(a,c,d){u.S.call(this,a,c,d);this.a.setAttribute(\"aria-label\",\"Subtitles Menu\")}});u.Ha.prototype.A=\"subtitles\";u.Ha.prototype.qa=\"Subtitles\";u.Ha.prototype.className=\"vjs-subtitles-button\";\r\nu.Ea=u.S.extend({i:function(a,c,d){u.S.call(this,a,c,d);this.a.setAttribute(\"aria-label\",\"Chapters Menu\")}});t=u.Ea.prototype;t.A=\"chapters\";t.qa=\"Chapters\";t.className=\"vjs-chapters-button\";t.ta=function(){for(var a=[],c,d=0;d<V(this.b).length;d++)c=V(this.b)[d],c.J()===this.A&&a.push(new u.Y(this.b,{track:c}));return a};\r\nt.Ka=function(){for(var a=V(this.b),c=0,d=a.length,e,g,j=this.I=[];c<d;c++)if(e=a[c],e.J()==this.A&&e.ub()){if(2>e.readyState()){this.Id=e;e.d(\"loaded\",u.bind(this,this.Ka));return}g=e;break}a=this.wa=new u.ma(this.b);a.a.appendChild(u.e(\"li\",{className:\"vjs-menu-title\",innerHTML:u.$(this.A),zd:-1}));if(g){e=g.fa;for(var k,c=0,d=e.length;c<d;c++)k=e[c],k=new u.Xa(this.b,{track:g,cue:k}),j.push(k),a.Z(k)}0<this.I.length&&this.show();return a};\r\nu.Xa=u.N.extend({i:function(a,c){var d=this.ca=c.track,e=this.cue=c.cue,g=a.currentTime();c.label=e.text;c.selected=e.startTime<=g&&g<e.va;u.N.call(this,a,c);d.d(\"cuechange\",u.bind(this,this.update))}});u.Xa.prototype.p=function(){u.N.prototype.p.call(this);this.b.currentTime(this.cue.startTime);this.update(this.cue.startTime)};u.Xa.prototype.update=function(){var a=this.cue,c=this.b.currentTime();this.selected(a.startTime<=c&&c<a.va)};\r\nu.k.B(u.Fa.prototype.g.children,{subtitlesButton:{},captionsButton:{},chaptersButton:{}});\r\nif(\"undefined\"!==typeof window.JSON&&\"function\"===window.JSON.parse)u.JSON=window.JSON;else{u.JSON={};var Z=/[\\u0000\\u00ad\\u0600-\\u0604\\u070f\\u17b4\\u17b5\\u200c-\\u200f\\u2028-\\u202f\\u2060-\\u206f\\ufeff\\ufff0-\\uffff]/g;u.JSON.parse=function(a,c){function d(a,e){var k,q,n=a[e];if(n&&\"object\"===typeof n)for(k in n)Object.prototype.hasOwnProperty.call(n,k)&&(q=d(n,k),q!==b?n[k]=q:delete n[k]);return c.call(a,e,n)}var e;a=String(a);Z.lastIndex=0;Z.test(a)&&(a=a.replace(Z,function(a){return\"\\\\u\"+(\"0000\"+a.charCodeAt(0).toString(16)).slice(-4)}));\r\nif(/^[\\],:{}\\s]*$/.test(a.replace(/\\\\(?:[\"\\\\\\/bfnrt]|u[0-9a-fA-F]{4})/g,\"@\").replace(/\"[^\"\\\\\\n\\r]*\"|true|false|null|-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?/g,\"]\").replace(/(?:^|:|,)(?:\\s*\\[)+/g,\"\")))return e=eval(\"(\"+a+\")\"),\"function\"===typeof c?d({\"\":e},\"\"):e;throw new SyntaxError(\"JSON.parse(): invalid or malformed JSON data\");}}\r\nu.fc=function(){var a,c,d=document.getElementsByTagName(\"video\");if(d&&0<d.length)for(var e=0,g=d.length;e<g;e++)if((c=d[e])&&c.getAttribute)c.player===b&&(a=c.getAttribute(\"data-setup\"),a!==h&&(a=u.JSON.parse(a||\"{}\"),v(c,a)));else{u.kb();break}else u.Ec||u.kb()};u.kb=function(){setTimeout(u.fc,1)};\"complete\"===document.readyState?u.Ec=f:u.U(window,\"load\",function(){u.Ec=f});u.kb();u.od=function(a,c){u.s.prototype[a]=c};var ga=this;ga.Ed=f;function $(a,c){var d=a.split(\".\"),e=ga;!(d[0]in e)&&e.execScript&&e.execScript(\"var \"+d[0]);for(var g;d.length&&(g=d.shift());)!d.length&&c!==b?e[g]=c:e=e[g]?e[g]:e[g]={}};$(\"videojs\",u);$(\"_V_\",u);$(\"videojs.options\",u.options);$(\"videojs.players\",u.xa);$(\"videojs.TOUCH_ENABLED\",u.ac);$(\"videojs.cache\",u.ra);$(\"videojs.Component\",u.c);u.c.prototype.player=u.c.prototype.K;u.c.prototype.dispose=u.c.prototype.D;u.c.prototype.createEl=u.c.prototype.e;u.c.prototype.el=u.c.prototype.w;u.c.prototype.addChild=u.c.prototype.Z;u.c.prototype.children=u.c.prototype.children;u.c.prototype.on=u.c.prototype.d;u.c.prototype.off=u.c.prototype.o;u.c.prototype.one=u.c.prototype.U;\r\nu.c.prototype.trigger=u.c.prototype.j;u.c.prototype.triggerReady=u.c.prototype.Ua;u.c.prototype.show=u.c.prototype.show;u.c.prototype.hide=u.c.prototype.C;u.c.prototype.width=u.c.prototype.width;u.c.prototype.height=u.c.prototype.height;u.c.prototype.dimensions=u.c.prototype.Xc;u.c.prototype.ready=u.c.prototype.L;u.c.prototype.addClass=u.c.prototype.n;u.c.prototype.removeClass=u.c.prototype.u;$(\"videojs.Player\",u.s);u.s.prototype.dispose=u.s.prototype.D;u.s.prototype.requestFullScreen=u.s.prototype.ya;\r\nu.s.prototype.cancelFullScreen=u.s.prototype.ob;u.s.prototype.bufferedPercent=u.s.prototype.Ja;u.s.prototype.usingNativeControls=u.s.prototype.Rb;u.s.prototype.reportUserActivity=u.s.prototype.Mb;u.s.prototype.userActive=u.s.prototype.ja;$(\"videojs.MediaLoader\",u.Pc);$(\"videojs.TextTrackDisplay\",u.bc);$(\"videojs.ControlBar\",u.Fa);$(\"videojs.Button\",u.q);$(\"videojs.PlayToggle\",u.Yb);$(\"videojs.FullscreenToggle\",u.Ga);$(\"videojs.BigPlayButton\",u.Wa);$(\"videojs.LoadingSpinner\",u.Wb);\r\n$(\"videojs.CurrentTimeDisplay\",u.Ya);$(\"videojs.DurationDisplay\",u.Za);$(\"videojs.TimeDivider\",u.cc);$(\"videojs.RemainingTimeDisplay\",u.fb);$(\"videojs.Slider\",u.O);$(\"videojs.ProgressControl\",u.eb);$(\"videojs.SeekBar\",u.Zb);$(\"videojs.LoadProgressBar\",u.ab);$(\"videojs.PlayProgressBar\",u.Xb);$(\"videojs.SeekHandle\",u.gb);$(\"videojs.VolumeControl\",u.ib);$(\"videojs.VolumeBar\",u.hb);$(\"videojs.VolumeLevel\",u.dc);$(\"videojs.VolumeMenuButton\",u.oa);$(\"videojs.VolumeHandle\",u.jb);$(\"videojs.MuteToggle\",u.da);\r\n$(\"videojs.PosterImage\",u.cb);$(\"videojs.Menu\",u.ma);$(\"videojs.MenuItem\",u.N);$(\"videojs.MenuButton\",u.R);u.R.prototype.createItems=u.R.prototype.ta;u.S.prototype.createItems=u.S.prototype.ta;u.Ea.prototype.createItems=u.Ea.prototype.ta;$(\"videojs.SubtitlesButton\",u.Ha);$(\"videojs.CaptionsButton\",u.Da);$(\"videojs.ChaptersButton\",u.Ea);$(\"videojs.MediaTechController\",u.r);u.r.prototype.features=u.r.prototype.m;u.r.prototype.m.volumeControl=u.r.prototype.m.Dc;u.r.prototype.m.fullscreenResize=u.r.prototype.m.Jd;\r\nu.r.prototype.m.progressEvents=u.r.prototype.m.Nd;u.r.prototype.m.timeupdateEvents=u.r.prototype.m.Sd;$(\"videojs.Html5\",u.l);u.l.Events=u.l.$a;u.l.isSupported=u.l.isSupported;u.l.canPlaySource=u.l.mb;u.l.prototype.setCurrentTime=u.l.prototype.sd;u.l.prototype.setVolume=u.l.prototype.xd;u.l.prototype.setMuted=u.l.prototype.vd;u.l.prototype.setPreload=u.l.prototype.wd;u.l.prototype.setAutoplay=u.l.prototype.rd;u.l.prototype.setLoop=u.l.prototype.ud;$(\"videojs.Flash\",u.f);u.f.isSupported=u.f.isSupported;\r\nu.f.canPlaySource=u.f.mb;u.f.onReady=u.f.onReady;$(\"videojs.TextTrack\",u.X);u.X.prototype.label=u.X.prototype.label;$(\"videojs.CaptionsTrack\",u.Ub);$(\"videojs.SubtitlesTrack\",u.$b);$(\"videojs.ChaptersTrack\",u.Vb);$(\"videojs.autoSetup\",u.fc);$(\"videojs.plugin\",u.od);$(\"videojs.createTimeRange\",u.tb);})();\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/webuploader/webuploader.css",
    "content": ".webuploader-container {\r\n\tposition: relative;\r\n}\r\n.webuploader-element-invisible {\r\n\tposition: absolute !important;\r\n\tclip: rect(1px 1px 1px 1px); /* IE6, IE7 */\r\n    clip: rect(1px,1px,1px,1px);\r\n}\r\n.webuploader-pick {\r\n\tposition: relative;\r\n\tdisplay: inline-block;\r\n\tcursor: pointer;\r\n\tbackground: #00b7ee;\r\n\tpadding: 10px 15px;\r\n\tcolor: #fff;\r\n\ttext-align: center;\r\n\tborder-radius: 3px;\r\n\toverflow: hidden;\r\n}\r\n.webuploader-pick-hover {\r\n\tbackground: #00a2d4;\r\n}\r\n\r\n.webuploader-pick-disable {\r\n\topacity: 0.6;\r\n\tpointer-events:none;\r\n}\r\n\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/webuploader/webuploader.custom.js",
    "content": "/*! WebUploader 0.1.2 */\r\n\r\n\r\n/**\r\n * @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。\r\n *\r\n * AMD API 内部的简单不完全实现，请忽略。只有当WebUploader被合并成一个文件的时候才会引入。\r\n */\r\n(function( root, factory ) {\r\n    var modules = {},\r\n\r\n        // 内部require, 简单不完全实现。\r\n        // https://github.com/amdjs/amdjs-api/wiki/require\r\n        _require = function( deps, callback ) {\r\n            var args, len, i;\r\n\r\n            // 如果deps不是数组，则直接返回指定module\r\n            if ( typeof deps === 'string' ) {\r\n                return getModule( deps );\r\n            } else {\r\n                args = [];\r\n                for( len = deps.length, i = 0; i < len; i++ ) {\r\n                    args.push( getModule( deps[ i ] ) );\r\n                }\r\n\r\n                return callback.apply( null, args );\r\n            }\r\n        },\r\n\r\n        // 内部define，暂时不支持不指定id.\r\n        _define = function( id, deps, factory ) {\r\n            if ( arguments.length === 2 ) {\r\n                factory = deps;\r\n                deps = null;\r\n            }\r\n\r\n            _require( deps || [], function() {\r\n                setModule( id, factory, arguments );\r\n            });\r\n        },\r\n\r\n        // 设置module, 兼容CommonJs写法。\r\n        setModule = function( id, factory, args ) {\r\n            var module = {\r\n                    exports: factory\r\n                },\r\n                returned;\r\n\r\n            if ( typeof factory === 'function' ) {\r\n                args.length || (args = [ _require, module.exports, module ]);\r\n                returned = factory.apply( null, args );\r\n                returned !== undefined && (module.exports = returned);\r\n            }\r\n\r\n            modules[ id ] = module.exports;\r\n        },\r\n\r\n        // 根据id获取module\r\n        getModule = function( id ) {\r\n            var module = modules[ id ] || root[ id ];\r\n\r\n            if ( !module ) {\r\n                throw new Error( '`' + id + '` is undefined' );\r\n            }\r\n\r\n            return module;\r\n        },\r\n\r\n        // 将所有modules，将路径ids装换成对象。\r\n        exportsTo = function( obj ) {\r\n            var key, host, parts, part, last, ucFirst;\r\n\r\n            // make the first character upper case.\r\n            ucFirst = function( str ) {\r\n                return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 ));\r\n            };\r\n\r\n            for ( key in modules ) {\r\n                host = obj;\r\n\r\n                if ( !modules.hasOwnProperty( key ) ) {\r\n                    continue;\r\n                }\r\n\r\n                parts = key.split('/');\r\n                last = ucFirst( parts.pop() );\r\n\r\n                while( (part = ucFirst( parts.shift() )) ) {\r\n                    host[ part ] = host[ part ] || {};\r\n                    host = host[ part ];\r\n                }\r\n\r\n                host[ last ] = modules[ key ];\r\n            }\r\n        },\r\n\r\n        exports = factory( root, _define, _require ),\r\n        origin;\r\n\r\n    // exports every module.\r\n    exportsTo( exports );\r\n\r\n    if ( typeof module === 'object' && typeof module.exports === 'object' ) {\r\n\r\n        // For CommonJS and CommonJS-like environments where a proper window is present,\r\n        module.exports = exports;\r\n    } else if ( typeof define === 'function' && define.amd ) {\r\n\r\n        // Allow using this built library as an AMD module\r\n        // in another project. That other project will only\r\n        // see this AMD call, not the internal modules in\r\n        // the closure below.\r\n        define([], exports );\r\n    } else {\r\n\r\n        // Browser globals case. Just assign the\r\n        // result to a property on the global.\r\n        origin = root.WebUploader;\r\n        root.WebUploader = exports;\r\n        root.WebUploader.noConflict = function() {\r\n            root.WebUploader = origin;\r\n        };\r\n    }\r\n})( this, function( window, define, require ) {\r\n\r\n\r\n    /**\r\n     * @fileOverview jQuery or Zepto\r\n     */\r\n    define('dollar-third',[],function() {\r\n        return window.jQuery || window.Zepto;\r\n    });\r\n    /**\r\n     * @fileOverview Dom 操作相关\r\n     */\r\n    define('dollar',[\r\n        'dollar-third'\r\n    ], function( _ ) {\r\n        return _;\r\n    });\r\n    /**\r\n     * @fileOverview 使用jQuery的Promise\r\n     */\r\n    define('promise-third',[\r\n        'dollar'\r\n    ], function( $ ) {\r\n        return {\r\n            Deferred: $.Deferred,\r\n            when: $.when,\r\n    \r\n            isPromise: function( anything ) {\r\n                return anything && typeof anything.then === 'function';\r\n            }\r\n        };\r\n    });\r\n    /**\r\n     * @fileOverview Promise/A+\r\n     */\r\n    define('promise',[\r\n        'promise-third'\r\n    ], function( _ ) {\r\n        return _;\r\n    });\r\n    /**\r\n     * @fileOverview 基础类方法。\r\n     */\r\n    \r\n    /**\r\n     * Web Uploader内部类的详细说明，以下提及的功能类，都可以在`WebUploader`这个变量中访问到。\r\n     *\r\n     * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id.\r\n     * 默认module id该文件的路径，而此路径将会转化成名字空间存放在WebUploader中。如：\r\n     *\r\n     * * module `base`：WebUploader.Base\r\n     * * module `file`: WebUploader.File\r\n     * * module `lib/dnd`: WebUploader.Lib.Dnd\r\n     * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd\r\n     *\r\n     *\r\n     * 以下文档将可能省略`WebUploader`前缀。\r\n     * @module WebUploader\r\n     * @title WebUploader API文档\r\n     */\r\n    define('base',[\r\n        'dollar',\r\n        'promise'\r\n    ], function( $, promise ) {\r\n    \r\n        var noop = function() {},\r\n            call = Function.call;\r\n    \r\n        // http://jsperf.com/uncurrythis\r\n        // 反科里化\r\n        function uncurryThis( fn ) {\r\n            return function() {\r\n                return call.apply( fn, arguments );\r\n            };\r\n        }\r\n    \r\n        function bindFn( fn, context ) {\r\n            return function() {\r\n                return fn.apply( context, arguments );\r\n            };\r\n        }\r\n    \r\n        function createObject( proto ) {\r\n            var f;\r\n    \r\n            if ( Object.create ) {\r\n                return Object.create( proto );\r\n            } else {\r\n                f = function() {};\r\n                f.prototype = proto;\r\n                return new f();\r\n            }\r\n        }\r\n    \r\n    \r\n        /**\r\n         * 基础类，提供一些简单常用的方法。\r\n         * @class Base\r\n         */\r\n        return {\r\n    \r\n            /**\r\n             * @property {String} version 当前版本号。\r\n             */\r\n            version: '0.1.2',\r\n    \r\n            /**\r\n             * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。\r\n             */\r\n            $: $,\r\n    \r\n            Deferred: promise.Deferred,\r\n    \r\n            isPromise: promise.isPromise,\r\n    \r\n            when: promise.when,\r\n    \r\n            /**\r\n             * @description  简单的浏览器检查结果。\r\n             *\r\n             * * `webkit`  webkit版本号，如果浏览器为非webkit内核，此属性为`undefined`。\r\n             * * `chrome`  chrome浏览器版本号，如果浏览器为chrome，此属性为`undefined`。\r\n             * * `ie`  ie浏览器版本号，如果浏览器为非ie，此属性为`undefined`。**暂不支持ie10+**\r\n             * * `firefox`  firefox浏览器版本号，如果浏览器为非firefox，此属性为`undefined`。\r\n             * * `safari`  safari浏览器版本号，如果浏览器为非safari，此属性为`undefined`。\r\n             * * `opera`  opera浏览器版本号，如果浏览器为非opera，此属性为`undefined`。\r\n             *\r\n             * @property {Object} [browser]\r\n             */\r\n            browser: (function( ua ) {\r\n                var ret = {},\r\n                    webkit = ua.match( /WebKit\\/([\\d.]+)/ ),\r\n                    chrome = ua.match( /Chrome\\/([\\d.]+)/ ) ||\r\n                        ua.match( /CriOS\\/([\\d.]+)/ ),\r\n    \r\n                    ie = ua.match( /MSIE\\s([\\d\\.]+)/ ) ||\r\n                        ua.match(/(?:trident)(?:.*rv:([\\w.]+))?/i),\r\n                    firefox = ua.match( /Firefox\\/([\\d.]+)/ ),\r\n                    safari = ua.match( /Safari\\/([\\d.]+)/ ),\r\n                    opera = ua.match( /OPR\\/([\\d.]+)/ );\r\n    \r\n                webkit && (ret.webkit = parseFloat( webkit[ 1 ] ));\r\n                chrome && (ret.chrome = parseFloat( chrome[ 1 ] ));\r\n                ie && (ret.ie = parseFloat( ie[ 1 ] ));\r\n                firefox && (ret.firefox = parseFloat( firefox[ 1 ] ));\r\n                safari && (ret.safari = parseFloat( safari[ 1 ] ));\r\n                opera && (ret.opera = parseFloat( opera[ 1 ] ));\r\n    \r\n                return ret;\r\n            })( navigator.userAgent ),\r\n    \r\n            /**\r\n             * @description  操作系统检查结果。\r\n             *\r\n             * * `android`  如果在android浏览器环境下，此值为对应的android版本号，否则为`undefined`。\r\n             * * `ios` 如果在ios浏览器环境下，此值为对应的ios版本号，否则为`undefined`。\r\n             * @property {Object} [os]\r\n             */\r\n            os: (function( ua ) {\r\n                var ret = {},\r\n    \r\n                    // osx = !!ua.match( /\\(Macintosh\\; Intel / ),\r\n                    android = ua.match( /(?:Android);?[\\s\\/]+([\\d.]+)?/ ),\r\n                    ios = ua.match( /(?:iPad|iPod|iPhone).*OS\\s([\\d_]+)/ );\r\n    \r\n                // osx && (ret.osx = true);\r\n                android && (ret.android = parseFloat( android[ 1 ] ));\r\n                ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) ));\r\n    \r\n                return ret;\r\n            })( navigator.userAgent ),\r\n    \r\n            /**\r\n             * 实现类与类之间的继承。\r\n             * @method inherits\r\n             * @grammar Base.inherits( super ) => child\r\n             * @grammar Base.inherits( super, protos ) => child\r\n             * @grammar Base.inherits( super, protos, statics ) => child\r\n             * @param  {Class} super 父类\r\n             * @param  {Object | Function} [protos] 子类或者对象。如果对象中包含constructor，子类将是用此属性值。\r\n             * @param  {Function} [protos.constructor] 子类构造器，不指定的话将创建个临时的直接执行父类构造器的方法。\r\n             * @param  {Object} [statics] 静态属性或方法。\r\n             * @return {Class} 返回子类。\r\n             * @example\r\n             * function Person() {\r\n             *     console.log( 'Super' );\r\n             * }\r\n             * Person.prototype.hello = function() {\r\n             *     console.log( 'hello' );\r\n             * };\r\n             *\r\n             * var Manager = Base.inherits( Person, {\r\n             *     world: function() {\r\n             *         console.log( 'World' );\r\n             *     }\r\n             * });\r\n             *\r\n             * // 因为没有指定构造器，父类的构造器将会执行。\r\n             * var instance = new Manager();    // => Super\r\n             *\r\n             * // 继承子父类的方法\r\n             * instance.hello();    // => hello\r\n             * instance.world();    // => World\r\n             *\r\n             * // 子类的__super__属性指向父类\r\n             * console.log( Manager.__super__ === Person );    // => true\r\n             */\r\n            inherits: function( Super, protos, staticProtos ) {\r\n                var child;\r\n    \r\n                if ( typeof protos === 'function' ) {\r\n                    child = protos;\r\n                    protos = null;\r\n                } else if ( protos && protos.hasOwnProperty('constructor') ) {\r\n                    child = protos.constructor;\r\n                } else {\r\n                    child = function() {\r\n                        return Super.apply( this, arguments );\r\n                    };\r\n                }\r\n    \r\n                // 复制静态方法\r\n                $.extend( true, child, Super, staticProtos || {} );\r\n    \r\n                /* jshint camelcase: false */\r\n    \r\n                // 让子类的__super__属性指向父类。\r\n                child.__super__ = Super.prototype;\r\n    \r\n                // 构建原型，添加原型方法或属性。\r\n                // 暂时用Object.create实现。\r\n                child.prototype = createObject( Super.prototype );\r\n                protos && $.extend( true, child.prototype, protos );\r\n    \r\n                return child;\r\n            },\r\n    \r\n            /**\r\n             * 一个不做任何事情的方法。可以用来赋值给默认的callback.\r\n             * @method noop\r\n             */\r\n            noop: noop,\r\n    \r\n            /**\r\n             * 返回一个新的方法，此方法将已指定的`context`来执行。\r\n             * @grammar Base.bindFn( fn, context ) => Function\r\n             * @method bindFn\r\n             * @example\r\n             * var doSomething = function() {\r\n             *         console.log( this.name );\r\n             *     },\r\n             *     obj = {\r\n             *         name: 'Object Name'\r\n             *     },\r\n             *     aliasFn = Base.bind( doSomething, obj );\r\n             *\r\n             *  aliasFn();    // => Object Name\r\n             *\r\n             */\r\n            bindFn: bindFn,\r\n    \r\n            /**\r\n             * 引用Console.log如果存在的话，否则引用一个[空函数loop](#WebUploader:Base.log)。\r\n             * @grammar Base.log( args... ) => undefined\r\n             * @method log\r\n             */\r\n            log: (function() {\r\n                if ( window.console ) {\r\n                    return bindFn( console.log, console );\r\n                }\r\n                return noop;\r\n            })(),\r\n    \r\n            nextTick: (function() {\r\n    \r\n                return function( cb ) {\r\n                    setTimeout( cb, 1 );\r\n                };\r\n    \r\n                // @bug 当浏览器不在当前窗口时就停了。\r\n                // var next = window.requestAnimationFrame ||\r\n                //     window.webkitRequestAnimationFrame ||\r\n                //     window.mozRequestAnimationFrame ||\r\n                //     function( cb ) {\r\n                //         window.setTimeout( cb, 1000 / 60 );\r\n                //     };\r\n    \r\n                // // fix: Uncaught TypeError: Illegal invocation\r\n                // return bindFn( next, window );\r\n            })(),\r\n    \r\n            /**\r\n             * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。\r\n             * 将用来将非数组对象转化成数组对象。\r\n             * @grammar Base.slice( target, start[, end] ) => Array\r\n             * @method slice\r\n             * @example\r\n             * function doSomthing() {\r\n             *     var args = Base.slice( arguments, 1 );\r\n             *     console.log( args );\r\n             * }\r\n             *\r\n             * doSomthing( 'ignored', 'arg2', 'arg3' );    // => Array [\"arg2\", \"arg3\"]\r\n             */\r\n            slice: uncurryThis( [].slice ),\r\n    \r\n            /**\r\n             * 生成唯一的ID\r\n             * @method guid\r\n             * @grammar Base.guid() => String\r\n             * @grammar Base.guid( prefx ) => String\r\n             */\r\n            guid: (function() {\r\n                var counter = 0;\r\n    \r\n                return function( prefix ) {\r\n                    var guid = (+new Date()).toString( 32 ),\r\n                        i = 0;\r\n    \r\n                    for ( ; i < 5; i++ ) {\r\n                        guid += Math.floor( Math.random() * 65535 ).toString( 32 );\r\n                    }\r\n    \r\n                    return (prefix || 'wu_') + guid + (counter++).toString( 32 );\r\n                };\r\n            })(),\r\n    \r\n            /**\r\n             * 格式化文件大小, 输出成带单位的字符串\r\n             * @method formatSize\r\n             * @grammar Base.formatSize( size ) => String\r\n             * @grammar Base.formatSize( size, pointLength ) => String\r\n             * @grammar Base.formatSize( size, pointLength, units ) => String\r\n             * @param {Number} size 文件大小\r\n             * @param {Number} [pointLength=2] 精确到的小数点数。\r\n             * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节，到千字节，一直往上指定。如果单位数组里面只指定了到了K(千字节)，同时文件大小大于M, 此方法的输出将还是显示成多少K.\r\n             * @example\r\n             * console.log( Base.formatSize( 100 ) );    // => 100B\r\n             * console.log( Base.formatSize( 1024 ) );    // => 1.00K\r\n             * console.log( Base.formatSize( 1024, 0 ) );    // => 1K\r\n             * console.log( Base.formatSize( 1024 * 1024 ) );    // => 1.00M\r\n             * console.log( Base.formatSize( 1024 * 1024 * 1024 ) );    // => 1.00G\r\n             * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) );    // => 1024MB\r\n             */\r\n            formatSize: function( size, pointLength, units ) {\r\n                var unit;\r\n    \r\n                units = units || [ 'B', 'K', 'M', 'G', 'TB' ];\r\n    \r\n                while ( (unit = units.shift()) && size > 1024 ) {\r\n                    size = size / 1024;\r\n                }\r\n    \r\n                return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) +\r\n                        unit;\r\n            }\r\n        };\r\n    });\r\n    /**\r\n     * 事件处理类，可以独立使用，也可以扩展给对象使用。\r\n     * @fileOverview Mediator\r\n     */\r\n    define('mediator',[\r\n        'base'\r\n    ], function( Base ) {\r\n        var $ = Base.$,\r\n            slice = [].slice,\r\n            separator = /\\s+/,\r\n            protos;\r\n    \r\n        // 根据条件过滤出事件handlers.\r\n        function findHandlers( arr, name, callback, context ) {\r\n            return $.grep( arr, function( handler ) {\r\n                return handler &&\r\n                        (!name || handler.e === name) &&\r\n                        (!callback || handler.cb === callback ||\r\n                        handler.cb._cb === callback) &&\r\n                        (!context || handler.ctx === context);\r\n            });\r\n        }\r\n    \r\n        function eachEvent( events, callback, iterator ) {\r\n            // 不支持对象，只支持多个event用空格隔开\r\n            $.each( (events || '').split( separator ), function( _, key ) {\r\n                iterator( key, callback );\r\n            });\r\n        }\r\n    \r\n        function triggerHanders( events, args ) {\r\n            var stoped = false,\r\n                i = -1,\r\n                len = events.length,\r\n                handler;\r\n    \r\n            while ( ++i < len ) {\r\n                handler = events[ i ];\r\n    \r\n                if ( handler.cb.apply( handler.ctx2, args ) === false ) {\r\n                    stoped = true;\r\n                    break;\r\n                }\r\n            }\r\n    \r\n            return !stoped;\r\n        }\r\n    \r\n        protos = {\r\n    \r\n            /**\r\n             * 绑定事件。\r\n             *\r\n             * `callback`方法在执行时，arguments将会来源于trigger的时候携带的参数。如\r\n             * ```javascript\r\n             * var obj = {};\r\n             *\r\n             * // 使得obj有事件行为\r\n             * Mediator.installTo( obj );\r\n             *\r\n             * obj.on( 'testa', function( arg1, arg2 ) {\r\n             *     console.log( arg1, arg2 ); // => 'arg1', 'arg2'\r\n             * });\r\n             *\r\n             * obj.trigger( 'testa', 'arg1', 'arg2' );\r\n             * ```\r\n             *\r\n             * 如果`callback`中，某一个方法`return false`了，则后续的其他`callback`都不会被执行到。\r\n             * 切会影响到`trigger`方法的返回值，为`false`。\r\n             *\r\n             * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处，\r\n             * 就是第一个参数为`type`，记录当前是什么事件在触发。此类`callback`的优先级比脚低，会再正常`callback`执行完后触发。\r\n             * ```javascript\r\n             * obj.on( 'all', function( type, arg1, arg2 ) {\r\n             *     console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2'\r\n             * });\r\n             * ```\r\n             *\r\n             * @method on\r\n             * @grammar on( name, callback[, context] ) => self\r\n             * @param  {String}   name     事件名，支持多个事件用空格隔开\r\n             * @param  {Function} callback 事件处理器\r\n             * @param  {Object}   [context]  事件处理器的上下文。\r\n             * @return {self} 返回自身，方便链式\r\n             * @chainable\r\n             * @class Mediator\r\n             */\r\n            on: function( name, callback, context ) {\r\n                var me = this,\r\n                    set;\r\n    \r\n                if ( !callback ) {\r\n                    return this;\r\n                }\r\n    \r\n                set = this._events || (this._events = []);\r\n    \r\n                eachEvent( name, callback, function( name, callback ) {\r\n                    var handler = { e: name };\r\n    \r\n                    handler.cb = callback;\r\n                    handler.ctx = context;\r\n                    handler.ctx2 = context || me;\r\n                    handler.id = set.length;\r\n    \r\n                    set.push( handler );\r\n                });\r\n    \r\n                return this;\r\n            },\r\n    \r\n            /**\r\n             * 绑定事件，且当handler执行完后，自动解除绑定。\r\n             * @method once\r\n             * @grammar once( name, callback[, context] ) => self\r\n             * @param  {String}   name     事件名\r\n             * @param  {Function} callback 事件处理器\r\n             * @param  {Object}   [context]  事件处理器的上下文。\r\n             * @return {self} 返回自身，方便链式\r\n             * @chainable\r\n             */\r\n            once: function( name, callback, context ) {\r\n                var me = this;\r\n    \r\n                if ( !callback ) {\r\n                    return me;\r\n                }\r\n    \r\n                eachEvent( name, callback, function( name, callback ) {\r\n                    var once = function() {\r\n                            me.off( name, once );\r\n                            return callback.apply( context || me, arguments );\r\n                        };\r\n    \r\n                    once._cb = callback;\r\n                    me.on( name, once, context );\r\n                });\r\n    \r\n                return me;\r\n            },\r\n    \r\n            /**\r\n             * 解除事件绑定\r\n             * @method off\r\n             * @grammar off( [name[, callback[, context] ] ] ) => self\r\n             * @param  {String}   [name]     事件名\r\n             * @param  {Function} [callback] 事件处理器\r\n             * @param  {Object}   [context]  事件处理器的上下文。\r\n             * @return {self} 返回自身，方便链式\r\n             * @chainable\r\n             */\r\n            off: function( name, cb, ctx ) {\r\n                var events = this._events;\r\n    \r\n                if ( !events ) {\r\n                    return this;\r\n                }\r\n    \r\n                if ( !name && !cb && !ctx ) {\r\n                    this._events = [];\r\n                    return this;\r\n                }\r\n    \r\n                eachEvent( name, cb, function( name, cb ) {\r\n                    $.each( findHandlers( events, name, cb, ctx ), function() {\r\n                        delete events[ this.id ];\r\n                    });\r\n                });\r\n    \r\n                return this;\r\n            },\r\n    \r\n            /**\r\n             * 触发事件\r\n             * @method trigger\r\n             * @grammar trigger( name[, args...] ) => self\r\n             * @param  {String}   type     事件名\r\n             * @param  {*} [...] 任意参数\r\n             * @return {Boolean} 如果handler中return false了，则返回false, 否则返回true\r\n             */\r\n            trigger: function( type ) {\r\n                var args, events, allEvents;\r\n    \r\n                if ( !this._events || !type ) {\r\n                    return this;\r\n                }\r\n    \r\n                args = slice.call( arguments, 1 );\r\n                events = findHandlers( this._events, type );\r\n                allEvents = findHandlers( this._events, 'all' );\r\n    \r\n                return triggerHanders( events, args ) &&\r\n                        triggerHanders( allEvents, arguments );\r\n            }\r\n        };\r\n    \r\n        /**\r\n         * 中介者，它本身是个单例，但可以通过[installTo](#WebUploader:Mediator:installTo)方法，使任何对象具备事件行为。\r\n         * 主要目的是负责模块与模块之间的合作，降低耦合度。\r\n         *\r\n         * @class Mediator\r\n         */\r\n        return $.extend({\r\n    \r\n            /**\r\n             * 可以通过这个接口，使任何对象具备事件功能。\r\n             * @method installTo\r\n             * @param  {Object} obj 需要具备事件行为的对象。\r\n             * @return {Object} 返回obj.\r\n             */\r\n            installTo: function( obj ) {\r\n                return $.extend( obj, protos );\r\n            }\r\n    \r\n        }, protos );\r\n    });\r\n    /**\r\n     * @fileOverview Uploader上传类\r\n     */\r\n    define('uploader',[\r\n        'base',\r\n        'mediator'\r\n    ], function( Base, Mediator ) {\r\n    \r\n        var $ = Base.$;\r\n    \r\n        /**\r\n         * 上传入口类。\r\n         * @class Uploader\r\n         * @constructor\r\n         * @grammar new Uploader( opts ) => Uploader\r\n         * @example\r\n         * var uploader = WebUploader.Uploader({\r\n         *     swf: 'path_of_swf/Uploader.swf',\r\n         *\r\n         *     // 开起分片上传。\r\n         *     chunked: true\r\n         * });\r\n         */\r\n        function Uploader( opts ) {\r\n            this.options = $.extend( true, {}, Uploader.options, opts );\r\n            this._init( this.options );\r\n        }\r\n    \r\n        // default Options\r\n        // widgets中有相应扩展\r\n        Uploader.options = {};\r\n        Mediator.installTo( Uploader.prototype );\r\n    \r\n        // 批量添加纯命令式方法。\r\n        $.each({\r\n            upload: 'start-upload',\r\n            stop: 'stop-upload',\r\n            getFile: 'get-file',\r\n            getFiles: 'get-files',\r\n            addFile: 'add-file',\r\n            addFiles: 'add-file',\r\n            sort: 'sort-files',\r\n            removeFile: 'remove-file',\r\n            skipFile: 'skip-file',\r\n            retry: 'retry',\r\n            isInProgress: 'is-in-progress',\r\n            makeThumb: 'make-thumb',\r\n            getDimension: 'get-dimension',\r\n            addButton: 'add-btn',\r\n            getRuntimeType: 'get-runtime-type',\r\n            refresh: 'refresh',\r\n            disable: 'disable',\r\n            enable: 'enable',\r\n            reset: 'reset'\r\n        }, function( fn, command ) {\r\n            Uploader.prototype[ fn ] = function() {\r\n                return this.request( command, arguments );\r\n            };\r\n        });\r\n    \r\n        $.extend( Uploader.prototype, {\r\n            state: 'pending',\r\n    \r\n            _init: function( opts ) {\r\n                var me = this;\r\n    \r\n                me.request( 'init', opts, function() {\r\n                    me.state = 'ready';\r\n                    me.trigger('ready');\r\n                });\r\n            },\r\n    \r\n            /**\r\n             * 获取或者设置Uploader配置项。\r\n             * @method option\r\n             * @grammar option( key ) => *\r\n             * @grammar option( key, val ) => self\r\n             * @example\r\n             *\r\n             * // 初始状态图片上传前不会压缩\r\n             * var uploader = new WebUploader.Uploader({\r\n             *     resize: null;\r\n             * });\r\n             *\r\n             * // 修改后图片上传前，尝试将图片压缩到1600 * 1600\r\n             * uploader.options( 'resize', {\r\n             *     width: 1600,\r\n             *     height: 1600\r\n             * });\r\n             */\r\n            option: function( key, val ) {\r\n                var opts = this.options;\r\n    \r\n                // setter\r\n                if ( arguments.length > 1 ) {\r\n    \r\n                    if ( $.isPlainObject( val ) &&\r\n                            $.isPlainObject( opts[ key ] ) ) {\r\n                        $.extend( opts[ key ], val );\r\n                    } else {\r\n                        opts[ key ] = val;\r\n                    }\r\n    \r\n                } else {    // getter\r\n                    return key ? opts[ key ] : opts;\r\n                }\r\n            },\r\n    \r\n            /**\r\n             * 获取文件统计信息。返回一个包含一下信息的对象。\r\n             * * `successNum` 上传成功的文件数\r\n             * * `uploadFailNum` 上传失败的文件数\r\n             * * `cancelNum` 被删除的文件数\r\n             * * `invalidNum` 无效的文件数\r\n             * * `queueNum` 还在队列中的文件数\r\n             * @method getStats\r\n             * @grammar getStats() => Object\r\n             */\r\n            getStats: function() {\r\n                // return this._mgr.getStats.apply( this._mgr, arguments );\r\n                var stats = this.request('get-stats');\r\n    \r\n                return {\r\n                    successNum: stats.numOfSuccess,\r\n    \r\n                    // who care?\r\n                    // queueFailNum: 0,\r\n                    cancelNum: stats.numOfCancel,\r\n                    invalidNum: stats.numOfInvalid,\r\n                    uploadFailNum: stats.numOfUploadFailed,\r\n                    queueNum: stats.numOfQueue\r\n                };\r\n            },\r\n    \r\n            // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器\r\n            trigger: function( type/*, args...*/ ) {\r\n                var args = [].slice.call( arguments, 1 ),\r\n                    opts = this.options,\r\n                    name = 'on' + type.substring( 0, 1 ).toUpperCase() +\r\n                        type.substring( 1 );\r\n    \r\n                if (\r\n                        // 调用通过on方法注册的handler.\r\n                        Mediator.trigger.apply( this, arguments ) === false ||\r\n    \r\n                        // 调用opts.onEvent\r\n                        $.isFunction( opts[ name ] ) &&\r\n                        opts[ name ].apply( this, args ) === false ||\r\n    \r\n                        // 调用this.onEvent\r\n                        $.isFunction( this[ name ] ) &&\r\n                        this[ name ].apply( this, args ) === false ||\r\n    \r\n                        // 广播所有uploader的事件。\r\n                        Mediator.trigger.apply( Mediator,\r\n                        [ this, type ].concat( args ) ) === false ) {\r\n    \r\n                    return false;\r\n                }\r\n    \r\n                return true;\r\n            },\r\n    \r\n            // widgets/widget.js将补充此方法的详细文档。\r\n            request: Base.noop\r\n        });\r\n    \r\n        /**\r\n         * 创建Uploader实例，等同于new Uploader( opts );\r\n         * @method create\r\n         * @class Base\r\n         * @static\r\n         * @grammar Base.create( opts ) => Uploader\r\n         */\r\n        Base.create = Uploader.create = function( opts ) {\r\n            return new Uploader( opts );\r\n        };\r\n    \r\n        // 暴露Uploader，可以通过它来扩展业务逻辑。\r\n        Base.Uploader = Uploader;\r\n    \r\n        return Uploader;\r\n    });\r\n    /**\r\n     * @fileOverview Runtime管理器，负责Runtime的选择, 连接\r\n     */\r\n    define('runtime/runtime',[\r\n        'base',\r\n        'mediator'\r\n    ], function( Base, Mediator ) {\r\n    \r\n        var $ = Base.$,\r\n            factories = {},\r\n    \r\n            // 获取对象的第一个key\r\n            getFirstKey = function( obj ) {\r\n                for ( var key in obj ) {\r\n                    if ( obj.hasOwnProperty( key ) ) {\r\n                        return key;\r\n                    }\r\n                }\r\n                return null;\r\n            };\r\n    \r\n        // 接口类。\r\n        function Runtime( options ) {\r\n            this.options = $.extend({\r\n                container: document.body\r\n            }, options );\r\n            this.uid = Base.guid('rt_');\r\n        }\r\n    \r\n        $.extend( Runtime.prototype, {\r\n    \r\n            getContainer: function() {\r\n                var opts = this.options,\r\n                    parent, container;\r\n    \r\n                if ( this._container ) {\r\n                    return this._container;\r\n                }\r\n    \r\n                parent = $( opts.container || document.body );\r\n                container = $( document.createElement('div') );\r\n    \r\n                container.attr( 'id', 'rt_' + this.uid );\r\n                container.css({\r\n                    position: 'absolute',\r\n                    top: '0px',\r\n                    left: '0px',\r\n                    width: '1px',\r\n                    height: '1px',\r\n                    overflow: 'hidden'\r\n                });\r\n    \r\n                parent.append( container );\r\n                parent.addClass('webuploader-container');\r\n                this._container = container;\r\n                return container;\r\n            },\r\n    \r\n            init: Base.noop,\r\n            exec: Base.noop,\r\n    \r\n            destroy: function() {\r\n                if ( this._container ) {\r\n                    this._container.parentNode.removeChild( this.__container );\r\n                }\r\n    \r\n                this.off();\r\n            }\r\n        });\r\n    \r\n        Runtime.orders = 'html5,flash';\r\n    \r\n    \r\n        /**\r\n         * 添加Runtime实现。\r\n         * @param {String} type    类型\r\n         * @param {Runtime} factory 具体Runtime实现。\r\n         */\r\n        Runtime.addRuntime = function( type, factory ) {\r\n            factories[ type ] = factory;\r\n        };\r\n    \r\n        Runtime.hasRuntime = function( type ) {\r\n            return !!(type ? factories[ type ] : getFirstKey( factories ));\r\n        };\r\n    \r\n        Runtime.create = function( opts, orders ) {\r\n            var type, runtime;\r\n    \r\n            orders = orders || Runtime.orders;\r\n            $.each( orders.split( /\\s*,\\s*/g ), function() {\r\n                if ( factories[ this ] ) {\r\n                    type = this;\r\n                    return false;\r\n                }\r\n            });\r\n    \r\n            type = type || getFirstKey( factories );\r\n    \r\n            if ( !type ) {\r\n                throw new Error('Runtime Error');\r\n            }\r\n    \r\n            runtime = new factories[ type ]( opts );\r\n            return runtime;\r\n        };\r\n    \r\n        Mediator.installTo( Runtime.prototype );\r\n        return Runtime;\r\n    });\r\n    \r\n    /**\r\n     * @fileOverview Runtime管理器，负责Runtime的选择, 连接\r\n     */\r\n    define('runtime/client',[\r\n        'base',\r\n        'mediator',\r\n        'runtime/runtime'\r\n    ], function( Base, Mediator, Runtime ) {\r\n    \r\n        var cache;\r\n    \r\n        cache = (function() {\r\n            var obj = {};\r\n    \r\n            return {\r\n                add: function( runtime ) {\r\n                    obj[ runtime.uid ] = runtime;\r\n                },\r\n    \r\n                get: function( ruid, standalone ) {\r\n                    var i;\r\n    \r\n                    if ( ruid ) {\r\n                        return obj[ ruid ];\r\n                    }\r\n    \r\n                    for ( i in obj ) {\r\n                        // 有些类型不能重用，比如filepicker.\r\n                        if ( standalone && obj[ i ].__standalone ) {\r\n                            continue;\r\n                        }\r\n    \r\n                        return obj[ i ];\r\n                    }\r\n    \r\n                    return null;\r\n                },\r\n    \r\n                remove: function( runtime ) {\r\n                    delete obj[ runtime.uid ];\r\n                }\r\n            };\r\n        })();\r\n    \r\n        function RuntimeClient( component, standalone ) {\r\n            var deferred = Base.Deferred(),\r\n                runtime;\r\n    \r\n            this.uid = Base.guid('client_');\r\n    \r\n            // 允许runtime没有初始化之前，注册一些方法在初始化后执行。\r\n            this.runtimeReady = function( cb ) {\r\n                return deferred.done( cb );\r\n            };\r\n    \r\n            this.connectRuntime = function( opts, cb ) {\r\n    \r\n                // already connected.\r\n                if ( runtime ) {\r\n                    throw new Error('already connected!');\r\n                }\r\n    \r\n                deferred.done( cb );\r\n    \r\n                if ( typeof opts === 'string' && cache.get( opts ) ) {\r\n                    runtime = cache.get( opts );\r\n                }\r\n    \r\n                // 像filePicker只能独立存在，不能公用。\r\n                runtime = runtime || cache.get( null, standalone );\r\n    \r\n                // 需要创建\r\n                if ( !runtime ) {\r\n                    runtime = Runtime.create( opts, opts.runtimeOrder );\r\n                    runtime.__promise = deferred.promise();\r\n                    runtime.once( 'ready', deferred.resolve );\r\n                    runtime.init();\r\n                    cache.add( runtime );\r\n                    runtime.__client = 1;\r\n                } else {\r\n                    // 来自cache\r\n                    Base.$.extend( runtime.options, opts );\r\n                    runtime.__promise.then( deferred.resolve );\r\n                    runtime.__client++;\r\n                }\r\n    \r\n                standalone && (runtime.__standalone = standalone);\r\n                return runtime;\r\n            };\r\n    \r\n            this.getRuntime = function() {\r\n                return runtime;\r\n            };\r\n    \r\n            this.disconnectRuntime = function() {\r\n                if ( !runtime ) {\r\n                    return;\r\n                }\r\n    \r\n                runtime.__client--;\r\n    \r\n                if ( runtime.__client <= 0 ) {\r\n                    cache.remove( runtime );\r\n                    delete runtime.__promise;\r\n                    runtime.destroy();\r\n                }\r\n    \r\n                runtime = null;\r\n            };\r\n    \r\n            this.exec = function() {\r\n                if ( !runtime ) {\r\n                    return;\r\n                }\r\n    \r\n                var args = Base.slice( arguments );\r\n                component && args.unshift( component );\r\n    \r\n                return runtime.exec.apply( this, args );\r\n            };\r\n    \r\n            this.getRuid = function() {\r\n                return runtime && runtime.uid;\r\n            };\r\n    \r\n            this.destroy = (function( destroy ) {\r\n                return function() {\r\n                    destroy && destroy.apply( this, arguments );\r\n                    this.trigger('destroy');\r\n                    this.off();\r\n                    this.exec('destroy');\r\n                    this.disconnectRuntime();\r\n                };\r\n            })( this.destroy );\r\n        }\r\n    \r\n        Mediator.installTo( RuntimeClient.prototype );\r\n        return RuntimeClient;\r\n    });\r\n    /**\r\n     * @fileOverview Blob\r\n     */\r\n    define('lib/blob',[\r\n        'base',\r\n        'runtime/client'\r\n    ], function( Base, RuntimeClient ) {\r\n    \r\n        function Blob( ruid, source ) {\r\n            var me = this;\r\n    \r\n            me.source = source;\r\n            me.ruid = ruid;\r\n    \r\n            RuntimeClient.call( me, 'Blob' );\r\n    \r\n            this.uid = source.uid || this.uid;\r\n            this.type = source.type || '';\r\n            this.size = source.size || 0;\r\n    \r\n            if ( ruid ) {\r\n                me.connectRuntime( ruid );\r\n            }\r\n        }\r\n    \r\n        Base.inherits( RuntimeClient, {\r\n            constructor: Blob,\r\n    \r\n            slice: function( start, end ) {\r\n                return this.exec( 'slice', start, end );\r\n            },\r\n    \r\n            getSource: function() {\r\n                return this.source;\r\n            }\r\n        });\r\n    \r\n        return Blob;\r\n    });\r\n    /**\r\n     * 为了统一化Flash的File和HTML5的File而存在。\r\n     * 以至于要调用Flash里面的File，也可以像调用HTML5版本的File一下。\r\n     * @fileOverview File\r\n     */\r\n    define('lib/file',[\r\n        'base',\r\n        'lib/blob'\r\n    ], function( Base, Blob ) {\r\n    \r\n        var uid = 1,\r\n            rExt = /\\.([^.]+)$/;\r\n    \r\n        function File( ruid, file ) {\r\n            var ext;\r\n    \r\n            Blob.apply( this, arguments );\r\n            this.name = file.name || ('untitled' + uid++);\r\n            ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : '';\r\n    \r\n            // todo 支持其他类型文件的转换。\r\n    \r\n            // 如果有mimetype, 但是文件名里面没有找出后缀规律\r\n            if ( !ext && this.type ) {\r\n                ext = /\\/(jpg|jpeg|png|gif|bmp)$/i.exec( this.type ) ?\r\n                        RegExp.$1.toLowerCase() : '';\r\n                this.name += '.' + ext;\r\n            }\r\n    \r\n            // 如果没有指定mimetype, 但是知道文件后缀。\r\n            if ( !this.type &&  ~'jpg,jpeg,png,gif,bmp'.indexOf( ext ) ) {\r\n                this.type = 'image/' + (ext === 'jpg' ? 'jpeg' : ext);\r\n            }\r\n    \r\n            this.ext = ext;\r\n            this.lastModifiedDate = file.lastModifiedDate ||\r\n                    (new Date()).toLocaleString();\r\n        }\r\n    \r\n        return Base.inherits( Blob, File );\r\n    });\r\n    \r\n    /**\r\n     * @fileOverview 错误信息\r\n     */\r\n    define('lib/filepicker',[\r\n        'base',\r\n        'runtime/client',\r\n        'lib/file'\r\n    ], function( Base, RuntimeClent, File ) {\r\n    \r\n        var $ = Base.$;\r\n    \r\n        function FilePicker( opts ) {\r\n            opts = this.options = $.extend({}, FilePicker.options, opts );\r\n    \r\n            opts.container = $( opts.id );\r\n    \r\n            if ( !opts.container.length ) {\r\n                throw new Error('按钮指定错误');\r\n            }\r\n    \r\n            opts.innerHTML = opts.innerHTML || opts.label ||\r\n                    opts.container.html() || '';\r\n    \r\n            opts.button = $( opts.button || document.createElement('div') );\r\n            opts.button.html( opts.innerHTML );\r\n            opts.container.html( opts.button );\r\n    \r\n            RuntimeClent.call( this, 'FilePicker', true );\r\n        }\r\n    \r\n        FilePicker.options = {\r\n            button: null,\r\n            container: null,\r\n            label: null,\r\n            innerHTML: null,\r\n            multiple: true,\r\n            accept: null,\r\n            name: 'file'\r\n        };\r\n    \r\n        Base.inherits( RuntimeClent, {\r\n            constructor: FilePicker,\r\n    \r\n            init: function() {\r\n                var me = this,\r\n                    opts = me.options,\r\n                    button = opts.button;\r\n    \r\n                button.addClass('webuploader-pick');\r\n    \r\n                me.on( 'all', function( type ) {\r\n                    var files;\r\n    \r\n                    switch ( type ) {\r\n                        case 'mouseenter':\r\n                            button.addClass('webuploader-pick-hover');\r\n                            break;\r\n    \r\n                        case 'mouseleave':\r\n                            button.removeClass('webuploader-pick-hover');\r\n                            break;\r\n    \r\n                        case 'change':\r\n                            files = me.exec('getFiles');\r\n                            me.trigger( 'select', $.map( files, function( file ) {\r\n                                file = new File( me.getRuid(), file );\r\n    \r\n                                // 记录来源。\r\n                                file._refer = opts.container;\r\n                                return file;\r\n                            }), opts.container );\r\n                            break;\r\n                    }\r\n                });\r\n    \r\n                me.connectRuntime( opts, function() {\r\n                    me.refresh();\r\n                    me.exec( 'init', opts );\r\n                    me.trigger('ready');\r\n                });\r\n    \r\n                $( window ).on( 'resize', function() {\r\n                    me.refresh();\r\n                });\r\n            },\r\n    \r\n            refresh: function() {\r\n                var shimContainer = this.getRuntime().getContainer(),\r\n                    button = this.options.button,\r\n                    width = button.outerWidth ?\r\n                            button.outerWidth() : button.width(),\r\n    \r\n                    height = button.outerHeight ?\r\n                            button.outerHeight() : button.height(),\r\n    \r\n                    pos = button.offset();\r\n    \r\n                width && height && shimContainer.css({\r\n                    bottom: 'auto',\r\n                    right: 'auto',\r\n                    width: width + 'px',\r\n                    height: height + 'px'\r\n                }).offset( pos );\r\n            },\r\n    \r\n            enable: function() {\r\n                var btn = this.options.button;\r\n    \r\n                btn.removeClass('webuploader-pick-disable');\r\n                this.refresh();\r\n            },\r\n    \r\n            disable: function() {\r\n                var btn = this.options.button;\r\n    \r\n                this.getRuntime().getContainer().css({\r\n                    top: '-99999px'\r\n                });\r\n    \r\n                btn.addClass('webuploader-pick-disable');\r\n            },\r\n    \r\n            destroy: function() {\r\n                if ( this.runtime ) {\r\n                    this.exec('destroy');\r\n                    this.disconnectRuntime();\r\n                }\r\n            }\r\n        });\r\n    \r\n        return FilePicker;\r\n    });\r\n    \r\n    /**\r\n     * @fileOverview 组件基类。\r\n     */\r\n    define('widgets/widget',[\r\n        'base',\r\n        'uploader'\r\n    ], function( Base, Uploader ) {\r\n    \r\n        var $ = Base.$,\r\n            _init = Uploader.prototype._init,\r\n            IGNORE = {},\r\n            widgetClass = [];\r\n    \r\n        function isArrayLike( obj ) {\r\n            if ( !obj ) {\r\n                return false;\r\n            }\r\n    \r\n            var length = obj.length,\r\n                type = $.type( obj );\r\n    \r\n            if ( obj.nodeType === 1 && length ) {\r\n                return true;\r\n            }\r\n    \r\n            return type === 'array' || type !== 'function' && type !== 'string' &&\r\n                    (length === 0 || typeof length === 'number' && length > 0 &&\r\n                    (length - 1) in obj);\r\n        }\r\n    \r\n        function Widget( uploader ) {\r\n            this.owner = uploader;\r\n            this.options = uploader.options;\r\n        }\r\n    \r\n        $.extend( Widget.prototype, {\r\n    \r\n            init: Base.noop,\r\n    \r\n            // 类Backbone的事件监听声明，监听uploader实例上的事件\r\n            // widget直接无法监听事件，事件只能通过uploader来传递\r\n            invoke: function( apiName, args ) {\r\n    \r\n                /*\r\n                    {\r\n                        'make-thumb': 'makeThumb'\r\n                    }\r\n                 */\r\n                var map = this.responseMap;\r\n    \r\n                // 如果无API响应声明则忽略\r\n                if ( !map || !(apiName in map) || !(map[ apiName ] in this) ||\r\n                        !$.isFunction( this[ map[ apiName ] ] ) ) {\r\n    \r\n                    return IGNORE;\r\n                }\r\n    \r\n                return this[ map[ apiName ] ].apply( this, args );\r\n    \r\n            },\r\n    \r\n            /**\r\n             * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。\r\n             * @method request\r\n             * @grammar request( command, args ) => * | Promise\r\n             * @grammar request( command, args, callback ) => Promise\r\n             * @for  Uploader\r\n             */\r\n            request: function() {\r\n                return this.owner.request.apply( this.owner, arguments );\r\n            }\r\n        });\r\n    \r\n        // 扩展Uploader.\r\n        $.extend( Uploader.prototype, {\r\n    \r\n            // 覆写_init用来初始化widgets\r\n            _init: function() {\r\n                var me = this,\r\n                    widgets = me._widgets = [];\r\n    \r\n                $.each( widgetClass, function( _, klass ) {\r\n                    widgets.push( new klass( me ) );\r\n                });\r\n    \r\n                return _init.apply( me, arguments );\r\n            },\r\n    \r\n            request: function( apiName, args, callback ) {\r\n                var i = 0,\r\n                    widgets = this._widgets,\r\n                    len = widgets.length,\r\n                    rlts = [],\r\n                    dfds = [],\r\n                    widget, rlt, promise, key;\r\n    \r\n                args = isArrayLike( args ) ? args : [ args ];\r\n    \r\n                for ( ; i < len; i++ ) {\r\n                    widget = widgets[ i ];\r\n                    rlt = widget.invoke( apiName, args );\r\n    \r\n                    if ( rlt !== IGNORE ) {\r\n    \r\n                        // Deferred对象\r\n                        if ( Base.isPromise( rlt ) ) {\r\n                            dfds.push( rlt );\r\n                        } else {\r\n                            rlts.push( rlt );\r\n                        }\r\n                    }\r\n                }\r\n    \r\n                // 如果有callback，则用异步方式。\r\n                if ( callback || dfds.length ) {\r\n                    promise = Base.when.apply( Base, dfds );\r\n                    key = promise.pipe ? 'pipe' : 'then';\r\n    \r\n                    // 很重要不能删除。删除了会死循环。\r\n                    // 保证执行顺序。让callback总是在下一个tick中执行。\r\n                    return promise[ key ](function() {\r\n                                var deferred = Base.Deferred(),\r\n                                    args = arguments;\r\n    \r\n                                setTimeout(function() {\r\n                                    deferred.resolve.apply( deferred, args );\r\n                                }, 1 );\r\n    \r\n                                return deferred.promise();\r\n                            })[ key ]( callback || Base.noop );\r\n                } else {\r\n                    return rlts[ 0 ];\r\n                }\r\n            }\r\n        });\r\n    \r\n        /**\r\n         * 添加组件\r\n         * @param  {object} widgetProto 组件原型，构造函数通过constructor属性定义\r\n         * @param  {object} responseMap API名称与函数实现的映射\r\n         * @example\r\n         *     Uploader.register( {\r\n         *         init: function( options ) {},\r\n         *         makeThumb: function() {}\r\n         *     }, {\r\n         *         'make-thumb': 'makeThumb'\r\n         *     } );\r\n         */\r\n        Uploader.register = Widget.register = function( responseMap, widgetProto ) {\r\n            var map = { init: 'init' },\r\n                klass;\r\n    \r\n            if ( arguments.length === 1 ) {\r\n                widgetProto = responseMap;\r\n                widgetProto.responseMap = map;\r\n            } else {\r\n                widgetProto.responseMap = $.extend( map, responseMap );\r\n            }\r\n    \r\n            klass = Base.inherits( Widget, widgetProto );\r\n            widgetClass.push( klass );\r\n    \r\n            return klass;\r\n        };\r\n    \r\n        return Widget;\r\n    });\r\n    /**\r\n     * @fileOverview 文件选择相关\r\n     */\r\n    define('widgets/filepicker',[\r\n        'base',\r\n        'uploader',\r\n        'lib/filepicker',\r\n        'widgets/widget'\r\n    ], function( Base, Uploader, FilePicker ) {\r\n        var $ = Base.$;\r\n    \r\n        $.extend( Uploader.options, {\r\n    \r\n            /**\r\n             * @property {Selector | Object} [pick=undefined]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 指定选择文件的按钮容器，不指定则不创建按钮。\r\n             *\r\n             * * `id` {Seletor} 指定选择文件的按钮容器，不指定则不创建按钮。\r\n             * * `label` {String} 请采用 `innerHTML` 代替\r\n             * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。\r\n             * * `multiple` {Boolean} 是否开起同时选择多个文件能力。\r\n             */\r\n            pick: null,\r\n    \r\n            /**\r\n             * @property {Arroy} [accept=null]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表，所以这里需要分开指定。\r\n             *\r\n             * * `title` {String} 文字描述\r\n             * * `extensions` {String} 允许的文件后缀，不带点，多个用逗号分割。\r\n             * * `mimeTypes` {String} 多个用逗号分割。\r\n             *\r\n             * 如：\r\n             *\r\n             * ```\r\n             * {\r\n             *     title: 'Images',\r\n             *     extensions: 'gif,jpg,jpeg,bmp,png',\r\n             *     mimeTypes: 'image/*'\r\n             * }\r\n             * ```\r\n             */\r\n            accept: null/*{\r\n                title: 'Images',\r\n                extensions: 'gif,jpg,jpeg,bmp,png',\r\n                mimeTypes: 'image/*'\r\n            }*/\r\n        });\r\n    \r\n        return Uploader.register({\r\n            'add-btn': 'addButton',\r\n            refresh: 'refresh',\r\n            disable: 'disable',\r\n            enable: 'enable'\r\n        }, {\r\n    \r\n            init: function( opts ) {\r\n                this.pickers = [];\r\n                return opts.pick && this.addButton( opts.pick );\r\n            },\r\n    \r\n            refresh: function() {\r\n                $.each( this.pickers, function() {\r\n                    this.refresh();\r\n                });\r\n            },\r\n    \r\n            /**\r\n             * @method addButton\r\n             * @for Uploader\r\n             * @grammar addButton( pick ) => Promise\r\n             * @description\r\n             * 添加文件选择按钮，如果一个按钮不够，需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。\r\n             * @example\r\n             * uploader.addButton({\r\n             *     id: '#btnContainer',\r\n             *     innerHTML: '选择文件'\r\n             * });\r\n             */\r\n            addButton: function( pick ) {\r\n                var me = this,\r\n                    opts = me.options,\r\n                    accept = opts.accept,\r\n                    options, picker, deferred;\r\n    \r\n                if ( !pick ) {\r\n                    return;\r\n                }\r\n    \r\n                deferred = Base.Deferred();\r\n                $.isPlainObject( pick ) || (pick = {\r\n                    id: pick\r\n                });\r\n    \r\n                options = $.extend({}, pick, {\r\n                    accept: $.isPlainObject( accept ) ? [ accept ] : accept,\r\n                    swf: opts.swf,\r\n                    runtimeOrder: opts.runtimeOrder\r\n                });\r\n    \r\n                picker = new FilePicker( options );\r\n    \r\n                picker.once( 'ready', deferred.resolve );\r\n                picker.on( 'select', function( files ) {\r\n                    me.owner.request( 'add-file', [ files ]);\r\n                });\r\n                picker.init();\r\n    \r\n                this.pickers.push( picker );\r\n    \r\n                return deferred.promise();\r\n            },\r\n    \r\n            disable: function() {\r\n                $.each( this.pickers, function() {\r\n                    this.disable();\r\n                });\r\n            },\r\n    \r\n            enable: function() {\r\n                $.each( this.pickers, function() {\r\n                    this.enable();\r\n                });\r\n            }\r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview Image\r\n     */\r\n    define('lib/image',[\r\n        'base',\r\n        'runtime/client',\r\n        'lib/blob'\r\n    ], function( Base, RuntimeClient, Blob ) {\r\n        var $ = Base.$;\r\n    \r\n        // 构造器。\r\n        function Image( opts ) {\r\n            this.options = $.extend({}, Image.options, opts );\r\n            RuntimeClient.call( this, 'Image' );\r\n    \r\n            this.on( 'load', function() {\r\n                this._info = this.exec('info');\r\n                this._meta = this.exec('meta');\r\n            });\r\n        }\r\n    \r\n        // 默认选项。\r\n        Image.options = {\r\n    \r\n            // 默认的图片处理质量\r\n            quality: 90,\r\n    \r\n            // 是否裁剪\r\n            crop: false,\r\n    \r\n            // 是否保留头部信息\r\n            preserveHeaders: true,\r\n    \r\n            // 是否允许放大。\r\n            allowMagnify: true\r\n        };\r\n    \r\n        // 继承RuntimeClient.\r\n        Base.inherits( RuntimeClient, {\r\n            constructor: Image,\r\n    \r\n            info: function( val ) {\r\n    \r\n                // setter\r\n                if ( val ) {\r\n                    this._info = val;\r\n                    return this;\r\n                }\r\n    \r\n                // getter\r\n                return this._info;\r\n            },\r\n    \r\n            meta: function( val ) {\r\n    \r\n                // setter\r\n                if ( val ) {\r\n                    this._meta = val;\r\n                    return this;\r\n                }\r\n    \r\n                // getter\r\n                return this._meta;\r\n            },\r\n    \r\n            loadFromBlob: function( blob ) {\r\n                var me = this,\r\n                    ruid = blob.getRuid();\r\n    \r\n                this.connectRuntime( ruid, function() {\r\n                    me.exec( 'init', me.options );\r\n                    me.exec( 'loadFromBlob', blob );\r\n                });\r\n            },\r\n    \r\n            resize: function() {\r\n                var args = Base.slice( arguments );\r\n                return this.exec.apply( this, [ 'resize' ].concat( args ) );\r\n            },\r\n    \r\n            getAsDataUrl: function( type ) {\r\n                return this.exec( 'getAsDataUrl', type );\r\n            },\r\n    \r\n            getAsBlob: function( type ) {\r\n                var blob = this.exec( 'getAsBlob', type );\r\n    \r\n                return new Blob( this.getRuid(), blob );\r\n            }\r\n        });\r\n    \r\n        return Image;\r\n    });\r\n    /**\r\n     * @fileOverview 图片操作, 负责预览图片和上传前压缩图片\r\n     */\r\n    define('widgets/image',[\r\n        'base',\r\n        'uploader',\r\n        'lib/image',\r\n        'widgets/widget'\r\n    ], function( Base, Uploader, Image ) {\r\n    \r\n        var $ = Base.$,\r\n            throttle;\r\n    \r\n        // 根据要处理的文件大小来节流，一次不能处理太多，会卡。\r\n        throttle = (function( max ) {\r\n            var occupied = 0,\r\n                waiting = [],\r\n                tick = function() {\r\n                    var item;\r\n    \r\n                    while ( waiting.length && occupied < max ) {\r\n                        item = waiting.shift();\r\n                        occupied += item[ 0 ];\r\n                        item[ 1 ]();\r\n                    }\r\n                };\r\n    \r\n            return function( emiter, size, cb ) {\r\n                waiting.push([ size, cb ]);\r\n                emiter.once( 'destroy', function() {\r\n                    occupied -= size;\r\n                    setTimeout( tick, 1 );\r\n                });\r\n                setTimeout( tick, 1 );\r\n            };\r\n        })( 5 * 1024 * 1024 );\r\n    \r\n        $.extend( Uploader.options, {\r\n    \r\n            /**\r\n             * @property {Object} [thumb]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 配置生成缩略图的选项。\r\n             *\r\n             * 默认为：\r\n             *\r\n             * ```javascript\r\n             * {\r\n             *     width: 110,\r\n             *     height: 110,\r\n             *\r\n             *     // 图片质量，只有type为`image/jpeg`的时候才有效。\r\n             *     quality: 70,\r\n             *\r\n             *     // 是否允许放大，如果想要生成小图的时候不失真，此选项应该设置为false.\r\n             *     allowMagnify: true,\r\n             *\r\n             *     // 是否允许裁剪。\r\n             *     crop: true,\r\n             *\r\n             *     // 是否保留头部meta信息。\r\n             *     preserveHeaders: false,\r\n             *\r\n             *     // 为空的话则保留原有图片格式。\r\n             *     // 否则强制转换成指定的类型。\r\n             *     type: 'image/jpeg'\r\n             * }\r\n             * ```\r\n             */\r\n            thumb: {\r\n                width: 110,\r\n                height: 110,\r\n                quality: 70,\r\n                allowMagnify: true,\r\n                crop: true,\r\n                preserveHeaders: false,\r\n    \r\n                // 为空的话则保留原有图片格式。\r\n                // 否则强制转换成指定的类型。\r\n                // IE 8下面 base64 大小不能超过 32K 否则预览失败，而非 jpeg 编码的图片很可\r\n                // 能会超过 32k, 所以这里设置成预览的时候都是 image/jpeg\r\n                type: 'image/jpeg'\r\n            },\r\n    \r\n            /**\r\n             * @property {Object} [compress]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 配置压缩的图片的选项。如果此选项为`false`, 则图片在上传前不进行压缩。\r\n             *\r\n             * 默认为：\r\n             *\r\n             * ```javascript\r\n             * {\r\n             *     width: 1600,\r\n             *     height: 1600,\r\n             *\r\n             *     // 图片质量，只有type为`image/jpeg`的时候才有效。\r\n             *     quality: 90,\r\n             *\r\n             *     // 是否允许放大，如果想要生成小图的时候不失真，此选项应该设置为false.\r\n             *     allowMagnify: false,\r\n             *\r\n             *     // 是否允许裁剪。\r\n             *     crop: false,\r\n             *\r\n             *     // 是否保留头部meta信息。\r\n             *     preserveHeaders: true\r\n             * }\r\n             * ```\r\n             */\r\n            compress: {\r\n                width: 1600,\r\n                height: 1600,\r\n                quality: 90,\r\n                allowMagnify: false,\r\n                crop: false,\r\n                preserveHeaders: true\r\n            }\r\n        });\r\n    \r\n        return Uploader.register({\r\n            'make-thumb': 'makeThumb',\r\n            'before-send-file': 'compressImage'\r\n        }, {\r\n    \r\n    \r\n            /**\r\n             * 生成缩略图，此过程为异步，所以需要传入`callback`。\r\n             * 通常情况在图片加入队里后调用此方法来生成预览图以增强交互效果。\r\n             *\r\n             * `callback`中可以接收到两个参数。\r\n             * * 第一个为error，如果生成缩略图有错误，此error将为真。\r\n             * * 第二个为ret, 缩略图的Data URL值。\r\n             *\r\n             * **注意**\r\n             * Date URL在IE6/7中不支持，所以不用调用此方法了，直接显示一张暂不支持预览图片好了。\r\n             *\r\n             *\r\n             * @method makeThumb\r\n             * @grammar makeThumb( file, callback ) => undefined\r\n             * @grammar makeThumb( file, callback, width, height ) => undefined\r\n             * @for Uploader\r\n             * @example\r\n             *\r\n             * uploader.on( 'fileQueued', function( file ) {\r\n             *     var $li = ...;\r\n             *\r\n             *     uploader.makeThumb( file, function( error, ret ) {\r\n             *         if ( error ) {\r\n             *             $li.text('预览错误');\r\n             *         } else {\r\n             *             $li.append('<img alt=\"\" src=\"' + ret + '\" />');\r\n             *         }\r\n             *     });\r\n             *\r\n             * });\r\n             */\r\n            makeThumb: function( file, cb, width, height ) {\r\n                var opts, image;\r\n    \r\n                file = this.request( 'get-file', file );\r\n    \r\n                // 只预览图片格式。\r\n                if ( !file.type.match( /^image/ ) ) {\r\n                    cb( true );\r\n                    return;\r\n                }\r\n    \r\n                opts = $.extend({}, this.options.thumb );\r\n    \r\n                // 如果传入的是object.\r\n                if ( $.isPlainObject( width ) ) {\r\n                    opts = $.extend( opts, width );\r\n                    width = null;\r\n                }\r\n    \r\n                width = width || opts.width;\r\n                height = height || opts.height;\r\n    \r\n                image = new Image( opts );\r\n    \r\n                image.once( 'load', function() {\r\n                    file._info = file._info || image.info();\r\n                    file._meta = file._meta || image.meta();\r\n                    image.resize( width, height );\r\n                });\r\n    \r\n                image.once( 'complete', function() {\r\n                    cb( false, image.getAsDataUrl( opts.type ) );\r\n                    image.destroy();\r\n                });\r\n    \r\n                image.once( 'error', function() {\r\n                    cb( true );\r\n                    image.destroy();\r\n                });\r\n    \r\n                throttle( image, file.source.size, function() {\r\n                    file._info && image.info( file._info );\r\n                    file._meta && image.meta( file._meta );\r\n                    image.loadFromBlob( file.source );\r\n                });\r\n            },\r\n    \r\n            compressImage: function( file ) {\r\n                var opts = this.options.compress || this.options.resize,\r\n                    compressSize = opts && opts.compressSize || 300 * 1024,\r\n                    image, deferred;\r\n    \r\n                file = this.request( 'get-file', file );\r\n    \r\n                // 只预览图片格式。\r\n                if ( !opts || !~'image/jpeg,image/jpg'.indexOf( file.type ) ||\r\n                        file.size < compressSize ||\r\n                        file._compressed ) {\r\n                    return;\r\n                }\r\n    \r\n                opts = $.extend({}, opts );\r\n                deferred = Base.Deferred();\r\n    \r\n                image = new Image( opts );\r\n    \r\n                deferred.always(function() {\r\n                    image.destroy();\r\n                    image = null;\r\n                });\r\n                image.once( 'error', deferred.reject );\r\n                image.once( 'load', function() {\r\n                    file._info = file._info || image.info();\r\n                    file._meta = file._meta || image.meta();\r\n                    image.resize( opts.width, opts.height );\r\n                });\r\n    \r\n                image.once( 'complete', function() {\r\n                    var blob, size;\r\n    \r\n                    // 移动端 UC / qq 浏览器的无图模式下\r\n                    // ctx.getImageData 处理大图的时候会报 Exception\r\n                    // INDEX_SIZE_ERR: DOM Exception 1\r\n                    try {\r\n                        blob = image.getAsBlob( opts.type );\r\n    \r\n                        size = file.size;\r\n    \r\n                        // 如果压缩后，比原来还大则不用压缩后的。\r\n                        if ( blob.size < size ) {\r\n                            // file.source.destroy && file.source.destroy();\r\n                            file.source = blob;\r\n                            file.size = blob.size;\r\n    \r\n                            file.trigger( 'resize', blob.size, size );\r\n                        }\r\n    \r\n                        // 标记，避免重复压缩。\r\n                        file._compressed = true;\r\n                        deferred.resolve();\r\n                    } catch ( e ) {\r\n                        // 出错了直接继续，让其上传原始图片\r\n                        deferred.resolve();\r\n                    }\r\n                });\r\n    \r\n                file._info && image.info( file._info );\r\n                file._meta && image.meta( file._meta );\r\n    \r\n                image.loadFromBlob( file.source );\r\n                return deferred.promise();\r\n            }\r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview 文件属性封装\r\n     */\r\n    define('file',[\r\n        'base',\r\n        'mediator'\r\n    ], function( Base, Mediator ) {\r\n    \r\n        var $ = Base.$,\r\n            idPrefix = 'WU_FILE_',\r\n            idSuffix = 0,\r\n            rExt = /\\.([^.]+)$/,\r\n            statusMap = {};\r\n    \r\n        function gid() {\r\n            return idPrefix + idSuffix++;\r\n        }\r\n    \r\n        /**\r\n         * 文件类\r\n         * @class File\r\n         * @constructor 构造函数\r\n         * @grammar new File( source ) => File\r\n         * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。\r\n         */\r\n        function WUFile( source ) {\r\n    \r\n            /**\r\n             * 文件名，包括扩展名（后缀）\r\n             * @property name\r\n             * @type {string}\r\n             */\r\n            this.name = source.name || 'Untitled';\r\n    \r\n            /**\r\n             * 文件体积（字节）\r\n             * @property size\r\n             * @type {uint}\r\n             * @default 0\r\n             */\r\n            this.size = source.size || 0;\r\n    \r\n            /**\r\n             * 文件MIMETYPE类型，与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny)\r\n             * @property type\r\n             * @type {string}\r\n             * @default 'application'\r\n             */\r\n            this.type = source.type || 'application';\r\n    \r\n            /**\r\n             * 文件最后修改日期\r\n             * @property lastModifiedDate\r\n             * @type {int}\r\n             * @default 当前时间戳\r\n             */\r\n            this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1);\r\n    \r\n            /**\r\n             * 文件ID，每个对象具有唯一ID，与文件名无关\r\n             * @property id\r\n             * @type {string}\r\n             */\r\n            this.id = gid();\r\n    \r\n            /**\r\n             * 文件扩展名，通过文件名获取，例如test.png的扩展名为png\r\n             * @property ext\r\n             * @type {string}\r\n             */\r\n            this.ext = rExt.exec( this.name ) ? RegExp.$1 : '';\r\n    \r\n    \r\n            /**\r\n             * 状态文字说明。在不同的status语境下有不同的用途。\r\n             * @property statusText\r\n             * @type {string}\r\n             */\r\n            this.statusText = '';\r\n    \r\n            // 存储文件状态，防止通过属性直接修改\r\n            statusMap[ this.id ] = WUFile.Status.INITED;\r\n    \r\n            this.source = source;\r\n            this.loaded = 0;\r\n    \r\n            this.on( 'error', function( msg ) {\r\n                this.setStatus( WUFile.Status.ERROR, msg );\r\n            });\r\n        }\r\n    \r\n        $.extend( WUFile.prototype, {\r\n    \r\n            /**\r\n             * 设置状态，状态变化时会触发`change`事件。\r\n             * @method setStatus\r\n             * @grammar setStatus( status[, statusText] );\r\n             * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status)\r\n             * @param {String} [statusText=''] 状态说明，常在error时使用，用http, abort,server等来标记是由于什么原因导致文件错误。\r\n             */\r\n            setStatus: function( status, text ) {\r\n    \r\n                var prevStatus = statusMap[ this.id ];\r\n    \r\n                typeof text !== 'undefined' && (this.statusText = text);\r\n    \r\n                if ( status !== prevStatus ) {\r\n                    statusMap[ this.id ] = status;\r\n                    /**\r\n                     * 文件状态变化\r\n                     * @event statuschange\r\n                     */\r\n                    this.trigger( 'statuschange', status, prevStatus );\r\n                }\r\n    \r\n            },\r\n    \r\n            /**\r\n             * 获取文件状态\r\n             * @return {File.Status}\r\n             * @example\r\n                     文件状态具体包括以下几种类型：\r\n                     {\r\n                         // 初始化\r\n                        INITED:     0,\r\n                        // 已入队列\r\n                        QUEUED:     1,\r\n                        // 正在上传\r\n                        PROGRESS:     2,\r\n                        // 上传出错\r\n                        ERROR:         3,\r\n                        // 上传成功\r\n                        COMPLETE:     4,\r\n                        // 上传取消\r\n                        CANCELLED:     5\r\n                    }\r\n             */\r\n            getStatus: function() {\r\n                return statusMap[ this.id ];\r\n            },\r\n    \r\n            /**\r\n             * 获取文件原始信息。\r\n             * @return {*}\r\n             */\r\n            getSource: function() {\r\n                return this.source;\r\n            },\r\n    \r\n            destory: function() {\r\n                delete statusMap[ this.id ];\r\n            }\r\n        });\r\n    \r\n        Mediator.installTo( WUFile.prototype );\r\n    \r\n        /**\r\n         * 文件状态值，具体包括以下几种类型：\r\n         * * `inited` 初始状态\r\n         * * `queued` 已经进入队列, 等待上传\r\n         * * `progress` 上传中\r\n         * * `complete` 上传完成。\r\n         * * `error` 上传出错，可重试\r\n         * * `interrupt` 上传中断，可续传。\r\n         * * `invalid` 文件不合格，不能重试上传。会自动从队列中移除。\r\n         * * `cancelled` 文件被移除。\r\n         * @property {Object} Status\r\n         * @namespace File\r\n         * @class File\r\n         * @static\r\n         */\r\n        WUFile.Status = {\r\n            INITED:     'inited',    // 初始状态\r\n            QUEUED:     'queued',    // 已经进入队列, 等待上传\r\n            PROGRESS:   'progress',    // 上传中\r\n            ERROR:      'error',    // 上传出错，可重试\r\n            COMPLETE:   'complete',    // 上传完成。\r\n            CANCELLED:  'cancelled',    // 上传取消。\r\n            INTERRUPT:  'interrupt',    // 上传中断，可续传。\r\n            INVALID:    'invalid'    // 文件不合格，不能重试上传。\r\n        };\r\n    \r\n        return WUFile;\r\n    });\r\n    \r\n    /**\r\n     * @fileOverview 文件队列\r\n     */\r\n    define('queue',[\r\n        'base',\r\n        'mediator',\r\n        'file'\r\n    ], function( Base, Mediator, WUFile ) {\r\n    \r\n        var $ = Base.$,\r\n            STATUS = WUFile.Status;\r\n    \r\n        /**\r\n         * 文件队列, 用来存储各个状态中的文件。\r\n         * @class Queue\r\n         * @extends Mediator\r\n         */\r\n        function Queue() {\r\n    \r\n            /**\r\n             * 统计文件数。\r\n             * * `numOfQueue` 队列中的文件数。\r\n             * * `numOfSuccess` 上传成功的文件数\r\n             * * `numOfCancel` 被移除的文件数\r\n             * * `numOfProgress` 正在上传中的文件数\r\n             * * `numOfUploadFailed` 上传错误的文件数。\r\n             * * `numOfInvalid` 无效的文件数。\r\n             * @property {Object} stats\r\n             */\r\n            this.stats = {\r\n                numOfQueue: 0,\r\n                numOfSuccess: 0,\r\n                numOfCancel: 0,\r\n                numOfProgress: 0,\r\n                numOfUploadFailed: 0,\r\n                numOfInvalid: 0\r\n            };\r\n    \r\n            // 上传队列，仅包括等待上传的文件\r\n            this._queue = [];\r\n    \r\n            // 存储所有文件\r\n            this._map = {};\r\n        }\r\n    \r\n        $.extend( Queue.prototype, {\r\n    \r\n            /**\r\n             * 将新文件加入对队列尾部\r\n             *\r\n             * @method append\r\n             * @param  {File} file   文件对象\r\n             */\r\n            append: function( file ) {\r\n                this._queue.push( file );\r\n                this._fileAdded( file );\r\n                return this;\r\n            },\r\n    \r\n            /**\r\n             * 将新文件加入对队列头部\r\n             *\r\n             * @method prepend\r\n             * @param  {File} file   文件对象\r\n             */\r\n            prepend: function( file ) {\r\n                this._queue.unshift( file );\r\n                this._fileAdded( file );\r\n                return this;\r\n            },\r\n    \r\n            /**\r\n             * 获取文件对象\r\n             *\r\n             * @method getFile\r\n             * @param  {String} fileId   文件ID\r\n             * @return {File}\r\n             */\r\n            getFile: function( fileId ) {\r\n                if ( typeof fileId !== 'string' ) {\r\n                    return fileId;\r\n                }\r\n                return this._map[ fileId ];\r\n            },\r\n    \r\n            /**\r\n             * 从队列中取出一个指定状态的文件。\r\n             * @grammar fetch( status ) => File\r\n             * @method fetch\r\n             * @param {String} status [文件状态值](#WebUploader:File:File.Status)\r\n             * @return {File} [File](#WebUploader:File)\r\n             */\r\n            fetch: function( status ) {\r\n                var len = this._queue.length,\r\n                    i, file;\r\n    \r\n                status = status || STATUS.QUEUED;\r\n    \r\n                for ( i = 0; i < len; i++ ) {\r\n                    file = this._queue[ i ];\r\n    \r\n                    if ( status === file.getStatus() ) {\r\n                        return file;\r\n                    }\r\n                }\r\n    \r\n                return null;\r\n            },\r\n    \r\n            /**\r\n             * 对队列进行排序，能够控制文件上传顺序。\r\n             * @grammar sort( fn ) => undefined\r\n             * @method sort\r\n             * @param {Function} fn 排序方法\r\n             */\r\n            sort: function( fn ) {\r\n                if ( typeof fn === 'function' ) {\r\n                    this._queue.sort( fn );\r\n                }\r\n            },\r\n    \r\n            /**\r\n             * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。\r\n             * @grammar getFiles( [status1[, status2 ...]] ) => Array\r\n             * @method getFiles\r\n             * @param {String} [status] [文件状态值](#WebUploader:File:File.Status)\r\n             */\r\n            getFiles: function() {\r\n                var sts = [].slice.call( arguments, 0 ),\r\n                    ret = [],\r\n                    i = 0,\r\n                    len = this._queue.length,\r\n                    file;\r\n    \r\n                for ( ; i < len; i++ ) {\r\n                    file = this._queue[ i ];\r\n    \r\n                    if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) {\r\n                        continue;\r\n                    }\r\n    \r\n                    ret.push( file );\r\n                }\r\n    \r\n                return ret;\r\n            },\r\n    \r\n            _fileAdded: function( file ) {\r\n                var me = this,\r\n                    existing = this._map[ file.id ];\r\n    \r\n                if ( !existing ) {\r\n                    this._map[ file.id ] = file;\r\n    \r\n                    file.on( 'statuschange', function( cur, pre ) {\r\n                        me._onFileStatusChange( cur, pre );\r\n                    });\r\n                }\r\n    \r\n                file.setStatus( STATUS.QUEUED );\r\n            },\r\n    \r\n            _onFileStatusChange: function( curStatus, preStatus ) {\r\n                var stats = this.stats;\r\n    \r\n                switch ( preStatus ) {\r\n                    case STATUS.PROGRESS:\r\n                        stats.numOfProgress--;\r\n                        break;\r\n    \r\n                    case STATUS.QUEUED:\r\n                        stats.numOfQueue --;\r\n                        break;\r\n    \r\n                    case STATUS.ERROR:\r\n                        stats.numOfUploadFailed--;\r\n                        break;\r\n    \r\n                    case STATUS.INVALID:\r\n                        stats.numOfInvalid--;\r\n                        break;\r\n                }\r\n    \r\n                switch ( curStatus ) {\r\n                    case STATUS.QUEUED:\r\n                        stats.numOfQueue++;\r\n                        break;\r\n    \r\n                    case STATUS.PROGRESS:\r\n                        stats.numOfProgress++;\r\n                        break;\r\n    \r\n                    case STATUS.ERROR:\r\n                        stats.numOfUploadFailed++;\r\n                        break;\r\n    \r\n                    case STATUS.COMPLETE:\r\n                        stats.numOfSuccess++;\r\n                        break;\r\n    \r\n                    case STATUS.CANCELLED:\r\n                        stats.numOfCancel++;\r\n                        break;\r\n    \r\n                    case STATUS.INVALID:\r\n                        stats.numOfInvalid++;\r\n                        break;\r\n                }\r\n            }\r\n    \r\n        });\r\n    \r\n        Mediator.installTo( Queue.prototype );\r\n    \r\n        return Queue;\r\n    });\r\n    /**\r\n     * @fileOverview 队列\r\n     */\r\n    define('widgets/queue',[\r\n        'base',\r\n        'uploader',\r\n        'queue',\r\n        'file',\r\n        'lib/file',\r\n        'runtime/client',\r\n        'widgets/widget'\r\n    ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) {\r\n    \r\n        var $ = Base.$,\r\n            rExt = /\\.\\w+$/,\r\n            Status = WUFile.Status;\r\n    \r\n        return Uploader.register({\r\n            'sort-files': 'sortFiles',\r\n            'add-file': 'addFiles',\r\n            'get-file': 'getFile',\r\n            'fetch-file': 'fetchFile',\r\n            'get-stats': 'getStats',\r\n            'get-files': 'getFiles',\r\n            'remove-file': 'removeFile',\r\n            'retry': 'retry',\r\n            'reset': 'reset',\r\n            'accept-file': 'acceptFile'\r\n        }, {\r\n    \r\n            init: function( opts ) {\r\n                var me = this,\r\n                    deferred, len, i, item, arr, accept, runtime;\r\n    \r\n                if ( $.isPlainObject( opts.accept ) ) {\r\n                    opts.accept = [ opts.accept ];\r\n                }\r\n    \r\n                // accept中的中生成匹配正则。\r\n                if ( opts.accept ) {\r\n                    arr = [];\r\n    \r\n                    for ( i = 0, len = opts.accept.length; i < len; i++ ) {\r\n                        item = opts.accept[ i ].extensions;\r\n                        item && arr.push( item );\r\n                    }\r\n    \r\n                    if ( arr.length ) {\r\n                        accept = '\\\\.' + arr.join(',')\r\n                                .replace( /,/g, '$|\\\\.' )\r\n                                .replace( /\\*/g, '.*' ) + '$';\r\n                    }\r\n    \r\n                    me.accept = new RegExp( accept, 'i' );\r\n                }\r\n    \r\n                me.queue = new Queue();\r\n                me.stats = me.queue.stats;\r\n    \r\n                // 如果当前不是html5运行时，那就算了。\r\n                // 不执行后续操作\r\n                if ( this.request('predict-runtime-type') !== 'html5' ) {\r\n                    return;\r\n                }\r\n    \r\n                // 创建一个 html5 运行时的 placeholder\r\n                // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。\r\n                deferred = Base.Deferred();\r\n                runtime = new RuntimeClient('Placeholder');\r\n                runtime.connectRuntime({\r\n                    runtimeOrder: 'html5'\r\n                }, function() {\r\n                    me._ruid = runtime.getRuid();\r\n                    deferred.resolve();\r\n                });\r\n                return deferred.promise();\r\n            },\r\n    \r\n    \r\n            // 为了支持外部直接添加一个原生File对象。\r\n            _wrapFile: function( file ) {\r\n                if ( !(file instanceof WUFile) ) {\r\n    \r\n                    if ( !(file instanceof File) ) {\r\n                        if ( !this._ruid ) {\r\n                            throw new Error('Can\\'t add external files.');\r\n                        }\r\n                        file = new File( this._ruid, file );\r\n                    }\r\n    \r\n                    file = new WUFile( file );\r\n                }\r\n    \r\n                return file;\r\n            },\r\n    \r\n            // 判断文件是否可以被加入队列\r\n            acceptFile: function( file ) {\r\n                var invalid = !file || file.size < 6 || this.accept &&\r\n    \r\n                        // 如果名字中有后缀，才做后缀白名单处理。\r\n                        rExt.exec( file.name ) && !this.accept.test( file.name );\r\n    \r\n                return !invalid;\r\n            },\r\n    \r\n    \r\n            /**\r\n             * @event beforeFileQueued\r\n             * @param {File} file File对象\r\n             * @description 当文件被加入队列之前触发，此事件的handler返回值为`false`，则此文件不会被添加进入队列。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * @event fileQueued\r\n             * @param {File} file File对象\r\n             * @description 当文件被加入队列以后触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            _addFile: function( file ) {\r\n                var me = this;\r\n    \r\n                file = me._wrapFile( file );\r\n    \r\n                // 不过类型判断允许不允许，先派送 `beforeFileQueued`\r\n                if ( !me.owner.trigger( 'beforeFileQueued', file ) ) {\r\n                    return;\r\n                }\r\n    \r\n                // 类型不匹配，则派送错误事件，并返回。\r\n                if ( !me.acceptFile( file ) ) {\r\n                    me.owner.trigger( 'error', 'Q_TYPE_DENIED', file );\r\n                    return;\r\n                }\r\n    \r\n                me.queue.append( file );\r\n                me.owner.trigger( 'fileQueued', file );\r\n                return file;\r\n            },\r\n    \r\n            getFile: function( fileId ) {\r\n                return this.queue.getFile( fileId );\r\n            },\r\n    \r\n            /**\r\n             * @event filesQueued\r\n             * @param {File} files 数组，内容为原始File(lib/File）对象。\r\n             * @description 当一批文件添加进队列以后触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * @method addFiles\r\n             * @grammar addFiles( file ) => undefined\r\n             * @grammar addFiles( [file1, file2 ...] ) => undefined\r\n             * @param {Array of File or File} [files] Files 对象 数组\r\n             * @description 添加文件到队列\r\n             * @for  Uploader\r\n             */\r\n            addFiles: function( files ) {\r\n                var me = this;\r\n    \r\n                if ( !files.length ) {\r\n                    files = [ files ];\r\n                }\r\n    \r\n                files = $.map( files, function( file ) {\r\n                    return me._addFile( file );\r\n                });\r\n    \r\n                me.owner.trigger( 'filesQueued', files );\r\n    \r\n                if ( me.options.auto ) {\r\n                    me.request('start-upload');\r\n                }\r\n            },\r\n    \r\n            getStats: function() {\r\n                return this.stats;\r\n            },\r\n    \r\n            /**\r\n             * @event fileDequeued\r\n             * @param {File} file File对象\r\n             * @description 当文件被移除队列后触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * @method removeFile\r\n             * @grammar removeFile( file ) => undefined\r\n             * @grammar removeFile( id ) => undefined\r\n             * @param {File|id} file File对象或这File对象的id\r\n             * @description 移除某一文件。\r\n             * @for  Uploader\r\n             * @example\r\n             *\r\n             * $li.on('click', '.remove-this', function() {\r\n             *     uploader.removeFile( file );\r\n             * })\r\n             */\r\n            removeFile: function( file ) {\r\n                var me = this;\r\n    \r\n                file = file.id ? file : me.queue.getFile( file );\r\n    \r\n                file.setStatus( Status.CANCELLED );\r\n                me.owner.trigger( 'fileDequeued', file );\r\n            },\r\n    \r\n            /**\r\n             * @method getFiles\r\n             * @grammar getFiles() => Array\r\n             * @grammar getFiles( status1, status2, status... ) => Array\r\n             * @description 返回指定状态的文件集合，不传参数将返回所有状态的文件。\r\n             * @for  Uploader\r\n             * @example\r\n             * console.log( uploader.getFiles() );    // => all files\r\n             * console.log( uploader.getFiles('error') )    // => all error files.\r\n             */\r\n            getFiles: function() {\r\n                return this.queue.getFiles.apply( this.queue, arguments );\r\n            },\r\n    \r\n            fetchFile: function() {\r\n                return this.queue.fetch.apply( this.queue, arguments );\r\n            },\r\n    \r\n            /**\r\n             * @method retry\r\n             * @grammar retry() => undefined\r\n             * @grammar retry( file ) => undefined\r\n             * @description 重试上传，重试指定文件，或者从出错的文件开始重新上传。\r\n             * @for  Uploader\r\n             * @example\r\n             * function retry() {\r\n             *     uploader.retry();\r\n             * }\r\n             */\r\n            retry: function( file, noForceStart ) {\r\n                var me = this,\r\n                    files, i, len;\r\n    \r\n                if ( file ) {\r\n                    file = file.id ? file : me.queue.getFile( file );\r\n                    file.setStatus( Status.QUEUED );\r\n                    noForceStart || me.request('start-upload');\r\n                    return;\r\n                }\r\n    \r\n                files = me.queue.getFiles( Status.ERROR );\r\n                i = 0;\r\n                len = files.length;\r\n    \r\n                for ( ; i < len; i++ ) {\r\n                    file = files[ i ];\r\n                    file.setStatus( Status.QUEUED );\r\n                }\r\n    \r\n                me.request('start-upload');\r\n            },\r\n    \r\n            /**\r\n             * @method sort\r\n             * @grammar sort( fn ) => undefined\r\n             * @description 排序队列中的文件，在上传之前调整可以控制上传顺序。\r\n             * @for  Uploader\r\n             */\r\n            sortFiles: function() {\r\n                return this.queue.sort.apply( this.queue, arguments );\r\n            },\r\n    \r\n            /**\r\n             * @method reset\r\n             * @grammar reset() => undefined\r\n             * @description 重置uploader。目前只重置了队列。\r\n             * @for  Uploader\r\n             * @example\r\n             * uploader.reset();\r\n             */\r\n            reset: function() {\r\n                this.queue = new Queue();\r\n                this.stats = this.queue.stats;\r\n            }\r\n        });\r\n    \r\n    });\r\n    /**\r\n     * @fileOverview 添加获取Runtime相关信息的方法。\r\n     */\r\n    define('widgets/runtime',[\r\n        'uploader',\r\n        'runtime/runtime',\r\n        'widgets/widget'\r\n    ], function( Uploader, Runtime ) {\r\n    \r\n        Uploader.support = function() {\r\n            return Runtime.hasRuntime.apply( Runtime, arguments );\r\n        };\r\n    \r\n        return Uploader.register({\r\n            'predict-runtime-type': 'predictRuntmeType'\r\n        }, {\r\n    \r\n            init: function() {\r\n                if ( !this.predictRuntmeType() ) {\r\n                    throw Error('Runtime Error');\r\n                }\r\n            },\r\n    \r\n            /**\r\n             * 预测Uploader将采用哪个`Runtime`\r\n             * @grammar predictRuntmeType() => String\r\n             * @method predictRuntmeType\r\n             * @for  Uploader\r\n             */\r\n            predictRuntmeType: function() {\r\n                var orders = this.options.runtimeOrder || Runtime.orders,\r\n                    type = this.type,\r\n                    i, len;\r\n    \r\n                if ( !type ) {\r\n                    orders = orders.split( /\\s*,\\s*/g );\r\n    \r\n                    for ( i = 0, len = orders.length; i < len; i++ ) {\r\n                        if ( Runtime.hasRuntime( orders[ i ] ) ) {\r\n                            this.type = type = orders[ i ];\r\n                            break;\r\n                        }\r\n                    }\r\n                }\r\n    \r\n                return type;\r\n            }\r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview Transport\r\n     */\r\n    define('lib/transport',[\r\n        'base',\r\n        'runtime/client',\r\n        'mediator'\r\n    ], function( Base, RuntimeClient, Mediator ) {\r\n    \r\n        var $ = Base.$;\r\n    \r\n        function Transport( opts ) {\r\n            var me = this;\r\n    \r\n            opts = me.options = $.extend( true, {}, Transport.options, opts || {} );\r\n            RuntimeClient.call( this, 'Transport' );\r\n    \r\n            this._blob = null;\r\n            this._formData = opts.formData || {};\r\n            this._headers = opts.headers || {};\r\n    \r\n            this.on( 'progress', this._timeout );\r\n            this.on( 'load error', function() {\r\n                me.trigger( 'progress', 1 );\r\n                clearTimeout( me._timer );\r\n            });\r\n        }\r\n    \r\n        Transport.options = {\r\n            server: '',\r\n            method: 'POST',\r\n    \r\n            // 跨域时，是否允许携带cookie, 只有html5 runtime才有效\r\n            withCredentials: false,\r\n            fileVal: 'file',\r\n            timeout: 2 * 60 * 1000,    // 2分钟\r\n            formData: {},\r\n            headers: {},\r\n            sendAsBinary: false\r\n        };\r\n    \r\n        $.extend( Transport.prototype, {\r\n    \r\n            // 添加Blob, 只能添加一次，最后一次有效。\r\n            appendBlob: function( key, blob, filename ) {\r\n                var me = this,\r\n                    opts = me.options;\r\n    \r\n                if ( me.getRuid() ) {\r\n                    me.disconnectRuntime();\r\n                }\r\n    \r\n                // 连接到blob归属的同一个runtime.\r\n                me.connectRuntime( blob.ruid, function() {\r\n                    me.exec('init');\r\n                });\r\n    \r\n                me._blob = blob;\r\n                opts.fileVal = key || opts.fileVal;\r\n                opts.filename = filename || opts.filename;\r\n            },\r\n    \r\n            // 添加其他字段\r\n            append: function( key, value ) {\r\n                if ( typeof key === 'object' ) {\r\n                    $.extend( this._formData, key );\r\n                } else {\r\n                    this._formData[ key ] = value;\r\n                }\r\n            },\r\n    \r\n            setRequestHeader: function( key, value ) {\r\n                if ( typeof key === 'object' ) {\r\n                    $.extend( this._headers, key );\r\n                } else {\r\n                    this._headers[ key ] = value;\r\n                }\r\n            },\r\n    \r\n            send: function( method ) {\r\n                this.exec( 'send', method );\r\n                this._timeout();\r\n            },\r\n    \r\n            abort: function() {\r\n                clearTimeout( this._timer );\r\n                return this.exec('abort');\r\n            },\r\n    \r\n            destroy: function() {\r\n                this.trigger('destroy');\r\n                this.off();\r\n                this.exec('destroy');\r\n                this.disconnectRuntime();\r\n            },\r\n    \r\n            getResponse: function() {\r\n                return this.exec('getResponse');\r\n            },\r\n    \r\n            getResponseAsJson: function() {\r\n                return this.exec('getResponseAsJson');\r\n            },\r\n    \r\n            getStatus: function() {\r\n                return this.exec('getStatus');\r\n            },\r\n    \r\n            _timeout: function() {\r\n                var me = this,\r\n                    duration = me.options.timeout;\r\n    \r\n                if ( !duration ) {\r\n                    return;\r\n                }\r\n    \r\n                clearTimeout( me._timer );\r\n                me._timer = setTimeout(function() {\r\n                    me.abort();\r\n                    me.trigger( 'error', 'timeout' );\r\n                }, duration );\r\n            }\r\n    \r\n        });\r\n    \r\n        // 让Transport具备事件功能。\r\n        Mediator.installTo( Transport.prototype );\r\n    \r\n        return Transport;\r\n    });\r\n    /**\r\n     * @fileOverview 负责文件上传相关。\r\n     */\r\n    define('widgets/upload',[\r\n        'base',\r\n        'uploader',\r\n        'file',\r\n        'lib/transport',\r\n        'widgets/widget'\r\n    ], function( Base, Uploader, WUFile, Transport ) {\r\n    \r\n        var $ = Base.$,\r\n            isPromise = Base.isPromise,\r\n            Status = WUFile.Status;\r\n    \r\n        // 添加默认配置项\r\n        $.extend( Uploader.options, {\r\n    \r\n    \r\n            /**\r\n             * @property {Boolean} [prepareNextFile=false]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 是否允许在文件传输时提前把下一个文件准备好。\r\n             * 对于一个文件的准备工作比较耗时，比如图片压缩，md5序列化。\r\n             * 如果能提前在当前文件传输期处理，可以节省总体耗时。\r\n             */\r\n            prepareNextFile: false,\r\n    \r\n            /**\r\n             * @property {Boolean} [chunked=false]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 是否要分片处理大文件上传。\r\n             */\r\n            chunked: false,\r\n    \r\n            /**\r\n             * @property {Boolean} [chunkSize=5242880]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 如果要分片，分多大一片？ 默认大小为5M.\r\n             */\r\n            chunkSize: 5 * 1024 * 1024,\r\n    \r\n            /**\r\n             * @property {Boolean} [chunkRetry=2]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 如果某个分片由于网络问题出错，允许自动重传多少次？\r\n             */\r\n            chunkRetry: 2,\r\n    \r\n            /**\r\n             * @property {Boolean} [threads=3]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 上传并发数。允许同时最大上传进程数。\r\n             */\r\n            threads: 3,\r\n    \r\n    \r\n            /**\r\n             * @property {Object} [formData]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 文件上传请求的参数表，每次发送都会发送此对象中的参数。\r\n             */\r\n            formData: null\r\n    \r\n            /**\r\n             * @property {Object} [fileVal='file']\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 设置文件上传域的name。\r\n             */\r\n    \r\n            /**\r\n             * @property {Object} [method='POST']\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 文件上传方式，`POST`或者`GET`。\r\n             */\r\n    \r\n            /**\r\n             * @property {Object} [sendAsBinary=false]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 是否已二进制的流的方式发送文件，这样整个上传内容`php://input`都为文件内容，\r\n             * 其他参数在$_GET数组中。\r\n             */\r\n        });\r\n    \r\n        // 负责将文件切片。\r\n        function CuteFile( file, chunkSize ) {\r\n            var pending = [],\r\n                blob = file.source,\r\n                total = blob.size,\r\n                chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1,\r\n                start = 0,\r\n                index = 0,\r\n                len;\r\n    \r\n            while ( index < chunks ) {\r\n                len = Math.min( chunkSize, total - start );\r\n    \r\n                pending.push({\r\n                    file: file,\r\n                    start: start,\r\n                    end: chunkSize ? (start + len) : total,\r\n                    total: total,\r\n                    chunks: chunks,\r\n                    chunk: index++\r\n                });\r\n                start += len;\r\n            }\r\n    \r\n            file.blocks = pending.concat();\r\n            file.remaning = pending.length;\r\n    \r\n            return {\r\n                file: file,\r\n    \r\n                has: function() {\r\n                    return !!pending.length;\r\n                },\r\n    \r\n                fetch: function() {\r\n                    return pending.shift();\r\n                }\r\n            };\r\n        }\r\n    \r\n        Uploader.register({\r\n            'start-upload': 'start',\r\n            'stop-upload': 'stop',\r\n            'skip-file': 'skipFile',\r\n            'is-in-progress': 'isInProgress'\r\n        }, {\r\n    \r\n            init: function() {\r\n                var owner = this.owner;\r\n    \r\n                this.runing = false;\r\n    \r\n                // 记录当前正在传的数据，跟threads相关\r\n                this.pool = [];\r\n    \r\n                // 缓存即将上传的文件。\r\n                this.pending = [];\r\n    \r\n                // 跟踪还有多少分片没有完成上传。\r\n                this.remaning = 0;\r\n                this.__tick = Base.bindFn( this._tick, this );\r\n    \r\n                owner.on( 'uploadComplete', function( file ) {\r\n                    // 把其他块取消了。\r\n                    file.blocks && $.each( file.blocks, function( _, v ) {\r\n                        v.transport && (v.transport.abort(), v.transport.destroy());\r\n                        delete v.transport;\r\n                    });\r\n    \r\n                    delete file.blocks;\r\n                    delete file.remaning;\r\n                });\r\n            },\r\n    \r\n            /**\r\n             * @event startUpload\r\n             * @description 当开始上传流程时触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * 开始上传。此方法可以从初始状态调用开始上传流程，也可以从暂停状态调用，继续上传流程。\r\n             * @grammar upload() => undefined\r\n             * @method upload\r\n             * @for  Uploader\r\n             */\r\n            start: function() {\r\n                var me = this;\r\n    \r\n                // 移出invalid的文件\r\n                $.each( me.request( 'get-files', Status.INVALID ), function() {\r\n                    me.request( 'remove-file', this );\r\n                });\r\n    \r\n                if ( me.runing ) {\r\n                    return;\r\n                }\r\n    \r\n                me.runing = true;\r\n    \r\n                // 如果有暂停的，则续传\r\n                $.each( me.pool, function( _, v ) {\r\n                    var file = v.file;\r\n    \r\n                    if ( file.getStatus() === Status.INTERRUPT ) {\r\n                        file.setStatus( Status.PROGRESS );\r\n                        me._trigged = false;\r\n                        v.transport && v.transport.send();\r\n                    }\r\n                });\r\n    \r\n                me._trigged = false;\r\n                me.owner.trigger('startUpload');\r\n                Base.nextTick( me.__tick );\r\n            },\r\n    \r\n            /**\r\n             * @event stopUpload\r\n             * @description 当开始上传流程暂停时触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。\r\n             * @grammar stop() => undefined\r\n             * @grammar stop( true ) => undefined\r\n             * @method stop\r\n             * @for  Uploader\r\n             */\r\n            stop: function( interrupt ) {\r\n                var me = this;\r\n    \r\n                if ( me.runing === false ) {\r\n                    return;\r\n                }\r\n    \r\n                me.runing = false;\r\n    \r\n                interrupt && $.each( me.pool, function( _, v ) {\r\n                    v.transport && v.transport.abort();\r\n                    v.file.setStatus( Status.INTERRUPT );\r\n                });\r\n    \r\n                me.owner.trigger('stopUpload');\r\n            },\r\n    \r\n            /**\r\n             * 判断`Uplaode`r是否正在上传中。\r\n             * @grammar isInProgress() => Boolean\r\n             * @method isInProgress\r\n             * @for  Uploader\r\n             */\r\n            isInProgress: function() {\r\n                return !!this.runing;\r\n            },\r\n    \r\n            getStats: function() {\r\n                return this.request('get-stats');\r\n            },\r\n    \r\n            /**\r\n             * 掉过一个文件上传，直接标记指定文件为已上传状态。\r\n             * @grammar skipFile( file ) => undefined\r\n             * @method skipFile\r\n             * @for  Uploader\r\n             */\r\n            skipFile: function( file, status ) {\r\n                file = this.request( 'get-file', file );\r\n    \r\n                file.setStatus( status || Status.COMPLETE );\r\n                file.skipped = true;\r\n    \r\n                // 如果正在上传。\r\n                file.blocks && $.each( file.blocks, function( _, v ) {\r\n                    var _tr = v.transport;\r\n    \r\n                    if ( _tr ) {\r\n                        _tr.abort();\r\n                        _tr.destroy();\r\n                        delete v.transport;\r\n                    }\r\n                });\r\n    \r\n                this.owner.trigger( 'uploadSkip', file );\r\n            },\r\n    \r\n            /**\r\n             * @event uploadFinished\r\n             * @description 当所有文件上传结束时触发。\r\n             * @for  Uploader\r\n             */\r\n            _tick: function() {\r\n                var me = this,\r\n                    opts = me.options,\r\n                    fn, val;\r\n    \r\n                // 上一个promise还没有结束，则等待完成后再执行。\r\n                if ( me._promise ) {\r\n                    return me._promise.always( me.__tick );\r\n                }\r\n    \r\n                // 还有位置，且还有文件要处理的话。\r\n                if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) {\r\n                    me._trigged = false;\r\n    \r\n                    fn = function( val ) {\r\n                        me._promise = null;\r\n    \r\n                        // 有可能是reject过来的，所以要检测val的类型。\r\n                        val && val.file && me._startSend( val );\r\n                        Base.nextTick( me.__tick );\r\n                    };\r\n    \r\n                    me._promise = isPromise( val ) ? val.always( fn ) : fn( val );\r\n    \r\n                // 没有要上传的了，且没有正在传输的了。\r\n                } else if ( !me.remaning && !me.getStats().numOfQueue ) {\r\n                    me.runing = false;\r\n    \r\n                    me._trigged || Base.nextTick(function() {\r\n                        me.owner.trigger('uploadFinished');\r\n                    });\r\n                    me._trigged = true;\r\n                }\r\n            },\r\n    \r\n            _nextBlock: function() {\r\n                var me = this,\r\n                    act = me._act,\r\n                    opts = me.options,\r\n                    next, done;\r\n    \r\n                // 如果当前文件还有没有需要传输的，则直接返回剩下的。\r\n                if ( act && act.has() &&\r\n                        act.file.getStatus() === Status.PROGRESS ) {\r\n    \r\n                    // 是否提前准备下一个文件\r\n                    if ( opts.prepareNextFile && !me.pending.length ) {\r\n                        me._prepareNextFile();\r\n                    }\r\n    \r\n                    return act.fetch();\r\n    \r\n                // 否则，如果正在运行，则准备下一个文件，并等待完成后返回下个分片。\r\n                } else if ( me.runing ) {\r\n    \r\n                    // 如果缓存中有，则直接在缓存中取，没有则去queue中取。\r\n                    if ( !me.pending.length && me.getStats().numOfQueue ) {\r\n                        me._prepareNextFile();\r\n                    }\r\n    \r\n                    next = me.pending.shift();\r\n                    done = function( file ) {\r\n                        if ( !file ) {\r\n                            return null;\r\n                        }\r\n    \r\n                        act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 );\r\n                        me._act = act;\r\n                        return act.fetch();\r\n                    };\r\n    \r\n                    // 文件可能还在prepare中，也有可能已经完全准备好了。\r\n                    return isPromise( next ) ?\r\n                            next[ next.pipe ? 'pipe' : 'then']( done ) :\r\n                            done( next );\r\n                }\r\n            },\r\n    \r\n    \r\n            /**\r\n             * @event uploadStart\r\n             * @param {File} file File对象\r\n             * @description 某个文件开始上传前触发，一个文件只会触发一次。\r\n             * @for  Uploader\r\n             */\r\n            _prepareNextFile: function() {\r\n                var me = this,\r\n                    file = me.request('fetch-file'),\r\n                    pending = me.pending,\r\n                    promise;\r\n    \r\n                if ( file ) {\r\n                    promise = me.request( 'before-send-file', file, function() {\r\n    \r\n                        // 有可能文件被skip掉了。文件被skip掉后，状态坑定不是Queued.\r\n                        if ( file.getStatus() === Status.QUEUED ) {\r\n                            me.owner.trigger( 'uploadStart', file );\r\n                            file.setStatus( Status.PROGRESS );\r\n                            return file;\r\n                        }\r\n    \r\n                        return me._finishFile( file );\r\n                    });\r\n    \r\n                    // 如果还在pending中，则替换成文件本身。\r\n                    promise.done(function() {\r\n                        var idx = $.inArray( promise, pending );\r\n    \r\n                        ~idx && pending.splice( idx, 1, file );\r\n                    });\r\n    \r\n                    // befeore-send-file的钩子就有错误发生。\r\n                    promise.fail(function( reason ) {\r\n                        file.setStatus( Status.ERROR, reason );\r\n                        me.owner.trigger( 'uploadError', file, reason );\r\n                        me.owner.trigger( 'uploadComplete', file );\r\n                    });\r\n    \r\n                    pending.push( promise );\r\n                }\r\n            },\r\n    \r\n            // 让出位置了，可以让其他分片开始上传\r\n            _popBlock: function( block ) {\r\n                var idx = $.inArray( block, this.pool );\r\n    \r\n                this.pool.splice( idx, 1 );\r\n                block.file.remaning--;\r\n                this.remaning--;\r\n            },\r\n    \r\n            // 开始上传，可以被掉过。如果promise被reject了，则表示跳过此分片。\r\n            _startSend: function( block ) {\r\n                var me = this,\r\n                    file = block.file,\r\n                    promise;\r\n    \r\n                me.pool.push( block );\r\n                me.remaning++;\r\n    \r\n                // 如果没有分片，则直接使用原始的。\r\n                // 不会丢失content-type信息。\r\n                block.blob = block.chunks === 1 ? file.source :\r\n                        file.source.slice( block.start, block.end );\r\n    \r\n                // hook, 每个分片发送之前可能要做些异步的事情。\r\n                promise = me.request( 'before-send', block, function() {\r\n    \r\n                    // 有可能文件已经上传出错了，所以不需要再传输了。\r\n                    if ( file.getStatus() === Status.PROGRESS ) {\r\n                        me._doSend( block );\r\n                    } else {\r\n                        me._popBlock( block );\r\n                        Base.nextTick( me.__tick );\r\n                    }\r\n                });\r\n    \r\n                // 如果为fail了，则跳过此分片。\r\n                promise.fail(function() {\r\n                    if ( file.remaning === 1 ) {\r\n                        me._finishFile( file ).always(function() {\r\n                            block.percentage = 1;\r\n                            me._popBlock( block );\r\n                            me.owner.trigger( 'uploadComplete', file );\r\n                            Base.nextTick( me.__tick );\r\n                        });\r\n                    } else {\r\n                        block.percentage = 1;\r\n                        me._popBlock( block );\r\n                        Base.nextTick( me.__tick );\r\n                    }\r\n                });\r\n            },\r\n    \r\n    \r\n            /**\r\n             * @event uploadBeforeSend\r\n             * @param {Object} object\r\n             * @param {Object} data 默认的上传参数，可以扩展此对象来控制上传参数。\r\n             * @description 当某个文件的分块在发送前触发，主要用来询问是否要添加附带参数，大文件在开起分片上传的前提下此事件可能会触发多次。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * @event uploadAccept\r\n             * @param {Object} object\r\n             * @param {Object} ret 服务端的返回数据，json格式，如果服务端不是json格式，从ret._raw中取数据，自行解析。\r\n             * @description 当某个文件上传到服务端响应后，会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * @event uploadProgress\r\n             * @param {File} file File对象\r\n             * @param {Number} percentage 上传进度\r\n             * @description 上传过程中触发，携带上传进度。\r\n             * @for  Uploader\r\n             */\r\n    \r\n    \r\n            /**\r\n             * @event uploadError\r\n             * @param {File} file File对象\r\n             * @param {String} reason 出错的code\r\n             * @description 当文件上传出错时触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * @event uploadSuccess\r\n             * @param {File} file File对象\r\n             * @param {Object} response 服务端返回的数据\r\n             * @description 当文件上传成功时触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * @event uploadComplete\r\n             * @param {File} [file] File对象\r\n             * @description 不管成功或者失败，文件上传完成时触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            // 做上传操作。\r\n            _doSend: function( block ) {\r\n                var me = this,\r\n                    owner = me.owner,\r\n                    opts = me.options,\r\n                    file = block.file,\r\n                    tr = new Transport( opts ),\r\n                    data = $.extend({}, opts.formData ),\r\n                    headers = $.extend({}, opts.headers ),\r\n                    requestAccept, ret;\r\n    \r\n                block.transport = tr;\r\n    \r\n                tr.on( 'destroy', function() {\r\n                    delete block.transport;\r\n                    me._popBlock( block );\r\n                    Base.nextTick( me.__tick );\r\n                });\r\n    \r\n                // 广播上传进度。以文件为单位。\r\n                tr.on( 'progress', function( percentage ) {\r\n                    var totalPercent = 0,\r\n                        uploaded = 0;\r\n    \r\n                    // 可能没有abort掉，progress还是执行进来了。\r\n                    // if ( !file.blocks ) {\r\n                    //     return;\r\n                    // }\r\n    \r\n                    totalPercent = block.percentage = percentage;\r\n    \r\n                    if ( block.chunks > 1 ) {    // 计算文件的整体速度。\r\n                        $.each( file.blocks, function( _, v ) {\r\n                            uploaded += (v.percentage || 0) * (v.end - v.start);\r\n                        });\r\n    \r\n                        totalPercent = uploaded / file.size;\r\n                    }\r\n    \r\n                    owner.trigger( 'uploadProgress', file, totalPercent || 0 );\r\n                });\r\n    \r\n                // 用来询问，是否返回的结果是有错误的。\r\n                requestAccept = function( reject ) {\r\n                    var fn;\r\n    \r\n                    ret = tr.getResponseAsJson() || {};\r\n                    ret._raw = tr.getResponse();\r\n                    fn = function( value ) {\r\n                        reject = value;\r\n                    };\r\n    \r\n                    // 服务端响应了，不代表成功了，询问是否响应正确。\r\n                    if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) {\r\n                        reject = reject || 'server';\r\n                    }\r\n    \r\n                    return reject;\r\n                };\r\n    \r\n                // 尝试重试，然后广播文件上传出错。\r\n                tr.on( 'error', function( type, flag ) {\r\n                    block.retried = block.retried || 0;\r\n    \r\n                    // 自动重试\r\n                    if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) &&\r\n                            block.retried < opts.chunkRetry ) {\r\n    \r\n                        block.retried++;\r\n                        tr.send();\r\n    \r\n                    } else {\r\n    \r\n                        // http status 500 ~ 600\r\n                        if ( !flag && type === 'server' ) {\r\n                            type = requestAccept( type );\r\n                        }\r\n    \r\n                        file.setStatus( Status.ERROR, type );\r\n                        owner.trigger( 'uploadError', file, type );\r\n                        owner.trigger( 'uploadComplete', file );\r\n                    }\r\n                });\r\n    \r\n                // 上传成功\r\n                tr.on( 'load', function() {\r\n                    var reason;\r\n    \r\n                    // 如果非预期，转向上传出错。\r\n                    if ( (reason = requestAccept()) ) {\r\n                        tr.trigger( 'error', reason, true );\r\n                        return;\r\n                    }\r\n    \r\n                    // 全部上传完成。\r\n                    if ( file.remaning === 1 ) {\r\n                        me._finishFile( file, ret );\r\n                    } else {\r\n                        tr.destroy();\r\n                    }\r\n                });\r\n    \r\n                // 配置默认的上传字段。\r\n                data = $.extend( data, {\r\n                    id: file.id,\r\n                    name: file.name,\r\n                    type: file.type,\r\n                    lastModifiedDate: file.lastModifiedDate,\r\n                    size: file.size\r\n                });\r\n    \r\n                block.chunks > 1 && $.extend( data, {\r\n                    chunks: block.chunks,\r\n                    chunk: block.chunk\r\n                });\r\n    \r\n                // 在发送之间可以添加字段什么的。。。\r\n                // 如果默认的字段不够使用，可以通过监听此事件来扩展\r\n                owner.trigger( 'uploadBeforeSend', block, data, headers );\r\n    \r\n                // 开始发送。\r\n                tr.appendBlob( opts.fileVal, block.blob, file.name );\r\n                tr.append( data );\r\n                tr.setRequestHeader( headers );\r\n                tr.send();\r\n            },\r\n    \r\n            // 完成上传。\r\n            _finishFile: function( file, ret, hds ) {\r\n                var owner = this.owner;\r\n    \r\n                return owner\r\n                        .request( 'after-send-file', arguments, function() {\r\n                            file.setStatus( Status.COMPLETE );\r\n                            owner.trigger( 'uploadSuccess', file, ret, hds );\r\n                        })\r\n                        .fail(function( reason ) {\r\n    \r\n                            // 如果外部已经标记为invalid什么的，不再改状态。\r\n                            if ( file.getStatus() === Status.PROGRESS ) {\r\n                                file.setStatus( Status.ERROR, reason );\r\n                            }\r\n    \r\n                            owner.trigger( 'uploadError', file, reason );\r\n                        })\r\n                        .always(function() {\r\n                            owner.trigger( 'uploadComplete', file );\r\n                        });\r\n            }\r\n    \r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview Runtime管理器，负责Runtime的选择, 连接\r\n     */\r\n    define('runtime/compbase',[],function() {\r\n    \r\n        function CompBase( owner, runtime ) {\r\n    \r\n            this.owner = owner;\r\n            this.options = owner.options;\r\n    \r\n            this.getRuntime = function() {\r\n                return runtime;\r\n            };\r\n    \r\n            this.getRuid = function() {\r\n                return runtime.uid;\r\n            };\r\n    \r\n            this.trigger = function() {\r\n                return owner.trigger.apply( owner, arguments );\r\n            };\r\n        }\r\n    \r\n        return CompBase;\r\n    });\r\n    /**\r\n     * @fileOverview Html5Runtime\r\n     */\r\n    define('runtime/html5/runtime',[\r\n        'base',\r\n        'runtime/runtime',\r\n        'runtime/compbase'\r\n    ], function( Base, Runtime, CompBase ) {\r\n    \r\n        var type = 'html5',\r\n            components = {};\r\n    \r\n        function Html5Runtime() {\r\n            var pool = {},\r\n                me = this,\r\n                destory = this.destory;\r\n    \r\n            Runtime.apply( me, arguments );\r\n            me.type = type;\r\n    \r\n    \r\n            // 这个方法的调用者，实际上是RuntimeClient\r\n            me.exec = function( comp, fn/*, args...*/) {\r\n                var client = this,\r\n                    uid = client.uid,\r\n                    args = Base.slice( arguments, 2 ),\r\n                    instance;\r\n    \r\n                if ( components[ comp ] ) {\r\n                    instance = pool[ uid ] = pool[ uid ] ||\r\n                            new components[ comp ]( client, me );\r\n    \r\n                    if ( instance[ fn ] ) {\r\n                        return instance[ fn ].apply( instance, args );\r\n                    }\r\n                }\r\n            };\r\n    \r\n            me.destory = function() {\r\n                // @todo 删除池子中的所有实例\r\n                return destory && destory.apply( this, arguments );\r\n            };\r\n        }\r\n    \r\n        Base.inherits( Runtime, {\r\n            constructor: Html5Runtime,\r\n    \r\n            // 不需要连接其他程序，直接执行callback\r\n            init: function() {\r\n                var me = this;\r\n                setTimeout(function() {\r\n                    me.trigger('ready');\r\n                }, 1 );\r\n            }\r\n    \r\n        });\r\n    \r\n        // 注册Components\r\n        Html5Runtime.register = function( name, component ) {\r\n            var klass = components[ name ] = Base.inherits( CompBase, component );\r\n            return klass;\r\n        };\r\n    \r\n        // 注册html5运行时。\r\n        // 只有在支持的前提下注册。\r\n        if ( window.Blob && window.FileReader && window.DataView ) {\r\n            Runtime.addRuntime( type, Html5Runtime );\r\n        }\r\n    \r\n        return Html5Runtime;\r\n    });\r\n    /**\r\n     * @fileOverview Blob Html实现\r\n     */\r\n    define('runtime/html5/blob',[\r\n        'runtime/html5/runtime',\r\n        'lib/blob'\r\n    ], function( Html5Runtime, Blob ) {\r\n    \r\n        return Html5Runtime.register( 'Blob', {\r\n            slice: function( start, end ) {\r\n                var blob = this.owner.source,\r\n                    slice = blob.slice || blob.webkitSlice || blob.mozSlice;\r\n    \r\n                blob = slice.call( blob, start, end );\r\n    \r\n                return new Blob( this.getRuid(), blob );\r\n            }\r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview FilePicker\r\n     */\r\n    define('runtime/html5/filepicker',[\r\n        'base',\r\n        'runtime/html5/runtime'\r\n    ], function( Base, Html5Runtime ) {\r\n    \r\n        var $ = Base.$;\r\n    \r\n        return Html5Runtime.register( 'FilePicker', {\r\n            init: function() {\r\n                var container = this.getRuntime().getContainer(),\r\n                    me = this,\r\n                    owner = me.owner,\r\n                    opts = me.options,\r\n                    lable = $( document.createElement('label') ),\r\n                    input = $( document.createElement('input') ),\r\n                    arr, i, len, mouseHandler;\r\n    \r\n                input.attr( 'type', 'file' );\r\n                input.attr( 'name', opts.name );\r\n                input.addClass('webuploader-element-invisible');\r\n    \r\n                lable.on( 'click', function() {\r\n                    input.trigger('click');\r\n                });\r\n    \r\n                lable.css({\r\n                    opacity: 0,\r\n                    width: '100%',\r\n                    height: '100%',\r\n                    display: 'block',\r\n                    cursor: 'pointer',\r\n                    background: '#ffffff'\r\n                });\r\n    \r\n                if ( opts.multiple ) {\r\n                    input.attr( 'multiple', 'multiple' );\r\n                }\r\n    \r\n                // @todo Firefox不支持单独指定后缀\r\n                if ( opts.accept && opts.accept.length > 0 ) {\r\n                    arr = [];\r\n    \r\n                    for ( i = 0, len = opts.accept.length; i < len; i++ ) {\r\n                        arr.push( opts.accept[ i ].mimeTypes );\r\n                    }\r\n    \r\n                    input.attr( 'accept', arr.join(',') );\r\n                }\r\n    \r\n                container.append( input );\r\n                container.append( lable );\r\n    \r\n                mouseHandler = function( e ) {\r\n                    owner.trigger( e.type );\r\n                };\r\n    \r\n                input.on( 'change', function( e ) {\r\n                    var fn = arguments.callee,\r\n                        clone;\r\n    \r\n                    me.files = e.target.files;\r\n    \r\n                    // reset input\r\n                    clone = this.cloneNode( true );\r\n                    this.parentNode.replaceChild( clone, this );\r\n    \r\n                    input.off();\r\n                    input = $( clone ).on( 'change', fn )\r\n                            .on( 'mouseenter mouseleave', mouseHandler );\r\n    \r\n                    owner.trigger('change');\r\n                });\r\n    \r\n                lable.on( 'mouseenter mouseleave', mouseHandler );\r\n    \r\n            },\r\n    \r\n    \r\n            getFiles: function() {\r\n                return this.files;\r\n            },\r\n    \r\n            destroy: function() {\r\n                // todo\r\n            }\r\n        });\r\n    });\r\n    /**\r\n     * Terms:\r\n     *\r\n     * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer\r\n     * @fileOverview Image控件\r\n     */\r\n    define('runtime/html5/util',[\r\n        'base'\r\n    ], function( Base ) {\r\n    \r\n        var urlAPI = window.createObjectURL && window ||\r\n                window.URL && URL.revokeObjectURL && URL ||\r\n                window.webkitURL,\r\n            createObjectURL = Base.noop,\r\n            revokeObjectURL = createObjectURL;\r\n    \r\n        if ( urlAPI ) {\r\n    \r\n            // 更安全的方式调用，比如android里面就能把context改成其他的对象。\r\n            createObjectURL = function() {\r\n                return urlAPI.createObjectURL.apply( urlAPI, arguments );\r\n            };\r\n    \r\n            revokeObjectURL = function() {\r\n                return urlAPI.revokeObjectURL.apply( urlAPI, arguments );\r\n            };\r\n        }\r\n    \r\n        return {\r\n            createObjectURL: createObjectURL,\r\n            revokeObjectURL: revokeObjectURL,\r\n    \r\n            dataURL2Blob: function( dataURI ) {\r\n                var byteStr, intArray, ab, i, mimetype, parts;\r\n    \r\n                parts = dataURI.split(',');\r\n    \r\n                if ( ~parts[ 0 ].indexOf('base64') ) {\r\n                    byteStr = atob( parts[ 1 ] );\r\n                } else {\r\n                    byteStr = decodeURIComponent( parts[ 1 ] );\r\n                }\r\n    \r\n                ab = new ArrayBuffer( byteStr.length );\r\n                intArray = new Uint8Array( ab );\r\n    \r\n                for ( i = 0; i < byteStr.length; i++ ) {\r\n                    intArray[ i ] = byteStr.charCodeAt( i );\r\n                }\r\n    \r\n                mimetype = parts[ 0 ].split(':')[ 1 ].split(';')[ 0 ];\r\n    \r\n                return this.arrayBufferToBlob( ab, mimetype );\r\n            },\r\n    \r\n            dataURL2ArrayBuffer: function( dataURI ) {\r\n                var byteStr, intArray, i, parts;\r\n    \r\n                parts = dataURI.split(',');\r\n    \r\n                if ( ~parts[ 0 ].indexOf('base64') ) {\r\n                    byteStr = atob( parts[ 1 ] );\r\n                } else {\r\n                    byteStr = decodeURIComponent( parts[ 1 ] );\r\n                }\r\n    \r\n                intArray = new Uint8Array( byteStr.length );\r\n    \r\n                for ( i = 0; i < byteStr.length; i++ ) {\r\n                    intArray[ i ] = byteStr.charCodeAt( i );\r\n                }\r\n    \r\n                return intArray.buffer;\r\n            },\r\n    \r\n            arrayBufferToBlob: function( buffer, type ) {\r\n                var builder = window.BlobBuilder || window.WebKitBlobBuilder,\r\n                    bb;\r\n    \r\n                // android不支持直接new Blob, 只能借助blobbuilder.\r\n                if ( builder ) {\r\n                    bb = new builder();\r\n                    bb.append( buffer );\r\n                    return bb.getBlob( type );\r\n                }\r\n    \r\n                return new Blob([ buffer ], type ? { type: type } : {} );\r\n            },\r\n    \r\n            // 抽出来主要是为了解决android下面canvas.toDataUrl不支持jpeg.\r\n            // 你得到的结果是png.\r\n            canvasToDataUrl: function( canvas, type, quality ) {\r\n                return canvas.toDataURL( type, quality / 100 );\r\n            },\r\n    \r\n            // imagemeat会复写这个方法，如果用户选择加载那个文件了的话。\r\n            parseMeta: function( blob, callback ) {\r\n                callback( false, {});\r\n            },\r\n    \r\n            // imagemeat会复写这个方法，如果用户选择加载那个文件了的话。\r\n            updateImageHead: function( data ) {\r\n                return data;\r\n            }\r\n        };\r\n    });\r\n    /**\r\n     * Terms:\r\n     *\r\n     * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer\r\n     * @fileOverview Image控件\r\n     */\r\n    define('runtime/html5/imagemeta',[\r\n        'runtime/html5/util'\r\n    ], function( Util ) {\r\n    \r\n        var api;\r\n    \r\n        api = {\r\n            parsers: {\r\n                0xffe1: []\r\n            },\r\n    \r\n            maxMetaDataSize: 262144,\r\n    \r\n            parse: function( blob, cb ) {\r\n                var me = this,\r\n                    fr = new FileReader();\r\n    \r\n                fr.onload = function() {\r\n                    cb( false, me._parse( this.result ) );\r\n                    fr = fr.onload = fr.onerror = null;\r\n                };\r\n    \r\n                fr.onerror = function( e ) {\r\n                    cb( e.message );\r\n                    fr = fr.onload = fr.onerror = null;\r\n                };\r\n    \r\n                blob = blob.slice( 0, me.maxMetaDataSize );\r\n                fr.readAsArrayBuffer( blob.getSource() );\r\n            },\r\n    \r\n            _parse: function( buffer, noParse ) {\r\n                if ( buffer.byteLength < 6 ) {\r\n                    return;\r\n                }\r\n    \r\n                var dataview = new DataView( buffer ),\r\n                    offset = 2,\r\n                    maxOffset = dataview.byteLength - 4,\r\n                    headLength = offset,\r\n                    ret = {},\r\n                    markerBytes, markerLength, parsers, i;\r\n    \r\n                if ( dataview.getUint16( 0 ) === 0xffd8 ) {\r\n    \r\n                    while ( offset < maxOffset ) {\r\n                        markerBytes = dataview.getUint16( offset );\r\n    \r\n                        if ( markerBytes >= 0xffe0 && markerBytes <= 0xffef ||\r\n                                markerBytes === 0xfffe ) {\r\n    \r\n                            markerLength = dataview.getUint16( offset + 2 ) + 2;\r\n    \r\n                            if ( offset + markerLength > dataview.byteLength ) {\r\n                                break;\r\n                            }\r\n    \r\n                            parsers = api.parsers[ markerBytes ];\r\n    \r\n                            if ( !noParse && parsers ) {\r\n                                for ( i = 0; i < parsers.length; i += 1 ) {\r\n                                    parsers[ i ].call( api, dataview, offset,\r\n                                            markerLength, ret );\r\n                                }\r\n                            }\r\n    \r\n                            offset += markerLength;\r\n                            headLength = offset;\r\n                        } else {\r\n                            break;\r\n                        }\r\n                    }\r\n    \r\n                    if ( headLength > 6 ) {\r\n                        if ( buffer.slice ) {\r\n                            ret.imageHead = buffer.slice( 2, headLength );\r\n                        } else {\r\n                            // Workaround for IE10, which does not yet\r\n                            // support ArrayBuffer.slice:\r\n                            ret.imageHead = new Uint8Array( buffer )\r\n                                    .subarray( 2, headLength );\r\n                        }\r\n                    }\r\n                }\r\n    \r\n                return ret;\r\n            },\r\n    \r\n            updateImageHead: function( buffer, head ) {\r\n                var data = this._parse( buffer, true ),\r\n                    buf1, buf2, bodyoffset;\r\n    \r\n    \r\n                bodyoffset = 2;\r\n                if ( data.imageHead ) {\r\n                    bodyoffset = 2 + data.imageHead.byteLength;\r\n                }\r\n    \r\n                if ( buffer.slice ) {\r\n                    buf2 = buffer.slice( bodyoffset );\r\n                } else {\r\n                    buf2 = new Uint8Array( buffer ).subarray( bodyoffset );\r\n                }\r\n    \r\n                buf1 = new Uint8Array( head.byteLength + 2 + buf2.byteLength );\r\n    \r\n                buf1[ 0 ] = 0xFF;\r\n                buf1[ 1 ] = 0xD8;\r\n                buf1.set( new Uint8Array( head ), 2 );\r\n                buf1.set( new Uint8Array( buf2 ), head.byteLength + 2 );\r\n    \r\n                return buf1.buffer;\r\n            }\r\n        };\r\n    \r\n        Util.parseMeta = function() {\r\n            return api.parse.apply( api, arguments );\r\n        };\r\n    \r\n        Util.updateImageHead = function() {\r\n            return api.updateImageHead.apply( api, arguments );\r\n        };\r\n    \r\n        return api;\r\n    });\r\n    /**\r\n     * 代码来自于：https://github.com/blueimp/JavaScript-Load-Image\r\n     * 暂时项目中只用了orientation.\r\n     *\r\n     * 去除了 Exif Sub IFD Pointer, GPS Info IFD Pointer, Exif Thumbnail.\r\n     * @fileOverview EXIF解析\r\n     */\r\n    \r\n    // Sample\r\n    // ====================================\r\n    // Make : Apple\r\n    // Model : iPhone 4S\r\n    // Orientation : 1\r\n    // XResolution : 72 [72/1]\r\n    // YResolution : 72 [72/1]\r\n    // ResolutionUnit : 2\r\n    // Software : QuickTime 7.7.1\r\n    // DateTime : 2013:09:01 22:53:55\r\n    // ExifIFDPointer : 190\r\n    // ExposureTime : 0.058823529411764705 [1/17]\r\n    // FNumber : 2.4 [12/5]\r\n    // ExposureProgram : Normal program\r\n    // ISOSpeedRatings : 800\r\n    // ExifVersion : 0220\r\n    // DateTimeOriginal : 2013:09:01 22:52:51\r\n    // DateTimeDigitized : 2013:09:01 22:52:51\r\n    // ComponentsConfiguration : YCbCr\r\n    // ShutterSpeedValue : 4.058893515764426\r\n    // ApertureValue : 2.5260688216892597 [4845/1918]\r\n    // BrightnessValue : -0.3126686601998395\r\n    // MeteringMode : Pattern\r\n    // Flash : Flash did not fire, compulsory flash mode\r\n    // FocalLength : 4.28 [107/25]\r\n    // SubjectArea : [4 values]\r\n    // FlashpixVersion : 0100\r\n    // ColorSpace : 1\r\n    // PixelXDimension : 2448\r\n    // PixelYDimension : 3264\r\n    // SensingMethod : One-chip color area sensor\r\n    // ExposureMode : 0\r\n    // WhiteBalance : Auto white balance\r\n    // FocalLengthIn35mmFilm : 35\r\n    // SceneCaptureType : Standard\r\n    define('runtime/html5/imagemeta/exif',[\r\n        'base',\r\n        'runtime/html5/imagemeta'\r\n    ], function( Base, ImageMeta ) {\r\n    \r\n        var EXIF = {};\r\n    \r\n        EXIF.ExifMap = function() {\r\n            return this;\r\n        };\r\n    \r\n        EXIF.ExifMap.prototype.map = {\r\n            'Orientation': 0x0112\r\n        };\r\n    \r\n        EXIF.ExifMap.prototype.get = function( id ) {\r\n            return this[ id ] || this[ this.map[ id ] ];\r\n        };\r\n    \r\n        EXIF.exifTagTypes = {\r\n            // byte, 8-bit unsigned int:\r\n            1: {\r\n                getValue: function( dataView, dataOffset ) {\r\n                    return dataView.getUint8( dataOffset );\r\n                },\r\n                size: 1\r\n            },\r\n    \r\n            // ascii, 8-bit byte:\r\n            2: {\r\n                getValue: function( dataView, dataOffset ) {\r\n                    return String.fromCharCode( dataView.getUint8( dataOffset ) );\r\n                },\r\n                size: 1,\r\n                ascii: true\r\n            },\r\n    \r\n            // short, 16 bit int:\r\n            3: {\r\n                getValue: function( dataView, dataOffset, littleEndian ) {\r\n                    return dataView.getUint16( dataOffset, littleEndian );\r\n                },\r\n                size: 2\r\n            },\r\n    \r\n            // long, 32 bit int:\r\n            4: {\r\n                getValue: function( dataView, dataOffset, littleEndian ) {\r\n                    return dataView.getUint32( dataOffset, littleEndian );\r\n                },\r\n                size: 4\r\n            },\r\n    \r\n            // rational = two long values,\r\n            // first is numerator, second is denominator:\r\n            5: {\r\n                getValue: function( dataView, dataOffset, littleEndian ) {\r\n                    return dataView.getUint32( dataOffset, littleEndian ) /\r\n                        dataView.getUint32( dataOffset + 4, littleEndian );\r\n                },\r\n                size: 8\r\n            },\r\n    \r\n            // slong, 32 bit signed int:\r\n            9: {\r\n                getValue: function( dataView, dataOffset, littleEndian ) {\r\n                    return dataView.getInt32( dataOffset, littleEndian );\r\n                },\r\n                size: 4\r\n            },\r\n    \r\n            // srational, two slongs, first is numerator, second is denominator:\r\n            10: {\r\n                getValue: function( dataView, dataOffset, littleEndian ) {\r\n                    return dataView.getInt32( dataOffset, littleEndian ) /\r\n                        dataView.getInt32( dataOffset + 4, littleEndian );\r\n                },\r\n                size: 8\r\n            }\r\n        };\r\n    \r\n        // undefined, 8-bit byte, value depending on field:\r\n        EXIF.exifTagTypes[ 7 ] = EXIF.exifTagTypes[ 1 ];\r\n    \r\n        EXIF.getExifValue = function( dataView, tiffOffset, offset, type, length,\r\n                littleEndian ) {\r\n    \r\n            var tagType = EXIF.exifTagTypes[ type ],\r\n                tagSize, dataOffset, values, i, str, c;\r\n    \r\n            if ( !tagType ) {\r\n                Base.log('Invalid Exif data: Invalid tag type.');\r\n                return;\r\n            }\r\n    \r\n            tagSize = tagType.size * length;\r\n    \r\n            // Determine if the value is contained in the dataOffset bytes,\r\n            // or if the value at the dataOffset is a pointer to the actual data:\r\n            dataOffset = tagSize > 4 ? tiffOffset + dataView.getUint32( offset + 8,\r\n                    littleEndian ) : (offset + 8);\r\n    \r\n            if ( dataOffset + tagSize > dataView.byteLength ) {\r\n                Base.log('Invalid Exif data: Invalid data offset.');\r\n                return;\r\n            }\r\n    \r\n            if ( length === 1 ) {\r\n                return tagType.getValue( dataView, dataOffset, littleEndian );\r\n            }\r\n    \r\n            values = [];\r\n    \r\n            for ( i = 0; i < length; i += 1 ) {\r\n                values[ i ] = tagType.getValue( dataView,\r\n                        dataOffset + i * tagType.size, littleEndian );\r\n            }\r\n    \r\n            if ( tagType.ascii ) {\r\n                str = '';\r\n    \r\n                // Concatenate the chars:\r\n                for ( i = 0; i < values.length; i += 1 ) {\r\n                    c = values[ i ];\r\n    \r\n                    // Ignore the terminating NULL byte(s):\r\n                    if ( c === '\\u0000' ) {\r\n                        break;\r\n                    }\r\n                    str += c;\r\n                }\r\n    \r\n                return str;\r\n            }\r\n            return values;\r\n        };\r\n    \r\n        EXIF.parseExifTag = function( dataView, tiffOffset, offset, littleEndian,\r\n                data ) {\r\n    \r\n            var tag = dataView.getUint16( offset, littleEndian );\r\n            data.exif[ tag ] = EXIF.getExifValue( dataView, tiffOffset, offset,\r\n                    dataView.getUint16( offset + 2, littleEndian ),    // tag type\r\n                    dataView.getUint32( offset + 4, littleEndian ),    // tag length\r\n                    littleEndian );\r\n        };\r\n    \r\n        EXIF.parseExifTags = function( dataView, tiffOffset, dirOffset,\r\n                littleEndian, data ) {\r\n    \r\n            var tagsNumber, dirEndOffset, i;\r\n    \r\n            if ( dirOffset + 6 > dataView.byteLength ) {\r\n                Base.log('Invalid Exif data: Invalid directory offset.');\r\n                return;\r\n            }\r\n    \r\n            tagsNumber = dataView.getUint16( dirOffset, littleEndian );\r\n            dirEndOffset = dirOffset + 2 + 12 * tagsNumber;\r\n    \r\n            if ( dirEndOffset + 4 > dataView.byteLength ) {\r\n                Base.log('Invalid Exif data: Invalid directory size.');\r\n                return;\r\n            }\r\n    \r\n            for ( i = 0; i < tagsNumber; i += 1 ) {\r\n                this.parseExifTag( dataView, tiffOffset,\r\n                        dirOffset + 2 + 12 * i,    // tag offset\r\n                        littleEndian, data );\r\n            }\r\n    \r\n            // Return the offset to the next directory:\r\n            return dataView.getUint32( dirEndOffset, littleEndian );\r\n        };\r\n    \r\n        // EXIF.getExifThumbnail = function(dataView, offset, length) {\r\n        //     var hexData,\r\n        //         i,\r\n        //         b;\r\n        //     if (!length || offset + length > dataView.byteLength) {\r\n        //         Base.log('Invalid Exif data: Invalid thumbnail data.');\r\n        //         return;\r\n        //     }\r\n        //     hexData = [];\r\n        //     for (i = 0; i < length; i += 1) {\r\n        //         b = dataView.getUint8(offset + i);\r\n        //         hexData.push((b < 16 ? '0' : '') + b.toString(16));\r\n        //     }\r\n        //     return 'data:image/jpeg,%' + hexData.join('%');\r\n        // };\r\n    \r\n        EXIF.parseExifData = function( dataView, offset, length, data ) {\r\n    \r\n            var tiffOffset = offset + 10,\r\n                littleEndian, dirOffset;\r\n    \r\n            // Check for the ASCII code for \"Exif\" (0x45786966):\r\n            if ( dataView.getUint32( offset + 4 ) !== 0x45786966 ) {\r\n                // No Exif data, might be XMP data instead\r\n                return;\r\n            }\r\n            if ( tiffOffset + 8 > dataView.byteLength ) {\r\n                Base.log('Invalid Exif data: Invalid segment size.');\r\n                return;\r\n            }\r\n    \r\n            // Check for the two null bytes:\r\n            if ( dataView.getUint16( offset + 8 ) !== 0x0000 ) {\r\n                Base.log('Invalid Exif data: Missing byte alignment offset.');\r\n                return;\r\n            }\r\n    \r\n            // Check the byte alignment:\r\n            switch ( dataView.getUint16( tiffOffset ) ) {\r\n                case 0x4949:\r\n                    littleEndian = true;\r\n                    break;\r\n    \r\n                case 0x4D4D:\r\n                    littleEndian = false;\r\n                    break;\r\n    \r\n                default:\r\n                    Base.log('Invalid Exif data: Invalid byte alignment marker.');\r\n                    return;\r\n            }\r\n    \r\n            // Check for the TIFF tag marker (0x002A):\r\n            if ( dataView.getUint16( tiffOffset + 2, littleEndian ) !== 0x002A ) {\r\n                Base.log('Invalid Exif data: Missing TIFF marker.');\r\n                return;\r\n            }\r\n    \r\n            // Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal:\r\n            dirOffset = dataView.getUint32( tiffOffset + 4, littleEndian );\r\n            // Create the exif object to store the tags:\r\n            data.exif = new EXIF.ExifMap();\r\n            // Parse the tags of the main image directory and retrieve the\r\n            // offset to the next directory, usually the thumbnail directory:\r\n            dirOffset = EXIF.parseExifTags( dataView, tiffOffset,\r\n                    tiffOffset + dirOffset, littleEndian, data );\r\n    \r\n            // 尝试读取缩略图\r\n            // if ( dirOffset ) {\r\n            //     thumbnailData = {exif: {}};\r\n            //     dirOffset = EXIF.parseExifTags(\r\n            //         dataView,\r\n            //         tiffOffset,\r\n            //         tiffOffset + dirOffset,\r\n            //         littleEndian,\r\n            //         thumbnailData\r\n            //     );\r\n    \r\n            //     // Check for JPEG Thumbnail offset:\r\n            //     if (thumbnailData.exif[0x0201]) {\r\n            //         data.exif.Thumbnail = EXIF.getExifThumbnail(\r\n            //             dataView,\r\n            //             tiffOffset + thumbnailData.exif[0x0201],\r\n            //             thumbnailData.exif[0x0202] // Thumbnail data length\r\n            //         );\r\n            //     }\r\n            // }\r\n        };\r\n    \r\n        ImageMeta.parsers[ 0xffe1 ].push( EXIF.parseExifData );\r\n        return EXIF;\r\n    });\r\n    /**\r\n     * @fileOverview Image\r\n     */\r\n    define('runtime/html5/image',[\r\n        'base',\r\n        'runtime/html5/runtime',\r\n        'runtime/html5/util'\r\n    ], function( Base, Html5Runtime, Util ) {\r\n    \r\n        var BLANK = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D';\r\n    \r\n        return Html5Runtime.register( 'Image', {\r\n    \r\n            // flag: 标记是否被修改过。\r\n            modified: false,\r\n    \r\n            init: function() {\r\n                var me = this,\r\n                    img = new Image();\r\n    \r\n                img.onload = function() {\r\n    \r\n                    me._info = {\r\n                        type: me.type,\r\n                        width: this.width,\r\n                        height: this.height\r\n                    };\r\n    \r\n                    // 读取meta信息。\r\n                    if ( !me._metas && 'image/jpeg' === me.type ) {\r\n                        Util.parseMeta( me._blob, function( error, ret ) {\r\n                            me._metas = ret;\r\n                            me.owner.trigger('load');\r\n                        });\r\n                    } else {\r\n                        me.owner.trigger('load');\r\n                    }\r\n                };\r\n    \r\n                img.onerror = function() {\r\n                    me.owner.trigger('error');\r\n                };\r\n    \r\n                me._img = img;\r\n            },\r\n    \r\n            loadFromBlob: function( blob ) {\r\n                var me = this,\r\n                    img = me._img;\r\n    \r\n                me._blob = blob;\r\n                me.type = blob.type;\r\n                img.src = Util.createObjectURL( blob.getSource() );\r\n                me.owner.once( 'load', function() {\r\n                    Util.revokeObjectURL( img.src );\r\n                });\r\n            },\r\n    \r\n            resize: function( width, height ) {\r\n                var canvas = this._canvas ||\r\n                        (this._canvas = document.createElement('canvas'));\r\n    \r\n                this._resize( this._img, canvas, width, height );\r\n                this._blob = null;    // 没用了，可以删掉了。\r\n                this.modified = true;\r\n                this.owner.trigger('complete');\r\n            },\r\n    \r\n            getAsBlob: function( type ) {\r\n                var blob = this._blob,\r\n                    opts = this.options,\r\n                    canvas;\r\n    \r\n                type = type || this.type;\r\n    \r\n                // blob需要重新生成。\r\n                if ( this.modified || this.type !== type ) {\r\n                    canvas = this._canvas;\r\n    \r\n                    if ( type === 'image/jpeg' ) {\r\n    \r\n                        blob = Util.canvasToDataUrl( canvas, 'image/jpeg',\r\n                                opts.quality );\r\n    \r\n                        if ( opts.preserveHeaders && this._metas &&\r\n                                this._metas.imageHead ) {\r\n    \r\n                            blob = Util.dataURL2ArrayBuffer( blob );\r\n                            blob = Util.updateImageHead( blob,\r\n                                    this._metas.imageHead );\r\n                            blob = Util.arrayBufferToBlob( blob, type );\r\n                            return blob;\r\n                        }\r\n                    } else {\r\n                        blob = Util.canvasToDataUrl( canvas, type );\r\n                    }\r\n    \r\n                    blob = Util.dataURL2Blob( blob );\r\n                }\r\n    \r\n                return blob;\r\n            },\r\n    \r\n            getAsDataUrl: function( type ) {\r\n                var opts = this.options;\r\n    \r\n                type = type || this.type;\r\n    \r\n                if ( type === 'image/jpeg' ) {\r\n                    return Util.canvasToDataUrl( this._canvas, type, opts.quality );\r\n                } else {\r\n                    return this._canvas.toDataURL( type );\r\n                }\r\n            },\r\n    \r\n            getOrientation: function() {\r\n                return this._metas && this._metas.exif &&\r\n                        this._metas.exif.get('Orientation') || 1;\r\n            },\r\n    \r\n            info: function( val ) {\r\n    \r\n                // setter\r\n                if ( val ) {\r\n                    this._info = val;\r\n                    return this;\r\n                }\r\n    \r\n                // getter\r\n                return this._info;\r\n            },\r\n    \r\n            meta: function( val ) {\r\n    \r\n                // setter\r\n                if ( val ) {\r\n                    this._meta = val;\r\n                    return this;\r\n                }\r\n    \r\n                // getter\r\n                return this._meta;\r\n            },\r\n    \r\n            destroy: function() {\r\n                var canvas = this._canvas;\r\n                this._img.onload = null;\r\n    \r\n                if ( canvas ) {\r\n                    canvas.getContext('2d')\r\n                            .clearRect( 0, 0, canvas.width, canvas.height );\r\n                    canvas.width = canvas.height = 0;\r\n                    this._canvas = null;\r\n                }\r\n    \r\n                // 释放内存。非常重要，否则释放不了image的内存。\r\n                this._img.src = BLANK;\r\n                this._img = this._blob = null;\r\n            },\r\n    \r\n            _resize: function( img, cvs, width, height ) {\r\n                var opts = this.options,\r\n                    naturalWidth = img.width,\r\n                    naturalHeight = img.height,\r\n                    orientation = this.getOrientation(),\r\n                    scale, w, h, x, y;\r\n    \r\n                // values that require 90 degree rotation\r\n                if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) {\r\n    \r\n                    // 交换width, height的值。\r\n                    width ^= height;\r\n                    height ^= width;\r\n                    width ^= height;\r\n                }\r\n    \r\n                scale = Math[ opts.crop ? 'max' : 'min' ]( width / naturalWidth,\r\n                        height / naturalHeight );\r\n    \r\n                // 不允许放大。\r\n                opts.allowMagnify || (scale = Math.min( 1, scale ));\r\n    \r\n                w = naturalWidth * scale;\r\n                h = naturalHeight * scale;\r\n    \r\n                if ( opts.crop ) {\r\n                    cvs.width = width;\r\n                    cvs.height = height;\r\n                } else {\r\n                    cvs.width = w;\r\n                    cvs.height = h;\r\n                }\r\n    \r\n                x = (cvs.width - w) / 2;\r\n                y = (cvs.height - h) / 2;\r\n    \r\n                opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation );\r\n    \r\n                this._renderImageToCanvas( cvs, img, x, y, w, h );\r\n            },\r\n    \r\n            _rotate2Orientaion: function( canvas, orientation ) {\r\n                var width = canvas.width,\r\n                    height = canvas.height,\r\n                    ctx = canvas.getContext('2d');\r\n    \r\n                switch ( orientation ) {\r\n                    case 5:\r\n                    case 6:\r\n                    case 7:\r\n                    case 8:\r\n                        canvas.width = height;\r\n                        canvas.height = width;\r\n                        break;\r\n                }\r\n    \r\n                switch ( orientation ) {\r\n                    case 2:    // horizontal flip\r\n                        ctx.translate( width, 0 );\r\n                        ctx.scale( -1, 1 );\r\n                        break;\r\n    \r\n                    case 3:    // 180 rotate left\r\n                        ctx.translate( width, height );\r\n                        ctx.rotate( Math.PI );\r\n                        break;\r\n    \r\n                    case 4:    // vertical flip\r\n                        ctx.translate( 0, height );\r\n                        ctx.scale( 1, -1 );\r\n                        break;\r\n    \r\n                    case 5:    // vertical flip + 90 rotate right\r\n                        ctx.rotate( 0.5 * Math.PI );\r\n                        ctx.scale( 1, -1 );\r\n                        break;\r\n    \r\n                    case 6:    // 90 rotate right\r\n                        ctx.rotate( 0.5 * Math.PI );\r\n                        ctx.translate( 0, -height );\r\n                        break;\r\n    \r\n                    case 7:    // horizontal flip + 90 rotate right\r\n                        ctx.rotate( 0.5 * Math.PI );\r\n                        ctx.translate( width, -height );\r\n                        ctx.scale( -1, 1 );\r\n                        break;\r\n    \r\n                    case 8:    // 90 rotate left\r\n                        ctx.rotate( -0.5 * Math.PI );\r\n                        ctx.translate( -width, 0 );\r\n                        break;\r\n                }\r\n            },\r\n    \r\n            // https://github.com/stomita/ios-imagefile-megapixel/\r\n            // blob/master/src/megapix-image.js\r\n            _renderImageToCanvas: (function() {\r\n    \r\n                // 如果不是ios, 不需要这么复杂！\r\n                if ( !Base.os.ios ) {\r\n                    return function( canvas, img, x, y, w, h ) {\r\n                        canvas.getContext('2d').drawImage( img, x, y, w, h );\r\n                    };\r\n                }\r\n    \r\n                /**\r\n                 * Detecting vertical squash in loaded image.\r\n                 * Fixes a bug which squash image vertically while drawing into\r\n                 * canvas for some images.\r\n                 */\r\n                function detectVerticalSquash( img, iw, ih ) {\r\n                    var canvas = document.createElement('canvas'),\r\n                        ctx = canvas.getContext('2d'),\r\n                        sy = 0,\r\n                        ey = ih,\r\n                        py = ih,\r\n                        data, alpha, ratio;\r\n    \r\n    \r\n                    canvas.width = 1;\r\n                    canvas.height = ih;\r\n                    ctx.drawImage( img, 0, 0 );\r\n                    data = ctx.getImageData( 0, 0, 1, ih ).data;\r\n    \r\n                    // search image edge pixel position in case\r\n                    // it is squashed vertically.\r\n                    while ( py > sy ) {\r\n                        alpha = data[ (py - 1) * 4 + 3 ];\r\n    \r\n                        if ( alpha === 0 ) {\r\n                            ey = py;\r\n                        } else {\r\n                            sy = py;\r\n                        }\r\n    \r\n                        py = (ey + sy) >> 1;\r\n                    }\r\n    \r\n                    ratio = (py / ih);\r\n                    return (ratio === 0) ? 1 : ratio;\r\n                }\r\n    \r\n                // fix ie7 bug\r\n                // http://stackoverflow.com/questions/11929099/\r\n                // html5-canvas-drawimage-ratio-bug-ios\r\n                if ( Base.os.ios >= 7 ) {\r\n                    return function( canvas, img, x, y, w, h ) {\r\n                        var iw = img.naturalWidth,\r\n                            ih = img.naturalHeight,\r\n                            vertSquashRatio = detectVerticalSquash( img, iw, ih );\r\n    \r\n                        return canvas.getContext('2d').drawImage( img, 0, 0,\r\n                            iw * vertSquashRatio, ih * vertSquashRatio,\r\n                            x, y, w, h );\r\n                    };\r\n                }\r\n    \r\n                /**\r\n                 * Detect subsampling in loaded image.\r\n                 * In iOS, larger images than 2M pixels may be\r\n                 * subsampled in rendering.\r\n                 */\r\n                function detectSubsampling( img ) {\r\n                    var iw = img.naturalWidth,\r\n                        ih = img.naturalHeight,\r\n                        canvas, ctx;\r\n    \r\n                    // subsampling may happen overmegapixel image\r\n                    if ( iw * ih > 1024 * 1024 ) {\r\n                        canvas = document.createElement('canvas');\r\n                        canvas.width = canvas.height = 1;\r\n                        ctx = canvas.getContext('2d');\r\n                        ctx.drawImage( img, -iw + 1, 0 );\r\n    \r\n                        // subsampled image becomes half smaller in rendering size.\r\n                        // check alpha channel value to confirm image is covering\r\n                        // edge pixel or not. if alpha value is 0\r\n                        // image is not covering, hence subsampled.\r\n                        return ctx.getImageData( 0, 0, 1, 1 ).data[ 3 ] === 0;\r\n                    } else {\r\n                        return false;\r\n                    }\r\n                }\r\n    \r\n    \r\n                return function( canvas, img, x, y, width, height ) {\r\n                    var iw = img.naturalWidth,\r\n                        ih = img.naturalHeight,\r\n                        ctx = canvas.getContext('2d'),\r\n                        subsampled = detectSubsampling( img ),\r\n                        doSquash = this.type === 'image/jpeg',\r\n                        d = 1024,\r\n                        sy = 0,\r\n                        dy = 0,\r\n                        tmpCanvas, tmpCtx, vertSquashRatio, dw, dh, sx, dx;\r\n    \r\n                    if ( subsampled ) {\r\n                        iw /= 2;\r\n                        ih /= 2;\r\n                    }\r\n    \r\n                    ctx.save();\r\n                    tmpCanvas = document.createElement('canvas');\r\n                    tmpCanvas.width = tmpCanvas.height = d;\r\n    \r\n                    tmpCtx = tmpCanvas.getContext('2d');\r\n                    vertSquashRatio = doSquash ?\r\n                            detectVerticalSquash( img, iw, ih ) : 1;\r\n    \r\n                    dw = Math.ceil( d * width / iw );\r\n                    dh = Math.ceil( d * height / ih / vertSquashRatio );\r\n    \r\n                    while ( sy < ih ) {\r\n                        sx = 0;\r\n                        dx = 0;\r\n                        while ( sx < iw ) {\r\n                            tmpCtx.clearRect( 0, 0, d, d );\r\n                            tmpCtx.drawImage( img, -sx, -sy );\r\n                            ctx.drawImage( tmpCanvas, 0, 0, d, d,\r\n                                    x + dx, y + dy, dw, dh );\r\n                            sx += d;\r\n                            dx += dw;\r\n                        }\r\n                        sy += d;\r\n                        dy += dh;\r\n                    }\r\n                    ctx.restore();\r\n                    tmpCanvas = tmpCtx = null;\r\n                };\r\n            })()\r\n        });\r\n    });\r\n    /**\r\n     * 这个方式性能不行，但是可以解决android里面的toDataUrl的bug\r\n     * android里面toDataUrl('image/jpege')得到的结果却是png.\r\n     *\r\n     * 所以这里没辙，只能借助这个工具\r\n     * @fileOverview jpeg encoder\r\n     */\r\n    define('runtime/html5/jpegencoder',[], function( require, exports, module ) {\r\n    \r\n        /*\r\n          Copyright (c) 2008, Adobe Systems Incorporated\r\n          All rights reserved.\r\n    \r\n          Redistribution and use in source and binary forms, with or without\r\n          modification, are permitted provided that the following conditions are\r\n          met:\r\n    \r\n          * Redistributions of source code must retain the above copyright notice,\r\n            this list of conditions and the following disclaimer.\r\n    \r\n          * Redistributions in binary form must reproduce the above copyright\r\n            notice, this list of conditions and the following disclaimer in the\r\n            documentation and/or other materials provided with the distribution.\r\n    \r\n          * Neither the name of Adobe Systems Incorporated nor the names of its\r\n            contributors may be used to endorse or promote products derived from\r\n            this software without specific prior written permission.\r\n    \r\n          THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\r\n          IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\r\n          THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r\n          PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\r\n          CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\r\n          EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\r\n          PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\r\n          PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\r\n          LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\r\n          NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r\n          SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r\n        */\r\n        /*\r\n        JPEG encoder ported to JavaScript and optimized by Andreas Ritter, www.bytestrom.eu, 11/2009\r\n    \r\n        Basic GUI blocking jpeg encoder\r\n        */\r\n    \r\n        function JPEGEncoder(quality) {\r\n          var self = this;\r\n            var fround = Math.round;\r\n            var ffloor = Math.floor;\r\n            var YTable = new Array(64);\r\n            var UVTable = new Array(64);\r\n            var fdtbl_Y = new Array(64);\r\n            var fdtbl_UV = new Array(64);\r\n            var YDC_HT;\r\n            var UVDC_HT;\r\n            var YAC_HT;\r\n            var UVAC_HT;\r\n    \r\n            var bitcode = new Array(65535);\r\n            var category = new Array(65535);\r\n            var outputfDCTQuant = new Array(64);\r\n            var DU = new Array(64);\r\n            var byteout = [];\r\n            var bytenew = 0;\r\n            var bytepos = 7;\r\n    \r\n            var YDU = new Array(64);\r\n            var UDU = new Array(64);\r\n            var VDU = new Array(64);\r\n            var clt = new Array(256);\r\n            var RGB_YUV_TABLE = new Array(2048);\r\n            var currentQuality;\r\n    \r\n            var ZigZag = [\r\n                     0, 1, 5, 6,14,15,27,28,\r\n                     2, 4, 7,13,16,26,29,42,\r\n                     3, 8,12,17,25,30,41,43,\r\n                     9,11,18,24,31,40,44,53,\r\n                    10,19,23,32,39,45,52,54,\r\n                    20,22,33,38,46,51,55,60,\r\n                    21,34,37,47,50,56,59,61,\r\n                    35,36,48,49,57,58,62,63\r\n                ];\r\n    \r\n            var std_dc_luminance_nrcodes = [0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0];\r\n            var std_dc_luminance_values = [0,1,2,3,4,5,6,7,8,9,10,11];\r\n            var std_ac_luminance_nrcodes = [0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d];\r\n            var std_ac_luminance_values = [\r\n                    0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,\r\n                    0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,\r\n                    0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08,\r\n                    0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,\r\n                    0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,\r\n                    0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28,\r\n                    0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,\r\n                    0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,\r\n                    0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,\r\n                    0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,\r\n                    0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,\r\n                    0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89,\r\n                    0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,\r\n                    0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,\r\n                    0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,\r\n                    0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,\r\n                    0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,\r\n                    0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2,\r\n                    0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,\r\n                    0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,\r\n                    0xf9,0xfa\r\n                ];\r\n    \r\n            var std_dc_chrominance_nrcodes = [0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0];\r\n            var std_dc_chrominance_values = [0,1,2,3,4,5,6,7,8,9,10,11];\r\n            var std_ac_chrominance_nrcodes = [0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77];\r\n            var std_ac_chrominance_values = [\r\n                    0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,\r\n                    0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,\r\n                    0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,\r\n                    0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,\r\n                    0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,\r\n                    0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26,\r\n                    0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,\r\n                    0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,\r\n                    0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,\r\n                    0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,\r\n                    0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,\r\n                    0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87,\r\n                    0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,\r\n                    0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,\r\n                    0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,\r\n                    0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,\r\n                    0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,\r\n                    0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,\r\n                    0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,\r\n                    0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,\r\n                    0xf9,0xfa\r\n                ];\r\n    \r\n            function initQuantTables(sf){\r\n                    var YQT = [\r\n                        16, 11, 10, 16, 24, 40, 51, 61,\r\n                        12, 12, 14, 19, 26, 58, 60, 55,\r\n                        14, 13, 16, 24, 40, 57, 69, 56,\r\n                        14, 17, 22, 29, 51, 87, 80, 62,\r\n                        18, 22, 37, 56, 68,109,103, 77,\r\n                        24, 35, 55, 64, 81,104,113, 92,\r\n                        49, 64, 78, 87,103,121,120,101,\r\n                        72, 92, 95, 98,112,100,103, 99\r\n                    ];\r\n    \r\n                    for (var i = 0; i < 64; i++) {\r\n                        var t = ffloor((YQT[i]*sf+50)/100);\r\n                        if (t < 1) {\r\n                            t = 1;\r\n                        } else if (t > 255) {\r\n                            t = 255;\r\n                        }\r\n                        YTable[ZigZag[i]] = t;\r\n                    }\r\n                    var UVQT = [\r\n                        17, 18, 24, 47, 99, 99, 99, 99,\r\n                        18, 21, 26, 66, 99, 99, 99, 99,\r\n                        24, 26, 56, 99, 99, 99, 99, 99,\r\n                        47, 66, 99, 99, 99, 99, 99, 99,\r\n                        99, 99, 99, 99, 99, 99, 99, 99,\r\n                        99, 99, 99, 99, 99, 99, 99, 99,\r\n                        99, 99, 99, 99, 99, 99, 99, 99,\r\n                        99, 99, 99, 99, 99, 99, 99, 99\r\n                    ];\r\n                    for (var j = 0; j < 64; j++) {\r\n                        var u = ffloor((UVQT[j]*sf+50)/100);\r\n                        if (u < 1) {\r\n                            u = 1;\r\n                        } else if (u > 255) {\r\n                            u = 255;\r\n                        }\r\n                        UVTable[ZigZag[j]] = u;\r\n                    }\r\n                    var aasf = [\r\n                        1.0, 1.387039845, 1.306562965, 1.175875602,\r\n                        1.0, 0.785694958, 0.541196100, 0.275899379\r\n                    ];\r\n                    var k = 0;\r\n                    for (var row = 0; row < 8; row++)\r\n                    {\r\n                        for (var col = 0; col < 8; col++)\r\n                        {\r\n                            fdtbl_Y[k]  = (1.0 / (YTable [ZigZag[k]] * aasf[row] * aasf[col] * 8.0));\r\n                            fdtbl_UV[k] = (1.0 / (UVTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0));\r\n                            k++;\r\n                        }\r\n                    }\r\n                }\r\n    \r\n                function computeHuffmanTbl(nrcodes, std_table){\r\n                    var codevalue = 0;\r\n                    var pos_in_table = 0;\r\n                    var HT = new Array();\r\n                    for (var k = 1; k <= 16; k++) {\r\n                        for (var j = 1; j <= nrcodes[k]; j++) {\r\n                            HT[std_table[pos_in_table]] = [];\r\n                            HT[std_table[pos_in_table]][0] = codevalue;\r\n                            HT[std_table[pos_in_table]][1] = k;\r\n                            pos_in_table++;\r\n                            codevalue++;\r\n                        }\r\n                        codevalue*=2;\r\n                    }\r\n                    return HT;\r\n                }\r\n    \r\n                function initHuffmanTbl()\r\n                {\r\n                    YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes,std_dc_luminance_values);\r\n                    UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes,std_dc_chrominance_values);\r\n                    YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes,std_ac_luminance_values);\r\n                    UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes,std_ac_chrominance_values);\r\n                }\r\n    \r\n                function initCategoryNumber()\r\n                {\r\n                    var nrlower = 1;\r\n                    var nrupper = 2;\r\n                    for (var cat = 1; cat <= 15; cat++) {\r\n                        //Positive numbers\r\n                        for (var nr = nrlower; nr<nrupper; nr++) {\r\n                            category[32767+nr] = cat;\r\n                            bitcode[32767+nr] = [];\r\n                            bitcode[32767+nr][1] = cat;\r\n                            bitcode[32767+nr][0] = nr;\r\n                        }\r\n                        //Negative numbers\r\n                        for (var nrneg =-(nrupper-1); nrneg<=-nrlower; nrneg++) {\r\n                            category[32767+nrneg] = cat;\r\n                            bitcode[32767+nrneg] = [];\r\n                            bitcode[32767+nrneg][1] = cat;\r\n                            bitcode[32767+nrneg][0] = nrupper-1+nrneg;\r\n                        }\r\n                        nrlower <<= 1;\r\n                        nrupper <<= 1;\r\n                    }\r\n                }\r\n    \r\n                function initRGBYUVTable() {\r\n                    for(var i = 0; i < 256;i++) {\r\n                        RGB_YUV_TABLE[i]            =  19595 * i;\r\n                        RGB_YUV_TABLE[(i+ 256)>>0]  =  38470 * i;\r\n                        RGB_YUV_TABLE[(i+ 512)>>0]  =   7471 * i + 0x8000;\r\n                        RGB_YUV_TABLE[(i+ 768)>>0]  = -11059 * i;\r\n                        RGB_YUV_TABLE[(i+1024)>>0]  = -21709 * i;\r\n                        RGB_YUV_TABLE[(i+1280)>>0]  =  32768 * i + 0x807FFF;\r\n                        RGB_YUV_TABLE[(i+1536)>>0]  = -27439 * i;\r\n                        RGB_YUV_TABLE[(i+1792)>>0]  = - 5329 * i;\r\n                    }\r\n                }\r\n    \r\n                // IO functions\r\n                function writeBits(bs)\r\n                {\r\n                    var value = bs[0];\r\n                    var posval = bs[1]-1;\r\n                    while ( posval >= 0 ) {\r\n                        if (value & (1 << posval) ) {\r\n                            bytenew |= (1 << bytepos);\r\n                        }\r\n                        posval--;\r\n                        bytepos--;\r\n                        if (bytepos < 0) {\r\n                            if (bytenew == 0xFF) {\r\n                                writeByte(0xFF);\r\n                                writeByte(0);\r\n                            }\r\n                            else {\r\n                                writeByte(bytenew);\r\n                            }\r\n                            bytepos=7;\r\n                            bytenew=0;\r\n                        }\r\n                    }\r\n                }\r\n    \r\n                function writeByte(value)\r\n                {\r\n                    byteout.push(clt[value]); // write char directly instead of converting later\r\n                }\r\n    \r\n                function writeWord(value)\r\n                {\r\n                    writeByte((value>>8)&0xFF);\r\n                    writeByte((value   )&0xFF);\r\n                }\r\n    \r\n                // DCT & quantization core\r\n                function fDCTQuant(data, fdtbl)\r\n                {\r\n                    var d0, d1, d2, d3, d4, d5, d6, d7;\r\n                    /* Pass 1: process rows. */\r\n                    var dataOff=0;\r\n                    var i;\r\n                    var I8 = 8;\r\n                    var I64 = 64;\r\n                    for (i=0; i<I8; ++i)\r\n                    {\r\n                        d0 = data[dataOff];\r\n                        d1 = data[dataOff+1];\r\n                        d2 = data[dataOff+2];\r\n                        d3 = data[dataOff+3];\r\n                        d4 = data[dataOff+4];\r\n                        d5 = data[dataOff+5];\r\n                        d6 = data[dataOff+6];\r\n                        d7 = data[dataOff+7];\r\n    \r\n                        var tmp0 = d0 + d7;\r\n                        var tmp7 = d0 - d7;\r\n                        var tmp1 = d1 + d6;\r\n                        var tmp6 = d1 - d6;\r\n                        var tmp2 = d2 + d5;\r\n                        var tmp5 = d2 - d5;\r\n                        var tmp3 = d3 + d4;\r\n                        var tmp4 = d3 - d4;\r\n    \r\n                        /* Even part */\r\n                        var tmp10 = tmp0 + tmp3;    /* phase 2 */\r\n                        var tmp13 = tmp0 - tmp3;\r\n                        var tmp11 = tmp1 + tmp2;\r\n                        var tmp12 = tmp1 - tmp2;\r\n    \r\n                        data[dataOff] = tmp10 + tmp11; /* phase 3 */\r\n                        data[dataOff+4] = tmp10 - tmp11;\r\n    \r\n                        var z1 = (tmp12 + tmp13) * 0.707106781; /* c4 */\r\n                        data[dataOff+2] = tmp13 + z1; /* phase 5 */\r\n                        data[dataOff+6] = tmp13 - z1;\r\n    \r\n                        /* Odd part */\r\n                        tmp10 = tmp4 + tmp5; /* phase 2 */\r\n                        tmp11 = tmp5 + tmp6;\r\n                        tmp12 = tmp6 + tmp7;\r\n    \r\n                        /* The rotator is modified from fig 4-8 to avoid extra negations. */\r\n                        var z5 = (tmp10 - tmp12) * 0.382683433; /* c6 */\r\n                        var z2 = 0.541196100 * tmp10 + z5; /* c2-c6 */\r\n                        var z4 = 1.306562965 * tmp12 + z5; /* c2+c6 */\r\n                        var z3 = tmp11 * 0.707106781; /* c4 */\r\n    \r\n                        var z11 = tmp7 + z3;    /* phase 5 */\r\n                        var z13 = tmp7 - z3;\r\n    \r\n                        data[dataOff+5] = z13 + z2; /* phase 6 */\r\n                        data[dataOff+3] = z13 - z2;\r\n                        data[dataOff+1] = z11 + z4;\r\n                        data[dataOff+7] = z11 - z4;\r\n    \r\n                        dataOff += 8; /* advance pointer to next row */\r\n                    }\r\n    \r\n                    /* Pass 2: process columns. */\r\n                    dataOff = 0;\r\n                    for (i=0; i<I8; ++i)\r\n                    {\r\n                        d0 = data[dataOff];\r\n                        d1 = data[dataOff + 8];\r\n                        d2 = data[dataOff + 16];\r\n                        d3 = data[dataOff + 24];\r\n                        d4 = data[dataOff + 32];\r\n                        d5 = data[dataOff + 40];\r\n                        d6 = data[dataOff + 48];\r\n                        d7 = data[dataOff + 56];\r\n    \r\n                        var tmp0p2 = d0 + d7;\r\n                        var tmp7p2 = d0 - d7;\r\n                        var tmp1p2 = d1 + d6;\r\n                        var tmp6p2 = d1 - d6;\r\n                        var tmp2p2 = d2 + d5;\r\n                        var tmp5p2 = d2 - d5;\r\n                        var tmp3p2 = d3 + d4;\r\n                        var tmp4p2 = d3 - d4;\r\n    \r\n                        /* Even part */\r\n                        var tmp10p2 = tmp0p2 + tmp3p2;  /* phase 2 */\r\n                        var tmp13p2 = tmp0p2 - tmp3p2;\r\n                        var tmp11p2 = tmp1p2 + tmp2p2;\r\n                        var tmp12p2 = tmp1p2 - tmp2p2;\r\n    \r\n                        data[dataOff] = tmp10p2 + tmp11p2; /* phase 3 */\r\n                        data[dataOff+32] = tmp10p2 - tmp11p2;\r\n    \r\n                        var z1p2 = (tmp12p2 + tmp13p2) * 0.707106781; /* c4 */\r\n                        data[dataOff+16] = tmp13p2 + z1p2; /* phase 5 */\r\n                        data[dataOff+48] = tmp13p2 - z1p2;\r\n    \r\n                        /* Odd part */\r\n                        tmp10p2 = tmp4p2 + tmp5p2; /* phase 2 */\r\n                        tmp11p2 = tmp5p2 + tmp6p2;\r\n                        tmp12p2 = tmp6p2 + tmp7p2;\r\n    \r\n                        /* The rotator is modified from fig 4-8 to avoid extra negations. */\r\n                        var z5p2 = (tmp10p2 - tmp12p2) * 0.382683433; /* c6 */\r\n                        var z2p2 = 0.541196100 * tmp10p2 + z5p2; /* c2-c6 */\r\n                        var z4p2 = 1.306562965 * tmp12p2 + z5p2; /* c2+c6 */\r\n                        var z3p2 = tmp11p2 * 0.707106781; /* c4 */\r\n    \r\n                        var z11p2 = tmp7p2 + z3p2;  /* phase 5 */\r\n                        var z13p2 = tmp7p2 - z3p2;\r\n    \r\n                        data[dataOff+40] = z13p2 + z2p2; /* phase 6 */\r\n                        data[dataOff+24] = z13p2 - z2p2;\r\n                        data[dataOff+ 8] = z11p2 + z4p2;\r\n                        data[dataOff+56] = z11p2 - z4p2;\r\n    \r\n                        dataOff++; /* advance pointer to next column */\r\n                    }\r\n    \r\n                    // Quantize/descale the coefficients\r\n                    var fDCTQuant;\r\n                    for (i=0; i<I64; ++i)\r\n                    {\r\n                        // Apply the quantization and scaling factor & Round to nearest integer\r\n                        fDCTQuant = data[i]*fdtbl[i];\r\n                        outputfDCTQuant[i] = (fDCTQuant > 0.0) ? ((fDCTQuant + 0.5)|0) : ((fDCTQuant - 0.5)|0);\r\n                        //outputfDCTQuant[i] = fround(fDCTQuant);\r\n    \r\n                    }\r\n                    return outputfDCTQuant;\r\n                }\r\n    \r\n                function writeAPP0()\r\n                {\r\n                    writeWord(0xFFE0); // marker\r\n                    writeWord(16); // length\r\n                    writeByte(0x4A); // J\r\n                    writeByte(0x46); // F\r\n                    writeByte(0x49); // I\r\n                    writeByte(0x46); // F\r\n                    writeByte(0); // = \"JFIF\",'\\0'\r\n                    writeByte(1); // versionhi\r\n                    writeByte(1); // versionlo\r\n                    writeByte(0); // xyunits\r\n                    writeWord(1); // xdensity\r\n                    writeWord(1); // ydensity\r\n                    writeByte(0); // thumbnwidth\r\n                    writeByte(0); // thumbnheight\r\n                }\r\n    \r\n                function writeSOF0(width, height)\r\n                {\r\n                    writeWord(0xFFC0); // marker\r\n                    writeWord(17);   // length, truecolor YUV JPG\r\n                    writeByte(8);    // precision\r\n                    writeWord(height);\r\n                    writeWord(width);\r\n                    writeByte(3);    // nrofcomponents\r\n                    writeByte(1);    // IdY\r\n                    writeByte(0x11); // HVY\r\n                    writeByte(0);    // QTY\r\n                    writeByte(2);    // IdU\r\n                    writeByte(0x11); // HVU\r\n                    writeByte(1);    // QTU\r\n                    writeByte(3);    // IdV\r\n                    writeByte(0x11); // HVV\r\n                    writeByte(1);    // QTV\r\n                }\r\n    \r\n                function writeDQT()\r\n                {\r\n                    writeWord(0xFFDB); // marker\r\n                    writeWord(132);    // length\r\n                    writeByte(0);\r\n                    for (var i=0; i<64; i++) {\r\n                        writeByte(YTable[i]);\r\n                    }\r\n                    writeByte(1);\r\n                    for (var j=0; j<64; j++) {\r\n                        writeByte(UVTable[j]);\r\n                    }\r\n                }\r\n    \r\n                function writeDHT()\r\n                {\r\n                    writeWord(0xFFC4); // marker\r\n                    writeWord(0x01A2); // length\r\n    \r\n                    writeByte(0); // HTYDCinfo\r\n                    for (var i=0; i<16; i++) {\r\n                        writeByte(std_dc_luminance_nrcodes[i+1]);\r\n                    }\r\n                    for (var j=0; j<=11; j++) {\r\n                        writeByte(std_dc_luminance_values[j]);\r\n                    }\r\n    \r\n                    writeByte(0x10); // HTYACinfo\r\n                    for (var k=0; k<16; k++) {\r\n                        writeByte(std_ac_luminance_nrcodes[k+1]);\r\n                    }\r\n                    for (var l=0; l<=161; l++) {\r\n                        writeByte(std_ac_luminance_values[l]);\r\n                    }\r\n    \r\n                    writeByte(1); // HTUDCinfo\r\n                    for (var m=0; m<16; m++) {\r\n                        writeByte(std_dc_chrominance_nrcodes[m+1]);\r\n                    }\r\n                    for (var n=0; n<=11; n++) {\r\n                        writeByte(std_dc_chrominance_values[n]);\r\n                    }\r\n    \r\n                    writeByte(0x11); // HTUACinfo\r\n                    for (var o=0; o<16; o++) {\r\n                        writeByte(std_ac_chrominance_nrcodes[o+1]);\r\n                    }\r\n                    for (var p=0; p<=161; p++) {\r\n                        writeByte(std_ac_chrominance_values[p]);\r\n                    }\r\n                }\r\n    \r\n                function writeSOS()\r\n                {\r\n                    writeWord(0xFFDA); // marker\r\n                    writeWord(12); // length\r\n                    writeByte(3); // nrofcomponents\r\n                    writeByte(1); // IdY\r\n                    writeByte(0); // HTY\r\n                    writeByte(2); // IdU\r\n                    writeByte(0x11); // HTU\r\n                    writeByte(3); // IdV\r\n                    writeByte(0x11); // HTV\r\n                    writeByte(0); // Ss\r\n                    writeByte(0x3f); // Se\r\n                    writeByte(0); // Bf\r\n                }\r\n    \r\n                function processDU(CDU, fdtbl, DC, HTDC, HTAC){\r\n                    var EOB = HTAC[0x00];\r\n                    var M16zeroes = HTAC[0xF0];\r\n                    var pos;\r\n                    var I16 = 16;\r\n                    var I63 = 63;\r\n                    var I64 = 64;\r\n                    var DU_DCT = fDCTQuant(CDU, fdtbl);\r\n                    //ZigZag reorder\r\n                    for (var j=0;j<I64;++j) {\r\n                        DU[ZigZag[j]]=DU_DCT[j];\r\n                    }\r\n                    var Diff = DU[0] - DC; DC = DU[0];\r\n                    //Encode DC\r\n                    if (Diff==0) {\r\n                        writeBits(HTDC[0]); // Diff might be 0\r\n                    } else {\r\n                        pos = 32767+Diff;\r\n                        writeBits(HTDC[category[pos]]);\r\n                        writeBits(bitcode[pos]);\r\n                    }\r\n                    //Encode ACs\r\n                    var end0pos = 63; // was const... which is crazy\r\n                    for (; (end0pos>0)&&(DU[end0pos]==0); end0pos--) {};\r\n                    //end0pos = first element in reverse order !=0\r\n                    if ( end0pos == 0) {\r\n                        writeBits(EOB);\r\n                        return DC;\r\n                    }\r\n                    var i = 1;\r\n                    var lng;\r\n                    while ( i <= end0pos ) {\r\n                        var startpos = i;\r\n                        for (; (DU[i]==0) && (i<=end0pos); ++i) {}\r\n                        var nrzeroes = i-startpos;\r\n                        if ( nrzeroes >= I16 ) {\r\n                            lng = nrzeroes>>4;\r\n                            for (var nrmarker=1; nrmarker <= lng; ++nrmarker)\r\n                                writeBits(M16zeroes);\r\n                            nrzeroes = nrzeroes&0xF;\r\n                        }\r\n                        pos = 32767+DU[i];\r\n                        writeBits(HTAC[(nrzeroes<<4)+category[pos]]);\r\n                        writeBits(bitcode[pos]);\r\n                        i++;\r\n                    }\r\n                    if ( end0pos != I63 ) {\r\n                        writeBits(EOB);\r\n                    }\r\n                    return DC;\r\n                }\r\n    \r\n                function initCharLookupTable(){\r\n                    var sfcc = String.fromCharCode;\r\n                    for(var i=0; i < 256; i++){ ///// ACHTUNG // 255\r\n                        clt[i] = sfcc(i);\r\n                    }\r\n                }\r\n    \r\n                this.encode = function(image,quality) // image data object\r\n                {\r\n                    // var time_start = new Date().getTime();\r\n    \r\n                    if(quality) setQuality(quality);\r\n    \r\n                    // Initialize bit writer\r\n                    byteout = new Array();\r\n                    bytenew=0;\r\n                    bytepos=7;\r\n    \r\n                    // Add JPEG headers\r\n                    writeWord(0xFFD8); // SOI\r\n                    writeAPP0();\r\n                    writeDQT();\r\n                    writeSOF0(image.width,image.height);\r\n                    writeDHT();\r\n                    writeSOS();\r\n    \r\n    \r\n                    // Encode 8x8 macroblocks\r\n                    var DCY=0;\r\n                    var DCU=0;\r\n                    var DCV=0;\r\n    \r\n                    bytenew=0;\r\n                    bytepos=7;\r\n    \r\n    \r\n                    this.encode.displayName = \"_encode_\";\r\n    \r\n                    var imageData = image.data;\r\n                    var width = image.width;\r\n                    var height = image.height;\r\n    \r\n                    var quadWidth = width*4;\r\n                    var tripleWidth = width*3;\r\n    \r\n                    var x, y = 0;\r\n                    var r, g, b;\r\n                    var start,p, col,row,pos;\r\n                    while(y < height){\r\n                        x = 0;\r\n                        while(x < quadWidth){\r\n                        start = quadWidth * y + x;\r\n                        p = start;\r\n                        col = -1;\r\n                        row = 0;\r\n    \r\n                        for(pos=0; pos < 64; pos++){\r\n                            row = pos >> 3;// /8\r\n                            col = ( pos & 7 ) * 4; // %8\r\n                            p = start + ( row * quadWidth ) + col;\r\n    \r\n                            if(y+row >= height){ // padding bottom\r\n                                p-= (quadWidth*(y+1+row-height));\r\n                            }\r\n    \r\n                            if(x+col >= quadWidth){ // padding right\r\n                                p-= ((x+col) - quadWidth +4)\r\n                            }\r\n    \r\n                            r = imageData[ p++ ];\r\n                            g = imageData[ p++ ];\r\n                            b = imageData[ p++ ];\r\n    \r\n    \r\n                            /* // calculate YUV values dynamically\r\n                            YDU[pos]=((( 0.29900)*r+( 0.58700)*g+( 0.11400)*b))-128; //-0x80\r\n                            UDU[pos]=(((-0.16874)*r+(-0.33126)*g+( 0.50000)*b));\r\n                            VDU[pos]=((( 0.50000)*r+(-0.41869)*g+(-0.08131)*b));\r\n                            */\r\n    \r\n                            // use lookup table (slightly faster)\r\n                            YDU[pos] = ((RGB_YUV_TABLE[r]             + RGB_YUV_TABLE[(g +  256)>>0] + RGB_YUV_TABLE[(b +  512)>>0]) >> 16)-128;\r\n                            UDU[pos] = ((RGB_YUV_TABLE[(r +  768)>>0] + RGB_YUV_TABLE[(g + 1024)>>0] + RGB_YUV_TABLE[(b + 1280)>>0]) >> 16)-128;\r\n                            VDU[pos] = ((RGB_YUV_TABLE[(r + 1280)>>0] + RGB_YUV_TABLE[(g + 1536)>>0] + RGB_YUV_TABLE[(b + 1792)>>0]) >> 16)-128;\r\n    \r\n                        }\r\n    \r\n                        DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT);\r\n                        DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);\r\n                        DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);\r\n                        x+=32;\r\n                        }\r\n                        y+=8;\r\n                    }\r\n    \r\n    \r\n                    ////////////////////////////////////////////////////////////////\r\n    \r\n                    // Do the bit alignment of the EOI marker\r\n                    if ( bytepos >= 0 ) {\r\n                        var fillbits = [];\r\n                        fillbits[1] = bytepos+1;\r\n                        fillbits[0] = (1<<(bytepos+1))-1;\r\n                        writeBits(fillbits);\r\n                    }\r\n    \r\n                    writeWord(0xFFD9); //EOI\r\n    \r\n                    var jpegDataUri = 'data:image/jpeg;base64,' + btoa(byteout.join(''));\r\n    \r\n                    byteout = [];\r\n    \r\n                    // benchmarking\r\n                    // var duration = new Date().getTime() - time_start;\r\n                    // console.log('Encoding time: '+ currentQuality + 'ms');\r\n                    //\r\n    \r\n                    return jpegDataUri\r\n            }\r\n    \r\n            function setQuality(quality){\r\n                if (quality <= 0) {\r\n                    quality = 1;\r\n                }\r\n                if (quality > 100) {\r\n                    quality = 100;\r\n                }\r\n    \r\n                if(currentQuality == quality) return // don't recalc if unchanged\r\n    \r\n                var sf = 0;\r\n                if (quality < 50) {\r\n                    sf = Math.floor(5000 / quality);\r\n                } else {\r\n                    sf = Math.floor(200 - quality*2);\r\n                }\r\n    \r\n                initQuantTables(sf);\r\n                currentQuality = quality;\r\n                // console.log('Quality set to: '+quality +'%');\r\n            }\r\n    \r\n            function init(){\r\n                // var time_start = new Date().getTime();\r\n                if(!quality) quality = 50;\r\n                // Create tables\r\n                initCharLookupTable()\r\n                initHuffmanTbl();\r\n                initCategoryNumber();\r\n                initRGBYUVTable();\r\n    \r\n                setQuality(quality);\r\n                // var duration = new Date().getTime() - time_start;\r\n                // console.log('Initialization '+ duration + 'ms');\r\n            }\r\n    \r\n            init();\r\n    \r\n        };\r\n    \r\n        JPEGEncoder.encode = function( data, quality ) {\r\n            var encoder = new JPEGEncoder( quality );\r\n    \r\n            return encoder.encode( data );\r\n        }\r\n    \r\n        return JPEGEncoder;\r\n    });\r\n    /**\r\n     * @fileOverview Fix android canvas.toDataUrl bug.\r\n     */\r\n    define('runtime/html5/androidpatch',[\r\n        'runtime/html5/util',\r\n        'runtime/html5/jpegencoder',\r\n        'base'\r\n    ], function( Util, encoder, Base ) {\r\n        var origin = Util.canvasToDataUrl,\r\n            supportJpeg;\r\n    \r\n        Util.canvasToDataUrl = function( canvas, type, quality ) {\r\n            var ctx, w, h, fragement, parts;\r\n    \r\n            // 非android手机直接跳过。\r\n            if ( !Base.os.android ) {\r\n                return origin.apply( null, arguments );\r\n            }\r\n    \r\n            // 检测是否canvas支持jpeg导出，根据数据格式来判断。\r\n            // JPEG 前两位分别是：255, 216\r\n            if ( type === 'image/jpeg' && typeof supportJpeg === 'undefined' ) {\r\n                fragement = origin.apply( null, arguments );\r\n    \r\n                parts = fragement.split(',');\r\n    \r\n                if ( ~parts[ 0 ].indexOf('base64') ) {\r\n                    fragement = atob( parts[ 1 ] );\r\n                } else {\r\n                    fragement = decodeURIComponent( parts[ 1 ] );\r\n                }\r\n    \r\n                fragement = fragement.substring( 0, 2 );\r\n    \r\n                supportJpeg = fragement.charCodeAt( 0 ) === 255 &&\r\n                        fragement.charCodeAt( 1 ) === 216;\r\n            }\r\n    \r\n            // 只有在android环境下才修复\r\n            if ( type === 'image/jpeg' && !supportJpeg ) {\r\n                w = canvas.width;\r\n                h = canvas.height;\r\n                ctx = canvas.getContext('2d');\r\n    \r\n                return encoder.encode( ctx.getImageData( 0, 0, w, h ), quality );\r\n            }\r\n    \r\n            return origin.apply( null, arguments );\r\n        };\r\n    });\r\n    /**\r\n     * @fileOverview Transport\r\n     * @todo 支持chunked传输，优势：\r\n     * 可以将大文件分成小块，挨个传输，可以提高大文件成功率，当失败的时候，也只需要重传那小部分，\r\n     * 而不需要重头再传一次。另外断点续传也需要用chunked方式。\r\n     */\r\n    define('runtime/html5/transport',[\r\n        'base',\r\n        'runtime/html5/runtime'\r\n    ], function( Base, Html5Runtime ) {\r\n    \r\n        var noop = Base.noop,\r\n            $ = Base.$;\r\n    \r\n        return Html5Runtime.register( 'Transport', {\r\n            init: function() {\r\n                this._status = 0;\r\n                this._response = null;\r\n            },\r\n    \r\n            send: function() {\r\n                var owner = this.owner,\r\n                    opts = this.options,\r\n                    xhr = this._initAjax(),\r\n                    blob = owner._blob,\r\n                    server = opts.server,\r\n                    formData, binary, fr;\r\n    \r\n                if ( opts.sendAsBinary ) {\r\n                    server += (/\\?/.test( server ) ? '&' : '?') +\r\n                            $.param( owner._formData );\r\n    \r\n                    binary = blob.getSource();\r\n                } else {\r\n                    formData = new FormData();\r\n                    $.each( owner._formData, function( k, v ) {\r\n                        formData.append( k, v );\r\n                    });\r\n    \r\n                    formData.append( opts.fileVal, blob.getSource(),\r\n                            opts.filename || owner._formData.name || '' );\r\n                }\r\n    \r\n                if ( opts.withCredentials && 'withCredentials' in xhr ) {\r\n                    xhr.open( opts.method, server, true );\r\n                    xhr.withCredentials = true;\r\n                } else {\r\n                    xhr.open( opts.method, server );\r\n                }\r\n    \r\n                this._setRequestHeader( xhr, opts.headers );\r\n    \r\n                if ( binary ) {\r\n                    xhr.overrideMimeType('application/octet-stream');\r\n    \r\n                    // android直接发送blob会导致服务端接收到的是空文件。\r\n                    // bug详情。\r\n                    // https://code.google.com/p/android/issues/detail?id=39882\r\n                    // 所以先用fileReader读取出来再通过arraybuffer的方式发送。\r\n                    if ( Base.os.android ) {\r\n                        fr = new FileReader();\r\n    \r\n                        fr.onload = function() {\r\n                            xhr.send( this.result );\r\n                            fr = fr.onload = null;\r\n                        };\r\n    \r\n                        fr.readAsArrayBuffer( binary );\r\n                    } else {\r\n                        xhr.send( binary );\r\n                    }\r\n                } else {\r\n                    xhr.send( formData );\r\n                }\r\n            },\r\n    \r\n            getResponse: function() {\r\n                return this._response;\r\n            },\r\n    \r\n            getResponseAsJson: function() {\r\n                return this._parseJson( this._response );\r\n            },\r\n    \r\n            getStatus: function() {\r\n                return this._status;\r\n            },\r\n    \r\n            abort: function() {\r\n                var xhr = this._xhr;\r\n    \r\n                if ( xhr ) {\r\n                    xhr.upload.onprogress = noop;\r\n                    xhr.onreadystatechange = noop;\r\n                    xhr.abort();\r\n    \r\n                    this._xhr = xhr = null;\r\n                }\r\n            },\r\n    \r\n            destroy: function() {\r\n                this.abort();\r\n            },\r\n    \r\n            _initAjax: function() {\r\n                var me = this,\r\n                    xhr = new XMLHttpRequest(),\r\n                    opts = this.options;\r\n    \r\n                if ( opts.withCredentials && !('withCredentials' in xhr) &&\r\n                        typeof XDomainRequest !== 'undefined' ) {\r\n                    xhr = new XDomainRequest();\r\n                }\r\n    \r\n                xhr.upload.onprogress = function( e ) {\r\n                    var percentage = 0;\r\n    \r\n                    if ( e.lengthComputable ) {\r\n                        percentage = e.loaded / e.total;\r\n                    }\r\n    \r\n                    return me.trigger( 'progress', percentage );\r\n                };\r\n    \r\n                xhr.onreadystatechange = function() {\r\n    \r\n                    if ( xhr.readyState !== 4 ) {\r\n                        return;\r\n                    }\r\n    \r\n                    xhr.upload.onprogress = noop;\r\n                    xhr.onreadystatechange = noop;\r\n                    me._xhr = null;\r\n                    me._status = xhr.status;\r\n    \r\n                    if ( xhr.status >= 200 && xhr.status < 300 ) {\r\n                        me._response = xhr.responseText;\r\n                        return me.trigger('load');\r\n                    } else if ( xhr.status >= 500 && xhr.status < 600 ) {\r\n                        me._response = xhr.responseText;\r\n                        return me.trigger( 'error', 'server' );\r\n                    }\r\n    \r\n    \r\n                    return me.trigger( 'error', me._status ? 'http' : 'abort' );\r\n                };\r\n    \r\n                me._xhr = xhr;\r\n                return xhr;\r\n            },\r\n    \r\n            _setRequestHeader: function( xhr, headers ) {\r\n                $.each( headers, function( key, val ) {\r\n                    xhr.setRequestHeader( key, val );\r\n                });\r\n            },\r\n    \r\n            _parseJson: function( str ) {\r\n                var json;\r\n    \r\n                try {\r\n                    json = JSON.parse( str );\r\n                } catch ( ex ) {\r\n                    json = {};\r\n                }\r\n    \r\n                return json;\r\n            }\r\n        });\r\n    });\r\n    define('webuploader',[\r\n        'base',\r\n        'widgets/filepicker',\r\n        'widgets/image',\r\n        'widgets/queue',\r\n        'widgets/runtime',\r\n        'widgets/upload',\r\n        'runtime/html5/blob',\r\n        'runtime/html5/filepicker',\r\n        'runtime/html5/imagemeta/exif',\r\n        'runtime/html5/image',\r\n        'runtime/html5/androidpatch',\r\n        'runtime/html5/transport'\r\n    ], function( Base ) {\r\n        return Base;\r\n    });\r\n    return require('webuploader');\r\n});\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/webuploader/webuploader.flashonly.js",
    "content": "/*! WebUploader 0.1.2 */\r\n\r\n\r\n/**\r\n * @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。\r\n *\r\n * AMD API 内部的简单不完全实现，请忽略。只有当WebUploader被合并成一个文件的时候才会引入。\r\n */\r\n(function( root, factory ) {\r\n    var modules = {},\r\n\r\n        // 内部require, 简单不完全实现。\r\n        // https://github.com/amdjs/amdjs-api/wiki/require\r\n        _require = function( deps, callback ) {\r\n            var args, len, i;\r\n\r\n            // 如果deps不是数组，则直接返回指定module\r\n            if ( typeof deps === 'string' ) {\r\n                return getModule( deps );\r\n            } else {\r\n                args = [];\r\n                for( len = deps.length, i = 0; i < len; i++ ) {\r\n                    args.push( getModule( deps[ i ] ) );\r\n                }\r\n\r\n                return callback.apply( null, args );\r\n            }\r\n        },\r\n\r\n        // 内部define，暂时不支持不指定id.\r\n        _define = function( id, deps, factory ) {\r\n            if ( arguments.length === 2 ) {\r\n                factory = deps;\r\n                deps = null;\r\n            }\r\n\r\n            _require( deps || [], function() {\r\n                setModule( id, factory, arguments );\r\n            });\r\n        },\r\n\r\n        // 设置module, 兼容CommonJs写法。\r\n        setModule = function( id, factory, args ) {\r\n            var module = {\r\n                    exports: factory\r\n                },\r\n                returned;\r\n\r\n            if ( typeof factory === 'function' ) {\r\n                args.length || (args = [ _require, module.exports, module ]);\r\n                returned = factory.apply( null, args );\r\n                returned !== undefined && (module.exports = returned);\r\n            }\r\n\r\n            modules[ id ] = module.exports;\r\n        },\r\n\r\n        // 根据id获取module\r\n        getModule = function( id ) {\r\n            var module = modules[ id ] || root[ id ];\r\n\r\n            if ( !module ) {\r\n                throw new Error( '`' + id + '` is undefined' );\r\n            }\r\n\r\n            return module;\r\n        },\r\n\r\n        // 将所有modules，将路径ids装换成对象。\r\n        exportsTo = function( obj ) {\r\n            var key, host, parts, part, last, ucFirst;\r\n\r\n            // make the first character upper case.\r\n            ucFirst = function( str ) {\r\n                return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 ));\r\n            };\r\n\r\n            for ( key in modules ) {\r\n                host = obj;\r\n\r\n                if ( !modules.hasOwnProperty( key ) ) {\r\n                    continue;\r\n                }\r\n\r\n                parts = key.split('/');\r\n                last = ucFirst( parts.pop() );\r\n\r\n                while( (part = ucFirst( parts.shift() )) ) {\r\n                    host[ part ] = host[ part ] || {};\r\n                    host = host[ part ];\r\n                }\r\n\r\n                host[ last ] = modules[ key ];\r\n            }\r\n        },\r\n\r\n        exports = factory( root, _define, _require ),\r\n        origin;\r\n\r\n    // exports every module.\r\n    exportsTo( exports );\r\n\r\n    if ( typeof module === 'object' && typeof module.exports === 'object' ) {\r\n\r\n        // For CommonJS and CommonJS-like environments where a proper window is present,\r\n        module.exports = exports;\r\n    } else if ( typeof define === 'function' && define.amd ) {\r\n\r\n        // Allow using this built library as an AMD module\r\n        // in another project. That other project will only\r\n        // see this AMD call, not the internal modules in\r\n        // the closure below.\r\n        define([], exports );\r\n    } else {\r\n\r\n        // Browser globals case. Just assign the\r\n        // result to a property on the global.\r\n        origin = root.WebUploader;\r\n        root.WebUploader = exports;\r\n        root.WebUploader.noConflict = function() {\r\n            root.WebUploader = origin;\r\n        };\r\n    }\r\n})( this, function( window, define, require ) {\r\n\r\n\r\n    /**\r\n     * @fileOverview jQuery or Zepto\r\n     */\r\n    define('dollar-third',[],function() {\r\n        return window.jQuery || window.Zepto;\r\n    });\r\n    /**\r\n     * @fileOverview Dom 操作相关\r\n     */\r\n    define('dollar',[\r\n        'dollar-third'\r\n    ], function( _ ) {\r\n        return _;\r\n    });\r\n    /**\r\n     * @fileOverview 使用jQuery的Promise\r\n     */\r\n    define('promise-third',[\r\n        'dollar'\r\n    ], function( $ ) {\r\n        return {\r\n            Deferred: $.Deferred,\r\n            when: $.when,\r\n    \r\n            isPromise: function( anything ) {\r\n                return anything && typeof anything.then === 'function';\r\n            }\r\n        };\r\n    });\r\n    /**\r\n     * @fileOverview Promise/A+\r\n     */\r\n    define('promise',[\r\n        'promise-third'\r\n    ], function( _ ) {\r\n        return _;\r\n    });\r\n    /**\r\n     * @fileOverview 基础类方法。\r\n     */\r\n    \r\n    /**\r\n     * Web Uploader内部类的详细说明，以下提及的功能类，都可以在`WebUploader`这个变量中访问到。\r\n     *\r\n     * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id.\r\n     * 默认module id该文件的路径，而此路径将会转化成名字空间存放在WebUploader中。如：\r\n     *\r\n     * * module `base`：WebUploader.Base\r\n     * * module `file`: WebUploader.File\r\n     * * module `lib/dnd`: WebUploader.Lib.Dnd\r\n     * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd\r\n     *\r\n     *\r\n     * 以下文档将可能省略`WebUploader`前缀。\r\n     * @module WebUploader\r\n     * @title WebUploader API文档\r\n     */\r\n    define('base',[\r\n        'dollar',\r\n        'promise'\r\n    ], function( $, promise ) {\r\n    \r\n        var noop = function() {},\r\n            call = Function.call;\r\n    \r\n        // http://jsperf.com/uncurrythis\r\n        // 反科里化\r\n        function uncurryThis( fn ) {\r\n            return function() {\r\n                return call.apply( fn, arguments );\r\n            };\r\n        }\r\n    \r\n        function bindFn( fn, context ) {\r\n            return function() {\r\n                return fn.apply( context, arguments );\r\n            };\r\n        }\r\n    \r\n        function createObject( proto ) {\r\n            var f;\r\n    \r\n            if ( Object.create ) {\r\n                return Object.create( proto );\r\n            } else {\r\n                f = function() {};\r\n                f.prototype = proto;\r\n                return new f();\r\n            }\r\n        }\r\n    \r\n    \r\n        /**\r\n         * 基础类，提供一些简单常用的方法。\r\n         * @class Base\r\n         */\r\n        return {\r\n    \r\n            /**\r\n             * @property {String} version 当前版本号。\r\n             */\r\n            version: '0.1.2',\r\n    \r\n            /**\r\n             * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。\r\n             */\r\n            $: $,\r\n    \r\n            Deferred: promise.Deferred,\r\n    \r\n            isPromise: promise.isPromise,\r\n    \r\n            when: promise.when,\r\n    \r\n            /**\r\n             * @description  简单的浏览器检查结果。\r\n             *\r\n             * * `webkit`  webkit版本号，如果浏览器为非webkit内核，此属性为`undefined`。\r\n             * * `chrome`  chrome浏览器版本号，如果浏览器为chrome，此属性为`undefined`。\r\n             * * `ie`  ie浏览器版本号，如果浏览器为非ie，此属性为`undefined`。**暂不支持ie10+**\r\n             * * `firefox`  firefox浏览器版本号，如果浏览器为非firefox，此属性为`undefined`。\r\n             * * `safari`  safari浏览器版本号，如果浏览器为非safari，此属性为`undefined`。\r\n             * * `opera`  opera浏览器版本号，如果浏览器为非opera，此属性为`undefined`。\r\n             *\r\n             * @property {Object} [browser]\r\n             */\r\n            browser: (function( ua ) {\r\n                var ret = {},\r\n                    webkit = ua.match( /WebKit\\/([\\d.]+)/ ),\r\n                    chrome = ua.match( /Chrome\\/([\\d.]+)/ ) ||\r\n                        ua.match( /CriOS\\/([\\d.]+)/ ),\r\n    \r\n                    ie = ua.match( /MSIE\\s([\\d\\.]+)/ ) ||\r\n                        ua.match(/(?:trident)(?:.*rv:([\\w.]+))?/i),\r\n                    firefox = ua.match( /Firefox\\/([\\d.]+)/ ),\r\n                    safari = ua.match( /Safari\\/([\\d.]+)/ ),\r\n                    opera = ua.match( /OPR\\/([\\d.]+)/ );\r\n    \r\n                webkit && (ret.webkit = parseFloat( webkit[ 1 ] ));\r\n                chrome && (ret.chrome = parseFloat( chrome[ 1 ] ));\r\n                ie && (ret.ie = parseFloat( ie[ 1 ] ));\r\n                firefox && (ret.firefox = parseFloat( firefox[ 1 ] ));\r\n                safari && (ret.safari = parseFloat( safari[ 1 ] ));\r\n                opera && (ret.opera = parseFloat( opera[ 1 ] ));\r\n    \r\n                return ret;\r\n            })( navigator.userAgent ),\r\n    \r\n            /**\r\n             * @description  操作系统检查结果。\r\n             *\r\n             * * `android`  如果在android浏览器环境下，此值为对应的android版本号，否则为`undefined`。\r\n             * * `ios` 如果在ios浏览器环境下，此值为对应的ios版本号，否则为`undefined`。\r\n             * @property {Object} [os]\r\n             */\r\n            os: (function( ua ) {\r\n                var ret = {},\r\n    \r\n                    // osx = !!ua.match( /\\(Macintosh\\; Intel / ),\r\n                    android = ua.match( /(?:Android);?[\\s\\/]+([\\d.]+)?/ ),\r\n                    ios = ua.match( /(?:iPad|iPod|iPhone).*OS\\s([\\d_]+)/ );\r\n    \r\n                // osx && (ret.osx = true);\r\n                android && (ret.android = parseFloat( android[ 1 ] ));\r\n                ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) ));\r\n    \r\n                return ret;\r\n            })( navigator.userAgent ),\r\n    \r\n            /**\r\n             * 实现类与类之间的继承。\r\n             * @method inherits\r\n             * @grammar Base.inherits( super ) => child\r\n             * @grammar Base.inherits( super, protos ) => child\r\n             * @grammar Base.inherits( super, protos, statics ) => child\r\n             * @param  {Class} super 父类\r\n             * @param  {Object | Function} [protos] 子类或者对象。如果对象中包含constructor，子类将是用此属性值。\r\n             * @param  {Function} [protos.constructor] 子类构造器，不指定的话将创建个临时的直接执行父类构造器的方法。\r\n             * @param  {Object} [statics] 静态属性或方法。\r\n             * @return {Class} 返回子类。\r\n             * @example\r\n             * function Person() {\r\n             *     console.log( 'Super' );\r\n             * }\r\n             * Person.prototype.hello = function() {\r\n             *     console.log( 'hello' );\r\n             * };\r\n             *\r\n             * var Manager = Base.inherits( Person, {\r\n             *     world: function() {\r\n             *         console.log( 'World' );\r\n             *     }\r\n             * });\r\n             *\r\n             * // 因为没有指定构造器，父类的构造器将会执行。\r\n             * var instance = new Manager();    // => Super\r\n             *\r\n             * // 继承子父类的方法\r\n             * instance.hello();    // => hello\r\n             * instance.world();    // => World\r\n             *\r\n             * // 子类的__super__属性指向父类\r\n             * console.log( Manager.__super__ === Person );    // => true\r\n             */\r\n            inherits: function( Super, protos, staticProtos ) {\r\n                var child;\r\n    \r\n                if ( typeof protos === 'function' ) {\r\n                    child = protos;\r\n                    protos = null;\r\n                } else if ( protos && protos.hasOwnProperty('constructor') ) {\r\n                    child = protos.constructor;\r\n                } else {\r\n                    child = function() {\r\n                        return Super.apply( this, arguments );\r\n                    };\r\n                }\r\n    \r\n                // 复制静态方法\r\n                $.extend( true, child, Super, staticProtos || {} );\r\n    \r\n                /* jshint camelcase: false */\r\n    \r\n                // 让子类的__super__属性指向父类。\r\n                child.__super__ = Super.prototype;\r\n    \r\n                // 构建原型，添加原型方法或属性。\r\n                // 暂时用Object.create实现。\r\n                child.prototype = createObject( Super.prototype );\r\n                protos && $.extend( true, child.prototype, protos );\r\n    \r\n                return child;\r\n            },\r\n    \r\n            /**\r\n             * 一个不做任何事情的方法。可以用来赋值给默认的callback.\r\n             * @method noop\r\n             */\r\n            noop: noop,\r\n    \r\n            /**\r\n             * 返回一个新的方法，此方法将已指定的`context`来执行。\r\n             * @grammar Base.bindFn( fn, context ) => Function\r\n             * @method bindFn\r\n             * @example\r\n             * var doSomething = function() {\r\n             *         console.log( this.name );\r\n             *     },\r\n             *     obj = {\r\n             *         name: 'Object Name'\r\n             *     },\r\n             *     aliasFn = Base.bind( doSomething, obj );\r\n             *\r\n             *  aliasFn();    // => Object Name\r\n             *\r\n             */\r\n            bindFn: bindFn,\r\n    \r\n            /**\r\n             * 引用Console.log如果存在的话，否则引用一个[空函数loop](#WebUploader:Base.log)。\r\n             * @grammar Base.log( args... ) => undefined\r\n             * @method log\r\n             */\r\n            log: (function() {\r\n                if ( window.console ) {\r\n                    return bindFn( console.log, console );\r\n                }\r\n                return noop;\r\n            })(),\r\n    \r\n            nextTick: (function() {\r\n    \r\n                return function( cb ) {\r\n                    setTimeout( cb, 1 );\r\n                };\r\n    \r\n                // @bug 当浏览器不在当前窗口时就停了。\r\n                // var next = window.requestAnimationFrame ||\r\n                //     window.webkitRequestAnimationFrame ||\r\n                //     window.mozRequestAnimationFrame ||\r\n                //     function( cb ) {\r\n                //         window.setTimeout( cb, 1000 / 60 );\r\n                //     };\r\n    \r\n                // // fix: Uncaught TypeError: Illegal invocation\r\n                // return bindFn( next, window );\r\n            })(),\r\n    \r\n            /**\r\n             * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。\r\n             * 将用来将非数组对象转化成数组对象。\r\n             * @grammar Base.slice( target, start[, end] ) => Array\r\n             * @method slice\r\n             * @example\r\n             * function doSomthing() {\r\n             *     var args = Base.slice( arguments, 1 );\r\n             *     console.log( args );\r\n             * }\r\n             *\r\n             * doSomthing( 'ignored', 'arg2', 'arg3' );    // => Array [\"arg2\", \"arg3\"]\r\n             */\r\n            slice: uncurryThis( [].slice ),\r\n    \r\n            /**\r\n             * 生成唯一的ID\r\n             * @method guid\r\n             * @grammar Base.guid() => String\r\n             * @grammar Base.guid( prefx ) => String\r\n             */\r\n            guid: (function() {\r\n                var counter = 0;\r\n    \r\n                return function( prefix ) {\r\n                    var guid = (+new Date()).toString( 32 ),\r\n                        i = 0;\r\n    \r\n                    for ( ; i < 5; i++ ) {\r\n                        guid += Math.floor( Math.random() * 65535 ).toString( 32 );\r\n                    }\r\n    \r\n                    return (prefix || 'wu_') + guid + (counter++).toString( 32 );\r\n                };\r\n            })(),\r\n    \r\n            /**\r\n             * 格式化文件大小, 输出成带单位的字符串\r\n             * @method formatSize\r\n             * @grammar Base.formatSize( size ) => String\r\n             * @grammar Base.formatSize( size, pointLength ) => String\r\n             * @grammar Base.formatSize( size, pointLength, units ) => String\r\n             * @param {Number} size 文件大小\r\n             * @param {Number} [pointLength=2] 精确到的小数点数。\r\n             * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节，到千字节，一直往上指定。如果单位数组里面只指定了到了K(千字节)，同时文件大小大于M, 此方法的输出将还是显示成多少K.\r\n             * @example\r\n             * console.log( Base.formatSize( 100 ) );    // => 100B\r\n             * console.log( Base.formatSize( 1024 ) );    // => 1.00K\r\n             * console.log( Base.formatSize( 1024, 0 ) );    // => 1K\r\n             * console.log( Base.formatSize( 1024 * 1024 ) );    // => 1.00M\r\n             * console.log( Base.formatSize( 1024 * 1024 * 1024 ) );    // => 1.00G\r\n             * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) );    // => 1024MB\r\n             */\r\n            formatSize: function( size, pointLength, units ) {\r\n                var unit;\r\n    \r\n                units = units || [ 'B', 'K', 'M', 'G', 'TB' ];\r\n    \r\n                while ( (unit = units.shift()) && size > 1024 ) {\r\n                    size = size / 1024;\r\n                }\r\n    \r\n                return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) +\r\n                        unit;\r\n            }\r\n        };\r\n    });\r\n    /**\r\n     * 事件处理类，可以独立使用，也可以扩展给对象使用。\r\n     * @fileOverview Mediator\r\n     */\r\n    define('mediator',[\r\n        'base'\r\n    ], function( Base ) {\r\n        var $ = Base.$,\r\n            slice = [].slice,\r\n            separator = /\\s+/,\r\n            protos;\r\n    \r\n        // 根据条件过滤出事件handlers.\r\n        function findHandlers( arr, name, callback, context ) {\r\n            return $.grep( arr, function( handler ) {\r\n                return handler &&\r\n                        (!name || handler.e === name) &&\r\n                        (!callback || handler.cb === callback ||\r\n                        handler.cb._cb === callback) &&\r\n                        (!context || handler.ctx === context);\r\n            });\r\n        }\r\n    \r\n        function eachEvent( events, callback, iterator ) {\r\n            // 不支持对象，只支持多个event用空格隔开\r\n            $.each( (events || '').split( separator ), function( _, key ) {\r\n                iterator( key, callback );\r\n            });\r\n        }\r\n    \r\n        function triggerHanders( events, args ) {\r\n            var stoped = false,\r\n                i = -1,\r\n                len = events.length,\r\n                handler;\r\n    \r\n            while ( ++i < len ) {\r\n                handler = events[ i ];\r\n    \r\n                if ( handler.cb.apply( handler.ctx2, args ) === false ) {\r\n                    stoped = true;\r\n                    break;\r\n                }\r\n            }\r\n    \r\n            return !stoped;\r\n        }\r\n    \r\n        protos = {\r\n    \r\n            /**\r\n             * 绑定事件。\r\n             *\r\n             * `callback`方法在执行时，arguments将会来源于trigger的时候携带的参数。如\r\n             * ```javascript\r\n             * var obj = {};\r\n             *\r\n             * // 使得obj有事件行为\r\n             * Mediator.installTo( obj );\r\n             *\r\n             * obj.on( 'testa', function( arg1, arg2 ) {\r\n             *     console.log( arg1, arg2 ); // => 'arg1', 'arg2'\r\n             * });\r\n             *\r\n             * obj.trigger( 'testa', 'arg1', 'arg2' );\r\n             * ```\r\n             *\r\n             * 如果`callback`中，某一个方法`return false`了，则后续的其他`callback`都不会被执行到。\r\n             * 切会影响到`trigger`方法的返回值，为`false`。\r\n             *\r\n             * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处，\r\n             * 就是第一个参数为`type`，记录当前是什么事件在触发。此类`callback`的优先级比脚低，会再正常`callback`执行完后触发。\r\n             * ```javascript\r\n             * obj.on( 'all', function( type, arg1, arg2 ) {\r\n             *     console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2'\r\n             * });\r\n             * ```\r\n             *\r\n             * @method on\r\n             * @grammar on( name, callback[, context] ) => self\r\n             * @param  {String}   name     事件名，支持多个事件用空格隔开\r\n             * @param  {Function} callback 事件处理器\r\n             * @param  {Object}   [context]  事件处理器的上下文。\r\n             * @return {self} 返回自身，方便链式\r\n             * @chainable\r\n             * @class Mediator\r\n             */\r\n            on: function( name, callback, context ) {\r\n                var me = this,\r\n                    set;\r\n    \r\n                if ( !callback ) {\r\n                    return this;\r\n                }\r\n    \r\n                set = this._events || (this._events = []);\r\n    \r\n                eachEvent( name, callback, function( name, callback ) {\r\n                    var handler = { e: name };\r\n    \r\n                    handler.cb = callback;\r\n                    handler.ctx = context;\r\n                    handler.ctx2 = context || me;\r\n                    handler.id = set.length;\r\n    \r\n                    set.push( handler );\r\n                });\r\n    \r\n                return this;\r\n            },\r\n    \r\n            /**\r\n             * 绑定事件，且当handler执行完后，自动解除绑定。\r\n             * @method once\r\n             * @grammar once( name, callback[, context] ) => self\r\n             * @param  {String}   name     事件名\r\n             * @param  {Function} callback 事件处理器\r\n             * @param  {Object}   [context]  事件处理器的上下文。\r\n             * @return {self} 返回自身，方便链式\r\n             * @chainable\r\n             */\r\n            once: function( name, callback, context ) {\r\n                var me = this;\r\n    \r\n                if ( !callback ) {\r\n                    return me;\r\n                }\r\n    \r\n                eachEvent( name, callback, function( name, callback ) {\r\n                    var once = function() {\r\n                            me.off( name, once );\r\n                            return callback.apply( context || me, arguments );\r\n                        };\r\n    \r\n                    once._cb = callback;\r\n                    me.on( name, once, context );\r\n                });\r\n    \r\n                return me;\r\n            },\r\n    \r\n            /**\r\n             * 解除事件绑定\r\n             * @method off\r\n             * @grammar off( [name[, callback[, context] ] ] ) => self\r\n             * @param  {String}   [name]     事件名\r\n             * @param  {Function} [callback] 事件处理器\r\n             * @param  {Object}   [context]  事件处理器的上下文。\r\n             * @return {self} 返回自身，方便链式\r\n             * @chainable\r\n             */\r\n            off: function( name, cb, ctx ) {\r\n                var events = this._events;\r\n    \r\n                if ( !events ) {\r\n                    return this;\r\n                }\r\n    \r\n                if ( !name && !cb && !ctx ) {\r\n                    this._events = [];\r\n                    return this;\r\n                }\r\n    \r\n                eachEvent( name, cb, function( name, cb ) {\r\n                    $.each( findHandlers( events, name, cb, ctx ), function() {\r\n                        delete events[ this.id ];\r\n                    });\r\n                });\r\n    \r\n                return this;\r\n            },\r\n    \r\n            /**\r\n             * 触发事件\r\n             * @method trigger\r\n             * @grammar trigger( name[, args...] ) => self\r\n             * @param  {String}   type     事件名\r\n             * @param  {*} [...] 任意参数\r\n             * @return {Boolean} 如果handler中return false了，则返回false, 否则返回true\r\n             */\r\n            trigger: function( type ) {\r\n                var args, events, allEvents;\r\n    \r\n                if ( !this._events || !type ) {\r\n                    return this;\r\n                }\r\n    \r\n                args = slice.call( arguments, 1 );\r\n                events = findHandlers( this._events, type );\r\n                allEvents = findHandlers( this._events, 'all' );\r\n    \r\n                return triggerHanders( events, args ) &&\r\n                        triggerHanders( allEvents, arguments );\r\n            }\r\n        };\r\n    \r\n        /**\r\n         * 中介者，它本身是个单例，但可以通过[installTo](#WebUploader:Mediator:installTo)方法，使任何对象具备事件行为。\r\n         * 主要目的是负责模块与模块之间的合作，降低耦合度。\r\n         *\r\n         * @class Mediator\r\n         */\r\n        return $.extend({\r\n    \r\n            /**\r\n             * 可以通过这个接口，使任何对象具备事件功能。\r\n             * @method installTo\r\n             * @param  {Object} obj 需要具备事件行为的对象。\r\n             * @return {Object} 返回obj.\r\n             */\r\n            installTo: function( obj ) {\r\n                return $.extend( obj, protos );\r\n            }\r\n    \r\n        }, protos );\r\n    });\r\n    /**\r\n     * @fileOverview Uploader上传类\r\n     */\r\n    define('uploader',[\r\n        'base',\r\n        'mediator'\r\n    ], function( Base, Mediator ) {\r\n    \r\n        var $ = Base.$;\r\n    \r\n        /**\r\n         * 上传入口类。\r\n         * @class Uploader\r\n         * @constructor\r\n         * @grammar new Uploader( opts ) => Uploader\r\n         * @example\r\n         * var uploader = WebUploader.Uploader({\r\n         *     swf: 'path_of_swf/Uploader.swf',\r\n         *\r\n         *     // 开起分片上传。\r\n         *     chunked: true\r\n         * });\r\n         */\r\n        function Uploader( opts ) {\r\n            this.options = $.extend( true, {}, Uploader.options, opts );\r\n            this._init( this.options );\r\n        }\r\n    \r\n        // default Options\r\n        // widgets中有相应扩展\r\n        Uploader.options = {};\r\n        Mediator.installTo( Uploader.prototype );\r\n    \r\n        // 批量添加纯命令式方法。\r\n        $.each({\r\n            upload: 'start-upload',\r\n            stop: 'stop-upload',\r\n            getFile: 'get-file',\r\n            getFiles: 'get-files',\r\n            addFile: 'add-file',\r\n            addFiles: 'add-file',\r\n            sort: 'sort-files',\r\n            removeFile: 'remove-file',\r\n            skipFile: 'skip-file',\r\n            retry: 'retry',\r\n            isInProgress: 'is-in-progress',\r\n            makeThumb: 'make-thumb',\r\n            getDimension: 'get-dimension',\r\n            addButton: 'add-btn',\r\n            getRuntimeType: 'get-runtime-type',\r\n            refresh: 'refresh',\r\n            disable: 'disable',\r\n            enable: 'enable',\r\n            reset: 'reset'\r\n        }, function( fn, command ) {\r\n            Uploader.prototype[ fn ] = function() {\r\n                return this.request( command, arguments );\r\n            };\r\n        });\r\n    \r\n        $.extend( Uploader.prototype, {\r\n            state: 'pending',\r\n    \r\n            _init: function( opts ) {\r\n                var me = this;\r\n    \r\n                me.request( 'init', opts, function() {\r\n                    me.state = 'ready';\r\n                    me.trigger('ready');\r\n                });\r\n            },\r\n    \r\n            /**\r\n             * 获取或者设置Uploader配置项。\r\n             * @method option\r\n             * @grammar option( key ) => *\r\n             * @grammar option( key, val ) => self\r\n             * @example\r\n             *\r\n             * // 初始状态图片上传前不会压缩\r\n             * var uploader = new WebUploader.Uploader({\r\n             *     resize: null;\r\n             * });\r\n             *\r\n             * // 修改后图片上传前，尝试将图片压缩到1600 * 1600\r\n             * uploader.options( 'resize', {\r\n             *     width: 1600,\r\n             *     height: 1600\r\n             * });\r\n             */\r\n            option: function( key, val ) {\r\n                var opts = this.options;\r\n    \r\n                // setter\r\n                if ( arguments.length > 1 ) {\r\n    \r\n                    if ( $.isPlainObject( val ) &&\r\n                            $.isPlainObject( opts[ key ] ) ) {\r\n                        $.extend( opts[ key ], val );\r\n                    } else {\r\n                        opts[ key ] = val;\r\n                    }\r\n    \r\n                } else {    // getter\r\n                    return key ? opts[ key ] : opts;\r\n                }\r\n            },\r\n    \r\n            /**\r\n             * 获取文件统计信息。返回一个包含一下信息的对象。\r\n             * * `successNum` 上传成功的文件数\r\n             * * `uploadFailNum` 上传失败的文件数\r\n             * * `cancelNum` 被删除的文件数\r\n             * * `invalidNum` 无效的文件数\r\n             * * `queueNum` 还在队列中的文件数\r\n             * @method getStats\r\n             * @grammar getStats() => Object\r\n             */\r\n            getStats: function() {\r\n                // return this._mgr.getStats.apply( this._mgr, arguments );\r\n                var stats = this.request('get-stats');\r\n    \r\n                return {\r\n                    successNum: stats.numOfSuccess,\r\n    \r\n                    // who care?\r\n                    // queueFailNum: 0,\r\n                    cancelNum: stats.numOfCancel,\r\n                    invalidNum: stats.numOfInvalid,\r\n                    uploadFailNum: stats.numOfUploadFailed,\r\n                    queueNum: stats.numOfQueue\r\n                };\r\n            },\r\n    \r\n            // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器\r\n            trigger: function( type/*, args...*/ ) {\r\n                var args = [].slice.call( arguments, 1 ),\r\n                    opts = this.options,\r\n                    name = 'on' + type.substring( 0, 1 ).toUpperCase() +\r\n                        type.substring( 1 );\r\n    \r\n                if (\r\n                        // 调用通过on方法注册的handler.\r\n                        Mediator.trigger.apply( this, arguments ) === false ||\r\n    \r\n                        // 调用opts.onEvent\r\n                        $.isFunction( opts[ name ] ) &&\r\n                        opts[ name ].apply( this, args ) === false ||\r\n    \r\n                        // 调用this.onEvent\r\n                        $.isFunction( this[ name ] ) &&\r\n                        this[ name ].apply( this, args ) === false ||\r\n    \r\n                        // 广播所有uploader的事件。\r\n                        Mediator.trigger.apply( Mediator,\r\n                        [ this, type ].concat( args ) ) === false ) {\r\n    \r\n                    return false;\r\n                }\r\n    \r\n                return true;\r\n            },\r\n    \r\n            // widgets/widget.js将补充此方法的详细文档。\r\n            request: Base.noop\r\n        });\r\n    \r\n        /**\r\n         * 创建Uploader实例，等同于new Uploader( opts );\r\n         * @method create\r\n         * @class Base\r\n         * @static\r\n         * @grammar Base.create( opts ) => Uploader\r\n         */\r\n        Base.create = Uploader.create = function( opts ) {\r\n            return new Uploader( opts );\r\n        };\r\n    \r\n        // 暴露Uploader，可以通过它来扩展业务逻辑。\r\n        Base.Uploader = Uploader;\r\n    \r\n        return Uploader;\r\n    });\r\n    /**\r\n     * @fileOverview Runtime管理器，负责Runtime的选择, 连接\r\n     */\r\n    define('runtime/runtime',[\r\n        'base',\r\n        'mediator'\r\n    ], function( Base, Mediator ) {\r\n    \r\n        var $ = Base.$,\r\n            factories = {},\r\n    \r\n            // 获取对象的第一个key\r\n            getFirstKey = function( obj ) {\r\n                for ( var key in obj ) {\r\n                    if ( obj.hasOwnProperty( key ) ) {\r\n                        return key;\r\n                    }\r\n                }\r\n                return null;\r\n            };\r\n    \r\n        // 接口类。\r\n        function Runtime( options ) {\r\n            this.options = $.extend({\r\n                container: document.body\r\n            }, options );\r\n            this.uid = Base.guid('rt_');\r\n        }\r\n    \r\n        $.extend( Runtime.prototype, {\r\n    \r\n            getContainer: function() {\r\n                var opts = this.options,\r\n                    parent, container;\r\n    \r\n                if ( this._container ) {\r\n                    return this._container;\r\n                }\r\n    \r\n                parent = $( opts.container || document.body );\r\n                container = $( document.createElement('div') );\r\n    \r\n                container.attr( 'id', 'rt_' + this.uid );\r\n                container.css({\r\n                    position: 'absolute',\r\n                    top: '0px',\r\n                    left: '0px',\r\n                    width: '1px',\r\n                    height: '1px',\r\n                    overflow: 'hidden'\r\n                });\r\n    \r\n                parent.append( container );\r\n                parent.addClass('webuploader-container');\r\n                this._container = container;\r\n                return container;\r\n            },\r\n    \r\n            init: Base.noop,\r\n            exec: Base.noop,\r\n    \r\n            destroy: function() {\r\n                if ( this._container ) {\r\n                    this._container.parentNode.removeChild( this.__container );\r\n                }\r\n    \r\n                this.off();\r\n            }\r\n        });\r\n    \r\n        Runtime.orders = 'html5,flash';\r\n    \r\n    \r\n        /**\r\n         * 添加Runtime实现。\r\n         * @param {String} type    类型\r\n         * @param {Runtime} factory 具体Runtime实现。\r\n         */\r\n        Runtime.addRuntime = function( type, factory ) {\r\n            factories[ type ] = factory;\r\n        };\r\n    \r\n        Runtime.hasRuntime = function( type ) {\r\n            return !!(type ? factories[ type ] : getFirstKey( factories ));\r\n        };\r\n    \r\n        Runtime.create = function( opts, orders ) {\r\n            var type, runtime;\r\n    \r\n            orders = orders || Runtime.orders;\r\n            $.each( orders.split( /\\s*,\\s*/g ), function() {\r\n                if ( factories[ this ] ) {\r\n                    type = this;\r\n                    return false;\r\n                }\r\n            });\r\n    \r\n            type = type || getFirstKey( factories );\r\n    \r\n            if ( !type ) {\r\n                throw new Error('Runtime Error');\r\n            }\r\n    \r\n            runtime = new factories[ type ]( opts );\r\n            return runtime;\r\n        };\r\n    \r\n        Mediator.installTo( Runtime.prototype );\r\n        return Runtime;\r\n    });\r\n    \r\n    /**\r\n     * @fileOverview Runtime管理器，负责Runtime的选择, 连接\r\n     */\r\n    define('runtime/client',[\r\n        'base',\r\n        'mediator',\r\n        'runtime/runtime'\r\n    ], function( Base, Mediator, Runtime ) {\r\n    \r\n        var cache;\r\n    \r\n        cache = (function() {\r\n            var obj = {};\r\n    \r\n            return {\r\n                add: function( runtime ) {\r\n                    obj[ runtime.uid ] = runtime;\r\n                },\r\n    \r\n                get: function( ruid, standalone ) {\r\n                    var i;\r\n    \r\n                    if ( ruid ) {\r\n                        return obj[ ruid ];\r\n                    }\r\n    \r\n                    for ( i in obj ) {\r\n                        // 有些类型不能重用，比如filepicker.\r\n                        if ( standalone && obj[ i ].__standalone ) {\r\n                            continue;\r\n                        }\r\n    \r\n                        return obj[ i ];\r\n                    }\r\n    \r\n                    return null;\r\n                },\r\n    \r\n                remove: function( runtime ) {\r\n                    delete obj[ runtime.uid ];\r\n                }\r\n            };\r\n        })();\r\n    \r\n        function RuntimeClient( component, standalone ) {\r\n            var deferred = Base.Deferred(),\r\n                runtime;\r\n    \r\n            this.uid = Base.guid('client_');\r\n    \r\n            // 允许runtime没有初始化之前，注册一些方法在初始化后执行。\r\n            this.runtimeReady = function( cb ) {\r\n                return deferred.done( cb );\r\n            };\r\n    \r\n            this.connectRuntime = function( opts, cb ) {\r\n    \r\n                // already connected.\r\n                if ( runtime ) {\r\n                    throw new Error('already connected!');\r\n                }\r\n    \r\n                deferred.done( cb );\r\n    \r\n                if ( typeof opts === 'string' && cache.get( opts ) ) {\r\n                    runtime = cache.get( opts );\r\n                }\r\n    \r\n                // 像filePicker只能独立存在，不能公用。\r\n                runtime = runtime || cache.get( null, standalone );\r\n    \r\n                // 需要创建\r\n                if ( !runtime ) {\r\n                    runtime = Runtime.create( opts, opts.runtimeOrder );\r\n                    runtime.__promise = deferred.promise();\r\n                    runtime.once( 'ready', deferred.resolve );\r\n                    runtime.init();\r\n                    cache.add( runtime );\r\n                    runtime.__client = 1;\r\n                } else {\r\n                    // 来自cache\r\n                    Base.$.extend( runtime.options, opts );\r\n                    runtime.__promise.then( deferred.resolve );\r\n                    runtime.__client++;\r\n                }\r\n    \r\n                standalone && (runtime.__standalone = standalone);\r\n                return runtime;\r\n            };\r\n    \r\n            this.getRuntime = function() {\r\n                return runtime;\r\n            };\r\n    \r\n            this.disconnectRuntime = function() {\r\n                if ( !runtime ) {\r\n                    return;\r\n                }\r\n    \r\n                runtime.__client--;\r\n    \r\n                if ( runtime.__client <= 0 ) {\r\n                    cache.remove( runtime );\r\n                    delete runtime.__promise;\r\n                    runtime.destroy();\r\n                }\r\n    \r\n                runtime = null;\r\n            };\r\n    \r\n            this.exec = function() {\r\n                if ( !runtime ) {\r\n                    return;\r\n                }\r\n    \r\n                var args = Base.slice( arguments );\r\n                component && args.unshift( component );\r\n    \r\n                return runtime.exec.apply( this, args );\r\n            };\r\n    \r\n            this.getRuid = function() {\r\n                return runtime && runtime.uid;\r\n            };\r\n    \r\n            this.destroy = (function( destroy ) {\r\n                return function() {\r\n                    destroy && destroy.apply( this, arguments );\r\n                    this.trigger('destroy');\r\n                    this.off();\r\n                    this.exec('destroy');\r\n                    this.disconnectRuntime();\r\n                };\r\n            })( this.destroy );\r\n        }\r\n    \r\n        Mediator.installTo( RuntimeClient.prototype );\r\n        return RuntimeClient;\r\n    });\r\n    /**\r\n     * @fileOverview Blob\r\n     */\r\n    define('lib/blob',[\r\n        'base',\r\n        'runtime/client'\r\n    ], function( Base, RuntimeClient ) {\r\n    \r\n        function Blob( ruid, source ) {\r\n            var me = this;\r\n    \r\n            me.source = source;\r\n            me.ruid = ruid;\r\n    \r\n            RuntimeClient.call( me, 'Blob' );\r\n    \r\n            this.uid = source.uid || this.uid;\r\n            this.type = source.type || '';\r\n            this.size = source.size || 0;\r\n    \r\n            if ( ruid ) {\r\n                me.connectRuntime( ruid );\r\n            }\r\n        }\r\n    \r\n        Base.inherits( RuntimeClient, {\r\n            constructor: Blob,\r\n    \r\n            slice: function( start, end ) {\r\n                return this.exec( 'slice', start, end );\r\n            },\r\n    \r\n            getSource: function() {\r\n                return this.source;\r\n            }\r\n        });\r\n    \r\n        return Blob;\r\n    });\r\n    /**\r\n     * 为了统一化Flash的File和HTML5的File而存在。\r\n     * 以至于要调用Flash里面的File，也可以像调用HTML5版本的File一下。\r\n     * @fileOverview File\r\n     */\r\n    define('lib/file',[\r\n        'base',\r\n        'lib/blob'\r\n    ], function( Base, Blob ) {\r\n    \r\n        var uid = 1,\r\n            rExt = /\\.([^.]+)$/;\r\n    \r\n        function File( ruid, file ) {\r\n            var ext;\r\n    \r\n            Blob.apply( this, arguments );\r\n            this.name = file.name || ('untitled' + uid++);\r\n            ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : '';\r\n    \r\n            // todo 支持其他类型文件的转换。\r\n    \r\n            // 如果有mimetype, 但是文件名里面没有找出后缀规律\r\n            if ( !ext && this.type ) {\r\n                ext = /\\/(jpg|jpeg|png|gif|bmp)$/i.exec( this.type ) ?\r\n                        RegExp.$1.toLowerCase() : '';\r\n                this.name += '.' + ext;\r\n            }\r\n    \r\n            // 如果没有指定mimetype, 但是知道文件后缀。\r\n            if ( !this.type &&  ~'jpg,jpeg,png,gif,bmp'.indexOf( ext ) ) {\r\n                this.type = 'image/' + (ext === 'jpg' ? 'jpeg' : ext);\r\n            }\r\n    \r\n            this.ext = ext;\r\n            this.lastModifiedDate = file.lastModifiedDate ||\r\n                    (new Date()).toLocaleString();\r\n        }\r\n    \r\n        return Base.inherits( Blob, File );\r\n    });\r\n    \r\n    /**\r\n     * @fileOverview 错误信息\r\n     */\r\n    define('lib/filepicker',[\r\n        'base',\r\n        'runtime/client',\r\n        'lib/file'\r\n    ], function( Base, RuntimeClent, File ) {\r\n    \r\n        var $ = Base.$;\r\n    \r\n        function FilePicker( opts ) {\r\n            opts = this.options = $.extend({}, FilePicker.options, opts );\r\n    \r\n            opts.container = $( opts.id );\r\n    \r\n            if ( !opts.container.length ) {\r\n                throw new Error('按钮指定错误');\r\n            }\r\n    \r\n            opts.innerHTML = opts.innerHTML || opts.label ||\r\n                    opts.container.html() || '';\r\n    \r\n            opts.button = $( opts.button || document.createElement('div') );\r\n            opts.button.html( opts.innerHTML );\r\n            opts.container.html( opts.button );\r\n    \r\n            RuntimeClent.call( this, 'FilePicker', true );\r\n        }\r\n    \r\n        FilePicker.options = {\r\n            button: null,\r\n            container: null,\r\n            label: null,\r\n            innerHTML: null,\r\n            multiple: true,\r\n            accept: null,\r\n            name: 'file'\r\n        };\r\n    \r\n        Base.inherits( RuntimeClent, {\r\n            constructor: FilePicker,\r\n    \r\n            init: function() {\r\n                var me = this,\r\n                    opts = me.options,\r\n                    button = opts.button;\r\n    \r\n                button.addClass('webuploader-pick');\r\n    \r\n                me.on( 'all', function( type ) {\r\n                    var files;\r\n    \r\n                    switch ( type ) {\r\n                        case 'mouseenter':\r\n                            button.addClass('webuploader-pick-hover');\r\n                            break;\r\n    \r\n                        case 'mouseleave':\r\n                            button.removeClass('webuploader-pick-hover');\r\n                            break;\r\n    \r\n                        case 'change':\r\n                            files = me.exec('getFiles');\r\n                            me.trigger( 'select', $.map( files, function( file ) {\r\n                                file = new File( me.getRuid(), file );\r\n    \r\n                                // 记录来源。\r\n                                file._refer = opts.container;\r\n                                return file;\r\n                            }), opts.container );\r\n                            break;\r\n                    }\r\n                });\r\n    \r\n                me.connectRuntime( opts, function() {\r\n                    me.refresh();\r\n                    me.exec( 'init', opts );\r\n                    me.trigger('ready');\r\n                });\r\n    \r\n                $( window ).on( 'resize', function() {\r\n                    me.refresh();\r\n                });\r\n            },\r\n    \r\n            refresh: function() {\r\n                var shimContainer = this.getRuntime().getContainer(),\r\n                    button = this.options.button,\r\n                    width = button.outerWidth ?\r\n                            button.outerWidth() : button.width(),\r\n    \r\n                    height = button.outerHeight ?\r\n                            button.outerHeight() : button.height(),\r\n    \r\n                    pos = button.offset();\r\n    \r\n                width && height && shimContainer.css({\r\n                    bottom: 'auto',\r\n                    right: 'auto',\r\n                    width: width + 'px',\r\n                    height: height + 'px'\r\n                }).offset( pos );\r\n            },\r\n    \r\n            enable: function() {\r\n                var btn = this.options.button;\r\n    \r\n                btn.removeClass('webuploader-pick-disable');\r\n                this.refresh();\r\n            },\r\n    \r\n            disable: function() {\r\n                var btn = this.options.button;\r\n    \r\n                this.getRuntime().getContainer().css({\r\n                    top: '-99999px'\r\n                });\r\n    \r\n                btn.addClass('webuploader-pick-disable');\r\n            },\r\n    \r\n            destroy: function() {\r\n                if ( this.runtime ) {\r\n                    this.exec('destroy');\r\n                    this.disconnectRuntime();\r\n                }\r\n            }\r\n        });\r\n    \r\n        return FilePicker;\r\n    });\r\n    \r\n    /**\r\n     * @fileOverview 组件基类。\r\n     */\r\n    define('widgets/widget',[\r\n        'base',\r\n        'uploader'\r\n    ], function( Base, Uploader ) {\r\n    \r\n        var $ = Base.$,\r\n            _init = Uploader.prototype._init,\r\n            IGNORE = {},\r\n            widgetClass = [];\r\n    \r\n        function isArrayLike( obj ) {\r\n            if ( !obj ) {\r\n                return false;\r\n            }\r\n    \r\n            var length = obj.length,\r\n                type = $.type( obj );\r\n    \r\n            if ( obj.nodeType === 1 && length ) {\r\n                return true;\r\n            }\r\n    \r\n            return type === 'array' || type !== 'function' && type !== 'string' &&\r\n                    (length === 0 || typeof length === 'number' && length > 0 &&\r\n                    (length - 1) in obj);\r\n        }\r\n    \r\n        function Widget( uploader ) {\r\n            this.owner = uploader;\r\n            this.options = uploader.options;\r\n        }\r\n    \r\n        $.extend( Widget.prototype, {\r\n    \r\n            init: Base.noop,\r\n    \r\n            // 类Backbone的事件监听声明，监听uploader实例上的事件\r\n            // widget直接无法监听事件，事件只能通过uploader来传递\r\n            invoke: function( apiName, args ) {\r\n    \r\n                /*\r\n                    {\r\n                        'make-thumb': 'makeThumb'\r\n                    }\r\n                 */\r\n                var map = this.responseMap;\r\n    \r\n                // 如果无API响应声明则忽略\r\n                if ( !map || !(apiName in map) || !(map[ apiName ] in this) ||\r\n                        !$.isFunction( this[ map[ apiName ] ] ) ) {\r\n    \r\n                    return IGNORE;\r\n                }\r\n    \r\n                return this[ map[ apiName ] ].apply( this, args );\r\n    \r\n            },\r\n    \r\n            /**\r\n             * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。\r\n             * @method request\r\n             * @grammar request( command, args ) => * | Promise\r\n             * @grammar request( command, args, callback ) => Promise\r\n             * @for  Uploader\r\n             */\r\n            request: function() {\r\n                return this.owner.request.apply( this.owner, arguments );\r\n            }\r\n        });\r\n    \r\n        // 扩展Uploader.\r\n        $.extend( Uploader.prototype, {\r\n    \r\n            // 覆写_init用来初始化widgets\r\n            _init: function() {\r\n                var me = this,\r\n                    widgets = me._widgets = [];\r\n    \r\n                $.each( widgetClass, function( _, klass ) {\r\n                    widgets.push( new klass( me ) );\r\n                });\r\n    \r\n                return _init.apply( me, arguments );\r\n            },\r\n    \r\n            request: function( apiName, args, callback ) {\r\n                var i = 0,\r\n                    widgets = this._widgets,\r\n                    len = widgets.length,\r\n                    rlts = [],\r\n                    dfds = [],\r\n                    widget, rlt, promise, key;\r\n    \r\n                args = isArrayLike( args ) ? args : [ args ];\r\n    \r\n                for ( ; i < len; i++ ) {\r\n                    widget = widgets[ i ];\r\n                    rlt = widget.invoke( apiName, args );\r\n    \r\n                    if ( rlt !== IGNORE ) {\r\n    \r\n                        // Deferred对象\r\n                        if ( Base.isPromise( rlt ) ) {\r\n                            dfds.push( rlt );\r\n                        } else {\r\n                            rlts.push( rlt );\r\n                        }\r\n                    }\r\n                }\r\n    \r\n                // 如果有callback，则用异步方式。\r\n                if ( callback || dfds.length ) {\r\n                    promise = Base.when.apply( Base, dfds );\r\n                    key = promise.pipe ? 'pipe' : 'then';\r\n    \r\n                    // 很重要不能删除。删除了会死循环。\r\n                    // 保证执行顺序。让callback总是在下一个tick中执行。\r\n                    return promise[ key ](function() {\r\n                                var deferred = Base.Deferred(),\r\n                                    args = arguments;\r\n    \r\n                                setTimeout(function() {\r\n                                    deferred.resolve.apply( deferred, args );\r\n                                }, 1 );\r\n    \r\n                                return deferred.promise();\r\n                            })[ key ]( callback || Base.noop );\r\n                } else {\r\n                    return rlts[ 0 ];\r\n                }\r\n            }\r\n        });\r\n    \r\n        /**\r\n         * 添加组件\r\n         * @param  {object} widgetProto 组件原型，构造函数通过constructor属性定义\r\n         * @param  {object} responseMap API名称与函数实现的映射\r\n         * @example\r\n         *     Uploader.register( {\r\n         *         init: function( options ) {},\r\n         *         makeThumb: function() {}\r\n         *     }, {\r\n         *         'make-thumb': 'makeThumb'\r\n         *     } );\r\n         */\r\n        Uploader.register = Widget.register = function( responseMap, widgetProto ) {\r\n            var map = { init: 'init' },\r\n                klass;\r\n    \r\n            if ( arguments.length === 1 ) {\r\n                widgetProto = responseMap;\r\n                widgetProto.responseMap = map;\r\n            } else {\r\n                widgetProto.responseMap = $.extend( map, responseMap );\r\n            }\r\n    \r\n            klass = Base.inherits( Widget, widgetProto );\r\n            widgetClass.push( klass );\r\n    \r\n            return klass;\r\n        };\r\n    \r\n        return Widget;\r\n    });\r\n    /**\r\n     * @fileOverview 文件选择相关\r\n     */\r\n    define('widgets/filepicker',[\r\n        'base',\r\n        'uploader',\r\n        'lib/filepicker',\r\n        'widgets/widget'\r\n    ], function( Base, Uploader, FilePicker ) {\r\n        var $ = Base.$;\r\n    \r\n        $.extend( Uploader.options, {\r\n    \r\n            /**\r\n             * @property {Selector | Object} [pick=undefined]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 指定选择文件的按钮容器，不指定则不创建按钮。\r\n             *\r\n             * * `id` {Seletor} 指定选择文件的按钮容器，不指定则不创建按钮。\r\n             * * `label` {String} 请采用 `innerHTML` 代替\r\n             * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。\r\n             * * `multiple` {Boolean} 是否开起同时选择多个文件能力。\r\n             */\r\n            pick: null,\r\n    \r\n            /**\r\n             * @property {Arroy} [accept=null]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表，所以这里需要分开指定。\r\n             *\r\n             * * `title` {String} 文字描述\r\n             * * `extensions` {String} 允许的文件后缀，不带点，多个用逗号分割。\r\n             * * `mimeTypes` {String} 多个用逗号分割。\r\n             *\r\n             * 如：\r\n             *\r\n             * ```\r\n             * {\r\n             *     title: 'Images',\r\n             *     extensions: 'gif,jpg,jpeg,bmp,png',\r\n             *     mimeTypes: 'image/*'\r\n             * }\r\n             * ```\r\n             */\r\n            accept: null/*{\r\n                title: 'Images',\r\n                extensions: 'gif,jpg,jpeg,bmp,png',\r\n                mimeTypes: 'image/*'\r\n            }*/\r\n        });\r\n    \r\n        return Uploader.register({\r\n            'add-btn': 'addButton',\r\n            refresh: 'refresh',\r\n            disable: 'disable',\r\n            enable: 'enable'\r\n        }, {\r\n    \r\n            init: function( opts ) {\r\n                this.pickers = [];\r\n                return opts.pick && this.addButton( opts.pick );\r\n            },\r\n    \r\n            refresh: function() {\r\n                $.each( this.pickers, function() {\r\n                    this.refresh();\r\n                });\r\n            },\r\n    \r\n            /**\r\n             * @method addButton\r\n             * @for Uploader\r\n             * @grammar addButton( pick ) => Promise\r\n             * @description\r\n             * 添加文件选择按钮，如果一个按钮不够，需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。\r\n             * @example\r\n             * uploader.addButton({\r\n             *     id: '#btnContainer',\r\n             *     innerHTML: '选择文件'\r\n             * });\r\n             */\r\n            addButton: function( pick ) {\r\n                var me = this,\r\n                    opts = me.options,\r\n                    accept = opts.accept,\r\n                    options, picker, deferred;\r\n    \r\n                if ( !pick ) {\r\n                    return;\r\n                }\r\n    \r\n                deferred = Base.Deferred();\r\n                $.isPlainObject( pick ) || (pick = {\r\n                    id: pick\r\n                });\r\n    \r\n                options = $.extend({}, pick, {\r\n                    accept: $.isPlainObject( accept ) ? [ accept ] : accept,\r\n                    swf: opts.swf,\r\n                    runtimeOrder: opts.runtimeOrder\r\n                });\r\n    \r\n                picker = new FilePicker( options );\r\n    \r\n                picker.once( 'ready', deferred.resolve );\r\n                picker.on( 'select', function( files ) {\r\n                    me.owner.request( 'add-file', [ files ]);\r\n                });\r\n                picker.init();\r\n    \r\n                this.pickers.push( picker );\r\n    \r\n                return deferred.promise();\r\n            },\r\n    \r\n            disable: function() {\r\n                $.each( this.pickers, function() {\r\n                    this.disable();\r\n                });\r\n            },\r\n    \r\n            enable: function() {\r\n                $.each( this.pickers, function() {\r\n                    this.enable();\r\n                });\r\n            }\r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview Image\r\n     */\r\n    define('lib/image',[\r\n        'base',\r\n        'runtime/client',\r\n        'lib/blob'\r\n    ], function( Base, RuntimeClient, Blob ) {\r\n        var $ = Base.$;\r\n    \r\n        // 构造器。\r\n        function Image( opts ) {\r\n            this.options = $.extend({}, Image.options, opts );\r\n            RuntimeClient.call( this, 'Image' );\r\n    \r\n            this.on( 'load', function() {\r\n                this._info = this.exec('info');\r\n                this._meta = this.exec('meta');\r\n            });\r\n        }\r\n    \r\n        // 默认选项。\r\n        Image.options = {\r\n    \r\n            // 默认的图片处理质量\r\n            quality: 90,\r\n    \r\n            // 是否裁剪\r\n            crop: false,\r\n    \r\n            // 是否保留头部信息\r\n            preserveHeaders: true,\r\n    \r\n            // 是否允许放大。\r\n            allowMagnify: true\r\n        };\r\n    \r\n        // 继承RuntimeClient.\r\n        Base.inherits( RuntimeClient, {\r\n            constructor: Image,\r\n    \r\n            info: function( val ) {\r\n    \r\n                // setter\r\n                if ( val ) {\r\n                    this._info = val;\r\n                    return this;\r\n                }\r\n    \r\n                // getter\r\n                return this._info;\r\n            },\r\n    \r\n            meta: function( val ) {\r\n    \r\n                // setter\r\n                if ( val ) {\r\n                    this._meta = val;\r\n                    return this;\r\n                }\r\n    \r\n                // getter\r\n                return this._meta;\r\n            },\r\n    \r\n            loadFromBlob: function( blob ) {\r\n                var me = this,\r\n                    ruid = blob.getRuid();\r\n    \r\n                this.connectRuntime( ruid, function() {\r\n                    me.exec( 'init', me.options );\r\n                    me.exec( 'loadFromBlob', blob );\r\n                });\r\n            },\r\n    \r\n            resize: function() {\r\n                var args = Base.slice( arguments );\r\n                return this.exec.apply( this, [ 'resize' ].concat( args ) );\r\n            },\r\n    \r\n            getAsDataUrl: function( type ) {\r\n                return this.exec( 'getAsDataUrl', type );\r\n            },\r\n    \r\n            getAsBlob: function( type ) {\r\n                var blob = this.exec( 'getAsBlob', type );\r\n    \r\n                return new Blob( this.getRuid(), blob );\r\n            }\r\n        });\r\n    \r\n        return Image;\r\n    });\r\n    /**\r\n     * @fileOverview 图片操作, 负责预览图片和上传前压缩图片\r\n     */\r\n    define('widgets/image',[\r\n        'base',\r\n        'uploader',\r\n        'lib/image',\r\n        'widgets/widget'\r\n    ], function( Base, Uploader, Image ) {\r\n    \r\n        var $ = Base.$,\r\n            throttle;\r\n    \r\n        // 根据要处理的文件大小来节流，一次不能处理太多，会卡。\r\n        throttle = (function( max ) {\r\n            var occupied = 0,\r\n                waiting = [],\r\n                tick = function() {\r\n                    var item;\r\n    \r\n                    while ( waiting.length && occupied < max ) {\r\n                        item = waiting.shift();\r\n                        occupied += item[ 0 ];\r\n                        item[ 1 ]();\r\n                    }\r\n                };\r\n    \r\n            return function( emiter, size, cb ) {\r\n                waiting.push([ size, cb ]);\r\n                emiter.once( 'destroy', function() {\r\n                    occupied -= size;\r\n                    setTimeout( tick, 1 );\r\n                });\r\n                setTimeout( tick, 1 );\r\n            };\r\n        })( 5 * 1024 * 1024 );\r\n    \r\n        $.extend( Uploader.options, {\r\n    \r\n            /**\r\n             * @property {Object} [thumb]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 配置生成缩略图的选项。\r\n             *\r\n             * 默认为：\r\n             *\r\n             * ```javascript\r\n             * {\r\n             *     width: 110,\r\n             *     height: 110,\r\n             *\r\n             *     // 图片质量，只有type为`image/jpeg`的时候才有效。\r\n             *     quality: 70,\r\n             *\r\n             *     // 是否允许放大，如果想要生成小图的时候不失真，此选项应该设置为false.\r\n             *     allowMagnify: true,\r\n             *\r\n             *     // 是否允许裁剪。\r\n             *     crop: true,\r\n             *\r\n             *     // 是否保留头部meta信息。\r\n             *     preserveHeaders: false,\r\n             *\r\n             *     // 为空的话则保留原有图片格式。\r\n             *     // 否则强制转换成指定的类型。\r\n             *     type: 'image/jpeg'\r\n             * }\r\n             * ```\r\n             */\r\n            thumb: {\r\n                width: 110,\r\n                height: 110,\r\n                quality: 70,\r\n                allowMagnify: true,\r\n                crop: true,\r\n                preserveHeaders: false,\r\n    \r\n                // 为空的话则保留原有图片格式。\r\n                // 否则强制转换成指定的类型。\r\n                // IE 8下面 base64 大小不能超过 32K 否则预览失败，而非 jpeg 编码的图片很可\r\n                // 能会超过 32k, 所以这里设置成预览的时候都是 image/jpeg\r\n                type: 'image/jpeg'\r\n            },\r\n    \r\n            /**\r\n             * @property {Object} [compress]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 配置压缩的图片的选项。如果此选项为`false`, 则图片在上传前不进行压缩。\r\n             *\r\n             * 默认为：\r\n             *\r\n             * ```javascript\r\n             * {\r\n             *     width: 1600,\r\n             *     height: 1600,\r\n             *\r\n             *     // 图片质量，只有type为`image/jpeg`的时候才有效。\r\n             *     quality: 90,\r\n             *\r\n             *     // 是否允许放大，如果想要生成小图的时候不失真，此选项应该设置为false.\r\n             *     allowMagnify: false,\r\n             *\r\n             *     // 是否允许裁剪。\r\n             *     crop: false,\r\n             *\r\n             *     // 是否保留头部meta信息。\r\n             *     preserveHeaders: true\r\n             * }\r\n             * ```\r\n             */\r\n            compress: {\r\n                width: 1600,\r\n                height: 1600,\r\n                quality: 90,\r\n                allowMagnify: false,\r\n                crop: false,\r\n                preserveHeaders: true\r\n            }\r\n        });\r\n    \r\n        return Uploader.register({\r\n            'make-thumb': 'makeThumb',\r\n            'before-send-file': 'compressImage'\r\n        }, {\r\n    \r\n    \r\n            /**\r\n             * 生成缩略图，此过程为异步，所以需要传入`callback`。\r\n             * 通常情况在图片加入队里后调用此方法来生成预览图以增强交互效果。\r\n             *\r\n             * `callback`中可以接收到两个参数。\r\n             * * 第一个为error，如果生成缩略图有错误，此error将为真。\r\n             * * 第二个为ret, 缩略图的Data URL值。\r\n             *\r\n             * **注意**\r\n             * Date URL在IE6/7中不支持，所以不用调用此方法了，直接显示一张暂不支持预览图片好了。\r\n             *\r\n             *\r\n             * @method makeThumb\r\n             * @grammar makeThumb( file, callback ) => undefined\r\n             * @grammar makeThumb( file, callback, width, height ) => undefined\r\n             * @for Uploader\r\n             * @example\r\n             *\r\n             * uploader.on( 'fileQueued', function( file ) {\r\n             *     var $li = ...;\r\n             *\r\n             *     uploader.makeThumb( file, function( error, ret ) {\r\n             *         if ( error ) {\r\n             *             $li.text('预览错误');\r\n             *         } else {\r\n             *             $li.append('<img alt=\"\" src=\"' + ret + '\" />');\r\n             *         }\r\n             *     });\r\n             *\r\n             * });\r\n             */\r\n            makeThumb: function( file, cb, width, height ) {\r\n                var opts, image;\r\n    \r\n                file = this.request( 'get-file', file );\r\n    \r\n                // 只预览图片格式。\r\n                if ( !file.type.match( /^image/ ) ) {\r\n                    cb( true );\r\n                    return;\r\n                }\r\n    \r\n                opts = $.extend({}, this.options.thumb );\r\n    \r\n                // 如果传入的是object.\r\n                if ( $.isPlainObject( width ) ) {\r\n                    opts = $.extend( opts, width );\r\n                    width = null;\r\n                }\r\n    \r\n                width = width || opts.width;\r\n                height = height || opts.height;\r\n    \r\n                image = new Image( opts );\r\n    \r\n                image.once( 'load', function() {\r\n                    file._info = file._info || image.info();\r\n                    file._meta = file._meta || image.meta();\r\n                    image.resize( width, height );\r\n                });\r\n    \r\n                image.once( 'complete', function() {\r\n                    cb( false, image.getAsDataUrl( opts.type ) );\r\n                    image.destroy();\r\n                });\r\n    \r\n                image.once( 'error', function() {\r\n                    cb( true );\r\n                    image.destroy();\r\n                });\r\n    \r\n                throttle( image, file.source.size, function() {\r\n                    file._info && image.info( file._info );\r\n                    file._meta && image.meta( file._meta );\r\n                    image.loadFromBlob( file.source );\r\n                });\r\n            },\r\n    \r\n            compressImage: function( file ) {\r\n                var opts = this.options.compress || this.options.resize,\r\n                    compressSize = opts && opts.compressSize || 300 * 1024,\r\n                    image, deferred;\r\n    \r\n                file = this.request( 'get-file', file );\r\n    \r\n                // 只预览图片格式。\r\n                if ( !opts || !~'image/jpeg,image/jpg'.indexOf( file.type ) ||\r\n                        file.size < compressSize ||\r\n                        file._compressed ) {\r\n                    return;\r\n                }\r\n    \r\n                opts = $.extend({}, opts );\r\n                deferred = Base.Deferred();\r\n    \r\n                image = new Image( opts );\r\n    \r\n                deferred.always(function() {\r\n                    image.destroy();\r\n                    image = null;\r\n                });\r\n                image.once( 'error', deferred.reject );\r\n                image.once( 'load', function() {\r\n                    file._info = file._info || image.info();\r\n                    file._meta = file._meta || image.meta();\r\n                    image.resize( opts.width, opts.height );\r\n                });\r\n    \r\n                image.once( 'complete', function() {\r\n                    var blob, size;\r\n    \r\n                    // 移动端 UC / qq 浏览器的无图模式下\r\n                    // ctx.getImageData 处理大图的时候会报 Exception\r\n                    // INDEX_SIZE_ERR: DOM Exception 1\r\n                    try {\r\n                        blob = image.getAsBlob( opts.type );\r\n    \r\n                        size = file.size;\r\n    \r\n                        // 如果压缩后，比原来还大则不用压缩后的。\r\n                        if ( blob.size < size ) {\r\n                            // file.source.destroy && file.source.destroy();\r\n                            file.source = blob;\r\n                            file.size = blob.size;\r\n    \r\n                            file.trigger( 'resize', blob.size, size );\r\n                        }\r\n    \r\n                        // 标记，避免重复压缩。\r\n                        file._compressed = true;\r\n                        deferred.resolve();\r\n                    } catch ( e ) {\r\n                        // 出错了直接继续，让其上传原始图片\r\n                        deferred.resolve();\r\n                    }\r\n                });\r\n    \r\n                file._info && image.info( file._info );\r\n                file._meta && image.meta( file._meta );\r\n    \r\n                image.loadFromBlob( file.source );\r\n                return deferred.promise();\r\n            }\r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview 文件属性封装\r\n     */\r\n    define('file',[\r\n        'base',\r\n        'mediator'\r\n    ], function( Base, Mediator ) {\r\n    \r\n        var $ = Base.$,\r\n            idPrefix = 'WU_FILE_',\r\n            idSuffix = 0,\r\n            rExt = /\\.([^.]+)$/,\r\n            statusMap = {};\r\n    \r\n        function gid() {\r\n            return idPrefix + idSuffix++;\r\n        }\r\n    \r\n        /**\r\n         * 文件类\r\n         * @class File\r\n         * @constructor 构造函数\r\n         * @grammar new File( source ) => File\r\n         * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。\r\n         */\r\n        function WUFile( source ) {\r\n    \r\n            /**\r\n             * 文件名，包括扩展名（后缀）\r\n             * @property name\r\n             * @type {string}\r\n             */\r\n            this.name = source.name || 'Untitled';\r\n    \r\n            /**\r\n             * 文件体积（字节）\r\n             * @property size\r\n             * @type {uint}\r\n             * @default 0\r\n             */\r\n            this.size = source.size || 0;\r\n    \r\n            /**\r\n             * 文件MIMETYPE类型，与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny)\r\n             * @property type\r\n             * @type {string}\r\n             * @default 'application'\r\n             */\r\n            this.type = source.type || 'application';\r\n    \r\n            /**\r\n             * 文件最后修改日期\r\n             * @property lastModifiedDate\r\n             * @type {int}\r\n             * @default 当前时间戳\r\n             */\r\n            this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1);\r\n    \r\n            /**\r\n             * 文件ID，每个对象具有唯一ID，与文件名无关\r\n             * @property id\r\n             * @type {string}\r\n             */\r\n            this.id = gid();\r\n    \r\n            /**\r\n             * 文件扩展名，通过文件名获取，例如test.png的扩展名为png\r\n             * @property ext\r\n             * @type {string}\r\n             */\r\n            this.ext = rExt.exec( this.name ) ? RegExp.$1 : '';\r\n    \r\n    \r\n            /**\r\n             * 状态文字说明。在不同的status语境下有不同的用途。\r\n             * @property statusText\r\n             * @type {string}\r\n             */\r\n            this.statusText = '';\r\n    \r\n            // 存储文件状态，防止通过属性直接修改\r\n            statusMap[ this.id ] = WUFile.Status.INITED;\r\n    \r\n            this.source = source;\r\n            this.loaded = 0;\r\n    \r\n            this.on( 'error', function( msg ) {\r\n                this.setStatus( WUFile.Status.ERROR, msg );\r\n            });\r\n        }\r\n    \r\n        $.extend( WUFile.prototype, {\r\n    \r\n            /**\r\n             * 设置状态，状态变化时会触发`change`事件。\r\n             * @method setStatus\r\n             * @grammar setStatus( status[, statusText] );\r\n             * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status)\r\n             * @param {String} [statusText=''] 状态说明，常在error时使用，用http, abort,server等来标记是由于什么原因导致文件错误。\r\n             */\r\n            setStatus: function( status, text ) {\r\n    \r\n                var prevStatus = statusMap[ this.id ];\r\n    \r\n                typeof text !== 'undefined' && (this.statusText = text);\r\n    \r\n                if ( status !== prevStatus ) {\r\n                    statusMap[ this.id ] = status;\r\n                    /**\r\n                     * 文件状态变化\r\n                     * @event statuschange\r\n                     */\r\n                    this.trigger( 'statuschange', status, prevStatus );\r\n                }\r\n    \r\n            },\r\n    \r\n            /**\r\n             * 获取文件状态\r\n             * @return {File.Status}\r\n             * @example\r\n                     文件状态具体包括以下几种类型：\r\n                     {\r\n                         // 初始化\r\n                        INITED:     0,\r\n                        // 已入队列\r\n                        QUEUED:     1,\r\n                        // 正在上传\r\n                        PROGRESS:     2,\r\n                        // 上传出错\r\n                        ERROR:         3,\r\n                        // 上传成功\r\n                        COMPLETE:     4,\r\n                        // 上传取消\r\n                        CANCELLED:     5\r\n                    }\r\n             */\r\n            getStatus: function() {\r\n                return statusMap[ this.id ];\r\n            },\r\n    \r\n            /**\r\n             * 获取文件原始信息。\r\n             * @return {*}\r\n             */\r\n            getSource: function() {\r\n                return this.source;\r\n            },\r\n    \r\n            destory: function() {\r\n                delete statusMap[ this.id ];\r\n            }\r\n        });\r\n    \r\n        Mediator.installTo( WUFile.prototype );\r\n    \r\n        /**\r\n         * 文件状态值，具体包括以下几种类型：\r\n         * * `inited` 初始状态\r\n         * * `queued` 已经进入队列, 等待上传\r\n         * * `progress` 上传中\r\n         * * `complete` 上传完成。\r\n         * * `error` 上传出错，可重试\r\n         * * `interrupt` 上传中断，可续传。\r\n         * * `invalid` 文件不合格，不能重试上传。会自动从队列中移除。\r\n         * * `cancelled` 文件被移除。\r\n         * @property {Object} Status\r\n         * @namespace File\r\n         * @class File\r\n         * @static\r\n         */\r\n        WUFile.Status = {\r\n            INITED:     'inited',    // 初始状态\r\n            QUEUED:     'queued',    // 已经进入队列, 等待上传\r\n            PROGRESS:   'progress',    // 上传中\r\n            ERROR:      'error',    // 上传出错，可重试\r\n            COMPLETE:   'complete',    // 上传完成。\r\n            CANCELLED:  'cancelled',    // 上传取消。\r\n            INTERRUPT:  'interrupt',    // 上传中断，可续传。\r\n            INVALID:    'invalid'    // 文件不合格，不能重试上传。\r\n        };\r\n    \r\n        return WUFile;\r\n    });\r\n    \r\n    /**\r\n     * @fileOverview 文件队列\r\n     */\r\n    define('queue',[\r\n        'base',\r\n        'mediator',\r\n        'file'\r\n    ], function( Base, Mediator, WUFile ) {\r\n    \r\n        var $ = Base.$,\r\n            STATUS = WUFile.Status;\r\n    \r\n        /**\r\n         * 文件队列, 用来存储各个状态中的文件。\r\n         * @class Queue\r\n         * @extends Mediator\r\n         */\r\n        function Queue() {\r\n    \r\n            /**\r\n             * 统计文件数。\r\n             * * `numOfQueue` 队列中的文件数。\r\n             * * `numOfSuccess` 上传成功的文件数\r\n             * * `numOfCancel` 被移除的文件数\r\n             * * `numOfProgress` 正在上传中的文件数\r\n             * * `numOfUploadFailed` 上传错误的文件数。\r\n             * * `numOfInvalid` 无效的文件数。\r\n             * @property {Object} stats\r\n             */\r\n            this.stats = {\r\n                numOfQueue: 0,\r\n                numOfSuccess: 0,\r\n                numOfCancel: 0,\r\n                numOfProgress: 0,\r\n                numOfUploadFailed: 0,\r\n                numOfInvalid: 0\r\n            };\r\n    \r\n            // 上传队列，仅包括等待上传的文件\r\n            this._queue = [];\r\n    \r\n            // 存储所有文件\r\n            this._map = {};\r\n        }\r\n    \r\n        $.extend( Queue.prototype, {\r\n    \r\n            /**\r\n             * 将新文件加入对队列尾部\r\n             *\r\n             * @method append\r\n             * @param  {File} file   文件对象\r\n             */\r\n            append: function( file ) {\r\n                this._queue.push( file );\r\n                this._fileAdded( file );\r\n                return this;\r\n            },\r\n    \r\n            /**\r\n             * 将新文件加入对队列头部\r\n             *\r\n             * @method prepend\r\n             * @param  {File} file   文件对象\r\n             */\r\n            prepend: function( file ) {\r\n                this._queue.unshift( file );\r\n                this._fileAdded( file );\r\n                return this;\r\n            },\r\n    \r\n            /**\r\n             * 获取文件对象\r\n             *\r\n             * @method getFile\r\n             * @param  {String} fileId   文件ID\r\n             * @return {File}\r\n             */\r\n            getFile: function( fileId ) {\r\n                if ( typeof fileId !== 'string' ) {\r\n                    return fileId;\r\n                }\r\n                return this._map[ fileId ];\r\n            },\r\n    \r\n            /**\r\n             * 从队列中取出一个指定状态的文件。\r\n             * @grammar fetch( status ) => File\r\n             * @method fetch\r\n             * @param {String} status [文件状态值](#WebUploader:File:File.Status)\r\n             * @return {File} [File](#WebUploader:File)\r\n             */\r\n            fetch: function( status ) {\r\n                var len = this._queue.length,\r\n                    i, file;\r\n    \r\n                status = status || STATUS.QUEUED;\r\n    \r\n                for ( i = 0; i < len; i++ ) {\r\n                    file = this._queue[ i ];\r\n    \r\n                    if ( status === file.getStatus() ) {\r\n                        return file;\r\n                    }\r\n                }\r\n    \r\n                return null;\r\n            },\r\n    \r\n            /**\r\n             * 对队列进行排序，能够控制文件上传顺序。\r\n             * @grammar sort( fn ) => undefined\r\n             * @method sort\r\n             * @param {Function} fn 排序方法\r\n             */\r\n            sort: function( fn ) {\r\n                if ( typeof fn === 'function' ) {\r\n                    this._queue.sort( fn );\r\n                }\r\n            },\r\n    \r\n            /**\r\n             * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。\r\n             * @grammar getFiles( [status1[, status2 ...]] ) => Array\r\n             * @method getFiles\r\n             * @param {String} [status] [文件状态值](#WebUploader:File:File.Status)\r\n             */\r\n            getFiles: function() {\r\n                var sts = [].slice.call( arguments, 0 ),\r\n                    ret = [],\r\n                    i = 0,\r\n                    len = this._queue.length,\r\n                    file;\r\n    \r\n                for ( ; i < len; i++ ) {\r\n                    file = this._queue[ i ];\r\n    \r\n                    if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) {\r\n                        continue;\r\n                    }\r\n    \r\n                    ret.push( file );\r\n                }\r\n    \r\n                return ret;\r\n            },\r\n    \r\n            _fileAdded: function( file ) {\r\n                var me = this,\r\n                    existing = this._map[ file.id ];\r\n    \r\n                if ( !existing ) {\r\n                    this._map[ file.id ] = file;\r\n    \r\n                    file.on( 'statuschange', function( cur, pre ) {\r\n                        me._onFileStatusChange( cur, pre );\r\n                    });\r\n                }\r\n    \r\n                file.setStatus( STATUS.QUEUED );\r\n            },\r\n    \r\n            _onFileStatusChange: function( curStatus, preStatus ) {\r\n                var stats = this.stats;\r\n    \r\n                switch ( preStatus ) {\r\n                    case STATUS.PROGRESS:\r\n                        stats.numOfProgress--;\r\n                        break;\r\n    \r\n                    case STATUS.QUEUED:\r\n                        stats.numOfQueue --;\r\n                        break;\r\n    \r\n                    case STATUS.ERROR:\r\n                        stats.numOfUploadFailed--;\r\n                        break;\r\n    \r\n                    case STATUS.INVALID:\r\n                        stats.numOfInvalid--;\r\n                        break;\r\n                }\r\n    \r\n                switch ( curStatus ) {\r\n                    case STATUS.QUEUED:\r\n                        stats.numOfQueue++;\r\n                        break;\r\n    \r\n                    case STATUS.PROGRESS:\r\n                        stats.numOfProgress++;\r\n                        break;\r\n    \r\n                    case STATUS.ERROR:\r\n                        stats.numOfUploadFailed++;\r\n                        break;\r\n    \r\n                    case STATUS.COMPLETE:\r\n                        stats.numOfSuccess++;\r\n                        break;\r\n    \r\n                    case STATUS.CANCELLED:\r\n                        stats.numOfCancel++;\r\n                        break;\r\n    \r\n                    case STATUS.INVALID:\r\n                        stats.numOfInvalid++;\r\n                        break;\r\n                }\r\n            }\r\n    \r\n        });\r\n    \r\n        Mediator.installTo( Queue.prototype );\r\n    \r\n        return Queue;\r\n    });\r\n    /**\r\n     * @fileOverview 队列\r\n     */\r\n    define('widgets/queue',[\r\n        'base',\r\n        'uploader',\r\n        'queue',\r\n        'file',\r\n        'lib/file',\r\n        'runtime/client',\r\n        'widgets/widget'\r\n    ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) {\r\n    \r\n        var $ = Base.$,\r\n            rExt = /\\.\\w+$/,\r\n            Status = WUFile.Status;\r\n    \r\n        return Uploader.register({\r\n            'sort-files': 'sortFiles',\r\n            'add-file': 'addFiles',\r\n            'get-file': 'getFile',\r\n            'fetch-file': 'fetchFile',\r\n            'get-stats': 'getStats',\r\n            'get-files': 'getFiles',\r\n            'remove-file': 'removeFile',\r\n            'retry': 'retry',\r\n            'reset': 'reset',\r\n            'accept-file': 'acceptFile'\r\n        }, {\r\n    \r\n            init: function( opts ) {\r\n                var me = this,\r\n                    deferred, len, i, item, arr, accept, runtime;\r\n    \r\n                if ( $.isPlainObject( opts.accept ) ) {\r\n                    opts.accept = [ opts.accept ];\r\n                }\r\n    \r\n                // accept中的中生成匹配正则。\r\n                if ( opts.accept ) {\r\n                    arr = [];\r\n    \r\n                    for ( i = 0, len = opts.accept.length; i < len; i++ ) {\r\n                        item = opts.accept[ i ].extensions;\r\n                        item && arr.push( item );\r\n                    }\r\n    \r\n                    if ( arr.length ) {\r\n                        accept = '\\\\.' + arr.join(',')\r\n                                .replace( /,/g, '$|\\\\.' )\r\n                                .replace( /\\*/g, '.*' ) + '$';\r\n                    }\r\n    \r\n                    me.accept = new RegExp( accept, 'i' );\r\n                }\r\n    \r\n                me.queue = new Queue();\r\n                me.stats = me.queue.stats;\r\n    \r\n                // 如果当前不是html5运行时，那就算了。\r\n                // 不执行后续操作\r\n                if ( this.request('predict-runtime-type') !== 'html5' ) {\r\n                    return;\r\n                }\r\n    \r\n                // 创建一个 html5 运行时的 placeholder\r\n                // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。\r\n                deferred = Base.Deferred();\r\n                runtime = new RuntimeClient('Placeholder');\r\n                runtime.connectRuntime({\r\n                    runtimeOrder: 'html5'\r\n                }, function() {\r\n                    me._ruid = runtime.getRuid();\r\n                    deferred.resolve();\r\n                });\r\n                return deferred.promise();\r\n            },\r\n    \r\n    \r\n            // 为了支持外部直接添加一个原生File对象。\r\n            _wrapFile: function( file ) {\r\n                if ( !(file instanceof WUFile) ) {\r\n    \r\n                    if ( !(file instanceof File) ) {\r\n                        if ( !this._ruid ) {\r\n                            throw new Error('Can\\'t add external files.');\r\n                        }\r\n                        file = new File( this._ruid, file );\r\n                    }\r\n    \r\n                    file = new WUFile( file );\r\n                }\r\n    \r\n                return file;\r\n            },\r\n    \r\n            // 判断文件是否可以被加入队列\r\n            acceptFile: function( file ) {\r\n                var invalid = !file || file.size < 6 || this.accept &&\r\n    \r\n                        // 如果名字中有后缀，才做后缀白名单处理。\r\n                        rExt.exec( file.name ) && !this.accept.test( file.name );\r\n    \r\n                return !invalid;\r\n            },\r\n    \r\n    \r\n            /**\r\n             * @event beforeFileQueued\r\n             * @param {File} file File对象\r\n             * @description 当文件被加入队列之前触发，此事件的handler返回值为`false`，则此文件不会被添加进入队列。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * @event fileQueued\r\n             * @param {File} file File对象\r\n             * @description 当文件被加入队列以后触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            _addFile: function( file ) {\r\n                var me = this;\r\n    \r\n                file = me._wrapFile( file );\r\n    \r\n                // 不过类型判断允许不允许，先派送 `beforeFileQueued`\r\n                if ( !me.owner.trigger( 'beforeFileQueued', file ) ) {\r\n                    return;\r\n                }\r\n    \r\n                // 类型不匹配，则派送错误事件，并返回。\r\n                if ( !me.acceptFile( file ) ) {\r\n                    me.owner.trigger( 'error', 'Q_TYPE_DENIED', file );\r\n                    return;\r\n                }\r\n    \r\n                me.queue.append( file );\r\n                me.owner.trigger( 'fileQueued', file );\r\n                return file;\r\n            },\r\n    \r\n            getFile: function( fileId ) {\r\n                return this.queue.getFile( fileId );\r\n            },\r\n    \r\n            /**\r\n             * @event filesQueued\r\n             * @param {File} files 数组，内容为原始File(lib/File）对象。\r\n             * @description 当一批文件添加进队列以后触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * @method addFiles\r\n             * @grammar addFiles( file ) => undefined\r\n             * @grammar addFiles( [file1, file2 ...] ) => undefined\r\n             * @param {Array of File or File} [files] Files 对象 数组\r\n             * @description 添加文件到队列\r\n             * @for  Uploader\r\n             */\r\n            addFiles: function( files ) {\r\n                var me = this;\r\n    \r\n                if ( !files.length ) {\r\n                    files = [ files ];\r\n                }\r\n    \r\n                files = $.map( files, function( file ) {\r\n                    return me._addFile( file );\r\n                });\r\n    \r\n                me.owner.trigger( 'filesQueued', files );\r\n    \r\n                if ( me.options.auto ) {\r\n                    me.request('start-upload');\r\n                }\r\n            },\r\n    \r\n            getStats: function() {\r\n                return this.stats;\r\n            },\r\n    \r\n            /**\r\n             * @event fileDequeued\r\n             * @param {File} file File对象\r\n             * @description 当文件被移除队列后触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * @method removeFile\r\n             * @grammar removeFile( file ) => undefined\r\n             * @grammar removeFile( id ) => undefined\r\n             * @param {File|id} file File对象或这File对象的id\r\n             * @description 移除某一文件。\r\n             * @for  Uploader\r\n             * @example\r\n             *\r\n             * $li.on('click', '.remove-this', function() {\r\n             *     uploader.removeFile( file );\r\n             * })\r\n             */\r\n            removeFile: function( file ) {\r\n                var me = this;\r\n    \r\n                file = file.id ? file : me.queue.getFile( file );\r\n    \r\n                file.setStatus( Status.CANCELLED );\r\n                me.owner.trigger( 'fileDequeued', file );\r\n            },\r\n    \r\n            /**\r\n             * @method getFiles\r\n             * @grammar getFiles() => Array\r\n             * @grammar getFiles( status1, status2, status... ) => Array\r\n             * @description 返回指定状态的文件集合，不传参数将返回所有状态的文件。\r\n             * @for  Uploader\r\n             * @example\r\n             * console.log( uploader.getFiles() );    // => all files\r\n             * console.log( uploader.getFiles('error') )    // => all error files.\r\n             */\r\n            getFiles: function() {\r\n                return this.queue.getFiles.apply( this.queue, arguments );\r\n            },\r\n    \r\n            fetchFile: function() {\r\n                return this.queue.fetch.apply( this.queue, arguments );\r\n            },\r\n    \r\n            /**\r\n             * @method retry\r\n             * @grammar retry() => undefined\r\n             * @grammar retry( file ) => undefined\r\n             * @description 重试上传，重试指定文件，或者从出错的文件开始重新上传。\r\n             * @for  Uploader\r\n             * @example\r\n             * function retry() {\r\n             *     uploader.retry();\r\n             * }\r\n             */\r\n            retry: function( file, noForceStart ) {\r\n                var me = this,\r\n                    files, i, len;\r\n    \r\n                if ( file ) {\r\n                    file = file.id ? file : me.queue.getFile( file );\r\n                    file.setStatus( Status.QUEUED );\r\n                    noForceStart || me.request('start-upload');\r\n                    return;\r\n                }\r\n    \r\n                files = me.queue.getFiles( Status.ERROR );\r\n                i = 0;\r\n                len = files.length;\r\n    \r\n                for ( ; i < len; i++ ) {\r\n                    file = files[ i ];\r\n                    file.setStatus( Status.QUEUED );\r\n                }\r\n    \r\n                me.request('start-upload');\r\n            },\r\n    \r\n            /**\r\n             * @method sort\r\n             * @grammar sort( fn ) => undefined\r\n             * @description 排序队列中的文件，在上传之前调整可以控制上传顺序。\r\n             * @for  Uploader\r\n             */\r\n            sortFiles: function() {\r\n                return this.queue.sort.apply( this.queue, arguments );\r\n            },\r\n    \r\n            /**\r\n             * @method reset\r\n             * @grammar reset() => undefined\r\n             * @description 重置uploader。目前只重置了队列。\r\n             * @for  Uploader\r\n             * @example\r\n             * uploader.reset();\r\n             */\r\n            reset: function() {\r\n                this.queue = new Queue();\r\n                this.stats = this.queue.stats;\r\n            }\r\n        });\r\n    \r\n    });\r\n    /**\r\n     * @fileOverview 添加获取Runtime相关信息的方法。\r\n     */\r\n    define('widgets/runtime',[\r\n        'uploader',\r\n        'runtime/runtime',\r\n        'widgets/widget'\r\n    ], function( Uploader, Runtime ) {\r\n    \r\n        Uploader.support = function() {\r\n            return Runtime.hasRuntime.apply( Runtime, arguments );\r\n        };\r\n    \r\n        return Uploader.register({\r\n            'predict-runtime-type': 'predictRuntmeType'\r\n        }, {\r\n    \r\n            init: function() {\r\n                if ( !this.predictRuntmeType() ) {\r\n                    throw Error('Runtime Error');\r\n                }\r\n            },\r\n    \r\n            /**\r\n             * 预测Uploader将采用哪个`Runtime`\r\n             * @grammar predictRuntmeType() => String\r\n             * @method predictRuntmeType\r\n             * @for  Uploader\r\n             */\r\n            predictRuntmeType: function() {\r\n                var orders = this.options.runtimeOrder || Runtime.orders,\r\n                    type = this.type,\r\n                    i, len;\r\n    \r\n                if ( !type ) {\r\n                    orders = orders.split( /\\s*,\\s*/g );\r\n    \r\n                    for ( i = 0, len = orders.length; i < len; i++ ) {\r\n                        if ( Runtime.hasRuntime( orders[ i ] ) ) {\r\n                            this.type = type = orders[ i ];\r\n                            break;\r\n                        }\r\n                    }\r\n                }\r\n    \r\n                return type;\r\n            }\r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview Transport\r\n     */\r\n    define('lib/transport',[\r\n        'base',\r\n        'runtime/client',\r\n        'mediator'\r\n    ], function( Base, RuntimeClient, Mediator ) {\r\n    \r\n        var $ = Base.$;\r\n    \r\n        function Transport( opts ) {\r\n            var me = this;\r\n    \r\n            opts = me.options = $.extend( true, {}, Transport.options, opts || {} );\r\n            RuntimeClient.call( this, 'Transport' );\r\n    \r\n            this._blob = null;\r\n            this._formData = opts.formData || {};\r\n            this._headers = opts.headers || {};\r\n    \r\n            this.on( 'progress', this._timeout );\r\n            this.on( 'load error', function() {\r\n                me.trigger( 'progress', 1 );\r\n                clearTimeout( me._timer );\r\n            });\r\n        }\r\n    \r\n        Transport.options = {\r\n            server: '',\r\n            method: 'POST',\r\n    \r\n            // 跨域时，是否允许携带cookie, 只有html5 runtime才有效\r\n            withCredentials: false,\r\n            fileVal: 'file',\r\n            timeout: 2 * 60 * 1000,    // 2分钟\r\n            formData: {},\r\n            headers: {},\r\n            sendAsBinary: false\r\n        };\r\n    \r\n        $.extend( Transport.prototype, {\r\n    \r\n            // 添加Blob, 只能添加一次，最后一次有效。\r\n            appendBlob: function( key, blob, filename ) {\r\n                var me = this,\r\n                    opts = me.options;\r\n    \r\n                if ( me.getRuid() ) {\r\n                    me.disconnectRuntime();\r\n                }\r\n    \r\n                // 连接到blob归属的同一个runtime.\r\n                me.connectRuntime( blob.ruid, function() {\r\n                    me.exec('init');\r\n                });\r\n    \r\n                me._blob = blob;\r\n                opts.fileVal = key || opts.fileVal;\r\n                opts.filename = filename || opts.filename;\r\n            },\r\n    \r\n            // 添加其他字段\r\n            append: function( key, value ) {\r\n                if ( typeof key === 'object' ) {\r\n                    $.extend( this._formData, key );\r\n                } else {\r\n                    this._formData[ key ] = value;\r\n                }\r\n            },\r\n    \r\n            setRequestHeader: function( key, value ) {\r\n                if ( typeof key === 'object' ) {\r\n                    $.extend( this._headers, key );\r\n                } else {\r\n                    this._headers[ key ] = value;\r\n                }\r\n            },\r\n    \r\n            send: function( method ) {\r\n                this.exec( 'send', method );\r\n                this._timeout();\r\n            },\r\n    \r\n            abort: function() {\r\n                clearTimeout( this._timer );\r\n                return this.exec('abort');\r\n            },\r\n    \r\n            destroy: function() {\r\n                this.trigger('destroy');\r\n                this.off();\r\n                this.exec('destroy');\r\n                this.disconnectRuntime();\r\n            },\r\n    \r\n            getResponse: function() {\r\n                return this.exec('getResponse');\r\n            },\r\n    \r\n            getResponseAsJson: function() {\r\n                return this.exec('getResponseAsJson');\r\n            },\r\n    \r\n            getStatus: function() {\r\n                return this.exec('getStatus');\r\n            },\r\n    \r\n            _timeout: function() {\r\n                var me = this,\r\n                    duration = me.options.timeout;\r\n    \r\n                if ( !duration ) {\r\n                    return;\r\n                }\r\n    \r\n                clearTimeout( me._timer );\r\n                me._timer = setTimeout(function() {\r\n                    me.abort();\r\n                    me.trigger( 'error', 'timeout' );\r\n                }, duration );\r\n            }\r\n    \r\n        });\r\n    \r\n        // 让Transport具备事件功能。\r\n        Mediator.installTo( Transport.prototype );\r\n    \r\n        return Transport;\r\n    });\r\n    /**\r\n     * @fileOverview 负责文件上传相关。\r\n     */\r\n    define('widgets/upload',[\r\n        'base',\r\n        'uploader',\r\n        'file',\r\n        'lib/transport',\r\n        'widgets/widget'\r\n    ], function( Base, Uploader, WUFile, Transport ) {\r\n    \r\n        var $ = Base.$,\r\n            isPromise = Base.isPromise,\r\n            Status = WUFile.Status;\r\n    \r\n        // 添加默认配置项\r\n        $.extend( Uploader.options, {\r\n    \r\n    \r\n            /**\r\n             * @property {Boolean} [prepareNextFile=false]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 是否允许在文件传输时提前把下一个文件准备好。\r\n             * 对于一个文件的准备工作比较耗时，比如图片压缩，md5序列化。\r\n             * 如果能提前在当前文件传输期处理，可以节省总体耗时。\r\n             */\r\n            prepareNextFile: false,\r\n    \r\n            /**\r\n             * @property {Boolean} [chunked=false]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 是否要分片处理大文件上传。\r\n             */\r\n            chunked: false,\r\n    \r\n            /**\r\n             * @property {Boolean} [chunkSize=5242880]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 如果要分片，分多大一片？ 默认大小为5M.\r\n             */\r\n            chunkSize: 5 * 1024 * 1024,\r\n    \r\n            /**\r\n             * @property {Boolean} [chunkRetry=2]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 如果某个分片由于网络问题出错，允许自动重传多少次？\r\n             */\r\n            chunkRetry: 2,\r\n    \r\n            /**\r\n             * @property {Boolean} [threads=3]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 上传并发数。允许同时最大上传进程数。\r\n             */\r\n            threads: 3,\r\n    \r\n    \r\n            /**\r\n             * @property {Object} [formData]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 文件上传请求的参数表，每次发送都会发送此对象中的参数。\r\n             */\r\n            formData: null\r\n    \r\n            /**\r\n             * @property {Object} [fileVal='file']\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 设置文件上传域的name。\r\n             */\r\n    \r\n            /**\r\n             * @property {Object} [method='POST']\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 文件上传方式，`POST`或者`GET`。\r\n             */\r\n    \r\n            /**\r\n             * @property {Object} [sendAsBinary=false]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 是否已二进制的流的方式发送文件，这样整个上传内容`php://input`都为文件内容，\r\n             * 其他参数在$_GET数组中。\r\n             */\r\n        });\r\n    \r\n        // 负责将文件切片。\r\n        function CuteFile( file, chunkSize ) {\r\n            var pending = [],\r\n                blob = file.source,\r\n                total = blob.size,\r\n                chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1,\r\n                start = 0,\r\n                index = 0,\r\n                len;\r\n    \r\n            while ( index < chunks ) {\r\n                len = Math.min( chunkSize, total - start );\r\n    \r\n                pending.push({\r\n                    file: file,\r\n                    start: start,\r\n                    end: chunkSize ? (start + len) : total,\r\n                    total: total,\r\n                    chunks: chunks,\r\n                    chunk: index++\r\n                });\r\n                start += len;\r\n            }\r\n    \r\n            file.blocks = pending.concat();\r\n            file.remaning = pending.length;\r\n    \r\n            return {\r\n                file: file,\r\n    \r\n                has: function() {\r\n                    return !!pending.length;\r\n                },\r\n    \r\n                fetch: function() {\r\n                    return pending.shift();\r\n                }\r\n            };\r\n        }\r\n    \r\n        Uploader.register({\r\n            'start-upload': 'start',\r\n            'stop-upload': 'stop',\r\n            'skip-file': 'skipFile',\r\n            'is-in-progress': 'isInProgress'\r\n        }, {\r\n    \r\n            init: function() {\r\n                var owner = this.owner;\r\n    \r\n                this.runing = false;\r\n    \r\n                // 记录当前正在传的数据，跟threads相关\r\n                this.pool = [];\r\n    \r\n                // 缓存即将上传的文件。\r\n                this.pending = [];\r\n    \r\n                // 跟踪还有多少分片没有完成上传。\r\n                this.remaning = 0;\r\n                this.__tick = Base.bindFn( this._tick, this );\r\n    \r\n                owner.on( 'uploadComplete', function( file ) {\r\n                    // 把其他块取消了。\r\n                    file.blocks && $.each( file.blocks, function( _, v ) {\r\n                        v.transport && (v.transport.abort(), v.transport.destroy());\r\n                        delete v.transport;\r\n                    });\r\n    \r\n                    delete file.blocks;\r\n                    delete file.remaning;\r\n                });\r\n            },\r\n    \r\n            /**\r\n             * @event startUpload\r\n             * @description 当开始上传流程时触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * 开始上传。此方法可以从初始状态调用开始上传流程，也可以从暂停状态调用，继续上传流程。\r\n             * @grammar upload() => undefined\r\n             * @method upload\r\n             * @for  Uploader\r\n             */\r\n            start: function() {\r\n                var me = this;\r\n    \r\n                // 移出invalid的文件\r\n                $.each( me.request( 'get-files', Status.INVALID ), function() {\r\n                    me.request( 'remove-file', this );\r\n                });\r\n    \r\n                if ( me.runing ) {\r\n                    return;\r\n                }\r\n    \r\n                me.runing = true;\r\n    \r\n                // 如果有暂停的，则续传\r\n                $.each( me.pool, function( _, v ) {\r\n                    var file = v.file;\r\n    \r\n                    if ( file.getStatus() === Status.INTERRUPT ) {\r\n                        file.setStatus( Status.PROGRESS );\r\n                        me._trigged = false;\r\n                        v.transport && v.transport.send();\r\n                    }\r\n                });\r\n    \r\n                me._trigged = false;\r\n                me.owner.trigger('startUpload');\r\n                Base.nextTick( me.__tick );\r\n            },\r\n    \r\n            /**\r\n             * @event stopUpload\r\n             * @description 当开始上传流程暂停时触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。\r\n             * @grammar stop() => undefined\r\n             * @grammar stop( true ) => undefined\r\n             * @method stop\r\n             * @for  Uploader\r\n             */\r\n            stop: function( interrupt ) {\r\n                var me = this;\r\n    \r\n                if ( me.runing === false ) {\r\n                    return;\r\n                }\r\n    \r\n                me.runing = false;\r\n    \r\n                interrupt && $.each( me.pool, function( _, v ) {\r\n                    v.transport && v.transport.abort();\r\n                    v.file.setStatus( Status.INTERRUPT );\r\n                });\r\n    \r\n                me.owner.trigger('stopUpload');\r\n            },\r\n    \r\n            /**\r\n             * 判断`Uplaode`r是否正在上传中。\r\n             * @grammar isInProgress() => Boolean\r\n             * @method isInProgress\r\n             * @for  Uploader\r\n             */\r\n            isInProgress: function() {\r\n                return !!this.runing;\r\n            },\r\n    \r\n            getStats: function() {\r\n                return this.request('get-stats');\r\n            },\r\n    \r\n            /**\r\n             * 掉过一个文件上传，直接标记指定文件为已上传状态。\r\n             * @grammar skipFile( file ) => undefined\r\n             * @method skipFile\r\n             * @for  Uploader\r\n             */\r\n            skipFile: function( file, status ) {\r\n                file = this.request( 'get-file', file );\r\n    \r\n                file.setStatus( status || Status.COMPLETE );\r\n                file.skipped = true;\r\n    \r\n                // 如果正在上传。\r\n                file.blocks && $.each( file.blocks, function( _, v ) {\r\n                    var _tr = v.transport;\r\n    \r\n                    if ( _tr ) {\r\n                        _tr.abort();\r\n                        _tr.destroy();\r\n                        delete v.transport;\r\n                    }\r\n                });\r\n    \r\n                this.owner.trigger( 'uploadSkip', file );\r\n            },\r\n    \r\n            /**\r\n             * @event uploadFinished\r\n             * @description 当所有文件上传结束时触发。\r\n             * @for  Uploader\r\n             */\r\n            _tick: function() {\r\n                var me = this,\r\n                    opts = me.options,\r\n                    fn, val;\r\n    \r\n                // 上一个promise还没有结束，则等待完成后再执行。\r\n                if ( me._promise ) {\r\n                    return me._promise.always( me.__tick );\r\n                }\r\n    \r\n                // 还有位置，且还有文件要处理的话。\r\n                if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) {\r\n                    me._trigged = false;\r\n    \r\n                    fn = function( val ) {\r\n                        me._promise = null;\r\n    \r\n                        // 有可能是reject过来的，所以要检测val的类型。\r\n                        val && val.file && me._startSend( val );\r\n                        Base.nextTick( me.__tick );\r\n                    };\r\n    \r\n                    me._promise = isPromise( val ) ? val.always( fn ) : fn( val );\r\n    \r\n                // 没有要上传的了，且没有正在传输的了。\r\n                } else if ( !me.remaning && !me.getStats().numOfQueue ) {\r\n                    me.runing = false;\r\n    \r\n                    me._trigged || Base.nextTick(function() {\r\n                        me.owner.trigger('uploadFinished');\r\n                    });\r\n                    me._trigged = true;\r\n                }\r\n            },\r\n    \r\n            _nextBlock: function() {\r\n                var me = this,\r\n                    act = me._act,\r\n                    opts = me.options,\r\n                    next, done;\r\n    \r\n                // 如果当前文件还有没有需要传输的，则直接返回剩下的。\r\n                if ( act && act.has() &&\r\n                        act.file.getStatus() === Status.PROGRESS ) {\r\n    \r\n                    // 是否提前准备下一个文件\r\n                    if ( opts.prepareNextFile && !me.pending.length ) {\r\n                        me._prepareNextFile();\r\n                    }\r\n    \r\n                    return act.fetch();\r\n    \r\n                // 否则，如果正在运行，则准备下一个文件，并等待完成后返回下个分片。\r\n                } else if ( me.runing ) {\r\n    \r\n                    // 如果缓存中有，则直接在缓存中取，没有则去queue中取。\r\n                    if ( !me.pending.length && me.getStats().numOfQueue ) {\r\n                        me._prepareNextFile();\r\n                    }\r\n    \r\n                    next = me.pending.shift();\r\n                    done = function( file ) {\r\n                        if ( !file ) {\r\n                            return null;\r\n                        }\r\n    \r\n                        act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 );\r\n                        me._act = act;\r\n                        return act.fetch();\r\n                    };\r\n    \r\n                    // 文件可能还在prepare中，也有可能已经完全准备好了。\r\n                    return isPromise( next ) ?\r\n                            next[ next.pipe ? 'pipe' : 'then']( done ) :\r\n                            done( next );\r\n                }\r\n            },\r\n    \r\n    \r\n            /**\r\n             * @event uploadStart\r\n             * @param {File} file File对象\r\n             * @description 某个文件开始上传前触发，一个文件只会触发一次。\r\n             * @for  Uploader\r\n             */\r\n            _prepareNextFile: function() {\r\n                var me = this,\r\n                    file = me.request('fetch-file'),\r\n                    pending = me.pending,\r\n                    promise;\r\n    \r\n                if ( file ) {\r\n                    promise = me.request( 'before-send-file', file, function() {\r\n    \r\n                        // 有可能文件被skip掉了。文件被skip掉后，状态坑定不是Queued.\r\n                        if ( file.getStatus() === Status.QUEUED ) {\r\n                            me.owner.trigger( 'uploadStart', file );\r\n                            file.setStatus( Status.PROGRESS );\r\n                            return file;\r\n                        }\r\n    \r\n                        return me._finishFile( file );\r\n                    });\r\n    \r\n                    // 如果还在pending中，则替换成文件本身。\r\n                    promise.done(function() {\r\n                        var idx = $.inArray( promise, pending );\r\n    \r\n                        ~idx && pending.splice( idx, 1, file );\r\n                    });\r\n    \r\n                    // befeore-send-file的钩子就有错误发生。\r\n                    promise.fail(function( reason ) {\r\n                        file.setStatus( Status.ERROR, reason );\r\n                        me.owner.trigger( 'uploadError', file, reason );\r\n                        me.owner.trigger( 'uploadComplete', file );\r\n                    });\r\n    \r\n                    pending.push( promise );\r\n                }\r\n            },\r\n    \r\n            // 让出位置了，可以让其他分片开始上传\r\n            _popBlock: function( block ) {\r\n                var idx = $.inArray( block, this.pool );\r\n    \r\n                this.pool.splice( idx, 1 );\r\n                block.file.remaning--;\r\n                this.remaning--;\r\n            },\r\n    \r\n            // 开始上传，可以被掉过。如果promise被reject了，则表示跳过此分片。\r\n            _startSend: function( block ) {\r\n                var me = this,\r\n                    file = block.file,\r\n                    promise;\r\n    \r\n                me.pool.push( block );\r\n                me.remaning++;\r\n    \r\n                // 如果没有分片，则直接使用原始的。\r\n                // 不会丢失content-type信息。\r\n                block.blob = block.chunks === 1 ? file.source :\r\n                        file.source.slice( block.start, block.end );\r\n    \r\n                // hook, 每个分片发送之前可能要做些异步的事情。\r\n                promise = me.request( 'before-send', block, function() {\r\n    \r\n                    // 有可能文件已经上传出错了，所以不需要再传输了。\r\n                    if ( file.getStatus() === Status.PROGRESS ) {\r\n                        me._doSend( block );\r\n                    } else {\r\n                        me._popBlock( block );\r\n                        Base.nextTick( me.__tick );\r\n                    }\r\n                });\r\n    \r\n                // 如果为fail了，则跳过此分片。\r\n                promise.fail(function() {\r\n                    if ( file.remaning === 1 ) {\r\n                        me._finishFile( file ).always(function() {\r\n                            block.percentage = 1;\r\n                            me._popBlock( block );\r\n                            me.owner.trigger( 'uploadComplete', file );\r\n                            Base.nextTick( me.__tick );\r\n                        });\r\n                    } else {\r\n                        block.percentage = 1;\r\n                        me._popBlock( block );\r\n                        Base.nextTick( me.__tick );\r\n                    }\r\n                });\r\n            },\r\n    \r\n    \r\n            /**\r\n             * @event uploadBeforeSend\r\n             * @param {Object} object\r\n             * @param {Object} data 默认的上传参数，可以扩展此对象来控制上传参数。\r\n             * @description 当某个文件的分块在发送前触发，主要用来询问是否要添加附带参数，大文件在开起分片上传的前提下此事件可能会触发多次。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * @event uploadAccept\r\n             * @param {Object} object\r\n             * @param {Object} ret 服务端的返回数据，json格式，如果服务端不是json格式，从ret._raw中取数据，自行解析。\r\n             * @description 当某个文件上传到服务端响应后，会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * @event uploadProgress\r\n             * @param {File} file File对象\r\n             * @param {Number} percentage 上传进度\r\n             * @description 上传过程中触发，携带上传进度。\r\n             * @for  Uploader\r\n             */\r\n    \r\n    \r\n            /**\r\n             * @event uploadError\r\n             * @param {File} file File对象\r\n             * @param {String} reason 出错的code\r\n             * @description 当文件上传出错时触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * @event uploadSuccess\r\n             * @param {File} file File对象\r\n             * @param {Object} response 服务端返回的数据\r\n             * @description 当文件上传成功时触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * @event uploadComplete\r\n             * @param {File} [file] File对象\r\n             * @description 不管成功或者失败，文件上传完成时触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            // 做上传操作。\r\n            _doSend: function( block ) {\r\n                var me = this,\r\n                    owner = me.owner,\r\n                    opts = me.options,\r\n                    file = block.file,\r\n                    tr = new Transport( opts ),\r\n                    data = $.extend({}, opts.formData ),\r\n                    headers = $.extend({}, opts.headers ),\r\n                    requestAccept, ret;\r\n    \r\n                block.transport = tr;\r\n    \r\n                tr.on( 'destroy', function() {\r\n                    delete block.transport;\r\n                    me._popBlock( block );\r\n                    Base.nextTick( me.__tick );\r\n                });\r\n    \r\n                // 广播上传进度。以文件为单位。\r\n                tr.on( 'progress', function( percentage ) {\r\n                    var totalPercent = 0,\r\n                        uploaded = 0;\r\n    \r\n                    // 可能没有abort掉，progress还是执行进来了。\r\n                    // if ( !file.blocks ) {\r\n                    //     return;\r\n                    // }\r\n    \r\n                    totalPercent = block.percentage = percentage;\r\n    \r\n                    if ( block.chunks > 1 ) {    // 计算文件的整体速度。\r\n                        $.each( file.blocks, function( _, v ) {\r\n                            uploaded += (v.percentage || 0) * (v.end - v.start);\r\n                        });\r\n    \r\n                        totalPercent = uploaded / file.size;\r\n                    }\r\n    \r\n                    owner.trigger( 'uploadProgress', file, totalPercent || 0 );\r\n                });\r\n    \r\n                // 用来询问，是否返回的结果是有错误的。\r\n                requestAccept = function( reject ) {\r\n                    var fn;\r\n    \r\n                    ret = tr.getResponseAsJson() || {};\r\n                    ret._raw = tr.getResponse();\r\n                    fn = function( value ) {\r\n                        reject = value;\r\n                    };\r\n    \r\n                    // 服务端响应了，不代表成功了，询问是否响应正确。\r\n                    if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) {\r\n                        reject = reject || 'server';\r\n                    }\r\n    \r\n                    return reject;\r\n                };\r\n    \r\n                // 尝试重试，然后广播文件上传出错。\r\n                tr.on( 'error', function( type, flag ) {\r\n                    block.retried = block.retried || 0;\r\n    \r\n                    // 自动重试\r\n                    if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) &&\r\n                            block.retried < opts.chunkRetry ) {\r\n    \r\n                        block.retried++;\r\n                        tr.send();\r\n    \r\n                    } else {\r\n    \r\n                        // http status 500 ~ 600\r\n                        if ( !flag && type === 'server' ) {\r\n                            type = requestAccept( type );\r\n                        }\r\n    \r\n                        file.setStatus( Status.ERROR, type );\r\n                        owner.trigger( 'uploadError', file, type );\r\n                        owner.trigger( 'uploadComplete', file );\r\n                    }\r\n                });\r\n    \r\n                // 上传成功\r\n                tr.on( 'load', function() {\r\n                    var reason;\r\n    \r\n                    // 如果非预期，转向上传出错。\r\n                    if ( (reason = requestAccept()) ) {\r\n                        tr.trigger( 'error', reason, true );\r\n                        return;\r\n                    }\r\n    \r\n                    // 全部上传完成。\r\n                    if ( file.remaning === 1 ) {\r\n                        me._finishFile( file, ret );\r\n                    } else {\r\n                        tr.destroy();\r\n                    }\r\n                });\r\n    \r\n                // 配置默认的上传字段。\r\n                data = $.extend( data, {\r\n                    id: file.id,\r\n                    name: file.name,\r\n                    type: file.type,\r\n                    lastModifiedDate: file.lastModifiedDate,\r\n                    size: file.size\r\n                });\r\n    \r\n                block.chunks > 1 && $.extend( data, {\r\n                    chunks: block.chunks,\r\n                    chunk: block.chunk\r\n                });\r\n    \r\n                // 在发送之间可以添加字段什么的。。。\r\n                // 如果默认的字段不够使用，可以通过监听此事件来扩展\r\n                owner.trigger( 'uploadBeforeSend', block, data, headers );\r\n    \r\n                // 开始发送。\r\n                tr.appendBlob( opts.fileVal, block.blob, file.name );\r\n                tr.append( data );\r\n                tr.setRequestHeader( headers );\r\n                tr.send();\r\n            },\r\n    \r\n            // 完成上传。\r\n            _finishFile: function( file, ret, hds ) {\r\n                var owner = this.owner;\r\n    \r\n                return owner\r\n                        .request( 'after-send-file', arguments, function() {\r\n                            file.setStatus( Status.COMPLETE );\r\n                            owner.trigger( 'uploadSuccess', file, ret, hds );\r\n                        })\r\n                        .fail(function( reason ) {\r\n    \r\n                            // 如果外部已经标记为invalid什么的，不再改状态。\r\n                            if ( file.getStatus() === Status.PROGRESS ) {\r\n                                file.setStatus( Status.ERROR, reason );\r\n                            }\r\n    \r\n                            owner.trigger( 'uploadError', file, reason );\r\n                        })\r\n                        .always(function() {\r\n                            owner.trigger( 'uploadComplete', file );\r\n                        });\r\n            }\r\n    \r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview 各种验证，包括文件总大小是否超出、单文件是否超出和文件是否重复。\r\n     */\r\n    \r\n    define('widgets/validator',[\r\n        'base',\r\n        'uploader',\r\n        'file',\r\n        'widgets/widget'\r\n    ], function( Base, Uploader, WUFile ) {\r\n    \r\n        var $ = Base.$,\r\n            validators = {},\r\n            api;\r\n    \r\n        /**\r\n         * @event error\r\n         * @param {String} type 错误类型。\r\n         * @description 当validate不通过时，会以派送错误事件的形式通知调用者。通过`upload.on('error', handler)`可以捕获到此类错误，目前有以下错误会在特定的情况下派送错来。\r\n         *\r\n         * * `Q_EXCEED_NUM_LIMIT` 在设置了`fileNumLimit`且尝试给`uploader`添加的文件数量超出这个值时派送。\r\n         * * `Q_EXCEED_SIZE_LIMIT` 在设置了`Q_EXCEED_SIZE_LIMIT`且尝试给`uploader`添加的文件总大小超出这个值时派送。\r\n         * @for  Uploader\r\n         */\r\n    \r\n        // 暴露给外面的api\r\n        api = {\r\n    \r\n            // 添加验证器\r\n            addValidator: function( type, cb ) {\r\n                validators[ type ] = cb;\r\n            },\r\n    \r\n            // 移除验证器\r\n            removeValidator: function( type ) {\r\n                delete validators[ type ];\r\n            }\r\n        };\r\n    \r\n        // 在Uploader初始化的时候启动Validators的初始化\r\n        Uploader.register({\r\n            init: function() {\r\n                var me = this;\r\n                $.each( validators, function() {\r\n                    this.call( me.owner );\r\n                });\r\n            }\r\n        });\r\n    \r\n        /**\r\n         * @property {int} [fileNumLimit=undefined]\r\n         * @namespace options\r\n         * @for Uploader\r\n         * @description 验证文件总数量, 超出则不允许加入队列。\r\n         */\r\n        api.addValidator( 'fileNumLimit', function() {\r\n            var uploader = this,\r\n                opts = uploader.options,\r\n                count = 0,\r\n                max = opts.fileNumLimit >> 0,\r\n                flag = true;\r\n    \r\n            if ( !max ) {\r\n                return;\r\n            }\r\n    \r\n            uploader.on( 'beforeFileQueued', function( file ) {\r\n    \r\n                if ( count >= max && flag ) {\r\n                    flag = false;\r\n                    this.trigger( 'error', 'Q_EXCEED_NUM_LIMIT', max, file );\r\n                    setTimeout(function() {\r\n                        flag = true;\r\n                    }, 1 );\r\n                }\r\n    \r\n                return count >= max ? false : true;\r\n            });\r\n    \r\n            uploader.on( 'fileQueued', function() {\r\n                count++;\r\n            });\r\n    \r\n            uploader.on( 'fileDequeued', function() {\r\n                count--;\r\n            });\r\n    \r\n            uploader.on( 'uploadFinished', function() {\r\n                count = 0;\r\n            });\r\n        });\r\n    \r\n    \r\n        /**\r\n         * @property {int} [fileSizeLimit=undefined]\r\n         * @namespace options\r\n         * @for Uploader\r\n         * @description 验证文件总大小是否超出限制, 超出则不允许加入队列。\r\n         */\r\n        api.addValidator( 'fileSizeLimit', function() {\r\n            var uploader = this,\r\n                opts = uploader.options,\r\n                count = 0,\r\n                max = opts.fileSizeLimit >> 0,\r\n                flag = true;\r\n    \r\n            if ( !max ) {\r\n                return;\r\n            }\r\n    \r\n            uploader.on( 'beforeFileQueued', function( file ) {\r\n                var invalid = count + file.size > max;\r\n    \r\n                if ( invalid && flag ) {\r\n                    flag = false;\r\n                    this.trigger( 'error', 'Q_EXCEED_SIZE_LIMIT', max, file );\r\n                    setTimeout(function() {\r\n                        flag = true;\r\n                    }, 1 );\r\n                }\r\n    \r\n                return invalid ? false : true;\r\n            });\r\n    \r\n            uploader.on( 'fileQueued', function( file ) {\r\n                count += file.size;\r\n            });\r\n    \r\n            uploader.on( 'fileDequeued', function( file ) {\r\n                count -= file.size;\r\n            });\r\n    \r\n            uploader.on( 'uploadFinished', function() {\r\n                count = 0;\r\n            });\r\n        });\r\n    \r\n        /**\r\n         * @property {int} [fileSingleSizeLimit=undefined]\r\n         * @namespace options\r\n         * @for Uploader\r\n         * @description 验证单个文件大小是否超出限制, 超出则不允许加入队列。\r\n         */\r\n        api.addValidator( 'fileSingleSizeLimit', function() {\r\n            var uploader = this,\r\n                opts = uploader.options,\r\n                max = opts.fileSingleSizeLimit;\r\n    \r\n            if ( !max ) {\r\n                return;\r\n            }\r\n    \r\n            uploader.on( 'beforeFileQueued', function( file ) {\r\n    \r\n                if ( file.size > max ) {\r\n                    file.setStatus( WUFile.Status.INVALID, 'exceed_size' );\r\n                    this.trigger( 'error', 'F_EXCEED_SIZE', file );\r\n                    return false;\r\n                }\r\n    \r\n            });\r\n    \r\n        });\r\n    \r\n        /**\r\n         * @property {int} [duplicate=undefined]\r\n         * @namespace options\r\n         * @for Uploader\r\n         * @description 去重， 根据文件名字、文件大小和最后修改时间来生成hash Key.\r\n         */\r\n        api.addValidator( 'duplicate', function() {\r\n            var uploader = this,\r\n                opts = uploader.options,\r\n                mapping = {};\r\n    \r\n            if ( opts.duplicate ) {\r\n                return;\r\n            }\r\n    \r\n            function hashString( str ) {\r\n                var hash = 0,\r\n                    i = 0,\r\n                    len = str.length,\r\n                    _char;\r\n    \r\n                for ( ; i < len; i++ ) {\r\n                    _char = str.charCodeAt( i );\r\n                    hash = _char + (hash << 6) + (hash << 16) - hash;\r\n                }\r\n    \r\n                return hash;\r\n            }\r\n    \r\n            uploader.on( 'beforeFileQueued', function( file ) {\r\n                var hash = file.__hash || (file.__hash = hashString( file.name +\r\n                        file.size + file.lastModifiedDate ));\r\n    \r\n                // 已经重复了\r\n                if ( mapping[ hash ] ) {\r\n                    this.trigger( 'error', 'F_DUPLICATE', file );\r\n                    return false;\r\n                }\r\n            });\r\n    \r\n            uploader.on( 'fileQueued', function( file ) {\r\n                var hash = file.__hash;\r\n    \r\n                hash && (mapping[ hash ] = true);\r\n            });\r\n    \r\n            uploader.on( 'fileDequeued', function( file ) {\r\n                var hash = file.__hash;\r\n    \r\n                hash && (delete mapping[ hash ]);\r\n            });\r\n        });\r\n    \r\n        return api;\r\n    });\r\n    \r\n    /**\r\n     * @fileOverview Runtime管理器，负责Runtime的选择, 连接\r\n     */\r\n    define('runtime/compbase',[],function() {\r\n    \r\n        function CompBase( owner, runtime ) {\r\n    \r\n            this.owner = owner;\r\n            this.options = owner.options;\r\n    \r\n            this.getRuntime = function() {\r\n                return runtime;\r\n            };\r\n    \r\n            this.getRuid = function() {\r\n                return runtime.uid;\r\n            };\r\n    \r\n            this.trigger = function() {\r\n                return owner.trigger.apply( owner, arguments );\r\n            };\r\n        }\r\n    \r\n        return CompBase;\r\n    });\r\n    /**\r\n     * @fileOverview FlashRuntime\r\n     */\r\n    define('runtime/flash/runtime',[\r\n        'base',\r\n        'runtime/runtime',\r\n        'runtime/compbase'\r\n    ], function( Base, Runtime, CompBase ) {\r\n    \r\n        var $ = Base.$,\r\n            type = 'flash',\r\n            components = {};\r\n    \r\n    \r\n        function getFlashVersion() {\r\n            var version;\r\n    \r\n            try {\r\n                version = navigator.plugins[ 'Shockwave Flash' ];\r\n                version = version.description;\r\n            } catch ( ex ) {\r\n                try {\r\n                    version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash')\r\n                            .GetVariable('$version');\r\n                } catch ( ex2 ) {\r\n                    version = '0.0';\r\n                }\r\n            }\r\n            version = version.match( /\\d+/g );\r\n            return parseFloat( version[ 0 ] + '.' + version[ 1 ], 10 );\r\n        }\r\n    \r\n        function FlashRuntime() {\r\n            var pool = {},\r\n                clients = {},\r\n                destory = this.destory,\r\n                me = this,\r\n                jsreciver = Base.guid('webuploader_');\r\n    \r\n            Runtime.apply( me, arguments );\r\n            me.type = type;\r\n    \r\n    \r\n            // 这个方法的调用者，实际上是RuntimeClient\r\n            me.exec = function( comp, fn/*, args...*/ ) {\r\n                var client = this,\r\n                    uid = client.uid,\r\n                    args = Base.slice( arguments, 2 ),\r\n                    instance;\r\n    \r\n                clients[ uid ] = client;\r\n    \r\n                if ( components[ comp ] ) {\r\n                    if ( !pool[ uid ] ) {\r\n                        pool[ uid ] = new components[ comp ]( client, me );\r\n                    }\r\n    \r\n                    instance = pool[ uid ];\r\n    \r\n                    if ( instance[ fn ] ) {\r\n                        return instance[ fn ].apply( instance, args );\r\n                    }\r\n                }\r\n    \r\n                return me.flashExec.apply( client, arguments );\r\n            };\r\n    \r\n            function handler( evt, obj ) {\r\n                var type = evt.type || evt,\r\n                    parts, uid;\r\n    \r\n                parts = type.split('::');\r\n                uid = parts[ 0 ];\r\n                type = parts[ 1 ];\r\n    \r\n                // console.log.apply( console, arguments );\r\n    \r\n                if ( type === 'Ready' && uid === me.uid ) {\r\n                    me.trigger('ready');\r\n                } else if ( clients[ uid ] ) {\r\n                    clients[ uid ].trigger( type.toLowerCase(), evt, obj );\r\n                }\r\n    \r\n                // Base.log( evt, obj );\r\n            }\r\n    \r\n            // flash的接受器。\r\n            window[ jsreciver ] = function() {\r\n                var args = arguments;\r\n    \r\n                // 为了能捕获得到。\r\n                setTimeout(function() {\r\n                    handler.apply( null, args );\r\n                }, 1 );\r\n            };\r\n    \r\n            this.jsreciver = jsreciver;\r\n    \r\n            this.destory = function() {\r\n                // @todo 删除池子中的所有实例\r\n                return destory && destory.apply( this, arguments );\r\n            };\r\n    \r\n            this.flashExec = function( comp, fn ) {\r\n                var flash = me.getFlash(),\r\n                    args = Base.slice( arguments, 2 );\r\n    \r\n                return flash.exec( this.uid, comp, fn, args );\r\n            };\r\n    \r\n            // @todo\r\n        }\r\n    \r\n        Base.inherits( Runtime, {\r\n            constructor: FlashRuntime,\r\n    \r\n            init: function() {\r\n                var container = this.getContainer(),\r\n                    opts = this.options,\r\n                    html;\r\n    \r\n                // if not the minimal height, shims are not initialized\r\n                // in older browsers (e.g FF3.6, IE6,7,8, Safari 4.0,5.0, etc)\r\n                container.css({\r\n                    position: 'absolute',\r\n                    top: '-8px',\r\n                    left: '-8px',\r\n                    width: '9px',\r\n                    height: '9px',\r\n                    overflow: 'hidden'\r\n                });\r\n    \r\n                // insert flash object\r\n                html = '<object id=\"' + this.uid + '\" type=\"application/' +\r\n                        'x-shockwave-flash\" data=\"' +  opts.swf + '\" ';\r\n    \r\n                if ( Base.browser.ie ) {\r\n                    html += 'classid=\"clsid:d27cdb6e-ae6d-11cf-96b8-444553540000\" ';\r\n                }\r\n    \r\n                html += 'width=\"100%\" height=\"100%\" style=\"outline:0\">'  +\r\n                    '<param name=\"movie\" value=\"' + opts.swf + '\" />' +\r\n                    '<param name=\"flashvars\" value=\"uid=' + this.uid +\r\n                    '&jsreciver=' + this.jsreciver + '\" />' +\r\n                    '<param name=\"wmode\" value=\"transparent\" />' +\r\n                    '<param name=\"allowscriptaccess\" value=\"always\" />' +\r\n                '</object>';\r\n    \r\n                container.html( html );\r\n            },\r\n    \r\n            getFlash: function() {\r\n                if ( this._flash ) {\r\n                    return this._flash;\r\n                }\r\n    \r\n                this._flash = $( '#' + this.uid ).get( 0 );\r\n                return this._flash;\r\n            }\r\n    \r\n        });\r\n    \r\n        FlashRuntime.register = function( name, component ) {\r\n            component = components[ name ] = Base.inherits( CompBase, $.extend({\r\n    \r\n                // @todo fix this later\r\n                flashExec: function() {\r\n                    var owner = this.owner,\r\n                        runtime = this.getRuntime();\r\n    \r\n                    return runtime.flashExec.apply( owner, arguments );\r\n                }\r\n            }, component ) );\r\n    \r\n            return component;\r\n        };\r\n    \r\n        if ( getFlashVersion() >= 11.4 ) {\r\n            Runtime.addRuntime( type, FlashRuntime );\r\n        }\r\n    \r\n        return FlashRuntime;\r\n    });\r\n    /**\r\n     * @fileOverview FilePicker\r\n     */\r\n    define('runtime/flash/filepicker',[\r\n        'base',\r\n        'runtime/flash/runtime'\r\n    ], function( Base, FlashRuntime ) {\r\n        var $ = Base.$;\r\n    \r\n        return FlashRuntime.register( 'FilePicker', {\r\n            init: function( opts ) {\r\n                var copy = $.extend({}, opts ),\r\n                    len, i;\r\n    \r\n                // 修复Flash再没有设置title的情况下无法弹出flash文件选择框的bug.\r\n                len = copy.accept && copy.accept.length;\r\n                for (  i = 0; i < len; i++ ) {\r\n                    if ( !copy.accept[ i ].title ) {\r\n                        copy.accept[ i ].title = 'Files';\r\n                    }\r\n                }\r\n    \r\n                delete copy.button;\r\n                delete copy.container;\r\n    \r\n                this.flashExec( 'FilePicker', 'init', copy );\r\n            },\r\n    \r\n            destroy: function() {\r\n                // todo\r\n            }\r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview 图片压缩\r\n     */\r\n    define('runtime/flash/image',[\r\n        'runtime/flash/runtime'\r\n    ], function( FlashRuntime ) {\r\n    \r\n        return FlashRuntime.register( 'Image', {\r\n            // init: function( options ) {\r\n            //     var owner = this.owner;\r\n    \r\n            //     this.flashExec( 'Image', 'init', options );\r\n            //     owner.on( 'load', function() {\r\n            //         debugger;\r\n            //     });\r\n            // },\r\n    \r\n            loadFromBlob: function( blob ) {\r\n                var owner = this.owner;\r\n    \r\n                owner.info() && this.flashExec( 'Image', 'info', owner.info() );\r\n                owner.meta() && this.flashExec( 'Image', 'meta', owner.meta() );\r\n    \r\n                this.flashExec( 'Image', 'loadFromBlob', blob.uid );\r\n            }\r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview  Transport flash实现\r\n     */\r\n    define('runtime/flash/transport',[\r\n        'base',\r\n        'runtime/flash/runtime',\r\n        'runtime/client'\r\n    ], function( Base, FlashRuntime, RuntimeClient ) {\r\n        var $ = Base.$;\r\n    \r\n        return FlashRuntime.register( 'Transport', {\r\n            init: function() {\r\n                this._status = 0;\r\n                this._response = null;\r\n                this._responseJson = null;\r\n            },\r\n    \r\n            send: function() {\r\n                var owner = this.owner,\r\n                    opts = this.options,\r\n                    xhr = this._initAjax(),\r\n                    blob = owner._blob,\r\n                    server = opts.server,\r\n                    binary;\r\n    \r\n                xhr.connectRuntime( blob.ruid );\r\n    \r\n                if ( opts.sendAsBinary ) {\r\n                    server += (/\\?/.test( server ) ? '&' : '?') +\r\n                            $.param( owner._formData );\r\n    \r\n                    binary = blob.uid;\r\n                } else {\r\n                    $.each( owner._formData, function( k, v ) {\r\n                        xhr.exec( 'append', k, v );\r\n                    });\r\n    \r\n                    xhr.exec( 'appendBlob', opts.fileVal, blob.uid,\r\n                            opts.filename || owner._formData.name || '' );\r\n                }\r\n    \r\n                this._setRequestHeader( xhr, opts.headers );\r\n                xhr.exec( 'send', {\r\n                    method: opts.method,\r\n                    url: server\r\n                }, binary );\r\n            },\r\n    \r\n            getStatus: function() {\r\n                return this._status;\r\n            },\r\n    \r\n            getResponse: function() {\r\n                return this._response;\r\n            },\r\n    \r\n            getResponseAsJson: function() {\r\n                return this._responseJson;\r\n            },\r\n    \r\n            abort: function() {\r\n                var xhr = this._xhr;\r\n    \r\n                if ( xhr ) {\r\n                    xhr.exec('abort');\r\n                    xhr.destroy();\r\n                    this._xhr = xhr = null;\r\n                }\r\n            },\r\n    \r\n            destroy: function() {\r\n                this.abort();\r\n            },\r\n    \r\n            _initAjax: function() {\r\n                var me = this,\r\n                    xhr = new RuntimeClient('XMLHttpRequest');\r\n    \r\n                xhr.on( 'uploadprogress progress', function( e ) {\r\n                    return me.trigger( 'progress', e.loaded / e.total );\r\n                });\r\n    \r\n                xhr.on( 'load', function() {\r\n                    var status = xhr.exec('getStatus'),\r\n                        err = '';\r\n    \r\n                    xhr.off();\r\n                    me._xhr = null;\r\n    \r\n                    if ( status >= 200 && status < 300 ) {\r\n                        me._response = xhr.exec('getResponse');\r\n                        me._responseJson = xhr.exec('getResponseAsJson');\r\n                    } else if ( status >= 500 && status < 600 ) {\r\n                        me._response = xhr.exec('getResponse');\r\n                        me._responseJson = xhr.exec('getResponseAsJson');\r\n                        err = 'server';\r\n                    } else {\r\n                        err = 'http';\r\n                    }\r\n    \r\n                    xhr.destroy();\r\n                    xhr = null;\r\n    \r\n                    return err ? me.trigger( 'error', err ) : me.trigger('load');\r\n                });\r\n    \r\n                xhr.on( 'error', function() {\r\n                    xhr.off();\r\n                    me._xhr = null;\r\n                    me.trigger( 'error', 'http' );\r\n                });\r\n    \r\n                me._xhr = xhr;\r\n                return xhr;\r\n            },\r\n    \r\n            _setRequestHeader: function( xhr, headers ) {\r\n                $.each( headers, function( key, val ) {\r\n                    xhr.exec( 'setRequestHeader', key, val );\r\n                });\r\n            }\r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview 只有flash实现的文件版本。\r\n     */\r\n    define('preset/flashonly',[\r\n        'base',\r\n    \r\n        // widgets\r\n        'widgets/filepicker',\r\n        'widgets/image',\r\n        'widgets/queue',\r\n        'widgets/runtime',\r\n        'widgets/upload',\r\n        'widgets/validator',\r\n    \r\n        // runtimes\r\n    \r\n        // flash\r\n        'runtime/flash/filepicker',\r\n        'runtime/flash/image',\r\n        'runtime/flash/transport'\r\n    ], function( Base ) {\r\n        return Base;\r\n    });\r\n    define('webuploader',[\r\n        'preset/flashonly'\r\n    ], function( preset ) {\r\n        return preset;\r\n    });\r\n    return require('webuploader');\r\n});\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/webuploader/webuploader.html5only.js",
    "content": "/*! WebUploader 0.1.2 */\r\n\r\n\r\n/**\r\n * @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。\r\n *\r\n * AMD API 内部的简单不完全实现，请忽略。只有当WebUploader被合并成一个文件的时候才会引入。\r\n */\r\n(function( root, factory ) {\r\n    var modules = {},\r\n\r\n        // 内部require, 简单不完全实现。\r\n        // https://github.com/amdjs/amdjs-api/wiki/require\r\n        _require = function( deps, callback ) {\r\n            var args, len, i;\r\n\r\n            // 如果deps不是数组，则直接返回指定module\r\n            if ( typeof deps === 'string' ) {\r\n                return getModule( deps );\r\n            } else {\r\n                args = [];\r\n                for( len = deps.length, i = 0; i < len; i++ ) {\r\n                    args.push( getModule( deps[ i ] ) );\r\n                }\r\n\r\n                return callback.apply( null, args );\r\n            }\r\n        },\r\n\r\n        // 内部define，暂时不支持不指定id.\r\n        _define = function( id, deps, factory ) {\r\n            if ( arguments.length === 2 ) {\r\n                factory = deps;\r\n                deps = null;\r\n            }\r\n\r\n            _require( deps || [], function() {\r\n                setModule( id, factory, arguments );\r\n            });\r\n        },\r\n\r\n        // 设置module, 兼容CommonJs写法。\r\n        setModule = function( id, factory, args ) {\r\n            var module = {\r\n                    exports: factory\r\n                },\r\n                returned;\r\n\r\n            if ( typeof factory === 'function' ) {\r\n                args.length || (args = [ _require, module.exports, module ]);\r\n                returned = factory.apply( null, args );\r\n                returned !== undefined && (module.exports = returned);\r\n            }\r\n\r\n            modules[ id ] = module.exports;\r\n        },\r\n\r\n        // 根据id获取module\r\n        getModule = function( id ) {\r\n            var module = modules[ id ] || root[ id ];\r\n\r\n            if ( !module ) {\r\n                throw new Error( '`' + id + '` is undefined' );\r\n            }\r\n\r\n            return module;\r\n        },\r\n\r\n        // 将所有modules，将路径ids装换成对象。\r\n        exportsTo = function( obj ) {\r\n            var key, host, parts, part, last, ucFirst;\r\n\r\n            // make the first character upper case.\r\n            ucFirst = function( str ) {\r\n                return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 ));\r\n            };\r\n\r\n            for ( key in modules ) {\r\n                host = obj;\r\n\r\n                if ( !modules.hasOwnProperty( key ) ) {\r\n                    continue;\r\n                }\r\n\r\n                parts = key.split('/');\r\n                last = ucFirst( parts.pop() );\r\n\r\n                while( (part = ucFirst( parts.shift() )) ) {\r\n                    host[ part ] = host[ part ] || {};\r\n                    host = host[ part ];\r\n                }\r\n\r\n                host[ last ] = modules[ key ];\r\n            }\r\n        },\r\n\r\n        exports = factory( root, _define, _require ),\r\n        origin;\r\n\r\n    // exports every module.\r\n    exportsTo( exports );\r\n\r\n    if ( typeof module === 'object' && typeof module.exports === 'object' ) {\r\n\r\n        // For CommonJS and CommonJS-like environments where a proper window is present,\r\n        module.exports = exports;\r\n    } else if ( typeof define === 'function' && define.amd ) {\r\n\r\n        // Allow using this built library as an AMD module\r\n        // in another project. That other project will only\r\n        // see this AMD call, not the internal modules in\r\n        // the closure below.\r\n        define([], exports );\r\n    } else {\r\n\r\n        // Browser globals case. Just assign the\r\n        // result to a property on the global.\r\n        origin = root.WebUploader;\r\n        root.WebUploader = exports;\r\n        root.WebUploader.noConflict = function() {\r\n            root.WebUploader = origin;\r\n        };\r\n    }\r\n})( this, function( window, define, require ) {\r\n\r\n\r\n    /**\r\n     * @fileOverview jQuery or Zepto\r\n     */\r\n    define('dollar-third',[],function() {\r\n        return window.jQuery || window.Zepto;\r\n    });\r\n    /**\r\n     * @fileOverview Dom 操作相关\r\n     */\r\n    define('dollar',[\r\n        'dollar-third'\r\n    ], function( _ ) {\r\n        return _;\r\n    });\r\n    /**\r\n     * @fileOverview 使用jQuery的Promise\r\n     */\r\n    define('promise-third',[\r\n        'dollar'\r\n    ], function( $ ) {\r\n        return {\r\n            Deferred: $.Deferred,\r\n            when: $.when,\r\n    \r\n            isPromise: function( anything ) {\r\n                return anything && typeof anything.then === 'function';\r\n            }\r\n        };\r\n    });\r\n    /**\r\n     * @fileOverview Promise/A+\r\n     */\r\n    define('promise',[\r\n        'promise-third'\r\n    ], function( _ ) {\r\n        return _;\r\n    });\r\n    /**\r\n     * @fileOverview 基础类方法。\r\n     */\r\n    \r\n    /**\r\n     * Web Uploader内部类的详细说明，以下提及的功能类，都可以在`WebUploader`这个变量中访问到。\r\n     *\r\n     * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id.\r\n     * 默认module id该文件的路径，而此路径将会转化成名字空间存放在WebUploader中。如：\r\n     *\r\n     * * module `base`：WebUploader.Base\r\n     * * module `file`: WebUploader.File\r\n     * * module `lib/dnd`: WebUploader.Lib.Dnd\r\n     * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd\r\n     *\r\n     *\r\n     * 以下文档将可能省略`WebUploader`前缀。\r\n     * @module WebUploader\r\n     * @title WebUploader API文档\r\n     */\r\n    define('base',[\r\n        'dollar',\r\n        'promise'\r\n    ], function( $, promise ) {\r\n    \r\n        var noop = function() {},\r\n            call = Function.call;\r\n    \r\n        // http://jsperf.com/uncurrythis\r\n        // 反科里化\r\n        function uncurryThis( fn ) {\r\n            return function() {\r\n                return call.apply( fn, arguments );\r\n            };\r\n        }\r\n    \r\n        function bindFn( fn, context ) {\r\n            return function() {\r\n                return fn.apply( context, arguments );\r\n            };\r\n        }\r\n    \r\n        function createObject( proto ) {\r\n            var f;\r\n    \r\n            if ( Object.create ) {\r\n                return Object.create( proto );\r\n            } else {\r\n                f = function() {};\r\n                f.prototype = proto;\r\n                return new f();\r\n            }\r\n        }\r\n    \r\n    \r\n        /**\r\n         * 基础类，提供一些简单常用的方法。\r\n         * @class Base\r\n         */\r\n        return {\r\n    \r\n            /**\r\n             * @property {String} version 当前版本号。\r\n             */\r\n            version: '0.1.2',\r\n    \r\n            /**\r\n             * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。\r\n             */\r\n            $: $,\r\n    \r\n            Deferred: promise.Deferred,\r\n    \r\n            isPromise: promise.isPromise,\r\n    \r\n            when: promise.when,\r\n    \r\n            /**\r\n             * @description  简单的浏览器检查结果。\r\n             *\r\n             * * `webkit`  webkit版本号，如果浏览器为非webkit内核，此属性为`undefined`。\r\n             * * `chrome`  chrome浏览器版本号，如果浏览器为chrome，此属性为`undefined`。\r\n             * * `ie`  ie浏览器版本号，如果浏览器为非ie，此属性为`undefined`。**暂不支持ie10+**\r\n             * * `firefox`  firefox浏览器版本号，如果浏览器为非firefox，此属性为`undefined`。\r\n             * * `safari`  safari浏览器版本号，如果浏览器为非safari，此属性为`undefined`。\r\n             * * `opera`  opera浏览器版本号，如果浏览器为非opera，此属性为`undefined`。\r\n             *\r\n             * @property {Object} [browser]\r\n             */\r\n            browser: (function( ua ) {\r\n                var ret = {},\r\n                    webkit = ua.match( /WebKit\\/([\\d.]+)/ ),\r\n                    chrome = ua.match( /Chrome\\/([\\d.]+)/ ) ||\r\n                        ua.match( /CriOS\\/([\\d.]+)/ ),\r\n    \r\n                    ie = ua.match( /MSIE\\s([\\d\\.]+)/ ) ||\r\n                        ua.match(/(?:trident)(?:.*rv:([\\w.]+))?/i),\r\n                    firefox = ua.match( /Firefox\\/([\\d.]+)/ ),\r\n                    safari = ua.match( /Safari\\/([\\d.]+)/ ),\r\n                    opera = ua.match( /OPR\\/([\\d.]+)/ );\r\n    \r\n                webkit && (ret.webkit = parseFloat( webkit[ 1 ] ));\r\n                chrome && (ret.chrome = parseFloat( chrome[ 1 ] ));\r\n                ie && (ret.ie = parseFloat( ie[ 1 ] ));\r\n                firefox && (ret.firefox = parseFloat( firefox[ 1 ] ));\r\n                safari && (ret.safari = parseFloat( safari[ 1 ] ));\r\n                opera && (ret.opera = parseFloat( opera[ 1 ] ));\r\n    \r\n                return ret;\r\n            })( navigator.userAgent ),\r\n    \r\n            /**\r\n             * @description  操作系统检查结果。\r\n             *\r\n             * * `android`  如果在android浏览器环境下，此值为对应的android版本号，否则为`undefined`。\r\n             * * `ios` 如果在ios浏览器环境下，此值为对应的ios版本号，否则为`undefined`。\r\n             * @property {Object} [os]\r\n             */\r\n            os: (function( ua ) {\r\n                var ret = {},\r\n    \r\n                    // osx = !!ua.match( /\\(Macintosh\\; Intel / ),\r\n                    android = ua.match( /(?:Android);?[\\s\\/]+([\\d.]+)?/ ),\r\n                    ios = ua.match( /(?:iPad|iPod|iPhone).*OS\\s([\\d_]+)/ );\r\n    \r\n                // osx && (ret.osx = true);\r\n                android && (ret.android = parseFloat( android[ 1 ] ));\r\n                ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) ));\r\n    \r\n                return ret;\r\n            })( navigator.userAgent ),\r\n    \r\n            /**\r\n             * 实现类与类之间的继承。\r\n             * @method inherits\r\n             * @grammar Base.inherits( super ) => child\r\n             * @grammar Base.inherits( super, protos ) => child\r\n             * @grammar Base.inherits( super, protos, statics ) => child\r\n             * @param  {Class} super 父类\r\n             * @param  {Object | Function} [protos] 子类或者对象。如果对象中包含constructor，子类将是用此属性值。\r\n             * @param  {Function} [protos.constructor] 子类构造器，不指定的话将创建个临时的直接执行父类构造器的方法。\r\n             * @param  {Object} [statics] 静态属性或方法。\r\n             * @return {Class} 返回子类。\r\n             * @example\r\n             * function Person() {\r\n             *     console.log( 'Super' );\r\n             * }\r\n             * Person.prototype.hello = function() {\r\n             *     console.log( 'hello' );\r\n             * };\r\n             *\r\n             * var Manager = Base.inherits( Person, {\r\n             *     world: function() {\r\n             *         console.log( 'World' );\r\n             *     }\r\n             * });\r\n             *\r\n             * // 因为没有指定构造器，父类的构造器将会执行。\r\n             * var instance = new Manager();    // => Super\r\n             *\r\n             * // 继承子父类的方法\r\n             * instance.hello();    // => hello\r\n             * instance.world();    // => World\r\n             *\r\n             * // 子类的__super__属性指向父类\r\n             * console.log( Manager.__super__ === Person );    // => true\r\n             */\r\n            inherits: function( Super, protos, staticProtos ) {\r\n                var child;\r\n    \r\n                if ( typeof protos === 'function' ) {\r\n                    child = protos;\r\n                    protos = null;\r\n                } else if ( protos && protos.hasOwnProperty('constructor') ) {\r\n                    child = protos.constructor;\r\n                } else {\r\n                    child = function() {\r\n                        return Super.apply( this, arguments );\r\n                    };\r\n                }\r\n    \r\n                // 复制静态方法\r\n                $.extend( true, child, Super, staticProtos || {} );\r\n    \r\n                /* jshint camelcase: false */\r\n    \r\n                // 让子类的__super__属性指向父类。\r\n                child.__super__ = Super.prototype;\r\n    \r\n                // 构建原型，添加原型方法或属性。\r\n                // 暂时用Object.create实现。\r\n                child.prototype = createObject( Super.prototype );\r\n                protos && $.extend( true, child.prototype, protos );\r\n    \r\n                return child;\r\n            },\r\n    \r\n            /**\r\n             * 一个不做任何事情的方法。可以用来赋值给默认的callback.\r\n             * @method noop\r\n             */\r\n            noop: noop,\r\n    \r\n            /**\r\n             * 返回一个新的方法，此方法将已指定的`context`来执行。\r\n             * @grammar Base.bindFn( fn, context ) => Function\r\n             * @method bindFn\r\n             * @example\r\n             * var doSomething = function() {\r\n             *         console.log( this.name );\r\n             *     },\r\n             *     obj = {\r\n             *         name: 'Object Name'\r\n             *     },\r\n             *     aliasFn = Base.bind( doSomething, obj );\r\n             *\r\n             *  aliasFn();    // => Object Name\r\n             *\r\n             */\r\n            bindFn: bindFn,\r\n    \r\n            /**\r\n             * 引用Console.log如果存在的话，否则引用一个[空函数loop](#WebUploader:Base.log)。\r\n             * @grammar Base.log( args... ) => undefined\r\n             * @method log\r\n             */\r\n            log: (function() {\r\n                if ( window.console ) {\r\n                    return bindFn( console.log, console );\r\n                }\r\n                return noop;\r\n            })(),\r\n    \r\n            nextTick: (function() {\r\n    \r\n                return function( cb ) {\r\n                    setTimeout( cb, 1 );\r\n                };\r\n    \r\n                // @bug 当浏览器不在当前窗口时就停了。\r\n                // var next = window.requestAnimationFrame ||\r\n                //     window.webkitRequestAnimationFrame ||\r\n                //     window.mozRequestAnimationFrame ||\r\n                //     function( cb ) {\r\n                //         window.setTimeout( cb, 1000 / 60 );\r\n                //     };\r\n    \r\n                // // fix: Uncaught TypeError: Illegal invocation\r\n                // return bindFn( next, window );\r\n            })(),\r\n    \r\n            /**\r\n             * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。\r\n             * 将用来将非数组对象转化成数组对象。\r\n             * @grammar Base.slice( target, start[, end] ) => Array\r\n             * @method slice\r\n             * @example\r\n             * function doSomthing() {\r\n             *     var args = Base.slice( arguments, 1 );\r\n             *     console.log( args );\r\n             * }\r\n             *\r\n             * doSomthing( 'ignored', 'arg2', 'arg3' );    // => Array [\"arg2\", \"arg3\"]\r\n             */\r\n            slice: uncurryThis( [].slice ),\r\n    \r\n            /**\r\n             * 生成唯一的ID\r\n             * @method guid\r\n             * @grammar Base.guid() => String\r\n             * @grammar Base.guid( prefx ) => String\r\n             */\r\n            guid: (function() {\r\n                var counter = 0;\r\n    \r\n                return function( prefix ) {\r\n                    var guid = (+new Date()).toString( 32 ),\r\n                        i = 0;\r\n    \r\n                    for ( ; i < 5; i++ ) {\r\n                        guid += Math.floor( Math.random() * 65535 ).toString( 32 );\r\n                    }\r\n    \r\n                    return (prefix || 'wu_') + guid + (counter++).toString( 32 );\r\n                };\r\n            })(),\r\n    \r\n            /**\r\n             * 格式化文件大小, 输出成带单位的字符串\r\n             * @method formatSize\r\n             * @grammar Base.formatSize( size ) => String\r\n             * @grammar Base.formatSize( size, pointLength ) => String\r\n             * @grammar Base.formatSize( size, pointLength, units ) => String\r\n             * @param {Number} size 文件大小\r\n             * @param {Number} [pointLength=2] 精确到的小数点数。\r\n             * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节，到千字节，一直往上指定。如果单位数组里面只指定了到了K(千字节)，同时文件大小大于M, 此方法的输出将还是显示成多少K.\r\n             * @example\r\n             * console.log( Base.formatSize( 100 ) );    // => 100B\r\n             * console.log( Base.formatSize( 1024 ) );    // => 1.00K\r\n             * console.log( Base.formatSize( 1024, 0 ) );    // => 1K\r\n             * console.log( Base.formatSize( 1024 * 1024 ) );    // => 1.00M\r\n             * console.log( Base.formatSize( 1024 * 1024 * 1024 ) );    // => 1.00G\r\n             * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) );    // => 1024MB\r\n             */\r\n            formatSize: function( size, pointLength, units ) {\r\n                var unit;\r\n    \r\n                units = units || [ 'B', 'K', 'M', 'G', 'TB' ];\r\n    \r\n                while ( (unit = units.shift()) && size > 1024 ) {\r\n                    size = size / 1024;\r\n                }\r\n    \r\n                return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) +\r\n                        unit;\r\n            }\r\n        };\r\n    });\r\n    /**\r\n     * 事件处理类，可以独立使用，也可以扩展给对象使用。\r\n     * @fileOverview Mediator\r\n     */\r\n    define('mediator',[\r\n        'base'\r\n    ], function( Base ) {\r\n        var $ = Base.$,\r\n            slice = [].slice,\r\n            separator = /\\s+/,\r\n            protos;\r\n    \r\n        // 根据条件过滤出事件handlers.\r\n        function findHandlers( arr, name, callback, context ) {\r\n            return $.grep( arr, function( handler ) {\r\n                return handler &&\r\n                        (!name || handler.e === name) &&\r\n                        (!callback || handler.cb === callback ||\r\n                        handler.cb._cb === callback) &&\r\n                        (!context || handler.ctx === context);\r\n            });\r\n        }\r\n    \r\n        function eachEvent( events, callback, iterator ) {\r\n            // 不支持对象，只支持多个event用空格隔开\r\n            $.each( (events || '').split( separator ), function( _, key ) {\r\n                iterator( key, callback );\r\n            });\r\n        }\r\n    \r\n        function triggerHanders( events, args ) {\r\n            var stoped = false,\r\n                i = -1,\r\n                len = events.length,\r\n                handler;\r\n    \r\n            while ( ++i < len ) {\r\n                handler = events[ i ];\r\n    \r\n                if ( handler.cb.apply( handler.ctx2, args ) === false ) {\r\n                    stoped = true;\r\n                    break;\r\n                }\r\n            }\r\n    \r\n            return !stoped;\r\n        }\r\n    \r\n        protos = {\r\n    \r\n            /**\r\n             * 绑定事件。\r\n             *\r\n             * `callback`方法在执行时，arguments将会来源于trigger的时候携带的参数。如\r\n             * ```javascript\r\n             * var obj = {};\r\n             *\r\n             * // 使得obj有事件行为\r\n             * Mediator.installTo( obj );\r\n             *\r\n             * obj.on( 'testa', function( arg1, arg2 ) {\r\n             *     console.log( arg1, arg2 ); // => 'arg1', 'arg2'\r\n             * });\r\n             *\r\n             * obj.trigger( 'testa', 'arg1', 'arg2' );\r\n             * ```\r\n             *\r\n             * 如果`callback`中，某一个方法`return false`了，则后续的其他`callback`都不会被执行到。\r\n             * 切会影响到`trigger`方法的返回值，为`false`。\r\n             *\r\n             * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处，\r\n             * 就是第一个参数为`type`，记录当前是什么事件在触发。此类`callback`的优先级比脚低，会再正常`callback`执行完后触发。\r\n             * ```javascript\r\n             * obj.on( 'all', function( type, arg1, arg2 ) {\r\n             *     console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2'\r\n             * });\r\n             * ```\r\n             *\r\n             * @method on\r\n             * @grammar on( name, callback[, context] ) => self\r\n             * @param  {String}   name     事件名，支持多个事件用空格隔开\r\n             * @param  {Function} callback 事件处理器\r\n             * @param  {Object}   [context]  事件处理器的上下文。\r\n             * @return {self} 返回自身，方便链式\r\n             * @chainable\r\n             * @class Mediator\r\n             */\r\n            on: function( name, callback, context ) {\r\n                var me = this,\r\n                    set;\r\n    \r\n                if ( !callback ) {\r\n                    return this;\r\n                }\r\n    \r\n                set = this._events || (this._events = []);\r\n    \r\n                eachEvent( name, callback, function( name, callback ) {\r\n                    var handler = { e: name };\r\n    \r\n                    handler.cb = callback;\r\n                    handler.ctx = context;\r\n                    handler.ctx2 = context || me;\r\n                    handler.id = set.length;\r\n    \r\n                    set.push( handler );\r\n                });\r\n    \r\n                return this;\r\n            },\r\n    \r\n            /**\r\n             * 绑定事件，且当handler执行完后，自动解除绑定。\r\n             * @method once\r\n             * @grammar once( name, callback[, context] ) => self\r\n             * @param  {String}   name     事件名\r\n             * @param  {Function} callback 事件处理器\r\n             * @param  {Object}   [context]  事件处理器的上下文。\r\n             * @return {self} 返回自身，方便链式\r\n             * @chainable\r\n             */\r\n            once: function( name, callback, context ) {\r\n                var me = this;\r\n    \r\n                if ( !callback ) {\r\n                    return me;\r\n                }\r\n    \r\n                eachEvent( name, callback, function( name, callback ) {\r\n                    var once = function() {\r\n                            me.off( name, once );\r\n                            return callback.apply( context || me, arguments );\r\n                        };\r\n    \r\n                    once._cb = callback;\r\n                    me.on( name, once, context );\r\n                });\r\n    \r\n                return me;\r\n            },\r\n    \r\n            /**\r\n             * 解除事件绑定\r\n             * @method off\r\n             * @grammar off( [name[, callback[, context] ] ] ) => self\r\n             * @param  {String}   [name]     事件名\r\n             * @param  {Function} [callback] 事件处理器\r\n             * @param  {Object}   [context]  事件处理器的上下文。\r\n             * @return {self} 返回自身，方便链式\r\n             * @chainable\r\n             */\r\n            off: function( name, cb, ctx ) {\r\n                var events = this._events;\r\n    \r\n                if ( !events ) {\r\n                    return this;\r\n                }\r\n    \r\n                if ( !name && !cb && !ctx ) {\r\n                    this._events = [];\r\n                    return this;\r\n                }\r\n    \r\n                eachEvent( name, cb, function( name, cb ) {\r\n                    $.each( findHandlers( events, name, cb, ctx ), function() {\r\n                        delete events[ this.id ];\r\n                    });\r\n                });\r\n    \r\n                return this;\r\n            },\r\n    \r\n            /**\r\n             * 触发事件\r\n             * @method trigger\r\n             * @grammar trigger( name[, args...] ) => self\r\n             * @param  {String}   type     事件名\r\n             * @param  {*} [...] 任意参数\r\n             * @return {Boolean} 如果handler中return false了，则返回false, 否则返回true\r\n             */\r\n            trigger: function( type ) {\r\n                var args, events, allEvents;\r\n    \r\n                if ( !this._events || !type ) {\r\n                    return this;\r\n                }\r\n    \r\n                args = slice.call( arguments, 1 );\r\n                events = findHandlers( this._events, type );\r\n                allEvents = findHandlers( this._events, 'all' );\r\n    \r\n                return triggerHanders( events, args ) &&\r\n                        triggerHanders( allEvents, arguments );\r\n            }\r\n        };\r\n    \r\n        /**\r\n         * 中介者，它本身是个单例，但可以通过[installTo](#WebUploader:Mediator:installTo)方法，使任何对象具备事件行为。\r\n         * 主要目的是负责模块与模块之间的合作，降低耦合度。\r\n         *\r\n         * @class Mediator\r\n         */\r\n        return $.extend({\r\n    \r\n            /**\r\n             * 可以通过这个接口，使任何对象具备事件功能。\r\n             * @method installTo\r\n             * @param  {Object} obj 需要具备事件行为的对象。\r\n             * @return {Object} 返回obj.\r\n             */\r\n            installTo: function( obj ) {\r\n                return $.extend( obj, protos );\r\n            }\r\n    \r\n        }, protos );\r\n    });\r\n    /**\r\n     * @fileOverview Uploader上传类\r\n     */\r\n    define('uploader',[\r\n        'base',\r\n        'mediator'\r\n    ], function( Base, Mediator ) {\r\n    \r\n        var $ = Base.$;\r\n    \r\n        /**\r\n         * 上传入口类。\r\n         * @class Uploader\r\n         * @constructor\r\n         * @grammar new Uploader( opts ) => Uploader\r\n         * @example\r\n         * var uploader = WebUploader.Uploader({\r\n         *     swf: 'path_of_swf/Uploader.swf',\r\n         *\r\n         *     // 开起分片上传。\r\n         *     chunked: true\r\n         * });\r\n         */\r\n        function Uploader( opts ) {\r\n            this.options = $.extend( true, {}, Uploader.options, opts );\r\n            this._init( this.options );\r\n        }\r\n    \r\n        // default Options\r\n        // widgets中有相应扩展\r\n        Uploader.options = {};\r\n        Mediator.installTo( Uploader.prototype );\r\n    \r\n        // 批量添加纯命令式方法。\r\n        $.each({\r\n            upload: 'start-upload',\r\n            stop: 'stop-upload',\r\n            getFile: 'get-file',\r\n            getFiles: 'get-files',\r\n            addFile: 'add-file',\r\n            addFiles: 'add-file',\r\n            sort: 'sort-files',\r\n            removeFile: 'remove-file',\r\n            skipFile: 'skip-file',\r\n            retry: 'retry',\r\n            isInProgress: 'is-in-progress',\r\n            makeThumb: 'make-thumb',\r\n            getDimension: 'get-dimension',\r\n            addButton: 'add-btn',\r\n            getRuntimeType: 'get-runtime-type',\r\n            refresh: 'refresh',\r\n            disable: 'disable',\r\n            enable: 'enable',\r\n            reset: 'reset'\r\n        }, function( fn, command ) {\r\n            Uploader.prototype[ fn ] = function() {\r\n                return this.request( command, arguments );\r\n            };\r\n        });\r\n    \r\n        $.extend( Uploader.prototype, {\r\n            state: 'pending',\r\n    \r\n            _init: function( opts ) {\r\n                var me = this;\r\n    \r\n                me.request( 'init', opts, function() {\r\n                    me.state = 'ready';\r\n                    me.trigger('ready');\r\n                });\r\n            },\r\n    \r\n            /**\r\n             * 获取或者设置Uploader配置项。\r\n             * @method option\r\n             * @grammar option( key ) => *\r\n             * @grammar option( key, val ) => self\r\n             * @example\r\n             *\r\n             * // 初始状态图片上传前不会压缩\r\n             * var uploader = new WebUploader.Uploader({\r\n             *     resize: null;\r\n             * });\r\n             *\r\n             * // 修改后图片上传前，尝试将图片压缩到1600 * 1600\r\n             * uploader.options( 'resize', {\r\n             *     width: 1600,\r\n             *     height: 1600\r\n             * });\r\n             */\r\n            option: function( key, val ) {\r\n                var opts = this.options;\r\n    \r\n                // setter\r\n                if ( arguments.length > 1 ) {\r\n    \r\n                    if ( $.isPlainObject( val ) &&\r\n                            $.isPlainObject( opts[ key ] ) ) {\r\n                        $.extend( opts[ key ], val );\r\n                    } else {\r\n                        opts[ key ] = val;\r\n                    }\r\n    \r\n                } else {    // getter\r\n                    return key ? opts[ key ] : opts;\r\n                }\r\n            },\r\n    \r\n            /**\r\n             * 获取文件统计信息。返回一个包含一下信息的对象。\r\n             * * `successNum` 上传成功的文件数\r\n             * * `uploadFailNum` 上传失败的文件数\r\n             * * `cancelNum` 被删除的文件数\r\n             * * `invalidNum` 无效的文件数\r\n             * * `queueNum` 还在队列中的文件数\r\n             * @method getStats\r\n             * @grammar getStats() => Object\r\n             */\r\n            getStats: function() {\r\n                // return this._mgr.getStats.apply( this._mgr, arguments );\r\n                var stats = this.request('get-stats');\r\n    \r\n                return {\r\n                    successNum: stats.numOfSuccess,\r\n    \r\n                    // who care?\r\n                    // queueFailNum: 0,\r\n                    cancelNum: stats.numOfCancel,\r\n                    invalidNum: stats.numOfInvalid,\r\n                    uploadFailNum: stats.numOfUploadFailed,\r\n                    queueNum: stats.numOfQueue\r\n                };\r\n            },\r\n    \r\n            // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器\r\n            trigger: function( type/*, args...*/ ) {\r\n                var args = [].slice.call( arguments, 1 ),\r\n                    opts = this.options,\r\n                    name = 'on' + type.substring( 0, 1 ).toUpperCase() +\r\n                        type.substring( 1 );\r\n    \r\n                if (\r\n                        // 调用通过on方法注册的handler.\r\n                        Mediator.trigger.apply( this, arguments ) === false ||\r\n    \r\n                        // 调用opts.onEvent\r\n                        $.isFunction( opts[ name ] ) &&\r\n                        opts[ name ].apply( this, args ) === false ||\r\n    \r\n                        // 调用this.onEvent\r\n                        $.isFunction( this[ name ] ) &&\r\n                        this[ name ].apply( this, args ) === false ||\r\n    \r\n                        // 广播所有uploader的事件。\r\n                        Mediator.trigger.apply( Mediator,\r\n                        [ this, type ].concat( args ) ) === false ) {\r\n    \r\n                    return false;\r\n                }\r\n    \r\n                return true;\r\n            },\r\n    \r\n            // widgets/widget.js将补充此方法的详细文档。\r\n            request: Base.noop\r\n        });\r\n    \r\n        /**\r\n         * 创建Uploader实例，等同于new Uploader( opts );\r\n         * @method create\r\n         * @class Base\r\n         * @static\r\n         * @grammar Base.create( opts ) => Uploader\r\n         */\r\n        Base.create = Uploader.create = function( opts ) {\r\n            return new Uploader( opts );\r\n        };\r\n    \r\n        // 暴露Uploader，可以通过它来扩展业务逻辑。\r\n        Base.Uploader = Uploader;\r\n    \r\n        return Uploader;\r\n    });\r\n    /**\r\n     * @fileOverview Runtime管理器，负责Runtime的选择, 连接\r\n     */\r\n    define('runtime/runtime',[\r\n        'base',\r\n        'mediator'\r\n    ], function( Base, Mediator ) {\r\n    \r\n        var $ = Base.$,\r\n            factories = {},\r\n    \r\n            // 获取对象的第一个key\r\n            getFirstKey = function( obj ) {\r\n                for ( var key in obj ) {\r\n                    if ( obj.hasOwnProperty( key ) ) {\r\n                        return key;\r\n                    }\r\n                }\r\n                return null;\r\n            };\r\n    \r\n        // 接口类。\r\n        function Runtime( options ) {\r\n            this.options = $.extend({\r\n                container: document.body\r\n            }, options );\r\n            this.uid = Base.guid('rt_');\r\n        }\r\n    \r\n        $.extend( Runtime.prototype, {\r\n    \r\n            getContainer: function() {\r\n                var opts = this.options,\r\n                    parent, container;\r\n    \r\n                if ( this._container ) {\r\n                    return this._container;\r\n                }\r\n    \r\n                parent = $( opts.container || document.body );\r\n                container = $( document.createElement('div') );\r\n    \r\n                container.attr( 'id', 'rt_' + this.uid );\r\n                container.css({\r\n                    position: 'absolute',\r\n                    top: '0px',\r\n                    left: '0px',\r\n                    width: '1px',\r\n                    height: '1px',\r\n                    overflow: 'hidden'\r\n                });\r\n    \r\n                parent.append( container );\r\n                parent.addClass('webuploader-container');\r\n                this._container = container;\r\n                return container;\r\n            },\r\n    \r\n            init: Base.noop,\r\n            exec: Base.noop,\r\n    \r\n            destroy: function() {\r\n                if ( this._container ) {\r\n                    this._container.parentNode.removeChild( this.__container );\r\n                }\r\n    \r\n                this.off();\r\n            }\r\n        });\r\n    \r\n        Runtime.orders = 'html5,flash';\r\n    \r\n    \r\n        /**\r\n         * 添加Runtime实现。\r\n         * @param {String} type    类型\r\n         * @param {Runtime} factory 具体Runtime实现。\r\n         */\r\n        Runtime.addRuntime = function( type, factory ) {\r\n            factories[ type ] = factory;\r\n        };\r\n    \r\n        Runtime.hasRuntime = function( type ) {\r\n            return !!(type ? factories[ type ] : getFirstKey( factories ));\r\n        };\r\n    \r\n        Runtime.create = function( opts, orders ) {\r\n            var type, runtime;\r\n    \r\n            orders = orders || Runtime.orders;\r\n            $.each( orders.split( /\\s*,\\s*/g ), function() {\r\n                if ( factories[ this ] ) {\r\n                    type = this;\r\n                    return false;\r\n                }\r\n            });\r\n    \r\n            type = type || getFirstKey( factories );\r\n    \r\n            if ( !type ) {\r\n                throw new Error('Runtime Error');\r\n            }\r\n    \r\n            runtime = new factories[ type ]( opts );\r\n            return runtime;\r\n        };\r\n    \r\n        Mediator.installTo( Runtime.prototype );\r\n        return Runtime;\r\n    });\r\n    \r\n    /**\r\n     * @fileOverview Runtime管理器，负责Runtime的选择, 连接\r\n     */\r\n    define('runtime/client',[\r\n        'base',\r\n        'mediator',\r\n        'runtime/runtime'\r\n    ], function( Base, Mediator, Runtime ) {\r\n    \r\n        var cache;\r\n    \r\n        cache = (function() {\r\n            var obj = {};\r\n    \r\n            return {\r\n                add: function( runtime ) {\r\n                    obj[ runtime.uid ] = runtime;\r\n                },\r\n    \r\n                get: function( ruid, standalone ) {\r\n                    var i;\r\n    \r\n                    if ( ruid ) {\r\n                        return obj[ ruid ];\r\n                    }\r\n    \r\n                    for ( i in obj ) {\r\n                        // 有些类型不能重用，比如filepicker.\r\n                        if ( standalone && obj[ i ].__standalone ) {\r\n                            continue;\r\n                        }\r\n    \r\n                        return obj[ i ];\r\n                    }\r\n    \r\n                    return null;\r\n                },\r\n    \r\n                remove: function( runtime ) {\r\n                    delete obj[ runtime.uid ];\r\n                }\r\n            };\r\n        })();\r\n    \r\n        function RuntimeClient( component, standalone ) {\r\n            var deferred = Base.Deferred(),\r\n                runtime;\r\n    \r\n            this.uid = Base.guid('client_');\r\n    \r\n            // 允许runtime没有初始化之前，注册一些方法在初始化后执行。\r\n            this.runtimeReady = function( cb ) {\r\n                return deferred.done( cb );\r\n            };\r\n    \r\n            this.connectRuntime = function( opts, cb ) {\r\n    \r\n                // already connected.\r\n                if ( runtime ) {\r\n                    throw new Error('already connected!');\r\n                }\r\n    \r\n                deferred.done( cb );\r\n    \r\n                if ( typeof opts === 'string' && cache.get( opts ) ) {\r\n                    runtime = cache.get( opts );\r\n                }\r\n    \r\n                // 像filePicker只能独立存在，不能公用。\r\n                runtime = runtime || cache.get( null, standalone );\r\n    \r\n                // 需要创建\r\n                if ( !runtime ) {\r\n                    runtime = Runtime.create( opts, opts.runtimeOrder );\r\n                    runtime.__promise = deferred.promise();\r\n                    runtime.once( 'ready', deferred.resolve );\r\n                    runtime.init();\r\n                    cache.add( runtime );\r\n                    runtime.__client = 1;\r\n                } else {\r\n                    // 来自cache\r\n                    Base.$.extend( runtime.options, opts );\r\n                    runtime.__promise.then( deferred.resolve );\r\n                    runtime.__client++;\r\n                }\r\n    \r\n                standalone && (runtime.__standalone = standalone);\r\n                return runtime;\r\n            };\r\n    \r\n            this.getRuntime = function() {\r\n                return runtime;\r\n            };\r\n    \r\n            this.disconnectRuntime = function() {\r\n                if ( !runtime ) {\r\n                    return;\r\n                }\r\n    \r\n                runtime.__client--;\r\n    \r\n                if ( runtime.__client <= 0 ) {\r\n                    cache.remove( runtime );\r\n                    delete runtime.__promise;\r\n                    runtime.destroy();\r\n                }\r\n    \r\n                runtime = null;\r\n            };\r\n    \r\n            this.exec = function() {\r\n                if ( !runtime ) {\r\n                    return;\r\n                }\r\n    \r\n                var args = Base.slice( arguments );\r\n                component && args.unshift( component );\r\n    \r\n                return runtime.exec.apply( this, args );\r\n            };\r\n    \r\n            this.getRuid = function() {\r\n                return runtime && runtime.uid;\r\n            };\r\n    \r\n            this.destroy = (function( destroy ) {\r\n                return function() {\r\n                    destroy && destroy.apply( this, arguments );\r\n                    this.trigger('destroy');\r\n                    this.off();\r\n                    this.exec('destroy');\r\n                    this.disconnectRuntime();\r\n                };\r\n            })( this.destroy );\r\n        }\r\n    \r\n        Mediator.installTo( RuntimeClient.prototype );\r\n        return RuntimeClient;\r\n    });\r\n    /**\r\n     * @fileOverview 错误信息\r\n     */\r\n    define('lib/dnd',[\r\n        'base',\r\n        'mediator',\r\n        'runtime/client'\r\n    ], function( Base, Mediator, RuntimeClent ) {\r\n    \r\n        var $ = Base.$;\r\n    \r\n        function DragAndDrop( opts ) {\r\n            opts = this.options = $.extend({}, DragAndDrop.options, opts );\r\n    \r\n            opts.container = $( opts.container );\r\n    \r\n            if ( !opts.container.length ) {\r\n                return;\r\n            }\r\n    \r\n            RuntimeClent.call( this, 'DragAndDrop' );\r\n        }\r\n    \r\n        DragAndDrop.options = {\r\n            accept: null,\r\n            disableGlobalDnd: false\r\n        };\r\n    \r\n        Base.inherits( RuntimeClent, {\r\n            constructor: DragAndDrop,\r\n    \r\n            init: function() {\r\n                var me = this;\r\n    \r\n                me.connectRuntime( me.options, function() {\r\n                    me.exec('init');\r\n                    me.trigger('ready');\r\n                });\r\n            },\r\n    \r\n            destroy: function() {\r\n                this.disconnectRuntime();\r\n            }\r\n        });\r\n    \r\n        Mediator.installTo( DragAndDrop.prototype );\r\n    \r\n        return DragAndDrop;\r\n    });\r\n    /**\r\n     * @fileOverview 组件基类。\r\n     */\r\n    define('widgets/widget',[\r\n        'base',\r\n        'uploader'\r\n    ], function( Base, Uploader ) {\r\n    \r\n        var $ = Base.$,\r\n            _init = Uploader.prototype._init,\r\n            IGNORE = {},\r\n            widgetClass = [];\r\n    \r\n        function isArrayLike( obj ) {\r\n            if ( !obj ) {\r\n                return false;\r\n            }\r\n    \r\n            var length = obj.length,\r\n                type = $.type( obj );\r\n    \r\n            if ( obj.nodeType === 1 && length ) {\r\n                return true;\r\n            }\r\n    \r\n            return type === 'array' || type !== 'function' && type !== 'string' &&\r\n                    (length === 0 || typeof length === 'number' && length > 0 &&\r\n                    (length - 1) in obj);\r\n        }\r\n    \r\n        function Widget( uploader ) {\r\n            this.owner = uploader;\r\n            this.options = uploader.options;\r\n        }\r\n    \r\n        $.extend( Widget.prototype, {\r\n    \r\n            init: Base.noop,\r\n    \r\n            // 类Backbone的事件监听声明，监听uploader实例上的事件\r\n            // widget直接无法监听事件，事件只能通过uploader来传递\r\n            invoke: function( apiName, args ) {\r\n    \r\n                /*\r\n                    {\r\n                        'make-thumb': 'makeThumb'\r\n                    }\r\n                 */\r\n                var map = this.responseMap;\r\n    \r\n                // 如果无API响应声明则忽略\r\n                if ( !map || !(apiName in map) || !(map[ apiName ] in this) ||\r\n                        !$.isFunction( this[ map[ apiName ] ] ) ) {\r\n    \r\n                    return IGNORE;\r\n                }\r\n    \r\n                return this[ map[ apiName ] ].apply( this, args );\r\n    \r\n            },\r\n    \r\n            /**\r\n             * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。\r\n             * @method request\r\n             * @grammar request( command, args ) => * | Promise\r\n             * @grammar request( command, args, callback ) => Promise\r\n             * @for  Uploader\r\n             */\r\n            request: function() {\r\n                return this.owner.request.apply( this.owner, arguments );\r\n            }\r\n        });\r\n    \r\n        // 扩展Uploader.\r\n        $.extend( Uploader.prototype, {\r\n    \r\n            // 覆写_init用来初始化widgets\r\n            _init: function() {\r\n                var me = this,\r\n                    widgets = me._widgets = [];\r\n    \r\n                $.each( widgetClass, function( _, klass ) {\r\n                    widgets.push( new klass( me ) );\r\n                });\r\n    \r\n                return _init.apply( me, arguments );\r\n            },\r\n    \r\n            request: function( apiName, args, callback ) {\r\n                var i = 0,\r\n                    widgets = this._widgets,\r\n                    len = widgets.length,\r\n                    rlts = [],\r\n                    dfds = [],\r\n                    widget, rlt, promise, key;\r\n    \r\n                args = isArrayLike( args ) ? args : [ args ];\r\n    \r\n                for ( ; i < len; i++ ) {\r\n                    widget = widgets[ i ];\r\n                    rlt = widget.invoke( apiName, args );\r\n    \r\n                    if ( rlt !== IGNORE ) {\r\n    \r\n                        // Deferred对象\r\n                        if ( Base.isPromise( rlt ) ) {\r\n                            dfds.push( rlt );\r\n                        } else {\r\n                            rlts.push( rlt );\r\n                        }\r\n                    }\r\n                }\r\n    \r\n                // 如果有callback，则用异步方式。\r\n                if ( callback || dfds.length ) {\r\n                    promise = Base.when.apply( Base, dfds );\r\n                    key = promise.pipe ? 'pipe' : 'then';\r\n    \r\n                    // 很重要不能删除。删除了会死循环。\r\n                    // 保证执行顺序。让callback总是在下一个tick中执行。\r\n                    return promise[ key ](function() {\r\n                                var deferred = Base.Deferred(),\r\n                                    args = arguments;\r\n    \r\n                                setTimeout(function() {\r\n                                    deferred.resolve.apply( deferred, args );\r\n                                }, 1 );\r\n    \r\n                                return deferred.promise();\r\n                            })[ key ]( callback || Base.noop );\r\n                } else {\r\n                    return rlts[ 0 ];\r\n                }\r\n            }\r\n        });\r\n    \r\n        /**\r\n         * 添加组件\r\n         * @param  {object} widgetProto 组件原型，构造函数通过constructor属性定义\r\n         * @param  {object} responseMap API名称与函数实现的映射\r\n         * @example\r\n         *     Uploader.register( {\r\n         *         init: function( options ) {},\r\n         *         makeThumb: function() {}\r\n         *     }, {\r\n         *         'make-thumb': 'makeThumb'\r\n         *     } );\r\n         */\r\n        Uploader.register = Widget.register = function( responseMap, widgetProto ) {\r\n            var map = { init: 'init' },\r\n                klass;\r\n    \r\n            if ( arguments.length === 1 ) {\r\n                widgetProto = responseMap;\r\n                widgetProto.responseMap = map;\r\n            } else {\r\n                widgetProto.responseMap = $.extend( map, responseMap );\r\n            }\r\n    \r\n            klass = Base.inherits( Widget, widgetProto );\r\n            widgetClass.push( klass );\r\n    \r\n            return klass;\r\n        };\r\n    \r\n        return Widget;\r\n    });\r\n    /**\r\n     * @fileOverview DragAndDrop Widget。\r\n     */\r\n    define('widgets/filednd',[\r\n        'base',\r\n        'uploader',\r\n        'lib/dnd',\r\n        'widgets/widget'\r\n    ], function( Base, Uploader, Dnd ) {\r\n        var $ = Base.$;\r\n    \r\n        Uploader.options.dnd = '';\r\n    \r\n        /**\r\n         * @property {Selector} [dnd=undefined]  指定Drag And Drop拖拽的容器，如果不指定，则不启动。\r\n         * @namespace options\r\n         * @for Uploader\r\n         */\r\n    \r\n        /**\r\n         * @event dndAccept\r\n         * @param {DataTransferItemList} items DataTransferItem\r\n         * @description 阻止此事件可以拒绝某些类型的文件拖入进来。目前只有 chrome 提供这样的 API，且只能通过 mime-type 验证。\r\n         * @for  Uploader\r\n         */\r\n        return Uploader.register({\r\n            init: function( opts ) {\r\n    \r\n                if ( !opts.dnd ||\r\n                        this.request('predict-runtime-type') !== 'html5' ) {\r\n                    return;\r\n                }\r\n    \r\n                var me = this,\r\n                    deferred = Base.Deferred(),\r\n                    options = $.extend({}, {\r\n                        disableGlobalDnd: opts.disableGlobalDnd,\r\n                        container: opts.dnd,\r\n                        accept: opts.accept\r\n                    }),\r\n                    dnd;\r\n    \r\n                dnd = new Dnd( options );\r\n    \r\n                dnd.once( 'ready', deferred.resolve );\r\n                dnd.on( 'drop', function( files ) {\r\n                    me.request( 'add-file', [ files ]);\r\n                });\r\n    \r\n                // 检测文件是否全部允许添加。\r\n                dnd.on( 'accept', function( items ) {\r\n                    return me.owner.trigger( 'dndAccept', items );\r\n                });\r\n    \r\n                dnd.init();\r\n    \r\n                return deferred.promise();\r\n            }\r\n        });\r\n    });\r\n    \r\n    /**\r\n     * @fileOverview 错误信息\r\n     */\r\n    define('lib/filepaste',[\r\n        'base',\r\n        'mediator',\r\n        'runtime/client'\r\n    ], function( Base, Mediator, RuntimeClent ) {\r\n    \r\n        var $ = Base.$;\r\n    \r\n        function FilePaste( opts ) {\r\n            opts = this.options = $.extend({}, opts );\r\n            opts.container = $( opts.container || document.body );\r\n            RuntimeClent.call( this, 'FilePaste' );\r\n        }\r\n    \r\n        Base.inherits( RuntimeClent, {\r\n            constructor: FilePaste,\r\n    \r\n            init: function() {\r\n                var me = this;\r\n    \r\n                me.connectRuntime( me.options, function() {\r\n                    me.exec('init');\r\n                    me.trigger('ready');\r\n                });\r\n            },\r\n    \r\n            destroy: function() {\r\n                this.exec('destroy');\r\n                this.disconnectRuntime();\r\n                this.off();\r\n            }\r\n        });\r\n    \r\n        Mediator.installTo( FilePaste.prototype );\r\n    \r\n        return FilePaste;\r\n    });\r\n    /**\r\n     * @fileOverview 组件基类。\r\n     */\r\n    define('widgets/filepaste',[\r\n        'base',\r\n        'uploader',\r\n        'lib/filepaste',\r\n        'widgets/widget'\r\n    ], function( Base, Uploader, FilePaste ) {\r\n        var $ = Base.$;\r\n    \r\n        /**\r\n         * @property {Selector} [paste=undefined]  指定监听paste事件的容器，如果不指定，不启用此功能。此功能为通过粘贴来添加截屏的图片。建议设置为`document.body`.\r\n         * @namespace options\r\n         * @for Uploader\r\n         */\r\n        return Uploader.register({\r\n            init: function( opts ) {\r\n    \r\n                if ( !opts.paste ||\r\n                        this.request('predict-runtime-type') !== 'html5' ) {\r\n                    return;\r\n                }\r\n    \r\n                var me = this,\r\n                    deferred = Base.Deferred(),\r\n                    options = $.extend({}, {\r\n                        container: opts.paste,\r\n                        accept: opts.accept\r\n                    }),\r\n                    paste;\r\n    \r\n                paste = new FilePaste( options );\r\n    \r\n                paste.once( 'ready', deferred.resolve );\r\n                paste.on( 'paste', function( files ) {\r\n                    me.owner.request( 'add-file', [ files ]);\r\n                });\r\n                paste.init();\r\n    \r\n                return deferred.promise();\r\n            }\r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview Blob\r\n     */\r\n    define('lib/blob',[\r\n        'base',\r\n        'runtime/client'\r\n    ], function( Base, RuntimeClient ) {\r\n    \r\n        function Blob( ruid, source ) {\r\n            var me = this;\r\n    \r\n            me.source = source;\r\n            me.ruid = ruid;\r\n    \r\n            RuntimeClient.call( me, 'Blob' );\r\n    \r\n            this.uid = source.uid || this.uid;\r\n            this.type = source.type || '';\r\n            this.size = source.size || 0;\r\n    \r\n            if ( ruid ) {\r\n                me.connectRuntime( ruid );\r\n            }\r\n        }\r\n    \r\n        Base.inherits( RuntimeClient, {\r\n            constructor: Blob,\r\n    \r\n            slice: function( start, end ) {\r\n                return this.exec( 'slice', start, end );\r\n            },\r\n    \r\n            getSource: function() {\r\n                return this.source;\r\n            }\r\n        });\r\n    \r\n        return Blob;\r\n    });\r\n    /**\r\n     * 为了统一化Flash的File和HTML5的File而存在。\r\n     * 以至于要调用Flash里面的File，也可以像调用HTML5版本的File一下。\r\n     * @fileOverview File\r\n     */\r\n    define('lib/file',[\r\n        'base',\r\n        'lib/blob'\r\n    ], function( Base, Blob ) {\r\n    \r\n        var uid = 1,\r\n            rExt = /\\.([^.]+)$/;\r\n    \r\n        function File( ruid, file ) {\r\n            var ext;\r\n    \r\n            Blob.apply( this, arguments );\r\n            this.name = file.name || ('untitled' + uid++);\r\n            ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : '';\r\n    \r\n            // todo 支持其他类型文件的转换。\r\n    \r\n            // 如果有mimetype, 但是文件名里面没有找出后缀规律\r\n            if ( !ext && this.type ) {\r\n                ext = /\\/(jpg|jpeg|png|gif|bmp)$/i.exec( this.type ) ?\r\n                        RegExp.$1.toLowerCase() : '';\r\n                this.name += '.' + ext;\r\n            }\r\n    \r\n            // 如果没有指定mimetype, 但是知道文件后缀。\r\n            if ( !this.type &&  ~'jpg,jpeg,png,gif,bmp'.indexOf( ext ) ) {\r\n                this.type = 'image/' + (ext === 'jpg' ? 'jpeg' : ext);\r\n            }\r\n    \r\n            this.ext = ext;\r\n            this.lastModifiedDate = file.lastModifiedDate ||\r\n                    (new Date()).toLocaleString();\r\n        }\r\n    \r\n        return Base.inherits( Blob, File );\r\n    });\r\n    \r\n    /**\r\n     * @fileOverview 错误信息\r\n     */\r\n    define('lib/filepicker',[\r\n        'base',\r\n        'runtime/client',\r\n        'lib/file'\r\n    ], function( Base, RuntimeClent, File ) {\r\n    \r\n        var $ = Base.$;\r\n    \r\n        function FilePicker( opts ) {\r\n            opts = this.options = $.extend({}, FilePicker.options, opts );\r\n    \r\n            opts.container = $( opts.id );\r\n    \r\n            if ( !opts.container.length ) {\r\n                throw new Error('按钮指定错误');\r\n            }\r\n    \r\n            opts.innerHTML = opts.innerHTML || opts.label ||\r\n                    opts.container.html() || '';\r\n    \r\n            opts.button = $( opts.button || document.createElement('div') );\r\n            opts.button.html( opts.innerHTML );\r\n            opts.container.html( opts.button );\r\n    \r\n            RuntimeClent.call( this, 'FilePicker', true );\r\n        }\r\n    \r\n        FilePicker.options = {\r\n            button: null,\r\n            container: null,\r\n            label: null,\r\n            innerHTML: null,\r\n            multiple: true,\r\n            accept: null,\r\n            name: 'file'\r\n        };\r\n    \r\n        Base.inherits( RuntimeClent, {\r\n            constructor: FilePicker,\r\n    \r\n            init: function() {\r\n                var me = this,\r\n                    opts = me.options,\r\n                    button = opts.button;\r\n    \r\n                button.addClass('webuploader-pick');\r\n    \r\n                me.on( 'all', function( type ) {\r\n                    var files;\r\n    \r\n                    switch ( type ) {\r\n                        case 'mouseenter':\r\n                            button.addClass('webuploader-pick-hover');\r\n                            break;\r\n    \r\n                        case 'mouseleave':\r\n                            button.removeClass('webuploader-pick-hover');\r\n                            break;\r\n    \r\n                        case 'change':\r\n                            files = me.exec('getFiles');\r\n                            me.trigger( 'select', $.map( files, function( file ) {\r\n                                file = new File( me.getRuid(), file );\r\n    \r\n                                // 记录来源。\r\n                                file._refer = opts.container;\r\n                                return file;\r\n                            }), opts.container );\r\n                            break;\r\n                    }\r\n                });\r\n    \r\n                me.connectRuntime( opts, function() {\r\n                    me.refresh();\r\n                    me.exec( 'init', opts );\r\n                    me.trigger('ready');\r\n                });\r\n    \r\n                $( window ).on( 'resize', function() {\r\n                    me.refresh();\r\n                });\r\n            },\r\n    \r\n            refresh: function() {\r\n                var shimContainer = this.getRuntime().getContainer(),\r\n                    button = this.options.button,\r\n                    width = button.outerWidth ?\r\n                            button.outerWidth() : button.width(),\r\n    \r\n                    height = button.outerHeight ?\r\n                            button.outerHeight() : button.height(),\r\n    \r\n                    pos = button.offset();\r\n    \r\n                width && height && shimContainer.css({\r\n                    bottom: 'auto',\r\n                    right: 'auto',\r\n                    width: width + 'px',\r\n                    height: height + 'px'\r\n                }).offset( pos );\r\n            },\r\n    \r\n            enable: function() {\r\n                var btn = this.options.button;\r\n    \r\n                btn.removeClass('webuploader-pick-disable');\r\n                this.refresh();\r\n            },\r\n    \r\n            disable: function() {\r\n                var btn = this.options.button;\r\n    \r\n                this.getRuntime().getContainer().css({\r\n                    top: '-99999px'\r\n                });\r\n    \r\n                btn.addClass('webuploader-pick-disable');\r\n            },\r\n    \r\n            destroy: function() {\r\n                if ( this.runtime ) {\r\n                    this.exec('destroy');\r\n                    this.disconnectRuntime();\r\n                }\r\n            }\r\n        });\r\n    \r\n        return FilePicker;\r\n    });\r\n    \r\n    /**\r\n     * @fileOverview 文件选择相关\r\n     */\r\n    define('widgets/filepicker',[\r\n        'base',\r\n        'uploader',\r\n        'lib/filepicker',\r\n        'widgets/widget'\r\n    ], function( Base, Uploader, FilePicker ) {\r\n        var $ = Base.$;\r\n    \r\n        $.extend( Uploader.options, {\r\n    \r\n            /**\r\n             * @property {Selector | Object} [pick=undefined]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 指定选择文件的按钮容器，不指定则不创建按钮。\r\n             *\r\n             * * `id` {Seletor} 指定选择文件的按钮容器，不指定则不创建按钮。\r\n             * * `label` {String} 请采用 `innerHTML` 代替\r\n             * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。\r\n             * * `multiple` {Boolean} 是否开起同时选择多个文件能力。\r\n             */\r\n            pick: null,\r\n    \r\n            /**\r\n             * @property {Arroy} [accept=null]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表，所以这里需要分开指定。\r\n             *\r\n             * * `title` {String} 文字描述\r\n             * * `extensions` {String} 允许的文件后缀，不带点，多个用逗号分割。\r\n             * * `mimeTypes` {String} 多个用逗号分割。\r\n             *\r\n             * 如：\r\n             *\r\n             * ```\r\n             * {\r\n             *     title: 'Images',\r\n             *     extensions: 'gif,jpg,jpeg,bmp,png',\r\n             *     mimeTypes: 'image/*'\r\n             * }\r\n             * ```\r\n             */\r\n            accept: null/*{\r\n                title: 'Images',\r\n                extensions: 'gif,jpg,jpeg,bmp,png',\r\n                mimeTypes: 'image/*'\r\n            }*/\r\n        });\r\n    \r\n        return Uploader.register({\r\n            'add-btn': 'addButton',\r\n            refresh: 'refresh',\r\n            disable: 'disable',\r\n            enable: 'enable'\r\n        }, {\r\n    \r\n            init: function( opts ) {\r\n                this.pickers = [];\r\n                return opts.pick && this.addButton( opts.pick );\r\n            },\r\n    \r\n            refresh: function() {\r\n                $.each( this.pickers, function() {\r\n                    this.refresh();\r\n                });\r\n            },\r\n    \r\n            /**\r\n             * @method addButton\r\n             * @for Uploader\r\n             * @grammar addButton( pick ) => Promise\r\n             * @description\r\n             * 添加文件选择按钮，如果一个按钮不够，需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。\r\n             * @example\r\n             * uploader.addButton({\r\n             *     id: '#btnContainer',\r\n             *     innerHTML: '选择文件'\r\n             * });\r\n             */\r\n            addButton: function( pick ) {\r\n                var me = this,\r\n                    opts = me.options,\r\n                    accept = opts.accept,\r\n                    options, picker, deferred;\r\n    \r\n                if ( !pick ) {\r\n                    return;\r\n                }\r\n    \r\n                deferred = Base.Deferred();\r\n                $.isPlainObject( pick ) || (pick = {\r\n                    id: pick\r\n                });\r\n    \r\n                options = $.extend({}, pick, {\r\n                    accept: $.isPlainObject( accept ) ? [ accept ] : accept,\r\n                    swf: opts.swf,\r\n                    runtimeOrder: opts.runtimeOrder\r\n                });\r\n    \r\n                picker = new FilePicker( options );\r\n    \r\n                picker.once( 'ready', deferred.resolve );\r\n                picker.on( 'select', function( files ) {\r\n                    me.owner.request( 'add-file', [ files ]);\r\n                });\r\n                picker.init();\r\n    \r\n                this.pickers.push( picker );\r\n    \r\n                return deferred.promise();\r\n            },\r\n    \r\n            disable: function() {\r\n                $.each( this.pickers, function() {\r\n                    this.disable();\r\n                });\r\n            },\r\n    \r\n            enable: function() {\r\n                $.each( this.pickers, function() {\r\n                    this.enable();\r\n                });\r\n            }\r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview Image\r\n     */\r\n    define('lib/image',[\r\n        'base',\r\n        'runtime/client',\r\n        'lib/blob'\r\n    ], function( Base, RuntimeClient, Blob ) {\r\n        var $ = Base.$;\r\n    \r\n        // 构造器。\r\n        function Image( opts ) {\r\n            this.options = $.extend({}, Image.options, opts );\r\n            RuntimeClient.call( this, 'Image' );\r\n    \r\n            this.on( 'load', function() {\r\n                this._info = this.exec('info');\r\n                this._meta = this.exec('meta');\r\n            });\r\n        }\r\n    \r\n        // 默认选项。\r\n        Image.options = {\r\n    \r\n            // 默认的图片处理质量\r\n            quality: 90,\r\n    \r\n            // 是否裁剪\r\n            crop: false,\r\n    \r\n            // 是否保留头部信息\r\n            preserveHeaders: true,\r\n    \r\n            // 是否允许放大。\r\n            allowMagnify: true\r\n        };\r\n    \r\n        // 继承RuntimeClient.\r\n        Base.inherits( RuntimeClient, {\r\n            constructor: Image,\r\n    \r\n            info: function( val ) {\r\n    \r\n                // setter\r\n                if ( val ) {\r\n                    this._info = val;\r\n                    return this;\r\n                }\r\n    \r\n                // getter\r\n                return this._info;\r\n            },\r\n    \r\n            meta: function( val ) {\r\n    \r\n                // setter\r\n                if ( val ) {\r\n                    this._meta = val;\r\n                    return this;\r\n                }\r\n    \r\n                // getter\r\n                return this._meta;\r\n            },\r\n    \r\n            loadFromBlob: function( blob ) {\r\n                var me = this,\r\n                    ruid = blob.getRuid();\r\n    \r\n                this.connectRuntime( ruid, function() {\r\n                    me.exec( 'init', me.options );\r\n                    me.exec( 'loadFromBlob', blob );\r\n                });\r\n            },\r\n    \r\n            resize: function() {\r\n                var args = Base.slice( arguments );\r\n                return this.exec.apply( this, [ 'resize' ].concat( args ) );\r\n            },\r\n    \r\n            getAsDataUrl: function( type ) {\r\n                return this.exec( 'getAsDataUrl', type );\r\n            },\r\n    \r\n            getAsBlob: function( type ) {\r\n                var blob = this.exec( 'getAsBlob', type );\r\n    \r\n                return new Blob( this.getRuid(), blob );\r\n            }\r\n        });\r\n    \r\n        return Image;\r\n    });\r\n    /**\r\n     * @fileOverview 图片操作, 负责预览图片和上传前压缩图片\r\n     */\r\n    define('widgets/image',[\r\n        'base',\r\n        'uploader',\r\n        'lib/image',\r\n        'widgets/widget'\r\n    ], function( Base, Uploader, Image ) {\r\n    \r\n        var $ = Base.$,\r\n            throttle;\r\n    \r\n        // 根据要处理的文件大小来节流，一次不能处理太多，会卡。\r\n        throttle = (function( max ) {\r\n            var occupied = 0,\r\n                waiting = [],\r\n                tick = function() {\r\n                    var item;\r\n    \r\n                    while ( waiting.length && occupied < max ) {\r\n                        item = waiting.shift();\r\n                        occupied += item[ 0 ];\r\n                        item[ 1 ]();\r\n                    }\r\n                };\r\n    \r\n            return function( emiter, size, cb ) {\r\n                waiting.push([ size, cb ]);\r\n                emiter.once( 'destroy', function() {\r\n                    occupied -= size;\r\n                    setTimeout( tick, 1 );\r\n                });\r\n                setTimeout( tick, 1 );\r\n            };\r\n        })( 5 * 1024 * 1024 );\r\n    \r\n        $.extend( Uploader.options, {\r\n    \r\n            /**\r\n             * @property {Object} [thumb]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 配置生成缩略图的选项。\r\n             *\r\n             * 默认为：\r\n             *\r\n             * ```javascript\r\n             * {\r\n             *     width: 110,\r\n             *     height: 110,\r\n             *\r\n             *     // 图片质量，只有type为`image/jpeg`的时候才有效。\r\n             *     quality: 70,\r\n             *\r\n             *     // 是否允许放大，如果想要生成小图的时候不失真，此选项应该设置为false.\r\n             *     allowMagnify: true,\r\n             *\r\n             *     // 是否允许裁剪。\r\n             *     crop: true,\r\n             *\r\n             *     // 是否保留头部meta信息。\r\n             *     preserveHeaders: false,\r\n             *\r\n             *     // 为空的话则保留原有图片格式。\r\n             *     // 否则强制转换成指定的类型。\r\n             *     type: 'image/jpeg'\r\n             * }\r\n             * ```\r\n             */\r\n            thumb: {\r\n                width: 110,\r\n                height: 110,\r\n                quality: 70,\r\n                allowMagnify: true,\r\n                crop: true,\r\n                preserveHeaders: false,\r\n    \r\n                // 为空的话则保留原有图片格式。\r\n                // 否则强制转换成指定的类型。\r\n                // IE 8下面 base64 大小不能超过 32K 否则预览失败，而非 jpeg 编码的图片很可\r\n                // 能会超过 32k, 所以这里设置成预览的时候都是 image/jpeg\r\n                type: 'image/jpeg'\r\n            },\r\n    \r\n            /**\r\n             * @property {Object} [compress]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 配置压缩的图片的选项。如果此选项为`false`, 则图片在上传前不进行压缩。\r\n             *\r\n             * 默认为：\r\n             *\r\n             * ```javascript\r\n             * {\r\n             *     width: 1600,\r\n             *     height: 1600,\r\n             *\r\n             *     // 图片质量，只有type为`image/jpeg`的时候才有效。\r\n             *     quality: 90,\r\n             *\r\n             *     // 是否允许放大，如果想要生成小图的时候不失真，此选项应该设置为false.\r\n             *     allowMagnify: false,\r\n             *\r\n             *     // 是否允许裁剪。\r\n             *     crop: false,\r\n             *\r\n             *     // 是否保留头部meta信息。\r\n             *     preserveHeaders: true\r\n             * }\r\n             * ```\r\n             */\r\n            compress: {\r\n                width: 1600,\r\n                height: 1600,\r\n                quality: 90,\r\n                allowMagnify: false,\r\n                crop: false,\r\n                preserveHeaders: true\r\n            }\r\n        });\r\n    \r\n        return Uploader.register({\r\n            'make-thumb': 'makeThumb',\r\n            'before-send-file': 'compressImage'\r\n        }, {\r\n    \r\n    \r\n            /**\r\n             * 生成缩略图，此过程为异步，所以需要传入`callback`。\r\n             * 通常情况在图片加入队里后调用此方法来生成预览图以增强交互效果。\r\n             *\r\n             * `callback`中可以接收到两个参数。\r\n             * * 第一个为error，如果生成缩略图有错误，此error将为真。\r\n             * * 第二个为ret, 缩略图的Data URL值。\r\n             *\r\n             * **注意**\r\n             * Date URL在IE6/7中不支持，所以不用调用此方法了，直接显示一张暂不支持预览图片好了。\r\n             *\r\n             *\r\n             * @method makeThumb\r\n             * @grammar makeThumb( file, callback ) => undefined\r\n             * @grammar makeThumb( file, callback, width, height ) => undefined\r\n             * @for Uploader\r\n             * @example\r\n             *\r\n             * uploader.on( 'fileQueued', function( file ) {\r\n             *     var $li = ...;\r\n             *\r\n             *     uploader.makeThumb( file, function( error, ret ) {\r\n             *         if ( error ) {\r\n             *             $li.text('预览错误');\r\n             *         } else {\r\n             *             $li.append('<img alt=\"\" src=\"' + ret + '\" />');\r\n             *         }\r\n             *     });\r\n             *\r\n             * });\r\n             */\r\n            makeThumb: function( file, cb, width, height ) {\r\n                var opts, image;\r\n    \r\n                file = this.request( 'get-file', file );\r\n    \r\n                // 只预览图片格式。\r\n                if ( !file.type.match( /^image/ ) ) {\r\n                    cb( true );\r\n                    return;\r\n                }\r\n    \r\n                opts = $.extend({}, this.options.thumb );\r\n    \r\n                // 如果传入的是object.\r\n                if ( $.isPlainObject( width ) ) {\r\n                    opts = $.extend( opts, width );\r\n                    width = null;\r\n                }\r\n    \r\n                width = width || opts.width;\r\n                height = height || opts.height;\r\n    \r\n                image = new Image( opts );\r\n    \r\n                image.once( 'load', function() {\r\n                    file._info = file._info || image.info();\r\n                    file._meta = file._meta || image.meta();\r\n                    image.resize( width, height );\r\n                });\r\n    \r\n                image.once( 'complete', function() {\r\n                    cb( false, image.getAsDataUrl( opts.type ) );\r\n                    image.destroy();\r\n                });\r\n    \r\n                image.once( 'error', function() {\r\n                    cb( true );\r\n                    image.destroy();\r\n                });\r\n    \r\n                throttle( image, file.source.size, function() {\r\n                    file._info && image.info( file._info );\r\n                    file._meta && image.meta( file._meta );\r\n                    image.loadFromBlob( file.source );\r\n                });\r\n            },\r\n    \r\n            compressImage: function( file ) {\r\n                var opts = this.options.compress || this.options.resize,\r\n                    compressSize = opts && opts.compressSize || 300 * 1024,\r\n                    image, deferred;\r\n    \r\n                file = this.request( 'get-file', file );\r\n    \r\n                // 只预览图片格式。\r\n                if ( !opts || !~'image/jpeg,image/jpg'.indexOf( file.type ) ||\r\n                        file.size < compressSize ||\r\n                        file._compressed ) {\r\n                    return;\r\n                }\r\n    \r\n                opts = $.extend({}, opts );\r\n                deferred = Base.Deferred();\r\n    \r\n                image = new Image( opts );\r\n    \r\n                deferred.always(function() {\r\n                    image.destroy();\r\n                    image = null;\r\n                });\r\n                image.once( 'error', deferred.reject );\r\n                image.once( 'load', function() {\r\n                    file._info = file._info || image.info();\r\n                    file._meta = file._meta || image.meta();\r\n                    image.resize( opts.width, opts.height );\r\n                });\r\n    \r\n                image.once( 'complete', function() {\r\n                    var blob, size;\r\n    \r\n                    // 移动端 UC / qq 浏览器的无图模式下\r\n                    // ctx.getImageData 处理大图的时候会报 Exception\r\n                    // INDEX_SIZE_ERR: DOM Exception 1\r\n                    try {\r\n                        blob = image.getAsBlob( opts.type );\r\n    \r\n                        size = file.size;\r\n    \r\n                        // 如果压缩后，比原来还大则不用压缩后的。\r\n                        if ( blob.size < size ) {\r\n                            // file.source.destroy && file.source.destroy();\r\n                            file.source = blob;\r\n                            file.size = blob.size;\r\n    \r\n                            file.trigger( 'resize', blob.size, size );\r\n                        }\r\n    \r\n                        // 标记，避免重复压缩。\r\n                        file._compressed = true;\r\n                        deferred.resolve();\r\n                    } catch ( e ) {\r\n                        // 出错了直接继续，让其上传原始图片\r\n                        deferred.resolve();\r\n                    }\r\n                });\r\n    \r\n                file._info && image.info( file._info );\r\n                file._meta && image.meta( file._meta );\r\n    \r\n                image.loadFromBlob( file.source );\r\n                return deferred.promise();\r\n            }\r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview 文件属性封装\r\n     */\r\n    define('file',[\r\n        'base',\r\n        'mediator'\r\n    ], function( Base, Mediator ) {\r\n    \r\n        var $ = Base.$,\r\n            idPrefix = 'WU_FILE_',\r\n            idSuffix = 0,\r\n            rExt = /\\.([^.]+)$/,\r\n            statusMap = {};\r\n    \r\n        function gid() {\r\n            return idPrefix + idSuffix++;\r\n        }\r\n    \r\n        /**\r\n         * 文件类\r\n         * @class File\r\n         * @constructor 构造函数\r\n         * @grammar new File( source ) => File\r\n         * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。\r\n         */\r\n        function WUFile( source ) {\r\n    \r\n            /**\r\n             * 文件名，包括扩展名（后缀）\r\n             * @property name\r\n             * @type {string}\r\n             */\r\n            this.name = source.name || 'Untitled';\r\n    \r\n            /**\r\n             * 文件体积（字节）\r\n             * @property size\r\n             * @type {uint}\r\n             * @default 0\r\n             */\r\n            this.size = source.size || 0;\r\n    \r\n            /**\r\n             * 文件MIMETYPE类型，与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny)\r\n             * @property type\r\n             * @type {string}\r\n             * @default 'application'\r\n             */\r\n            this.type = source.type || 'application';\r\n    \r\n            /**\r\n             * 文件最后修改日期\r\n             * @property lastModifiedDate\r\n             * @type {int}\r\n             * @default 当前时间戳\r\n             */\r\n            this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1);\r\n    \r\n            /**\r\n             * 文件ID，每个对象具有唯一ID，与文件名无关\r\n             * @property id\r\n             * @type {string}\r\n             */\r\n            this.id = gid();\r\n    \r\n            /**\r\n             * 文件扩展名，通过文件名获取，例如test.png的扩展名为png\r\n             * @property ext\r\n             * @type {string}\r\n             */\r\n            this.ext = rExt.exec( this.name ) ? RegExp.$1 : '';\r\n    \r\n    \r\n            /**\r\n             * 状态文字说明。在不同的status语境下有不同的用途。\r\n             * @property statusText\r\n             * @type {string}\r\n             */\r\n            this.statusText = '';\r\n    \r\n            // 存储文件状态，防止通过属性直接修改\r\n            statusMap[ this.id ] = WUFile.Status.INITED;\r\n    \r\n            this.source = source;\r\n            this.loaded = 0;\r\n    \r\n            this.on( 'error', function( msg ) {\r\n                this.setStatus( WUFile.Status.ERROR, msg );\r\n            });\r\n        }\r\n    \r\n        $.extend( WUFile.prototype, {\r\n    \r\n            /**\r\n             * 设置状态，状态变化时会触发`change`事件。\r\n             * @method setStatus\r\n             * @grammar setStatus( status[, statusText] );\r\n             * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status)\r\n             * @param {String} [statusText=''] 状态说明，常在error时使用，用http, abort,server等来标记是由于什么原因导致文件错误。\r\n             */\r\n            setStatus: function( status, text ) {\r\n    \r\n                var prevStatus = statusMap[ this.id ];\r\n    \r\n                typeof text !== 'undefined' && (this.statusText = text);\r\n    \r\n                if ( status !== prevStatus ) {\r\n                    statusMap[ this.id ] = status;\r\n                    /**\r\n                     * 文件状态变化\r\n                     * @event statuschange\r\n                     */\r\n                    this.trigger( 'statuschange', status, prevStatus );\r\n                }\r\n    \r\n            },\r\n    \r\n            /**\r\n             * 获取文件状态\r\n             * @return {File.Status}\r\n             * @example\r\n                     文件状态具体包括以下几种类型：\r\n                     {\r\n                         // 初始化\r\n                        INITED:     0,\r\n                        // 已入队列\r\n                        QUEUED:     1,\r\n                        // 正在上传\r\n                        PROGRESS:     2,\r\n                        // 上传出错\r\n                        ERROR:         3,\r\n                        // 上传成功\r\n                        COMPLETE:     4,\r\n                        // 上传取消\r\n                        CANCELLED:     5\r\n                    }\r\n             */\r\n            getStatus: function() {\r\n                return statusMap[ this.id ];\r\n            },\r\n    \r\n            /**\r\n             * 获取文件原始信息。\r\n             * @return {*}\r\n             */\r\n            getSource: function() {\r\n                return this.source;\r\n            },\r\n    \r\n            destory: function() {\r\n                delete statusMap[ this.id ];\r\n            }\r\n        });\r\n    \r\n        Mediator.installTo( WUFile.prototype );\r\n    \r\n        /**\r\n         * 文件状态值，具体包括以下几种类型：\r\n         * * `inited` 初始状态\r\n         * * `queued` 已经进入队列, 等待上传\r\n         * * `progress` 上传中\r\n         * * `complete` 上传完成。\r\n         * * `error` 上传出错，可重试\r\n         * * `interrupt` 上传中断，可续传。\r\n         * * `invalid` 文件不合格，不能重试上传。会自动从队列中移除。\r\n         * * `cancelled` 文件被移除。\r\n         * @property {Object} Status\r\n         * @namespace File\r\n         * @class File\r\n         * @static\r\n         */\r\n        WUFile.Status = {\r\n            INITED:     'inited',    // 初始状态\r\n            QUEUED:     'queued',    // 已经进入队列, 等待上传\r\n            PROGRESS:   'progress',    // 上传中\r\n            ERROR:      'error',    // 上传出错，可重试\r\n            COMPLETE:   'complete',    // 上传完成。\r\n            CANCELLED:  'cancelled',    // 上传取消。\r\n            INTERRUPT:  'interrupt',    // 上传中断，可续传。\r\n            INVALID:    'invalid'    // 文件不合格，不能重试上传。\r\n        };\r\n    \r\n        return WUFile;\r\n    });\r\n    \r\n    /**\r\n     * @fileOverview 文件队列\r\n     */\r\n    define('queue',[\r\n        'base',\r\n        'mediator',\r\n        'file'\r\n    ], function( Base, Mediator, WUFile ) {\r\n    \r\n        var $ = Base.$,\r\n            STATUS = WUFile.Status;\r\n    \r\n        /**\r\n         * 文件队列, 用来存储各个状态中的文件。\r\n         * @class Queue\r\n         * @extends Mediator\r\n         */\r\n        function Queue() {\r\n    \r\n            /**\r\n             * 统计文件数。\r\n             * * `numOfQueue` 队列中的文件数。\r\n             * * `numOfSuccess` 上传成功的文件数\r\n             * * `numOfCancel` 被移除的文件数\r\n             * * `numOfProgress` 正在上传中的文件数\r\n             * * `numOfUploadFailed` 上传错误的文件数。\r\n             * * `numOfInvalid` 无效的文件数。\r\n             * @property {Object} stats\r\n             */\r\n            this.stats = {\r\n                numOfQueue: 0,\r\n                numOfSuccess: 0,\r\n                numOfCancel: 0,\r\n                numOfProgress: 0,\r\n                numOfUploadFailed: 0,\r\n                numOfInvalid: 0\r\n            };\r\n    \r\n            // 上传队列，仅包括等待上传的文件\r\n            this._queue = [];\r\n    \r\n            // 存储所有文件\r\n            this._map = {};\r\n        }\r\n    \r\n        $.extend( Queue.prototype, {\r\n    \r\n            /**\r\n             * 将新文件加入对队列尾部\r\n             *\r\n             * @method append\r\n             * @param  {File} file   文件对象\r\n             */\r\n            append: function( file ) {\r\n                this._queue.push( file );\r\n                this._fileAdded( file );\r\n                return this;\r\n            },\r\n    \r\n            /**\r\n             * 将新文件加入对队列头部\r\n             *\r\n             * @method prepend\r\n             * @param  {File} file   文件对象\r\n             */\r\n            prepend: function( file ) {\r\n                this._queue.unshift( file );\r\n                this._fileAdded( file );\r\n                return this;\r\n            },\r\n    \r\n            /**\r\n             * 获取文件对象\r\n             *\r\n             * @method getFile\r\n             * @param  {String} fileId   文件ID\r\n             * @return {File}\r\n             */\r\n            getFile: function( fileId ) {\r\n                if ( typeof fileId !== 'string' ) {\r\n                    return fileId;\r\n                }\r\n                return this._map[ fileId ];\r\n            },\r\n    \r\n            /**\r\n             * 从队列中取出一个指定状态的文件。\r\n             * @grammar fetch( status ) => File\r\n             * @method fetch\r\n             * @param {String} status [文件状态值](#WebUploader:File:File.Status)\r\n             * @return {File} [File](#WebUploader:File)\r\n             */\r\n            fetch: function( status ) {\r\n                var len = this._queue.length,\r\n                    i, file;\r\n    \r\n                status = status || STATUS.QUEUED;\r\n    \r\n                for ( i = 0; i < len; i++ ) {\r\n                    file = this._queue[ i ];\r\n    \r\n                    if ( status === file.getStatus() ) {\r\n                        return file;\r\n                    }\r\n                }\r\n    \r\n                return null;\r\n            },\r\n    \r\n            /**\r\n             * 对队列进行排序，能够控制文件上传顺序。\r\n             * @grammar sort( fn ) => undefined\r\n             * @method sort\r\n             * @param {Function} fn 排序方法\r\n             */\r\n            sort: function( fn ) {\r\n                if ( typeof fn === 'function' ) {\r\n                    this._queue.sort( fn );\r\n                }\r\n            },\r\n    \r\n            /**\r\n             * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。\r\n             * @grammar getFiles( [status1[, status2 ...]] ) => Array\r\n             * @method getFiles\r\n             * @param {String} [status] [文件状态值](#WebUploader:File:File.Status)\r\n             */\r\n            getFiles: function() {\r\n                var sts = [].slice.call( arguments, 0 ),\r\n                    ret = [],\r\n                    i = 0,\r\n                    len = this._queue.length,\r\n                    file;\r\n    \r\n                for ( ; i < len; i++ ) {\r\n                    file = this._queue[ i ];\r\n    \r\n                    if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) {\r\n                        continue;\r\n                    }\r\n    \r\n                    ret.push( file );\r\n                }\r\n    \r\n                return ret;\r\n            },\r\n    \r\n            _fileAdded: function( file ) {\r\n                var me = this,\r\n                    existing = this._map[ file.id ];\r\n    \r\n                if ( !existing ) {\r\n                    this._map[ file.id ] = file;\r\n    \r\n                    file.on( 'statuschange', function( cur, pre ) {\r\n                        me._onFileStatusChange( cur, pre );\r\n                    });\r\n                }\r\n    \r\n                file.setStatus( STATUS.QUEUED );\r\n            },\r\n    \r\n            _onFileStatusChange: function( curStatus, preStatus ) {\r\n                var stats = this.stats;\r\n    \r\n                switch ( preStatus ) {\r\n                    case STATUS.PROGRESS:\r\n                        stats.numOfProgress--;\r\n                        break;\r\n    \r\n                    case STATUS.QUEUED:\r\n                        stats.numOfQueue --;\r\n                        break;\r\n    \r\n                    case STATUS.ERROR:\r\n                        stats.numOfUploadFailed--;\r\n                        break;\r\n    \r\n                    case STATUS.INVALID:\r\n                        stats.numOfInvalid--;\r\n                        break;\r\n                }\r\n    \r\n                switch ( curStatus ) {\r\n                    case STATUS.QUEUED:\r\n                        stats.numOfQueue++;\r\n                        break;\r\n    \r\n                    case STATUS.PROGRESS:\r\n                        stats.numOfProgress++;\r\n                        break;\r\n    \r\n                    case STATUS.ERROR:\r\n                        stats.numOfUploadFailed++;\r\n                        break;\r\n    \r\n                    case STATUS.COMPLETE:\r\n                        stats.numOfSuccess++;\r\n                        break;\r\n    \r\n                    case STATUS.CANCELLED:\r\n                        stats.numOfCancel++;\r\n                        break;\r\n    \r\n                    case STATUS.INVALID:\r\n                        stats.numOfInvalid++;\r\n                        break;\r\n                }\r\n            }\r\n    \r\n        });\r\n    \r\n        Mediator.installTo( Queue.prototype );\r\n    \r\n        return Queue;\r\n    });\r\n    /**\r\n     * @fileOverview 队列\r\n     */\r\n    define('widgets/queue',[\r\n        'base',\r\n        'uploader',\r\n        'queue',\r\n        'file',\r\n        'lib/file',\r\n        'runtime/client',\r\n        'widgets/widget'\r\n    ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) {\r\n    \r\n        var $ = Base.$,\r\n            rExt = /\\.\\w+$/,\r\n            Status = WUFile.Status;\r\n    \r\n        return Uploader.register({\r\n            'sort-files': 'sortFiles',\r\n            'add-file': 'addFiles',\r\n            'get-file': 'getFile',\r\n            'fetch-file': 'fetchFile',\r\n            'get-stats': 'getStats',\r\n            'get-files': 'getFiles',\r\n            'remove-file': 'removeFile',\r\n            'retry': 'retry',\r\n            'reset': 'reset',\r\n            'accept-file': 'acceptFile'\r\n        }, {\r\n    \r\n            init: function( opts ) {\r\n                var me = this,\r\n                    deferred, len, i, item, arr, accept, runtime;\r\n    \r\n                if ( $.isPlainObject( opts.accept ) ) {\r\n                    opts.accept = [ opts.accept ];\r\n                }\r\n    \r\n                // accept中的中生成匹配正则。\r\n                if ( opts.accept ) {\r\n                    arr = [];\r\n    \r\n                    for ( i = 0, len = opts.accept.length; i < len; i++ ) {\r\n                        item = opts.accept[ i ].extensions;\r\n                        item && arr.push( item );\r\n                    }\r\n    \r\n                    if ( arr.length ) {\r\n                        accept = '\\\\.' + arr.join(',')\r\n                                .replace( /,/g, '$|\\\\.' )\r\n                                .replace( /\\*/g, '.*' ) + '$';\r\n                    }\r\n    \r\n                    me.accept = new RegExp( accept, 'i' );\r\n                }\r\n    \r\n                me.queue = new Queue();\r\n                me.stats = me.queue.stats;\r\n    \r\n                // 如果当前不是html5运行时，那就算了。\r\n                // 不执行后续操作\r\n                if ( this.request('predict-runtime-type') !== 'html5' ) {\r\n                    return;\r\n                }\r\n    \r\n                // 创建一个 html5 运行时的 placeholder\r\n                // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。\r\n                deferred = Base.Deferred();\r\n                runtime = new RuntimeClient('Placeholder');\r\n                runtime.connectRuntime({\r\n                    runtimeOrder: 'html5'\r\n                }, function() {\r\n                    me._ruid = runtime.getRuid();\r\n                    deferred.resolve();\r\n                });\r\n                return deferred.promise();\r\n            },\r\n    \r\n    \r\n            // 为了支持外部直接添加一个原生File对象。\r\n            _wrapFile: function( file ) {\r\n                if ( !(file instanceof WUFile) ) {\r\n    \r\n                    if ( !(file instanceof File) ) {\r\n                        if ( !this._ruid ) {\r\n                            throw new Error('Can\\'t add external files.');\r\n                        }\r\n                        file = new File( this._ruid, file );\r\n                    }\r\n    \r\n                    file = new WUFile( file );\r\n                }\r\n    \r\n                return file;\r\n            },\r\n    \r\n            // 判断文件是否可以被加入队列\r\n            acceptFile: function( file ) {\r\n                var invalid = !file || file.size < 6 || this.accept &&\r\n    \r\n                        // 如果名字中有后缀，才做后缀白名单处理。\r\n                        rExt.exec( file.name ) && !this.accept.test( file.name );\r\n    \r\n                return !invalid;\r\n            },\r\n    \r\n    \r\n            /**\r\n             * @event beforeFileQueued\r\n             * @param {File} file File对象\r\n             * @description 当文件被加入队列之前触发，此事件的handler返回值为`false`，则此文件不会被添加进入队列。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * @event fileQueued\r\n             * @param {File} file File对象\r\n             * @description 当文件被加入队列以后触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            _addFile: function( file ) {\r\n                var me = this;\r\n    \r\n                file = me._wrapFile( file );\r\n    \r\n                // 不过类型判断允许不允许，先派送 `beforeFileQueued`\r\n                if ( !me.owner.trigger( 'beforeFileQueued', file ) ) {\r\n                    return;\r\n                }\r\n    \r\n                // 类型不匹配，则派送错误事件，并返回。\r\n                if ( !me.acceptFile( file ) ) {\r\n                    me.owner.trigger( 'error', 'Q_TYPE_DENIED', file );\r\n                    return;\r\n                }\r\n    \r\n                me.queue.append( file );\r\n                me.owner.trigger( 'fileQueued', file );\r\n                return file;\r\n            },\r\n    \r\n            getFile: function( fileId ) {\r\n                return this.queue.getFile( fileId );\r\n            },\r\n    \r\n            /**\r\n             * @event filesQueued\r\n             * @param {File} files 数组，内容为原始File(lib/File）对象。\r\n             * @description 当一批文件添加进队列以后触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * @method addFiles\r\n             * @grammar addFiles( file ) => undefined\r\n             * @grammar addFiles( [file1, file2 ...] ) => undefined\r\n             * @param {Array of File or File} [files] Files 对象 数组\r\n             * @description 添加文件到队列\r\n             * @for  Uploader\r\n             */\r\n            addFiles: function( files ) {\r\n                var me = this;\r\n    \r\n                if ( !files.length ) {\r\n                    files = [ files ];\r\n                }\r\n    \r\n                files = $.map( files, function( file ) {\r\n                    return me._addFile( file );\r\n                });\r\n    \r\n                me.owner.trigger( 'filesQueued', files );\r\n    \r\n                if ( me.options.auto ) {\r\n                    me.request('start-upload');\r\n                }\r\n            },\r\n    \r\n            getStats: function() {\r\n                return this.stats;\r\n            },\r\n    \r\n            /**\r\n             * @event fileDequeued\r\n             * @param {File} file File对象\r\n             * @description 当文件被移除队列后触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * @method removeFile\r\n             * @grammar removeFile( file ) => undefined\r\n             * @grammar removeFile( id ) => undefined\r\n             * @param {File|id} file File对象或这File对象的id\r\n             * @description 移除某一文件。\r\n             * @for  Uploader\r\n             * @example\r\n             *\r\n             * $li.on('click', '.remove-this', function() {\r\n             *     uploader.removeFile( file );\r\n             * })\r\n             */\r\n            removeFile: function( file ) {\r\n                var me = this;\r\n    \r\n                file = file.id ? file : me.queue.getFile( file );\r\n    \r\n                file.setStatus( Status.CANCELLED );\r\n                me.owner.trigger( 'fileDequeued', file );\r\n            },\r\n    \r\n            /**\r\n             * @method getFiles\r\n             * @grammar getFiles() => Array\r\n             * @grammar getFiles( status1, status2, status... ) => Array\r\n             * @description 返回指定状态的文件集合，不传参数将返回所有状态的文件。\r\n             * @for  Uploader\r\n             * @example\r\n             * console.log( uploader.getFiles() );    // => all files\r\n             * console.log( uploader.getFiles('error') )    // => all error files.\r\n             */\r\n            getFiles: function() {\r\n                return this.queue.getFiles.apply( this.queue, arguments );\r\n            },\r\n    \r\n            fetchFile: function() {\r\n                return this.queue.fetch.apply( this.queue, arguments );\r\n            },\r\n    \r\n            /**\r\n             * @method retry\r\n             * @grammar retry() => undefined\r\n             * @grammar retry( file ) => undefined\r\n             * @description 重试上传，重试指定文件，或者从出错的文件开始重新上传。\r\n             * @for  Uploader\r\n             * @example\r\n             * function retry() {\r\n             *     uploader.retry();\r\n             * }\r\n             */\r\n            retry: function( file, noForceStart ) {\r\n                var me = this,\r\n                    files, i, len;\r\n    \r\n                if ( file ) {\r\n                    file = file.id ? file : me.queue.getFile( file );\r\n                    file.setStatus( Status.QUEUED );\r\n                    noForceStart || me.request('start-upload');\r\n                    return;\r\n                }\r\n    \r\n                files = me.queue.getFiles( Status.ERROR );\r\n                i = 0;\r\n                len = files.length;\r\n    \r\n                for ( ; i < len; i++ ) {\r\n                    file = files[ i ];\r\n                    file.setStatus( Status.QUEUED );\r\n                }\r\n    \r\n                me.request('start-upload');\r\n            },\r\n    \r\n            /**\r\n             * @method sort\r\n             * @grammar sort( fn ) => undefined\r\n             * @description 排序队列中的文件，在上传之前调整可以控制上传顺序。\r\n             * @for  Uploader\r\n             */\r\n            sortFiles: function() {\r\n                return this.queue.sort.apply( this.queue, arguments );\r\n            },\r\n    \r\n            /**\r\n             * @method reset\r\n             * @grammar reset() => undefined\r\n             * @description 重置uploader。目前只重置了队列。\r\n             * @for  Uploader\r\n             * @example\r\n             * uploader.reset();\r\n             */\r\n            reset: function() {\r\n                this.queue = new Queue();\r\n                this.stats = this.queue.stats;\r\n            }\r\n        });\r\n    \r\n    });\r\n    /**\r\n     * @fileOverview 添加获取Runtime相关信息的方法。\r\n     */\r\n    define('widgets/runtime',[\r\n        'uploader',\r\n        'runtime/runtime',\r\n        'widgets/widget'\r\n    ], function( Uploader, Runtime ) {\r\n    \r\n        Uploader.support = function() {\r\n            return Runtime.hasRuntime.apply( Runtime, arguments );\r\n        };\r\n    \r\n        return Uploader.register({\r\n            'predict-runtime-type': 'predictRuntmeType'\r\n        }, {\r\n    \r\n            init: function() {\r\n                if ( !this.predictRuntmeType() ) {\r\n                    throw Error('Runtime Error');\r\n                }\r\n            },\r\n    \r\n            /**\r\n             * 预测Uploader将采用哪个`Runtime`\r\n             * @grammar predictRuntmeType() => String\r\n             * @method predictRuntmeType\r\n             * @for  Uploader\r\n             */\r\n            predictRuntmeType: function() {\r\n                var orders = this.options.runtimeOrder || Runtime.orders,\r\n                    type = this.type,\r\n                    i, len;\r\n    \r\n                if ( !type ) {\r\n                    orders = orders.split( /\\s*,\\s*/g );\r\n    \r\n                    for ( i = 0, len = orders.length; i < len; i++ ) {\r\n                        if ( Runtime.hasRuntime( orders[ i ] ) ) {\r\n                            this.type = type = orders[ i ];\r\n                            break;\r\n                        }\r\n                    }\r\n                }\r\n    \r\n                return type;\r\n            }\r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview Transport\r\n     */\r\n    define('lib/transport',[\r\n        'base',\r\n        'runtime/client',\r\n        'mediator'\r\n    ], function( Base, RuntimeClient, Mediator ) {\r\n    \r\n        var $ = Base.$;\r\n    \r\n        function Transport( opts ) {\r\n            var me = this;\r\n    \r\n            opts = me.options = $.extend( true, {}, Transport.options, opts || {} );\r\n            RuntimeClient.call( this, 'Transport' );\r\n    \r\n            this._blob = null;\r\n            this._formData = opts.formData || {};\r\n            this._headers = opts.headers || {};\r\n    \r\n            this.on( 'progress', this._timeout );\r\n            this.on( 'load error', function() {\r\n                me.trigger( 'progress', 1 );\r\n                clearTimeout( me._timer );\r\n            });\r\n        }\r\n    \r\n        Transport.options = {\r\n            server: '',\r\n            method: 'POST',\r\n    \r\n            // 跨域时，是否允许携带cookie, 只有html5 runtime才有效\r\n            withCredentials: false,\r\n            fileVal: 'file',\r\n            timeout: 2 * 60 * 1000,    // 2分钟\r\n            formData: {},\r\n            headers: {},\r\n            sendAsBinary: false\r\n        };\r\n    \r\n        $.extend( Transport.prototype, {\r\n    \r\n            // 添加Blob, 只能添加一次，最后一次有效。\r\n            appendBlob: function( key, blob, filename ) {\r\n                var me = this,\r\n                    opts = me.options;\r\n    \r\n                if ( me.getRuid() ) {\r\n                    me.disconnectRuntime();\r\n                }\r\n    \r\n                // 连接到blob归属的同一个runtime.\r\n                me.connectRuntime( blob.ruid, function() {\r\n                    me.exec('init');\r\n                });\r\n    \r\n                me._blob = blob;\r\n                opts.fileVal = key || opts.fileVal;\r\n                opts.filename = filename || opts.filename;\r\n            },\r\n    \r\n            // 添加其他字段\r\n            append: function( key, value ) {\r\n                if ( typeof key === 'object' ) {\r\n                    $.extend( this._formData, key );\r\n                } else {\r\n                    this._formData[ key ] = value;\r\n                }\r\n            },\r\n    \r\n            setRequestHeader: function( key, value ) {\r\n                if ( typeof key === 'object' ) {\r\n                    $.extend( this._headers, key );\r\n                } else {\r\n                    this._headers[ key ] = value;\r\n                }\r\n            },\r\n    \r\n            send: function( method ) {\r\n                this.exec( 'send', method );\r\n                this._timeout();\r\n            },\r\n    \r\n            abort: function() {\r\n                clearTimeout( this._timer );\r\n                return this.exec('abort');\r\n            },\r\n    \r\n            destroy: function() {\r\n                this.trigger('destroy');\r\n                this.off();\r\n                this.exec('destroy');\r\n                this.disconnectRuntime();\r\n            },\r\n    \r\n            getResponse: function() {\r\n                return this.exec('getResponse');\r\n            },\r\n    \r\n            getResponseAsJson: function() {\r\n                return this.exec('getResponseAsJson');\r\n            },\r\n    \r\n            getStatus: function() {\r\n                return this.exec('getStatus');\r\n            },\r\n    \r\n            _timeout: function() {\r\n                var me = this,\r\n                    duration = me.options.timeout;\r\n    \r\n                if ( !duration ) {\r\n                    return;\r\n                }\r\n    \r\n                clearTimeout( me._timer );\r\n                me._timer = setTimeout(function() {\r\n                    me.abort();\r\n                    me.trigger( 'error', 'timeout' );\r\n                }, duration );\r\n            }\r\n    \r\n        });\r\n    \r\n        // 让Transport具备事件功能。\r\n        Mediator.installTo( Transport.prototype );\r\n    \r\n        return Transport;\r\n    });\r\n    /**\r\n     * @fileOverview 负责文件上传相关。\r\n     */\r\n    define('widgets/upload',[\r\n        'base',\r\n        'uploader',\r\n        'file',\r\n        'lib/transport',\r\n        'widgets/widget'\r\n    ], function( Base, Uploader, WUFile, Transport ) {\r\n    \r\n        var $ = Base.$,\r\n            isPromise = Base.isPromise,\r\n            Status = WUFile.Status;\r\n    \r\n        // 添加默认配置项\r\n        $.extend( Uploader.options, {\r\n    \r\n    \r\n            /**\r\n             * @property {Boolean} [prepareNextFile=false]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 是否允许在文件传输时提前把下一个文件准备好。\r\n             * 对于一个文件的准备工作比较耗时，比如图片压缩，md5序列化。\r\n             * 如果能提前在当前文件传输期处理，可以节省总体耗时。\r\n             */\r\n            prepareNextFile: false,\r\n    \r\n            /**\r\n             * @property {Boolean} [chunked=false]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 是否要分片处理大文件上传。\r\n             */\r\n            chunked: false,\r\n    \r\n            /**\r\n             * @property {Boolean} [chunkSize=5242880]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 如果要分片，分多大一片？ 默认大小为5M.\r\n             */\r\n            chunkSize: 5 * 1024 * 1024,\r\n    \r\n            /**\r\n             * @property {Boolean} [chunkRetry=2]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 如果某个分片由于网络问题出错，允许自动重传多少次？\r\n             */\r\n            chunkRetry: 2,\r\n    \r\n            /**\r\n             * @property {Boolean} [threads=3]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 上传并发数。允许同时最大上传进程数。\r\n             */\r\n            threads: 3,\r\n    \r\n    \r\n            /**\r\n             * @property {Object} [formData]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 文件上传请求的参数表，每次发送都会发送此对象中的参数。\r\n             */\r\n            formData: null\r\n    \r\n            /**\r\n             * @property {Object} [fileVal='file']\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 设置文件上传域的name。\r\n             */\r\n    \r\n            /**\r\n             * @property {Object} [method='POST']\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 文件上传方式，`POST`或者`GET`。\r\n             */\r\n    \r\n            /**\r\n             * @property {Object} [sendAsBinary=false]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 是否已二进制的流的方式发送文件，这样整个上传内容`php://input`都为文件内容，\r\n             * 其他参数在$_GET数组中。\r\n             */\r\n        });\r\n    \r\n        // 负责将文件切片。\r\n        function CuteFile( file, chunkSize ) {\r\n            var pending = [],\r\n                blob = file.source,\r\n                total = blob.size,\r\n                chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1,\r\n                start = 0,\r\n                index = 0,\r\n                len;\r\n    \r\n            while ( index < chunks ) {\r\n                len = Math.min( chunkSize, total - start );\r\n    \r\n                pending.push({\r\n                    file: file,\r\n                    start: start,\r\n                    end: chunkSize ? (start + len) : total,\r\n                    total: total,\r\n                    chunks: chunks,\r\n                    chunk: index++\r\n                });\r\n                start += len;\r\n            }\r\n    \r\n            file.blocks = pending.concat();\r\n            file.remaning = pending.length;\r\n    \r\n            return {\r\n                file: file,\r\n    \r\n                has: function() {\r\n                    return !!pending.length;\r\n                },\r\n    \r\n                fetch: function() {\r\n                    return pending.shift();\r\n                }\r\n            };\r\n        }\r\n    \r\n        Uploader.register({\r\n            'start-upload': 'start',\r\n            'stop-upload': 'stop',\r\n            'skip-file': 'skipFile',\r\n            'is-in-progress': 'isInProgress'\r\n        }, {\r\n    \r\n            init: function() {\r\n                var owner = this.owner;\r\n    \r\n                this.runing = false;\r\n    \r\n                // 记录当前正在传的数据，跟threads相关\r\n                this.pool = [];\r\n    \r\n                // 缓存即将上传的文件。\r\n                this.pending = [];\r\n    \r\n                // 跟踪还有多少分片没有完成上传。\r\n                this.remaning = 0;\r\n                this.__tick = Base.bindFn( this._tick, this );\r\n    \r\n                owner.on( 'uploadComplete', function( file ) {\r\n                    // 把其他块取消了。\r\n                    file.blocks && $.each( file.blocks, function( _, v ) {\r\n                        v.transport && (v.transport.abort(), v.transport.destroy());\r\n                        delete v.transport;\r\n                    });\r\n    \r\n                    delete file.blocks;\r\n                    delete file.remaning;\r\n                });\r\n            },\r\n    \r\n            /**\r\n             * @event startUpload\r\n             * @description 当开始上传流程时触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * 开始上传。此方法可以从初始状态调用开始上传流程，也可以从暂停状态调用，继续上传流程。\r\n             * @grammar upload() => undefined\r\n             * @method upload\r\n             * @for  Uploader\r\n             */\r\n            start: function() {\r\n                var me = this;\r\n    \r\n                // 移出invalid的文件\r\n                $.each( me.request( 'get-files', Status.INVALID ), function() {\r\n                    me.request( 'remove-file', this );\r\n                });\r\n    \r\n                if ( me.runing ) {\r\n                    return;\r\n                }\r\n    \r\n                me.runing = true;\r\n    \r\n                // 如果有暂停的，则续传\r\n                $.each( me.pool, function( _, v ) {\r\n                    var file = v.file;\r\n    \r\n                    if ( file.getStatus() === Status.INTERRUPT ) {\r\n                        file.setStatus( Status.PROGRESS );\r\n                        me._trigged = false;\r\n                        v.transport && v.transport.send();\r\n                    }\r\n                });\r\n    \r\n                me._trigged = false;\r\n                me.owner.trigger('startUpload');\r\n                Base.nextTick( me.__tick );\r\n            },\r\n    \r\n            /**\r\n             * @event stopUpload\r\n             * @description 当开始上传流程暂停时触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。\r\n             * @grammar stop() => undefined\r\n             * @grammar stop( true ) => undefined\r\n             * @method stop\r\n             * @for  Uploader\r\n             */\r\n            stop: function( interrupt ) {\r\n                var me = this;\r\n    \r\n                if ( me.runing === false ) {\r\n                    return;\r\n                }\r\n    \r\n                me.runing = false;\r\n    \r\n                interrupt && $.each( me.pool, function( _, v ) {\r\n                    v.transport && v.transport.abort();\r\n                    v.file.setStatus( Status.INTERRUPT );\r\n                });\r\n    \r\n                me.owner.trigger('stopUpload');\r\n            },\r\n    \r\n            /**\r\n             * 判断`Uplaode`r是否正在上传中。\r\n             * @grammar isInProgress() => Boolean\r\n             * @method isInProgress\r\n             * @for  Uploader\r\n             */\r\n            isInProgress: function() {\r\n                return !!this.runing;\r\n            },\r\n    \r\n            getStats: function() {\r\n                return this.request('get-stats');\r\n            },\r\n    \r\n            /**\r\n             * 掉过一个文件上传，直接标记指定文件为已上传状态。\r\n             * @grammar skipFile( file ) => undefined\r\n             * @method skipFile\r\n             * @for  Uploader\r\n             */\r\n            skipFile: function( file, status ) {\r\n                file = this.request( 'get-file', file );\r\n    \r\n                file.setStatus( status || Status.COMPLETE );\r\n                file.skipped = true;\r\n    \r\n                // 如果正在上传。\r\n                file.blocks && $.each( file.blocks, function( _, v ) {\r\n                    var _tr = v.transport;\r\n    \r\n                    if ( _tr ) {\r\n                        _tr.abort();\r\n                        _tr.destroy();\r\n                        delete v.transport;\r\n                    }\r\n                });\r\n    \r\n                this.owner.trigger( 'uploadSkip', file );\r\n            },\r\n    \r\n            /**\r\n             * @event uploadFinished\r\n             * @description 当所有文件上传结束时触发。\r\n             * @for  Uploader\r\n             */\r\n            _tick: function() {\r\n                var me = this,\r\n                    opts = me.options,\r\n                    fn, val;\r\n    \r\n                // 上一个promise还没有结束，则等待完成后再执行。\r\n                if ( me._promise ) {\r\n                    return me._promise.always( me.__tick );\r\n                }\r\n    \r\n                // 还有位置，且还有文件要处理的话。\r\n                if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) {\r\n                    me._trigged = false;\r\n    \r\n                    fn = function( val ) {\r\n                        me._promise = null;\r\n    \r\n                        // 有可能是reject过来的，所以要检测val的类型。\r\n                        val && val.file && me._startSend( val );\r\n                        Base.nextTick( me.__tick );\r\n                    };\r\n    \r\n                    me._promise = isPromise( val ) ? val.always( fn ) : fn( val );\r\n    \r\n                // 没有要上传的了，且没有正在传输的了。\r\n                } else if ( !me.remaning && !me.getStats().numOfQueue ) {\r\n                    me.runing = false;\r\n    \r\n                    me._trigged || Base.nextTick(function() {\r\n                        me.owner.trigger('uploadFinished');\r\n                    });\r\n                    me._trigged = true;\r\n                }\r\n            },\r\n    \r\n            _nextBlock: function() {\r\n                var me = this,\r\n                    act = me._act,\r\n                    opts = me.options,\r\n                    next, done;\r\n    \r\n                // 如果当前文件还有没有需要传输的，则直接返回剩下的。\r\n                if ( act && act.has() &&\r\n                        act.file.getStatus() === Status.PROGRESS ) {\r\n    \r\n                    // 是否提前准备下一个文件\r\n                    if ( opts.prepareNextFile && !me.pending.length ) {\r\n                        me._prepareNextFile();\r\n                    }\r\n    \r\n                    return act.fetch();\r\n    \r\n                // 否则，如果正在运行，则准备下一个文件，并等待完成后返回下个分片。\r\n                } else if ( me.runing ) {\r\n    \r\n                    // 如果缓存中有，则直接在缓存中取，没有则去queue中取。\r\n                    if ( !me.pending.length && me.getStats().numOfQueue ) {\r\n                        me._prepareNextFile();\r\n                    }\r\n    \r\n                    next = me.pending.shift();\r\n                    done = function( file ) {\r\n                        if ( !file ) {\r\n                            return null;\r\n                        }\r\n    \r\n                        act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 );\r\n                        me._act = act;\r\n                        return act.fetch();\r\n                    };\r\n    \r\n                    // 文件可能还在prepare中，也有可能已经完全准备好了。\r\n                    return isPromise( next ) ?\r\n                            next[ next.pipe ? 'pipe' : 'then']( done ) :\r\n                            done( next );\r\n                }\r\n            },\r\n    \r\n    \r\n            /**\r\n             * @event uploadStart\r\n             * @param {File} file File对象\r\n             * @description 某个文件开始上传前触发，一个文件只会触发一次。\r\n             * @for  Uploader\r\n             */\r\n            _prepareNextFile: function() {\r\n                var me = this,\r\n                    file = me.request('fetch-file'),\r\n                    pending = me.pending,\r\n                    promise;\r\n    \r\n                if ( file ) {\r\n                    promise = me.request( 'before-send-file', file, function() {\r\n    \r\n                        // 有可能文件被skip掉了。文件被skip掉后，状态坑定不是Queued.\r\n                        if ( file.getStatus() === Status.QUEUED ) {\r\n                            me.owner.trigger( 'uploadStart', file );\r\n                            file.setStatus( Status.PROGRESS );\r\n                            return file;\r\n                        }\r\n    \r\n                        return me._finishFile( file );\r\n                    });\r\n    \r\n                    // 如果还在pending中，则替换成文件本身。\r\n                    promise.done(function() {\r\n                        var idx = $.inArray( promise, pending );\r\n    \r\n                        ~idx && pending.splice( idx, 1, file );\r\n                    });\r\n    \r\n                    // befeore-send-file的钩子就有错误发生。\r\n                    promise.fail(function( reason ) {\r\n                        file.setStatus( Status.ERROR, reason );\r\n                        me.owner.trigger( 'uploadError', file, reason );\r\n                        me.owner.trigger( 'uploadComplete', file );\r\n                    });\r\n    \r\n                    pending.push( promise );\r\n                }\r\n            },\r\n    \r\n            // 让出位置了，可以让其他分片开始上传\r\n            _popBlock: function( block ) {\r\n                var idx = $.inArray( block, this.pool );\r\n    \r\n                this.pool.splice( idx, 1 );\r\n                block.file.remaning--;\r\n                this.remaning--;\r\n            },\r\n    \r\n            // 开始上传，可以被掉过。如果promise被reject了，则表示跳过此分片。\r\n            _startSend: function( block ) {\r\n                var me = this,\r\n                    file = block.file,\r\n                    promise;\r\n    \r\n                me.pool.push( block );\r\n                me.remaning++;\r\n    \r\n                // 如果没有分片，则直接使用原始的。\r\n                // 不会丢失content-type信息。\r\n                block.blob = block.chunks === 1 ? file.source :\r\n                        file.source.slice( block.start, block.end );\r\n    \r\n                // hook, 每个分片发送之前可能要做些异步的事情。\r\n                promise = me.request( 'before-send', block, function() {\r\n    \r\n                    // 有可能文件已经上传出错了，所以不需要再传输了。\r\n                    if ( file.getStatus() === Status.PROGRESS ) {\r\n                        me._doSend( block );\r\n                    } else {\r\n                        me._popBlock( block );\r\n                        Base.nextTick( me.__tick );\r\n                    }\r\n                });\r\n    \r\n                // 如果为fail了，则跳过此分片。\r\n                promise.fail(function() {\r\n                    if ( file.remaning === 1 ) {\r\n                        me._finishFile( file ).always(function() {\r\n                            block.percentage = 1;\r\n                            me._popBlock( block );\r\n                            me.owner.trigger( 'uploadComplete', file );\r\n                            Base.nextTick( me.__tick );\r\n                        });\r\n                    } else {\r\n                        block.percentage = 1;\r\n                        me._popBlock( block );\r\n                        Base.nextTick( me.__tick );\r\n                    }\r\n                });\r\n            },\r\n    \r\n    \r\n            /**\r\n             * @event uploadBeforeSend\r\n             * @param {Object} object\r\n             * @param {Object} data 默认的上传参数，可以扩展此对象来控制上传参数。\r\n             * @description 当某个文件的分块在发送前触发，主要用来询问是否要添加附带参数，大文件在开起分片上传的前提下此事件可能会触发多次。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * @event uploadAccept\r\n             * @param {Object} object\r\n             * @param {Object} ret 服务端的返回数据，json格式，如果服务端不是json格式，从ret._raw中取数据，自行解析。\r\n             * @description 当某个文件上传到服务端响应后，会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * @event uploadProgress\r\n             * @param {File} file File对象\r\n             * @param {Number} percentage 上传进度\r\n             * @description 上传过程中触发，携带上传进度。\r\n             * @for  Uploader\r\n             */\r\n    \r\n    \r\n            /**\r\n             * @event uploadError\r\n             * @param {File} file File对象\r\n             * @param {String} reason 出错的code\r\n             * @description 当文件上传出错时触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * @event uploadSuccess\r\n             * @param {File} file File对象\r\n             * @param {Object} response 服务端返回的数据\r\n             * @description 当文件上传成功时触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * @event uploadComplete\r\n             * @param {File} [file] File对象\r\n             * @description 不管成功或者失败，文件上传完成时触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            // 做上传操作。\r\n            _doSend: function( block ) {\r\n                var me = this,\r\n                    owner = me.owner,\r\n                    opts = me.options,\r\n                    file = block.file,\r\n                    tr = new Transport( opts ),\r\n                    data = $.extend({}, opts.formData ),\r\n                    headers = $.extend({}, opts.headers ),\r\n                    requestAccept, ret;\r\n    \r\n                block.transport = tr;\r\n    \r\n                tr.on( 'destroy', function() {\r\n                    delete block.transport;\r\n                    me._popBlock( block );\r\n                    Base.nextTick( me.__tick );\r\n                });\r\n    \r\n                // 广播上传进度。以文件为单位。\r\n                tr.on( 'progress', function( percentage ) {\r\n                    var totalPercent = 0,\r\n                        uploaded = 0;\r\n    \r\n                    // 可能没有abort掉，progress还是执行进来了。\r\n                    // if ( !file.blocks ) {\r\n                    //     return;\r\n                    // }\r\n    \r\n                    totalPercent = block.percentage = percentage;\r\n    \r\n                    if ( block.chunks > 1 ) {    // 计算文件的整体速度。\r\n                        $.each( file.blocks, function( _, v ) {\r\n                            uploaded += (v.percentage || 0) * (v.end - v.start);\r\n                        });\r\n    \r\n                        totalPercent = uploaded / file.size;\r\n                    }\r\n    \r\n                    owner.trigger( 'uploadProgress', file, totalPercent || 0 );\r\n                });\r\n    \r\n                // 用来询问，是否返回的结果是有错误的。\r\n                requestAccept = function( reject ) {\r\n                    var fn;\r\n    \r\n                    ret = tr.getResponseAsJson() || {};\r\n                    ret._raw = tr.getResponse();\r\n                    fn = function( value ) {\r\n                        reject = value;\r\n                    };\r\n    \r\n                    // 服务端响应了，不代表成功了，询问是否响应正确。\r\n                    if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) {\r\n                        reject = reject || 'server';\r\n                    }\r\n    \r\n                    return reject;\r\n                };\r\n    \r\n                // 尝试重试，然后广播文件上传出错。\r\n                tr.on( 'error', function( type, flag ) {\r\n                    block.retried = block.retried || 0;\r\n    \r\n                    // 自动重试\r\n                    if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) &&\r\n                            block.retried < opts.chunkRetry ) {\r\n    \r\n                        block.retried++;\r\n                        tr.send();\r\n    \r\n                    } else {\r\n    \r\n                        // http status 500 ~ 600\r\n                        if ( !flag && type === 'server' ) {\r\n                            type = requestAccept( type );\r\n                        }\r\n    \r\n                        file.setStatus( Status.ERROR, type );\r\n                        owner.trigger( 'uploadError', file, type );\r\n                        owner.trigger( 'uploadComplete', file );\r\n                    }\r\n                });\r\n    \r\n                // 上传成功\r\n                tr.on( 'load', function() {\r\n                    var reason;\r\n    \r\n                    // 如果非预期，转向上传出错。\r\n                    if ( (reason = requestAccept()) ) {\r\n                        tr.trigger( 'error', reason, true );\r\n                        return;\r\n                    }\r\n    \r\n                    // 全部上传完成。\r\n                    if ( file.remaning === 1 ) {\r\n                        me._finishFile( file, ret );\r\n                    } else {\r\n                        tr.destroy();\r\n                    }\r\n                });\r\n    \r\n                // 配置默认的上传字段。\r\n                data = $.extend( data, {\r\n                    id: file.id,\r\n                    name: file.name,\r\n                    type: file.type,\r\n                    lastModifiedDate: file.lastModifiedDate,\r\n                    size: file.size\r\n                });\r\n    \r\n                block.chunks > 1 && $.extend( data, {\r\n                    chunks: block.chunks,\r\n                    chunk: block.chunk\r\n                });\r\n    \r\n                // 在发送之间可以添加字段什么的。。。\r\n                // 如果默认的字段不够使用，可以通过监听此事件来扩展\r\n                owner.trigger( 'uploadBeforeSend', block, data, headers );\r\n    \r\n                // 开始发送。\r\n                tr.appendBlob( opts.fileVal, block.blob, file.name );\r\n                tr.append( data );\r\n                tr.setRequestHeader( headers );\r\n                tr.send();\r\n            },\r\n    \r\n            // 完成上传。\r\n            _finishFile: function( file, ret, hds ) {\r\n                var owner = this.owner;\r\n    \r\n                return owner\r\n                        .request( 'after-send-file', arguments, function() {\r\n                            file.setStatus( Status.COMPLETE );\r\n                            owner.trigger( 'uploadSuccess', file, ret, hds );\r\n                        })\r\n                        .fail(function( reason ) {\r\n    \r\n                            // 如果外部已经标记为invalid什么的，不再改状态。\r\n                            if ( file.getStatus() === Status.PROGRESS ) {\r\n                                file.setStatus( Status.ERROR, reason );\r\n                            }\r\n    \r\n                            owner.trigger( 'uploadError', file, reason );\r\n                        })\r\n                        .always(function() {\r\n                            owner.trigger( 'uploadComplete', file );\r\n                        });\r\n            }\r\n    \r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview 各种验证，包括文件总大小是否超出、单文件是否超出和文件是否重复。\r\n     */\r\n    \r\n    define('widgets/validator',[\r\n        'base',\r\n        'uploader',\r\n        'file',\r\n        'widgets/widget'\r\n    ], function( Base, Uploader, WUFile ) {\r\n    \r\n        var $ = Base.$,\r\n            validators = {},\r\n            api;\r\n    \r\n        /**\r\n         * @event error\r\n         * @param {String} type 错误类型。\r\n         * @description 当validate不通过时，会以派送错误事件的形式通知调用者。通过`upload.on('error', handler)`可以捕获到此类错误，目前有以下错误会在特定的情况下派送错来。\r\n         *\r\n         * * `Q_EXCEED_NUM_LIMIT` 在设置了`fileNumLimit`且尝试给`uploader`添加的文件数量超出这个值时派送。\r\n         * * `Q_EXCEED_SIZE_LIMIT` 在设置了`Q_EXCEED_SIZE_LIMIT`且尝试给`uploader`添加的文件总大小超出这个值时派送。\r\n         * @for  Uploader\r\n         */\r\n    \r\n        // 暴露给外面的api\r\n        api = {\r\n    \r\n            // 添加验证器\r\n            addValidator: function( type, cb ) {\r\n                validators[ type ] = cb;\r\n            },\r\n    \r\n            // 移除验证器\r\n            removeValidator: function( type ) {\r\n                delete validators[ type ];\r\n            }\r\n        };\r\n    \r\n        // 在Uploader初始化的时候启动Validators的初始化\r\n        Uploader.register({\r\n            init: function() {\r\n                var me = this;\r\n                $.each( validators, function() {\r\n                    this.call( me.owner );\r\n                });\r\n            }\r\n        });\r\n    \r\n        /**\r\n         * @property {int} [fileNumLimit=undefined]\r\n         * @namespace options\r\n         * @for Uploader\r\n         * @description 验证文件总数量, 超出则不允许加入队列。\r\n         */\r\n        api.addValidator( 'fileNumLimit', function() {\r\n            var uploader = this,\r\n                opts = uploader.options,\r\n                count = 0,\r\n                max = opts.fileNumLimit >> 0,\r\n                flag = true;\r\n    \r\n            if ( !max ) {\r\n                return;\r\n            }\r\n    \r\n            uploader.on( 'beforeFileQueued', function( file ) {\r\n    \r\n                if ( count >= max && flag ) {\r\n                    flag = false;\r\n                    this.trigger( 'error', 'Q_EXCEED_NUM_LIMIT', max, file );\r\n                    setTimeout(function() {\r\n                        flag = true;\r\n                    }, 1 );\r\n                }\r\n    \r\n                return count >= max ? false : true;\r\n            });\r\n    \r\n            uploader.on( 'fileQueued', function() {\r\n                count++;\r\n            });\r\n    \r\n            uploader.on( 'fileDequeued', function() {\r\n                count--;\r\n            });\r\n    \r\n            uploader.on( 'uploadFinished', function() {\r\n                count = 0;\r\n            });\r\n        });\r\n    \r\n    \r\n        /**\r\n         * @property {int} [fileSizeLimit=undefined]\r\n         * @namespace options\r\n         * @for Uploader\r\n         * @description 验证文件总大小是否超出限制, 超出则不允许加入队列。\r\n         */\r\n        api.addValidator( 'fileSizeLimit', function() {\r\n            var uploader = this,\r\n                opts = uploader.options,\r\n                count = 0,\r\n                max = opts.fileSizeLimit >> 0,\r\n                flag = true;\r\n    \r\n            if ( !max ) {\r\n                return;\r\n            }\r\n    \r\n            uploader.on( 'beforeFileQueued', function( file ) {\r\n                var invalid = count + file.size > max;\r\n    \r\n                if ( invalid && flag ) {\r\n                    flag = false;\r\n                    this.trigger( 'error', 'Q_EXCEED_SIZE_LIMIT', max, file );\r\n                    setTimeout(function() {\r\n                        flag = true;\r\n                    }, 1 );\r\n                }\r\n    \r\n                return invalid ? false : true;\r\n            });\r\n    \r\n            uploader.on( 'fileQueued', function( file ) {\r\n                count += file.size;\r\n            });\r\n    \r\n            uploader.on( 'fileDequeued', function( file ) {\r\n                count -= file.size;\r\n            });\r\n    \r\n            uploader.on( 'uploadFinished', function() {\r\n                count = 0;\r\n            });\r\n        });\r\n    \r\n        /**\r\n         * @property {int} [fileSingleSizeLimit=undefined]\r\n         * @namespace options\r\n         * @for Uploader\r\n         * @description 验证单个文件大小是否超出限制, 超出则不允许加入队列。\r\n         */\r\n        api.addValidator( 'fileSingleSizeLimit', function() {\r\n            var uploader = this,\r\n                opts = uploader.options,\r\n                max = opts.fileSingleSizeLimit;\r\n    \r\n            if ( !max ) {\r\n                return;\r\n            }\r\n    \r\n            uploader.on( 'beforeFileQueued', function( file ) {\r\n    \r\n                if ( file.size > max ) {\r\n                    file.setStatus( WUFile.Status.INVALID, 'exceed_size' );\r\n                    this.trigger( 'error', 'F_EXCEED_SIZE', file );\r\n                    return false;\r\n                }\r\n    \r\n            });\r\n    \r\n        });\r\n    \r\n        /**\r\n         * @property {int} [duplicate=undefined]\r\n         * @namespace options\r\n         * @for Uploader\r\n         * @description 去重， 根据文件名字、文件大小和最后修改时间来生成hash Key.\r\n         */\r\n        api.addValidator( 'duplicate', function() {\r\n            var uploader = this,\r\n                opts = uploader.options,\r\n                mapping = {};\r\n    \r\n            if ( opts.duplicate ) {\r\n                return;\r\n            }\r\n    \r\n            function hashString( str ) {\r\n                var hash = 0,\r\n                    i = 0,\r\n                    len = str.length,\r\n                    _char;\r\n    \r\n                for ( ; i < len; i++ ) {\r\n                    _char = str.charCodeAt( i );\r\n                    hash = _char + (hash << 6) + (hash << 16) - hash;\r\n                }\r\n    \r\n                return hash;\r\n            }\r\n    \r\n            uploader.on( 'beforeFileQueued', function( file ) {\r\n                var hash = file.__hash || (file.__hash = hashString( file.name +\r\n                        file.size + file.lastModifiedDate ));\r\n    \r\n                // 已经重复了\r\n                if ( mapping[ hash ] ) {\r\n                    this.trigger( 'error', 'F_DUPLICATE', file );\r\n                    return false;\r\n                }\r\n            });\r\n    \r\n            uploader.on( 'fileQueued', function( file ) {\r\n                var hash = file.__hash;\r\n    \r\n                hash && (mapping[ hash ] = true);\r\n            });\r\n    \r\n            uploader.on( 'fileDequeued', function( file ) {\r\n                var hash = file.__hash;\r\n    \r\n                hash && (delete mapping[ hash ]);\r\n            });\r\n        });\r\n    \r\n        return api;\r\n    });\r\n    \r\n    /**\r\n     * @fileOverview Runtime管理器，负责Runtime的选择, 连接\r\n     */\r\n    define('runtime/compbase',[],function() {\r\n    \r\n        function CompBase( owner, runtime ) {\r\n    \r\n            this.owner = owner;\r\n            this.options = owner.options;\r\n    \r\n            this.getRuntime = function() {\r\n                return runtime;\r\n            };\r\n    \r\n            this.getRuid = function() {\r\n                return runtime.uid;\r\n            };\r\n    \r\n            this.trigger = function() {\r\n                return owner.trigger.apply( owner, arguments );\r\n            };\r\n        }\r\n    \r\n        return CompBase;\r\n    });\r\n    /**\r\n     * @fileOverview Html5Runtime\r\n     */\r\n    define('runtime/html5/runtime',[\r\n        'base',\r\n        'runtime/runtime',\r\n        'runtime/compbase'\r\n    ], function( Base, Runtime, CompBase ) {\r\n    \r\n        var type = 'html5',\r\n            components = {};\r\n    \r\n        function Html5Runtime() {\r\n            var pool = {},\r\n                me = this,\r\n                destory = this.destory;\r\n    \r\n            Runtime.apply( me, arguments );\r\n            me.type = type;\r\n    \r\n    \r\n            // 这个方法的调用者，实际上是RuntimeClient\r\n            me.exec = function( comp, fn/*, args...*/) {\r\n                var client = this,\r\n                    uid = client.uid,\r\n                    args = Base.slice( arguments, 2 ),\r\n                    instance;\r\n    \r\n                if ( components[ comp ] ) {\r\n                    instance = pool[ uid ] = pool[ uid ] ||\r\n                            new components[ comp ]( client, me );\r\n    \r\n                    if ( instance[ fn ] ) {\r\n                        return instance[ fn ].apply( instance, args );\r\n                    }\r\n                }\r\n            };\r\n    \r\n            me.destory = function() {\r\n                // @todo 删除池子中的所有实例\r\n                return destory && destory.apply( this, arguments );\r\n            };\r\n        }\r\n    \r\n        Base.inherits( Runtime, {\r\n            constructor: Html5Runtime,\r\n    \r\n            // 不需要连接其他程序，直接执行callback\r\n            init: function() {\r\n                var me = this;\r\n                setTimeout(function() {\r\n                    me.trigger('ready');\r\n                }, 1 );\r\n            }\r\n    \r\n        });\r\n    \r\n        // 注册Components\r\n        Html5Runtime.register = function( name, component ) {\r\n            var klass = components[ name ] = Base.inherits( CompBase, component );\r\n            return klass;\r\n        };\r\n    \r\n        // 注册html5运行时。\r\n        // 只有在支持的前提下注册。\r\n        if ( window.Blob && window.FileReader && window.DataView ) {\r\n            Runtime.addRuntime( type, Html5Runtime );\r\n        }\r\n    \r\n        return Html5Runtime;\r\n    });\r\n    /**\r\n     * @fileOverview Blob Html实现\r\n     */\r\n    define('runtime/html5/blob',[\r\n        'runtime/html5/runtime',\r\n        'lib/blob'\r\n    ], function( Html5Runtime, Blob ) {\r\n    \r\n        return Html5Runtime.register( 'Blob', {\r\n            slice: function( start, end ) {\r\n                var blob = this.owner.source,\r\n                    slice = blob.slice || blob.webkitSlice || blob.mozSlice;\r\n    \r\n                blob = slice.call( blob, start, end );\r\n    \r\n                return new Blob( this.getRuid(), blob );\r\n            }\r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview FilePaste\r\n     */\r\n    define('runtime/html5/dnd',[\r\n        'base',\r\n        'runtime/html5/runtime',\r\n        'lib/file'\r\n    ], function( Base, Html5Runtime, File ) {\r\n    \r\n        var $ = Base.$,\r\n            prefix = 'webuploader-dnd-';\r\n    \r\n        return Html5Runtime.register( 'DragAndDrop', {\r\n            init: function() {\r\n                var elem = this.elem = this.options.container;\r\n    \r\n                this.dragEnterHandler = Base.bindFn( this._dragEnterHandler, this );\r\n                this.dragOverHandler = Base.bindFn( this._dragOverHandler, this );\r\n                this.dragLeaveHandler = Base.bindFn( this._dragLeaveHandler, this );\r\n                this.dropHandler = Base.bindFn( this._dropHandler, this );\r\n                this.dndOver = false;\r\n    \r\n                elem.on( 'dragenter', this.dragEnterHandler );\r\n                elem.on( 'dragover', this.dragOverHandler );\r\n                elem.on( 'dragleave', this.dragLeaveHandler );\r\n                elem.on( 'drop', this.dropHandler );\r\n    \r\n                if ( this.options.disableGlobalDnd ) {\r\n                    $( document ).on( 'dragover', this.dragOverHandler );\r\n                    $( document ).on( 'drop', this.dropHandler );\r\n                }\r\n            },\r\n    \r\n            _dragEnterHandler: function( e ) {\r\n                var me = this,\r\n                    denied = me._denied || false,\r\n                    items;\r\n    \r\n                e = e.originalEvent || e;\r\n    \r\n                if ( !me.dndOver ) {\r\n                    me.dndOver = true;\r\n    \r\n                    // 注意只有 chrome 支持。\r\n                    items = e.dataTransfer.items;\r\n    \r\n                    if ( items && items.length ) {\r\n                        me._denied = denied = !me.trigger( 'accept', items );\r\n                    }\r\n    \r\n                    me.elem.addClass( prefix + 'over' );\r\n                    me.elem[ denied ? 'addClass' :\r\n                            'removeClass' ]( prefix + 'denied' );\r\n                }\r\n    \r\n    \r\n                e.dataTransfer.dropEffect = denied ? 'none' : 'copy';\r\n    \r\n                return false;\r\n            },\r\n    \r\n            _dragOverHandler: function( e ) {\r\n                // 只处理框内的。\r\n                var parentElem = this.elem.parent().get( 0 );\r\n                if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) {\r\n                    return false;\r\n                }\r\n    \r\n                clearTimeout( this._leaveTimer );\r\n                this._dragEnterHandler.call( this, e );\r\n    \r\n                return false;\r\n            },\r\n    \r\n            _dragLeaveHandler: function() {\r\n                var me = this,\r\n                    handler;\r\n    \r\n                handler = function() {\r\n                    me.dndOver = false;\r\n                    me.elem.removeClass( prefix + 'over ' + prefix + 'denied' );\r\n                };\r\n    \r\n                clearTimeout( me._leaveTimer );\r\n                me._leaveTimer = setTimeout( handler, 100 );\r\n                return false;\r\n            },\r\n    \r\n            _dropHandler: function( e ) {\r\n                var me = this,\r\n                    ruid = me.getRuid(),\r\n                    parentElem = me.elem.parent().get( 0 );\r\n    \r\n                // 只处理框内的。\r\n                if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) {\r\n                    return false;\r\n                }\r\n    \r\n                me._getTansferFiles( e, function( results ) {\r\n                    me.trigger( 'drop', $.map( results, function( file ) {\r\n                        return new File( ruid, file );\r\n                    }) );\r\n                });\r\n    \r\n                me.dndOver = false;\r\n                me.elem.removeClass( prefix + 'over' );\r\n                return false;\r\n            },\r\n    \r\n            // 如果传入 callback 则去查看文件夹，否则只管当前文件夹。\r\n            _getTansferFiles: function( e, callback ) {\r\n                var results  = [],\r\n                    promises = [],\r\n                    items, files, dataTransfer, file, item, i, len, canAccessFolder;\r\n    \r\n                e = e.originalEvent || e;\r\n    \r\n                dataTransfer = e.dataTransfer;\r\n                items = dataTransfer.items;\r\n                files = dataTransfer.files;\r\n    \r\n                canAccessFolder = !!(items && items[ 0 ].webkitGetAsEntry);\r\n    \r\n                for ( i = 0, len = files.length; i < len; i++ ) {\r\n                    file = files[ i ];\r\n                    item = items && items[ i ];\r\n    \r\n                    if ( canAccessFolder && item.webkitGetAsEntry().isDirectory ) {\r\n    \r\n                        promises.push( this._traverseDirectoryTree(\r\n                                item.webkitGetAsEntry(), results ) );\r\n                    } else {\r\n                        results.push( file );\r\n                    }\r\n                }\r\n    \r\n                Base.when.apply( Base, promises ).done(function() {\r\n    \r\n                    if ( !results.length ) {\r\n                        return;\r\n                    }\r\n    \r\n                    callback( results );\r\n                });\r\n            },\r\n    \r\n            _traverseDirectoryTree: function( entry, results ) {\r\n                var deferred = Base.Deferred(),\r\n                    me = this;\r\n    \r\n                if ( entry.isFile ) {\r\n                    entry.file(function( file ) {\r\n                        results.push( file );\r\n                        deferred.resolve();\r\n                    });\r\n                } else if ( entry.isDirectory ) {\r\n                    entry.createReader().readEntries(function( entries ) {\r\n                        var len = entries.length,\r\n                            promises = [],\r\n                            arr = [],    // 为了保证顺序。\r\n                            i;\r\n    \r\n                        for ( i = 0; i < len; i++ ) {\r\n                            promises.push( me._traverseDirectoryTree(\r\n                                    entries[ i ], arr ) );\r\n                        }\r\n    \r\n                        Base.when.apply( Base, promises ).then(function() {\r\n                            results.push.apply( results, arr );\r\n                            deferred.resolve();\r\n                        }, deferred.reject );\r\n                    });\r\n                }\r\n    \r\n                return deferred.promise();\r\n            },\r\n    \r\n            destroy: function() {\r\n                var elem = this.elem;\r\n    \r\n                elem.off( 'dragenter', this.dragEnterHandler );\r\n                elem.off( 'dragover', this.dragEnterHandler );\r\n                elem.off( 'dragleave', this.dragLeaveHandler );\r\n                elem.off( 'drop', this.dropHandler );\r\n    \r\n                if ( this.options.disableGlobalDnd ) {\r\n                    $( document ).off( 'dragover', this.dragOverHandler );\r\n                    $( document ).off( 'drop', this.dropHandler );\r\n                }\r\n            }\r\n        });\r\n    });\r\n    \r\n    /**\r\n     * @fileOverview FilePaste\r\n     */\r\n    define('runtime/html5/filepaste',[\r\n        'base',\r\n        'runtime/html5/runtime',\r\n        'lib/file'\r\n    ], function( Base, Html5Runtime, File ) {\r\n    \r\n        return Html5Runtime.register( 'FilePaste', {\r\n            init: function() {\r\n                var opts = this.options,\r\n                    elem = this.elem = opts.container,\r\n                    accept = '.*',\r\n                    arr, i, len, item;\r\n    \r\n                // accetp的mimeTypes中生成匹配正则。\r\n                if ( opts.accept ) {\r\n                    arr = [];\r\n    \r\n                    for ( i = 0, len = opts.accept.length; i < len; i++ ) {\r\n                        item = opts.accept[ i ].mimeTypes;\r\n                        item && arr.push( item );\r\n                    }\r\n    \r\n                    if ( arr.length ) {\r\n                        accept = arr.join(',');\r\n                        accept = accept.replace( /,/g, '|' ).replace( /\\*/g, '.*' );\r\n                    }\r\n                }\r\n                this.accept = accept = new RegExp( accept, 'i' );\r\n                this.hander = Base.bindFn( this._pasteHander, this );\r\n                elem.on( 'paste', this.hander );\r\n            },\r\n    \r\n            _pasteHander: function( e ) {\r\n                var allowed = [],\r\n                    ruid = this.getRuid(),\r\n                    items, item, blob, i, len;\r\n    \r\n                e = e.originalEvent || e;\r\n                items = e.clipboardData.items;\r\n    \r\n                for ( i = 0, len = items.length; i < len; i++ ) {\r\n                    item = items[ i ];\r\n    \r\n                    if ( item.kind !== 'file' || !(blob = item.getAsFile()) ) {\r\n                        continue;\r\n                    }\r\n    \r\n                    allowed.push( new File( ruid, blob ) );\r\n                }\r\n    \r\n                if ( allowed.length ) {\r\n                    // 不阻止非文件粘贴（文字粘贴）的事件冒泡\r\n                    e.preventDefault();\r\n                    e.stopPropagation();\r\n                    this.trigger( 'paste', allowed );\r\n                }\r\n            },\r\n    \r\n            destroy: function() {\r\n                this.elem.off( 'paste', this.hander );\r\n            }\r\n        });\r\n    });\r\n    \r\n    /**\r\n     * @fileOverview FilePicker\r\n     */\r\n    define('runtime/html5/filepicker',[\r\n        'base',\r\n        'runtime/html5/runtime'\r\n    ], function( Base, Html5Runtime ) {\r\n    \r\n        var $ = Base.$;\r\n    \r\n        return Html5Runtime.register( 'FilePicker', {\r\n            init: function() {\r\n                var container = this.getRuntime().getContainer(),\r\n                    me = this,\r\n                    owner = me.owner,\r\n                    opts = me.options,\r\n                    lable = $( document.createElement('label') ),\r\n                    input = $( document.createElement('input') ),\r\n                    arr, i, len, mouseHandler;\r\n    \r\n                input.attr( 'type', 'file' );\r\n                input.attr( 'name', opts.name );\r\n                input.addClass('webuploader-element-invisible');\r\n    \r\n                lable.on( 'click', function() {\r\n                    input.trigger('click');\r\n                });\r\n    \r\n                lable.css({\r\n                    opacity: 0,\r\n                    width: '100%',\r\n                    height: '100%',\r\n                    display: 'block',\r\n                    cursor: 'pointer',\r\n                    background: '#ffffff'\r\n                });\r\n    \r\n                if ( opts.multiple ) {\r\n                    input.attr( 'multiple', 'multiple' );\r\n                }\r\n    \r\n                // @todo Firefox不支持单独指定后缀\r\n                if ( opts.accept && opts.accept.length > 0 ) {\r\n                    arr = [];\r\n    \r\n                    for ( i = 0, len = opts.accept.length; i < len; i++ ) {\r\n                        arr.push( opts.accept[ i ].mimeTypes );\r\n                    }\r\n    \r\n                    input.attr( 'accept', arr.join(',') );\r\n                }\r\n    \r\n                container.append( input );\r\n                container.append( lable );\r\n    \r\n                mouseHandler = function( e ) {\r\n                    owner.trigger( e.type );\r\n                };\r\n    \r\n                input.on( 'change', function( e ) {\r\n                    var fn = arguments.callee,\r\n                        clone;\r\n    \r\n                    me.files = e.target.files;\r\n    \r\n                    // reset input\r\n                    clone = this.cloneNode( true );\r\n                    this.parentNode.replaceChild( clone, this );\r\n    \r\n                    input.off();\r\n                    input = $( clone ).on( 'change', fn )\r\n                            .on( 'mouseenter mouseleave', mouseHandler );\r\n    \r\n                    owner.trigger('change');\r\n                });\r\n    \r\n                lable.on( 'mouseenter mouseleave', mouseHandler );\r\n    \r\n            },\r\n    \r\n    \r\n            getFiles: function() {\r\n                return this.files;\r\n            },\r\n    \r\n            destroy: function() {\r\n                // todo\r\n            }\r\n        });\r\n    });\r\n    /**\r\n     * Terms:\r\n     *\r\n     * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer\r\n     * @fileOverview Image控件\r\n     */\r\n    define('runtime/html5/util',[\r\n        'base'\r\n    ], function( Base ) {\r\n    \r\n        var urlAPI = window.createObjectURL && window ||\r\n                window.URL && URL.revokeObjectURL && URL ||\r\n                window.webkitURL,\r\n            createObjectURL = Base.noop,\r\n            revokeObjectURL = createObjectURL;\r\n    \r\n        if ( urlAPI ) {\r\n    \r\n            // 更安全的方式调用，比如android里面就能把context改成其他的对象。\r\n            createObjectURL = function() {\r\n                return urlAPI.createObjectURL.apply( urlAPI, arguments );\r\n            };\r\n    \r\n            revokeObjectURL = function() {\r\n                return urlAPI.revokeObjectURL.apply( urlAPI, arguments );\r\n            };\r\n        }\r\n    \r\n        return {\r\n            createObjectURL: createObjectURL,\r\n            revokeObjectURL: revokeObjectURL,\r\n    \r\n            dataURL2Blob: function( dataURI ) {\r\n                var byteStr, intArray, ab, i, mimetype, parts;\r\n    \r\n                parts = dataURI.split(',');\r\n    \r\n                if ( ~parts[ 0 ].indexOf('base64') ) {\r\n                    byteStr = atob( parts[ 1 ] );\r\n                } else {\r\n                    byteStr = decodeURIComponent( parts[ 1 ] );\r\n                }\r\n    \r\n                ab = new ArrayBuffer( byteStr.length );\r\n                intArray = new Uint8Array( ab );\r\n    \r\n                for ( i = 0; i < byteStr.length; i++ ) {\r\n                    intArray[ i ] = byteStr.charCodeAt( i );\r\n                }\r\n    \r\n                mimetype = parts[ 0 ].split(':')[ 1 ].split(';')[ 0 ];\r\n    \r\n                return this.arrayBufferToBlob( ab, mimetype );\r\n            },\r\n    \r\n            dataURL2ArrayBuffer: function( dataURI ) {\r\n                var byteStr, intArray, i, parts;\r\n    \r\n                parts = dataURI.split(',');\r\n    \r\n                if ( ~parts[ 0 ].indexOf('base64') ) {\r\n                    byteStr = atob( parts[ 1 ] );\r\n                } else {\r\n                    byteStr = decodeURIComponent( parts[ 1 ] );\r\n                }\r\n    \r\n                intArray = new Uint8Array( byteStr.length );\r\n    \r\n                for ( i = 0; i < byteStr.length; i++ ) {\r\n                    intArray[ i ] = byteStr.charCodeAt( i );\r\n                }\r\n    \r\n                return intArray.buffer;\r\n            },\r\n    \r\n            arrayBufferToBlob: function( buffer, type ) {\r\n                var builder = window.BlobBuilder || window.WebKitBlobBuilder,\r\n                    bb;\r\n    \r\n                // android不支持直接new Blob, 只能借助blobbuilder.\r\n                if ( builder ) {\r\n                    bb = new builder();\r\n                    bb.append( buffer );\r\n                    return bb.getBlob( type );\r\n                }\r\n    \r\n                return new Blob([ buffer ], type ? { type: type } : {} );\r\n            },\r\n    \r\n            // 抽出来主要是为了解决android下面canvas.toDataUrl不支持jpeg.\r\n            // 你得到的结果是png.\r\n            canvasToDataUrl: function( canvas, type, quality ) {\r\n                return canvas.toDataURL( type, quality / 100 );\r\n            },\r\n    \r\n            // imagemeat会复写这个方法，如果用户选择加载那个文件了的话。\r\n            parseMeta: function( blob, callback ) {\r\n                callback( false, {});\r\n            },\r\n    \r\n            // imagemeat会复写这个方法，如果用户选择加载那个文件了的话。\r\n            updateImageHead: function( data ) {\r\n                return data;\r\n            }\r\n        };\r\n    });\r\n    /**\r\n     * Terms:\r\n     *\r\n     * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer\r\n     * @fileOverview Image控件\r\n     */\r\n    define('runtime/html5/imagemeta',[\r\n        'runtime/html5/util'\r\n    ], function( Util ) {\r\n    \r\n        var api;\r\n    \r\n        api = {\r\n            parsers: {\r\n                0xffe1: []\r\n            },\r\n    \r\n            maxMetaDataSize: 262144,\r\n    \r\n            parse: function( blob, cb ) {\r\n                var me = this,\r\n                    fr = new FileReader();\r\n    \r\n                fr.onload = function() {\r\n                    cb( false, me._parse( this.result ) );\r\n                    fr = fr.onload = fr.onerror = null;\r\n                };\r\n    \r\n                fr.onerror = function( e ) {\r\n                    cb( e.message );\r\n                    fr = fr.onload = fr.onerror = null;\r\n                };\r\n    \r\n                blob = blob.slice( 0, me.maxMetaDataSize );\r\n                fr.readAsArrayBuffer( blob.getSource() );\r\n            },\r\n    \r\n            _parse: function( buffer, noParse ) {\r\n                if ( buffer.byteLength < 6 ) {\r\n                    return;\r\n                }\r\n    \r\n                var dataview = new DataView( buffer ),\r\n                    offset = 2,\r\n                    maxOffset = dataview.byteLength - 4,\r\n                    headLength = offset,\r\n                    ret = {},\r\n                    markerBytes, markerLength, parsers, i;\r\n    \r\n                if ( dataview.getUint16( 0 ) === 0xffd8 ) {\r\n    \r\n                    while ( offset < maxOffset ) {\r\n                        markerBytes = dataview.getUint16( offset );\r\n    \r\n                        if ( markerBytes >= 0xffe0 && markerBytes <= 0xffef ||\r\n                                markerBytes === 0xfffe ) {\r\n    \r\n                            markerLength = dataview.getUint16( offset + 2 ) + 2;\r\n    \r\n                            if ( offset + markerLength > dataview.byteLength ) {\r\n                                break;\r\n                            }\r\n    \r\n                            parsers = api.parsers[ markerBytes ];\r\n    \r\n                            if ( !noParse && parsers ) {\r\n                                for ( i = 0; i < parsers.length; i += 1 ) {\r\n                                    parsers[ i ].call( api, dataview, offset,\r\n                                            markerLength, ret );\r\n                                }\r\n                            }\r\n    \r\n                            offset += markerLength;\r\n                            headLength = offset;\r\n                        } else {\r\n                            break;\r\n                        }\r\n                    }\r\n    \r\n                    if ( headLength > 6 ) {\r\n                        if ( buffer.slice ) {\r\n                            ret.imageHead = buffer.slice( 2, headLength );\r\n                        } else {\r\n                            // Workaround for IE10, which does not yet\r\n                            // support ArrayBuffer.slice:\r\n                            ret.imageHead = new Uint8Array( buffer )\r\n                                    .subarray( 2, headLength );\r\n                        }\r\n                    }\r\n                }\r\n    \r\n                return ret;\r\n            },\r\n    \r\n            updateImageHead: function( buffer, head ) {\r\n                var data = this._parse( buffer, true ),\r\n                    buf1, buf2, bodyoffset;\r\n    \r\n    \r\n                bodyoffset = 2;\r\n                if ( data.imageHead ) {\r\n                    bodyoffset = 2 + data.imageHead.byteLength;\r\n                }\r\n    \r\n                if ( buffer.slice ) {\r\n                    buf2 = buffer.slice( bodyoffset );\r\n                } else {\r\n                    buf2 = new Uint8Array( buffer ).subarray( bodyoffset );\r\n                }\r\n    \r\n                buf1 = new Uint8Array( head.byteLength + 2 + buf2.byteLength );\r\n    \r\n                buf1[ 0 ] = 0xFF;\r\n                buf1[ 1 ] = 0xD8;\r\n                buf1.set( new Uint8Array( head ), 2 );\r\n                buf1.set( new Uint8Array( buf2 ), head.byteLength + 2 );\r\n    \r\n                return buf1.buffer;\r\n            }\r\n        };\r\n    \r\n        Util.parseMeta = function() {\r\n            return api.parse.apply( api, arguments );\r\n        };\r\n    \r\n        Util.updateImageHead = function() {\r\n            return api.updateImageHead.apply( api, arguments );\r\n        };\r\n    \r\n        return api;\r\n    });\r\n    /**\r\n     * 代码来自于：https://github.com/blueimp/JavaScript-Load-Image\r\n     * 暂时项目中只用了orientation.\r\n     *\r\n     * 去除了 Exif Sub IFD Pointer, GPS Info IFD Pointer, Exif Thumbnail.\r\n     * @fileOverview EXIF解析\r\n     */\r\n    \r\n    // Sample\r\n    // ====================================\r\n    // Make : Apple\r\n    // Model : iPhone 4S\r\n    // Orientation : 1\r\n    // XResolution : 72 [72/1]\r\n    // YResolution : 72 [72/1]\r\n    // ResolutionUnit : 2\r\n    // Software : QuickTime 7.7.1\r\n    // DateTime : 2013:09:01 22:53:55\r\n    // ExifIFDPointer : 190\r\n    // ExposureTime : 0.058823529411764705 [1/17]\r\n    // FNumber : 2.4 [12/5]\r\n    // ExposureProgram : Normal program\r\n    // ISOSpeedRatings : 800\r\n    // ExifVersion : 0220\r\n    // DateTimeOriginal : 2013:09:01 22:52:51\r\n    // DateTimeDigitized : 2013:09:01 22:52:51\r\n    // ComponentsConfiguration : YCbCr\r\n    // ShutterSpeedValue : 4.058893515764426\r\n    // ApertureValue : 2.5260688216892597 [4845/1918]\r\n    // BrightnessValue : -0.3126686601998395\r\n    // MeteringMode : Pattern\r\n    // Flash : Flash did not fire, compulsory flash mode\r\n    // FocalLength : 4.28 [107/25]\r\n    // SubjectArea : [4 values]\r\n    // FlashpixVersion : 0100\r\n    // ColorSpace : 1\r\n    // PixelXDimension : 2448\r\n    // PixelYDimension : 3264\r\n    // SensingMethod : One-chip color area sensor\r\n    // ExposureMode : 0\r\n    // WhiteBalance : Auto white balance\r\n    // FocalLengthIn35mmFilm : 35\r\n    // SceneCaptureType : Standard\r\n    define('runtime/html5/imagemeta/exif',[\r\n        'base',\r\n        'runtime/html5/imagemeta'\r\n    ], function( Base, ImageMeta ) {\r\n    \r\n        var EXIF = {};\r\n    \r\n        EXIF.ExifMap = function() {\r\n            return this;\r\n        };\r\n    \r\n        EXIF.ExifMap.prototype.map = {\r\n            'Orientation': 0x0112\r\n        };\r\n    \r\n        EXIF.ExifMap.prototype.get = function( id ) {\r\n            return this[ id ] || this[ this.map[ id ] ];\r\n        };\r\n    \r\n        EXIF.exifTagTypes = {\r\n            // byte, 8-bit unsigned int:\r\n            1: {\r\n                getValue: function( dataView, dataOffset ) {\r\n                    return dataView.getUint8( dataOffset );\r\n                },\r\n                size: 1\r\n            },\r\n    \r\n            // ascii, 8-bit byte:\r\n            2: {\r\n                getValue: function( dataView, dataOffset ) {\r\n                    return String.fromCharCode( dataView.getUint8( dataOffset ) );\r\n                },\r\n                size: 1,\r\n                ascii: true\r\n            },\r\n    \r\n            // short, 16 bit int:\r\n            3: {\r\n                getValue: function( dataView, dataOffset, littleEndian ) {\r\n                    return dataView.getUint16( dataOffset, littleEndian );\r\n                },\r\n                size: 2\r\n            },\r\n    \r\n            // long, 32 bit int:\r\n            4: {\r\n                getValue: function( dataView, dataOffset, littleEndian ) {\r\n                    return dataView.getUint32( dataOffset, littleEndian );\r\n                },\r\n                size: 4\r\n            },\r\n    \r\n            // rational = two long values,\r\n            // first is numerator, second is denominator:\r\n            5: {\r\n                getValue: function( dataView, dataOffset, littleEndian ) {\r\n                    return dataView.getUint32( dataOffset, littleEndian ) /\r\n                        dataView.getUint32( dataOffset + 4, littleEndian );\r\n                },\r\n                size: 8\r\n            },\r\n    \r\n            // slong, 32 bit signed int:\r\n            9: {\r\n                getValue: function( dataView, dataOffset, littleEndian ) {\r\n                    return dataView.getInt32( dataOffset, littleEndian );\r\n                },\r\n                size: 4\r\n            },\r\n    \r\n            // srational, two slongs, first is numerator, second is denominator:\r\n            10: {\r\n                getValue: function( dataView, dataOffset, littleEndian ) {\r\n                    return dataView.getInt32( dataOffset, littleEndian ) /\r\n                        dataView.getInt32( dataOffset + 4, littleEndian );\r\n                },\r\n                size: 8\r\n            }\r\n        };\r\n    \r\n        // undefined, 8-bit byte, value depending on field:\r\n        EXIF.exifTagTypes[ 7 ] = EXIF.exifTagTypes[ 1 ];\r\n    \r\n        EXIF.getExifValue = function( dataView, tiffOffset, offset, type, length,\r\n                littleEndian ) {\r\n    \r\n            var tagType = EXIF.exifTagTypes[ type ],\r\n                tagSize, dataOffset, values, i, str, c;\r\n    \r\n            if ( !tagType ) {\r\n                Base.log('Invalid Exif data: Invalid tag type.');\r\n                return;\r\n            }\r\n    \r\n            tagSize = tagType.size * length;\r\n    \r\n            // Determine if the value is contained in the dataOffset bytes,\r\n            // or if the value at the dataOffset is a pointer to the actual data:\r\n            dataOffset = tagSize > 4 ? tiffOffset + dataView.getUint32( offset + 8,\r\n                    littleEndian ) : (offset + 8);\r\n    \r\n            if ( dataOffset + tagSize > dataView.byteLength ) {\r\n                Base.log('Invalid Exif data: Invalid data offset.');\r\n                return;\r\n            }\r\n    \r\n            if ( length === 1 ) {\r\n                return tagType.getValue( dataView, dataOffset, littleEndian );\r\n            }\r\n    \r\n            values = [];\r\n    \r\n            for ( i = 0; i < length; i += 1 ) {\r\n                values[ i ] = tagType.getValue( dataView,\r\n                        dataOffset + i * tagType.size, littleEndian );\r\n            }\r\n    \r\n            if ( tagType.ascii ) {\r\n                str = '';\r\n    \r\n                // Concatenate the chars:\r\n                for ( i = 0; i < values.length; i += 1 ) {\r\n                    c = values[ i ];\r\n    \r\n                    // Ignore the terminating NULL byte(s):\r\n                    if ( c === '\\u0000' ) {\r\n                        break;\r\n                    }\r\n                    str += c;\r\n                }\r\n    \r\n                return str;\r\n            }\r\n            return values;\r\n        };\r\n    \r\n        EXIF.parseExifTag = function( dataView, tiffOffset, offset, littleEndian,\r\n                data ) {\r\n    \r\n            var tag = dataView.getUint16( offset, littleEndian );\r\n            data.exif[ tag ] = EXIF.getExifValue( dataView, tiffOffset, offset,\r\n                    dataView.getUint16( offset + 2, littleEndian ),    // tag type\r\n                    dataView.getUint32( offset + 4, littleEndian ),    // tag length\r\n                    littleEndian );\r\n        };\r\n    \r\n        EXIF.parseExifTags = function( dataView, tiffOffset, dirOffset,\r\n                littleEndian, data ) {\r\n    \r\n            var tagsNumber, dirEndOffset, i;\r\n    \r\n            if ( dirOffset + 6 > dataView.byteLength ) {\r\n                Base.log('Invalid Exif data: Invalid directory offset.');\r\n                return;\r\n            }\r\n    \r\n            tagsNumber = dataView.getUint16( dirOffset, littleEndian );\r\n            dirEndOffset = dirOffset + 2 + 12 * tagsNumber;\r\n    \r\n            if ( dirEndOffset + 4 > dataView.byteLength ) {\r\n                Base.log('Invalid Exif data: Invalid directory size.');\r\n                return;\r\n            }\r\n    \r\n            for ( i = 0; i < tagsNumber; i += 1 ) {\r\n                this.parseExifTag( dataView, tiffOffset,\r\n                        dirOffset + 2 + 12 * i,    // tag offset\r\n                        littleEndian, data );\r\n            }\r\n    \r\n            // Return the offset to the next directory:\r\n            return dataView.getUint32( dirEndOffset, littleEndian );\r\n        };\r\n    \r\n        // EXIF.getExifThumbnail = function(dataView, offset, length) {\r\n        //     var hexData,\r\n        //         i,\r\n        //         b;\r\n        //     if (!length || offset + length > dataView.byteLength) {\r\n        //         Base.log('Invalid Exif data: Invalid thumbnail data.');\r\n        //         return;\r\n        //     }\r\n        //     hexData = [];\r\n        //     for (i = 0; i < length; i += 1) {\r\n        //         b = dataView.getUint8(offset + i);\r\n        //         hexData.push((b < 16 ? '0' : '') + b.toString(16));\r\n        //     }\r\n        //     return 'data:image/jpeg,%' + hexData.join('%');\r\n        // };\r\n    \r\n        EXIF.parseExifData = function( dataView, offset, length, data ) {\r\n    \r\n            var tiffOffset = offset + 10,\r\n                littleEndian, dirOffset;\r\n    \r\n            // Check for the ASCII code for \"Exif\" (0x45786966):\r\n            if ( dataView.getUint32( offset + 4 ) !== 0x45786966 ) {\r\n                // No Exif data, might be XMP data instead\r\n                return;\r\n            }\r\n            if ( tiffOffset + 8 > dataView.byteLength ) {\r\n                Base.log('Invalid Exif data: Invalid segment size.');\r\n                return;\r\n            }\r\n    \r\n            // Check for the two null bytes:\r\n            if ( dataView.getUint16( offset + 8 ) !== 0x0000 ) {\r\n                Base.log('Invalid Exif data: Missing byte alignment offset.');\r\n                return;\r\n            }\r\n    \r\n            // Check the byte alignment:\r\n            switch ( dataView.getUint16( tiffOffset ) ) {\r\n                case 0x4949:\r\n                    littleEndian = true;\r\n                    break;\r\n    \r\n                case 0x4D4D:\r\n                    littleEndian = false;\r\n                    break;\r\n    \r\n                default:\r\n                    Base.log('Invalid Exif data: Invalid byte alignment marker.');\r\n                    return;\r\n            }\r\n    \r\n            // Check for the TIFF tag marker (0x002A):\r\n            if ( dataView.getUint16( tiffOffset + 2, littleEndian ) !== 0x002A ) {\r\n                Base.log('Invalid Exif data: Missing TIFF marker.');\r\n                return;\r\n            }\r\n    \r\n            // Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal:\r\n            dirOffset = dataView.getUint32( tiffOffset + 4, littleEndian );\r\n            // Create the exif object to store the tags:\r\n            data.exif = new EXIF.ExifMap();\r\n            // Parse the tags of the main image directory and retrieve the\r\n            // offset to the next directory, usually the thumbnail directory:\r\n            dirOffset = EXIF.parseExifTags( dataView, tiffOffset,\r\n                    tiffOffset + dirOffset, littleEndian, data );\r\n    \r\n            // 尝试读取缩略图\r\n            // if ( dirOffset ) {\r\n            //     thumbnailData = {exif: {}};\r\n            //     dirOffset = EXIF.parseExifTags(\r\n            //         dataView,\r\n            //         tiffOffset,\r\n            //         tiffOffset + dirOffset,\r\n            //         littleEndian,\r\n            //         thumbnailData\r\n            //     );\r\n    \r\n            //     // Check for JPEG Thumbnail offset:\r\n            //     if (thumbnailData.exif[0x0201]) {\r\n            //         data.exif.Thumbnail = EXIF.getExifThumbnail(\r\n            //             dataView,\r\n            //             tiffOffset + thumbnailData.exif[0x0201],\r\n            //             thumbnailData.exif[0x0202] // Thumbnail data length\r\n            //         );\r\n            //     }\r\n            // }\r\n        };\r\n    \r\n        ImageMeta.parsers[ 0xffe1 ].push( EXIF.parseExifData );\r\n        return EXIF;\r\n    });\r\n    /**\r\n     * @fileOverview Image\r\n     */\r\n    define('runtime/html5/image',[\r\n        'base',\r\n        'runtime/html5/runtime',\r\n        'runtime/html5/util'\r\n    ], function( Base, Html5Runtime, Util ) {\r\n    \r\n        var BLANK = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D';\r\n    \r\n        return Html5Runtime.register( 'Image', {\r\n    \r\n            // flag: 标记是否被修改过。\r\n            modified: false,\r\n    \r\n            init: function() {\r\n                var me = this,\r\n                    img = new Image();\r\n    \r\n                img.onload = function() {\r\n    \r\n                    me._info = {\r\n                        type: me.type,\r\n                        width: this.width,\r\n                        height: this.height\r\n                    };\r\n    \r\n                    // 读取meta信息。\r\n                    if ( !me._metas && 'image/jpeg' === me.type ) {\r\n                        Util.parseMeta( me._blob, function( error, ret ) {\r\n                            me._metas = ret;\r\n                            me.owner.trigger('load');\r\n                        });\r\n                    } else {\r\n                        me.owner.trigger('load');\r\n                    }\r\n                };\r\n    \r\n                img.onerror = function() {\r\n                    me.owner.trigger('error');\r\n                };\r\n    \r\n                me._img = img;\r\n            },\r\n    \r\n            loadFromBlob: function( blob ) {\r\n                var me = this,\r\n                    img = me._img;\r\n    \r\n                me._blob = blob;\r\n                me.type = blob.type;\r\n                img.src = Util.createObjectURL( blob.getSource() );\r\n                me.owner.once( 'load', function() {\r\n                    Util.revokeObjectURL( img.src );\r\n                });\r\n            },\r\n    \r\n            resize: function( width, height ) {\r\n                var canvas = this._canvas ||\r\n                        (this._canvas = document.createElement('canvas'));\r\n    \r\n                this._resize( this._img, canvas, width, height );\r\n                this._blob = null;    // 没用了，可以删掉了。\r\n                this.modified = true;\r\n                this.owner.trigger('complete');\r\n            },\r\n    \r\n            getAsBlob: function( type ) {\r\n                var blob = this._blob,\r\n                    opts = this.options,\r\n                    canvas;\r\n    \r\n                type = type || this.type;\r\n    \r\n                // blob需要重新生成。\r\n                if ( this.modified || this.type !== type ) {\r\n                    canvas = this._canvas;\r\n    \r\n                    if ( type === 'image/jpeg' ) {\r\n    \r\n                        blob = Util.canvasToDataUrl( canvas, 'image/jpeg',\r\n                                opts.quality );\r\n    \r\n                        if ( opts.preserveHeaders && this._metas &&\r\n                                this._metas.imageHead ) {\r\n    \r\n                            blob = Util.dataURL2ArrayBuffer( blob );\r\n                            blob = Util.updateImageHead( blob,\r\n                                    this._metas.imageHead );\r\n                            blob = Util.arrayBufferToBlob( blob, type );\r\n                            return blob;\r\n                        }\r\n                    } else {\r\n                        blob = Util.canvasToDataUrl( canvas, type );\r\n                    }\r\n    \r\n                    blob = Util.dataURL2Blob( blob );\r\n                }\r\n    \r\n                return blob;\r\n            },\r\n    \r\n            getAsDataUrl: function( type ) {\r\n                var opts = this.options;\r\n    \r\n                type = type || this.type;\r\n    \r\n                if ( type === 'image/jpeg' ) {\r\n                    return Util.canvasToDataUrl( this._canvas, type, opts.quality );\r\n                } else {\r\n                    return this._canvas.toDataURL( type );\r\n                }\r\n            },\r\n    \r\n            getOrientation: function() {\r\n                return this._metas && this._metas.exif &&\r\n                        this._metas.exif.get('Orientation') || 1;\r\n            },\r\n    \r\n            info: function( val ) {\r\n    \r\n                // setter\r\n                if ( val ) {\r\n                    this._info = val;\r\n                    return this;\r\n                }\r\n    \r\n                // getter\r\n                return this._info;\r\n            },\r\n    \r\n            meta: function( val ) {\r\n    \r\n                // setter\r\n                if ( val ) {\r\n                    this._meta = val;\r\n                    return this;\r\n                }\r\n    \r\n                // getter\r\n                return this._meta;\r\n            },\r\n    \r\n            destroy: function() {\r\n                var canvas = this._canvas;\r\n                this._img.onload = null;\r\n    \r\n                if ( canvas ) {\r\n                    canvas.getContext('2d')\r\n                            .clearRect( 0, 0, canvas.width, canvas.height );\r\n                    canvas.width = canvas.height = 0;\r\n                    this._canvas = null;\r\n                }\r\n    \r\n                // 释放内存。非常重要，否则释放不了image的内存。\r\n                this._img.src = BLANK;\r\n                this._img = this._blob = null;\r\n            },\r\n    \r\n            _resize: function( img, cvs, width, height ) {\r\n                var opts = this.options,\r\n                    naturalWidth = img.width,\r\n                    naturalHeight = img.height,\r\n                    orientation = this.getOrientation(),\r\n                    scale, w, h, x, y;\r\n    \r\n                // values that require 90 degree rotation\r\n                if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) {\r\n    \r\n                    // 交换width, height的值。\r\n                    width ^= height;\r\n                    height ^= width;\r\n                    width ^= height;\r\n                }\r\n    \r\n                scale = Math[ opts.crop ? 'max' : 'min' ]( width / naturalWidth,\r\n                        height / naturalHeight );\r\n    \r\n                // 不允许放大。\r\n                opts.allowMagnify || (scale = Math.min( 1, scale ));\r\n    \r\n                w = naturalWidth * scale;\r\n                h = naturalHeight * scale;\r\n    \r\n                if ( opts.crop ) {\r\n                    cvs.width = width;\r\n                    cvs.height = height;\r\n                } else {\r\n                    cvs.width = w;\r\n                    cvs.height = h;\r\n                }\r\n    \r\n                x = (cvs.width - w) / 2;\r\n                y = (cvs.height - h) / 2;\r\n    \r\n                opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation );\r\n    \r\n                this._renderImageToCanvas( cvs, img, x, y, w, h );\r\n            },\r\n    \r\n            _rotate2Orientaion: function( canvas, orientation ) {\r\n                var width = canvas.width,\r\n                    height = canvas.height,\r\n                    ctx = canvas.getContext('2d');\r\n    \r\n                switch ( orientation ) {\r\n                    case 5:\r\n                    case 6:\r\n                    case 7:\r\n                    case 8:\r\n                        canvas.width = height;\r\n                        canvas.height = width;\r\n                        break;\r\n                }\r\n    \r\n                switch ( orientation ) {\r\n                    case 2:    // horizontal flip\r\n                        ctx.translate( width, 0 );\r\n                        ctx.scale( -1, 1 );\r\n                        break;\r\n    \r\n                    case 3:    // 180 rotate left\r\n                        ctx.translate( width, height );\r\n                        ctx.rotate( Math.PI );\r\n                        break;\r\n    \r\n                    case 4:    // vertical flip\r\n                        ctx.translate( 0, height );\r\n                        ctx.scale( 1, -1 );\r\n                        break;\r\n    \r\n                    case 5:    // vertical flip + 90 rotate right\r\n                        ctx.rotate( 0.5 * Math.PI );\r\n                        ctx.scale( 1, -1 );\r\n                        break;\r\n    \r\n                    case 6:    // 90 rotate right\r\n                        ctx.rotate( 0.5 * Math.PI );\r\n                        ctx.translate( 0, -height );\r\n                        break;\r\n    \r\n                    case 7:    // horizontal flip + 90 rotate right\r\n                        ctx.rotate( 0.5 * Math.PI );\r\n                        ctx.translate( width, -height );\r\n                        ctx.scale( -1, 1 );\r\n                        break;\r\n    \r\n                    case 8:    // 90 rotate left\r\n                        ctx.rotate( -0.5 * Math.PI );\r\n                        ctx.translate( -width, 0 );\r\n                        break;\r\n                }\r\n            },\r\n    \r\n            // https://github.com/stomita/ios-imagefile-megapixel/\r\n            // blob/master/src/megapix-image.js\r\n            _renderImageToCanvas: (function() {\r\n    \r\n                // 如果不是ios, 不需要这么复杂！\r\n                if ( !Base.os.ios ) {\r\n                    return function( canvas, img, x, y, w, h ) {\r\n                        canvas.getContext('2d').drawImage( img, x, y, w, h );\r\n                    };\r\n                }\r\n    \r\n                /**\r\n                 * Detecting vertical squash in loaded image.\r\n                 * Fixes a bug which squash image vertically while drawing into\r\n                 * canvas for some images.\r\n                 */\r\n                function detectVerticalSquash( img, iw, ih ) {\r\n                    var canvas = document.createElement('canvas'),\r\n                        ctx = canvas.getContext('2d'),\r\n                        sy = 0,\r\n                        ey = ih,\r\n                        py = ih,\r\n                        data, alpha, ratio;\r\n    \r\n    \r\n                    canvas.width = 1;\r\n                    canvas.height = ih;\r\n                    ctx.drawImage( img, 0, 0 );\r\n                    data = ctx.getImageData( 0, 0, 1, ih ).data;\r\n    \r\n                    // search image edge pixel position in case\r\n                    // it is squashed vertically.\r\n                    while ( py > sy ) {\r\n                        alpha = data[ (py - 1) * 4 + 3 ];\r\n    \r\n                        if ( alpha === 0 ) {\r\n                            ey = py;\r\n                        } else {\r\n                            sy = py;\r\n                        }\r\n    \r\n                        py = (ey + sy) >> 1;\r\n                    }\r\n    \r\n                    ratio = (py / ih);\r\n                    return (ratio === 0) ? 1 : ratio;\r\n                }\r\n    \r\n                // fix ie7 bug\r\n                // http://stackoverflow.com/questions/11929099/\r\n                // html5-canvas-drawimage-ratio-bug-ios\r\n                if ( Base.os.ios >= 7 ) {\r\n                    return function( canvas, img, x, y, w, h ) {\r\n                        var iw = img.naturalWidth,\r\n                            ih = img.naturalHeight,\r\n                            vertSquashRatio = detectVerticalSquash( img, iw, ih );\r\n    \r\n                        return canvas.getContext('2d').drawImage( img, 0, 0,\r\n                            iw * vertSquashRatio, ih * vertSquashRatio,\r\n                            x, y, w, h );\r\n                    };\r\n                }\r\n    \r\n                /**\r\n                 * Detect subsampling in loaded image.\r\n                 * In iOS, larger images than 2M pixels may be\r\n                 * subsampled in rendering.\r\n                 */\r\n                function detectSubsampling( img ) {\r\n                    var iw = img.naturalWidth,\r\n                        ih = img.naturalHeight,\r\n                        canvas, ctx;\r\n    \r\n                    // subsampling may happen overmegapixel image\r\n                    if ( iw * ih > 1024 * 1024 ) {\r\n                        canvas = document.createElement('canvas');\r\n                        canvas.width = canvas.height = 1;\r\n                        ctx = canvas.getContext('2d');\r\n                        ctx.drawImage( img, -iw + 1, 0 );\r\n    \r\n                        // subsampled image becomes half smaller in rendering size.\r\n                        // check alpha channel value to confirm image is covering\r\n                        // edge pixel or not. if alpha value is 0\r\n                        // image is not covering, hence subsampled.\r\n                        return ctx.getImageData( 0, 0, 1, 1 ).data[ 3 ] === 0;\r\n                    } else {\r\n                        return false;\r\n                    }\r\n                }\r\n    \r\n    \r\n                return function( canvas, img, x, y, width, height ) {\r\n                    var iw = img.naturalWidth,\r\n                        ih = img.naturalHeight,\r\n                        ctx = canvas.getContext('2d'),\r\n                        subsampled = detectSubsampling( img ),\r\n                        doSquash = this.type === 'image/jpeg',\r\n                        d = 1024,\r\n                        sy = 0,\r\n                        dy = 0,\r\n                        tmpCanvas, tmpCtx, vertSquashRatio, dw, dh, sx, dx;\r\n    \r\n                    if ( subsampled ) {\r\n                        iw /= 2;\r\n                        ih /= 2;\r\n                    }\r\n    \r\n                    ctx.save();\r\n                    tmpCanvas = document.createElement('canvas');\r\n                    tmpCanvas.width = tmpCanvas.height = d;\r\n    \r\n                    tmpCtx = tmpCanvas.getContext('2d');\r\n                    vertSquashRatio = doSquash ?\r\n                            detectVerticalSquash( img, iw, ih ) : 1;\r\n    \r\n                    dw = Math.ceil( d * width / iw );\r\n                    dh = Math.ceil( d * height / ih / vertSquashRatio );\r\n    \r\n                    while ( sy < ih ) {\r\n                        sx = 0;\r\n                        dx = 0;\r\n                        while ( sx < iw ) {\r\n                            tmpCtx.clearRect( 0, 0, d, d );\r\n                            tmpCtx.drawImage( img, -sx, -sy );\r\n                            ctx.drawImage( tmpCanvas, 0, 0, d, d,\r\n                                    x + dx, y + dy, dw, dh );\r\n                            sx += d;\r\n                            dx += dw;\r\n                        }\r\n                        sy += d;\r\n                        dy += dh;\r\n                    }\r\n                    ctx.restore();\r\n                    tmpCanvas = tmpCtx = null;\r\n                };\r\n            })()\r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview Transport\r\n     * @todo 支持chunked传输，优势：\r\n     * 可以将大文件分成小块，挨个传输，可以提高大文件成功率，当失败的时候，也只需要重传那小部分，\r\n     * 而不需要重头再传一次。另外断点续传也需要用chunked方式。\r\n     */\r\n    define('runtime/html5/transport',[\r\n        'base',\r\n        'runtime/html5/runtime'\r\n    ], function( Base, Html5Runtime ) {\r\n    \r\n        var noop = Base.noop,\r\n            $ = Base.$;\r\n    \r\n        return Html5Runtime.register( 'Transport', {\r\n            init: function() {\r\n                this._status = 0;\r\n                this._response = null;\r\n            },\r\n    \r\n            send: function() {\r\n                var owner = this.owner,\r\n                    opts = this.options,\r\n                    xhr = this._initAjax(),\r\n                    blob = owner._blob,\r\n                    server = opts.server,\r\n                    formData, binary, fr;\r\n    \r\n                if ( opts.sendAsBinary ) {\r\n                    server += (/\\?/.test( server ) ? '&' : '?') +\r\n                            $.param( owner._formData );\r\n    \r\n                    binary = blob.getSource();\r\n                } else {\r\n                    formData = new FormData();\r\n                    $.each( owner._formData, function( k, v ) {\r\n                        formData.append( k, v );\r\n                    });\r\n    \r\n                    formData.append( opts.fileVal, blob.getSource(),\r\n                            opts.filename || owner._formData.name || '' );\r\n                }\r\n    \r\n                if ( opts.withCredentials && 'withCredentials' in xhr ) {\r\n                    xhr.open( opts.method, server, true );\r\n                    xhr.withCredentials = true;\r\n                } else {\r\n                    xhr.open( opts.method, server );\r\n                }\r\n    \r\n                this._setRequestHeader( xhr, opts.headers );\r\n    \r\n                if ( binary ) {\r\n                    xhr.overrideMimeType('application/octet-stream');\r\n    \r\n                    // android直接发送blob会导致服务端接收到的是空文件。\r\n                    // bug详情。\r\n                    // https://code.google.com/p/android/issues/detail?id=39882\r\n                    // 所以先用fileReader读取出来再通过arraybuffer的方式发送。\r\n                    if ( Base.os.android ) {\r\n                        fr = new FileReader();\r\n    \r\n                        fr.onload = function() {\r\n                            xhr.send( this.result );\r\n                            fr = fr.onload = null;\r\n                        };\r\n    \r\n                        fr.readAsArrayBuffer( binary );\r\n                    } else {\r\n                        xhr.send( binary );\r\n                    }\r\n                } else {\r\n                    xhr.send( formData );\r\n                }\r\n            },\r\n    \r\n            getResponse: function() {\r\n                return this._response;\r\n            },\r\n    \r\n            getResponseAsJson: function() {\r\n                return this._parseJson( this._response );\r\n            },\r\n    \r\n            getStatus: function() {\r\n                return this._status;\r\n            },\r\n    \r\n            abort: function() {\r\n                var xhr = this._xhr;\r\n    \r\n                if ( xhr ) {\r\n                    xhr.upload.onprogress = noop;\r\n                    xhr.onreadystatechange = noop;\r\n                    xhr.abort();\r\n    \r\n                    this._xhr = xhr = null;\r\n                }\r\n            },\r\n    \r\n            destroy: function() {\r\n                this.abort();\r\n            },\r\n    \r\n            _initAjax: function() {\r\n                var me = this,\r\n                    xhr = new XMLHttpRequest(),\r\n                    opts = this.options;\r\n    \r\n                if ( opts.withCredentials && !('withCredentials' in xhr) &&\r\n                        typeof XDomainRequest !== 'undefined' ) {\r\n                    xhr = new XDomainRequest();\r\n                }\r\n    \r\n                xhr.upload.onprogress = function( e ) {\r\n                    var percentage = 0;\r\n    \r\n                    if ( e.lengthComputable ) {\r\n                        percentage = e.loaded / e.total;\r\n                    }\r\n    \r\n                    return me.trigger( 'progress', percentage );\r\n                };\r\n    \r\n                xhr.onreadystatechange = function() {\r\n    \r\n                    if ( xhr.readyState !== 4 ) {\r\n                        return;\r\n                    }\r\n    \r\n                    xhr.upload.onprogress = noop;\r\n                    xhr.onreadystatechange = noop;\r\n                    me._xhr = null;\r\n                    me._status = xhr.status;\r\n    \r\n                    if ( xhr.status >= 200 && xhr.status < 300 ) {\r\n                        me._response = xhr.responseText;\r\n                        return me.trigger('load');\r\n                    } else if ( xhr.status >= 500 && xhr.status < 600 ) {\r\n                        me._response = xhr.responseText;\r\n                        return me.trigger( 'error', 'server' );\r\n                    }\r\n    \r\n    \r\n                    return me.trigger( 'error', me._status ? 'http' : 'abort' );\r\n                };\r\n    \r\n                me._xhr = xhr;\r\n                return xhr;\r\n            },\r\n    \r\n            _setRequestHeader: function( xhr, headers ) {\r\n                $.each( headers, function( key, val ) {\r\n                    xhr.setRequestHeader( key, val );\r\n                });\r\n            },\r\n    \r\n            _parseJson: function( str ) {\r\n                var json;\r\n    \r\n                try {\r\n                    json = JSON.parse( str );\r\n                } catch ( ex ) {\r\n                    json = {};\r\n                }\r\n    \r\n                return json;\r\n            }\r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview 只有html5实现的文件版本。\r\n     */\r\n    define('preset/html5only',[\r\n        'base',\r\n    \r\n        // widgets\r\n        'widgets/filednd',\r\n        'widgets/filepaste',\r\n        'widgets/filepicker',\r\n        'widgets/image',\r\n        'widgets/queue',\r\n        'widgets/runtime',\r\n        'widgets/upload',\r\n        'widgets/validator',\r\n    \r\n        // runtimes\r\n        // html5\r\n        'runtime/html5/blob',\r\n        'runtime/html5/dnd',\r\n        'runtime/html5/filepaste',\r\n        'runtime/html5/filepicker',\r\n        'runtime/html5/imagemeta/exif',\r\n        'runtime/html5/image',\r\n        'runtime/html5/transport'\r\n    ], function( Base ) {\r\n        return Base;\r\n    });\r\n    define('webuploader',[\r\n        'preset/html5only'\r\n    ], function( preset ) {\r\n        return preset;\r\n    });\r\n    return require('webuploader');\r\n});\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/webuploader/webuploader.js",
    "content": "/*! WebUploader 0.1.2 */\r\n\r\n\r\n/**\r\n * @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。\r\n *\r\n * AMD API 内部的简单不完全实现，请忽略。只有当WebUploader被合并成一个文件的时候才会引入。\r\n */\r\n(function( root, factory ) {\r\n    var modules = {},\r\n\r\n        // 内部require, 简单不完全实现。\r\n        // https://github.com/amdjs/amdjs-api/wiki/require\r\n        _require = function( deps, callback ) {\r\n            var args, len, i;\r\n\r\n            // 如果deps不是数组，则直接返回指定module\r\n            if ( typeof deps === 'string' ) {\r\n                return getModule( deps );\r\n            } else {\r\n                args = [];\r\n                for( len = deps.length, i = 0; i < len; i++ ) {\r\n                    args.push( getModule( deps[ i ] ) );\r\n                }\r\n\r\n                return callback.apply( null, args );\r\n            }\r\n        },\r\n\r\n        // 内部define，暂时不支持不指定id.\r\n        _define = function( id, deps, factory ) {\r\n            if ( arguments.length === 2 ) {\r\n                factory = deps;\r\n                deps = null;\r\n            }\r\n\r\n            _require( deps || [], function() {\r\n                setModule( id, factory, arguments );\r\n            });\r\n        },\r\n\r\n        // 设置module, 兼容CommonJs写法。\r\n        setModule = function( id, factory, args ) {\r\n            var module = {\r\n                    exports: factory\r\n                },\r\n                returned;\r\n\r\n            if ( typeof factory === 'function' ) {\r\n                args.length || (args = [ _require, module.exports, module ]);\r\n                returned = factory.apply( null, args );\r\n                returned !== undefined && (module.exports = returned);\r\n            }\r\n\r\n            modules[ id ] = module.exports;\r\n        },\r\n\r\n        // 根据id获取module\r\n        getModule = function( id ) {\r\n            var module = modules[ id ] || root[ id ];\r\n\r\n            if ( !module ) {\r\n                throw new Error( '`' + id + '` is undefined' );\r\n            }\r\n\r\n            return module;\r\n        },\r\n\r\n        // 将所有modules，将路径ids装换成对象。\r\n        exportsTo = function( obj ) {\r\n            var key, host, parts, part, last, ucFirst;\r\n\r\n            // make the first character upper case.\r\n            ucFirst = function( str ) {\r\n                return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 ));\r\n            };\r\n\r\n            for ( key in modules ) {\r\n                host = obj;\r\n\r\n                if ( !modules.hasOwnProperty( key ) ) {\r\n                    continue;\r\n                }\r\n\r\n                parts = key.split('/');\r\n                last = ucFirst( parts.pop() );\r\n\r\n                while( (part = ucFirst( parts.shift() )) ) {\r\n                    host[ part ] = host[ part ] || {};\r\n                    host = host[ part ];\r\n                }\r\n\r\n                host[ last ] = modules[ key ];\r\n            }\r\n        },\r\n\r\n        exports = factory( root, _define, _require ),\r\n        origin;\r\n\r\n    // exports every module.\r\n    exportsTo( exports );\r\n\r\n    if ( typeof module === 'object' && typeof module.exports === 'object' ) {\r\n\r\n        // For CommonJS and CommonJS-like environments where a proper window is present,\r\n        module.exports = exports;\r\n    } else if ( typeof define === 'function' && define.amd ) {\r\n\r\n        // Allow using this built library as an AMD module\r\n        // in another project. That other project will only\r\n        // see this AMD call, not the internal modules in\r\n        // the closure below.\r\n        define([], exports );\r\n    } else {\r\n\r\n        // Browser globals case. Just assign the\r\n        // result to a property on the global.\r\n        origin = root.WebUploader;\r\n        root.WebUploader = exports;\r\n        root.WebUploader.noConflict = function() {\r\n            root.WebUploader = origin;\r\n        };\r\n    }\r\n})( this, function( window, define, require ) {\r\n\r\n\r\n    /**\r\n     * @fileOverview jQuery or Zepto\r\n     */\r\n    define('dollar-third',[],function() {\r\n        return window.jQuery || window.Zepto;\r\n    });\r\n    /**\r\n     * @fileOverview Dom 操作相关\r\n     */\r\n    define('dollar',[\r\n        'dollar-third'\r\n    ], function( _ ) {\r\n        return _;\r\n    });\r\n    /**\r\n     * @fileOverview 使用jQuery的Promise\r\n     */\r\n    define('promise-third',[\r\n        'dollar'\r\n    ], function( $ ) {\r\n        return {\r\n            Deferred: $.Deferred,\r\n            when: $.when,\r\n    \r\n            isPromise: function( anything ) {\r\n                return anything && typeof anything.then === 'function';\r\n            }\r\n        };\r\n    });\r\n    /**\r\n     * @fileOverview Promise/A+\r\n     */\r\n    define('promise',[\r\n        'promise-third'\r\n    ], function( _ ) {\r\n        return _;\r\n    });\r\n    /**\r\n     * @fileOverview 基础类方法。\r\n     */\r\n    \r\n    /**\r\n     * Web Uploader内部类的详细说明，以下提及的功能类，都可以在`WebUploader`这个变量中访问到。\r\n     *\r\n     * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id.\r\n     * 默认module id该文件的路径，而此路径将会转化成名字空间存放在WebUploader中。如：\r\n     *\r\n     * * module `base`：WebUploader.Base\r\n     * * module `file`: WebUploader.File\r\n     * * module `lib/dnd`: WebUploader.Lib.Dnd\r\n     * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd\r\n     *\r\n     *\r\n     * 以下文档将可能省略`WebUploader`前缀。\r\n     * @module WebUploader\r\n     * @title WebUploader API文档\r\n     */\r\n    define('base',[\r\n        'dollar',\r\n        'promise'\r\n    ], function( $, promise ) {\r\n    \r\n        var noop = function() {},\r\n            call = Function.call;\r\n    \r\n        // http://jsperf.com/uncurrythis\r\n        // 反科里化\r\n        function uncurryThis( fn ) {\r\n            return function() {\r\n                return call.apply( fn, arguments );\r\n            };\r\n        }\r\n    \r\n        function bindFn( fn, context ) {\r\n            return function() {\r\n                return fn.apply( context, arguments );\r\n            };\r\n        }\r\n    \r\n        function createObject( proto ) {\r\n            var f;\r\n    \r\n            if ( Object.create ) {\r\n                return Object.create( proto );\r\n            } else {\r\n                f = function() {};\r\n                f.prototype = proto;\r\n                return new f();\r\n            }\r\n        }\r\n    \r\n    \r\n        /**\r\n         * 基础类，提供一些简单常用的方法。\r\n         * @class Base\r\n         */\r\n        return {\r\n    \r\n            /**\r\n             * @property {String} version 当前版本号。\r\n             */\r\n            version: '0.1.2',\r\n    \r\n            /**\r\n             * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。\r\n             */\r\n            $: $,\r\n    \r\n            Deferred: promise.Deferred,\r\n    \r\n            isPromise: promise.isPromise,\r\n    \r\n            when: promise.when,\r\n    \r\n            /**\r\n             * @description  简单的浏览器检查结果。\r\n             *\r\n             * * `webkit`  webkit版本号，如果浏览器为非webkit内核，此属性为`undefined`。\r\n             * * `chrome`  chrome浏览器版本号，如果浏览器为chrome，此属性为`undefined`。\r\n             * * `ie`  ie浏览器版本号，如果浏览器为非ie，此属性为`undefined`。**暂不支持ie10+**\r\n             * * `firefox`  firefox浏览器版本号，如果浏览器为非firefox，此属性为`undefined`。\r\n             * * `safari`  safari浏览器版本号，如果浏览器为非safari，此属性为`undefined`。\r\n             * * `opera`  opera浏览器版本号，如果浏览器为非opera，此属性为`undefined`。\r\n             *\r\n             * @property {Object} [browser]\r\n             */\r\n            browser: (function( ua ) {\r\n                var ret = {},\r\n                    webkit = ua.match( /WebKit\\/([\\d.]+)/ ),\r\n                    chrome = ua.match( /Chrome\\/([\\d.]+)/ ) ||\r\n                        ua.match( /CriOS\\/([\\d.]+)/ ),\r\n    \r\n                    ie = ua.match( /MSIE\\s([\\d\\.]+)/ ) ||\r\n                        ua.match(/(?:trident)(?:.*rv:([\\w.]+))?/i),\r\n                    firefox = ua.match( /Firefox\\/([\\d.]+)/ ),\r\n                    safari = ua.match( /Safari\\/([\\d.]+)/ ),\r\n                    opera = ua.match( /OPR\\/([\\d.]+)/ );\r\n    \r\n                webkit && (ret.webkit = parseFloat( webkit[ 1 ] ));\r\n                chrome && (ret.chrome = parseFloat( chrome[ 1 ] ));\r\n                ie && (ret.ie = parseFloat( ie[ 1 ] ));\r\n                firefox && (ret.firefox = parseFloat( firefox[ 1 ] ));\r\n                safari && (ret.safari = parseFloat( safari[ 1 ] ));\r\n                opera && (ret.opera = parseFloat( opera[ 1 ] ));\r\n    \r\n                return ret;\r\n            })( navigator.userAgent ),\r\n    \r\n            /**\r\n             * @description  操作系统检查结果。\r\n             *\r\n             * * `android`  如果在android浏览器环境下，此值为对应的android版本号，否则为`undefined`。\r\n             * * `ios` 如果在ios浏览器环境下，此值为对应的ios版本号，否则为`undefined`。\r\n             * @property {Object} [os]\r\n             */\r\n            os: (function( ua ) {\r\n                var ret = {},\r\n    \r\n                    // osx = !!ua.match( /\\(Macintosh\\; Intel / ),\r\n                    android = ua.match( /(?:Android);?[\\s\\/]+([\\d.]+)?/ ),\r\n                    ios = ua.match( /(?:iPad|iPod|iPhone).*OS\\s([\\d_]+)/ );\r\n    \r\n                // osx && (ret.osx = true);\r\n                android && (ret.android = parseFloat( android[ 1 ] ));\r\n                ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) ));\r\n    \r\n                return ret;\r\n            })( navigator.userAgent ),\r\n    \r\n            /**\r\n             * 实现类与类之间的继承。\r\n             * @method inherits\r\n             * @grammar Base.inherits( super ) => child\r\n             * @grammar Base.inherits( super, protos ) => child\r\n             * @grammar Base.inherits( super, protos, statics ) => child\r\n             * @param  {Class} super 父类\r\n             * @param  {Object | Function} [protos] 子类或者对象。如果对象中包含constructor，子类将是用此属性值。\r\n             * @param  {Function} [protos.constructor] 子类构造器，不指定的话将创建个临时的直接执行父类构造器的方法。\r\n             * @param  {Object} [statics] 静态属性或方法。\r\n             * @return {Class} 返回子类。\r\n             * @example\r\n             * function Person() {\r\n             *     console.log( 'Super' );\r\n             * }\r\n             * Person.prototype.hello = function() {\r\n             *     console.log( 'hello' );\r\n             * };\r\n             *\r\n             * var Manager = Base.inherits( Person, {\r\n             *     world: function() {\r\n             *         console.log( 'World' );\r\n             *     }\r\n             * });\r\n             *\r\n             * // 因为没有指定构造器，父类的构造器将会执行。\r\n             * var instance = new Manager();    // => Super\r\n             *\r\n             * // 继承子父类的方法\r\n             * instance.hello();    // => hello\r\n             * instance.world();    // => World\r\n             *\r\n             * // 子类的__super__属性指向父类\r\n             * console.log( Manager.__super__ === Person );    // => true\r\n             */\r\n            inherits: function( Super, protos, staticProtos ) {\r\n                var child;\r\n    \r\n                if ( typeof protos === 'function' ) {\r\n                    child = protos;\r\n                    protos = null;\r\n                } else if ( protos && protos.hasOwnProperty('constructor') ) {\r\n                    child = protos.constructor;\r\n                } else {\r\n                    child = function() {\r\n                        return Super.apply( this, arguments );\r\n                    };\r\n                }\r\n    \r\n                // 复制静态方法\r\n                $.extend( true, child, Super, staticProtos || {} );\r\n    \r\n                /* jshint camelcase: false */\r\n    \r\n                // 让子类的__super__属性指向父类。\r\n                child.__super__ = Super.prototype;\r\n    \r\n                // 构建原型，添加原型方法或属性。\r\n                // 暂时用Object.create实现。\r\n                child.prototype = createObject( Super.prototype );\r\n                protos && $.extend( true, child.prototype, protos );\r\n    \r\n                return child;\r\n            },\r\n    \r\n            /**\r\n             * 一个不做任何事情的方法。可以用来赋值给默认的callback.\r\n             * @method noop\r\n             */\r\n            noop: noop,\r\n    \r\n            /**\r\n             * 返回一个新的方法，此方法将已指定的`context`来执行。\r\n             * @grammar Base.bindFn( fn, context ) => Function\r\n             * @method bindFn\r\n             * @example\r\n             * var doSomething = function() {\r\n             *         console.log( this.name );\r\n             *     },\r\n             *     obj = {\r\n             *         name: 'Object Name'\r\n             *     },\r\n             *     aliasFn = Base.bind( doSomething, obj );\r\n             *\r\n             *  aliasFn();    // => Object Name\r\n             *\r\n             */\r\n            bindFn: bindFn,\r\n    \r\n            /**\r\n             * 引用Console.log如果存在的话，否则引用一个[空函数loop](#WebUploader:Base.log)。\r\n             * @grammar Base.log( args... ) => undefined\r\n             * @method log\r\n             */\r\n            log: (function() {\r\n                if ( window.console ) {\r\n                    return bindFn( console.log, console );\r\n                }\r\n                return noop;\r\n            })(),\r\n    \r\n            nextTick: (function() {\r\n    \r\n                return function( cb ) {\r\n                    setTimeout( cb, 1 );\r\n                };\r\n    \r\n                // @bug 当浏览器不在当前窗口时就停了。\r\n                // var next = window.requestAnimationFrame ||\r\n                //     window.webkitRequestAnimationFrame ||\r\n                //     window.mozRequestAnimationFrame ||\r\n                //     function( cb ) {\r\n                //         window.setTimeout( cb, 1000 / 60 );\r\n                //     };\r\n    \r\n                // // fix: Uncaught TypeError: Illegal invocation\r\n                // return bindFn( next, window );\r\n            })(),\r\n    \r\n            /**\r\n             * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。\r\n             * 将用来将非数组对象转化成数组对象。\r\n             * @grammar Base.slice( target, start[, end] ) => Array\r\n             * @method slice\r\n             * @example\r\n             * function doSomthing() {\r\n             *     var args = Base.slice( arguments, 1 );\r\n             *     console.log( args );\r\n             * }\r\n             *\r\n             * doSomthing( 'ignored', 'arg2', 'arg3' );    // => Array [\"arg2\", \"arg3\"]\r\n             */\r\n            slice: uncurryThis( [].slice ),\r\n    \r\n            /**\r\n             * 生成唯一的ID\r\n             * @method guid\r\n             * @grammar Base.guid() => String\r\n             * @grammar Base.guid( prefx ) => String\r\n             */\r\n            guid: (function() {\r\n                var counter = 0;\r\n    \r\n                return function( prefix ) {\r\n                    var guid = (+new Date()).toString( 32 ),\r\n                        i = 0;\r\n    \r\n                    for ( ; i < 5; i++ ) {\r\n                        guid += Math.floor( Math.random() * 65535 ).toString( 32 );\r\n                    }\r\n    \r\n                    return (prefix || 'wu_') + guid + (counter++).toString( 32 );\r\n                };\r\n            })(),\r\n    \r\n            /**\r\n             * 格式化文件大小, 输出成带单位的字符串\r\n             * @method formatSize\r\n             * @grammar Base.formatSize( size ) => String\r\n             * @grammar Base.formatSize( size, pointLength ) => String\r\n             * @grammar Base.formatSize( size, pointLength, units ) => String\r\n             * @param {Number} size 文件大小\r\n             * @param {Number} [pointLength=2] 精确到的小数点数。\r\n             * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节，到千字节，一直往上指定。如果单位数组里面只指定了到了K(千字节)，同时文件大小大于M, 此方法的输出将还是显示成多少K.\r\n             * @example\r\n             * console.log( Base.formatSize( 100 ) );    // => 100B\r\n             * console.log( Base.formatSize( 1024 ) );    // => 1.00K\r\n             * console.log( Base.formatSize( 1024, 0 ) );    // => 1K\r\n             * console.log( Base.formatSize( 1024 * 1024 ) );    // => 1.00M\r\n             * console.log( Base.formatSize( 1024 * 1024 * 1024 ) );    // => 1.00G\r\n             * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) );    // => 1024MB\r\n             */\r\n            formatSize: function( size, pointLength, units ) {\r\n                var unit;\r\n    \r\n                units = units || [ 'B', 'K', 'M', 'G', 'TB' ];\r\n    \r\n                while ( (unit = units.shift()) && size > 1024 ) {\r\n                    size = size / 1024;\r\n                }\r\n    \r\n                return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) +\r\n                        unit;\r\n            }\r\n        };\r\n    });\r\n    /**\r\n     * 事件处理类，可以独立使用，也可以扩展给对象使用。\r\n     * @fileOverview Mediator\r\n     */\r\n    define('mediator',[\r\n        'base'\r\n    ], function( Base ) {\r\n        var $ = Base.$,\r\n            slice = [].slice,\r\n            separator = /\\s+/,\r\n            protos;\r\n    \r\n        // 根据条件过滤出事件handlers.\r\n        function findHandlers( arr, name, callback, context ) {\r\n            return $.grep( arr, function( handler ) {\r\n                return handler &&\r\n                        (!name || handler.e === name) &&\r\n                        (!callback || handler.cb === callback ||\r\n                        handler.cb._cb === callback) &&\r\n                        (!context || handler.ctx === context);\r\n            });\r\n        }\r\n    \r\n        function eachEvent( events, callback, iterator ) {\r\n            // 不支持对象，只支持多个event用空格隔开\r\n            $.each( (events || '').split( separator ), function( _, key ) {\r\n                iterator( key, callback );\r\n            });\r\n        }\r\n    \r\n        function triggerHanders( events, args ) {\r\n            var stoped = false,\r\n                i = -1,\r\n                len = events.length,\r\n                handler;\r\n    \r\n            while ( ++i < len ) {\r\n                handler = events[ i ];\r\n    \r\n                if ( handler.cb.apply( handler.ctx2, args ) === false ) {\r\n                    stoped = true;\r\n                    break;\r\n                }\r\n            }\r\n    \r\n            return !stoped;\r\n        }\r\n    \r\n        protos = {\r\n    \r\n            /**\r\n             * 绑定事件。\r\n             *\r\n             * `callback`方法在执行时，arguments将会来源于trigger的时候携带的参数。如\r\n             * ```javascript\r\n             * var obj = {};\r\n             *\r\n             * // 使得obj有事件行为\r\n             * Mediator.installTo( obj );\r\n             *\r\n             * obj.on( 'testa', function( arg1, arg2 ) {\r\n             *     console.log( arg1, arg2 ); // => 'arg1', 'arg2'\r\n             * });\r\n             *\r\n             * obj.trigger( 'testa', 'arg1', 'arg2' );\r\n             * ```\r\n             *\r\n             * 如果`callback`中，某一个方法`return false`了，则后续的其他`callback`都不会被执行到。\r\n             * 切会影响到`trigger`方法的返回值，为`false`。\r\n             *\r\n             * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处，\r\n             * 就是第一个参数为`type`，记录当前是什么事件在触发。此类`callback`的优先级比脚低，会再正常`callback`执行完后触发。\r\n             * ```javascript\r\n             * obj.on( 'all', function( type, arg1, arg2 ) {\r\n             *     console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2'\r\n             * });\r\n             * ```\r\n             *\r\n             * @method on\r\n             * @grammar on( name, callback[, context] ) => self\r\n             * @param  {String}   name     事件名，支持多个事件用空格隔开\r\n             * @param  {Function} callback 事件处理器\r\n             * @param  {Object}   [context]  事件处理器的上下文。\r\n             * @return {self} 返回自身，方便链式\r\n             * @chainable\r\n             * @class Mediator\r\n             */\r\n            on: function( name, callback, context ) {\r\n                var me = this,\r\n                    set;\r\n    \r\n                if ( !callback ) {\r\n                    return this;\r\n                }\r\n    \r\n                set = this._events || (this._events = []);\r\n    \r\n                eachEvent( name, callback, function( name, callback ) {\r\n                    var handler = { e: name };\r\n    \r\n                    handler.cb = callback;\r\n                    handler.ctx = context;\r\n                    handler.ctx2 = context || me;\r\n                    handler.id = set.length;\r\n    \r\n                    set.push( handler );\r\n                });\r\n    \r\n                return this;\r\n            },\r\n    \r\n            /**\r\n             * 绑定事件，且当handler执行完后，自动解除绑定。\r\n             * @method once\r\n             * @grammar once( name, callback[, context] ) => self\r\n             * @param  {String}   name     事件名\r\n             * @param  {Function} callback 事件处理器\r\n             * @param  {Object}   [context]  事件处理器的上下文。\r\n             * @return {self} 返回自身，方便链式\r\n             * @chainable\r\n             */\r\n            once: function( name, callback, context ) {\r\n                var me = this;\r\n    \r\n                if ( !callback ) {\r\n                    return me;\r\n                }\r\n    \r\n                eachEvent( name, callback, function( name, callback ) {\r\n                    var once = function() {\r\n                            me.off( name, once );\r\n                            return callback.apply( context || me, arguments );\r\n                        };\r\n    \r\n                    once._cb = callback;\r\n                    me.on( name, once, context );\r\n                });\r\n    \r\n                return me;\r\n            },\r\n    \r\n            /**\r\n             * 解除事件绑定\r\n             * @method off\r\n             * @grammar off( [name[, callback[, context] ] ] ) => self\r\n             * @param  {String}   [name]     事件名\r\n             * @param  {Function} [callback] 事件处理器\r\n             * @param  {Object}   [context]  事件处理器的上下文。\r\n             * @return {self} 返回自身，方便链式\r\n             * @chainable\r\n             */\r\n            off: function( name, cb, ctx ) {\r\n                var events = this._events;\r\n    \r\n                if ( !events ) {\r\n                    return this;\r\n                }\r\n    \r\n                if ( !name && !cb && !ctx ) {\r\n                    this._events = [];\r\n                    return this;\r\n                }\r\n    \r\n                eachEvent( name, cb, function( name, cb ) {\r\n                    $.each( findHandlers( events, name, cb, ctx ), function() {\r\n                        delete events[ this.id ];\r\n                    });\r\n                });\r\n    \r\n                return this;\r\n            },\r\n    \r\n            /**\r\n             * 触发事件\r\n             * @method trigger\r\n             * @grammar trigger( name[, args...] ) => self\r\n             * @param  {String}   type     事件名\r\n             * @param  {*} [...] 任意参数\r\n             * @return {Boolean} 如果handler中return false了，则返回false, 否则返回true\r\n             */\r\n            trigger: function( type ) {\r\n                var args, events, allEvents;\r\n    \r\n                if ( !this._events || !type ) {\r\n                    return this;\r\n                }\r\n    \r\n                args = slice.call( arguments, 1 );\r\n                events = findHandlers( this._events, type );\r\n                allEvents = findHandlers( this._events, 'all' );\r\n    \r\n                return triggerHanders( events, args ) &&\r\n                        triggerHanders( allEvents, arguments );\r\n            }\r\n        };\r\n    \r\n        /**\r\n         * 中介者，它本身是个单例，但可以通过[installTo](#WebUploader:Mediator:installTo)方法，使任何对象具备事件行为。\r\n         * 主要目的是负责模块与模块之间的合作，降低耦合度。\r\n         *\r\n         * @class Mediator\r\n         */\r\n        return $.extend({\r\n    \r\n            /**\r\n             * 可以通过这个接口，使任何对象具备事件功能。\r\n             * @method installTo\r\n             * @param  {Object} obj 需要具备事件行为的对象。\r\n             * @return {Object} 返回obj.\r\n             */\r\n            installTo: function( obj ) {\r\n                return $.extend( obj, protos );\r\n            }\r\n    \r\n        }, protos );\r\n    });\r\n    /**\r\n     * @fileOverview Uploader上传类\r\n     */\r\n    define('uploader',[\r\n        'base',\r\n        'mediator'\r\n    ], function( Base, Mediator ) {\r\n    \r\n        var $ = Base.$;\r\n    \r\n        /**\r\n         * 上传入口类。\r\n         * @class Uploader\r\n         * @constructor\r\n         * @grammar new Uploader( opts ) => Uploader\r\n         * @example\r\n         * var uploader = WebUploader.Uploader({\r\n         *     swf: 'path_of_swf/Uploader.swf',\r\n         *\r\n         *     // 开起分片上传。\r\n         *     chunked: true\r\n         * });\r\n         */\r\n        function Uploader( opts ) {\r\n            this.options = $.extend( true, {}, Uploader.options, opts );\r\n            this._init( this.options );\r\n        }\r\n    \r\n        // default Options\r\n        // widgets中有相应扩展\r\n        Uploader.options = {};\r\n        Mediator.installTo( Uploader.prototype );\r\n    \r\n        // 批量添加纯命令式方法。\r\n        $.each({\r\n            upload: 'start-upload',\r\n            stop: 'stop-upload',\r\n            getFile: 'get-file',\r\n            getFiles: 'get-files',\r\n            addFile: 'add-file',\r\n            addFiles: 'add-file',\r\n            sort: 'sort-files',\r\n            removeFile: 'remove-file',\r\n            skipFile: 'skip-file',\r\n            retry: 'retry',\r\n            isInProgress: 'is-in-progress',\r\n            makeThumb: 'make-thumb',\r\n            getDimension: 'get-dimension',\r\n            addButton: 'add-btn',\r\n            getRuntimeType: 'get-runtime-type',\r\n            refresh: 'refresh',\r\n            disable: 'disable',\r\n            enable: 'enable',\r\n            reset: 'reset'\r\n        }, function( fn, command ) {\r\n            Uploader.prototype[ fn ] = function() {\r\n                return this.request( command, arguments );\r\n            };\r\n        });\r\n    \r\n        $.extend( Uploader.prototype, {\r\n            state: 'pending',\r\n    \r\n            _init: function( opts ) {\r\n                var me = this;\r\n    \r\n                me.request( 'init', opts, function() {\r\n                    me.state = 'ready';\r\n                    me.trigger('ready');\r\n                });\r\n            },\r\n    \r\n            /**\r\n             * 获取或者设置Uploader配置项。\r\n             * @method option\r\n             * @grammar option( key ) => *\r\n             * @grammar option( key, val ) => self\r\n             * @example\r\n             *\r\n             * // 初始状态图片上传前不会压缩\r\n             * var uploader = new WebUploader.Uploader({\r\n             *     resize: null;\r\n             * });\r\n             *\r\n             * // 修改后图片上传前，尝试将图片压缩到1600 * 1600\r\n             * uploader.options( 'resize', {\r\n             *     width: 1600,\r\n             *     height: 1600\r\n             * });\r\n             */\r\n            option: function( key, val ) {\r\n                var opts = this.options;\r\n    \r\n                // setter\r\n                if ( arguments.length > 1 ) {\r\n    \r\n                    if ( $.isPlainObject( val ) &&\r\n                            $.isPlainObject( opts[ key ] ) ) {\r\n                        $.extend( opts[ key ], val );\r\n                    } else {\r\n                        opts[ key ] = val;\r\n                    }\r\n    \r\n                } else {    // getter\r\n                    return key ? opts[ key ] : opts;\r\n                }\r\n            },\r\n    \r\n            /**\r\n             * 获取文件统计信息。返回一个包含一下信息的对象。\r\n             * * `successNum` 上传成功的文件数\r\n             * * `uploadFailNum` 上传失败的文件数\r\n             * * `cancelNum` 被删除的文件数\r\n             * * `invalidNum` 无效的文件数\r\n             * * `queueNum` 还在队列中的文件数\r\n             * @method getStats\r\n             * @grammar getStats() => Object\r\n             */\r\n            getStats: function() {\r\n                // return this._mgr.getStats.apply( this._mgr, arguments );\r\n                var stats = this.request('get-stats');\r\n    \r\n                return {\r\n                    successNum: stats.numOfSuccess,\r\n    \r\n                    // who care?\r\n                    // queueFailNum: 0,\r\n                    cancelNum: stats.numOfCancel,\r\n                    invalidNum: stats.numOfInvalid,\r\n                    uploadFailNum: stats.numOfUploadFailed,\r\n                    queueNum: stats.numOfQueue\r\n                };\r\n            },\r\n    \r\n            // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器\r\n            trigger: function( type/*, args...*/ ) {\r\n                var args = [].slice.call( arguments, 1 ),\r\n                    opts = this.options,\r\n                    name = 'on' + type.substring( 0, 1 ).toUpperCase() +\r\n                        type.substring( 1 );\r\n    \r\n                if (\r\n                        // 调用通过on方法注册的handler.\r\n                        Mediator.trigger.apply( this, arguments ) === false ||\r\n    \r\n                        // 调用opts.onEvent\r\n                        $.isFunction( opts[ name ] ) &&\r\n                        opts[ name ].apply( this, args ) === false ||\r\n    \r\n                        // 调用this.onEvent\r\n                        $.isFunction( this[ name ] ) &&\r\n                        this[ name ].apply( this, args ) === false ||\r\n    \r\n                        // 广播所有uploader的事件。\r\n                        Mediator.trigger.apply( Mediator,\r\n                        [ this, type ].concat( args ) ) === false ) {\r\n    \r\n                    return false;\r\n                }\r\n    \r\n                return true;\r\n            },\r\n    \r\n            // widgets/widget.js将补充此方法的详细文档。\r\n            request: Base.noop\r\n        });\r\n    \r\n        /**\r\n         * 创建Uploader实例，等同于new Uploader( opts );\r\n         * @method create\r\n         * @class Base\r\n         * @static\r\n         * @grammar Base.create( opts ) => Uploader\r\n         */\r\n        Base.create = Uploader.create = function( opts ) {\r\n            return new Uploader( opts );\r\n        };\r\n    \r\n        // 暴露Uploader，可以通过它来扩展业务逻辑。\r\n        Base.Uploader = Uploader;\r\n    \r\n        return Uploader;\r\n    });\r\n    /**\r\n     * @fileOverview Runtime管理器，负责Runtime的选择, 连接\r\n     */\r\n    define('runtime/runtime',[\r\n        'base',\r\n        'mediator'\r\n    ], function( Base, Mediator ) {\r\n    \r\n        var $ = Base.$,\r\n            factories = {},\r\n    \r\n            // 获取对象的第一个key\r\n            getFirstKey = function( obj ) {\r\n                for ( var key in obj ) {\r\n                    if ( obj.hasOwnProperty( key ) ) {\r\n                        return key;\r\n                    }\r\n                }\r\n                return null;\r\n            };\r\n    \r\n        // 接口类。\r\n        function Runtime( options ) {\r\n            this.options = $.extend({\r\n                container: document.body\r\n            }, options );\r\n            this.uid = Base.guid('rt_');\r\n        }\r\n    \r\n        $.extend( Runtime.prototype, {\r\n    \r\n            getContainer: function() {\r\n                var opts = this.options,\r\n                    parent, container;\r\n    \r\n                if ( this._container ) {\r\n                    return this._container;\r\n                }\r\n    \r\n                parent = $( opts.container || document.body );\r\n                container = $( document.createElement('div') );\r\n    \r\n                container.attr( 'id', 'rt_' + this.uid );\r\n                container.css({\r\n                    position: 'absolute',\r\n                    top: '0px',\r\n                    left: '0px',\r\n                    width: '1px',\r\n                    height: '1px',\r\n                    overflow: 'hidden'\r\n                });\r\n    \r\n                parent.append( container );\r\n                parent.addClass('webuploader-container');\r\n                this._container = container;\r\n                return container;\r\n            },\r\n    \r\n            init: Base.noop,\r\n            exec: Base.noop,\r\n    \r\n            destroy: function() {\r\n                if ( this._container ) {\r\n                    this._container.parentNode.removeChild( this.__container );\r\n                }\r\n    \r\n                this.off();\r\n            }\r\n        });\r\n    \r\n        Runtime.orders = 'html5,flash';\r\n    \r\n    \r\n        /**\r\n         * 添加Runtime实现。\r\n         * @param {String} type    类型\r\n         * @param {Runtime} factory 具体Runtime实现。\r\n         */\r\n        Runtime.addRuntime = function( type, factory ) {\r\n            factories[ type ] = factory;\r\n        };\r\n    \r\n        Runtime.hasRuntime = function( type ) {\r\n            return !!(type ? factories[ type ] : getFirstKey( factories ));\r\n        };\r\n    \r\n        Runtime.create = function( opts, orders ) {\r\n            var type, runtime;\r\n    \r\n            orders = orders || Runtime.orders;\r\n            $.each( orders.split( /\\s*,\\s*/g ), function() {\r\n                if ( factories[ this ] ) {\r\n                    type = this;\r\n                    return false;\r\n                }\r\n            });\r\n    \r\n            type = type || getFirstKey( factories );\r\n    \r\n            if ( !type ) {\r\n                throw new Error('Runtime Error');\r\n            }\r\n    \r\n            runtime = new factories[ type ]( opts );\r\n            return runtime;\r\n        };\r\n    \r\n        Mediator.installTo( Runtime.prototype );\r\n        return Runtime;\r\n    });\r\n    \r\n    /**\r\n     * @fileOverview Runtime管理器，负责Runtime的选择, 连接\r\n     */\r\n    define('runtime/client',[\r\n        'base',\r\n        'mediator',\r\n        'runtime/runtime'\r\n    ], function( Base, Mediator, Runtime ) {\r\n    \r\n        var cache;\r\n    \r\n        cache = (function() {\r\n            var obj = {};\r\n    \r\n            return {\r\n                add: function( runtime ) {\r\n                    obj[ runtime.uid ] = runtime;\r\n                },\r\n    \r\n                get: function( ruid, standalone ) {\r\n                    var i;\r\n    \r\n                    if ( ruid ) {\r\n                        return obj[ ruid ];\r\n                    }\r\n    \r\n                    for ( i in obj ) {\r\n                        // 有些类型不能重用，比如filepicker.\r\n                        if ( standalone && obj[ i ].__standalone ) {\r\n                            continue;\r\n                        }\r\n    \r\n                        return obj[ i ];\r\n                    }\r\n    \r\n                    return null;\r\n                },\r\n    \r\n                remove: function( runtime ) {\r\n                    delete obj[ runtime.uid ];\r\n                }\r\n            };\r\n        })();\r\n    \r\n        function RuntimeClient( component, standalone ) {\r\n            var deferred = Base.Deferred(),\r\n                runtime;\r\n    \r\n            this.uid = Base.guid('client_');\r\n    \r\n            // 允许runtime没有初始化之前，注册一些方法在初始化后执行。\r\n            this.runtimeReady = function( cb ) {\r\n                return deferred.done( cb );\r\n            };\r\n    \r\n            this.connectRuntime = function( opts, cb ) {\r\n    \r\n                // already connected.\r\n                if ( runtime ) {\r\n                    throw new Error('already connected!');\r\n                }\r\n    \r\n                deferred.done( cb );\r\n    \r\n                if ( typeof opts === 'string' && cache.get( opts ) ) {\r\n                    runtime = cache.get( opts );\r\n                }\r\n    \r\n                // 像filePicker只能独立存在，不能公用。\r\n                runtime = runtime || cache.get( null, standalone );\r\n    \r\n                // 需要创建\r\n                if ( !runtime ) {\r\n                    runtime = Runtime.create( opts, opts.runtimeOrder );\r\n                    runtime.__promise = deferred.promise();\r\n                    runtime.once( 'ready', deferred.resolve );\r\n                    runtime.init();\r\n                    cache.add( runtime );\r\n                    runtime.__client = 1;\r\n                } else {\r\n                    // 来自cache\r\n                    Base.$.extend( runtime.options, opts );\r\n                    runtime.__promise.then( deferred.resolve );\r\n                    runtime.__client++;\r\n                }\r\n    \r\n                standalone && (runtime.__standalone = standalone);\r\n                return runtime;\r\n            };\r\n    \r\n            this.getRuntime = function() {\r\n                return runtime;\r\n            };\r\n    \r\n            this.disconnectRuntime = function() {\r\n                if ( !runtime ) {\r\n                    return;\r\n                }\r\n    \r\n                runtime.__client--;\r\n    \r\n                if ( runtime.__client <= 0 ) {\r\n                    cache.remove( runtime );\r\n                    delete runtime.__promise;\r\n                    runtime.destroy();\r\n                }\r\n    \r\n                runtime = null;\r\n            };\r\n    \r\n            this.exec = function() {\r\n                if ( !runtime ) {\r\n                    return;\r\n                }\r\n    \r\n                var args = Base.slice( arguments );\r\n                component && args.unshift( component );\r\n    \r\n                return runtime.exec.apply( this, args );\r\n            };\r\n    \r\n            this.getRuid = function() {\r\n                return runtime && runtime.uid;\r\n            };\r\n    \r\n            this.destroy = (function( destroy ) {\r\n                return function() {\r\n                    destroy && destroy.apply( this, arguments );\r\n                    this.trigger('destroy');\r\n                    this.off();\r\n                    this.exec('destroy');\r\n                    this.disconnectRuntime();\r\n                };\r\n            })( this.destroy );\r\n        }\r\n    \r\n        Mediator.installTo( RuntimeClient.prototype );\r\n        return RuntimeClient;\r\n    });\r\n    /**\r\n     * @fileOverview 错误信息\r\n     */\r\n    define('lib/dnd',[\r\n        'base',\r\n        'mediator',\r\n        'runtime/client'\r\n    ], function( Base, Mediator, RuntimeClent ) {\r\n    \r\n        var $ = Base.$;\r\n    \r\n        function DragAndDrop( opts ) {\r\n            opts = this.options = $.extend({}, DragAndDrop.options, opts );\r\n    \r\n            opts.container = $( opts.container );\r\n    \r\n            if ( !opts.container.length ) {\r\n                return;\r\n            }\r\n    \r\n            RuntimeClent.call( this, 'DragAndDrop' );\r\n        }\r\n    \r\n        DragAndDrop.options = {\r\n            accept: null,\r\n            disableGlobalDnd: false\r\n        };\r\n    \r\n        Base.inherits( RuntimeClent, {\r\n            constructor: DragAndDrop,\r\n    \r\n            init: function() {\r\n                var me = this;\r\n    \r\n                me.connectRuntime( me.options, function() {\r\n                    me.exec('init');\r\n                    me.trigger('ready');\r\n                });\r\n            },\r\n    \r\n            destroy: function() {\r\n                this.disconnectRuntime();\r\n            }\r\n        });\r\n    \r\n        Mediator.installTo( DragAndDrop.prototype );\r\n    \r\n        return DragAndDrop;\r\n    });\r\n    /**\r\n     * @fileOverview 组件基类。\r\n     */\r\n    define('widgets/widget',[\r\n        'base',\r\n        'uploader'\r\n    ], function( Base, Uploader ) {\r\n    \r\n        var $ = Base.$,\r\n            _init = Uploader.prototype._init,\r\n            IGNORE = {},\r\n            widgetClass = [];\r\n    \r\n        function isArrayLike( obj ) {\r\n            if ( !obj ) {\r\n                return false;\r\n            }\r\n    \r\n            var length = obj.length,\r\n                type = $.type( obj );\r\n    \r\n            if ( obj.nodeType === 1 && length ) {\r\n                return true;\r\n            }\r\n    \r\n            return type === 'array' || type !== 'function' && type !== 'string' &&\r\n                    (length === 0 || typeof length === 'number' && length > 0 &&\r\n                    (length - 1) in obj);\r\n        }\r\n    \r\n        function Widget( uploader ) {\r\n            this.owner = uploader;\r\n            this.options = uploader.options;\r\n        }\r\n    \r\n        $.extend( Widget.prototype, {\r\n    \r\n            init: Base.noop,\r\n    \r\n            // 类Backbone的事件监听声明，监听uploader实例上的事件\r\n            // widget直接无法监听事件，事件只能通过uploader来传递\r\n            invoke: function( apiName, args ) {\r\n    \r\n                /*\r\n                    {\r\n                        'make-thumb': 'makeThumb'\r\n                    }\r\n                 */\r\n                var map = this.responseMap;\r\n    \r\n                // 如果无API响应声明则忽略\r\n                if ( !map || !(apiName in map) || !(map[ apiName ] in this) ||\r\n                        !$.isFunction( this[ map[ apiName ] ] ) ) {\r\n    \r\n                    return IGNORE;\r\n                }\r\n    \r\n                return this[ map[ apiName ] ].apply( this, args );\r\n    \r\n            },\r\n    \r\n            /**\r\n             * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。\r\n             * @method request\r\n             * @grammar request( command, args ) => * | Promise\r\n             * @grammar request( command, args, callback ) => Promise\r\n             * @for  Uploader\r\n             */\r\n            request: function() {\r\n                return this.owner.request.apply( this.owner, arguments );\r\n            }\r\n        });\r\n    \r\n        // 扩展Uploader.\r\n        $.extend( Uploader.prototype, {\r\n    \r\n            // 覆写_init用来初始化widgets\r\n            _init: function() {\r\n                var me = this,\r\n                    widgets = me._widgets = [];\r\n    \r\n                $.each( widgetClass, function( _, klass ) {\r\n                    widgets.push( new klass( me ) );\r\n                });\r\n    \r\n                return _init.apply( me, arguments );\r\n            },\r\n    \r\n            request: function( apiName, args, callback ) {\r\n                var i = 0,\r\n                    widgets = this._widgets,\r\n                    len = widgets.length,\r\n                    rlts = [],\r\n                    dfds = [],\r\n                    widget, rlt, promise, key;\r\n    \r\n                args = isArrayLike( args ) ? args : [ args ];\r\n    \r\n                for ( ; i < len; i++ ) {\r\n                    widget = widgets[ i ];\r\n                    rlt = widget.invoke( apiName, args );\r\n    \r\n                    if ( rlt !== IGNORE ) {\r\n    \r\n                        // Deferred对象\r\n                        if ( Base.isPromise( rlt ) ) {\r\n                            dfds.push( rlt );\r\n                        } else {\r\n                            rlts.push( rlt );\r\n                        }\r\n                    }\r\n                }\r\n    \r\n                // 如果有callback，则用异步方式。\r\n                if ( callback || dfds.length ) {\r\n                    promise = Base.when.apply( Base, dfds );\r\n                    key = promise.pipe ? 'pipe' : 'then';\r\n    \r\n                    // 很重要不能删除。删除了会死循环。\r\n                    // 保证执行顺序。让callback总是在下一个tick中执行。\r\n                    return promise[ key ](function() {\r\n                                var deferred = Base.Deferred(),\r\n                                    args = arguments;\r\n    \r\n                                setTimeout(function() {\r\n                                    deferred.resolve.apply( deferred, args );\r\n                                }, 1 );\r\n    \r\n                                return deferred.promise();\r\n                            })[ key ]( callback || Base.noop );\r\n                } else {\r\n                    return rlts[ 0 ];\r\n                }\r\n            }\r\n        });\r\n    \r\n        /**\r\n         * 添加组件\r\n         * @param  {object} widgetProto 组件原型，构造函数通过constructor属性定义\r\n         * @param  {object} responseMap API名称与函数实现的映射\r\n         * @example\r\n         *     Uploader.register( {\r\n         *         init: function( options ) {},\r\n         *         makeThumb: function() {}\r\n         *     }, {\r\n         *         'make-thumb': 'makeThumb'\r\n         *     } );\r\n         */\r\n        Uploader.register = Widget.register = function( responseMap, widgetProto ) {\r\n            var map = { init: 'init' },\r\n                klass;\r\n    \r\n            if ( arguments.length === 1 ) {\r\n                widgetProto = responseMap;\r\n                widgetProto.responseMap = map;\r\n            } else {\r\n                widgetProto.responseMap = $.extend( map, responseMap );\r\n            }\r\n    \r\n            klass = Base.inherits( Widget, widgetProto );\r\n            widgetClass.push( klass );\r\n    \r\n            return klass;\r\n        };\r\n    \r\n        return Widget;\r\n    });\r\n    /**\r\n     * @fileOverview DragAndDrop Widget。\r\n     */\r\n    define('widgets/filednd',[\r\n        'base',\r\n        'uploader',\r\n        'lib/dnd',\r\n        'widgets/widget'\r\n    ], function( Base, Uploader, Dnd ) {\r\n        var $ = Base.$;\r\n    \r\n        Uploader.options.dnd = '';\r\n    \r\n        /**\r\n         * @property {Selector} [dnd=undefined]  指定Drag And Drop拖拽的容器，如果不指定，则不启动。\r\n         * @namespace options\r\n         * @for Uploader\r\n         */\r\n    \r\n        /**\r\n         * @event dndAccept\r\n         * @param {DataTransferItemList} items DataTransferItem\r\n         * @description 阻止此事件可以拒绝某些类型的文件拖入进来。目前只有 chrome 提供这样的 API，且只能通过 mime-type 验证。\r\n         * @for  Uploader\r\n         */\r\n        return Uploader.register({\r\n            init: function( opts ) {\r\n    \r\n                if ( !opts.dnd ||\r\n                        this.request('predict-runtime-type') !== 'html5' ) {\r\n                    return;\r\n                }\r\n    \r\n                var me = this,\r\n                    deferred = Base.Deferred(),\r\n                    options = $.extend({}, {\r\n                        disableGlobalDnd: opts.disableGlobalDnd,\r\n                        container: opts.dnd,\r\n                        accept: opts.accept\r\n                    }),\r\n                    dnd;\r\n    \r\n                dnd = new Dnd( options );\r\n    \r\n                dnd.once( 'ready', deferred.resolve );\r\n                dnd.on( 'drop', function( files ) {\r\n                    me.request( 'add-file', [ files ]);\r\n                });\r\n    \r\n                // 检测文件是否全部允许添加。\r\n                dnd.on( 'accept', function( items ) {\r\n                    return me.owner.trigger( 'dndAccept', items );\r\n                });\r\n    \r\n                dnd.init();\r\n    \r\n                return deferred.promise();\r\n            }\r\n        });\r\n    });\r\n    \r\n    /**\r\n     * @fileOverview 错误信息\r\n     */\r\n    define('lib/filepaste',[\r\n        'base',\r\n        'mediator',\r\n        'runtime/client'\r\n    ], function( Base, Mediator, RuntimeClent ) {\r\n    \r\n        var $ = Base.$;\r\n    \r\n        function FilePaste( opts ) {\r\n            opts = this.options = $.extend({}, opts );\r\n            opts.container = $( opts.container || document.body );\r\n            RuntimeClent.call( this, 'FilePaste' );\r\n        }\r\n    \r\n        Base.inherits( RuntimeClent, {\r\n            constructor: FilePaste,\r\n    \r\n            init: function() {\r\n                var me = this;\r\n    \r\n                me.connectRuntime( me.options, function() {\r\n                    me.exec('init');\r\n                    me.trigger('ready');\r\n                });\r\n            },\r\n    \r\n            destroy: function() {\r\n                this.exec('destroy');\r\n                this.disconnectRuntime();\r\n                this.off();\r\n            }\r\n        });\r\n    \r\n        Mediator.installTo( FilePaste.prototype );\r\n    \r\n        return FilePaste;\r\n    });\r\n    /**\r\n     * @fileOverview 组件基类。\r\n     */\r\n    define('widgets/filepaste',[\r\n        'base',\r\n        'uploader',\r\n        'lib/filepaste',\r\n        'widgets/widget'\r\n    ], function( Base, Uploader, FilePaste ) {\r\n        var $ = Base.$;\r\n    \r\n        /**\r\n         * @property {Selector} [paste=undefined]  指定监听paste事件的容器，如果不指定，不启用此功能。此功能为通过粘贴来添加截屏的图片。建议设置为`document.body`.\r\n         * @namespace options\r\n         * @for Uploader\r\n         */\r\n        return Uploader.register({\r\n            init: function( opts ) {\r\n    \r\n                if ( !opts.paste ||\r\n                        this.request('predict-runtime-type') !== 'html5' ) {\r\n                    return;\r\n                }\r\n    \r\n                var me = this,\r\n                    deferred = Base.Deferred(),\r\n                    options = $.extend({}, {\r\n                        container: opts.paste,\r\n                        accept: opts.accept\r\n                    }),\r\n                    paste;\r\n    \r\n                paste = new FilePaste( options );\r\n    \r\n                paste.once( 'ready', deferred.resolve );\r\n                paste.on( 'paste', function( files ) {\r\n                    me.owner.request( 'add-file', [ files ]);\r\n                });\r\n                paste.init();\r\n    \r\n                return deferred.promise();\r\n            }\r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview Blob\r\n     */\r\n    define('lib/blob',[\r\n        'base',\r\n        'runtime/client'\r\n    ], function( Base, RuntimeClient ) {\r\n    \r\n        function Blob( ruid, source ) {\r\n            var me = this;\r\n    \r\n            me.source = source;\r\n            me.ruid = ruid;\r\n    \r\n            RuntimeClient.call( me, 'Blob' );\r\n    \r\n            this.uid = source.uid || this.uid;\r\n            this.type = source.type || '';\r\n            this.size = source.size || 0;\r\n    \r\n            if ( ruid ) {\r\n                me.connectRuntime( ruid );\r\n            }\r\n        }\r\n    \r\n        Base.inherits( RuntimeClient, {\r\n            constructor: Blob,\r\n    \r\n            slice: function( start, end ) {\r\n                return this.exec( 'slice', start, end );\r\n            },\r\n    \r\n            getSource: function() {\r\n                return this.source;\r\n            }\r\n        });\r\n    \r\n        return Blob;\r\n    });\r\n    /**\r\n     * 为了统一化Flash的File和HTML5的File而存在。\r\n     * 以至于要调用Flash里面的File，也可以像调用HTML5版本的File一下。\r\n     * @fileOverview File\r\n     */\r\n    define('lib/file',[\r\n        'base',\r\n        'lib/blob'\r\n    ], function( Base, Blob ) {\r\n    \r\n        var uid = 1,\r\n            rExt = /\\.([^.]+)$/;\r\n    \r\n        function File( ruid, file ) {\r\n            var ext;\r\n    \r\n            Blob.apply( this, arguments );\r\n            this.name = file.name || ('untitled' + uid++);\r\n            ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : '';\r\n    \r\n            // todo 支持其他类型文件的转换。\r\n    \r\n            // 如果有mimetype, 但是文件名里面没有找出后缀规律\r\n            if ( !ext && this.type ) {\r\n                ext = /\\/(jpg|jpeg|png|gif|bmp)$/i.exec( this.type ) ?\r\n                        RegExp.$1.toLowerCase() : '';\r\n                this.name += '.' + ext;\r\n            }\r\n    \r\n            // 如果没有指定mimetype, 但是知道文件后缀。\r\n            if ( !this.type &&  ~'jpg,jpeg,png,gif,bmp'.indexOf( ext ) ) {\r\n                this.type = 'image/' + (ext === 'jpg' ? 'jpeg' : ext);\r\n            }\r\n    \r\n            this.ext = ext;\r\n            this.lastModifiedDate = file.lastModifiedDate ||\r\n                    (new Date()).toLocaleString();\r\n        }\r\n    \r\n        return Base.inherits( Blob, File );\r\n    });\r\n    \r\n    /**\r\n     * @fileOverview 错误信息\r\n     */\r\n    define('lib/filepicker',[\r\n        'base',\r\n        'runtime/client',\r\n        'lib/file'\r\n    ], function( Base, RuntimeClent, File ) {\r\n    \r\n        var $ = Base.$;\r\n    \r\n        function FilePicker( opts ) {\r\n            opts = this.options = $.extend({}, FilePicker.options, opts );\r\n    \r\n            opts.container = $( opts.id );\r\n    \r\n            if ( !opts.container.length ) {\r\n                throw new Error('按钮指定错误');\r\n            }\r\n    \r\n            opts.innerHTML = opts.innerHTML || opts.label ||\r\n                    opts.container.html() || '';\r\n    \r\n            opts.button = $( opts.button || document.createElement('div') );\r\n            opts.button.html( opts.innerHTML );\r\n            opts.container.html( opts.button );\r\n    \r\n            RuntimeClent.call( this, 'FilePicker', true );\r\n        }\r\n    \r\n        FilePicker.options = {\r\n            button: null,\r\n            container: null,\r\n            label: null,\r\n            innerHTML: null,\r\n            multiple: true,\r\n            accept: null,\r\n            name: 'file'\r\n        };\r\n    \r\n        Base.inherits( RuntimeClent, {\r\n            constructor: FilePicker,\r\n    \r\n            init: function() {\r\n                var me = this,\r\n                    opts = me.options,\r\n                    button = opts.button;\r\n    \r\n                button.addClass('webuploader-pick');\r\n    \r\n                me.on( 'all', function( type ) {\r\n                    var files;\r\n    \r\n                    switch ( type ) {\r\n                        case 'mouseenter':\r\n                            button.addClass('webuploader-pick-hover');\r\n                            break;\r\n    \r\n                        case 'mouseleave':\r\n                            button.removeClass('webuploader-pick-hover');\r\n                            break;\r\n    \r\n                        case 'change':\r\n                            files = me.exec('getFiles');\r\n                            me.trigger( 'select', $.map( files, function( file ) {\r\n                                file = new File( me.getRuid(), file );\r\n    \r\n                                // 记录来源。\r\n                                file._refer = opts.container;\r\n                                return file;\r\n                            }), opts.container );\r\n                            break;\r\n                    }\r\n                });\r\n    \r\n                me.connectRuntime( opts, function() {\r\n                    me.refresh();\r\n                    me.exec( 'init', opts );\r\n                    me.trigger('ready');\r\n                });\r\n    \r\n                $( window ).on( 'resize', function() {\r\n                    me.refresh();\r\n                });\r\n            },\r\n    \r\n            refresh: function() {\r\n                var shimContainer = this.getRuntime().getContainer(),\r\n                    button = this.options.button,\r\n                    width = button.outerWidth ?\r\n                            button.outerWidth() : button.width(),\r\n    \r\n                    height = button.outerHeight ?\r\n                            button.outerHeight() : button.height(),\r\n    \r\n                    pos = button.offset();\r\n    \r\n                width && height && shimContainer.css({\r\n                    bottom: 'auto',\r\n                    right: 'auto',\r\n                    width: width + 'px',\r\n                    height: height + 'px'\r\n                }).offset( pos );\r\n            },\r\n    \r\n            enable: function() {\r\n                var btn = this.options.button;\r\n    \r\n                btn.removeClass('webuploader-pick-disable');\r\n                this.refresh();\r\n            },\r\n    \r\n            disable: function() {\r\n                var btn = this.options.button;\r\n    \r\n                this.getRuntime().getContainer().css({\r\n                    top: '-99999px'\r\n                });\r\n    \r\n                btn.addClass('webuploader-pick-disable');\r\n            },\r\n    \r\n            destroy: function() {\r\n                if ( this.runtime ) {\r\n                    this.exec('destroy');\r\n                    this.disconnectRuntime();\r\n                }\r\n            }\r\n        });\r\n    \r\n        return FilePicker;\r\n    });\r\n    \r\n    /**\r\n     * @fileOverview 文件选择相关\r\n     */\r\n    define('widgets/filepicker',[\r\n        'base',\r\n        'uploader',\r\n        'lib/filepicker',\r\n        'widgets/widget'\r\n    ], function( Base, Uploader, FilePicker ) {\r\n        var $ = Base.$;\r\n    \r\n        $.extend( Uploader.options, {\r\n    \r\n            /**\r\n             * @property {Selector | Object} [pick=undefined]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 指定选择文件的按钮容器，不指定则不创建按钮。\r\n             *\r\n             * * `id` {Seletor} 指定选择文件的按钮容器，不指定则不创建按钮。\r\n             * * `label` {String} 请采用 `innerHTML` 代替\r\n             * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。\r\n             * * `multiple` {Boolean} 是否开起同时选择多个文件能力。\r\n             */\r\n            pick: null,\r\n    \r\n            /**\r\n             * @property {Arroy} [accept=null]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表，所以这里需要分开指定。\r\n             *\r\n             * * `title` {String} 文字描述\r\n             * * `extensions` {String} 允许的文件后缀，不带点，多个用逗号分割。\r\n             * * `mimeTypes` {String} 多个用逗号分割。\r\n             *\r\n             * 如：\r\n             *\r\n             * ```\r\n             * {\r\n             *     title: 'Images',\r\n             *     extensions: 'gif,jpg,jpeg,bmp,png',\r\n             *     mimeTypes: 'image/*'\r\n             * }\r\n             * ```\r\n             */\r\n            accept: null/*{\r\n                title: 'Images',\r\n                extensions: 'gif,jpg,jpeg,bmp,png',\r\n                mimeTypes: 'image/*'\r\n            }*/\r\n        });\r\n    \r\n        return Uploader.register({\r\n            'add-btn': 'addButton',\r\n            refresh: 'refresh',\r\n            disable: 'disable',\r\n            enable: 'enable'\r\n        }, {\r\n    \r\n            init: function( opts ) {\r\n                this.pickers = [];\r\n                return opts.pick && this.addButton( opts.pick );\r\n            },\r\n    \r\n            refresh: function() {\r\n                $.each( this.pickers, function() {\r\n                    this.refresh();\r\n                });\r\n            },\r\n    \r\n            /**\r\n             * @method addButton\r\n             * @for Uploader\r\n             * @grammar addButton( pick ) => Promise\r\n             * @description\r\n             * 添加文件选择按钮，如果一个按钮不够，需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。\r\n             * @example\r\n             * uploader.addButton({\r\n             *     id: '#btnContainer',\r\n             *     innerHTML: '选择文件'\r\n             * });\r\n             */\r\n            addButton: function( pick ) {\r\n                var me = this,\r\n                    opts = me.options,\r\n                    accept = opts.accept,\r\n                    options, picker, deferred;\r\n    \r\n                if ( !pick ) {\r\n                    return;\r\n                }\r\n    \r\n                deferred = Base.Deferred();\r\n                $.isPlainObject( pick ) || (pick = {\r\n                    id: pick\r\n                });\r\n    \r\n                options = $.extend({}, pick, {\r\n                    accept: $.isPlainObject( accept ) ? [ accept ] : accept,\r\n                    swf: opts.swf,\r\n                    runtimeOrder: opts.runtimeOrder\r\n                });\r\n    \r\n                picker = new FilePicker( options );\r\n    \r\n                picker.once( 'ready', deferred.resolve );\r\n                picker.on( 'select', function( files ) {\r\n                    me.owner.request( 'add-file', [ files ]);\r\n                });\r\n                picker.init();\r\n    \r\n                this.pickers.push( picker );\r\n    \r\n                return deferred.promise();\r\n            },\r\n    \r\n            disable: function() {\r\n                $.each( this.pickers, function() {\r\n                    this.disable();\r\n                });\r\n            },\r\n    \r\n            enable: function() {\r\n                $.each( this.pickers, function() {\r\n                    this.enable();\r\n                });\r\n            }\r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview Image\r\n     */\r\n    define('lib/image',[\r\n        'base',\r\n        'runtime/client',\r\n        'lib/blob'\r\n    ], function( Base, RuntimeClient, Blob ) {\r\n        var $ = Base.$;\r\n    \r\n        // 构造器。\r\n        function Image( opts ) {\r\n            this.options = $.extend({}, Image.options, opts );\r\n            RuntimeClient.call( this, 'Image' );\r\n    \r\n            this.on( 'load', function() {\r\n                this._info = this.exec('info');\r\n                this._meta = this.exec('meta');\r\n            });\r\n        }\r\n    \r\n        // 默认选项。\r\n        Image.options = {\r\n    \r\n            // 默认的图片处理质量\r\n            quality: 90,\r\n    \r\n            // 是否裁剪\r\n            crop: false,\r\n    \r\n            // 是否保留头部信息\r\n            preserveHeaders: true,\r\n    \r\n            // 是否允许放大。\r\n            allowMagnify: true\r\n        };\r\n    \r\n        // 继承RuntimeClient.\r\n        Base.inherits( RuntimeClient, {\r\n            constructor: Image,\r\n    \r\n            info: function( val ) {\r\n    \r\n                // setter\r\n                if ( val ) {\r\n                    this._info = val;\r\n                    return this;\r\n                }\r\n    \r\n                // getter\r\n                return this._info;\r\n            },\r\n    \r\n            meta: function( val ) {\r\n    \r\n                // setter\r\n                if ( val ) {\r\n                    this._meta = val;\r\n                    return this;\r\n                }\r\n    \r\n                // getter\r\n                return this._meta;\r\n            },\r\n    \r\n            loadFromBlob: function( blob ) {\r\n                var me = this,\r\n                    ruid = blob.getRuid();\r\n    \r\n                this.connectRuntime( ruid, function() {\r\n                    me.exec( 'init', me.options );\r\n                    me.exec( 'loadFromBlob', blob );\r\n                });\r\n            },\r\n    \r\n            resize: function() {\r\n                var args = Base.slice( arguments );\r\n                return this.exec.apply( this, [ 'resize' ].concat( args ) );\r\n            },\r\n    \r\n            getAsDataUrl: function( type ) {\r\n                return this.exec( 'getAsDataUrl', type );\r\n            },\r\n    \r\n            getAsBlob: function( type ) {\r\n                var blob = this.exec( 'getAsBlob', type );\r\n    \r\n                return new Blob( this.getRuid(), blob );\r\n            }\r\n        });\r\n    \r\n        return Image;\r\n    });\r\n    /**\r\n     * @fileOverview 图片操作, 负责预览图片和上传前压缩图片\r\n     */\r\n    define('widgets/image',[\r\n        'base',\r\n        'uploader',\r\n        'lib/image',\r\n        'widgets/widget'\r\n    ], function( Base, Uploader, Image ) {\r\n    \r\n        var $ = Base.$,\r\n            throttle;\r\n    \r\n        // 根据要处理的文件大小来节流，一次不能处理太多，会卡。\r\n        throttle = (function( max ) {\r\n            var occupied = 0,\r\n                waiting = [],\r\n                tick = function() {\r\n                    var item;\r\n    \r\n                    while ( waiting.length && occupied < max ) {\r\n                        item = waiting.shift();\r\n                        occupied += item[ 0 ];\r\n                        item[ 1 ]();\r\n                    }\r\n                };\r\n    \r\n            return function( emiter, size, cb ) {\r\n                waiting.push([ size, cb ]);\r\n                emiter.once( 'destroy', function() {\r\n                    occupied -= size;\r\n                    setTimeout( tick, 1 );\r\n                });\r\n                setTimeout( tick, 1 );\r\n            };\r\n        })( 5 * 1024 * 1024 );\r\n    \r\n        $.extend( Uploader.options, {\r\n    \r\n            /**\r\n             * @property {Object} [thumb]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 配置生成缩略图的选项。\r\n             *\r\n             * 默认为：\r\n             *\r\n             * ```javascript\r\n             * {\r\n             *     width: 110,\r\n             *     height: 110,\r\n             *\r\n             *     // 图片质量，只有type为`image/jpeg`的时候才有效。\r\n             *     quality: 70,\r\n             *\r\n             *     // 是否允许放大，如果想要生成小图的时候不失真，此选项应该设置为false.\r\n             *     allowMagnify: true,\r\n             *\r\n             *     // 是否允许裁剪。\r\n             *     crop: true,\r\n             *\r\n             *     // 是否保留头部meta信息。\r\n             *     preserveHeaders: false,\r\n             *\r\n             *     // 为空的话则保留原有图片格式。\r\n             *     // 否则强制转换成指定的类型。\r\n             *     type: 'image/jpeg'\r\n             * }\r\n             * ```\r\n             */\r\n            thumb: {\r\n                width: 110,\r\n                height: 110,\r\n                quality: 70,\r\n                allowMagnify: true,\r\n                crop: true,\r\n                preserveHeaders: false,\r\n    \r\n                // 为空的话则保留原有图片格式。\r\n                // 否则强制转换成指定的类型。\r\n                // IE 8下面 base64 大小不能超过 32K 否则预览失败，而非 jpeg 编码的图片很可\r\n                // 能会超过 32k, 所以这里设置成预览的时候都是 image/jpeg\r\n                type: 'image/jpeg'\r\n            },\r\n    \r\n            /**\r\n             * @property {Object} [compress]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 配置压缩的图片的选项。如果此选项为`false`, 则图片在上传前不进行压缩。\r\n             *\r\n             * 默认为：\r\n             *\r\n             * ```javascript\r\n             * {\r\n             *     width: 1600,\r\n             *     height: 1600,\r\n             *\r\n             *     // 图片质量，只有type为`image/jpeg`的时候才有效。\r\n             *     quality: 90,\r\n             *\r\n             *     // 是否允许放大，如果想要生成小图的时候不失真，此选项应该设置为false.\r\n             *     allowMagnify: false,\r\n             *\r\n             *     // 是否允许裁剪。\r\n             *     crop: false,\r\n             *\r\n             *     // 是否保留头部meta信息。\r\n             *     preserveHeaders: true\r\n             * }\r\n             * ```\r\n             */\r\n            compress: {\r\n                width: 1600,\r\n                height: 1600,\r\n                quality: 90,\r\n                allowMagnify: false,\r\n                crop: false,\r\n                preserveHeaders: true\r\n            }\r\n        });\r\n    \r\n        return Uploader.register({\r\n            'make-thumb': 'makeThumb',\r\n            'before-send-file': 'compressImage'\r\n        }, {\r\n    \r\n    \r\n            /**\r\n             * 生成缩略图，此过程为异步，所以需要传入`callback`。\r\n             * 通常情况在图片加入队里后调用此方法来生成预览图以增强交互效果。\r\n             *\r\n             * `callback`中可以接收到两个参数。\r\n             * * 第一个为error，如果生成缩略图有错误，此error将为真。\r\n             * * 第二个为ret, 缩略图的Data URL值。\r\n             *\r\n             * **注意**\r\n             * Date URL在IE6/7中不支持，所以不用调用此方法了，直接显示一张暂不支持预览图片好了。\r\n             *\r\n             *\r\n             * @method makeThumb\r\n             * @grammar makeThumb( file, callback ) => undefined\r\n             * @grammar makeThumb( file, callback, width, height ) => undefined\r\n             * @for Uploader\r\n             * @example\r\n             *\r\n             * uploader.on( 'fileQueued', function( file ) {\r\n             *     var $li = ...;\r\n             *\r\n             *     uploader.makeThumb( file, function( error, ret ) {\r\n             *         if ( error ) {\r\n             *             $li.text('预览错误');\r\n             *         } else {\r\n             *             $li.append('<img alt=\"\" src=\"' + ret + '\" />');\r\n             *         }\r\n             *     });\r\n             *\r\n             * });\r\n             */\r\n            makeThumb: function( file, cb, width, height ) {\r\n                var opts, image;\r\n    \r\n                file = this.request( 'get-file', file );\r\n    \r\n                // 只预览图片格式。\r\n                if ( !file.type.match( /^image/ ) ) {\r\n                    cb( true );\r\n                    return;\r\n                }\r\n    \r\n                opts = $.extend({}, this.options.thumb );\r\n    \r\n                // 如果传入的是object.\r\n                if ( $.isPlainObject( width ) ) {\r\n                    opts = $.extend( opts, width );\r\n                    width = null;\r\n                }\r\n    \r\n                width = width || opts.width;\r\n                height = height || opts.height;\r\n    \r\n                image = new Image( opts );\r\n    \r\n                image.once( 'load', function() {\r\n                    file._info = file._info || image.info();\r\n                    file._meta = file._meta || image.meta();\r\n                    image.resize( width, height );\r\n                });\r\n    \r\n                image.once( 'complete', function() {\r\n                    cb( false, image.getAsDataUrl( opts.type ) );\r\n                    image.destroy();\r\n                });\r\n    \r\n                image.once( 'error', function() {\r\n                    cb( true );\r\n                    image.destroy();\r\n                });\r\n    \r\n                throttle( image, file.source.size, function() {\r\n                    file._info && image.info( file._info );\r\n                    file._meta && image.meta( file._meta );\r\n                    image.loadFromBlob( file.source );\r\n                });\r\n            },\r\n    \r\n            compressImage: function( file ) {\r\n                var opts = this.options.compress || this.options.resize,\r\n                    compressSize = opts && opts.compressSize || 300 * 1024,\r\n                    image, deferred;\r\n    \r\n                file = this.request( 'get-file', file );\r\n    \r\n                // 只预览图片格式。\r\n                if ( !opts || !~'image/jpeg,image/jpg'.indexOf( file.type ) ||\r\n                        file.size < compressSize ||\r\n                        file._compressed ) {\r\n                    return;\r\n                }\r\n    \r\n                opts = $.extend({}, opts );\r\n                deferred = Base.Deferred();\r\n    \r\n                image = new Image( opts );\r\n    \r\n                deferred.always(function() {\r\n                    image.destroy();\r\n                    image = null;\r\n                });\r\n                image.once( 'error', deferred.reject );\r\n                image.once( 'load', function() {\r\n                    file._info = file._info || image.info();\r\n                    file._meta = file._meta || image.meta();\r\n                    image.resize( opts.width, opts.height );\r\n                });\r\n    \r\n                image.once( 'complete', function() {\r\n                    var blob, size;\r\n    \r\n                    // 移动端 UC / qq 浏览器的无图模式下\r\n                    // ctx.getImageData 处理大图的时候会报 Exception\r\n                    // INDEX_SIZE_ERR: DOM Exception 1\r\n                    try {\r\n                        blob = image.getAsBlob( opts.type );\r\n    \r\n                        size = file.size;\r\n    \r\n                        // 如果压缩后，比原来还大则不用压缩后的。\r\n                        if ( blob.size < size ) {\r\n                            // file.source.destroy && file.source.destroy();\r\n                            file.source = blob;\r\n                            file.size = blob.size;\r\n    \r\n                            file.trigger( 'resize', blob.size, size );\r\n                        }\r\n    \r\n                        // 标记，避免重复压缩。\r\n                        file._compressed = true;\r\n                        deferred.resolve();\r\n                    } catch ( e ) {\r\n                        // 出错了直接继续，让其上传原始图片\r\n                        deferred.resolve();\r\n                    }\r\n                });\r\n    \r\n                file._info && image.info( file._info );\r\n                file._meta && image.meta( file._meta );\r\n    \r\n                image.loadFromBlob( file.source );\r\n                return deferred.promise();\r\n            }\r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview 文件属性封装\r\n     */\r\n    define('file',[\r\n        'base',\r\n        'mediator'\r\n    ], function( Base, Mediator ) {\r\n    \r\n        var $ = Base.$,\r\n            idPrefix = 'WU_FILE_',\r\n            idSuffix = 0,\r\n            rExt = /\\.([^.]+)$/,\r\n            statusMap = {};\r\n    \r\n        function gid() {\r\n            return idPrefix + idSuffix++;\r\n        }\r\n    \r\n        /**\r\n         * 文件类\r\n         * @class File\r\n         * @constructor 构造函数\r\n         * @grammar new File( source ) => File\r\n         * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。\r\n         */\r\n        function WUFile( source ) {\r\n    \r\n            /**\r\n             * 文件名，包括扩展名（后缀）\r\n             * @property name\r\n             * @type {string}\r\n             */\r\n            this.name = source.name || 'Untitled';\r\n    \r\n            /**\r\n             * 文件体积（字节）\r\n             * @property size\r\n             * @type {uint}\r\n             * @default 0\r\n             */\r\n            this.size = source.size || 0;\r\n    \r\n            /**\r\n             * 文件MIMETYPE类型，与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny)\r\n             * @property type\r\n             * @type {string}\r\n             * @default 'application'\r\n             */\r\n            this.type = source.type || 'application';\r\n    \r\n            /**\r\n             * 文件最后修改日期\r\n             * @property lastModifiedDate\r\n             * @type {int}\r\n             * @default 当前时间戳\r\n             */\r\n            this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1);\r\n    \r\n            /**\r\n             * 文件ID，每个对象具有唯一ID，与文件名无关\r\n             * @property id\r\n             * @type {string}\r\n             */\r\n            this.id = gid();\r\n    \r\n            /**\r\n             * 文件扩展名，通过文件名获取，例如test.png的扩展名为png\r\n             * @property ext\r\n             * @type {string}\r\n             */\r\n            this.ext = rExt.exec( this.name ) ? RegExp.$1 : '';\r\n    \r\n    \r\n            /**\r\n             * 状态文字说明。在不同的status语境下有不同的用途。\r\n             * @property statusText\r\n             * @type {string}\r\n             */\r\n            this.statusText = '';\r\n    \r\n            // 存储文件状态，防止通过属性直接修改\r\n            statusMap[ this.id ] = WUFile.Status.INITED;\r\n    \r\n            this.source = source;\r\n            this.loaded = 0;\r\n    \r\n            this.on( 'error', function( msg ) {\r\n                this.setStatus( WUFile.Status.ERROR, msg );\r\n            });\r\n        }\r\n    \r\n        $.extend( WUFile.prototype, {\r\n    \r\n            /**\r\n             * 设置状态，状态变化时会触发`change`事件。\r\n             * @method setStatus\r\n             * @grammar setStatus( status[, statusText] );\r\n             * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status)\r\n             * @param {String} [statusText=''] 状态说明，常在error时使用，用http, abort,server等来标记是由于什么原因导致文件错误。\r\n             */\r\n            setStatus: function( status, text ) {\r\n    \r\n                var prevStatus = statusMap[ this.id ];\r\n    \r\n                typeof text !== 'undefined' && (this.statusText = text);\r\n    \r\n                if ( status !== prevStatus ) {\r\n                    statusMap[ this.id ] = status;\r\n                    /**\r\n                     * 文件状态变化\r\n                     * @event statuschange\r\n                     */\r\n                    this.trigger( 'statuschange', status, prevStatus );\r\n                }\r\n    \r\n            },\r\n    \r\n            /**\r\n             * 获取文件状态\r\n             * @return {File.Status}\r\n             * @example\r\n                     文件状态具体包括以下几种类型：\r\n                     {\r\n                         // 初始化\r\n                        INITED:     0,\r\n                        // 已入队列\r\n                        QUEUED:     1,\r\n                        // 正在上传\r\n                        PROGRESS:     2,\r\n                        // 上传出错\r\n                        ERROR:         3,\r\n                        // 上传成功\r\n                        COMPLETE:     4,\r\n                        // 上传取消\r\n                        CANCELLED:     5\r\n                    }\r\n             */\r\n            getStatus: function() {\r\n                return statusMap[ this.id ];\r\n            },\r\n    \r\n            /**\r\n             * 获取文件原始信息。\r\n             * @return {*}\r\n             */\r\n            getSource: function() {\r\n                return this.source;\r\n            },\r\n    \r\n            destory: function() {\r\n                delete statusMap[ this.id ];\r\n            }\r\n        });\r\n    \r\n        Mediator.installTo( WUFile.prototype );\r\n    \r\n        /**\r\n         * 文件状态值，具体包括以下几种类型：\r\n         * * `inited` 初始状态\r\n         * * `queued` 已经进入队列, 等待上传\r\n         * * `progress` 上传中\r\n         * * `complete` 上传完成。\r\n         * * `error` 上传出错，可重试\r\n         * * `interrupt` 上传中断，可续传。\r\n         * * `invalid` 文件不合格，不能重试上传。会自动从队列中移除。\r\n         * * `cancelled` 文件被移除。\r\n         * @property {Object} Status\r\n         * @namespace File\r\n         * @class File\r\n         * @static\r\n         */\r\n        WUFile.Status = {\r\n            INITED:     'inited',    // 初始状态\r\n            QUEUED:     'queued',    // 已经进入队列, 等待上传\r\n            PROGRESS:   'progress',    // 上传中\r\n            ERROR:      'error',    // 上传出错，可重试\r\n            COMPLETE:   'complete',    // 上传完成。\r\n            CANCELLED:  'cancelled',    // 上传取消。\r\n            INTERRUPT:  'interrupt',    // 上传中断，可续传。\r\n            INVALID:    'invalid'    // 文件不合格，不能重试上传。\r\n        };\r\n    \r\n        return WUFile;\r\n    });\r\n    \r\n    /**\r\n     * @fileOverview 文件队列\r\n     */\r\n    define('queue',[\r\n        'base',\r\n        'mediator',\r\n        'file'\r\n    ], function( Base, Mediator, WUFile ) {\r\n    \r\n        var $ = Base.$,\r\n            STATUS = WUFile.Status;\r\n    \r\n        /**\r\n         * 文件队列, 用来存储各个状态中的文件。\r\n         * @class Queue\r\n         * @extends Mediator\r\n         */\r\n        function Queue() {\r\n    \r\n            /**\r\n             * 统计文件数。\r\n             * * `numOfQueue` 队列中的文件数。\r\n             * * `numOfSuccess` 上传成功的文件数\r\n             * * `numOfCancel` 被移除的文件数\r\n             * * `numOfProgress` 正在上传中的文件数\r\n             * * `numOfUploadFailed` 上传错误的文件数。\r\n             * * `numOfInvalid` 无效的文件数。\r\n             * @property {Object} stats\r\n             */\r\n            this.stats = {\r\n                numOfQueue: 0,\r\n                numOfSuccess: 0,\r\n                numOfCancel: 0,\r\n                numOfProgress: 0,\r\n                numOfUploadFailed: 0,\r\n                numOfInvalid: 0\r\n            };\r\n    \r\n            // 上传队列，仅包括等待上传的文件\r\n            this._queue = [];\r\n    \r\n            // 存储所有文件\r\n            this._map = {};\r\n        }\r\n    \r\n        $.extend( Queue.prototype, {\r\n    \r\n            /**\r\n             * 将新文件加入对队列尾部\r\n             *\r\n             * @method append\r\n             * @param  {File} file   文件对象\r\n             */\r\n            append: function( file ) {\r\n                this._queue.push( file );\r\n                this._fileAdded( file );\r\n                return this;\r\n            },\r\n    \r\n            /**\r\n             * 将新文件加入对队列头部\r\n             *\r\n             * @method prepend\r\n             * @param  {File} file   文件对象\r\n             */\r\n            prepend: function( file ) {\r\n                this._queue.unshift( file );\r\n                this._fileAdded( file );\r\n                return this;\r\n            },\r\n    \r\n            /**\r\n             * 获取文件对象\r\n             *\r\n             * @method getFile\r\n             * @param  {String} fileId   文件ID\r\n             * @return {File}\r\n             */\r\n            getFile: function( fileId ) {\r\n                if ( typeof fileId !== 'string' ) {\r\n                    return fileId;\r\n                }\r\n                return this._map[ fileId ];\r\n            },\r\n    \r\n            /**\r\n             * 从队列中取出一个指定状态的文件。\r\n             * @grammar fetch( status ) => File\r\n             * @method fetch\r\n             * @param {String} status [文件状态值](#WebUploader:File:File.Status)\r\n             * @return {File} [File](#WebUploader:File)\r\n             */\r\n            fetch: function( status ) {\r\n                var len = this._queue.length,\r\n                    i, file;\r\n    \r\n                status = status || STATUS.QUEUED;\r\n    \r\n                for ( i = 0; i < len; i++ ) {\r\n                    file = this._queue[ i ];\r\n    \r\n                    if ( status === file.getStatus() ) {\r\n                        return file;\r\n                    }\r\n                }\r\n    \r\n                return null;\r\n            },\r\n    \r\n            /**\r\n             * 对队列进行排序，能够控制文件上传顺序。\r\n             * @grammar sort( fn ) => undefined\r\n             * @method sort\r\n             * @param {Function} fn 排序方法\r\n             */\r\n            sort: function( fn ) {\r\n                if ( typeof fn === 'function' ) {\r\n                    this._queue.sort( fn );\r\n                }\r\n            },\r\n    \r\n            /**\r\n             * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。\r\n             * @grammar getFiles( [status1[, status2 ...]] ) => Array\r\n             * @method getFiles\r\n             * @param {String} [status] [文件状态值](#WebUploader:File:File.Status)\r\n             */\r\n            getFiles: function() {\r\n                var sts = [].slice.call( arguments, 0 ),\r\n                    ret = [],\r\n                    i = 0,\r\n                    len = this._queue.length,\r\n                    file;\r\n    \r\n                for ( ; i < len; i++ ) {\r\n                    file = this._queue[ i ];\r\n    \r\n                    if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) {\r\n                        continue;\r\n                    }\r\n    \r\n                    ret.push( file );\r\n                }\r\n    \r\n                return ret;\r\n            },\r\n    \r\n            _fileAdded: function( file ) {\r\n                var me = this,\r\n                    existing = this._map[ file.id ];\r\n    \r\n                if ( !existing ) {\r\n                    this._map[ file.id ] = file;\r\n    \r\n                    file.on( 'statuschange', function( cur, pre ) {\r\n                        me._onFileStatusChange( cur, pre );\r\n                    });\r\n                }\r\n    \r\n                file.setStatus( STATUS.QUEUED );\r\n            },\r\n    \r\n            _onFileStatusChange: function( curStatus, preStatus ) {\r\n                var stats = this.stats;\r\n    \r\n                switch ( preStatus ) {\r\n                    case STATUS.PROGRESS:\r\n                        stats.numOfProgress--;\r\n                        break;\r\n    \r\n                    case STATUS.QUEUED:\r\n                        stats.numOfQueue --;\r\n                        break;\r\n    \r\n                    case STATUS.ERROR:\r\n                        stats.numOfUploadFailed--;\r\n                        break;\r\n    \r\n                    case STATUS.INVALID:\r\n                        stats.numOfInvalid--;\r\n                        break;\r\n                }\r\n    \r\n                switch ( curStatus ) {\r\n                    case STATUS.QUEUED:\r\n                        stats.numOfQueue++;\r\n                        break;\r\n    \r\n                    case STATUS.PROGRESS:\r\n                        stats.numOfProgress++;\r\n                        break;\r\n    \r\n                    case STATUS.ERROR:\r\n                        stats.numOfUploadFailed++;\r\n                        break;\r\n    \r\n                    case STATUS.COMPLETE:\r\n                        stats.numOfSuccess++;\r\n                        break;\r\n    \r\n                    case STATUS.CANCELLED:\r\n                        stats.numOfCancel++;\r\n                        break;\r\n    \r\n                    case STATUS.INVALID:\r\n                        stats.numOfInvalid++;\r\n                        break;\r\n                }\r\n            }\r\n    \r\n        });\r\n    \r\n        Mediator.installTo( Queue.prototype );\r\n    \r\n        return Queue;\r\n    });\r\n    /**\r\n     * @fileOverview 队列\r\n     */\r\n    define('widgets/queue',[\r\n        'base',\r\n        'uploader',\r\n        'queue',\r\n        'file',\r\n        'lib/file',\r\n        'runtime/client',\r\n        'widgets/widget'\r\n    ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) {\r\n    \r\n        var $ = Base.$,\r\n            rExt = /\\.\\w+$/,\r\n            Status = WUFile.Status;\r\n    \r\n        return Uploader.register({\r\n            'sort-files': 'sortFiles',\r\n            'add-file': 'addFiles',\r\n            'get-file': 'getFile',\r\n            'fetch-file': 'fetchFile',\r\n            'get-stats': 'getStats',\r\n            'get-files': 'getFiles',\r\n            'remove-file': 'removeFile',\r\n            'retry': 'retry',\r\n            'reset': 'reset',\r\n            'accept-file': 'acceptFile'\r\n        }, {\r\n    \r\n            init: function( opts ) {\r\n                var me = this,\r\n                    deferred, len, i, item, arr, accept, runtime;\r\n    \r\n                if ( $.isPlainObject( opts.accept ) ) {\r\n                    opts.accept = [ opts.accept ];\r\n                }\r\n    \r\n                // accept中的中生成匹配正则。\r\n                if ( opts.accept ) {\r\n                    arr = [];\r\n    \r\n                    for ( i = 0, len = opts.accept.length; i < len; i++ ) {\r\n                        item = opts.accept[ i ].extensions;\r\n                        item && arr.push( item );\r\n                    }\r\n    \r\n                    if ( arr.length ) {\r\n                        accept = '\\\\.' + arr.join(',')\r\n                                .replace( /,/g, '$|\\\\.' )\r\n                                .replace( /\\*/g, '.*' ) + '$';\r\n                    }\r\n    \r\n                    me.accept = new RegExp( accept, 'i' );\r\n                }\r\n    \r\n                me.queue = new Queue();\r\n                me.stats = me.queue.stats;\r\n    \r\n                // 如果当前不是html5运行时，那就算了。\r\n                // 不执行后续操作\r\n                if ( this.request('predict-runtime-type') !== 'html5' ) {\r\n                    return;\r\n                }\r\n    \r\n                // 创建一个 html5 运行时的 placeholder\r\n                // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。\r\n                deferred = Base.Deferred();\r\n                runtime = new RuntimeClient('Placeholder');\r\n                runtime.connectRuntime({\r\n                    runtimeOrder: 'html5'\r\n                }, function() {\r\n                    me._ruid = runtime.getRuid();\r\n                    deferred.resolve();\r\n                });\r\n                return deferred.promise();\r\n            },\r\n    \r\n    \r\n            // 为了支持外部直接添加一个原生File对象。\r\n            _wrapFile: function( file ) {\r\n                if ( !(file instanceof WUFile) ) {\r\n    \r\n                    if ( !(file instanceof File) ) {\r\n                        if ( !this._ruid ) {\r\n                            throw new Error('Can\\'t add external files.');\r\n                        }\r\n                        file = new File( this._ruid, file );\r\n                    }\r\n    \r\n                    file = new WUFile( file );\r\n                }\r\n    \r\n                return file;\r\n            },\r\n    \r\n            // 判断文件是否可以被加入队列\r\n            acceptFile: function( file ) {\r\n                var invalid = !file || file.size < 6 || this.accept &&\r\n    \r\n                        // 如果名字中有后缀，才做后缀白名单处理。\r\n                        rExt.exec( file.name ) && !this.accept.test( file.name );\r\n    \r\n                return !invalid;\r\n            },\r\n    \r\n    \r\n            /**\r\n             * @event beforeFileQueued\r\n             * @param {File} file File对象\r\n             * @description 当文件被加入队列之前触发，此事件的handler返回值为`false`，则此文件不会被添加进入队列。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * @event fileQueued\r\n             * @param {File} file File对象\r\n             * @description 当文件被加入队列以后触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            _addFile: function( file ) {\r\n                var me = this;\r\n    \r\n                file = me._wrapFile( file );\r\n    \r\n                // 不过类型判断允许不允许，先派送 `beforeFileQueued`\r\n                if ( !me.owner.trigger( 'beforeFileQueued', file ) ) {\r\n                    return;\r\n                }\r\n    \r\n                // 类型不匹配，则派送错误事件，并返回。\r\n                if ( !me.acceptFile( file ) ) {\r\n                    me.owner.trigger( 'error', 'Q_TYPE_DENIED', file );\r\n                    return;\r\n                }\r\n    \r\n                me.queue.append( file );\r\n                me.owner.trigger( 'fileQueued', file );\r\n                return file;\r\n            },\r\n    \r\n            getFile: function( fileId ) {\r\n                return this.queue.getFile( fileId );\r\n            },\r\n    \r\n            /**\r\n             * @event filesQueued\r\n             * @param {File} files 数组，内容为原始File(lib/File）对象。\r\n             * @description 当一批文件添加进队列以后触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * @method addFiles\r\n             * @grammar addFiles( file ) => undefined\r\n             * @grammar addFiles( [file1, file2 ...] ) => undefined\r\n             * @param {Array of File or File} [files] Files 对象 数组\r\n             * @description 添加文件到队列\r\n             * @for  Uploader\r\n             */\r\n            addFiles: function( files ) {\r\n                var me = this;\r\n    \r\n                if ( !files.length ) {\r\n                    files = [ files ];\r\n                }\r\n    \r\n                files = $.map( files, function( file ) {\r\n                    return me._addFile( file );\r\n                });\r\n    \r\n                me.owner.trigger( 'filesQueued', files );\r\n    \r\n                if ( me.options.auto ) {\r\n                    me.request('start-upload');\r\n                }\r\n            },\r\n    \r\n            getStats: function() {\r\n                return this.stats;\r\n            },\r\n    \r\n            /**\r\n             * @event fileDequeued\r\n             * @param {File} file File对象\r\n             * @description 当文件被移除队列后触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * @method removeFile\r\n             * @grammar removeFile( file ) => undefined\r\n             * @grammar removeFile( id ) => undefined\r\n             * @param {File|id} file File对象或这File对象的id\r\n             * @description 移除某一文件。\r\n             * @for  Uploader\r\n             * @example\r\n             *\r\n             * $li.on('click', '.remove-this', function() {\r\n             *     uploader.removeFile( file );\r\n             * })\r\n             */\r\n            removeFile: function( file ) {\r\n                var me = this;\r\n    \r\n                file = file.id ? file : me.queue.getFile( file );\r\n    \r\n                file.setStatus( Status.CANCELLED );\r\n                me.owner.trigger( 'fileDequeued', file );\r\n            },\r\n    \r\n            /**\r\n             * @method getFiles\r\n             * @grammar getFiles() => Array\r\n             * @grammar getFiles( status1, status2, status... ) => Array\r\n             * @description 返回指定状态的文件集合，不传参数将返回所有状态的文件。\r\n             * @for  Uploader\r\n             * @example\r\n             * console.log( uploader.getFiles() );    // => all files\r\n             * console.log( uploader.getFiles('error') )    // => all error files.\r\n             */\r\n            getFiles: function() {\r\n                return this.queue.getFiles.apply( this.queue, arguments );\r\n            },\r\n    \r\n            fetchFile: function() {\r\n                return this.queue.fetch.apply( this.queue, arguments );\r\n            },\r\n    \r\n            /**\r\n             * @method retry\r\n             * @grammar retry() => undefined\r\n             * @grammar retry( file ) => undefined\r\n             * @description 重试上传，重试指定文件，或者从出错的文件开始重新上传。\r\n             * @for  Uploader\r\n             * @example\r\n             * function retry() {\r\n             *     uploader.retry();\r\n             * }\r\n             */\r\n            retry: function( file, noForceStart ) {\r\n                var me = this,\r\n                    files, i, len;\r\n    \r\n                if ( file ) {\r\n                    file = file.id ? file : me.queue.getFile( file );\r\n                    file.setStatus( Status.QUEUED );\r\n                    noForceStart || me.request('start-upload');\r\n                    return;\r\n                }\r\n    \r\n                files = me.queue.getFiles( Status.ERROR );\r\n                i = 0;\r\n                len = files.length;\r\n    \r\n                for ( ; i < len; i++ ) {\r\n                    file = files[ i ];\r\n                    file.setStatus( Status.QUEUED );\r\n                }\r\n    \r\n                me.request('start-upload');\r\n            },\r\n    \r\n            /**\r\n             * @method sort\r\n             * @grammar sort( fn ) => undefined\r\n             * @description 排序队列中的文件，在上传之前调整可以控制上传顺序。\r\n             * @for  Uploader\r\n             */\r\n            sortFiles: function() {\r\n                return this.queue.sort.apply( this.queue, arguments );\r\n            },\r\n    \r\n            /**\r\n             * @method reset\r\n             * @grammar reset() => undefined\r\n             * @description 重置uploader。目前只重置了队列。\r\n             * @for  Uploader\r\n             * @example\r\n             * uploader.reset();\r\n             */\r\n            reset: function() {\r\n                this.queue = new Queue();\r\n                this.stats = this.queue.stats;\r\n            }\r\n        });\r\n    \r\n    });\r\n    /**\r\n     * @fileOverview 添加获取Runtime相关信息的方法。\r\n     */\r\n    define('widgets/runtime',[\r\n        'uploader',\r\n        'runtime/runtime',\r\n        'widgets/widget'\r\n    ], function( Uploader, Runtime ) {\r\n    \r\n        Uploader.support = function() {\r\n            return Runtime.hasRuntime.apply( Runtime, arguments );\r\n        };\r\n    \r\n        return Uploader.register({\r\n            'predict-runtime-type': 'predictRuntmeType'\r\n        }, {\r\n    \r\n            init: function() {\r\n                if ( !this.predictRuntmeType() ) {\r\n                    throw Error('Runtime Error');\r\n                }\r\n            },\r\n    \r\n            /**\r\n             * 预测Uploader将采用哪个`Runtime`\r\n             * @grammar predictRuntmeType() => String\r\n             * @method predictRuntmeType\r\n             * @for  Uploader\r\n             */\r\n            predictRuntmeType: function() {\r\n                var orders = this.options.runtimeOrder || Runtime.orders,\r\n                    type = this.type,\r\n                    i, len;\r\n    \r\n                if ( !type ) {\r\n                    orders = orders.split( /\\s*,\\s*/g );\r\n    \r\n                    for ( i = 0, len = orders.length; i < len; i++ ) {\r\n                        if ( Runtime.hasRuntime( orders[ i ] ) ) {\r\n                            this.type = type = orders[ i ];\r\n                            break;\r\n                        }\r\n                    }\r\n                }\r\n    \r\n                return type;\r\n            }\r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview Transport\r\n     */\r\n    define('lib/transport',[\r\n        'base',\r\n        'runtime/client',\r\n        'mediator'\r\n    ], function( Base, RuntimeClient, Mediator ) {\r\n    \r\n        var $ = Base.$;\r\n    \r\n        function Transport( opts ) {\r\n            var me = this;\r\n    \r\n            opts = me.options = $.extend( true, {}, Transport.options, opts || {} );\r\n            RuntimeClient.call( this, 'Transport' );\r\n    \r\n            this._blob = null;\r\n            this._formData = opts.formData || {};\r\n            this._headers = opts.headers || {};\r\n    \r\n            this.on( 'progress', this._timeout );\r\n            this.on( 'load error', function() {\r\n                me.trigger( 'progress', 1 );\r\n                clearTimeout( me._timer );\r\n            });\r\n        }\r\n    \r\n        Transport.options = {\r\n            server: '',\r\n            method: 'POST',\r\n    \r\n            // 跨域时，是否允许携带cookie, 只有html5 runtime才有效\r\n            withCredentials: false,\r\n            fileVal: 'file',\r\n            timeout: 2 * 60 * 1000,    // 2分钟\r\n            formData: {},\r\n            headers: {},\r\n            sendAsBinary: false\r\n        };\r\n    \r\n        $.extend( Transport.prototype, {\r\n    \r\n            // 添加Blob, 只能添加一次，最后一次有效。\r\n            appendBlob: function( key, blob, filename ) {\r\n                var me = this,\r\n                    opts = me.options;\r\n    \r\n                if ( me.getRuid() ) {\r\n                    me.disconnectRuntime();\r\n                }\r\n    \r\n                // 连接到blob归属的同一个runtime.\r\n                me.connectRuntime( blob.ruid, function() {\r\n                    me.exec('init');\r\n                });\r\n    \r\n                me._blob = blob;\r\n                opts.fileVal = key || opts.fileVal;\r\n                opts.filename = filename || opts.filename;\r\n            },\r\n    \r\n            // 添加其他字段\r\n            append: function( key, value ) {\r\n                if ( typeof key === 'object' ) {\r\n                    $.extend( this._formData, key );\r\n                } else {\r\n                    this._formData[ key ] = value;\r\n                }\r\n            },\r\n    \r\n            setRequestHeader: function( key, value ) {\r\n                if ( typeof key === 'object' ) {\r\n                    $.extend( this._headers, key );\r\n                } else {\r\n                    this._headers[ key ] = value;\r\n                }\r\n            },\r\n    \r\n            send: function( method ) {\r\n                this.exec( 'send', method );\r\n                this._timeout();\r\n            },\r\n    \r\n            abort: function() {\r\n                clearTimeout( this._timer );\r\n                return this.exec('abort');\r\n            },\r\n    \r\n            destroy: function() {\r\n                this.trigger('destroy');\r\n                this.off();\r\n                this.exec('destroy');\r\n                this.disconnectRuntime();\r\n            },\r\n    \r\n            getResponse: function() {\r\n                return this.exec('getResponse');\r\n            },\r\n    \r\n            getResponseAsJson: function() {\r\n                return this.exec('getResponseAsJson');\r\n            },\r\n    \r\n            getStatus: function() {\r\n                return this.exec('getStatus');\r\n            },\r\n    \r\n            _timeout: function() {\r\n                var me = this,\r\n                    duration = me.options.timeout;\r\n    \r\n                if ( !duration ) {\r\n                    return;\r\n                }\r\n    \r\n                clearTimeout( me._timer );\r\n                me._timer = setTimeout(function() {\r\n                    me.abort();\r\n                    me.trigger( 'error', 'timeout' );\r\n                }, duration );\r\n            }\r\n    \r\n        });\r\n    \r\n        // 让Transport具备事件功能。\r\n        Mediator.installTo( Transport.prototype );\r\n    \r\n        return Transport;\r\n    });\r\n    /**\r\n     * @fileOverview 负责文件上传相关。\r\n     */\r\n    define('widgets/upload',[\r\n        'base',\r\n        'uploader',\r\n        'file',\r\n        'lib/transport',\r\n        'widgets/widget'\r\n    ], function( Base, Uploader, WUFile, Transport ) {\r\n    \r\n        var $ = Base.$,\r\n            isPromise = Base.isPromise,\r\n            Status = WUFile.Status;\r\n    \r\n        // 添加默认配置项\r\n        $.extend( Uploader.options, {\r\n    \r\n    \r\n            /**\r\n             * @property {Boolean} [prepareNextFile=false]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 是否允许在文件传输时提前把下一个文件准备好。\r\n             * 对于一个文件的准备工作比较耗时，比如图片压缩，md5序列化。\r\n             * 如果能提前在当前文件传输期处理，可以节省总体耗时。\r\n             */\r\n            prepareNextFile: false,\r\n    \r\n            /**\r\n             * @property {Boolean} [chunked=false]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 是否要分片处理大文件上传。\r\n             */\r\n            chunked: false,\r\n    \r\n            /**\r\n             * @property {Boolean} [chunkSize=5242880]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 如果要分片，分多大一片？ 默认大小为5M.\r\n             */\r\n            chunkSize: 5 * 1024 * 1024,\r\n    \r\n            /**\r\n             * @property {Boolean} [chunkRetry=2]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 如果某个分片由于网络问题出错，允许自动重传多少次？\r\n             */\r\n            chunkRetry: 2,\r\n    \r\n            /**\r\n             * @property {Boolean} [threads=3]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 上传并发数。允许同时最大上传进程数。\r\n             */\r\n            threads: 3,\r\n    \r\n    \r\n            /**\r\n             * @property {Object} [formData]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 文件上传请求的参数表，每次发送都会发送此对象中的参数。\r\n             */\r\n            formData: null\r\n    \r\n            /**\r\n             * @property {Object} [fileVal='file']\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 设置文件上传域的name。\r\n             */\r\n    \r\n            /**\r\n             * @property {Object} [method='POST']\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 文件上传方式，`POST`或者`GET`。\r\n             */\r\n    \r\n            /**\r\n             * @property {Object} [sendAsBinary=false]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 是否已二进制的流的方式发送文件，这样整个上传内容`php://input`都为文件内容，\r\n             * 其他参数在$_GET数组中。\r\n             */\r\n        });\r\n    \r\n        // 负责将文件切片。\r\n        function CuteFile( file, chunkSize ) {\r\n            var pending = [],\r\n                blob = file.source,\r\n                total = blob.size,\r\n                chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1,\r\n                start = 0,\r\n                index = 0,\r\n                len;\r\n    \r\n            while ( index < chunks ) {\r\n                len = Math.min( chunkSize, total - start );\r\n    \r\n                pending.push({\r\n                    file: file,\r\n                    start: start,\r\n                    end: chunkSize ? (start + len) : total,\r\n                    total: total,\r\n                    chunks: chunks,\r\n                    chunk: index++\r\n                });\r\n                start += len;\r\n            }\r\n    \r\n            file.blocks = pending.concat();\r\n            file.remaning = pending.length;\r\n    \r\n            return {\r\n                file: file,\r\n    \r\n                has: function() {\r\n                    return !!pending.length;\r\n                },\r\n    \r\n                fetch: function() {\r\n                    return pending.shift();\r\n                }\r\n            };\r\n        }\r\n    \r\n        Uploader.register({\r\n            'start-upload': 'start',\r\n            'stop-upload': 'stop',\r\n            'skip-file': 'skipFile',\r\n            'is-in-progress': 'isInProgress'\r\n        }, {\r\n    \r\n            init: function() {\r\n                var owner = this.owner;\r\n    \r\n                this.runing = false;\r\n    \r\n                // 记录当前正在传的数据，跟threads相关\r\n                this.pool = [];\r\n    \r\n                // 缓存即将上传的文件。\r\n                this.pending = [];\r\n    \r\n                // 跟踪还有多少分片没有完成上传。\r\n                this.remaning = 0;\r\n                this.__tick = Base.bindFn( this._tick, this );\r\n    \r\n                owner.on( 'uploadComplete', function( file ) {\r\n                    // 把其他块取消了。\r\n                    file.blocks && $.each( file.blocks, function( _, v ) {\r\n                        v.transport && (v.transport.abort(), v.transport.destroy());\r\n                        delete v.transport;\r\n                    });\r\n    \r\n                    delete file.blocks;\r\n                    delete file.remaning;\r\n                });\r\n            },\r\n    \r\n            /**\r\n             * @event startUpload\r\n             * @description 当开始上传流程时触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * 开始上传。此方法可以从初始状态调用开始上传流程，也可以从暂停状态调用，继续上传流程。\r\n             * @grammar upload() => undefined\r\n             * @method upload\r\n             * @for  Uploader\r\n             */\r\n            start: function() {\r\n                var me = this;\r\n    \r\n                // 移出invalid的文件\r\n                $.each( me.request( 'get-files', Status.INVALID ), function() {\r\n                    me.request( 'remove-file', this );\r\n                });\r\n    \r\n                if ( me.runing ) {\r\n                    return;\r\n                }\r\n    \r\n                me.runing = true;\r\n    \r\n                // 如果有暂停的，则续传\r\n                $.each( me.pool, function( _, v ) {\r\n                    var file = v.file;\r\n    \r\n                    if ( file.getStatus() === Status.INTERRUPT ) {\r\n                        file.setStatus( Status.PROGRESS );\r\n                        me._trigged = false;\r\n                        v.transport && v.transport.send();\r\n                    }\r\n                });\r\n    \r\n                me._trigged = false;\r\n                me.owner.trigger('startUpload');\r\n                Base.nextTick( me.__tick );\r\n            },\r\n    \r\n            /**\r\n             * @event stopUpload\r\n             * @description 当开始上传流程暂停时触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。\r\n             * @grammar stop() => undefined\r\n             * @grammar stop( true ) => undefined\r\n             * @method stop\r\n             * @for  Uploader\r\n             */\r\n            stop: function( interrupt ) {\r\n                var me = this;\r\n    \r\n                if ( me.runing === false ) {\r\n                    return;\r\n                }\r\n    \r\n                me.runing = false;\r\n    \r\n                interrupt && $.each( me.pool, function( _, v ) {\r\n                    v.transport && v.transport.abort();\r\n                    v.file.setStatus( Status.INTERRUPT );\r\n                });\r\n    \r\n                me.owner.trigger('stopUpload');\r\n            },\r\n    \r\n            /**\r\n             * 判断`Uplaode`r是否正在上传中。\r\n             * @grammar isInProgress() => Boolean\r\n             * @method isInProgress\r\n             * @for  Uploader\r\n             */\r\n            isInProgress: function() {\r\n                return !!this.runing;\r\n            },\r\n    \r\n            getStats: function() {\r\n                return this.request('get-stats');\r\n            },\r\n    \r\n            /**\r\n             * 掉过一个文件上传，直接标记指定文件为已上传状态。\r\n             * @grammar skipFile( file ) => undefined\r\n             * @method skipFile\r\n             * @for  Uploader\r\n             */\r\n            skipFile: function( file, status ) {\r\n                file = this.request( 'get-file', file );\r\n    \r\n                file.setStatus( status || Status.COMPLETE );\r\n                file.skipped = true;\r\n    \r\n                // 如果正在上传。\r\n                file.blocks && $.each( file.blocks, function( _, v ) {\r\n                    var _tr = v.transport;\r\n    \r\n                    if ( _tr ) {\r\n                        _tr.abort();\r\n                        _tr.destroy();\r\n                        delete v.transport;\r\n                    }\r\n                });\r\n    \r\n                this.owner.trigger( 'uploadSkip', file );\r\n            },\r\n    \r\n            /**\r\n             * @event uploadFinished\r\n             * @description 当所有文件上传结束时触发。\r\n             * @for  Uploader\r\n             */\r\n            _tick: function() {\r\n                var me = this,\r\n                    opts = me.options,\r\n                    fn, val;\r\n    \r\n                // 上一个promise还没有结束，则等待完成后再执行。\r\n                if ( me._promise ) {\r\n                    return me._promise.always( me.__tick );\r\n                }\r\n    \r\n                // 还有位置，且还有文件要处理的话。\r\n                if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) {\r\n                    me._trigged = false;\r\n    \r\n                    fn = function( val ) {\r\n                        me._promise = null;\r\n    \r\n                        // 有可能是reject过来的，所以要检测val的类型。\r\n                        val && val.file && me._startSend( val );\r\n                        Base.nextTick( me.__tick );\r\n                    };\r\n    \r\n                    me._promise = isPromise( val ) ? val.always( fn ) : fn( val );\r\n    \r\n                // 没有要上传的了，且没有正在传输的了。\r\n                } else if ( !me.remaning && !me.getStats().numOfQueue ) {\r\n                    me.runing = false;\r\n    \r\n                    me._trigged || Base.nextTick(function() {\r\n                        me.owner.trigger('uploadFinished');\r\n                    });\r\n                    me._trigged = true;\r\n                }\r\n            },\r\n    \r\n            _nextBlock: function() {\r\n                var me = this,\r\n                    act = me._act,\r\n                    opts = me.options,\r\n                    next, done;\r\n    \r\n                // 如果当前文件还有没有需要传输的，则直接返回剩下的。\r\n                if ( act && act.has() &&\r\n                        act.file.getStatus() === Status.PROGRESS ) {\r\n    \r\n                    // 是否提前准备下一个文件\r\n                    if ( opts.prepareNextFile && !me.pending.length ) {\r\n                        me._prepareNextFile();\r\n                    }\r\n    \r\n                    return act.fetch();\r\n    \r\n                // 否则，如果正在运行，则准备下一个文件，并等待完成后返回下个分片。\r\n                } else if ( me.runing ) {\r\n    \r\n                    // 如果缓存中有，则直接在缓存中取，没有则去queue中取。\r\n                    if ( !me.pending.length && me.getStats().numOfQueue ) {\r\n                        me._prepareNextFile();\r\n                    }\r\n    \r\n                    next = me.pending.shift();\r\n                    done = function( file ) {\r\n                        if ( !file ) {\r\n                            return null;\r\n                        }\r\n    \r\n                        act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 );\r\n                        me._act = act;\r\n                        return act.fetch();\r\n                    };\r\n    \r\n                    // 文件可能还在prepare中，也有可能已经完全准备好了。\r\n                    return isPromise( next ) ?\r\n                            next[ next.pipe ? 'pipe' : 'then']( done ) :\r\n                            done( next );\r\n                }\r\n            },\r\n    \r\n    \r\n            /**\r\n             * @event uploadStart\r\n             * @param {File} file File对象\r\n             * @description 某个文件开始上传前触发，一个文件只会触发一次。\r\n             * @for  Uploader\r\n             */\r\n            _prepareNextFile: function() {\r\n                var me = this,\r\n                    file = me.request('fetch-file'),\r\n                    pending = me.pending,\r\n                    promise;\r\n    \r\n                if ( file ) {\r\n                    promise = me.request( 'before-send-file', file, function() {\r\n    \r\n                        // 有可能文件被skip掉了。文件被skip掉后，状态坑定不是Queued.\r\n                        if ( file.getStatus() === Status.QUEUED ) {\r\n                            me.owner.trigger( 'uploadStart', file );\r\n                            file.setStatus( Status.PROGRESS );\r\n                            return file;\r\n                        }\r\n    \r\n                        return me._finishFile( file );\r\n                    });\r\n    \r\n                    // 如果还在pending中，则替换成文件本身。\r\n                    promise.done(function() {\r\n                        var idx = $.inArray( promise, pending );\r\n    \r\n                        ~idx && pending.splice( idx, 1, file );\r\n                    });\r\n    \r\n                    // befeore-send-file的钩子就有错误发生。\r\n                    promise.fail(function( reason ) {\r\n                        file.setStatus( Status.ERROR, reason );\r\n                        me.owner.trigger( 'uploadError', file, reason );\r\n                        me.owner.trigger( 'uploadComplete', file );\r\n                    });\r\n    \r\n                    pending.push( promise );\r\n                }\r\n            },\r\n    \r\n            // 让出位置了，可以让其他分片开始上传\r\n            _popBlock: function( block ) {\r\n                var idx = $.inArray( block, this.pool );\r\n    \r\n                this.pool.splice( idx, 1 );\r\n                block.file.remaning--;\r\n                this.remaning--;\r\n            },\r\n    \r\n            // 开始上传，可以被掉过。如果promise被reject了，则表示跳过此分片。\r\n            _startSend: function( block ) {\r\n                var me = this,\r\n                    file = block.file,\r\n                    promise;\r\n    \r\n                me.pool.push( block );\r\n                me.remaning++;\r\n    \r\n                // 如果没有分片，则直接使用原始的。\r\n                // 不会丢失content-type信息。\r\n                block.blob = block.chunks === 1 ? file.source :\r\n                        file.source.slice( block.start, block.end );\r\n    \r\n                // hook, 每个分片发送之前可能要做些异步的事情。\r\n                promise = me.request( 'before-send', block, function() {\r\n    \r\n                    // 有可能文件已经上传出错了，所以不需要再传输了。\r\n                    if ( file.getStatus() === Status.PROGRESS ) {\r\n                        me._doSend( block );\r\n                    } else {\r\n                        me._popBlock( block );\r\n                        Base.nextTick( me.__tick );\r\n                    }\r\n                });\r\n    \r\n                // 如果为fail了，则跳过此分片。\r\n                promise.fail(function() {\r\n                    if ( file.remaning === 1 ) {\r\n                        me._finishFile( file ).always(function() {\r\n                            block.percentage = 1;\r\n                            me._popBlock( block );\r\n                            me.owner.trigger( 'uploadComplete', file );\r\n                            Base.nextTick( me.__tick );\r\n                        });\r\n                    } else {\r\n                        block.percentage = 1;\r\n                        me._popBlock( block );\r\n                        Base.nextTick( me.__tick );\r\n                    }\r\n                });\r\n            },\r\n    \r\n    \r\n            /**\r\n             * @event uploadBeforeSend\r\n             * @param {Object} object\r\n             * @param {Object} data 默认的上传参数，可以扩展此对象来控制上传参数。\r\n             * @description 当某个文件的分块在发送前触发，主要用来询问是否要添加附带参数，大文件在开起分片上传的前提下此事件可能会触发多次。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * @event uploadAccept\r\n             * @param {Object} object\r\n             * @param {Object} ret 服务端的返回数据，json格式，如果服务端不是json格式，从ret._raw中取数据，自行解析。\r\n             * @description 当某个文件上传到服务端响应后，会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * @event uploadProgress\r\n             * @param {File} file File对象\r\n             * @param {Number} percentage 上传进度\r\n             * @description 上传过程中触发，携带上传进度。\r\n             * @for  Uploader\r\n             */\r\n    \r\n    \r\n            /**\r\n             * @event uploadError\r\n             * @param {File} file File对象\r\n             * @param {String} reason 出错的code\r\n             * @description 当文件上传出错时触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * @event uploadSuccess\r\n             * @param {File} file File对象\r\n             * @param {Object} response 服务端返回的数据\r\n             * @description 当文件上传成功时触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * @event uploadComplete\r\n             * @param {File} [file] File对象\r\n             * @description 不管成功或者失败，文件上传完成时触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            // 做上传操作。\r\n            _doSend: function( block ) {\r\n                var me = this,\r\n                    owner = me.owner,\r\n                    opts = me.options,\r\n                    file = block.file,\r\n                    tr = new Transport( opts ),\r\n                    data = $.extend({}, opts.formData ),\r\n                    headers = $.extend({}, opts.headers ),\r\n                    requestAccept, ret;\r\n    \r\n                block.transport = tr;\r\n    \r\n                tr.on( 'destroy', function() {\r\n                    delete block.transport;\r\n                    me._popBlock( block );\r\n                    Base.nextTick( me.__tick );\r\n                });\r\n    \r\n                // 广播上传进度。以文件为单位。\r\n                tr.on( 'progress', function( percentage ) {\r\n                    var totalPercent = 0,\r\n                        uploaded = 0;\r\n    \r\n                    // 可能没有abort掉，progress还是执行进来了。\r\n                    // if ( !file.blocks ) {\r\n                    //     return;\r\n                    // }\r\n    \r\n                    totalPercent = block.percentage = percentage;\r\n    \r\n                    if ( block.chunks > 1 ) {    // 计算文件的整体速度。\r\n                        $.each( file.blocks, function( _, v ) {\r\n                            uploaded += (v.percentage || 0) * (v.end - v.start);\r\n                        });\r\n    \r\n                        totalPercent = uploaded / file.size;\r\n                    }\r\n    \r\n                    owner.trigger( 'uploadProgress', file, totalPercent || 0 );\r\n                });\r\n    \r\n                // 用来询问，是否返回的结果是有错误的。\r\n                requestAccept = function( reject ) {\r\n                    var fn;\r\n    \r\n                    ret = tr.getResponseAsJson() || {};\r\n                    ret._raw = tr.getResponse();\r\n                    fn = function( value ) {\r\n                        reject = value;\r\n                    };\r\n    \r\n                    // 服务端响应了，不代表成功了，询问是否响应正确。\r\n                    if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) {\r\n                        reject = reject || 'server';\r\n                    }\r\n    \r\n                    return reject;\r\n                };\r\n    \r\n                // 尝试重试，然后广播文件上传出错。\r\n                tr.on( 'error', function( type, flag ) {\r\n                    block.retried = block.retried || 0;\r\n    \r\n                    // 自动重试\r\n                    if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) &&\r\n                            block.retried < opts.chunkRetry ) {\r\n    \r\n                        block.retried++;\r\n                        tr.send();\r\n    \r\n                    } else {\r\n    \r\n                        // http status 500 ~ 600\r\n                        if ( !flag && type === 'server' ) {\r\n                            type = requestAccept( type );\r\n                        }\r\n    \r\n                        file.setStatus( Status.ERROR, type );\r\n                        owner.trigger( 'uploadError', file, type );\r\n                        owner.trigger( 'uploadComplete', file );\r\n                    }\r\n                });\r\n    \r\n                // 上传成功\r\n                tr.on( 'load', function() {\r\n                    var reason;\r\n    \r\n                    // 如果非预期，转向上传出错。\r\n                    if ( (reason = requestAccept()) ) {\r\n                        tr.trigger( 'error', reason, true );\r\n                        return;\r\n                    }\r\n    \r\n                    // 全部上传完成。\r\n                    if ( file.remaning === 1 ) {\r\n                        me._finishFile( file, ret );\r\n                    } else {\r\n                        tr.destroy();\r\n                    }\r\n                });\r\n    \r\n                // 配置默认的上传字段。\r\n                data = $.extend( data, {\r\n                    id: file.id,\r\n                    name: file.name,\r\n                    type: file.type,\r\n                    lastModifiedDate: file.lastModifiedDate,\r\n                    size: file.size\r\n                });\r\n    \r\n                block.chunks > 1 && $.extend( data, {\r\n                    chunks: block.chunks,\r\n                    chunk: block.chunk\r\n                });\r\n    \r\n                // 在发送之间可以添加字段什么的。。。\r\n                // 如果默认的字段不够使用，可以通过监听此事件来扩展\r\n                owner.trigger( 'uploadBeforeSend', block, data, headers );\r\n    \r\n                // 开始发送。\r\n                tr.appendBlob( opts.fileVal, block.blob, file.name );\r\n                tr.append( data );\r\n                tr.setRequestHeader( headers );\r\n                tr.send();\r\n            },\r\n    \r\n            // 完成上传。\r\n            _finishFile: function( file, ret, hds ) {\r\n                var owner = this.owner;\r\n    \r\n                return owner\r\n                        .request( 'after-send-file', arguments, function() {\r\n                            file.setStatus( Status.COMPLETE );\r\n                            owner.trigger( 'uploadSuccess', file, ret, hds );\r\n                        })\r\n                        .fail(function( reason ) {\r\n    \r\n                            // 如果外部已经标记为invalid什么的，不再改状态。\r\n                            if ( file.getStatus() === Status.PROGRESS ) {\r\n                                file.setStatus( Status.ERROR, reason );\r\n                            }\r\n    \r\n                            owner.trigger( 'uploadError', file, reason );\r\n                        })\r\n                        .always(function() {\r\n                            owner.trigger( 'uploadComplete', file );\r\n                        });\r\n            }\r\n    \r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview 各种验证，包括文件总大小是否超出、单文件是否超出和文件是否重复。\r\n     */\r\n    \r\n    define('widgets/validator',[\r\n        'base',\r\n        'uploader',\r\n        'file',\r\n        'widgets/widget'\r\n    ], function( Base, Uploader, WUFile ) {\r\n    \r\n        var $ = Base.$,\r\n            validators = {},\r\n            api;\r\n    \r\n        /**\r\n         * @event error\r\n         * @param {String} type 错误类型。\r\n         * @description 当validate不通过时，会以派送错误事件的形式通知调用者。通过`upload.on('error', handler)`可以捕获到此类错误，目前有以下错误会在特定的情况下派送错来。\r\n         *\r\n         * * `Q_EXCEED_NUM_LIMIT` 在设置了`fileNumLimit`且尝试给`uploader`添加的文件数量超出这个值时派送。\r\n         * * `Q_EXCEED_SIZE_LIMIT` 在设置了`Q_EXCEED_SIZE_LIMIT`且尝试给`uploader`添加的文件总大小超出这个值时派送。\r\n         * @for  Uploader\r\n         */\r\n    \r\n        // 暴露给外面的api\r\n        api = {\r\n    \r\n            // 添加验证器\r\n            addValidator: function( type, cb ) {\r\n                validators[ type ] = cb;\r\n            },\r\n    \r\n            // 移除验证器\r\n            removeValidator: function( type ) {\r\n                delete validators[ type ];\r\n            }\r\n        };\r\n    \r\n        // 在Uploader初始化的时候启动Validators的初始化\r\n        Uploader.register({\r\n            init: function() {\r\n                var me = this;\r\n                $.each( validators, function() {\r\n                    this.call( me.owner );\r\n                });\r\n            }\r\n        });\r\n    \r\n        /**\r\n         * @property {int} [fileNumLimit=undefined]\r\n         * @namespace options\r\n         * @for Uploader\r\n         * @description 验证文件总数量, 超出则不允许加入队列。\r\n         */\r\n        api.addValidator( 'fileNumLimit', function() {\r\n            var uploader = this,\r\n                opts = uploader.options,\r\n                count = 0,\r\n                max = opts.fileNumLimit >> 0,\r\n                flag = true;\r\n    \r\n            if ( !max ) {\r\n                return;\r\n            }\r\n    \r\n            uploader.on( 'beforeFileQueued', function( file ) {\r\n    \r\n                if ( count >= max && flag ) {\r\n                    flag = false;\r\n                    this.trigger( 'error', 'Q_EXCEED_NUM_LIMIT', max, file );\r\n                    setTimeout(function() {\r\n                        flag = true;\r\n                    }, 1 );\r\n                }\r\n    \r\n                return count >= max ? false : true;\r\n            });\r\n    \r\n            uploader.on( 'fileQueued', function() {\r\n                count++;\r\n            });\r\n    \r\n            uploader.on( 'fileDequeued', function() {\r\n                count--;\r\n            });\r\n    \r\n            uploader.on( 'uploadFinished', function() {\r\n                count = 0;\r\n            });\r\n        });\r\n    \r\n    \r\n        /**\r\n         * @property {int} [fileSizeLimit=undefined]\r\n         * @namespace options\r\n         * @for Uploader\r\n         * @description 验证文件总大小是否超出限制, 超出则不允许加入队列。\r\n         */\r\n        api.addValidator( 'fileSizeLimit', function() {\r\n            var uploader = this,\r\n                opts = uploader.options,\r\n                count = 0,\r\n                max = opts.fileSizeLimit >> 0,\r\n                flag = true;\r\n    \r\n            if ( !max ) {\r\n                return;\r\n            }\r\n    \r\n            uploader.on( 'beforeFileQueued', function( file ) {\r\n                var invalid = count + file.size > max;\r\n    \r\n                if ( invalid && flag ) {\r\n                    flag = false;\r\n                    this.trigger( 'error', 'Q_EXCEED_SIZE_LIMIT', max, file );\r\n                    setTimeout(function() {\r\n                        flag = true;\r\n                    }, 1 );\r\n                }\r\n    \r\n                return invalid ? false : true;\r\n            });\r\n    \r\n            uploader.on( 'fileQueued', function( file ) {\r\n                count += file.size;\r\n            });\r\n    \r\n            uploader.on( 'fileDequeued', function( file ) {\r\n                count -= file.size;\r\n            });\r\n    \r\n            uploader.on( 'uploadFinished', function() {\r\n                count = 0;\r\n            });\r\n        });\r\n    \r\n        /**\r\n         * @property {int} [fileSingleSizeLimit=undefined]\r\n         * @namespace options\r\n         * @for Uploader\r\n         * @description 验证单个文件大小是否超出限制, 超出则不允许加入队列。\r\n         */\r\n        api.addValidator( 'fileSingleSizeLimit', function() {\r\n            var uploader = this,\r\n                opts = uploader.options,\r\n                max = opts.fileSingleSizeLimit;\r\n    \r\n            if ( !max ) {\r\n                return;\r\n            }\r\n    \r\n            uploader.on( 'beforeFileQueued', function( file ) {\r\n    \r\n                if ( file.size > max ) {\r\n                    file.setStatus( WUFile.Status.INVALID, 'exceed_size' );\r\n                    this.trigger( 'error', 'F_EXCEED_SIZE', file );\r\n                    return false;\r\n                }\r\n    \r\n            });\r\n    \r\n        });\r\n    \r\n        /**\r\n         * @property {int} [duplicate=undefined]\r\n         * @namespace options\r\n         * @for Uploader\r\n         * @description 去重， 根据文件名字、文件大小和最后修改时间来生成hash Key.\r\n         */\r\n        api.addValidator( 'duplicate', function() {\r\n            var uploader = this,\r\n                opts = uploader.options,\r\n                mapping = {};\r\n    \r\n            if ( opts.duplicate ) {\r\n                return;\r\n            }\r\n    \r\n            function hashString( str ) {\r\n                var hash = 0,\r\n                    i = 0,\r\n                    len = str.length,\r\n                    _char;\r\n    \r\n                for ( ; i < len; i++ ) {\r\n                    _char = str.charCodeAt( i );\r\n                    hash = _char + (hash << 6) + (hash << 16) - hash;\r\n                }\r\n    \r\n                return hash;\r\n            }\r\n    \r\n            uploader.on( 'beforeFileQueued', function( file ) {\r\n                var hash = file.__hash || (file.__hash = hashString( file.name +\r\n                        file.size + file.lastModifiedDate ));\r\n    \r\n                // 已经重复了\r\n                if ( mapping[ hash ] ) {\r\n                    this.trigger( 'error', 'F_DUPLICATE', file );\r\n                    return false;\r\n                }\r\n            });\r\n    \r\n            uploader.on( 'fileQueued', function( file ) {\r\n                var hash = file.__hash;\r\n    \r\n                hash && (mapping[ hash ] = true);\r\n            });\r\n    \r\n            uploader.on( 'fileDequeued', function( file ) {\r\n                var hash = file.__hash;\r\n    \r\n                hash && (delete mapping[ hash ]);\r\n            });\r\n        });\r\n    \r\n        return api;\r\n    });\r\n    \r\n    /**\r\n     * @fileOverview Runtime管理器，负责Runtime的选择, 连接\r\n     */\r\n    define('runtime/compbase',[],function() {\r\n    \r\n        function CompBase( owner, runtime ) {\r\n    \r\n            this.owner = owner;\r\n            this.options = owner.options;\r\n    \r\n            this.getRuntime = function() {\r\n                return runtime;\r\n            };\r\n    \r\n            this.getRuid = function() {\r\n                return runtime.uid;\r\n            };\r\n    \r\n            this.trigger = function() {\r\n                return owner.trigger.apply( owner, arguments );\r\n            };\r\n        }\r\n    \r\n        return CompBase;\r\n    });\r\n    /**\r\n     * @fileOverview Html5Runtime\r\n     */\r\n    define('runtime/html5/runtime',[\r\n        'base',\r\n        'runtime/runtime',\r\n        'runtime/compbase'\r\n    ], function( Base, Runtime, CompBase ) {\r\n    \r\n        var type = 'html5',\r\n            components = {};\r\n    \r\n        function Html5Runtime() {\r\n            var pool = {},\r\n                me = this,\r\n                destory = this.destory;\r\n    \r\n            Runtime.apply( me, arguments );\r\n            me.type = type;\r\n    \r\n    \r\n            // 这个方法的调用者，实际上是RuntimeClient\r\n            me.exec = function( comp, fn/*, args...*/) {\r\n                var client = this,\r\n                    uid = client.uid,\r\n                    args = Base.slice( arguments, 2 ),\r\n                    instance;\r\n    \r\n                if ( components[ comp ] ) {\r\n                    instance = pool[ uid ] = pool[ uid ] ||\r\n                            new components[ comp ]( client, me );\r\n    \r\n                    if ( instance[ fn ] ) {\r\n                        return instance[ fn ].apply( instance, args );\r\n                    }\r\n                }\r\n            };\r\n    \r\n            me.destory = function() {\r\n                // @todo 删除池子中的所有实例\r\n                return destory && destory.apply( this, arguments );\r\n            };\r\n        }\r\n    \r\n        Base.inherits( Runtime, {\r\n            constructor: Html5Runtime,\r\n    \r\n            // 不需要连接其他程序，直接执行callback\r\n            init: function() {\r\n                var me = this;\r\n                setTimeout(function() {\r\n                    me.trigger('ready');\r\n                }, 1 );\r\n            }\r\n    \r\n        });\r\n    \r\n        // 注册Components\r\n        Html5Runtime.register = function( name, component ) {\r\n            var klass = components[ name ] = Base.inherits( CompBase, component );\r\n            return klass;\r\n        };\r\n    \r\n        // 注册html5运行时。\r\n        // 只有在支持的前提下注册。\r\n        if ( window.Blob && window.FileReader && window.DataView ) {\r\n            Runtime.addRuntime( type, Html5Runtime );\r\n        }\r\n    \r\n        return Html5Runtime;\r\n    });\r\n    /**\r\n     * @fileOverview Blob Html实现\r\n     */\r\n    define('runtime/html5/blob',[\r\n        'runtime/html5/runtime',\r\n        'lib/blob'\r\n    ], function( Html5Runtime, Blob ) {\r\n    \r\n        return Html5Runtime.register( 'Blob', {\r\n            slice: function( start, end ) {\r\n                var blob = this.owner.source,\r\n                    slice = blob.slice || blob.webkitSlice || blob.mozSlice;\r\n    \r\n                blob = slice.call( blob, start, end );\r\n    \r\n                return new Blob( this.getRuid(), blob );\r\n            }\r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview FilePaste\r\n     */\r\n    define('runtime/html5/dnd',[\r\n        'base',\r\n        'runtime/html5/runtime',\r\n        'lib/file'\r\n    ], function( Base, Html5Runtime, File ) {\r\n    \r\n        var $ = Base.$,\r\n            prefix = 'webuploader-dnd-';\r\n    \r\n        return Html5Runtime.register( 'DragAndDrop', {\r\n            init: function() {\r\n                var elem = this.elem = this.options.container;\r\n    \r\n                this.dragEnterHandler = Base.bindFn( this._dragEnterHandler, this );\r\n                this.dragOverHandler = Base.bindFn( this._dragOverHandler, this );\r\n                this.dragLeaveHandler = Base.bindFn( this._dragLeaveHandler, this );\r\n                this.dropHandler = Base.bindFn( this._dropHandler, this );\r\n                this.dndOver = false;\r\n    \r\n                elem.on( 'dragenter', this.dragEnterHandler );\r\n                elem.on( 'dragover', this.dragOverHandler );\r\n                elem.on( 'dragleave', this.dragLeaveHandler );\r\n                elem.on( 'drop', this.dropHandler );\r\n    \r\n                if ( this.options.disableGlobalDnd ) {\r\n                    $( document ).on( 'dragover', this.dragOverHandler );\r\n                    $( document ).on( 'drop', this.dropHandler );\r\n                }\r\n            },\r\n    \r\n            _dragEnterHandler: function( e ) {\r\n                var me = this,\r\n                    denied = me._denied || false,\r\n                    items;\r\n    \r\n                e = e.originalEvent || e;\r\n    \r\n                if ( !me.dndOver ) {\r\n                    me.dndOver = true;\r\n    \r\n                    // 注意只有 chrome 支持。\r\n                    items = e.dataTransfer.items;\r\n    \r\n                    if ( items && items.length ) {\r\n                        me._denied = denied = !me.trigger( 'accept', items );\r\n                    }\r\n    \r\n                    me.elem.addClass( prefix + 'over' );\r\n                    me.elem[ denied ? 'addClass' :\r\n                            'removeClass' ]( prefix + 'denied' );\r\n                }\r\n    \r\n    \r\n                e.dataTransfer.dropEffect = denied ? 'none' : 'copy';\r\n    \r\n                return false;\r\n            },\r\n    \r\n            _dragOverHandler: function( e ) {\r\n                // 只处理框内的。\r\n                var parentElem = this.elem.parent().get( 0 );\r\n                if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) {\r\n                    return false;\r\n                }\r\n    \r\n                clearTimeout( this._leaveTimer );\r\n                this._dragEnterHandler.call( this, e );\r\n    \r\n                return false;\r\n            },\r\n    \r\n            _dragLeaveHandler: function() {\r\n                var me = this,\r\n                    handler;\r\n    \r\n                handler = function() {\r\n                    me.dndOver = false;\r\n                    me.elem.removeClass( prefix + 'over ' + prefix + 'denied' );\r\n                };\r\n    \r\n                clearTimeout( me._leaveTimer );\r\n                me._leaveTimer = setTimeout( handler, 100 );\r\n                return false;\r\n            },\r\n    \r\n            _dropHandler: function( e ) {\r\n                var me = this,\r\n                    ruid = me.getRuid(),\r\n                    parentElem = me.elem.parent().get( 0 );\r\n    \r\n                // 只处理框内的。\r\n                if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) {\r\n                    return false;\r\n                }\r\n    \r\n                me._getTansferFiles( e, function( results ) {\r\n                    me.trigger( 'drop', $.map( results, function( file ) {\r\n                        return new File( ruid, file );\r\n                    }) );\r\n                });\r\n    \r\n                me.dndOver = false;\r\n                me.elem.removeClass( prefix + 'over' );\r\n                return false;\r\n            },\r\n    \r\n            // 如果传入 callback 则去查看文件夹，否则只管当前文件夹。\r\n            _getTansferFiles: function( e, callback ) {\r\n                var results  = [],\r\n                    promises = [],\r\n                    items, files, dataTransfer, file, item, i, len, canAccessFolder;\r\n    \r\n                e = e.originalEvent || e;\r\n    \r\n                dataTransfer = e.dataTransfer;\r\n                items = dataTransfer.items;\r\n                files = dataTransfer.files;\r\n    \r\n                canAccessFolder = !!(items && items[ 0 ].webkitGetAsEntry);\r\n    \r\n                for ( i = 0, len = files.length; i < len; i++ ) {\r\n                    file = files[ i ];\r\n                    item = items && items[ i ];\r\n    \r\n                    if ( canAccessFolder && item.webkitGetAsEntry().isDirectory ) {\r\n    \r\n                        promises.push( this._traverseDirectoryTree(\r\n                                item.webkitGetAsEntry(), results ) );\r\n                    } else {\r\n                        results.push( file );\r\n                    }\r\n                }\r\n    \r\n                Base.when.apply( Base, promises ).done(function() {\r\n    \r\n                    if ( !results.length ) {\r\n                        return;\r\n                    }\r\n    \r\n                    callback( results );\r\n                });\r\n            },\r\n    \r\n            _traverseDirectoryTree: function( entry, results ) {\r\n                var deferred = Base.Deferred(),\r\n                    me = this;\r\n    \r\n                if ( entry.isFile ) {\r\n                    entry.file(function( file ) {\r\n                        results.push( file );\r\n                        deferred.resolve();\r\n                    });\r\n                } else if ( entry.isDirectory ) {\r\n                    entry.createReader().readEntries(function( entries ) {\r\n                        var len = entries.length,\r\n                            promises = [],\r\n                            arr = [],    // 为了保证顺序。\r\n                            i;\r\n    \r\n                        for ( i = 0; i < len; i++ ) {\r\n                            promises.push( me._traverseDirectoryTree(\r\n                                    entries[ i ], arr ) );\r\n                        }\r\n    \r\n                        Base.when.apply( Base, promises ).then(function() {\r\n                            results.push.apply( results, arr );\r\n                            deferred.resolve();\r\n                        }, deferred.reject );\r\n                    });\r\n                }\r\n    \r\n                return deferred.promise();\r\n            },\r\n    \r\n            destroy: function() {\r\n                var elem = this.elem;\r\n    \r\n                elem.off( 'dragenter', this.dragEnterHandler );\r\n                elem.off( 'dragover', this.dragEnterHandler );\r\n                elem.off( 'dragleave', this.dragLeaveHandler );\r\n                elem.off( 'drop', this.dropHandler );\r\n    \r\n                if ( this.options.disableGlobalDnd ) {\r\n                    $( document ).off( 'dragover', this.dragOverHandler );\r\n                    $( document ).off( 'drop', this.dropHandler );\r\n                }\r\n            }\r\n        });\r\n    });\r\n    \r\n    /**\r\n     * @fileOverview FilePaste\r\n     */\r\n    define('runtime/html5/filepaste',[\r\n        'base',\r\n        'runtime/html5/runtime',\r\n        'lib/file'\r\n    ], function( Base, Html5Runtime, File ) {\r\n    \r\n        return Html5Runtime.register( 'FilePaste', {\r\n            init: function() {\r\n                var opts = this.options,\r\n                    elem = this.elem = opts.container,\r\n                    accept = '.*',\r\n                    arr, i, len, item;\r\n    \r\n                // accetp的mimeTypes中生成匹配正则。\r\n                if ( opts.accept ) {\r\n                    arr = [];\r\n    \r\n                    for ( i = 0, len = opts.accept.length; i < len; i++ ) {\r\n                        item = opts.accept[ i ].mimeTypes;\r\n                        item && arr.push( item );\r\n                    }\r\n    \r\n                    if ( arr.length ) {\r\n                        accept = arr.join(',');\r\n                        accept = accept.replace( /,/g, '|' ).replace( /\\*/g, '.*' );\r\n                    }\r\n                }\r\n                this.accept = accept = new RegExp( accept, 'i' );\r\n                this.hander = Base.bindFn( this._pasteHander, this );\r\n                elem.on( 'paste', this.hander );\r\n            },\r\n    \r\n            _pasteHander: function( e ) {\r\n                var allowed = [],\r\n                    ruid = this.getRuid(),\r\n                    items, item, blob, i, len;\r\n    \r\n                e = e.originalEvent || e;\r\n                items = e.clipboardData.items;\r\n    \r\n                for ( i = 0, len = items.length; i < len; i++ ) {\r\n                    item = items[ i ];\r\n    \r\n                    if ( item.kind !== 'file' || !(blob = item.getAsFile()) ) {\r\n                        continue;\r\n                    }\r\n    \r\n                    allowed.push( new File( ruid, blob ) );\r\n                }\r\n    \r\n                if ( allowed.length ) {\r\n                    // 不阻止非文件粘贴（文字粘贴）的事件冒泡\r\n                    e.preventDefault();\r\n                    e.stopPropagation();\r\n                    this.trigger( 'paste', allowed );\r\n                }\r\n            },\r\n    \r\n            destroy: function() {\r\n                this.elem.off( 'paste', this.hander );\r\n            }\r\n        });\r\n    });\r\n    \r\n    /**\r\n     * @fileOverview FilePicker\r\n     */\r\n    define('runtime/html5/filepicker',[\r\n        'base',\r\n        'runtime/html5/runtime'\r\n    ], function( Base, Html5Runtime ) {\r\n    \r\n        var $ = Base.$;\r\n    \r\n        return Html5Runtime.register( 'FilePicker', {\r\n            init: function() {\r\n                var container = this.getRuntime().getContainer(),\r\n                    me = this,\r\n                    owner = me.owner,\r\n                    opts = me.options,\r\n                    lable = $( document.createElement('label') ),\r\n                    input = $( document.createElement('input') ),\r\n                    arr, i, len, mouseHandler;\r\n    \r\n                input.attr( 'type', 'file' );\r\n                input.attr( 'name', opts.name );\r\n                input.addClass('webuploader-element-invisible');\r\n    \r\n                lable.on( 'click', function() {\r\n                    input.trigger('click');\r\n                });\r\n    \r\n                lable.css({\r\n                    opacity: 0,\r\n                    width: '100%',\r\n                    height: '100%',\r\n                    display: 'block',\r\n                    cursor: 'pointer',\r\n                    background: '#ffffff'\r\n                });\r\n    \r\n                if ( opts.multiple ) {\r\n                    input.attr( 'multiple', 'multiple' );\r\n                }\r\n    \r\n                // @todo Firefox不支持单独指定后缀\r\n                if ( opts.accept && opts.accept.length > 0 ) {\r\n                    arr = [];\r\n    \r\n                    for ( i = 0, len = opts.accept.length; i < len; i++ ) {\r\n                        arr.push( opts.accept[ i ].mimeTypes );\r\n                    }\r\n    \r\n                    input.attr( 'accept', arr.join(',') );\r\n                }\r\n    \r\n                container.append( input );\r\n                container.append( lable );\r\n    \r\n                mouseHandler = function( e ) {\r\n                    owner.trigger( e.type );\r\n                };\r\n    \r\n                input.on( 'change', function( e ) {\r\n                    var fn = arguments.callee,\r\n                        clone;\r\n    \r\n                    me.files = e.target.files;\r\n    \r\n                    // reset input\r\n                    clone = this.cloneNode( true );\r\n                    this.parentNode.replaceChild( clone, this );\r\n    \r\n                    input.off();\r\n                    input = $( clone ).on( 'change', fn )\r\n                            .on( 'mouseenter mouseleave', mouseHandler );\r\n    \r\n                    owner.trigger('change');\r\n                });\r\n    \r\n                lable.on( 'mouseenter mouseleave', mouseHandler );\r\n    \r\n            },\r\n    \r\n    \r\n            getFiles: function() {\r\n                return this.files;\r\n            },\r\n    \r\n            destroy: function() {\r\n                // todo\r\n            }\r\n        });\r\n    });\r\n    /**\r\n     * Terms:\r\n     *\r\n     * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer\r\n     * @fileOverview Image控件\r\n     */\r\n    define('runtime/html5/util',[\r\n        'base'\r\n    ], function( Base ) {\r\n    \r\n        var urlAPI = window.createObjectURL && window ||\r\n                window.URL && URL.revokeObjectURL && URL ||\r\n                window.webkitURL,\r\n            createObjectURL = Base.noop,\r\n            revokeObjectURL = createObjectURL;\r\n    \r\n        if ( urlAPI ) {\r\n    \r\n            // 更安全的方式调用，比如android里面就能把context改成其他的对象。\r\n            createObjectURL = function() {\r\n                return urlAPI.createObjectURL.apply( urlAPI, arguments );\r\n            };\r\n    \r\n            revokeObjectURL = function() {\r\n                return urlAPI.revokeObjectURL.apply( urlAPI, arguments );\r\n            };\r\n        }\r\n    \r\n        return {\r\n            createObjectURL: createObjectURL,\r\n            revokeObjectURL: revokeObjectURL,\r\n    \r\n            dataURL2Blob: function( dataURI ) {\r\n                var byteStr, intArray, ab, i, mimetype, parts;\r\n    \r\n                parts = dataURI.split(',');\r\n    \r\n                if ( ~parts[ 0 ].indexOf('base64') ) {\r\n                    byteStr = atob( parts[ 1 ] );\r\n                } else {\r\n                    byteStr = decodeURIComponent( parts[ 1 ] );\r\n                }\r\n    \r\n                ab = new ArrayBuffer( byteStr.length );\r\n                intArray = new Uint8Array( ab );\r\n    \r\n                for ( i = 0; i < byteStr.length; i++ ) {\r\n                    intArray[ i ] = byteStr.charCodeAt( i );\r\n                }\r\n    \r\n                mimetype = parts[ 0 ].split(':')[ 1 ].split(';')[ 0 ];\r\n    \r\n                return this.arrayBufferToBlob( ab, mimetype );\r\n            },\r\n    \r\n            dataURL2ArrayBuffer: function( dataURI ) {\r\n                var byteStr, intArray, i, parts;\r\n    \r\n                parts = dataURI.split(',');\r\n    \r\n                if ( ~parts[ 0 ].indexOf('base64') ) {\r\n                    byteStr = atob( parts[ 1 ] );\r\n                } else {\r\n                    byteStr = decodeURIComponent( parts[ 1 ] );\r\n                }\r\n    \r\n                intArray = new Uint8Array( byteStr.length );\r\n    \r\n                for ( i = 0; i < byteStr.length; i++ ) {\r\n                    intArray[ i ] = byteStr.charCodeAt( i );\r\n                }\r\n    \r\n                return intArray.buffer;\r\n            },\r\n    \r\n            arrayBufferToBlob: function( buffer, type ) {\r\n                var builder = window.BlobBuilder || window.WebKitBlobBuilder,\r\n                    bb;\r\n    \r\n                // android不支持直接new Blob, 只能借助blobbuilder.\r\n                if ( builder ) {\r\n                    bb = new builder();\r\n                    bb.append( buffer );\r\n                    return bb.getBlob( type );\r\n                }\r\n    \r\n                return new Blob([ buffer ], type ? { type: type } : {} );\r\n            },\r\n    \r\n            // 抽出来主要是为了解决android下面canvas.toDataUrl不支持jpeg.\r\n            // 你得到的结果是png.\r\n            canvasToDataUrl: function( canvas, type, quality ) {\r\n                return canvas.toDataURL( type, quality / 100 );\r\n            },\r\n    \r\n            // imagemeat会复写这个方法，如果用户选择加载那个文件了的话。\r\n            parseMeta: function( blob, callback ) {\r\n                callback( false, {});\r\n            },\r\n    \r\n            // imagemeat会复写这个方法，如果用户选择加载那个文件了的话。\r\n            updateImageHead: function( data ) {\r\n                return data;\r\n            }\r\n        };\r\n    });\r\n    /**\r\n     * Terms:\r\n     *\r\n     * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer\r\n     * @fileOverview Image控件\r\n     */\r\n    define('runtime/html5/imagemeta',[\r\n        'runtime/html5/util'\r\n    ], function( Util ) {\r\n    \r\n        var api;\r\n    \r\n        api = {\r\n            parsers: {\r\n                0xffe1: []\r\n            },\r\n    \r\n            maxMetaDataSize: 262144,\r\n    \r\n            parse: function( blob, cb ) {\r\n                var me = this,\r\n                    fr = new FileReader();\r\n    \r\n                fr.onload = function() {\r\n                    cb( false, me._parse( this.result ) );\r\n                    fr = fr.onload = fr.onerror = null;\r\n                };\r\n    \r\n                fr.onerror = function( e ) {\r\n                    cb( e.message );\r\n                    fr = fr.onload = fr.onerror = null;\r\n                };\r\n    \r\n                blob = blob.slice( 0, me.maxMetaDataSize );\r\n                fr.readAsArrayBuffer( blob.getSource() );\r\n            },\r\n    \r\n            _parse: function( buffer, noParse ) {\r\n                if ( buffer.byteLength < 6 ) {\r\n                    return;\r\n                }\r\n    \r\n                var dataview = new DataView( buffer ),\r\n                    offset = 2,\r\n                    maxOffset = dataview.byteLength - 4,\r\n                    headLength = offset,\r\n                    ret = {},\r\n                    markerBytes, markerLength, parsers, i;\r\n    \r\n                if ( dataview.getUint16( 0 ) === 0xffd8 ) {\r\n    \r\n                    while ( offset < maxOffset ) {\r\n                        markerBytes = dataview.getUint16( offset );\r\n    \r\n                        if ( markerBytes >= 0xffe0 && markerBytes <= 0xffef ||\r\n                                markerBytes === 0xfffe ) {\r\n    \r\n                            markerLength = dataview.getUint16( offset + 2 ) + 2;\r\n    \r\n                            if ( offset + markerLength > dataview.byteLength ) {\r\n                                break;\r\n                            }\r\n    \r\n                            parsers = api.parsers[ markerBytes ];\r\n    \r\n                            if ( !noParse && parsers ) {\r\n                                for ( i = 0; i < parsers.length; i += 1 ) {\r\n                                    parsers[ i ].call( api, dataview, offset,\r\n                                            markerLength, ret );\r\n                                }\r\n                            }\r\n    \r\n                            offset += markerLength;\r\n                            headLength = offset;\r\n                        } else {\r\n                            break;\r\n                        }\r\n                    }\r\n    \r\n                    if ( headLength > 6 ) {\r\n                        if ( buffer.slice ) {\r\n                            ret.imageHead = buffer.slice( 2, headLength );\r\n                        } else {\r\n                            // Workaround for IE10, which does not yet\r\n                            // support ArrayBuffer.slice:\r\n                            ret.imageHead = new Uint8Array( buffer )\r\n                                    .subarray( 2, headLength );\r\n                        }\r\n                    }\r\n                }\r\n    \r\n                return ret;\r\n            },\r\n    \r\n            updateImageHead: function( buffer, head ) {\r\n                var data = this._parse( buffer, true ),\r\n                    buf1, buf2, bodyoffset;\r\n    \r\n    \r\n                bodyoffset = 2;\r\n                if ( data.imageHead ) {\r\n                    bodyoffset = 2 + data.imageHead.byteLength;\r\n                }\r\n    \r\n                if ( buffer.slice ) {\r\n                    buf2 = buffer.slice( bodyoffset );\r\n                } else {\r\n                    buf2 = new Uint8Array( buffer ).subarray( bodyoffset );\r\n                }\r\n    \r\n                buf1 = new Uint8Array( head.byteLength + 2 + buf2.byteLength );\r\n    \r\n                buf1[ 0 ] = 0xFF;\r\n                buf1[ 1 ] = 0xD8;\r\n                buf1.set( new Uint8Array( head ), 2 );\r\n                buf1.set( new Uint8Array( buf2 ), head.byteLength + 2 );\r\n    \r\n                return buf1.buffer;\r\n            }\r\n        };\r\n    \r\n        Util.parseMeta = function() {\r\n            return api.parse.apply( api, arguments );\r\n        };\r\n    \r\n        Util.updateImageHead = function() {\r\n            return api.updateImageHead.apply( api, arguments );\r\n        };\r\n    \r\n        return api;\r\n    });\r\n    /**\r\n     * 代码来自于：https://github.com/blueimp/JavaScript-Load-Image\r\n     * 暂时项目中只用了orientation.\r\n     *\r\n     * 去除了 Exif Sub IFD Pointer, GPS Info IFD Pointer, Exif Thumbnail.\r\n     * @fileOverview EXIF解析\r\n     */\r\n    \r\n    // Sample\r\n    // ====================================\r\n    // Make : Apple\r\n    // Model : iPhone 4S\r\n    // Orientation : 1\r\n    // XResolution : 72 [72/1]\r\n    // YResolution : 72 [72/1]\r\n    // ResolutionUnit : 2\r\n    // Software : QuickTime 7.7.1\r\n    // DateTime : 2013:09:01 22:53:55\r\n    // ExifIFDPointer : 190\r\n    // ExposureTime : 0.058823529411764705 [1/17]\r\n    // FNumber : 2.4 [12/5]\r\n    // ExposureProgram : Normal program\r\n    // ISOSpeedRatings : 800\r\n    // ExifVersion : 0220\r\n    // DateTimeOriginal : 2013:09:01 22:52:51\r\n    // DateTimeDigitized : 2013:09:01 22:52:51\r\n    // ComponentsConfiguration : YCbCr\r\n    // ShutterSpeedValue : 4.058893515764426\r\n    // ApertureValue : 2.5260688216892597 [4845/1918]\r\n    // BrightnessValue : -0.3126686601998395\r\n    // MeteringMode : Pattern\r\n    // Flash : Flash did not fire, compulsory flash mode\r\n    // FocalLength : 4.28 [107/25]\r\n    // SubjectArea : [4 values]\r\n    // FlashpixVersion : 0100\r\n    // ColorSpace : 1\r\n    // PixelXDimension : 2448\r\n    // PixelYDimension : 3264\r\n    // SensingMethod : One-chip color area sensor\r\n    // ExposureMode : 0\r\n    // WhiteBalance : Auto white balance\r\n    // FocalLengthIn35mmFilm : 35\r\n    // SceneCaptureType : Standard\r\n    define('runtime/html5/imagemeta/exif',[\r\n        'base',\r\n        'runtime/html5/imagemeta'\r\n    ], function( Base, ImageMeta ) {\r\n    \r\n        var EXIF = {};\r\n    \r\n        EXIF.ExifMap = function() {\r\n            return this;\r\n        };\r\n    \r\n        EXIF.ExifMap.prototype.map = {\r\n            'Orientation': 0x0112\r\n        };\r\n    \r\n        EXIF.ExifMap.prototype.get = function( id ) {\r\n            return this[ id ] || this[ this.map[ id ] ];\r\n        };\r\n    \r\n        EXIF.exifTagTypes = {\r\n            // byte, 8-bit unsigned int:\r\n            1: {\r\n                getValue: function( dataView, dataOffset ) {\r\n                    return dataView.getUint8( dataOffset );\r\n                },\r\n                size: 1\r\n            },\r\n    \r\n            // ascii, 8-bit byte:\r\n            2: {\r\n                getValue: function( dataView, dataOffset ) {\r\n                    return String.fromCharCode( dataView.getUint8( dataOffset ) );\r\n                },\r\n                size: 1,\r\n                ascii: true\r\n            },\r\n    \r\n            // short, 16 bit int:\r\n            3: {\r\n                getValue: function( dataView, dataOffset, littleEndian ) {\r\n                    return dataView.getUint16( dataOffset, littleEndian );\r\n                },\r\n                size: 2\r\n            },\r\n    \r\n            // long, 32 bit int:\r\n            4: {\r\n                getValue: function( dataView, dataOffset, littleEndian ) {\r\n                    return dataView.getUint32( dataOffset, littleEndian );\r\n                },\r\n                size: 4\r\n            },\r\n    \r\n            // rational = two long values,\r\n            // first is numerator, second is denominator:\r\n            5: {\r\n                getValue: function( dataView, dataOffset, littleEndian ) {\r\n                    return dataView.getUint32( dataOffset, littleEndian ) /\r\n                        dataView.getUint32( dataOffset + 4, littleEndian );\r\n                },\r\n                size: 8\r\n            },\r\n    \r\n            // slong, 32 bit signed int:\r\n            9: {\r\n                getValue: function( dataView, dataOffset, littleEndian ) {\r\n                    return dataView.getInt32( dataOffset, littleEndian );\r\n                },\r\n                size: 4\r\n            },\r\n    \r\n            // srational, two slongs, first is numerator, second is denominator:\r\n            10: {\r\n                getValue: function( dataView, dataOffset, littleEndian ) {\r\n                    return dataView.getInt32( dataOffset, littleEndian ) /\r\n                        dataView.getInt32( dataOffset + 4, littleEndian );\r\n                },\r\n                size: 8\r\n            }\r\n        };\r\n    \r\n        // undefined, 8-bit byte, value depending on field:\r\n        EXIF.exifTagTypes[ 7 ] = EXIF.exifTagTypes[ 1 ];\r\n    \r\n        EXIF.getExifValue = function( dataView, tiffOffset, offset, type, length,\r\n                littleEndian ) {\r\n    \r\n            var tagType = EXIF.exifTagTypes[ type ],\r\n                tagSize, dataOffset, values, i, str, c;\r\n    \r\n            if ( !tagType ) {\r\n                Base.log('Invalid Exif data: Invalid tag type.');\r\n                return;\r\n            }\r\n    \r\n            tagSize = tagType.size * length;\r\n    \r\n            // Determine if the value is contained in the dataOffset bytes,\r\n            // or if the value at the dataOffset is a pointer to the actual data:\r\n            dataOffset = tagSize > 4 ? tiffOffset + dataView.getUint32( offset + 8,\r\n                    littleEndian ) : (offset + 8);\r\n    \r\n            if ( dataOffset + tagSize > dataView.byteLength ) {\r\n                Base.log('Invalid Exif data: Invalid data offset.');\r\n                return;\r\n            }\r\n    \r\n            if ( length === 1 ) {\r\n                return tagType.getValue( dataView, dataOffset, littleEndian );\r\n            }\r\n    \r\n            values = [];\r\n    \r\n            for ( i = 0; i < length; i += 1 ) {\r\n                values[ i ] = tagType.getValue( dataView,\r\n                        dataOffset + i * tagType.size, littleEndian );\r\n            }\r\n    \r\n            if ( tagType.ascii ) {\r\n                str = '';\r\n    \r\n                // Concatenate the chars:\r\n                for ( i = 0; i < values.length; i += 1 ) {\r\n                    c = values[ i ];\r\n    \r\n                    // Ignore the terminating NULL byte(s):\r\n                    if ( c === '\\u0000' ) {\r\n                        break;\r\n                    }\r\n                    str += c;\r\n                }\r\n    \r\n                return str;\r\n            }\r\n            return values;\r\n        };\r\n    \r\n        EXIF.parseExifTag = function( dataView, tiffOffset, offset, littleEndian,\r\n                data ) {\r\n    \r\n            var tag = dataView.getUint16( offset, littleEndian );\r\n            data.exif[ tag ] = EXIF.getExifValue( dataView, tiffOffset, offset,\r\n                    dataView.getUint16( offset + 2, littleEndian ),    // tag type\r\n                    dataView.getUint32( offset + 4, littleEndian ),    // tag length\r\n                    littleEndian );\r\n        };\r\n    \r\n        EXIF.parseExifTags = function( dataView, tiffOffset, dirOffset,\r\n                littleEndian, data ) {\r\n    \r\n            var tagsNumber, dirEndOffset, i;\r\n    \r\n            if ( dirOffset + 6 > dataView.byteLength ) {\r\n                Base.log('Invalid Exif data: Invalid directory offset.');\r\n                return;\r\n            }\r\n    \r\n            tagsNumber = dataView.getUint16( dirOffset, littleEndian );\r\n            dirEndOffset = dirOffset + 2 + 12 * tagsNumber;\r\n    \r\n            if ( dirEndOffset + 4 > dataView.byteLength ) {\r\n                Base.log('Invalid Exif data: Invalid directory size.');\r\n                return;\r\n            }\r\n    \r\n            for ( i = 0; i < tagsNumber; i += 1 ) {\r\n                this.parseExifTag( dataView, tiffOffset,\r\n                        dirOffset + 2 + 12 * i,    // tag offset\r\n                        littleEndian, data );\r\n            }\r\n    \r\n            // Return the offset to the next directory:\r\n            return dataView.getUint32( dirEndOffset, littleEndian );\r\n        };\r\n    \r\n        // EXIF.getExifThumbnail = function(dataView, offset, length) {\r\n        //     var hexData,\r\n        //         i,\r\n        //         b;\r\n        //     if (!length || offset + length > dataView.byteLength) {\r\n        //         Base.log('Invalid Exif data: Invalid thumbnail data.');\r\n        //         return;\r\n        //     }\r\n        //     hexData = [];\r\n        //     for (i = 0; i < length; i += 1) {\r\n        //         b = dataView.getUint8(offset + i);\r\n        //         hexData.push((b < 16 ? '0' : '') + b.toString(16));\r\n        //     }\r\n        //     return 'data:image/jpeg,%' + hexData.join('%');\r\n        // };\r\n    \r\n        EXIF.parseExifData = function( dataView, offset, length, data ) {\r\n    \r\n            var tiffOffset = offset + 10,\r\n                littleEndian, dirOffset;\r\n    \r\n            // Check for the ASCII code for \"Exif\" (0x45786966):\r\n            if ( dataView.getUint32( offset + 4 ) !== 0x45786966 ) {\r\n                // No Exif data, might be XMP data instead\r\n                return;\r\n            }\r\n            if ( tiffOffset + 8 > dataView.byteLength ) {\r\n                Base.log('Invalid Exif data: Invalid segment size.');\r\n                return;\r\n            }\r\n    \r\n            // Check for the two null bytes:\r\n            if ( dataView.getUint16( offset + 8 ) !== 0x0000 ) {\r\n                Base.log('Invalid Exif data: Missing byte alignment offset.');\r\n                return;\r\n            }\r\n    \r\n            // Check the byte alignment:\r\n            switch ( dataView.getUint16( tiffOffset ) ) {\r\n                case 0x4949:\r\n                    littleEndian = true;\r\n                    break;\r\n    \r\n                case 0x4D4D:\r\n                    littleEndian = false;\r\n                    break;\r\n    \r\n                default:\r\n                    Base.log('Invalid Exif data: Invalid byte alignment marker.');\r\n                    return;\r\n            }\r\n    \r\n            // Check for the TIFF tag marker (0x002A):\r\n            if ( dataView.getUint16( tiffOffset + 2, littleEndian ) !== 0x002A ) {\r\n                Base.log('Invalid Exif data: Missing TIFF marker.');\r\n                return;\r\n            }\r\n    \r\n            // Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal:\r\n            dirOffset = dataView.getUint32( tiffOffset + 4, littleEndian );\r\n            // Create the exif object to store the tags:\r\n            data.exif = new EXIF.ExifMap();\r\n            // Parse the tags of the main image directory and retrieve the\r\n            // offset to the next directory, usually the thumbnail directory:\r\n            dirOffset = EXIF.parseExifTags( dataView, tiffOffset,\r\n                    tiffOffset + dirOffset, littleEndian, data );\r\n    \r\n            // 尝试读取缩略图\r\n            // if ( dirOffset ) {\r\n            //     thumbnailData = {exif: {}};\r\n            //     dirOffset = EXIF.parseExifTags(\r\n            //         dataView,\r\n            //         tiffOffset,\r\n            //         tiffOffset + dirOffset,\r\n            //         littleEndian,\r\n            //         thumbnailData\r\n            //     );\r\n    \r\n            //     // Check for JPEG Thumbnail offset:\r\n            //     if (thumbnailData.exif[0x0201]) {\r\n            //         data.exif.Thumbnail = EXIF.getExifThumbnail(\r\n            //             dataView,\r\n            //             tiffOffset + thumbnailData.exif[0x0201],\r\n            //             thumbnailData.exif[0x0202] // Thumbnail data length\r\n            //         );\r\n            //     }\r\n            // }\r\n        };\r\n    \r\n        ImageMeta.parsers[ 0xffe1 ].push( EXIF.parseExifData );\r\n        return EXIF;\r\n    });\r\n    /**\r\n     * 这个方式性能不行，但是可以解决android里面的toDataUrl的bug\r\n     * android里面toDataUrl('image/jpege')得到的结果却是png.\r\n     *\r\n     * 所以这里没辙，只能借助这个工具\r\n     * @fileOverview jpeg encoder\r\n     */\r\n    define('runtime/html5/jpegencoder',[], function( require, exports, module ) {\r\n    \r\n        /*\r\n          Copyright (c) 2008, Adobe Systems Incorporated\r\n          All rights reserved.\r\n    \r\n          Redistribution and use in source and binary forms, with or without\r\n          modification, are permitted provided that the following conditions are\r\n          met:\r\n    \r\n          * Redistributions of source code must retain the above copyright notice,\r\n            this list of conditions and the following disclaimer.\r\n    \r\n          * Redistributions in binary form must reproduce the above copyright\r\n            notice, this list of conditions and the following disclaimer in the\r\n            documentation and/or other materials provided with the distribution.\r\n    \r\n          * Neither the name of Adobe Systems Incorporated nor the names of its\r\n            contributors may be used to endorse or promote products derived from\r\n            this software without specific prior written permission.\r\n    \r\n          THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\r\n          IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\r\n          THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\r\n          PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\r\n          CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\r\n          EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\r\n          PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\r\n          PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\r\n          LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\r\n          NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\r\n          SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\r\n        */\r\n        /*\r\n        JPEG encoder ported to JavaScript and optimized by Andreas Ritter, www.bytestrom.eu, 11/2009\r\n    \r\n        Basic GUI blocking jpeg encoder\r\n        */\r\n    \r\n        function JPEGEncoder(quality) {\r\n          var self = this;\r\n            var fround = Math.round;\r\n            var ffloor = Math.floor;\r\n            var YTable = new Array(64);\r\n            var UVTable = new Array(64);\r\n            var fdtbl_Y = new Array(64);\r\n            var fdtbl_UV = new Array(64);\r\n            var YDC_HT;\r\n            var UVDC_HT;\r\n            var YAC_HT;\r\n            var UVAC_HT;\r\n    \r\n            var bitcode = new Array(65535);\r\n            var category = new Array(65535);\r\n            var outputfDCTQuant = new Array(64);\r\n            var DU = new Array(64);\r\n            var byteout = [];\r\n            var bytenew = 0;\r\n            var bytepos = 7;\r\n    \r\n            var YDU = new Array(64);\r\n            var UDU = new Array(64);\r\n            var VDU = new Array(64);\r\n            var clt = new Array(256);\r\n            var RGB_YUV_TABLE = new Array(2048);\r\n            var currentQuality;\r\n    \r\n            var ZigZag = [\r\n                     0, 1, 5, 6,14,15,27,28,\r\n                     2, 4, 7,13,16,26,29,42,\r\n                     3, 8,12,17,25,30,41,43,\r\n                     9,11,18,24,31,40,44,53,\r\n                    10,19,23,32,39,45,52,54,\r\n                    20,22,33,38,46,51,55,60,\r\n                    21,34,37,47,50,56,59,61,\r\n                    35,36,48,49,57,58,62,63\r\n                ];\r\n    \r\n            var std_dc_luminance_nrcodes = [0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0];\r\n            var std_dc_luminance_values = [0,1,2,3,4,5,6,7,8,9,10,11];\r\n            var std_ac_luminance_nrcodes = [0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d];\r\n            var std_ac_luminance_values = [\r\n                    0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,\r\n                    0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,\r\n                    0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08,\r\n                    0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,\r\n                    0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,\r\n                    0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28,\r\n                    0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,\r\n                    0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,\r\n                    0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,\r\n                    0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,\r\n                    0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,\r\n                    0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89,\r\n                    0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,\r\n                    0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,\r\n                    0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,\r\n                    0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,\r\n                    0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,\r\n                    0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2,\r\n                    0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,\r\n                    0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,\r\n                    0xf9,0xfa\r\n                ];\r\n    \r\n            var std_dc_chrominance_nrcodes = [0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0];\r\n            var std_dc_chrominance_values = [0,1,2,3,4,5,6,7,8,9,10,11];\r\n            var std_ac_chrominance_nrcodes = [0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77];\r\n            var std_ac_chrominance_values = [\r\n                    0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,\r\n                    0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,\r\n                    0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,\r\n                    0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,\r\n                    0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,\r\n                    0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26,\r\n                    0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,\r\n                    0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,\r\n                    0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,\r\n                    0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,\r\n                    0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,\r\n                    0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87,\r\n                    0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,\r\n                    0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,\r\n                    0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,\r\n                    0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,\r\n                    0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,\r\n                    0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,\r\n                    0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,\r\n                    0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,\r\n                    0xf9,0xfa\r\n                ];\r\n    \r\n            function initQuantTables(sf){\r\n                    var YQT = [\r\n                        16, 11, 10, 16, 24, 40, 51, 61,\r\n                        12, 12, 14, 19, 26, 58, 60, 55,\r\n                        14, 13, 16, 24, 40, 57, 69, 56,\r\n                        14, 17, 22, 29, 51, 87, 80, 62,\r\n                        18, 22, 37, 56, 68,109,103, 77,\r\n                        24, 35, 55, 64, 81,104,113, 92,\r\n                        49, 64, 78, 87,103,121,120,101,\r\n                        72, 92, 95, 98,112,100,103, 99\r\n                    ];\r\n    \r\n                    for (var i = 0; i < 64; i++) {\r\n                        var t = ffloor((YQT[i]*sf+50)/100);\r\n                        if (t < 1) {\r\n                            t = 1;\r\n                        } else if (t > 255) {\r\n                            t = 255;\r\n                        }\r\n                        YTable[ZigZag[i]] = t;\r\n                    }\r\n                    var UVQT = [\r\n                        17, 18, 24, 47, 99, 99, 99, 99,\r\n                        18, 21, 26, 66, 99, 99, 99, 99,\r\n                        24, 26, 56, 99, 99, 99, 99, 99,\r\n                        47, 66, 99, 99, 99, 99, 99, 99,\r\n                        99, 99, 99, 99, 99, 99, 99, 99,\r\n                        99, 99, 99, 99, 99, 99, 99, 99,\r\n                        99, 99, 99, 99, 99, 99, 99, 99,\r\n                        99, 99, 99, 99, 99, 99, 99, 99\r\n                    ];\r\n                    for (var j = 0; j < 64; j++) {\r\n                        var u = ffloor((UVQT[j]*sf+50)/100);\r\n                        if (u < 1) {\r\n                            u = 1;\r\n                        } else if (u > 255) {\r\n                            u = 255;\r\n                        }\r\n                        UVTable[ZigZag[j]] = u;\r\n                    }\r\n                    var aasf = [\r\n                        1.0, 1.387039845, 1.306562965, 1.175875602,\r\n                        1.0, 0.785694958, 0.541196100, 0.275899379\r\n                    ];\r\n                    var k = 0;\r\n                    for (var row = 0; row < 8; row++)\r\n                    {\r\n                        for (var col = 0; col < 8; col++)\r\n                        {\r\n                            fdtbl_Y[k]  = (1.0 / (YTable [ZigZag[k]] * aasf[row] * aasf[col] * 8.0));\r\n                            fdtbl_UV[k] = (1.0 / (UVTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0));\r\n                            k++;\r\n                        }\r\n                    }\r\n                }\r\n    \r\n                function computeHuffmanTbl(nrcodes, std_table){\r\n                    var codevalue = 0;\r\n                    var pos_in_table = 0;\r\n                    var HT = new Array();\r\n                    for (var k = 1; k <= 16; k++) {\r\n                        for (var j = 1; j <= nrcodes[k]; j++) {\r\n                            HT[std_table[pos_in_table]] = [];\r\n                            HT[std_table[pos_in_table]][0] = codevalue;\r\n                            HT[std_table[pos_in_table]][1] = k;\r\n                            pos_in_table++;\r\n                            codevalue++;\r\n                        }\r\n                        codevalue*=2;\r\n                    }\r\n                    return HT;\r\n                }\r\n    \r\n                function initHuffmanTbl()\r\n                {\r\n                    YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes,std_dc_luminance_values);\r\n                    UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes,std_dc_chrominance_values);\r\n                    YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes,std_ac_luminance_values);\r\n                    UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes,std_ac_chrominance_values);\r\n                }\r\n    \r\n                function initCategoryNumber()\r\n                {\r\n                    var nrlower = 1;\r\n                    var nrupper = 2;\r\n                    for (var cat = 1; cat <= 15; cat++) {\r\n                        //Positive numbers\r\n                        for (var nr = nrlower; nr<nrupper; nr++) {\r\n                            category[32767+nr] = cat;\r\n                            bitcode[32767+nr] = [];\r\n                            bitcode[32767+nr][1] = cat;\r\n                            bitcode[32767+nr][0] = nr;\r\n                        }\r\n                        //Negative numbers\r\n                        for (var nrneg =-(nrupper-1); nrneg<=-nrlower; nrneg++) {\r\n                            category[32767+nrneg] = cat;\r\n                            bitcode[32767+nrneg] = [];\r\n                            bitcode[32767+nrneg][1] = cat;\r\n                            bitcode[32767+nrneg][0] = nrupper-1+nrneg;\r\n                        }\r\n                        nrlower <<= 1;\r\n                        nrupper <<= 1;\r\n                    }\r\n                }\r\n    \r\n                function initRGBYUVTable() {\r\n                    for(var i = 0; i < 256;i++) {\r\n                        RGB_YUV_TABLE[i]            =  19595 * i;\r\n                        RGB_YUV_TABLE[(i+ 256)>>0]  =  38470 * i;\r\n                        RGB_YUV_TABLE[(i+ 512)>>0]  =   7471 * i + 0x8000;\r\n                        RGB_YUV_TABLE[(i+ 768)>>0]  = -11059 * i;\r\n                        RGB_YUV_TABLE[(i+1024)>>0]  = -21709 * i;\r\n                        RGB_YUV_TABLE[(i+1280)>>0]  =  32768 * i + 0x807FFF;\r\n                        RGB_YUV_TABLE[(i+1536)>>0]  = -27439 * i;\r\n                        RGB_YUV_TABLE[(i+1792)>>0]  = - 5329 * i;\r\n                    }\r\n                }\r\n    \r\n                // IO functions\r\n                function writeBits(bs)\r\n                {\r\n                    var value = bs[0];\r\n                    var posval = bs[1]-1;\r\n                    while ( posval >= 0 ) {\r\n                        if (value & (1 << posval) ) {\r\n                            bytenew |= (1 << bytepos);\r\n                        }\r\n                        posval--;\r\n                        bytepos--;\r\n                        if (bytepos < 0) {\r\n                            if (bytenew == 0xFF) {\r\n                                writeByte(0xFF);\r\n                                writeByte(0);\r\n                            }\r\n                            else {\r\n                                writeByte(bytenew);\r\n                            }\r\n                            bytepos=7;\r\n                            bytenew=0;\r\n                        }\r\n                    }\r\n                }\r\n    \r\n                function writeByte(value)\r\n                {\r\n                    byteout.push(clt[value]); // write char directly instead of converting later\r\n                }\r\n    \r\n                function writeWord(value)\r\n                {\r\n                    writeByte((value>>8)&0xFF);\r\n                    writeByte((value   )&0xFF);\r\n                }\r\n    \r\n                // DCT & quantization core\r\n                function fDCTQuant(data, fdtbl)\r\n                {\r\n                    var d0, d1, d2, d3, d4, d5, d6, d7;\r\n                    /* Pass 1: process rows. */\r\n                    var dataOff=0;\r\n                    var i;\r\n                    var I8 = 8;\r\n                    var I64 = 64;\r\n                    for (i=0; i<I8; ++i)\r\n                    {\r\n                        d0 = data[dataOff];\r\n                        d1 = data[dataOff+1];\r\n                        d2 = data[dataOff+2];\r\n                        d3 = data[dataOff+3];\r\n                        d4 = data[dataOff+4];\r\n                        d5 = data[dataOff+5];\r\n                        d6 = data[dataOff+6];\r\n                        d7 = data[dataOff+7];\r\n    \r\n                        var tmp0 = d0 + d7;\r\n                        var tmp7 = d0 - d7;\r\n                        var tmp1 = d1 + d6;\r\n                        var tmp6 = d1 - d6;\r\n                        var tmp2 = d2 + d5;\r\n                        var tmp5 = d2 - d5;\r\n                        var tmp3 = d3 + d4;\r\n                        var tmp4 = d3 - d4;\r\n    \r\n                        /* Even part */\r\n                        var tmp10 = tmp0 + tmp3;    /* phase 2 */\r\n                        var tmp13 = tmp0 - tmp3;\r\n                        var tmp11 = tmp1 + tmp2;\r\n                        var tmp12 = tmp1 - tmp2;\r\n    \r\n                        data[dataOff] = tmp10 + tmp11; /* phase 3 */\r\n                        data[dataOff+4] = tmp10 - tmp11;\r\n    \r\n                        var z1 = (tmp12 + tmp13) * 0.707106781; /* c4 */\r\n                        data[dataOff+2] = tmp13 + z1; /* phase 5 */\r\n                        data[dataOff+6] = tmp13 - z1;\r\n    \r\n                        /* Odd part */\r\n                        tmp10 = tmp4 + tmp5; /* phase 2 */\r\n                        tmp11 = tmp5 + tmp6;\r\n                        tmp12 = tmp6 + tmp7;\r\n    \r\n                        /* The rotator is modified from fig 4-8 to avoid extra negations. */\r\n                        var z5 = (tmp10 - tmp12) * 0.382683433; /* c6 */\r\n                        var z2 = 0.541196100 * tmp10 + z5; /* c2-c6 */\r\n                        var z4 = 1.306562965 * tmp12 + z5; /* c2+c6 */\r\n                        var z3 = tmp11 * 0.707106781; /* c4 */\r\n    \r\n                        var z11 = tmp7 + z3;    /* phase 5 */\r\n                        var z13 = tmp7 - z3;\r\n    \r\n                        data[dataOff+5] = z13 + z2; /* phase 6 */\r\n                        data[dataOff+3] = z13 - z2;\r\n                        data[dataOff+1] = z11 + z4;\r\n                        data[dataOff+7] = z11 - z4;\r\n    \r\n                        dataOff += 8; /* advance pointer to next row */\r\n                    }\r\n    \r\n                    /* Pass 2: process columns. */\r\n                    dataOff = 0;\r\n                    for (i=0; i<I8; ++i)\r\n                    {\r\n                        d0 = data[dataOff];\r\n                        d1 = data[dataOff + 8];\r\n                        d2 = data[dataOff + 16];\r\n                        d3 = data[dataOff + 24];\r\n                        d4 = data[dataOff + 32];\r\n                        d5 = data[dataOff + 40];\r\n                        d6 = data[dataOff + 48];\r\n                        d7 = data[dataOff + 56];\r\n    \r\n                        var tmp0p2 = d0 + d7;\r\n                        var tmp7p2 = d0 - d7;\r\n                        var tmp1p2 = d1 + d6;\r\n                        var tmp6p2 = d1 - d6;\r\n                        var tmp2p2 = d2 + d5;\r\n                        var tmp5p2 = d2 - d5;\r\n                        var tmp3p2 = d3 + d4;\r\n                        var tmp4p2 = d3 - d4;\r\n    \r\n                        /* Even part */\r\n                        var tmp10p2 = tmp0p2 + tmp3p2;  /* phase 2 */\r\n                        var tmp13p2 = tmp0p2 - tmp3p2;\r\n                        var tmp11p2 = tmp1p2 + tmp2p2;\r\n                        var tmp12p2 = tmp1p2 - tmp2p2;\r\n    \r\n                        data[dataOff] = tmp10p2 + tmp11p2; /* phase 3 */\r\n                        data[dataOff+32] = tmp10p2 - tmp11p2;\r\n    \r\n                        var z1p2 = (tmp12p2 + tmp13p2) * 0.707106781; /* c4 */\r\n                        data[dataOff+16] = tmp13p2 + z1p2; /* phase 5 */\r\n                        data[dataOff+48] = tmp13p2 - z1p2;\r\n    \r\n                        /* Odd part */\r\n                        tmp10p2 = tmp4p2 + tmp5p2; /* phase 2 */\r\n                        tmp11p2 = tmp5p2 + tmp6p2;\r\n                        tmp12p2 = tmp6p2 + tmp7p2;\r\n    \r\n                        /* The rotator is modified from fig 4-8 to avoid extra negations. */\r\n                        var z5p2 = (tmp10p2 - tmp12p2) * 0.382683433; /* c6 */\r\n                        var z2p2 = 0.541196100 * tmp10p2 + z5p2; /* c2-c6 */\r\n                        var z4p2 = 1.306562965 * tmp12p2 + z5p2; /* c2+c6 */\r\n                        var z3p2 = tmp11p2 * 0.707106781; /* c4 */\r\n    \r\n                        var z11p2 = tmp7p2 + z3p2;  /* phase 5 */\r\n                        var z13p2 = tmp7p2 - z3p2;\r\n    \r\n                        data[dataOff+40] = z13p2 + z2p2; /* phase 6 */\r\n                        data[dataOff+24] = z13p2 - z2p2;\r\n                        data[dataOff+ 8] = z11p2 + z4p2;\r\n                        data[dataOff+56] = z11p2 - z4p2;\r\n    \r\n                        dataOff++; /* advance pointer to next column */\r\n                    }\r\n    \r\n                    // Quantize/descale the coefficients\r\n                    var fDCTQuant;\r\n                    for (i=0; i<I64; ++i)\r\n                    {\r\n                        // Apply the quantization and scaling factor & Round to nearest integer\r\n                        fDCTQuant = data[i]*fdtbl[i];\r\n                        outputfDCTQuant[i] = (fDCTQuant > 0.0) ? ((fDCTQuant + 0.5)|0) : ((fDCTQuant - 0.5)|0);\r\n                        //outputfDCTQuant[i] = fround(fDCTQuant);\r\n    \r\n                    }\r\n                    return outputfDCTQuant;\r\n                }\r\n    \r\n                function writeAPP0()\r\n                {\r\n                    writeWord(0xFFE0); // marker\r\n                    writeWord(16); // length\r\n                    writeByte(0x4A); // J\r\n                    writeByte(0x46); // F\r\n                    writeByte(0x49); // I\r\n                    writeByte(0x46); // F\r\n                    writeByte(0); // = \"JFIF\",'\\0'\r\n                    writeByte(1); // versionhi\r\n                    writeByte(1); // versionlo\r\n                    writeByte(0); // xyunits\r\n                    writeWord(1); // xdensity\r\n                    writeWord(1); // ydensity\r\n                    writeByte(0); // thumbnwidth\r\n                    writeByte(0); // thumbnheight\r\n                }\r\n    \r\n                function writeSOF0(width, height)\r\n                {\r\n                    writeWord(0xFFC0); // marker\r\n                    writeWord(17);   // length, truecolor YUV JPG\r\n                    writeByte(8);    // precision\r\n                    writeWord(height);\r\n                    writeWord(width);\r\n                    writeByte(3);    // nrofcomponents\r\n                    writeByte(1);    // IdY\r\n                    writeByte(0x11); // HVY\r\n                    writeByte(0);    // QTY\r\n                    writeByte(2);    // IdU\r\n                    writeByte(0x11); // HVU\r\n                    writeByte(1);    // QTU\r\n                    writeByte(3);    // IdV\r\n                    writeByte(0x11); // HVV\r\n                    writeByte(1);    // QTV\r\n                }\r\n    \r\n                function writeDQT()\r\n                {\r\n                    writeWord(0xFFDB); // marker\r\n                    writeWord(132);    // length\r\n                    writeByte(0);\r\n                    for (var i=0; i<64; i++) {\r\n                        writeByte(YTable[i]);\r\n                    }\r\n                    writeByte(1);\r\n                    for (var j=0; j<64; j++) {\r\n                        writeByte(UVTable[j]);\r\n                    }\r\n                }\r\n    \r\n                function writeDHT()\r\n                {\r\n                    writeWord(0xFFC4); // marker\r\n                    writeWord(0x01A2); // length\r\n    \r\n                    writeByte(0); // HTYDCinfo\r\n                    for (var i=0; i<16; i++) {\r\n                        writeByte(std_dc_luminance_nrcodes[i+1]);\r\n                    }\r\n                    for (var j=0; j<=11; j++) {\r\n                        writeByte(std_dc_luminance_values[j]);\r\n                    }\r\n    \r\n                    writeByte(0x10); // HTYACinfo\r\n                    for (var k=0; k<16; k++) {\r\n                        writeByte(std_ac_luminance_nrcodes[k+1]);\r\n                    }\r\n                    for (var l=0; l<=161; l++) {\r\n                        writeByte(std_ac_luminance_values[l]);\r\n                    }\r\n    \r\n                    writeByte(1); // HTUDCinfo\r\n                    for (var m=0; m<16; m++) {\r\n                        writeByte(std_dc_chrominance_nrcodes[m+1]);\r\n                    }\r\n                    for (var n=0; n<=11; n++) {\r\n                        writeByte(std_dc_chrominance_values[n]);\r\n                    }\r\n    \r\n                    writeByte(0x11); // HTUACinfo\r\n                    for (var o=0; o<16; o++) {\r\n                        writeByte(std_ac_chrominance_nrcodes[o+1]);\r\n                    }\r\n                    for (var p=0; p<=161; p++) {\r\n                        writeByte(std_ac_chrominance_values[p]);\r\n                    }\r\n                }\r\n    \r\n                function writeSOS()\r\n                {\r\n                    writeWord(0xFFDA); // marker\r\n                    writeWord(12); // length\r\n                    writeByte(3); // nrofcomponents\r\n                    writeByte(1); // IdY\r\n                    writeByte(0); // HTY\r\n                    writeByte(2); // IdU\r\n                    writeByte(0x11); // HTU\r\n                    writeByte(3); // IdV\r\n                    writeByte(0x11); // HTV\r\n                    writeByte(0); // Ss\r\n                    writeByte(0x3f); // Se\r\n                    writeByte(0); // Bf\r\n                }\r\n    \r\n                function processDU(CDU, fdtbl, DC, HTDC, HTAC){\r\n                    var EOB = HTAC[0x00];\r\n                    var M16zeroes = HTAC[0xF0];\r\n                    var pos;\r\n                    var I16 = 16;\r\n                    var I63 = 63;\r\n                    var I64 = 64;\r\n                    var DU_DCT = fDCTQuant(CDU, fdtbl);\r\n                    //ZigZag reorder\r\n                    for (var j=0;j<I64;++j) {\r\n                        DU[ZigZag[j]]=DU_DCT[j];\r\n                    }\r\n                    var Diff = DU[0] - DC; DC = DU[0];\r\n                    //Encode DC\r\n                    if (Diff==0) {\r\n                        writeBits(HTDC[0]); // Diff might be 0\r\n                    } else {\r\n                        pos = 32767+Diff;\r\n                        writeBits(HTDC[category[pos]]);\r\n                        writeBits(bitcode[pos]);\r\n                    }\r\n                    //Encode ACs\r\n                    var end0pos = 63; // was const... which is crazy\r\n                    for (; (end0pos>0)&&(DU[end0pos]==0); end0pos--) {};\r\n                    //end0pos = first element in reverse order !=0\r\n                    if ( end0pos == 0) {\r\n                        writeBits(EOB);\r\n                        return DC;\r\n                    }\r\n                    var i = 1;\r\n                    var lng;\r\n                    while ( i <= end0pos ) {\r\n                        var startpos = i;\r\n                        for (; (DU[i]==0) && (i<=end0pos); ++i) {}\r\n                        var nrzeroes = i-startpos;\r\n                        if ( nrzeroes >= I16 ) {\r\n                            lng = nrzeroes>>4;\r\n                            for (var nrmarker=1; nrmarker <= lng; ++nrmarker)\r\n                                writeBits(M16zeroes);\r\n                            nrzeroes = nrzeroes&0xF;\r\n                        }\r\n                        pos = 32767+DU[i];\r\n                        writeBits(HTAC[(nrzeroes<<4)+category[pos]]);\r\n                        writeBits(bitcode[pos]);\r\n                        i++;\r\n                    }\r\n                    if ( end0pos != I63 ) {\r\n                        writeBits(EOB);\r\n                    }\r\n                    return DC;\r\n                }\r\n    \r\n                function initCharLookupTable(){\r\n                    var sfcc = String.fromCharCode;\r\n                    for(var i=0; i < 256; i++){ ///// ACHTUNG // 255\r\n                        clt[i] = sfcc(i);\r\n                    }\r\n                }\r\n    \r\n                this.encode = function(image,quality) // image data object\r\n                {\r\n                    // var time_start = new Date().getTime();\r\n    \r\n                    if(quality) setQuality(quality);\r\n    \r\n                    // Initialize bit writer\r\n                    byteout = new Array();\r\n                    bytenew=0;\r\n                    bytepos=7;\r\n    \r\n                    // Add JPEG headers\r\n                    writeWord(0xFFD8); // SOI\r\n                    writeAPP0();\r\n                    writeDQT();\r\n                    writeSOF0(image.width,image.height);\r\n                    writeDHT();\r\n                    writeSOS();\r\n    \r\n    \r\n                    // Encode 8x8 macroblocks\r\n                    var DCY=0;\r\n                    var DCU=0;\r\n                    var DCV=0;\r\n    \r\n                    bytenew=0;\r\n                    bytepos=7;\r\n    \r\n    \r\n                    this.encode.displayName = \"_encode_\";\r\n    \r\n                    var imageData = image.data;\r\n                    var width = image.width;\r\n                    var height = image.height;\r\n    \r\n                    var quadWidth = width*4;\r\n                    var tripleWidth = width*3;\r\n    \r\n                    var x, y = 0;\r\n                    var r, g, b;\r\n                    var start,p, col,row,pos;\r\n                    while(y < height){\r\n                        x = 0;\r\n                        while(x < quadWidth){\r\n                        start = quadWidth * y + x;\r\n                        p = start;\r\n                        col = -1;\r\n                        row = 0;\r\n    \r\n                        for(pos=0; pos < 64; pos++){\r\n                            row = pos >> 3;// /8\r\n                            col = ( pos & 7 ) * 4; // %8\r\n                            p = start + ( row * quadWidth ) + col;\r\n    \r\n                            if(y+row >= height){ // padding bottom\r\n                                p-= (quadWidth*(y+1+row-height));\r\n                            }\r\n    \r\n                            if(x+col >= quadWidth){ // padding right\r\n                                p-= ((x+col) - quadWidth +4)\r\n                            }\r\n    \r\n                            r = imageData[ p++ ];\r\n                            g = imageData[ p++ ];\r\n                            b = imageData[ p++ ];\r\n    \r\n    \r\n                            /* // calculate YUV values dynamically\r\n                            YDU[pos]=((( 0.29900)*r+( 0.58700)*g+( 0.11400)*b))-128; //-0x80\r\n                            UDU[pos]=(((-0.16874)*r+(-0.33126)*g+( 0.50000)*b));\r\n                            VDU[pos]=((( 0.50000)*r+(-0.41869)*g+(-0.08131)*b));\r\n                            */\r\n    \r\n                            // use lookup table (slightly faster)\r\n                            YDU[pos] = ((RGB_YUV_TABLE[r]             + RGB_YUV_TABLE[(g +  256)>>0] + RGB_YUV_TABLE[(b +  512)>>0]) >> 16)-128;\r\n                            UDU[pos] = ((RGB_YUV_TABLE[(r +  768)>>0] + RGB_YUV_TABLE[(g + 1024)>>0] + RGB_YUV_TABLE[(b + 1280)>>0]) >> 16)-128;\r\n                            VDU[pos] = ((RGB_YUV_TABLE[(r + 1280)>>0] + RGB_YUV_TABLE[(g + 1536)>>0] + RGB_YUV_TABLE[(b + 1792)>>0]) >> 16)-128;\r\n    \r\n                        }\r\n    \r\n                        DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT);\r\n                        DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);\r\n                        DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);\r\n                        x+=32;\r\n                        }\r\n                        y+=8;\r\n                    }\r\n    \r\n    \r\n                    ////////////////////////////////////////////////////////////////\r\n    \r\n                    // Do the bit alignment of the EOI marker\r\n                    if ( bytepos >= 0 ) {\r\n                        var fillbits = [];\r\n                        fillbits[1] = bytepos+1;\r\n                        fillbits[0] = (1<<(bytepos+1))-1;\r\n                        writeBits(fillbits);\r\n                    }\r\n    \r\n                    writeWord(0xFFD9); //EOI\r\n    \r\n                    var jpegDataUri = 'data:image/jpeg;base64,' + btoa(byteout.join(''));\r\n    \r\n                    byteout = [];\r\n    \r\n                    // benchmarking\r\n                    // var duration = new Date().getTime() - time_start;\r\n                    // console.log('Encoding time: '+ currentQuality + 'ms');\r\n                    //\r\n    \r\n                    return jpegDataUri\r\n            }\r\n    \r\n            function setQuality(quality){\r\n                if (quality <= 0) {\r\n                    quality = 1;\r\n                }\r\n                if (quality > 100) {\r\n                    quality = 100;\r\n                }\r\n    \r\n                if(currentQuality == quality) return // don't recalc if unchanged\r\n    \r\n                var sf = 0;\r\n                if (quality < 50) {\r\n                    sf = Math.floor(5000 / quality);\r\n                } else {\r\n                    sf = Math.floor(200 - quality*2);\r\n                }\r\n    \r\n                initQuantTables(sf);\r\n                currentQuality = quality;\r\n                // console.log('Quality set to: '+quality +'%');\r\n            }\r\n    \r\n            function init(){\r\n                // var time_start = new Date().getTime();\r\n                if(!quality) quality = 50;\r\n                // Create tables\r\n                initCharLookupTable()\r\n                initHuffmanTbl();\r\n                initCategoryNumber();\r\n                initRGBYUVTable();\r\n    \r\n                setQuality(quality);\r\n                // var duration = new Date().getTime() - time_start;\r\n                // console.log('Initialization '+ duration + 'ms');\r\n            }\r\n    \r\n            init();\r\n    \r\n        };\r\n    \r\n        JPEGEncoder.encode = function( data, quality ) {\r\n            var encoder = new JPEGEncoder( quality );\r\n    \r\n            return encoder.encode( data );\r\n        }\r\n    \r\n        return JPEGEncoder;\r\n    });\r\n    /**\r\n     * @fileOverview Fix android canvas.toDataUrl bug.\r\n     */\r\n    define('runtime/html5/androidpatch',[\r\n        'runtime/html5/util',\r\n        'runtime/html5/jpegencoder',\r\n        'base'\r\n    ], function( Util, encoder, Base ) {\r\n        var origin = Util.canvasToDataUrl,\r\n            supportJpeg;\r\n    \r\n        Util.canvasToDataUrl = function( canvas, type, quality ) {\r\n            var ctx, w, h, fragement, parts;\r\n    \r\n            // 非android手机直接跳过。\r\n            if ( !Base.os.android ) {\r\n                return origin.apply( null, arguments );\r\n            }\r\n    \r\n            // 检测是否canvas支持jpeg导出，根据数据格式来判断。\r\n            // JPEG 前两位分别是：255, 216\r\n            if ( type === 'image/jpeg' && typeof supportJpeg === 'undefined' ) {\r\n                fragement = origin.apply( null, arguments );\r\n    \r\n                parts = fragement.split(',');\r\n    \r\n                if ( ~parts[ 0 ].indexOf('base64') ) {\r\n                    fragement = atob( parts[ 1 ] );\r\n                } else {\r\n                    fragement = decodeURIComponent( parts[ 1 ] );\r\n                }\r\n    \r\n                fragement = fragement.substring( 0, 2 );\r\n    \r\n                supportJpeg = fragement.charCodeAt( 0 ) === 255 &&\r\n                        fragement.charCodeAt( 1 ) === 216;\r\n            }\r\n    \r\n            // 只有在android环境下才修复\r\n            if ( type === 'image/jpeg' && !supportJpeg ) {\r\n                w = canvas.width;\r\n                h = canvas.height;\r\n                ctx = canvas.getContext('2d');\r\n    \r\n                return encoder.encode( ctx.getImageData( 0, 0, w, h ), quality );\r\n            }\r\n    \r\n            return origin.apply( null, arguments );\r\n        };\r\n    });\r\n    /**\r\n     * @fileOverview Image\r\n     */\r\n    define('runtime/html5/image',[\r\n        'base',\r\n        'runtime/html5/runtime',\r\n        'runtime/html5/util'\r\n    ], function( Base, Html5Runtime, Util ) {\r\n    \r\n        var BLANK = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D';\r\n    \r\n        return Html5Runtime.register( 'Image', {\r\n    \r\n            // flag: 标记是否被修改过。\r\n            modified: false,\r\n    \r\n            init: function() {\r\n                var me = this,\r\n                    img = new Image();\r\n    \r\n                img.onload = function() {\r\n    \r\n                    me._info = {\r\n                        type: me.type,\r\n                        width: this.width,\r\n                        height: this.height\r\n                    };\r\n    \r\n                    // 读取meta信息。\r\n                    if ( !me._metas && 'image/jpeg' === me.type ) {\r\n                        Util.parseMeta( me._blob, function( error, ret ) {\r\n                            me._metas = ret;\r\n                            me.owner.trigger('load');\r\n                        });\r\n                    } else {\r\n                        me.owner.trigger('load');\r\n                    }\r\n                };\r\n    \r\n                img.onerror = function() {\r\n                    me.owner.trigger('error');\r\n                };\r\n    \r\n                me._img = img;\r\n            },\r\n    \r\n            loadFromBlob: function( blob ) {\r\n                var me = this,\r\n                    img = me._img;\r\n    \r\n                me._blob = blob;\r\n                me.type = blob.type;\r\n                img.src = Util.createObjectURL( blob.getSource() );\r\n                me.owner.once( 'load', function() {\r\n                    Util.revokeObjectURL( img.src );\r\n                });\r\n            },\r\n    \r\n            resize: function( width, height ) {\r\n                var canvas = this._canvas ||\r\n                        (this._canvas = document.createElement('canvas'));\r\n    \r\n                this._resize( this._img, canvas, width, height );\r\n                this._blob = null;    // 没用了，可以删掉了。\r\n                this.modified = true;\r\n                this.owner.trigger('complete');\r\n            },\r\n    \r\n            getAsBlob: function( type ) {\r\n                var blob = this._blob,\r\n                    opts = this.options,\r\n                    canvas;\r\n    \r\n                type = type || this.type;\r\n    \r\n                // blob需要重新生成。\r\n                if ( this.modified || this.type !== type ) {\r\n                    canvas = this._canvas;\r\n    \r\n                    if ( type === 'image/jpeg' ) {\r\n    \r\n                        blob = Util.canvasToDataUrl( canvas, 'image/jpeg',\r\n                                opts.quality );\r\n    \r\n                        if ( opts.preserveHeaders && this._metas &&\r\n                                this._metas.imageHead ) {\r\n    \r\n                            blob = Util.dataURL2ArrayBuffer( blob );\r\n                            blob = Util.updateImageHead( blob,\r\n                                    this._metas.imageHead );\r\n                            blob = Util.arrayBufferToBlob( blob, type );\r\n                            return blob;\r\n                        }\r\n                    } else {\r\n                        blob = Util.canvasToDataUrl( canvas, type );\r\n                    }\r\n    \r\n                    blob = Util.dataURL2Blob( blob );\r\n                }\r\n    \r\n                return blob;\r\n            },\r\n    \r\n            getAsDataUrl: function( type ) {\r\n                var opts = this.options;\r\n    \r\n                type = type || this.type;\r\n    \r\n                if ( type === 'image/jpeg' ) {\r\n                    return Util.canvasToDataUrl( this._canvas, type, opts.quality );\r\n                } else {\r\n                    return this._canvas.toDataURL( type );\r\n                }\r\n            },\r\n    \r\n            getOrientation: function() {\r\n                return this._metas && this._metas.exif &&\r\n                        this._metas.exif.get('Orientation') || 1;\r\n            },\r\n    \r\n            info: function( val ) {\r\n    \r\n                // setter\r\n                if ( val ) {\r\n                    this._info = val;\r\n                    return this;\r\n                }\r\n    \r\n                // getter\r\n                return this._info;\r\n            },\r\n    \r\n            meta: function( val ) {\r\n    \r\n                // setter\r\n                if ( val ) {\r\n                    this._meta = val;\r\n                    return this;\r\n                }\r\n    \r\n                // getter\r\n                return this._meta;\r\n            },\r\n    \r\n            destroy: function() {\r\n                var canvas = this._canvas;\r\n                this._img.onload = null;\r\n    \r\n                if ( canvas ) {\r\n                    canvas.getContext('2d')\r\n                            .clearRect( 0, 0, canvas.width, canvas.height );\r\n                    canvas.width = canvas.height = 0;\r\n                    this._canvas = null;\r\n                }\r\n    \r\n                // 释放内存。非常重要，否则释放不了image的内存。\r\n                this._img.src = BLANK;\r\n                this._img = this._blob = null;\r\n            },\r\n    \r\n            _resize: function( img, cvs, width, height ) {\r\n                var opts = this.options,\r\n                    naturalWidth = img.width,\r\n                    naturalHeight = img.height,\r\n                    orientation = this.getOrientation(),\r\n                    scale, w, h, x, y;\r\n    \r\n                // values that require 90 degree rotation\r\n                if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) {\r\n    \r\n                    // 交换width, height的值。\r\n                    width ^= height;\r\n                    height ^= width;\r\n                    width ^= height;\r\n                }\r\n    \r\n                scale = Math[ opts.crop ? 'max' : 'min' ]( width / naturalWidth,\r\n                        height / naturalHeight );\r\n    \r\n                // 不允许放大。\r\n                opts.allowMagnify || (scale = Math.min( 1, scale ));\r\n    \r\n                w = naturalWidth * scale;\r\n                h = naturalHeight * scale;\r\n    \r\n                if ( opts.crop ) {\r\n                    cvs.width = width;\r\n                    cvs.height = height;\r\n                } else {\r\n                    cvs.width = w;\r\n                    cvs.height = h;\r\n                }\r\n    \r\n                x = (cvs.width - w) / 2;\r\n                y = (cvs.height - h) / 2;\r\n    \r\n                opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation );\r\n    \r\n                this._renderImageToCanvas( cvs, img, x, y, w, h );\r\n            },\r\n    \r\n            _rotate2Orientaion: function( canvas, orientation ) {\r\n                var width = canvas.width,\r\n                    height = canvas.height,\r\n                    ctx = canvas.getContext('2d');\r\n    \r\n                switch ( orientation ) {\r\n                    case 5:\r\n                    case 6:\r\n                    case 7:\r\n                    case 8:\r\n                        canvas.width = height;\r\n                        canvas.height = width;\r\n                        break;\r\n                }\r\n    \r\n                switch ( orientation ) {\r\n                    case 2:    // horizontal flip\r\n                        ctx.translate( width, 0 );\r\n                        ctx.scale( -1, 1 );\r\n                        break;\r\n    \r\n                    case 3:    // 180 rotate left\r\n                        ctx.translate( width, height );\r\n                        ctx.rotate( Math.PI );\r\n                        break;\r\n    \r\n                    case 4:    // vertical flip\r\n                        ctx.translate( 0, height );\r\n                        ctx.scale( 1, -1 );\r\n                        break;\r\n    \r\n                    case 5:    // vertical flip + 90 rotate right\r\n                        ctx.rotate( 0.5 * Math.PI );\r\n                        ctx.scale( 1, -1 );\r\n                        break;\r\n    \r\n                    case 6:    // 90 rotate right\r\n                        ctx.rotate( 0.5 * Math.PI );\r\n                        ctx.translate( 0, -height );\r\n                        break;\r\n    \r\n                    case 7:    // horizontal flip + 90 rotate right\r\n                        ctx.rotate( 0.5 * Math.PI );\r\n                        ctx.translate( width, -height );\r\n                        ctx.scale( -1, 1 );\r\n                        break;\r\n    \r\n                    case 8:    // 90 rotate left\r\n                        ctx.rotate( -0.5 * Math.PI );\r\n                        ctx.translate( -width, 0 );\r\n                        break;\r\n                }\r\n            },\r\n    \r\n            // https://github.com/stomita/ios-imagefile-megapixel/\r\n            // blob/master/src/megapix-image.js\r\n            _renderImageToCanvas: (function() {\r\n    \r\n                // 如果不是ios, 不需要这么复杂！\r\n                if ( !Base.os.ios ) {\r\n                    return function( canvas, img, x, y, w, h ) {\r\n                        canvas.getContext('2d').drawImage( img, x, y, w, h );\r\n                    };\r\n                }\r\n    \r\n                /**\r\n                 * Detecting vertical squash in loaded image.\r\n                 * Fixes a bug which squash image vertically while drawing into\r\n                 * canvas for some images.\r\n                 */\r\n                function detectVerticalSquash( img, iw, ih ) {\r\n                    var canvas = document.createElement('canvas'),\r\n                        ctx = canvas.getContext('2d'),\r\n                        sy = 0,\r\n                        ey = ih,\r\n                        py = ih,\r\n                        data, alpha, ratio;\r\n    \r\n    \r\n                    canvas.width = 1;\r\n                    canvas.height = ih;\r\n                    ctx.drawImage( img, 0, 0 );\r\n                    data = ctx.getImageData( 0, 0, 1, ih ).data;\r\n    \r\n                    // search image edge pixel position in case\r\n                    // it is squashed vertically.\r\n                    while ( py > sy ) {\r\n                        alpha = data[ (py - 1) * 4 + 3 ];\r\n    \r\n                        if ( alpha === 0 ) {\r\n                            ey = py;\r\n                        } else {\r\n                            sy = py;\r\n                        }\r\n    \r\n                        py = (ey + sy) >> 1;\r\n                    }\r\n    \r\n                    ratio = (py / ih);\r\n                    return (ratio === 0) ? 1 : ratio;\r\n                }\r\n    \r\n                // fix ie7 bug\r\n                // http://stackoverflow.com/questions/11929099/\r\n                // html5-canvas-drawimage-ratio-bug-ios\r\n                if ( Base.os.ios >= 7 ) {\r\n                    return function( canvas, img, x, y, w, h ) {\r\n                        var iw = img.naturalWidth,\r\n                            ih = img.naturalHeight,\r\n                            vertSquashRatio = detectVerticalSquash( img, iw, ih );\r\n    \r\n                        return canvas.getContext('2d').drawImage( img, 0, 0,\r\n                            iw * vertSquashRatio, ih * vertSquashRatio,\r\n                            x, y, w, h );\r\n                    };\r\n                }\r\n    \r\n                /**\r\n                 * Detect subsampling in loaded image.\r\n                 * In iOS, larger images than 2M pixels may be\r\n                 * subsampled in rendering.\r\n                 */\r\n                function detectSubsampling( img ) {\r\n                    var iw = img.naturalWidth,\r\n                        ih = img.naturalHeight,\r\n                        canvas, ctx;\r\n    \r\n                    // subsampling may happen overmegapixel image\r\n                    if ( iw * ih > 1024 * 1024 ) {\r\n                        canvas = document.createElement('canvas');\r\n                        canvas.width = canvas.height = 1;\r\n                        ctx = canvas.getContext('2d');\r\n                        ctx.drawImage( img, -iw + 1, 0 );\r\n    \r\n                        // subsampled image becomes half smaller in rendering size.\r\n                        // check alpha channel value to confirm image is covering\r\n                        // edge pixel or not. if alpha value is 0\r\n                        // image is not covering, hence subsampled.\r\n                        return ctx.getImageData( 0, 0, 1, 1 ).data[ 3 ] === 0;\r\n                    } else {\r\n                        return false;\r\n                    }\r\n                }\r\n    \r\n    \r\n                return function( canvas, img, x, y, width, height ) {\r\n                    var iw = img.naturalWidth,\r\n                        ih = img.naturalHeight,\r\n                        ctx = canvas.getContext('2d'),\r\n                        subsampled = detectSubsampling( img ),\r\n                        doSquash = this.type === 'image/jpeg',\r\n                        d = 1024,\r\n                        sy = 0,\r\n                        dy = 0,\r\n                        tmpCanvas, tmpCtx, vertSquashRatio, dw, dh, sx, dx;\r\n    \r\n                    if ( subsampled ) {\r\n                        iw /= 2;\r\n                        ih /= 2;\r\n                    }\r\n    \r\n                    ctx.save();\r\n                    tmpCanvas = document.createElement('canvas');\r\n                    tmpCanvas.width = tmpCanvas.height = d;\r\n    \r\n                    tmpCtx = tmpCanvas.getContext('2d');\r\n                    vertSquashRatio = doSquash ?\r\n                            detectVerticalSquash( img, iw, ih ) : 1;\r\n    \r\n                    dw = Math.ceil( d * width / iw );\r\n                    dh = Math.ceil( d * height / ih / vertSquashRatio );\r\n    \r\n                    while ( sy < ih ) {\r\n                        sx = 0;\r\n                        dx = 0;\r\n                        while ( sx < iw ) {\r\n                            tmpCtx.clearRect( 0, 0, d, d );\r\n                            tmpCtx.drawImage( img, -sx, -sy );\r\n                            ctx.drawImage( tmpCanvas, 0, 0, d, d,\r\n                                    x + dx, y + dy, dw, dh );\r\n                            sx += d;\r\n                            dx += dw;\r\n                        }\r\n                        sy += d;\r\n                        dy += dh;\r\n                    }\r\n                    ctx.restore();\r\n                    tmpCanvas = tmpCtx = null;\r\n                };\r\n            })()\r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview Transport\r\n     * @todo 支持chunked传输，优势：\r\n     * 可以将大文件分成小块，挨个传输，可以提高大文件成功率，当失败的时候，也只需要重传那小部分，\r\n     * 而不需要重头再传一次。另外断点续传也需要用chunked方式。\r\n     */\r\n    define('runtime/html5/transport',[\r\n        'base',\r\n        'runtime/html5/runtime'\r\n    ], function( Base, Html5Runtime ) {\r\n    \r\n        var noop = Base.noop,\r\n            $ = Base.$;\r\n    \r\n        return Html5Runtime.register( 'Transport', {\r\n            init: function() {\r\n                this._status = 0;\r\n                this._response = null;\r\n            },\r\n    \r\n            send: function() {\r\n                var owner = this.owner,\r\n                    opts = this.options,\r\n                    xhr = this._initAjax(),\r\n                    blob = owner._blob,\r\n                    server = opts.server,\r\n                    formData, binary, fr;\r\n    \r\n                if ( opts.sendAsBinary ) {\r\n                    server += (/\\?/.test( server ) ? '&' : '?') +\r\n                            $.param( owner._formData );\r\n    \r\n                    binary = blob.getSource();\r\n                } else {\r\n                    formData = new FormData();\r\n                    $.each( owner._formData, function( k, v ) {\r\n                        formData.append( k, v );\r\n                    });\r\n    \r\n                    formData.append( opts.fileVal, blob.getSource(),\r\n                            opts.filename || owner._formData.name || '' );\r\n                }\r\n    \r\n                if ( opts.withCredentials && 'withCredentials' in xhr ) {\r\n                    xhr.open( opts.method, server, true );\r\n                    xhr.withCredentials = true;\r\n                } else {\r\n                    xhr.open( opts.method, server );\r\n                }\r\n    \r\n                this._setRequestHeader( xhr, opts.headers );\r\n    \r\n                if ( binary ) {\r\n                    xhr.overrideMimeType('application/octet-stream');\r\n    \r\n                    // android直接发送blob会导致服务端接收到的是空文件。\r\n                    // bug详情。\r\n                    // https://code.google.com/p/android/issues/detail?id=39882\r\n                    // 所以先用fileReader读取出来再通过arraybuffer的方式发送。\r\n                    if ( Base.os.android ) {\r\n                        fr = new FileReader();\r\n    \r\n                        fr.onload = function() {\r\n                            xhr.send( this.result );\r\n                            fr = fr.onload = null;\r\n                        };\r\n    \r\n                        fr.readAsArrayBuffer( binary );\r\n                    } else {\r\n                        xhr.send( binary );\r\n                    }\r\n                } else {\r\n                    xhr.send( formData );\r\n                }\r\n            },\r\n    \r\n            getResponse: function() {\r\n                return this._response;\r\n            },\r\n    \r\n            getResponseAsJson: function() {\r\n                return this._parseJson( this._response );\r\n            },\r\n    \r\n            getStatus: function() {\r\n                return this._status;\r\n            },\r\n    \r\n            abort: function() {\r\n                var xhr = this._xhr;\r\n    \r\n                if ( xhr ) {\r\n                    xhr.upload.onprogress = noop;\r\n                    xhr.onreadystatechange = noop;\r\n                    xhr.abort();\r\n    \r\n                    this._xhr = xhr = null;\r\n                }\r\n            },\r\n    \r\n            destroy: function() {\r\n                this.abort();\r\n            },\r\n    \r\n            _initAjax: function() {\r\n                var me = this,\r\n                    xhr = new XMLHttpRequest(),\r\n                    opts = this.options;\r\n    \r\n                if ( opts.withCredentials && !('withCredentials' in xhr) &&\r\n                        typeof XDomainRequest !== 'undefined' ) {\r\n                    xhr = new XDomainRequest();\r\n                }\r\n    \r\n                xhr.upload.onprogress = function( e ) {\r\n                    var percentage = 0;\r\n    \r\n                    if ( e.lengthComputable ) {\r\n                        percentage = e.loaded / e.total;\r\n                    }\r\n    \r\n                    return me.trigger( 'progress', percentage );\r\n                };\r\n    \r\n                xhr.onreadystatechange = function() {\r\n    \r\n                    if ( xhr.readyState !== 4 ) {\r\n                        return;\r\n                    }\r\n    \r\n                    xhr.upload.onprogress = noop;\r\n                    xhr.onreadystatechange = noop;\r\n                    me._xhr = null;\r\n                    me._status = xhr.status;\r\n    \r\n                    if ( xhr.status >= 200 && xhr.status < 300 ) {\r\n                        me._response = xhr.responseText;\r\n                        return me.trigger('load');\r\n                    } else if ( xhr.status >= 500 && xhr.status < 600 ) {\r\n                        me._response = xhr.responseText;\r\n                        return me.trigger( 'error', 'server' );\r\n                    }\r\n    \r\n    \r\n                    return me.trigger( 'error', me._status ? 'http' : 'abort' );\r\n                };\r\n    \r\n                me._xhr = xhr;\r\n                return xhr;\r\n            },\r\n    \r\n            _setRequestHeader: function( xhr, headers ) {\r\n                $.each( headers, function( key, val ) {\r\n                    xhr.setRequestHeader( key, val );\r\n                });\r\n            },\r\n    \r\n            _parseJson: function( str ) {\r\n                var json;\r\n    \r\n                try {\r\n                    json = JSON.parse( str );\r\n                } catch ( ex ) {\r\n                    json = {};\r\n                }\r\n    \r\n                return json;\r\n            }\r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview FlashRuntime\r\n     */\r\n    define('runtime/flash/runtime',[\r\n        'base',\r\n        'runtime/runtime',\r\n        'runtime/compbase'\r\n    ], function( Base, Runtime, CompBase ) {\r\n    \r\n        var $ = Base.$,\r\n            type = 'flash',\r\n            components = {};\r\n    \r\n    \r\n        function getFlashVersion() {\r\n            var version;\r\n    \r\n            try {\r\n                version = navigator.plugins[ 'Shockwave Flash' ];\r\n                version = version.description;\r\n            } catch ( ex ) {\r\n                try {\r\n                    version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash')\r\n                            .GetVariable('$version');\r\n                } catch ( ex2 ) {\r\n                    version = '0.0';\r\n                }\r\n            }\r\n            version = version.match( /\\d+/g );\r\n            return parseFloat( version[ 0 ] + '.' + version[ 1 ], 10 );\r\n        }\r\n    \r\n        function FlashRuntime() {\r\n            var pool = {},\r\n                clients = {},\r\n                destory = this.destory,\r\n                me = this,\r\n                jsreciver = Base.guid('webuploader_');\r\n    \r\n            Runtime.apply( me, arguments );\r\n            me.type = type;\r\n    \r\n    \r\n            // 这个方法的调用者，实际上是RuntimeClient\r\n            me.exec = function( comp, fn/*, args...*/ ) {\r\n                var client = this,\r\n                    uid = client.uid,\r\n                    args = Base.slice( arguments, 2 ),\r\n                    instance;\r\n    \r\n                clients[ uid ] = client;\r\n    \r\n                if ( components[ comp ] ) {\r\n                    if ( !pool[ uid ] ) {\r\n                        pool[ uid ] = new components[ comp ]( client, me );\r\n                    }\r\n    \r\n                    instance = pool[ uid ];\r\n    \r\n                    if ( instance[ fn ] ) {\r\n                        return instance[ fn ].apply( instance, args );\r\n                    }\r\n                }\r\n    \r\n                return me.flashExec.apply( client, arguments );\r\n            };\r\n    \r\n            function handler( evt, obj ) {\r\n                var type = evt.type || evt,\r\n                    parts, uid;\r\n    \r\n                parts = type.split('::');\r\n                uid = parts[ 0 ];\r\n                type = parts[ 1 ];\r\n    \r\n                // console.log.apply( console, arguments );\r\n    \r\n                if ( type === 'Ready' && uid === me.uid ) {\r\n                    me.trigger('ready');\r\n                } else if ( clients[ uid ] ) {\r\n                    clients[ uid ].trigger( type.toLowerCase(), evt, obj );\r\n                }\r\n    \r\n                // Base.log( evt, obj );\r\n            }\r\n    \r\n            // flash的接受器。\r\n            window[ jsreciver ] = function() {\r\n                var args = arguments;\r\n    \r\n                // 为了能捕获得到。\r\n                setTimeout(function() {\r\n                    handler.apply( null, args );\r\n                }, 1 );\r\n            };\r\n    \r\n            this.jsreciver = jsreciver;\r\n    \r\n            this.destory = function() {\r\n                // @todo 删除池子中的所有实例\r\n                return destory && destory.apply( this, arguments );\r\n            };\r\n    \r\n            this.flashExec = function( comp, fn ) {\r\n                var flash = me.getFlash(),\r\n                    args = Base.slice( arguments, 2 );\r\n    \r\n                return flash.exec( this.uid, comp, fn, args );\r\n            };\r\n    \r\n            // @todo\r\n        }\r\n    \r\n        Base.inherits( Runtime, {\r\n            constructor: FlashRuntime,\r\n    \r\n            init: function() {\r\n                var container = this.getContainer(),\r\n                    opts = this.options,\r\n                    html;\r\n    \r\n                // if not the minimal height, shims are not initialized\r\n                // in older browsers (e.g FF3.6, IE6,7,8, Safari 4.0,5.0, etc)\r\n                container.css({\r\n                    position: 'absolute',\r\n                    top: '-8px',\r\n                    left: '-8px',\r\n                    width: '9px',\r\n                    height: '9px',\r\n                    overflow: 'hidden'\r\n                });\r\n    \r\n                // insert flash object\r\n                html = '<object id=\"' + this.uid + '\" type=\"application/' +\r\n                        'x-shockwave-flash\" data=\"' +  opts.swf + '\" ';\r\n    \r\n                if ( Base.browser.ie ) {\r\n                    html += 'classid=\"clsid:d27cdb6e-ae6d-11cf-96b8-444553540000\" ';\r\n                }\r\n    \r\n                html += 'width=\"100%\" height=\"100%\" style=\"outline:0\">'  +\r\n                    '<param name=\"movie\" value=\"' + opts.swf + '\" />' +\r\n                    '<param name=\"flashvars\" value=\"uid=' + this.uid +\r\n                    '&jsreciver=' + this.jsreciver + '\" />' +\r\n                    '<param name=\"wmode\" value=\"transparent\" />' +\r\n                    '<param name=\"allowscriptaccess\" value=\"always\" />' +\r\n                '</object>';\r\n    \r\n                container.html( html );\r\n            },\r\n    \r\n            getFlash: function() {\r\n                if ( this._flash ) {\r\n                    return this._flash;\r\n                }\r\n    \r\n                this._flash = $( '#' + this.uid ).get( 0 );\r\n                return this._flash;\r\n            }\r\n    \r\n        });\r\n    \r\n        FlashRuntime.register = function( name, component ) {\r\n            component = components[ name ] = Base.inherits( CompBase, $.extend({\r\n    \r\n                // @todo fix this later\r\n                flashExec: function() {\r\n                    var owner = this.owner,\r\n                        runtime = this.getRuntime();\r\n    \r\n                    return runtime.flashExec.apply( owner, arguments );\r\n                }\r\n            }, component ) );\r\n    \r\n            return component;\r\n        };\r\n    \r\n        if ( getFlashVersion() >= 11.4 ) {\r\n            Runtime.addRuntime( type, FlashRuntime );\r\n        }\r\n    \r\n        return FlashRuntime;\r\n    });\r\n    /**\r\n     * @fileOverview FilePicker\r\n     */\r\n    define('runtime/flash/filepicker',[\r\n        'base',\r\n        'runtime/flash/runtime'\r\n    ], function( Base, FlashRuntime ) {\r\n        var $ = Base.$;\r\n    \r\n        return FlashRuntime.register( 'FilePicker', {\r\n            init: function( opts ) {\r\n                var copy = $.extend({}, opts ),\r\n                    len, i;\r\n    \r\n                // 修复Flash再没有设置title的情况下无法弹出flash文件选择框的bug.\r\n                len = copy.accept && copy.accept.length;\r\n                for (  i = 0; i < len; i++ ) {\r\n                    if ( !copy.accept[ i ].title ) {\r\n                        copy.accept[ i ].title = 'Files';\r\n                    }\r\n                }\r\n    \r\n                delete copy.button;\r\n                delete copy.container;\r\n    \r\n                this.flashExec( 'FilePicker', 'init', copy );\r\n            },\r\n    \r\n            destroy: function() {\r\n                // todo\r\n            }\r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview 图片压缩\r\n     */\r\n    define('runtime/flash/image',[\r\n        'runtime/flash/runtime'\r\n    ], function( FlashRuntime ) {\r\n    \r\n        return FlashRuntime.register( 'Image', {\r\n            // init: function( options ) {\r\n            //     var owner = this.owner;\r\n    \r\n            //     this.flashExec( 'Image', 'init', options );\r\n            //     owner.on( 'load', function() {\r\n            //         debugger;\r\n            //     });\r\n            // },\r\n    \r\n            loadFromBlob: function( blob ) {\r\n                var owner = this.owner;\r\n    \r\n                owner.info() && this.flashExec( 'Image', 'info', owner.info() );\r\n                owner.meta() && this.flashExec( 'Image', 'meta', owner.meta() );\r\n    \r\n                this.flashExec( 'Image', 'loadFromBlob', blob.uid );\r\n            }\r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview  Transport flash实现\r\n     */\r\n    define('runtime/flash/transport',[\r\n        'base',\r\n        'runtime/flash/runtime',\r\n        'runtime/client'\r\n    ], function( Base, FlashRuntime, RuntimeClient ) {\r\n        var $ = Base.$;\r\n    \r\n        return FlashRuntime.register( 'Transport', {\r\n            init: function() {\r\n                this._status = 0;\r\n                this._response = null;\r\n                this._responseJson = null;\r\n            },\r\n    \r\n            send: function() {\r\n                var owner = this.owner,\r\n                    opts = this.options,\r\n                    xhr = this._initAjax(),\r\n                    blob = owner._blob,\r\n                    server = opts.server,\r\n                    binary;\r\n    \r\n                xhr.connectRuntime( blob.ruid );\r\n    \r\n                if ( opts.sendAsBinary ) {\r\n                    server += (/\\?/.test( server ) ? '&' : '?') +\r\n                            $.param( owner._formData );\r\n    \r\n                    binary = blob.uid;\r\n                } else {\r\n                    $.each( owner._formData, function( k, v ) {\r\n                        xhr.exec( 'append', k, v );\r\n                    });\r\n    \r\n                    xhr.exec( 'appendBlob', opts.fileVal, blob.uid,\r\n                            opts.filename || owner._formData.name || '' );\r\n                }\r\n    \r\n                this._setRequestHeader( xhr, opts.headers );\r\n                xhr.exec( 'send', {\r\n                    method: opts.method,\r\n                    url: server\r\n                }, binary );\r\n            },\r\n    \r\n            getStatus: function() {\r\n                return this._status;\r\n            },\r\n    \r\n            getResponse: function() {\r\n                return this._response;\r\n            },\r\n    \r\n            getResponseAsJson: function() {\r\n                return this._responseJson;\r\n            },\r\n    \r\n            abort: function() {\r\n                var xhr = this._xhr;\r\n    \r\n                if ( xhr ) {\r\n                    xhr.exec('abort');\r\n                    xhr.destroy();\r\n                    this._xhr = xhr = null;\r\n                }\r\n            },\r\n    \r\n            destroy: function() {\r\n                this.abort();\r\n            },\r\n    \r\n            _initAjax: function() {\r\n                var me = this,\r\n                    xhr = new RuntimeClient('XMLHttpRequest');\r\n    \r\n                xhr.on( 'uploadprogress progress', function( e ) {\r\n                    return me.trigger( 'progress', e.loaded / e.total );\r\n                });\r\n    \r\n                xhr.on( 'load', function() {\r\n                    var status = xhr.exec('getStatus'),\r\n                        err = '';\r\n    \r\n                    xhr.off();\r\n                    me._xhr = null;\r\n    \r\n                    if ( status >= 200 && status < 300 ) {\r\n                        me._response = xhr.exec('getResponse');\r\n                        me._responseJson = xhr.exec('getResponseAsJson');\r\n                    } else if ( status >= 500 && status < 600 ) {\r\n                        me._response = xhr.exec('getResponse');\r\n                        me._responseJson = xhr.exec('getResponseAsJson');\r\n                        err = 'server';\r\n                    } else {\r\n                        err = 'http';\r\n                    }\r\n    \r\n                    xhr.destroy();\r\n                    xhr = null;\r\n    \r\n                    return err ? me.trigger( 'error', err ) : me.trigger('load');\r\n                });\r\n    \r\n                xhr.on( 'error', function() {\r\n                    xhr.off();\r\n                    me._xhr = null;\r\n                    me.trigger( 'error', 'http' );\r\n                });\r\n    \r\n                me._xhr = xhr;\r\n                return xhr;\r\n            },\r\n    \r\n            _setRequestHeader: function( xhr, headers ) {\r\n                $.each( headers, function( key, val ) {\r\n                    xhr.exec( 'setRequestHeader', key, val );\r\n                });\r\n            }\r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview 完全版本。\r\n     */\r\n    define('preset/all',[\r\n        'base',\r\n    \r\n        // widgets\r\n        'widgets/filednd',\r\n        'widgets/filepaste',\r\n        'widgets/filepicker',\r\n        'widgets/image',\r\n        'widgets/queue',\r\n        'widgets/runtime',\r\n        'widgets/upload',\r\n        'widgets/validator',\r\n    \r\n        // runtimes\r\n        // html5\r\n        'runtime/html5/blob',\r\n        'runtime/html5/dnd',\r\n        'runtime/html5/filepaste',\r\n        'runtime/html5/filepicker',\r\n        'runtime/html5/imagemeta/exif',\r\n        'runtime/html5/androidpatch',\r\n        'runtime/html5/image',\r\n        'runtime/html5/transport',\r\n    \r\n        // flash\r\n        'runtime/flash/filepicker',\r\n        'runtime/flash/image',\r\n        'runtime/flash/transport'\r\n    ], function( Base ) {\r\n        return Base;\r\n    });\r\n    define('webuploader',[\r\n        'preset/all'\r\n    ], function( preset ) {\r\n        return preset;\r\n    });\r\n    return require('webuploader');\r\n});\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/webuploader/webuploader.withoutimage.js",
    "content": "/*! WebUploader 0.1.2 */\r\n\r\n\r\n/**\r\n * @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。\r\n *\r\n * AMD API 内部的简单不完全实现，请忽略。只有当WebUploader被合并成一个文件的时候才会引入。\r\n */\r\n(function( root, factory ) {\r\n    var modules = {},\r\n\r\n        // 内部require, 简单不完全实现。\r\n        // https://github.com/amdjs/amdjs-api/wiki/require\r\n        _require = function( deps, callback ) {\r\n            var args, len, i;\r\n\r\n            // 如果deps不是数组，则直接返回指定module\r\n            if ( typeof deps === 'string' ) {\r\n                return getModule( deps );\r\n            } else {\r\n                args = [];\r\n                for( len = deps.length, i = 0; i < len; i++ ) {\r\n                    args.push( getModule( deps[ i ] ) );\r\n                }\r\n\r\n                return callback.apply( null, args );\r\n            }\r\n        },\r\n\r\n        // 内部define，暂时不支持不指定id.\r\n        _define = function( id, deps, factory ) {\r\n            if ( arguments.length === 2 ) {\r\n                factory = deps;\r\n                deps = null;\r\n            }\r\n\r\n            _require( deps || [], function() {\r\n                setModule( id, factory, arguments );\r\n            });\r\n        },\r\n\r\n        // 设置module, 兼容CommonJs写法。\r\n        setModule = function( id, factory, args ) {\r\n            var module = {\r\n                    exports: factory\r\n                },\r\n                returned;\r\n\r\n            if ( typeof factory === 'function' ) {\r\n                args.length || (args = [ _require, module.exports, module ]);\r\n                returned = factory.apply( null, args );\r\n                returned !== undefined && (module.exports = returned);\r\n            }\r\n\r\n            modules[ id ] = module.exports;\r\n        },\r\n\r\n        // 根据id获取module\r\n        getModule = function( id ) {\r\n            var module = modules[ id ] || root[ id ];\r\n\r\n            if ( !module ) {\r\n                throw new Error( '`' + id + '` is undefined' );\r\n            }\r\n\r\n            return module;\r\n        },\r\n\r\n        // 将所有modules，将路径ids装换成对象。\r\n        exportsTo = function( obj ) {\r\n            var key, host, parts, part, last, ucFirst;\r\n\r\n            // make the first character upper case.\r\n            ucFirst = function( str ) {\r\n                return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 ));\r\n            };\r\n\r\n            for ( key in modules ) {\r\n                host = obj;\r\n\r\n                if ( !modules.hasOwnProperty( key ) ) {\r\n                    continue;\r\n                }\r\n\r\n                parts = key.split('/');\r\n                last = ucFirst( parts.pop() );\r\n\r\n                while( (part = ucFirst( parts.shift() )) ) {\r\n                    host[ part ] = host[ part ] || {};\r\n                    host = host[ part ];\r\n                }\r\n\r\n                host[ last ] = modules[ key ];\r\n            }\r\n        },\r\n\r\n        exports = factory( root, _define, _require ),\r\n        origin;\r\n\r\n    // exports every module.\r\n    exportsTo( exports );\r\n\r\n    if ( typeof module === 'object' && typeof module.exports === 'object' ) {\r\n\r\n        // For CommonJS and CommonJS-like environments where a proper window is present,\r\n        module.exports = exports;\r\n    } else if ( typeof define === 'function' && define.amd ) {\r\n\r\n        // Allow using this built library as an AMD module\r\n        // in another project. That other project will only\r\n        // see this AMD call, not the internal modules in\r\n        // the closure below.\r\n        define([], exports );\r\n    } else {\r\n\r\n        // Browser globals case. Just assign the\r\n        // result to a property on the global.\r\n        origin = root.WebUploader;\r\n        root.WebUploader = exports;\r\n        root.WebUploader.noConflict = function() {\r\n            root.WebUploader = origin;\r\n        };\r\n    }\r\n})( this, function( window, define, require ) {\r\n\r\n\r\n    /**\r\n     * @fileOverview jQuery or Zepto\r\n     */\r\n    define('dollar-third',[],function() {\r\n        return window.jQuery || window.Zepto;\r\n    });\r\n    /**\r\n     * @fileOverview Dom 操作相关\r\n     */\r\n    define('dollar',[\r\n        'dollar-third'\r\n    ], function( _ ) {\r\n        return _;\r\n    });\r\n    /**\r\n     * @fileOverview 使用jQuery的Promise\r\n     */\r\n    define('promise-third',[\r\n        'dollar'\r\n    ], function( $ ) {\r\n        return {\r\n            Deferred: $.Deferred,\r\n            when: $.when,\r\n    \r\n            isPromise: function( anything ) {\r\n                return anything && typeof anything.then === 'function';\r\n            }\r\n        };\r\n    });\r\n    /**\r\n     * @fileOverview Promise/A+\r\n     */\r\n    define('promise',[\r\n        'promise-third'\r\n    ], function( _ ) {\r\n        return _;\r\n    });\r\n    /**\r\n     * @fileOverview 基础类方法。\r\n     */\r\n    \r\n    /**\r\n     * Web Uploader内部类的详细说明，以下提及的功能类，都可以在`WebUploader`这个变量中访问到。\r\n     *\r\n     * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id.\r\n     * 默认module id该文件的路径，而此路径将会转化成名字空间存放在WebUploader中。如：\r\n     *\r\n     * * module `base`：WebUploader.Base\r\n     * * module `file`: WebUploader.File\r\n     * * module `lib/dnd`: WebUploader.Lib.Dnd\r\n     * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd\r\n     *\r\n     *\r\n     * 以下文档将可能省略`WebUploader`前缀。\r\n     * @module WebUploader\r\n     * @title WebUploader API文档\r\n     */\r\n    define('base',[\r\n        'dollar',\r\n        'promise'\r\n    ], function( $, promise ) {\r\n    \r\n        var noop = function() {},\r\n            call = Function.call;\r\n    \r\n        // http://jsperf.com/uncurrythis\r\n        // 反科里化\r\n        function uncurryThis( fn ) {\r\n            return function() {\r\n                return call.apply( fn, arguments );\r\n            };\r\n        }\r\n    \r\n        function bindFn( fn, context ) {\r\n            return function() {\r\n                return fn.apply( context, arguments );\r\n            };\r\n        }\r\n    \r\n        function createObject( proto ) {\r\n            var f;\r\n    \r\n            if ( Object.create ) {\r\n                return Object.create( proto );\r\n            } else {\r\n                f = function() {};\r\n                f.prototype = proto;\r\n                return new f();\r\n            }\r\n        }\r\n    \r\n    \r\n        /**\r\n         * 基础类，提供一些简单常用的方法。\r\n         * @class Base\r\n         */\r\n        return {\r\n    \r\n            /**\r\n             * @property {String} version 当前版本号。\r\n             */\r\n            version: '0.1.2',\r\n    \r\n            /**\r\n             * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。\r\n             */\r\n            $: $,\r\n    \r\n            Deferred: promise.Deferred,\r\n    \r\n            isPromise: promise.isPromise,\r\n    \r\n            when: promise.when,\r\n    \r\n            /**\r\n             * @description  简单的浏览器检查结果。\r\n             *\r\n             * * `webkit`  webkit版本号，如果浏览器为非webkit内核，此属性为`undefined`。\r\n             * * `chrome`  chrome浏览器版本号，如果浏览器为chrome，此属性为`undefined`。\r\n             * * `ie`  ie浏览器版本号，如果浏览器为非ie，此属性为`undefined`。**暂不支持ie10+**\r\n             * * `firefox`  firefox浏览器版本号，如果浏览器为非firefox，此属性为`undefined`。\r\n             * * `safari`  safari浏览器版本号，如果浏览器为非safari，此属性为`undefined`。\r\n             * * `opera`  opera浏览器版本号，如果浏览器为非opera，此属性为`undefined`。\r\n             *\r\n             * @property {Object} [browser]\r\n             */\r\n            browser: (function( ua ) {\r\n                var ret = {},\r\n                    webkit = ua.match( /WebKit\\/([\\d.]+)/ ),\r\n                    chrome = ua.match( /Chrome\\/([\\d.]+)/ ) ||\r\n                        ua.match( /CriOS\\/([\\d.]+)/ ),\r\n    \r\n                    ie = ua.match( /MSIE\\s([\\d\\.]+)/ ) ||\r\n                        ua.match(/(?:trident)(?:.*rv:([\\w.]+))?/i),\r\n                    firefox = ua.match( /Firefox\\/([\\d.]+)/ ),\r\n                    safari = ua.match( /Safari\\/([\\d.]+)/ ),\r\n                    opera = ua.match( /OPR\\/([\\d.]+)/ );\r\n    \r\n                webkit && (ret.webkit = parseFloat( webkit[ 1 ] ));\r\n                chrome && (ret.chrome = parseFloat( chrome[ 1 ] ));\r\n                ie && (ret.ie = parseFloat( ie[ 1 ] ));\r\n                firefox && (ret.firefox = parseFloat( firefox[ 1 ] ));\r\n                safari && (ret.safari = parseFloat( safari[ 1 ] ));\r\n                opera && (ret.opera = parseFloat( opera[ 1 ] ));\r\n    \r\n                return ret;\r\n            })( navigator.userAgent ),\r\n    \r\n            /**\r\n             * @description  操作系统检查结果。\r\n             *\r\n             * * `android`  如果在android浏览器环境下，此值为对应的android版本号，否则为`undefined`。\r\n             * * `ios` 如果在ios浏览器环境下，此值为对应的ios版本号，否则为`undefined`。\r\n             * @property {Object} [os]\r\n             */\r\n            os: (function( ua ) {\r\n                var ret = {},\r\n    \r\n                    // osx = !!ua.match( /\\(Macintosh\\; Intel / ),\r\n                    android = ua.match( /(?:Android);?[\\s\\/]+([\\d.]+)?/ ),\r\n                    ios = ua.match( /(?:iPad|iPod|iPhone).*OS\\s([\\d_]+)/ );\r\n    \r\n                // osx && (ret.osx = true);\r\n                android && (ret.android = parseFloat( android[ 1 ] ));\r\n                ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) ));\r\n    \r\n                return ret;\r\n            })( navigator.userAgent ),\r\n    \r\n            /**\r\n             * 实现类与类之间的继承。\r\n             * @method inherits\r\n             * @grammar Base.inherits( super ) => child\r\n             * @grammar Base.inherits( super, protos ) => child\r\n             * @grammar Base.inherits( super, protos, statics ) => child\r\n             * @param  {Class} super 父类\r\n             * @param  {Object | Function} [protos] 子类或者对象。如果对象中包含constructor，子类将是用此属性值。\r\n             * @param  {Function} [protos.constructor] 子类构造器，不指定的话将创建个临时的直接执行父类构造器的方法。\r\n             * @param  {Object} [statics] 静态属性或方法。\r\n             * @return {Class} 返回子类。\r\n             * @example\r\n             * function Person() {\r\n             *     console.log( 'Super' );\r\n             * }\r\n             * Person.prototype.hello = function() {\r\n             *     console.log( 'hello' );\r\n             * };\r\n             *\r\n             * var Manager = Base.inherits( Person, {\r\n             *     world: function() {\r\n             *         console.log( 'World' );\r\n             *     }\r\n             * });\r\n             *\r\n             * // 因为没有指定构造器，父类的构造器将会执行。\r\n             * var instance = new Manager();    // => Super\r\n             *\r\n             * // 继承子父类的方法\r\n             * instance.hello();    // => hello\r\n             * instance.world();    // => World\r\n             *\r\n             * // 子类的__super__属性指向父类\r\n             * console.log( Manager.__super__ === Person );    // => true\r\n             */\r\n            inherits: function( Super, protos, staticProtos ) {\r\n                var child;\r\n    \r\n                if ( typeof protos === 'function' ) {\r\n                    child = protos;\r\n                    protos = null;\r\n                } else if ( protos && protos.hasOwnProperty('constructor') ) {\r\n                    child = protos.constructor;\r\n                } else {\r\n                    child = function() {\r\n                        return Super.apply( this, arguments );\r\n                    };\r\n                }\r\n    \r\n                // 复制静态方法\r\n                $.extend( true, child, Super, staticProtos || {} );\r\n    \r\n                /* jshint camelcase: false */\r\n    \r\n                // 让子类的__super__属性指向父类。\r\n                child.__super__ = Super.prototype;\r\n    \r\n                // 构建原型，添加原型方法或属性。\r\n                // 暂时用Object.create实现。\r\n                child.prototype = createObject( Super.prototype );\r\n                protos && $.extend( true, child.prototype, protos );\r\n    \r\n                return child;\r\n            },\r\n    \r\n            /**\r\n             * 一个不做任何事情的方法。可以用来赋值给默认的callback.\r\n             * @method noop\r\n             */\r\n            noop: noop,\r\n    \r\n            /**\r\n             * 返回一个新的方法，此方法将已指定的`context`来执行。\r\n             * @grammar Base.bindFn( fn, context ) => Function\r\n             * @method bindFn\r\n             * @example\r\n             * var doSomething = function() {\r\n             *         console.log( this.name );\r\n             *     },\r\n             *     obj = {\r\n             *         name: 'Object Name'\r\n             *     },\r\n             *     aliasFn = Base.bind( doSomething, obj );\r\n             *\r\n             *  aliasFn();    // => Object Name\r\n             *\r\n             */\r\n            bindFn: bindFn,\r\n    \r\n            /**\r\n             * 引用Console.log如果存在的话，否则引用一个[空函数loop](#WebUploader:Base.log)。\r\n             * @grammar Base.log( args... ) => undefined\r\n             * @method log\r\n             */\r\n            log: (function() {\r\n                if ( window.console ) {\r\n                    return bindFn( console.log, console );\r\n                }\r\n                return noop;\r\n            })(),\r\n    \r\n            nextTick: (function() {\r\n    \r\n                return function( cb ) {\r\n                    setTimeout( cb, 1 );\r\n                };\r\n    \r\n                // @bug 当浏览器不在当前窗口时就停了。\r\n                // var next = window.requestAnimationFrame ||\r\n                //     window.webkitRequestAnimationFrame ||\r\n                //     window.mozRequestAnimationFrame ||\r\n                //     function( cb ) {\r\n                //         window.setTimeout( cb, 1000 / 60 );\r\n                //     };\r\n    \r\n                // // fix: Uncaught TypeError: Illegal invocation\r\n                // return bindFn( next, window );\r\n            })(),\r\n    \r\n            /**\r\n             * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。\r\n             * 将用来将非数组对象转化成数组对象。\r\n             * @grammar Base.slice( target, start[, end] ) => Array\r\n             * @method slice\r\n             * @example\r\n             * function doSomthing() {\r\n             *     var args = Base.slice( arguments, 1 );\r\n             *     console.log( args );\r\n             * }\r\n             *\r\n             * doSomthing( 'ignored', 'arg2', 'arg3' );    // => Array [\"arg2\", \"arg3\"]\r\n             */\r\n            slice: uncurryThis( [].slice ),\r\n    \r\n            /**\r\n             * 生成唯一的ID\r\n             * @method guid\r\n             * @grammar Base.guid() => String\r\n             * @grammar Base.guid( prefx ) => String\r\n             */\r\n            guid: (function() {\r\n                var counter = 0;\r\n    \r\n                return function( prefix ) {\r\n                    var guid = (+new Date()).toString( 32 ),\r\n                        i = 0;\r\n    \r\n                    for ( ; i < 5; i++ ) {\r\n                        guid += Math.floor( Math.random() * 65535 ).toString( 32 );\r\n                    }\r\n    \r\n                    return (prefix || 'wu_') + guid + (counter++).toString( 32 );\r\n                };\r\n            })(),\r\n    \r\n            /**\r\n             * 格式化文件大小, 输出成带单位的字符串\r\n             * @method formatSize\r\n             * @grammar Base.formatSize( size ) => String\r\n             * @grammar Base.formatSize( size, pointLength ) => String\r\n             * @grammar Base.formatSize( size, pointLength, units ) => String\r\n             * @param {Number} size 文件大小\r\n             * @param {Number} [pointLength=2] 精确到的小数点数。\r\n             * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节，到千字节，一直往上指定。如果单位数组里面只指定了到了K(千字节)，同时文件大小大于M, 此方法的输出将还是显示成多少K.\r\n             * @example\r\n             * console.log( Base.formatSize( 100 ) );    // => 100B\r\n             * console.log( Base.formatSize( 1024 ) );    // => 1.00K\r\n             * console.log( Base.formatSize( 1024, 0 ) );    // => 1K\r\n             * console.log( Base.formatSize( 1024 * 1024 ) );    // => 1.00M\r\n             * console.log( Base.formatSize( 1024 * 1024 * 1024 ) );    // => 1.00G\r\n             * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) );    // => 1024MB\r\n             */\r\n            formatSize: function( size, pointLength, units ) {\r\n                var unit;\r\n    \r\n                units = units || [ 'B', 'K', 'M', 'G', 'TB' ];\r\n    \r\n                while ( (unit = units.shift()) && size > 1024 ) {\r\n                    size = size / 1024;\r\n                }\r\n    \r\n                return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) +\r\n                        unit;\r\n            }\r\n        };\r\n    });\r\n    /**\r\n     * 事件处理类，可以独立使用，也可以扩展给对象使用。\r\n     * @fileOverview Mediator\r\n     */\r\n    define('mediator',[\r\n        'base'\r\n    ], function( Base ) {\r\n        var $ = Base.$,\r\n            slice = [].slice,\r\n            separator = /\\s+/,\r\n            protos;\r\n    \r\n        // 根据条件过滤出事件handlers.\r\n        function findHandlers( arr, name, callback, context ) {\r\n            return $.grep( arr, function( handler ) {\r\n                return handler &&\r\n                        (!name || handler.e === name) &&\r\n                        (!callback || handler.cb === callback ||\r\n                        handler.cb._cb === callback) &&\r\n                        (!context || handler.ctx === context);\r\n            });\r\n        }\r\n    \r\n        function eachEvent( events, callback, iterator ) {\r\n            // 不支持对象，只支持多个event用空格隔开\r\n            $.each( (events || '').split( separator ), function( _, key ) {\r\n                iterator( key, callback );\r\n            });\r\n        }\r\n    \r\n        function triggerHanders( events, args ) {\r\n            var stoped = false,\r\n                i = -1,\r\n                len = events.length,\r\n                handler;\r\n    \r\n            while ( ++i < len ) {\r\n                handler = events[ i ];\r\n    \r\n                if ( handler.cb.apply( handler.ctx2, args ) === false ) {\r\n                    stoped = true;\r\n                    break;\r\n                }\r\n            }\r\n    \r\n            return !stoped;\r\n        }\r\n    \r\n        protos = {\r\n    \r\n            /**\r\n             * 绑定事件。\r\n             *\r\n             * `callback`方法在执行时，arguments将会来源于trigger的时候携带的参数。如\r\n             * ```javascript\r\n             * var obj = {};\r\n             *\r\n             * // 使得obj有事件行为\r\n             * Mediator.installTo( obj );\r\n             *\r\n             * obj.on( 'testa', function( arg1, arg2 ) {\r\n             *     console.log( arg1, arg2 ); // => 'arg1', 'arg2'\r\n             * });\r\n             *\r\n             * obj.trigger( 'testa', 'arg1', 'arg2' );\r\n             * ```\r\n             *\r\n             * 如果`callback`中，某一个方法`return false`了，则后续的其他`callback`都不会被执行到。\r\n             * 切会影响到`trigger`方法的返回值，为`false`。\r\n             *\r\n             * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处，\r\n             * 就是第一个参数为`type`，记录当前是什么事件在触发。此类`callback`的优先级比脚低，会再正常`callback`执行完后触发。\r\n             * ```javascript\r\n             * obj.on( 'all', function( type, arg1, arg2 ) {\r\n             *     console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2'\r\n             * });\r\n             * ```\r\n             *\r\n             * @method on\r\n             * @grammar on( name, callback[, context] ) => self\r\n             * @param  {String}   name     事件名，支持多个事件用空格隔开\r\n             * @param  {Function} callback 事件处理器\r\n             * @param  {Object}   [context]  事件处理器的上下文。\r\n             * @return {self} 返回自身，方便链式\r\n             * @chainable\r\n             * @class Mediator\r\n             */\r\n            on: function( name, callback, context ) {\r\n                var me = this,\r\n                    set;\r\n    \r\n                if ( !callback ) {\r\n                    return this;\r\n                }\r\n    \r\n                set = this._events || (this._events = []);\r\n    \r\n                eachEvent( name, callback, function( name, callback ) {\r\n                    var handler = { e: name };\r\n    \r\n                    handler.cb = callback;\r\n                    handler.ctx = context;\r\n                    handler.ctx2 = context || me;\r\n                    handler.id = set.length;\r\n    \r\n                    set.push( handler );\r\n                });\r\n    \r\n                return this;\r\n            },\r\n    \r\n            /**\r\n             * 绑定事件，且当handler执行完后，自动解除绑定。\r\n             * @method once\r\n             * @grammar once( name, callback[, context] ) => self\r\n             * @param  {String}   name     事件名\r\n             * @param  {Function} callback 事件处理器\r\n             * @param  {Object}   [context]  事件处理器的上下文。\r\n             * @return {self} 返回自身，方便链式\r\n             * @chainable\r\n             */\r\n            once: function( name, callback, context ) {\r\n                var me = this;\r\n    \r\n                if ( !callback ) {\r\n                    return me;\r\n                }\r\n    \r\n                eachEvent( name, callback, function( name, callback ) {\r\n                    var once = function() {\r\n                            me.off( name, once );\r\n                            return callback.apply( context || me, arguments );\r\n                        };\r\n    \r\n                    once._cb = callback;\r\n                    me.on( name, once, context );\r\n                });\r\n    \r\n                return me;\r\n            },\r\n    \r\n            /**\r\n             * 解除事件绑定\r\n             * @method off\r\n             * @grammar off( [name[, callback[, context] ] ] ) => self\r\n             * @param  {String}   [name]     事件名\r\n             * @param  {Function} [callback] 事件处理器\r\n             * @param  {Object}   [context]  事件处理器的上下文。\r\n             * @return {self} 返回自身，方便链式\r\n             * @chainable\r\n             */\r\n            off: function( name, cb, ctx ) {\r\n                var events = this._events;\r\n    \r\n                if ( !events ) {\r\n                    return this;\r\n                }\r\n    \r\n                if ( !name && !cb && !ctx ) {\r\n                    this._events = [];\r\n                    return this;\r\n                }\r\n    \r\n                eachEvent( name, cb, function( name, cb ) {\r\n                    $.each( findHandlers( events, name, cb, ctx ), function() {\r\n                        delete events[ this.id ];\r\n                    });\r\n                });\r\n    \r\n                return this;\r\n            },\r\n    \r\n            /**\r\n             * 触发事件\r\n             * @method trigger\r\n             * @grammar trigger( name[, args...] ) => self\r\n             * @param  {String}   type     事件名\r\n             * @param  {*} [...] 任意参数\r\n             * @return {Boolean} 如果handler中return false了，则返回false, 否则返回true\r\n             */\r\n            trigger: function( type ) {\r\n                var args, events, allEvents;\r\n    \r\n                if ( !this._events || !type ) {\r\n                    return this;\r\n                }\r\n    \r\n                args = slice.call( arguments, 1 );\r\n                events = findHandlers( this._events, type );\r\n                allEvents = findHandlers( this._events, 'all' );\r\n    \r\n                return triggerHanders( events, args ) &&\r\n                        triggerHanders( allEvents, arguments );\r\n            }\r\n        };\r\n    \r\n        /**\r\n         * 中介者，它本身是个单例，但可以通过[installTo](#WebUploader:Mediator:installTo)方法，使任何对象具备事件行为。\r\n         * 主要目的是负责模块与模块之间的合作，降低耦合度。\r\n         *\r\n         * @class Mediator\r\n         */\r\n        return $.extend({\r\n    \r\n            /**\r\n             * 可以通过这个接口，使任何对象具备事件功能。\r\n             * @method installTo\r\n             * @param  {Object} obj 需要具备事件行为的对象。\r\n             * @return {Object} 返回obj.\r\n             */\r\n            installTo: function( obj ) {\r\n                return $.extend( obj, protos );\r\n            }\r\n    \r\n        }, protos );\r\n    });\r\n    /**\r\n     * @fileOverview Uploader上传类\r\n     */\r\n    define('uploader',[\r\n        'base',\r\n        'mediator'\r\n    ], function( Base, Mediator ) {\r\n    \r\n        var $ = Base.$;\r\n    \r\n        /**\r\n         * 上传入口类。\r\n         * @class Uploader\r\n         * @constructor\r\n         * @grammar new Uploader( opts ) => Uploader\r\n         * @example\r\n         * var uploader = WebUploader.Uploader({\r\n         *     swf: 'path_of_swf/Uploader.swf',\r\n         *\r\n         *     // 开起分片上传。\r\n         *     chunked: true\r\n         * });\r\n         */\r\n        function Uploader( opts ) {\r\n            this.options = $.extend( true, {}, Uploader.options, opts );\r\n            this._init( this.options );\r\n        }\r\n    \r\n        // default Options\r\n        // widgets中有相应扩展\r\n        Uploader.options = {};\r\n        Mediator.installTo( Uploader.prototype );\r\n    \r\n        // 批量添加纯命令式方法。\r\n        $.each({\r\n            upload: 'start-upload',\r\n            stop: 'stop-upload',\r\n            getFile: 'get-file',\r\n            getFiles: 'get-files',\r\n            addFile: 'add-file',\r\n            addFiles: 'add-file',\r\n            sort: 'sort-files',\r\n            removeFile: 'remove-file',\r\n            skipFile: 'skip-file',\r\n            retry: 'retry',\r\n            isInProgress: 'is-in-progress',\r\n            makeThumb: 'make-thumb',\r\n            getDimension: 'get-dimension',\r\n            addButton: 'add-btn',\r\n            getRuntimeType: 'get-runtime-type',\r\n            refresh: 'refresh',\r\n            disable: 'disable',\r\n            enable: 'enable',\r\n            reset: 'reset'\r\n        }, function( fn, command ) {\r\n            Uploader.prototype[ fn ] = function() {\r\n                return this.request( command, arguments );\r\n            };\r\n        });\r\n    \r\n        $.extend( Uploader.prototype, {\r\n            state: 'pending',\r\n    \r\n            _init: function( opts ) {\r\n                var me = this;\r\n    \r\n                me.request( 'init', opts, function() {\r\n                    me.state = 'ready';\r\n                    me.trigger('ready');\r\n                });\r\n            },\r\n    \r\n            /**\r\n             * 获取或者设置Uploader配置项。\r\n             * @method option\r\n             * @grammar option( key ) => *\r\n             * @grammar option( key, val ) => self\r\n             * @example\r\n             *\r\n             * // 初始状态图片上传前不会压缩\r\n             * var uploader = new WebUploader.Uploader({\r\n             *     resize: null;\r\n             * });\r\n             *\r\n             * // 修改后图片上传前，尝试将图片压缩到1600 * 1600\r\n             * uploader.options( 'resize', {\r\n             *     width: 1600,\r\n             *     height: 1600\r\n             * });\r\n             */\r\n            option: function( key, val ) {\r\n                var opts = this.options;\r\n    \r\n                // setter\r\n                if ( arguments.length > 1 ) {\r\n    \r\n                    if ( $.isPlainObject( val ) &&\r\n                            $.isPlainObject( opts[ key ] ) ) {\r\n                        $.extend( opts[ key ], val );\r\n                    } else {\r\n                        opts[ key ] = val;\r\n                    }\r\n    \r\n                } else {    // getter\r\n                    return key ? opts[ key ] : opts;\r\n                }\r\n            },\r\n    \r\n            /**\r\n             * 获取文件统计信息。返回一个包含一下信息的对象。\r\n             * * `successNum` 上传成功的文件数\r\n             * * `uploadFailNum` 上传失败的文件数\r\n             * * `cancelNum` 被删除的文件数\r\n             * * `invalidNum` 无效的文件数\r\n             * * `queueNum` 还在队列中的文件数\r\n             * @method getStats\r\n             * @grammar getStats() => Object\r\n             */\r\n            getStats: function() {\r\n                // return this._mgr.getStats.apply( this._mgr, arguments );\r\n                var stats = this.request('get-stats');\r\n    \r\n                return {\r\n                    successNum: stats.numOfSuccess,\r\n    \r\n                    // who care?\r\n                    // queueFailNum: 0,\r\n                    cancelNum: stats.numOfCancel,\r\n                    invalidNum: stats.numOfInvalid,\r\n                    uploadFailNum: stats.numOfUploadFailed,\r\n                    queueNum: stats.numOfQueue\r\n                };\r\n            },\r\n    \r\n            // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器\r\n            trigger: function( type/*, args...*/ ) {\r\n                var args = [].slice.call( arguments, 1 ),\r\n                    opts = this.options,\r\n                    name = 'on' + type.substring( 0, 1 ).toUpperCase() +\r\n                        type.substring( 1 );\r\n    \r\n                if (\r\n                        // 调用通过on方法注册的handler.\r\n                        Mediator.trigger.apply( this, arguments ) === false ||\r\n    \r\n                        // 调用opts.onEvent\r\n                        $.isFunction( opts[ name ] ) &&\r\n                        opts[ name ].apply( this, args ) === false ||\r\n    \r\n                        // 调用this.onEvent\r\n                        $.isFunction( this[ name ] ) &&\r\n                        this[ name ].apply( this, args ) === false ||\r\n    \r\n                        // 广播所有uploader的事件。\r\n                        Mediator.trigger.apply( Mediator,\r\n                        [ this, type ].concat( args ) ) === false ) {\r\n    \r\n                    return false;\r\n                }\r\n    \r\n                return true;\r\n            },\r\n    \r\n            // widgets/widget.js将补充此方法的详细文档。\r\n            request: Base.noop\r\n        });\r\n    \r\n        /**\r\n         * 创建Uploader实例，等同于new Uploader( opts );\r\n         * @method create\r\n         * @class Base\r\n         * @static\r\n         * @grammar Base.create( opts ) => Uploader\r\n         */\r\n        Base.create = Uploader.create = function( opts ) {\r\n            return new Uploader( opts );\r\n        };\r\n    \r\n        // 暴露Uploader，可以通过它来扩展业务逻辑。\r\n        Base.Uploader = Uploader;\r\n    \r\n        return Uploader;\r\n    });\r\n    /**\r\n     * @fileOverview Runtime管理器，负责Runtime的选择, 连接\r\n     */\r\n    define('runtime/runtime',[\r\n        'base',\r\n        'mediator'\r\n    ], function( Base, Mediator ) {\r\n    \r\n        var $ = Base.$,\r\n            factories = {},\r\n    \r\n            // 获取对象的第一个key\r\n            getFirstKey = function( obj ) {\r\n                for ( var key in obj ) {\r\n                    if ( obj.hasOwnProperty( key ) ) {\r\n                        return key;\r\n                    }\r\n                }\r\n                return null;\r\n            };\r\n    \r\n        // 接口类。\r\n        function Runtime( options ) {\r\n            this.options = $.extend({\r\n                container: document.body\r\n            }, options );\r\n            this.uid = Base.guid('rt_');\r\n        }\r\n    \r\n        $.extend( Runtime.prototype, {\r\n    \r\n            getContainer: function() {\r\n                var opts = this.options,\r\n                    parent, container;\r\n    \r\n                if ( this._container ) {\r\n                    return this._container;\r\n                }\r\n    \r\n                parent = $( opts.container || document.body );\r\n                container = $( document.createElement('div') );\r\n    \r\n                container.attr( 'id', 'rt_' + this.uid );\r\n                container.css({\r\n                    position: 'absolute',\r\n                    top: '0px',\r\n                    left: '0px',\r\n                    width: '1px',\r\n                    height: '1px',\r\n                    overflow: 'hidden'\r\n                });\r\n    \r\n                parent.append( container );\r\n                parent.addClass('webuploader-container');\r\n                this._container = container;\r\n                return container;\r\n            },\r\n    \r\n            init: Base.noop,\r\n            exec: Base.noop,\r\n    \r\n            destroy: function() {\r\n                if ( this._container ) {\r\n                    this._container.parentNode.removeChild( this.__container );\r\n                }\r\n    \r\n                this.off();\r\n            }\r\n        });\r\n    \r\n        Runtime.orders = 'html5,flash';\r\n    \r\n    \r\n        /**\r\n         * 添加Runtime实现。\r\n         * @param {String} type    类型\r\n         * @param {Runtime} factory 具体Runtime实现。\r\n         */\r\n        Runtime.addRuntime = function( type, factory ) {\r\n            factories[ type ] = factory;\r\n        };\r\n    \r\n        Runtime.hasRuntime = function( type ) {\r\n            return !!(type ? factories[ type ] : getFirstKey( factories ));\r\n        };\r\n    \r\n        Runtime.create = function( opts, orders ) {\r\n            var type, runtime;\r\n    \r\n            orders = orders || Runtime.orders;\r\n            $.each( orders.split( /\\s*,\\s*/g ), function() {\r\n                if ( factories[ this ] ) {\r\n                    type = this;\r\n                    return false;\r\n                }\r\n            });\r\n    \r\n            type = type || getFirstKey( factories );\r\n    \r\n            if ( !type ) {\r\n                throw new Error('Runtime Error');\r\n            }\r\n    \r\n            runtime = new factories[ type ]( opts );\r\n            return runtime;\r\n        };\r\n    \r\n        Mediator.installTo( Runtime.prototype );\r\n        return Runtime;\r\n    });\r\n    \r\n    /**\r\n     * @fileOverview Runtime管理器，负责Runtime的选择, 连接\r\n     */\r\n    define('runtime/client',[\r\n        'base',\r\n        'mediator',\r\n        'runtime/runtime'\r\n    ], function( Base, Mediator, Runtime ) {\r\n    \r\n        var cache;\r\n    \r\n        cache = (function() {\r\n            var obj = {};\r\n    \r\n            return {\r\n                add: function( runtime ) {\r\n                    obj[ runtime.uid ] = runtime;\r\n                },\r\n    \r\n                get: function( ruid, standalone ) {\r\n                    var i;\r\n    \r\n                    if ( ruid ) {\r\n                        return obj[ ruid ];\r\n                    }\r\n    \r\n                    for ( i in obj ) {\r\n                        // 有些类型不能重用，比如filepicker.\r\n                        if ( standalone && obj[ i ].__standalone ) {\r\n                            continue;\r\n                        }\r\n    \r\n                        return obj[ i ];\r\n                    }\r\n    \r\n                    return null;\r\n                },\r\n    \r\n                remove: function( runtime ) {\r\n                    delete obj[ runtime.uid ];\r\n                }\r\n            };\r\n        })();\r\n    \r\n        function RuntimeClient( component, standalone ) {\r\n            var deferred = Base.Deferred(),\r\n                runtime;\r\n    \r\n            this.uid = Base.guid('client_');\r\n    \r\n            // 允许runtime没有初始化之前，注册一些方法在初始化后执行。\r\n            this.runtimeReady = function( cb ) {\r\n                return deferred.done( cb );\r\n            };\r\n    \r\n            this.connectRuntime = function( opts, cb ) {\r\n    \r\n                // already connected.\r\n                if ( runtime ) {\r\n                    throw new Error('already connected!');\r\n                }\r\n    \r\n                deferred.done( cb );\r\n    \r\n                if ( typeof opts === 'string' && cache.get( opts ) ) {\r\n                    runtime = cache.get( opts );\r\n                }\r\n    \r\n                // 像filePicker只能独立存在，不能公用。\r\n                runtime = runtime || cache.get( null, standalone );\r\n    \r\n                // 需要创建\r\n                if ( !runtime ) {\r\n                    runtime = Runtime.create( opts, opts.runtimeOrder );\r\n                    runtime.__promise = deferred.promise();\r\n                    runtime.once( 'ready', deferred.resolve );\r\n                    runtime.init();\r\n                    cache.add( runtime );\r\n                    runtime.__client = 1;\r\n                } else {\r\n                    // 来自cache\r\n                    Base.$.extend( runtime.options, opts );\r\n                    runtime.__promise.then( deferred.resolve );\r\n                    runtime.__client++;\r\n                }\r\n    \r\n                standalone && (runtime.__standalone = standalone);\r\n                return runtime;\r\n            };\r\n    \r\n            this.getRuntime = function() {\r\n                return runtime;\r\n            };\r\n    \r\n            this.disconnectRuntime = function() {\r\n                if ( !runtime ) {\r\n                    return;\r\n                }\r\n    \r\n                runtime.__client--;\r\n    \r\n                if ( runtime.__client <= 0 ) {\r\n                    cache.remove( runtime );\r\n                    delete runtime.__promise;\r\n                    runtime.destroy();\r\n                }\r\n    \r\n                runtime = null;\r\n            };\r\n    \r\n            this.exec = function() {\r\n                if ( !runtime ) {\r\n                    return;\r\n                }\r\n    \r\n                var args = Base.slice( arguments );\r\n                component && args.unshift( component );\r\n    \r\n                return runtime.exec.apply( this, args );\r\n            };\r\n    \r\n            this.getRuid = function() {\r\n                return runtime && runtime.uid;\r\n            };\r\n    \r\n            this.destroy = (function( destroy ) {\r\n                return function() {\r\n                    destroy && destroy.apply( this, arguments );\r\n                    this.trigger('destroy');\r\n                    this.off();\r\n                    this.exec('destroy');\r\n                    this.disconnectRuntime();\r\n                };\r\n            })( this.destroy );\r\n        }\r\n    \r\n        Mediator.installTo( RuntimeClient.prototype );\r\n        return RuntimeClient;\r\n    });\r\n    /**\r\n     * @fileOverview 错误信息\r\n     */\r\n    define('lib/dnd',[\r\n        'base',\r\n        'mediator',\r\n        'runtime/client'\r\n    ], function( Base, Mediator, RuntimeClent ) {\r\n    \r\n        var $ = Base.$;\r\n    \r\n        function DragAndDrop( opts ) {\r\n            opts = this.options = $.extend({}, DragAndDrop.options, opts );\r\n    \r\n            opts.container = $( opts.container );\r\n    \r\n            if ( !opts.container.length ) {\r\n                return;\r\n            }\r\n    \r\n            RuntimeClent.call( this, 'DragAndDrop' );\r\n        }\r\n    \r\n        DragAndDrop.options = {\r\n            accept: null,\r\n            disableGlobalDnd: false\r\n        };\r\n    \r\n        Base.inherits( RuntimeClent, {\r\n            constructor: DragAndDrop,\r\n    \r\n            init: function() {\r\n                var me = this;\r\n    \r\n                me.connectRuntime( me.options, function() {\r\n                    me.exec('init');\r\n                    me.trigger('ready');\r\n                });\r\n            },\r\n    \r\n            destroy: function() {\r\n                this.disconnectRuntime();\r\n            }\r\n        });\r\n    \r\n        Mediator.installTo( DragAndDrop.prototype );\r\n    \r\n        return DragAndDrop;\r\n    });\r\n    /**\r\n     * @fileOverview 组件基类。\r\n     */\r\n    define('widgets/widget',[\r\n        'base',\r\n        'uploader'\r\n    ], function( Base, Uploader ) {\r\n    \r\n        var $ = Base.$,\r\n            _init = Uploader.prototype._init,\r\n            IGNORE = {},\r\n            widgetClass = [];\r\n    \r\n        function isArrayLike( obj ) {\r\n            if ( !obj ) {\r\n                return false;\r\n            }\r\n    \r\n            var length = obj.length,\r\n                type = $.type( obj );\r\n    \r\n            if ( obj.nodeType === 1 && length ) {\r\n                return true;\r\n            }\r\n    \r\n            return type === 'array' || type !== 'function' && type !== 'string' &&\r\n                    (length === 0 || typeof length === 'number' && length > 0 &&\r\n                    (length - 1) in obj);\r\n        }\r\n    \r\n        function Widget( uploader ) {\r\n            this.owner = uploader;\r\n            this.options = uploader.options;\r\n        }\r\n    \r\n        $.extend( Widget.prototype, {\r\n    \r\n            init: Base.noop,\r\n    \r\n            // 类Backbone的事件监听声明，监听uploader实例上的事件\r\n            // widget直接无法监听事件，事件只能通过uploader来传递\r\n            invoke: function( apiName, args ) {\r\n    \r\n                /*\r\n                    {\r\n                        'make-thumb': 'makeThumb'\r\n                    }\r\n                 */\r\n                var map = this.responseMap;\r\n    \r\n                // 如果无API响应声明则忽略\r\n                if ( !map || !(apiName in map) || !(map[ apiName ] in this) ||\r\n                        !$.isFunction( this[ map[ apiName ] ] ) ) {\r\n    \r\n                    return IGNORE;\r\n                }\r\n    \r\n                return this[ map[ apiName ] ].apply( this, args );\r\n    \r\n            },\r\n    \r\n            /**\r\n             * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。\r\n             * @method request\r\n             * @grammar request( command, args ) => * | Promise\r\n             * @grammar request( command, args, callback ) => Promise\r\n             * @for  Uploader\r\n             */\r\n            request: function() {\r\n                return this.owner.request.apply( this.owner, arguments );\r\n            }\r\n        });\r\n    \r\n        // 扩展Uploader.\r\n        $.extend( Uploader.prototype, {\r\n    \r\n            // 覆写_init用来初始化widgets\r\n            _init: function() {\r\n                var me = this,\r\n                    widgets = me._widgets = [];\r\n    \r\n                $.each( widgetClass, function( _, klass ) {\r\n                    widgets.push( new klass( me ) );\r\n                });\r\n    \r\n                return _init.apply( me, arguments );\r\n            },\r\n    \r\n            request: function( apiName, args, callback ) {\r\n                var i = 0,\r\n                    widgets = this._widgets,\r\n                    len = widgets.length,\r\n                    rlts = [],\r\n                    dfds = [],\r\n                    widget, rlt, promise, key;\r\n    \r\n                args = isArrayLike( args ) ? args : [ args ];\r\n    \r\n                for ( ; i < len; i++ ) {\r\n                    widget = widgets[ i ];\r\n                    rlt = widget.invoke( apiName, args );\r\n    \r\n                    if ( rlt !== IGNORE ) {\r\n    \r\n                        // Deferred对象\r\n                        if ( Base.isPromise( rlt ) ) {\r\n                            dfds.push( rlt );\r\n                        } else {\r\n                            rlts.push( rlt );\r\n                        }\r\n                    }\r\n                }\r\n    \r\n                // 如果有callback，则用异步方式。\r\n                if ( callback || dfds.length ) {\r\n                    promise = Base.when.apply( Base, dfds );\r\n                    key = promise.pipe ? 'pipe' : 'then';\r\n    \r\n                    // 很重要不能删除。删除了会死循环。\r\n                    // 保证执行顺序。让callback总是在下一个tick中执行。\r\n                    return promise[ key ](function() {\r\n                                var deferred = Base.Deferred(),\r\n                                    args = arguments;\r\n    \r\n                                setTimeout(function() {\r\n                                    deferred.resolve.apply( deferred, args );\r\n                                }, 1 );\r\n    \r\n                                return deferred.promise();\r\n                            })[ key ]( callback || Base.noop );\r\n                } else {\r\n                    return rlts[ 0 ];\r\n                }\r\n            }\r\n        });\r\n    \r\n        /**\r\n         * 添加组件\r\n         * @param  {object} widgetProto 组件原型，构造函数通过constructor属性定义\r\n         * @param  {object} responseMap API名称与函数实现的映射\r\n         * @example\r\n         *     Uploader.register( {\r\n         *         init: function( options ) {},\r\n         *         makeThumb: function() {}\r\n         *     }, {\r\n         *         'make-thumb': 'makeThumb'\r\n         *     } );\r\n         */\r\n        Uploader.register = Widget.register = function( responseMap, widgetProto ) {\r\n            var map = { init: 'init' },\r\n                klass;\r\n    \r\n            if ( arguments.length === 1 ) {\r\n                widgetProto = responseMap;\r\n                widgetProto.responseMap = map;\r\n            } else {\r\n                widgetProto.responseMap = $.extend( map, responseMap );\r\n            }\r\n    \r\n            klass = Base.inherits( Widget, widgetProto );\r\n            widgetClass.push( klass );\r\n    \r\n            return klass;\r\n        };\r\n    \r\n        return Widget;\r\n    });\r\n    /**\r\n     * @fileOverview DragAndDrop Widget。\r\n     */\r\n    define('widgets/filednd',[\r\n        'base',\r\n        'uploader',\r\n        'lib/dnd',\r\n        'widgets/widget'\r\n    ], function( Base, Uploader, Dnd ) {\r\n        var $ = Base.$;\r\n    \r\n        Uploader.options.dnd = '';\r\n    \r\n        /**\r\n         * @property {Selector} [dnd=undefined]  指定Drag And Drop拖拽的容器，如果不指定，则不启动。\r\n         * @namespace options\r\n         * @for Uploader\r\n         */\r\n    \r\n        /**\r\n         * @event dndAccept\r\n         * @param {DataTransferItemList} items DataTransferItem\r\n         * @description 阻止此事件可以拒绝某些类型的文件拖入进来。目前只有 chrome 提供这样的 API，且只能通过 mime-type 验证。\r\n         * @for  Uploader\r\n         */\r\n        return Uploader.register({\r\n            init: function( opts ) {\r\n    \r\n                if ( !opts.dnd ||\r\n                        this.request('predict-runtime-type') !== 'html5' ) {\r\n                    return;\r\n                }\r\n    \r\n                var me = this,\r\n                    deferred = Base.Deferred(),\r\n                    options = $.extend({}, {\r\n                        disableGlobalDnd: opts.disableGlobalDnd,\r\n                        container: opts.dnd,\r\n                        accept: opts.accept\r\n                    }),\r\n                    dnd;\r\n    \r\n                dnd = new Dnd( options );\r\n    \r\n                dnd.once( 'ready', deferred.resolve );\r\n                dnd.on( 'drop', function( files ) {\r\n                    me.request( 'add-file', [ files ]);\r\n                });\r\n    \r\n                // 检测文件是否全部允许添加。\r\n                dnd.on( 'accept', function( items ) {\r\n                    return me.owner.trigger( 'dndAccept', items );\r\n                });\r\n    \r\n                dnd.init();\r\n    \r\n                return deferred.promise();\r\n            }\r\n        });\r\n    });\r\n    \r\n    /**\r\n     * @fileOverview 错误信息\r\n     */\r\n    define('lib/filepaste',[\r\n        'base',\r\n        'mediator',\r\n        'runtime/client'\r\n    ], function( Base, Mediator, RuntimeClent ) {\r\n    \r\n        var $ = Base.$;\r\n    \r\n        function FilePaste( opts ) {\r\n            opts = this.options = $.extend({}, opts );\r\n            opts.container = $( opts.container || document.body );\r\n            RuntimeClent.call( this, 'FilePaste' );\r\n        }\r\n    \r\n        Base.inherits( RuntimeClent, {\r\n            constructor: FilePaste,\r\n    \r\n            init: function() {\r\n                var me = this;\r\n    \r\n                me.connectRuntime( me.options, function() {\r\n                    me.exec('init');\r\n                    me.trigger('ready');\r\n                });\r\n            },\r\n    \r\n            destroy: function() {\r\n                this.exec('destroy');\r\n                this.disconnectRuntime();\r\n                this.off();\r\n            }\r\n        });\r\n    \r\n        Mediator.installTo( FilePaste.prototype );\r\n    \r\n        return FilePaste;\r\n    });\r\n    /**\r\n     * @fileOverview 组件基类。\r\n     */\r\n    define('widgets/filepaste',[\r\n        'base',\r\n        'uploader',\r\n        'lib/filepaste',\r\n        'widgets/widget'\r\n    ], function( Base, Uploader, FilePaste ) {\r\n        var $ = Base.$;\r\n    \r\n        /**\r\n         * @property {Selector} [paste=undefined]  指定监听paste事件的容器，如果不指定，不启用此功能。此功能为通过粘贴来添加截屏的图片。建议设置为`document.body`.\r\n         * @namespace options\r\n         * @for Uploader\r\n         */\r\n        return Uploader.register({\r\n            init: function( opts ) {\r\n    \r\n                if ( !opts.paste ||\r\n                        this.request('predict-runtime-type') !== 'html5' ) {\r\n                    return;\r\n                }\r\n    \r\n                var me = this,\r\n                    deferred = Base.Deferred(),\r\n                    options = $.extend({}, {\r\n                        container: opts.paste,\r\n                        accept: opts.accept\r\n                    }),\r\n                    paste;\r\n    \r\n                paste = new FilePaste( options );\r\n    \r\n                paste.once( 'ready', deferred.resolve );\r\n                paste.on( 'paste', function( files ) {\r\n                    me.owner.request( 'add-file', [ files ]);\r\n                });\r\n                paste.init();\r\n    \r\n                return deferred.promise();\r\n            }\r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview Blob\r\n     */\r\n    define('lib/blob',[\r\n        'base',\r\n        'runtime/client'\r\n    ], function( Base, RuntimeClient ) {\r\n    \r\n        function Blob( ruid, source ) {\r\n            var me = this;\r\n    \r\n            me.source = source;\r\n            me.ruid = ruid;\r\n    \r\n            RuntimeClient.call( me, 'Blob' );\r\n    \r\n            this.uid = source.uid || this.uid;\r\n            this.type = source.type || '';\r\n            this.size = source.size || 0;\r\n    \r\n            if ( ruid ) {\r\n                me.connectRuntime( ruid );\r\n            }\r\n        }\r\n    \r\n        Base.inherits( RuntimeClient, {\r\n            constructor: Blob,\r\n    \r\n            slice: function( start, end ) {\r\n                return this.exec( 'slice', start, end );\r\n            },\r\n    \r\n            getSource: function() {\r\n                return this.source;\r\n            }\r\n        });\r\n    \r\n        return Blob;\r\n    });\r\n    /**\r\n     * 为了统一化Flash的File和HTML5的File而存在。\r\n     * 以至于要调用Flash里面的File，也可以像调用HTML5版本的File一下。\r\n     * @fileOverview File\r\n     */\r\n    define('lib/file',[\r\n        'base',\r\n        'lib/blob'\r\n    ], function( Base, Blob ) {\r\n    \r\n        var uid = 1,\r\n            rExt = /\\.([^.]+)$/;\r\n    \r\n        function File( ruid, file ) {\r\n            var ext;\r\n    \r\n            Blob.apply( this, arguments );\r\n            this.name = file.name || ('untitled' + uid++);\r\n            ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : '';\r\n    \r\n            // todo 支持其他类型文件的转换。\r\n    \r\n            // 如果有mimetype, 但是文件名里面没有找出后缀规律\r\n            if ( !ext && this.type ) {\r\n                ext = /\\/(jpg|jpeg|png|gif|bmp)$/i.exec( this.type ) ?\r\n                        RegExp.$1.toLowerCase() : '';\r\n                this.name += '.' + ext;\r\n            }\r\n    \r\n            // 如果没有指定mimetype, 但是知道文件后缀。\r\n            if ( !this.type &&  ~'jpg,jpeg,png,gif,bmp'.indexOf( ext ) ) {\r\n                this.type = 'image/' + (ext === 'jpg' ? 'jpeg' : ext);\r\n            }\r\n    \r\n            this.ext = ext;\r\n            this.lastModifiedDate = file.lastModifiedDate ||\r\n                    (new Date()).toLocaleString();\r\n        }\r\n    \r\n        return Base.inherits( Blob, File );\r\n    });\r\n    \r\n    /**\r\n     * @fileOverview 错误信息\r\n     */\r\n    define('lib/filepicker',[\r\n        'base',\r\n        'runtime/client',\r\n        'lib/file'\r\n    ], function( Base, RuntimeClent, File ) {\r\n    \r\n        var $ = Base.$;\r\n    \r\n        function FilePicker( opts ) {\r\n            opts = this.options = $.extend({}, FilePicker.options, opts );\r\n    \r\n            opts.container = $( opts.id );\r\n    \r\n            if ( !opts.container.length ) {\r\n                throw new Error('按钮指定错误');\r\n            }\r\n    \r\n            opts.innerHTML = opts.innerHTML || opts.label ||\r\n                    opts.container.html() || '';\r\n    \r\n            opts.button = $( opts.button || document.createElement('div') );\r\n            opts.button.html( opts.innerHTML );\r\n            opts.container.html( opts.button );\r\n    \r\n            RuntimeClent.call( this, 'FilePicker', true );\r\n        }\r\n    \r\n        FilePicker.options = {\r\n            button: null,\r\n            container: null,\r\n            label: null,\r\n            innerHTML: null,\r\n            multiple: true,\r\n            accept: null,\r\n            name: 'file'\r\n        };\r\n    \r\n        Base.inherits( RuntimeClent, {\r\n            constructor: FilePicker,\r\n    \r\n            init: function() {\r\n                var me = this,\r\n                    opts = me.options,\r\n                    button = opts.button;\r\n    \r\n                button.addClass('webuploader-pick');\r\n    \r\n                me.on( 'all', function( type ) {\r\n                    var files;\r\n    \r\n                    switch ( type ) {\r\n                        case 'mouseenter':\r\n                            button.addClass('webuploader-pick-hover');\r\n                            break;\r\n    \r\n                        case 'mouseleave':\r\n                            button.removeClass('webuploader-pick-hover');\r\n                            break;\r\n    \r\n                        case 'change':\r\n                            files = me.exec('getFiles');\r\n                            me.trigger( 'select', $.map( files, function( file ) {\r\n                                file = new File( me.getRuid(), file );\r\n    \r\n                                // 记录来源。\r\n                                file._refer = opts.container;\r\n                                return file;\r\n                            }), opts.container );\r\n                            break;\r\n                    }\r\n                });\r\n    \r\n                me.connectRuntime( opts, function() {\r\n                    me.refresh();\r\n                    me.exec( 'init', opts );\r\n                    me.trigger('ready');\r\n                });\r\n    \r\n                $( window ).on( 'resize', function() {\r\n                    me.refresh();\r\n                });\r\n            },\r\n    \r\n            refresh: function() {\r\n                var shimContainer = this.getRuntime().getContainer(),\r\n                    button = this.options.button,\r\n                    width = button.outerWidth ?\r\n                            button.outerWidth() : button.width(),\r\n    \r\n                    height = button.outerHeight ?\r\n                            button.outerHeight() : button.height(),\r\n    \r\n                    pos = button.offset();\r\n    \r\n                width && height && shimContainer.css({\r\n                    bottom: 'auto',\r\n                    right: 'auto',\r\n                    width: width + 'px',\r\n                    height: height + 'px'\r\n                }).offset( pos );\r\n            },\r\n    \r\n            enable: function() {\r\n                var btn = this.options.button;\r\n    \r\n                btn.removeClass('webuploader-pick-disable');\r\n                this.refresh();\r\n            },\r\n    \r\n            disable: function() {\r\n                var btn = this.options.button;\r\n    \r\n                this.getRuntime().getContainer().css({\r\n                    top: '-99999px'\r\n                });\r\n    \r\n                btn.addClass('webuploader-pick-disable');\r\n            },\r\n    \r\n            destroy: function() {\r\n                if ( this.runtime ) {\r\n                    this.exec('destroy');\r\n                    this.disconnectRuntime();\r\n                }\r\n            }\r\n        });\r\n    \r\n        return FilePicker;\r\n    });\r\n    \r\n    /**\r\n     * @fileOverview 文件选择相关\r\n     */\r\n    define('widgets/filepicker',[\r\n        'base',\r\n        'uploader',\r\n        'lib/filepicker',\r\n        'widgets/widget'\r\n    ], function( Base, Uploader, FilePicker ) {\r\n        var $ = Base.$;\r\n    \r\n        $.extend( Uploader.options, {\r\n    \r\n            /**\r\n             * @property {Selector | Object} [pick=undefined]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 指定选择文件的按钮容器，不指定则不创建按钮。\r\n             *\r\n             * * `id` {Seletor} 指定选择文件的按钮容器，不指定则不创建按钮。\r\n             * * `label` {String} 请采用 `innerHTML` 代替\r\n             * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。\r\n             * * `multiple` {Boolean} 是否开起同时选择多个文件能力。\r\n             */\r\n            pick: null,\r\n    \r\n            /**\r\n             * @property {Arroy} [accept=null]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表，所以这里需要分开指定。\r\n             *\r\n             * * `title` {String} 文字描述\r\n             * * `extensions` {String} 允许的文件后缀，不带点，多个用逗号分割。\r\n             * * `mimeTypes` {String} 多个用逗号分割。\r\n             *\r\n             * 如：\r\n             *\r\n             * ```\r\n             * {\r\n             *     title: 'Images',\r\n             *     extensions: 'gif,jpg,jpeg,bmp,png',\r\n             *     mimeTypes: 'image/*'\r\n             * }\r\n             * ```\r\n             */\r\n            accept: null/*{\r\n                title: 'Images',\r\n                extensions: 'gif,jpg,jpeg,bmp,png',\r\n                mimeTypes: 'image/*'\r\n            }*/\r\n        });\r\n    \r\n        return Uploader.register({\r\n            'add-btn': 'addButton',\r\n            refresh: 'refresh',\r\n            disable: 'disable',\r\n            enable: 'enable'\r\n        }, {\r\n    \r\n            init: function( opts ) {\r\n                this.pickers = [];\r\n                return opts.pick && this.addButton( opts.pick );\r\n            },\r\n    \r\n            refresh: function() {\r\n                $.each( this.pickers, function() {\r\n                    this.refresh();\r\n                });\r\n            },\r\n    \r\n            /**\r\n             * @method addButton\r\n             * @for Uploader\r\n             * @grammar addButton( pick ) => Promise\r\n             * @description\r\n             * 添加文件选择按钮，如果一个按钮不够，需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。\r\n             * @example\r\n             * uploader.addButton({\r\n             *     id: '#btnContainer',\r\n             *     innerHTML: '选择文件'\r\n             * });\r\n             */\r\n            addButton: function( pick ) {\r\n                var me = this,\r\n                    opts = me.options,\r\n                    accept = opts.accept,\r\n                    options, picker, deferred;\r\n    \r\n                if ( !pick ) {\r\n                    return;\r\n                }\r\n    \r\n                deferred = Base.Deferred();\r\n                $.isPlainObject( pick ) || (pick = {\r\n                    id: pick\r\n                });\r\n    \r\n                options = $.extend({}, pick, {\r\n                    accept: $.isPlainObject( accept ) ? [ accept ] : accept,\r\n                    swf: opts.swf,\r\n                    runtimeOrder: opts.runtimeOrder\r\n                });\r\n    \r\n                picker = new FilePicker( options );\r\n    \r\n                picker.once( 'ready', deferred.resolve );\r\n                picker.on( 'select', function( files ) {\r\n                    me.owner.request( 'add-file', [ files ]);\r\n                });\r\n                picker.init();\r\n    \r\n                this.pickers.push( picker );\r\n    \r\n                return deferred.promise();\r\n            },\r\n    \r\n            disable: function() {\r\n                $.each( this.pickers, function() {\r\n                    this.disable();\r\n                });\r\n            },\r\n    \r\n            enable: function() {\r\n                $.each( this.pickers, function() {\r\n                    this.enable();\r\n                });\r\n            }\r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview 文件属性封装\r\n     */\r\n    define('file',[\r\n        'base',\r\n        'mediator'\r\n    ], function( Base, Mediator ) {\r\n    \r\n        var $ = Base.$,\r\n            idPrefix = 'WU_FILE_',\r\n            idSuffix = 0,\r\n            rExt = /\\.([^.]+)$/,\r\n            statusMap = {};\r\n    \r\n        function gid() {\r\n            return idPrefix + idSuffix++;\r\n        }\r\n    \r\n        /**\r\n         * 文件类\r\n         * @class File\r\n         * @constructor 构造函数\r\n         * @grammar new File( source ) => File\r\n         * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。\r\n         */\r\n        function WUFile( source ) {\r\n    \r\n            /**\r\n             * 文件名，包括扩展名（后缀）\r\n             * @property name\r\n             * @type {string}\r\n             */\r\n            this.name = source.name || 'Untitled';\r\n    \r\n            /**\r\n             * 文件体积（字节）\r\n             * @property size\r\n             * @type {uint}\r\n             * @default 0\r\n             */\r\n            this.size = source.size || 0;\r\n    \r\n            /**\r\n             * 文件MIMETYPE类型，与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny)\r\n             * @property type\r\n             * @type {string}\r\n             * @default 'application'\r\n             */\r\n            this.type = source.type || 'application';\r\n    \r\n            /**\r\n             * 文件最后修改日期\r\n             * @property lastModifiedDate\r\n             * @type {int}\r\n             * @default 当前时间戳\r\n             */\r\n            this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1);\r\n    \r\n            /**\r\n             * 文件ID，每个对象具有唯一ID，与文件名无关\r\n             * @property id\r\n             * @type {string}\r\n             */\r\n            this.id = gid();\r\n    \r\n            /**\r\n             * 文件扩展名，通过文件名获取，例如test.png的扩展名为png\r\n             * @property ext\r\n             * @type {string}\r\n             */\r\n            this.ext = rExt.exec( this.name ) ? RegExp.$1 : '';\r\n    \r\n    \r\n            /**\r\n             * 状态文字说明。在不同的status语境下有不同的用途。\r\n             * @property statusText\r\n             * @type {string}\r\n             */\r\n            this.statusText = '';\r\n    \r\n            // 存储文件状态，防止通过属性直接修改\r\n            statusMap[ this.id ] = WUFile.Status.INITED;\r\n    \r\n            this.source = source;\r\n            this.loaded = 0;\r\n    \r\n            this.on( 'error', function( msg ) {\r\n                this.setStatus( WUFile.Status.ERROR, msg );\r\n            });\r\n        }\r\n    \r\n        $.extend( WUFile.prototype, {\r\n    \r\n            /**\r\n             * 设置状态，状态变化时会触发`change`事件。\r\n             * @method setStatus\r\n             * @grammar setStatus( status[, statusText] );\r\n             * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status)\r\n             * @param {String} [statusText=''] 状态说明，常在error时使用，用http, abort,server等来标记是由于什么原因导致文件错误。\r\n             */\r\n            setStatus: function( status, text ) {\r\n    \r\n                var prevStatus = statusMap[ this.id ];\r\n    \r\n                typeof text !== 'undefined' && (this.statusText = text);\r\n    \r\n                if ( status !== prevStatus ) {\r\n                    statusMap[ this.id ] = status;\r\n                    /**\r\n                     * 文件状态变化\r\n                     * @event statuschange\r\n                     */\r\n                    this.trigger( 'statuschange', status, prevStatus );\r\n                }\r\n    \r\n            },\r\n    \r\n            /**\r\n             * 获取文件状态\r\n             * @return {File.Status}\r\n             * @example\r\n                     文件状态具体包括以下几种类型：\r\n                     {\r\n                         // 初始化\r\n                        INITED:     0,\r\n                        // 已入队列\r\n                        QUEUED:     1,\r\n                        // 正在上传\r\n                        PROGRESS:     2,\r\n                        // 上传出错\r\n                        ERROR:         3,\r\n                        // 上传成功\r\n                        COMPLETE:     4,\r\n                        // 上传取消\r\n                        CANCELLED:     5\r\n                    }\r\n             */\r\n            getStatus: function() {\r\n                return statusMap[ this.id ];\r\n            },\r\n    \r\n            /**\r\n             * 获取文件原始信息。\r\n             * @return {*}\r\n             */\r\n            getSource: function() {\r\n                return this.source;\r\n            },\r\n    \r\n            destory: function() {\r\n                delete statusMap[ this.id ];\r\n            }\r\n        });\r\n    \r\n        Mediator.installTo( WUFile.prototype );\r\n    \r\n        /**\r\n         * 文件状态值，具体包括以下几种类型：\r\n         * * `inited` 初始状态\r\n         * * `queued` 已经进入队列, 等待上传\r\n         * * `progress` 上传中\r\n         * * `complete` 上传完成。\r\n         * * `error` 上传出错，可重试\r\n         * * `interrupt` 上传中断，可续传。\r\n         * * `invalid` 文件不合格，不能重试上传。会自动从队列中移除。\r\n         * * `cancelled` 文件被移除。\r\n         * @property {Object} Status\r\n         * @namespace File\r\n         * @class File\r\n         * @static\r\n         */\r\n        WUFile.Status = {\r\n            INITED:     'inited',    // 初始状态\r\n            QUEUED:     'queued',    // 已经进入队列, 等待上传\r\n            PROGRESS:   'progress',    // 上传中\r\n            ERROR:      'error',    // 上传出错，可重试\r\n            COMPLETE:   'complete',    // 上传完成。\r\n            CANCELLED:  'cancelled',    // 上传取消。\r\n            INTERRUPT:  'interrupt',    // 上传中断，可续传。\r\n            INVALID:    'invalid'    // 文件不合格，不能重试上传。\r\n        };\r\n    \r\n        return WUFile;\r\n    });\r\n    \r\n    /**\r\n     * @fileOverview 文件队列\r\n     */\r\n    define('queue',[\r\n        'base',\r\n        'mediator',\r\n        'file'\r\n    ], function( Base, Mediator, WUFile ) {\r\n    \r\n        var $ = Base.$,\r\n            STATUS = WUFile.Status;\r\n    \r\n        /**\r\n         * 文件队列, 用来存储各个状态中的文件。\r\n         * @class Queue\r\n         * @extends Mediator\r\n         */\r\n        function Queue() {\r\n    \r\n            /**\r\n             * 统计文件数。\r\n             * * `numOfQueue` 队列中的文件数。\r\n             * * `numOfSuccess` 上传成功的文件数\r\n             * * `numOfCancel` 被移除的文件数\r\n             * * `numOfProgress` 正在上传中的文件数\r\n             * * `numOfUploadFailed` 上传错误的文件数。\r\n             * * `numOfInvalid` 无效的文件数。\r\n             * @property {Object} stats\r\n             */\r\n            this.stats = {\r\n                numOfQueue: 0,\r\n                numOfSuccess: 0,\r\n                numOfCancel: 0,\r\n                numOfProgress: 0,\r\n                numOfUploadFailed: 0,\r\n                numOfInvalid: 0\r\n            };\r\n    \r\n            // 上传队列，仅包括等待上传的文件\r\n            this._queue = [];\r\n    \r\n            // 存储所有文件\r\n            this._map = {};\r\n        }\r\n    \r\n        $.extend( Queue.prototype, {\r\n    \r\n            /**\r\n             * 将新文件加入对队列尾部\r\n             *\r\n             * @method append\r\n             * @param  {File} file   文件对象\r\n             */\r\n            append: function( file ) {\r\n                this._queue.push( file );\r\n                this._fileAdded( file );\r\n                return this;\r\n            },\r\n    \r\n            /**\r\n             * 将新文件加入对队列头部\r\n             *\r\n             * @method prepend\r\n             * @param  {File} file   文件对象\r\n             */\r\n            prepend: function( file ) {\r\n                this._queue.unshift( file );\r\n                this._fileAdded( file );\r\n                return this;\r\n            },\r\n    \r\n            /**\r\n             * 获取文件对象\r\n             *\r\n             * @method getFile\r\n             * @param  {String} fileId   文件ID\r\n             * @return {File}\r\n             */\r\n            getFile: function( fileId ) {\r\n                if ( typeof fileId !== 'string' ) {\r\n                    return fileId;\r\n                }\r\n                return this._map[ fileId ];\r\n            },\r\n    \r\n            /**\r\n             * 从队列中取出一个指定状态的文件。\r\n             * @grammar fetch( status ) => File\r\n             * @method fetch\r\n             * @param {String} status [文件状态值](#WebUploader:File:File.Status)\r\n             * @return {File} [File](#WebUploader:File)\r\n             */\r\n            fetch: function( status ) {\r\n                var len = this._queue.length,\r\n                    i, file;\r\n    \r\n                status = status || STATUS.QUEUED;\r\n    \r\n                for ( i = 0; i < len; i++ ) {\r\n                    file = this._queue[ i ];\r\n    \r\n                    if ( status === file.getStatus() ) {\r\n                        return file;\r\n                    }\r\n                }\r\n    \r\n                return null;\r\n            },\r\n    \r\n            /**\r\n             * 对队列进行排序，能够控制文件上传顺序。\r\n             * @grammar sort( fn ) => undefined\r\n             * @method sort\r\n             * @param {Function} fn 排序方法\r\n             */\r\n            sort: function( fn ) {\r\n                if ( typeof fn === 'function' ) {\r\n                    this._queue.sort( fn );\r\n                }\r\n            },\r\n    \r\n            /**\r\n             * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。\r\n             * @grammar getFiles( [status1[, status2 ...]] ) => Array\r\n             * @method getFiles\r\n             * @param {String} [status] [文件状态值](#WebUploader:File:File.Status)\r\n             */\r\n            getFiles: function() {\r\n                var sts = [].slice.call( arguments, 0 ),\r\n                    ret = [],\r\n                    i = 0,\r\n                    len = this._queue.length,\r\n                    file;\r\n    \r\n                for ( ; i < len; i++ ) {\r\n                    file = this._queue[ i ];\r\n    \r\n                    if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) {\r\n                        continue;\r\n                    }\r\n    \r\n                    ret.push( file );\r\n                }\r\n    \r\n                return ret;\r\n            },\r\n    \r\n            _fileAdded: function( file ) {\r\n                var me = this,\r\n                    existing = this._map[ file.id ];\r\n    \r\n                if ( !existing ) {\r\n                    this._map[ file.id ] = file;\r\n    \r\n                    file.on( 'statuschange', function( cur, pre ) {\r\n                        me._onFileStatusChange( cur, pre );\r\n                    });\r\n                }\r\n    \r\n                file.setStatus( STATUS.QUEUED );\r\n            },\r\n    \r\n            _onFileStatusChange: function( curStatus, preStatus ) {\r\n                var stats = this.stats;\r\n    \r\n                switch ( preStatus ) {\r\n                    case STATUS.PROGRESS:\r\n                        stats.numOfProgress--;\r\n                        break;\r\n    \r\n                    case STATUS.QUEUED:\r\n                        stats.numOfQueue --;\r\n                        break;\r\n    \r\n                    case STATUS.ERROR:\r\n                        stats.numOfUploadFailed--;\r\n                        break;\r\n    \r\n                    case STATUS.INVALID:\r\n                        stats.numOfInvalid--;\r\n                        break;\r\n                }\r\n    \r\n                switch ( curStatus ) {\r\n                    case STATUS.QUEUED:\r\n                        stats.numOfQueue++;\r\n                        break;\r\n    \r\n                    case STATUS.PROGRESS:\r\n                        stats.numOfProgress++;\r\n                        break;\r\n    \r\n                    case STATUS.ERROR:\r\n                        stats.numOfUploadFailed++;\r\n                        break;\r\n    \r\n                    case STATUS.COMPLETE:\r\n                        stats.numOfSuccess++;\r\n                        break;\r\n    \r\n                    case STATUS.CANCELLED:\r\n                        stats.numOfCancel++;\r\n                        break;\r\n    \r\n                    case STATUS.INVALID:\r\n                        stats.numOfInvalid++;\r\n                        break;\r\n                }\r\n            }\r\n    \r\n        });\r\n    \r\n        Mediator.installTo( Queue.prototype );\r\n    \r\n        return Queue;\r\n    });\r\n    /**\r\n     * @fileOverview 队列\r\n     */\r\n    define('widgets/queue',[\r\n        'base',\r\n        'uploader',\r\n        'queue',\r\n        'file',\r\n        'lib/file',\r\n        'runtime/client',\r\n        'widgets/widget'\r\n    ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) {\r\n    \r\n        var $ = Base.$,\r\n            rExt = /\\.\\w+$/,\r\n            Status = WUFile.Status;\r\n    \r\n        return Uploader.register({\r\n            'sort-files': 'sortFiles',\r\n            'add-file': 'addFiles',\r\n            'get-file': 'getFile',\r\n            'fetch-file': 'fetchFile',\r\n            'get-stats': 'getStats',\r\n            'get-files': 'getFiles',\r\n            'remove-file': 'removeFile',\r\n            'retry': 'retry',\r\n            'reset': 'reset',\r\n            'accept-file': 'acceptFile'\r\n        }, {\r\n    \r\n            init: function( opts ) {\r\n                var me = this,\r\n                    deferred, len, i, item, arr, accept, runtime;\r\n    \r\n                if ( $.isPlainObject( opts.accept ) ) {\r\n                    opts.accept = [ opts.accept ];\r\n                }\r\n    \r\n                // accept中的中生成匹配正则。\r\n                if ( opts.accept ) {\r\n                    arr = [];\r\n    \r\n                    for ( i = 0, len = opts.accept.length; i < len; i++ ) {\r\n                        item = opts.accept[ i ].extensions;\r\n                        item && arr.push( item );\r\n                    }\r\n    \r\n                    if ( arr.length ) {\r\n                        accept = '\\\\.' + arr.join(',')\r\n                                .replace( /,/g, '$|\\\\.' )\r\n                                .replace( /\\*/g, '.*' ) + '$';\r\n                    }\r\n    \r\n                    me.accept = new RegExp( accept, 'i' );\r\n                }\r\n    \r\n                me.queue = new Queue();\r\n                me.stats = me.queue.stats;\r\n    \r\n                // 如果当前不是html5运行时，那就算了。\r\n                // 不执行后续操作\r\n                if ( this.request('predict-runtime-type') !== 'html5' ) {\r\n                    return;\r\n                }\r\n    \r\n                // 创建一个 html5 运行时的 placeholder\r\n                // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。\r\n                deferred = Base.Deferred();\r\n                runtime = new RuntimeClient('Placeholder');\r\n                runtime.connectRuntime({\r\n                    runtimeOrder: 'html5'\r\n                }, function() {\r\n                    me._ruid = runtime.getRuid();\r\n                    deferred.resolve();\r\n                });\r\n                return deferred.promise();\r\n            },\r\n    \r\n    \r\n            // 为了支持外部直接添加一个原生File对象。\r\n            _wrapFile: function( file ) {\r\n                if ( !(file instanceof WUFile) ) {\r\n    \r\n                    if ( !(file instanceof File) ) {\r\n                        if ( !this._ruid ) {\r\n                            throw new Error('Can\\'t add external files.');\r\n                        }\r\n                        file = new File( this._ruid, file );\r\n                    }\r\n    \r\n                    file = new WUFile( file );\r\n                }\r\n    \r\n                return file;\r\n            },\r\n    \r\n            // 判断文件是否可以被加入队列\r\n            acceptFile: function( file ) {\r\n                var invalid = !file || file.size < 6 || this.accept &&\r\n    \r\n                        // 如果名字中有后缀，才做后缀白名单处理。\r\n                        rExt.exec( file.name ) && !this.accept.test( file.name );\r\n    \r\n                return !invalid;\r\n            },\r\n    \r\n    \r\n            /**\r\n             * @event beforeFileQueued\r\n             * @param {File} file File对象\r\n             * @description 当文件被加入队列之前触发，此事件的handler返回值为`false`，则此文件不会被添加进入队列。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * @event fileQueued\r\n             * @param {File} file File对象\r\n             * @description 当文件被加入队列以后触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            _addFile: function( file ) {\r\n                var me = this;\r\n    \r\n                file = me._wrapFile( file );\r\n    \r\n                // 不过类型判断允许不允许，先派送 `beforeFileQueued`\r\n                if ( !me.owner.trigger( 'beforeFileQueued', file ) ) {\r\n                    return;\r\n                }\r\n    \r\n                // 类型不匹配，则派送错误事件，并返回。\r\n                if ( !me.acceptFile( file ) ) {\r\n                    me.owner.trigger( 'error', 'Q_TYPE_DENIED', file );\r\n                    return;\r\n                }\r\n    \r\n                me.queue.append( file );\r\n                me.owner.trigger( 'fileQueued', file );\r\n                return file;\r\n            },\r\n    \r\n            getFile: function( fileId ) {\r\n                return this.queue.getFile( fileId );\r\n            },\r\n    \r\n            /**\r\n             * @event filesQueued\r\n             * @param {File} files 数组，内容为原始File(lib/File）对象。\r\n             * @description 当一批文件添加进队列以后触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * @method addFiles\r\n             * @grammar addFiles( file ) => undefined\r\n             * @grammar addFiles( [file1, file2 ...] ) => undefined\r\n             * @param {Array of File or File} [files] Files 对象 数组\r\n             * @description 添加文件到队列\r\n             * @for  Uploader\r\n             */\r\n            addFiles: function( files ) {\r\n                var me = this;\r\n    \r\n                if ( !files.length ) {\r\n                    files = [ files ];\r\n                }\r\n    \r\n                files = $.map( files, function( file ) {\r\n                    return me._addFile( file );\r\n                });\r\n    \r\n                me.owner.trigger( 'filesQueued', files );\r\n    \r\n                if ( me.options.auto ) {\r\n                    me.request('start-upload');\r\n                }\r\n            },\r\n    \r\n            getStats: function() {\r\n                return this.stats;\r\n            },\r\n    \r\n            /**\r\n             * @event fileDequeued\r\n             * @param {File} file File对象\r\n             * @description 当文件被移除队列后触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * @method removeFile\r\n             * @grammar removeFile( file ) => undefined\r\n             * @grammar removeFile( id ) => undefined\r\n             * @param {File|id} file File对象或这File对象的id\r\n             * @description 移除某一文件。\r\n             * @for  Uploader\r\n             * @example\r\n             *\r\n             * $li.on('click', '.remove-this', function() {\r\n             *     uploader.removeFile( file );\r\n             * })\r\n             */\r\n            removeFile: function( file ) {\r\n                var me = this;\r\n    \r\n                file = file.id ? file : me.queue.getFile( file );\r\n    \r\n                file.setStatus( Status.CANCELLED );\r\n                me.owner.trigger( 'fileDequeued', file );\r\n            },\r\n    \r\n            /**\r\n             * @method getFiles\r\n             * @grammar getFiles() => Array\r\n             * @grammar getFiles( status1, status2, status... ) => Array\r\n             * @description 返回指定状态的文件集合，不传参数将返回所有状态的文件。\r\n             * @for  Uploader\r\n             * @example\r\n             * console.log( uploader.getFiles() );    // => all files\r\n             * console.log( uploader.getFiles('error') )    // => all error files.\r\n             */\r\n            getFiles: function() {\r\n                return this.queue.getFiles.apply( this.queue, arguments );\r\n            },\r\n    \r\n            fetchFile: function() {\r\n                return this.queue.fetch.apply( this.queue, arguments );\r\n            },\r\n    \r\n            /**\r\n             * @method retry\r\n             * @grammar retry() => undefined\r\n             * @grammar retry( file ) => undefined\r\n             * @description 重试上传，重试指定文件，或者从出错的文件开始重新上传。\r\n             * @for  Uploader\r\n             * @example\r\n             * function retry() {\r\n             *     uploader.retry();\r\n             * }\r\n             */\r\n            retry: function( file, noForceStart ) {\r\n                var me = this,\r\n                    files, i, len;\r\n    \r\n                if ( file ) {\r\n                    file = file.id ? file : me.queue.getFile( file );\r\n                    file.setStatus( Status.QUEUED );\r\n                    noForceStart || me.request('start-upload');\r\n                    return;\r\n                }\r\n    \r\n                files = me.queue.getFiles( Status.ERROR );\r\n                i = 0;\r\n                len = files.length;\r\n    \r\n                for ( ; i < len; i++ ) {\r\n                    file = files[ i ];\r\n                    file.setStatus( Status.QUEUED );\r\n                }\r\n    \r\n                me.request('start-upload');\r\n            },\r\n    \r\n            /**\r\n             * @method sort\r\n             * @grammar sort( fn ) => undefined\r\n             * @description 排序队列中的文件，在上传之前调整可以控制上传顺序。\r\n             * @for  Uploader\r\n             */\r\n            sortFiles: function() {\r\n                return this.queue.sort.apply( this.queue, arguments );\r\n            },\r\n    \r\n            /**\r\n             * @method reset\r\n             * @grammar reset() => undefined\r\n             * @description 重置uploader。目前只重置了队列。\r\n             * @for  Uploader\r\n             * @example\r\n             * uploader.reset();\r\n             */\r\n            reset: function() {\r\n                this.queue = new Queue();\r\n                this.stats = this.queue.stats;\r\n            }\r\n        });\r\n    \r\n    });\r\n    /**\r\n     * @fileOverview 添加获取Runtime相关信息的方法。\r\n     */\r\n    define('widgets/runtime',[\r\n        'uploader',\r\n        'runtime/runtime',\r\n        'widgets/widget'\r\n    ], function( Uploader, Runtime ) {\r\n    \r\n        Uploader.support = function() {\r\n            return Runtime.hasRuntime.apply( Runtime, arguments );\r\n        };\r\n    \r\n        return Uploader.register({\r\n            'predict-runtime-type': 'predictRuntmeType'\r\n        }, {\r\n    \r\n            init: function() {\r\n                if ( !this.predictRuntmeType() ) {\r\n                    throw Error('Runtime Error');\r\n                }\r\n            },\r\n    \r\n            /**\r\n             * 预测Uploader将采用哪个`Runtime`\r\n             * @grammar predictRuntmeType() => String\r\n             * @method predictRuntmeType\r\n             * @for  Uploader\r\n             */\r\n            predictRuntmeType: function() {\r\n                var orders = this.options.runtimeOrder || Runtime.orders,\r\n                    type = this.type,\r\n                    i, len;\r\n    \r\n                if ( !type ) {\r\n                    orders = orders.split( /\\s*,\\s*/g );\r\n    \r\n                    for ( i = 0, len = orders.length; i < len; i++ ) {\r\n                        if ( Runtime.hasRuntime( orders[ i ] ) ) {\r\n                            this.type = type = orders[ i ];\r\n                            break;\r\n                        }\r\n                    }\r\n                }\r\n    \r\n                return type;\r\n            }\r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview Transport\r\n     */\r\n    define('lib/transport',[\r\n        'base',\r\n        'runtime/client',\r\n        'mediator'\r\n    ], function( Base, RuntimeClient, Mediator ) {\r\n    \r\n        var $ = Base.$;\r\n    \r\n        function Transport( opts ) {\r\n            var me = this;\r\n    \r\n            opts = me.options = $.extend( true, {}, Transport.options, opts || {} );\r\n            RuntimeClient.call( this, 'Transport' );\r\n    \r\n            this._blob = null;\r\n            this._formData = opts.formData || {};\r\n            this._headers = opts.headers || {};\r\n    \r\n            this.on( 'progress', this._timeout );\r\n            this.on( 'load error', function() {\r\n                me.trigger( 'progress', 1 );\r\n                clearTimeout( me._timer );\r\n            });\r\n        }\r\n    \r\n        Transport.options = {\r\n            server: '',\r\n            method: 'POST',\r\n    \r\n            // 跨域时，是否允许携带cookie, 只有html5 runtime才有效\r\n            withCredentials: false,\r\n            fileVal: 'file',\r\n            timeout: 2 * 60 * 1000,    // 2分钟\r\n            formData: {},\r\n            headers: {},\r\n            sendAsBinary: false\r\n        };\r\n    \r\n        $.extend( Transport.prototype, {\r\n    \r\n            // 添加Blob, 只能添加一次，最后一次有效。\r\n            appendBlob: function( key, blob, filename ) {\r\n                var me = this,\r\n                    opts = me.options;\r\n    \r\n                if ( me.getRuid() ) {\r\n                    me.disconnectRuntime();\r\n                }\r\n    \r\n                // 连接到blob归属的同一个runtime.\r\n                me.connectRuntime( blob.ruid, function() {\r\n                    me.exec('init');\r\n                });\r\n    \r\n                me._blob = blob;\r\n                opts.fileVal = key || opts.fileVal;\r\n                opts.filename = filename || opts.filename;\r\n            },\r\n    \r\n            // 添加其他字段\r\n            append: function( key, value ) {\r\n                if ( typeof key === 'object' ) {\r\n                    $.extend( this._formData, key );\r\n                } else {\r\n                    this._formData[ key ] = value;\r\n                }\r\n            },\r\n    \r\n            setRequestHeader: function( key, value ) {\r\n                if ( typeof key === 'object' ) {\r\n                    $.extend( this._headers, key );\r\n                } else {\r\n                    this._headers[ key ] = value;\r\n                }\r\n            },\r\n    \r\n            send: function( method ) {\r\n                this.exec( 'send', method );\r\n                this._timeout();\r\n            },\r\n    \r\n            abort: function() {\r\n                clearTimeout( this._timer );\r\n                return this.exec('abort');\r\n            },\r\n    \r\n            destroy: function() {\r\n                this.trigger('destroy');\r\n                this.off();\r\n                this.exec('destroy');\r\n                this.disconnectRuntime();\r\n            },\r\n    \r\n            getResponse: function() {\r\n                return this.exec('getResponse');\r\n            },\r\n    \r\n            getResponseAsJson: function() {\r\n                return this.exec('getResponseAsJson');\r\n            },\r\n    \r\n            getStatus: function() {\r\n                return this.exec('getStatus');\r\n            },\r\n    \r\n            _timeout: function() {\r\n                var me = this,\r\n                    duration = me.options.timeout;\r\n    \r\n                if ( !duration ) {\r\n                    return;\r\n                }\r\n    \r\n                clearTimeout( me._timer );\r\n                me._timer = setTimeout(function() {\r\n                    me.abort();\r\n                    me.trigger( 'error', 'timeout' );\r\n                }, duration );\r\n            }\r\n    \r\n        });\r\n    \r\n        // 让Transport具备事件功能。\r\n        Mediator.installTo( Transport.prototype );\r\n    \r\n        return Transport;\r\n    });\r\n    /**\r\n     * @fileOverview 负责文件上传相关。\r\n     */\r\n    define('widgets/upload',[\r\n        'base',\r\n        'uploader',\r\n        'file',\r\n        'lib/transport',\r\n        'widgets/widget'\r\n    ], function( Base, Uploader, WUFile, Transport ) {\r\n    \r\n        var $ = Base.$,\r\n            isPromise = Base.isPromise,\r\n            Status = WUFile.Status;\r\n    \r\n        // 添加默认配置项\r\n        $.extend( Uploader.options, {\r\n    \r\n    \r\n            /**\r\n             * @property {Boolean} [prepareNextFile=false]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 是否允许在文件传输时提前把下一个文件准备好。\r\n             * 对于一个文件的准备工作比较耗时，比如图片压缩，md5序列化。\r\n             * 如果能提前在当前文件传输期处理，可以节省总体耗时。\r\n             */\r\n            prepareNextFile: false,\r\n    \r\n            /**\r\n             * @property {Boolean} [chunked=false]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 是否要分片处理大文件上传。\r\n             */\r\n            chunked: false,\r\n    \r\n            /**\r\n             * @property {Boolean} [chunkSize=5242880]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 如果要分片，分多大一片？ 默认大小为5M.\r\n             */\r\n            chunkSize: 5 * 1024 * 1024,\r\n    \r\n            /**\r\n             * @property {Boolean} [chunkRetry=2]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 如果某个分片由于网络问题出错，允许自动重传多少次？\r\n             */\r\n            chunkRetry: 2,\r\n    \r\n            /**\r\n             * @property {Boolean} [threads=3]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 上传并发数。允许同时最大上传进程数。\r\n             */\r\n            threads: 3,\r\n    \r\n    \r\n            /**\r\n             * @property {Object} [formData]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 文件上传请求的参数表，每次发送都会发送此对象中的参数。\r\n             */\r\n            formData: null\r\n    \r\n            /**\r\n             * @property {Object} [fileVal='file']\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 设置文件上传域的name。\r\n             */\r\n    \r\n            /**\r\n             * @property {Object} [method='POST']\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 文件上传方式，`POST`或者`GET`。\r\n             */\r\n    \r\n            /**\r\n             * @property {Object} [sendAsBinary=false]\r\n             * @namespace options\r\n             * @for Uploader\r\n             * @description 是否已二进制的流的方式发送文件，这样整个上传内容`php://input`都为文件内容，\r\n             * 其他参数在$_GET数组中。\r\n             */\r\n        });\r\n    \r\n        // 负责将文件切片。\r\n        function CuteFile( file, chunkSize ) {\r\n            var pending = [],\r\n                blob = file.source,\r\n                total = blob.size,\r\n                chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1,\r\n                start = 0,\r\n                index = 0,\r\n                len;\r\n    \r\n            while ( index < chunks ) {\r\n                len = Math.min( chunkSize, total - start );\r\n    \r\n                pending.push({\r\n                    file: file,\r\n                    start: start,\r\n                    end: chunkSize ? (start + len) : total,\r\n                    total: total,\r\n                    chunks: chunks,\r\n                    chunk: index++\r\n                });\r\n                start += len;\r\n            }\r\n    \r\n            file.blocks = pending.concat();\r\n            file.remaning = pending.length;\r\n    \r\n            return {\r\n                file: file,\r\n    \r\n                has: function() {\r\n                    return !!pending.length;\r\n                },\r\n    \r\n                fetch: function() {\r\n                    return pending.shift();\r\n                }\r\n            };\r\n        }\r\n    \r\n        Uploader.register({\r\n            'start-upload': 'start',\r\n            'stop-upload': 'stop',\r\n            'skip-file': 'skipFile',\r\n            'is-in-progress': 'isInProgress'\r\n        }, {\r\n    \r\n            init: function() {\r\n                var owner = this.owner;\r\n    \r\n                this.runing = false;\r\n    \r\n                // 记录当前正在传的数据，跟threads相关\r\n                this.pool = [];\r\n    \r\n                // 缓存即将上传的文件。\r\n                this.pending = [];\r\n    \r\n                // 跟踪还有多少分片没有完成上传。\r\n                this.remaning = 0;\r\n                this.__tick = Base.bindFn( this._tick, this );\r\n    \r\n                owner.on( 'uploadComplete', function( file ) {\r\n                    // 把其他块取消了。\r\n                    file.blocks && $.each( file.blocks, function( _, v ) {\r\n                        v.transport && (v.transport.abort(), v.transport.destroy());\r\n                        delete v.transport;\r\n                    });\r\n    \r\n                    delete file.blocks;\r\n                    delete file.remaning;\r\n                });\r\n            },\r\n    \r\n            /**\r\n             * @event startUpload\r\n             * @description 当开始上传流程时触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * 开始上传。此方法可以从初始状态调用开始上传流程，也可以从暂停状态调用，继续上传流程。\r\n             * @grammar upload() => undefined\r\n             * @method upload\r\n             * @for  Uploader\r\n             */\r\n            start: function() {\r\n                var me = this;\r\n    \r\n                // 移出invalid的文件\r\n                $.each( me.request( 'get-files', Status.INVALID ), function() {\r\n                    me.request( 'remove-file', this );\r\n                });\r\n    \r\n                if ( me.runing ) {\r\n                    return;\r\n                }\r\n    \r\n                me.runing = true;\r\n    \r\n                // 如果有暂停的，则续传\r\n                $.each( me.pool, function( _, v ) {\r\n                    var file = v.file;\r\n    \r\n                    if ( file.getStatus() === Status.INTERRUPT ) {\r\n                        file.setStatus( Status.PROGRESS );\r\n                        me._trigged = false;\r\n                        v.transport && v.transport.send();\r\n                    }\r\n                });\r\n    \r\n                me._trigged = false;\r\n                me.owner.trigger('startUpload');\r\n                Base.nextTick( me.__tick );\r\n            },\r\n    \r\n            /**\r\n             * @event stopUpload\r\n             * @description 当开始上传流程暂停时触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。\r\n             * @grammar stop() => undefined\r\n             * @grammar stop( true ) => undefined\r\n             * @method stop\r\n             * @for  Uploader\r\n             */\r\n            stop: function( interrupt ) {\r\n                var me = this;\r\n    \r\n                if ( me.runing === false ) {\r\n                    return;\r\n                }\r\n    \r\n                me.runing = false;\r\n    \r\n                interrupt && $.each( me.pool, function( _, v ) {\r\n                    v.transport && v.transport.abort();\r\n                    v.file.setStatus( Status.INTERRUPT );\r\n                });\r\n    \r\n                me.owner.trigger('stopUpload');\r\n            },\r\n    \r\n            /**\r\n             * 判断`Uplaode`r是否正在上传中。\r\n             * @grammar isInProgress() => Boolean\r\n             * @method isInProgress\r\n             * @for  Uploader\r\n             */\r\n            isInProgress: function() {\r\n                return !!this.runing;\r\n            },\r\n    \r\n            getStats: function() {\r\n                return this.request('get-stats');\r\n            },\r\n    \r\n            /**\r\n             * 掉过一个文件上传，直接标记指定文件为已上传状态。\r\n             * @grammar skipFile( file ) => undefined\r\n             * @method skipFile\r\n             * @for  Uploader\r\n             */\r\n            skipFile: function( file, status ) {\r\n                file = this.request( 'get-file', file );\r\n    \r\n                file.setStatus( status || Status.COMPLETE );\r\n                file.skipped = true;\r\n    \r\n                // 如果正在上传。\r\n                file.blocks && $.each( file.blocks, function( _, v ) {\r\n                    var _tr = v.transport;\r\n    \r\n                    if ( _tr ) {\r\n                        _tr.abort();\r\n                        _tr.destroy();\r\n                        delete v.transport;\r\n                    }\r\n                });\r\n    \r\n                this.owner.trigger( 'uploadSkip', file );\r\n            },\r\n    \r\n            /**\r\n             * @event uploadFinished\r\n             * @description 当所有文件上传结束时触发。\r\n             * @for  Uploader\r\n             */\r\n            _tick: function() {\r\n                var me = this,\r\n                    opts = me.options,\r\n                    fn, val;\r\n    \r\n                // 上一个promise还没有结束，则等待完成后再执行。\r\n                if ( me._promise ) {\r\n                    return me._promise.always( me.__tick );\r\n                }\r\n    \r\n                // 还有位置，且还有文件要处理的话。\r\n                if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) {\r\n                    me._trigged = false;\r\n    \r\n                    fn = function( val ) {\r\n                        me._promise = null;\r\n    \r\n                        // 有可能是reject过来的，所以要检测val的类型。\r\n                        val && val.file && me._startSend( val );\r\n                        Base.nextTick( me.__tick );\r\n                    };\r\n    \r\n                    me._promise = isPromise( val ) ? val.always( fn ) : fn( val );\r\n    \r\n                // 没有要上传的了，且没有正在传输的了。\r\n                } else if ( !me.remaning && !me.getStats().numOfQueue ) {\r\n                    me.runing = false;\r\n    \r\n                    me._trigged || Base.nextTick(function() {\r\n                        me.owner.trigger('uploadFinished');\r\n                    });\r\n                    me._trigged = true;\r\n                }\r\n            },\r\n    \r\n            _nextBlock: function() {\r\n                var me = this,\r\n                    act = me._act,\r\n                    opts = me.options,\r\n                    next, done;\r\n    \r\n                // 如果当前文件还有没有需要传输的，则直接返回剩下的。\r\n                if ( act && act.has() &&\r\n                        act.file.getStatus() === Status.PROGRESS ) {\r\n    \r\n                    // 是否提前准备下一个文件\r\n                    if ( opts.prepareNextFile && !me.pending.length ) {\r\n                        me._prepareNextFile();\r\n                    }\r\n    \r\n                    return act.fetch();\r\n    \r\n                // 否则，如果正在运行，则准备下一个文件，并等待完成后返回下个分片。\r\n                } else if ( me.runing ) {\r\n    \r\n                    // 如果缓存中有，则直接在缓存中取，没有则去queue中取。\r\n                    if ( !me.pending.length && me.getStats().numOfQueue ) {\r\n                        me._prepareNextFile();\r\n                    }\r\n    \r\n                    next = me.pending.shift();\r\n                    done = function( file ) {\r\n                        if ( !file ) {\r\n                            return null;\r\n                        }\r\n    \r\n                        act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 );\r\n                        me._act = act;\r\n                        return act.fetch();\r\n                    };\r\n    \r\n                    // 文件可能还在prepare中，也有可能已经完全准备好了。\r\n                    return isPromise( next ) ?\r\n                            next[ next.pipe ? 'pipe' : 'then']( done ) :\r\n                            done( next );\r\n                }\r\n            },\r\n    \r\n    \r\n            /**\r\n             * @event uploadStart\r\n             * @param {File} file File对象\r\n             * @description 某个文件开始上传前触发，一个文件只会触发一次。\r\n             * @for  Uploader\r\n             */\r\n            _prepareNextFile: function() {\r\n                var me = this,\r\n                    file = me.request('fetch-file'),\r\n                    pending = me.pending,\r\n                    promise;\r\n    \r\n                if ( file ) {\r\n                    promise = me.request( 'before-send-file', file, function() {\r\n    \r\n                        // 有可能文件被skip掉了。文件被skip掉后，状态坑定不是Queued.\r\n                        if ( file.getStatus() === Status.QUEUED ) {\r\n                            me.owner.trigger( 'uploadStart', file );\r\n                            file.setStatus( Status.PROGRESS );\r\n                            return file;\r\n                        }\r\n    \r\n                        return me._finishFile( file );\r\n                    });\r\n    \r\n                    // 如果还在pending中，则替换成文件本身。\r\n                    promise.done(function() {\r\n                        var idx = $.inArray( promise, pending );\r\n    \r\n                        ~idx && pending.splice( idx, 1, file );\r\n                    });\r\n    \r\n                    // befeore-send-file的钩子就有错误发生。\r\n                    promise.fail(function( reason ) {\r\n                        file.setStatus( Status.ERROR, reason );\r\n                        me.owner.trigger( 'uploadError', file, reason );\r\n                        me.owner.trigger( 'uploadComplete', file );\r\n                    });\r\n    \r\n                    pending.push( promise );\r\n                }\r\n            },\r\n    \r\n            // 让出位置了，可以让其他分片开始上传\r\n            _popBlock: function( block ) {\r\n                var idx = $.inArray( block, this.pool );\r\n    \r\n                this.pool.splice( idx, 1 );\r\n                block.file.remaning--;\r\n                this.remaning--;\r\n            },\r\n    \r\n            // 开始上传，可以被掉过。如果promise被reject了，则表示跳过此分片。\r\n            _startSend: function( block ) {\r\n                var me = this,\r\n                    file = block.file,\r\n                    promise;\r\n    \r\n                me.pool.push( block );\r\n                me.remaning++;\r\n    \r\n                // 如果没有分片，则直接使用原始的。\r\n                // 不会丢失content-type信息。\r\n                block.blob = block.chunks === 1 ? file.source :\r\n                        file.source.slice( block.start, block.end );\r\n    \r\n                // hook, 每个分片发送之前可能要做些异步的事情。\r\n                promise = me.request( 'before-send', block, function() {\r\n    \r\n                    // 有可能文件已经上传出错了，所以不需要再传输了。\r\n                    if ( file.getStatus() === Status.PROGRESS ) {\r\n                        me._doSend( block );\r\n                    } else {\r\n                        me._popBlock( block );\r\n                        Base.nextTick( me.__tick );\r\n                    }\r\n                });\r\n    \r\n                // 如果为fail了，则跳过此分片。\r\n                promise.fail(function() {\r\n                    if ( file.remaning === 1 ) {\r\n                        me._finishFile( file ).always(function() {\r\n                            block.percentage = 1;\r\n                            me._popBlock( block );\r\n                            me.owner.trigger( 'uploadComplete', file );\r\n                            Base.nextTick( me.__tick );\r\n                        });\r\n                    } else {\r\n                        block.percentage = 1;\r\n                        me._popBlock( block );\r\n                        Base.nextTick( me.__tick );\r\n                    }\r\n                });\r\n            },\r\n    \r\n    \r\n            /**\r\n             * @event uploadBeforeSend\r\n             * @param {Object} object\r\n             * @param {Object} data 默认的上传参数，可以扩展此对象来控制上传参数。\r\n             * @description 当某个文件的分块在发送前触发，主要用来询问是否要添加附带参数，大文件在开起分片上传的前提下此事件可能会触发多次。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * @event uploadAccept\r\n             * @param {Object} object\r\n             * @param {Object} ret 服务端的返回数据，json格式，如果服务端不是json格式，从ret._raw中取数据，自行解析。\r\n             * @description 当某个文件上传到服务端响应后，会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * @event uploadProgress\r\n             * @param {File} file File对象\r\n             * @param {Number} percentage 上传进度\r\n             * @description 上传过程中触发，携带上传进度。\r\n             * @for  Uploader\r\n             */\r\n    \r\n    \r\n            /**\r\n             * @event uploadError\r\n             * @param {File} file File对象\r\n             * @param {String} reason 出错的code\r\n             * @description 当文件上传出错时触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * @event uploadSuccess\r\n             * @param {File} file File对象\r\n             * @param {Object} response 服务端返回的数据\r\n             * @description 当文件上传成功时触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            /**\r\n             * @event uploadComplete\r\n             * @param {File} [file] File对象\r\n             * @description 不管成功或者失败，文件上传完成时触发。\r\n             * @for  Uploader\r\n             */\r\n    \r\n            // 做上传操作。\r\n            _doSend: function( block ) {\r\n                var me = this,\r\n                    owner = me.owner,\r\n                    opts = me.options,\r\n                    file = block.file,\r\n                    tr = new Transport( opts ),\r\n                    data = $.extend({}, opts.formData ),\r\n                    headers = $.extend({}, opts.headers ),\r\n                    requestAccept, ret;\r\n    \r\n                block.transport = tr;\r\n    \r\n                tr.on( 'destroy', function() {\r\n                    delete block.transport;\r\n                    me._popBlock( block );\r\n                    Base.nextTick( me.__tick );\r\n                });\r\n    \r\n                // 广播上传进度。以文件为单位。\r\n                tr.on( 'progress', function( percentage ) {\r\n                    var totalPercent = 0,\r\n                        uploaded = 0;\r\n    \r\n                    // 可能没有abort掉，progress还是执行进来了。\r\n                    // if ( !file.blocks ) {\r\n                    //     return;\r\n                    // }\r\n    \r\n                    totalPercent = block.percentage = percentage;\r\n    \r\n                    if ( block.chunks > 1 ) {    // 计算文件的整体速度。\r\n                        $.each( file.blocks, function( _, v ) {\r\n                            uploaded += (v.percentage || 0) * (v.end - v.start);\r\n                        });\r\n    \r\n                        totalPercent = uploaded / file.size;\r\n                    }\r\n    \r\n                    owner.trigger( 'uploadProgress', file, totalPercent || 0 );\r\n                });\r\n    \r\n                // 用来询问，是否返回的结果是有错误的。\r\n                requestAccept = function( reject ) {\r\n                    var fn;\r\n    \r\n                    ret = tr.getResponseAsJson() || {};\r\n                    ret._raw = tr.getResponse();\r\n                    fn = function( value ) {\r\n                        reject = value;\r\n                    };\r\n    \r\n                    // 服务端响应了，不代表成功了，询问是否响应正确。\r\n                    if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) {\r\n                        reject = reject || 'server';\r\n                    }\r\n    \r\n                    return reject;\r\n                };\r\n    \r\n                // 尝试重试，然后广播文件上传出错。\r\n                tr.on( 'error', function( type, flag ) {\r\n                    block.retried = block.retried || 0;\r\n    \r\n                    // 自动重试\r\n                    if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) &&\r\n                            block.retried < opts.chunkRetry ) {\r\n    \r\n                        block.retried++;\r\n                        tr.send();\r\n    \r\n                    } else {\r\n    \r\n                        // http status 500 ~ 600\r\n                        if ( !flag && type === 'server' ) {\r\n                            type = requestAccept( type );\r\n                        }\r\n    \r\n                        file.setStatus( Status.ERROR, type );\r\n                        owner.trigger( 'uploadError', file, type );\r\n                        owner.trigger( 'uploadComplete', file );\r\n                    }\r\n                });\r\n    \r\n                // 上传成功\r\n                tr.on( 'load', function() {\r\n                    var reason;\r\n    \r\n                    // 如果非预期，转向上传出错。\r\n                    if ( (reason = requestAccept()) ) {\r\n                        tr.trigger( 'error', reason, true );\r\n                        return;\r\n                    }\r\n    \r\n                    // 全部上传完成。\r\n                    if ( file.remaning === 1 ) {\r\n                        me._finishFile( file, ret );\r\n                    } else {\r\n                        tr.destroy();\r\n                    }\r\n                });\r\n    \r\n                // 配置默认的上传字段。\r\n                data = $.extend( data, {\r\n                    id: file.id,\r\n                    name: file.name,\r\n                    type: file.type,\r\n                    lastModifiedDate: file.lastModifiedDate,\r\n                    size: file.size\r\n                });\r\n    \r\n                block.chunks > 1 && $.extend( data, {\r\n                    chunks: block.chunks,\r\n                    chunk: block.chunk\r\n                });\r\n    \r\n                // 在发送之间可以添加字段什么的。。。\r\n                // 如果默认的字段不够使用，可以通过监听此事件来扩展\r\n                owner.trigger( 'uploadBeforeSend', block, data, headers );\r\n    \r\n                // 开始发送。\r\n                tr.appendBlob( opts.fileVal, block.blob, file.name );\r\n                tr.append( data );\r\n                tr.setRequestHeader( headers );\r\n                tr.send();\r\n            },\r\n    \r\n            // 完成上传。\r\n            _finishFile: function( file, ret, hds ) {\r\n                var owner = this.owner;\r\n    \r\n                return owner\r\n                        .request( 'after-send-file', arguments, function() {\r\n                            file.setStatus( Status.COMPLETE );\r\n                            owner.trigger( 'uploadSuccess', file, ret, hds );\r\n                        })\r\n                        .fail(function( reason ) {\r\n    \r\n                            // 如果外部已经标记为invalid什么的，不再改状态。\r\n                            if ( file.getStatus() === Status.PROGRESS ) {\r\n                                file.setStatus( Status.ERROR, reason );\r\n                            }\r\n    \r\n                            owner.trigger( 'uploadError', file, reason );\r\n                        })\r\n                        .always(function() {\r\n                            owner.trigger( 'uploadComplete', file );\r\n                        });\r\n            }\r\n    \r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview 各种验证，包括文件总大小是否超出、单文件是否超出和文件是否重复。\r\n     */\r\n    \r\n    define('widgets/validator',[\r\n        'base',\r\n        'uploader',\r\n        'file',\r\n        'widgets/widget'\r\n    ], function( Base, Uploader, WUFile ) {\r\n    \r\n        var $ = Base.$,\r\n            validators = {},\r\n            api;\r\n    \r\n        /**\r\n         * @event error\r\n         * @param {String} type 错误类型。\r\n         * @description 当validate不通过时，会以派送错误事件的形式通知调用者。通过`upload.on('error', handler)`可以捕获到此类错误，目前有以下错误会在特定的情况下派送错来。\r\n         *\r\n         * * `Q_EXCEED_NUM_LIMIT` 在设置了`fileNumLimit`且尝试给`uploader`添加的文件数量超出这个值时派送。\r\n         * * `Q_EXCEED_SIZE_LIMIT` 在设置了`Q_EXCEED_SIZE_LIMIT`且尝试给`uploader`添加的文件总大小超出这个值时派送。\r\n         * @for  Uploader\r\n         */\r\n    \r\n        // 暴露给外面的api\r\n        api = {\r\n    \r\n            // 添加验证器\r\n            addValidator: function( type, cb ) {\r\n                validators[ type ] = cb;\r\n            },\r\n    \r\n            // 移除验证器\r\n            removeValidator: function( type ) {\r\n                delete validators[ type ];\r\n            }\r\n        };\r\n    \r\n        // 在Uploader初始化的时候启动Validators的初始化\r\n        Uploader.register({\r\n            init: function() {\r\n                var me = this;\r\n                $.each( validators, function() {\r\n                    this.call( me.owner );\r\n                });\r\n            }\r\n        });\r\n    \r\n        /**\r\n         * @property {int} [fileNumLimit=undefined]\r\n         * @namespace options\r\n         * @for Uploader\r\n         * @description 验证文件总数量, 超出则不允许加入队列。\r\n         */\r\n        api.addValidator( 'fileNumLimit', function() {\r\n            var uploader = this,\r\n                opts = uploader.options,\r\n                count = 0,\r\n                max = opts.fileNumLimit >> 0,\r\n                flag = true;\r\n    \r\n            if ( !max ) {\r\n                return;\r\n            }\r\n    \r\n            uploader.on( 'beforeFileQueued', function( file ) {\r\n    \r\n                if ( count >= max && flag ) {\r\n                    flag = false;\r\n                    this.trigger( 'error', 'Q_EXCEED_NUM_LIMIT', max, file );\r\n                    setTimeout(function() {\r\n                        flag = true;\r\n                    }, 1 );\r\n                }\r\n    \r\n                return count >= max ? false : true;\r\n            });\r\n    \r\n            uploader.on( 'fileQueued', function() {\r\n                count++;\r\n            });\r\n    \r\n            uploader.on( 'fileDequeued', function() {\r\n                count--;\r\n            });\r\n    \r\n            uploader.on( 'uploadFinished', function() {\r\n                count = 0;\r\n            });\r\n        });\r\n    \r\n    \r\n        /**\r\n         * @property {int} [fileSizeLimit=undefined]\r\n         * @namespace options\r\n         * @for Uploader\r\n         * @description 验证文件总大小是否超出限制, 超出则不允许加入队列。\r\n         */\r\n        api.addValidator( 'fileSizeLimit', function() {\r\n            var uploader = this,\r\n                opts = uploader.options,\r\n                count = 0,\r\n                max = opts.fileSizeLimit >> 0,\r\n                flag = true;\r\n    \r\n            if ( !max ) {\r\n                return;\r\n            }\r\n    \r\n            uploader.on( 'beforeFileQueued', function( file ) {\r\n                var invalid = count + file.size > max;\r\n    \r\n                if ( invalid && flag ) {\r\n                    flag = false;\r\n                    this.trigger( 'error', 'Q_EXCEED_SIZE_LIMIT', max, file );\r\n                    setTimeout(function() {\r\n                        flag = true;\r\n                    }, 1 );\r\n                }\r\n    \r\n                return invalid ? false : true;\r\n            });\r\n    \r\n            uploader.on( 'fileQueued', function( file ) {\r\n                count += file.size;\r\n            });\r\n    \r\n            uploader.on( 'fileDequeued', function( file ) {\r\n                count -= file.size;\r\n            });\r\n    \r\n            uploader.on( 'uploadFinished', function() {\r\n                count = 0;\r\n            });\r\n        });\r\n    \r\n        /**\r\n         * @property {int} [fileSingleSizeLimit=undefined]\r\n         * @namespace options\r\n         * @for Uploader\r\n         * @description 验证单个文件大小是否超出限制, 超出则不允许加入队列。\r\n         */\r\n        api.addValidator( 'fileSingleSizeLimit', function() {\r\n            var uploader = this,\r\n                opts = uploader.options,\r\n                max = opts.fileSingleSizeLimit;\r\n    \r\n            if ( !max ) {\r\n                return;\r\n            }\r\n    \r\n            uploader.on( 'beforeFileQueued', function( file ) {\r\n    \r\n                if ( file.size > max ) {\r\n                    file.setStatus( WUFile.Status.INVALID, 'exceed_size' );\r\n                    this.trigger( 'error', 'F_EXCEED_SIZE', file );\r\n                    return false;\r\n                }\r\n    \r\n            });\r\n    \r\n        });\r\n    \r\n        /**\r\n         * @property {int} [duplicate=undefined]\r\n         * @namespace options\r\n         * @for Uploader\r\n         * @description 去重， 根据文件名字、文件大小和最后修改时间来生成hash Key.\r\n         */\r\n        api.addValidator( 'duplicate', function() {\r\n            var uploader = this,\r\n                opts = uploader.options,\r\n                mapping = {};\r\n    \r\n            if ( opts.duplicate ) {\r\n                return;\r\n            }\r\n    \r\n            function hashString( str ) {\r\n                var hash = 0,\r\n                    i = 0,\r\n                    len = str.length,\r\n                    _char;\r\n    \r\n                for ( ; i < len; i++ ) {\r\n                    _char = str.charCodeAt( i );\r\n                    hash = _char + (hash << 6) + (hash << 16) - hash;\r\n                }\r\n    \r\n                return hash;\r\n            }\r\n    \r\n            uploader.on( 'beforeFileQueued', function( file ) {\r\n                var hash = file.__hash || (file.__hash = hashString( file.name +\r\n                        file.size + file.lastModifiedDate ));\r\n    \r\n                // 已经重复了\r\n                if ( mapping[ hash ] ) {\r\n                    this.trigger( 'error', 'F_DUPLICATE', file );\r\n                    return false;\r\n                }\r\n            });\r\n    \r\n            uploader.on( 'fileQueued', function( file ) {\r\n                var hash = file.__hash;\r\n    \r\n                hash && (mapping[ hash ] = true);\r\n            });\r\n    \r\n            uploader.on( 'fileDequeued', function( file ) {\r\n                var hash = file.__hash;\r\n    \r\n                hash && (delete mapping[ hash ]);\r\n            });\r\n        });\r\n    \r\n        return api;\r\n    });\r\n    \r\n    /**\r\n     * @fileOverview Runtime管理器，负责Runtime的选择, 连接\r\n     */\r\n    define('runtime/compbase',[],function() {\r\n    \r\n        function CompBase( owner, runtime ) {\r\n    \r\n            this.owner = owner;\r\n            this.options = owner.options;\r\n    \r\n            this.getRuntime = function() {\r\n                return runtime;\r\n            };\r\n    \r\n            this.getRuid = function() {\r\n                return runtime.uid;\r\n            };\r\n    \r\n            this.trigger = function() {\r\n                return owner.trigger.apply( owner, arguments );\r\n            };\r\n        }\r\n    \r\n        return CompBase;\r\n    });\r\n    /**\r\n     * @fileOverview Html5Runtime\r\n     */\r\n    define('runtime/html5/runtime',[\r\n        'base',\r\n        'runtime/runtime',\r\n        'runtime/compbase'\r\n    ], function( Base, Runtime, CompBase ) {\r\n    \r\n        var type = 'html5',\r\n            components = {};\r\n    \r\n        function Html5Runtime() {\r\n            var pool = {},\r\n                me = this,\r\n                destory = this.destory;\r\n    \r\n            Runtime.apply( me, arguments );\r\n            me.type = type;\r\n    \r\n    \r\n            // 这个方法的调用者，实际上是RuntimeClient\r\n            me.exec = function( comp, fn/*, args...*/) {\r\n                var client = this,\r\n                    uid = client.uid,\r\n                    args = Base.slice( arguments, 2 ),\r\n                    instance;\r\n    \r\n                if ( components[ comp ] ) {\r\n                    instance = pool[ uid ] = pool[ uid ] ||\r\n                            new components[ comp ]( client, me );\r\n    \r\n                    if ( instance[ fn ] ) {\r\n                        return instance[ fn ].apply( instance, args );\r\n                    }\r\n                }\r\n            };\r\n    \r\n            me.destory = function() {\r\n                // @todo 删除池子中的所有实例\r\n                return destory && destory.apply( this, arguments );\r\n            };\r\n        }\r\n    \r\n        Base.inherits( Runtime, {\r\n            constructor: Html5Runtime,\r\n    \r\n            // 不需要连接其他程序，直接执行callback\r\n            init: function() {\r\n                var me = this;\r\n                setTimeout(function() {\r\n                    me.trigger('ready');\r\n                }, 1 );\r\n            }\r\n    \r\n        });\r\n    \r\n        // 注册Components\r\n        Html5Runtime.register = function( name, component ) {\r\n            var klass = components[ name ] = Base.inherits( CompBase, component );\r\n            return klass;\r\n        };\r\n    \r\n        // 注册html5运行时。\r\n        // 只有在支持的前提下注册。\r\n        if ( window.Blob && window.FileReader && window.DataView ) {\r\n            Runtime.addRuntime( type, Html5Runtime );\r\n        }\r\n    \r\n        return Html5Runtime;\r\n    });\r\n    /**\r\n     * @fileOverview Blob Html实现\r\n     */\r\n    define('runtime/html5/blob',[\r\n        'runtime/html5/runtime',\r\n        'lib/blob'\r\n    ], function( Html5Runtime, Blob ) {\r\n    \r\n        return Html5Runtime.register( 'Blob', {\r\n            slice: function( start, end ) {\r\n                var blob = this.owner.source,\r\n                    slice = blob.slice || blob.webkitSlice || blob.mozSlice;\r\n    \r\n                blob = slice.call( blob, start, end );\r\n    \r\n                return new Blob( this.getRuid(), blob );\r\n            }\r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview FilePaste\r\n     */\r\n    define('runtime/html5/dnd',[\r\n        'base',\r\n        'runtime/html5/runtime',\r\n        'lib/file'\r\n    ], function( Base, Html5Runtime, File ) {\r\n    \r\n        var $ = Base.$,\r\n            prefix = 'webuploader-dnd-';\r\n    \r\n        return Html5Runtime.register( 'DragAndDrop', {\r\n            init: function() {\r\n                var elem = this.elem = this.options.container;\r\n    \r\n                this.dragEnterHandler = Base.bindFn( this._dragEnterHandler, this );\r\n                this.dragOverHandler = Base.bindFn( this._dragOverHandler, this );\r\n                this.dragLeaveHandler = Base.bindFn( this._dragLeaveHandler, this );\r\n                this.dropHandler = Base.bindFn( this._dropHandler, this );\r\n                this.dndOver = false;\r\n    \r\n                elem.on( 'dragenter', this.dragEnterHandler );\r\n                elem.on( 'dragover', this.dragOverHandler );\r\n                elem.on( 'dragleave', this.dragLeaveHandler );\r\n                elem.on( 'drop', this.dropHandler );\r\n    \r\n                if ( this.options.disableGlobalDnd ) {\r\n                    $( document ).on( 'dragover', this.dragOverHandler );\r\n                    $( document ).on( 'drop', this.dropHandler );\r\n                }\r\n            },\r\n    \r\n            _dragEnterHandler: function( e ) {\r\n                var me = this,\r\n                    denied = me._denied || false,\r\n                    items;\r\n    \r\n                e = e.originalEvent || e;\r\n    \r\n                if ( !me.dndOver ) {\r\n                    me.dndOver = true;\r\n    \r\n                    // 注意只有 chrome 支持。\r\n                    items = e.dataTransfer.items;\r\n    \r\n                    if ( items && items.length ) {\r\n                        me._denied = denied = !me.trigger( 'accept', items );\r\n                    }\r\n    \r\n                    me.elem.addClass( prefix + 'over' );\r\n                    me.elem[ denied ? 'addClass' :\r\n                            'removeClass' ]( prefix + 'denied' );\r\n                }\r\n    \r\n    \r\n                e.dataTransfer.dropEffect = denied ? 'none' : 'copy';\r\n    \r\n                return false;\r\n            },\r\n    \r\n            _dragOverHandler: function( e ) {\r\n                // 只处理框内的。\r\n                var parentElem = this.elem.parent().get( 0 );\r\n                if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) {\r\n                    return false;\r\n                }\r\n    \r\n                clearTimeout( this._leaveTimer );\r\n                this._dragEnterHandler.call( this, e );\r\n    \r\n                return false;\r\n            },\r\n    \r\n            _dragLeaveHandler: function() {\r\n                var me = this,\r\n                    handler;\r\n    \r\n                handler = function() {\r\n                    me.dndOver = false;\r\n                    me.elem.removeClass( prefix + 'over ' + prefix + 'denied' );\r\n                };\r\n    \r\n                clearTimeout( me._leaveTimer );\r\n                me._leaveTimer = setTimeout( handler, 100 );\r\n                return false;\r\n            },\r\n    \r\n            _dropHandler: function( e ) {\r\n                var me = this,\r\n                    ruid = me.getRuid(),\r\n                    parentElem = me.elem.parent().get( 0 );\r\n    \r\n                // 只处理框内的。\r\n                if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) {\r\n                    return false;\r\n                }\r\n    \r\n                me._getTansferFiles( e, function( results ) {\r\n                    me.trigger( 'drop', $.map( results, function( file ) {\r\n                        return new File( ruid, file );\r\n                    }) );\r\n                });\r\n    \r\n                me.dndOver = false;\r\n                me.elem.removeClass( prefix + 'over' );\r\n                return false;\r\n            },\r\n    \r\n            // 如果传入 callback 则去查看文件夹，否则只管当前文件夹。\r\n            _getTansferFiles: function( e, callback ) {\r\n                var results  = [],\r\n                    promises = [],\r\n                    items, files, dataTransfer, file, item, i, len, canAccessFolder;\r\n    \r\n                e = e.originalEvent || e;\r\n    \r\n                dataTransfer = e.dataTransfer;\r\n                items = dataTransfer.items;\r\n                files = dataTransfer.files;\r\n    \r\n                canAccessFolder = !!(items && items[ 0 ].webkitGetAsEntry);\r\n    \r\n                for ( i = 0, len = files.length; i < len; i++ ) {\r\n                    file = files[ i ];\r\n                    item = items && items[ i ];\r\n    \r\n                    if ( canAccessFolder && item.webkitGetAsEntry().isDirectory ) {\r\n    \r\n                        promises.push( this._traverseDirectoryTree(\r\n                                item.webkitGetAsEntry(), results ) );\r\n                    } else {\r\n                        results.push( file );\r\n                    }\r\n                }\r\n    \r\n                Base.when.apply( Base, promises ).done(function() {\r\n    \r\n                    if ( !results.length ) {\r\n                        return;\r\n                    }\r\n    \r\n                    callback( results );\r\n                });\r\n            },\r\n    \r\n            _traverseDirectoryTree: function( entry, results ) {\r\n                var deferred = Base.Deferred(),\r\n                    me = this;\r\n    \r\n                if ( entry.isFile ) {\r\n                    entry.file(function( file ) {\r\n                        results.push( file );\r\n                        deferred.resolve();\r\n                    });\r\n                } else if ( entry.isDirectory ) {\r\n                    entry.createReader().readEntries(function( entries ) {\r\n                        var len = entries.length,\r\n                            promises = [],\r\n                            arr = [],    // 为了保证顺序。\r\n                            i;\r\n    \r\n                        for ( i = 0; i < len; i++ ) {\r\n                            promises.push( me._traverseDirectoryTree(\r\n                                    entries[ i ], arr ) );\r\n                        }\r\n    \r\n                        Base.when.apply( Base, promises ).then(function() {\r\n                            results.push.apply( results, arr );\r\n                            deferred.resolve();\r\n                        }, deferred.reject );\r\n                    });\r\n                }\r\n    \r\n                return deferred.promise();\r\n            },\r\n    \r\n            destroy: function() {\r\n                var elem = this.elem;\r\n    \r\n                elem.off( 'dragenter', this.dragEnterHandler );\r\n                elem.off( 'dragover', this.dragEnterHandler );\r\n                elem.off( 'dragleave', this.dragLeaveHandler );\r\n                elem.off( 'drop', this.dropHandler );\r\n    \r\n                if ( this.options.disableGlobalDnd ) {\r\n                    $( document ).off( 'dragover', this.dragOverHandler );\r\n                    $( document ).off( 'drop', this.dropHandler );\r\n                }\r\n            }\r\n        });\r\n    });\r\n    \r\n    /**\r\n     * @fileOverview FilePaste\r\n     */\r\n    define('runtime/html5/filepaste',[\r\n        'base',\r\n        'runtime/html5/runtime',\r\n        'lib/file'\r\n    ], function( Base, Html5Runtime, File ) {\r\n    \r\n        return Html5Runtime.register( 'FilePaste', {\r\n            init: function() {\r\n                var opts = this.options,\r\n                    elem = this.elem = opts.container,\r\n                    accept = '.*',\r\n                    arr, i, len, item;\r\n    \r\n                // accetp的mimeTypes中生成匹配正则。\r\n                if ( opts.accept ) {\r\n                    arr = [];\r\n    \r\n                    for ( i = 0, len = opts.accept.length; i < len; i++ ) {\r\n                        item = opts.accept[ i ].mimeTypes;\r\n                        item && arr.push( item );\r\n                    }\r\n    \r\n                    if ( arr.length ) {\r\n                        accept = arr.join(',');\r\n                        accept = accept.replace( /,/g, '|' ).replace( /\\*/g, '.*' );\r\n                    }\r\n                }\r\n                this.accept = accept = new RegExp( accept, 'i' );\r\n                this.hander = Base.bindFn( this._pasteHander, this );\r\n                elem.on( 'paste', this.hander );\r\n            },\r\n    \r\n            _pasteHander: function( e ) {\r\n                var allowed = [],\r\n                    ruid = this.getRuid(),\r\n                    items, item, blob, i, len;\r\n    \r\n                e = e.originalEvent || e;\r\n                items = e.clipboardData.items;\r\n    \r\n                for ( i = 0, len = items.length; i < len; i++ ) {\r\n                    item = items[ i ];\r\n    \r\n                    if ( item.kind !== 'file' || !(blob = item.getAsFile()) ) {\r\n                        continue;\r\n                    }\r\n    \r\n                    allowed.push( new File( ruid, blob ) );\r\n                }\r\n    \r\n                if ( allowed.length ) {\r\n                    // 不阻止非文件粘贴（文字粘贴）的事件冒泡\r\n                    e.preventDefault();\r\n                    e.stopPropagation();\r\n                    this.trigger( 'paste', allowed );\r\n                }\r\n            },\r\n    \r\n            destroy: function() {\r\n                this.elem.off( 'paste', this.hander );\r\n            }\r\n        });\r\n    });\r\n    \r\n    /**\r\n     * @fileOverview FilePicker\r\n     */\r\n    define('runtime/html5/filepicker',[\r\n        'base',\r\n        'runtime/html5/runtime'\r\n    ], function( Base, Html5Runtime ) {\r\n    \r\n        var $ = Base.$;\r\n    \r\n        return Html5Runtime.register( 'FilePicker', {\r\n            init: function() {\r\n                var container = this.getRuntime().getContainer(),\r\n                    me = this,\r\n                    owner = me.owner,\r\n                    opts = me.options,\r\n                    lable = $( document.createElement('label') ),\r\n                    input = $( document.createElement('input') ),\r\n                    arr, i, len, mouseHandler;\r\n    \r\n                input.attr( 'type', 'file' );\r\n                input.attr( 'name', opts.name );\r\n                input.addClass('webuploader-element-invisible');\r\n    \r\n                lable.on( 'click', function() {\r\n                    input.trigger('click');\r\n                });\r\n    \r\n                lable.css({\r\n                    opacity: 0,\r\n                    width: '100%',\r\n                    height: '100%',\r\n                    display: 'block',\r\n                    cursor: 'pointer',\r\n                    background: '#ffffff'\r\n                });\r\n    \r\n                if ( opts.multiple ) {\r\n                    input.attr( 'multiple', 'multiple' );\r\n                }\r\n    \r\n                // @todo Firefox不支持单独指定后缀\r\n                if ( opts.accept && opts.accept.length > 0 ) {\r\n                    arr = [];\r\n    \r\n                    for ( i = 0, len = opts.accept.length; i < len; i++ ) {\r\n                        arr.push( opts.accept[ i ].mimeTypes );\r\n                    }\r\n    \r\n                    input.attr( 'accept', arr.join(',') );\r\n                }\r\n    \r\n                container.append( input );\r\n                container.append( lable );\r\n    \r\n                mouseHandler = function( e ) {\r\n                    owner.trigger( e.type );\r\n                };\r\n    \r\n                input.on( 'change', function( e ) {\r\n                    var fn = arguments.callee,\r\n                        clone;\r\n    \r\n                    me.files = e.target.files;\r\n    \r\n                    // reset input\r\n                    clone = this.cloneNode( true );\r\n                    this.parentNode.replaceChild( clone, this );\r\n    \r\n                    input.off();\r\n                    input = $( clone ).on( 'change', fn )\r\n                            .on( 'mouseenter mouseleave', mouseHandler );\r\n    \r\n                    owner.trigger('change');\r\n                });\r\n    \r\n                lable.on( 'mouseenter mouseleave', mouseHandler );\r\n    \r\n            },\r\n    \r\n    \r\n            getFiles: function() {\r\n                return this.files;\r\n            },\r\n    \r\n            destroy: function() {\r\n                // todo\r\n            }\r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview Transport\r\n     * @todo 支持chunked传输，优势：\r\n     * 可以将大文件分成小块，挨个传输，可以提高大文件成功率，当失败的时候，也只需要重传那小部分，\r\n     * 而不需要重头再传一次。另外断点续传也需要用chunked方式。\r\n     */\r\n    define('runtime/html5/transport',[\r\n        'base',\r\n        'runtime/html5/runtime'\r\n    ], function( Base, Html5Runtime ) {\r\n    \r\n        var noop = Base.noop,\r\n            $ = Base.$;\r\n    \r\n        return Html5Runtime.register( 'Transport', {\r\n            init: function() {\r\n                this._status = 0;\r\n                this._response = null;\r\n            },\r\n    \r\n            send: function() {\r\n                var owner = this.owner,\r\n                    opts = this.options,\r\n                    xhr = this._initAjax(),\r\n                    blob = owner._blob,\r\n                    server = opts.server,\r\n                    formData, binary, fr;\r\n    \r\n                if ( opts.sendAsBinary ) {\r\n                    server += (/\\?/.test( server ) ? '&' : '?') +\r\n                            $.param( owner._formData );\r\n    \r\n                    binary = blob.getSource();\r\n                } else {\r\n                    formData = new FormData();\r\n                    $.each( owner._formData, function( k, v ) {\r\n                        formData.append( k, v );\r\n                    });\r\n    \r\n                    formData.append( opts.fileVal, blob.getSource(),\r\n                            opts.filename || owner._formData.name || '' );\r\n                }\r\n    \r\n                if ( opts.withCredentials && 'withCredentials' in xhr ) {\r\n                    xhr.open( opts.method, server, true );\r\n                    xhr.withCredentials = true;\r\n                } else {\r\n                    xhr.open( opts.method, server );\r\n                }\r\n    \r\n                this._setRequestHeader( xhr, opts.headers );\r\n    \r\n                if ( binary ) {\r\n                    xhr.overrideMimeType('application/octet-stream');\r\n    \r\n                    // android直接发送blob会导致服务端接收到的是空文件。\r\n                    // bug详情。\r\n                    // https://code.google.com/p/android/issues/detail?id=39882\r\n                    // 所以先用fileReader读取出来再通过arraybuffer的方式发送。\r\n                    if ( Base.os.android ) {\r\n                        fr = new FileReader();\r\n    \r\n                        fr.onload = function() {\r\n                            xhr.send( this.result );\r\n                            fr = fr.onload = null;\r\n                        };\r\n    \r\n                        fr.readAsArrayBuffer( binary );\r\n                    } else {\r\n                        xhr.send( binary );\r\n                    }\r\n                } else {\r\n                    xhr.send( formData );\r\n                }\r\n            },\r\n    \r\n            getResponse: function() {\r\n                return this._response;\r\n            },\r\n    \r\n            getResponseAsJson: function() {\r\n                return this._parseJson( this._response );\r\n            },\r\n    \r\n            getStatus: function() {\r\n                return this._status;\r\n            },\r\n    \r\n            abort: function() {\r\n                var xhr = this._xhr;\r\n    \r\n                if ( xhr ) {\r\n                    xhr.upload.onprogress = noop;\r\n                    xhr.onreadystatechange = noop;\r\n                    xhr.abort();\r\n    \r\n                    this._xhr = xhr = null;\r\n                }\r\n            },\r\n    \r\n            destroy: function() {\r\n                this.abort();\r\n            },\r\n    \r\n            _initAjax: function() {\r\n                var me = this,\r\n                    xhr = new XMLHttpRequest(),\r\n                    opts = this.options;\r\n    \r\n                if ( opts.withCredentials && !('withCredentials' in xhr) &&\r\n                        typeof XDomainRequest !== 'undefined' ) {\r\n                    xhr = new XDomainRequest();\r\n                }\r\n    \r\n                xhr.upload.onprogress = function( e ) {\r\n                    var percentage = 0;\r\n    \r\n                    if ( e.lengthComputable ) {\r\n                        percentage = e.loaded / e.total;\r\n                    }\r\n    \r\n                    return me.trigger( 'progress', percentage );\r\n                };\r\n    \r\n                xhr.onreadystatechange = function() {\r\n    \r\n                    if ( xhr.readyState !== 4 ) {\r\n                        return;\r\n                    }\r\n    \r\n                    xhr.upload.onprogress = noop;\r\n                    xhr.onreadystatechange = noop;\r\n                    me._xhr = null;\r\n                    me._status = xhr.status;\r\n    \r\n                    if ( xhr.status >= 200 && xhr.status < 300 ) {\r\n                        me._response = xhr.responseText;\r\n                        return me.trigger('load');\r\n                    } else if ( xhr.status >= 500 && xhr.status < 600 ) {\r\n                        me._response = xhr.responseText;\r\n                        return me.trigger( 'error', 'server' );\r\n                    }\r\n    \r\n    \r\n                    return me.trigger( 'error', me._status ? 'http' : 'abort' );\r\n                };\r\n    \r\n                me._xhr = xhr;\r\n                return xhr;\r\n            },\r\n    \r\n            _setRequestHeader: function( xhr, headers ) {\r\n                $.each( headers, function( key, val ) {\r\n                    xhr.setRequestHeader( key, val );\r\n                });\r\n            },\r\n    \r\n            _parseJson: function( str ) {\r\n                var json;\r\n    \r\n                try {\r\n                    json = JSON.parse( str );\r\n                } catch ( ex ) {\r\n                    json = {};\r\n                }\r\n    \r\n                return json;\r\n            }\r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview FlashRuntime\r\n     */\r\n    define('runtime/flash/runtime',[\r\n        'base',\r\n        'runtime/runtime',\r\n        'runtime/compbase'\r\n    ], function( Base, Runtime, CompBase ) {\r\n    \r\n        var $ = Base.$,\r\n            type = 'flash',\r\n            components = {};\r\n    \r\n    \r\n        function getFlashVersion() {\r\n            var version;\r\n    \r\n            try {\r\n                version = navigator.plugins[ 'Shockwave Flash' ];\r\n                version = version.description;\r\n            } catch ( ex ) {\r\n                try {\r\n                    version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash')\r\n                            .GetVariable('$version');\r\n                } catch ( ex2 ) {\r\n                    version = '0.0';\r\n                }\r\n            }\r\n            version = version.match( /\\d+/g );\r\n            return parseFloat( version[ 0 ] + '.' + version[ 1 ], 10 );\r\n        }\r\n    \r\n        function FlashRuntime() {\r\n            var pool = {},\r\n                clients = {},\r\n                destory = this.destory,\r\n                me = this,\r\n                jsreciver = Base.guid('webuploader_');\r\n    \r\n            Runtime.apply( me, arguments );\r\n            me.type = type;\r\n    \r\n    \r\n            // 这个方法的调用者，实际上是RuntimeClient\r\n            me.exec = function( comp, fn/*, args...*/ ) {\r\n                var client = this,\r\n                    uid = client.uid,\r\n                    args = Base.slice( arguments, 2 ),\r\n                    instance;\r\n    \r\n                clients[ uid ] = client;\r\n    \r\n                if ( components[ comp ] ) {\r\n                    if ( !pool[ uid ] ) {\r\n                        pool[ uid ] = new components[ comp ]( client, me );\r\n                    }\r\n    \r\n                    instance = pool[ uid ];\r\n    \r\n                    if ( instance[ fn ] ) {\r\n                        return instance[ fn ].apply( instance, args );\r\n                    }\r\n                }\r\n    \r\n                return me.flashExec.apply( client, arguments );\r\n            };\r\n    \r\n            function handler( evt, obj ) {\r\n                var type = evt.type || evt,\r\n                    parts, uid;\r\n    \r\n                parts = type.split('::');\r\n                uid = parts[ 0 ];\r\n                type = parts[ 1 ];\r\n    \r\n                // console.log.apply( console, arguments );\r\n    \r\n                if ( type === 'Ready' && uid === me.uid ) {\r\n                    me.trigger('ready');\r\n                } else if ( clients[ uid ] ) {\r\n                    clients[ uid ].trigger( type.toLowerCase(), evt, obj );\r\n                }\r\n    \r\n                // Base.log( evt, obj );\r\n            }\r\n    \r\n            // flash的接受器。\r\n            window[ jsreciver ] = function() {\r\n                var args = arguments;\r\n    \r\n                // 为了能捕获得到。\r\n                setTimeout(function() {\r\n                    handler.apply( null, args );\r\n                }, 1 );\r\n            };\r\n    \r\n            this.jsreciver = jsreciver;\r\n    \r\n            this.destory = function() {\r\n                // @todo 删除池子中的所有实例\r\n                return destory && destory.apply( this, arguments );\r\n            };\r\n    \r\n            this.flashExec = function( comp, fn ) {\r\n                var flash = me.getFlash(),\r\n                    args = Base.slice( arguments, 2 );\r\n    \r\n                return flash.exec( this.uid, comp, fn, args );\r\n            };\r\n    \r\n            // @todo\r\n        }\r\n    \r\n        Base.inherits( Runtime, {\r\n            constructor: FlashRuntime,\r\n    \r\n            init: function() {\r\n                var container = this.getContainer(),\r\n                    opts = this.options,\r\n                    html;\r\n    \r\n                // if not the minimal height, shims are not initialized\r\n                // in older browsers (e.g FF3.6, IE6,7,8, Safari 4.0,5.0, etc)\r\n                container.css({\r\n                    position: 'absolute',\r\n                    top: '-8px',\r\n                    left: '-8px',\r\n                    width: '9px',\r\n                    height: '9px',\r\n                    overflow: 'hidden'\r\n                });\r\n    \r\n                // insert flash object\r\n                html = '<object id=\"' + this.uid + '\" type=\"application/' +\r\n                        'x-shockwave-flash\" data=\"' +  opts.swf + '\" ';\r\n    \r\n                if ( Base.browser.ie ) {\r\n                    html += 'classid=\"clsid:d27cdb6e-ae6d-11cf-96b8-444553540000\" ';\r\n                }\r\n    \r\n                html += 'width=\"100%\" height=\"100%\" style=\"outline:0\">'  +\r\n                    '<param name=\"movie\" value=\"' + opts.swf + '\" />' +\r\n                    '<param name=\"flashvars\" value=\"uid=' + this.uid +\r\n                    '&jsreciver=' + this.jsreciver + '\" />' +\r\n                    '<param name=\"wmode\" value=\"transparent\" />' +\r\n                    '<param name=\"allowscriptaccess\" value=\"always\" />' +\r\n                '</object>';\r\n    \r\n                container.html( html );\r\n            },\r\n    \r\n            getFlash: function() {\r\n                if ( this._flash ) {\r\n                    return this._flash;\r\n                }\r\n    \r\n                this._flash = $( '#' + this.uid ).get( 0 );\r\n                return this._flash;\r\n            }\r\n    \r\n        });\r\n    \r\n        FlashRuntime.register = function( name, component ) {\r\n            component = components[ name ] = Base.inherits( CompBase, $.extend({\r\n    \r\n                // @todo fix this later\r\n                flashExec: function() {\r\n                    var owner = this.owner,\r\n                        runtime = this.getRuntime();\r\n    \r\n                    return runtime.flashExec.apply( owner, arguments );\r\n                }\r\n            }, component ) );\r\n    \r\n            return component;\r\n        };\r\n    \r\n        if ( getFlashVersion() >= 11.4 ) {\r\n            Runtime.addRuntime( type, FlashRuntime );\r\n        }\r\n    \r\n        return FlashRuntime;\r\n    });\r\n    /**\r\n     * @fileOverview FilePicker\r\n     */\r\n    define('runtime/flash/filepicker',[\r\n        'base',\r\n        'runtime/flash/runtime'\r\n    ], function( Base, FlashRuntime ) {\r\n        var $ = Base.$;\r\n    \r\n        return FlashRuntime.register( 'FilePicker', {\r\n            init: function( opts ) {\r\n                var copy = $.extend({}, opts ),\r\n                    len, i;\r\n    \r\n                // 修复Flash再没有设置title的情况下无法弹出flash文件选择框的bug.\r\n                len = copy.accept && copy.accept.length;\r\n                for (  i = 0; i < len; i++ ) {\r\n                    if ( !copy.accept[ i ].title ) {\r\n                        copy.accept[ i ].title = 'Files';\r\n                    }\r\n                }\r\n    \r\n                delete copy.button;\r\n                delete copy.container;\r\n    \r\n                this.flashExec( 'FilePicker', 'init', copy );\r\n            },\r\n    \r\n            destroy: function() {\r\n                // todo\r\n            }\r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview  Transport flash实现\r\n     */\r\n    define('runtime/flash/transport',[\r\n        'base',\r\n        'runtime/flash/runtime',\r\n        'runtime/client'\r\n    ], function( Base, FlashRuntime, RuntimeClient ) {\r\n        var $ = Base.$;\r\n    \r\n        return FlashRuntime.register( 'Transport', {\r\n            init: function() {\r\n                this._status = 0;\r\n                this._response = null;\r\n                this._responseJson = null;\r\n            },\r\n    \r\n            send: function() {\r\n                var owner = this.owner,\r\n                    opts = this.options,\r\n                    xhr = this._initAjax(),\r\n                    blob = owner._blob,\r\n                    server = opts.server,\r\n                    binary;\r\n    \r\n                xhr.connectRuntime( blob.ruid );\r\n    \r\n                if ( opts.sendAsBinary ) {\r\n                    server += (/\\?/.test( server ) ? '&' : '?') +\r\n                            $.param( owner._formData );\r\n    \r\n                    binary = blob.uid;\r\n                } else {\r\n                    $.each( owner._formData, function( k, v ) {\r\n                        xhr.exec( 'append', k, v );\r\n                    });\r\n    \r\n                    xhr.exec( 'appendBlob', opts.fileVal, blob.uid,\r\n                            opts.filename || owner._formData.name || '' );\r\n                }\r\n    \r\n                this._setRequestHeader( xhr, opts.headers );\r\n                xhr.exec( 'send', {\r\n                    method: opts.method,\r\n                    url: server\r\n                }, binary );\r\n            },\r\n    \r\n            getStatus: function() {\r\n                return this._status;\r\n            },\r\n    \r\n            getResponse: function() {\r\n                return this._response;\r\n            },\r\n    \r\n            getResponseAsJson: function() {\r\n                return this._responseJson;\r\n            },\r\n    \r\n            abort: function() {\r\n                var xhr = this._xhr;\r\n    \r\n                if ( xhr ) {\r\n                    xhr.exec('abort');\r\n                    xhr.destroy();\r\n                    this._xhr = xhr = null;\r\n                }\r\n            },\r\n    \r\n            destroy: function() {\r\n                this.abort();\r\n            },\r\n    \r\n            _initAjax: function() {\r\n                var me = this,\r\n                    xhr = new RuntimeClient('XMLHttpRequest');\r\n    \r\n                xhr.on( 'uploadprogress progress', function( e ) {\r\n                    return me.trigger( 'progress', e.loaded / e.total );\r\n                });\r\n    \r\n                xhr.on( 'load', function() {\r\n                    var status = xhr.exec('getStatus'),\r\n                        err = '';\r\n    \r\n                    xhr.off();\r\n                    me._xhr = null;\r\n    \r\n                    if ( status >= 200 && status < 300 ) {\r\n                        me._response = xhr.exec('getResponse');\r\n                        me._responseJson = xhr.exec('getResponseAsJson');\r\n                    } else if ( status >= 500 && status < 600 ) {\r\n                        me._response = xhr.exec('getResponse');\r\n                        me._responseJson = xhr.exec('getResponseAsJson');\r\n                        err = 'server';\r\n                    } else {\r\n                        err = 'http';\r\n                    }\r\n    \r\n                    xhr.destroy();\r\n                    xhr = null;\r\n    \r\n                    return err ? me.trigger( 'error', err ) : me.trigger('load');\r\n                });\r\n    \r\n                xhr.on( 'error', function() {\r\n                    xhr.off();\r\n                    me._xhr = null;\r\n                    me.trigger( 'error', 'http' );\r\n                });\r\n    \r\n                me._xhr = xhr;\r\n                return xhr;\r\n            },\r\n    \r\n            _setRequestHeader: function( xhr, headers ) {\r\n                $.each( headers, function( key, val ) {\r\n                    xhr.exec( 'setRequestHeader', key, val );\r\n                });\r\n            }\r\n        });\r\n    });\r\n    /**\r\n     * @fileOverview 没有图像处理的版本。\r\n     */\r\n    define('preset/withoutimage',[\r\n        'base',\r\n    \r\n        // widgets\r\n        'widgets/filednd',\r\n        'widgets/filepaste',\r\n        'widgets/filepicker',\r\n        'widgets/queue',\r\n        'widgets/runtime',\r\n        'widgets/upload',\r\n        'widgets/validator',\r\n    \r\n        // runtimes\r\n        // html5\r\n        'runtime/html5/blob',\r\n        'runtime/html5/dnd',\r\n        'runtime/html5/filepaste',\r\n        'runtime/html5/filepicker',\r\n        'runtime/html5/transport',\r\n    \r\n        // flash\r\n        'runtime/flash/filepicker',\r\n        'runtime/flash/transport'\r\n    ], function( Base ) {\r\n        return Base;\r\n    });\r\n    define('webuploader',[\r\n        'preset/withoutimage'\r\n    ], function( preset ) {\r\n        return preset;\r\n    });\r\n    return require('webuploader');\r\n});\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/third-party/zeroclipboard/ZeroClipboard.js",
    "content": "/*!\r\n* ZeroClipboard\r\n* The ZeroClipboard library provides an easy way to copy text to the clipboard using an invisible Adobe Flash movie and a JavaScript interface.\r\n* Copyright (c) 2014 Jon Rohan, James M. Greene\r\n* Licensed MIT\r\n* http://zeroclipboard.org/\r\n* v2.0.0-beta.5\r\n*/\r\n(function(window) {\r\n  \"use strict\";\r\n  var _currentElement;\r\n  var _flashState = {\r\n    bridge: null,\r\n    version: \"0.0.0\",\r\n    pluginType: \"unknown\",\r\n    disabled: null,\r\n    outdated: null,\r\n    unavailable: null,\r\n    deactivated: null,\r\n    overdue: null,\r\n    ready: null\r\n  };\r\n  var _clipData = {};\r\n  var _clipDataFormatMap = null;\r\n  var _clientIdCounter = 0;\r\n  var _clientMeta = {};\r\n  var _elementIdCounter = 0;\r\n  var _elementMeta = {};\r\n  var _swfPath = function() {\r\n    var i, jsDir, tmpJsPath, jsPath, swfPath = \"ZeroClipboard.swf\";\r\n    if (!(document.currentScript && (jsPath = document.currentScript.src))) {\r\n      var scripts = document.getElementsByTagName(\"script\");\r\n      if (\"readyState\" in scripts[0]) {\r\n        for (i = scripts.length; i--; ) {\r\n          if (scripts[i].readyState === \"interactive\" && (jsPath = scripts[i].src)) {\r\n            break;\r\n          }\r\n        }\r\n      } else if (document.readyState === \"loading\") {\r\n        jsPath = scripts[scripts.length - 1].src;\r\n      } else {\r\n        for (i = scripts.length; i--; ) {\r\n          tmpJsPath = scripts[i].src;\r\n          if (!tmpJsPath) {\r\n            jsDir = null;\r\n            break;\r\n          }\r\n          tmpJsPath = tmpJsPath.split(\"#\")[0].split(\"?\")[0];\r\n          tmpJsPath = tmpJsPath.slice(0, tmpJsPath.lastIndexOf(\"/\") + 1);\r\n          if (jsDir == null) {\r\n            jsDir = tmpJsPath;\r\n          } else if (jsDir !== tmpJsPath) {\r\n            jsDir = null;\r\n            break;\r\n          }\r\n        }\r\n        if (jsDir !== null) {\r\n          jsPath = jsDir;\r\n        }\r\n      }\r\n    }\r\n    if (jsPath) {\r\n      jsPath = jsPath.split(\"#\")[0].split(\"?\")[0];\r\n      swfPath = jsPath.slice(0, jsPath.lastIndexOf(\"/\") + 1) + swfPath;\r\n    }\r\n    return swfPath;\r\n  }();\r\n  var _camelizeCssPropName = function() {\r\n    var matcherRegex = /\\-([a-z])/g, replacerFn = function(match, group) {\r\n      return group.toUpperCase();\r\n    };\r\n    return function(prop) {\r\n      return prop.replace(matcherRegex, replacerFn);\r\n    };\r\n  }();\r\n  var _getStyle = function(el, prop) {\r\n    var value, camelProp, tagName;\r\n    if (window.getComputedStyle) {\r\n      value = window.getComputedStyle(el, null).getPropertyValue(prop);\r\n    } else {\r\n      camelProp = _camelizeCssPropName(prop);\r\n      if (el.currentStyle) {\r\n        value = el.currentStyle[camelProp];\r\n      } else {\r\n        value = el.style[camelProp];\r\n      }\r\n    }\r\n    if (prop === \"cursor\") {\r\n      if (!value || value === \"auto\") {\r\n        tagName = el.tagName.toLowerCase();\r\n        if (tagName === \"a\") {\r\n          return \"pointer\";\r\n        }\r\n      }\r\n    }\r\n    return value;\r\n  };\r\n  var _elementMouseOver = function(event) {\r\n    if (!event) {\r\n      event = window.event;\r\n    }\r\n    var target;\r\n    if (this !== window) {\r\n      target = this;\r\n    } else if (event.target) {\r\n      target = event.target;\r\n    } else if (event.srcElement) {\r\n      target = event.srcElement;\r\n    }\r\n    ZeroClipboard.activate(target);\r\n  };\r\n  var _addEventHandler = function(element, method, func) {\r\n    if (!element || element.nodeType !== 1) {\r\n      return;\r\n    }\r\n    if (element.addEventListener) {\r\n      element.addEventListener(method, func, false);\r\n    } else if (element.attachEvent) {\r\n      element.attachEvent(\"on\" + method, func);\r\n    }\r\n  };\r\n  var _removeEventHandler = function(element, method, func) {\r\n    if (!element || element.nodeType !== 1) {\r\n      return;\r\n    }\r\n    if (element.removeEventListener) {\r\n      element.removeEventListener(method, func, false);\r\n    } else if (element.detachEvent) {\r\n      element.detachEvent(\"on\" + method, func);\r\n    }\r\n  };\r\n  var _addClass = function(element, value) {\r\n    if (!element || element.nodeType !== 1) {\r\n      return element;\r\n    }\r\n    if (element.classList) {\r\n      if (!element.classList.contains(value)) {\r\n        element.classList.add(value);\r\n      }\r\n      return element;\r\n    }\r\n    if (value && typeof value === \"string\") {\r\n      var classNames = (value || \"\").split(/\\s+/);\r\n      if (element.nodeType === 1) {\r\n        if (!element.className) {\r\n          element.className = value;\r\n        } else {\r\n          var className = \" \" + element.className + \" \", setClass = element.className;\r\n          for (var c = 0, cl = classNames.length; c < cl; c++) {\r\n            if (className.indexOf(\" \" + classNames[c] + \" \") < 0) {\r\n              setClass += \" \" + classNames[c];\r\n            }\r\n          }\r\n          element.className = setClass.replace(/^\\s+|\\s+$/g, \"\");\r\n        }\r\n      }\r\n    }\r\n    return element;\r\n  };\r\n  var _removeClass = function(element, value) {\r\n    if (!element || element.nodeType !== 1) {\r\n      return element;\r\n    }\r\n    if (element.classList) {\r\n      if (element.classList.contains(value)) {\r\n        element.classList.remove(value);\r\n      }\r\n      return element;\r\n    }\r\n    if (value && typeof value === \"string\" || value === undefined) {\r\n      var classNames = (value || \"\").split(/\\s+/);\r\n      if (element.nodeType === 1 && element.className) {\r\n        if (value) {\r\n          var className = (\" \" + element.className + \" \").replace(/[\\n\\t]/g, \" \");\r\n          for (var c = 0, cl = classNames.length; c < cl; c++) {\r\n            className = className.replace(\" \" + classNames[c] + \" \", \" \");\r\n          }\r\n          element.className = className.replace(/^\\s+|\\s+$/g, \"\");\r\n        } else {\r\n          element.className = \"\";\r\n        }\r\n      }\r\n    }\r\n    return element;\r\n  };\r\n  var _getZoomFactor = function() {\r\n    var rect, physicalWidth, logicalWidth, zoomFactor = 1;\r\n    if (typeof document.body.getBoundingClientRect === \"function\") {\r\n      rect = document.body.getBoundingClientRect();\r\n      physicalWidth = rect.right - rect.left;\r\n      logicalWidth = document.body.offsetWidth;\r\n      zoomFactor = Math.round(physicalWidth / logicalWidth * 100) / 100;\r\n    }\r\n    return zoomFactor;\r\n  };\r\n  var _getDOMObjectPosition = function(obj, defaultZIndex) {\r\n    var info = {\r\n      left: 0,\r\n      top: 0,\r\n      width: 0,\r\n      height: 0,\r\n      zIndex: _getSafeZIndex(defaultZIndex) - 1\r\n    };\r\n    if (obj.getBoundingClientRect) {\r\n      var rect = obj.getBoundingClientRect();\r\n      var pageXOffset, pageYOffset, zoomFactor;\r\n      if (\"pageXOffset\" in window && \"pageYOffset\" in window) {\r\n        pageXOffset = window.pageXOffset;\r\n        pageYOffset = window.pageYOffset;\r\n      } else {\r\n        zoomFactor = _getZoomFactor();\r\n        pageXOffset = Math.round(document.documentElement.scrollLeft / zoomFactor);\r\n        pageYOffset = Math.round(document.documentElement.scrollTop / zoomFactor);\r\n      }\r\n      var leftBorderWidth = document.documentElement.clientLeft || 0;\r\n      var topBorderWidth = document.documentElement.clientTop || 0;\r\n      info.left = rect.left + pageXOffset - leftBorderWidth;\r\n      info.top = rect.top + pageYOffset - topBorderWidth;\r\n      info.width = \"width\" in rect ? rect.width : rect.right - rect.left;\r\n      info.height = \"height\" in rect ? rect.height : rect.bottom - rect.top;\r\n    }\r\n    return info;\r\n  };\r\n  var _cacheBust = function(path, options) {\r\n    var cacheBust = options == null || options && options.cacheBust === true;\r\n    if (cacheBust) {\r\n      return (path.indexOf(\"?\") === -1 ? \"?\" : \"&\") + \"noCache=\" + new Date().getTime();\r\n    } else {\r\n      return \"\";\r\n    }\r\n  };\r\n  var _vars = function(options) {\r\n    var i, len, domain, domains, str = \"\", trustedOriginsExpanded = [];\r\n    if (options.trustedDomains) {\r\n      if (typeof options.trustedDomains === \"string\") {\r\n        domains = [ options.trustedDomains ];\r\n      } else if (typeof options.trustedDomains === \"object\" && \"length\" in options.trustedDomains) {\r\n        domains = options.trustedDomains;\r\n      }\r\n    }\r\n    if (domains && domains.length) {\r\n      for (i = 0, len = domains.length; i < len; i++) {\r\n        if (domains.hasOwnProperty(i) && domains[i] && typeof domains[i] === \"string\") {\r\n          domain = _extractDomain(domains[i]);\r\n          if (!domain) {\r\n            continue;\r\n          }\r\n          if (domain === \"*\") {\r\n            trustedOriginsExpanded = [ domain ];\r\n            break;\r\n          }\r\n          trustedOriginsExpanded.push.apply(trustedOriginsExpanded, [ domain, \"//\" + domain, window.location.protocol + \"//\" + domain ]);\r\n        }\r\n      }\r\n    }\r\n    if (trustedOriginsExpanded.length) {\r\n      str += \"trustedOrigins=\" + encodeURIComponent(trustedOriginsExpanded.join(\",\"));\r\n    }\r\n    if (options.forceEnhancedClipboard === true) {\r\n      str += (str ? \"&\" : \"\") + \"forceEnhancedClipboard=true\";\r\n    }\r\n    return str;\r\n  };\r\n  var _inArray = function(elem, array, fromIndex) {\r\n    if (typeof array.indexOf === \"function\") {\r\n      return array.indexOf(elem, fromIndex);\r\n    }\r\n    var i, len = array.length;\r\n    if (typeof fromIndex === \"undefined\") {\r\n      fromIndex = 0;\r\n    } else if (fromIndex < 0) {\r\n      fromIndex = len + fromIndex;\r\n    }\r\n    for (i = fromIndex; i < len; i++) {\r\n      if (array.hasOwnProperty(i) && array[i] === elem) {\r\n        return i;\r\n      }\r\n    }\r\n    return -1;\r\n  };\r\n  var _prepClip = function(elements) {\r\n    if (typeof elements === \"string\") {\r\n      throw new TypeError(\"ZeroClipboard doesn't accept query strings.\");\r\n    }\r\n    return typeof elements.length !== \"number\" ? [ elements ] : elements;\r\n  };\r\n  var _dispatchCallback = function(func, context, args, async) {\r\n    if (async) {\r\n      window.setTimeout(function() {\r\n        func.apply(context, args);\r\n      }, 0);\r\n    } else {\r\n      func.apply(context, args);\r\n    }\r\n  };\r\n  var _getSafeZIndex = function(val) {\r\n    var zIndex, tmp;\r\n    if (val) {\r\n      if (typeof val === \"number\" && val > 0) {\r\n        zIndex = val;\r\n      } else if (typeof val === \"string\" && (tmp = parseInt(val, 10)) && !isNaN(tmp) && tmp > 0) {\r\n        zIndex = tmp;\r\n      }\r\n    }\r\n    if (!zIndex) {\r\n      if (typeof _globalConfig.zIndex === \"number\" && _globalConfig.zIndex > 0) {\r\n        zIndex = _globalConfig.zIndex;\r\n      } else if (typeof _globalConfig.zIndex === \"string\" && (tmp = parseInt(_globalConfig.zIndex, 10)) && !isNaN(tmp) && tmp > 0) {\r\n        zIndex = tmp;\r\n      }\r\n    }\r\n    return zIndex || 0;\r\n  };\r\n  var _extend = function() {\r\n    var i, len, arg, prop, src, copy, target = arguments[0] || {};\r\n    for (i = 1, len = arguments.length; i < len; i++) {\r\n      if ((arg = arguments[i]) != null) {\r\n        for (prop in arg) {\r\n          if (arg.hasOwnProperty(prop)) {\r\n            src = target[prop];\r\n            copy = arg[prop];\r\n            if (target === copy) {\r\n              continue;\r\n            }\r\n            if (copy !== undefined) {\r\n              target[prop] = copy;\r\n            }\r\n          }\r\n        }\r\n      }\r\n    }\r\n    return target;\r\n  };\r\n  var _extractDomain = function(originOrUrl) {\r\n    if (originOrUrl == null || originOrUrl === \"\") {\r\n      return null;\r\n    }\r\n    originOrUrl = originOrUrl.replace(/^\\s+|\\s+$/g, \"\");\r\n    if (originOrUrl === \"\") {\r\n      return null;\r\n    }\r\n    var protocolIndex = originOrUrl.indexOf(\"//\");\r\n    originOrUrl = protocolIndex === -1 ? originOrUrl : originOrUrl.slice(protocolIndex + 2);\r\n    var pathIndex = originOrUrl.indexOf(\"/\");\r\n    originOrUrl = pathIndex === -1 ? originOrUrl : protocolIndex === -1 || pathIndex === 0 ? null : originOrUrl.slice(0, pathIndex);\r\n    if (originOrUrl && originOrUrl.slice(-4).toLowerCase() === \".swf\") {\r\n      return null;\r\n    }\r\n    return originOrUrl || null;\r\n  };\r\n  var _determineScriptAccess = function() {\r\n    var _extractAllDomains = function(origins, resultsArray) {\r\n      var i, len, tmp;\r\n      if (origins == null || resultsArray[0] === \"*\") {\r\n        return;\r\n      }\r\n      if (typeof origins === \"string\") {\r\n        origins = [ origins ];\r\n      }\r\n      if (!(typeof origins === \"object\" && typeof origins.length === \"number\")) {\r\n        return;\r\n      }\r\n      for (i = 0, len = origins.length; i < len; i++) {\r\n        if (origins.hasOwnProperty(i) && (tmp = _extractDomain(origins[i]))) {\r\n          if (tmp === \"*\") {\r\n            resultsArray.length = 0;\r\n            resultsArray.push(\"*\");\r\n            break;\r\n          }\r\n          if (_inArray(tmp, resultsArray) === -1) {\r\n            resultsArray.push(tmp);\r\n          }\r\n        }\r\n      }\r\n    };\r\n    return function(currentDomain, configOptions) {\r\n      var swfDomain = _extractDomain(configOptions.swfPath);\r\n      if (swfDomain === null) {\r\n        swfDomain = currentDomain;\r\n      }\r\n      var trustedDomains = [];\r\n      _extractAllDomains(configOptions.trustedOrigins, trustedDomains);\r\n      _extractAllDomains(configOptions.trustedDomains, trustedDomains);\r\n      var len = trustedDomains.length;\r\n      if (len > 0) {\r\n        if (len === 1 && trustedDomains[0] === \"*\") {\r\n          return \"always\";\r\n        }\r\n        if (_inArray(currentDomain, trustedDomains) !== -1) {\r\n          if (len === 1 && currentDomain === swfDomain) {\r\n            return \"sameDomain\";\r\n          }\r\n          return \"always\";\r\n        }\r\n      }\r\n      return \"never\";\r\n    };\r\n  }();\r\n  var _objectKeys = function(obj) {\r\n    if (obj == null) {\r\n      return [];\r\n    }\r\n    if (Object.keys) {\r\n      return Object.keys(obj);\r\n    }\r\n    var keys = [];\r\n    for (var prop in obj) {\r\n      if (obj.hasOwnProperty(prop)) {\r\n        keys.push(prop);\r\n      }\r\n    }\r\n    return keys;\r\n  };\r\n  var _deleteOwnProperties = function(obj) {\r\n    if (obj) {\r\n      for (var prop in obj) {\r\n        if (obj.hasOwnProperty(prop)) {\r\n          delete obj[prop];\r\n        }\r\n      }\r\n    }\r\n    return obj;\r\n  };\r\n  var _safeActiveElement = function() {\r\n    try {\r\n      return document.activeElement;\r\n    } catch (err) {}\r\n    return null;\r\n  };\r\n  var _pick = function(obj, keys) {\r\n    var newObj = {};\r\n    for (var i = 0, len = keys.length; i < len; i++) {\r\n      if (keys[i] in obj) {\r\n        newObj[keys[i]] = obj[keys[i]];\r\n      }\r\n    }\r\n    return newObj;\r\n  };\r\n  var _omit = function(obj, keys) {\r\n    var newObj = {};\r\n    for (var prop in obj) {\r\n      if (_inArray(prop, keys) === -1) {\r\n        newObj[prop] = obj[prop];\r\n      }\r\n    }\r\n    return newObj;\r\n  };\r\n  var _mapClipDataToFlash = function(clipData) {\r\n    var newClipData = {}, formatMap = {};\r\n    if (!(typeof clipData === \"object\" && clipData)) {\r\n      return;\r\n    }\r\n    for (var dataFormat in clipData) {\r\n      if (dataFormat && clipData.hasOwnProperty(dataFormat) && typeof clipData[dataFormat] === \"string\" && clipData[dataFormat]) {\r\n        switch (dataFormat.toLowerCase()) {\r\n         case \"text/plain\":\r\n         case \"text\":\r\n         case \"air:text\":\r\n         case \"flash:text\":\r\n          newClipData.text = clipData[dataFormat];\r\n          formatMap.text = dataFormat;\r\n          break;\r\n\r\n         case \"text/html\":\r\n         case \"html\":\r\n         case \"air:html\":\r\n         case \"flash:html\":\r\n          newClipData.html = clipData[dataFormat];\r\n          formatMap.html = dataFormat;\r\n          break;\r\n\r\n         case \"application/rtf\":\r\n         case \"text/rtf\":\r\n         case \"rtf\":\r\n         case \"richtext\":\r\n         case \"air:rtf\":\r\n         case \"flash:rtf\":\r\n          newClipData.rtf = clipData[dataFormat];\r\n          formatMap.rtf = dataFormat;\r\n          break;\r\n\r\n         default:\r\n          break;\r\n        }\r\n      }\r\n    }\r\n    return {\r\n      data: newClipData,\r\n      formatMap: formatMap\r\n    };\r\n  };\r\n  var _mapClipResultsFromFlash = function(clipResults, formatMap) {\r\n    if (!(typeof clipResults === \"object\" && clipResults && typeof formatMap === \"object\" && formatMap)) {\r\n      return clipResults;\r\n    }\r\n    var newResults = {};\r\n    for (var prop in clipResults) {\r\n      if (clipResults.hasOwnProperty(prop)) {\r\n        if (prop !== \"success\" && prop !== \"data\") {\r\n          newResults[prop] = clipResults[prop];\r\n          continue;\r\n        }\r\n        newResults[prop] = {};\r\n        var tmpHash = clipResults[prop];\r\n        for (var dataFormat in tmpHash) {\r\n          if (dataFormat && tmpHash.hasOwnProperty(dataFormat) && formatMap.hasOwnProperty(dataFormat)) {\r\n            newResults[prop][formatMap[dataFormat]] = tmpHash[dataFormat];\r\n          }\r\n        }\r\n      }\r\n    }\r\n    return newResults;\r\n  };\r\n  var _args = function(arraySlice) {\r\n    return function(args) {\r\n      return arraySlice.call(args, 0);\r\n    };\r\n  }(window.Array.prototype.slice);\r\n  var _detectFlashSupport = function() {\r\n    var plugin, ax, mimeType, hasFlash = false, isActiveX = false, isPPAPI = false, flashVersion = \"\";\r\n    function parseFlashVersion(desc) {\r\n      var matches = desc.match(/[\\d]+/g);\r\n      matches.length = 3;\r\n      return matches.join(\".\");\r\n    }\r\n    function isPepperFlash(flashPlayerFileName) {\r\n      return !!flashPlayerFileName && (flashPlayerFileName = flashPlayerFileName.toLowerCase()) && (/^(pepflashplayer\\.dll|libpepflashplayer\\.so|pepperflashplayer\\.plugin)$/.test(flashPlayerFileName) || flashPlayerFileName.slice(-13) === \"chrome.plugin\");\r\n    }\r\n    function inspectPlugin(plugin) {\r\n      if (plugin) {\r\n        hasFlash = true;\r\n        if (plugin.version) {\r\n          flashVersion = parseFlashVersion(plugin.version);\r\n        }\r\n        if (!flashVersion && plugin.description) {\r\n          flashVersion = parseFlashVersion(plugin.description);\r\n        }\r\n        if (plugin.filename) {\r\n          isPPAPI = isPepperFlash(plugin.filename);\r\n        }\r\n      }\r\n    }\r\n    if (navigator.plugins && navigator.plugins.length) {\r\n      plugin = navigator.plugins[\"Shockwave Flash\"];\r\n      inspectPlugin(plugin);\r\n      if (navigator.plugins[\"Shockwave Flash 2.0\"]) {\r\n        hasFlash = true;\r\n        flashVersion = \"2.0.0.11\";\r\n      }\r\n    } else if (navigator.mimeTypes && navigator.mimeTypes.length) {\r\n      mimeType = navigator.mimeTypes[\"application/x-shockwave-flash\"];\r\n      plugin = mimeType && mimeType.enabledPlugin;\r\n      inspectPlugin(plugin);\r\n    } else if (typeof ActiveXObject !== \"undefined\") {\r\n      isActiveX = true;\r\n      try {\r\n        ax = new ActiveXObject(\"ShockwaveFlash.ShockwaveFlash.7\");\r\n        hasFlash = true;\r\n        flashVersion = parseFlashVersion(ax.GetVariable(\"$version\"));\r\n      } catch (e1) {\r\n        try {\r\n          ax = new ActiveXObject(\"ShockwaveFlash.ShockwaveFlash.6\");\r\n          hasFlash = true;\r\n          flashVersion = \"6.0.21\";\r\n        } catch (e2) {\r\n          try {\r\n            ax = new ActiveXObject(\"ShockwaveFlash.ShockwaveFlash\");\r\n            hasFlash = true;\r\n            flashVersion = parseFlashVersion(ax.GetVariable(\"$version\"));\r\n          } catch (e3) {\r\n            isActiveX = false;\r\n          }\r\n        }\r\n      }\r\n    }\r\n    _flashState.disabled = hasFlash !== true;\r\n    _flashState.outdated = flashVersion && parseFloat(flashVersion) < 11;\r\n    _flashState.version = flashVersion || \"0.0.0\";\r\n    _flashState.pluginType = isPPAPI ? \"pepper\" : isActiveX ? \"activex\" : hasFlash ? \"netscape\" : \"unknown\";\r\n  };\r\n  _detectFlashSupport();\r\n  var ZeroClipboard = function(elements) {\r\n    if (!(this instanceof ZeroClipboard)) {\r\n      return new ZeroClipboard(elements);\r\n    }\r\n    this.id = \"\" + _clientIdCounter++;\r\n    _clientMeta[this.id] = {\r\n      instance: this,\r\n      elements: [],\r\n      handlers: {}\r\n    };\r\n    if (elements) {\r\n      this.clip(elements);\r\n    }\r\n    if (typeof _flashState.ready !== \"boolean\") {\r\n      _flashState.ready = false;\r\n    }\r\n    if (!ZeroClipboard.isFlashUnusable() && _flashState.bridge === null) {\r\n      var _client = this;\r\n      var maxWait = _globalConfig.flashLoadTimeout;\r\n      if (typeof maxWait === \"number\" && maxWait >= 0) {\r\n        setTimeout(function() {\r\n          if (typeof _flashState.deactivated !== \"boolean\") {\r\n            _flashState.deactivated = true;\r\n          }\r\n          if (_flashState.deactivated === true) {\r\n            ZeroClipboard.emit({\r\n              type: \"error\",\r\n              name: \"flash-deactivated\",\r\n              client: _client\r\n            });\r\n          }\r\n        }, maxWait);\r\n      }\r\n      _flashState.overdue = false;\r\n      _bridge();\r\n    }\r\n  };\r\n  ZeroClipboard.prototype.setText = function(text) {\r\n    ZeroClipboard.setData(\"text/plain\", text);\r\n    return this;\r\n  };\r\n  ZeroClipboard.prototype.setHtml = function(html) {\r\n    ZeroClipboard.setData(\"text/html\", html);\r\n    return this;\r\n  };\r\n  ZeroClipboard.prototype.setRichText = function(richText) {\r\n    ZeroClipboard.setData(\"application/rtf\", richText);\r\n    return this;\r\n  };\r\n  ZeroClipboard.prototype.setData = function() {\r\n    ZeroClipboard.setData.apply(ZeroClipboard, _args(arguments));\r\n    return this;\r\n  };\r\n  ZeroClipboard.prototype.clearData = function() {\r\n    ZeroClipboard.clearData.apply(ZeroClipboard, _args(arguments));\r\n    return this;\r\n  };\r\n  ZeroClipboard.prototype.setSize = function(width, height) {\r\n    _setSize(width, height);\r\n    return this;\r\n  };\r\n  var _setHandCursor = function(enabled) {\r\n    if (_flashState.ready === true && _flashState.bridge && typeof _flashState.bridge.setHandCursor === \"function\") {\r\n      _flashState.bridge.setHandCursor(enabled);\r\n    } else {\r\n      _flashState.ready = false;\r\n    }\r\n  };\r\n  ZeroClipboard.prototype.destroy = function() {\r\n    this.unclip();\r\n    this.off();\r\n    delete _clientMeta[this.id];\r\n  };\r\n  var _getAllClients = function() {\r\n    var i, len, client, clients = [], clientIds = _objectKeys(_clientMeta);\r\n    for (i = 0, len = clientIds.length; i < len; i++) {\r\n      client = _clientMeta[clientIds[i]].instance;\r\n      if (client && client instanceof ZeroClipboard) {\r\n        clients.push(client);\r\n      }\r\n    }\r\n    return clients;\r\n  };\r\n  ZeroClipboard.version = \"2.0.0-beta.5\";\r\n  var _globalConfig = {\r\n    swfPath: _swfPath,\r\n    trustedDomains: window.location.host ? [ window.location.host ] : [],\r\n    cacheBust: true,\r\n    forceHandCursor: false,\r\n    forceEnhancedClipboard: false,\r\n    zIndex: 999999999,\r\n    debug: false,\r\n    title: null,\r\n    autoActivate: true,\r\n    flashLoadTimeout: 3e4\r\n  };\r\n  ZeroClipboard.isFlashUnusable = function() {\r\n    return !!(_flashState.disabled || _flashState.outdated || _flashState.unavailable || _flashState.deactivated);\r\n  };\r\n  ZeroClipboard.config = function(options) {\r\n    if (typeof options === \"object\" && options !== null) {\r\n      _extend(_globalConfig, options);\r\n    }\r\n    if (typeof options === \"string\" && options) {\r\n      if (_globalConfig.hasOwnProperty(options)) {\r\n        return _globalConfig[options];\r\n      }\r\n      return;\r\n    }\r\n    var copy = {};\r\n    for (var prop in _globalConfig) {\r\n      if (_globalConfig.hasOwnProperty(prop)) {\r\n        if (typeof _globalConfig[prop] === \"object\" && _globalConfig[prop] !== null) {\r\n          if (\"length\" in _globalConfig[prop]) {\r\n            copy[prop] = _globalConfig[prop].slice(0);\r\n          } else {\r\n            copy[prop] = _extend({}, _globalConfig[prop]);\r\n          }\r\n        } else {\r\n          copy[prop] = _globalConfig[prop];\r\n        }\r\n      }\r\n    }\r\n    return copy;\r\n  };\r\n  ZeroClipboard.destroy = function() {\r\n    ZeroClipboard.deactivate();\r\n    for (var clientId in _clientMeta) {\r\n      if (_clientMeta.hasOwnProperty(clientId) && _clientMeta[clientId]) {\r\n        var client = _clientMeta[clientId].instance;\r\n        if (client && typeof client.destroy === \"function\") {\r\n          client.destroy();\r\n        }\r\n      }\r\n    }\r\n    var flashBridge = _flashState.bridge;\r\n    if (flashBridge) {\r\n      var htmlBridge = _getHtmlBridge(flashBridge);\r\n      if (htmlBridge) {\r\n        if (_flashState.pluginType === \"activex\" && \"readyState\" in flashBridge) {\r\n          flashBridge.style.display = \"none\";\r\n          (function removeSwfFromIE() {\r\n            if (flashBridge.readyState === 4) {\r\n              for (var prop in flashBridge) {\r\n                if (typeof flashBridge[prop] === \"function\") {\r\n                  flashBridge[prop] = null;\r\n                }\r\n              }\r\n              flashBridge.parentNode.removeChild(flashBridge);\r\n              if (htmlBridge.parentNode) {\r\n                htmlBridge.parentNode.removeChild(htmlBridge);\r\n              }\r\n            } else {\r\n              setTimeout(removeSwfFromIE, 10);\r\n            }\r\n          })();\r\n        } else {\r\n          flashBridge.parentNode.removeChild(flashBridge);\r\n          if (htmlBridge.parentNode) {\r\n            htmlBridge.parentNode.removeChild(htmlBridge);\r\n          }\r\n        }\r\n      }\r\n      _flashState.ready = null;\r\n      _flashState.bridge = null;\r\n      _flashState.deactivated = null;\r\n    }\r\n    ZeroClipboard.clearData();\r\n  };\r\n  ZeroClipboard.activate = function(element) {\r\n    if (_currentElement) {\r\n      _removeClass(_currentElement, _globalConfig.hoverClass);\r\n      _removeClass(_currentElement, _globalConfig.activeClass);\r\n    }\r\n    _currentElement = element;\r\n    _addClass(element, _globalConfig.hoverClass);\r\n    _reposition();\r\n    var newTitle = _globalConfig.title || element.getAttribute(\"title\");\r\n    if (newTitle) {\r\n      var htmlBridge = _getHtmlBridge(_flashState.bridge);\r\n      if (htmlBridge) {\r\n        htmlBridge.setAttribute(\"title\", newTitle);\r\n      }\r\n    }\r\n    var useHandCursor = _globalConfig.forceHandCursor === true || _getStyle(element, \"cursor\") === \"pointer\";\r\n    _setHandCursor(useHandCursor);\r\n  };\r\n  ZeroClipboard.deactivate = function() {\r\n    var htmlBridge = _getHtmlBridge(_flashState.bridge);\r\n    if (htmlBridge) {\r\n      htmlBridge.removeAttribute(\"title\");\r\n      htmlBridge.style.left = \"0px\";\r\n      htmlBridge.style.top = \"-9999px\";\r\n      _setSize(1, 1);\r\n    }\r\n    if (_currentElement) {\r\n      _removeClass(_currentElement, _globalConfig.hoverClass);\r\n      _removeClass(_currentElement, _globalConfig.activeClass);\r\n      _currentElement = null;\r\n    }\r\n  };\r\n  ZeroClipboard.state = function() {\r\n    return {\r\n      browser: _pick(window.navigator, [ \"userAgent\", \"platform\", \"appName\" ]),\r\n      flash: _omit(_flashState, [ \"bridge\" ]),\r\n      zeroclipboard: {\r\n        version: ZeroClipboard.version,\r\n        config: ZeroClipboard.config()\r\n      }\r\n    };\r\n  };\r\n  ZeroClipboard.setData = function(format, data) {\r\n    var dataObj;\r\n    if (typeof format === \"object\" && format && typeof data === \"undefined\") {\r\n      dataObj = format;\r\n      ZeroClipboard.clearData();\r\n    } else if (typeof format === \"string\" && format) {\r\n      dataObj = {};\r\n      dataObj[format] = data;\r\n    } else {\r\n      return;\r\n    }\r\n    for (var dataFormat in dataObj) {\r\n      if (dataFormat && dataObj.hasOwnProperty(dataFormat) && typeof dataObj[dataFormat] === \"string\" && dataObj[dataFormat]) {\r\n        _clipData[dataFormat] = dataObj[dataFormat];\r\n      }\r\n    }\r\n  };\r\n  ZeroClipboard.clearData = function(format) {\r\n    if (typeof format === \"undefined\") {\r\n      _deleteOwnProperties(_clipData);\r\n      _clipDataFormatMap = null;\r\n    } else if (typeof format === \"string\" && _clipData.hasOwnProperty(format)) {\r\n      delete _clipData[format];\r\n    }\r\n  };\r\n  var _bridge = function() {\r\n    var flashBridge, len;\r\n    var container = document.getElementById(\"global-zeroclipboard-html-bridge\");\r\n    if (!container) {\r\n      var allowScriptAccess = _determineScriptAccess(window.location.host, _globalConfig);\r\n      var allowNetworking = allowScriptAccess === \"never\" ? \"none\" : \"all\";\r\n      var flashvars = _vars(_globalConfig);\r\n      var swfUrl = _globalConfig.swfPath + _cacheBust(_globalConfig.swfPath, _globalConfig);\r\n      container = _createHtmlBridge();\r\n      var divToBeReplaced = document.createElement(\"div\");\r\n      container.appendChild(divToBeReplaced);\r\n      document.body.appendChild(container);\r\n      var tmpDiv = document.createElement(\"div\");\r\n      var oldIE = _flashState.pluginType === \"activex\";\r\n      tmpDiv.innerHTML = '<object id=\"global-zeroclipboard-flash-bridge\" name=\"global-zeroclipboard-flash-bridge\" ' + 'width=\"100%\" height=\"100%\" ' + (oldIE ? 'classid=\"clsid:d27cdb6e-ae6d-11cf-96b8-444553540000\"' : 'type=\"application/x-shockwave-flash\" data=\"' + swfUrl + '\"') + \">\" + (oldIE ? '<param name=\"movie\" value=\"' + swfUrl + '\"/>' : \"\") + '<param name=\"allowScriptAccess\" value=\"' + allowScriptAccess + '\"/>' + '<param name=\"allowNetworking\" value=\"' + allowNetworking + '\"/>' + '<param name=\"menu\" value=\"false\"/>' + '<param name=\"wmode\" value=\"transparent\"/>' + '<param name=\"flashvars\" value=\"' + flashvars + '\"/>' + \"</object>\";\r\n      flashBridge = tmpDiv.firstChild;\r\n      tmpDiv = null;\r\n      flashBridge.ZeroClipboard = ZeroClipboard;\r\n      container.replaceChild(flashBridge, divToBeReplaced);\r\n    }\r\n    if (!flashBridge) {\r\n      flashBridge = document[\"global-zeroclipboard-flash-bridge\"];\r\n      if (flashBridge && (len = flashBridge.length)) {\r\n        flashBridge = flashBridge[len - 1];\r\n      }\r\n      if (!flashBridge) {\r\n        flashBridge = container.firstChild;\r\n      }\r\n    }\r\n    _flashState.bridge = flashBridge || null;\r\n  };\r\n  var _createHtmlBridge = function() {\r\n    var container = document.createElement(\"div\");\r\n    container.id = \"global-zeroclipboard-html-bridge\";\r\n    container.className = \"global-zeroclipboard-container\";\r\n    container.style.position = \"absolute\";\r\n    container.style.left = \"0px\";\r\n    container.style.top = \"-9999px\";\r\n    container.style.width = \"1px\";\r\n    container.style.height = \"1px\";\r\n    container.style.zIndex = \"\" + _getSafeZIndex(_globalConfig.zIndex);\r\n    return container;\r\n  };\r\n  var _getHtmlBridge = function(flashBridge) {\r\n    var htmlBridge = flashBridge && flashBridge.parentNode;\r\n    while (htmlBridge && htmlBridge.nodeName === \"OBJECT\" && htmlBridge.parentNode) {\r\n      htmlBridge = htmlBridge.parentNode;\r\n    }\r\n    return htmlBridge || null;\r\n  };\r\n  var _reposition = function() {\r\n    if (_currentElement) {\r\n      var pos = _getDOMObjectPosition(_currentElement, _globalConfig.zIndex);\r\n      var htmlBridge = _getHtmlBridge(_flashState.bridge);\r\n      if (htmlBridge) {\r\n        htmlBridge.style.top = pos.top + \"px\";\r\n        htmlBridge.style.left = pos.left + \"px\";\r\n        htmlBridge.style.width = pos.width + \"px\";\r\n        htmlBridge.style.height = pos.height + \"px\";\r\n        htmlBridge.style.zIndex = pos.zIndex + 1;\r\n      }\r\n      _setSize(pos.width, pos.height);\r\n    }\r\n  };\r\n  var _setSize = function(width, height) {\r\n    var htmlBridge = _getHtmlBridge(_flashState.bridge);\r\n    if (htmlBridge) {\r\n      htmlBridge.style.width = width + \"px\";\r\n      htmlBridge.style.height = height + \"px\";\r\n    }\r\n  };\r\n  ZeroClipboard.emit = function(event) {\r\n    var eventType, eventObj, performCallbackAsync, clients, i, len, eventCopy, returnVal, tmp;\r\n    if (typeof event === \"string\" && event) {\r\n      eventType = event;\r\n    }\r\n    if (typeof event === \"object\" && event && typeof event.type === \"string\" && event.type) {\r\n      eventType = event.type;\r\n      eventObj = event;\r\n    }\r\n    if (!eventType) {\r\n      return;\r\n    }\r\n    event = _createEvent(eventType, eventObj);\r\n    _preprocessEvent(event);\r\n    if (event.type === \"ready\" && _flashState.overdue === true) {\r\n      return ZeroClipboard.emit({\r\n        type: \"error\",\r\n        name: \"flash-overdue\"\r\n      });\r\n    }\r\n    performCallbackAsync = !/^(before)?copy$/.test(event.type);\r\n    if (event.client) {\r\n      _dispatchClientCallbacks.call(event.client, event, performCallbackAsync);\r\n    } else {\r\n      clients = event.target && event.target !== window && _globalConfig.autoActivate === true ? _getAllClientsClippedToElement(event.target) : _getAllClients();\r\n      for (i = 0, len = clients.length; i < len; i++) {\r\n        eventCopy = _extend({}, event, {\r\n          client: clients[i]\r\n        });\r\n        _dispatchClientCallbacks.call(clients[i], eventCopy, performCallbackAsync);\r\n      }\r\n    }\r\n    if (event.type === \"copy\") {\r\n      tmp = _mapClipDataToFlash(_clipData);\r\n      returnVal = tmp.data;\r\n      _clipDataFormatMap = tmp.formatMap;\r\n    }\r\n    return returnVal;\r\n  };\r\n  var _dispatchClientCallbacks = function(event, async) {\r\n    var handlers = _clientMeta[this.id] && _clientMeta[this.id].handlers[event.type];\r\n    if (handlers && handlers.length) {\r\n      var i, len, func, context, originalContext = this;\r\n      for (i = 0, len = handlers.length; i < len; i++) {\r\n        func = handlers[i];\r\n        context = originalContext;\r\n        if (typeof func === \"string\" && typeof window[func] === \"function\") {\r\n          func = window[func];\r\n        }\r\n        if (typeof func === \"object\" && func && typeof func.handleEvent === \"function\") {\r\n          context = func;\r\n          func = func.handleEvent;\r\n        }\r\n        if (typeof func === \"function\") {\r\n          _dispatchCallback(func, context, [ event ], async);\r\n        }\r\n      }\r\n    }\r\n    return this;\r\n  };\r\n  var _eventMessages = {\r\n    ready: \"Flash communication is established\",\r\n    error: {\r\n      \"flash-disabled\": \"Flash is disabled or not installed\",\r\n      \"flash-outdated\": \"Flash is too outdated to support ZeroClipboard\",\r\n      \"flash-unavailable\": \"Flash is unable to communicate bidirectionally with JavaScript\",\r\n      \"flash-deactivated\": \"Flash is too outdated for your browser and/or is configured as click-to-activate\",\r\n      \"flash-overdue\": \"Flash communication was established but NOT within the acceptable time limit\"\r\n    }\r\n  };\r\n  var _createEvent = function(eventType, event) {\r\n    if (!(eventType || event && event.type)) {\r\n      return;\r\n    }\r\n    event = event || {};\r\n    eventType = (eventType || event.type).toLowerCase();\r\n    _extend(event, {\r\n      type: eventType,\r\n      target: event.target || _currentElement || null,\r\n      relatedTarget: event.relatedTarget || null,\r\n      currentTarget: _flashState && _flashState.bridge || null\r\n    });\r\n    var msg = _eventMessages[event.type];\r\n    if (event.type === \"error\" && event.name && msg) {\r\n      msg = msg[event.name];\r\n    }\r\n    if (msg) {\r\n      event.message = msg;\r\n    }\r\n    if (event.type === \"ready\") {\r\n      _extend(event, {\r\n        target: null,\r\n        version: _flashState.version\r\n      });\r\n    }\r\n    if (event.type === \"error\") {\r\n      event.target = null;\r\n      if (/^flash-(outdated|unavailable|deactivated|overdue)$/.test(event.name)) {\r\n        _extend(event, {\r\n          version: _flashState.version,\r\n          minimumVersion: \"11.0.0\"\r\n        });\r\n      }\r\n    }\r\n    if (event.type === \"copy\") {\r\n      event.clipboardData = {\r\n        setData: ZeroClipboard.setData,\r\n        clearData: ZeroClipboard.clearData\r\n      };\r\n    }\r\n    if (event.type === \"aftercopy\") {\r\n      event = _mapClipResultsFromFlash(event, _clipDataFormatMap);\r\n    }\r\n    if (event.target && !event.relatedTarget) {\r\n      event.relatedTarget = _getRelatedTarget(event.target);\r\n    }\r\n    return event;\r\n  };\r\n  var _getRelatedTarget = function(targetEl) {\r\n    var relatedTargetId = targetEl && targetEl.getAttribute && targetEl.getAttribute(\"data-clipboard-target\");\r\n    return relatedTargetId ? document.getElementById(relatedTargetId) : null;\r\n  };\r\n  var _preprocessEvent = function(event) {\r\n    var element = event.target || _currentElement;\r\n    switch (event.type) {\r\n     case \"error\":\r\n      if (_inArray(event.name, [ \"flash-disabled\", \"flash-outdated\", \"flash-deactivated\", \"flash-overdue\" ])) {\r\n        _extend(_flashState, {\r\n          disabled: event.name === \"flash-disabled\",\r\n          outdated: event.name === \"flash-outdated\",\r\n          unavailable: event.name === \"flash-unavailable\",\r\n          deactivated: event.name === \"flash-deactivated\",\r\n          overdue: event.name === \"flash-overdue\",\r\n          ready: false\r\n        });\r\n      }\r\n      break;\r\n\r\n     case \"ready\":\r\n      var wasDeactivated = _flashState.deactivated === true;\r\n      _extend(_flashState, {\r\n        disabled: false,\r\n        outdated: false,\r\n        unavailable: false,\r\n        deactivated: false,\r\n        overdue: wasDeactivated,\r\n        ready: !wasDeactivated\r\n      });\r\n      break;\r\n\r\n     case \"copy\":\r\n      var textContent, htmlContent, targetEl = event.relatedTarget;\r\n      if (!(_clipData[\"text/html\"] || _clipData[\"text/plain\"]) && targetEl && (htmlContent = targetEl.value || targetEl.outerHTML || targetEl.innerHTML) && (textContent = targetEl.value || targetEl.textContent || targetEl.innerText)) {\r\n        event.clipboardData.clearData();\r\n        event.clipboardData.setData(\"text/plain\", textContent);\r\n        if (htmlContent !== textContent) {\r\n          event.clipboardData.setData(\"text/html\", htmlContent);\r\n        }\r\n      } else if (!_clipData[\"text/plain\"] && event.target && (textContent = event.target.getAttribute(\"data-clipboard-text\"))) {\r\n        event.clipboardData.clearData();\r\n        event.clipboardData.setData(\"text/plain\", textContent);\r\n      }\r\n      break;\r\n\r\n     case \"aftercopy\":\r\n      ZeroClipboard.clearData();\r\n      if (element && element !== _safeActiveElement() && element.focus) {\r\n        element.focus();\r\n      }\r\n      break;\r\n\r\n     case \"mouseover\":\r\n      _addClass(element, _globalConfig.hoverClass);\r\n      break;\r\n\r\n     case \"mouseout\":\r\n      if (_globalConfig.autoActivate === true) {\r\n        ZeroClipboard.deactivate();\r\n      }\r\n      break;\r\n\r\n     case \"mousedown\":\r\n      _addClass(element, _globalConfig.activeClass);\r\n      break;\r\n\r\n     case \"mouseup\":\r\n      _removeClass(element, _globalConfig.activeClass);\r\n      break;\r\n    }\r\n  };\r\n  ZeroClipboard.prototype.on = function(eventName, func) {\r\n    var i, len, events, added = {}, handlers = _clientMeta[this.id] && _clientMeta[this.id].handlers;\r\n    if (typeof eventName === \"string\" && eventName) {\r\n      events = eventName.toLowerCase().split(/\\s+/);\r\n    } else if (typeof eventName === \"object\" && eventName && typeof func === \"undefined\") {\r\n      for (i in eventName) {\r\n        if (eventName.hasOwnProperty(i) && typeof i === \"string\" && i && typeof eventName[i] === \"function\") {\r\n          this.on(i, eventName[i]);\r\n        }\r\n      }\r\n    }\r\n    if (events && events.length) {\r\n      for (i = 0, len = events.length; i < len; i++) {\r\n        eventName = events[i].replace(/^on/, \"\");\r\n        added[eventName] = true;\r\n        if (!handlers[eventName]) {\r\n          handlers[eventName] = [];\r\n        }\r\n        handlers[eventName].push(func);\r\n      }\r\n      if (added.ready && _flashState.ready) {\r\n        ZeroClipboard.emit({\r\n          type: \"ready\",\r\n          client: this\r\n        });\r\n      }\r\n      if (added.error) {\r\n        var errorTypes = [ \"disabled\", \"outdated\", \"unavailable\", \"deactivated\", \"overdue\" ];\r\n        for (i = 0, len = errorTypes.length; i < len; i++) {\r\n          if (_flashState[errorTypes[i]]) {\r\n            ZeroClipboard.emit({\r\n              type: \"error\",\r\n              name: \"flash-\" + errorTypes[i],\r\n              client: this\r\n            });\r\n            break;\r\n          }\r\n        }\r\n      }\r\n    }\r\n    return this;\r\n  };\r\n  ZeroClipboard.prototype.off = function(eventName, func) {\r\n    var i, len, foundIndex, events, perEventHandlers, handlers = _clientMeta[this.id] && _clientMeta[this.id].handlers;\r\n    if (arguments.length === 0) {\r\n      events = _objectKeys(handlers);\r\n    } else if (typeof eventName === \"string\" && eventName) {\r\n      events = eventName.split(/\\s+/);\r\n    } else if (typeof eventName === \"object\" && eventName && typeof func === \"undefined\") {\r\n      for (i in eventName) {\r\n        if (eventName.hasOwnProperty(i) && typeof i === \"string\" && i && typeof eventName[i] === \"function\") {\r\n          this.off(i, eventName[i]);\r\n        }\r\n      }\r\n    }\r\n    if (events && events.length) {\r\n      for (i = 0, len = events.length; i < len; i++) {\r\n        eventName = events[i].toLowerCase().replace(/^on/, \"\");\r\n        perEventHandlers = handlers[eventName];\r\n        if (perEventHandlers && perEventHandlers.length) {\r\n          if (func) {\r\n            foundIndex = _inArray(func, perEventHandlers);\r\n            while (foundIndex !== -1) {\r\n              perEventHandlers.splice(foundIndex, 1);\r\n              foundIndex = _inArray(func, perEventHandlers, foundIndex);\r\n            }\r\n          } else {\r\n            handlers[eventName].length = 0;\r\n          }\r\n        }\r\n      }\r\n    }\r\n    return this;\r\n  };\r\n  ZeroClipboard.prototype.handlers = function(eventName) {\r\n    var prop, copy = null, handlers = _clientMeta[this.id] && _clientMeta[this.id].handlers;\r\n    if (handlers) {\r\n      if (typeof eventName === \"string\" && eventName) {\r\n        return handlers[eventName] ? handlers[eventName].slice(0) : null;\r\n      }\r\n      copy = {};\r\n      for (prop in handlers) {\r\n        if (handlers.hasOwnProperty(prop) && handlers[prop]) {\r\n          copy[prop] = handlers[prop].slice(0);\r\n        }\r\n      }\r\n    }\r\n    return copy;\r\n  };\r\n  ZeroClipboard.prototype.clip = function(elements) {\r\n    elements = _prepClip(elements);\r\n    for (var i = 0; i < elements.length; i++) {\r\n      if (elements.hasOwnProperty(i) && elements[i] && elements[i].nodeType === 1) {\r\n        if (!elements[i].zcClippingId) {\r\n          elements[i].zcClippingId = \"zcClippingId_\" + _elementIdCounter++;\r\n          _elementMeta[elements[i].zcClippingId] = [ this.id ];\r\n          if (_globalConfig.autoActivate === true) {\r\n            _addEventHandler(elements[i], \"mouseover\", _elementMouseOver);\r\n          }\r\n        } else if (_inArray(this.id, _elementMeta[elements[i].zcClippingId]) === -1) {\r\n          _elementMeta[elements[i].zcClippingId].push(this.id);\r\n        }\r\n        var clippedElements = _clientMeta[this.id].elements;\r\n        if (_inArray(elements[i], clippedElements) === -1) {\r\n          clippedElements.push(elements[i]);\r\n        }\r\n      }\r\n    }\r\n    return this;\r\n  };\r\n  ZeroClipboard.prototype.unclip = function(elements) {\r\n    var meta = _clientMeta[this.id];\r\n    if (!meta) {\r\n      return this;\r\n    }\r\n    var clippedElements = meta.elements;\r\n    var arrayIndex;\r\n    if (typeof elements === \"undefined\") {\r\n      elements = clippedElements.slice(0);\r\n    } else {\r\n      elements = _prepClip(elements);\r\n    }\r\n    for (var i = elements.length; i--; ) {\r\n      if (elements.hasOwnProperty(i) && elements[i] && elements[i].nodeType === 1) {\r\n        arrayIndex = 0;\r\n        while ((arrayIndex = _inArray(elements[i], clippedElements, arrayIndex)) !== -1) {\r\n          clippedElements.splice(arrayIndex, 1);\r\n        }\r\n        var clientIds = _elementMeta[elements[i].zcClippingId];\r\n        if (clientIds) {\r\n          arrayIndex = 0;\r\n          while ((arrayIndex = _inArray(this.id, clientIds, arrayIndex)) !== -1) {\r\n            clientIds.splice(arrayIndex, 1);\r\n          }\r\n          if (clientIds.length === 0) {\r\n            if (_globalConfig.autoActivate === true) {\r\n              _removeEventHandler(elements[i], \"mouseover\", _elementMouseOver);\r\n            }\r\n            delete elements[i].zcClippingId;\r\n          }\r\n        }\r\n      }\r\n    }\r\n    return this;\r\n  };\r\n  ZeroClipboard.prototype.elements = function() {\r\n    var meta = _clientMeta[this.id];\r\n    return meta && meta.elements ? meta.elements.slice(0) : [];\r\n  };\r\n  var _getAllClientsClippedToElement = function(element) {\r\n    var elementMetaId, clientIds, i, len, client, clients = [];\r\n    if (element && element.nodeType === 1 && (elementMetaId = element.zcClippingId) && _elementMeta.hasOwnProperty(elementMetaId)) {\r\n      clientIds = _elementMeta[elementMetaId];\r\n      if (clientIds && clientIds.length) {\r\n        for (i = 0, len = clientIds.length; i < len; i++) {\r\n          client = _clientMeta[clientIds[i]].instance;\r\n          if (client && client instanceof ZeroClipboard) {\r\n            clients.push(client);\r\n          }\r\n        }\r\n      }\r\n    }\r\n    return clients;\r\n  };\r\n  _globalConfig.hoverClass = \"zeroclipboard-is-hover\";\r\n  _globalConfig.activeClass = \"zeroclipboard-is-active\";\r\n  if (typeof define === \"function\" && define.amd) {\r\n    define(function() {\r\n      return ZeroClipboard;\r\n    });\r\n  } else if (typeof module === \"object\" && module && typeof module.exports === \"object\" && module.exports) {\r\n    module.exports = ZeroClipboard;\r\n  } else {\r\n    window.ZeroClipboard = ZeroClipboard;\r\n  }\r\n})(function() {\r\n  return this;\r\n}());"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/ueditor.all.js",
    "content": "/*!\r\n * UEditor\r\n * version: ueditor\r\n * build: Wed Dec 26 2018 17:25:05 GMT+0800 (CST)\r\n */\r\n\r\n(function(){\r\n\r\n// editor.js\r\nUEDITOR_CONFIG = window.UEDITOR_CONFIG || {};\r\n\r\nvar baidu = window.baidu || {};\r\n\r\nwindow.baidu = baidu;\r\n\r\nwindow.UE = baidu.editor =  window.UE || {};\r\n\r\nUE.plugins = {};\r\n\r\nUE.commands = {};\r\n\r\nUE.instants = {};\r\n\r\nUE.I18N = {};\r\n\r\nUE._customizeUI = {};\r\n\r\nUE.version = \"1.4.3\";\r\n\r\nvar dom = UE.dom = {};\r\n\r\n// core/browser.js\r\n/**\r\n * 浏览器判断模块\r\n * @file\r\n * @module UE.browser\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 提供浏览器检测的模块\r\n * @unfile\r\n * @module UE.browser\r\n */\r\nvar browser = UE.browser = function(){\r\n    var agent = navigator.userAgent.toLowerCase(),\r\n        opera = window.opera,\r\n        browser = {\r\n        /**\r\n         * @property {boolean} ie 检测当前浏览器是否为IE\r\n         * @example\r\n         * ```javascript\r\n         * if ( UE.browser.ie ) {\r\n         *     console.log( '当前浏览器是IE' );\r\n         * }\r\n         * ```\r\n         */\r\n        ie\t\t:  /(msie\\s|trident.*rv:)([\\w.]+)/.test(agent),\r\n\r\n        /**\r\n         * @property {boolean} opera 检测当前浏览器是否为Opera\r\n         * @example\r\n         * ```javascript\r\n         * if ( UE.browser.opera ) {\r\n         *     console.log( '当前浏览器是Opera' );\r\n         * }\r\n         * ```\r\n         */\r\n        opera\t: ( !!opera && opera.version ),\r\n\r\n        /**\r\n         * @property {boolean} webkit 检测当前浏览器是否是webkit内核的浏览器\r\n         * @example\r\n         * ```javascript\r\n         * if ( UE.browser.webkit ) {\r\n         *     console.log( '当前浏览器是webkit内核浏览器' );\r\n         * }\r\n         * ```\r\n         */\r\n        webkit\t: ( agent.indexOf( ' applewebkit/' ) > -1 ),\r\n\r\n        /**\r\n         * @property {boolean} mac 检测当前浏览器是否是运行在mac平台下\r\n         * @example\r\n         * ```javascript\r\n         * if ( UE.browser.mac ) {\r\n         *     console.log( '当前浏览器运行在mac平台下' );\r\n         * }\r\n         * ```\r\n         */\r\n        mac\t: ( agent.indexOf( 'macintosh' ) > -1 ),\r\n\r\n        /**\r\n         * @property {boolean} quirks 检测当前浏览器是否处于“怪异模式”下\r\n         * @example\r\n         * ```javascript\r\n         * if ( UE.browser.quirks ) {\r\n         *     console.log( '当前浏览器运行处于“怪异模式”' );\r\n         * }\r\n         * ```\r\n         */\r\n        quirks : ( document.compatMode == 'BackCompat' )\r\n    };\r\n\r\n    /**\r\n    * @property {boolean} gecko 检测当前浏览器内核是否是gecko内核\r\n    * @example\r\n    * ```javascript\r\n    * if ( UE.browser.gecko ) {\r\n    *     console.log( '当前浏览器内核是gecko内核' );\r\n    * }\r\n    * ```\r\n    */\r\n    browser.gecko =( navigator.product == 'Gecko' && !browser.webkit && !browser.opera && !browser.ie);\r\n\r\n    var version = 0;\r\n\r\n    // Internet Explorer 6.0+\r\n    if ( browser.ie ){\r\n\r\n        var v1 =  agent.match(/(?:msie\\s([\\w.]+))/);\r\n        var v2 = agent.match(/(?:trident.*rv:([\\w.]+))/);\r\n        if(v1 && v2 && v1[1] && v2[1]){\r\n            version = Math.max(v1[1]*1,v2[1]*1);\r\n        }else if(v1 && v1[1]){\r\n            version = v1[1]*1;\r\n        }else if(v2 && v2[1]){\r\n            version = v2[1]*1;\r\n        }else{\r\n            version = 0;\r\n        }\r\n\r\n        browser.ie11Compat = document.documentMode == 11;\r\n        /**\r\n         * @property { boolean } ie9Compat 检测浏览器模式是否为 IE9 兼容模式\r\n         * @warning 如果浏览器不是IE， 则该值为undefined\r\n         * @example\r\n         * ```javascript\r\n         * if ( UE.browser.ie9Compat ) {\r\n         *     console.log( '当前浏览器运行在IE9兼容模式下' );\r\n         * }\r\n         * ```\r\n         */\r\n        browser.ie9Compat = document.documentMode == 9;\r\n\r\n        /**\r\n         * @property { boolean } ie8 检测浏览器是否是IE8浏览器\r\n         * @warning 如果浏览器不是IE， 则该值为undefined\r\n         * @example\r\n         * ```javascript\r\n         * if ( UE.browser.ie8 ) {\r\n         *     console.log( '当前浏览器是IE8浏览器' );\r\n         * }\r\n         * ```\r\n         */\r\n        browser.ie8 = !!document.documentMode;\r\n\r\n        /**\r\n         * @property { boolean } ie8Compat 检测浏览器模式是否为 IE8 兼容模式\r\n         * @warning 如果浏览器不是IE， 则该值为undefined\r\n         * @example\r\n         * ```javascript\r\n         * if ( UE.browser.ie8Compat ) {\r\n         *     console.log( '当前浏览器运行在IE8兼容模式下' );\r\n         * }\r\n         * ```\r\n         */\r\n        browser.ie8Compat = document.documentMode == 8;\r\n\r\n        /**\r\n         * @property { boolean } ie7Compat 检测浏览器模式是否为 IE7 兼容模式\r\n         * @warning 如果浏览器不是IE， 则该值为undefined\r\n         * @example\r\n         * ```javascript\r\n         * if ( UE.browser.ie7Compat ) {\r\n         *     console.log( '当前浏览器运行在IE7兼容模式下' );\r\n         * }\r\n         * ```\r\n         */\r\n        browser.ie7Compat = ( ( version == 7 && !document.documentMode )\r\n                || document.documentMode == 7 );\r\n\r\n        /**\r\n         * @property { boolean } ie6Compat 检测浏览器模式是否为 IE6 模式 或者怪异模式\r\n         * @warning 如果浏览器不是IE， 则该值为undefined\r\n         * @example\r\n         * ```javascript\r\n         * if ( UE.browser.ie6Compat ) {\r\n         *     console.log( '当前浏览器运行在IE6模式或者怪异模式下' );\r\n         * }\r\n         * ```\r\n         */\r\n        browser.ie6Compat = ( version < 7 || browser.quirks );\r\n\r\n        browser.ie9above = version > 8;\r\n\r\n        browser.ie9below = version < 9;\r\n\r\n        browser.ie11above = version > 10;\r\n\r\n        browser.ie11below = version < 11;\r\n\r\n    }\r\n\r\n    // Gecko.\r\n    if ( browser.gecko ){\r\n        var geckoRelease = agent.match( /rv:([\\d\\.]+)/ );\r\n        if ( geckoRelease )\r\n        {\r\n            geckoRelease = geckoRelease[1].split( '.' );\r\n            version = geckoRelease[0] * 10000 + ( geckoRelease[1] || 0 ) * 100 + ( geckoRelease[2] || 0 ) * 1;\r\n        }\r\n    }\r\n\r\n    /**\r\n     * @property { Number } chrome 检测当前浏览器是否为Chrome, 如果是，则返回Chrome的大版本号\r\n     * @warning 如果浏览器不是chrome， 则该值为undefined\r\n     * @example\r\n     * ```javascript\r\n     * if ( UE.browser.chrome ) {\r\n     *     console.log( '当前浏览器是Chrome' );\r\n     * }\r\n     * ```\r\n     */\r\n    if (/chrome\\/(\\d+\\.\\d)/i.test(agent)) {\r\n        browser.chrome = + RegExp['\\x241'];\r\n    }\r\n\r\n    /**\r\n     * @property { Number } safari 检测当前浏览器是否为Safari, 如果是，则返回Safari的大版本号\r\n     * @warning 如果浏览器不是safari， 则该值为undefined\r\n     * @example\r\n     * ```javascript\r\n     * if ( UE.browser.safari ) {\r\n     *     console.log( '当前浏览器是Safari' );\r\n     * }\r\n     * ```\r\n     */\r\n    if(/(\\d+\\.\\d)?(?:\\.\\d)?\\s+safari\\/?(\\d+\\.\\d+)?/i.test(agent) && !/chrome/i.test(agent)){\r\n    \tbrowser.safari = + (RegExp['\\x241'] || RegExp['\\x242']);\r\n    }\r\n\r\n\r\n    // Opera 9.50+\r\n    if ( browser.opera )\r\n        version = parseFloat( opera.version() );\r\n\r\n    // WebKit 522+ (Safari 3+)\r\n    if ( browser.webkit )\r\n        version = parseFloat( agent.match( / applewebkit\\/(\\d+)/ )[1] );\r\n\r\n    /**\r\n     * @property { Number } version 检测当前浏览器版本号\r\n     * @remind\r\n     * <ul>\r\n     *     <li>IE系列返回值为5,6,7,8,9,10等</li>\r\n     *     <li>gecko系列会返回10900，158900等</li>\r\n     *     <li>webkit系列会返回其build号 (如 522等)</li>\r\n     * </ul>\r\n     * @example\r\n     * ```javascript\r\n     * console.log( '当前浏览器版本号是： ' + UE.browser.version );\r\n     * ```\r\n     */\r\n    browser.version = version;\r\n\r\n    /**\r\n     * @property { boolean } isCompatible 检测当前浏览器是否能够与UEditor良好兼容\r\n     * @example\r\n     * ```javascript\r\n     * if ( UE.browser.isCompatible ) {\r\n     *     console.log( '浏览器与UEditor能够良好兼容' );\r\n     * }\r\n     * ```\r\n     */\r\n    browser.isCompatible =\r\n        !browser.mobile && (\r\n        ( browser.ie && version >= 6 ) ||\r\n        ( browser.gecko && version >= 10801 ) ||\r\n        ( browser.opera && version >= 9.5 ) ||\r\n        ( browser.air && version >= 1 ) ||\r\n        ( browser.webkit && version >= 522 ) ||\r\n        false );\r\n    return browser;\r\n}();\r\n//快捷方式\r\nvar ie = browser.ie,\r\n    webkit = browser.webkit,\r\n    gecko = browser.gecko,\r\n    opera = browser.opera;\r\n\r\n// core/utils.js\r\n/**\r\n * 工具函数包\r\n * @file\r\n * @module UE.utils\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * UEditor封装使用的静态工具函数\r\n * @module UE.utils\r\n * @unfile\r\n */\r\n\r\nvar utils = UE.utils = {\r\n\r\n    /**\r\n     * 用给定的迭代器遍历对象\r\n     * @method each\r\n     * @param { Object } obj 需要遍历的对象\r\n     * @param { Function } iterator 迭代器， 该方法接受两个参数， 第一个参数是当前所处理的value， 第二个参数是当前遍历对象的key\r\n     * @example\r\n     * ```javascript\r\n     * var demoObj = {\r\n     *     key1: 1,\r\n     *     key2: 2\r\n     * };\r\n     *\r\n     * //output: key1: 1, key2: 2\r\n     * UE.utils.each( demoObj, funciton ( value, key ) {\r\n     *\r\n     *     console.log( key + \":\" + value );\r\n     *\r\n     * } );\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 用给定的迭代器遍历数组或类数组对象\r\n     * @method each\r\n     * @param { Array } array 需要遍历的数组或者类数组\r\n     * @param { Function } iterator 迭代器， 该方法接受两个参数， 第一个参数是当前所处理的value， 第二个参数是当前遍历对象的key\r\n     * @example\r\n     * ```javascript\r\n     * var divs = document.getElmentByTagNames( \"div\" );\r\n     *\r\n     * //output: 0: DIV, 1: DIV ...\r\n     * UE.utils.each( divs, funciton ( value, key ) {\r\n     *\r\n     *     console.log( key + \":\" + value.tagName );\r\n     *\r\n     * } );\r\n     * ```\r\n     */\r\n    each : function(obj, iterator, context) {\r\n        if (obj == null) return;\r\n        if (obj.length === +obj.length) {\r\n            for (var i = 0, l = obj.length; i < l; i++) {\r\n                if(iterator.call(context, obj[i], i, obj) === false)\r\n                    return false;\r\n            }\r\n        } else {\r\n            for (var key in obj) {\r\n                if (obj.hasOwnProperty(key)) {\r\n                    if(iterator.call(context, obj[key], key, obj) === false)\r\n                        return false;\r\n                }\r\n            }\r\n        }\r\n    },\r\n\r\n    /**\r\n     * 以给定对象作为原型创建一个新对象\r\n     * @method makeInstance\r\n     * @param { Object } protoObject 该对象将作为新创建对象的原型\r\n     * @return { Object } 新的对象， 该对象的原型是给定的protoObject对象\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * var protoObject = { sayHello: function () { console.log('Hello UEditor!'); } };\r\n     *\r\n     * var newObject = UE.utils.makeInstance( protoObject );\r\n     * //output: Hello UEditor!\r\n     * newObject.sayHello();\r\n     * ```\r\n     */\r\n    makeInstance:function (obj) {\r\n        var noop = new Function();\r\n        noop.prototype = obj;\r\n        obj = new noop;\r\n        noop.prototype = null;\r\n        return obj;\r\n    },\r\n\r\n    /**\r\n     * 将source对象中的属性扩展到target对象上\r\n     * @method extend\r\n     * @remind 该方法将强制把source对象上的属性复制到target对象上\r\n     * @see UE.utils.extend(Object,Object,Boolean)\r\n     * @param { Object } target 目标对象， 新的属性将附加到该对象上\r\n     * @param { Object } source 源对象， 该对象的属性会被附加到target对象上\r\n     * @return { Object } 返回target对象\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * var target = { name: 'target', sex: 1 },\r\n     *      source = { name: 'source', age: 17 };\r\n     *\r\n     * UE.utils.extend( target, source );\r\n     *\r\n     * //output: { name: 'source', sex: 1, age: 17 }\r\n     * console.log( target );\r\n     *\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 将source对象中的属性扩展到target对象上， 根据指定的isKeepTarget值决定是否保留目标对象中与\r\n     * 源对象属性名相同的属性值。\r\n     * @method extend\r\n     * @param { Object } target 目标对象， 新的属性将附加到该对象上\r\n     * @param { Object } source 源对象， 该对象的属性会被附加到target对象上\r\n     * @param { Boolean } isKeepTarget 是否保留目标对象中与源对象中属性名相同的属性\r\n     * @return { Object } 返回target对象\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * var target = { name: 'target', sex: 1 },\r\n     *      source = { name: 'source', age: 17 };\r\n     *\r\n     * UE.utils.extend( target, source, true );\r\n     *\r\n     * //output: { name: 'target', sex: 1, age: 17 }\r\n     * console.log( target );\r\n     *\r\n     * ```\r\n     */\r\n    extend:function (t, s, b) {\r\n        if (s) {\r\n            for (var k in s) {\r\n                if (!b || !t.hasOwnProperty(k)) {\r\n                    t[k] = s[k];\r\n                }\r\n            }\r\n        }\r\n        return t;\r\n    },\r\n\r\n    /**\r\n     * 将给定的多个对象的属性复制到目标对象target上\r\n     * @method extend2\r\n     * @remind 该方法将强制把源对象上的属性复制到target对象上\r\n     * @remind 该方法支持两个及以上的参数， 从第二个参数开始， 其属性都会被复制到第一个参数上。 如果遇到同名的属性，\r\n     *          将会覆盖掉之前的值。\r\n     * @param { Object } target 目标对象， 新的属性将附加到该对象上\r\n     * @param { Object... } source 源对象， 支持多个对象， 该对象的属性会被附加到target对象上\r\n     * @return { Object } 返回target对象\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * var target = {},\r\n     *     source1 = { name: 'source', age: 17 },\r\n     *     source2 = { title: 'dev' };\r\n     *\r\n     * UE.utils.extend2( target, source1, source2 );\r\n     *\r\n     * //output: { name: 'source', age: 17, title: 'dev' }\r\n     * console.log( target );\r\n     *\r\n     * ```\r\n     */\r\n    extend2:function (t) {\r\n        var a = arguments;\r\n        for (var i = 1; i < a.length; i++) {\r\n            var x = a[i];\r\n            for (var k in x) {\r\n                if (!t.hasOwnProperty(k)) {\r\n                    t[k] = x[k];\r\n                }\r\n            }\r\n        }\r\n        return t;\r\n    },\r\n\r\n    /**\r\n     * 模拟继承机制， 使得subClass继承自superClass\r\n     * @method inherits\r\n     * @param { Object } subClass 子类对象\r\n     * @param { Object } superClass 超类对象\r\n     * @warning 该方法只能让subClass继承超类的原型， subClass对象自身的属性和方法不会被继承\r\n     * @return { Object } 继承superClass后的子类对象\r\n     * @example\r\n     * ```javascript\r\n     * function SuperClass(){\r\n     *     this.name = \"小李\";\r\n     * }\r\n     *\r\n     * SuperClass.prototype = {\r\n     *     hello:function(str){\r\n     *         console.log(this.name + str);\r\n     *     }\r\n     * }\r\n     *\r\n     * function SubClass(){\r\n     *     this.name = \"小张\";\r\n     * }\r\n     *\r\n     * UE.utils.inherits(SubClass,SuperClass);\r\n     *\r\n     * var sub = new SubClass();\r\n     * //output: '小张早上好!\r\n     * sub.hello(\"早上好!\");\r\n     * ```\r\n     */\r\n    inherits:function (subClass, superClass) {\r\n        var oldP = subClass.prototype,\r\n            newP = utils.makeInstance(superClass.prototype);\r\n        utils.extend(newP, oldP, true);\r\n        subClass.prototype = newP;\r\n        return (newP.constructor = subClass);\r\n    },\r\n\r\n    /**\r\n     * 用指定的context对象作为函数fn的上下文\r\n     * @method bind\r\n     * @param { Function } fn 需要绑定上下文的函数对象\r\n     * @param { Object } content 函数fn新的上下文对象\r\n     * @return { Function } 一个新的函数， 该函数作为原始函数fn的代理， 将完成fn的上下文调换工作。\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * var name = 'window',\r\n     *     newTest = null;\r\n     *\r\n     * function test () {\r\n     *     console.log( this.name );\r\n     * }\r\n     *\r\n     * newTest = UE.utils.bind( test, { name: 'object' } );\r\n     *\r\n     * //output: object\r\n     * newTest();\r\n     *\r\n     * //output: window\r\n     * test();\r\n     *\r\n     * ```\r\n     */\r\n    bind:function (fn, context) {\r\n        return function () {\r\n            return fn.apply(context, arguments);\r\n        };\r\n    },\r\n\r\n    /**\r\n     * 创建延迟指定时间后执行的函数fn\r\n     * @method defer\r\n     * @param { Function } fn 需要延迟执行的函数对象\r\n     * @param { int } delay 延迟的时间， 单位是毫秒\r\n     * @warning 该方法的时间控制是不精确的，仅仅只能保证函数的执行是在给定的时间之后，\r\n     *           而不能保证刚好到达延迟时间时执行。\r\n     * @return { Function } 目标函数fn的代理函数， 只有执行该函数才能起到延时效果\r\n     * @example\r\n     * ```javascript\r\n     * var start = 0;\r\n     *\r\n     * function test(){\r\n     *     console.log( new Date() - start );\r\n     * }\r\n     *\r\n     * var testDefer = UE.utils.defer( test, 1000 );\r\n     * //\r\n     * start = new Date();\r\n     * //output: (大约在1000毫秒之后输出) 1000\r\n     * testDefer();\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 创建延迟指定时间后执行的函数fn, 如果在延迟时间内再次执行该方法， 将会根据指定的exclusion的值，\r\n     * 决定是否取消前一次函数的执行， 如果exclusion的值为true， 则取消执行，反之，将继续执行前一个方法。\r\n     * @method defer\r\n     * @param { Function } fn 需要延迟执行的函数对象\r\n     * @param { int } delay 延迟的时间， 单位是毫秒\r\n     * @param { Boolean } exclusion 如果在延迟时间内再次执行该函数，该值将决定是否取消执行前一次函数的执行，\r\n     *                     值为true表示取消执行， 反之则将在执行前一次函数之后才执行本次函数调用。\r\n     * @warning 该方法的时间控制是不精确的，仅仅只能保证函数的执行是在给定的时间之后，\r\n     *           而不能保证刚好到达延迟时间时执行。\r\n     * @return { Function } 目标函数fn的代理函数， 只有执行该函数才能起到延时效果\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * function test(){\r\n     *     console.log(1);\r\n     * }\r\n     *\r\n     * var testDefer = UE.utils.defer( test, 1000, true );\r\n     *\r\n     * //output: (两次调用仅有一次输出) 1\r\n     * testDefer();\r\n     * testDefer();\r\n     * ```\r\n     */\r\n    defer:function (fn, delay, exclusion) {\r\n        var timerID;\r\n        return function () {\r\n            if (exclusion) {\r\n                clearTimeout(timerID);\r\n            }\r\n            timerID = setTimeout(fn, delay);\r\n        };\r\n    },\r\n\r\n    /**\r\n     * 获取元素item在数组array中首次出现的位置, 如果未找到item， 则返回-1\r\n     * @method indexOf\r\n     * @remind 该方法的匹配过程使用的是恒等“===”\r\n     * @param { Array } array 需要查找的数组对象\r\n     * @param { * } item 需要在目标数组中查找的值\r\n     * @return { int } 返回item在目标数组array中首次出现的位置， 如果在数组中未找到item， 则返回-1\r\n     * @example\r\n     * ```javascript\r\n     * var item = 1,\r\n     *     arr = [ 3, 4, 6, 8, 1, 1, 2 ];\r\n     *\r\n     * //output: 4\r\n     * console.log( UE.utils.indexOf( arr, item ) );\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 获取元素item数组array中首次出现的位置, 如果未找到item， 则返回-1。通过start的值可以指定搜索的起始位置。\r\n     * @method indexOf\r\n     * @remind 该方法的匹配过程使用的是恒等“===”\r\n     * @param { Array } array 需要查找的数组对象\r\n     * @param { * } item 需要在目标数组中查找的值\r\n     * @param { int } start 搜索的起始位置\r\n     * @return { int } 返回item在目标数组array中的start位置之后首次出现的位置， 如果在数组中未找到item， 则返回-1\r\n     * @example\r\n     * ```javascript\r\n     * var item = 1,\r\n     *     arr = [ 3, 4, 6, 8, 1, 2, 8, 3, 2, 1, 1, 4 ];\r\n     *\r\n     * //output: 9\r\n     * console.log( UE.utils.indexOf( arr, item, 5 ) );\r\n     * ```\r\n     */\r\n    indexOf:function (array, item, start) {\r\n        var index = -1;\r\n        start = this.isNumber(start) ? start : 0;\r\n        this.each(array, function (v, i) {\r\n            if (i >= start && v === item) {\r\n                index = i;\r\n                return false;\r\n            }\r\n        });\r\n        return index;\r\n    },\r\n\r\n    /**\r\n     * 移除数组array中所有的元素item\r\n     * @method removeItem\r\n     * @param { Array } array 要移除元素的目标数组\r\n     * @param { * } item 将要被移除的元素\r\n     * @remind 该方法的匹配过程使用的是恒等“===”\r\n     * @example\r\n     * ```javascript\r\n     * var arr = [ 4, 5, 7, 1, 3, 4, 6 ];\r\n     *\r\n     * UE.utils.removeItem( arr, 4 );\r\n     * //output: [ 5, 7, 1, 3, 6 ]\r\n     * console.log( arr );\r\n     *\r\n     * ```\r\n     */\r\n    removeItem:function (array, item) {\r\n        for (var i = 0, l = array.length; i < l; i++) {\r\n            if (array[i] === item) {\r\n                array.splice(i, 1);\r\n                i--;\r\n            }\r\n        }\r\n    },\r\n\r\n    /**\r\n     * 删除字符串str的首尾空格\r\n     * @method trim\r\n     * @param { String } str 需要删除首尾空格的字符串\r\n     * @return { String } 删除了首尾的空格后的字符串\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * var str = \" UEdtior \";\r\n     *\r\n     * //output: 9\r\n     * console.log( str.length );\r\n     *\r\n     * //output: 7\r\n     * console.log( UE.utils.trim( \" UEdtior \" ).length );\r\n     *\r\n     * //output: 9\r\n     * console.log( str.length );\r\n     *\r\n     *  ```\r\n     */\r\n    trim:function (str) {\r\n        return str.replace(/(^[ \\t\\n\\r]+)|([ \\t\\n\\r]+$)/g, '');\r\n    },\r\n\r\n    /**\r\n     * 将字符串str以','分隔成数组后，将该数组转换成哈希对象， 其生成的hash对象的key为数组中的元素， value为1\r\n     * @method listToMap\r\n     * @warning 该方法在生成的hash对象中，会为每一个key同时生成一个另一个全大写的key。\r\n     * @param { String } str 该字符串将被以','分割为数组， 然后进行转化\r\n     * @return { Object } 转化之后的hash对象\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * //output: Object {UEdtior: 1, UEDTIOR: 1, Hello: 1, HELLO: 1}\r\n     * console.log( UE.utils.listToMap( 'UEdtior,Hello' ) );\r\n     *\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 将字符串数组转换成哈希对象， 其生成的hash对象的key为数组中的元素， value为1\r\n     * @method listToMap\r\n     * @warning 该方法在生成的hash对象中，会为每一个key同时生成一个另一个全大写的key。\r\n     * @param { Array } arr 字符串数组\r\n     * @return { Object } 转化之后的hash对象\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * //output: Object {UEdtior: 1, UEDTIOR: 1, Hello: 1, HELLO: 1}\r\n     * console.log( UE.utils.listToMap( [ 'UEdtior', 'Hello' ] ) );\r\n     *\r\n     * ```\r\n     */\r\n    listToMap:function (list) {\r\n        if (!list)return {};\r\n        list = utils.isArray(list) ? list : list.split(',');\r\n        for (var i = 0, ci, obj = {}; ci = list[i++];) {\r\n            obj[ci.toUpperCase()] = obj[ci] = 1;\r\n        }\r\n        return obj;\r\n    },\r\n\r\n    /**\r\n     * 将str中的html符号转义,将转义“'，&，<，\"，>”五个字符\r\n     * @method unhtml\r\n     * @param { String } str 需要转义的字符串\r\n     * @return { String } 转义后的字符串\r\n     * @example\r\n     * ```javascript\r\n     * var html = '<body>&</body>';\r\n     *\r\n     * //output: &lt;body&gt;&amp;&lt;/body&gt;\r\n     * console.log( UE.utils.unhtml( html ) );\r\n     *\r\n     * ```\r\n     */\r\n    unhtml:function (str, reg) {\r\n        return str ? str.replace(reg || /[&<\">'](?:(amp|lt|quot|gt|#39|nbsp|#\\d+);)?/g, function (a, b) {\r\n            if (b) {\r\n                return a;\r\n            } else {\r\n                return {\r\n                    '<':'&lt;',\r\n                    '&':'&amp;',\r\n                    '\"':'&quot;',\r\n                    '>':'&gt;',\r\n                    \"'\":'&#39;'\r\n                }[a]\r\n            }\r\n\r\n        }) : '';\r\n    },\r\n    /**\r\n     * 将url中的html字符转义， 仅转义  ', \", <, > 四个字符\r\n     * @param  { String } str 需要转义的字符串\r\n     * @param  { RegExp } reg 自定义的正则\r\n     * @return { String }     转义后的字符串\r\n     */\r\n    unhtmlForUrl:function (str, reg) {\r\n        return str ? str.replace(reg || /[<\">']/g, function (a) {\r\n            return {\r\n                '<':'&lt;',\r\n                '&':'&amp;',\r\n                '\"':'&quot;',\r\n                '>':'&gt;',\r\n                \"'\":'&#39;'\r\n            }[a]\r\n\r\n        }) : '';\r\n    },\r\n\r\n    /**\r\n     * 将str中的转义字符还原成html字符\r\n     * @see UE.utils.unhtml(String);\r\n     * @method html\r\n     * @param { String } str 需要逆转义的字符串\r\n     * @return { String } 逆转义后的字符串\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * var str = '&lt;body&gt;&amp;&lt;/body&gt;';\r\n     *\r\n     * //output: <body>&</body>\r\n     * console.log( UE.utils.html( str ) );\r\n     *\r\n     * ```\r\n     */\r\n    html:function (str) {\r\n        return str ? str.replace(/&((g|l|quo)t|amp|#39|nbsp);/g, function (m) {\r\n            return {\r\n                '&lt;':'<',\r\n                '&amp;':'&',\r\n                '&quot;':'\"',\r\n                '&gt;':'>',\r\n                '&#39;':\"'\",\r\n                '&nbsp;':' '\r\n            }[m]\r\n        }) : '';\r\n    },\r\n\r\n    /**\r\n     * 将css样式转换为驼峰的形式\r\n     * @method cssStyleToDomStyle\r\n     * @param { String } cssName 需要转换的css样式名\r\n     * @return { String } 转换成驼峰形式后的css样式名\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * var str = 'border-top';\r\n     *\r\n     * //output: borderTop\r\n     * console.log( UE.utils.cssStyleToDomStyle( str ) );\r\n     *\r\n     * ```\r\n     */\r\n    cssStyleToDomStyle:function () {\r\n        var test = document.createElement('div').style,\r\n            cache = {\r\n                'float':test.cssFloat != undefined ? 'cssFloat' : test.styleFloat != undefined ? 'styleFloat' : 'float'\r\n            };\r\n\r\n        return function (cssName) {\r\n            return cache[cssName] || (cache[cssName] = cssName.toLowerCase().replace(/-./g, function (match) {\r\n                return match.charAt(1).toUpperCase();\r\n            }));\r\n        };\r\n    }(),\r\n\r\n    /**\r\n     * 动态加载文件到doc中\r\n     * @method loadFile\r\n     * @param { DomDocument } document 需要加载资源文件的文档对象\r\n     * @param { Object } options 加载资源文件的属性集合， 取值请参考代码示例\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * UE.utils.loadFile( document, {\r\n     *     src:\"test.js\",\r\n     *     tag:\"script\",\r\n     *     type:\"text/javascript\",\r\n     *     defer:\"defer\"\r\n     * } );\r\n     *\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 动态加载文件到doc中，加载成功后执行的回调函数fn\r\n     * @method loadFile\r\n     * @param { DomDocument } document 需要加载资源文件的文档对象\r\n     * @param { Object } options 加载资源文件的属性集合， 该集合支持的值是script标签和style标签支持的所有属性。\r\n     * @param { Function } fn 资源文件加载成功之后执行的回调\r\n     * @warning 对于在同一个文档中多次加载同一URL的文件， 该方法会在第一次加载之后缓存该请求，\r\n     *           在此之后的所有同一URL的请求， 将会直接触发回调。\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * UE.utils.loadFile( document, {\r\n     *     src:\"test.js\",\r\n     *     tag:\"script\",\r\n     *     type:\"text/javascript\",\r\n     *     defer:\"defer\"\r\n     * }, function () {\r\n     *     console.log('加载成功');\r\n     * } );\r\n     *\r\n     * ```\r\n     */\r\n    loadFile:function () {\r\n        var tmpList = [];\r\n\r\n        function getItem(doc, obj) {\r\n            try {\r\n                for (var i = 0, ci; ci = tmpList[i++];) {\r\n                    if (ci.doc === doc && ci.url == (obj.src || obj.href)) {\r\n                        return ci;\r\n                    }\r\n                }\r\n            } catch (e) {\r\n                return null;\r\n            }\r\n\r\n        }\r\n\r\n        return function (doc, obj, fn) {\r\n            var item = getItem(doc, obj);\r\n            if (item) {\r\n                if (item.ready) {\r\n                    fn && fn();\r\n                } else {\r\n                    item.funs.push(fn)\r\n                }\r\n                return;\r\n            }\r\n            tmpList.push({\r\n                doc:doc,\r\n                url:obj.src || obj.href,\r\n                funs:[fn]\r\n            });\r\n            if (!doc.body) {\r\n                var html = [];\r\n                for (var p in obj) {\r\n                    if (p == 'tag')continue;\r\n                    html.push(p + '=\"' + obj[p] + '\"')\r\n                }\r\n                doc.write('<' + obj.tag + ' ' + html.join(' ') + ' ></' + obj.tag + '>');\r\n                return;\r\n            }\r\n            if (obj.id && doc.getElementById(obj.id)) {\r\n                return;\r\n            }\r\n            var element = doc.createElement(obj.tag);\r\n            delete obj.tag;\r\n            for (var p in obj) {\r\n                element.setAttribute(p, obj[p]);\r\n            }\r\n            element.onload = element.onreadystatechange = function () {\r\n                if (!this.readyState || /loaded|complete/.test(this.readyState)) {\r\n                    item = getItem(doc, obj);\r\n                    if (item.funs.length > 0) {\r\n                        item.ready = 1;\r\n                        for (var fi; fi = item.funs.pop();) {\r\n                            fi();\r\n                        }\r\n                    }\r\n                    element.onload = element.onreadystatechange = null;\r\n                }\r\n            };\r\n            element.onerror = function () {\r\n                throw Error('The load ' + (obj.href || obj.src) + ' fails,check the url settings of file ueditor.config.js ')\r\n            };\r\n            doc.getElementsByTagName(\"head\")[0].appendChild(element);\r\n        }\r\n    }(),\r\n\r\n    /**\r\n     * 判断obj对象是否为空\r\n     * @method isEmptyObject\r\n     * @param { * } obj 需要判断的对象\r\n     * @remind 如果判断的对象是NULL， 将直接返回true， 如果是数组且为空， 返回true， 如果是字符串， 且字符串为空，\r\n     *          返回true， 如果是普通对象， 且该对象没有任何实例属性， 返回true\r\n     * @return { Boolean } 对象是否为空\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * //output: true\r\n     * console.log( UE.utils.isEmptyObject( {} ) );\r\n     *\r\n     * //output: true\r\n     * console.log( UE.utils.isEmptyObject( [] ) );\r\n     *\r\n     * //output: true\r\n     * console.log( UE.utils.isEmptyObject( \"\" ) );\r\n     *\r\n     * //output: false\r\n     * console.log( UE.utils.isEmptyObject( { key: 1 } ) );\r\n     *\r\n     * //output: false\r\n     * console.log( UE.utils.isEmptyObject( [1] ) );\r\n     *\r\n     * //output: false\r\n     * console.log( UE.utils.isEmptyObject( \"1\" ) );\r\n     *\r\n     * ```\r\n     */\r\n    isEmptyObject:function (obj) {\r\n        if (obj == null) return true;\r\n        if (this.isArray(obj) || this.isString(obj)) return obj.length === 0;\r\n        for (var key in obj) if (obj.hasOwnProperty(key)) return false;\r\n        return true;\r\n    },\r\n\r\n    /**\r\n     * 把rgb格式的颜色值转换成16进制格式\r\n     * @method fixColor\r\n     * @param { String } rgb格式的颜色值\r\n     * @param { String }\r\n     * @example\r\n     * rgb(255,255,255)  => \"#ffffff\"\r\n     */\r\n    fixColor:function (name, value) {\r\n        if (/color/i.test(name) && /rgba?/.test(value)) {\r\n            var array = value.split(\",\");\r\n            if (array.length > 3)\r\n                return \"\";\r\n            value = \"#\";\r\n            for (var i = 0, color; color = array[i++];) {\r\n                color = parseInt(color.replace(/[^\\d]/gi, ''), 10).toString(16);\r\n                value += color.length == 1 ? \"0\" + color : color;\r\n            }\r\n            value = value.toUpperCase();\r\n        }\r\n        return  value;\r\n    },\r\n    /**\r\n     * 只针对border,padding,margin做了处理，因为性能问题\r\n     * @public\r\n     * @function\r\n     * @param {String}    val style字符串\r\n     */\r\n    optCss:function (val) {\r\n        var padding, margin, border;\r\n        val = val.replace(/(padding|margin|border)\\-([^:]+):([^;]+);?/gi, function (str, key, name, val) {\r\n            if (val.split(' ').length == 1) {\r\n                switch (key) {\r\n                    case 'padding':\r\n                        !padding && (padding = {});\r\n                        padding[name] = val;\r\n                        return '';\r\n                    case 'margin':\r\n                        !margin && (margin = {});\r\n                        margin[name] = val;\r\n                        return '';\r\n                    case 'border':\r\n                        return val == 'initial' ? '' : str;\r\n                }\r\n            }\r\n            return str;\r\n        });\r\n\r\n        function opt(obj, name) {\r\n            if (!obj) {\r\n                return '';\r\n            }\r\n            var t = obj.top , b = obj.bottom, l = obj.left, r = obj.right, val = '';\r\n            if (!t || !l || !b || !r) {\r\n                for (var p in obj) {\r\n                    val += ';' + name + '-' + p + ':' + obj[p] + ';';\r\n                }\r\n            } else {\r\n                val += ';' + name + ':' +\r\n                    (t == b && b == l && l == r ? t :\r\n                        t == b && l == r ? (t + ' ' + l) :\r\n                            l == r ? (t + ' ' + l + ' ' + b) : (t + ' ' + r + ' ' + b + ' ' + l)) + ';'\r\n            }\r\n            return val;\r\n        }\r\n\r\n        val += opt(padding, 'padding') + opt(margin, 'margin');\r\n        return val.replace(/^[ \\n\\r\\t;]*|[ \\n\\r\\t]*$/, '').replace(/;([ \\n\\r\\t]+)|\\1;/g, ';')\r\n            .replace(/(&((l|g)t|quot|#39))?;{2,}/g, function (a, b) {\r\n                return b ? b + \";;\" : ';'\r\n            });\r\n    },\r\n\r\n    /**\r\n     * 克隆对象\r\n     * @method clone\r\n     * @param { Object } source 源对象\r\n     * @return { Object } source的一个副本\r\n     */\r\n\r\n    /**\r\n     * 深度克隆对象，将source的属性克隆到target对象， 会覆盖target重名的属性。\r\n     * @method clone\r\n     * @param { Object } source 源对象\r\n     * @param { Object } target 目标对象\r\n     * @return { Object } 附加了source对象所有属性的target对象\r\n     */\r\n    clone:function (source, target) {\r\n        var tmp;\r\n        target = target || {};\r\n        for (var i in source) {\r\n            if (source.hasOwnProperty(i)) {\r\n                tmp = source[i];\r\n                if (typeof tmp == 'object') {\r\n                    target[i] = utils.isArray(tmp) ? [] : {};\r\n                    utils.clone(source[i], target[i])\r\n                } else {\r\n                    target[i] = tmp;\r\n                }\r\n            }\r\n        }\r\n        return target;\r\n    },\r\n\r\n    /**\r\n     * 把cm／pt为单位的值转换为px为单位的值\r\n     * @method transUnitToPx\r\n     * @param { String } 待转换的带单位的字符串\r\n     * @return { String } 转换为px为计量单位的值的字符串\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * //output: 500px\r\n     * console.log( UE.utils.transUnitToPx( '20cm' ) );\r\n     *\r\n     * //output: 27px\r\n     * console.log( UE.utils.transUnitToPx( '20pt' ) );\r\n     *\r\n     * ```\r\n     */\r\n    transUnitToPx:function (val) {\r\n        if (!/(pt|cm)/.test(val)) {\r\n            return val\r\n        }\r\n        var unit;\r\n        val.replace(/([\\d.]+)(\\w+)/, function (str, v, u) {\r\n            val = v;\r\n            unit = u;\r\n        });\r\n        switch (unit) {\r\n            case 'cm':\r\n                val = parseFloat(val) * 25;\r\n                break;\r\n            case 'pt':\r\n                val = Math.round(parseFloat(val) * 96 / 72);\r\n        }\r\n        return val + (val ? 'px' : '');\r\n    },\r\n\r\n    /**\r\n     * 在dom树ready之后执行给定的回调函数\r\n     * @method domReady\r\n     * @remind 如果在执行该方法的时候， dom树已经ready， 那么回调函数将立刻执行\r\n     * @param { Function } fn dom树ready之后的回调函数\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * UE.utils.domReady( function () {\r\n     *\r\n     *     console.log('123');\r\n     *\r\n     * } );\r\n     *\r\n     * ```\r\n     */\r\n    domReady:function () {\r\n\r\n        var fnArr = [];\r\n\r\n        function doReady(doc) {\r\n            //确保onready只执行一次\r\n            doc.isReady = true;\r\n            for (var ci; ci = fnArr.pop(); ci()) {\r\n            }\r\n        }\r\n\r\n        return function (onready, win) {\r\n            win = win || window;\r\n            var doc = win.document;\r\n            onready && fnArr.push(onready);\r\n            if (doc.readyState === \"complete\") {\r\n                doReady(doc);\r\n            } else {\r\n                doc.isReady && doReady(doc);\r\n                if (browser.ie && browser.version != 11) {\r\n                    (function () {\r\n                        if (doc.isReady) return;\r\n                        try {\r\n                            doc.documentElement.doScroll(\"left\");\r\n                        } catch (error) {\r\n                            setTimeout(arguments.callee, 0);\r\n                            return;\r\n                        }\r\n                        doReady(doc);\r\n                    })();\r\n                    win.attachEvent('onload', function () {\r\n                        doReady(doc)\r\n                    });\r\n                } else {\r\n                    doc.addEventListener(\"DOMContentLoaded\", function () {\r\n                        doc.removeEventListener(\"DOMContentLoaded\", arguments.callee, false);\r\n                        doReady(doc);\r\n                    }, false);\r\n                    win.addEventListener('load', function () {\r\n                        doReady(doc)\r\n                    }, false);\r\n                }\r\n            }\r\n\r\n        }\r\n    }(),\r\n\r\n    /**\r\n     * 动态添加css样式\r\n     * @method cssRule\r\n     * @param { String } 节点名称\r\n     * @grammar UE.utils.cssRule('添加的样式的节点名称',['样式'，'放到哪个document上'])\r\n     * @grammar UE.utils.cssRule('body','body{background:#ccc}') => null  //给body添加背景颜色\r\n     * @grammar UE.utils.cssRule('body') =>样式的字符串  //取得key值为body的样式的内容,如果没有找到key值先关的样式将返回空，例如刚才那个背景颜色，将返回 body{background:#ccc}\r\n     * @grammar UE.utils.cssRule('body',document) => 返回指定key的样式，并且指定是哪个document\r\n     * @grammar UE.utils.cssRule('body','') =>null //清空给定的key值的背景颜色\r\n     */\r\n    cssRule:browser.ie && browser.version != 11 ? function (key, style, doc) {\r\n        var indexList, index;\r\n        if(style === undefined || style && style.nodeType && style.nodeType == 9){\r\n            //获取样式\r\n            doc = style && style.nodeType && style.nodeType == 9 ? style : (doc || document);\r\n            indexList = doc.indexList || (doc.indexList = {});\r\n            index = indexList[key];\r\n            if(index !==  undefined){\r\n                return doc.styleSheets[index].cssText\r\n            }\r\n            return undefined;\r\n        }\r\n        doc = doc || document;\r\n        indexList = doc.indexList || (doc.indexList = {});\r\n        index = indexList[key];\r\n        //清除样式\r\n        if(style === ''){\r\n            if(index!== undefined){\r\n                doc.styleSheets[index].cssText = '';\r\n                delete indexList[key];\r\n                return true\r\n            }\r\n            return false;\r\n        }\r\n\r\n        //添加样式\r\n        if(index!== undefined){\r\n            sheetStyle =  doc.styleSheets[index];\r\n        }else{\r\n            sheetStyle = doc.createStyleSheet('', index = doc.styleSheets.length);\r\n            indexList[key] = index;\r\n        }\r\n        sheetStyle.cssText = style;\r\n    }: function (key, style, doc) {\r\n        var head, node;\r\n        if(style === undefined || style && style.nodeType && style.nodeType == 9){\r\n            //获取样式\r\n            doc = style && style.nodeType && style.nodeType == 9 ? style : (doc || document);\r\n            node = doc.getElementById(key);\r\n            return node ? node.innerHTML : undefined;\r\n        }\r\n        doc = doc || document;\r\n        node = doc.getElementById(key);\r\n\r\n        //清除样式\r\n        if(style === ''){\r\n            if(node){\r\n                node.parentNode.removeChild(node);\r\n                return true\r\n            }\r\n            return false;\r\n        }\r\n\r\n        //添加样式\r\n        if(node){\r\n            node.innerHTML = style;\r\n        }else{\r\n            node = doc.createElement('style');\r\n            node.id = key;\r\n            node.innerHTML = style;\r\n            doc.getElementsByTagName('head')[0].appendChild(node);\r\n        }\r\n    },\r\n    sort:function(array,compareFn){\r\n        compareFn = compareFn || function(item1, item2){ return item1.localeCompare(item2);};\r\n        for(var i= 0,len = array.length; i<len; i++){\r\n            for(var j = i,length = array.length; j<length; j++){\r\n                if(compareFn(array[i], array[j]) > 0){\r\n                    var t = array[i];\r\n                    array[i] = array[j];\r\n                    array[j] = t;\r\n                }\r\n            }\r\n        }\r\n        return array;\r\n    },\r\n    serializeParam:function (json) {\r\n        var strArr = [];\r\n        for (var i in json) {\r\n            //忽略默认的几个参数\r\n            if(i==\"method\" || i==\"timeout\" || i==\"async\") continue;\r\n            //传递过来的对象和函数不在提交之列\r\n            if (!((typeof json[i]).toLowerCase() == \"function\" || (typeof json[i]).toLowerCase() == \"object\")) {\r\n                strArr.push( encodeURIComponent(i) + \"=\"+encodeURIComponent(json[i]) );\r\n            } else if (utils.isArray(json[i])) {\r\n                //支持传数组内容\r\n                for(var j = 0; j < json[i].length; j++) {\r\n                    strArr.push( encodeURIComponent(i) + \"[]=\"+encodeURIComponent(json[i][j]) );\r\n                }\r\n            }\r\n        }\r\n        return strArr.join(\"&\");\r\n    },\r\n    formatUrl:function (url) {\r\n        var u = url.replace(/&&/g, '&');\r\n        u = u.replace(/\\?&/g, '?');\r\n        u = u.replace(/&$/g, '');\r\n        u = u.replace(/&#/g, '#');\r\n        u = u.replace(/&+/g, '&');\r\n        return u;\r\n    },\r\n    isCrossDomainUrl:function (url) {\r\n        var a = document.createElement('a');\r\n        a.href = url;\r\n        if (browser.ie) {\r\n            a.href = a.href;\r\n        }\r\n        return !(a.protocol == location.protocol && a.hostname == location.hostname &&\r\n        (a.port == location.port || (a.port == '80' && location.port == '') || (a.port == '' && location.port == '80')));\r\n    },\r\n    clearEmptyAttrs : function(obj){\r\n        for(var p in obj){\r\n            if(obj[p] === ''){\r\n                delete obj[p]\r\n            }\r\n        }\r\n        return obj;\r\n    },\r\n    str2json : function(s){\r\n\r\n        if (!utils.isString(s)) return null;\r\n        if (window.JSON) {\r\n            return JSON.parse(s);\r\n        } else {\r\n            return (new Function(\"return \" + utils.trim(s || '')))();\r\n        }\r\n\r\n    },\r\n    json2str : (function(){\r\n\r\n        if (window.JSON) {\r\n\r\n            return JSON.stringify;\r\n\r\n        } else {\r\n\r\n            var escapeMap = {\r\n                \"\\b\": '\\\\b',\r\n                \"\\t\": '\\\\t',\r\n                \"\\n\": '\\\\n',\r\n                \"\\f\": '\\\\f',\r\n                \"\\r\": '\\\\r',\r\n                '\"' : '\\\\\"',\r\n                \"\\\\\": '\\\\\\\\'\r\n            };\r\n\r\n            function encodeString(source) {\r\n                if (/[\"\\\\\\x00-\\x1f]/.test(source)) {\r\n                    source = source.replace(\r\n                        /[\"\\\\\\x00-\\x1f]/g,\r\n                        function (match) {\r\n                            var c = escapeMap[match];\r\n                            if (c) {\r\n                                return c;\r\n                            }\r\n                            c = match.charCodeAt();\r\n                            return \"\\\\u00\"\r\n                            + Math.floor(c / 16).toString(16)\r\n                            + (c % 16).toString(16);\r\n                        });\r\n                }\r\n                return '\"' + source + '\"';\r\n            }\r\n\r\n            function encodeArray(source) {\r\n                var result = [\"[\"],\r\n                    l = source.length,\r\n                    preComma, i, item;\r\n\r\n                for (i = 0; i < l; i++) {\r\n                    item = source[i];\r\n\r\n                    switch (typeof item) {\r\n                        case \"undefined\":\r\n                        case \"function\":\r\n                        case \"unknown\":\r\n                            break;\r\n                        default:\r\n                            if(preComma) {\r\n                                result.push(',');\r\n                            }\r\n                            result.push(utils.json2str(item));\r\n                            preComma = 1;\r\n                    }\r\n                }\r\n                result.push(\"]\");\r\n                return result.join(\"\");\r\n            }\r\n\r\n            function pad(source) {\r\n                return source < 10 ? '0' + source : source;\r\n            }\r\n\r\n            function encodeDate(source){\r\n                return '\"' + source.getFullYear() + \"-\"\r\n                + pad(source.getMonth() + 1) + \"-\"\r\n                + pad(source.getDate()) + \"T\"\r\n                + pad(source.getHours()) + \":\"\r\n                + pad(source.getMinutes()) + \":\"\r\n                + pad(source.getSeconds()) + '\"';\r\n            }\r\n\r\n            return function (value) {\r\n                switch (typeof value) {\r\n                    case 'undefined':\r\n                        return 'undefined';\r\n\r\n                    case 'number':\r\n                        return isFinite(value) ? String(value) : \"null\";\r\n\r\n                    case 'string':\r\n                        return encodeString(value);\r\n\r\n                    case 'boolean':\r\n                        return String(value);\r\n\r\n                    default:\r\n                        if (value === null) {\r\n                            return 'null';\r\n                        } else if (utils.isArray(value)) {\r\n                            return encodeArray(value);\r\n                        } else if (utils.isDate(value)) {\r\n                            return encodeDate(value);\r\n                        } else {\r\n                            var result = ['{'],\r\n                                encode = utils.json2str,\r\n                                preComma,\r\n                                item;\r\n\r\n                            for (var key in value) {\r\n                                if (Object.prototype.hasOwnProperty.call(value, key)) {\r\n                                    item = value[key];\r\n                                    switch (typeof item) {\r\n                                        case 'undefined':\r\n                                        case 'unknown':\r\n                                        case 'function':\r\n                                            break;\r\n                                        default:\r\n                                            if (preComma) {\r\n                                                result.push(',');\r\n                                            }\r\n                                            preComma = 1;\r\n                                            result.push(encode(key) + ':' + encode(item));\r\n                                    }\r\n                                }\r\n                            }\r\n                            result.push('}');\r\n                            return result.join('');\r\n                        }\r\n                }\r\n            };\r\n        }\r\n\r\n    })()\r\n\r\n};\r\n/**\r\n * 判断给定的对象是否是字符串\r\n * @method isString\r\n * @param { * } object 需要判断的对象\r\n * @return { Boolean } 给定的对象是否是字符串\r\n */\r\n\r\n/**\r\n * 判断给定的对象是否是数组\r\n * @method isArray\r\n * @param { * } object 需要判断的对象\r\n * @return { Boolean } 给定的对象是否是数组\r\n */\r\n\r\n/**\r\n * 判断给定的对象是否是一个Function\r\n * @method isFunction\r\n * @param { * } object 需要判断的对象\r\n * @return { Boolean } 给定的对象是否是Function\r\n */\r\n\r\n/**\r\n * 判断给定的对象是否是Number\r\n * @method isNumber\r\n * @param { * } object 需要判断的对象\r\n * @return { Boolean } 给定的对象是否是Number\r\n */\r\n\r\n/**\r\n * 判断给定的对象是否是一个正则表达式\r\n * @method isRegExp\r\n * @param { * } object 需要判断的对象\r\n * @return { Boolean } 给定的对象是否是正则表达式\r\n */\r\n\r\n/**\r\n * 判断给定的对象是否是一个普通对象\r\n * @method isObject\r\n * @param { * } object 需要判断的对象\r\n * @return { Boolean } 给定的对象是否是普通对象\r\n */\r\nutils.each(['String', 'Function', 'Array', 'Number', 'RegExp', 'Object', 'Date'], function (v) {\r\n    UE.utils['is' + v] = function (obj) {\r\n        return Object.prototype.toString.apply(obj) == '[object ' + v + ']';\r\n    }\r\n});\r\n\r\n\r\n// core/EventBase.js\r\n/**\r\n * UE采用的事件基类\r\n * @file\r\n * @module UE\r\n * @class EventBase\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * UEditor公用空间，UEditor所有的功能都挂载在该空间下\r\n * @unfile\r\n * @module UE\r\n */\r\n\r\n/**\r\n * UE采用的事件基类，继承此类的对应类将获取addListener,removeListener,fireEvent方法。\r\n * 在UE中，Editor以及所有ui实例都继承了该类，故可以在对应的ui对象以及editor对象上使用上述方法。\r\n * @unfile\r\n * @module UE\r\n * @class EventBase\r\n */\r\n\r\n/**\r\n * 通过此构造器，子类可以继承EventBase获取事件监听的方法\r\n * @constructor\r\n * @example\r\n * ```javascript\r\n * UE.EventBase.call(editor);\r\n * ```\r\n */\r\nvar EventBase = UE.EventBase = function () {};\r\n\r\nEventBase.prototype = {\r\n\r\n    /**\r\n     * 注册事件监听器\r\n     * @method addListener\r\n     * @param { String } types 监听的事件名称，同时监听多个事件使用空格分隔\r\n     * @param { Function } fn 监听的事件被触发时，会执行该回调函数\r\n     * @waining 事件被触发时，监听的函数假如返回的值恒等于true，回调函数的队列中后面的函数将不执行\r\n     * @example\r\n     * ```javascript\r\n     * editor.addListener('selectionchange',function(){\r\n     *      console.log(\"选区已经变化！\");\r\n     * })\r\n     * editor.addListener('beforegetcontent aftergetcontent',function(type){\r\n     *         if(type == 'beforegetcontent'){\r\n     *             //do something\r\n     *         }else{\r\n     *             //do something\r\n     *         }\r\n     *         console.log(this.getContent) // this是注册的事件的编辑器实例\r\n     * })\r\n     * ```\r\n     * @see UE.EventBase:fireEvent(String)\r\n     */\r\n    addListener:function (types, listener) {\r\n        types = utils.trim(types).split(/\\s+/);\r\n        for (var i = 0, ti; ti = types[i++];) {\r\n            getListener(this, ti, true).push(listener);\r\n        }\r\n    },\r\n\r\n    on : function(types, listener){\r\n      return this.addListener(types,listener);\r\n    },\r\n    off : function(types, listener){\r\n        return this.removeListener(types, listener)\r\n    },\r\n    trigger:function(){\r\n        return this.fireEvent.apply(this,arguments);\r\n    },\r\n    /**\r\n     * 移除事件监听器\r\n     * @method removeListener\r\n     * @param { String } types 移除的事件名称，同时移除多个事件使用空格分隔\r\n     * @param { Function } fn 移除监听事件的函数引用\r\n     * @example\r\n     * ```javascript\r\n     * //changeCallback为方法体\r\n     * editor.removeListener(\"selectionchange\",changeCallback);\r\n     * ```\r\n     */\r\n    removeListener:function (types, listener) {\r\n        types = utils.trim(types).split(/\\s+/);\r\n        for (var i = 0, ti; ti = types[i++];) {\r\n            utils.removeItem(getListener(this, ti) || [], listener);\r\n        }\r\n    },\r\n\r\n    /**\r\n     * 触发事件\r\n     * @method fireEvent\r\n     * @param { String } types 触发的事件名称，同时触发多个事件使用空格分隔\r\n     * @remind 该方法会触发addListener\r\n     * @return { * } 返回触发事件的队列中，最后执行的回调函数的返回值\r\n     * @example\r\n     * ```javascript\r\n     * editor.fireEvent(\"selectionchange\");\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 触发事件\r\n     * @method fireEvent\r\n     * @param { String } types 触发的事件名称，同时触发多个事件使用空格分隔\r\n     * @param { *... } options 可选参数，可以传入一个或多个参数，会传给事件触发的回调函数\r\n     * @return { * } 返回触发事件的队列中，最后执行的回调函数的返回值\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * editor.addListener( \"selectionchange\", function ( type, arg1, arg2 ) {\r\n     *\r\n     *     console.log( arg1 + \" \" + arg2 );\r\n     *\r\n     * } );\r\n     *\r\n     * //触发selectionchange事件， 会执行上面的事件监听器\r\n     * //output: Hello World\r\n     * editor.fireEvent(\"selectionchange\", \"Hello\", \"World\");\r\n     * ```\r\n     */\r\n    fireEvent:function () {\r\n        var types = arguments[0];\r\n        types = utils.trim(types).split(' ');\r\n        for (var i = 0, ti; ti = types[i++];) {\r\n            var listeners = getListener(this, ti),\r\n                r, t, k;\r\n            if (listeners) {\r\n                k = listeners.length;\r\n                while (k--) {\r\n                    if(!listeners[k])continue;\r\n                    t = listeners[k].apply(this, arguments);\r\n                    if(t === true){\r\n                        return t;\r\n                    }\r\n                    if (t !== undefined) {\r\n                        r = t;\r\n                    }\r\n                }\r\n            }\r\n            if (t = this['on' + ti.toLowerCase()]) {\r\n                r = t.apply(this, arguments);\r\n            }\r\n        }\r\n        return r;\r\n    }\r\n};\r\n/**\r\n * 获得对象所拥有监听类型的所有监听器\r\n * @unfile\r\n * @module UE\r\n * @since 1.2.6.1\r\n * @method getListener\r\n * @public\r\n * @param { Object } obj  查询监听器的对象\r\n * @param { String } type 事件类型\r\n * @param { Boolean } force  为true且当前所有type类型的侦听器不存在时，创建一个空监听器数组\r\n * @return { Array } 监听器数组\r\n */\r\nfunction getListener(obj, type, force) {\r\n    var allListeners;\r\n    type = type.toLowerCase();\r\n    return ( ( allListeners = ( obj.__allListeners || force && ( obj.__allListeners = {} ) ) )\r\n        && ( allListeners[type] || force && ( allListeners[type] = [] ) ) );\r\n}\r\n\r\n\r\n\r\n// core/dtd.js\r\n///import editor.js\r\n///import core/dom/dom.js\r\n///import core/utils.js\r\n/**\r\n * dtd html语义化的体现类\r\n * @constructor\r\n * @namespace dtd\r\n */\r\nvar dtd = dom.dtd = (function() {\r\n    function _( s ) {\r\n        for (var k in s) {\r\n            s[k.toUpperCase()] = s[k];\r\n        }\r\n        return s;\r\n    }\r\n    var X = utils.extend2;\r\n    var A = _({isindex:1,fieldset:1}),\r\n        B = _({input:1,button:1,select:1,textarea:1,label:1}),\r\n        C = X( _({a:1}), B ),\r\n        D = X( {iframe:1}, C ),\r\n        E = _({hr:1,ul:1,menu:1,div:1,blockquote:1,noscript:1,table:1,center:1,address:1,dir:1,pre:1,h5:1,dl:1,h4:1,noframes:1,h6:1,ol:1,h1:1,h3:1,h2:1}),\r\n        F = _({ins:1,del:1,script:1,style:1}),\r\n        G = X( _({b:1,acronym:1,bdo:1,'var':1,'#':1,abbr:1,code:1,br:1,i:1,cite:1,kbd:1,u:1,strike:1,s:1,tt:1,strong:1,q:1,samp:1,em:1,dfn:1,span:1}), F ),\r\n        H = X( _({sub:1,img:1,embed:1,object:1,sup:1,basefont:1,map:1,applet:1,font:1,big:1,small:1}), G ),\r\n        I = X( _({p:1}), H ),\r\n        J = X( _({iframe:1}), H, B ),\r\n        K = _({img:1,embed:1,noscript:1,br:1,kbd:1,center:1,button:1,basefont:1,h5:1,h4:1,samp:1,h6:1,ol:1,h1:1,h3:1,h2:1,form:1,font:1,'#':1,select:1,menu:1,ins:1,abbr:1,label:1,code:1,table:1,script:1,cite:1,input:1,iframe:1,strong:1,textarea:1,noframes:1,big:1,small:1,span:1,hr:1,sub:1,bdo:1,'var':1,div:1,object:1,sup:1,strike:1,dir:1,map:1,dl:1,applet:1,del:1,isindex:1,fieldset:1,ul:1,b:1,acronym:1,a:1,blockquote:1,i:1,u:1,s:1,tt:1,address:1,q:1,pre:1,p:1,em:1,dfn:1}),\r\n\r\n        L = X( _({a:0}), J ),//a不能被切开，所以把他\r\n        M = _({tr:1}),\r\n        N = _({'#':1}),\r\n        O = X( _({param:1}), K ),\r\n        P = X( _({form:1}), A, D, E, I ),\r\n        Q = _({li:1,ol:1,ul:1}),\r\n        R = _({style:1,script:1}),\r\n        S = _({base:1,link:1,meta:1,title:1}),\r\n        T = X( S, R ),\r\n        U = _({head:1,body:1}),\r\n        V = _({html:1});\r\n\r\n    var block = _({address:1,blockquote:1,center:1,dir:1,div:1,dl:1,fieldset:1,form:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,hr:1,isindex:1,menu:1,noframes:1,ol:1,p:1,pre:1,table:1,ul:1}),\r\n\r\n        empty =  _({area:1,base:1,basefont:1,br:1,col:1,command:1,dialog:1,embed:1,hr:1,img:1,input:1,isindex:1,keygen:1,link:1,meta:1,param:1,source:1,track:1,wbr:1});\r\n\r\n    return  _({\r\n\r\n        // $ 表示自定的属性\r\n\r\n        // body外的元素列表.\r\n        $nonBodyContent: X( V, U, S ),\r\n\r\n        //块结构元素列表\r\n        $block : block,\r\n\r\n        //内联元素列表\r\n        $inline : L,\r\n\r\n        $inlineWithA : X(_({a:1}),L),\r\n\r\n        $body : X( _({script:1,style:1}), block ),\r\n\r\n        $cdata : _({script:1,style:1}),\r\n\r\n        //自闭和元素\r\n        $empty : empty,\r\n\r\n        //不是自闭合，但不能让range选中里边\r\n        $nonChild : _({iframe:1,textarea:1}),\r\n        //列表元素列表\r\n        $listItem : _({dd:1,dt:1,li:1}),\r\n\r\n        //列表根元素列表\r\n        $list: _({ul:1,ol:1,dl:1}),\r\n\r\n        //不能认为是空的元素\r\n        $isNotEmpty : _({table:1,ul:1,ol:1,dl:1,iframe:1,area:1,base:1,col:1,hr:1,img:1,embed:1,input:1,link:1,meta:1,param:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1}),\r\n\r\n        //如果没有子节点就可以删除的元素列表，像span,a\r\n        $removeEmpty : _({a:1,abbr:1,acronym:1,address:1,b:1,bdo:1,big:1,cite:1,code:1,del:1,dfn:1,em:1,font:1,i:1,ins:1,label:1,kbd:1,q:1,s:1,samp:1,small:1,span:1,strike:1,strong:1,sub:1,sup:1,tt:1,u:1,'var':1}),\r\n\r\n        $removeEmptyBlock : _({'p':1,'div':1}),\r\n\r\n        //在table元素里的元素列表\r\n        $tableContent : _({caption:1,col:1,colgroup:1,tbody:1,td:1,tfoot:1,th:1,thead:1,tr:1,table:1}),\r\n        //不转换的标签\r\n        $notTransContent : _({pre:1,script:1,style:1,textarea:1}),\r\n        html: U,\r\n        head: T,\r\n        style: N,\r\n        script: N,\r\n        body: P,\r\n        base: {},\r\n        link: {},\r\n        meta: {},\r\n        title: N,\r\n        col : {},\r\n        tr : _({td:1,th:1}),\r\n        img : {},\r\n        embed: {},\r\n        colgroup : _({thead:1,col:1,tbody:1,tr:1,tfoot:1}),\r\n        noscript : P,\r\n        td : P,\r\n        br : {},\r\n        th : P,\r\n        center : P,\r\n        kbd : L,\r\n        button : X( I, E ),\r\n        basefont : {},\r\n        h5 : L,\r\n        h4 : L,\r\n        samp : L,\r\n        h6 : L,\r\n        ol : Q,\r\n        h1 : L,\r\n        h3 : L,\r\n        option : N,\r\n        h2 : L,\r\n        form : X( A, D, E, I ),\r\n        select : _({optgroup:1,option:1}),\r\n        font : L,\r\n        ins : L,\r\n        menu : Q,\r\n        abbr : L,\r\n        label : L,\r\n        table : _({thead:1,col:1,tbody:1,tr:1,colgroup:1,caption:1,tfoot:1}),\r\n        code : L,\r\n        tfoot : M,\r\n        cite : L,\r\n        li : P,\r\n        input : {},\r\n        iframe : P,\r\n        strong : L,\r\n        textarea : N,\r\n        noframes : P,\r\n        big : L,\r\n        small : L,\r\n        //trace:\r\n        span :_({'#':1,br:1,b:1,strong:1,u:1,i:1,em:1,sub:1,sup:1,strike:1,span:1}),\r\n        hr : L,\r\n        dt : L,\r\n        sub : L,\r\n        optgroup : _({option:1}),\r\n        param : {},\r\n        bdo : L,\r\n        'var' : L,\r\n        div : P,\r\n        object : O,\r\n        sup : L,\r\n        dd : P,\r\n        strike : L,\r\n        area : {},\r\n        dir : Q,\r\n        map : X( _({area:1,form:1,p:1}), A, F, E ),\r\n        applet : O,\r\n        dl : _({dt:1,dd:1}),\r\n        del : L,\r\n        isindex : {},\r\n        fieldset : X( _({legend:1}), K ),\r\n        thead : M,\r\n        ul : Q,\r\n        acronym : L,\r\n        b : L,\r\n        a : X( _({a:1}), J ),\r\n        blockquote :X(_({td:1,tr:1,tbody:1,li:1}),P),\r\n        caption : L,\r\n        i : L,\r\n        u : L,\r\n        tbody : M,\r\n        s : L,\r\n        address : X( D, I ),\r\n        tt : L,\r\n        legend : L,\r\n        q : L,\r\n        pre : X( G, C ),\r\n        p : X(_({'a':1}),L),\r\n        em :L,\r\n        dfn : L\r\n    });\r\n})();\r\n\r\n\r\n// core/domUtils.js\r\n/**\r\n * Dom操作工具包\r\n * @file\r\n * @module UE.dom.domUtils\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * Dom操作工具包\r\n * @unfile\r\n * @module UE.dom.domUtils\r\n */\r\nfunction getDomNode(node, start, ltr, startFromChild, fn, guard) {\r\n    var tmpNode = startFromChild && node[start],\r\n        parent;\r\n    !tmpNode && (tmpNode = node[ltr]);\r\n    while (!tmpNode && (parent = (parent || node).parentNode)) {\r\n        if (parent.tagName == 'BODY' || guard && !guard(parent)) {\r\n            return null;\r\n        }\r\n        tmpNode = parent[ltr];\r\n    }\r\n    if (tmpNode && fn && !fn(tmpNode)) {\r\n        return  getDomNode(tmpNode, start, ltr, false, fn);\r\n    }\r\n    return tmpNode;\r\n}\r\nvar attrFix = ie && browser.version < 9 ? {\r\n        tabindex:\"tabIndex\",\r\n        readonly:\"readOnly\",\r\n        \"for\":\"htmlFor\",\r\n        \"class\":\"className\",\r\n        maxlength:\"maxLength\",\r\n        cellspacing:\"cellSpacing\",\r\n        cellpadding:\"cellPadding\",\r\n        rowspan:\"rowSpan\",\r\n        colspan:\"colSpan\",\r\n        usemap:\"useMap\",\r\n        frameborder:\"frameBorder\"\r\n    } : {\r\n        tabindex:\"tabIndex\",\r\n        readonly:\"readOnly\"\r\n    },\r\n    styleBlock = utils.listToMap([\r\n        '-webkit-box', '-moz-box', 'block' ,\r\n        'list-item' , 'table' , 'table-row-group' ,\r\n        'table-header-group', 'table-footer-group' ,\r\n        'table-row' , 'table-column-group' , 'table-column' ,\r\n        'table-cell' , 'table-caption'\r\n    ]);\r\nvar domUtils = dom.domUtils = {\r\n    //节点常量\r\n    NODE_ELEMENT:1,\r\n    NODE_DOCUMENT:9,\r\n    NODE_TEXT:3,\r\n    NODE_COMMENT:8,\r\n    NODE_DOCUMENT_FRAGMENT:11,\r\n\r\n    //位置关系\r\n    POSITION_IDENTICAL:0,\r\n    POSITION_DISCONNECTED:1,\r\n    POSITION_FOLLOWING:2,\r\n    POSITION_PRECEDING:4,\r\n    POSITION_IS_CONTAINED:8,\r\n    POSITION_CONTAINS:16,\r\n    //ie6使用其他的会有一段空白出现\r\n    fillChar:ie && browser.version == '6' ? '\\ufeff' : '\\u200B',\r\n    //-------------------------Node部分--------------------------------\r\n    keys:{\r\n        /*Backspace*/ 8:1, /*Delete*/ 46:1,\r\n        /*Shift*/ 16:1, /*Ctrl*/ 17:1, /*Alt*/ 18:1,\r\n        37:1, 38:1, 39:1, 40:1,\r\n        13:1 /*enter*/\r\n    },\r\n    /**\r\n     * 获取节点A相对于节点B的位置关系\r\n     * @method getPosition\r\n     * @param { Node } nodeA 需要查询位置关系的节点A\r\n     * @param { Node } nodeB 需要查询位置关系的节点B\r\n     * @return { Number } 节点A与节点B的关系\r\n     * @example\r\n     * ```javascript\r\n     * //output: 20\r\n     * var position = UE.dom.domUtils.getPosition( document.documentElement, document.body );\r\n     *\r\n     * switch ( position ) {\r\n     *\r\n     *      //0\r\n     *      case UE.dom.domUtils.POSITION_IDENTICAL:\r\n     *          console.log('元素相同');\r\n     *          break;\r\n     *      //1\r\n     *      case UE.dom.domUtils.POSITION_DISCONNECTED:\r\n     *          console.log('两个节点在不同的文档中');\r\n     *          break;\r\n     *      //2\r\n     *      case UE.dom.domUtils.POSITION_FOLLOWING:\r\n     *          console.log('节点A在节点B之后');\r\n     *          break;\r\n     *      //4\r\n     *      case UE.dom.domUtils.POSITION_PRECEDING;\r\n     *          console.log('节点A在节点B之前');\r\n     *          break;\r\n     *      //8\r\n     *      case UE.dom.domUtils.POSITION_IS_CONTAINED:\r\n     *          console.log('节点A被节点B包含');\r\n     *          break;\r\n     *      case 10:\r\n     *          console.log('节点A被节点B包含且节点A在节点B之后');\r\n     *          break;\r\n     *      //16\r\n     *      case UE.dom.domUtils.POSITION_CONTAINS:\r\n     *          console.log('节点A包含节点B');\r\n     *          break;\r\n     *      case 20:\r\n     *          console.log('节点A包含节点B且节点A在节点B之前');\r\n     *          break;\r\n     *\r\n     * }\r\n     * ```\r\n     */\r\n    getPosition:function (nodeA, nodeB) {\r\n        // 如果两个节点是同一个节点\r\n        if (nodeA === nodeB) {\r\n            // domUtils.POSITION_IDENTICAL\r\n            return 0;\r\n        }\r\n        var node,\r\n            parentsA = [nodeA],\r\n            parentsB = [nodeB];\r\n        node = nodeA;\r\n        while (node = node.parentNode) {\r\n            // 如果nodeB是nodeA的祖先节点\r\n            if (node === nodeB) {\r\n                // domUtils.POSITION_IS_CONTAINED + domUtils.POSITION_FOLLOWING\r\n                return 10;\r\n            }\r\n            parentsA.push(node);\r\n        }\r\n        node = nodeB;\r\n        while (node = node.parentNode) {\r\n            // 如果nodeA是nodeB的祖先节点\r\n            if (node === nodeA) {\r\n                // domUtils.POSITION_CONTAINS + domUtils.POSITION_PRECEDING\r\n                return 20;\r\n            }\r\n            parentsB.push(node);\r\n        }\r\n        parentsA.reverse();\r\n        parentsB.reverse();\r\n        if (parentsA[0] !== parentsB[0]) {\r\n            // domUtils.POSITION_DISCONNECTED\r\n            return 1;\r\n        }\r\n        var i = -1;\r\n        while (i++, parentsA[i] === parentsB[i]) {\r\n        }\r\n        nodeA = parentsA[i];\r\n        nodeB = parentsB[i];\r\n        while (nodeA = nodeA.nextSibling) {\r\n            if (nodeA === nodeB) {\r\n                // domUtils.POSITION_PRECEDING\r\n                return 4\r\n            }\r\n        }\r\n        // domUtils.POSITION_FOLLOWING\r\n        return  2;\r\n    },\r\n\r\n    /**\r\n     * 检测节点node在父节点中的索引位置\r\n     * @method getNodeIndex\r\n     * @param { Node } node 需要检测的节点对象\r\n     * @return { Number } 该节点在父节点中的位置\r\n     * @see UE.dom.domUtils.getNodeIndex(Node,Boolean)\r\n     */\r\n\r\n    /**\r\n     * 检测节点node在父节点中的索引位置， 根据给定的mergeTextNode参数决定是否要合并多个连续的文本节点为一个节点\r\n     * @method getNodeIndex\r\n     * @param { Node } node 需要检测的节点对象\r\n     * @param { Boolean } mergeTextNode 是否合并多个连续的文本节点为一个节点\r\n     * @return { Number } 该节点在父节点中的位置\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     *      var node = document.createElement(\"div\");\r\n     *\r\n     *      node.appendChild( document.createTextNode( \"hello\" ) );\r\n     *      node.appendChild( document.createTextNode( \"world\" ) );\r\n     *      node.appendChild( node = document.createElement( \"div\" ) );\r\n     *\r\n     *      //output: 2\r\n     *      console.log( UE.dom.domUtils.getNodeIndex( node ) );\r\n     *\r\n     *      //output: 1\r\n     *      console.log( UE.dom.domUtils.getNodeIndex( node, true ) );\r\n     *\r\n     * ```\r\n     */\r\n    getNodeIndex:function (node, ignoreTextNode) {\r\n        var preNode = node,\r\n            i = 0;\r\n        while (preNode = preNode.previousSibling) {\r\n            if (ignoreTextNode && preNode.nodeType == 3) {\r\n                if(preNode.nodeType != preNode.nextSibling.nodeType ){\r\n                    i++;\r\n                }\r\n                continue;\r\n            }\r\n            i++;\r\n        }\r\n        return i;\r\n    },\r\n\r\n    /**\r\n     * 检测节点node是否在给定的document对象上\r\n     * @method inDoc\r\n     * @param { Node } node 需要检测的节点对象\r\n     * @param { DomDocument } doc 需要检测的document对象\r\n     * @return { Boolean } 该节点node是否在给定的document的dom树上\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * var node = document.createElement(\"div\");\r\n     *\r\n     * //output: false\r\n     * console.log( UE.do.domUtils.inDoc( node, document ) );\r\n     *\r\n     * document.body.appendChild( node );\r\n     *\r\n     * //output: true\r\n     * console.log( UE.do.domUtils.inDoc( node, document ) );\r\n     *\r\n     * ```\r\n     */\r\n    inDoc:function (node, doc) {\r\n        return domUtils.getPosition(node, doc) == 10;\r\n    },\r\n    /**\r\n     * 根据给定的过滤规则filterFn， 查找符合该过滤规则的node节点的第一个祖先节点，\r\n     * 查找的起点是给定node节点的父节点。\r\n     * @method findParent\r\n     * @param { Node } node 需要查找的节点\r\n     * @param { Function } filterFn 自定义的过滤方法。\r\n     * @warning 查找的终点是到body节点为止\r\n     * @remind 自定义的过滤方法filterFn接受一个Node对象作为参数， 该对象代表当前执行检测的祖先节点。 如果该\r\n     *          节点满足过滤条件， 则要求返回true， 这时将直接返回该节点作为findParent()的结果， 否则， 请返回false。\r\n     * @return { Node | Null } 如果找到符合过滤条件的节点， 就返回该节点， 否则返回NULL\r\n     * @example\r\n     * ```javascript\r\n     * var filterNode = UE.dom.domUtils.findParent( document.body.firstChild, function ( node ) {\r\n     *\r\n     *     //由于查找的终点是body节点， 所以永远也不会匹配当前过滤器的条件， 即这里永远会返回false\r\n     *     return node.tagName === \"HTML\";\r\n     *\r\n     * } );\r\n     *\r\n     * //output: true\r\n     * console.log( filterNode === null );\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 根据给定的过滤规则filterFn， 查找符合该过滤规则的node节点的第一个祖先节点，\r\n     * 如果includeSelf的值为true，则查找的起点是给定的节点node， 否则， 起点是node的父节点\r\n     * @method findParent\r\n     * @param { Node } node 需要查找的节点\r\n     * @param { Function } filterFn 自定义的过滤方法。\r\n     * @param { Boolean } includeSelf 查找过程是否包含自身\r\n     * @warning 查找的终点是到body节点为止\r\n     * @remind 自定义的过滤方法filterFn接受一个Node对象作为参数， 该对象代表当前执行检测的祖先节点。 如果该\r\n     *          节点满足过滤条件， 则要求返回true， 这时将直接返回该节点作为findParent()的结果， 否则， 请返回false。\r\n     * @remind 如果includeSelf为true， 则过滤器第一次执行时的参数会是节点本身。\r\n     *          反之， 过滤器第一次执行时的参数将是该节点的父节点。\r\n     * @return { Node | Null } 如果找到符合过滤条件的节点， 就返回该节点， 否则返回NULL\r\n     * @example\r\n     * ```html\r\n     * <body>\r\n     *\r\n     *      <div id=\"test\">\r\n     *      </div>\r\n     *\r\n     *      <script type=\"text/javascript\">\r\n     *\r\n     *          //output: DIV, BODY\r\n     *          var filterNode = UE.dom.domUtils.findParent( document.getElementById( \"test\" ), function ( node ) {\r\n     *\r\n     *              console.log( node.tagName );\r\n     *              return false;\r\n     *\r\n     *          }, true );\r\n     *\r\n     *      </script>\r\n     * </body>\r\n     * ```\r\n     */\r\n    findParent:function (node, filterFn, includeSelf) {\r\n        if (node && !domUtils.isBody(node)) {\r\n            node = includeSelf ? node : node.parentNode;\r\n            while (node) {\r\n                if (!filterFn || filterFn(node) || domUtils.isBody(node)) {\r\n                    return filterFn && !filterFn(node) && domUtils.isBody(node) ? null : node;\r\n                }\r\n                node = node.parentNode;\r\n            }\r\n        }\r\n        return null;\r\n    },\r\n    /**\r\n     * 查找node的节点名为tagName的第一个祖先节点， 查找的起点是node节点的父节点。\r\n     * @method findParentByTagName\r\n     * @param { Node } node 需要查找的节点对象\r\n     * @param { Array } tagNames 需要查找的父节点的名称数组\r\n     * @warning 查找的终点是到body节点为止\r\n     * @return { Node | NULL } 如果找到符合条件的节点， 则返回该节点， 否则返回NULL\r\n     * @example\r\n     * ```javascript\r\n     * var node = UE.dom.domUtils.findParentByTagName( document.getElementsByTagName(\"div\")[0], [ \"BODY\" ] );\r\n     * //output: BODY\r\n     * console.log( node.tagName );\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 查找node的节点名为tagName的祖先节点， 如果includeSelf的值为true，则查找的起点是给定的节点node，\r\n     * 否则， 起点是node的父节点。\r\n     * @method findParentByTagName\r\n     * @param { Node } node 需要查找的节点对象\r\n     * @param { Array } tagNames 需要查找的父节点的名称数组\r\n     * @param { Boolean } includeSelf 查找过程是否包含node节点自身\r\n     * @warning 查找的终点是到body节点为止\r\n     * @return { Node | NULL } 如果找到符合条件的节点， 则返回该节点， 否则返回NULL\r\n     * @example\r\n     * ```javascript\r\n     * var queryTarget = document.getElementsByTagName(\"div\")[0];\r\n     * var node = UE.dom.domUtils.findParentByTagName( queryTarget, [ \"DIV\" ], true );\r\n     * //output: true\r\n     * console.log( queryTarget === node );\r\n     * ```\r\n     */\r\n    findParentByTagName:function (node, tagNames, includeSelf, excludeFn) {\r\n        tagNames = utils.listToMap(utils.isArray(tagNames) ? tagNames : [tagNames]);\r\n        return domUtils.findParent(node, function (node) {\r\n            return tagNames[node.tagName] && !(excludeFn && excludeFn(node));\r\n        }, includeSelf);\r\n    },\r\n    /**\r\n     * 查找节点node的祖先节点集合， 查找的起点是给定节点的父节点，结果集中不包含给定的节点。\r\n     * @method findParents\r\n     * @param { Node } node 需要查找的节点对象\r\n     * @return { Array } 给定节点的祖先节点数组\r\n     * @grammar UE.dom.domUtils.findParents(node)  => Array  //返回一个祖先节点数组集合，不包含自身\r\n     * @grammar UE.dom.domUtils.findParents(node,includeSelf)  => Array  //返回一个祖先节点数组集合，includeSelf指定是否包含自身\r\n     * @grammar UE.dom.domUtils.findParents(node,includeSelf,filterFn)  => Array  //返回一个祖先节点数组集合，filterFn指定过滤条件，返回true的node将被选取\r\n     * @grammar UE.dom.domUtils.findParents(node,includeSelf,filterFn,closerFirst)  => Array  //返回一个祖先节点数组集合，closerFirst为true的话，node的直接父亲节点是数组的第0个\r\n     */\r\n\r\n    /**\r\n     * 查找节点node的祖先节点集合， 如果includeSelf的值为true，\r\n     * 则返回的结果集中允许出现当前给定的节点， 否则， 该节点不会出现在其结果集中。\r\n     * @method findParents\r\n     * @param { Node } node 需要查找的节点对象\r\n     * @param { Boolean } includeSelf 查找的结果中是否允许包含当前查找的节点对象\r\n     * @return { Array } 给定节点的祖先节点数组\r\n     */\r\n    findParents:function (node, includeSelf, filterFn, closerFirst) {\r\n        var parents = includeSelf && ( filterFn && filterFn(node) || !filterFn ) ? [node] : [];\r\n        while (node = domUtils.findParent(node, filterFn)) {\r\n            parents.push(node);\r\n        }\r\n        return closerFirst ? parents : parents.reverse();\r\n    },\r\n\r\n    /**\r\n     * 在节点node后面插入新节点newNode\r\n     * @method insertAfter\r\n     * @param { Node } node 目标节点\r\n     * @param { Node } newNode 新插入的节点， 该节点将置于目标节点之后\r\n     * @return { Node } 新插入的节点\r\n     */\r\n    insertAfter:function (node, newNode) {\r\n        return node.nextSibling ? node.parentNode.insertBefore(newNode, node.nextSibling):\r\n            node.parentNode.appendChild(newNode);\r\n    },\r\n\r\n    /**\r\n     * 删除节点node及其下属的所有节点\r\n     * @method remove\r\n     * @param { Node } node 需要删除的节点对象\r\n     * @return { Node } 返回刚删除的节点对象\r\n     * @example\r\n     * ```html\r\n     * <div id=\"test\">\r\n     *     <div id=\"child\">你好</div>\r\n     * </div>\r\n     * <script>\r\n     *     UE.dom.domUtils.remove( document.body, false );\r\n     *     //output: false\r\n     *     console.log( document.getElementById( \"child\" ) !== null );\r\n     * </script>\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 删除节点node，并根据keepChildren的值决定是否保留子节点\r\n     * @method remove\r\n     * @param { Node } node 需要删除的节点对象\r\n     * @param { Boolean } keepChildren 是否需要保留子节点\r\n     * @return { Node } 返回刚删除的节点对象\r\n     * @example\r\n     * ```html\r\n     * <div id=\"test\">\r\n     *     <div id=\"child\">你好</div>\r\n     * </div>\r\n     * <script>\r\n     *     UE.dom.domUtils.remove( document.body, true );\r\n     *     //output: true\r\n     *     console.log( document.getElementById( \"child\" ) !== null );\r\n     * </script>\r\n     * ```\r\n     */\r\n    remove:function (node, keepChildren) {\r\n        var parent = node.parentNode,\r\n            child;\r\n        if (parent) {\r\n            if (keepChildren && node.hasChildNodes()) {\r\n                while (child = node.firstChild) {\r\n                    parent.insertBefore(child, node);\r\n                }\r\n            }\r\n            parent.removeChild(node);\r\n        }\r\n        return node;\r\n    },\r\n\r\n    /**\r\n     * 取得node节点的下一个兄弟节点， 如果该节点其后没有兄弟节点， 则递归查找其父节点之后的第一个兄弟节点，\r\n     * 直到找到满足条件的节点或者递归到BODY节点之后才会结束。\r\n     * @method getNextDomNode\r\n     * @param { Node } node 需要获取其后的兄弟节点的节点对象\r\n     * @return { Node | NULL } 如果找满足条件的节点， 则返回该节点， 否则返回NULL\r\n     * @example\r\n     * ```html\r\n     *     <body>\r\n     *      <div id=\"test\">\r\n     *          <span></span>\r\n     *      </div>\r\n     *      <i>xxx</i>\r\n     * </body>\r\n     * <script>\r\n     *\r\n     *     //output: i节点\r\n     *     console.log( UE.dom.domUtils.getNextDomNode( document.getElementById( \"test\" ) ) );\r\n     *\r\n     * </script>\r\n     * ```\r\n     * @example\r\n     * ```html\r\n     * <body>\r\n     *      <div>\r\n     *          <span></span>\r\n     *          <i id=\"test\">xxx</i>\r\n     *      </div>\r\n     *      <b>xxx</b>\r\n     * </body>\r\n     * <script>\r\n     *\r\n     *     //由于id为test的i节点之后没有兄弟节点， 则查找其父节点（div）后面的兄弟节点\r\n     *     //output: b节点\r\n     *     console.log( UE.dom.domUtils.getNextDomNode( document.getElementById( \"test\" ) ) );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 取得node节点的下一个兄弟节点， 如果startFromChild的值为ture，则先获取其子节点，\r\n     * 如果有子节点则直接返回第一个子节点；如果没有子节点或者startFromChild的值为false，\r\n     * 则执行<a href=\"#UE.dom.domUtils.getNextDomNode(Node)\">getNextDomNode(Node node)</a>的查找过程。\r\n     * @method getNextDomNode\r\n     * @param { Node } node 需要获取其后的兄弟节点的节点对象\r\n     * @param { Boolean } startFromChild 查找过程是否从其子节点开始\r\n     * @return { Node | NULL } 如果找满足条件的节点， 则返回该节点， 否则返回NULL\r\n     * @see UE.dom.domUtils.getNextDomNode(Node)\r\n     */\r\n    getNextDomNode:function (node, startFromChild, filterFn, guard) {\r\n        return getDomNode(node, 'firstChild', 'nextSibling', startFromChild, filterFn, guard);\r\n    },\r\n    getPreDomNode:function (node, startFromChild, filterFn, guard) {\r\n        return getDomNode(node, 'lastChild', 'previousSibling', startFromChild, filterFn, guard);\r\n    },\r\n    /**\r\n     * 检测节点node是否属是UEditor定义的bookmark节点\r\n     * @method isBookmarkNode\r\n     * @private\r\n     * @param { Node } node 需要检测的节点对象\r\n     * @return { Boolean } 是否是bookmark节点\r\n     * @example\r\n     * ```html\r\n     * <span id=\"_baidu_bookmark_1\"></span>\r\n     * <script>\r\n     *      var bookmarkNode = document.getElementById(\"_baidu_bookmark_1\");\r\n     *      //output: true\r\n     *      console.log( UE.dom.domUtils.isBookmarkNode( bookmarkNode ) );\r\n     * </script>\r\n     * ```\r\n     */\r\n    isBookmarkNode:function (node) {\r\n        return node.nodeType == 1 && node.id && /^_baidu_bookmark_/i.test(node.id);\r\n    },\r\n    /**\r\n     * 获取节点node所属的window对象\r\n     * @method  getWindow\r\n     * @param { Node } node 节点对象\r\n     * @return { Window } 当前节点所属的window对象\r\n     * @example\r\n     * ```javascript\r\n     * //output: true\r\n     * console.log( UE.dom.domUtils.getWindow( document.body ) === window );\r\n     * ```\r\n     */\r\n    getWindow:function (node) {\r\n        var doc = node.ownerDocument || node;\r\n        return doc.defaultView || doc.parentWindow;\r\n    },\r\n    /**\r\n     * 获取离nodeA与nodeB最近的公共的祖先节点\r\n     * @method  getCommonAncestor\r\n     * @param { Node } nodeA 第一个节点\r\n     * @param { Node } nodeB 第二个节点\r\n     * @remind 如果给定的两个节点是同一个节点， 将直接返回该节点。\r\n     * @return { Node | NULL } 如果未找到公共节点， 返回NULL， 否则返回最近的公共祖先节点。\r\n     * @example\r\n     * ```javascript\r\n     * var commonAncestor = UE.dom.domUtils.getCommonAncestor( document.body, document.body.firstChild );\r\n     * //output: true\r\n     * console.log( commonAncestor.tagName.toLowerCase() === 'body' );\r\n     * ```\r\n     */\r\n    getCommonAncestor:function (nodeA, nodeB) {\r\n        if (nodeA === nodeB)\r\n            return nodeA;\r\n        var parentsA = [nodeA] , parentsB = [nodeB], parent = nodeA, i = -1;\r\n        while (parent = parent.parentNode) {\r\n            if (parent === nodeB) {\r\n                return parent;\r\n            }\r\n            parentsA.push(parent);\r\n        }\r\n        parent = nodeB;\r\n        while (parent = parent.parentNode) {\r\n            if (parent === nodeA)\r\n                return parent;\r\n            parentsB.push(parent);\r\n        }\r\n        parentsA.reverse();\r\n        parentsB.reverse();\r\n        while (i++, parentsA[i] === parentsB[i]) {\r\n        }\r\n        return i == 0 ? null : parentsA[i - 1];\r\n\r\n    },\r\n    /**\r\n     * 清除node节点左右连续为空的兄弟inline节点\r\n     * @method clearEmptySibling\r\n     * @param { Node } node 执行的节点对象， 如果该节点的左右连续的兄弟节点是空的inline节点，\r\n     * 则这些兄弟节点将被删除\r\n     * @grammar UE.dom.domUtils.clearEmptySibling(node,ignoreNext)  //ignoreNext指定是否忽略右边空节点\r\n     * @grammar UE.dom.domUtils.clearEmptySibling(node,ignoreNext,ignorePre)  //ignorePre指定是否忽略左边空节点\r\n     * @example\r\n     * ```html\r\n     * <body>\r\n     *     <div></div>\r\n     *     <span id=\"test\"></span>\r\n     *     <i></i>\r\n     *     <b></b>\r\n     *     <em>xxx</em>\r\n     *     <span></span>\r\n     * </body>\r\n     * <script>\r\n     *\r\n     *      UE.dom.domUtils.clearEmptySibling( document.getElementById( \"test\" ) );\r\n     *\r\n     *      //output: <div></div><span id=\"test\"></span><em>xxx</em><span></span>\r\n     *      console.log( document.body.innerHTML );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 清除node节点左右连续为空的兄弟inline节点， 如果ignoreNext的值为true，\r\n     * 则忽略对右边兄弟节点的操作。\r\n     * @method clearEmptySibling\r\n     * @param { Node } node 执行的节点对象， 如果该节点的左右连续的兄弟节点是空的inline节点，\r\n     * @param { Boolean } ignoreNext 是否忽略忽略对右边的兄弟节点的操作\r\n     * 则这些兄弟节点将被删除\r\n     * @see UE.dom.domUtils.clearEmptySibling(Node)\r\n     */\r\n\r\n    /**\r\n     * 清除node节点左右连续为空的兄弟inline节点， 如果ignoreNext的值为true，\r\n     * 则忽略对右边兄弟节点的操作， 如果ignorePre的值为true，则忽略对左边兄弟节点的操作。\r\n     * @method clearEmptySibling\r\n     * @param { Node } node 执行的节点对象， 如果该节点的左右连续的兄弟节点是空的inline节点，\r\n     * @param { Boolean } ignoreNext 是否忽略忽略对右边的兄弟节点的操作\r\n     * @param { Boolean } ignorePre 是否忽略忽略对左边的兄弟节点的操作\r\n     * 则这些兄弟节点将被删除\r\n     * @see UE.dom.domUtils.clearEmptySibling(Node)\r\n     */\r\n    clearEmptySibling:function (node, ignoreNext, ignorePre) {\r\n        function clear(next, dir) {\r\n            var tmpNode;\r\n            while (next && !domUtils.isBookmarkNode(next) && (domUtils.isEmptyInlineElement(next)\r\n                //这里不能把空格算进来会吧空格干掉，出现文字间的空格丢掉了\r\n                || !new RegExp('[^\\t\\n\\r' + domUtils.fillChar + ']').test(next.nodeValue) )) {\r\n                tmpNode = next[dir];\r\n                domUtils.remove(next);\r\n                next = tmpNode;\r\n            }\r\n        }\r\n        !ignoreNext && clear(node.nextSibling, 'nextSibling');\r\n        !ignorePre && clear(node.previousSibling, 'previousSibling');\r\n    },\r\n    /**\r\n     * 将一个文本节点textNode拆分成两个文本节点，offset指定拆分位置\r\n     * @method split\r\n     * @param { Node } textNode 需要拆分的文本节点对象\r\n     * @param { int } offset 需要拆分的位置， 位置计算从0开始\r\n     * @return { Node } 拆分后形成的新节点\r\n     * @example\r\n     * ```html\r\n     * <div id=\"test\">abcdef</div>\r\n     * <script>\r\n     *      var newNode = UE.dom.domUtils.split( document.getElementById( \"test\" ).firstChild, 3 );\r\n     *      //output: def\r\n     *      console.log( newNode.nodeValue );\r\n     * </script>\r\n     * ```\r\n     */\r\n    split:function (node, offset) {\r\n        var doc = node.ownerDocument;\r\n        if (browser.ie && offset == node.nodeValue.length) {\r\n            var next = doc.createTextNode('');\r\n            return domUtils.insertAfter(node, next);\r\n        }\r\n        var retval = node.splitText(offset);\r\n        //ie8下splitText不会跟新childNodes,我们手动触发他的更新\r\n        if (browser.ie8) {\r\n            var tmpNode = doc.createTextNode('');\r\n            domUtils.insertAfter(retval, tmpNode);\r\n            domUtils.remove(tmpNode);\r\n        }\r\n        return retval;\r\n    },\r\n\r\n    /**\r\n     * 检测文本节点textNode是否为空节点（包括空格、换行、占位符等字符）\r\n     * @method  isWhitespace\r\n     * @param { Node } node 需要检测的节点对象\r\n     * @return { Boolean } 检测的节点是否为空\r\n     * @example\r\n     * ```html\r\n     * <div id=\"test\">\r\n     *\r\n     * </div>\r\n     * <script>\r\n     *      //output: true\r\n     *      console.log( UE.dom.domUtils.isWhitespace( document.getElementById(\"test\").firstChild ) );\r\n     * </script>\r\n     * ```\r\n     */\r\n    isWhitespace:function (node) {\r\n        return !new RegExp('[^ \\t\\n\\r' + domUtils.fillChar + ']').test(node.nodeValue);\r\n    },\r\n    /**\r\n     * 获取元素element相对于viewport的位置坐标\r\n     * @method getXY\r\n     * @param { Node } element 需要计算位置的节点对象\r\n     * @return { Object } 返回形如{x:left,y:top}的一个key-value映射对象， 其中键x代表水平偏移距离，\r\n     *                          y代表垂直偏移距离。\r\n     *\r\n     * @example\r\n     * ```javascript\r\n     * var location = UE.dom.domUtils.getXY( document.getElementById(\"test\") );\r\n     * //output: test的坐标为: 12, 24\r\n     * console.log( 'test的坐标为： ', location.x, ',', location.y );\r\n     * ```\r\n     */\r\n    getXY:function (element) {\r\n        var x = 0, y = 0;\r\n        while (element.offsetParent) {\r\n            y += element.offsetTop;\r\n            x += element.offsetLeft;\r\n            element = element.offsetParent;\r\n        }\r\n        return { 'x':x, 'y':y};\r\n    },\r\n    /**\r\n     * 为元素element绑定原生DOM事件，type为事件类型，handler为处理函数\r\n     * @method on\r\n     * @param { Node } element 需要绑定事件的节点对象\r\n     * @param { String } type 绑定的事件类型\r\n     * @param { Function } handler 事件处理器\r\n     * @example\r\n     * ```javascript\r\n     * UE.dom.domUtils.on(document.body,\"click\",function(e){\r\n     *     //e为事件对象，this为被点击元素对戏那个\r\n     * });\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 为元素element绑定原生DOM事件，type为事件类型，handler为处理函数\r\n     * @method on\r\n     * @param { Node } element 需要绑定事件的节点对象\r\n     * @param { Array } type 绑定的事件类型数组\r\n     * @param { Function } handler 事件处理器\r\n     * @example\r\n     * ```javascript\r\n     * UE.dom.domUtils.on(document.body,[\"click\",\"mousedown\"],function(evt){\r\n     *     //evt为事件对象，this为被点击元素对象\r\n     * });\r\n     * ```\r\n     */\r\n    on:function (element, type, handler) {\r\n\r\n        var types = utils.isArray(type) ? type : utils.trim(type).split(/\\s+/),\r\n            k = types.length;\r\n        if (k) while (k--) {\r\n            type = types[k];\r\n            if (element.addEventListener) {\r\n                element.addEventListener(type, handler, false);\r\n            } else {\r\n                if (!handler._d) {\r\n                    handler._d = {\r\n                        els : []\r\n                    };\r\n                }\r\n                var key = type + handler.toString(),index = utils.indexOf(handler._d.els,element);\r\n                if (!handler._d[key] || index == -1) {\r\n                    if(index == -1){\r\n                        handler._d.els.push(element);\r\n                    }\r\n                    if(!handler._d[key]){\r\n                        handler._d[key] = function (evt) {\r\n                            return handler.call(evt.srcElement, evt || window.event);\r\n                        };\r\n                    }\r\n\r\n\r\n                    element.attachEvent('on' + type, handler._d[key]);\r\n                }\r\n            }\r\n        }\r\n        element = null;\r\n    },\r\n    /**\r\n     * 解除DOM事件绑定\r\n     * @method un\r\n     * @param { Node } element 需要解除事件绑定的节点对象\r\n     * @param { String } type 需要接触绑定的事件类型\r\n     * @param { Function } handler 对应的事件处理器\r\n     * @example\r\n     * ```javascript\r\n     * UE.dom.domUtils.un(document.body,\"click\",function(evt){\r\n     *     //evt为事件对象，this为被点击元素对象\r\n     * });\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 解除DOM事件绑定\r\n     * @method un\r\n     * @param { Node } element 需要解除事件绑定的节点对象\r\n     * @param { Array } type 需要接触绑定的事件类型数组\r\n     * @param { Function } handler 对应的事件处理器\r\n     * @example\r\n     * ```javascript\r\n     * UE.dom.domUtils.un(document.body, [\"click\",\"mousedown\"],function(evt){\r\n     *     //evt为事件对象，this为被点击元素对象\r\n     * });\r\n     * ```\r\n     */\r\n    un:function (element, type, handler) {\r\n        var types = utils.isArray(type) ? type : utils.trim(type).split(/\\s+/),\r\n            k = types.length;\r\n        if (k) while (k--) {\r\n            type = types[k];\r\n            if (element.removeEventListener) {\r\n                element.removeEventListener(type, handler, false);\r\n            } else {\r\n                var key = type + handler.toString();\r\n                try{\r\n                    element.detachEvent('on' + type, handler._d ? handler._d[key] : handler);\r\n                }catch(e){}\r\n                if (handler._d && handler._d[key]) {\r\n                    var index = utils.indexOf(handler._d.els,element);\r\n                    if(index!=-1){\r\n                        handler._d.els.splice(index,1);\r\n                    }\r\n                    handler._d.els.length == 0 && delete handler._d[key];\r\n                }\r\n            }\r\n        }\r\n    },\r\n\r\n    /**\r\n     * 比较节点nodeA与节点nodeB是否具有相同的标签名、属性名以及属性值\r\n     * @method  isSameElement\r\n     * @param { Node } nodeA 需要比较的节点\r\n     * @param { Node } nodeB 需要比较的节点\r\n     * @return { Boolean } 两个节点是否具有相同的标签名、属性名以及属性值\r\n     * @example\r\n     * ```html\r\n     * <span style=\"font-size:12px\">ssss</span>\r\n     * <span style=\"font-size:12px\">bbbbb</span>\r\n     * <span style=\"font-size:13px\">ssss</span>\r\n     * <span style=\"font-size:14px\">bbbbb</span>\r\n     *\r\n     * <script>\r\n     *\r\n     *     var nodes = document.getElementsByTagName( \"span\" );\r\n     *\r\n     *     //output: true\r\n     *     console.log( UE.dom.domUtils.isSameElement( nodes[0], nodes[1] ) );\r\n     *\r\n     *     //output: false\r\n     *     console.log( UE.dom.domUtils.isSameElement( nodes[2], nodes[3] ) );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n    isSameElement:function (nodeA, nodeB) {\r\n        if (nodeA.tagName != nodeB.tagName) {\r\n            return false;\r\n        }\r\n        var thisAttrs = nodeA.attributes,\r\n            otherAttrs = nodeB.attributes;\r\n        if (!ie && thisAttrs.length != otherAttrs.length) {\r\n            return false;\r\n        }\r\n        var attrA, attrB, al = 0, bl = 0;\r\n        for (var i = 0; attrA = thisAttrs[i++];) {\r\n            if (attrA.nodeName == 'style') {\r\n                if (attrA.specified) {\r\n                    al++;\r\n                }\r\n                if (domUtils.isSameStyle(nodeA, nodeB)) {\r\n                    continue;\r\n                } else {\r\n                    return false;\r\n                }\r\n            }\r\n            if (ie) {\r\n                if (attrA.specified) {\r\n                    al++;\r\n                    attrB = otherAttrs.getNamedItem(attrA.nodeName);\r\n                } else {\r\n                    continue;\r\n                }\r\n            } else {\r\n                attrB = nodeB.attributes[attrA.nodeName];\r\n            }\r\n            if (!attrB.specified || attrA.nodeValue != attrB.nodeValue) {\r\n                return false;\r\n            }\r\n        }\r\n        // 有可能attrB的属性包含了attrA的属性之外还有自己的属性\r\n        if (ie) {\r\n            for (i = 0; attrB = otherAttrs[i++];) {\r\n                if (attrB.specified) {\r\n                    bl++;\r\n                }\r\n            }\r\n            if (al != bl) {\r\n                return false;\r\n            }\r\n        }\r\n        return true;\r\n    },\r\n\r\n    /**\r\n     * 判断节点nodeA与节点nodeB的元素的style属性是否一致\r\n     * @method isSameStyle\r\n     * @param { Node } nodeA 需要比较的节点\r\n     * @param { Node } nodeB 需要比较的节点\r\n     * @return { Boolean } 两个节点是否具有相同的style属性值\r\n     * @example\r\n     * ```html\r\n     * <span style=\"font-size:12px\">ssss</span>\r\n     * <span style=\"font-size:12px\">bbbbb</span>\r\n     * <span style=\"font-size:13px\">ssss</span>\r\n     * <span style=\"font-size:14px\">bbbbb</span>\r\n     *\r\n     * <script>\r\n     *\r\n     *     var nodes = document.getElementsByTagName( \"span\" );\r\n     *\r\n     *     //output: true\r\n     *     console.log( UE.dom.domUtils.isSameStyle( nodes[0], nodes[1] ) );\r\n     *\r\n     *     //output: false\r\n     *     console.log( UE.dom.domUtils.isSameStyle( nodes[2], nodes[3] ) );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n    isSameStyle:function (nodeA, nodeB) {\r\n        var styleA = nodeA.style.cssText.replace(/( ?; ?)/g, ';').replace(/( ?: ?)/g, ':'),\r\n            styleB = nodeB.style.cssText.replace(/( ?; ?)/g, ';').replace(/( ?: ?)/g, ':');\r\n        if (browser.opera) {\r\n            styleA = nodeA.style;\r\n            styleB = nodeB.style;\r\n            if (styleA.length != styleB.length)\r\n                return false;\r\n            for (var p in styleA) {\r\n                if (/^(\\d+|csstext)$/i.test(p)) {\r\n                    continue;\r\n                }\r\n                if (styleA[p] != styleB[p]) {\r\n                    return false;\r\n                }\r\n            }\r\n            return true;\r\n        }\r\n        if (!styleA || !styleB) {\r\n            return styleA == styleB;\r\n        }\r\n        styleA = styleA.split(';');\r\n        styleB = styleB.split(';');\r\n        if (styleA.length != styleB.length) {\r\n            return false;\r\n        }\r\n        for (var i = 0, ci; ci = styleA[i++];) {\r\n            if (utils.indexOf(styleB, ci) == -1) {\r\n                return false;\r\n            }\r\n        }\r\n        return true;\r\n    },\r\n    /**\r\n     * 检查节点node是否为block元素\r\n     * @method isBlockElm\r\n     * @param { Node } node 需要检测的节点对象\r\n     * @return { Boolean } 是否是block元素节点\r\n     * @warning 该方法的判断规则如下： 如果该元素原本是block元素， 则不论该元素当前的css样式是什么都会返回true；\r\n     *          否则，检测该元素的css样式， 如果该元素当前是block元素， 则返回true。 其余情况下都返回false。\r\n     * @example\r\n     * ```html\r\n     * <span id=\"test1\" style=\"display: block\"></span>\r\n     * <span id=\"test2\"></span>\r\n     * <div id=\"test3\" style=\"display: inline\"></div>\r\n     *\r\n     * <script>\r\n     *\r\n     *     //output: true\r\n     *     console.log( UE.dom.domUtils.isBlockElm( document.getElementById(\"test1\") ) );\r\n     *\r\n     *     //output: false\r\n     *     console.log( UE.dom.domUtils.isBlockElm( document.getElementById(\"test2\") ) );\r\n     *\r\n     *     //output: true\r\n     *     console.log( UE.dom.domUtils.isBlockElm( document.getElementById(\"test3\") ) );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n    isBlockElm:function (node) {\r\n        return node.nodeType == 1 && (dtd.$block[node.tagName] || styleBlock[domUtils.getComputedStyle(node, 'display')]) && !dtd.$nonChild[node.tagName];\r\n    },\r\n    /**\r\n     * 检测node节点是否为body节点\r\n     * @method isBody\r\n     * @param { Element } node 需要检测的dom元素\r\n     * @return { Boolean } 给定的元素是否是body元素\r\n     * @example\r\n     * ```javascript\r\n     * //output: true\r\n     * console.log( UE.dom.domUtils.isBody( document.body ) );\r\n     * ```\r\n     */\r\n    isBody:function (node) {\r\n        return  node && node.nodeType == 1 && node.tagName.toLowerCase() == 'body';\r\n    },\r\n    /**\r\n     * 以node节点为分界，将该节点的指定祖先节点parent拆分成两个独立的节点，\r\n     * 拆分形成的两个节点之间是node节点\r\n     * @method breakParent\r\n     * @param { Node } node 作为分界的节点对象\r\n     * @param { Node } parent 该节点必须是node节点的祖先节点， 且是block节点。\r\n     * @return { Node } 给定的node分界节点\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     *      var node = document.createElement(\"span\"),\r\n     *          wrapNode = document.createElement( \"div\" ),\r\n     *          parent = document.createElement(\"p\");\r\n     *\r\n     *      parent.appendChild( node );\r\n     *      wrapNode.appendChild( parent );\r\n     *\r\n     *      //拆分前\r\n     *      //output: <p><span></span></p>\r\n     *      console.log( wrapNode.innerHTML );\r\n     *\r\n     *\r\n     *      UE.dom.domUtils.breakParent( node, parent );\r\n     *      //拆分后\r\n     *      //output: <p></p><span></span><p></p>\r\n     *      console.log( wrapNode.innerHTML );\r\n     *\r\n     * ```\r\n     */\r\n    breakParent:function (node, parent) {\r\n        var tmpNode,\r\n            parentClone = node,\r\n            clone = node,\r\n            leftNodes,\r\n            rightNodes;\r\n        do {\r\n            parentClone = parentClone.parentNode;\r\n            if (leftNodes) {\r\n                tmpNode = parentClone.cloneNode(false);\r\n                tmpNode.appendChild(leftNodes);\r\n                leftNodes = tmpNode;\r\n                tmpNode = parentClone.cloneNode(false);\r\n                tmpNode.appendChild(rightNodes);\r\n                rightNodes = tmpNode;\r\n            } else {\r\n                leftNodes = parentClone.cloneNode(false);\r\n                rightNodes = leftNodes.cloneNode(false);\r\n            }\r\n            while (tmpNode = clone.previousSibling) {\r\n                leftNodes.insertBefore(tmpNode, leftNodes.firstChild);\r\n            }\r\n            while (tmpNode = clone.nextSibling) {\r\n                rightNodes.appendChild(tmpNode);\r\n            }\r\n            clone = parentClone;\r\n        } while (parent !== parentClone);\r\n        tmpNode = parent.parentNode;\r\n        tmpNode.insertBefore(leftNodes, parent);\r\n        tmpNode.insertBefore(rightNodes, parent);\r\n        tmpNode.insertBefore(node, rightNodes);\r\n        domUtils.remove(parent);\r\n        return node;\r\n    },\r\n    /**\r\n     * 检查节点node是否是空inline节点\r\n     * @method  isEmptyInlineElement\r\n     * @param { Node } node 需要检测的节点对象\r\n     * @return { Number }  如果给定的节点是空的inline节点， 则返回1, 否则返回0。\r\n     * @example\r\n     * ```html\r\n     * <b><i></i></b> => 1\r\n     * <b><i></i><u></u></b> => 1\r\n     * <b></b> => 1\r\n     * <b>xx<i></i></b> => 0\r\n     * ```\r\n     */\r\n    isEmptyInlineElement:function (node) {\r\n        if (node.nodeType != 1 || !dtd.$removeEmpty[ node.tagName ]) {\r\n            return 0;\r\n        }\r\n        node = node.firstChild;\r\n        while (node) {\r\n            //如果是创建的bookmark就跳过\r\n            if (domUtils.isBookmarkNode(node)) {\r\n                return 0;\r\n            }\r\n            if (node.nodeType == 1 && !domUtils.isEmptyInlineElement(node) ||\r\n                node.nodeType == 3 && !domUtils.isWhitespace(node)\r\n                ) {\r\n                return 0;\r\n            }\r\n            node = node.nextSibling;\r\n        }\r\n        return 1;\r\n\r\n    },\r\n\r\n    /**\r\n     * 删除node节点下首尾两端的空白文本子节点\r\n     * @method trimWhiteTextNode\r\n     * @param { Element } node 需要执行删除操作的元素对象\r\n     * @example\r\n     * ```javascript\r\n     *      var node = document.createElement(\"div\");\r\n     *\r\n     *      node.appendChild( document.createTextNode( \"\" ) );\r\n     *\r\n     *      node.appendChild( document.createElement(\"div\") );\r\n     *\r\n     *      node.appendChild( document.createTextNode( \"\" ) );\r\n     *\r\n     *      //3\r\n     *      console.log( node.childNodes.length );\r\n     *\r\n     *      UE.dom.domUtils.trimWhiteTextNode( node );\r\n     *\r\n     *      //1\r\n     *      console.log( node.childNodes.length );\r\n     * ```\r\n     */\r\n    trimWhiteTextNode:function (node) {\r\n        function remove(dir) {\r\n            var child;\r\n            while ((child = node[dir]) && child.nodeType == 3 && domUtils.isWhitespace(child)) {\r\n                node.removeChild(child);\r\n            }\r\n        }\r\n        remove('firstChild');\r\n        remove('lastChild');\r\n    },\r\n\r\n    /**\r\n     * 合并node节点下相同的子节点\r\n     * @name mergeChild\r\n     * @desc\r\n     * UE.dom.domUtils.mergeChild(node,tagName) //tagName要合并的子节点的标签\r\n     * @example\r\n     * <p><span style=\"font-size:12px;\">xx<span style=\"font-size:12px;\">aa</span>xx</span></p>\r\n     * ==> UE.dom.domUtils.mergeChild(node,'span')\r\n     * <p><span style=\"font-size:12px;\">xxaaxx</span></p>\r\n     */\r\n    mergeChild:function (node, tagName, attrs) {\r\n        var list = domUtils.getElementsByTagName(node, node.tagName.toLowerCase());\r\n        for (var i = 0, ci; ci = list[i++];) {\r\n            if (!ci.parentNode || domUtils.isBookmarkNode(ci)) {\r\n                continue;\r\n            }\r\n            //span单独处理\r\n            if (ci.tagName.toLowerCase() == 'span') {\r\n                if (node === ci.parentNode) {\r\n                    domUtils.trimWhiteTextNode(node);\r\n                    if (node.childNodes.length == 1) {\r\n                        node.style.cssText = ci.style.cssText + \";\" + node.style.cssText;\r\n                        domUtils.remove(ci, true);\r\n                        continue;\r\n                    }\r\n                }\r\n                ci.style.cssText = node.style.cssText + ';' + ci.style.cssText;\r\n                if (attrs) {\r\n                    var style = attrs.style;\r\n                    if (style) {\r\n                        style = style.split(';');\r\n                        for (var j = 0, s; s = style[j++];) {\r\n                            ci.style[utils.cssStyleToDomStyle(s.split(':')[0])] = s.split(':')[1];\r\n                        }\r\n                    }\r\n                }\r\n                if (domUtils.isSameStyle(ci, node)) {\r\n                    domUtils.remove(ci, true);\r\n                }\r\n                continue;\r\n            }\r\n            if (domUtils.isSameElement(node, ci)) {\r\n                domUtils.remove(ci, true);\r\n            }\r\n        }\r\n    },\r\n\r\n    /**\r\n     * 原生方法getElementsByTagName的封装\r\n     * @method getElementsByTagName\r\n     * @param { Node } node 目标节点对象\r\n     * @param { String } tagName 需要查找的节点的tagName， 多个tagName以空格分割\r\n     * @return { Array } 符合条件的节点集合\r\n     */\r\n    getElementsByTagName:function (node, name,filter) {\r\n        if(filter && utils.isString(filter)){\r\n           var className = filter;\r\n           filter =  function(node){return domUtils.hasClass(node,className)}\r\n        }\r\n        name = utils.trim(name).replace(/[ ]{2,}/g,' ').split(' ');\r\n        var arr = [];\r\n        for(var n = 0,ni;ni=name[n++];){\r\n            var list = node.getElementsByTagName(ni);\r\n            for (var i = 0, ci; ci = list[i++];) {\r\n                if(!filter || filter(ci))\r\n                    arr.push(ci);\r\n            }\r\n        }\r\n\r\n        return arr;\r\n    },\r\n    /**\r\n     * 将节点node提取到父节点上\r\n     * @method mergeToParent\r\n     * @param { Element } node 需要提取的元素对象\r\n     * @example\r\n     * ```html\r\n     * <div id=\"parent\">\r\n     *     <div id=\"sub\">\r\n     *         <span id=\"child\"></span>\r\n     *     </div>\r\n     * </div>\r\n     *\r\n     * <script>\r\n     *\r\n     *     var child = document.getElementById( \"child\" );\r\n     *\r\n     *     //output: sub\r\n     *     console.log( child.parentNode.id );\r\n     *\r\n     *     UE.dom.domUtils.mergeToParent( child );\r\n     *\r\n     *     //output: parent\r\n     *     console.log( child.parentNode.id );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n    mergeToParent:function (node) {\r\n        var parent = node.parentNode;\r\n        while (parent && dtd.$removeEmpty[parent.tagName]) {\r\n            if (parent.tagName == node.tagName || parent.tagName == 'A') {//针对a标签单独处理\r\n                domUtils.trimWhiteTextNode(parent);\r\n                //span需要特殊处理  不处理这样的情况 <span stlye=\"color:#fff\">xxx<span style=\"color:#ccc\">xxx</span>xxx</span>\r\n                if (parent.tagName == 'SPAN' && !domUtils.isSameStyle(parent, node)\r\n                    || (parent.tagName == 'A' && node.tagName == 'SPAN')) {\r\n                    if (parent.childNodes.length > 1 || parent !== node.parentNode) {\r\n                        node.style.cssText = parent.style.cssText + \";\" + node.style.cssText;\r\n                        parent = parent.parentNode;\r\n                        continue;\r\n                    } else {\r\n                        parent.style.cssText += \";\" + node.style.cssText;\r\n                        //trace:952 a标签要保持下划线\r\n                        if (parent.tagName == 'A') {\r\n                            parent.style.textDecoration = 'underline';\r\n                        }\r\n                    }\r\n                }\r\n                if (parent.tagName != 'A') {\r\n                    parent === node.parentNode && domUtils.remove(node, true);\r\n                    break;\r\n                }\r\n            }\r\n            parent = parent.parentNode;\r\n        }\r\n    },\r\n    /**\r\n     * 合并节点node的左右兄弟节点\r\n     * @method mergeSibling\r\n     * @param { Element } node 需要合并的目标节点\r\n     * @example\r\n     * ```html\r\n     * <b>xxxx</b><b id=\"test\">ooo</b><b>xxxx</b>\r\n     *\r\n     * <script>\r\n     *     var demoNode = document.getElementById(\"test\");\r\n     *     UE.dom.domUtils.mergeSibling( demoNode );\r\n     *     //output: xxxxoooxxxx\r\n     *     console.log( demoNode.innerHTML );\r\n     * </script>\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 合并节点node的左右兄弟节点， 可以根据给定的条件选择是否忽略合并左节点。\r\n     * @method mergeSibling\r\n     * @param { Element } node 需要合并的目标节点\r\n     * @param { Boolean } ignorePre 是否忽略合并左节点\r\n     * @example\r\n     * ```html\r\n     * <b>xxxx</b><b id=\"test\">ooo</b><b>xxxx</b>\r\n     *\r\n     * <script>\r\n     *     var demoNode = document.getElementById(\"test\");\r\n     *     UE.dom.domUtils.mergeSibling( demoNode, true );\r\n     *     //output: oooxxxx\r\n     *     console.log( demoNode.innerHTML );\r\n     * </script>\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 合并节点node的左右兄弟节点，可以根据给定的条件选择是否忽略合并左右节点。\r\n     * @method mergeSibling\r\n     * @param { Element } node 需要合并的目标节点\r\n     * @param { Boolean } ignorePre 是否忽略合并左节点\r\n     * @param { Boolean } ignoreNext 是否忽略合并右节点\r\n     * @remind 如果同时忽略左右节点， 则该操作什么也不会做\r\n     * @example\r\n     * ```html\r\n     * <b>xxxx</b><b id=\"test\">ooo</b><b>xxxx</b>\r\n     *\r\n     * <script>\r\n     *     var demoNode = document.getElementById(\"test\");\r\n     *     UE.dom.domUtils.mergeSibling( demoNode, false, true );\r\n     *     //output: xxxxooo\r\n     *     console.log( demoNode.innerHTML );\r\n     * </script>\r\n     * ```\r\n     */\r\n    mergeSibling:function (node, ignorePre, ignoreNext) {\r\n        function merge(rtl, start, node) {\r\n            var next;\r\n            if ((next = node[rtl]) && !domUtils.isBookmarkNode(next) && next.nodeType == 1 && domUtils.isSameElement(node, next)) {\r\n                while (next.firstChild) {\r\n                    if (start == 'firstChild') {\r\n                        node.insertBefore(next.lastChild, node.firstChild);\r\n                    } else {\r\n                        node.appendChild(next.firstChild);\r\n                    }\r\n                }\r\n                domUtils.remove(next);\r\n            }\r\n        }\r\n        !ignorePre && merge('previousSibling', 'firstChild', node);\r\n        !ignoreNext && merge('nextSibling', 'lastChild', node);\r\n    },\r\n\r\n    /**\r\n     * 设置节点node及其子节点不会被选中\r\n     * @method unSelectable\r\n     * @param { Element } node 需要执行操作的dom元素\r\n     * @remind 执行该操作后的节点， 将不能被鼠标选中\r\n     * @example\r\n     * ```javascript\r\n     * UE.dom.domUtils.unSelectable( document.body );\r\n     * ```\r\n     */\r\n    unSelectable:ie && browser.ie9below || browser.opera ? function (node) {\r\n        //for ie9\r\n        node.onselectstart = function () {\r\n            return false;\r\n        };\r\n        node.onclick = node.onkeyup = node.onkeydown = function () {\r\n            return false;\r\n        };\r\n        node.unselectable = 'on';\r\n        node.setAttribute(\"unselectable\", \"on\");\r\n        for (var i = 0, ci; ci = node.all[i++];) {\r\n            switch (ci.tagName.toLowerCase()) {\r\n                case 'iframe' :\r\n                case 'textarea' :\r\n                case 'input' :\r\n                case 'select' :\r\n                    break;\r\n                default :\r\n                    ci.unselectable = 'on';\r\n                    node.setAttribute(\"unselectable\", \"on\");\r\n            }\r\n        }\r\n    } : function (node) {\r\n        node.style.MozUserSelect =\r\n            node.style.webkitUserSelect =\r\n                node.style.msUserSelect =\r\n                    node.style.KhtmlUserSelect = 'none';\r\n    },\r\n    /**\r\n     * 删除节点node上的指定属性名称的属性\r\n     * @method  removeAttributes\r\n     * @param { Node } node 需要删除属性的节点对象\r\n     * @param { String } attrNames 可以是空格隔开的多个属性名称，该操作将会依次删除相应的属性\r\n     * @example\r\n     * ```html\r\n     * <div id=\"wrap\">\r\n     *      <span style=\"font-size:14px;\" id=\"test\" name=\"followMe\">xxxxx</span>\r\n     * </div>\r\n     *\r\n     * <script>\r\n     *\r\n     *     UE.dom.domUtils.removeAttributes( document.getElementById( \"test\" ), \"id name\" );\r\n     *\r\n     *     //output: <span style=\"font-size:14px;\">xxxxx</span>\r\n     *     console.log( document.getElementById(\"wrap\").innerHTML );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 删除节点node上的指定属性名称的属性\r\n     * @method  removeAttributes\r\n     * @param { Node } node 需要删除属性的节点对象\r\n     * @param { Array } attrNames 需要删除的属性名数组\r\n     * @example\r\n     * ```html\r\n     * <div id=\"wrap\">\r\n     *      <span style=\"font-size:14px;\" id=\"test\" name=\"followMe\">xxxxx</span>\r\n     * </div>\r\n     *\r\n     * <script>\r\n     *\r\n     *     UE.dom.domUtils.removeAttributes( document.getElementById( \"test\" ), [\"id\", \"name\"] );\r\n     *\r\n     *     //output: <span style=\"font-size:14px;\">xxxxx</span>\r\n     *     console.log( document.getElementById(\"wrap\").innerHTML );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n    removeAttributes:function (node, attrNames) {\r\n        attrNames = utils.isArray(attrNames) ? attrNames : utils.trim(attrNames).replace(/[ ]{2,}/g,' ').split(' ');\r\n        for (var i = 0, ci; ci = attrNames[i++];) {\r\n            ci = attrFix[ci] || ci;\r\n            switch (ci) {\r\n                case 'className':\r\n                    node[ci] = '';\r\n                    break;\r\n                case 'style':\r\n                    node.style.cssText = '';\r\n                    var val = node.getAttributeNode('style');\r\n                    !browser.ie && val && node.removeAttributeNode(val);\r\n            }\r\n            node.removeAttribute(ci);\r\n        }\r\n    },\r\n    /**\r\n     * 在doc下创建一个标签名为tag，属性为attrs的元素\r\n     * @method createElement\r\n     * @param { DomDocument } doc 新创建的元素属于该document节点创建\r\n     * @param { String } tagName 需要创建的元素的标签名\r\n     * @param { Object } attrs 新创建的元素的属性key-value集合\r\n     * @return { Element } 新创建的元素对象\r\n     * @example\r\n     * ```javascript\r\n     * var ele = UE.dom.domUtils.createElement( document, 'div', {\r\n     *     id: 'test'\r\n     * } );\r\n     *\r\n     * //output: DIV\r\n     * console.log( ele.tagName );\r\n     *\r\n     * //output: test\r\n     * console.log( ele.id );\r\n     *\r\n     * ```\r\n     */\r\n    createElement:function (doc, tag, attrs) {\r\n        return domUtils.setAttributes(doc.createElement(tag), attrs)\r\n    },\r\n    /**\r\n     * 为节点node添加属性attrs，attrs为属性键值对\r\n     * @method setAttributes\r\n     * @param { Element } node 需要设置属性的元素对象\r\n     * @param { Object } attrs 需要设置的属性名-值对\r\n     * @return { Element } 设置属性的元素对象\r\n     * @example\r\n     * ```html\r\n     * <span id=\"test\"></span>\r\n     *\r\n     * <script>\r\n     *\r\n     *     var testNode = UE.dom.domUtils.setAttributes( document.getElementById( \"test\" ), {\r\n     *         id: 'demo'\r\n     *     } );\r\n     *\r\n     *     //output: demo\r\n     *     console.log( testNode.id );\r\n     *\r\n     * </script>\r\n     *\r\n     */\r\n    setAttributes:function (node, attrs) {\r\n        for (var attr in attrs) {\r\n            if(attrs.hasOwnProperty(attr)){\r\n                var value = attrs[attr];\r\n                switch (attr) {\r\n                    case 'class':\r\n                        //ie下要这样赋值，setAttribute不起作用\r\n                        node.className = value;\r\n                        break;\r\n                    case 'style' :\r\n                        node.style.cssText = node.style.cssText + \";\" + value;\r\n                        break;\r\n                    case 'innerHTML':\r\n                        node[attr] = value;\r\n                        break;\r\n                    case 'value':\r\n                        node.value = value;\r\n                        break;\r\n                    default:\r\n                        node.setAttribute(attrFix[attr] || attr, value);\r\n                }\r\n            }\r\n        }\r\n        return node;\r\n    },\r\n\r\n    /**\r\n     * 获取元素element经过计算后的样式值\r\n     * @method getComputedStyle\r\n     * @param { Element } element 需要获取样式的元素对象\r\n     * @param { String } styleName 需要获取的样式名\r\n     * @return { String } 获取到的样式值\r\n     * @example\r\n     * ```html\r\n     * <style type=\"text/css\">\r\n     *      #test {\r\n     *          font-size: 15px;\r\n     *      }\r\n     * </style>\r\n     *\r\n     * <span id=\"test\"></span>\r\n     *\r\n     * <script>\r\n     *     //output: 15px\r\n     *     console.log( UE.dom.domUtils.getComputedStyle( document.getElementById( \"test\" ), 'font-size' ) );\r\n     * </script>\r\n     * ```\r\n     */\r\n    getComputedStyle:function (element, styleName) {\r\n        //一下的属性单独处理\r\n        var pros = 'width height top left';\r\n\r\n        if(pros.indexOf(styleName) > -1){\r\n            return element['offset' + styleName.replace(/^\\w/,function(s){return s.toUpperCase()})] + 'px';\r\n        }\r\n        //忽略文本节点\r\n        if (element.nodeType == 3) {\r\n            element = element.parentNode;\r\n        }\r\n        //ie下font-size若body下定义了font-size，则从currentStyle里会取到这个font-size. 取不到实际值，故此修改.\r\n        if (browser.ie && browser.version < 9 && styleName == 'font-size' && !element.style.fontSize &&\r\n            !dtd.$empty[element.tagName] && !dtd.$nonChild[element.tagName]) {\r\n            var span = element.ownerDocument.createElement('span');\r\n            span.style.cssText = 'padding:0;border:0;font-family:simsun;';\r\n            span.innerHTML = '.';\r\n            element.appendChild(span);\r\n            var result = span.offsetHeight;\r\n            element.removeChild(span);\r\n            span = null;\r\n            return result + 'px';\r\n        }\r\n        try {\r\n            var value = domUtils.getStyle(element, styleName) ||\r\n                (window.getComputedStyle ? domUtils.getWindow(element).getComputedStyle(element, '').getPropertyValue(styleName) :\r\n                    ( element.currentStyle || element.style )[utils.cssStyleToDomStyle(styleName)]);\r\n\r\n        } catch (e) {\r\n            return \"\";\r\n        }\r\n        return utils.transUnitToPx(utils.fixColor(styleName, value));\r\n    },\r\n    /**\r\n     * 删除元素element指定的className\r\n     * @method removeClasses\r\n     * @param { Element } ele 需要删除class的元素节点\r\n     * @param { String } classNames 需要删除的className， 多个className之间以空格分开\r\n     * @example\r\n     * ```html\r\n     * <span id=\"test\" class=\"test1 test2 test3\">xxx</span>\r\n     *\r\n     * <script>\r\n     *\r\n     *     var testNode = document.getElementById( \"test\" );\r\n     *     UE.dom.domUtils.removeClasses( testNode, \"test1 test2\" );\r\n     *\r\n     *     //output: test3\r\n     *     console.log( testNode.className );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 删除元素element指定的className\r\n     * @method removeClasses\r\n     * @param { Element } ele 需要删除class的元素节点\r\n     * @param { Array } classNames 需要删除的className数组\r\n     * @example\r\n     * ```html\r\n     * <span id=\"test\" class=\"test1 test2 test3\">xxx</span>\r\n     *\r\n     * <script>\r\n     *\r\n     *     var testNode = document.getElementById( \"test\" );\r\n     *     UE.dom.domUtils.removeClasses( testNode, [\"test1\", \"test2\"] );\r\n     *\r\n     *     //output: test3\r\n     *     console.log( testNode.className );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n    removeClasses:function (elm, classNames) {\r\n        classNames = utils.isArray(classNames) ? classNames :\r\n            utils.trim(classNames).replace(/[ ]{2,}/g,' ').split(' ');\r\n        for(var i = 0,ci,cls = elm.className;ci=classNames[i++];){\r\n            cls = cls.replace(new RegExp('\\\\b' + ci + '\\\\b'),'')\r\n        }\r\n        cls = utils.trim(cls).replace(/[ ]{2,}/g,' ');\r\n        if(cls){\r\n            elm.className = cls;\r\n        }else{\r\n            domUtils.removeAttributes(elm,['class']);\r\n        }\r\n    },\r\n    /**\r\n     * 给元素element添加className\r\n     * @method addClass\r\n     * @param { Node } ele 需要增加className的元素\r\n     * @param { String } classNames 需要添加的className， 多个className之间以空格分割\r\n     * @remind 相同的类名不会被重复添加\r\n     * @example\r\n     * ```html\r\n     * <span id=\"test\" class=\"cls1 cls2\"></span>\r\n     *\r\n     * <script>\r\n     *     var testNode = document.getElementById(\"test\");\r\n     *\r\n     *     UE.dom.domUtils.addClass( testNode, \"cls2 cls3 cls4\" );\r\n     *\r\n     *     //output: cl1 cls2 cls3 cls4\r\n     *     console.log( testNode.className );\r\n     *\r\n     * <script>\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 给元素element添加className\r\n     * @method addClass\r\n     * @param { Node } ele 需要增加className的元素\r\n     * @param { Array } classNames 需要添加的className的数组\r\n     * @remind 相同的类名不会被重复添加\r\n     * @example\r\n     * ```html\r\n     * <span id=\"test\" class=\"cls1 cls2\"></span>\r\n     *\r\n     * <script>\r\n     *     var testNode = document.getElementById(\"test\");\r\n     *\r\n     *     UE.dom.domUtils.addClass( testNode, [\"cls2\", \"cls3\", \"cls4\"] );\r\n     *\r\n     *     //output: cl1 cls2 cls3 cls4\r\n     *     console.log( testNode.className );\r\n     *\r\n     * <script>\r\n     * ```\r\n     */\r\n    addClass:function (elm, classNames) {\r\n        if(!elm)return;\r\n        classNames = utils.trim(classNames).replace(/[ ]{2,}/g,' ').split(' ');\r\n        for(var i = 0,ci,cls = elm.className;ci=classNames[i++];){\r\n            if(!new RegExp('\\\\b' + ci + '\\\\b').test(cls)){\r\n                cls += ' ' + ci;\r\n            }\r\n        }\r\n        elm.className = utils.trim(cls);\r\n    },\r\n    /**\r\n     * 判断元素element是否包含给定的样式类名className\r\n     * @method hasClass\r\n     * @param { Node } ele 需要检测的元素\r\n     * @param { String } classNames 需要检测的className， 多个className之间用空格分割\r\n     * @return { Boolean } 元素是否包含所有给定的className\r\n     * @example\r\n     * ```html\r\n     * <span id=\"test1\" class=\"cls1 cls2\"></span>\r\n     *\r\n     * <script>\r\n     *     var test1 = document.getElementById(\"test1\");\r\n     *\r\n     *     //output: false\r\n     *     console.log( UE.dom.domUtils.hasClass( test1, \"cls2 cls1 cls3\" ) );\r\n     *\r\n     *     //output: true\r\n     *     console.log( UE.dom.domUtils.hasClass( test1, \"cls2 cls1\" ) );\r\n     * </script>\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 判断元素element是否包含给定的样式类名className\r\n     * @method hasClass\r\n     * @param { Node } ele 需要检测的元素\r\n     * @param { Array } classNames 需要检测的className数组\r\n     * @return { Boolean } 元素是否包含所有给定的className\r\n     * @example\r\n     * ```html\r\n     * <span id=\"test1\" class=\"cls1 cls2\"></span>\r\n     *\r\n     * <script>\r\n     *     var test1 = document.getElementById(\"test1\");\r\n     *\r\n     *     //output: false\r\n     *     console.log( UE.dom.domUtils.hasClass( test1, [ \"cls2\", \"cls1\", \"cls3\" ] ) );\r\n     *\r\n     *     //output: true\r\n     *     console.log( UE.dom.domUtils.hasClass( test1, [ \"cls2\", \"cls1\" ]) );\r\n     * </script>\r\n     * ```\r\n     */\r\n    hasClass:function (element, className) {\r\n        if(utils.isRegExp(className)){\r\n            return className.test(element.className)\r\n        }\r\n        className = utils.trim(className).replace(/[ ]{2,}/g,' ').split(' ');\r\n        for(var i = 0,ci,cls = element.className;ci=className[i++];){\r\n            if(!new RegExp('\\\\b' + ci + '\\\\b','i').test(cls)){\r\n                return false;\r\n            }\r\n        }\r\n        return i - 1 == className.length;\r\n    },\r\n\r\n    /**\r\n     * 阻止事件默认行为\r\n     * @method preventDefault\r\n     * @param { Event } evt 需要阻止默认行为的事件对象\r\n     * @example\r\n     * ```javascript\r\n     * UE.dom.domUtils.preventDefault( evt );\r\n     * ```\r\n     */\r\n    preventDefault:function (evt) {\r\n        evt.preventDefault ? evt.preventDefault() : (evt.returnValue = false);\r\n    },\r\n    /**\r\n     * 删除元素element指定的样式\r\n     * @method removeStyle\r\n     * @param { Element } element 需要删除样式的元素\r\n     * @param { String } styleName 需要删除的样式名\r\n     * @example\r\n     * ```html\r\n     * <span id=\"test\" style=\"color: red; background: blue;\"></span>\r\n     *\r\n     * <script>\r\n     *\r\n     *     var testNode = document.getElementById(\"test\");\r\n     *\r\n     *     UE.dom.domUtils.removeStyle( testNode, 'color' );\r\n     *\r\n     *     //output: background: blue;\r\n     *     console.log( testNode.style.cssText );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n    removeStyle:function (element, name) {\r\n        if(browser.ie ){\r\n            //针对color先单独处理一下\r\n            if(name == 'color'){\r\n                name = '(^|;)' + name;\r\n            }\r\n            element.style.cssText = element.style.cssText.replace(new RegExp(name + '[^:]*:[^;]+;?','ig'),'')\r\n        }else{\r\n            if (element.style.removeProperty) {\r\n                element.style.removeProperty (name);\r\n            }else {\r\n                element.style.removeAttribute (utils.cssStyleToDomStyle(name));\r\n            }\r\n        }\r\n\r\n\r\n        if (!element.style.cssText) {\r\n            domUtils.removeAttributes(element, ['style']);\r\n        }\r\n    },\r\n    /**\r\n     * 获取元素element的style属性的指定值\r\n     * @method getStyle\r\n     * @param { Element } element 需要获取属性值的元素\r\n     * @param { String } styleName 需要获取的style的名称\r\n     * @warning 该方法仅获取元素style属性中所标明的值\r\n     * @return { String } 该元素包含指定的style属性值\r\n     * @example\r\n     * ```html\r\n     * <div id=\"test\" style=\"color: red;\"></div>\r\n     *\r\n     * <script>\r\n     *\r\n     *      var testNode = document.getElementById( \"test\" );\r\n     *\r\n     *      //output: red\r\n     *      console.log( UE.dom.domUtils.getStyle( testNode, \"color\" ) );\r\n     *\r\n     *      //output: \"\"\r\n     *      console.log( UE.dom.domUtils.getStyle( testNode, \"background\" ) );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n    getStyle:function (element, name) {\r\n        var value = element.style[ utils.cssStyleToDomStyle(name) ];\r\n        return utils.fixColor(name, value);\r\n    },\r\n    /**\r\n     * 为元素element设置样式属性值\r\n     * @method setStyle\r\n     * @param { Element } element 需要设置样式的元素\r\n     * @param { String } styleName 样式名\r\n     * @param { String } styleValue 样式值\r\n     * @example\r\n     * ```html\r\n     * <div id=\"test\"></div>\r\n     *\r\n     * <script>\r\n     *\r\n     *      var testNode = document.getElementById( \"test\" );\r\n     *\r\n     *      //output: \"\"\r\n     *      console.log( testNode.style.color );\r\n     *\r\n     *      UE.dom.domUtils.setStyle( testNode, 'color', 'red' );\r\n     *      //output: \"red\"\r\n     *      console.log( testNode.style.color );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n    setStyle:function (element, name, value) {\r\n        element.style[utils.cssStyleToDomStyle(name)] = value;\r\n        if(!utils.trim(element.style.cssText)){\r\n            this.removeAttributes(element,'style')\r\n        }\r\n    },\r\n    /**\r\n     * 为元素element设置多个样式属性值\r\n     * @method setStyles\r\n     * @param { Element } element 需要设置样式的元素\r\n     * @param { Object } styles 样式名值对\r\n     * @example\r\n     * ```html\r\n     * <div id=\"test\"></div>\r\n     *\r\n     * <script>\r\n     *\r\n     *      var testNode = document.getElementById( \"test\" );\r\n     *\r\n     *      //output: \"\"\r\n     *      console.log( testNode.style.color );\r\n     *\r\n     *      UE.dom.domUtils.setStyles( testNode, {\r\n     *          'color': 'red'\r\n     *      } );\r\n     *      //output: \"red\"\r\n     *      console.log( testNode.style.color );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n    setStyles:function (element, styles) {\r\n        for (var name in styles) {\r\n            if (styles.hasOwnProperty(name)) {\r\n                domUtils.setStyle(element, name, styles[name]);\r\n            }\r\n        }\r\n    },\r\n    /**\r\n     * 删除_moz_dirty属性\r\n     * @private\r\n     * @method removeDirtyAttr\r\n     */\r\n    removeDirtyAttr:function (node) {\r\n        for (var i = 0, ci, nodes = node.getElementsByTagName('*'); ci = nodes[i++];) {\r\n            ci.removeAttribute('_moz_dirty');\r\n        }\r\n        node.removeAttribute('_moz_dirty');\r\n    },\r\n    /**\r\n     * 获取子节点的数量\r\n     * @method getChildCount\r\n     * @param { Element } node 需要检测的元素\r\n     * @return { Number } 给定的node元素的子节点数量\r\n     * @example\r\n     * ```html\r\n     * <div id=\"test\">\r\n     *      <span></span>\r\n     * </div>\r\n     *\r\n     * <script>\r\n     *\r\n     *     //output: 3\r\n     *     console.log( UE.dom.domUtils.getChildCount( document.getElementById(\"test\") ) );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 根据给定的过滤规则， 获取符合条件的子节点的数量\r\n     * @method getChildCount\r\n     * @param { Element } node 需要检测的元素\r\n     * @param { Function } fn 过滤器， 要求对符合条件的子节点返回true， 反之则要求返回false\r\n     * @return { Number } 符合过滤条件的node元素的子节点数量\r\n     * @example\r\n     * ```html\r\n     * <div id=\"test\">\r\n     *      <span></span>\r\n     * </div>\r\n     *\r\n     * <script>\r\n     *\r\n     *     //output: 1\r\n     *     console.log( UE.dom.domUtils.getChildCount( document.getElementById(\"test\"), function ( node ) {\r\n     *\r\n     *         return node.nodeType === 1;\r\n     *\r\n     *     } ) );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n    getChildCount:function (node, fn) {\r\n        var count = 0, first = node.firstChild;\r\n        fn = fn || function () {\r\n            return 1;\r\n        };\r\n        while (first) {\r\n            if (fn(first)) {\r\n                count++;\r\n            }\r\n            first = first.nextSibling;\r\n        }\r\n        return count;\r\n    },\r\n\r\n    /**\r\n     * 判断给定节点是否为空节点\r\n     * @method isEmptyNode\r\n     * @param { Node } node 需要检测的节点对象\r\n     * @return { Boolean } 节点是否为空\r\n     * @example\r\n     * ```javascript\r\n     * UE.dom.domUtils.isEmptyNode( document.body );\r\n     * ```\r\n     */\r\n    isEmptyNode:function (node) {\r\n        return !node.firstChild || domUtils.getChildCount(node, function (node) {\r\n            return  !domUtils.isBr(node) && !domUtils.isBookmarkNode(node) && !domUtils.isWhitespace(node)\r\n        }) == 0\r\n    },\r\n    clearSelectedArr:function (nodes) {\r\n        var node;\r\n        while (node = nodes.pop()) {\r\n            domUtils.removeAttributes(node, ['class']);\r\n        }\r\n    },\r\n    /**\r\n     * 将显示区域滚动到指定节点的位置\r\n     * @method scrollToView\r\n     * @param    {Node}   node    节点\r\n     * @param    {window}   win      window对象\r\n     * @param    {Number}    offsetTop    距离上方的偏移量\r\n     */\r\n    scrollToView:function (node, win, offsetTop) {\r\n        var getViewPaneSize = function () {\r\n                var doc = win.document,\r\n                    mode = doc.compatMode == 'CSS1Compat';\r\n                return {\r\n                    width:( mode ? doc.documentElement.clientWidth : doc.body.clientWidth ) || 0,\r\n                    height:( mode ? doc.documentElement.clientHeight : doc.body.clientHeight ) || 0\r\n                };\r\n            },\r\n            getScrollPosition = function (win) {\r\n                if ('pageXOffset' in win) {\r\n                    return {\r\n                        x:win.pageXOffset || 0,\r\n                        y:win.pageYOffset || 0\r\n                    };\r\n                }\r\n                else {\r\n                    var doc = win.document;\r\n                    return {\r\n                        x:doc.documentElement.scrollLeft || doc.body.scrollLeft || 0,\r\n                        y:doc.documentElement.scrollTop || doc.body.scrollTop || 0\r\n                    };\r\n                }\r\n            };\r\n        var winHeight = getViewPaneSize().height, offset = winHeight * -1 + offsetTop;\r\n        offset += (node.offsetHeight || 0);\r\n        var elementPosition = domUtils.getXY(node);\r\n        offset += elementPosition.y;\r\n        var currentScroll = getScrollPosition(win).y;\r\n        // offset += 50;\r\n        if (offset > currentScroll || offset < currentScroll - winHeight) {\r\n            win.scrollTo(0, offset + (offset < 0 ? -20 : 20));\r\n        }\r\n    },\r\n    /**\r\n     * 判断给定节点是否为br\r\n     * @method isBr\r\n     * @param { Node } node 需要判断的节点对象\r\n     * @return { Boolean } 给定的节点是否是br节点\r\n     */\r\n    isBr:function (node) {\r\n        return node.nodeType == 1 && node.tagName == 'BR';\r\n    },\r\n    /**\r\n     * 判断给定的节点是否是一个“填充”节点\r\n     * @private\r\n     * @method isFillChar\r\n     * @param { Node } node 需要判断的节点\r\n     * @param { Boolean } isInStart 是否从节点内容的开始位置匹配\r\n     * @returns { Boolean } 节点是否是填充节点\r\n     */\r\n    isFillChar:function (node,isInStart) {\r\n        if(node.nodeType != 3)\r\n            return false;\r\n        var text = node.nodeValue;\r\n        if(isInStart){\r\n            return new RegExp('^' + domUtils.fillChar).test(text)\r\n        }\r\n        return !text.replace(new RegExp(domUtils.fillChar,'g'), '').length\r\n    },\r\n    isStartInblock:function (range) {\r\n        var tmpRange = range.cloneRange(),\r\n            flag = 0,\r\n            start = tmpRange.startContainer,\r\n            tmp;\r\n        if(start.nodeType == 1 && start.childNodes[tmpRange.startOffset]){\r\n            start = start.childNodes[tmpRange.startOffset];\r\n            var pre = start.previousSibling;\r\n            while(pre && domUtils.isFillChar(pre)){\r\n                start = pre;\r\n                pre = pre.previousSibling;\r\n            }\r\n        }\r\n        if(this.isFillChar(start,true) && tmpRange.startOffset == 1){\r\n            tmpRange.setStartBefore(start);\r\n            start = tmpRange.startContainer;\r\n        }\r\n\r\n        while (start && domUtils.isFillChar(start)) {\r\n            tmp = start;\r\n            start = start.previousSibling\r\n        }\r\n        if (tmp) {\r\n            tmpRange.setStartBefore(tmp);\r\n            start = tmpRange.startContainer;\r\n        }\r\n        if (start.nodeType == 1 && domUtils.isEmptyNode(start) && tmpRange.startOffset == 1) {\r\n            tmpRange.setStart(start, 0).collapse(true);\r\n        }\r\n        while (!tmpRange.startOffset) {\r\n            start = tmpRange.startContainer;\r\n            if (domUtils.isBlockElm(start) || domUtils.isBody(start)) {\r\n                flag = 1;\r\n                break;\r\n            }\r\n            var pre = tmpRange.startContainer.previousSibling,\r\n                tmpNode;\r\n            if (!pre) {\r\n                tmpRange.setStartBefore(tmpRange.startContainer);\r\n            } else {\r\n                while (pre && domUtils.isFillChar(pre)) {\r\n                    tmpNode = pre;\r\n                    pre = pre.previousSibling;\r\n                }\r\n                if (tmpNode) {\r\n                    tmpRange.setStartBefore(tmpNode);\r\n                } else {\r\n                    tmpRange.setStartBefore(tmpRange.startContainer);\r\n                }\r\n            }\r\n        }\r\n        return flag && !domUtils.isBody(tmpRange.startContainer) ? 1 : 0;\r\n    },\r\n\r\n    /**\r\n     * 判断给定的元素是否是一个空元素\r\n     * @method isEmptyBlock\r\n     * @param { Element } node 需要判断的元素\r\n     * @return { Boolean } 是否是空元素\r\n     * @example\r\n     * ```html\r\n     * <div id=\"test\"></div>\r\n     *\r\n     * <script>\r\n     *     //output: true\r\n     *     console.log( UE.dom.domUtils.isEmptyBlock( document.getElementById(\"test\") ) );\r\n     * </script>\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 根据指定的判断规则判断给定的元素是否是一个空元素\r\n     * @method isEmptyBlock\r\n     * @param { Element } node 需要判断的元素\r\n     * @param { RegExp } reg 对内容执行判断的正则表达式对象\r\n     * @return { Boolean } 是否是空元素\r\n     */\r\n    isEmptyBlock:function (node,reg) {\r\n        // HaoChuan9421\r\n        if(!node){\r\n            return;\r\n        }\r\n        if(node.nodeType != 1)\r\n            return 0;\r\n        reg = reg || new RegExp('[ \\xa0\\t\\r\\n' + domUtils.fillChar + ']', 'g');\r\n\r\n        if (node[browser.ie ? 'innerText' : 'textContent'].replace(reg, '').length > 0) {\r\n            return 0;\r\n        }\r\n        for (var n in dtd.$isNotEmpty) {\r\n            if (node.getElementsByTagName(n).length) {\r\n                return 0;\r\n            }\r\n        }\r\n        return 1;\r\n    },\r\n\r\n    /**\r\n     * 移动元素使得该元素的位置移动指定的偏移量的距离\r\n     * @method setViewportOffset\r\n     * @param { Element } element 需要设置偏移量的元素\r\n     * @param { Object } offset 偏移量， 形如{ left: 100, top: 50 }的一个键值对， 表示该元素将在\r\n     *                                  现有的位置上向水平方向偏移offset.left的距离， 在竖直方向上偏移\r\n     *                                  offset.top的距离\r\n     * @example\r\n     * ```html\r\n     * <div id=\"test\" style=\"top: 100px; left: 50px; position: absolute;\"></div>\r\n     *\r\n     * <script>\r\n     *\r\n     *     var testNode = document.getElementById(\"test\");\r\n     *\r\n     *     UE.dom.domUtils.setViewportOffset( testNode, {\r\n     *         left: 200,\r\n     *         top: 50\r\n     *     } );\r\n     *\r\n     *     //output: top: 300px; left: 100px; position: absolute;\r\n     *     console.log( testNode.style.cssText );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n    setViewportOffset:function (element, offset) {\r\n        var left = parseInt(element.style.left) | 0;\r\n        var top = parseInt(element.style.top) | 0;\r\n        var rect = element.getBoundingClientRect();\r\n        var offsetLeft = offset.left - rect.left;\r\n        var offsetTop = offset.top - rect.top;\r\n        if (offsetLeft) {\r\n            element.style.left = left + offsetLeft + 'px';\r\n        }\r\n        if (offsetTop) {\r\n            element.style.top = top + offsetTop + 'px';\r\n        }\r\n    },\r\n\r\n    /**\r\n     * 用“填充字符”填充节点\r\n     * @method fillNode\r\n     * @private\r\n     * @param { DomDocument } doc 填充的节点所在的docment对象\r\n     * @param { Node } node 需要填充的节点对象\r\n     * @example\r\n     * ```html\r\n     * <div id=\"test\"></div>\r\n     *\r\n     * <script>\r\n     *     var testNode = document.getElementById(\"test\");\r\n     *\r\n     *     //output: 0\r\n     *     console.log( testNode.childNodes.length );\r\n     *\r\n     *     UE.dom.domUtils.fillNode( document, testNode );\r\n     *\r\n     *     //output: 1\r\n     *     console.log( testNode.childNodes.length );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n    fillNode:function (doc, node) {\r\n        var tmpNode = browser.ie ? doc.createTextNode(domUtils.fillChar) : doc.createElement('br');\r\n        node.innerHTML = '';\r\n        node.appendChild(tmpNode);\r\n    },\r\n\r\n    /**\r\n     * 把节点src的所有子节点追加到另一个节点tag上去\r\n     * @method moveChild\r\n     * @param { Node } src 源节点， 该节点下的所有子节点将被移除\r\n     * @param { Node } tag 目标节点， 从源节点移除的子节点将被追加到该节点下\r\n     * @example\r\n     * ```html\r\n     * <div id=\"test1\">\r\n     *      <span></span>\r\n     * </div>\r\n     * <div id=\"test2\">\r\n     *     <div></div>\r\n     * </div>\r\n     *\r\n     * <script>\r\n     *\r\n     *     var test1 = document.getElementById(\"test1\"),\r\n     *         test2 = document.getElementById(\"test2\");\r\n     *\r\n     *     UE.dom.domUtils.moveChild( test1, test2 );\r\n     *\r\n     *     //output: \"\"（空字符串）\r\n     *     console.log( test1.innerHTML );\r\n     *\r\n     *     //output: \"<div></div><span></span>\"\r\n     *     console.log( test2.innerHTML );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 把节点src的所有子节点移动到另一个节点tag上去, 可以通过dir参数控制附加的行为是“追加”还是“插入顶部”\r\n     * @method moveChild\r\n     * @param { Node } src 源节点， 该节点下的所有子节点将被移除\r\n     * @param { Node } tag 目标节点， 从源节点移除的子节点将被附加到该节点下\r\n     * @param { Boolean } dir 附加方式， 如果为true， 则附加进去的节点将被放到目标节点的顶部， 反之，则放到末尾\r\n     * @example\r\n     * ```html\r\n     * <div id=\"test1\">\r\n     *      <span></span>\r\n     * </div>\r\n     * <div id=\"test2\">\r\n     *     <div></div>\r\n     * </div>\r\n     *\r\n     * <script>\r\n     *\r\n     *     var test1 = document.getElementById(\"test1\"),\r\n     *         test2 = document.getElementById(\"test2\");\r\n     *\r\n     *     UE.dom.domUtils.moveChild( test1, test2, true );\r\n     *\r\n     *     //output: \"\"（空字符串）\r\n     *     console.log( test1.innerHTML );\r\n     *\r\n     *     //output: \"<span></span><div></div>\"\r\n     *     console.log( test2.innerHTML );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n    moveChild:function (src, tag, dir) {\r\n        while (src.firstChild) {\r\n            if (dir && tag.firstChild) {\r\n                tag.insertBefore(src.lastChild, tag.firstChild);\r\n            } else {\r\n                tag.appendChild(src.firstChild);\r\n            }\r\n        }\r\n    },\r\n\r\n    /**\r\n     * 判断节点的标签上是否不存在任何属性\r\n     * @method hasNoAttributes\r\n     * @private\r\n     * @param { Node } node 需要检测的节点对象\r\n     * @return { Boolean } 节点是否不包含任何属性\r\n     * @example\r\n     * ```html\r\n     * <div id=\"test\"><span>xxxx</span></div>\r\n     *\r\n     * <script>\r\n     *\r\n     *     //output: false\r\n     *     console.log( UE.dom.domUtils.hasNoAttributes( document.getElementById(\"test\") ) );\r\n     *\r\n     *     //output: true\r\n     *     console.log( UE.dom.domUtils.hasNoAttributes( document.getElementById(\"test\").firstChild ) );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n    hasNoAttributes:function (node) {\r\n        return browser.ie ? /^<\\w+\\s*?>/.test(node.outerHTML) : node.attributes.length == 0;\r\n    },\r\n\r\n    /**\r\n     * 检测节点是否是UEditor所使用的辅助节点\r\n     * @method isCustomeNode\r\n     * @private\r\n     * @param { Node } node 需要检测的节点\r\n     * @remind 辅助节点是指编辑器要完成工作临时添加的节点， 在输出的时候将会从编辑器内移除， 不会影响最终的结果。\r\n     * @return { Boolean } 给定的节点是否是一个辅助节点\r\n     */\r\n    isCustomeNode:function (node) {\r\n        return node.nodeType == 1 && node.getAttribute('_ue_custom_node_');\r\n    },\r\n\r\n    /**\r\n     * 检测节点的标签是否是给定的标签\r\n     * @method isTagNode\r\n     * @param { Node } node 需要检测的节点对象\r\n     * @param { String } tagName 标签\r\n     * @return { Boolean } 节点的标签是否是给定的标签\r\n     * @example\r\n     * ```html\r\n     * <div id=\"test\"></div>\r\n     *\r\n     * <script>\r\n     *\r\n     *     //output: true\r\n     *     console.log( UE.dom.domUtils.isTagNode( document.getElementById(\"test\"), \"div\" ) );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n    isTagNode:function (node, tagNames) {\r\n        return node.nodeType == 1 && new RegExp('\\\\b' + node.tagName + '\\\\b','i').test(tagNames)\r\n    },\r\n\r\n    /**\r\n     * 给定一个节点数组，在通过指定的过滤器过滤后， 获取其中满足过滤条件的第一个节点\r\n     * @method filterNodeList\r\n     * @param { Array } nodeList 需要过滤的节点数组\r\n     * @param { Function } fn 过滤器， 对符合条件的节点， 执行结果返回true， 反之则返回false\r\n     * @return { Node | NULL } 如果找到符合过滤条件的节点， 则返回该节点， 否则返回NULL\r\n     * @example\r\n     * ```javascript\r\n     * var divNodes = document.getElementsByTagName(\"div\");\r\n     * divNodes = [].slice.call( divNodes, 0 );\r\n     *\r\n     * //output: null\r\n     * console.log( UE.dom.domUtils.filterNodeList( divNodes, function ( node ) {\r\n     *     return node.tagName.toLowerCase() !== 'div';\r\n     * } ) );\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 给定一个节点数组nodeList和一组标签名tagNames， 获取其中能够匹配标签名的节点集合中的第一个节点\r\n     * @method filterNodeList\r\n     * @param { Array } nodeList 需要过滤的节点数组\r\n     * @param { String } tagNames 需要匹配的标签名， 多个标签名之间用空格分割\r\n     * @return { Node | NULL } 如果找到标签名匹配的节点， 则返回该节点， 否则返回NULL\r\n     * @example\r\n     * ```javascript\r\n     * var divNodes = document.getElementsByTagName(\"div\");\r\n     * divNodes = [].slice.call( divNodes, 0 );\r\n     *\r\n     * //output: null\r\n     * console.log( UE.dom.domUtils.filterNodeList( divNodes, 'a span' ) );\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 给定一个节点数组，在通过指定的过滤器过滤后， 如果参数forAll为true， 则会返回所有满足过滤\r\n     * 条件的节点集合， 否则， 返回满足条件的节点集合中的第一个节点\r\n     * @method filterNodeList\r\n     * @param { Array } nodeList 需要过滤的节点数组\r\n     * @param { Function } fn 过滤器， 对符合条件的节点， 执行结果返回true， 反之则返回false\r\n     * @param { Boolean } forAll 是否返回整个节点数组, 如果该参数为false， 则返回节点集合中的第一个节点\r\n     * @return { Array | Node | NULL } 如果找到符合过滤条件的节点， 则根据参数forAll的值决定返回满足\r\n     *                                      过滤条件的节点数组或第一个节点， 否则返回NULL\r\n     * @example\r\n     * ```javascript\r\n     * var divNodes = document.getElementsByTagName(\"div\");\r\n     * divNodes = [].slice.call( divNodes, 0 );\r\n     *\r\n     * //output: 3（假定有3个div）\r\n     * console.log( divNodes.length );\r\n     *\r\n     * var nodes = UE.dom.domUtils.filterNodeList( divNodes, function ( node ) {\r\n     *     return node.tagName.toLowerCase() === 'div';\r\n     * }, true );\r\n     *\r\n     * //output: 3\r\n     * console.log( nodes.length );\r\n     *\r\n     * var node = UE.dom.domUtils.filterNodeList( divNodes, function ( node ) {\r\n     *     return node.tagName.toLowerCase() === 'div';\r\n     * }, false );\r\n     *\r\n     * //output: div\r\n     * console.log( node.nodeName );\r\n     * ```\r\n     */\r\n    filterNodeList : function(nodelist,filter,forAll){\r\n        var results = [];\r\n        if(!utils .isFunction(filter)){\r\n            var str = filter;\r\n            filter = function(n){\r\n                return utils.indexOf(utils.isArray(str) ? str:str.split(' '), n.tagName.toLowerCase()) != -1\r\n            };\r\n        }\r\n        utils.each(nodelist,function(n){\r\n            filter(n) && results.push(n)\r\n        });\r\n        return results.length  == 0 ? null : results.length == 1 || !forAll ? results[0] : results\r\n    },\r\n\r\n    /**\r\n     * 查询给定的range选区是否在给定的node节点内，且在该节点的最末尾\r\n     * @method isInNodeEndBoundary\r\n     * @param { UE.dom.Range } rng 需要判断的range对象， 该对象的startContainer不能为NULL\r\n     * @param node 需要检测的节点对象\r\n     * @return { Number } 如果给定的选取range对象是在node内部的最末端， 则返回1, 否则返回0\r\n     */\r\n    isInNodeEndBoundary : function (rng,node){\r\n        var start = rng.startContainer;\r\n        if(start.nodeType == 3 && rng.startOffset != start.nodeValue.length){\r\n            return 0;\r\n        }\r\n        if(start.nodeType == 1 && rng.startOffset != start.childNodes.length){\r\n            return 0;\r\n        }\r\n        while(start !== node){\r\n            if(start.nextSibling){\r\n                return 0\r\n            };\r\n            start = start.parentNode;\r\n        }\r\n        return 1;\r\n    },\r\n    isBoundaryNode : function (node,dir){\r\n        var tmp;\r\n        while(!domUtils.isBody(node)){\r\n            tmp = node;\r\n            node = node.parentNode;\r\n            if(tmp !== node[dir]){\r\n                return false;\r\n            }\r\n        }\r\n        return true;\r\n    },\r\n    fillHtml :  browser.ie11below ? '&nbsp;' : '<br/>'\r\n};\r\nvar fillCharReg = new RegExp(domUtils.fillChar, 'g');\r\n\r\n// core/Range.js\r\n/**\r\n * Range封装\r\n * @file\r\n * @module UE.dom\r\n * @class Range\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * dom操作封装\r\n * @unfile\r\n * @module UE.dom\r\n */\r\n\r\n/**\r\n * Range实现类，本类是UEditor底层核心类，封装不同浏览器之间的Range操作。\r\n * @unfile\r\n * @module UE.dom\r\n * @class Range\r\n */\r\n\r\n\r\n(function () {\r\n    var guid = 0,\r\n        fillChar = domUtils.fillChar,\r\n        fillData;\r\n\r\n    /**\r\n     * 更新range的collapse状态\r\n     * @param  {Range}   range    range对象\r\n     */\r\n    function updateCollapse(range) {\r\n        range.collapsed =\r\n            range.startContainer && range.endContainer &&\r\n                range.startContainer === range.endContainer &&\r\n                range.startOffset == range.endOffset;\r\n    }\r\n\r\n    function selectOneNode(rng){\r\n        return !rng.collapsed && rng.startContainer.nodeType == 1 && rng.startContainer === rng.endContainer && rng.endOffset - rng.startOffset == 1\r\n    }\r\n    function setEndPoint(toStart, node, offset, range) {\r\n        //如果node是自闭合标签要处理\r\n        if (node.nodeType == 1 && (dtd.$empty[node.tagName] || dtd.$nonChild[node.tagName])) {\r\n            offset = domUtils.getNodeIndex(node) + (toStart ? 0 : 1);\r\n            node = node.parentNode;\r\n        }\r\n        if (toStart) {\r\n            range.startContainer = node;\r\n            range.startOffset = offset;\r\n            if (!range.endContainer) {\r\n                range.collapse(true);\r\n            }\r\n        } else {\r\n            range.endContainer = node;\r\n            range.endOffset = offset;\r\n            if (!range.startContainer) {\r\n                range.collapse(false);\r\n            }\r\n        }\r\n        updateCollapse(range);\r\n        return range;\r\n    }\r\n\r\n    function execContentsAction(range, action) {\r\n        //调整边界\r\n        //range.includeBookmark();\r\n        var start = range.startContainer,\r\n            end = range.endContainer,\r\n            startOffset = range.startOffset,\r\n            endOffset = range.endOffset,\r\n            doc = range.document,\r\n            frag = doc.createDocumentFragment(),\r\n            tmpStart, tmpEnd;\r\n        if (start.nodeType == 1) {\r\n            start = start.childNodes[startOffset] || (tmpStart = start.appendChild(doc.createTextNode('')));\r\n        }\r\n        if (end.nodeType == 1) {\r\n            end = end.childNodes[endOffset] || (tmpEnd = end.appendChild(doc.createTextNode('')));\r\n        }\r\n        if (start === end && start.nodeType == 3) {\r\n            frag.appendChild(doc.createTextNode(start.substringData(startOffset, endOffset - startOffset)));\r\n            //is not clone\r\n            if (action) {\r\n                start.deleteData(startOffset, endOffset - startOffset);\r\n                range.collapse(true);\r\n            }\r\n            return frag;\r\n        }\r\n        var current, currentLevel, clone = frag,\r\n            startParents = domUtils.findParents(start, true), endParents = domUtils.findParents(end, true);\r\n        for (var i = 0; startParents[i] == endParents[i];) {\r\n            i++;\r\n        }\r\n        for (var j = i, si; si = startParents[j]; j++) {\r\n            current = si.nextSibling;\r\n            if (si == start) {\r\n                if (!tmpStart) {\r\n                    if (range.startContainer.nodeType == 3) {\r\n                        clone.appendChild(doc.createTextNode(start.nodeValue.slice(startOffset)));\r\n                        //is not clone\r\n                        if (action) {\r\n                            start.deleteData(startOffset, start.nodeValue.length - startOffset);\r\n                        }\r\n                    } else {\r\n                        clone.appendChild(!action ? start.cloneNode(true) : start);\r\n                    }\r\n                }\r\n            } else {\r\n                currentLevel = si.cloneNode(false);\r\n                clone.appendChild(currentLevel);\r\n            }\r\n            while (current) {\r\n                if (current === end || current === endParents[j]) {\r\n                    break;\r\n                }\r\n                si = current.nextSibling;\r\n                clone.appendChild(!action ? current.cloneNode(true) : current);\r\n                current = si;\r\n            }\r\n            clone = currentLevel;\r\n        }\r\n        clone = frag;\r\n        if (!startParents[i]) {\r\n            clone.appendChild(startParents[i - 1].cloneNode(false));\r\n            clone = clone.firstChild;\r\n        }\r\n        for (var j = i, ei; ei = endParents[j]; j++) {\r\n            current = ei.previousSibling;\r\n            if (ei == end) {\r\n                if (!tmpEnd && range.endContainer.nodeType == 3) {\r\n                    clone.appendChild(doc.createTextNode(end.substringData(0, endOffset)));\r\n                    //is not clone\r\n                    if (action) {\r\n                        end.deleteData(0, endOffset);\r\n                    }\r\n                }\r\n            } else {\r\n                currentLevel = ei.cloneNode(false);\r\n                clone.appendChild(currentLevel);\r\n            }\r\n            //如果两端同级，右边第一次已经被开始做了\r\n            if (j != i || !startParents[i]) {\r\n                while (current) {\r\n                    if (current === start) {\r\n                        break;\r\n                    }\r\n                    ei = current.previousSibling;\r\n                    clone.insertBefore(!action ? current.cloneNode(true) : current, clone.firstChild);\r\n                    current = ei;\r\n                }\r\n            }\r\n            clone = currentLevel;\r\n        }\r\n        if (action) {\r\n            range.setStartBefore(!endParents[i] ? endParents[i - 1] : !startParents[i] ? startParents[i - 1] : endParents[i]).collapse(true);\r\n        }\r\n        tmpStart && domUtils.remove(tmpStart);\r\n        tmpEnd && domUtils.remove(tmpEnd);\r\n        return frag;\r\n    }\r\n\r\n    /**\r\n     * 创建一个跟document绑定的空的Range实例\r\n     * @constructor\r\n     * @param { Document } document 新建的选区所属的文档对象\r\n     */\r\n\r\n    /**\r\n     * @property { Node } startContainer 当前Range的开始边界的容器节点, 可以是一个元素节点或者是文本节点\r\n     */\r\n\r\n    /**\r\n     * @property { Node } startOffset 当前Range的开始边界容器节点的偏移量, 如果是元素节点，\r\n     *                              该值就是childNodes中的第几个节点， 如果是文本节点就是文本内容的第几个字符\r\n     */\r\n\r\n    /**\r\n     * @property { Node } endContainer 当前Range的结束边界的容器节点, 可以是一个元素节点或者是文本节点\r\n     */\r\n\r\n    /**\r\n     * @property { Node } endOffset 当前Range的结束边界容器节点的偏移量, 如果是元素节点，\r\n     *                              该值就是childNodes中的第几个节点， 如果是文本节点就是文本内容的第几个字符\r\n     */\r\n\r\n    /**\r\n     * @property { Boolean } collapsed 当前Range是否闭合\r\n     * @default true\r\n     * @remind Range是闭合的时候， startContainer === endContainer && startOffset === endOffset\r\n     */\r\n\r\n    /**\r\n     * @property { Document } document 当前Range所属的Document对象\r\n     * @remind 不同range的的document属性可以是不同的\r\n     */\r\n    var Range = dom.Range = function (document) {\r\n        var me = this;\r\n        me.startContainer =\r\n            me.startOffset =\r\n                me.endContainer =\r\n                    me.endOffset = null;\r\n        me.document = document;\r\n        me.collapsed = true;\r\n    };\r\n\r\n    /**\r\n     * 删除fillData\r\n     * @param doc\r\n     * @param excludeNode\r\n     */\r\n    function removeFillData(doc, excludeNode) {\r\n        try {\r\n            if (fillData && domUtils.inDoc(fillData, doc)) {\r\n                if (!fillData.nodeValue.replace(fillCharReg, '').length) {\r\n                    var tmpNode = fillData.parentNode;\r\n                    domUtils.remove(fillData);\r\n                    while (tmpNode && domUtils.isEmptyInlineElement(tmpNode) &&\r\n                        //safari的contains有bug\r\n                        (browser.safari ? !(domUtils.getPosition(tmpNode,excludeNode) & domUtils.POSITION_CONTAINS) : !tmpNode.contains(excludeNode))\r\n                        ) {\r\n                        fillData = tmpNode.parentNode;\r\n                        domUtils.remove(tmpNode);\r\n                        tmpNode = fillData;\r\n                    }\r\n                } else {\r\n                    fillData.nodeValue = fillData.nodeValue.replace(fillCharReg, '');\r\n                }\r\n            }\r\n        } catch (e) {\r\n        }\r\n    }\r\n\r\n    /**\r\n     * @param node\r\n     * @param dir\r\n     */\r\n    function mergeSibling(node, dir) {\r\n        var tmpNode;\r\n        node = node[dir];\r\n        while (node && domUtils.isFillChar(node)) {\r\n            tmpNode = node[dir];\r\n            domUtils.remove(node);\r\n            node = tmpNode;\r\n        }\r\n    }\r\n\r\n    Range.prototype = {\r\n\r\n        /**\r\n         * 克隆选区的内容到一个DocumentFragment里\r\n         * @method cloneContents\r\n         * @return { DocumentFragment | NULL } 如果选区是闭合的将返回null， 否则， 返回包含所clone内容的DocumentFragment元素\r\n         * @example\r\n         * ```html\r\n         * <body>\r\n         *      <!-- 中括号表示选区 -->\r\n         *      <b>x<i>x[x</i>xx]x</b>\r\n         *\r\n         *      <script>\r\n         *          //range是已选中的选区\r\n         *          var fragment = range.cloneContents(),\r\n         *              node = document.createElement(\"div\");\r\n         *\r\n         *          node.appendChild( fragment );\r\n         *\r\n         *          //output: <i>x</i>xx\r\n         *          console.log( node.innerHTML );\r\n         *\r\n         *      </script>\r\n         * </body>\r\n         * ```\r\n         */\r\n        cloneContents:function () {\r\n            return this.collapsed ? null : execContentsAction(this, 0);\r\n        },\r\n\r\n        /**\r\n         * 删除当前选区范围中的所有内容\r\n         * @method deleteContents\r\n         * @remind 执行完该操作后， 当前Range对象变成了闭合状态\r\n         * @return { UE.dom.Range } 当前操作的Range对象\r\n         * @example\r\n         * ```html\r\n         * <body>\r\n         *      <!-- 中括号表示选区 -->\r\n         *      <b>x<i>x[x</i>xx]x</b>\r\n         *\r\n         *      <script>\r\n         *          //range是已选中的选区\r\n         *          range.deleteContents();\r\n         *\r\n         *          //竖线表示闭合后的选区位置\r\n         *          //output: <b>x<i>x</i>|x</b>\r\n         *          console.log( document.body.innerHTML );\r\n         *\r\n         *          //此时， range的各项属性为\r\n         *          //output: B\r\n         *          console.log( range.startContainer.tagName );\r\n         *          //output: 2\r\n         *          console.log( range.startOffset );\r\n         *          //output: B\r\n         *          console.log( range.endContainer.tagName );\r\n         *          //output: 2\r\n         *          console.log( range.endOffset );\r\n         *          //output: true\r\n         *          console.log( range.collapsed );\r\n         *\r\n         *      </script>\r\n         * </body>\r\n         * ```\r\n         */\r\n        deleteContents:function () {\r\n            var txt;\r\n            if (!this.collapsed) {\r\n                execContentsAction(this, 1);\r\n            }\r\n            if (browser.webkit) {\r\n                txt = this.startContainer;\r\n                if (txt.nodeType == 3 && !txt.nodeValue.length) {\r\n                    this.setStartBefore(txt).collapse(true);\r\n                    domUtils.remove(txt);\r\n                }\r\n            }\r\n            return this;\r\n        },\r\n\r\n        /**\r\n         * 将当前选区的内容提取到一个DocumentFragment里\r\n         * @method extractContents\r\n         * @remind 执行该操作后， 选区将变成闭合状态\r\n         * @warning 执行该操作后， 原来选区所选中的内容将从dom树上剥离出来\r\n         * @return { DocumentFragment } 返回包含所提取内容的DocumentFragment对象\r\n         * @example\r\n         * ```html\r\n         * <body>\r\n         *      <!-- 中括号表示选区 -->\r\n         *      <b>x<i>x[x</i>xx]x</b>\r\n         *\r\n         *      <script>\r\n         *          //range是已选中的选区\r\n         *          var fragment = range.extractContents(),\r\n         *              node = document.createElement( \"div\" );\r\n         *\r\n         *          node.appendChild( fragment );\r\n         *\r\n         *          //竖线表示闭合后的选区位置\r\n         *\r\n         *          //output: <b>x<i>x</i>|x</b>\r\n         *          console.log( document.body.innerHTML );\r\n         *          //output: <i>x</i>xx\r\n         *          console.log( node.innerHTML );\r\n         *\r\n         *          //此时， range的各项属性为\r\n         *          //output: B\r\n         *          console.log( range.startContainer.tagName );\r\n         *          //output: 2\r\n         *          console.log( range.startOffset );\r\n         *          //output: B\r\n         *          console.log( range.endContainer.tagName );\r\n         *          //output: 2\r\n         *          console.log( range.endOffset );\r\n         *          //output: true\r\n         *          console.log( range.collapsed );\r\n         *\r\n         *      </script>\r\n         * </body>\r\n         */\r\n        extractContents:function () {\r\n            return this.collapsed ? null : execContentsAction(this, 2);\r\n        },\r\n\r\n        /**\r\n         * 设置Range的开始容器节点和偏移量\r\n         * @method  setStart\r\n         * @remind 如果给定的节点是元素节点，那么offset指的是其子元素中索引为offset的元素，\r\n         *          如果是文本节点，那么offset指的是其文本内容的第offset个字符\r\n         * @remind 如果提供的容器节点是一个不能包含子元素的节点， 则该选区的开始容器将被设置\r\n         *          为该节点的父节点， 此时， 其距离开始容器的偏移量也变成了该节点在其父节点\r\n         *          中的索引\r\n         * @param { Node } node 将被设为当前选区开始边界容器的节点对象\r\n         * @param { int } offset 选区的开始位置偏移量\r\n         * @return { UE.dom.Range } 当前range对象\r\n         * @example\r\n         * ```html\r\n         * <!-- 选区 -->\r\n         * <b>xxx<i>x<span>xx</span>xx<em>xx</em>xxx</i>[xxx]</b>\r\n         *\r\n         * <script>\r\n         *\r\n         *     //执行操作\r\n         *     range.setStart( document.getElementsByTagName(\"i\")[0], 1 );\r\n         *\r\n         *     //此时， 选区变成了\r\n         *     //<b>xxx<i>x[<span>xx</span>xx<em>xx</em>xxx</i>xxx]</b>\r\n         *\r\n         * </script>\r\n         * ```\r\n         * @example\r\n         * ```html\r\n         * <!-- 选区 -->\r\n         * <b>xxx<img>[xx]x</b>\r\n         *\r\n         * <script>\r\n         *\r\n         *     //执行操作\r\n         *     range.setStart( document.getElementsByTagName(\"img\")[0], 3 );\r\n         *\r\n         *     //此时， 选区变成了\r\n         *     //<b>xxx[<img>xx]x</b>\r\n         *\r\n         * </script>\r\n         * ```\r\n         */\r\n        setStart:function (node, offset) {\r\n            return setEndPoint(true, node, offset, this);\r\n        },\r\n\r\n        /**\r\n         * 设置Range的结束容器和偏移量\r\n         * @method  setEnd\r\n         * @param { Node } node 作为当前选区结束边界容器的节点对象\r\n         * @param { int } offset 结束边界的偏移量\r\n         * @see UE.dom.Range:setStart(Node,int)\r\n         * @return { UE.dom.Range } 当前range对象\r\n         */\r\n        setEnd:function (node, offset) {\r\n            return setEndPoint(false, node, offset, this);\r\n        },\r\n\r\n        /**\r\n         * 将Range开始位置设置到node节点之后\r\n         * @method  setStartAfter\r\n         * @remind 该操作将会把给定节点的父节点作为range的开始容器， 且偏移量是该节点在其父节点中的位置索引+1\r\n         * @param { Node } node 选区的开始边界将紧接着该节点之后\r\n         * @return { UE.dom.Range } 当前range对象\r\n         * @example\r\n         * ```html\r\n         * <!-- 选区示例 -->\r\n         * <b>xx<i>xxx</i><span>xx[x</span>xxx]</b>\r\n         *\r\n         * <script>\r\n         *\r\n         *     //执行操作\r\n         *     range.setStartAfter( document.getElementsByTagName(\"i\")[0] );\r\n         *\r\n         *     //结果选区\r\n         *     //<b>xx<i>xxx</i>[<span>xxx</span>xxx]</b>\r\n         *\r\n         * </script>\r\n         * ```\r\n         */\r\n        setStartAfter:function (node) {\r\n            return this.setStart(node.parentNode, domUtils.getNodeIndex(node) + 1);\r\n        },\r\n\r\n        /**\r\n         * 将Range开始位置设置到node节点之前\r\n         * @method  setStartBefore\r\n         * @remind 该操作将会把给定节点的父节点作为range的开始容器， 且偏移量是该节点在其父节点中的位置索引\r\n         * @param { Node } node 新的选区开始位置在该节点之前\r\n         * @see UE.dom.Range:setStartAfter(Node)\r\n         * @return { UE.dom.Range } 当前range对象\r\n         */\r\n        setStartBefore:function (node) {\r\n            return this.setStart(node.parentNode, domUtils.getNodeIndex(node));\r\n        },\r\n\r\n        /**\r\n         * 将Range结束位置设置到node节点之后\r\n         * @method  setEndAfter\r\n         * @remind 该操作将会把给定节点的父节点作为range的结束容器， 且偏移量是该节点在其父节点中的位置索引+1\r\n         * @param { Node } node 目标节点\r\n         * @see UE.dom.Range:setStartAfter(Node)\r\n         * @return { UE.dom.Range } 当前range对象\r\n         * @example\r\n         * ```html\r\n         * <!-- 选区示例 -->\r\n         * <b>[xx<i>xxx</i><span>xx]x</span>xxx</b>\r\n         *\r\n         * <script>\r\n         *\r\n         *     //执行操作\r\n         *     range.setStartAfter( document.getElementsByTagName(\"span\")[0] );\r\n         *\r\n         *     //结果选区\r\n         *     //<b>[xx<i>xxx</i><span>xxx</span>]xxx</b>\r\n         *\r\n         * </script>\r\n         * ```\r\n         */\r\n        setEndAfter:function (node) {\r\n            return this.setEnd(node.parentNode, domUtils.getNodeIndex(node) + 1);\r\n        },\r\n\r\n        /**\r\n         * 将Range结束位置设置到node节点之前\r\n         * @method  setEndBefore\r\n         * @remind 该操作将会把给定节点的父节点作为range的结束容器， 且偏移量是该节点在其父节点中的位置索引\r\n         * @param { Node } node 目标节点\r\n         * @see UE.dom.Range:setEndAfter(Node)\r\n         * @return { UE.dom.Range } 当前range对象\r\n         */\r\n        setEndBefore:function (node) {\r\n            return this.setEnd(node.parentNode, domUtils.getNodeIndex(node));\r\n        },\r\n\r\n        /**\r\n         * 设置Range的开始位置到node节点内的第一个子节点之前\r\n         * @method  setStartAtFirst\r\n         * @remind 选区的开始容器将变成给定的节点， 且偏移量为0\r\n         * @remind 如果给定的节点是元素节点， 则该节点必须是允许包含子节点的元素。\r\n         * @param { Node } node 目标节点\r\n         * @see UE.dom.Range:setStartBefore(Node)\r\n         * @return { UE.dom.Range } 当前range对象\r\n         * @example\r\n         * ```html\r\n         * <!-- 选区示例 -->\r\n         * <b>xx<i>xxx</i><span>[xx]x</span>xxx</b>\r\n         *\r\n         * <script>\r\n         *\r\n         *     //执行操作\r\n         *     range.setStartAtFirst( document.getElementsByTagName(\"i\")[0] );\r\n         *\r\n         *     //结果选区\r\n         *     //<b>xx<i>[xxx</i><span>xx]x</span>xxx</b>\r\n         *\r\n         * </script>\r\n         * ```\r\n         */\r\n        setStartAtFirst:function (node) {\r\n            return this.setStart(node, 0);\r\n        },\r\n\r\n        /**\r\n         * 设置Range的开始位置到node节点内的最后一个节点之后\r\n         * @method setStartAtLast\r\n         * @remind 选区的开始容器将变成给定的节点， 且偏移量为该节点的子节点数\r\n         * @remind 如果给定的节点是元素节点， 则该节点必须是允许包含子节点的元素。\r\n         * @param { Node } node 目标节点\r\n         * @see UE.dom.Range:setStartAtFirst(Node)\r\n         * @return { UE.dom.Range } 当前range对象\r\n         */\r\n        setStartAtLast:function (node) {\r\n            return this.setStart(node, node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length);\r\n        },\r\n\r\n        /**\r\n         * 设置Range的结束位置到node节点内的第一个节点之前\r\n         * @method  setEndAtFirst\r\n         * @param { Node } node 目标节点\r\n         * @remind 选区的结束容器将变成给定的节点， 且偏移量为0\r\n         * @remind node必须是一个元素节点， 且必须是允许包含子节点的元素。\r\n         * @see UE.dom.Range:setStartAtFirst(Node)\r\n         * @return { UE.dom.Range } 当前range对象\r\n         */\r\n        setEndAtFirst:function (node) {\r\n            return this.setEnd(node, 0);\r\n        },\r\n\r\n        /**\r\n         * 设置Range的结束位置到node节点内的最后一个节点之后\r\n         * @method  setEndAtLast\r\n         * @param { Node } node 目标节点\r\n         * @remind 选区的结束容器将变成给定的节点， 且偏移量为该节点的子节点数量\r\n         * @remind node必须是一个元素节点， 且必须是允许包含子节点的元素。\r\n         * @see UE.dom.Range:setStartAtFirst(Node)\r\n         * @return { UE.dom.Range } 当前range对象\r\n         */\r\n        setEndAtLast:function (node) {\r\n            return this.setEnd(node, node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length);\r\n        },\r\n\r\n        /**\r\n         * 选中给定节点\r\n         * @method  selectNode\r\n         * @remind 此时， 选区的开始容器和结束容器都是该节点的父节点， 其startOffset是该节点在父节点中的位置索引，\r\n         *          而endOffset为startOffset+1\r\n         * @param { Node } node 需要选中的节点\r\n         * @return { UE.dom.Range } 当前range对象，此时的range仅包含当前给定的节点对象\r\n         * @example\r\n         * ```html\r\n         * <!-- 选区示例 -->\r\n         * <b>xx<i>xxx</i><span>[xx]x</span>xxx</b>\r\n         *\r\n         * <script>\r\n         *\r\n         *     //执行操作\r\n         *     range.selectNode( document.getElementsByTagName(\"i\")[0] );\r\n         *\r\n         *     //结果选区\r\n         *     //<b>xx[<i>xxx</i>]<span>xxx</span>xxx</b>\r\n         *\r\n         * </script>\r\n         * ```\r\n         */\r\n        selectNode:function (node) {\r\n            return this.setStartBefore(node).setEndAfter(node);\r\n        },\r\n\r\n        /**\r\n         * 选中给定节点内部的所有节点\r\n         * @method  selectNodeContents\r\n         * @remind 此时， 选区的开始容器和结束容器都是该节点， 其startOffset为0，\r\n         *          而endOffset是该节点的子节点数。\r\n         * @param { Node } node 目标节点， 当前range将包含该节点内的所有节点\r\n         * @return { UE.dom.Range } 当前range对象， 此时range仅包含给定节点的所有子节点\r\n         * @example\r\n         * ```html\r\n         * <!-- 选区示例 -->\r\n         * <b>xx<i>xxx</i><span>[xx]x</span>xxx</b>\r\n         *\r\n         * <script>\r\n         *\r\n         *     //执行操作\r\n         *     range.selectNode( document.getElementsByTagName(\"b\")[0] );\r\n         *\r\n         *     //结果选区\r\n         *     //<b>[xx<i>xxx</i><span>xxx</span>xxx]</b>\r\n         *\r\n         * </script>\r\n         * ```\r\n         */\r\n        selectNodeContents:function (node) {\r\n            return this.setStart(node, 0).setEndAtLast(node);\r\n        },\r\n\r\n        /**\r\n         * clone当前Range对象\r\n         * @method  cloneRange\r\n         * @remind 返回的range是一个全新的range对象， 其内部所有属性与当前被clone的range相同。\r\n         * @return { UE.dom.Range } 当前range对象的一个副本\r\n         */\r\n        cloneRange:function () {\r\n            var me = this;\r\n            return new Range(me.document).setStart(me.startContainer, me.startOffset).setEnd(me.endContainer, me.endOffset);\r\n\r\n        },\r\n\r\n        /**\r\n         * 向当前选区的结束处闭合选区\r\n         * @method  collapse\r\n         * @return { UE.dom.Range } 当前range对象\r\n         * @example\r\n         * ```html\r\n         * <!-- 选区示例 -->\r\n         * <b>xx<i>xxx</i><span>[xx]x</span>xxx</b>\r\n         *\r\n         * <script>\r\n         *\r\n         *     //执行操作\r\n         *     range.collapse();\r\n         *\r\n         *     //结果选区\r\n         *     //“|”表示选区已闭合\r\n         *     //<b>xx<i>xxx</i><span>xx|x</span>xxx</b>\r\n         *\r\n         * </script>\r\n         * ```\r\n         */\r\n\r\n        /**\r\n         * 闭合当前选区，根据给定的toStart参数项决定是向当前选区开始处闭合还是向结束处闭合，\r\n         * 如果toStart的值为true，则向开始位置闭合， 反之，向结束位置闭合。\r\n         * @method  collapse\r\n         * @param { Boolean } toStart 是否向选区开始处闭合\r\n         * @return { UE.dom.Range } 当前range对象，此时range对象处于闭合状态\r\n         * @see UE.dom.Range:collapse()\r\n         * @example\r\n         * ```html\r\n         * <!-- 选区示例 -->\r\n         * <b>xx<i>xxx</i><span>[xx]x</span>xxx</b>\r\n         *\r\n         * <script>\r\n         *\r\n         *     //执行操作\r\n         *     range.collapse( true );\r\n         *\r\n         *     //结果选区\r\n         *     //“|”表示选区已闭合\r\n         *     //<b>xx<i>xxx</i><span>|xxx</span>xxx</b>\r\n         *\r\n         * </script>\r\n         * ```\r\n         */\r\n        collapse:function (toStart) {\r\n            var me = this;\r\n            if (toStart) {\r\n                me.endContainer = me.startContainer;\r\n                me.endOffset = me.startOffset;\r\n            } else {\r\n                me.startContainer = me.endContainer;\r\n                me.startOffset = me.endOffset;\r\n            }\r\n            me.collapsed = true;\r\n            return me;\r\n        },\r\n\r\n        /**\r\n         * 调整range的开始位置和结束位置，使其\"收缩\"到最小的位置\r\n         * @method  shrinkBoundary\r\n         * @return { UE.dom.Range } 当前range对象\r\n         * @example\r\n         * ```html\r\n         * <span>xx<b>xx[</b>xxxxx]</span> => <span>xx<b>xx</b>[xxxxx]</span>\r\n         * ```\r\n         *\r\n         * @example\r\n         * ```html\r\n         * <!-- 选区示例 -->\r\n         * <b>x[xx</b><i>]xxx</i>\r\n         *\r\n         * <script>\r\n         *\r\n         *     //执行收缩\r\n         *     range.shrinkBoundary();\r\n         *\r\n         *     //结果选区\r\n         *     //<b>x[xx]</b><i>xxx</i>\r\n         * </script>\r\n         * ```\r\n         *\r\n         * @example\r\n         * ```html\r\n         * [<b><i>xxxx</i>xxxxxxx</b>] => <b><i>[xxxx</i>xxxxxxx]</b>\r\n         * ```\r\n         */\r\n\r\n        /**\r\n         * 调整range的开始位置和结束位置，使其\"收缩\"到最小的位置，\r\n         * 如果ignoreEnd的值为true，则忽略对结束位置的调整\r\n         * @method  shrinkBoundary\r\n         * @param { Boolean } ignoreEnd 是否忽略对结束位置的调整\r\n         * @return { UE.dom.Range } 当前range对象\r\n         * @see UE.dom.domUtils.Range:shrinkBoundary()\r\n         */\r\n        shrinkBoundary:function (ignoreEnd) {\r\n            var me = this, child,\r\n                collapsed = me.collapsed;\r\n            function check(node){\r\n                return node.nodeType == 1 && !domUtils.isBookmarkNode(node) && !dtd.$empty[node.tagName] && !dtd.$nonChild[node.tagName]\r\n            }\r\n            while (me.startContainer.nodeType == 1 //是element\r\n                && (child = me.startContainer.childNodes[me.startOffset]) //子节点也是element\r\n                && check(child)) {\r\n                me.setStart(child, 0);\r\n            }\r\n            if (collapsed) {\r\n                return me.collapse(true);\r\n            }\r\n            if (!ignoreEnd) {\r\n                while (me.endContainer.nodeType == 1//是element\r\n                    && me.endOffset > 0 //如果是空元素就退出 endOffset=0那么endOffst-1为负值，childNodes[endOffset]报错\r\n                    && (child = me.endContainer.childNodes[me.endOffset - 1]) //子节点也是element\r\n                    && check(child)) {\r\n                    me.setEnd(child, child.childNodes.length);\r\n                }\r\n            }\r\n            return me;\r\n        },\r\n\r\n        /**\r\n         * 获取离当前选区内包含的所有节点最近的公共祖先节点，\r\n         * @method  getCommonAncestor\r\n         * @remind 返回的公共祖先节点一定不是range自身的容器节点， 但有可能是一个文本节点\r\n         * @return { Node } 当前range对象内所有节点的公共祖先节点\r\n         * @example\r\n         * ```html\r\n         * //选区示例\r\n         * <span>xxx<b>x[x<em>xx]x</em>xxx</b>xx</span>\r\n         * <script>\r\n         *\r\n         *     var node = range.getCommonAncestor();\r\n         *\r\n         *     //公共祖先节点是： b节点\r\n         *     //输出： B\r\n         *     console.log(node.tagName);\r\n         *\r\n         * </script>\r\n         * ```\r\n         */\r\n\r\n        /**\r\n         * 获取当前选区所包含的所有节点的公共祖先节点， 可以根据给定的参数 includeSelf 决定获取到\r\n         * 的公共祖先节点是否可以是当前选区的startContainer或endContainer节点， 如果 includeSelf\r\n         * 的取值为true， 则返回的节点可以是自身的容器节点， 否则， 则不能是容器节点\r\n         * @method  getCommonAncestor\r\n         * @param { Boolean } includeSelf 是否允许获取到的公共祖先节点是当前range对象的容器节点\r\n         * @return { Node } 当前range对象内所有节点的公共祖先节点\r\n         * @see UE.dom.Range:getCommonAncestor()\r\n         * @example\r\n         * ```html\r\n         * <body>\r\n         *\r\n         *     <!-- 选区示例 -->\r\n         *     <b>xxx<i>xxxx<span>xx[x</span>xx]x</i>xxxxxxx</b>\r\n         *\r\n         *     <script>\r\n         *\r\n         *         var node = range.getCommonAncestor( false );\r\n         *\r\n         *         //这里的公共祖先节点是B而不是I， 是因为参数限制了获取到的节点不能是容器节点\r\n         *         //output: B\r\n         *         console.log( node.tagName );\r\n         *\r\n         *     </script>\r\n         *\r\n         * </body>\r\n         * ```\r\n         */\r\n\r\n        /**\r\n         * 获取当前选区所包含的所有节点的公共祖先节点， 可以根据给定的参数 includeSelf 决定获取到\r\n         * 的公共祖先节点是否可以是当前选区的startContainer或endContainer节点， 如果 includeSelf\r\n         * 的取值为true， 则返回的节点可以是自身的容器节点， 否则， 则不能是容器节点； 同时可以根据\r\n         * ignoreTextNode 参数的取值决定是否忽略类型为文本节点的祖先节点。\r\n         * @method  getCommonAncestor\r\n         * @param { Boolean } includeSelf 是否允许获取到的公共祖先节点是当前range对象的容器节点\r\n         * @param { Boolean } ignoreTextNode 获取祖先节点的过程中是否忽略类型为文本节点的祖先节点\r\n         * @return { Node } 当前range对象内所有节点的公共祖先节点\r\n         * @see UE.dom.Range:getCommonAncestor()\r\n         * @see UE.dom.Range:getCommonAncestor(Boolean)\r\n         * @example\r\n         * ```html\r\n         * <body>\r\n         *\r\n         *     <!-- 选区示例 -->\r\n         *     <b>xxx<i>xxxx<span>x[x]x</span>xxx</i>xxxxxxx</b>\r\n         *\r\n         *     <script>\r\n         *\r\n         *         var node = range.getCommonAncestor( true, false );\r\n         *\r\n         *         //output: SPAN\r\n         *         console.log( node.tagName );\r\n         *\r\n         *     </script>\r\n         *\r\n         * </body>\r\n         * ```\r\n         */\r\n        getCommonAncestor:function (includeSelf, ignoreTextNode) {\r\n            var me = this,\r\n                start = me.startContainer,\r\n                end = me.endContainer;\r\n            if (start === end) {\r\n                if (includeSelf && selectOneNode(this)) {\r\n                    start = start.childNodes[me.startOffset];\r\n                    if(start.nodeType == 1)\r\n                        return start;\r\n                }\r\n                //只有在上来就相等的情况下才会出现是文本的情况\r\n                return ignoreTextNode && start.nodeType == 3 ? start.parentNode : start;\r\n            }\r\n            return domUtils.getCommonAncestor(start, end);\r\n        },\r\n\r\n        /**\r\n         * 调整当前Range的开始和结束边界容器，如果是容器节点是文本节点,就调整到包含该文本节点的父节点上\r\n         * @method trimBoundary\r\n         * @remind 该操作有可能会引起文本节点被切开\r\n         * @return { UE.dom.Range } 当前range对象\r\n         * @example\r\n         * ```html\r\n         *\r\n         * //选区示例\r\n         * <b>xxx<i>[xxxxx]</i>xxx</b>\r\n         *\r\n         * <script>\r\n         *     //未调整前， 选区的开始容器和结束都是文本节点\r\n         *     //执行调整\r\n         *     range.trimBoundary();\r\n         *\r\n         *     //调整之后， 容器节点变成了i节点\r\n         *     //<b>xxx[<i>xxxxx</i>]xxx</b>\r\n         * </script>\r\n         * ```\r\n         */\r\n\r\n        /**\r\n         * 调整当前Range的开始和结束边界容器，如果是容器节点是文本节点,就调整到包含该文本节点的父节点上，\r\n         * 可以根据 ignoreEnd 参数的值决定是否调整对结束边界的调整\r\n         * @method trimBoundary\r\n         * @param { Boolean } ignoreEnd 是否忽略对结束边界的调整\r\n         * @return { UE.dom.Range } 当前range对象\r\n         * @example\r\n         * ```html\r\n         *\r\n         * //选区示例\r\n         * <b>xxx<i>[xxxxx]</i>xxx</b>\r\n         *\r\n         * <script>\r\n         *     //未调整前， 选区的开始容器和结束都是文本节点\r\n         *     //执行调整\r\n         *     range.trimBoundary( true );\r\n         *\r\n         *     //调整之后， 开始容器节点变成了i节点\r\n         *     //但是， 结束容器没有发生变化\r\n         *     //<b>xxx[<i>xxxxx]</i>xxx</b>\r\n         * </script>\r\n         * ```\r\n         */\r\n        trimBoundary:function (ignoreEnd) {\r\n            this.txtToElmBoundary();\r\n            var start = this.startContainer,\r\n                offset = this.startOffset,\r\n                collapsed = this.collapsed,\r\n                end = this.endContainer;\r\n            if (start.nodeType == 3) {\r\n                if (offset == 0) {\r\n                    this.setStartBefore(start);\r\n                } else {\r\n                    if (offset >= start.nodeValue.length) {\r\n                        this.setStartAfter(start);\r\n                    } else {\r\n                        var textNode = domUtils.split(start, offset);\r\n                        //跟新结束边界\r\n                        if (start === end) {\r\n                            this.setEnd(textNode, this.endOffset - offset);\r\n                        } else if (start.parentNode === end) {\r\n                            this.endOffset += 1;\r\n                        }\r\n                        this.setStartBefore(textNode);\r\n                    }\r\n                }\r\n                if (collapsed) {\r\n                    return this.collapse(true);\r\n                }\r\n            }\r\n            if (!ignoreEnd) {\r\n                offset = this.endOffset;\r\n                end = this.endContainer;\r\n                if (end.nodeType == 3) {\r\n                    if (offset == 0) {\r\n                        this.setEndBefore(end);\r\n                    } else {\r\n                        offset < end.nodeValue.length && domUtils.split(end, offset);\r\n                        this.setEndAfter(end);\r\n                    }\r\n                }\r\n            }\r\n            return this;\r\n        },\r\n\r\n        /**\r\n         * 如果选区在文本的边界上，就扩展选区到文本的父节点上, 如果当前选区是闭合的， 则什么也不做\r\n         * @method txtToElmBoundary\r\n         * @remind 该操作不会修改dom节点\r\n         * @return { UE.dom.Range } 当前range对象\r\n         */\r\n\r\n        /**\r\n         * 如果选区在文本的边界上，就扩展选区到文本的父节点上, 如果当前选区是闭合的， 则根据参数项\r\n         * ignoreCollapsed 的值决定是否执行该调整\r\n         * @method txtToElmBoundary\r\n         * @param { Boolean } ignoreCollapsed 是否忽略选区的闭合状态， 如果该参数取值为true， 则\r\n         *                      不论选区是否闭合， 都会执行该操作， 反之， 则不会对闭合的选区执行该操作\r\n         * @return { UE.dom.Range } 当前range对象\r\n         */\r\n        txtToElmBoundary:function (ignoreCollapsed) {\r\n            function adjust(r, c) {\r\n                var container = r[c + 'Container'],\r\n                    offset = r[c + 'Offset'];\r\n                if (container.nodeType == 3) {\r\n                    if (!offset) {\r\n                        r['set' + c.replace(/(\\w)/, function (a) {\r\n                            return a.toUpperCase();\r\n                        }) + 'Before'](container);\r\n                    } else if (offset >= container.nodeValue.length) {\r\n                        r['set' + c.replace(/(\\w)/, function (a) {\r\n                            return a.toUpperCase();\r\n                        }) + 'After' ](container);\r\n                    }\r\n                }\r\n            }\r\n\r\n            if (ignoreCollapsed || !this.collapsed) {\r\n                adjust(this, 'start');\r\n                adjust(this, 'end');\r\n            }\r\n            return this;\r\n        },\r\n\r\n        /**\r\n         * 在当前选区的开始位置前插入节点，新插入的节点会被该range包含\r\n         * @method  insertNode\r\n         * @param { Node } node 需要插入的节点\r\n         * @remind 插入的节点可以是一个DocumentFragment依次插入多个节点\r\n         * @return { UE.dom.Range } 当前range对象\r\n         */\r\n        insertNode:function (node) {\r\n            var first = node, length = 1;\r\n            if (node.nodeType == 11) {\r\n                first = node.firstChild;\r\n                length = node.childNodes.length;\r\n            }\r\n            this.trimBoundary(true);\r\n            var start = this.startContainer,\r\n                offset = this.startOffset;\r\n            var nextNode = start.childNodes[ offset ];\r\n            if (nextNode) {\r\n                start.insertBefore(node, nextNode);\r\n            } else {\r\n                start.appendChild(node);\r\n            }\r\n            if (first.parentNode === this.endContainer) {\r\n                this.endOffset = this.endOffset + length;\r\n            }\r\n            return this.setStartBefore(first);\r\n        },\r\n\r\n        /**\r\n         * 闭合选区到当前选区的开始位置， 并且定位光标到闭合后的位置\r\n         * @method  setCursor\r\n         * @return { UE.dom.Range } 当前range对象\r\n         * @see UE.dom.Range:collapse()\r\n         */\r\n\r\n        /**\r\n         * 闭合选区，可以根据参数toEnd的值控制选区是向前闭合还是向后闭合， 并且定位光标到闭合后的位置。\r\n         * @method  setCursor\r\n         * @param { Boolean } toEnd 是否向后闭合， 如果为true， 则闭合选区时， 将向结束容器方向闭合，\r\n         *                      反之，则向开始容器方向闭合\r\n         * @return { UE.dom.Range } 当前range对象\r\n         * @see UE.dom.Range:collapse(Boolean)\r\n         */\r\n        setCursor:function (toEnd, noFillData) {\r\n            return this.collapse(!toEnd).select(noFillData);\r\n        },\r\n\r\n        /**\r\n         * 创建当前range的一个书签，记录下当前range的位置，方便当dom树改变时，还能找回原来的选区位置\r\n         * @method createBookmark\r\n         * @param { Boolean } serialize 控制返回的标记位置是对当前位置的引用还是ID，如果该值为true，则\r\n         *                              返回标记位置的ID， 反之则返回标记位置节点的引用\r\n         * @return { Object } 返回一个书签记录键值对， 其包含的key有： start => 开始标记的ID或者引用，\r\n         *                          end => 结束标记的ID或引用， id => 当前标记的类型， 如果为true，则表示\r\n         *                          返回的记录的类型为ID， 反之则为引用\r\n         */\r\n        createBookmark:function (serialize, same) {\r\n            var endNode,\r\n                startNode = this.document.createElement('span');\r\n            startNode.style.cssText = 'display:none;line-height:0px;';\r\n            startNode.appendChild(this.document.createTextNode('\\u200D'));\r\n            startNode.id = '_baidu_bookmark_start_' + (same ? '' : guid++);\r\n\r\n            if (!this.collapsed) {\r\n                endNode = startNode.cloneNode(true);\r\n                endNode.id = '_baidu_bookmark_end_' + (same ? '' : guid++);\r\n            }\r\n            this.insertNode(startNode);\r\n            if (endNode) {\r\n                this.collapse().insertNode(endNode).setEndBefore(endNode);\r\n            }\r\n            this.setStartAfter(startNode);\r\n            return {\r\n                start:serialize ? startNode.id : startNode,\r\n                end:endNode ? serialize ? endNode.id : endNode : null,\r\n                id:serialize\r\n            }\r\n        },\r\n\r\n        /**\r\n         *  调整当前range的边界到书签位置，并删除该书签对象所标记的位置内的节点\r\n         *  @method  moveToBookmark\r\n         *  @param { BookMark } bookmark createBookmark所创建的标签对象\r\n         *  @return { UE.dom.Range } 当前range对象\r\n         *  @see UE.dom.Range:createBookmark(Boolean)\r\n         */\r\n        moveToBookmark:function (bookmark) {\r\n            var start = bookmark.id ? this.document.getElementById(bookmark.start) : bookmark.start,\r\n                end = bookmark.end && bookmark.id ? this.document.getElementById(bookmark.end) : bookmark.end;\r\n            this.setStartBefore(start);\r\n            domUtils.remove(start);\r\n            if (end) {\r\n                this.setEndBefore(end);\r\n                domUtils.remove(end);\r\n            } else {\r\n                this.collapse(true);\r\n            }\r\n            return this;\r\n        },\r\n\r\n        /**\r\n         * 调整range的边界，使其\"放大\"到最近的父节点\r\n         * @method  enlarge\r\n         * @remind 会引起选区的变化\r\n         * @return { UE.dom.Range } 当前range对象\r\n         */\r\n\r\n        /**\r\n         * 调整range的边界，使其\"放大\"到最近的父节点，根据参数 toBlock 的取值， 可以\r\n         * 要求扩大之后的父节点是block节点\r\n         * @method  enlarge\r\n         * @param { Boolean } toBlock 是否要求扩大之后的父节点必须是block节点\r\n         * @return { UE.dom.Range } 当前range对象\r\n         */\r\n        enlarge:function (toBlock, stopFn) {\r\n            var isBody = domUtils.isBody,\r\n                pre, node, tmp = this.document.createTextNode('');\r\n            if (toBlock) {\r\n                node = this.startContainer;\r\n                if (node.nodeType == 1) {\r\n                    if (node.childNodes[this.startOffset]) {\r\n                        pre = node = node.childNodes[this.startOffset]\r\n                    } else {\r\n                        node.appendChild(tmp);\r\n                        pre = node = tmp;\r\n                    }\r\n                } else {\r\n                    pre = node;\r\n                }\r\n                while (1) {\r\n                    if (domUtils.isBlockElm(node)) {\r\n                        node = pre;\r\n                        while ((pre = node.previousSibling) && !domUtils.isBlockElm(pre)) {\r\n                            node = pre;\r\n                        }\r\n                        this.setStartBefore(node);\r\n                        break;\r\n                    }\r\n                    pre = node;\r\n                    node = node.parentNode;\r\n                }\r\n                node = this.endContainer;\r\n                if (node.nodeType == 1) {\r\n                    if (pre = node.childNodes[this.endOffset]) {\r\n                        node.insertBefore(tmp, pre);\r\n                    } else {\r\n                        node.appendChild(tmp);\r\n                    }\r\n                    pre = node = tmp;\r\n                } else {\r\n                    pre = node;\r\n                }\r\n                while (1) {\r\n                    if (domUtils.isBlockElm(node)) {\r\n                        node = pre;\r\n                        while ((pre = node.nextSibling) && !domUtils.isBlockElm(pre)) {\r\n                            node = pre;\r\n                        }\r\n                        this.setEndAfter(node);\r\n                        break;\r\n                    }\r\n                    pre = node;\r\n                    node = node.parentNode;\r\n                }\r\n                if (tmp.parentNode === this.endContainer) {\r\n                    this.endOffset--;\r\n                }\r\n                domUtils.remove(tmp);\r\n            }\r\n\r\n            // 扩展边界到最大\r\n            if (!this.collapsed) {\r\n                while (this.startOffset == 0) {\r\n                    if (stopFn && stopFn(this.startContainer)) {\r\n                        break;\r\n                    }\r\n                    if (isBody(this.startContainer)) {\r\n                        break;\r\n                    }\r\n                    this.setStartBefore(this.startContainer);\r\n                }\r\n                while (this.endOffset == (this.endContainer.nodeType == 1 ? this.endContainer.childNodes.length : this.endContainer.nodeValue.length)) {\r\n                    if (stopFn && stopFn(this.endContainer)) {\r\n                        break;\r\n                    }\r\n                    if (isBody(this.endContainer)) {\r\n                        break;\r\n                    }\r\n                    this.setEndAfter(this.endContainer);\r\n                }\r\n            }\r\n            return this;\r\n        },\r\n        enlargeToBlockElm:function(ignoreEnd){\r\n            while(!domUtils.isBlockElm(this.startContainer)){\r\n                this.setStartBefore(this.startContainer);\r\n            }\r\n            if(!ignoreEnd){\r\n                while(!domUtils.isBlockElm(this.endContainer)){\r\n                    this.setEndAfter(this.endContainer);\r\n                }\r\n            }\r\n            return this;\r\n        },\r\n        /**\r\n         * 调整Range的边界，使其\"缩小\"到最合适的位置\r\n         * @method adjustmentBoundary\r\n         * @return { UE.dom.Range } 当前range对象\r\n         * @see UE.dom.Range:shrinkBoundary()\r\n         */\r\n        adjustmentBoundary:function () {\r\n            if (!this.collapsed) {\r\n                while (!domUtils.isBody(this.startContainer) &&\r\n                    this.startOffset == this.startContainer[this.startContainer.nodeType == 3 ? 'nodeValue' : 'childNodes'].length &&\r\n                    this.startContainer[this.startContainer.nodeType == 3 ? 'nodeValue' : 'childNodes'].length\r\n                    ) {\r\n\r\n                    this.setStartAfter(this.startContainer);\r\n                }\r\n                while (!domUtils.isBody(this.endContainer) && !this.endOffset &&\r\n                    this.endContainer[this.endContainer.nodeType == 3 ? 'nodeValue' : 'childNodes'].length\r\n                    ) {\r\n                    this.setEndBefore(this.endContainer);\r\n                }\r\n            }\r\n            return this;\r\n        },\r\n\r\n        /**\r\n         * 给range选区中的内容添加给定的inline标签\r\n         * @method applyInlineStyle\r\n         * @param { String } tagName 需要添加的标签名\r\n         * @example\r\n         * ```html\r\n         * <p>xxxx[xxxx]x</p>  ==>  range.applyInlineStyle(\"strong\")  ==>  <p>xxxx[<strong>xxxx</strong>]x</p>\r\n         * ```\r\n         */\r\n\r\n        /**\r\n         * 给range选区中的内容添加给定的inline标签， 并且为标签附加上一些初始化属性。\r\n         * @method applyInlineStyle\r\n         * @param { String } tagName 需要添加的标签名\r\n         * @param { Object } attrs 跟随新添加的标签的属性\r\n         * @return { UE.dom.Range } 当前选区\r\n         * @example\r\n         * ```html\r\n         * <p>xxxx[xxxx]x</p>\r\n         *\r\n         * ==>\r\n         *\r\n         * <!-- 执行操作 -->\r\n         * range.applyInlineStyle(\"strong\",{\"style\":\"font-size:12px\"})\r\n         *\r\n         * ==>\r\n         *\r\n         * <p>xxxx[<strong style=\"font-size:12px\">xxxx</strong>]x</p>\r\n         * ```\r\n         */\r\n        applyInlineStyle:function (tagName, attrs, list) {\r\n            if (this.collapsed)return this;\r\n            this.trimBoundary().enlarge(false,\r\n                function (node) {\r\n                    return node.nodeType == 1 && domUtils.isBlockElm(node)\r\n                }).adjustmentBoundary();\r\n            var bookmark = this.createBookmark(),\r\n                end = bookmark.end,\r\n                filterFn = function (node) {\r\n                    return node.nodeType == 1 ? node.tagName.toLowerCase() != 'br' : !domUtils.isWhitespace(node);\r\n                },\r\n                current = domUtils.getNextDomNode(bookmark.start, false, filterFn),\r\n                node,\r\n                pre,\r\n                range = this.cloneRange();\r\n            while (current && (domUtils.getPosition(current, end) & domUtils.POSITION_PRECEDING)) {\r\n                if (current.nodeType == 3 || dtd[tagName][current.tagName]) {\r\n                    range.setStartBefore(current);\r\n                    node = current;\r\n                    while (node && (node.nodeType == 3 || dtd[tagName][node.tagName]) && node !== end) {\r\n                        pre = node;\r\n                        node = domUtils.getNextDomNode(node, node.nodeType == 1, null, function (parent) {\r\n                            return dtd[tagName][parent.tagName];\r\n                        });\r\n                    }\r\n                    var frag = range.setEndAfter(pre).extractContents(), elm;\r\n                    if (list && list.length > 0) {\r\n                        var level, top;\r\n                        top = level = list[0].cloneNode(false);\r\n                        for (var i = 1, ci; ci = list[i++];) {\r\n                            level.appendChild(ci.cloneNode(false));\r\n                            level = level.firstChild;\r\n                        }\r\n                        elm = level;\r\n                    } else {\r\n                        elm = range.document.createElement(tagName);\r\n                    }\r\n                    if (attrs) {\r\n                        domUtils.setAttributes(elm, attrs);\r\n                    }\r\n                    elm.appendChild(frag);\r\n                    range.insertNode(list ? top : elm);\r\n                    //处理下滑线在a上的情况\r\n                    var aNode;\r\n                    if (tagName == 'span' && attrs.style && /text\\-decoration/.test(attrs.style) && (aNode = domUtils.findParentByTagName(elm, 'a', true))) {\r\n                        domUtils.setAttributes(aNode, attrs);\r\n                        domUtils.remove(elm, true);\r\n                        elm = aNode;\r\n                    } else {\r\n                        domUtils.mergeSibling(elm);\r\n                        domUtils.clearEmptySibling(elm);\r\n                    }\r\n                    //去除子节点相同的\r\n                    domUtils.mergeChild(elm, attrs);\r\n                    current = domUtils.getNextDomNode(elm, false, filterFn);\r\n                    domUtils.mergeToParent(elm);\r\n                    if (node === end) {\r\n                        break;\r\n                    }\r\n                } else {\r\n                    current = domUtils.getNextDomNode(current, true, filterFn);\r\n                }\r\n            }\r\n            return this.moveToBookmark(bookmark);\r\n        },\r\n\r\n        /**\r\n         * 移除当前选区内指定的inline标签，但保留其中的内容\r\n         * @method removeInlineStyle\r\n         * @param { String } tagName 需要移除的标签名\r\n         * @return { UE.dom.Range } 当前的range对象\r\n         * @example\r\n         * ```html\r\n         * xx[x<span>xxx<em>yyy</em>zz]z</span>  => range.removeInlineStyle([\"em\"])  => xx[x<span>xxxyyyzz]z</span>\r\n         * ```\r\n         */\r\n\r\n        /**\r\n         * 移除当前选区内指定的一组inline标签，但保留其中的内容\r\n         * @method removeInlineStyle\r\n         * @param { Array } tagNameArr 需要移除的标签名的数组\r\n         * @return { UE.dom.Range } 当前的range对象\r\n         * @see UE.dom.Range:removeInlineStyle(String)\r\n         */\r\n        removeInlineStyle:function (tagNames) {\r\n            if (this.collapsed)return this;\r\n            tagNames = utils.isArray(tagNames) ? tagNames : [tagNames];\r\n            this.shrinkBoundary().adjustmentBoundary();\r\n            var start = this.startContainer, end = this.endContainer;\r\n            while (1) {\r\n                if (start.nodeType == 1) {\r\n                    if (utils.indexOf(tagNames, start.tagName.toLowerCase()) > -1) {\r\n                        break;\r\n                    }\r\n                    if (start.tagName.toLowerCase() == 'body') {\r\n                        start = null;\r\n                        break;\r\n                    }\r\n                }\r\n                start = start.parentNode;\r\n            }\r\n            while (1) {\r\n                if (end.nodeType == 1) {\r\n                    if (utils.indexOf(tagNames, end.tagName.toLowerCase()) > -1) {\r\n                        break;\r\n                    }\r\n                    if (end.tagName.toLowerCase() == 'body') {\r\n                        end = null;\r\n                        break;\r\n                    }\r\n                }\r\n                end = end.parentNode;\r\n            }\r\n            var bookmark = this.createBookmark(),\r\n                frag,\r\n                tmpRange;\r\n            if (start) {\r\n                tmpRange = this.cloneRange().setEndBefore(bookmark.start).setStartBefore(start);\r\n                frag = tmpRange.extractContents();\r\n                tmpRange.insertNode(frag);\r\n                domUtils.clearEmptySibling(start, true);\r\n                start.parentNode.insertBefore(bookmark.start, start);\r\n            }\r\n            if (end) {\r\n                tmpRange = this.cloneRange().setStartAfter(bookmark.end).setEndAfter(end);\r\n                frag = tmpRange.extractContents();\r\n                tmpRange.insertNode(frag);\r\n                domUtils.clearEmptySibling(end, false, true);\r\n                end.parentNode.insertBefore(bookmark.end, end.nextSibling);\r\n            }\r\n            var current = domUtils.getNextDomNode(bookmark.start, false, function (node) {\r\n                return node.nodeType == 1;\r\n            }), next;\r\n            while (current && current !== bookmark.end) {\r\n                next = domUtils.getNextDomNode(current, true, function (node) {\r\n                    return node.nodeType == 1;\r\n                });\r\n                if (utils.indexOf(tagNames, current.tagName.toLowerCase()) > -1) {\r\n                    domUtils.remove(current, true);\r\n                }\r\n                current = next;\r\n            }\r\n            return this.moveToBookmark(bookmark);\r\n        },\r\n\r\n        /**\r\n         * 获取当前选中的自闭合的节点\r\n         * @method  getClosedNode\r\n         * @return { Node | NULL } 如果当前选中的是自闭合节点， 则返回该节点， 否则返回NULL\r\n         */\r\n        getClosedNode:function () {\r\n            var node;\r\n            if (!this.collapsed) {\r\n                var range = this.cloneRange().adjustmentBoundary().shrinkBoundary();\r\n                if (selectOneNode(range)) {\r\n                    var child = range.startContainer.childNodes[range.startOffset];\r\n                    if (child && child.nodeType == 1 && (dtd.$empty[child.tagName] || dtd.$nonChild[child.tagName])) {\r\n                        node = child;\r\n                    }\r\n                }\r\n            }\r\n            return node;\r\n        },\r\n\r\n        /**\r\n         * 在页面上高亮range所表示的选区\r\n         * @method select\r\n         * @return { UE.dom.Range } 返回当前Range对象\r\n         */\r\n            //这里不区分ie9以上，trace:3824\r\n        select:browser.ie ? function (noFillData, textRange) {\r\n            var nativeRange;\r\n            if (!this.collapsed)\r\n                this.shrinkBoundary();\r\n            var node = this.getClosedNode();\r\n            if (node && !textRange) {\r\n                try {\r\n                    nativeRange = this.document.body.createControlRange();\r\n                    nativeRange.addElement(node);\r\n                    nativeRange.select();\r\n                } catch (e) {}\r\n                return this;\r\n            }\r\n            var bookmark = this.createBookmark(),\r\n                start = bookmark.start,\r\n                end;\r\n            nativeRange = this.document.body.createTextRange();\r\n            nativeRange.moveToElementText(start);\r\n            nativeRange.moveStart('character', 1);\r\n            if (!this.collapsed) {\r\n                var nativeRangeEnd = this.document.body.createTextRange();\r\n                end = bookmark.end;\r\n                nativeRangeEnd.moveToElementText(end);\r\n                nativeRange.setEndPoint('EndToEnd', nativeRangeEnd);\r\n            } else {\r\n                if (!noFillData && this.startContainer.nodeType != 3) {\r\n                    //使用<span>|x<span>固定住光标\r\n                    var tmpText = this.document.createTextNode(fillChar),\r\n                        tmp = this.document.createElement('span');\r\n                    tmp.appendChild(this.document.createTextNode(fillChar));\r\n                    start.parentNode.insertBefore(tmp, start);\r\n                    start.parentNode.insertBefore(tmpText, start);\r\n                    //当点b,i,u时，不能清除i上边的b\r\n                    removeFillData(this.document, tmpText);\r\n                    fillData = tmpText;\r\n                    mergeSibling(tmp, 'previousSibling');\r\n                    mergeSibling(start, 'nextSibling');\r\n                    nativeRange.moveStart('character', -1);\r\n                    nativeRange.collapse(true);\r\n                }\r\n            }\r\n            this.moveToBookmark(bookmark);\r\n            tmp && domUtils.remove(tmp);\r\n            //IE在隐藏状态下不支持range操作，catch一下\r\n            try {\r\n                nativeRange.select();\r\n            } catch (e) {\r\n            }\r\n            return this;\r\n        } : function (notInsertFillData) {\r\n            function checkOffset(rng){\r\n\r\n                function check(node,offset,dir){\r\n                    if(node.nodeType == 3 && node.nodeValue.length < offset){\r\n                        rng[dir + 'Offset'] = node.nodeValue.length\r\n                    }\r\n                }\r\n                check(rng.startContainer,rng.startOffset,'start');\r\n                check(rng.endContainer,rng.endOffset,'end');\r\n            }\r\n            var win = domUtils.getWindow(this.document),\r\n                sel = win.getSelection(),\r\n                txtNode;\r\n            //FF下关闭自动长高时滚动条在关闭dialog时会跳\r\n            //ff下如果不body.focus将不能定位闭合光标到编辑器内\r\n            browser.gecko ? this.document.body.focus() : win.focus();\r\n            if (sel) {\r\n                sel.removeAllRanges();\r\n                // trace:870 chrome/safari后边是br对于闭合得range不能定位 所以去掉了判断\r\n                // this.startContainer.nodeType != 3 &&! ((child = this.startContainer.childNodes[this.startOffset]) && child.nodeType == 1 && child.tagName == 'BR'\r\n                if (this.collapsed && !notInsertFillData) {\r\n//                    //opear如果没有节点接着，原生的不能够定位,不能在body的第一级插入空白节点\r\n//                    if (notInsertFillData && browser.opera && !domUtils.isBody(this.startContainer) && this.startContainer.nodeType == 1) {\r\n//                        var tmp = this.document.createTextNode('');\r\n//                        this.insertNode(tmp).setStart(tmp, 0).collapse(true);\r\n//                    }\r\n//\r\n                    //处理光标落在文本节点的情况\r\n                    //处理以下的情况\r\n                    //<b>|xxxx</b>\r\n                    //<b>xxxx</b>|xxxx\r\n                    //xxxx<b>|</b>\r\n                    var start = this.startContainer,child = start;\r\n                    if(start.nodeType == 1){\r\n                        child = start.childNodes[this.startOffset];\r\n\r\n                    }\r\n                    if( !(start.nodeType == 3 && this.startOffset)  &&\r\n                        (child ?\r\n                            (!child.previousSibling || child.previousSibling.nodeType != 3)\r\n                            :\r\n                            (!start.lastChild || start.lastChild.nodeType != 3)\r\n                        )\r\n                    ){\r\n                        txtNode = this.document.createTextNode(fillChar);\r\n                        //跟着前边走\r\n                        this.insertNode(txtNode);\r\n                        removeFillData(this.document, txtNode);\r\n                        mergeSibling(txtNode, 'previousSibling');\r\n                        mergeSibling(txtNode, 'nextSibling');\r\n                        fillData = txtNode;\r\n                        this.setStart(txtNode, browser.webkit ? 1 : 0).collapse(true);\r\n                    }\r\n                }\r\n                var nativeRange = this.document.createRange();\r\n                if(this.collapsed && browser.opera && this.startContainer.nodeType == 1){\r\n                    var child = this.startContainer.childNodes[this.startOffset];\r\n                    if(!child){\r\n                        //往前靠拢\r\n                        child = this.startContainer.lastChild;\r\n                        if( child && domUtils.isBr(child)){\r\n                            this.setStartBefore(child).collapse(true);\r\n                        }\r\n                    }else{\r\n                        //向后靠拢\r\n                        while(child && domUtils.isBlockElm(child)){\r\n                            if(child.nodeType == 1 && child.childNodes[0]){\r\n                                child = child.childNodes[0]\r\n                            }else{\r\n                                break;\r\n                            }\r\n                        }\r\n                        child && this.setStartBefore(child).collapse(true)\r\n                    }\r\n\r\n                }\r\n                //是createAddress最后一位算的不准，现在这里进行微调\r\n                checkOffset(this);\r\n                nativeRange.setStart(this.startContainer, this.startOffset);\r\n                nativeRange.setEnd(this.endContainer, this.endOffset);\r\n                sel.addRange(nativeRange);\r\n            }\r\n            return this;\r\n        },\r\n\r\n        /**\r\n         * 滚动到当前range开始的位置\r\n         * @method scrollToView\r\n         * @param { Window } win 当前range对象所属的window对象\r\n         * @return { UE.dom.Range } 当前Range对象\r\n         */\r\n\r\n        /**\r\n         * 滚动到距离当前range开始位置 offset 的位置处\r\n         * @method scrollToView\r\n         * @param { Window } win 当前range对象所属的window对象\r\n         * @param { Number } offset 距离range开始位置处的偏移量， 如果为正数， 则向下偏移， 反之， 则向上偏移\r\n         * @return { UE.dom.Range } 当前Range对象\r\n         */\r\n        scrollToView:function (win, offset) {\r\n            win = win ? window : domUtils.getWindow(this.document);\r\n            var me = this,\r\n                span = me.document.createElement('span');\r\n            //trace:717\r\n            span.innerHTML = '&nbsp;';\r\n            me.cloneRange().insertNode(span);\r\n            domUtils.scrollToView(span, win, offset);\r\n            domUtils.remove(span);\r\n            return me;\r\n        },\r\n\r\n        /**\r\n         * 判断当前选区内容是否占位符\r\n         * @private\r\n         * @method inFillChar\r\n         * @return { Boolean } 如果是占位符返回true，否则返回false\r\n         */\r\n        inFillChar : function(){\r\n            var start = this.startContainer;\r\n            if(this.collapsed && start.nodeType == 3\r\n                && start.nodeValue.replace(new RegExp('^' + domUtils.fillChar),'').length + 1 == start.nodeValue.length\r\n                ){\r\n                return true;\r\n            }\r\n            return false;\r\n        },\r\n\r\n        /**\r\n         * 保存\r\n         * @method createAddress\r\n         * @private\r\n         * @return { Boolean } 返回开始和结束的位置\r\n         * @example\r\n         * ```html\r\n         * <body>\r\n         *     <p>\r\n         *         aaaa\r\n         *         <em>\r\n         *             <!-- 选区开始 -->\r\n         *             bbbb\r\n         *             <!-- 选区结束 -->\r\n         *         </em>\r\n         *     </p>\r\n         *\r\n         *     <script>\r\n         *         //output: {startAddress:[0,1,0,0],endAddress:[0,1,0,4]}\r\n         *         console.log( range.createAddress() );\r\n         *     </script>\r\n         * </body>\r\n         * ```\r\n         */\r\n        createAddress : function(ignoreEnd,ignoreTxt){\r\n            var addr = {},me = this;\r\n\r\n            function getAddress(isStart){\r\n                var node = isStart ? me.startContainer : me.endContainer;\r\n                var parents = domUtils.findParents(node,true,function(node){return !domUtils.isBody(node)}),\r\n                    addrs = [];\r\n                for(var i = 0,ci;ci = parents[i++];){\r\n                    addrs.push(domUtils.getNodeIndex(ci,ignoreTxt));\r\n                }\r\n                var firstIndex = 0;\r\n\r\n                if(ignoreTxt){\r\n                    if(node.nodeType == 3){\r\n                        var tmpNode = node.previousSibling;\r\n                        while(tmpNode && tmpNode.nodeType == 3){\r\n                            firstIndex += tmpNode.nodeValue.replace(fillCharReg,'').length;\r\n                            tmpNode = tmpNode.previousSibling;\r\n                        }\r\n                        firstIndex +=  (isStart ? me.startOffset : me.endOffset)// - (fillCharReg.test(node.nodeValue) ? 1 : 0 )\r\n                    }else{\r\n                        node =  node.childNodes[ isStart ? me.startOffset : me.endOffset];\r\n                        if(node){\r\n                            firstIndex = domUtils.getNodeIndex(node,ignoreTxt);\r\n                        }else{\r\n                            node = isStart ? me.startContainer : me.endContainer;\r\n                            var first = node.firstChild;\r\n                            while(first){\r\n                                if(domUtils.isFillChar(first)){\r\n                                    first = first.nextSibling;\r\n                                    continue;\r\n                                }\r\n                                firstIndex++;\r\n                                if(first.nodeType == 3){\r\n                                    while( first && first.nodeType == 3){\r\n                                        first = first.nextSibling;\r\n                                    }\r\n                                }else{\r\n                                    first = first.nextSibling;\r\n                                }\r\n                            }\r\n                        }\r\n                    }\r\n\r\n                }else{\r\n                    firstIndex = isStart ? domUtils.isFillChar(node) ? 0 : me.startOffset  : me.endOffset\r\n                }\r\n                if(firstIndex < 0){\r\n                    firstIndex = 0;\r\n                }\r\n                addrs.push(firstIndex);\r\n                return addrs;\r\n            }\r\n            addr.startAddress = getAddress(true);\r\n            if(!ignoreEnd){\r\n                addr.endAddress = me.collapsed ? [].concat(addr.startAddress) : getAddress();\r\n            }\r\n            return addr;\r\n        },\r\n\r\n        /**\r\n         * 保存\r\n         * @method createAddress\r\n         * @private\r\n         * @return { Boolean } 返回开始和结束的位置\r\n         * @example\r\n         * ```html\r\n         * <body>\r\n         *     <p>\r\n         *         aaaa\r\n         *         <em>\r\n         *             <!-- 选区开始 -->\r\n         *             bbbb\r\n         *             <!-- 选区结束 -->\r\n         *         </em>\r\n         *     </p>\r\n         *\r\n         *     <script>\r\n         *         var range = editor.selection.getRange();\r\n         *         range.moveToAddress({startAddress:[0,1,0,0],endAddress:[0,1,0,4]});\r\n         *         range.select();\r\n         *         //output: 'bbbb'\r\n         *         console.log(editor.selection.getText());\r\n         *     </script>\r\n         * </body>\r\n         * ```\r\n         */\r\n        moveToAddress : function(addr,ignoreEnd){\r\n            var me = this;\r\n            function getNode(address,isStart){\r\n                var tmpNode = me.document.body,\r\n                    parentNode,offset;\r\n                for(var i= 0,ci,l=address.length;i<l;i++){\r\n                    ci = address[i];\r\n                    parentNode = tmpNode;\r\n                    tmpNode = tmpNode.childNodes[ci];\r\n                    if(!tmpNode){\r\n                        offset = ci;\r\n                        break;\r\n                    }\r\n                }\r\n                if(isStart){\r\n                    if(tmpNode){\r\n                        me.setStartBefore(tmpNode)\r\n                    }else{\r\n                        me.setStart(parentNode,offset)\r\n                    }\r\n                }else{\r\n                    if(tmpNode){\r\n                        me.setEndBefore(tmpNode)\r\n                    }else{\r\n                        me.setEnd(parentNode,offset)\r\n                    }\r\n                }\r\n            }\r\n            getNode(addr.startAddress,true);\r\n            !ignoreEnd && addr.endAddress &&  getNode(addr.endAddress);\r\n            return me;\r\n        },\r\n\r\n        /**\r\n         * 判断给定的Range对象是否和当前Range对象表示的是同一个选区\r\n         * @method equals\r\n         * @param { UE.dom.Range } 需要判断的Range对象\r\n         * @return { Boolean } 如果给定的Range对象与当前Range对象表示的是同一个选区， 则返回true， 否则返回false\r\n         */\r\n        equals : function(rng){\r\n            for(var p in this){\r\n                if(this.hasOwnProperty(p)){\r\n                    if(this[p] !== rng[p])\r\n                        return false\r\n                }\r\n            }\r\n            return true;\r\n\r\n        },\r\n\r\n        /**\r\n         * 遍历range内的节点。每当遍历一个节点时， 都会执行参数项 doFn 指定的函数， 该函数的接受当前遍历的节点\r\n         * 作为其参数。\r\n         * @method traversal\r\n         * @param { Function }  doFn 对每个遍历的节点要执行的方法， 该方法接受当前遍历的节点作为其参数\r\n         * @return { UE.dom.Range } 当前range对象\r\n         * @example\r\n         * ```html\r\n         *\r\n         * <body>\r\n         *\r\n         *     <!-- 选区开始 -->\r\n         *     <span></span>\r\n         *     <a></a>\r\n         *     <!-- 选区结束 -->\r\n         * </body>\r\n         *\r\n         * <script>\r\n         *\r\n         *     //output: <span></span><a></a>\r\n         *     console.log( range.cloneContents() );\r\n         *\r\n         *     range.traversal( function ( node ) {\r\n         *\r\n         *         if ( node.nodeType === 1 ) {\r\n         *             node.className = \"test\";\r\n         *         }\r\n         *\r\n         *     } );\r\n         *\r\n         *     //output: <span class=\"test\"></span><a class=\"test\"></a>\r\n         *     console.log( range.cloneContents() );\r\n         *\r\n         * </script>\r\n         * ```\r\n         */\r\n\r\n        /**\r\n         * 遍历range内的节点。\r\n         * 每当遍历一个节点时， 都会执行参数项 doFn 指定的函数， 该函数的接受当前遍历的节点\r\n         * 作为其参数。\r\n         * 可以通过参数项 filterFn 来指定一个过滤器， 只有符合该过滤器过滤规则的节点才会触\r\n         * 发doFn函数的执行\r\n         * @method traversal\r\n         * @param { Function } doFn 对每个遍历的节点要执行的方法， 该方法接受当前遍历的节点作为其参数\r\n         * @param { Function } filterFn 过滤器， 该函数接受当前遍历的节点作为参数， 如果该节点满足过滤\r\n         *                      规则， 请返回true， 该节点会触发doFn， 否则， 请返回false， 则该节点不\r\n         *                      会触发doFn。\r\n         * @return { UE.dom.Range } 当前range对象\r\n         * @see UE.dom.Range:traversal(Function)\r\n         * @example\r\n         * ```html\r\n         *\r\n         * <body>\r\n         *\r\n         *     <!-- 选区开始 -->\r\n         *     <span></span>\r\n         *     <a></a>\r\n         *     <!-- 选区结束 -->\r\n         * </body>\r\n         *\r\n         * <script>\r\n         *\r\n         *     //output: <span></span><a></a>\r\n         *     console.log( range.cloneContents() );\r\n         *\r\n         *     range.traversal( function ( node ) {\r\n         *\r\n         *         node.className = \"test\";\r\n         *\r\n         *     }, function ( node ) {\r\n         *          return node.nodeType === 1;\r\n         *     } );\r\n         *\r\n         *     //output: <span class=\"test\"></span><a class=\"test\"></a>\r\n         *     console.log( range.cloneContents() );\r\n         *\r\n         * </script>\r\n         * ```\r\n         */\r\n        traversal:function(doFn,filterFn){\r\n            if (this.collapsed)\r\n                return this;\r\n            var bookmark = this.createBookmark(),\r\n                end = bookmark.end,\r\n                current = domUtils.getNextDomNode(bookmark.start, false, filterFn);\r\n            while (current && current !== end && (domUtils.getPosition(current, end) & domUtils.POSITION_PRECEDING)) {\r\n                var tmpNode = domUtils.getNextDomNode(current,false,filterFn);\r\n                doFn(current);\r\n                current = tmpNode;\r\n            }\r\n            return this.moveToBookmark(bookmark);\r\n        }\r\n    };\r\n})();\r\n\r\n// core/Selection.js\r\n/**\r\n * 选集\r\n * @file\r\n * @module UE.dom\r\n * @class Selection\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 选区集合\r\n * @unfile\r\n * @module UE.dom\r\n * @class Selection\r\n */\r\n(function () {\r\n\r\n    function getBoundaryInformation( range, start ) {\r\n        var getIndex = domUtils.getNodeIndex;\r\n        range = range.duplicate();\r\n        range.collapse( start );\r\n        var parent = range.parentElement();\r\n        //如果节点里没有子节点，直接退出\r\n        if ( !parent.hasChildNodes() ) {\r\n            return  {container:parent, offset:0};\r\n        }\r\n        var siblings = parent.children,\r\n            child,\r\n            testRange = range.duplicate(),\r\n            startIndex = 0, endIndex = siblings.length - 1, index = -1,\r\n            distance;\r\n        while ( startIndex <= endIndex ) {\r\n            index = Math.floor( (startIndex + endIndex) / 2 );\r\n            child = siblings[index];\r\n            testRange.moveToElementText( child );\r\n            var position = testRange.compareEndPoints( 'StartToStart', range );\r\n            if ( position > 0 ) {\r\n                endIndex = index - 1;\r\n            } else if ( position < 0 ) {\r\n                startIndex = index + 1;\r\n            } else {\r\n                //trace:1043\r\n                return  {container:parent, offset:getIndex( child )};\r\n            }\r\n        }\r\n        if ( index == -1 ) {\r\n            testRange.moveToElementText( parent );\r\n            testRange.setEndPoint( 'StartToStart', range );\r\n            distance = testRange.text.replace( /(\\r\\n|\\r)/g, '\\n' ).length;\r\n            siblings = parent.childNodes;\r\n            if ( !distance ) {\r\n                child = siblings[siblings.length - 1];\r\n                return  {container:child, offset:child.nodeValue.length};\r\n            }\r\n\r\n            var i = siblings.length;\r\n            while ( distance > 0 ){\r\n                distance -= siblings[ --i ].nodeValue.length;\r\n            }\r\n            return {container:siblings[i], offset:-distance};\r\n        }\r\n        testRange.collapse( position > 0 );\r\n        testRange.setEndPoint( position > 0 ? 'StartToStart' : 'EndToStart', range );\r\n        distance = testRange.text.replace( /(\\r\\n|\\r)/g, '\\n' ).length;\r\n        if ( !distance ) {\r\n            return  dtd.$empty[child.tagName] || dtd.$nonChild[child.tagName] ?\r\n            {container:parent, offset:getIndex( child ) + (position > 0 ? 0 : 1)} :\r\n            {container:child, offset:position > 0 ? 0 : child.childNodes.length}\r\n        }\r\n        while ( distance > 0 ) {\r\n            try {\r\n                var pre = child;\r\n                child = child[position > 0 ? 'previousSibling' : 'nextSibling'];\r\n                distance -= child.nodeValue.length;\r\n            } catch ( e ) {\r\n                return {container:parent, offset:getIndex( pre )};\r\n            }\r\n        }\r\n        return  {container:child, offset:position > 0 ? -distance : child.nodeValue.length + distance}\r\n    }\r\n\r\n    /**\r\n     * 将ieRange转换为Range对象\r\n     * @param {Range}   ieRange    ieRange对象\r\n     * @param {Range}   range      Range对象\r\n     * @return  {Range}  range       返回转换后的Range对象\r\n     */\r\n    function transformIERangeToRange( ieRange, range ) {\r\n        if ( ieRange.item ) {\r\n            range.selectNode( ieRange.item( 0 ) );\r\n        } else {\r\n            var bi = getBoundaryInformation( ieRange, true );\r\n            range.setStart( bi.container, bi.offset );\r\n            if ( ieRange.compareEndPoints( 'StartToEnd', ieRange ) != 0 ) {\r\n                bi = getBoundaryInformation( ieRange, false );\r\n                range.setEnd( bi.container, bi.offset );\r\n            }\r\n        }\r\n        return range;\r\n    }\r\n\r\n    /**\r\n     * 获得ieRange\r\n     * @param {Selection} sel    Selection对象\r\n     * @return {ieRange}    得到ieRange\r\n     */\r\n    function _getIERange( sel ) {\r\n        var ieRange;\r\n        //ie下有可能报错\r\n        try {\r\n            ieRange = sel.getNative().createRange();\r\n        } catch ( e ) {\r\n            return null;\r\n        }\r\n        var el = ieRange.item ? ieRange.item( 0 ) : ieRange.parentElement();\r\n        if ( ( el.ownerDocument || el ) === sel.document ) {\r\n            return ieRange;\r\n        }\r\n        return null;\r\n    }\r\n\r\n    var Selection = dom.Selection = function ( doc ) {\r\n        var me = this, iframe;\r\n        me.document = doc;\r\n        if ( browser.ie9below ) {\r\n            iframe = domUtils.getWindow( doc ).frameElement;\r\n            domUtils.on( iframe, 'beforedeactivate', function () {\r\n                me._bakIERange = me.getIERange();\r\n            } );\r\n            domUtils.on( iframe, 'activate', function () {\r\n                try {\r\n                    if ( !_getIERange( me ) && me._bakIERange ) {\r\n                        me._bakIERange.select();\r\n                    }\r\n                } catch ( ex ) {\r\n                }\r\n                me._bakIERange = null;\r\n            } );\r\n        }\r\n        iframe = doc = null;\r\n    };\r\n\r\n    Selection.prototype = {\r\n\r\n        rangeInBody : function(rng,txtRange){\r\n            var node = browser.ie9below || txtRange ? rng.item ? rng.item() : rng.parentElement() : rng.startContainer;\r\n\r\n            return node === this.document.body || domUtils.inDoc(node,this.document);\r\n        },\r\n\r\n        /**\r\n         * 获取原生seleciton对象\r\n         * @method getNative\r\n         * @return { Object } 获得selection对象\r\n         * @example\r\n         * ```javascript\r\n         * editor.selection.getNative();\r\n         * ```\r\n         */\r\n        getNative:function () {\r\n            var doc = this.document;\r\n            try {\r\n                return !doc ? null : browser.ie9below ? doc.selection : domUtils.getWindow( doc ).getSelection();\r\n            } catch ( e ) {\r\n                return null;\r\n            }\r\n        },\r\n\r\n        /**\r\n         * 获得ieRange\r\n         * @method getIERange\r\n         * @return { Object } 返回ie原生的Range\r\n         * @example\r\n         * ```javascript\r\n         * editor.selection.getIERange();\r\n         * ```\r\n         */\r\n        getIERange:function () {\r\n            var ieRange = _getIERange( this );\r\n            if ( !ieRange ) {\r\n                if ( this._bakIERange ) {\r\n                    return this._bakIERange;\r\n                }\r\n            }\r\n            return ieRange;\r\n        },\r\n\r\n        /**\r\n         * 缓存当前选区的range和选区的开始节点\r\n         * @method cache\r\n         */\r\n        cache:function () {\r\n            this.clear();\r\n            this._cachedRange = this.getRange();\r\n            this._cachedStartElement = this.getStart();\r\n            this._cachedStartElementPath = this.getStartElementPath();\r\n        },\r\n\r\n        /**\r\n         * 获取选区开始位置的父节点到body\r\n         * @method getStartElementPath\r\n         * @return { Array } 返回父节点集合\r\n         * @example\r\n         * ```javascript\r\n         * editor.selection.getStartElementPath();\r\n         * ```\r\n         */\r\n        getStartElementPath:function () {\r\n            if ( this._cachedStartElementPath ) {\r\n                return this._cachedStartElementPath;\r\n            }\r\n            var start = this.getStart();\r\n            if ( start ) {\r\n                return domUtils.findParents( start, true, null, true )\r\n            }\r\n            return [];\r\n        },\r\n\r\n        /**\r\n         * 清空缓存\r\n         * @method clear\r\n         */\r\n        clear:function () {\r\n            this._cachedStartElementPath = this._cachedRange = this._cachedStartElement = null;\r\n        },\r\n\r\n        /**\r\n         * 编辑器是否得到了选区\r\n         * @method isFocus\r\n         */\r\n        isFocus:function () {\r\n            try {\r\n                if(browser.ie9below){\r\n\r\n                    var nativeRange = _getIERange(this);\r\n                    return !!(nativeRange && this.rangeInBody(nativeRange));\r\n                }else{\r\n                    return !!this.getNative().rangeCount;\r\n                }\r\n            } catch ( e ) {\r\n                return false;\r\n            }\r\n\r\n        },\r\n\r\n        /**\r\n         * 获取选区对应的Range\r\n         * @method getRange\r\n         * @return { Object } 得到Range对象\r\n         * @example\r\n         * ```javascript\r\n         * editor.selection.getRange();\r\n         * ```\r\n         */\r\n        getRange:function () {\r\n            var me = this;\r\n            function optimze( range ) {\r\n                var child = me.document.body.firstChild,\r\n                    collapsed = range.collapsed;\r\n                while ( child && child.firstChild ) {\r\n                    range.setStart( child, 0 );\r\n                    child = child.firstChild;\r\n                }\r\n                if ( !range.startContainer ) {\r\n                    range.setStart( me.document.body, 0 )\r\n                }\r\n                if ( collapsed ) {\r\n                    range.collapse( true );\r\n                }\r\n            }\r\n\r\n            if ( me._cachedRange != null ) {\r\n                return this._cachedRange;\r\n            }\r\n            var range = new baidu.editor.dom.Range( me.document );\r\n\r\n            if ( browser.ie9below ) {\r\n                var nativeRange = me.getIERange();\r\n                if ( nativeRange ) {\r\n                    //备份的_bakIERange可能已经实效了，dom树发生了变化比如从源码模式切回来，所以try一下，实效就放到body开始位置\r\n                    try{\r\n                        transformIERangeToRange( nativeRange, range );\r\n                    }catch(e){\r\n                        optimze( range );\r\n                    }\r\n\r\n                } else {\r\n                    optimze( range );\r\n                }\r\n            } else {\r\n                var sel = me.getNative();\r\n                if ( sel && sel.rangeCount ) {\r\n                    var firstRange = sel.getRangeAt( 0 );\r\n                    var lastRange = sel.getRangeAt( sel.rangeCount - 1 );\r\n                    range.setStart( firstRange.startContainer, firstRange.startOffset ).setEnd( lastRange.endContainer, lastRange.endOffset );\r\n                    if ( range.collapsed && domUtils.isBody( range.startContainer ) && !range.startOffset ) {\r\n                        optimze( range );\r\n                    }\r\n                } else {\r\n                    //trace:1734 有可能已经不在dom树上了，标识的节点\r\n                    if ( this._bakRange && domUtils.inDoc( this._bakRange.startContainer, this.document ) ){\r\n                        return this._bakRange;\r\n                    }\r\n                    optimze( range );\r\n                }\r\n            }\r\n            return this._bakRange = range;\r\n        },\r\n\r\n        /**\r\n         * 获取开始元素，用于状态反射\r\n         * @method getStart\r\n         * @return { Element } 获得开始元素\r\n         * @example\r\n         * ```javascript\r\n         * editor.selection.getStart();\r\n         * ```\r\n         */\r\n        getStart:function () {\r\n            if ( this._cachedStartElement ) {\r\n                return this._cachedStartElement;\r\n            }\r\n            var range = browser.ie9below ? this.getIERange() : this.getRange(),\r\n                tmpRange,\r\n                start, tmp, parent;\r\n            if ( browser.ie9below ) {\r\n                if ( !range ) {\r\n                    //todo 给第一个值可能会有问题\r\n                    return this.document.body.firstChild;\r\n                }\r\n                //control元素\r\n                if ( range.item ){\r\n                    return range.item( 0 );\r\n                }\r\n                tmpRange = range.duplicate();\r\n                //修正ie下<b>x</b>[xx] 闭合后 <b>x|</b>xx\r\n                tmpRange.text.length > 0 && tmpRange.moveStart( 'character', 1 );\r\n                tmpRange.collapse( 1 );\r\n                start = tmpRange.parentElement();\r\n                parent = tmp = range.parentElement();\r\n                while ( tmp = tmp.parentNode ) {\r\n                    if ( tmp == start ) {\r\n                        start = parent;\r\n                        break;\r\n                    }\r\n                }\r\n            } else {\r\n                range.shrinkBoundary();\r\n                start = range.startContainer;\r\n                if ( start.nodeType == 1 && start.hasChildNodes() ){\r\n                    start = start.childNodes[Math.min( start.childNodes.length - 1, range.startOffset )];\r\n                }\r\n                if ( start.nodeType == 3 ){\r\n                    return start.parentNode;\r\n                }\r\n            }\r\n            return start;\r\n        },\r\n\r\n        /**\r\n         * 得到选区中的文本\r\n         * @method getText\r\n         * @return { String } 选区中包含的文本\r\n         * @example\r\n         * ```javascript\r\n         * editor.selection.getText();\r\n         * ```\r\n         */\r\n        getText:function () {\r\n            var nativeSel, nativeRange;\r\n            if ( this.isFocus() && (nativeSel = this.getNative()) ) {\r\n                nativeRange = browser.ie9below ? nativeSel.createRange() : nativeSel.getRangeAt( 0 );\r\n                return browser.ie9below ? nativeRange.text : nativeRange.toString();\r\n            }\r\n            return '';\r\n        },\r\n\r\n        /**\r\n         * 清除选区\r\n         * @method clearRange\r\n         * @example\r\n         * ```javascript\r\n         * editor.selection.clearRange();\r\n         * ```\r\n         */\r\n        clearRange : function(){\r\n            this.getNative()[browser.ie9below ? 'empty' : 'removeAllRanges']();\r\n        }\r\n    };\r\n})();\r\n\r\n// core/Editor.js\r\n/**\r\n * 编辑器主类，包含编辑器提供的大部分公用接口\r\n * @file\r\n * @module UE\r\n * @class Editor\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * UEditor公用空间，UEditor所有的功能都挂载在该空间下\r\n * @unfile\r\n * @module UE\r\n */\r\n\r\n/**\r\n * UEditor的核心类，为用户提供与编辑器交互的接口。\r\n * @unfile\r\n * @module UE\r\n * @class Editor\r\n */\r\n\r\n(function () {\r\n    var uid = 0, _selectionChangeTimer;\r\n\r\n    /**\r\n     * 获取编辑器的html内容，赋值到编辑器所在表单的textarea文本域里面\r\n     * @private\r\n     * @method setValue\r\n     * @param { UE.Editor } editor 编辑器事例\r\n     */\r\n    function setValue(form, editor) {\r\n        var textarea;\r\n        if (editor.textarea) {\r\n            if (utils.isString(editor.textarea)) {\r\n                for (var i = 0, ti, tis = domUtils.getElementsByTagName(form, 'textarea'); ti = tis[i++];) {\r\n                    if (ti.id == 'ueditor_textarea_' + editor.options.textarea) {\r\n                        textarea = ti;\r\n                        break;\r\n                    }\r\n                }\r\n            } else {\r\n                textarea = editor.textarea;\r\n            }\r\n        }\r\n        if (!textarea) {\r\n            form.appendChild(textarea = domUtils.createElement(document, 'textarea', {\r\n                'name': editor.options.textarea,\r\n                'id': 'ueditor_textarea_' + editor.options.textarea,\r\n                'style': \"display:none\"\r\n            }));\r\n            //不要产生多个textarea\r\n            editor.textarea = textarea;\r\n        }\r\n        !textarea.getAttribute('name') && textarea.setAttribute('name', editor.options.textarea );\r\n        textarea.value = editor.hasContents() ?\r\n            (editor.options.allHtmlEnabled ? editor.getAllHtml() : editor.getContent(null, null, true)) :\r\n            ''\r\n    }\r\n    function loadPlugins(me){\r\n        //初始化插件\r\n        for (var pi in UE.plugins) {\r\n            UE.plugins[pi].call(me);\r\n        }\r\n\r\n    }\r\n    function checkCurLang(I18N){\r\n        for(var lang in I18N){\r\n            return lang\r\n        }\r\n    }\r\n\r\n    function langReadied(me){\r\n        me.langIsReady = true;\r\n\r\n        me.fireEvent(\"langReady\");\r\n    }\r\n\r\n    /**\r\n     * 编辑器准备就绪后会触发该事件\r\n     * @module UE\r\n     * @class Editor\r\n     * @event ready\r\n     * @remind render方法执行完成之后,会触发该事件\r\n     * @remind\r\n     * @example\r\n     * ```javascript\r\n     * editor.addListener( 'ready', function( editor ) {\r\n     *     editor.execCommand( 'focus' ); //编辑器家在完成后，让编辑器拿到焦点\r\n     * } );\r\n     * ```\r\n     */\r\n    /**\r\n     * 执行destroy方法,会触发该事件\r\n     * @module UE\r\n     * @class Editor\r\n     * @event destroy\r\n     * @see UE.Editor:destroy()\r\n     */\r\n    /**\r\n     * 执行reset方法,会触发该事件\r\n     * @module UE\r\n     * @class Editor\r\n     * @event reset\r\n     * @see UE.Editor:reset()\r\n     */\r\n    /**\r\n     * 执行focus方法,会触发该事件\r\n     * @module UE\r\n     * @class Editor\r\n     * @event focus\r\n     * @see UE.Editor:focus(Boolean)\r\n     */\r\n    /**\r\n     * 语言加载完成会触发该事件\r\n     * @module UE\r\n     * @class Editor\r\n     * @event langReady\r\n     */\r\n    /**\r\n     * 运行命令之后会触发该命令\r\n     * @module UE\r\n     * @class Editor\r\n     * @event beforeExecCommand\r\n     */\r\n    /**\r\n     * 运行命令之后会触发该命令\r\n     * @module UE\r\n     * @class Editor\r\n     * @event afterExecCommand\r\n     */\r\n    /**\r\n     * 运行命令之前会触发该命令\r\n     * @module UE\r\n     * @class Editor\r\n     * @event firstBeforeExecCommand\r\n     */\r\n    /**\r\n     * 在getContent方法执行之前会触发该事件\r\n     * @module UE\r\n     * @class Editor\r\n     * @event beforeGetContent\r\n     * @see UE.Editor:getContent()\r\n     */\r\n    /**\r\n     * 在getContent方法执行之后会触发该事件\r\n     * @module UE\r\n     * @class Editor\r\n     * @event afterGetContent\r\n     * @see UE.Editor:getContent()\r\n     */\r\n    /**\r\n     * 在getAllHtml方法执行时会触发该事件\r\n     * @module UE\r\n     * @class Editor\r\n     * @event getAllHtml\r\n     * @see UE.Editor:getAllHtml()\r\n     */\r\n    /**\r\n     * 在setContent方法执行之前会触发该事件\r\n     * @module UE\r\n     * @class Editor\r\n     * @event beforeSetContent\r\n     * @see UE.Editor:setContent(String)\r\n     */\r\n    /**\r\n     * 在setContent方法执行之后会触发该事件\r\n     * @module UE\r\n     * @class Editor\r\n     * @event afterSetContent\r\n     * @see UE.Editor:setContent(String)\r\n     */\r\n    /**\r\n     * 每当编辑器内部选区发生改变时，将触发该事件\r\n     * @event selectionchange\r\n     * @warning 该事件的触发非常频繁，不建议在该事件的处理过程中做重量级的处理\r\n     * @example\r\n     * ```javascript\r\n     * editor.addListener( 'selectionchange', function( editor ) {\r\n     *     console.log('选区发生改变');\r\n     * }\r\n     */\r\n    /**\r\n     * 在所有selectionchange的监听函数执行之前，会触发该事件\r\n     * @module UE\r\n     * @class Editor\r\n     * @event beforeSelectionChange\r\n     * @see UE.Editor:selectionchange\r\n     */\r\n    /**\r\n     * 在所有selectionchange的监听函数执行完之后，会触发该事件\r\n     * @module UE\r\n     * @class Editor\r\n     * @event afterSelectionChange\r\n     * @see UE.Editor:selectionchange\r\n     */\r\n    /**\r\n     * 编辑器内容发生改变时会触发该事件\r\n     * @module UE\r\n     * @class Editor\r\n     * @event contentChange\r\n     */\r\n\r\n\r\n    /**\r\n     * 以默认参数构建一个编辑器实例\r\n     * @constructor\r\n     * @remind 通过 改构造方法实例化的编辑器,不带ui层.需要render到一个容器,编辑器实例才能正常渲染到页面\r\n     * @example\r\n     * ```javascript\r\n     * var editor = new UE.Editor();\r\n     * editor.execCommand('blod');\r\n     * ```\r\n     * @see UE.Config\r\n     */\r\n\r\n    /**\r\n     * 以给定的参数集合创建一个编辑器实例，对于未指定的参数，将应用默认参数。\r\n     * @constructor\r\n     * @remind 通过 改构造方法实例化的编辑器,不带ui层.需要render到一个容器,编辑器实例才能正常渲染到页面\r\n     * @param { Object } setting 创建编辑器的参数\r\n     * @example\r\n     * ```javascript\r\n     * var editor = new UE.Editor();\r\n     * editor.execCommand('blod');\r\n     * ```\r\n     * @see UE.Config\r\n     */\r\n    var Editor = UE.Editor = function (options) {\r\n        var me = this;\r\n        me.uid = uid++;\r\n        EventBase.call(me);\r\n        me.commands = {};\r\n        me.options = utils.extend(utils.clone(options || {}), UEDITOR_CONFIG, true);\r\n        me.shortcutkeys = {};\r\n        me.inputRules = [];\r\n        me.outputRules = [];\r\n        //设置默认的常用属性\r\n        me.setOpt(Editor.defaultOptions(me));\r\n\r\n        /* 尝试异步加载后台配置 */\r\n        me.loadServerConfig();\r\n\r\n        if(!utils.isEmptyObject(UE.I18N)){\r\n            //修改默认的语言类型\r\n            me.options.lang = checkCurLang(UE.I18N);\r\n            UE.plugin.load(me);\r\n            langReadied(me);\r\n\r\n        }else{\r\n            utils.loadFile(document, {\r\n                src: me.options.langPath + me.options.lang + \"/\" + me.options.lang + \".js\",\r\n                tag: \"script\",\r\n                type: \"text/javascript\",\r\n                defer: \"defer\"\r\n            }, function () {\r\n                UE.plugin.load(me);\r\n                langReadied(me);\r\n            });\r\n        }\r\n\r\n        UE.instants['ueditorInstant' + me.uid] = me;\r\n    };\r\n    Editor.prototype = {\r\n         registerCommand : function(name,obj){\r\n            this.commands[name] = obj;\r\n         },\r\n        /**\r\n         * 编辑器对外提供的监听ready事件的接口， 通过调用该方法，达到的效果与监听ready事件是一致的\r\n         * @method ready\r\n         * @param { Function } fn 编辑器ready之后所执行的回调, 如果在注册事件之前编辑器已经ready，将会\r\n         * 立即触发该回调。\r\n         * @remind 需要等待编辑器加载完成后才能执行的代码,可以使用该方法传入\r\n         * @example\r\n         * ```javascript\r\n         * editor.ready( function( editor ) {\r\n         *     editor.setContent('初始化完毕');\r\n         * } );\r\n         * ```\r\n         * @see UE.Editor.event:ready\r\n         */\r\n        ready: function (fn) {\r\n            var me = this;\r\n            if (fn) {\r\n                me.isReady ? fn.apply(me) : me.addListener('ready', fn);\r\n            }\r\n        },\r\n\r\n        /**\r\n         * 该方法是提供给插件里面使用，设置配置项默认值\r\n         * @method setOpt\r\n         * @warning 三处设置配置项的优先级: 实例化时传入参数 > setOpt()设置 > config文件里设置\r\n         * @warning 该方法仅供编辑器插件内部和编辑器初始化时调用，其他地方不能调用。\r\n         * @param { String } key 编辑器的可接受的选项名称\r\n         * @param { * } val  该选项可接受的值\r\n         * @example\r\n         * ```javascript\r\n         * editor.setOpt( 'initContent', '欢迎使用编辑器' );\r\n         * ```\r\n         */\r\n\r\n        /**\r\n         * 该方法是提供给插件里面使用，以{key:value}集合的方式设置插件内用到的配置项默认值\r\n         * @method setOpt\r\n         * @warning 三处设置配置项的优先级: 实例化时传入参数 > setOpt()设置 > config文件里设置\r\n         * @warning 该方法仅供编辑器插件内部和编辑器初始化时调用，其他地方不能调用。\r\n         * @param { Object } options 将要设置的选项的键值对对象\r\n         * @example\r\n         * ```javascript\r\n         * editor.setOpt( {\r\n         *     'initContent': '欢迎使用编辑器'\r\n         * } );\r\n         * ```\r\n         */\r\n        setOpt: function (key, val) {\r\n            var obj = {};\r\n            if (utils.isString(key)) {\r\n                obj[key] = val\r\n            } else {\r\n                obj = key;\r\n            }\r\n            utils.extend(this.options, obj, true);\r\n        },\r\n        getOpt:function(key){\r\n            return this.options[key]\r\n        },\r\n        /**\r\n         * 销毁编辑器实例，使用textarea代替\r\n         * @method destroy\r\n         * @example\r\n         * ```javascript\r\n         * editor.destroy();\r\n         * ```\r\n         */\r\n        destroy: function () {\r\n\r\n            var me = this;\r\n            me.fireEvent('destroy');\r\n            var container = me.container.parentNode;\r\n            var textarea = me.textarea;\r\n            if (!textarea) {\r\n                textarea = document.createElement('textarea');\r\n                container.parentNode.insertBefore(textarea, container);\r\n            } else {\r\n                textarea.style.display = ''\r\n            }\r\n\r\n            textarea.style.width = me.iframe.offsetWidth + 'px';\r\n            textarea.style.height = me.iframe.offsetHeight + 'px';\r\n            textarea.value = me.getContent();\r\n            textarea.id = me.key;\r\n            container.innerHTML = '';\r\n            domUtils.remove(container);\r\n            var key = me.key;\r\n            //trace:2004\r\n            for (var p in me) {\r\n                if (me.hasOwnProperty(p)) {\r\n                    delete this[p];\r\n                }\r\n            }\r\n            UE.delEditor(key);\r\n        },\r\n\r\n        /**\r\n         * 渲染编辑器的DOM到指定容器\r\n         * @method render\r\n         * @param { String } containerId 指定一个容器ID\r\n         * @remind 执行该方法,会触发ready事件\r\n         * @warning 必须且只能调用一次\r\n         */\r\n\r\n        /**\r\n         * 渲染编辑器的DOM到指定容器\r\n         * @method render\r\n         * @param { Element } containerDom 直接指定容器对象\r\n         * @remind 执行该方法,会触发ready事件\r\n         * @warning 必须且只能调用一次\r\n         */\r\n        render: function (container) {\r\n            var me = this,\r\n                options = me.options,\r\n                getStyleValue=function(attr){\r\n                    return parseInt(domUtils.getComputedStyle(container,attr));\r\n                };\r\n            if (utils.isString(container)) {\r\n                container = document.getElementById(container);\r\n            }\r\n            if (container) {\r\n                if(options.initialFrameWidth){\r\n                    options.minFrameWidth = options.initialFrameWidth\r\n                }else{\r\n                    options.minFrameWidth = options.initialFrameWidth = container.offsetWidth;\r\n                }\r\n                if(options.initialFrameHeight){\r\n                    options.minFrameHeight = options.initialFrameHeight\r\n                }else{\r\n                    options.initialFrameHeight = options.minFrameHeight = container.offsetHeight;\r\n                }\r\n\r\n                container.style.width = /%$/.test(options.initialFrameWidth) ?  '100%' : options.initialFrameWidth-\r\n                    getStyleValue(\"padding-left\")- getStyleValue(\"padding-right\") +'px';\r\n                container.style.height = /%$/.test(options.initialFrameHeight) ?  '100%' : options.initialFrameHeight -\r\n                    getStyleValue(\"padding-top\")- getStyleValue(\"padding-bottom\") +'px';\r\n\r\n                container.style.zIndex = options.zIndex;\r\n\r\n                var html = ( ie && browser.version < 9  ? '' : '<!DOCTYPE html>') +\r\n                    '<html xmlns=\\'http://www.w3.org/1999/xhtml\\' class=\\'view\\' ><head>' +\r\n                    '<style type=\\'text/css\\'>' +\r\n                    //设置四周的留边\r\n                    '.view{padding:0;word-wrap:break-word;cursor:text;height:90%;}\\n' +\r\n                    //设置默认字体和字号\r\n                    //font-family不能呢随便改，在safari下fillchar会有解析问题\r\n                    'body{margin:8px;font-family:sans-serif;font-size:16px;}' +\r\n                    //设置段落间距\r\n                    'p{margin:5px 0;}</style>' +\r\n                    ( options.iframeCssUrl ? '<link rel=\\'stylesheet\\' type=\\'text/css\\' href=\\'' + utils.unhtml(options.iframeCssUrl) + '\\'/>' : '' ) +\r\n                    (options.initialStyle ? '<style>' + options.initialStyle + '</style>' : '') +\r\n                    '</head><body class=\\'view\\' ></body>' +\r\n                    '<script type=\\'text/javascript\\' ' + (ie ? 'defer=\\'defer\\'' : '' ) +' id=\\'_initialScript\\'>' +\r\n                    'setTimeout(function(){editor = window.parent.UE.instants[\\'ueditorInstant' + me.uid + '\\'];editor._setup(document);},0);' +\r\n                    'var _tmpScript = document.getElementById(\\'_initialScript\\');_tmpScript.parentNode.removeChild(_tmpScript);</script></html>';\r\n                container.appendChild(domUtils.createElement(document, 'iframe', {\r\n                    id: 'ueditor_' + me.uid,\r\n                    width: \"100%\",\r\n                    height: \"100%\",\r\n                    frameborder: \"0\",\r\n                    //先注释掉了，加的原因忘记了，但开启会直接导致全屏模式下内容多时不会出现滚动条\r\n//                    scrolling :'no',\r\n                    src: 'javascript:void(function(){document.open();' + (options.customDomain && document.domain != location.hostname ?  'document.domain=\"' + document.domain + '\";' : '') +\r\n                        'document.write(\"' + html + '\");document.close();}())'\r\n                }));\r\n                container.style.overflow = 'hidden';\r\n                //解决如果是给定的百分比，会导致高度算不对的问题\r\n                setTimeout(function(){\r\n                    if( /%$/.test(options.initialFrameWidth)){\r\n                        options.minFrameWidth = options.initialFrameWidth = container.offsetWidth;\r\n                        //如果这里给定宽度，会导致ie在拖动窗口大小时，编辑区域不随着变化\r\n//                        container.style.width = options.initialFrameWidth + 'px';\r\n                    }\r\n                    if(/%$/.test(options.initialFrameHeight)){\r\n                        options.minFrameHeight = options.initialFrameHeight = container.offsetHeight;\r\n                        container.style.height = options.initialFrameHeight + 'px';\r\n                    }\r\n                })\r\n            }\r\n        },\r\n\r\n        /**\r\n         * 编辑器初始化\r\n         * @method _setup\r\n         * @private\r\n         * @param { Element } doc 编辑器Iframe中的文档对象\r\n         */\r\n        _setup: function (doc) {\r\n\r\n            var me = this,\r\n                options = me.options;\r\n            if (ie) {\r\n                doc.body.disabled = true;\r\n                doc.body.contentEditable = true;\r\n                doc.body.disabled = false;\r\n            } else {\r\n                doc.body.contentEditable = true;\r\n            }\r\n            doc.body.spellcheck = false;\r\n            me.document = doc;\r\n            me.window = doc.defaultView || doc.parentWindow;\r\n            me.iframe = me.window.frameElement;\r\n            me.body = doc.body;\r\n            me.selection = new dom.Selection(doc);\r\n            //gecko初始化就能得到range,无法判断isFocus了\r\n            var geckoSel;\r\n            if (browser.gecko && (geckoSel = this.selection.getNative())) {\r\n                geckoSel.removeAllRanges();\r\n            }\r\n            this._initEvents();\r\n            //为form提交提供一个隐藏的textarea\r\n            for (var form = this.iframe.parentNode; !domUtils.isBody(form); form = form.parentNode) {\r\n                if (form.tagName == 'FORM') {\r\n                    me.form = form;\r\n                    if(me.options.autoSyncData){\r\n                        domUtils.on(me.window,'blur',function(){\r\n                            setValue(form,me);\r\n                        });\r\n                    }else{\r\n                        domUtils.on(form, 'submit', function () {\r\n                            setValue(this, me);\r\n                        });\r\n                    }\r\n                    break;\r\n                }\r\n            }\r\n            if (options.initialContent) {\r\n                if (options.autoClearinitialContent) {\r\n                    var oldExecCommand = me.execCommand;\r\n                    me.execCommand = function () {\r\n                        me.fireEvent('firstBeforeExecCommand');\r\n                        return oldExecCommand.apply(me, arguments);\r\n                    };\r\n                    this._setDefaultContent(options.initialContent);\r\n                } else\r\n                    this.setContent(options.initialContent, false, true);\r\n            }\r\n\r\n            //编辑器不能为空内容\r\n\r\n            if (domUtils.isEmptyNode(me.body)) {\r\n                me.body.innerHTML = '<p>' + (browser.ie ? '' : '<br/>') + '</p>';\r\n            }\r\n            //如果要求focus, 就把光标定位到内容开始\r\n            if (options.focus) {\r\n                setTimeout(function () {\r\n                    me.focus(me.options.focusInEnd);\r\n                    //如果自动清除开着，就不需要做selectionchange;\r\n                    !me.options.autoClearinitialContent && me._selectionChange();\r\n                }, 0);\r\n            }\r\n            if (!me.container) {\r\n                me.container = this.iframe.parentNode;\r\n            }\r\n            if (options.fullscreen && me.ui) {\r\n                me.ui.setFullScreen(true);\r\n            }\r\n\r\n            try {\r\n                me.document.execCommand('2D-position', false, false);\r\n            } catch (e) {\r\n            }\r\n            try {\r\n                me.document.execCommand('enableInlineTableEditing', false, false);\r\n            } catch (e) {\r\n            }\r\n            try {\r\n                me.document.execCommand('enableObjectResizing', false, false);\r\n            } catch (e) {\r\n            }\r\n\r\n            //挂接快捷键\r\n            me._bindshortcutKeys();\r\n            me.isReady = 1;\r\n            me.fireEvent('ready');\r\n            options.onready && options.onready.call(me);\r\n            if (!browser.ie9below) {\r\n                domUtils.on(me.window, ['blur', 'focus'], function (e) {\r\n                    //chrome下会出现alt+tab切换时，导致选区位置不对\r\n                    if (e.type == 'blur') {\r\n                        me._bakRange = me.selection.getRange();\r\n                        try {\r\n                            me._bakNativeRange = me.selection.getNative().getRangeAt(0);\r\n                            me.selection.getNative().removeAllRanges();\r\n                        } catch (e) {\r\n                            me._bakNativeRange = null;\r\n                        }\r\n\r\n                    } else {\r\n                        try {\r\n                            me._bakRange && me._bakRange.select();\r\n                        } catch (e) {\r\n                        }\r\n                    }\r\n                });\r\n            }\r\n            //trace:1518 ff3.6body不够寛，会导致点击空白处无法获得焦点\r\n            if (browser.gecko && browser.version <= 10902) {\r\n                //修复ff3.6初始化进来，不能点击获得焦点\r\n                me.body.contentEditable = false;\r\n                setTimeout(function () {\r\n                    me.body.contentEditable = true;\r\n                }, 100);\r\n                setInterval(function () {\r\n                    me.body.style.height = me.iframe.offsetHeight - 20 + 'px'\r\n                }, 100)\r\n            }\r\n\r\n            !options.isShow && me.setHide();\r\n            options.readonly && me.setDisabled();\r\n        },\r\n\r\n        /**\r\n         * 同步数据到编辑器所在的form\r\n         * 从编辑器的容器节点向上查找form元素，若找到，就同步编辑内容到找到的form里，为提交数据做准备，主要用于是手动提交的情况\r\n         * 后台取得数据的键值，使用你容器上的name属性，如果没有就使用参数里的textarea项\r\n         * @method sync\r\n         * @example\r\n         * ```javascript\r\n         * editor.sync();\r\n         * form.sumbit(); //form变量已经指向了form元素\r\n         * ```\r\n         */\r\n\r\n        /**\r\n         * 根据传入的formId，在页面上查找要同步数据的表单，若找到，就同步编辑内容到找到的form里，为提交数据做准备\r\n         * 后台取得数据的键值，该键值默认使用给定的编辑器容器的name属性，如果没有name属性则使用参数项里给定的“textarea”项\r\n         * @method sync\r\n         * @param { String } formID 指定一个要同步数据的form的id,编辑器的数据会同步到你指定form下\r\n         */\r\n        sync: function (formId) {\r\n            var me = this,\r\n                form = formId ? document.getElementById(formId) :\r\n                    domUtils.findParent(me.iframe.parentNode, function (node) {\r\n                        return node.tagName == 'FORM'\r\n                    }, true);\r\n            form && setValue(form, me);\r\n        },\r\n\r\n        /**\r\n         * 设置编辑器高度\r\n         * @method setHeight\r\n         * @remind 当配置项autoHeightEnabled为真时,该方法无效\r\n         * @param { Number } number 设置的高度值，纯数值，不带单位\r\n         * @example\r\n         * ```javascript\r\n         * editor.setHeight(number);\r\n         * ```\r\n         */\r\n        setHeight: function (height,notSetHeight) {\r\n            if (height !== parseInt(this.iframe.parentNode.style.height)) {\r\n                this.iframe.parentNode.style.height = height + 'px';\r\n            }\r\n            !notSetHeight && (this.options.minFrameHeight = this.options.initialFrameHeight = height);\r\n            this.body.style.height = height + 'px';\r\n            !notSetHeight && this.trigger('setHeight')\r\n        },\r\n\r\n        /**\r\n         * 为编辑器的编辑命令提供快捷键\r\n         * 这个接口是为插件扩展提供的接口,主要是为新添加的插件，如果需要添加快捷键，所提供的接口\r\n         * @method addshortcutkey\r\n         * @param { Object } keyset 命令名和快捷键键值对对象，多个按钮的快捷键用“＋”分隔\r\n         * @example\r\n         * ```javascript\r\n         * editor.addshortcutkey({\r\n         *     \"Bold\" : \"ctrl+66\",//^B\r\n         *     \"Italic\" : \"ctrl+73\", //^I\r\n         * });\r\n         * ```\r\n         */\r\n        /**\r\n         * 这个接口是为插件扩展提供的接口,主要是为新添加的插件，如果需要添加快捷键，所提供的接口\r\n         * @method addshortcutkey\r\n         * @param { String } cmd 触发快捷键时，响应的命令\r\n         * @param { String } keys 快捷键的字符串，多个按钮用“＋”分隔\r\n         * @example\r\n         * ```javascript\r\n         * editor.addshortcutkey(\"Underline\", \"ctrl+85\"); //^U\r\n         * ```\r\n         */\r\n        addshortcutkey: function (cmd, keys) {\r\n            var obj = {};\r\n            if (keys) {\r\n                obj[cmd] = keys\r\n            } else {\r\n                obj = cmd;\r\n            }\r\n            utils.extend(this.shortcutkeys, obj)\r\n        },\r\n\r\n        /**\r\n         * 对编辑器设置keydown事件监听，绑定快捷键和命令，当快捷键组合触发成功，会响应对应的命令\r\n         * @method _bindshortcutKeys\r\n         * @private\r\n         */\r\n        _bindshortcutKeys: function () {\r\n            var me = this, shortcutkeys = this.shortcutkeys;\r\n            me.addListener('keydown', function (type, e) {\r\n                var keyCode = e.keyCode || e.which;\r\n                for (var i in shortcutkeys) {\r\n                    var tmp = shortcutkeys[i].split(',');\r\n                    for (var t = 0, ti; ti = tmp[t++];) {\r\n                        ti = ti.split(':');\r\n                        var key = ti[0], param = ti[1];\r\n                        if (/^(ctrl)(\\+shift)?\\+(\\d+)$/.test(key.toLowerCase()) || /^(\\d+)$/.test(key)) {\r\n                            if (( (RegExp.$1 == 'ctrl' ? (e.ctrlKey || e.metaKey) : 0)\r\n                                && (RegExp.$2 != \"\" ? e[RegExp.$2.slice(1) + \"Key\"] : 1)\r\n                                && keyCode == RegExp.$3\r\n                                ) ||\r\n                                keyCode == RegExp.$1\r\n                                ) {\r\n                                if (me.queryCommandState(i,param) != -1)\r\n                                    me.execCommand(i, param);\r\n                                domUtils.preventDefault(e);\r\n                            }\r\n                        }\r\n                    }\r\n\r\n                }\r\n            });\r\n        },\r\n\r\n        /**\r\n         * 获取编辑器的内容\r\n         * @method getContent\r\n         * @warning 该方法获取到的是经过编辑器内置的过滤规则进行过滤后得到的内容\r\n         * @return { String } 编辑器的内容字符串, 如果编辑器的内容为空，或者是空的标签内容（如:”&lt;p&gt;&lt;br/&gt;&lt;/p&gt;“）， 则返回空字符串\r\n         * @example\r\n         * ```javascript\r\n         * //编辑器html内容:<p>1<strong>2<em>34</em>5</strong>6</p>\r\n         * var content = editor.getContent(); //返回值:<p>1<strong>2<em>34</em>5</strong>6</p>\r\n         * ```\r\n         */\r\n\r\n        /**\r\n         * 获取编辑器的内容。 可以通过参数定义编辑器内置的判空规则\r\n         * @method getContent\r\n         * @param { Function } fn 自定的判空规则， 要求该方法返回一个boolean类型的值，\r\n         *                      代表当前编辑器的内容是否空，\r\n         *                      如果返回true， 则该方法将直接返回空字符串；如果返回false，则编辑器将返回\r\n         *                      经过内置过滤规则处理后的内容。\r\n         * @remind 该方法在处理包含有初始化内容的时候能起到很好的作用。\r\n         * @warning 该方法获取到的是经过编辑器内置的过滤规则进行过滤后得到的内容\r\n         * @return { String } 编辑器的内容字符串\r\n         * @example\r\n         * ```javascript\r\n         * // editor 是一个编辑器的实例\r\n         * var content = editor.getContent( function ( editor ) {\r\n         *      return editor.body.innerHTML === '欢迎使用UEditor'; //返回空字符串\r\n         * } );\r\n         * ```\r\n         */\r\n        getContent: function (cmd, fn,notSetCursor,ignoreBlank,formatter) {\r\n            var me = this;\r\n            if (cmd && utils.isFunction(cmd)) {\r\n                fn = cmd;\r\n                cmd = '';\r\n            }\r\n            if (fn ? !fn() : !this.hasContents()) {\r\n                return '';\r\n            }\r\n            me.fireEvent('beforegetcontent');\r\n            var root = UE.htmlparser(me.body.innerHTML,ignoreBlank);\r\n            me.filterOutputRule(root);\r\n            me.fireEvent('aftergetcontent', cmd,root);\r\n            return  root.toHtml(formatter);\r\n        },\r\n\r\n        /**\r\n         * 取得完整的html代码，可以直接显示成完整的html文档\r\n         * @method getAllHtml\r\n         * @return { String } 编辑器的内容html文档字符串\r\n         * @eaxmple\r\n         * ```javascript\r\n         * editor.getAllHtml(); //返回格式大致是: <html><head>...</head><body>...</body></html>\r\n         * ```\r\n         */\r\n        getAllHtml: function () {\r\n            var me = this,\r\n                headHtml = [],\r\n                html = '';\r\n            me.fireEvent('getAllHtml', headHtml);\r\n            if (browser.ie && browser.version > 8) {\r\n                var headHtmlForIE9 = '';\r\n                utils.each(me.document.styleSheets, function (si) {\r\n                    headHtmlForIE9 += ( si.href ? '<link rel=\"stylesheet\" type=\"text/css\" href=\"' + si.href + '\" />' : '<style>' + si.cssText + '</style>');\r\n                });\r\n                utils.each(me.document.getElementsByTagName('script'), function (si) {\r\n                    headHtmlForIE9 += si.outerHTML;\r\n                });\r\n\r\n            }\r\n            return '<html><head>' + (me.options.charset ? '<meta http-equiv=\"Content-Type\" content=\"text/html; charset=' + me.options.charset + '\"/>' : '')\r\n                + (headHtmlForIE9 || me.document.getElementsByTagName('head')[0].innerHTML) + headHtml.join('\\n') + '</head>'\r\n                + '<body ' + (ie && browser.version < 9 ? 'class=\"view\"' : '') + '>' + me.getContent(null, null, true) + '</body></html>';\r\n        },\r\n\r\n        /**\r\n         * 得到编辑器的纯文本内容，但会保留段落格式\r\n         * @method getPlainTxt\r\n         * @return { String } 编辑器带段落格式的纯文本内容字符串\r\n         * @example\r\n         * ```javascript\r\n         * //编辑器html内容:<p><strong>1</strong></p><p><strong>2</strong></p>\r\n         * console.log(editor.getPlainTxt()); //输出:\"1\\n2\\n\r\n         * ```\r\n         */\r\n        getPlainTxt: function () {\r\n            var reg = new RegExp(domUtils.fillChar, 'g'),\r\n                html = this.body.innerHTML.replace(/[\\n\\r]/g, '');//ie要先去了\\n在处理\r\n            html = html.replace(/<(p|div)[^>]*>(<br\\/?>|&nbsp;)<\\/\\1>/gi, '\\n')\r\n                .replace(/<br\\/?>/gi, '\\n')\r\n                .replace(/<[^>/]+>/g, '')\r\n                .replace(/(\\n)?<\\/([^>]+)>/g, function (a, b, c) {\r\n                    return dtd.$block[c] ? '\\n' : b ? b : '';\r\n                });\r\n            //取出来的空格会有c2a0会变成乱码，处理这种情况\\u00a0\r\n            return html.replace(reg, '').replace(/\\u00a0/g, ' ').replace(/&nbsp;/g, ' ');\r\n        },\r\n\r\n        /**\r\n         * 获取编辑器中的纯文本内容,没有段落格式\r\n         * @method getContentTxt\r\n         * @return { String } 编辑器不带段落格式的纯文本内容字符串\r\n         * @example\r\n         * ```javascript\r\n         * //编辑器html内容:<p><strong>1</strong></p><p><strong>2</strong></p>\r\n         * console.log(editor.getPlainTxt()); //输出:\"12\r\n         * ```\r\n         */\r\n        getContentTxt: function () {\r\n            var reg = new RegExp(domUtils.fillChar, 'g');\r\n            //取出来的空格会有c2a0会变成乱码，处理这种情况\\u00a0\r\n            return this.body[browser.ie ? 'innerText' : 'textContent'].replace(reg, '').replace(/\\u00a0/g, ' ');\r\n        },\r\n\r\n        /**\r\n         * 设置编辑器的内容，可修改编辑器当前的html内容\r\n         * @method setContent\r\n         * @warning 通过该方法插入的内容，是经过编辑器内置的过滤规则进行过滤后得到的内容\r\n         * @warning 该方法会触发selectionchange事件\r\n         * @param { String } html 要插入的html内容\r\n         * @example\r\n         * ```javascript\r\n         * editor.getContent('<p>test</p>');\r\n         * ```\r\n         */\r\n\r\n        /**\r\n         * 设置编辑器的内容，可修改编辑器当前的html内容\r\n         * @method setContent\r\n         * @warning 通过该方法插入的内容，是经过编辑器内置的过滤规则进行过滤后得到的内容\r\n         * @warning 该方法会触发selectionchange事件\r\n         * @param { String } html 要插入的html内容\r\n         * @param { Boolean } isAppendTo 若传入true，不清空原来的内容，在最后插入内容，否则，清空内容再插入\r\n         * @example\r\n         * ```javascript\r\n         * //假设设置前的编辑器内容是 <p>old text</p>\r\n         * editor.setContent('<p>new text</p>', true); //插入的结果是<p>old text</p><p>new text</p>\r\n         * ```\r\n         */\r\n        setContent: function (html, isAppendTo, notFireSelectionchange) {\r\n            var me = this;\r\n\r\n            me.fireEvent('beforesetcontent', html);\r\n            var root = UE.htmlparser(html);\r\n            me.filterInputRule(root);\r\n            html = root.toHtml();\r\n\r\n            me.body.innerHTML = (isAppendTo ? me.body.innerHTML : '') + html;\r\n\r\n\r\n            function isCdataDiv(node){\r\n                return  node.tagName == 'DIV' && node.getAttribute('cdata_tag');\r\n            }\r\n            //给文本或者inline节点套p标签\r\n            if (me.options.enterTag == 'p') {\r\n\r\n                var child = this.body.firstChild, tmpNode;\r\n                if (!child || child.nodeType == 1 &&\r\n                    (dtd.$cdata[child.tagName] || isCdataDiv(child) ||\r\n                        domUtils.isCustomeNode(child)\r\n                        )\r\n                    && child === this.body.lastChild) {\r\n                    this.body.innerHTML = '<p>' + (browser.ie ? '&nbsp;' : '<br/>') + '</p>' + this.body.innerHTML;\r\n\r\n                } else {\r\n                    var p = me.document.createElement('p');\r\n                    while (child) {\r\n                        while (child && (child.nodeType == 3 || child.nodeType == 1 && dtd.p[child.tagName] && !dtd.$cdata[child.tagName])) {\r\n                            tmpNode = child.nextSibling;\r\n                            p.appendChild(child);\r\n                            child = tmpNode;\r\n                        }\r\n                        if (p.firstChild) {\r\n                            if (!child) {\r\n                                me.body.appendChild(p);\r\n                                break;\r\n                            } else {\r\n                                child.parentNode.insertBefore(p, child);\r\n                                p = me.document.createElement('p');\r\n                            }\r\n                        }\r\n                        child = child.nextSibling;\r\n                    }\r\n                }\r\n            }\r\n            me.fireEvent('aftersetcontent');\r\n            me.fireEvent('contentchange');\r\n\r\n            !notFireSelectionchange && me._selectionChange();\r\n            //清除保存的选区\r\n            me._bakRange = me._bakIERange = me._bakNativeRange = null;\r\n            //trace:1742 setContent后gecko能得到焦点问题\r\n            var geckoSel;\r\n            if (browser.gecko && (geckoSel = this.selection.getNative())) {\r\n                geckoSel.removeAllRanges();\r\n            }\r\n            if(me.options.autoSyncData){\r\n                me.form && setValue(me.form,me);\r\n            }\r\n        },\r\n\r\n        /**\r\n         * 让编辑器获得焦点，默认focus到编辑器头部\r\n         * @method focus\r\n         * @example\r\n         * ```javascript\r\n         * editor.focus()\r\n         * ```\r\n         */\r\n\r\n        /**\r\n         * 让编辑器获得焦点，toEnd确定focus位置\r\n         * @method focus\r\n         * @param { Boolean } toEnd 默认focus到编辑器头部，toEnd为true时focus到内容尾部\r\n         * @example\r\n         * ```javascript\r\n         * editor.focus(true)\r\n         * ```\r\n         */\r\n        focus: function (toEnd) {\r\n            try {\r\n                var me = this,\r\n                    rng = me.selection.getRange();\r\n                if (toEnd) {\r\n                    var node = me.body.lastChild;\r\n                    if(node && node.nodeType == 1 && !dtd.$empty[node.tagName]){\r\n                        if(domUtils.isEmptyBlock(node)){\r\n                            rng.setStartAtFirst(node)\r\n                        }else{\r\n                            rng.setStartAtLast(node)\r\n                        }\r\n                        rng.collapse(true);\r\n                    }\r\n                    rng.setCursor(true);\r\n                } else {\r\n                    if(!rng.collapsed && domUtils.isBody(rng.startContainer) && rng.startOffset == 0){\r\n\r\n                        var node = me.body.firstChild;\r\n                        if(node && node.nodeType == 1 && !dtd.$empty[node.tagName]){\r\n                            rng.setStartAtFirst(node).collapse(true);\r\n                        }\r\n                    }\r\n\r\n                    rng.select(true);\r\n\r\n                }\r\n                this.fireEvent('focus selectionchange');\r\n            } catch (e) {\r\n            }\r\n\r\n        },\r\n        isFocus:function(){\r\n            return this.selection.isFocus();\r\n        },\r\n        blur:function(){\r\n            var sel = this.selection.getNative();\r\n            if(sel.empty && browser.ie){\r\n                var nativeRng = document.body.createTextRange();\r\n                nativeRng.moveToElementText(document.body);\r\n                nativeRng.collapse(true);\r\n                nativeRng.select();\r\n                sel.empty()\r\n            }else{\r\n                sel.removeAllRanges()\r\n            }\r\n\r\n            //this.fireEvent('blur selectionchange');\r\n        },\r\n        /**\r\n         * 初始化UE事件及部分事件代理\r\n         * @method _initEvents\r\n         * @private\r\n         */\r\n        _initEvents: function () {\r\n            var me = this,\r\n                doc = me.document,\r\n                win = me.window;\r\n            me._proxyDomEvent = utils.bind(me._proxyDomEvent, me);\r\n            domUtils.on(doc, ['click', 'contextmenu', 'mousedown', 'keydown', 'keyup', 'keypress', 'mouseup', 'mouseover', 'mouseout', 'selectstart'], me._proxyDomEvent);\r\n            domUtils.on(win, ['focus', 'blur'], me._proxyDomEvent);\r\n            domUtils.on(me.body,'drop',function(e){\r\n                //阻止ff下默认的弹出新页面打开图片\r\n                if(browser.gecko && e.stopPropagation) { e.stopPropagation(); }\r\n                me.fireEvent('contentchange')\r\n            });\r\n            domUtils.on(doc, ['mouseup', 'keydown'], function (evt) {\r\n                //特殊键不触发selectionchange\r\n                if (evt.type == 'keydown' && (evt.ctrlKey || evt.metaKey || evt.shiftKey || evt.altKey)) {\r\n                    return;\r\n                }\r\n                if (evt.button == 2)return;\r\n                me._selectionChange(250, evt);\r\n            });\r\n        },\r\n        /**\r\n         * 触发事件代理\r\n         * @method _proxyDomEvent\r\n         * @private\r\n         * @return { * } fireEvent的返回值\r\n         * @see UE.EventBase:fireEvent(String)\r\n         */\r\n        _proxyDomEvent: function (evt) {\r\n            if(this.fireEvent('before' + evt.type.replace(/^on/, '').toLowerCase()) === false){\r\n                return false;\r\n            }\r\n            if(this.fireEvent(evt.type.replace(/^on/, ''), evt) === false){\r\n                return false;\r\n            }\r\n            return this.fireEvent('after' + evt.type.replace(/^on/, '').toLowerCase())\r\n        },\r\n        /**\r\n         * 变化选区\r\n         * @method _selectionChange\r\n         * @private\r\n         */\r\n        _selectionChange: function (delay, evt) {\r\n            var me = this;\r\n            //有光标才做selectionchange 为了解决未focus时点击source不能触发更改工具栏状态的问题（source命令notNeedUndo=1）\r\n//            if ( !me.selection.isFocus() ){\r\n//                return;\r\n//            }\r\n\r\n\r\n            var hackForMouseUp = false;\r\n            var mouseX, mouseY;\r\n            if (browser.ie && browser.version < 9 && evt && evt.type == 'mouseup') {\r\n                var range = this.selection.getRange();\r\n                if (!range.collapsed) {\r\n                    hackForMouseUp = true;\r\n                    mouseX = evt.clientX;\r\n                    mouseY = evt.clientY;\r\n                }\r\n            }\r\n            clearTimeout(_selectionChangeTimer);\r\n            _selectionChangeTimer = setTimeout(function () {\r\n                if (!me.selection || !me.selection.getNative()) {\r\n                    return;\r\n                }\r\n                //修复一个IE下的bug: 鼠标点击一段已选择的文本中间时，可能在mouseup后的一段时间内取到的range是在selection的type为None下的错误值.\r\n                //IE下如果用户是拖拽一段已选择文本，则不会触发mouseup事件，所以这里的特殊处理不会对其有影响\r\n                var ieRange;\r\n                if (hackForMouseUp && me.selection.getNative().type == 'None') {\r\n                    ieRange = me.document.body.createTextRange();\r\n                    try {\r\n                        ieRange.moveToPoint(mouseX, mouseY);\r\n                    } catch (ex) {\r\n                        ieRange = null;\r\n                    }\r\n                }\r\n                var bakGetIERange;\r\n                if (ieRange) {\r\n                    bakGetIERange = me.selection.getIERange;\r\n                    me.selection.getIERange = function () {\r\n                        return ieRange;\r\n                    };\r\n                }\r\n                me.selection.cache();\r\n                if (bakGetIERange) {\r\n                    me.selection.getIERange = bakGetIERange;\r\n                }\r\n                if (me.selection._cachedRange && me.selection._cachedStartElement) {\r\n                    me.fireEvent('beforeselectionchange');\r\n                    // 第二个参数causeByUi为true代表由用户交互造成的selectionchange.\r\n                    me.fireEvent('selectionchange', !!evt);\r\n                    me.fireEvent('afterselectionchange');\r\n                    me.selection.clear();\r\n                }\r\n            }, delay || 50);\r\n        },\r\n\r\n        /**\r\n         * 执行编辑命令\r\n         * @method _callCmdFn\r\n         * @private\r\n         * @param { String } fnName 函数名称\r\n         * @param { * } args 传给命令函数的参数\r\n         * @return { * } 返回命令函数运行的返回值\r\n         */\r\n        _callCmdFn: function (fnName, args) {\r\n            var cmdName = args[0].toLowerCase(),\r\n                cmd, cmdFn;\r\n            cmd = this.commands[cmdName] || UE.commands[cmdName];\r\n            cmdFn = cmd && cmd[fnName];\r\n            //没有querycommandstate或者没有command的都默认返回0\r\n            if ((!cmd || !cmdFn) && fnName == 'queryCommandState') {\r\n                return 0;\r\n            } else if (cmdFn) {\r\n                return cmdFn.apply(this, args);\r\n            }\r\n        },\r\n\r\n        /**\r\n         * 执行编辑命令cmdName，完成富文本编辑效果\r\n         * @method execCommand\r\n         * @param { String } cmdName 需要执行的命令\r\n         * @remind 具体命令的使用请参考<a href=\"#COMMAND.LIST\">命令列表</a>\r\n         * @return { * } 返回命令函数运行的返回值\r\n         * @example\r\n         * ```javascript\r\n         * editor.execCommand(cmdName);\r\n         * ```\r\n         */\r\n        execCommand: function (cmdName) {\r\n            cmdName = cmdName.toLowerCase();\r\n            var me = this,\r\n                result,\r\n                cmd = me.commands[cmdName] || UE.commands[cmdName];\r\n            if (!cmd || !cmd.execCommand) {\r\n                return null;\r\n            }\r\n            if (!cmd.notNeedUndo && !me.__hasEnterExecCommand) {\r\n                me.__hasEnterExecCommand = true;\r\n                if (me.queryCommandState.apply(me,arguments) != -1) {\r\n                    me.fireEvent('saveScene');\r\n                    me.fireEvent.apply(me, ['beforeexeccommand', cmdName].concat(arguments));\r\n                    result = this._callCmdFn('execCommand', arguments);\r\n                    //保存场景时，做了内容对比，再看是否进行contentchange触发，这里多触发了一次，去掉\r\n//                    (!cmd.ignoreContentChange && !me._ignoreContentChange) && me.fireEvent('contentchange');\r\n                    me.fireEvent.apply(me, ['afterexeccommand', cmdName].concat(arguments));\r\n                    me.fireEvent('saveScene');\r\n                }\r\n                me.__hasEnterExecCommand = false;\r\n            } else {\r\n                result = this._callCmdFn('execCommand', arguments);\r\n                (!me.__hasEnterExecCommand && !cmd.ignoreContentChange && !me._ignoreContentChange) && me.fireEvent('contentchange')\r\n            }\r\n            (!me.__hasEnterExecCommand && !cmd.ignoreContentChange && !me._ignoreContentChange) && me._selectionChange();\r\n            return result;\r\n        },\r\n\r\n        /**\r\n         * 根据传入的command命令，查选编辑器当前的选区，返回命令的状态\r\n         * @method  queryCommandState\r\n         * @param { String } cmdName 需要查询的命令名称\r\n         * @remind 具体命令的使用请参考<a href=\"#COMMAND.LIST\">命令列表</a>\r\n         * @return { Number } number 返回放前命令的状态，返回值三种情况：(-1|0|1)\r\n         * @example\r\n         * ```javascript\r\n         * editor.queryCommandState(cmdName)  => (-1|0|1)\r\n         * ```\r\n         * @see COMMAND.LIST\r\n         */\r\n        queryCommandState: function (cmdName) {\r\n            return this._callCmdFn('queryCommandState', arguments);\r\n        },\r\n\r\n        /**\r\n         * 根据传入的command命令，查选编辑器当前的选区，根据命令返回相关的值\r\n         * @method queryCommandValue\r\n         * @param { String } cmdName 需要查询的命令名称\r\n         * @remind 具体命令的使用请参考<a href=\"#COMMAND.LIST\">命令列表</a>\r\n         * @remind 只有部分插件有此方法\r\n         * @return { * } 返回每个命令特定的当前状态值\r\n         * @grammar editor.queryCommandValue(cmdName)  =>  {*}\r\n         * @see COMMAND.LIST\r\n         */\r\n        queryCommandValue: function (cmdName) {\r\n            return this._callCmdFn('queryCommandValue', arguments);\r\n        },\r\n\r\n        /**\r\n         * 检查编辑区域中是否有内容\r\n         * @method  hasContents\r\n         * @remind 默认有文本内容，或者有以下节点都不认为是空\r\n         * table,ul,ol,dl,iframe,area,base,col,hr,img,embed,input,link,meta,param\r\n         * @return { Boolean } 检查有内容返回true，否则返回false\r\n         * @example\r\n         * ```javascript\r\n         * editor.hasContents()\r\n         * ```\r\n         */\r\n\r\n        /**\r\n         * 检查编辑区域中是否有内容，若包含参数tags中的节点类型，直接返回true\r\n         * @method  hasContents\r\n         * @param { Array } tags 传入数组判断时用到的节点类型\r\n         * @return { Boolean } 若文档中包含tags数组里对应的tag，返回true，否则返回false\r\n         * @example\r\n         * ```javascript\r\n         * editor.hasContents(['span']);\r\n         * ```\r\n         */\r\n        hasContents: function (tags) {\r\n            if (tags) {\r\n                for (var i = 0, ci; ci = tags[i++];) {\r\n                    if (this.document.getElementsByTagName(ci).length > 0) {\r\n                        return true;\r\n                    }\r\n                }\r\n            }\r\n            if (!domUtils.isEmptyBlock(this.body)) {\r\n                return true\r\n            }\r\n            //随时添加,定义的特殊标签如果存在，不能认为是空\r\n            tags = ['div'];\r\n            for (i = 0; ci = tags[i++];) {\r\n                var nodes = domUtils.getElementsByTagName(this.document, ci);\r\n                for (var n = 0, cn; cn = nodes[n++];) {\r\n                    if (domUtils.isCustomeNode(cn)) {\r\n                        return true;\r\n                    }\r\n                }\r\n            }\r\n            return false;\r\n        },\r\n\r\n        /**\r\n         * 重置编辑器，可用来做多个tab使用同一个编辑器实例\r\n         * @method  reset\r\n         * @remind 此方法会清空编辑器内容，清空回退列表，会触发reset事件\r\n         * @example\r\n         * ```javascript\r\n         * editor.reset()\r\n         * ```\r\n         */\r\n        reset: function () {\r\n            this.fireEvent('reset');\r\n        },\r\n\r\n        /**\r\n         * 设置当前编辑区域可以编辑\r\n         * @method setEnabled\r\n         * @example\r\n         * ```javascript\r\n         * editor.setEnabled()\r\n         * ```\r\n         */\r\n        setEnabled: function () {\r\n            var me = this, range;\r\n            if (me.body.contentEditable == 'false') {\r\n                me.body.contentEditable = true;\r\n                range = me.selection.getRange();\r\n                //有可能内容丢失了\r\n                try {\r\n                    range.moveToBookmark(me.lastBk);\r\n                    delete me.lastBk\r\n                } catch (e) {\r\n                    range.setStartAtFirst(me.body).collapse(true)\r\n                }\r\n                range.select(true);\r\n                if (me.bkqueryCommandState) {\r\n                    me.queryCommandState = me.bkqueryCommandState;\r\n                    delete me.bkqueryCommandState;\r\n                }\r\n                if (me.bkqueryCommandValue) {\r\n                    me.queryCommandValue = me.bkqueryCommandValue;\r\n                    delete me.bkqueryCommandValue;\r\n                }\r\n                me.fireEvent('selectionchange');\r\n            }\r\n        },\r\n        enable: function () {\r\n            return this.setEnabled();\r\n        },\r\n\r\n        /** 设置当前编辑区域不可编辑\r\n         * @method setDisabled\r\n         */\r\n\r\n        /** 设置当前编辑区域不可编辑,except中的命令除外\r\n         * @method setDisabled\r\n         * @param { String } except 例外命令的字符串\r\n         * @remind 即使设置了disable，此处配置的例外命令仍然可以执行\r\n         * @example\r\n         * ```javascript\r\n         * editor.setDisabled('bold'); //禁用工具栏中除加粗之外的所有功能\r\n         * ```\r\n         */\r\n\r\n        /** 设置当前编辑区域不可编辑,except中的命令除外\r\n         * @method setDisabled\r\n         * @param { Array } except 例外命令的字符串数组，数组中的命令仍然可以执行\r\n         * @remind 即使设置了disable，此处配置的例外命令仍然可以执行\r\n         * @example\r\n         * ```javascript\r\n         * editor.setDisabled(['bold','insertimage']); //禁用工具栏中除加粗和插入图片之外的所有功能\r\n         * ```\r\n         */\r\n        setDisabled: function (except) {\r\n            var me = this;\r\n            except = except ? utils.isArray(except) ? except : [except] : [];\r\n            if (me.body.contentEditable == 'true') {\r\n                if (!me.lastBk) {\r\n                    me.lastBk = me.selection.getRange().createBookmark(true);\r\n                }\r\n                me.body.contentEditable = false;\r\n                me.bkqueryCommandState = me.queryCommandState;\r\n                me.bkqueryCommandValue = me.queryCommandValue;\r\n                me.queryCommandState = function (type) {\r\n                    if (utils.indexOf(except, type) != -1) {\r\n                        return me.bkqueryCommandState.apply(me, arguments);\r\n                    }\r\n                    return -1;\r\n                };\r\n                me.queryCommandValue = function (type) {\r\n                    if (utils.indexOf(except, type) != -1) {\r\n                        return me.bkqueryCommandValue.apply(me, arguments);\r\n                    }\r\n                    return null;\r\n                };\r\n                me.fireEvent('selectionchange');\r\n            }\r\n        },\r\n        disable: function (except) {\r\n            return this.setDisabled(except);\r\n        },\r\n\r\n        /**\r\n         * 设置默认内容\r\n         * @method _setDefaultContent\r\n         * @private\r\n         * @param  { String } cont 要存入的内容\r\n         */\r\n        _setDefaultContent: function () {\r\n            function clear() {\r\n                var me = this;\r\n                if (me.document.getElementById('initContent')) {\r\n                    me.body.innerHTML = '<p>' + (ie ? '' : '<br/>') + '</p>';\r\n                    me.removeListener('firstBeforeExecCommand focus', clear);\r\n                    setTimeout(function () {\r\n                        me.focus();\r\n                        me._selectionChange();\r\n                    }, 0)\r\n                }\r\n            }\r\n\r\n            return function (cont) {\r\n                var me = this;\r\n                me.body.innerHTML = '<p id=\"initContent\">' + cont + '</p>';\r\n\r\n                me.addListener('firstBeforeExecCommand focus', clear);\r\n            }\r\n        }(),\r\n\r\n        /**\r\n         * 显示编辑器\r\n         * @method setShow\r\n         * @example\r\n         * ```javascript\r\n         * editor.setShow()\r\n         * ```\r\n         */\r\n        setShow: function () {\r\n            var me = this, range = me.selection.getRange();\r\n            if (me.container.style.display == 'none') {\r\n                //有可能内容丢失了\r\n                try {\r\n                    range.moveToBookmark(me.lastBk);\r\n                    delete me.lastBk\r\n                } catch (e) {\r\n                    range.setStartAtFirst(me.body).collapse(true)\r\n                }\r\n                //ie下focus实效，所以做了个延迟\r\n                setTimeout(function () {\r\n                    range.select(true);\r\n                }, 100);\r\n                me.container.style.display = '';\r\n            }\r\n\r\n        },\r\n        show: function () {\r\n            return this.setShow();\r\n        },\r\n        /**\r\n         * 隐藏编辑器\r\n         * @method setHide\r\n         * @example\r\n         * ```javascript\r\n         * editor.setHide()\r\n         * ```\r\n         */\r\n        setHide: function () {\r\n            var me = this;\r\n            if (!me.lastBk) {\r\n                me.lastBk = me.selection.getRange().createBookmark(true);\r\n            }\r\n            me.container.style.display = 'none'\r\n        },\r\n        hide: function () {\r\n            return this.setHide();\r\n        },\r\n\r\n        /**\r\n         * 根据指定的路径，获取对应的语言资源\r\n         * @method getLang\r\n         * @param { String } path 路径根据的是lang目录下的语言文件的路径结构\r\n         * @return { Object | String } 根据路径返回语言资源的Json格式对象或者语言字符串\r\n         * @example\r\n         * ```javascript\r\n         * editor.getLang('contextMenu.delete'); //如果当前是中文，那返回是的是'删除'\r\n         * ```\r\n         */\r\n        getLang: function (path) {\r\n            // HaoChuan9421\r\n            if(!this.options){\r\n                return '';\r\n            }\r\n            var lang = UE.I18N[this.options.lang];\r\n            if (!lang) {\r\n                throw Error(\"not import language file\");\r\n            }\r\n            path = (path || \"\").split(\".\");\r\n            for (var i = 0, ci; ci = path[i++];) {\r\n                lang = lang[ci];\r\n                if (!lang)break;\r\n            }\r\n            return lang;\r\n        },\r\n\r\n        /**\r\n         * 计算编辑器html内容字符串的长度\r\n         * @method  getContentLength\r\n         * @return { Number } 返回计算的长度\r\n         * @example\r\n         * ```javascript\r\n         * //编辑器html内容<p><strong>132</strong></p>\r\n         * editor.getContentLength() //返回27\r\n         * ```\r\n         */\r\n        /**\r\n         * 计算编辑器当前纯文本内容的长度\r\n         * @method  getContentLength\r\n         * @param { Boolean } ingoneHtml 传入true时，只按照纯文本来计算\r\n         * @return { Number } 返回计算的长度，内容中有hr/img/iframe标签，长度加1\r\n         * @example\r\n         * ```javascript\r\n         * //编辑器html内容<p><strong>132</strong></p>\r\n         * editor.getContentLength() //返回3\r\n         * ```\r\n         */\r\n        getContentLength: function (ingoneHtml, tagNames) {\r\n            var count = this.getContent(false,false,true).length;\r\n            if (ingoneHtml) {\r\n                tagNames = (tagNames || []).concat([ 'hr', 'img', 'iframe']);\r\n                count = this.getContentTxt().replace(/[\\t\\r\\n]+/g, '').length;\r\n                for (var i = 0, ci; ci = tagNames[i++];) {\r\n                    count += this.document.getElementsByTagName(ci).length;\r\n                }\r\n            }\r\n            return count;\r\n        },\r\n\r\n        /**\r\n         * 注册输入过滤规则\r\n         * @method  addInputRule\r\n         * @param { Function } rule 要添加的过滤规则\r\n         * @example\r\n         * ```javascript\r\n         * editor.addInputRule(function(root){\r\n         *   $.each(root.getNodesByTagName('div'),function(i,node){\r\n         *       node.tagName=\"p\";\r\n         *   });\r\n         * });\r\n         * ```\r\n         */\r\n        addInputRule: function (rule) {\r\n            this.inputRules.push(rule);\r\n        },\r\n\r\n        /**\r\n         * 执行注册的过滤规则\r\n         * @method  filterInputRule\r\n         * @param { UE.uNode } root 要过滤的uNode节点\r\n         * @remind 执行editor.setContent方法和执行'inserthtml'命令后，会运行该过滤函数\r\n         * @example\r\n         * ```javascript\r\n         * editor.filterInputRule(editor.body);\r\n         * ```\r\n         * @see UE.Editor:addInputRule\r\n         */\r\n        filterInputRule: function (root) {\r\n            for (var i = 0, ci; ci = this.inputRules[i++];) {\r\n                ci.call(this, root)\r\n            }\r\n        },\r\n\r\n        /**\r\n         * 注册输出过滤规则\r\n         * @method  addOutputRule\r\n         * @param { Function } rule 要添加的过滤规则\r\n         * @example\r\n         * ```javascript\r\n         * editor.addOutputRule(function(root){\r\n         *   $.each(root.getNodesByTagName('p'),function(i,node){\r\n         *       node.tagName=\"div\";\r\n         *   });\r\n         * });\r\n         * ```\r\n         */\r\n        addOutputRule: function (rule) {\r\n            this.outputRules.push(rule)\r\n        },\r\n\r\n        /**\r\n         * 根据输出过滤规则，过滤编辑器内容\r\n         * @method  filterOutputRule\r\n         * @remind 执行editor.getContent方法的时候，会先运行该过滤函数\r\n         * @param { UE.uNode } root 要过滤的uNode节点\r\n         * @example\r\n         * ```javascript\r\n         * editor.filterOutputRule(editor.body);\r\n         * ```\r\n         * @see UE.Editor:addOutputRule\r\n         */\r\n        filterOutputRule: function (root) {\r\n            for (var i = 0, ci; ci = this.outputRules[i++];) {\r\n                ci.call(this, root)\r\n            }\r\n        },\r\n\r\n        /**\r\n         * 根据action名称获取请求的路径\r\n         * @method  getActionUrl\r\n         * @remind 假如没有设置serverUrl,会根据imageUrl设置默认的controller路径\r\n         * @param { String } action action名称\r\n         * @example\r\n         * ```javascript\r\n         * editor.getActionUrl('config'); //返回 \"/ueditor/php/controller.php?action=config\"\r\n         * editor.getActionUrl('image'); //返回 \"/ueditor/php/controller.php?action=uplaodimage\"\r\n         * editor.getActionUrl('scrawl'); //返回 \"/ueditor/php/controller.php?action=uplaodscrawl\"\r\n         * editor.getActionUrl('imageManager'); //返回 \"/ueditor/php/controller.php?action=listimage\"\r\n         * ```\r\n         */\r\n        getActionUrl: function(action){\r\n            var actionName = this.getOpt(action) || action,\r\n                imageUrl = this.getOpt('imageUrl'),\r\n                serverUrl = this.getOpt('serverUrl');\r\n\r\n            if(!serverUrl && imageUrl) {\r\n                serverUrl = imageUrl.replace(/^(.*[\\/]).+([\\.].+)$/, '$1controller$2');\r\n            }\r\n\r\n            if(serverUrl) {\r\n                serverUrl = serverUrl + (serverUrl.indexOf('?') == -1 ? '?':'&') + 'action=' + (actionName || '');\r\n                return utils.formatUrl(serverUrl);\r\n            } else {\r\n                return '';\r\n            }\r\n        }\r\n    };\r\n    utils.inherits(Editor, EventBase);\r\n})();\r\n\r\n\r\n// core/Editor.defaultoptions.js\r\n//维护编辑器一下默认的不在插件中的配置项\r\nUE.Editor.defaultOptions = function(editor){\r\n\r\n    var _url = editor.options.UEDITOR_HOME_URL;\r\n    return {\r\n        isShow: true,\r\n        initialContent: '',\r\n        initialStyle:'',\r\n        autoClearinitialContent: false,\r\n        iframeCssUrl: _url + 'themes/iframe.css',\r\n        textarea: 'editorValue',\r\n        focus: false,\r\n        focusInEnd: true,\r\n        autoClearEmptyNode: true,\r\n        fullscreen: false,\r\n        readonly: false,\r\n        zIndex: 999,\r\n        imagePopup: true,\r\n        enterTag: 'p',\r\n        customDomain: false,\r\n        lang: 'zh-cn',\r\n        langPath: _url + 'lang/',\r\n        theme: 'default',\r\n        themePath: _url + 'themes/',\r\n        allHtmlEnabled: false,\r\n        scaleEnabled: false,\r\n        tableNativeEditInFF: false,\r\n        autoSyncData : true,\r\n        fileNameFormat: '{time}{rand:6}'\r\n    }\r\n};\r\n\r\n// core/loadconfig.js\r\n(function(){\r\n\r\n    UE.Editor.prototype.loadServerConfig = function(){\r\n        var me = this;\r\n        setTimeout(function(){\r\n            try{\r\n                me.options.imageUrl && me.setOpt('serverUrl', me.options.imageUrl.replace(/^(.*[\\/]).+([\\.].+)$/, '$1controller$2'));\r\n\r\n                var configUrl = me.getActionUrl('config'),\r\n                    isJsonp = utils.isCrossDomainUrl(configUrl);\r\n\r\n                /* 发出ajax请求 */\r\n                me._serverConfigLoaded = false;\r\n\r\n                configUrl && UE.ajax.request(configUrl,{\r\n                    'method': 'GET',\r\n                    'dataType': isJsonp ? 'jsonp':'',\r\n                    'onsuccess':function(r){\r\n                        try {\r\n                            var config = isJsonp ? r:eval(\"(\"+r.responseText+\")\");\r\n                            utils.extend(me.options, config);\r\n                            me.fireEvent('serverConfigLoaded');\r\n                            me._serverConfigLoaded = true;\r\n                        } catch (e) {\r\n                            showErrorMsg(me.getLang('loadconfigFormatError'));\r\n                        }\r\n                    },\r\n                    'onerror':function(){\r\n                        showErrorMsg(me.getLang('loadconfigHttpError'));\r\n                    }\r\n                });\r\n            } catch(e){\r\n                showErrorMsg(me.getLang('loadconfigError'));\r\n            }\r\n        });\r\n\r\n        function showErrorMsg(msg) {\r\n            console && console.error(msg);\r\n            //me.fireEvent('showMessage', {\r\n            //    'title': msg,\r\n            //    'type': 'error'\r\n            //});\r\n        }\r\n    };\r\n\r\n    UE.Editor.prototype.isServerConfigLoaded = function(){\r\n        var me = this;\r\n        return me._serverConfigLoaded || false;\r\n    };\r\n\r\n    UE.Editor.prototype.afterConfigReady = function(handler){\r\n        if (!handler || !utils.isFunction(handler)) return;\r\n        var me = this;\r\n        var readyHandler = function(){\r\n            handler.apply(me, arguments);\r\n            me.removeListener('serverConfigLoaded', readyHandler);\r\n        };\r\n\r\n        if (me.isServerConfigLoaded()) {\r\n            handler.call(me, 'serverConfigLoaded');\r\n        } else {\r\n            me.addListener('serverConfigLoaded', readyHandler);\r\n        }\r\n    };\r\n\r\n})();\r\n\r\n\r\n// core/ajax.js\r\n/**\r\n * @file\r\n * @module UE.ajax\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 提供对ajax请求的支持\r\n * @module UE.ajax\r\n */\r\nUE.ajax = function() {\r\n\r\n    //创建一个ajaxRequest对象\r\n    var fnStr = 'XMLHttpRequest()';\r\n    try {\r\n        new ActiveXObject(\"Msxml2.XMLHTTP\");\r\n        fnStr = 'ActiveXObject(\\'Msxml2.XMLHTTP\\')';\r\n    } catch (e) {\r\n        try {\r\n            new ActiveXObject(\"Microsoft.XMLHTTP\");\r\n            fnStr = 'ActiveXObject(\\'Microsoft.XMLHTTP\\')'\r\n        } catch (e) {\r\n        }\r\n    }\r\n    var creatAjaxRequest = new Function('return new ' + fnStr);\r\n\r\n\r\n    /**\r\n     * 将json参数转化成适合ajax提交的参数列表\r\n     * @param json\r\n     */\r\n    function json2str(json) {\r\n        var strArr = [];\r\n        for (var i in json) {\r\n            //忽略默认的几个参数\r\n            if(i==\"method\" || i==\"timeout\" || i==\"async\" || i==\"dataType\" || i==\"callback\") continue;\r\n            //忽略控制\r\n            if(json[i] == undefined || json[i] == null) continue;\r\n            //传递过来的对象和函数不在提交之列\r\n            if (!((typeof json[i]).toLowerCase() == \"function\" || (typeof json[i]).toLowerCase() == \"object\")) {\r\n                strArr.push( encodeURIComponent(i) + \"=\"+encodeURIComponent(json[i]) );\r\n            } else if (utils.isArray(json[i])) {\r\n            //支持传数组内容\r\n                for(var j = 0; j < json[i].length; j++) {\r\n                    strArr.push( encodeURIComponent(i) + \"[]=\"+encodeURIComponent(json[i][j]) );\r\n                }\r\n            }\r\n        }\r\n        return strArr.join(\"&\");\r\n    }\r\n\r\n    function doAjax(url, ajaxOptions) {\r\n        var xhr = creatAjaxRequest(),\r\n        //是否超时\r\n            timeIsOut = false,\r\n        //默认参数\r\n            defaultAjaxOptions = {\r\n                method:\"POST\",\r\n                timeout:5000,\r\n                async:true,\r\n                data:{},//需要传递对象的话只能覆盖\r\n                onsuccess:function() {\r\n                },\r\n                onerror:function() {\r\n                }\r\n            };\r\n\r\n        if (typeof url === \"object\") {\r\n            ajaxOptions = url;\r\n            url = ajaxOptions.url;\r\n        }\r\n        if (!xhr || !url) return;\r\n        var ajaxOpts = ajaxOptions ? utils.extend(defaultAjaxOptions,ajaxOptions) : defaultAjaxOptions;\r\n\r\n        var submitStr = json2str(ajaxOpts);  // { name:\"Jim\",city:\"Beijing\" } --> \"name=Jim&city=Beijing\"\r\n        //如果用户直接通过data参数传递json对象过来，则也要将此json对象转化为字符串\r\n        if (!utils.isEmptyObject(ajaxOpts.data)){\r\n            submitStr += (submitStr? \"&\":\"\") + json2str(ajaxOpts.data);\r\n        }\r\n        //超时检测\r\n        var timerID = setTimeout(function() {\r\n            if (xhr.readyState != 4) {\r\n                timeIsOut = true;\r\n                xhr.abort();\r\n                clearTimeout(timerID);\r\n            }\r\n        }, ajaxOpts.timeout);\r\n\r\n        var method = ajaxOpts.method.toUpperCase();\r\n        var str = url + (url.indexOf(\"?\")==-1?\"?\":\"&\") + (method==\"POST\"?\"\":submitStr+ \"&noCache=\" + +new Date);\r\n        xhr.open(method, str, ajaxOpts.async);\r\n        xhr.onreadystatechange = function() {\r\n            if (xhr.readyState == 4) {\r\n                if (!timeIsOut && xhr.status == 200) {\r\n                    ajaxOpts.onsuccess(xhr);\r\n                } else {\r\n                    ajaxOpts.onerror(xhr);\r\n                }\r\n            }\r\n        };\r\n        if (method == \"POST\") {\r\n            xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');\r\n            xhr.send(submitStr);\r\n        } else {\r\n            xhr.send(null);\r\n        }\r\n    }\r\n\r\n    function doJsonp(url, opts) {\r\n\r\n        var successhandler = opts.onsuccess || function(){},\r\n            scr = document.createElement('SCRIPT'),\r\n            options = opts || {},\r\n            charset = options['charset'],\r\n            callbackField = options['jsonp'] || 'callback',\r\n            callbackFnName,\r\n            timeOut = options['timeOut'] || 0,\r\n            timer,\r\n            reg = new RegExp('(\\\\?|&)' + callbackField + '=([^&]*)'),\r\n            matches;\r\n\r\n        if (utils.isFunction(successhandler)) {\r\n            callbackFnName = 'bd__editor__' + Math.floor(Math.random() * 2147483648).toString(36);\r\n            window[callbackFnName] = getCallBack(0);\r\n        } else if(utils.isString(successhandler)){\r\n            callbackFnName = successhandler;\r\n        } else {\r\n            if (matches = reg.exec(url)) {\r\n                callbackFnName = matches[2];\r\n            }\r\n        }\r\n\r\n        url = url.replace(reg, '\\x241' + callbackField + '=' + callbackFnName);\r\n\r\n        if (url.search(reg) < 0) {\r\n            url += (url.indexOf('?') < 0 ? '?' : '&') + callbackField + '=' + callbackFnName;\r\n        }\r\n\r\n        var queryStr = json2str(opts);  // { name:\"Jim\",city:\"Beijing\" } --> \"name=Jim&city=Beijing\"\r\n        //如果用户直接通过data参数传递json对象过来，则也要将此json对象转化为字符串\r\n        if (!utils.isEmptyObject(opts.data)){\r\n            queryStr += (queryStr? \"&\":\"\") + json2str(opts.data);\r\n        }\r\n        if (queryStr) {\r\n            url = url.replace(/\\?/, '?' + queryStr + '&');\r\n        }\r\n\r\n        scr.onerror = getCallBack(1);\r\n        if( timeOut ){\r\n            timer = setTimeout(getCallBack(1), timeOut);\r\n        }\r\n        createScriptTag(scr, url, charset);\r\n\r\n        function createScriptTag(scr, url, charset) {\r\n            scr.setAttribute('type', 'text/javascript');\r\n            scr.setAttribute('defer', 'defer');\r\n            charset && scr.setAttribute('charset', charset);\r\n            scr.setAttribute('src', url);\r\n            document.getElementsByTagName('head')[0].appendChild(scr);\r\n        }\r\n\r\n        function getCallBack(onTimeOut){\r\n            return function(){\r\n                try {\r\n                    if(onTimeOut){\r\n                        options.onerror && options.onerror();\r\n                    }else{\r\n                        try{\r\n                            clearTimeout(timer);\r\n                            successhandler.apply(window, arguments);\r\n                        } catch (e){}\r\n                    }\r\n                } catch (exception) {\r\n                    options.onerror && options.onerror.call(window, exception);\r\n                } finally {\r\n                    options.oncomplete && options.oncomplete.apply(window, arguments);\r\n                    scr.parentNode && scr.parentNode.removeChild(scr);\r\n                    window[callbackFnName] = null;\r\n                    try {\r\n                        delete window[callbackFnName];\r\n                    }catch(e){}\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    return {\r\n        /**\r\n         * 根据给定的参数项，向指定的url发起一个ajax请求。 ajax请求完成后，会根据请求结果调用相应回调： 如果请求\r\n         * 成功， 则调用onsuccess回调， 失败则调用 onerror 回调\r\n         * @method request\r\n         * @param { URLString } url ajax请求的url地址\r\n         * @param { Object } ajaxOptions ajax请求选项的键值对，支持的选项如下：\r\n         * @example\r\n         * ```javascript\r\n         * //向sayhello.php发起一个异步的Ajax GET请求, 请求超时时间为10s， 请求完成后执行相应的回调。\r\n         * UE.ajax.requeset( 'sayhello.php', {\r\n         *\r\n         *     //请求方法。可选值： 'GET', 'POST'，默认值是'POST'\r\n         *     method: 'GET',\r\n         *\r\n         *     //超时时间。 默认为5000， 单位是ms\r\n         *     timeout: 10000,\r\n         *\r\n         *     //是否是异步请求。 true为异步请求， false为同步请求\r\n         *     async: true,\r\n         *\r\n         *     //请求携带的数据。如果请求为GET请求， data会经过stringify后附加到请求url之后。\r\n         *     data: {\r\n         *         name: 'ueditor'\r\n         *     },\r\n         *\r\n         *     //请求成功后的回调， 该回调接受当前的XMLHttpRequest对象作为参数。\r\n         *     onsuccess: function ( xhr ) {\r\n         *         console.log( xhr.responseText );\r\n         *     },\r\n         *\r\n         *     //请求失败或者超时后的回调。\r\n         *     onerror: function ( xhr ) {\r\n         *          alert( 'Ajax请求失败' );\r\n         *     }\r\n         *\r\n         * } );\r\n         * ```\r\n         */\r\n\r\n        /**\r\n         * 根据给定的参数项发起一个ajax请求， 参数项里必须包含一个url地址。 ajax请求完成后，会根据请求结果调用相应回调： 如果请求\r\n         * 成功， 则调用onsuccess回调， 失败则调用 onerror 回调。\r\n         * @method request\r\n         * @warning 如果在参数项里未提供一个key为“url”的地址值，则该请求将直接退出。\r\n         * @param { Object } ajaxOptions ajax请求选项的键值对，支持的选项如下：\r\n         * @example\r\n         * ```javascript\r\n         *\r\n         * //向sayhello.php发起一个异步的Ajax POST请求, 请求超时时间为5s， 请求完成后不执行任何回调。\r\n         * UE.ajax.requeset( 'sayhello.php', {\r\n         *\r\n         *     //请求的地址， 该项是必须的。\r\n         *     url: 'sayhello.php'\r\n         *\r\n         * } );\r\n         * ```\r\n         */\r\n\t\trequest:function(url, opts) {\r\n            if (opts && opts.dataType == 'jsonp') {\r\n                doJsonp(url, opts);\r\n            } else {\r\n                doAjax(url, opts);\r\n            }\r\n\t\t},\r\n        getJSONP:function(url, data, fn) {\r\n            var opts = {\r\n                'data': data,\r\n                'oncomplete': fn\r\n            };\r\n            doJsonp(url, opts);\r\n\t\t}\r\n\t};\r\n\r\n\r\n}();\r\n\r\n\r\n// core/filterword.js\r\n/**\r\n * UE过滤word的静态方法\r\n * @file\r\n */\r\n\r\n/**\r\n * UEditor公用空间，UEditor所有的功能都挂载在该空间下\r\n * @module UE\r\n */\r\n\r\n\r\n/**\r\n * 根据传入html字符串过滤word\r\n * @module UE\r\n * @since 1.2.6.1\r\n * @method filterWord\r\n * @param { String } html html字符串\r\n * @return { String } 已过滤后的结果字符串\r\n * @example\r\n * ```javascript\r\n * UE.filterWord(html);\r\n * ```\r\n */\r\nvar filterWord = UE.filterWord = function () {\r\n\r\n    //是否是word过来的内容\r\n    function isWordDocument( str ) {\r\n        return /(class=\"?Mso|style=\"[^\"]*\\bmso\\-|w:WordDocument|<(v|o):|lang=)/ig.test( str );\r\n    }\r\n    //去掉小数\r\n    function transUnit( v ) {\r\n        v = v.replace( /[\\d.]+\\w+/g, function ( m ) {\r\n            return utils.transUnitToPx(m);\r\n        } );\r\n        return v;\r\n    }\r\n\r\n    function filterPasteWord( str ) {\r\n        return str.replace(/[\\t\\r\\n]+/g,' ')\r\n                .replace( /<!--[\\s\\S]*?-->/ig, \"\" )\r\n                //转换图片\r\n                .replace(/<v:shape [^>]*>[\\s\\S]*?.<\\/v:shape>/gi,function(str){\r\n                    //opera能自己解析出image所这里直接返回空\r\n                    if(browser.opera){\r\n                        return '';\r\n                    }\r\n                    try{\r\n                        //有可能是bitmap占为图，无用，直接过滤掉，主要体现在粘贴excel表格中\r\n                        if(/Bitmap/i.test(str)){\r\n                            return '';\r\n                        }\r\n                        var width = str.match(/width:([ \\d.]*p[tx])/i)[1],\r\n                            height = str.match(/height:([ \\d.]*p[tx])/i)[1],\r\n                            src =  str.match(/src=\\s*\"([^\"]*)\"/i)[1];\r\n                        return '<img width=\"'+ transUnit(width) +'\" height=\"'+transUnit(height) +'\" src=\"' + src + '\" />';\r\n                    } catch(e){\r\n                        return '';\r\n                    }\r\n                })\r\n                //针对wps添加的多余标签处理\r\n                .replace(/<\\/?div[^>]*>/g,'')\r\n                //去掉多余的属性\r\n                .replace( /v:\\w+=([\"']?)[^'\"]+\\1/g, '' )\r\n                .replace( /<(!|script[^>]*>.*?<\\/script(?=[>\\s])|\\/?(\\?xml(:\\w+)?|xml|meta|link|style|\\w+:\\w+)(?=[\\s\\/>]))[^>]*>/gi, \"\" )\r\n                .replace( /<p [^>]*class=\"?MsoHeading\"?[^>]*>(.*?)<\\/p>/gi, \"<p><strong>$1</strong></p>\" )\r\n                //去掉多余的属性\r\n                .replace( /\\s+(class|lang|align)\\s*=\\s*(['\"]?)([\\w-]+)\\2/ig, function(str,name,marks,val){\r\n                    //保留list的标示\r\n                    return name == 'class' && val == 'MsoListParagraph' ? str : ''\r\n                })\r\n                //清除多余的font/span不能匹配&nbsp;有可能是空格\r\n                .replace( /<(font|span)[^>]*>(\\s*)<\\/\\1>/gi, function(a,b,c){\r\n                    return c.replace(/[\\t\\r\\n ]+/g,' ')\r\n                })\r\n                //处理style的问题\r\n                .replace( /(<[a-z][^>]*)\\sstyle=([\"'])([^\\2]*?)\\2/gi, function( str, tag, tmp, style ) {\r\n                    var n = [],\r\n                        s = style.replace( /^\\s+|\\s+$/, '' )\r\n                            .replace(/&#39;/g,'\\'')\r\n                            .replace( /&quot;/gi, \"'\" )\r\n                            .replace(/[\\d.]+(cm|pt)/g,function(str){\r\n                                return utils.transUnitToPx(str)\r\n                            })\r\n                            .split( /;\\s*/g );\r\n\r\n                    for ( var i = 0,v; v = s[i];i++ ) {\r\n\r\n                        var name, value,\r\n                            parts = v.split( \":\" );\r\n\r\n                        if ( parts.length == 2 ) {\r\n                            name = parts[0].toLowerCase();\r\n                            value = parts[1].toLowerCase();\r\n                            if(/^(background)\\w*/.test(name) && value.replace(/(initial|\\s)/g,'').length == 0\r\n                                ||\r\n                                /^(margin)\\w*/.test(name) && /^0\\w+$/.test(value)\r\n                            ){\r\n                                continue;\r\n                            }\r\n\r\n                            switch ( name ) {\r\n                                case \"mso-padding-alt\":\r\n                                case \"mso-padding-top-alt\":\r\n                                case \"mso-padding-right-alt\":\r\n                                case \"mso-padding-bottom-alt\":\r\n                                case \"mso-padding-left-alt\":\r\n                                case \"mso-margin-alt\":\r\n                                case \"mso-margin-top-alt\":\r\n                                case \"mso-margin-right-alt\":\r\n                                case \"mso-margin-bottom-alt\":\r\n                                case \"mso-margin-left-alt\":\r\n                                //ie下会出现挤到一起的情况\r\n                               //case \"mso-table-layout-alt\":\r\n                                case \"mso-height\":\r\n                                case \"mso-width\":\r\n                                case \"mso-vertical-align-alt\":\r\n                                    //trace:1819 ff下会解析出padding在table上\r\n                                    if(!/<table/.test(tag))\r\n                                        n[i] = name.replace( /^mso-|-alt$/g, \"\" ) + \":\" + transUnit( value );\r\n                                    continue;\r\n                                case \"horiz-align\":\r\n                                    n[i] = \"text-align:\" + value;\r\n                                    continue;\r\n\r\n                                case \"vert-align\":\r\n                                    n[i] = \"vertical-align:\" + value;\r\n                                    continue;\r\n\r\n                                case \"font-color\":\r\n                                case \"mso-foreground\":\r\n                                    n[i] = \"color:\" + value;\r\n                                    continue;\r\n\r\n                                case \"mso-background\":\r\n                                case \"mso-highlight\":\r\n                                    n[i] = \"background:\" + value;\r\n                                    continue;\r\n\r\n                                case \"mso-default-height\":\r\n                                    n[i] = \"min-height:\" + transUnit( value );\r\n                                    continue;\r\n\r\n                                case \"mso-default-width\":\r\n                                    n[i] = \"min-width:\" + transUnit( value );\r\n                                    continue;\r\n\r\n                                case \"mso-padding-between-alt\":\r\n                                    n[i] = \"border-collapse:separate;border-spacing:\" + transUnit( value );\r\n                                    continue;\r\n\r\n                                case \"text-line-through\":\r\n                                    if ( (value == \"single\") || (value == \"double\") ) {\r\n                                        n[i] = \"text-decoration:line-through\";\r\n                                    }\r\n                                    continue;\r\n                                case \"mso-zero-height\":\r\n                                    if ( value == \"yes\" ) {\r\n                                        n[i] = \"display:none\";\r\n                                    }\r\n                                    continue;\r\n//                                case 'background':\r\n//                                    break;\r\n                                case 'margin':\r\n                                    if ( !/[1-9]/.test( value ) ) {\r\n                                        continue;\r\n                                    }\r\n\r\n                            }\r\n\r\n                            if ( /^(mso|column|font-emph|lang|layout|line-break|list-image|nav|panose|punct|row|ruby|sep|size|src|tab-|table-border|text-(?:decor|trans)|top-bar|version|vnd|word-break)/.test( name )\r\n                                ||\r\n                                /text\\-indent|padding|margin/.test(name) && /\\-[\\d.]+/.test(value)\r\n                            ) {\r\n                                continue;\r\n                            }\r\n\r\n                            n[i] = name + \":\" + parts[1];\r\n                        }\r\n                    }\r\n                    return tag + (n.length ? ' style=\"' + n.join( ';').replace(/;{2,}/g,';') + '\"' : '');\r\n                })\r\n\r\n\r\n    }\r\n\r\n    return function ( html ) {\r\n        return (isWordDocument( html ) ? filterPasteWord( html ) : html);\r\n    };\r\n}();\r\n\r\n// core/node.js\r\n/**\r\n * 编辑器模拟的节点类\r\n * @file\r\n * @module UE\r\n * @class uNode\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * UEditor公用空间，UEditor所有的功能都挂载在该空间下\r\n * @unfile\r\n * @module UE\r\n */\r\n\r\n(function () {\r\n\r\n    /**\r\n     * 编辑器模拟的节点类\r\n     * @unfile\r\n     * @module UE\r\n     * @class uNode\r\n     */\r\n\r\n    /**\r\n     * 通过一个键值对，创建一个uNode对象\r\n     * @constructor\r\n     * @param { Object } attr 传入要创建的uNode的初始属性\r\n     * @example\r\n     * ```javascript\r\n     * var node = new uNode({\r\n     *     type:'element',\r\n     *     tagName:'span',\r\n     *     attrs:{style:'font-size:14px;'}\r\n     * }\r\n     * ```\r\n     */\r\n    var uNode = UE.uNode = function (obj) {\r\n        this.type = obj.type;\r\n        this.data = obj.data;\r\n        this.tagName = obj.tagName;\r\n        this.parentNode = obj.parentNode;\r\n        this.attrs = obj.attrs || {};\r\n        this.children = obj.children;\r\n    };\r\n\r\n    var notTransAttrs = {\r\n        'href':1,\r\n        'src':1,\r\n        '_src':1,\r\n        '_href':1,\r\n        'cdata_data':1\r\n    };\r\n\r\n    var notTransTagName = {\r\n        style:1,\r\n        script:1\r\n    };\r\n\r\n    var indentChar = '    ',\r\n        breakChar = '\\n';\r\n\r\n    function insertLine(arr, current, begin) {\r\n        arr.push(breakChar);\r\n        return current + (begin ? 1 : -1);\r\n    }\r\n\r\n    function insertIndent(arr, current) {\r\n        //插入缩进\r\n        for (var i = 0; i < current; i++) {\r\n            arr.push(indentChar);\r\n        }\r\n    }\r\n\r\n    //创建uNode的静态方法\r\n    //支持标签和html\r\n    uNode.createElement = function (html) {\r\n        if (/[<>]/.test(html)) {\r\n            return UE.htmlparser(html).children[0]\r\n        } else {\r\n            return new uNode({\r\n                type:'element',\r\n                children:[],\r\n                tagName:html\r\n            })\r\n        }\r\n    };\r\n    uNode.createText = function (data,noTrans) {\r\n        return new UE.uNode({\r\n            type:'text',\r\n            'data':noTrans ? data : utils.unhtml(data || '')\r\n        })\r\n    };\r\n    function nodeToHtml(node, arr, formatter, current) {\r\n        switch (node.type) {\r\n            case 'root':\r\n                for (var i = 0, ci; ci = node.children[i++];) {\r\n                    //插入新行\r\n                    if (formatter && ci.type == 'element' && !dtd.$inlineWithA[ci.tagName] && i > 1) {\r\n                        insertLine(arr, current, true);\r\n                        insertIndent(arr, current)\r\n                    }\r\n                    nodeToHtml(ci, arr, formatter, current)\r\n                }\r\n                break;\r\n            case 'text':\r\n                isText(node, arr);\r\n                break;\r\n            case 'element':\r\n                isElement(node, arr, formatter, current);\r\n                break;\r\n            case 'comment':\r\n                isComment(node, arr, formatter);\r\n        }\r\n        return arr;\r\n    }\r\n\r\n    function isText(node, arr) {\r\n        if(node.parentNode.tagName == 'pre'){\r\n            //源码模式下输入html标签，不能做转换处理，直接输出\r\n            arr.push(node.data)\r\n        }else{\r\n            arr.push(notTransTagName[node.parentNode.tagName] ? utils.html(node.data) : node.data.replace(/[ ]{2}/g,' &nbsp;'))\r\n        }\r\n\r\n    }\r\n\r\n    function isElement(node, arr, formatter, current) {\r\n        var attrhtml = '';\r\n        if (node.attrs) {\r\n            attrhtml = [];\r\n            var attrs = node.attrs;\r\n            for (var a in attrs) {\r\n                //这里就针对\r\n                //<p>'<img src='http://nsclick.baidu.com/u.gif?&asdf=\\\"sdf&asdfasdfs;asdf'></p>\r\n                //这里边的\\\"做转换，要不用innerHTML直接被截断了，属性src\r\n                //有可能做的不够\r\n                attrhtml.push(a + (attrs[a] !== undefined ? '=\"' + (notTransAttrs[a] ? utils.html(attrs[a]).replace(/[\"]/g, function (a) {\r\n                   return '&quot;'\r\n                }) : utils.unhtml(attrs[a])) + '\"' : ''))\r\n            }\r\n            attrhtml = attrhtml.join(' ');\r\n        }\r\n        arr.push('<' + node.tagName +\r\n            (attrhtml ? ' ' + attrhtml  : '') +\r\n            (dtd.$empty[node.tagName] ? '\\/' : '' ) + '>'\r\n        );\r\n        //插入新行\r\n        if (formatter  &&  !dtd.$inlineWithA[node.tagName] && node.tagName != 'pre') {\r\n            if(node.children && node.children.length){\r\n                current = insertLine(arr, current, true);\r\n                insertIndent(arr, current)\r\n            }\r\n\r\n        }\r\n        if (node.children && node.children.length) {\r\n            for (var i = 0, ci; ci = node.children[i++];) {\r\n                if (formatter && ci.type == 'element' &&  !dtd.$inlineWithA[ci.tagName] && i > 1) {\r\n                    insertLine(arr, current);\r\n                    insertIndent(arr, current)\r\n                }\r\n                nodeToHtml(ci, arr, formatter, current)\r\n            }\r\n        }\r\n        if (!dtd.$empty[node.tagName]) {\r\n            if (formatter && !dtd.$inlineWithA[node.tagName]  && node.tagName != 'pre') {\r\n\r\n                if(node.children && node.children.length){\r\n                    current = insertLine(arr, current);\r\n                    insertIndent(arr, current)\r\n                }\r\n            }\r\n            arr.push('<\\/' + node.tagName + '>');\r\n        }\r\n\r\n    }\r\n\r\n    function isComment(node, arr) {\r\n        arr.push('<!--' + node.data + '-->');\r\n    }\r\n\r\n    function getNodeById(root, id) {\r\n        var node;\r\n        if (root.type == 'element' && root.getAttr('id') == id) {\r\n            return root;\r\n        }\r\n        if (root.children && root.children.length) {\r\n            for (var i = 0, ci; ci = root.children[i++];) {\r\n                if (node = getNodeById(ci, id)) {\r\n                    return node;\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    function getNodesByTagName(node, tagName, arr) {\r\n        if (node.type == 'element' && node.tagName == tagName) {\r\n            arr.push(node);\r\n        }\r\n        if (node.children && node.children.length) {\r\n            for (var i = 0, ci; ci = node.children[i++];) {\r\n                getNodesByTagName(ci, tagName, arr)\r\n            }\r\n        }\r\n    }\r\n    function nodeTraversal(root,fn){\r\n        if(root.children && root.children.length){\r\n            for(var i= 0,ci;ci=root.children[i];){\r\n                nodeTraversal(ci,fn);\r\n                //ci被替换的情况，这里就不再走 fn了\r\n                if(ci.parentNode ){\r\n                    if(ci.children && ci.children.length){\r\n                        fn(ci)\r\n                    }\r\n                    if(ci.parentNode) i++\r\n                }\r\n            }\r\n        }else{\r\n            fn(root)\r\n        }\r\n\r\n    }\r\n    uNode.prototype = {\r\n\r\n        /**\r\n         * 当前节点对象，转换成html文本\r\n         * @method toHtml\r\n         * @return { String } 返回转换后的html字符串\r\n         * @example\r\n         * ```javascript\r\n         * node.toHtml();\r\n         * ```\r\n         */\r\n\r\n        /**\r\n         * 当前节点对象，转换成html文本\r\n         * @method toHtml\r\n         * @param { Boolean } formatter 是否格式化返回值\r\n         * @return { String } 返回转换后的html字符串\r\n         * @example\r\n         * ```javascript\r\n         * node.toHtml( true );\r\n         * ```\r\n         */\r\n        toHtml:function (formatter) {\r\n            var arr = [];\r\n            nodeToHtml(this, arr, formatter, 0);\r\n            return arr.join('')\r\n        },\r\n\r\n        /**\r\n         * 获取节点的html内容\r\n         * @method innerHTML\r\n         * @warning 假如节点的type不是'element'，或节点的标签名称不在dtd列表里，直接返回当前节点\r\n         * @return { String } 返回节点的html内容\r\n         * @example\r\n         * ```javascript\r\n         * var htmlstr = node.innerHTML();\r\n         * ```\r\n         */\r\n\r\n        /**\r\n         * 设置节点的html内容\r\n         * @method innerHTML\r\n         * @warning 假如节点的type不是'element'，或节点的标签名称不在dtd列表里，直接返回当前节点\r\n         * @param { String } htmlstr 传入要设置的html内容\r\n         * @return { UE.uNode } 返回节点本身\r\n         * @example\r\n         * ```javascript\r\n         * node.innerHTML('<span>text</span>');\r\n         * ```\r\n         */\r\n        innerHTML:function (htmlstr) {\r\n            if (this.type != 'element' || dtd.$empty[this.tagName]) {\r\n                return this;\r\n            }\r\n            if (utils.isString(htmlstr)) {\r\n                if(this.children){\r\n                    for (var i = 0, ci; ci = this.children[i++];) {\r\n                        ci.parentNode = null;\r\n                    }\r\n                }\r\n                this.children = [];\r\n                var tmpRoot = UE.htmlparser(htmlstr);\r\n                for (var i = 0, ci; ci = tmpRoot.children[i++];) {\r\n                    this.children.push(ci);\r\n                    ci.parentNode = this;\r\n                }\r\n                return this;\r\n            } else {\r\n                var tmpRoot = new UE.uNode({\r\n                    type:'root',\r\n                    children:this.children\r\n                });\r\n                return tmpRoot.toHtml();\r\n            }\r\n        },\r\n\r\n        /**\r\n         * 获取节点的纯文本内容\r\n         * @method innerText\r\n         * @warning 假如节点的type不是'element'，或节点的标签名称不在dtd列表里，直接返回当前节点\r\n         * @return { String } 返回节点的存文本内容\r\n         * @example\r\n         * ```javascript\r\n         * var textStr = node.innerText();\r\n         * ```\r\n         */\r\n\r\n        /**\r\n         * 设置节点的纯文本内容\r\n         * @method innerText\r\n         * @warning 假如节点的type不是'element'，或节点的标签名称不在dtd列表里，直接返回当前节点\r\n         * @param { String } textStr 传入要设置的文本内容\r\n         * @return { UE.uNode } 返回节点本身\r\n         * @example\r\n         * ```javascript\r\n         * node.innerText('<span>text</span>');\r\n         * ```\r\n         */\r\n        innerText:function (textStr,noTrans) {\r\n            if (this.type != 'element' || dtd.$empty[this.tagName]) {\r\n                return this;\r\n            }\r\n            if (textStr) {\r\n                if(this.children){\r\n                    for (var i = 0, ci; ci = this.children[i++];) {\r\n                        ci.parentNode = null;\r\n                    }\r\n                }\r\n                this.children = [];\r\n                this.appendChild(uNode.createText(textStr,noTrans));\r\n                return this;\r\n            } else {\r\n                return this.toHtml().replace(/<[^>]+>/g, '');\r\n            }\r\n        },\r\n\r\n        /**\r\n         * 获取当前对象的data属性\r\n         * @method getData\r\n         * @return { Object } 若节点的type值是elemenet，返回空字符串，否则返回节点的data属性\r\n         * @example\r\n         * ```javascript\r\n         * node.getData();\r\n         * ```\r\n         */\r\n        getData:function () {\r\n            if (this.type == 'element')\r\n                return '';\r\n            return this.data\r\n        },\r\n\r\n        /**\r\n         * 获取当前节点下的第一个子节点\r\n         * @method firstChild\r\n         * @return { UE.uNode } 返回第一个子节点\r\n         * @example\r\n         * ```javascript\r\n         * node.firstChild(); //返回第一个子节点\r\n         * ```\r\n         */\r\n        firstChild:function () {\r\n//            if (this.type != 'element' || dtd.$empty[this.tagName]) {\r\n//                return this;\r\n//            }\r\n            return this.children ? this.children[0] : null;\r\n        },\r\n\r\n        /**\r\n         * 获取当前节点下的最后一个子节点\r\n         * @method lastChild\r\n         * @return { UE.uNode } 返回最后一个子节点\r\n         * @example\r\n         * ```javascript\r\n         * node.lastChild(); //返回最后一个子节点\r\n         * ```\r\n         */\r\n        lastChild:function () {\r\n//            if (this.type != 'element' || dtd.$empty[this.tagName] ) {\r\n//                return this;\r\n//            }\r\n            return this.children ? this.children[this.children.length - 1] : null;\r\n        },\r\n\r\n        /**\r\n         * 获取和当前节点有相同父亲节点的前一个节点\r\n         * @method previousSibling\r\n         * @return { UE.uNode } 返回前一个节点\r\n         * @example\r\n         * ```javascript\r\n         * node.children[2].previousSibling(); //返回子节点node.children[1]\r\n         * ```\r\n         */\r\n        previousSibling : function(){\r\n            var parent = this.parentNode;\r\n            for (var i = 0, ci; ci = parent.children[i]; i++) {\r\n                if (ci === this) {\r\n                   return i == 0 ? null : parent.children[i-1];\r\n                }\r\n            }\r\n\r\n        },\r\n\r\n        /**\r\n         * 获取和当前节点有相同父亲节点的后一个节点\r\n         * @method nextSibling\r\n         * @return { UE.uNode } 返回后一个节点,找不到返回null\r\n         * @example\r\n         * ```javascript\r\n         * node.children[2].nextSibling(); //如果有，返回子节点node.children[3]\r\n         * ```\r\n         */\r\n        nextSibling : function(){\r\n            var parent = this.parentNode;\r\n            for (var i = 0, ci; ci = parent.children[i++];) {\r\n                if (ci === this) {\r\n                    return parent.children[i];\r\n                }\r\n            }\r\n        },\r\n\r\n        /**\r\n         * 用新的节点替换当前节点\r\n         * @method replaceChild\r\n         * @param { UE.uNode } target 要替换成该节点参数\r\n         * @param { UE.uNode } source 要被替换掉的节点\r\n         * @return { UE.uNode } 返回替换之后的节点对象\r\n         * @example\r\n         * ```javascript\r\n         * node.replaceChild(newNode, childNode); //用newNode替换childNode,childNode是node的子节点\r\n         * ```\r\n         */\r\n        replaceChild:function (target, source) {\r\n            if (this.children) {\r\n                if(target.parentNode){\r\n                    target.parentNode.removeChild(target);\r\n                }\r\n                for (var i = 0, ci; ci = this.children[i]; i++) {\r\n                    if (ci === source) {\r\n                        this.children.splice(i, 1, target);\r\n                        source.parentNode = null;\r\n                        target.parentNode = this;\r\n                        return target;\r\n                    }\r\n                }\r\n            }\r\n        },\r\n\r\n        /**\r\n         * 在节点的子节点列表最后位置插入一个节点\r\n         * @method appendChild\r\n         * @param { UE.uNode } node 要插入的节点\r\n         * @return { UE.uNode } 返回刚插入的子节点\r\n         * @example\r\n         * ```javascript\r\n         * node.appendChild( newNode ); //在node内插入子节点newNode\r\n         * ```\r\n         */\r\n        appendChild:function (node) {\r\n            if (this.type == 'root' || (this.type == 'element' && !dtd.$empty[this.tagName])) {\r\n                if (!this.children) {\r\n                    this.children = []\r\n                }\r\n                if(node.parentNode){\r\n                    node.parentNode.removeChild(node);\r\n                }\r\n                for (var i = 0, ci; ci = this.children[i]; i++) {\r\n                    if (ci === node) {\r\n                        this.children.splice(i, 1);\r\n                        break;\r\n                    }\r\n                }\r\n                this.children.push(node);\r\n                node.parentNode = this;\r\n                return node;\r\n            }\r\n\r\n\r\n        },\r\n\r\n        /**\r\n         * 在传入节点的前面插入一个节点\r\n         * @method insertBefore\r\n         * @param { UE.uNode } target 要插入的节点\r\n         * @param { UE.uNode } source 在该参数节点前面插入\r\n         * @return { UE.uNode } 返回刚插入的子节点\r\n         * @example\r\n         * ```javascript\r\n         * node.parentNode.insertBefore(newNode, node); //在node节点后面插入newNode\r\n         * ```\r\n         */\r\n        insertBefore:function (target, source) {\r\n            if (this.children) {\r\n                if(target.parentNode){\r\n                    target.parentNode.removeChild(target);\r\n                }\r\n                for (var i = 0, ci; ci = this.children[i]; i++) {\r\n                    if (ci === source) {\r\n                        this.children.splice(i, 0, target);\r\n                        target.parentNode = this;\r\n                        return target;\r\n                    }\r\n                }\r\n\r\n            }\r\n        },\r\n\r\n        /**\r\n         * 在传入节点的后面插入一个节点\r\n         * @method insertAfter\r\n         * @param { UE.uNode } target 要插入的节点\r\n         * @param { UE.uNode } source 在该参数节点后面插入\r\n         * @return { UE.uNode } 返回刚插入的子节点\r\n         * @example\r\n         * ```javascript\r\n         * node.parentNode.insertAfter(newNode, node); //在node节点后面插入newNode\r\n         * ```\r\n         */\r\n        insertAfter:function (target, source) {\r\n            if (this.children) {\r\n                if(target.parentNode){\r\n                    target.parentNode.removeChild(target);\r\n                }\r\n                for (var i = 0, ci; ci = this.children[i]; i++) {\r\n                    if (ci === source) {\r\n                        this.children.splice(i + 1, 0, target);\r\n                        target.parentNode = this;\r\n                        return target;\r\n                    }\r\n\r\n                }\r\n            }\r\n        },\r\n\r\n        /**\r\n         * 从当前节点的子节点列表中，移除节点\r\n         * @method removeChild\r\n         * @param { UE.uNode } node 要移除的节点引用\r\n         * @param { Boolean } keepChildren 是否保留移除节点的子节点，若传入true，自动把移除节点的子节点插入到移除的位置\r\n         * @return { * } 返回刚移除的子节点\r\n         * @example\r\n         * ```javascript\r\n         * node.removeChild(childNode,true); //在node的子节点列表中移除child节点，并且吧child的子节点插入到移除的位置\r\n         * ```\r\n         */\r\n        removeChild:function (node,keepChildren) {\r\n            if (this.children) {\r\n                for (var i = 0, ci; ci = this.children[i]; i++) {\r\n                    if (ci === node) {\r\n                        this.children.splice(i, 1);\r\n                        ci.parentNode = null;\r\n                        if(keepChildren && ci.children && ci.children.length){\r\n                            for(var j= 0,cj;cj=ci.children[j];j++){\r\n                                this.children.splice(i+j,0,cj);\r\n                                cj.parentNode = this;\r\n\r\n                            }\r\n                        }\r\n                        return ci;\r\n                    }\r\n                }\r\n            }\r\n        },\r\n\r\n        /**\r\n         * 获取当前节点所代表的元素属性，即获取attrs对象下的属性值\r\n         * @method getAttr\r\n         * @param { String } attrName 要获取的属性名称\r\n         * @return { * } 返回attrs对象下的属性值\r\n         * @example\r\n         * ```javascript\r\n         * node.getAttr('title');\r\n         * ```\r\n         */\r\n        getAttr:function (attrName) {\r\n            return this.attrs && this.attrs[attrName.toLowerCase()]\r\n        },\r\n\r\n        /**\r\n         * 设置当前节点所代表的元素属性，即设置attrs对象下的属性值\r\n         * @method setAttr\r\n         * @param { String } attrName 要设置的属性名称\r\n         * @param { * } attrVal 要设置的属性值，类型视设置的属性而定\r\n         * @return { * } 返回attrs对象下的属性值\r\n         * @example\r\n         * ```javascript\r\n         * node.setAttr('title','标题');\r\n         * ```\r\n         */\r\n        setAttr:function (attrName, attrVal) {\r\n            if (!attrName) {\r\n                delete this.attrs;\r\n                return;\r\n            }\r\n            if(!this.attrs){\r\n                this.attrs = {};\r\n            }\r\n            if (utils.isObject(attrName)) {\r\n                for (var a in attrName) {\r\n                    if (!attrName[a]) {\r\n                        delete this.attrs[a]\r\n                    } else {\r\n                        this.attrs[a.toLowerCase()] = attrName[a];\r\n                    }\r\n                }\r\n            } else {\r\n                if (!attrVal) {\r\n                    delete this.attrs[attrName]\r\n                } else {\r\n                    this.attrs[attrName.toLowerCase()] = attrVal;\r\n                }\r\n\r\n            }\r\n        },\r\n\r\n        /**\r\n         * 获取当前节点在父节点下的位置索引\r\n         * @method getIndex\r\n         * @return { Number } 返回索引数值，如果没有父节点，返回-1\r\n         * @example\r\n         * ```javascript\r\n         * node.getIndex();\r\n         * ```\r\n         */\r\n        getIndex:function(){\r\n            var parent = this.parentNode;\r\n            for(var i= 0,ci;ci=parent.children[i];i++){\r\n                if(ci === this){\r\n                    return i;\r\n                }\r\n            }\r\n            return -1;\r\n        },\r\n\r\n        /**\r\n         * 在当前节点下，根据id查找节点\r\n         * @method getNodeById\r\n         * @param { String } id 要查找的id\r\n         * @return { UE.uNode } 返回找到的节点\r\n         * @example\r\n         * ```javascript\r\n         * node.getNodeById('textId');\r\n         * ```\r\n         */\r\n        getNodeById:function (id) {\r\n            var node;\r\n            if (this.children && this.children.length) {\r\n                for (var i = 0, ci; ci = this.children[i++];) {\r\n                    if (node = getNodeById(ci, id)) {\r\n                        return node;\r\n                    }\r\n                }\r\n            }\r\n        },\r\n\r\n        /**\r\n         * 在当前节点下，根据元素名称查找节点列表\r\n         * @method getNodesByTagName\r\n         * @param { String } tagNames 要查找的元素名称\r\n         * @return { Array } 返回找到的节点列表\r\n         * @example\r\n         * ```javascript\r\n         * node.getNodesByTagName('span');\r\n         * ```\r\n         */\r\n        getNodesByTagName:function (tagNames) {\r\n            tagNames = utils.trim(tagNames).replace(/[ ]{2,}/g, ' ').split(' ');\r\n            var arr = [], me = this;\r\n            utils.each(tagNames, function (tagName) {\r\n                if (me.children && me.children.length) {\r\n                    for (var i = 0, ci; ci = me.children[i++];) {\r\n                        getNodesByTagName(ci, tagName, arr)\r\n                    }\r\n                }\r\n            });\r\n            return arr;\r\n        },\r\n\r\n        /**\r\n         * 根据样式名称，获取节点的样式值\r\n         * @method getStyle\r\n         * @param { String } name 要获取的样式名称\r\n         * @return { String } 返回样式值\r\n         * @example\r\n         * ```javascript\r\n         * node.getStyle('font-size');\r\n         * ```\r\n         */\r\n        getStyle:function (name) {\r\n            var cssStyle = this.getAttr('style');\r\n            if (!cssStyle) {\r\n                return ''\r\n            }\r\n            var reg = new RegExp('(^|;)\\\\s*' + name + ':([^;]+)','i');\r\n            var match = cssStyle.match(reg);\r\n            if (match && match[0]) {\r\n                return match[2]\r\n            }\r\n            return '';\r\n        },\r\n\r\n        /**\r\n         * 给节点设置样式\r\n         * @method setStyle\r\n         * @param { String } name 要设置的的样式名称\r\n         * @param { String } val 要设置的的样值\r\n         * @example\r\n         * ```javascript\r\n         * node.setStyle('font-size', '12px');\r\n         * ```\r\n         */\r\n        setStyle:function (name, val) {\r\n            function exec(name, val) {\r\n                var reg = new RegExp('(^|;)\\\\s*' + name + ':([^;]+;?)', 'gi');\r\n                cssStyle = cssStyle.replace(reg, '$1');\r\n                if (val) {\r\n                    cssStyle = name + ':' + utils.unhtml(val) + ';' + cssStyle\r\n                }\r\n\r\n            }\r\n\r\n            var cssStyle = this.getAttr('style');\r\n            if (!cssStyle) {\r\n                cssStyle = '';\r\n            }\r\n            if (utils.isObject(name)) {\r\n                for (var a in name) {\r\n                    exec(a, name[a])\r\n                }\r\n            } else {\r\n                exec(name, val)\r\n            }\r\n            this.setAttr('style', utils.trim(cssStyle))\r\n        },\r\n\r\n        /**\r\n         * 传入一个函数，递归遍历当前节点下的所有节点\r\n         * @method traversal\r\n         * @param { Function } fn 遍历到节点的时，传入节点作为参数，运行此函数\r\n         * @example\r\n         * ```javascript\r\n         * traversal(node, function(){\r\n         *     console.log(node.type);\r\n         * });\r\n         * ```\r\n         */\r\n        traversal:function(fn){\r\n            if(this.children && this.children.length){\r\n                nodeTraversal(this,fn);\r\n            }\r\n            return this;\r\n        }\r\n    }\r\n})();\r\n\r\n\r\n// core/htmlparser.js\r\n/**\r\n * html字符串转换成uNode节点\r\n * @file\r\n * @module UE\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * UEditor公用空间，UEditor所有的功能都挂载在该空间下\r\n * @unfile\r\n * @module UE\r\n */\r\n\r\n/**\r\n * html字符串转换成uNode节点的静态方法\r\n * @method htmlparser\r\n * @param { String } htmlstr 要转换的html代码\r\n * @param { Boolean } ignoreBlank 若设置为true，转换的时候忽略\\n\\r\\t等空白字符\r\n * @return { uNode } 给定的html片段转换形成的uNode对象\r\n * @example\r\n * ```javascript\r\n * var root = UE.htmlparser('<p><b>htmlparser</b></p>', true);\r\n * ```\r\n */\r\n\r\nvar htmlparser = UE.htmlparser = function (htmlstr,ignoreBlank) {\r\n    //todo 原来的方式  [^\"'<>\\/] 有\\/就不能配对上 <TD vAlign=top background=../AAA.JPG> 这样的标签了\r\n    //先去掉了，加上的原因忘了，这里先记录\r\n    var re_tag = /<(?:(?:\\/([^>]+)>)|(?:!--([\\S|\\s]*?)-->)|(?:([^\\s\\/<>]+)\\s*((?:(?:\"[^\"]*\")|(?:'[^']*')|[^\"'<>])*)\\/?>))/g,\r\n        re_attr = /([\\w\\-:.]+)(?:(?:\\s*=\\s*(?:(?:\"([^\"]*)\")|(?:'([^']*)')|([^\\s>]+)))|(?=\\s|$))/g;\r\n\r\n    //ie下取得的html可能会有\\n存在，要去掉，在处理replace(/[\\t\\r\\n]*/g,'');代码高量的\\n不能去除\r\n    var allowEmptyTags = {\r\n        b:1,code:1,i:1,u:1,strike:1,s:1,tt:1,strong:1,q:1,samp:1,em:1,span:1,\r\n        sub:1,img:1,sup:1,font:1,big:1,small:1,iframe:1,a:1,br:1,pre:1\r\n    };\r\n    htmlstr = htmlstr.replace(new RegExp(domUtils.fillChar, 'g'), '');\r\n    if(!ignoreBlank){\r\n        htmlstr = htmlstr.replace(new RegExp('[\\\\r\\\\t\\\\n'+(ignoreBlank?'':' ')+']*<\\/?(\\\\w+)\\\\s*(?:[^>]*)>[\\\\r\\\\t\\\\n'+(ignoreBlank?'':' ')+']*','g'), function(a,b){\r\n            //br暂时单独处理\r\n            if(b && allowEmptyTags[b.toLowerCase()]){\r\n                return a.replace(/(^[\\n\\r]+)|([\\n\\r]+$)/g,'');\r\n            }\r\n            return a.replace(new RegExp('^[\\\\r\\\\n'+(ignoreBlank?'':' ')+']+'),'').replace(new RegExp('[\\\\r\\\\n'+(ignoreBlank?'':' ')+']+$'),'');\r\n        });\r\n    }\r\n\r\n    var notTransAttrs = {\r\n        'href':1,\r\n        'src':1\r\n    };\r\n\r\n    var uNode = UE.uNode,\r\n        needParentNode = {\r\n            'td':'tr',\r\n            'tr':['tbody','thead','tfoot'],\r\n            'tbody':'table',\r\n            'th':'tr',\r\n            'thead':'table',\r\n            'tfoot':'table',\r\n            'caption':'table',\r\n            'li':['ul', 'ol'],\r\n            'dt':'dl',\r\n            'dd':'dl',\r\n            'option':'select'\r\n        },\r\n        needChild = {\r\n            'ol':'li',\r\n            'ul':'li'\r\n        };\r\n\r\n    function text(parent, data) {\r\n\r\n        if(needChild[parent.tagName]){\r\n            var tmpNode = uNode.createElement(needChild[parent.tagName]);\r\n            parent.appendChild(tmpNode);\r\n            tmpNode.appendChild(uNode.createText(data));\r\n            parent = tmpNode;\r\n        }else{\r\n\r\n            parent.appendChild(uNode.createText(data));\r\n        }\r\n    }\r\n\r\n    function element(parent, tagName, htmlattr) {\r\n        var needParentTag;\r\n        if (needParentTag = needParentNode[tagName]) {\r\n            var tmpParent = parent,hasParent;\r\n            while(tmpParent.type != 'root'){\r\n                if(utils.isArray(needParentTag) ? utils.indexOf(needParentTag, tmpParent.tagName) != -1 : needParentTag == tmpParent.tagName){\r\n                    parent = tmpParent;\r\n                    hasParent = true;\r\n                    break;\r\n                }\r\n                tmpParent = tmpParent.parentNode;\r\n            }\r\n            if(!hasParent){\r\n                parent = element(parent, utils.isArray(needParentTag) ? needParentTag[0] : needParentTag)\r\n            }\r\n        }\r\n        //按dtd处理嵌套\r\n//        if(parent.type != 'root' && !dtd[parent.tagName][tagName])\r\n//            parent = parent.parentNode;\r\n        var elm = new uNode({\r\n            parentNode:parent,\r\n            type:'element',\r\n            tagName:tagName.toLowerCase(),\r\n            //是自闭合的处理一下\r\n            children:dtd.$empty[tagName] ? null : []\r\n        });\r\n        //如果属性存在，处理属性\r\n        if (htmlattr) {\r\n            var attrs = {}, match;\r\n            while (match = re_attr.exec(htmlattr)) {\r\n                attrs[match[1].toLowerCase()] = notTransAttrs[match[1].toLowerCase()] ? (match[2] || match[3] || match[4]) : utils.unhtml(match[2] || match[3] || match[4])\r\n            }\r\n            elm.attrs = attrs;\r\n        }\r\n        //trace:3970\r\n//        //如果parent下不能放elm\r\n//        if(dtd.$inline[parent.tagName] && dtd.$block[elm.tagName] && !dtd[parent.tagName][elm.tagName]){\r\n//            parent = parent.parentNode;\r\n//            elm.parentNode = parent;\r\n//        }\r\n        parent.children.push(elm);\r\n        //如果是自闭合节点返回父亲节点\r\n        return  dtd.$empty[tagName] ? parent : elm\r\n    }\r\n\r\n    function comment(parent, data) {\r\n        parent.children.push(new uNode({\r\n            type:'comment',\r\n            data:data,\r\n            parentNode:parent\r\n        }));\r\n    }\r\n\r\n    var match, currentIndex = 0, nextIndex = 0;\r\n    //设置根节点\r\n    var root = new uNode({\r\n        type:'root',\r\n        children:[]\r\n    });\r\n    var currentParent = root;\r\n\r\n    while (match = re_tag.exec(htmlstr)) {\r\n        currentIndex = match.index;\r\n        try{\r\n            if (currentIndex > nextIndex) {\r\n                //text node\r\n                text(currentParent, htmlstr.slice(nextIndex, currentIndex));\r\n            }\r\n            if (match[3]) {\r\n\r\n                if(dtd.$cdata[currentParent.tagName]){\r\n                    text(currentParent, match[0]);\r\n                }else{\r\n                    //start tag\r\n                    currentParent = element(currentParent, match[3].toLowerCase(), match[4]);\r\n                }\r\n\r\n\r\n            } else if (match[1]) {\r\n                if(currentParent.type != 'root'){\r\n                    if(dtd.$cdata[currentParent.tagName] && !dtd.$cdata[match[1]]){\r\n                        text(currentParent, match[0]);\r\n                    }else{\r\n                        var tmpParent = currentParent;\r\n                        while(currentParent.type == 'element' && currentParent.tagName != match[1].toLowerCase()){\r\n                            currentParent = currentParent.parentNode;\r\n                            if(currentParent.type == 'root'){\r\n                                currentParent = tmpParent;\r\n                                throw 'break'\r\n                            }\r\n                        }\r\n                        //end tag\r\n                        currentParent = currentParent.parentNode;\r\n                    }\r\n\r\n                }\r\n\r\n            } else if (match[2]) {\r\n                //comment\r\n                comment(currentParent, match[2])\r\n            }\r\n        }catch(e){}\r\n\r\n        nextIndex = re_tag.lastIndex;\r\n\r\n    }\r\n    //如果结束是文本，就有可能丢掉，所以这里手动判断一下\r\n    //例如 <li>sdfsdfsdf<li>sdfsdfsdfsdf\r\n    if (nextIndex < htmlstr.length) {\r\n        text(currentParent, htmlstr.slice(nextIndex));\r\n    }\r\n    return root;\r\n};\r\n\r\n\r\n// core/filternode.js\r\n/**\r\n * UE过滤节点的静态方法\r\n * @file\r\n */\r\n\r\n/**\r\n * UEditor公用空间，UEditor所有的功能都挂载在该空间下\r\n * @module UE\r\n */\r\n\r\n\r\n/**\r\n * 根据传入节点和过滤规则过滤相应节点\r\n * @module UE\r\n * @since 1.2.6.1\r\n * @method filterNode\r\n * @param { Object } root 指定root节点\r\n * @param { Object } rules 过滤规则json对象\r\n * @example\r\n * ```javascript\r\n * UE.filterNode(root,editor.options.filterRules);\r\n * ```\r\n */\r\nvar filterNode = UE.filterNode = function () {\r\n    function filterNode(node,rules){\r\n        switch (node.type) {\r\n            case 'text':\r\n                break;\r\n            case 'element':\r\n                var val;\r\n                if(val = rules[node.tagName]){\r\n                   if(val === '-'){\r\n                       node.parentNode.removeChild(node)\r\n                   }else if(utils.isFunction(val)){\r\n                       var parentNode = node.parentNode,\r\n                           index = node.getIndex();\r\n                       val(node);\r\n                       if(node.parentNode){\r\n                           if(node.children){\r\n                               for(var i = 0,ci;ci=node.children[i];){\r\n                                   filterNode(ci,rules);\r\n                                   if(ci.parentNode){\r\n                                       i++;\r\n                                   }\r\n                               }\r\n                           }\r\n                       }else{\r\n                           for(var i = index,ci;ci=parentNode.children[i];){\r\n                               filterNode(ci,rules);\r\n                               if(ci.parentNode){\r\n                                   i++;\r\n                               }\r\n                           }\r\n                       }\r\n\r\n\r\n                   }else{\r\n                       var attrs = val['$'];\r\n                       if(attrs && node.attrs){\r\n                           var tmpAttrs = {},tmpVal;\r\n                           for(var a in attrs){\r\n                               tmpVal = node.getAttr(a);\r\n                               //todo 只先对style单独处理\r\n                               if(a == 'style' && utils.isArray(attrs[a])){\r\n                                   var tmpCssStyle = [];\r\n                                   utils.each(attrs[a],function(v){\r\n                                       var tmp;\r\n                                       if(tmp = node.getStyle(v)){\r\n                                           tmpCssStyle.push(v + ':' + tmp);\r\n                                       }\r\n                                   });\r\n                                   tmpVal = tmpCssStyle.join(';')\r\n                               }\r\n                               if(tmpVal){\r\n                                   tmpAttrs[a] = tmpVal;\r\n                               }\r\n\r\n                           }\r\n                           node.attrs = tmpAttrs;\r\n                       }\r\n                       if(node.children){\r\n                           for(var i = 0,ci;ci=node.children[i];){\r\n                               filterNode(ci,rules);\r\n                               if(ci.parentNode){\r\n                                   i++;\r\n                               }\r\n                           }\r\n                       }\r\n                   }\r\n                }else{\r\n                    //如果不在名单里扣出子节点并删除该节点,cdata除外\r\n                    if(dtd.$cdata[node.tagName]){\r\n                        node.parentNode.removeChild(node)\r\n                    }else{\r\n                        var parentNode = node.parentNode,\r\n                            index = node.getIndex();\r\n                        node.parentNode.removeChild(node,true);\r\n                        for(var i = index,ci;ci=parentNode.children[i];){\r\n                            filterNode(ci,rules);\r\n                            if(ci.parentNode){\r\n                                i++;\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n                break;\r\n            case 'comment':\r\n                node.parentNode.removeChild(node)\r\n        }\r\n\r\n    }\r\n    return function(root,rules){\r\n        if(utils.isEmptyObject(rules)){\r\n            return root;\r\n        }\r\n        var val;\r\n        if(val = rules['-']){\r\n            utils.each(val.split(' '),function(k){\r\n                rules[k] = '-'\r\n            })\r\n        }\r\n        for(var i= 0,ci;ci=root.children[i];){\r\n            filterNode(ci,rules);\r\n            if(ci.parentNode){\r\n               i++;\r\n            }\r\n        }\r\n        return root;\r\n    }\r\n}();\r\n\r\n// core/plugin.js\r\n/**\r\n * Created with JetBrains PhpStorm.\r\n * User: campaign\r\n * Date: 10/8/13\r\n * Time: 6:15 PM\r\n * To change this template use File | Settings | File Templates.\r\n */\r\nUE.plugin = function(){\r\n    var _plugins = {};\r\n    return {\r\n        register : function(pluginName,fn,oldOptionName,afterDisabled){\r\n            if(oldOptionName && utils.isFunction(oldOptionName)){\r\n                afterDisabled = oldOptionName;\r\n                oldOptionName = null\r\n            }\r\n            _plugins[pluginName] = {\r\n                optionName : oldOptionName || pluginName,\r\n                execFn : fn,\r\n                //当插件被禁用时执行\r\n                afterDisabled : afterDisabled\r\n            }\r\n        },\r\n        load : function(editor){\r\n            utils.each(_plugins,function(plugin){\r\n                var _export = plugin.execFn.call(editor);\r\n                if(editor.options[plugin.optionName] !== false){\r\n                    if(_export){\r\n                        //后边需要再做扩展\r\n                        utils.each(_export,function(v,k){\r\n                            switch(k.toLowerCase()){\r\n                                case 'shortcutkey':\r\n                                    editor.addshortcutkey(v);\r\n                                    break;\r\n                                case 'bindevents':\r\n                                    utils.each(v,function(fn,eventName){\r\n                                        editor.addListener(eventName,fn);\r\n                                    });\r\n                                    break;\r\n                                case 'bindmultievents':\r\n                                    utils.each(utils.isArray(v) ? v:[v],function(event){\r\n                                        var types = utils.trim(event.type).split(/\\s+/);\r\n                                        utils.each(types,function(eventName){\r\n                                            editor.addListener(eventName, event.handler);\r\n                                        });\r\n                                    });\r\n                                    break;\r\n                                case 'commands':\r\n                                    utils.each(v,function(execFn,execName){\r\n                                        editor.commands[execName] = execFn\r\n                                    });\r\n                                    break;\r\n                                case 'outputrule':\r\n                                    editor.addOutputRule(v);\r\n                                    break;\r\n                                case 'inputrule':\r\n                                    editor.addInputRule(v);\r\n                                    break;\r\n                                case 'defaultoptions':\r\n                                    editor.setOpt(v)\r\n                            }\r\n                        })\r\n                    }\r\n\r\n                }else if(plugin.afterDisabled){\r\n                    plugin.afterDisabled.call(editor)\r\n                }\r\n\r\n            });\r\n            //向下兼容\r\n            utils.each(UE.plugins,function(plugin){\r\n                plugin.call(editor);\r\n            });\r\n        },\r\n        run : function(pluginName,editor){\r\n            var plugin = _plugins[pluginName];\r\n            if(plugin){\r\n                plugin.exeFn.call(editor)\r\n            }\r\n        }\r\n    }\r\n}();\r\n\r\n// core/keymap.js\r\nvar keymap = UE.keymap  = {\r\n    'Backspace' : 8,\r\n    'Tab' : 9,\r\n    'Enter' : 13,\r\n\r\n    'Shift':16,\r\n    'Control':17,\r\n    'Alt':18,\r\n    'CapsLock':20,\r\n\r\n    'Esc':27,\r\n\r\n    'Spacebar':32,\r\n\r\n    'PageUp':33,\r\n    'PageDown':34,\r\n    'End':35,\r\n    'Home':36,\r\n\r\n    'Left':37,\r\n    'Up':38,\r\n    'Right':39,\r\n    'Down':40,\r\n\r\n    'Insert':45,\r\n\r\n    'Del':46,\r\n\r\n    'NumLock':144,\r\n\r\n    'Cmd':91,\r\n\r\n    '=':187,\r\n    '-':189,\r\n\r\n    \"b\":66,\r\n    'i':73,\r\n    //回退\r\n    'z':90,\r\n    'y':89,\r\n    //粘贴\r\n    'v' : 86,\r\n    'x' : 88,\r\n\r\n    's' : 83,\r\n\r\n    'n' : 78\r\n};\r\n\r\n// core/localstorage.js\r\n//存储媒介封装\r\nvar LocalStorage = UE.LocalStorage = (function () {\r\n\r\n    var storage = window.localStorage || getUserData() || null,\r\n        LOCAL_FILE = 'localStorage';\r\n\r\n    return {\r\n\r\n        saveLocalData: function (key, data) {\r\n\r\n            if (storage && data) {\r\n                storage.setItem(key, data);\r\n                return true;\r\n            }\r\n\r\n            return false;\r\n\r\n        },\r\n\r\n        getLocalData: function (key) {\r\n\r\n            if (storage) {\r\n                return storage.getItem(key);\r\n            }\r\n\r\n            return null;\r\n\r\n        },\r\n\r\n        removeItem: function (key) {\r\n\r\n            storage && storage.removeItem(key);\r\n\r\n        }\r\n\r\n    };\r\n\r\n    function getUserData() {\r\n\r\n        var container = document.createElement(\"div\");\r\n        container.style.display = \"none\";\r\n\r\n        if (!container.addBehavior) {\r\n            return null;\r\n        }\r\n\r\n        container.addBehavior(\"#default#userdata\");\r\n\r\n        return {\r\n\r\n            getItem: function (key) {\r\n\r\n                var result = null;\r\n\r\n                try {\r\n                    document.body.appendChild(container);\r\n                    container.load(LOCAL_FILE);\r\n                    result = container.getAttribute(key);\r\n                    document.body.removeChild(container);\r\n                } catch (e) {\r\n                }\r\n\r\n                return result;\r\n\r\n            },\r\n\r\n            setItem: function (key, value) {\r\n\r\n                document.body.appendChild(container);\r\n                container.setAttribute(key, value);\r\n                container.save(LOCAL_FILE);\r\n                document.body.removeChild(container);\r\n\r\n            },\r\n\r\n            //// 暂时没有用到\r\n            //clear: function () {\r\n            //\r\n            //    var expiresTime = new Date();\r\n            //    expiresTime.setFullYear(expiresTime.getFullYear() - 1);\r\n            //    document.body.appendChild(container);\r\n            //    container.expires = expiresTime.toUTCString();\r\n            //    container.save(LOCAL_FILE);\r\n            //    document.body.removeChild(container);\r\n            //\r\n            //},\r\n\r\n            removeItem: function (key) {\r\n\r\n                document.body.appendChild(container);\r\n                container.removeAttribute(key);\r\n                container.save(LOCAL_FILE);\r\n                document.body.removeChild(container);\r\n\r\n            }\r\n\r\n        };\r\n\r\n    }\r\n\r\n})();\r\n\r\n(function () {\r\n\r\n    var ROOTKEY = 'ueditor_preference';\r\n\r\n    UE.Editor.prototype.setPreferences = function(key,value){\r\n        var obj = {};\r\n        if (utils.isString(key)) {\r\n            obj[ key ] = value;\r\n        } else {\r\n            obj = key;\r\n        }\r\n        var data = LocalStorage.getLocalData(ROOTKEY);\r\n        if (data && (data = utils.str2json(data))) {\r\n            utils.extend(data, obj);\r\n        } else {\r\n            data = obj;\r\n        }\r\n        data && LocalStorage.saveLocalData(ROOTKEY, utils.json2str(data));\r\n    };\r\n\r\n    UE.Editor.prototype.getPreferences = function(key){\r\n        var data = LocalStorage.getLocalData(ROOTKEY);\r\n        if (data && (data = utils.str2json(data))) {\r\n            return key ? data[key] : data\r\n        }\r\n        return null;\r\n    };\r\n\r\n    UE.Editor.prototype.removePreferences = function (key) {\r\n        var data = LocalStorage.getLocalData(ROOTKEY);\r\n        if (data && (data = utils.str2json(data))) {\r\n            data[key] = undefined;\r\n            delete data[key]\r\n        }\r\n        data && LocalStorage.saveLocalData(ROOTKEY, utils.json2str(data));\r\n    };\r\n\r\n})();\r\n\r\n\r\n// plugins/defaultfilter.js\r\n///import core\r\n///plugin 编辑器默认的过滤转换机制\r\n\r\nUE.plugins['defaultfilter'] = function () {\r\n    var me = this;\r\n    me.setOpt({\r\n        'allowDivTransToP':true,\r\n        'disabledTableInTable':true\r\n    });\r\n    //默认的过滤处理\r\n    //进入编辑器的内容处理\r\n    me.addInputRule(function (root) {\r\n        var allowDivTransToP = this.options.allowDivTransToP;\r\n        var val;\r\n        function tdParent(node){\r\n            while(node && node.type == 'element'){\r\n                if(node.tagName == 'td'){\r\n                    return true;\r\n                }\r\n                node = node.parentNode;\r\n            }\r\n            return false;\r\n        }\r\n        //进行默认的处理\r\n        root.traversal(function (node) {\r\n            if (node.type == 'element') {\r\n                if (!dtd.$cdata[node.tagName] && me.options.autoClearEmptyNode && dtd.$inline[node.tagName] && !dtd.$empty[node.tagName] && (!node.attrs || utils.isEmptyObject(node.attrs))) {\r\n                    if (!node.firstChild()) node.parentNode.removeChild(node);\r\n                    else if (node.tagName == 'span' && (!node.attrs || utils.isEmptyObject(node.attrs))) {\r\n                        node.parentNode.removeChild(node, true)\r\n                    }\r\n                    return;\r\n                }\r\n                switch (node.tagName) {\r\n                    case 'style':\r\n                    case 'script':\r\n                        node.setAttr({\r\n                            cdata_tag: node.tagName,\r\n                            cdata_data: (node.innerHTML() || ''),\r\n                            '_ue_custom_node_':'true'\r\n                        });\r\n                        node.tagName = 'div';\r\n                        node.innerHTML('');\r\n                        break;\r\n                    case 'a':\r\n                        if (val = node.getAttr('href')) {\r\n                            node.setAttr('_href', val)\r\n                        }\r\n                        break;\r\n                    case 'img':\r\n                        //todo base64暂时去掉，后边做远程图片上传后，干掉这个\r\n                        if (val = node.getAttr('src')) {\r\n                            if (/^data:/.test(val)) {\r\n                                node.parentNode.removeChild(node);\r\n                                break;\r\n                            }\r\n                        }\r\n                        node.setAttr('_src', node.getAttr('src'));\r\n                        break;\r\n                    case 'span':\r\n                        if (browser.webkit && (val = node.getStyle('white-space'))) {\r\n                            if (/nowrap|normal/.test(val)) {\r\n                                node.setStyle('white-space', '');\r\n                                if (me.options.autoClearEmptyNode && utils.isEmptyObject(node.attrs)) {\r\n                                    node.parentNode.removeChild(node, true)\r\n                                }\r\n                            }\r\n                        }\r\n                        val = node.getAttr('id');\r\n                        if(val && /^_baidu_bookmark_/i.test(val)){\r\n                            node.parentNode.removeChild(node)\r\n                        }\r\n                        break;\r\n                    case 'p':\r\n                        if (val = node.getAttr('align')) {\r\n                            node.setAttr('align');\r\n                            node.setStyle('text-align', val)\r\n                        }\r\n                        //trace:3431\r\n//                        var cssStyle = node.getAttr('style');\r\n//                        if (cssStyle) {\r\n//                            cssStyle = cssStyle.replace(/(margin|padding)[^;]+/g, '');\r\n//                            node.setAttr('style', cssStyle)\r\n//\r\n//                        }\r\n                        //p标签不允许嵌套\r\n                        utils.each(node.children,function(n){\r\n                            if(n.type == 'element' && n.tagName == 'p'){\r\n                                var next = n.nextSibling();\r\n                                node.parentNode.insertAfter(n,node);\r\n                                var last = n;\r\n                                while(next){\r\n                                    var tmp = next.nextSibling();\r\n                                    node.parentNode.insertAfter(next,last);\r\n                                    last = next;\r\n                                    next = tmp;\r\n                                }\r\n                                return false;\r\n                            }\r\n                        });\r\n                        if (!node.firstChild()) {\r\n                            node.innerHTML(browser.ie ? '&nbsp;' : '<br/>')\r\n                        }\r\n                        break;\r\n                    case 'div':\r\n                        if(node.getAttr('cdata_tag')){\r\n                            break;\r\n                        }\r\n                        //针对代码这里不处理插入代码的div\r\n                        val = node.getAttr('class');\r\n                        if(val && /^line number\\d+/.test(val)){\r\n                            break;\r\n                        }\r\n                        if(!allowDivTransToP){\r\n                            break;\r\n                        }\r\n                        var tmpNode, p = UE.uNode.createElement('p');\r\n                        while (tmpNode = node.firstChild()) {\r\n                            if (tmpNode.type == 'text' || !UE.dom.dtd.$block[tmpNode.tagName]) {\r\n                                p.appendChild(tmpNode);\r\n                            } else {\r\n                                if (p.firstChild()) {\r\n                                    node.parentNode.insertBefore(p, node);\r\n                                    p = UE.uNode.createElement('p');\r\n                                } else {\r\n                                    node.parentNode.insertBefore(tmpNode, node);\r\n                                }\r\n                            }\r\n                        }\r\n                        if (p.firstChild()) {\r\n                            node.parentNode.insertBefore(p, node);\r\n                        }\r\n                        node.parentNode.removeChild(node);\r\n                        break;\r\n                    case 'dl':\r\n                        node.tagName = 'ul';\r\n                        break;\r\n                    case 'dt':\r\n                    case 'dd':\r\n                        node.tagName = 'li';\r\n                        break;\r\n                    case 'li':\r\n                        var className = node.getAttr('class');\r\n                        if (!className || !/list\\-/.test(className)) {\r\n                            node.setAttr()\r\n                        }\r\n                        var tmpNodes = node.getNodesByTagName('ol ul');\r\n                        UE.utils.each(tmpNodes, function (n) {\r\n                            node.parentNode.insertAfter(n, node);\r\n                        });\r\n                        break;\r\n                    case 'td':\r\n                    case 'th':\r\n                    case 'caption':\r\n                        if(!node.children || !node.children.length){\r\n                            node.appendChild(browser.ie11below ? UE.uNode.createText(' ') : UE.uNode.createElement('br'))\r\n                        }\r\n                        break;\r\n                    case 'table':\r\n                        if(me.options.disabledTableInTable && tdParent(node)){\r\n                            node.parentNode.insertBefore(UE.uNode.createText(node.innerText()),node);\r\n                            node.parentNode.removeChild(node)\r\n                        }\r\n                }\r\n\r\n            }\r\n//            if(node.type == 'comment'){\r\n//                node.parentNode.removeChild(node);\r\n//            }\r\n        })\r\n\r\n    });\r\n\r\n    //从编辑器出去的内容处理\r\n    me.addOutputRule(function (root) {\r\n\r\n        var val;\r\n        root.traversal(function (node) {\r\n            if (node.type == 'element') {\r\n\r\n                if (me.options.autoClearEmptyNode && dtd.$inline[node.tagName] && !dtd.$empty[node.tagName] && (!node.attrs || utils.isEmptyObject(node.attrs))) {\r\n\r\n                    if (!node.firstChild()) node.parentNode.removeChild(node);\r\n                    else if (node.tagName == 'span' && (!node.attrs || utils.isEmptyObject(node.attrs))) {\r\n                        node.parentNode.removeChild(node, true)\r\n                    }\r\n                    return;\r\n                }\r\n                switch (node.tagName) {\r\n                    case 'div':\r\n                        if (val = node.getAttr('cdata_tag')) {\r\n                            node.tagName = val;\r\n                            node.appendChild(UE.uNode.createText(node.getAttr('cdata_data')));\r\n                            node.setAttr({cdata_tag: '', cdata_data: '','_ue_custom_node_':''});\r\n                        }\r\n                        break;\r\n                    case 'a':\r\n                        if (val = node.getAttr('_href')) {\r\n                            node.setAttr({\r\n                                'href': utils.html(val),\r\n                                '_href': ''\r\n                            })\r\n                        }\r\n                        break;\r\n                        break;\r\n                    case 'span':\r\n                        val = node.getAttr('id');\r\n                        if(val && /^_baidu_bookmark_/i.test(val)){\r\n                            node.parentNode.removeChild(node)\r\n                        }\r\n                        break;\r\n                    case 'img':\r\n                        if (val = node.getAttr('_src')) {\r\n                            node.setAttr({\r\n                                'src': node.getAttr('_src'),\r\n                                '_src': ''\r\n                            })\r\n                        }\r\n\r\n\r\n                }\r\n            }\r\n\r\n        })\r\n\r\n\r\n    });\r\n};\r\n\r\n\r\n// plugins/inserthtml.js\r\n/**\r\n * 插入html字符串插件\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 插入html代码\r\n * @command inserthtml\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @param { String } html 插入的html字符串\r\n * @remaind 插入的标签内容是在当前的选区位置上插入，如果当前是闭合状态，那直接插入内容， 如果当前是选中状态，将先清除当前选中内容后，再做插入\r\n * @warning 注意:该命令会对当前选区的位置，对插入的内容进行过滤转换处理。 过滤的规则遵循html语意化的原则。\r\n * @example\r\n * ```javascript\r\n * //xxx[BB]xxx 当前选区为非闭合选区，选中BB这两个文本\r\n * //执行命令，插入<b>CC</b>\r\n * //插入后的效果 xxx<b>CC</b>xxx\r\n * //<p>xx|xxx</p> 当前选区为闭合状态\r\n * //插入<p>CC</p>\r\n * //结果 <p>xx</p><p>CC</p><p>xxx</p>\r\n * //<p>xxxx</p>|</p>xxx</p> 当前选区在两个p标签之间\r\n * //插入 xxxx\r\n * //结果 <p>xxxx</p><p>xxxx</p></p>xxx</p>\r\n * ```\r\n */\r\n\r\nUE.commands['inserthtml'] = {\r\n    execCommand: function (command,html,notNeedFilter){\r\n        var me = this,\r\n            range,\r\n            div;\r\n        if(!html){\r\n            return;\r\n        }\r\n        if(me.fireEvent('beforeinserthtml',html) === true){\r\n            return;\r\n        }\r\n        range = me.selection.getRange();\r\n        div = range.document.createElement( 'div' );\r\n        div.style.display = 'inline';\r\n\r\n        if (!notNeedFilter) {\r\n            var root = UE.htmlparser(html);\r\n            //如果给了过滤规则就先进行过滤\r\n            if(me.options.filterRules){\r\n                UE.filterNode(root,me.options.filterRules);\r\n            }\r\n            //执行默认的处理\r\n            me.filterInputRule(root);\r\n            html = root.toHtml()\r\n        }\r\n        div.innerHTML = utils.trim( html );\r\n\r\n        if ( !range.collapsed ) {\r\n            var tmpNode = range.startContainer;\r\n            if(domUtils.isFillChar(tmpNode)){\r\n                range.setStartBefore(tmpNode)\r\n            }\r\n            tmpNode = range.endContainer;\r\n            if(domUtils.isFillChar(tmpNode)){\r\n                range.setEndAfter(tmpNode)\r\n            }\r\n            range.txtToElmBoundary();\r\n            //结束边界可能放到了br的前边，要把br包含进来\r\n            // x[xxx]<br/>\r\n            if(range.endContainer && range.endContainer.nodeType == 1){\r\n                tmpNode = range.endContainer.childNodes[range.endOffset];\r\n                if(tmpNode && domUtils.isBr(tmpNode)){\r\n                    range.setEndAfter(tmpNode);\r\n                }\r\n            }\r\n            if(range.startOffset == 0){\r\n                tmpNode = range.startContainer;\r\n                if(domUtils.isBoundaryNode(tmpNode,'firstChild') ){\r\n                    tmpNode = range.endContainer;\r\n                    if(range.endOffset == (tmpNode.nodeType == 3 ? tmpNode.nodeValue.length : tmpNode.childNodes.length) && domUtils.isBoundaryNode(tmpNode,'lastChild')){\r\n                        me.body.innerHTML = '<p>'+(browser.ie ? '' : '<br/>')+'</p>';\r\n                        range.setStart(me.body.firstChild,0).collapse(true)\r\n\r\n                    }\r\n                }\r\n            }\r\n            !range.collapsed && range.deleteContents();\r\n            if(range.startContainer.nodeType == 1){\r\n                var child = range.startContainer.childNodes[range.startOffset],pre;\r\n                if(child && domUtils.isBlockElm(child) && (pre = child.previousSibling) && domUtils.isBlockElm(pre)){\r\n                    range.setEnd(pre,pre.childNodes.length).collapse();\r\n                    while(child.firstChild){\r\n                        pre.appendChild(child.firstChild);\r\n                    }\r\n                    domUtils.remove(child);\r\n                }\r\n            }\r\n\r\n        }\r\n\r\n\r\n        var child,parent,pre,tmp,hadBreak = 0, nextNode;\r\n        //如果当前位置选中了fillchar要干掉，要不会产生空行\r\n        if(range.inFillChar()){\r\n            child = range.startContainer;\r\n            if(domUtils.isFillChar(child)){\r\n                range.setStartBefore(child).collapse(true);\r\n                domUtils.remove(child);\r\n            }else if(domUtils.isFillChar(child,true)){\r\n                child.nodeValue = child.nodeValue.replace(fillCharReg,'');\r\n                range.startOffset--;\r\n                range.collapsed && range.collapse(true)\r\n            }\r\n        }\r\n        //列表单独处理\r\n        var li = domUtils.findParentByTagName(range.startContainer,'li',true);\r\n        if(li){\r\n            var next,last;\r\n            while(child = div.firstChild){\r\n                //针对hr单独处理一下先\r\n                while(child && (child.nodeType == 3 || !domUtils.isBlockElm(child) || child.tagName=='HR' )){\r\n                    next = child.nextSibling;\r\n                    range.insertNode( child).collapse();\r\n                    last = child;\r\n                    child = next;\r\n\r\n                }\r\n                if(child){\r\n                    if(/^(ol|ul)$/i.test(child.tagName)){\r\n                        while(child.firstChild){\r\n                            last = child.firstChild;\r\n                            domUtils.insertAfter(li,child.firstChild);\r\n                            li = li.nextSibling;\r\n                        }\r\n                        domUtils.remove(child)\r\n                    }else{\r\n                        var tmpLi;\r\n                        next = child.nextSibling;\r\n                        tmpLi = me.document.createElement('li');\r\n                        domUtils.insertAfter(li,tmpLi);\r\n                        tmpLi.appendChild(child);\r\n                        last = child;\r\n                        child = next;\r\n                        li = tmpLi;\r\n                    }\r\n                }\r\n            }\r\n            li = domUtils.findParentByTagName(range.startContainer,'li',true);\r\n            if(domUtils.isEmptyBlock(li)){\r\n                domUtils.remove(li)\r\n            }\r\n            if(last){\r\n\r\n                range.setStartAfter(last).collapse(true).select(true)\r\n            }\r\n        }else{\r\n            while ( child = div.firstChild ) {\r\n                if(hadBreak){\r\n                    var p = me.document.createElement('p');\r\n                    while(child && (child.nodeType == 3 || !dtd.$block[child.tagName])){\r\n                        nextNode = child.nextSibling;\r\n                        p.appendChild(child);\r\n                        child = nextNode;\r\n                    }\r\n                    if(p.firstChild){\r\n\r\n                        child = p\r\n                    }\r\n                }\r\n                range.insertNode( child );\r\n                nextNode = child.nextSibling;\r\n                if ( !hadBreak && child.nodeType == domUtils.NODE_ELEMENT && domUtils.isBlockElm( child ) ){\r\n\r\n                    parent = domUtils.findParent( child,function ( node ){ return domUtils.isBlockElm( node ); } );\r\n                    if ( parent && parent.tagName.toLowerCase() != 'body' && !(dtd[parent.tagName][child.nodeName] && child.parentNode === parent)){\r\n                        if(!dtd[parent.tagName][child.nodeName]){\r\n                            pre = parent;\r\n                        }else{\r\n                            tmp = child.parentNode;\r\n                            while (tmp !== parent){\r\n                                pre = tmp;\r\n                                tmp = tmp.parentNode;\r\n\r\n                            }\r\n                        }\r\n\r\n\r\n                        domUtils.breakParent( child, pre || tmp );\r\n                        //去掉break后前一个多余的节点  <p>|<[p> ==> <p></p><div></div><p>|</p>\r\n                        var pre = child.previousSibling;\r\n                        domUtils.trimWhiteTextNode(pre);\r\n                        if(!pre.childNodes.length){\r\n                            domUtils.remove(pre);\r\n                        }\r\n                        //trace:2012,在非ie的情况，切开后剩下的节点有可能不能点入光标添加br占位\r\n\r\n                        if(!browser.ie &&\r\n                            (next = child.nextSibling) &&\r\n                            domUtils.isBlockElm(next) &&\r\n                            next.lastChild &&\r\n                            !domUtils.isBr(next.lastChild)){\r\n                            next.appendChild(me.document.createElement('br'));\r\n                        }\r\n                        hadBreak = 1;\r\n                    }\r\n                }\r\n                var next = child.nextSibling;\r\n                if(!div.firstChild && next && domUtils.isBlockElm(next)){\r\n\r\n                    range.setStart(next,0).collapse(true);\r\n                    break;\r\n                }\r\n                range.setEndAfter( child ).collapse();\r\n\r\n            }\r\n\r\n            child = range.startContainer;\r\n\r\n            if(nextNode && domUtils.isBr(nextNode)){\r\n                domUtils.remove(nextNode)\r\n            }\r\n            //用chrome可能有空白展位符\r\n            if(domUtils.isBlockElm(child) && domUtils.isEmptyNode(child)){\r\n                if(nextNode = child.nextSibling){\r\n                    domUtils.remove(child);\r\n                    if(nextNode.nodeType == 1 && dtd.$block[nextNode.tagName]){\r\n\r\n                        range.setStart(nextNode,0).collapse(true).shrinkBoundary()\r\n                    }\r\n                }else{\r\n\r\n                    try{\r\n                        child.innerHTML = browser.ie ? domUtils.fillChar : '<br/>';\r\n                    }catch(e){\r\n                        range.setStartBefore(child);\r\n                        domUtils.remove(child)\r\n                    }\r\n\r\n                }\r\n\r\n            }\r\n            //加上true因为在删除表情等时会删两次，第一次是删的fillData\r\n            try{\r\n                range.select(true);\r\n            }catch(e){}\r\n\r\n        }\r\n\r\n\r\n\r\n        setTimeout(function(){\r\n            range = me.selection.getRange();\r\n            range.scrollToView(me.autoHeightEnabled,me.autoHeightEnabled ? domUtils.getXY(me.iframe).y:0);\r\n            me.fireEvent('afterinserthtml', html);\r\n        },200);\r\n    }\r\n};\r\n\r\n\r\n// plugins/autotypeset.js\r\n/**\r\n * 自动排版\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 对当前编辑器的内容执行自动排版， 排版的行为根据config配置文件里的“autotypeset”选项进行控制。\r\n * @command autotypeset\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'autotypeset' );\r\n * ```\r\n */\r\n\r\nUE.plugins['autotypeset'] = function(){\r\n\r\n    this.setOpt({'autotypeset': {\r\n        mergeEmptyline: true,           //合并空行\r\n        removeClass: true,              //去掉冗余的class\r\n        removeEmptyline: false,         //去掉空行\r\n        textAlign:\"left\",               //段落的排版方式，可以是 left,right,center,justify 去掉这个属性表示不执行排版\r\n        imageBlockLine: 'center',       //图片的浮动方式，独占一行剧中,左右浮动，默认: center,left,right,none 去掉这个属性表示不执行排版\r\n        pasteFilter: false,             //根据规则过滤没事粘贴进来的内容\r\n        clearFontSize: false,           //去掉所有的内嵌字号，使用编辑器默认的字号\r\n        clearFontFamily: false,         //去掉所有的内嵌字体，使用编辑器默认的字体\r\n        removeEmptyNode: false,         // 去掉空节点\r\n        //可以去掉的标签\r\n        removeTagNames: utils.extend({div:1},dtd.$removeEmpty),\r\n        indent: false,                  // 行首缩进\r\n        indentValue : '2em',            //行首缩进的大小\r\n        bdc2sb: false,\r\n        tobdc: false\r\n    }});\r\n\r\n    var me = this,\r\n        opt = me.options.autotypeset,\r\n        remainClass = {\r\n            'selectTdClass':1,\r\n            'pagebreak':1,\r\n            'anchorclass':1\r\n        },\r\n        remainTag = {\r\n            'li':1\r\n        },\r\n        tags = {\r\n            div:1,\r\n            p:1,\r\n            //trace:2183 这些也认为是行\r\n            blockquote:1,center:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,\r\n            span:1\r\n        },\r\n        highlightCont;\r\n    //升级了版本，但配置项目里没有autotypeset\r\n    if(!opt){\r\n        return;\r\n    }\r\n\r\n    readLocalOpts();\r\n\r\n    function isLine(node,notEmpty){\r\n        if(!node || node.nodeType == 3)\r\n            return 0;\r\n        if(domUtils.isBr(node))\r\n            return 1;\r\n        if(node && node.parentNode && tags[node.tagName.toLowerCase()]){\r\n            if(highlightCont && highlightCont.contains(node)\r\n                ||\r\n                node.getAttribute('pagebreak')\r\n            ){\r\n                return 0;\r\n            }\r\n\r\n            return notEmpty ? !domUtils.isEmptyBlock(node) : domUtils.isEmptyBlock(node,new RegExp('[\\\\s'+domUtils.fillChar\r\n                +']','g'));\r\n        }\r\n    }\r\n\r\n    function removeNotAttributeSpan(node){\r\n        if(!node.style.cssText){\r\n            domUtils.removeAttributes(node,['style']);\r\n            if(node.tagName.toLowerCase() == 'span' && domUtils.hasNoAttributes(node)){\r\n                domUtils.remove(node,true);\r\n            }\r\n        }\r\n    }\r\n    function autotype(type,html){\r\n\r\n        var me = this,cont;\r\n        if(html){\r\n            if(!opt.pasteFilter){\r\n                return;\r\n            }\r\n            cont = me.document.createElement('div');\r\n            cont.innerHTML = html.html;\r\n        }else{\r\n            cont = me.document.body;\r\n        }\r\n        var nodes = domUtils.getElementsByTagName(cont,'*');\r\n\r\n        // 行首缩进，段落方向，段间距，段内间距\r\n        for(var i=0,ci;ci=nodes[i++];){\r\n\r\n            if(me.fireEvent('excludeNodeinautotype',ci) === true){\r\n                continue;\r\n            }\r\n             //font-size\r\n            if(opt.clearFontSize && ci.style.fontSize){\r\n                domUtils.removeStyle(ci,'font-size');\r\n\r\n                removeNotAttributeSpan(ci);\r\n\r\n            }\r\n            //font-family\r\n            if(opt.clearFontFamily && ci.style.fontFamily){\r\n                domUtils.removeStyle(ci,'font-family');\r\n                removeNotAttributeSpan(ci);\r\n            }\r\n\r\n            if(isLine(ci)){\r\n                //合并空行\r\n                if(opt.mergeEmptyline ){\r\n                    var next = ci.nextSibling,tmpNode,isBr = domUtils.isBr(ci);\r\n                    while(isLine(next)){\r\n                        tmpNode = next;\r\n                        next = tmpNode.nextSibling;\r\n                        if(isBr && (!next || next && !domUtils.isBr(next))){\r\n                            break;\r\n                        }\r\n                        domUtils.remove(tmpNode);\r\n                    }\r\n\r\n                }\r\n                 //去掉空行，保留占位的空行\r\n                if(opt.removeEmptyline && domUtils.inDoc(ci,cont) && !remainTag[ci.parentNode.tagName.toLowerCase()] ){\r\n                    if(domUtils.isBr(ci)){\r\n                        next = ci.nextSibling;\r\n                        if(next && !domUtils.isBr(next)){\r\n                            continue;\r\n                        }\r\n                    }\r\n                    domUtils.remove(ci);\r\n                    continue;\r\n\r\n                }\r\n\r\n            }\r\n            if(isLine(ci,true) && ci.tagName != 'SPAN'){\r\n                if(opt.indent){\r\n                    ci.style.textIndent = opt.indentValue;\r\n                }\r\n                if(opt.textAlign){\r\n                    ci.style.textAlign = opt.textAlign;\r\n                }\r\n                // if(opt.lineHeight)\r\n                //     ci.style.lineHeight = opt.lineHeight + 'cm';\r\n\r\n            }\r\n\r\n            //去掉class,保留的class不去掉\r\n            if(opt.removeClass && ci.className && !remainClass[ci.className.toLowerCase()]){\r\n\r\n                if(highlightCont && highlightCont.contains(ci)){\r\n                     continue;\r\n                }\r\n                domUtils.removeAttributes(ci,['class']);\r\n            }\r\n\r\n            //表情不处理\r\n            if(opt.imageBlockLine && ci.tagName.toLowerCase() == 'img' && !ci.getAttribute('emotion')){\r\n                if(html){\r\n                    var img = ci;\r\n                    switch (opt.imageBlockLine){\r\n                        case 'left':\r\n                        case 'right':\r\n                        case 'none':\r\n                            var pN = img.parentNode,tmpNode,pre,next;\r\n                            while(dtd.$inline[pN.tagName] || pN.tagName == 'A'){\r\n                                pN = pN.parentNode;\r\n                            }\r\n                            tmpNode = pN;\r\n                            if(tmpNode.tagName == 'P' && domUtils.getStyle(tmpNode,'text-align') == 'center'){\r\n                                if(!domUtils.isBody(tmpNode) && domUtils.getChildCount(tmpNode,function(node){return !domUtils.isBr(node) && !domUtils.isWhitespace(node)}) == 1){\r\n                                    pre = tmpNode.previousSibling;\r\n                                    next = tmpNode.nextSibling;\r\n                                    if(pre && next && pre.nodeType == 1 &&  next.nodeType == 1 && pre.tagName == next.tagName && domUtils.isBlockElm(pre)){\r\n                                        pre.appendChild(tmpNode.firstChild);\r\n                                        while(next.firstChild){\r\n                                            pre.appendChild(next.firstChild);\r\n                                        }\r\n                                        domUtils.remove(tmpNode);\r\n                                        domUtils.remove(next);\r\n                                    }else{\r\n                                        domUtils.setStyle(tmpNode,'text-align','');\r\n                                    }\r\n\r\n\r\n                                }\r\n\r\n\r\n                            }\r\n                            domUtils.setStyle(img,'float', opt.imageBlockLine);\r\n                            break;\r\n                        case 'center':\r\n                            if(me.queryCommandValue('imagefloat') != 'center'){\r\n                                pN = img.parentNode;\r\n                                domUtils.setStyle(img,'float','none');\r\n                                tmpNode = img;\r\n                                while(pN && domUtils.getChildCount(pN,function(node){return !domUtils.isBr(node) && !domUtils.isWhitespace(node)}) == 1\r\n                                    && (dtd.$inline[pN.tagName] || pN.tagName == 'A')){\r\n                                    tmpNode = pN;\r\n                                    pN = pN.parentNode;\r\n                                }\r\n                                var pNode = me.document.createElement('p');\r\n                                domUtils.setAttributes(pNode,{\r\n\r\n                                    style:'text-align:center'\r\n                                });\r\n                                tmpNode.parentNode.insertBefore(pNode,tmpNode);\r\n                                pNode.appendChild(tmpNode);\r\n                                domUtils.setStyle(tmpNode,'float','');\r\n\r\n                            }\r\n\r\n\r\n                    }\r\n                } else {\r\n                    var range = me.selection.getRange();\r\n                    range.selectNode(ci).select();\r\n                    me.execCommand('imagefloat', opt.imageBlockLine);\r\n                }\r\n\r\n            }\r\n\r\n            //去掉冗余的标签\r\n            if(opt.removeEmptyNode){\r\n                if(opt.removeTagNames[ci.tagName.toLowerCase()] && domUtils.hasNoAttributes(ci) && domUtils.isEmptyBlock(ci)){\r\n                    domUtils.remove(ci);\r\n                }\r\n            }\r\n        }\r\n        if(opt.tobdc){\r\n            var root = UE.htmlparser(cont.innerHTML);\r\n            root.traversal(function(node){\r\n                if(node.type == 'text'){\r\n                    node.data = ToDBC(node.data)\r\n                }\r\n            });\r\n            cont.innerHTML = root.toHtml()\r\n        }\r\n        if(opt.bdc2sb){\r\n            var root = UE.htmlparser(cont.innerHTML);\r\n            root.traversal(function(node){\r\n                if(node.type == 'text'){\r\n                    node.data = DBC2SB(node.data)\r\n                }\r\n            });\r\n            cont.innerHTML = root.toHtml()\r\n        }\r\n        if(html){\r\n            html.html = cont.innerHTML;\r\n        }\r\n    }\r\n    if(opt.pasteFilter){\r\n        me.addListener('beforepaste',autotype);\r\n    }\r\n\r\n    function DBC2SB(str) {\r\n        var result = '';\r\n        for (var i = 0; i < str.length; i++) {\r\n            var code = str.charCodeAt(i); //获取当前字符的unicode编码\r\n            if (code >= 65281 && code <= 65373)//在这个unicode编码范围中的是所有的英文字母已经各种字符\r\n            {\r\n                result += String.fromCharCode(str.charCodeAt(i) - 65248); //把全角字符的unicode编码转换为对应半角字符的unicode码\r\n            } else if (code == 12288)//空格\r\n            {\r\n                result += String.fromCharCode(str.charCodeAt(i) - 12288 + 32);\r\n            } else {\r\n                result += str.charAt(i);\r\n            }\r\n        }\r\n        return result;\r\n    }\r\n    function ToDBC(txtstring) {\r\n        txtstring = utils.html(txtstring);\r\n        var tmp = \"\";\r\n        var mark = \"\";/*用于判断,如果是html尖括里的标记,则不进行全角的转换*/\r\n        for (var i = 0; i < txtstring.length; i++) {\r\n            if (txtstring.charCodeAt(i) == 32) {\r\n                tmp = tmp + String.fromCharCode(12288);\r\n            }\r\n            else if (txtstring.charCodeAt(i) < 127) {\r\n                tmp = tmp + String.fromCharCode(txtstring.charCodeAt(i) + 65248);\r\n            }\r\n            else {\r\n                tmp += txtstring.charAt(i);\r\n            }\r\n        }\r\n        return tmp;\r\n    }\r\n\r\n    function readLocalOpts() {\r\n        var cookieOpt = me.getPreferences('autotypeset');\r\n        utils.extend(me.options.autotypeset, cookieOpt);\r\n    }\r\n\r\n    me.commands['autotypeset'] = {\r\n        execCommand:function () {\r\n            me.removeListener('beforepaste',autotype);\r\n            if(opt.pasteFilter){\r\n                me.addListener('beforepaste',autotype);\r\n            }\r\n            autotype.call(me)\r\n        }\r\n\r\n    };\r\n\r\n};\r\n\r\n\r\n\r\n// plugins/autosubmit.js\r\n/**\r\n * 快捷键提交\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 提交表单\r\n * @command autosubmit\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'autosubmit' );\r\n * ```\r\n */\r\n\r\nUE.plugin.register('autosubmit',function(){\r\n    return {\r\n        shortcutkey:{\r\n            \"autosubmit\":\"ctrl+13\" //手动提交\r\n        },\r\n        commands:{\r\n            'autosubmit':{\r\n                execCommand:function () {\r\n                    var me=this,\r\n                        form = domUtils.findParentByTagName(me.iframe,\"form\", false);\r\n                    if (form){\r\n                        if(me.fireEvent(\"beforesubmit\")===false){\r\n                            return;\r\n                        }\r\n                        me.sync();\r\n                        form.submit();\r\n                    }\r\n                }\r\n            }\r\n        }\r\n    }\r\n});\r\n\r\n// plugins/background.js\r\n/**\r\n * 背景插件，为UEditor提供设置背景功能\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\nUE.plugin.register('background', function () {\r\n    var me = this,\r\n        cssRuleId = 'editor_background',\r\n        isSetColored,\r\n        reg = new RegExp('body[\\\\s]*\\\\{(.+)\\\\}', 'i');\r\n\r\n    function stringToObj(str) {\r\n        var obj = {}, styles = str.split(';');\r\n        utils.each(styles, function (v) {\r\n            var index = v.indexOf(':'),\r\n                key = utils.trim(v.substr(0, index)).toLowerCase();\r\n            key && (obj[key] = utils.trim(v.substr(index + 1) || ''));\r\n        });\r\n        return obj;\r\n    }\r\n\r\n    function setBackground(obj) {\r\n        if (obj) {\r\n            var styles = [];\r\n            for (var name in obj) {\r\n                if (obj.hasOwnProperty(name)) {\r\n                    styles.push(name + \":\" + obj[name] + '; ');\r\n                }\r\n            }\r\n            utils.cssRule(cssRuleId, styles.length ? ('body{' + styles.join(\"\") + '}') : '', me.document);\r\n        } else {\r\n            utils.cssRule(cssRuleId, '', me.document)\r\n        }\r\n    }\r\n    //重写editor.hasContent方法\r\n\r\n    var orgFn = me.hasContents;\r\n    me.hasContents = function(){\r\n        if(me.queryCommandValue('background')){\r\n            return true\r\n        }\r\n        return orgFn.apply(me,arguments);\r\n    };\r\n    return {\r\n        bindEvents: {\r\n            'getAllHtml': function (type, headHtml) {\r\n                var body = this.body,\r\n                    su = domUtils.getComputedStyle(body, \"background-image\"),\r\n                    url = \"\";\r\n                if (su.indexOf(me.options.imagePath) > 0) {\r\n                    url = su.substring(su.indexOf(me.options.imagePath), su.length - 1).replace(/\"|\\(|\\)/ig, \"\");\r\n                } else {\r\n                    url = su != \"none\" ? su.replace(/url\\(\"?|\"?\\)/ig, \"\") : \"\";\r\n                }\r\n                var html = '<style type=\"text/css\">body{';\r\n                var bgObj = {\r\n                    \"background-color\": domUtils.getComputedStyle(body, \"background-color\") || \"#ffffff\",\r\n                    'background-image': url ? 'url(' + url + ')' : '',\r\n                    'background-repeat': domUtils.getComputedStyle(body, \"background-repeat\") || \"\",\r\n                    'background-position': browser.ie ? (domUtils.getComputedStyle(body, \"background-position-x\") + \" \" + domUtils.getComputedStyle(body, \"background-position-y\")) : domUtils.getComputedStyle(body, \"background-position\"),\r\n                    'height': domUtils.getComputedStyle(body, \"height\")\r\n                };\r\n                for (var name in bgObj) {\r\n                    if (bgObj.hasOwnProperty(name)) {\r\n                        html += name + \":\" + bgObj[name] + \"; \";\r\n                    }\r\n                }\r\n                html += '}</style> ';\r\n                headHtml.push(html);\r\n            },\r\n            'aftersetcontent': function () {\r\n                if(isSetColored == false) setBackground();\r\n            }\r\n        },\r\n        inputRule: function (root) {\r\n            isSetColored = false;\r\n            utils.each(root.getNodesByTagName('p'), function (p) {\r\n                var styles = p.getAttr('data-background');\r\n                if (styles) {\r\n                    isSetColored = true;\r\n                    setBackground(stringToObj(styles));\r\n                    p.parentNode.removeChild(p);\r\n                }\r\n            })\r\n        },\r\n        outputRule: function (root) {\r\n            var me = this,\r\n                styles = (utils.cssRule(cssRuleId, me.document) || '').replace(/[\\n\\r]+/g, '').match(reg);\r\n            if (styles) {\r\n                root.appendChild(UE.uNode.createElement('<p style=\"display:none;\" data-background=\"' + utils.trim(styles[1].replace(/\"/g, '').replace(/[\\s]+/g, ' ')) + '\"><br/></p>'));\r\n            }\r\n        },\r\n        commands: {\r\n            'background': {\r\n                execCommand: function (cmd, obj) {\r\n                    setBackground(obj);\r\n                },\r\n                queryCommandValue: function () {\r\n                    var me = this,\r\n                        styles = (utils.cssRule(cssRuleId, me.document) || '').replace(/[\\n\\r]+/g, '').match(reg);\r\n                    return styles ? stringToObj(styles[1]) : null;\r\n                },\r\n                notNeedUndo: true\r\n            }\r\n        }\r\n    }\r\n});\r\n\r\n// plugins/image.js\r\n/**\r\n * 图片插入、排版插件\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 图片对齐方式\r\n * @command imagefloat\r\n * @method execCommand\r\n * @remind 值center为独占一行居中\r\n * @param { String } cmd 命令字符串\r\n * @param { String } align 对齐方式，可传left、right、none、center\r\n * @remaind center表示图片独占一行\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'imagefloat', 'center' );\r\n * ```\r\n */\r\n\r\n/**\r\n * 如果选区所在位置是图片区域\r\n * @command imagefloat\r\n * @method queryCommandValue\r\n * @param { String } cmd 命令字符串\r\n * @return { String } 返回图片对齐方式\r\n * @example\r\n * ```javascript\r\n * editor.queryCommandValue( 'imagefloat' );\r\n * ```\r\n */\r\n\r\nUE.commands['imagefloat'] = {\r\n    execCommand:function (cmd, align) {\r\n        var me = this,\r\n            range = me.selection.getRange();\r\n        if (!range.collapsed) {\r\n            var img = range.getClosedNode();\r\n            if (img && img.tagName == 'IMG') {\r\n                switch (align) {\r\n                    case 'left':\r\n                    case 'right':\r\n                    case 'none':\r\n                        var pN = img.parentNode, tmpNode, pre, next;\r\n                        while (dtd.$inline[pN.tagName] || pN.tagName == 'A') {\r\n                            pN = pN.parentNode;\r\n                        }\r\n                        tmpNode = pN;\r\n                        if (tmpNode.tagName == 'P' && domUtils.getStyle(tmpNode, 'text-align') == 'center') {\r\n                            if (!domUtils.isBody(tmpNode) && domUtils.getChildCount(tmpNode, function (node) {\r\n                                return !domUtils.isBr(node) && !domUtils.isWhitespace(node);\r\n                            }) == 1) {\r\n                                pre = tmpNode.previousSibling;\r\n                                next = tmpNode.nextSibling;\r\n                                if (pre && next && pre.nodeType == 1 && next.nodeType == 1 && pre.tagName == next.tagName && domUtils.isBlockElm(pre)) {\r\n                                    pre.appendChild(tmpNode.firstChild);\r\n                                    while (next.firstChild) {\r\n                                        pre.appendChild(next.firstChild);\r\n                                    }\r\n                                    domUtils.remove(tmpNode);\r\n                                    domUtils.remove(next);\r\n                                } else {\r\n                                    domUtils.setStyle(tmpNode, 'text-align', '');\r\n                                }\r\n\r\n\r\n                            }\r\n\r\n                            range.selectNode(img).select();\r\n                        }\r\n                        domUtils.setStyle(img, 'float', align == 'none' ? '' : align);\r\n                        if(align == 'none'){\r\n                            domUtils.removeAttributes(img,'align');\r\n                        }\r\n\r\n                        break;\r\n                    case 'center':\r\n                        if (me.queryCommandValue('imagefloat') != 'center') {\r\n                            pN = img.parentNode;\r\n                            domUtils.setStyle(img, 'float', '');\r\n                            domUtils.removeAttributes(img,'align');\r\n                            tmpNode = img;\r\n                            while (pN && domUtils.getChildCount(pN, function (node) {\r\n                                return !domUtils.isBr(node) && !domUtils.isWhitespace(node);\r\n                            }) == 1\r\n                                && (dtd.$inline[pN.tagName] || pN.tagName == 'A')) {\r\n                                tmpNode = pN;\r\n                                pN = pN.parentNode;\r\n                            }\r\n                            range.setStartBefore(tmpNode).setCursor(false);\r\n                            pN = me.document.createElement('div');\r\n                            pN.appendChild(tmpNode);\r\n                            domUtils.setStyle(tmpNode, 'float', '');\r\n\r\n                            me.execCommand('insertHtml', '<p id=\"_img_parent_tmp\" style=\"text-align:center\">' + pN.innerHTML + '</p>');\r\n\r\n                            tmpNode = me.document.getElementById('_img_parent_tmp');\r\n                            tmpNode.removeAttribute('id');\r\n                            tmpNode = tmpNode.firstChild;\r\n                            range.selectNode(tmpNode).select();\r\n                            //去掉后边多余的元素\r\n                            next = tmpNode.parentNode.nextSibling;\r\n                            if (next && domUtils.isEmptyNode(next)) {\r\n                                domUtils.remove(next);\r\n                            }\r\n\r\n                        }\r\n\r\n                        break;\r\n                }\r\n\r\n            }\r\n        }\r\n    },\r\n    queryCommandValue:function () {\r\n        var range = this.selection.getRange(),\r\n            startNode, floatStyle;\r\n        if (range.collapsed) {\r\n            return 'none';\r\n        }\r\n        startNode = range.getClosedNode();\r\n        if (startNode && startNode.nodeType == 1 && startNode.tagName == 'IMG') {\r\n            floatStyle = domUtils.getComputedStyle(startNode, 'float') || startNode.getAttribute('align');\r\n\r\n            if (floatStyle == 'none') {\r\n                floatStyle = domUtils.getComputedStyle(startNode.parentNode, 'text-align') == 'center' ? 'center' : floatStyle;\r\n            }\r\n            return {\r\n                left:1,\r\n                right:1,\r\n                center:1\r\n            }[floatStyle] ? floatStyle : 'none';\r\n        }\r\n        return 'none';\r\n\r\n\r\n    },\r\n    queryCommandState:function () {\r\n        var range = this.selection.getRange(),\r\n            startNode;\r\n\r\n        if (range.collapsed)  return -1;\r\n\r\n        startNode = range.getClosedNode();\r\n        if (startNode && startNode.nodeType == 1 && startNode.tagName == 'IMG') {\r\n            return 0;\r\n        }\r\n        return -1;\r\n    }\r\n};\r\n\r\n\r\n/**\r\n * 插入图片\r\n * @command insertimage\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @param { Object } opt 属性键值对，这些属性都将被复制到当前插入图片\r\n * @remind 该命令第二个参数可接受一个图片配置项对象的数组，可以插入多张图片，\r\n * 此时数组的每一个元素都是一个Object类型的图片属性集合。\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'insertimage', {\r\n *     src:'a/b/c.jpg',\r\n *     width:'100',\r\n *     height:'100'\r\n * } );\r\n * ```\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'insertimage', [{\r\n *     src:'a/b/c.jpg',\r\n *     width:'100',\r\n *     height:'100'\r\n * },{\r\n *     src:'a/b/d.jpg',\r\n *     width:'100',\r\n *     height:'100'\r\n * }] );\r\n * ```\r\n */\r\n\r\nUE.commands['insertimage'] = {\r\n    execCommand:function (cmd, opt) {\r\n\r\n        opt = utils.isArray(opt) ? opt : [opt];\r\n        if (!opt.length) {\r\n            return;\r\n        }\r\n        var me = this,\r\n            range = me.selection.getRange(),\r\n            img = range.getClosedNode();\r\n\r\n        if(me.fireEvent('beforeinsertimage', opt) === true){\r\n            return;\r\n        }\r\n\r\n        function unhtmlData(imgCi) {\r\n\r\n            utils.each('width,height,border,hspace,vspace'.split(','), function (item) {\r\n\r\n                if (imgCi[item]) {\r\n                    imgCi[item] = parseInt(imgCi[item], 10) || 0;\r\n                }\r\n            });\r\n\r\n            utils.each('src,_src'.split(','), function (item) {\r\n\r\n                if (imgCi[item]) {\r\n                    imgCi[item] = utils.unhtmlForUrl(imgCi[item]);\r\n                }\r\n            });\r\n            utils.each('title,alt'.split(','), function (item) {\r\n\r\n                if (imgCi[item]) {\r\n                    imgCi[item] = utils.unhtml(imgCi[item]);\r\n                }\r\n            });\r\n        }\r\n\r\n        if (img && /img/i.test(img.tagName) && (img.className != \"edui-faked-video\" || img.className.indexOf(\"edui-upload-video\")!=-1) && !img.getAttribute(\"word_img\")) {\r\n            var first = opt.shift();\r\n            var floatStyle = first['floatStyle'];\r\n            delete first['floatStyle'];\r\n////                img.style.border = (first.border||0) +\"px solid #000\";\r\n////                img.style.margin = (first.margin||0) +\"px\";\r\n//                img.style.cssText += ';margin:' + (first.margin||0) +\"px;\" + 'border:' + (first.border||0) +\"px solid #000\";\r\n            domUtils.setAttributes(img, first);\r\n            me.execCommand('imagefloat', floatStyle);\r\n            if (opt.length > 0) {\r\n                range.setStartAfter(img).setCursor(false, true);\r\n                me.execCommand('insertimage', opt);\r\n            }\r\n\r\n        } else {\r\n            var html = [], str = '', ci;\r\n            ci = opt[0];\r\n            if (opt.length == 1) {\r\n                unhtmlData(ci);\r\n\r\n                str = '<img src=\"' + ci.src + '\" ' + (ci._src ? ' _src=\"' + ci._src + '\" ' : '') +\r\n                    (ci.width ? 'width=\"' + ci.width + '\" ' : '') +\r\n                    (ci.height ? ' height=\"' + ci.height + '\" ' : '') +\r\n                    (ci['floatStyle'] == 'left' || ci['floatStyle'] == 'right' ? ' style=\"float:' + ci['floatStyle'] + ';\"' : '') +\r\n                    (ci.title && ci.title != \"\" ? ' title=\"' + ci.title + '\"' : '') +\r\n                    (ci.border && ci.border != \"0\" ? ' border=\"' + ci.border + '\"' : '') +\r\n                    (ci.alt && ci.alt != \"\" ? ' alt=\"' + ci.alt + '\"' : '') +\r\n                    (ci.hspace && ci.hspace != \"0\" ? ' hspace = \"' + ci.hspace + '\"' : '') +\r\n                    (ci.vspace && ci.vspace != \"0\" ? ' vspace = \"' + ci.vspace + '\"' : '') + '/>';\r\n                if (ci['floatStyle'] == 'center') {\r\n                    str = '<p style=\"text-align: center\">' + str + '</p>';\r\n                }\r\n                html.push(str);\r\n\r\n            } else {\r\n                for (var i = 0; ci = opt[i++];) {\r\n                    unhtmlData(ci);\r\n                    str = '<p ' + (ci['floatStyle'] == 'center' ? 'style=\"text-align: center\" ' : '') + '><img src=\"' + ci.src + '\" ' +\r\n                        (ci.width ? 'width=\"' + ci.width + '\" ' : '') + (ci._src ? ' _src=\"' + ci._src + '\" ' : '') +\r\n                        (ci.height ? ' height=\"' + ci.height + '\" ' : '') +\r\n                        ' style=\"' + (ci['floatStyle'] && ci['floatStyle'] != 'center' ? 'float:' + ci['floatStyle'] + ';' : '') +\r\n                        (ci.border || '') + '\" ' +\r\n                        (ci.title ? ' title=\"' + ci.title + '\"' : '') + ' /></p>';\r\n                    html.push(str);\r\n                }\r\n            }\r\n\r\n            me.execCommand('insertHtml', html.join(''));\r\n        }\r\n\r\n        me.fireEvent('afterinsertimage', opt)\r\n    }\r\n};\r\n\r\n\r\n// plugins/justify.js\r\n/**\r\n * 段落格式\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 段落对齐方式\r\n * @command justify\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @param { String } align 对齐方式：left => 居左，right => 居右，center => 居中，justify => 两端对齐\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'justify', 'center' );\r\n * ```\r\n */\r\n/**\r\n * 如果选区所在位置是段落区域，返回当前段落对齐方式\r\n * @command justify\r\n * @method queryCommandValue\r\n * @param { String } cmd 命令字符串\r\n * @return { String } 返回段落对齐方式\r\n * @example\r\n * ```javascript\r\n * editor.queryCommandValue( 'justify' );\r\n * ```\r\n */\r\n\r\nUE.plugins['justify']=function(){\r\n    var me=this,\r\n        block = domUtils.isBlockElm,\r\n        defaultValue = {\r\n            left:1,\r\n            right:1,\r\n            center:1,\r\n            justify:1\r\n        },\r\n        doJustify = function (range, style) {\r\n            var bookmark = range.createBookmark(),\r\n                filterFn = function (node) {\r\n                    return node.nodeType == 1 ? node.tagName.toLowerCase() != 'br' && !domUtils.isBookmarkNode(node) : !domUtils.isWhitespace(node);\r\n                };\r\n\r\n            range.enlarge(true);\r\n            var bookmark2 = range.createBookmark(),\r\n                current = domUtils.getNextDomNode(bookmark2.start, false, filterFn),\r\n                tmpRange = range.cloneRange(),\r\n                tmpNode;\r\n            while (current && !(domUtils.getPosition(current, bookmark2.end) & domUtils.POSITION_FOLLOWING)) {\r\n                if (current.nodeType == 3 || !block(current)) {\r\n                    tmpRange.setStartBefore(current);\r\n                    while (current && current !== bookmark2.end && !block(current)) {\r\n                        tmpNode = current;\r\n                        current = domUtils.getNextDomNode(current, false, null, function (node) {\r\n                            return !block(node);\r\n                        });\r\n                    }\r\n                    tmpRange.setEndAfter(tmpNode);\r\n                    var common = tmpRange.getCommonAncestor();\r\n                    if (!domUtils.isBody(common) && block(common)) {\r\n                        domUtils.setStyles(common, utils.isString(style) ? {'text-align':style} : style);\r\n                        current = common;\r\n                    } else {\r\n                        var p = range.document.createElement('p');\r\n                        domUtils.setStyles(p, utils.isString(style) ? {'text-align':style} : style);\r\n                        var frag = tmpRange.extractContents();\r\n                        p.appendChild(frag);\r\n                        tmpRange.insertNode(p);\r\n                        current = p;\r\n                    }\r\n                    current = domUtils.getNextDomNode(current, false, filterFn);\r\n                } else {\r\n                    current = domUtils.getNextDomNode(current, true, filterFn);\r\n                }\r\n            }\r\n            return range.moveToBookmark(bookmark2).moveToBookmark(bookmark);\r\n        };\r\n\r\n    UE.commands['justify'] = {\r\n        execCommand:function (cmdName, align) {\r\n            var range = this.selection.getRange(),\r\n                txt;\r\n\r\n            //闭合时单独处理\r\n            if (range.collapsed) {\r\n                txt = this.document.createTextNode('p');\r\n                range.insertNode(txt);\r\n            }\r\n            doJustify(range, align);\r\n            if (txt) {\r\n                range.setStartBefore(txt).collapse(true);\r\n                domUtils.remove(txt);\r\n            }\r\n\r\n            range.select();\r\n\r\n\r\n            return true;\r\n        },\r\n        queryCommandValue:function () {\r\n            var startNode = this.selection.getStart(),\r\n                value = domUtils.getComputedStyle(startNode, 'text-align');\r\n            return defaultValue[value] ? value : 'left';\r\n        },\r\n        queryCommandState:function () {\r\n            var start = this.selection.getStart(),\r\n                cell = start && domUtils.findParentByTagName(start, [\"td\", \"th\",\"caption\"], true);\r\n\r\n            return cell? -1:0;\r\n        }\r\n\r\n    };\r\n};\r\n\r\n\r\n// plugins/font.js\r\n/**\r\n * 字体颜色,背景色,字号,字体,下划线,删除线\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 字体颜色\r\n * @command forecolor\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @param { String } value 色值(必须十六进制)\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'forecolor', '#000' );\r\n * ```\r\n */\r\n/**\r\n * 返回选区字体颜色\r\n * @command forecolor\r\n * @method queryCommandValue\r\n * @param { String } cmd 命令字符串\r\n * @return { String } 返回字体颜色\r\n * @example\r\n * ```javascript\r\n * editor.queryCommandValue( 'forecolor' );\r\n * ```\r\n */\r\n\r\n/**\r\n * 字体背景颜色\r\n * @command backcolor\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @param { String } value 色值(必须十六进制)\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'backcolor', '#000' );\r\n * ```\r\n */\r\n/**\r\n * 返回选区字体颜色\r\n * @command backcolor\r\n * @method queryCommandValue\r\n * @param { String } cmd 命令字符串\r\n * @return { String } 返回字体背景颜色\r\n * @example\r\n * ```javascript\r\n * editor.queryCommandValue( 'backcolor' );\r\n * ```\r\n */\r\n\r\n/**\r\n * 字体大小\r\n * @command fontsize\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @param { String } value 字体大小\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'fontsize', '14px' );\r\n * ```\r\n */\r\n/**\r\n * 返回选区字体大小\r\n * @command fontsize\r\n * @method queryCommandValue\r\n * @param { String } cmd 命令字符串\r\n * @return { String } 返回字体大小\r\n * @example\r\n * ```javascript\r\n * editor.queryCommandValue( 'fontsize' );\r\n * ```\r\n */\r\n\r\n/**\r\n * 字体样式\r\n * @command fontfamily\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @param { String } value 字体样式\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'fontfamily', '微软雅黑' );\r\n * ```\r\n */\r\n/**\r\n * 返回选区字体样式\r\n * @command fontfamily\r\n * @method queryCommandValue\r\n * @param { String } cmd 命令字符串\r\n * @return { String } 返回字体样式\r\n * @example\r\n * ```javascript\r\n * editor.queryCommandValue( 'fontfamily' );\r\n * ```\r\n */\r\n\r\n/**\r\n * 字体下划线,与删除线互斥\r\n * @command underline\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'underline' );\r\n * ```\r\n */\r\n\r\n/**\r\n * 字体删除线,与下划线互斥\r\n * @command strikethrough\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'strikethrough' );\r\n * ```\r\n */\r\n\r\n/**\r\n * 字体边框\r\n * @command fontborder\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'fontborder' );\r\n * ```\r\n */\r\n\r\nUE.plugins['font'] = function () {\r\n    var me = this,\r\n        fonts = {\r\n            'forecolor': 'color',\r\n            'backcolor': 'background-color',\r\n            'fontsize': 'font-size',\r\n            'fontfamily': 'font-family',\r\n            'underline': 'text-decoration',\r\n            'strikethrough': 'text-decoration',\r\n            'fontborder': 'border'\r\n        },\r\n        needCmd = {'underline': 1, 'strikethrough': 1, 'fontborder': 1},\r\n        needSetChild = {\r\n            'forecolor': 'color',\r\n            'backcolor': 'background-color',\r\n            'fontsize': 'font-size',\r\n            'fontfamily': 'font-family'\r\n\r\n        };\r\n    me.setOpt({\r\n        'fontfamily': [\r\n            { name: 'songti', val: '宋体,SimSun'},\r\n            { name: 'yahei', val: '微软雅黑,Microsoft YaHei'},\r\n            { name: 'kaiti', val: '楷体,楷体_GB2312, SimKai'},\r\n            { name: 'heiti', val: '黑体, SimHei'},\r\n            { name: 'lishu', val: '隶书, SimLi'},\r\n            { name: 'andaleMono', val: 'andale mono'},\r\n            { name: 'arial', val: 'arial, helvetica,sans-serif'},\r\n            { name: 'arialBlack', val: 'arial black,avant garde'},\r\n            { name: 'comicSansMs', val: 'comic sans ms'},\r\n            { name: 'impact', val: 'impact,chicago'},\r\n            { name: 'timesNewRoman', val: 'times new roman'}\r\n        ],\r\n        'fontsize': [10, 11, 12, 14, 16, 18, 20, 24, 36]\r\n    });\r\n\r\n    function mergeWithParent(node){\r\n        var parent;\r\n        while(parent = node.parentNode){\r\n            if(parent.tagName == 'SPAN' && domUtils.getChildCount(parent,function(child){\r\n                return !domUtils.isBookmarkNode(child) && !domUtils.isBr(child)\r\n            }) == 1) {\r\n                parent.style.cssText += node.style.cssText;\r\n                domUtils.remove(node,true);\r\n                node = parent;\r\n\r\n            }else{\r\n                break;\r\n            }\r\n        }\r\n\r\n    }\r\n    function mergeChild(rng,cmdName,value){\r\n        if(needSetChild[cmdName]){\r\n            rng.adjustmentBoundary();\r\n            if(!rng.collapsed && rng.startContainer.nodeType == 1){\r\n                var start = rng.startContainer.childNodes[rng.startOffset];\r\n                if(start && domUtils.isTagNode(start,'span')){\r\n                    var bk = rng.createBookmark();\r\n                    utils.each(domUtils.getElementsByTagName(start, 'span'), function (span) {\r\n                        if (!span.parentNode || domUtils.isBookmarkNode(span))return;\r\n                        if(cmdName == 'backcolor' && domUtils.getComputedStyle(span,'background-color').toLowerCase() === value){\r\n                            return;\r\n                        }\r\n                        domUtils.removeStyle(span,needSetChild[cmdName]);\r\n                        if(span.style.cssText.replace(/^\\s+$/,'').length == 0){\r\n                            domUtils.remove(span,true)\r\n                        }\r\n                    });\r\n                    rng.moveToBookmark(bk)\r\n                }\r\n            }\r\n        }\r\n\r\n    }\r\n    function mergesibling(rng,cmdName,value) {\r\n        var collapsed = rng.collapsed,\r\n            bk = rng.createBookmark(), common;\r\n        if (collapsed) {\r\n            common = bk.start.parentNode;\r\n            while (dtd.$inline[common.tagName]) {\r\n                common = common.parentNode;\r\n            }\r\n        } else {\r\n            common = domUtils.getCommonAncestor(bk.start, bk.end);\r\n        }\r\n        utils.each(domUtils.getElementsByTagName(common, 'span'), function (span) {\r\n            if (!span.parentNode || domUtils.isBookmarkNode(span))return;\r\n            if (/\\s*border\\s*:\\s*none;?\\s*/i.test(span.style.cssText)) {\r\n                if(/^\\s*border\\s*:\\s*none;?\\s*$/.test(span.style.cssText)){\r\n                    domUtils.remove(span, true);\r\n                }else{\r\n                    domUtils.removeStyle(span,'border');\r\n                }\r\n                return\r\n            }\r\n            if (/border/i.test(span.style.cssText) && span.parentNode.tagName == 'SPAN' && /border/i.test(span.parentNode.style.cssText)) {\r\n                span.style.cssText = span.style.cssText.replace(/border[^:]*:[^;]+;?/gi, '');\r\n            }\r\n            if(!(cmdName=='fontborder' && value=='none')){\r\n                var next = span.nextSibling;\r\n                while (next && next.nodeType == 1 && next.tagName == 'SPAN' ) {\r\n                    if(domUtils.isBookmarkNode(next) && cmdName == 'fontborder') {\r\n                        span.appendChild(next);\r\n                        next = span.nextSibling;\r\n                        continue;\r\n                    }\r\n                    if (next.style.cssText == span.style.cssText) {\r\n                        domUtils.moveChild(next, span);\r\n                        domUtils.remove(next);\r\n                    }\r\n                    if (span.nextSibling === next)\r\n                        break;\r\n                    next = span.nextSibling;\r\n                }\r\n            }\r\n\r\n\r\n            mergeWithParent(span);\r\n            if(browser.ie && browser.version > 8 ){\r\n                //拷贝父亲们的特别的属性,这里只做背景颜色的处理\r\n                var parent = domUtils.findParent(span,function(n){return n.tagName == 'SPAN' && /background-color/.test(n.style.cssText)});\r\n                if(parent && !/background-color/.test(span.style.cssText)){\r\n                    span.style.backgroundColor = parent.style.backgroundColor;\r\n                }\r\n            }\r\n\r\n        });\r\n        rng.moveToBookmark(bk);\r\n        mergeChild(rng,cmdName,value)\r\n    }\r\n\r\n    me.addInputRule(function (root) {\r\n        utils.each(root.getNodesByTagName('u s del font strike'), function (node) {\r\n            if (node.tagName == 'font') {\r\n                var cssStyle = [];\r\n                for (var p in node.attrs) {\r\n                    switch (p) {\r\n                        case 'size':\r\n                            cssStyle.push('font-size:' +\r\n                                ({\r\n                                '1':'10',\r\n                                '2':'12',\r\n                                '3':'16',\r\n                                '4':'18',\r\n                                '5':'24',\r\n                                '6':'32',\r\n                                '7':'48'\r\n                            }[node.attrs[p]] || node.attrs[p]) + 'px');\r\n                            break;\r\n                        case 'color':\r\n                            cssStyle.push('color:' + node.attrs[p]);\r\n                            break;\r\n                        case 'face':\r\n                            cssStyle.push('font-family:' + node.attrs[p]);\r\n                            break;\r\n                        case 'style':\r\n                            cssStyle.push(node.attrs[p]);\r\n                    }\r\n                }\r\n                node.attrs = {\r\n                    'style': cssStyle.join(';')\r\n                };\r\n            } else {\r\n                var val = node.tagName == 'u' ? 'underline' : 'line-through';\r\n                node.attrs = {\r\n                    'style': (node.getAttr('style') || '') + 'text-decoration:' + val + ';'\r\n                }\r\n            }\r\n            node.tagName = 'span';\r\n        });\r\n//        utils.each(root.getNodesByTagName('span'), function (node) {\r\n//            var val;\r\n//            if(val = node.getAttr('class')){\r\n//                if(/fontstrikethrough/.test(val)){\r\n//                    node.setStyle('text-decoration','line-through');\r\n//                    if(node.attrs['class']){\r\n//                        node.attrs['class'] = node.attrs['class'].replace(/fontstrikethrough/,'');\r\n//                    }else{\r\n//                        node.setAttr('class')\r\n//                    }\r\n//                }\r\n//                if(/fontborder/.test(val)){\r\n//                    node.setStyle('border','1px solid #000');\r\n//                    if(node.attrs['class']){\r\n//                        node.attrs['class'] = node.attrs['class'].replace(/fontborder/,'');\r\n//                    }else{\r\n//                        node.setAttr('class')\r\n//                    }\r\n//                }\r\n//            }\r\n//        });\r\n    });\r\n//    me.addOutputRule(function(root){\r\n//        utils.each(root.getNodesByTagName('span'), function (node) {\r\n//            var val;\r\n//            if(val = node.getStyle('text-decoration')){\r\n//                if(/line-through/.test(val)){\r\n//                    if(node.attrs['class']){\r\n//                        node.attrs['class'] += ' fontstrikethrough';\r\n//                    }else{\r\n//                        node.setAttr('class','fontstrikethrough')\r\n//                    }\r\n//                }\r\n//\r\n//                node.setStyle('text-decoration')\r\n//            }\r\n//            if(val = node.getStyle('border')){\r\n//                if(/1px/.test(val) && /solid/.test(val)){\r\n//                    if(node.attrs['class']){\r\n//                        node.attrs['class'] += ' fontborder';\r\n//\r\n//                    }else{\r\n//                        node.setAttr('class','fontborder')\r\n//                    }\r\n//                }\r\n//                node.setStyle('border')\r\n//\r\n//            }\r\n//        });\r\n//    });\r\n    for (var p in fonts) {\r\n        (function (cmd, style) {\r\n            UE.commands[cmd] = {\r\n                execCommand: function (cmdName, value) {\r\n                    value = value || (this.queryCommandState(cmdName) ? 'none' : cmdName == 'underline' ? 'underline' :\r\n                        cmdName == 'fontborder' ? '1px solid #000' :\r\n                            'line-through');\r\n                    var me = this,\r\n                        range = this.selection.getRange(),\r\n                        text;\r\n\r\n                    if (value == 'default') {\r\n\r\n                        if (range.collapsed) {\r\n                            text = me.document.createTextNode('font');\r\n                            range.insertNode(text).select();\r\n\r\n                        }\r\n                        me.execCommand('removeFormat', 'span,a', style);\r\n                        if (text) {\r\n                            range.setStartBefore(text).collapse(true);\r\n                            domUtils.remove(text);\r\n                        }\r\n                        mergesibling(range,cmdName,value);\r\n                        range.select()\r\n                    } else {\r\n                        if (!range.collapsed) {\r\n                            if (needCmd[cmd] && me.queryCommandValue(cmd)) {\r\n                                me.execCommand('removeFormat', 'span,a', style);\r\n                            }\r\n                            range = me.selection.getRange();\r\n\r\n                            range.applyInlineStyle('span', {'style': style + ':' + value});\r\n                            mergesibling(range, cmdName,value);\r\n                            range.select();\r\n                        } else {\r\n\r\n                            var span = domUtils.findParentByTagName(range.startContainer, 'span', true);\r\n                            text = me.document.createTextNode('font');\r\n                            if (span && !span.children.length && !span[browser.ie ? 'innerText' : 'textContent'].replace(fillCharReg, '').length) {\r\n                                //for ie hack when enter\r\n                                range.insertNode(text);\r\n                                if (needCmd[cmd]) {\r\n                                    range.selectNode(text).select();\r\n                                    me.execCommand('removeFormat', 'span,a', style, null);\r\n\r\n                                    span = domUtils.findParentByTagName(text, 'span', true);\r\n                                    range.setStartBefore(text);\r\n\r\n                                }\r\n                                span && (span.style.cssText += ';' + style + ':' + value);\r\n                                range.collapse(true).select();\r\n\r\n\r\n                            } else {\r\n                                range.insertNode(text);\r\n                                range.selectNode(text).select();\r\n                                span = range.document.createElement('span');\r\n\r\n                                if (needCmd[cmd]) {\r\n                                    //a标签内的不处理跳过\r\n                                    if (domUtils.findParentByTagName(text, 'a', true)) {\r\n                                        range.setStartBefore(text).setCursor();\r\n                                        domUtils.remove(text);\r\n                                        return;\r\n                                    }\r\n                                    me.execCommand('removeFormat', 'span,a', style);\r\n                                }\r\n\r\n                                span.style.cssText = style + ':' + value;\r\n\r\n\r\n                                text.parentNode.insertBefore(span, text);\r\n                                //修复，span套span 但样式不继承的问题\r\n                                if (!browser.ie || browser.ie && browser.version == 9) {\r\n                                    var spanParent = span.parentNode;\r\n                                    while (!domUtils.isBlockElm(spanParent)) {\r\n                                        if (spanParent.tagName == 'SPAN') {\r\n                                            //opera合并style不会加入\";\"\r\n                                            span.style.cssText = spanParent.style.cssText + \";\" + span.style.cssText;\r\n                                        }\r\n                                        spanParent = spanParent.parentNode;\r\n                                    }\r\n                                }\r\n\r\n\r\n                                if (opera) {\r\n                                    setTimeout(function () {\r\n                                        range.setStart(span, 0).collapse(true);\r\n                                        mergesibling(range, cmdName,value);\r\n                                        range.select();\r\n                                    });\r\n                                } else {\r\n                                    range.setStart(span, 0).collapse(true);\r\n                                    mergesibling(range,cmdName,value);\r\n                                    range.select();\r\n                                }\r\n\r\n                                //trace:981\r\n                                //domUtils.mergeToParent(span)\r\n                            }\r\n                            domUtils.remove(text);\r\n                        }\r\n\r\n\r\n                    }\r\n                    return true;\r\n                },\r\n                queryCommandValue: function (cmdName) {\r\n                    var startNode = this.selection.getStart();\r\n\r\n                    //trace:946\r\n                    if (cmdName == 'underline' || cmdName == 'strikethrough') {\r\n                        var tmpNode = startNode, value;\r\n                        while (tmpNode && !domUtils.isBlockElm(tmpNode) && !domUtils.isBody(tmpNode)) {\r\n                            if (tmpNode.nodeType == 1) {\r\n                                value = domUtils.getComputedStyle(tmpNode, style);\r\n                                if (value != 'none') {\r\n                                    return value;\r\n                                }\r\n                            }\r\n\r\n                            tmpNode = tmpNode.parentNode;\r\n                        }\r\n                        return 'none';\r\n                    }\r\n                    if (cmdName == 'fontborder') {\r\n                        var tmp = startNode, val;\r\n                        while (tmp && dtd.$inline[tmp.tagName]) {\r\n                            if (val = domUtils.getComputedStyle(tmp, 'border')) {\r\n\r\n                                if (/1px/.test(val) && /solid/.test(val)) {\r\n                                    return val;\r\n                                }\r\n                            }\r\n                            tmp = tmp.parentNode;\r\n                        }\r\n                        return ''\r\n                    }\r\n\r\n                    if( cmdName == 'FontSize' ) {\r\n                        var styleVal = domUtils.getComputedStyle(startNode, style),\r\n                            tmp = /^([\\d\\.]+)(\\w+)$/.exec( styleVal );\r\n\r\n                        if( tmp ) {\r\n\r\n                            return Math.floor( tmp[1] ) + tmp[2];\r\n\r\n                        }\r\n\r\n                        return styleVal;\r\n\r\n                    }\r\n\r\n                    return  domUtils.getComputedStyle(startNode, style);\r\n                },\r\n                queryCommandState: function (cmdName) {\r\n                    if (!needCmd[cmdName])\r\n                        return 0;\r\n                    var val = this.queryCommandValue(cmdName);\r\n                    if (cmdName == 'fontborder') {\r\n                        return /1px/.test(val) && /solid/.test(val)\r\n                    } else {\r\n                        return  cmdName == 'underline' ? /underline/.test(val) : /line\\-through/.test(val);\r\n\r\n                    }\r\n\r\n                }\r\n            };\r\n        })(p, fonts[p]);\r\n    }\r\n};\r\n\r\n// plugins/link.js\r\n/**\r\n * 超链接\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 插入超链接\r\n * @command link\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @param { Object } options   设置自定义属性，例如：url、title、target\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'link', '{\r\n *     url:'ueditor.baidu.com',\r\n *     title:'ueditor',\r\n *     target:'_blank'\r\n * }' );\r\n * ```\r\n */\r\n/**\r\n * 返回当前选中的第一个超链接节点\r\n * @command link\r\n * @method queryCommandValue\r\n * @param { String } cmd 命令字符串\r\n * @return { Element } 超链接节点\r\n * @example\r\n * ```javascript\r\n * editor.queryCommandValue( 'link' );\r\n * ```\r\n */\r\n\r\n/**\r\n * 取消超链接\r\n * @command unlink\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'unlink');\r\n * ```\r\n */\r\n\r\nUE.plugins['link'] = function(){\r\n    function optimize( range ) {\r\n        var start = range.startContainer,end = range.endContainer;\r\n\r\n        if ( start = domUtils.findParentByTagName( start, 'a', true ) ) {\r\n            range.setStartBefore( start );\r\n        }\r\n        if ( end = domUtils.findParentByTagName( end, 'a', true ) ) {\r\n            range.setEndAfter( end );\r\n        }\r\n    }\r\n\r\n\r\n    UE.commands['unlink'] = {\r\n        execCommand : function() {\r\n            var range = this.selection.getRange(),\r\n                bookmark;\r\n            if(range.collapsed && !domUtils.findParentByTagName( range.startContainer, 'a', true )){\r\n                return;\r\n            }\r\n            bookmark = range.createBookmark();\r\n            optimize( range );\r\n            range.removeInlineStyle( 'a' ).moveToBookmark( bookmark ).select();\r\n        },\r\n        queryCommandState : function(){\r\n            return !this.highlight && this.queryCommandValue('link') ?  0 : -1;\r\n        }\r\n\r\n    };\r\n    function doLink(range,opt,me){\r\n        var rngClone = range.cloneRange(),\r\n            link = me.queryCommandValue('link');\r\n        optimize( range = range.adjustmentBoundary() );\r\n        var start = range.startContainer;\r\n        if(start.nodeType == 1 && link){\r\n            start = start.childNodes[range.startOffset];\r\n            if(start && start.nodeType == 1 && start.tagName == 'A' && /^(?:https?|ftp|file)\\s*:\\s*\\/\\//.test(start[browser.ie?'innerText':'textContent'])){\r\n                start[browser.ie ? 'innerText' : 'textContent'] =  utils.html(opt.textValue||opt.href);\r\n\r\n            }\r\n        }\r\n        if( !rngClone.collapsed || link){\r\n            range.removeInlineStyle( 'a' );\r\n            rngClone = range.cloneRange();\r\n        }\r\n\r\n        if ( rngClone.collapsed ) {\r\n            var a = range.document.createElement( 'a'),\r\n                text = '';\r\n            if(opt.textValue){\r\n\r\n                text =   utils.html(opt.textValue);\r\n                delete opt.textValue;\r\n            }else{\r\n                text =   utils.html(opt.href);\r\n\r\n            }\r\n            domUtils.setAttributes( a, opt );\r\n            start =  domUtils.findParentByTagName( rngClone.startContainer, 'a', true );\r\n            if(start && domUtils.isInNodeEndBoundary(rngClone,start)){\r\n                range.setStartAfter(start).collapse(true);\r\n\r\n            }\r\n            a[browser.ie ? 'innerText' : 'textContent'] = text;\r\n            range.insertNode(a).selectNode( a );\r\n        } else {\r\n            range.applyInlineStyle( 'a', opt );\r\n\r\n        }\r\n    }\r\n    UE.commands['link'] = {\r\n        execCommand : function( cmdName, opt ) {\r\n            var range;\r\n            opt._href && (opt._href = utils.unhtml(opt._href,/[<\">]/g));\r\n            opt.href && (opt.href = utils.unhtml(opt.href,/[<\">]/g));\r\n            opt.textValue && (opt.textValue = utils.unhtml(opt.textValue,/[<\">]/g));\r\n            doLink(range=this.selection.getRange(),opt,this);\r\n            //闭合都不加占位符，如果加了会在a后边多个占位符节点，导致a是图片背景组成的列表，出现空白问题\r\n            range.collapse().select(true);\r\n\r\n        },\r\n        queryCommandValue : function() {\r\n            var range = this.selection.getRange(),\r\n                node;\r\n            if ( range.collapsed ) {\r\n//                    node = this.selection.getStart();\r\n                //在ie下getstart()取值偏上了\r\n                node = range.startContainer;\r\n                node = node.nodeType == 1 ? node : node.parentNode;\r\n\r\n                if ( node && (node = domUtils.findParentByTagName( node, 'a', true )) && ! domUtils.isInNodeEndBoundary(range,node)) {\r\n\r\n                    return node;\r\n                }\r\n            } else {\r\n                //trace:1111  如果是<p><a>xx</a></p> startContainer是p就会找不到a\r\n                range.shrinkBoundary();\r\n                var start = range.startContainer.nodeType  == 3 || !range.startContainer.childNodes[range.startOffset] ? range.startContainer : range.startContainer.childNodes[range.startOffset],\r\n                    end =  range.endContainer.nodeType == 3 || range.endOffset == 0 ? range.endContainer : range.endContainer.childNodes[range.endOffset-1],\r\n                    common = range.getCommonAncestor();\r\n                node = domUtils.findParentByTagName( common, 'a', true );\r\n                if ( !node && common.nodeType == 1){\r\n\r\n                    var as = common.getElementsByTagName( 'a' ),\r\n                        ps,pe;\r\n\r\n                    for ( var i = 0,ci; ci = as[i++]; ) {\r\n                        ps = domUtils.getPosition( ci, start ),pe = domUtils.getPosition( ci,end);\r\n                        if ( (ps & domUtils.POSITION_FOLLOWING || ps & domUtils.POSITION_CONTAINS)\r\n                            &&\r\n                            (pe & domUtils.POSITION_PRECEDING || pe & domUtils.POSITION_CONTAINS)\r\n                            ) {\r\n                            node = ci;\r\n                            break;\r\n                        }\r\n                    }\r\n                }\r\n                return node;\r\n            }\r\n\r\n        },\r\n        queryCommandState : function() {\r\n            //判断如果是视频的话连接不可用\r\n            //fix 853\r\n            var img = this.selection.getRange().getClosedNode(),\r\n                flag = img && (img.className == \"edui-faked-video\" || img.className.indexOf(\"edui-upload-video\")!=-1);\r\n            return flag ? -1 : 0;\r\n        }\r\n    };\r\n};\r\n\r\n// plugins/iframe.js\r\n///import core\r\n///import plugins\\inserthtml.js\r\n///commands 插入框架\r\n///commandsName  InsertFrame\r\n///commandsTitle  插入Iframe\r\n///commandsDialog  dialogs\\insertframe\r\n\r\nUE.plugins['insertframe'] = function() {\r\n   var me =this;\r\n    function deleteIframe(){\r\n        me._iframe && delete me._iframe;\r\n    }\r\n\r\n    me.addListener(\"selectionchange\",function(){\r\n        deleteIframe();\r\n    });\r\n\r\n};\r\n\r\n\r\n\r\n// plugins/scrawl.js\r\n///import core\r\n///commands 涂鸦\r\n///commandsName  Scrawl\r\n///commandsTitle  涂鸦\r\n///commandsDialog  dialogs\\scrawl\r\nUE.commands['scrawl'] = {\r\n    queryCommandState : function(){\r\n        return ( browser.ie && browser.version  <= 8 ) ? -1 :0;\r\n    }\r\n};\r\n\r\n\r\n// plugins/removeformat.js\r\n/**\r\n * 清除格式\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 清除文字样式\r\n * @command removeformat\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @param   {String}   tags     以逗号隔开的标签。如：strong\r\n * @param   {String}   style    样式如：color\r\n * @param   {String}   attrs    属性如:width\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'removeformat', 'strong','color','width' );\r\n * ```\r\n */\r\n\r\nUE.plugins['removeformat'] = function(){\r\n    var me = this;\r\n    me.setOpt({\r\n       'removeFormatTags': 'b,big,code,del,dfn,em,font,i,ins,kbd,q,samp,small,span,strike,strong,sub,sup,tt,u,var',\r\n       'removeFormatAttributes':'class,style,lang,width,height,align,hspace,valign'\r\n    });\r\n    me.commands['removeformat'] = {\r\n        execCommand : function( cmdName, tags, style, attrs,notIncludeA ) {\r\n\r\n            var tagReg = new RegExp( '^(?:' + (tags || this.options.removeFormatTags).replace( /,/g, '|' ) + ')$', 'i' ) ,\r\n                removeFormatAttributes = style ? [] : (attrs || this.options.removeFormatAttributes).split( ',' ),\r\n                range = new dom.Range( this.document ),\r\n                bookmark,node,parent,\r\n                filter = function( node ) {\r\n                    return node.nodeType == 1;\r\n                };\r\n\r\n            function isRedundantSpan (node) {\r\n                if (node.nodeType == 3 || node.tagName.toLowerCase() != 'span'){\r\n                    return 0;\r\n                }\r\n                if (browser.ie) {\r\n                    //ie 下判断实效，所以只能简单用style来判断\r\n                    //return node.style.cssText == '' ? 1 : 0;\r\n                    var attrs = node.attributes;\r\n                    if ( attrs.length ) {\r\n                        for ( var i = 0,l = attrs.length; i<l; i++ ) {\r\n                            if ( attrs[i].specified ) {\r\n                                return 0;\r\n                            }\r\n                        }\r\n                        return 1;\r\n                    }\r\n                }\r\n                return !node.attributes.length;\r\n            }\r\n            function doRemove( range ) {\r\n\r\n                var bookmark1 = range.createBookmark();\r\n                if ( range.collapsed ) {\r\n                    range.enlarge( true );\r\n                }\r\n\r\n                //不能把a标签切了\r\n                if(!notIncludeA){\r\n                    var aNode = domUtils.findParentByTagName(range.startContainer,'a',true);\r\n                    if(aNode){\r\n                        range.setStartBefore(aNode);\r\n                    }\r\n\r\n                    aNode = domUtils.findParentByTagName(range.endContainer,'a',true);\r\n                    if(aNode){\r\n                        range.setEndAfter(aNode);\r\n                    }\r\n\r\n                }\r\n\r\n\r\n                bookmark = range.createBookmark();\r\n\r\n                node = bookmark.start;\r\n\r\n                //切开始\r\n                while ( (parent = node.parentNode) && !domUtils.isBlockElm( parent ) ) {\r\n                    domUtils.breakParent( node, parent );\r\n\r\n                    domUtils.clearEmptySibling( node );\r\n                }\r\n                if ( bookmark.end ) {\r\n                    //切结束\r\n                    node = bookmark.end;\r\n                    while ( (parent = node.parentNode) && !domUtils.isBlockElm( parent ) ) {\r\n                        domUtils.breakParent( node, parent );\r\n                        domUtils.clearEmptySibling( node );\r\n                    }\r\n\r\n                    //开始去除样式\r\n                    var current = domUtils.getNextDomNode( bookmark.start, false, filter ),\r\n                        next;\r\n                    while ( current ) {\r\n                        if ( current == bookmark.end ) {\r\n                            break;\r\n                        }\r\n\r\n                        next = domUtils.getNextDomNode( current, true, filter );\r\n\r\n                        if ( !dtd.$empty[current.tagName.toLowerCase()] && !domUtils.isBookmarkNode( current ) ) {\r\n                            if ( tagReg.test( current.tagName ) ) {\r\n                                if ( style ) {\r\n                                    domUtils.removeStyle( current, style );\r\n                                    if ( isRedundantSpan( current ) && style != 'text-decoration'){\r\n                                        domUtils.remove( current, true );\r\n                                    }\r\n                                } else {\r\n                                    domUtils.remove( current, true );\r\n                                }\r\n                            } else {\r\n                                //trace:939  不能把list上的样式去掉\r\n                                if(!dtd.$tableContent[current.tagName] && !dtd.$list[current.tagName]){\r\n                                    domUtils.removeAttributes( current, removeFormatAttributes );\r\n                                    if ( isRedundantSpan( current ) ){\r\n                                        domUtils.remove( current, true );\r\n                                    }\r\n                                }\r\n\r\n                            }\r\n                        }\r\n                        current = next;\r\n                    }\r\n                }\r\n                //trace:1035\r\n                //trace:1096 不能把td上的样式去掉，比如边框\r\n                var pN = bookmark.start.parentNode;\r\n                if(domUtils.isBlockElm(pN) && !dtd.$tableContent[pN.tagName] && !dtd.$list[pN.tagName]){\r\n                    domUtils.removeAttributes(  pN,removeFormatAttributes );\r\n                }\r\n                pN = bookmark.end.parentNode;\r\n                if(bookmark.end && domUtils.isBlockElm(pN) && !dtd.$tableContent[pN.tagName]&& !dtd.$list[pN.tagName]){\r\n                    domUtils.removeAttributes(  pN,removeFormatAttributes );\r\n                }\r\n                range.moveToBookmark( bookmark ).moveToBookmark(bookmark1);\r\n                //清除冗余的代码 <b><bookmark></b>\r\n                var node = range.startContainer,\r\n                    tmp,\r\n                    collapsed = range.collapsed;\r\n                while(node.nodeType == 1 && domUtils.isEmptyNode(node) && dtd.$removeEmpty[node.tagName]){\r\n                    tmp = node.parentNode;\r\n                    range.setStartBefore(node);\r\n                    //trace:937\r\n                    //更新结束边界\r\n                    if(range.startContainer === range.endContainer){\r\n                        range.endOffset--;\r\n                    }\r\n                    domUtils.remove(node);\r\n                    node = tmp;\r\n                }\r\n\r\n                if(!collapsed){\r\n                    node = range.endContainer;\r\n                    while(node.nodeType == 1 && domUtils.isEmptyNode(node) && dtd.$removeEmpty[node.tagName]){\r\n                        tmp = node.parentNode;\r\n                        range.setEndBefore(node);\r\n                        domUtils.remove(node);\r\n\r\n                        node = tmp;\r\n                    }\r\n\r\n\r\n                }\r\n            }\r\n\r\n\r\n\r\n            range = this.selection.getRange();\r\n            doRemove( range );\r\n            range.select();\r\n\r\n        }\r\n\r\n    };\r\n\r\n};\r\n\r\n\r\n// plugins/blockquote.js\r\n/**\r\n * 添加引用\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 添加引用\r\n * @command blockquote\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'blockquote' );\r\n * ```\r\n */\r\n\r\n/**\r\n * 添加引用\r\n * @command blockquote\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @param { Object } attrs 节点属性\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'blockquote',{\r\n *     style: \"color: red;\"\r\n * } );\r\n * ```\r\n */\r\n\r\n\r\nUE.plugins['blockquote'] = function(){\r\n    var me = this;\r\n    function getObj(editor){\r\n        return domUtils.filterNodeList(editor.selection.getStartElementPath(),'blockquote');\r\n    }\r\n    me.commands['blockquote'] = {\r\n        execCommand : function( cmdName, attrs ) {\r\n            var range = this.selection.getRange(),\r\n                obj = getObj(this),\r\n                blockquote = dtd.blockquote,\r\n                bookmark = range.createBookmark();\r\n\r\n            if ( obj ) {\r\n\r\n                    var start = range.startContainer,\r\n                        startBlock = domUtils.isBlockElm(start) ? start : domUtils.findParent(start,function(node){return domUtils.isBlockElm(node)}),\r\n\r\n                        end = range.endContainer,\r\n                        endBlock = domUtils.isBlockElm(end) ? end :  domUtils.findParent(end,function(node){return domUtils.isBlockElm(node)});\r\n\r\n                    //处理一下li\r\n                    startBlock = domUtils.findParentByTagName(startBlock,'li',true) || startBlock;\r\n                    endBlock = domUtils.findParentByTagName(endBlock,'li',true) || endBlock;\r\n\r\n\r\n                    if(startBlock.tagName == 'LI' || startBlock.tagName == 'TD' || startBlock === obj || domUtils.isBody(startBlock)){\r\n                        domUtils.remove(obj,true);\r\n                    }else{\r\n                        domUtils.breakParent(startBlock,obj);\r\n                    }\r\n\r\n                    if(startBlock !== endBlock){\r\n                        obj = domUtils.findParentByTagName(endBlock,'blockquote');\r\n                        if(obj){\r\n                            if(endBlock.tagName == 'LI' || endBlock.tagName == 'TD'|| domUtils.isBody(endBlock)){\r\n                                obj.parentNode && domUtils.remove(obj,true);\r\n                            }else{\r\n                                domUtils.breakParent(endBlock,obj);\r\n                            }\r\n\r\n                        }\r\n                    }\r\n\r\n                    var blockquotes = domUtils.getElementsByTagName(this.document,'blockquote');\r\n                    for(var i=0,bi;bi=blockquotes[i++];){\r\n                        if(!bi.childNodes.length){\r\n                            domUtils.remove(bi);\r\n                        }else if(domUtils.getPosition(bi,startBlock)&domUtils.POSITION_FOLLOWING && domUtils.getPosition(bi,endBlock)&domUtils.POSITION_PRECEDING){\r\n                            domUtils.remove(bi,true);\r\n                        }\r\n                    }\r\n\r\n\r\n\r\n\r\n            } else {\r\n\r\n                var tmpRange = range.cloneRange(),\r\n                    node = tmpRange.startContainer.nodeType == 1 ? tmpRange.startContainer : tmpRange.startContainer.parentNode,\r\n                    preNode = node,\r\n                    doEnd = 1;\r\n\r\n                //调整开始\r\n                while ( 1 ) {\r\n                    if ( domUtils.isBody(node) ) {\r\n                        if ( preNode !== node ) {\r\n                            if ( range.collapsed ) {\r\n                                tmpRange.selectNode( preNode );\r\n                                doEnd = 0;\r\n                            } else {\r\n                                tmpRange.setStartBefore( preNode );\r\n                            }\r\n                        }else{\r\n                            tmpRange.setStart(node,0);\r\n                        }\r\n\r\n                        break;\r\n                    }\r\n                    if ( !blockquote[node.tagName] ) {\r\n                        if ( range.collapsed ) {\r\n                            tmpRange.selectNode( preNode );\r\n                        } else{\r\n                            tmpRange.setStartBefore( preNode);\r\n                        }\r\n                        break;\r\n                    }\r\n\r\n                    preNode = node;\r\n                    node = node.parentNode;\r\n                }\r\n\r\n                //调整结束\r\n                if ( doEnd ) {\r\n                    preNode = node =  node = tmpRange.endContainer.nodeType == 1 ? tmpRange.endContainer : tmpRange.endContainer.parentNode;\r\n                    while ( 1 ) {\r\n\r\n                        if ( domUtils.isBody( node ) ) {\r\n                            if ( preNode !== node ) {\r\n\r\n                                tmpRange.setEndAfter( preNode );\r\n\r\n                            } else {\r\n                                tmpRange.setEnd( node, node.childNodes.length );\r\n                            }\r\n\r\n                            break;\r\n                        }\r\n                        if ( !blockquote[node.tagName] ) {\r\n                            tmpRange.setEndAfter( preNode );\r\n                            break;\r\n                        }\r\n\r\n                        preNode = node;\r\n                        node = node.parentNode;\r\n                    }\r\n\r\n                }\r\n\r\n\r\n                node = range.document.createElement( 'blockquote' );\r\n                domUtils.setAttributes( node, attrs );\r\n                node.appendChild( tmpRange.extractContents() );\r\n                tmpRange.insertNode( node );\r\n                //去除重复的\r\n                var childs = domUtils.getElementsByTagName(node,'blockquote');\r\n                for(var i=0,ci;ci=childs[i++];){\r\n                    if(ci.parentNode){\r\n                        domUtils.remove(ci,true);\r\n                    }\r\n                }\r\n\r\n            }\r\n            range.moveToBookmark( bookmark ).select();\r\n        },\r\n        queryCommandState : function() {\r\n            return getObj(this) ? 1 : 0;\r\n        }\r\n    };\r\n};\r\n\r\n\r\n\r\n// plugins/convertcase.js\r\n/**\r\n * 大小写转换\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 把选区内文本变大写，与“tolowercase”命令互斥\r\n * @command touppercase\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'touppercase' );\r\n * ```\r\n */\r\n\r\n/**\r\n * 把选区内文本变小写，与“touppercase”命令互斥\r\n * @command tolowercase\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'tolowercase' );\r\n * ```\r\n */\r\nUE.commands['touppercase'] =\r\nUE.commands['tolowercase'] = {\r\n    execCommand:function (cmd) {\r\n        var me = this;\r\n        var rng = me.selection.getRange();\r\n        if(rng.collapsed){\r\n            return rng;\r\n        }\r\n        var bk = rng.createBookmark(),\r\n            bkEnd = bk.end,\r\n            filterFn = function( node ) {\r\n                return !domUtils.isBr(node) && !domUtils.isWhitespace( node );\r\n            },\r\n            curNode = domUtils.getNextDomNode( bk.start, false, filterFn );\r\n        while ( curNode && (domUtils.getPosition( curNode, bkEnd ) & domUtils.POSITION_PRECEDING) ) {\r\n\r\n            if ( curNode.nodeType == 3 ) {\r\n                curNode.nodeValue = curNode.nodeValue[cmd == 'touppercase' ? 'toUpperCase' : 'toLowerCase']();\r\n            }\r\n            curNode = domUtils.getNextDomNode( curNode, true, filterFn );\r\n            if(curNode === bkEnd){\r\n                break;\r\n            }\r\n\r\n        }\r\n        rng.moveToBookmark(bk).select();\r\n    }\r\n};\r\n\r\n\r\n\r\n// plugins/indent.js\r\n/**\r\n * 首行缩进\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 缩进\r\n * @command indent\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'indent' );\r\n * ```\r\n */\r\nUE.commands['indent'] = {\r\n    execCommand : function() {\r\n         var me = this,value = me.queryCommandState(\"indent\") ? \"0em\" : (me.options.indentValue || '2em');\r\n         me.execCommand('Paragraph','p',{style:'text-indent:'+ value});\r\n    },\r\n    queryCommandState : function() {\r\n        var pN = domUtils.filterNodeList(this.selection.getStartElementPath(),'p h1 h2 h3 h4 h5 h6');\r\n        return pN && pN.style.textIndent && parseInt(pN.style.textIndent) ?  1 : 0;\r\n    }\r\n\r\n};\r\n\r\n\r\n// plugins/print.js\r\n/**\r\n * 打印\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 打印\r\n * @command print\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'print' );\r\n * ```\r\n */\r\nUE.commands['print'] = {\r\n    execCommand : function(){\r\n        this.window.print();\r\n    },\r\n    notNeedUndo : 1\r\n};\r\n\r\n\r\n\r\n// plugins/preview.js\r\n/**\r\n * 预览\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 预览\r\n * @command preview\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'preview' );\r\n * ```\r\n */\r\nUE.commands['preview'] = {\r\n    execCommand : function(){\r\n        var w = window.open('', '_blank', ''),\r\n            d = w.document;\r\n        d.open();\r\n        d.write('<!DOCTYPE html><html><head><meta charset=\"utf-8\"/><script src=\"'+this.options.UEDITOR_HOME_URL+'ueditor.parse.js\"></script><script>' +\r\n            \"setTimeout(function(){uParse('div',{rootPath: '\"+ this.options.UEDITOR_HOME_URL +\"'})},300)\" +\r\n            '</script></head><body><div>'+this.getContent(null,null,true)+'</div></body></html>');\r\n        d.close();\r\n    },\r\n    notNeedUndo : 1\r\n};\r\n\r\n\r\n// plugins/selectall.js\r\n/**\r\n * 全选\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 选中所有内容\r\n * @command selectall\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'selectall' );\r\n * ```\r\n */\r\nUE.plugins['selectall'] = function(){\r\n    var me = this;\r\n    me.commands['selectall'] = {\r\n        execCommand : function(){\r\n            //去掉了原生的selectAll,因为会出现报错和当内容为空时，不能出现闭合状态的光标\r\n            var me = this,body = me.body,\r\n                range = me.selection.getRange();\r\n            range.selectNodeContents(body);\r\n            if(domUtils.isEmptyBlock(body)){\r\n                //opera不能自动合并到元素的里边，要手动处理一下\r\n                if(browser.opera && body.firstChild && body.firstChild.nodeType == 1){\r\n                    range.setStartAtFirst(body.firstChild);\r\n                }\r\n                range.collapse(true);\r\n            }\r\n            range.select(true);\r\n        },\r\n        notNeedUndo : 1\r\n    };\r\n\r\n\r\n    //快捷键\r\n    me.addshortcutkey({\r\n         \"selectAll\" : \"ctrl+65\"\r\n    });\r\n};\r\n\r\n\r\n// plugins/paragraph.js\r\n/**\r\n * 段落样式\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 段落格式\r\n * @command paragraph\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @param {String}   style               标签值为：'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'\r\n * @param {Object}   attrs               标签的属性\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'Paragraph','h1','{\r\n *     class:'test'\r\n * }' );\r\n * ```\r\n */\r\n\r\n/**\r\n * 返回选区内节点标签名\r\n * @command paragraph\r\n * @method queryCommandValue\r\n * @param { String } cmd 命令字符串\r\n * @return { String } 节点标签名\r\n * @example\r\n * ```javascript\r\n * editor.queryCommandValue( 'Paragraph' );\r\n * ```\r\n */\r\n\r\nUE.plugins['paragraph'] = function() {\r\n    var me = this,\r\n        block = domUtils.isBlockElm,\r\n        notExchange = ['TD','LI','PRE'],\r\n\r\n        doParagraph = function(range,style,attrs,sourceCmdName){\r\n            var bookmark = range.createBookmark(),\r\n                filterFn = function( node ) {\r\n                    return   node.nodeType == 1 ? node.tagName.toLowerCase() != 'br' &&  !domUtils.isBookmarkNode(node) : !domUtils.isWhitespace( node );\r\n                },\r\n                para;\r\n\r\n            range.enlarge( true );\r\n            var bookmark2 = range.createBookmark(),\r\n                current = domUtils.getNextDomNode( bookmark2.start, false, filterFn ),\r\n                tmpRange = range.cloneRange(),\r\n                tmpNode;\r\n            while ( current && !(domUtils.getPosition( current, bookmark2.end ) & domUtils.POSITION_FOLLOWING) ) {\r\n                if ( current.nodeType == 3 || !block( current ) ) {\r\n                    tmpRange.setStartBefore( current );\r\n                    while ( current && current !== bookmark2.end && !block( current ) ) {\r\n                        tmpNode = current;\r\n                        current = domUtils.getNextDomNode( current, false, null, function( node ) {\r\n                            return !block( node );\r\n                        } );\r\n                    }\r\n                    tmpRange.setEndAfter( tmpNode );\r\n                    \r\n                    para = range.document.createElement( style );\r\n                    if(attrs){\r\n                        domUtils.setAttributes(para,attrs);\r\n                        if(sourceCmdName && sourceCmdName == 'customstyle' && attrs.style){\r\n                            para.style.cssText = attrs.style;\r\n                        }\r\n                    }\r\n                    para.appendChild( tmpRange.extractContents() );\r\n                    //需要内容占位\r\n                    if(domUtils.isEmptyNode(para)){\r\n                        domUtils.fillChar(range.document,para);\r\n                        \r\n                    }\r\n\r\n                    tmpRange.insertNode( para );\r\n\r\n                    var parent = para.parentNode;\r\n                    //如果para上一级是一个block元素且不是body,td就删除它\r\n                    if ( block( parent ) && !domUtils.isBody( para.parentNode ) && utils.indexOf(notExchange,parent.tagName)==-1) {\r\n                        //存储dir,style\r\n                        if(!(sourceCmdName && sourceCmdName == 'customstyle')){\r\n                            parent.getAttribute('dir') && para.setAttribute('dir',parent.getAttribute('dir'));\r\n                            //trace:1070\r\n                            parent.style.cssText && (para.style.cssText = parent.style.cssText + ';' + para.style.cssText);\r\n                            //trace:1030\r\n                            parent.style.textAlign && !para.style.textAlign && (para.style.textAlign = parent.style.textAlign);\r\n                            parent.style.textIndent && !para.style.textIndent && (para.style.textIndent = parent.style.textIndent);\r\n                            parent.style.padding && !para.style.padding && (para.style.padding = parent.style.padding);\r\n                        }\r\n\r\n                        //trace:1706 选择的就是h1-6要删除\r\n                        if(attrs && /h\\d/i.test(parent.tagName) && !/h\\d/i.test(para.tagName) ){\r\n                            domUtils.setAttributes(parent,attrs);\r\n                            if(sourceCmdName && sourceCmdName == 'customstyle' && attrs.style){\r\n                                parent.style.cssText = attrs.style;\r\n                            }\r\n                            domUtils.remove(para,true);\r\n                            para = parent;\r\n                        }else{\r\n                            domUtils.remove( para.parentNode, true );\r\n                        }\r\n\r\n                    }\r\n                    if(  utils.indexOf(notExchange,parent.tagName)!=-1){\r\n                        current = parent;\r\n                    }else{\r\n                       current = para;\r\n                    }\r\n\r\n\r\n                    current = domUtils.getNextDomNode( current, false, filterFn );\r\n                } else {\r\n                    current = domUtils.getNextDomNode( current, true, filterFn );\r\n                }\r\n            }\r\n            return range.moveToBookmark( bookmark2 ).moveToBookmark( bookmark );\r\n        };\r\n    me.setOpt('paragraph',{'p':'', 'h1':'', 'h2':'', 'h3':'', 'h4':'', 'h5':'', 'h6':''});\r\n    me.commands['paragraph'] = {\r\n        execCommand : function( cmdName, style,attrs,sourceCmdName ) {\r\n            var range = this.selection.getRange();\r\n             //闭合时单独处理\r\n            if(range.collapsed){\r\n                var txt = this.document.createTextNode('p');\r\n                range.insertNode(txt);\r\n                //去掉冗余的fillchar\r\n                if(browser.ie){\r\n                    var node = txt.previousSibling;\r\n                    if(node && domUtils.isWhitespace(node)){\r\n                        domUtils.remove(node);\r\n                    }\r\n                    node = txt.nextSibling;\r\n                    if(node && domUtils.isWhitespace(node)){\r\n                        domUtils.remove(node);\r\n                    }\r\n                }\r\n\r\n            }\r\n            range = doParagraph(range,style,attrs,sourceCmdName);\r\n            if(txt){\r\n                range.setStartBefore(txt).collapse(true);\r\n                pN = txt.parentNode;\r\n\r\n                domUtils.remove(txt);\r\n\r\n                if(domUtils.isBlockElm(pN)&&domUtils.isEmptyNode(pN)){\r\n                    domUtils.fillNode(this.document,pN);\r\n                }\r\n\r\n            }\r\n\r\n            if(browser.gecko && range.collapsed && range.startContainer.nodeType == 1){\r\n                var child = range.startContainer.childNodes[range.startOffset];\r\n                if(child && child.nodeType == 1 && child.tagName.toLowerCase() == style){\r\n                    range.setStart(child,0).collapse(true);\r\n                }\r\n            }\r\n            //trace:1097 原来有true，原因忘了，但去了就不能清除多余的占位符了\r\n            range.select();\r\n\r\n\r\n            return true;\r\n        },\r\n        queryCommandValue : function() {\r\n            var node = domUtils.filterNodeList(this.selection.getStartElementPath(),'p h1 h2 h3 h4 h5 h6');\r\n            return node ? node.tagName.toLowerCase() : '';\r\n        }\r\n    };\r\n};\r\n\r\n\r\n// plugins/directionality.js\r\n/**\r\n * 设置文字输入的方向的插件\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n(function() {\r\n    var block = domUtils.isBlockElm ,\r\n        getObj = function(editor){\r\n//            var startNode = editor.selection.getStart(),\r\n//                parents;\r\n//            if ( startNode ) {\r\n//                //查找所有的是block的父亲节点\r\n//                parents = domUtils.findParents( startNode, true, block, true );\r\n//                for ( var i = 0,ci; ci = parents[i++]; ) {\r\n//                    if ( ci.getAttribute( 'dir' ) ) {\r\n//                        return ci;\r\n//                    }\r\n//                }\r\n//            }\r\n            return domUtils.filterNodeList(editor.selection.getStartElementPath(),function(n){return n && n.nodeType == 1 && n.getAttribute('dir')});\r\n\r\n        },\r\n        doDirectionality = function(range,editor,forward){\r\n            \r\n            var bookmark,\r\n                filterFn = function( node ) {\r\n                    return   node.nodeType == 1 ? !domUtils.isBookmarkNode(node) : !domUtils.isWhitespace(node);\r\n                },\r\n\r\n                obj = getObj( editor );\r\n\r\n            if ( obj && range.collapsed ) {\r\n                obj.setAttribute( 'dir', forward );\r\n                return range;\r\n            }\r\n            bookmark = range.createBookmark();\r\n            range.enlarge( true );\r\n            var bookmark2 = range.createBookmark(),\r\n                current = domUtils.getNextDomNode( bookmark2.start, false, filterFn ),\r\n                tmpRange = range.cloneRange(),\r\n                tmpNode;\r\n            while ( current &&  !(domUtils.getPosition( current, bookmark2.end ) & domUtils.POSITION_FOLLOWING) ) {\r\n                if ( current.nodeType == 3 || !block( current ) ) {\r\n                    tmpRange.setStartBefore( current );\r\n                    while ( current && current !== bookmark2.end && !block( current ) ) {\r\n                        tmpNode = current;\r\n                        current = domUtils.getNextDomNode( current, false, null, function( node ) {\r\n                            return !block( node );\r\n                        } );\r\n                    }\r\n                    tmpRange.setEndAfter( tmpNode );\r\n                    var common = tmpRange.getCommonAncestor();\r\n                    if ( !domUtils.isBody( common ) && block( common ) ) {\r\n                        //遍历到了block节点\r\n                        common.setAttribute( 'dir', forward );\r\n                        current = common;\r\n                    } else {\r\n                        //没有遍历到，添加一个block节点\r\n                        var p = range.document.createElement( 'p' );\r\n                        p.setAttribute( 'dir', forward );\r\n                        var frag = tmpRange.extractContents();\r\n                        p.appendChild( frag );\r\n                        tmpRange.insertNode( p );\r\n                        current = p;\r\n                    }\r\n\r\n                    current = domUtils.getNextDomNode( current, false, filterFn );\r\n                } else {\r\n                    current = domUtils.getNextDomNode( current, true, filterFn );\r\n                }\r\n            }\r\n            return range.moveToBookmark( bookmark2 ).moveToBookmark( bookmark );\r\n        };\r\n\r\n    /**\r\n     * 文字输入方向\r\n     * @command directionality\r\n     * @method execCommand\r\n     * @param { String } cmdName 命令字符串\r\n     * @param { String } forward 传入'ltr'表示从左向右输入，传入'rtl'表示从右向左输入\r\n     * @example\r\n     * ```javascript\r\n     * editor.execCommand( 'directionality', 'ltr');\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 查询当前选区的文字输入方向\r\n     * @command directionality\r\n     * @method queryCommandValue\r\n     * @param { String } cmdName 命令字符串\r\n     * @return { String } 返回'ltr'表示从左向右输入，返回'rtl'表示从右向左输入\r\n     * @example\r\n     * ```javascript\r\n     * editor.queryCommandValue( 'directionality');\r\n     * ```\r\n     */\r\n    UE.commands['directionality'] = {\r\n        execCommand : function( cmdName,forward ) {\r\n            var range = this.selection.getRange();\r\n            //闭合时单独处理\r\n            if(range.collapsed){\r\n                var txt = this.document.createTextNode('d');\r\n                range.insertNode(txt);\r\n            }\r\n            doDirectionality(range,this,forward);\r\n            if(txt){\r\n                range.setStartBefore(txt).collapse(true);\r\n                domUtils.remove(txt);\r\n            }\r\n\r\n            range.select();\r\n            return true;\r\n        },\r\n        queryCommandValue : function() {\r\n            var node = getObj(this);\r\n            return node ? node.getAttribute('dir') : 'ltr';\r\n        }\r\n    };\r\n})();\r\n\r\n\r\n\r\n// plugins/horizontal.js\r\n/**\r\n * 插入分割线插件\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 插入分割线\r\n * @command horizontal\r\n * @method execCommand\r\n * @param { String } cmdName 命令字符串\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'horizontal' );\r\n * ```\r\n */\r\nUE.plugins['horizontal'] = function(){\r\n    var me = this;\r\n    me.commands['horizontal'] = {\r\n        execCommand : function( cmdName ) {\r\n            var me = this;\r\n            if(me.queryCommandState(cmdName)!==-1){\r\n                me.execCommand('insertHtml','<hr>');\r\n                var range = me.selection.getRange(),\r\n                    start = range.startContainer;\r\n                if(start.nodeType == 1 && !start.childNodes[range.startOffset] ){\r\n\r\n                    var tmp;\r\n                    if(tmp = start.childNodes[range.startOffset - 1]){\r\n                        if(tmp.nodeType == 1 && tmp.tagName == 'HR'){\r\n                            if(me.options.enterTag == 'p'){\r\n                                tmp = me.document.createElement('p');\r\n                                range.insertNode(tmp);\r\n                                range.setStart(tmp,0).setCursor();\r\n\r\n                            }else{\r\n                                tmp = me.document.createElement('br');\r\n                                range.insertNode(tmp);\r\n                                range.setStartBefore(tmp).setCursor();\r\n                            }\r\n                        }\r\n                    }\r\n\r\n                }\r\n                return true;\r\n            }\r\n\r\n        },\r\n        //边界在table里不能加分隔线\r\n        queryCommandState : function() {\r\n            return domUtils.filterNodeList(this.selection.getStartElementPath(),'table') ? -1 : 0;\r\n        }\r\n    };\r\n//    me.addListener('delkeyup',function(){\r\n//        var rng = this.selection.getRange();\r\n//        if(browser.ie && browser.version > 8){\r\n//            rng.txtToElmBoundary(true);\r\n//            if(domUtils.isStartInblock(rng)){\r\n//                var tmpNode = rng.startContainer;\r\n//                var pre = tmpNode.previousSibling;\r\n//                if(pre && domUtils.isTagNode(pre,'hr')){\r\n//                    domUtils.remove(pre);\r\n//                    rng.select();\r\n//                    return;\r\n//                }\r\n//            }\r\n//        }\r\n//        if(domUtils.isBody(rng.startContainer)){\r\n//            var hr = rng.startContainer.childNodes[rng.startOffset -1];\r\n//            if(hr && hr.nodeName == 'HR'){\r\n//                var next = hr.nextSibling;\r\n//                if(next){\r\n//                    rng.setStart(next,0)\r\n//                }else if(hr.previousSibling){\r\n//                    rng.setStartAtLast(hr.previousSibling)\r\n//                }else{\r\n//                    var p = this.document.createElement('p');\r\n//                    hr.parentNode.insertBefore(p,hr);\r\n//                    domUtils.fillNode(this.document,p);\r\n//                    rng.setStart(p,0);\r\n//                }\r\n//                domUtils.remove(hr);\r\n//                rng.setCursor(false,true);\r\n//            }\r\n//        }\r\n//    })\r\n    me.addListener('delkeydown',function(name,evt){\r\n        var rng = this.selection.getRange();\r\n        rng.txtToElmBoundary(true);\r\n        if(domUtils.isStartInblock(rng)){\r\n            var tmpNode = rng.startContainer;\r\n            var pre = tmpNode.previousSibling;\r\n            if(pre && domUtils.isTagNode(pre,'hr')){\r\n                domUtils.remove(pre);\r\n                rng.select();\r\n                domUtils.preventDefault(evt);\r\n                return true;\r\n\r\n            }\r\n        }\r\n\r\n    })\r\n};\r\n\r\n\r\n\r\n// plugins/time.js\r\n/**\r\n * 插入时间和日期\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 插入时间，默认格式：12:59:59\r\n * @command time\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'time');\r\n * ```\r\n */\r\n\r\n/**\r\n * 插入日期，默认格式：2013-08-30\r\n * @command date\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'date');\r\n * ```\r\n */\r\nUE.commands['time'] = UE.commands[\"date\"] = {\r\n    execCommand : function(cmd, format){\r\n        var date = new Date;\r\n\r\n        function formatTime(date, format) {\r\n            var hh = ('0' + date.getHours()).slice(-2),\r\n                ii = ('0' + date.getMinutes()).slice(-2),\r\n                ss = ('0' + date.getSeconds()).slice(-2);\r\n            format = format || 'hh:ii:ss';\r\n            return format.replace(/hh/ig, hh).replace(/ii/ig, ii).replace(/ss/ig, ss);\r\n        }\r\n        function formatDate(date, format) {\r\n            var yyyy = ('000' + date.getFullYear()).slice(-4),\r\n                yy = yyyy.slice(-2),\r\n                mm = ('0' + (date.getMonth()+1)).slice(-2),\r\n                dd = ('0' + date.getDate()).slice(-2);\r\n            format = format || 'yyyy-mm-dd';\r\n            return format.replace(/yyyy/ig, yyyy).replace(/yy/ig, yy).replace(/mm/ig, mm).replace(/dd/ig, dd);\r\n        }\r\n\r\n        this.execCommand('insertHtml',cmd == \"time\" ? formatTime(date, format):formatDate(date, format) );\r\n    }\r\n};\r\n\r\n\r\n// plugins/rowspacing.js\r\n/**\r\n * 段前段后间距插件\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 设置段间距\r\n * @command rowspacing\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @param { String } value 段间距的值，以px为单位\r\n * @param { String } dir 间距位置，top或bottom，分别表示段前和段后\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'rowspacing', '10', 'top' );\r\n * ```\r\n */\r\n\r\nUE.plugins['rowspacing'] = function(){\r\n    var me = this;\r\n    me.setOpt({\r\n        'rowspacingtop':['5', '10', '15', '20', '25'],\r\n        'rowspacingbottom':['5', '10', '15', '20', '25']\r\n\r\n    });\r\n    me.commands['rowspacing'] =  {\r\n        execCommand : function( cmdName,value,dir ) {\r\n            this.execCommand('paragraph','p',{style:'margin-'+dir+':'+value + 'px'});\r\n            return true;\r\n        },\r\n        queryCommandValue : function(cmdName,dir) {\r\n            var pN = domUtils.filterNodeList(this.selection.getStartElementPath(),function(node){return domUtils.isBlockElm(node) }),\r\n                value;\r\n            //trace:1026\r\n            if(pN){\r\n                value = domUtils.getComputedStyle(pN,'margin-'+dir).replace(/[^\\d]/g,'');\r\n                return !value ? 0 : value;\r\n            }\r\n            return 0;\r\n\r\n        }\r\n    };\r\n};\r\n\r\n\r\n\r\n\r\n// plugins/lineheight.js\r\n/**\r\n * 设置行内间距\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\nUE.plugins['lineheight'] = function(){\r\n    var me = this;\r\n    me.setOpt({'lineheight':['1', '1.5','1.75','2', '3', '4', '5']});\r\n\r\n    /**\r\n     * 行距\r\n     * @command lineheight\r\n     * @method execCommand\r\n     * @param { String } cmdName 命令字符串\r\n     * @param { String } value 传入的行高值， 该值是当前字体的倍数， 例如： 1.5, 1.75\r\n     * @example\r\n     * ```javascript\r\n     * editor.execCommand( 'lineheight', 1.5);\r\n     * ```\r\n     */\r\n    /**\r\n     * 查询当前选区内容的行高大小\r\n     * @command lineheight\r\n     * @method queryCommandValue\r\n     * @param { String } cmd 命令字符串\r\n     * @return { String } 返回当前行高大小\r\n     * @example\r\n     * ```javascript\r\n     * editor.queryCommandValue( 'lineheight' );\r\n     * ```\r\n     */\r\n\r\n    me.commands['lineheight'] =  {\r\n        execCommand : function( cmdName,value ) {\r\n            this.execCommand('paragraph','p',{style:'line-height:'+ (value == \"1\" ? \"normal\" : value + 'em') });\r\n            return true;\r\n        },\r\n        queryCommandValue : function() {\r\n            var pN = domUtils.filterNodeList(this.selection.getStartElementPath(),function(node){return domUtils.isBlockElm(node)});\r\n            if(pN){\r\n                var value = domUtils.getComputedStyle(pN,'line-height');\r\n                return value == 'normal' ? 1 : value.replace(/[^\\d.]*/ig,\"\");\r\n            }\r\n        }\r\n    };\r\n};\r\n\r\n\r\n\r\n\r\n// plugins/insertcode.js\r\n/**\r\n * 插入代码插件\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\nUE.plugins['insertcode'] = function() {\r\n    var me = this;\r\n    me.ready(function(){\r\n        utils.cssRule('pre','pre{margin:.5em 0;padding:.4em .6em;border-radius:8px;background:#f8f8f8;}',\r\n            me.document)\r\n    });\r\n    me.setOpt('insertcode',{\r\n            'as3':'ActionScript3',\r\n            'bash':'Bash/Shell',\r\n            'cpp':'C/C++',\r\n            'css':'Css',\r\n            'cf':'CodeFunction',\r\n            'c#':'C#',\r\n            'delphi':'Delphi',\r\n            'diff':'Diff',\r\n            'erlang':'Erlang',\r\n            'groovy':'Groovy',\r\n            'html':'Html',\r\n            'java':'Java',\r\n            'jfx':'JavaFx',\r\n            'js':'Javascript',\r\n            'pl':'Perl',\r\n            'php':'Php',\r\n            'plain':'Plain Text',\r\n            'ps':'PowerShell',\r\n            'python':'Python',\r\n            'ruby':'Ruby',\r\n            'scala':'Scala',\r\n            'sql':'Sql',\r\n            'vb':'Vb',\r\n            'xml':'Xml'\r\n    });\r\n\r\n    /**\r\n     * 插入代码\r\n     * @command insertcode\r\n     * @method execCommand\r\n     * @param { String } cmd 命令字符串\r\n     * @param { String } lang 插入代码的语言\r\n     * @example\r\n     * ```javascript\r\n     * editor.execCommand( 'insertcode', 'javascript' );\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 如果选区所在位置是插入插入代码区域，返回代码的语言\r\n     * @command insertcode\r\n     * @method queryCommandValue\r\n     * @param { String } cmd 命令字符串\r\n     * @return { String } 返回代码的语言\r\n     * @example\r\n     * ```javascript\r\n     * editor.queryCommandValue( 'insertcode' );\r\n     * ```\r\n     */\r\n\r\n    me.commands['insertcode'] = {\r\n        execCommand : function(cmd,lang){\r\n            var me = this,\r\n                rng = me.selection.getRange(),\r\n                pre = domUtils.findParentByTagName(rng.startContainer,'pre',true);\r\n            if(pre){\r\n                pre.className = 'brush:'+lang+';toolbar:false;';\r\n            }else{\r\n                var code = '';\r\n                if(rng.collapsed){\r\n                    code = browser.ie && browser.ie11below ? (browser.version <= 8 ? '&nbsp;':''):'<br/>';\r\n                }else{\r\n                    var frag = rng.extractContents();\r\n                    var div = me.document.createElement('div');\r\n                    div.appendChild(frag);\r\n\r\n                    utils.each(UE.filterNode(UE.htmlparser(div.innerHTML.replace(/[\\r\\t]/g,'')),me.options.filterTxtRules).children,function(node){\r\n                        if(browser.ie && browser.ie11below && browser.version > 8){\r\n\r\n                            if(node.type =='element'){\r\n                                if(node.tagName == 'br'){\r\n                                    code += '\\n'\r\n                                }else if(!dtd.$empty[node.tagName]){\r\n                                    utils.each(node.children,function(cn){\r\n                                        if(cn.type =='element'){\r\n                                            if(cn.tagName == 'br'){\r\n                                                code += '\\n'\r\n                                            }else if(!dtd.$empty[node.tagName]){\r\n                                                code += cn.innerText();\r\n                                            }\r\n                                        }else{\r\n                                            code += cn.data\r\n                                        }\r\n                                    })\r\n                                    if(!/\\n$/.test(code)){\r\n                                        code += '\\n';\r\n                                    }\r\n                                }\r\n                            }else{\r\n                                code += node.data + '\\n'\r\n                            }\r\n                            if(!node.nextSibling() && /\\n$/.test(code)){\r\n                                code = code.replace(/\\n$/,'');\r\n                            }\r\n                        }else{\r\n                            if(browser.ie && browser.ie11below){\r\n\r\n                                if(node.type =='element'){\r\n                                    if(node.tagName == 'br'){\r\n                                        code += '<br>'\r\n                                    }else if(!dtd.$empty[node.tagName]){\r\n                                        utils.each(node.children,function(cn){\r\n                                            if(cn.type =='element'){\r\n                                                if(cn.tagName == 'br'){\r\n                                                    code += '<br>'\r\n                                                }else if(!dtd.$empty[node.tagName]){\r\n                                                    code += cn.innerText();\r\n                                                }\r\n                                            }else{\r\n                                                code += cn.data\r\n                                            }\r\n                                        });\r\n                                        if(!/br>$/.test(code)){\r\n                                            code += '<br>';\r\n                                        }\r\n                                    }\r\n                                }else{\r\n                                    code += node.data + '<br>'\r\n                                }\r\n                                if(!node.nextSibling() && /<br>$/.test(code)){\r\n                                    code = code.replace(/<br>$/,'');\r\n                                }\r\n\r\n                            }else{\r\n                                code += (node.type == 'element' ? (dtd.$empty[node.tagName] ?  '' : node.innerText()) : node.data);\r\n                                if(!/br\\/?\\s*>$/.test(code)){\r\n                                    if(!node.nextSibling())\r\n                                        return;\r\n                                    code += '<br>'\r\n                                }\r\n                            }\r\n\r\n                        }\r\n\r\n                    });\r\n                }\r\n                me.execCommand('inserthtml','<pre id=\"coder\"class=\"brush:'+lang+';toolbar:false\">'+code+'</pre>',true);\r\n\r\n                pre = me.document.getElementById('coder');\r\n                domUtils.removeAttributes(pre,'id');\r\n                var tmpNode = pre.previousSibling;\r\n\r\n                if(tmpNode && (tmpNode.nodeType == 3 && tmpNode.nodeValue.length == 1 && browser.ie && browser.version == 6 ||  domUtils.isEmptyBlock(tmpNode))){\r\n\r\n                    domUtils.remove(tmpNode)\r\n                }\r\n                var rng = me.selection.getRange();\r\n                if(domUtils.isEmptyBlock(pre)){\r\n                    rng.setStart(pre,0).setCursor(false,true)\r\n                }else{\r\n                    rng.selectNodeContents(pre).select()\r\n                }\r\n            }\r\n\r\n\r\n\r\n        },\r\n        queryCommandValue : function(){\r\n            var path = this.selection.getStartElementPath();\r\n            var lang = '';\r\n            utils.each(path,function(node){\r\n                if(node.nodeName =='PRE'){\r\n                    var match = node.className.match(/brush:([^;]+)/);\r\n                    lang = match && match[1] ? match[1] : '';\r\n                    return false;\r\n                }\r\n            });\r\n            return lang;\r\n        }\r\n    };\r\n\r\n    me.addInputRule(function(root){\r\n       utils.each(root.getNodesByTagName('pre'),function(pre){\r\n           var brs = pre.getNodesByTagName('br');\r\n           if(brs.length){\r\n               browser.ie && browser.ie11below && browser.version > 8 && utils.each(brs,function(br){\r\n                   var txt = UE.uNode.createText('\\n');\r\n                   br.parentNode.insertBefore(txt,br);\r\n                   br.parentNode.removeChild(br);\r\n               });\r\n               return;\r\n            }\r\n           if(browser.ie && browser.ie11below && browser.version > 8)\r\n                return;\r\n            var code = pre.innerText().split(/\\n/);\r\n            pre.innerHTML('');\r\n            utils.each(code,function(c){\r\n                if(c.length){\r\n                    pre.appendChild(UE.uNode.createText(c));\r\n                }\r\n                pre.appendChild(UE.uNode.createElement('br'))\r\n            })\r\n       })\r\n    });\r\n    me.addOutputRule(function(root){\r\n        utils.each(root.getNodesByTagName('pre'),function(pre){\r\n            var code = '';\r\n            utils.each(pre.children,function(n){\r\n               if(n.type == 'text'){\r\n                   //在ie下文本内容有可能末尾带有\\n要去掉\r\n                   //trace:3396\r\n                   code += n.data.replace(/[ ]/g,'&nbsp;').replace(/\\n$/,'');\r\n               }else{\r\n                   if(n.tagName == 'br'){\r\n                       code  += '\\n'\r\n                   }else{\r\n                       code += (!dtd.$empty[n.tagName] ? '' : n.innerText());\r\n                   }\r\n\r\n               }\r\n\r\n            });\r\n\r\n            pre.innerText(code.replace(/(&nbsp;|\\n)+$/,''))\r\n        })\r\n    });\r\n    //不需要判断highlight的command列表\r\n    me.notNeedCodeQuery ={\r\n        help:1,\r\n        undo:1,\r\n        redo:1,\r\n        source:1,\r\n        print:1,\r\n        searchreplace:1,\r\n        fullscreen:1,\r\n        preview:1,\r\n        insertparagraph:1,\r\n        elementpath:1,\r\n        insertcode:1,\r\n        inserthtml:1,\r\n        selectall:1\r\n    };\r\n    //将queyCommamndState重置\r\n    var orgQuery = me.queryCommandState;\r\n    me.queryCommandState = function(cmd){\r\n        var me = this;\r\n\r\n        if(!me.notNeedCodeQuery[cmd.toLowerCase()] && me.selection && me.queryCommandValue('insertcode')){\r\n            return -1;\r\n        }\r\n        return UE.Editor.prototype.queryCommandState.apply(this,arguments)\r\n    };\r\n    me.addListener('beforeenterkeydown',function(){\r\n        var rng = me.selection.getRange();\r\n        var pre = domUtils.findParentByTagName(rng.startContainer,'pre',true);\r\n        if(pre){\r\n            me.fireEvent('saveScene');\r\n            if(!rng.collapsed){\r\n               rng.deleteContents();\r\n            }\r\n            if(!browser.ie || browser.ie9above){\r\n                var tmpNode = me.document.createElement('br'),pre;\r\n                rng.insertNode(tmpNode).setStartAfter(tmpNode).collapse(true);\r\n                var next = tmpNode.nextSibling;\r\n                if(!next && (!browser.ie || browser.version > 10)){\r\n                    rng.insertNode(tmpNode.cloneNode(false));\r\n                }else{\r\n                    rng.setStartAfter(tmpNode);\r\n                }\r\n                pre = tmpNode.previousSibling;\r\n                var tmp;\r\n                while(pre ){\r\n                    tmp = pre;\r\n                    pre = pre.previousSibling;\r\n                    if(!pre || pre.nodeName == 'BR'){\r\n                        pre = tmp;\r\n                        break;\r\n                    }\r\n                }\r\n                if(pre){\r\n                    var str = '';\r\n                    while(pre && pre.nodeName != 'BR' &&  new RegExp('^[\\\\s'+domUtils.fillChar+']*$').test(pre.nodeValue)){\r\n                        str += pre.nodeValue;\r\n                        pre = pre.nextSibling;\r\n                    }\r\n                    if(pre.nodeName != 'BR'){\r\n                        var match = pre.nodeValue.match(new RegExp('^([\\\\s'+domUtils.fillChar+']+)'));\r\n                        if(match && match[1]){\r\n                            str += match[1]\r\n                        }\r\n\r\n                    }\r\n                    if(str){\r\n                        str = me.document.createTextNode(str);\r\n                        rng.insertNode(str).setStartAfter(str);\r\n                    }\r\n                }\r\n                rng.collapse(true).select(true);\r\n            }else{\r\n                if(browser.version > 8){\r\n\r\n                    var txt = me.document.createTextNode('\\n');\r\n                    var start = rng.startContainer;\r\n                    if(rng.startOffset == 0){\r\n                        var preNode = start.previousSibling;\r\n                        if(preNode){\r\n                            rng.insertNode(txt);\r\n                            var fillchar = me.document.createTextNode(' ');\r\n                            rng.setStartAfter(txt).insertNode(fillchar).setStart(fillchar,0).collapse(true).select(true)\r\n                        }\r\n                    }else{\r\n                        rng.insertNode(txt).setStartAfter(txt);\r\n                        var fillchar = me.document.createTextNode(' ');\r\n                        start = rng.startContainer.childNodes[rng.startOffset];\r\n                        if(start && !/^\\n/.test(start.nodeValue)){\r\n                            rng.setStartBefore(txt)\r\n                        }\r\n                        rng.insertNode(fillchar).setStart(fillchar,0).collapse(true).select(true)\r\n                    }\r\n\r\n                }else{\r\n                    var tmpNode = me.document.createElement('br');\r\n                    rng.insertNode(tmpNode);\r\n                    rng.insertNode(me.document.createTextNode(domUtils.fillChar));\r\n                    rng.setStartAfter(tmpNode);\r\n                    pre = tmpNode.previousSibling;\r\n                    var tmp;\r\n                    while(pre ){\r\n                        tmp = pre;\r\n                        pre = pre.previousSibling;\r\n                        if(!pre || pre.nodeName == 'BR'){\r\n                            pre = tmp;\r\n                            break;\r\n                        }\r\n                    }\r\n                    if(pre){\r\n                        var str = '';\r\n                        while(pre && pre.nodeName != 'BR' &&  new RegExp('^[ '+domUtils.fillChar+']*$').test(pre.nodeValue)){\r\n                            str += pre.nodeValue;\r\n                            pre = pre.nextSibling;\r\n                        }\r\n                        if(pre.nodeName != 'BR'){\r\n                            var match = pre.nodeValue.match(new RegExp('^([ '+domUtils.fillChar+']+)'));\r\n                            if(match && match[1]){\r\n                                str += match[1]\r\n                            }\r\n\r\n                        }\r\n\r\n                        str = me.document.createTextNode(str);\r\n                        rng.insertNode(str).setStartAfter(str);\r\n                    }\r\n                    rng.collapse(true).select();\r\n                }\r\n\r\n\r\n            }\r\n            me.fireEvent('saveScene');\r\n            return true;\r\n        }\r\n\r\n\r\n    });\r\n\r\n    me.addListener('tabkeydown',function(cmd,evt){\r\n        var rng = me.selection.getRange();\r\n        var pre = domUtils.findParentByTagName(rng.startContainer,'pre',true);\r\n        if(pre){\r\n            me.fireEvent('saveScene');\r\n            if(evt.shiftKey){\r\n\r\n            }else{\r\n                if(!rng.collapsed){\r\n                    var bk = rng.createBookmark();\r\n                    var start = bk.start.previousSibling;\r\n\r\n                    while(start){\r\n                        if(pre.firstChild === start && !domUtils.isBr(start)){\r\n                            pre.insertBefore(me.document.createTextNode('    '),start);\r\n\r\n                            break;\r\n                        }\r\n                        if(domUtils.isBr(start)){\r\n                            pre.insertBefore(me.document.createTextNode('    '),start.nextSibling);\r\n\r\n                            break;\r\n                        }\r\n                        start = start.previousSibling;\r\n                    }\r\n                    var end = bk.end;\r\n                    start = bk.start.nextSibling;\r\n                    if(pre.firstChild === bk.start){\r\n                        pre.insertBefore(me.document.createTextNode('    '),start.nextSibling)\r\n\r\n                    }\r\n                    while(start && start !== end){\r\n                        if(domUtils.isBr(start) && start.nextSibling){\r\n                            if(start.nextSibling === end){\r\n                                break;\r\n                            }\r\n                            pre.insertBefore(me.document.createTextNode('    '),start.nextSibling)\r\n                        }\r\n\r\n                        start = start.nextSibling;\r\n                    }\r\n                    rng.moveToBookmark(bk).select();\r\n                }else{\r\n                    var tmpNode = me.document.createTextNode('    ');\r\n                    rng.insertNode(tmpNode).setStartAfter(tmpNode).collapse(true).select(true);\r\n                }\r\n            }\r\n\r\n\r\n            me.fireEvent('saveScene');\r\n            return true;\r\n        }\r\n\r\n\r\n    });\r\n\r\n\r\n    me.addListener('beforeinserthtml',function(evtName,html){\r\n        var me = this,\r\n            rng = me.selection.getRange(),\r\n            pre = domUtils.findParentByTagName(rng.startContainer,'pre',true);\r\n        if(pre){\r\n            if(!rng.collapsed){\r\n                rng.deleteContents()\r\n            }\r\n            var htmlstr = '';\r\n            if(browser.ie && browser.version > 8){\r\n\r\n                utils.each(UE.filterNode(UE.htmlparser(html),me.options.filterTxtRules).children,function(node){\r\n                    if(node.type =='element'){\r\n                        if(node.tagName == 'br'){\r\n                            htmlstr += '\\n'\r\n                        }else if(!dtd.$empty[node.tagName]){\r\n                            utils.each(node.children,function(cn){\r\n                                if(cn.type =='element'){\r\n                                    if(cn.tagName == 'br'){\r\n                                        htmlstr += '\\n'\r\n                                    }else if(!dtd.$empty[node.tagName]){\r\n                                        htmlstr += cn.innerText();\r\n                                    }\r\n                                }else{\r\n                                    htmlstr += cn.data\r\n                                }\r\n                            })\r\n                            if(!/\\n$/.test(htmlstr)){\r\n                                htmlstr += '\\n';\r\n                            }\r\n                        }\r\n                    }else{\r\n                        htmlstr += node.data + '\\n'\r\n                    }\r\n                    if(!node.nextSibling() && /\\n$/.test(htmlstr)){\r\n                        htmlstr = htmlstr.replace(/\\n$/,'');\r\n                    }\r\n                });\r\n                var tmpNode = me.document.createTextNode(utils.html(htmlstr.replace(/&nbsp;/g,' ')));\r\n                rng.insertNode(tmpNode).selectNode(tmpNode).select();\r\n            }else{\r\n                var frag = me.document.createDocumentFragment();\r\n\r\n                utils.each(UE.filterNode(UE.htmlparser(html),me.options.filterTxtRules).children,function(node){\r\n                    if(node.type =='element'){\r\n                        if(node.tagName == 'br'){\r\n                            frag.appendChild(me.document.createElement('br'))\r\n                        }else if(!dtd.$empty[node.tagName]){\r\n                            utils.each(node.children,function(cn){\r\n                                if(cn.type =='element'){\r\n                                    if(cn.tagName == 'br'){\r\n\r\n                                        frag.appendChild(me.document.createElement('br'))\r\n                                    }else if(!dtd.$empty[node.tagName]){\r\n                                        frag.appendChild(me.document.createTextNode(utils.html(cn.innerText().replace(/&nbsp;/g,' '))));\r\n\r\n                                    }\r\n                                }else{\r\n                                    frag.appendChild(me.document.createTextNode(utils.html( cn.data.replace(/&nbsp;/g,' '))));\r\n\r\n                                }\r\n                            })\r\n                            if(frag.lastChild.nodeName != 'BR'){\r\n                                frag.appendChild(me.document.createElement('br'))\r\n                            }\r\n                        }\r\n                    }else{\r\n                        frag.appendChild(me.document.createTextNode(utils.html( node.data.replace(/&nbsp;/g,' '))));\r\n                    }\r\n                    if(!node.nextSibling() && frag.lastChild.nodeName == 'BR'){\r\n                       frag.removeChild(frag.lastChild)\r\n                    }\r\n\r\n\r\n                });\r\n                rng.insertNode(frag).select();\r\n\r\n            }\r\n\r\n            return true;\r\n        }\r\n    });\r\n    //方向键的处理\r\n    me.addListener('keydown',function(cmd,evt){\r\n        var me = this,keyCode = evt.keyCode || evt.which;\r\n        if(keyCode == 40){\r\n            var rng = me.selection.getRange(),pre,start = rng.startContainer;\r\n            if(rng.collapsed && (pre = domUtils.findParentByTagName(rng.startContainer,'pre',true)) && !pre.nextSibling){\r\n                var last = pre.lastChild\r\n                while(last && last.nodeName == 'BR'){\r\n                    last = last.previousSibling;\r\n                }\r\n                if(last === start || rng.startContainer === pre && rng.startOffset == pre.childNodes.length){\r\n                    me.execCommand('insertparagraph');\r\n                    domUtils.preventDefault(evt)\r\n                }\r\n\r\n            }\r\n        }\r\n    });\r\n    //trace:3395\r\n    me.addListener('delkeydown',function(type,evt){\r\n        var rng = this.selection.getRange();\r\n        rng.txtToElmBoundary(true);\r\n        var start = rng.startContainer;\r\n        if(domUtils.isTagNode(start,'pre') && rng.collapsed && domUtils.isStartInblock(rng)){\r\n            var p = me.document.createElement('p');\r\n            domUtils.fillNode(me.document,p);\r\n            start.parentNode.insertBefore(p,start);\r\n            domUtils.remove(start);\r\n            rng.setStart(p,0).setCursor(false,true);\r\n            domUtils.preventDefault(evt);\r\n            return true;\r\n        }\r\n    })\r\n};\r\n\r\n\r\n// plugins/cleardoc.js\r\n/**\r\n * 清空文档插件\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 清空文档\r\n * @command cleardoc\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * //editor 是编辑器实例\r\n * editor.execCommand('cleardoc');\r\n * ```\r\n */\r\n\r\nUE.commands['cleardoc'] = {\r\n    execCommand : function( cmdName) {\r\n        var me = this,\r\n            enterTag = me.options.enterTag,\r\n            range = me.selection.getRange();\r\n        if(enterTag == \"br\"){\r\n            me.body.innerHTML = \"<br/>\";\r\n            range.setStart(me.body,0).setCursor();\r\n        }else{\r\n            me.body.innerHTML = \"<p>\"+(ie ? \"\" : \"<br/>\")+\"</p>\";\r\n            range.setStart(me.body.firstChild,0).setCursor(false,true);\r\n        }\r\n        setTimeout(function(){\r\n            me.fireEvent(\"clearDoc\");\r\n        },0);\r\n\r\n    }\r\n};\r\n\r\n\r\n\r\n// plugins/anchor.js\r\n/**\r\n * 锚点插件，为UEditor提供插入锚点支持\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\nUE.plugin.register('anchor', function (){\r\n\r\n    return {\r\n        bindEvents:{\r\n            'ready':function(){\r\n                utils.cssRule('anchor',\r\n                    '.anchorclass{background: url(\\''\r\n                        + this.options.themePath\r\n                        + this.options.theme +'/images/anchor.gif\\') no-repeat scroll left center transparent;cursor: auto;display: inline-block;height: 16px;width: 15px;}',\r\n                    this.document);\r\n            }\r\n        },\r\n       outputRule: function(root){\r\n           utils.each(root.getNodesByTagName('img'),function(a){\r\n               var val;\r\n               if(val = a.getAttr('anchorname')){\r\n                   a.tagName = 'a';\r\n                   a.setAttr({\r\n                       anchorname : '',\r\n                       name : val,\r\n                       'class' : ''\r\n                   })\r\n               }\r\n           })\r\n       },\r\n       inputRule:function(root){\r\n           utils.each(root.getNodesByTagName('a'),function(a){\r\n               var val;\r\n               if((val = a.getAttr('name')) && !a.getAttr('href')){\r\n                   a.tagName = 'img';\r\n                   a.setAttr({\r\n                       anchorname :a.getAttr('name'),\r\n                       'class' : 'anchorclass'\r\n                   });\r\n                   a.setAttr('name')\r\n\r\n               }\r\n           })\r\n\r\n       },\r\n       commands:{\r\n           /**\r\n            * 插入锚点\r\n            * @command anchor\r\n            * @method execCommand\r\n            * @param { String } cmd 命令字符串\r\n            * @param { String } name 锚点名称字符串\r\n            * @example\r\n            * ```javascript\r\n            * //editor 是编辑器实例\r\n            * editor.execCommand('anchor', 'anchor1');\r\n            * ```\r\n            */\r\n           'anchor':{\r\n               execCommand:function (cmd, name) {\r\n                   var range = this.selection.getRange(),img = range.getClosedNode();\r\n                   if (img && img.getAttribute('anchorname')) {\r\n                       if (name) {\r\n                           img.setAttribute('anchorname', name);\r\n                       } else {\r\n                           range.setStartBefore(img).setCursor();\r\n                           domUtils.remove(img);\r\n                       }\r\n                   } else {\r\n                       if (name) {\r\n                           //只在选区的开始插入\r\n                           var anchor = this.document.createElement('img');\r\n                           range.collapse(true);\r\n                           domUtils.setAttributes(anchor,{\r\n                               'anchorname':name,\r\n                               'class':'anchorclass'\r\n                           });\r\n                           range.insertNode(anchor).setStartAfter(anchor).setCursor(false,true);\r\n                       }\r\n                   }\r\n               }\r\n           }\r\n       }\r\n    }\r\n});\r\n\r\n\r\n// plugins/wordcount.js\r\n///import core\r\n///commands 字数统计\r\n///commandsName  WordCount,wordCount\r\n///commandsTitle  字数统计\r\n/*\r\n * Created by JetBrains WebStorm.\r\n * User: taoqili\r\n * Date: 11-9-7\r\n * Time: 下午8:18\r\n * To change this template use File | Settings | File Templates.\r\n */\r\n\r\nUE.plugins['wordcount'] = function(){\r\n    var me = this;\r\n    me.setOpt('wordCount',true);\r\n    me.addListener('contentchange',function(){\r\n        me.fireEvent('wordcount');\r\n    });\r\n    var timer;\r\n    me.addListener('ready',function(){\r\n        var me = this;\r\n        domUtils.on(me.body,\"keyup\",function(evt){\r\n            var code = evt.keyCode||evt.which,\r\n                //忽略的按键,ctr,alt,shift,方向键\r\n                ignores = {\"16\":1,\"18\":1,\"20\":1,\"37\":1,\"38\":1,\"39\":1,\"40\":1};\r\n            if(code in ignores) return;\r\n            clearTimeout(timer);\r\n            timer = setTimeout(function(){\r\n                me.fireEvent('wordcount');\r\n            },200)\r\n        })\r\n    });\r\n};\r\n\r\n\r\n// plugins/pagebreak.js\r\n/**\r\n * 分页功能插件\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\nUE.plugins['pagebreak'] = function () {\r\n    var me = this,\r\n        notBreakTags = ['td'];\r\n    me.setOpt('pageBreakTag','_ueditor_page_break_tag_');\r\n\r\n    function fillNode(node){\r\n        if(domUtils.isEmptyBlock(node)){\r\n            var firstChild = node.firstChild,tmpNode;\r\n\r\n            while(firstChild && firstChild.nodeType == 1 && domUtils.isEmptyBlock(firstChild)){\r\n                tmpNode = firstChild;\r\n                firstChild = firstChild.firstChild;\r\n            }\r\n            !tmpNode && (tmpNode = node);\r\n            domUtils.fillNode(me.document,tmpNode);\r\n        }\r\n    }\r\n    //分页符样式添加\r\n\r\n    me.ready(function(){\r\n        utils.cssRule('pagebreak','.pagebreak{display:block;clear:both !important;cursor:default !important;width: 100% !important;margin:0;}',me.document);\r\n    });\r\n    function isHr(node){\r\n        return node && node.nodeType == 1 && node.tagName == 'HR' && node.className == 'pagebreak';\r\n    }\r\n    me.addInputRule(function(root){\r\n        root.traversal(function(node){\r\n            if(node.type == 'text' && node.data == me.options.pageBreakTag){\r\n                var hr = UE.uNode.createElement('<hr class=\"pagebreak\" noshade=\"noshade\" size=\"5\" style=\"-webkit-user-select: none;\">');\r\n                node.parentNode.insertBefore(hr,node);\r\n                node.parentNode.removeChild(node)\r\n            }\r\n        })\r\n    });\r\n    me.addOutputRule(function(node){\r\n        utils.each(node.getNodesByTagName('hr'),function(n){\r\n            if(n.getAttr('class') == 'pagebreak'){\r\n                var txt = UE.uNode.createText(me.options.pageBreakTag);\r\n                n.parentNode.insertBefore(txt,n);\r\n                n.parentNode.removeChild(n);\r\n            }\r\n        })\r\n\r\n    });\r\n\r\n    /**\r\n     * 插入分页符\r\n     * @command pagebreak\r\n     * @method execCommand\r\n     * @param { String } cmd 命令字符串\r\n     * @remind 在表格中插入分页符会把表格切分成两部分\r\n     * @remind 获取编辑器内的数据时， 编辑器会把分页符转换成“_ueditor_page_break_tag_”字符串，\r\n     *          以便于提交数据到服务器端后处理分页。\r\n     * @example\r\n     * ```javascript\r\n     * editor.execCommand( 'pagebreak'); //插入一个hr标签，带有样式类名pagebreak\r\n     * ```\r\n     */\r\n\r\n    me.commands['pagebreak'] = {\r\n        execCommand:function () {\r\n            var range = me.selection.getRange(),hr = me.document.createElement('hr');\r\n            domUtils.setAttributes(hr,{\r\n                'class' : 'pagebreak',\r\n                noshade:\"noshade\",\r\n                size:\"5\"\r\n            });\r\n            domUtils.unSelectable(hr);\r\n            //table单独处理\r\n            var node = domUtils.findParentByTagName(range.startContainer, notBreakTags, true),\r\n\r\n                parents = [], pN;\r\n            if (node) {\r\n                switch (node.tagName) {\r\n                    case 'TD':\r\n                        pN = node.parentNode;\r\n                        if (!pN.previousSibling) {\r\n                            var table = domUtils.findParentByTagName(pN, 'table');\r\n//                            var tableWrapDiv = table.parentNode;\r\n//                            if(tableWrapDiv && tableWrapDiv.nodeType == 1\r\n//                                && tableWrapDiv.tagName == 'DIV'\r\n//                                && tableWrapDiv.getAttribute('dropdrag')\r\n//                                ){\r\n//                                domUtils.remove(tableWrapDiv,true);\r\n//                            }\r\n                            table.parentNode.insertBefore(hr, table);\r\n                            parents = domUtils.findParents(hr, true);\r\n\r\n                        } else {\r\n                            pN.parentNode.insertBefore(hr, pN);\r\n                            parents = domUtils.findParents(hr);\r\n\r\n                        }\r\n                        pN = parents[1];\r\n                        if (hr !== pN) {\r\n                            domUtils.breakParent(hr, pN);\r\n\r\n                        }\r\n                        //table要重写绑定一下拖拽\r\n                        me.fireEvent('afteradjusttable',me.document);\r\n                }\r\n\r\n            } else {\r\n\r\n                if (!range.collapsed) {\r\n                    range.deleteContents();\r\n                    var start = range.startContainer;\r\n                    while ( !domUtils.isBody(start) && domUtils.isBlockElm(start) && domUtils.isEmptyNode(start)) {\r\n                        range.setStartBefore(start).collapse(true);\r\n                        domUtils.remove(start);\r\n                        start = range.startContainer;\r\n                    }\r\n\r\n                }\r\n                range.insertNode(hr);\r\n\r\n                var pN = hr.parentNode, nextNode;\r\n                while (!domUtils.isBody(pN)) {\r\n                    domUtils.breakParent(hr, pN);\r\n                    nextNode = hr.nextSibling;\r\n                    if (nextNode && domUtils.isEmptyBlock(nextNode)) {\r\n                        domUtils.remove(nextNode);\r\n                    }\r\n                    pN = hr.parentNode;\r\n                }\r\n                nextNode = hr.nextSibling;\r\n                var pre = hr.previousSibling;\r\n                if(isHr(pre)){\r\n                    domUtils.remove(pre);\r\n                }else{\r\n                    pre && fillNode(pre);\r\n                }\r\n\r\n                if(!nextNode){\r\n                    var p = me.document.createElement('p');\r\n\r\n                    hr.parentNode.appendChild(p);\r\n                    domUtils.fillNode(me.document,p);\r\n                    range.setStart(p,0).collapse(true);\r\n                }else{\r\n                    if(isHr(nextNode)){\r\n                        domUtils.remove(nextNode);\r\n                    }else{\r\n                        fillNode(nextNode);\r\n                    }\r\n                    range.setEndAfter(hr).collapse(false);\r\n                }\r\n\r\n                range.select(true);\r\n\r\n            }\r\n\r\n        }\r\n    };\r\n};\r\n\r\n// plugins/wordimage.js\r\n///import core\r\n///commands 本地图片引导上传\r\n///commandsName  WordImage\r\n///commandsTitle  本地图片引导上传\r\n///commandsDialog  dialogs\\wordimage\r\n\r\nUE.plugin.register('wordimage',function(){\r\n    var me = this,\r\n        images = [];\r\n    return {\r\n        commands : {\r\n            'wordimage':{\r\n                execCommand:function () {\r\n                    var images = domUtils.getElementsByTagName(me.body, \"img\");\r\n                    var urlList = [];\r\n                    for (var i = 0, ci; ci = images[i++];) {\r\n                        var url = ci.getAttribute(\"word_img\");\r\n                        url && urlList.push(url);\r\n                    }\r\n                    return urlList;\r\n                },\r\n                queryCommandState:function () {\r\n                    images = domUtils.getElementsByTagName(me.body, \"img\");\r\n                    for (var i = 0, ci; ci = images[i++];) {\r\n                        if (ci.getAttribute(\"word_img\")) {\r\n                            return 1;\r\n                        }\r\n                    }\r\n                    return -1;\r\n                },\r\n                notNeedUndo:true\r\n            }\r\n        },\r\n        inputRule : function (root) {\r\n            utils.each(root.getNodesByTagName('img'), function (img) {\r\n                var attrs = img.attrs,\r\n                    flag = parseInt(attrs.width) < 128 || parseInt(attrs.height) < 43,\r\n                    opt = me.options,\r\n                    src = opt.UEDITOR_HOME_URL + 'themes/default/images/spacer.gif';\r\n                if (attrs['src'] && /^(?:(file:\\/+))/.test(attrs['src'])) {\r\n                    img.setAttr({\r\n                        width:attrs.width,\r\n                        height:attrs.height,\r\n                        alt:attrs.alt,\r\n                        word_img: attrs.src,\r\n                        src:src,\r\n                        'style':'background:url(' + ( flag ? opt.themePath + opt.theme + '/images/word.gif' : opt.langPath + opt.lang + '/images/localimage.png') + ') no-repeat center center;border:1px solid #ddd'\r\n                    })\r\n                }\r\n            })\r\n        }\r\n    }\r\n});\r\n\r\n// plugins/dragdrop.js\r\nUE.plugins['dragdrop'] = function (){\r\n\r\n    var me = this;\r\n    me.ready(function(){\r\n        domUtils.on(this.body,'dragend',function(){\r\n            var rng = me.selection.getRange();\r\n            var node = rng.getClosedNode()||me.selection.getStart();\r\n\r\n            if(node && node.tagName == 'IMG'){\r\n\r\n                var pre = node.previousSibling,next;\r\n                while(next = node.nextSibling){\r\n                    if(next.nodeType == 1 && next.tagName == 'SPAN' && !next.firstChild){\r\n                        domUtils.remove(next)\r\n                    }else{\r\n                        break;\r\n                    }\r\n                }\r\n\r\n\r\n                if((pre && pre.nodeType == 1 && !domUtils.isEmptyBlock(pre) || !pre) && (!next || next && !domUtils.isEmptyBlock(next))){\r\n                    if(pre && pre.tagName == 'P' && !domUtils.isEmptyBlock(pre)){\r\n                        pre.appendChild(node);\r\n                        domUtils.moveChild(next,pre);\r\n                        domUtils.remove(next);\r\n                    }else  if(next && next.tagName == 'P' && !domUtils.isEmptyBlock(next)){\r\n                        next.insertBefore(node,next.firstChild);\r\n                    }\r\n\r\n                    if(pre && pre.tagName == 'P' && domUtils.isEmptyBlock(pre)){\r\n                        domUtils.remove(pre)\r\n                    }\r\n                    if(next && next.tagName == 'P' && domUtils.isEmptyBlock(next)){\r\n                        domUtils.remove(next)\r\n                    }\r\n                    rng.selectNode(node).select();\r\n                    me.fireEvent('saveScene');\r\n\r\n                }\r\n\r\n            }\r\n\r\n        })\r\n    });\r\n    me.addListener('keyup', function(type, evt) {\r\n        var keyCode = evt.keyCode || evt.which;\r\n        if (keyCode == 13) {\r\n            var rng = me.selection.getRange(),node;\r\n            if(node = domUtils.findParentByTagName(rng.startContainer,'p',true)){\r\n                if(domUtils.getComputedStyle(node,'text-align') == 'center'){\r\n                    domUtils.removeStyle(node,'text-align')\r\n                }\r\n            }\r\n        }\r\n    })\r\n};\r\n\r\n\r\n// plugins/undo.js\r\n/**\r\n * undo redo\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 撤销上一次执行的命令\r\n * @command undo\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'undo' );\r\n * ```\r\n */\r\n\r\n/**\r\n * 重做上一次执行的命令\r\n * @command redo\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'redo' );\r\n * ```\r\n */\r\n\r\nUE.plugins['undo'] = function () {\r\n    var saveSceneTimer;\r\n    var me = this,\r\n        maxUndoCount = me.options.maxUndoCount || 20,\r\n        maxInputCount = me.options.maxInputCount || 20,\r\n        fillchar = new RegExp(domUtils.fillChar + '|<\\/hr>', 'gi');// ie会产生多余的</hr>\r\n    var noNeedFillCharTags = {\r\n        ol:1,ul:1,table:1,tbody:1,tr:1,body:1\r\n    };\r\n    var orgState = me.options.autoClearEmptyNode;\r\n    function compareAddr(indexA, indexB) {\r\n        if (indexA.length != indexB.length)\r\n            return 0;\r\n        for (var i = 0, l = indexA.length; i < l; i++) {\r\n            if (indexA[i] != indexB[i])\r\n                return 0\r\n        }\r\n        return 1;\r\n    }\r\n\r\n    function compareRangeAddress(rngAddrA, rngAddrB) {\r\n        if (rngAddrA.collapsed != rngAddrB.collapsed) {\r\n            return 0;\r\n        }\r\n        if (!compareAddr(rngAddrA.startAddress, rngAddrB.startAddress) || !compareAddr(rngAddrA.endAddress, rngAddrB.endAddress)) {\r\n            return 0;\r\n        }\r\n        return 1;\r\n    }\r\n\r\n    function UndoManager() {\r\n        this.list = [];\r\n        this.index = 0;\r\n        this.hasUndo = false;\r\n        this.hasRedo = false;\r\n        this.undo = function () {\r\n            if (this.hasUndo) {\r\n                if (!this.list[this.index - 1] && this.list.length == 1) {\r\n                    this.reset();\r\n                    return;\r\n                }\r\n                while (this.list[this.index].content == this.list[this.index - 1].content) {\r\n                    this.index--;\r\n                    if (this.index == 0) {\r\n                        return this.restore(0);\r\n                    }\r\n                }\r\n                this.restore(--this.index);\r\n            }\r\n        };\r\n        this.redo = function () {\r\n            if (this.hasRedo) {\r\n                while (this.list[this.index].content == this.list[this.index + 1].content) {\r\n                    this.index++;\r\n                    if (this.index == this.list.length - 1) {\r\n                        return this.restore(this.index);\r\n                    }\r\n                }\r\n                this.restore(++this.index);\r\n            }\r\n        };\r\n\r\n        this.restore = function () {\r\n            var me = this.editor;\r\n            var scene = this.list[this.index];\r\n            var root = UE.htmlparser(scene.content.replace(fillchar, ''));\r\n            me.options.autoClearEmptyNode = false;\r\n            me.filterInputRule(root);\r\n            me.options.autoClearEmptyNode = orgState;\r\n            //trace:873\r\n            //去掉展位符\r\n            me.document.body.innerHTML = root.toHtml();\r\n            me.fireEvent('afterscencerestore');\r\n            //处理undo后空格不展位的问题\r\n            if (browser.ie) {\r\n                utils.each(domUtils.getElementsByTagName(me.document,'td th caption p'),function(node){\r\n                    if(domUtils.isEmptyNode(node)){\r\n                        domUtils.fillNode(me.document, node);\r\n                    }\r\n                })\r\n            }\r\n\r\n            try{\r\n                var rng = new dom.Range(me.document).moveToAddress(scene.address);\r\n                rng.select(noNeedFillCharTags[rng.startContainer.nodeName.toLowerCase()]);\r\n            }catch(e){}\r\n\r\n            this.update();\r\n            this.clearKey();\r\n            //不能把自己reset了\r\n            me.fireEvent('reset', true);\r\n        };\r\n\r\n        this.getScene = function () {\r\n            var me = this.editor;\r\n            var rng = me.selection.getRange(),\r\n                rngAddress = rng.createAddress(false,true);\r\n            me.fireEvent('beforegetscene');\r\n            var root = UE.htmlparser(me.body.innerHTML);\r\n            me.options.autoClearEmptyNode = false;\r\n            me.filterOutputRule(root);\r\n            me.options.autoClearEmptyNode = orgState;\r\n            var cont = root.toHtml();\r\n            //trace:3461\r\n            //这个会引起回退时导致空格丢失的情况\r\n//            browser.ie && (cont = cont.replace(/>&nbsp;</g, '><').replace(/\\s*</g, '<').replace(/>\\s*/g, '>'));\r\n            me.fireEvent('aftergetscene');\r\n\r\n            return {\r\n                address:rngAddress,\r\n                content:cont\r\n            }\r\n        };\r\n        this.save = function (notCompareRange,notSetCursor) {\r\n            clearTimeout(saveSceneTimer);\r\n            var currentScene = this.getScene(notSetCursor),\r\n                lastScene = this.list[this.index];\r\n\r\n            if(lastScene && lastScene.content != currentScene.content){\r\n                me.trigger('contentchange')\r\n            }\r\n            //内容相同位置相同不存\r\n            if (lastScene && lastScene.content == currentScene.content &&\r\n                ( notCompareRange ? 1 : compareRangeAddress(lastScene.address, currentScene.address) )\r\n                ) {\r\n                return;\r\n            }\r\n            this.list = this.list.slice(0, this.index + 1);\r\n            this.list.push(currentScene);\r\n            //如果大于最大数量了，就把最前的剔除\r\n            if (this.list.length > maxUndoCount) {\r\n                this.list.shift();\r\n            }\r\n            this.index = this.list.length - 1;\r\n            this.clearKey();\r\n            //跟新undo/redo状态\r\n            this.update();\r\n\r\n        };\r\n        this.update = function () {\r\n            this.hasRedo = !!this.list[this.index + 1];\r\n            this.hasUndo = !!this.list[this.index - 1];\r\n        };\r\n        this.reset = function () {\r\n            this.list = [];\r\n            this.index = 0;\r\n            this.hasUndo = false;\r\n            this.hasRedo = false;\r\n            this.clearKey();\r\n        };\r\n        this.clearKey = function () {\r\n            keycont = 0;\r\n            lastKeyCode = null;\r\n        };\r\n    }\r\n\r\n    me.undoManger = new UndoManager();\r\n    me.undoManger.editor = me;\r\n    function saveScene() {\r\n        this.undoManger.save();\r\n    }\r\n\r\n    me.addListener('saveScene', function () {\r\n        var args = Array.prototype.splice.call(arguments,1);\r\n        this.undoManger.save.apply(this.undoManger,args);\r\n    });\r\n\r\n//    me.addListener('beforeexeccommand', saveScene);\r\n//    me.addListener('afterexeccommand', saveScene);\r\n\r\n    me.addListener('reset', function (type, exclude) {\r\n        if (!exclude) {\r\n            this.undoManger.reset();\r\n        }\r\n    });\r\n    me.commands['redo'] = me.commands['undo'] = {\r\n        execCommand:function (cmdName) {\r\n            this.undoManger[cmdName]();\r\n        },\r\n        queryCommandState:function (cmdName) {\r\n            return this.undoManger['has' + (cmdName.toLowerCase() == 'undo' ? 'Undo' : 'Redo')] ? 0 : -1;\r\n        },\r\n        notNeedUndo:1\r\n    };\r\n\r\n    var keys = {\r\n            //  /*Backspace*/ 8:1, /*Delete*/ 46:1,\r\n            /*Shift*/ 16:1, /*Ctrl*/ 17:1, /*Alt*/ 18:1,\r\n            37:1, 38:1, 39:1, 40:1\r\n\r\n        },\r\n        keycont = 0,\r\n        lastKeyCode;\r\n    //输入法状态下不计算字符数\r\n    var inputType = false;\r\n    me.addListener('ready', function () {\r\n        domUtils.on(this.body, 'compositionstart', function () {\r\n            inputType = true;\r\n        });\r\n        domUtils.on(this.body, 'compositionend', function () {\r\n            inputType = false;\r\n        })\r\n    });\r\n    //快捷键\r\n    me.addshortcutkey({\r\n        \"Undo\":\"ctrl+90\", //undo\r\n        \"Redo\":\"ctrl+89\" //redo\r\n\r\n    });\r\n    var isCollapsed = true;\r\n    me.addListener('keydown', function (type, evt) {\r\n\r\n        var me = this;\r\n        var keyCode = evt.keyCode || evt.which;\r\n        if (!keys[keyCode] && !evt.ctrlKey && !evt.metaKey && !evt.shiftKey && !evt.altKey) {\r\n            if (inputType)\r\n                return;\r\n\r\n            if(!me.selection.getRange().collapsed){\r\n                me.undoManger.save(false,true);\r\n                isCollapsed = false;\r\n                return;\r\n            }\r\n            if (me.undoManger.list.length == 0) {\r\n                me.undoManger.save(true);\r\n            }\r\n            clearTimeout(saveSceneTimer);\r\n            function save(cont){\r\n                cont.undoManger.save(false,true);\r\n                cont.fireEvent('selectionchange');\r\n            }\r\n            saveSceneTimer = setTimeout(function(){\r\n                if(inputType){\r\n                    var interalTimer = setInterval(function(){\r\n                        if(!inputType){\r\n                            save(me);\r\n                            clearInterval(interalTimer)\r\n                        }\r\n                    },300)\r\n                    return;\r\n                }\r\n                save(me);\r\n            },200);\r\n\r\n            lastKeyCode = keyCode;\r\n            keycont++;\r\n            if (keycont >= maxInputCount ) {\r\n                save(me)\r\n            }\r\n        }\r\n    });\r\n    me.addListener('keyup', function (type, evt) {\r\n        var keyCode = evt.keyCode || evt.which;\r\n        if (!keys[keyCode] && !evt.ctrlKey && !evt.metaKey && !evt.shiftKey && !evt.altKey) {\r\n            if (inputType)\r\n                return;\r\n            if(!isCollapsed){\r\n                this.undoManger.save(false,true);\r\n                isCollapsed = true;\r\n            }\r\n        }\r\n    });\r\n    //扩展实例，添加关闭和开启命令undo\r\n    me.stopCmdUndo = function(){\r\n        me.__hasEnterExecCommand = true;\r\n    };\r\n    me.startCmdUndo = function(){\r\n        me.__hasEnterExecCommand = false;\r\n    }\r\n};\r\n\r\n\r\n// plugins/copy.js\r\nUE.plugin.register('copy', function () {\r\n\r\n    var me = this;\r\n\r\n    function initZeroClipboard() {\r\n\r\n        ZeroClipboard.config({\r\n            debug: false,\r\n            swfPath: me.options.UEDITOR_HOME_URL + 'third-party/zeroclipboard/ZeroClipboard.swf'\r\n        });\r\n\r\n        var client = me.zeroclipboard = new ZeroClipboard();\r\n\r\n        // 复制内容\r\n        client.on('copy', function (e) {\r\n            var client = e.client,\r\n                rng = me.selection.getRange(),\r\n                div = document.createElement('div');\r\n\r\n            div.appendChild(rng.cloneContents());\r\n            client.setText(div.innerText || div.textContent);\r\n            client.setHtml(div.innerHTML);\r\n            rng.select();\r\n        });\r\n        // hover事件传递到target\r\n        client.on('mouseover mouseout', function (e) {\r\n            var target = e.target;\r\n            if (e.type == 'mouseover') {\r\n                domUtils.addClass(target, 'edui-state-hover');\r\n            } else if (e.type == 'mouseout') {\r\n                domUtils.removeClasses(target, 'edui-state-hover');\r\n            }\r\n        });\r\n        // flash加载不成功\r\n        client.on('wrongflash noflash', function () {\r\n            ZeroClipboard.destroy();\r\n        });\r\n    }\r\n\r\n    return {\r\n        bindEvents: {\r\n            'ready': function () {\r\n                if (!browser.ie) {\r\n                    if (window.ZeroClipboard) {\r\n                        initZeroClipboard();\r\n                    } else {\r\n                        utils.loadFile(document, {\r\n                            src: me.options.UEDITOR_HOME_URL + \"third-party/zeroclipboard/ZeroClipboard.js\",\r\n                            tag: \"script\",\r\n                            type: \"text/javascript\",\r\n                            defer: \"defer\"\r\n                        }, function () {\r\n                            initZeroClipboard();\r\n                        });\r\n                    }\r\n                }\r\n            }\r\n        },\r\n        commands: {\r\n            'copy': {\r\n                execCommand: function (cmd) {\r\n                    if (!me.document.execCommand('copy')) {\r\n                        alert(me.getLang('copymsg'));\r\n                    }\r\n                }\r\n            }\r\n        }\r\n    }\r\n});\r\n\r\n\r\n// plugins/paste.js\r\n///import core\r\n///import plugins/inserthtml.js\r\n///import plugins/undo.js\r\n///import plugins/serialize.js\r\n///commands 粘贴\r\n///commandsName  PastePlain\r\n///commandsTitle  纯文本粘贴模式\r\n/**\r\n * @description 粘贴\r\n * @author zhanyi\r\n */\r\nUE.plugins['paste'] = function () {\r\n    function getClipboardData(callback) {\r\n        var doc = this.document;\r\n        if (doc.getElementById('baidu_pastebin')) {\r\n            return;\r\n        }\r\n        var range = this.selection.getRange(),\r\n            bk = range.createBookmark(),\r\n        //创建剪贴的容器div\r\n            pastebin = doc.createElement('div');\r\n        pastebin.id = 'baidu_pastebin';\r\n        // Safari 要求div必须有内容，才能粘贴内容进来\r\n        browser.webkit && pastebin.appendChild(doc.createTextNode(domUtils.fillChar + domUtils.fillChar));\r\n        doc.body.appendChild(pastebin);\r\n        //trace:717 隐藏的span不能得到top\r\n        //bk.start.innerHTML = '&nbsp;';\r\n        bk.start.style.display = '';\r\n        pastebin.style.cssText = \"position:absolute;width:1px;height:1px;overflow:hidden;left:-1000px;white-space:nowrap;top:\" +\r\n            //要在现在光标平行的位置加入，否则会出现跳动的问题\r\n            domUtils.getXY(bk.start).y + 'px';\r\n\r\n        range.selectNodeContents(pastebin).select(true);\r\n\r\n        setTimeout(function () {\r\n            if (browser.webkit) {\r\n                for (var i = 0, pastebins = doc.querySelectorAll('#baidu_pastebin'), pi; pi = pastebins[i++];) {\r\n                    if (domUtils.isEmptyNode(pi)) {\r\n                        domUtils.remove(pi);\r\n                    } else {\r\n                        pastebin = pi;\r\n                        break;\r\n                    }\r\n                }\r\n            }\r\n            try {\r\n                pastebin.parentNode.removeChild(pastebin);\r\n            } catch (e) {\r\n            }\r\n            range.moveToBookmark(bk).select(true);\r\n            callback(pastebin);\r\n        }, 0);\r\n    }\r\n\r\n    var me = this;\r\n\r\n    me.setOpt({\r\n        retainOnlyLabelPasted : false\r\n    });\r\n\r\n    var txtContent, htmlContent, address;\r\n\r\n    function getPureHtml(html){\r\n        return html.replace(/<(\\/?)([\\w\\-]+)([^>]*)>/gi, function (a, b, tagName, attrs) {\r\n            tagName = tagName.toLowerCase();\r\n            if ({img: 1}[tagName]) {\r\n                return a;\r\n            }\r\n            attrs = attrs.replace(/([\\w\\-]*?)\\s*=\\s*((\"([^\"]*)\")|('([^']*)')|([^\\s>]+))/gi, function (str, atr, val) {\r\n                if ({\r\n                    'src': 1,\r\n                    'href': 1,\r\n                    'name': 1\r\n                }[atr.toLowerCase()]) {\r\n                    return atr + '=' + val + ' '\r\n                }\r\n                return ''\r\n            });\r\n            if ({\r\n                'span': 1,\r\n                'div': 1\r\n            }[tagName]) {\r\n                return ''\r\n            } else {\r\n\r\n                return '<' + b + tagName + ' ' + utils.trim(attrs) + '>'\r\n            }\r\n\r\n        });\r\n    }\r\n    function filter(div) {\r\n        var html;\r\n        if (div.firstChild) {\r\n            //去掉cut中添加的边界值\r\n            var nodes = domUtils.getElementsByTagName(div, 'span');\r\n            for (var i = 0, ni; ni = nodes[i++];) {\r\n                if (ni.id == '_baidu_cut_start' || ni.id == '_baidu_cut_end') {\r\n                    domUtils.remove(ni);\r\n                }\r\n            }\r\n\r\n            if (browser.webkit) {\r\n\r\n                var brs = div.querySelectorAll('div br');\r\n                for (var i = 0, bi; bi = brs[i++];) {\r\n                    var pN = bi.parentNode;\r\n                    if (pN.tagName == 'DIV' && pN.childNodes.length == 1) {\r\n                        pN.innerHTML = '<p><br/></p>';\r\n                        domUtils.remove(pN);\r\n                    }\r\n                }\r\n                var divs = div.querySelectorAll('#baidu_pastebin');\r\n                for (var i = 0, di; di = divs[i++];) {\r\n                    var tmpP = me.document.createElement('p');\r\n                    di.parentNode.insertBefore(tmpP, di);\r\n                    while (di.firstChild) {\r\n                        tmpP.appendChild(di.firstChild);\r\n                    }\r\n                    domUtils.remove(di);\r\n                }\r\n\r\n                var metas = div.querySelectorAll('meta');\r\n                for (var i = 0, ci; ci = metas[i++];) {\r\n                    domUtils.remove(ci);\r\n                }\r\n\r\n                var brs = div.querySelectorAll('br');\r\n                for (i = 0; ci = brs[i++];) {\r\n                    if (/^apple-/i.test(ci.className)) {\r\n                        domUtils.remove(ci);\r\n                    }\r\n                }\r\n            }\r\n            if (browser.gecko) {\r\n                var dirtyNodes = div.querySelectorAll('[_moz_dirty]');\r\n                for (i = 0; ci = dirtyNodes[i++];) {\r\n                    ci.removeAttribute('_moz_dirty');\r\n                }\r\n            }\r\n            if (!browser.ie) {\r\n                var spans = div.querySelectorAll('span.Apple-style-span');\r\n                for (var i = 0, ci; ci = spans[i++];) {\r\n                    domUtils.remove(ci, true);\r\n                }\r\n            }\r\n\r\n            //ie下使用innerHTML会产生多余的\\r\\n字符，也会产生&nbsp;这里过滤掉\r\n            html = div.innerHTML;//.replace(/>(?:(\\s|&nbsp;)*?)</g,'><');\r\n\r\n            //过滤word粘贴过来的冗余属性\r\n            html = UE.filterWord(html);\r\n            //取消了忽略空白的第二个参数，粘贴过来的有些是有空白的，会被套上相关的标签\r\n            var root = UE.htmlparser(html);\r\n            //如果给了过滤规则就先进行过滤\r\n            if (me.options.filterRules) {\r\n                UE.filterNode(root, me.options.filterRules);\r\n            }\r\n            //执行默认的处理\r\n            me.filterInputRule(root);\r\n            //针对chrome的处理\r\n            if (browser.webkit) {\r\n                var br = root.lastChild();\r\n                if (br && br.type == 'element' && br.tagName == 'br') {\r\n                    root.removeChild(br)\r\n                }\r\n                utils.each(me.body.querySelectorAll('div'), function (node) {\r\n                    if (domUtils.isEmptyBlock(node)) {\r\n                        domUtils.remove(node,true)\r\n                    }\r\n                })\r\n            }\r\n            html = {'html': root.toHtml()};\r\n            me.fireEvent('beforepaste', html, root);\r\n            //抢了默认的粘贴，那后边的内容就不执行了，比如表格粘贴\r\n            if(!html.html){\r\n                return;\r\n            }\r\n            root = UE.htmlparser(html.html,true);\r\n            //如果开启了纯文本模式\r\n            if (me.queryCommandState('pasteplain') === 1) {\r\n                me.execCommand('insertHtml', UE.filterNode(root, me.options.filterTxtRules).toHtml(), true);\r\n            } else {\r\n                //文本模式\r\n                UE.filterNode(root, me.options.filterTxtRules);\r\n                txtContent = root.toHtml();\r\n                //完全模式\r\n                htmlContent = html.html;\r\n\r\n                address = me.selection.getRange().createAddress(true);\r\n                me.execCommand('insertHtml', me.getOpt('retainOnlyLabelPasted') === true ?  getPureHtml(htmlContent) : htmlContent, true);\r\n            }\r\n            me.fireEvent(\"afterpaste\", html);\r\n        }\r\n    }\r\n\r\n    me.addListener('pasteTransfer', function (cmd, plainType) {\r\n\r\n        if (address && txtContent && htmlContent && txtContent != htmlContent) {\r\n            var range = me.selection.getRange();\r\n            range.moveToAddress(address, true);\r\n\r\n            if (!range.collapsed) {\r\n\r\n                while (!domUtils.isBody(range.startContainer)\r\n                    ) {\r\n                    var start = range.startContainer;\r\n                    if(start.nodeType == 1){\r\n                        start = start.childNodes[range.startOffset];\r\n                        if(!start){\r\n                            range.setStartBefore(range.startContainer);\r\n                            continue;\r\n                        }\r\n                        var pre = start.previousSibling;\r\n\r\n                        if(pre && pre.nodeType == 3 && new RegExp('^[\\n\\r\\t '+domUtils.fillChar+']*$').test(pre.nodeValue)){\r\n                            range.setStartBefore(pre)\r\n                        }\r\n                    }\r\n                    if(range.startOffset == 0){\r\n                        range.setStartBefore(range.startContainer);\r\n                    }else{\r\n                        break;\r\n                    }\r\n\r\n                }\r\n                while (!domUtils.isBody(range.endContainer)\r\n                    ) {\r\n                    var end = range.endContainer;\r\n                    if(end.nodeType == 1){\r\n                        end = end.childNodes[range.endOffset];\r\n                        if(!end){\r\n                            range.setEndAfter(range.endContainer);\r\n                            continue;\r\n                        }\r\n                        var next = end.nextSibling;\r\n                        if(next && next.nodeType == 3 && new RegExp('^[\\n\\r\\t'+domUtils.fillChar+']*$').test(next.nodeValue)){\r\n                            range.setEndAfter(next)\r\n                        }\r\n                    }\r\n                    if(range.endOffset == range.endContainer[range.endContainer.nodeType == 3 ? 'nodeValue' : 'childNodes'].length){\r\n                        range.setEndAfter(range.endContainer);\r\n                    }else{\r\n                        break;\r\n                    }\r\n\r\n                }\r\n\r\n            }\r\n\r\n            range.deleteContents();\r\n            range.select(true);\r\n            me.__hasEnterExecCommand = true;\r\n            var html = htmlContent;\r\n            if (plainType === 2 ) {\r\n                html = getPureHtml(html);\r\n            } else if (plainType) {\r\n                html = txtContent;\r\n            }\r\n            me.execCommand('inserthtml', html, true);\r\n            me.__hasEnterExecCommand = false;\r\n            var rng = me.selection.getRange();\r\n            while (!domUtils.isBody(rng.startContainer) && !rng.startOffset &&\r\n                rng.startContainer[rng.startContainer.nodeType == 3 ? 'nodeValue' : 'childNodes'].length\r\n                ) {\r\n                rng.setStartBefore(rng.startContainer);\r\n            }\r\n            var tmpAddress = rng.createAddress(true);\r\n            address.endAddress = tmpAddress.startAddress;\r\n        }\r\n    });\r\n\r\n    me.addListener('ready', function () {\r\n        domUtils.on(me.body, 'cut', function () {\r\n            var range = me.selection.getRange();\r\n            if (!range.collapsed && me.undoManger) {\r\n                me.undoManger.save();\r\n            }\r\n        });\r\n\r\n        //ie下beforepaste在点击右键时也会触发，所以用监控键盘才处理\r\n        domUtils.on(me.body, browser.ie || browser.opera ? 'keydown' : 'paste', function (e) {\r\n            if ((browser.ie || browser.opera) && ((!e.ctrlKey && !e.metaKey) || e.keyCode != '86')) {\r\n                return;\r\n            }\r\n            getClipboardData.call(me, function (div) {\r\n                filter(div);\r\n            });\r\n        });\r\n\r\n    });\r\n\r\n    me.commands['paste'] = {\r\n        execCommand: function (cmd) {\r\n            if (browser.ie) {\r\n                getClipboardData.call(me, function (div) {\r\n                    filter(div);\r\n                });\r\n                me.document.execCommand('paste');\r\n            } else {\r\n                alert(me.getLang('pastemsg'));\r\n            }\r\n        }\r\n    }\r\n};\r\n\r\n\r\n\r\n// plugins/puretxtpaste.js\r\n/**\r\n * 纯文本粘贴插件\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\nUE.plugins['pasteplain'] = function(){\r\n    var me = this;\r\n    me.setOpt({\r\n        'pasteplain':false,\r\n        'filterTxtRules' : function(){\r\n            function transP(node){\r\n                node.tagName = 'p';\r\n                node.setStyle();\r\n            }\r\n            function removeNode(node){\r\n                node.parentNode.removeChild(node,true)\r\n            }\r\n            return {\r\n                //直接删除及其字节点内容\r\n                '-' : 'script style object iframe embed input select',\r\n                'p': {$:{}},\r\n                'br':{$:{}},\r\n                div: function (node) {\r\n                    var tmpNode, p = UE.uNode.createElement('p');\r\n                    while (tmpNode = node.firstChild()) {\r\n                        if (tmpNode.type == 'text' || !UE.dom.dtd.$block[tmpNode.tagName]) {\r\n                            p.appendChild(tmpNode);\r\n                        } else {\r\n                            if (p.firstChild()) {\r\n                                node.parentNode.insertBefore(p, node);\r\n                                p = UE.uNode.createElement('p');\r\n                            } else {\r\n                                node.parentNode.insertBefore(tmpNode, node);\r\n                            }\r\n                        }\r\n                    }\r\n                    if (p.firstChild()) {\r\n                        node.parentNode.insertBefore(p, node);\r\n                    }\r\n                    node.parentNode.removeChild(node);\r\n                },\r\n                ol: removeNode,\r\n                ul: removeNode,\r\n                dl:removeNode,\r\n                dt:removeNode,\r\n                dd:removeNode,\r\n                'li':removeNode,\r\n                'caption':transP,\r\n                'th':transP,\r\n                'tr':transP,\r\n                'h1':transP,'h2':transP,'h3':transP,'h4':transP,'h5':transP,'h6':transP,\r\n                'td':function(node){\r\n                        //没有内容的td直接删掉\r\n                        var txt = !!node.innerText();\r\n                        if(txt){\r\n                         node.parentNode.insertAfter(UE.uNode.createText(' &nbsp; &nbsp;'),node);\r\n                    }\r\n                    node.parentNode.removeChild(node,node.innerText())\r\n                }\r\n            }\r\n        }()\r\n    });\r\n    //暂时这里支持一下老版本的属性\r\n    var pasteplain = me.options.pasteplain;\r\n\r\n    /**\r\n     * 启用或取消纯文本粘贴模式\r\n     * @command pasteplain\r\n     * @method execCommand\r\n     * @param { String } cmd 命令字符串\r\n     * @example\r\n     * ```javascript\r\n     * editor.queryCommandState( 'pasteplain' );\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 查询当前是否处于纯文本粘贴模式\r\n     * @command pasteplain\r\n     * @method queryCommandState\r\n     * @param { String } cmd 命令字符串\r\n     * @return { int } 如果处于纯文本模式，返回1，否则，返回0\r\n     * @example\r\n     * ```javascript\r\n     * editor.queryCommandState( 'pasteplain' );\r\n     * ```\r\n     */\r\n    me.commands['pasteplain'] = {\r\n        queryCommandState: function (){\r\n            return pasteplain ? 1 : 0;\r\n        },\r\n        execCommand: function (){\r\n            pasteplain = !pasteplain|0;\r\n        },\r\n        notNeedUndo : 1\r\n    };\r\n};\r\n\r\n// plugins/list.js\r\n/**\r\n * 有序列表,无序列表插件\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\nUE.plugins['list'] = function () {\r\n    var me = this,\r\n        notExchange = {\r\n            'TD':1,\r\n            'PRE':1,\r\n            'BLOCKQUOTE':1\r\n        };\r\n    var customStyle = {\r\n        'cn' : 'cn-1-',\r\n        'cn1' : 'cn-2-',\r\n        'cn2' : 'cn-3-',\r\n        'num':  'num-1-',\r\n        'num1' : 'num-2-',\r\n        'num2' : 'num-3-',\r\n        'dash'  : 'dash',\r\n        'dot':'dot'\r\n    };\r\n\r\n    me.setOpt( {\r\n        'autoTransWordToList':false,\r\n        'insertorderedlist':{\r\n            'num':'',\r\n            'num1':'',\r\n            'num2':'',\r\n            'cn':'',\r\n            'cn1':'',\r\n            'cn2':'',\r\n            'decimal':'',\r\n            'lower-alpha':'',\r\n            'lower-roman':'',\r\n            'upper-alpha':'',\r\n            'upper-roman':''\r\n        },\r\n        'insertunorderedlist':{\r\n            'circle':'',\r\n            'disc':'',\r\n            'square':'',\r\n            'dash' : '',\r\n            'dot':''\r\n        },\r\n        listDefaultPaddingLeft : '30',\r\n        listiconpath : 'http://bs.baidu.com/listicon/',\r\n        maxListLevel : -1,//-1不限制\r\n        disablePInList:false\r\n    } );\r\n    function listToArray(list){\r\n        var arr = [];\r\n        for(var p in list){\r\n            arr.push(p)\r\n        }\r\n        return arr;\r\n    }\r\n    var listStyle = {\r\n        'OL':listToArray(me.options.insertorderedlist),\r\n        'UL':listToArray(me.options.insertunorderedlist)\r\n    };\r\n    var liiconpath = me.options.listiconpath;\r\n\r\n    //根据用户配置，调整customStyle\r\n    for(var s in customStyle){\r\n        if(!me.options.insertorderedlist.hasOwnProperty(s) && !me.options.insertunorderedlist.hasOwnProperty(s)){\r\n            delete customStyle[s];\r\n        }\r\n    }\r\n\r\n    me.ready(function () {\r\n        var customCss = [];\r\n        for(var p in customStyle){\r\n            if(p == 'dash' || p == 'dot'){\r\n                customCss.push('li.list-' + customStyle[p] + '{background-image:url(' + liiconpath +customStyle[p]+'.gif)}');\r\n                customCss.push('ul.custom_'+p+'{list-style:none;}ul.custom_'+p+' li{background-position:0 3px;background-repeat:no-repeat}');\r\n            }else{\r\n                for(var i= 0;i<99;i++){\r\n                    customCss.push('li.list-' + customStyle[p] + i + '{background-image:url(' + liiconpath + 'list-'+customStyle[p] + i + '.gif)}')\r\n                }\r\n                customCss.push('ol.custom_'+p+'{list-style:none;}ol.custom_'+p+' li{background-position:0 3px;background-repeat:no-repeat}');\r\n            }\r\n            switch(p){\r\n                case 'cn':\r\n                    customCss.push('li.list-'+p+'-paddingleft-1{padding-left:25px}');\r\n                    customCss.push('li.list-'+p+'-paddingleft-2{padding-left:40px}');\r\n                    customCss.push('li.list-'+p+'-paddingleft-3{padding-left:55px}');\r\n                    break;\r\n                case 'cn1':\r\n                    customCss.push('li.list-'+p+'-paddingleft-1{padding-left:30px}');\r\n                    customCss.push('li.list-'+p+'-paddingleft-2{padding-left:40px}');\r\n                    customCss.push('li.list-'+p+'-paddingleft-3{padding-left:55px}');\r\n                    break;\r\n                case 'cn2':\r\n                    customCss.push('li.list-'+p+'-paddingleft-1{padding-left:40px}');\r\n                    customCss.push('li.list-'+p+'-paddingleft-2{padding-left:55px}');\r\n                    customCss.push('li.list-'+p+'-paddingleft-3{padding-left:68px}');\r\n                    break;\r\n                case 'num':\r\n                case 'num1':\r\n                    customCss.push('li.list-'+p+'-paddingleft-1{padding-left:25px}');\r\n                    break;\r\n                case 'num2':\r\n                    customCss.push('li.list-'+p+'-paddingleft-1{padding-left:35px}');\r\n                    customCss.push('li.list-'+p+'-paddingleft-2{padding-left:40px}');\r\n                    break;\r\n                case 'dash':\r\n                    customCss.push('li.list-'+p+'-paddingleft{padding-left:35px}');\r\n                    break;\r\n                case 'dot':\r\n                    customCss.push('li.list-'+p+'-paddingleft{padding-left:20px}');\r\n            }\r\n        }\r\n        customCss.push('.list-paddingleft-1{padding-left:0}');\r\n        customCss.push('.list-paddingleft-2{padding-left:'+me.options.listDefaultPaddingLeft+'px}');\r\n        customCss.push('.list-paddingleft-3{padding-left:'+me.options.listDefaultPaddingLeft*2+'px}');\r\n        //如果不给宽度会在自定应样式里出现滚动条\r\n        utils.cssRule('list', 'ol,ul{margin:0;pading:0;'+(browser.ie ? '' : 'width:95%')+'}li{clear:both;}'+customCss.join('\\n'), me.document);\r\n    });\r\n    //单独处理剪切的问题\r\n    me.ready(function(){\r\n        domUtils.on(me.body,'cut',function(){\r\n            setTimeout(function(){\r\n                var rng = me.selection.getRange(),li;\r\n                //trace:3416\r\n                if(!rng.collapsed){\r\n                    if(li = domUtils.findParentByTagName(rng.startContainer,'li',true)){\r\n                        if(!li.nextSibling && domUtils.isEmptyBlock(li)){\r\n                            var pn = li.parentNode,node;\r\n                            if(node = pn.previousSibling){\r\n                                domUtils.remove(pn);\r\n                                rng.setStartAtLast(node).collapse(true);\r\n                                rng.select(true);\r\n                            }else if(node = pn.nextSibling){\r\n                                domUtils.remove(pn);\r\n                                rng.setStartAtFirst(node).collapse(true);\r\n                                rng.select(true);\r\n                            }else{\r\n                                var tmpNode = me.document.createElement('p');\r\n                                domUtils.fillNode(me.document,tmpNode);\r\n                                pn.parentNode.insertBefore(tmpNode,pn);\r\n                                domUtils.remove(pn);\r\n                                rng.setStart(tmpNode,0).collapse(true);\r\n                                rng.select(true);\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n\r\n            })\r\n        })\r\n    });\r\n\r\n    function getStyle(node){\r\n        var cls = node.className;\r\n        if(domUtils.hasClass(node,/custom_/)){\r\n            return cls.match(/custom_(\\w+)/)[1]\r\n        }\r\n        return domUtils.getStyle(node, 'list-style-type')\r\n\r\n    }\r\n\r\n    me.addListener('beforepaste',function(type,html){\r\n        var me = this,\r\n            rng = me.selection.getRange(),li;\r\n        var root = UE.htmlparser(html.html,true);\r\n        if(li = domUtils.findParentByTagName(rng.startContainer,'li',true)){\r\n            var list = li.parentNode,tagName = list.tagName == 'OL' ? 'ul':'ol';\r\n            utils.each(root.getNodesByTagName(tagName),function(n){\r\n                n.tagName = list.tagName;\r\n                n.setAttr();\r\n                if(n.parentNode === root){\r\n                    type = getStyle(list) || (list.tagName == 'OL' ? 'decimal' : 'disc')\r\n                }else{\r\n                    var className = n.parentNode.getAttr('class');\r\n                    if(className && /custom_/.test(className)){\r\n                        type = className.match(/custom_(\\w+)/)[1]\r\n                    }else{\r\n                        type = n.parentNode.getStyle('list-style-type');\r\n                    }\r\n                    if(!type){\r\n                        type = list.tagName == 'OL' ? 'decimal' : 'disc';\r\n                    }\r\n                }\r\n                var index = utils.indexOf(listStyle[list.tagName], type);\r\n                if(n.parentNode !== root)\r\n                    index = index + 1 == listStyle[list.tagName].length ? 0 : index + 1;\r\n                var currentStyle = listStyle[list.tagName][index];\r\n                if(customStyle[currentStyle]){\r\n                    n.setAttr('class', 'custom_' + currentStyle)\r\n\r\n                }else{\r\n                    n.setStyle('list-style-type',currentStyle)\r\n                }\r\n            })\r\n\r\n        }\r\n\r\n        html.html = root.toHtml();\r\n    });\r\n    //导出时，去掉p标签\r\n    me.getOpt('disablePInList') === true && me.addOutputRule(function(root){\r\n        utils.each(root.getNodesByTagName('li'),function(li){\r\n            var newChildrens = [],index=0;\r\n            utils.each(li.children,function(n){\r\n                if(n.tagName == 'p'){\r\n                    var tmpNode;\r\n                    while(tmpNode = n.children.pop()) {\r\n                        newChildrens.splice(index,0,tmpNode);\r\n                        tmpNode.parentNode = li;\r\n                        lastNode = tmpNode;\r\n                    }\r\n                    tmpNode = newChildrens[newChildrens.length-1];\r\n                    if(!tmpNode || tmpNode.type != 'element' || tmpNode.tagName != 'br'){\r\n                        var br = UE.uNode.createElement('br');\r\n                        br.parentNode = li;\r\n                        newChildrens.push(br);\r\n                    }\r\n\r\n                    index = newChildrens.length;\r\n                }\r\n            });\r\n            if(newChildrens.length){\r\n                li.children = newChildrens;\r\n            }\r\n        });\r\n    });\r\n    //进入编辑器的li要套p标签\r\n    me.addInputRule(function(root){\r\n        utils.each(root.getNodesByTagName('li'),function(li){\r\n            var tmpP = UE.uNode.createElement('p');\r\n            for(var i= 0,ci;ci=li.children[i];){\r\n                if(ci.type == 'text' || dtd.p[ci.tagName]){\r\n                    tmpP.appendChild(ci);\r\n                }else{\r\n                    if(tmpP.firstChild()){\r\n                        li.insertBefore(tmpP,ci);\r\n                        tmpP = UE.uNode.createElement('p');\r\n                        i = i + 2;\r\n                    }else{\r\n                        i++;\r\n                    }\r\n\r\n                }\r\n            }\r\n            if(tmpP.firstChild() && !tmpP.parentNode || !li.firstChild()){\r\n                li.appendChild(tmpP);\r\n            }\r\n            //trace:3357\r\n            //p不能为空\r\n            if (!tmpP.firstChild()) {\r\n                tmpP.innerHTML(browser.ie ? '&nbsp;' : '<br/>')\r\n            }\r\n            //去掉末尾的空白\r\n            var p = li.firstChild();\r\n            var lastChild = p.lastChild();\r\n            if(lastChild && lastChild.type == 'text' && /^\\s*$/.test(lastChild.data)){\r\n                p.removeChild(lastChild)\r\n            }\r\n        });\r\n        if(me.options.autoTransWordToList){\r\n            var orderlisttype = {\r\n                    'num1':/^\\d+\\)/,\r\n                    'decimal':/^\\d+\\./,\r\n                    'lower-alpha':/^[a-z]+\\)/,\r\n                    'upper-alpha':/^[A-Z]+\\./,\r\n                    'cn':/^[\\u4E00\\u4E8C\\u4E09\\u56DB\\u516d\\u4e94\\u4e03\\u516b\\u4e5d]+[\\u3001]/,\r\n                    'cn2':/^\\([\\u4E00\\u4E8C\\u4E09\\u56DB\\u516d\\u4e94\\u4e03\\u516b\\u4e5d]+\\)/\r\n                },\r\n                unorderlisttype = {\r\n                    'square':'n'\r\n                };\r\n            function checkListType(content,container){\r\n                var span = container.firstChild();\r\n                if(span &&  span.type == 'element' && span.tagName == 'span' && /Wingdings|Symbol/.test(span.getStyle('font-family'))){\r\n                    for(var p in unorderlisttype){\r\n                        if(unorderlisttype[p] == span.data){\r\n                            return p\r\n                        }\r\n                    }\r\n                    return 'disc'\r\n                }\r\n                for(var p in orderlisttype){\r\n                    if(orderlisttype[p].test(content)){\r\n                        return p;\r\n                    }\r\n                }\r\n\r\n            }\r\n            utils.each(root.getNodesByTagName('p'),function(node){\r\n                if(node.getAttr('class') != 'MsoListParagraph'){\r\n                    return\r\n                }\r\n\r\n                //word粘贴过来的会带有margin要去掉,但这样也可能会误命中一些央视\r\n                node.setStyle('margin','');\r\n                node.setStyle('margin-left','');\r\n                node.setAttr('class','');\r\n\r\n                function appendLi(list,p,type){\r\n                    if(list.tagName == 'ol'){\r\n                        if(browser.ie){\r\n                            var first = p.firstChild();\r\n                            if(first.type =='element' && first.tagName == 'span' && orderlisttype[type].test(first.innerText())){\r\n                                p.removeChild(first);\r\n                            }\r\n                        }else{\r\n                            p.innerHTML(p.innerHTML().replace(orderlisttype[type],''));\r\n                        }\r\n                    }else{\r\n                        p.removeChild(p.firstChild())\r\n                    }\r\n\r\n                    var li = UE.uNode.createElement('li');\r\n                    li.appendChild(p);\r\n                    list.appendChild(li);\r\n                }\r\n                var tmp = node,type,cacheNode = node;\r\n\r\n                if(node.parentNode.tagName != 'li' && (type = checkListType(node.innerText(),node))){\r\n\r\n                    var list = UE.uNode.createElement(me.options.insertorderedlist.hasOwnProperty(type) ? 'ol' : 'ul');\r\n                    if(customStyle[type]){\r\n                        list.setAttr('class','custom_'+type)\r\n                    }else{\r\n                        list.setStyle('list-style-type',type)\r\n                    }\r\n                    while(node && node.parentNode.tagName != 'li' && checkListType(node.innerText(),node)){\r\n                        tmp = node.nextSibling();\r\n                        if(!tmp){\r\n                            node.parentNode.insertBefore(list,node)\r\n                        }\r\n                        appendLi(list,node,type);\r\n                        node = tmp;\r\n                    }\r\n                    if(!list.parentNode && node && node.parentNode){\r\n                        node.parentNode.insertBefore(list,node)\r\n                    }\r\n                }\r\n                var span = cacheNode.firstChild();\r\n                if(span && span.type == 'element' && span.tagName == 'span' && /^\\s*(&nbsp;)+\\s*$/.test(span.innerText())){\r\n                    span.parentNode.removeChild(span)\r\n                }\r\n            })\r\n        }\r\n\r\n    });\r\n\r\n    //调整索引标签\r\n    me.addListener('contentchange',function(){\r\n        adjustListStyle(me.document)\r\n    });\r\n\r\n    function adjustListStyle(doc,ignore){\r\n        utils.each(domUtils.getElementsByTagName(doc,'ol ul'),function(node){\r\n\r\n            if(!domUtils.inDoc(node,doc))\r\n                return;\r\n\r\n            var parent = node.parentNode;\r\n            if(parent.tagName == node.tagName){\r\n                var nodeStyleType = getStyle(node) || (node.tagName == 'OL' ? 'decimal' : 'disc'),\r\n                    parentStyleType = getStyle(parent) || (parent.tagName == 'OL' ? 'decimal' : 'disc');\r\n                if(nodeStyleType == parentStyleType){\r\n                    var styleIndex = utils.indexOf(listStyle[node.tagName], nodeStyleType);\r\n                    styleIndex = styleIndex + 1 == listStyle[node.tagName].length ? 0 : styleIndex + 1;\r\n                    setListStyle(node,listStyle[node.tagName][styleIndex])\r\n                }\r\n\r\n            }\r\n            var index = 0,type = 2;\r\n            if( domUtils.hasClass(node,/custom_/)){\r\n                if(!(/[ou]l/i.test(parent.tagName) && domUtils.hasClass(parent,/custom_/))){\r\n                    type = 1;\r\n                }\r\n            }else{\r\n                if(/[ou]l/i.test(parent.tagName) && domUtils.hasClass(parent,/custom_/)){\r\n                    type = 3;\r\n                }\r\n            }\r\n\r\n            var style = domUtils.getStyle(node, 'list-style-type');\r\n            style && (node.style.cssText = 'list-style-type:' + style);\r\n            node.className = utils.trim(node.className.replace(/list-paddingleft-\\w+/,'')) + ' list-paddingleft-' + type;\r\n            utils.each(domUtils.getElementsByTagName(node,'li'),function(li){\r\n                li.style.cssText && (li.style.cssText = '');\r\n                if(!li.firstChild){\r\n                    domUtils.remove(li);\r\n                    return;\r\n                }\r\n                if(li.parentNode !== node){\r\n                    return;\r\n                }\r\n                index++;\r\n                if(domUtils.hasClass(node,/custom_/) ){\r\n                    var paddingLeft = 1,currentStyle = getStyle(node);\r\n                    if(node.tagName == 'OL'){\r\n                        if(currentStyle){\r\n                            switch(currentStyle){\r\n                                case 'cn' :\r\n                                case 'cn1':\r\n                                case 'cn2':\r\n                                    if(index > 10 && (index % 10 == 0 || index > 10 && index < 20)){\r\n                                        paddingLeft = 2\r\n                                    }else if(index > 20){\r\n                                        paddingLeft = 3\r\n                                    }\r\n                                    break;\r\n                                case 'num2' :\r\n                                    if(index > 9){\r\n                                        paddingLeft = 2\r\n                                    }\r\n                            }\r\n                        }\r\n                        li.className = 'list-'+customStyle[currentStyle]+ index + ' ' + 'list-'+currentStyle+'-paddingleft-' + paddingLeft;\r\n                    }else{\r\n                        li.className = 'list-'+customStyle[currentStyle]  + ' ' + 'list-'+currentStyle+'-paddingleft';\r\n                    }\r\n                }else{\r\n                    li.className = li.className.replace(/list-[\\w\\-]+/gi,'');\r\n                }\r\n                var className = li.getAttribute('class');\r\n                if(className !== null && !className.replace(/\\s/g,'')){\r\n                    domUtils.removeAttributes(li,'class')\r\n                }\r\n            });\r\n            !ignore && adjustList(node,node.tagName.toLowerCase(),getStyle(node)||domUtils.getStyle(node, 'list-style-type'),true);\r\n        })\r\n    }\r\n    function adjustList(list, tag, style,ignoreEmpty) {\r\n        var nextList = list.nextSibling;\r\n        if (nextList && nextList.nodeType == 1 && nextList.tagName.toLowerCase() == tag && (getStyle(nextList) || domUtils.getStyle(nextList, 'list-style-type') || (tag == 'ol' ? 'decimal' : 'disc')) == style) {\r\n            domUtils.moveChild(nextList, list);\r\n            if (nextList.childNodes.length == 0) {\r\n                domUtils.remove(nextList);\r\n            }\r\n        }\r\n        if(nextList && domUtils.isFillChar(nextList)){\r\n            domUtils.remove(nextList);\r\n        }\r\n        var preList = list.previousSibling;\r\n        if (preList && preList.nodeType == 1 && preList.tagName.toLowerCase() == tag && (getStyle(preList) || domUtils.getStyle(preList, 'list-style-type') || (tag == 'ol' ? 'decimal' : 'disc')) == style) {\r\n            domUtils.moveChild(list, preList);\r\n        }\r\n        if(preList && domUtils.isFillChar(preList)){\r\n            domUtils.remove(preList);\r\n        }\r\n        !ignoreEmpty && domUtils.isEmptyBlock(list) && domUtils.remove(list);\r\n        if(getStyle(list)){\r\n            adjustListStyle(list.ownerDocument,true)\r\n        }\r\n    }\r\n\r\n    function setListStyle(list,style){\r\n        if(customStyle[style]){\r\n            list.className = 'custom_' + style;\r\n        }\r\n        try{\r\n            domUtils.setStyle(list, 'list-style-type', style);\r\n        }catch(e){}\r\n    }\r\n    function clearEmptySibling(node) {\r\n        var tmpNode = node.previousSibling;\r\n        if (tmpNode && domUtils.isEmptyBlock(tmpNode)) {\r\n            domUtils.remove(tmpNode);\r\n        }\r\n        tmpNode = node.nextSibling;\r\n        if (tmpNode && domUtils.isEmptyBlock(tmpNode)) {\r\n            domUtils.remove(tmpNode);\r\n        }\r\n    }\r\n\r\n    me.addListener('keydown', function (type, evt) {\r\n        function preventAndSave() {\r\n            evt.preventDefault ? evt.preventDefault() : (evt.returnValue = false);\r\n            me.fireEvent('contentchange');\r\n            me.undoManger && me.undoManger.save();\r\n        }\r\n        function findList(node,filterFn){\r\n            while(node && !domUtils.isBody(node)){\r\n                if(filterFn(node)){\r\n                    return null\r\n                }\r\n                if(node.nodeType == 1 && /[ou]l/i.test(node.tagName)){\r\n                    return node;\r\n                }\r\n                node = node.parentNode;\r\n            }\r\n            return null;\r\n        }\r\n        var keyCode = evt.keyCode || evt.which;\r\n        if (keyCode == 13 && !evt.shiftKey) {//回车\r\n            var rng = me.selection.getRange(),\r\n                parent = domUtils.findParent(rng.startContainer,function(node){return domUtils.isBlockElm(node)},true),\r\n                li = domUtils.findParentByTagName(rng.startContainer,'li',true);\r\n            if(parent && parent.tagName != 'PRE' && !li){\r\n                var html = parent.innerHTML.replace(new RegExp(domUtils.fillChar, 'g'),'');\r\n                if(/^\\s*1\\s*\\.[^\\d]/.test(html)){\r\n                    parent.innerHTML = html.replace(/^\\s*1\\s*\\./,'');\r\n                    rng.setStartAtLast(parent).collapse(true).select();\r\n                    me.__hasEnterExecCommand = true;\r\n                    me.execCommand('insertorderedlist');\r\n                    me.__hasEnterExecCommand = false;\r\n                }\r\n            }\r\n            var range = me.selection.getRange(),\r\n                start = findList(range.startContainer,function (node) {\r\n                    return node.tagName == 'TABLE';\r\n                }),\r\n                end = range.collapsed ? start : findList(range.endContainer,function (node) {\r\n                    return node.tagName == 'TABLE';\r\n                });\r\n\r\n            if (start && end && start === end) {\r\n\r\n                if (!range.collapsed) {\r\n                    start = domUtils.findParentByTagName(range.startContainer, 'li', true);\r\n                    end = domUtils.findParentByTagName(range.endContainer, 'li', true);\r\n                    if (start && end && start === end) {\r\n                        range.deleteContents();\r\n                        li = domUtils.findParentByTagName(range.startContainer, 'li', true);\r\n                        if (li && domUtils.isEmptyBlock(li)) {\r\n\r\n                            pre = li.previousSibling;\r\n                            next = li.nextSibling;\r\n                            p = me.document.createElement('p');\r\n\r\n                            domUtils.fillNode(me.document, p);\r\n                            parentList = li.parentNode;\r\n                            if (pre && next) {\r\n                                range.setStart(next, 0).collapse(true).select(true);\r\n                                domUtils.remove(li);\r\n\r\n                            } else {\r\n                                if (!pre && !next || !pre) {\r\n\r\n                                    parentList.parentNode.insertBefore(p, parentList);\r\n\r\n\r\n                                } else {\r\n                                    li.parentNode.parentNode.insertBefore(p, parentList.nextSibling);\r\n                                }\r\n                                domUtils.remove(li);\r\n                                if (!parentList.firstChild) {\r\n                                    domUtils.remove(parentList);\r\n                                }\r\n                                range.setStart(p, 0).setCursor();\r\n\r\n\r\n                            }\r\n                            preventAndSave();\r\n                            return;\r\n\r\n                        }\r\n                    } else {\r\n                        var tmpRange = range.cloneRange(),\r\n                            bk = tmpRange.collapse(false).createBookmark();\r\n\r\n                        range.deleteContents();\r\n                        tmpRange.moveToBookmark(bk);\r\n                        var li = domUtils.findParentByTagName(tmpRange.startContainer, 'li', true);\r\n\r\n                        clearEmptySibling(li);\r\n                        tmpRange.select();\r\n                        preventAndSave();\r\n                        return;\r\n                    }\r\n                }\r\n\r\n\r\n                li = domUtils.findParentByTagName(range.startContainer, 'li', true);\r\n\r\n                if (li) {\r\n                    if (domUtils.isEmptyBlock(li)) {\r\n                        bk = range.createBookmark();\r\n                        var parentList = li.parentNode;\r\n                        if (li !== parentList.lastChild) {\r\n                            domUtils.breakParent(li, parentList);\r\n                            clearEmptySibling(li);\r\n                        } else {\r\n\r\n                            parentList.parentNode.insertBefore(li, parentList.nextSibling);\r\n                            if (domUtils.isEmptyNode(parentList)) {\r\n                                domUtils.remove(parentList);\r\n                            }\r\n                        }\r\n                        //嵌套不处理\r\n                        if (!dtd.$list[li.parentNode.tagName]) {\r\n\r\n                            if (!domUtils.isBlockElm(li.firstChild)) {\r\n                                p = me.document.createElement('p');\r\n                                li.parentNode.insertBefore(p, li);\r\n                                while (li.firstChild) {\r\n                                    p.appendChild(li.firstChild);\r\n                                }\r\n                                domUtils.remove(li);\r\n                            } else {\r\n                                domUtils.remove(li, true);\r\n                            }\r\n                        }\r\n                        range.moveToBookmark(bk).select();\r\n\r\n\r\n                    } else {\r\n                        var first = li.firstChild;\r\n                        if (!first || !domUtils.isBlockElm(first)) {\r\n                            var p = me.document.createElement('p');\r\n\r\n                            !li.firstChild && domUtils.fillNode(me.document, p);\r\n                            while (li.firstChild) {\r\n\r\n                                p.appendChild(li.firstChild);\r\n                            }\r\n                            li.appendChild(p);\r\n                            first = p;\r\n                        }\r\n\r\n                        var span = me.document.createElement('span');\r\n\r\n                        range.insertNode(span);\r\n                        domUtils.breakParent(span, li);\r\n\r\n                        var nextLi = span.nextSibling;\r\n                        first = nextLi.firstChild;\r\n\r\n                        if (!first) {\r\n                            p = me.document.createElement('p');\r\n\r\n                            domUtils.fillNode(me.document, p);\r\n                            nextLi.appendChild(p);\r\n                            first = p;\r\n                        }\r\n                        if (domUtils.isEmptyNode(first)) {\r\n                            first.innerHTML = '';\r\n                            domUtils.fillNode(me.document, first);\r\n                        }\r\n\r\n                        range.setStart(first, 0).collapse(true).shrinkBoundary().select();\r\n                        domUtils.remove(span);\r\n                        var pre = nextLi.previousSibling;\r\n                        if (pre && domUtils.isEmptyBlock(pre)) {\r\n                            pre.innerHTML = '<p></p>';\r\n                            domUtils.fillNode(me.document, pre.firstChild);\r\n                        }\r\n\r\n                    }\r\n//                        }\r\n                    preventAndSave();\r\n                }\r\n\r\n\r\n            }\r\n\r\n\r\n        }\r\n        if (keyCode == 8) {\r\n            //修中ie中li下的问题\r\n            range = me.selection.getRange();\r\n            if (range.collapsed && domUtils.isStartInblock(range)) {\r\n                tmpRange = range.cloneRange().trimBoundary();\r\n                li = domUtils.findParentByTagName(range.startContainer, 'li', true);\r\n                //要在li的最左边，才能处理\r\n                if (li && domUtils.isStartInblock(tmpRange)) {\r\n                    start = domUtils.findParentByTagName(range.startContainer, 'p', true);\r\n                    if (start && start !== li.firstChild) {\r\n                        var parentList = domUtils.findParentByTagName(start,['ol','ul']);\r\n                        domUtils.breakParent(start,parentList);\r\n                        clearEmptySibling(start);\r\n                        me.fireEvent('contentchange');\r\n                        range.setStart(start,0).setCursor(false,true);\r\n                        me.fireEvent('saveScene');\r\n                        domUtils.preventDefault(evt);\r\n                        return;\r\n                    }\r\n\r\n                    if (li && (pre = li.previousSibling)) {\r\n                        if (keyCode == 46 && li.childNodes.length) {\r\n                            return;\r\n                        }\r\n                        //有可能上边的兄弟节点是个2级菜单，要追加到2级菜单的最后的li\r\n                        if (dtd.$list[pre.tagName]) {\r\n                            pre = pre.lastChild;\r\n                        }\r\n                        me.undoManger && me.undoManger.save();\r\n                        first = li.firstChild;\r\n                        if (domUtils.isBlockElm(first)) {\r\n                            if (domUtils.isEmptyNode(first)) {\r\n//                                    range.setEnd(pre, pre.childNodes.length).shrinkBoundary().collapse().select(true);\r\n                                pre.appendChild(first);\r\n                                range.setStart(first, 0).setCursor(false, true);\r\n                                //first不是唯一的节点\r\n                                while (li.firstChild) {\r\n                                    pre.appendChild(li.firstChild);\r\n                                }\r\n                            } else {\r\n\r\n                                span = me.document.createElement('span');\r\n                                range.insertNode(span);\r\n                                //判断pre是否是空的节点,如果是<p><br/></p>类型的空节点，干掉p标签防止它占位\r\n                                if (domUtils.isEmptyBlock(pre)) {\r\n                                    pre.innerHTML = '';\r\n                                }\r\n                                domUtils.moveChild(li, pre);\r\n                                range.setStartBefore(span).collapse(true).select(true);\r\n\r\n                                domUtils.remove(span);\r\n\r\n                            }\r\n                        } else {\r\n                            if (domUtils.isEmptyNode(li)) {\r\n                                var p = me.document.createElement('p');\r\n                                pre.appendChild(p);\r\n                                range.setStart(p, 0).setCursor();\r\n//                                    range.setEnd(pre, pre.childNodes.length).shrinkBoundary().collapse().select(true);\r\n                            } else {\r\n                                range.setEnd(pre, pre.childNodes.length).collapse().select(true);\r\n                                while (li.firstChild) {\r\n                                    pre.appendChild(li.firstChild);\r\n                                }\r\n                            }\r\n                        }\r\n                        domUtils.remove(li);\r\n                        me.fireEvent('contentchange');\r\n                        me.fireEvent('saveScene');\r\n                        domUtils.preventDefault(evt);\r\n                        return;\r\n\r\n                    }\r\n                    //trace:980\r\n\r\n                    if (li && !li.previousSibling) {\r\n                        var parentList = li.parentNode;\r\n                        var bk = range.createBookmark();\r\n                        if(domUtils.isTagNode(parentList.parentNode,'ol ul')){\r\n                            parentList.parentNode.insertBefore(li,parentList);\r\n                            if(domUtils.isEmptyNode(parentList)){\r\n                                domUtils.remove(parentList)\r\n                            }\r\n                        }else{\r\n\r\n                            while(li.firstChild){\r\n                                parentList.parentNode.insertBefore(li.firstChild,parentList);\r\n                            }\r\n\r\n                            domUtils.remove(li);\r\n                            if(domUtils.isEmptyNode(parentList)){\r\n                                domUtils.remove(parentList)\r\n                            }\r\n\r\n                        }\r\n                        range.moveToBookmark(bk).setCursor(false,true);\r\n                        me.fireEvent('contentchange');\r\n                        me.fireEvent('saveScene');\r\n                        domUtils.preventDefault(evt);\r\n                        return;\r\n\r\n                    }\r\n\r\n\r\n                }\r\n\r\n\r\n            }\r\n\r\n        }\r\n    });\r\n\r\n    me.addListener('keyup',function(type, evt){\r\n        var keyCode = evt.keyCode || evt.which;\r\n        if (keyCode == 8) {\r\n            var rng = me.selection.getRange(),list;\r\n            if(list = domUtils.findParentByTagName(rng.startContainer,['ol', 'ul'],true)){\r\n                adjustList(list,list.tagName.toLowerCase(),getStyle(list)||domUtils.getComputedStyle(list,'list-style-type'),true)\r\n            }\r\n        }\r\n    });\r\n    //处理tab键\r\n    me.addListener('tabkeydown',function(){\r\n\r\n        var range = me.selection.getRange();\r\n\r\n        //控制级数\r\n        function checkLevel(li){\r\n            if(me.options.maxListLevel != -1){\r\n                var level = li.parentNode,levelNum = 0;\r\n                while(/[ou]l/i.test(level.tagName)){\r\n                    levelNum++;\r\n                    level = level.parentNode;\r\n                }\r\n                if(levelNum >= me.options.maxListLevel){\r\n                    return true;\r\n                }\r\n            }\r\n        }\r\n        //只以开始为准\r\n        //todo 后续改进\r\n        var li = domUtils.findParentByTagName(range.startContainer, 'li', true);\r\n        if(li){\r\n\r\n            var bk;\r\n            if(range.collapsed){\r\n                if(checkLevel(li))\r\n                    return true;\r\n                var parentLi = li.parentNode,\r\n                    list = me.document.createElement(parentLi.tagName),\r\n                    index = utils.indexOf(listStyle[list.tagName], getStyle(parentLi)||domUtils.getComputedStyle(parentLi, 'list-style-type'));\r\n                index = index + 1 == listStyle[list.tagName].length ? 0 : index + 1;\r\n                var currentStyle = listStyle[list.tagName][index];\r\n                setListStyle(list,currentStyle);\r\n                if(domUtils.isStartInblock(range)){\r\n                    me.fireEvent('saveScene');\r\n                    bk = range.createBookmark();\r\n                    parentLi.insertBefore(list, li);\r\n                    list.appendChild(li);\r\n                    adjustList(list,list.tagName.toLowerCase(),currentStyle);\r\n                    me.fireEvent('contentchange');\r\n                    range.moveToBookmark(bk).select(true);\r\n                    return true;\r\n                }\r\n            }else{\r\n                me.fireEvent('saveScene');\r\n                bk = range.createBookmark();\r\n                for(var i= 0,closeList,parents = domUtils.findParents(li),ci;ci=parents[i++];){\r\n                    if(domUtils.isTagNode(ci,'ol ul')){\r\n                        closeList = ci;\r\n                        break;\r\n                    }\r\n                }\r\n                var current = li;\r\n                if(bk.end){\r\n                    while(current && !(domUtils.getPosition(current, bk.end) & domUtils.POSITION_FOLLOWING)){\r\n                        if(checkLevel(current)){\r\n                            current = domUtils.getNextDomNode(current,false,null,function(node){return node !== closeList});\r\n                            continue;\r\n                        }\r\n                        var parentLi = current.parentNode,\r\n                            list = me.document.createElement(parentLi.tagName),\r\n                            index = utils.indexOf(listStyle[list.tagName], getStyle(parentLi)||domUtils.getComputedStyle(parentLi, 'list-style-type'));\r\n                        var currentIndex = index + 1 == listStyle[list.tagName].length ? 0 : index + 1;\r\n                        var currentStyle = listStyle[list.tagName][currentIndex];\r\n                        setListStyle(list,currentStyle);\r\n                        parentLi.insertBefore(list, current);\r\n                        while(current && !(domUtils.getPosition(current, bk.end) & domUtils.POSITION_FOLLOWING)){\r\n                            li = current.nextSibling;\r\n                            list.appendChild(current);\r\n                            if(!li || domUtils.isTagNode(li,'ol ul')){\r\n                                if(li){\r\n                                    while(li = li.firstChild){\r\n                                        if(li.tagName == 'LI'){\r\n                                            break;\r\n                                        }\r\n                                    }\r\n                                }else{\r\n                                    li = domUtils.getNextDomNode(current,false,null,function(node){return node !== closeList});\r\n                                }\r\n                                break;\r\n                            }\r\n                            current = li;\r\n                        }\r\n                        adjustList(list,list.tagName.toLowerCase(),currentStyle);\r\n                        current = li;\r\n                    }\r\n                }\r\n                me.fireEvent('contentchange');\r\n                range.moveToBookmark(bk).select();\r\n                return true;\r\n            }\r\n        }\r\n\r\n    });\r\n    function getLi(start){\r\n        while(start && !domUtils.isBody(start)){\r\n            if(start.nodeName == 'TABLE'){\r\n                return null;\r\n            }\r\n            if(start.nodeName == 'LI'){\r\n                return start\r\n            }\r\n            start = start.parentNode;\r\n        }\r\n    }\r\n\r\n    /**\r\n     * 有序列表，与“insertunorderedlist”命令互斥\r\n     * @command insertorderedlist\r\n     * @method execCommand\r\n     * @param { String } command 命令字符串\r\n     * @param { String } style 插入的有序列表类型，值为：decimal,lower-alpha,lower-roman,upper-alpha,upper-roman,cn,cn1,cn2,num,num1,num2\r\n     * @example\r\n     * ```javascript\r\n     * editor.execCommand( 'insertorderedlist','decimal');\r\n     * ```\r\n     */\r\n    /**\r\n     * 查询当前选区内容是否有序列表\r\n     * @command insertorderedlist\r\n     * @method queryCommandState\r\n     * @param { String } cmd 命令字符串\r\n     * @return { int } 如果当前选区是有序列表返回1，否则返回0\r\n     * @example\r\n     * ```javascript\r\n     * editor.queryCommandState( 'insertorderedlist' );\r\n     * ```\r\n     */\r\n    /**\r\n     * 查询当前选区内容是否有序列表\r\n     * @command insertorderedlist\r\n     * @method queryCommandValue\r\n     * @param { String } cmd 命令字符串\r\n     * @return { String } 返回当前有序列表的类型，值为null或decimal,lower-alpha,lower-roman,upper-alpha,upper-roman,cn,cn1,cn2,num,num1,num2\r\n     * @example\r\n     * ```javascript\r\n     * editor.queryCommandValue( 'insertorderedlist' );\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 无序列表，与“insertorderedlist”命令互斥\r\n     * @command insertunorderedlist\r\n     * @method execCommand\r\n     * @param { String } command 命令字符串\r\n     * @param { String } style 插入的无序列表类型，值为：circle,disc,square,dash,dot\r\n     * @example\r\n     * ```javascript\r\n     * editor.execCommand( 'insertunorderedlist','circle');\r\n     * ```\r\n     */\r\n    /**\r\n     * 查询当前是否有word文档粘贴进来的图片\r\n     * @command insertunorderedlist\r\n     * @method insertunorderedlist\r\n     * @param { String } command 命令字符串\r\n     * @return { int } 如果当前选区是无序列表返回1，否则返回0\r\n     * @example\r\n     * ```javascript\r\n     * editor.queryCommandState( 'insertunorderedlist' );\r\n     * ```\r\n     */\r\n    /**\r\n     * 查询当前选区内容是否有序列表\r\n     * @command insertunorderedlist\r\n     * @method queryCommandValue\r\n     * @param { String } command 命令字符串\r\n     * @return { String } 返回当前无序列表的类型，值为null或circle,disc,square,dash,dot\r\n     * @example\r\n     * ```javascript\r\n     * editor.queryCommandValue( 'insertunorderedlist' );\r\n     * ```\r\n     */\r\n\r\n    me.commands['insertorderedlist'] =\r\n    me.commands['insertunorderedlist'] = {\r\n            execCommand:function (command, style) {\r\n\r\n                if (!style) {\r\n                    style = command.toLowerCase() == 'insertorderedlist' ? 'decimal' : 'disc';\r\n                }\r\n                var me = this,\r\n                    range = this.selection.getRange(),\r\n                    filterFn = function (node) {\r\n                        return   node.nodeType == 1 ? node.tagName.toLowerCase() != 'br' : !domUtils.isWhitespace(node);\r\n                    },\r\n                    tag = command.toLowerCase() == 'insertorderedlist' ? 'ol' : 'ul',\r\n                    frag = me.document.createDocumentFragment();\r\n                //去掉是因为会出现选到末尾，导致adjustmentBoundary缩到ol/ul的位置\r\n                //range.shrinkBoundary();//.adjustmentBoundary();\r\n                range.adjustmentBoundary().shrinkBoundary();\r\n                var bko = range.createBookmark(true),\r\n                    start = getLi(me.document.getElementById(bko.start)),\r\n                    modifyStart = 0,\r\n                    end =  getLi(me.document.getElementById(bko.end)),\r\n                    modifyEnd = 0,\r\n                    startParent, endParent,\r\n                    list, tmp;\r\n\r\n                if (start || end) {\r\n                    start && (startParent = start.parentNode);\r\n                    if (!bko.end) {\r\n                        end = start;\r\n                    }\r\n                    end && (endParent = end.parentNode);\r\n\r\n                    if (startParent === endParent) {\r\n                        while (start !== end) {\r\n                            tmp = start;\r\n                            start = start.nextSibling;\r\n                            if (!domUtils.isBlockElm(tmp.firstChild)) {\r\n                                var p = me.document.createElement('p');\r\n                                while (tmp.firstChild) {\r\n                                    p.appendChild(tmp.firstChild);\r\n                                }\r\n                                tmp.appendChild(p);\r\n                            }\r\n                            frag.appendChild(tmp);\r\n                        }\r\n                        tmp = me.document.createElement('span');\r\n                        startParent.insertBefore(tmp, end);\r\n                        if (!domUtils.isBlockElm(end.firstChild)) {\r\n                            p = me.document.createElement('p');\r\n                            while (end.firstChild) {\r\n                                p.appendChild(end.firstChild);\r\n                            }\r\n                            end.appendChild(p);\r\n                        }\r\n                        frag.appendChild(end);\r\n                        domUtils.breakParent(tmp, startParent);\r\n                        if (domUtils.isEmptyNode(tmp.previousSibling)) {\r\n                            domUtils.remove(tmp.previousSibling);\r\n                        }\r\n                        if (domUtils.isEmptyNode(tmp.nextSibling)) {\r\n                            domUtils.remove(tmp.nextSibling)\r\n                        }\r\n                        var nodeStyle = getStyle(startParent) || domUtils.getComputedStyle(startParent, 'list-style-type') || (command.toLowerCase() == 'insertorderedlist' ? 'decimal' : 'disc');\r\n                        if (startParent.tagName.toLowerCase() == tag && nodeStyle == style) {\r\n                            for (var i = 0, ci, tmpFrag = me.document.createDocumentFragment(); ci = frag.firstChild;) {\r\n                                if(domUtils.isTagNode(ci,'ol ul')){\r\n//                                  删除时，子列表不处理\r\n//                                  utils.each(domUtils.getElementsByTagName(ci,'li'),function(li){\r\n//                                        while(li.firstChild){\r\n//                                            tmpFrag.appendChild(li.firstChild);\r\n//                                        }\r\n//\r\n//                                    });\r\n                                    tmpFrag.appendChild(ci);\r\n                                }else{\r\n                                    while (ci.firstChild) {\r\n\r\n                                        tmpFrag.appendChild(ci.firstChild);\r\n                                        domUtils.remove(ci);\r\n                                    }\r\n                                }\r\n\r\n                            }\r\n                            tmp.parentNode.insertBefore(tmpFrag, tmp);\r\n                        } else {\r\n                            list = me.document.createElement(tag);\r\n                            setListStyle(list,style);\r\n                            list.appendChild(frag);\r\n                            tmp.parentNode.insertBefore(list, tmp);\r\n                        }\r\n\r\n                        domUtils.remove(tmp);\r\n                        list && adjustList(list, tag, style);\r\n                        range.moveToBookmark(bko).select();\r\n                        return;\r\n                    }\r\n                    //开始\r\n                    if (start) {\r\n                        while (start) {\r\n                            tmp = start.nextSibling;\r\n                            if (domUtils.isTagNode(start, 'ol ul')) {\r\n                                frag.appendChild(start);\r\n                            } else {\r\n                                var tmpfrag = me.document.createDocumentFragment(),\r\n                                    hasBlock = 0;\r\n                                while (start.firstChild) {\r\n                                    if (domUtils.isBlockElm(start.firstChild)) {\r\n                                        hasBlock = 1;\r\n                                    }\r\n                                    tmpfrag.appendChild(start.firstChild);\r\n                                }\r\n                                if (!hasBlock) {\r\n                                    var tmpP = me.document.createElement('p');\r\n                                    tmpP.appendChild(tmpfrag);\r\n                                    frag.appendChild(tmpP);\r\n                                } else {\r\n                                    frag.appendChild(tmpfrag);\r\n                                }\r\n                                domUtils.remove(start);\r\n                            }\r\n\r\n                            start = tmp;\r\n                        }\r\n                        startParent.parentNode.insertBefore(frag, startParent.nextSibling);\r\n                        if (domUtils.isEmptyNode(startParent)) {\r\n                            range.setStartBefore(startParent);\r\n                            domUtils.remove(startParent);\r\n                        } else {\r\n                            range.setStartAfter(startParent);\r\n                        }\r\n                        modifyStart = 1;\r\n                    }\r\n\r\n                    if (end && domUtils.inDoc(endParent, me.document)) {\r\n                        //结束\r\n                        start = endParent.firstChild;\r\n                        while (start && start !== end) {\r\n                            tmp = start.nextSibling;\r\n                            if (domUtils.isTagNode(start, 'ol ul')) {\r\n                                frag.appendChild(start);\r\n                            } else {\r\n                                tmpfrag = me.document.createDocumentFragment();\r\n                                hasBlock = 0;\r\n                                while (start.firstChild) {\r\n                                    if (domUtils.isBlockElm(start.firstChild)) {\r\n                                        hasBlock = 1;\r\n                                    }\r\n                                    tmpfrag.appendChild(start.firstChild);\r\n                                }\r\n                                if (!hasBlock) {\r\n                                    tmpP = me.document.createElement('p');\r\n                                    tmpP.appendChild(tmpfrag);\r\n                                    frag.appendChild(tmpP);\r\n                                } else {\r\n                                    frag.appendChild(tmpfrag);\r\n                                }\r\n                                domUtils.remove(start);\r\n                            }\r\n                            start = tmp;\r\n                        }\r\n                        var tmpDiv = domUtils.createElement(me.document, 'div', {\r\n                            'tmpDiv':1\r\n                        });\r\n                        domUtils.moveChild(end, tmpDiv);\r\n\r\n                        frag.appendChild(tmpDiv);\r\n                        domUtils.remove(end);\r\n                        endParent.parentNode.insertBefore(frag, endParent);\r\n                        range.setEndBefore(endParent);\r\n                        if (domUtils.isEmptyNode(endParent)) {\r\n                            domUtils.remove(endParent);\r\n                        }\r\n\r\n                        modifyEnd = 1;\r\n                    }\r\n\r\n\r\n                }\r\n\r\n                if (!modifyStart) {\r\n                    range.setStartBefore(me.document.getElementById(bko.start));\r\n                }\r\n                if (bko.end && !modifyEnd) {\r\n                    range.setEndAfter(me.document.getElementById(bko.end));\r\n                }\r\n                range.enlarge(true, function (node) {\r\n                    return notExchange[node.tagName];\r\n                });\r\n\r\n                frag = me.document.createDocumentFragment();\r\n\r\n                var bk = range.createBookmark(),\r\n                    current = domUtils.getNextDomNode(bk.start, false, filterFn),\r\n                    tmpRange = range.cloneRange(),\r\n                    tmpNode,\r\n                    block = domUtils.isBlockElm;\r\n\r\n                while (current && current !== bk.end && (domUtils.getPosition(current, bk.end) & domUtils.POSITION_PRECEDING)) {\r\n\r\n                    if (current.nodeType == 3 || dtd.li[current.tagName]) {\r\n                        if (current.nodeType == 1 && dtd.$list[current.tagName]) {\r\n                            while (current.firstChild) {\r\n                                frag.appendChild(current.firstChild);\r\n                            }\r\n                            tmpNode = domUtils.getNextDomNode(current, false, filterFn);\r\n                            domUtils.remove(current);\r\n                            current = tmpNode;\r\n                            continue;\r\n\r\n                        }\r\n                        tmpNode = current;\r\n                        tmpRange.setStartBefore(current);\r\n\r\n                        while (current && current !== bk.end && (!block(current) || domUtils.isBookmarkNode(current) )) {\r\n                            tmpNode = current;\r\n                            current = domUtils.getNextDomNode(current, false, null, function (node) {\r\n                                return !notExchange[node.tagName];\r\n                            });\r\n                        }\r\n\r\n                        if (current && block(current)) {\r\n                            tmp = domUtils.getNextDomNode(tmpNode, false, filterFn);\r\n                            if (tmp && domUtils.isBookmarkNode(tmp)) {\r\n                                current = domUtils.getNextDomNode(tmp, false, filterFn);\r\n                                tmpNode = tmp;\r\n                            }\r\n                        }\r\n                        tmpRange.setEndAfter(tmpNode);\r\n\r\n                        current = domUtils.getNextDomNode(tmpNode, false, filterFn);\r\n\r\n                        var li = range.document.createElement('li');\r\n\r\n                        li.appendChild(tmpRange.extractContents());\r\n                        if(domUtils.isEmptyNode(li)){\r\n                            var tmpNode = range.document.createElement('p');\r\n                            while(li.firstChild){\r\n                                tmpNode.appendChild(li.firstChild)\r\n                            }\r\n                            li.appendChild(tmpNode);\r\n                        }\r\n                        frag.appendChild(li);\r\n                    } else {\r\n                        current = domUtils.getNextDomNode(current, true, filterFn);\r\n                    }\r\n                }\r\n                range.moveToBookmark(bk).collapse(true);\r\n                list = me.document.createElement(tag);\r\n                setListStyle(list,style);\r\n                list.appendChild(frag);\r\n                range.insertNode(list);\r\n                //当前list上下看能否合并\r\n                adjustList(list, tag, style);\r\n                //去掉冗余的tmpDiv\r\n                for (var i = 0, ci, tmpDivs = domUtils.getElementsByTagName(list, 'div'); ci = tmpDivs[i++];) {\r\n                    if (ci.getAttribute('tmpDiv')) {\r\n                        domUtils.remove(ci, true)\r\n                    }\r\n                }\r\n                range.moveToBookmark(bko).select();\r\n\r\n            },\r\n            queryCommandState:function (command) {\r\n                var tag = command.toLowerCase() == 'insertorderedlist' ? 'ol' : 'ul';\r\n                var path = this.selection.getStartElementPath();\r\n                for(var i= 0,ci;ci = path[i++];){\r\n                    if(ci.nodeName == 'TABLE'){\r\n                        return 0\r\n                    }\r\n                    if(tag == ci.nodeName.toLowerCase()){\r\n                        return 1\r\n                    };\r\n                }\r\n                return 0;\r\n\r\n            },\r\n            queryCommandValue:function (command) {\r\n                var tag = command.toLowerCase() == 'insertorderedlist' ? 'ol' : 'ul';\r\n                var path = this.selection.getStartElementPath(),\r\n                    node;\r\n                for(var i= 0,ci;ci = path[i++];){\r\n                    if(ci.nodeName == 'TABLE'){\r\n                        node = null;\r\n                        break;\r\n                    }\r\n                    if(tag == ci.nodeName.toLowerCase()){\r\n                        node = ci;\r\n                        break;\r\n                    };\r\n                }\r\n                return node ? getStyle(node) || domUtils.getComputedStyle(node, 'list-style-type') : null;\r\n            }\r\n        };\r\n};\r\n\r\n\r\n\r\n// plugins/source.js\r\n/**\r\n * 源码编辑插件\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n(function (){\r\n    var sourceEditors = {\r\n        textarea: function (editor, holder){\r\n            var textarea = holder.ownerDocument.createElement('textarea');\r\n            textarea.style.cssText = 'position:absolute;resize:none;width:100%;height:100%;border:0;padding:0;margin:0;overflow-y:auto;';\r\n            // todo: IE下只有onresize属性可用... 很纠结\r\n            if (browser.ie && browser.version < 8) {\r\n                textarea.style.width = holder.offsetWidth + 'px';\r\n                textarea.style.height = holder.offsetHeight + 'px';\r\n                holder.onresize = function (){\r\n                    textarea.style.width = holder.offsetWidth + 'px';\r\n                    textarea.style.height = holder.offsetHeight + 'px';\r\n                };\r\n            }\r\n            holder.appendChild(textarea);\r\n            return {\r\n                setContent: function (content){\r\n                    textarea.value = content;\r\n                },\r\n                getContent: function (){\r\n                    return textarea.value;\r\n                },\r\n                select: function (){\r\n                    var range;\r\n                    if (browser.ie) {\r\n                        range = textarea.createTextRange();\r\n                        range.collapse(true);\r\n                        range.select();\r\n                    } else {\r\n                        //todo: chrome下无法设置焦点\r\n                        textarea.setSelectionRange(0, 0);\r\n                        textarea.focus();\r\n                    }\r\n                },\r\n                dispose: function (){\r\n                    holder.removeChild(textarea);\r\n                    // todo\r\n                    holder.onresize = null;\r\n                    textarea = null;\r\n                    holder = null;\r\n                }\r\n            };\r\n        },\r\n        codemirror: function (editor, holder){\r\n\r\n            var codeEditor = window.CodeMirror(holder, {\r\n                mode: \"text/html\",\r\n                tabMode: \"indent\",\r\n                lineNumbers: true,\r\n                lineWrapping:true\r\n            });\r\n            var dom = codeEditor.getWrapperElement();\r\n            dom.style.cssText = 'position:absolute;left:0;top:0;width:100%;height:100%;font-family:consolas,\"Courier new\",monospace;font-size:13px;';\r\n            codeEditor.getScrollerElement().style.cssText = 'position:absolute;left:0;top:0;width:100%;height:100%;';\r\n            codeEditor.refresh();\r\n            return {\r\n                getCodeMirror:function(){\r\n                    return codeEditor;\r\n                },\r\n                setContent: function (content){\r\n                    codeEditor.setValue(content);\r\n                },\r\n                getContent: function (){\r\n                    return codeEditor.getValue();\r\n                },\r\n                select: function (){\r\n                    codeEditor.focus();\r\n                },\r\n                dispose: function (){\r\n                    holder.removeChild(dom);\r\n                    dom = null;\r\n                    codeEditor = null;\r\n                }\r\n            };\r\n        }\r\n    };\r\n\r\n    UE.plugins['source'] = function (){\r\n        var me = this;\r\n        var opt = this.options;\r\n        var sourceMode = false;\r\n        var sourceEditor;\r\n        var orgSetContent;\r\n        opt.sourceEditor = browser.ie  ? 'textarea' : (opt.sourceEditor || 'codemirror');\r\n\r\n        me.setOpt({\r\n            sourceEditorFirst:false\r\n        });\r\n        function createSourceEditor(holder){\r\n            return sourceEditors[opt.sourceEditor == 'codemirror' && window.CodeMirror ? 'codemirror' : 'textarea'](me, holder);\r\n        }\r\n\r\n        var bakCssText;\r\n        //解决在源码模式下getContent不能得到最新的内容问题\r\n        var oldGetContent,\r\n            bakAddress;\r\n\r\n        /**\r\n         * 切换源码模式和编辑模式\r\n         * @command source\r\n         * @method execCommand\r\n         * @param { String } cmd 命令字符串\r\n         * @example\r\n         * ```javascript\r\n         * editor.execCommand( 'source');\r\n         * ```\r\n         */\r\n\r\n        /**\r\n         * 查询当前编辑区域的状态是源码模式还是可视化模式\r\n         * @command source\r\n         * @method queryCommandState\r\n         * @param { String } cmd 命令字符串\r\n         * @return { int } 如果当前是源码编辑模式，返回1，否则返回0\r\n         * @example\r\n         * ```javascript\r\n         * editor.queryCommandState( 'source' );\r\n         * ```\r\n         */\r\n\r\n        me.commands['source'] = {\r\n            execCommand: function (){\r\n\r\n                sourceMode = !sourceMode;\r\n                if (sourceMode) {\r\n                    bakAddress = me.selection.getRange().createAddress(false,true);\r\n                    me.undoManger && me.undoManger.save(true);\r\n                    if(browser.gecko){\r\n                        me.body.contentEditable = false;\r\n                    }\r\n\r\n                    bakCssText = me.iframe.style.cssText;\r\n                    me.iframe.style.cssText += 'position:absolute;left:-32768px;top:-32768px;';\r\n\r\n\r\n                    me.fireEvent('beforegetcontent');\r\n                    var root = UE.htmlparser(me.body.innerHTML);\r\n                    me.filterOutputRule(root);\r\n                    root.traversal(function (node) {\r\n                        if (node.type == 'element') {\r\n                            switch (node.tagName) {\r\n                                case 'td':\r\n                                case 'th':\r\n                                case 'caption':\r\n                                if(node.children && node.children.length == 1){\r\n                                    if(node.firstChild().tagName == 'br' ){\r\n                                        node.removeChild(node.firstChild())\r\n                                    }\r\n                                };\r\n                                break;\r\n                                case 'pre':\r\n                                    node.innerText(node.innerText().replace(/&nbsp;/g,' '))\r\n\r\n                            }\r\n                        }\r\n                    });\r\n\r\n                    me.fireEvent('aftergetcontent');\r\n\r\n                    var content = root.toHtml(true);\r\n\r\n                    sourceEditor = createSourceEditor(me.iframe.parentNode);\r\n\r\n                    sourceEditor.setContent(content);\r\n\r\n                    orgSetContent = me.setContent;\r\n\r\n                    me.setContent = function(html){\r\n                        //这里暂时不触发事件，防止报错\r\n                        var root = UE.htmlparser(html);\r\n                        me.filterInputRule(root);\r\n                        html = root.toHtml();\r\n                        sourceEditor.setContent(html);\r\n                    };\r\n\r\n                    setTimeout(function (){\r\n                        sourceEditor.select();\r\n                        me.addListener('fullscreenchanged', function(){\r\n                            try{\r\n                                sourceEditor.getCodeMirror().refresh()\r\n                            }catch(e){}\r\n                        });\r\n                    });\r\n\r\n                    //重置getContent，源码模式下取值也能是最新的数据\r\n                    oldGetContent = me.getContent;\r\n                    me.getContent = function (){\r\n                        return sourceEditor.getContent() || '<p>' + (browser.ie ? '' : '<br/>')+'</p>';\r\n                    };\r\n                } else {\r\n                    me.iframe.style.cssText = bakCssText;\r\n                    var cont = sourceEditor.getContent() || '<p>' + (browser.ie ? '' : '<br/>')+'</p>';\r\n                    //处理掉block节点前后的空格,有可能会误命中，暂时不考虑\r\n                    cont = cont.replace(new RegExp('[\\\\r\\\\t\\\\n ]*<\\/?(\\\\w+)\\\\s*(?:[^>]*)>','g'), function(a,b){\r\n                        if(b && !dtd.$inlineWithA[b.toLowerCase()]){\r\n                            return a.replace(/(^[\\n\\r\\t ]*)|([\\n\\r\\t ]*$)/g,'');\r\n                        }\r\n                        return a.replace(/(^[\\n\\r\\t]*)|([\\n\\r\\t]*$)/g,'')\r\n                    });\r\n\r\n                    me.setContent = orgSetContent;\r\n\r\n                    me.setContent(cont);\r\n                    sourceEditor.dispose();\r\n                    sourceEditor = null;\r\n                    //还原getContent方法\r\n                    me.getContent = oldGetContent;\r\n                    var first = me.body.firstChild;\r\n                    //trace:1106 都删除空了，下边会报错，所以补充一个p占位\r\n                    if(!first){\r\n                        me.body.innerHTML = '<p>'+(browser.ie?'':'<br/>')+'</p>';\r\n                        first = me.body.firstChild;\r\n                    }\r\n\r\n\r\n                    //要在ifm为显示时ff才能取到selection,否则报错\r\n                    //这里不能比较位置了\r\n                    me.undoManger && me.undoManger.save(true);\r\n\r\n                    if(browser.gecko){\r\n\r\n                        var input = document.createElement('input');\r\n                        input.style.cssText = 'position:absolute;left:0;top:-32768px';\r\n\r\n                        document.body.appendChild(input);\r\n\r\n                        me.body.contentEditable = false;\r\n                        setTimeout(function(){\r\n                            domUtils.setViewportOffset(input, { left: -32768, top: 0 });\r\n                            input.focus();\r\n                            setTimeout(function(){\r\n                                me.body.contentEditable = true;\r\n                                me.selection.getRange().moveToAddress(bakAddress).select(true);\r\n                                domUtils.remove(input);\r\n                            });\r\n\r\n                        });\r\n                    }else{\r\n                        //ie下有可能报错，比如在代码顶头的情况\r\n                        try{\r\n                            me.selection.getRange().moveToAddress(bakAddress).select(true);\r\n                        }catch(e){}\r\n\r\n                    }\r\n                }\r\n                this.fireEvent('sourcemodechanged', sourceMode);\r\n            },\r\n            queryCommandState: function (){\r\n                return sourceMode|0;\r\n            },\r\n            notNeedUndo : 1\r\n        };\r\n        var oldQueryCommandState = me.queryCommandState;\r\n\r\n        me.queryCommandState = function (cmdName){\r\n            cmdName = cmdName.toLowerCase();\r\n            if (sourceMode) {\r\n                //源码模式下可以开启的命令\r\n                return cmdName in {\r\n                    'source' : 1,\r\n                    'fullscreen' : 1\r\n                } ? 1 : -1\r\n            }\r\n            return oldQueryCommandState.apply(this, arguments);\r\n        };\r\n\r\n        if(opt.sourceEditor == \"codemirror\"){\r\n\r\n            me.addListener(\"ready\",function(){\r\n                utils.loadFile(document,{\r\n                    src : opt.codeMirrorJsUrl || opt.UEDITOR_HOME_URL + \"third-party/codemirror/codemirror.js\",\r\n                    tag : \"script\",\r\n                    type : \"text/javascript\",\r\n                    defer : \"defer\"\r\n                },function(){\r\n                    if(opt.sourceEditorFirst){\r\n                        setTimeout(function(){\r\n                            me.execCommand(\"source\");\r\n                        },0);\r\n                    }\r\n                });\r\n                utils.loadFile(document,{\r\n                    tag : \"link\",\r\n                    rel : \"stylesheet\",\r\n                    type : \"text/css\",\r\n                    href : opt.codeMirrorCssUrl || opt.UEDITOR_HOME_URL + \"third-party/codemirror/codemirror.css\"\r\n                });\r\n\r\n            });\r\n        }\r\n\r\n    };\r\n\r\n})();\r\n\r\n// plugins/enterkey.js\r\n///import core\r\n///import plugins/undo.js\r\n///commands 设置回车标签p或br\r\n///commandsName  EnterKey\r\n///commandsTitle  设置回车标签p或br\r\n/**\r\n * @description 处理回车\r\n * @author zhanyi\r\n */\r\nUE.plugins['enterkey'] = function() {\r\n    var hTag,\r\n        me = this,\r\n        tag = me.options.enterTag;\r\n    me.addListener('keyup', function(type, evt) {\r\n\r\n        var keyCode = evt.keyCode || evt.which;\r\n        if (keyCode == 13) {\r\n            var range = me.selection.getRange(),\r\n                start = range.startContainer,\r\n                doSave;\r\n\r\n            //修正在h1-h6里边回车后不能嵌套p的问题\r\n            if (!browser.ie) {\r\n\r\n                if (/h\\d/i.test(hTag)) {\r\n                    if (browser.gecko) {\r\n                        var h = domUtils.findParentByTagName(start, [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6','blockquote','caption','table'], true);\r\n                        if (!h) {\r\n                            me.document.execCommand('formatBlock', false, '<p>');\r\n                            doSave = 1;\r\n                        }\r\n                    } else {\r\n                        //chrome remove div\r\n                        if (start.nodeType == 1) {\r\n                            var tmp = me.document.createTextNode(''),div;\r\n                            range.insertNode(tmp);\r\n                            div = domUtils.findParentByTagName(tmp, 'div', true);\r\n                            if (div) {\r\n                                var p = me.document.createElement('p');\r\n                                while (div.firstChild) {\r\n                                    p.appendChild(div.firstChild);\r\n                                }\r\n                                div.parentNode.insertBefore(p, div);\r\n                                domUtils.remove(div);\r\n                                range.setStartBefore(tmp).setCursor();\r\n                                doSave = 1;\r\n                            }\r\n                            domUtils.remove(tmp);\r\n\r\n                        }\r\n                    }\r\n\r\n                    if (me.undoManger && doSave) {\r\n                        me.undoManger.save();\r\n                    }\r\n                }\r\n                //没有站位符，会出现多行的问题\r\n                browser.opera &&  range.select();\r\n            }else{\r\n                me.fireEvent('saveScene',true,true)\r\n            }\r\n        }\r\n    });\r\n\r\n    me.addListener('keydown', function(type, evt) {\r\n        var keyCode = evt.keyCode || evt.which;\r\n        if (keyCode == 13) {//回车\r\n            if(me.fireEvent('beforeenterkeydown')){\r\n                domUtils.preventDefault(evt);\r\n                return;\r\n            }\r\n            me.fireEvent('saveScene',true,true);\r\n            hTag = '';\r\n\r\n\r\n            var range = me.selection.getRange();\r\n\r\n            if (!range.collapsed) {\r\n                //跨td不能删\r\n                var start = range.startContainer,\r\n                    end = range.endContainer,\r\n                    startTd = domUtils.findParentByTagName(start, 'td', true),\r\n                    endTd = domUtils.findParentByTagName(end, 'td', true);\r\n                if (startTd && endTd && startTd !== endTd || !startTd && endTd || startTd && !endTd) {\r\n                    evt.preventDefault ? evt.preventDefault() : ( evt.returnValue = false);\r\n                    return;\r\n                }\r\n            }\r\n            if (tag == 'p') {\r\n\r\n\r\n                if (!browser.ie) {\r\n\r\n                    start = domUtils.findParentByTagName(range.startContainer, ['ol','ul','p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6','blockquote','caption'], true);\r\n\r\n                    //opera下执行formatblock会在table的场景下有问题，回车在opera原生支持很好，所以暂时在opera去掉调用这个原生的command\r\n                    //trace:2431\r\n                    if (!start && !browser.opera) {\r\n\r\n                        me.document.execCommand('formatBlock', false, '<p>');\r\n\r\n                        if (browser.gecko) {\r\n                            range = me.selection.getRange();\r\n                            start = domUtils.findParentByTagName(range.startContainer, 'p', true);\r\n                            start && domUtils.removeDirtyAttr(start);\r\n                        }\r\n\r\n\r\n                    } else {\r\n                        hTag = start.tagName;\r\n                        start.tagName.toLowerCase() == 'p' && browser.gecko && domUtils.removeDirtyAttr(start);\r\n                    }\r\n\r\n                }\r\n\r\n            } else {\r\n                evt.preventDefault ? evt.preventDefault() : ( evt.returnValue = false);\r\n\r\n                if (!range.collapsed) {\r\n                    range.deleteContents();\r\n                    start = range.startContainer;\r\n                    if (start.nodeType == 1 && (start = start.childNodes[range.startOffset])) {\r\n                        while (start.nodeType == 1) {\r\n                            if (dtd.$empty[start.tagName]) {\r\n                                range.setStartBefore(start).setCursor();\r\n                                if (me.undoManger) {\r\n                                    me.undoManger.save();\r\n                                }\r\n                                return false;\r\n                            }\r\n                            if (!start.firstChild) {\r\n                                var br = range.document.createElement('br');\r\n                                start.appendChild(br);\r\n                                range.setStart(start, 0).setCursor();\r\n                                if (me.undoManger) {\r\n                                    me.undoManger.save();\r\n                                }\r\n                                return false;\r\n                            }\r\n                            start = start.firstChild;\r\n                        }\r\n                        if (start === range.startContainer.childNodes[range.startOffset]) {\r\n                            br = range.document.createElement('br');\r\n                            range.insertNode(br).setCursor();\r\n\r\n                        } else {\r\n                            range.setStart(start, 0).setCursor();\r\n                        }\r\n\r\n\r\n                    } else {\r\n                        br = range.document.createElement('br');\r\n                        range.insertNode(br).setStartAfter(br).setCursor();\r\n                    }\r\n\r\n\r\n                } else {\r\n                    br = range.document.createElement('br');\r\n                    range.insertNode(br);\r\n                    var parent = br.parentNode;\r\n                    if (parent.lastChild === br) {\r\n                        br.parentNode.insertBefore(br.cloneNode(true), br);\r\n                        range.setStartBefore(br);\r\n                    } else {\r\n                        range.setStartAfter(br);\r\n                    }\r\n                    range.setCursor();\r\n\r\n                }\r\n\r\n            }\r\n\r\n        }\r\n    });\r\n};\r\n\r\n\r\n// plugins/keystrokes.js\r\n/* 处理特殊键的兼容性问题 */\r\nUE.plugins['keystrokes'] = function() {\r\n    var me = this;\r\n    var collapsed = true;\r\n    me.addListener('keydown', function(type, evt) {\r\n        var keyCode = evt.keyCode || evt.which,\r\n            rng = me.selection.getRange();\r\n\r\n        //处理全选的情况\r\n        if(!rng.collapsed && !(evt.ctrlKey || evt.shiftKey || evt.altKey || evt.metaKey) && (keyCode >= 65 && keyCode <=90\r\n            || keyCode >= 48 && keyCode <= 57 ||\r\n            keyCode >= 96 && keyCode <= 111 || {\r\n                    13:1,\r\n                    8:1,\r\n                    46:1\r\n                }[keyCode])\r\n            ){\r\n\r\n            var tmpNode = rng.startContainer;\r\n            if(domUtils.isFillChar(tmpNode)){\r\n                rng.setStartBefore(tmpNode)\r\n            }\r\n            tmpNode = rng.endContainer;\r\n            if(domUtils.isFillChar(tmpNode)){\r\n                rng.setEndAfter(tmpNode)\r\n            }\r\n            rng.txtToElmBoundary();\r\n            //结束边界可能放到了br的前边，要把br包含进来\r\n            // x[xxx]<br/>\r\n            if(rng.endContainer && rng.endContainer.nodeType == 1){\r\n                tmpNode = rng.endContainer.childNodes[rng.endOffset];\r\n                if(tmpNode && domUtils.isBr(tmpNode)){\r\n                    rng.setEndAfter(tmpNode);\r\n                }\r\n            }\r\n            if(rng.startOffset == 0){\r\n                tmpNode = rng.startContainer;\r\n                if(domUtils.isBoundaryNode(tmpNode,'firstChild') ){\r\n                    tmpNode = rng.endContainer;\r\n                    if(rng.endOffset == (tmpNode.nodeType == 3 ? tmpNode.nodeValue.length : tmpNode.childNodes.length) && domUtils.isBoundaryNode(tmpNode,'lastChild')){\r\n                        me.fireEvent('saveScene');\r\n                        me.body.innerHTML = '<p>'+(browser.ie ? '' : '<br/>')+'</p>';\r\n                        rng.setStart(me.body.firstChild,0).setCursor(false,true);\r\n                        me._selectionChange();\r\n                        return;\r\n                    }\r\n                }\r\n            }\r\n        }\r\n\r\n        //处理backspace\r\n        if (keyCode == keymap.Backspace) {\r\n            rng = me.selection.getRange();\r\n            collapsed = rng.collapsed;\r\n            if(me.fireEvent('delkeydown',evt)){\r\n                return;\r\n            }\r\n            var start,end;\r\n            //避免按两次删除才能生效的问题\r\n            if(rng.collapsed && rng.inFillChar()){\r\n                start = rng.startContainer;\r\n\r\n                if(domUtils.isFillChar(start)){\r\n                    rng.setStartBefore(start).shrinkBoundary(true).collapse(true);\r\n                    domUtils.remove(start)\r\n                }else{\r\n                    start.nodeValue = start.nodeValue.replace(new RegExp('^' + domUtils.fillChar ),'');\r\n                    rng.startOffset--;\r\n                    rng.collapse(true).select(true)\r\n                }\r\n            }\r\n\r\n            //解决选中control元素不能删除的问题\r\n            if (start = rng.getClosedNode()) {\r\n                me.fireEvent('saveScene');\r\n                rng.setStartBefore(start);\r\n                domUtils.remove(start);\r\n                rng.setCursor();\r\n                me.fireEvent('saveScene');\r\n                domUtils.preventDefault(evt);\r\n                return;\r\n            }\r\n            //阻止在table上的删除\r\n            if (!browser.ie) {\r\n                start = domUtils.findParentByTagName(rng.startContainer, 'table', true);\r\n                end = domUtils.findParentByTagName(rng.endContainer, 'table', true);\r\n                if (start && !end || !start && end || start !== end) {\r\n                    evt.preventDefault();\r\n                    return;\r\n                }\r\n            }\r\n\r\n        }\r\n        //处理tab键的逻辑\r\n        if (keyCode == keymap.Tab) {\r\n            //不处理以下标签\r\n            var excludeTagNameForTabKey = {\r\n                'ol' : 1,\r\n                'ul' : 1,\r\n                'table':1\r\n            };\r\n            //处理组件里的tab按下事件\r\n            if(me.fireEvent('tabkeydown',evt)){\r\n                domUtils.preventDefault(evt);\r\n                return;\r\n            }\r\n            var range = me.selection.getRange();\r\n            me.fireEvent('saveScene');\r\n            for (var i = 0,txt = '',tabSize = me.options.tabSize|| 4,tabNode =  me.options.tabNode || '&nbsp;'; i < tabSize; i++) {\r\n                txt += tabNode;\r\n            }\r\n            var span = me.document.createElement('span');\r\n            span.innerHTML = txt + domUtils.fillChar;\r\n            if (range.collapsed) {\r\n                range.insertNode(span.cloneNode(true).firstChild).setCursor(true);\r\n            } else {\r\n                var filterFn = function(node) {\r\n                    return domUtils.isBlockElm(node) && !excludeTagNameForTabKey[node.tagName.toLowerCase()]\r\n\r\n                };\r\n                //普通的情况\r\n                start = domUtils.findParent(range.startContainer, filterFn,true);\r\n                end = domUtils.findParent(range.endContainer, filterFn,true);\r\n                if (start && end && start === end) {\r\n                    range.deleteContents();\r\n                    range.insertNode(span.cloneNode(true).firstChild).setCursor(true);\r\n                } else {\r\n                    var bookmark = range.createBookmark();\r\n                    range.enlarge(true);\r\n                    var bookmark2 = range.createBookmark(),\r\n                        current = domUtils.getNextDomNode(bookmark2.start, false, filterFn);\r\n                    while (current && !(domUtils.getPosition(current, bookmark2.end) & domUtils.POSITION_FOLLOWING)) {\r\n                        current.insertBefore(span.cloneNode(true).firstChild, current.firstChild);\r\n                        current = domUtils.getNextDomNode(current, false, filterFn);\r\n                    }\r\n                    range.moveToBookmark(bookmark2).moveToBookmark(bookmark).select();\r\n                }\r\n            }\r\n            domUtils.preventDefault(evt)\r\n        }\r\n        //trace:1634\r\n        //ff的del键在容器空的时候，也会删除\r\n        if(browser.gecko && keyCode == 46){\r\n            range = me.selection.getRange();\r\n            if(range.collapsed){\r\n                start = range.startContainer;\r\n                if(domUtils.isEmptyBlock(start)){\r\n                    var parent = start.parentNode;\r\n                    while(domUtils.getChildCount(parent) == 1 && !domUtils.isBody(parent)){\r\n                        start = parent;\r\n                        parent = parent.parentNode;\r\n                    }\r\n                    if(start === parent.lastChild)\r\n                        evt.preventDefault();\r\n                    return;\r\n                }\r\n            }\r\n        }\r\n    });\r\n    me.addListener('keyup', function(type, evt) {\r\n        var keyCode = evt.keyCode || evt.which,\r\n            rng,me = this;\r\n        if(keyCode == keymap.Backspace){\r\n            if(me.fireEvent('delkeyup')){\r\n                return;\r\n            }\r\n            rng = me.selection.getRange();\r\n            if(rng.collapsed){\r\n                var tmpNode,\r\n                    autoClearTagName = ['h1','h2','h3','h4','h5','h6'];\r\n                if(tmpNode = domUtils.findParentByTagName(rng.startContainer,autoClearTagName,true)){\r\n                    if(domUtils.isEmptyBlock(tmpNode)){\r\n                        var pre = tmpNode.previousSibling;\r\n                        if(pre && pre.nodeName != 'TABLE'){\r\n                            domUtils.remove(tmpNode);\r\n                            rng.setStartAtLast(pre).setCursor(false,true);\r\n                            return;\r\n                        }else{\r\n                            var next = tmpNode.nextSibling;\r\n                            if(next && next.nodeName != 'TABLE'){\r\n                                domUtils.remove(tmpNode);\r\n                                rng.setStartAtFirst(next).setCursor(false,true);\r\n                                return;\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n                //处理当删除到body时，要重新给p标签展位\r\n                if(domUtils.isBody(rng.startContainer)){\r\n                    var tmpNode = domUtils.createElement(me.document,'p',{\r\n                        'innerHTML' : browser.ie ? domUtils.fillChar : '<br/>'\r\n                    });\r\n                    rng.insertNode(tmpNode).setStart(tmpNode,0).setCursor(false,true);\r\n                }\r\n            }\r\n\r\n\r\n            //chrome下如果删除了inline标签，浏览器会有记忆，在输入文字还是会套上刚才删除的标签，所以这里再选一次就不会了\r\n            if( !collapsed && (rng.startContainer.nodeType == 3 || rng.startContainer.nodeType == 1 && domUtils.isEmptyBlock(rng.startContainer))){\r\n                if(browser.ie){\r\n                    var span = rng.document.createElement('span');\r\n                    rng.insertNode(span).setStartBefore(span).collapse(true);\r\n                    rng.select();\r\n                    domUtils.remove(span)\r\n                }else{\r\n                    rng.select()\r\n                }\r\n\r\n            }\r\n        }\r\n\r\n\r\n    })\r\n};\r\n\r\n// plugins/fiximgclick.js\r\n///import core\r\n///commands 修复chrome下图片不能点击的问题，出现八个角可改变大小\r\n///commandsName  FixImgClick\r\n///commandsTitle  修复chrome下图片不能点击的问题，出现八个角可改变大小\r\n//修复chrome下图片不能点击的问题，出现八个角可改变大小\r\n\r\nUE.plugins['fiximgclick'] = (function () {\r\n\r\n    var elementUpdated = false;\r\n    function Scale() {\r\n        this.editor = null;\r\n        this.resizer = null;\r\n        this.cover = null;\r\n        this.doc = document;\r\n        this.prePos = {x: 0, y: 0};\r\n        this.startPos = {x: 0, y: 0};\r\n    }\r\n\r\n    (function () {\r\n        var rect = [\r\n            //[left, top, width, height]\r\n            [0, 0, -1, -1],\r\n            [0, 0, 0, -1],\r\n            [0, 0, 1, -1],\r\n            [0, 0, -1, 0],\r\n            [0, 0, 1, 0],\r\n            [0, 0, -1, 1],\r\n            [0, 0, 0, 1],\r\n            [0, 0, 1, 1]\r\n        ];\r\n\r\n        Scale.prototype = {\r\n            init: function (editor) {\r\n                var me = this;\r\n                me.editor = editor;\r\n                me.startPos = this.prePos = {x: 0, y: 0};\r\n                me.dragId = -1;\r\n\r\n                var hands = [],\r\n                    cover = me.cover = document.createElement('div'),\r\n                    resizer = me.resizer = document.createElement('div');\r\n\r\n                cover.id = me.editor.ui.id + '_imagescale_cover';\r\n                cover.style.cssText = 'position:absolute;display:none;z-index:' + (me.editor.options.zIndex) + ';filter:alpha(opacity=0); opacity:0;background:#CCC;';\r\n                domUtils.on(cover, 'mousedown click', function () {\r\n                    me.hide();\r\n                });\r\n\r\n                for (i = 0; i < 8; i++) {\r\n                    hands.push('<span class=\"edui-editor-imagescale-hand' + i + '\"></span>');\r\n                }\r\n                resizer.id = me.editor.ui.id + '_imagescale';\r\n                resizer.className = 'edui-editor-imagescale';\r\n                resizer.innerHTML = hands.join('');\r\n                resizer.style.cssText += ';display:none;border:1px solid #3b77ff;z-index:' + (me.editor.options.zIndex) + ';';\r\n\r\n                me.editor.ui.getDom().appendChild(cover);\r\n                me.editor.ui.getDom().appendChild(resizer);\r\n\r\n                me.initStyle();\r\n                me.initEvents();\r\n            },\r\n            initStyle: function () {\r\n                utils.cssRule('imagescale', '.edui-editor-imagescale{display:none;position:absolute;border:1px solid #38B2CE;cursor:hand;-webkit-box-sizing: content-box;-moz-box-sizing: content-box;box-sizing: content-box;}' +\r\n                    '.edui-editor-imagescale span{position:absolute;width:6px;height:6px;overflow:hidden;font-size:0px;display:block;background-color:#3C9DD0;}'\r\n                    + '.edui-editor-imagescale .edui-editor-imagescale-hand0{cursor:nw-resize;top:0;margin-top:-4px;left:0;margin-left:-4px;}'\r\n                    + '.edui-editor-imagescale .edui-editor-imagescale-hand1{cursor:n-resize;top:0;margin-top:-4px;left:50%;margin-left:-4px;}'\r\n                    + '.edui-editor-imagescale .edui-editor-imagescale-hand2{cursor:ne-resize;top:0;margin-top:-4px;left:100%;margin-left:-3px;}'\r\n                    + '.edui-editor-imagescale .edui-editor-imagescale-hand3{cursor:w-resize;top:50%;margin-top:-4px;left:0;margin-left:-4px;}'\r\n                    + '.edui-editor-imagescale .edui-editor-imagescale-hand4{cursor:e-resize;top:50%;margin-top:-4px;left:100%;margin-left:-3px;}'\r\n                    + '.edui-editor-imagescale .edui-editor-imagescale-hand5{cursor:sw-resize;top:100%;margin-top:-3px;left:0;margin-left:-4px;}'\r\n                    + '.edui-editor-imagescale .edui-editor-imagescale-hand6{cursor:s-resize;top:100%;margin-top:-3px;left:50%;margin-left:-4px;}'\r\n                    + '.edui-editor-imagescale .edui-editor-imagescale-hand7{cursor:se-resize;top:100%;margin-top:-3px;left:100%;margin-left:-3px;}');\r\n            },\r\n            initEvents: function () {\r\n                var me = this;\r\n\r\n                me.startPos.x = me.startPos.y = 0;\r\n                me.isDraging = false;\r\n            },\r\n            _eventHandler: function (e) {\r\n                var me = this;\r\n                switch (e.type) {\r\n                    case 'mousedown':\r\n                        var hand = e.target || e.srcElement, hand;\r\n                        if (hand.className.indexOf('edui-editor-imagescale-hand') != -1 && me.dragId == -1) {\r\n                            me.dragId = hand.className.slice(-1);\r\n                            me.startPos.x = me.prePos.x = e.clientX;\r\n                            me.startPos.y = me.prePos.y = e.clientY;\r\n                            domUtils.on(me.doc,'mousemove', me.proxy(me._eventHandler, me));\r\n                        }\r\n                        break;\r\n                    case 'mousemove':\r\n                        if (me.dragId != -1) {\r\n                            me.updateContainerStyle(me.dragId, {x: e.clientX - me.prePos.x, y: e.clientY - me.prePos.y});\r\n                            me.prePos.x = e.clientX;\r\n                            me.prePos.y = e.clientY;\r\n                            elementUpdated = true;\r\n                            me.updateTargetElement();\r\n\r\n                        }\r\n                        break;\r\n                    case 'mouseup':\r\n                        if (me.dragId != -1) {\r\n                            me.updateContainerStyle(me.dragId, {x: e.clientX - me.prePos.x, y: e.clientY - me.prePos.y});\r\n                            me.updateTargetElement();\r\n                            if (me.target.parentNode) me.attachTo(me.target);\r\n                            me.dragId = -1;\r\n                        }\r\n                        domUtils.un(me.doc,'mousemove', me.proxy(me._eventHandler, me));\r\n                        //修复只是点击挪动点，但没有改变大小，不应该触发contentchange\r\n                        if(elementUpdated){\r\n                            elementUpdated = false;\r\n                            me.editor.fireEvent('contentchange');\r\n                        }\r\n\r\n                        break;\r\n                    default:\r\n                        break;\r\n                }\r\n            },\r\n            updateTargetElement: function () {\r\n                var me = this;\r\n                domUtils.setStyles(me.target, {\r\n                    'width': me.resizer.style.width,\r\n                    'height': me.resizer.style.height\r\n                });\r\n                me.target.width = parseInt(me.resizer.style.width);\r\n                me.target.height = parseInt(me.resizer.style.height);\r\n                me.attachTo(me.target);\r\n            },\r\n            updateContainerStyle: function (dir, offset) {\r\n                var me = this,\r\n                    dom = me.resizer, tmp;\r\n\r\n                if (rect[dir][0] != 0) {\r\n                    tmp = parseInt(dom.style.left) + offset.x;\r\n                    dom.style.left = me._validScaledProp('left', tmp) + 'px';\r\n                }\r\n                if (rect[dir][1] != 0) {\r\n                    tmp = parseInt(dom.style.top) + offset.y;\r\n                    dom.style.top = me._validScaledProp('top', tmp) + 'px';\r\n                }\r\n                if (rect[dir][2] != 0) {\r\n                    tmp = dom.clientWidth + rect[dir][2] * offset.x;\r\n                    dom.style.width = me._validScaledProp('width', tmp) + 'px';\r\n                }\r\n                if (rect[dir][3] != 0) {\r\n                    tmp = dom.clientHeight + rect[dir][3] * offset.y;\r\n                    dom.style.height = me._validScaledProp('height', tmp) + 'px';\r\n                }\r\n            },\r\n            _validScaledProp: function (prop, value) {\r\n                var ele = this.resizer,\r\n                    wrap = document;\r\n\r\n                value = isNaN(value) ? 0 : value;\r\n                switch (prop) {\r\n                    case 'left':\r\n                        return value < 0 ? 0 : (value + ele.clientWidth) > wrap.clientWidth ? wrap.clientWidth - ele.clientWidth : value;\r\n                    case 'top':\r\n                        return value < 0 ? 0 : (value + ele.clientHeight) > wrap.clientHeight ? wrap.clientHeight - ele.clientHeight : value;\r\n                    case 'width':\r\n                        return value <= 0 ? 1 : (value + ele.offsetLeft) > wrap.clientWidth ? wrap.clientWidth - ele.offsetLeft : value;\r\n                    case 'height':\r\n                        return value <= 0 ? 1 : (value + ele.offsetTop) > wrap.clientHeight ? wrap.clientHeight - ele.offsetTop : value;\r\n                }\r\n            },\r\n            hideCover: function () {\r\n                this.cover.style.display = 'none';\r\n            },\r\n            showCover: function () {\r\n                var me = this,\r\n                    editorPos = domUtils.getXY(me.editor.ui.getDom()),\r\n                    iframePos = domUtils.getXY(me.editor.iframe);\r\n\r\n                domUtils.setStyles(me.cover, {\r\n                    'width': me.editor.iframe.offsetWidth + 'px',\r\n                    'height': me.editor.iframe.offsetHeight + 'px',\r\n                    'top': iframePos.y - editorPos.y + 'px',\r\n                    'left': iframePos.x - editorPos.x + 'px',\r\n                    'position': 'absolute',\r\n                    'display': ''\r\n                })\r\n            },\r\n            show: function (targetObj) {\r\n                var me = this;\r\n                me.resizer.style.display = 'block';\r\n                if(targetObj) me.attachTo(targetObj);\r\n\r\n                domUtils.on(this.resizer, 'mousedown', me.proxy(me._eventHandler, me));\r\n                domUtils.on(me.doc, 'mouseup', me.proxy(me._eventHandler, me));\r\n\r\n                me.showCover();\r\n                me.editor.fireEvent('afterscaleshow', me);\r\n                me.editor.fireEvent('saveScene');\r\n            },\r\n            hide: function () {\r\n                var me = this;\r\n                me.hideCover();\r\n                me.resizer.style.display = 'none';\r\n\r\n                domUtils.un(me.resizer, 'mousedown', me.proxy(me._eventHandler, me));\r\n                domUtils.un(me.doc, 'mouseup', me.proxy(me._eventHandler, me));\r\n                me.editor.fireEvent('afterscalehide', me);\r\n            },\r\n            proxy: function( fn, context ) {\r\n                return function(e) {\r\n                    return fn.apply( context || this, arguments);\r\n                };\r\n            },\r\n            attachTo: function (targetObj) {\r\n                var me = this,\r\n                    target = me.target = targetObj,\r\n                    resizer = this.resizer,\r\n                    imgPos = domUtils.getXY(target),\r\n                    iframePos = domUtils.getXY(me.editor.iframe),\r\n                    editorPos = domUtils.getXY(resizer.parentNode);\r\n\r\n                domUtils.setStyles(resizer, {\r\n                    'width': target.width + 'px',\r\n                    'height': target.height + 'px',\r\n                    'left': iframePos.x + imgPos.x - me.editor.document.body.scrollLeft - editorPos.x - parseInt(resizer.style.borderLeftWidth) + 'px',\r\n                    'top': iframePos.y + imgPos.y - me.editor.document.body.scrollTop - editorPos.y - parseInt(resizer.style.borderTopWidth) + 'px'\r\n                });\r\n            }\r\n        }\r\n    })();\r\n\r\n    return function () {\r\n        var me = this,\r\n            imageScale;\r\n\r\n        me.setOpt('imageScaleEnabled', true);\r\n\r\n        if ( !browser.ie && me.options.imageScaleEnabled) {\r\n            me.addListener('click', function (type, e) {\r\n\r\n                var range = me.selection.getRange(),\r\n                    img = range.getClosedNode();\r\n\r\n                if (img && img.tagName == 'IMG' && me.body.contentEditable!=\"false\") {\r\n\r\n                    if (img.className.indexOf(\"edui-faked-music\") != -1 ||\r\n                        img.getAttribute(\"anchorname\") ||\r\n                        domUtils.hasClass(img, 'loadingclass') ||\r\n                        domUtils.hasClass(img, 'loaderrorclass')) { return }\r\n\r\n                    if (!imageScale) {\r\n                        imageScale = new Scale();\r\n                        imageScale.init(me);\r\n                        me.ui.getDom().appendChild(imageScale.resizer);\r\n\r\n                        var _keyDownHandler = function (e) {\r\n                            imageScale.hide();\r\n                            if(imageScale.target) me.selection.getRange().selectNode(imageScale.target).select();\r\n                        }, _mouseDownHandler = function (e) {\r\n                            var ele = e.target || e.srcElement;\r\n                            if (ele && (ele.className===undefined || ele.className.indexOf('edui-editor-imagescale') == -1)) {\r\n                                _keyDownHandler(e);\r\n                            }\r\n                        }, timer;\r\n\r\n                        me.addListener('afterscaleshow', function (e) {\r\n                            me.addListener('beforekeydown', _keyDownHandler);\r\n                            me.addListener('beforemousedown', _mouseDownHandler);\r\n                            domUtils.on(document, 'keydown', _keyDownHandler);\r\n                            domUtils.on(document,'mousedown', _mouseDownHandler);\r\n                            me.selection.getNative().removeAllRanges();\r\n                        });\r\n                        me.addListener('afterscalehide', function (e) {\r\n                            me.removeListener('beforekeydown', _keyDownHandler);\r\n                            me.removeListener('beforemousedown', _mouseDownHandler);\r\n                            domUtils.un(document, 'keydown', _keyDownHandler);\r\n                            domUtils.un(document,'mousedown', _mouseDownHandler);\r\n                            var target = imageScale.target;\r\n                            if (target.parentNode) {\r\n                                me.selection.getRange().selectNode(target).select();\r\n                            }\r\n                        });\r\n                        //TODO 有iframe的情况，mousedown不能往下传。。\r\n                        domUtils.on(imageScale.resizer, 'mousedown', function (e) {\r\n                            me.selection.getNative().removeAllRanges();\r\n                            var ele = e.target || e.srcElement;\r\n                            if (ele && ele.className.indexOf('edui-editor-imagescale-hand') == -1) {\r\n                                timer = setTimeout(function () {\r\n                                    imageScale.hide();\r\n                                    if(imageScale.target) me.selection.getRange().selectNode(ele).select();\r\n                                }, 200);\r\n                            }\r\n                        });\r\n                        domUtils.on(imageScale.resizer, 'mouseup', function (e) {\r\n                            var ele = e.target || e.srcElement;\r\n                            if (ele && ele.className.indexOf('edui-editor-imagescale-hand') == -1) {\r\n                                clearTimeout(timer);\r\n                            }\r\n                        });\r\n                    }\r\n                    imageScale.show(img);\r\n                } else {\r\n                    if (imageScale && imageScale.resizer.style.display != 'none') imageScale.hide();\r\n                }\r\n            });\r\n        }\r\n\r\n        if (browser.webkit) {\r\n            me.addListener('click', function (type, e) {\r\n                if (e.target.tagName == 'IMG' && me.body.contentEditable!=\"false\") {\r\n                    var range = new dom.Range(me.document);\r\n                    range.selectNode(e.target).select();\r\n                }\r\n            });\r\n        }\r\n    }\r\n})();\r\n\r\n// plugins/autolink.js\r\n///import core\r\n///commands 为非ie浏览器自动添加a标签\r\n///commandsName  AutoLink\r\n///commandsTitle  自动增加链接\r\n/**\r\n * @description 为非ie浏览器自动添加a标签\r\n * @author zhanyi\r\n */\r\n\r\nUE.plugin.register('autolink',function(){\r\n    var cont = 0;\r\n\r\n    return !browser.ie ? {\r\n\r\n            bindEvents:{\r\n                'reset' : function(){\r\n                    cont = 0;\r\n                },\r\n                'keydown':function(type, evt) {\r\n                    var me = this;\r\n                    var keyCode = evt.keyCode || evt.which;\r\n\r\n                    if (keyCode == 32 || keyCode == 13) {\r\n\r\n                        var sel = me.selection.getNative(),\r\n                            range = sel.getRangeAt(0).cloneRange(),\r\n                            offset,\r\n                            charCode;\r\n\r\n                        var start = range.startContainer;\r\n                        while (start.nodeType == 1 && range.startOffset > 0) {\r\n                            start = range.startContainer.childNodes[range.startOffset - 1];\r\n                            if (!start){\r\n                                break;\r\n                            }\r\n                            range.setStart(start, start.nodeType == 1 ? start.childNodes.length : start.nodeValue.length);\r\n                            range.collapse(true);\r\n                            start = range.startContainer;\r\n                        }\r\n\r\n                        do{\r\n                            if (range.startOffset == 0) {\r\n                                start = range.startContainer.previousSibling;\r\n\r\n                                while (start && start.nodeType == 1) {\r\n                                    start = start.lastChild;\r\n                                }\r\n                                if (!start || domUtils.isFillChar(start)){\r\n                                    break;\r\n                                }\r\n                                offset = start.nodeValue.length;\r\n                            } else {\r\n                                start = range.startContainer;\r\n                                offset = range.startOffset;\r\n                            }\r\n                            range.setStart(start, offset - 1);\r\n                            charCode = range.toString().charCodeAt(0);\r\n                        } while (charCode != 160 && charCode != 32);\r\n\r\n                        if (range.toString().replace(new RegExp(domUtils.fillChar, 'g'), '').match(/(?:https?:\\/\\/|ssh:\\/\\/|ftp:\\/\\/|file:\\/|www\\.)/i)) {\r\n                            while(range.toString().length){\r\n                                if(/^(?:https?:\\/\\/|ssh:\\/\\/|ftp:\\/\\/|file:\\/|www\\.)/i.test(range.toString())){\r\n                                    break;\r\n                                }\r\n                                try{\r\n                                    range.setStart(range.startContainer,range.startOffset+1);\r\n                                }catch(e){\r\n                                    //trace:2121\r\n                                    var start = range.startContainer;\r\n                                    while(!(next = start.nextSibling)){\r\n                                        if(domUtils.isBody(start)){\r\n                                            return;\r\n                                        }\r\n                                        start = start.parentNode;\r\n\r\n                                    }\r\n                                    range.setStart(next,0);\r\n\r\n                                }\r\n\r\n                            }\r\n                            //range的开始边界已经在a标签里的不再处理\r\n                            if(domUtils.findParentByTagName(range.startContainer,'a',true)){\r\n                                return;\r\n                            }\r\n                            var a = me.document.createElement('a'),text = me.document.createTextNode(' '),href;\r\n\r\n                            me.undoManger && me.undoManger.save();\r\n                            a.appendChild(range.extractContents());\r\n                            a.href = a.innerHTML = a.innerHTML.replace(/<[^>]+>/g,'');\r\n                            href = a.getAttribute(\"href\").replace(new RegExp(domUtils.fillChar,'g'),'');\r\n                            href = /^(?:https?:\\/\\/)/ig.test(href) ? href : \"http://\"+ href;\r\n                            a.setAttribute('_src',utils.html(href));\r\n                            a.href = utils.html(href);\r\n\r\n                            range.insertNode(a);\r\n                            a.parentNode.insertBefore(text, a.nextSibling);\r\n                            range.setStart(text, 0);\r\n                            range.collapse(true);\r\n                            sel.removeAllRanges();\r\n                            sel.addRange(range);\r\n                            me.undoManger && me.undoManger.save();\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n        }:{}\r\n    },function(){\r\n        var keyCodes = {\r\n            37:1, 38:1, 39:1, 40:1,\r\n            13:1,32:1\r\n        };\r\n        function checkIsCludeLink(node){\r\n            if(node.nodeType == 3){\r\n                return null\r\n            }\r\n            if(node.nodeName == 'A'){\r\n                return node;\r\n            }\r\n            var lastChild = node.lastChild;\r\n\r\n            while(lastChild){\r\n                if(lastChild.nodeName == 'A'){\r\n                    return lastChild;\r\n                }\r\n                if(lastChild.nodeType == 3){\r\n                    if(domUtils.isWhitespace(lastChild)){\r\n                        lastChild = lastChild.previousSibling;\r\n                        continue;\r\n                    }\r\n                    return null\r\n                }\r\n                lastChild = lastChild.lastChild;\r\n            }\r\n        }\r\n        browser.ie && this.addListener('keyup',function(cmd,evt){\r\n            var me = this,keyCode = evt.keyCode;\r\n            if(keyCodes[keyCode]){\r\n                var rng = me.selection.getRange();\r\n                var start = rng.startContainer;\r\n\r\n                if(keyCode == 13){\r\n                    while(start && !domUtils.isBody(start) && !domUtils.isBlockElm(start)){\r\n                        start = start.parentNode;\r\n                    }\r\n                    if(start && !domUtils.isBody(start) && start.nodeName == 'P'){\r\n                        var pre = start.previousSibling;\r\n                        if(pre && pre.nodeType == 1){\r\n                            var pre = checkIsCludeLink(pre);\r\n                            if(pre && !pre.getAttribute('_href')){\r\n                                domUtils.remove(pre,true);\r\n                            }\r\n                        }\r\n                    }\r\n                }else if(keyCode == 32 ){\r\n                    if(start.nodeType == 3 && /^\\s$/.test(start.nodeValue)){\r\n                        start = start.previousSibling;\r\n                        if(start && start.nodeName == 'A' && !start.getAttribute('_href')){\r\n                            domUtils.remove(start,true);\r\n                        }\r\n                    }\r\n                }else {\r\n                    start = domUtils.findParentByTagName(start,'a',true);\r\n                    if(start && !start.getAttribute('_href')){\r\n                        var bk = rng.createBookmark();\r\n\r\n                        domUtils.remove(start,true);\r\n                        rng.moveToBookmark(bk).select(true)\r\n                    }\r\n                }\r\n\r\n            }\r\n\r\n\r\n        });\r\n    }\r\n);\r\n\r\n// plugins/autoheight.js\r\n///import core\r\n///commands 当输入内容超过编辑器高度时，编辑器自动增高\r\n///commandsName  AutoHeight,autoHeightEnabled\r\n///commandsTitle  自动增高\r\n/**\r\n * @description 自动伸展\r\n * @author zhanyi\r\n */\r\nUE.plugins['autoheight'] = function () {\r\n    var me = this;\r\n    //提供开关，就算加载也可以关闭\r\n    me.autoHeightEnabled = me.options.autoHeightEnabled !== false;\r\n    if (!me.autoHeightEnabled) {\r\n        return;\r\n    }\r\n\r\n    var bakOverflow,\r\n        lastHeight = 0,\r\n        options = me.options,\r\n        currentHeight,\r\n        timer;\r\n\r\n    function adjustHeight() {\r\n        var me = this;\r\n        clearTimeout(timer);\r\n        if(isFullscreen)return;\r\n        if (!me.queryCommandState || me.queryCommandState && me.queryCommandState('source') != 1) {\r\n            timer = setTimeout(function(){\r\n\r\n                var node = me.body.lastChild;\r\n                while(node && node.nodeType != 1){\r\n                    node = node.previousSibling;\r\n                }\r\n                if(node && node.nodeType == 1){\r\n                    node.style.clear = 'both';\r\n                    currentHeight = Math.max(domUtils.getXY(node).y + node.offsetHeight + 25 ,Math.max(options.minFrameHeight, options.initialFrameHeight)) ;\r\n                    if (currentHeight != lastHeight) {\r\n                        if (currentHeight !== parseInt(me.iframe.parentNode.style.height)) {\r\n                            me.iframe.parentNode.style.height = currentHeight + 'px';\r\n                        }\r\n                        me.body.style.height = currentHeight + 'px';\r\n                        lastHeight = currentHeight;\r\n                    }\r\n                    domUtils.removeStyle(node,'clear');\r\n                }\r\n\r\n\r\n            },50)\r\n        }\r\n    }\r\n    var isFullscreen;\r\n    me.addListener('fullscreenchanged',function(cmd,f){\r\n        isFullscreen = f\r\n    });\r\n    me.addListener('destroy', function () {\r\n        me.removeListener('contentchange afterinserthtml keyup mouseup',adjustHeight)\r\n    });\r\n    me.enableAutoHeight = function () {\r\n        var me = this;\r\n        if (!me.autoHeightEnabled) {\r\n            return;\r\n        }\r\n        var doc = me.document;\r\n        me.autoHeightEnabled = true;\r\n        bakOverflow = doc.body.style.overflowY;\r\n        doc.body.style.overflowY = 'hidden';\r\n        me.addListener('contentchange afterinserthtml keyup mouseup',adjustHeight);\r\n        //ff不给事件算得不对\r\n\r\n        setTimeout(function () {\r\n            adjustHeight.call(me);\r\n        }, browser.gecko ? 100 : 0);\r\n        me.fireEvent('autoheightchanged', me.autoHeightEnabled);\r\n    };\r\n    me.disableAutoHeight = function () {\r\n\r\n        me.body.style.overflowY = bakOverflow || '';\r\n\r\n        me.removeListener('contentchange', adjustHeight);\r\n        me.removeListener('keyup', adjustHeight);\r\n        me.removeListener('mouseup', adjustHeight);\r\n        me.autoHeightEnabled = false;\r\n        me.fireEvent('autoheightchanged', me.autoHeightEnabled);\r\n    };\r\n\r\n    me.on('setHeight',function(){\r\n        me.disableAutoHeight()\r\n    });\r\n    me.addListener('ready', function () {\r\n        me.enableAutoHeight();\r\n        //trace:1764\r\n        var timer;\r\n        domUtils.on(browser.ie ? me.body : me.document, browser.webkit ? 'dragover' : 'drop', function () {\r\n            clearTimeout(timer);\r\n            timer = setTimeout(function () {\r\n                //trace:3681\r\n                adjustHeight.call(me);\r\n            }, 100);\r\n\r\n        });\r\n        //修复内容过多时，回到顶部，顶部内容被工具栏遮挡问题\r\n        var lastScrollY;\r\n        window.onscroll = function(){\r\n            if(lastScrollY === null){\r\n                lastScrollY = this.scrollY\r\n            }else if(this.scrollY == 0 && lastScrollY != 0){\r\n                me.window.scrollTo(0,0);\r\n                lastScrollY = null;\r\n            }\r\n        }\r\n    });\r\n\r\n\r\n};\r\n\r\n\r\n\r\n// plugins/autofloat.js\r\n///import core\r\n///commands 悬浮工具栏\r\n///commandsName  AutoFloat,autoFloatEnabled\r\n///commandsTitle  悬浮工具栏\r\n/**\r\n *  modified by chengchao01\r\n *  注意： 引入此功能后，在IE6下会将body的背景图片覆盖掉！\r\n */\r\nUE.plugins['autofloat'] = function() {\r\n    var me = this,\r\n        lang = me.getLang();\r\n    me.setOpt({\r\n        topOffset:0\r\n    });\r\n    var optsAutoFloatEnabled = me.options.autoFloatEnabled !== false,\r\n        topOffset = me.options.topOffset;\r\n\r\n\r\n    //如果不固定toolbar的位置，则直接退出\r\n    if(!optsAutoFloatEnabled){\r\n        return;\r\n    }\r\n    var uiUtils = UE.ui.uiUtils,\r\n        LteIE6 = browser.ie && browser.version <= 6,\r\n        quirks = browser.quirks;\r\n\r\n    function checkHasUI(){\r\n        if(!UE.ui){\r\n            alert(lang.autofloatMsg);\r\n            return 0;\r\n        }\r\n        return 1;\r\n    }\r\n    function fixIE6FixedPos(){\r\n        var docStyle = document.body.style;\r\n        docStyle.backgroundImage = 'url(\"about:blank\")';\r\n        docStyle.backgroundAttachment = 'fixed';\r\n    }\r\n    var\tbakCssText,\r\n        placeHolder = document.createElement('div'),\r\n        toolbarBox,orgTop,\r\n        getPosition,\r\n        flag =true;   //ie7模式下需要偏移\r\n    function setFloating(){\r\n        var toobarBoxPos = domUtils.getXY(toolbarBox),\r\n            origalFloat = domUtils.getComputedStyle(toolbarBox,'position'),\r\n            origalLeft = domUtils.getComputedStyle(toolbarBox,'left');\r\n        toolbarBox.style.width = toolbarBox.offsetWidth + 'px';\r\n        toolbarBox.style.zIndex = me.options.zIndex * 1 + 1;\r\n        toolbarBox.parentNode.insertBefore(placeHolder, toolbarBox);\r\n        if (LteIE6 || (quirks && browser.ie)) {\r\n            if(toolbarBox.style.position != 'absolute'){\r\n                toolbarBox.style.position = 'absolute';\r\n            }\r\n            toolbarBox.style.top = (document.body.scrollTop||document.documentElement.scrollTop) - orgTop + topOffset  + 'px';\r\n        } else {\r\n            if (browser.ie7Compat && flag) {\r\n                flag = false;\r\n                toolbarBox.style.left =  domUtils.getXY(toolbarBox).x - document.documentElement.getBoundingClientRect().left+2  + 'px';\r\n            }\r\n            if(toolbarBox.style.position != 'fixed'){\r\n                toolbarBox.style.position = 'fixed';\r\n                toolbarBox.style.top = topOffset +\"px\";\r\n                ((origalFloat == 'absolute' || origalFloat == 'relative') && parseFloat(origalLeft)) && (toolbarBox.style.left = toobarBoxPos.x + 'px');\r\n            }\r\n        }\r\n    }\r\n    function unsetFloating(){\r\n        flag = true;\r\n        if(placeHolder.parentNode){\r\n            placeHolder.parentNode.removeChild(placeHolder);\r\n        }\r\n\r\n        toolbarBox.style.cssText = bakCssText;\r\n    }\r\n\r\n    function updateFloating(){\r\n        var rect3 = getPosition(me.container);\r\n        var offset=me.options.toolbarTopOffset||0;\r\n        if (rect3.top < 0 && rect3.bottom - toolbarBox.offsetHeight > offset) {\r\n            setFloating();\r\n        }else{\r\n            unsetFloating();\r\n        }\r\n    }\r\n    var defer_updateFloating = utils.defer(function(){\r\n        updateFloating();\r\n    },browser.ie ? 200 : 100,true);\r\n\r\n    me.addListener('destroy',function(){\r\n        domUtils.un(window, ['scroll','resize'], updateFloating);\r\n        me.removeListener('keydown', defer_updateFloating);\r\n    });\r\n\r\n    me.addListener('ready', function(){\r\n        if(checkHasUI(me)){\r\n            //加载了ui组件，但在new时，没有加载ui，导致编辑器实例上没有ui类，所以这里做判断\r\n            if(!me.ui){\r\n                return;\r\n            }\r\n            getPosition = uiUtils.getClientRect;\r\n            toolbarBox = me.ui.getDom('toolbarbox');\r\n            orgTop = getPosition(toolbarBox).top;\r\n            bakCssText = toolbarBox.style.cssText;\r\n            placeHolder.style.height = toolbarBox.offsetHeight + 'px';\r\n            if(LteIE6){\r\n                fixIE6FixedPos();\r\n            }\r\n            domUtils.on(window, ['scroll','resize'], updateFloating);\r\n            me.addListener('keydown', defer_updateFloating);\r\n\r\n            me.addListener('beforefullscreenchange', function (t, enabled){\r\n                if (enabled) {\r\n                    unsetFloating();\r\n                }\r\n            });\r\n            me.addListener('fullscreenchanged', function (t, enabled){\r\n                if (!enabled) {\r\n                    updateFloating();\r\n                }\r\n            });\r\n            me.addListener('sourcemodechanged', function (t, enabled){\r\n                setTimeout(function (){\r\n                    updateFloating();\r\n                },0);\r\n            });\r\n            me.addListener(\"clearDoc\",function(){\r\n                setTimeout(function(){\r\n                    updateFloating();\r\n                },0);\r\n\r\n            })\r\n        }\r\n    });\r\n};\r\n\r\n\r\n// plugins/video.js\r\n/**\r\n * video插件， 为UEditor提供视频插入支持\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\nUE.plugins['video'] = function (){\r\n    var me =this;\r\n\r\n    /**\r\n     * 创建插入视频字符窜\r\n     * @param url 视频地址\r\n     * @param width 视频宽度\r\n     * @param height 视频高度\r\n     * @param align 视频对齐\r\n     * @param toEmbed 是否以flash代替显示\r\n     * @param addParagraph  是否需要添加P 标签\r\n     */\r\n    function creatInsertStr(url,width,height,id,align,classname,type){\r\n\r\n        url = utils.unhtmlForUrl(url);\r\n        align = utils.unhtml(align);\r\n        classname = utils.unhtml(classname).trim();\r\n\r\n        width = parseInt(width, 10) || 0;\r\n        height = parseInt(height, 10) || 0;\r\n\r\n        var str;\r\n        switch (type){\r\n            case 'image':\r\n                str = '<img ' + (id ? 'id=\"' + id+'\"' : '') + ' width=\"'+ width +'\" height=\"' + height + '\" _url=\"'+url+'\" class=\"' + classname.replace(/\\bvideo-js\\b/, '') + '\"'  +\r\n                    ' src=\"' + me.options.UEDITOR_HOME_URL+'themes/default/images/spacer.gif\" style=\"background:url('+me.options.UEDITOR_HOME_URL+'themes/default/images/videologo.gif) no-repeat center center; border:1px solid gray;'+(align ? 'float:' + align + ';': '')+'\" />'\r\n                break;\r\n            case 'embed':\r\n                str = '<embed type=\"application/x-shockwave-flash\" class=\"' + classname + '\" pluginspage=\"http://www.macromedia.com/go/getflashplayer\"' +\r\n                    ' src=\"' +  utils.html(url) + '\" width=\"' + width  + '\" height=\"' + height  + '\"'  + (align ? ' style=\"float:' + align + '\"': '') +\r\n                    ' wmode=\"transparent\" play=\"true\" loop=\"false\" menu=\"false\" allowscriptaccess=\"never\" allowfullscreen=\"true\" >';\r\n                break;\r\n            case 'video':\r\n                var ext = url.substr(url.lastIndexOf('.') + 1);\r\n                if(ext == 'ogv') ext = 'ogg';\r\n                str = '<video' + (id ? ' id=\"' + id + '\"' : '') + ' class=\"' + classname + ' video-js\" ' + (align ? ' style=\"float:' + align + '\"': '') +\r\n                    ' controls preload=\"none\" width=\"' + width + '\" height=\"' + height + '\" src=\"' + url + '\" data-setup=\"{}\">' +\r\n                    '<source src=\"' + url + '\" type=\"video/' + ext + '\" /></video>';\r\n                break;\r\n        }\r\n        return str;\r\n    }\r\n\r\n    function switchImgAndVideo(root,img2video){\r\n        utils.each(root.getNodesByTagName(img2video ? 'img' : 'embed video'),function(node){\r\n            var className = node.getAttr('class');\r\n            if(className && className.indexOf('edui-faked-video') != -1){\r\n                var html = creatInsertStr( img2video ? node.getAttr('_url') : node.getAttr('src'),node.getAttr('width'),node.getAttr('height'),null,node.getStyle('float') || '',className,img2video ? 'embed':'image');\r\n                node.parentNode.replaceChild(UE.uNode.createElement(html),node);\r\n            }\r\n            if(className && className.indexOf('edui-upload-video') != -1){\r\n                var html = creatInsertStr( img2video ? node.getAttr('_url') : node.getAttr('src'),node.getAttr('width'),node.getAttr('height'),null,node.getStyle('float') || '',className,img2video ? 'video':'image');\r\n                node.parentNode.replaceChild(UE.uNode.createElement(html),node);\r\n            }\r\n        })\r\n    }\r\n\r\n    me.addOutputRule(function(root){\r\n        switchImgAndVideo(root,true)\r\n    });\r\n    me.addInputRule(function(root){\r\n        switchImgAndVideo(root)\r\n    });\r\n\r\n    /**\r\n     * 插入视频\r\n     * @command insertvideo\r\n     * @method execCommand\r\n     * @param { String } cmd 命令字符串\r\n     * @param { Object } videoAttr 键值对对象， 描述一个视频的所有属性\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * var videoAttr = {\r\n     *      //视频地址\r\n     *      url: 'http://www.youku.com/xxx',\r\n     *      //视频宽高值， 单位px\r\n     *      width: 200,\r\n     *      height: 100\r\n     * };\r\n     *\r\n     * //editor 是编辑器实例\r\n     * //向编辑器插入单个视频\r\n     * editor.execCommand( 'insertvideo', videoAttr );\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 插入视频\r\n     * @command insertvideo\r\n     * @method execCommand\r\n     * @param { String } cmd 命令字符串\r\n     * @param { Array } videoArr 需要插入的视频的数组， 其中的每一个元素都是一个键值对对象， 描述了一个视频的所有属性\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * var videoAttr1 = {\r\n     *      //视频地址\r\n     *      url: 'http://www.youku.com/xxx',\r\n     *      //视频宽高值， 单位px\r\n     *      width: 200,\r\n     *      height: 100\r\n     * },\r\n     * videoAttr2 = {\r\n     *      //视频地址\r\n     *      url: 'http://www.youku.com/xxx',\r\n     *      //视频宽高值， 单位px\r\n     *      width: 200,\r\n     *      height: 100\r\n     * }\r\n     *\r\n     * //editor 是编辑器实例\r\n     * //该方法将会向编辑器内插入两个视频\r\n     * editor.execCommand( 'insertvideo', [ videoAttr1, videoAttr2 ] );\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 查询当前光标所在处是否是一个视频\r\n     * @command insertvideo\r\n     * @method queryCommandState\r\n     * @param { String } cmd 需要查询的命令字符串\r\n     * @return { int } 如果当前光标所在处的元素是一个视频对象， 则返回1，否则返回0\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * //editor 是编辑器实例\r\n     * editor.queryCommandState( 'insertvideo' );\r\n     * ```\r\n     */\r\n    me.commands[\"insertvideo\"] = {\r\n        execCommand: function (cmd, videoObjs, type){\r\n            videoObjs = utils.isArray(videoObjs)?videoObjs:[videoObjs];\r\n            var html = [],id = 'tmpVedio', cl;\r\n            for(var i=0,vi,len = videoObjs.length;i<len;i++){\r\n                vi = videoObjs[i];\r\n                cl = (type == 'upload' ? 'edui-upload-video video-js vjs-default-skin':'edui-faked-video');\r\n                html.push(creatInsertStr( vi.url, vi.width || 420,  vi.height || 280, id + i, null, cl, 'image'));\r\n            }\r\n            me.execCommand(\"inserthtml\",html.join(\"\"),true);\r\n            var rng = this.selection.getRange();\r\n            for(var i= 0,len=videoObjs.length;i<len;i++){\r\n                var img = this.document.getElementById('tmpVedio'+i);\r\n                domUtils.removeAttributes(img,'id');\r\n                rng.selectNode(img).select();\r\n                me.execCommand('imagefloat',videoObjs[i].align)\r\n            }\r\n        },\r\n        queryCommandState : function(){\r\n            var img = me.selection.getRange().getClosedNode(),\r\n                flag = img && (img.className == \"edui-faked-video\" || img.className.indexOf(\"edui-upload-video\")!=-1);\r\n            return flag ? 1 : 0;\r\n        }\r\n    };\r\n};\r\n\r\n\r\n// plugins/table.core.js\r\n/**\r\n * Created with JetBrains WebStorm.\r\n * User: taoqili\r\n * Date: 13-1-18\r\n * Time: 上午11:09\r\n * To change this template use File | Settings | File Templates.\r\n */\r\n/**\r\n * UE表格操作类\r\n * @param table\r\n * @constructor\r\n */\r\n(function () {\r\n    var UETable = UE.UETable = function (table) {\r\n        this.table = table;\r\n        this.indexTable = [];\r\n        this.selectedTds = [];\r\n        this.cellsRange = {};\r\n        this.update(table);\r\n    };\r\n\r\n    //===以下为静态工具方法===\r\n    UETable.removeSelectedClass = function (cells) {\r\n        utils.each(cells, function (cell) {\r\n            domUtils.removeClasses(cell, \"selectTdClass\");\r\n        })\r\n    };\r\n    UETable.addSelectedClass = function (cells) {\r\n        utils.each(cells, function (cell) {\r\n            domUtils.addClass(cell, \"selectTdClass\");\r\n        })\r\n    };\r\n    UETable.isEmptyBlock = function (node) {\r\n        var reg = new RegExp(domUtils.fillChar, 'g');\r\n        if (node[browser.ie ? 'innerText' : 'textContent'].replace(/^\\s*$/, '').replace(reg, '').length > 0) {\r\n            return 0;\r\n        }\r\n        for (var i in dtd.$isNotEmpty) if (dtd.$isNotEmpty.hasOwnProperty(i)) {\r\n            if (node.getElementsByTagName(i).length) {\r\n                return 0;\r\n            }\r\n        }\r\n        return 1;\r\n    };\r\n    UETable.getWidth = function (cell) {\r\n        if (!cell)return 0;\r\n        return parseInt(domUtils.getComputedStyle(cell, \"width\"), 10);\r\n    };\r\n\r\n    /**\r\n     * 获取单元格或者单元格组的“对齐”状态。 如果当前的检测对象是一个单元格组， 只有在满足所有单元格的 水平和竖直 对齐属性都相同的\r\n     * 条件时才会返回其状态值，否则将返回null； 如果当前只检测了一个单元格， 则直接返回当前单元格的对齐状态；\r\n     * @param table cell or table cells , 支持单个单元格dom对象 或者 单元格dom对象数组\r\n     * @return { align: 'left' || 'right' || 'center', valign: 'top' || 'middle' || 'bottom' } 或者 null\r\n     */\r\n    UETable.getTableCellAlignState = function ( cells ) {\r\n\r\n        !utils.isArray( cells ) && ( cells = [cells] );\r\n\r\n        var result = {},\r\n            status = ['align', 'valign'],\r\n            tempStatus = null,\r\n            isSame = true;//状态是否相同\r\n\r\n        utils.each( cells, function( cellNode ){\r\n\r\n            utils.each( status, function( currentState ){\r\n\r\n                tempStatus = cellNode.getAttribute( currentState );\r\n\r\n                if( !result[ currentState ] && tempStatus ) {\r\n                    result[ currentState ] = tempStatus;\r\n                } else if( !result[ currentState ] || ( tempStatus !== result[ currentState ] ) ) {\r\n                    isSame = false;\r\n                    return false;\r\n                }\r\n\r\n            } );\r\n\r\n            return isSame;\r\n\r\n        });\r\n\r\n        return isSame ? result : null;\r\n\r\n    };\r\n\r\n    /**\r\n     * 根据当前选区获取相关的table信息\r\n     * @return {Object}\r\n     */\r\n    UETable.getTableItemsByRange = function (editor) {\r\n        var start = editor.selection.getStart();\r\n\r\n        //ff下会选中bookmark\r\n        if( start && start.id && start.id.indexOf('_baidu_bookmark_start_') === 0 && start.nextSibling) {\r\n            start = start.nextSibling;\r\n        }\r\n\r\n        //在table或者td边缘有可能存在选中tr的情况\r\n        var cell = start && domUtils.findParentByTagName(start, [\"td\", \"th\"], true),\r\n            tr = cell && cell.parentNode,\r\n            caption = start && domUtils.findParentByTagName(start, 'caption', true),\r\n            table = caption ? caption.parentNode : tr && tr.parentNode.parentNode;\r\n\r\n        return {\r\n            cell:cell,\r\n            tr:tr,\r\n            table:table,\r\n            caption:caption\r\n        }\r\n    };\r\n    UETable.getUETableBySelected = function (editor) {\r\n        var table = UETable.getTableItemsByRange(editor).table;\r\n        if (table && table.ueTable && table.ueTable.selectedTds.length) {\r\n            return table.ueTable;\r\n        }\r\n        return null;\r\n    };\r\n\r\n    UETable.getDefaultValue = function (editor, table) {\r\n        var borderMap = {\r\n                thin:'0px',\r\n                medium:'1px',\r\n                thick:'2px'\r\n            },\r\n            tableBorder, tdPadding, tdBorder, tmpValue;\r\n        if (!table) {\r\n            table = editor.document.createElement('table');\r\n            table.insertRow(0).insertCell(0).innerHTML = 'xxx';\r\n            editor.body.appendChild(table);\r\n            var td = table.getElementsByTagName('td')[0];\r\n            tmpValue = domUtils.getComputedStyle(table, 'border-left-width');\r\n            tableBorder = parseInt(borderMap[tmpValue] || tmpValue, 10);\r\n            tmpValue = domUtils.getComputedStyle(td, 'padding-left');\r\n            tdPadding = parseInt(borderMap[tmpValue] || tmpValue, 10);\r\n            tmpValue = domUtils.getComputedStyle(td, 'border-left-width');\r\n            tdBorder = parseInt(borderMap[tmpValue] || tmpValue, 10);\r\n            domUtils.remove(table);\r\n            return {\r\n                tableBorder:tableBorder,\r\n                tdPadding:tdPadding,\r\n                tdBorder:tdBorder\r\n            };\r\n        } else {\r\n            td = table.getElementsByTagName('td')[0];\r\n            tmpValue = domUtils.getComputedStyle(table, 'border-left-width');\r\n            tableBorder = parseInt(borderMap[tmpValue] || tmpValue, 10);\r\n            tmpValue = domUtils.getComputedStyle(td, 'padding-left');\r\n            tdPadding = parseInt(borderMap[tmpValue] || tmpValue, 10);\r\n            tmpValue = domUtils.getComputedStyle(td, 'border-left-width');\r\n            tdBorder = parseInt(borderMap[tmpValue] || tmpValue, 10);\r\n            return {\r\n                tableBorder:tableBorder,\r\n                tdPadding:tdPadding,\r\n                tdBorder:tdBorder\r\n            };\r\n        }\r\n    };\r\n    /**\r\n     * 根据当前点击的td或者table获取索引对象\r\n     * @param tdOrTable\r\n     */\r\n    UETable.getUETable = function (tdOrTable) {\r\n        var tag = tdOrTable.tagName.toLowerCase();\r\n        tdOrTable = (tag == \"td\" || tag == \"th\" || tag == 'caption') ? domUtils.findParentByTagName(tdOrTable, \"table\", true) : tdOrTable;\r\n        if (!tdOrTable.ueTable) {\r\n            tdOrTable.ueTable = new UETable(tdOrTable);\r\n        }\r\n        return tdOrTable.ueTable;\r\n    };\r\n\r\n    UETable.cloneCell = function(cell,ignoreMerge,keepPro){\r\n        if (!cell || utils.isString(cell)) {\r\n            return this.table.ownerDocument.createElement(cell || 'td');\r\n        }\r\n        var flag = domUtils.hasClass(cell, \"selectTdClass\");\r\n        flag && domUtils.removeClasses(cell, \"selectTdClass\");\r\n        var tmpCell = cell.cloneNode(true);\r\n        if (ignoreMerge) {\r\n            tmpCell.rowSpan = tmpCell.colSpan = 1;\r\n        }\r\n        //去掉宽高\r\n        !keepPro && domUtils.removeAttributes(tmpCell,'width height');\r\n        !keepPro && domUtils.removeAttributes(tmpCell,'style');\r\n\r\n        tmpCell.style.borderLeftStyle = \"\";\r\n        tmpCell.style.borderTopStyle = \"\";\r\n        tmpCell.style.borderLeftColor = cell.style.borderRightColor;\r\n        tmpCell.style.borderLeftWidth = cell.style.borderRightWidth;\r\n        tmpCell.style.borderTopColor = cell.style.borderBottomColor;\r\n        tmpCell.style.borderTopWidth = cell.style.borderBottomWidth;\r\n        flag && domUtils.addClass(cell, \"selectTdClass\");\r\n        return tmpCell;\r\n    }\r\n\r\n    UETable.prototype = {\r\n        getMaxRows:function () {\r\n            var rows = this.table.rows, maxLen = 1;\r\n            for (var i = 0, row; row = rows[i]; i++) {\r\n                var currentMax = 1;\r\n                for (var j = 0, cj; cj = row.cells[j++];) {\r\n                    currentMax = Math.max(cj.rowSpan || 1, currentMax);\r\n                }\r\n                maxLen = Math.max(currentMax + i, maxLen);\r\n            }\r\n            return maxLen;\r\n        },\r\n        /**\r\n         * 获取当前表格的最大列数\r\n         */\r\n        getMaxCols:function () {\r\n            var rows = this.table.rows, maxLen = 0, cellRows = {};\r\n            for (var i = 0, row; row = rows[i]; i++) {\r\n                var cellsNum = 0;\r\n                for (var j = 0, cj; cj = row.cells[j++];) {\r\n                    cellsNum += (cj.colSpan || 1);\r\n                    if (cj.rowSpan && cj.rowSpan > 1) {\r\n                        for (var k = 1; k < cj.rowSpan; k++) {\r\n                            if (!cellRows['row_' + (i + k)]) {\r\n                                cellRows['row_' + (i + k)] = (cj.colSpan || 1);\r\n                            } else {\r\n                                cellRows['row_' + (i + k)]++\r\n                            }\r\n                        }\r\n\r\n                    }\r\n                }\r\n                cellsNum += cellRows['row_' + i] || 0;\r\n                maxLen = Math.max(cellsNum, maxLen);\r\n            }\r\n            return maxLen;\r\n        },\r\n        getCellColIndex:function (cell) {\r\n\r\n        },\r\n        /**\r\n         * 获取当前cell旁边的单元格，\r\n         * @param cell\r\n         * @param right\r\n         */\r\n        getHSideCell:function (cell, right) {\r\n            try {\r\n                var cellInfo = this.getCellInfo(cell),\r\n                    previewRowIndex, previewColIndex;\r\n                var len = this.selectedTds.length,\r\n                    range = this.cellsRange;\r\n                //首行或者首列没有前置单元格\r\n                if ((!right && (!len ? !cellInfo.colIndex : !range.beginColIndex)) || (right && (!len ? (cellInfo.colIndex == (this.colsNum - 1)) : (range.endColIndex == this.colsNum - 1)))) return null;\r\n\r\n                previewRowIndex = !len ? cellInfo.rowIndex : range.beginRowIndex;\r\n                previewColIndex = !right ? ( !len ? (cellInfo.colIndex < 1 ? 0 : (cellInfo.colIndex - 1)) : range.beginColIndex - 1)\r\n                    : ( !len ? cellInfo.colIndex + 1 : range.endColIndex + 1);\r\n                return this.getCell(this.indexTable[previewRowIndex][previewColIndex].rowIndex, this.indexTable[previewRowIndex][previewColIndex].cellIndex);\r\n            } catch (e) {\r\n                showError(e);\r\n            }\r\n        },\r\n        getTabNextCell:function (cell, preRowIndex) {\r\n            var cellInfo = this.getCellInfo(cell),\r\n                rowIndex = preRowIndex || cellInfo.rowIndex,\r\n                colIndex = cellInfo.colIndex + 1 + (cellInfo.colSpan - 1),\r\n                nextCell;\r\n            try {\r\n                nextCell = this.getCell(this.indexTable[rowIndex][colIndex].rowIndex, this.indexTable[rowIndex][colIndex].cellIndex);\r\n            } catch (e) {\r\n                try {\r\n                    rowIndex = rowIndex * 1 + 1;\r\n                    colIndex = 0;\r\n                    nextCell = this.getCell(this.indexTable[rowIndex][colIndex].rowIndex, this.indexTable[rowIndex][colIndex].cellIndex);\r\n                } catch (e) {\r\n                }\r\n            }\r\n            return nextCell;\r\n\r\n        },\r\n        /**\r\n         * 获取视觉上的后置单元格\r\n         * @param cell\r\n         * @param bottom\r\n         */\r\n        getVSideCell:function (cell, bottom, ignoreRange) {\r\n            try {\r\n                var cellInfo = this.getCellInfo(cell),\r\n                    nextRowIndex, nextColIndex;\r\n                var len = this.selectedTds.length && !ignoreRange,\r\n                    range = this.cellsRange;\r\n                //末行或者末列没有后置单元格\r\n                if ((!bottom && (cellInfo.rowIndex == 0)) || (bottom && (!len ? (cellInfo.rowIndex + cellInfo.rowSpan > this.rowsNum - 1) : (range.endRowIndex == this.rowsNum - 1)))) return null;\r\n\r\n                nextRowIndex = !bottom ? ( !len ? cellInfo.rowIndex - 1 : range.beginRowIndex - 1)\r\n                    : ( !len ? (cellInfo.rowIndex + cellInfo.rowSpan) : range.endRowIndex + 1);\r\n                nextColIndex = !len ? cellInfo.colIndex : range.beginColIndex;\r\n                return this.getCell(this.indexTable[nextRowIndex][nextColIndex].rowIndex, this.indexTable[nextRowIndex][nextColIndex].cellIndex);\r\n            } catch (e) {\r\n                showError(e);\r\n            }\r\n        },\r\n        /**\r\n         * 获取相同结束位置的单元格，xOrY指代了是获取x轴相同还是y轴相同\r\n         */\r\n        getSameEndPosCells:function (cell, xOrY) {\r\n            try {\r\n                var flag = (xOrY.toLowerCase() === \"x\"),\r\n                    end = domUtils.getXY(cell)[flag ? 'x' : 'y'] + cell[\"offset\" + (flag ? 'Width' : 'Height')],\r\n                    rows = this.table.rows,\r\n                    cells = null, returns = [];\r\n                for (var i = 0; i < this.rowsNum; i++) {\r\n                    cells = rows[i].cells;\r\n                    for (var j = 0, tmpCell; tmpCell = cells[j++];) {\r\n                        var tmpEnd = domUtils.getXY(tmpCell)[flag ? 'x' : 'y'] + tmpCell[\"offset\" + (flag ? 'Width' : 'Height')];\r\n                        //对应行的td已经被上面行rowSpan了\r\n                        if (tmpEnd > end && flag) break;\r\n                        if (cell == tmpCell || end == tmpEnd) {\r\n                            //只获取单一的单元格\r\n                            //todo 仅获取单一单元格在特定情况下会造成returns为空，从而影响后续的拖拽实现，修正这个。需考虑性能\r\n                            if (tmpCell[flag ? \"colSpan\" : \"rowSpan\"] == 1) {\r\n                                returns.push(tmpCell);\r\n                            }\r\n                            if (flag) break;\r\n                        }\r\n                    }\r\n                }\r\n                return returns;\r\n            } catch (e) {\r\n                showError(e);\r\n            }\r\n        },\r\n        setCellContent:function (cell, content) {\r\n            cell.innerHTML = content || (browser.ie ? domUtils.fillChar : \"<br />\");\r\n        },\r\n        cloneCell:UETable.cloneCell,\r\n        /**\r\n         * 获取跟当前单元格的右边竖线为左边的所有未合并单元格\r\n         */\r\n        getSameStartPosXCells:function (cell) {\r\n            try {\r\n                var start = domUtils.getXY(cell).x + cell.offsetWidth,\r\n                    rows = this.table.rows, cells , returns = [];\r\n                for (var i = 0; i < this.rowsNum; i++) {\r\n                    cells = rows[i].cells;\r\n                    for (var j = 0, tmpCell; tmpCell = cells[j++];) {\r\n                        var tmpStart = domUtils.getXY(tmpCell).x;\r\n                        if (tmpStart > start) break;\r\n                        if (tmpStart == start && tmpCell.colSpan == 1) {\r\n                            returns.push(tmpCell);\r\n                            break;\r\n                        }\r\n                    }\r\n                }\r\n                return returns;\r\n            } catch (e) {\r\n                showError(e);\r\n            }\r\n        },\r\n        /**\r\n         * 更新table对应的索引表\r\n         */\r\n        update:function (table) {\r\n            this.table = table || this.table;\r\n            this.selectedTds = [];\r\n            this.cellsRange = {};\r\n            this.indexTable = [];\r\n            var rows = this.table.rows,\r\n                rowsNum = this.getMaxRows(),\r\n                dNum = rowsNum - rows.length,\r\n                colsNum = this.getMaxCols();\r\n            while (dNum--) {\r\n                this.table.insertRow(rows.length);\r\n            }\r\n            this.rowsNum = rowsNum;\r\n            this.colsNum = colsNum;\r\n            for (var i = 0, len = rows.length; i < len; i++) {\r\n                this.indexTable[i] = new Array(colsNum);\r\n            }\r\n            //填充索引表\r\n            for (var rowIndex = 0, row; row = rows[rowIndex]; rowIndex++) {\r\n                for (var cellIndex = 0, cell, cells = row.cells; cell = cells[cellIndex]; cellIndex++) {\r\n                    //修正整行被rowSpan时导致的行数计算错误\r\n                    if (cell.rowSpan > rowsNum) {\r\n                        cell.rowSpan = rowsNum;\r\n                    }\r\n                    var colIndex = cellIndex,\r\n                        rowSpan = cell.rowSpan || 1,\r\n                        colSpan = cell.colSpan || 1;\r\n                    //当已经被上一行rowSpan或者被前一列colSpan了，则跳到下一个单元格进行\r\n                    while (this.indexTable[rowIndex][colIndex]) colIndex++;\r\n                    for (var j = 0; j < rowSpan; j++) {\r\n                        for (var k = 0; k < colSpan; k++) {\r\n                            this.indexTable[rowIndex + j][colIndex + k] = {\r\n                                rowIndex:rowIndex,\r\n                                cellIndex:cellIndex,\r\n                                colIndex:colIndex,\r\n                                rowSpan:rowSpan,\r\n                                colSpan:colSpan\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n            //修复残缺td\r\n            for (j = 0; j < rowsNum; j++) {\r\n                for (k = 0; k < colsNum; k++) {\r\n                    if (this.indexTable[j][k] === undefined) {\r\n                        row = rows[j];\r\n                        cell = row.cells[row.cells.length - 1];\r\n                        cell = cell ? cell.cloneNode(true) : this.table.ownerDocument.createElement(\"td\");\r\n                        this.setCellContent(cell);\r\n                        if (cell.colSpan !== 1)cell.colSpan = 1;\r\n                        if (cell.rowSpan !== 1)cell.rowSpan = 1;\r\n                        row.appendChild(cell);\r\n                        this.indexTable[j][k] = {\r\n                            rowIndex:j,\r\n                            cellIndex:cell.cellIndex,\r\n                            colIndex:k,\r\n                            rowSpan:1,\r\n                            colSpan:1\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n            //当框选后删除行或者列后撤销，需要重建选区。\r\n            var tds = domUtils.getElementsByTagName(this.table, \"td\"),\r\n                selectTds = [];\r\n            utils.each(tds, function (td) {\r\n                if (domUtils.hasClass(td, \"selectTdClass\")) {\r\n                    selectTds.push(td);\r\n                }\r\n            });\r\n            if (selectTds.length) {\r\n                var start = selectTds[0],\r\n                    end = selectTds[selectTds.length - 1],\r\n                    startInfo = this.getCellInfo(start),\r\n                    endInfo = this.getCellInfo(end);\r\n                this.selectedTds = selectTds;\r\n                this.cellsRange = {\r\n                    beginRowIndex:startInfo.rowIndex,\r\n                    beginColIndex:startInfo.colIndex,\r\n                    endRowIndex:endInfo.rowIndex + endInfo.rowSpan - 1,\r\n                    endColIndex:endInfo.colIndex + endInfo.colSpan - 1\r\n                };\r\n            }\r\n            //给第一行设置firstRow的样式名称,在排序图标的样式上使用到\r\n            if(!domUtils.hasClass(this.table.rows[0], \"firstRow\")) {\r\n                domUtils.addClass(this.table.rows[0], \"firstRow\");\r\n                for(var i = 1; i< this.table.rows.length; i++) {\r\n                    domUtils.removeClasses(this.table.rows[i], \"firstRow\");\r\n                }\r\n            }\r\n        },\r\n        /**\r\n         * 获取单元格的索引信息\r\n         */\r\n        getCellInfo:function (cell) {\r\n            if (!cell) return;\r\n            var cellIndex = cell.cellIndex,\r\n                rowIndex = cell.parentNode.rowIndex,\r\n                rowInfo = this.indexTable[rowIndex],\r\n                numCols = this.colsNum;\r\n            for (var colIndex = cellIndex; colIndex < numCols; colIndex++) {\r\n                var cellInfo = rowInfo[colIndex];\r\n                if (cellInfo.rowIndex === rowIndex && cellInfo.cellIndex === cellIndex) {\r\n                    return cellInfo;\r\n                }\r\n            }\r\n        },\r\n        /**\r\n         * 根据行列号获取单元格\r\n         */\r\n        getCell:function (rowIndex, cellIndex) {\r\n            return rowIndex < this.rowsNum && this.table.rows[rowIndex].cells[cellIndex] || null;\r\n        },\r\n        /**\r\n         * 删除单元格\r\n         */\r\n        deleteCell:function (cell, rowIndex) {\r\n            rowIndex = typeof rowIndex == 'number' ? rowIndex : cell.parentNode.rowIndex;\r\n            var row = this.table.rows[rowIndex];\r\n            row.deleteCell(cell.cellIndex);\r\n        },\r\n        /**\r\n         * 根据始末两个单元格获取被框选的所有单元格范围\r\n         */\r\n        getCellsRange:function (cellA, cellB) {\r\n            function checkRange(beginRowIndex, beginColIndex, endRowIndex, endColIndex) {\r\n                var tmpBeginRowIndex = beginRowIndex,\r\n                    tmpBeginColIndex = beginColIndex,\r\n                    tmpEndRowIndex = endRowIndex,\r\n                    tmpEndColIndex = endColIndex,\r\n                    cellInfo, colIndex, rowIndex;\r\n                // 通过indexTable检查是否存在超出TableRange上边界的情况\r\n                if (beginRowIndex > 0) {\r\n                    for (colIndex = beginColIndex; colIndex < endColIndex; colIndex++) {\r\n                        cellInfo = me.indexTable[beginRowIndex][colIndex];\r\n                        rowIndex = cellInfo.rowIndex;\r\n                        if (rowIndex < beginRowIndex) {\r\n                            tmpBeginRowIndex = Math.min(rowIndex, tmpBeginRowIndex);\r\n                        }\r\n                    }\r\n                }\r\n                // 通过indexTable检查是否存在超出TableRange右边界的情况\r\n                if (endColIndex < me.colsNum) {\r\n                    for (rowIndex = beginRowIndex; rowIndex < endRowIndex; rowIndex++) {\r\n                        cellInfo = me.indexTable[rowIndex][endColIndex];\r\n                        colIndex = cellInfo.colIndex + cellInfo.colSpan - 1;\r\n                        if (colIndex > endColIndex) {\r\n                            tmpEndColIndex = Math.max(colIndex, tmpEndColIndex);\r\n                        }\r\n                    }\r\n                }\r\n                // 检查是否有超出TableRange下边界的情况\r\n                if (endRowIndex < me.rowsNum) {\r\n                    for (colIndex = beginColIndex; colIndex < endColIndex; colIndex++) {\r\n                        cellInfo = me.indexTable[endRowIndex][colIndex];\r\n                        rowIndex = cellInfo.rowIndex + cellInfo.rowSpan - 1;\r\n                        if (rowIndex > endRowIndex) {\r\n                            tmpEndRowIndex = Math.max(rowIndex, tmpEndRowIndex);\r\n                        }\r\n                    }\r\n                }\r\n                // 检查是否有超出TableRange左边界的情况\r\n                if (beginColIndex > 0) {\r\n                    for (rowIndex = beginRowIndex; rowIndex < endRowIndex; rowIndex++) {\r\n                        cellInfo = me.indexTable[rowIndex][beginColIndex];\r\n                        colIndex = cellInfo.colIndex;\r\n                        if (colIndex < beginColIndex) {\r\n                            tmpBeginColIndex = Math.min(cellInfo.colIndex, tmpBeginColIndex);\r\n                        }\r\n                    }\r\n                }\r\n                //递归调用直至所有完成所有框选单元格的扩展\r\n                if (tmpBeginRowIndex != beginRowIndex || tmpBeginColIndex != beginColIndex || tmpEndRowIndex != endRowIndex || tmpEndColIndex != endColIndex) {\r\n                    return checkRange(tmpBeginRowIndex, tmpBeginColIndex, tmpEndRowIndex, tmpEndColIndex);\r\n                } else {\r\n                    // 不需要扩展TableRange的情况\r\n                    return {\r\n                        beginRowIndex:beginRowIndex,\r\n                        beginColIndex:beginColIndex,\r\n                        endRowIndex:endRowIndex,\r\n                        endColIndex:endColIndex\r\n                    };\r\n                }\r\n            }\r\n\r\n            try {\r\n                var me = this,\r\n                    cellAInfo = me.getCellInfo(cellA);\r\n                if (cellA === cellB) {\r\n                    return {\r\n                        beginRowIndex:cellAInfo.rowIndex,\r\n                        beginColIndex:cellAInfo.colIndex,\r\n                        endRowIndex:cellAInfo.rowIndex + cellAInfo.rowSpan - 1,\r\n                        endColIndex:cellAInfo.colIndex + cellAInfo.colSpan - 1\r\n                    };\r\n                }\r\n                var cellBInfo = me.getCellInfo(cellB);\r\n                // 计算TableRange的四个边\r\n                var beginRowIndex = Math.min(cellAInfo.rowIndex, cellBInfo.rowIndex),\r\n                    beginColIndex = Math.min(cellAInfo.colIndex, cellBInfo.colIndex),\r\n                    endRowIndex = Math.max(cellAInfo.rowIndex + cellAInfo.rowSpan - 1, cellBInfo.rowIndex + cellBInfo.rowSpan - 1),\r\n                    endColIndex = Math.max(cellAInfo.colIndex + cellAInfo.colSpan - 1, cellBInfo.colIndex + cellBInfo.colSpan - 1);\r\n\r\n                return checkRange(beginRowIndex, beginColIndex, endRowIndex, endColIndex);\r\n            } catch (e) {\r\n                //throw e;\r\n            }\r\n        },\r\n        /**\r\n         * 依据cellsRange获取对应的单元格集合\r\n         */\r\n        getCells:function (range) {\r\n            //每次获取cells之前必须先清除上次的选择，否则会对后续获取操作造成影响\r\n            this.clearSelected();\r\n            var beginRowIndex = range.beginRowIndex,\r\n                beginColIndex = range.beginColIndex,\r\n                endRowIndex = range.endRowIndex,\r\n                endColIndex = range.endColIndex,\r\n                cellInfo, rowIndex, colIndex, tdHash = {}, returnTds = [];\r\n            for (var i = beginRowIndex; i <= endRowIndex; i++) {\r\n                for (var j = beginColIndex; j <= endColIndex; j++) {\r\n                    cellInfo = this.indexTable[i][j];\r\n                    rowIndex = cellInfo.rowIndex;\r\n                    colIndex = cellInfo.colIndex;\r\n                    // 如果Cells里已经包含了此Cell则跳过\r\n                    var key = rowIndex + '|' + colIndex;\r\n                    if (tdHash[key]) continue;\r\n                    tdHash[key] = 1;\r\n                    if (rowIndex < i || colIndex < j || rowIndex + cellInfo.rowSpan - 1 > endRowIndex || colIndex + cellInfo.colSpan - 1 > endColIndex) {\r\n                        return null;\r\n                    }\r\n                    returnTds.push(this.getCell(rowIndex, cellInfo.cellIndex));\r\n                }\r\n            }\r\n            return returnTds;\r\n        },\r\n        /**\r\n         * 清理已经选中的单元格\r\n         */\r\n        clearSelected:function () {\r\n            UETable.removeSelectedClass(this.selectedTds);\r\n            this.selectedTds = [];\r\n            this.cellsRange = {};\r\n        },\r\n        /**\r\n         * 根据range设置已经选中的单元格\r\n         */\r\n        setSelected:function (range) {\r\n            var cells = this.getCells(range);\r\n            UETable.addSelectedClass(cells);\r\n            this.selectedTds = cells;\r\n            this.cellsRange = range;\r\n        },\r\n        isFullRow:function () {\r\n            var range = this.cellsRange;\r\n            return (range.endColIndex - range.beginColIndex + 1) == this.colsNum;\r\n        },\r\n        isFullCol:function () {\r\n            var range = this.cellsRange,\r\n                table = this.table,\r\n                ths = table.getElementsByTagName(\"th\"),\r\n                rows = range.endRowIndex - range.beginRowIndex + 1;\r\n            return  !ths.length ? rows == this.rowsNum : rows == this.rowsNum || (rows == this.rowsNum - 1);\r\n\r\n        },\r\n        /**\r\n         * 获取视觉上的前置单元格，默认是左边，top传入时\r\n         * @param cell\r\n         * @param top\r\n         */\r\n        getNextCell:function (cell, bottom, ignoreRange) {\r\n            try {\r\n                var cellInfo = this.getCellInfo(cell),\r\n                    nextRowIndex, nextColIndex;\r\n                var len = this.selectedTds.length && !ignoreRange,\r\n                    range = this.cellsRange;\r\n                //末行或者末列没有后置单元格\r\n                if ((!bottom && (cellInfo.rowIndex == 0)) || (bottom && (!len ? (cellInfo.rowIndex + cellInfo.rowSpan > this.rowsNum - 1) : (range.endRowIndex == this.rowsNum - 1)))) return null;\r\n\r\n                nextRowIndex = !bottom ? ( !len ? cellInfo.rowIndex - 1 : range.beginRowIndex - 1)\r\n                    : ( !len ? (cellInfo.rowIndex + cellInfo.rowSpan) : range.endRowIndex + 1);\r\n                nextColIndex = !len ? cellInfo.colIndex : range.beginColIndex;\r\n                return this.getCell(this.indexTable[nextRowIndex][nextColIndex].rowIndex, this.indexTable[nextRowIndex][nextColIndex].cellIndex);\r\n            } catch (e) {\r\n                showError(e);\r\n            }\r\n        },\r\n        getPreviewCell:function (cell, top) {\r\n            try {\r\n                var cellInfo = this.getCellInfo(cell),\r\n                    previewRowIndex, previewColIndex;\r\n                var len = this.selectedTds.length,\r\n                    range = this.cellsRange;\r\n                //首行或者首列没有前置单元格\r\n                if ((!top && (!len ? !cellInfo.colIndex : !range.beginColIndex)) || (top && (!len ? (cellInfo.rowIndex > (this.colsNum - 1)) : (range.endColIndex == this.colsNum - 1)))) return null;\r\n\r\n                previewRowIndex = !top ? ( !len ? cellInfo.rowIndex : range.beginRowIndex )\r\n                    : ( !len ? (cellInfo.rowIndex < 1 ? 0 : (cellInfo.rowIndex - 1)) : range.beginRowIndex);\r\n                previewColIndex = !top ? ( !len ? (cellInfo.colIndex < 1 ? 0 : (cellInfo.colIndex - 1)) : range.beginColIndex - 1)\r\n                    : ( !len ? cellInfo.colIndex : range.endColIndex + 1);\r\n                return this.getCell(this.indexTable[previewRowIndex][previewColIndex].rowIndex, this.indexTable[previewRowIndex][previewColIndex].cellIndex);\r\n            } catch (e) {\r\n                showError(e);\r\n            }\r\n        },\r\n        /**\r\n         * 移动单元格中的内容\r\n         */\r\n        moveContent:function (cellTo, cellFrom) {\r\n            if (UETable.isEmptyBlock(cellFrom)) return;\r\n            if (UETable.isEmptyBlock(cellTo)) {\r\n                cellTo.innerHTML = cellFrom.innerHTML;\r\n                return;\r\n            }\r\n            var child = cellTo.lastChild;\r\n            if (child.nodeType == 3 || !dtd.$block[child.tagName]) {\r\n                cellTo.appendChild(cellTo.ownerDocument.createElement('br'))\r\n            }\r\n            while (child = cellFrom.firstChild) {\r\n                cellTo.appendChild(child);\r\n            }\r\n        },\r\n        /**\r\n         * 向右合并单元格\r\n         */\r\n        mergeRight:function (cell) {\r\n            var cellInfo = this.getCellInfo(cell),\r\n                rightColIndex = cellInfo.colIndex + cellInfo.colSpan,\r\n                rightCellInfo = this.indexTable[cellInfo.rowIndex][rightColIndex],\r\n                rightCell = this.getCell(rightCellInfo.rowIndex, rightCellInfo.cellIndex);\r\n            //合并\r\n            cell.colSpan = cellInfo.colSpan + rightCellInfo.colSpan;\r\n            //被合并的单元格不应存在宽度属性\r\n            cell.removeAttribute(\"width\");\r\n            //移动内容\r\n            this.moveContent(cell, rightCell);\r\n            //删掉被合并的Cell\r\n            this.deleteCell(rightCell, rightCellInfo.rowIndex);\r\n            this.update();\r\n        },\r\n        /**\r\n         * 向下合并单元格\r\n         */\r\n        mergeDown:function (cell) {\r\n            var cellInfo = this.getCellInfo(cell),\r\n                downRowIndex = cellInfo.rowIndex + cellInfo.rowSpan,\r\n                downCellInfo = this.indexTable[downRowIndex][cellInfo.colIndex],\r\n                downCell = this.getCell(downCellInfo.rowIndex, downCellInfo.cellIndex);\r\n            cell.rowSpan = cellInfo.rowSpan + downCellInfo.rowSpan;\r\n            cell.removeAttribute(\"height\");\r\n            this.moveContent(cell, downCell);\r\n            this.deleteCell(downCell, downCellInfo.rowIndex);\r\n            this.update();\r\n        },\r\n        /**\r\n         * 合并整个range中的内容\r\n         */\r\n        mergeRange:function () {\r\n            //由于合并操作可以在任意时刻进行，所以无法通过鼠标位置等信息实时生成range，只能通过缓存实例中的cellsRange对象来访问\r\n            var range = this.cellsRange,\r\n                leftTopCell = this.getCell(range.beginRowIndex, this.indexTable[range.beginRowIndex][range.beginColIndex].cellIndex);\r\n\r\n            if (leftTopCell.tagName == \"TH\" && range.endRowIndex !== range.beginRowIndex) {\r\n                var index = this.indexTable,\r\n                    info = this.getCellInfo(leftTopCell);\r\n                leftTopCell = this.getCell(1, index[1][info.colIndex].cellIndex);\r\n                range = this.getCellsRange(leftTopCell, this.getCell(index[this.rowsNum - 1][info.colIndex].rowIndex, index[this.rowsNum - 1][info.colIndex].cellIndex));\r\n            }\r\n\r\n            // 删除剩余的Cells\r\n            var cells = this.getCells(range);\r\n            for(var i= 0,ci;ci=cells[i++];){\r\n                if (ci !== leftTopCell) {\r\n                    this.moveContent(leftTopCell, ci);\r\n                    this.deleteCell(ci);\r\n                }\r\n            }\r\n            // 修改左上角Cell的rowSpan和colSpan，并调整宽度属性设置\r\n            leftTopCell.rowSpan = range.endRowIndex - range.beginRowIndex + 1;\r\n            leftTopCell.rowSpan > 1 && leftTopCell.removeAttribute(\"height\");\r\n            leftTopCell.colSpan = range.endColIndex - range.beginColIndex + 1;\r\n            leftTopCell.colSpan > 1 && leftTopCell.removeAttribute(\"width\");\r\n            if (leftTopCell.rowSpan == this.rowsNum && leftTopCell.colSpan != 1) {\r\n                leftTopCell.colSpan = 1;\r\n            }\r\n\r\n            if (leftTopCell.colSpan == this.colsNum && leftTopCell.rowSpan != 1) {\r\n                var rowIndex = leftTopCell.parentNode.rowIndex;\r\n                //解决IE下的表格操作问题\r\n                if( this.table.deleteRow ) {\r\n                    for (var i = rowIndex+ 1, curIndex=rowIndex+ 1, len=leftTopCell.rowSpan; i < len; i++) {\r\n                        this.table.deleteRow(curIndex);\r\n                    }\r\n                } else {\r\n                    for (var i = 0, len=leftTopCell.rowSpan - 1; i < len; i++) {\r\n                        var row = this.table.rows[rowIndex + 1];\r\n                        row.parentNode.removeChild(row);\r\n                    }\r\n                }\r\n                leftTopCell.rowSpan = 1;\r\n            }\r\n            this.update();\r\n        },\r\n        /**\r\n         * 插入一行单元格\r\n         */\r\n        insertRow:function (rowIndex, sourceCell) {\r\n            var numCols = this.colsNum,\r\n                table = this.table,\r\n                row = table.insertRow(rowIndex), cell,\r\n                isInsertTitle = typeof sourceCell == 'string' && sourceCell.toUpperCase() == 'TH';\r\n\r\n            function replaceTdToTh(colIndex, cell, tableRow) {\r\n                if (colIndex == 0) {\r\n                    var tr = tableRow.nextSibling || tableRow.previousSibling,\r\n                        th = tr.cells[colIndex];\r\n                    if (th.tagName == 'TH') {\r\n                        th = cell.ownerDocument.createElement(\"th\");\r\n                        th.appendChild(cell.firstChild);\r\n                        tableRow.insertBefore(th, cell);\r\n                        domUtils.remove(cell)\r\n                    }\r\n                }else{\r\n                    if (cell.tagName == 'TH') {\r\n                        var td = cell.ownerDocument.createElement(\"td\");\r\n                        td.appendChild(cell.firstChild);\r\n                        tableRow.insertBefore(td, cell);\r\n                        domUtils.remove(cell)\r\n                    }\r\n                }\r\n            }\r\n\r\n            //首行直接插入,无需考虑部分单元格被rowspan的情况\r\n            if (rowIndex == 0 || rowIndex == this.rowsNum) {\r\n                for (var colIndex = 0; colIndex < numCols; colIndex++) {\r\n                    cell = this.cloneCell(sourceCell, true);\r\n                    this.setCellContent(cell);\r\n                    cell.getAttribute('vAlign') && cell.setAttribute('vAlign', cell.getAttribute('vAlign'));\r\n                    row.appendChild(cell);\r\n                    if(!isInsertTitle) replaceTdToTh(colIndex, cell, row);\r\n                }\r\n            } else {\r\n                var infoRow = this.indexTable[rowIndex],\r\n                    cellIndex = 0;\r\n                for (colIndex = 0; colIndex < numCols; colIndex++) {\r\n                    var cellInfo = infoRow[colIndex];\r\n                    //如果存在某个单元格的rowspan穿过待插入行的位置，则修改该单元格的rowspan即可，无需插入单元格\r\n                    if (cellInfo.rowIndex < rowIndex) {\r\n                        cell = this.getCell(cellInfo.rowIndex, cellInfo.cellIndex);\r\n                        cell.rowSpan = cellInfo.rowSpan + 1;\r\n                    } else {\r\n                        cell = this.cloneCell(sourceCell, true);\r\n                        this.setCellContent(cell);\r\n                        row.appendChild(cell);\r\n                    }\r\n                    if(!isInsertTitle) replaceTdToTh(colIndex, cell, row);\r\n                }\r\n            }\r\n            //框选时插入不触发contentchange，需要手动更新索引。\r\n            this.update();\r\n            return row;\r\n        },\r\n        /**\r\n         * 删除一行单元格\r\n         * @param rowIndex\r\n         */\r\n        deleteRow:function (rowIndex) {\r\n            var row = this.table.rows[rowIndex],\r\n                infoRow = this.indexTable[rowIndex],\r\n                colsNum = this.colsNum,\r\n                count = 0;     //处理计数\r\n            for (var colIndex = 0; colIndex < colsNum;) {\r\n                var cellInfo = infoRow[colIndex],\r\n                    cell = this.getCell(cellInfo.rowIndex, cellInfo.cellIndex);\r\n                if (cell.rowSpan > 1) {\r\n                    if (cellInfo.rowIndex == rowIndex) {\r\n                        var clone = cell.cloneNode(true);\r\n                        clone.rowSpan = cell.rowSpan - 1;\r\n                        clone.innerHTML = \"\";\r\n                        cell.rowSpan = 1;\r\n                        var nextRowIndex = rowIndex + 1,\r\n                            nextRow = this.table.rows[nextRowIndex],\r\n                            insertCellIndex,\r\n                            preMerged = this.getPreviewMergedCellsNum(nextRowIndex, colIndex) - count;\r\n                        if (preMerged < colIndex) {\r\n                            insertCellIndex = colIndex - preMerged - 1;\r\n                            //nextRow.insertCell(insertCellIndex);\r\n                            domUtils.insertAfter(nextRow.cells[insertCellIndex], clone);\r\n                        } else {\r\n                            if (nextRow.cells.length) nextRow.insertBefore(clone, nextRow.cells[0])\r\n                        }\r\n                        count += 1;\r\n                        //cell.parentNode.removeChild(cell);\r\n                    }\r\n                }\r\n                colIndex += cell.colSpan || 1;\r\n            }\r\n            var deleteTds = [], cacheMap = {};\r\n            for (colIndex = 0; colIndex < colsNum; colIndex++) {\r\n                var tmpRowIndex = infoRow[colIndex].rowIndex,\r\n                    tmpCellIndex = infoRow[colIndex].cellIndex,\r\n                    key = tmpRowIndex + \"_\" + tmpCellIndex;\r\n                if (cacheMap[key])continue;\r\n                cacheMap[key] = 1;\r\n                cell = this.getCell(tmpRowIndex, tmpCellIndex);\r\n                deleteTds.push(cell);\r\n            }\r\n            var mergeTds = [];\r\n            utils.each(deleteTds, function (td) {\r\n                if (td.rowSpan == 1) {\r\n                    td.parentNode.removeChild(td);\r\n                } else {\r\n                    mergeTds.push(td);\r\n                }\r\n            });\r\n            utils.each(mergeTds, function (td) {\r\n                td.rowSpan--;\r\n            });\r\n            row.parentNode.removeChild(row);\r\n            //浏览器方法本身存在bug,采用自定义方法删除\r\n            //this.table.deleteRow(rowIndex);\r\n            this.update();\r\n        },\r\n        insertCol:function (colIndex, sourceCell, defaultValue) {\r\n            var rowsNum = this.rowsNum,\r\n                rowIndex = 0,\r\n                tableRow, cell,\r\n                backWidth = parseInt((this.table.offsetWidth - (this.colsNum + 1) * 20 - (this.colsNum + 1)) / (this.colsNum + 1), 10),\r\n                isInsertTitleCol = typeof sourceCell == 'string' && sourceCell.toUpperCase() == 'TH';\r\n\r\n            function replaceTdToTh(rowIndex, cell, tableRow) {\r\n                if (rowIndex == 0) {\r\n                    var th = cell.nextSibling || cell.previousSibling;\r\n                    if (th.tagName == 'TH') {\r\n                        th = cell.ownerDocument.createElement(\"th\");\r\n                        th.appendChild(cell.firstChild);\r\n                        tableRow.insertBefore(th, cell);\r\n                        domUtils.remove(cell)\r\n                    }\r\n                }else{\r\n                    if (cell.tagName == 'TH') {\r\n                        var td = cell.ownerDocument.createElement(\"td\");\r\n                        td.appendChild(cell.firstChild);\r\n                        tableRow.insertBefore(td, cell);\r\n                        domUtils.remove(cell)\r\n                    }\r\n                }\r\n            }\r\n\r\n            var preCell;\r\n            if (colIndex == 0 || colIndex == this.colsNum) {\r\n                for (; rowIndex < rowsNum; rowIndex++) {\r\n                    tableRow = this.table.rows[rowIndex];\r\n                    preCell = tableRow.cells[colIndex == 0 ? colIndex : tableRow.cells.length];\r\n                    cell = this.cloneCell(sourceCell, true); //tableRow.insertCell(colIndex == 0 ? colIndex : tableRow.cells.length);\r\n                    this.setCellContent(cell);\r\n                    cell.setAttribute('vAlign', cell.getAttribute('vAlign'));\r\n                    preCell && cell.setAttribute('width', preCell.getAttribute('width'));\r\n                    if (!colIndex) {\r\n                        tableRow.insertBefore(cell, tableRow.cells[0]);\r\n                    } else {\r\n                        domUtils.insertAfter(tableRow.cells[tableRow.cells.length - 1], cell);\r\n                    }\r\n                    if(!isInsertTitleCol) replaceTdToTh(rowIndex, cell, tableRow)\r\n                }\r\n            } else {\r\n                for (; rowIndex < rowsNum; rowIndex++) {\r\n                    var cellInfo = this.indexTable[rowIndex][colIndex];\r\n                    if (cellInfo.colIndex < colIndex) {\r\n                        cell = this.getCell(cellInfo.rowIndex, cellInfo.cellIndex);\r\n                        cell.colSpan = cellInfo.colSpan + 1;\r\n                    } else {\r\n                        tableRow = this.table.rows[rowIndex];\r\n                        preCell = tableRow.cells[cellInfo.cellIndex];\r\n\r\n                        cell = this.cloneCell(sourceCell, true);//tableRow.insertCell(cellInfo.cellIndex);\r\n                        this.setCellContent(cell);\r\n                        cell.setAttribute('vAlign', cell.getAttribute('vAlign'));\r\n                        preCell && cell.setAttribute('width', preCell.getAttribute('width'));\r\n                        //防止IE下报错\r\n                        preCell ? tableRow.insertBefore(cell, preCell) : tableRow.appendChild(cell);\r\n                    }\r\n                    if(!isInsertTitleCol) replaceTdToTh(rowIndex, cell, tableRow);\r\n                }\r\n            }\r\n            //框选时插入不触发contentchange，需要手动更新索引\r\n            this.update();\r\n            this.updateWidth(backWidth, defaultValue || {tdPadding:10, tdBorder:1});\r\n        },\r\n        updateWidth:function (width, defaultValue) {\r\n            var table = this.table,\r\n                tmpWidth = UETable.getWidth(table) - defaultValue.tdPadding * 2 - defaultValue.tdBorder + width;\r\n            if (tmpWidth < table.ownerDocument.body.offsetWidth) {\r\n                table.setAttribute(\"width\", tmpWidth);\r\n                return;\r\n            }\r\n            var tds = domUtils.getElementsByTagName(this.table, \"td th\");\r\n            utils.each(tds, function (td) {\r\n                td.setAttribute(\"width\", width);\r\n            })\r\n        },\r\n        deleteCol:function (colIndex) {\r\n            var indexTable = this.indexTable,\r\n                tableRows = this.table.rows,\r\n                backTableWidth = this.table.getAttribute(\"width\"),\r\n                backTdWidth = 0,\r\n                rowsNum = this.rowsNum,\r\n                cacheMap = {};\r\n            for (var rowIndex = 0; rowIndex < rowsNum;) {\r\n                var infoRow = indexTable[rowIndex],\r\n                    cellInfo = infoRow[colIndex],\r\n                    key = cellInfo.rowIndex + '_' + cellInfo.colIndex;\r\n                // 跳过已经处理过的Cell\r\n                if (cacheMap[key])continue;\r\n                cacheMap[key] = 1;\r\n                var cell = this.getCell(cellInfo.rowIndex, cellInfo.cellIndex);\r\n                if (!backTdWidth) backTdWidth = cell && parseInt(cell.offsetWidth / cell.colSpan, 10).toFixed(0);\r\n                // 如果Cell的colSpan大于1, 就修改colSpan, 否则就删掉这个Cell\r\n                if (cell.colSpan > 1) {\r\n                    cell.colSpan--;\r\n                } else {\r\n                    tableRows[rowIndex].deleteCell(cellInfo.cellIndex);\r\n                }\r\n                rowIndex += cellInfo.rowSpan || 1;\r\n            }\r\n            this.table.setAttribute(\"width\", backTableWidth - backTdWidth);\r\n            this.update();\r\n        },\r\n        splitToCells:function (cell) {\r\n            var me = this,\r\n                cells = this.splitToRows(cell);\r\n            utils.each(cells, function (cell) {\r\n                me.splitToCols(cell);\r\n            })\r\n        },\r\n        splitToRows:function (cell) {\r\n            var cellInfo = this.getCellInfo(cell),\r\n                rowIndex = cellInfo.rowIndex,\r\n                colIndex = cellInfo.colIndex,\r\n                results = [];\r\n            // 修改Cell的rowSpan\r\n            cell.rowSpan = 1;\r\n            results.push(cell);\r\n            // 补齐单元格\r\n            for (var i = rowIndex, endRow = rowIndex + cellInfo.rowSpan; i < endRow; i++) {\r\n                if (i == rowIndex)continue;\r\n                var tableRow = this.table.rows[i],\r\n                    tmpCell = tableRow.insertCell(colIndex - this.getPreviewMergedCellsNum(i, colIndex));\r\n                tmpCell.colSpan = cellInfo.colSpan;\r\n                this.setCellContent(tmpCell);\r\n                tmpCell.setAttribute('vAlign', cell.getAttribute('vAlign'));\r\n                tmpCell.setAttribute('align', cell.getAttribute('align'));\r\n                if (cell.style.cssText) {\r\n                    tmpCell.style.cssText = cell.style.cssText;\r\n                }\r\n                results.push(tmpCell);\r\n            }\r\n            this.update();\r\n            return results;\r\n        },\r\n        getPreviewMergedCellsNum:function (rowIndex, colIndex) {\r\n            var indexRow = this.indexTable[rowIndex],\r\n                num = 0;\r\n            for (var i = 0; i < colIndex;) {\r\n                var colSpan = indexRow[i].colSpan,\r\n                    tmpRowIndex = indexRow[i].rowIndex;\r\n                num += (colSpan - (tmpRowIndex == rowIndex ? 1 : 0));\r\n                i += colSpan;\r\n            }\r\n            return num;\r\n        },\r\n        splitToCols:function (cell) {\r\n            var backWidth = (cell.offsetWidth / cell.colSpan - 22).toFixed(0),\r\n\r\n                cellInfo = this.getCellInfo(cell),\r\n                rowIndex = cellInfo.rowIndex,\r\n                colIndex = cellInfo.colIndex,\r\n                results = [];\r\n            // 修改Cell的rowSpan\r\n            cell.colSpan = 1;\r\n            cell.setAttribute(\"width\", backWidth);\r\n            results.push(cell);\r\n            // 补齐单元格\r\n            for (var j = colIndex, endCol = colIndex + cellInfo.colSpan; j < endCol; j++) {\r\n                if (j == colIndex)continue;\r\n                var tableRow = this.table.rows[rowIndex],\r\n                    tmpCell = tableRow.insertCell(this.indexTable[rowIndex][j].cellIndex + 1);\r\n                tmpCell.rowSpan = cellInfo.rowSpan;\r\n                this.setCellContent(tmpCell);\r\n                tmpCell.setAttribute('vAlign', cell.getAttribute('vAlign'));\r\n                tmpCell.setAttribute('align', cell.getAttribute('align'));\r\n                tmpCell.setAttribute('width', backWidth);\r\n                if (cell.style.cssText) {\r\n                    tmpCell.style.cssText = cell.style.cssText;\r\n                }\r\n                //处理th的情况\r\n                if (cell.tagName == 'TH') {\r\n                    var th = cell.ownerDocument.createElement('th');\r\n                    th.appendChild(tmpCell.firstChild);\r\n                    th.setAttribute('vAlign', cell.getAttribute('vAlign'));\r\n                    th.rowSpan = tmpCell.rowSpan;\r\n                    tableRow.insertBefore(th, tmpCell);\r\n                    domUtils.remove(tmpCell);\r\n                }\r\n                results.push(tmpCell);\r\n            }\r\n            this.update();\r\n            return results;\r\n        },\r\n        isLastCell:function (cell, rowsNum, colsNum) {\r\n            rowsNum = rowsNum || this.rowsNum;\r\n            colsNum = colsNum || this.colsNum;\r\n            var cellInfo = this.getCellInfo(cell);\r\n            return ((cellInfo.rowIndex + cellInfo.rowSpan) == rowsNum) &&\r\n                ((cellInfo.colIndex + cellInfo.colSpan) == colsNum);\r\n        },\r\n        getLastCell:function (cells) {\r\n            cells = cells || this.table.getElementsByTagName(\"td\");\r\n            var firstInfo = this.getCellInfo(cells[0]);\r\n            var me = this, last = cells[0],\r\n                tr = last.parentNode,\r\n                cellsNum = 0, cols = 0, rows;\r\n            utils.each(cells, function (cell) {\r\n                if (cell.parentNode == tr)cols += cell.colSpan || 1;\r\n                cellsNum += cell.rowSpan * cell.colSpan || 1;\r\n            });\r\n            rows = cellsNum / cols;\r\n            utils.each(cells, function (cell) {\r\n                if (me.isLastCell(cell, rows, cols)) {\r\n                    last = cell;\r\n                    return false;\r\n                }\r\n            });\r\n            return last;\r\n\r\n        },\r\n        selectRow:function (rowIndex) {\r\n            var indexRow = this.indexTable[rowIndex],\r\n                start = this.getCell(indexRow[0].rowIndex, indexRow[0].cellIndex),\r\n                end = this.getCell(indexRow[this.colsNum - 1].rowIndex, indexRow[this.colsNum - 1].cellIndex),\r\n                range = this.getCellsRange(start, end);\r\n            this.setSelected(range);\r\n        },\r\n        selectTable:function () {\r\n            var tds = this.table.getElementsByTagName(\"td\"),\r\n                range = this.getCellsRange(tds[0], tds[tds.length - 1]);\r\n            this.setSelected(range);\r\n        },\r\n        setBackground:function (cells, value) {\r\n            if (typeof value === \"string\") {\r\n                utils.each(cells, function (cell) {\r\n                    cell.style.backgroundColor = value;\r\n                })\r\n            } else if (typeof value === \"object\") {\r\n                value = utils.extend({\r\n                    repeat:true,\r\n                    colorList:[\"#ddd\", \"#fff\"]\r\n                }, value);\r\n                var rowIndex = this.getCellInfo(cells[0]).rowIndex,\r\n                    count = 0,\r\n                    colors = value.colorList,\r\n                    getColor = function (list, index, repeat) {\r\n                        return list[index] ? list[index] : repeat ? list[index % list.length] : \"\";\r\n                    };\r\n                for (var i = 0, cell; cell = cells[i++];) {\r\n                    var cellInfo = this.getCellInfo(cell);\r\n                    cell.style.backgroundColor = getColor(colors, ((rowIndex + count) == cellInfo.rowIndex) ? count : ++count, value.repeat);\r\n                }\r\n            }\r\n        },\r\n        removeBackground:function (cells) {\r\n            utils.each(cells, function (cell) {\r\n                cell.style.backgroundColor = \"\";\r\n            })\r\n        }\r\n\r\n\r\n    };\r\n    function showError(e) {\r\n    }\r\n})();\r\n\r\n// plugins/table.cmds.js\r\n/**\r\n * Created with JetBrains PhpStorm.\r\n * User: taoqili\r\n * Date: 13-2-20\r\n * Time: 下午6:25\r\n * To change this template use File | Settings | File Templates.\r\n */\r\n;\r\n(function () {\r\n    var UT = UE.UETable,\r\n        getTableItemsByRange = function (editor) {\r\n            return UT.getTableItemsByRange(editor);\r\n        },\r\n        getUETableBySelected = function (editor) {\r\n            return UT.getUETableBySelected(editor)\r\n        },\r\n        getDefaultValue = function (editor, table) {\r\n            return UT.getDefaultValue(editor, table);\r\n        },\r\n        getUETable = function (tdOrTable) {\r\n            return UT.getUETable(tdOrTable);\r\n        };\r\n\r\n\r\n    UE.commands['inserttable'] = {\r\n        queryCommandState: function () {\r\n            return getTableItemsByRange(this).table ? -1 : 0;\r\n        },\r\n        execCommand: function (cmd, opt) {\r\n            function createTable(opt, tdWidth) {\r\n                var html = [],\r\n                    rowsNum = opt.numRows,\r\n                    colsNum = opt.numCols;\r\n                for (var r = 0; r < rowsNum; r++) {\r\n                    html.push('<tr' + (r == 0 ? ' class=\"firstRow\"':'') + '>');\r\n                    for (var c = 0; c < colsNum; c++) {\r\n                        html.push('<td width=\"' + tdWidth + '\"  vAlign=\"' + opt.tdvalign + '\" >' + (browser.ie && browser.version < 11 ? domUtils.fillChar : '<br/>') + '</td>')\r\n                    }\r\n                    html.push('</tr>')\r\n                }\r\n                //禁止指定table-width\r\n                return '<table><tbody>' + html.join('') + '</tbody></table>'\r\n            }\r\n\r\n            if (!opt) {\r\n                opt = utils.extend({}, {\r\n                    numCols: this.options.defaultCols,\r\n                    numRows: this.options.defaultRows,\r\n                    tdvalign: this.options.tdvalign\r\n                })\r\n            }\r\n            var me = this;\r\n            var range = this.selection.getRange(),\r\n                start = range.startContainer,\r\n                firstParentBlock = domUtils.findParent(start, function (node) {\r\n                    return domUtils.isBlockElm(node);\r\n                }, true) || me.body;\r\n\r\n            var defaultValue = getDefaultValue(me),\r\n                tableWidth = firstParentBlock.offsetWidth,\r\n                tdWidth = Math.floor(tableWidth / opt.numCols - defaultValue.tdPadding * 2 - defaultValue.tdBorder);\r\n\r\n            //todo其他属性\r\n            !opt.tdvalign && (opt.tdvalign = me.options.tdvalign);\r\n            me.execCommand(\"inserthtml\", createTable(opt, tdWidth));\r\n        }\r\n    };\r\n\r\n    UE.commands['insertparagraphbeforetable'] = {\r\n        queryCommandState: function () {\r\n            return getTableItemsByRange(this).cell ? 0 : -1;\r\n        },\r\n        execCommand: function () {\r\n            var table = getTableItemsByRange(this).table;\r\n            if (table) {\r\n                var p = this.document.createElement(\"p\");\r\n                p.innerHTML = browser.ie ? '&nbsp;' : '<br />';\r\n                table.parentNode.insertBefore(p, table);\r\n                this.selection.getRange().setStart(p, 0).setCursor();\r\n            }\r\n        }\r\n    };\r\n\r\n    UE.commands['deletetable'] = {\r\n        queryCommandState: function () {\r\n            var rng = this.selection.getRange();\r\n            return domUtils.findParentByTagName(rng.startContainer, 'table', true) ? 0 : -1;\r\n        },\r\n        execCommand: function (cmd, table) {\r\n            var rng = this.selection.getRange();\r\n            table = table || domUtils.findParentByTagName(rng.startContainer, 'table', true);\r\n            if (table) {\r\n                var next = table.nextSibling;\r\n                if (!next) {\r\n                    next = domUtils.createElement(this.document, 'p', {\r\n                        'innerHTML': browser.ie ? domUtils.fillChar : '<br/>'\r\n                    });\r\n                    table.parentNode.insertBefore(next, table);\r\n                }\r\n                domUtils.remove(table);\r\n                rng = this.selection.getRange();\r\n                if (next.nodeType == 3) {\r\n                    rng.setStartBefore(next)\r\n                } else {\r\n                    rng.setStart(next, 0)\r\n                }\r\n                rng.setCursor(false, true)\r\n                this.fireEvent(\"tablehasdeleted\")\r\n\r\n            }\r\n\r\n        }\r\n    };\r\n    UE.commands['cellalign'] = {\r\n        queryCommandState: function () {\r\n            return getSelectedArr(this).length ? 0 : -1\r\n        },\r\n        execCommand: function (cmd, align) {\r\n            var selectedTds = getSelectedArr(this);\r\n            if (selectedTds.length) {\r\n                for (var i = 0, ci; ci = selectedTds[i++];) {\r\n                    ci.setAttribute('align', align);\r\n                }\r\n            }\r\n        }\r\n    };\r\n    UE.commands['cellvalign'] = {\r\n        queryCommandState: function () {\r\n            return getSelectedArr(this).length ? 0 : -1;\r\n        },\r\n        execCommand: function (cmd, valign) {\r\n            var selectedTds = getSelectedArr(this);\r\n            if (selectedTds.length) {\r\n                for (var i = 0, ci; ci = selectedTds[i++];) {\r\n                    ci.setAttribute('vAlign', valign);\r\n                }\r\n            }\r\n        }\r\n    };\r\n    UE.commands['insertcaption'] = {\r\n        queryCommandState: function () {\r\n            var table = getTableItemsByRange(this).table;\r\n            if (table) {\r\n                return table.getElementsByTagName('caption').length == 0 ? 1 : -1;\r\n            }\r\n            return -1;\r\n        },\r\n        execCommand: function () {\r\n            var table = getTableItemsByRange(this).table;\r\n            if (table) {\r\n                var caption = this.document.createElement('caption');\r\n                caption.innerHTML = browser.ie ? domUtils.fillChar : '<br/>';\r\n                table.insertBefore(caption, table.firstChild);\r\n                var range = this.selection.getRange();\r\n                range.setStart(caption, 0).setCursor();\r\n            }\r\n\r\n        }\r\n    };\r\n    UE.commands['deletecaption'] = {\r\n        queryCommandState: function () {\r\n            var rng = this.selection.getRange(),\r\n                table = domUtils.findParentByTagName(rng.startContainer, 'table');\r\n            if (table) {\r\n                return table.getElementsByTagName('caption').length == 0 ? -1 : 1;\r\n            }\r\n            return -1;\r\n        },\r\n        execCommand: function () {\r\n            var rng = this.selection.getRange(),\r\n                table = domUtils.findParentByTagName(rng.startContainer, 'table');\r\n            if (table) {\r\n                domUtils.remove(table.getElementsByTagName('caption')[0]);\r\n                var range = this.selection.getRange();\r\n                range.setStart(table.rows[0].cells[0], 0).setCursor();\r\n            }\r\n\r\n        }\r\n    };\r\n    UE.commands['inserttitle'] = {\r\n        queryCommandState: function () {\r\n            var table = getTableItemsByRange(this).table;\r\n            if (table) {\r\n                var firstRow = table.rows[0];\r\n                return firstRow.cells[firstRow.cells.length-1].tagName.toLowerCase() != 'th' ? 0 : -1\r\n            }\r\n            return -1;\r\n        },\r\n        execCommand: function () {\r\n            var table = getTableItemsByRange(this).table;\r\n            if (table) {\r\n                getUETable(table).insertRow(0, 'th');\r\n            }\r\n            var th = table.getElementsByTagName('th')[0];\r\n            this.selection.getRange().setStart(th, 0).setCursor(false, true);\r\n        }\r\n    };\r\n    UE.commands['deletetitle'] = {\r\n        queryCommandState: function () {\r\n            var table = getTableItemsByRange(this).table;\r\n            if (table) {\r\n                var firstRow = table.rows[0];\r\n                return firstRow.cells[firstRow.cells.length-1].tagName.toLowerCase() == 'th' ? 0 : -1\r\n            }\r\n            return -1;\r\n        },\r\n        execCommand: function () {\r\n            var table = getTableItemsByRange(this).table;\r\n            if (table) {\r\n                domUtils.remove(table.rows[0])\r\n            }\r\n            var td = table.getElementsByTagName('td')[0];\r\n            this.selection.getRange().setStart(td, 0).setCursor(false, true);\r\n        }\r\n    };\r\n    UE.commands['inserttitlecol'] = {\r\n        queryCommandState: function () {\r\n            var table = getTableItemsByRange(this).table;\r\n            if (table) {\r\n                var lastRow = table.rows[table.rows.length-1];\r\n                return lastRow.getElementsByTagName('th').length ? -1 : 0;\r\n            }\r\n            return -1;\r\n        },\r\n        execCommand: function (cmd) {\r\n            var table = getTableItemsByRange(this).table;\r\n            if (table) {\r\n                getUETable(table).insertCol(0, 'th');\r\n            }\r\n            resetTdWidth(table, this);\r\n            var th = table.getElementsByTagName('th')[0];\r\n            this.selection.getRange().setStart(th, 0).setCursor(false, true);\r\n        }\r\n    };\r\n    UE.commands['deletetitlecol'] = {\r\n        queryCommandState: function () {\r\n            var table = getTableItemsByRange(this).table;\r\n            if (table) {\r\n                var lastRow = table.rows[table.rows.length-1];\r\n                return lastRow.getElementsByTagName('th').length ? 0 : -1;\r\n            }\r\n            return -1;\r\n        },\r\n        execCommand: function () {\r\n            var table = getTableItemsByRange(this).table;\r\n            if (table) {\r\n                for(var i = 0; i< table.rows.length; i++ ){\r\n                    domUtils.remove(table.rows[i].children[0])\r\n                }\r\n            }\r\n            resetTdWidth(table, this);\r\n            var td = table.getElementsByTagName('td')[0];\r\n            this.selection.getRange().setStart(td, 0).setCursor(false, true);\r\n        }\r\n    };\r\n\r\n    UE.commands[\"mergeright\"] = {\r\n        queryCommandState: function (cmd) {\r\n            var tableItems = getTableItemsByRange(this),\r\n                table = tableItems.table,\r\n                cell = tableItems.cell;\r\n\r\n            if (!table || !cell) return -1;\r\n            var ut = getUETable(table);\r\n            if (ut.selectedTds.length) return -1;\r\n\r\n            var cellInfo = ut.getCellInfo(cell),\r\n                rightColIndex = cellInfo.colIndex + cellInfo.colSpan;\r\n            if (rightColIndex >= ut.colsNum) return -1; // 如果处于最右边则不能向右合并\r\n\r\n            var rightCellInfo = ut.indexTable[cellInfo.rowIndex][rightColIndex],\r\n                rightCell = table.rows[rightCellInfo.rowIndex].cells[rightCellInfo.cellIndex];\r\n            if (!rightCell || cell.tagName != rightCell.tagName) return -1; // TH和TD不能相互合并\r\n\r\n            // 当且仅当两个Cell的开始列号和结束列号一致时能进行合并\r\n            return (rightCellInfo.rowIndex == cellInfo.rowIndex && rightCellInfo.rowSpan == cellInfo.rowSpan) ? 0 : -1;\r\n        },\r\n        execCommand: function (cmd) {\r\n            var rng = this.selection.getRange(),\r\n                bk = rng.createBookmark(true);\r\n            var cell = getTableItemsByRange(this).cell,\r\n                ut = getUETable(cell);\r\n            ut.mergeRight(cell);\r\n            rng.moveToBookmark(bk).select();\r\n        }\r\n    };\r\n    UE.commands[\"mergedown\"] = {\r\n        queryCommandState: function (cmd) {\r\n            var tableItems = getTableItemsByRange(this),\r\n                table = tableItems.table,\r\n                cell = tableItems.cell;\r\n\r\n            if (!table || !cell) return -1;\r\n            var ut = getUETable(table);\r\n            if (ut.selectedTds.length)return -1;\r\n\r\n            var cellInfo = ut.getCellInfo(cell),\r\n                downRowIndex = cellInfo.rowIndex + cellInfo.rowSpan;\r\n            if (downRowIndex >= ut.rowsNum) return -1; // 如果处于最下边则不能向下合并\r\n\r\n            var downCellInfo = ut.indexTable[downRowIndex][cellInfo.colIndex],\r\n                downCell = table.rows[downCellInfo.rowIndex].cells[downCellInfo.cellIndex];\r\n            if (!downCell || cell.tagName != downCell.tagName) return -1; // TH和TD不能相互合并\r\n\r\n            // 当且仅当两个Cell的开始列号和结束列号一致时能进行合并\r\n            return (downCellInfo.colIndex == cellInfo.colIndex && downCellInfo.colSpan == cellInfo.colSpan) ? 0 : -1;\r\n        },\r\n        execCommand: function () {\r\n            var rng = this.selection.getRange(),\r\n                bk = rng.createBookmark(true);\r\n            var cell = getTableItemsByRange(this).cell,\r\n                ut = getUETable(cell);\r\n            ut.mergeDown(cell);\r\n            rng.moveToBookmark(bk).select();\r\n        }\r\n    };\r\n    UE.commands[\"mergecells\"] = {\r\n        queryCommandState: function () {\r\n            return getUETableBySelected(this) ? 0 : -1;\r\n        },\r\n        execCommand: function () {\r\n            var ut = getUETableBySelected(this);\r\n            if (ut && ut.selectedTds.length) {\r\n                var cell = ut.selectedTds[0];\r\n                ut.mergeRange();\r\n                var rng = this.selection.getRange();\r\n                if (domUtils.isEmptyBlock(cell)) {\r\n                    rng.setStart(cell, 0).collapse(true)\r\n                } else {\r\n                    rng.selectNodeContents(cell)\r\n                }\r\n                rng.select();\r\n            }\r\n\r\n\r\n        }\r\n    };\r\n    UE.commands[\"insertrow\"] = {\r\n        queryCommandState: function () {\r\n            var tableItems = getTableItemsByRange(this),\r\n                cell = tableItems.cell;\r\n            return cell && (cell.tagName == \"TD\" || (cell.tagName == 'TH' && tableItems.tr !== tableItems.table.rows[0])) &&\r\n                getUETable(tableItems.table).rowsNum < this.options.maxRowNum ? 0 : -1;\r\n        },\r\n        execCommand: function () {\r\n            var rng = this.selection.getRange(),\r\n                bk = rng.createBookmark(true);\r\n            var tableItems = getTableItemsByRange(this),\r\n                cell = tableItems.cell,\r\n                table = tableItems.table,\r\n                ut = getUETable(table),\r\n                cellInfo = ut.getCellInfo(cell);\r\n            //ut.insertRow(!ut.selectedTds.length ? cellInfo.rowIndex:ut.cellsRange.beginRowIndex,'');\r\n            if (!ut.selectedTds.length) {\r\n                ut.insertRow(cellInfo.rowIndex, cell);\r\n            } else {\r\n                var range = ut.cellsRange;\r\n                for (var i = 0, len = range.endRowIndex - range.beginRowIndex + 1; i < len; i++) {\r\n                    ut.insertRow(range.beginRowIndex, cell);\r\n                }\r\n            }\r\n            rng.moveToBookmark(bk).select();\r\n            if (table.getAttribute(\"interlaced\") === \"enabled\")this.fireEvent(\"interlacetable\", table);\r\n        }\r\n    };\r\n    //后插入行\r\n    UE.commands[\"insertrownext\"] = {\r\n        queryCommandState: function () {\r\n            var tableItems = getTableItemsByRange(this),\r\n                cell = tableItems.cell;\r\n            return cell && (cell.tagName == \"TD\") && getUETable(tableItems.table).rowsNum < this.options.maxRowNum ? 0 : -1;\r\n        },\r\n        execCommand: function () {\r\n            var rng = this.selection.getRange(),\r\n                bk = rng.createBookmark(true);\r\n            var tableItems = getTableItemsByRange(this),\r\n                cell = tableItems.cell,\r\n                table = tableItems.table,\r\n                ut = getUETable(table),\r\n                cellInfo = ut.getCellInfo(cell);\r\n            //ut.insertRow(!ut.selectedTds.length? cellInfo.rowIndex + cellInfo.rowSpan : ut.cellsRange.endRowIndex + 1,'');\r\n            if (!ut.selectedTds.length) {\r\n                ut.insertRow(cellInfo.rowIndex + cellInfo.rowSpan, cell);\r\n            } else {\r\n                var range = ut.cellsRange;\r\n                for (var i = 0, len = range.endRowIndex - range.beginRowIndex + 1; i < len; i++) {\r\n                    ut.insertRow(range.endRowIndex + 1, cell);\r\n                }\r\n            }\r\n            rng.moveToBookmark(bk).select();\r\n            if (table.getAttribute(\"interlaced\") === \"enabled\")this.fireEvent(\"interlacetable\", table);\r\n        }\r\n    };\r\n    UE.commands[\"deleterow\"] = {\r\n        queryCommandState: function () {\r\n            var tableItems = getTableItemsByRange(this);\r\n            return tableItems.cell ? 0 : -1;\r\n        },\r\n        execCommand: function () {\r\n            var cell = getTableItemsByRange(this).cell,\r\n                ut = getUETable(cell),\r\n                cellsRange = ut.cellsRange,\r\n                cellInfo = ut.getCellInfo(cell),\r\n                preCell = ut.getVSideCell(cell),\r\n                nextCell = ut.getVSideCell(cell, true),\r\n                rng = this.selection.getRange();\r\n            if (utils.isEmptyObject(cellsRange)) {\r\n                ut.deleteRow(cellInfo.rowIndex);\r\n            } else {\r\n                for (var i = cellsRange.beginRowIndex; i < cellsRange.endRowIndex + 1; i++) {\r\n                    ut.deleteRow(cellsRange.beginRowIndex);\r\n                }\r\n            }\r\n            var table = ut.table;\r\n            if (!table.getElementsByTagName('td').length) {\r\n                var nextSibling = table.nextSibling;\r\n                domUtils.remove(table);\r\n                if (nextSibling) {\r\n                    rng.setStart(nextSibling, 0).setCursor(false, true);\r\n                }\r\n            } else {\r\n                if (cellInfo.rowSpan == 1 || cellInfo.rowSpan == cellsRange.endRowIndex - cellsRange.beginRowIndex + 1) {\r\n                    if (nextCell || preCell) rng.selectNodeContents(nextCell || preCell).setCursor(false, true);\r\n                } else {\r\n                    var newCell = ut.getCell(cellInfo.rowIndex, ut.indexTable[cellInfo.rowIndex][cellInfo.colIndex].cellIndex);\r\n                    if (newCell) rng.selectNodeContents(newCell).setCursor(false, true);\r\n                }\r\n            }\r\n            if (table.getAttribute(\"interlaced\") === \"enabled\")this.fireEvent(\"interlacetable\", table);\r\n        }\r\n    };\r\n    UE.commands[\"insertcol\"] = {\r\n        queryCommandState: function (cmd) {\r\n            var tableItems = getTableItemsByRange(this),\r\n                cell = tableItems.cell;\r\n            return cell && (cell.tagName == \"TD\" || (cell.tagName == 'TH' && cell !== tableItems.tr.cells[0])) &&\r\n                getUETable(tableItems.table).colsNum < this.options.maxColNum ? 0 : -1;\r\n        },\r\n        execCommand: function (cmd) {\r\n            var rng = this.selection.getRange(),\r\n                bk = rng.createBookmark(true);\r\n            if (this.queryCommandState(cmd) == -1)return;\r\n            var cell = getTableItemsByRange(this).cell,\r\n                ut = getUETable(cell),\r\n                cellInfo = ut.getCellInfo(cell);\r\n\r\n            //ut.insertCol(!ut.selectedTds.length ? cellInfo.colIndex:ut.cellsRange.beginColIndex);\r\n            if (!ut.selectedTds.length) {\r\n                ut.insertCol(cellInfo.colIndex, cell);\r\n            } else {\r\n                var range = ut.cellsRange;\r\n                for (var i = 0, len = range.endColIndex - range.beginColIndex + 1; i < len; i++) {\r\n                    ut.insertCol(range.beginColIndex, cell);\r\n                }\r\n            }\r\n            rng.moveToBookmark(bk).select(true);\r\n        }\r\n    };\r\n    UE.commands[\"insertcolnext\"] = {\r\n        queryCommandState: function () {\r\n            var tableItems = getTableItemsByRange(this),\r\n                cell = tableItems.cell;\r\n            return cell && getUETable(tableItems.table).colsNum < this.options.maxColNum ? 0 : -1;\r\n        },\r\n        execCommand: function () {\r\n            var rng = this.selection.getRange(),\r\n                bk = rng.createBookmark(true);\r\n            var cell = getTableItemsByRange(this).cell,\r\n                ut = getUETable(cell),\r\n                cellInfo = ut.getCellInfo(cell);\r\n            //ut.insertCol(!ut.selectedTds.length ? cellInfo.colIndex + cellInfo.colSpan:ut.cellsRange.endColIndex +1);\r\n            if (!ut.selectedTds.length) {\r\n                ut.insertCol(cellInfo.colIndex + cellInfo.colSpan, cell);\r\n            } else {\r\n                var range = ut.cellsRange;\r\n                for (var i = 0, len = range.endColIndex - range.beginColIndex + 1; i < len; i++) {\r\n                    ut.insertCol(range.endColIndex + 1, cell);\r\n                }\r\n            }\r\n            rng.moveToBookmark(bk).select();\r\n        }\r\n    };\r\n\r\n    UE.commands[\"deletecol\"] = {\r\n        queryCommandState: function () {\r\n            var tableItems = getTableItemsByRange(this);\r\n            return tableItems.cell ? 0 : -1;\r\n        },\r\n        execCommand: function () {\r\n            var cell = getTableItemsByRange(this).cell,\r\n                ut = getUETable(cell),\r\n                range = ut.cellsRange,\r\n                cellInfo = ut.getCellInfo(cell),\r\n                preCell = ut.getHSideCell(cell),\r\n                nextCell = ut.getHSideCell(cell, true);\r\n            if (utils.isEmptyObject(range)) {\r\n                ut.deleteCol(cellInfo.colIndex);\r\n            } else {\r\n                for (var i = range.beginColIndex; i < range.endColIndex + 1; i++) {\r\n                    ut.deleteCol(range.beginColIndex);\r\n                }\r\n            }\r\n            var table = ut.table,\r\n                rng = this.selection.getRange();\r\n\r\n            if (!table.getElementsByTagName('td').length) {\r\n                var nextSibling = table.nextSibling;\r\n                domUtils.remove(table);\r\n                if (nextSibling) {\r\n                    rng.setStart(nextSibling, 0).setCursor(false, true);\r\n                }\r\n            } else {\r\n                if (domUtils.inDoc(cell, this.document)) {\r\n                    rng.setStart(cell, 0).setCursor(false, true);\r\n                } else {\r\n                    if (nextCell && domUtils.inDoc(nextCell, this.document)) {\r\n                        rng.selectNodeContents(nextCell).setCursor(false, true);\r\n                    } else {\r\n                        if (preCell && domUtils.inDoc(preCell, this.document)) {\r\n                            rng.selectNodeContents(preCell).setCursor(true, true);\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n        }\r\n    };\r\n    UE.commands[\"splittocells\"] = {\r\n        queryCommandState: function () {\r\n            var tableItems = getTableItemsByRange(this),\r\n                cell = tableItems.cell;\r\n            if (!cell) return -1;\r\n            var ut = getUETable(tableItems.table);\r\n            if (ut.selectedTds.length > 0) return -1;\r\n            return cell && (cell.colSpan > 1 || cell.rowSpan > 1) ? 0 : -1;\r\n        },\r\n        execCommand: function () {\r\n            var rng = this.selection.getRange(),\r\n                bk = rng.createBookmark(true);\r\n            var cell = getTableItemsByRange(this).cell,\r\n                ut = getUETable(cell);\r\n            ut.splitToCells(cell);\r\n            rng.moveToBookmark(bk).select();\r\n        }\r\n    };\r\n    UE.commands[\"splittorows\"] = {\r\n        queryCommandState: function () {\r\n            var tableItems = getTableItemsByRange(this),\r\n                cell = tableItems.cell;\r\n            if (!cell) return -1;\r\n            var ut = getUETable(tableItems.table);\r\n            if (ut.selectedTds.length > 0) return -1;\r\n            return cell && cell.rowSpan > 1 ? 0 : -1;\r\n        },\r\n        execCommand: function () {\r\n            var rng = this.selection.getRange(),\r\n                bk = rng.createBookmark(true);\r\n            var cell = getTableItemsByRange(this).cell,\r\n                ut = getUETable(cell);\r\n            ut.splitToRows(cell);\r\n            rng.moveToBookmark(bk).select();\r\n        }\r\n    };\r\n    UE.commands[\"splittocols\"] = {\r\n        queryCommandState: function () {\r\n            var tableItems = getTableItemsByRange(this),\r\n                cell = tableItems.cell;\r\n            if (!cell) return -1;\r\n            var ut = getUETable(tableItems.table);\r\n            if (ut.selectedTds.length > 0) return -1;\r\n            return cell && cell.colSpan > 1 ? 0 : -1;\r\n        },\r\n        execCommand: function () {\r\n            var rng = this.selection.getRange(),\r\n                bk = rng.createBookmark(true);\r\n            var cell = getTableItemsByRange(this).cell,\r\n                ut = getUETable(cell);\r\n            ut.splitToCols(cell);\r\n            rng.moveToBookmark(bk).select();\r\n\r\n        }\r\n    };\r\n\r\n    UE.commands[\"adaptbytext\"] =\r\n        UE.commands[\"adaptbywindow\"] = {\r\n            queryCommandState: function () {\r\n                return getTableItemsByRange(this).table ? 0 : -1\r\n            },\r\n            execCommand: function (cmd) {\r\n                var tableItems = getTableItemsByRange(this),\r\n                    table = tableItems.table;\r\n                if (table) {\r\n                    if (cmd == 'adaptbywindow') {\r\n                        resetTdWidth(table, this);\r\n                    } else {\r\n                        var cells = domUtils.getElementsByTagName(table, \"td th\");\r\n                        utils.each(cells, function (cell) {\r\n                            cell.removeAttribute(\"width\");\r\n                        });\r\n                        table.removeAttribute(\"width\");\r\n                    }\r\n                }\r\n            }\r\n        };\r\n\r\n    //平均分配各列\r\n    UE.commands['averagedistributecol'] = {\r\n        queryCommandState: function () {\r\n            var ut = getUETableBySelected(this);\r\n            if (!ut) return -1;\r\n            return ut.isFullRow() || ut.isFullCol() ? 0 : -1;\r\n        },\r\n        execCommand: function (cmd) {\r\n            var me = this,\r\n                ut = getUETableBySelected(me);\r\n\r\n            function getAverageWidth() {\r\n                var tb = ut.table,\r\n                    averageWidth, sumWidth = 0, colsNum = 0,\r\n                    tbAttr = getDefaultValue(me, tb);\r\n\r\n                if (ut.isFullRow()) {\r\n                    sumWidth = tb.offsetWidth;\r\n                    colsNum = ut.colsNum;\r\n                } else {\r\n                    var begin = ut.cellsRange.beginColIndex,\r\n                        end = ut.cellsRange.endColIndex,\r\n                        node;\r\n                    for (var i = begin; i <= end;) {\r\n                        node = ut.selectedTds[i];\r\n                        sumWidth += node.offsetWidth;\r\n                        i += node.colSpan;\r\n                        colsNum += 1;\r\n                    }\r\n                }\r\n                averageWidth = Math.ceil(sumWidth / colsNum) - tbAttr.tdBorder * 2 - tbAttr.tdPadding * 2;\r\n                return averageWidth;\r\n            }\r\n\r\n            function setAverageWidth(averageWidth) {\r\n                utils.each(domUtils.getElementsByTagName(ut.table, \"th\"), function (node) {\r\n                    node.setAttribute(\"width\", \"\");\r\n                });\r\n                var cells = ut.isFullRow() ? domUtils.getElementsByTagName(ut.table, \"td\") : ut.selectedTds;\r\n\r\n                utils.each(cells, function (node) {\r\n                    if (node.colSpan == 1) {\r\n                        node.setAttribute(\"width\", averageWidth);\r\n                    }\r\n                });\r\n            }\r\n\r\n            if (ut && ut.selectedTds.length) {\r\n                setAverageWidth(getAverageWidth());\r\n            }\r\n        }\r\n    };\r\n    //平均分配各行\r\n    UE.commands['averagedistributerow'] = {\r\n        queryCommandState: function () {\r\n            var ut = getUETableBySelected(this);\r\n            if (!ut) return -1;\r\n            if (ut.selectedTds && /th/ig.test(ut.selectedTds[0].tagName)) return -1;\r\n            return ut.isFullRow() || ut.isFullCol() ? 0 : -1;\r\n        },\r\n        execCommand: function (cmd) {\r\n            var me = this,\r\n                ut = getUETableBySelected(me);\r\n\r\n            function getAverageHeight() {\r\n                var averageHeight, rowNum, sumHeight = 0,\r\n                    tb = ut.table,\r\n                    tbAttr = getDefaultValue(me, tb),\r\n                    tdpadding = parseInt(domUtils.getComputedStyle(tb.getElementsByTagName('td')[0], \"padding-top\"));\r\n\r\n                if (ut.isFullCol()) {\r\n                    var captionArr = domUtils.getElementsByTagName(tb, \"caption\"),\r\n                        thArr = domUtils.getElementsByTagName(tb, \"th\"),\r\n                        captionHeight, thHeight;\r\n\r\n                    if (captionArr.length > 0) {\r\n                        captionHeight = captionArr[0].offsetHeight;\r\n                    }\r\n                    if (thArr.length > 0) {\r\n                        thHeight = thArr[0].offsetHeight;\r\n                    }\r\n\r\n                    sumHeight = tb.offsetHeight - (captionHeight || 0) - (thHeight || 0);\r\n                    rowNum = thArr.length == 0 ? ut.rowsNum : (ut.rowsNum - 1);\r\n                } else {\r\n                    var begin = ut.cellsRange.beginRowIndex,\r\n                        end = ut.cellsRange.endRowIndex,\r\n                        count = 0,\r\n                        trs = domUtils.getElementsByTagName(tb, \"tr\");\r\n                    for (var i = begin; i <= end; i++) {\r\n                        sumHeight += trs[i].offsetHeight;\r\n                        count += 1;\r\n                    }\r\n                    rowNum = count;\r\n                }\r\n                //ie8下是混杂模式\r\n                if (browser.ie && browser.version < 9) {\r\n                    averageHeight = Math.ceil(sumHeight / rowNum);\r\n                } else {\r\n                    averageHeight = Math.ceil(sumHeight / rowNum) - tbAttr.tdBorder * 2 - tdpadding * 2;\r\n                }\r\n                return averageHeight;\r\n            }\r\n\r\n            function setAverageHeight(averageHeight) {\r\n                var cells = ut.isFullCol() ? domUtils.getElementsByTagName(ut.table, \"td\") : ut.selectedTds;\r\n                utils.each(cells, function (node) {\r\n                    if (node.rowSpan == 1) {\r\n                        node.setAttribute(\"height\", averageHeight);\r\n                    }\r\n                });\r\n            }\r\n\r\n            if (ut && ut.selectedTds.length) {\r\n                setAverageHeight(getAverageHeight());\r\n            }\r\n        }\r\n    };\r\n\r\n    //单元格对齐方式\r\n    UE.commands['cellalignment'] = {\r\n        queryCommandState: function () {\r\n            return getTableItemsByRange(this).table ? 0 : -1\r\n        },\r\n        execCommand: function (cmd, data) {\r\n            var me = this,\r\n                ut = getUETableBySelected(me);\r\n\r\n            if (!ut) {\r\n                var start = me.selection.getStart(),\r\n                    cell = start && domUtils.findParentByTagName(start, [\"td\", \"th\", \"caption\"], true);\r\n                if (!/caption/ig.test(cell.tagName)) {\r\n                    domUtils.setAttributes(cell, data);\r\n                } else {\r\n                    cell.style.textAlign = data.align;\r\n                    cell.style.verticalAlign = data.vAlign;\r\n                }\r\n                me.selection.getRange().setCursor(true);\r\n            } else {\r\n                utils.each(ut.selectedTds, function (cell) {\r\n                    domUtils.setAttributes(cell, data);\r\n                });\r\n            }\r\n        },\r\n        /**\r\n         * 查询当前点击的单元格的对齐状态， 如果当前已经选择了多个单元格， 则会返回所有单元格经过统一协调过后的状态\r\n         * @see UE.UETable.getTableCellAlignState\r\n         */\r\n        queryCommandValue: function (cmd) {\r\n\r\n            var activeMenuCell = getTableItemsByRange( this).cell;\r\n\r\n            if( !activeMenuCell ) {\r\n                activeMenuCell = getSelectedArr(this)[0];\r\n            }\r\n\r\n            if (!activeMenuCell) {\r\n\r\n                return null;\r\n\r\n            } else {\r\n\r\n                //获取同时选中的其他单元格\r\n                var cells = UE.UETable.getUETable(activeMenuCell).selectedTds;\r\n\r\n                !cells.length && ( cells = activeMenuCell );\r\n\r\n                return UE.UETable.getTableCellAlignState(cells);\r\n\r\n            }\r\n\r\n        }\r\n    };\r\n    //表格对齐方式\r\n    UE.commands['tablealignment'] = {\r\n        queryCommandState: function () {\r\n            if (browser.ie && browser.version < 8) {\r\n                return -1;\r\n            }\r\n            return getTableItemsByRange(this).table ? 0 : -1\r\n        },\r\n        execCommand: function (cmd, value) {\r\n            var me = this,\r\n                start = me.selection.getStart(),\r\n                table = start && domUtils.findParentByTagName(start, [\"table\"], true);\r\n\r\n            if (table) {\r\n                table.setAttribute(\"align\",value);\r\n            }\r\n        }\r\n    };\r\n\r\n    //表格属性\r\n    UE.commands['edittable'] = {\r\n        queryCommandState: function () {\r\n            return getTableItemsByRange(this).table ? 0 : -1\r\n        },\r\n        execCommand: function (cmd, color) {\r\n            var rng = this.selection.getRange(),\r\n                table = domUtils.findParentByTagName(rng.startContainer, 'table');\r\n            if (table) {\r\n                var arr = domUtils.getElementsByTagName(table, \"td\").concat(\r\n                    domUtils.getElementsByTagName(table, \"th\"),\r\n                    domUtils.getElementsByTagName(table, \"caption\")\r\n                );\r\n                utils.each(arr, function (node) {\r\n                    node.style.borderColor = color;\r\n                });\r\n            }\r\n        }\r\n    };\r\n    //单元格属性\r\n    UE.commands['edittd'] = {\r\n        queryCommandState: function () {\r\n            return getTableItemsByRange(this).table ? 0 : -1\r\n        },\r\n        execCommand: function (cmd, bkColor) {\r\n            var me = this,\r\n                ut = getUETableBySelected(me);\r\n\r\n            if (!ut) {\r\n                var start = me.selection.getStart(),\r\n                    cell = start && domUtils.findParentByTagName(start, [\"td\", \"th\", \"caption\"], true);\r\n                if (cell) {\r\n                    cell.style.backgroundColor = bkColor;\r\n                }\r\n            } else {\r\n                utils.each(ut.selectedTds, function (cell) {\r\n                    cell.style.backgroundColor = bkColor;\r\n                });\r\n            }\r\n        }\r\n    };\r\n\r\n    UE.commands[\"settablebackground\"] = {\r\n        queryCommandState: function () {\r\n            return getSelectedArr(this).length > 1 ? 0 : -1;\r\n        },\r\n        execCommand: function (cmd, value) {\r\n            var cells, ut;\r\n            cells = getSelectedArr(this);\r\n            ut = getUETable(cells[0]);\r\n            ut.setBackground(cells, value);\r\n        }\r\n    };\r\n\r\n    UE.commands[\"cleartablebackground\"] = {\r\n        queryCommandState: function () {\r\n            var cells = getSelectedArr(this);\r\n            if (!cells.length)return -1;\r\n            for (var i = 0, cell; cell = cells[i++];) {\r\n                if (cell.style.backgroundColor !== \"\") return 0;\r\n            }\r\n            return -1;\r\n        },\r\n        execCommand: function () {\r\n            var cells = getSelectedArr(this),\r\n                ut = getUETable(cells[0]);\r\n            ut.removeBackground(cells);\r\n        }\r\n    };\r\n\r\n    UE.commands[\"interlacetable\"] = UE.commands[\"uninterlacetable\"] = {\r\n        queryCommandState: function (cmd) {\r\n            var table = getTableItemsByRange(this).table;\r\n            if (!table) return -1;\r\n            var interlaced = table.getAttribute(\"interlaced\");\r\n            if (cmd == \"interlacetable\") {\r\n                //TODO 待定\r\n                //是否需要待定，如果设置，则命令只能单次执行成功，但反射具备toggle效果；否则可以覆盖前次命令，但反射将不存在toggle效果\r\n                return (interlaced === \"enabled\") ? -1 : 0;\r\n            } else {\r\n                return (!interlaced || interlaced === \"disabled\") ? -1 : 0;\r\n            }\r\n        },\r\n        execCommand: function (cmd, classList) {\r\n            var table = getTableItemsByRange(this).table;\r\n            if (cmd == \"interlacetable\") {\r\n                table.setAttribute(\"interlaced\", \"enabled\");\r\n                this.fireEvent(\"interlacetable\", table, classList);\r\n            } else {\r\n                table.setAttribute(\"interlaced\", \"disabled\");\r\n                this.fireEvent(\"uninterlacetable\", table);\r\n            }\r\n        }\r\n    };\r\n    UE.commands[\"setbordervisible\"] = {\r\n        queryCommandState: function (cmd) {\r\n            var table = getTableItemsByRange(this).table;\r\n            if (!table) return -1;\r\n            return 0;\r\n        },\r\n        execCommand: function () {\r\n            var table = getTableItemsByRange(this).table;\r\n            utils.each(domUtils.getElementsByTagName(table,'td'),function(td){\r\n                td.style.borderWidth = '1px';\r\n                td.style.borderStyle = 'solid';\r\n            })\r\n        }\r\n    };\r\n    function resetTdWidth(table, editor) {\r\n        var tds = domUtils.getElementsByTagName(table,'td th');\r\n        utils.each(tds, function (td) {\r\n            td.removeAttribute(\"width\");\r\n        });\r\n        table.setAttribute('width', getTableWidth(editor, true, getDefaultValue(editor, table)));\r\n        var tdsWidths = [];\r\n        setTimeout(function () {\r\n            utils.each(tds, function (td) {\r\n                (td.colSpan == 1) && tdsWidths.push(td.offsetWidth)\r\n            })\r\n            utils.each(tds, function (td,i) {\r\n                (td.colSpan == 1) && td.setAttribute(\"width\", tdsWidths[i] + \"\");\r\n            })\r\n        }, 0);\r\n    }\r\n\r\n    function getTableWidth(editor, needIEHack, defaultValue) {\r\n        var body = editor.body;\r\n        return body.offsetWidth - (needIEHack ? parseInt(domUtils.getComputedStyle(body, 'margin-left'), 10) * 2 : 0) - defaultValue.tableBorder * 2 - (editor.options.offsetWidth || 0);\r\n    }\r\n\r\n    function getSelectedArr(editor) {\r\n        var cell = getTableItemsByRange(editor).cell;\r\n        if (cell) {\r\n            var ut = getUETable(cell);\r\n            return ut.selectedTds.length ? ut.selectedTds : [cell];\r\n        } else {\r\n            return [];\r\n        }\r\n    }\r\n})();\r\n\r\n\r\n// plugins/table.action.js\r\n/**\r\n * Created with JetBrains PhpStorm.\r\n * User: taoqili\r\n * Date: 12-10-12\r\n * Time: 上午10:05\r\n * To change this template use File | Settings | File Templates.\r\n */\r\nUE.plugins['table'] = function () {\r\n    var me = this,\r\n        tabTimer = null,\r\n        //拖动计时器\r\n        tableDragTimer = null,\r\n        //双击计时器\r\n        tableResizeTimer = null,\r\n        //单元格最小宽度\r\n        cellMinWidth = 5,\r\n        isInResizeBuffer = false,\r\n        //单元格边框大小\r\n        cellBorderWidth = 5,\r\n        //鼠标偏移距离\r\n        offsetOfTableCell = 10,\r\n        //记录在有限时间内的点击状态， 共有3个取值， 0, 1, 2。 0代表未初始化， 1代表单击了1次，2代表2次\r\n        singleClickState = 0,\r\n        userActionStatus = null,\r\n        //双击允许的时间范围\r\n        dblclickTime = 360,\r\n        UT = UE.UETable,\r\n        getUETable = function (tdOrTable) {\r\n            return UT.getUETable(tdOrTable);\r\n        },\r\n        getUETableBySelected = function (editor) {\r\n            return UT.getUETableBySelected(editor);\r\n        },\r\n        getDefaultValue = function (editor, table) {\r\n            return UT.getDefaultValue(editor, table);\r\n        },\r\n        removeSelectedClass = function (cells) {\r\n            return UT.removeSelectedClass(cells);\r\n        };\r\n\r\n    function showError(e) {\r\n//        throw e;\r\n    }\r\n    me.ready(function(){\r\n        var me = this;\r\n        var orgGetText = me.selection.getText;\r\n        me.selection.getText = function(){\r\n            var table = getUETableBySelected(me);\r\n            if(table){\r\n                var str = '';\r\n                utils.each(table.selectedTds,function(td){\r\n                    str += td[browser.ie?'innerText':'textContent'];\r\n                })\r\n                return str;\r\n            }else{\r\n                return orgGetText.call(me.selection)\r\n            }\r\n\r\n        }\r\n    })\r\n\r\n    //处理拖动及框选相关方法\r\n    var startTd = null, //鼠标按下时的锚点td\r\n        currentTd = null, //当前鼠标经过时的td\r\n        onDrag = \"\", //指示当前拖动状态，其值可为\"\",\"h\",\"v\" ,分别表示未拖动状态，横向拖动状态，纵向拖动状态，用于鼠标移动过程中的判断\r\n        onBorder = false, //检测鼠标按下时是否处在单元格边缘位置\r\n        dragButton = null,\r\n        dragOver = false,\r\n        dragLine = null, //模拟的拖动线\r\n        dragTd = null;    //发生拖动的目标td\r\n\r\n    var mousedown = false,\r\n    //todo 判断混乱模式\r\n        needIEHack = true;\r\n\r\n    me.setOpt({\r\n        'maxColNum':20,\r\n        'maxRowNum':100,\r\n        'defaultCols':5,\r\n        'defaultRows':5,\r\n        'tdvalign':'top',\r\n        'cursorpath':me.options.UEDITOR_HOME_URL + \"themes/default/images/cursor_\",\r\n        'tableDragable':false,\r\n        'classList':[\"ue-table-interlace-color-single\",\"ue-table-interlace-color-double\"]\r\n    });\r\n    me.getUETable = getUETable;\r\n    var commands = {\r\n        'deletetable':1,\r\n        'inserttable':1,\r\n        'cellvalign':1,\r\n        'insertcaption':1,\r\n        'deletecaption':1,\r\n        'inserttitle':1,\r\n        'deletetitle':1,\r\n        \"mergeright\":1,\r\n        \"mergedown\":1,\r\n        \"mergecells\":1,\r\n        \"insertrow\":1,\r\n        \"insertrownext\":1,\r\n        \"deleterow\":1,\r\n        \"insertcol\":1,\r\n        \"insertcolnext\":1,\r\n        \"deletecol\":1,\r\n        \"splittocells\":1,\r\n        \"splittorows\":1,\r\n        \"splittocols\":1,\r\n        \"adaptbytext\":1,\r\n        \"adaptbywindow\":1,\r\n        \"adaptbycustomer\":1,\r\n        \"insertparagraph\":1,\r\n        \"insertparagraphbeforetable\":1,\r\n        \"averagedistributecol\":1,\r\n        \"averagedistributerow\":1\r\n    };\r\n    me.ready(function () {\r\n        utils.cssRule('table',\r\n            //选中的td上的样式\r\n            '.selectTdClass{background-color:#edf5fa !important}' +\r\n                'table.noBorderTable td,table.noBorderTable th,table.noBorderTable caption{border:1px dashed #ddd !important}' +\r\n                //插入的表格的默认样式\r\n                'table{margin-bottom:10px;border-collapse:collapse;display:table;}' +\r\n                'td,th{padding: 5px 10px;border: 1px solid #DDD;}' +\r\n                'caption{border:1px dashed #DDD;border-bottom:0;padding:3px;text-align:center;}' +\r\n                'th{border-top:1px solid #BBB;background-color:#F7F7F7;}' +\r\n                'table tr.firstRow th{border-top-width:2px;}' +\r\n                '.ue-table-interlace-color-single{ background-color: #fcfcfc; } .ue-table-interlace-color-double{ background-color: #f7faff; }' +\r\n                'td p{margin:0;padding:0;}', me.document);\r\n\r\n        var tableCopyList, isFullCol, isFullRow;\r\n        //注册del/backspace事件\r\n        me.addListener('keydown', function (cmd, evt) {\r\n            var me = this;\r\n            var keyCode = evt.keyCode || evt.which;\r\n\r\n            if (keyCode == 8) {\r\n\r\n                var ut = getUETableBySelected(me);\r\n                if (ut && ut.selectedTds.length) {\r\n\r\n                    if (ut.isFullCol()) {\r\n                        me.execCommand('deletecol')\r\n                    } else if (ut.isFullRow()) {\r\n                        me.execCommand('deleterow')\r\n                    } else {\r\n                        me.fireEvent('delcells');\r\n                    }\r\n                    domUtils.preventDefault(evt);\r\n                }\r\n\r\n                var caption = domUtils.findParentByTagName(me.selection.getStart(), 'caption', true),\r\n                    range = me.selection.getRange();\r\n                if (range.collapsed && caption && isEmptyBlock(caption)) {\r\n                    me.fireEvent('saveScene');\r\n                    var table = caption.parentNode;\r\n                    domUtils.remove(caption);\r\n                    if (table) {\r\n                        range.setStart(table.rows[0].cells[0], 0).setCursor(false, true);\r\n                    }\r\n                    me.fireEvent('saveScene');\r\n                }\r\n\r\n            }\r\n\r\n            if (keyCode == 46) {\r\n\r\n                ut = getUETableBySelected(me);\r\n                if (ut) {\r\n                    me.fireEvent('saveScene');\r\n                    for (var i = 0, ci; ci = ut.selectedTds[i++];) {\r\n                        domUtils.fillNode(me.document, ci)\r\n                    }\r\n                    me.fireEvent('saveScene');\r\n                    domUtils.preventDefault(evt);\r\n\r\n                }\r\n\r\n            }\r\n            if (keyCode == 13) {\r\n\r\n                var rng = me.selection.getRange(),\r\n                    caption = domUtils.findParentByTagName(rng.startContainer, 'caption', true);\r\n                if (caption) {\r\n                    var table = domUtils.findParentByTagName(caption, 'table');\r\n                    if (!rng.collapsed) {\r\n\r\n                        rng.deleteContents();\r\n                        me.fireEvent('saveScene');\r\n                    } else {\r\n                        if (caption) {\r\n                            rng.setStart(table.rows[0].cells[0], 0).setCursor(false, true);\r\n                        }\r\n                    }\r\n                    domUtils.preventDefault(evt);\r\n                    return;\r\n                }\r\n                if (rng.collapsed) {\r\n                    var table = domUtils.findParentByTagName(rng.startContainer, 'table');\r\n                    if (table) {\r\n                        var cell = table.rows[0].cells[0],\r\n                            start = domUtils.findParentByTagName(me.selection.getStart(), ['td', 'th'], true),\r\n                            preNode = table.previousSibling;\r\n                        if (cell === start && (!preNode || preNode.nodeType == 1 && preNode.tagName == 'TABLE' ) && domUtils.isStartInblock(rng)) {\r\n                            var first = domUtils.findParent(me.selection.getStart(), function(n){return domUtils.isBlockElm(n)}, true);\r\n                            if(first && ( /t(h|d)/i.test(first.tagName) || first ===  start.firstChild )){\r\n                                me.execCommand('insertparagraphbeforetable');\r\n                                domUtils.preventDefault(evt);\r\n                            }\r\n\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n\r\n            if ((evt.ctrlKey || evt.metaKey) && evt.keyCode == '67') {\r\n                tableCopyList = null;\r\n                var ut = getUETableBySelected(me);\r\n                if (ut) {\r\n                    var tds = ut.selectedTds;\r\n                    isFullCol = ut.isFullCol();\r\n                    isFullRow = ut.isFullRow();\r\n                    tableCopyList = [\r\n                        [ut.cloneCell(tds[0],null,true)]\r\n                    ];\r\n                    for (var i = 1, ci; ci = tds[i]; i++) {\r\n                        if (ci.parentNode !== tds[i - 1].parentNode) {\r\n                            tableCopyList.push([ut.cloneCell(ci,null,true)]);\r\n                        } else {\r\n                            tableCopyList[tableCopyList.length - 1].push(ut.cloneCell(ci,null,true));\r\n                        }\r\n\r\n                    }\r\n                }\r\n            }\r\n        });\r\n        me.addListener(\"tablehasdeleted\",function(){\r\n            toggleDraggableState(this, false, \"\", null);\r\n            if (dragButton)domUtils.remove(dragButton);\r\n        });\r\n\r\n        me.addListener('beforepaste', function (cmd, html) {\r\n            var me = this;\r\n            var rng = me.selection.getRange();\r\n            if (domUtils.findParentByTagName(rng.startContainer, 'caption', true)) {\r\n                var div = me.document.createElement(\"div\");\r\n                div.innerHTML = html.html;\r\n                //trace:3729\r\n                html.html = div[browser.ie9below ? 'innerText' : 'textContent'];\r\n                return;\r\n            }\r\n            var table = getUETableBySelected(me);\r\n            if (tableCopyList) {\r\n                me.fireEvent('saveScene');\r\n                var rng = me.selection.getRange();\r\n                var td = domUtils.findParentByTagName(rng.startContainer, ['td', 'th'], true), tmpNode, preNode;\r\n                if (td) {\r\n                    var ut = getUETable(td);\r\n                    if (isFullRow) {\r\n                        var rowIndex = ut.getCellInfo(td).rowIndex;\r\n                        if (td.tagName == 'TH') {\r\n                            rowIndex++;\r\n                        }\r\n                        for (var i = 0, ci; ci = tableCopyList[i++];) {\r\n                            var tr = ut.insertRow(rowIndex++, \"td\");\r\n                            for (var j = 0, cj; cj = ci[j]; j++) {\r\n                                var cell = tr.cells[j];\r\n                                if (!cell) {\r\n                                    cell = tr.insertCell(j)\r\n                                }\r\n                                cell.innerHTML = cj.innerHTML;\r\n                                cj.getAttribute('width') && cell.setAttribute('width', cj.getAttribute('width'));\r\n                                cj.getAttribute('vAlign') && cell.setAttribute('vAlign', cj.getAttribute('vAlign'));\r\n                                cj.getAttribute('align') && cell.setAttribute('align', cj.getAttribute('align'));\r\n                                cj.style.cssText && (cell.style.cssText = cj.style.cssText)\r\n                            }\r\n                            for (var j = 0, cj; cj = tr.cells[j]; j++) {\r\n                                if (!ci[j])\r\n                                    break;\r\n                                cj.innerHTML = ci[j].innerHTML;\r\n                                ci[j].getAttribute('width') && cj.setAttribute('width', ci[j].getAttribute('width'));\r\n                                ci[j].getAttribute('vAlign') && cj.setAttribute('vAlign', ci[j].getAttribute('vAlign'));\r\n                                ci[j].getAttribute('align') && cj.setAttribute('align', ci[j].getAttribute('align'));\r\n                                ci[j].style.cssText && (cj.style.cssText = ci[j].style.cssText)\r\n                            }\r\n                        }\r\n                    } else {\r\n                        if (isFullCol) {\r\n                            cellInfo = ut.getCellInfo(td);\r\n                            var maxColNum = 0;\r\n                            for (var j = 0, ci = tableCopyList[0], cj; cj = ci[j++];) {\r\n                                maxColNum += cj.colSpan || 1;\r\n                            }\r\n                            me.__hasEnterExecCommand = true;\r\n                            for (i = 0; i < maxColNum; i++) {\r\n                                me.execCommand('insertcol');\r\n                            }\r\n                            me.__hasEnterExecCommand = false;\r\n                            td = ut.table.rows[0].cells[cellInfo.cellIndex];\r\n                            if (td.tagName == 'TH') {\r\n                                td = ut.table.rows[1].cells[cellInfo.cellIndex];\r\n                            }\r\n                        }\r\n                        for (var i = 0, ci; ci = tableCopyList[i++];) {\r\n                            tmpNode = td;\r\n                            for (var j = 0, cj; cj = ci[j++];) {\r\n                                if (td) {\r\n                                    td.innerHTML = cj.innerHTML;\r\n                                    //todo 定制处理\r\n                                    cj.getAttribute('width') && td.setAttribute('width', cj.getAttribute('width'));\r\n                                    cj.getAttribute('vAlign') && td.setAttribute('vAlign', cj.getAttribute('vAlign'));\r\n                                    cj.getAttribute('align') && td.setAttribute('align', cj.getAttribute('align'));\r\n                                    cj.style.cssText && (td.style.cssText = cj.style.cssText);\r\n                                    preNode = td;\r\n                                    td = td.nextSibling;\r\n                                } else {\r\n                                    var cloneTd = cj.cloneNode(true);\r\n                                    domUtils.removeAttributes(cloneTd, ['class', 'rowSpan', 'colSpan']);\r\n\r\n                                    preNode.parentNode.appendChild(cloneTd)\r\n                                }\r\n                            }\r\n                            td = ut.getNextCell(tmpNode, true, true);\r\n                            if (!tableCopyList[i])\r\n                                break;\r\n                            if (!td) {\r\n                                var cellInfo = ut.getCellInfo(tmpNode);\r\n                                ut.table.insertRow(ut.table.rows.length);\r\n                                ut.update();\r\n                                td = ut.getVSideCell(tmpNode, true);\r\n                            }\r\n                        }\r\n                    }\r\n                    ut.update();\r\n                } else {\r\n                    table = me.document.createElement('table');\r\n                    for (var i = 0, ci; ci = tableCopyList[i++];) {\r\n                        var tr = table.insertRow(table.rows.length);\r\n                        for (var j = 0, cj; cj = ci[j++];) {\r\n                            cloneTd = UT.cloneCell(cj,null,true);\r\n                            domUtils.removeAttributes(cloneTd, ['class']);\r\n                            tr.appendChild(cloneTd)\r\n                        }\r\n                        if (j == 2 && cloneTd.rowSpan > 1) {\r\n                            cloneTd.rowSpan = 1;\r\n                        }\r\n                    }\r\n\r\n                    var defaultValue = getDefaultValue(me),\r\n                        width = me.body.offsetWidth -\r\n                            (needIEHack ? parseInt(domUtils.getComputedStyle(me.body, 'margin-left'), 10) * 2 : 0) - defaultValue.tableBorder * 2 - (me.options.offsetWidth || 0);\r\n                    me.execCommand('insertHTML', '<table  ' +\r\n                        ( isFullCol && isFullRow ? 'width=\"' + width + '\"' : '') +\r\n                        '>' + table.innerHTML.replace(/>\\s*</g, '><').replace(/\\bth\\b/gi, \"td\") + '</table>')\r\n                }\r\n                me.fireEvent('contentchange');\r\n                me.fireEvent('saveScene');\r\n                html.html = '';\r\n                return true;\r\n            } else {\r\n                var div = me.document.createElement(\"div\"), tables;\r\n                div.innerHTML = html.html;\r\n                tables = div.getElementsByTagName(\"table\");\r\n                if (domUtils.findParentByTagName(me.selection.getStart(), 'table')) {\r\n                    utils.each(tables, function (t) {\r\n                        domUtils.remove(t)\r\n                    });\r\n                    if (domUtils.findParentByTagName(me.selection.getStart(), 'caption', true)) {\r\n                        div.innerHTML = div[browser.ie ? 'innerText' : 'textContent'];\r\n                    }\r\n                } else {\r\n                    utils.each(tables, function (table) {\r\n                        removeStyleSize(table, true);\r\n                        domUtils.removeAttributes(table, ['style', 'border']);\r\n                        utils.each(domUtils.getElementsByTagName(table, \"td\"), function (td) {\r\n                            if (isEmptyBlock(td)) {\r\n                                domUtils.fillNode(me.document, td);\r\n                            }\r\n                            removeStyleSize(td, true);\r\n//                            domUtils.removeAttributes(td, ['style'])\r\n                        });\r\n                    });\r\n                }\r\n                html.html = div.innerHTML;\r\n            }\r\n        });\r\n\r\n        me.addListener('afterpaste', function () {\r\n            utils.each(domUtils.getElementsByTagName(me.body, \"table\"), function (table) {\r\n                if (table.offsetWidth > me.body.offsetWidth) {\r\n                    var defaultValue = getDefaultValue(me, table);\r\n                    table.style.width = me.body.offsetWidth - (needIEHack ? parseInt(domUtils.getComputedStyle(me.body, 'margin-left'), 10) * 2 : 0) - defaultValue.tableBorder * 2 - (me.options.offsetWidth || 0) + 'px'\r\n                }\r\n            })\r\n        });\r\n        me.addListener('blur', function () {\r\n            tableCopyList = null;\r\n        });\r\n        var timer;\r\n        me.addListener('keydown', function () {\r\n            clearTimeout(timer);\r\n            timer = setTimeout(function () {\r\n                var rng = me.selection.getRange(),\r\n                    cell = domUtils.findParentByTagName(rng.startContainer, ['th', 'td'], true);\r\n                if (cell) {\r\n                    var table = cell.parentNode.parentNode.parentNode;\r\n                    if (table.offsetWidth > table.getAttribute(\"width\")) {\r\n                        cell.style.wordBreak = \"break-all\";\r\n                    }\r\n                }\r\n\r\n            }, 100);\r\n        });\r\n        me.addListener(\"selectionchange\", function () {\r\n            toggleDraggableState(me, false, \"\", null);\r\n        });\r\n\r\n\r\n        //内容变化时触发索引更新\r\n        //todo 可否考虑标记检测，如果不涉及表格的变化就不进行索引重建和更新\r\n        me.addListener(\"contentchange\", function () {\r\n            var me = this;\r\n            //尽可能排除一些不需要更新的状况\r\n            hideDragLine(me);\r\n            if (getUETableBySelected(me))return;\r\n            var rng = me.selection.getRange();\r\n            var start = rng.startContainer;\r\n            start = domUtils.findParentByTagName(start, ['td', 'th'], true);\r\n            utils.each(domUtils.getElementsByTagName(me.document, 'table'), function (table) {\r\n                if (me.fireEvent(\"excludetable\", table) === true) return;\r\n                table.ueTable = new UT(table);\r\n                //trace:3742\r\n//                utils.each(domUtils.getElementsByTagName(me.document, 'td'), function (td) {\r\n//\r\n//                    if (domUtils.isEmptyBlock(td) && td !== start) {\r\n//                        domUtils.fillNode(me.document, td);\r\n//                        if (browser.ie && browser.version == 6) {\r\n//                            td.innerHTML = '&nbsp;'\r\n//                        }\r\n//                    }\r\n//                });\r\n//                utils.each(domUtils.getElementsByTagName(me.document, 'th'), function (th) {\r\n//                    if (domUtils.isEmptyBlock(th) && th !== start) {\r\n//                        domUtils.fillNode(me.document, th);\r\n//                        if (browser.ie && browser.version == 6) {\r\n//                            th.innerHTML = '&nbsp;'\r\n//                        }\r\n//                    }\r\n//                });\r\n                table.onmouseover = function () {\r\n                    me.fireEvent('tablemouseover', table);\r\n                };\r\n                table.onmousemove = function () {\r\n                    me.fireEvent('tablemousemove', table);\r\n                    me.options.tableDragable && toggleDragButton(true, this, me);\r\n                    utils.defer(function(){\r\n                        me.fireEvent('contentchange',50)\r\n                    },true)\r\n                };\r\n                table.onmouseout = function () {\r\n                    me.fireEvent('tablemouseout', table);\r\n                    toggleDraggableState(me, false, \"\", null);\r\n                    hideDragLine(me);\r\n                };\r\n                table.onclick = function (evt) {\r\n                    evt = me.window.event || evt;\r\n                    var target = getParentTdOrTh(evt.target || evt.srcElement);\r\n                    if (!target)return;\r\n                    var ut = getUETable(target),\r\n                        table = ut.table,\r\n                        cellInfo = ut.getCellInfo(target),\r\n                        cellsRange,\r\n                        rng = me.selection.getRange();\r\n//                    if (\"topLeft\" == inPosition(table, mouseCoords(evt))) {\r\n//                        cellsRange = ut.getCellsRange(ut.table.rows[0].cells[0], ut.getLastCell());\r\n//                        ut.setSelected(cellsRange);\r\n//                        return;\r\n//                    }\r\n//                    if (\"bottomRight\" == inPosition(table, mouseCoords(evt))) {\r\n//\r\n//                        return;\r\n//                    }\r\n                    if (inTableSide(table, target, evt, true)) {\r\n                        var endTdCol = ut.getCell(ut.indexTable[ut.rowsNum - 1][cellInfo.colIndex].rowIndex, ut.indexTable[ut.rowsNum - 1][cellInfo.colIndex].cellIndex);\r\n                        if (evt.shiftKey && ut.selectedTds.length) {\r\n                            if (ut.selectedTds[0] !== endTdCol) {\r\n                                cellsRange = ut.getCellsRange(ut.selectedTds[0], endTdCol);\r\n                                ut.setSelected(cellsRange);\r\n                            } else {\r\n                                rng && rng.selectNodeContents(endTdCol).select();\r\n                            }\r\n                        } else {\r\n                            if (target !== endTdCol) {\r\n                                cellsRange = ut.getCellsRange(target, endTdCol);\r\n                                ut.setSelected(cellsRange);\r\n                            } else {\r\n                                rng && rng.selectNodeContents(endTdCol).select();\r\n                            }\r\n                        }\r\n                        return;\r\n                    }\r\n                    if (inTableSide(table, target, evt)) {\r\n                        var endTdRow = ut.getCell(ut.indexTable[cellInfo.rowIndex][ut.colsNum - 1].rowIndex, ut.indexTable[cellInfo.rowIndex][ut.colsNum - 1].cellIndex);\r\n                        if (evt.shiftKey && ut.selectedTds.length) {\r\n                            if (ut.selectedTds[0] !== endTdRow) {\r\n                                cellsRange = ut.getCellsRange(ut.selectedTds[0], endTdRow);\r\n                                ut.setSelected(cellsRange);\r\n                            } else {\r\n                                rng && rng.selectNodeContents(endTdRow).select();\r\n                            }\r\n                        } else {\r\n                            if (target !== endTdRow) {\r\n                                cellsRange = ut.getCellsRange(target, endTdRow);\r\n                                ut.setSelected(cellsRange);\r\n                            } else {\r\n                                rng && rng.selectNodeContents(endTdRow).select();\r\n                            }\r\n                        }\r\n                    }\r\n                };\r\n            });\r\n\r\n            switchBorderColor(me, true);\r\n        });\r\n\r\n        domUtils.on(me.document, \"mousemove\", mouseMoveEvent);\r\n\r\n        domUtils.on(me.document, \"mouseout\", function (evt) {\r\n            var target = evt.target || evt.srcElement;\r\n            if (target.tagName == \"TABLE\") {\r\n                toggleDraggableState(me, false, \"\", null);\r\n            }\r\n        });\r\n        /**\r\n         * 表格隔行变色\r\n         */\r\n        me.addListener(\"interlacetable\",function(type,table,classList){\r\n            if(!table) return;\r\n            var me = this,\r\n                rows = table.rows,\r\n                len = rows.length,\r\n                getClass = function(list,index,repeat){\r\n                    return list[index] ? list[index] : repeat ? list[index % list.length]: \"\";\r\n                };\r\n            for(var i = 0;i<len;i++){\r\n                rows[i].className = getClass( classList|| me.options.classList,i,true);\r\n            }\r\n        });\r\n        me.addListener(\"uninterlacetable\",function(type,table){\r\n            if(!table) return;\r\n            var me = this,\r\n                rows = table.rows,\r\n                classList = me.options.classList,\r\n                len = rows.length;\r\n            for(var i = 0;i<len;i++){\r\n                domUtils.removeClasses( rows[i], classList );\r\n            }\r\n        });\r\n\r\n        me.addListener(\"mousedown\", mouseDownEvent);\r\n        me.addListener(\"mouseup\", mouseUpEvent);\r\n        //拖动的时候触发mouseup\r\n        domUtils.on( me.body, 'dragstart', function( evt ){\r\n            mouseUpEvent.call( me, 'dragstart', evt );\r\n        });\r\n        me.addOutputRule(function(root){\r\n            utils.each(root.getNodesByTagName('div'),function(n){\r\n                if (n.getAttr('id') == 'ue_tableDragLine') {\r\n                    n.parentNode.removeChild(n);\r\n                }\r\n            });\r\n        });\r\n\r\n        var currentRowIndex = 0;\r\n        me.addListener(\"mousedown\", function () {\r\n            currentRowIndex = 0;\r\n        });\r\n        me.addListener('tabkeydown', function () {\r\n            var range = this.selection.getRange(),\r\n                common = range.getCommonAncestor(true, true),\r\n                table = domUtils.findParentByTagName(common, 'table');\r\n            if (table) {\r\n                if (domUtils.findParentByTagName(common, 'caption', true)) {\r\n                    var cell = domUtils.getElementsByTagName(table, 'th td');\r\n                    if (cell && cell.length) {\r\n                        range.setStart(cell[0], 0).setCursor(false, true)\r\n                    }\r\n                } else {\r\n                    var cell = domUtils.findParentByTagName(common, ['td', 'th'], true),\r\n                        ua = getUETable(cell);\r\n                    currentRowIndex = cell.rowSpan > 1 ? currentRowIndex : ua.getCellInfo(cell).rowIndex;\r\n                    var nextCell = ua.getTabNextCell(cell, currentRowIndex);\r\n                    if (nextCell) {\r\n                        if (isEmptyBlock(nextCell)) {\r\n                            range.setStart(nextCell, 0).setCursor(false, true)\r\n                        } else {\r\n                            range.selectNodeContents(nextCell).select()\r\n                        }\r\n                    } else {\r\n                        me.fireEvent('saveScene');\r\n                        me.__hasEnterExecCommand = true;\r\n                        this.execCommand('insertrownext');\r\n                        me.__hasEnterExecCommand = false;\r\n                        range = this.selection.getRange();\r\n                        range.setStart(table.rows[table.rows.length - 1].cells[0], 0).setCursor();\r\n                        me.fireEvent('saveScene');\r\n                    }\r\n                }\r\n                return true;\r\n            }\r\n\r\n        });\r\n        browser.ie && me.addListener('selectionchange', function () {\r\n            toggleDraggableState(this, false, \"\", null);\r\n        });\r\n        me.addListener(\"keydown\", function (type, evt) {\r\n            var me = this;\r\n            //处理在表格的最后一个输入tab产生新的表格\r\n            var keyCode = evt.keyCode || evt.which;\r\n            if (keyCode == 8 || keyCode == 46) {\r\n                return;\r\n            }\r\n            var notCtrlKey = !evt.ctrlKey && !evt.metaKey && !evt.shiftKey && !evt.altKey;\r\n            notCtrlKey && removeSelectedClass(domUtils.getElementsByTagName(me.body, \"td\"));\r\n            var ut = getUETableBySelected(me);\r\n            if (!ut) return;\r\n            notCtrlKey && ut.clearSelected();\r\n        });\r\n\r\n        me.addListener(\"beforegetcontent\", function () {\r\n            switchBorderColor(this, false);\r\n            browser.ie && utils.each(this.document.getElementsByTagName('caption'), function (ci) {\r\n                if (domUtils.isEmptyNode(ci)) {\r\n                    ci.innerHTML = '&nbsp;'\r\n                }\r\n            });\r\n        });\r\n        me.addListener(\"aftergetcontent\", function () {\r\n            switchBorderColor(this, true);\r\n        });\r\n        me.addListener(\"getAllHtml\", function () {\r\n            removeSelectedClass(me.document.getElementsByTagName(\"td\"));\r\n        });\r\n        //修正全屏状态下插入的表格宽度在非全屏状态下撑开编辑器的情况\r\n        me.addListener(\"fullscreenchanged\", function (type, fullscreen) {\r\n            if (!fullscreen) {\r\n                var ratio = this.body.offsetWidth / document.body.offsetWidth,\r\n                    tables = domUtils.getElementsByTagName(this.body, \"table\");\r\n                utils.each(tables, function (table) {\r\n                    if (table.offsetWidth < me.body.offsetWidth) return false;\r\n                    var tds = domUtils.getElementsByTagName(table, \"td\"),\r\n                        backWidths = [];\r\n                    utils.each(tds, function (td) {\r\n                        backWidths.push(td.offsetWidth);\r\n                    });\r\n                    for (var i = 0, td; td = tds[i]; i++) {\r\n                        td.setAttribute(\"width\", Math.floor(backWidths[i] * ratio));\r\n                    }\r\n                    table.setAttribute(\"width\", Math.floor(getTableWidth(me, needIEHack, getDefaultValue(me))))\r\n                });\r\n            }\r\n        });\r\n\r\n        //重写execCommand命令，用于处理框选时的处理\r\n        var oldExecCommand = me.execCommand;\r\n        me.execCommand = function (cmd, datatat) {\r\n\r\n            var me = this,\r\n                args = arguments;\r\n\r\n            cmd = cmd.toLowerCase();\r\n            var ut = getUETableBySelected(me), tds,\r\n                range = new dom.Range(me.document),\r\n                cmdFun = me.commands[cmd] || UE.commands[cmd],\r\n                result;\r\n            if (!cmdFun) return;\r\n            if (ut && !commands[cmd] && !cmdFun.notNeedUndo && !me.__hasEnterExecCommand) {\r\n                me.__hasEnterExecCommand = true;\r\n                me.fireEvent(\"beforeexeccommand\", cmd);\r\n                tds = ut.selectedTds;\r\n                var lastState = -2, lastValue = -2, value, state;\r\n                for (var i = 0, td; td = tds[i]; i++) {\r\n                    if (isEmptyBlock(td)) {\r\n                        range.setStart(td, 0).setCursor(false, true)\r\n                    } else {\r\n                        range.selectNode(td).select(true);\r\n                    }\r\n                    state = me.queryCommandState(cmd);\r\n                    value = me.queryCommandValue(cmd);\r\n                    if (state != -1) {\r\n                        if (lastState !== state || lastValue !== value) {\r\n                            me._ignoreContentChange = true;\r\n                            result = oldExecCommand.apply(me, arguments);\r\n                            me._ignoreContentChange = false;\r\n\r\n                        }\r\n                        lastState = me.queryCommandState(cmd);\r\n                        lastValue = me.queryCommandValue(cmd);\r\n                        if (domUtils.isEmptyBlock(td)) {\r\n                            domUtils.fillNode(me.document, td)\r\n                        }\r\n                    }\r\n                }\r\n                range.setStart(tds[0], 0).shrinkBoundary(true).setCursor(false, true);\r\n                me.fireEvent('contentchange');\r\n                me.fireEvent(\"afterexeccommand\", cmd);\r\n                me.__hasEnterExecCommand = false;\r\n                me._selectionChange();\r\n            } else {\r\n                result = oldExecCommand.apply(me, arguments);\r\n            }\r\n            return result;\r\n        };\r\n\r\n\r\n    });\r\n    /**\r\n     * 删除obj的宽高style，改成属性宽高\r\n     * @param obj\r\n     * @param replaceToProperty\r\n     */\r\n    function removeStyleSize(obj, replaceToProperty) {\r\n        removeStyle(obj, \"width\", true);\r\n        removeStyle(obj, \"height\", true);\r\n    }\r\n\r\n    function removeStyle(obj, styleName, replaceToProperty) {\r\n        if (obj.style[styleName]) {\r\n            replaceToProperty && obj.setAttribute(styleName, parseInt(obj.style[styleName], 10));\r\n            obj.style[styleName] = \"\";\r\n        }\r\n    }\r\n\r\n    function getParentTdOrTh(ele) {\r\n        if (ele.tagName == \"TD\" || ele.tagName == \"TH\") return ele;\r\n        var td;\r\n        if (td = domUtils.findParentByTagName(ele, \"td\", true) || domUtils.findParentByTagName(ele, \"th\", true)) return td;\r\n        return null;\r\n    }\r\n\r\n    function isEmptyBlock(node) {\r\n        var reg = new RegExp(domUtils.fillChar, 'g');\r\n        if (node[browser.ie ? 'innerText' : 'textContent'].replace(/^\\s*$/, '').replace(reg, '').length > 0) {\r\n            return 0;\r\n        }\r\n        for (var n in dtd.$isNotEmpty) {\r\n            if (node.getElementsByTagName(n).length) {\r\n                return 0;\r\n            }\r\n        }\r\n        return 1;\r\n    }\r\n\r\n\r\n    function mouseCoords(evt) {\r\n        if (evt.pageX || evt.pageY) {\r\n            return { x:evt.pageX, y:evt.pageY };\r\n        }\r\n        return {\r\n            x:evt.clientX + me.document.body.scrollLeft - me.document.body.clientLeft,\r\n            y:evt.clientY + me.document.body.scrollTop - me.document.body.clientTop\r\n        };\r\n    }\r\n\r\n    function mouseMoveEvent(evt) {\r\n\r\n        if( isEditorDisabled() ) {\r\n            return;\r\n        }\r\n\r\n        try {\r\n\r\n            //普通状态下鼠标移动\r\n            var target = getParentTdOrTh(evt.target || evt.srcElement),\r\n                pos;\r\n\r\n            //区分用户的行为是拖动还是双击\r\n            if( isInResizeBuffer  ) {\r\n\r\n                me.body.style.webkitUserSelect = 'none';\r\n\r\n                if( Math.abs( userActionStatus.x - evt.clientX ) > offsetOfTableCell || Math.abs( userActionStatus.y - evt.clientY ) > offsetOfTableCell ) {\r\n                    clearTableDragTimer();\r\n                    isInResizeBuffer = false;\r\n                    singleClickState = 0;\r\n                    //drag action\r\n                    tableBorderDrag(evt);\r\n                }\r\n            }\r\n\r\n            //修改单元格大小时的鼠标移动\r\n            if (onDrag && dragTd) {\r\n                singleClickState = 0;\r\n                me.body.style.webkitUserSelect = 'none';\r\n                me.selection.getNative()[browser.ie9below ? 'empty' : 'removeAllRanges']();\r\n                pos = mouseCoords(evt);\r\n                toggleDraggableState(me, true, onDrag, pos, target);\r\n                if (onDrag == \"h\") {\r\n                    dragLine.style.left = getPermissionX(dragTd, evt) + \"px\";\r\n                } else if (onDrag == \"v\") {\r\n                    dragLine.style.top = getPermissionY(dragTd, evt) + \"px\";\r\n                }\r\n                return;\r\n            }\r\n            //当鼠标处于table上时，修改移动过程中的光标状态\r\n            if (target) {\r\n                //针对使用table作为容器的组件不触发拖拽效果\r\n                if (me.fireEvent('excludetable', target) === true)\r\n                    return;\r\n                pos = mouseCoords(evt);\r\n                var state = getRelation(target, pos),\r\n                    table = domUtils.findParentByTagName(target, \"table\", true);\r\n\r\n                if (inTableSide(table, target, evt, true)) {\r\n                    if (me.fireEvent(\"excludetable\", table) === true) return;\r\n                    me.body.style.cursor = \"url(\" + me.options.cursorpath + \"h.png),pointer\";\r\n                } else if (inTableSide(table, target, evt)) {\r\n                    if (me.fireEvent(\"excludetable\", table) === true) return;\r\n                    me.body.style.cursor = \"url(\" + me.options.cursorpath + \"v.png),pointer\";\r\n                } else {\r\n                    me.body.style.cursor = \"text\";\r\n                    var curCell = target;\r\n                    if (/\\d/.test(state)) {\r\n                        state = state.replace(/\\d/, '');\r\n                        target = getUETable(target).getPreviewCell(target, state == \"v\");\r\n                    }\r\n                    //位于第一行的顶部或者第一列的左边时不可拖动\r\n                    toggleDraggableState(me, target ? !!state : false, target ? state : '', pos, target);\r\n\r\n                }\r\n            } else {\r\n                toggleDragButton(false, table, me);\r\n            }\r\n\r\n        } catch (e) {\r\n            showError(e);\r\n        }\r\n    }\r\n\r\n    var dragButtonTimer;\r\n\r\n    function toggleDragButton(show, table, editor) {\r\n        if (!show) {\r\n            if (dragOver)return;\r\n            dragButtonTimer = setTimeout(function () {\r\n                !dragOver && dragButton && dragButton.parentNode && dragButton.parentNode.removeChild(dragButton);\r\n            }, 2000);\r\n        } else {\r\n            createDragButton(table, editor);\r\n        }\r\n    }\r\n\r\n    function createDragButton(table, editor) {\r\n        var pos = domUtils.getXY(table),\r\n            doc = table.ownerDocument;\r\n        if (dragButton && dragButton.parentNode)return dragButton;\r\n        dragButton = doc.createElement(\"div\");\r\n        dragButton.contentEditable = false;\r\n        dragButton.innerHTML = \"\";\r\n        dragButton.style.cssText = \"width:15px;height:15px;background-image:url(\" + editor.options.UEDITOR_HOME_URL + \"dialogs/table/dragicon.png);position: absolute;cursor:move;top:\" + (pos.y - 15) + \"px;left:\" + (pos.x) + \"px;\";\r\n        domUtils.unSelectable(dragButton);\r\n        dragButton.onmouseover = function (evt) {\r\n            dragOver = true;\r\n        };\r\n        dragButton.onmouseout = function (evt) {\r\n            dragOver = false;\r\n        };\r\n        domUtils.on(dragButton, 'click', function (type, evt) {\r\n            doClick(evt, this);\r\n        });\r\n        domUtils.on(dragButton, 'dblclick', function (type, evt) {\r\n            doDblClick(evt);\r\n        });\r\n        domUtils.on(dragButton, 'dragstart', function (type, evt) {\r\n            domUtils.preventDefault(evt);\r\n        });\r\n        var timer;\r\n\r\n        function doClick(evt, button) {\r\n            // 部分浏览器下需要清理\r\n            clearTimeout(timer);\r\n            timer = setTimeout(function () {\r\n                editor.fireEvent(\"tableClicked\", table, button);\r\n            }, 300);\r\n        }\r\n\r\n        function doDblClick(evt) {\r\n            clearTimeout(timer);\r\n            var ut = getUETable(table),\r\n                start = table.rows[0].cells[0],\r\n                end = ut.getLastCell(),\r\n                range = ut.getCellsRange(start, end);\r\n            editor.selection.getRange().setStart(start, 0).setCursor(false, true);\r\n            ut.setSelected(range);\r\n        }\r\n\r\n        doc.body.appendChild(dragButton);\r\n    }\r\n\r\n\r\n//    function inPosition(table, pos) {\r\n//        var tablePos = domUtils.getXY(table),\r\n//            width = table.offsetWidth,\r\n//            height = table.offsetHeight;\r\n//        if (pos.x - tablePos.x < 5 && pos.y - tablePos.y < 5) {\r\n//            return \"topLeft\";\r\n//        } else if (tablePos.x + width - pos.x < 5 && tablePos.y + height - pos.y < 5) {\r\n//            return \"bottomRight\";\r\n//        }\r\n//    }\r\n\r\n    function inTableSide(table, cell, evt, top) {\r\n        var pos = mouseCoords(evt),\r\n            state = getRelation(cell, pos);\r\n\r\n        if (top) {\r\n            var caption = table.getElementsByTagName(\"caption\")[0],\r\n                capHeight = caption ? caption.offsetHeight : 0;\r\n            return (state == \"v1\") && ((pos.y - domUtils.getXY(table).y - capHeight) < 8);\r\n        } else {\r\n            return (state == \"h1\") && ((pos.x - domUtils.getXY(table).x) < 8);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * 获取拖动时允许的X轴坐标\r\n     * @param dragTd\r\n     * @param evt\r\n     */\r\n    function getPermissionX(dragTd, evt) {\r\n        var ut = getUETable(dragTd);\r\n        if (ut) {\r\n            var preTd = ut.getSameEndPosCells(dragTd, \"x\")[0],\r\n                nextTd = ut.getSameStartPosXCells(dragTd)[0],\r\n                mouseX = mouseCoords(evt).x,\r\n                left = (preTd ? domUtils.getXY(preTd).x : domUtils.getXY(ut.table).x) + 20 ,\r\n                right = nextTd ? domUtils.getXY(nextTd).x + nextTd.offsetWidth - 20 : (me.body.offsetWidth + 5 || parseInt(domUtils.getComputedStyle(me.body, \"width\"), 10));\r\n\r\n            left += cellMinWidth;\r\n            right -= cellMinWidth;\r\n\r\n            return mouseX < left ? left : mouseX > right ? right : mouseX;\r\n        }\r\n    }\r\n\r\n    /**\r\n     * 获取拖动时允许的Y轴坐标\r\n     */\r\n    function getPermissionY(dragTd, evt) {\r\n        try {\r\n            var top = domUtils.getXY(dragTd).y,\r\n                mousePosY = mouseCoords(evt).y;\r\n            return mousePosY < top ? top : mousePosY;\r\n        } catch (e) {\r\n            showError(e);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * 移动状态切换\r\n     */\r\n    function toggleDraggableState(editor, draggable, dir, mousePos, cell) {\r\n        try {\r\n            editor.body.style.cursor = dir == \"h\" ? \"col-resize\" : dir == \"v\" ? \"row-resize\" : \"text\";\r\n            if (browser.ie) {\r\n                if (dir && !mousedown && !getUETableBySelected(editor)) {\r\n                    getDragLine(editor, editor.document);\r\n                    showDragLineAt(dir, cell);\r\n                } else {\r\n                    hideDragLine(editor)\r\n                }\r\n            }\r\n            onBorder = draggable;\r\n        } catch (e) {\r\n            showError(e);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * 获取与UETable相关的resize line\r\n     * @param uetable UETable对象\r\n     */\r\n    function getResizeLineByUETable() {\r\n\r\n        var lineId = '_UETableResizeLine',\r\n            line = this.document.getElementById( lineId );\r\n\r\n        if( !line ) {\r\n            line = this.document.createElement(\"div\");\r\n            line.id = lineId;\r\n            line.contnetEditable = false;\r\n            line.setAttribute(\"unselectable\", \"on\");\r\n\r\n            var styles = {\r\n                width: 2*cellBorderWidth + 1 + 'px',\r\n                position: 'absolute',\r\n                'z-index': 100000,\r\n                cursor: 'col-resize',\r\n                background: 'red',\r\n                display: 'none'\r\n            };\r\n\r\n            //切换状态\r\n            line.onmouseout = function(){\r\n                this.style.display = 'none';\r\n            };\r\n\r\n            utils.extend( line.style, styles );\r\n\r\n            this.document.body.appendChild( line );\r\n\r\n        }\r\n\r\n        return line;\r\n\r\n    }\r\n\r\n    /**\r\n     * 更新resize-line\r\n     */\r\n    function updateResizeLine( cell, uetable ) {\r\n\r\n        var line = getResizeLineByUETable.call( this ),\r\n            table = uetable.table,\r\n            styles = {\r\n                top: domUtils.getXY( table ).y + 'px',\r\n                left: domUtils.getXY( cell).x + cell.offsetWidth - cellBorderWidth + 'px',\r\n                display: 'block',\r\n                height: table.offsetHeight + 'px'\r\n            };\r\n\r\n        utils.extend( line.style, styles );\r\n\r\n    }\r\n\r\n    /**\r\n     * 显示resize-line\r\n     */\r\n    function showResizeLine( cell ) {\r\n\r\n        var uetable = getUETable( cell );\r\n\r\n        updateResizeLine.call( this, cell, uetable );\r\n\r\n    }\r\n\r\n    /**\r\n     * 获取鼠标与当前单元格的相对位置\r\n     * @param ele\r\n     * @param mousePos\r\n     */\r\n    function getRelation(ele, mousePos) {\r\n        var elePos = domUtils.getXY(ele);\r\n\r\n        if( !elePos ) {\r\n            return '';\r\n        }\r\n\r\n        if (elePos.x + ele.offsetWidth - mousePos.x < cellBorderWidth) {\r\n            return \"h\";\r\n        }\r\n        if (mousePos.x - elePos.x < cellBorderWidth) {\r\n            return 'h1'\r\n        }\r\n        if (elePos.y + ele.offsetHeight - mousePos.y < cellBorderWidth) {\r\n            return \"v\";\r\n        }\r\n        if (mousePos.y - elePos.y < cellBorderWidth) {\r\n            return 'v1'\r\n        }\r\n        return '';\r\n    }\r\n\r\n    function mouseDownEvent(type, evt) {\r\n\r\n        if( isEditorDisabled() ) {\r\n            return ;\r\n        }\r\n\r\n        userActionStatus = {\r\n            x: evt.clientX,\r\n            y: evt.clientY\r\n        };\r\n\r\n        //右键菜单单独处理\r\n        if (evt.button == 2) {\r\n            var ut = getUETableBySelected(me),\r\n                flag = false;\r\n\r\n            if (ut) {\r\n                var td = getTargetTd(me, evt);\r\n                utils.each(ut.selectedTds, function (ti) {\r\n                    if (ti === td) {\r\n                        flag = true;\r\n                    }\r\n                });\r\n                if (!flag) {\r\n                    removeSelectedClass(domUtils.getElementsByTagName(me.body, \"th td\"));\r\n                    ut.clearSelected()\r\n                } else {\r\n                    td = ut.selectedTds[0];\r\n                    setTimeout(function () {\r\n                        me.selection.getRange().setStart(td, 0).setCursor(false, true);\r\n                    }, 0);\r\n\r\n                }\r\n            }\r\n        } else {\r\n            tableClickHander( evt );\r\n        }\r\n\r\n    }\r\n\r\n    //清除表格的计时器\r\n    function clearTableTimer() {\r\n        tabTimer && clearTimeout( tabTimer );\r\n        tabTimer = null;\r\n    }\r\n\r\n    //双击收缩\r\n    function tableDbclickHandler(evt) {\r\n        singleClickState = 0;\r\n        evt = evt || me.window.event;\r\n        var target = getParentTdOrTh(evt.target || evt.srcElement);\r\n        if (target) {\r\n            var h;\r\n            if (h = getRelation(target, mouseCoords(evt))) {\r\n\r\n                hideDragLine( me );\r\n\r\n                if (h == 'h1') {\r\n                    h = 'h';\r\n                    if (inTableSide(domUtils.findParentByTagName(target, \"table\"), target, evt)) {\r\n                        me.execCommand('adaptbywindow');\r\n                    } else {\r\n                        target = getUETable(target).getPreviewCell(target);\r\n                        if (target) {\r\n                            var rng = me.selection.getRange();\r\n                            rng.selectNodeContents(target).setCursor(true, true)\r\n                        }\r\n                    }\r\n                }\r\n                if (h == 'h') {\r\n                    var ut = getUETable(target),\r\n                        table = ut.table,\r\n                        cells = getCellsByMoveBorder( target, table, true );\r\n\r\n                    cells = extractArray( cells, 'left' );\r\n\r\n                    ut.width = ut.offsetWidth;\r\n\r\n                    var oldWidth = [],\r\n                        newWidth = [];\r\n\r\n                    utils.each( cells, function( cell ){\r\n\r\n                        oldWidth.push( cell.offsetWidth );\r\n\r\n                    } );\r\n\r\n                    utils.each( cells, function( cell ){\r\n\r\n                        cell.removeAttribute(\"width\");\r\n\r\n                    } );\r\n\r\n                    window.setTimeout( function(){\r\n\r\n                        //是否允许改变\r\n                        var changeable = true;\r\n\r\n                        utils.each( cells, function( cell, index ){\r\n\r\n                            var width = cell.offsetWidth;\r\n\r\n                            if( width > oldWidth[index] ) {\r\n                                changeable = false;\r\n                                return false;\r\n                            }\r\n\r\n                            newWidth.push( width );\r\n\r\n                        } );\r\n\r\n                        var change = changeable ? newWidth : oldWidth;\r\n\r\n                        utils.each( cells, function( cell, index ){\r\n\r\n                            cell.width = change[index] - getTabcellSpace();\r\n\r\n                        } );\r\n\r\n\r\n                    }, 0 );\r\n\r\n//                    minWidth -= cellMinWidth;\r\n//\r\n//                    table.removeAttribute(\"width\");\r\n//                    utils.each(cells, function (cell) {\r\n//                        cell.style.width = \"\";\r\n//                        cell.width -= minWidth;\r\n//                    });\r\n\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    function tableClickHander( evt ) {\r\n\r\n        removeSelectedClass(domUtils.getElementsByTagName(me.body, \"td th\"));\r\n        //trace:3113\r\n        //选中单元格，点击table外部，不会清掉table上挂的ueTable,会引起getUETableBySelected方法返回值\r\n        utils.each(me.document.getElementsByTagName('table'), function (t) {\r\n            t.ueTable = null;\r\n        });\r\n        startTd = getTargetTd(me, evt);\r\n        if( !startTd ) return;\r\n        var table = domUtils.findParentByTagName(startTd, \"table\", true);\r\n        ut = getUETable(table);\r\n        ut && ut.clearSelected();\r\n\r\n        //判断当前鼠标状态\r\n        if (!onBorder) {\r\n            me.document.body.style.webkitUserSelect = '';\r\n            mousedown = true;\r\n            me.addListener('mouseover', mouseOverEvent);\r\n        } else {\r\n            //边框上的动作处理\r\n            borderActionHandler( evt );\r\n        }\r\n\r\n\r\n    }\r\n\r\n    //处理表格边框上的动作, 这里做延时处理，避免两种动作互相影响\r\n    function borderActionHandler( evt ) {\r\n\r\n        if ( browser.ie ) {\r\n            evt = reconstruct(evt );\r\n        }\r\n\r\n        clearTableDragTimer();\r\n\r\n        //是否正在等待resize的缓冲中\r\n        isInResizeBuffer = true;\r\n\r\n        tableDragTimer = setTimeout(function(){\r\n            tableBorderDrag( evt );\r\n        }, dblclickTime);\r\n\r\n    }\r\n\r\n    function extractArray( originArr, key ) {\r\n\r\n        var result = [],\r\n            tmp = null;\r\n\r\n        for( var i = 0, len = originArr.length; i<len; i++ ) {\r\n\r\n            tmp = originArr[ i ][ key ];\r\n\r\n            if( tmp ) {\r\n                result.push( tmp );\r\n            }\r\n\r\n        }\r\n\r\n        return result;\r\n\r\n    }\r\n\r\n    function clearTableDragTimer() {\r\n        tableDragTimer && clearTimeout(tableDragTimer);\r\n        tableDragTimer = null;\r\n    }\r\n\r\n    function reconstruct( obj ) {\r\n\r\n        var attrs = ['pageX', 'pageY', 'clientX', 'clientY', 'srcElement', 'target'],\r\n            newObj = {};\r\n\r\n        if( obj ) {\r\n\r\n            for( var i = 0, key, val; key = attrs[i]; i++ ) {\r\n                val=obj[ key ];\r\n                val && (newObj[ key ] = val);\r\n            }\r\n\r\n        }\r\n\r\n        return newObj;\r\n\r\n    }\r\n\r\n    //边框拖动\r\n    function tableBorderDrag( evt ) {\r\n\r\n        isInResizeBuffer = false;\r\n\r\n        startTd = evt.target || evt.srcElement;\r\n        if( !startTd ) return;\r\n        var state = getRelation(startTd, mouseCoords(evt));\r\n        if (/\\d/.test(state)) {\r\n            state = state.replace(/\\d/, '');\r\n            startTd = getUETable(startTd).getPreviewCell(startTd, state == 'v');\r\n        }\r\n        hideDragLine(me);\r\n        getDragLine(me, me.document);\r\n        me.fireEvent('saveScene');\r\n        showDragLineAt(state, startTd);\r\n        mousedown = true;\r\n        //拖动开始\r\n        onDrag = state;\r\n        dragTd = startTd;\r\n    }\r\n\r\n    function mouseUpEvent(type, evt) {\r\n\r\n        if( isEditorDisabled() ) {\r\n            return ;\r\n        }\r\n\r\n        clearTableDragTimer();\r\n\r\n        isInResizeBuffer = false;\r\n\r\n        if( onBorder ) {\r\n            singleClickState = ++singleClickState % 3;\r\n\r\n            userActionStatus = {\r\n                x: evt.clientX,\r\n                y: evt.clientY\r\n            };\r\n\r\n            tableResizeTimer = setTimeout(function(){\r\n                singleClickState > 0 && singleClickState--;\r\n            }, dblclickTime );\r\n\r\n            if( singleClickState === 2 ) {\r\n\r\n                singleClickState = 0;\r\n                tableDbclickHandler(evt);\r\n                return;\r\n\r\n            }\r\n\r\n        }\r\n\r\n        if (evt.button == 2)return;\r\n        var me = this;\r\n        //清除表格上原生跨选问题\r\n        var range = me.selection.getRange(),\r\n            start = domUtils.findParentByTagName(range.startContainer, 'table', true),\r\n            end = domUtils.findParentByTagName(range.endContainer, 'table', true);\r\n\r\n        if (start || end) {\r\n            if (start === end) {\r\n                start = domUtils.findParentByTagName(range.startContainer, ['td', 'th', 'caption'], true);\r\n                end = domUtils.findParentByTagName(range.endContainer, ['td', 'th', 'caption'], true);\r\n                if (start !== end) {\r\n                    me.selection.clearRange()\r\n                }\r\n            } else {\r\n                me.selection.clearRange()\r\n            }\r\n        }\r\n        mousedown = false;\r\n        me.document.body.style.webkitUserSelect = '';\r\n        //拖拽状态下的mouseUP\r\n        if ( onDrag && dragTd ) {\r\n\r\n            me.selection.getNative()[browser.ie9below ? 'empty' : 'removeAllRanges']();\r\n\r\n            singleClickState = 0;\r\n            dragLine = me.document.getElementById('ue_tableDragLine');\r\n\r\n            // trace 3973\r\n            if (dragLine) {\r\n                var dragTdPos = domUtils.getXY(dragTd),\r\n                    dragLinePos = domUtils.getXY(dragLine);\r\n\r\n                switch (onDrag) {\r\n                    case \"h\":\r\n                        changeColWidth(dragTd, dragLinePos.x - dragTdPos.x);\r\n                        break;\r\n                    case \"v\":\r\n                        changeRowHeight(dragTd, dragLinePos.y - dragTdPos.y - dragTd.offsetHeight);\r\n                        break;\r\n                    default:\r\n                }\r\n                onDrag = \"\";\r\n                dragTd = null;\r\n\r\n                hideDragLine(me);\r\n                me.fireEvent('saveScene');\r\n                return;\r\n            }\r\n        }\r\n        //正常状态下的mouseup\r\n        if (!startTd) {\r\n            var target = domUtils.findParentByTagName(evt.target || evt.srcElement, \"td\", true);\r\n            if (!target) target = domUtils.findParentByTagName(evt.target || evt.srcElement, \"th\", true);\r\n            if (target && (target.tagName == \"TD\" || target.tagName == \"TH\")) {\r\n                if (me.fireEvent(\"excludetable\", target) === true) return;\r\n                range = new dom.Range(me.document);\r\n                range.setStart(target, 0).setCursor(false, true);\r\n            }\r\n        } else {\r\n            var ut = getUETable(startTd),\r\n                cell = ut ? ut.selectedTds[0] : null;\r\n            if (cell) {\r\n                range = new dom.Range(me.document);\r\n                if (domUtils.isEmptyBlock(cell)) {\r\n                    range.setStart(cell, 0).setCursor(false, true);\r\n                } else {\r\n                    range.selectNodeContents(cell).shrinkBoundary().setCursor(false, true);\r\n                }\r\n            } else {\r\n                range = me.selection.getRange().shrinkBoundary();\r\n                if (!range.collapsed) {\r\n                    var start = domUtils.findParentByTagName(range.startContainer, ['td', 'th'], true),\r\n                        end = domUtils.findParentByTagName(range.endContainer, ['td', 'th'], true);\r\n                    //在table里边的不能清除\r\n                    if (start && !end || !start && end || start && end && start !== end) {\r\n                        range.setCursor(false, true);\r\n                    }\r\n                }\r\n            }\r\n            startTd = null;\r\n            me.removeListener('mouseover', mouseOverEvent);\r\n        }\r\n        me._selectionChange(250, evt);\r\n    }\r\n\r\n    function mouseOverEvent(type, evt) {\r\n\r\n        if( isEditorDisabled() ) {\r\n            return;\r\n        }\r\n\r\n        var me = this,\r\n            tar = evt.target || evt.srcElement;\r\n        currentTd = domUtils.findParentByTagName(tar, \"td\", true) || domUtils.findParentByTagName(tar, \"th\", true);\r\n        //需要判断两个TD是否位于同一个表格内\r\n        if (startTd && currentTd &&\r\n            ((startTd.tagName == \"TD\" && currentTd.tagName == \"TD\") || (startTd.tagName == \"TH\" && currentTd.tagName == \"TH\")) &&\r\n            domUtils.findParentByTagName(startTd, 'table') == domUtils.findParentByTagName(currentTd, 'table')) {\r\n            var ut = getUETable(currentTd);\r\n            if (startTd != currentTd) {\r\n                me.document.body.style.webkitUserSelect = 'none';\r\n                me.selection.getNative()[browser.ie9below ? 'empty' : 'removeAllRanges']();\r\n                var range = ut.getCellsRange(startTd, currentTd);\r\n                ut.setSelected(range);\r\n            } else {\r\n                me.document.body.style.webkitUserSelect = '';\r\n                ut.clearSelected();\r\n            }\r\n\r\n        }\r\n        evt.preventDefault ? evt.preventDefault() : (evt.returnValue = false);\r\n    }\r\n\r\n    function setCellHeight(cell, height, backHeight) {\r\n        var lineHight = parseInt(domUtils.getComputedStyle(cell, \"line-height\"), 10),\r\n            tmpHeight = backHeight + height;\r\n        height = tmpHeight < lineHight ? lineHight : tmpHeight;\r\n        if (cell.style.height) cell.style.height = \"\";\r\n        cell.rowSpan == 1 ? cell.setAttribute(\"height\", height) : (cell.removeAttribute && cell.removeAttribute(\"height\"));\r\n    }\r\n\r\n    function getWidth(cell) {\r\n        if (!cell)return 0;\r\n        return parseInt(domUtils.getComputedStyle(cell, \"width\"), 10);\r\n    }\r\n\r\n    function changeColWidth(cell, changeValue) {\r\n\r\n        var ut = getUETable(cell);\r\n        if (ut) {\r\n\r\n            //根据当前移动的边框获取相关的单元格\r\n            var table = ut.table,\r\n                cells = getCellsByMoveBorder( cell, table );\r\n\r\n            table.style.width = \"\";\r\n            table.removeAttribute(\"width\");\r\n\r\n            //修正改变量\r\n            changeValue = correctChangeValue( changeValue, cell, cells );\r\n\r\n            if (cell.nextSibling) {\r\n\r\n                var i=0;\r\n\r\n                utils.each( cells, function( cellGroup ){\r\n\r\n                    cellGroup.left.width = (+cellGroup.left.width)+changeValue;\r\n                    cellGroup.right && ( cellGroup.right.width = (+cellGroup.right.width)-changeValue );\r\n\r\n                } );\r\n\r\n            } else {\r\n\r\n                utils.each( cells, function( cellGroup ){\r\n                    cellGroup.left.width -= -changeValue;\r\n                } );\r\n\r\n            }\r\n        }\r\n\r\n    }\r\n\r\n    function isEditorDisabled() {\r\n        return me.body.contentEditable === \"false\";\r\n    }\r\n\r\n    function changeRowHeight(td, changeValue) {\r\n        if (Math.abs(changeValue) < 10) return;\r\n        var ut = getUETable(td);\r\n        if (ut) {\r\n            var cells = ut.getSameEndPosCells(td, \"y\"),\r\n            //备份需要连带变化的td的原始高度，否则后期无法获取正确的值\r\n                backHeight = cells[0] ? cells[0].offsetHeight : 0;\r\n            for (var i = 0, cell; cell = cells[i++];) {\r\n                setCellHeight(cell, changeValue, backHeight);\r\n            }\r\n        }\r\n\r\n    }\r\n\r\n    /**\r\n     * 获取调整单元格大小的相关单元格\r\n     * @isContainMergeCell 返回的结果中是否包含发生合并后的单元格\r\n     */\r\n    function getCellsByMoveBorder( cell, table, isContainMergeCell ) {\r\n\r\n        if( !table ) {\r\n            table = domUtils.findParentByTagName( cell, 'table' );\r\n        }\r\n\r\n        if( !table ) {\r\n            return null;\r\n        }\r\n\r\n        //获取到该单元格所在行的序列号\r\n        var index = domUtils.getNodeIndex( cell ),\r\n            temp = cell,\r\n            rows = table.rows,\r\n            colIndex = 0;\r\n\r\n        while( temp ) {\r\n            //获取到当前单元格在未发生单元格合并时的序列\r\n            if( temp.nodeType === 1 ) {\r\n                colIndex += (temp.colSpan || 1);\r\n            }\r\n            temp = temp.previousSibling;\r\n        }\r\n\r\n        temp = null;\r\n\r\n        //记录想关的单元格\r\n        var borderCells = [];\r\n\r\n        utils.each(rows, function( tabRow ){\r\n\r\n            var cells = tabRow.cells,\r\n                currIndex = 0;\r\n\r\n            utils.each( cells, function( tabCell ){\r\n\r\n                currIndex += (tabCell.colSpan || 1);\r\n\r\n                if( currIndex === colIndex ) {\r\n\r\n                    borderCells.push({\r\n                        left: tabCell,\r\n                        right: tabCell.nextSibling || null\r\n                    });\r\n\r\n                    return false;\r\n\r\n                } else if( currIndex > colIndex ) {\r\n\r\n                    if( isContainMergeCell ) {\r\n                        borderCells.push({\r\n                            left: tabCell\r\n                        });\r\n                    }\r\n\r\n                    return false;\r\n                }\r\n\r\n\r\n            } );\r\n\r\n        });\r\n\r\n        return borderCells;\r\n\r\n    }\r\n\r\n\r\n    /**\r\n     * 通过给定的单元格集合获取最小的单元格width\r\n     */\r\n    function getMinWidthByTableCells( cells ) {\r\n\r\n        var minWidth = Number.MAX_VALUE;\r\n\r\n        for( var i = 0, curCell; curCell = cells[ i ] ; i++ ) {\r\n\r\n            minWidth = Math.min( minWidth, curCell.width || getTableCellWidth( curCell ) );\r\n\r\n        }\r\n\r\n        return minWidth;\r\n\r\n    }\r\n\r\n    function correctChangeValue( changeValue, relatedCell, cells ) {\r\n\r\n        //为单元格的paading预留空间\r\n        changeValue -= getTabcellSpace();\r\n\r\n        if( changeValue < 0 ) {\r\n            return 0;\r\n        }\r\n\r\n        changeValue -= getTableCellWidth( relatedCell );\r\n\r\n        //确定方向\r\n        var direction = changeValue < 0 ? 'left':'right';\r\n\r\n        changeValue = Math.abs(changeValue);\r\n\r\n        //只关心非最后一个单元格就可以\r\n        utils.each( cells, function( cellGroup ){\r\n\r\n            var curCell = cellGroup[direction];\r\n\r\n            //为单元格保留最小空间\r\n            if( curCell ) {\r\n                changeValue = Math.min( changeValue, getTableCellWidth( curCell )-cellMinWidth );\r\n            }\r\n\r\n\r\n        } );\r\n\r\n\r\n        //修正越界\r\n        changeValue = changeValue < 0 ? 0 : changeValue;\r\n\r\n        return direction === 'left' ? -changeValue : changeValue;\r\n\r\n    }\r\n\r\n    function getTableCellWidth( cell ) {\r\n\r\n        var width = 0,\r\n            //偏移纠正量\r\n            offset = 0,\r\n            width = cell.offsetWidth - getTabcellSpace();\r\n\r\n        //最后一个节点纠正一下\r\n        if( !cell.nextSibling ) {\r\n\r\n            width -= getTableCellOffset( cell );\r\n\r\n        }\r\n\r\n        width = width < 0 ? 0 : width;\r\n\r\n        try {\r\n            cell.width = width;\r\n        } catch(e) {\r\n        }\r\n\r\n        return width;\r\n\r\n    }\r\n\r\n    /**\r\n     * 获取单元格所在表格的最末单元格的偏移量\r\n     */\r\n    function getTableCellOffset( cell ) {\r\n\r\n        tab = domUtils.findParentByTagName( cell, \"table\", false);\r\n\r\n        if( tab.offsetVal === undefined ) {\r\n\r\n            var prev = cell.previousSibling;\r\n\r\n            if( prev ) {\r\n\r\n                //最后一个单元格和前一个单元格的width diff结果 如果恰好为一个border width， 则条件成立\r\n                tab.offsetVal = cell.offsetWidth - prev.offsetWidth === UT.borderWidth ? UT.borderWidth : 0;\r\n\r\n            } else {\r\n                tab.offsetVal = 0;\r\n            }\r\n\r\n        }\r\n\r\n        return tab.offsetVal;\r\n\r\n    }\r\n\r\n    function getTabcellSpace() {\r\n\r\n        if( UT.tabcellSpace === undefined ) {\r\n\r\n            var cell = null,\r\n                tab = me.document.createElement(\"table\"),\r\n                tbody = me.document.createElement(\"tbody\"),\r\n                trow = me.document.createElement(\"tr\"),\r\n                tabcell = me.document.createElement(\"td\"),\r\n                mirror = null;\r\n\r\n            tabcell.style.cssText = 'border: 0;';\r\n            tabcell.width = 1;\r\n\r\n            trow.appendChild( tabcell );\r\n            trow.appendChild( mirror = tabcell.cloneNode( false ) );\r\n\r\n            tbody.appendChild( trow );\r\n\r\n            tab.appendChild( tbody );\r\n\r\n            tab.style.cssText = \"visibility: hidden;\";\r\n\r\n            me.body.appendChild( tab );\r\n\r\n            UT.paddingSpace = tabcell.offsetWidth - 1;\r\n\r\n            var tmpTabWidth = tab.offsetWidth;\r\n\r\n            tabcell.style.cssText = '';\r\n            mirror.style.cssText = '';\r\n\r\n            UT.borderWidth = ( tab.offsetWidth - tmpTabWidth ) / 3;\r\n\r\n            UT.tabcellSpace = UT.paddingSpace + UT.borderWidth;\r\n\r\n            me.body.removeChild( tab );\r\n\r\n        }\r\n\r\n        getTabcellSpace = function(){ return UT.tabcellSpace; };\r\n\r\n        return UT.tabcellSpace;\r\n\r\n    }\r\n\r\n    function getDragLine(editor, doc) {\r\n        if (mousedown)return;\r\n        dragLine = editor.document.createElement(\"div\");\r\n        domUtils.setAttributes(dragLine, {\r\n            id:\"ue_tableDragLine\",\r\n            unselectable:'on',\r\n            contenteditable:false,\r\n            'onresizestart':'return false',\r\n            'ondragstart':'return false',\r\n            'onselectstart':'return false',\r\n            style:\"background-color:blue;position:absolute;padding:0;margin:0;background-image:none;border:0px none;opacity:0;filter:alpha(opacity=0)\"\r\n        });\r\n        editor.body.appendChild(dragLine);\r\n    }\r\n\r\n    function hideDragLine(editor) {\r\n        if (mousedown)return;\r\n        var line;\r\n        while (line = editor.document.getElementById('ue_tableDragLine')) {\r\n            domUtils.remove(line)\r\n        }\r\n    }\r\n\r\n    /**\r\n     * 依据state（v|h）在cell位置显示横线\r\n     * @param state\r\n     * @param cell\r\n     */\r\n    function showDragLineAt(state, cell) {\r\n        if (!cell) return;\r\n        var table = domUtils.findParentByTagName(cell, \"table\"),\r\n            caption = table.getElementsByTagName('caption'),\r\n            width = table.offsetWidth,\r\n            height = table.offsetHeight - (caption.length > 0 ? caption[0].offsetHeight : 0),\r\n            tablePos = domUtils.getXY(table),\r\n            cellPos = domUtils.getXY(cell), css;\r\n        switch (state) {\r\n            case \"h\":\r\n                css = 'height:' + height + 'px;top:' + (tablePos.y + (caption.length > 0 ? caption[0].offsetHeight : 0)) + 'px;left:' + (cellPos.x + cell.offsetWidth);\r\n                dragLine.style.cssText = css + 'px;position: absolute;display:block;background-color:blue;width:1px;border:0; color:blue;opacity:.3;filter:alpha(opacity=30)';\r\n                break;\r\n            case \"v\":\r\n                css = 'width:' + width + 'px;left:' + tablePos.x + 'px;top:' + (cellPos.y + cell.offsetHeight );\r\n                //必须加上border:0和color:blue，否则低版ie不支持背景色显示\r\n                dragLine.style.cssText = css + 'px;overflow:hidden;position: absolute;display:block;background-color:blue;height:1px;border:0;color:blue;opacity:.2;filter:alpha(opacity=20)';\r\n                break;\r\n            default:\r\n        }\r\n    }\r\n\r\n    /**\r\n     * 当表格边框颜色为白色时设置为虚线,true为添加虚线\r\n     * @param editor\r\n     * @param flag\r\n     */\r\n    function switchBorderColor(editor, flag) {\r\n        var tableArr = domUtils.getElementsByTagName(editor.body, \"table\"), color;\r\n        for (var i = 0, node; node = tableArr[i++];) {\r\n            var td = domUtils.getElementsByTagName(node, \"td\");\r\n            if (td[0]) {\r\n                if (flag) {\r\n                    color = (td[0].style.borderColor).replace(/\\s/g, \"\");\r\n                    if (/(#ffffff)|(rgb\\(255,255,255\\))/ig.test(color))\r\n                        domUtils.addClass(node, \"noBorderTable\")\r\n                } else {\r\n                    domUtils.removeClasses(node, \"noBorderTable\")\r\n                }\r\n            }\r\n\r\n        }\r\n    }\r\n\r\n    function getTableWidth(editor, needIEHack, defaultValue) {\r\n        var body = editor.body;\r\n        return body.offsetWidth - (needIEHack ? parseInt(domUtils.getComputedStyle(body, 'margin-left'), 10) * 2 : 0) - defaultValue.tableBorder * 2 - (editor.options.offsetWidth || 0);\r\n    }\r\n\r\n    /**\r\n     * 获取当前拖动的单元格\r\n     */\r\n    function getTargetTd(editor, evt) {\r\n\r\n        var target = domUtils.findParentByTagName(evt.target || evt.srcElement, [\"td\", \"th\"], true),\r\n            dir = null;\r\n\r\n        if( !target ) {\r\n            return null;\r\n        }\r\n\r\n        dir = getRelation( target, mouseCoords( evt ) );\r\n\r\n        //如果有前一个节点， 需要做一个修正， 否则可能会得到一个错误的td\r\n\r\n        if( !target ) {\r\n            return null;\r\n        }\r\n\r\n        if( dir === 'h1' && target.previousSibling ) {\r\n\r\n            var position = domUtils.getXY( target),\r\n                cellWidth = target.offsetWidth;\r\n\r\n            if( Math.abs( position.x + cellWidth - evt.clientX ) > cellWidth / 3 ) {\r\n                target = target.previousSibling;\r\n            }\r\n\r\n        } else if( dir === 'v1' && target.parentNode.previousSibling ) {\r\n\r\n            var position = domUtils.getXY( target),\r\n                cellHeight = target.offsetHeight;\r\n\r\n            if( Math.abs( position.y + cellHeight - evt.clientY ) > cellHeight / 3 ) {\r\n                target = target.parentNode.previousSibling.firstChild;\r\n            }\r\n\r\n        }\r\n\r\n\r\n        //排除了非td内部以及用于代码高亮部分的td\r\n        return target && !(editor.fireEvent(\"excludetable\", target) === true) ? target : null;\r\n    }\r\n\r\n};\r\n\r\n\r\n// plugins/table.sort.js\r\n/**\r\n * Created with JetBrains PhpStorm.\r\n * User: Jinqn\r\n * Date: 13-10-12\r\n * Time: 上午10:20\r\n * To change this template use File | Settings | File Templates.\r\n */\r\n\r\nUE.UETable.prototype.sortTable = function (sortByCellIndex, compareFn) {\r\n    var table = this.table,\r\n        rows = table.rows,\r\n        trArray = [],\r\n        flag = rows[0].cells[0].tagName === \"TH\",\r\n        lastRowIndex = 0;\r\n    if(this.selectedTds.length){\r\n        var range = this.cellsRange,\r\n            len = range.endRowIndex + 1;\r\n        for (var i = range.beginRowIndex; i < len; i++) {\r\n            trArray[i] = rows[i];\r\n        }\r\n        trArray.splice(0,range.beginRowIndex);\r\n        lastRowIndex = (range.endRowIndex +1) === this.rowsNum ? 0 : range.endRowIndex +1;\r\n    }else{\r\n        for (var i = 0,len = rows.length; i < len; i++) {\r\n            trArray[i] = rows[i];\r\n        }\r\n    }\r\n\r\n    var Fn = {\r\n        'reversecurrent': function(td1,td2){\r\n            return 1;\r\n        },\r\n        'orderbyasc': function(td1,td2){\r\n            var value1 = td1.innerText||td1.textContent,\r\n                value2 = td2.innerText||td2.textContent;\r\n            return value1.localeCompare(value2);\r\n        },\r\n        'reversebyasc': function(td1,td2){\r\n            var value1 = td1.innerHTML,\r\n                value2 = td2.innerHTML;\r\n            return value2.localeCompare(value1);\r\n        },\r\n        'orderbynum': function(td1,td2){\r\n            var value1 = td1[browser.ie ? 'innerText':'textContent'].match(/\\d+/),\r\n                value2 = td2[browser.ie ? 'innerText':'textContent'].match(/\\d+/);\r\n            if(value1) value1 = +value1[0];\r\n            if(value2) value2 = +value2[0];\r\n            return (value1||0) - (value2||0);\r\n        },\r\n        'reversebynum': function(td1,td2){\r\n            var value1 = td1[browser.ie ? 'innerText':'textContent'].match(/\\d+/),\r\n                value2 = td2[browser.ie ? 'innerText':'textContent'].match(/\\d+/);\r\n            if(value1) value1 = +value1[0];\r\n            if(value2) value2 = +value2[0];\r\n            return (value2||0) - (value1||0);\r\n        }\r\n    };\r\n\r\n    //对表格设置排序的标记data-sort-type\r\n    table.setAttribute('data-sort-type', compareFn && typeof compareFn === \"string\" && Fn[compareFn] ? compareFn:'');\r\n\r\n    //th不参与排序\r\n    flag && trArray.splice(0, 1);\r\n    trArray = utils.sort(trArray,function (tr1, tr2) {\r\n        var result;\r\n        if (compareFn && typeof compareFn === \"function\") {\r\n            result = compareFn.call(this, tr1.cells[sortByCellIndex], tr2.cells[sortByCellIndex]);\r\n        } else if (compareFn && typeof compareFn === \"number\") {\r\n            result = 1;\r\n        } else if (compareFn && typeof compareFn === \"string\" && Fn[compareFn]) {\r\n            result = Fn[compareFn].call(this, tr1.cells[sortByCellIndex], tr2.cells[sortByCellIndex]);\r\n        } else {\r\n            result = Fn['orderbyasc'].call(this, tr1.cells[sortByCellIndex], tr2.cells[sortByCellIndex]);\r\n        }\r\n        return result;\r\n    });\r\n    var fragment = table.ownerDocument.createDocumentFragment();\r\n    for (var j = 0, len = trArray.length; j < len; j++) {\r\n        fragment.appendChild(trArray[j]);\r\n    }\r\n    var tbody = table.getElementsByTagName(\"tbody\")[0];\r\n    if(!lastRowIndex){\r\n        tbody.appendChild(fragment);\r\n    }else{\r\n        tbody.insertBefore(fragment,rows[lastRowIndex- range.endRowIndex + range.beginRowIndex - 1])\r\n    }\r\n};\r\n\r\nUE.plugins['tablesort'] = function () {\r\n    var me = this,\r\n        UT = UE.UETable,\r\n        getUETable = function (tdOrTable) {\r\n            return UT.getUETable(tdOrTable);\r\n        },\r\n        getTableItemsByRange = function (editor) {\r\n            return UT.getTableItemsByRange(editor);\r\n        };\r\n\r\n\r\n    me.ready(function () {\r\n        //添加表格可排序的样式\r\n        utils.cssRule('tablesort',\r\n            'table.sortEnabled tr.firstRow th,table.sortEnabled tr.firstRow td{padding-right:20px;background-repeat: no-repeat;background-position: center right;' +\r\n                '   background-image:url(' + me.options.themePath + me.options.theme + '/images/sortable.png);}',\r\n            me.document);\r\n\r\n        //做单元格合并操作时,清除可排序标识\r\n        me.addListener(\"afterexeccommand\", function (type, cmd) {\r\n            if( cmd == 'mergeright' || cmd == 'mergedown' || cmd == 'mergecells') {\r\n                this.execCommand('disablesort');\r\n            }\r\n        });\r\n    });\r\n\r\n\r\n\r\n    //表格排序\r\n    UE.commands['sorttable'] = {\r\n        queryCommandState: function () {\r\n            var me = this,\r\n                tableItems = getTableItemsByRange(me);\r\n            if (!tableItems.cell) return -1;\r\n            var table = tableItems.table,\r\n                cells = table.getElementsByTagName(\"td\");\r\n            for (var i = 0, cell; cell = cells[i++];) {\r\n                if (cell.rowSpan != 1 || cell.colSpan != 1) return -1;\r\n            }\r\n            return 0;\r\n        },\r\n        execCommand: function (cmd, fn) {\r\n            var me = this,\r\n                range = me.selection.getRange(),\r\n                bk = range.createBookmark(true),\r\n                tableItems = getTableItemsByRange(me),\r\n                cell = tableItems.cell,\r\n                ut = getUETable(tableItems.table),\r\n                cellInfo = ut.getCellInfo(cell);\r\n            ut.sortTable(cellInfo.cellIndex, fn);\r\n            range.moveToBookmark(bk);\r\n            try{\r\n                range.select();\r\n            }catch(e){}\r\n        }\r\n    };\r\n\r\n    //设置表格可排序,清除表格可排序\r\n    UE.commands[\"enablesort\"] = UE.commands[\"disablesort\"] = {\r\n        queryCommandState: function (cmd) {\r\n            var table = getTableItemsByRange(this).table;\r\n            if(table && cmd=='enablesort') {\r\n                var cells = domUtils.getElementsByTagName(table, 'th td');\r\n                for(var i = 0; i<cells.length; i++) {\r\n                    if(cells[i].getAttribute('colspan')>1 || cells[i].getAttribute('rowspan')>1) return -1;\r\n                }\r\n            }\r\n\r\n            return !table ? -1: cmd=='enablesort' ^ table.getAttribute('data-sort')!='sortEnabled' ? -1:0;\r\n        },\r\n        execCommand: function (cmd) {\r\n            var table = getTableItemsByRange(this).table;\r\n            table.setAttribute(\"data-sort\", cmd == \"enablesort\" ? \"sortEnabled\" : \"sortDisabled\");\r\n            cmd == \"enablesort\" ? domUtils.addClass(table,\"sortEnabled\"):domUtils.removeClasses(table,\"sortEnabled\");\r\n        }\r\n    };\r\n};\r\n\r\n\r\n// plugins/contextmenu.js\r\n///import core\r\n///commands 右键菜单\r\n///commandsName  ContextMenu\r\n///commandsTitle  右键菜单\r\n/**\r\n * 右键菜单\r\n * @function\r\n * @name baidu.editor.plugins.contextmenu\r\n * @author zhanyi\r\n */\r\n\r\nUE.plugins['contextmenu'] = function () {\r\n    var me = this;\r\n    me.setOpt('enableContextMenu',true);\r\n    if(me.getOpt('enableContextMenu') === false){\r\n        return;\r\n    }\r\n    var lang = me.getLang( \"contextMenu\" ),\r\n            menu,\r\n            items = me.options.contextMenu || [\r\n                {label:lang['selectall'], cmdName:'selectall'},\r\n                {\r\n                    label:lang.cleardoc,\r\n                    cmdName:'cleardoc',\r\n                    exec:function () {\r\n                        if ( confirm( lang.confirmclear ) ) {\r\n                            this.execCommand( 'cleardoc' );\r\n                        }\r\n                    }\r\n                },\r\n                '-',\r\n                {\r\n                    label:lang.unlink,\r\n                    cmdName:'unlink'\r\n                },\r\n                '-',\r\n                {\r\n                    group:lang.paragraph,\r\n                    icon:'justifyjustify',\r\n                    subMenu:[\r\n                        {\r\n                            label:lang.justifyleft,\r\n                            cmdName:'justify',\r\n                            value:'left'\r\n                        },\r\n                        {\r\n                            label:lang.justifyright,\r\n                            cmdName:'justify',\r\n                            value:'right'\r\n                        },\r\n                        {\r\n                            label:lang.justifycenter,\r\n                            cmdName:'justify',\r\n                            value:'center'\r\n                        },\r\n                        {\r\n                            label:lang.justifyjustify,\r\n                            cmdName:'justify',\r\n                            value:'justify'\r\n                        }\r\n                    ]\r\n                },\r\n                '-',\r\n                {\r\n                    group:lang.table,\r\n                    icon:'table',\r\n                    subMenu:[\r\n                        {\r\n                            label:lang.inserttable,\r\n                            cmdName:'inserttable'\r\n                        },\r\n                        {\r\n                            label:lang.deletetable,\r\n                            cmdName:'deletetable'\r\n                        },\r\n                        '-',\r\n                        {\r\n                            label:lang.deleterow,\r\n                            cmdName:'deleterow'\r\n                        },\r\n                        {\r\n                            label:lang.deletecol,\r\n                            cmdName:'deletecol'\r\n                        },\r\n                        {\r\n                            label:lang.insertcol,\r\n                            cmdName:'insertcol'\r\n                        },\r\n                        {\r\n                            label:lang.insertcolnext,\r\n                            cmdName:'insertcolnext'\r\n                        },\r\n                        {\r\n                            label:lang.insertrow,\r\n                            cmdName:'insertrow'\r\n                        },\r\n                        {\r\n                            label:lang.insertrownext,\r\n                            cmdName:'insertrownext'\r\n                        },\r\n                        '-',\r\n                        {\r\n                            label:lang.insertcaption,\r\n                            cmdName:'insertcaption'\r\n                        },\r\n                        {\r\n                            label:lang.deletecaption,\r\n                            cmdName:'deletecaption'\r\n                        },\r\n                        {\r\n                            label:lang.inserttitle,\r\n                            cmdName:'inserttitle'\r\n                        },\r\n                        {\r\n                            label:lang.deletetitle,\r\n                            cmdName:'deletetitle'\r\n                        },\r\n                        {\r\n                            label:lang.inserttitlecol,\r\n                            cmdName:'inserttitlecol'\r\n                        },\r\n                        {\r\n                            label:lang.deletetitlecol,\r\n                            cmdName:'deletetitlecol'\r\n                        },\r\n                        '-',\r\n                        {\r\n                            label:lang.mergecells,\r\n                            cmdName:'mergecells'\r\n                        },\r\n                        {\r\n                            label:lang.mergeright,\r\n                            cmdName:'mergeright'\r\n                        },\r\n                        {\r\n                            label:lang.mergedown,\r\n                            cmdName:'mergedown'\r\n                        },\r\n                        '-',\r\n                        {\r\n                            label:lang.splittorows,\r\n                            cmdName:'splittorows'\r\n                        },\r\n                        {\r\n                            label:lang.splittocols,\r\n                            cmdName:'splittocols'\r\n                        },\r\n                        {\r\n                            label:lang.splittocells,\r\n                            cmdName:'splittocells'\r\n                        },\r\n                        '-',\r\n                        {\r\n                            label:lang.averageDiseRow,\r\n                            cmdName:'averagedistributerow'\r\n                        },\r\n                        {\r\n                            label:lang.averageDisCol,\r\n                            cmdName:'averagedistributecol'\r\n                        },\r\n                        '-',\r\n                        {\r\n                            label:lang.edittd,\r\n                            cmdName:'edittd',\r\n                            exec:function () {\r\n                                if ( UE.ui['edittd'] ) {\r\n                                    new UE.ui['edittd']( this );\r\n                                }\r\n                                this.getDialog('edittd').open();\r\n                            }\r\n                        },\r\n                        {\r\n                            label:lang.edittable,\r\n                            cmdName:'edittable',\r\n                            exec:function () {\r\n                                if ( UE.ui['edittable'] ) {\r\n                                    new UE.ui['edittable']( this );\r\n                                }\r\n                                this.getDialog('edittable').open();\r\n                            }\r\n                        },\r\n                        {\r\n                            label:lang.setbordervisible,\r\n                            cmdName:'setbordervisible'\r\n                        }\r\n                    ]\r\n                },\r\n                {\r\n                    group:lang.tablesort,\r\n                    icon:'tablesort',\r\n                    subMenu:[\r\n                        {\r\n                            label:lang.enablesort,\r\n                            cmdName:'enablesort'\r\n                        },\r\n                        {\r\n                            label:lang.disablesort,\r\n                            cmdName:'disablesort'\r\n                        },\r\n                        '-',\r\n                        {\r\n                            label:lang.reversecurrent,\r\n                            cmdName:'sorttable',\r\n                            value:'reversecurrent'\r\n                        },\r\n                        {\r\n                            label:lang.orderbyasc,\r\n                            cmdName:'sorttable',\r\n                            value:'orderbyasc'\r\n                        },\r\n                        {\r\n                            label:lang.reversebyasc,\r\n                            cmdName:'sorttable',\r\n                            value:'reversebyasc'\r\n                        },\r\n                        {\r\n                            label:lang.orderbynum,\r\n                            cmdName:'sorttable',\r\n                            value:'orderbynum'\r\n                        },\r\n                        {\r\n                            label:lang.reversebynum,\r\n                            cmdName:'sorttable',\r\n                            value:'reversebynum'\r\n                        }\r\n                    ]\r\n                },\r\n                {\r\n                    group:lang.borderbk,\r\n                    icon:'borderBack',\r\n                    subMenu:[\r\n                        {\r\n                            label:lang.setcolor,\r\n                            cmdName:\"interlacetable\",\r\n                            exec:function(){\r\n                                this.execCommand(\"interlacetable\");\r\n                            }\r\n                        },\r\n                        {\r\n                            label:lang.unsetcolor,\r\n                            cmdName:\"uninterlacetable\",\r\n                            exec:function(){\r\n                                this.execCommand(\"uninterlacetable\");\r\n                            }\r\n                        },\r\n                        {\r\n                            label:lang.setbackground,\r\n                            cmdName:\"settablebackground\",\r\n                            exec:function(){\r\n                                this.execCommand(\"settablebackground\",{repeat:true,colorList:[\"#bbb\",\"#ccc\"]});\r\n                            }\r\n                        },\r\n                        {\r\n                            label:lang.unsetbackground,\r\n                            cmdName:\"cleartablebackground\",\r\n                            exec:function(){\r\n                                this.execCommand(\"cleartablebackground\");\r\n                            }\r\n                        },\r\n                        {\r\n                            label:lang.redandblue,\r\n                            cmdName:\"settablebackground\",\r\n                            exec:function(){\r\n                                this.execCommand(\"settablebackground\",{repeat:true,colorList:[\"red\",\"blue\"]});\r\n                            }\r\n                        },\r\n                        {\r\n                            label:lang.threecolorgradient,\r\n                            cmdName:\"settablebackground\",\r\n                            exec:function(){\r\n                                this.execCommand(\"settablebackground\",{repeat:true,colorList:[\"#aaa\",\"#bbb\",\"#ccc\"]});\r\n                            }\r\n                        }\r\n                    ]\r\n                },\r\n                {\r\n                    group:lang.aligntd,\r\n                    icon:'aligntd',\r\n                    subMenu:[\r\n                        {\r\n                            cmdName:'cellalignment',\r\n                            value:{align:'left',vAlign:'top'}\r\n                        },\r\n                        {\r\n                            cmdName:'cellalignment',\r\n                            value:{align:'center',vAlign:'top'}\r\n                        },\r\n                        {\r\n                            cmdName:'cellalignment',\r\n                            value:{align:'right',vAlign:'top'}\r\n                        },\r\n                        {\r\n                            cmdName:'cellalignment',\r\n                            value:{align:'left',vAlign:'middle'}\r\n                        },\r\n                        {\r\n                            cmdName:'cellalignment',\r\n                            value:{align:'center',vAlign:'middle'}\r\n                        },\r\n                        {\r\n                            cmdName:'cellalignment',\r\n                            value:{align:'right',vAlign:'middle'}\r\n                        },\r\n                        {\r\n                            cmdName:'cellalignment',\r\n                            value:{align:'left',vAlign:'bottom'}\r\n                        },\r\n                        {\r\n                            cmdName:'cellalignment',\r\n                            value:{align:'center',vAlign:'bottom'}\r\n                        },\r\n                        {\r\n                            cmdName:'cellalignment',\r\n                            value:{align:'right',vAlign:'bottom'}\r\n                        }\r\n                    ]\r\n                },\r\n                {\r\n                    group:lang.aligntable,\r\n                    icon:'aligntable',\r\n                    subMenu:[\r\n                        {\r\n                            cmdName:'tablealignment',\r\n                            className: 'left',\r\n                            label:lang.tableleft,\r\n                            value:\"left\"\r\n                        },\r\n                        {\r\n                            cmdName:'tablealignment',\r\n                            className: 'center',\r\n                            label:lang.tablecenter,\r\n                            value:\"center\"\r\n                        },\r\n                        {\r\n                            cmdName:'tablealignment',\r\n                            className: 'right',\r\n                            label:lang.tableright,\r\n                            value:\"right\"\r\n                        }\r\n                    ]\r\n                },\r\n                '-',\r\n                {\r\n                    label:lang.insertparagraphbefore,\r\n                    cmdName:'insertparagraph',\r\n                    value:true\r\n                },\r\n                {\r\n                    label:lang.insertparagraphafter,\r\n                    cmdName:'insertparagraph'\r\n                },\r\n                {\r\n                    label:lang['copy'],\r\n                    cmdName:'copy'\r\n                },\r\n                {\r\n                    label:lang['paste'],\r\n                    cmdName:'paste'\r\n                }\r\n            ];\r\n    if ( !items.length ) {\r\n        return;\r\n    }\r\n    var uiUtils = UE.ui.uiUtils;\r\n\r\n    me.addListener( 'contextmenu', function ( type, evt ) {\r\n\r\n        var offset = uiUtils.getViewportOffsetByEvent( evt );\r\n        me.fireEvent( 'beforeselectionchange' );\r\n        if ( menu ) {\r\n            menu.destroy();\r\n        }\r\n        for ( var i = 0, ti, contextItems = []; ti = items[i]; i++ ) {\r\n            var last;\r\n            (function ( item ) {\r\n                if ( item == '-' ) {\r\n                    if ( (last = contextItems[contextItems.length - 1 ] ) && last !== '-' ) {\r\n                        contextItems.push( '-' );\r\n                    }\r\n                } else if ( item.hasOwnProperty( \"group\" ) ) {\r\n                    for ( var j = 0, cj, subMenu = []; cj = item.subMenu[j]; j++ ) {\r\n                        (function ( subItem ) {\r\n                            if ( subItem == '-' ) {\r\n                                if ( (last = subMenu[subMenu.length - 1 ] ) && last !== '-' ) {\r\n                                    subMenu.push( '-' );\r\n                                }else{\r\n                                    subMenu.splice(subMenu.length-1);\r\n                                }\r\n                            } else {\r\n                                if ( (me.commands[subItem.cmdName] || UE.commands[subItem.cmdName] || subItem.query) &&\r\n                                        (subItem.query ? subItem.query() : me.queryCommandState( subItem.cmdName )) > -1 ) {\r\n                                    subMenu.push( {\r\n                                        'label':subItem.label || me.getLang( \"contextMenu.\" + subItem.cmdName + (subItem.value || '') )||\"\",\r\n                                        'className':'edui-for-' +subItem.cmdName + ( subItem.className ? ( ' edui-for-' + subItem.cmdName + '-' + subItem.className ) : '' ),\r\n                                        onclick:subItem.exec ? function () {\r\n                                                subItem.exec.call( me );\r\n                                        } : function () {\r\n                                            me.execCommand( subItem.cmdName, subItem.value );\r\n                                        }\r\n                                    } );\r\n                                }\r\n                            }\r\n                        })( cj );\r\n                    }\r\n                    if ( subMenu.length ) {\r\n                        function getLabel(){\r\n                            switch (item.icon){\r\n                                case \"table\":\r\n                                    return me.getLang( \"contextMenu.table\" );\r\n                                case \"justifyjustify\":\r\n                                    return me.getLang( \"contextMenu.paragraph\" );\r\n                                case \"aligntd\":\r\n                                    return me.getLang(\"contextMenu.aligntd\");\r\n                                case \"aligntable\":\r\n                                    return me.getLang(\"contextMenu.aligntable\");\r\n                                case \"tablesort\":\r\n                                    return lang.tablesort;\r\n                                case \"borderBack\":\r\n                                    return lang.borderbk;\r\n                                default :\r\n                                    return '';\r\n                            }\r\n                        }\r\n                        contextItems.push( {\r\n                            //todo 修正成自动获取方式\r\n                            'label':getLabel(),\r\n                            className:'edui-for-' + item.icon,\r\n                            'subMenu':{\r\n                                items:subMenu,\r\n                                editor:me\r\n                            }\r\n                        } );\r\n                    }\r\n\r\n                } else {\r\n                    //有可能commmand没有加载右键不能出来，或者没有command也想能展示出来添加query方法\r\n                    if ( (me.commands[item.cmdName] || UE.commands[item.cmdName] || item.query) &&\r\n                            (item.query ? item.query.call(me) : me.queryCommandState( item.cmdName )) > -1 ) {\r\n\r\n                        contextItems.push( {\r\n                            'label':item.label || me.getLang( \"contextMenu.\" + item.cmdName ),\r\n                            className:'edui-for-' + (item.icon ? item.icon : item.cmdName + (item.value || '')),\r\n                            onclick:item.exec ? function () {\r\n                                item.exec.call( me );\r\n                            } : function () {\r\n                                me.execCommand( item.cmdName, item.value );\r\n                            }\r\n                        } );\r\n                    }\r\n\r\n                }\r\n\r\n            })( ti );\r\n        }\r\n        if ( contextItems[contextItems.length - 1] == '-' ) {\r\n            contextItems.pop();\r\n        }\r\n\r\n        menu = new UE.ui.Menu( {\r\n            items:contextItems,\r\n            className:\"edui-contextmenu\",\r\n            editor:me\r\n        } );\r\n        menu.render();\r\n        menu.showAt( offset );\r\n\r\n        me.fireEvent(\"aftershowcontextmenu\",menu);\r\n\r\n        domUtils.preventDefault( evt );\r\n        if ( browser.ie ) {\r\n            var ieRange;\r\n            try {\r\n                ieRange = me.selection.getNative().createRange();\r\n            } catch ( e ) {\r\n                return;\r\n            }\r\n            if ( ieRange.item ) {\r\n                var range = new dom.Range( me.document );\r\n                range.selectNode( ieRange.item( 0 ) ).select( true, true );\r\n            }\r\n        }\r\n    });\r\n\r\n    // 添加复制的flash按钮\r\n    me.addListener('aftershowcontextmenu', function(type, menu) {\r\n        if (me.zeroclipboard) {\r\n            var items = menu.items;\r\n            for (var key in items) {\r\n                if (items[key].className == 'edui-for-copy') {\r\n                    me.zeroclipboard.clip(items[key].getDom());\r\n                }\r\n            }\r\n        }\r\n    });\r\n\r\n};\r\n\r\n\r\n// plugins/shortcutmenu.js\r\n///import core\r\n///commands       弹出菜单\r\n// commandsName  popupmenu\r\n///commandsTitle  弹出菜单\r\n/**\r\n * 弹出菜单\r\n * @function\r\n * @name baidu.editor.plugins.popupmenu\r\n * @author xuheng\r\n */\r\n\r\nUE.plugins['shortcutmenu'] = function () {\r\n    var me = this,\r\n        menu,\r\n        items = me.options.shortcutMenu || [];\r\n\r\n    if (!items.length) {\r\n        return;\r\n    }\r\n\r\n    me.addListener ('contextmenu mouseup' , function (type , e) {\r\n        var me = this,\r\n            customEvt = {\r\n                type : type ,\r\n                target : e.target || e.srcElement ,\r\n                screenX : e.screenX ,\r\n                screenY : e.screenY ,\r\n                clientX : e.clientX ,\r\n                clientY : e.clientY\r\n            };\r\n\r\n        setTimeout (function () {\r\n            var rng = me.selection.getRange ();\r\n            if (rng.collapsed === false || type == \"contextmenu\") {\r\n\r\n                if (!menu) {\r\n                    menu = new baidu.editor.ui.ShortCutMenu ({\r\n                        editor : me ,\r\n                        items : items ,\r\n                        theme : me.options.theme ,\r\n                        className : 'edui-shortcutmenu'\r\n                    });\r\n\r\n                    menu.render ();\r\n                    me.fireEvent (\"afterrendershortcutmenu\" , menu);\r\n                }\r\n\r\n                menu.show (customEvt , !!UE.plugins['contextmenu']);\r\n            }\r\n        });\r\n\r\n        if (type == 'contextmenu') {\r\n            domUtils.preventDefault (e);\r\n            if (browser.ie9below) {\r\n                var ieRange;\r\n                try {\r\n                    ieRange = me.selection.getNative().createRange();\r\n                } catch (e) {\r\n                    return;\r\n                }\r\n                if (ieRange.item) {\r\n                    var range = new dom.Range (me.document);\r\n                    range.selectNode (ieRange.item (0)).select (true , true);\r\n\r\n                }\r\n            }\r\n        }\r\n    });\r\n\r\n    me.addListener ('keydown' , function (type) {\r\n        if (type == \"keydown\") {\r\n            menu && !menu.isHidden && menu.hide ();\r\n        }\r\n\r\n    });\r\n\r\n};\r\n\r\n\r\n\r\n\r\n// plugins/basestyle.js\r\n/**\r\n * B、I、sub、super命令支持\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\nUE.plugins['basestyle'] = function(){\r\n\r\n    /**\r\n     * 字体加粗\r\n     * @command bold\r\n     * @param { String } cmd 命令字符串\r\n     * @remind 对已加粗的文本内容执行该命令， 将取消加粗\r\n     * @method execCommand\r\n     * @example\r\n     * ```javascript\r\n     * //editor是编辑器实例\r\n     * //对当前选中的文本内容执行加粗操作\r\n     * //第一次执行， 文本内容加粗\r\n     * editor.execCommand( 'bold' );\r\n     *\r\n     * //第二次执行， 文本内容取消加粗\r\n     * editor.execCommand( 'bold' );\r\n     * ```\r\n     */\r\n\r\n\r\n    /**\r\n     * 字体倾斜\r\n     * @command italic\r\n     * @method execCommand\r\n     * @param { String } cmd 命令字符串\r\n     * @remind 对已倾斜的文本内容执行该命令， 将取消倾斜\r\n     * @example\r\n     * ```javascript\r\n     * //editor是编辑器实例\r\n     * //对当前选中的文本内容执行斜体操作\r\n     * //第一次操作， 文本内容将变成斜体\r\n     * editor.execCommand( 'italic' );\r\n     *\r\n     * //再次对同一文本内容执行， 则文本内容将恢复正常\r\n     * editor.execCommand( 'italic' );\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 下标文本，与“superscript”命令互斥\r\n     * @command subscript\r\n     * @method execCommand\r\n     * @remind  把选中的文本内容切换成下标文本， 如果当前选中的文本已经是下标， 则该操作会把文本内容还原成正常文本\r\n     * @param { String } cmd 命令字符串\r\n     * @example\r\n     * ```javascript\r\n     * //editor是编辑器实例\r\n     * //对当前选中的文本内容执行下标操作\r\n     * //第一次操作， 文本内容将变成下标文本\r\n     * editor.execCommand( 'subscript' );\r\n     *\r\n     * //再次对同一文本内容执行， 则文本内容将恢复正常\r\n     * editor.execCommand( 'subscript' );\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 上标文本，与“subscript”命令互斥\r\n     * @command superscript\r\n     * @method execCommand\r\n     * @remind 把选中的文本内容切换成上标文本， 如果当前选中的文本已经是上标， 则该操作会把文本内容还原成正常文本\r\n     * @param { String } cmd 命令字符串\r\n     * @example\r\n     * ```javascript\r\n     * //editor是编辑器实例\r\n     * //对当前选中的文本内容执行上标操作\r\n     * //第一次操作， 文本内容将变成上标文本\r\n     * editor.execCommand( 'superscript' );\r\n     *\r\n     * //再次对同一文本内容执行， 则文本内容将恢复正常\r\n     * editor.execCommand( 'superscript' );\r\n     * ```\r\n     */\r\n    var basestyles = {\r\n            'bold':['strong','b'],\r\n            'italic':['em','i'],\r\n            'subscript':['sub'],\r\n            'superscript':['sup']\r\n        },\r\n        getObj = function(editor,tagNames){\r\n            return domUtils.filterNodeList(editor.selection.getStartElementPath(),tagNames);\r\n        },\r\n        me = this;\r\n    //添加快捷键\r\n    me.addshortcutkey({\r\n        \"Bold\" : \"ctrl+66\",//^B\r\n        \"Italic\" : \"ctrl+73\", //^I\r\n        \"Underline\" : \"ctrl+85\"//^U\r\n    });\r\n    me.addInputRule(function(root){\r\n        utils.each(root.getNodesByTagName('b i'),function(node){\r\n            switch (node.tagName){\r\n                case 'b':\r\n                    node.tagName = 'strong';\r\n                    break;\r\n                case 'i':\r\n                    node.tagName = 'em';\r\n            }\r\n        });\r\n    });\r\n    for ( var style in basestyles ) {\r\n        (function( cmd, tagNames ) {\r\n            me.commands[cmd] = {\r\n                execCommand : function( cmdName ) {\r\n                    var range = me.selection.getRange(),obj = getObj(this,tagNames);\r\n                    if ( range.collapsed ) {\r\n                        if ( obj ) {\r\n                            var tmpText =  me.document.createTextNode('');\r\n                            range.insertNode( tmpText ).removeInlineStyle( tagNames );\r\n                            range.setStartBefore(tmpText);\r\n                            domUtils.remove(tmpText);\r\n                        } else {\r\n                            var tmpNode = range.document.createElement( tagNames[0] );\r\n                            if(cmdName == 'superscript' || cmdName == 'subscript'){\r\n                                tmpText = me.document.createTextNode('');\r\n                                range.insertNode(tmpText)\r\n                                    .removeInlineStyle(['sub','sup'])\r\n                                    .setStartBefore(tmpText)\r\n                                    .collapse(true);\r\n                            }\r\n                            range.insertNode( tmpNode ).setStart( tmpNode, 0 );\r\n                        }\r\n                        range.collapse( true );\r\n                    } else {\r\n                        if(cmdName == 'superscript' || cmdName == 'subscript'){\r\n                            if(!obj || obj.tagName.toLowerCase() != cmdName){\r\n                                range.removeInlineStyle(['sub','sup']);\r\n                            }\r\n                        }\r\n                        obj ? range.removeInlineStyle( tagNames ) : range.applyInlineStyle( tagNames[0] );\r\n                    }\r\n                    range.select();\r\n                },\r\n                queryCommandState : function() {\r\n                   return getObj(this,tagNames) ? 1 : 0;\r\n                }\r\n            };\r\n        })( style, basestyles[style] );\r\n    }\r\n};\r\n\r\n\r\n\r\n// plugins/elementpath.js\r\n/**\r\n * 选取路径命令\r\n * @file\r\n */\r\nUE.plugins['elementpath'] = function(){\r\n    var currentLevel,\r\n        tagNames,\r\n        me = this;\r\n    me.setOpt('elementPathEnabled',true);\r\n    if(!me.options.elementPathEnabled){\r\n        return;\r\n    }\r\n    me.commands['elementpath'] = {\r\n        execCommand : function( cmdName, level ) {\r\n            var start = tagNames[level],\r\n                range = me.selection.getRange();\r\n            currentLevel = level*1;\r\n            range.selectNode(start).select();\r\n        },\r\n        queryCommandValue : function() {\r\n            //产生一个副本，不能修改原来的startElementPath;\r\n            var parents = [].concat(this.selection.getStartElementPath()).reverse(),\r\n                names = [];\r\n            tagNames = parents;\r\n            for(var i=0,ci;ci=parents[i];i++){\r\n                if(ci.nodeType == 3) {\r\n                    continue;\r\n                }\r\n                var name = ci.tagName.toLowerCase();\r\n                if(name == 'img' && ci.getAttribute('anchorname')){\r\n                    name = 'anchor';\r\n                }\r\n                names[i] = name;\r\n                if(currentLevel == i){\r\n                   currentLevel = -1;\r\n                    break;\r\n                }\r\n            }\r\n            return names;\r\n        }\r\n    };\r\n};\r\n\r\n\r\n\r\n// plugins/formatmatch.js\r\n/**\r\n * 格式刷，只格式inline的\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 格式刷\r\n * @command formatmatch\r\n * @method execCommand\r\n * @remind 该操作不能复制段落格式\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * //editor是编辑器实例\r\n * //获取格式刷\r\n * editor.execCommand( 'formatmatch' );\r\n * ```\r\n */\r\nUE.plugins['formatmatch'] = function(){\r\n\r\n    var me = this,\r\n        list = [],img,\r\n        flag = 0;\r\n\r\n     me.addListener('reset',function(){\r\n         list = [];\r\n         flag = 0;\r\n     });\r\n\r\n    function addList(type,evt){\r\n        \r\n        if(browser.webkit){\r\n            var target = evt.target.tagName == 'IMG' ? evt.target : null;\r\n        }\r\n\r\n        function addFormat(range){\r\n\r\n            if(text){\r\n                range.selectNode(text);\r\n            }\r\n            return range.applyInlineStyle(list[list.length-1].tagName,null,list);\r\n\r\n        }\r\n\r\n        me.undoManger && me.undoManger.save();\r\n\r\n        var range = me.selection.getRange(),\r\n            imgT = target || range.getClosedNode();\r\n        if(img && imgT && imgT.tagName == 'IMG'){\r\n            //trace:964\r\n\r\n            imgT.style.cssText += ';float:' + (img.style.cssFloat || img.style.styleFloat ||'none') + ';display:' + (img.style.display||'inline');\r\n\r\n            img = null;\r\n        }else{\r\n            if(!img){\r\n                var collapsed = range.collapsed;\r\n                if(collapsed){\r\n                    var text = me.document.createTextNode('match');\r\n                    range.insertNode(text).select();\r\n\r\n\r\n                }\r\n                me.__hasEnterExecCommand = true;\r\n                //不能把block上的属性干掉\r\n                //trace:1553\r\n                var removeFormatAttributes = me.options.removeFormatAttributes;\r\n                me.options.removeFormatAttributes = '';\r\n                me.execCommand('removeformat');\r\n                me.options.removeFormatAttributes = removeFormatAttributes;\r\n                me.__hasEnterExecCommand = false;\r\n                //trace:969\r\n                range = me.selection.getRange();\r\n                if(list.length){\r\n                    addFormat(range);\r\n                }\r\n                if(text){\r\n                    range.setStartBefore(text).collapse(true);\r\n\r\n                }\r\n                range.select();\r\n                text && domUtils.remove(text);\r\n            }\r\n\r\n        }\r\n\r\n\r\n\r\n\r\n        me.undoManger && me.undoManger.save();\r\n        me.removeListener('mouseup',addList);\r\n        flag = 0;\r\n    }\r\n\r\n    me.commands['formatmatch'] = {\r\n        execCommand : function( cmdName ) {\r\n          \r\n            if(flag){\r\n                flag = 0;\r\n                list = [];\r\n                 me.removeListener('mouseup',addList);\r\n                return;\r\n            }\r\n\r\n\r\n              \r\n            var range = me.selection.getRange();\r\n            img = range.getClosedNode();\r\n            if(!img || img.tagName != 'IMG'){\r\n               range.collapse(true).shrinkBoundary();\r\n               var start = range.startContainer;\r\n               list = domUtils.findParents(start,true,function(node){\r\n                   return !domUtils.isBlockElm(node) && node.nodeType == 1;\r\n               });\r\n               //a不能加入格式刷, 并且克隆节点\r\n               for(var i=0,ci;ci=list[i];i++){\r\n                   if(ci.tagName == 'A'){\r\n                       list.splice(i,1);\r\n                       break;\r\n                   }\r\n               }\r\n\r\n            }\r\n\r\n            me.addListener('mouseup',addList);\r\n            flag = 1;\r\n\r\n\r\n        },\r\n        queryCommandState : function() {\r\n            return flag;\r\n        },\r\n        notNeedUndo : 1\r\n    };\r\n};\r\n\r\n\r\n\r\n// plugins/searchreplace.js\r\n///import core\r\n///commands 查找替换\r\n///commandsName  SearchReplace\r\n///commandsTitle  查询替换\r\n///commandsDialog  dialogs\\searchreplace\r\n/**\r\n * @description 查找替换\r\n * @author zhanyi\r\n */\r\n\r\nUE.plugin.register('searchreplace',function(){\r\n    var me = this;\r\n\r\n    var _blockElm = {'table':1,'tbody':1,'tr':1,'ol':1,'ul':1};\r\n\r\n    function findTextInString(textContent,opt,currentIndex){\r\n        var str = opt.searchStr;\r\n        if(opt.dir == -1){\r\n            textContent = textContent.split('').reverse().join('');\r\n            str = str.split('').reverse().join('');\r\n            currentIndex = textContent.length - currentIndex;\r\n\r\n        }\r\n        var reg = new RegExp(str,'g' + (opt.casesensitive ? '' : 'i')),match;\r\n\r\n        while(match = reg.exec(textContent)){\r\n            if(match.index >= currentIndex){\r\n                return opt.dir == -1 ? textContent.length - match.index - opt.searchStr.length : match.index;\r\n            }\r\n        }\r\n        return  -1\r\n    }\r\n    function findTextBlockElm(node,currentIndex,opt){\r\n        var textContent,index,methodName = opt.all || opt.dir == 1 ? 'getNextDomNode' : 'getPreDomNode';\r\n        if(domUtils.isBody(node)){\r\n            node = node.firstChild;\r\n        }\r\n        var first = 1;\r\n        while(node){\r\n            textContent = node.nodeType == 3 ? node.nodeValue : node[browser.ie ? 'innerText' : 'textContent'];\r\n            index = findTextInString(textContent,opt,currentIndex );\r\n            first = 0;\r\n            if(index!=-1){\r\n                return {\r\n                    'node':node,\r\n                    'index':index\r\n                }\r\n            }\r\n            node = domUtils[methodName](node);\r\n            while(node && _blockElm[node.nodeName.toLowerCase()]){\r\n                node = domUtils[methodName](node,true);\r\n            }\r\n            if(node){\r\n                currentIndex = opt.dir == -1 ? (node.nodeType == 3 ? node.nodeValue : node[browser.ie ? 'innerText' : 'textContent']).length : 0;\r\n            }\r\n\r\n        }\r\n    }\r\n    function findNTextInBlockElm(node,index,str){\r\n        var currentIndex = 0,\r\n            currentNode = node.firstChild,\r\n            currentNodeLength = 0,\r\n            result;\r\n        while(currentNode){\r\n            if(currentNode.nodeType == 3){\r\n                currentNodeLength = currentNode.nodeValue.replace(/(^[\\t\\r\\n]+)|([\\t\\r\\n]+$)/,'').length;\r\n                currentIndex += currentNodeLength;\r\n                if(currentIndex >= index){\r\n                    return {\r\n                        'node':currentNode,\r\n                        'index': currentNodeLength - (currentIndex - index)\r\n                    }\r\n                }\r\n            }else if(!dtd.$empty[currentNode.tagName]){\r\n                currentNodeLength = currentNode[browser.ie ? 'innerText' : 'textContent'].replace(/(^[\\t\\r\\n]+)|([\\t\\r\\n]+$)/,'').length\r\n                currentIndex += currentNodeLength;\r\n                if(currentIndex >= index){\r\n                    result = findNTextInBlockElm(currentNode,currentNodeLength - (currentIndex - index),str);\r\n                    if(result){\r\n                        return result;\r\n                    }\r\n                }\r\n            }\r\n            currentNode = domUtils.getNextDomNode(currentNode);\r\n\r\n        }\r\n    }\r\n\r\n    function searchReplace(me,opt){\r\n\r\n        var rng = me.selection.getRange(),\r\n            startBlockNode,\r\n            searchStr = opt.searchStr,\r\n            span = me.document.createElement('span');\r\n        span.innerHTML = '$$ueditor_searchreplace_key$$';\r\n\r\n        rng.shrinkBoundary(true);\r\n\r\n        //判断是不是第一次选中\r\n        if(!rng.collapsed){\r\n            rng.select();\r\n            var rngText = me.selection.getText();\r\n            if(new RegExp('^' + opt.searchStr + '$',(opt.casesensitive ? '' : 'i')).test(rngText)){\r\n                if(opt.replaceStr != undefined){\r\n                    replaceText(rng,opt.replaceStr);\r\n                    rng.select();\r\n                    return true;\r\n                }else{\r\n                    rng.collapse(opt.dir == -1)\r\n                }\r\n\r\n            }\r\n        }\r\n\r\n\r\n        rng.insertNode(span);\r\n        rng.enlargeToBlockElm(true);\r\n        startBlockNode = rng.startContainer;\r\n        var currentIndex = startBlockNode[browser.ie ? 'innerText' : 'textContent'].indexOf('$$ueditor_searchreplace_key$$');\r\n        rng.setStartBefore(span);\r\n        domUtils.remove(span);\r\n        var result = findTextBlockElm(startBlockNode,currentIndex,opt);\r\n        if(result){\r\n            var rngStart = findNTextInBlockElm(result.node,result.index,searchStr);\r\n            var rngEnd = findNTextInBlockElm(result.node,result.index + searchStr.length,searchStr);\r\n            rng.setStart(rngStart.node,rngStart.index).setEnd(rngEnd.node,rngEnd.index);\r\n\r\n            if(opt.replaceStr !== undefined){\r\n                replaceText(rng,opt.replaceStr)\r\n            }\r\n            rng.select();\r\n            return true;\r\n        }else{\r\n            rng.setCursor()\r\n        }\r\n\r\n    }\r\n    function replaceText(rng,str){\r\n\r\n        str = me.document.createTextNode(str);\r\n        rng.deleteContents().insertNode(str);\r\n\r\n    }\r\n    return {\r\n        commands:{\r\n            'searchreplace':{\r\n                execCommand:function(cmdName,opt){\r\n                    utils.extend(opt,{\r\n                        all : false,\r\n                        casesensitive : false,\r\n                        dir : 1\r\n                    },true);\r\n                    var num = 0;\r\n                    if(opt.all){\r\n\r\n                        var rng = me.selection.getRange(),\r\n                            first = me.body.firstChild;\r\n                        if(first && first.nodeType == 1){\r\n                            rng.setStart(first,0);\r\n                            rng.shrinkBoundary(true);\r\n                        }else if(first.nodeType == 3){\r\n                            rng.setStartBefore(first)\r\n                        }\r\n                        rng.collapse(true).select(true);\r\n                        if(opt.replaceStr !== undefined){\r\n                            me.fireEvent('saveScene');\r\n                        }\r\n                        while(searchReplace(this,opt)){\r\n                            num++;\r\n                        }\r\n                        if(num){\r\n                            me.fireEvent('saveScene');\r\n                        }\r\n                    }else{\r\n                        if(opt.replaceStr !== undefined){\r\n                            me.fireEvent('saveScene');\r\n                        }\r\n                        if(searchReplace(this,opt)){\r\n                            num++\r\n                        }\r\n                        if(num){\r\n                            me.fireEvent('saveScene');\r\n                        }\r\n\r\n                    }\r\n\r\n                    return num;\r\n                },\r\n                notNeedUndo:1\r\n            }\r\n        }\r\n    }\r\n});\r\n\r\n// plugins/customstyle.js\r\n/**\r\n * 自定义样式\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 根据config配置文件里“customstyle”选项的值对匹配的标签执行样式替换。\r\n * @command customstyle\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'customstyle' );\r\n * ```\r\n */\r\nUE.plugins['customstyle'] = function() {\r\n    var me = this;\r\n    me.setOpt({ 'customstyle':[\r\n        {tag:'h1',name:'tc', style:'font-size:32px;font-weight:bold;border-bottom:#ccc 2px solid;padding:0 4px 0 0;text-align:center;margin:0 0 20px 0;'},\r\n        {tag:'h1',name:'tl', style:'font-size:32px;font-weight:bold;border-bottom:#ccc 2px solid;padding:0 4px 0 0;text-align:left;margin:0 0 10px 0;'},\r\n        {tag:'span',name:'im', style:'font-size:16px;font-style:italic;font-weight:bold;line-height:18px;'},\r\n        {tag:'span',name:'hi', style:'font-size:16px;font-style:italic;font-weight:bold;color:rgb(51, 153, 204);line-height:18px;'}\r\n    ]});\r\n    me.commands['customstyle'] = {\r\n        execCommand : function(cmdName, obj) {\r\n            var me = this,\r\n                    tagName = obj.tag,\r\n                    node = domUtils.findParent(me.selection.getStart(), function(node) {\r\n                        return node.getAttribute('label');\r\n                    }, true),\r\n                    range,bk,tmpObj = {};\r\n            for (var p in obj) {\r\n               if(obj[p]!==undefined)\r\n                    tmpObj[p] = obj[p];\r\n            }\r\n            delete tmpObj.tag;\r\n            if (node && node.getAttribute('label') == obj.label) {\r\n                range = this.selection.getRange();\r\n                bk = range.createBookmark();\r\n                if (range.collapsed) {\r\n                    //trace:1732 删掉自定义标签，要有p来回填站位\r\n                    if(dtd.$block[node.tagName]){\r\n                        var fillNode = me.document.createElement('p');\r\n                        domUtils.moveChild(node, fillNode);\r\n                        node.parentNode.insertBefore(fillNode, node);\r\n                        domUtils.remove(node);\r\n                    }else{\r\n                        domUtils.remove(node,true);\r\n                    }\r\n\r\n                } else {\r\n\r\n                    var common = domUtils.getCommonAncestor(bk.start, bk.end),\r\n                            nodes = domUtils.getElementsByTagName(common, tagName);\r\n                    if(new RegExp(tagName,'i').test(common.tagName)){\r\n                        nodes.push(common);\r\n                    }\r\n                    for (var i = 0,ni; ni = nodes[i++];) {\r\n                        if (ni.getAttribute('label') == obj.label) {\r\n                            var ps = domUtils.getPosition(ni, bk.start),pe = domUtils.getPosition(ni, bk.end);\r\n                            if ((ps & domUtils.POSITION_FOLLOWING || ps & domUtils.POSITION_CONTAINS)\r\n                                    &&\r\n                                    (pe & domUtils.POSITION_PRECEDING || pe & domUtils.POSITION_CONTAINS)\r\n                                    )\r\n                                if (dtd.$block[tagName]) {\r\n                                    var fillNode = me.document.createElement('p');\r\n                                    domUtils.moveChild(ni, fillNode);\r\n                                    ni.parentNode.insertBefore(fillNode, ni);\r\n                                }\r\n                            domUtils.remove(ni, true);\r\n                        }\r\n                    }\r\n                    node = domUtils.findParent(common, function(node) {\r\n                        return node.getAttribute('label') == obj.label;\r\n                    }, true);\r\n                    if (node) {\r\n\r\n                        domUtils.remove(node, true);\r\n\r\n                    }\r\n\r\n                }\r\n                range.moveToBookmark(bk).select();\r\n            } else {\r\n                if (dtd.$block[tagName]) {\r\n                    this.execCommand('paragraph', tagName, tmpObj,'customstyle');\r\n                    range = me.selection.getRange();\r\n                    if (!range.collapsed) {\r\n                        range.collapse();\r\n                        node = domUtils.findParent(me.selection.getStart(), function(node) {\r\n                            return node.getAttribute('label') == obj.label;\r\n                        }, true);\r\n                        var pNode = me.document.createElement('p');\r\n                        domUtils.insertAfter(node, pNode);\r\n                        domUtils.fillNode(me.document, pNode);\r\n                        range.setStart(pNode, 0).setCursor();\r\n                    }\r\n                } else {\r\n\r\n                    range = me.selection.getRange();\r\n                    if (range.collapsed) {\r\n                        node = me.document.createElement(tagName);\r\n                        domUtils.setAttributes(node, tmpObj);\r\n                        range.insertNode(node).setStart(node, 0).setCursor();\r\n\r\n                        return;\r\n                    }\r\n\r\n                    bk = range.createBookmark();\r\n                    range.applyInlineStyle(tagName, tmpObj).moveToBookmark(bk).select();\r\n                }\r\n            }\r\n\r\n        },\r\n        queryCommandValue : function() {\r\n            var parent = domUtils.filterNodeList(\r\n                this.selection.getStartElementPath(),\r\n                function(node){return node.getAttribute('label')}\r\n            );\r\n            return  parent ? parent.getAttribute('label') : '';\r\n        }\r\n    };\r\n    //当去掉customstyle是，如果是块元素，用p代替\r\n    me.addListener('keyup', function(type, evt) {\r\n        var keyCode = evt.keyCode || evt.which;\r\n\r\n        if (keyCode == 32 || keyCode == 13) {\r\n            var range = me.selection.getRange();\r\n            if (range.collapsed) {\r\n                var node = domUtils.findParent(me.selection.getStart(), function(node) {\r\n                    return node.getAttribute('label');\r\n                }, true);\r\n                if (node && dtd.$block[node.tagName] && domUtils.isEmptyNode(node)) {\r\n                        var p = me.document.createElement('p');\r\n                        domUtils.insertAfter(node, p);\r\n                        domUtils.fillNode(me.document, p);\r\n                        domUtils.remove(node);\r\n                        range.setStart(p, 0).setCursor();\r\n\r\n\r\n                }\r\n            }\r\n        }\r\n    });\r\n};\r\n\r\n// plugins/catchremoteimage.js\r\n///import core\r\n///commands 远程图片抓取\r\n///commandsName  catchRemoteImage,catchremoteimageenable\r\n///commandsTitle  远程图片抓取\r\n/**\r\n * 远程图片抓取,当开启本插件时所有不符合本地域名的图片都将被抓取成为本地服务器上的图片\r\n */\r\nUE.plugins['catchremoteimage'] = function () {\r\n    var me = this,\r\n        ajax = UE.ajax;\r\n\r\n    /* 设置默认值 */\r\n    if (me.options.catchRemoteImageEnable === false) return;\r\n    me.setOpt({\r\n        catchRemoteImageEnable: false\r\n    });\r\n\r\n    me.addListener(\"afterpaste\", function () {\r\n        me.fireEvent(\"catchRemoteImage\");\r\n    });\r\n\r\n    me.addListener(\"catchRemoteImage\", function () {\r\n\r\n        var catcherLocalDomain = me.getOpt('catcherLocalDomain'),\r\n            catcherActionUrl = me.getActionUrl(me.getOpt('catcherActionName')),\r\n            catcherUrlPrefix = me.getOpt('catcherUrlPrefix'),\r\n            catcherFieldName = me.getOpt('catcherFieldName');\r\n\r\n        var remoteImages = [],\r\n            imgs = domUtils.getElementsByTagName(me.document, \"img\"),\r\n            test = function (src, urls) {\r\n                if (src.indexOf(location.host) != -1 || /(^\\.)|(^\\/)/.test(src)) {\r\n                    return true;\r\n                }\r\n                if (urls) {\r\n                    for (var j = 0, url; url = urls[j++];) {\r\n                        if (src.indexOf(url) !== -1) {\r\n                            return true;\r\n                        }\r\n                    }\r\n                }\r\n                return false;\r\n            };\r\n\r\n        for (var i = 0, ci; ci = imgs[i++];) {\r\n            if (ci.getAttribute(\"word_img\")) {\r\n                continue;\r\n            }\r\n            var src = ci.getAttribute(\"_src\") || ci.src || \"\";\r\n            if (/^(https?|ftp):/i.test(src) && !test(src, catcherLocalDomain)) {\r\n                remoteImages.push(src);\r\n            }\r\n        }\r\n\r\n        if (remoteImages.length) {\r\n            catchremoteimage(remoteImages, {\r\n                //成功抓取\r\n                success: function (r) {\r\n                    try {\r\n                        var info = r.state !== undefined ? r:eval(\"(\" + r.responseText + \")\");\r\n                    } catch (e) {\r\n                        return;\r\n                    }\r\n\r\n                    /* 获取源路径和新路径 */\r\n                    var i, j, ci, cj, oldSrc, newSrc, list = info.list;\r\n\r\n                    for (i = 0; ci = imgs[i++];) {\r\n                        oldSrc = ci.getAttribute(\"_src\") || ci.src || \"\";\r\n                        for (j = 0; cj = list[j++];) {\r\n                            if (oldSrc == cj.source && cj.state == \"SUCCESS\") {  //抓取失败时不做替换处理\r\n                                newSrc = catcherUrlPrefix + cj.url;\r\n                                domUtils.setAttributes(ci, {\r\n                                    \"src\": newSrc,\r\n                                    \"_src\": newSrc\r\n                                });\r\n                                break;\r\n                            }\r\n                        }\r\n                    }\r\n                    me.fireEvent('catchremotesuccess')\r\n                },\r\n                //回调失败，本次请求超时\r\n                error: function () {\r\n                    me.fireEvent(\"catchremoteerror\");\r\n                }\r\n            });\r\n        }\r\n\r\n        function catchremoteimage(imgs, callbacks) {\r\n            var params = utils.serializeParam(me.queryCommandValue('serverparam')) || '',\r\n                url = utils.formatUrl(catcherActionUrl + (catcherActionUrl.indexOf('?') == -1 ? '?':'&') + params),\r\n                isJsonp = utils.isCrossDomainUrl(url),\r\n                opt = {\r\n                    'method': 'POST',\r\n                    'dataType': isJsonp ? 'jsonp':'',\r\n                    'timeout': 60000, //单位：毫秒，回调请求超时设置。目标用户如果网速不是很快的话此处建议设置一个较大的数值\r\n                    'onsuccess': callbacks[\"success\"],\r\n                    'onerror': callbacks[\"error\"]\r\n                };\r\n            opt[catcherFieldName] = imgs;\r\n            ajax.request(url, opt);\r\n        }\r\n\r\n    });\r\n};\r\n\r\n// plugins/snapscreen.js\r\n/**\r\n * 截屏插件，为UEditor提供插入支持\r\n * @file\r\n * @since 1.4.2\r\n */\r\nUE.plugin.register('snapscreen', function (){\r\n\r\n    var me = this;\r\n    var snapplugin;\r\n\r\n    function getLocation(url){\r\n        var search,\r\n            a = document.createElement('a'),\r\n            params = utils.serializeParam(me.queryCommandValue('serverparam')) || '';\r\n\r\n        a.href = url;\r\n        if (browser.ie) {\r\n            a.href = a.href;\r\n        }\r\n\r\n\r\n        search = a.search;\r\n        if (params) {\r\n            search = search + (search.indexOf('?') == -1 ? '?':'&')+ params;\r\n            search = search.replace(/[&]+/ig, '&');\r\n        }\r\n        return {\r\n            'port': a.port,\r\n            'hostname': a.hostname,\r\n            'path': a.pathname + search ||  + a.hash\r\n        }\r\n    }\r\n\r\n    return {\r\n        commands:{\r\n            /**\r\n             * 字体背景颜色\r\n             * @command snapscreen\r\n             * @method execCommand\r\n             * @param { String } cmd 命令字符串\r\n             * @example\r\n             * ```javascript\r\n             * editor.execCommand('snapscreen');\r\n             * ```\r\n             */\r\n            'snapscreen':{\r\n                execCommand:function (cmd) {\r\n                    var url, local, res;\r\n                    var lang = me.getLang(\"snapScreen_plugin\");\r\n\r\n                    if(!snapplugin){\r\n                        var container = me.container;\r\n                        var doc = me.container.ownerDocument || me.container.document;\r\n                        snapplugin = doc.createElement(\"object\");\r\n                        try{snapplugin.type = \"application/x-pluginbaidusnap\";}catch(e){\r\n                            return;\r\n                        }\r\n                        snapplugin.style.cssText = \"position:absolute;left:-9999px;width:0;height:0;\";\r\n                        snapplugin.setAttribute(\"width\",\"0\");\r\n                        snapplugin.setAttribute(\"height\",\"0\");\r\n                        container.appendChild(snapplugin);\r\n                    }\r\n\r\n                    function onSuccess(rs){\r\n                        try{\r\n                            rs = eval(\"(\"+ rs +\")\");\r\n                            if(rs.state == 'SUCCESS'){\r\n                                var opt = me.options;\r\n                                me.execCommand('insertimage', {\r\n                                    src: opt.snapscreenUrlPrefix + rs.url,\r\n                                    _src: opt.snapscreenUrlPrefix + rs.url,\r\n                                    alt: rs.title || '',\r\n                                    floatStyle: opt.snapscreenImgAlign\r\n                                });\r\n                            } else {\r\n                                alert(rs.state);\r\n                            }\r\n                        }catch(e){\r\n                            alert(lang.callBackErrorMsg);\r\n                        }\r\n                    }\r\n                    url = me.getActionUrl(me.getOpt('snapscreenActionName'));\r\n                    local = getLocation(url);\r\n                    setTimeout(function () {\r\n                        try{\r\n                            res =snapplugin.saveSnapshot(local.hostname, local.path, local.port);\r\n                        }catch(e){\r\n                            me.ui._dialogs['snapscreenDialog'].open();\r\n                            return;\r\n                        }\r\n\r\n                        onSuccess(res);\r\n                    }, 50);\r\n                },\r\n                queryCommandState: function(){\r\n                    return (navigator.userAgent.indexOf(\"Windows\",0) != -1) ? 0:-1;\r\n                }\r\n            }\r\n        }\r\n    }\r\n});\r\n\r\n\r\n// plugins/insertparagraph.js\r\n/**\r\n * 插入段落\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n\r\n/**\r\n * 插入段落\r\n * @command insertparagraph\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * //editor是编辑器实例\r\n * editor.execCommand( 'insertparagraph' );\r\n * ```\r\n */\r\n\r\nUE.commands['insertparagraph'] = {\r\n    execCommand : function( cmdName,front) {\r\n        var me = this,\r\n            range = me.selection.getRange(),\r\n            start = range.startContainer,tmpNode;\r\n        while(start ){\r\n            if(domUtils.isBody(start)){\r\n                break;\r\n            }\r\n            tmpNode = start;\r\n            start = start.parentNode;\r\n        }\r\n        if(tmpNode){\r\n            var p = me.document.createElement('p');\r\n            if(front){\r\n                tmpNode.parentNode.insertBefore(p,tmpNode)\r\n            }else{\r\n                tmpNode.parentNode.insertBefore(p,tmpNode.nextSibling)\r\n            }\r\n            domUtils.fillNode(me.document,p);\r\n            range.setStart(p,0).setCursor(false,true);\r\n        }\r\n    }\r\n};\r\n\r\n\r\n\r\n// plugins/webapp.js\r\n/**\r\n * 百度应用\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n\r\n/**\r\n * 插入百度应用\r\n * @command webapp\r\n * @method execCommand\r\n * @remind 需要百度APPKey\r\n * @remind 百度应用主页： <a href=\"http://app.baidu.com/\" target=\"_blank\">http://app.baidu.com/</a>\r\n * @param { Object } appOptions 应用所需的参数项， 支持的key有： title=>应用标题， width=>应用容器宽度，\r\n * height=>应用容器高度，logo=>应用logo，url=>应用地址\r\n * @example\r\n * ```javascript\r\n * //editor是编辑器实例\r\n * //在编辑器里插入一个“植物大战僵尸”的APP\r\n * editor.execCommand( 'webapp' , {\r\n *     title: '植物大战僵尸',\r\n *     width: 560,\r\n *     height: 465,\r\n *     logo: '应用展示的图片',\r\n *     url: '百度应用的地址'\r\n * } );\r\n * ```\r\n */\r\n\r\n//UE.plugins['webapp'] = function () {\r\n//    var me = this;\r\n//    function createInsertStr( obj, toIframe, addParagraph ) {\r\n//        return !toIframe ?\r\n//                (addParagraph ? '<p>' : '') + '<img title=\"'+obj.title+'\" width=\"' + obj.width + '\" height=\"' + obj.height + '\"' +\r\n//                        ' src=\"' + me.options.UEDITOR_HOME_URL + 'themes/default/images/spacer.gif\" style=\"background:url(' + obj.logo+') no-repeat center center; border:1px solid gray;\" class=\"edui-faked-webapp\" _url=\"' + obj.url + '\" />' +\r\n//                        (addParagraph ? '</p>' : '')\r\n//                :\r\n//                '<iframe class=\"edui-faked-webapp\" title=\"'+obj.title+'\" width=\"' + obj.width + '\" height=\"' + obj.height + '\"  scrolling=\"no\" frameborder=\"0\" src=\"' + obj.url + '\" logo_url = '+obj.logo+'></iframe>';\r\n//    }\r\n//\r\n//    function switchImgAndIframe( img2frame ) {\r\n//        var tmpdiv,\r\n//                nodes = domUtils.getElementsByTagName( me.document, !img2frame ? \"iframe\" : \"img\" );\r\n//        for ( var i = 0, node; node = nodes[i++]; ) {\r\n//            if ( node.className != \"edui-faked-webapp\" ){\r\n//                continue;\r\n//            }\r\n//            tmpdiv = me.document.createElement( \"div\" );\r\n//            tmpdiv.innerHTML = createInsertStr( img2frame ? {url:node.getAttribute( \"_url\" ), width:node.width, height:node.height,title:node.title,logo:node.style.backgroundImage.replace(\"url(\",\"\").replace(\")\",\"\")} : {url:node.getAttribute( \"src\", 2 ),title:node.title, width:node.width, height:node.height,logo:node.getAttribute(\"logo_url\")}, img2frame ? true : false,false );\r\n//            node.parentNode.replaceChild( tmpdiv.firstChild, node );\r\n//        }\r\n//    }\r\n//\r\n//    me.addListener( \"beforegetcontent\", function () {\r\n//        switchImgAndIframe( true );\r\n//    } );\r\n//    me.addListener( 'aftersetcontent', function () {\r\n//        switchImgAndIframe( false );\r\n//    } );\r\n//    me.addListener( 'aftergetcontent', function ( cmdName ) {\r\n//        if ( cmdName == 'aftergetcontent' && me.queryCommandState( 'source' ) ){\r\n//            return;\r\n//        }\r\n//        switchImgAndIframe( false );\r\n//    } );\r\n//\r\n//    me.commands['webapp'] = {\r\n//        execCommand:function ( cmd, obj ) {\r\n//            me.execCommand( \"inserthtml\", createInsertStr( obj, false,true ) );\r\n//        }\r\n//    };\r\n//};\r\n\r\nUE.plugin.register('webapp', function (){\r\n    var me = this;\r\n    function createInsertStr(obj,toEmbed){\r\n        return  !toEmbed ?\r\n            '<img title=\"'+obj.title+'\" width=\"' + obj.width + '\" height=\"' + obj.height + '\"' +\r\n                ' src=\"' + me.options.UEDITOR_HOME_URL + 'themes/default/images/spacer.gif\" _logo_url=\"'+obj.logo+'\" style=\"background:url(' + obj.logo\r\n                +') no-repeat center center; border:1px solid gray;\" class=\"edui-faked-webapp\" _url=\"' + obj.url + '\" ' +\r\n                (obj.align && !obj.cssfloat? 'align=\"' + obj.align + '\"' : '') +\r\n                (obj.cssfloat ? 'style=\"float:' + obj.cssfloat + '\"' : '') +\r\n                '/>'\r\n            :\r\n            '<iframe class=\"edui-faked-webapp\" title=\"'+obj.title+'\" ' +\r\n                (obj.align && !obj.cssfloat? 'align=\"' + obj.align + '\"' : '') +\r\n                (obj.cssfloat ? 'style=\"float:' + obj.cssfloat + '\"' : '') +\r\n                'width=\"' + obj.width + '\" height=\"' + obj.height + '\"  scrolling=\"no\" frameborder=\"0\" src=\"' + obj.url + '\" logo_url = \"'+obj.logo+'\"></iframe>'\r\n\r\n    }\r\n    return {\r\n        outputRule: function(root){\r\n            utils.each(root.getNodesByTagName('img'),function(node){\r\n                var html;\r\n                if(node.getAttr('class') == 'edui-faked-webapp'){\r\n                    html =  createInsertStr({\r\n                        title:node.getAttr('title'),\r\n                        'width':node.getAttr('width'),\r\n                        'height':node.getAttr('height'),\r\n                        'align':node.getAttr('align'),\r\n                        'cssfloat':node.getStyle('float'),\r\n                        'url':node.getAttr(\"_url\"),\r\n                        'logo':node.getAttr('_logo_url')\r\n                    },true);\r\n                    var embed = UE.uNode.createElement(html);\r\n                    node.parentNode.replaceChild(embed,node);\r\n                }\r\n            })\r\n        },\r\n        inputRule:function(root){\r\n            utils.each(root.getNodesByTagName('iframe'),function(node){\r\n                if(node.getAttr('class') == 'edui-faked-webapp'){\r\n                    var img = UE.uNode.createElement(createInsertStr({\r\n                        title:node.getAttr('title'),\r\n                        'width':node.getAttr('width'),\r\n                        'height':node.getAttr('height'),\r\n                        'align':node.getAttr('align'),\r\n                        'cssfloat':node.getStyle('float'),\r\n                        'url':node.getAttr(\"src\"),\r\n                        'logo':node.getAttr('logo_url')\r\n                    }));\r\n                    node.parentNode.replaceChild(img,node);\r\n                }\r\n            })\r\n\r\n        },\r\n        commands:{\r\n            /**\r\n             * 插入百度应用\r\n             * @command webapp\r\n             * @method execCommand\r\n             * @remind 需要百度APPKey\r\n             * @remind 百度应用主页： <a href=\"http://app.baidu.com/\" target=\"_blank\">http://app.baidu.com/</a>\r\n             * @param { Object } appOptions 应用所需的参数项， 支持的key有： title=>应用标题， width=>应用容器宽度，\r\n             * height=>应用容器高度，logo=>应用logo，url=>应用地址\r\n             * @example\r\n             * ```javascript\r\n             * //editor是编辑器实例\r\n             * //在编辑器里插入一个“植物大战僵尸”的APP\r\n             * editor.execCommand( 'webapp' , {\r\n             *     title: '植物大战僵尸',\r\n             *     width: 560,\r\n             *     height: 465,\r\n             *     logo: '应用展示的图片',\r\n             *     url: '百度应用的地址'\r\n             * } );\r\n             * ```\r\n             */\r\n            'webapp':{\r\n                execCommand:function (cmd, obj) {\r\n\r\n                    var me = this,\r\n                        str = createInsertStr(utils.extend(obj,{\r\n                            align:'none'\r\n                        }), false);\r\n                    me.execCommand(\"inserthtml\",str);\r\n                },\r\n                queryCommandState:function () {\r\n                    var me = this,\r\n                        img = me.selection.getRange().getClosedNode(),\r\n                        flag = img && (img.className == \"edui-faked-webapp\");\r\n                    return flag ? 1 : 0;\r\n                }\r\n            }\r\n        }\r\n    }\r\n});\r\n\r\n// plugins/template.js\r\n///import core\r\n///import plugins\\inserthtml.js\r\n///import plugins\\cleardoc.js\r\n///commands 模板\r\n///commandsName  template\r\n///commandsTitle  模板\r\n///commandsDialog  dialogs\\template\r\nUE.plugins['template'] = function () {\r\n    UE.commands['template'] = {\r\n        execCommand:function (cmd, obj) {\r\n            obj.html && this.execCommand(\"inserthtml\", obj.html);\r\n        }\r\n    };\r\n    this.addListener(\"click\", function (type, evt) {\r\n        var el = evt.target || evt.srcElement,\r\n            range = this.selection.getRange();\r\n        var tnode = domUtils.findParent(el, function (node) {\r\n            if (node.className && domUtils.hasClass(node, \"ue_t\")) {\r\n                return node;\r\n            }\r\n        }, true);\r\n        tnode && range.selectNode(tnode).shrinkBoundary().select();\r\n    });\r\n    this.addListener(\"keydown\", function (type, evt) {\r\n        var range = this.selection.getRange();\r\n        if (!range.collapsed) {\r\n            if (!evt.ctrlKey && !evt.metaKey && !evt.shiftKey && !evt.altKey) {\r\n                var tnode = domUtils.findParent(range.startContainer, function (node) {\r\n                    if (node.className && domUtils.hasClass(node, \"ue_t\")) {\r\n                        return node;\r\n                    }\r\n                }, true);\r\n                if (tnode) {\r\n                    domUtils.removeClasses(tnode, [\"ue_t\"]);\r\n                }\r\n            }\r\n        }\r\n    });\r\n};\r\n\r\n\r\n// plugins/music.js\r\n/**\r\n * 插入音乐命令\r\n * @file\r\n */\r\nUE.plugin.register('music', function (){\r\n    var me = this;\r\n    function creatInsertStr(url,width,height,align,cssfloat,toEmbed){\r\n        return  !toEmbed ?\r\n                '<img ' +\r\n                    (align && !cssfloat? 'align=\"' + align + '\"' : '') +\r\n                    (cssfloat ? 'style=\"float:' + cssfloat + '\"' : '') +\r\n                    ' width=\"'+ width +'\" height=\"' + height + '\" _url=\"'+url+'\" class=\"edui-faked-music\"' +\r\n                    ' src=\"'+me.options.langPath+me.options.lang+'/images/music.png\" />'\r\n            :\r\n            '<embed type=\"application/x-shockwave-flash\" class=\"edui-faked-music\" pluginspage=\"http://www.macromedia.com/go/getflashplayer\"' +\r\n                ' src=\"' + url + '\" width=\"' + width  + '\" height=\"' + height  + '\" '+ (align && !cssfloat? 'align=\"' + align + '\"' : '') +\r\n                (cssfloat ? 'style=\"float:' + cssfloat + '\"' : '') +\r\n                ' wmode=\"transparent\" play=\"true\" loop=\"false\" menu=\"false\" allowscriptaccess=\"never\" allowfullscreen=\"true\" >';\r\n    }\r\n    return {\r\n        outputRule: function(root){\r\n            utils.each(root.getNodesByTagName('img'),function(node){\r\n                var html;\r\n                if(node.getAttr('class') == 'edui-faked-music'){\r\n                    var cssfloat = node.getStyle('float');\r\n                    var align = node.getAttr('align');\r\n                    html =  creatInsertStr(node.getAttr(\"_url\"), node.getAttr('width'), node.getAttr('height'), align, cssfloat, true);\r\n                    var embed = UE.uNode.createElement(html);\r\n                    node.parentNode.replaceChild(embed,node);\r\n                }\r\n            })\r\n        },\r\n        inputRule:function(root){\r\n            utils.each(root.getNodesByTagName('embed'),function(node){\r\n                if(node.getAttr('class') == 'edui-faked-music'){\r\n                    var cssfloat = node.getStyle('float');\r\n                    var align = node.getAttr('align');\r\n                    html =  creatInsertStr(node.getAttr(\"src\"), node.getAttr('width'), node.getAttr('height'), align, cssfloat,false);\r\n                    var img = UE.uNode.createElement(html);\r\n                    node.parentNode.replaceChild(img,node);\r\n                }\r\n            })\r\n\r\n        },\r\n        commands:{\r\n            /**\r\n             * 插入音乐\r\n             * @command music\r\n             * @method execCommand\r\n             * @param { Object } musicOptions 插入音乐的参数项， 支持的key有： url=>音乐地址；\r\n             * width=>音乐容器宽度；height=>音乐容器高度；align=>音乐文件的对齐方式， 可选值有: left, center, right, none\r\n             * @example\r\n             * ```javascript\r\n             * //editor是编辑器实例\r\n             * //在编辑器里插入一个“植物大战僵尸”的APP\r\n             * editor.execCommand( 'music' , {\r\n             *     width: 400,\r\n             *     height: 95,\r\n             *     align: \"center\",\r\n             *     url: \"音乐地址\"\r\n             * } );\r\n             * ```\r\n             */\r\n            'music':{\r\n                execCommand:function (cmd, musicObj) {\r\n                    var me = this,\r\n                        str = creatInsertStr(musicObj.url, musicObj.width || 400, musicObj.height || 95, \"none\", false);\r\n                    me.execCommand(\"inserthtml\",str);\r\n                },\r\n                queryCommandState:function () {\r\n                    var me = this,\r\n                        img = me.selection.getRange().getClosedNode(),\r\n                        flag = img && (img.className == \"edui-faked-music\");\r\n                    return flag ? 1 : 0;\r\n                }\r\n            }\r\n        }\r\n    }\r\n});\r\n\r\n// plugins/autoupload.js\r\n/**\r\n * @description\r\n * 1.拖放文件到编辑区域，自动上传并插入到选区\r\n * 2.插入粘贴板的图片，自动上传并插入到选区\r\n * @author Jinqn\r\n * @date 2013-10-14\r\n */\r\nUE.plugin.register('autoupload', function (){\r\n\r\n    function sendAndInsertFile(file, editor) {\r\n        var me  = editor;\r\n        //模拟数据\r\n        var fieldName, urlPrefix, maxSize, allowFiles, actionUrl,\r\n            loadingHtml, errorHandler, successHandler,\r\n            filetype = /image\\/\\w+/i.test(file.type) ? 'image':'file',\r\n            loadingId = 'loading_' + (+new Date()).toString(36);\r\n\r\n        fieldName = me.getOpt(filetype + 'FieldName');\r\n        urlPrefix = me.getOpt(filetype + 'UrlPrefix');\r\n        maxSize = me.getOpt(filetype + 'MaxSize');\r\n        allowFiles = me.getOpt(filetype + 'AllowFiles');\r\n        actionUrl = me.getActionUrl(me.getOpt(filetype + 'ActionName'));\r\n        errorHandler = function(title) {\r\n            var loader = me.document.getElementById(loadingId);\r\n            loader && domUtils.remove(loader);\r\n            me.fireEvent('showmessage', {\r\n                'id': loadingId,\r\n                'content': title,\r\n                'type': 'error',\r\n                'timeout': 4000\r\n            });\r\n        };\r\n\r\n        if (filetype == 'image') {\r\n            loadingHtml = '<img class=\"loadingclass\" id=\"' + loadingId + '\" src=\"' +\r\n                me.options.themePath + me.options.theme +\r\n                '/images/spacer.gif\" title=\"' + (me.getLang('autoupload.loading') || '') + '\" >';\r\n            successHandler = function(data) {\r\n                var link = urlPrefix + data.url,\r\n                    loader = me.document.getElementById(loadingId);\r\n                if (loader) {\r\n                    loader.setAttribute('src', link);\r\n                    loader.setAttribute('_src', link);\r\n                    loader.setAttribute('title', data.title || '');\r\n                    loader.setAttribute('alt', data.original || '');\r\n                    loader.removeAttribute('id');\r\n                    domUtils.removeClasses(loader, 'loadingclass');\r\n                }\r\n            };\r\n        } else {\r\n            loadingHtml = '<p>' +\r\n                '<img class=\"loadingclass\" id=\"' + loadingId + '\" src=\"' +\r\n                me.options.themePath + me.options.theme +\r\n                '/images/spacer.gif\" title=\"' + (me.getLang('autoupload.loading') || '') + '\" >' +\r\n                '</p>';\r\n            successHandler = function(data) {\r\n                var link = urlPrefix + data.url,\r\n                    loader = me.document.getElementById(loadingId);\r\n\r\n                var rng = me.selection.getRange(),\r\n                    bk = rng.createBookmark();\r\n                rng.selectNode(loader).select();\r\n                me.execCommand('insertfile', {'url': link});\r\n                rng.moveToBookmark(bk).select();\r\n            };\r\n        }\r\n\r\n        /* 插入loading的占位符 */\r\n        me.execCommand('inserthtml', loadingHtml);\r\n\r\n        /* 判断后端配置是否没有加载成功 */\r\n        if (!me.getOpt(filetype + 'ActionName')) {\r\n            errorHandler(me.getLang('autoupload.errorLoadConfig'));\r\n            return;\r\n        }\r\n        /* 判断文件大小是否超出限制 */\r\n        if(file.size > maxSize) {\r\n            errorHandler(me.getLang('autoupload.exceedSizeError'));\r\n            return;\r\n        }\r\n        /* 判断文件格式是否超出允许 */\r\n        var fileext = file.name ? file.name.substr(file.name.lastIndexOf('.')):'';\r\n        if ((fileext && filetype != 'image') || (allowFiles && (allowFiles.join('') + '.').indexOf(fileext.toLowerCase() + '.') == -1)) {\r\n            errorHandler(me.getLang('autoupload.exceedTypeError'));\r\n            return;\r\n        }\r\n\r\n        /* 创建Ajax并提交 */\r\n        var xhr = new XMLHttpRequest(),\r\n            fd = new FormData(),\r\n            params = utils.serializeParam(me.queryCommandValue('serverparam')) || '',\r\n            url = utils.formatUrl(actionUrl + (actionUrl.indexOf('?') == -1 ? '?':'&') + params);\r\n\r\n        fd.append(fieldName, file, file.name || ('blob.' + file.type.substr('image/'.length)));\r\n        fd.append('type', 'ajax');\r\n        xhr.open(\"post\", url, true);\r\n        xhr.setRequestHeader(\"X-Requested-With\", \"XMLHttpRequest\");\r\n        xhr.addEventListener('load', function (e) {\r\n            try{\r\n                var json = (new Function(\"return \" + utils.trim(e.target.response)))();\r\n                if (json.state == 'SUCCESS' && json.url) {\r\n                    successHandler(json);\r\n                } else {\r\n                    errorHandler(json.state);\r\n                }\r\n            }catch(er){\r\n                errorHandler(me.getLang('autoupload.loadError'));\r\n            }\r\n        });\r\n        xhr.send(fd);\r\n    }\r\n\r\n    function getPasteImage(e){\r\n        return e.clipboardData && e.clipboardData.items && e.clipboardData.items.length == 1 && /^image\\//.test(e.clipboardData.items[0].type) ? e.clipboardData.items:null;\r\n    }\r\n    function getDropImage(e){\r\n        return  e.dataTransfer && e.dataTransfer.files ? e.dataTransfer.files:null;\r\n    }\r\n\r\n    return {\r\n        outputRule: function(root){\r\n            utils.each(root.getNodesByTagName('img'),function(n){\r\n                if (/\\b(loaderrorclass)|(bloaderrorclass)\\b/.test(n.getAttr('class'))) {\r\n                    n.parentNode.removeChild(n);\r\n                }\r\n            });\r\n            utils.each(root.getNodesByTagName('p'),function(n){\r\n                if (/\\bloadpara\\b/.test(n.getAttr('class'))) {\r\n                    n.parentNode.removeChild(n);\r\n                }\r\n            });\r\n        },\r\n        bindEvents:{\r\n            //插入粘贴板的图片，拖放插入图片\r\n            'ready':function(e){\r\n                var me = this;\r\n                if(window.FormData && window.FileReader) {\r\n                    domUtils.on(me.body, 'paste drop', function(e){\r\n                        var hasImg = false,\r\n                            items;\r\n                        //获取粘贴板文件列表或者拖放文件列表\r\n                        items = e.type == 'paste' ? getPasteImage(e):getDropImage(e);\r\n                        if(items){\r\n                            var len = items.length,\r\n                                file;\r\n                            while (len--){\r\n                                file = items[len];\r\n                                if(file.getAsFile) file = file.getAsFile();\r\n                                if(file && file.size > 0) {\r\n                                    sendAndInsertFile(file, me);\r\n                                    hasImg = true;\r\n                                }\r\n                            }\r\n                            hasImg && e.preventDefault();\r\n                        }\r\n\r\n                    });\r\n                    //取消拖放图片时出现的文字光标位置提示\r\n                    domUtils.on(me.body, 'dragover', function (e) {\r\n                        if(e.dataTransfer.types[0] == 'Files') {\r\n                            e.preventDefault();\r\n                        }\r\n                    });\r\n\r\n                    //设置loading的样式\r\n                    utils.cssRule('loading',\r\n                        '.loadingclass{display:inline-block;cursor:default;background: url(\\''\r\n                            + this.options.themePath\r\n                            + this.options.theme +'/images/loading.gif\\') no-repeat center center transparent;border:1px solid #cccccc;margin-left:1px;height: 22px;width: 22px;}\\n' +\r\n                            '.loaderrorclass{display:inline-block;cursor:default;background: url(\\''\r\n                            + this.options.themePath\r\n                            + this.options.theme +'/images/loaderror.png\\') no-repeat center center transparent;border:1px solid #cccccc;margin-right:1px;height: 22px;width: 22px;' +\r\n                            '}',\r\n                        this.document);\r\n                }\r\n            }\r\n        }\r\n    }\r\n});\r\n\r\n// plugins/autosave.js\r\nUE.plugin.register('autosave', function (){\r\n\r\n    var me = this,\r\n        //无限循环保护\r\n        lastSaveTime = new Date(),\r\n        //最小保存间隔时间\r\n        MIN_TIME = 20,\r\n        //auto save key\r\n        saveKey = null;\r\n\r\n    function save ( editor ) {\r\n\r\n        var saveData;\r\n\r\n        if ( new Date() - lastSaveTime < MIN_TIME ) {\r\n            return;\r\n        }\r\n\r\n        if ( !editor.hasContents() ) {\r\n            //这里不能调用命令来删除， 会造成事件死循环\r\n            saveKey && me.removePreferences( saveKey );\r\n            return;\r\n        }\r\n\r\n        lastSaveTime = new Date();\r\n\r\n        editor._saveFlag = null;\r\n\r\n        saveData = me.body.innerHTML;\r\n\r\n        if ( editor.fireEvent( \"beforeautosave\", {\r\n            content: saveData\r\n        } ) === false ) {\r\n            return;\r\n        }\r\n\r\n        me.setPreferences( saveKey, saveData );\r\n\r\n        editor.fireEvent( \"afterautosave\", {\r\n            content: saveData\r\n        } );\r\n\r\n    }\r\n\r\n    return {\r\n        defaultOptions: {\r\n            //默认间隔时间\r\n            saveInterval: 500,\r\n            enableAutoSave: true // HaoChuan9421\r\n        },\r\n        bindEvents:{\r\n            'ready':function(){\r\n\r\n                var _suffix = \"-drafts-data\",\r\n                    key = null;\r\n\r\n                if ( me.key ) {\r\n                    key = me.key + _suffix;\r\n                } else {\r\n                    key = ( me.container.parentNode.id || 'ue-common' ) + _suffix;\r\n                }\r\n\r\n                //页面地址+编辑器ID 保持唯一\r\n                saveKey = ( location.protocol + location.host + location.pathname ).replace( /[.:\\/]/g, '_' ) + key;\r\n\r\n            },\r\n\r\n            'contentchange': function () {\r\n                // HaoChuan9421\r\n                if (!me.getOpt('enableAutoSave')) {\r\n                    return;\r\n                }\r\n\r\n                if ( !saveKey ) {\r\n                    return;\r\n                }\r\n\r\n                if ( me._saveFlag ) {\r\n                    window.clearTimeout( me._saveFlag );\r\n                }\r\n\r\n                if ( me.options.saveInterval > 0 ) {\r\n\r\n                    me._saveFlag = window.setTimeout( function () {\r\n\r\n                        save( me );\r\n\r\n                    }, me.options.saveInterval );\r\n\r\n                } else {\r\n\r\n                    save(me);\r\n\r\n                }\r\n\r\n\r\n            }\r\n        },\r\n        commands:{\r\n            'clearlocaldata':{\r\n                execCommand:function (cmd, name) {\r\n                    if ( saveKey && me.getPreferences( saveKey ) ) {\r\n                        me.removePreferences( saveKey )\r\n                    }\r\n                },\r\n                notNeedUndo: true,\r\n                ignoreContentChange:true\r\n            },\r\n\r\n            'getlocaldata':{\r\n                execCommand:function (cmd, name) {\r\n                    return saveKey ? me.getPreferences( saveKey ) || '' : '';\r\n                },\r\n                notNeedUndo: true,\r\n                ignoreContentChange:true\r\n            },\r\n\r\n            'drafts':{\r\n                execCommand:function (cmd, name) {\r\n                    if ( saveKey ) {\r\n                        me.body.innerHTML = me.getPreferences( saveKey ) || '<p>'+domUtils.fillHtml+'</p>';\r\n                        me.focus(true);\r\n                    }\r\n                },\r\n                queryCommandState: function () {\r\n                    return saveKey ? ( me.getPreferences( saveKey ) === null ? -1 : 0 ) : -1;\r\n                },\r\n                notNeedUndo: true,\r\n                ignoreContentChange:true\r\n            }\r\n        }\r\n    }\r\n\r\n});\r\n\r\n// plugins/charts.js\r\nUE.plugin.register('charts', function (){\r\n\r\n    var me = this;\r\n\r\n    return {\r\n        bindEvents: {\r\n            'chartserror': function () {\r\n            }\r\n        },\r\n        commands:{\r\n            'charts': {\r\n                execCommand: function ( cmd, data ) {\r\n\r\n                    var tableNode = domUtils.findParentByTagName(this.selection.getRange().startContainer, 'table', true),\r\n                        flagText = [],\r\n                        config = {};\r\n\r\n                    if ( !tableNode ) {\r\n                        return false;\r\n                    }\r\n\r\n                    if ( !validData( tableNode ) ) {\r\n                        me.fireEvent( \"chartserror\" );\r\n                        return false;\r\n                    }\r\n\r\n                    config.title = data.title || '';\r\n                    config.subTitle = data.subTitle || '';\r\n                    config.xTitle = data.xTitle || '';\r\n                    config.yTitle = data.yTitle || '';\r\n                    config.suffix = data.suffix || '';\r\n                    config.tip = data.tip || '';\r\n                    //数据对齐方式\r\n                    config.dataFormat = data.tableDataFormat || '';\r\n                    //图表类型\r\n                    config.chartType = data.chartType || 0;\r\n\r\n                    for ( var key in config ) {\r\n\r\n                        if ( !config.hasOwnProperty( key ) ) {\r\n                            continue;\r\n                        }\r\n\r\n                        flagText.push( key+\":\"+config[ key ] );\r\n\r\n                    }\r\n\r\n                    tableNode.setAttribute( \"data-chart\", flagText.join( \";\" ) );\r\n                    domUtils.addClass( tableNode, \"edui-charts-table\" );\r\n\r\n\r\n\r\n                },\r\n                queryCommandState: function ( cmd, name ) {\r\n\r\n                    var tableNode = domUtils.findParentByTagName(this.selection.getRange().startContainer, 'table', true);\r\n                    return tableNode && validData( tableNode ) ? 0 : -1;\r\n\r\n                }\r\n            }\r\n        },\r\n        inputRule:function(root){\r\n            utils.each(root.getNodesByTagName('table'),function( tableNode ){\r\n\r\n                if ( tableNode.getAttr(\"data-chart\") !== undefined ) {\r\n                    tableNode.setAttr(\"style\");\r\n                }\r\n\r\n            })\r\n\r\n        },\r\n        outputRule:function(root){\r\n            utils.each(root.getNodesByTagName('table'),function( tableNode ){\r\n\r\n                if ( tableNode.getAttr(\"data-chart\") !== undefined ) {\r\n                    tableNode.setAttr(\"style\", \"display: none;\");\r\n                }\r\n\r\n            })\r\n\r\n        }\r\n    }\r\n\r\n    function validData ( table ) {\r\n\r\n        var firstRows = null,\r\n            cellCount = 0;\r\n\r\n        //行数不够\r\n        if ( table.rows.length < 2 ) {\r\n            return false;\r\n        }\r\n\r\n        //列数不够\r\n        if ( table.rows[0].cells.length < 2 ) {\r\n            return false;\r\n        }\r\n\r\n        //第一行所有cell必须是th\r\n        firstRows = table.rows[ 0 ].cells;\r\n        cellCount = firstRows.length;\r\n\r\n        for ( var i = 0, cell; cell = firstRows[ i ]; i++ ) {\r\n\r\n            if ( cell.tagName.toLowerCase() !== 'th' ) {\r\n                return false;\r\n            }\r\n\r\n        }\r\n\r\n        for ( var i = 1, row; row = table.rows[ i ]; i++ ) {\r\n\r\n            //每行单元格数不匹配， 返回false\r\n            if ( row.cells.length != cellCount ) {\r\n                return false;\r\n            }\r\n\r\n            //第一列不是th也返回false\r\n            if ( row.cells[0].tagName.toLowerCase() !== 'th' ) {\r\n                return false;\r\n            }\r\n\r\n            for ( var j = 1, cell; cell = row.cells[ j ]; j++ ) {\r\n\r\n                var value = utils.trim( ( cell.innerText || cell.textContent || '' ) );\r\n\r\n                value = value.replace( new RegExp( UE.dom.domUtils.fillChar, 'g' ), '' ).replace( /^\\s+|\\s+$/g, '' );\r\n\r\n                //必须是数字\r\n                if ( !/^\\d*\\.?\\d+$/.test( value ) ) {\r\n                    return false;\r\n                }\r\n\r\n            }\r\n\r\n        }\r\n\r\n        return true;\r\n\r\n    }\r\n\r\n});\r\n\r\n// plugins/section.js\r\n/**\r\n * 目录大纲支持插件\r\n * @file\r\n * @since 1.3.0\r\n */\r\nUE.plugin.register('section', function (){\r\n    /* 目录节点对象 */\r\n    function Section(option){\r\n        this.tag = '';\r\n        this.level = -1,\r\n            this.dom = null;\r\n        this.nextSection = null;\r\n        this.previousSection = null;\r\n        this.parentSection = null;\r\n        this.startAddress = [];\r\n        this.endAddress = [];\r\n        this.children = [];\r\n    }\r\n    function getSection(option) {\r\n        var section = new Section();\r\n        return utils.extend(section, option);\r\n    }\r\n    function getNodeFromAddress(startAddress, root) {\r\n        var current = root;\r\n        for(var i = 0;i < startAddress.length; i++) {\r\n            if(!current.childNodes) return null;\r\n            current = current.childNodes[startAddress[i]];\r\n        }\r\n        return current;\r\n    }\r\n\r\n    var me = this;\r\n\r\n    return {\r\n        bindMultiEvents:{\r\n            type: 'aftersetcontent afterscencerestore',\r\n            handler: function(){\r\n                me.fireEvent('updateSections');\r\n            }\r\n        },\r\n        bindEvents:{\r\n            /* 初始化、拖拽、粘贴、执行setcontent之后 */\r\n            'ready': function (){\r\n                me.fireEvent('updateSections');\r\n                domUtils.on(me.body, 'drop paste', function(){\r\n                    me.fireEvent('updateSections');\r\n                });\r\n            },\r\n            /* 执行paragraph命令之后 */\r\n            'afterexeccommand': function (type, cmd) {\r\n                if(cmd == 'paragraph') {\r\n                    me.fireEvent('updateSections');\r\n                }\r\n            },\r\n            /* 部分键盘操作，触发updateSections事件 */\r\n            'keyup': function (type, e) {\r\n                var me = this,\r\n                    range = me.selection.getRange();\r\n                if(range.collapsed != true) {\r\n                    me.fireEvent('updateSections');\r\n                } else {\r\n                    var keyCode = e.keyCode || e.which;\r\n                    if(keyCode == 13 || keyCode == 8 || keyCode == 46) {\r\n                        me.fireEvent('updateSections');\r\n                    }\r\n                }\r\n            }\r\n        },\r\n        commands:{\r\n            'getsections': {\r\n                execCommand: function (cmd, levels) {\r\n                    var levelFn = levels || ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];\r\n\r\n                    for (var i = 0; i < levelFn.length; i++) {\r\n                        if (typeof levelFn[i] == 'string') {\r\n                            levelFn[i] = function(fn){\r\n                                return function(node){\r\n                                    return node.tagName == fn.toUpperCase()\r\n                                };\r\n                            }(levelFn[i]);\r\n                        } else if (typeof levelFn[i] != 'function') {\r\n                            levelFn[i] = function (node) {\r\n                                return null;\r\n                            }\r\n                        }\r\n                    }\r\n                    function getSectionLevel(node) {\r\n                        for (var i = 0; i < levelFn.length; i++) {\r\n                            if (levelFn[i](node)) return i;\r\n                        }\r\n                        return -1;\r\n                    }\r\n\r\n                    var me = this,\r\n                        Directory = getSection({'level':-1, 'title':'root'}),\r\n                        previous = Directory;\r\n\r\n                    function traversal(node, Directory) {\r\n                        var level,\r\n                            tmpSection = null,\r\n                            parent,\r\n                            child,\r\n                            children = node.childNodes;\r\n                        for (var i = 0, len = children.length; i < len; i++) {\r\n                            child = children[i];\r\n                            level = getSectionLevel(child);\r\n                            if (level >= 0) {\r\n                                var address = me.selection.getRange().selectNode(child).createAddress(true).startAddress,\r\n                                    current = getSection({\r\n                                        'tag': child.tagName,\r\n                                        'title': child.innerText || child.textContent || '',\r\n                                        'level': level,\r\n                                        'dom': child,\r\n                                        'startAddress': utils.clone(address, []),\r\n                                        'endAddress': utils.clone(address, []),\r\n                                        'children': []\r\n                                    });\r\n                                previous.nextSection = current;\r\n                                current.previousSection = previous;\r\n                                parent = previous;\r\n                                while(level <= parent.level){\r\n                                    parent = parent.parentSection;\r\n                                }\r\n                                current.parentSection = parent;\r\n                                parent.children.push(current);\r\n                                tmpSection = previous = current;\r\n                            } else {\r\n                                child.nodeType === 1 && traversal(child, Directory);\r\n                                tmpSection && tmpSection.endAddress[tmpSection.endAddress.length - 1] ++;\r\n                            }\r\n                        }\r\n                    }\r\n                    traversal(me.body, Directory);\r\n                    return Directory;\r\n                },\r\n                notNeedUndo: true\r\n            },\r\n            'movesection': {\r\n                execCommand: function (cmd, sourceSection, targetSection, isAfter) {\r\n\r\n                    var me = this,\r\n                        targetAddress,\r\n                        target;\r\n\r\n                    if(!sourceSection || !targetSection || targetSection.level == -1) return;\r\n\r\n                    targetAddress = isAfter ? targetSection.endAddress:targetSection.startAddress;\r\n                    target = getNodeFromAddress(targetAddress, me.body);\r\n\r\n                    /* 判断目标地址是否被源章节包含 */\r\n                    if(!targetAddress || !target || isContainsAddress(sourceSection.startAddress, sourceSection.endAddress, targetAddress)) return;\r\n\r\n                    var startNode = getNodeFromAddress(sourceSection.startAddress, me.body),\r\n                        endNode = getNodeFromAddress(sourceSection.endAddress, me.body),\r\n                        current,\r\n                        nextNode;\r\n\r\n                    if(isAfter) {\r\n                        current = endNode;\r\n                        while ( current && !(domUtils.getPosition( startNode, current ) & domUtils.POSITION_FOLLOWING) ) {\r\n                            nextNode = current.previousSibling;\r\n                            domUtils.insertAfter(target, current);\r\n                            if(current == startNode) break;\r\n                            current = nextNode;\r\n                        }\r\n                    } else {\r\n                        current = startNode;\r\n                        while ( current && !(domUtils.getPosition( current, endNode ) & domUtils.POSITION_FOLLOWING) ) {\r\n                            nextNode = current.nextSibling;\r\n                            target.parentNode.insertBefore(current, target);\r\n                            if(current == endNode) break;\r\n                            current = nextNode;\r\n                        }\r\n                    }\r\n\r\n                    me.fireEvent('updateSections');\r\n\r\n                    /* 获取地址的包含关系 */\r\n                    function isContainsAddress(startAddress, endAddress, addressTarget){\r\n                        var isAfterStartAddress = false,\r\n                            isBeforeEndAddress = false;\r\n                        for(var i = 0; i< startAddress.length; i++){\r\n                            if(i >= addressTarget.length) break;\r\n                            if(addressTarget[i] > startAddress[i]) {\r\n                                isAfterStartAddress = true;\r\n                                break;\r\n                            } else if(addressTarget[i] < startAddress[i]) {\r\n                                break;\r\n                            }\r\n                        }\r\n                        for(var i = 0; i< endAddress.length; i++){\r\n                            if(i >= addressTarget.length) break;\r\n                            if(addressTarget[i] < startAddress[i]) {\r\n                                isBeforeEndAddress = true;\r\n                                break;\r\n                            } else if(addressTarget[i] > startAddress[i]) {\r\n                                break;\r\n                            }\r\n                        }\r\n                        return isAfterStartAddress && isBeforeEndAddress;\r\n                    }\r\n                }\r\n            },\r\n            'deletesection': {\r\n                execCommand: function (cmd, section, keepChildren) {\r\n                    var me = this;\r\n\r\n                    if(!section) return;\r\n\r\n                    function getNodeFromAddress(startAddress) {\r\n                        var current = me.body;\r\n                        for(var i = 0;i < startAddress.length; i++) {\r\n                            if(!current.childNodes) return null;\r\n                            current = current.childNodes[startAddress[i]];\r\n                        }\r\n                        return current;\r\n                    }\r\n\r\n                    var startNode = getNodeFromAddress(section.startAddress),\r\n                        endNode = getNodeFromAddress(section.endAddress),\r\n                        current = startNode,\r\n                        nextNode;\r\n\r\n                    if(!keepChildren) {\r\n                        while ( current && domUtils.inDoc(endNode, me.document) && !(domUtils.getPosition( current, endNode ) & domUtils.POSITION_FOLLOWING) ) {\r\n                            nextNode = current.nextSibling;\r\n                            domUtils.remove(current);\r\n                            current = nextNode;\r\n                        }\r\n                    } else {\r\n                        domUtils.remove(current);\r\n                    }\r\n\r\n                    me.fireEvent('updateSections');\r\n                }\r\n            },\r\n            'selectsection': {\r\n                execCommand: function (cmd, section) {\r\n                    if(!section && !section.dom) return false;\r\n                    var me = this,\r\n                        range = me.selection.getRange(),\r\n                        address = {\r\n                            'startAddress':utils.clone(section.startAddress, []),\r\n                            'endAddress':utils.clone(section.endAddress, [])\r\n                        };\r\n                    address.endAddress[address.endAddress.length - 1]++;\r\n                    range.moveToAddress(address).select().scrollToView();\r\n                    return true;\r\n                },\r\n                notNeedUndo: true\r\n            },\r\n            'scrolltosection': {\r\n                execCommand: function (cmd, section) {\r\n                    if(!section && !section.dom) return false;\r\n                    var me = this,\r\n                        range = me.selection.getRange(),\r\n                        address = {\r\n                            'startAddress':section.startAddress,\r\n                            'endAddress':section.endAddress\r\n                        };\r\n                    address.endAddress[address.endAddress.length - 1]++;\r\n                    range.moveToAddress(address).scrollToView();\r\n                    return true;\r\n                },\r\n                notNeedUndo: true\r\n            }\r\n        }\r\n    }\r\n});\r\n\r\n// plugins/simpleupload.js\r\n/**\r\n * @description\r\n * 简单上传:点击按钮,直接选择文件上传。\r\n * 原 UEditor 作者使用了 form 表单 + iframe 的方式上传\r\n * 但由于同源策略的限制，父页面无法访问跨域的 iframe 内容\r\n * 导致无法获取接口返回的数据，使得单图上传无法在跨域的情况下使用\r\n * 这里改为普通的XHR上传，兼容到IE10+\r\n * @author HaoChuan9421 <hc199421@gmail.com>\r\n * @date 2018-12-20\r\n */\r\nUE.plugin.register('simpleupload', function() {\r\n  var me = this,\r\n    containerBtn,\r\n    timestrap = (+new Date()).toString(36);\r\n\r\n  function initUploadBtn() {\r\n    var w = containerBtn.offsetWidth || 20,\r\n      h = containerBtn.offsetHeight || 20,\r\n      btnStyle = 'display:block;width:' + w + 'px;height:' + h + 'px;overflow:hidden;border:0;margin:0;padding:0;position:absolute;top:0;left:0;filter:alpha(opacity=0);-moz-opacity:0;-khtml-opacity: 0;opacity: 0;cursor:pointer;';\r\n\r\n    var form = document.createElement('form');\r\n    var input = document.createElement('input');\r\n    form.id = 'edui_form_' + timestrap;\r\n    form.enctype = 'multipart/form-data';\r\n    form.style = btnStyle;\r\n    input.id = 'edui_input_' + timestrap;\r\n    input.type = 'file'\r\n    input.accept = 'image/*';\r\n    input.name = me.options.imageFieldName;\r\n    input.style = btnStyle;\r\n    form.appendChild(input);\r\n    containerBtn.appendChild(form);\r\n\r\n    input.addEventListener('change', function(event) {\r\n      if (!input.value) return;\r\n      var loadingId = 'loading_' + (+new Date()).toString(36);\r\n      var imageActionUrl = me.getActionUrl(me.getOpt('imageActionName'));\r\n      var params = utils.serializeParam(me.queryCommandValue('serverparam')) || '';\r\n      var action = utils.formatUrl(imageActionUrl + (imageActionUrl.indexOf('?') == -1 ? '?' : '&') + params);\r\n      var allowFiles = me.getOpt('imageAllowFiles');\r\n      me.focus();\r\n      me.execCommand('inserthtml', '<img class=\"loadingclass\" id=\"' + loadingId + '\" src=\"' + me.options.themePath + me.options.theme + '/images/spacer.gif\" title=\"' + (me.getLang('simpleupload.loading') || '') + '\" >');\r\n\r\n      function showErrorLoader(title) {\r\n        if (loadingId) {\r\n          var loader = me.document.getElementById(loadingId);\r\n          loader && domUtils.remove(loader);\r\n          me.fireEvent('showmessage', {\r\n            'id': loadingId,\r\n            'content': title,\r\n            'type': 'error',\r\n            'timeout': 4000\r\n          });\r\n        }\r\n      }\r\n      /* 判断后端配置是否没有加载成功 */\r\n      if (!me.getOpt('imageActionName')) {\r\n        showErrorLoader(me.getLang('autoupload.errorLoadConfig'));\r\n        return;\r\n      }\r\n      // 判断文件格式是否错误\r\n      var filename = input.value,\r\n        fileext = filename ? filename.substr(filename.lastIndexOf('.')) : '';\r\n      if (!fileext || (allowFiles && (allowFiles.join('') + '.').indexOf(fileext.toLowerCase() + '.') == -1)) {\r\n        showErrorLoader(me.getLang('simpleupload.exceedTypeError'));\r\n        return;\r\n      }\r\n\r\n      var xhr = new XMLHttpRequest()\r\n      xhr.open('post', action, true)\r\n      if (me.options.headers && Object.prototype.toString.apply(me.options.headers) === \"[object Object]\") {\r\n        for (var key in me.options.headers) {\r\n          xhr.setRequestHeader(key, me.options.headers[key])\r\n        }\r\n      }\r\n      xhr.onload = function() {\r\n        if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {\r\n          var res = JSON.parse(xhr.responseText)\r\n          var link = me.options.imageUrlPrefix + res.url;\r\n\r\n          if (res.state == 'SUCCESS' && res.url) {\r\n            loader = me.document.getElementById(loadingId);\r\n            loader.setAttribute('src', link);\r\n            loader.setAttribute('_src', link);\r\n            loader.setAttribute('title', res.title || '');\r\n            loader.setAttribute('alt', res.original || '');\r\n            loader.removeAttribute('id');\r\n            domUtils.removeClasses(loader, 'loadingclass');\r\n            me.fireEvent(\"contentchange\");\r\n          } else {\r\n            showErrorLoader(res.state);\r\n          }\r\n        } else {\r\n          showErrorLoader(me.getLang('simpleupload.loadError'));\r\n        }\r\n      };\r\n      xhr.onerror = function() {\r\n        showErrorLoader(me.getLang('simpleupload.loadError'));\r\n      };\r\n      xhr.send(new FormData(form));\r\n      form.reset();\r\n    })\r\n  }\r\n\r\n  return {\r\n    bindEvents: {\r\n      'ready': function() {\r\n        //设置loading的样式\r\n        utils.cssRule('loading',\r\n          '.loadingclass{display:inline-block;cursor:default;background: url(\\'' +\r\n          this.options.themePath +\r\n          this.options.theme + '/images/loading.gif\\') no-repeat center center transparent;border:1px solid #cccccc;margin-right:1px;height: 22px;width: 22px;}\\n' +\r\n          '.loaderrorclass{display:inline-block;cursor:default;background: url(\\'' +\r\n          this.options.themePath +\r\n          this.options.theme + '/images/loaderror.png\\') no-repeat center center transparent;border:1px solid #cccccc;margin-right:1px;height: 22px;width: 22px;' +\r\n          '}',\r\n          this.document);\r\n      },\r\n      /* 初始化简单上传按钮 */\r\n      'simpleuploadbtnready': function(type, container) {\r\n        containerBtn = container;\r\n        me.afterConfigReady(initUploadBtn);\r\n      }\r\n    },\r\n    outputRule: function(root) {\r\n      utils.each(root.getNodesByTagName('img'), function(n) {\r\n        if (/\\b(loaderrorclass)|(bloaderrorclass)\\b/.test(n.getAttr('class'))) {\r\n          n.parentNode.removeChild(n);\r\n        }\r\n      });\r\n    }\r\n  }\r\n});\r\n\r\n// plugins/serverparam.js\r\n/**\r\n * 服务器提交的额外参数列表设置插件\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\nUE.plugin.register('serverparam', function (){\r\n\r\n    var me = this,\r\n        serverParam = {};\r\n\r\n    return {\r\n        commands:{\r\n            /**\r\n             * 修改服务器提交的额外参数列表,清除所有项\r\n             * @command serverparam\r\n             * @method execCommand\r\n             * @param { String } cmd 命令字符串\r\n             * @example\r\n             * ```javascript\r\n             * editor.execCommand('serverparam');\r\n             * editor.queryCommandValue('serverparam'); //返回空\r\n             * ```\r\n             */\r\n            /**\r\n             * 修改服务器提交的额外参数列表,删除指定项\r\n             * @command serverparam\r\n             * @method execCommand\r\n             * @param { String } cmd 命令字符串\r\n             * @param { String } key 要清除的属性\r\n             * @example\r\n             * ```javascript\r\n             * editor.execCommand('serverparam', 'name'); //删除属性name\r\n             * ```\r\n             */\r\n            /**\r\n             * 修改服务器提交的额外参数列表,使用键值添加项\r\n             * @command serverparam\r\n             * @method execCommand\r\n             * @param { String } cmd 命令字符串\r\n             * @param { String } key 要添加的属性\r\n             * @param { String } value 要添加属性的值\r\n             * @example\r\n             * ```javascript\r\n             * editor.execCommand('serverparam', 'name', 'hello');\r\n             * editor.queryCommandValue('serverparam'); //返回对象 {'name': 'hello'}\r\n             * ```\r\n             */\r\n            /**\r\n             * 修改服务器提交的额外参数列表,传入键值对对象添加多项\r\n             * @command serverparam\r\n             * @method execCommand\r\n             * @param { String } cmd 命令字符串\r\n             * @param { Object } key 传入的键值对对象\r\n             * @example\r\n             * ```javascript\r\n             * editor.execCommand('serverparam', {'name': 'hello'});\r\n             * editor.queryCommandValue('serverparam'); //返回对象 {'name': 'hello'}\r\n             * ```\r\n             */\r\n            /**\r\n             * 修改服务器提交的额外参数列表,使用自定义函数添加多项\r\n             * @command serverparam\r\n             * @method execCommand\r\n             * @param { String } cmd 命令字符串\r\n             * @param { Function } key 自定义获取参数的函数\r\n             * @example\r\n             * ```javascript\r\n             * editor.execCommand('serverparam', function(editor){\r\n             *     return {'key': 'value'};\r\n             * });\r\n             * editor.queryCommandValue('serverparam'); //返回对象 {'key': 'value'}\r\n             * ```\r\n             */\r\n\r\n            /**\r\n             * 获取服务器提交的额外参数列表\r\n             * @command serverparam\r\n             * @method queryCommandValue\r\n             * @param { String } cmd 命令字符串\r\n             * @example\r\n             * ```javascript\r\n             * editor.queryCommandValue( 'serverparam' ); //返回对象 {'key': 'value'}\r\n             * ```\r\n             */\r\n            'serverparam':{\r\n                execCommand:function (cmd, key, value) {\r\n                    if (key === undefined || key === null) { //不传参数,清空列表\r\n                        serverParam = {};\r\n                    } else if (utils.isString(key)) { //传入键值\r\n                        if(value === undefined || value === null) {\r\n                            delete serverParam[key];\r\n                        } else {\r\n                            serverParam[key] = value;\r\n                        }\r\n                    } else if (utils.isObject(key)) { //传入对象,覆盖列表项\r\n                        utils.extend(serverParam, key, true);\r\n                    } else if (utils.isFunction(key)){ //传入函数,添加列表项\r\n                        utils.extend(serverParam, key(), true);\r\n                    }\r\n                },\r\n                queryCommandValue: function(){\r\n                    return serverParam || {};\r\n                }\r\n            }\r\n        }\r\n    }\r\n});\r\n\r\n\r\n// plugins/insertfile.js\r\n/**\r\n * 插入附件\r\n */\r\nUE.plugin.register('insertfile', function (){\r\n\r\n    var me = this;\r\n\r\n    function getFileIcon(url){\r\n        var ext = url.substr(url.lastIndexOf('.') + 1).toLowerCase(),\r\n            maps = {\r\n                \"rar\":\"icon_rar.gif\",\r\n                \"zip\":\"icon_rar.gif\",\r\n                \"tar\":\"icon_rar.gif\",\r\n                \"gz\":\"icon_rar.gif\",\r\n                \"bz2\":\"icon_rar.gif\",\r\n                \"doc\":\"icon_doc.gif\",\r\n                \"docx\":\"icon_doc.gif\",\r\n                \"pdf\":\"icon_pdf.gif\",\r\n                \"mp3\":\"icon_mp3.gif\",\r\n                \"xls\":\"icon_xls.gif\",\r\n                \"chm\":\"icon_chm.gif\",\r\n                \"ppt\":\"icon_ppt.gif\",\r\n                \"pptx\":\"icon_ppt.gif\",\r\n                \"avi\":\"icon_mv.gif\",\r\n                \"rmvb\":\"icon_mv.gif\",\r\n                \"wmv\":\"icon_mv.gif\",\r\n                \"flv\":\"icon_mv.gif\",\r\n                \"swf\":\"icon_mv.gif\",\r\n                \"rm\":\"icon_mv.gif\",\r\n                \"exe\":\"icon_exe.gif\",\r\n                \"psd\":\"icon_psd.gif\",\r\n                \"txt\":\"icon_txt.gif\",\r\n                \"jpg\":\"icon_jpg.gif\",\r\n                \"png\":\"icon_jpg.gif\",\r\n                \"jpeg\":\"icon_jpg.gif\",\r\n                \"gif\":\"icon_jpg.gif\",\r\n                \"ico\":\"icon_jpg.gif\",\r\n                \"bmp\":\"icon_jpg.gif\"\r\n            };\r\n        return maps[ext] ? maps[ext]:maps['txt'];\r\n    }\r\n\r\n    return {\r\n        commands:{\r\n            'insertfile': {\r\n                execCommand: function (command, filelist){\r\n                    filelist = utils.isArray(filelist) ? filelist : [filelist];\r\n\r\n                    var i, item, icon, title,\r\n                        html = '',\r\n                        URL = me.getOpt('UEDITOR_HOME_URL'),\r\n                        iconDir = URL + (URL.substr(URL.length - 1) == '/' ? '':'/') + 'dialogs/attachment/fileTypeImages/';\r\n                    for (i = 0; i < filelist.length; i++) {\r\n                        item = filelist[i];\r\n                        icon = iconDir + getFileIcon(item.url);\r\n                        title = item.title || item.url.substr(item.url.lastIndexOf('/') + 1);\r\n                        html += '<p style=\"line-height: 16px;\">' +\r\n                            '<img style=\"vertical-align: middle; margin-right: 2px;\" src=\"'+ icon + '\" _src=\"' + icon + '\" />' +\r\n                            '<a style=\"font-size:12px; color:#0066cc;\" href=\"' + item.url +'\" title=\"' + title + '\">' + title + '</a>' +\r\n                            '</p>';\r\n                    }\r\n                    me.execCommand('insertHtml', html);\r\n                }\r\n            }\r\n        }\r\n    }\r\n});\r\n\r\n\r\n\r\n\r\n// plugins/xssFilter.js\r\n/**\r\n * @file xssFilter.js\r\n * @desc xss过滤器\r\n * @author robbenmu\r\n */\r\n\r\nUE.plugins.xssFilter = function() {\r\n\r\n\tvar config = UEDITOR_CONFIG;\r\n\tvar whitList = config.whitList;\r\n\r\n\tfunction filter(node) {\r\n\r\n\t\tvar tagName = node.tagName;\r\n\t\tvar attrs = node.attrs;\r\n\r\n\t\tif (!whitList.hasOwnProperty(tagName)) {\r\n\t\t\tnode.parentNode.removeChild(node);\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tUE.utils.each(attrs, function (val, key) {\r\n\r\n\t\t\tif (whitList[tagName].indexOf(key) === -1) {\r\n\t\t\t\tnode.setAttr(key);\r\n\t\t\t}\r\n\t\t});\r\n\t}\r\n\r\n\t// 添加inserthtml\\paste等操作用的过滤规则\r\n\tif (whitList && config.xssFilterRules) {\r\n\t\tthis.options.filterRules = function () {\r\n\r\n\t\t\tvar result = {};\r\n\r\n\t\t\tUE.utils.each(whitList, function(val, key) {\r\n\t\t\t\tresult[key] = function (node) {\r\n\t\t\t\t\treturn filter(node);\r\n\t\t\t\t};\r\n\t\t\t});\r\n\r\n\t\t\treturn result;\r\n\t\t}();\r\n\t}\r\n\r\n\tvar tagList = [];\r\n\r\n\tUE.utils.each(whitList, function (val, key) {\r\n\t\ttagList.push(key);\r\n\t});\r\n\r\n\t// 添加input过滤规则\r\n\t//\r\n\tif (whitList && config.inputXssFilter) {\r\n\t\tthis.addInputRule(function (root) {\r\n\r\n\t\t\troot.traversal(function(node) {\r\n\t\t\t\tif (node.type !== 'element') {\r\n\t\t\t\t\treturn false;\r\n\t\t\t\t}\r\n\t\t\t\tfilter(node);\r\n\t\t\t});\r\n\t\t});\r\n\t}\r\n\t// 添加output过滤规则\r\n\t//\r\n\tif (whitList && config.outputXssFilter) {\r\n\t\tthis.addOutputRule(function (root) {\r\n\r\n\t\t\troot.traversal(function(node) {\r\n\t\t\t\tif (node.type !== 'element') {\r\n\t\t\t\t\treturn false;\r\n\t\t\t\t}\r\n\t\t\t\tfilter(node);\r\n\t\t\t});\r\n\t\t});\r\n\t}\r\n\r\n};\r\n\r\n\r\n// ui/ui.js\r\nvar baidu = baidu || {};\r\nbaidu.editor = baidu.editor || {};\r\nUE.ui = baidu.editor.ui = {};\r\n\r\n// ui/uiutils.js\r\n(function (){\r\n    var browser = baidu.editor.browser,\r\n        domUtils = baidu.editor.dom.domUtils;\r\n\r\n    var magic = '$EDITORUI';\r\n    var root = window[magic] = {};\r\n    var uidMagic = 'ID' + magic;\r\n    var uidCount = 0;\r\n\r\n    var uiUtils = baidu.editor.ui.uiUtils = {\r\n        uid: function (obj){\r\n            return (obj ? obj[uidMagic] || (obj[uidMagic] = ++ uidCount) : ++ uidCount);\r\n        },\r\n        hook: function ( fn, callback ) {\r\n            var dg;\r\n            if (fn && fn._callbacks) {\r\n                dg = fn;\r\n            } else {\r\n                dg = function (){\r\n                    var q;\r\n                    if (fn) {\r\n                        q = fn.apply(this, arguments);\r\n                    }\r\n                    var callbacks = dg._callbacks;\r\n                    var k = callbacks.length;\r\n                    while (k --) {\r\n                        var r = callbacks[k].apply(this, arguments);\r\n                        if (q === undefined) {\r\n                            q = r;\r\n                        }\r\n                    }\r\n                    return q;\r\n                };\r\n                dg._callbacks = [];\r\n            }\r\n            dg._callbacks.push(callback);\r\n            return dg;\r\n        },\r\n        createElementByHtml: function (html){\r\n            var el = document.createElement('div');\r\n            el.innerHTML = html;\r\n            el = el.firstChild;\r\n            el.parentNode.removeChild(el);\r\n            return el;\r\n        },\r\n        getViewportElement: function (){\r\n            return (browser.ie && browser.quirks) ?\r\n                document.body : document.documentElement;\r\n        },\r\n        getClientRect: function (element){\r\n            var bcr;\r\n            //trace  IE6下在控制编辑器显隐时可能会报错，catch一下\r\n            try{\r\n                bcr = element.getBoundingClientRect();\r\n            }catch(e){\r\n                bcr={left:0,top:0,height:0,width:0}\r\n            }\r\n            var rect = {\r\n                left: Math.round(bcr.left),\r\n                top: Math.round(bcr.top),\r\n                height: Math.round(bcr.bottom - bcr.top),\r\n                width: Math.round(bcr.right - bcr.left)\r\n            };\r\n            var doc;\r\n            while ((doc = element.ownerDocument) !== document &&\r\n                (element = domUtils.getWindow(doc).frameElement)) {\r\n                bcr = element.getBoundingClientRect();\r\n                rect.left += bcr.left;\r\n                rect.top += bcr.top;\r\n            }\r\n            rect.bottom = rect.top + rect.height;\r\n            rect.right = rect.left + rect.width;\r\n            return rect;\r\n        },\r\n        getViewportRect: function (){\r\n            var viewportEl = uiUtils.getViewportElement();\r\n            var width = (window.innerWidth || viewportEl.clientWidth) | 0;\r\n            var height = (window.innerHeight ||viewportEl.clientHeight) | 0;\r\n            return {\r\n                left: 0,\r\n                top: 0,\r\n                height: height,\r\n                width: width,\r\n                bottom: height,\r\n                right: width\r\n            };\r\n        },\r\n        setViewportOffset: function (element, offset){\r\n            var rect;\r\n            var fixedLayer = uiUtils.getFixedLayer();\r\n            if (element.parentNode === fixedLayer) {\r\n                element.style.left = offset.left + 'px';\r\n                element.style.top = offset.top + 'px';\r\n            } else {\r\n                domUtils.setViewportOffset(element, offset);\r\n            }\r\n        },\r\n        getEventOffset: function (evt){\r\n            var el = evt.target || evt.srcElement;\r\n            var rect = uiUtils.getClientRect(el);\r\n            var offset = uiUtils.getViewportOffsetByEvent(evt);\r\n            return {\r\n                left: offset.left - rect.left,\r\n                top: offset.top - rect.top\r\n            };\r\n        },\r\n        getViewportOffsetByEvent: function (evt){\r\n            var el = evt.target || evt.srcElement;\r\n            var frameEl = domUtils.getWindow(el).frameElement;\r\n            var offset = {\r\n                left: evt.clientX,\r\n                top: evt.clientY\r\n            };\r\n            if (frameEl && el.ownerDocument !== document) {\r\n                var rect = uiUtils.getClientRect(frameEl);\r\n                offset.left += rect.left;\r\n                offset.top += rect.top;\r\n            }\r\n            return offset;\r\n        },\r\n        setGlobal: function (id, obj){\r\n            root[id] = obj;\r\n            return magic + '[\"' + id  + '\"]';\r\n        },\r\n        unsetGlobal: function (id){\r\n            delete root[id];\r\n        },\r\n        copyAttributes: function (tgt, src){\r\n            var attributes = src.attributes;\r\n            var k = attributes.length;\r\n            while (k --) {\r\n                var attrNode = attributes[k];\r\n                if ( attrNode.nodeName != 'style' && attrNode.nodeName != 'class' && (!browser.ie || attrNode.specified) ) {\r\n                    tgt.setAttribute(attrNode.nodeName, attrNode.nodeValue);\r\n                }\r\n            }\r\n            if (src.className) {\r\n                domUtils.addClass(tgt,src.className);\r\n            }\r\n            if (src.style.cssText) {\r\n                tgt.style.cssText += ';' + src.style.cssText;\r\n            }\r\n        },\r\n        removeStyle: function (el, styleName){\r\n            if (el.style.removeProperty) {\r\n                el.style.removeProperty(styleName);\r\n            } else if (el.style.removeAttribute) {\r\n                el.style.removeAttribute(styleName);\r\n            } else throw '';\r\n        },\r\n        contains: function (elA, elB){\r\n            return elA && elB && (elA === elB ? false : (\r\n                elA.contains ? elA.contains(elB) :\r\n                    elA.compareDocumentPosition(elB) & 16\r\n                ));\r\n        },\r\n        startDrag: function (evt, callbacks,doc){\r\n            var doc = doc || document;\r\n            var startX = evt.clientX;\r\n            var startY = evt.clientY;\r\n            function handleMouseMove(evt){\r\n                var x = evt.clientX - startX;\r\n                var y = evt.clientY - startY;\r\n                callbacks.ondragmove(x, y,evt);\r\n                if (evt.stopPropagation) {\r\n                    evt.stopPropagation();\r\n                } else {\r\n                    evt.cancelBubble = true;\r\n                }\r\n            }\r\n            if (doc.addEventListener) {\r\n                function handleMouseUp(evt){\r\n                    doc.removeEventListener('mousemove', handleMouseMove, true);\r\n                    doc.removeEventListener('mouseup', handleMouseUp, true);\r\n                    window.removeEventListener('mouseup', handleMouseUp, true);\r\n                    callbacks.ondragstop();\r\n                }\r\n                doc.addEventListener('mousemove', handleMouseMove, true);\r\n                doc.addEventListener('mouseup', handleMouseUp, true);\r\n                window.addEventListener('mouseup', handleMouseUp, true);\r\n\r\n                evt.preventDefault();\r\n            } else {\r\n                var elm = evt.srcElement;\r\n                elm.setCapture();\r\n                function releaseCaptrue(){\r\n                    elm.releaseCapture();\r\n                    elm.detachEvent('onmousemove', handleMouseMove);\r\n                    elm.detachEvent('onmouseup', releaseCaptrue);\r\n                    elm.detachEvent('onlosecaptrue', releaseCaptrue);\r\n                    callbacks.ondragstop();\r\n                }\r\n                elm.attachEvent('onmousemove', handleMouseMove);\r\n                elm.attachEvent('onmouseup', releaseCaptrue);\r\n                elm.attachEvent('onlosecaptrue', releaseCaptrue);\r\n                evt.returnValue = false;\r\n            }\r\n            callbacks.ondragstart();\r\n        },\r\n        getFixedLayer: function (){\r\n            var layer = document.getElementById('edui_fixedlayer');\r\n            if (layer == null) {\r\n                layer = document.createElement('div');\r\n                layer.id = 'edui_fixedlayer';\r\n                document.body.appendChild(layer);\r\n                if (browser.ie && browser.version <= 8) {\r\n                    layer.style.position = 'absolute';\r\n                    bindFixedLayer();\r\n                    setTimeout(updateFixedOffset);\r\n                } else {\r\n                    layer.style.position = 'fixed';\r\n                }\r\n                layer.style.left = '0';\r\n                layer.style.top = '0';\r\n                layer.style.width = '0';\r\n                layer.style.height = '0';\r\n            }\r\n            return layer;\r\n        },\r\n        makeUnselectable: function (element){\r\n            if (browser.opera || (browser.ie && browser.version < 9)) {\r\n                element.unselectable = 'on';\r\n                if (element.hasChildNodes()) {\r\n                    for (var i=0; i<element.childNodes.length; i++) {\r\n                        if (element.childNodes[i].nodeType == 1) {\r\n                            uiUtils.makeUnselectable(element.childNodes[i]);\r\n                        }\r\n                    }\r\n                }\r\n            } else {\r\n                if (element.style.MozUserSelect !== undefined) {\r\n                    element.style.MozUserSelect = 'none';\r\n                } else if (element.style.WebkitUserSelect !== undefined) {\r\n                    element.style.WebkitUserSelect = 'none';\r\n                } else if (element.style.KhtmlUserSelect !== undefined) {\r\n                    element.style.KhtmlUserSelect = 'none';\r\n                }\r\n            }\r\n        }\r\n    };\r\n    function updateFixedOffset(){\r\n        var layer = document.getElementById('edui_fixedlayer');\r\n        uiUtils.setViewportOffset(layer, {\r\n            left: 0,\r\n            top: 0\r\n        });\r\n//        layer.style.display = 'none';\r\n//        layer.style.display = 'block';\r\n\r\n        //#trace: 1354\r\n//        setTimeout(updateFixedOffset);\r\n    }\r\n    function bindFixedLayer(adjOffset){\r\n        domUtils.on(window, 'scroll', updateFixedOffset);\r\n        domUtils.on(window, 'resize', baidu.editor.utils.defer(updateFixedOffset, 0, true));\r\n    }\r\n})();\r\n\r\n\r\n// ui/uibase.js\r\n(function () {\r\n    var utils = baidu.editor.utils,\r\n        uiUtils = baidu.editor.ui.uiUtils,\r\n        EventBase = baidu.editor.EventBase,\r\n        UIBase = baidu.editor.ui.UIBase = function () {\r\n        };\r\n\r\n    UIBase.prototype = {\r\n        className:'',\r\n        uiName:'',\r\n        initOptions:function (options) {\r\n            var me = this;\r\n            for (var k in options) {\r\n                me[k] = options[k];\r\n            }\r\n            this.id = this.id || 'edui' + uiUtils.uid();\r\n        },\r\n        initUIBase:function () {\r\n            this._globalKey = utils.unhtml(uiUtils.setGlobal(this.id, this));\r\n        },\r\n        render:function (holder) {\r\n            var html = this.renderHtml();\r\n            var el = uiUtils.createElementByHtml(html);\r\n\r\n            //by xuheng 给每个node添加class\r\n            var list = domUtils.getElementsByTagName(el, \"*\");\r\n            var theme = \"edui-\" + (this.theme || this.editor.options.theme);\r\n            var layer = document.getElementById('edui_fixedlayer');\r\n            for (var i = 0, node; node = list[i++];) {\r\n                domUtils.addClass(node, theme);\r\n            }\r\n            domUtils.addClass(el, theme);\r\n            if(layer){\r\n                layer.className=\"\";\r\n                domUtils.addClass(layer,theme);\r\n            }\r\n\r\n            var seatEl = this.getDom();\r\n            if (seatEl != null) {\r\n                seatEl.parentNode.replaceChild(el, seatEl);\r\n                uiUtils.copyAttributes(el, seatEl);\r\n            } else {\r\n                if (typeof holder == 'string') {\r\n                    holder = document.getElementById(holder);\r\n                }\r\n                holder = holder || uiUtils.getFixedLayer();\r\n                domUtils.addClass(holder, theme);\r\n                holder.appendChild(el);\r\n            }\r\n            this.postRender();\r\n        },\r\n        getDom:function (name) {\r\n            if (!name) {\r\n                return document.getElementById(this.id);\r\n            } else {\r\n                return document.getElementById(this.id + '_' + name);\r\n            }\r\n        },\r\n        postRender:function () {\r\n            this.fireEvent('postrender');\r\n        },\r\n        getHtmlTpl:function () {\r\n            return '';\r\n        },\r\n        formatHtml:function (tpl) {\r\n            var prefix = 'edui-' + this.uiName;\r\n            return (tpl\r\n                .replace(/##/g, this.id)\r\n                .replace(/%%-/g, this.uiName ? prefix + '-' : '')\r\n                .replace(/%%/g, (this.uiName ? prefix : '') + ' ' + this.className)\r\n                .replace(/\\$\\$/g, this._globalKey));\r\n        },\r\n        renderHtml:function () {\r\n            return this.formatHtml(this.getHtmlTpl());\r\n        },\r\n        dispose:function () {\r\n            var box = this.getDom();\r\n            if (box) baidu.editor.dom.domUtils.remove(box);\r\n            uiUtils.unsetGlobal(this.id);\r\n        }\r\n    };\r\n    utils.inherits(UIBase, EventBase);\r\n})();\r\n\r\n\r\n// ui/separator.js\r\n(function (){\r\n    var utils = baidu.editor.utils,\r\n        UIBase = baidu.editor.ui.UIBase,\r\n        Separator = baidu.editor.ui.Separator = function (options){\r\n            this.initOptions(options);\r\n            this.initSeparator();\r\n        };\r\n    Separator.prototype = {\r\n        uiName: 'separator',\r\n        initSeparator: function (){\r\n            this.initUIBase();\r\n        },\r\n        getHtmlTpl: function (){\r\n            return '<div id=\"##\" class=\"edui-box %%\"></div>';\r\n        }\r\n    };\r\n    utils.inherits(Separator, UIBase);\r\n\r\n})();\r\n\r\n\r\n// ui/mask.js\r\n///import core\r\n///import uicore\r\n(function (){\r\n    var utils = baidu.editor.utils,\r\n        domUtils = baidu.editor.dom.domUtils,\r\n        UIBase = baidu.editor.ui.UIBase,\r\n        uiUtils = baidu.editor.ui.uiUtils;\r\n    \r\n    var Mask = baidu.editor.ui.Mask = function (options){\r\n        this.initOptions(options);\r\n        this.initUIBase();\r\n    };\r\n    Mask.prototype = {\r\n        getHtmlTpl: function (){\r\n            return '<div id=\"##\" class=\"edui-mask %%\" onclick=\"return $$._onClick(event, this);\" onmousedown=\"return $$._onMouseDown(event, this);\"></div>';\r\n        },\r\n        postRender: function (){\r\n            var me = this;\r\n            domUtils.on(window, 'resize', function (){\r\n                setTimeout(function (){\r\n                    if (!me.isHidden()) {\r\n                        me._fill();\r\n                    }\r\n                });\r\n            });\r\n        },\r\n        show: function (zIndex){\r\n            this._fill();\r\n            this.getDom().style.display = '';\r\n            this.getDom().style.zIndex = zIndex;\r\n        },\r\n        hide: function (){\r\n            this.getDom().style.display = 'none';\r\n            this.getDom().style.zIndex = '';\r\n        },\r\n        isHidden: function (){\r\n            return this.getDom().style.display == 'none';\r\n        },\r\n        _onMouseDown: function (){\r\n            return false;\r\n        },\r\n        _onClick: function (e, target){\r\n            this.fireEvent('click', e, target);\r\n        },\r\n        _fill: function (){\r\n            var el = this.getDom();\r\n            var vpRect = uiUtils.getViewportRect();\r\n            el.style.width = vpRect.width + 'px';\r\n            el.style.height = vpRect.height + 'px';\r\n        }\r\n    };\r\n    utils.inherits(Mask, UIBase);\r\n})();\r\n\r\n\r\n// ui/popup.js\r\n///import core\r\n///import uicore\r\n(function () {\r\n    var utils = baidu.editor.utils,\r\n        uiUtils = baidu.editor.ui.uiUtils,\r\n        domUtils = baidu.editor.dom.domUtils,\r\n        UIBase = baidu.editor.ui.UIBase,\r\n        Popup = baidu.editor.ui.Popup = function (options){\r\n            this.initOptions(options);\r\n            this.initPopup();\r\n        };\r\n\r\n    var allPopups = [];\r\n    function closeAllPopup( evt,el ){\r\n        for ( var i = 0; i < allPopups.length; i++ ) {\r\n            var pop = allPopups[i];\r\n            if (!pop.isHidden()) {\r\n                if (pop.queryAutoHide(el) !== false) {\r\n                    if(evt&&/scroll/ig.test(evt.type)&&pop.className==\"edui-wordpastepop\")   return;\r\n                    pop.hide();\r\n                }\r\n            }\r\n        }\r\n\r\n        if(allPopups.length)\r\n            pop.editor.fireEvent(\"afterhidepop\");\r\n    }\r\n\r\n    Popup.postHide = closeAllPopup;\r\n\r\n    var ANCHOR_CLASSES = ['edui-anchor-topleft','edui-anchor-topright',\r\n        'edui-anchor-bottomleft','edui-anchor-bottomright'];\r\n    Popup.prototype = {\r\n        SHADOW_RADIUS: 5,\r\n        content: null,\r\n        _hidden: false,\r\n        autoRender: true,\r\n        canSideLeft: true,\r\n        canSideUp: true,\r\n        initPopup: function (){\r\n            this.initUIBase();\r\n            allPopups.push( this );\r\n        },\r\n        getHtmlTpl: function (){\r\n            return '<div id=\"##\" class=\"edui-popup %%\" onmousedown=\"return false;\">' +\r\n                ' <div id=\"##_body\" class=\"edui-popup-body\">' +\r\n                ' <iframe style=\"position:absolute;z-index:-1;left:0;top:0;background-color: transparent;\" frameborder=\"0\" width=\"100%\" height=\"100%\" src=\"about:blank\"></iframe>' +\r\n                ' <div class=\"edui-shadow\"></div>' +\r\n                ' <div id=\"##_content\" class=\"edui-popup-content\">' +\r\n                this.getContentHtmlTpl() +\r\n                '  </div>' +\r\n                ' </div>' +\r\n                '</div>';\r\n        },\r\n        getContentHtmlTpl: function (){\r\n            if(this.content){\r\n                if (typeof this.content == 'string') {\r\n                    return this.content;\r\n                }\r\n                return this.content.renderHtml();\r\n            }else{\r\n                return ''\r\n            }\r\n\r\n        },\r\n        _UIBase_postRender: UIBase.prototype.postRender,\r\n        postRender: function (){\r\n\r\n\r\n            if (this.content instanceof UIBase) {\r\n                this.content.postRender();\r\n            }\r\n\r\n            //捕获鼠标滚轮\r\n            if( this.captureWheel && !this.captured ) {\r\n\r\n                this.captured = true;\r\n\r\n                var winHeight = ( document.documentElement.clientHeight || document.body.clientHeight )  - 80,\r\n                    _height = this.getDom().offsetHeight,\r\n                    _top = uiUtils.getClientRect( this.combox.getDom() ).top,\r\n                    content = this.getDom('content'),\r\n                    ifr = this.getDom('body').getElementsByTagName('iframe'),\r\n                    me = this;\r\n\r\n                ifr.length && ( ifr = ifr[0] );\r\n\r\n                while( _top + _height > winHeight ) {\r\n                    _height -= 30;\r\n                }\r\n                content.style.height = _height + 'px';\r\n                //同步更改iframe高度\r\n                ifr && ( ifr.style.height = _height + 'px' );\r\n\r\n                //阻止在combox上的鼠标滚轮事件, 防止用户的正常操作被误解\r\n                if( window.XMLHttpRequest ) {\r\n\r\n                    domUtils.on( content, ( 'onmousewheel' in document.body ) ? 'mousewheel' :'DOMMouseScroll' , function(e){\r\n\r\n                        if(e.preventDefault) {\r\n                            e.preventDefault();\r\n                        } else {\r\n                            e.returnValue = false;\r\n                        }\r\n\r\n                        if( e.wheelDelta ) {\r\n\r\n                            content.scrollTop -= ( e.wheelDelta / 120 )*60;\r\n\r\n                        } else {\r\n\r\n                            content.scrollTop -= ( e.detail / -3 )*60;\r\n\r\n                        }\r\n\r\n                    });\r\n\r\n                } else {\r\n\r\n                    //ie6\r\n                    domUtils.on( this.getDom(), 'mousewheel' , function(e){\r\n\r\n                        e.returnValue = false;\r\n\r\n                        me.getDom('content').scrollTop -= ( e.wheelDelta / 120 )*60;\r\n\r\n                    });\r\n\r\n                }\r\n\r\n            }\r\n            this.fireEvent('postRenderAfter');\r\n            this.hide(true);\r\n            this._UIBase_postRender();\r\n        },\r\n        _doAutoRender: function (){\r\n            if (!this.getDom() && this.autoRender) {\r\n                this.render();\r\n            }\r\n        },\r\n        mesureSize: function (){\r\n            var box = this.getDom('content');\r\n            return uiUtils.getClientRect(box);\r\n        },\r\n        fitSize: function (){\r\n            if( this.captureWheel && this.sized ) {\r\n                return this.__size;\r\n            }\r\n            this.sized = true;\r\n            var popBodyEl = this.getDom('body');\r\n            popBodyEl.style.width = '';\r\n            popBodyEl.style.height = '';\r\n            var size = this.mesureSize();\r\n            if( this.captureWheel ) {\r\n                popBodyEl.style.width =  -(-20 -size.width) + 'px';\r\n                var height = parseInt( this.getDom('content').style.height, 10 );\r\n                !window.isNaN( height ) && ( size.height = height );\r\n            } else {\r\n                popBodyEl.style.width =  size.width + 'px';\r\n            }\r\n            popBodyEl.style.height = size.height + 'px';\r\n            this.__size = size;\r\n            this.captureWheel && (this.getDom('content').style.overflow = 'auto');\r\n            return size;\r\n        },\r\n        showAnchor: function ( element, hoz ){\r\n            this.showAnchorRect( uiUtils.getClientRect( element ), hoz );\r\n        },\r\n        showAnchorRect: function ( rect, hoz, adj ){\r\n            this._doAutoRender();\r\n            var vpRect = uiUtils.getViewportRect();\r\n            this.getDom().style.visibility = 'hidden';\r\n            this._show();\r\n            var popSize = this.fitSize();\r\n\r\n            var sideLeft, sideUp, left, top;\r\n            if (hoz) {\r\n                sideLeft = this.canSideLeft && (rect.right + popSize.width > vpRect.right && rect.left > popSize.width);\r\n                sideUp = this.canSideUp && (rect.top + popSize.height > vpRect.bottom && rect.bottom > popSize.height);\r\n                left = (sideLeft ? rect.left - popSize.width : rect.right);\r\n                top = (sideUp ? rect.bottom - popSize.height : rect.top);\r\n            } else {\r\n                sideLeft = this.canSideLeft && (rect.right + popSize.width > vpRect.right && rect.left > popSize.width);\r\n                sideUp = this.canSideUp && (rect.top + popSize.height > vpRect.bottom && rect.bottom > popSize.height);\r\n                left = (sideLeft ? rect.right - popSize.width : rect.left);\r\n                top = (sideUp ? rect.top - popSize.height : rect.bottom);\r\n            }\r\n\r\n            var popEl = this.getDom();\r\n            uiUtils.setViewportOffset(popEl, {\r\n                left: left,\r\n                top: top\r\n            });\r\n            domUtils.removeClasses(popEl, ANCHOR_CLASSES);\r\n            popEl.className += ' ' + ANCHOR_CLASSES[(sideUp ? 1 : 0) * 2 + (sideLeft ? 1 : 0)];\r\n            if(this.editor){\r\n                popEl.style.zIndex = this.editor.container.style.zIndex * 1 + 10;\r\n                baidu.editor.ui.uiUtils.getFixedLayer().style.zIndex = popEl.style.zIndex - 1;\r\n            }\r\n            this.getDom().style.visibility = 'visible';\r\n\r\n        },\r\n        showAt: function (offset) {\r\n            var left = offset.left;\r\n            var top = offset.top;\r\n            var rect = {\r\n                left: left,\r\n                top: top,\r\n                right: left,\r\n                bottom: top,\r\n                height: 0,\r\n                width: 0\r\n            };\r\n            this.showAnchorRect(rect, false, true);\r\n        },\r\n        _show: function (){\r\n            if (this._hidden) {\r\n                var box = this.getDom();\r\n                box.style.display = '';\r\n                this._hidden = false;\r\n//                if (box.setActive) {\r\n//                    box.setActive();\r\n//                }\r\n                this.fireEvent('show');\r\n            }\r\n        },\r\n        isHidden: function (){\r\n            return this._hidden;\r\n        },\r\n        show: function (){\r\n            this._doAutoRender();\r\n            this._show();\r\n        },\r\n        hide: function (notNofity){\r\n            if (!this._hidden && this.getDom()) {\r\n                this.getDom().style.display = 'none';\r\n                this._hidden = true;\r\n                if (!notNofity) {\r\n                    this.fireEvent('hide');\r\n                }\r\n            }\r\n        },\r\n        queryAutoHide: function (el){\r\n            return !el || !uiUtils.contains(this.getDom(), el);\r\n        }\r\n    };\r\n    utils.inherits(Popup, UIBase);\r\n    \r\n    domUtils.on( document, 'mousedown', function ( evt ) {\r\n        var el = evt.target || evt.srcElement;\r\n        closeAllPopup( evt,el );\r\n    } );\r\n    domUtils.on( window, 'scroll', function (evt,el) {\r\n        closeAllPopup( evt,el );\r\n    } );\r\n\r\n})();\r\n\r\n\r\n// ui/colorpicker.js\r\n///import core\r\n///import uicore\r\n(function (){\r\n    var utils = baidu.editor.utils,\r\n        UIBase = baidu.editor.ui.UIBase,\r\n        ColorPicker = baidu.editor.ui.ColorPicker = function (options){\r\n            this.initOptions(options);\r\n            this.noColorText = this.noColorText || this.editor.getLang(\"clearColor\");\r\n            this.initUIBase();\r\n        };\r\n\r\n    ColorPicker.prototype = {\r\n        getHtmlTpl: function (){\r\n            return genColorPicker(this.noColorText,this.editor);\r\n        },\r\n        _onTableClick: function (evt){\r\n            var tgt = evt.target || evt.srcElement;\r\n            var color = tgt.getAttribute('data-color');\r\n            if (color) {\r\n                this.fireEvent('pickcolor', color);\r\n            }\r\n        },\r\n        _onTableOver: function (evt){\r\n            var tgt = evt.target || evt.srcElement;\r\n            var color = tgt.getAttribute('data-color');\r\n            if (color) {\r\n                this.getDom('preview').style.backgroundColor = color;\r\n            }\r\n        },\r\n        _onTableOut: function (){\r\n            this.getDom('preview').style.backgroundColor = '';\r\n        },\r\n        _onPickNoColor: function (){\r\n            this.fireEvent('picknocolor');\r\n        }\r\n    };\r\n    utils.inherits(ColorPicker, UIBase);\r\n\r\n    var COLORS = (\r\n        'ffffff,000000,eeece1,1f497d,4f81bd,c0504d,9bbb59,8064a2,4bacc6,f79646,' +\r\n            'f2f2f2,7f7f7f,ddd9c3,c6d9f0,dbe5f1,f2dcdb,ebf1dd,e5e0ec,dbeef3,fdeada,' +\r\n            'd8d8d8,595959,c4bd97,8db3e2,b8cce4,e5b9b7,d7e3bc,ccc1d9,b7dde8,fbd5b5,' +\r\n            'bfbfbf,3f3f3f,938953,548dd4,95b3d7,d99694,c3d69b,b2a2c7,92cddc,fac08f,' +\r\n            'a5a5a5,262626,494429,17365d,366092,953734,76923c,5f497a,31859b,e36c09,' +\r\n            '7f7f7f,0c0c0c,1d1b10,0f243e,244061,632423,4f6128,3f3151,205867,974806,' +\r\n            'c00000,ff0000,ffc000,ffff00,92d050,00b050,00b0f0,0070c0,002060,7030a0,').split(',');\r\n\r\n    function genColorPicker(noColorText,editor){\r\n        var html = '<div id=\"##\" class=\"edui-colorpicker %%\">' +\r\n            '<div class=\"edui-colorpicker-topbar edui-clearfix\">' +\r\n            '<div unselectable=\"on\" id=\"##_preview\" class=\"edui-colorpicker-preview\"></div>' +\r\n            '<div unselectable=\"on\" class=\"edui-colorpicker-nocolor\" onclick=\"$$._onPickNoColor(event, this);\">'+ noColorText +'</div>' +\r\n            '</div>' +\r\n            '<table  class=\"edui-box\" style=\"border-collapse: collapse;\" onmouseover=\"$$._onTableOver(event, this);\" onmouseout=\"$$._onTableOut(event, this);\" onclick=\"return $$._onTableClick(event, this);\" cellspacing=\"0\" cellpadding=\"0\">' +\r\n            '<tr style=\"border-bottom: 1px solid #ddd;font-size: 13px;line-height: 25px;color:#39C;padding-top: 2px\"><td colspan=\"10\">'+editor.getLang(\"themeColor\")+'</td> </tr>'+\r\n            '<tr class=\"edui-colorpicker-tablefirstrow\" >';\r\n        for (var i=0; i<COLORS.length; i++) {\r\n            if (i && i%10 === 0) {\r\n                html += '</tr>'+(i==60?'<tr style=\"border-bottom: 1px solid #ddd;font-size: 13px;line-height: 25px;color:#39C;\"><td colspan=\"10\">'+editor.getLang(\"standardColor\")+'</td></tr>':'')+'<tr'+(i==60?' class=\"edui-colorpicker-tablefirstrow\"':'')+'>';\r\n            }\r\n            html += i<70 ? '<td style=\"padding: 0 2px;\"><a hidefocus title=\"'+COLORS[i]+'\" onclick=\"return false;\" href=\"javascript:\" unselectable=\"on\" class=\"edui-box edui-colorpicker-colorcell\"' +\r\n                ' data-color=\"#'+ COLORS[i] +'\"'+\r\n                ' style=\"background-color:#'+ COLORS[i] +';border:solid #ccc;'+\r\n                (i<10 || i>=60?'border-width:1px;':\r\n                    i>=10&&i<20?'border-width:1px 1px 0 1px;':\r\n\r\n                        'border-width:0 1px 0 1px;')+\r\n                '\"' +\r\n                '></a></td>':'';\r\n        }\r\n        html += '</tr></table></div>';\r\n        return html;\r\n    }\r\n})();\r\n\r\n\r\n// ui/tablepicker.js\r\n///import core\r\n///import uicore\r\n(function (){\r\n    var utils = baidu.editor.utils,\r\n        uiUtils = baidu.editor.ui.uiUtils,\r\n        UIBase = baidu.editor.ui.UIBase;\r\n    \r\n    var TablePicker = baidu.editor.ui.TablePicker = function (options){\r\n        this.initOptions(options);\r\n        this.initTablePicker();\r\n    };\r\n    TablePicker.prototype = {\r\n        defaultNumRows: 10,\r\n        defaultNumCols: 10,\r\n        maxNumRows: 20,\r\n        maxNumCols: 20,\r\n        numRows: 10,\r\n        numCols: 10,\r\n        lengthOfCellSide: 22,\r\n        initTablePicker: function (){\r\n            this.initUIBase();\r\n        },\r\n        getHtmlTpl: function (){\r\n            var me = this;\r\n            return '<div id=\"##\" class=\"edui-tablepicker %%\">' +\r\n                 '<div class=\"edui-tablepicker-body\">' +\r\n                  '<div class=\"edui-infoarea\">' +\r\n                   '<span id=\"##_label\" class=\"edui-label\"></span>' +\r\n                  '</div>' +\r\n                  '<div class=\"edui-pickarea\"' +\r\n                   ' onmousemove=\"$$._onMouseMove(event, this);\"' +\r\n                   ' onmouseover=\"$$._onMouseOver(event, this);\"' +\r\n                   ' onmouseout=\"$$._onMouseOut(event, this);\"' +\r\n                   ' onclick=\"$$._onClick(event, this);\"' +\r\n                  '>' +\r\n                    '<div id=\"##_overlay\" class=\"edui-overlay\"></div>' +\r\n                  '</div>' +\r\n                 '</div>' +\r\n                '</div>';\r\n        },\r\n        _UIBase_render: UIBase.prototype.render,\r\n        render: function (holder){\r\n            this._UIBase_render(holder);\r\n            this.getDom('label').innerHTML = '0'+this.editor.getLang(\"t_row\")+' x 0'+this.editor.getLang(\"t_col\");\r\n        },\r\n        _track: function (numCols, numRows){\r\n            var style = this.getDom('overlay').style;\r\n            var sideLen = this.lengthOfCellSide;\r\n            style.width = numCols * sideLen + 'px';\r\n            style.height = numRows * sideLen + 'px';\r\n            var label = this.getDom('label');\r\n            label.innerHTML = numCols +this.editor.getLang(\"t_col\")+' x ' + numRows + this.editor.getLang(\"t_row\");\r\n            this.numCols = numCols;\r\n            this.numRows = numRows;\r\n        },\r\n        _onMouseOver: function (evt, el){\r\n            var rel = evt.relatedTarget || evt.fromElement;\r\n            if (!uiUtils.contains(el, rel) && el !== rel) {\r\n                this.getDom('label').innerHTML = '0'+this.editor.getLang(\"t_col\")+' x 0'+this.editor.getLang(\"t_row\");\r\n                this.getDom('overlay').style.visibility = '';\r\n            }\r\n        },\r\n        _onMouseOut: function (evt, el){\r\n            var rel = evt.relatedTarget || evt.toElement;\r\n            if (!uiUtils.contains(el, rel) && el !== rel) {\r\n                this.getDom('label').innerHTML = '0'+this.editor.getLang(\"t_col\")+' x 0'+this.editor.getLang(\"t_row\");\r\n                this.getDom('overlay').style.visibility = 'hidden';\r\n            }\r\n        },\r\n        _onMouseMove: function (evt, el){\r\n            var style = this.getDom('overlay').style;\r\n            var offset = uiUtils.getEventOffset(evt);\r\n            var sideLen = this.lengthOfCellSide;\r\n            var numCols = Math.ceil(offset.left / sideLen);\r\n            var numRows = Math.ceil(offset.top / sideLen);\r\n            this._track(numCols, numRows);\r\n        },\r\n        _onClick: function (){\r\n            this.fireEvent('picktable', this.numCols, this.numRows);\r\n        }\r\n    };\r\n    utils.inherits(TablePicker, UIBase);\r\n})();\r\n\r\n\r\n// ui/stateful.js\r\n(function (){\r\n    var browser = baidu.editor.browser,\r\n        domUtils = baidu.editor.dom.domUtils,\r\n        uiUtils = baidu.editor.ui.uiUtils;\r\n    \r\n    var TPL_STATEFUL = 'onmousedown=\"$$.Stateful_onMouseDown(event, this);\"' +\r\n        ' onmouseup=\"$$.Stateful_onMouseUp(event, this);\"' +\r\n        ( browser.ie ? (\r\n        ' onmouseenter=\"$$.Stateful_onMouseEnter(event, this);\"' +\r\n        ' onmouseleave=\"$$.Stateful_onMouseLeave(event, this);\"' )\r\n        : (\r\n        ' onmouseover=\"$$.Stateful_onMouseOver(event, this);\"' +\r\n        ' onmouseout=\"$$.Stateful_onMouseOut(event, this);\"' ));\r\n    \r\n    baidu.editor.ui.Stateful = {\r\n        alwalysHoverable: false,\r\n        target:null,//目标元素和this指向dom不一样\r\n        Stateful_init: function (){\r\n            this._Stateful_dGetHtmlTpl = this.getHtmlTpl;\r\n            this.getHtmlTpl = this.Stateful_getHtmlTpl;\r\n        },\r\n        Stateful_getHtmlTpl: function (){\r\n            var tpl = this._Stateful_dGetHtmlTpl();\r\n            // 使用function避免$转义\r\n            return tpl.replace(/stateful/g, function (){ return TPL_STATEFUL; });\r\n        },\r\n        Stateful_onMouseEnter: function (evt, el){\r\n            this.target=el;\r\n            if (!this.isDisabled() || this.alwalysHoverable) {\r\n                this.addState('hover');\r\n                this.fireEvent('over');\r\n            }\r\n        },\r\n        Stateful_onMouseLeave: function (evt, el){\r\n            if (!this.isDisabled() || this.alwalysHoverable) {\r\n                this.removeState('hover');\r\n                this.removeState('active');\r\n                this.fireEvent('out');\r\n            }\r\n        },\r\n        Stateful_onMouseOver: function (evt, el){\r\n            var rel = evt.relatedTarget;\r\n            if (!uiUtils.contains(el, rel) && el !== rel) {\r\n                this.Stateful_onMouseEnter(evt, el);\r\n            }\r\n        },\r\n        Stateful_onMouseOut: function (evt, el){\r\n            var rel = evt.relatedTarget;\r\n            if (!uiUtils.contains(el, rel) && el !== rel) {\r\n                this.Stateful_onMouseLeave(evt, el);\r\n            }\r\n        },\r\n        Stateful_onMouseDown: function (evt, el){\r\n            if (!this.isDisabled()) {\r\n                this.addState('active');\r\n            }\r\n        },\r\n        Stateful_onMouseUp: function (evt, el){\r\n            if (!this.isDisabled()) {\r\n                this.removeState('active');\r\n            }\r\n        },\r\n        Stateful_postRender: function (){\r\n            if (this.disabled && !this.hasState('disabled')) {\r\n                this.addState('disabled');\r\n            }\r\n        },\r\n        hasState: function (state){\r\n            return domUtils.hasClass(this.getStateDom(), 'edui-state-' + state);\r\n        },\r\n        addState: function (state){\r\n            if (!this.hasState(state)) {\r\n                this.getStateDom().className += ' edui-state-' + state;\r\n            }\r\n        },\r\n        removeState: function (state){\r\n            if (this.hasState(state)) {\r\n                domUtils.removeClasses(this.getStateDom(), ['edui-state-' + state]);\r\n            }\r\n        },\r\n        getStateDom: function (){\r\n            return this.getDom('state');\r\n        },\r\n        isChecked: function (){\r\n            return this.hasState('checked');\r\n        },\r\n        setChecked: function (checked){\r\n            if (!this.isDisabled() && checked) {\r\n                this.addState('checked');\r\n            } else {\r\n                this.removeState('checked');\r\n            }\r\n        },\r\n        isDisabled: function (){\r\n            return this.hasState('disabled');\r\n        },\r\n        setDisabled: function (disabled){\r\n            if (disabled) {\r\n                this.removeState('hover');\r\n                this.removeState('checked');\r\n                this.removeState('active');\r\n                this.addState('disabled');\r\n            } else {\r\n                this.removeState('disabled');\r\n            }\r\n        }\r\n    };\r\n})();\r\n\r\n\r\n// ui/button.js\r\n///import core\r\n///import uicore\r\n///import ui/stateful.js\r\n(function (){\r\n    var utils = baidu.editor.utils,\r\n        UIBase = baidu.editor.ui.UIBase,\r\n        Stateful = baidu.editor.ui.Stateful,\r\n        Button = baidu.editor.ui.Button = function (options){\r\n            if(options.name){\r\n                var btnName = options.name;\r\n                var cssRules = options.cssRules;\r\n                if(!options.className){\r\n                    options.className =  'edui-for-' + btnName;\r\n                }\r\n                options.cssRules = '.edui-default  .edui-for-'+ btnName +' .edui-icon {'+ cssRules +'}'\r\n            }\r\n            this.initOptions(options);\r\n            this.initButton();\r\n        };\r\n    Button.prototype = {\r\n        uiName: 'button',\r\n        label: '',\r\n        title: '',\r\n        showIcon: true,\r\n        showText: true,\r\n        cssRules:'',\r\n        initButton: function (){\r\n            this.initUIBase();\r\n            this.Stateful_init();\r\n            if(this.cssRules){\r\n                utils.cssRule('edui-customize-'+this.name+'-style',this.cssRules);\r\n            }\r\n        },\r\n        getHtmlTpl: function (){\r\n            return '<div id=\"##\" class=\"edui-box %%\">' +\r\n                '<div id=\"##_state\" stateful>' +\r\n                 '<div class=\"%%-wrap\"><div id=\"##_body\" unselectable=\"on\" ' + (this.title ? 'title=\"' + this.title + '\"' : '') +\r\n                 ' class=\"%%-body\" onmousedown=\"return $$._onMouseDown(event, this);\" onclick=\"return $$._onClick(event, this);\">' +\r\n                  (this.showIcon ? '<div class=\"edui-box edui-icon\"></div>' : '') +\r\n                  (this.showText ? '<div class=\"edui-box edui-label\">' + this.label + '</div>' : '') +\r\n                 '</div>' +\r\n                '</div>' +\r\n                '</div></div>';\r\n        },\r\n        postRender: function (){\r\n            this.Stateful_postRender();\r\n            this.setDisabled(this.disabled)\r\n        },\r\n        _onMouseDown: function (e){\r\n            var target = e.target || e.srcElement,\r\n                tagName = target && target.tagName && target.tagName.toLowerCase();\r\n            if (tagName == 'input' || tagName == 'object' || tagName == 'object') {\r\n                return false;\r\n            }\r\n        },\r\n        _onClick: function (){\r\n            if (!this.isDisabled()) {\r\n                this.fireEvent('click');\r\n            }\r\n        },\r\n        setTitle: function(text){\r\n            var label = this.getDom('label');\r\n            label.innerHTML = text;\r\n        }\r\n    };\r\n    utils.inherits(Button, UIBase);\r\n    utils.extend(Button.prototype, Stateful);\r\n\r\n})();\r\n\r\n\r\n// ui/splitbutton.js\r\n///import core\r\n///import uicore\r\n///import ui/stateful.js\r\n(function (){\r\n    var utils = baidu.editor.utils,\r\n        uiUtils = baidu.editor.ui.uiUtils,\r\n        domUtils = baidu.editor.dom.domUtils,\r\n        UIBase = baidu.editor.ui.UIBase,\r\n        Stateful = baidu.editor.ui.Stateful,\r\n        SplitButton = baidu.editor.ui.SplitButton = function (options){\r\n            this.initOptions(options);\r\n            this.initSplitButton();\r\n        };\r\n    SplitButton.prototype = {\r\n        popup: null,\r\n        uiName: 'splitbutton',\r\n        title: '',\r\n        initSplitButton: function (){\r\n            this.initUIBase();\r\n            this.Stateful_init();\r\n            var me = this;\r\n            if (this.popup != null) {\r\n                var popup = this.popup;\r\n                this.popup = null;\r\n                this.setPopup(popup);\r\n            }\r\n        },\r\n        _UIBase_postRender: UIBase.prototype.postRender,\r\n        postRender: function (){\r\n            this.Stateful_postRender();\r\n            this._UIBase_postRender();\r\n        },\r\n        setPopup: function (popup){\r\n            if (this.popup === popup) return;\r\n            if (this.popup != null) {\r\n                this.popup.dispose();\r\n            }\r\n            popup.addListener('show', utils.bind(this._onPopupShow, this));\r\n            popup.addListener('hide', utils.bind(this._onPopupHide, this));\r\n            popup.addListener('postrender', utils.bind(function (){\r\n                popup.getDom('body').appendChild(\r\n                    uiUtils.createElementByHtml('<div id=\"' +\r\n                        this.popup.id + '_bordereraser\" class=\"edui-bordereraser edui-background\" style=\"width:' +\r\n                        (uiUtils.getClientRect(this.getDom()).width + 20) + 'px\"></div>')\r\n                    );\r\n                popup.getDom().className += ' ' + this.className;\r\n            }, this));\r\n            this.popup = popup;\r\n        },\r\n        _onPopupShow: function (){\r\n            this.addState('opened');\r\n        },\r\n        _onPopupHide: function (){\r\n            this.removeState('opened');\r\n        },\r\n        getHtmlTpl: function (){\r\n            return '<div id=\"##\" class=\"edui-box %%\">' +\r\n                '<div '+ (this.title ? 'title=\"' + this.title + '\"' : '') +' id=\"##_state\" stateful><div class=\"%%-body\">' +\r\n                '<div id=\"##_button_body\" class=\"edui-box edui-button-body\" onclick=\"$$._onButtonClick(event, this);\">' +\r\n                '<div class=\"edui-box edui-icon\"></div>' +\r\n                '</div>' +\r\n                '<div class=\"edui-box edui-splitborder\"></div>' +\r\n                '<div class=\"edui-box edui-arrow\" onclick=\"$$._onArrowClick();\"></div>' +\r\n                '</div></div></div>';\r\n        },\r\n        showPopup: function (){\r\n            // 当popup往上弹出的时候，做特殊处理\r\n            var rect = uiUtils.getClientRect(this.getDom());\r\n            rect.top -= this.popup.SHADOW_RADIUS;\r\n            rect.height += this.popup.SHADOW_RADIUS;\r\n            this.popup.showAnchorRect(rect);\r\n        },\r\n        _onArrowClick: function (event, el){\r\n            if (!this.isDisabled()) {\r\n                this.showPopup();\r\n            }\r\n        },\r\n        _onButtonClick: function (){\r\n            if (!this.isDisabled()) {\r\n                this.fireEvent('buttonclick');\r\n            }\r\n        }\r\n    };\r\n    utils.inherits(SplitButton, UIBase);\r\n    utils.extend(SplitButton.prototype, Stateful, true);\r\n\r\n})();\r\n\r\n\r\n// ui/colorbutton.js\r\n///import core\r\n///import uicore\r\n///import ui/colorpicker.js\r\n///import ui/popup.js\r\n///import ui/splitbutton.js\r\n(function (){\r\n    var utils = baidu.editor.utils,\r\n        uiUtils = baidu.editor.ui.uiUtils,\r\n        ColorPicker = baidu.editor.ui.ColorPicker,\r\n        Popup = baidu.editor.ui.Popup,\r\n        SplitButton = baidu.editor.ui.SplitButton,\r\n        ColorButton = baidu.editor.ui.ColorButton = function (options){\r\n            this.initOptions(options);\r\n            this.initColorButton();\r\n        };\r\n    ColorButton.prototype = {\r\n        initColorButton: function (){\r\n            var me = this;\r\n            this.popup = new Popup({\r\n                content: new ColorPicker({\r\n                    noColorText: me.editor.getLang(\"clearColor\"),\r\n                    editor:me.editor,\r\n                    onpickcolor: function (t, color){\r\n                        me._onPickColor(color);\r\n                    },\r\n                    onpicknocolor: function (t, color){\r\n                        me._onPickNoColor(color);\r\n                    }\r\n                }),\r\n                editor:me.editor\r\n            });\r\n            this.initSplitButton();\r\n        },\r\n        _SplitButton_postRender: SplitButton.prototype.postRender,\r\n        postRender: function (){\r\n            this._SplitButton_postRender();\r\n            this.getDom('button_body').appendChild(\r\n                uiUtils.createElementByHtml('<div id=\"' + this.id + '_colorlump\" class=\"edui-colorlump\"></div>')\r\n            );\r\n            this.getDom().className += ' edui-colorbutton';\r\n        },\r\n        setColor: function (color){\r\n            this.getDom('colorlump').style.backgroundColor = color;\r\n            this.color = color;\r\n        },\r\n        _onPickColor: function (color){\r\n            if (this.fireEvent('pickcolor', color) !== false) {\r\n                this.setColor(color);\r\n                this.popup.hide();\r\n            }\r\n        },\r\n        _onPickNoColor: function (color){\r\n            if (this.fireEvent('picknocolor') !== false) {\r\n                this.popup.hide();\r\n            }\r\n        }\r\n    };\r\n    utils.inherits(ColorButton, SplitButton);\r\n\r\n})();\r\n\r\n\r\n// ui/tablebutton.js\r\n///import core\r\n///import uicore\r\n///import ui/popup.js\r\n///import ui/tablepicker.js\r\n///import ui/splitbutton.js\r\n(function (){\r\n    var utils = baidu.editor.utils,\r\n        Popup = baidu.editor.ui.Popup,\r\n        TablePicker = baidu.editor.ui.TablePicker,\r\n        SplitButton = baidu.editor.ui.SplitButton,\r\n        TableButton = baidu.editor.ui.TableButton = function (options){\r\n            this.initOptions(options);\r\n            this.initTableButton();\r\n        };\r\n    TableButton.prototype = {\r\n        initTableButton: function (){\r\n            var me = this;\r\n            this.popup = new Popup({\r\n                content: new TablePicker({\r\n                    editor:me.editor,\r\n                    onpicktable: function (t, numCols, numRows){\r\n                        me._onPickTable(numCols, numRows);\r\n                    }\r\n                }),\r\n                'editor':me.editor\r\n            });\r\n            this.initSplitButton();\r\n        },\r\n        _onPickTable: function (numCols, numRows){\r\n            if (this.fireEvent('picktable', numCols, numRows) !== false) {\r\n                this.popup.hide();\r\n            }\r\n        }\r\n    };\r\n    utils.inherits(TableButton, SplitButton);\r\n\r\n})();\r\n\r\n\r\n// ui/autotypesetpicker.js\r\n///import core\r\n///import uicore\r\n(function () {\r\n    var utils = baidu.editor.utils,\r\n        UIBase = baidu.editor.ui.UIBase;\r\n\r\n    var AutoTypeSetPicker = baidu.editor.ui.AutoTypeSetPicker = function (options) {\r\n        this.initOptions(options);\r\n        this.initAutoTypeSetPicker();\r\n    };\r\n    AutoTypeSetPicker.prototype = {\r\n        initAutoTypeSetPicker:function () {\r\n            this.initUIBase();\r\n        },\r\n        getHtmlTpl:function () {\r\n            var me = this.editor,\r\n                opt = me.options.autotypeset,\r\n                lang = me.getLang(\"autoTypeSet\");\r\n\r\n            var textAlignInputName = 'textAlignValue' + me.uid,\r\n                imageBlockInputName = 'imageBlockLineValue' + me.uid,\r\n                symbolConverInputName = 'symbolConverValue' + me.uid;\r\n\r\n            return '<div id=\"##\" class=\"edui-autotypesetpicker %%\">' +\r\n                '<div class=\"edui-autotypesetpicker-body\">' +\r\n                '<table >' +\r\n                '<tr><td nowrap><input type=\"checkbox\" name=\"mergeEmptyline\" ' + (opt[\"mergeEmptyline\"] ? \"checked\" : \"\" ) + '>' + lang.mergeLine + '</td><td colspan=\"2\"><input type=\"checkbox\" name=\"removeEmptyline\" ' + (opt[\"removeEmptyline\"] ? \"checked\" : \"\" ) + '>' + lang.delLine + '</td></tr>' +\r\n                '<tr><td nowrap><input type=\"checkbox\" name=\"removeClass\" ' + (opt[\"removeClass\"] ? \"checked\" : \"\" ) + '>' + lang.removeFormat + '</td><td colspan=\"2\"><input type=\"checkbox\" name=\"indent\" ' + (opt[\"indent\"] ? \"checked\" : \"\" ) + '>' + lang.indent + '</td></tr>' +\r\n                '<tr>' +\r\n                '<td nowrap><input type=\"checkbox\" name=\"textAlign\" ' + (opt[\"textAlign\"] ? \"checked\" : \"\" ) + '>' + lang.alignment + '</td>' +\r\n                '<td colspan=\"2\" id=\"' + textAlignInputName + '\">' +\r\n                '<input type=\"radio\" name=\"'+ textAlignInputName +'\" value=\"left\" ' + ((opt[\"textAlign\"] && opt[\"textAlign\"] == \"left\") ? \"checked\" : \"\") + '>' + me.getLang(\"justifyleft\") +\r\n                '<input type=\"radio\" name=\"'+ textAlignInputName +'\" value=\"center\" ' + ((opt[\"textAlign\"] && opt[\"textAlign\"] == \"center\") ? \"checked\" : \"\") + '>' + me.getLang(\"justifycenter\") +\r\n                '<input type=\"radio\" name=\"'+ textAlignInputName +'\" value=\"right\" ' + ((opt[\"textAlign\"] && opt[\"textAlign\"] == \"right\") ? \"checked\" : \"\") + '>' + me.getLang(\"justifyright\") +\r\n                '</td>' +\r\n                '</tr>' +\r\n                '<tr>' +\r\n                '<td nowrap><input type=\"checkbox\" name=\"imageBlockLine\" ' + (opt[\"imageBlockLine\"] ? \"checked\" : \"\" ) + '>' + lang.imageFloat + '</td>' +\r\n                '<td nowrap id=\"'+ imageBlockInputName +'\">' +\r\n                '<input type=\"radio\" name=\"'+ imageBlockInputName +'\" value=\"none\" ' + ((opt[\"imageBlockLine\"] && opt[\"imageBlockLine\"] == \"none\") ? \"checked\" : \"\") + '>' + me.getLang(\"default\") +\r\n                '<input type=\"radio\" name=\"'+ imageBlockInputName +'\" value=\"left\" ' + ((opt[\"imageBlockLine\"] && opt[\"imageBlockLine\"] == \"left\") ? \"checked\" : \"\") + '>' + me.getLang(\"justifyleft\") +\r\n                '<input type=\"radio\" name=\"'+ imageBlockInputName +'\" value=\"center\" ' + ((opt[\"imageBlockLine\"] && opt[\"imageBlockLine\"] == \"center\") ? \"checked\" : \"\") + '>' + me.getLang(\"justifycenter\") +\r\n                '<input type=\"radio\" name=\"'+ imageBlockInputName +'\" value=\"right\" ' + ((opt[\"imageBlockLine\"] && opt[\"imageBlockLine\"] == \"right\") ? \"checked\" : \"\") + '>' + me.getLang(\"justifyright\") +\r\n                '</td>' +\r\n                '</tr>' +\r\n                '<tr><td nowrap><input type=\"checkbox\" name=\"clearFontSize\" ' + (opt[\"clearFontSize\"] ? \"checked\" : \"\" ) + '>' + lang.removeFontsize + '</td><td colspan=\"2\"><input type=\"checkbox\" name=\"clearFontFamily\" ' + (opt[\"clearFontFamily\"] ? \"checked\" : \"\" ) + '>' + lang.removeFontFamily + '</td></tr>' +\r\n                '<tr><td nowrap colspan=\"3\"><input type=\"checkbox\" name=\"removeEmptyNode\" ' + (opt[\"removeEmptyNode\"] ? \"checked\" : \"\" ) + '>' + lang.removeHtml + '</td></tr>' +\r\n                '<tr><td nowrap colspan=\"3\"><input type=\"checkbox\" name=\"pasteFilter\" ' + (opt[\"pasteFilter\"] ? \"checked\" : \"\" ) + '>' + lang.pasteFilter + '</td></tr>' +\r\n                '<tr>' +\r\n                '<td nowrap><input type=\"checkbox\" name=\"symbolConver\" ' + (opt[\"bdc2sb\"] || opt[\"tobdc\"] ? \"checked\" : \"\" ) + '>' + lang.symbol + '</td>' +\r\n                '<td id=\"' + symbolConverInputName + '\">' +\r\n                '<input type=\"radio\" name=\"bdc\" value=\"bdc2sb\" ' + (opt[\"bdc2sb\"] ? \"checked\" : \"\" ) + '>' + lang.bdc2sb +\r\n                '<input type=\"radio\" name=\"bdc\" value=\"tobdc\" ' + (opt[\"tobdc\"] ? \"checked\" : \"\" ) + '>' + lang.tobdc + '' +\r\n                '</td>' +\r\n                '<td nowrap align=\"right\"><button >' + lang.run + '</button></td>' +\r\n                '</tr>' +\r\n                '</table>' +\r\n                '</div>' +\r\n                '</div>';\r\n\r\n\r\n        },\r\n        _UIBase_render:UIBase.prototype.render\r\n    };\r\n    utils.inherits(AutoTypeSetPicker, UIBase);\r\n})();\r\n\r\n\r\n// ui/autotypesetbutton.js\r\n///import core\r\n///import uicore\r\n///import ui/popup.js\r\n///import ui/autotypesetpicker.js\r\n///import ui/splitbutton.js\r\n(function (){\r\n    var utils = baidu.editor.utils,\r\n        Popup = baidu.editor.ui.Popup,\r\n        AutoTypeSetPicker = baidu.editor.ui.AutoTypeSetPicker,\r\n        SplitButton = baidu.editor.ui.SplitButton,\r\n        AutoTypeSetButton = baidu.editor.ui.AutoTypeSetButton = function (options){\r\n            this.initOptions(options);\r\n            this.initAutoTypeSetButton();\r\n        };\r\n    function getPara(me){\r\n\r\n        var opt = {},\r\n            cont = me.getDom(),\r\n            editorId = me.editor.uid,\r\n            inputType = null,\r\n            attrName = null,\r\n            ipts = domUtils.getElementsByTagName(cont,\"input\");\r\n        for(var i=ipts.length-1,ipt;ipt=ipts[i--];){\r\n            inputType = ipt.getAttribute(\"type\");\r\n            if(inputType==\"checkbox\"){\r\n                attrName = ipt.getAttribute(\"name\");\r\n                opt[attrName] && delete opt[attrName];\r\n                if(ipt.checked){\r\n                    var attrValue = document.getElementById( attrName + \"Value\" + editorId );\r\n                    if(attrValue){\r\n                        if(/input/ig.test(attrValue.tagName)){\r\n                            opt[attrName] = attrValue.value;\r\n                        } else {\r\n                            var iptChilds = attrValue.getElementsByTagName(\"input\");\r\n                            for(var j=iptChilds.length-1,iptchild;iptchild=iptChilds[j--];){\r\n                                if(iptchild.checked){\r\n                                    opt[attrName] = iptchild.value;\r\n                                    break;\r\n                                }\r\n                            }\r\n                        }\r\n                    } else {\r\n                        opt[attrName] = true;\r\n                    }\r\n                } else {\r\n                    opt[attrName] = false;\r\n                }\r\n            } else {\r\n                opt[ipt.getAttribute(\"value\")] = ipt.checked;\r\n            }\r\n\r\n        }\r\n\r\n        var selects = domUtils.getElementsByTagName(cont,\"select\");\r\n        for(var i=0,si;si=selects[i++];){\r\n            var attr = si.getAttribute('name');\r\n            opt[attr] = opt[attr] ? si.value : '';\r\n        }\r\n\r\n        utils.extend(me.editor.options.autotypeset,opt);\r\n\r\n        me.editor.setPreferences('autotypeset', opt);\r\n    }\r\n\r\n    AutoTypeSetButton.prototype = {\r\n        initAutoTypeSetButton: function (){\r\n\r\n            var me = this;\r\n            this.popup = new Popup({\r\n                //传入配置参数\r\n                content: new AutoTypeSetPicker({editor:me.editor}),\r\n                'editor':me.editor,\r\n                hide : function(){\r\n                    if (!this._hidden && this.getDom()) {\r\n                        getPara(this);\r\n                        this.getDom().style.display = 'none';\r\n                        this._hidden = true;\r\n                        this.fireEvent('hide');\r\n                    }\r\n                }\r\n            });\r\n            var flag = 0;\r\n            this.popup.addListener('postRenderAfter',function(){\r\n                var popupUI = this;\r\n                if(flag)return;\r\n                var cont = this.getDom(),\r\n                    btn = cont.getElementsByTagName('button')[0];\r\n\r\n                btn.onclick = function(){\r\n                    getPara(popupUI);\r\n                    me.editor.execCommand('autotypeset');\r\n                    popupUI.hide()\r\n                };\r\n\r\n                domUtils.on(cont, 'click', function(e) {\r\n                    var target = e.target || e.srcElement,\r\n                        editorId = me.editor.uid;\r\n                    if (target && target.tagName == 'INPUT') {\r\n\r\n                        // 点击图片浮动的checkbox,去除对应的radio\r\n                        if (target.name == 'imageBlockLine' || target.name == 'textAlign' || target.name == 'symbolConver') {\r\n                            var checked = target.checked,\r\n                                radioTd = document.getElementById( target.name + 'Value' + editorId),\r\n                                radios = radioTd.getElementsByTagName('input'),\r\n                                defalutSelect = {\r\n                                    'imageBlockLine': 'none',\r\n                                    'textAlign': 'left',\r\n                                    'symbolConver': 'tobdc'\r\n                                };\r\n\r\n                            for (var i = 0; i < radios.length; i++) {\r\n                                if (checked) {\r\n                                    if (radios[i].value == defalutSelect[target.name]) {\r\n                                        radios[i].checked = 'checked';\r\n                                    }\r\n                                } else {\r\n                                    radios[i].checked = false;\r\n                                }\r\n                            }\r\n                        }\r\n                        // 点击radio,选中对应的checkbox\r\n                        if (target.name == ('imageBlockLineValue' + editorId) || target.name == ('textAlignValue' + editorId) || target.name == 'bdc') {\r\n                            var checkboxs = target.parentNode.previousSibling.getElementsByTagName('input');\r\n                            checkboxs && (checkboxs[0].checked = true);\r\n                        }\r\n\r\n                        getPara(popupUI);\r\n                    }\r\n                });\r\n\r\n                flag = 1;\r\n            });\r\n            this.initSplitButton();\r\n        }\r\n    };\r\n    utils.inherits(AutoTypeSetButton, SplitButton);\r\n\r\n})();\r\n\r\n\r\n// ui/cellalignpicker.js\r\n///import core\r\n///import uicore\r\n(function () {\r\n    var utils = baidu.editor.utils,\r\n        Popup = baidu.editor.ui.Popup,\r\n        Stateful = baidu.editor.ui.Stateful,\r\n        UIBase = baidu.editor.ui.UIBase;\r\n\r\n    /**\r\n     * 该参数将新增一个参数： selected， 参数类型为一个Object， 形如{ 'align': 'center', 'valign': 'top' }， 表示单元格的初始\r\n     * 对齐状态为： 竖直居上，水平居中; 其中 align的取值为：'center', 'left', 'right'; valign的取值为: 'top', 'middle', 'bottom'\r\n     * @update 2013/4/2 hancong03@baidu.com\r\n     */\r\n    var CellAlignPicker = baidu.editor.ui.CellAlignPicker = function (options) {\r\n        this.initOptions(options);\r\n        this.initSelected();\r\n        this.initCellAlignPicker();\r\n    };\r\n    CellAlignPicker.prototype = {\r\n        //初始化选中状态， 该方法将根据传递进来的参数获取到应该选中的对齐方式图标的索引\r\n        initSelected: function(){\r\n\r\n            var status = {\r\n\r\n                valign: {\r\n                    top: 0,\r\n                    middle: 1,\r\n                    bottom: 2\r\n                },\r\n                align: {\r\n                    left: 0,\r\n                    center: 1,\r\n                    right: 2\r\n                },\r\n                count: 3\r\n\r\n                },\r\n                result = -1;\r\n\r\n            if( this.selected ) {\r\n                this.selectedIndex = status.valign[ this.selected.valign ] * status.count + status.align[ this.selected.align ];\r\n            }\r\n\r\n        },\r\n        initCellAlignPicker:function () {\r\n            this.initUIBase();\r\n            this.Stateful_init();\r\n        },\r\n        getHtmlTpl:function () {\r\n\r\n            var alignType = [ 'left', 'center', 'right' ],\r\n                COUNT = 9,\r\n                tempClassName = null,\r\n                tempIndex = -1,\r\n                tmpl = [];\r\n\r\n\r\n            for( var i= 0; i<COUNT; i++ ) {\r\n\r\n                tempClassName = this.selectedIndex === i ? ' class=\"edui-cellalign-selected\" ' : '';\r\n                tempIndex = i % 3;\r\n\r\n                tempIndex === 0 && tmpl.push('<tr>');\r\n\r\n                tmpl.push( '<td index=\"'+ i +'\" ' + tempClassName + ' stateful><div class=\"edui-icon edui-'+ alignType[ tempIndex ] +'\"></div></td>' );\r\n\r\n                tempIndex === 2 && tmpl.push('</tr>');\r\n\r\n            }\r\n\r\n            return '<div id=\"##\" class=\"edui-cellalignpicker %%\">' +\r\n                '<div class=\"edui-cellalignpicker-body\">' +\r\n                '<table onclick=\"$$._onClick(event);\">' +\r\n                tmpl.join('') +\r\n                '</table>' +\r\n                '</div>' +\r\n                '</div>';\r\n        },\r\n        getStateDom: function (){\r\n            return this.target;\r\n        },\r\n        _onClick: function (evt){\r\n            var target= evt.target || evt.srcElement;\r\n            if(/icon/.test(target.className)){\r\n                this.items[target.parentNode.getAttribute(\"index\")].onclick();\r\n                Popup.postHide(evt);\r\n            }\r\n        },\r\n        _UIBase_render:UIBase.prototype.render\r\n    };\r\n    utils.inherits(CellAlignPicker, UIBase);\r\n    utils.extend(CellAlignPicker.prototype, Stateful,true);\r\n})();\r\n\r\n\r\n\r\n\r\n\r\n// ui/pastepicker.js\r\n///import core\r\n///import uicore\r\n(function () {\r\n    var utils = baidu.editor.utils,\r\n        Stateful = baidu.editor.ui.Stateful,\r\n        uiUtils = baidu.editor.ui.uiUtils,\r\n        UIBase = baidu.editor.ui.UIBase;\r\n\r\n    var PastePicker = baidu.editor.ui.PastePicker = function (options) {\r\n        this.initOptions(options);\r\n        this.initPastePicker();\r\n    };\r\n    PastePicker.prototype = {\r\n        initPastePicker:function () {\r\n            this.initUIBase();\r\n            this.Stateful_init();\r\n        },\r\n        getHtmlTpl:function () {\r\n            return '<div class=\"edui-pasteicon\" onclick=\"$$._onClick(this)\"></div>' +\r\n                '<div class=\"edui-pastecontainer\">' +\r\n                '<div class=\"edui-title\">' + this.editor.getLang(\"pasteOpt\") + '</div>' +\r\n                '<div class=\"edui-button\">' +\r\n                '<div title=\"' + this.editor.getLang(\"pasteSourceFormat\") + '\" onclick=\"$$.format(false)\" stateful>' +\r\n                '<div class=\"edui-richtxticon\"></div></div>' +\r\n                '<div title=\"' + this.editor.getLang(\"tagFormat\") + '\" onclick=\"$$.format(2)\" stateful>' +\r\n                '<div class=\"edui-tagicon\"></div></div>' +\r\n                '<div title=\"' + this.editor.getLang(\"pasteTextFormat\") + '\" onclick=\"$$.format(true)\" stateful>' +\r\n                '<div class=\"edui-plaintxticon\"></div></div>' +\r\n                '</div>' +\r\n                '</div>' +\r\n                '</div>'\r\n        },\r\n        getStateDom:function () {\r\n            return this.target;\r\n        },\r\n        format:function (param) {\r\n            this.editor.ui._isTransfer = true;\r\n            this.editor.fireEvent('pasteTransfer', param);\r\n        },\r\n        _onClick:function (cur) {\r\n            var node = domUtils.getNextDomNode(cur),\r\n                screenHt = uiUtils.getViewportRect().height,\r\n                subPop = uiUtils.getClientRect(node);\r\n\r\n            if ((subPop.top + subPop.height) > screenHt)\r\n                node.style.top = (-subPop.height - cur.offsetHeight) + \"px\";\r\n            else\r\n                node.style.top = \"\";\r\n\r\n            if (/hidden/ig.test(domUtils.getComputedStyle(node, \"visibility\"))) {\r\n                node.style.visibility = \"visible\";\r\n                domUtils.addClass(cur, \"edui-state-opened\");\r\n            } else {\r\n                node.style.visibility = \"hidden\";\r\n                domUtils.removeClasses(cur, \"edui-state-opened\")\r\n            }\r\n        },\r\n        _UIBase_render:UIBase.prototype.render\r\n    };\r\n    utils.inherits(PastePicker, UIBase);\r\n    utils.extend(PastePicker.prototype, Stateful, true);\r\n})();\r\n\r\n\r\n\r\n\r\n\r\n\r\n// ui/toolbar.js\r\n(function (){\r\n    var utils = baidu.editor.utils,\r\n        uiUtils = baidu.editor.ui.uiUtils,\r\n        UIBase = baidu.editor.ui.UIBase,\r\n        Toolbar = baidu.editor.ui.Toolbar = function (options){\r\n            this.initOptions(options);\r\n            this.initToolbar();\r\n        };\r\n    Toolbar.prototype = {\r\n        items: null,\r\n        initToolbar: function (){\r\n            this.items = this.items || [];\r\n            this.initUIBase();\r\n        },\r\n        add: function (item,index){\r\n            if(index === undefined){\r\n                this.items.push(item);\r\n            }else{\r\n                this.items.splice(index,0,item)\r\n            }\r\n\r\n        },\r\n        getHtmlTpl: function (){\r\n            var buff = [];\r\n            for (var i=0; i<this.items.length; i++) {\r\n                buff[i] = this.items[i].renderHtml();\r\n            }\r\n            return '<div id=\"##\" class=\"edui-toolbar %%\" onselectstart=\"return false;\" onmousedown=\"return $$._onMouseDown(event, this);\">' +\r\n                buff.join('') +\r\n                '</div>'\r\n        },\r\n        postRender: function (){\r\n            var box = this.getDom();\r\n            for (var i=0; i<this.items.length; i++) {\r\n                this.items[i].postRender();\r\n            }\r\n            uiUtils.makeUnselectable(box);\r\n        },\r\n        _onMouseDown: function (e){\r\n            var target = e.target || e.srcElement,\r\n                tagName = target && target.tagName && target.tagName.toLowerCase();\r\n            if (tagName == 'input' || tagName == 'object' || tagName == 'object') {\r\n                return false;\r\n            }\r\n        }\r\n    };\r\n    utils.inherits(Toolbar, UIBase);\r\n\r\n})();\r\n\r\n\r\n// ui/menu.js\r\n///import core\r\n///import uicore\r\n///import ui\\popup.js\r\n///import ui\\stateful.js\r\n(function () {\r\n    var utils = baidu.editor.utils,\r\n        domUtils = baidu.editor.dom.domUtils,\r\n        uiUtils = baidu.editor.ui.uiUtils,\r\n        UIBase = baidu.editor.ui.UIBase,\r\n        Popup = baidu.editor.ui.Popup,\r\n        Stateful = baidu.editor.ui.Stateful,\r\n        CellAlignPicker = baidu.editor.ui.CellAlignPicker,\r\n\r\n        Menu = baidu.editor.ui.Menu = function (options) {\r\n            this.initOptions(options);\r\n            this.initMenu();\r\n        };\r\n\r\n    var menuSeparator = {\r\n        renderHtml:function () {\r\n            return '<div class=\"edui-menuitem edui-menuseparator\"><div class=\"edui-menuseparator-inner\"></div></div>';\r\n        },\r\n        postRender:function () {\r\n        },\r\n        queryAutoHide:function () {\r\n            return true;\r\n        }\r\n    };\r\n    Menu.prototype = {\r\n        items:null,\r\n        uiName:'menu',\r\n        initMenu:function () {\r\n            this.items = this.items || [];\r\n            this.initPopup();\r\n            this.initItems();\r\n        },\r\n        initItems:function () {\r\n            for (var i = 0; i < this.items.length; i++) {\r\n                var item = this.items[i];\r\n                if (item == '-') {\r\n                    this.items[i] = this.getSeparator();\r\n                } else if (!(item instanceof MenuItem)) {\r\n                    item.editor = this.editor;\r\n                    item.theme = this.editor.options.theme;\r\n                    this.items[i] = this.createItem(item);\r\n                }\r\n            }\r\n        },\r\n        getSeparator:function () {\r\n            return menuSeparator;\r\n        },\r\n        createItem:function (item) {\r\n            //新增一个参数menu, 该参数存储了menuItem所对应的menu引用\r\n            item.menu = this;\r\n            return new MenuItem(item);\r\n        },\r\n        _Popup_getContentHtmlTpl:Popup.prototype.getContentHtmlTpl,\r\n        getContentHtmlTpl:function () {\r\n            if (this.items.length == 0) {\r\n                return this._Popup_getContentHtmlTpl();\r\n            }\r\n            var buff = [];\r\n            for (var i = 0; i < this.items.length; i++) {\r\n                var item = this.items[i];\r\n                buff[i] = item.renderHtml();\r\n            }\r\n            return ('<div class=\"%%-body\">' + buff.join('') + '</div>');\r\n        },\r\n        _Popup_postRender:Popup.prototype.postRender,\r\n        postRender:function () {\r\n            var me = this;\r\n            for (var i = 0; i < this.items.length; i++) {\r\n                var item = this.items[i];\r\n                item.ownerMenu = this;\r\n                item.postRender();\r\n            }\r\n            domUtils.on(this.getDom(), 'mouseover', function (evt) {\r\n                evt = evt || event;\r\n                var rel = evt.relatedTarget || evt.fromElement;\r\n                var el = me.getDom();\r\n                if (!uiUtils.contains(el, rel) && el !== rel) {\r\n                    me.fireEvent('over');\r\n                }\r\n            });\r\n            this._Popup_postRender();\r\n        },\r\n        queryAutoHide:function (el) {\r\n            if (el) {\r\n                if (uiUtils.contains(this.getDom(), el)) {\r\n                    return false;\r\n                }\r\n                for (var i = 0; i < this.items.length; i++) {\r\n                    var item = this.items[i];\r\n                    if (item.queryAutoHide(el) === false) {\r\n                        return false;\r\n                    }\r\n                }\r\n            }\r\n        },\r\n        clearItems:function () {\r\n            for (var i = 0; i < this.items.length; i++) {\r\n                var item = this.items[i];\r\n                clearTimeout(item._showingTimer);\r\n                clearTimeout(item._closingTimer);\r\n                if (item.subMenu) {\r\n                    item.subMenu.destroy();\r\n                }\r\n            }\r\n            this.items = [];\r\n        },\r\n        destroy:function () {\r\n            if (this.getDom()) {\r\n                domUtils.remove(this.getDom());\r\n            }\r\n            this.clearItems();\r\n        },\r\n        dispose:function () {\r\n            this.destroy();\r\n        }\r\n    };\r\n    utils.inherits(Menu, Popup);\r\n\r\n    /**\r\n     * @update 2013/04/03 hancong03 新增一个参数menu, 该参数存储了menuItem所对应的menu引用\r\n     * @type {Function}\r\n     */\r\n    var MenuItem = baidu.editor.ui.MenuItem = function (options) {\r\n        this.initOptions(options);\r\n        this.initUIBase();\r\n        this.Stateful_init();\r\n        if (this.subMenu && !(this.subMenu instanceof Menu)) {\r\n            if (options.className && options.className.indexOf(\"aligntd\") != -1) {\r\n                var me = this;\r\n\r\n                //获取单元格对齐初始状态\r\n                this.subMenu.selected = this.editor.queryCommandValue( 'cellalignment' );\r\n\r\n                this.subMenu = new Popup({\r\n                    content:new CellAlignPicker(this.subMenu),\r\n                    parentMenu:me,\r\n                    editor:me.editor,\r\n                    destroy:function () {\r\n                        if (this.getDom()) {\r\n                            domUtils.remove(this.getDom());\r\n                        }\r\n                    }\r\n                });\r\n                this.subMenu.addListener(\"postRenderAfter\", function () {\r\n                    domUtils.on(this.getDom(), \"mouseover\", function () {\r\n                        me.addState('opened');\r\n                    });\r\n                });\r\n            } else {\r\n                this.subMenu = new Menu(this.subMenu);\r\n            }\r\n        }\r\n    };\r\n    MenuItem.prototype = {\r\n        label:'',\r\n        subMenu:null,\r\n        ownerMenu:null,\r\n        uiName:'menuitem',\r\n        alwalysHoverable:true,\r\n        getHtmlTpl:function () {\r\n            return '<div id=\"##\" class=\"%%\" stateful onclick=\"$$._onClick(event, this);\">' +\r\n                '<div class=\"%%-body\">' +\r\n                this.renderLabelHtml() +\r\n                '</div>' +\r\n                '</div>';\r\n        },\r\n        postRender:function () {\r\n            var me = this;\r\n            this.addListener('over', function () {\r\n                me.ownerMenu.fireEvent('submenuover', me);\r\n                if (me.subMenu) {\r\n                    me.delayShowSubMenu();\r\n                }\r\n            });\r\n            if (this.subMenu) {\r\n                this.getDom().className += ' edui-hassubmenu';\r\n                this.subMenu.render();\r\n                this.addListener('out', function () {\r\n                    me.delayHideSubMenu();\r\n                });\r\n                this.subMenu.addListener('over', function () {\r\n                    clearTimeout(me._closingTimer);\r\n                    me._closingTimer = null;\r\n                    me.addState('opened');\r\n                });\r\n                this.ownerMenu.addListener('hide', function () {\r\n                    me.hideSubMenu();\r\n                });\r\n                this.ownerMenu.addListener('submenuover', function (t, subMenu) {\r\n                    if (subMenu !== me) {\r\n                        me.delayHideSubMenu();\r\n                    }\r\n                });\r\n                this.subMenu._bakQueryAutoHide = this.subMenu.queryAutoHide;\r\n                this.subMenu.queryAutoHide = function (el) {\r\n                    if (el && uiUtils.contains(me.getDom(), el)) {\r\n                        return false;\r\n                    }\r\n                    return this._bakQueryAutoHide(el);\r\n                };\r\n            }\r\n            this.getDom().style.tabIndex = '-1';\r\n            uiUtils.makeUnselectable(this.getDom());\r\n            this.Stateful_postRender();\r\n        },\r\n        delayShowSubMenu:function () {\r\n            var me = this;\r\n            if (!me.isDisabled()) {\r\n                me.addState('opened');\r\n                clearTimeout(me._showingTimer);\r\n                clearTimeout(me._closingTimer);\r\n                me._closingTimer = null;\r\n                me._showingTimer = setTimeout(function () {\r\n                    me.showSubMenu();\r\n                }, 250);\r\n            }\r\n        },\r\n        delayHideSubMenu:function () {\r\n            var me = this;\r\n            if (!me.isDisabled()) {\r\n                me.removeState('opened');\r\n                clearTimeout(me._showingTimer);\r\n                if (!me._closingTimer) {\r\n                    me._closingTimer = setTimeout(function () {\r\n                        if (!me.hasState('opened')) {\r\n                            me.hideSubMenu();\r\n                        }\r\n                        me._closingTimer = null;\r\n                    }, 400);\r\n                }\r\n            }\r\n        },\r\n        renderLabelHtml:function () {\r\n            return '<div class=\"edui-arrow\"></div>' +\r\n                '<div class=\"edui-box edui-icon\"></div>' +\r\n                '<div class=\"edui-box edui-label %%-label\">' + (this.label || '') + '</div>';\r\n        },\r\n        getStateDom:function () {\r\n            return this.getDom();\r\n        },\r\n        queryAutoHide:function (el) {\r\n            if (this.subMenu && this.hasState('opened')) {\r\n                return this.subMenu.queryAutoHide(el);\r\n            }\r\n        },\r\n        _onClick:function (event, this_) {\r\n            if (this.hasState('disabled')) return;\r\n            if (this.fireEvent('click', event, this_) !== false) {\r\n                if (this.subMenu) {\r\n                    this.showSubMenu();\r\n                } else {\r\n                    Popup.postHide(event);\r\n                }\r\n            }\r\n        },\r\n        showSubMenu:function () {\r\n            var rect = uiUtils.getClientRect(this.getDom());\r\n            rect.right -= 5;\r\n            rect.left += 2;\r\n            rect.width -= 7;\r\n            rect.top -= 4;\r\n            rect.bottom += 4;\r\n            rect.height += 8;\r\n            this.subMenu.showAnchorRect(rect, true, true);\r\n        },\r\n        hideSubMenu:function () {\r\n            this.subMenu.hide();\r\n        }\r\n    };\r\n    utils.inherits(MenuItem, UIBase);\r\n    utils.extend(MenuItem.prototype, Stateful, true);\r\n})();\r\n\r\n\r\n// ui/combox.js\r\n///import core\r\n///import uicore\r\n///import ui/menu.js\r\n///import ui/splitbutton.js\r\n(function (){\r\n    // todo: menu和item提成通用list\r\n    var utils = baidu.editor.utils,\r\n        uiUtils = baidu.editor.ui.uiUtils,\r\n        Menu = baidu.editor.ui.Menu,\r\n        SplitButton = baidu.editor.ui.SplitButton,\r\n        Combox = baidu.editor.ui.Combox = function (options){\r\n            this.initOptions(options);\r\n            this.initCombox();\r\n        };\r\n    Combox.prototype = {\r\n        uiName: 'combox',\r\n        onbuttonclick:function () {\r\n            this.showPopup();\r\n        },\r\n        initCombox: function (){\r\n            var me = this;\r\n            this.items = this.items || [];\r\n            for (var i=0; i<this.items.length; i++) {\r\n                var item = this.items[i];\r\n                item.uiName = 'listitem';\r\n                item.index = i;\r\n                item.onclick = function (){\r\n                    me.selectByIndex(this.index);\r\n                };\r\n            }\r\n            this.popup = new Menu({\r\n                items: this.items,\r\n                uiName: 'list',\r\n                editor:this.editor,\r\n                captureWheel: true,\r\n                combox: this\r\n            });\r\n\r\n            this.initSplitButton();\r\n        },\r\n        _SplitButton_postRender: SplitButton.prototype.postRender,\r\n        postRender: function (){\r\n            this._SplitButton_postRender();\r\n            this.setLabel(this.label || '');\r\n            this.setValue(this.initValue || '');\r\n        },\r\n        showPopup: function (){\r\n            var rect = uiUtils.getClientRect(this.getDom());\r\n            rect.top += 1;\r\n            rect.bottom -= 1;\r\n            rect.height -= 2;\r\n            this.popup.showAnchorRect(rect);\r\n        },\r\n        getValue: function (){\r\n            return this.value;\r\n        },\r\n        setValue: function (value){\r\n            var index = this.indexByValue(value);\r\n            if (index != -1) {\r\n                this.selectedIndex = index;\r\n                this.setLabel(this.items[index].label);\r\n                this.value = this.items[index].value;\r\n            } else {\r\n                this.selectedIndex = -1;\r\n                this.setLabel(this.getLabelForUnknowValue(value));\r\n                this.value = value;\r\n            }\r\n        },\r\n        setLabel: function (label){\r\n            this.getDom('button_body').innerHTML = label;\r\n            this.label = label;\r\n        },\r\n        getLabelForUnknowValue: function (value){\r\n            return value;\r\n        },\r\n        indexByValue: function (value){\r\n            for (var i=0; i<this.items.length; i++) {\r\n                if (value == this.items[i].value) {\r\n                    return i;\r\n                }\r\n            }\r\n            return -1;\r\n        },\r\n        getItem: function (index){\r\n            return this.items[index];\r\n        },\r\n        selectByIndex: function (index){\r\n            if (index < this.items.length && this.fireEvent('select', index) !== false) {\r\n                this.selectedIndex = index;\r\n                this.value = this.items[index].value;\r\n                this.setLabel(this.items[index].label);\r\n            }\r\n        }\r\n    };\r\n    utils.inherits(Combox, SplitButton);\r\n})();\r\n\r\n\r\n// ui/dialog.js\r\n///import core\r\n///import uicore\r\n///import ui/mask.js\r\n///import ui/button.js\r\n(function (){\r\n    var utils = baidu.editor.utils,\r\n        domUtils = baidu.editor.dom.domUtils,\r\n        uiUtils = baidu.editor.ui.uiUtils,\r\n        Mask = baidu.editor.ui.Mask,\r\n        UIBase = baidu.editor.ui.UIBase,\r\n        Button = baidu.editor.ui.Button,\r\n        Dialog = baidu.editor.ui.Dialog = function (options){\r\n            if(options.name){\r\n                var name = options.name;\r\n                var cssRules = options.cssRules;\r\n                if(!options.className){\r\n                    options.className =  'edui-for-' + name;\r\n                }\r\n                if(cssRules){\r\n                    options.cssRules = '.edui-default .edui-for-'+ name +' .edui-dialog-content  {'+ cssRules +'}'\r\n                }\r\n            }\r\n            this.initOptions(utils.extend({\r\n                autoReset: true,\r\n                draggable: true,\r\n                onok: function (){},\r\n                oncancel: function (){},\r\n                onclose: function (t, ok){\r\n                    return ok ? this.onok() : this.oncancel();\r\n                },\r\n                //是否控制dialog中的scroll事件， 默认为不阻止\r\n                holdScroll: false\r\n            },options));\r\n            this.initDialog();\r\n        };\r\n    var modalMask;\r\n    var dragMask;\r\n    var activeDialog;\r\n    Dialog.prototype = {\r\n        draggable: false,\r\n        uiName: 'dialog',\r\n        initDialog: function (){\r\n            var me = this,\r\n                theme=this.editor.options.theme;\r\n            if(this.cssRules){\r\n                utils.cssRule('edui-customize-'+this.name+'-style',this.cssRules);\r\n            }\r\n            this.initUIBase();\r\n            this.modalMask = (modalMask || (modalMask = new Mask({\r\n                className: 'edui-dialog-modalmask',\r\n                theme:theme,\r\n                onclick: function (){\r\n                    activeDialog && activeDialog.close(false);\r\n                }\r\n            })));\r\n            this.dragMask = (dragMask || (dragMask = new Mask({\r\n                className: 'edui-dialog-dragmask',\r\n                theme:theme\r\n            })));\r\n            this.closeButton = new Button({\r\n                className: 'edui-dialog-closebutton',\r\n                title: me.closeDialog,\r\n                theme:theme,\r\n                onclick: function (){\r\n                    me.close(false);\r\n                }\r\n            });\r\n\r\n            this.fullscreen && this.initResizeEvent();\r\n\r\n            if (this.buttons) {\r\n                for (var i=0; i<this.buttons.length; i++) {\r\n                    if (!(this.buttons[i] instanceof Button)) {\r\n                        this.buttons[i] = new Button(utils.extend(this.buttons[i],{\r\n                            editor : this.editor\r\n                        },true));\r\n                    }\r\n                }\r\n            }\r\n        },\r\n        initResizeEvent: function () {\r\n\r\n            var me = this;\r\n\r\n            domUtils.on( window, \"resize\", function () {\r\n\r\n                if ( me._hidden || me._hidden === undefined ) {\r\n                    return;\r\n                }\r\n\r\n                if ( me.__resizeTimer ) {\r\n                    window.clearTimeout( me.__resizeTimer );\r\n                }\r\n\r\n                me.__resizeTimer = window.setTimeout( function () {\r\n\r\n                    me.__resizeTimer = null;\r\n\r\n                    var dialogWrapNode = me.getDom(),\r\n                        contentNode = me.getDom('content'),\r\n                        wrapRect = UE.ui.uiUtils.getClientRect( dialogWrapNode ),\r\n                        contentRect = UE.ui.uiUtils.getClientRect( contentNode ),\r\n                        vpRect = uiUtils.getViewportRect();\r\n\r\n                    contentNode.style.width = ( vpRect.width - wrapRect.width + contentRect.width ) + \"px\";\r\n                    contentNode.style.height = ( vpRect.height - wrapRect.height + contentRect.height ) + \"px\";\r\n\r\n                    dialogWrapNode.style.width = vpRect.width + \"px\";\r\n                    dialogWrapNode.style.height = vpRect.height + \"px\";\r\n\r\n                    me.fireEvent( \"resize\" );\r\n\r\n                }, 100 );\r\n\r\n            } );\r\n\r\n        },\r\n        fitSize: function (){\r\n            var popBodyEl = this.getDom('body');\r\n//            if (!(baidu.editor.browser.ie && baidu.editor.browser.version == 7)) {\r\n//                uiUtils.removeStyle(popBodyEl, 'width');\r\n//                uiUtils.removeStyle(popBodyEl, 'height');\r\n//            }\r\n            var size = this.mesureSize();\r\n            popBodyEl.style.width = size.width + 'px';\r\n            popBodyEl.style.height = size.height + 'px';\r\n            return size;\r\n        },\r\n        safeSetOffset: function (offset){\r\n            var me = this;\r\n            var el = me.getDom();\r\n            var vpRect = uiUtils.getViewportRect();\r\n            var rect = uiUtils.getClientRect(el);\r\n            var left = offset.left;\r\n            if (left + rect.width > vpRect.right) {\r\n                left = vpRect.right - rect.width;\r\n            }\r\n            var top = offset.top;\r\n            if (top + rect.height > vpRect.bottom) {\r\n                top = vpRect.bottom - rect.height;\r\n            }\r\n            el.style.left = Math.max(left, 0) + 'px';\r\n            el.style.top = Math.max(top, 0) + 'px';\r\n        },\r\n        showAtCenter: function (){\r\n\r\n            var vpRect = uiUtils.getViewportRect();\r\n\r\n            if ( !this.fullscreen ) {\r\n                this.getDom().style.display = '';\r\n                var popSize = this.fitSize();\r\n                var titleHeight = this.getDom('titlebar').offsetHeight | 0;\r\n                var left = vpRect.width / 2 - popSize.width / 2;\r\n                var top = vpRect.height / 2 - (popSize.height - titleHeight) / 2 - titleHeight;\r\n                var popEl = this.getDom();\r\n                this.safeSetOffset({\r\n                    left: Math.max(left | 0, 0),\r\n                    top: Math.max(top | 0, 0)\r\n                });\r\n                if (!domUtils.hasClass(popEl, 'edui-state-centered')) {\r\n                    popEl.className += ' edui-state-centered';\r\n                }\r\n            } else {\r\n                var dialogWrapNode = this.getDom(),\r\n                    contentNode = this.getDom('content');\r\n\r\n                dialogWrapNode.style.display = \"block\";\r\n\r\n                var wrapRect = UE.ui.uiUtils.getClientRect( dialogWrapNode ),\r\n                    contentRect = UE.ui.uiUtils.getClientRect( contentNode );\r\n                dialogWrapNode.style.left = \"-100000px\";\r\n\r\n                contentNode.style.width = ( vpRect.width - wrapRect.width + contentRect.width ) + \"px\";\r\n                contentNode.style.height = ( vpRect.height - wrapRect.height + contentRect.height ) + \"px\";\r\n\r\n                dialogWrapNode.style.width = vpRect.width + \"px\";\r\n                dialogWrapNode.style.height = vpRect.height + \"px\";\r\n                dialogWrapNode.style.left = 0;\r\n\r\n                //保存环境的overflow值\r\n                this._originalContext = {\r\n                    html: {\r\n                        overflowX: document.documentElement.style.overflowX,\r\n                        overflowY: document.documentElement.style.overflowY\r\n                    },\r\n                    body: {\r\n                        overflowX: document.body.style.overflowX,\r\n                        overflowY: document.body.style.overflowY\r\n                    }\r\n                };\r\n\r\n                document.documentElement.style.overflowX = 'hidden';\r\n                document.documentElement.style.overflowY = 'hidden';\r\n                document.body.style.overflowX = 'hidden';\r\n                document.body.style.overflowY = 'hidden';\r\n\r\n            }\r\n\r\n            this._show();\r\n        },\r\n        getContentHtml: function (){\r\n            var contentHtml = '';\r\n            if (typeof this.content == 'string') {\r\n                contentHtml = this.content;\r\n            } else if (this.iframeUrl) {\r\n                contentHtml = '<span id=\"'+ this.id +'_contmask\" class=\"dialogcontmask\"></span><iframe id=\"'+ this.id +\r\n                    '_iframe\" class=\"%%-iframe\" height=\"100%\" width=\"100%\" frameborder=\"0\" src=\"'+ this.iframeUrl +'\"></iframe>';\r\n            }\r\n            return contentHtml;\r\n        },\r\n        getHtmlTpl: function (){\r\n            var footHtml = '';\r\n\r\n            if (this.buttons) {\r\n                var buff = [];\r\n                for (var i=0; i<this.buttons.length; i++) {\r\n                    buff[i] = this.buttons[i].renderHtml();\r\n                }\r\n                footHtml = '<div class=\"%%-foot\">' +\r\n                     '<div id=\"##_buttons\" class=\"%%-buttons\">' + buff.join('') + '</div>' +\r\n                    '</div>';\r\n            }\r\n\r\n            return '<div id=\"##\" class=\"%%\"><div '+ ( !this.fullscreen ? 'class=\"%%\"' : 'class=\"%%-wrap edui-dialog-fullscreen-flag\"' ) +'><div id=\"##_body\" class=\"%%-body\">' +\r\n                '<div class=\"%%-shadow\"></div>' +\r\n                '<div id=\"##_titlebar\" class=\"%%-titlebar\">' +\r\n                '<div class=\"%%-draghandle\" onmousedown=\"$$._onTitlebarMouseDown(event, this);\">' +\r\n                 '<span class=\"%%-caption\">' + (this.title || '') + '</span>' +\r\n                '</div>' +\r\n                this.closeButton.renderHtml() +\r\n                '</div>' +\r\n                '<div id=\"##_content\" class=\"%%-content\">'+ ( this.autoReset ? '' : this.getContentHtml()) +'</div>' +\r\n                footHtml +\r\n                '</div></div></div>';\r\n        },\r\n        postRender: function (){\r\n            // todo: 保持居中/记住上次关闭位置选项\r\n            if (!this.modalMask.getDom()) {\r\n                this.modalMask.render();\r\n                this.modalMask.hide();\r\n            }\r\n            if (!this.dragMask.getDom()) {\r\n                this.dragMask.render();\r\n                this.dragMask.hide();\r\n            }\r\n            var me = this;\r\n            this.addListener('show', function (){\r\n                me.modalMask.show(this.getDom().style.zIndex - 2);\r\n            });\r\n            this.addListener('hide', function (){\r\n                me.modalMask.hide();\r\n            });\r\n            if (this.buttons) {\r\n                for (var i=0; i<this.buttons.length; i++) {\r\n                    this.buttons[i].postRender();\r\n                }\r\n            }\r\n            domUtils.on(window, 'resize', function (){\r\n                setTimeout(function (){\r\n                    if (!me.isHidden()) {\r\n                        me.safeSetOffset(uiUtils.getClientRect(me.getDom()));\r\n                    }\r\n                });\r\n            });\r\n\r\n            //hold住scroll事件，防止dialog的滚动影响页面\r\n//            if( this.holdScroll ) {\r\n//\r\n//                if( !me.iframeUrl ) {\r\n//                    domUtils.on( document.getElementById( me.id + \"_iframe\"), !browser.gecko ? \"mousewheel\" : \"DOMMouseScroll\", function(e){\r\n//                        domUtils.preventDefault(e);\r\n//                    } );\r\n//                } else {\r\n//                    me.addListener('dialogafterreset', function(){\r\n//                        window.setTimeout(function(){\r\n//                            var iframeWindow = document.getElementById( me.id + \"_iframe\").contentWindow;\r\n//\r\n//                            if( browser.ie ) {\r\n//\r\n//                                var timer = window.setInterval(function(){\r\n//\r\n//                                    if( iframeWindow.document && iframeWindow.document.body ) {\r\n//                                        window.clearInterval( timer );\r\n//                                        timer = null;\r\n//                                        domUtils.on( iframeWindow.document.body, !browser.gecko ? \"mousewheel\" : \"DOMMouseScroll\", function(e){\r\n//                                            domUtils.preventDefault(e);\r\n//                                        } );\r\n//                                    }\r\n//\r\n//                                }, 100);\r\n//\r\n//                            } else {\r\n//                                domUtils.on( iframeWindow, !browser.gecko ? \"mousewheel\" : \"DOMMouseScroll\", function(e){\r\n//                                    domUtils.preventDefault(e);\r\n//                                } );\r\n//                            }\r\n//\r\n//                        }, 1);\r\n//                    });\r\n//                }\r\n//\r\n//            }\r\n            this._hide();\r\n        },\r\n        mesureSize: function (){\r\n            var body = this.getDom('body');\r\n            var width = uiUtils.getClientRect(this.getDom('content')).width;\r\n            var dialogBodyStyle = body.style;\r\n            dialogBodyStyle.width = width;\r\n            return uiUtils.getClientRect(body);\r\n        },\r\n        _onTitlebarMouseDown: function (evt, el){\r\n            if (this.draggable) {\r\n                var rect;\r\n                var vpRect = uiUtils.getViewportRect();\r\n                var me = this;\r\n                uiUtils.startDrag(evt, {\r\n                    ondragstart: function (){\r\n                        rect = uiUtils.getClientRect(me.getDom());\r\n                        me.getDom('contmask').style.visibility = 'visible';\r\n                        me.dragMask.show(me.getDom().style.zIndex - 1);\r\n                    },\r\n                    ondragmove: function (x, y){\r\n                        var left = rect.left + x;\r\n                        var top = rect.top + y;\r\n                        me.safeSetOffset({\r\n                            left: left,\r\n                            top: top\r\n                        });\r\n                    },\r\n                    ondragstop: function (){\r\n                        me.getDom('contmask').style.visibility = 'hidden';\r\n                        domUtils.removeClasses(me.getDom(), ['edui-state-centered']);\r\n                        me.dragMask.hide();\r\n                    }\r\n                });\r\n            }\r\n        },\r\n        reset: function (){\r\n            this.getDom('content').innerHTML = this.getContentHtml();\r\n            this.fireEvent('dialogafterreset');\r\n        },\r\n        _show: function (){\r\n            if (this._hidden) {\r\n                this.getDom().style.display = '';\r\n\r\n                //要高过编辑器的zindxe\r\n                this.editor.container.style.zIndex && (this.getDom().style.zIndex = this.editor.container.style.zIndex * 1 + 10);\r\n                this._hidden = false;\r\n                this.fireEvent('show');\r\n                baidu.editor.ui.uiUtils.getFixedLayer().style.zIndex = this.getDom().style.zIndex - 4;\r\n            }\r\n        },\r\n        isHidden: function (){\r\n            return this._hidden;\r\n        },\r\n        _hide: function (){\r\n            if (!this._hidden) {\r\n                var wrapNode = this.getDom();\r\n                wrapNode.style.display = 'none';\r\n                wrapNode.style.zIndex = '';\r\n                wrapNode.style.width = '';\r\n                wrapNode.style.height = '';\r\n                this._hidden = true;\r\n                this.fireEvent('hide');\r\n            }\r\n        },\r\n        open: function (){\r\n            if (this.autoReset) {\r\n                //有可能还没有渲染\r\n                try{\r\n                    this.reset();\r\n                }catch(e){\r\n                    this.render();\r\n                    this.open()\r\n                }\r\n            }\r\n            this.showAtCenter();\r\n            if (this.iframeUrl) {\r\n                try {\r\n                    this.getDom('iframe').focus();\r\n                } catch(ex){}\r\n            }\r\n            activeDialog = this;\r\n        },\r\n        _onCloseButtonClick: function (evt, el){\r\n            this.close(false);\r\n        },\r\n        close: function (ok){\r\n            if (this.fireEvent('close', ok) !== false) {\r\n                //还原环境\r\n                if ( this.fullscreen ) {\r\n\r\n                    document.documentElement.style.overflowX = this._originalContext.html.overflowX;\r\n                    document.documentElement.style.overflowY = this._originalContext.html.overflowY;\r\n                    document.body.style.overflowX = this._originalContext.body.overflowX;\r\n                    document.body.style.overflowY = this._originalContext.body.overflowY;\r\n                    delete this._originalContext;\r\n\r\n                }\r\n                this._hide();\r\n\r\n                //销毁content\r\n                var content = this.getDom('content');\r\n                var iframe = this.getDom('iframe');\r\n                if (content && iframe) {\r\n                    var doc = iframe.contentDocument || iframe.contentWindow.document;\r\n                    doc && (doc.body.innerHTML = '');\r\n                    domUtils.remove(content);\r\n                }\r\n            }\r\n        }\r\n    };\r\n    utils.inherits(Dialog, UIBase);\r\n})();\r\n\r\n\r\n// ui/menubutton.js\r\n///import core\r\n///import uicore\r\n///import ui/menu.js\r\n///import ui/splitbutton.js\r\n(function (){\r\n    var utils = baidu.editor.utils,\r\n        Menu = baidu.editor.ui.Menu,\r\n        SplitButton = baidu.editor.ui.SplitButton,\r\n        MenuButton = baidu.editor.ui.MenuButton = function (options){\r\n            this.initOptions(options);\r\n            this.initMenuButton();\r\n        };\r\n    MenuButton.prototype = {\r\n        initMenuButton: function (){\r\n            var me = this;\r\n            this.uiName = \"menubutton\";\r\n            this.popup = new Menu({\r\n                items: me.items,\r\n                className: me.className,\r\n                editor:me.editor\r\n            });\r\n            this.popup.addListener('show', function (){\r\n                var list = this;\r\n                for (var i=0; i<list.items.length; i++) {\r\n                    list.items[i].removeState('checked');\r\n                    if (list.items[i].value == me._value) {\r\n                        list.items[i].addState('checked');\r\n                        this.value = me._value;\r\n                    }\r\n                }\r\n            });\r\n            this.initSplitButton();\r\n        },\r\n        setValue : function(value){\r\n            this._value = value;\r\n        }\r\n        \r\n    };\r\n    utils.inherits(MenuButton, SplitButton);\r\n})();\r\n\r\n// ui/multiMenu.js\r\n///import core\r\n///import uicore\r\n ///commands 表情\r\n(function(){\r\n    var utils = baidu.editor.utils,\r\n        Popup = baidu.editor.ui.Popup,\r\n        SplitButton = baidu.editor.ui.SplitButton,\r\n        MultiMenuPop = baidu.editor.ui.MultiMenuPop = function(options){\r\n            this.initOptions(options);\r\n            this.initMultiMenu();\r\n        };\r\n\r\n    MultiMenuPop.prototype = {\r\n        initMultiMenu: function (){\r\n            var me = this;\r\n            this.popup = new Popup({\r\n                content: '',\r\n                editor : me.editor,\r\n                iframe_rendered: false,\r\n                onshow: function (){\r\n                    if (!this.iframe_rendered) {\r\n                        this.iframe_rendered = true;\r\n                        this.getDom('content').innerHTML = '<iframe id=\"'+me.id+'_iframe\" src=\"'+ me.iframeUrl +'\" frameborder=\"0\"></iframe>';\r\n                        me.editor.container.style.zIndex && (this.getDom().style.zIndex = me.editor.container.style.zIndex * 1 + 1);\r\n                    }\r\n                }\r\n               // canSideUp:false,\r\n               // canSideLeft:false\r\n            });\r\n            this.onbuttonclick = function(){\r\n                this.showPopup();\r\n            };\r\n            this.initSplitButton();\r\n        }\r\n\r\n    };\r\n\r\n    utils.inherits(MultiMenuPop, SplitButton);\r\n})();\r\n\r\n\r\n// ui/shortcutmenu.js\r\n(function () {\r\n    var UI = baidu.editor.ui,\r\n        UIBase = UI.UIBase,\r\n        uiUtils = UI.uiUtils,\r\n        utils = baidu.editor.utils,\r\n        domUtils = baidu.editor.dom.domUtils;\r\n\r\n    var allMenus = [],//存储所有快捷菜单\r\n        timeID,\r\n        isSubMenuShow = false;//是否有子pop显示\r\n\r\n    var ShortCutMenu = UI.ShortCutMenu = function (options) {\r\n        this.initOptions (options);\r\n        this.initShortCutMenu ();\r\n    };\r\n\r\n    ShortCutMenu.postHide = hideAllMenu;\r\n\r\n    ShortCutMenu.prototype = {\r\n        isHidden : true ,\r\n        SPACE : 5 ,\r\n        initShortCutMenu : function () {\r\n            this.items = this.items || [];\r\n            this.initUIBase ();\r\n            this.initItems ();\r\n            this.initEvent ();\r\n            allMenus.push (this);\r\n        } ,\r\n        initEvent : function () {\r\n            var me = this,\r\n                doc = me.editor.document;\r\n\r\n            domUtils.on (doc , \"mousemove\" , function (e) {\r\n                if (me.isHidden === false) {\r\n                    //有pop显示就不隐藏快捷菜单\r\n                    if (me.getSubMenuMark () || me.eventType == \"contextmenu\")   return;\r\n\r\n\r\n                    var flag = true,\r\n                        el = me.getDom (),\r\n                        wt = el.offsetWidth,\r\n                        ht = el.offsetHeight,\r\n                        distanceX = wt / 2 + me.SPACE,//距离中心X标准\r\n                        distanceY = ht / 2,//距离中心Y标准\r\n                        x = Math.abs (e.screenX - me.left),//离中心距离横坐标\r\n                        y = Math.abs (e.screenY - me.top);//离中心距离纵坐标\r\n\r\n                    clearTimeout (timeID);\r\n                    timeID = setTimeout (function () {\r\n                        if (y > 0 && y < distanceY) {\r\n                            me.setOpacity (el , \"1\");\r\n                        } else if (y > distanceY && y < distanceY + 70) {\r\n                            me.setOpacity (el , \"0.5\");\r\n                            flag = false;\r\n                        } else if (y > distanceY + 70 && y < distanceY + 140) {\r\n                            me.hide ();\r\n                        }\r\n\r\n                        if (flag && x > 0 && x < distanceX) {\r\n                            me.setOpacity (el , \"1\")\r\n                        } else if (x > distanceX && x < distanceX + 70) {\r\n                            me.setOpacity (el , \"0.5\")\r\n                        } else if (x > distanceX + 70 && x < distanceX + 140) {\r\n                            me.hide ();\r\n                        }\r\n                    });\r\n                }\r\n            });\r\n\r\n            //ie\\ff下 mouseout不准\r\n            if (browser.chrome) {\r\n                domUtils.on (doc , \"mouseout\" , function (e) {\r\n                    var relatedTgt = e.relatedTarget || e.toElement;\r\n\r\n                    if (relatedTgt == null || relatedTgt.tagName == \"HTML\") {\r\n                        me.hide ();\r\n                    }\r\n                });\r\n            }\r\n\r\n            me.editor.addListener (\"afterhidepop\" , function () {\r\n                if (!me.isHidden) {\r\n                    isSubMenuShow = true;\r\n                }\r\n            });\r\n\r\n        } ,\r\n        initItems : function () {\r\n            if (utils.isArray (this.items)) {\r\n                for (var i = 0, len = this.items.length ; i < len ; i++) {\r\n                    var item = this.items[i].toLowerCase ();\r\n\r\n                    if (UI[item]) {\r\n                        this.items[i] = new UI[item] (this.editor);\r\n                        this.items[i].className += \" edui-shortcutsubmenu \";\r\n                    }\r\n                }\r\n            }\r\n        } ,\r\n        setOpacity : function (el , value) {\r\n            if (browser.ie && browser.version < 9) {\r\n                el.style.filter = \"alpha(opacity = \" + parseFloat (value) * 100 + \");\"\r\n            } else {\r\n                el.style.opacity = value;\r\n            }\r\n        } ,\r\n        getSubMenuMark : function () {\r\n            isSubMenuShow = false;\r\n            var layerEle = uiUtils.getFixedLayer ();\r\n            var list = domUtils.getElementsByTagName (layerEle , \"div\" , function (node) {\r\n                return domUtils.hasClass (node , \"edui-shortcutsubmenu edui-popup\")\r\n            });\r\n\r\n            for (var i = 0, node ; node = list[i++] ;) {\r\n                if (node.style.display != \"none\") {\r\n                    isSubMenuShow = true;\r\n                }\r\n            }\r\n            return isSubMenuShow;\r\n        } ,\r\n        show : function (e , hasContextmenu) {\r\n            var me = this,\r\n                offset = {},\r\n                el = this.getDom (),\r\n                fixedlayer = uiUtils.getFixedLayer ();\r\n\r\n            function setPos (offset) {\r\n                if (offset.left < 0) {\r\n                    offset.left = 0;\r\n                }\r\n                if (offset.top < 0) {\r\n                    offset.top = 0;\r\n                }\r\n                el.style.cssText = \"position:absolute;left:\" + offset.left + \"px;top:\" + offset.top + \"px;\";\r\n            }\r\n\r\n            function setPosByCxtMenu (menu) {\r\n                if (!menu.tagName) {\r\n                    menu = menu.getDom ();\r\n                }\r\n                offset.left = parseInt (menu.style.left);\r\n                offset.top = parseInt (menu.style.top);\r\n                offset.top -= el.offsetHeight + 15;\r\n                setPos (offset);\r\n            }\r\n\r\n\r\n            me.eventType = e.type;\r\n            el.style.cssText = \"display:block;left:-9999px\";\r\n\r\n            if (e.type == \"contextmenu\" && hasContextmenu) {\r\n                var menu = domUtils.getElementsByTagName (fixedlayer , \"div\" , \"edui-contextmenu\")[0];\r\n                if (menu) {\r\n                    setPosByCxtMenu (menu)\r\n                } else {\r\n                    me.editor.addListener (\"aftershowcontextmenu\" , function (type , menu) {\r\n                        setPosByCxtMenu (menu);\r\n                    });\r\n                }\r\n            } else {\r\n                offset = uiUtils.getViewportOffsetByEvent (e);\r\n                offset.top -= el.offsetHeight + me.SPACE;\r\n                offset.left += me.SPACE + 20;\r\n                setPos (offset);\r\n                me.setOpacity (el , 0.2);\r\n            }\r\n\r\n\r\n            me.isHidden = false;\r\n            me.left = e.screenX + el.offsetWidth / 2 - me.SPACE;\r\n            me.top = e.screenY - (el.offsetHeight / 2) - me.SPACE;\r\n\r\n            if (me.editor) {\r\n                el.style.zIndex = me.editor.container.style.zIndex * 1 + 10;\r\n                fixedlayer.style.zIndex = el.style.zIndex - 1;\r\n            }\r\n        } ,\r\n        hide : function () {\r\n            if (this.getDom ()) {\r\n                this.getDom ().style.display = \"none\";\r\n            }\r\n            this.isHidden = true;\r\n        } ,\r\n        postRender : function () {\r\n            if (utils.isArray (this.items)) {\r\n                for (var i = 0, item ; item = this.items[i++] ;) {\r\n                    item.postRender ();\r\n                }\r\n            }\r\n        } ,\r\n        getHtmlTpl : function () {\r\n            var buff;\r\n            if (utils.isArray (this.items)) {\r\n                buff = [];\r\n                for (var i = 0 ; i < this.items.length ; i++) {\r\n                    buff[i] = this.items[i].renderHtml ();\r\n                }\r\n                buff = buff.join (\"\");\r\n            } else {\r\n                buff = this.items;\r\n            }\r\n\r\n            return '<div id=\"##\" class=\"%% edui-toolbar\" data-src=\"shortcutmenu\" onmousedown=\"return false;\" onselectstart=\"return false;\" >' +\r\n                buff +\r\n                '</div>';\r\n        }\r\n    };\r\n\r\n    utils.inherits (ShortCutMenu , UIBase);\r\n\r\n    function hideAllMenu (e) {\r\n        var tgt = e.target || e.srcElement,\r\n            cur = domUtils.findParent (tgt , function (node) {\r\n                return domUtils.hasClass (node , \"edui-shortcutmenu\") || domUtils.hasClass (node , \"edui-popup\");\r\n            } , true);\r\n\r\n        if (!cur) {\r\n            for (var i = 0, menu ; menu = allMenus[i++] ;) {\r\n                menu.hide ()\r\n            }\r\n        }\r\n    }\r\n\r\n    domUtils.on (document , 'mousedown' , function (e) {\r\n        hideAllMenu (e);\r\n    });\r\n\r\n    domUtils.on (window , 'scroll' , function (e) {\r\n        hideAllMenu (e);\r\n    });\r\n\r\n}) ();\r\n\r\n\r\n// ui/breakline.js\r\n(function (){\r\n    var utils = baidu.editor.utils,\r\n        UIBase = baidu.editor.ui.UIBase,\r\n        Breakline = baidu.editor.ui.Breakline = function (options){\r\n            this.initOptions(options);\r\n            this.initSeparator();\r\n        };\r\n    Breakline.prototype = {\r\n        uiName: 'Breakline',\r\n        initSeparator: function (){\r\n            this.initUIBase();\r\n        },\r\n        getHtmlTpl: function (){\r\n            return '<br/>';\r\n        }\r\n    };\r\n    utils.inherits(Breakline, UIBase);\r\n\r\n})();\r\n\r\n\r\n// ui/message.js\r\n///import core\r\n///import uicore\r\n(function () {\r\n    var utils = baidu.editor.utils,\r\n        domUtils = baidu.editor.dom.domUtils,\r\n        UIBase = baidu.editor.ui.UIBase,\r\n        Message = baidu.editor.ui.Message = function (options){\r\n            this.initOptions(options);\r\n            this.initMessage();\r\n        };\r\n\r\n    Message.prototype = {\r\n        initMessage: function (){\r\n            this.initUIBase();\r\n        },\r\n        getHtmlTpl: function (){\r\n            return '<div id=\"##\" class=\"edui-message %%\">' +\r\n            ' <div id=\"##_closer\" class=\"edui-message-closer\">×</div>' +\r\n            ' <div id=\"##_body\" class=\"edui-message-body edui-message-type-info\">' +\r\n            ' <iframe style=\"position:absolute;z-index:-1;left:0;top:0;background-color: transparent;\" frameborder=\"0\" width=\"100%\" height=\"100%\" src=\"about:blank\"></iframe>' +\r\n            ' <div class=\"edui-shadow\"></div>' +\r\n            ' <div id=\"##_content\" class=\"edui-message-content\">' +\r\n            '  </div>' +\r\n            ' </div>' +\r\n            '</div>';\r\n        },\r\n        reset: function(opt){\r\n            var me = this;\r\n            if (!opt.keepshow) {\r\n                clearTimeout(this.timer);\r\n                me.timer = setTimeout(function(){\r\n                    me.hide();\r\n                }, opt.timeout || 4000);\r\n            }\r\n\r\n            opt.content !== undefined && me.setContent(opt.content);\r\n            opt.type !== undefined && me.setType(opt.type);\r\n\r\n            me.show();\r\n        },\r\n        postRender: function(){\r\n            var me = this,\r\n                closer = this.getDom('closer');\r\n            closer && domUtils.on(closer, 'click', function(){\r\n                me.hide();\r\n            });\r\n        },\r\n        setContent: function(content){\r\n            this.getDom('content').innerHTML = content;\r\n        },\r\n        setType: function(type){\r\n            type = type || 'info';\r\n            var body = this.getDom('body');\r\n            body.className = body.className.replace(/edui-message-type-[\\w-]+/, 'edui-message-type-' + type);\r\n        },\r\n        getContent: function(){\r\n            return this.getDom('content').innerHTML;\r\n        },\r\n        getType: function(){\r\n            var arr = this.getDom('body').match(/edui-message-type-([\\w-]+)/);\r\n            return arr ? arr[1]:'';\r\n        },\r\n        show: function (){\r\n            this.getDom().style.display = 'block';\r\n        },\r\n        hide: function (){\r\n            var dom = this.getDom();\r\n            if (dom) {\r\n                dom.style.display = 'none';\r\n                dom.parentNode && dom.parentNode.removeChild(dom);\r\n            }\r\n        }\r\n    };\r\n\r\n    utils.inherits(Message, UIBase);\r\n\r\n})();\r\n\r\n\r\n// adapter/editorui.js\r\n//ui跟编辑器的适配層\r\n//那个按钮弹出是dialog，是下拉筐等都是在这个js中配置\r\n//自己写的ui也要在这里配置，放到baidu.editor.ui下边，当编辑器实例化的时候会根据ueditor.config中的toolbars找到相应的进行实例化\r\n(function () {\r\n    var utils = baidu.editor.utils;\r\n    var editorui = baidu.editor.ui;\r\n    var _Dialog = editorui.Dialog;\r\n    editorui.buttons = {};\r\n\r\n    editorui.Dialog = function (options) {\r\n        var dialog = new _Dialog(options);\r\n        dialog.addListener('hide', function () {\r\n\r\n            if (dialog.editor) {\r\n                var editor = dialog.editor;\r\n                try {\r\n                    if (browser.gecko) {\r\n                        var y = editor.window.scrollY,\r\n                            x = editor.window.scrollX;\r\n                        editor.body.focus();\r\n                        editor.window.scrollTo(x, y);\r\n                    } else {\r\n                        editor.focus();\r\n                    }\r\n\r\n\r\n                } catch (ex) {\r\n                }\r\n            }\r\n        });\r\n        return dialog;\r\n    };\r\n\r\n    var iframeUrlMap = {\r\n        'anchor':'~/dialogs/anchor/anchor.html',\r\n        'insertimage':'~/dialogs/image/image.html',\r\n        'link':'~/dialogs/link/link.html',\r\n        'spechars':'~/dialogs/spechars/spechars.html',\r\n        'searchreplace':'~/dialogs/searchreplace/searchreplace.html',\r\n        'map':'~/dialogs/map/map.html',\r\n        'gmap':'~/dialogs/gmap/gmap.html',\r\n        'insertvideo':'~/dialogs/video/video.html',\r\n        'help':'~/dialogs/help/help.html',\r\n        'preview':'~/dialogs/preview/preview.html',\r\n        'emotion':'~/dialogs/emotion/emotion.html',\r\n        'wordimage':'~/dialogs/wordimage/wordimage.html',\r\n        'attachment':'~/dialogs/attachment/attachment.html',\r\n        'insertframe':'~/dialogs/insertframe/insertframe.html',\r\n        'edittip':'~/dialogs/table/edittip.html',\r\n        'edittable':'~/dialogs/table/edittable.html',\r\n        'edittd':'~/dialogs/table/edittd.html',\r\n        'webapp':'~/dialogs/webapp/webapp.html',\r\n        'snapscreen':'~/dialogs/snapscreen/snapscreen.html',\r\n        'scrawl':'~/dialogs/scrawl/scrawl.html',\r\n        'music':'~/dialogs/music/music.html',\r\n        'template':'~/dialogs/template/template.html',\r\n        'background':'~/dialogs/background/background.html',\r\n        'charts': '~/dialogs/charts/charts.html'\r\n    };\r\n    //为工具栏添加按钮，以下都是统一的按钮触发命令，所以写在一起\r\n    var btnCmds = ['undo', 'redo', 'formatmatch',\r\n        'bold', 'italic', 'underline', 'fontborder', 'touppercase', 'tolowercase',\r\n        'strikethrough', 'subscript', 'superscript', 'source', 'indent', 'outdent',\r\n        'blockquote', 'pasteplain', 'pagebreak',\r\n        'selectall', 'print','horizontal', 'removeformat', 'time', 'date', 'unlink',\r\n        'insertparagraphbeforetable', 'insertrow', 'insertcol', 'mergeright', 'mergedown', 'deleterow',\r\n        'deletecol', 'splittorows', 'splittocols', 'splittocells', 'mergecells', 'deletetable', 'drafts'];\r\n\r\n    for (var i = 0, ci; ci = btnCmds[i++];) {\r\n        ci = ci.toLowerCase();\r\n        editorui[ci] = function (cmd) {\r\n            return function (editor) {\r\n                var ui = new editorui.Button({\r\n                    className:'edui-for-' + cmd,\r\n                    title:editor.options.labelMap[cmd] || editor.getLang(\"labelMap.\" + cmd) || '',\r\n                    onclick:function () {\r\n                        editor.execCommand(cmd);\r\n                    },\r\n                    theme:editor.options.theme,\r\n                    showText:false\r\n                });\r\n                editorui.buttons[cmd] = ui;\r\n                editor.addListener('selectionchange', function (type, causeByUi, uiReady) {\r\n                    var state = editor.queryCommandState(cmd);\r\n                    if (state == -1) {\r\n                        ui.setDisabled(true);\r\n                        ui.setChecked(false);\r\n                    } else {\r\n                        if (!uiReady) {\r\n                            ui.setDisabled(false);\r\n                            ui.setChecked(state);\r\n                        }\r\n                    }\r\n                });\r\n                return ui;\r\n            };\r\n        }(ci);\r\n    }\r\n\r\n    //清除文档\r\n    editorui.cleardoc = function (editor) {\r\n        var ui = new editorui.Button({\r\n            className:'edui-for-cleardoc',\r\n            title:editor.options.labelMap.cleardoc || editor.getLang(\"labelMap.cleardoc\") || '',\r\n            theme:editor.options.theme,\r\n            onclick:function () {\r\n                if (confirm(editor.getLang(\"confirmClear\"))) {\r\n                    editor.execCommand('cleardoc');\r\n                }\r\n            }\r\n        });\r\n        editorui.buttons[\"cleardoc\"] = ui;\r\n        editor.addListener('selectionchange', function () {\r\n            ui.setDisabled(editor.queryCommandState('cleardoc') == -1);\r\n        });\r\n        return ui;\r\n    };\r\n\r\n    //排版，图片排版，文字方向\r\n    var typeset = {\r\n        'justify':['left', 'right', 'center', 'justify'],\r\n        'imagefloat':['none', 'left', 'center', 'right'],\r\n        'directionality':['ltr', 'rtl']\r\n    };\r\n\r\n    for (var p in typeset) {\r\n\r\n        (function (cmd, val) {\r\n            for (var i = 0, ci; ci = val[i++];) {\r\n                (function (cmd2) {\r\n                    editorui[cmd.replace('float', '') + cmd2] = function (editor) {\r\n                        var ui = new editorui.Button({\r\n                            className:'edui-for-' + cmd.replace('float', '') + cmd2,\r\n                            title:editor.options.labelMap[cmd.replace('float', '') + cmd2] || editor.getLang(\"labelMap.\" + cmd.replace('float', '') + cmd2) || '',\r\n                            theme:editor.options.theme,\r\n                            onclick:function () {\r\n                                editor.execCommand(cmd, cmd2);\r\n                            }\r\n                        });\r\n                        editorui.buttons[cmd] = ui;\r\n                        editor.addListener('selectionchange', function (type, causeByUi, uiReady) {\r\n                            ui.setDisabled(editor.queryCommandState(cmd) == -1);\r\n                            ui.setChecked(editor.queryCommandValue(cmd) == cmd2 && !uiReady);\r\n                        });\r\n                        return ui;\r\n                    };\r\n                })(ci)\r\n            }\r\n        })(p, typeset[p])\r\n    }\r\n\r\n    //字体颜色和背景颜色\r\n    for (var i = 0, ci; ci = ['backcolor', 'forecolor'][i++];) {\r\n        editorui[ci] = function (cmd) {\r\n            return function (editor) {\r\n                var ui = new editorui.ColorButton({\r\n                    className:'edui-for-' + cmd,\r\n                    color:'default',\r\n                    title:editor.options.labelMap[cmd] || editor.getLang(\"labelMap.\" + cmd) || '',\r\n                    editor:editor,\r\n                    onpickcolor:function (t, color) {\r\n                        editor.execCommand(cmd, color);\r\n                    },\r\n                    onpicknocolor:function () {\r\n                        editor.execCommand(cmd, 'default');\r\n                        this.setColor('transparent');\r\n                        this.color = 'default';\r\n                    },\r\n                    onbuttonclick:function () {\r\n                        editor.execCommand(cmd, this.color);\r\n                    }\r\n                });\r\n                editorui.buttons[cmd] = ui;\r\n                editor.addListener('selectionchange', function () {\r\n                    ui.setDisabled(editor.queryCommandState(cmd) == -1);\r\n                });\r\n                return ui;\r\n            };\r\n        }(ci);\r\n    }\r\n\r\n\r\n    var dialogBtns = {\r\n        noOk:['searchreplace', 'help', 'spechars', 'webapp','preview'],\r\n        ok:['attachment', 'anchor', 'link', 'insertimage', 'map', 'gmap', 'insertframe', 'wordimage',\r\n            'insertvideo', 'insertframe', 'edittip', 'edittable', 'edittd', 'scrawl', 'template', 'music', 'background', 'charts']\r\n    };\r\n\r\n    for (var p in dialogBtns) {\r\n        (function (type, vals) {\r\n            for (var i = 0, ci; ci = vals[i++];) {\r\n                //todo opera下存在问题\r\n                if (browser.opera && ci === \"searchreplace\") {\r\n                    continue;\r\n                }\r\n                (function (cmd) {\r\n                    editorui[cmd] = function (editor, iframeUrl, title) {\r\n                        iframeUrl = iframeUrl || (editor.options.iframeUrlMap || {})[cmd] || iframeUrlMap[cmd];\r\n                        title = editor.options.labelMap[cmd] || editor.getLang(\"labelMap.\" + cmd) || '';\r\n\r\n                        var dialog;\r\n                        //没有iframeUrl不创建dialog\r\n                        if (iframeUrl) {\r\n                            dialog = new editorui.Dialog(utils.extend({\r\n                                iframeUrl:editor.ui.mapUrl(iframeUrl),\r\n                                editor:editor,\r\n                                className:'edui-for-' + cmd,\r\n                                title:title,\r\n                                holdScroll: cmd === 'insertimage',\r\n                                fullscreen: /charts|preview/.test(cmd),\r\n                                closeDialog:editor.getLang(\"closeDialog\")\r\n                            }, type == 'ok' ? {\r\n                                buttons:[\r\n                                    {\r\n                                        className:'edui-okbutton',\r\n                                        label:editor.getLang(\"ok\"),\r\n                                        editor:editor,\r\n                                        onclick:function () {\r\n                                            dialog.close(true);\r\n                                        }\r\n                                    },\r\n                                    {\r\n                                        className:'edui-cancelbutton',\r\n                                        label:editor.getLang(\"cancel\"),\r\n                                        editor:editor,\r\n                                        onclick:function () {\r\n                                            dialog.close(false);\r\n                                        }\r\n                                    }\r\n                                ]\r\n                            } : {}));\r\n\r\n                            editor.ui._dialogs[cmd + \"Dialog\"] = dialog;\r\n                        }\r\n\r\n                        var ui = new editorui.Button({\r\n                            className:'edui-for-' + cmd,\r\n                            title:title,\r\n                            onclick:function () {\r\n                                if (dialog) {\r\n                                    switch (cmd) {\r\n                                        case \"wordimage\":\r\n                                            var images = editor.execCommand(\"wordimage\");\r\n                                            if (images && images.length) {\r\n                                                dialog.render();\r\n                                                dialog.open();\r\n                                            }\r\n                                            break;\r\n                                        case \"scrawl\":\r\n                                            if (editor.queryCommandState(\"scrawl\") != -1) {\r\n                                                dialog.render();\r\n                                                dialog.open();\r\n                                            }\r\n\r\n                                            break;\r\n                                        default:\r\n                                            dialog.render();\r\n                                            dialog.open();\r\n                                    }\r\n                                }\r\n                            },\r\n                            theme:editor.options.theme,\r\n                            disabled:(cmd == 'scrawl' && editor.queryCommandState(\"scrawl\") == -1) || ( cmd == 'charts' )\r\n                        });\r\n                        editorui.buttons[cmd] = ui;\r\n                        editor.addListener('selectionchange', function () {\r\n                            //只存在于右键菜单而无工具栏按钮的ui不需要检测状态\r\n                            var unNeedCheckState = {'edittable':1};\r\n                            if (cmd in unNeedCheckState)return;\r\n\r\n                            var state = editor.queryCommandState(cmd);\r\n                            if (ui.getDom()) {\r\n                                ui.setDisabled(state == -1);\r\n                                ui.setChecked(state);\r\n                            }\r\n\r\n                        });\r\n\r\n                        return ui;\r\n                    };\r\n                })(ci.toLowerCase())\r\n            }\r\n        })(p, dialogBtns[p]);\r\n    }\r\n\r\n    editorui.snapscreen = function (editor, iframeUrl, title) {\r\n        title = editor.options.labelMap['snapscreen'] || editor.getLang(\"labelMap.snapscreen\") || '';\r\n        var ui = new editorui.Button({\r\n            className:'edui-for-snapscreen',\r\n            title:title,\r\n            onclick:function () {\r\n                editor.execCommand(\"snapscreen\");\r\n            },\r\n            theme:editor.options.theme\r\n\r\n        });\r\n        editorui.buttons['snapscreen'] = ui;\r\n        iframeUrl = iframeUrl || (editor.options.iframeUrlMap || {})[\"snapscreen\"] || iframeUrlMap[\"snapscreen\"];\r\n        if (iframeUrl) {\r\n            var dialog = new editorui.Dialog({\r\n                iframeUrl:editor.ui.mapUrl(iframeUrl),\r\n                editor:editor,\r\n                className:'edui-for-snapscreen',\r\n                title:title,\r\n                buttons:[\r\n                    {\r\n                        className:'edui-okbutton',\r\n                        label:editor.getLang(\"ok\"),\r\n                        editor:editor,\r\n                        onclick:function () {\r\n                            dialog.close(true);\r\n                        }\r\n                    },\r\n                    {\r\n                        className:'edui-cancelbutton',\r\n                        label:editor.getLang(\"cancel\"),\r\n                        editor:editor,\r\n                        onclick:function () {\r\n                            dialog.close(false);\r\n                        }\r\n                    }\r\n                ]\r\n\r\n            });\r\n            dialog.render();\r\n            editor.ui._dialogs[\"snapscreenDialog\"] = dialog;\r\n        }\r\n        editor.addListener('selectionchange', function () {\r\n            ui.setDisabled(editor.queryCommandState('snapscreen') == -1);\r\n        });\r\n        return ui;\r\n    };\r\n\r\n    editorui.insertcode = function (editor, list, title) {\r\n        list = editor.options['insertcode'] || [];\r\n        title = editor.options.labelMap['insertcode'] || editor.getLang(\"labelMap.insertcode\") || '';\r\n       // if (!list.length) return;\r\n        var items = [];\r\n        utils.each(list,function(key,val){\r\n            items.push({\r\n                label:key,\r\n                value:val,\r\n                theme:editor.options.theme,\r\n                renderLabelHtml:function () {\r\n                    return '<div class=\"edui-label %%-label\" >' + (this.label || '') + '</div>';\r\n                }\r\n            });\r\n        });\r\n\r\n        var ui = new editorui.Combox({\r\n            editor:editor,\r\n            items:items,\r\n            onselect:function (t, index) {\r\n                editor.execCommand('insertcode', this.items[index].value);\r\n            },\r\n            onbuttonclick:function () {\r\n                this.showPopup();\r\n            },\r\n            title:title,\r\n            initValue:title,\r\n            className:'edui-for-insertcode',\r\n            indexByValue:function (value) {\r\n                if (value) {\r\n                    for (var i = 0, ci; ci = this.items[i]; i++) {\r\n                        if (ci.value.indexOf(value) != -1)\r\n                            return i;\r\n                    }\r\n                }\r\n\r\n                return -1;\r\n            }\r\n        });\r\n        editorui.buttons['insertcode'] = ui;\r\n        editor.addListener('selectionchange', function (type, causeByUi, uiReady) {\r\n            if (!uiReady) {\r\n                var state = editor.queryCommandState('insertcode');\r\n                if (state == -1) {\r\n                    ui.setDisabled(true);\r\n                } else {\r\n                    ui.setDisabled(false);\r\n                    var value = editor.queryCommandValue('insertcode');\r\n                    if(!value){\r\n                        ui.setValue(title);\r\n                        return;\r\n                    }\r\n                    //trace:1871 ie下从源码模式切换回来时，字体会带单引号，而且会有逗号\r\n                    value && (value = value.replace(/['\"]/g, '').split(',')[0]);\r\n                    ui.setValue(value);\r\n\r\n                }\r\n            }\r\n\r\n        });\r\n        return ui;\r\n    };\r\n    editorui.fontfamily = function (editor, list, title) {\r\n\r\n        list = editor.options['fontfamily'] || [];\r\n        title = editor.options.labelMap['fontfamily'] || editor.getLang(\"labelMap.fontfamily\") || '';\r\n        if (!list.length) return;\r\n        for (var i = 0, ci, items = []; ci = list[i]; i++) {\r\n            var langLabel = editor.getLang('fontfamily')[ci.name] || \"\";\r\n            (function (key, val) {\r\n                items.push({\r\n                    label:key,\r\n                    value:val,\r\n                    theme:editor.options.theme,\r\n                    renderLabelHtml:function () {\r\n                        return '<div class=\"edui-label %%-label\" style=\"font-family:' +\r\n                            utils.unhtml(this.value) + '\">' + (this.label || '') + '</div>';\r\n                    }\r\n                });\r\n            })(ci.label || langLabel, ci.val)\r\n        }\r\n        var ui = new editorui.Combox({\r\n            editor:editor,\r\n            items:items,\r\n            onselect:function (t, index) {\r\n                editor.execCommand('FontFamily', this.items[index].value);\r\n            },\r\n            onbuttonclick:function () {\r\n                this.showPopup();\r\n            },\r\n            title:title,\r\n            initValue:title,\r\n            className:'edui-for-fontfamily',\r\n            indexByValue:function (value) {\r\n                if (value) {\r\n                    for (var i = 0, ci; ci = this.items[i]; i++) {\r\n                        if (ci.value.indexOf(value) != -1)\r\n                            return i;\r\n                    }\r\n                }\r\n\r\n                return -1;\r\n            }\r\n        });\r\n        editorui.buttons['fontfamily'] = ui;\r\n        editor.addListener('selectionchange', function (type, causeByUi, uiReady) {\r\n            if (!uiReady) {\r\n                var state = editor.queryCommandState('FontFamily');\r\n                if (state == -1) {\r\n                    ui.setDisabled(true);\r\n                } else {\r\n                    ui.setDisabled(false);\r\n                    var value = editor.queryCommandValue('FontFamily');\r\n                    //trace:1871 ie下从源码模式切换回来时，字体会带单引号，而且会有逗号\r\n                    value && (value = value.replace(/['\"]/g, '').split(',')[0]);\r\n                    ui.setValue(value);\r\n\r\n                }\r\n            }\r\n\r\n        });\r\n        return ui;\r\n    };\r\n\r\n    editorui.fontsize = function (editor, list, title) {\r\n        title = editor.options.labelMap['fontsize'] || editor.getLang(\"labelMap.fontsize\") || '';\r\n        list = list || editor.options['fontsize'] || [];\r\n        if (!list.length) return;\r\n        var items = [];\r\n        for (var i = 0; i < list.length; i++) {\r\n            var size = list[i] + 'px';\r\n            items.push({\r\n                label:size,\r\n                value:size,\r\n                theme:editor.options.theme,\r\n                renderLabelHtml:function () {\r\n                    return '<div class=\"edui-label %%-label\" style=\"line-height:1;font-size:' +\r\n                        this.value + '\">' + (this.label || '') + '</div>';\r\n                }\r\n            });\r\n        }\r\n        var ui = new editorui.Combox({\r\n            editor:editor,\r\n            items:items,\r\n            title:title,\r\n            initValue:title,\r\n            onselect:function (t, index) {\r\n                editor.execCommand('FontSize', this.items[index].value);\r\n            },\r\n            onbuttonclick:function () {\r\n                this.showPopup();\r\n            },\r\n            className:'edui-for-fontsize'\r\n        });\r\n        editorui.buttons['fontsize'] = ui;\r\n        editor.addListener('selectionchange', function (type, causeByUi, uiReady) {\r\n            if (!uiReady) {\r\n                var state = editor.queryCommandState('FontSize');\r\n                if (state == -1) {\r\n                    ui.setDisabled(true);\r\n                } else {\r\n                    ui.setDisabled(false);\r\n                    ui.setValue(editor.queryCommandValue('FontSize'));\r\n                }\r\n            }\r\n\r\n        });\r\n        return ui;\r\n    };\r\n\r\n    editorui.paragraph = function (editor, list, title) {\r\n        title = editor.options.labelMap['paragraph'] || editor.getLang(\"labelMap.paragraph\") || '';\r\n        list = editor.options['paragraph'] || [];\r\n        if (utils.isEmptyObject(list)) return;\r\n        var items = [];\r\n        for (var i in list) {\r\n            items.push({\r\n                value:i,\r\n                label:list[i] || editor.getLang(\"paragraph\")[i],\r\n                theme:editor.options.theme,\r\n                renderLabelHtml:function () {\r\n                    return '<div class=\"edui-label %%-label\"><span class=\"edui-for-' + this.value + '\">' + (this.label || '') + '</span></div>';\r\n                }\r\n            })\r\n        }\r\n        var ui = new editorui.Combox({\r\n            editor:editor,\r\n            items:items,\r\n            title:title,\r\n            initValue:title,\r\n            className:'edui-for-paragraph',\r\n            onselect:function (t, index) {\r\n                editor.execCommand('Paragraph', this.items[index].value);\r\n            },\r\n            onbuttonclick:function () {\r\n                this.showPopup();\r\n            }\r\n        });\r\n        editorui.buttons['paragraph'] = ui;\r\n        editor.addListener('selectionchange', function (type, causeByUi, uiReady) {\r\n            if (!uiReady) {\r\n                var state = editor.queryCommandState('Paragraph');\r\n                if (state == -1) {\r\n                    ui.setDisabled(true);\r\n                } else {\r\n                    ui.setDisabled(false);\r\n                    var value = editor.queryCommandValue('Paragraph');\r\n                    var index = ui.indexByValue(value);\r\n                    if (index != -1) {\r\n                        ui.setValue(value);\r\n                    } else {\r\n                        ui.setValue(ui.initValue);\r\n                    }\r\n                }\r\n            }\r\n\r\n        });\r\n        return ui;\r\n    };\r\n\r\n\r\n    //自定义标题\r\n    editorui.customstyle = function (editor) {\r\n        var list = editor.options['customstyle'] || [],\r\n            title = editor.options.labelMap['customstyle'] || editor.getLang(\"labelMap.customstyle\") || '';\r\n        if (!list.length)return;\r\n        var langCs = editor.getLang('customstyle');\r\n        for (var i = 0, items = [], t; t = list[i++];) {\r\n            (function (t) {\r\n                var ck = {};\r\n                ck.label = t.label ? t.label : langCs[t.name];\r\n                ck.style = t.style;\r\n                ck.className = t.className;\r\n                ck.tag = t.tag;\r\n                items.push({\r\n                    label:ck.label,\r\n                    value:ck,\r\n                    theme:editor.options.theme,\r\n                    renderLabelHtml:function () {\r\n                        return '<div class=\"edui-label %%-label\">' + '<' + ck.tag + ' ' + (ck.className ? ' class=\"' + ck.className + '\"' : \"\")\r\n                            + (ck.style ? ' style=\"' + ck.style + '\"' : \"\") + '>' + ck.label + \"<\\/\" + ck.tag + \">\"\r\n                            + '</div>';\r\n                    }\r\n                });\r\n            })(t);\r\n        }\r\n\r\n        var ui = new editorui.Combox({\r\n            editor:editor,\r\n            items:items,\r\n            title:title,\r\n            initValue:title,\r\n            className:'edui-for-customstyle',\r\n            onselect:function (t, index) {\r\n                editor.execCommand('customstyle', this.items[index].value);\r\n            },\r\n            onbuttonclick:function () {\r\n                this.showPopup();\r\n            },\r\n            indexByValue:function (value) {\r\n                for (var i = 0, ti; ti = this.items[i++];) {\r\n                    if (ti.label == value) {\r\n                        return i - 1\r\n                    }\r\n                }\r\n                return -1;\r\n            }\r\n        });\r\n        editorui.buttons['customstyle'] = ui;\r\n        editor.addListener('selectionchange', function (type, causeByUi, uiReady) {\r\n            if (!uiReady) {\r\n                var state = editor.queryCommandState('customstyle');\r\n                if (state == -1) {\r\n                    ui.setDisabled(true);\r\n                } else {\r\n                    ui.setDisabled(false);\r\n                    var value = editor.queryCommandValue('customstyle');\r\n                    var index = ui.indexByValue(value);\r\n                    if (index != -1) {\r\n                        ui.setValue(value);\r\n                    } else {\r\n                        ui.setValue(ui.initValue);\r\n                    }\r\n                }\r\n            }\r\n\r\n        });\r\n        return ui;\r\n    };\r\n    editorui.inserttable = function (editor, iframeUrl, title) {\r\n        title = editor.options.labelMap['inserttable'] || editor.getLang(\"labelMap.inserttable\") || '';\r\n        var ui = new editorui.TableButton({\r\n            editor:editor,\r\n            title:title,\r\n            className:'edui-for-inserttable',\r\n            onpicktable:function (t, numCols, numRows) {\r\n                editor.execCommand('InsertTable', {numRows:numRows, numCols:numCols, border:1});\r\n            },\r\n            onbuttonclick:function () {\r\n                this.showPopup();\r\n            }\r\n        });\r\n        editorui.buttons['inserttable'] = ui;\r\n        editor.addListener('selectionchange', function () {\r\n            ui.setDisabled(editor.queryCommandState('inserttable') == -1);\r\n        });\r\n        return ui;\r\n    };\r\n\r\n    editorui.lineheight = function (editor) {\r\n        var val = editor.options.lineheight || [];\r\n        if (!val.length)return;\r\n        for (var i = 0, ci, items = []; ci = val[i++];) {\r\n            items.push({\r\n                //todo:写死了\r\n                label:ci,\r\n                value:ci,\r\n                theme:editor.options.theme,\r\n                onclick:function () {\r\n                    editor.execCommand(\"lineheight\", this.value);\r\n                }\r\n            })\r\n        }\r\n        var ui = new editorui.MenuButton({\r\n            editor:editor,\r\n            className:'edui-for-lineheight',\r\n            title:editor.options.labelMap['lineheight'] || editor.getLang(\"labelMap.lineheight\") || '',\r\n            items:items,\r\n            onbuttonclick:function () {\r\n                var value = editor.queryCommandValue('LineHeight') || this.value;\r\n                editor.execCommand(\"LineHeight\", value);\r\n            }\r\n        });\r\n        editorui.buttons['lineheight'] = ui;\r\n        editor.addListener('selectionchange', function () {\r\n            var state = editor.queryCommandState('LineHeight');\r\n            if (state == -1) {\r\n                ui.setDisabled(true);\r\n            } else {\r\n                ui.setDisabled(false);\r\n                var value = editor.queryCommandValue('LineHeight');\r\n                value && ui.setValue((value + '').replace(/cm/, ''));\r\n                ui.setChecked(state)\r\n            }\r\n        });\r\n        return ui;\r\n    };\r\n\r\n    var rowspacings = ['top', 'bottom'];\r\n    for (var r = 0, ri; ri = rowspacings[r++];) {\r\n        (function (cmd) {\r\n            editorui['rowspacing' + cmd] = function (editor) {\r\n                var val = editor.options['rowspacing' + cmd] || [];\r\n                if (!val.length) return null;\r\n                for (var i = 0, ci, items = []; ci = val[i++];) {\r\n                    items.push({\r\n                        label:ci,\r\n                        value:ci,\r\n                        theme:editor.options.theme,\r\n                        onclick:function () {\r\n                            editor.execCommand(\"rowspacing\", this.value, cmd);\r\n                        }\r\n                    })\r\n                }\r\n                var ui = new editorui.MenuButton({\r\n                    editor:editor,\r\n                    className:'edui-for-rowspacing' + cmd,\r\n                    title:editor.options.labelMap['rowspacing' + cmd] || editor.getLang(\"labelMap.rowspacing\" + cmd) || '',\r\n                    items:items,\r\n                    onbuttonclick:function () {\r\n                        var value = editor.queryCommandValue('rowspacing', cmd) || this.value;\r\n                        editor.execCommand(\"rowspacing\", value, cmd);\r\n                    }\r\n                });\r\n                editorui.buttons[cmd] = ui;\r\n                editor.addListener('selectionchange', function () {\r\n                    var state = editor.queryCommandState('rowspacing', cmd);\r\n                    if (state == -1) {\r\n                        ui.setDisabled(true);\r\n                    } else {\r\n                        ui.setDisabled(false);\r\n                        var value = editor.queryCommandValue('rowspacing', cmd);\r\n                        value && ui.setValue((value + '').replace(/%/, ''));\r\n                        ui.setChecked(state)\r\n                    }\r\n                });\r\n                return ui;\r\n            }\r\n        })(ri)\r\n    }\r\n    //有序，无序列表\r\n    var lists = ['insertorderedlist', 'insertunorderedlist'];\r\n    for (var l = 0, cl; cl = lists[l++];) {\r\n        (function (cmd) {\r\n            editorui[cmd] = function (editor) {\r\n                var vals = editor.options[cmd],\r\n                    _onMenuClick = function () {\r\n                        editor.execCommand(cmd, this.value);\r\n                    }, items = [];\r\n                for (var i in vals) {\r\n                    items.push({\r\n                        label:vals[i] || editor.getLang()[cmd][i] || \"\",\r\n                        value:i,\r\n                        theme:editor.options.theme,\r\n                        onclick:_onMenuClick\r\n                    })\r\n                }\r\n                var ui = new editorui.MenuButton({\r\n                    editor:editor,\r\n                    className:'edui-for-' + cmd,\r\n                    title:editor.getLang(\"labelMap.\" + cmd) || '',\r\n                    'items':items,\r\n                    onbuttonclick:function () {\r\n                        var value = editor.queryCommandValue(cmd) || this.value;\r\n                        editor.execCommand(cmd, value);\r\n                    }\r\n                });\r\n                editorui.buttons[cmd] = ui;\r\n                editor.addListener('selectionchange', function () {\r\n                    var state = editor.queryCommandState(cmd);\r\n                    if (state == -1) {\r\n                        ui.setDisabled(true);\r\n                    } else {\r\n                        ui.setDisabled(false);\r\n                        var value = editor.queryCommandValue(cmd);\r\n                        ui.setValue(value);\r\n                        ui.setChecked(state)\r\n                    }\r\n                });\r\n                return ui;\r\n            };\r\n        })(cl)\r\n    }\r\n\r\n    editorui.fullscreen = function (editor, title) {\r\n        title = editor.options.labelMap['fullscreen'] || editor.getLang(\"labelMap.fullscreen\") || '';\r\n        var ui = new editorui.Button({\r\n            className:'edui-for-fullscreen',\r\n            title:title,\r\n            theme:editor.options.theme,\r\n            onclick:function () {\r\n                if (editor.ui) {\r\n                    editor.ui.setFullScreen(!editor.ui.isFullScreen());\r\n                }\r\n                this.setChecked(editor.ui.isFullScreen());\r\n            }\r\n        });\r\n        editorui.buttons['fullscreen'] = ui;\r\n        editor.addListener('selectionchange', function () {\r\n            var state = editor.queryCommandState('fullscreen');\r\n            ui.setDisabled(state == -1);\r\n            ui.setChecked(editor.ui.isFullScreen());\r\n        });\r\n        return ui;\r\n    };\r\n\r\n    // 表情\r\n    editorui[\"emotion\"] = function (editor, iframeUrl) {\r\n        var cmd = \"emotion\";\r\n        var ui = new editorui.MultiMenuPop({\r\n            title:editor.options.labelMap[cmd] || editor.getLang(\"labelMap.\" + cmd + \"\") || '',\r\n            editor:editor,\r\n            className:'edui-for-' + cmd,\r\n            iframeUrl:editor.ui.mapUrl(iframeUrl || (editor.options.iframeUrlMap || {})[cmd] || iframeUrlMap[cmd])\r\n        });\r\n        editorui.buttons[cmd] = ui;\r\n\r\n        editor.addListener('selectionchange', function () {\r\n            ui.setDisabled(editor.queryCommandState(cmd) == -1)\r\n        });\r\n        return ui;\r\n    };\r\n\r\n    editorui.autotypeset = function (editor) {\r\n        var ui = new editorui.AutoTypeSetButton({\r\n            editor:editor,\r\n            title:editor.options.labelMap['autotypeset'] || editor.getLang(\"labelMap.autotypeset\") || '',\r\n            className:'edui-for-autotypeset',\r\n            onbuttonclick:function () {\r\n                editor.execCommand('autotypeset')\r\n            }\r\n        });\r\n        editorui.buttons['autotypeset'] = ui;\r\n        editor.addListener('selectionchange', function () {\r\n            ui.setDisabled(editor.queryCommandState('autotypeset') == -1);\r\n        });\r\n        return ui;\r\n    };\r\n\r\n    /* 简单上传插件 */\r\n    editorui[\"simpleupload\"] = function (editor) {\r\n        var name = 'simpleupload',\r\n            ui = new editorui.Button({\r\n                className:'edui-for-' + name,\r\n                title:editor.options.labelMap[name] || editor.getLang(\"labelMap.\" + name) || '',\r\n                onclick:function () {},\r\n                theme:editor.options.theme,\r\n                showText:false\r\n            });\r\n        editorui.buttons[name] = ui;\r\n        editor.addListener('ready', function() {\r\n            var b = ui.getDom('body'),\r\n                iconSpan = b.children[0];\r\n            editor.fireEvent('simpleuploadbtnready', iconSpan);\r\n        });\r\n        editor.addListener('selectionchange', function (type, causeByUi, uiReady) {\r\n            var state = editor.queryCommandState(name);\r\n            if (state == -1) {\r\n                ui.setDisabled(true);\r\n                ui.setChecked(false);\r\n            } else {\r\n                if (!uiReady) {\r\n                    ui.setDisabled(false);\r\n                    ui.setChecked(state);\r\n                }\r\n            }\r\n        });\r\n        return ui;\r\n    };\r\n\r\n})();\r\n\r\n\r\n// adapter/editor.js\r\n///import core\r\n///commands 全屏\r\n///commandsName FullScreen\r\n///commandsTitle  全屏\r\n(function () {\r\n    var utils = baidu.editor.utils,\r\n        uiUtils = baidu.editor.ui.uiUtils,\r\n        UIBase = baidu.editor.ui.UIBase,\r\n        domUtils = baidu.editor.dom.domUtils;\r\n    var nodeStack = [];\r\n\r\n    function EditorUI(options) {\r\n        this.initOptions(options);\r\n        this.initEditorUI();\r\n    }\r\n\r\n    EditorUI.prototype = {\r\n        uiName:'editor',\r\n        initEditorUI:function () {\r\n            this.editor.ui = this;\r\n            this._dialogs = {};\r\n            this.initUIBase();\r\n            this._initToolbars();\r\n            var editor = this.editor,\r\n                me = this;\r\n\r\n            editor.addListener('ready', function () {\r\n                //提供getDialog方法\r\n                editor.getDialog = function (name) {\r\n                    return editor.ui._dialogs[name + \"Dialog\"];\r\n                };\r\n                domUtils.on(editor.window, 'scroll', function (evt) {\r\n                    baidu.editor.ui.Popup.postHide(evt);\r\n                });\r\n                //提供编辑器实时宽高(全屏时宽高不变化)\r\n                editor.ui._actualFrameWidth = editor.options.initialFrameWidth;\r\n\r\n                UE.browser.ie && UE.browser.version === 6 && editor.container.ownerDocument.execCommand(\"BackgroundImageCache\", false, true);\r\n\r\n                //display bottom-bar label based on config\r\n                if (editor.options.elementPathEnabled) {\r\n                    editor.ui.getDom('elementpath').innerHTML = '<div class=\"edui-editor-breadcrumb\">' + editor.getLang(\"elementPathTip\") + ':</div>';\r\n                }\r\n                if (editor.options.wordCount) {\r\n                    function countFn() {\r\n                        setCount(editor,me);\r\n                        domUtils.un(editor.document, \"click\", arguments.callee);\r\n                    }\r\n                    domUtils.on(editor.document, \"click\", countFn);\r\n                    editor.ui.getDom('wordcount').innerHTML = editor.getLang(\"wordCountTip\");\r\n                }\r\n                editor.ui._scale();\r\n                if (editor.options.scaleEnabled) {\r\n                    if (editor.autoHeightEnabled) {\r\n                        editor.disableAutoHeight();\r\n                    }\r\n                    me.enableScale();\r\n                } else {\r\n                    me.disableScale();\r\n                }\r\n                if (!editor.options.elementPathEnabled && !editor.options.wordCount && !editor.options.scaleEnabled) {\r\n                    editor.ui.getDom('elementpath').style.display = \"none\";\r\n                    editor.ui.getDom('wordcount').style.display = \"none\";\r\n                    editor.ui.getDom('scale').style.display = \"none\";\r\n                }\r\n\r\n                if (!editor.selection.isFocus())return;\r\n                editor.fireEvent('selectionchange', false, true);\r\n\r\n\r\n            });\r\n\r\n            editor.addListener('mousedown', function (t, evt) {\r\n                var el = evt.target || evt.srcElement;\r\n                baidu.editor.ui.Popup.postHide(evt, el);\r\n                baidu.editor.ui.ShortCutMenu.postHide(evt);\r\n\r\n            });\r\n            editor.addListener(\"delcells\", function () {\r\n                if (UE.ui['edittip']) {\r\n                    new UE.ui['edittip'](editor);\r\n                }\r\n                editor.getDialog('edittip').open();\r\n            });\r\n\r\n            var pastePop, isPaste = false, timer;\r\n            editor.addListener(\"afterpaste\", function () {\r\n                if(editor.queryCommandState('pasteplain'))\r\n                    return;\r\n                if(baidu.editor.ui.PastePicker){\r\n                    pastePop = new baidu.editor.ui.Popup({\r\n                        content:new baidu.editor.ui.PastePicker({editor:editor}),\r\n                        editor:editor,\r\n                        className:'edui-wordpastepop'\r\n                    });\r\n                    pastePop.render();\r\n                }\r\n                isPaste = true;\r\n            });\r\n\r\n            editor.addListener(\"afterinserthtml\", function () {\r\n                clearTimeout(timer);\r\n                timer = setTimeout(function () {\r\n                    if (pastePop && (isPaste || editor.ui._isTransfer)) {\r\n                        if(pastePop.isHidden()){\r\n                            var span = domUtils.createElement(editor.document, 'span', {\r\n                                    'style':\"line-height:0px;\",\r\n                                    'innerHTML':'\\ufeff'\r\n                                }),\r\n                                range = editor.selection.getRange();\r\n                            range.insertNode(span);\r\n                            var tmp= getDomNode(span, 'firstChild', 'previousSibling');\r\n                            tmp && pastePop.showAnchor(tmp.nodeType == 3 ? tmp.parentNode : tmp);\r\n                            domUtils.remove(span);\r\n                        }else{\r\n                            pastePop.show();\r\n                        }\r\n                        delete editor.ui._isTransfer;\r\n                        isPaste = false;\r\n                    }\r\n                }, 200)\r\n            });\r\n            editor.addListener('contextmenu', function (t, evt) {\r\n                baidu.editor.ui.Popup.postHide(evt);\r\n            });\r\n            editor.addListener('keydown', function (t, evt) {\r\n                if (pastePop)    pastePop.dispose(evt);\r\n                var keyCode = evt.keyCode || evt.which;\r\n                if(evt.altKey&&keyCode==90){\r\n                    UE.ui.buttons['fullscreen'].onclick();\r\n                }\r\n            });\r\n            editor.addListener('wordcount', function (type) {\r\n                setCount(this,me);\r\n            });\r\n            function setCount(editor,ui) {\r\n                editor.setOpt({\r\n                    wordCount:true,\r\n                    maximumWords:10000,\r\n                    wordCountMsg:editor.options.wordCountMsg || editor.getLang(\"wordCountMsg\"),\r\n                    wordOverFlowMsg:editor.options.wordOverFlowMsg || editor.getLang(\"wordOverFlowMsg\")\r\n                });\r\n                var opt = editor.options,\r\n                    max = opt.maximumWords,\r\n                    msg = opt.wordCountMsg ,\r\n                    errMsg = opt.wordOverFlowMsg,\r\n                    countDom = ui.getDom('wordcount');\r\n                if (!opt.wordCount) {\r\n                    return;\r\n                }\r\n                var count = editor.getContentLength(true);\r\n                if (count > max) {\r\n                    countDom.innerHTML = errMsg;\r\n                    editor.fireEvent(\"wordcountoverflow\");\r\n                } else {\r\n                    countDom.innerHTML = msg.replace(\"{#leave}\", max - count).replace(\"{#count}\", count);\r\n                }\r\n            }\r\n\r\n            editor.addListener('selectionchange', function () {\r\n                if (editor.options.elementPathEnabled) {\r\n                    me[(editor.queryCommandState('elementpath') == -1 ? 'dis' : 'en') + 'ableElementPath']()\r\n                }\r\n                if (editor.options.scaleEnabled) {\r\n                    me[(editor.queryCommandState('scale') == -1 ? 'dis' : 'en') + 'ableScale']();\r\n\r\n                }\r\n            });\r\n            var popup = new baidu.editor.ui.Popup({\r\n                editor:editor,\r\n                content:'',\r\n                className:'edui-bubble',\r\n                _onEditButtonClick:function () {\r\n                    this.hide();\r\n                    editor.ui._dialogs.linkDialog.open();\r\n                },\r\n                _onImgEditButtonClick:function (name) {\r\n                    this.hide();\r\n                    editor.ui._dialogs[name] && editor.ui._dialogs[name].open();\r\n\r\n                },\r\n                _onImgSetFloat:function (value) {\r\n                    this.hide();\r\n                    editor.execCommand(\"imagefloat\", value);\r\n\r\n                },\r\n                _setIframeAlign:function (value) {\r\n                    var frame = popup.anchorEl;\r\n                    var newFrame = frame.cloneNode(true);\r\n                    switch (value) {\r\n                        case -2:\r\n                            newFrame.setAttribute(\"align\", \"\");\r\n                            break;\r\n                        case -1:\r\n                            newFrame.setAttribute(\"align\", \"left\");\r\n                            break;\r\n                        case 1:\r\n                            newFrame.setAttribute(\"align\", \"right\");\r\n                            break;\r\n                    }\r\n                    frame.parentNode.insertBefore(newFrame, frame);\r\n                    domUtils.remove(frame);\r\n                    popup.anchorEl = newFrame;\r\n                    popup.showAnchor(popup.anchorEl);\r\n                },\r\n                _updateIframe:function () {\r\n                    var frame = editor._iframe = popup.anchorEl;\r\n                    if(domUtils.hasClass(frame, 'ueditor_baidumap')) {\r\n                        editor.selection.getRange().selectNode(frame).select();\r\n                        editor.ui._dialogs.mapDialog.open();\r\n                        popup.hide();\r\n                    } else {\r\n                        editor.ui._dialogs.insertframeDialog.open();\r\n                        popup.hide();\r\n                    }\r\n                },\r\n                _onRemoveButtonClick:function (cmdName) {\r\n                    editor.execCommand(cmdName);\r\n                    this.hide();\r\n                },\r\n                queryAutoHide:function (el) {\r\n                    if (el && el.ownerDocument == editor.document) {\r\n                        if (el.tagName.toLowerCase() == 'img' || domUtils.findParentByTagName(el, 'a', true)) {\r\n                            return el !== popup.anchorEl;\r\n                        }\r\n                    }\r\n                    return baidu.editor.ui.Popup.prototype.queryAutoHide.call(this, el);\r\n                }\r\n            });\r\n            popup.render();\r\n            if (editor.options.imagePopup) {\r\n                editor.addListener('mouseover', function (t, evt) {\r\n                    evt = evt || window.event;\r\n                    var el = evt.target || evt.srcElement;\r\n                    if (editor.ui._dialogs.insertframeDialog && /iframe/ig.test(el.tagName)) {\r\n                        var html = popup.formatHtml(\r\n                            '<nobr>' + editor.getLang(\"property\") + ': <span onclick=$$._setIframeAlign(-2) class=\"edui-clickable\">' + editor.getLang(\"default\") + '</span>&nbsp;&nbsp;<span onclick=$$._setIframeAlign(-1) class=\"edui-clickable\">' + editor.getLang(\"justifyleft\") + '</span>&nbsp;&nbsp;<span onclick=$$._setIframeAlign(1) class=\"edui-clickable\">' + editor.getLang(\"justifyright\") + '</span>&nbsp;&nbsp;' +\r\n                                ' <span onclick=\"$$._updateIframe( this);\" class=\"edui-clickable\">' + editor.getLang(\"modify\") + '</span></nobr>');\r\n                        if (html) {\r\n                            popup.getDom('content').innerHTML = html;\r\n                            popup.anchorEl = el;\r\n                            popup.showAnchor(popup.anchorEl);\r\n                        } else {\r\n                            popup.hide();\r\n                        }\r\n                    }\r\n                });\r\n                editor.addListener('selectionchange', function (t, causeByUi) {\r\n                    if (!causeByUi) return;\r\n                    var html = '', str = \"\",\r\n                        img = editor.selection.getRange().getClosedNode(),\r\n                        dialogs = editor.ui._dialogs;\r\n                    if (img && img.tagName == 'IMG') {\r\n                        var dialogName = 'insertimageDialog';\r\n                        if (img.className.indexOf(\"edui-faked-video\") != -1 || img.className.indexOf(\"edui-upload-video\") != -1) {\r\n                            dialogName = \"insertvideoDialog\"\r\n                        }\r\n                        if (img.className.indexOf(\"edui-faked-webapp\") != -1) {\r\n                            dialogName = \"webappDialog\"\r\n                        }\r\n                        if (img.src.indexOf(\"http://api.map.baidu.com\") != -1) {\r\n                            dialogName = \"mapDialog\"\r\n                        }\r\n                        if (img.className.indexOf(\"edui-faked-music\") != -1) {\r\n                            dialogName = \"musicDialog\"\r\n                        }\r\n                        if (img.src.indexOf(\"http://maps.google.com/maps/api/staticmap\") != -1) {\r\n                            dialogName = \"gmapDialog\"\r\n                        }\r\n                        if (img.getAttribute(\"anchorname\")) {\r\n                            dialogName = \"anchorDialog\";\r\n                            html = popup.formatHtml(\r\n                                '<nobr>' + editor.getLang(\"property\") + ': <span onclick=$$._onImgEditButtonClick(\"anchorDialog\") class=\"edui-clickable\">' + editor.getLang(\"modify\") + '</span>&nbsp;&nbsp;' +\r\n                                    '<span onclick=$$._onRemoveButtonClick(\\'anchor\\') class=\"edui-clickable\">' + editor.getLang(\"delete\") + '</span></nobr>');\r\n                        }\r\n                        if (img.getAttribute(\"word_img\")) {\r\n                            //todo 放到dialog去做查询\r\n                            editor.word_img = [img.getAttribute(\"word_img\")];\r\n                            dialogName = \"wordimageDialog\"\r\n                        }\r\n                        if(domUtils.hasClass(img, 'loadingclass') || domUtils.hasClass(img, 'loaderrorclass')) {\r\n                            dialogName = \"\";\r\n                        }\r\n                        if (!dialogs[dialogName]) {\r\n                            return;\r\n                        }\r\n                        str = '<nobr>' + editor.getLang(\"property\") + ': '+\r\n                            '<span onclick=$$._onImgSetFloat(\"none\") class=\"edui-clickable\">' + editor.getLang(\"default\") + '</span>&nbsp;&nbsp;' +\r\n                            '<span onclick=$$._onImgSetFloat(\"left\") class=\"edui-clickable\">' + editor.getLang(\"justifyleft\") + '</span>&nbsp;&nbsp;' +\r\n                            '<span onclick=$$._onImgSetFloat(\"right\") class=\"edui-clickable\">' + editor.getLang(\"justifyright\") + '</span>&nbsp;&nbsp;' +\r\n                            '<span onclick=$$._onImgSetFloat(\"center\") class=\"edui-clickable\">' + editor.getLang(\"justifycenter\") + '</span>&nbsp;&nbsp;'+\r\n                            '<span onclick=\"$$._onImgEditButtonClick(\\'' + dialogName + '\\');\" class=\"edui-clickable\">' + editor.getLang(\"modify\") + '</span></nobr>';\r\n\r\n                        !html && (html = popup.formatHtml(str))\r\n\r\n                    }\r\n                    if (editor.ui._dialogs.linkDialog) {\r\n                        var link = editor.queryCommandValue('link');\r\n                        var url;\r\n                        if (link && (url = (link.getAttribute('_href') || link.getAttribute('href', 2)))) {\r\n                            var txt = url;\r\n                            if (url.length > 30) {\r\n                                txt = url.substring(0, 20) + \"...\";\r\n                            }\r\n                            if (html) {\r\n                                html += '<div style=\"height:5px;\"></div>'\r\n                            }\r\n                            html += popup.formatHtml(\r\n                                '<nobr>' + editor.getLang(\"anthorMsg\") + ': <a target=\"_blank\" href=\"' + url + '\" title=\"' + url + '\" >' + txt + '</a>' +\r\n                                    ' <span class=\"edui-clickable\" onclick=\"$$._onEditButtonClick();\">' + editor.getLang(\"modify\") + '</span>' +\r\n                                    ' <span class=\"edui-clickable\" onclick=\"$$._onRemoveButtonClick(\\'unlink\\');\"> ' + editor.getLang(\"clear\") + '</span></nobr>');\r\n                            popup.showAnchor(link);\r\n                        }\r\n                    }\r\n\r\n                    if (html) {\r\n                        popup.getDom('content').innerHTML = html;\r\n                        popup.anchorEl = img || link;\r\n                        popup.showAnchor(popup.anchorEl);\r\n                    } else {\r\n                        popup.hide();\r\n                    }\r\n                });\r\n            }\r\n\r\n        },\r\n        _initToolbars:function () {\r\n            var editor = this.editor;\r\n            var toolbars = this.toolbars || [];\r\n            var toolbarUis = [];\r\n            for (var i = 0; i < toolbars.length; i++) {\r\n                var toolbar = toolbars[i];\r\n                var toolbarUi = new baidu.editor.ui.Toolbar({theme:editor.options.theme});\r\n                for (var j = 0; j < toolbar.length; j++) {\r\n                    var toolbarItem = toolbar[j];\r\n                    var toolbarItemUi = null;\r\n                    if (typeof toolbarItem == 'string') {\r\n                        toolbarItem = toolbarItem.toLowerCase();\r\n                        if (toolbarItem == '|') {\r\n                            toolbarItem = 'Separator';\r\n                        }\r\n                        if(toolbarItem == '||'){\r\n                            toolbarItem = 'Breakline';\r\n                        }\r\n                        if (baidu.editor.ui[toolbarItem]) {\r\n                            toolbarItemUi = new baidu.editor.ui[toolbarItem](editor);\r\n                        }\r\n\r\n                        //fullscreen这里单独处理一下，放到首行去\r\n                        if (toolbarItem == 'fullscreen') {\r\n                            if (toolbarUis && toolbarUis[0]) {\r\n                                toolbarUis[0].items.splice(0, 0, toolbarItemUi);\r\n                            } else {\r\n                                toolbarItemUi && toolbarUi.items.splice(0, 0, toolbarItemUi);\r\n                            }\r\n\r\n                            continue;\r\n\r\n\r\n                        }\r\n                    } else {\r\n                        toolbarItemUi = toolbarItem;\r\n                    }\r\n                    if (toolbarItemUi && toolbarItemUi.id) {\r\n\r\n                        toolbarUi.add(toolbarItemUi);\r\n                    }\r\n                }\r\n                toolbarUis[i] = toolbarUi;\r\n            }\r\n\r\n            //接受外部定制的UI（修复因 utils.each 无法准确的循环出对象的全部元素而导致的自定义 UI 不符合预期的 BUG by HaoChuan9421）\r\n\r\n            // utils.each(UE._customizeUI,function(obj,key){\r\n            //     var itemUI,index;\r\n            //     if(obj.id && obj.id != editor.key){\r\n            //        return false;\r\n            //     }\r\n            //     itemUI = obj.execFn.call(editor,editor,key);\r\n            //     if(itemUI){\r\n            //         index = obj.index;\r\n            //         if(index === undefined){\r\n            //             index = toolbarUi.items.length;\r\n            //         }\r\n            //         toolbarUi.add(itemUI,index)\r\n            //     }\r\n            // });\r\n\r\n            \r\n            for(var key in UE._customizeUI){\r\n                var obj = UE._customizeUI[key]\r\n                var itemUI,index;\r\n                if(!obj.id || obj.id == editor.key){\r\n                    itemUI = obj.execFn.call(editor,editor,key);\r\n                    if(itemUI){\r\n                        index = obj.index;\r\n                        if(index === undefined){\r\n                            index = toolbarUi.items.length;\r\n                        }\r\n                        toolbarUi.add(itemUI,index)\r\n                    }\r\n                }\r\n            }\r\n\r\n            this.toolbars = toolbarUis;\r\n        },\r\n        getHtmlTpl:function () {\r\n            return '<div id=\"##\" class=\"%%\">' +\r\n                '<div id=\"##_toolbarbox\" class=\"%%-toolbarbox\">' +\r\n                (this.toolbars.length ?\r\n                    '<div id=\"##_toolbarboxouter\" class=\"%%-toolbarboxouter\"><div class=\"%%-toolbarboxinner\">' +\r\n                        this.renderToolbarBoxHtml() +\r\n                        '</div></div>' : '') +\r\n                '<div id=\"##_toolbarmsg\" class=\"%%-toolbarmsg\" style=\"display:none;\">' +\r\n                '<div id = \"##_upload_dialog\" class=\"%%-toolbarmsg-upload\" onclick=\"$$.showWordImageDialog();\">' + this.editor.getLang(\"clickToUpload\") + '</div>' +\r\n                '<div class=\"%%-toolbarmsg-close\" onclick=\"$$.hideToolbarMsg();\">x</div>' +\r\n                '<div id=\"##_toolbarmsg_label\" class=\"%%-toolbarmsg-label\"></div>' +\r\n                '<div style=\"height:0;overflow:hidden;clear:both;\"></div>' +\r\n                '</div>' +\r\n                '<div id=\"##_message_holder\" class=\"%%-messageholder\"></div>' +\r\n                '</div>' +\r\n                '<div id=\"##_iframeholder\" class=\"%%-iframeholder\">' +\r\n                '</div>' +\r\n                //modify wdcount by matao\r\n                '<div id=\"##_bottombar\" class=\"%%-bottomContainer\"><table><tr>' +\r\n                '<td id=\"##_elementpath\" class=\"%%-bottombar\"></td>' +\r\n                '<td id=\"##_wordcount\" class=\"%%-wordcount\"></td>' +\r\n                '<td id=\"##_scale\" class=\"%%-scale\"><div class=\"%%-icon\"></div></td>' +\r\n                '</tr></table></div>' +\r\n                '<div id=\"##_scalelayer\"></div>' +\r\n                '</div>';\r\n        },\r\n        showWordImageDialog:function () {\r\n            this._dialogs['wordimageDialog'].open();\r\n        },\r\n        renderToolbarBoxHtml:function () {\r\n            var buff = [];\r\n            for (var i = 0; i < this.toolbars.length; i++) {\r\n                buff.push(this.toolbars[i].renderHtml());\r\n            }\r\n            return buff.join('');\r\n        },\r\n        setFullScreen:function (fullscreen) {\r\n\r\n            var editor = this.editor,\r\n                container = editor.container.parentNode.parentNode;\r\n            if (this._fullscreen != fullscreen) {\r\n                this._fullscreen = fullscreen;\r\n                this.editor.fireEvent('beforefullscreenchange', fullscreen);\r\n                if (baidu.editor.browser.gecko) {\r\n                    var bk = editor.selection.getRange().createBookmark();\r\n                }\r\n                if (fullscreen) {\r\n                    while (container.tagName != \"BODY\") {\r\n                        var position = baidu.editor.dom.domUtils.getComputedStyle(container, \"position\");\r\n                        nodeStack.push(position);\r\n                        container.style.position = \"static\";\r\n                        container = container.parentNode;\r\n                    }\r\n                    this._bakHtmlOverflow = document.documentElement.style.overflow;\r\n                    this._bakBodyOverflow = document.body.style.overflow;\r\n                    this._bakAutoHeight = this.editor.autoHeightEnabled;\r\n                    this._bakScrollTop = Math.max(document.documentElement.scrollTop, document.body.scrollTop);\r\n\r\n                    this._bakEditorContaninerWidth = editor.iframe.parentNode.offsetWidth;\r\n                    if (this._bakAutoHeight) {\r\n                        //当全屏时不能执行自动长高\r\n                        editor.autoHeightEnabled = false;\r\n                        this.editor.disableAutoHeight();\r\n                    }\r\n\r\n                    document.documentElement.style.overflow = 'hidden';\r\n                    //修复，滚动条不收起的问题\r\n\r\n                    window.scrollTo(0,window.scrollY);\r\n                    this._bakCssText = this.getDom().style.cssText;\r\n                    this._bakCssText1 = this.getDom('iframeholder').style.cssText;\r\n                    editor.iframe.parentNode.style.width = '';\r\n                    this._updateFullScreen();\r\n                } else {\r\n                    while (container.tagName != \"BODY\") {\r\n                        container.style.position = nodeStack.shift();\r\n                        container = container.parentNode;\r\n                    }\r\n                    this.getDom().style.cssText = this._bakCssText;\r\n                    this.getDom('iframeholder').style.cssText = this._bakCssText1;\r\n                    if (this._bakAutoHeight) {\r\n                        editor.autoHeightEnabled = true;\r\n                        this.editor.enableAutoHeight();\r\n                    }\r\n\r\n                    document.documentElement.style.overflow = this._bakHtmlOverflow;\r\n                    document.body.style.overflow = this._bakBodyOverflow;\r\n                    editor.iframe.parentNode.style.width = this._bakEditorContaninerWidth + 'px';\r\n                    window.scrollTo(0, this._bakScrollTop);\r\n                }\r\n                if (browser.gecko && editor.body.contentEditable === 'true') {\r\n                    var input = document.createElement('input');\r\n                    document.body.appendChild(input);\r\n                    editor.body.contentEditable = false;\r\n                    setTimeout(function () {\r\n                        input.focus();\r\n                        setTimeout(function () {\r\n                            editor.body.contentEditable = true;\r\n                            editor.fireEvent('fullscreenchanged', fullscreen);\r\n                            editor.selection.getRange().moveToBookmark(bk).select(true);\r\n                            baidu.editor.dom.domUtils.remove(input);\r\n                            fullscreen && window.scroll(0, 0);\r\n                        }, 0)\r\n                    }, 0)\r\n                }\r\n\r\n                if(editor.body.contentEditable === 'true'){\r\n                    this.editor.fireEvent('fullscreenchanged', fullscreen);\r\n                    this.triggerLayout();\r\n                }\r\n\r\n            }\r\n        },\r\n        _updateFullScreen:function () {\r\n            if (this._fullscreen) {\r\n                var vpRect = uiUtils.getViewportRect();\r\n                this.getDom().style.cssText = 'border:0;position:absolute;left:0;top:' + (this.editor.options.topOffset || 0) + 'px;width:' + vpRect.width + 'px;height:' + vpRect.height + 'px;z-index:' + (this.getDom().style.zIndex * 1 + 100);\r\n                uiUtils.setViewportOffset(this.getDom(), { left:0, top:this.editor.options.topOffset || 0 });\r\n                this.editor.setHeight(vpRect.height - this.getDom('toolbarbox').offsetHeight - this.getDom('bottombar').offsetHeight - (this.editor.options.topOffset || 0),true);\r\n                //不手动调一下，会导致全屏失效\r\n                if(browser.gecko){\r\n                    try{\r\n                        window.onresize();\r\n                    }catch(e){\r\n\r\n                    }\r\n\r\n                }\r\n            }\r\n        },\r\n        _updateElementPath:function () {\r\n            var bottom = this.getDom('elementpath'), list;\r\n            if (this.elementPathEnabled && (list = this.editor.queryCommandValue('elementpath'))) {\r\n\r\n                var buff = [];\r\n                for (var i = 0, ci; ci = list[i]; i++) {\r\n                    buff[i] = this.formatHtml('<span unselectable=\"on\" onclick=\"$$.editor.execCommand(&quot;elementpath&quot;, &quot;' + i + '&quot;);\">' + ci + '</span>');\r\n                }\r\n                bottom.innerHTML = '<div class=\"edui-editor-breadcrumb\" onmousedown=\"return false;\">' + this.editor.getLang(\"elementPathTip\") + ': ' + buff.join(' &gt; ') + '</div>';\r\n\r\n            } else {\r\n                bottom.style.display = 'none'\r\n            }\r\n        },\r\n        disableElementPath:function () {\r\n            var bottom = this.getDom('elementpath');\r\n            bottom.innerHTML = '';\r\n            bottom.style.display = 'none';\r\n            this.elementPathEnabled = false;\r\n\r\n        },\r\n        enableElementPath:function () {\r\n            var bottom = this.getDom('elementpath');\r\n            bottom.style.display = '';\r\n            this.elementPathEnabled = true;\r\n            this._updateElementPath();\r\n        },\r\n        _scale:function () {\r\n            var doc = document,\r\n                editor = this.editor,\r\n                editorHolder = editor.container,\r\n                editorDocument = editor.document,\r\n                toolbarBox = this.getDom(\"toolbarbox\"),\r\n                bottombar = this.getDom(\"bottombar\"),\r\n                scale = this.getDom(\"scale\"),\r\n                scalelayer = this.getDom(\"scalelayer\");\r\n\r\n            var isMouseMove = false,\r\n                position = null,\r\n                minEditorHeight = 0,\r\n                minEditorWidth = editor.options.minFrameWidth,\r\n                pageX = 0,\r\n                pageY = 0,\r\n                scaleWidth = 0,\r\n                scaleHeight = 0;\r\n\r\n            function down() {\r\n                position = domUtils.getXY(editorHolder);\r\n\r\n                if (!minEditorHeight) {\r\n                    minEditorHeight = editor.options.minFrameHeight + toolbarBox.offsetHeight + bottombar.offsetHeight;\r\n                }\r\n\r\n                scalelayer.style.cssText = \"position:absolute;left:0;display:;top:0;background-color:#41ABFF;opacity:0.4;filter: Alpha(opacity=40);width:\" + editorHolder.offsetWidth + \"px;height:\"\r\n                    + editorHolder.offsetHeight + \"px;z-index:\" + (editor.options.zIndex + 1);\r\n\r\n                domUtils.on(doc, \"mousemove\", move);\r\n                domUtils.on(editorDocument, \"mouseup\", up);\r\n                domUtils.on(doc, \"mouseup\", up);\r\n            }\r\n\r\n            var me = this;\r\n            //by xuheng 全屏时关掉缩放\r\n            this.editor.addListener('fullscreenchanged', function (e, fullScreen) {\r\n                if (fullScreen) {\r\n                    me.disableScale();\r\n\r\n                } else {\r\n                    if (me.editor.options.scaleEnabled) {\r\n                        me.enableScale();\r\n                        var tmpNode = me.editor.document.createElement('span');\r\n                        me.editor.body.appendChild(tmpNode);\r\n                        me.editor.body.style.height = Math.max(domUtils.getXY(tmpNode).y, me.editor.iframe.offsetHeight - 20) + 'px';\r\n                        domUtils.remove(tmpNode)\r\n                    }\r\n                }\r\n            });\r\n            function move(event) {\r\n                clearSelection();\r\n                var e = event || window.event;\r\n                pageX = e.pageX || (doc.documentElement.scrollLeft + e.clientX);\r\n                pageY = e.pageY || (doc.documentElement.scrollTop + e.clientY);\r\n                scaleWidth = pageX - position.x;\r\n                scaleHeight = pageY - position.y;\r\n\r\n                if (scaleWidth >= minEditorWidth) {\r\n                    isMouseMove = true;\r\n                    scalelayer.style.width = scaleWidth + 'px';\r\n                }\r\n                if (scaleHeight >= minEditorHeight) {\r\n                    isMouseMove = true;\r\n                    scalelayer.style.height = scaleHeight + \"px\";\r\n                }\r\n            }\r\n\r\n            function up() {\r\n                if (isMouseMove) {\r\n                    isMouseMove = false;\r\n                    editor.ui._actualFrameWidth = scalelayer.offsetWidth - 2;\r\n                    editorHolder.style.width = editor.ui._actualFrameWidth + 'px';\r\n\r\n                    editor.setHeight(scalelayer.offsetHeight - bottombar.offsetHeight - toolbarBox.offsetHeight - 2,true);\r\n                }\r\n                if (scalelayer) {\r\n                    scalelayer.style.display = \"none\";\r\n                }\r\n                clearSelection();\r\n                domUtils.un(doc, \"mousemove\", move);\r\n                domUtils.un(editorDocument, \"mouseup\", up);\r\n                domUtils.un(doc, \"mouseup\", up);\r\n            }\r\n\r\n            function clearSelection() {\r\n                if (browser.ie)\r\n                    doc.selection.clear();\r\n                else\r\n                    window.getSelection().removeAllRanges();\r\n            }\r\n\r\n            this.enableScale = function () {\r\n                //trace:2868\r\n                if (editor.queryCommandState(\"source\") == 1)    return;\r\n                scale.style.display = \"\";\r\n                this.scaleEnabled = true;\r\n                domUtils.on(scale, \"mousedown\", down);\r\n            };\r\n            this.disableScale = function () {\r\n                scale.style.display = \"none\";\r\n                this.scaleEnabled = false;\r\n                domUtils.un(scale, \"mousedown\", down);\r\n            };\r\n        },\r\n        isFullScreen:function () {\r\n            return this._fullscreen;\r\n        },\r\n        postRender:function () {\r\n            UIBase.prototype.postRender.call(this);\r\n            for (var i = 0; i < this.toolbars.length; i++) {\r\n                this.toolbars[i].postRender();\r\n            }\r\n            var me = this;\r\n            var timerId,\r\n                domUtils = baidu.editor.dom.domUtils,\r\n                updateFullScreenTime = function () {\r\n                    clearTimeout(timerId);\r\n                    timerId = setTimeout(function () {\r\n                        me._updateFullScreen();\r\n                    });\r\n                };\r\n            domUtils.on(window, 'resize', updateFullScreenTime);\r\n\r\n            me.addListener('destroy', function () {\r\n                domUtils.un(window, 'resize', updateFullScreenTime);\r\n                clearTimeout(timerId);\r\n            })\r\n        },\r\n        showToolbarMsg:function (msg, flag) {\r\n            this.getDom('toolbarmsg_label').innerHTML = msg;\r\n            this.getDom('toolbarmsg').style.display = '';\r\n            //\r\n            if (!flag) {\r\n                var w = this.getDom('upload_dialog');\r\n                w.style.display = 'none';\r\n            }\r\n        },\r\n        hideToolbarMsg:function () {\r\n            this.getDom('toolbarmsg').style.display = 'none';\r\n        },\r\n        mapUrl:function (url) {\r\n            return url ? url.replace('~/', this.editor.options.UEDITOR_HOME_URL || '') : ''\r\n        },\r\n        triggerLayout:function () {\r\n            var dom = this.getDom();\r\n            if (dom.style.zoom == '1') {\r\n                dom.style.zoom = '100%';\r\n            } else {\r\n                dom.style.zoom = '1';\r\n            }\r\n        }\r\n    };\r\n    utils.inherits(EditorUI, baidu.editor.ui.UIBase);\r\n\r\n\r\n    var instances = {};\r\n\r\n\r\n    UE.ui.Editor = function (options) {\r\n        var editor = new UE.Editor(options);\r\n        editor.options.editor = editor;\r\n        utils.loadFile(document, {\r\n            href:editor.options.themePath + editor.options.theme + \"/css/ueditor.css\",\r\n            tag:\"link\",\r\n            type:\"text/css\",\r\n            rel:\"stylesheet\"\r\n        });\r\n\r\n        var oldRender = editor.render;\r\n        editor.render = function (holder) {\r\n            if (holder.constructor === String) {\r\n                editor.key = holder;\r\n                instances[holder] = editor;\r\n            }\r\n            utils.domReady(function () {\r\n                editor.langIsReady ? renderUI() : editor.addListener(\"langReady\", renderUI);\r\n                function renderUI() {\r\n                    editor.setOpt({\r\n                        labelMap:editor.options.labelMap || editor.getLang('labelMap')\r\n                    });\r\n                    new EditorUI(editor.options);\r\n                    if (holder) {\r\n                        if (holder.constructor === String) {\r\n                            holder = document.getElementById(holder);\r\n                        }\r\n                        holder && holder.getAttribute('name') && ( editor.options.textarea = holder.getAttribute('name'));\r\n                        if (holder && /script|textarea/ig.test(holder.tagName)) {\r\n                            var newDiv = document.createElement('div');\r\n                            holder.parentNode.insertBefore(newDiv, holder);\r\n                            var cont = holder.value || holder.innerHTML;\r\n                            editor.options.initialContent = /^[\\t\\r\\n ]*$/.test(cont) ? editor.options.initialContent :\r\n                                cont.replace(/>[\\n\\r\\t]+([ ]{4})+/g, '>')\r\n                                    .replace(/[\\n\\r\\t]+([ ]{4})+</g, '<')\r\n                                    .replace(/>[\\n\\r\\t]+</g, '><');\r\n                            holder.className && (newDiv.className = holder.className);\r\n                            holder.style.cssText && (newDiv.style.cssText = holder.style.cssText);\r\n                            if (/textarea/i.test(holder.tagName)) {\r\n                                editor.textarea = holder;\r\n                                editor.textarea.style.display = 'none';\r\n\r\n\r\n                            } else {\r\n                                holder.parentNode.removeChild(holder);\r\n\r\n\r\n                            }\r\n                            if(holder.id){\r\n                                newDiv.id = holder.id;\r\n                                domUtils.removeAttributes(holder,'id');\r\n                            }\r\n                            holder = newDiv;\r\n                            holder.innerHTML = '';\r\n                        }\r\n\r\n                    }\r\n                    domUtils.addClass(holder, \"edui-\" + editor.options.theme);\r\n                    editor.ui.render(holder);\r\n                    var opt = editor.options;\r\n                    //给实例添加一个编辑器的容器引用\r\n                    editor.container = editor.ui.getDom();\r\n                    var parents = domUtils.findParents(holder,true);\r\n                    var displays = [];\r\n                    for(var i = 0 ,ci;ci=parents[i];i++){\r\n                        displays[i] = ci.style.display;\r\n                        ci.style.display = 'block'\r\n                    }\r\n                    if (opt.initialFrameWidth) {\r\n                        opt.minFrameWidth = opt.initialFrameWidth;\r\n                    } else {\r\n                        opt.minFrameWidth = opt.initialFrameWidth = holder.offsetWidth;\r\n                        var styleWidth = holder.style.width;\r\n                        if(/%$/.test(styleWidth)) {\r\n                            opt.initialFrameWidth = styleWidth;\r\n                        }\r\n                    }\r\n                    if (opt.initialFrameHeight) {\r\n                        opt.minFrameHeight = opt.initialFrameHeight;\r\n                    } else {\r\n                        opt.initialFrameHeight = opt.minFrameHeight = holder.offsetHeight;\r\n                    }\r\n                    for(var i = 0 ,ci;ci=parents[i];i++){\r\n                        ci.style.display =  displays[i]\r\n                    }\r\n                    //编辑器最外容器设置了高度，会导致，编辑器不占位\r\n                    //todo 先去掉，没有找到原因\r\n                    if(holder.style.height){\r\n                        holder.style.height = ''\r\n                    }\r\n                    editor.container.style.width = opt.initialFrameWidth + (/%$/.test(opt.initialFrameWidth) ? '' : 'px');\r\n                    editor.container.style.zIndex = opt.zIndex;\r\n                    oldRender.call(editor, editor.ui.getDom('iframeholder'));\r\n                    editor.fireEvent(\"afteruiready\");\r\n                }\r\n            })\r\n        };\r\n        return editor;\r\n    };\r\n\r\n\r\n    /**\r\n     * @file\r\n     * @name UE\r\n     * @short UE\r\n     * @desc UEditor的顶部命名空间\r\n     */\r\n    /**\r\n     * @name getEditor\r\n     * @since 1.2.4+\r\n     * @grammar UE.getEditor(id,[opt])  =>  Editor实例\r\n     * @desc 提供一个全局的方法得到编辑器实例\r\n     *\r\n     * * ''id''  放置编辑器的容器id, 如果容器下的编辑器已经存在，就直接返回\r\n     * * ''opt'' 编辑器的可选参数\r\n     * @example\r\n     *  UE.getEditor('containerId',{onready:function(){//创建一个编辑器实例\r\n     *      this.setContent('hello')\r\n     *  }});\r\n     *  UE.getEditor('containerId'); //返回刚创建的实例\r\n     *\r\n     */\r\n    UE.getEditor = function (id, opt) {\r\n        var editor = instances[id];\r\n        if (!editor) {\r\n            editor = instances[id] = new UE.ui.Editor(opt);\r\n            editor.render(id);\r\n        }\r\n        return editor;\r\n    };\r\n\r\n\r\n    UE.delEditor = function (id) {\r\n        var editor;\r\n        if (editor = instances[id]) {\r\n            editor.key && editor.destroy();\r\n            delete instances[id]\r\n        }\r\n    };\r\n\r\n    UE.registerUI = function(uiName,fn,index,editorId){\r\n        utils.each(uiName.split(/\\s+/), function (name) {\r\n            UE._customizeUI[name] = {\r\n                id : editorId,\r\n                execFn:fn,\r\n                index:index\r\n            };\r\n        })\r\n\r\n    }\r\n\r\n})();\r\n\r\n// adapter/message.js\r\nUE.registerUI('message', function(editor) {\r\n\r\n    var editorui = baidu.editor.ui;\r\n    var Message = editorui.Message;\r\n    var holder;\r\n    var _messageItems = [];\r\n    var me = editor;\r\n\r\n    me.addListener('ready', function(){\r\n        holder = document.getElementById(me.ui.id + '_message_holder');\r\n        updateHolderPos();\r\n        // HaoChuan9421\r\n        // setTimeout(function(){\r\n        //     updateHolderPos();\r\n        // }, 500);\r\n    });\r\n\r\n    me.addListener('showmessage', function(type, opt){\r\n        opt = utils.isString(opt) ? {\r\n            'content': opt\r\n        } : opt;\r\n        var message = new Message({\r\n                'timeout': opt.timeout,\r\n                'type': opt.type,\r\n                'content': opt.content,\r\n                'keepshow': opt.keepshow,\r\n                'editor': me\r\n            }),\r\n            mid = opt.id || ('msg_' + (+new Date()).toString(36));\r\n        message.render(holder);\r\n        _messageItems[mid] = message;\r\n        message.reset(opt);\r\n        updateHolderPos();\r\n        return mid;\r\n    });\r\n\r\n    me.addListener('updatemessage',function(type, id, opt){\r\n        opt = utils.isString(opt) ? {\r\n            'content': opt\r\n        } : opt;\r\n        var message = _messageItems[id];\r\n        message.render(holder);\r\n        message && message.reset(opt);\r\n    });\r\n\r\n    me.addListener('hidemessage',function(type, id){\r\n        var message = _messageItems[id];\r\n        message && message.hide();\r\n    });\r\n\r\n    function updateHolderPos(){\r\n        var toolbarbox = me.ui.getDom('toolbarbox');\r\n        if (toolbarbox) {\r\n            holder.style.top = toolbarbox.offsetHeight + 3 + 'px';\r\n        }\r\n        holder.style.zIndex = Math.max(me.options.zIndex, me.iframe.style.zIndex) + 1;\r\n    }\r\n\r\n});\r\n\r\n\r\n// adapter/autosave.js\r\nUE.registerUI('autosave', function(editor) {\r\n    var timer = null,uid = null;\r\n    editor.on('afterautosave',function(){\r\n        clearTimeout(timer);\r\n\r\n        timer = setTimeout(function(){\r\n            if(uid){\r\n                editor.trigger('hidemessage',uid);\r\n            }\r\n            uid = editor.trigger('showmessage',{\r\n                content : editor.getLang('autosave.success'),\r\n                timeout : 2000\r\n            });\r\n\r\n        },2000)\r\n    })\r\n\r\n});\r\n\r\n\r\n\r\n})();\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/ueditor.config.js",
    "content": "/**\r\n * ueditor完整配置项\r\n * 可以在这里配置整个编辑器的特性\r\n */\r\n/** ************************提示********************************\r\n * 所有被注释的配置项均为UEditor默认值。\r\n * 修改默认配置请首先确保已经完全明确该参数的真实用途。\r\n * 主要有两种修改方案，一种是取消此处注释，然后修改成对应参数；另一种是在实例化编辑器时传入对应参数。\r\n * 当升级编辑器时，可直接使用旧版配置文件替换新版配置文件,不用担心旧版配置文件中因缺少新功能所需的参数而导致脚本报错。\r\n **************************提示********************************/\r\n\r\n(function () {\r\n    /**\r\n     * 编辑器资源文件根路径。它所表示的含义是：以编辑器实例化页面为当前路径，指向编辑器资源文件（即dialog等文件夹）的路径。\r\n     * 鉴于很多同学在使用编辑器的时候出现的种种路径问题，此处强烈建议大家使用\"相对于网站根目录的相对路径\"进行配置。\r\n     * \"相对于网站根目录的相对路径\"也就是以斜杠开头的形如\"/myProject/ueditor/\"这样的路径。\r\n     * 如果站点中有多个不在同一层级的页面需要实例化编辑器，且引用了同一UEditor的时候，此处的URL可能不适用于每个页面的编辑器。\r\n     * 因此，UEditor提供了针对不同页面的编辑器可单独配置的根路径，具体来说，在需要实例化编辑器的页面最顶部写上如下代码即可。当然，需要令此处的URL等于对应的配置。\r\n     * window.UEDITOR_HOME_URL = \"/xxxx/xxxx/\";\r\n     */\r\n    var URL = window.UEDITOR_HOME_URL || getUEBasePath();\r\n\r\n    /**\r\n     * 配置项主体。注意，此处所有涉及到路径的配置别遗漏URL变量。\r\n     */\r\n    window.UEDITOR_CONFIG = {\r\n\r\n        // 为编辑器实例添加一个路径，这个不能被注释\r\n        UEDITOR_HOME_URL: URL,\r\n\r\n        // 服务器统一请求接口路径\r\n        serverUrl: URL + 'php/controller.php',\r\n\r\n        // 工具栏上的所有的功能按钮和下拉框，可以在new编辑器的实例时选择自己需要的重新定义\r\n        toolbars: [[\r\n            'source', '|', 'undo', 'redo', '|',\r\n            'bold', 'italic', 'underline', 'strikethrough', '|', 'superscript', 'subscript', '|', 'forecolor', 'backcolor', '|',\r\n            'removeformat', '|', 'insertorderedlist', 'insertunorderedlist', '|', 'selectall', 'cleardoc', 'paragraph', '|',\r\n            'fontfamily', 'fontsize', '|', 'justifyleft', 'justifycenter', 'justifyright', 'justifyjustify', '|',\r\n            'link', 'unlink', '|', 'emotion', '|',\r\n            '|', 'horizontal', 'print', 'preview', 'fullscreen', 'drafts', 'formula'\r\n        ]],\r\n        // 当鼠标放在工具栏上时显示的tooltip提示,留空支持自动多语言配置，否则以配置值为准\r\n        //, labelMap:{\r\n        //    'anchor':'', 'undo':''\r\n        // }\r\n\r\n        // 语言配置项,默认是zh-cn。有需要的话也可以使用如下这样的方式来自动多语言切换，当然，前提条件是lang文件夹下存在对应的语言文件：\r\n        // lang值也可以通过自动获取 (navigator.language||navigator.browserLanguage ||navigator.userLanguage).toLowerCase()\r\n        //, lang:\"zh-cn\"\r\n        //, langPath:URL +\"lang/\"\r\n\r\n        // 主题配置项,默认是default。有需要的话也可以使用如下这样的方式来自动多主题切换，当然，前提条件是themes文件夹下存在对应的主题文件：\r\n        // 现有如下皮肤:default\r\n        //, theme:'default'\r\n        //, themePath:URL +\"themes/\"\r\n\r\n        //, zIndex : 900     //编辑器层级的基数,默认是900\r\n\r\n        // 针对getAllHtml方法，会在对应的head标签中增加该编码设置。\r\n        //, charset:\"utf-8\"\r\n\r\n        // 若实例化编辑器的页面手动修改的domain，此处需要设置为true\r\n        //, customDomain:false\r\n\r\n        // 常用配置项目\r\n        //, isShow : true    //默认显示编辑器\r\n\r\n        //, textarea:'editorValue' // 提交表单时，服务器获取编辑器提交内容的所用的参数，多实例时可以给容器name属性，会将name给定的值最为每个实例的键值，不用每次实例化的时候都设置这个值\r\n\r\n        //, initialContent:'欢迎使用ueditor!'    //初始化编辑器的内容,也可以通过textarea/script给值，看官网例子\r\n\r\n        //, autoClearinitialContent:true //是否自动清除编辑器初始内容，注意：如果focus属性设置为true,这个也为真，那么编辑器一上来就会触发导致初始化的内容看不到了\r\n\r\n        //, focus:false //初始化时，是否让编辑器获得焦点true或false\r\n\r\n        // 如果自定义，最好给p标签如下的行高，要不输入中文时，会有跳动感\r\n        //, initialStyle:'p{line-height:1em}'//编辑器层级的基数,可以用来改变字体等\r\n\r\n        //, iframeCssUrl: URL + '/themes/iframe.css' //给编辑区域的iframe引入一个css文件\r\n\r\n        // indentValue\r\n        // 首行缩进距离,默认是2em\r\n        //, indentValue:'2em'\r\n\r\n        //, initialFrameWidth:1000  //初始化编辑器宽度,默认1000\r\n        //, initialFrameHeight:320  //初始化编辑器高度,默认320\r\n\r\n        //, readonly : false //编辑器初始化结束后,编辑区域是否是只读的，默认是false\r\n\r\n        //, autoClearEmptyNode : true //getContent时，是否删除空的inlineElement节点（包括嵌套的情况）\r\n\r\n        // 启用自动保存\r\n        //, enableAutoSave: true\r\n        // 自动保存间隔时间， 单位ms\r\n        //, saveInterval: 500\r\n\r\n        //, fullscreen : false //是否开启初始化时即全屏，默认关闭\r\n\r\n        //, imagePopup:true      //图片操作的浮层开关，默认打开\r\n\r\n        //, autoSyncData:true //自动同步编辑器要提交的数据\r\n        //, emotionLocalization:false //是否开启表情本地化，默认关闭。若要开启请确保emotion文件夹下包含官网提供的images表情文件夹\r\n\r\n        // 粘贴只保留标签，去除标签所有属性\r\n        //, retainOnlyLabelPasted: false\r\n\r\n        //, pasteplain:false  //是否默认为纯文本粘贴。false为不使用纯文本粘贴，true为使用纯文本粘贴\r\n        // 纯文本粘贴模式下的过滤规则\r\n        // 'filterTxtRules' : function(){\r\n        //    function transP(node){\r\n        //        node.tagName = 'p';\r\n        //        node.setStyle();\r\n        //    }\r\n        //    return {\r\n        //        //直接删除及其字节点内容\r\n        //        '-' : 'script style object iframe embed input select',\r\n        //        'p': {$:{}},\r\n        //        'br':{$:{}},\r\n        //        'div':{'$':{}},\r\n        //        'li':{'$':{}},\r\n        //        'caption':transP,\r\n        //        'th':transP,\r\n        //        'tr':transP,\r\n        //        'h1':transP,'h2':transP,'h3':transP,'h4':transP,'h5':transP,'h6':transP,\r\n        //        'td':function(node){\r\n        //            //没有内容的td直接删掉\r\n        //            var txt = !!node.innerText();\r\n        //            if(txt){\r\n        //                node.parentNode.insertAfter(UE.uNode.createText(' &nbsp; &nbsp;'),node);\r\n        //            }\r\n        //            node.parentNode.removeChild(node,node.innerText())\r\n        //        }\r\n        //    }\r\n        // }()\r\n\r\n        //, allHtmlEnabled:false //提交到后台的数据是否包含整个html字符串\r\n\r\n        // insertorderedlist\r\n        // 有序列表的下拉配置,值留空时支持多语言自动识别，若配置值，则以此值为准\r\n        //, 'insertorderedlist':{\r\n        //      //自定的样式\r\n        //        'num':'1,2,3...',\r\n        //        'num1':'1),2),3)...',\r\n        //        'num2':'(1),(2),(3)...',\r\n        //        'cn':'一,二,三....',\r\n        //        'cn1':'一),二),三)....',\r\n        //        'cn2':'(一),(二),(三)....',\r\n        //     //系统自带\r\n        //     'decimal' : '' ,         //'1,2,3...'\r\n        //     'lower-alpha' : '' ,    // 'a,b,c...'\r\n        //     'lower-roman' : '' ,    //'i,ii,iii...'\r\n        //     'upper-alpha' : '' , lang   //'A,B,C'\r\n        //     'upper-roman' : ''      //'I,II,III...'\r\n        // }\r\n\r\n        // insertunorderedlist\r\n        // 无序列表的下拉配置，值留空时支持多语言自动识别，若配置值，则以此值为准\r\n        //, insertunorderedlist : { //自定的样式\r\n        //    'dash' :'— 破折号', //-破折号\r\n        //    'dot':' 。 小圆圈', //系统自带\r\n        //    'circle' : '',  // '○ 小圆圈'\r\n        //    'disc' : '',    // '● 小圆点'\r\n        //    'square' : ''   //'■ 小方块'\r\n        // }\r\n        //, listDefaultPaddingLeft : '30'//默认的左边缩进的基数倍\r\n        //, listiconpath : 'http://bs.baidu.com/listicon/'//自定义标号的路径\r\n        //, maxListLevel : 3 //限制可以tab的级数, 设置-1为不限制\r\n\r\n        //, autoTransWordToList:false  //禁止word中粘贴进来的列表自动变成列表标签\r\n\r\n        // fontfamily\r\n        // 字体设置 label留空支持多语言自动切换，若配置，则以配置值为准\r\n        //, 'fontfamily':[\r\n        //    { label:'',name:'songti',val:'宋体,SimSun'},\r\n        //    { label:'',name:'kaiti',val:'楷体,楷体_GB2312, SimKai'},\r\n        //    { label:'',name:'yahei',val:'微软雅黑,Microsoft YaHei'},\r\n        //    { label:'',name:'heiti',val:'黑体, SimHei'},\r\n        //    { label:'',name:'lishu',val:'隶书, SimLi'},\r\n        //    { label:'',name:'andaleMono',val:'andale mono'},\r\n        //    { label:'',name:'arial',val:'arial, helvetica,sans-serif'},\r\n        //    { label:'',name:'arialBlack',val:'arial black,avant garde'},\r\n        //    { label:'',name:'comicSansMs',val:'comic sans ms'},\r\n        //    { label:'',name:'impact',val:'impact,chicago'},\r\n        //    { label:'',name:'timesNewRoman',val:'times new roman'}\r\n        // ]\r\n\r\n        // fontsize\r\n        // 字号\r\n        //, 'fontsize':[10, 11, 12, 14, 16, 18, 20, 24, 36]\r\n\r\n        // paragraph\r\n        // 段落格式 值留空时支持多语言自动识别，若配置，则以配置值为准\r\n        //, 'paragraph':{'p':'', 'h1':'', 'h2':'', 'h3':'', 'h4':'', 'h5':'', 'h6':''}\r\n\r\n        // rowspacingtop\r\n        // 段间距 值和显示的名字相同\r\n        //, 'rowspacingtop':['5', '10', '15', '20', '25']\r\n\r\n        // rowspacingBottom\r\n        // 段间距 值和显示的名字相同\r\n        //, 'rowspacingbottom':['5', '10', '15', '20', '25']\r\n\r\n        // lineheight\r\n        // 行内间距 值和显示的名字相同\r\n        //, 'lineheight':['1', '1.5','1.75','2', '3', '4', '5']\r\n\r\n        // customstyle\r\n        // 自定义样式，不支持国际化，此处配置值即可最后显示值\r\n        // block的元素是依据设置段落的逻辑设置的，inline的元素依据BIU的逻辑设置\r\n        // 尽量使用一些常用的标签\r\n        // 参数说明\r\n        // tag 使用的标签名字\r\n        // label 显示的名字也是用来标识不同类型的标识符，注意这个值每个要不同，\r\n        // style 添加的样式\r\n        // 每一个对象就是一个自定义的样式\r\n        //, 'customstyle':[\r\n        //    {tag:'h1', name:'tc', label:'', style:'border-bottom:#ccc 2px solid;padding:0 4px 0 0;text-align:center;margin:0 0 20px 0;'},\r\n        //    {tag:'h1', name:'tl',label:'', style:'border-bottom:#ccc 2px solid;padding:0 4px 0 0;margin:0 0 10px 0;'},\r\n        //    {tag:'span',name:'im', label:'', style:'font-style:italic;font-weight:bold'},\r\n        //    {tag:'span',name:'hi', label:'', style:'font-style:italic;font-weight:bold;color:rgb(51, 153, 204)'}\r\n        // ]\r\n\r\n        // 打开右键菜单功能\r\n        //, enableContextMenu: true\r\n        // 右键菜单的内容，可以参考plugins/contextmenu.js里边的默认菜单的例子，label留空支持国际化，否则以此配置为准\r\n        //, contextMenu:[\r\n        //    {\r\n        //        label:'',       //显示的名称\r\n        //        cmdName:'selectall',//执行的command命令，当点击这个右键菜单时\r\n        //        //exec可选，有了exec就会在点击时执行这个function，优先级高于cmdName\r\n        //        exec:function () {\r\n        //            //this是当前编辑器的实例\r\n        //            //this.ui._dialogs['inserttableDialog'].open();\r\n        //        }\r\n        //    }\r\n        // ]\r\n\r\n        // 快捷菜单\r\n        //, shortcutMenu:[\"fontfamily\", \"fontsize\", \"bold\", \"italic\", \"underline\", \"forecolor\", \"backcolor\", \"insertorderedlist\", \"insertunorderedlist\"]\r\n\r\n        // elementPathEnabled\r\n        // 是否启用元素路径，默认是显示\r\n        //, elementPathEnabled : true\r\n\r\n        // wordCount\r\n        //, wordCount:true          //是否开启字数统计\r\n        //, maximumWords:10000       //允许的最大字符数\r\n        // 字数统计提示，{#count}代表当前字数，{#leave}代表还可以输入多少字符数,留空支持多语言自动切换，否则按此配置显示\r\n        //, wordCountMsg:''   //当前已输入 {#count} 个字符，您还可以输入{#leave} 个字符\r\n        // 超出字数限制提示  留空支持多语言自动切换，否则按此配置显示\r\n        //, wordOverFlowMsg:''    //<span style=\"color:red;\">你输入的字符个数已经超出最大允许值，服务器可能会拒绝保存！</span>\r\n\r\n        // tab\r\n        // 点击tab键时移动的距离,tabSize倍数，tabNode什么字符做为单位\r\n        //, tabSize:4\r\n        //, tabNode:'&nbsp;'\r\n\r\n        // removeFormat\r\n        // 清除格式时可以删除的标签和属性\r\n        // removeForamtTags标签\r\n        //, removeFormatTags:'b,big,code,del,dfn,em,font,i,ins,kbd,q,samp,small,span,strike,strong,sub,sup,tt,u,var'\r\n        // removeFormatAttributes属性\r\n        //, removeFormatAttributes:'class,style,lang,width,height,align,hspace,valign'\r\n\r\n        // undo\r\n        // 可以最多回退的次数,默认20\r\n        //, maxUndoCount:20\r\n        // 当输入的字符数超过该值时，保存一次现场\r\n        //, maxInputCount:1\r\n\r\n        // autoHeightEnabled\r\n        // 是否自动长高,默认true\r\n        //, autoHeightEnabled:true\r\n\r\n        // scaleEnabled\r\n        // 是否可以拉伸长高,默认true(当开启时，自动长高失效)\r\n        //, scaleEnabled:false\r\n        //, minFrameWidth:800    //编辑器拖动时最小宽度,默认800\r\n        //, minFrameHeight:220  //编辑器拖动时最小高度,默认220\r\n\r\n        // autoFloatEnabled\r\n        // 是否保持toolbar的位置不动,默认true\r\n        //, autoFloatEnabled:true\r\n        // 浮动时工具栏距离浏览器顶部的高度，用于某些具有固定头部的页面\r\n        //, topOffset:30\r\n        // 编辑器底部距离工具栏高度(如果参数大于等于编辑器高度，则设置无效)\r\n        //, toolbarTopOffset:400\r\n\r\n        // 设置远程图片是否抓取到本地保存\r\n        //, catchRemoteImageEnable: true //设置是否抓取远程图片\r\n\r\n        // pageBreakTag\r\n        // 分页标识符,默认是_ueditor_page_break_tag_\r\n        //, pageBreakTag:'_ueditor_page_break_tag_'\r\n\r\n        // autotypeset\r\n        // 自动排版参数\r\n        //, autotypeset: {\r\n        //    mergeEmptyline: true,           //合并空行\r\n        //    removeClass: true,              //去掉冗余的class\r\n        //    removeEmptyline: false,         //去掉空行\r\n        //    textAlign:\"left\",               //段落的排版方式，可以是 left,right,center,justify 去掉这个属性表示不执行排版\r\n        //    imageBlockLine: 'center',       //图片的浮动方式，独占一行剧中,左右浮动，默认: center,left,right,none 去掉这个属性表示不执行排版\r\n        //    pasteFilter: false,             //根据规则过滤没事粘贴进来的内容\r\n        //    clearFontSize: false,           //去掉所有的内嵌字号，使用编辑器默认的字号\r\n        //    clearFontFamily: false,         //去掉所有的内嵌字体，使用编辑器默认的字体\r\n        //    removeEmptyNode: false,         // 去掉空节点\r\n        //    //可以去掉的标签\r\n        //    removeTagNames: {标签名字:1},\r\n        //    indent: false,                  // 行首缩进\r\n        //    indentValue : '2em',            //行首缩进的大小\r\n        //    bdc2sb: false,\r\n        //    tobdc: false\r\n        // }\r\n\r\n        // tableDragable\r\n        // 表格是否可以拖拽\r\n        //, tableDragable: true\r\n\r\n        // sourceEditor\r\n        // 源码的查看方式,codemirror 是代码高亮，textarea是文本框,默认是codemirror\r\n        // 注意默认codemirror只能在ie8+和非ie中使用\r\n        //, sourceEditor:\"codemirror\"\r\n        // 如果sourceEditor是codemirror，还用配置一下两个参数\r\n        // codeMirrorJsUrl js加载的路径，默认是 URL + \"third-party/codemirror/codemirror.js\"\r\n        //, codeMirrorJsUrl:URL + \"third-party/codemirror/codemirror.js\"\r\n        // codeMirrorCssUrl css加载的路径，默认是 URL + \"third-party/codemirror/codemirror.css\"\r\n        //, codeMirrorCssUrl:URL + \"third-party/codemirror/codemirror.css\"\r\n        // 编辑器初始化完成后是否进入源码模式，默认为否。\r\n        //, sourceEditorFirst:false\r\n\r\n        // iframeUrlMap\r\n        // dialog内容的路径 ～会被替换成URL,垓属性一旦打开，将覆盖所有的dialog的默认路径\r\n        //, iframeUrlMap:{\r\n        //    'anchor':'~/dialogs/anchor/anchor.html',\r\n        // }\r\n\r\n        // allowLinkProtocol 允许的链接地址，有这些前缀的链接地址不会自动添加http\r\n        //, allowLinkProtocols: ['http:', 'https:', '#', '/', 'ftp:', 'mailto:', 'tel:', 'git:', 'svn:']\r\n\r\n        // webAppKey 百度应用的APIkey，每个站长必须首先去百度官网注册一个key后方能正常使用app功能，注册介绍，http://app.baidu.com/static/cms/getapikey.html\r\n        //, webAppKey: \"\"\r\n\r\n        // 默认过滤规则相关配置项目\r\n        //, disabledTableInTable:true  //禁止表格嵌套\r\n        //, allowDivTransToP:true      //允许进入编辑器的div标签自动变成p标签\r\n        //, rgb2Hex:true               //默认产出的数据中的color自动从rgb格式变成16进制格式\r\n\r\n        // xss 过滤是否开启,inserthtml等操作\r\n        xssFilterRules: true,\r\n        // input xss过滤\r\n        inputXssFilter: true,\r\n        // output xss过滤\r\n        outputXssFilter: true,\r\n        // xss过滤白名单 名单来源: https://raw.githubusercontent.com/leizongmin/js-xss/master/lib/default.js\r\n        whiteList: {\r\n            a: ['target', 'href', 'title', 'class', 'style'],\r\n            abbr: ['title', 'class', 'style'],\r\n            address: ['class', 'style'],\r\n            area: ['shape', 'coords', 'href', 'alt'],\r\n            article: [],\r\n            aside: [],\r\n            audio: ['autoplay', 'controls', 'loop', 'preload', 'src', 'class', 'style'],\r\n            b: ['class', 'style'],\r\n            bdi: ['dir'],\r\n            bdo: ['dir'],\r\n            big: [],\r\n            blockquote: ['cite', 'class', 'style'],\r\n            br: [],\r\n            caption: ['class', 'style'],\r\n            center: [],\r\n            cite: [],\r\n            code: ['class', 'style'],\r\n            col: ['align', 'valign', 'span', 'width', 'class', 'style'],\r\n            colgroup: ['align', 'valign', 'span', 'width', 'class', 'style'],\r\n            dd: ['class', 'style'],\r\n            del: ['datetime'],\r\n            details: ['open'],\r\n            div: ['class', 'style'],\r\n            dl: ['class', 'style'],\r\n            dt: ['class', 'style'],\r\n            em: ['class', 'style'],\r\n            font: ['color', 'size', 'face'],\r\n            footer: [],\r\n            h1: ['class', 'style'],\r\n            h2: ['class', 'style'],\r\n            h3: ['class', 'style'],\r\n            h4: ['class', 'style'],\r\n            h5: ['class', 'style'],\r\n            h6: ['class', 'style'],\r\n            header: [],\r\n            hr: [],\r\n            i: ['class', 'style'],\r\n            img: ['src', 'alt', 'title', 'width', 'height', 'id', '_src', 'loadingclass', 'class', 'data-latex'],\r\n            ins: ['datetime'],\r\n            li: ['class', 'style'],\r\n            mark: [],\r\n            nav: [],\r\n            ol: ['class', 'style'],\r\n            p: ['class', 'style'],\r\n            pre: ['class', 'style'],\r\n            s: [],\r\n            section: [],\r\n            small: [],\r\n            span: ['class', 'style'],\r\n            sub: ['class', 'style'],\r\n            sup: ['class', 'style'],\r\n            strong: ['class', 'style'],\r\n            table: ['width', 'border', 'align', 'valign', 'class', 'style'],\r\n            tbody: ['align', 'valign', 'class', 'style'],\r\n            td: ['width', 'rowspan', 'colspan', 'align', 'valign', 'class', 'style'],\r\n            tfoot: ['align', 'valign', 'class', 'style'],\r\n            th: ['width', 'rowspan', 'colspan', 'align', 'valign', 'class', 'style'],\r\n            thead: ['align', 'valign', 'class', 'style'],\r\n            tr: ['rowspan', 'align', 'valign', 'class', 'style'],\r\n            tt: [],\r\n            u: [],\r\n            ul: ['class', 'style'],\r\n            video: ['autoplay', 'controls', 'loop', 'preload', 'src', 'height', 'width', 'class', 'style']\r\n        }\r\n    };\r\n\r\n    function getUEBasePath (docUrl, confUrl) {\r\n        return getBasePath(docUrl || self.document.URL || self.location.href, confUrl || getConfigFilePath());\r\n    }\r\n\r\n    function getConfigFilePath () {\r\n        var configPath = document.getElementsByTagName('script');\r\n\r\n        return configPath[ configPath.length - 1 ].src;\r\n    }\r\n\r\n    function getBasePath (docUrl, confUrl) {\r\n        var basePath = confUrl;\r\n\r\n        if (/^(\\/|\\\\\\\\)/.test(confUrl)) {\r\n            basePath = /^.+?\\w(\\/|\\\\\\\\)/.exec(docUrl)[0] + confUrl.replace(/^(\\/|\\\\\\\\)/, '');\r\n        } else if (!/^[a-z]+:/i.test(confUrl)) {\r\n            docUrl = docUrl.split('#')[0].split('?')[0].replace(/[^\\\\\\/]+$/, '');\r\n\r\n            basePath = docUrl + '' + confUrl;\r\n        }\r\n\r\n        return optimizationPath(basePath);\r\n    }\r\n\r\n    function optimizationPath (path) {\r\n        let protocol = /^[a-z]+:\\/\\//.exec(path)[ 0 ],\r\n            tmp = null,\r\n            res = [];\r\n\r\n        path = path.replace(protocol, '').split('?')[0].split('#')[0];\r\n\r\n        path = path.replace(/\\\\/g, '/').split(/\\//);\r\n\r\n        path[ path.length - 1 ] = '';\r\n\r\n        while (path.length) {\r\n            if ((tmp = path.shift()) === '..') {\r\n                res.pop();\r\n            } else if (tmp !== '.') {\r\n                res.push(tmp);\r\n            }\r\n        }\r\n\r\n        return protocol + res.join('/');\r\n    }\r\n\r\n    window.UE = {\r\n        getUEBasePath: getUEBasePath\r\n    };\r\n})();\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor/ueditor.parse.js",
    "content": "/*!\r\n * UEditor\r\n * version: ueditor\r\n * build: Wed Dec 26 2018 17:25:05 GMT+0800 (CST)\r\n */\r\n\r\n(function(){\r\n\r\n(function(){\r\n    UE = window.UE || {};\r\n    var isIE = !!window.ActiveXObject;\r\n    //定义utils工具\r\n    var utils = {\r\n            removeLastbs : function(url){\r\n                return url.replace(/\\/$/,'')\r\n            },\r\n            extend : function(t,s){\r\n                var a = arguments,\r\n                    notCover = this.isBoolean(a[a.length - 1]) ? a[a.length - 1] : false,\r\n                    len = this.isBoolean(a[a.length - 1]) ? a.length - 1 : a.length;\r\n                for (var i = 1; i < len; i++) {\r\n                    var x = a[i];\r\n                    for (var k in x) {\r\n                        if (!notCover || !t.hasOwnProperty(k)) {\r\n                            t[k] = x[k];\r\n                        }\r\n                    }\r\n                }\r\n                return t;\r\n            },\r\n            isIE : isIE,\r\n            cssRule : isIE ? function(key,style,doc){\r\n                var indexList,index;\r\n                doc = doc || document;\r\n                if(doc.indexList){\r\n                    indexList = doc.indexList;\r\n                }else{\r\n                    indexList = doc.indexList =  {};\r\n                }\r\n                var sheetStyle;\r\n                if(!indexList[key]){\r\n                    if(style === undefined){\r\n                        return ''\r\n                    }\r\n                    sheetStyle = doc.createStyleSheet('',index = doc.styleSheets.length);\r\n                    indexList[key] = index;\r\n                }else{\r\n                    sheetStyle = doc.styleSheets[indexList[key]];\r\n                }\r\n                if(style === undefined){\r\n                    return sheetStyle.cssText\r\n                }\r\n                sheetStyle.cssText = sheetStyle.cssText + '\\n' + (style || '')\r\n            } : function(key,style,doc){\r\n                doc = doc || document;\r\n                var head = doc.getElementsByTagName('head')[0],node;\r\n                if(!(node = doc.getElementById(key))){\r\n                    if(style === undefined){\r\n                        return ''\r\n                    }\r\n                    node = doc.createElement('style');\r\n                    node.id = key;\r\n                    head.appendChild(node)\r\n                }\r\n                if(style === undefined){\r\n                    return node.innerHTML\r\n                }\r\n                if(style !== ''){\r\n                    node.innerHTML = node.innerHTML + '\\n' + style;\r\n                }else{\r\n                    head.removeChild(node)\r\n                }\r\n            },\r\n            domReady : function (onready) {\r\n                var doc = window.document;\r\n                if (doc.readyState === \"complete\") {\r\n                    onready();\r\n                }else{\r\n                    if (isIE) {\r\n                        (function () {\r\n                            if (doc.isReady) return;\r\n                            try {\r\n                                doc.documentElement.doScroll(\"left\");\r\n                            } catch (error) {\r\n                                setTimeout(arguments.callee, 0);\r\n                                return;\r\n                            }\r\n                            onready();\r\n                        })();\r\n                        window.attachEvent('onload', function(){\r\n                            onready()\r\n                        });\r\n                    } else {\r\n                        doc.addEventListener(\"DOMContentLoaded\", function () {\r\n                            doc.removeEventListener(\"DOMContentLoaded\", arguments.callee, false);\r\n                            onready();\r\n                        }, false);\r\n                        window.addEventListener('load', function(){onready()}, false);\r\n                    }\r\n                }\r\n\r\n            },\r\n            each : function(obj, iterator, context) {\r\n                if (obj == null) return;\r\n                if (obj.length === +obj.length) {\r\n                    for (var i = 0, l = obj.length; i < l; i++) {\r\n                        if(iterator.call(context, obj[i], i, obj) === false)\r\n                            return false;\r\n                    }\r\n                } else {\r\n                    for (var key in obj) {\r\n                        if (obj.hasOwnProperty(key)) {\r\n                            if(iterator.call(context, obj[key], key, obj) === false)\r\n                                return false;\r\n                        }\r\n                    }\r\n                }\r\n            },\r\n            inArray : function(arr,item){\r\n                var index = -1;\r\n                this.each(arr,function(v,i){\r\n                    if(v === item){\r\n                        index = i;\r\n                        return false;\r\n                    }\r\n                });\r\n                return index;\r\n            },\r\n            pushItem : function(arr,item){\r\n                if(this.inArray(arr,item)==-1){\r\n                    arr.push(item)\r\n                }\r\n            },\r\n            trim: function (str) {\r\n                return str.replace(/(^[ \\t\\n\\r]+)|([ \\t\\n\\r]+$)/g, '');\r\n            },\r\n            indexOf: function (array, item, start) {\r\n                var index = -1;\r\n                start = this.isNumber(start) ? start : 0;\r\n                this.each(array, function (v, i) {\r\n                    if (i >= start && v === item) {\r\n                        index = i;\r\n                        return false;\r\n                    }\r\n                });\r\n                return index;\r\n            },\r\n            hasClass: function (element, className) {\r\n                className = className.replace(/(^[ ]+)|([ ]+$)/g, '').replace(/[ ]{2,}/g, ' ').split(' ');\r\n                for (var i = 0, ci, cls = element.className; ci = className[i++];) {\r\n                    if (!new RegExp('\\\\b' + ci + '\\\\b', 'i').test(cls)) {\r\n                        return false;\r\n                    }\r\n                }\r\n                return i - 1 == className.length;\r\n            },\r\n            addClass:function (elm, classNames) {\r\n                if(!elm)return;\r\n                classNames = this.trim(classNames).replace(/[ ]{2,}/g,' ').split(' ');\r\n                for(var i = 0,ci,cls = elm.className;ci=classNames[i++];){\r\n                    if(!new RegExp('\\\\b' + ci + '\\\\b').test(cls)){\r\n                        cls += ' ' + ci;\r\n                    }\r\n                }\r\n                elm.className = utils.trim(cls);\r\n            },\r\n            removeClass:function (elm, classNames) {\r\n                classNames = this.isArray(classNames) ? classNames :\r\n                    this.trim(classNames).replace(/[ ]{2,}/g,' ').split(' ');\r\n                for(var i = 0,ci,cls = elm.className;ci=classNames[i++];){\r\n                    cls = cls.replace(new RegExp('\\\\b' + ci + '\\\\b'),'')\r\n                }\r\n                cls = this.trim(cls).replace(/[ ]{2,}/g,' ');\r\n                elm.className = cls;\r\n                !cls && elm.removeAttribute('className');\r\n            },\r\n            on: function (element, type, handler) {\r\n                var types = this.isArray(type) ? type : type.split(/\\s+/),\r\n                    k = types.length;\r\n                if (k) while (k--) {\r\n                    type = types[k];\r\n                    if (element.addEventListener) {\r\n                        element.addEventListener(type, handler, false);\r\n                    } else {\r\n                        if (!handler._d) {\r\n                            handler._d = {\r\n                                els : []\r\n                            };\r\n                        }\r\n                        var key = type + handler.toString(),index = utils.indexOf(handler._d.els,element);\r\n                        if (!handler._d[key] || index == -1) {\r\n                            if(index == -1){\r\n                                handler._d.els.push(element);\r\n                            }\r\n                            if(!handler._d[key]){\r\n                                handler._d[key] = function (evt) {\r\n                                    return handler.call(evt.srcElement, evt || window.event);\r\n                                };\r\n                            }\r\n\r\n\r\n                            element.attachEvent('on' + type, handler._d[key]);\r\n                        }\r\n                    }\r\n                }\r\n                element = null;\r\n            },\r\n            off: function (element, type, handler) {\r\n                var types = this.isArray(type) ? type : type.split(/\\s+/),\r\n                    k = types.length;\r\n                if (k) while (k--) {\r\n                    type = types[k];\r\n                    if (element.removeEventListener) {\r\n                        element.removeEventListener(type, handler, false);\r\n                    } else {\r\n                        var key = type + handler.toString();\r\n                        try{\r\n                            element.detachEvent('on' + type, handler._d ? handler._d[key] : handler);\r\n                        }catch(e){}\r\n                        if (handler._d && handler._d[key]) {\r\n                            var index = utils.indexOf(handler._d.els,element);\r\n                            if(index!=-1){\r\n                                handler._d.els.splice(index,1);\r\n                            }\r\n                            handler._d.els.length == 0 && delete handler._d[key];\r\n                        }\r\n                    }\r\n                }\r\n            },\r\n            loadFile : function () {\r\n                var tmpList = [];\r\n                function getItem(doc,obj){\r\n                    try{\r\n                        for(var i= 0,ci;ci=tmpList[i++];){\r\n                            if(ci.doc === doc && ci.url == (obj.src || obj.href)){\r\n                                return ci;\r\n                            }\r\n                        }\r\n                    }catch(e){\r\n                        return null;\r\n                    }\r\n\r\n                }\r\n                return function (doc, obj, fn) {\r\n                    var item = getItem(doc,obj);\r\n                    if (item) {\r\n                        if(item.ready){\r\n                            fn && fn();\r\n                        }else{\r\n                            item.funs.push(fn)\r\n                        }\r\n                        return;\r\n                    }\r\n                    tmpList.push({\r\n                        doc:doc,\r\n                        url:obj.src||obj.href,\r\n                        funs:[fn]\r\n                    });\r\n                    if (!doc.body) {\r\n                        var html = [];\r\n                        for(var p in obj){\r\n                            if(p == 'tag')continue;\r\n                            html.push(p + '=\"' + obj[p] + '\"')\r\n                        }\r\n                        doc.write('<' + obj.tag + ' ' + html.join(' ') + ' ></'+obj.tag+'>');\r\n                        return;\r\n                    }\r\n                    if (obj.id && doc.getElementById(obj.id)) {\r\n                        return;\r\n                    }\r\n                    var element = doc.createElement(obj.tag);\r\n                    delete obj.tag;\r\n                    for (var p in obj) {\r\n                        element.setAttribute(p, obj[p]);\r\n                    }\r\n                    element.onload = element.onreadystatechange = function () {\r\n                        if (!this.readyState || /loaded|complete/.test(this.readyState)) {\r\n                            item = getItem(doc,obj);\r\n                            if (item.funs.length > 0) {\r\n                                item.ready = 1;\r\n                                for (var fi; fi = item.funs.pop();) {\r\n                                    fi();\r\n                                }\r\n                            }\r\n                            element.onload = element.onreadystatechange = null;\r\n                        }\r\n                    };\r\n                    element.onerror = function(){\r\n                        throw Error('The load '+(obj.href||obj.src)+' fails,check the url')\r\n                    };\r\n                    doc.getElementsByTagName(\"head\")[0].appendChild(element);\r\n                }\r\n            }()\r\n    };\r\n    utils.each(['String', 'Function', 'Array', 'Number', 'RegExp', 'Object','Boolean'], function (v) {\r\n        utils['is' + v] = function (obj) {\r\n            return Object.prototype.toString.apply(obj) == '[object ' + v + ']';\r\n        }\r\n    });\r\n    var parselist = {};\r\n    UE.parse = {\r\n        register : function(parseName,fn){\r\n            parselist[parseName] = fn;\r\n        },\r\n        load : function(opt){\r\n            utils.each(parselist,function(v){\r\n                v.call(opt,utils);\r\n            })\r\n        }\r\n    };\r\n    uParse = function(selector,opt){\r\n        utils.domReady(function(){\r\n            var contents;\r\n            if(document.querySelectorAll){\r\n                contents = document.querySelectorAll(selector)\r\n            }else{\r\n                if(/^#/.test(selector)){\r\n                    contents = [document.getElementById(selector.replace(/^#/,''))]\r\n                }else if(/^\\./.test(selector)){\r\n                    var contents = [];\r\n                    utils.each(document.getElementsByTagName('*'),function(node){\r\n                        if(node.className && new RegExp('\\\\b' + selector.replace(/^\\./,'') + '\\\\b','i').test(node.className)){\r\n                            contents.push(node)\r\n                        }\r\n                    })\r\n                }else{\r\n                    contents = document.getElementsByTagName(selector)\r\n                }\r\n            }\r\n            utils.each(contents,function(v){\r\n                UE.parse.load(utils.extend({root:v,selector:selector},opt))\r\n            })\r\n        })\r\n    }\r\n})();\r\n\r\nUE.parse.register('insertcode',function(utils){\r\n    var pres = this.root.getElementsByTagName('pre');\r\n    if(pres.length){\r\n        if(typeof XRegExp == \"undefined\"){\r\n            var jsurl,cssurl;\r\n            if(this.rootPath !== undefined){\r\n                jsurl = utils.removeLastbs(this.rootPath)  + '/third-party/SyntaxHighlighter/shCore.js';\r\n                cssurl = utils.removeLastbs(this.rootPath) + '/third-party/SyntaxHighlighter/shCoreDefault.css';\r\n            }else{\r\n                jsurl = this.highlightJsUrl;\r\n                cssurl = this.highlightCssUrl;\r\n            }\r\n            utils.loadFile(document,{\r\n                id : \"syntaxhighlighter_css\",\r\n                tag : \"link\",\r\n                rel : \"stylesheet\",\r\n                type : \"text/css\",\r\n                href : cssurl\r\n            });\r\n            utils.loadFile(document,{\r\n                id : \"syntaxhighlighter_js\",\r\n                src : jsurl,\r\n                tag : \"script\",\r\n                type : \"text/javascript\",\r\n                defer : \"defer\"\r\n            },function(){\r\n                utils.each(pres,function(pi){\r\n                    if(pi && /brush/i.test(pi.className)){\r\n                        SyntaxHighlighter.highlight(pi);\r\n                    }\r\n                });\r\n            });\r\n        }else{\r\n            utils.each(pres,function(pi){\r\n                if(pi && /brush/i.test(pi.className)){\r\n                    SyntaxHighlighter.highlight(pi);\r\n                }\r\n            });\r\n        }\r\n    }\r\n\r\n});\r\nUE.parse.register('table', function (utils) {\r\n    var me = this,\r\n        root = this.root,\r\n        tables = root.getElementsByTagName('table');\r\n    if (tables.length) {\r\n        var selector = this.selector;\r\n        //追加默认的表格样式\r\n        utils.cssRule('table',\r\n            selector + ' table.noBorderTable td,' +\r\n                selector + ' table.noBorderTable th,' +\r\n                selector + ' table.noBorderTable caption{border:1px dashed #ddd !important}' +\r\n                selector + ' table.sortEnabled tr.firstRow th,' + selector + ' table.sortEnabled tr.firstRow td{padding-right:20px; background-repeat: no-repeat;' +\r\n                    'background-position: center right; background-image:url(' + this.rootPath + 'themes/default/images/sortable.png);}' +\r\n                selector + ' table.sortEnabled tr.firstRow th:hover,' + selector + ' table.sortEnabled tr.firstRow td:hover{background-color: #EEE;}' +\r\n                selector + ' table{margin-bottom:10px;border-collapse:collapse;display:table;}' +\r\n                selector + ' td,' + selector + ' th{ background:white; padding: 5px 10px;border: 1px solid #DDD;}' +\r\n                selector + ' caption{border:1px dashed #DDD;border-bottom:0;padding:3px;text-align:center;}' +\r\n                selector + ' th{border-top:1px solid #BBB;background:#F7F7F7;}' +\r\n                selector + ' table tr.firstRow th{border-top:2px solid #BBB;background:#F7F7F7;}' +\r\n                selector + ' tr.ue-table-interlace-color-single td{ background: #fcfcfc; }' +\r\n                selector + ' tr.ue-table-interlace-color-double td{ background: #f7faff; }' +\r\n                selector + ' td p{margin:0;padding:0;}',\r\n            document);\r\n        //填充空的单元格\r\n\r\n        utils.each('td th caption'.split(' '), function (tag) {\r\n            var cells = root.getElementsByTagName(tag);\r\n            cells.length && utils.each(cells, function (node) {\r\n                if (!node.firstChild) {\r\n                    node.innerHTML = '&nbsp;';\r\n\r\n                }\r\n            })\r\n        });\r\n\r\n        //表格可排序\r\n        var tables = root.getElementsByTagName('table');\r\n        utils.each(tables, function (table) {\r\n            if (/\\bsortEnabled\\b/.test(table.className)) {\r\n                utils.on(table, 'click', function(e){\r\n                    var target = e.target || e.srcElement,\r\n                        cell = findParentByTagName(target, ['td', 'th']);\r\n                    var table = findParentByTagName(target, 'table'),\r\n                        colIndex = utils.indexOf(table.rows[0].cells, cell),\r\n                        sortType = table.getAttribute('data-sort-type');\r\n                    if(colIndex != -1) {\r\n                        sortTable(table, colIndex, me.tableSortCompareFn || sortType);\r\n                        updateTable(table);\r\n                    }\r\n                });\r\n            }\r\n        });\r\n\r\n        //按照标签名查找父节点\r\n        function findParentByTagName(target, tagNames) {\r\n            var i, current = target;\r\n            tagNames = utils.isArray(tagNames) ? tagNames:[tagNames];\r\n            while(current){\r\n                for(i = 0;i < tagNames.length; i++) {\r\n                    if(current.tagName == tagNames[i].toUpperCase()) return current;\r\n                }\r\n                current = current.parentNode;\r\n            }\r\n            return null;\r\n        }\r\n        //表格排序\r\n        function sortTable(table, sortByCellIndex, compareFn) {\r\n            var rows = table.rows,\r\n                trArray = [],\r\n                flag = rows[0].cells[0].tagName === \"TH\",\r\n                lastRowIndex = 0;\r\n\r\n            for (var i = 0,len = rows.length; i < len; i++) {\r\n                trArray[i] = rows[i];\r\n            }\r\n\r\n            var Fn = {\r\n                'reversecurrent': function(td1,td2){\r\n                    return 1;\r\n                },\r\n                'orderbyasc': function(td1,td2){\r\n                    var value1 = td1.innerText||td1.textContent,\r\n                        value2 = td2.innerText||td2.textContent;\r\n                    return value1.localeCompare(value2);\r\n                },\r\n                'reversebyasc': function(td1,td2){\r\n                    var value1 = td1.innerHTML,\r\n                        value2 = td2.innerHTML;\r\n                    return value2.localeCompare(value1);\r\n                },\r\n                'orderbynum': function(td1,td2){\r\n                    var value1 = td1[utils.isIE ? 'innerText':'textContent'].match(/\\d+/),\r\n                        value2 = td2[utils.isIE ? 'innerText':'textContent'].match(/\\d+/);\r\n                    if(value1) value1 = +value1[0];\r\n                    if(value2) value2 = +value2[0];\r\n                    return (value1||0) - (value2||0);\r\n                },\r\n                'reversebynum': function(td1,td2){\r\n                    var value1 = td1[utils.isIE ? 'innerText':'textContent'].match(/\\d+/),\r\n                        value2 = td2[utils.isIE ? 'innerText':'textContent'].match(/\\d+/);\r\n                    if(value1) value1 = +value1[0];\r\n                    if(value2) value2 = +value2[0];\r\n                    return (value2||0) - (value1||0);\r\n                }\r\n            };\r\n\r\n            //对表格设置排序的标记data-sort-type\r\n            table.setAttribute('data-sort-type', compareFn && typeof compareFn === \"string\" && Fn[compareFn] ? compareFn:'');\r\n\r\n            //th不参与排序\r\n            flag && trArray.splice(0, 1);\r\n            trArray = sort(trArray,function (tr1, tr2) {\r\n                var result;\r\n                if (compareFn && typeof compareFn === \"function\") {\r\n                    result = compareFn.call(this, tr1.cells[sortByCellIndex], tr2.cells[sortByCellIndex]);\r\n                } else if (compareFn && typeof compareFn === \"number\") {\r\n                    result = 1;\r\n                } else if (compareFn && typeof compareFn === \"string\" && Fn[compareFn]) {\r\n                    result = Fn[compareFn].call(this, tr1.cells[sortByCellIndex], tr2.cells[sortByCellIndex]);\r\n                } else {\r\n                    result = Fn['orderbyasc'].call(this, tr1.cells[sortByCellIndex], tr2.cells[sortByCellIndex]);\r\n                }\r\n                return result;\r\n            });\r\n            var fragment = table.ownerDocument.createDocumentFragment();\r\n            for (var j = 0, len = trArray.length; j < len; j++) {\r\n                fragment.appendChild(trArray[j]);\r\n            }\r\n            var tbody = table.getElementsByTagName(\"tbody\")[0];\r\n            if(!lastRowIndex){\r\n                tbody.appendChild(fragment);\r\n            }else{\r\n                tbody.insertBefore(fragment,rows[lastRowIndex- range.endRowIndex + range.beginRowIndex - 1])\r\n            }\r\n        }\r\n        //冒泡排序\r\n        function sort(array, compareFn){\r\n            compareFn = compareFn || function(item1, item2){ return item1.localeCompare(item2);};\r\n            for(var i= 0,len = array.length; i<len; i++){\r\n                for(var j = i,length = array.length; j<length; j++){\r\n                    if(compareFn(array[i], array[j]) > 0){\r\n                        var t = array[i];\r\n                        array[i] = array[j];\r\n                        array[j] = t;\r\n                    }\r\n                }\r\n            }\r\n            return array;\r\n        }\r\n        //更新表格\r\n        function updateTable(table) {\r\n            //给第一行设置firstRow的样式名称,在排序图标的样式上使用到\r\n            if(!utils.hasClass(table.rows[0], \"firstRow\")) {\r\n                for(var i = 1; i< table.rows.length; i++) {\r\n                    utils.removeClass(table.rows[i], \"firstRow\");\r\n                }\r\n                utils.addClass(table.rows[0], \"firstRow\");\r\n            }\r\n        }\r\n    }\r\n});\r\nUE.parse.register('charts',function( utils ){\r\n\r\n    utils.cssRule('chartsContainerHeight','.edui-chart-container { height:'+(this.chartContainerHeight||300)+'px}');\r\n    var resourceRoot = this.rootPath,\r\n        containers = this.root,\r\n        sources = null;\r\n\r\n    //不存在指定的根路径， 则直接退出\r\n    if ( !resourceRoot ) {\r\n        return;\r\n    }\r\n\r\n    if ( sources = parseSources() ) {\r\n\r\n        loadResources();\r\n\r\n    }\r\n\r\n\r\n    function parseSources () {\r\n\r\n        if ( !containers ) {\r\n            return null;\r\n        }\r\n\r\n        return extractChartData( containers );\r\n\r\n    }\r\n\r\n    /**\r\n     * 提取数据\r\n     */\r\n    function extractChartData ( rootNode ) {\r\n\r\n        var data = [],\r\n            tables = rootNode.getElementsByTagName( \"table\" );\r\n\r\n        for ( var i = 0, tableNode; tableNode = tables[ i ]; i++ ) {\r\n\r\n            if ( tableNode.getAttribute( \"data-chart\" ) !== null ) {\r\n\r\n                data.push( formatData( tableNode ) );\r\n\r\n            }\r\n\r\n        }\r\n\r\n        return data.length ? data : null;\r\n\r\n    }\r\n\r\n    function formatData ( tableNode ) {\r\n\r\n        var meta = tableNode.getAttribute( \"data-chart\" ),\r\n            metaConfig = {},\r\n            data = [];\r\n\r\n        //提取table数据\r\n        for ( var i = 0, row; row = tableNode.rows[ i ]; i++ ) {\r\n\r\n            var rowData = [];\r\n\r\n            for ( var j = 0, cell; cell = row.cells[ j ]; j++ ) {\r\n\r\n                var value = ( cell.innerText || cell.textContent || '' );\r\n                rowData.push( cell.tagName == 'TH' ? value:(value | 0) );\r\n\r\n            }\r\n\r\n            data.push( rowData );\r\n\r\n        }\r\n\r\n        //解析元信息\r\n        meta = meta.split( \";\" );\r\n        for ( var i = 0, metaData; metaData = meta[ i ]; i++ ) {\r\n\r\n            metaData = metaData.split( \":\" );\r\n            metaConfig[ metaData[ 0 ] ] = metaData[ 1 ];\r\n\r\n        }\r\n\r\n\r\n        return {\r\n            table: tableNode,\r\n            meta: metaConfig,\r\n            data: data\r\n        };\r\n\r\n    }\r\n\r\n    //加载资源\r\n    function loadResources () {\r\n\r\n        loadJQuery();\r\n\r\n    }\r\n\r\n    function loadJQuery () {\r\n\r\n        //不存在jquery， 则加载jquery\r\n        if ( !window.jQuery ) {\r\n\r\n            utils.loadFile(document,{\r\n                src : resourceRoot + \"/third-party/jquery-1.10.2.min.js\",\r\n                tag : \"script\",\r\n                type : \"text/javascript\",\r\n                defer : \"defer\"\r\n            },function(){\r\n\r\n                loadHighcharts();\r\n\r\n            });\r\n\r\n        } else {\r\n\r\n            loadHighcharts();\r\n\r\n        }\r\n\r\n    }\r\n\r\n    function loadHighcharts () {\r\n\r\n        //不存在Highcharts， 则加载Highcharts\r\n        if ( !window.Highcharts ) {\r\n\r\n            utils.loadFile(document,{\r\n                src : resourceRoot + \"/third-party/highcharts/highcharts.js\",\r\n                tag : \"script\",\r\n                type : \"text/javascript\",\r\n                defer : \"defer\"\r\n            },function(){\r\n\r\n                loadTypeConfig();\r\n\r\n            });\r\n\r\n        } else {\r\n\r\n            loadTypeConfig();\r\n\r\n        }\r\n\r\n    }\r\n\r\n    //加载图表差异化配置文件\r\n    function loadTypeConfig () {\r\n\r\n        utils.loadFile(document,{\r\n            src : resourceRoot + \"/dialogs/charts/chart.config.js\",\r\n            tag : \"script\",\r\n            type : \"text/javascript\",\r\n            defer : \"defer\"\r\n        },function(){\r\n\r\n            render();\r\n\r\n        });\r\n\r\n    }\r\n\r\n    //渲染图表\r\n    function render () {\r\n\r\n        var config = null,\r\n            chartConfig = null,\r\n            container = null;\r\n\r\n        for ( var i = 0, len = sources.length; i < len; i++ ) {\r\n\r\n            config = sources[ i ];\r\n\r\n            chartConfig = analysisConfig( config );\r\n\r\n            container = createContainer( config.table );\r\n\r\n            renderChart( container, typeConfig[ config.meta.chartType ], chartConfig );\r\n\r\n        }\r\n\r\n\r\n    }\r\n\r\n    /**\r\n     * 渲染图表\r\n     * @param container 图表容器节点对象\r\n     * @param typeConfig 图表类型配置\r\n     * @param config 图表通用配置\r\n     * */\r\n    function renderChart ( container, typeConfig, config ) {\r\n\r\n\r\n        $( container ).highcharts( $.extend( {}, typeConfig, {\r\n\r\n            credits: {\r\n                enabled: false\r\n            },\r\n            exporting: {\r\n                enabled: false\r\n            },\r\n            title: {\r\n                text: config.title,\r\n                x: -20 //center\r\n            },\r\n            subtitle: {\r\n                text: config.subTitle,\r\n                x: -20\r\n            },\r\n            xAxis: {\r\n                title: {\r\n                    text: config.xTitle\r\n                },\r\n                categories: config.categories\r\n            },\r\n            yAxis: {\r\n                title: {\r\n                    text: config.yTitle\r\n                },\r\n                plotLines: [{\r\n                    value: 0,\r\n                    width: 1,\r\n                    color: '#808080'\r\n                }]\r\n            },\r\n            tooltip: {\r\n                enabled: true,\r\n                valueSuffix: config.suffix\r\n            },\r\n            legend: {\r\n                layout: 'vertical',\r\n                align: 'right',\r\n                verticalAlign: 'middle',\r\n                borderWidth: 1\r\n            },\r\n            series: config.series\r\n\r\n        } ));\r\n\r\n    }\r\n\r\n    /**\r\n     * 创建图表的容器\r\n     * 新创建的容器会替换掉对应的table对象\r\n     * */\r\n    function createContainer ( tableNode ) {\r\n\r\n        var container = document.createElement( \"div\" );\r\n        container.className = \"edui-chart-container\";\r\n\r\n        tableNode.parentNode.replaceChild( container, tableNode );\r\n\r\n        return container;\r\n\r\n    }\r\n\r\n    //根据config解析出正确的类别和图表数据信息\r\n    function analysisConfig ( config ) {\r\n\r\n        var series = [],\r\n        //数据类别\r\n            categories = [],\r\n            result = [],\r\n            data = config.data,\r\n            meta = config.meta;\r\n\r\n        //数据对齐方式为相反的方式， 需要反转数据\r\n        if ( meta.dataFormat != \"1\" ) {\r\n\r\n            for ( var i = 0, len = data.length; i < len ; i++ ) {\r\n\r\n                for ( var j = 0, jlen = data[ i ].length; j < jlen; j++ ) {\r\n\r\n                    if ( !result[ j ] ) {\r\n                        result[ j ] = [];\r\n                    }\r\n\r\n                    result[ j ][ i ] = data[ i ][ j ];\r\n\r\n                }\r\n\r\n            }\r\n\r\n            data = result;\r\n\r\n        }\r\n\r\n        result = {};\r\n\r\n        //普通图表\r\n        if ( meta.chartType != typeConfig.length - 1 ) {\r\n\r\n            categories = data[ 0 ].slice( 1 );\r\n\r\n            for ( var i = 1, curData; curData = data[ i ]; i++ ) {\r\n                series.push( {\r\n                    name: curData[ 0 ],\r\n                    data: curData.slice( 1 )\r\n                } );\r\n            }\r\n\r\n            result.series = series;\r\n            result.categories = categories;\r\n            result.title = meta.title;\r\n            result.subTitle = meta.subTitle;\r\n            result.xTitle = meta.xTitle;\r\n            result.yTitle = meta.yTitle;\r\n            result.suffix = meta.suffix;\r\n\r\n        } else {\r\n\r\n            var curData = [];\r\n\r\n            for ( var i = 1, len = data[ 0 ].length; i < len; i++ ) {\r\n\r\n                curData.push( [ data[ 0 ][ i ], data[ 1 ][ i ] | 0 ] );\r\n\r\n            }\r\n\r\n            //饼图\r\n            series[ 0 ] = {\r\n                type: 'pie',\r\n                name: meta.tip,\r\n                data: curData\r\n            };\r\n\r\n            result.series = series;\r\n            result.title = meta.title;\r\n            result.suffix = meta.suffix;\r\n\r\n        }\r\n\r\n        return result;\r\n\r\n    }\r\n\r\n});\r\nUE.parse.register('background', function (utils) {\r\n    var me = this,\r\n        root = me.root,\r\n        p = root.getElementsByTagName('p'),\r\n        styles;\r\n\r\n    for (var i = 0,ci; ci = p[i++];) {\r\n        styles = ci.getAttribute('data-background');\r\n        if (styles){\r\n            ci.parentNode.removeChild(ci);\r\n        }\r\n    }\r\n\r\n    //追加默认的表格样式\r\n    styles && utils.cssRule('ueditor_background', me.selector + '{' + styles + '}', document);\r\n});\r\nUE.parse.register('list',function(utils){\r\n    var customCss = [],\r\n        customStyle = {\r\n            'cn'    :   'cn-1-',\r\n            'cn1'   :   'cn-2-',\r\n            'cn2'   :   'cn-3-',\r\n            'num'   :   'num-1-',\r\n            'num1'  :   'num-2-',\r\n            'num2'  :   'num-3-',\r\n            'dash'  :   'dash',\r\n            'dot'   :   'dot'\r\n        };\r\n\r\n\r\n    utils.extend(this,{\r\n        liiconpath : 'http://bs.baidu.com/listicon/',\r\n        listDefaultPaddingLeft : '20'\r\n    });\r\n\r\n    var root = this.root,\r\n        ols = root.getElementsByTagName('ol'),\r\n        uls = root.getElementsByTagName('ul'),\r\n        selector = this.selector;\r\n\r\n    if(ols.length){\r\n        applyStyle.call(this,ols);\r\n    }\r\n\r\n    if(uls.length){\r\n        applyStyle.call(this,uls);\r\n    }\r\n\r\n    if(ols.length || uls.length){\r\n        customCss.push(selector +' .list-paddingleft-1{padding-left:0}');\r\n        customCss.push(selector +' .list-paddingleft-2{padding-left:'+ this.listDefaultPaddingLeft+'px}');\r\n        customCss.push(selector +' .list-paddingleft-3{padding-left:'+ this.listDefaultPaddingLeft*2+'px}');\r\n\r\n        utils.cssRule('list', selector +' ol,'+selector +' ul{margin:0;padding:0;}li{clear:both;}'+customCss.join('\\n'), document);\r\n    }\r\n    function applyStyle(nodes){\r\n        var T = this;\r\n        utils.each(nodes,function(list){\r\n            if(list.className && /custom_/i.test(list.className)){\r\n                var listStyle = list.className.match(/custom_(\\w+)/)[1];\r\n                if(listStyle == 'dash' || listStyle == 'dot'){\r\n                    utils.pushItem(customCss,selector +' li.list-' + customStyle[listStyle] + '{background-image:url(' + T.liiconpath +customStyle[listStyle]+'.gif)}');\r\n                    utils.pushItem(customCss,selector +' ul.custom_'+listStyle+'{list-style:none;} '+ selector +' ul.custom_'+listStyle+' li{background-position:0 3px;background-repeat:no-repeat}');\r\n\r\n                }else{\r\n                    var index = 1;\r\n                    utils.each(list.childNodes,function(li){\r\n                        if(li.tagName == 'LI'){\r\n                            utils.pushItem(customCss,selector + ' li.list-' + customStyle[listStyle] + index + '{background-image:url(' + T.liiconpath  + 'list-'+customStyle[listStyle] +index + '.gif)}');\r\n                            index++;\r\n                        }\r\n                    });\r\n                    utils.pushItem(customCss,selector + ' ol.custom_'+listStyle+'{list-style:none;}'+selector+' ol.custom_'+listStyle+' li{background-position:0 3px;background-repeat:no-repeat}');\r\n                }\r\n                switch(listStyle){\r\n                    case 'cn':\r\n                        utils.pushItem(customCss,selector + ' li.list-'+listStyle+'-paddingleft-1{padding-left:25px}');\r\n                        utils.pushItem(customCss,selector + ' li.list-'+listStyle+'-paddingleft-2{padding-left:40px}');\r\n                        utils.pushItem(customCss,selector + ' li.list-'+listStyle+'-paddingleft-3{padding-left:55px}');\r\n                        break;\r\n                    case 'cn1':\r\n                        utils.pushItem(customCss,selector + ' li.list-'+listStyle+'-paddingleft-1{padding-left:30px}');\r\n                        utils.pushItem(customCss,selector + ' li.list-'+listStyle+'-paddingleft-2{padding-left:40px}');\r\n                        utils.pushItem(customCss,selector + ' li.list-'+listStyle+'-paddingleft-3{padding-left:55px}');\r\n                        break;\r\n                    case 'cn2':\r\n                        utils.pushItem(customCss,selector + ' li.list-'+listStyle+'-paddingleft-1{padding-left:40px}');\r\n                        utils.pushItem(customCss,selector + ' li.list-'+listStyle+'-paddingleft-2{padding-left:55px}');\r\n                        utils.pushItem(customCss,selector + ' li.list-'+listStyle+'-paddingleft-3{padding-left:68px}');\r\n                        break;\r\n                    case 'num':\r\n                    case 'num1':\r\n                        utils.pushItem(customCss,selector + ' li.list-'+listStyle+'-paddingleft-1{padding-left:25px}');\r\n                        break;\r\n                    case 'num2':\r\n                        utils.pushItem(customCss,selector + ' li.list-'+listStyle+'-paddingleft-1{padding-left:35px}');\r\n                        utils.pushItem(customCss,selector + ' li.list-'+listStyle+'-paddingleft-2{padding-left:40px}');\r\n                        break;\r\n                    case 'dash':\r\n                        utils.pushItem(customCss,selector + ' li.list-'+listStyle+'-paddingleft{padding-left:35px}');\r\n                        break;\r\n                    case 'dot':\r\n                        utils.pushItem(customCss,selector + ' li.list-'+listStyle+'-paddingleft{padding-left:20px}');\r\n                }\r\n            }\r\n        });\r\n    }\r\n\r\n\r\n});\r\nUE.parse.register('vedio',function(utils){\r\n    var video = this.root.getElementsByTagName('video'),\r\n        audio = this.root.getElementsByTagName('audio');\r\n\r\n    document.createElement('video');document.createElement('audio');\r\n    if(video.length || audio.length){\r\n        var sourcePath = utils.removeLastbs(this.rootPath),\r\n            jsurl = sourcePath + '/third-party/video-js/video.js',\r\n            cssurl = sourcePath + '/third-party/video-js/video-js.min.css',\r\n            swfUrl = sourcePath + '/third-party/video-js/video-js.swf';\r\n\r\n        if(window.videojs) {\r\n            videojs.autoSetup();\r\n        } else {\r\n            utils.loadFile(document,{\r\n                id : \"video_css\",\r\n                tag : \"link\",\r\n                rel : \"stylesheet\",\r\n                type : \"text/css\",\r\n                href : cssurl\r\n            });\r\n            utils.loadFile(document,{\r\n                id : \"video_js\",\r\n                src : jsurl,\r\n                tag : \"script\",\r\n                type : \"text/javascript\"\r\n            },function(){\r\n                videojs.options.flash.swf = swfUrl;\r\n                videojs.autoSetup();\r\n            });\r\n        }\r\n\r\n    }\r\n});\r\n\r\n})();\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/anchor/anchor.html",
    "content": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\r\n    \"http://www.w3.org/TR/html4/loose.dtd\">\r\n<html>\r\n    <head>\r\n        <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\r\n        <title></title>\r\n        <style type=\"text/css\">\r\n            *{color: #838383;margin: 0;padding: 0}\r\n            html,body {font-size: 12px;overflow: hidden; }\r\n            .content{padding:5px 0 0 15px;}\r\n            input{width:210px;height:21px;line-height:21px;margin-left: 4px;}\r\n        </style>\r\n    </head>\r\n    <body>\r\n        <div class=\"content\">\r\n            <span><var id=\"lang_input_anchorName\"></var></span><input id=\"anchorName\"  value=\"\" />\r\n        </div>\r\n        <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n        <script type=\"text/javascript\">\r\n            var anchorInput = $G('anchorName'),\r\n                node = editor.selection.getRange().getClosedNode();\r\n            if(node && node.tagName == 'IMG' && (node = node.getAttribute('anchorname'))){\r\n                anchorInput.value = node;\r\n            }\r\n            anchorInput.onkeydown = function(evt){\r\n                evt = evt || window.event;\r\n                if(evt.keyCode == 13){\r\n                    editor.execCommand('anchor', anchorInput.value);\r\n                    dialog.close();\r\n                    domUtils.preventDefault(evt)\r\n                }\r\n            };\r\n            dialog.onok = function (){\r\n                editor.execCommand('anchor', anchorInput.value);\r\n                dialog.close();\r\n            };\r\n            $focus(anchorInput);\r\n        </script>\r\n    </body>\r\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/attachment/attachment.css",
    "content": "@charset \"utf-8\";\r\n/* dialog样式 */\r\n.wrapper {\r\n    zoom: 1;\r\n    width: 630px;\r\n    *width: 626px;\r\n    height: 380px;\r\n    margin: 0 auto;\r\n    padding: 10px;\r\n    position: relative;\r\n    font-family: sans-serif;\r\n}\r\n\r\n/*tab样式框大小*/\r\n.tabhead {\r\n    float:left;\r\n}\r\n.tabbody {\r\n    width: 100%;\r\n    height: 346px;\r\n    position: relative;\r\n    clear: both;\r\n}\r\n\r\n.tabbody .panel {\r\n    position: absolute;\r\n    width: 0;\r\n    height: 0;\r\n    background: #fff;\r\n    overflow: hidden;\r\n    display: none;\r\n}\r\n\r\n.tabbody .panel.focus {\r\n    width: 100%;\r\n    height: 346px;\r\n    display: block;\r\n}\r\n\r\n/* 上传附件 */\r\n.tabbody #upload.panel {\r\n    width: 0;\r\n    height: 0;\r\n    overflow: hidden;\r\n    position: absolute !important;\r\n    clip: rect(1px, 1px, 1px, 1px);\r\n    background: #fff;\r\n    display: block;\r\n}\r\n\r\n.tabbody #upload.panel.focus {\r\n    width: 100%;\r\n    height: 346px;\r\n    display: block;\r\n    clip: auto;\r\n}\r\n\r\n#upload .queueList {\r\n    margin: 0;\r\n    width: 100%;\r\n    height: 100%;\r\n    position: absolute;\r\n    overflow: hidden;\r\n}\r\n\r\n#upload p {\r\n    margin: 0;\r\n}\r\n\r\n.element-invisible {\r\n    width: 0 !important;\r\n    height: 0 !important;\r\n    border: 0;\r\n    padding: 0;\r\n    margin: 0;\r\n    overflow: hidden;\r\n    position: absolute !important;\r\n    clip: rect(1px, 1px, 1px, 1px);\r\n}\r\n\r\n#upload .placeholder {\r\n    margin: 10px;\r\n    border: 2px dashed #e6e6e6;\r\n    *border: 0px dashed #e6e6e6;\r\n    height: 172px;\r\n    padding-top: 150px;\r\n    text-align: center;\r\n    background: url(./images/image.png) center 70px no-repeat;\r\n    color: #cccccc;\r\n    font-size: 18px;\r\n    position: relative;\r\n    top:0;\r\n    *top: 10px;\r\n}\r\n\r\n#upload .placeholder .webuploader-pick {\r\n    font-size: 18px;\r\n    background: #00b7ee;\r\n    border-radius: 3px;\r\n    line-height: 44px;\r\n    padding: 0 30px;\r\n    *width: 120px;\r\n    color: #fff;\r\n    display: inline-block;\r\n    margin: 0 auto 20px auto;\r\n    cursor: pointer;\r\n    box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);\r\n}\r\n\r\n#upload .placeholder .webuploader-pick-hover {\r\n    background: #00a2d4;\r\n}\r\n\r\n\r\n#filePickerContainer {\r\n    text-align: center;\r\n}\r\n\r\n#upload .placeholder .flashTip {\r\n    color: #666666;\r\n    font-size: 12px;\r\n    position: absolute;\r\n    width: 100%;\r\n    text-align: center;\r\n    bottom: 20px;\r\n}\r\n\r\n#upload .placeholder .flashTip a {\r\n    color: #0785d1;\r\n    text-decoration: none;\r\n}\r\n\r\n#upload .placeholder .flashTip a:hover {\r\n    text-decoration: underline;\r\n}\r\n\r\n#upload .placeholder.webuploader-dnd-over {\r\n    border-color: #999999;\r\n}\r\n\r\n#upload .filelist {\r\n    list-style: none;\r\n    margin: 0;\r\n    padding: 0;\r\n    overflow-x: hidden;\r\n    overflow-y: auto;\r\n    position: relative;\r\n    height: 300px;\r\n}\r\n\r\n#upload .filelist:after {\r\n    content: '';\r\n    display: block;\r\n    width: 0;\r\n    height: 0;\r\n    overflow: hidden;\r\n    clear: both;\r\n}\r\n\r\n#upload .filelist li {\r\n    width: 113px;\r\n    height: 113px;\r\n    background: url(./images/bg.png);\r\n    text-align: center;\r\n    margin: 9px 0 0 9px;\r\n    *margin: 6px 0 0 6px;\r\n    position: relative;\r\n    display: block;\r\n    float: left;\r\n    overflow: hidden;\r\n    font-size: 12px;\r\n}\r\n\r\n#upload .filelist li p.log {\r\n    position: relative;\r\n    top: -45px;\r\n}\r\n\r\n#upload .filelist li p.title {\r\n    position: absolute;\r\n    top: 0;\r\n    left: 0;\r\n    width: 100%;\r\n    overflow: hidden;\r\n    white-space: nowrap;\r\n    text-overflow: ellipsis;\r\n    top: 5px;\r\n    text-indent: 5px;\r\n    text-align: left;\r\n}\r\n\r\n#upload .filelist li p.progress {\r\n    position: absolute;\r\n    width: 100%;\r\n    bottom: 0;\r\n    left: 0;\r\n    height: 8px;\r\n    overflow: hidden;\r\n    z-index: 50;\r\n    margin: 0;\r\n    border-radius: 0;\r\n    background: none;\r\n    -webkit-box-shadow: 0 0 0;\r\n}\r\n\r\n#upload .filelist li p.progress span {\r\n    display: none;\r\n    overflow: hidden;\r\n    width: 0;\r\n    height: 100%;\r\n    background: #1483d8 url(./images/progress.png) repeat-x;\r\n\r\n    -webit-transition: width 200ms linear;\r\n    -moz-transition: width 200ms linear;\r\n    -o-transition: width 200ms linear;\r\n    -ms-transition: width 200ms linear;\r\n    transition: width 200ms linear;\r\n\r\n    -webkit-animation: progressmove 2s linear infinite;\r\n    -moz-animation: progressmove 2s linear infinite;\r\n    -o-animation: progressmove 2s linear infinite;\r\n    -ms-animation: progressmove 2s linear infinite;\r\n    animation: progressmove 2s linear infinite;\r\n\r\n    -webkit-transform: translateZ(0);\r\n}\r\n\r\n@-webkit-keyframes progressmove {\r\n    0% {\r\n        background-position: 0 0;\r\n    }\r\n    100% {\r\n        background-position: 17px 0;\r\n    }\r\n}\r\n\r\n@-moz-keyframes progressmove {\r\n    0% {\r\n        background-position: 0 0;\r\n    }\r\n    100% {\r\n        background-position: 17px 0;\r\n    }\r\n}\r\n\r\n@keyframes progressmove {\r\n    0% {\r\n        background-position: 0 0;\r\n    }\r\n    100% {\r\n        background-position: 17px 0;\r\n    }\r\n}\r\n\r\n#upload .filelist li p.imgWrap {\r\n    position: relative;\r\n    z-index: 2;\r\n    line-height: 113px;\r\n    vertical-align: middle;\r\n    overflow: hidden;\r\n    width: 113px;\r\n    height: 113px;\r\n\r\n    -webkit-transform-origin: 50% 50%;\r\n    -moz-transform-origin: 50% 50%;\r\n    -o-transform-origin: 50% 50%;\r\n    -ms-transform-origin: 50% 50%;\r\n    transform-origin: 50% 50%;\r\n\r\n    -webit-transition: 200ms ease-out;\r\n    -moz-transition: 200ms ease-out;\r\n    -o-transition: 200ms ease-out;\r\n    -ms-transition: 200ms ease-out;\r\n    transition: 200ms ease-out;\r\n}\r\n#upload .filelist li p.imgWrap.notimage {\r\n    margin-top: 0;\r\n    width: 111px;\r\n    height: 111px;\r\n    border: 1px #eeeeee solid;\r\n}\r\n#upload .filelist li p.imgWrap.notimage i.file-preview {\r\n    margin-top: 15px;\r\n}\r\n\r\n#upload .filelist li img {\r\n    width: 100%;\r\n}\r\n\r\n#upload .filelist li p.error {\r\n    background: #f43838;\r\n    color: #fff;\r\n    position: absolute;\r\n    bottom: 0;\r\n    left: 0;\r\n    height: 28px;\r\n    line-height: 28px;\r\n    width: 100%;\r\n    z-index: 100;\r\n    display:none;\r\n}\r\n\r\n#upload .filelist li .success {\r\n    display: block;\r\n    position: absolute;\r\n    left: 0;\r\n    bottom: 0;\r\n    height: 40px;\r\n    width: 100%;\r\n    z-index: 200;\r\n    background: url(./images/success.png) no-repeat right bottom;\r\n    background-image: url(./images/success.gif) \\9;\r\n}\r\n\r\n#upload .filelist li.filePickerBlock {\r\n    width: 113px;\r\n    height: 113px;\r\n    background: url(./images/image.png) no-repeat center 12px;\r\n    border: 1px solid #eeeeee;\r\n    border-radius: 0;\r\n}\r\n#upload .filelist li.filePickerBlock div.webuploader-pick  {\r\n    width: 100%;\r\n    height: 100%;\r\n    margin: 0;\r\n    padding: 0;\r\n    opacity: 0;\r\n    background: none;\r\n    font-size: 0;\r\n}\r\n\r\n#upload .filelist div.file-panel {\r\n    position: absolute;\r\n    height: 0;\r\n    filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr='#80000000', endColorstr='#80000000') \\0;\r\n    background: rgba(0, 0, 0, 0.5);\r\n    width: 100%;\r\n    top: 0;\r\n    left: 0;\r\n    overflow: hidden;\r\n    z-index: 300;\r\n}\r\n\r\n#upload .filelist div.file-panel span {\r\n    width: 24px;\r\n    height: 24px;\r\n    display: inline;\r\n    float: right;\r\n    text-indent: -9999px;\r\n    overflow: hidden;\r\n    background: url(./images/icons.png) no-repeat;\r\n    background: url(./images/icons.gif) no-repeat \\9;\r\n    margin: 5px 1px 1px;\r\n    cursor: pointer;\r\n    -webkit-tap-highlight-color: rgba(0,0,0,0);\r\n    -webkit-user-select: none;\r\n    -moz-user-select: none;\r\n    -ms-user-select: none;\r\n    user-select: none;\r\n}\r\n\r\n#upload .filelist div.file-panel span.rotateLeft {\r\n    display:none;\r\n    background-position: 0 -24px;\r\n}\r\n\r\n#upload .filelist div.file-panel span.rotateLeft:hover {\r\n    background-position: 0 0;\r\n}\r\n\r\n#upload .filelist div.file-panel span.rotateRight {\r\n    display:none;\r\n    background-position: -24px -24px;\r\n}\r\n\r\n#upload .filelist div.file-panel span.rotateRight:hover {\r\n    background-position: -24px 0;\r\n}\r\n\r\n#upload .filelist div.file-panel span.cancel {\r\n    background-position: -48px -24px;\r\n}\r\n\r\n#upload .filelist div.file-panel span.cancel:hover {\r\n    background-position: -48px 0;\r\n}\r\n\r\n#upload .statusBar {\r\n    height: 45px;\r\n    border-bottom: 1px solid #dadada;\r\n    margin: 0 10px;\r\n    padding: 0;\r\n    line-height: 45px;\r\n    vertical-align: middle;\r\n    position: relative;\r\n}\r\n\r\n#upload .statusBar .progress {\r\n    border: 1px solid #1483d8;\r\n    width: 198px;\r\n    background: #fff;\r\n    height: 18px;\r\n    position: absolute;\r\n    top: 12px;\r\n    display: none;\r\n    text-align: center;\r\n    line-height: 18px;\r\n    color: #6dbfff;\r\n    margin: 0 10px 0 0;\r\n}\r\n#upload .statusBar .progress span.percentage {\r\n    width: 0;\r\n    height: 100%;\r\n    left: 0;\r\n    top: 0;\r\n    background: #1483d8;\r\n    position: absolute;\r\n}\r\n#upload .statusBar .progress span.text {\r\n    position: relative;\r\n    z-index: 10;\r\n}\r\n\r\n#upload .statusBar .info {\r\n    display: inline-block;\r\n    font-size: 14px;\r\n    color: #666666;\r\n}\r\n\r\n#upload .statusBar .btns {\r\n    position: absolute;\r\n    top: 7px;\r\n    right: 0;\r\n    line-height: 30px;\r\n}\r\n\r\n#filePickerBtn {\r\n    display: inline-block;\r\n    float: left;\r\n}\r\n#upload .statusBar .btns .webuploader-pick,\r\n#upload .statusBar .btns .uploadBtn,\r\n#upload .statusBar .btns .uploadBtn.state-uploading,\r\n#upload .statusBar .btns .uploadBtn.state-paused {\r\n    background: #ffffff;\r\n    border: 1px solid #cfcfcf;\r\n    color: #565656;\r\n    padding: 0 18px;\r\n    display: inline-block;\r\n    border-radius: 3px;\r\n    margin-left: 10px;\r\n    cursor: pointer;\r\n    font-size: 14px;\r\n    float: left;\r\n    -webkit-user-select: none;\r\n    -moz-user-select: none;\r\n    -ms-user-select: none;\r\n    user-select: none;\r\n}\r\n#upload .statusBar .btns .webuploader-pick-hover,\r\n#upload .statusBar .btns .uploadBtn:hover,\r\n#upload .statusBar .btns .uploadBtn.state-uploading:hover,\r\n#upload .statusBar .btns .uploadBtn.state-paused:hover {\r\n    background: #f0f0f0;\r\n}\r\n\r\n#upload .statusBar .btns .uploadBtn,\r\n#upload .statusBar .btns .uploadBtn.state-paused{\r\n    background: #00b7ee;\r\n    color: #fff;\r\n    border-color: transparent;\r\n}\r\n#upload .statusBar .btns .uploadBtn:hover,\r\n#upload .statusBar .btns .uploadBtn.state-paused:hover{\r\n    background: #00a2d4;\r\n}\r\n\r\n#upload .statusBar .btns .uploadBtn.disabled {\r\n    pointer-events: none;\r\n    filter:alpha(opacity=60);\r\n    -moz-opacity:0.6;\r\n    -khtml-opacity: 0.6;\r\n    opacity: 0.6;\r\n}\r\n\r\n\r\n\r\n/* 图片管理样式 */\r\n#online {\r\n    width: 100%;\r\n    height: 336px;\r\n    padding: 10px 0 0 0;\r\n}\r\n#online #fileList{\r\n    width: 100%;\r\n    height: 100%;\r\n    overflow-x: hidden;\r\n    overflow-y: auto;\r\n    position: relative;\r\n}\r\n#online ul {\r\n    display: block;\r\n    list-style: none;\r\n    margin: 0;\r\n    padding: 0;\r\n}\r\n#online li {\r\n    float: left;\r\n    display: block;\r\n    list-style: none;\r\n    padding: 0;\r\n    width: 113px;\r\n    height: 113px;\r\n    margin: 0 0 9px 9px;\r\n    *margin: 0 0 6px 6px;\r\n    background-color: #eee;\r\n    overflow: hidden;\r\n    cursor: pointer;\r\n    position: relative;\r\n}\r\n#online li.clearFloat {\r\n    float: none;\r\n    clear: both;\r\n    display: block;\r\n    width:0;\r\n    height:0;\r\n    margin: 0;\r\n    padding: 0;\r\n}\r\n#online li img {\r\n    cursor: pointer;\r\n}\r\n#online li div.file-wrapper {\r\n    cursor: pointer;\r\n    position: absolute;\r\n    display: block;\r\n    width: 111px;\r\n    height: 111px;\r\n    border: 1px solid #eee;\r\n    background: url(\"./images/bg.png\") repeat;\r\n}\r\n#online li div span.file-title{\r\n    display: block;\r\n    padding: 0 3px;\r\n    margin: 3px 0 0 0;\r\n    font-size: 12px;\r\n    height: 13px;\r\n    color: #555555;\r\n    text-align: center;\r\n    width: 107px;\r\n    white-space: nowrap;\r\n    word-break: break-all;\r\n    overflow: hidden;\r\n    text-overflow: ellipsis;\r\n}\r\n#online li .icon {\r\n    cursor: pointer;\r\n    width: 113px;\r\n    height: 113px;\r\n    position: absolute;\r\n    top: 0;\r\n    left: 0;\r\n    z-index: 2;\r\n    border: 0;\r\n    background-repeat: no-repeat;\r\n}\r\n#online li .icon:hover {\r\n    width: 107px;\r\n    height: 107px;\r\n    border: 3px solid #1094fa;\r\n}\r\n#online li.selected .icon {\r\n    background-image: url(images/success.png);\r\n    background-image: url(images/success.gif) \\9;\r\n    background-position: 75px 75px;\r\n}\r\n#online li.selected .icon:hover {\r\n    width: 107px;\r\n    height: 107px;\r\n    border: 3px solid #1094fa;\r\n    background-position: 72px 72px;\r\n}\r\n\r\n\r\n/* 在线文件的文件预览图标 */\r\ni.file-preview {\r\n    display: block;\r\n    margin: 10px auto;\r\n    width: 70px;\r\n    height: 70px;\r\n    background-image: url(\"./images/file-icons.png\");\r\n    background-image: url(\"./images/file-icons.gif\") \\9;\r\n    background-position: -140px center;\r\n    background-repeat: no-repeat;\r\n}\r\ni.file-preview.file-type-dir{\r\n    background-position: 0 center;\r\n}\r\ni.file-preview.file-type-file{\r\n    background-position: -140px center;\r\n}\r\ni.file-preview.file-type-filelist{\r\n    background-position: -210px center;\r\n}\r\ni.file-preview.file-type-zip,\r\ni.file-preview.file-type-rar,\r\ni.file-preview.file-type-7z,\r\ni.file-preview.file-type-tar,\r\ni.file-preview.file-type-gz,\r\ni.file-preview.file-type-bz2{\r\n    background-position: -280px center;\r\n}\r\ni.file-preview.file-type-xls,\r\ni.file-preview.file-type-xlsx{\r\n    background-position: -350px center;\r\n}\r\ni.file-preview.file-type-doc,\r\ni.file-preview.file-type-docx{\r\n    background-position: -420px center;\r\n}\r\ni.file-preview.file-type-ppt,\r\ni.file-preview.file-type-pptx{\r\n    background-position: -490px center;\r\n}\r\ni.file-preview.file-type-vsd{\r\n    background-position: -560px center;\r\n}\r\ni.file-preview.file-type-pdf{\r\n    background-position: -630px center;\r\n}\r\ni.file-preview.file-type-txt,\r\ni.file-preview.file-type-md,\r\ni.file-preview.file-type-json,\r\ni.file-preview.file-type-htm,\r\ni.file-preview.file-type-xml,\r\ni.file-preview.file-type-html,\r\ni.file-preview.file-type-js,\r\ni.file-preview.file-type-css,\r\ni.file-preview.file-type-php,\r\ni.file-preview.file-type-jsp,\r\ni.file-preview.file-type-asp{\r\n    background-position: -700px center;\r\n}\r\ni.file-preview.file-type-apk{\r\n    background-position: -770px center;\r\n}\r\ni.file-preview.file-type-exe{\r\n    background-position: -840px center;\r\n}\r\ni.file-preview.file-type-ipa{\r\n    background-position: -910px center;\r\n}\r\ni.file-preview.file-type-mp4,\r\ni.file-preview.file-type-swf,\r\ni.file-preview.file-type-mkv,\r\ni.file-preview.file-type-avi,\r\ni.file-preview.file-type-flv,\r\ni.file-preview.file-type-mov,\r\ni.file-preview.file-type-mpg,\r\ni.file-preview.file-type-mpeg,\r\ni.file-preview.file-type-ogv,\r\ni.file-preview.file-type-webm,\r\ni.file-preview.file-type-rm,\r\ni.file-preview.file-type-rmvb{\r\n    background-position: -980px center;\r\n}\r\ni.file-preview.file-type-ogg,\r\ni.file-preview.file-type-wav,\r\ni.file-preview.file-type-wmv,\r\ni.file-preview.file-type-mid,\r\ni.file-preview.file-type-mp3{\r\n    background-position: -1050px center;\r\n}\r\ni.file-preview.file-type-jpg,\r\ni.file-preview.file-type-jpeg,\r\ni.file-preview.file-type-gif,\r\ni.file-preview.file-type-bmp,\r\ni.file-preview.file-type-png,\r\ni.file-preview.file-type-psd{\r\n    background-position: -140px center;\r\n}"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/attachment/attachment.html",
    "content": "<!doctype html>\r\n<html>\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <title>ueditor图片对话框</title>\r\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n\r\n    <!-- jquery -->\r\n    <script type=\"text/javascript\" src=\"../../third-party/jquery-1.10.2.min.js\"></script>\r\n\r\n    <!-- webuploader -->\r\n    <script src=\"../../third-party/webuploader/webuploader.min.js\"></script>\r\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"../../third-party/webuploader/webuploader.css\">\r\n\r\n    <!-- attachment dialog -->\r\n    <link rel=\"stylesheet\" href=\"attachment.css\" type=\"text/css\" />\r\n</head>\r\n<body>\r\n\r\n    <div class=\"wrapper\">\r\n        <div id=\"tabhead\" class=\"tabhead\">\r\n            <span class=\"tab focus\" data-content-id=\"upload\"><var id=\"lang_tab_upload\"></var></span>\r\n            <span class=\"tab\" data-content-id=\"online\"><var id=\"lang_tab_online\"></var></span>\r\n        </div>\r\n        <div id=\"tabbody\" class=\"tabbody\">\r\n            <!-- 上传图片 -->\r\n            <div id=\"upload\" class=\"panel focus\">\r\n                <div id=\"queueList\" class=\"queueList\">\r\n                    <div class=\"statusBar element-invisible\">\r\n                        <div class=\"progress\">\r\n                            <span class=\"text\">0%</span>\r\n                            <span class=\"percentage\"></span>\r\n                        </div><div class=\"info\"></div>\r\n                        <div class=\"btns\">\r\n                            <div id=\"filePickerBtn\"></div>\r\n                            <div class=\"uploadBtn\"><var id=\"lang_start_upload\"></var></div>\r\n                        </div>\r\n                    </div>\r\n                    <div id=\"dndArea\" class=\"placeholder\">\r\n                        <div class=\"filePickerContainer\">\r\n                            <div id=\"filePickerReady\"></div>\r\n                        </div>\r\n                    </div>\r\n                    <ul class=\"filelist element-invisible\">\r\n                        <li id=\"filePickerBlock\" class=\"filePickerBlock\"></li>\r\n                    </ul>\r\n                </div>\r\n            </div>\r\n\r\n            <!-- 在线图片 -->\r\n            <div id=\"online\" class=\"panel\">\r\n                <div id=\"fileList\"><var id=\"lang_imgLoading\"></var></div>\r\n            </div>\r\n\r\n        </div>\r\n    </div>\r\n    <script type=\"text/javascript\" src=\"attachment.js\"></script>\r\n\r\n</body>\r\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/attachment/attachment.js",
    "content": "/**\n * User: Jinqn\n * Date: 14-04-08\n * Time: 下午16:34\n * 上传图片对话框逻辑代码,包括tab: 远程图片/上传图片/在线图片/搜索图片\n */\n\n(function () {\n\n    var uploadFile,\n        onlineFile;\n\n    window.onload = function () {\n        initTabs();\n        initButtons();\n    };\n\n    /* 初始化tab标签 */\n    function initTabs() {\n        var tabs = $G('tabhead').children;\n        for (var i = 0; i < tabs.length; i++) {\n            domUtils.on(tabs[i], \"click\", function (e) {\n                var target = e.target || e.srcElement;\n                setTabFocus(target.getAttribute('data-content-id'));\n            });\n        }\n\n        setTabFocus('upload');\n    }\n\n    /* 初始化tabbody */\n    function setTabFocus(id) {\n        if(!id) return;\n        var i, bodyId, tabs = $G('tabhead').children;\n        for (i = 0; i < tabs.length; i++) {\n            bodyId = tabs[i].getAttribute('data-content-id')\n            if (bodyId == id) {\n                domUtils.addClass(tabs[i], 'focus');\n                domUtils.addClass($G(bodyId), 'focus');\n            } else {\n                domUtils.removeClasses(tabs[i], 'focus');\n                domUtils.removeClasses($G(bodyId), 'focus');\n            }\n        }\n        switch (id) {\n            case 'upload':\n                uploadFile = uploadFile || new UploadFile('queueList');\n                break;\n            case 'online':\n                onlineFile = onlineFile || new OnlineFile('fileList');\n                break;\n        }\n    }\n\n    /* 初始化onok事件 */\n    function initButtons() {\n\n        dialog.onok = function () {\n            var list = [], id, tabs = $G('tabhead').children;\n            for (var i = 0; i < tabs.length; i++) {\n                if (domUtils.hasClass(tabs[i], 'focus')) {\n                    id = tabs[i].getAttribute('data-content-id');\n                    break;\n                }\n            }\n\n            switch (id) {\n                case 'upload':\n                    list = uploadFile.getInsertList();\n                    var count = uploadFile.getQueueCount();\n                    if (count) {\n                        $('.info', '#queueList').html('<span style=\"color:red;\">' + '还有2个未上传文件'.replace(/[\\d]/, count) + '</span>');\n                        return false;\n                    }\n                    break;\n                case 'online':\n                    list = onlineFile.getInsertList();\n                    break;\n            }\n\n            editor.execCommand('insertfile', list);\n        };\n    }\n\n\n    /* 上传附件 */\n    function UploadFile(target) {\n        this.$wrap = target.constructor == String ? $('#' + target) : $(target);\n        this.init();\n    }\n    UploadFile.prototype = {\n        init: function () {\n            this.fileList = [];\n            this.initContainer();\n            this.initUploader();\n        },\n        initContainer: function () {\n            this.$queue = this.$wrap.find('.filelist');\n        },\n        /* 初始化容器 */\n        initUploader: function () {\n            var _this = this,\n                $ = jQuery,    // just in case. Make sure it's not an other libaray.\n                $wrap = _this.$wrap,\n            // 图片容器\n                $queue = $wrap.find('.filelist'),\n            // 状态栏，包括进度和控制按钮\n                $statusBar = $wrap.find('.statusBar'),\n            // 文件总体选择信息。\n                $info = $statusBar.find('.info'),\n            // 上传按钮\n                $upload = $wrap.find('.uploadBtn'),\n            // 上传按钮\n                $filePickerBtn = $wrap.find('.filePickerBtn'),\n            // 上传按钮\n                $filePickerBlock = $wrap.find('.filePickerBlock'),\n            // 没选择文件之前的内容。\n                $placeHolder = $wrap.find('.placeholder'),\n            // 总体进度条\n                $progress = $statusBar.find('.progress').hide(),\n            // 添加的文件数量\n                fileCount = 0,\n            // 添加的文件总大小\n                fileSize = 0,\n            // 优化retina, 在retina下这个值是2\n                ratio = window.devicePixelRatio || 1,\n            // 缩略图大小\n                thumbnailWidth = 113 * ratio,\n                thumbnailHeight = 113 * ratio,\n            // 可能有pedding, ready, uploading, confirm, done.\n                state = '',\n            // 所有文件的进度信息，key为file id\n                percentages = {},\n                supportTransition = (function () {\n                    var s = document.createElement('p').style,\n                        r = 'transition' in s ||\n                            'WebkitTransition' in s ||\n                            'MozTransition' in s ||\n                            'msTransition' in s ||\n                            'OTransition' in s;\n                    s = null;\n                    return r;\n                })(),\n            // WebUploader实例\n                uploader,\n                actionUrl = editor.getActionUrl(editor.getOpt('fileActionName')),\n                fileMaxSize = editor.getOpt('fileMaxSize'),\n                acceptExtensions = (editor.getOpt('fileAllowFiles') || []).join('').replace(/\\./g, ',').replace(/^[,]/, '');;\n\n            if (!WebUploader.Uploader.support()) {\n                $('#filePickerReady').after($('<div>').html(lang.errorNotSupport)).hide();\n                return;\n            } else if (!editor.getOpt('fileActionName')) {\n                $('#filePickerReady').after($('<div>').html(lang.errorLoadConfig)).hide();\n                return;\n            }\n\n            uploader = _this.uploader = WebUploader.create({\n                pick: {\n                    id: '#filePickerReady',\n                    label: lang.uploadSelectFile\n                },\n                swf: '../../third-party/webuploader/Uploader.swf',\n                server: actionUrl,\n                fileVal: editor.getOpt('fileFieldName'),\n                duplicate: true,\n                fileSingleSizeLimit: fileMaxSize,\n                compress: false\n            });\n            uploader.addButton({\n                id: '#filePickerBlock'\n            });\n            uploader.addButton({\n                id: '#filePickerBtn',\n                label: lang.uploadAddFile\n            });\n\n            setState('pedding');\n\n            // 当有文件添加进来时执行，负责view的创建\n            function addFile(file) {\n                var $li = $('<li id=\"' + file.id + '\">' +\n                        '<p class=\"title\">' + file.name + '</p>' +\n                        '<p class=\"imgWrap\"></p>' +\n                        '<p class=\"progress\"><span></span></p>' +\n                        '</li>'),\n\n                    $btns = $('<div class=\"file-panel\">' +\n                        '<span class=\"cancel\">' + lang.uploadDelete + '</span>' +\n                        '<span class=\"rotateRight\">' + lang.uploadTurnRight + '</span>' +\n                        '<span class=\"rotateLeft\">' + lang.uploadTurnLeft + '</span></div>').appendTo($li),\n                    $prgress = $li.find('p.progress span'),\n                    $wrap = $li.find('p.imgWrap'),\n                    $info = $('<p class=\"error\"></p>').hide().appendTo($li),\n\n                    showError = function (code) {\n                        switch (code) {\n                            case 'exceed_size':\n                                text = lang.errorExceedSize;\n                                break;\n                            case 'interrupt':\n                                text = lang.errorInterrupt;\n                                break;\n                            case 'http':\n                                text = lang.errorHttp;\n                                break;\n                            case 'not_allow_type':\n                                text = lang.errorFileType;\n                                break;\n                            default:\n                                text = lang.errorUploadRetry;\n                                break;\n                        }\n                        $info.text(text).show();\n                    };\n\n                if (file.getStatus() === 'invalid') {\n                    showError(file.statusText);\n                } else {\n                    $wrap.text(lang.uploadPreview);\n                    if ('|png|jpg|jpeg|bmp|gif|'.indexOf('|'+file.ext.toLowerCase()+'|') == -1) {\n                        $wrap.empty().addClass('notimage').append('<i class=\"file-preview file-type-' + file.ext.toLowerCase() + '\"></i>' +\n                        '<span class=\"file-title\" title=\"' + file.name + '\">' + file.name + '</span>');\n                    } else {\n                        if (browser.ie && browser.version <= 7) {\n                            $wrap.text(lang.uploadNoPreview);\n                        } else {\n                            uploader.makeThumb(file, function (error, src) {\n                                if (error || !src) {\n                                    $wrap.text(lang.uploadNoPreview);\n                                } else {\n                                    var $img = $('<img src=\"' + src + '\">');\n                                    $wrap.empty().append($img);\n                                    $img.on('error', function () {\n                                        $wrap.text(lang.uploadNoPreview);\n                                    });\n                                }\n                            }, thumbnailWidth, thumbnailHeight);\n                        }\n                    }\n                    percentages[ file.id ] = [ file.size, 0 ];\n                    file.rotation = 0;\n\n                    /* 检查文件格式 */\n                    if (!file.ext || acceptExtensions.indexOf(file.ext.toLowerCase()) == -1) {\n                        showError('not_allow_type');\n                        uploader.removeFile(file);\n                    }\n                }\n\n                file.on('statuschange', function (cur, prev) {\n                    if (prev === 'progress') {\n                        $prgress.hide().width(0);\n                    } else if (prev === 'queued') {\n                        $li.off('mouseenter mouseleave');\n                        $btns.remove();\n                    }\n                    // 成功\n                    if (cur === 'error' || cur === 'invalid') {\n                        showError(file.statusText);\n                        percentages[ file.id ][ 1 ] = 1;\n                    } else if (cur === 'interrupt') {\n                        showError('interrupt');\n                    } else if (cur === 'queued') {\n                        percentages[ file.id ][ 1 ] = 0;\n                    } else if (cur === 'progress') {\n                        $info.hide();\n                        $prgress.css('display', 'block');\n                    } else if (cur === 'complete') {\n                    }\n\n                    $li.removeClass('state-' + prev).addClass('state-' + cur);\n                });\n\n                $li.on('mouseenter', function () {\n                    $btns.stop().animate({height: 30});\n                });\n                $li.on('mouseleave', function () {\n                    $btns.stop().animate({height: 0});\n                });\n\n                $btns.on('click', 'span', function () {\n                    var index = $(this).index(),\n                        deg;\n\n                    switch (index) {\n                        case 0:\n                            uploader.removeFile(file);\n                            return;\n                        case 1:\n                            file.rotation += 90;\n                            break;\n                        case 2:\n                            file.rotation -= 90;\n                            break;\n                    }\n\n                    if (supportTransition) {\n                        deg = 'rotate(' + file.rotation + 'deg)';\n                        $wrap.css({\n                            '-webkit-transform': deg,\n                            '-mos-transform': deg,\n                            '-o-transform': deg,\n                            'transform': deg\n                        });\n                    } else {\n                        $wrap.css('filter', 'progid:DXImageTransform.Microsoft.BasicImage(rotation=' + (~~((file.rotation / 90) % 4 + 4) % 4) + ')');\n                    }\n\n                });\n\n                $li.insertBefore($filePickerBlock);\n            }\n\n            // 负责view的销毁\n            function removeFile(file) {\n                var $li = $('#' + file.id);\n                delete percentages[ file.id ];\n                updateTotalProgress();\n                $li.off().find('.file-panel').off().end().remove();\n            }\n\n            function updateTotalProgress() {\n                var loaded = 0,\n                    total = 0,\n                    spans = $progress.children(),\n                    percent;\n\n                $.each(percentages, function (k, v) {\n                    total += v[ 0 ];\n                    loaded += v[ 0 ] * v[ 1 ];\n                });\n\n                percent = total ? loaded / total : 0;\n\n                spans.eq(0).text(Math.round(percent * 100) + '%');\n                spans.eq(1).css('width', Math.round(percent * 100) + '%');\n                updateStatus();\n            }\n\n            function setState(val, files) {\n\n                if (val != state) {\n\n                    var stats = uploader.getStats();\n\n                    $upload.removeClass('state-' + state);\n                    $upload.addClass('state-' + val);\n\n                    switch (val) {\n\n                        /* 未选择文件 */\n                        case 'pedding':\n                            $queue.addClass('element-invisible');\n                            $statusBar.addClass('element-invisible');\n                            $placeHolder.removeClass('element-invisible');\n                            $progress.hide(); $info.hide();\n                            uploader.refresh();\n                            break;\n\n                        /* 可以开始上传 */\n                        case 'ready':\n                            $placeHolder.addClass('element-invisible');\n                            $queue.removeClass('element-invisible');\n                            $statusBar.removeClass('element-invisible');\n                            $progress.hide(); $info.show();\n                            $upload.text(lang.uploadStart);\n                            uploader.refresh();\n                            break;\n\n                        /* 上传中 */\n                        case 'uploading':\n                            $progress.show(); $info.hide();\n                            $upload.text(lang.uploadPause);\n                            break;\n\n                        /* 暂停上传 */\n                        case 'paused':\n                            $progress.show(); $info.hide();\n                            $upload.text(lang.uploadContinue);\n                            break;\n\n                        case 'confirm':\n                            $progress.show(); $info.hide();\n                            $upload.text(lang.uploadStart);\n\n                            stats = uploader.getStats();\n                            if (stats.successNum && !stats.uploadFailNum) {\n                                setState('finish');\n                                return;\n                            }\n                            break;\n\n                        case 'finish':\n                            $progress.hide(); $info.show();\n                            if (stats.uploadFailNum) {\n                                $upload.text(lang.uploadRetry);\n                            } else {\n                                $upload.text(lang.uploadStart);\n                            }\n                            break;\n                    }\n\n                    state = val;\n                    updateStatus();\n\n                }\n\n                if (!_this.getQueueCount()) {\n                    $upload.addClass('disabled')\n                } else {\n                    $upload.removeClass('disabled')\n                }\n\n            }\n\n            function updateStatus() {\n                var text = '', stats;\n\n                if (state === 'ready') {\n                    text = lang.updateStatusReady.replace('_', fileCount).replace('_KB', WebUploader.formatSize(fileSize));\n                } else if (state === 'confirm') {\n                    stats = uploader.getStats();\n                    if (stats.uploadFailNum) {\n                        text = lang.updateStatusConfirm.replace('_', stats.successNum).replace('_', stats.successNum);\n                    }\n                } else {\n                    stats = uploader.getStats();\n                    text = lang.updateStatusFinish.replace('_', fileCount).\n                        replace('_KB', WebUploader.formatSize(fileSize)).\n                        replace('_', stats.successNum);\n\n                    if (stats.uploadFailNum) {\n                        text += lang.updateStatusError.replace('_', stats.uploadFailNum);\n                    }\n                }\n\n                $info.html(text);\n            }\n\n            uploader.on('fileQueued', function (file) {\n                fileCount++;\n                fileSize += file.size;\n\n                if (fileCount === 1) {\n                    $placeHolder.addClass('element-invisible');\n                    $statusBar.show();\n                }\n\n                addFile(file);\n            });\n\n            uploader.on('fileDequeued', function (file) {\n                fileCount--;\n                fileSize -= file.size;\n\n                removeFile(file);\n                updateTotalProgress();\n            });\n\n            uploader.on('filesQueued', function (file) {\n                if (!uploader.isInProgress() && (state == 'pedding' || state == 'finish' || state == 'confirm' || state == 'ready')) {\n                    setState('ready');\n                }\n                updateTotalProgress();\n            });\n\n            uploader.on('all', function (type, files) {\n                switch (type) {\n                    case 'uploadFinished':\n                        setState('confirm', files);\n                        break;\n                    case 'startUpload':\n                        /* 添加额外的GET参数 */\n                        var params = utils.serializeParam(editor.queryCommandValue('serverparam')) || '',\n                            url = utils.formatUrl(actionUrl + (actionUrl.indexOf('?') == -1 ? '?':'&') + 'encode=utf-8&' + params);\n                        uploader.option('server', url);\n                        setState('uploading', files);\n                        break;\n                    case 'stopUpload':\n                        setState('paused', files);\n                        break;\n                }\n            });\n\n            uploader.on('uploadBeforeSend', function (file, data, header) {\n                //这里可以通过data对象添加POST参数\n                header['X_Requested_With'] = 'XMLHttpRequest';\n                // HaoChuan9421\n                if(editor.options.headers && Object.prototype.toString.apply(editor.options.headers) === \"[object Object]\"){\n                    for(var key in editor.options.headers){\n                        header[key] = editor.options.headers[key]\n                    }\n                }\n            });\n\n            uploader.on('uploadProgress', function (file, percentage) {\n                var $li = $('#' + file.id),\n                    $percent = $li.find('.progress span');\n\n                $percent.css('width', percentage * 100 + '%');\n                percentages[ file.id ][ 1 ] = percentage;\n                updateTotalProgress();\n            });\n\n            uploader.on('uploadSuccess', function (file, ret) {\n                var $file = $('#' + file.id);\n                try {\n                    var responseText = (ret._raw || ret),\n                        json = utils.str2json(responseText);\n                    if (json.state == 'SUCCESS') {\n                        _this.fileList.push(json);\n                        $file.append('<span class=\"success\"></span>');\n                    } else {\n                        $file.find('.error').text(json.state).show();\n                    }\n                } catch (e) {\n                    $file.find('.error').text(lang.errorServerUpload).show();\n                }\n            });\n\n            uploader.on('uploadError', function (file, code) {\n            });\n            uploader.on('error', function (code, file) {\n                if (code == 'Q_TYPE_DENIED' || code == 'F_EXCEED_SIZE') {\n                    addFile(file);\n                }\n            });\n            uploader.on('uploadComplete', function (file, ret) {\n            });\n\n            $upload.on('click', function () {\n                if ($(this).hasClass('disabled')) {\n                    return false;\n                }\n\n                if (state === 'ready') {\n                    uploader.upload();\n                } else if (state === 'paused') {\n                    uploader.upload();\n                } else if (state === 'uploading') {\n                    uploader.stop();\n                }\n            });\n\n            $upload.addClass('state-' + state);\n            updateTotalProgress();\n        },\n        getQueueCount: function () {\n            var file, i, status, readyFile = 0, files = this.uploader.getFiles();\n            for (i = 0; file = files[i++]; ) {\n                status = file.getStatus();\n                if (status == 'queued' || status == 'uploading' || status == 'progress') readyFile++;\n            }\n            return readyFile;\n        },\n        getInsertList: function () {\n            var i, link, data, list = [],\n                prefix = editor.getOpt('fileUrlPrefix');\n            for (i = 0; i < this.fileList.length; i++) {\n                data = this.fileList[i];\n                link = data.url;\n                list.push({\n                    title: data.original || link.substr(link.lastIndexOf('/') + 1),\n                    url: prefix + link\n                });\n            }\n            return list;\n        }\n    };\n\n\n    /* 在线附件 */\n    function OnlineFile(target) {\n        this.container = utils.isString(target) ? document.getElementById(target) : target;\n        this.init();\n    }\n    OnlineFile.prototype = {\n        init: function () {\n            this.initContainer();\n            this.initEvents();\n            this.initData();\n        },\n        /* 初始化容器 */\n        initContainer: function () {\n            this.container.innerHTML = '';\n            this.list = document.createElement('ul');\n            this.clearFloat = document.createElement('li');\n\n            domUtils.addClass(this.list, 'list');\n            domUtils.addClass(this.clearFloat, 'clearFloat');\n\n            this.list.appendChild(this.clearFloat);\n            this.container.appendChild(this.list);\n        },\n        /* 初始化滚动事件,滚动到地步自动拉取数据 */\n        initEvents: function () {\n            var _this = this;\n\n            /* 滚动拉取图片 */\n            domUtils.on($G('fileList'), 'scroll', function(e){\n                var panel = this;\n                if (panel.scrollHeight - (panel.offsetHeight + panel.scrollTop) < 10) {\n                    _this.getFileData();\n                }\n            });\n            /* 选中图片 */\n            domUtils.on(this.list, 'click', function (e) {\n                var target = e.target || e.srcElement,\n                    li = target.parentNode;\n\n                if (li.tagName.toLowerCase() == 'li') {\n                    if (domUtils.hasClass(li, 'selected')) {\n                        domUtils.removeClasses(li, 'selected');\n                    } else {\n                        domUtils.addClass(li, 'selected');\n                    }\n                }\n            });\n        },\n        /* 初始化第一次的数据 */\n        initData: function () {\n\n            /* 拉取数据需要使用的值 */\n            this.state = 0;\n            this.listSize = editor.getOpt('fileManagerListSize');\n            this.listIndex = 0;\n            this.listEnd = false;\n\n            /* 第一次拉取数据 */\n            this.getFileData();\n        },\n        /* 向后台拉取图片列表数据 */\n        getFileData: function () {\n            var _this = this;\n\n            if(!_this.listEnd && !this.isLoadingData) {\n                this.isLoadingData = true;\n                ajax.request(editor.getActionUrl(editor.getOpt('fileManagerActionName')), {\n                    timeout: 100000,\n                    data: utils.extend({\n                            start: this.listIndex,\n                            size: this.listSize\n                        }, editor.queryCommandValue('serverparam')),\n                    method: 'get',\n                    onsuccess: function (r) {\n                        try {\n                            var json = eval('(' + r.responseText + ')');\n                            if (json.state == 'SUCCESS') {\n                                _this.pushData(json.list);\n                                _this.listIndex = parseInt(json.start) + parseInt(json.list.length);\n                                if(_this.listIndex >= json.total) {\n                                    _this.listEnd = true;\n                                }\n                                _this.isLoadingData = false;\n                            }\n                        } catch (e) {\n                            if(r.responseText.indexOf('ue_separate_ue') != -1) {\n                                var list = r.responseText.split(r.responseText);\n                                _this.pushData(list);\n                                _this.listIndex = parseInt(list.length);\n                                _this.listEnd = true;\n                                _this.isLoadingData = false;\n                            }\n                        }\n                    },\n                    onerror: function () {\n                        _this.isLoadingData = false;\n                    }\n                });\n            }\n        },\n        /* 添加图片到列表界面上 */\n        pushData: function (list) {\n            var i, item, img, filetype, preview, icon, _this = this,\n                urlPrefix = editor.getOpt('fileManagerUrlPrefix');\n            for (i = 0; i < list.length; i++) {\n                if(list[i] && list[i].url) {\n                    item = document.createElement('li');\n                    icon = document.createElement('span');\n                    filetype = list[i].url.substr(list[i].url.lastIndexOf('.') + 1);\n\n                    if ( \"png|jpg|jpeg|gif|bmp\".indexOf(filetype) != -1 ) {\n                        preview = document.createElement('img');\n                        domUtils.on(preview, 'load', (function(image){\n                            return function(){\n                                _this.scale(image, image.parentNode.offsetWidth, image.parentNode.offsetHeight);\n                            };\n                        })(preview));\n                        preview.width = 113;\n                        preview.setAttribute('src', urlPrefix + list[i].url + (list[i].url.indexOf('?') == -1 ? '?noCache=':'&noCache=') + (+new Date()).toString(36) );\n                    } else {\n                        var ic = document.createElement('i'),\n                            textSpan = document.createElement('span');\n                        textSpan.innerHTML = list[i].url.substr(list[i].url.lastIndexOf('/') + 1);\n                        preview = document.createElement('div');\n                        preview.appendChild(ic);\n                        preview.appendChild(textSpan);\n                        domUtils.addClass(preview, 'file-wrapper');\n                        domUtils.addClass(textSpan, 'file-title');\n                        domUtils.addClass(ic, 'file-type-' + filetype);\n                        domUtils.addClass(ic, 'file-preview');\n                    }\n                    domUtils.addClass(icon, 'icon');\n                    item.setAttribute('data-url', urlPrefix + list[i].url);\n                    if (list[i].original) {\n                        item.setAttribute('data-title', list[i].original);\n                    }\n\n                    item.appendChild(preview);\n                    item.appendChild(icon);\n                    this.list.insertBefore(item, this.clearFloat);\n                }\n            }\n        },\n        /* 改变图片大小 */\n        scale: function (img, w, h, type) {\n            var ow = img.width,\n                oh = img.height;\n\n            if (type == 'justify') {\n                if (ow >= oh) {\n                    img.width = w;\n                    img.height = h * oh / ow;\n                    img.style.marginLeft = '-' + parseInt((img.width - w) / 2) + 'px';\n                } else {\n                    img.width = w * ow / oh;\n                    img.height = h;\n                    img.style.marginTop = '-' + parseInt((img.height - h) / 2) + 'px';\n                }\n            } else {\n                if (ow >= oh) {\n                    img.width = w * ow / oh;\n                    img.height = h;\n                    img.style.marginLeft = '-' + parseInt((img.width - w) / 2) + 'px';\n                } else {\n                    img.width = w;\n                    img.height = h * oh / ow;\n                    img.style.marginTop = '-' + parseInt((img.height - h) / 2) + 'px';\n                }\n            }\n        },\n        getInsertList: function () {\n            var i, lis = this.list.children, list = [];\n            for (i = 0; i < lis.length; i++) {\n                if (domUtils.hasClass(lis[i], 'selected')) {\n                    var url = lis[i].getAttribute('data-url');\n                    var title = lis[i].getAttribute('data-title') || url.substr(url.lastIndexOf('/') + 1);\n                    list.push({\n                        title: title,\n                        url: url\n                    });\n                }\n            }\n            return list;\n        }\n    };\n\n\n})();"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/background/background.css",
    "content": ".wrapper{ width: 424px;margin: 10px auto; zoom:1;position: relative}\r\n.tabbody{height:225px;}\r\n.tabbody .panel { position: absolute;width:100%; height:100%;background: #fff; display: none;}\r\n.tabbody .focus { display: block;}\r\n\r\nbody{font-size: 12px;color: #888;overflow: hidden;}\r\ninput,label{vertical-align:middle}\r\n.clear{clear: both;}\r\n.pl{padding-left: 18px;padding-left: 23px\\9;}\r\n\r\n#imageList {width: 420px;height: 215px;margin-top: 10px;overflow: hidden;overflow-y: auto;}\r\n#imageList div {float: left;width: 100px;height: 95px;margin: 5px 10px;}\r\n#imageList img {cursor: pointer;border: 2px solid white;}\r\n\r\n.bgarea{margin: 10px;padding: 5px;height: 84%;border: 1px solid #A8A297;}\r\n.content div{margin: 10px 0 10px 5px;}\r\n.content .iptradio{margin: 0px 5px 5px 0px;}\r\n.txt{width:280px;}\r\n\r\n.wrapcolor{height: 19px;}\r\ndiv.color{float: left;margin: 0;}\r\n#colorPicker{width: 17px;height: 17px;border: 1px solid #CCC;display: inline-block;border-radius: 3px;box-shadow: 2px 2px 5px #D3D6DA;margin: 0;float: left;}\r\ndiv.alignment,#custom{margin-left: 23px;margin-left: 28px\\9;}\r\n#custom input{height: 15px;min-height: 15px;width:20px;}\r\n#repeatType{width:100px;}\r\n\r\n\r\n/* 图片管理样式 */\r\n#imgManager {\r\n    width: 100%;\r\n    height: 225px;\r\n}\r\n#imgManager #imageList{\r\n    width: 100%;\r\n    overflow-x: hidden;\r\n    overflow-y: auto;\r\n}\r\n#imgManager ul {\r\n    display: block;\r\n    list-style: none;\r\n    margin: 0;\r\n    padding: 0;\r\n}\r\n#imgManager li {\r\n    float: left;\r\n    display: block;\r\n    list-style: none;\r\n    padding: 0;\r\n    width: 113px;\r\n    height: 113px;\r\n    margin: 9px 0 0 19px;\r\n    background-color: #eee;\r\n    overflow: hidden;\r\n    cursor: pointer;\r\n    position: relative;\r\n}\r\n#imgManager li.clearFloat {\r\n    float: none;\r\n    clear: both;\r\n    display: block;\r\n    width:0;\r\n    height:0;\r\n    margin: 0;\r\n    padding: 0;\r\n}\r\n#imgManager li img {\r\n    cursor: pointer;\r\n}\r\n#imgManager li .icon {\r\n    cursor: pointer;\r\n    width: 113px;\r\n    height: 113px;\r\n    position: absolute;\r\n    top: 0;\r\n    left: 0;\r\n    z-index: 2;\r\n    border: 0;\r\n    background-repeat: no-repeat;\r\n}\r\n#imgManager li .icon:hover {\r\n    width: 107px;\r\n    height: 107px;\r\n    border: 3px solid #1094fa;\r\n}\r\n#imgManager li.selected .icon {\r\n    background-image: url(images/success.png);\r\n    background-position: 75px 75px;\r\n}\r\n#imgManager li.selected .icon:hover {\r\n    width: 107px;\r\n    height: 107px;\r\n    border: 3px solid #1094fa;\r\n    background-position: 72px 72px;\r\n}"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/background/background.html",
    "content": "<!DOCTYPE HTML>\r\n<html>\r\n<head>\r\n    <meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"/>\r\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"background.css\">\r\n</head>\r\n<body>\r\n    <div id=\"bg_container\" class=\"wrapper\">\r\n        <div id=\"tabHeads\" class=\"tabhead\">\r\n            <span class=\"focus\" data-content-id=\"normal\"><var id=\"lang_background_normal\"></var></span>\r\n            <span class=\"\" data-content-id=\"imgManager\"><var id=\"lang_background_local\"></var></span>\r\n        </div>\r\n        <div id=\"tabBodys\" class=\"tabbody\">\r\n            <div id=\"normal\" class=\"panel focus\">\r\n                <fieldset class=\"bgarea\">\r\n                    <legend><var id=\"lang_background_set\"></var></legend>\r\n                    <div class=\"content\">\r\n                        <div>\r\n                            <label><input id=\"nocolorRadio\" class=\"iptradio\" type=\"radio\" name=\"t\" value=\"none\" checked=\"checked\"><var id=\"lang_background_none\"></var></label>\r\n                            <label><input id=\"coloredRadio\" class=\"iptradio\" type=\"radio\" name=\"t\" value=\"color\"><var id=\"lang_background_colored\"></var></label>\r\n                        </div>\r\n                        <div class=\"wrapcolor pl\">\r\n                            <div class=\"color\">\r\n                                <var id=\"lang_background_color\"></var>:\r\n                            </div>\r\n                            <div id=\"colorPicker\"></div>\r\n                            <div class=\"clear\"></div>\r\n                        </div>\r\n                        <div class=\"wrapcolor pl\">\r\n                            <label><var id=\"lang_background_netimg\"></var>:</label><input class=\"txt\" type=\"text\" id=\"url\">\r\n                        </div>\r\n                        <div id=\"alignment\" class=\"alignment\">\r\n                            <var id=\"lang_background_align\"></var>:<select id=\"repeatType\">\r\n                                <option value=\"center\"></option>\r\n                                <option value=\"repeat-x\"></option>\r\n                                <option value=\"repeat-y\"></option>\r\n                                <option value=\"repeat\"></option>\r\n                                <option value=\"self\"></option>\r\n                            </select>\r\n                        </div>\r\n                        <div id=\"custom\" >\r\n                            <var id=\"lang_background_position\"></var>:x:<input type=\"text\" size=\"1\" id=\"x\" maxlength=\"4\" value=\"0\">px&nbsp;&nbsp;y:<input type=\"text\" size=\"1\" id=\"y\" maxlength=\"4\" value=\"0\">px\r\n                        </div>\r\n                    </div>\r\n                </fieldset>\r\n\r\n            </div>\r\n            <div id=\"imgManager\" class=\"panel\">\r\n                <div id=\"imageList\" style=\"\"></div>\r\n            </div>\r\n        </div>\r\n    </div>\r\n    <script type=\"text/javascript\" src=\"background.js\"></script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/background/background.js",
    "content": "(function () {\r\n\r\n    var onlineImage,\r\n        backupStyle = editor.queryCommandValue('background');\r\n\r\n    window.onload = function () {\r\n        initTabs();\r\n        initColorSelector();\r\n    };\r\n\r\n    /* 初始化tab标签 */\r\n    function initTabs(){\r\n        var tabs = $G('tabHeads').children;\r\n        for (var i = 0; i < tabs.length; i++) {\r\n            domUtils.on(tabs[i], \"click\", function (e) {\r\n                var target = e.target || e.srcElement;\r\n                for (var j = 0; j < tabs.length; j++) {\r\n                    if(tabs[j] == target){\r\n                        tabs[j].className = \"focus\";\r\n                        var contentId = tabs[j].getAttribute('data-content-id');\r\n                        $G(contentId).style.display = \"block\";\r\n                        if(contentId == 'imgManager') {\r\n                            initImagePanel();\r\n                        }\r\n                    }else {\r\n                        tabs[j].className = \"\";\r\n                        $G(tabs[j].getAttribute('data-content-id')).style.display = \"none\";\r\n                    }\r\n                }\r\n            });\r\n        }\r\n    }\r\n\r\n    /* 初始化颜色设置 */\r\n    function initColorSelector () {\r\n        var obj = editor.queryCommandValue('background');\r\n        if (obj) {\r\n            var color = obj['background-color'],\r\n                repeat = obj['background-repeat'] || 'repeat',\r\n                image = obj['background-image'] || '',\r\n                position = obj['background-position'] || 'center center',\r\n                pos = position.split(' '),\r\n                x = parseInt(pos[0]) || 0,\r\n                y = parseInt(pos[1]) || 0;\r\n\r\n            if(repeat == 'no-repeat' && (x || y)) repeat = 'self';\r\n\r\n            image = image.match(/url[\\s]*\\(([^\\)]*)\\)/);\r\n            image = image ? image[1]:'';\r\n            updateFormState('colored', color, image, repeat, x, y);\r\n        } else {\r\n            updateFormState();\r\n        }\r\n\r\n        var updateHandler = function () {\r\n            updateFormState();\r\n            updateBackground();\r\n        }\r\n        domUtils.on($G('nocolorRadio'), 'click', updateBackground);\r\n        domUtils.on($G('coloredRadio'), 'click', updateHandler);\r\n        domUtils.on($G('url'), 'keyup', function(){\r\n            if($G('url').value && $G('alignment').style.display == \"none\") {\r\n                utils.each($G('repeatType').children, function(item){\r\n                    item.selected = ('repeat' == item.getAttribute('value') ? 'selected':false);\r\n                });\r\n            }\r\n            updateHandler();\r\n        });\r\n        domUtils.on($G('repeatType'), 'change', updateHandler);\r\n        domUtils.on($G('x'), 'keyup', updateBackground);\r\n        domUtils.on($G('y'), 'keyup', updateBackground);\r\n\r\n        initColorPicker();\r\n    }\r\n\r\n    /* 初始化颜色选择器 */\r\n    function initColorPicker() {\r\n        var me = editor,\r\n            cp = $G(\"colorPicker\");\r\n\r\n        /* 生成颜色选择器ui对象 */\r\n        var popup = new UE.ui.Popup({\r\n            content: new UE.ui.ColorPicker({\r\n                noColorText: me.getLang(\"clearColor\"),\r\n                editor: me,\r\n                onpickcolor: function (t, color) {\r\n                    updateFormState('colored', color);\r\n                    updateBackground();\r\n                    UE.ui.Popup.postHide();\r\n                },\r\n                onpicknocolor: function (t, color) {\r\n                    updateFormState('colored', 'transparent');\r\n                    updateBackground();\r\n                    UE.ui.Popup.postHide();\r\n                }\r\n            }),\r\n            editor: me,\r\n            onhide: function () {\r\n            }\r\n        });\r\n\r\n        /* 设置颜色选择器 */\r\n        domUtils.on(cp, \"click\", function () {\r\n            popup.showAnchor(this);\r\n        });\r\n        domUtils.on(document, 'mousedown', function (evt) {\r\n            var el = evt.target || evt.srcElement;\r\n            UE.ui.Popup.postHide(el);\r\n        });\r\n        domUtils.on(window, 'scroll', function () {\r\n            UE.ui.Popup.postHide();\r\n        });\r\n    }\r\n\r\n    /* 初始化在线图片列表 */\r\n    function initImagePanel() {\r\n        onlineImage = onlineImage || new OnlineImage('imageList');\r\n    }\r\n\r\n    /* 更新背景色设置面板 */\r\n    function updateFormState (radio, color, url, align, x, y) {\r\n        var nocolorRadio = $G('nocolorRadio'),\r\n            coloredRadio = $G('coloredRadio');\r\n\r\n        if(radio) {\r\n            nocolorRadio.checked = (radio == 'colored' ? false:'checked');\r\n            coloredRadio.checked = (radio == 'colored' ? 'checked':false);\r\n        }\r\n        if(color) {\r\n            domUtils.setStyle($G(\"colorPicker\"), \"background-color\", color);\r\n        }\r\n\r\n        if(url && /^\\//.test(url)) {\r\n            var a = document.createElement('a');\r\n            a.href = url;\r\n            browser.ie && (a.href = a.href);\r\n            url = browser.ie ? a.href:(a.protocol + '//' + a.host + a.pathname + a.search + a.hash);\r\n        }\r\n\r\n        if(url || url === '') {\r\n            $G('url').value = url;\r\n        }\r\n        if(align) {\r\n            utils.each($G('repeatType').children, function(item){\r\n                item.selected = (align == item.getAttribute('value') ? 'selected':false);\r\n            });\r\n        }\r\n        if(x || y) {\r\n            $G('x').value = parseInt(x) || 0;\r\n            $G('y').value = parseInt(y) || 0;\r\n        }\r\n\r\n        $G('alignment').style.display = coloredRadio.checked && $G('url').value ? '':'none';\r\n        $G('custom').style.display = coloredRadio.checked && $G('url').value && $G('repeatType').value == 'self' ? '':'none';\r\n    }\r\n\r\n    /* 更新背景颜色 */\r\n    function updateBackground () {\r\n        if ($G('coloredRadio').checked) {\r\n            var color = domUtils.getStyle($G(\"colorPicker\"), \"background-color\"),\r\n                bgimg = $G(\"url\").value,\r\n                align = $G(\"repeatType\").value,\r\n                backgroundObj = {\r\n                    \"background-repeat\": \"no-repeat\",\r\n                    \"background-position\": \"center center\"\r\n                };\r\n\r\n            if (color) backgroundObj[\"background-color\"] = color;\r\n            if (bgimg) backgroundObj[\"background-image\"] = 'url(' + bgimg + ')';\r\n            if (align == 'self') {\r\n                backgroundObj[\"background-position\"] = $G(\"x\").value + \"px \" + $G(\"y\").value + \"px\";\r\n            } else if (align == 'repeat-x' || align == 'repeat-y' || align == 'repeat') {\r\n                backgroundObj[\"background-repeat\"] = align;\r\n            }\r\n\r\n            editor.execCommand('background', backgroundObj);\r\n        } else {\r\n            editor.execCommand('background', null);\r\n        }\r\n    }\r\n\r\n\r\n    /* 在线图片 */\r\n    function OnlineImage(target) {\r\n        this.container = utils.isString(target) ? document.getElementById(target) : target;\r\n        this.init();\r\n    }\r\n    OnlineImage.prototype = {\r\n        init: function () {\r\n            this.reset();\r\n            this.initEvents();\r\n        },\r\n        /* 初始化容器 */\r\n        initContainer: function () {\r\n            this.container.innerHTML = '';\r\n            this.list = document.createElement('ul');\r\n            this.clearFloat = document.createElement('li');\r\n\r\n            domUtils.addClass(this.list, 'list');\r\n            domUtils.addClass(this.clearFloat, 'clearFloat');\r\n\r\n            this.list.id = 'imageListUl';\r\n            this.list.appendChild(this.clearFloat);\r\n            this.container.appendChild(this.list);\r\n        },\r\n        /* 初始化滚动事件,滚动到地步自动拉取数据 */\r\n        initEvents: function () {\r\n            var _this = this;\r\n\r\n            /* 滚动拉取图片 */\r\n            domUtils.on($G('imageList'), 'scroll', function(e){\r\n                var panel = this;\r\n                if (panel.scrollHeight - (panel.offsetHeight + panel.scrollTop) < 10) {\r\n                    _this.getImageData();\r\n                }\r\n            });\r\n            /* 选中图片 */\r\n            domUtils.on(this.container, 'click', function (e) {\r\n                var target = e.target || e.srcElement,\r\n                    li = target.parentNode,\r\n                    nodes = $G('imageListUl').childNodes;\r\n\r\n                if (li.tagName.toLowerCase() == 'li') {\r\n                    updateFormState('nocolor', null, '');\r\n                    for (var i = 0, node; node = nodes[i++];) {\r\n                        if (node == li && !domUtils.hasClass(node, 'selected')) {\r\n                            domUtils.addClass(node, 'selected');\r\n                            updateFormState('colored', null, li.firstChild.getAttribute(\"_src\"), 'repeat');\r\n                        } else {\r\n                            domUtils.removeClasses(node, 'selected');\r\n                        }\r\n                    }\r\n                    updateBackground();\r\n                }\r\n            });\r\n        },\r\n        /* 初始化第一次的数据 */\r\n        initData: function () {\r\n\r\n            /* 拉取数据需要使用的值 */\r\n            this.state = 0;\r\n            this.listSize = editor.getOpt('imageManagerListSize');\r\n            this.listIndex = 0;\r\n            this.listEnd = false;\r\n\r\n            /* 第一次拉取数据 */\r\n            this.getImageData();\r\n        },\r\n        /* 重置界面 */\r\n        reset: function() {\r\n            this.initContainer();\r\n            this.initData();\r\n        },\r\n        /* 向后台拉取图片列表数据 */\r\n        getImageData: function () {\r\n            var _this = this;\r\n\r\n            if(!_this.listEnd && !this.isLoadingData) {\r\n                this.isLoadingData = true;\r\n                var url = editor.getActionUrl(editor.getOpt('imageManagerActionName')),\r\n                    isJsonp = utils.isCrossDomainUrl(url);\r\n                ajax.request(url, {\r\n                    'timeout': 100000,\r\n                    'dataType': isJsonp ? 'jsonp':'',\r\n                    'data': utils.extend({\r\n                            start: this.listIndex,\r\n                            size: this.listSize\r\n                        }, editor.queryCommandValue('serverparam')),\r\n                    'method': 'get',\r\n                    'onsuccess': function (r) {\r\n                        try {\r\n                            var json = isJsonp ? r:eval('(' + r.responseText + ')');\r\n                            if (json.state == 'SUCCESS') {\r\n                                _this.pushData(json.list);\r\n                                _this.listIndex = parseInt(json.start) + parseInt(json.list.length);\r\n                                if(_this.listIndex >= json.total) {\r\n                                    _this.listEnd = true;\r\n                                }\r\n                                _this.isLoadingData = false;\r\n                            }\r\n                        } catch (e) {\r\n                            if(r.responseText.indexOf('ue_separate_ue') != -1) {\r\n                                var list = r.responseText.split(r.responseText);\r\n                                _this.pushData(list);\r\n                                _this.listIndex = parseInt(list.length);\r\n                                _this.listEnd = true;\r\n                                _this.isLoadingData = false;\r\n                            }\r\n                        }\r\n                    },\r\n                    'onerror': function () {\r\n                        _this.isLoadingData = false;\r\n                    }\r\n                });\r\n            }\r\n        },\r\n        /* 添加图片到列表界面上 */\r\n        pushData: function (list) {\r\n            var i, item, img, icon, _this = this,\r\n                urlPrefix = editor.getOpt('imageManagerUrlPrefix');\r\n            for (i = 0; i < list.length; i++) {\r\n                if(list[i] && list[i].url) {\r\n                    item = document.createElement('li');\r\n                    img = document.createElement('img');\r\n                    icon = document.createElement('span');\r\n\r\n                    domUtils.on(img, 'load', (function(image){\r\n                        return function(){\r\n                            _this.scale(image, image.parentNode.offsetWidth, image.parentNode.offsetHeight);\r\n                        }\r\n                    })(img));\r\n                    img.width = 113;\r\n                    img.setAttribute('src', urlPrefix + list[i].url + (list[i].url.indexOf('?') == -1 ? '?noCache=':'&noCache=') + (+new Date()).toString(36) );\r\n                    img.setAttribute('_src', urlPrefix + list[i].url);\r\n                    domUtils.addClass(icon, 'icon');\r\n\r\n                    item.appendChild(img);\r\n                    item.appendChild(icon);\r\n                    this.list.insertBefore(item, this.clearFloat);\r\n                }\r\n            }\r\n        },\r\n        /* 改变图片大小 */\r\n        scale: function (img, w, h, type) {\r\n            var ow = img.width,\r\n                oh = img.height;\r\n\r\n            if (type == 'justify') {\r\n                if (ow >= oh) {\r\n                    img.width = w;\r\n                    img.height = h * oh / ow;\r\n                    img.style.marginLeft = '-' + parseInt((img.width - w) / 2) + 'px';\r\n                } else {\r\n                    img.width = w * ow / oh;\r\n                    img.height = h;\r\n                    img.style.marginTop = '-' + parseInt((img.height - h) / 2) + 'px';\r\n                }\r\n            } else {\r\n                if (ow >= oh) {\r\n                    img.width = w * ow / oh;\r\n                    img.height = h;\r\n                    img.style.marginLeft = '-' + parseInt((img.width - w) / 2) + 'px';\r\n                } else {\r\n                    img.width = w;\r\n                    img.height = h * oh / ow;\r\n                    img.style.marginTop = '-' + parseInt((img.height - h) / 2) + 'px';\r\n                }\r\n            }\r\n        },\r\n        getInsertList: function () {\r\n            var i, lis = this.list.children, list = [], align = getAlign();\r\n            for (i = 0; i < lis.length; i++) {\r\n                if (domUtils.hasClass(lis[i], 'selected')) {\r\n                    var img = lis[i].firstChild,\r\n                        src = img.getAttribute('_src');\r\n                    list.push({\r\n                        src: src,\r\n                        _src: src,\r\n                        floatStyle: align\r\n                    });\r\n                }\r\n\r\n            }\r\n            return list;\r\n        }\r\n    };\r\n\r\n    dialog.onok = function () {\r\n        updateBackground();\r\n        editor.fireEvent('saveScene');\r\n    };\r\n    dialog.oncancel = function () {\r\n        editor.execCommand('background', backupStyle);\r\n    };\r\n\r\n})();"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/charts/chart.config.js",
    "content": "/*\n * 图表配置文件\n * */\n\n\n//不同类型的配置\nvar typeConfig = [\n    {\n        chart: {\n            type: 'line'\n        },\n        plotOptions: {\n            line: {\n                dataLabels: {\n                    enabled: false\n                },\n                enableMouseTracking: true\n            }\n        }\n    }, {\n        chart: {\n            type: 'line'\n        },\n        plotOptions: {\n            line: {\n                dataLabels: {\n                    enabled: true\n                },\n                enableMouseTracking: false\n            }\n        }\n    }, {\n        chart: {\n            type: 'area'\n        }\n    }, {\n        chart: {\n            type: 'bar'\n        }\n    }, {\n        chart: {\n            type: 'column'\n        }\n    }, {\n        chart: {\n            plotBackgroundColor: null,\n            plotBorderWidth: null,\n            plotShadow: false\n        },\n        plotOptions: {\n            pie: {\n                allowPointSelect: true,\n                cursor: 'pointer',\n                dataLabels: {\n                    enabled: true,\n                    color: '#000000',\n                    connectorColor: '#000000',\n                    formatter: function() {\n                        return '<b>'+ this.point.name +'</b>: '+ ( Math.round( this.point.percentage*100 ) / 100 ) +' %';\n                    }\n                }\n            }\n        }\n    }\n];\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/charts/charts.css",
    "content": "html, body {\n    width: 100%;\n    height: 100%;\n    margin: 0;\n    padding: 0;\n    overflow-x: hidden;\n}\n\n.main {\n    width: 100%;\n    overflow: hidden;\n}\n\n.table-view {\n    height: 100%;\n    float: left;\n    margin: 20px;\n    width: 40%;\n}\n\n.table-view .table-container {\n    width: 100%;\n    margin-bottom: 50px;\n    overflow: scroll;\n}\n\n.table-view th {\n    padding: 5px 10px;\n    background-color: #F7F7F7;\n}\n\n.table-view td {\n    width: 50px;\n    text-align: center;\n    padding:0;\n}\n\n.table-container input {\n    width: 40px;\n    padding: 5px;\n    border: none;\n    outline: none;\n}\n\n.table-view caption {\n    font-size: 18px;\n    text-align: left;\n}\n\n.charts-view {\n    /*margin-left: 49%!important;*/\n    width: 50%;\n    margin-left: 49%;\n    height: 400px;\n}\n\n.charts-container {\n    border-left: 1px solid #c3c3c3;\n}\n\n.charts-format fieldset {\n    padding-left: 20px;\n    margin-bottom: 50px;\n}\n\n.charts-format legend {\n    padding-left: 10px;\n    padding-right: 10px;\n}\n\n.format-item-container {\n    padding: 20px;\n}\n\n.format-item-container label {\n    display: block;\n    margin: 10px 0;\n}\n\n.charts-format .data-item {\n    border: 1px solid black;\n    outline: none;\n    padding: 2px 3px;\n}\n\n/* 图表类型 */\n\n.charts-type {\n    margin-top: 50px;\n    height: 300px;\n}\n\n.scroll-view {\n    border: 1px solid #c3c3c3;\n    border-left: none;\n    border-right: none;\n    overflow: hidden;\n}\n\n.scroll-container {\n    margin: 20px;\n    width: 100%;\n    overflow: hidden;\n}\n\n.scroll-bed {\n    width: 10000px;\n    _margin-top: 20px;\n    -webkit-transition: margin-left .5s ease;\n    -moz-transition: margin-left .5s ease;\n    transition: margin-left .5s ease;\n}\n\n.view-box {\n    display: inline-block;\n    *display: inline;\n    *zoom: 1;\n    margin-right: 20px;\n    border: 2px solid white;\n    line-height: 0;\n    overflow: hidden;\n    cursor: pointer;\n}\n\n.view-box img {\n    border: 1px solid #cecece;\n}\n\n.view-box.selected {\n    border-color: #7274A7;\n}\n\n.button-container {\n    margin-bottom: 20px;\n    text-align: center;\n}\n\n.button-container a {\n    display: inline-block;\n    width: 100px;\n    height: 25px;\n    line-height: 25px;\n    border: 1px solid #c2ccd1;\n    margin-right: 30px;\n    text-decoration: none;\n    color: black;\n    -webkit-border-radius: 2px;\n    -moz-border-radius: 2px;\n    border-radius: 2px;\n}\n\n.button-container a:HOVER {\n    background: #fcfcfc;\n}\n\n.button-container a:ACTIVE {\n    border-top-color: #c2ccd1;\n    box-shadow:inset 0 5px 4px -4px rgba(49, 49, 64, 0.1);\n}\n\n.edui-charts-not-data {\n    height: 100px;\n    line-height: 100px;\n    text-align: center;\n}"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/charts/charts.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n    <head>\r\n        <title>chart</title>\r\n        <meta chartset=\"utf-8\">\r\n        <link rel=\"stylesheet\" type=\"text/css\" href=\"charts.css\">\r\n        <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n    </head>\r\n    <body>\r\n        <div class=\"main\">\r\n            <div class=\"table-view\">\r\n                <h3><var id=\"lang_data_source\"></var></h3>\r\n                <div id=\"tableContainer\" class=\"table-container\"></div>\r\n                <h3><var id=\"lang_chart_format\"></var></h3>\r\n                <form name=\"data-form\">\r\n                    <div class=\"charts-format\">\r\n                        <fieldset>\r\n                            <legend><var id=\"lang_data_align\"></var></legend>\r\n                            <div class=\"format-item-container\">\r\n                                <label>\r\n                                    <input type=\"radio\" class=\"format-ctrl not-pie-item\" name=\"charts-format\" value=\"1\" checked=\"checked\">\r\n                                    <var id=\"lang_chart_align_same\"></var>\r\n                                </label>\r\n                                <label>\r\n                                    <input type=\"radio\" class=\"format-ctrl not-pie-item\" name=\"charts-format\" value=\"-1\">\r\n                                    <var id=\"lang_chart_align_reverse\"></var>\r\n                                </label>\r\n                                <br>\r\n                            </div>\r\n                        </fieldset>\r\n                        <fieldset>\r\n                            <legend><var id=\"lang_chart_title\"></var></legend>\r\n                            <div class=\"format-item-container\">\r\n                                <label>\r\n                                    <var id=\"lang_chart_main_title\"></var><input type=\"text\" name=\"title\" class=\"data-item\">\r\n                                </label>\r\n                                <label>\r\n                                    <var id=\"lang_chart_sub_title\"></var><input type=\"text\" name=\"sub-title\" class=\"data-item not-pie-item\">\r\n                                </label>\r\n                                <label>\r\n                                    <var id=\"lang_chart_x_title\"></var><input type=\"text\" name=\"x-title\" class=\"data-item not-pie-item\">\r\n                                </label>\r\n                                <label>\r\n                                    <var id=\"lang_chart_y_title\"></var><input type=\"text\" name=\"y-title\" class=\"data-item not-pie-item\">\r\n                                </label>\r\n                            </div>\r\n                        </fieldset>\r\n                        <fieldset>\r\n                            <legend><var id=\"lang_chart_tip\"></var></legend>\r\n                            <div class=\"format-item-container\">\r\n                                <label>\r\n                                    <var id=\"lang_cahrt_tip_prefix\"></var>\r\n                                    <input type=\"text\" id=\"tipInput\" name=\"tip\" class=\"data-item\" disabled=\"disabled\">\r\n                                </label>\r\n                                <p><var id=\"lang_cahrt_tip_description\"></var></p>\r\n                            </div>\r\n                        </fieldset>\r\n                        <fieldset>\r\n                            <legend><var id=\"lang_chart_data_unit\"></var></legend>\r\n                            <div class=\"format-item-container\">\r\n                                <label><var id=\"lang_chart_data_unit_title\"></var><input type=\"text\" name=\"unit\" class=\"data-item\"></label>\r\n                                <p><var id=\"lang_chart_data_unit_description\"></var></p>\r\n                            </div>\r\n                        </fieldset>\r\n                    </div>\r\n                </form>\r\n            </div>\r\n            <div class=\"charts-view\">\r\n                <div id=\"chartsContainer\" class=\"charts-container\"></div>\r\n                <div id=\"chartsType\" class=\"charts-type\">\r\n                    <h3><var id=\"lang_chart_type\"></var></h3>\r\n                    <div class=\"scroll-view\">\r\n                        <div class=\"scroll-container\">\r\n                            <div id=\"scrollBed\" class=\"scroll-bed\"></div>\r\n                        </div>\r\n                        <div id=\"buttonContainer\" class=\"button-container\">\r\n                            <a href=\"#\" data-title=\"prev\"><var id=\"lang_prev_btn\"></var></a>\r\n                            <a href=\"#\" data-title=\"next\"><var id=\"lang_next_btn\"></var></a>\r\n                        </div>\r\n                    </div>\r\n                </div>\r\n            </div>\r\n        </div>\r\n        <script src=\"../../third-party/jquery-1.10.2.min.js\"></script>\r\n        <script src=\"../../third-party/highcharts/highcharts.js\"></script>\r\n        <script src=\"chart.config.js\"></script>\r\n        <script src=\"charts.js\"></script>\r\n    </body>\r\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/charts/charts.js",
    "content": "/*\n * 图片转换对话框脚本\n **/\n\nvar tableData = [],\n    //编辑器页面table\n    editorTable = null,\n    chartsConfig = window.typeConfig,\n    resizeTimer = null,\n    //初始默认图表类型\n    currentChartType = 0;\n\nwindow.onload = function () {\n\n    editorTable = domUtils.findParentByTagName( editor.selection.getRange().startContainer, 'table', true);\n\n    //未找到表格， 显示错误页面\n    if ( !editorTable ) {\n        document.body.innerHTML = \"<div class='edui-charts-not-data'>未找到数据</div>\";\n        return;\n    }\n\n    //初始化图表类型选择\n    initChartsTypeView();\n    renderTable( editorTable );\n    initEvent();\n    initUserConfig( editorTable.getAttribute( \"data-chart\" ) );\n    $( \"#scrollBed .view-box:eq(\"+ currentChartType +\")\" ).trigger( \"click\" );\n    updateViewType( currentChartType );\n\n    dialog.addListener( \"resize\", function () {\n\n        if ( resizeTimer != null ) {\n            window.clearTimeout( resizeTimer );\n        }\n\n        resizeTimer = window.setTimeout( function () {\n\n            resizeTimer = null;\n\n            renderCharts();\n\n        }, 500 );\n\n    } );\n\n};\n\nfunction initChartsTypeView () {\n\n    var contents = [];\n\n    for ( var i = 0, len = chartsConfig.length; i<len; i++ ) {\n\n        contents.push( '<div class=\"view-box\" data-chart-type=\"'+ i +'\"><img width=\"300\" src=\"images/charts'+ i +'.png\"></div>' );\n\n    }\n\n    $( \"#scrollBed\" ).html( contents.join( \"\" ) );\n\n}\n\n//渲染table， 以便用户修改数据\nfunction renderTable ( table ) {\n\n    var tableHtml = [];\n\n    //构造数据\n    for ( var i = 0, row; row = table.rows[ i ]; i++ ) {\n\n        tableData[ i ] = [];\n        tableHtml[ i ] = [];\n\n        for ( var j = 0, cell; cell = row.cells[ j ]; j++ ) {\n\n            var value = getCellValue( cell );\n\n            if ( i > 0 && j > 0 ) {\n                value = +value;\n            }\n\n            if ( i === 0 || j === 0 ) {\n                tableHtml[ i ].push( '<th>'+ value +'</th>' );\n            } else {\n                tableHtml[ i ].push( '<td><input type=\"text\" class=\"data-item\" value=\"'+ value +'\"></td>' );\n            }\n\n            tableData[ i ][ j ] = value;\n\n        }\n\n        tableHtml[ i ] = tableHtml[ i ].join( \"\" );\n\n    }\n\n    //draw 表格\n    $( \"#tableContainer\" ).html( '<table id=\"showTable\" border=\"1\"><tbody><tr>'+ tableHtml.join( \"</tr><tr>\" ) +'</tr></tbody></table>' );\n\n}\n\n/*\n * 根据表格已有的图表属性初始化当前图表属性\n */\nfunction initUserConfig ( config ) {\n\n    var parsedConfig = {};\n\n    if ( !config ) {\n        return;\n    }\n\n    config = config.split( \";\" );\n\n    $.each( config, function ( index, item ) {\n\n        item = item.split( \":\" );\n        parsedConfig[ item[ 0 ] ] = item[ 1 ];\n\n    } );\n\n    setUserConfig( parsedConfig );\n\n}\n\nfunction initEvent () {\n\n    var cacheValue = null,\n        //图表类型数\n        typeViewCount = chartsConfig.length- 1,\n        $chartsTypeViewBox = $( '#scrollBed .view-box' );\n\n    $( \".charts-format\" ).delegate( \".format-ctrl\", \"change\", function () {\n\n        renderCharts();\n\n    } )\n\n    $( \".table-view\" ).delegate( \".data-item\", \"focus\", function () {\n\n        cacheValue = this.value;\n\n    } ).delegate( \".data-item\", \"blur\", function () {\n\n        if ( this.value !== cacheValue ) {\n            renderCharts();\n        }\n\n        cacheValue = null;\n\n    } );\n\n    $( \"#buttonContainer\" ).delegate( \"a\", \"click\", function (e) {\n\n        e.preventDefault();\n\n        if ( this.getAttribute( \"data-title\" ) === 'prev' ) {\n\n            if ( currentChartType > 0 ) {\n                currentChartType--;\n                updateViewType( currentChartType );\n            }\n\n        } else {\n\n            if ( currentChartType < typeViewCount ) {\n                currentChartType++;\n                updateViewType( currentChartType );\n            }\n\n        }\n\n    } );\n\n    //图表类型变化\n    $( '#scrollBed' ).delegate( \".view-box\", \"click\", function (e) {\n\n        var index = $( this ).attr( \"data-chart-type\" );\n        $chartsTypeViewBox.removeClass( \"selected\" );\n        $( $chartsTypeViewBox[ index ] ).addClass( \"selected\" );\n\n        currentChartType = index | 0;\n\n        //饼图， 禁用部分配置\n        if ( currentChartType === chartsConfig.length - 1 ) {\n\n            disableNotPieConfig();\n\n        //启用完整配置\n        } else {\n\n            enableNotPieConfig();\n\n        }\n\n        renderCharts();\n\n    } );\n\n}\n\nfunction renderCharts () {\n\n    var data = collectData();\n\n    $('#chartsContainer').highcharts( $.extend( {}, chartsConfig[ currentChartType ], {\n\n        credits: {\n            enabled: false\n        },\n        exporting: {\n            enabled: false\n        },\n        title: {\n            text: data.title,\n            x: -20 //center\n        },\n        subtitle: {\n            text: data.subTitle,\n            x: -20\n        },\n        xAxis: {\n            title: {\n                text: data.xTitle\n            },\n            categories: data.categories\n        },\n        yAxis: {\n            title: {\n                text: data.yTitle\n            },\n            plotLines: [{\n                value: 0,\n                width: 1,\n                color: '#808080'\n            }]\n        },\n        tooltip: {\n            enabled: true,\n            valueSuffix: data.suffix\n        },\n        legend: {\n            layout: 'vertical',\n            align: 'right',\n            verticalAlign: 'middle',\n            borderWidth: 1\n        },\n        series: data.series\n\n    } ));\n\n}\n\nfunction updateViewType ( index ) {\n\n    $( \"#scrollBed\" ).css( 'marginLeft', -index*324+'px' );\n\n}\n\nfunction collectData () {\n\n    var form = document.forms[ 'data-form' ],\n        data = null;\n\n    if ( currentChartType !== chartsConfig.length - 1 ) {\n\n        data = getSeriesAndCategories();\n        $.extend( data, getUserConfig() );\n\n    //饼图数据格式\n    } else {\n        data = getSeriesForPieChart();\n        data.title = form[ 'title' ].value;\n        data.suffix = form[ 'unit' ].value;\n    }\n\n    return data;\n\n}\n\n/**\n * 获取用户配置信息\n */\nfunction getUserConfig () {\n\n    var form = document.forms[ 'data-form' ],\n        info = {\n            title: form[ 'title' ].value,\n            subTitle: form[ 'sub-title' ].value,\n            xTitle: form[ 'x-title' ].value,\n            yTitle: form[ 'y-title' ].value,\n            suffix: form[ 'unit' ].value,\n            //数据对齐方式\n            tableDataFormat: getTableDataFormat (),\n            //饼图提示文字\n            tip: $( \"#tipInput\" ).val()\n        };\n\n    return info;\n\n}\n\nfunction setUserConfig ( config ) {\n\n    var form = document.forms[ 'data-form' ];\n\n    config.title && ( form[ 'title' ].value = config.title );\n    config.subTitle && ( form[ 'sub-title' ].value = config.subTitle );\n    config.xTitle && ( form[ 'x-title' ].value = config.xTitle );\n    config.yTitle && ( form[ 'y-title' ].value = config.yTitle );\n    config.suffix && ( form[ 'unit' ].value = config.suffix );\n    config.dataFormat == \"-1\" && ( form[ 'charts-format' ][ 1 ].checked = true );\n    config.tip && ( form[ 'tip' ].value = config.tip );\n    currentChartType = config.chartType || 0;\n\n}\n\nfunction getSeriesAndCategories () {\n\n    var form = document.forms[ 'data-form' ],\n        series = [],\n        categories = [],\n        tmp = [],\n        tableData = getTableData();\n\n    //反转数据\n    if ( getTableDataFormat() === \"-1\" ) {\n\n        for ( var i = 0, len = tableData.length; i < len; i++ ) {\n\n            for ( var j = 0, jlen = tableData[ i ].length; j < jlen; j++ ) {\n\n                if ( !tmp[ j ] ) {\n                    tmp[ j ] = [];\n                }\n\n                tmp[ j ][ i ] = tableData[ i ][ j ];\n\n            }\n\n        }\n\n        tableData = tmp;\n\n    }\n\n    categories = tableData[0].slice( 1 );\n\n    for ( var i = 1, data; data = tableData[ i ]; i++ ) {\n\n        series.push( {\n            name: data[ 0 ],\n            data: data.slice( 1 )\n        } );\n\n    }\n\n    return {\n        series: series,\n        categories: categories\n    };\n\n}\n\n/*\n * 获取数据源数据对齐方式\n */\nfunction getTableDataFormat () {\n\n    var form = document.forms[ 'data-form' ],\n        items = form['charts-format'];\n\n    return items[ 0 ].checked ? items[ 0 ].value : items[ 1 ].value;\n\n}\n\n/*\n * 禁用非饼图类型的配置项\n */\nfunction disableNotPieConfig() {\n\n    updateConfigItem( 'disable' );\n\n}\n\n/*\n * 启用非饼图类型的配置项\n */\nfunction enableNotPieConfig() {\n\n    updateConfigItem( 'enable' );\n\n}\n\nfunction updateConfigItem ( value ) {\n\n    var table = $( \"#showTable\" )[ 0 ],\n        isDisable = value === 'disable' ? true : false;\n\n    //table中的input处理\n    for ( var i = 2 , row; row = table.rows[ i ]; i++ ) {\n\n        for ( var j = 1, cell; cell = row.cells[ j ]; j++ ) {\n\n            $( \"input\", cell ).attr( \"disabled\", isDisable );\n\n        }\n\n    }\n\n    //其他项处理\n    $( \"input.not-pie-item\" ).attr( \"disabled\", isDisable );\n    $( \"#tipInput\" ).attr( \"disabled\", !isDisable )\n\n}\n\n/*\n * 获取饼图数据\n * 饼图的数据只取第一行的\n **/\nfunction getSeriesForPieChart () {\n\n    var series = {\n            type: 'pie',\n            name: $(\"#tipInput\").val(),\n            data: []\n        },\n        tableData = getTableData();\n\n\n    for ( var j = 1, jlen = tableData[ 0 ].length; j < jlen; j++ ) {\n\n        var title = tableData[ 0 ][ j ],\n            val = tableData[ 1 ][ j ];\n\n        series.data.push( [ title, val ] );\n\n    }\n\n    return {\n        series: [ series ]\n    };\n\n}\n\nfunction getTableData () {\n\n    var table = document.getElementById( \"showTable\" ),\n        xCount = table.rows[0].cells.length - 1,\n        values = getTableInputValue();\n\n    for ( var i = 0, value; value = values[ i ]; i++ ) {\n\n        tableData[ Math.floor( i / xCount ) + 1 ][ i % xCount + 1 ] = values[ i ];\n\n    }\n\n    return tableData;\n\n}\n\nfunction getTableInputValue () {\n\n    var table = document.getElementById( \"showTable\" ),\n        inputs = table.getElementsByTagName( \"input\" ),\n        values = [];\n\n    for ( var i = 0, input; input = inputs[ i ]; i++ ) {\n        values.push( input.value | 0 );\n    }\n\n    return values;\n\n}\n\nfunction getCellValue ( cell ) {\n\n    var value = utils.trim( ( cell.innerText || cell.textContent || '' ) );\n\n    return value.replace( new RegExp( UE.dom.domUtils.fillChar, 'g' ), '' ).replace( /^\\s+|\\s+$/g, '' );\n\n}\n\n\n//dialog确认事件\ndialog.onok = function () {\n\n    //收集信息\n    var form = document.forms[ 'data-form' ],\n        info = getUserConfig();\n\n    //添加图表类型\n    info.chartType = currentChartType;\n\n    //同步表格数据到编辑器\n    syncTableData();\n\n    //执行图表命令\n    editor.execCommand( 'charts', info );\n\n};\n\n/*\n * 同步图表编辑视图的表格数据到编辑器里的原始表格\n */\nfunction syncTableData () {\n\n    var tableData = getTableData();\n\n    for ( var i = 1, row; row = editorTable.rows[ i ]; i++ ) {\n\n        for ( var j = 1, cell; cell = row.cells[ j ]; j++ ) {\n\n            cell.innerHTML = tableData[ i ] [ j ];\n\n        }\n\n    }\n\n}"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/emotion/emotion.css",
    "content": ".jd img{\r\n    background:transparent url(images/jxface2.gif?v=1.1) no-repeat scroll left top;\r\n    cursor:pointer;width:35px;height:35px;display:block;\r\n}\r\n.pp img{\r\n    background:transparent url(images/fface.gif?v=1.1) no-repeat scroll left top;\r\n    cursor:pointer;width:25px;height:25px;display:block;\r\n}\r\n.ldw img{\r\n    background:transparent url(images/wface.gif?v=1.1) no-repeat scroll left top;\r\n    cursor:pointer;width:35px;height:35px;display:block;\r\n}\r\n.tsj img{\r\n    background:transparent url(images/tface.gif?v=1.1) no-repeat scroll left top;\r\n    cursor:pointer;width:35px;height:35px;display:block;\r\n}\r\n.cat img{\r\n    background:transparent url(images/cface.gif?v=1.1) no-repeat scroll left top;\r\n    cursor:pointer;width:35px;height:35px;display:block;\r\n}\r\n.bb img{\r\n    background:transparent url(images/bface.gif?v=1.1) no-repeat scroll left top;\r\n    cursor:pointer;width:35px;height:35px;display:block;\r\n}\r\n.youa img{\r\n    background:transparent url(images/yface.gif?v=1.1) no-repeat scroll left top;\r\n    cursor:pointer;width:35px;height:35px;display:block;\r\n}\r\n\r\n.smileytable td {height: 37px;}\r\n#tabPanel{margin-left:5px;overflow: hidden;}\r\n#tabContent {float:left;background:#FFFFFF;}\r\n#tabContent div{display: none;width:480px;overflow:hidden;}\r\n#tabIconReview.show{left:17px;display:block;}\r\n.menuFocus{background:#ACCD3C;}\r\n.menuDefault{background:#FFFFFF;}\r\n#tabIconReview{position:absolute;left:406px;left:398px \\9;top:41px;z-index:65533;width:90px;height:76px;}\r\nimg.review{width:90px;height:76px;border:2px solid #9cb945;background:#FFFFFF;background-position:center;background-repeat:no-repeat;}\r\n\r\n.wrapper .tabbody{position:relative;float:left;clear:both;padding:10px;width: 95%;}\r\n.tabbody table{width: 100%;}\r\n.tabbody td{border:1px solid #BAC498;}\r\n.tabbody td span{display: block;zoom:1;padding:0 4px;}"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/emotion/emotion.html",
    "content": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.0 Transitional//EN\" >\r\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\r\n<head>\r\n    <title></title>\r\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\r\n    <meta name=\"robots\" content=\"noindex, nofollow\"/>\r\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"emotion.css\">\r\n</head>\r\n<body>\r\n<div id=\"tabPanel\" class=\"wrapper\">\r\n    <div id=\"tabHeads\" class=\"tabhead\">\r\n        <span><var id=\"lang_input_choice\"></var></span>\r\n        <span><var id=\"lang_input_Tuzki\"></var></span>\r\n        <span><var id=\"lang_input_lvdouwa\"></var></span>\r\n        <span><var id=\"lang_input_BOBO\"></var></span>\r\n        <span><var id=\"lang_input_babyCat\"></var></span>\r\n        <span><var id=\"lang_input_bubble\"></var></span>\r\n        <span><var id=\"lang_input_youa\"></var></span>\r\n    </div>\r\n    <div id=\"tabBodys\" class=\"tabbody\">\r\n        <div id=\"tab0\"></div>\r\n        <div id=\"tab1\"></div>\r\n        <div id=\"tab2\"></div>\r\n        <div id=\"tab3\"></div>\r\n        <div id=\"tab4\"></div>\r\n        <div id=\"tab5\"></div>\r\n        <div id=\"tab6\"></div>\r\n    </div>\r\n</div>\r\n<div id=\"tabIconReview\">\r\n    <img id='faceReview' class='review' src=\"../../themes/default/images/spacer.gif\"/>\r\n</div>\r\n<script type=\"text/javascript\" src=\"emotion.js\"></script>\r\n<script type=\"text/javascript\">\r\n    var emotion = {\r\n        tabNum:7, //切换面板数量\r\n        SmilmgName:{ tab0:['j_00', 84], tab1:['t_00', 40], tab2:['w_00', 52], tab3:['B_00', 63], tab4:['C_00', 20], tab5:['i_f', 50], tab6:['y_00', 40] }, //图片前缀名\r\n        imageFolders:{ tab0:'jx2/', tab1:'tsj/', tab2:'ldw/', tab3:'bobo/', tab4:'babycat/', tab5:'face/', tab6:'youa/'}, //图片对应文件夹路径\r\n        imageCss:{tab0:'jd', tab1:'tsj', tab2:'ldw', tab3:'bb', tab4:'cat', tab5:'pp', tab6:'youa'}, //图片css类名\r\n        imageCssOffset:{tab0:35, tab1:35, tab2:35, tab3:35, tab4:35, tab5:25, tab6:35}, //图片偏移\r\n        SmileyInfor:{\r\n            tab0:['Kiss', 'Love', 'Yeah', '啊！', '背扭', '顶', '抖胸', '88', '汗', '瞌睡', '鲁拉', '拍砖', '揉脸', '生日快乐', '大笑', '瀑布汗~', '惊讶', '臭美', '傻笑', '抛媚眼', '发怒', '打酱油', '俯卧撑', '气愤', '?', '吻', '怒', '胜利', 'HI', 'KISS', '不说', '不要', '扯花', '大心', '顶', '大惊', '飞吻', '鬼脸', '害羞', '口水', '狂哭', '来', '发财了', '吃西瓜', '套牢', '害羞', '庆祝', '我来了', '敲打', '晕了', '胜利', '臭美', '被打了', '贪吃', '迎接', '酷', '微笑', '亲吻', '调皮', '惊恐', '耍酷', '发火', '害羞', '汗水', '大哭', '', '加油', '困', '你NB', '晕倒', '开心', '偷笑', '大哭', '滴汗', '叹气', '超赞', '??', '飞吻', '天使', '撒花', '生气', '被砸', '吓傻', '随意吐'],\r\n            tab1:['Kiss', 'Love', 'Yeah', '啊！', '背扭', '顶', '抖胸', '88', '汗', '瞌睡', '鲁拉', '拍砖', '揉脸', '生日快乐', '摊手', '睡觉', '瘫坐', '无聊', '星星闪', '旋转', '也不行', '郁闷', '正Music', '抓墙', '撞墙至死', '歪头', '戳眼', '飘过', '互相拍砖', '砍死你', '扔桌子', '少林寺', '什么？', '转头', '我爱牛奶', '我踢', '摇晃', '晕厥', '在笼子里', '震荡'],\r\n            tab2:['大笑', '瀑布汗~', '惊讶', '臭美', '傻笑', '抛媚眼', '发怒', '我错了', 'money', '气愤', '挑逗', '吻', '怒', '胜利', '委屈', '受伤', '说啥呢？', '闭嘴', '不', '逗你玩儿', '飞吻', '眩晕', '魔法', '我来了', '睡了', '我打', '闭嘴', '打', '打晕了', '刷牙', '爆揍', '炸弹', '倒立', '刮胡子', '邪恶的笑', '不要不要', '爱恋中', '放大仔细看', '偷窥', '超高兴', '晕', '松口气', '我跑', '享受', '修养', '哭', '汗', '啊~', '热烈欢迎', '打酱油', '俯卧撑', '?'],\r\n            tab3:['HI', 'KISS', '不说', '不要', '扯花', '大心', '顶', '大惊', '飞吻', '鬼脸', '害羞', '口水', '狂哭', '来', '泪眼', '流泪', '生气', '吐舌', '喜欢', '旋转', '再见', '抓狂', '汗', '鄙视', '拜', '吐血', '嘘', '打人', '蹦跳', '变脸', '扯肉', '吃To', '吃花', '吹泡泡糖', '大变身', '飞天舞', '回眸', '可怜', '猛抽', '泡泡', '苹果', '亲', '', '骚舞', '烧香', '睡', '套娃娃', '捅捅', '舞倒', '西红柿', '爱慕', '摇', '摇摆', '杂耍', '招财', '被殴', '被球闷', '大惊', '理想', '欧打', '呕吐', '碎', '吐痰'],\r\n            tab4:['发财了', '吃西瓜', '套牢', '害羞', '庆祝', '我来了', '敲打', '晕了', '胜利', '臭美', '被打了', '贪吃', '迎接', '酷', '顶', '幸运', '爱心', '躲', '送花', '选择'],\r\n            tab5:['微笑', '亲吻', '调皮', '惊讶', '耍酷', '发火', '害羞', '汗水', '大哭', '得意', '鄙视', '困', '夸奖', '晕倒', '疑问', '媒婆', '狂吐', '青蛙', '发愁', '亲吻', '', '爱心', '心碎', '玫瑰', '礼物', '哭', '奸笑', '可爱', '得意', '呲牙', '暴汗', '楚楚可怜', '困', '哭', '生气', '惊讶', '口水', '彩虹', '夜空', '太阳', '钱钱', '灯泡', '咖啡', '蛋糕', '音乐', '爱', '胜利', '赞', '鄙视', 'OK'],\r\n            tab6:['男兜', '女兜', '开心', '乖乖', '偷笑', '大笑', '抽泣', '大哭', '无奈', '滴汗', '叹气', '狂晕', '委屈', '超赞', '??', '疑问', '飞吻', '天使', '撒花', '生气', '被砸', '口水', '泪奔', '吓傻', '吐舌头', '点头', '随意吐', '旋转', '困困', '鄙视', '狂顶', '篮球', '再见', '欢迎光临', '恭喜发财', '稍等', '我在线', '恕不议价', '库房有货', '货在路上']\r\n        }\r\n    };\r\n</script>\r\n</body>\r\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/emotion/emotion.js",
    "content": "window.onload = function () {\r\n    editor.setOpt({\r\n        emotionLocalization:false\r\n    });\r\n\r\n    emotion.SmileyPath = editor.options.emotionLocalization === true ? 'images/' : \"http://img.baidu.com/hi/\";\r\n    emotion.SmileyBox = createTabList( emotion.tabNum );\r\n    emotion.tabExist = createArr( emotion.tabNum );\r\n\r\n    initImgName();\r\n    initEvtHandler( \"tabHeads\" );\r\n};\r\n\r\nfunction initImgName() {\r\n    for ( var pro in emotion.SmilmgName ) {\r\n        var tempName = emotion.SmilmgName[pro],\r\n                tempBox = emotion.SmileyBox[pro],\r\n                tempStr = \"\";\r\n\r\n        if ( tempBox.length ) return;\r\n        for ( var i = 1; i <= tempName[1]; i++ ) {\r\n            tempStr = tempName[0];\r\n            if ( i < 10 ) tempStr = tempStr + '0';\r\n            tempStr = tempStr + i + '.gif';\r\n            tempBox.push( tempStr );\r\n        }\r\n    }\r\n}\r\n\r\nfunction initEvtHandler( conId ) {\r\n    var tabHeads = $G( conId );\r\n    for ( var i = 0, j = 0; i < tabHeads.childNodes.length; i++ ) {\r\n        var tabObj = tabHeads.childNodes[i];\r\n        if ( tabObj.nodeType == 1 ) {\r\n            domUtils.on( tabObj, \"click\", (function ( index ) {\r\n                return function () {\r\n                    switchTab( index );\r\n                };\r\n            })( j ) );\r\n            j++;\r\n        }\r\n    }\r\n    switchTab( 0 );\r\n    $G( \"tabIconReview\" ).style.display = 'none';\r\n}\r\n\r\nfunction InsertSmiley( url, evt ) {\r\n    var obj = {\r\n        src:editor.options.emotionLocalization ? editor.options.UEDITOR_HOME_URL + \"dialogs/emotion/\" + url : url\r\n    };\r\n    obj._src = obj.src;\r\n    editor.execCommand( 'insertimage', obj );\r\n    if ( !evt.ctrlKey ) {\r\n        dialog.popup.hide();\r\n    }\r\n}\r\n\r\nfunction switchTab( index ) {\r\n\r\n    autoHeight( index );\r\n    if ( emotion.tabExist[index] == 0 ) {\r\n        emotion.tabExist[index] = 1;\r\n        createTab( 'tab' + index );\r\n    }\r\n    //获取呈现元素句柄数组\r\n    var tabHeads = $G( \"tabHeads\" ).getElementsByTagName( \"span\" ),\r\n            tabBodys = $G( \"tabBodys\" ).getElementsByTagName( \"div\" ),\r\n            i = 0, L = tabHeads.length;\r\n    //隐藏所有呈现元素\r\n    for ( ; i < L; i++ ) {\r\n        tabHeads[i].className = \"\";\r\n        tabBodys[i].style.display = \"none\";\r\n    }\r\n    //显示对应呈现元素\r\n    tabHeads[index].className = \"focus\";\r\n    tabBodys[index].style.display = \"block\";\r\n}\r\n\r\nfunction autoHeight( index ) {\r\n    var iframe = dialog.getDom( \"iframe\" ),\r\n            parent = iframe.parentNode.parentNode;\r\n    switch ( index ) {\r\n        case 0:\r\n            iframe.style.height = \"380px\";\r\n            parent.style.height = \"392px\";\r\n            break;\r\n        case 1:\r\n            iframe.style.height = \"220px\";\r\n            parent.style.height = \"232px\";\r\n            break;\r\n        case 2:\r\n            iframe.style.height = \"260px\";\r\n            parent.style.height = \"272px\";\r\n            break;\r\n        case 3:\r\n            iframe.style.height = \"300px\";\r\n            parent.style.height = \"312px\";\r\n            break;\r\n        case 4:\r\n            iframe.style.height = \"140px\";\r\n            parent.style.height = \"152px\";\r\n            break;\r\n        case 5:\r\n            iframe.style.height = \"260px\";\r\n            parent.style.height = \"272px\";\r\n            break;\r\n        case 6:\r\n            iframe.style.height = \"230px\";\r\n            parent.style.height = \"242px\";\r\n            break;\r\n        default:\r\n\r\n    }\r\n}\r\n\r\n\r\nfunction createTab( tabName ) {\r\n    var faceVersion = \"?v=1.1\", //版本号\r\n            tab = $G( tabName ), //获取将要生成的Div句柄\r\n            imagePath = emotion.SmileyPath + emotion.imageFolders[tabName], //获取显示表情和预览表情的路径\r\n            positionLine = 11 / 2, //中间数\r\n            iWidth = iHeight = 35, //图片长宽\r\n            iColWidth = 3, //表格剩余空间的显示比例\r\n            tableCss = emotion.imageCss[tabName],\r\n            cssOffset = emotion.imageCssOffset[tabName],\r\n            textHTML = ['<table class=\"smileytable\">'],\r\n            i = 0, imgNum = emotion.SmileyBox[tabName].length, imgColNum = 11, faceImage,\r\n            sUrl, realUrl, posflag, offset, infor;\r\n\r\n    for ( ; i < imgNum; ) {\r\n        textHTML.push( '<tr>' );\r\n        for ( var j = 0; j < imgColNum; j++, i++ ) {\r\n            faceImage = emotion.SmileyBox[tabName][i];\r\n            if ( faceImage ) {\r\n                sUrl = imagePath + faceImage + faceVersion;\r\n                realUrl = imagePath + faceImage;\r\n                posflag = j < positionLine ? 0 : 1;\r\n                offset = cssOffset * i * (-1) - 1;\r\n                infor = emotion.SmileyInfor[tabName][i];\r\n\r\n                textHTML.push( '<td  class=\"' + tableCss + '\"   border=\"1\" width=\"' + iColWidth + '%\" style=\"border-collapse:collapse;\" align=\"center\"  bgcolor=\"transparent\" onclick=\"InsertSmiley(\\'' + realUrl.replace( /'/g, \"\\\\'\" ) + '\\',event)\" onmouseover=\"over(this,\\'' + sUrl + '\\',\\'' + posflag + '\\')\" onmouseout=\"out(this)\">' );\r\n                textHTML.push( '<span>' );\r\n                textHTML.push( '<img  style=\"background-position:left ' + offset + 'px;\" title=\"' + infor + '\" src=\"' + emotion.SmileyPath + (editor.options.emotionLocalization ? '0.gif\" width=\"' : 'default/0.gif\" width=\"') + iWidth + '\" height=\"' + iHeight + '\"></img>' );\r\n                textHTML.push( '</span>' );\r\n            } else {\r\n                textHTML.push( '<td width=\"' + iColWidth + '%\"   bgcolor=\"#FFFFFF\">' );\r\n            }\r\n            textHTML.push( '</td>' );\r\n        }\r\n        textHTML.push( '</tr>' );\r\n    }\r\n    textHTML.push( '</table>' );\r\n    textHTML = textHTML.join( \"\" );\r\n    tab.innerHTML = textHTML;\r\n}\r\n\r\nfunction over( td, srcPath, posFlag ) {\r\n    td.style.backgroundColor = \"#ACCD3C\";\r\n    $G( 'faceReview' ).style.backgroundImage = \"url(\" + srcPath + \")\";\r\n    if ( posFlag == 1 ) $G( \"tabIconReview\" ).className = \"show\";\r\n    $G( \"tabIconReview\" ).style.display = 'block';\r\n}\r\n\r\nfunction out( td ) {\r\n    td.style.backgroundColor = \"transparent\";\r\n    var tabIconRevew = $G( \"tabIconReview\" );\r\n    tabIconRevew.className = \"\";\r\n    tabIconRevew.style.display = 'none';\r\n}\r\n\r\nfunction createTabList( tabNum ) {\r\n    var obj = {};\r\n    for ( var i = 0; i < tabNum; i++ ) {\r\n        obj[\"tab\" + i] = [];\r\n    }\r\n    return obj;\r\n}\r\n\r\nfunction createArr( tabNum ) {\r\n    var arr = [];\r\n    for ( var i = 0; i < tabNum; i++ ) {\r\n        arr[i] = 0;\r\n    }\r\n    return arr;\r\n}\r\n\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/gmap/gmap.html",
    "content": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\r\n        \"http://www.w3.org/TR/html4/loose.dtd\">\r\n<html>\r\n<head>\r\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\r\n    <title></title>\r\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n    <style type=\"text/css\">\r\n        .content{width:530px; height: 350px;margin: 10px auto;}\r\n        .content table{width: 100%}\r\n        .content table td{vertical-align: middle;}\r\n        #address{width:220px;height:21px;background: #FFF;border:1px solid #d7d7d7; line-height: 21px;}\r\n    </style>\r\n    <script type=\"text/javascript\" src=\"http://maps.googleapis.com/maps/api/js?sensor=false\"></script>\r\n</head>\r\n<body>\r\n<div class=\"content\">\r\n    <table>\r\n        <tr>\r\n            <td><label for=\"address\"><var id=\"lang_input_address\"></var></label></td>\r\n            <td><input id=\"address\" type=\"text\" /></td>\r\n            <td><a id=\"doSearch\" href=\"javascript:void(0)\" class=\"button\"><var id=\"lang_input_search\"></var></a></td>\r\n        </tr>\r\n    </table>\r\n    <div id=\"container\" style=\"width: 100%; height: 340px;margin: 5px auto; border: 1px solid gray;\"></div>\r\n</div>\r\n<script type=\"text/javascript\">\r\n    domUtils.on(window,\"load\",function(){\r\n        var map = new google.maps.Map(document.getElementById('container'), {\r\n                zoom: 3,\r\n                streetViewControl: false,\r\n                scaleControl: true,\r\n                mapTypeId: google.maps.MapTypeId.ROADMAP\r\n            });\r\n            var imgcss;\r\n            var marker = new google.maps.Marker({\r\n                map: map,\r\n                draggable: true\r\n            });\r\n            function doSearch(){\r\n                var address = document.getElementById('address').value;\r\n                var geocoder = new google.maps.Geocoder();\r\n                geocoder.geocode( { 'address': address}, function (results, status) {\r\n                    if (status == google.maps.GeocoderStatus.OK) {\r\n                        var bounds = results[0].geometry.viewport;\r\n                        map.fitBounds(bounds);\r\n                        marker.setPosition(results[0].geometry.location);\r\n                        marker.setTitle(address);\r\n                    } else alert(lang.searchError);\r\n                });\r\n            }\r\n            $G('address').onkeydown = function (evt){\r\n                evt = evt || event;\r\n                if (evt.keyCode == 13) {\r\n                    doSearch();\r\n                }\r\n            };\r\n            $G(\"doSearch\").onclick = doSearch;\r\n            dialog.onok = function (){\r\n                var center = map.getCenter();\r\n                var point = marker.getPosition();\r\n                var url = \"http://maps.googleapis.com/maps/api/staticmap?center=\" + center.lat() + ',' + center.lng() + \"&zoom=\" + map.zoom + \"&size=520x340&maptype=\" + map.getMapTypeId() + \"&markers=\" + point.lat() + ',' + point.lng() + \"&sensor=false\";\r\n                editor.execCommand('inserthtml', '<img width=\"520\" height=\"340\" src=\"' + url + '\"' + (imgcss ? ' style=\"' + imgcss + '\"' :'') + '/>');\r\n            };\r\n\r\n            function getPars(str,par){\r\n                var reg = new RegExp(par+\"=((\\\\d+|[.,])*)\",\"g\");\r\n                return reg.exec(str)[1];\r\n            }\r\n            var img = editor.selection.getRange().getClosedNode();\r\n            if(img && img.src.indexOf(\"http://maps.googleapis.com/maps/api/staticmap\")!=-1){\r\n                var url = img.getAttribute(\"src\");\r\n                var centers = getPars(url,\"center\").split(\",\");\r\n                point = new google.maps.LatLng(Number(centers[0]),Number(centers[1]));\r\n                map.setCenter(point);\r\n                map.setZoom(Number(getPars(url,\"zoom\")));\r\n                centers = getPars(url,\"markers\").split(\",\");\r\n                marker.setPosition(new google.maps.LatLng(Number(centers[0]),Number(centers[1])));\r\n                imgcss = img.style.cssText;\r\n            }else{\r\n                setTimeout(function(){\r\n                    doSearch();\r\n                },30)\r\n            }\r\n    });\r\n\r\n</script>\r\n</body>\r\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/help/help.css",
    "content": ".wrapper{width: 370px;margin: 10px auto;zoom: 1;}\r\n.tabbody{height: 360px;}\r\n.tabbody .panel{width:100%;height: 360px;position: absolute;background: #fff;}\r\n.tabbody .panel h1{font-size:26px;margin: 5px 0 0 5px;}\r\n.tabbody .panel p{font-size:12px;margin: 5px 0 0 5px;}\r\n.tabbody table{width:90%;line-height: 20px;margin: 5px 0 0 5px;;}\r\n.tabbody table thead{font-weight: bold;line-height: 25px;}"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/help/help.html",
    "content": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\r\n        \"http://www.w3.org/TR/html4/loose.dtd\">\r\n<html>\r\n<head>\r\n    <title>帮助</title>\r\n    <meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\"/>\r\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"help.css\">\r\n</head>\r\n<body>\r\n<div class=\"wrapper\" id=\"helptab\">\r\n    <div id=\"tabHeads\" class=\"tabhead\">\r\n        <span class=\"focus\" tabsrc=\"about\"><var id=\"lang_input_about\"></var></span>\r\n        <span tabsrc=\"shortcuts\"><var id=\"lang_input_shortcuts\"></var></span>\r\n    </div>\r\n    <div id=\"tabBodys\" class=\"tabbody\">\r\n        <div id=\"about\" class=\"panel\">\r\n            <h1>UEditor</h1>\r\n            <p id=\"version\"></p>\r\n            <p><var id=\"lang_input_introduction\"></var></p>\r\n        </div>\r\n        <div id=\"shortcuts\" class=\"panel\">\r\n            <table>\r\n                <thead>\r\n                <tr>\r\n                    <td><var id=\"lang_Txt_shortcuts\"></var></td>\r\n                    <td><var id=\"lang_Txt_func\"></var></td>\r\n                </tr>\r\n                </thead>\r\n                <tbody>\r\n                <tr>\r\n                    <td>ctrl+b</td>\r\n                    <td><var id=\"lang_Txt_bold\"></var></td>\r\n                </tr>\r\n                <tr>\r\n                    <td>ctrl+c</td>\r\n                    <td><var id=\"lang_Txt_copy\"></var></td>\r\n                </tr>\r\n                <tr>\r\n                    <td>ctrl+x</td>\r\n                    <td><var id=\"lang_Txt_cut\"></var></td>\r\n                </tr>\r\n                <tr>\r\n                    <td>ctrl+v</td>\r\n                    <td><var id=\"lang_Txt_Paste\"></var></td>\r\n                </tr>\r\n                <tr>\r\n                    <td>ctrl+y</td>\r\n                    <td><var id=\"lang_Txt_undo\"></var></td>\r\n                </tr>\r\n                <tr>\r\n                    <td>ctrl+z</td>\r\n                    <td><var id=\"lang_Txt_redo\"></var></td>\r\n                </tr>\r\n                <tr>\r\n                    <td>ctrl+i</td>\r\n                    <td><var id=\"lang_Txt_italic\"></var></td>\r\n                </tr>\r\n                <tr>\r\n                    <td>ctrl+u</td>\r\n                    <td><var id=\"lang_Txt_underline\"></var></td>\r\n                </tr>\r\n                <tr>\r\n                    <td>ctrl+a</td>\r\n                    <td><var id=\"lang_Txt_selectAll\"></var></td>\r\n                </tr>\r\n                <tr>\r\n                    <td>shift+enter</td>\r\n                    <td><var id=\"lang_Txt_visualEnter\"></var></td>\r\n                </tr>\r\n                <tr>\r\n                    <td>alt+z</td>\r\n                    <td><var id=\"lang_Txt_fullscreen\"></var></td>\r\n                </tr>\r\n                </tbody>\r\n            </table>\r\n        </div>\r\n    </div>\r\n</div>\r\n<script type=\"text/javascript\" src=\"help.js\"></script>\r\n</body>\r\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/help/help.js",
    "content": "/**\r\n * Created with JetBrains PhpStorm.\r\n * User: xuheng\r\n * Date: 12-9-26\r\n * Time: 下午1:06\r\n * To change this template use File | Settings | File Templates.\r\n */\r\n/**\r\n * tab点击处理事件\r\n * @param tabHeads\r\n * @param tabBodys\r\n * @param obj\r\n */\r\nfunction clickHandler( tabHeads,tabBodys,obj ) {\r\n    //head样式更改\r\n    for ( var k = 0, len = tabHeads.length; k < len; k++ ) {\r\n        tabHeads[k].className = \"\";\r\n    }\r\n    obj.className = \"focus\";\r\n    //body显隐\r\n    var tabSrc = obj.getAttribute( \"tabSrc\" );\r\n    for ( var j = 0, length = tabBodys.length; j < length; j++ ) {\r\n        var body = tabBodys[j],\r\n            id = body.getAttribute( \"id\" );\r\n        body.onclick = function(){\r\n            this.style.zoom = 1;\r\n        };\r\n        if ( id != tabSrc ) {\r\n            body.style.zIndex = 1;\r\n        } else {\r\n            body.style.zIndex = 200;\r\n        }\r\n    }\r\n\r\n}\r\n\r\n/**\r\n * TAB切换\r\n * @param tabParentId  tab的父节点ID或者对象本身\r\n */\r\nfunction switchTab( tabParentId ) {\r\n    var tabElements = $G( tabParentId ).children,\r\n        tabHeads = tabElements[0].children,\r\n        tabBodys = tabElements[1].children;\r\n\r\n    for ( var i = 0, length = tabHeads.length; i < length; i++ ) {\r\n        var head = tabHeads[i];\r\n        if ( head.className === \"focus\" )clickHandler(tabHeads,tabBodys, head );\r\n        head.onclick = function () {\r\n            clickHandler(tabHeads,tabBodys,this);\r\n        }\r\n    }\r\n}\r\nswitchTab(\"helptab\");\r\n\r\ndocument.getElementById('version').innerHTML = parent.UE.version;"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/image/image.css",
    "content": "@charset \"utf-8\";\r\n/* dialog样式 */\r\n.wrapper {\r\n    zoom: 1;\r\n    width: 630px;\r\n    *width: 626px;\r\n    height: 380px;\r\n    margin: 0 auto;\r\n    padding: 10px;\r\n    position: relative;\r\n    font-family: sans-serif;\r\n}\r\n\r\n/*tab样式框大小*/\r\n.tabhead {\r\n    float:left;\r\n}\r\n.tabbody {\r\n    width: 100%;\r\n    height: 346px;\r\n    position: relative;\r\n    clear: both;\r\n}\r\n\r\n.tabbody .panel {\r\n    position: absolute;\r\n    width: 0;\r\n    height: 0;\r\n    background: #fff;\r\n    overflow: hidden;\r\n    display: none;\r\n}\r\n\r\n.tabbody .panel.focus {\r\n    width: 100%;\r\n    height: 346px;\r\n    display: block;\r\n}\r\n\r\n/* 图片对齐方式 */\r\n.alignBar{\r\n    float:right;\r\n    margin-top: 5px;\r\n    position: relative;\r\n}\r\n\r\n.alignBar .algnLabel{\r\n    float:left;\r\n    height: 20px;\r\n    line-height: 20px;\r\n}\r\n\r\n.alignBar #alignIcon{\r\n    zoom:1;\r\n    _display: inline;\r\n    display: inline-block;\r\n    position: relative;\r\n}\r\n.alignBar #alignIcon span{\r\n    float: left;\r\n    cursor: pointer;\r\n    display: block;\r\n    width: 19px;\r\n    height: 17px;\r\n    margin-right: 3px;\r\n    margin-left: 3px;\r\n    background-image: url(./images/alignicon.jpg);\r\n}\r\n.alignBar #alignIcon .none-align{\r\n    background-position: 0 -18px;\r\n}\r\n.alignBar #alignIcon .left-align{\r\n    background-position: -20px -18px;\r\n}\r\n.alignBar #alignIcon .right-align{\r\n    background-position: -40px -18px;\r\n}\r\n.alignBar #alignIcon .center-align{\r\n    background-position: -60px -18px;\r\n}\r\n.alignBar #alignIcon .none-align.focus{\r\n    background-position: 0 0;\r\n}\r\n.alignBar #alignIcon .left-align.focus{\r\n    background-position: -20px 0;\r\n}\r\n.alignBar #alignIcon .right-align.focus{\r\n    background-position: -40px 0;\r\n}\r\n.alignBar #alignIcon .center-align.focus{\r\n    background-position: -60px 0;\r\n}\r\n\r\n\r\n\r\n\r\n/* 远程图片样式 */\r\n#remote {\r\n    z-index: 200;\r\n}\r\n\r\n#remote .top{\r\n    width: 100%;\r\n    margin-top: 25px;\r\n}\r\n#remote .left{\r\n    display: block;\r\n    float: left;\r\n    width: 300px;\r\n    height:10px;\r\n}\r\n#remote .right{\r\n    display: block;\r\n    float: right;\r\n    width: 300px;\r\n    height:10px;\r\n}\r\n#remote .row{\r\n    margin-left: 20px;\r\n    clear: both;\r\n    height: 40px;\r\n}\r\n\r\n#remote .row label{\r\n    text-align: center;\r\n    width: 50px;\r\n    zoom:1;\r\n    _display: inline;\r\n    display:inline-block;\r\n    vertical-align: middle;\r\n}\r\n#remote .row label.algnLabel{\r\n    float: left;\r\n\r\n}\r\n\r\n#remote input.text{\r\n    width: 150px;\r\n    padding: 3px 6px;\r\n    font-size: 14px;\r\n    line-height: 1.42857143;\r\n    color: #555;\r\n    background-color: #fff;\r\n    background-image: none;\r\n    border: 1px solid #ccc;\r\n    border-radius: 4px;\r\n    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\r\n    box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\r\n    -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\r\n    transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\r\n}\r\n#remote input.text:focus {\r\n    border-color: #66afe9;\r\n    outline: 0;\r\n    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6);\r\n    box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6);\r\n}\r\n#remote #url{\r\n    width: 500px;\r\n    margin-bottom: 2px;\r\n}\r\n#remote #width,\r\n#remote #height{\r\n    width: 20px;\r\n    margin-left: 2px;\r\n    margin-right: 2px;\r\n}\r\n#remote #border,\r\n#remote #vhSpace,\r\n#remote #title{\r\n    width: 180px;\r\n    margin-right: 5px;\r\n}\r\n#remote #lock{\r\n}\r\n#remote #lockicon{\r\n    zoom: 1;\r\n    _display:inline;\r\n    display: inline-block;\r\n    width: 20px;\r\n    height: 20px;\r\n    background: url(\"../../themes/default/images/lock.gif\") -13px -13px no-repeat;\r\n    vertical-align: middle;\r\n}\r\n#remote #preview{\r\n    clear: both;\r\n    width: 260px;\r\n    height: 240px;\r\n    z-index: 9999;\r\n    margin-top: 10px;\r\n    background-color: #eee;\r\n    overflow: hidden;\r\n}\r\n\r\n/* 上传图片 */\r\n.tabbody #upload.panel {\r\n    width: 0;\r\n    height: 0;\r\n    overflow: hidden;\r\n    position: absolute !important;\r\n    clip: rect(1px, 1px, 1px, 1px);\r\n    background: #fff;\r\n    display: block;\r\n}\r\n\r\n.tabbody #upload.panel.focus {\r\n    width: 100%;\r\n    height: 346px;\r\n    display: block;\r\n    clip: auto;\r\n}\r\n\r\n#upload .queueList {\r\n    margin: 0;\r\n    width: 100%;\r\n    height: 100%;\r\n    position: absolute;\r\n    overflow: hidden;\r\n}\r\n\r\n#upload p {\r\n    margin: 0;\r\n}\r\n\r\n.element-invisible {\r\n    width: 0 !important;\r\n    height: 0 !important;\r\n    border: 0;\r\n    padding: 0;\r\n    margin: 0;\r\n    overflow: hidden;\r\n    position: absolute !important;\r\n    clip: rect(1px, 1px, 1px, 1px);\r\n}\r\n\r\n#upload .placeholder {\r\n    margin: 10px;\r\n    border: 2px dashed #e6e6e6;\r\n    *border: 0px dashed #e6e6e6;\r\n    height: 172px;\r\n    padding-top: 150px;\r\n    text-align: center;\r\n    background: url(./images/image.png) center 70px no-repeat;\r\n    color: #cccccc;\r\n    font-size: 18px;\r\n    position: relative;\r\n    top:0;\r\n    *top: 10px;\r\n}\r\n\r\n#upload .placeholder .webuploader-pick {\r\n    font-size: 18px;\r\n    background: #00b7ee;\r\n    border-radius: 3px;\r\n    line-height: 44px;\r\n    padding: 0 30px;\r\n    *width: 120px;\r\n    color: #fff;\r\n    display: inline-block;\r\n    margin: 0 auto 20px auto;\r\n    cursor: pointer;\r\n    box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);\r\n}\r\n\r\n#upload .placeholder .webuploader-pick-hover {\r\n    background: #00a2d4;\r\n}\r\n\r\n\r\n#filePickerContainer {\r\n    text-align: center;\r\n}\r\n\r\n#upload .placeholder .flashTip {\r\n    color: #666666;\r\n    font-size: 12px;\r\n    position: absolute;\r\n    width: 100%;\r\n    text-align: center;\r\n    bottom: 20px;\r\n}\r\n\r\n#upload .placeholder .flashTip a {\r\n    color: #0785d1;\r\n    text-decoration: none;\r\n}\r\n\r\n#upload .placeholder .flashTip a:hover {\r\n    text-decoration: underline;\r\n}\r\n\r\n#upload .placeholder.webuploader-dnd-over {\r\n    border-color: #999999;\r\n}\r\n\r\n#upload .filelist {\r\n    list-style: none;\r\n    margin: 0;\r\n    padding: 0;\r\n    overflow-x: hidden;\r\n    overflow-y: auto;\r\n    position: relative;\r\n    height: 300px;\r\n}\r\n\r\n#upload .filelist:after {\r\n    content: '';\r\n    display: block;\r\n    width: 0;\r\n    height: 0;\r\n    overflow: hidden;\r\n    clear: both;\r\n    position: relative;\r\n}\r\n\r\n#upload .filelist li {\r\n    width: 113px;\r\n    height: 113px;\r\n    background: url(./images/bg.png);\r\n    text-align: center;\r\n    margin: 9px 0 0 9px;\r\n    *margin: 6px 0 0 6px;\r\n    position: relative;\r\n    display: block;\r\n    float: left;\r\n    overflow: hidden;\r\n    font-size: 12px;\r\n}\r\n\r\n#upload .filelist li p.log {\r\n    position: relative;\r\n    top: -45px;\r\n}\r\n\r\n#upload .filelist li p.title {\r\n    position: absolute;\r\n    top: 0;\r\n    left: 0;\r\n    width: 100%;\r\n    overflow: hidden;\r\n    white-space: nowrap;\r\n    text-overflow: ellipsis;\r\n    top: 5px;\r\n    text-indent: 5px;\r\n    text-align: left;\r\n}\r\n\r\n#upload .filelist li p.progress {\r\n    position: absolute;\r\n    width: 100%;\r\n    bottom: 0;\r\n    left: 0;\r\n    height: 8px;\r\n    overflow: hidden;\r\n    z-index: 50;\r\n    margin: 0;\r\n    border-radius: 0;\r\n    background: none;\r\n    -webkit-box-shadow: 0 0 0;\r\n}\r\n\r\n#upload .filelist li p.progress span {\r\n    display: none;\r\n    overflow: hidden;\r\n    width: 0;\r\n    height: 100%;\r\n    background: #1483d8 url(./images/progress.png) repeat-x;\r\n\r\n    -webit-transition: width 200ms linear;\r\n    -moz-transition: width 200ms linear;\r\n    -o-transition: width 200ms linear;\r\n    -ms-transition: width 200ms linear;\r\n    transition: width 200ms linear;\r\n\r\n    -webkit-animation: progressmove 2s linear infinite;\r\n    -moz-animation: progressmove 2s linear infinite;\r\n    -o-animation: progressmove 2s linear infinite;\r\n    -ms-animation: progressmove 2s linear infinite;\r\n    animation: progressmove 2s linear infinite;\r\n\r\n    -webkit-transform: translateZ(0);\r\n}\r\n\r\n@-webkit-keyframes progressmove {\r\n    0% {\r\n        background-position: 0 0;\r\n    }\r\n    100% {\r\n        background-position: 17px 0;\r\n    }\r\n}\r\n\r\n@-moz-keyframes progressmove {\r\n    0% {\r\n        background-position: 0 0;\r\n    }\r\n    100% {\r\n        background-position: 17px 0;\r\n    }\r\n}\r\n\r\n@keyframes progressmove {\r\n    0% {\r\n        background-position: 0 0;\r\n    }\r\n    100% {\r\n        background-position: 17px 0;\r\n    }\r\n}\r\n\r\n#upload .filelist li p.imgWrap {\r\n    position: relative;\r\n    z-index: 2;\r\n    line-height: 113px;\r\n    vertical-align: middle;\r\n    overflow: hidden;\r\n    width: 113px;\r\n    height: 113px;\r\n\r\n    -webkit-transform-origin: 50% 50%;\r\n    -moz-transform-origin: 50% 50%;\r\n    -o-transform-origin: 50% 50%;\r\n    -ms-transform-origin: 50% 50%;\r\n    transform-origin: 50% 50%;\r\n\r\n    -webit-transition: 200ms ease-out;\r\n    -moz-transition: 200ms ease-out;\r\n    -o-transition: 200ms ease-out;\r\n    -ms-transition: 200ms ease-out;\r\n    transition: 200ms ease-out;\r\n}\r\n\r\n#upload .filelist li img {\r\n    width: 100%;\r\n}\r\n\r\n#upload .filelist li p.error {\r\n    background: #f43838;\r\n    color: #fff;\r\n    position: absolute;\r\n    bottom: 0;\r\n    left: 0;\r\n    height: 28px;\r\n    line-height: 28px;\r\n    width: 100%;\r\n    z-index: 100;\r\n    display:none;\r\n}\r\n\r\n#upload .filelist li .success {\r\n    display: block;\r\n    position: absolute;\r\n    left: 0;\r\n    bottom: 0;\r\n    height: 40px;\r\n    width: 100%;\r\n    z-index: 200;\r\n    background: url(./images/success.png) no-repeat right bottom;\r\n    background: url(./images/success.gif) no-repeat right bottom \\9;\r\n}\r\n\r\n#upload .filelist li.filePickerBlock {\r\n    width: 113px;\r\n    height: 113px;\r\n    background: url(./images/image.png) no-repeat center 12px;\r\n    border: 1px solid #eeeeee;\r\n    border-radius: 0;\r\n}\r\n#upload .filelist li.filePickerBlock div.webuploader-pick  {\r\n    width: 100%;\r\n    height: 100%;\r\n    margin: 0;\r\n    padding: 0;\r\n    opacity: 0;\r\n    background: none;\r\n    font-size: 0;\r\n}\r\n\r\n#upload .filelist div.file-panel {\r\n    position: absolute;\r\n    height: 0;\r\n    filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr='#80000000', endColorstr='#80000000') \\0;\r\n    background: rgba(0, 0, 0, 0.5);\r\n    width: 100%;\r\n    top: 0;\r\n    left: 0;\r\n    overflow: hidden;\r\n    z-index: 300;\r\n}\r\n\r\n#upload .filelist div.file-panel span {\r\n    width: 24px;\r\n    height: 24px;\r\n    display: inline;\r\n    float: right;\r\n    text-indent: -9999px;\r\n    overflow: hidden;\r\n    background: url(./images/icons.png) no-repeat;\r\n    background: url(./images/icons.gif) no-repeat \\9;\r\n    margin: 5px 1px 1px;\r\n    cursor: pointer;\r\n    -webkit-tap-highlight-color: rgba(0,0,0,0);\r\n    -webkit-user-select: none;\r\n    -moz-user-select: none;\r\n    -ms-user-select: none;\r\n    user-select: none;\r\n}\r\n\r\n#upload .filelist div.file-panel span.rotateLeft {\r\n    display:none;\r\n    background-position: 0 -24px;\r\n}\r\n\r\n#upload .filelist div.file-panel span.rotateLeft:hover {\r\n    background-position: 0 0;\r\n}\r\n\r\n#upload .filelist div.file-panel span.rotateRight {\r\n    display:none;\r\n    background-position: -24px -24px;\r\n}\r\n\r\n#upload .filelist div.file-panel span.rotateRight:hover {\r\n    background-position: -24px 0;\r\n}\r\n\r\n#upload .filelist div.file-panel span.cancel {\r\n    background-position: -48px -24px;\r\n}\r\n\r\n#upload .filelist div.file-panel span.cancel:hover {\r\n    background-position: -48px 0;\r\n}\r\n\r\n#upload .statusBar {\r\n    height: 45px;\r\n    border-bottom: 1px solid #dadada;\r\n    margin: 0 10px;\r\n    padding: 0;\r\n    line-height: 45px;\r\n    vertical-align: middle;\r\n    position: relative;\r\n}\r\n\r\n#upload .statusBar .progress {\r\n    border: 1px solid #1483d8;\r\n    width: 198px;\r\n    background: #fff;\r\n    height: 18px;\r\n    position: absolute;\r\n    top: 12px;\r\n    display: none;\r\n    text-align: center;\r\n    line-height: 18px;\r\n    color: #6dbfff;\r\n    margin: 0 10px 0 0;\r\n}\r\n#upload .statusBar .progress span.percentage {\r\n    width: 0;\r\n    height: 100%;\r\n    left: 0;\r\n    top: 0;\r\n    background: #1483d8;\r\n    position: absolute;\r\n}\r\n#upload .statusBar .progress span.text {\r\n    position: relative;\r\n    z-index: 10;\r\n}\r\n\r\n#upload .statusBar .info {\r\n    display: inline-block;\r\n    font-size: 14px;\r\n    color: #666666;\r\n}\r\n\r\n#upload .statusBar .btns {\r\n    position: absolute;\r\n    top: 7px;\r\n    right: 0;\r\n    line-height: 30px;\r\n}\r\n\r\n#filePickerBtn {\r\n    display: inline-block;\r\n    float: left;\r\n}\r\n#upload .statusBar .btns .webuploader-pick,\r\n#upload .statusBar .btns .uploadBtn,\r\n#upload .statusBar .btns .uploadBtn.state-uploading,\r\n#upload .statusBar .btns .uploadBtn.state-paused {\r\n    background: #ffffff;\r\n    border: 1px solid #cfcfcf;\r\n    color: #565656;\r\n    padding: 0 18px;\r\n    display: inline-block;\r\n    border-radius: 3px;\r\n    margin-left: 10px;\r\n    cursor: pointer;\r\n    font-size: 14px;\r\n    float: left;\r\n    -webkit-user-select: none;\r\n    -moz-user-select: none;\r\n    -ms-user-select: none;\r\n    user-select: none;\r\n}\r\n#upload .statusBar .btns .webuploader-pick-hover,\r\n#upload .statusBar .btns .uploadBtn:hover,\r\n#upload .statusBar .btns .uploadBtn.state-uploading:hover,\r\n#upload .statusBar .btns .uploadBtn.state-paused:hover {\r\n    background: #f0f0f0;\r\n}\r\n\r\n#upload .statusBar .btns .uploadBtn,\r\n#upload .statusBar .btns .uploadBtn.state-paused{\r\n    background: #00b7ee;\r\n    color: #fff;\r\n    border-color: transparent;\r\n}\r\n#upload .statusBar .btns .uploadBtn:hover,\r\n#upload .statusBar .btns .uploadBtn.state-paused:hover{\r\n    background: #00a2d4;\r\n}\r\n\r\n#upload .statusBar .btns .uploadBtn.disabled {\r\n    pointer-events: none;\r\n    filter:alpha(opacity=60);\r\n    -moz-opacity:0.6;\r\n    -khtml-opacity: 0.6;\r\n    opacity: 0.6;\r\n}\r\n\r\n\r\n\r\n/* 图片管理样式 */\r\n#online {\r\n    width: 100%;\r\n    height: 336px;\r\n    padding: 10px 0 0 0;\r\n}\r\n#online #imageList{\r\n    width: 100%;\r\n    height: 100%;\r\n    overflow-x: hidden;\r\n    overflow-y: auto;\r\n    position: relative;\r\n}\r\n#online ul {\r\n    display: block;\r\n    list-style: none;\r\n    margin: 0;\r\n    padding: 0;\r\n}\r\n#online li {\r\n    float: left;\r\n    display: block;\r\n    list-style: none;\r\n    padding: 0;\r\n    width: 113px;\r\n    height: 113px;\r\n    margin: 0 0 9px 9px;\r\n    *margin: 0 0 6px 6px;\r\n    background-color: #eee;\r\n    overflow: hidden;\r\n    cursor: pointer;\r\n    position: relative;\r\n}\r\n#online li.clearFloat {\r\n    float: none;\r\n    clear: both;\r\n    display: block;\r\n    width:0;\r\n    height:0;\r\n    margin: 0;\r\n    padding: 0;\r\n}\r\n#online li img {\r\n    cursor: pointer;\r\n}\r\n#online li .icon {\r\n    cursor: pointer;\r\n    width: 113px;\r\n    height: 113px;\r\n    position: absolute;\r\n    top: 0;\r\n    left: 0;\r\n    z-index: 2;\r\n    border: 0;\r\n    background-repeat: no-repeat;\r\n}\r\n#online li .icon:hover {\r\n    width: 107px;\r\n    height: 107px;\r\n    border: 3px solid #1094fa;\r\n}\r\n#online li.selected .icon {\r\n    background-image: url(images/success.png);\r\n    background-image: url(images/success.gif)\\9;\r\n    background-position: 75px 75px;\r\n}\r\n#online li.selected .icon:hover {\r\n    width: 107px;\r\n    height: 107px;\r\n    border: 3px solid #1094fa;\r\n    background-position: 72px 72px;\r\n}\r\n\r\n\r\n/* 图片搜索样式 */\r\n#search .searchBar {\r\n    width: 100%;\r\n    height: 30px;\r\n    margin: 10px 0 5px 0;\r\n    padding: 0;\r\n}\r\n\r\n#search input.text{\r\n    width: 150px;\r\n    padding: 3px 6px;\r\n    font-size: 14px;\r\n    line-height: 1.42857143;\r\n    color: #555;\r\n    background-color: #fff;\r\n    background-image: none;\r\n    border: 1px solid #ccc;\r\n    border-radius: 4px;\r\n    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\r\n    box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\r\n    -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\r\n    transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\r\n}\r\n#search input.text:focus {\r\n    border-color: #66afe9;\r\n    outline: 0;\r\n    -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6);\r\n    box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075), 0 0 8px rgba(102, 175, 233, .6);\r\n}\r\n#search input.searchTxt {\r\n    margin-left:5px;\r\n    padding-left: 5px;\r\n    background: #FFF;\r\n    width: 300px;\r\n    *width: 260px;\r\n    height: 21px;\r\n    line-height: 21px;\r\n    float: left;\r\n    dislay: block;\r\n}\r\n\r\n#search .searchType {\r\n    width: 65px;\r\n    height: 28px;\r\n    padding:0;\r\n    line-height: 28px;\r\n    border: 1px solid #d7d7d7;\r\n    border-radius: 0;\r\n    vertical-align: top;\r\n    margin-left: 5px;\r\n    float: left;\r\n    dislay: block;\r\n}\r\n\r\n#search #searchBtn,\r\n#search #searchReset {\r\n    display: inline-block;\r\n    margin-bottom: 0;\r\n    margin-right: 5px;\r\n    padding: 4px 10px;\r\n    font-weight: 400;\r\n    text-align: center;\r\n    vertical-align: middle;\r\n    cursor: pointer;\r\n    background-image: none;\r\n    border: 1px solid transparent;\r\n    white-space: nowrap;\r\n    font-size: 14px;\r\n    border-radius: 4px;\r\n    -webkit-user-select: none;\r\n    -moz-user-select: none;\r\n    -ms-user-select: none;\r\n    user-select: none;\r\n    vertical-align: top;\r\n    float: right;\r\n}\r\n\r\n#search #searchBtn {\r\n    color: white;\r\n    border-color: #285e8e;\r\n    background-color: #3b97d7;\r\n}\r\n#search #searchReset {\r\n    color: #333;\r\n    border-color: #ccc;\r\n    background-color: #fff;\r\n}\r\n#search #searchBtn:hover {\r\n    background-color: #3276b1;\r\n}\r\n#search #searchReset:hover {\r\n    background-color: #eee;\r\n}\r\n\r\n#search .msg {\r\n    margin-left: 5px;\r\n}\r\n\r\n#search .searchList{\r\n    width: 100%;\r\n    height: 300px;\r\n    overflow: hidden;\r\n    clear: both;\r\n}\r\n#search .searchList ul{\r\n    margin:0;\r\n    padding:0;\r\n    list-style:none;\r\n    clear: both;\r\n    width: 100%;\r\n    height: 100%;\r\n    overflow-x: hidden;\r\n    overflow-y: auto;\r\n    zoom: 1;\r\n    position: relative;\r\n}\r\n\r\n#search .searchList li {\r\n    list-style:none;\r\n    float: left;\r\n    display: block;\r\n    width: 115px;\r\n    margin: 5px 10px 5px 20px;\r\n    *margin: 5px 10px 5px 15px;\r\n    padding:0;\r\n    font-size: 12px;\r\n    box-shadow: 0 1px 3px rgba(0, 0, 0, .3);\r\n    -moz-box-shadow: 0 1px 3px rgba(0, 0, 0, .3);\r\n    -webkit-box-shadow: 0 1px 3px rgba(0, 0, 0, .3);\r\n    position: relative;\r\n    vertical-align: top;\r\n    text-align: center;\r\n    overflow: hidden;\r\n    cursor: pointer;\r\n    filter: alpha(Opacity=100);\r\n    -moz-opacity: 1;\r\n    opacity: 1;\r\n    border: 2px solid #eee;\r\n}\r\n\r\n#search .searchList li.selected {\r\n    filter: alpha(Opacity=40);\r\n    -moz-opacity: 0.4;\r\n    opacity: 0.4;\r\n    border: 2px solid #00a0e9;\r\n}\r\n\r\n#search .searchList li p {\r\n    background-color: #eee;\r\n    margin: 0;\r\n    padding: 0;\r\n    position: relative;\r\n    width:100%;\r\n    height:115px;\r\n    overflow: hidden;\r\n}\r\n\r\n#search .searchList li p img {\r\n    cursor: pointer;\r\n    border: 0;\r\n}\r\n\r\n#search .searchList li a {\r\n    color: #999;\r\n    border-top: 1px solid #F2F2F2;\r\n    background: #FAFAFA;\r\n    text-align: center;\r\n    display: block;\r\n    padding: 0 5px;\r\n    width: 105px;\r\n    height:32px;\r\n    line-height:32px;\r\n    white-space:nowrap;\r\n    text-overflow:ellipsis;\r\n    text-decoration: none;\r\n    overflow: hidden;\r\n    word-break: break-all;\r\n}\r\n\r\n#search .searchList a:hover {\r\n    text-decoration: underline;\r\n    color: #333;\r\n}\r\n#search .searchList .clearFloat{\r\n    clear: both;\r\n}"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/image/image.html",
    "content": "<!doctype html>\r\n<html>\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <title>ueditor图片对话框</title>\r\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n\r\n    <!-- jquery -->\r\n    <script type=\"text/javascript\" src=\"../../third-party/jquery-1.10.2.min.js\"></script>\r\n\r\n    <!-- webuploader -->\r\n    <script src=\"../../third-party/webuploader/webuploader.min.js\"></script>\r\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"../../third-party/webuploader/webuploader.css\">\r\n\r\n    <!-- image dialog -->\r\n    <link rel=\"stylesheet\" href=\"image.css\" type=\"text/css\" />\r\n</head>\r\n<body>\r\n\r\n    <div class=\"wrapper\">\r\n        <div id=\"tabhead\" class=\"tabhead\">\r\n            <span class=\"tab\" data-content-id=\"remote\"><var id=\"lang_tab_remote\"></var></span>\r\n            <span class=\"tab focus\" data-content-id=\"upload\"><var id=\"lang_tab_upload\"></var></span>\r\n            <span class=\"tab\" data-content-id=\"online\"><var id=\"lang_tab_online\"></var></span>\r\n            <span class=\"tab\" data-content-id=\"search\"><var id=\"lang_tab_search\"></var></span>\r\n        </div>\r\n        <div class=\"alignBar\">\r\n            <label class=\"algnLabel\"><var id=\"lang_input_align\"></var></label>\r\n                    <span id=\"alignIcon\">\r\n                        <span id=\"noneAlign\" class=\"none-align focus\" data-align=\"none\"></span>\r\n                        <span id=\"leftAlign\" class=\"left-align\" data-align=\"left\"></span>\r\n                        <span id=\"rightAlign\" class=\"right-align\" data-align=\"right\"></span>\r\n                        <span id=\"centerAlign\" class=\"center-align\" data-align=\"center\"></span>\r\n                    </span>\r\n            <input id=\"align\" name=\"align\" type=\"hidden\" value=\"none\"/>\r\n        </div>\r\n        <div id=\"tabbody\" class=\"tabbody\">\r\n\r\n            <!-- 远程图片 -->\r\n            <div id=\"remote\" class=\"panel\">\r\n                <div class=\"top\">\r\n                    <div class=\"row\">\r\n                        <label for=\"url\"><var id=\"lang_input_url\"></var></label>\r\n                        <span><input class=\"text\" id=\"url\" type=\"text\"/></span>\r\n                    </div>\r\n                </div>\r\n                <div class=\"left\">\r\n                    <div class=\"row\">\r\n                        <label><var id=\"lang_input_size\"></var></label>\r\n                        <span><var id=\"lang_input_width\">&nbsp;&nbsp;</var><input class=\"text\" type=\"text\" id=\"width\"/>px </span>\r\n                        <span><var id=\"lang_input_height\">&nbsp;&nbsp;</var><input class=\"text\" type=\"text\" id=\"height\"/>px </span>\r\n                        <span><input id=\"lock\" type=\"checkbox\" disabled=\"disabled\"><span id=\"lockicon\"></span></span>\r\n                    </div>\r\n                    <div class=\"row\">\r\n                        <label><var id=\"lang_input_border\"></var></label>\r\n                        <span><input class=\"text\" type=\"text\" id=\"border\"/>px </span>\r\n                    </div>\r\n                    <div class=\"row\">\r\n                        <label><var id=\"lang_input_vhspace\"></var></label>\r\n                        <span><input class=\"text\" type=\"text\" id=\"vhSpace\"/>px </span>\r\n                    </div>\r\n                    <div class=\"row\">\r\n                        <label><var id=\"lang_input_title\"></var></label>\r\n                        <span><input class=\"text\" type=\"text\" id=\"title\"/></span>\r\n                    </div>\r\n                </div>\r\n                <div class=\"right\"><div id=\"preview\"></div></div>\r\n            </div>\r\n\r\n            <!-- 上传图片 -->\r\n            <div id=\"upload\" class=\"panel focus\">\r\n                <div id=\"queueList\" class=\"queueList\">\r\n                    <div class=\"statusBar element-invisible\">\r\n                        <div class=\"progress\">\r\n                            <span class=\"text\">0%</span>\r\n                            <span class=\"percentage\"></span>\r\n                        </div><div class=\"info\"></div>\r\n                        <div class=\"btns\">\r\n                            <div id=\"filePickerBtn\"></div>\r\n                            <div class=\"uploadBtn\"><var id=\"lang_start_upload\"></var></div>\r\n                        </div>\r\n                    </div>\r\n                    <div id=\"dndArea\" class=\"placeholder\">\r\n                        <div class=\"filePickerContainer\">\r\n                            <div id=\"filePickerReady\"></div>\r\n                        </div>\r\n                    </div>\r\n                    <ul class=\"filelist element-invisible\">\r\n                        <li id=\"filePickerBlock\" class=\"filePickerBlock\"></li>\r\n                    </ul>\r\n                </div>\r\n            </div>\r\n\r\n            <!-- 在线图片 -->\r\n            <div id=\"online\" class=\"panel\">\r\n                <div id=\"imageList\"><var id=\"lang_imgLoading\"></var></div>\r\n            </div>\r\n\r\n            <!-- 搜索图片 -->\r\n            <div id=\"search\" class=\"panel\">\r\n                <div class=\"searchBar\">\r\n                    <input id=\"searchTxt\" class=\"searchTxt text\" type=\"text\" />\r\n                    <select id=\"searchType\" class=\"searchType\">\r\n                        <option value=\"&s=4&z=0\"></option>\r\n                        <option value=\"&s=1&z=19\"></option>\r\n                        <option value=\"&s=2&z=0\"></option>\r\n                        <option value=\"&s=3&z=0\"></option>\r\n                    </select>\r\n                    <input id=\"searchReset\" type=\"button\"  />\r\n                    <input id=\"searchBtn\" type=\"button\"  />\r\n                </div>\r\n                <div id=\"searchList\" class=\"searchList\"><ul id=\"searchListUl\"></ul></div>\r\n            </div>\r\n\r\n        </div>\r\n    </div>\r\n    <script type=\"text/javascript\" src=\"image.js\"></script>\r\n\r\n</body>\r\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/image/image.js",
    "content": "/**\r\n * User: Jinqn\r\n * Date: 14-04-08\r\n * Time: 下午16:34\r\n * 上传图片对话框逻辑代码,包括tab: 远程图片/上传图片/在线图片/搜索图片\r\n */\r\n\r\n(function () {\r\n\r\n    var remoteImage,\r\n        uploadImage,\r\n        onlineImage,\r\n        searchImage;\r\n\r\n    window.onload = function () {\r\n        initTabs();\r\n        initAlign();\r\n        initButtons();\r\n    };\r\n\r\n    /* 初始化tab标签 */\r\n    function initTabs() {\r\n        var tabs = $G('tabhead').children;\r\n        for (var i = 0; i < tabs.length; i++) {\r\n            domUtils.on(tabs[i], \"click\", function (e) {\r\n                var target = e.target || e.srcElement;\r\n                setTabFocus(target.getAttribute('data-content-id'));\r\n            });\r\n        }\r\n\r\n        var img = editor.selection.getRange().getClosedNode();\r\n        if (img && img.tagName && img.tagName.toLowerCase() == 'img') {\r\n            setTabFocus('remote');\r\n        } else {\r\n            setTabFocus('upload');\r\n        }\r\n    }\r\n\r\n    /* 初始化tabbody */\r\n    function setTabFocus(id) {\r\n        if(!id) return;\r\n        var i, bodyId, tabs = $G('tabhead').children;\r\n        for (i = 0; i < tabs.length; i++) {\r\n            bodyId = tabs[i].getAttribute('data-content-id');\r\n            if (bodyId == id) {\r\n                domUtils.addClass(tabs[i], 'focus');\r\n                domUtils.addClass($G(bodyId), 'focus');\r\n            } else {\r\n                domUtils.removeClasses(tabs[i], 'focus');\r\n                domUtils.removeClasses($G(bodyId), 'focus');\r\n            }\r\n        }\r\n        switch (id) {\r\n            case 'remote':\r\n                remoteImage = remoteImage || new RemoteImage();\r\n                break;\r\n            case 'upload':\r\n                setAlign(editor.getOpt('imageInsertAlign'));\r\n                uploadImage = uploadImage || new UploadImage('queueList');\r\n                break;\r\n            case 'online':\r\n                setAlign(editor.getOpt('imageManagerInsertAlign'));\r\n                onlineImage = onlineImage || new OnlineImage('imageList');\r\n                onlineImage.reset();\r\n                break;\r\n            case 'search':\r\n                setAlign(editor.getOpt('imageManagerInsertAlign'));\r\n                searchImage = searchImage || new SearchImage();\r\n                break;\r\n        }\r\n    }\r\n\r\n    /* 初始化onok事件 */\r\n    function initButtons() {\r\n\r\n        dialog.onok = function () {\r\n            var remote = false, list = [], id, tabs = $G('tabhead').children;\r\n            for (var i = 0; i < tabs.length; i++) {\r\n                if (domUtils.hasClass(tabs[i], 'focus')) {\r\n                    id = tabs[i].getAttribute('data-content-id');\r\n                    break;\r\n                }\r\n            }\r\n\r\n            switch (id) {\r\n                case 'remote':\r\n                    list = remoteImage.getInsertList();\r\n                    break;\r\n                case 'upload':\r\n                    list = uploadImage.getInsertList();\r\n                    var count = uploadImage.getQueueCount();\r\n                    if (count) {\r\n                        $('.info', '#queueList').html('<span style=\"color:red;\">' + '还有2个未上传文件'.replace(/[\\d]/, count) + '</span>');\r\n                        return false;\r\n                    }\r\n                    break;\r\n                case 'online':\r\n                    list = onlineImage.getInsertList();\r\n                    break;\r\n                case 'search':\r\n                    list = searchImage.getInsertList();\r\n                    remote = true;\r\n                    break;\r\n            }\r\n\r\n            if(list) {\r\n                editor.execCommand('insertimage', list);\r\n                remote && editor.fireEvent(\"catchRemoteImage\");\r\n            }\r\n        };\r\n    }\r\n\r\n\r\n    /* 初始化对其方式的点击事件 */\r\n    function initAlign(){\r\n        /* 点击align图标 */\r\n        domUtils.on($G(\"alignIcon\"), 'click', function(e){\r\n            var target = e.target || e.srcElement;\r\n            if(target.className && target.className.indexOf('-align') != -1) {\r\n                setAlign(target.getAttribute('data-align'));\r\n            }\r\n        });\r\n    }\r\n\r\n    /* 设置对齐方式 */\r\n    function setAlign(align){\r\n        align = align || 'none';\r\n        var aligns = $G(\"alignIcon\").children;\r\n        for(i = 0; i < aligns.length; i++){\r\n            if(aligns[i].getAttribute('data-align') == align) {\r\n                domUtils.addClass(aligns[i], 'focus');\r\n                $G(\"align\").value = aligns[i].getAttribute('data-align');\r\n            } else {\r\n                domUtils.removeClasses(aligns[i], 'focus');\r\n            }\r\n        }\r\n    }\r\n    /* 获取对齐方式 */\r\n    function getAlign(){\r\n        var align = $G(\"align\").value || 'none';\r\n        return align == 'none' ? '':align;\r\n    }\r\n\r\n\r\n    /* 在线图片 */\r\n    function RemoteImage(target) {\r\n        this.container = utils.isString(target) ? document.getElementById(target) : target;\r\n        this.init();\r\n    }\r\n    RemoteImage.prototype = {\r\n        init: function () {\r\n            this.initContainer();\r\n            this.initEvents();\r\n        },\r\n        initContainer: function () {\r\n            this.dom = {\r\n                'url': $G('url'),\r\n                'width': $G('width'),\r\n                'height': $G('height'),\r\n                'border': $G('border'),\r\n                'vhSpace': $G('vhSpace'),\r\n                'title': $G('title'),\r\n                'align': $G('align')\r\n            };\r\n            var img = editor.selection.getRange().getClosedNode();\r\n            if (img) {\r\n                this.setImage(img);\r\n            }\r\n        },\r\n        initEvents: function () {\r\n            var _this = this,\r\n                locker = $G('lock');\r\n\r\n            /* 改变url */\r\n            domUtils.on($G(\"url\"), 'keyup', updatePreview);\r\n            domUtils.on($G(\"border\"), 'keyup', updatePreview);\r\n            domUtils.on($G(\"title\"), 'keyup', updatePreview);\r\n\r\n            domUtils.on($G(\"width\"), 'keyup', function(){\r\n                updatePreview();\r\n                if(locker.checked) {\r\n                    var proportion =locker.getAttribute('data-proportion');\r\n                    $G('height').value = Math.round(this.value / proportion);\r\n                } else {\r\n                    _this.updateLocker();\r\n                }\r\n            });\r\n            domUtils.on($G(\"height\"), 'keyup', function(){\r\n                updatePreview();\r\n                if(locker.checked) {\r\n                    var proportion =locker.getAttribute('data-proportion');\r\n                    $G('width').value = Math.round(this.value * proportion);\r\n                } else {\r\n                    _this.updateLocker();\r\n                }\r\n            });\r\n            domUtils.on($G(\"lock\"), 'change', function(){\r\n                var proportion = parseInt($G(\"width\").value) /parseInt($G(\"height\").value);\r\n                locker.setAttribute('data-proportion', proportion);\r\n            });\r\n\r\n            function updatePreview(){\r\n                _this.setPreview();\r\n            }\r\n        },\r\n        updateLocker: function(){\r\n            var width = $G('width').value,\r\n                height = $G('height').value,\r\n                locker = $G('lock');\r\n            if(width && height && width == parseInt(width) && height == parseInt(height)) {\r\n                locker.disabled = false;\r\n                locker.title = '';\r\n            } else {\r\n                locker.checked = false;\r\n                locker.disabled = 'disabled';\r\n                locker.title = lang.remoteLockError;\r\n            }\r\n        },\r\n        setImage: function(img){\r\n            /* 不是正常的图片 */\r\n            if (!img.tagName || img.tagName.toLowerCase() != 'img' && !img.getAttribute(\"src\") || !img.src) return;\r\n\r\n            var wordImgFlag = img.getAttribute(\"word_img\"),\r\n                src = wordImgFlag ? wordImgFlag.replace(\"&amp;\", \"&\") : (img.getAttribute('_src') || img.getAttribute(\"src\", 2).replace(\"&amp;\", \"&\")),\r\n                align = editor.queryCommandValue(\"imageFloat\");\r\n\r\n            /* 防止onchange事件循环调用 */\r\n            if (src !== $G(\"url\").value) $G(\"url\").value = src;\r\n            if(src) {\r\n                /* 设置表单内容 */\r\n                $G(\"width\").value = img.width || '';\r\n                $G(\"height\").value = img.height || '';\r\n                $G(\"border\").value = img.getAttribute(\"border\") || '0';\r\n                $G(\"vhSpace\").value = img.getAttribute(\"vspace\") || '0';\r\n                $G(\"title\").value = img.title || img.alt || '';\r\n                setAlign(align);\r\n                this.setPreview();\r\n                this.updateLocker();\r\n            }\r\n        },\r\n        getData: function(){\r\n            var data = {};\r\n            for(var k in this.dom){\r\n                data[k] = this.dom[k].value;\r\n            }\r\n            return data;\r\n        },\r\n        setPreview: function(){\r\n            var url = $G('url').value,\r\n                ow = parseInt($G('width').value, 10) || 0,\r\n                oh = parseInt($G('height').value, 10) || 0,\r\n                border = parseInt($G('border').value, 10) || 0,\r\n                title = $G('title').value,\r\n                preview = $G('preview'),\r\n                width,\r\n                height;\r\n\r\n            url = utils.unhtmlForUrl(url);\r\n            title = utils.unhtml(title);\r\n\r\n            width = ((!ow || !oh) ? preview.offsetWidth:Math.min(ow, preview.offsetWidth));\r\n            width = width+(border*2) > preview.offsetWidth ? width:(preview.offsetWidth - (border*2));\r\n            height = (!ow || !oh) ? '':width*oh/ow;\r\n\r\n            if(url) {\r\n                preview.innerHTML = '<img src=\"' + url + '\" width=\"' + width + '\" height=\"' + height + '\" border=\"' + border + 'px solid #000\" title=\"' + title + '\" />';\r\n            }\r\n        },\r\n        getInsertList: function () {\r\n            var data = this.getData();\r\n            if(data['url']) {\r\n                return [{\r\n                    src: data['url'],\r\n                    _src: data['url'],\r\n                    width: data['width'] || '',\r\n                    height: data['height'] || '',\r\n                    border: data['border'] || '',\r\n                    floatStyle: data['align'] || '',\r\n                    vspace: data['vhSpace'] || '',\r\n                    title: data['title'] || '',\r\n                    alt: data['title'] || '',\r\n                    style: \"width:\" + data['width'] + \"px;height:\" + data['height'] + \"px;\"\r\n                }];\r\n            } else {\r\n                return [];\r\n            }\r\n        }\r\n    };\r\n\r\n\r\n\r\n    /* 上传图片 */\r\n    function UploadImage(target) {\r\n        this.$wrap = target.constructor == String ? $('#' + target) : $(target);\r\n        this.init();\r\n    }\r\n    UploadImage.prototype = {\r\n        init: function () {\r\n            this.imageList = [];\r\n            this.initContainer();\r\n            this.initUploader();\r\n        },\r\n        initContainer: function () {\r\n            this.$queue = this.$wrap.find('.filelist');\r\n        },\r\n        /* 初始化容器 */\r\n        initUploader: function () {\r\n            var _this = this,\r\n                $ = jQuery,    // just in case. Make sure it's not an other libaray.\r\n                $wrap = _this.$wrap,\r\n            // 图片容器\r\n                $queue = $wrap.find('.filelist'),\r\n            // 状态栏，包括进度和控制按钮\r\n                $statusBar = $wrap.find('.statusBar'),\r\n            // 文件总体选择信息。\r\n                $info = $statusBar.find('.info'),\r\n            // 上传按钮\r\n                $upload = $wrap.find('.uploadBtn'),\r\n            // 上传按钮\r\n                $filePickerBtn = $wrap.find('.filePickerBtn'),\r\n            // 上传按钮\r\n                $filePickerBlock = $wrap.find('.filePickerBlock'),\r\n            // 没选择文件之前的内容。\r\n                $placeHolder = $wrap.find('.placeholder'),\r\n            // 总体进度条\r\n                $progress = $statusBar.find('.progress').hide(),\r\n            // 添加的文件数量\r\n                fileCount = 0,\r\n            // 添加的文件总大小\r\n                fileSize = 0,\r\n            // 优化retina, 在retina下这个值是2\r\n                ratio = window.devicePixelRatio || 1,\r\n            // 缩略图大小\r\n                thumbnailWidth = 113 * ratio,\r\n                thumbnailHeight = 113 * ratio,\r\n            // 可能有pedding, ready, uploading, confirm, done.\r\n                state = '',\r\n            // 所有文件的进度信息，key为file id\r\n                percentages = {},\r\n                supportTransition = (function () {\r\n                    var s = document.createElement('p').style,\r\n                        r = 'transition' in s ||\r\n                            'WebkitTransition' in s ||\r\n                            'MozTransition' in s ||\r\n                            'msTransition' in s ||\r\n                            'OTransition' in s;\r\n                    s = null;\r\n                    return r;\r\n                })(),\r\n            // WebUploader实例\r\n                uploader,\r\n                actionUrl = editor.getActionUrl(editor.getOpt('imageActionName')),\r\n                acceptExtensions = (editor.getOpt('imageAllowFiles') || []).join('').replace(/\\./g, ',').replace(/^[,]/, ''),\r\n                imageMaxSize = editor.getOpt('imageMaxSize'),\r\n                imageCompressBorder = editor.getOpt('imageCompressBorder');\r\n\r\n            if (!WebUploader.Uploader.support()) {\r\n                $('#filePickerReady').after($('<div>').html(lang.errorNotSupport)).hide();\r\n                return;\r\n            } else if (!editor.getOpt('imageActionName')) {\r\n                $('#filePickerReady').after($('<div>').html(lang.errorLoadConfig)).hide();\r\n                return;\r\n            }\r\n\r\n            uploader = _this.uploader = WebUploader.create({\r\n                pick: {\r\n                    id: '#filePickerReady',\r\n                    label: lang.uploadSelectFile\r\n                },\r\n                accept: {\r\n                    title: 'Images',\r\n                    extensions: acceptExtensions,\r\n                    mimeTypes: 'image/*'\r\n                },\r\n                swf: '../../third-party/webuploader/Uploader.swf',\r\n                server: actionUrl,\r\n                fileVal: editor.getOpt('imageFieldName'),\r\n                duplicate: true,\r\n                fileSingleSizeLimit: imageMaxSize,    // 默认 2 M\r\n                compress: editor.getOpt('imageCompressEnable') ? {\r\n                    width: imageCompressBorder,\r\n                    height: imageCompressBorder,\r\n                    // 图片质量，只有type为`image/jpeg`的时候才有效。\r\n                    quality: 90,\r\n                    // 是否允许放大，如果想要生成小图的时候不失真，此选项应该设置为false.\r\n                    allowMagnify: false,\r\n                    // 是否允许裁剪。\r\n                    crop: false,\r\n                    // 是否保留头部meta信息。\r\n                    preserveHeaders: true\r\n                }:false\r\n            });\r\n            uploader.addButton({\r\n                id: '#filePickerBlock'\r\n            });\r\n            uploader.addButton({\r\n                id: '#filePickerBtn',\r\n                label: lang.uploadAddFile\r\n            });\r\n\r\n            setState('pedding');\r\n\r\n            // 当有文件添加进来时执行，负责view的创建\r\n            function addFile(file) {\r\n                var $li = $('<li id=\"' + file.id + '\">' +\r\n                        '<p class=\"title\">' + file.name + '</p>' +\r\n                        '<p class=\"imgWrap\"></p>' +\r\n                        '<p class=\"progress\"><span></span></p>' +\r\n                        '</li>'),\r\n\r\n                    $btns = $('<div class=\"file-panel\">' +\r\n                        '<span class=\"cancel\">' + lang.uploadDelete + '</span>' +\r\n                        '<span class=\"rotateRight\">' + lang.uploadTurnRight + '</span>' +\r\n                        '<span class=\"rotateLeft\">' + lang.uploadTurnLeft + '</span></div>').appendTo($li),\r\n                    $prgress = $li.find('p.progress span'),\r\n                    $wrap = $li.find('p.imgWrap'),\r\n                    $info = $('<p class=\"error\"></p>').hide().appendTo($li),\r\n\r\n                    showError = function (code) {\r\n                        switch (code) {\r\n                            case 'exceed_size':\r\n                                text = lang.errorExceedSize;\r\n                                break;\r\n                            case 'interrupt':\r\n                                text = lang.errorInterrupt;\r\n                                break;\r\n                            case 'http':\r\n                                text = lang.errorHttp;\r\n                                break;\r\n                            case 'not_allow_type':\r\n                                text = lang.errorFileType;\r\n                                break;\r\n                            default:\r\n                                text = lang.errorUploadRetry;\r\n                                break;\r\n                        }\r\n                        $info.text(text).show();\r\n                    };\r\n\r\n                if (file.getStatus() === 'invalid') {\r\n                    showError(file.statusText);\r\n                } else {\r\n                    $wrap.text(lang.uploadPreview);\r\n                    if (browser.ie && browser.version <= 7) {\r\n                        $wrap.text(lang.uploadNoPreview);\r\n                    } else {\r\n                        uploader.makeThumb(file, function (error, src) {\r\n                            if (error || !src) {\r\n                                $wrap.text(lang.uploadNoPreview);\r\n                            } else {\r\n                                var $img = $('<img src=\"' + src + '\">');\r\n                                $wrap.empty().append($img);\r\n                                $img.on('error', function () {\r\n                                    $wrap.text(lang.uploadNoPreview);\r\n                                });\r\n                            }\r\n                        }, thumbnailWidth, thumbnailHeight);\r\n                    }\r\n                    percentages[ file.id ] = [ file.size, 0 ];\r\n                    file.rotation = 0;\r\n\r\n                    /* 检查文件格式 */\r\n                    if (!file.ext || acceptExtensions.indexOf(file.ext.toLowerCase()) == -1) {\r\n                        showError('not_allow_type');\r\n                        uploader.removeFile(file);\r\n                    }\r\n                }\r\n\r\n                file.on('statuschange', function (cur, prev) {\r\n                    if (prev === 'progress') {\r\n                        $prgress.hide().width(0);\r\n                    } else if (prev === 'queued') {\r\n                        $li.off('mouseenter mouseleave');\r\n                        $btns.remove();\r\n                    }\r\n                    // 成功\r\n                    if (cur === 'error' || cur === 'invalid') {\r\n                        showError(file.statusText);\r\n                        percentages[ file.id ][ 1 ] = 1;\r\n                    } else if (cur === 'interrupt') {\r\n                        showError('interrupt');\r\n                    } else if (cur === 'queued') {\r\n                        percentages[ file.id ][ 1 ] = 0;\r\n                    } else if (cur === 'progress') {\r\n                        $info.hide();\r\n                        $prgress.css('display', 'block');\r\n                    } else if (cur === 'complete') {\r\n                    }\r\n\r\n                    $li.removeClass('state-' + prev).addClass('state-' + cur);\r\n                });\r\n\r\n                $li.on('mouseenter', function () {\r\n                    $btns.stop().animate({height: 30});\r\n                });\r\n                $li.on('mouseleave', function () {\r\n                    $btns.stop().animate({height: 0});\r\n                });\r\n\r\n                $btns.on('click', 'span', function () {\r\n                    var index = $(this).index(),\r\n                        deg;\r\n\r\n                    switch (index) {\r\n                        case 0:\r\n                            uploader.removeFile(file);\r\n                            return;\r\n                        case 1:\r\n                            file.rotation += 90;\r\n                            break;\r\n                        case 2:\r\n                            file.rotation -= 90;\r\n                            break;\r\n                    }\r\n\r\n                    if (supportTransition) {\r\n                        deg = 'rotate(' + file.rotation + 'deg)';\r\n                        $wrap.css({\r\n                            '-webkit-transform': deg,\r\n                            '-mos-transform': deg,\r\n                            '-o-transform': deg,\r\n                            'transform': deg\r\n                        });\r\n                    } else {\r\n                        $wrap.css('filter', 'progid:DXImageTransform.Microsoft.BasicImage(rotation=' + (~~((file.rotation / 90) % 4 + 4) % 4) + ')');\r\n                    }\r\n\r\n                });\r\n\r\n                $li.insertBefore($filePickerBlock);\r\n            }\r\n\r\n            // 负责view的销毁\r\n            function removeFile(file) {\r\n                var $li = $('#' + file.id);\r\n                delete percentages[ file.id ];\r\n                updateTotalProgress();\r\n                $li.off().find('.file-panel').off().end().remove();\r\n            }\r\n\r\n            function updateTotalProgress() {\r\n                var loaded = 0,\r\n                    total = 0,\r\n                    spans = $progress.children(),\r\n                    percent;\r\n\r\n                $.each(percentages, function (k, v) {\r\n                    total += v[ 0 ];\r\n                    loaded += v[ 0 ] * v[ 1 ];\r\n                });\r\n\r\n                percent = total ? loaded / total : 0;\r\n\r\n                spans.eq(0).text(Math.round(percent * 100) + '%');\r\n                spans.eq(1).css('width', Math.round(percent * 100) + '%');\r\n                updateStatus();\r\n            }\r\n\r\n            function setState(val, files) {\r\n\r\n                if (val != state) {\r\n\r\n                    var stats = uploader.getStats();\r\n\r\n                    $upload.removeClass('state-' + state);\r\n                    $upload.addClass('state-' + val);\r\n\r\n                    switch (val) {\r\n\r\n                        /* 未选择文件 */\r\n                        case 'pedding':\r\n                            $queue.addClass('element-invisible');\r\n                            $statusBar.addClass('element-invisible');\r\n                            $placeHolder.removeClass('element-invisible');\r\n                            $progress.hide(); $info.hide();\r\n                            uploader.refresh();\r\n                            break;\r\n\r\n                        /* 可以开始上传 */\r\n                        case 'ready':\r\n                            $placeHolder.addClass('element-invisible');\r\n                            $queue.removeClass('element-invisible');\r\n                            $statusBar.removeClass('element-invisible');\r\n                            $progress.hide(); $info.show();\r\n                            $upload.text(lang.uploadStart);\r\n                            uploader.refresh();\r\n                            break;\r\n\r\n                        /* 上传中 */\r\n                        case 'uploading':\r\n                            $progress.show(); $info.hide();\r\n                            $upload.text(lang.uploadPause);\r\n                            break;\r\n\r\n                        /* 暂停上传 */\r\n                        case 'paused':\r\n                            $progress.show(); $info.hide();\r\n                            $upload.text(lang.uploadContinue);\r\n                            break;\r\n\r\n                        case 'confirm':\r\n                            $progress.show(); $info.hide();\r\n                            $upload.text(lang.uploadStart);\r\n\r\n                            stats = uploader.getStats();\r\n                            if (stats.successNum && !stats.uploadFailNum) {\r\n                                setState('finish');\r\n                                return;\r\n                            }\r\n                            break;\r\n\r\n                        case 'finish':\r\n                            $progress.hide(); $info.show();\r\n                            if (stats.uploadFailNum) {\r\n                                $upload.text(lang.uploadRetry);\r\n                            } else {\r\n                                $upload.text(lang.uploadStart);\r\n                            }\r\n                            break;\r\n                    }\r\n\r\n                    state = val;\r\n                    updateStatus();\r\n\r\n                }\r\n\r\n                if (!_this.getQueueCount()) {\r\n                    $upload.addClass('disabled')\r\n                } else {\r\n                    $upload.removeClass('disabled')\r\n                }\r\n\r\n            }\r\n\r\n            function updateStatus() {\r\n                var text = '', stats;\r\n\r\n                if (state === 'ready') {\r\n                    text = lang.updateStatusReady.replace('_', fileCount).replace('_KB', WebUploader.formatSize(fileSize));\r\n                } else if (state === 'confirm') {\r\n                    stats = uploader.getStats();\r\n                    if (stats.uploadFailNum) {\r\n                        text = lang.updateStatusConfirm.replace('_', stats.successNum).replace('_', stats.successNum);\r\n                    }\r\n                } else {\r\n                    stats = uploader.getStats();\r\n                    text = lang.updateStatusFinish.replace('_', fileCount).\r\n                        replace('_KB', WebUploader.formatSize(fileSize)).\r\n                        replace('_', stats.successNum);\r\n\r\n                    if (stats.uploadFailNum) {\r\n                        text += lang.updateStatusError.replace('_', stats.uploadFailNum);\r\n                    }\r\n                }\r\n\r\n                $info.html(text);\r\n            }\r\n\r\n            uploader.on('fileQueued', function (file) {\r\n                fileCount++;\r\n                fileSize += file.size;\r\n\r\n                if (fileCount === 1) {\r\n                    $placeHolder.addClass('element-invisible');\r\n                    $statusBar.show();\r\n                }\r\n\r\n                addFile(file);\r\n            });\r\n\r\n            uploader.on('fileDequeued', function (file) {\r\n                fileCount--;\r\n                fileSize -= file.size;\r\n\r\n                removeFile(file);\r\n                updateTotalProgress();\r\n            });\r\n\r\n            uploader.on('filesQueued', function (file) {\r\n                if (!uploader.isInProgress() && (state == 'pedding' || state == 'finish' || state == 'confirm' || state == 'ready')) {\r\n                    setState('ready');\r\n                }\r\n                updateTotalProgress();\r\n            });\r\n\r\n            uploader.on('all', function (type, files) {\r\n                switch (type) {\r\n                    case 'uploadFinished':\r\n                        setState('confirm', files);\r\n                        break;\r\n                    case 'startUpload':\r\n                        /* 添加额外的GET参数 */\r\n                        var params = utils.serializeParam(editor.queryCommandValue('serverparam')) || '',\r\n                            url = utils.formatUrl(actionUrl + (actionUrl.indexOf('?') == -1 ? '?':'&') + 'encode=utf-8&' + params);\r\n                        uploader.option('server', url);\r\n                        setState('uploading', files);\r\n                        break;\r\n                    case 'stopUpload':\r\n                        setState('paused', files);\r\n                        break;\r\n                }\r\n            });\r\n\r\n            uploader.on('uploadBeforeSend', function (file, data, header) {\r\n                //这里可以通过data对象添加POST参数\r\n                header['X_Requested_With'] = 'XMLHttpRequest';\r\n                // HaoChuan9421\r\n                if(editor.options.headers && Object.prototype.toString.apply(editor.options.headers) === \"[object Object]\"){\r\n                    for(var key in editor.options.headers){\r\n                        header[key] = editor.options.headers[key]\r\n                    }\r\n                }\r\n            });\r\n\r\n            uploader.on('uploadProgress', function (file, percentage) {\r\n                var $li = $('#' + file.id),\r\n                    $percent = $li.find('.progress span');\r\n\r\n                $percent.css('width', percentage * 100 + '%');\r\n                percentages[ file.id ][ 1 ] = percentage;\r\n                updateTotalProgress();\r\n            });\r\n\r\n            uploader.on('uploadSuccess', function (file, ret) {\r\n                var $file = $('#' + file.id);\r\n                try {\r\n                    var responseText = (ret._raw || ret),\r\n                        json = utils.str2json(responseText);\r\n                    if (json.state == 'SUCCESS') {\r\n                        _this.imageList.push(json);\r\n                        $file.append('<span class=\"success\"></span>');\r\n                    } else {\r\n                        $file.find('.error').text(json.state).show();\r\n                    }\r\n                } catch (e) {\r\n                    $file.find('.error').text(lang.errorServerUpload).show();\r\n                }\r\n            });\r\n\r\n            uploader.on('uploadError', function (file, code) {\r\n            });\r\n            uploader.on('error', function (code, file) {\r\n                if (code == 'Q_TYPE_DENIED' || code == 'F_EXCEED_SIZE') {\r\n                    addFile(file);\r\n                }\r\n            });\r\n            uploader.on('uploadComplete', function (file, ret) {\r\n            });\r\n\r\n            $upload.on('click', function () {\r\n                if ($(this).hasClass('disabled')) {\r\n                    return false;\r\n                }\r\n\r\n                if (state === 'ready') {\r\n                    uploader.upload();\r\n                } else if (state === 'paused') {\r\n                    uploader.upload();\r\n                } else if (state === 'uploading') {\r\n                    uploader.stop();\r\n                }\r\n            });\r\n\r\n            $upload.addClass('state-' + state);\r\n            updateTotalProgress();\r\n        },\r\n        getQueueCount: function () {\r\n            var file, i, status, readyFile = 0, files = this.uploader.getFiles();\r\n            for (i = 0; file = files[i++]; ) {\r\n                status = file.getStatus();\r\n                if (status == 'queued' || status == 'uploading' || status == 'progress') readyFile++;\r\n            }\r\n            return readyFile;\r\n        },\r\n        destroy: function () {\r\n            this.$wrap.remove();\r\n        },\r\n        getInsertList: function () {\r\n            var i, data, list = [],\r\n                align = getAlign(),\r\n                prefix = editor.getOpt('imageUrlPrefix');\r\n            for (i = 0; i < this.imageList.length; i++) {\r\n                data = this.imageList[i];\r\n                list.push({\r\n                    src: prefix + data.url,\r\n                    _src: prefix + data.url,\r\n                    title: data.title,\r\n                    alt: data.original,\r\n                    floatStyle: align\r\n                });\r\n            }\r\n            return list;\r\n        }\r\n    };\r\n\r\n\r\n    /* 在线图片 */\r\n    function OnlineImage(target) {\r\n        this.container = utils.isString(target) ? document.getElementById(target) : target;\r\n        this.init();\r\n    }\r\n    OnlineImage.prototype = {\r\n        init: function () {\r\n            this.reset();\r\n            this.initEvents();\r\n        },\r\n        /* 初始化容器 */\r\n        initContainer: function () {\r\n            this.container.innerHTML = '';\r\n            this.list = document.createElement('ul');\r\n            this.clearFloat = document.createElement('li');\r\n\r\n            domUtils.addClass(this.list, 'list');\r\n            domUtils.addClass(this.clearFloat, 'clearFloat');\r\n\r\n            this.list.appendChild(this.clearFloat);\r\n            this.container.appendChild(this.list);\r\n        },\r\n        /* 初始化滚动事件,滚动到地步自动拉取数据 */\r\n        initEvents: function () {\r\n            var _this = this;\r\n\r\n            /* 滚动拉取图片 */\r\n            domUtils.on($G('imageList'), 'scroll', function(e){\r\n                var panel = this;\r\n                if (panel.scrollHeight - (panel.offsetHeight + panel.scrollTop) < 10) {\r\n                    _this.getImageData();\r\n                }\r\n            });\r\n            /* 选中图片 */\r\n            domUtils.on(this.container, 'click', function (e) {\r\n                var target = e.target || e.srcElement,\r\n                    li = target.parentNode;\r\n\r\n                if (li.tagName.toLowerCase() == 'li') {\r\n                    if (domUtils.hasClass(li, 'selected')) {\r\n                        domUtils.removeClasses(li, 'selected');\r\n                    } else {\r\n                        domUtils.addClass(li, 'selected');\r\n                    }\r\n                }\r\n            });\r\n        },\r\n        /* 初始化第一次的数据 */\r\n        initData: function () {\r\n\r\n            /* 拉取数据需要使用的值 */\r\n            this.state = 0;\r\n            this.listSize = editor.getOpt('imageManagerListSize');\r\n            this.listIndex = 0;\r\n            this.listEnd = false;\r\n\r\n            /* 第一次拉取数据 */\r\n            this.getImageData();\r\n        },\r\n        /* 重置界面 */\r\n        reset: function() {\r\n            this.initContainer();\r\n            this.initData();\r\n        },\r\n        /* 向后台拉取图片列表数据 */\r\n        getImageData: function () {\r\n            var _this = this;\r\n\r\n            if(!_this.listEnd && !this.isLoadingData) {\r\n                this.isLoadingData = true;\r\n                var url = editor.getActionUrl(editor.getOpt('imageManagerActionName')),\r\n                    isJsonp = utils.isCrossDomainUrl(url);\r\n                ajax.request(url, {\r\n                    'timeout': 100000,\r\n                    'dataType': isJsonp ? 'jsonp':'',\r\n                    'data': utils.extend({\r\n                            start: this.listIndex,\r\n                            size: this.listSize\r\n                        }, editor.queryCommandValue('serverparam')),\r\n                    'method': 'get',\r\n                    'onsuccess': function (r) {\r\n                        try {\r\n                            var json = isJsonp ? r:eval('(' + r.responseText + ')');\r\n                            if (json.state == 'SUCCESS') {\r\n                                _this.pushData(json.list);\r\n                                _this.listIndex = parseInt(json.start) + parseInt(json.list.length);\r\n                                if(_this.listIndex >= json.total) {\r\n                                    _this.listEnd = true;\r\n                                }\r\n                                _this.isLoadingData = false;\r\n                            }\r\n                        } catch (e) {\r\n                            if(r.responseText.indexOf('ue_separate_ue') != -1) {\r\n                                var list = r.responseText.split(r.responseText);\r\n                                _this.pushData(list);\r\n                                _this.listIndex = parseInt(list.length);\r\n                                _this.listEnd = true;\r\n                                _this.isLoadingData = false;\r\n                            }\r\n                        }\r\n                    },\r\n                    'onerror': function () {\r\n                        _this.isLoadingData = false;\r\n                    }\r\n                });\r\n            }\r\n        },\r\n        /* 添加图片到列表界面上 */\r\n        pushData: function (list) {\r\n            var i, item, img, icon, _this = this,\r\n                urlPrefix = editor.getOpt('imageManagerUrlPrefix');\r\n            for (i = 0; i < list.length; i++) {\r\n                if(list[i] && list[i].url) {\r\n                    item = document.createElement('li');\r\n                    img = document.createElement('img');\r\n                    icon = document.createElement('span');\r\n\r\n                    domUtils.on(img, 'load', (function(image){\r\n                        return function(){\r\n                            _this.scale(image, image.parentNode.offsetWidth, image.parentNode.offsetHeight);\r\n                        }\r\n                    })(img));\r\n                    img.width = 113;\r\n                    img.setAttribute('src', urlPrefix + list[i].url + (list[i].url.indexOf('?') == -1 ? '?noCache=':'&noCache=') + (+new Date()).toString(36) );\r\n                    img.setAttribute('_src', urlPrefix + list[i].url);\r\n                    domUtils.addClass(icon, 'icon');\r\n\r\n                    item.appendChild(img);\r\n                    item.appendChild(icon);\r\n                    this.list.insertBefore(item, this.clearFloat);\r\n                }\r\n            }\r\n        },\r\n        /* 改变图片大小 */\r\n        scale: function (img, w, h, type) {\r\n            var ow = img.width,\r\n                oh = img.height;\r\n\r\n            if (type == 'justify') {\r\n                if (ow >= oh) {\r\n                    img.width = w;\r\n                    img.height = h * oh / ow;\r\n                    img.style.marginLeft = '-' + parseInt((img.width - w) / 2) + 'px';\r\n                } else {\r\n                    img.width = w * ow / oh;\r\n                    img.height = h;\r\n                    img.style.marginTop = '-' + parseInt((img.height - h) / 2) + 'px';\r\n                }\r\n            } else {\r\n                if (ow >= oh) {\r\n                    img.width = w * ow / oh;\r\n                    img.height = h;\r\n                    img.style.marginLeft = '-' + parseInt((img.width - w) / 2) + 'px';\r\n                } else {\r\n                    img.width = w;\r\n                    img.height = h * oh / ow;\r\n                    img.style.marginTop = '-' + parseInt((img.height - h) / 2) + 'px';\r\n                }\r\n            }\r\n        },\r\n        getInsertList: function () {\r\n            var i, lis = this.list.children, list = [], align = getAlign();\r\n            for (i = 0; i < lis.length; i++) {\r\n                if (domUtils.hasClass(lis[i], 'selected')) {\r\n                    var img = lis[i].firstChild,\r\n                        src = img.getAttribute('_src');\r\n                    list.push({\r\n                        src: src,\r\n                        _src: src,\r\n                        alt: src.substr(src.lastIndexOf('/') + 1),\r\n                        floatStyle: align\r\n                    });\r\n                }\r\n\r\n            }\r\n            return list;\r\n        }\r\n    };\r\n\r\n    /*搜索图片 */\r\n    function SearchImage() {\r\n        this.init();\r\n    }\r\n    SearchImage.prototype = {\r\n        init: function () {\r\n            this.initEvents();\r\n        },\r\n        initEvents: function(){\r\n            var _this = this;\r\n\r\n            /* 点击搜索按钮 */\r\n            domUtils.on($G('searchBtn'), 'click', function(){\r\n                var key = $G('searchTxt').value;\r\n                if(key && key != lang.searchRemind) {\r\n                    _this.getImageData();\r\n                }\r\n            });\r\n            /* 点击清除妞 */\r\n            domUtils.on($G('searchReset'), 'click', function(){\r\n                $G('searchTxt').value = lang.searchRemind;\r\n                $G('searchListUl').innerHTML = '';\r\n                $G('searchType').selectedIndex = 0;\r\n            });\r\n            /* 搜索框聚焦 */\r\n            domUtils.on($G('searchTxt'), 'focus', function(){\r\n                var key = $G('searchTxt').value;\r\n                if(key && key == lang.searchRemind) {\r\n                    $G('searchTxt').value = '';\r\n                }\r\n            });\r\n            /* 搜索框回车键搜索 */\r\n            domUtils.on($G('searchTxt'), 'keydown', function(e){\r\n                var keyCode = e.keyCode || e.which;\r\n                if (keyCode == 13) {\r\n                    $G('searchBtn').click();\r\n                }\r\n            });\r\n\r\n            /* 选中图片 */\r\n            domUtils.on($G('searchList'), 'click', function(e){\r\n                var target = e.target || e.srcElement,\r\n                    li = target.parentNode.parentNode;\r\n\r\n                if (li.tagName.toLowerCase() == 'li') {\r\n                    if (domUtils.hasClass(li, 'selected')) {\r\n                        domUtils.removeClasses(li, 'selected');\r\n                    } else {\r\n                        domUtils.addClass(li, 'selected');\r\n                    }\r\n                }\r\n            });\r\n        },\r\n        encodeToGb2312:function (str){\r\n            if(!str) return '';\r\n            var strOut = \"\",\r\n                z = 'D2BBB6A18140C6DF814181428143CDF2D5C9C8FDC9CFCFC2D8A2B2BBD3EB8144D8A4B3F38145D7A8C7D2D8A7CAC08146C7F0B1FBD2B5B4D4B6ABCBBFD8A9814781488149B6AA814AC1BDD1CF814BC9A5D8AD814CB8F6D1BEE3DCD6D0814D814EB7E1814FB4AE8150C1D98151D8BC8152CDE8B5A4CEAAD6F78153C0F6BED9D8AF815481558156C4CB8157BEC38158D8B1C3B4D2E58159D6AECEDAD5A7BAF5B7A6C0D6815AC6B9C5D2C7C7815BB9D4815CB3CBD2D2815D815ED8BFBEC5C6F2D2B2CFB0CFE7815F816081618162CAE981638164D8C081658166816781688169816AC2F2C2D2816BC8E9816C816D816E816F817081718172817381748175C7AC8176817781788179817A817B817CC1CB817DD3E8D5F9817ECAC2B6FED8A1D3DABFF78180D4C6BBA5D8C1CEE5BEAE81818182D8A88183D1C7D0A9818481858186D8BDD9EFCDF6BFBA8187BDBBBAA5D2E0B2FABAE0C4B68188CFEDBEA9CDA4C1C18189818A818BC7D7D9F1818CD9F4818D818E818F8190C8CBD8E9819181928193D2DACAB2C8CAD8ECD8EAD8C6BDF6C6CDB3F08194D8EBBDF1BDE98195C8D4B4D381968197C2D88198B2D6D7D0CACBCBFBD5CCB8B6CFC98199819A819BD9DAD8F0C7AA819CD8EE819DB4FAC1EED2D4819E819FD8ED81A0D2C7D8EFC3C781A181A281A3D1F681A4D6D9D8F281A5D8F5BCFEBCDB81A681A781A8C8CE81A9B7DD81AAB7C281ABC6F381AC81AD81AE81AF81B081B181B2D8F8D2C181B381B4CEE9BCBFB7FCB7A5D0DD81B581B681B781B881B9D6DAD3C5BBEFBBE1D8F181BA81BBC9A1CEB0B4AB81BCD8F381BDC9CBD8F6C2D7D8F781BE81BFCEB1D8F981C081C181C2B2AEB9C081C3D9A381C4B0E981C5C1E681C6C9EC81C7CBC581C8CBC6D9A481C981CA81CB81CC81CDB5E881CE81CFB5AB81D081D181D281D381D481D5CEBBB5CDD7A1D7F4D3D381D6CCE581D7BACE81D8D9A2D9DCD3E0D8FDB7F0D7F7D8FED8FAD9A1C4E381D981DAD3B6D8F4D9DD81DBD8FB81DCC5E581DD81DEC0D081DF81E0D1F0B0DB81E181E2BCD1D9A681E3D9A581E481E581E681E7D9ACD9AE81E8D9ABCAB981E981EA81EBD9A9D6B681EC81ED81EEB3DED9A881EFC0FD81F0CACC81F1D9AA81F2D9A781F381F4D9B081F581F6B6B181F781F881F9B9A981FAD2C081FB81FCCFC081FD81FEC2C28240BDC4D5ECB2E0C7C8BFEBD9AD8241D9AF8242CEEABAEE82438244824582468247C7D682488249824A824B824C824D824E824F8250B1E3825182528253B4D9B6EDD9B48254825582568257BFA182588259825AD9DEC7CEC0FED9B8825B825C825D825E825FCBD7B7FD8260D9B58261D9B7B1A3D3E1D9B98262D0C58263D9B682648265D9B18266D9B2C1A9D9B382678268BCF3D0DEB8A98269BEE3826AD9BD826B826C826D826ED9BA826FB0B3827082718272D9C28273827482758276827782788279827A827B827C827D827E8280D9C4B1B68281D9BF82828283B5B98284BEF3828582868287CCC8BAF2D2D08288D9C38289828ABDE8828BB3AB828C828D828ED9C5BEEB828FD9C6D9BBC4DF8290D9BED9C1D9C0829182928293829482958296829782988299829A829BD5AE829CD6B5829DC7E3829E829F82A082A1D9C882A282A382A4BCD9D9CA82A582A682A7D9BC82A8D9CBC6AB82A982AA82AB82AC82ADD9C982AE82AF82B082B1D7F682B2CDA382B382B482B582B682B782B882B982BABDA182BB82BC82BD82BE82BF82C0D9CC82C182C282C382C482C582C682C782C882C9C5BCCDB582CA82CB82CCD9CD82CD82CED9C7B3A5BFFE82CF82D082D182D2B8B582D382D4C0FC82D582D682D782D8B0F882D982DA82DB82DC82DD82DE82DF82E082E182E282E382E482E582E682E782E882E982EA82EB82EC82EDB4F682EED9CE82EFD9CFB4A2D9D082F082F1B4DF82F282F382F482F582F6B0C182F782F882F982FA82FB82FC82FDD9D1C9B582FE8340834183428343834483458346834783488349834A834B834C834D834E834F83508351CFF1835283538354835583568357D9D283588359835AC1C5835B835C835D835E835F836083618362836383648365D9D6C9AE8366836783688369D9D5D9D4D9D7836A836B836C836DCBDB836EBDA9836F8370837183728373C6A7837483758376837783788379837A837B837C837DD9D3D9D8837E83808381D9D9838283838384838583868387C8E583888389838A838B838C838D838E838F839083918392839383948395C0DC8396839783988399839A839B839C839D839E839F83A083A183A283A383A483A583A683A783A883A983AA83AB83AC83AD83AE83AF83B083B183B2B6F9D8A3D4CA83B3D4AAD0D6B3E4D5D783B4CFC8B9E283B5BFCB83B6C3E283B783B883B9B6D283BA83BBCDC3D9EED9F083BC83BD83BEB5B383BFB6B583C083C183C283C383C4BEA483C583C6C8EB83C783C8C8AB83C983CAB0CBB9ABC1F9D9E283CBC0BCB9B283CCB9D8D0CBB1F8C6E4BEDFB5E4D7C883CDD1F8BCE6CADE83CE83CFBCBDD9E6D8E783D083D1C4DA83D283D3B8D4C8BD83D483D5B2E1D4D983D683D783D883D9C3B083DA83DBC3E1DAA2C8DF83DCD0B483DDBEFCC5A983DE83DF83E0B9DA83E1DAA383E2D4A9DAA483E383E483E583E683E7D9FBB6AC83E883E9B7EBB1F9D9FCB3E5BEF683EABFF6D2B1C0E483EB83EC83EDB6B3D9FED9FD83EE83EFBEBB83F083F183F2C6E083F3D7BCDAA183F4C1B983F5B5F2C1E883F683F7BCF583F8B4D583F983FA83FB83FC83FD83FE844084418442C1DD8443C4FD84448445BCB8B7B284468447B7EF84488449844A844B844C844DD9EC844EC6BE844FBFADBBCB84508451B5CA8452DBC9D0D78453CDB9B0BCB3F6BBF7DBCABAAF8454D4E4B5B6B5F3D8D6C8D084558456B7D6C7D0D8D78457BFAF84588459DBBBD8D8845A845BD0CCBBAE845C845D845EEBBEC1D0C1F5D4F2B8D5B4B4845FB3F584608461C9BE846284638464C5D0846584668467C5D9C0FB8468B1F08469D8D9B9CE846AB5BD846B846CD8DA846D846ED6C6CBA2C8AFC9B2B4CCBFCC846FB9F48470D8DBD8DCB6E7BCC1CCEA847184728473847484758476CFF78477D8DDC7B084788479B9D0BDA3847A847BCCDE847CC6CA847D847E848084818482D8E08483D8DE84848485D8DF848684878488B0FE8489BEE7848ACAA3BCF4848B848C848D848EB8B1848F8490B8EE849184928493849484958496849784988499849AD8E2849BBDCB849CD8E4D8E3849D849E849F84A084A1C5FC84A284A384A484A584A684A784A8D8E584A984AAD8E684AB84AC84AD84AE84AF84B084B1C1A684B2C8B0B0ECB9A6BCD3CEF1DBBDC1D384B384B484B584B6B6AFD6FAC5ACBDD9DBBEDBBF84B784B884B9C0F8BEA2C0CD84BA84BB84BC84BD84BE84BF84C084C184C284C3DBC0CAC684C484C584C6B2AA84C784C884C9D3C284CAC3E384CBD1AB84CC84CD84CE84CFDBC284D0C0D584D184D284D3DBC384D4BFB184D584D684D784D884D984DAC4BC84DB84DC84DD84DEC7DA84DF84E084E184E284E384E484E584E684E784E884E9DBC484EA84EB84EC84ED84EE84EF84F084F1D9E8C9D784F284F384F4B9B4CEF0D4C884F584F684F784F8B0FCB4D284F9D0D984FA84FB84FC84FDD9E984FEDECBD9EB8540854185428543D8B0BBAFB1B18544B3D7D8CE85458546D4D185478548BDB3BFEF8549CFBB854A854BD8D0854C854D854EB7CB854F85508551D8D185528553855485558556855785588559855A855BC6A5C7F8D2BD855C855DD8D2C4E4855ECAAE855FC7A78560D8A68561C9FDCEE7BBDCB0EB856285638564BBAAD0AD8565B1B0D7E4D7BF8566B5A5C2F4C4CF85678568B2A98569B2B7856AB1E5DFB2D5BCBFA8C2ACD8D5C2B1856BD8D4CED4856CDAE0856DCEC0856E856FD8B4C3AED3A1CEA38570BCB4C8B4C2D18571BEEDD0B68572DAE18573857485758576C7E485778578B3A78579B6F2CCFCC0FA857A857BC0F7857CD1B9D1E1D8C7857D857E85808581858285838584B2DE85858586C0E58587BAF185888589D8C8858AD4AD858B858CCFE1D8C9858DD8CACFC3858EB3F8BEC7858F859085918592D8CB8593859485958596859785988599DBCC859A859B859C859DC8A5859E859F85A0CFD885A1C8FEB2CE85A285A385A485A585A6D3D6B2E6BCB0D3D1CBABB7B485A785A885A9B7A285AA85ABCAE585ACC8A1CADCB1E4D0F085ADC5D185AE85AF85B0DBC5B5FE85B185B2BFDAB9C5BEE4C1ED85B3DFB6DFB5D6BBBDD0D5D9B0C8B6A3BFC9CCA8DFB3CAB7D3D285B4D8CFD2B6BAC5CBBECCBE85B5DFB7B5F0DFB485B685B785B8D3F585B9B3D4B8F785BADFBA85BBBACFBCAAB5F585BCCDACC3FBBAF3C0F4CDC2CFF2DFB8CFC585BDC2C0DFB9C2F085BE85BF85C0BEFD85C1C1DFCDCCD2F7B7CDDFC185C2DFC485C385C4B7F1B0C9B6D6B7D485C5BAACCCFDBFD4CBB1C6F485C6D6A8DFC585C7CEE2B3B385C885C9CEFCB4B585CACEC7BAF085CBCEE185CCD1BD85CD85CEDFC085CF85D0B4F485D1B3CA85D2B8E6DFBB85D385D485D585D6C4C585D7DFBCDFBDDFBEC5BBDFBFDFC2D4B1DFC385D8C7BACED885D985DA85DB85DC85DDC4D885DEDFCA85DFDFCF85E0D6DC85E185E285E385E485E585E685E785E8DFC9DFDACEB685E9BAC7DFCEDFC8C5DE85EA85EBC9EBBAF4C3FC85EC85EDBED785EEDFC685EFDFCD85F0C5D885F185F285F385F4D5A6BACD85F5BECCD3BDB8C085F6D6E485F7DFC7B9BEBFA785F885F9C1FCDFCBDFCC85FADFD085FB85FC85FD85FE8640DFDBDFE58641DFD7DFD6D7C9DFE3DFE4E5EBD2A7DFD28642BFA98643D4DB8644BFC8DFD4864586468647CFCC86488649DFDD864AD1CA864BDFDEB0A7C6B7DFD3864CBAE5864DB6DFCDDBB9FED4D5864E864FDFDFCFECB0A5DFE7DFD1D1C6DFD5DFD8DFD9DFDC8650BBA98651DFE0DFE18652DFE2DFE6DFE8D3B486538654865586568657B8E7C5B6DFEAC9DAC1A8C4C486588659BFDECFF8865A865B865CD5DCDFEE865D865E865F866086618662B2B88663BADFDFEC8664DBC18665D1E48666866786688669CBF4B4BD866AB0A6866B866C866D866E866FDFF1CCC6DFF286708671DFED867286738674867586768677DFE986788679867A867BDFEB867CDFEFDFF0BBBD867D867EDFF386808681DFF48682BBA38683CADBCEA8E0A7B3AA8684E0A6868586868687E0A186888689868A868BDFFE868CCDD9DFFC868DDFFA868EBFD0D7C4868FC9CC86908691DFF8B0A186928693869486958696DFFD869786988699869ADFFBE0A2869B869C869D869E869FE0A886A086A186A286A3B7C886A486A5C6A1C9B6C0B2DFF586A686A7C5BE86A8D8C4DFF9C4F686A986AA86AB86AC86AD86AEE0A3E0A4E0A5D0A586AF86B0E0B4CCE486B1E0B186B2BFA6E0AFCEB9E0ABC9C686B386B4C0AEE0AEBAEDBAB0E0A986B586B686B7DFF686B8E0B386B986BAE0B886BB86BC86BDB4ADE0B986BE86BFCFB2BAC886C0E0B086C186C286C386C486C586C686C7D0FA86C886C986CA86CB86CC86CD86CE86CF86D0E0AC86D1D4FB86D2DFF786D3C5E786D4E0AD86D5D3F786D6E0B6E0B786D786D886D986DA86DBE0C4D0E186DC86DD86DEE0BC86DF86E0E0C9E0CA86E186E286E3E0BEE0AAC9A4E0C186E4E0B286E586E686E786E886E9CAC8E0C386EAE0B586EBCECB86ECCBC3E0CDE0C6E0C286EDE0CB86EEE0BAE0BFE0C086EF86F0E0C586F186F2E0C7E0C886F3E0CC86F4E0BB86F586F686F786F886F9CBD4E0D586FAE0D6E0D286FB86FC86FD86FE87408741E0D0BCCE87428743E0D18744B8C2D8C587458746874787488749874A874B874CD0EA874D874EC2EF874F8750E0CFE0BD875187528753E0D4E0D387548755E0D78756875787588759E0DCE0D8875A875B875CD6F6B3B0875DD7EC875ECBBB875F8760E0DA8761CEFB876287638764BAD987658766876787688769876A876B876C876D876E876F8770E0E1E0DDD2AD87718772877387748775E0E287768777E0DBE0D9E0DF87788779E0E0877A877B877C877D877EE0DE8780E0E4878187828783C6F7D8ACD4EBE0E6CAC98784878587868787E0E587888789878A878BB8C1878C878D878E878FE0E7E0E887908791879287938794879587968797E0E9E0E387988799879A879B879C879D879EBABFCCE7879F87A087A1E0EA87A287A387A487A587A687A787A887A987AA87AB87AC87AD87AE87AF87B0CFF987B187B287B387B487B587B687B787B887B987BA87BBE0EB87BC87BD87BE87BF87C087C187C2C8C287C387C487C587C6BDC087C787C887C987CA87CB87CC87CD87CE87CF87D087D187D287D3C4D287D487D587D687D787D887D987DA87DB87DCE0EC87DD87DEE0ED87DF87E0C7F4CBC487E1E0EEBBD8D8B6D2F2E0EFCDC587E2B6DA87E387E487E587E687E787E8E0F187E9D4B087EA87EBC0A7B4D187EC87EDCEA7E0F087EE87EF87F0E0F2B9CC87F187F2B9FACDBCE0F387F387F487F5C6D4E0F487F6D4B287F7C8A6E0F6E0F587F887F987FA87FB87FC87FD87FE8840884188428843884488458846884788488849E0F7884A884BCDC1884C884D884ECAA5884F885088518852D4DADBD7DBD98853DBD8B9E7DBDCDBDDB5D888548855DBDA8856885788588859885ADBDBB3A1DBDF885B885CBBF8885DD6B7885EDBE0885F886088618862BEF988638864B7BB8865DBD0CCAEBFB2BBB5D7F8BFD38866886788688869886ABFE9886B886CBCE1CCB3DBDEB0D3CEEBB7D8D7B9C6C2886D886EC0A4886FCCB98870DBE7DBE1C6BADBE38871DBE88872C5F7887388748875DBEA88768877DBE9BFC088788879887ADBE6DBE5887B887C887D887E8880B4B9C0ACC2A2DBE2DBE48881888288838884D0CDDBED88858886888788888889C0DDDBF2888A888B888C888D888E888F8890B6E28891889288938894DBF3DBD2B9B8D4ABDBEC8895BFD1DBF08896DBD18897B5E68898DBEBBFE58899889A889BDBEE889CDBF1889D889E889FDBF988A088A188A288A388A488A588A688A788A8B9A1B0A388A988AA88AB88AC88AD88AE88AFC2F188B088B1B3C7DBEF88B288B3DBF888B4C6D2DBF488B588B6DBF5DBF7DBF688B788B8DBFE88B9D3F2B2BA88BA88BB88BCDBFD88BD88BE88BF88C088C188C288C388C4DCA488C5DBFB88C688C788C888C9DBFA88CA88CB88CCDBFCC5E0BBF988CD88CEDCA388CF88D0DCA588D1CCC388D288D388D4B6D1DDC088D588D688D7DCA188D8DCA288D988DA88DBC7B588DC88DD88DEB6E988DF88E088E1DCA788E288E388E488E5DCA688E6DCA9B1A488E788E8B5CC88E988EA88EB88EC88EDBFB088EE88EF88F088F188F2D1DF88F388F488F588F6B6C288F788F888F988FA88FB88FC88FD88FE894089418942894389448945DCA88946894789488949894A894B894CCBFAEBF3894D894E894FCBDC89508951CBFE895289538954CCC189558956895789588959C8FB895A895B895C895D895E895FDCAA89608961896289638964CCEEDCAB89658966896789688969896A896B896C896D896E896F897089718972897389748975DBD38976DCAFDCAC8977BEB38978CAFB8979897A897BDCAD897C897D897E89808981898289838984C9CAC4B989858986898789888989C7BDDCAE898A898B898CD4F6D0E6898D898E898F89908991899289938994C4ABB6D589958996899789988999899A899B899C899D899E899F89A089A189A289A389A489A589A6DBD489A789A889A989AAB1DA89AB89AC89ADDBD589AE89AF89B089B189B289B389B489B589B689B789B8DBD689B989BA89BBBABE89BC89BD89BE89BF89C089C189C289C389C489C589C689C789C889C9C8C089CA89CB89CC89CD89CE89CFCABFC8C989D0D7B389D1C9F989D289D3BFC789D489D5BAF889D689D7D2BC89D889D989DA89DB89DC89DD89DE89DFE2BA89E0B4A689E189E2B1B889E389E489E589E689E7B8B489E8CFC489E989EA89EB89ECD9E7CFA6CDE289ED89EED9EDB6E089EFD2B989F089F1B9BB89F289F389F489F5E2B9E2B789F6B4F389F7CCECCCABB7F289F8D8B2D1EBBABB89F9CAA789FA89FBCDB789FC89FDD2C4BFE4BCD0B6E189FEDEC58A408A418A428A43DEC6DBBC8A44D1D98A458A46C6E6C4CEB7EE8A47B7DC8A488A49BFFCD7E08A4AC6F58A4B8A4CB1BCDEC8BDB1CCD7DECA8A4DDEC98A4E8A4F8A508A518A52B5EC8A53C9DD8A548A55B0C28A568A578A588A598A5A8A5B8A5C8A5D8A5E8A5F8A608A618A62C5AEC5AB8A63C4CC8A64BCE9CBFD8A658A668A67BAC38A688A698A6AE5F9C8E7E5FACDFD8A6BD7B1B8BEC2E88A6CC8D18A6D8A6EE5FB8A6F8A708A718A72B6CABCCB8A738A74D1FDE6A18A75C3EE8A768A778A788A79E6A48A7A8A7B8A7C8A7DE5FEE6A5CDD78A7E8A80B7C1E5FCE5FDE6A38A818A82C4DDE6A88A838A84E6A78A858A868A878A888A898A8AC3C38A8BC6DE8A8C8A8DE6AA8A8E8A8F8A908A918A928A938A94C4B78A958A968A97E6A2CABC8A988A998A9A8A9BBDE3B9C3E6A6D0D5CEAF8A9C8A9DE6A9E6B08A9ED2A68A9FBDAAE6AD8AA08AA18AA28AA38AA4E6AF8AA5C0D18AA68AA7D2CC8AA88AA98AAABCA78AAB8AAC8AAD8AAE8AAF8AB08AB18AB28AB38AB48AB58AB6E6B18AB7D2F68AB88AB98ABAD7CB8ABBCDFE8ABCCDDEC2A6E6ABE6ACBDBFE6AEE6B38ABD8ABEE6B28ABF8AC08AC18AC2E6B68AC3E6B88AC48AC58AC68AC7C4EF8AC88AC98ACAC4C88ACB8ACCBEEAC9EF8ACD8ACEE6B78ACFB6F08AD08AD18AD2C3E48AD38AD48AD58AD68AD78AD88AD9D3E9E6B48ADAE6B58ADBC8A28ADC8ADD8ADE8ADF8AE0E6BD8AE18AE28AE3E6B98AE48AE58AE68AE78AE8C6C58AE98AEACDF1E6BB8AEB8AEC8AED8AEE8AEF8AF08AF18AF28AF38AF4E6BC8AF58AF68AF78AF8BBE98AF98AFA8AFB8AFC8AFD8AFE8B40E6BE8B418B428B438B44E6BA8B458B46C0B78B478B488B498B4A8B4B8B4C8B4D8B4E8B4FD3A4E6BFC9F4E6C38B508B51E6C48B528B538B548B55D0F68B568B578B588B598B5A8B5B8B5C8B5D8B5E8B5F8B608B618B628B638B648B658B668B67C3BD8B688B698B6A8B6B8B6C8B6D8B6EC3C4E6C28B6F8B708B718B728B738B748B758B768B778B788B798B7A8B7B8B7CE6C18B7D8B7E8B808B818B828B838B84E6C7CFB18B85EBF48B868B87E6CA8B888B898B8A8B8B8B8CE6C58B8D8B8EBCDEC9A98B8F8B908B918B928B938B94BCB58B958B96CFD38B978B988B998B9A8B9BE6C88B9CE6C98B9DE6CE8B9EE6D08B9F8BA08BA1E6D18BA28BA38BA4E6CBB5D58BA5E6CC8BA68BA7E6CF8BA88BA9C4DB8BAAE6C68BAB8BAC8BAD8BAE8BAFE6CD8BB08BB18BB28BB38BB48BB58BB68BB78BB88BB98BBA8BBB8BBC8BBD8BBE8BBF8BC08BC18BC28BC38BC48BC58BC6E6D28BC78BC88BC98BCA8BCB8BCC8BCD8BCE8BCF8BD08BD18BD2E6D4E6D38BD38BD48BD58BD68BD78BD88BD98BDA8BDB8BDC8BDD8BDE8BDF8BE08BE18BE28BE38BE48BE58BE68BE78BE88BE98BEA8BEB8BECE6D58BEDD9F88BEE8BEFE6D68BF08BF18BF28BF38BF48BF58BF68BF7E6D78BF88BF98BFA8BFB8BFC8BFD8BFE8C408C418C428C438C448C458C468C47D7D3E6DD8C48E6DEBFD7D4D08C49D7D6B4E6CBEFE6DAD8C3D7CED0A28C4AC3CF8C4B8C4CE6DFBCBEB9C2E6DBD1A78C4D8C4EBAA2C2CF8C4FD8AB8C508C518C52CAEBE5EE8C53E6DC8C54B7F58C558C568C578C58C8E68C598C5AC4F58C5B8C5CE5B2C4FE8C5DCBFCE5B3D5AC8C5ED3EECAD8B0B28C5FCBCECDEA8C608C61BAEA8C628C638C64E5B58C65E5B48C66D7DAB9D9D6E6B6A8CDF0D2CBB1A6CAB58C67B3E8C9F3BFCDD0FBCAD2E5B6BBC28C688C698C6ACFDCB9AC8C6B8C6C8C6D8C6ED4D78C6F8C70BAA6D1E7CFFCBCD28C71E5B7C8DD8C728C738C74BFEDB1F6CBDE8C758C76BCC58C77BCC4D2FAC3DCBFDC8C788C798C7A8C7BB8BB8C7C8C7D8C7EC3C28C80BAAED4A28C818C828C838C848C858C868C878C888C89C7DEC4AFB2EC8C8AB9D18C8B8C8CE5BBC1C88C8D8C8ED5AF8C8F8C908C918C928C93E5BC8C94E5BE8C958C968C978C988C998C9A8C9BB4E7B6D4CBC2D1B0B5BC8C9C8C9DCAD98C9EB7E28C9F8CA0C9E48CA1BDAB8CA28CA3CEBED7F08CA48CA58CA68CA7D0A18CA8C9D98CA98CAAB6FBE6D8BCE28CABB3BE8CACC9D08CADE6D9B3A28CAE8CAF8CB08CB1DECC8CB2D3C8DECD8CB3D2A28CB48CB58CB68CB7DECE8CB88CB98CBA8CBBBECD8CBC8CBDDECF8CBE8CBF8CC0CAACD2FCB3DFE5EAC4E1BEA1CEB2C4F2BED6C6A8B2E38CC18CC2BED38CC38CC4C7FCCCEBBDECCEDD8CC58CC6CABAC6C1E5ECD0BC8CC78CC88CC9D5B98CCA8CCB8CCCE5ED8CCD8CCE8CCF8CD0CAF48CD1CDC0C2C58CD2E5EF8CD3C2C4E5F08CD48CD58CD68CD78CD88CD98CDAE5F8CDCD8CDBC9BD8CDC8CDD8CDE8CDF8CE08CE18CE2D2D9E1A88CE38CE48CE58CE6D3EC8CE7CBEAC6F18CE88CE98CEA8CEB8CECE1AC8CED8CEE8CEFE1A7E1A98CF08CF1E1AAE1AF8CF28CF3B2ED8CF4E1ABB8DAE1ADE1AEE1B0B5BAE1B18CF58CF68CF78CF88CF9E1B3E1B88CFA8CFB8CFC8CFD8CFED1D28D40E1B6E1B5C1EB8D418D428D43E1B78D44D4C08D45E1B28D46E1BAB0B68D478D488D498D4AE1B48D4BBFF98D4CE1B98D4D8D4EE1BB8D4F8D508D518D528D538D54E1BE8D558D568D578D588D598D5AE1BC8D5B8D5C8D5D8D5E8D5F8D60D6C58D618D628D638D648D658D668D67CFBF8D688D69E1BDE1BFC2CD8D6AB6EB8D6BD3F88D6C8D6DC7CD8D6E8D6FB7E58D708D718D728D738D748D758D768D778D788D79BEFE8D7A8D7B8D7C8D7D8D7E8D80E1C0E1C18D818D82E1C7B3E78D838D848D858D868D878D88C6E98D898D8A8D8B8D8C8D8DB4DE8D8ED1C28D8F8D908D918D92E1C88D938D94E1C68D958D968D978D988D99E1C58D9AE1C3E1C28D9BB1C08D9C8D9D8D9ED5B8E1C48D9F8DA08DA18DA28DA3E1CB8DA48DA58DA68DA78DA88DA98DAA8DABE1CCE1CA8DAC8DAD8DAE8DAF8DB08DB18DB28DB3EFFA8DB48DB5E1D3E1D2C7B68DB68DB78DB88DB98DBA8DBB8DBC8DBD8DBE8DBF8DC0E1C98DC18DC2E1CE8DC3E1D08DC48DC58DC68DC78DC88DC98DCA8DCB8DCC8DCD8DCEE1D48DCFE1D1E1CD8DD08DD1E1CF8DD28DD38DD48DD5E1D58DD68DD78DD88DD98DDA8DDB8DDC8DDD8DDE8DDF8DE08DE18DE2E1D68DE38DE48DE58DE68DE78DE88DE98DEA8DEB8DEC8DED8DEE8DEF8DF08DF18DF28DF38DF48DF58DF68DF78DF8E1D78DF98DFA8DFBE1D88DFC8DFD8DFE8E408E418E428E438E448E458E468E478E488E498E4A8E4B8E4C8E4D8E4E8E4F8E508E518E528E538E548E55E1DA8E568E578E588E598E5A8E5B8E5C8E5D8E5E8E5F8E608E618E62E1DB8E638E648E658E668E678E688E69CEA18E6A8E6B8E6C8E6D8E6E8E6F8E708E718E728E738E748E758E76E7DD8E77B4A8D6DD8E788E79D1B2B3B28E7A8E7BB9A4D7F3C7C9BEDEB9AE8E7CCED78E7D8E7EB2EEDBCF8E80BCBAD2D1CBC8B0CD8E818E82CFEF8E838E848E858E868E87D9E3BDED8E888E89B1D2CAD0B2BC8E8ACBA7B7AB8E8BCAA68E8C8E8D8E8ECFA38E8F8E90E0F8D5CAE0FB8E918E92E0FAC5C1CCFB8E93C1B1E0F9D6E3B2AFD6C4B5DB8E948E958E968E978E988E998E9A8E9BB4F8D6A18E9C8E9D8E9E8E9F8EA0CFAFB0EF8EA18EA2E0FC8EA38EA48EA58EA68EA7E1A1B3A38EA88EA9E0FDE0FEC3B18EAA8EAB8EAC8EADC3DD8EAEE1A2B7F98EAF8EB08EB18EB28EB38EB4BBCF8EB58EB68EB78EB88EB98EBA8EBBE1A3C4BB8EBC8EBD8EBE8EBF8EC0E1A48EC18EC2E1A58EC38EC4E1A6B4B18EC58EC68EC78EC88EC98ECA8ECB8ECC8ECD8ECE8ECF8ED08ED18ED28ED3B8C9C6BDC4EA8ED4B2A28ED5D0D28ED6E7DBBBC3D3D7D3C48ED7B9E3E2CF8ED88ED98EDAD7AF8EDBC7ECB1D38EDC8EDDB4B2E2D18EDE8EDF8EE0D0F2C2AEE2D08EE1BFE2D3A6B5D7E2D2B5EA8EE2C3EDB8FD8EE3B8AE8EE4C5D3B7CFE2D48EE58EE68EE78EE8E2D3B6C8D7F98EE98EEA8EEB8EEC8EEDCDA58EEE8EEF8EF08EF18EF2E2D88EF3E2D6CAFCBFB5D3B9E2D58EF48EF58EF68EF7E2D78EF88EF98EFA8EFB8EFC8EFD8EFE8F408F418F42C1AEC0C88F438F448F458F468F478F48E2DBE2DAC0AA8F498F4AC1CE8F4B8F4C8F4D8F4EE2DC8F4F8F508F518F528F538F548F558F568F578F588F598F5AE2DD8F5BE2DE8F5C8F5D8F5E8F5F8F608F618F628F638F64DBC88F65D1D3CDA28F668F67BDA88F688F698F6ADEC3D8A5BFAADBCDD2ECC6FAC5AA8F6B8F6C8F6DDEC48F6EB1D7DFAE8F6F8F708F71CABD8F72DFB18F73B9AD8F74D2FD8F75B8A5BAEB8F768F77B3DA8F788F798F7AB5DCD5C58F7B8F7C8F7D8F7EC3D6CFD2BBA18F80E5F3E5F28F818F82E5F48F83CDE48F84C8F58F858F868F878F888F898F8A8F8BB5AFC7BF8F8CE5F68F8D8F8E8F8FECB08F908F918F928F938F948F958F968F978F988F998F9A8F9B8F9C8F9D8F9EE5E68F9FB9E9B5B18FA0C2BCE5E8E5E7E5E98FA18FA28FA38FA4D2CD8FA58FA68FA7E1EAD0CE8FA8CDAE8FA9D1E58FAA8FABB2CAB1EB8FACB1F2C5ED8FAD8FAED5C3D3B08FAFE1DC8FB08FB18FB2E1DD8FB3D2DB8FB4B3B9B1CB8FB58FB68FB7CDF9D5F7E1DE8FB8BEB6B4FD8FB9E1DFBADCE1E0BBB2C2C9E1E18FBA8FBB8FBCD0EC8FBDCDBD8FBE8FBFE1E28FC0B5C3C5C7E1E38FC18FC2E1E48FC38FC48FC58FC6D3F98FC78FC88FC98FCA8FCB8FCCE1E58FCDD1AD8FCE8FCFE1E6CEA28FD08FD18FD28FD38FD48FD5E1E78FD6B5C28FD78FD88FD98FDAE1E8BBD58FDB8FDC8FDD8FDE8FDFD0C4E2E0B1D8D2E48FE08FE1E2E18FE28FE3BCC9C8CC8FE4E2E3ECFEECFDDFAF8FE58FE68FE7E2E2D6BECDFCC3A68FE88FE98FEAE3C38FEB8FECD6D2E2E78FED8FEEE2E88FEF8FF0D3C78FF18FF2E2ECBFEC8FF3E2EDE2E58FF48FF5B3C08FF68FF78FF8C4EE8FF98FFAE2EE8FFB8FFCD0C38FFDBAF6E2E9B7DEBBB3CCACCBCBE2E4E2E6E2EAE2EB8FFE90409041E2F790429043E2F4D4F5E2F390449045C5AD9046D5FAC5C2B2C090479048E2EF9049E2F2C1AFCBBC904A904BB5A1E2F9904C904D904EBCB1E2F1D0D4D4B9E2F5B9D6E2F6904F90509051C7D390529053905490559056E2F0905790589059905A905BD7DCEDA1905C905DE2F8905EEDA5E2FECAD1905F906090619062906390649065C1B59066BBD090679068BFD69069BAE3906A906BCBA1906C906D906EEDA6EDA3906F9070EDA29071907290739074BBD6EDA7D0F490759076EDA4BADEB6F7E3A1B6B2CCF1B9A79077CFA2C7A190789079BFD2907A907BB6F1907CE2FAE2FBE2FDE2FCC4D5E3A2907DD3C1907E90809081E3A7C7C49082908390849085CFA490869087E3A9BAB790889089908A908BE3A8908CBBDA908DE3A3908E908F9090E3A4E3AA9091E3A69092CEF2D3C690939094BBBC90959096D4C39097C4FA90989099EDA8D0FCE3A5909AC3F5909BE3ADB1AF909CE3B2909D909E909FBCC290A090A1E3ACB5BF90A290A390A490A590A690A790A890A9C7E9E3B090AA90AB90ACBEAACDEF90AD90AE90AF90B090B1BBF390B290B390B4CCE890B590B6E3AF90B7E3B190B8CFA7E3AE90B9CEA9BBDD90BA90BB90BC90BD90BEB5EBBEE5B2D2B3CD90BFB1B9E3ABB2D1B5ACB9DFB6E890C090C1CFEBE3B790C2BBCC90C390C4C8C7D0CA90C590C690C790C890C9E3B8B3EE90CA90CB90CC90CDEDA990CED3FAD3E490CF90D090D1EDAAE3B9D2E290D290D390D490D590D6E3B590D790D890D990DAD3DE90DB90DC90DD90DEB8D0E3B390DF90E0E3B6B7DF90E1E3B4C0A290E290E390E4E3BA90E590E690E790E890E990EA90EB90EC90ED90EE90EF90F090F190F290F390F490F590F690F7D4B890F890F990FA90FB90FC90FD90FE9140B4C89141E3BB9142BBC59143C9F791449145C9E5914691479148C4BD9149914A914B914C914D914E914FEDAB9150915191529153C2FD9154915591569157BBDBBFAE91589159915A915B915C915D915ECEBF915F916091619162E3BC9163BFB6916491659166916791689169916A916B916C916D916E916F9170917191729173917491759176B1EF91779178D4F79179917A917B917C917DE3BE917E9180918191829183918491859186EDAD918791889189918A918B918C918D918E918FE3BFBAA9EDAC91909191E3BD91929193919491959196919791989199919A919BE3C0919C919D919E919F91A091A1BAB691A291A391A4B6AE91A591A691A791A891A9D0B891AAB0C3EDAE91AB91AC91AD91AE91AFEDAFC0C191B0E3C191B191B291B391B491B591B691B791B891B991BA91BB91BC91BD91BE91BF91C091C1C5B391C291C391C491C591C691C791C891C991CA91CB91CC91CD91CE91CFE3C291D091D191D291D391D491D591D691D791D8DCB291D991DA91DB91DC91DD91DEEDB091DFB8EA91E0CEECEAA7D0E7CAF9C8D6CFB7B3C9CED2BDE491E191E2E3DEBBF2EAA8D5BD91E3C6DDEAA991E491E591E6EAAA91E7EAACEAAB91E8EAAEEAAD91E991EA91EB91ECBDD891EDEAAF91EEC2BE91EF91F091F191F2B4C1B4F791F391F4BBA791F591F691F791F891F9ECE6ECE5B7BFCBF9B1E291FAECE791FB91FC91FDC9C8ECE8ECE991FECAD6DED0B2C5D4FA92409241C6CBB0C7B4F2C8D3924292439244CDD092459246BFB8924792489249924A924B924C924DBFDB924E924FC7A4D6B49250C0A9DED1C9A8D1EFC5A4B0E7B3B6C8C592519252B0E292539254B7F692559256C5FA92579258B6F39259D5D2B3D0BCBC925A925B925CB3AD925D925E925F9260BEF1B0D1926192629263926492659266D2D6CAE3D7A59267CDB6B6B6BFB9D5DB9268B8A7C5D79269926A926BDED2BFD9C2D5C7C0926CBBA4B1A8926D926EC5EA926F9270C5FBCCA79271927292739274B1A7927592769277B5D692789279927AC4A8927BDED3D1BAB3E9927CC3F2927D927EB7F79280D6F4B5A3B2F0C4B4C4E9C0ADDED49281B0E8C5C4C1E09282B9D59283BEDCCDD8B0CE9284CDCFDED6BED0D7BEDED5D5D0B0DD92859286C4E292879288C2A3BCF09289D3B5C0B9C5A1B2A6D4F1928A928BC0A8CAC3DED7D5FC928CB9B0928DC8ADCBA9928EDED9BFBD928F929092919292C6B4D7A7CAB0C4C39293B3D6B9D29294929592969297D6B8EAFCB0B492989299929A929BBFE6929C929DCCF4929E929F92A092A1CDDA92A292A392A4D6BFC2CE92A5CECECCA2D0AEC4D3B5B2DED8D5F5BCB7BBD392A692A7B0A492A8C5B2B4EC92A992AA92ABD5F192AC92ADEAFD92AE92AF92B092B192B292B3DEDACDA692B492B5CDEC92B692B792B892B9CEE6DEDC92BACDB1C0A692BB92BCD7BD92BDDEDBB0C6BAB4C9D3C4F3BEE892BE92BF92C092C1B2B692C292C392C492C592C692C792C892C9C0CCCBF092CABCF1BBBBB5B792CB92CC92CDC5F592CEDEE692CF92D092D1DEE3BEDD92D292D3DEDF92D492D592D692D7B4B7BDDD92D892D9DEE0C4ED92DA92DB92DC92DDCFC692DEB5E092DF92E092E192E2B6DECADAB5F4DEE592E3D5C692E4DEE1CCCDC6FE92E5C5C592E692E792E8D2B492E9BEF292EA92EB92EC92ED92EE92EF92F0C2D392F1CCBDB3B892F2BDD392F3BFD8CDC6D1DAB4EB92F4DEE4DEDDDEE792F5EAFE92F692F7C2B0DEE292F892F9D6C0B5A792FAB2F492FBDEE892FCDEF292FD92FE934093419342DEED9343DEF193449345C8E0934693479348D7E1DEEFC3E8CCE19349B2E5934A934B934CD2BE934D934E934F9350935193529353DEEE9354DEEBCED59355B4A79356935793589359935ABFABBEBE935B935CBDD2935D935E935F9360DEE99361D4AE9362DEDE9363DEEA9364936593669367C0BF9368DEECB2F3B8E9C2A79369936ABDC1936B936C936D936E936FDEF5DEF893709371B2ABB4A493729373B4EAC9A6937493759376937793789379DEF6CBD1937AB8E3937BDEF7DEFA937C937D937E9380DEF9938193829383CCC29384B0E1B4EE93859386938793889389938AE5BA938B938C938D938E938FD0AF93909391B2EB9392EBA19393DEF493949395C9E3DEF3B0DAD2A1B1F79396CCAF939793989399939A939B939C939DDEF0939ECBA4939F93A093A1D5AA93A293A393A493A593A6DEFB93A793A893A993AA93AB93AC93AD93AEB4DD93AFC4A693B093B193B2DEFD93B393B493B593B693B793B893B993BA93BB93BCC3FEC4A1DFA193BD93BE93BF93C093C193C293C3C1CC93C4DEFCBEEF93C5C6B293C693C793C893C993CA93CB93CC93CD93CEB3C5C8F693CF93D0CBBADEFE93D193D2DFA493D393D493D593D6D7B293D793D893D993DA93DBB3B793DC93DD93DE93DFC1C393E093E1C7CBB2A5B4E993E2D7AB93E393E493E593E6C4EC93E7DFA2DFA393E8DFA593E9BAB393EA93EB93ECDFA693EDC0DE93EE93EFC9C393F093F193F293F393F493F593F6B2D9C7E693F7DFA793F8C7DC93F993FA93FB93FCDFA8EBA293FD93FE944094419442CBD3944394449445DFAA9446DFA99447B2C194489449944A944B944C944D944E944F9450945194529453945494559456945794589459945A945B945C945D945E945F9460C5CA94619462946394649465946694679468DFAB9469946A946B946C946D946E946F9470D4DC94719472947394749475C8C19476947794789479947A947B947C947D947E948094819482DFAC94839484948594869487BEF094889489DFADD6A7948A948B948C948DEAB7EBB6CAD5948ED8FCB8C4948FB9A594909491B7C5D5FE94929493949494959496B9CA94979498D0A7F4CD9499949AB5D0949B949CC3F4949DBEC8949E949F94A0EBB7B0BD94A194A2BDCC94A3C1B294A4B1D6B3A894A594A694A7B8D2C9A294A894A9B6D894AA94AB94AC94ADEBB8BEB494AE94AF94B0CAFD94B1C7C394B2D5FB94B394B4B7F394B594B694B794B894B994BA94BB94BC94BD94BE94BF94C094C194C294C3CEC494C494C594C6D5ABB1F394C794C894C9ECB3B0DF94CAECB594CB94CC94CDB6B794CEC1CF94CFF5FAD0B194D094D1D5E594D2CED394D394D4BDEFB3E294D5B8AB94D6D5B694D7EDBD94D8B6CF94D9CBB9D0C294DA94DB94DC94DD94DE94DF94E094E1B7BD94E294E3ECB6CAA994E494E594E6C5D494E7ECB9ECB8C2C3ECB794E894E994EA94EBD0FDECBA94ECECBBD7E594ED94EEECBC94EF94F094F1ECBDC6EC94F294F394F494F594F694F794F894F9CEDE94FABCC894FB94FCC8D5B5A9BEC9D6BCD4E794FD94FED1AED0F1EAB8EAB9EABABAB59540954195429543CAB1BFF595449545CDFA9546954795489549954AEAC0954BB0BAEABE954C954DC0A5954E954F9550EABB9551B2FD9552C3F7BBE8955395549555D2D7CEF4EABF955695579558EABC9559955A955BEAC3955CD0C7D3B3955D955E955F9560B4BA9561C3C1D7F29562956395649565D5D19566CAC79567EAC595689569EAC4EAC7EAC6956A956B956C956D956ED6E7956FCFD495709571EACB9572BBCE9573957495759576957795789579BDFAC9CE957A957BEACC957C957DC9B9CFFEEACAD4CEEACDEACF957E9580CDED9581958295839584EAC99585EACE95869587CEEE9588BBDE9589B3BF958A958B958C958D958EC6D5BEB0CEFA958F95909591C7E79592BEA7EAD095939594D6C7959595969597C1C095989599959AD4DD959BEAD1959C959DCFBE959E959F95A095A1EAD295A295A395A495A5CAEE95A695A795A895A9C5AFB0B595AA95AB95AC95AD95AEEAD495AF95B095B195B295B395B495B595B695B7EAD3F4DF95B895B995BA95BB95BCC4BA95BD95BE95BF95C095C1B1A995C295C395C495C5E5DF95C695C795C895C9EAD595CA95CB95CC95CD95CE95CF95D095D195D295D395D495D595D695D795D895D995DA95DB95DC95DD95DE95DF95E095E195E295E3CAEF95E4EAD6EAD7C6D895E595E695E795E895E995EA95EB95ECEAD895ED95EEEAD995EF95F095F195F295F395F4D4BB95F5C7FAD2B7B8FC95F695F7EAC295F8B2DC95F995FAC2FC95FBD4F8CCE6D7EE95FC95FD95FE9640964196429643D4C2D3D0EBC3C5F39644B7FE96459646EBD4964796489649CBB7EBDE964AC0CA964B964C964DCDFB964EB3AF964FC6DA965096519652965396549655EBFC9656C4BE9657CEB4C4A9B1BED4FD9658CAF59659D6EC965A965BC6D3B6E4965C965D965E965FBBFA96609661D0E096629663C9B19664D4D3C8A896659666B8CB9667E8BEC9BC96689669E8BB966AC0EED0D3B2C4B4E5966BE8BC966C966DD5C8966E966F967096719672B6C59673E8BDCAF8B8DCCCF5967496759676C0B496779678D1EEE8BFE8C29679967ABABC967BB1ADBDDC967CEABDE8C3967DE8C6967EE8CB9680968196829683E8CC9684CBC9B0E59685BCAB96869687B9B996889689E8C1968ACDF7968BE8CA968C968D968E968FCEF69690969196929693D5ED9694C1D6E8C49695C3B69696B9FBD6A6E8C8969796989699CAE0D4E6969AE8C0969BE8C5E8C7969CC7B9B7E3969DE8C9969EBFDDE8D2969F96A0E8D796A1E8D5BCDCBCCFE8DB96A296A396A496A596A696A796A896A9E8DE96AAE8DAB1FA96AB96AC96AD96AE96AF96B096B196B296B396B4B0D8C4B3B8CCC6E2C8BEC8E196B596B696B7E8CFE8D4E8D696B8B9F1E8D8D7F596B9C4FB96BAE8DC96BB96BCB2E996BD96BE96BFE8D196C096C1BCED96C296C3BFC2E8CDD6F996C4C1F8B2F196C596C696C796C896C996CA96CB96CCE8DF96CDCAC1E8D996CE96CF96D096D1D5A496D2B1EAD5BBE8CEE8D0B6B0E8D396D3E8DDC0B896D4CAF796D5CBA896D696D7C6DCC0F596D896D996DA96DB96DCE8E996DD96DE96DFD0A396E096E196E296E396E496E596E6E8F2D6EA96E796E896E996EA96EB96EC96EDE8E0E8E196EE96EF96F0D1F9BACBB8F996F196F2B8F1D4D4E8EF96F3E8EEE8ECB9F0CCD2E8E6CEA6BFF296F4B0B8E8F1E8F096F5D7C096F6E8E496F7CDA9C9A396F8BBB8BDDBE8EA96F996FA96FB96FC96FD96FE9740974197429743E8E2E8E3E8E5B5B5E8E7C7C5E8EBE8EDBDB0D7AE9744E8F897459746974797489749974A974B974CE8F5974DCDB0E8F6974E974F9750975197529753975497559756C1BA9757E8E89758C3B7B0F09759975A975B975C975D975E975F9760E8F4976197629763E8F7976497659766B9A3976797689769976A976B976C976D976E976F9770C9D2977197729773C3CECEE0C0E69774977597769777CBF39778CCDDD0B59779977ACAE1977BE8F3977C977D977E9780978197829783978497859786BCEC9787E8F997889789978A978B978C978DC3DE978EC6E5978FB9F79790979197929793B0F497949795D7D897969797BCAC9798C5EF9799979A979B979C979DCCC4979E979FE9A697A097A197A297A397A497A597A697A797A897A9C9AD97AAE9A2C0E297AB97AC97ADBFC397AE97AF97B0E8FEB9D797B1E8FB97B297B397B497B5E9A497B697B797B8D2CE97B997BA97BB97BC97BDE9A397BED6B2D7B597BFE9A797C0BDB797C197C297C397C497C597C697C797C897C997CA97CB97CCE8FCE8FD97CD97CE97CFE9A197D097D197D297D397D497D597D697D7CDD697D897D9D2AC97DA97DB97DCE9B297DD97DE97DF97E0E9A997E197E297E3B4AA97E4B4BB97E597E6E9AB97E797E897E997EA97EB97EC97ED97EE97EF97F097F197F297F397F497F597F697F7D0A897F897F9E9A597FA97FBB3FE97FC97FDE9ACC0E397FEE9AA98409841E9B998429843E9B89844984598469847E9AE98489849E8FA984A984BE9A8984C984D984E984F9850BFACE9B1E9BA98519852C2A5985398549855E9AF9856B8C59857E9AD9858D3DCE9B4E9B5E9B79859985A985BE9C7985C985D985E985F98609861C0C6E9C598629863E9B098649865E9BBB0F19866986798689869986A986B986C986D986E986FE9BCD5A598709871E9BE9872E9BF987398749875E9C198769877C1F198789879C8B6987A987B987CE9BD987D987E988098819882E9C29883988498859886988798889889988AE9C3988BE9B3988CE9B6988DBBB1988E988F9890E9C0989198929893989498959896BCF7989798989899E9C4E9C6989A989B989C989D989E989F98A098A198A298A398A498A5E9CA98A698A798A898A9E9CE98AA98AB98AC98AD98AE98AF98B098B198B298B3B2DB98B4E9C898B598B698B798B898B998BA98BB98BC98BD98BEB7AE98BF98C098C198C298C398C498C598C698C798C898C998CAE9CBE9CC98CB98CC98CD98CE98CF98D0D5C198D1C4A398D298D398D498D598D698D7E9D898D8BAE198D998DA98DB98DCE9C998DDD3A398DE98DF98E0E9D498E198E298E398E498E598E698E7E9D7E9D098E898E998EA98EB98ECE9CF98ED98EEC7C198EF98F098F198F298F398F498F598F6E9D298F798F898F998FA98FB98FC98FDE9D9B3C898FEE9D399409941994299439944CFF0994599469947E9CD99489949994A994B994C994D994E994F995099519952B3F79953995499559956995799589959E9D6995A995BE9DA995C995D995ECCB4995F99609961CFAD99629963996499659966996799689969996AE9D5996BE9DCE9DB996C996D996E996F9970E9DE99719972997399749975997699779978E9D19979997A997B997C997D997E99809981E9DD9982E9DFC3CA9983998499859986998799889989998A998B998C998D998E998F9990999199929993999499959996999799989999999A999B999C999D999E999F99A099A199A299A399A499A599A699A799A899A999AA99AB99AC99AD99AE99AF99B099B199B299B399B499B599B699B799B899B999BA99BB99BC99BD99BE99BF99C099C199C299C399C499C599C699C799C899C999CA99CB99CC99CD99CE99CF99D099D199D299D399D499D599D699D799D899D999DA99DB99DC99DD99DE99DF99E099E199E299E399E499E599E699E799E899E999EA99EB99EC99ED99EE99EF99F099F199F299F399F499F5C7B7B4CEBBB6D0C0ECA399F699F7C5B799F899F999FA99FB99FC99FD99FE9A409A419A42D3FB9A439A449A459A46ECA49A47ECA5C6DB9A489A499A4ABFEE9A4B9A4C9A4D9A4EECA69A4F9A50ECA7D0AA9A51C7B89A529A53B8E89A549A559A569A579A589A599A5A9A5B9A5C9A5D9A5E9A5FECA89A609A619A629A639A649A659A669A67D6B9D5FDB4CBB2BDCEE4C6E79A689A69CDE19A6A9A6B9A6C9A6D9A6E9A6F9A709A719A729A739A749A759A769A77B4F59A78CBC0BCDF9A799A7A9A7B9A7CE9E2E9E3D1EAE9E59A7DB4F9E9E49A7ED1B3CAE2B2D09A80E9E89A819A829A839A84E9E6E9E79A859A86D6B39A879A889A89E9E9E9EA9A8A9A8B9A8C9A8D9A8EE9EB9A8F9A909A919A929A939A949A959A96E9EC9A979A989A999A9A9A9B9A9C9A9D9A9EECAFC5B9B6CE9A9FD2F39AA09AA19AA29AA39AA49AA59AA6B5EE9AA7BBD9ECB19AA89AA9D2E39AAA9AAB9AAC9AAD9AAECEE39AAFC4B89AB0C3BF9AB19AB2B6BED8B9B1C8B1CFB1D1C5FE9AB3B1D09AB4C3AB9AB59AB69AB79AB89AB9D5B19ABA9ABB9ABC9ABD9ABE9ABF9AC09AC1EBA4BAC19AC29AC39AC4CCBA9AC59AC69AC7EBA59AC8EBA79AC99ACA9ACBEBA89ACC9ACD9ACEEBA69ACF9AD09AD19AD29AD39AD49AD5EBA9EBABEBAA9AD69AD79AD89AD99ADAEBAC9ADBCACFD8B5C3F19ADCC3A5C6F8EBADC4CA9ADDEBAEEBAFEBB0B7D59ADE9ADF9AE0B7FA9AE1EBB1C7E29AE2EBB39AE3BAA4D1F5B0B1EBB2EBB49AE49AE59AE6B5AAC2C8C7E89AE7EBB59AE8CBAEE3DF9AE99AEAD3C09AEB9AEC9AED9AEED9DB9AEF9AF0CDA1D6ADC7F39AF19AF29AF3D9E0BBE39AF4BABAE3E29AF59AF69AF79AF89AF9CFAB9AFA9AFB9AFCE3E0C9C79AFDBAB99AFE9B409B41D1B4E3E1C8EAB9AFBDADB3D8CEDB9B429B43CCC09B449B459B46E3E8E3E9CDF49B479B489B499B4A9B4BCCAD9B4CBCB39B4DE3EA9B4EE3EB9B4F9B50D0DA9B519B529B53C6FBB7DA9B549B55C7DFD2CACED69B56E3E4E3EC9B57C9F2B3C19B589B59E3E79B5A9B5BC6E3E3E59B5C9B5DEDB3E3E69B5E9B5F9B609B61C9B39B62C5E69B639B649B65B9B59B66C3BB9B67E3E3C5BDC1A4C2D9B2D79B68E3EDBBA6C4AD9B69E3F0BEDA9B6A9B6BE3FBE3F5BAD39B6C9B6D9B6E9B6FB7D0D3CD9B70D6CED5D3B9C1D5B4D1D89B719B729B739B74D0B9C7F69B759B769B77C8AAB2B49B78C3DA9B799B7A9B7BE3EE9B7C9B7DE3FCE3EFB7A8E3F7E3F49B7E9B809B81B7BA9B829B83C5A29B84E3F6C5DDB2A8C6FC9B85C4E09B869B87D7A29B88C0E1E3F99B899B8AE3FAE3FDCCA9E3F39B8BD3BE9B8CB1C3EDB4E3F1E3F29B8DE3F8D0BAC6C3D4F3E3FE9B8E9B8FBDE09B909B91E4A79B929B93E4A69B949B959B96D1F3E4A39B97E4A99B989B999B9AC8F79B9B9B9C9B9D9B9ECFB49B9FE4A8E4AEC2E59BA09BA1B6B49BA29BA39BA49BA59BA69BA7BDF29BA8E4A29BA99BAABAE9E4AA9BAB9BACE4AC9BAD9BAEB6FDD6DEE4B29BAFE4AD9BB09BB19BB2E4A19BB3BBEECDDDC7A2C5C99BB49BB5C1F79BB6E4A49BB7C7B3BDACBDBDE4A59BB8D7C7B2E29BB9E4ABBCC3E4AF9BBABBEBE4B0C5A8E4B19BBB9BBC9BBD9BBED5E3BFA39BBFE4BA9BC0E4B79BC1E4BB9BC29BC3E4BD9BC49BC5C6D69BC69BC7BAC6C0CB9BC89BC99BCAB8A1E4B49BCB9BCC9BCD9BCED4A19BCF9BD0BAA3BDFE9BD19BD29BD3E4BC9BD49BD59BD69BD79BD8CDBF9BD99BDAC4F99BDB9BDCCFFBC9E69BDD9BDED3BF9BDFCFD19BE09BE1E4B39BE2E4B8E4B9CCE99BE39BE49BE59BE69BE7CCCE9BE8C0D4E4B5C1B0E4B6CED09BE9BBC1B5D39BEAC8F3BDA7D5C7C9ACB8A2E4CA9BEB9BECE4CCD1C49BED9BEED2BA9BEF9BF0BAAD9BF19BF2BAD49BF39BF49BF59BF69BF79BF8E4C3B5ED9BF99BFA9BFBD7CDE4C0CFFDE4BF9BFC9BFD9BFEC1DCCCCA9C409C419C429C43CAE79C449C459C469C47C4D79C48CCD4E4C89C499C4A9C4BE4C7E4C19C4CE4C4B5AD9C4D9C4ED3D99C4FE4C69C509C519C529C53D2F9B4E39C54BBB49C559C56C9EE9C57B4BE9C589C599C5ABBEC9C5BD1CD9C5CCCEDEDB59C5D9C5E9C5F9C609C619C629C639C64C7E59C659C669C679C68D4A89C69E4CBD7D5E4C29C6ABDA5E4C59C6B9C6CD3E69C6DE4C9C9F89C6E9C6FE4BE9C709C71D3E59C729C73C7FEB6C99C74D4FCB2B3E4D79C759C769C77CEC29C78E4CD9C79CEBC9C7AB8DB9C7B9C7CE4D69C7DBFCA9C7E9C809C81D3CE9C82C3EC9C839C849C859C869C879C889C899C8AC5C8E4D89C8B9C8C9C8D9C8E9C8F9C909C919C92CDC4E4CF9C939C949C959C96E4D4E4D59C97BAFE9C98CFE69C999C9AD5BF9C9B9C9C9C9DE4D29C9E9C9F9CA09CA19CA29CA39CA49CA59CA69CA79CA8E4D09CA99CAAE4CE9CAB9CAC9CAD9CAE9CAF9CB09CB19CB29CB39CB49CB59CB69CB79CB89CB9CDE5CAAA9CBA9CBB9CBCC0A39CBDBDA6E4D39CBE9CBFB8C89CC09CC19CC29CC39CC4E4E7D4B49CC59CC69CC79CC89CC99CCA9CCBE4DB9CCC9CCD9CCEC1EF9CCF9CD0E4E99CD19CD2D2E79CD39CD4E4DF9CD5E4E09CD69CD7CFAA9CD89CD99CDA9CDBCBDD9CDCE4DAE4D19CDDE4E59CDEC8DCE4E39CDF9CE0C4E7E4E29CE1E4E19CE29CE39CE4B3FCE4E89CE59CE69CE79CE8B5E19CE99CEA9CEBD7CC9CEC9CED9CEEE4E69CEFBBAC9CF0D7D2CCCFEBF89CF1E4E49CF29CF3B9F69CF49CF59CF6D6CDE4D9E4DCC2FAE4DE9CF7C2CBC0C4C2D09CF8B1F5CCB29CF99CFA9CFB9CFC9CFD9CFE9D409D419D429D43B5CE9D449D459D469D47E4EF9D489D499D4A9D4B9D4C9D4D9D4E9D4FC6AF9D509D519D52C6E19D539D54E4F59D559D569D579D589D59C2A99D5A9D5B9D5CC0ECD1DDE4EE9D5D9D5E9D5F9D609D619D629D639D649D659D66C4AE9D679D689D69E4ED9D6A9D6B9D6C9D6DE4F6E4F4C2FE9D6EE4DD9D6FE4F09D70CAFE9D71D5C49D729D73E4F19D749D759D769D779D789D799D7AD1FA9D7B9D7C9D7D9D7E9D809D819D82E4EBE4EC9D839D849D85E4F29D86CEAB9D879D889D899D8A9D8B9D8C9D8D9D8E9D8F9D90C5CB9D919D929D93C7B19D94C2BA9D959D969D97E4EA9D989D999D9AC1CA9D9B9D9C9D9D9D9E9D9F9DA0CCB6B3B19DA19DA29DA3E4FB9DA4E4F39DA59DA69DA7E4FA9DA8E4FD9DA9E4FC9DAA9DAB9DAC9DAD9DAE9DAF9DB0B3CE9DB19DB29DB3B3BAE4F79DB49DB5E4F9E4F8C5EC9DB69DB79DB89DB99DBA9DBB9DBC9DBD9DBE9DBF9DC09DC19DC2C0BD9DC39DC49DC59DC6D4E89DC79DC89DC99DCA9DCBE5A29DCC9DCD9DCE9DCF9DD09DD19DD29DD39DD49DD59DD6B0C49DD79DD8E5A49DD99DDAE5A39DDB9DDC9DDD9DDE9DDF9DE0BCA49DE1E5A59DE29DE39DE49DE59DE69DE7E5A19DE89DE99DEA9DEB9DEC9DED9DEEE4FEB1F49DEF9DF09DF19DF29DF39DF49DF59DF69DF79DF89DF9E5A89DFAE5A9E5A69DFB9DFC9DFD9DFE9E409E419E429E439E449E459E469E47E5A7E5AA9E489E499E4A9E4B9E4C9E4D9E4E9E4F9E509E519E529E539E549E559E569E579E589E599E5A9E5B9E5C9E5D9E5E9E5F9E609E619E629E639E649E659E669E679E68C6D99E699E6A9E6B9E6C9E6D9E6E9E6F9E70E5ABE5AD9E719E729E739E749E759E769E77E5AC9E789E799E7A9E7B9E7C9E7D9E7E9E809E819E829E839E849E859E869E879E889E89E5AF9E8A9E8B9E8CE5AE9E8D9E8E9E8F9E909E919E929E939E949E959E969E979E989E999E9A9E9B9E9C9E9D9E9EB9E09E9F9EA0E5B09EA19EA29EA39EA49EA59EA69EA79EA89EA99EAA9EAB9EAC9EAD9EAEE5B19EAF9EB09EB19EB29EB39EB49EB59EB69EB79EB89EB99EBABBF0ECE1C3F09EBBB5C6BBD29EBC9EBD9EBE9EBFC1E9D4EE9EC0BEC49EC19EC29EC3D7C69EC4D4D6B2D3ECBE9EC59EC69EC79EC8EAC19EC99ECA9ECBC2AFB4B69ECC9ECD9ECED1D79ECF9ED09ED1B3B49ED2C8B2BFBBECC09ED39ED4D6CB9ED59ED6ECBFECC19ED79ED89ED99EDA9EDB9EDC9EDD9EDE9EDF9EE09EE19EE29EE3ECC5BEE6CCBFC5DABEBC9EE4ECC69EE5B1FE9EE69EE79EE8ECC4D5A8B5E39EE9ECC2C1B6B3E39EEA9EEBECC3CBB8C0C3CCFE9EEC9EED9EEE9EEFC1D29EF0ECC89EF19EF29EF39EF49EF59EF69EF79EF89EF99EFA9EFB9EFC9EFDBAE6C0D39EFED6F29F409F419F42D1CC9F439F449F459F46BFBE9F47B7B3C9D5ECC7BBE29F48CCCCBDFDC8C89F49CFA99F4A9F4B9F4C9F4D9F4E9F4F9F50CDE99F51C5EB9F529F539F54B7E99F559F569F579F589F599F5A9F5B9F5C9F5D9F5E9F5FD1C9BAB89F609F619F629F639F64ECC99F659F66ECCA9F67BBC0ECCB9F68ECE2B1BAB7D99F699F6A9F6B9F6C9F6D9F6E9F6F9F709F719F729F73BDB99F749F759F769F779F789F799F7A9F7BECCCD1E6ECCD9F7C9F7D9F7E9F80C8BB9F819F829F839F849F859F869F879F889F899F8A9F8B9F8C9F8D9F8EECD19F8F9F909F919F92ECD39F93BBCD9F94BCE59F959F969F979F989F999F9A9F9B9F9C9F9D9F9E9F9F9FA09FA1ECCF9FA2C9B79FA39FA49FA59FA69FA7C3BA9FA8ECE3D5D5ECD09FA99FAA9FAB9FAC9FADD6F39FAE9FAF9FB0ECD2ECCE9FB19FB29FB39FB4ECD49FB5ECD59FB69FB7C9BF9FB89FB99FBA9FBB9FBC9FBDCFA89FBE9FBF9FC09FC19FC2D0DC9FC39FC49FC59FC6D1AC9FC79FC89FC99FCAC8DB9FCB9FCC9FCDECD6CEF59FCE9FCF9FD09FD19FD2CAECECDA9FD39FD49FD59FD69FD79FD89FD9ECD99FDA9FDB9FDCB0BE9FDD9FDE9FDF9FE09FE19FE2ECD79FE3ECD89FE49FE59FE6ECE49FE79FE89FE99FEA9FEB9FEC9FED9FEE9FEFC8BC9FF09FF19FF29FF39FF49FF59FF69FF79FF89FF9C1C79FFA9FFB9FFC9FFD9FFEECDCD1E0A040A041A042A043A044A045A046A047A048A049ECDBA04AA04BA04CA04DD4EFA04EECDDA04FA050A051A052A053A054DBC6A055A056A057A058A059A05AA05BA05CA05DA05EECDEA05FA060A061A062A063A064A065A066A067A068A069A06AB1ACA06BA06CA06DA06EA06FA070A071A072A073A074A075A076A077A078A079A07AA07BA07CA07DA07EA080A081ECDFA082A083A084A085A086A087A088A089A08AA08BECE0A08CD7A6A08DC5C0A08EA08FA090EBBCB0AEA091A092A093BEF4B8B8D2AFB0D6B5F9A094D8B3A095CBACA096E3DDA097A098A099A09AA09BA09CA09DC6ACB0E6A09EA09FA0A0C5C6EBB9A0A1A0A2A0A3A0A4EBBAA0A5A0A6A0A7EBBBA0A8A0A9D1C0A0AAC5A3A0ABEAF2A0ACC4B2A0ADC4B5C0CEA0AEA0AFA0B0EAF3C4C1A0B1CEEFA0B2A0B3A0B4A0B5EAF0EAF4A0B6A0B7C9FCA0B8A0B9C7A3A0BAA0BBA0BCCCD8CEFEA0BDA0BEA0BFEAF5EAF6CFACC0E7A0C0A0C1EAF7A0C2A0C3A0C4A0C5A0C6B6BFEAF8A0C7EAF9A0C8EAFAA0C9A0CAEAFBA0CBA0CCA0CDA0CEA0CFA0D0A0D1A0D2A0D3A0D4A0D5A0D6EAF1A0D7A0D8A0D9A0DAA0DBA0DCA0DDA0DEA0DFA0E0A0E1A0E2C8AEE1EBA0E3B7B8E1ECA0E4A0E5A0E6E1EDA0E7D7B4E1EEE1EFD3CCA0E8A0E9A0EAA0EBA0ECA0EDA0EEE1F1BFF1E1F0B5D2A0EFA0F0A0F1B1B7A0F2A0F3A0F4A0F5E1F3E1F2A0F6BAFCA0F7E1F4A0F8A0F9A0FAA0FBB9B7A0FCBED1A0FDA0FEAA40AA41C4FCAA42BADDBDC6AA43AA44AA45AA46AA47AA48E1F5E1F7AA49AA4AB6C0CFC1CAA8E1F6D5F8D3FCE1F8E1FCE1F9AA4BAA4CE1FAC0EAAA4DE1FEE2A1C0C7AA4EAA4FAA50AA51E1FBAA52E1FDAA53AA54AA55AA56AA57AA58E2A5AA59AA5AAA5BC1D4AA5CAA5DAA5EAA5FE2A3AA60E2A8B2FEE2A2AA61AA62AA63C3CDB2C2E2A7E2A6AA64AA65E2A4E2A9AA66AA67E2ABAA68AA69AA6AD0C9D6EDC3A8E2ACAA6BCFD7AA6CAA6DE2AEAA6EAA6FBAEFAA70AA71E9E0E2ADE2AAAA72AA73AA74AA75BBABD4B3AA76AA77AA78AA79AA7AAA7BAA7CAA7DAA7EAA80AA81AA82AA83E2B0AA84AA85E2AFAA86E9E1AA87AA88AA89AA8AE2B1AA8BAA8CAA8DAA8EAA8FAA90AA91AA92E2B2AA93AA94AA95AA96AA97AA98AA99AA9AAA9BAA9CAA9DE2B3CCA1AA9EE2B4AA9FAAA0AB40AB41AB42AB43AB44AB45AB46AB47AB48AB49AB4AAB4BE2B5AB4CAB4DAB4EAB4FAB50D0FEAB51AB52C2CAAB53D3F1AB54CDF5AB55AB56E7E0AB57AB58E7E1AB59AB5AAB5BAB5CBEC1AB5DAB5EAB5FAB60C2EAAB61AB62AB63E7E4AB64AB65E7E3AB66AB67AB68AB69AB6AAB6BCDE6AB6CC3B5AB6DAB6EE7E2BBB7CFD6AB6FC1E1E7E9AB70AB71AB72E7E8AB73AB74E7F4B2A3AB75AB76AB77AB78E7EAAB79E7E6AB7AAB7BAB7CAB7DAB7EE7ECE7EBC9BAAB80AB81D5E4AB82E7E5B7A9E7E7AB83AB84AB85AB86AB87AB88AB89E7EEAB8AAB8BAB8CAB8DE7F3AB8ED6E9AB8FAB90AB91AB92E7EDAB93E7F2AB94E7F1AB95AB96AB97B0E0AB98AB99AB9AAB9BE7F5AB9CAB9DAB9EAB9FABA0AC40AC41AC42AC43AC44AC45AC46AC47AC48AC49AC4AC7F2AC4BC0C5C0EDAC4CAC4DC1F0E7F0AC4EAC4FAC50AC51E7F6CBF6AC52AC53AC54AC55AC56AC57AC58AC59AC5AE8A2E8A1AC5BAC5CAC5DAC5EAC5FAC60D7C1AC61AC62E7FAE7F9AC63E7FBAC64E7F7AC65E7FEAC66E7FDAC67E7FCAC68AC69C1D5C7D9C5FDC5C3AC6AAC6BAC6CAC6DAC6EC7EDAC6FAC70AC71AC72E8A3AC73AC74AC75AC76AC77AC78AC79AC7AAC7BAC7CAC7DAC7EAC80AC81AC82AC83AC84AC85AC86E8A6AC87E8A5AC88E8A7BAF7E7F8E8A4AC89C8F0C9AAAC8AAC8BAC8CAC8DAC8EAC8FAC90AC91AC92AC93AC94AC95AC96E8A9AC97AC98B9E5AC99AC9AAC9BAC9CAC9DD1FEE8A8AC9EAC9FACA0AD40AD41AD42E8AAAD43E8ADE8AEAD44C1A7AD45AD46AD47E8AFAD48AD49AD4AE8B0AD4BAD4CE8ACAD4DE8B4AD4EAD4FAD50AD51AD52AD53AD54AD55AD56AD57AD58E8ABAD59E8B1AD5AAD5BAD5CAD5DAD5EAD5FAD60AD61E8B5E8B2E8B3AD62AD63AD64AD65AD66AD67AD68AD69AD6AAD6BAD6CAD6DAD6EAD6FAD70AD71E8B7AD72AD73AD74AD75AD76AD77AD78AD79AD7AAD7BAD7CAD7DAD7EAD80AD81AD82AD83AD84AD85AD86AD87AD88AD89E8B6AD8AAD8BAD8CAD8DAD8EAD8FAD90AD91AD92B9CFAD93F0ACAD94F0ADAD95C6B0B0EAC8BFAD96CDDFAD97AD98AD99AD9AAD9BAD9CAD9DCECDEAB1AD9EAD9FADA0AE40EAB2AE41C6BFB4C9AE42AE43AE44AE45AE46AE47AE48EAB3AE49AE4AAE4BAE4CD5E7AE4DAE4EAE4FAE50AE51AE52AE53AE54DDF9AE55EAB4AE56EAB5AE57EAB6AE58AE59AE5AAE5BB8CADFB0C9F5AE5CCCF0AE5DAE5EC9FAAE5FAE60AE61AE62AE63C9FBAE64AE65D3C3CBA6AE66B8A6F0AEB1C2AE67E5B8CCEFD3C9BCD7C9EAAE68B5E7AE69C4D0B5E9AE6AEEAEBBADAE6BAE6CE7DEAE6DEEAFAE6EAE6FAE70AE71B3A9AE72AE73EEB2AE74AE75EEB1BDE7AE76EEB0CEB7AE77AE78AE79AE7AC5CFAE7BAE7CAE7DAE7EC1F4DBCEEEB3D0F3AE80AE81AE82AE83AE84AE85AE86AE87C2D4C6E8AE88AE89AE8AB7ACAE8BAE8CAE8DAE8EAE8FAE90AE91EEB4AE92B3EBAE93AE94AE95BBFBEEB5AE96AE97AE98AE99AE9AE7DCAE9BAE9CAE9DEEB6AE9EAE9FBDAEAEA0AF40AF41AF42F1E2AF43AF44AF45CAE8AF46D2C9F0DAAF47F0DBAF48F0DCC1C6AF49B8EDBECEAF4AAF4BF0DEAF4CC5B1F0DDD1F1AF4DF0E0B0CCBDEAAF4EAF4FAF50AF51AF52D2DFF0DFAF53B4AFB7E8F0E6F0E5C6A3F0E1F0E2B4C3AF54AF55F0E3D5EEAF56AF57CCDBBED2BCB2AF58AF59AF5AF0E8F0E7F0E4B2A1AF5BD6A2D3B8BEB7C8ACAF5CAF5DF0EAAF5EAF5FAF60AF61D1F7AF62D6CCBADBF0E9AF63B6BBAF64AF65CDB4AF66AF67C6A6AF68AF69AF6AC1A1F0EBF0EEAF6BF0EDF0F0F0ECAF6CBBBEF0EFAF6DAF6EAF6FAF70CCB5F0F2AF71AF72B3D5AF73AF74AF75AF76B1D4AF77AF78F0F3AF79AF7AF0F4F0F6B4E1AF7BF0F1AF7CF0F7AF7DAF7EAF80AF81F0FAAF82F0F8AF83AF84AF85F0F5AF86AF87AF88AF89F0FDAF8AF0F9F0FCF0FEAF8BF1A1AF8CAF8DAF8ECEC1F1A4AF8FF1A3AF90C1F6F0FBCADDAF91AF92B4F1B1F1CCB1AF93F1A6AF94AF95F1A7AF96AF97F1ACD5CEF1A9AF98AF99C8B3AF9AAF9BAF9CF1A2AF9DF1ABF1A8F1A5AF9EAF9FF1AAAFA0B040B041B042B043B044B045B046B0A9F1ADB047B048B049B04AB04BB04CF1AFB04DF1B1B04EB04FB050B051B052F1B0B053F1AEB054B055B056B057D1A2B058B059B05AB05BB05CB05DB05EF1B2B05FB060B061F1B3B062B063B064B065B066B067B068B069B9EFB06AB06BB5C7B06CB0D7B0D9B06DB06EB06FD4EDB070B5C4B071BDD4BBCAF0A7B072B073B8DEB074B075F0A8B076B077B0A8B078F0A9B079B07ACDEEB07BB07CF0AAB07DB07EB080B081B082B083B084B085B086B087F0ABB088B089B08AB08BB08CB08DB08EB08FB090C6A4B091B092D6E5F1E4B093F1E5B094B095B096B097B098B099B09AB09BB09CB09DC3F3B09EB09FD3DBB0A0B140D6D1C5E8B141D3AFB142D2E6B143B144EEC1B0BBD5B5D1CEBCE0BAD0B145BFF8B146B8C7B5C1C5CCB147B148CAA2B149B14AB14BC3CBB14CB14DB14EB14FB150EEC2B151B152B153B154B155B156B157B158C4BFB6A2B159EDECC3A4B15AD6B1B15BB15CB15DCFE0EDEFB15EB15FC5CEB160B6DCB161B162CAA1B163B164EDEDB165B166EDF0EDF1C3BCB167BFB4B168EDEEB169B16AB16BB16CB16DB16EB16FB170B171B172B173EDF4EDF2B174B175B176B177D5E6C3DFB178EDF3B179B17AB17BEDF6B17CD5A3D1A3B17DB17EB180EDF5B181C3D0B182B183B184B185B186EDF7BFF4BEECEDF8B187CCF7B188D1DBB189B18AB18BD7C5D5F6B18CEDFCB18DB18EB18FEDFBB190B191B192B193B194B195B196B197EDF9EDFAB198B199B19AB19BB19CB19DB19EB19FEDFDBEA6B1A0B240B241B242B243CBAFEEA1B6BDB244EEA2C4C0B245EDFEB246B247BDDEB2C7B248B249B24AB24BB24CB24DB24EB24FB250B251B252B253B6C3B254B255B256EEA5D8BAEEA3EEA6B257B258B259C3E9B3F2B25AB25BB25CB25DB25EB25FEEA7EEA4CFB9B260B261EEA8C2F7B262B263B264B265B266B267B268B269B26AB26BB26CB26DEEA9EEAAB26EDEABB26FB270C6B3B271C7C6B272D6F5B5C9B273CBB2B274B275B276EEABB277B278CDABB279EEACB27AB27BB27CB27DB27ED5B0B280EEADB281F6C4B282B283B284B285B286B287B288B289B28AB28BB28CB28DB28EDBC7B28FB290B291B292B293B294B295B296B297B4A3B298B299B29AC3ACF1E6B29BB29CB29DB29EB29FCAB8D2D3B2A0D6AAB340EFF2B341BED8B342BDC3EFF3B6CCB0ABB343B344B345B346CAAFB347B348EDB6B349EDB7B34AB34BB34CB34DCEF9B7AFBFF3EDB8C2EBC9B0B34EB34FB350B351B352B353EDB9B354B355C6F6BFB3B356B357B358EDBCC5F8B359D1D0B35AD7A9EDBAEDBBB35BD1E2B35CEDBFEDC0B35DEDC4B35EB35FB360EDC8B361EDC6EDCED5E8B362EDC9B363B364EDC7EDBEB365B366C5E9B367B368B369C6C6B36AB36BC9E9D4D2EDC1EDC2EDC3EDC5B36CC0F9B36DB4A1B36EB36FB370B371B9E8B372EDD0B373B374B375B376EDD1B377EDCAB378EDCFB379CEF8B37AB37BCBB6EDCCEDCDB37CB37DB37EB380B381CFF5B382B383B384B385B386B387B388B389B38AB38BB38CB38DEDD2C1F2D3B2EDCBC8B7B38EB38FB390B391B392B393B394B395BCEFB396B397B398B399C5F0B39AB39BB39CB39DB39EB39FB3A0B440B441B442EDD6B443B5EFB444B445C2B5B0ADCBE9B446B447B1AEB448EDD4B449B44AB44BCDEBB5E2B44CEDD5EDD3EDD7B44DB44EB5FAB44FEDD8B450EDD9B451EDDCB452B1CCB453B454B455B456B457B458B459B45AC5F6BCEEEDDACCBCB2EAB45BB45CB45DB45EEDDBB45FB460B461B462C4EBB463B464B4C5B465B466B467B0F5B468B469B46AEDDFC0DAB4E8B46BB46CB46DB46EC5CDB46FB470B471EDDDBFC4B472B473B474EDDEB475B476B477B478B479B47AB47BB47CB47DB47EB480B481B482B483C4A5B484B485B486EDE0B487B488B489B48AB48BEDE1B48CEDE3B48DB48EC1D7B48FB490BBC7B491B492B493B494B495B496BDB8B497B498B499EDE2B49AB49BB49CB49DB49EB49FB4A0B540B541B542B543B544B545EDE4B546B547B548B549B54AB54BB54CB54DB54EB54FEDE6B550B551B552B553B554EDE5B555B556B557B558B559B55AB55BB55CB55DB55EB55FB560B561B562B563EDE7B564B565B566B567B568CABEECEAC0F1B569C9E7B56AECEBC6EEB56BB56CB56DB56EECECB56FC6EDECEDB570B571B572B573B574B575B576B577B578ECF0B579B57AD7E6ECF3B57BB57CECF1ECEEECEFD7A3C9F1CBEEECF4B57DECF2B57EB580CFE9B581ECF6C6B1B582B583B584B585BCC0B586ECF5B587B588B589B58AB58BB58CB58DB5BBBBF6B58EECF7B58FB590B591B592B593D9F7BDFBB594B595C2BBECF8B596B597B598B599ECF9B59AB59BB59CB59DB8A3B59EB59FB5A0B640B641B642B643B644B645B646ECFAB647B648B649B64AB64BB64CB64DB64EB64FB650B651B652ECFBB653B654B655B656B657B658B659B65AB65BB65CB65DECFCB65EB65FB660B661B662D3EDD8AEC0EBB663C7DDBACCB664D0E3CBBDB665CDBAB666B667B8D1B668B669B1FCB66AC7EFB66BD6D6B66CB66DB66EBFC6C3EBB66FB670EFF5B671B672C3D8B673B674B675B676B677B678D7E2B679B67AB67BEFF7B3D3B67CC7D8D1EDB67DD6C8B67EEFF8B680EFF6B681BBFDB3C6B682B683B684B685B686B687B688BDD5B689B68AD2C6B68BBBE0B68CB68DCFA1B68EEFFCEFFBB68FB690EFF9B691B692B693B694B3CCB695C9D4CBB0B696B697B698B699B69AEFFEB69BB69CB0DEB69DB69ED6C9B69FB6A0B740EFFDB741B3EDB742B743F6D5B744B745B746B747B748B749B74AB74BB74CB74DB74EB74FB750B751B752CEC8B753B754B755F0A2B756F0A1B757B5BEBCDABBFCB758B8E5B759B75AB75BB75CB75DB75EC4C2B75FB760B761B762B763B764B765B766B767B768F0A3B769B76AB76BB76CB76DCBEBB76EB76FB770B771B772B773B774B775B776B777B778B779B77AB77BB77CB77DB77EB780B781B782B783B784B785B786F0A6B787B788B789D1A8B78ABEBFC7EEF1B6F1B7BFD5B78BB78CB78DB78EB4A9F1B8CDBBB78FC7D4D5ADB790F1B9B791F1BAB792B793B794B795C7CFB796B797B798D2A4D6CFB799B79AF1BBBDD1B4B0BEBDB79BB79CB79DB4DCCED1B79EBFDFF1BDB79FB7A0B840B841BFFAF1BCB842F1BFB843B844B845F1BEF1C0B846B847B848B849B84AF1C1B84BB84CB84DB84EB84FB850B851B852B853B854B855C1FEB856B857B858B859B85AB85BB85CB85DB85EB85FB860C1A2B861B862B863B864B865B866B867B868B869B86ACAFAB86BB86CD5BEB86DB86EB86FB870BEBABEB9D5C2B871B872BFA2B873CDAFF1B5B874B875B876B877B878B879BDDFB87AB6CBB87BB87CB87DB87EB880B881B882B883B884D6F1F3C3B885B886F3C4B887B8CDB888B889B88AF3C6F3C7B88BB0CAB88CF3C5B88DF3C9CBF1B88EB88FB890F3CBB891D0A6B892B893B1CAF3C8B894B895B896F3CFB897B5D1B898B899F3D7B89AF3D2B89BB89CB89DF3D4F3D3B7FBB89EB1BFB89FF3CEF3CAB5DAB8A0F3D0B940B941F3D1B942F3D5B943B944B945B946F3CDB947BCE3B948C1FDB949F3D6B94AB94BB94CB94DB94EB94FF3DAB950F3CCB951B5C8B952BDEEF3DCB953B954B7A4BFF0D6FECDB2B955B4F0B956B2DFB957F3D8B958F3D9C9B8B959F3DDB95AB95BF3DEB95CF3E1B95DB95EB95FB960B961B962B963B964B965B966B967F3DFB968B969F3E3F3E2B96AB96BF3DBB96CBFEAB96DB3EFB96EF3E0B96FB970C7A9B971BCF2B972B973B974B975F3EBB976B977B978B979B97AB97BB97CB9BFB97DB97EF3E4B980B981B982B2ADBBFEB983CBE3B984B985B986B987F3EDF3E9B988B989B98AB9DCF3EEB98BB98CB98DF3E5F3E6F3EAC2E1F3ECF3EFF3E8BCFDB98EB98FB990CFE4B991B992F3F0B993B994B995F3E7B996B997B998B999B99AB99BB99CB99DF3F2B99EB99FB9A0BA40D7ADC6AABA41BA42BA43BA44F3F3BA45BA46BA47BA48F3F1BA49C2A8BA4ABA4BBA4CBA4DBA4EB8DDF3F5BA4FBA50F3F4BA51BA52BA53B4DBBA54BA55BA56F3F6F3F7BA57BA58BA59F3F8BA5ABA5BBA5CC0BABA5DBA5EC0E9BA5FBA60BA61BA62BA63C5F1BA64BA65BA66BA67F3FBBA68F3FABA69BA6ABA6BBA6CBA6DBA6EBA6FBA70B4D8BA71BA72BA73F3FEF3F9BA74BA75F3FCBA76BA77BA78BA79BA7ABA7BF3FDBA7CBA7DBA7EBA80BA81BA82BA83BA84F4A1BA85BA86BA87BA88BA89BA8AF4A3BBC9BA8BBA8CF4A2BA8DBA8EBA8FBA90BA91BA92BA93BA94BA95BA96BA97BA98BA99F4A4BA9ABA9BBA9CBA9DBA9EBA9FB2BEF4A6F4A5BAA0BB40BB41BB42BB43BB44BB45BB46BB47BB48BB49BCAEBB4ABB4BBB4CBB4DBB4EBB4FBB50BB51BB52BB53BB54BB55BB56BB57BB58BB59BB5ABB5BBB5CBB5DBB5EBB5FBB60BB61BB62BB63BB64BB65BB66BB67BB68BB69BB6ABB6BBB6CBB6DBB6EC3D7D9E1BB6FBB70BB71BB72BB73BB74C0E0F4CCD7D1BB75BB76BB77BB78BB79BB7ABB7BBB7CBB7DBB7EBB80B7DBBB81BB82BB83BB84BB85BB86BB87F4CEC1A3BB88BB89C6C9BB8AB4D6D5B3BB8BBB8CBB8DF4D0F4CFF4D1CBDABB8EBB8FF4D2BB90D4C1D6E0BB91BB92BB93BB94B7E0BB95BB96BB97C1B8BB98BB99C1BBF4D3BEACBB9ABB9BBB9CBB9DBB9EB4E2BB9FBBA0F4D4F4D5BEABBC40BC41F4D6BC42BC43BC44F4DBBC45F4D7F4DABC46BAFDBC47F4D8F4D9BC48BC49BC4ABC4BBC4CBC4DBC4EB8E2CCC7F4DCBC4FB2DABC50BC51C3D3BC52BC53D4E3BFB7BC54BC55BC56BC57BC58BC59BC5AF4DDBC5BBC5CBC5DBC5EBC5FBC60C5B4BC61BC62BC63BC64BC65BC66BC67BC68F4E9BC69BC6ACFB5BC6BBC6CBC6DBC6EBC6FBC70BC71BC72BC73BC74BC75BC76BC77BC78CEC9BC79BC7ABC7BBC7CBC7DBC7EBC80BC81BC82BC83BC84BC85BC86BC87BC88BC89BC8ABC8BBC8CBC8DBC8ECBD8BC8FCBF7BC90BC91BC92BC93BDF4BC94BC95BC96D7CFBC97BC98BC99C0DBBC9ABC9BBC9CBC9DBC9EBC9FBCA0BD40BD41BD42BD43BD44BD45BD46BD47BD48BD49BD4ABD4BBD4CBD4DBD4EBD4FBD50BD51BD52BD53BD54BD55BD56BD57BD58BD59BD5ABD5BBD5CBD5DBD5EBD5FBD60BD61BD62BD63BD64BD65BD66BD67BD68BD69BD6ABD6BBD6CBD6DBD6EBD6FBD70BD71BD72BD73BD74BD75BD76D0F5BD77BD78BD79BD7ABD7BBD7CBD7DBD7EF4EABD80BD81BD82BD83BD84BD85BD86BD87BD88BD89BD8ABD8BBD8CBD8DBD8EBD8FBD90BD91BD92BD93BD94BD95BD96BD97BD98BD99BD9ABD9BBD9CBD9DBD9EBD9FBDA0BE40BE41BE42BE43BE44BE45BE46BE47BE48BE49BE4ABE4BBE4CF4EBBE4DBE4EBE4FBE50BE51BE52BE53F4ECBE54BE55BE56BE57BE58BE59BE5ABE5BBE5CBE5DBE5EBE5FBE60BE61BE62BE63BE64BE65BE66BE67BE68BE69BE6ABE6BBE6CBE6DBE6EBE6FBE70BE71BE72BE73BE74BE75BE76BE77BE78BE79BE7ABE7BBE7CBE7DBE7EBE80BE81BE82BE83BE84BE85BE86BE87BE88BE89BE8ABE8BBE8CBE8DBE8EBE8FBE90BE91BE92BE93BE94BE95BE96BE97BE98BE99BE9ABE9BBE9CBE9DBE9EBE9FBEA0BF40BF41BF42BF43BF44BF45BF46BF47BF48BF49BF4ABF4BBF4CBF4DBF4EBF4FBF50BF51BF52BF53BF54BF55BF56BF57BF58BF59BF5ABF5BBF5CBF5DBF5EBF5FBF60BF61BF62BF63BF64BF65BF66BF67BF68BF69BF6ABF6BBF6CBF6DBF6EBF6FBF70BF71BF72BF73BF74BF75BF76BF77BF78BF79BF7ABF7BBF7CBF7DBF7EBF80F7E3BF81BF82BF83BF84BF85B7B1BF86BF87BF88BF89BF8AF4EDBF8BBF8CBF8DBF8EBF8FBF90BF91BF92BF93BF94BF95BF96BF97BF98BF99BF9ABF9BBF9CBF9DBF9EBF9FBFA0C040C041C042C043C044C045C046C047C048C049C04AC04BC04CC04DC04EC04FC050C051C052C053C054C055C056C057C058C059C05AC05BC05CC05DC05EC05FC060C061C062C063D7EBC064C065C066C067C068C069C06AC06BC06CC06DC06EC06FC070C071C072C073C074C075C076C077C078C079C07AC07BF4EEC07CC07DC07EE6F9BEC0E6FABAECE6FBCFCBE6FCD4BCBCB6E6FDE6FEBCCDC8D2CEB3E7A1C080B4BFE7A2C9B4B8D9C4C9C081D7DDC2DAB7D7D6BDCEC6B7C4C082C083C5A6E7A3CFDFE7A4E7A5E7A6C1B7D7E9C9F0CFB8D6AFD6D5E7A7B0EDE7A8E7A9C9DCD2EFBEADE7AAB0F3C8DEBDE1E7ABC8C6C084E7ACBBE6B8F8D1A4E7ADC2E7BEF8BDCACDB3E7AEE7AFBEEED0E5C085CBE7CCD0BCCCE7B0BCA8D0F7E7B1C086D0F8E7B2E7B3B4C2E7B4E7B5C9FECEACC3E0E7B7B1C1B3F1C087E7B8E7B9D7DBD5C0E7BAC2CCD7BAE7BBE7BCE7BDBCEAC3E5C0C2E7BEE7BFBCA9C088E7C0E7C1E7B6B6D0E7C2C089E7C3E7C4BBBAB5DEC2C6B1E0E7C5D4B5E7C6B8BFE7C8E7C7B7ECC08AE7C9B2F8E7CAE7CBE7CCE7CDE7CEE7CFE7D0D3A7CBF5E7D1E7D2E7D3E7D4C9C9E7D5E7D6E7D7E7D8E7D9BDC9E7DAF3BEC08BB8D7C08CC8B1C08DC08EC08FC090C091C092C093F3BFC094F3C0F3C1C095C096C097C098C099C09AC09BC09CC09DC09EB9DECDF8C09FC0A0D8E8BAB1C140C2DEEEB7C141B7A3C142C143C144C145EEB9C146EEB8B0D5C147C148C149C14AC14BEEBBD5D6D7EFC14CC14DC14ED6C3C14FC150EEBDCAF0C151EEBCC152C153C154C155EEBEC156C157C158C159EEC0C15AC15BEEBFC15CC15DC15EC15FC160C161C162C163D1F2C164C7BCC165C3C0C166C167C168C169C16AB8E1C16BC16CC16DC16EC16FC1E7C170C171F4C6D0DFF4C7C172CFDBC173C174C8BAC175C176F4C8C177C178C179C17AC17BC17CC17DF4C9F4CAC17EF4CBC180C181C182C183C184D9FAB8FEC185C186E5F1D3F0C187F4E0C188CECCC189C18AC18BB3E1C18CC18DC18EC18FF1B4C190D2EEC191F4E1C192C193C194C195C196CFE8F4E2C197C198C7CCC199C19AC19BC19CC19DC19EB5D4B4E4F4E4C19FC1A0C240F4E3F4E5C241C242F4E6C243C244C245C246F4E7C247BAB2B0BFC248F4E8C249C24AC24BC24CC24DC24EC24FB7ADD2EDC250C251C252D2ABC0CFC253BFBCEBA3D5DFEAC8C254C255C256C257F1F3B6F8CBA3C258C259C4CDC25AF1E7C25BF1E8B8FBF1E9BAC4D4C5B0D2C25CC25DF1EAC25EC25FC260F1EBC261F1ECC262C263F1EDF1EEF1EFF1F1F1F0C5D5C264C265C266C267C268C269F1F2C26AB6FAC26BF1F4D2AEDEC7CBCAC26CC26DB3DCC26EB5A2C26FB9A2C270C271C4F4F1F5C272C273F1F6C274C275C276C1C4C1FBD6B0F1F7C277C278C279C27AF1F8C27BC1AAC27CC27DC27EC6B8C280BEDBC281C282C283C284C285C286C287C288C289C28AC28BC28CC28DC28EF1F9B4CFC28FC290C291C292C293C294F1FAC295C296C297C298C299C29AC29BC29CC29DC29EC29FC2A0C340EDB2EDB1C341C342CBE0D2DEC343CBC1D5D8C344C8E2C345C0DFBCA1C346C347C348C349C34AC34BEBC1C34CC34DD0A4C34ED6E2C34FB6C7B8D8EBC0B8CEC350EBBFB3A6B9C9D6ABC351B7F4B7CAC352C353C354BCE7B7BEEBC6C355EBC7B0B9BFCFC356EBC5D3FDC357EBC8C358C359EBC9C35AC35BB7CEC35CEBC2EBC4C9F6D6D7D5CDD0B2EBCFCEB8EBD0C35DB5A8C35EC35FC360C361C362B1B3EBD2CCA5C363C364C365C366C367C368C369C5D6EBD3C36AEBD1C5DFEBCECAA4EBD5B0FBC36BC36CBAFAC36DC36ED8B7F1E3C36FEBCAEBCBEBCCEBCDEBD6E6C0EBD9C370BFE8D2C8EBD7EBDCB8ECEBD8C371BDBAC372D0D8C373B0B7C374EBDDC4DCC375C376C377C378D6ACC379C37AC37BB4E0C37CC37DC2F6BCB9C37EC380EBDAEBDBD4E0C6EAC4D4EBDFC5A7D9F5C381B2B1C382EBE4C383BDC5C384C385C386EBE2C387C388C389C38AC38BC38CC38DC38EC38FC390C391C392C393EBE3C394C395B8ACC396CDD1EBE5C397C398C399EBE1C39AC1B3C39BC39CC39DC39EC39FC6A2C3A0C440C441C442C443C444C445CCF3C446EBE6C447C0B0D2B8EBE7C448C449C44AB8AFB8ADC44BEBE8C7BBCDF3C44CC44DC44EEBEAEBEBC44FC450C451C452C453EBEDC454C455C456C457D0C8C458EBF2C459EBEEC45AC45BC45CEBF1C8F9C45DD1FCEBECC45EC45FEBE9C460C461C462C463B8B9CFD9C4E5EBEFEBF0CCDACDC8B0F2C464EBF6C465C466C467C468C469EBF5C46AB2B2C46BC46CC46DC46EB8E0C46FEBF7C470C471C472C473C474C475B1ECC476C477CCC5C4A4CFA5C478C479C47AC47BC47CEBF9C47DC47EECA2C480C5F2C481EBFAC482C483C484C485C486C487C488C489C9C5C48AC48BC48CC48DC48EC48FE2DFEBFEC490C491C492C493CDCEECA1B1DBD3B7C494C495D2DCC496C497C498EBFDC499EBFBC49AC49BC49CC49DC49EC49FC4A0C540C541C542C543C544C545C546C547C548C549C54AC54BC54CC54DC54EB3BCC54FC550C551EAB0C552C553D7D4C554F4ABB3F4C555C556C557C558C559D6C1D6C2C55AC55BC55CC55DC55EC55FD5E9BECAC560F4A7C561D2A8F4A8F4A9C562F4AABECBD3DFC563C564C565C566C567C9E0C9E1C568C569F3C2C56ACAE6C56BCCF2C56CC56DC56EC56FC570C571E2B6CBB4C572CEE8D6DBC573F4ADF4AEF4AFC574C575C576C577F4B2C578BABDF4B3B0E3F4B0C579F4B1BDA2B2D5C57AF4B6F4B7B6E6B2B0CFCFF4B4B4ACC57BF4B5C57CC57DF4B8C57EC580C581C582C583F4B9C584C585CDA7C586F4BAC587F4BBC588C589C58AF4BCC58BC58CC58DC58EC58FC590C591C592CBD2C593F4BDC594C595C596C597F4BEC598C599C59AC59BC59CC59DC59EC59FF4BFC5A0C640C641C642C643F4DEC1BCBCE8C644C9ABD1DEE5F5C645C646C647C648DCB3D2D5C649C64ADCB4B0ACDCB5C64BC64CBDDAC64DDCB9C64EC64FC650D8C2C651DCB7D3F3C652C9D6DCBADCB6C653DCBBC3A2C654C655C656C657DCBCDCC5DCBDC658C659CEDFD6A5C65ADCCFC65BDCCDC65CC65DDCD2BDE6C2ABC65EDCB8DCCBDCCEDCBEB7D2B0C5DCC7D0BEDCC1BBA8C65FB7BCDCCCC660C661DCC6DCBFC7DBC662C663C664D1BFDCC0C665C666DCCAC667C668DCD0C669C66ACEADDCC2C66BDCC3DCC8DCC9B2D4DCD1CBD5C66CD4B7DCDBDCDFCCA6DCE6C66DC3E7DCDCC66EC66FBFC1DCD9C670B0FAB9B6DCE5DCD3C671DCC4DCD6C8F4BFE0C672C673C674C675C9BBC676C677C678B1BDC679D3A2C67AC67BDCDAC67CC67DDCD5C67EC6BBC680DCDEC681C682C683C684C685D7C2C3AFB7B6C7D1C3A9DCE2DCD8DCEBDCD4C686C687DCDDC688BEA5DCD7C689DCE0C68AC68BDCE3DCE4C68CDCF8C68DC68EDCE1DDA2DCE7C68FC690C691C692C693C694C695C696C697C698BCEBB4C4C699C69AC3A3B2E7DCFAC69BDCF2C69CDCEFC69DDCFCDCEED2F0B2E8C69EC8D7C8E3DCFBC69FDCEDC6A0C740C741DCF7C742C743DCF5C744C745BEA3DCF4C746B2DDC747C748C749C74AC74BDCF3BCF6DCE8BBC4C74CC0F3C74DC74EC74FC750C751BCD4DCE9DCEAC752DCF1DCF6DCF9B5B4C753C8D9BBE7DCFEDCFDD3ABDDA1DDA3DDA5D2F1DDA4DDA6DDA7D2A9C754C755C756C757C758C759C75ABAC9DDA9C75BC75CDDB6DDB1DDB4C75DC75EC75FC760C761C762C763DDB0C6CEC764C765C0F2C766C767C768C769C9AFC76AC76BC76CDCECDDAEC76DC76EC76FC770DDB7C771C772DCF0DDAFC773DDB8C774DDACC775C776C777C778C779C77AC77BDDB9DDB3DDADC4AAC77CC77DC77EC780DDA8C0B3C1ABDDAADDABC781DDB2BBF1DDB5D3A8DDBAC782DDBBC3A7C783C784DDD2DDBCC785C786C787DDD1C788B9BDC789C78ABED5C78BBEFAC78CC78DBACAC78EC78FC790C791DDCAC792DDC5C793DDBFC794C795C796B2CBDDC3C797DDCBB2A4DDD5C798C799C79ADDBEC79BC79CC79DC6D0DDD0C79EC79FC7A0C840C841DDD4C1E2B7C6C842C843C844C845C846DDCEDDCFC847C848C849DDC4C84AC84BC84CDDBDC84DDDCDCCD1C84EDDC9C84FC850C851C852DDC2C3C8C6BCCEAEDDCCC853DDC8C854C855C856C857C858C859DDC1C85AC85BC85CDDC6C2DCC85DC85EC85FC860C861C862D3A9D3AADDD3CFF4C8F8C863C864C865C866C867C868C869C86ADDE6C86BC86CC86DC86EC86FC870DDC7C871C872C873DDE0C2E4C874C875C876C877C878C879C87AC87BDDE1C87CC87DC87EC880C881C882C883C884C885C886DDD7C887C888C889C88AC88BD6F8C88CDDD9DDD8B8F0DDD6C88DC88EC88FC890C6CFC891B6ADC892C893C894C895C896DDE2C897BAF9D4E1DDE7C898C899C89AB4D0C89BDDDAC89CBFFBDDE3C89DDDDFC89EDDDDC89FC8A0C940C941C942C943C944B5D9C945C946C947C948DDDBDDDCDDDEC949BDAFDDE4C94ADDE5C94BC94CC94DC94EC94FC950C951C952DDF5C953C3C9C954C955CBE2C956C957C958C959DDF2C95AC95BC95CC95DC95EC95FC960C961C962C963C964C965C966D8E1C967C968C6D1C969DDF4C96AC96BC96CD5F4DDF3DDF0C96DC96EDDECC96FDDEFC970DDE8C971C972D0EEC973C974C975C976C8D8DDEEC977C978DDE9C979C97ADDEACBF2C97BDDEDC97CC97DB1CDC97EC980C981C982C983C984C0B6C985BCBBDDF1C986C987DDF7C988DDF6DDEBC989C98AC98BC98CC98DC5EEC98EC98FC990DDFBC991C992C993C994C995C996C997C998C999C99AC99BDEA4C99CC99DDEA3C99EC99FC9A0CA40CA41CA42CA43CA44CA45CA46CA47CA48DDF8CA49CA4ACA4BCA4CC3EFCA4DC2FBCA4ECA4FCA50D5E1CA51CA52CEB5CA53CA54CA55CA56DDFDCA57B2CCCA58CA59CA5ACA5BCA5CCA5DCA5ECA5FCA60C4E8CADFCA61CA62CA63CA64CA65CA66CA67CA68CA69CA6AC7BEDDFADDFCDDFEDEA2B0AAB1CECA6BCA6CCA6DCA6ECA6FDEACCA70CA71CA72CA73DEA6BDB6C8EFCA74CA75CA76CA77CA78CA79CA7ACA7BCA7CCA7DCA7EDEA1CA80CA81DEA5CA82CA83CA84CA85DEA9CA86CA87CA88CA89CA8ADEA8CA8BCA8CCA8DDEA7CA8ECA8FCA90CA91CA92CA93CA94CA95CA96DEADCA97D4CCCA98CA99CA9ACA9BDEB3DEAADEAECA9CCA9DC0D9CA9ECA9FCAA0CB40CB41B1A1DEB6CB42DEB1CB43CB44CB45CB46CB47CB48CB49DEB2CB4ACB4BCB4CCB4DCB4ECB4FCB50CB51CB52CB53CB54D1A6DEB5CB55CB56CB57CB58CB59CB5ACB5BDEAFCB5CCB5DCB5EDEB0CB5FD0BDCB60CB61CB62DEB4CAEDDEB9CB63CB64CB65CB66CB67CB68DEB8CB69DEB7CB6ACB6BCB6CCB6DCB6ECB6FCB70DEBBCB71CB72CB73CB74CB75CB76CB77BDE5CB78CB79CB7ACB7BCB7CB2D8C3EACB7DCB7EDEBACB80C5BACB81CB82CB83CB84CB85CB86DEBCCB87CB88CB89CB8ACB8BCB8CCB8DCCD9CB8ECB8FCB90CB91B7AACB92CB93CB94CB95CB96CB97CB98CB99CB9ACB9BCB9CCB9DCB9ECB9FCBA0CC40CC41D4E5CC42CC43CC44DEBDCC45CC46CC47CC48CC49DEBFCC4ACC4BCC4CCC4DCC4ECC4FCC50CC51CC52CC53CC54C4A2CC55CC56CC57CC58DEC1CC59CC5ACC5BCC5CCC5DCC5ECC5FCC60CC61CC62CC63CC64CC65CC66CC67CC68DEBECC69DEC0CC6ACC6BCC6CCC6DCC6ECC6FCC70CC71CC72CC73CC74CC75CC76CC77D5BACC78CC79CC7ADEC2CC7BCC7CCC7DCC7ECC80CC81CC82CC83CC84CC85CC86CC87CC88CC89CC8ACC8BF2AEBBA2C2B2C5B0C2C7CC8CCC8DF2AFCC8ECC8FCC90CC91CC92D0E9CC93CC94CC95D3DDCC96CC97CC98EBBDCC99CC9ACC9BCC9CCC9DCC9ECC9FCCA0B3E6F2B0CD40F2B1CD41CD42CAADCD43CD44CD45CD46CD47CD48CD49BAE7F2B3F2B5F2B4CBE4CFBAF2B2CAB4D2CFC2ECCD4ACD4BCD4CCD4DCD4ECD4FCD50CEC3F2B8B0F6F2B7CD51CD52CD53CD54CD55F2BECD56B2CFCD57CD58CD59CD5ACD5BCD5CD1C1F2BACD5DCD5ECD5FCD60CD61F2BCD4E9CD62CD63F2BBF2B6F2BFF2BDCD64F2B9CD65CD66F2C7F2C4F2C6CD67CD68F2CAF2C2F2C0CD69CD6ACD6BF2C5CD6CCD6DCD6ECD6FCD70D6FBCD71CD72CD73F2C1CD74C7F9C9DFCD75F2C8B9C6B5B0CD76CD77F2C3F2C9F2D0F2D6CD78CD79BBD7CD7ACD7BCD7CF2D5CDDCCD7DD6EBCD7ECD80F2D2F2D4CD81CD82CD83CD84B8F2CD85CD86CD87CD88F2CBCD89CD8ACD8BF2CEC2F9CD8CD5DDF2CCF2CDF2CFF2D3CD8DCD8ECD8FF2D9D3BCCD90CD91CD92CD93B6EACD94CAF1CD95B7E4F2D7CD96CD97CD98F2D8F2DAF2DDF2DBCD99CD9AF2DCCD9BCD9CCD9DCD9ED1D1F2D1CD9FCDC9CDA0CECFD6A9CE40F2E3CE41C3DBCE42F2E0CE43CE44C0AFF2ECF2DECE45F2E1CE46CE47CE48F2E8CE49CE4ACE4BCE4CF2E2CE4DCE4EF2E7CE4FCE50F2E6CE51CE52F2E9CE53CE54CE55F2DFCE56CE57F2E4F2EACE58CE59CE5ACE5BCE5CCE5DCE5ED3ACF2E5B2F5CE5FCE60F2F2CE61D0ABCE62CE63CE64CE65F2F5CE66CE67CE68BBC8CE69F2F9CE6ACE6BCE6CCE6DCE6ECE6FF2F0CE70CE71F2F6F2F8F2FACE72CE73CE74CE75CE76CE77CE78CE79F2F3CE7AF2F1CE7BCE7CCE7DBAFBCE7EB5FBCE80CE81CE82CE83F2EFF2F7F2EDF2EECE84CE85CE86F2EBF3A6CE87F3A3CE88CE89F3A2CE8ACE8BF2F4CE8CC8DACE8DCE8ECE8FCE90CE91F2FBCE92CE93CE94F3A5CE95CE96CE97CE98CE99CE9ACE9BC3F8CE9CCE9DCE9ECE9FCEA0CF40CF41CF42F2FDCF43CF44F3A7F3A9F3A4CF45F2FCCF46CF47CF48F3ABCF49F3AACF4ACF4BCF4CCF4DC2DDCF4ECF4FF3AECF50CF51F3B0CF52CF53CF54CF55CF56F3A1CF57CF58CF59F3B1F3ACCF5ACF5BCF5CCF5DCF5EF3AFF2FEF3ADCF5FCF60CF61CF62CF63CF64CF65F3B2CF66CF67CF68CF69F3B4CF6ACF6BCF6CCF6DF3A8CF6ECF6FCF70CF71F3B3CF72CF73CF74F3B5CF75CF76CF77CF78CF79CF7ACF7BCF7CCF7DCF7ED0B7CF80CF81CF82CF83F3B8CF84CF85CF86CF87D9F9CF88CF89CF8ACF8BCF8CCF8DF3B9CF8ECF8FCF90CF91CF92CF93CF94CF95F3B7CF96C8E4F3B6CF97CF98CF99CF9AF3BACF9BCF9CCF9DCF9ECF9FF3BBB4C0CFA0D040D041D042D043D044D045D046D047D048D049D04AD04BD04CD04DEEC3D04ED04FD050D051D052D053F3BCD054D055F3BDD056D057D058D1AAD059D05AD05BF4ACD0C6D05CD05DD05ED05FD060D061D0D0D1DCD062D063D064D065D066D067CFCED068D069BDD6D06AD1C3D06BD06CD06DD06ED06FD070D071BAE2E1E9D2C2F1C2B2B9D072D073B1EDF1C3D074C9C0B3C4D075D9F2D076CBA5D077F1C4D078D079D07AD07BD6D4D07CD07DD07ED080D081F1C5F4C0F1C6D082D4ACF1C7D083B0C0F4C1D084D085F4C2D086D087B4FCD088C5DBD089D08AD08BD08CCCBBD08DD08ED08FD0E4D090D091D092D093D094CDE0D095D096D097D098D099F1C8D09AD9F3D09BD09CD09DD09ED09FD0A0B1BBD140CFAED141D142D143B8A4D144D145D146D147D148F1CAD149D14AD14BD14CF1CBD14DD14ED14FD150B2C3C1D1D151D152D7B0F1C9D153D154F1CCD155D156D157D158F1CED159D15AD15BD9F6D15CD2E1D4A3D15DD15EF4C3C8B9D15FD160D161D162D163F4C4D164D165F1CDF1CFBFE3F1D0D166D167F1D4D168D169D16AD16BD16CD16DD16EF1D6F1D1D16FC9D1C5E1D170D171D172C2E3B9FCD173D174F1D3D175F1D5D176D177D178B9D3D179D17AD17BD17CD17DD17ED180F1DBD181D182D183D184D185BAD6D186B0FDF1D9D187D188D189D18AD18BF1D8F1D2F1DAD18CD18DD18ED18FD190F1D7D191D192D193C8ECD194D195D196D197CDCAF1DDD198D199D19AD19BE5BDD19CD19DD19EF1DCD19FF1DED1A0D240D241D242D243D244D245D246D247D248F1DFD249D24ACFE5D24BD24CD24DD24ED24FD250D251D252D253D254D255D256D257D258D259D25AD25BD25CD25DD25ED25FD260D261D262D263F4C5BDF3D264D265D266D267D268D269F1E0D26AD26BD26CD26DD26ED26FD270D271D272D273D274D275D276D277D278D279D27AD27BD27CD27DF1E1D27ED280D281CEF7D282D2AAD283F1FBD284D285B8B2D286D287D288D289D28AD28BD28CD28DD28ED28FD290D291D292D293D294D295D296D297D298D299D29AD29BD29CD29DD29ED29FD2A0D340D341D342D343D344D345D346D347D348D349D34AD34BD34CD34DD34ED34FD350D351D352D353D354D355D356D357D358D359D35AD35BD35CD35DD35EBCFBB9DBD35FB9E6C3D9CAD3EAE8C0C0BEF5EAE9EAEAEAEBD360EAECEAEDEAEEEAEFBDC7D361D362D363F5FBD364D365D366F5FDD367F5FED368F5FCD369D36AD36BD36CBDE2D36DF6A1B4A5D36ED36FD370D371F6A2D372D373D374F6A3D375D376D377ECB2D378D379D37AD37BD37CD37DD37ED380D381D382D383D384D1D4D385D386D387D388D389D38AD9EAD38BD38CD38DD38ED38FD390D391D392D393D394D395D396D397D398D399D39AD39BD39CD39DD39ED39FD3A0D440D441D442D443D444D445D446D447D448D449D44AD44BD44CD44DD44ED44FD450D451D452D453D454D455D456D457D458D459D45AD45BD45CD45DD45ED45FF6A4D460D461D462D463D464D465D466D467D468EEBAD469D46AD46BD46CD46DD46ED46FD470D471D472D473D474D475D476D477D478D479D47AD47BD47CD47DD47ED480D481D482D483D484D485D486D487D488D489D48AD48BD48CD48DD48ED48FD490D491D492D493D494D495D496D497D498D499D5B2D49AD49BD49CD49DD49ED49FD4A0D540D541D542D543D544D545D546D547D3FECCDCD548D549D54AD54BD54CD54DD54ED54FCAC4D550D551D552D553D554D555D556D557D558D559D55AD55BD55CD55DD55ED55FD560D561D562D563D564D565D566D567D568D569D56AD56BD56CD56DD56ED56FD570D571D572D573D574D575D576D577D578D579D57AD57BD57CD57DD57ED580D581D582D583D584D585D586D587D588D589D58AD58BD58CD58DD58ED58FD590D591D592D593D594D595D596D597D598D599D59AD59BD59CD59DD59ED59FD5A0D640D641D642D643D644D645D646D647D648D649D64AD64BD64CD64DD64ED64FD650D651D652D653D654D655D656D657D658D659D65AD65BD65CD65DD65ED65FD660D661D662E5C0D663D664D665D666D667D668D669D66AD66BD66CD66DD66ED66FD670D671D672D673D674D675D676D677D678D679D67AD67BD67CD67DD67ED680D681F6A5D682D683D684D685D686D687D688D689D68AD68BD68CD68DD68ED68FD690D691D692D693D694D695D696D697D698D699D69AD69BD69CD69DD69ED69FD6A0D740D741D742D743D744D745D746D747D748D749D74AD74BD74CD74DD74ED74FD750D751D752D753D754D755D756D757D758D759D75AD75BD75CD75DD75ED75FBEAFD760D761D762D763D764C6A9D765D766D767D768D769D76AD76BD76CD76DD76ED76FD770D771D772D773D774D775D776D777D778D779D77AD77BD77CD77DD77ED780D781D782D783D784D785D786D787D788D789D78AD78BD78CD78DD78ED78FD790D791D792D793D794D795D796D797D798DAA5BCC6B6A9B8BCC8CFBCA5DAA6DAA7CCD6C8C3DAA8C6FDD799D1B5D2E9D1B6BCC7D79ABDB2BBE4DAA9DAAAD1C8DAABD0EDB6EFC2DBD79BCBCFB7EDC9E8B7C3BEF7D6A4DAACDAADC6C0D7E7CAB6D79CD5A9CBDFD5EFDAAED6DFB4CADAB0DAAFD79DD2EBDAB1DAB2DAB3CAD4DAB4CAABDAB5DAB6B3CFD6EFDAB7BBB0B5AEDAB8DAB9B9EED1AFD2E8DABAB8C3CFEAB2EFDABBDABCD79EBDEBCEDCD3EFDABDCEF3DABED3D5BBE5DABFCBB5CBD0DAC0C7EBD6EEDAC1C5B5B6C1DAC2B7CCBFCEDAC3DAC4CBADDAC5B5F7DAC6C1C2D7BBDAC7CCB8D79FD2EAC4B1DAC8B5FDBBD1DAC9D0B3DACADACBCEBDDACCDACDDACEB2F7DAD1DACFD1E8DAD0C3D5DAD2D7A0DAD3DAD4DAD5D0BBD2A5B0F9DAD6C7ABDAD7BDF7C3A1DAD8DAD9C3FDCCB7DADADADBC0BEC6D7DADCDADDC7B4DADEDADFB9C8D840D841D842D843D844D845D846D847D848BBEDD849D84AD84BD84CB6B9F4F8D84DF4F9D84ED84FCDE3D850D851D852D853D854D855D856D857F5B9D858D859D85AD85BEBE0D85CD85DD85ED85FD860D861CFF3BBBFD862D863D864D865D866D867D868BAC0D4A5D869D86AD86BD86CD86DD86ED86FE1D9D870D871D872D873F5F4B1AAB2F2D874D875D876D877D878D879D87AF5F5D87BD87CF5F7D87DD87ED880BAD1F5F6D881C3B2D882D883D884D885D886D887D888F5F9D889D88AD88BF5F8D88CD88DD88ED88FD890D891D892D893D894D895D896D897D898D899D89AD89BD89CD89DD89ED89FD8A0D940D941D942D943D944D945D946D947D948D949D94AD94BD94CD94DD94ED94FD950D951D952D953D954D955D956D957D958D959D95AD95BD95CD95DD95ED95FD960D961D962D963D964D965D966D967D968D969D96AD96BD96CD96DD96ED96FD970D971D972D973D974D975D976D977D978D979D97AD97BD97CD97DD97ED980D981D982D983D984D985D986D987D988D989D98AD98BD98CD98DD98ED98FD990D991D992D993D994D995D996D997D998D999D99AD99BD99CD99DD99ED99FD9A0DA40DA41DA42DA43DA44DA45DA46DA47DA48DA49DA4ADA4BDA4CDA4DDA4EB1B4D5EAB8BADA4FB9B1B2C6D4F0CFCDB0DCD5CBBBF5D6CAB7B7CCB0C6B6B1E1B9BAD6FCB9E1B7A1BCFAEADAEADBCCF9B9F3EADCB4FBC3B3B7D1BAD8EADDD4F4EADEBCD6BBDFEADFC1DEC2B8D4DFD7CAEAE0EAE1EAE4EAE2EAE3C9DEB8B3B6C4EAE5CAEAC9CDB4CDDA50DA51E2D9C5E2EAE6C0B5DA52D7B8EAE7D7ACC8FCD8D3D8CDD4DEDA53D4F9C9C4D3AEB8D3B3E0DA54C9E2F4F6DA55DA56DA57BAD5DA58F4F7DA59DA5AD7DFDA5BDA5CF4F1B8B0D5D4B8CFC6F0DA5DDA5EDA5FDA60DA61DA62DA63DA64DA65B3C3DA66DA67F4F2B3ACDA68DA69DA6ADA6BD4BDC7F7DA6CDA6DDA6EDA6FDA70F4F4DA71DA72F4F3DA73DA74DA75DA76DA77DA78DA79DA7ADA7BDA7CCCCBDA7DDA7EDA80C8A4DA81DA82DA83DA84DA85DA86DA87DA88DA89DA8ADA8BDA8CDA8DF4F5DA8ED7E3C5BFF5C0DA8FDA90F5BBDA91F5C3DA92F5C2DA93D6BAF5C1DA94DA95DA96D4BEF5C4DA97F5CCDA98DA99DA9ADA9BB0CFB5F8DA9CF5C9F5CADA9DC5DCDA9EDA9FDAA0DB40F5C5F5C6DB41DB42F5C7F5CBDB43BEE0F5C8B8FADB44DB45DB46F5D0F5D3DB47DB48DB49BFE7DB4AB9F2F5BCF5CDDB4BDB4CC2B7DB4DDB4EDB4FCCF8DB50BCF9DB51F5CEF5CFF5D1B6E5F5D2DB52F5D5DB53DB54DB55DB56DB57DB58DB59F5BDDB5ADB5BDB5CF5D4D3BBDB5DB3ECDB5EDB5FCCA4DB60DB61DB62DB63F5D6DB64DB65DB66DB67DB68DB69DB6ADB6BF5D7BEE1F5D8DB6CDB6DCCDFF5DBDB6EDB6FDB70DB71DB72B2C8D7D9DB73F5D9DB74F5DAF5DCDB75F5E2DB76DB77DB78F5E0DB79DB7ADB7BF5DFF5DDDB7CDB7DF5E1DB7EDB80F5DEF5E4F5E5DB81CCE3DB82DB83E5BFB5B8F5E3F5E8CCA3DB84DB85DB86DB87DB88F5E6F5E7DB89DB8ADB8BDB8CDB8DDB8EF5BEDB8FDB90DB91DB92DB93DB94DB95DB96DB97DB98DB99DB9AB1C4DB9BDB9CF5BFDB9DDB9EB5C5B2E4DB9FF5ECF5E9DBA0B6D7DC40F5EDDC41F5EADC42DC43DC44DC45DC46F5EBDC47DC48B4DADC49D4EADC4ADC4BDC4CF5EEDC4DB3F9DC4EDC4FDC50DC51DC52DC53DC54F5EFF5F1DC55DC56DC57F5F0DC58DC59DC5ADC5BDC5CDC5DDC5EF5F2DC5FF5F3DC60DC61DC62DC63DC64DC65DC66DC67DC68DC69DC6ADC6BC9EDB9AADC6CDC6DC7FBDC6EDC6FB6E3DC70DC71DC72DC73DC74DC75DC76CCC9DC77DC78DC79DC7ADC7BDC7CDC7DDC7EDC80DC81DC82DC83DC84DC85DC86DC87DC88DC89DC8AEAA6DC8BDC8CDC8DDC8EDC8FDC90DC91DC92DC93DC94DC95DC96DC97DC98DC99DC9ADC9BDC9CDC9DDC9EDC9FDCA0DD40DD41DD42DD43DD44DD45DD46DD47DD48DD49DD4ADD4BDD4CDD4DDD4EDD4FDD50DD51DD52DD53DD54DD55DD56DD57DD58DD59DD5ADD5BDD5CDD5DDD5EDD5FDD60DD61DD62DD63DD64DD65DD66DD67DD68DD69DD6ADD6BDD6CDD6DDD6EDD6FDD70DD71DD72DD73DD74DD75DD76DD77DD78DD79DD7ADD7BDD7CDD7DDD7EDD80DD81DD82DD83DD84DD85DD86DD87DD88DD89DD8ADD8BDD8CDD8DDD8EDD8FDD90DD91DD92DD93DD94DD95DD96DD97DD98DD99DD9ADD9BDD9CDD9DDD9EDD9FDDA0DE40DE41DE42DE43DE44DE45DE46DE47DE48DE49DE4ADE4BDE4CDE4DDE4EDE4FDE50DE51DE52DE53DE54DE55DE56DE57DE58DE59DE5ADE5BDE5CDE5DDE5EDE5FDE60B3B5D4FEB9ECD0F9DE61E9EDD7AAE9EEC2D6C8EDBAE4E9EFE9F0E9F1D6E1E9F2E9F3E9F5E9F4E9F6E9F7C7E1E9F8D4D8E9F9BDCEDE62E9FAE9FBBDCFE9FCB8A8C1BEE9FDB1B2BBD4B9F5E9FEDE63EAA1EAA2EAA3B7F8BCADDE64CAE4E0CED4AFCFBDD5B7EAA4D5DEEAA5D0C1B9BCDE65B4C7B1D9DE66DE67DE68C0B1DE69DE6ADE6BDE6CB1E6B1E7DE6DB1E8DE6EDE6FDE70DE71B3BDC8E8DE72DE73DE74DE75E5C1DE76DE77B1DFDE78DE79DE7AC1C9B4EFDE7BDE7CC7A8D3D8DE7DC6F9D1B8DE7EB9FDC2F5DE80DE81DE82DE83DE84D3ADDE85D4CBBDFCDE86E5C2B7B5E5C3DE87DE88BBB9D5E2DE89BDF8D4B6CEA5C1ACB3D9DE8ADE8BCCF6DE8CE5C6E5C4E5C8DE8DE5CAE5C7B5CFC6C8DE8EB5FCE5C5DE8FCAF6DE90DE91E5C9DE92DE93DE94C3D4B1C5BCA3DE95DE96DE97D7B7DE98DE99CDCBCBCDCACACCD3E5CCE5CBC4E6DE9ADE9BD1A1D1B7E5CDDE9CE5D0DE9DCDB8D6F0E5CFB5DDDE9ECDBEDE9FE5D1B6BADEA0DF40CDA8B9E4DF41CAC5B3D1CBD9D4ECE5D2B7EADF42DF43DF44E5CEDF45DF46DF47DF48DF49DF4AE5D5B4FEE5D6DF4BDF4CDF4DDF4EDF4FE5D3E5D4DF50D2DDDF51DF52C2DFB1C6DF53D3E2DF54DF55B6DDCBECDF56E5D7DF57DF58D3F6DF59DF5ADF5BDF5CDF5DB1E9DF5EB6F4E5DAE5D8E5D9B5C0DF5FDF60DF61D2C5E5DCDF62DF63E5DEDF64DF65DF66DF67DF68DF69E5DDC7B2DF6AD2A3DF6BDF6CE5DBDF6DDF6EDF6FDF70D4E2D5DADF71DF72DF73DF74DF75E5E0D7F1DF76DF77DF78DF79DF7ADF7BDF7CE5E1DF7DB1DCD1FBDF7EE5E2E5E4DF80DF81DF82DF83E5E3DF84DF85E5E5DF86DF87DF88DF89DF8AD2D8DF8BB5CBDF8CE7DFDF8DDAF5DF8EDAF8DF8FDAF6DF90DAF7DF91DF92DF93DAFAD0CFC4C7DF94DF95B0EEDF96DF97DF98D0B0DF99DAF9DF9AD3CABAAADBA2C7F1DF9BDAFCDAFBC9DBDAFDDF9CDBA1D7DEDAFEC1DADF9DDF9EDBA5DF9FDFA0D3F4E040E041DBA7DBA4E042DBA8E043E044BDBCE045E046E047C0C9DBA3DBA6D6A3E048DBA9E049E04AE04BDBADE04CE04DE04EDBAEDBACBAC2E04FE050E051BFA4DBABE052E053E054DBAAD4C7B2BFE055E056DBAFE057B9F9E058DBB0E059E05AE05BE05CB3BBE05DE05EE05FB5A6E060E061E062E063B6BCDBB1E064E065E066B6F5E067DBB2E068E069E06AE06BE06CE06DE06EE06FE070E071E072E073E074E075E076E077E078E079E07AE07BB1C9E07CE07DE07EE080DBB4E081E082E083DBB3DBB5E084E085E086E087E088E089E08AE08BE08CE08DE08EDBB7E08FDBB6E090E091E092E093E094E095E096DBB8E097E098E099E09AE09BE09CE09DE09EE09FDBB9E0A0E140DBBAE141E142D3CFF4FAC7F5D7C3C5E4F4FCF4FDF4FBE143BEC6E144E145E146E147D0EFE148E149B7D3E14AE14BD4CDCCAAE14CE14DF5A2F5A1BAA8F4FECBD6E14EE14FE150F5A4C0D2E151B3EAE152CDAAF5A5F5A3BDB4F5A8E153F5A9BDCDC3B8BFE1CBE1F5AAE154E155E156F5A6F5A7C4F0E157E158E159E15AE15BF5ACE15CB4BCE15DD7EDE15EB4D7F5ABF5AEE15FE160F5ADF5AFD0D1E161E162E163E164E165E166E167C3D1C8A9E168E169E16AE16BE16CE16DF5B0F5B1E16EE16FE170E171E172E173F5B2E174E175F5B3F5B4F5B5E176E177E178E179F5B7F5B6E17AE17BE17CE17DF5B8E17EE180E181E182E183E184E185E186E187E188E189E18AB2C9E18BD3D4CACDE18CC0EFD6D8D2B0C1BFE18DBDF0E18EE18FE190E191E192E193E194E195E196E197B8AAE198E199E19AE19BE19CE19DE19EE19FE1A0E240E241E242E243E244E245E246E247E248E249E24AE24BE24CE24DE24EE24FE250E251E252E253E254E255E256E257E258E259E25AE25BE25CE25DE25EE25FE260E261E262E263E264E265E266E267E268E269E26AE26BE26CE26DE26EE26FE270E271E272E273E274E275E276E277E278E279E27AE27BE27CE27DE27EE280E281E282E283E284E285E286E287E288E289E28AE28BE28CE28DE28EE28FE290E291E292E293E294E295E296E297E298E299E29AE29BE29CE29DE29EE29FE2A0E340E341E342E343E344E345E346E347E348E349E34AE34BE34CE34DE34EE34FE350E351E352E353E354E355E356E357E358E359E35AE35BE35CE35DE35EE35FE360E361E362E363E364E365E366E367E368E369E36AE36BE36CE36DBCF8E36EE36FE370E371E372E373E374E375E376E377E378E379E37AE37BE37CE37DE37EE380E381E382E383E384E385E386E387F6C6E388E389E38AE38BE38CE38DE38EE38FE390E391E392E393E394E395E396E397E398E399E39AE39BE39CE39DE39EE39FE3A0E440E441E442E443E444E445F6C7E446E447E448E449E44AE44BE44CE44DE44EE44FE450E451E452E453E454E455E456E457E458E459E45AE45BE45CE45DE45EF6C8E45FE460E461E462E463E464E465E466E467E468E469E46AE46BE46CE46DE46EE46FE470E471E472E473E474E475E476E477E478E479E47AE47BE47CE47DE47EE480E481E482E483E484E485E486E487E488E489E48AE48BE48CE48DE48EE48FE490E491E492E493E494E495E496E497E498E499E49AE49BE49CE49DE49EE49FE4A0E540E541E542E543E544E545E546E547E548E549E54AE54BE54CE54DE54EE54FE550E551E552E553E554E555E556E557E558E559E55AE55BE55CE55DE55EE55FE560E561E562E563E564E565E566E567E568E569E56AE56BE56CE56DE56EE56FE570E571E572E573F6C9E574E575E576E577E578E579E57AE57BE57CE57DE57EE580E581E582E583E584E585E586E587E588E589E58AE58BE58CE58DE58EE58FE590E591E592E593E594E595E596E597E598E599E59AE59BE59CE59DE59EE59FF6CAE5A0E640E641E642E643E644E645E646E647E648E649E64AE64BE64CE64DE64EE64FE650E651E652E653E654E655E656E657E658E659E65AE65BE65CE65DE65EE65FE660E661E662F6CCE663E664E665E666E667E668E669E66AE66BE66CE66DE66EE66FE670E671E672E673E674E675E676E677E678E679E67AE67BE67CE67DE67EE680E681E682E683E684E685E686E687E688E689E68AE68BE68CE68DE68EE68FE690E691E692E693E694E695E696E697E698E699E69AE69BE69CE69DF6CBE69EE69FE6A0E740E741E742E743E744E745E746E747F7E9E748E749E74AE74BE74CE74DE74EE74FE750E751E752E753E754E755E756E757E758E759E75AE75BE75CE75DE75EE75FE760E761E762E763E764E765E766E767E768E769E76AE76BE76CE76DE76EE76FE770E771E772E773E774E775E776E777E778E779E77AE77BE77CE77DE77EE780E781E782E783E784E785E786E787E788E789E78AE78BE78CE78DE78EE78FE790E791E792E793E794E795E796E797E798E799E79AE79BE79CE79DE79EE79FE7A0E840E841E842E843E844E845E846E847E848E849E84AE84BE84CE84DE84EF6CDE84FE850E851E852E853E854E855E856E857E858E859E85AE85BE85CE85DE85EE85FE860E861E862E863E864E865E866E867E868E869E86AE86BE86CE86DE86EE86FE870E871E872E873E874E875E876E877E878E879E87AF6CEE87BE87CE87DE87EE880E881E882E883E884E885E886E887E888E889E88AE88BE88CE88DE88EE88FE890E891E892E893E894EEC4EEC5EEC6D5EBB6A4EEC8EEC7EEC9EECAC7A5EECBEECCE895B7B0B5F6EECDEECFE896EECEE897B8C6EED0EED1EED2B6DBB3AED6D3C4C6B1B5B8D6EED3EED4D4BFC7D5BEFBCED9B9B3EED6EED5EED8EED7C5A5EED9EEDAC7AEEEDBC7AFEEDCB2A7EEDDEEDEEEDFEEE0EEE1D7EAEEE2EEE3BCD8EEE4D3CBCCFAB2ACC1E5EEE5C7A6C3ADE898EEE6EEE7EEE8EEE9EEEAEEEBEEECE899EEEDEEEEEEEFE89AE89BEEF0EEF1EEF2EEF4EEF3E89CEEF5CDADC2C1EEF6EEF7EEF8D5A1EEF9CFB3EEFAEEFBE89DEEFCEEFDEFA1EEFEEFA2B8F5C3FAEFA3EFA4BDC2D2BFB2F9EFA5EFA6EFA7D2F8EFA8D6FDEFA9C6CCE89EEFAAEFABC1B4EFACCFFACBF8EFAEEFADB3FAB9F8EFAFEFB0D0E2EFB1EFB2B7E6D0BFEFB3EFB4EFB5C8F1CCE0EFB6EFB7EFB8EFB9EFBAD5E0EFBBB4EDC3AAEFBCE89FEFBDEFBEEFBFE8A0CEFDEFC0C2E0B4B8D7B6BDF5E940CFC7EFC3EFC1EFC2EFC4B6A7BCFCBEE2C3CCEFC5EFC6E941EFC7EFCFEFC8EFC9EFCAC7C2EFF1B6CDEFCBE942EFCCEFCDB6C6C3BEEFCEE943EFD0EFD1EFD2D5F2E944EFD3C4F7E945EFD4C4F8EFD5EFD6B8E4B0F7EFD7EFD8EFD9E946EFDAEFDBEFDCEFDDE947EFDEBEB5EFE1EFDFEFE0E948EFE2EFE3C1CDEFE4EFE5EFE6EFE7EFE8EFE9EFEAEFEBEFECC0D8E949EFEDC1ADEFEEEFEFEFF0E94AE94BCFE2E94CE94DE94EE94FE950E951E952E953B3A4E954E955E956E957E958E959E95AE95BE95CE95DE95EE95FE960E961E962E963E964E965E966E967E968E969E96AE96BE96CE96DE96EE96FE970E971E972E973E974E975E976E977E978E979E97AE97BE97CE97DE97EE980E981E982E983E984E985E986E987E988E989E98AE98BE98CE98DE98EE98FE990E991E992E993E994E995E996E997E998E999E99AE99BE99CE99DE99EE99FE9A0EA40EA41EA42EA43EA44EA45EA46EA47EA48EA49EA4AEA4BEA4CEA4DEA4EEA4FEA50EA51EA52EA53EA54EA55EA56EA57EA58EA59EA5AEA5BC3C5E3C5C9C1E3C6EA5CB1D5CECAB4B3C8F2E3C7CFD0E3C8BCE4E3C9E3CAC3C6D5A2C4D6B9EBCEC5E3CBC3F6E3CCEA5DB7A7B8F3BAD2E3CDE3CED4C4E3CFEA5EE3D0D1CBE3D1E3D2E3D3E3D4D1D6E3D5B2FBC0BBE3D6EA5FC0ABE3D7E3D8E3D9EA60E3DAE3DBEA61B8B7DAE2EA62B6D3EA63DAE4DAE3EA64EA65EA66EA67EA68EA69EA6ADAE6EA6BEA6CEA6DC8EEEA6EEA6FDAE5B7C0D1F4D2F5D5F3BDD7EA70EA71EA72EA73D7E8DAE8DAE7EA74B0A2CDD3EA75DAE9EA76B8BDBCCAC2BDC2A4B3C2DAEAEA77C2AAC4B0BDB5EA78EA79CFDEEA7AEA7BEA7CDAEBC9C2EA7DEA7EEA80EA81EA82B1DDEA83EA84EA85DAECEA86B6B8D4BAEA87B3FDEA88EA89DAEDD4C9CFD5C5E3EA8ADAEEEA8BEA8CEA8DEA8EEA8FDAEFEA90DAF0C1EACCD5CFDDEA91EA92EA93EA94EA95EA96EA97EA98EA99EA9AEA9BEA9CEA9DD3E7C2A1EA9EDAF1EA9FEAA0CBE5EB40DAF2EB41CBE6D2FEEB42EB43EB44B8F4EB45EB46DAF3B0AFCFB6EB47EB48D5CFEB49EB4AEB4BEB4CEB4DEB4EEB4FEB50EB51EB52CBEDEB53EB54EB55EB56EB57EB58EB59EB5ADAF4EB5BEB5CE3C4EB5DEB5EC1A5EB5FEB60F6BFEB61EB62F6C0F6C1C4D1EB63C8B8D1E3EB64EB65D0DBD1C5BCAFB9CDEB66EFF4EB67EB68B4C6D3BAF6C2B3FBEB69EB6AF6C3EB6BEB6CB5F1EB6DEB6EEB6FEB70EB71EB72EB73EB74EB75EB76F6C5EB77EB78EB79EB7AEB7BEB7CEB7DD3EAF6A7D1A9EB7EEB80EB81EB82F6A9EB83EB84EB85F6A8EB86EB87C1E3C0D7EB88B1A2EB89EB8AEB8BEB8CCEEDEB8DD0E8F6ABEB8EEB8FCFF6EB90F6AAD5F0F6ACC3B9EB91EB92EB93BBF4F6AEF6ADEB94EB95EB96C4DEEB97EB98C1D8EB99EB9AEB9BEB9CEB9DCBAAEB9ECFBCEB9FEBA0EC40EC41EC42EC43EC44EC45EC46EC47EC48F6AFEC49EC4AF6B0EC4BEC4CF6B1EC4DC2B6EC4EEC4FEC50EC51EC52B0D4C5F9EC53EC54EC55EC56F6B2EC57EC58EC59EC5AEC5BEC5CEC5DEC5EEC5FEC60EC61EC62EC63EC64EC65EC66EC67EC68EC69C7E0F6A6EC6AEC6BBEB8EC6CEC6DBEB2EC6EB5E5EC6FEC70B7C7EC71BFBFC3D2C3E6EC72EC73D8CCEC74EC75EC76B8EFEC77EC78EC79EC7AEC7BEC7CEC7DEC7EEC80BDF9D1A5EC81B0D0EC82EC83EC84EC85EC86F7B0EC87EC88EC89EC8AEC8BEC8CEC8DEC8EF7B1EC8FEC90EC91EC92EC93D0ACEC94B0B0EC95EC96EC97F7B2F7B3EC98F7B4EC99EC9AEC9BC7CAEC9CEC9DEC9EEC9FECA0ED40ED41BECFED42ED43F7B7ED44ED45ED46ED47ED48ED49ED4AF7B6ED4BB1DEED4CF7B5ED4DED4EF7B8ED4FF7B9ED50ED51ED52ED53ED54ED55ED56ED57ED58ED59ED5AED5BED5CED5DED5EED5FED60ED61ED62ED63ED64ED65ED66ED67ED68ED69ED6AED6BED6CED6DED6EED6FED70ED71ED72ED73ED74ED75ED76ED77ED78ED79ED7AED7BED7CED7DED7EED80ED81CEA4C8CDED82BAABE8B8E8B9E8BABEC2ED83ED84ED85ED86ED87D2F4ED88D4CFC9D8ED89ED8AED8BED8CED8DED8EED8FED90ED91ED92ED93ED94ED95ED96ED97ED98ED99ED9AED9BED9CED9DED9EED9FEDA0EE40EE41EE42EE43EE44EE45EE46EE47EE48EE49EE4AEE4BEE4CEE4DEE4EEE4FEE50EE51EE52EE53EE54EE55EE56EE57EE58EE59EE5AEE5BEE5CEE5DEE5EEE5FEE60EE61EE62EE63EE64EE65EE66EE67EE68EE69EE6AEE6BEE6CEE6DEE6EEE6FEE70EE71EE72EE73EE74EE75EE76EE77EE78EE79EE7AEE7BEE7CEE7DEE7EEE80EE81EE82EE83EE84EE85EE86EE87EE88EE89EE8AEE8BEE8CEE8DEE8EEE8FEE90EE91EE92EE93EE94EE95EE96EE97EE98EE99EE9AEE9BEE9CEE9DEE9EEE9FEEA0EF40EF41EF42EF43EF44EF45D2B3B6A5C7EAF1FCCFEECBB3D0EBE7EFCDE7B9CBB6D9F1FDB0E4CBCCF1FED4A4C2ADC1ECC6C4BEB1F2A1BCD5EF46F2A2F2A3EF47F2A4D2C3C6B5EF48CDC7F2A5EF49D3B1BFC5CCE2EF4AF2A6F2A7D1D5B6EEF2A8F2A9B5DFF2AAF2ABEF4BB2FCF2ACF2ADC8A7EF4CEF4DEF4EEF4FEF50EF51EF52EF53EF54EF55EF56EF57EF58EF59EF5AEF5BEF5CEF5DEF5EEF5FEF60EF61EF62EF63EF64EF65EF66EF67EF68EF69EF6AEF6BEF6CEF6DEF6EEF6FEF70EF71B7E7EF72EF73ECA9ECAAECABEF74ECACEF75EF76C6AEECADECAEEF77EF78EF79B7C9CAB3EF7AEF7BEF7CEF7DEF7EEF80EF81E2B8F7CFEF82EF83EF84EF85EF86EF87EF88EF89EF8AEF8BEF8CEF8DEF8EEF8FEF90EF91EF92EF93EF94EF95EF96EF97EF98EF99EF9AEF9BEF9CEF9DEF9EEF9FEFA0F040F041F042F043F044F7D0F045F046B2CDF047F048F049F04AF04BF04CF04DF04EF04FF050F051F052F053F054F055F056F057F058F059F05AF05BF05CF05DF05EF05FF060F061F062F063F7D1F064F065F066F067F068F069F06AF06BF06CF06DF06EF06FF070F071F072F073F074F075F076F077F078F079F07AF07BF07CF07DF07EF080F081F082F083F084F085F086F087F088F089F7D3F7D2F08AF08BF08CF08DF08EF08FF090F091F092F093F094F095F096E2BBF097BCA2F098E2BCE2BDE2BEE2BFE2C0E2C1B7B9D2FBBDA4CACEB1A5CBC7F099E2C2B6FCC8C4E2C3F09AF09BBDC8F09CB1FDE2C4F09DB6F6E2C5C4D9F09EF09FE2C6CFDAB9DDE2C7C0A1F0A0E2C8B2F6F140E2C9F141C1F3E2CAE2CBC2F8E2CCE2CDE2CECAD7D8B8D9E5CFE3F142F143F144F145F146F147F148F149F14AF14BF14CF0A5F14DF14EDCB0F14FF150F151F152F153F154F155F156F157F158F159F15AF15BF15CF15DF15EF15FF160F161F162F163F164F165F166F167F168F169F16AF16BF16CF16DF16EF16FF170F171F172F173F174F175F176F177F178F179F17AF17BF17CF17DF17EF180F181F182F183F184F185F186F187F188F189F18AF18BF18CF18DF18EF18FF190F191F192F193F194F195F196F197F198F199F19AF19BF19CF19DF19EF19FF1A0F240F241F242F243F244F245F246F247F248F249F24AF24BF24CF24DF24EF24FF250F251F252F253F254F255F256F257F258F259F25AF25BF25CF25DF25EF25FF260F261F262F263F264F265F266F267F268F269F26AF26BF26CF26DF26EF26FF270F271F272F273F274F275F276F277F278F279F27AF27BF27CF27DF27EF280F281F282F283F284F285F286F287F288F289F28AF28BF28CF28DF28EF28FF290F291F292F293F294F295F296F297F298F299F29AF29BF29CF29DF29EF29FF2A0F340F341F342F343F344F345F346F347F348F349F34AF34BF34CF34DF34EF34FF350F351C2EDD4A6CDD4D1B1B3DBC7FDF352B2B5C2BFE6E0CABBE6E1E6E2BED4E6E3D7A4CDD5E6E5BCDDE6E4E6E6E6E7C2EEF353BDBEE6E8C2E6BAA7E6E9F354E6EAB3D2D1E9F355F356BFA5E6EBC6EFE6ECE6EDF357F358E6EEC6ADE6EFF359C9A7E6F0E6F1E6F2E5B9E6F3E6F4C2E2E6F5E6F6D6E8E6F7F35AE6F8B9C7F35BF35CF35DF35EF35FF360F361F7BBF7BAF362F363F364F365F7BEF7BCBAA1F366F7BFF367F7C0F368F369F36AF7C2F7C1F7C4F36BF36CF7C3F36DF36EF36FF370F371F7C5F7C6F372F373F374F375F7C7F376CBE8F377F378F379F37AB8DFF37BF37CF37DF37EF380F381F7D4F382F7D5F383F384F385F386F7D6F387F388F389F38AF7D8F38BF7DAF38CF7D7F38DF38EF38FF390F391F392F393F394F395F7DBF396F7D9F397F398F399F39AF39BF39CF39DD7D7F39EF39FF3A0F440F7DCF441F442F443F444F445F446F7DDF447F448F449F7DEF44AF44BF44CF44DF44EF44FF450F451F452F453F454F7DFF455F456F457F7E0F458F459F45AF45BF45CF45DF45EF45FF460F461F462DBCBF463F464D8AAF465F466F467F468F469F46AF46BF46CE5F7B9EDF46DF46EF46FF470BFFDBBEAF7C9C6C7F7C8F471F7CAF7CCF7CBF472F473F474F7CDF475CEBAF476F7CEF477F478C4A7F479F47AF47BF47CF47DF47EF480F481F482F483F484F485F486F487F488F489F48AF48BF48CF48DF48EF48FF490F491F492F493F494F495F496F497F498F499F49AF49BF49CF49DF49EF49FF4A0F540F541F542F543F544F545F546F547F548F549F54AF54BF54CF54DF54EF54FF550F551F552F553F554F555F556F557F558F559F55AF55BF55CF55DF55EF55FF560F561F562F563F564F565F566F567F568F569F56AF56BF56CF56DF56EF56FF570F571F572F573F574F575F576F577F578F579F57AF57BF57CF57DF57EF580F581F582F583F584F585F586F587F588F589F58AF58BF58CF58DF58EF58FF590F591F592F593F594F595F596F597F598F599F59AF59BF59CF59DF59EF59FF5A0F640F641F642F643F644F645F646F647F648F649F64AF64BF64CF64DF64EF64FF650F651F652F653F654F655F656F657F658F659F65AF65BF65CF65DF65EF65FF660F661F662F663F664F665F666F667F668F669F66AF66BF66CF66DF66EF66FF670F671F672F673F674F675F676F677F678F679F67AF67BF67CF67DF67EF680F681F682F683F684F685F686F687F688F689F68AF68BF68CF68DF68EF68FF690F691F692F693F694F695F696F697F698F699F69AF69BF69CF69DF69EF69FF6A0F740F741F742F743F744F745F746F747F748F749F74AF74BF74CF74DF74EF74FF750F751F752F753F754F755F756F757F758F759F75AF75BF75CF75DF75EF75FF760F761F762F763F764F765F766F767F768F769F76AF76BF76CF76DF76EF76FF770F771F772F773F774F775F776F777F778F779F77AF77BF77CF77DF77EF780D3E3F781F782F6CFF783C2B3F6D0F784F785F6D1F6D2F6D3F6D4F786F787F6D6F788B1ABF6D7F789F6D8F6D9F6DAF78AF6DBF6DCF78BF78CF78DF78EF6DDF6DECFCAF78FF6DFF6E0F6E1F6E2F6E3F6E4C0F0F6E5F6E6F6E7F6E8F6E9F790F6EAF791F6EBF6ECF792F6EDF6EEF6EFF6F0F6F1F6F2F6F3F6F4BEA8F793F6F5F6F6F6F7F6F8F794F795F796F797F798C8FAF6F9F6FAF6FBF6FCF799F79AF6FDF6FEF7A1F7A2F7A3F7A4F7A5F79BF79CF7A6F7A7F7A8B1EEF7A9F7AAF7ABF79DF79EF7ACF7ADC1DBF7AEF79FF7A0F7AFF840F841F842F843F844F845F846F847F848F849F84AF84BF84CF84DF84EF84FF850F851F852F853F854F855F856F857F858F859F85AF85BF85CF85DF85EF85FF860F861F862F863F864F865F866F867F868F869F86AF86BF86CF86DF86EF86FF870F871F872F873F874F875F876F877F878F879F87AF87BF87CF87DF87EF880F881F882F883F884F885F886F887F888F889F88AF88BF88CF88DF88EF88FF890F891F892F893F894F895F896F897F898F899F89AF89BF89CF89DF89EF89FF8A0F940F941F942F943F944F945F946F947F948F949F94AF94BF94CF94DF94EF94FF950F951F952F953F954F955F956F957F958F959F95AF95BF95CF95DF95EF95FF960F961F962F963F964F965F966F967F968F969F96AF96BF96CF96DF96EF96FF970F971F972F973F974F975F976F977F978F979F97AF97BF97CF97DF97EF980F981F982F983F984F985F986F987F988F989F98AF98BF98CF98DF98EF98FF990F991F992F993F994F995F996F997F998F999F99AF99BF99CF99DF99EF99FF9A0FA40FA41FA42FA43FA44FA45FA46FA47FA48FA49FA4AFA4BFA4CFA4DFA4EFA4FFA50FA51FA52FA53FA54FA55FA56FA57FA58FA59FA5AFA5BFA5CFA5DFA5EFA5FFA60FA61FA62FA63FA64FA65FA66FA67FA68FA69FA6AFA6BFA6CFA6DFA6EFA6FFA70FA71FA72FA73FA74FA75FA76FA77FA78FA79FA7AFA7BFA7CFA7DFA7EFA80FA81FA82FA83FA84FA85FA86FA87FA88FA89FA8AFA8BFA8CFA8DFA8EFA8FFA90FA91FA92FA93FA94FA95FA96FA97FA98FA99FA9AFA9BFA9CFA9DFA9EFA9FFAA0FB40FB41FB42FB43FB44FB45FB46FB47FB48FB49FB4AFB4BFB4CFB4DFB4EFB4FFB50FB51FB52FB53FB54FB55FB56FB57FB58FB59FB5AFB5BC4F1F0AFBCA6F0B0C3F9FB5CC5B8D1BBFB5DF0B1F0B2F0B3F0B4F0B5D1BCFB5ED1ECFB5FF0B7F0B6D4A7FB60CDD2F0B8F0BAF0B9F0BBF0BCFB61FB62B8EBF0BDBAE8FB63F0BEF0BFBEE9F0C0B6ECF0C1F0C2F0C3F0C4C8B5F0C5F0C6FB64F0C7C5F4FB65F0C8FB66FB67FB68F0C9FB69F0CAF7BDFB6AF0CBF0CCF0CDFB6BF0CEFB6CFB6DFB6EFB6FF0CFBAD7FB70F0D0F0D1F0D2F0D3F0D4F0D5F0D6F0D8FB71FB72D3A5F0D7FB73F0D9FB74FB75FB76FB77FB78FB79FB7AFB7BFB7CFB7DF5BAC2B9FB7EFB80F7E4FB81FB82FB83FB84F7E5F7E6FB85FB86F7E7FB87FB88FB89FB8AFB8BFB8CF7E8C2B4FB8DFB8EFB8FFB90FB91FB92FB93FB94FB95F7EAFB96F7EBFB97FB98FB99FB9AFB9BFB9CC2F3FB9DFB9EFB9FFBA0FC40FC41FC42FC43FC44FC45FC46FC47FC48F4F0FC49FC4AFC4BF4EFFC4CFC4DC2E9FC4EF7E1F7E2FC4FFC50FC51FC52FC53BBC6FC54FC55FC56FC57D9E4FC58FC59FC5ACAF2C0E8F0A4FC5BBADAFC5CFC5DC7ADFC5EFC5FFC60C4ACFC61FC62F7ECF7EDF7EEFC63F7F0F7EFFC64F7F1FC65FC66F7F4FC67F7F3FC68F7F2F7F5FC69FC6AFC6BFC6CF7F6FC6DFC6EFC6FFC70FC71FC72FC73FC74FC75EDE9FC76EDEAEDEBFC77F6BCFC78FC79FC7AFC7BFC7CFC7DFC7EFC80FC81FC82FC83FC84F6BDFC85F6BEB6A6FC86D8BEFC87FC88B9C4FC89FC8AFC8BD8BBFC8CDCB1FC8DFC8EFC8FFC90FC91FC92CAF3FC93F7F7FC94FC95FC96FC97FC98FC99FC9AFC9BFC9CF7F8FC9DFC9EF7F9FC9FFCA0FD40FD41FD42FD43FD44F7FBFD45F7FAFD46B1C7FD47F7FCF7FDFD48FD49FD4AFD4BFD4CF7FEFD4DFD4EFD4FFD50FD51FD52FD53FD54FD55FD56FD57C6EBECB4FD58FD59FD5AFD5BFD5CFD5DFD5EFD5FFD60FD61FD62FD63FD64FD65FD66FD67FD68FD69FD6AFD6BFD6CFD6DFD6EFD6FFD70FD71FD72FD73FD74FD75FD76FD77FD78FD79FD7AFD7BFD7CFD7DFD7EFD80FD81FD82FD83FD84FD85B3DDF6B3FD86FD87F6B4C1E4F6B5F6B6F6B7F6B8F6B9F6BAC8A3F6BBFD88FD89FD8AFD8BFD8CFD8DFD8EFD8FFD90FD91FD92FD93C1FAB9A8EDE8FD94FD95FD96B9EAD9DFFD97FD98FD99FD9AFD9';\r\n\r\n            for (var i = 0; i < str.length; i++) {\r\n                var c = str.charAt(i),\r\n                    code = str.charCodeAt(i);\r\n                if (c == \" \") strOut += \"+\";\r\n                else if (code >= 19968 && code <= 40869) {\r\n                    var index = code - 19968;\r\n                    strOut += \"%\" + z.substr(index * 4, 2) + \"%\" + z.substr(index * 4 + 2, 2);\r\n                } else {\r\n                    strOut += \"%\" + str.charCodeAt(i).toString(16);\r\n                }\r\n            }\r\n            return strOut;\r\n        },\r\n        /* 改变图片大小 */\r\n        scale: function (img, w, h) {\r\n            var ow = img.width,\r\n                oh = img.height;\r\n\r\n            if (ow >= oh) {\r\n                img.width = w * ow / oh;\r\n                img.height = h;\r\n                img.style.marginLeft = '-' + parseInt((img.width - w) / 2) + 'px';\r\n            } else {\r\n                img.width = w;\r\n                img.height = h * oh / ow;\r\n                img.style.marginTop = '-' + parseInt((img.height - h) / 2) + 'px';\r\n            }\r\n        },\r\n        getImageData: function(){\r\n            var _this = this,\r\n                key = $G('searchTxt').value,\r\n                type = $G('searchType').value,\r\n                keepOriginName = editor.options.keepOriginName ? \"1\" : \"0\",\r\n                url = \"http://image.baidu.com/i?ct=201326592&cl=2&lm=-1&st=-1&tn=baiduimagejson&istype=2&rn=32&fm=index&pv=&word=\" + _this.encodeToGb2312(key) + type + \"&keeporiginname=\" + keepOriginName + \"&\" + +new Date;\r\n\r\n            $G('searchListUl').innerHTML = lang.searchLoading;\r\n            ajax.request(url, {\r\n                'dataType': 'jsonp',\r\n                'charset': 'GB18030',\r\n                'onsuccess':function(json){\r\n                    var list = [];\r\n                    if(json && json.data) {\r\n                        for(var i = 0; i < json.data.length; i++) {\r\n                            if(json.data[i].objURL) {\r\n                                list.push({\r\n                                    title: json.data[i].fromPageTitleEnc,\r\n                                    src: json.data[i].objURL,\r\n                                    url: json.data[i].fromURL\r\n                                });\r\n                            }\r\n                        }\r\n                    }\r\n                    _this.setList(list);\r\n                },\r\n                'onerror':function(){\r\n                    $G('searchListUl').innerHTML = lang.searchRetry;\r\n                }\r\n            });\r\n        },\r\n        /* 添加图片到列表界面上 */\r\n        setList: function (list) {\r\n            var i, item, p, img, link, _this = this,\r\n                listUl = $G('searchListUl');\r\n\r\n            listUl.innerHTML = '';\r\n            if(list.length) {\r\n                for (i = 0; i < list.length; i++) {\r\n                    item = document.createElement('li');\r\n                    p = document.createElement('p');\r\n                    img = document.createElement('img');\r\n                    link = document.createElement('a');\r\n\r\n                    img.onload = function () {\r\n                        _this.scale(this, 113, 113);\r\n                    };\r\n                    img.width = 113;\r\n                    img.setAttribute('src', list[i].src);\r\n\r\n                    link.href = list[i].url;\r\n                    link.target = '_blank';\r\n                    link.title = list[i].title;\r\n                    link.innerHTML = list[i].title;\r\n\r\n                    p.appendChild(img);\r\n                    item.appendChild(p);\r\n                    item.appendChild(link);\r\n                    listUl.appendChild(item);\r\n                }\r\n            } else {\r\n                listUl.innerHTML = lang.searchRetry;\r\n            }\r\n        },\r\n        getInsertList: function () {\r\n            var child,\r\n                src,\r\n                align = getAlign(),\r\n                list = [],\r\n                items = $G('searchListUl').children;\r\n            for(var i = 0; i < items.length; i++) {\r\n                child = items[i].firstChild && items[i].firstChild.firstChild;\r\n                if(child.tagName && child.tagName.toLowerCase() == 'img' && domUtils.hasClass(items[i], 'selected')) {\r\n                    src = child.src;\r\n                    list.push({\r\n                        src: src,\r\n                        _src: src,\r\n                        alt: src.substr(src.lastIndexOf('/') + 1),\r\n                        floatStyle: align\r\n                    });\r\n                }\r\n            }\r\n            return list;\r\n        }\r\n    };\r\n\r\n})();\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/insertframe/insertframe.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\r\n    <title></title>\r\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n    <style type=\"text/css\">\r\n        .warp {width: 320px;height: 153px;margin-left:5px;padding: 20px 0 0 15px;position: relative;}\r\n        #url {width: 290px; margin-bottom: 2px; margin-left: -6px; margin-left: -2px\\9;*margin-left:0;_margin-left:0; }\r\n        .format span{display: inline-block; width: 58px;text-align: center; zoom:1;}\r\n        table td{padding:5px 0;}\r\n        #align{width: 65px;height: 23px;line-height: 22px;}\r\n    </style>\r\n</head>\r\n<body>\r\n<div class=\"warp\">\r\n        <table width=\"300\" cellpadding=\"0\" cellspacing=\"0\">\r\n            <tr>\r\n                <td colspan=\"2\" class=\"format\">\r\n                    <span><var id=\"lang_input_address\"></var></span>\r\n                    <input style=\"width:200px\" id=\"url\" type=\"text\" value=\"\"/>\r\n                </td>\r\n            </tr>\r\n            <tr>\r\n                <td colspan=\"2\" class=\"format\"><span><var id=\"lang_input_width\"></var></span><input style=\"width:200px\" type=\"text\" id=\"width\"/> px</td>\r\n\r\n            </tr>\r\n            <tr>\r\n                <td colspan=\"2\" class=\"format\"><span><var id=\"lang_input_height\"></var></span><input style=\"width:200px\" type=\"text\" id=\"height\"/> px</td>\r\n            </tr>\r\n            <tr>\r\n                <td><span><var id=\"lang_input_isScroll\"></var></span><input type=\"checkbox\" id=\"scroll\"/> </td>\r\n                <td><span><var id=\"lang_input_frameborder\"></var></span><input type=\"checkbox\" id=\"frameborder\"/> </td>\r\n            </tr>\r\n\r\n            <tr>\r\n                <td colspan=\"2\"><span><var id=\"lang_input_alignMode\"></var></span>\r\n                    <select id=\"align\">\r\n                        <option value=\"\"></option>\r\n                        <option value=\"left\"></option>\r\n                        <option value=\"right\"></option>\r\n                    </select>\r\n                </td>\r\n            </tr>\r\n        </table>\r\n</div>\r\n<script type=\"text/javascript\">\r\n    var iframe = editor._iframe;\r\n    if(iframe){\r\n        $G(\"url\").value = iframe.getAttribute(\"src\")||\"\";\r\n        $G(\"width\").value = iframe.getAttribute(\"width\")||iframe.style.width.replace(\"px\",\"\")||\"\";\r\n        $G(\"height\").value = iframe.getAttribute(\"height\") || iframe.style.height.replace(\"px\",\"\") ||\"\";\r\n        $G(\"scroll\").checked = (iframe.getAttribute(\"scrolling\") == \"yes\") ? true : false;\r\n        $G(\"frameborder\").checked = (iframe.getAttribute(\"frameborder\") == \"1\") ? true : false;\r\n        $G(\"align\").value = iframe.align ? iframe.align : \"\";\r\n    }\r\n    function queding(){\r\n        var  url = $G(\"url\").value.replace(/^\\s*|\\s*$/ig,\"\"),\r\n                width = $G(\"width\").value,\r\n                height = $G(\"height\").value,\r\n                scroll = $G(\"scroll\"),\r\n                frameborder = $G(\"frameborder\"),\r\n                float = $G(\"align\").value,\r\n                newIframe = editor.document.createElement(\"iframe\"),\r\n                div;\r\n        if(!url){\r\n            alert(lang.enterAddress);\r\n            return false;\r\n        }\r\n        newIframe.setAttribute(\"src\",/http:\\/\\/|https:\\/\\//ig.test(url) ? url : \"http://\"+url);\r\n        /^[1-9]+[.]?\\d*$/g.test( width ) ? newIframe.setAttribute(\"width\",width) : \"\";\r\n        /^[1-9]+[.]?\\d*$/g.test( height ) ? newIframe.setAttribute(\"height\",height) : \"\";\r\n        scroll.checked ?  newIframe.setAttribute(\"scrolling\",\"yes\") : newIframe.setAttribute(\"scrolling\",\"no\");\r\n        frameborder.checked ?  newIframe.setAttribute(\"frameborder\",\"1\",0) : newIframe.setAttribute(\"frameborder\",\"0\",0);\r\n        float ? newIframe.setAttribute(\"align\",float) :  newIframe.setAttribute(\"align\",\"\");\r\n        if(iframe){\r\n            iframe.parentNode.insertBefore(newIframe,iframe);\r\n            domUtils.remove(iframe);\r\n        }else{\r\n            div = editor.document.createElement(\"div\");\r\n            div.appendChild(newIframe);\r\n            editor.execCommand(\"inserthtml\",div.innerHTML);\r\n        }\r\n        editor._iframe = null;\r\n        dialog.close();\r\n    }\r\n    dialog.onok = queding;\r\n    $G(\"url\").onkeydown = function(evt){\r\n        evt = evt || event;\r\n        if(evt.keyCode == 13){\r\n            queding();\r\n        }\r\n    };\r\n    $focus($G( \"url\" ));\r\n\r\n</script>\r\n</body>\r\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/internal.js",
    "content": "(function () {\r\n    var parent = window.parent;\r\n    //dialog对象\r\n    dialog = parent.$EDITORUI[window.frameElement.id.replace( /_iframe$/, '' )];\r\n    //当前打开dialog的编辑器实例\r\n    editor = dialog.editor;\r\n\r\n    UE = parent.UE;\r\n\r\n    domUtils = UE.dom.domUtils;\r\n\r\n    utils = UE.utils;\r\n\r\n    browser = UE.browser;\r\n\r\n    ajax = UE.ajax;\r\n\r\n    $G = function ( id ) {\r\n        return document.getElementById( id )\r\n    };\r\n    //focus元素\r\n    $focus = function ( node ) {\r\n        setTimeout( function () {\r\n            if ( browser.ie ) {\r\n                var r = node.createTextRange();\r\n                r.collapse( false );\r\n                r.select();\r\n            } else {\r\n                node.focus()\r\n            }\r\n        }, 0 )\r\n    };\r\n    utils.loadFile(document,{\r\n        href:editor.options.themePath + editor.options.theme + \"/dialogbase.css?cache=\"+Math.random(),\r\n        tag:\"link\",\r\n        type:\"text/css\",\r\n        rel:\"stylesheet\"\r\n    });\r\n    lang = editor.getLang(dialog.className.split( \"-\" )[2]);\r\n    if(lang){\r\n        domUtils.on(window,'load',function () {\r\n\r\n            var langImgPath = editor.options.langPath + editor.options.lang + \"/images/\";\r\n            //针对静态资源\r\n            for ( var i in lang[\"static\"] ) {\r\n                var dom = $G( i );\r\n                if(!dom) continue;\r\n                var tagName = dom.tagName,\r\n                    content = lang[\"static\"][i];\r\n                if(content.src){\r\n                    //clone\r\n                    content = utils.extend({},content,false);\r\n                    content.src = langImgPath + content.src;\r\n                }\r\n                if(content.style){\r\n                    content = utils.extend({},content,false);\r\n                    content.style = content.style.replace(/url\\s*\\(/g,\"url(\" + langImgPath)\r\n                }\r\n                switch ( tagName.toLowerCase() ) {\r\n                    case \"var\":\r\n                        dom.parentNode.replaceChild( document.createTextNode( content ), dom );\r\n                        break;\r\n                    case \"select\":\r\n                        var ops = dom.options;\r\n                        for ( var j = 0, oj; oj = ops[j]; ) {\r\n                            oj.innerHTML = content.options[j++];\r\n                        }\r\n                        for ( var p in content ) {\r\n                            p != \"options\" && dom.setAttribute( p, content[p] );\r\n                        }\r\n                        break;\r\n                    default :\r\n                        domUtils.setAttributes( dom, content);\r\n                }\r\n            }\r\n        } );\r\n    }\r\n\r\n\r\n})();\r\n\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/link/link.html",
    "content": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\r\n        \"http://www.w3.org/TR/html4/loose.dtd\">\r\n<html>\r\n<head>\r\n    <title></title>\r\n    <meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"/>\r\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n    <style type=\"text/css\">\r\n        *{margin:0;padding:0;color: #838383;}\r\n        table{font-size: 12px;margin: 10px;line-height: 30px}\r\n        .txt{width:300px;height:21px;line-height:21px;border:1px solid #d7d7d7;}\r\n    </style>\r\n</head>\r\n<body>\r\n    <table>\r\n        <tr>\r\n            <td><label for=\"text\"> <var id=\"lang_input_text\"></var></label></td>\r\n            <td><input class=\"txt\" id=\"text\" type=\"text\" disabled=\"true\"/></td>\r\n        </tr>\r\n        <tr>\r\n            <td><label for=\"href\"> <var id=\"lang_input_url\"></var></label></td>\r\n            <td><input class=\"txt\" id=\"href\" type=\"text\" /></td>\r\n        </tr>\r\n        <tr>\r\n            <td><label for=\"title\"> <var id=\"lang_input_title\"></var></label></td>\r\n            <td><input class=\"txt\" id=\"title\" type=\"text\"/></td>\r\n        </tr>\r\n        <tr>\r\n             <td colspan=\"2\">\r\n                 <label for=\"target\"><var id=\"lang_input_target\"></var></label>\r\n                 <input id=\"target\" type=\"checkbox\"/>\r\n             </td>\r\n        </tr>\r\n        <tr>\r\n            <td colspan=\"2\" id=\"msg\"></td>\r\n        </tr>\r\n    </table>\r\n<script type=\"text/javascript\">\r\n    var range = editor.selection.getRange(),\r\n        link = range.collapsed ? editor.queryCommandValue( \"link\" ) : editor.selection.getStart(),\r\n        url,\r\n        text = $G('text'),\r\n        rangeLink = domUtils.findParentByTagName(range.getCommonAncestor(),'a',true),\r\n        orgText;\r\n    link = domUtils.findParentByTagName( link, \"a\", true );\r\n    if(link){\r\n        url = utils.html(link.getAttribute( '_href' ) || link.getAttribute( 'href', 2 ));\r\n\r\n        if(rangeLink === link && !link.getElementsByTagName('img').length){\r\n            text.removeAttribute('disabled');\r\n            orgText = text.value = link[browser.ie ? 'innerText':'textContent'];\r\n        }else{\r\n            text.setAttribute('disabled','true');\r\n            text.value = lang.validLink;\r\n        }\r\n\r\n    }else{\r\n        if(range.collapsed){\r\n            text.removeAttribute('disabled');\r\n            text.value = '';\r\n        }else{\r\n            text.setAttribute('disabled','true');\r\n            text.value = lang.validLink;\r\n        }\r\n\r\n    }\r\n    $G(\"title\").value = url ? link.title : \"\";\r\n    $G(\"href\").value = url ? url: '';\r\n    $G(\"target\").checked = url && link.target == \"_blank\" ? true :  false;\r\n    $focus($G(\"href\"));\r\n\r\n    function handleDialogOk(){\r\n        var href =$G('href').value.replace(/^\\s+|\\s+$/g, '');\r\n        if(href){\r\n            if(!hrefStartWith(href,[\"http\",\"/\",\"ftp://\",'#'])) {\r\n                href  = \"http://\" + href;\r\n            }\r\n            var obj = {\r\n                'href' : href,\r\n                'target' : $G(\"target\").checked ? \"_blank\" : '_self',\r\n                'title' : $G(\"title\").value.replace(/^\\s+|\\s+$/g, ''),\r\n                '_href':href\r\n            };\r\n            //修改链接内容的情况太特殊了，所以先做到这里了\r\n            //todo:情况多的时候，做到command里\r\n            if(orgText && text.value != orgText){\r\n                link[browser.ie ? 'innerText' : 'textContent'] =  obj.textValue = text.value;\r\n                range.selectNode(link).select()\r\n            }\r\n            if(range.collapsed){\r\n                obj.textValue = text.value;\r\n            }\r\n            editor.execCommand('link',utils.clearEmptyAttrs(obj) );\r\n            dialog.close();\r\n        }\r\n    }\r\n    dialog.onok = handleDialogOk;\r\n    $G('href').onkeydown = $G('title').onkeydown = function(evt){\r\n        evt = evt || window.event;\r\n        if (evt.keyCode == 13) {\r\n            handleDialogOk();\r\n            return false;\r\n        }\r\n    };\r\n    $G('href').onblur = function(){\r\n        if(!hrefStartWith(this.value,[\"http\",\"/\",\"ftp://\",'#'])){\r\n            $G(\"msg\").innerHTML = \"<span style='color: red'>\"+lang.httpPrompt+\"</span>\";\r\n        }else{\r\n            $G(\"msg\").innerHTML = \"\";\r\n        }\r\n    };\r\n\r\n    function hrefStartWith(href,arr){\r\n        href = href.replace(/^\\s+|\\s+$/g, '');\r\n        for(var i=0,ai;ai=arr[i++];){\r\n            if(href.indexOf(ai)==0){\r\n                return true;\r\n            }\r\n        }\r\n        return false;\r\n    }\r\n\r\n\r\n</script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/map/map.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\r\n    <title></title>\r\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n    <script type=\"text/javascript\" src=\"http://api.map.baidu.com/api?v=1.1&services=true\"></script>\r\n    <style type=\"text/css\">\r\n        .content{width:530px; height: 350px;margin: 10px auto;}\r\n        .content table{width: 100%}\r\n        .content table td{vertical-align: middle;}\r\n        #city,#address{height:21px;background: #FFF;border:1px solid #d7d7d7; line-height: 21px;}\r\n        #city{width:60px}\r\n        #address{width:130px}\r\n        #is_dynamic_label span{vertical-align:middle;margin: 3px 0px 3px 3px;}\r\n        #is_dynamic_label input{vertical-align:middle;margin: 3px 3px 3px 50px;}\r\n    </style>\r\n</head>\r\n<body>\r\n<div class=\"content\">\r\n    <table>\r\n        <tr>\r\n            <td><var id=\"lang_city\"></var>:</td>\r\n            <td><input id=\"city\" type=\"text\" /></td>\r\n            <td><var id=\"lang_address\"></var>:</td>\r\n            <td><input id=\"address\" type=\"text\" value=\"\" /></td>\r\n            <td><a href=\"javascript:doSearch()\" class=\"button\"><var id=\"lang_search\"></var></a></td>\r\n            <td><label id=\"is_dynamic_label\" for=\"is_dynamic\"><input id=\"is_dynamic\" type=\"checkbox\" name=\"is_dynamic\" /><span><var id=\"lang_dynamicmap\"></var></span></label></td>\r\n        </tr>\r\n    </table>\r\n    <div style=\"width:100%;height:340px;margin:5px auto;border:1px solid gray\" id=\"container\"></div>\r\n\r\n</div>\r\n<script type=\"text/javascript\">\r\n    var map = new BMap.Map(\"container\"),marker,point,styleStr;\r\n    map.enableScrollWheelZoom();\r\n    map.enableContinuousZoom();\r\n    function doSearch(){\r\n        if (!document.getElementById('city').value) {\r\n            alert(lang.cityMsg);\r\n            return;\r\n        }\r\n        var search = new BMap.LocalSearch(document.getElementById('city').value, {\r\n            onSearchComplete: function (results){\r\n                if (results && results.getNumPois()) {\r\n                    var points = [];\r\n                    for (var i=0; i<results.getCurrentNumPois(); i++) {\r\n                        points.push(results.getPoi(i).point);\r\n                    }\r\n                    if (points.length > 1) {\r\n                        map.setViewport(points);\r\n                    } else {\r\n                        map.centerAndZoom(points[0], 13);\r\n                    }\r\n                    point = map.getCenter();\r\n                    marker.setPoint(point);\r\n                } else {\r\n                    alert(lang.errorMsg);\r\n                }\r\n            }\r\n        });\r\n        search.search(document.getElementById('address').value || document.getElementById('city').value);\r\n    }\r\n    //获得参数\r\n    function getPars(str,par){\r\n        var reg = new RegExp(par+\"=((\\\\d+|[.,])*)\",\"g\");\r\n        return reg.exec(str)[1];\r\n    }\r\n    function init(){\r\n        var mapNode = editor.selection.getRange().getClosedNode(),\r\n            isMapImg = mapNode && /api[.]map[.]baidu[.]com/ig.test(mapNode.getAttribute(\"src\")),\r\n            isMapIframe = mapNode && domUtils.hasClass(mapNode, 'ueditor_baidumap');\r\n        if(isMapImg || isMapIframe){\r\n            var url, centerPos, markerPos;\r\n            if(isMapIframe) {\r\n                url = decodeURIComponent(mapNode.getAttribute(\"src\"));\r\n                $G('is_dynamic').checked = true;\r\n                styleStr = mapNode.style.cssText;\r\n            } else {\r\n                url = mapNode.getAttribute(\"src\");\r\n                styleStr = mapNode.style.cssText;\r\n            }\r\n\r\n            centerPos = getPars(url,\"center\").split(\",\");\r\n            markerPos = getPars(url, \"markers\").split(\",\");\r\n            point = new BMap.Point(Number(centerPos[0]),Number(centerPos[1]));\r\n            marker = new BMap.Marker(new BMap.Point(Number(markerPos[0]), Number(markerPos[1])));\r\n            map.addControl(new BMap.NavigationControl());\r\n            map.centerAndZoom(point, Number(getPars(url,\"zoom\")));\r\n        }else{\r\n            point = new BMap.Point(116.404, 39.915);    // 创建点坐标\r\n            marker = new BMap.Marker(point);\r\n            map.addControl(new BMap.NavigationControl());\r\n            map.centerAndZoom(point, 10);                     // 初始化地图,设置中心点坐标和地图级别。\r\n        }\r\n        marker.enableDragging();\r\n        map.addOverlay(marker);\r\n    }\r\n    init();\r\n    document.getElementById('address').onkeydown = function (evt){\r\n        evt = evt || event;\r\n        if (evt.keyCode == 13) {\r\n            doSearch();\r\n        }\r\n    };\r\n    dialog.onok = function (){\r\n        var center = map.getCenter();\r\n        var zoom = map.zoomLevel;\r\n        var size = map.getSize();\r\n        var mapWidth = size.width;\r\n        var mapHeight = size.height;\r\n        var point = marker.getPoint();\r\n\r\n        if($G('is_dynamic').checked) {\r\n            var URL = editor.options.UEDITOR_HOME_URL,\r\n                url = [URL + (/\\/$/.test(URL) ? '':'/') + \"dialogs/map/show.html\" +\r\n                    '#center=' + center.lng + ',' + center.lat,\r\n                    '&zoom=' + zoom,\r\n                    '&width=' + mapWidth,\r\n                    '&height=' + mapHeight,\r\n                    '&markers=' + point.lng + ',' + point.lat,\r\n                    '&markerStyles=' + 'l,A'].join('');\r\n            editor.execCommand('inserthtml', '<iframe class=\"ueditor_baidumap\" src=\"' + url + '\"' + (styleStr ? ' style=\"' + styleStr + '\"' :'') + ' frameborder=\"0\" width=\"' + (mapWidth+4) + '\" height=\"' + (mapHeight+4) + '\"></iframe>');\r\n        } else {\r\n            var url = \"http://api.map.baidu.com/staticimage?center=\" + center.lng + ',' + center.lat +\r\n                    \"&zoom=\" + zoom + \"&width=\" + size.width + '&height=' + size.height + \"&markers=\" + point.lng + ',' + point.lat;\r\n            editor.execCommand('inserthtml', '<img width=\"'+ size.width +'\"height=\"'+ size.height +'\" src=\"' + url + '\"' + (styleStr ? ' style=\"' + styleStr + '\"' :'') + '/>');\r\n        }\r\n    };\r\n    document.getElementById(\"address\").focus();\r\n</script>\r\n\r\n\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/map/show.html",
    "content": "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n        \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n<head>\n    <meta charset=\"utf-8\"/>\n    <meta name=\"keywords\" content=\"百度地图,百度地图API，百度地图自定义工具，百度地图所见即所得工具\"/>\n    <meta name=\"description\" content=\"百度地图API自定义地图，帮助用户在可视化操作下生成百度地图\"/>\n    <title>百度地图API自定义地图</title>\n    <!--引用百度地图API-->\n    <style type=\"text/css\">\n        html, body {\n            margin: 0;\n            padding: 0;\n            overflow: hidden;\n        }\n    </style>\n    <script type=\"text/javascript\" src=\"http://api.map.baidu.com/api?key=&v=1.1&services=true\"></script>\n</head>\n\n<body onload=\"initMap();\">\n<!--百度地图容器-->\n<div style=\"width:697px;height:550px;border:#ccc solid 1px;\" id=\"dituContent\"></div>\n</body>\n<script type=\"text/javascript\">\n    function getParam(name) {\n        return location.href.match(new RegExp('[?#&]' + name + '=([^?#&]+)', 'i')) ? RegExp.$1 : '';\n    }\n    var map, marker;\n    var centerParam = getParam('center');\n    var zoomParam = getParam('zoom');\n    var widthParam = getParam('width');\n    var heightParam = getParam('height');\n    var markersParam = getParam('markers');\n    var markerStylesParam = getParam('markerStyles');\n\n    //创建和初始化地图函数：\n    function initMap() {\n        // [FF]切换模式后报错\n        if (!window.BMap) {\n            return;\n        }\n        var dituContent = document.getElementById('dituContent');\n        dituContent.style.width = widthParam + 'px';\n        dituContent.style.height = heightParam + 'px';\n\n        createMap();//创建地图\n        setMapEvent();//设置地图事件\n        addMapControl();//向地图添加控件\n\n        // 创建标注\n        var markersArr = markersParam.split(',');\n        var point = new BMap.Point(markersArr[0], markersArr[1]);\n        marker = new BMap.Marker(point);\n        marker.enableDragging();\n        map.addOverlay(marker); // 将标注添加到地图中\n\n        if(parent.editor && parent.document.body.contentEditable==\"true\") { //在编辑状态下\n            setMapListener();//地图改变修改外层的iframe标签src属性\n        }\n    }\n\n    //创建地图函数：\n    function createMap() {\n        map = new BMap.Map(\"dituContent\");//在百度地图容器中创建一个地图\n        var centerArr = centerParam.split(',');\n        var point = new BMap.Point(parseFloat(centerArr[0]), parseFloat(centerArr[1]));//定义一个中心点坐标\n        map.centerAndZoom(point, parseInt(zoomParam));//设定地图的中心点和坐标并将地图显示在地图容器中\n    }\n\n    //地图事件设置函数：\n    function setMapEvent() {\n        map.enableDragging();//启用地图拖拽事件，默认启用(可不写)\n        map.enableScrollWheelZoom();//启用地图滚轮放大缩小\n        map.enableDoubleClickZoom();//启用鼠标双击放大，默认启用(可不写)\n        map.enableKeyboard();//启用键盘上下左右键移动地图\n    }\n\n    //地图控件添加函数：\n    function addMapControl() {\n        //向地图中添加缩放控件\n        var ctrl_nav = new BMap.NavigationControl({anchor: BMAP_ANCHOR_TOP_LEFT, type: BMAP_NAVIGATION_CONTROL_LARGE});\n        map.addControl(ctrl_nav);\n        //向地图中添加缩略图控件\n        var ctrl_ove = new BMap.OverviewMapControl({anchor: BMAP_ANCHOR_BOTTOM_RIGHT, isOpen: 1});\n        map.addControl(ctrl_ove);\n        //向地图中添加比例尺控件\n        var ctrl_sca = new BMap.ScaleControl({anchor: BMAP_ANCHOR_BOTTOM_LEFT});\n        map.addControl(ctrl_sca);\n    }\n\n    function setMapListener() {\n        var editor = parent.editor, containerIframe,\n            iframes = parent.document.getElementsByTagName('iframe');\n        for (var key in iframes) {\n            if (iframes[key].contentWindow == window) {\n                containerIframe = iframes[key];\n                break;\n            }\n        }\n        if (containerIframe) {\n            map.addEventListener('moveend', mapListenerHandler);\n            map.addEventListener('zoomend', mapListenerHandler);\n            marker.addEventListener('dragend', mapListenerHandler);\n        }\n\n        function mapListenerHandler() {\n            var zoom = map.getZoom(),\n                center = map.getCenter(),\n                marker = window.marker.getPoint();\n            containerIframe.src = containerIframe.src.\n                replace(new RegExp('([?#&])center=([^?#&]+)', 'i'), '$1center=' + center.lng + ',' + center.lat).\n                replace(new RegExp('([?#&])markers=([^?#&]+)', 'i'), '$1markers=' + marker.lng + ',' + marker.lat).\n                replace(new RegExp('([?#&])zoom=([^?#&]+)', 'i'), '$1zoom=' + zoom);\n            editor.fireEvent('saveScene');\n        }\n    }\n</script>\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/music/music.css",
    "content": ".wrapper{margin: 5px 10px;}\r\n\r\n.searchBar{height:30px;padding:7px 0 3px;text-align:center;}\r\n.searchBtn{font-size:13px;height:24px;}\r\n\r\n.resultBar{width:460px;margin:5px auto;border: 1px solid #CCC;border-radius: 5px;box-shadow: 2px 2px 5px #D3D6DA;overflow: hidden;}\r\n\r\n.listPanel{overflow: hidden;}\r\n.panelon{display:block;}\r\n.paneloff{display:none}\r\n\r\n.page{width:220px;margin:20px auto;overflow: hidden;}\r\n.pageon{float:right;width:24px;line-height:24px;height:24px;margin-right: 5px;background: none;border: none;color: #000;font-weight: bold;text-align:center}\r\n.pageoff{float:right;width:24px;line-height:24px;height:24px;cursor:pointer;background-color: #fff;\r\n   border: 1px solid #E7ECF0;color: #2D64B3;margin-right: 5px;text-decoration: none;text-align:center;}\r\n\r\n.m-box{width:460px;}\r\n.m-m{float: left;line-height: 20px;height: 20px;}\r\n.m-h{height:24px;line-height:24px;padding-left: 46px;background-color:#FAFAFA;border-bottom: 1px solid #DAD8D8;font-weight: bold;font-size: 12px;color: #333;}\r\n.m-l{float:left;width:40px; }\r\n.m-t{float:left;width:140px;}\r\n.m-s{float:left;width:110px;}\r\n.m-z{float:left;width:100px;}\r\n.m-try-t{float: left;width: 60px;;}\r\n\r\n.m-try{float:left;width:20px;height:20px;background:url('http://static.tieba.baidu.com/tb/editor/images/try_music.gif') no-repeat ;}\r\n.m-trying{float:left;width:20px;height:20px;background:url('http://static.tieba.baidu.com/tb/editor/images/stop_music.gif') no-repeat ;}\r\n\r\n.loading{width:95px;height:7px;font-size:7px;margin:60px auto;background:url(http://static.tieba.baidu.com/tb/editor/images/loading.gif) no-repeat}\r\n.empty{width:300px;height:40px;padding:2px;margin:50px auto;line-height:40px; color:#006699;text-align:center;}"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/music/music.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n    <meta http-equiv=\"content-type\" content=\"text/html; charset=utf-8\">\r\n    <title>插入音乐</title>\r\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"music.css\">\r\n</head>\r\n<body>\r\n<div class=\"wrapper\">\r\n    <div class=\"searchBar\">\r\n        <input id=\"J_searchName\" type=\"text\"/>\r\n        <input type=\"button\" class=\"searchBtn\" id=\"J_searchBtn\">\r\n    </div>\r\n    <div class=\"resultBar\" id=\"J_resultBar\">\r\n        <div class=\"loading\" style=\"display:none\"></div>\r\n        <div class=\"empty\"><var id=\"lang_input_tips\"></var></div>\r\n    </div>\r\n    <div id=\"J_preview\"></div>\r\n</div>\r\n<script type=\"text/javascript\" src=\"music.js\"></script>\r\n<script type=\"text/javascript\">\r\n    var music = new Music;\r\n    dialog.onok = function () {\r\n        music.exec();\r\n    };\r\n    dialog.oncancel = function () {\r\n        $G('J_preview').innerHTML = \"\";\r\n    };\r\n</script>\r\n</body>\r\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/music/music.js",
    "content": "function Music() {\r\n    this.init();\r\n}\r\n(function () {\r\n    var pages = [],\r\n        panels = [],\r\n        selectedItem = null;\r\n    Music.prototype = {\r\n        total:70,\r\n        pageSize:10,\r\n        dataUrl:\"http://tingapi.ting.baidu.com/v1/restserver/ting?method=baidu.ting.search.common\",\r\n        playerUrl:\"http://box.baidu.com/widget/flash/bdspacesong.swf\",\r\n\r\n        init:function () {\r\n            var me = this;\r\n            domUtils.on($G(\"J_searchName\"), \"keyup\", function (event) {\r\n                var e = window.event || event;\r\n                if (e.keyCode == 13) {\r\n                    me.dosearch();\r\n                }\r\n            });\r\n            domUtils.on($G(\"J_searchBtn\"), \"click\", function () {\r\n                me.dosearch();\r\n            });\r\n        },\r\n        callback:function (data) {\r\n            var me = this;\r\n            me.data = data.song_list;\r\n            setTimeout(function () {\r\n                $G('J_resultBar').innerHTML = me._renderTemplate(data.song_list);\r\n            }, 300);\r\n        },\r\n        dosearch:function () {\r\n            var me = this;\r\n            selectedItem = null;\r\n            var key = $G('J_searchName').value;\r\n            if (utils.trim(key) == \"\")return false;\r\n            key = encodeURIComponent(key);\r\n            me._sent(key);\r\n        },\r\n        doselect:function (i) {\r\n            var me = this;\r\n            if (typeof i == 'object') {\r\n                selectedItem = i;\r\n            } else if (typeof i == 'number') {\r\n                selectedItem = me.data[i];\r\n            }\r\n        },\r\n        onpageclick:function (id) {\r\n            var me = this;\r\n            for (var i = 0; i < pages.length; i++) {\r\n                $G(pages[i]).className = 'pageoff';\r\n                $G(panels[i]).className = 'paneloff';\r\n            }\r\n            $G('page' + id).className = 'pageon';\r\n            $G('panel' + id).className = 'panelon';\r\n        },\r\n        listenTest:function (elem) {\r\n            var me = this,\r\n                view = $G('J_preview'),\r\n                is_play_action = (elem.className == 'm-try'),\r\n                old_trying = me._getTryingElem();\r\n\r\n            if (old_trying) {\r\n                old_trying.className = 'm-try';\r\n                view.innerHTML = '';\r\n            }\r\n            if (is_play_action) {\r\n                elem.className = 'm-trying';\r\n                view.innerHTML = me._buildMusicHtml(me._getUrl(true));\r\n            }\r\n        },\r\n        _sent:function (param) {\r\n            var me = this;\r\n            $G('J_resultBar').innerHTML = '<div class=\"loading\"></div>';\r\n\r\n            utils.loadFile(document, {\r\n                src:me.dataUrl + '&query=' + param + '&page_size=' + me.total + '&callback=music.callback&.r=' + Math.random(),\r\n                tag:\"script\",\r\n                type:\"text/javascript\",\r\n                defer:\"defer\"\r\n            });\r\n        },\r\n        _removeHtml:function (str) {\r\n            var reg = /<\\s*\\/?\\s*[^>]*\\s*>/gi;\r\n            return str.replace(reg, \"\");\r\n        },\r\n        _getUrl:function (isTryListen) {\r\n            var me = this;\r\n            var param = 'from=tiebasongwidget&url=&name=' + encodeURIComponent(me._removeHtml(selectedItem.title)) + '&artist='\r\n                + encodeURIComponent(me._removeHtml(selectedItem.author)) + '&extra='\r\n                + encodeURIComponent(me._removeHtml(selectedItem.album_title))\r\n                + '&autoPlay='+isTryListen+'' + '&loop=true';\r\n            return  me.playerUrl + \"?\" + param;\r\n        },\r\n        _getTryingElem:function () {\r\n            var s = $G('J_listPanel').getElementsByTagName('span');\r\n\r\n            for (var i = 0; i < s.length; i++) {\r\n                if (s[i].className == 'm-trying')\r\n                    return s[i];\r\n            }\r\n            return null;\r\n        },\r\n        _buildMusicHtml:function (playerUrl) {\r\n            var html = '<embed class=\"BDE_try_Music\" allowfullscreen=\"false\" pluginspage=\"http://www.macromedia.com/go/getflashplayer\"';\r\n            html += ' src=\"' + playerUrl + '\"';\r\n            html += ' width=\"1\" height=\"1\" style=\"position:absolute;left:-2000px;\"';\r\n            html += ' type=\"application/x-shockwave-flash\" wmode=\"transparent\" play=\"true\" loop=\"false\"';\r\n            html += ' menu=\"false\" allowscriptaccess=\"never\" scale=\"noborder\">';\r\n            return html;\r\n        },\r\n        _byteLength:function (str) {\r\n            return str.replace(/[^\\u0000-\\u007f]/g, \"\\u0061\\u0061\").length;\r\n        },\r\n        _getMaxText:function (s) {\r\n            var me = this;\r\n            s = me._removeHtml(s);\r\n            if (me._byteLength(s) > 12)\r\n                return s.substring(0, 5) + '...';\r\n            if (!s) s = \"&nbsp;\";\r\n            return s;\r\n        },\r\n        _rebuildData:function (data) {\r\n            var me = this,\r\n                newData = [],\r\n                d = me.pageSize,\r\n                itembox;\r\n            for (var i = 0; i < data.length; i++) {\r\n                if ((i + d) % d == 0) {\r\n                    itembox = [];\r\n                    newData.push(itembox)\r\n                }\r\n                itembox.push(data[i]);\r\n            }\r\n            return newData;\r\n        },\r\n        _renderTemplate:function (data) {\r\n            var me = this;\r\n            if (data.length == 0)return '<div class=\"empty\">' + lang.emptyTxt + '</div>';\r\n            data = me._rebuildData(data);\r\n            var s = [], p = [], t = [];\r\n            s.push('<div id=\"J_listPanel\" class=\"listPanel\">');\r\n            p.push('<div class=\"page\">');\r\n            for (var i = 0, tmpList; tmpList = data[i++];) {\r\n                panels.push('panel' + i);\r\n                pages.push('page' + i);\r\n                if (i == 1) {\r\n                    s.push('<div id=\"panel' + i + '\" class=\"panelon\">');\r\n                    if (data.length != 1) {\r\n                        t.push('<div id=\"page' + i + '\" onclick=\"music.onpageclick(' + i + ')\" class=\"pageon\">' + (i ) + '</div>');\r\n                    }\r\n                } else {\r\n                    s.push('<div id=\"panel' + i + '\" class=\"paneloff\">');\r\n                    t.push('<div id=\"page' + i + '\" onclick=\"music.onpageclick(' + i + ')\" class=\"pageoff\">' + (i ) + '</div>');\r\n                }\r\n                s.push('<div class=\"m-box\">');\r\n                s.push('<div class=\"m-h\"><span class=\"m-t\">' + lang.chapter + '</span><span class=\"m-s\">' + lang.singer\r\n                    + '</span><span class=\"m-z\">' + lang.special + '</span><span class=\"m-try-t\">' + lang.listenTest + '</span></div>');\r\n                for (var j = 0, tmpObj; tmpObj = tmpList[j++];) {\r\n                    s.push('<label for=\"radio-' + i + '-' + j + '\" class=\"m-m\">');\r\n                    s.push('<input type=\"radio\" id=\"radio-' + i + '-' + j + '\" name=\"musicId\" class=\"m-l\" onclick=\"music.doselect(' + (me.pageSize * (i-1) + (j-1)) + ')\"/>');\r\n                    s.push('<span class=\"m-t\">' + me._getMaxText(tmpObj.title) + '</span>');\r\n                    s.push('<span class=\"m-s\">' + me._getMaxText(tmpObj.author) + '</span>');\r\n                    s.push('<span class=\"m-z\">' + me._getMaxText(tmpObj.album_title) + '</span>');\r\n                    s.push('<span class=\"m-try\" onclick=\"music.doselect(' + (me.pageSize * (i-1) + (j-1)) + ');music.listenTest(this)\"></span>');\r\n                    s.push('</label>');\r\n                }\r\n                s.push('</div>');\r\n                s.push('</div>');\r\n            }\r\n            t.reverse();\r\n            p.push(t.join(''));\r\n            s.push('</div>');\r\n            p.push('</div>');\r\n            return s.join('') + p.join('');\r\n        },\r\n        exec:function () {\r\n            var me = this;\r\n            if (selectedItem == null)   return;\r\n            $G('J_preview').innerHTML = \"\";\r\n            editor.execCommand('music', {\r\n                url:me._getUrl(false),\r\n                width:400,\r\n                height:95\r\n            });\r\n        }\r\n    };\r\n})();\r\n\r\n\r\n\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/preview/preview.html",
    "content": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\n    \"http://www.w3.org/TR/html4/loose.dtd\">\n<html>\n    <head>\n        <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\n        <style>\n            html,body{\n                height:100%;\n                width:100%;\n                padding:0;\n                margin:0;\n            }\n            #preview{\n                width:100%;\n                height:100%;\n                padding:0;\n                margin:0;\n            }\n            #preview *{font-family:sans-serif;font-size:16px;}\n        </style>\n        <script type=\"text/javascript\" src=\"../internal.js\"></script>\n        <script src=\"../../ueditor.parse.js\"></script>\n        <title></title>\n    </head>\n    <body class=\"view\">\n        <div id=\"preview\" style=\"margin:8px\">\n\n        </div>\n    </body>\n    <script>\n        document.getElementById('preview').innerHTML = editor.getContent();\n        uParse('#preview',{\n            rootPath : '../../',\n            chartContainerHeight:500\n        })\n        dialog.oncancel = function(){\n            document.getElementById('preview').innerHTML = '';\n        }\n    </script>\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/scrawl/scrawl.css",
    "content": "/*common\r\n*/\r\nbody{margin: 0;}\r\ntable{width:100%;}\r\ntable td{padding:2px 4px;vertical-align: middle;}\r\na{text-decoration: none;}\r\nem{font-style: normal;}\r\n.border_style1{border: 1px solid #ccc;border-radius: 5px;box-shadow:2px 2px 5px #d3d6da;}\r\n/*module\r\n*/\r\n.main{margin: 8px;overflow: hidden;}\r\n\r\n.hot{float:left;height:335px;}\r\n.drawBoard{position: relative; cursor: crosshair;}\r\n.brushBorad{position: absolute;left:0;top:0;z-index: 998;}\r\n.picBoard{border: none;text-align: center;line-height: 300px;cursor: default;}\r\n.operateBar{margin-top:10px;font-size:12px;text-align: center;}\r\n.operateBar span{margin-left: 10px;}\r\n\r\n.drawToolbar{float:right;width:110px;height:300px;overflow: hidden;}\r\n.colorBar{margin-top:10px;font-size: 12px;text-align: center;}\r\n.colorBar a{display:block;width: 10px;height: 10px;border:1px solid #1006F1;border-radius: 3px; box-shadow:2px 2px 5px #d3d6da;opacity: 0.3}\r\n.sectionBar{margin-top:15px;font-size: 12px;text-align: center;}\r\n.sectionBar a{display:inline-block;width:10px;height:12px;color: #888;text-indent: -999px;opacity: 0.3}\r\n.size1{background: url('images/size.png') 1px center no-repeat ;}\r\n.size2{background: url('images/size.png') -10px center no-repeat;}\r\n.size3{background: url('images/size.png') -22px center no-repeat;}\r\n.size4{background: url('images/size.png') -35px center no-repeat;}\r\n\r\n.addImgH{position: relative;}\r\n.addImgH_form{position: absolute;left: 18px;top: -1px;width: 75px;height: 21px;opacity: 0;cursor: pointer;}\r\n.addImgH_form input{width: 100%;}\r\n/*scrawl遮罩层\r\n*/\r\n.maskLayerNull{display: none;}\r\n.maskLayer{position: absolute;top:0;left:0;width: 100%; height: 100%;opacity: 0.7;\r\n    background-color: #fff;text-align:center;font-weight:bold;line-height:300px;z-index: 1000;}\r\n/*btn state\r\n*/\r\n.previousStepH .icon{display: inline-block;width:16px;height:16px;background-image: url('images/undoH.png');cursor: pointer;}\r\n.previousStepH .text{color:#888;cursor:pointer;}\r\n.previousStep .icon{display: inline-block;width:16px;height:16px;background-image: url('images/undo.png');cursor:default;}\r\n.previousStep .text{color:#ccc;cursor:default;}\r\n\r\n.nextStepH .icon{display: inline-block;width:16px;height:16px;background-image: url('images/redoH.png');cursor: pointer;}\r\n.nextStepH .text{color:#888;cursor:pointer;}\r\n.nextStep .icon{display: inline-block;width:16px;height:16px;background-image: url('images/redo.png');cursor:default;}\r\n.nextStep .text{color:#ccc;cursor:default;}\r\n\r\n.clearBoardH .icon{display: inline-block;width:16px;height:16px;background-image: url('images/emptyH.png');cursor: pointer;}\r\n.clearBoardH .text{color:#888;cursor:pointer;}\r\n.clearBoard .icon{display: inline-block;width:16px;height:16px;background-image: url('images/empty.png');cursor:default;}\r\n.clearBoard .text{color:#ccc;cursor:default;}\r\n\r\n.scaleBoardH .icon{display: inline-block;width:16px;height:16px;background-image: url('images/scaleH.png');cursor: pointer;}\r\n.scaleBoardH .text{color:#888;cursor:pointer;}\r\n.scaleBoard .icon{display: inline-block;width:16px;height:16px;background-image: url('images/scale.png');cursor:default;}\r\n.scaleBoard .text{color:#ccc;cursor:default;}\r\n\r\n.removeImgH .icon{display: inline-block;width:16px;height:16px;background-image: url('images/delimgH.png');cursor: pointer;}\r\n.removeImgH .text{color:#888;cursor:pointer;}\r\n.removeImg .icon{display: inline-block;width:16px;height:16px;background-image: url('images/delimg.png');cursor:default;}\r\n.removeImg .text{color:#ccc;cursor:default;}\r\n\r\n.addImgH .icon{vertical-align:top;display: inline-block;width:16px;height:16px;background-image: url('images/addimg.png')}\r\n.addImgH .text{color:#888;cursor:pointer;}\r\n/*icon\r\n*/\r\n.brushIcon{display: inline-block;width:16px;height:16px;background-image: url('images/brush.png')}\r\n.eraserIcon{display: inline-block;width:16px;height:16px;background-image: url('images/eraser.png')}\r\n\r\n\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/scrawl/scrawl.html",
    "content": "<!DOCTYPE html>\r\n<html>\r\n<head>\r\n    <title></title>\r\n    <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\r\n    <meta name=\"robots\" content=\"noindex, nofollow\"/>\r\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"scrawl.css\">\r\n</head>\r\n<body>\r\n<div class=\"main\" id=\"J_wrap\">\r\n    <div class=\"hot\">\r\n        <div class=\"drawBoard border_style1\">\r\n            <canvas id=\"J_brushBoard\" class=\"brushBorad\" width=\"360\" height=\"300\"></canvas>\r\n            <div id=\"J_picBoard\" class=\"picBoard\" style=\"width: 360px;height: 300px\"></div>\r\n        </div>\r\n        <div id=\"J_operateBar\" class=\"operateBar\">\r\n            <span id=\"J_previousStep\" class=\"previousStep\">\r\n                <em class=\"icon\"></em>\r\n                <em class=\"text\"><var id=\"lang_input_previousStep\"></var></em>\r\n            </span>\r\n            <span id=\"J_nextStep\" class=\"nextStep\">\r\n                <em class=\"icon\"></em>\r\n                <em class=\"text\"><var id=\"lang_input_nextsStep\"></var></em>\r\n            </span>\r\n            <span id=\"J_clearBoard\" class=\"clearBoard\">\r\n                <em class=\"icon\"></em>\r\n                <em class=\"text\"><var id=\"lang_input_clear\"></var></em>\r\n            </span>\r\n            <span id=\"J_sacleBoard\" class=\"scaleBoard\">\r\n                <em class=\"icon\"></em>\r\n                <em class=\"text\"><var id=\"lang_input_ScalePic\"></var></em>\r\n            </span>\r\n        </div>\r\n    </div>\r\n    <div class=\"drawToolbar border_style1\">\r\n        <div id=\"J_colorBar\" class=\"colorBar\"></div>\r\n        <div id=\"J_brushBar\" class=\"sectionBar\">\r\n            <em class=\"brushIcon\"></em>\r\n            <a href=\"javascript:void(0)\" class=\"size1\">1</a>\r\n            <a href=\"javascript:void(0)\" class=\"size2\">3</a>\r\n            <a href=\"javascript:void(0)\" class=\"size3\">5</a>\r\n            <a href=\"javascript:void(0)\" class=\"size4\">7</a>\r\n        </div>\r\n        <div id=\"J_eraserBar\" class=\"sectionBar\">\r\n            <em class=\"eraserIcon\"></em>\r\n            <a href=\"javascript:void(0)\" class=\"size1\">1</a>\r\n            <a href=\"javascript:void(0)\" class=\"size2\">3</a>\r\n            <a href=\"javascript:void(0)\" class=\"size3\">5</a>\r\n            <a href=\"javascript:void(0)\" class=\"size4\">7</a>\r\n        </div>\r\n        <div class=\"sectionBar\">\r\n            <div id=\"J_addImg\" class=\"addImgH\">\r\n                <em class=\"icon\"></em>\r\n                <em class=\"text\"><var id=\"lang_input_addPic\"></var></em>\r\n                <form method=\"post\" id=\"fileForm\" enctype=\"multipart/form-data\" class=\"addImgH_form\" target=\"up\">\r\n                    <input type=\"file\" name=\"upfile\" id=\"J_imgTxt\"\r\n                           accept=\"image/gif,image/jpeg,image/png,image/jpg,image/bmp\"/>\r\n                </form>\r\n                <iframe name=\"up\" style=\"display: none\"></iframe>\r\n            </div>\r\n        </div>\r\n        <div class=\"sectionBar\">\r\n            <span id=\"J_removeImg\" class=\"removeImg\">\r\n                <em class=\"icon\"></em>\r\n                <em class=\"text\"><var id=\"lang_input_removePic\"></var></em>\r\n            </span>\r\n        </div>\r\n    </div>\r\n</div>\r\n<div id=\"J_maskLayer\" class=\"maskLayerNull\"></div>\r\n\r\n<script type=\"text/javascript\" src=\"scrawl.js\"></script>\r\n<script type=\"text/javascript\">\r\n    var settings = {\r\n        drawBrushSize:3, //画笔初始大小\r\n        drawBrushColor:\"#4bacc6\", //画笔初始颜色\r\n        colorList:['c00000', 'ff0000', 'ffc000', 'ffff00', '92d050', '00b050', '00b0f0', '0070c0', '002060', '7030a0', 'ffffff',\r\n            '000000', 'eeece1', '1f497d', '4f81bd', 'c0504d', '9bbb59', '8064a2', '4bacc6', 'f79646'], //画笔选择颜色\r\n        saveNum:10  //撤销次数\r\n    };\r\n\r\n    var scrawlObj = new scrawl( settings );\r\n    scrawlObj.isCancelScrawl = false;\r\n\r\n    dialog.onok = function () {\r\n        exec( scrawlObj );\r\n        return false;\r\n    };\r\n    dialog.oncancel = function () {\r\n        scrawlObj.isCancelScrawl = true;\r\n    };\r\n</script>\r\n</body>\r\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/scrawl/scrawl.js",
    "content": "/**\r\n * Created with JetBrains PhpStorm.\r\n * User: xuheng\r\n * Date: 12-5-22\r\n * Time: 上午11:38\r\n * To change this template use File | Settings | File Templates.\r\n */\r\nvar scrawl = function (options) {\r\n    options && this.initOptions(options);\r\n};\r\n(function () {\r\n    var canvas = $G(\"J_brushBoard\"),\r\n        context = canvas.getContext('2d'),\r\n        drawStep = [], //undo redo存储\r\n        drawStepIndex = 0; //undo redo指针\r\n\r\n    scrawl.prototype = {\r\n        isScrawl:false, //是否涂鸦\r\n        brushWidth:-1, //画笔粗细\r\n        brushColor:\"\", //画笔颜色\r\n\r\n        initOptions:function (options) {\r\n            var me = this;\r\n            me.originalState(options);//初始页面状态\r\n            me._buildToolbarColor(options.colorList);//动态生成颜色选择集合\r\n\r\n            me._addBoardListener(options.saveNum);//添加画板处理\r\n            me._addOPerateListener(options.saveNum);//添加undo redo clearBoard处理\r\n            me._addColorBarListener();//添加颜色选择处理\r\n            me._addBrushBarListener();//添加画笔大小处理\r\n            me._addEraserBarListener();//添加橡皮大小处理\r\n            me._addAddImgListener();//添加增添背景图片处理\r\n            me._addRemoveImgListenter();//删除背景图片处理\r\n            me._addScalePicListenter();//添加缩放处理\r\n            me._addClearSelectionListenter();//添加清楚选中状态处理\r\n\r\n            me._originalColorSelect(options.drawBrushColor);//初始化颜色选中\r\n            me._originalBrushSelect(options.drawBrushSize);//初始化画笔选中\r\n            me._clearSelection();//清楚选中状态\r\n        },\r\n\r\n        originalState:function (options) {\r\n            var me = this;\r\n\r\n            me.brushWidth = options.drawBrushSize;//同步画笔粗细\r\n            me.brushColor = options.drawBrushColor;//同步画笔颜色\r\n\r\n            context.lineWidth = me.brushWidth;//初始画笔大小\r\n            context.strokeStyle = me.brushColor;//初始画笔颜色\r\n            context.fillStyle = \"transparent\";//初始画布背景颜色\r\n            context.lineCap = \"round\";//去除锯齿\r\n            context.fill();\r\n        },\r\n        _buildToolbarColor:function (colorList) {\r\n            var tmp = null, arr = [];\r\n            arr.push(\"<table id='J_colorList'>\");\r\n            for (var i = 0, color; color = colorList[i++];) {\r\n                if ((i - 1) % 5 == 0) {\r\n                    if (i != 1) {\r\n                        arr.push(\"</tr>\");\r\n                    }\r\n                    arr.push(\"<tr>\");\r\n                }\r\n                tmp = '#' + color;\r\n                arr.push(\"<td><a title='\" + tmp + \"' href='javascript:void(0)' style='background-color:\" + tmp + \"'></a></td>\");\r\n            }\r\n            arr.push(\"</tr></table>\");\r\n            $G(\"J_colorBar\").innerHTML = arr.join(\"\");\r\n        },\r\n\r\n        _addBoardListener:function (saveNum) {\r\n            var me = this,\r\n                margin = 0,\r\n                startX = -1,\r\n                startY = -1,\r\n                isMouseDown = false,\r\n                isMouseMove = false,\r\n                isMouseUp = false,\r\n                buttonPress = 0, button, flag = '';\r\n\r\n            margin = parseInt(domUtils.getComputedStyle($G(\"J_wrap\"), \"margin-left\"));\r\n            drawStep.push(context.getImageData(0, 0, context.canvas.width, context.canvas.height));\r\n            drawStepIndex += 1;\r\n\r\n            domUtils.on(canvas, [\"mousedown\", \"mousemove\", \"mouseup\", \"mouseout\"], function (e) {\r\n                button = browser.webkit ? e.which : buttonPress;\r\n                switch (e.type) {\r\n                    case 'mousedown':\r\n                        buttonPress = 1;\r\n                        flag = 1;\r\n                        isMouseDown = true;\r\n                        isMouseUp = false;\r\n                        isMouseMove = false;\r\n                        me.isScrawl = true;\r\n                        startX = e.clientX - margin;//10为外边距总和\r\n                        startY = e.clientY - margin;\r\n                        context.beginPath();\r\n                        break;\r\n                    case 'mousemove' :\r\n                        if (!flag && button == 0) {\r\n                            return;\r\n                        }\r\n                        if (!flag && button) {\r\n                            startX = e.clientX - margin;//10为外边距总和\r\n                            startY = e.clientY - margin;\r\n                            context.beginPath();\r\n                            flag = 1;\r\n                        }\r\n                        if (isMouseUp || !isMouseDown) {\r\n                            return;\r\n                        }\r\n                        var endX = e.clientX - margin,\r\n                            endY = e.clientY - margin;\r\n\r\n                        context.moveTo(startX, startY);\r\n                        context.lineTo(endX, endY);\r\n                        context.stroke();\r\n                        startX = endX;\r\n                        startY = endY;\r\n                        isMouseMove = true;\r\n                        break;\r\n                    case 'mouseup':\r\n                        buttonPress = 0;\r\n                        if (!isMouseDown)return;\r\n                        if (!isMouseMove) {\r\n                            context.arc(startX, startY, context.lineWidth, 0, Math.PI * 2, false);\r\n                            context.fillStyle = context.strokeStyle;\r\n                            context.fill();\r\n                        }\r\n                        context.closePath();\r\n                        me._saveOPerate(saveNum);\r\n                        isMouseDown = false;\r\n                        isMouseMove = false;\r\n                        isMouseUp = true;\r\n                        startX = -1;\r\n                        startY = -1;\r\n                        break;\r\n                    case 'mouseout':\r\n                        flag = '';\r\n                        buttonPress = 0;\r\n                        if (button == 1) return;\r\n                        context.closePath();\r\n                        break;\r\n                }\r\n            });\r\n        },\r\n        _addOPerateListener:function (saveNum) {\r\n            var me = this;\r\n            domUtils.on($G(\"J_previousStep\"), \"click\", function () {\r\n                if (drawStepIndex > 1) {\r\n                    drawStepIndex -= 1;\r\n                    context.clearRect(0, 0, context.canvas.width, context.canvas.height);\r\n                    context.putImageData(drawStep[drawStepIndex - 1], 0, 0);\r\n                    me.btn2Highlight(\"J_nextStep\");\r\n                    drawStepIndex == 1 && me.btn2disable(\"J_previousStep\");\r\n                }\r\n            });\r\n            domUtils.on($G(\"J_nextStep\"), \"click\", function () {\r\n                if (drawStepIndex > 0 && drawStepIndex < drawStep.length) {\r\n                    context.clearRect(0, 0, context.canvas.width, context.canvas.height);\r\n                    context.putImageData(drawStep[drawStepIndex], 0, 0);\r\n                    drawStepIndex += 1;\r\n                    me.btn2Highlight(\"J_previousStep\");\r\n                    drawStepIndex == drawStep.length && me.btn2disable(\"J_nextStep\");\r\n                }\r\n            });\r\n            domUtils.on($G(\"J_clearBoard\"), \"click\", function () {\r\n                context.clearRect(0, 0, context.canvas.width, context.canvas.height);\r\n                drawStep = [];\r\n                me._saveOPerate(saveNum);\r\n                drawStepIndex = 1;\r\n                me.isScrawl = false;\r\n                me.btn2disable(\"J_previousStep\");\r\n                me.btn2disable(\"J_nextStep\");\r\n                me.btn2disable(\"J_clearBoard\");\r\n            });\r\n        },\r\n        _addColorBarListener:function () {\r\n            var me = this;\r\n            domUtils.on($G(\"J_colorBar\"), \"click\", function (e) {\r\n                var target = me.getTarget(e),\r\n                    color = target.title;\r\n                if (!!color) {\r\n                    me._addColorSelect(target);\r\n\r\n                    me.brushColor = color;\r\n                    context.globalCompositeOperation = \"source-over\";\r\n                    context.lineWidth = me.brushWidth;\r\n                    context.strokeStyle = color;\r\n                }\r\n            });\r\n        },\r\n        _addBrushBarListener:function () {\r\n            var me = this;\r\n            domUtils.on($G(\"J_brushBar\"), \"click\", function (e) {\r\n                var target = me.getTarget(e),\r\n                    size = browser.ie ? target.innerText : target.text;\r\n                if (!!size) {\r\n                    me._addBESelect(target);\r\n\r\n                    context.globalCompositeOperation = \"source-over\";\r\n                    context.lineWidth = parseInt(size);\r\n                    context.strokeStyle = me.brushColor;\r\n                    me.brushWidth = context.lineWidth;\r\n                }\r\n            });\r\n        },\r\n        _addEraserBarListener:function () {\r\n            var me = this;\r\n            domUtils.on($G(\"J_eraserBar\"), \"click\", function (e) {\r\n                var target = me.getTarget(e),\r\n                    size = browser.ie ? target.innerText : target.text;\r\n                if (!!size) {\r\n                    me._addBESelect(target);\r\n\r\n                    context.lineWidth = parseInt(size);\r\n                    context.globalCompositeOperation = \"destination-out\";\r\n                    context.strokeStyle = \"#FFF\";\r\n                }\r\n            });\r\n        },\r\n        _addAddImgListener:function () {\r\n            var file = $G(\"J_imgTxt\");\r\n            if (!window.FileReader) {\r\n                $G(\"J_addImg\").style.display = 'none';\r\n                $G(\"J_removeImg\").style.display = 'none';\r\n                $G(\"J_sacleBoard\").style.display = 'none';\r\n            }\r\n            domUtils.on(file, \"change\", function (e) {\r\n                var frm = file.parentNode;\r\n                addMaskLayer(lang.backgroundUploading);\r\n\r\n                var target = e.target || e.srcElement,\r\n                    reader = new FileReader();\r\n                reader.onload = function(evt){\r\n                    var target = evt.target || evt.srcElement;\r\n                    ue_callback(target.result, 'SUCCESS');\r\n                };\r\n                reader.readAsDataURL(target.files[0]);\r\n                frm.reset();\r\n            });\r\n        },\r\n        _addRemoveImgListenter:function () {\r\n            var me = this;\r\n            domUtils.on($G(\"J_removeImg\"), \"click\", function () {\r\n                $G(\"J_picBoard\").innerHTML = \"\";\r\n                me.btn2disable(\"J_removeImg\");\r\n                me.btn2disable(\"J_sacleBoard\");\r\n            });\r\n        },\r\n        _addScalePicListenter:function () {\r\n            domUtils.on($G(\"J_sacleBoard\"), \"click\", function () {\r\n                var picBoard = $G(\"J_picBoard\"),\r\n                    scaleCon = $G(\"J_scaleCon\"),\r\n                    img = picBoard.children[0];\r\n\r\n                if (img) {\r\n                    if (!scaleCon) {\r\n                        picBoard.style.cssText = \"position:relative;z-index:999;\"+picBoard.style.cssText;\r\n                        img.style.cssText = \"position: absolute;top:\" + (canvas.height - img.height) / 2 + \"px;left:\" + (canvas.width - img.width) / 2 + \"px;\";\r\n                        var scale = new ScaleBoy();\r\n                        picBoard.appendChild(scale.init());\r\n                        scale.startScale(img);\r\n                    } else {\r\n                        if (scaleCon.style.visibility == \"visible\") {\r\n                            scaleCon.style.visibility = \"hidden\";\r\n                            picBoard.style.position = \"\";\r\n                            picBoard.style.zIndex = \"\";\r\n                        } else {\r\n                            scaleCon.style.visibility = \"visible\";\r\n                            picBoard.style.cssText += \"position:relative;z-index:999\";\r\n                        }\r\n                    }\r\n                }\r\n            });\r\n        },\r\n        _addClearSelectionListenter:function () {\r\n            var doc = document;\r\n            domUtils.on(doc, 'mousemove', function (e) {\r\n                if (browser.ie && browser.version < 11)\r\n                    doc.selection.clear();\r\n                else\r\n                    window.getSelection().removeAllRanges();\r\n            });\r\n        },\r\n        _clearSelection:function () {\r\n            var list = [\"J_operateBar\", \"J_colorBar\", \"J_brushBar\", \"J_eraserBar\", \"J_picBoard\"];\r\n            for (var i = 0, group; group = list[i++];) {\r\n                domUtils.unSelectable($G(group));\r\n            }\r\n        },\r\n\r\n        _saveOPerate:function (saveNum) {\r\n            var me = this;\r\n            if (drawStep.length <= saveNum) {\r\n                if(drawStepIndex<drawStep.length){\r\n                    me.btn2disable(\"J_nextStep\");\r\n                    drawStep.splice(drawStepIndex);\r\n                }\r\n                drawStep.push(context.getImageData(0, 0, context.canvas.width, context.canvas.height));\r\n                drawStepIndex = drawStep.length;\r\n            } else {\r\n                drawStep.shift();\r\n                drawStep.push(context.getImageData(0, 0, context.canvas.width, context.canvas.height));\r\n                drawStepIndex = drawStep.length;\r\n            }\r\n            me.btn2Highlight(\"J_previousStep\");\r\n            me.btn2Highlight(\"J_clearBoard\");\r\n        },\r\n\r\n        _originalColorSelect:function (title) {\r\n            var colorList = $G(\"J_colorList\").getElementsByTagName(\"td\");\r\n            for (var j = 0, cell; cell = colorList[j++];) {\r\n                if (cell.children[0].title.toLowerCase() == title) {\r\n                    cell.children[0].style.opacity = 1;\r\n                }\r\n            }\r\n        },\r\n        _originalBrushSelect:function (text) {\r\n            var brushList = $G(\"J_brushBar\").children;\r\n            for (var i = 0, ele; ele = brushList[i++];) {\r\n                if (ele.tagName.toLowerCase() == \"a\") {\r\n                    var size = browser.ie ? ele.innerText : ele.text;\r\n                    if (size.toLowerCase() == text) {\r\n                        ele.style.opacity = 1;\r\n                    }\r\n                }\r\n            }\r\n        },\r\n        _addColorSelect:function (target) {\r\n            var me = this,\r\n                colorList = $G(\"J_colorList\").getElementsByTagName(\"td\"),\r\n                eraserList = $G(\"J_eraserBar\").children,\r\n                brushList = $G(\"J_brushBar\").children;\r\n\r\n            for (var i = 0, cell; cell = colorList[i++];) {\r\n                cell.children[0].style.opacity = 0.3;\r\n            }\r\n            for (var k = 0, ele; ele = brushList[k++];) {\r\n                if (ele.tagName.toLowerCase() == \"a\") {\r\n                    ele.style.opacity = 0.3;\r\n                    var size = browser.ie ? ele.innerText : ele.text;\r\n                    if (size.toLowerCase() == this.brushWidth) {\r\n                        ele.style.opacity = 1;\r\n                    }\r\n                }\r\n            }\r\n            for (var j = 0, node; node = eraserList[j++];) {\r\n                if (node.tagName.toLowerCase() == \"a\") {\r\n                    node.style.opacity = 0.3;\r\n                }\r\n            }\r\n\r\n            target.style.opacity = 1;\r\n            target.blur();\r\n        },\r\n        _addBESelect:function (target) {\r\n            var brushList = $G(\"J_brushBar\").children;\r\n            var eraserList = $G(\"J_eraserBar\").children;\r\n\r\n            for (var i = 0, ele; ele = brushList[i++];) {\r\n                if (ele.tagName.toLowerCase() == \"a\") {\r\n                    ele.style.opacity = 0.3;\r\n                }\r\n            }\r\n            for (var j = 0, node; node = eraserList[j++];) {\r\n                if (node.tagName.toLowerCase() == \"a\") {\r\n                    node.style.opacity = 0.3;\r\n                }\r\n            }\r\n\r\n            target.style.opacity = 1;\r\n            target.blur();\r\n        },\r\n        getCanvasData:function () {\r\n            var picContainer = $G(\"J_picBoard\"),\r\n                img = picContainer.children[0];\r\n            if (img) {\r\n                var x, y;\r\n                if (img.style.position == \"absolute\") {\r\n                    x = parseInt(img.style.left);\r\n                    y = parseInt(img.style.top);\r\n                } else {\r\n                    x = (picContainer.offsetWidth - img.width) / 2;\r\n                    y = (picContainer.offsetHeight - img.height) / 2;\r\n                }\r\n                context.globalCompositeOperation = \"destination-over\";\r\n                context.drawImage(img, x, y, img.width, img.height);\r\n            } else {\r\n                context.globalCompositeOperation = \"destination-atop\";\r\n                context.fillStyle = \"#fff\";//重置画布背景白色\r\n                context.fillRect(0, 0, canvas.width, canvas.height);\r\n            }\r\n            try {\r\n                return canvas.toDataURL(\"image/png\").substring(22);\r\n            } catch (e) {\r\n                return \"\";\r\n            }\r\n        },\r\n        btn2Highlight:function (id) {\r\n            var cur = $G(id);\r\n            cur.className.indexOf(\"H\") == -1 && (cur.className += \"H\");\r\n        },\r\n        btn2disable:function (id) {\r\n            var cur = $G(id);\r\n            cur.className.indexOf(\"H\") != -1 && (cur.className = cur.className.replace(\"H\", \"\"));\r\n        },\r\n        getTarget:function (evt) {\r\n            return evt.target || evt.srcElement;\r\n        }\r\n    };\r\n})();\r\n\r\nvar ScaleBoy = function () {\r\n    this.dom = null;\r\n    this.scalingElement = null;\r\n};\r\n(function () {\r\n    function _appendStyle() {\r\n        var doc = document,\r\n            head = doc.getElementsByTagName('head')[0],\r\n            style = doc.createElement('style'),\r\n            cssText = '.scale{visibility:hidden;cursor:move;position:absolute;left:0;top:0;width:100px;height:50px;background-color:#fff;font-size:0;line-height:0;opacity:.4;filter:Alpha(opacity=40);}'\r\n                + '.scale span{position:absolute;left:0;top:0;width:6px;height:6px;background-color:#006DAE;}'\r\n                + '.scale .hand0, .scale .hand7{cursor:nw-resize;}'\r\n                + '.scale .hand1, .scale .hand6{left:50%;margin-left:-3px;cursor:n-resize;}'\r\n                + '.scale .hand2, .scale .hand4, .scale .hand7{left:100%;margin-left:-6px;}'\r\n                + '.scale .hand3, .scale .hand4{top:50%;margin-top:-3px;cursor:w-resize;}'\r\n                + '.scale .hand5, .scale .hand6, .scale .hand7{margin-top:-6px;top:100%;}'\r\n                + '.scale .hand2, .scale .hand5{cursor:ne-resize;}';\r\n        style.type = 'text/css';\r\n\r\n        try {\r\n            style.appendChild(doc.createTextNode(cssText));\r\n        } catch (e) {\r\n            style.styleSheet.cssText = cssText;\r\n        }\r\n        head.appendChild(style);\r\n    }\r\n\r\n    function _getDom() {\r\n        var doc = document,\r\n            hand,\r\n            arr = [],\r\n            scale = doc.createElement('div');\r\n\r\n        scale.id = 'J_scaleCon';\r\n        scale.className = 'scale';\r\n        for (var i = 0; i < 8; i++) {\r\n            arr.push(\"<span class='hand\" + i + \"'></span>\");\r\n        }\r\n        scale.innerHTML = arr.join(\"\");\r\n        return scale;\r\n    }\r\n\r\n    var rect = [\r\n        //[left, top, width, height]\r\n        [1, 1, -1, -1],\r\n        [0, 1, 0, -1],\r\n        [0, 1, 1, -1],\r\n        [1, 0, -1, 0],\r\n        [0, 0, 1, 0],\r\n        [1, 0, -1, 1],\r\n        [0, 0, 0, 1],\r\n        [0, 0, 1, 1]\r\n    ];\r\n    ScaleBoy.prototype = {\r\n        init:function () {\r\n            _appendStyle();\r\n            var me = this,\r\n                scale = me.dom = _getDom();\r\n\r\n            me.scaleMousemove.fp = me;\r\n            domUtils.on(scale, 'mousedown', function (e) {\r\n                var target = e.target || e.srcElement;\r\n                me.start = {x:e.clientX, y:e.clientY};\r\n                if (target.className.indexOf('hand') != -1) {\r\n                    me.dir = target.className.replace('hand', '');\r\n                }\r\n                domUtils.on(document.body, 'mousemove', me.scaleMousemove);\r\n                e.stopPropagation ? e.stopPropagation() : e.cancelBubble = true;\r\n            });\r\n            domUtils.on(document.body, 'mouseup', function (e) {\r\n                if (me.start) {\r\n                    domUtils.un(document.body, 'mousemove', me.scaleMousemove);\r\n                    if (me.moved) {\r\n                        me.updateScaledElement({position:{x:scale.style.left, y:scale.style.top}, size:{w:scale.style.width, h:scale.style.height}});\r\n                    }\r\n                    delete me.start;\r\n                    delete me.moved;\r\n                    delete me.dir;\r\n                }\r\n            });\r\n            return scale;\r\n        },\r\n        startScale:function (objElement) {\r\n            var me = this, Idom = me.dom;\r\n\r\n            Idom.style.cssText = 'visibility:visible;top:' + objElement.style.top + ';left:' + objElement.style.left + ';width:' + objElement.offsetWidth + 'px;height:' + objElement.offsetHeight + 'px;';\r\n            me.scalingElement = objElement;\r\n        },\r\n        updateScaledElement:function (objStyle) {\r\n            var cur = this.scalingElement,\r\n                pos = objStyle.position,\r\n                size = objStyle.size;\r\n            if (pos) {\r\n                typeof pos.x != 'undefined' && (cur.style.left = pos.x);\r\n                typeof pos.y != 'undefined' && (cur.style.top = pos.y);\r\n            }\r\n            if (size) {\r\n                size.w && (cur.style.width = size.w);\r\n                size.h && (cur.style.height = size.h);\r\n            }\r\n        },\r\n        updateStyleByDir:function (dir, offset) {\r\n            var me = this,\r\n                dom = me.dom, tmp;\r\n\r\n            rect['def'] = [1, 1, 0, 0];\r\n            if (rect[dir][0] != 0) {\r\n                tmp = parseInt(dom.style.left) + offset.x;\r\n                dom.style.left = me._validScaledProp('left', tmp) + 'px';\r\n            }\r\n            if (rect[dir][1] != 0) {\r\n                tmp = parseInt(dom.style.top) + offset.y;\r\n                dom.style.top = me._validScaledProp('top', tmp) + 'px';\r\n            }\r\n            if (rect[dir][2] != 0) {\r\n                tmp = dom.clientWidth + rect[dir][2] * offset.x;\r\n                dom.style.width = me._validScaledProp('width', tmp) + 'px';\r\n            }\r\n            if (rect[dir][3] != 0) {\r\n                tmp = dom.clientHeight + rect[dir][3] * offset.y;\r\n                dom.style.height = me._validScaledProp('height', tmp) + 'px';\r\n            }\r\n            if (dir === 'def') {\r\n                me.updateScaledElement({position:{x:dom.style.left, y:dom.style.top}});\r\n            }\r\n        },\r\n        scaleMousemove:function (e) {\r\n            var me = arguments.callee.fp,\r\n                start = me.start,\r\n                dir = me.dir || 'def',\r\n                offset = {x:e.clientX - start.x, y:e.clientY - start.y};\r\n\r\n            me.updateStyleByDir(dir, offset);\r\n            arguments.callee.fp.start = {x:e.clientX, y:e.clientY};\r\n            arguments.callee.fp.moved = 1;\r\n        },\r\n        _validScaledProp:function (prop, value) {\r\n            var ele = this.dom,\r\n                wrap = $G(\"J_picBoard\");\r\n\r\n            value = isNaN(value) ? 0 : value;\r\n            switch (prop) {\r\n                case 'left':\r\n                    return value < 0 ? 0 : (value + ele.clientWidth) > wrap.clientWidth ? wrap.clientWidth - ele.clientWidth : value;\r\n                case 'top':\r\n                    return value < 0 ? 0 : (value + ele.clientHeight) > wrap.clientHeight ? wrap.clientHeight - ele.clientHeight : value;\r\n                case 'width':\r\n                    return value <= 0 ? 1 : (value + ele.offsetLeft) > wrap.clientWidth ? wrap.clientWidth - ele.offsetLeft : value;\r\n                case 'height':\r\n                    return value <= 0 ? 1 : (value + ele.offsetTop) > wrap.clientHeight ? wrap.clientHeight - ele.offsetTop : value;\r\n            }\r\n        }\r\n    };\r\n})();\r\n\r\n//后台回调\r\nfunction ue_callback(url, state) {\r\n    var doc = document,\r\n        picBorard = $G(\"J_picBoard\"),\r\n        img = doc.createElement(\"img\");\r\n\r\n    //图片缩放\r\n    function scale(img, max, oWidth, oHeight) {\r\n        var width = 0, height = 0, percent, ow = img.width || oWidth, oh = img.height || oHeight;\r\n        if (ow > max || oh > max) {\r\n            if (ow >= oh) {\r\n                if (width = ow - max) {\r\n                    percent = (width / ow).toFixed(2);\r\n                    img.height = oh - oh * percent;\r\n                    img.width = max;\r\n                }\r\n            } else {\r\n                if (height = oh - max) {\r\n                    percent = (height / oh).toFixed(2);\r\n                    img.width = ow - ow * percent;\r\n                    img.height = max;\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    //移除遮罩层\r\n    removeMaskLayer();\r\n    //状态响应\r\n    if (state == \"SUCCESS\") {\r\n        picBorard.innerHTML = \"\";\r\n        img.onload = function () {\r\n            scale(this, 300);\r\n            picBorard.appendChild(img);\r\n\r\n            var obj = new scrawl();\r\n            obj.btn2Highlight(\"J_removeImg\");\r\n            //trace 2457\r\n            obj.btn2Highlight(\"J_sacleBoard\");\r\n        };\r\n        img.src = url;\r\n    } else {\r\n        alert(state);\r\n    }\r\n}\r\n//去掉遮罩层\r\nfunction removeMaskLayer() {\r\n    var maskLayer = $G(\"J_maskLayer\");\r\n    maskLayer.className = \"maskLayerNull\";\r\n    maskLayer.innerHTML = \"\";\r\n    dialog.buttons[0].setDisabled(false);\r\n}\r\n//添加遮罩层\r\nfunction addMaskLayer(html) {\r\n    var maskLayer = $G(\"J_maskLayer\");\r\n    dialog.buttons[0].setDisabled(true);\r\n    maskLayer.className = \"maskLayer\";\r\n    maskLayer.innerHTML = html;\r\n}\r\n//执行确认按钮方法\r\nfunction exec(scrawlObj) {\r\n    if (scrawlObj.isScrawl) {\r\n        addMaskLayer(lang.scrawlUpLoading);\r\n        var base64 = scrawlObj.getCanvasData();\r\n        if (!!base64) {\r\n            var options = {\r\n                timeout:100000,\r\n                onsuccess:function (xhr) {\r\n                    if (!scrawlObj.isCancelScrawl) {\r\n                        var responseObj;\r\n                        responseObj = eval(\"(\" + xhr.responseText + \")\");\r\n                        if (responseObj.state == \"SUCCESS\") {\r\n                            var imgObj = {},\r\n                                url = editor.options.scrawlUrlPrefix + responseObj.url;\r\n                            imgObj.src = url;\r\n                            imgObj._src = url;\r\n                            imgObj.alt = responseObj.original || '';\r\n                            imgObj.title = responseObj.title || '';\r\n                            editor.execCommand(\"insertImage\", imgObj);\r\n                            dialog.close();\r\n                        } else {\r\n                            alert(responseObj.state);\r\n                        }\r\n\r\n                    }\r\n                },\r\n                onerror:function () {\r\n                    alert(lang.imageError);\r\n                    dialog.close();\r\n                }\r\n            };\r\n            options[editor.getOpt('scrawlFieldName')] = base64;\r\n\r\n            var actionUrl = editor.getActionUrl(editor.getOpt('scrawlActionName')),\r\n                params = utils.serializeParam(editor.queryCommandValue('serverparam')) || '',\r\n                url = utils.formatUrl(actionUrl + (actionUrl.indexOf('?') == -1 ? '?':'&') + params);\r\n            ajax.request(url, options);\r\n        }\r\n    } else {\r\n        addMaskLayer(lang.noScarwl + \"&nbsp;&nbsp;&nbsp;<input type='button' value='\" + lang.continueBtn + \"'  onclick='removeMaskLayer()'/>\");\r\n    }\r\n}\r\n\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/searchreplace/searchreplace.html",
    "content": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\r\n        \"http://www.w3.org/TR/html4/loose.dtd\">\r\n<html>\r\n<head>\r\n    <title></title>\r\n    <meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\"/>\r\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n    <style type=\"text/css\">\r\n        .warpper{ position:relative;width: 380px; height: 100%; margin: 10px auto;}\r\n        .tabbody{height: 160px;}\r\n        .tabbody table{width:100%;border-collapse: separate;border-spacing: 3px;}\r\n        .tabbody .panel{width:373px;height:100%;padding-left: 5px;position: absolute;background-color: #fff;}\r\n        .tabbody input.int{ width:190px;height:21px;border:1px solid #d7d7d7;line-height:21px;}\r\n        .tabbody input.btn{padding: 0 5px; text-align:center;line-height:24px; text-decoration: none;height:24px;background:url(\"../../themes/default/images/dialog-title-bg.png\") repeat-x;border:1px solid #ccc; }\r\n    </style>\r\n</head>\r\n<body>\r\n<div class=\"warpper\" id=\"searchtab\">\r\n    <div id=\"head\" class=\"tabhead\">\r\n        <span  tabsrc=\"find\" class=\"focus\"><var id=\"lang_tab_search\"></var></span>\r\n        <span  tabsrc=\"replace\" ><var id=\"lang_tab_replace\"></var></span>\r\n    </div>\r\n    <div class=\"tabbody\">\r\n        <div class=\"panel\" id=\"find\">\r\n            <table>\r\n                <tr>\r\n                    <td width=\"80\"><var id=\"lang_search1\"></var>: </td>\r\n                    <td><input id=\"findtxt\" type=\"text\" class=\"int\" /></td>\r\n                </tr>\r\n                <!--<tr>-->\r\n\r\n                    <!--<td colspan=\"2\"><span style=\"color:red\"><var id=\"lang_searchReg\"></var></span></td>-->\r\n                <!--</tr>-->\r\n                <tr>\r\n                    <td><var id=\"lang_case_sensitive1\"></var></td>\r\n                    <td>\r\n                        <input id=\"matchCase\" type=\"checkbox\" />\r\n                    </td>\r\n                </tr>\r\n                <tr>\r\n                    <td colspan=\"2\">\r\n                        <input id=\"nextFindBtn\" type=\"button\" class=\"btn\" />\r\n                        <input id=\"preFindBtn\" type=\"button\" class=\"btn\" />\r\n                    </td>\r\n                </tr>\r\n                <tr>\r\n                    <td colspan=\"2\">\r\n                        &nbsp;\r\n                    </td>\r\n                </tr>\r\n                <tr>\r\n                    <td colspan=\"2\">\r\n                        <span id=\"search-msg\" style=\"color:red\"></span>\r\n                    </td>\r\n                </tr>\r\n            </table>\r\n        </div>\r\n        <div class=\"panel\" id=\"replace\">\r\n            <table>\r\n                <tr>\r\n                    <td width=\"80\"><var id=\"lang_search2\"></var>: </td>\r\n                    <td><input id=\"findtxt1\" type=\"text\" class=\"int\"  /></td>\r\n                </tr>\r\n                <!--<tr>-->\r\n\r\n                    <!--<td colspan=\"2\"><span style=\"color:red\"><var id=\"lang_searchReg1\"></var></span></td>-->\r\n                <!--</tr>-->\r\n                <tr>\r\n                    <td><var id=\"lang_replace\"></var>: </td>\r\n                    <td><input id=\"replacetxt\" type=\"text\" class=\"int\" /></td>\r\n                </tr>\r\n                <tr>\r\n                    <td><var id=\"lang_case_sensitive2\"></var></td>\r\n                    <td>\r\n                        <input id=\"matchCase1\" type=\"checkbox\" />\r\n                    </td>\r\n                </tr>\r\n                <tr>\r\n                    <td colspan=\"2\">\r\n                        <input id=\"nextReplaceBtn\" type=\"button\" class=\"btn\" />\r\n                        <input id=\"preReplaceBtn\" type=\"button\" class=\"btn\" />\r\n                        <input id=\"repalceBtn\" type=\"button\" class=\"btn\" />\r\n                        <input id=\"repalceAllBtn\" type=\"button\" class=\"btn\" />\r\n                    </td>\r\n                </tr>\r\n                <tr>\r\n                    <td colspan=\"2\">\r\n                        &nbsp;\r\n                    </td>\r\n                </tr>\r\n                <tr>\r\n                    <td colspan=\"2\">\r\n                        <span id=\"replace-msg\" style=\"color:red\"></span>\r\n                    </td>\r\n                </tr>\r\n            </table>\r\n        </div>\r\n    </div>\r\n</div>\r\n<script type=\"text/javascript\" src=\"searchreplace.js\"></script>\r\n</body>\r\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/searchreplace/searchreplace.js",
    "content": "/**\r\n * Created with JetBrains PhpStorm.\r\n * User: xuheng\r\n * Date: 12-9-26\r\n * Time: 下午12:29\r\n * To change this template use File | Settings | File Templates.\r\n */\r\n\r\n//清空上次查选的痕迹\r\neditor.firstForSR = 0;\r\neditor.currentRangeForSR = null;\r\n//给tab注册切换事件\r\n/**\r\n * tab点击处理事件\r\n * @param tabHeads\r\n * @param tabBodys\r\n * @param obj\r\n */\r\nfunction clickHandler( tabHeads,tabBodys,obj ) {\r\n    //head样式更改\r\n    for ( var k = 0, len = tabHeads.length; k < len; k++ ) {\r\n        tabHeads[k].className = \"\";\r\n    }\r\n    obj.className = \"focus\";\r\n    //body显隐\r\n    var tabSrc = obj.getAttribute( \"tabSrc\" );\r\n    for ( var j = 0, length = tabBodys.length; j < length; j++ ) {\r\n        var body = tabBodys[j],\r\n            id = body.getAttribute( \"id\" );\r\n        if ( id != tabSrc ) {\r\n            body.style.zIndex = 1;\r\n        } else {\r\n            body.style.zIndex = 200;\r\n        }\r\n    }\r\n\r\n}\r\n\r\n/**\r\n * TAB切换\r\n * @param tabParentId  tab的父节点ID或者对象本身\r\n */\r\nfunction switchTab( tabParentId ) {\r\n    var tabElements = $G( tabParentId ).children,\r\n        tabHeads = tabElements[0].children,\r\n        tabBodys = tabElements[1].children;\r\n\r\n    for ( var i = 0, length = tabHeads.length; i < length; i++ ) {\r\n        var head = tabHeads[i];\r\n        if ( head.className === \"focus\" )clickHandler(tabHeads,tabBodys, head );\r\n        head.onclick = function () {\r\n            clickHandler(tabHeads,tabBodys,this);\r\n        }\r\n    }\r\n}\r\n$G('searchtab').onmousedown = function(){\r\n    $G('search-msg').innerHTML = '';\r\n    $G('replace-msg').innerHTML = ''\r\n}\r\n//是否区分大小写\r\nfunction getMatchCase(id) {\r\n    return $G(id).checked ? true : false;\r\n}\r\n//查找\r\n$G(\"nextFindBtn\").onclick = function (txt, dir, mcase) {\r\n    var findtxt = $G(\"findtxt\").value, obj;\r\n    if (!findtxt) {\r\n        return false;\r\n    }\r\n    obj = {\r\n        searchStr:findtxt,\r\n        dir:1,\r\n        casesensitive:getMatchCase(\"matchCase\")\r\n    };\r\n    if (!frCommond(obj)) {\r\n        var bk = editor.selection.getRange().createBookmark();\r\n        $G('search-msg').innerHTML = lang.getEnd;\r\n        editor.selection.getRange().moveToBookmark(bk).select();\r\n\r\n\r\n    }\r\n};\r\n$G(\"nextReplaceBtn\").onclick = function (txt, dir, mcase) {\r\n    var findtxt = $G(\"findtxt1\").value, obj;\r\n    if (!findtxt) {\r\n        return false;\r\n    }\r\n    obj = {\r\n        searchStr:findtxt,\r\n        dir:1,\r\n        casesensitive:getMatchCase(\"matchCase1\")\r\n    };\r\n    frCommond(obj);\r\n};\r\n$G(\"preFindBtn\").onclick = function (txt, dir, mcase) {\r\n    var findtxt = $G(\"findtxt\").value, obj;\r\n    if (!findtxt) {\r\n        return false;\r\n    }\r\n    obj = {\r\n        searchStr:findtxt,\r\n        dir:-1,\r\n        casesensitive:getMatchCase(\"matchCase\")\r\n    };\r\n    if (!frCommond(obj)) {\r\n        $G('search-msg').innerHTML = lang.getStart;\r\n    }\r\n};\r\n$G(\"preReplaceBtn\").onclick = function (txt, dir, mcase) {\r\n    var findtxt = $G(\"findtxt1\").value, obj;\r\n    if (!findtxt) {\r\n        return false;\r\n    }\r\n    obj = {\r\n        searchStr:findtxt,\r\n        dir:-1,\r\n        casesensitive:getMatchCase(\"matchCase1\")\r\n    };\r\n    frCommond(obj);\r\n};\r\n//替换\r\n$G(\"repalceBtn\").onclick = function () {\r\n    var findtxt = $G(\"findtxt1\").value.replace(/^\\s|\\s$/g, \"\"), obj,\r\n        replacetxt = $G(\"replacetxt\").value.replace(/^\\s|\\s$/g, \"\");\r\n    if (!findtxt) {\r\n        return false;\r\n    }\r\n    if (findtxt == replacetxt || (!getMatchCase(\"matchCase1\") && findtxt.toLowerCase() == replacetxt.toLowerCase())) {\r\n        return false;\r\n    }\r\n    obj = {\r\n        searchStr:findtxt,\r\n        dir:1,\r\n        casesensitive:getMatchCase(\"matchCase1\"),\r\n        replaceStr:replacetxt\r\n    };\r\n    frCommond(obj);\r\n};\r\n//全部替换\r\n$G(\"repalceAllBtn\").onclick = function () {\r\n    var findtxt = $G(\"findtxt1\").value.replace(/^\\s|\\s$/g, \"\"), obj,\r\n        replacetxt = $G(\"replacetxt\").value.replace(/^\\s|\\s$/g, \"\");\r\n    if (!findtxt) {\r\n        return false;\r\n    }\r\n    if (findtxt == replacetxt || (!getMatchCase(\"matchCase1\") && findtxt.toLowerCase() == replacetxt.toLowerCase())) {\r\n        return false;\r\n    }\r\n    obj = {\r\n        searchStr:findtxt,\r\n        casesensitive:getMatchCase(\"matchCase1\"),\r\n        replaceStr:replacetxt,\r\n        all:true\r\n    };\r\n    var num = frCommond(obj);\r\n    if (num) {\r\n        $G('replace-msg').innerHTML = lang.countMsg.replace(\"{#count}\", num);\r\n    }\r\n};\r\n//执行\r\nvar frCommond = function (obj) {\r\n    return editor.execCommand(\"searchreplace\", obj);\r\n};\r\nswitchTab(\"searchtab\");"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/snapscreen/snapscreen.html",
    "content": "<!DOCTYPE HTML>\r\n<html>\r\n    <head>\r\n        <meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\"/>\r\n        <title></title>\r\n        <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n        <style type=\"text/css\">\r\n            *{color: #838383}\r\n            html,body {\r\n                font-size: 12px;\r\n                width:100%;\r\n                height:100%;\r\n                overflow: hidden;\r\n                margin:0px;\r\n                padding:0px;\r\n            }\r\n            h2 { font-size: 16px; margin: 20px auto;}\r\n            .content{\r\n                padding:5px 15px 0 15px;\r\n                height:100%;\r\n            }\r\n            dt,dd { margin-left: 0; padding-left: 0;}\r\n            dt a { display: block;\r\n                    height: 30px;\r\n                    line-height: 30px;\r\n                    width: 55px;\r\n                    background: #EFEFEF;\r\n                    border: 1px solid #CCC;\r\n                    padding: 0 10px;\r\n                    text-decoration: none;\r\n            }\r\n            dt a:hover{\r\n                background: #e0e0e0;\r\n                border-color: #999\r\n            }\r\n            dt a:active{\r\n                background: #ccc;\r\n                border-color: #999;\r\n                color: #666;\r\n            }\r\n            dd { line-height:20px;margin-top: 10px;}\r\n            span{ padding-right:4px;}\r\n            input{width:210px;height:21px;background: #FFF;border:1px solid #d7d7d7;padding: 0px; margin: 0px; }\r\n\r\n\r\n        </style>\r\n    </head>\r\n    <body>\r\n        <div class=\"content\">\r\n            <h2><var id=\"lang_showMsg\"></var></h2>\r\n            <dl>\r\n                <dt><a href=\"../../third-party/snapscreen/UEditorSnapscreen.exe\" target=\"_blank\" id=\"downlink\"><var id=\"lang_download\"></var></a></dt>\r\n                <dd><var id=\"lang_step1\"></var></dd>\r\n                <dd><var id=\"lang_step2\"></var></dd>\r\n            </dl>\r\n        </div>\r\n    </body>\r\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/spechars/spechars.html",
    "content": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\r\n        \"http://www.w3.org/TR/html4/loose.dtd\">\r\n<html>\r\n<head>\r\n    <title></title>\r\n    <meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"/>\r\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n    <style type=\"text/css\">\r\n        html,body{overflow:hidden;}\r\n        #specharsTab{width: 97%;margin: 10px auto; zoom:1;position: relative}\r\n        .tabbody {height:447px;}\r\n        .tabbody span{ margin: 5px 3px;text-align: center;display:inline-block;width: 40px;height:16px;line-height: 16px;cursor: pointer; }\r\n    </style>\r\n</head>\r\n<body>\r\n    <div id=\"specharsTab\">\r\n        <div id=\"tabHeads\" class=\"tabhead\"></div><div id=\"tabBodys\" class=\"tabbody\"></div>\r\n    </div>\r\n<script type=\"text/javascript\" src=\"spechars.js\"></script>\r\n</body>\r\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/spechars/spechars.js",
    "content": "/**\r\n * Created with JetBrains PhpStorm.\r\n * User: xuheng\r\n * Date: 12-9-26\r\n * Time: 下午1:09\r\n * To change this template use File | Settings | File Templates.\r\n */\r\nvar charsContent = [\r\n    { name:\"tsfh\", title:lang.tsfh, content:toArray(\"、,。,·,ˉ,ˇ,¨,〃,々,—,～,‖,…,‘,’,“,”,〔,〕,〈,〉,《,》,「,」,『,』,〖,〗,【,】,±,×,÷,∶,∧,∨,∑,∏,∪,∩,∈,∷,√,⊥,∥,∠,⌒,⊙,∫,∮,≡,≌,≈,∽,∝,≠,≮,≯,≤,≥,∞,∵,∴,♂,♀,°,′,″,℃,＄,¤,￠,￡,‰,§,№,☆,★,○,●,◎,◇,◆,□,■,△,▲,※,→,←,↑,↓,〓,〡,〢,〣,〤,〥,〦,〧,〨,〩,㊣,㎎,㎏,㎜,㎝,㎞,㎡,㏄,㏎,㏑,㏒,㏕,︰,￢,￤,℡,ˊ,ˋ,˙,–,―,‥,‵,℅,℉,↖,↗,↘,↙,∕,∟,∣,≒,≦,≧,⊿,═,║,╒,╓,╔,╕,╖,╗,╘,╙,╚,╛,╜,╝,╞,╟,╠,╡,╢,╣,╤,╥,╦,╧,╨,╩,╪,╫,╬,╭,╮,╯,╰,╱,╲,╳,▁,▂,▃,▄,▅,▆,▇,�,█,▉,▊,▋,▌,▍,▎,▏,▓,▔,▕,▼,▽,◢,◣,◤,◥,☉,⊕,〒,〝,〞\")},\r\n    { name:\"lmsz\", title:lang.lmsz, content:toArray(\"ⅰ,ⅱ,ⅲ,ⅳ,ⅴ,ⅵ,ⅶ,ⅷ,ⅸ,ⅹ,Ⅰ,Ⅱ,Ⅲ,Ⅳ,Ⅴ,Ⅵ,Ⅶ,Ⅷ,Ⅸ,Ⅹ,Ⅺ,Ⅻ\")},\r\n    { name:\"szfh\", title:lang.szfh, content:toArray(\"⒈,⒉,⒊,⒋,⒌,⒍,⒎,⒏,⒐,⒑,⒒,⒓,⒔,⒕,⒖,⒗,⒘,⒙,⒚,⒛,⑴,⑵,⑶,⑷,⑸,⑹,⑺,⑻,⑼,⑽,⑾,⑿,⒀,⒁,⒂,⒃,⒄,⒅,⒆,⒇,①,②,③,④,⑤,⑥,⑦,⑧,⑨,⑩,㈠,㈡,㈢,㈣,㈤,㈥,㈦,㈧,㈨,㈩\")},\r\n    { name:\"rwfh\", title:lang.rwfh, content:toArray(\"ぁ,あ,ぃ,い,ぅ,う,ぇ,え,ぉ,お,か,が,き,ぎ,く,ぐ,け,げ,こ,ご,さ,ざ,し,じ,す,ず,せ,ぜ,そ,ぞ,た,だ,ち,ぢ,っ,つ,づ,て,で,と,ど,な,に,ぬ,ね,の,は,ば,ぱ,ひ,び,ぴ,ふ,ぶ,ぷ,へ,べ,ぺ,ほ,ぼ,ぽ,ま,み,む,め,も,ゃ,や,ゅ,ゆ,ょ,よ,ら,り,る,れ,ろ,ゎ,わ,ゐ,ゑ,を,ん,ァ,ア,ィ,イ,ゥ,ウ,ェ,エ,ォ,オ,カ,ガ,キ,ギ,ク,グ,ケ,ゲ,コ,ゴ,サ,ザ,シ,ジ,ス,ズ,セ,ゼ,ソ,ゾ,タ,ダ,チ,ヂ,ッ,ツ,ヅ,テ,デ,ト,ド,ナ,ニ,ヌ,ネ,ノ,ハ,バ,パ,ヒ,ビ,ピ,フ,ブ,プ,ヘ,ベ,ペ,ホ,ボ,ポ,マ,ミ,ム,メ,モ,ャ,ヤ,ュ,ユ,ョ,ヨ,ラ,リ,ル,レ,ロ,ヮ,ワ,ヰ,ヱ,ヲ,ン,ヴ,ヵ,ヶ\")},\r\n    { name:\"xlzm\", title:lang.xlzm, content:toArray(\"Α,Β,Γ,Δ,Ε,Ζ,Η,Θ,Ι,Κ,Λ,Μ,Ν,Ξ,Ο,Π,Ρ,Σ,Τ,Υ,Φ,Χ,Ψ,Ω,α,β,γ,δ,ε,ζ,η,θ,ι,κ,λ,μ,ν,ξ,ο,π,ρ,σ,τ,υ,φ,χ,ψ,ω\")},\r\n    { name:\"ewzm\", title:lang.ewzm, content:toArray(\"А,Б,В,Г,Д,Е,Ё,Ж,З,И,Й,К,Л,М,Н,О,П,Р,С,Т,У,Ф,Х,Ц,Ч,Ш,Щ,Ъ,Ы,Ь,Э,Ю,Я,а,б,в,г,д,е,ё,ж,з,и,й,к,л,м,н,о,п,р,с,т,у,ф,х,ц,ч,ш,щ,ъ,ы,ь,э,ю,я\")},\r\n    { name:\"pyzm\", title:lang.pyzm, content:toArray(\"ā,á,ǎ,à,ē,é,ě,è,ī,í,ǐ,ì,ō,ó,ǒ,ò,ū,ú,ǔ,ù,ǖ,ǘ,ǚ,ǜ,ü\")},\r\n    { name:\"yyyb\", title:lang.yyyb, content:toArray(\"i:,i,e,æ,ʌ,ə:,ə,u:,u,ɔ:,ɔ,a:,ei,ai,ɔi,əu,au,iə,εə,uə,p,t,k,b,d,g,f,s,ʃ,θ,h,v,z,ʒ,ð,tʃ,tr,ts,dʒ,dr,dz,m,n,ŋ,l,r,w,j,\")},\r\n    { name:\"zyzf\", title:lang.zyzf, content:toArray(\"ㄅ,ㄆ,ㄇ,ㄈ,ㄉ,ㄊ,ㄋ,ㄌ,ㄍ,ㄎ,ㄏ,ㄐ,ㄑ,ㄒ,ㄓ,ㄔ,ㄕ,ㄖ,ㄗ,ㄘ,ㄙ,ㄚ,ㄛ,ㄜ,ㄝ,ㄞ,ㄟ,ㄠ,ㄡ,ㄢ,ㄣ,ㄤ,ㄥ,ㄦ,ㄧ,ㄨ\")}\r\n];\r\n(function createTab(content) {\r\n    for (var i = 0, ci; ci = content[i++];) {\r\n        var span = document.createElement(\"span\");\r\n        span.setAttribute(\"tabSrc\", ci.name);\r\n        span.innerHTML = ci.title;\r\n        if (i == 1)span.className = \"focus\";\r\n        domUtils.on(span, \"click\", function () {\r\n            var tmps = $G(\"tabHeads\").children;\r\n            for (var k = 0, sk; sk = tmps[k++];) {\r\n                sk.className = \"\";\r\n            }\r\n            tmps = $G(\"tabBodys\").children;\r\n            for (var k = 0, sk; sk = tmps[k++];) {\r\n                sk.style.display = \"none\";\r\n            }\r\n            this.className = \"focus\";\r\n            $G(this.getAttribute(\"tabSrc\")).style.display = \"\";\r\n        });\r\n        $G(\"tabHeads\").appendChild(span);\r\n        domUtils.insertAfter(span, document.createTextNode(\"\\n\"));\r\n        var div = document.createElement(\"div\");\r\n        div.id = ci.name;\r\n        div.style.display = (i == 1) ? \"\" : \"none\";\r\n        var cons = ci.content;\r\n        for (var j = 0, con; con = cons[j++];) {\r\n            var charSpan = document.createElement(\"span\");\r\n            charSpan.innerHTML = con;\r\n            domUtils.on(charSpan, \"click\", function () {\r\n                editor.execCommand(\"insertHTML\", this.innerHTML);\r\n                dialog.close();\r\n            });\r\n            div.appendChild(charSpan);\r\n        }\r\n        $G(\"tabBodys\").appendChild(div);\r\n    }\r\n})(charsContent);\r\nfunction toArray(str) {\r\n    return str.split(\",\");\r\n}\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/table/edittable.css",
    "content": "body{\n    overflow: hidden;\n    width: 540px;\n}\n.wrapper {\n    margin: 10px auto 0;\n    font-size: 12px;\n    overflow: hidden;\n    width: 520px;\n    height: 315px;\n}\n\n.clear {\n    clear: both;\n}\n\n.wrapper .left {\n    float: left;\n    margin-left: 10px;;\n}\n\n.wrapper .right {\n    float: right;\n    border-left: 2px dotted #EDEDED;\n    padding-left: 15px;\n}\n\n.section {\n    margin-bottom: 15px;\n    width: 240px;\n    overflow: hidden;\n}\n\n.section h3 {\n    font-weight: bold;\n    padding: 5px 0;\n    margin-bottom: 10px;\n    border-bottom: 1px solid #EDEDED;\n    font-size: 12px;\n}\n\n.section ul {\n    list-style: none;\n    overflow: hidden;\n    clear: both;\n\n}\n\n.section li {\n    float: left;\n    width: 120px;;\n}\n\n.section .tone {\n    width: 80px;;\n}\n\n.section .preview {\n    width: 220px;\n}\n\n.section .preview table {\n    text-align: center;\n    vertical-align: middle;\n    color: #666;\n}\n\n.section .preview caption {\n    font-weight: bold;\n}\n\n.section .preview td {\n    border-width: 1px;\n    border-style: solid;\n    height: 22px;\n}\n\n.section .preview th {\n    border-style: solid;\n    border-color: #DDD;\n    border-width: 2px 1px 1px 1px;\n    height: 22px;\n    background-color: #F7F7F7;\n}"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/table/edittable.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n    <title></title>\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"edittable.css\">\n</head>\n<body>\n<div class=\"wrapper\">\n    <div class=\"left\">\n        <div class=\"section\">\n            <h3><var id=\"lang_tableStyle\"></var></h3>\n            <ul>\n                <li>\n                    <label onselectstart=\"return false\"><input type=\"checkbox\" id=\"J_title\" name=\"style\"/><var id=\"lang_insertTitle\"></var></label>\n                </li>\n                <li>\n                    <label onselectstart=\"return false\"><input type=\"checkbox\" id=\"J_titleCol\" name=\"style\"/><var id=\"lang_insertTitleCol\"></var></label>\n                </li>\n            </ul>\n            <ul>\n                <li>\n                    <label onselectstart=\"return false\"><input type=\"checkbox\" id=\"J_caption\" name=\"style\"/><var id=\"lang_insertCaption\"></var></label>\n                </li>\n                <li>\n                    <label onselectstart=\"return false\"><input type=\"checkbox\" id=\"J_sorttable\" name=\"style\"/><var id=\"lang_orderbycontent\"></var></label>\n                </li>\n            </ul>\n            <div class=\"clear\"></div>\n        </div>\n        <div class=\"section\">\n            <h3><var id=\"lang_tableSize\"></var></h3>\n            <ul>\n                <li>\n                    <label><input type=\"radio\" id=\"J_autoSizeContent\" name=\"size\"/><var id=\"lang_autoSizeContent\"></var></label>\n                </li>\n                <li>\n                    <label><input type=\"radio\" id=\"J_autoSizePage\" name=\"size\"/><var id=\"lang_autoSizePage\"></var></label>\n                </li>\n            </ul>\n            <div class=\"clear\"></div>\n        </div>\n        <div class=\"section\">\n            <h3><var id=\"lang_borderStyle\"></var></h3>\n            <ul>\n                <li>\n                    <span><var id=\"lang_color\"></var></span>\n                    <input type=\"text\" class=\"tone\" id=\"J_tone\" readonly='readonly' />\n                </li>\n            </ul>\n            <div class=\"clear\"></div>\n        </div>\n    </div>\n    <div class=\"right\">\n        <div class=\"section\">\n            <h3><var id=\"lang_example\"></var></h3>\n            <div class=\"preview\" id=\"J_preview\">\n            </div>\n        </div>\n    </div>\n</div>\n<script type=\"text/javascript\" src=\"edittable.js\"></script>\n</body>\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/table/edittable.js",
    "content": "/**\n * Created with JetBrains PhpStorm.\n * User: xuheng\n * Date: 12-12-19\n * Time: 下午4:55\n * To change this template use File | Settings | File Templates.\n */\n(function () {\n    var title = $G(\"J_title\"),\n        titleCol = $G(\"J_titleCol\"),\n        caption = $G(\"J_caption\"),\n        sorttable = $G(\"J_sorttable\"),\n        autoSizeContent = $G(\"J_autoSizeContent\"),\n        autoSizePage = $G(\"J_autoSizePage\"),\n        tone = $G(\"J_tone\"),\n        me,\n        preview = $G(\"J_preview\");\n\n    var editTable = function () {\n        me = this;\n        me.init();\n    };\n    editTable.prototype = {\n        init:function () {\n            var colorPiker = new UE.ui.ColorPicker({\n                    editor:editor\n                }),\n                colorPop = new UE.ui.Popup({\n                    editor:editor,\n                    content:colorPiker\n                });\n\n            title.checked = editor.queryCommandState(\"inserttitle\") == -1;\n            titleCol.checked = editor.queryCommandState(\"inserttitlecol\") == -1;\n            caption.checked = editor.queryCommandState(\"insertcaption\") == -1;\n            sorttable.checked = editor.queryCommandState(\"enablesort\") == 1;\n\n            var enablesortState = editor.queryCommandState(\"enablesort\"),\n                disablesortState = editor.queryCommandState(\"disablesort\");\n\n            sorttable.checked = !!(enablesortState < 0 && disablesortState >=0);\n            sorttable.disabled = !!(enablesortState < 0 && disablesortState < 0);\n            sorttable.title = enablesortState < 0 && disablesortState < 0 ? lang.errorMsg:'';\n\n            me.createTable(title.checked, titleCol.checked, caption.checked);\n            me.setAutoSize();\n            me.setColor(me.getColor());\n\n            domUtils.on(title, \"click\", me.titleHanler);\n            domUtils.on(titleCol, \"click\", me.titleColHanler);\n            domUtils.on(caption, \"click\", me.captionHanler);\n            domUtils.on(sorttable, \"click\", me.sorttableHanler);\n            domUtils.on(autoSizeContent, \"click\", me.autoSizeContentHanler);\n            domUtils.on(autoSizePage, \"click\", me.autoSizePageHanler);\n\n            domUtils.on(tone, \"click\", function () {\n                colorPop.showAnchor(tone);\n            });\n            domUtils.on(document, 'mousedown', function () {\n                colorPop.hide();\n            });\n            colorPiker.addListener(\"pickcolor\", function () {\n                me.setColor(arguments[1]);\n                colorPop.hide();\n            });\n            colorPiker.addListener(\"picknocolor\", function () {\n                me.setColor(\"\");\n                colorPop.hide();\n            });\n        },\n\n        createTable:function (hasTitle, hasTitleCol, hasCaption) {\n            var arr = [],\n                sortSpan = '<span>^</span>';\n            arr.push(\"<table id='J_example'>\");\n            if (hasCaption) {\n                arr.push(\"<caption>\" + lang.captionName + \"</caption>\")\n            }\n            if (hasTitle) {\n                arr.push(\"<tr>\");\n                if(hasTitleCol) { arr.push(\"<th>\" + lang.titleName + \"</th>\"); }\n                for (var j = 0; j < 5; j++) {\n                    arr.push(\"<th>\" + lang.titleName + \"</th>\");\n                }\n                arr.push(\"</tr>\");\n            }\n            for (var i = 0; i < 6; i++) {\n                arr.push(\"<tr>\");\n                if(hasTitleCol) { arr.push(\"<th>\" + lang.titleName + \"</th>\") }\n                for (var k = 0; k < 5; k++) {\n                    arr.push(\"<td>\" + lang.cellsName + \"</td>\")\n                }\n                arr.push(\"</tr>\");\n            }\n            arr.push(\"</table>\");\n            preview.innerHTML = arr.join(\"\");\n            this.updateSortSpan();\n        },\n        titleHanler:function () {\n            var example = $G(\"J_example\"),\n                frg=document.createDocumentFragment(),\n                color = domUtils.getComputedStyle(domUtils.getElementsByTagName(example, \"td\")[0], \"border-color\"),\n                colCount = example.rows[0].children.length;\n\n            if (title.checked) {\n                example.insertRow(0);\n                for (var i = 0, node; i < colCount; i++) {\n                    node = document.createElement(\"th\");\n                    node.innerHTML = lang.titleName;\n                    frg.appendChild(node);\n                }\n                example.rows[0].appendChild(frg);\n\n            } else {\n                domUtils.remove(example.rows[0]);\n            }\n            me.setColor(color);\n            me.updateSortSpan();\n        },\n        titleColHanler:function () {\n            var example = $G(\"J_example\"),\n                color = domUtils.getComputedStyle(domUtils.getElementsByTagName(example, \"td\")[0], \"border-color\"),\n                colArr = example.rows,\n                colCount = colArr.length;\n\n            if (titleCol.checked) {\n                for (var i = 0, node; i < colCount; i++) {\n                    node = document.createElement(\"th\");\n                    node.innerHTML = lang.titleName;\n                    colArr[i].insertBefore(node, colArr[i].children[0]);\n                }\n            } else {\n                for (var i = 0; i < colCount; i++) {\n                    domUtils.remove(colArr[i].children[0]);\n                }\n            }\n            me.setColor(color);\n            me.updateSortSpan();\n        },\n        captionHanler:function () {\n            var example = $G(\"J_example\");\n            if (caption.checked) {\n                var row = document.createElement('caption');\n                row.innerHTML = lang.captionName;\n                example.insertBefore(row, example.firstChild);\n            } else {\n                domUtils.remove(domUtils.getElementsByTagName(example, 'caption')[0]);\n            }\n        },\n        sorttableHanler:function(){\n            me.updateSortSpan();\n        },\n        autoSizeContentHanler:function () {\n            var example = $G(\"J_example\");\n            example.removeAttribute(\"width\");\n        },\n        autoSizePageHanler:function () {\n            var example = $G(\"J_example\");\n            var tds = example.getElementsByTagName(example, \"td\");\n            utils.each(tds, function (td) {\n                td.removeAttribute(\"width\");\n            });\n            example.setAttribute('width', '100%');\n        },\n        updateSortSpan: function(){\n            var example = $G(\"J_example\"),\n                row = example.rows[0];\n\n            var spans = domUtils.getElementsByTagName(example,\"span\");\n            utils.each(spans,function(span){\n                span.parentNode.removeChild(span);\n            });\n            if (sorttable.checked) {\n                utils.each(row.cells, function(cell, i){\n                    var span = document.createElement(\"span\");\n                    span.innerHTML = \"^\";\n                    cell.appendChild(span);\n                });\n            }\n        },\n        getColor:function () {\n            var start = editor.selection.getStart(), color,\n                cell = domUtils.findParentByTagName(start, [\"td\", \"th\", \"caption\"], true);\n            color = cell && domUtils.getComputedStyle(cell, \"border-color\");\n            if (!color)  color = \"#DDDDDD\";\n            return color;\n        },\n        setColor:function (color) {\n            var example = $G(\"J_example\"),\n                arr = domUtils.getElementsByTagName(example, \"td\").concat(\n                    domUtils.getElementsByTagName(example, \"th\"),\n                    domUtils.getElementsByTagName(example, \"caption\")\n                );\n\n            tone.value = color;\n            utils.each(arr, function (node) {\n                node.style.borderColor = color;\n            });\n\n        },\n        setAutoSize:function () {\n            var me = this;\n            autoSizePage.checked = true;\n            me.autoSizePageHanler();\n        }\n    };\n\n    new editTable;\n\n    dialog.onok = function () {\n        editor.__hasEnterExecCommand = true;\n\n        var checks = {\n            title:\"inserttitle deletetitle\",\n            titleCol:\"inserttitlecol deletetitlecol\",\n            caption:\"insertcaption deletecaption\",\n            sorttable:\"enablesort disablesort\"\n        };\n        editor.fireEvent('saveScene');\n        for(var i in checks){\n            var cmds = checks[i].split(\" \"),\n                input = $G(\"J_\" + i);\n            if(input[\"checked\"]){\n                editor.queryCommandState(cmds[0])!=-1 &&editor.execCommand(cmds[0]);\n            }else{\n                editor.queryCommandState(cmds[1])!=-1 &&editor.execCommand(cmds[1]);\n            }\n        }\n\n        editor.execCommand(\"edittable\", tone.value);\n        autoSizeContent.checked ?editor.execCommand('adaptbytext') : \"\";\n        autoSizePage.checked ? editor.execCommand(\"adaptbywindow\") : \"\";\n        editor.fireEvent('saveScene');\n\n        editor.__hasEnterExecCommand = false;\n    };\n})();"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/table/edittd.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n    <title></title>\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\n    <style type=\"text/css\">\n        .section {\n            text-align: center;\n            margin-top: 10px;\n        }\n        .section input {\n            margin-left: 5px;\n            width: 70px;\n        }\n    </style>\n</head>\n<body>\n<div class=\"section\">\n    <span><var id=\"lang_tdBkColor\"></var></span>\n    <input type=\"text\" id=\"J_tone\"/>\n</div>\n<script type=\"text/javascript\">\n    var tone = $G(\"J_tone\"),\n            colorPiker = new UE.ui.ColorPicker({\n                editor:editor\n            }),\n            colorPop = new UE.ui.Popup({\n                editor:editor,\n                content:colorPiker\n            });\n    domUtils.on(tone, \"click\", function () {\n        colorPop.showAnchor(tone);\n    });\n    domUtils.on(document, 'mousedown', function () {\n        colorPop.hide();\n    });\n    colorPiker.addListener(\"pickcolor\", function () {\n        tone.value = arguments[1];\n        colorPop.hide();\n    });\n    colorPiker.addListener(\"picknocolor\", function () {\n        tone.value=\"\";\n        colorPop.hide();\n    });\n    dialog.onok=function(){\n        editor.execCommand(\"edittd\",tone.value);\n    };\n\n    var start = editor.selection.getStart(),\n        cell = start && domUtils.findParentByTagName(start, [\"td\", \"th\"], true);\n    if(cell){\n        var color = domUtils.getComputedStyle(cell,'background-color');\n        if(/^#/.test(color)){\n            tone.value = color\n        }\n\n    }\n\n</script>\n</body>\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/table/edittip.html",
    "content": "<!DOCTYPE html>\n<html>\n<head>\n    <title>表格删除提示</title>\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\n    <style type=\"text/css\">\n        .section {\n            width: 200px;\n            margin: 10px auto 0;\n            font-size: 14px;\n        }\n\n        .item {\n            text-align: center;\n        }\n    </style>\n</head>\n<body>\n<div class=\"section\">\n    <div class=\"item\">\n        <label><input type=\"radio\" id=\"J_delRow\" name=\"cmd\" checked/><var id=\"lang_delRow\"></var></label>\n    </div>\n    <div class=\"item\">\n        <label><input type=\"radio\" id=\"J_delCol\" name=\"cmd\"/><var id=\"lang_delCol\"></var></label>\n    </div>\n</div>\n<script type=\"text/javascript\">\n    dialog.onok = function () {\n        $G(\"J_delRow\").checked ? editor.execCommand(\"deleterow\") : editor.execCommand(\"deletecol\");\n    };\n</script>\n</body>\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/template/config.js",
    "content": "/**\r\n * Created with JetBrains PhpStorm.\r\n * User: xuheng\r\n * Date: 12-8-8\r\n * Time: 下午2:00\r\n * To change this template use File | Settings | File Templates.\r\n */\r\nvar templates = [\r\n    {\r\n        \"pre\":\"pre0.png\",\r\n        'title':lang.blank,\r\n        'preHtml':'<p class=\"ue_t\">&nbsp;欢迎使用UEditor！</p>',\r\n        \"html\":'<p class=\"ue_t\">欢迎使用UEditor！</p>'\r\n\r\n    },\r\n    {\r\n        \"pre\":\"pre1.png\",\r\n        'title':lang.blog,\r\n        'preHtml':'<h1 label=\"Title center\" name=\"tc\" style=\"border-bottom-color:#cccccc;border-bottom-width:2px;border-bottom-style:solid;padding:0px 4px 0px 0px;text-align:center;margin:0px 0px 20px;\"><span style=\"color:#c0504d;\">深入理解Range</span></h1><p style=\"text-align:center;\"><strong class=\" \">UEditor二次开发</strong></p><h3><span class=\" \" style=\"font-family:幼圆\">什么是Range</span></h3><p style=\"text-indent:2em;\">对于“插入”选项卡上的库，在设计时都充分考虑了其中的项与文档整体外观的协调性。 </p><br /><h3><span class=\" \" style=\"font-family:幼圆\">Range能干什么</span></h3><p style=\"text-indent:2em;\">在“开始”选项卡上，通过从快速样式库中为所选文本选择一种外观，您可以方便地更改文档中所选文本的格式。</p>',\r\n        \"html\":'<h1 class=\"ue_t\" label=\"Title center\" name=\"tc\" style=\"border-bottom-color:#cccccc;border-bottom-width:2px;border-bottom-style:solid;padding:0px 4px 0px 0px;text-align:center;margin:0px 0px 20px;\"><span style=\"color:#c0504d;\">[键入文档标题]</span></h1><p style=\"text-align:center;\"><strong class=\"ue_t\">[键入文档副标题]</strong></p><h3><span class=\"ue_t\" style=\"font-family:幼圆\">[标题 1]</span></h3><p class=\"ue_t\"  style=\"text-indent:2em;\">对于“插入”选项卡上的库，在设计时都充分考虑了其中的项与文档整体外观的协调性。 您可以使用这些库来插入表格、页眉、页脚、列表、封面以及其他文档构建基块。 您创建的图片、图表或关系图也将与当前的文档外观协调一致。</p><h3><span class=\"ue_t\" style=\"font-family:幼圆\">[标题 2]</span></h3><p class=\"ue_t\"  style=\"text-indent:2em;\">在“开始”选项卡上，通过从快速样式库中为所选文本选择一种外观，您可以方便地更改文档中所选文本的格式。 您还可以使用“开始”选项卡上的其他控件来直接设置文本格式。大多数控件都允许您选择是使用当前主题外观，还是使用某种直接指定的格式。 </p><h3><span class=\"ue_t\" style=\"font-family:幼圆\">[标题 3]</span></h3><p class=\"ue_t\">对于“插入”选项卡上的库，在设计时都充分考虑了其中的项与文档整体外观的协调性。 您可以使用这些库来插入表格、页眉、页脚、列表、封面以及其他文档构建基块。 您创建的图片、图表或关系图也将与当前的文档外观协调一致。</p><p class=\"ue_t\"><br /></p>'\r\n\r\n    },\r\n    {\r\n        \"pre\":\"pre2.png\",\r\n        'title':lang.resume,\r\n        'preHtml':'<h1 label=\"Title left\" name=\"tl\" style=\"border-bottom-color:#cccccc;border-bottom-width:2px;border-bottom-style:solid;padding:0px 4px 0px 0px;margin:0px 0px 10px;\"><span style=\"color:#e36c09;\" class=\" \">WEB前端开发简历</span></h1><table width=\"100%\" border=\"1\" bordercolor=\"#95B3D7\" style=\"border-collapse:collapse;\"><tbody><tr><td width=\"100\" style=\"text-align:center;\"><p><span style=\"background-color:transparent;\">插</span><br /></p><p>入</p><p>照</p><p>片</p></td><td><p><span style=\"background-color:transparent;\"> 联系电话：</span><span class=\"ue_t\" style=\"background-color:transparent;\">[键入您的电话]</span><br /></p><p><span style=\"background-color:transparent;\"> 电子邮件：</span><span class=\"ue_t\" style=\"background-color:transparent;\">[键入您的电子邮件地址]</span><br /></p><p><span style=\"background-color:transparent;\"> 家庭住址：</span><span class=\"ue_t\" style=\"background-color:transparent;\">[键入您的地址]</span><br /></p></td></tr></tbody></table><h3><span style=\"color:#E36C09;font-size:20px;\">目标职位</span></h3><p style=\"text-indent:2em;\" class=\" \">WEB前端研发工程师</p><h3><span style=\"color:#e36c09;font-size:20px;\">学历</span></h3><p><span style=\"display:none;line-height:0px;\" id=\"_baidu_bookmark_start_26\">﻿</span></p><ol style=\"list-style-type:decimal;\"><li><p><span class=\"ue_t\">[起止时间]</span> <span class=\"ue_t\">[学校名称] </span> <span class=\"ue_t\">[所学专业]</span> <span class=\"ue_t\">[所获学位]</span></p></li></ol><h3><span style=\"color:#e36c09;font-size:20px;\" class=\"ue_t\">工作经验</span></h3><p><br /></p>',\r\n        \"html\":'<h1 label=\"Title left\" name=\"tl\" style=\"border-bottom-color:#cccccc;border-bottom-width:2px;border-bottom-style:solid;padding:0px 4px 0px 0px;margin:0px 0px 10px;\"><span style=\"color:#e36c09;\" class=\"ue_t\">[此处键入简历标题]</span></h1><p><span style=\"color:#e36c09;\"><br /></span></p><table width=\"100%\" border=\"1\" bordercolor=\"#95B3D7\" style=\"border-collapse:collapse;\"><tbody><tr><td width=\"200\" style=\"text-align:center;\" class=\"ue_t\">【此处插入照片】</td><td><p><br /></p><p> 联系电话：<span class=\"ue_t\">[键入您的电话]</span></p><p><br /></p><p> 电子邮件：<span class=\"ue_t\">[键入您的电子邮件地址]</span></p><p><br /></p><p> 家庭住址：<span class=\"ue_t\">[键入您的地址]</span></p><p><br /></p></td></tr></tbody></table><h3><span style=\"color:#e36c09;font-size:20px;\">目标职位</span></h3><p style=\"text-indent:2em;\" class=\"ue_t\">[此处键入您的期望职位]</p><h3><span style=\"color:#e36c09;font-size:20px;\">学历</span></h3><p><span style=\"display:none;line-height:0px;\" id=\"_baidu_bookmark_start_26\">﻿</span></p><ol style=\"list-style-type:decimal;\"><li><p><span class=\"ue_t\">[键入起止时间]</span> <span class=\"ue_t\">[键入学校名称] </span> <span class=\"ue_t\">[键入所学专业]</span> <span class=\"ue_t\">[键入所获学位]</span></p></li><li><p><span class=\"ue_t\">[键入起止时间]</span> <span class=\"ue_t\">[键入学校名称]</span> <span class=\"ue_t\">[键入所学专业]</span> <span class=\"ue_t\">[键入所获学位]</span></p></li></ol><h3><span style=\"color:#e36c09;font-size:20px;\" class=\"ue_t\">工作经验</span></h3><ol style=\"list-style-type:decimal;\"><li><p><span class=\"ue_t\">[键入起止时间]</span> <span class=\"ue_t\">[键入公司名称]</span> <span class=\"ue_t\">[键入职位名称]</span> </p></li><ol style=\"list-style-type:lower-alpha;\"><li><p><span class=\"ue_t\">[键入负责项目]</span> <span class=\"ue_t\">[键入项目简介]</span></p></li><li><p><span class=\"ue_t\">[键入负责项目]</span> <span class=\"ue_t\">[键入项目简介]</span></p></li></ol><li><p><span class=\"ue_t\">[键入起止时间]</span> <span class=\"ue_t\">[键入公司名称]</span> <span class=\"ue_t\">[键入职位名称]</span> </p></li><ol style=\"list-style-type:lower-alpha;\"><li><p><span class=\"ue_t\">[键入负责项目]</span> <span class=\"ue_t\">[键入项目简介]</span></p></li></ol></ol><p><span style=\"color:#e36c09;font-size:20px;\">掌握技能</span></p><p style=\"text-indent:2em;\"> &nbsp;<span class=\"ue_t\">[这里可以键入您所掌握的技能]</span><br /></p>'\r\n\r\n    },\r\n    {\r\n        \"pre\":\"pre3.png\",\r\n        'title':lang.richText,\r\n        'preHtml':'<h1 label=\"Title center\" name=\"tc\" style=\"border-bottom-color:#cccccc;border-bottom-width:2px;border-bottom-style:solid;padding:0px 4px 0px 0px;text-align:center;margin:0px 0px 20px;\" class=\"ue_t\">[此处键入文章标题]</h1><p><img src=\"http://img.baidu.com/hi/youa/y_0034.gif\" width=\"150\" height=\"100\" border=\"0\" hspace=\"0\" vspace=\"0\" style=\"width:150px;height:100px;float:left;\" />图文混排方法</p><p>图片居左，文字围绕图片排版</p><p>方法：在文字前面插入图片，设置居左对齐，然后即可在右边输入多行文</p><p><br /></p><p><img src=\"http://img.baidu.com/hi/youa/y_0040.gif\" width=\"100\" height=\"100\" border=\"0\" hspace=\"0\" vspace=\"0\" style=\"width:100px;height:100px;float:right;\" /></p><p>还有没有什么其他的环绕方式呢？这里是居右环绕</p><p><br /></p><p>欢迎大家多多尝试，为UEditor提供更多高质量模板！</p>',\r\n        \"html\":'<p><br /></p><h1 label=\"Title center\" name=\"tc\" style=\"border-bottom-color:#cccccc;border-bottom-width:2px;border-bottom-style:solid;padding:0px 4px 0px 0px;text-align:center;margin:0px 0px 20px;\" class=\"ue_t\">[此处键入文章标题]</h1><p><img src=\"http://img.baidu.com/hi/youa/y_0034.gif\" width=\"300\" height=\"200\" border=\"0\" hspace=\"0\" vspace=\"0\" style=\"width:300px;height:200px;float:left;\" />图文混排方法</p><p>1. 图片居左，文字围绕图片排版</p><p>方法：在文字前面插入图片，设置居左对齐，然后即可在右边输入多行文本</p><p><br /></p><p>2. 图片居右，文字围绕图片排版</p><p>方法：在文字前面插入图片，设置居右对齐，然后即可在左边输入多行文本</p><p><br /></p><p>3. 图片居中环绕排版</p><p>方法：亲，这个真心没有办法。。。</p><p><br /></p><p><br /></p><p><img src=\"http://img.baidu.com/hi/youa/y_0040.gif\" width=\"300\" height=\"300\" border=\"0\" hspace=\"0\" vspace=\"0\" style=\"width:300px;height:300px;float:right;\" /></p><p>还有没有什么其他的环绕方式呢？这里是居右环绕</p><p><br /></p><p>欢迎大家多多尝试，为UEditor提供更多高质量模板！</p><p><br /></p><p>占位</p><p><br /></p><p>占位</p><p><br /></p><p>占位</p><p><br /></p><p>占位</p><p><br /></p><p>占位</p><p><br /></p><p><br /></p>'\r\n    },\r\n    {\r\n        \"pre\":\"pre4.png\",\r\n        'title':lang.sciPapers,\r\n        'preHtml':'<h2 style=\"border-bottom-color:#cccccc;border-bottom-width:2px;border-bottom-style:solid;padding:0px 4px 0px 0px;margin:0px 0px 10px;text-align:center;\" class=\"ue_t\">[键入文章标题]</h2><p><strong><span style=\"font-size:12px;\">摘要</span></strong><span style=\"font-size:12px;\" class=\"ue_t\">：这里可以输入很长很长很长很长很长很长很长很长很差的摘要</span></p><p style=\"line-height:1.5em;\"><strong>标题 1</strong></p><p style=\"text-indent:2em;\"><span style=\"font-size:14px;\" class=\"ue_t\">这里可以输入很多内容，可以图文混排，可以有列表等。</span></p><p style=\"line-height:1.5em;\"><strong>标题 2</strong></p><ol style=\"list-style-type:lower-alpha;\"><li><p class=\"ue_t\">列表 1</p></li><li><p class=\"ue_t\">列表 2</p></li><ol style=\"list-style-type:lower-roman;\"><li><p class=\"ue_t\">多级列表 1</p></li><li><p class=\"ue_t\">多级列表 2</p></li></ol><li><p class=\"ue_t\">列表 3<br /></p></li></ol><p style=\"line-height:1.5em;\"><strong>标题 3</strong></p><p style=\"text-indent:2em;\"><span style=\"font-size:14px;\" class=\"ue_t\">来个文字图文混排的</span></p><p style=\"text-indent:2em;\"><br /></p>',\r\n        'html':'<h2 style=\"border-bottom-color:#cccccc;border-bottom-width:2px;border-bottom-style:solid;padding:0px 4px 0px 0px;margin:0px 0px 10px;text-align:center;\" class=\"ue_t\">[键入文章标题]</h2><p><strong><span style=\"font-size:12px;\">摘要</span></strong><span style=\"font-size:12px;\" class=\"ue_t\">：这里可以输入很长很长很长很长很长很长很长很长很差的摘要</span></p><p style=\"line-height:1.5em;\"><strong>标题 1</strong></p><p style=\"text-indent:2em;\"><span style=\"font-size:14px;\" class=\"ue_t\">这里可以输入很多内容，可以图文混排，可以有列表等。</span></p><p style=\"line-height:1.5em;\"><strong>标题 2</strong></p><p style=\"text-indent:2em;\"><span style=\"font-size:14px;\" class=\"ue_t\">来个列表瞅瞅：</span></p><ol style=\"list-style-type:lower-alpha;\"><li><p class=\"ue_t\">列表 1</p></li><li><p class=\"ue_t\">列表 2</p></li><ol style=\"list-style-type:lower-roman;\"><li><p class=\"ue_t\">多级列表 1</p></li><li><p class=\"ue_t\">多级列表 2</p></li></ol><li><p class=\"ue_t\">列表 3<br /></p></li></ol><p style=\"line-height:1.5em;\"><strong>标题 3</strong></p><p style=\"text-indent:2em;\"><span style=\"font-size:14px;\" class=\"ue_t\">来个文字图文混排的</span></p><p style=\"text-indent:2em;\"><span style=\"font-size:14px;\" class=\"ue_t\">这里可以多行</span></p><p style=\"text-indent:2em;\"><span style=\"font-size:14px;\" class=\"ue_t\">右边是图片</span></p><p style=\"text-indent:2em;\"><span style=\"font-size:14px;\" class=\"ue_t\">绝对没有问题的，不信你也可以试试看</span></p><p><br /></p>'\r\n    }\r\n];"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/template/template.css",
    "content": ".wrap{ padding: 5px;font-size: 14px;}\r\n.left{width:425px;float: left;}\r\n.right{width:160px;border: 1px solid #ccc;float: right;padding: 5px;margin-right: 5px;}\r\n.right .pre{height: 332px;overflow-y: auto;}\r\n.right .preitem{border: white 1px solid;margin: 5px 0;padding: 2px 0;}\r\n.right .preitem:hover{background-color: lemonChiffon;cursor: pointer;border: #ccc 1px solid;}\r\n.right .preitem img{display: block;margin: 0 auto;width:100px;}\r\n.clear{clear: both;}\r\n.top{height:26px;line-height: 26px;padding: 5px;}\r\n.bottom{height:320px;width:100%;margin: 0 auto;}\r\n.transparent{ background: url(\"images/bg.gif\") repeat;}\r\n.bottom table tr td{border:1px dashed #ccc;}\r\n#colorPicker{width: 17px;height: 17px;border: 1px solid #CCC;display: inline-block;border-radius: 3px;box-shadow: 2px 2px 5px #D3D6DA;}\r\n.border_style1{padding:2px;border: 1px solid #ccc;border-radius: 5px;box-shadow:2px 2px 5px #d3d6da;}\r\np{margin: 5px 0}\r\ntable{clear:both;margin-bottom:10px;border-collapse:collapse;word-break:break-all;}\r\nli{clear:both}\r\nol{padding-left:40px; }"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/template/template.html",
    "content": "<!DOCTYPE HTML>\r\n<html>\r\n<head>\r\n    <title></title>\r\n    <meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"/>\r\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"template.css\">\r\n</head>\r\n<body>\r\n    <div class=\"wrap\">\r\n        <div class=\"left\">\r\n            <div class=\"top\">\r\n                <label><var id=\"lang_template_clear\"></var>：<input id=\"issave\" type=\"checkbox\"></label>\r\n            </div>\r\n            <div class=\"bottom border_style1\" id=\"preview\"></div>\r\n        </div>\r\n        <fieldset  class=\"right border_style1\">\r\n            <legend><var id=\"lang_template_select\"></var></legend>\r\n            <div class=\"pre\" id=\"preitem\"></div>\r\n        </fieldset>\r\n        <div class=\"clear\"></div>\r\n    </div>\r\n    <script type=\"text/javascript\" src=\"config.js\"></script>\r\n    <script type=\"text/javascript\" src=\"template.js\"></script>\r\n</body>\r\n</html>\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/template/template.js",
    "content": "/**\r\n * Created with JetBrains PhpStorm.\r\n * User: xuheng\r\n * Date: 12-8-8\r\n * Time: 下午2:09\r\n * To change this template use File | Settings | File Templates.\r\n */\r\n(function () {\r\n    var me = editor,\r\n            preview = $G( \"preview\" ),\r\n            preitem = $G( \"preitem\" ),\r\n            tmps = templates,\r\n            currentTmp;\r\n    var initPre = function () {\r\n        var str = \"\";\r\n        for ( var i = 0, tmp; tmp = tmps[i++]; ) {\r\n            str += '<div class=\"preitem\" onclick=\"pre(' + i + ')\"><img src=\"' + \"images/\" + tmp.pre + '\" ' + (tmp.title ? \"alt=\" + tmp.title + \" title=\" + tmp.title + \"\" : \"\") + '></div>';\r\n        }\r\n        preitem.innerHTML = str;\r\n    };\r\n    var pre = function ( n ) {\r\n        var tmp = tmps[n - 1];\r\n        currentTmp = tmp;\r\n        clearItem();\r\n        domUtils.setStyles( preitem.childNodes[n - 1], {\r\n            \"background-color\":\"lemonChiffon\",\r\n            \"border\":\"#ccc 1px solid\"\r\n        } );\r\n        preview.innerHTML = tmp.preHtml ? tmp.preHtml : \"\";\r\n    };\r\n    var clearItem = function () {\r\n        var items = preitem.children;\r\n        for ( var i = 0, item; item = items[i++]; ) {\r\n            domUtils.setStyles( item, {\r\n                \"background-color\":\"\",\r\n                \"border\":\"white 1px solid\"\r\n            } );\r\n        }\r\n    };\r\n    dialog.onok = function () {\r\n        if ( !$G( \"issave\" ).checked ){\r\n            me.execCommand( \"cleardoc\" );\r\n        }\r\n        var obj = {\r\n            html:currentTmp && currentTmp.html\r\n        };\r\n        me.execCommand( \"template\", obj );\r\n    };\r\n    initPre();\r\n    window.pre = pre;\r\n    pre(2)\r\n\r\n})();"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/video/video.css",
    "content": "@charset \"utf-8\";\r\n.wrapper{ width: 570px;_width:575px;margin: 10px auto; zoom:1;position: relative}\r\n.tabbody{height: 335px;}\r\n.tabbody .panel {\r\n    position: absolute;\r\n    width: 0;\r\n    height: 0;\r\n    background: #fff;\r\n    overflow: hidden;\r\n    display: none;\r\n}\r\n.tabbody .panel.focus {\r\n    width: 100%;\r\n    height: 335px;\r\n    display: block;\r\n}\r\n\r\n.tabbody .panel table td{vertical-align: middle;}\r\n#videoUrl {\r\n    width: 490px;\r\n    height: 21px;\r\n    line-height: 21px;\r\n    margin: 8px 5px;\r\n    background: #FFF;\r\n    border: 1px solid #d7d7d7;\r\n}\r\n#videoSearchTxt{margin-left:15px;background: #FFF;width:200px;height:21px;line-height:21px;border: 1px solid #d7d7d7;}\r\n#searchList{width: 570px;overflow: auto;zoom:1;height: 270px;}\r\n#searchList div{float: left;width: 120px;height: 135px;margin: 5px 15px;}\r\n#searchList img{margin: 2px 8px;cursor: pointer;border: 2px solid #fff} /*不用缩略图*/\r\n#searchList p{margin-left: 10px;}\r\n#videoType{\r\n    width: 65px;\r\n    height: 23px;\r\n    line-height: 22px;\r\n    border: 1px solid #d7d7d7;\r\n}\r\n#videoSearchBtn,#videoSearchReset{\r\n    /*width: 80px;*/\r\n    height: 25px;\r\n    line-height: 25px;\r\n    background: #eee;\r\n    border: 1px solid #d7d7d7;\r\n    cursor: pointer;\r\n    padding: 0 5px;\r\n}\r\n\r\n\r\n\r\n#preview{position: relative;width: 420px;padding:0;overflow: hidden; margin-left: 10px; _margin-left:5px; height: 280px;background-color: #ddd;float: left}\r\n#preview .previewMsg {position:absolute;top:0;margin:0;padding:0;height:280px;width:100%;background-color: #666;}\r\n#preview .previewMsg span{display:block;margin: 125px auto 0 auto;text-align:center;font-size:18px;color:#fff;}\r\n#preview .previewVideo {position:absolute;top:0;margin:0;padding:0;height:280px;width:100%;}\r\n.edui-video-wrapper fieldset{\r\n    border: 1px solid #ddd;\r\n    padding-left: 5px;\r\n    margin-bottom: 20px;\r\n    padding-bottom: 5px;\r\n    width: 115px;\r\n}\r\n\r\n#videoInfo {width: 120px;float: left;margin-left: 10px;_margin-left:7px;}\r\nfieldset{\r\n    border: 1px solid #ddd;\r\n    padding-left: 5px;\r\n    margin-bottom: 20px;\r\n    padding-bottom: 5px;\r\n    width: 115px;\r\n}\r\nfieldset legend{font-weight: bold;}\r\nfieldset p{line-height: 30px;}\r\nfieldset input.txt{\r\n    width: 65px;\r\n    height: 21px;\r\n    line-height: 21px;\r\n    margin: 8px 5px;\r\n    background: #FFF;\r\n    border: 1px solid #d7d7d7;\r\n}\r\nlabel.url{font-weight: bold;margin-left: 5px;color: #06c;}\r\n#videoFloat div{cursor:pointer;opacity: 0.5;filter: alpha(opacity = 50);margin:9px;_margin:5px;width:38px;height:36px;float:left;}\r\n#videoFloat .focus{opacity: 1;filter: alpha(opacity = 100)}\r\nspan.view{display: inline-block;width: 30px;float: right;cursor: pointer;color: blue}\r\n\r\n\r\n\r\n\r\n/* upload video */\r\n.tabbody #upload.panel {\r\n    width: 0;\r\n    height: 0;\r\n    overflow: hidden;\r\n    position: absolute !important;\r\n    clip: rect(1px, 1px, 1px, 1px);\r\n    background: #fff;\r\n    display: block;\r\n}\r\n.tabbody #upload.panel.focus {\r\n    width: 100%;\r\n    height: 335px;\r\n    display: block;\r\n    clip: auto;\r\n}\r\n#upload_alignment div{cursor:pointer;opacity: 0.5;filter: alpha(opacity = 50);margin:9px;_margin:5px;width:38px;height:36px;float:left;}\r\n#upload_alignment .focus{opacity: 1;filter: alpha(opacity = 100)}\r\n#upload_left { width:427px; float:left; }\r\n#upload_left .controller { height: 30px; clear: both; }\r\n#uploadVideoInfo{margin-top:10px;float:right;padding-right:8px;}\r\n\r\n#upload .queueList {\r\n    margin: 0;\r\n}\r\n\r\n#upload p {\r\n    margin: 0;\r\n}\r\n\r\n.element-invisible {\r\n    width: 0 !important;\r\n    height: 0 !important;\r\n    border: 0;\r\n    padding: 0;\r\n    margin: 0;\r\n    overflow: hidden;\r\n    position: absolute !important;\r\n    clip: rect(1px, 1px, 1px, 1px);\r\n}\r\n\r\n#upload .placeholder {\r\n    margin: 10px;\r\n    margin-right:0;\r\n    border: 2px dashed #e6e6e6;\r\n    *border: 0px dashed #e6e6e6;\r\n    height: 161px;\r\n    padding-top: 150px;\r\n    text-align: center;\r\n    width: 97%;\r\n    float: left;\r\n    background: url(./images/image.png) center 70px no-repeat;\r\n    color: #cccccc;\r\n    font-size: 18px;\r\n    position: relative;\r\n    top:0;\r\n    *margin-left: 0;\r\n    *left: 10px;\r\n}\r\n\r\n#upload .placeholder .webuploader-pick {\r\n    font-size: 18px;\r\n    background: #00b7ee;\r\n    border-radius: 3px;\r\n    line-height: 44px;\r\n    padding: 0 30px;\r\n    *width: 120px;\r\n    color: #fff;\r\n    display: inline-block;\r\n    margin: 0 auto 20px auto;\r\n    cursor: pointer;\r\n    box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);\r\n}\r\n\r\n#upload .placeholder .webuploader-pick-hover {\r\n    background: #00a2d4;\r\n}\r\n\r\n\r\n#filePickerContainer {\r\n    text-align: center;\r\n}\r\n\r\n#upload .placeholder .flashTip {\r\n    color: #666666;\r\n    font-size: 12px;\r\n    position: absolute;\r\n    width: 100%;\r\n    text-align: center;\r\n    bottom: 20px;\r\n}\r\n\r\n#upload .placeholder .flashTip a {\r\n    color: #0785d1;\r\n    text-decoration: none;\r\n}\r\n\r\n#upload .placeholder .flashTip a:hover {\r\n    text-decoration: underline;\r\n}\r\n\r\n#upload .placeholder.webuploader-dnd-over {\r\n    border-color: #999999;\r\n}\r\n\r\n#upload .filelist {\r\n    list-style: none;\r\n    margin: 0;\r\n    padding: 0;\r\n    overflow-x: hidden;\r\n    overflow-y: auto;\r\n    position: relative;\r\n    height: 285px;\r\n}\r\n\r\n#upload .filelist:after {\r\n    content: '';\r\n    display: block;\r\n    width: 0;\r\n    height: 0;\r\n    overflow: hidden;\r\n    clear: both;\r\n}\r\n\r\n#upload .filelist li {\r\n    width: 113px;\r\n    height: 113px;\r\n    background: url(./images/bg.png);\r\n    text-align: center;\r\n    margin: 15px 0 0 20px;\r\n    *margin: 15px 0 0 15px;\r\n    position: relative;\r\n    display: block;\r\n    float: left;\r\n    overflow: hidden;\r\n    font-size: 12px;\r\n}\r\n\r\n#upload .filelist li p.log {\r\n    position: relative;\r\n    top: -45px;\r\n}\r\n\r\n#upload .filelist li p.title {\r\n    position: absolute;\r\n    top: 0;\r\n    left: 0;\r\n    width: 100%;\r\n    overflow: hidden;\r\n    white-space: nowrap;\r\n    text-overflow: ellipsis;\r\n    top: 5px;\r\n    text-indent: 5px;\r\n    text-align: left;\r\n}\r\n\r\n#upload .filelist li p.progress {\r\n    position: absolute;\r\n    width: 100%;\r\n    bottom: 0;\r\n    left: 0;\r\n    height: 8px;\r\n    overflow: hidden;\r\n    z-index: 50;\r\n    margin: 0;\r\n    border-radius: 0;\r\n    background: none;\r\n    -webkit-box-shadow: 0 0 0;\r\n}\r\n\r\n#upload .filelist li p.progress span {\r\n    display: none;\r\n    overflow: hidden;\r\n    width: 0;\r\n    height: 100%;\r\n    background: #1483d8 url(./images/progress.png) repeat-x;\r\n\r\n    -webit-transition: width 200ms linear;\r\n    -moz-transition: width 200ms linear;\r\n    -o-transition: width 200ms linear;\r\n    -ms-transition: width 200ms linear;\r\n    transition: width 200ms linear;\r\n\r\n    -webkit-animation: progressmove 2s linear infinite;\r\n    -moz-animation: progressmove 2s linear infinite;\r\n    -o-animation: progressmove 2s linear infinite;\r\n    -ms-animation: progressmove 2s linear infinite;\r\n    animation: progressmove 2s linear infinite;\r\n\r\n    -webkit-transform: translateZ(0);\r\n}\r\n\r\n@-webkit-keyframes progressmove {\r\n    0% {\r\n        background-position: 0 0;\r\n    }\r\n    100% {\r\n        background-position: 17px 0;\r\n    }\r\n}\r\n\r\n@-moz-keyframes progressmove {\r\n    0% {\r\n        background-position: 0 0;\r\n    }\r\n    100% {\r\n        background-position: 17px 0;\r\n    }\r\n}\r\n\r\n@keyframes progressmove {\r\n    0% {\r\n        background-position: 0 0;\r\n    }\r\n    100% {\r\n        background-position: 17px 0;\r\n    }\r\n}\r\n\r\n#upload .filelist li p.imgWrap {\r\n    position: relative;\r\n    z-index: 2;\r\n    line-height: 113px;\r\n    vertical-align: middle;\r\n    overflow: hidden;\r\n    width: 113px;\r\n    height: 113px;\r\n\r\n    -webkit-transform-origin: 50% 50%;\r\n    -moz-transform-origin: 50% 50%;\r\n    -o-transform-origin: 50% 50%;\r\n    -ms-transform-origin: 50% 50%;\r\n    transform-origin: 50% 50%;\r\n\r\n    -webit-transition: 200ms ease-out;\r\n    -moz-transition: 200ms ease-out;\r\n    -o-transition: 200ms ease-out;\r\n    -ms-transition: 200ms ease-out;\r\n    transition: 200ms ease-out;\r\n}\r\n#upload .filelist li p.imgWrap.notimage {\r\n    margin-top: 0;\r\n    width: 111px;\r\n    height: 111px;\r\n    border: 1px #eeeeee solid;\r\n}\r\n#upload .filelist li p.imgWrap.notimage i.file-preview {\r\n    margin-top: 15px;\r\n}\r\n\r\n#upload .filelist li img {\r\n    width: 100%;\r\n}\r\n\r\n#upload .filelist li p.error {\r\n    background: #f43838;\r\n    color: #fff;\r\n    position: absolute;\r\n    bottom: 0;\r\n    left: 0;\r\n    height: 28px;\r\n    line-height: 28px;\r\n    width: 100%;\r\n    z-index: 100;\r\n    display:none;\r\n}\r\n\r\n#upload .filelist li .success {\r\n    display: block;\r\n    position: absolute;\r\n    left: 0;\r\n    bottom: 0;\r\n    height: 40px;\r\n    width: 100%;\r\n    z-index: 200;\r\n    background: url(./images/success.png) no-repeat right bottom;\r\n    background-image: url(./images/success.gif) \\9;\r\n}\r\n\r\n#upload .filelist li.filePickerBlock {\r\n    width: 113px;\r\n    height: 113px;\r\n    background: url(./images/image.png) no-repeat center 12px;\r\n    border: 1px solid #eeeeee;\r\n    border-radius: 0;\r\n}\r\n#upload .filelist li.filePickerBlock div.webuploader-pick  {\r\n    width: 100%;\r\n    height: 100%;\r\n    margin: 0;\r\n    padding: 0;\r\n    opacity: 0;\r\n    background: none;\r\n    font-size: 0;\r\n}\r\n\r\n#upload .filelist div.file-panel {\r\n    position: absolute;\r\n    height: 0;\r\n    filter: progid:DXImageTransform.Microsoft.gradient(GradientType=0, startColorstr='#80000000', endColorstr='#80000000') \\0;\r\n    background: rgba(0, 0, 0, 0.5);\r\n    width: 100%;\r\n    top: 0;\r\n    left: 0;\r\n    overflow: hidden;\r\n    z-index: 300;\r\n}\r\n\r\n#upload .filelist div.file-panel span {\r\n    width: 24px;\r\n    height: 24px;\r\n    display: inline;\r\n    float: right;\r\n    text-indent: -9999px;\r\n    overflow: hidden;\r\n    background: url(./images/icons.png) no-repeat;\r\n    background: url(./images/icons.gif) no-repeat \\9;\r\n    margin: 5px 1px 1px;\r\n    cursor: pointer;\r\n    -webkit-tap-highlight-color: rgba(0,0,0,0);\r\n    -webkit-user-select: none;\r\n    -moz-user-select: none;\r\n    -ms-user-select: none;\r\n    user-select: none;\r\n}\r\n\r\n#upload .filelist div.file-panel span.rotateLeft {\r\n    display:none;\r\n    background-position: 0 -24px;\r\n}\r\n\r\n#upload .filelist div.file-panel span.rotateLeft:hover {\r\n    background-position: 0 0;\r\n}\r\n\r\n#upload .filelist div.file-panel span.rotateRight {\r\n    display:none;\r\n    background-position: -24px -24px;\r\n}\r\n\r\n#upload .filelist div.file-panel span.rotateRight:hover {\r\n    background-position: -24px 0;\r\n}\r\n\r\n#upload .filelist div.file-panel span.cancel {\r\n    background-position: -48px -24px;\r\n}\r\n\r\n#upload .filelist div.file-panel span.cancel:hover {\r\n    background-position: -48px 0;\r\n}\r\n\r\n#upload .statusBar {\r\n    height: 45px;\r\n    border-bottom: 1px solid #dadada;\r\n    margin: 0 10px;\r\n    padding: 0;\r\n    line-height: 45px;\r\n    vertical-align: middle;\r\n    position: relative;\r\n}\r\n\r\n#upload .statusBar .progress {\r\n    border: 1px solid #1483d8;\r\n    width: 198px;\r\n    background: #fff;\r\n    height: 18px;\r\n    position: absolute;\r\n    top: 12px;\r\n    display: none;\r\n    text-align: center;\r\n    line-height: 18px;\r\n    color: #6dbfff;\r\n    margin: 0 10px 0 0;\r\n}\r\n#upload .statusBar .progress span.percentage {\r\n    width: 0;\r\n    height: 100%;\r\n    left: 0;\r\n    top: 0;\r\n    background: #1483d8;\r\n    position: absolute;\r\n}\r\n#upload .statusBar .progress span.text {\r\n    position: relative;\r\n    z-index: 10;\r\n}\r\n\r\n#upload .statusBar .info {\r\n    display: inline-block;\r\n    font-size: 14px;\r\n    color: #666666;\r\n}\r\n\r\n#upload .statusBar .btns {\r\n    position: absolute;\r\n    top: 7px;\r\n    right: 0;\r\n    line-height: 30px;\r\n}\r\n\r\n#filePickerBtn {\r\n    display: inline-block;\r\n    float: left;\r\n}\r\n#upload .statusBar .btns .webuploader-pick,\r\n#upload .statusBar .btns .uploadBtn,\r\n#upload .statusBar .btns .uploadBtn.state-uploading,\r\n#upload .statusBar .btns .uploadBtn.state-paused {\r\n    background: #ffffff;\r\n    border: 1px solid #cfcfcf;\r\n    color: #565656;\r\n    padding: 0 18px;\r\n    display: inline-block;\r\n    border-radius: 3px;\r\n    margin-left: 10px;\r\n    cursor: pointer;\r\n    font-size: 14px;\r\n    float: left;\r\n    -webkit-user-select: none;\r\n    -moz-user-select: none;\r\n    -ms-user-select: none;\r\n    user-select: none;\r\n}\r\n#upload .statusBar .btns .webuploader-pick-hover,\r\n#upload .statusBar .btns .uploadBtn:hover,\r\n#upload .statusBar .btns .uploadBtn.state-uploading:hover,\r\n#upload .statusBar .btns .uploadBtn.state-paused:hover {\r\n    background: #f0f0f0;\r\n}\r\n\r\n#upload .statusBar .btns .uploadBtn,\r\n#upload .statusBar .btns .uploadBtn.state-paused{\r\n    background: #00b7ee;\r\n    color: #fff;\r\n    border-color: transparent;\r\n}\r\n#upload .statusBar .btns .uploadBtn:hover,\r\n#upload .statusBar .btns .uploadBtn.state-paused:hover{\r\n    background: #00a2d4;\r\n}\r\n\r\n#upload .statusBar .btns .uploadBtn.disabled {\r\n    pointer-events: none;\r\n    filter:alpha(opacity=60);\r\n    -moz-opacity:0.6;\r\n    -khtml-opacity: 0.6;\r\n    opacity: 0.6;\r\n}\r\n\r\n\r\n/* 在线文件的文件预览图标 */\r\ni.file-preview {\r\n    display: block;\r\n    margin: 10px auto;\r\n    width: 70px;\r\n    height: 70px;\r\n    background-image: url(\"./images/file-icons.png\");\r\n    background-image: url(\"./images/file-icons.gif\") \\9;\r\n    background-position: -140px center;\r\n    background-repeat: no-repeat;\r\n}\r\ni.file-preview.file-type-dir{\r\n    background-position: 0 center;\r\n}\r\ni.file-preview.file-type-file{\r\n    background-position: -140px center;\r\n}\r\ni.file-preview.file-type-filelist{\r\n    background-position: -210px center;\r\n}\r\ni.file-preview.file-type-zip,\r\ni.file-preview.file-type-rar,\r\ni.file-preview.file-type-7z,\r\ni.file-preview.file-type-tar,\r\ni.file-preview.file-type-gz,\r\ni.file-preview.file-type-bz2{\r\n    background-position: -280px center;\r\n}\r\ni.file-preview.file-type-xls,\r\ni.file-preview.file-type-xlsx{\r\n    background-position: -350px center;\r\n}\r\ni.file-preview.file-type-doc,\r\ni.file-preview.file-type-docx{\r\n    background-position: -420px center;\r\n}\r\ni.file-preview.file-type-ppt,\r\ni.file-preview.file-type-pptx{\r\n    background-position: -490px center;\r\n}\r\ni.file-preview.file-type-vsd{\r\n    background-position: -560px center;\r\n}\r\ni.file-preview.file-type-pdf{\r\n    background-position: -630px center;\r\n}\r\ni.file-preview.file-type-txt,\r\ni.file-preview.file-type-md,\r\ni.file-preview.file-type-json,\r\ni.file-preview.file-type-htm,\r\ni.file-preview.file-type-xml,\r\ni.file-preview.file-type-html,\r\ni.file-preview.file-type-js,\r\ni.file-preview.file-type-css,\r\ni.file-preview.file-type-php,\r\ni.file-preview.file-type-jsp,\r\ni.file-preview.file-type-asp{\r\n    background-position: -700px center;\r\n}\r\ni.file-preview.file-type-apk{\r\n    background-position: -770px center;\r\n}\r\ni.file-preview.file-type-exe{\r\n    background-position: -840px center;\r\n}\r\ni.file-preview.file-type-ipa{\r\n    background-position: -910px center;\r\n}\r\ni.file-preview.file-type-mp4,\r\ni.file-preview.file-type-swf,\r\ni.file-preview.file-type-mkv,\r\ni.file-preview.file-type-avi,\r\ni.file-preview.file-type-flv,\r\ni.file-preview.file-type-mov,\r\ni.file-preview.file-type-mpg,\r\ni.file-preview.file-type-mpeg,\r\ni.file-preview.file-type-ogv,\r\ni.file-preview.file-type-webm,\r\ni.file-preview.file-type-rm,\r\ni.file-preview.file-type-rmvb{\r\n    background-position: -980px center;\r\n}\r\ni.file-preview.file-type-ogg,\r\ni.file-preview.file-type-wav,\r\ni.file-preview.file-type-wmv,\r\ni.file-preview.file-type-mid,\r\ni.file-preview.file-type-mp3{\r\n    background-position: -1050px center;\r\n}\r\ni.file-preview.file-type-jpg,\r\ni.file-preview.file-type-jpeg,\r\ni.file-preview.file-type-gif,\r\ni.file-preview.file-type-bmp,\r\ni.file-preview.file-type-png,\r\ni.file-preview.file-type-psd{\r\n    background-position: -140px center;\r\n}"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/video/video.html",
    "content": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\r\n        \"http://www.w3.org/TR/html4/loose.dtd\">\r\n<html>\r\n<head>\r\n    <title></title>\r\n    <meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"/>\r\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n    <link rel=\"stylesheet\" type=\"text/css\" href=\"video.css\" />\r\n</head>\r\n<body>\r\n<div class=\"wrapper\">\r\n    <div id=\"videoTab\">\r\n        <div id=\"tabHeads\" class=\"tabhead\">\r\n            <span tabSrc=\"video\" class=\"focus\" data-content-id=\"video\"><var id=\"lang_tab_insertV\"></var></span>\r\n            <span tabSrc=\"upload\" data-content-id=\"upload\"><var id=\"lang_tab_uploadV\"></var></span>\r\n        </div>\r\n        <div id=\"tabBodys\" class=\"tabbody\">\r\n            <div id=\"video\" class=\"panel focus\">\r\n               <table><tr><td><label for=\"videoUrl\" class=\"url\"><var id=\"lang_video_url\"></var></label></td><td><input id=\"videoUrl\" type=\"text\"></td></tr></table>\r\n               <div id=\"preview\"></div>\r\n               <div id=\"videoInfo\">\r\n                   <fieldset>\r\n                       <legend><var id=\"lang_video_size\"></var></legend>\r\n                       <table>\r\n                           <tr><td><label for=\"videoWidth\"><var id=\"lang_videoW\"></var></label></td><td><input class=\"txt\" id=\"videoWidth\" type=\"text\"/></td></tr>\r\n                           <tr><td><label for=\"videoHeight\"><var id=\"lang_videoH\"></var></label></td><td><input class=\"txt\" id=\"videoHeight\" type=\"text\"/></td></tr>\r\n                       </table>\r\n                   </fieldset>\r\n                   <fieldset>\r\n                      <legend><var id=\"lang_alignment\"></var></legend>\r\n                      <div id=\"videoFloat\"></div>\r\n                  </fieldset>\r\n               </div>\r\n            </div>\r\n            <div id=\"upload\" class=\"panel\">\r\n                <div id=\"upload_left\">\r\n                    <div id=\"queueList\" class=\"queueList\">\r\n                        <div class=\"statusBar element-invisible\">\r\n                            <div class=\"progress\">\r\n                                <span class=\"text\">0%</span>\r\n                                <span class=\"percentage\"></span>\r\n                            </div><div class=\"info\"></div>\r\n                            <div class=\"btns\">\r\n                                <div id=\"filePickerBtn\"></div>\r\n                                <div class=\"uploadBtn\"><var id=\"lang_start_upload\"></var></div>\r\n                            </div>\r\n                        </div>\r\n                        <div id=\"dndArea\" class=\"placeholder\">\r\n                            <div class=\"filePickerContainer\">\r\n                                <div id=\"filePickerReady\"></div>\r\n                            </div>\r\n                        </div>\r\n                        <ul class=\"filelist element-invisible\">\r\n                            <li id=\"filePickerBlock\" class=\"filePickerBlock\"></li>\r\n                        </ul>\r\n                    </div>\r\n                </div>\r\n                <div id=\"uploadVideoInfo\">\r\n                    <fieldset>\r\n                        <legend><var id=\"lang_upload_size\"></var></legend>\r\n                        <table>\r\n                            <tr><td><label><var id=\"lang_upload_width\"></var></label></td><td><input class=\"txt\" id=\"upload_width\" type=\"text\"/></td></tr>\r\n                            <tr><td><label><var id=\"lang_upload_height\"></var></label></td><td><input class=\"txt\" id=\"upload_height\" type=\"text\"/></td></tr>\r\n                        </table>\r\n                    </fieldset>\r\n                    <fieldset>\r\n                        <legend><var id=\"lang_upload_alignment\"></var></legend>\r\n                        <div id=\"upload_alignment\"></div>\r\n                    </fieldset>\r\n                </div>\r\n            </div>\r\n        </div>\r\n    </div>\r\n</div>\r\n\r\n<!-- jquery -->\r\n<script type=\"text/javascript\" src=\"../../third-party/jquery-1.10.2.min.js\"></script>\r\n\r\n<!-- webuploader -->\r\n<script type=\"text/javascript\" src=\"../../third-party/webuploader/webuploader.min.js\"></script>\r\n<link rel=\"stylesheet\" type=\"text/css\" href=\"../../third-party/webuploader/webuploader.css\">\r\n\r\n<!-- video -->\r\n<script type=\"text/javascript\" src=\"video.js\"></script>\r\n</body>\r\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/video/video.js",
    "content": "/**\r\n * Created by JetBrains PhpStorm.\r\n * User: taoqili\r\n * Date: 12-2-20\r\n * Time: 上午11:19\r\n * To change this template use File | Settings | File Templates.\r\n */\r\n\r\n(function(){\r\n\r\n    var video = {},\r\n        uploadVideoList = [],\r\n        isModifyUploadVideo = false,\r\n        uploadFile;\r\n\r\n    window.onload = function(){\r\n        $focus($G(\"videoUrl\"));\r\n        initTabs();\r\n        initVideo();\r\n        initUpload();\r\n    };\r\n\r\n    /* 初始化tab标签 */\r\n    function initTabs(){\r\n        var tabs = $G('tabHeads').children;\r\n        for (var i = 0; i < tabs.length; i++) {\r\n            domUtils.on(tabs[i], \"click\", function (e) {\r\n                var j, bodyId, target = e.target || e.srcElement;\r\n                for (j = 0; j < tabs.length; j++) {\r\n                    bodyId = tabs[j].getAttribute('data-content-id');\r\n                    if(tabs[j] == target){\r\n                        domUtils.addClass(tabs[j], 'focus');\r\n                        domUtils.addClass($G(bodyId), 'focus');\r\n                    }else {\r\n                        domUtils.removeClasses(tabs[j], 'focus');\r\n                        domUtils.removeClasses($G(bodyId), 'focus');\r\n                    }\r\n                }\r\n            });\r\n        }\r\n    }\r\n\r\n    function initVideo(){\r\n        createAlignButton( [\"videoFloat\", \"upload_alignment\"] );\r\n        addUrlChangeListener($G(\"videoUrl\"));\r\n        addOkListener();\r\n\r\n        //编辑视频时初始化相关信息\r\n        (function(){\r\n            var img = editor.selection.getRange().getClosedNode(),url;\r\n            if(img && img.className){\r\n                var hasFakedClass = (img.className == \"edui-faked-video\"),\r\n                    hasUploadClass = img.className.indexOf(\"edui-upload-video\")!=-1;\r\n                if(hasFakedClass || hasUploadClass) {\r\n                    $G(\"videoUrl\").value = url = img.getAttribute(\"_url\");\r\n                    $G(\"videoWidth\").value = img.width;\r\n                    $G(\"videoHeight\").value = img.height;\r\n                    var align = domUtils.getComputedStyle(img,\"float\"),\r\n                        parentAlign = domUtils.getComputedStyle(img.parentNode,\"text-align\");\r\n                    updateAlignButton(parentAlign===\"center\"?\"center\":align);\r\n                }\r\n                if(hasUploadClass) {\r\n                    isModifyUploadVideo = true;\r\n                }\r\n            }\r\n            createPreviewVideo(url);\r\n        })();\r\n    }\r\n\r\n    /**\r\n     * 监听确认和取消两个按钮事件，用户执行插入或者清空正在播放的视频实例操作\r\n     */\r\n    function addOkListener(){\r\n        dialog.onok = function(){\r\n            $G(\"preview\").innerHTML = \"\";\r\n            var currentTab =  findFocus(\"tabHeads\",\"tabSrc\");\r\n            switch(currentTab){\r\n                case \"video\":\r\n                    return insertSingle();\r\n                    break;\r\n                case \"videoSearch\":\r\n                    return insertSearch(\"searchList\");\r\n                    break;\r\n                case \"upload\":\r\n                    return insertUpload();\r\n                    break;\r\n            }\r\n        };\r\n        dialog.oncancel = function(){\r\n            $G(\"preview\").innerHTML = \"\";\r\n        };\r\n    }\r\n\r\n    /**\r\n     * 依据传入的align值更新按钮信息\r\n     * @param align\r\n     */\r\n    function updateAlignButton( align ) {\r\n        var aligns = $G( \"videoFloat\" ).children;\r\n        for ( var i = 0, ci; ci = aligns[i++]; ) {\r\n            if ( ci.getAttribute( \"name\" ) == align ) {\r\n                if ( ci.className !=\"focus\" ) {\r\n                    ci.className = \"focus\";\r\n                }\r\n            } else {\r\n                if ( ci.className ==\"focus\" ) {\r\n                    ci.className = \"\";\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    /**\r\n     * 将单个视频信息插入编辑器中\r\n     */\r\n    function insertSingle(){\r\n        var width = $G(\"videoWidth\"),\r\n            height = $G(\"videoHeight\"),\r\n            url=$G('videoUrl').value,\r\n            align = findFocus(\"videoFloat\",\"name\");\r\n        if(!url) return false;\r\n        if ( !checkNum( [width, height] ) ) return false;\r\n        editor.execCommand('insertvideo', {\r\n            url: convert_url(url),\r\n            width: width.value,\r\n            height: height.value,\r\n            align: align\r\n        }, isModifyUploadVideo ? 'upload':null);\r\n    }\r\n\r\n    /**\r\n     * 将元素id下的所有代表视频的图片插入编辑器中\r\n     * @param id\r\n     */\r\n    function insertSearch(id){\r\n        var imgs = domUtils.getElementsByTagName($G(id),\"img\"),\r\n            videoObjs=[];\r\n        for(var i=0,img; img=imgs[i++];){\r\n            if(img.getAttribute(\"selected\")){\r\n                videoObjs.push({\r\n                    url:img.getAttribute(\"ue_video_url\"),\r\n                    width:420,\r\n                    height:280,\r\n                    align:\"none\"\r\n                });\r\n            }\r\n        }\r\n        editor.execCommand('insertvideo',videoObjs);\r\n    }\r\n\r\n    /**\r\n     * 找到id下具有focus类的节点并返回该节点下的某个属性\r\n     * @param id\r\n     * @param returnProperty\r\n     */\r\n    function findFocus( id, returnProperty ) {\r\n        var tabs = $G( id ).children,\r\n                property;\r\n        for ( var i = 0, ci; ci = tabs[i++]; ) {\r\n            if ( ci.className==\"focus\" ) {\r\n                property = ci.getAttribute( returnProperty );\r\n                break;\r\n            }\r\n        }\r\n        return property;\r\n    }\r\n    function convert_url(url){\r\n        if ( !url ) return '';\r\n        url = utils.trim(url)\r\n            .replace(/v\\.youku\\.com\\/v_show\\/id_([\\w\\-=]+)\\.html/i, 'player.youku.com/player.php/sid/$1/v.swf')\r\n            .replace(/(www\\.)?youtube\\.com\\/watch\\?v=([\\w\\-]+)/i, \"www.youtube.com/v/$2\")\r\n            .replace(/youtu.be\\/(\\w+)$/i, \"www.youtube.com/v/$1\")\r\n            .replace(/v\\.ku6\\.com\\/.+\\/([\\w\\.]+)\\.html.*$/i, \"player.ku6.com/refer/$1/v.swf\")\r\n            .replace(/www\\.56\\.com\\/u\\d+\\/v_([\\w\\-]+)\\.html/i, \"player.56.com/v_$1.swf\")\r\n            .replace(/www.56.com\\/w\\d+\\/play_album\\-aid\\-\\d+_vid\\-([^.]+)\\.html/i, \"player.56.com/v_$1.swf\")\r\n            .replace(/v\\.pps\\.tv\\/play_([\\w]+)\\.html.*$/i, \"player.pps.tv/player/sid/$1/v.swf\")\r\n            .replace(/www\\.letv\\.com\\/ptv\\/vplay\\/([\\d]+)\\.html.*$/i, \"i7.imgs.letv.com/player/swfPlayer.swf?id=$1&autoplay=0\")\r\n            .replace(/www\\.tudou\\.com\\/programs\\/view\\/([\\w\\-]+)\\/?/i, \"www.tudou.com/v/$1\")\r\n            .replace(/v\\.qq\\.com\\/cover\\/[\\w]+\\/[\\w]+\\/([\\w]+)\\.html/i, \"static.video.qq.com/TPout.swf?vid=$1\")\r\n            .replace(/v\\.qq\\.com\\/.+[\\?\\&]vid=([^&]+).*$/i, \"static.video.qq.com/TPout.swf?vid=$1\")\r\n            .replace(/my\\.tv\\.sohu\\.com\\/[\\w]+\\/[\\d]+\\/([\\d]+)\\.shtml.*$/i, \"share.vrs.sohu.com/my/v.swf&id=$1\");\r\n\r\n        return url;\r\n    }\r\n\r\n    /**\r\n      * 检测传入的所有input框中输入的长宽是否是正数\r\n      * @param nodes input框集合，\r\n      */\r\n     function checkNum( nodes ) {\r\n         for ( var i = 0, ci; ci = nodes[i++]; ) {\r\n             var value = ci.value;\r\n             if ( !isNumber( value ) && value) {\r\n                 alert( lang.numError );\r\n                 ci.value = \"\";\r\n                 ci.focus();\r\n                 return false;\r\n             }\r\n         }\r\n         return true;\r\n     }\r\n\r\n    /**\r\n     * 数字判断\r\n     * @param value\r\n     */\r\n    function isNumber( value ) {\r\n        return /(0|^[1-9]\\d*$)/.test( value );\r\n    }\r\n\r\n    /**\r\n      * 创建图片浮动选择按钮\r\n      * @param ids\r\n      */\r\n     function createAlignButton( ids ) {\r\n         for ( var i = 0, ci; ci = ids[i++]; ) {\r\n             var floatContainer = $G( ci ),\r\n                     nameMaps = {\"none\":lang['default'], \"left\":lang.floatLeft, \"right\":lang.floatRight, \"center\":lang.block};\r\n             for ( var j in nameMaps ) {\r\n                 var div = document.createElement( \"div\" );\r\n                 div.setAttribute( \"name\", j );\r\n                 if ( j == \"none\" ) div.className=\"focus\";\r\n                 div.style.cssText = \"background:url(images/\" + j + \"_focus.jpg);\";\r\n                 div.setAttribute( \"title\", nameMaps[j] );\r\n                 floatContainer.appendChild( div );\r\n             }\r\n             switchSelect( ci );\r\n         }\r\n     }\r\n\r\n    /**\r\n     * 选择切换\r\n     * @param selectParentId\r\n     */\r\n    function switchSelect( selectParentId ) {\r\n        var selects = $G( selectParentId ).children;\r\n        for ( var i = 0, ci; ci = selects[i++]; ) {\r\n            domUtils.on( ci, \"click\", function () {\r\n                for ( var j = 0, cj; cj = selects[j++]; ) {\r\n                    cj.className = \"\";\r\n                    cj.removeAttribute && cj.removeAttribute( \"class\" );\r\n                }\r\n                this.className = \"focus\";\r\n            } )\r\n        }\r\n    }\r\n\r\n    /**\r\n     * 监听url改变事件\r\n     * @param url\r\n     */\r\n    function addUrlChangeListener(url){\r\n        if (browser.ie) {\r\n            url.onpropertychange = function () {\r\n                createPreviewVideo( this.value );\r\n            }\r\n        } else {\r\n            url.addEventListener( \"input\", function () {\r\n                createPreviewVideo( this.value );\r\n            }, false );\r\n        }\r\n    }\r\n\r\n    /**\r\n     * 根据url生成视频预览\r\n     * @param url\r\n     */\r\n    function createPreviewVideo(url){\r\n        if ( !url )return;\r\n\r\n        var conUrl = convert_url(url);\r\n\r\n        conUrl = utils.unhtmlForUrl(conUrl);\r\n\r\n        $G(\"preview\").innerHTML = '<div class=\"previewMsg\"><span>'+lang.urlError+'</span></div>'+\r\n        '<embed class=\"previewVideo\" type=\"application/x-shockwave-flash\" pluginspage=\"http://www.macromedia.com/go/getflashplayer\"' +\r\n            ' src=\"' + conUrl + '\"' +\r\n            ' width=\"' + 420  + '\"' +\r\n            ' height=\"' + 280  + '\"' +\r\n            ' wmode=\"transparent\" play=\"true\" loop=\"false\" menu=\"false\" allowscriptaccess=\"never\" allowfullscreen=\"true\" >' +\r\n        '</embed>';\r\n    }\r\n\r\n\r\n    /* 插入上传视频 */\r\n    function insertUpload(){\r\n        var videoObjs=[],\r\n            uploadDir = editor.getOpt('videoUrlPrefix'),\r\n            width = parseInt($G('upload_width').value, 10) || 420,\r\n            height = parseInt($G('upload_height').value, 10) || 280,\r\n            align = findFocus(\"upload_alignment\",\"name\") || 'none';\r\n        for(var key in uploadVideoList) {\r\n            var file = uploadVideoList[key];\r\n            videoObjs.push({\r\n                url: uploadDir + file.url,\r\n                width:width,\r\n                height:height,\r\n                align:align\r\n            });\r\n        }\r\n\r\n        var count = uploadFile.getQueueCount();\r\n        if (count) {\r\n            $('.info', '#queueList').html('<span style=\"color:red;\">' + '还有2个未上传文件'.replace(/[\\d]/, count) + '</span>');\r\n            return false;\r\n        } else {\r\n            editor.execCommand('insertvideo', videoObjs, 'upload');\r\n        }\r\n    }\r\n\r\n    /*初始化上传标签*/\r\n    function initUpload(){\r\n        uploadFile = new UploadFile('queueList');\r\n    }\r\n\r\n\r\n    /* 上传附件 */\r\n    function UploadFile(target) {\r\n        this.$wrap = target.constructor == String ? $('#' + target) : $(target);\r\n        this.init();\r\n    }\r\n    UploadFile.prototype = {\r\n        init: function () {\r\n            this.fileList = [];\r\n            this.initContainer();\r\n            this.initUploader();\r\n        },\r\n        initContainer: function () {\r\n            this.$queue = this.$wrap.find('.filelist');\r\n        },\r\n        /* 初始化容器 */\r\n        initUploader: function () {\r\n            var _this = this,\r\n                $ = jQuery,    // just in case. Make sure it's not an other libaray.\r\n                $wrap = _this.$wrap,\r\n            // 图片容器\r\n                $queue = $wrap.find('.filelist'),\r\n            // 状态栏，包括进度和控制按钮\r\n                $statusBar = $wrap.find('.statusBar'),\r\n            // 文件总体选择信息。\r\n                $info = $statusBar.find('.info'),\r\n            // 上传按钮\r\n                $upload = $wrap.find('.uploadBtn'),\r\n            // 上传按钮\r\n                $filePickerBtn = $wrap.find('.filePickerBtn'),\r\n            // 上传按钮\r\n                $filePickerBlock = $wrap.find('.filePickerBlock'),\r\n            // 没选择文件之前的内容。\r\n                $placeHolder = $wrap.find('.placeholder'),\r\n            // 总体进度条\r\n                $progress = $statusBar.find('.progress').hide(),\r\n            // 添加的文件数量\r\n                fileCount = 0,\r\n            // 添加的文件总大小\r\n                fileSize = 0,\r\n            // 优化retina, 在retina下这个值是2\r\n                ratio = window.devicePixelRatio || 1,\r\n            // 缩略图大小\r\n                thumbnailWidth = 113 * ratio,\r\n                thumbnailHeight = 113 * ratio,\r\n            // 可能有pedding, ready, uploading, confirm, done.\r\n                state = '',\r\n            // 所有文件的进度信息，key为file id\r\n                percentages = {},\r\n                supportTransition = (function () {\r\n                    var s = document.createElement('p').style,\r\n                        r = 'transition' in s ||\r\n                            'WebkitTransition' in s ||\r\n                            'MozTransition' in s ||\r\n                            'msTransition' in s ||\r\n                            'OTransition' in s;\r\n                    s = null;\r\n                    return r;\r\n                })(),\r\n            // WebUploader实例\r\n                uploader,\r\n                actionUrl = editor.getActionUrl(editor.getOpt('videoActionName')),\r\n                fileMaxSize = editor.getOpt('videoMaxSize'),\r\n                acceptExtensions = (editor.getOpt('videoAllowFiles') || []).join('').replace(/\\./g, ',').replace(/^[,]/, '');;\r\n\r\n            if (!WebUploader.Uploader.support()) {\r\n                $('#filePickerReady').after($('<div>').html(lang.errorNotSupport)).hide();\r\n                return;\r\n            } else if (!editor.getOpt('videoActionName')) {\r\n                $('#filePickerReady').after($('<div>').html(lang.errorLoadConfig)).hide();\r\n                return;\r\n            }\r\n\r\n            uploader = _this.uploader = WebUploader.create({\r\n                pick: {\r\n                    id: '#filePickerReady',\r\n                    label: lang.uploadSelectFile\r\n                },\r\n                swf: '../../third-party/webuploader/Uploader.swf',\r\n                server: actionUrl,\r\n                fileVal: editor.getOpt('videoFieldName'),\r\n                duplicate: true,\r\n                fileSingleSizeLimit: fileMaxSize,\r\n                compress: false\r\n            });\r\n            uploader.addButton({\r\n                id: '#filePickerBlock'\r\n            });\r\n            uploader.addButton({\r\n                id: '#filePickerBtn',\r\n                label: lang.uploadAddFile\r\n            });\r\n\r\n            setState('pedding');\r\n\r\n            // 当有文件添加进来时执行，负责view的创建\r\n            function addFile(file) {\r\n                var $li = $('<li id=\"' + file.id + '\">' +\r\n                        '<p class=\"title\">' + file.name + '</p>' +\r\n                        '<p class=\"imgWrap\"></p>' +\r\n                        '<p class=\"progress\"><span></span></p>' +\r\n                        '</li>'),\r\n\r\n                    $btns = $('<div class=\"file-panel\">' +\r\n                        '<span class=\"cancel\">' + lang.uploadDelete + '</span>' +\r\n                        '<span class=\"rotateRight\">' + lang.uploadTurnRight + '</span>' +\r\n                        '<span class=\"rotateLeft\">' + lang.uploadTurnLeft + '</span></div>').appendTo($li),\r\n                    $prgress = $li.find('p.progress span'),\r\n                    $wrap = $li.find('p.imgWrap'),\r\n                    $info = $('<p class=\"error\"></p>').hide().appendTo($li),\r\n\r\n                    showError = function (code) {\r\n                        switch (code) {\r\n                            case 'exceed_size':\r\n                                text = lang.errorExceedSize;\r\n                                break;\r\n                            case 'interrupt':\r\n                                text = lang.errorInterrupt;\r\n                                break;\r\n                            case 'http':\r\n                                text = lang.errorHttp;\r\n                                break;\r\n                            case 'not_allow_type':\r\n                                text = lang.errorFileType;\r\n                                break;\r\n                            default:\r\n                                text = lang.errorUploadRetry;\r\n                                break;\r\n                        }\r\n                        $info.text(text).show();\r\n                    };\r\n\r\n                if (file.getStatus() === 'invalid') {\r\n                    showError(file.statusText);\r\n                } else {\r\n                    $wrap.text(lang.uploadPreview);\r\n                    if ('|png|jpg|jpeg|bmp|gif|'.indexOf('|'+file.ext.toLowerCase()+'|') == -1) {\r\n                        $wrap.empty().addClass('notimage').append('<i class=\"file-preview file-type-' + file.ext.toLowerCase() + '\"></i>' +\r\n                            '<span class=\"file-title\">' + file.name + '</span>');\r\n                    } else {\r\n                        if (browser.ie && browser.version <= 7) {\r\n                            $wrap.text(lang.uploadNoPreview);\r\n                        } else {\r\n                            uploader.makeThumb(file, function (error, src) {\r\n                                if (error || !src || (/^data:/.test(src) && browser.ie && browser.version <= 7)) {\r\n                                    $wrap.text(lang.uploadNoPreview);\r\n                                } else {\r\n                                    var $img = $('<img src=\"' + src + '\">');\r\n                                    $wrap.empty().append($img);\r\n                                    $img.on('error', function () {\r\n                                        $wrap.text(lang.uploadNoPreview);\r\n                                    });\r\n                                }\r\n                            }, thumbnailWidth, thumbnailHeight);\r\n                        }\r\n                    }\r\n                    percentages[ file.id ] = [ file.size, 0 ];\r\n                    file.rotation = 0;\r\n\r\n                    /* 检查文件格式 */\r\n                    if (!file.ext || acceptExtensions.indexOf(file.ext.toLowerCase()) == -1) {\r\n                        showError('not_allow_type');\r\n                        uploader.removeFile(file);\r\n                    }\r\n                }\r\n\r\n                file.on('statuschange', function (cur, prev) {\r\n                    if (prev === 'progress') {\r\n                        $prgress.hide().width(0);\r\n                    } else if (prev === 'queued') {\r\n                        $li.off('mouseenter mouseleave');\r\n                        $btns.remove();\r\n                    }\r\n                    // 成功\r\n                    if (cur === 'error' || cur === 'invalid') {\r\n                        showError(file.statusText);\r\n                        percentages[ file.id ][ 1 ] = 1;\r\n                    } else if (cur === 'interrupt') {\r\n                        showError('interrupt');\r\n                    } else if (cur === 'queued') {\r\n                        percentages[ file.id ][ 1 ] = 0;\r\n                    } else if (cur === 'progress') {\r\n                        $info.hide();\r\n                        $prgress.css('display', 'block');\r\n                    } else if (cur === 'complete') {\r\n                    }\r\n\r\n                    $li.removeClass('state-' + prev).addClass('state-' + cur);\r\n                });\r\n\r\n                $li.on('mouseenter', function () {\r\n                    $btns.stop().animate({height: 30});\r\n                });\r\n                $li.on('mouseleave', function () {\r\n                    $btns.stop().animate({height: 0});\r\n                });\r\n\r\n                $btns.on('click', 'span', function () {\r\n                    var index = $(this).index(),\r\n                        deg;\r\n\r\n                    switch (index) {\r\n                        case 0:\r\n                            uploader.removeFile(file);\r\n                            return;\r\n                        case 1:\r\n                            file.rotation += 90;\r\n                            break;\r\n                        case 2:\r\n                            file.rotation -= 90;\r\n                            break;\r\n                    }\r\n\r\n                    if (supportTransition) {\r\n                        deg = 'rotate(' + file.rotation + 'deg)';\r\n                        $wrap.css({\r\n                            '-webkit-transform': deg,\r\n                            '-mos-transform': deg,\r\n                            '-o-transform': deg,\r\n                            'transform': deg\r\n                        });\r\n                    } else {\r\n                        $wrap.css('filter', 'progid:DXImageTransform.Microsoft.BasicImage(rotation=' + (~~((file.rotation / 90) % 4 + 4) % 4) + ')');\r\n                    }\r\n\r\n                });\r\n\r\n                $li.insertBefore($filePickerBlock);\r\n            }\r\n\r\n            // 负责view的销毁\r\n            function removeFile(file) {\r\n                var $li = $('#' + file.id);\r\n                delete percentages[ file.id ];\r\n                updateTotalProgress();\r\n                $li.off().find('.file-panel').off().end().remove();\r\n            }\r\n\r\n            function updateTotalProgress() {\r\n                var loaded = 0,\r\n                    total = 0,\r\n                    spans = $progress.children(),\r\n                    percent;\r\n\r\n                $.each(percentages, function (k, v) {\r\n                    total += v[ 0 ];\r\n                    loaded += v[ 0 ] * v[ 1 ];\r\n                });\r\n\r\n                percent = total ? loaded / total : 0;\r\n\r\n                spans.eq(0).text(Math.round(percent * 100) + '%');\r\n                spans.eq(1).css('width', Math.round(percent * 100) + '%');\r\n                updateStatus();\r\n            }\r\n\r\n            function setState(val, files) {\r\n\r\n                if (val != state) {\r\n\r\n                    var stats = uploader.getStats();\r\n\r\n                    $upload.removeClass('state-' + state);\r\n                    $upload.addClass('state-' + val);\r\n\r\n                    switch (val) {\r\n\r\n                        /* 未选择文件 */\r\n                        case 'pedding':\r\n                            $queue.addClass('element-invisible');\r\n                            $statusBar.addClass('element-invisible');\r\n                            $placeHolder.removeClass('element-invisible');\r\n                            $progress.hide(); $info.hide();\r\n                            uploader.refresh();\r\n                            break;\r\n\r\n                        /* 可以开始上传 */\r\n                        case 'ready':\r\n                            $placeHolder.addClass('element-invisible');\r\n                            $queue.removeClass('element-invisible');\r\n                            $statusBar.removeClass('element-invisible');\r\n                            $progress.hide(); $info.show();\r\n                            $upload.text(lang.uploadStart);\r\n                            uploader.refresh();\r\n                            break;\r\n\r\n                        /* 上传中 */\r\n                        case 'uploading':\r\n                            $progress.show(); $info.hide();\r\n                            $upload.text(lang.uploadPause);\r\n                            break;\r\n\r\n                        /* 暂停上传 */\r\n                        case 'paused':\r\n                            $progress.show(); $info.hide();\r\n                            $upload.text(lang.uploadContinue);\r\n                            break;\r\n\r\n                        case 'confirm':\r\n                            $progress.show(); $info.hide();\r\n                            $upload.text(lang.uploadStart);\r\n\r\n                            stats = uploader.getStats();\r\n                            if (stats.successNum && !stats.uploadFailNum) {\r\n                                setState('finish');\r\n                                return;\r\n                            }\r\n                            break;\r\n\r\n                        case 'finish':\r\n                            $progress.hide(); $info.show();\r\n                            if (stats.uploadFailNum) {\r\n                                $upload.text(lang.uploadRetry);\r\n                            } else {\r\n                                $upload.text(lang.uploadStart);\r\n                            }\r\n                            break;\r\n                    }\r\n\r\n                    state = val;\r\n                    updateStatus();\r\n\r\n                }\r\n\r\n                if (!_this.getQueueCount()) {\r\n                    $upload.addClass('disabled')\r\n                } else {\r\n                    $upload.removeClass('disabled')\r\n                }\r\n\r\n            }\r\n\r\n            function updateStatus() {\r\n                var text = '', stats;\r\n\r\n                if (state === 'ready') {\r\n                    text = lang.updateStatusReady.replace('_', fileCount).replace('_KB', WebUploader.formatSize(fileSize));\r\n                } else if (state === 'confirm') {\r\n                    stats = uploader.getStats();\r\n                    if (stats.uploadFailNum) {\r\n                        text = lang.updateStatusConfirm.replace('_', stats.successNum).replace('_', stats.successNum);\r\n                    }\r\n                } else {\r\n                    stats = uploader.getStats();\r\n                    text = lang.updateStatusFinish.replace('_', fileCount).\r\n                        replace('_KB', WebUploader.formatSize(fileSize)).\r\n                        replace('_', stats.successNum);\r\n\r\n                    if (stats.uploadFailNum) {\r\n                        text += lang.updateStatusError.replace('_', stats.uploadFailNum);\r\n                    }\r\n                }\r\n\r\n                $info.html(text);\r\n            }\r\n\r\n            uploader.on('fileQueued', function (file) {\r\n                fileCount++;\r\n                fileSize += file.size;\r\n\r\n                if (fileCount === 1) {\r\n                    $placeHolder.addClass('element-invisible');\r\n                    $statusBar.show();\r\n                }\r\n\r\n                addFile(file);\r\n            });\r\n\r\n            uploader.on('fileDequeued', function (file) {\r\n                fileCount--;\r\n                fileSize -= file.size;\r\n\r\n                removeFile(file);\r\n                updateTotalProgress();\r\n            });\r\n\r\n            uploader.on('filesQueued', function (file) {\r\n                if (!uploader.isInProgress() && (state == 'pedding' || state == 'finish' || state == 'confirm' || state == 'ready')) {\r\n                    setState('ready');\r\n                }\r\n                updateTotalProgress();\r\n            });\r\n\r\n            uploader.on('all', function (type, files) {\r\n                switch (type) {\r\n                    case 'uploadFinished':\r\n                        setState('confirm', files);\r\n                        break;\r\n                    case 'startUpload':\r\n                        /* 添加额外的GET参数 */\r\n                        var params = utils.serializeParam(editor.queryCommandValue('serverparam')) || '',\r\n                            url = utils.formatUrl(actionUrl + (actionUrl.indexOf('?') == -1 ? '?':'&') + 'encode=utf-8&' + params);\r\n                        uploader.option('server', url);\r\n                        setState('uploading', files);\r\n                        break;\r\n                    case 'stopUpload':\r\n                        setState('paused', files);\r\n                        break;\r\n                }\r\n            });\r\n\r\n            uploader.on('uploadBeforeSend', function (file, data, header) {\r\n                //这里可以通过data对象添加POST参数\r\n                header['X_Requested_With'] = 'XMLHttpRequest';\r\n                // HaoChuan9421\r\n                if(editor.options.headers && Object.prototype.toString.apply(editor.options.headers) === \"[object Object]\"){\r\n                    for(var key in editor.options.headers){\r\n                        header[key] = editor.options.headers[key]\r\n                    }\r\n                }\r\n            });\r\n\r\n            uploader.on('uploadProgress', function (file, percentage) {\r\n                var $li = $('#' + file.id),\r\n                    $percent = $li.find('.progress span');\r\n\r\n                $percent.css('width', percentage * 100 + '%');\r\n                percentages[ file.id ][ 1 ] = percentage;\r\n                updateTotalProgress();\r\n            });\r\n\r\n            uploader.on('uploadSuccess', function (file, ret) {\r\n                var $file = $('#' + file.id);\r\n                try {\r\n                    var responseText = (ret._raw || ret),\r\n                        json = utils.str2json(responseText);\r\n                    if (json.state == 'SUCCESS') {\r\n                        uploadVideoList.push({\r\n                            'url': json.url,\r\n                            'type': json.type,\r\n                            'original':json.original\r\n                        });\r\n                        $file.append('<span class=\"success\"></span>');\r\n                    } else {\r\n                        $file.find('.error').text(json.state).show();\r\n                    }\r\n                } catch (e) {\r\n                    $file.find('.error').text(lang.errorServerUpload).show();\r\n                }\r\n            });\r\n\r\n            uploader.on('uploadError', function (file, code) {\r\n            });\r\n            uploader.on('error', function (code, file) {\r\n                if (code == 'Q_TYPE_DENIED' || code == 'F_EXCEED_SIZE') {\r\n                    addFile(file);\r\n                }\r\n            });\r\n            uploader.on('uploadComplete', function (file, ret) {\r\n            });\r\n\r\n            $upload.on('click', function () {\r\n                if ($(this).hasClass('disabled')) {\r\n                    return false;\r\n                }\r\n\r\n                if (state === 'ready') {\r\n                    uploader.upload();\r\n                } else if (state === 'paused') {\r\n                    uploader.upload();\r\n                } else if (state === 'uploading') {\r\n                    uploader.stop();\r\n                }\r\n            });\r\n\r\n            $upload.addClass('state-' + state);\r\n            updateTotalProgress();\r\n        },\r\n        getQueueCount: function () {\r\n            var file, i, status, readyFile = 0, files = this.uploader.getFiles();\r\n            for (i = 0; file = files[i++]; ) {\r\n                status = file.getStatus();\r\n                if (status == 'queued' || status == 'uploading' || status == 'progress') readyFile++;\r\n            }\r\n            return readyFile;\r\n        },\r\n        refresh: function(){\r\n            this.uploader.refresh();\r\n        }\r\n    };\r\n\r\n})();\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/webapp/webapp.html",
    "content": "<!DOCTYPE>\r\n<html>\r\n<head>\r\n    <title></title>\r\n    <meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"/>\r\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n    <style type=\"text/css\">\r\n        .wrapper{width: 540px; margin: 10px auto;}\r\n        #appShow {border: 1px solid #ddd;}\r\n        .errorMsg{font-size: 13px;margin: 10px;color: #dd0000}\r\n    </style>\r\n</head>\r\n<body>\r\n    <div class=\"wrapper\">\r\n        <div id=\"appShow\"></div>\r\n    </div>\r\n    <script type=\"text/javascript\">\r\n        //此处配置您在百度上申请到的appkey。\r\n        var apikey = editor.options.webAppKey;\r\n        if ( apikey && apikey.length == 24 ) {\r\n            var searchConfig = {\r\n                container:'appShow', //容器ID\r\n                tips:\"\", //该值用于自动清空\r\n                search:1, //是否显示搜索框\r\n                ps:12, //每页显示的条数\r\n                suggest:1, //是否开启搜索自动完成\r\n                limit:0, //搜索结果显示条数，0表示无限制\r\n                searchNow:0, //是否在初始化完成时立即搜索\r\n                apikey:apikey, //每人得\r\n                pager:1,\r\n                cid:7134562,\r\n                outputHTML:1\r\n            },baiduApp;\r\n\r\n            function clickCallback() {\r\n                baiduApp.addEventListener( 'getAppHTML', function ( e, data ) {\r\n                    var url = 'http://app.baidu.com/app/enter?appid='+data.data['app_id'] +'&tn=app_canvas&app_spce_id=1&apikey='+apikey+'&api_key=' + apikey;\r\n                    editor.execCommand( \"webapp\", {url:url,width:data.uniWidth,height:data.uniHeight+60,logo:data.data['app_logo'],title:data.data['app_name']});\r\n                    dialog.close();\r\n                } );\r\n            }\r\n\r\n            var script = document.createElement( \"script\" );\r\n            script.type = \"text/javascript\";\r\n            script.src = \"http://app.baidu.com/appweb/api/search?auto=yes&container=container&apikey=\" + apikey + \"&instanceName=baiduApp&callback=clickCallback&config=searchConfig\";\r\n            document.body.appendChild( script );\r\n        } else {\r\n            $G( \"appShow\" ).innerHTML = \"<p class='errorMsg'>\"+lang.tip1+\"<a title='\"+lang.anthorApi+\"' href='http://app.baidu.com/static/cms/getapikey.html' target='_blank'>\"+lang.applyFor+\"</a></p><p class='errorMsg'>\"+lang.tip2+\"</p>\" ;\r\n        }\r\n\r\n    </script>\r\n</body>\r\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/wordimage/tangram.js",
    "content": "// Copyright (c) 2009, Baidu Inc. All rights reserved.\n// \n// Licensed under the BSD License\n// you may not use this file except in compliance with the License.\n// You may obtain a copy of the License at\n// \n//      http:// tangram.baidu.com/license.html\n// \n// Unless required by applicable law or agreed to in writing, software\n// distributed under the License is distributed on an \"AS-IS\" BASIS,\n// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n// See the License for the specific language governing permissions and\n// limitations under the License.\n /**\n * @namespace T Tangram七巧板\n * @name T\n * @version 1.6.0\n*/\n\n/**\n * 声明baidu包\n * @author: allstar, erik, meizz, berg\n */\nvar T,\n    baidu = T = baidu || {version: \"1.5.0\"};\nbaidu.guid = \"$BAIDU$\";\nbaidu.$$ = window[baidu.guid] = window[baidu.guid] || {global:{}};\n\n/**\n * 使用flash资源封装的一些功能\n * @namespace baidu.flash\n */\nbaidu.flash = baidu.flash || {};\n\n/**\n * 操作dom的方法\n * @namespace baidu.dom \n */\nbaidu.dom = baidu.dom || {};\n\n\n/**\n * 从文档中获取指定的DOM元素\n * @name baidu.dom.g\n * @function\n * @grammar baidu.dom.g(id)\n * @param {string|HTMLElement} id 元素的id或DOM元素.\n * @shortcut g,T.G\n * @meta standard\n * @see baidu.dom.q\n *\n * @return {HTMLElement|null} 获取的元素，查找不到时返回null,如果参数不合法，直接返回参数.\n */\nbaidu.dom.g = function(id) {\n    if (!id) return null;\n    if ('string' == typeof id || id instanceof String) {\n        return document.getElementById(id);\n    } else if (id.nodeName && (id.nodeType == 1 || id.nodeType == 9)) {\n        return id;\n    }\n    return null;\n};\nbaidu.g = baidu.G = baidu.dom.g;\n\n\n/**\n * 操作数组的方法\n * @namespace baidu.array\n */\n\nbaidu.array = baidu.array || {};\n\n\n/**\n * 遍历数组中所有元素\n * @name baidu.array.each\n * @function\n * @grammar baidu.array.each(source, iterator[, thisObject])\n * @param {Array} source 需要遍历的数组\n * @param {Function} iterator 对每个数组元素进行调用的函数，该函数有两个参数，第一个为数组元素，第二个为数组索引值，function (item, index)。\n * @param {Object} [thisObject] 函数调用时的this指针，如果没有此参数，默认是当前遍历的数组\n * @remark\n * each方法不支持对Object的遍历,对Object的遍历使用baidu.object.each 。\n * @shortcut each\n * @meta standard\n *             \n * @returns {Array} 遍历的数组\n */\n \nbaidu.each = baidu.array.forEach = baidu.array.each = function (source, iterator, thisObject) {\n    var returnValue, item, i, len = source.length;\n    \n    if ('function' == typeof iterator) {\n        for (i = 0; i < len; i++) {\n            item = source[i];\n            returnValue = iterator.call(thisObject || source, item, i);\n    \n            if (returnValue === false) {\n                break;\n            }\n        }\n    }\n    return source;\n};\n\n/**\n * 对语言层面的封装，包括类型判断、模块扩展、继承基类以及对象自定义事件的支持。\n * @namespace baidu.lang\n */\nbaidu.lang = baidu.lang || {};\n\n\n/**\n * 判断目标参数是否为function或Function实例\n * @name baidu.lang.isFunction\n * @function\n * @grammar baidu.lang.isFunction(source)\n * @param {Any} source 目标参数\n * @version 1.2\n * @see baidu.lang.isString,baidu.lang.isObject,baidu.lang.isNumber,baidu.lang.isArray,baidu.lang.isElement,baidu.lang.isBoolean,baidu.lang.isDate\n * @meta standard\n * @returns {boolean} 类型判断结果\n */\nbaidu.lang.isFunction = function (source) {\n    return '[object Function]' == Object.prototype.toString.call(source);\n};\n\n/**\n * 判断目标参数是否string类型或String对象\n * @name baidu.lang.isString\n * @function\n * @grammar baidu.lang.isString(source)\n * @param {Any} source 目标参数\n * @shortcut isString\n * @meta standard\n * @see baidu.lang.isObject,baidu.lang.isNumber,baidu.lang.isArray,baidu.lang.isElement,baidu.lang.isBoolean,baidu.lang.isDate\n *             \n * @returns {boolean} 类型判断结果\n */\nbaidu.lang.isString = function (source) {\n    return '[object String]' == Object.prototype.toString.call(source);\n};\nbaidu.isString = baidu.lang.isString;\n\n\n/**\n * 判断浏览器类型和特性的属性\n * @namespace baidu.browser\n */\nbaidu.browser = baidu.browser || {};\n\n\n/**\n * 判断是否为opera浏览器\n * @property opera opera版本号\n * @grammar baidu.browser.opera\n * @meta standard\n * @see baidu.browser.ie,baidu.browser.firefox,baidu.browser.safari,baidu.browser.chrome\n * @returns {Number} opera版本号\n */\n\n/**\n * opera 从10开始不是用opera后面的字符串进行版本的判断\n * 在Browser identification最后添加Version + 数字进行版本标识\n * opera后面的数字保持在9.80不变\n */\nbaidu.browser.opera = /opera(\\/| )(\\d+(\\.\\d+)?)(.+?(version\\/(\\d+(\\.\\d+)?)))?/i.test(navigator.userAgent) ?  + ( RegExp[\"\\x246\"] || RegExp[\"\\x242\"] ) : undefined;\n\n\n/**\n * 在目标元素的指定位置插入HTML代码\n * @name baidu.dom.insertHTML\n * @function\n * @grammar baidu.dom.insertHTML(element, position, html)\n * @param {HTMLElement|string} element 目标元素或目标元素的id\n * @param {string} position 插入html的位置信息，取值为beforeBegin,afterBegin,beforeEnd,afterEnd\n * @param {string} html 要插入的html\n * @remark\n * \n * 对于position参数，大小写不敏感<br>\n * 参数的意思：beforeBegin&lt;span&gt;afterBegin   this is span! beforeEnd&lt;/span&gt; afterEnd <br />\n * 此外，如果使用本函数插入带有script标签的HTML字符串，script标签对应的脚本将不会被执行。\n * \n * @shortcut insertHTML\n * @meta standard\n *             \n * @returns {HTMLElement} 目标元素\n */\nbaidu.dom.insertHTML = function (element, position, html) {\n    element = baidu.dom.g(element);\n    var range,begin;\n    if (element.insertAdjacentHTML && !baidu.browser.opera) {\n        element.insertAdjacentHTML(position, html);\n    } else {\n        range = element.ownerDocument.createRange();\n        position = position.toUpperCase();\n        if (position == 'AFTERBEGIN' || position == 'BEFOREEND') {\n            range.selectNodeContents(element);\n            range.collapse(position == 'AFTERBEGIN');\n        } else {\n            begin = position == 'BEFOREBEGIN';\n            range[begin ? 'setStartBefore' : 'setEndAfter'](element);\n            range.collapse(begin);\n        }\n        range.insertNode(range.createContextualFragment(html));\n    }\n    return element;\n};\n\nbaidu.insertHTML = baidu.dom.insertHTML;\n\n/**\n * 操作flash对象的方法，包括创建flash对象、获取flash对象以及判断flash插件的版本号\n * @namespace baidu.swf\n */\nbaidu.swf = baidu.swf || {};\n\n\n/**\n * 浏览器支持的flash插件版本\n * @property version 浏览器支持的flash插件版本\n * @grammar baidu.swf.version\n * @return {String} 版本号\n * @meta standard\n */\nbaidu.swf.version = (function () {\n    var n = navigator;\n    if (n.plugins && n.mimeTypes.length) {\n        var plugin = n.plugins[\"Shockwave Flash\"];\n        if (plugin && plugin.description) {\n            return plugin.description\n                    .replace(/([a-zA-Z]|\\s)+/, \"\")\n                    .replace(/(\\s)+r/, \".\") + \".0\";\n        }\n    } else if (window.ActiveXObject && !window.opera) {\n        for (var i = 12; i >= 2; i--) {\n            try {\n                var c = new ActiveXObject('ShockwaveFlash.ShockwaveFlash.' + i);\n                if (c) {\n                    var version = c.GetVariable(\"$version\");\n                    return version.replace(/WIN/g,'').replace(/,/g,'.');\n                }\n            } catch(e) {}\n        }\n    }\n})();\n\n/**\n * 操作字符串的方法\n * @namespace baidu.string\n */\nbaidu.string = baidu.string || {};\n\n\n/**\n * 对目标字符串进行html编码\n * @name baidu.string.encodeHTML\n * @function\n * @grammar baidu.string.encodeHTML(source)\n * @param {string} source 目标字符串\n * @remark\n * 编码字符有5个：&<>\"'\n * @shortcut encodeHTML\n * @meta standard\n * @see baidu.string.decodeHTML\n *             \n * @returns {string} html编码后的字符串\n */\nbaidu.string.encodeHTML = function (source) {\n    return String(source)\n                .replace(/&/g,'&amp;')\n                .replace(/</g,'&lt;')\n                .replace(/>/g,'&gt;')\n                .replace(/\"/g, \"&quot;\")\n                .replace(/'/g, \"&#39;\");\n};\n\nbaidu.encodeHTML = baidu.string.encodeHTML;\n\n/**\n * 创建flash对象的html字符串\n * @name baidu.swf.createHTML\n * @function\n * @grammar baidu.swf.createHTML(options)\n * \n * @param {Object} \toptions \t\t\t\t\t创建flash的选项参数\n * @param {string} \toptions.id \t\t\t\t\t要创建的flash的标识\n * @param {string} \toptions.url \t\t\t\tflash文件的url\n * @param {String} \toptions.errorMessage \t\t未安装flash player或flash player版本号过低时的提示\n * @param {string} \toptions.ver \t\t\t\t最低需要的flash player版本号\n * @param {string} \toptions.width \t\t\t\tflash的宽度\n * @param {string} \toptions.height \t\t\t\tflash的高度\n * @param {string} \toptions.align \t\t\t\tflash的对齐方式，允许值：middle/left/right/top/bottom\n * @param {string} \toptions.base \t\t\t\t设置用于解析swf文件中的所有相对路径语句的基本目录或URL\n * @param {string} \toptions.bgcolor \t\t\tswf文件的背景色\n * @param {string} \toptions.salign \t\t\t\t设置缩放的swf文件在由width和height设置定义的区域内的位置。允许值：l/r/t/b/tl/tr/bl/br\n * @param {boolean} options.menu \t\t\t\t是否显示右键菜单，允许值：true/false\n * @param {boolean} options.loop \t\t\t\t播放到最后一帧时是否重新播放，允许值： true/false\n * @param {boolean} options.play \t\t\t\tflash是否在浏览器加载时就开始播放。允许值：true/false\n * @param {string} \toptions.quality \t\t\t设置flash播放的画质，允许值：low/medium/high/autolow/autohigh/best\n * @param {string} \toptions.scale \t\t\t\t设置flash内容如何缩放来适应设置的宽高。允许值：showall/noborder/exactfit\n * @param {string} \toptions.wmode \t\t\t\t设置flash的显示模式。允许值：window/opaque/transparent\n * @param {string} \toptions.allowscriptaccess \t设置flash与页面的通信权限。允许值：always/never/sameDomain\n * @param {string} \toptions.allownetworking \t设置swf文件中允许使用的网络API。允许值：all/internal/none\n * @param {boolean} options.allowfullscreen \t是否允许flash全屏。允许值：true/false\n * @param {boolean} options.seamlesstabbing \t允许设置执行无缝跳格，从而使用户能跳出flash应用程序。该参数只能在安装Flash7及更高版本的Windows中使用。允许值：true/false\n * @param {boolean} options.devicefont \t\t\t设置静态文本对象是否以设备字体呈现。允许值：true/false\n * @param {boolean} options.swliveconnect \t\t第一次加载flash时浏览器是否应启动Java。允许值：true/false\n * @param {Object} \toptions.vars \t\t\t\t要传递给flash的参数，支持JSON或string类型。\n * \n * @see baidu.swf.create\n * @meta standard\n * @returns {string} flash对象的html字符串\n */\nbaidu.swf.createHTML = function (options) {\n    options = options || {};\n    var version = baidu.swf.version, \n        needVersion = options['ver'] || '6.0.0', \n        vUnit1, vUnit2, i, k, len, item, tmpOpt = {},\n        encodeHTML = baidu.string.encodeHTML;\n    for (k in options) {\n        tmpOpt[k] = options[k];\n    }\n    options = tmpOpt;\n    if (version) {\n        version = version.split('.');\n        needVersion = needVersion.split('.');\n        for (i = 0; i < 3; i++) {\n            vUnit1 = parseInt(version[i], 10);\n            vUnit2 = parseInt(needVersion[i], 10);\n            if (vUnit2 < vUnit1) {\n                break;\n            } else if (vUnit2 > vUnit1) {\n                return '';\n            }\n        }\n    } else {\n        return '';\n    }\n    \n    var vars = options['vars'],\n        objProperties = ['classid', 'codebase', 'id', 'width', 'height', 'align'];\n    options['align'] = options['align'] || 'middle';\n    options['classid'] = 'clsid:d27cdb6e-ae6d-11cf-96b8-444553540000';\n    options['codebase'] = 'http://fpdownload.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version=6,0,0,0';\n    options['movie'] = options['url'] || '';\n    delete options['vars'];\n    delete options['url'];\n    if ('string' == typeof vars) {\n        options['flashvars'] = vars;\n    } else {\n        var fvars = [];\n        for (k in vars) {\n            item = vars[k];\n            fvars.push(k + \"=\" + encodeURIComponent(item));\n        }\n        options['flashvars'] = fvars.join('&');\n    }\n    var str = ['<object '];\n    for (i = 0, len = objProperties.length; i < len; i++) {\n        item = objProperties[i];\n        str.push(' ', item, '=\"', encodeHTML(options[item]), '\"');\n    }\n    str.push('>');\n    var params = {\n        'wmode'             : 1,\n        'scale'             : 1,\n        'quality'           : 1,\n        'play'              : 1,\n        'loop'              : 1,\n        'menu'              : 1,\n        'salign'            : 1,\n        'bgcolor'           : 1,\n        'base'              : 1,\n        'allowscriptaccess' : 1,\n        'allownetworking'   : 1,\n        'allowfullscreen'   : 1,\n        'seamlesstabbing'   : 1,\n        'devicefont'        : 1,\n        'swliveconnect'     : 1,\n        'flashvars'         : 1,\n        'movie'             : 1\n    };\n    \n    for (k in options) {\n        item = options[k];\n        k = k.toLowerCase();\n        if (params[k] && (item || item === false || item === 0)) {\n            str.push('<param name=\"' + k + '\" value=\"' + encodeHTML(item) + '\" />');\n        }\n    }\n    options['src']  = options['movie'];\n    options['name'] = options['id'];\n    delete options['id'];\n    delete options['movie'];\n    delete options['classid'];\n    delete options['codebase'];\n    options['type'] = 'application/x-shockwave-flash';\n    options['pluginspage'] = 'http://www.macromedia.com/go/getflashplayer';\n    str.push('<embed');\n    var salign;\n    for (k in options) {\n        item = options[k];\n        if (item || item === false || item === 0) {\n            if ((new RegExp(\"^salign\\x24\", \"i\")).test(k)) {\n                salign = item;\n                continue;\n            }\n            \n            str.push(' ', k, '=\"', encodeHTML(item), '\"');\n        }\n    }\n    \n    if (salign) {\n        str.push(' salign=\"', encodeHTML(salign), '\"');\n    }\n    str.push('></embed></object>');\n    \n    return str.join('');\n};\n\n\n/**\n * 在页面中创建一个flash对象\n * @name baidu.swf.create\n * @function\n * @grammar baidu.swf.create(options[, container])\n * \n * @param {Object} \toptions \t\t\t\t\t创建flash的选项参数\n * @param {string} \toptions.id \t\t\t\t\t要创建的flash的标识\n * @param {string} \toptions.url \t\t\t\tflash文件的url\n * @param {String} \toptions.errorMessage \t\t未安装flash player或flash player版本号过低时的提示\n * @param {string} \toptions.ver \t\t\t\t最低需要的flash player版本号\n * @param {string} \toptions.width \t\t\t\tflash的宽度\n * @param {string} \toptions.height \t\t\t\tflash的高度\n * @param {string} \toptions.align \t\t\t\tflash的对齐方式，允许值：middle/left/right/top/bottom\n * @param {string} \toptions.base \t\t\t\t设置用于解析swf文件中的所有相对路径语句的基本目录或URL\n * @param {string} \toptions.bgcolor \t\t\tswf文件的背景色\n * @param {string} \toptions.salign \t\t\t\t设置缩放的swf文件在由width和height设置定义的区域内的位置。允许值：l/r/t/b/tl/tr/bl/br\n * @param {boolean} options.menu \t\t\t\t是否显示右键菜单，允许值：true/false\n * @param {boolean} options.loop \t\t\t\t播放到最后一帧时是否重新播放，允许值： true/false\n * @param {boolean} options.play \t\t\t\tflash是否在浏览器加载时就开始播放。允许值：true/false\n * @param {string} \toptions.quality \t\t\t设置flash播放的画质，允许值：low/medium/high/autolow/autohigh/best\n * @param {string} \toptions.scale \t\t\t\t设置flash内容如何缩放来适应设置的宽高。允许值：showall/noborder/exactfit\n * @param {string} \toptions.wmode \t\t\t\t设置flash的显示模式。允许值：window/opaque/transparent\n * @param {string} \toptions.allowscriptaccess \t设置flash与页面的通信权限。允许值：always/never/sameDomain\n * @param {string} \toptions.allownetworking \t设置swf文件中允许使用的网络API。允许值：all/internal/none\n * @param {boolean} options.allowfullscreen \t是否允许flash全屏。允许值：true/false\n * @param {boolean} options.seamlesstabbing \t允许设置执行无缝跳格，从而使用户能跳出flash应用程序。该参数只能在安装Flash7及更高版本的Windows中使用。允许值：true/false\n * @param {boolean} options.devicefont \t\t\t设置静态文本对象是否以设备字体呈现。允许值：true/false\n * @param {boolean} options.swliveconnect \t\t第一次加载flash时浏览器是否应启动Java。允许值：true/false\n * @param {Object} \toptions.vars \t\t\t\t要传递给flash的参数，支持JSON或string类型。\n * \n * @param {HTMLElement|string} [container] \t\tflash对象的父容器元素，不传递该参数时在当前代码位置创建flash对象。\n * @meta standard\n * @see baidu.swf.createHTML,baidu.swf.getMovie\n */\nbaidu.swf.create = function (options, target) {\n    options = options || {};\n    var html = baidu.swf.createHTML(options) \n               || options['errorMessage'] \n               || '';\n                \n    if (target && 'string' == typeof target) {\n        target = document.getElementById(target);\n    }\n    baidu.dom.insertHTML( target || document.body ,'beforeEnd',html );\n};\n/**\n * 判断是否为ie浏览器\n * @name baidu.browser.ie\n * @field\n * @grammar baidu.browser.ie\n * @returns {Number} IE版本号\n */\nbaidu.browser.ie = baidu.ie = /msie (\\d+\\.\\d+)/i.test(navigator.userAgent) ? (document.documentMode || + RegExp['\\x241']) : undefined;\n\n/**\n * 移除数组中的项\n * @name baidu.array.remove\n * @function\n * @grammar baidu.array.remove(source, match)\n * @param {Array} source 需要移除项的数组\n * @param {Any} match 要移除的项\n * @meta standard\n * @see baidu.array.removeAt\n *             \n * @returns {Array} 移除后的数组\n */\nbaidu.array.remove = function (source, match) {\n    var len = source.length;\n        \n    while (len--) {\n        if (len in source && source[len] === match) {\n            source.splice(len, 1);\n        }\n    }\n    return source;\n};\n\n/**\n * 判断目标参数是否Array对象\n * @name baidu.lang.isArray\n * @function\n * @grammar baidu.lang.isArray(source)\n * @param {Any} source 目标参数\n * @meta standard\n * @see baidu.lang.isString,baidu.lang.isObject,baidu.lang.isNumber,baidu.lang.isElement,baidu.lang.isBoolean,baidu.lang.isDate\n *             \n * @returns {boolean} 类型判断结果\n */\nbaidu.lang.isArray = function (source) {\n    return '[object Array]' == Object.prototype.toString.call(source);\n};\n\n\n\n/**\n * 将一个变量转换成array\n * @name baidu.lang.toArray\n * @function\n * @grammar baidu.lang.toArray(source)\n * @param {mix} source 需要转换成array的变量\n * @version 1.3\n * @meta standard\n * @returns {array} 转换后的array\n */\nbaidu.lang.toArray = function (source) {\n    if (source === null || source === undefined)\n        return [];\n    if (baidu.lang.isArray(source))\n        return source;\n    if (typeof source.length !== 'number' || typeof source === 'string' || baidu.lang.isFunction(source)) {\n        return [source];\n    }\n    if (source.item) {\n        var l = source.length, array = new Array(l);\n        while (l--)\n            array[l] = source[l];\n        return array;\n    }\n\n    return [].slice.call(source);\n};\n\n/**\n * 获得flash对象的实例\n * @name baidu.swf.getMovie\n * @function\n * @grammar baidu.swf.getMovie(name)\n * @param {string} name flash对象的名称\n * @see baidu.swf.create\n * @meta standard\n * @returns {HTMLElement} flash对象的实例\n */\nbaidu.swf.getMovie = function (name) {\n\tvar movie = document[name], ret;\n    return baidu.browser.ie == 9 ?\n    \tmovie && movie.length ? \n    \t\t(ret = baidu.array.remove(baidu.lang.toArray(movie),function(item){\n    \t\t\treturn item.tagName.toLowerCase() != \"embed\";\n    \t\t})).length == 1 ? ret[0] : ret\n    \t\t: movie\n    \t: movie || window[name];\n};\n\n\nbaidu.flash._Base = (function(){\n   \n    var prefix = 'bd__flash__';\n\n    /**\n     * 创建一个随机的字符串\n     * @private\n     * @return {String}\n     */\n    function _createString(){\n        return  prefix + Math.floor(Math.random() * 2147483648).toString(36);\n    };\n   \n    /**\n     * 检查flash状态\n     * @private\n     * @param {Object} target flash对象\n     * @return {Boolean}\n     */\n    function _checkReady(target){\n        if(typeof target !== 'undefined' && typeof target.flashInit !== 'undefined' && target.flashInit()){\n            return true;\n        }else{\n            return false;\n        }\n    };\n\n    /**\n     * 调用之前进行压栈的函数\n     * @private\n     * @param {Array} callQueue 调用队列\n     * @param {Object} target flash对象\n     * @return {Null}\n     */\n    function _callFn(callQueue, target){\n        var result = null;\n        \n        callQueue = callQueue.reverse();\n        baidu.each(callQueue, function(item){\n            result = target.call(item.fnName, item.params);\n            item.callBack(result);\n        });\n    };\n\n    /**\n     * 为传入的匿名函数创建函数名\n     * @private\n     * @param {String|Function} fun 传入的匿名函数或者函数名\n     * @return {String}\n     */\n    function _createFunName(fun){\n        var name = '';\n\n        if(baidu.lang.isFunction(fun)){\n            name = _createString();\n            window[name] = function(){\n                fun.apply(window, arguments);\n            };\n\n            return name;\n        }else if(baidu.lang.isString){\n            return fun;\n        }\n    };\n\n    /**\n     * 绘制flash\n     * @private\n     * @param {Object} options 创建参数\n     * @return {Object} \n     */\n    function _render(options){\n        if(!options.id){\n            options.id = _createString();\n        }\n        \n        var container = options.container || '';\n        delete(options.container);\n        \n        baidu.swf.create(options, container);\n        \n        return baidu.swf.getMovie(options.id);\n    };\n\n    return function(options, callBack){\n        var me = this,\n            autoRender = (typeof options.autoRender !== 'undefined' ? options.autoRender : true),\n            createOptions = options.createOptions || {},\n            target = null,\n            isReady = false,\n            callQueue = [],\n            timeHandle = null,\n            callBack = callBack || [];\n\n        /**\n         * 将flash文件绘制到页面上\n         * @public\n         * @return {Null}\n         */\n        me.render = function(){\n            target = _render(createOptions);\n            \n            if(callBack.length > 0){\n                baidu.each(callBack, function(funName, index){\n                    callBack[index] = _createFunName(options[funName] || new Function());\n                });    \n            }\n            me.call('setJSFuncName', [callBack]);\n        };\n\n        /**\n         * 返回flash状态\n         * @return {Boolean}\n         */\n        me.isReady = function(){\n            return isReady;\n        };\n\n        /**\n         * 调用flash接口的统一入口\n         * @param {String} fnName 调用的函数名\n         * @param {Array} params 传入的参数组成的数组,若不许要参数，需传入空数组\n         * @param {Function} [callBack] 异步调用后将返回值作为参数的调用回调函数，如无返回值，可以不传入此参数\n         * @return {Null}\n        */\n        me.call = function(fnName, params, callBack){\n            if(!fnName) return null;\n            callBack = callBack || new Function();\n\n            var result = null;\n    \n            if(isReady){\n                result = target.call(fnName, params);\n                callBack(result);\n            }else{\n                callQueue.push({\n                    fnName: fnName,\n                    params: params,\n                    callBack: callBack\n                });\n    \n                (!timeHandle) && (timeHandle = setInterval(_check, 200));\n            }\n        };\n    \n        /**\n         * 为传入的匿名函数创建函数名\n         * @public\n         * @param {String|Function} fun 传入的匿名函数或者函数名\n         * @return {String}\n         */\n        me.createFunName = function(fun){\n            return _createFunName(fun);    \n        };\n\n        /**\n         * 检查flash是否ready， 并进行调用\n         * @private\n         * @return {Null}\n         */\n        function _check(){\n            if(_checkReady(target)){\n                clearInterval(timeHandle);\n                timeHandle = null;\n                _call();\n\n                isReady = true;\n            }               \n        };\n\n        /**\n         * 调用之前进行压栈的函数\n         * @private\n         * @return {Null}\n         */\n        function _call(){\n            _callFn(callQueue, target);\n            callQueue = [];\n        }\n\n        autoRender && me.render(); \n    };\n})();\n\n\n\n/**\n * 创建flash based imageUploader\n * @class\n * @grammar baidu.flash.imageUploader(options)\n * @param {Object} createOptions 创建flash时需要的参数，请参照baidu.swf.create文档\n * @config {Object} vars 创建imageUploader时所需要的参数\n * @config {Number} vars.gridWidth 每一个预览图片所占的宽度，应该为flash寛的整除\n * @config {Number} vars.gridHeight 每一个预览图片所占的高度，应该为flash高的整除\n * @config {Number} vars.picWidth 单张预览图片的宽度\n * @config {Number} vars.picHeight 单张预览图片的高度\n * @config {String} vars.uploadDataFieldName POST请求中图片数据的key,默认值'picdata'\n * @config {String} vars.picDescFieldName POST请求中图片描述的key,默认值'picDesc'\n * @config {Number} vars.maxSize 文件的最大体积,单位'MB'\n * @config {Number} vars.compressSize 上传前如果图片体积超过该值，会先压缩\n * @config {Number} vars.maxNum:32 最大上传多少个文件\n * @config {Number} vars.compressLength 能接受的最大边长，超过该值会等比压缩\n * @config {String} vars.url 上传的url地址\n * @config {Number} vars.mode mode == 0时，是使用滚动条，mode == 1时，拉伸flash, 默认值为0\n * @see baidu.swf.createHTML\n * @param {String} backgroundUrl 背景图片路径\n * @param {String} listBacgroundkUrl 布局控件背景\n * @param {String} buttonUrl 按钮图片不背景\n * @param {String|Function} selectFileCallback 选择文件的回调\n * @param {String|Function} exceedFileCallback文件超出限制的最大体积时的回调\n * @param {String|Function} deleteFileCallback 删除文件的回调\n * @param {String|Function} startUploadCallback 开始上传某个文件时的回调\n * @param {String|Function} uploadCompleteCallback 某个文件上传完成的回调\n * @param {String|Function} uploadErrorCallback 某个文件上传失败的回调\n * @param {String|Function} allCompleteCallback 全部上传完成时的回调\n * @param {String|Function} changeFlashHeight 改变Flash的高度，mode==1的时候才有用\n */ \nbaidu.flash.imageUploader = baidu.flash.imageUploader || function(options){\n   \n    var me = this,\n        options = options || {},\n        _flash = new baidu.flash._Base(options, [\n            'selectFileCallback', \n            'exceedFileCallback', \n            'deleteFileCallback', \n            'startUploadCallback',\n            'uploadCompleteCallback',\n            'uploadErrorCallback',\n            'allCompleteCallback',\n            'changeFlashHeight'\n        ]);\n    /**\n     * 开始或回复上传图片\n     * @public\n     * @return {Null}\n     */\n    me.upload = function(){\n        _flash.call('upload');\n    };\n\n    /**\n     * 暂停上传图片\n     * @public\n     * @return {Null}\n     */\n    me.pause = function(){\n        _flash.call('pause');\n    };\n    me.addCustomizedParams = function(index,obj){\n        _flash.call('addCustomizedParams',[index,obj]);\n    }\n};\n\n/**\n * 操作原生对象的方法\n * @namespace baidu.object\n */\nbaidu.object = baidu.object || {};\n\n\n/**\n * 将源对象的所有属性拷贝到目标对象中\n * @author erik\n * @name baidu.object.extend\n * @function\n * @grammar baidu.object.extend(target, source)\n * @param {Object} target 目标对象\n * @param {Object} source 源对象\n * @see baidu.array.merge\n * @remark\n * \n1.目标对象中，与源对象key相同的成员将会被覆盖。<br>\n2.源对象的prototype成员不会拷贝。\n\t\t\n * @shortcut extend\n * @meta standard\n *             \n * @returns {Object} 目标对象\n */\nbaidu.extend =\nbaidu.object.extend = function (target, source) {\n    for (var p in source) {\n        if (source.hasOwnProperty(p)) {\n            target[p] = source[p];\n        }\n    }\n    \n    return target;\n};\n\n\n\n\n\n/**\n * 创建flash based fileUploader\n * @class\n * @grammar baidu.flash.fileUploader(options)\n * @param {Object} options\n * @config {Object} createOptions 创建flash时需要的参数，请参照baidu.swf.create文档\n * @config {String} createOptions.width\n * @config {String} createOptions.height\n * @config {Number} maxNum 最大可选文件数\n * @config {Function|String} selectFile\n * @config {Function|String} exceedMaxSize\n * @config {Function|String} deleteFile\n * @config {Function|String} uploadStart\n * @config {Function|String} uploadComplete\n * @config {Function|String} uploadError\n * @config {Function|String} uploadProgress\n */\nbaidu.flash.fileUploader = baidu.flash.fileUploader || function(options){\n    var me = this,\n        options = options || {};\n    \n    options.createOptions = baidu.extend({\n        wmod: 'transparent'\n    },options.createOptions || {});\n    \n    var _flash = new baidu.flash._Base(options, [\n        'selectFile',\n        'exceedMaxSize',\n        'deleteFile',\n        'uploadStart',\n        'uploadComplete',\n        'uploadError', \n        'uploadProgress'\n    ]);\n\n    _flash.call('setMaxNum', options.maxNum ? [options.maxNum] : [1]);\n\n    /**\n     * 设置当鼠标移动到flash上时，是否变成手型\n     * @public\n     * @param {Boolean} isCursor\n     * @return {Null}\n     */\n    me.setHandCursor = function(isCursor){\n        _flash.call('setHandCursor', [isCursor || false]);\n    };\n\n    /**\n     * 设置鼠标相应函数名\n     * @param {String|Function} fun\n     */\n    me.setMSFunName = function(fun){\n        _flash.call('setMSFunName',[_flash.createFunName(fun)]);\n    }; \n\n    /**\n     * 执行上传操作\n     * @param {String} url 上传的url\n     * @param {String} fieldName 上传的表单字段名\n     * @param {Object} postData 键值对，上传的POST数据\n     * @param {Number|Array|null|-1} [index]上传的文件序列\n     *                            Int值上传该文件\n     *                            Array一次串行上传该序列文件\n     *                            -1/null上传所有文件\n     * @return {Null}\n     */\n    me.upload = function(url, fieldName, postData, index){\n\n        if(typeof url !== 'string' || typeof fieldName !== 'string') return null;\n        if(typeof index === 'undefined') index = -1;\n\n        _flash.call('upload', [url, fieldName, postData, index]);\n    };\n\n    /**\n     * 取消上传操作\n     * @public\n     * @param {Number|-1} index\n     */\n    me.cancel = function(index){\n        if(typeof index === 'undefined') index = -1;\n        _flash.call('cancel', [index]);\n    };\n\n    /**\n     * 删除文件\n     * @public\n     * @param {Number|Array} [index] 要删除的index，不传则全部删除\n     * @param {Function} callBack\n     * */\n    me.deleteFile = function(index, callBack){\n\n        var callBackAll = function(list){\n                callBack && callBack(list);\n            };\n\n        if(typeof index === 'undefined'){\n            _flash.call('deleteFilesAll', [], callBackAll);\n            return;\n        };\n        \n        if(typeof index === 'Number') index = [index];\n        index.sort(function(a,b){\n            return b-a;\n        });\n        baidu.each(index, function(item){\n            _flash.call('deleteFileBy', item, callBackAll);\n        });\n    };\n\n    /**\n     * 添加文件类型，支持macType\n     * @public\n     * @param {Object|Array[Object]} type {description:String, extention:String}\n     * @return {Null};\n     */\n    me.addFileType = function(type){\n        var type = type || [[]];\n        \n        if(type instanceof Array) type = [type];\n        else type = [[type]];\n        _flash.call('addFileTypes', type);\n    };\n    \n    /**\n     * 设置文件类型，支持macType\n     * @public\n     * @param {Object|Array[Object]} type {description:String, extention:String}\n     * @return {Null};\n     */\n    me.setFileType = function(type){\n        var type = type || [[]];\n        \n        if(type instanceof Array) type = [type];\n        else type = [[type]];\n        _flash.call('setFileTypes', type);\n    };\n\n    /**\n     * 设置可选文件的数量限制\n     * @public\n     * @param {Number} num\n     * @return {Null}\n     */\n    me.setMaxNum = function(num){\n        _flash.call('setMaxNum', [num]);\n    };\n\n    /**\n     * 设置可选文件大小限制，以兆M为单位\n     * @public\n     * @param {Number} num,0为无限制\n     * @return {Null}\n     */\n    me.setMaxSize = function(num){\n        _flash.call('setMaxSize', [num]);\n    };\n\n    /**\n     * @public\n     */\n    me.getFileAll = function(callBack){\n        _flash.call('getFileAll', [], callBack);\n    };\n\n    /**\n     * @public\n     * @param {Number} index\n     * @param {Function} [callBack]\n     */\n    me.getFileByIndex = function(index, callBack){\n        _flash.call('getFileByIndex', [], callBack);\n    };\n\n    /**\n     * @public\n     * @param {Number} index\n     * @param {function} [callBack]\n     */\n    me.getStatusByIndex = function(index, callBack){\n        _flash.call('getStatusByIndex', [], callBack);\n    };\n};\n\n/**\n * 使用动态script标签请求服务器资源，包括由服务器端的回调和浏览器端的回调\n * @namespace baidu.sio\n */\nbaidu.sio = baidu.sio || {};\n\n/**\n * \n * @param {HTMLElement} src script节点\n * @param {String} url script节点的地址\n * @param {String} [charset] 编码\n */\nbaidu.sio._createScriptTag = function(scr, url, charset){\n    scr.setAttribute('type', 'text/javascript');\n    charset && scr.setAttribute('charset', charset);\n    scr.setAttribute('src', url);\n    document.getElementsByTagName('head')[0].appendChild(scr);\n};\n\n/**\n * 删除script的属性，再删除script标签，以解决修复内存泄漏的问题\n * \n * @param {HTMLElement} src script节点\n */\nbaidu.sio._removeScriptTag = function(scr){\n    if (scr.clearAttributes) {\n        scr.clearAttributes();\n    } else {\n        for (var attr in scr) {\n            if (scr.hasOwnProperty(attr)) {\n                delete scr[attr];\n            }\n        }\n    }\n    if(scr && scr.parentNode){\n        scr.parentNode.removeChild(scr);\n    }\n    scr = null;\n};\n\n\n/**\n * 通过script标签加载数据，加载完成由浏览器端触发回调\n * @name baidu.sio.callByBrowser\n * @function\n * @grammar baidu.sio.callByBrowser(url, opt_callback, opt_options)\n * @param {string} url 加载数据的url\n * @param {Function|string} opt_callback 数据加载结束时调用的函数或函数名\n * @param {Object} opt_options 其他可选项\n * @config {String} [charset] script的字符集\n * @config {Integer} [timeOut] 超时时间，超过这个时间将不再响应本请求，并触发onfailure函数\n * @config {Function} [onfailure] timeOut设定后才生效，到达超时时间时触发本函数\n * @remark\n * 1、与callByServer不同，callback参数只支持Function类型，不支持string。\n * 2、如果请求了一个不存在的页面，callback函数在IE/opera下也会被调用，因此使用者需要在onsuccess函数中判断数据是否正确加载。\n * @meta standard\n * @see baidu.sio.callByServer\n */\nbaidu.sio.callByBrowser = function (url, opt_callback, opt_options) {\n    var scr = document.createElement(\"SCRIPT\"),\n        scriptLoaded = 0,\n        options = opt_options || {},\n        charset = options['charset'],\n        callback = opt_callback || function(){},\n        timeOut = options['timeOut'] || 0,\n        timer;\n    scr.onload = scr.onreadystatechange = function () {\n        if (scriptLoaded) {\n            return;\n        }\n        \n        var readyState = scr.readyState;\n        if ('undefined' == typeof readyState\n            || readyState == \"loaded\"\n            || readyState == \"complete\") {\n            scriptLoaded = 1;\n            try {\n                callback();\n                clearTimeout(timer);\n            } finally {\n                scr.onload = scr.onreadystatechange = null;\n                baidu.sio._removeScriptTag(scr);\n            }\n        }\n    };\n\n    if( timeOut ){\n        timer = setTimeout(function(){\n            scr.onload = scr.onreadystatechange = null;\n            baidu.sio._removeScriptTag(scr);\n            options.onfailure && options.onfailure();\n        }, timeOut);\n    }\n    \n    baidu.sio._createScriptTag(scr, url, charset);\n};\n\n/**\n * 通过script标签加载数据，加载完成由服务器端触发回调\n * @name baidu.sio.callByServer\n * @function\n * @grammar baidu.sio.callByServer(url, callback[, opt_options])\n * @param {string} url 加载数据的url.\n * @param {Function|string} callback 服务器端调用的函数或函数名。如果没有指定本参数，将在URL中寻找options['queryField']做为callback的方法名.\n * @param {Object} opt_options 加载数据时的选项.\n * @config {string} [charset] script的字符集\n * @config {string} [queryField] 服务器端callback请求字段名，默认为callback\n * @config {Integer} [timeOut] 超时时间(单位：ms)，超过这个时间将不再响应本请求，并触发onfailure函数\n * @config {Function} [onfailure] timeOut设定后才生效，到达超时时间时触发本函数\n * @remark\n * 如果url中已经包含key为“options['queryField']”的query项，将会被替换成callback中参数传递或自动生成的函数名。\n * @meta standard\n * @see baidu.sio.callByBrowser\n */\nbaidu.sio.callByServer = /**@function*/function(url, callback, opt_options) {\n    var scr = document.createElement('SCRIPT'),\n        prefix = 'bd__cbs__',\n        callbackName,\n        callbackImpl,\n        options = opt_options || {},\n        charset = options['charset'],\n        queryField = options['queryField'] || 'callback',\n        timeOut = options['timeOut'] || 0,\n        timer,\n        reg = new RegExp('(\\\\?|&)' + queryField + '=([^&]*)'),\n        matches;\n\n    if (baidu.lang.isFunction(callback)) {\n        callbackName = prefix + Math.floor(Math.random() * 2147483648).toString(36);\n        window[callbackName] = getCallBack(0);\n    } else if(baidu.lang.isString(callback)){\n        callbackName = callback;\n    } else {\n        if (matches = reg.exec(url)) {\n            callbackName = matches[2];\n        }\n    }\n\n    if( timeOut ){\n        timer = setTimeout(getCallBack(1), timeOut);\n    }\n    url = url.replace(reg, '\\x241' + queryField + '=' + callbackName);\n    \n    if (url.search(reg) < 0) {\n        url += (url.indexOf('?') < 0 ? '?' : '&') + queryField + '=' + callbackName;\n    }\n    baidu.sio._createScriptTag(scr, url, charset);\n\n    /*\n     * 返回一个函数，用于立即（挂在window上）或者超时（挂在setTimeout中）时执行\n     */\n    function getCallBack(onTimeOut){\n        /*global callbackName, callback, scr, options;*/\n        return function(){\n            try {\n                if( onTimeOut ){\n                    options.onfailure && options.onfailure();\n                }else{\n                    callback.apply(window, arguments);\n                    clearTimeout(timer);\n                }\n                window[callbackName] = null;\n                delete window[callbackName];\n            } catch (exception) {\n            } finally {\n                baidu.sio._removeScriptTag(scr);\n            }\n        }\n    }\n};\n\n/**\n * 通过请求一个图片的方式令服务器存储一条日志\n * @function\n * @grammar baidu.sio.log(url)\n * @param {string} url 要发送的地址.\n * @author: int08h,leeight\n */\nbaidu.sio.log = function(url) {\n  var img = new Image(),\n      key = 'tangram_sio_log_' + Math.floor(Math.random() *\n            2147483648).toString(36);\n  window[key] = img;\n\n  img.onload = img.onerror = img.onabort = function() {\n    img.onload = img.onerror = img.onabort = null;\n\n    window[key] = null;\n    img = null;\n  };\n  img.src = url;\n};\n\n\n\n/*\n * Tangram\n * Copyright 2009 Baidu Inc. All rights reserved.\n * \n * path: baidu/json.js\n * author: erik\n * version: 1.1.0\n * date: 2009/12/02\n */\n\n\n/**\n * 操作json对象的方法\n * @namespace baidu.json\n */\nbaidu.json = baidu.json || {};\n/*\n * Tangram\n * Copyright 2009 Baidu Inc. All rights reserved.\n * \n * path: baidu/json/parse.js\n * author: erik, berg\n * version: 1.2\n * date: 2009/11/23\n */\n\n\n\n/**\n * 将字符串解析成json对象。注：不会自动祛除空格\n * @name baidu.json.parse\n * @function\n * @grammar baidu.json.parse(data)\n * @param {string} source 需要解析的字符串\n * @remark\n * 该方法的实现与ecma-262第五版中规定的JSON.parse不同，暂时只支持传入一个参数。后续会进行功能丰富。\n * @meta standard\n * @see baidu.json.stringify,baidu.json.decode\n *             \n * @returns {JSON} 解析结果json对象\n */\nbaidu.json.parse = function (data) {\n    //2010/12/09：更新至不使用原生parse，不检测用户输入是否正确\n    return (new Function(\"return (\" + data + \")\"))();\n};\n/*\n * Tangram\n * Copyright 2009 Baidu Inc. All rights reserved.\n * \n * path: baidu/json/decode.js\n * author: erik, cat\n * version: 1.3.4\n * date: 2010/12/23\n */\n\n\n\n/**\n * 将字符串解析成json对象，为过时接口，今后会被baidu.json.parse代替\n * @name baidu.json.decode\n * @function\n * @grammar baidu.json.decode(source)\n * @param {string} source 需要解析的字符串\n * @meta out\n * @see baidu.json.encode,baidu.json.parse\n *             \n * @returns {JSON} 解析结果json对象\n */\nbaidu.json.decode = baidu.json.parse;\n/*\n * Tangram\n * Copyright 2009 Baidu Inc. All rights reserved.\n * \n * path: baidu/json/stringify.js\n * author: erik\n * version: 1.1.0\n * date: 2010/01/11\n */\n\n\n\n/**\n * 将json对象序列化\n * @name baidu.json.stringify\n * @function\n * @grammar baidu.json.stringify(value)\n * @param {JSON} value 需要序列化的json对象\n * @remark\n * 该方法的实现与ecma-262第五版中规定的JSON.stringify不同，暂时只支持传入一个参数。后续会进行功能丰富。\n * @meta standard\n * @see baidu.json.parse,baidu.json.encode\n *             \n * @returns {string} 序列化后的字符串\n */\nbaidu.json.stringify = (function () {\n    /**\n     * 字符串处理时需要转义的字符表\n     * @private\n     */\n    var escapeMap = {\n        \"\\b\": '\\\\b',\n        \"\\t\": '\\\\t',\n        \"\\n\": '\\\\n',\n        \"\\f\": '\\\\f',\n        \"\\r\": '\\\\r',\n        '\"' : '\\\\\"',\n        \"\\\\\": '\\\\\\\\'\n    };\n    \n    /**\n     * 字符串序列化\n     * @private\n     */\n    function encodeString(source) {\n        if (/[\"\\\\\\x00-\\x1f]/.test(source)) {\n            source = source.replace(\n                /[\"\\\\\\x00-\\x1f]/g, \n                function (match) {\n                    var c = escapeMap[match];\n                    if (c) {\n                        return c;\n                    }\n                    c = match.charCodeAt();\n                    return \"\\\\u00\" \n                            + Math.floor(c / 16).toString(16) \n                            + (c % 16).toString(16);\n                });\n        }\n        return '\"' + source + '\"';\n    }\n    \n    /**\n     * 数组序列化\n     * @private\n     */\n    function encodeArray(source) {\n        var result = [\"[\"], \n            l = source.length,\n            preComma, i, item;\n            \n        for (i = 0; i < l; i++) {\n            item = source[i];\n            \n            switch (typeof item) {\n            case \"undefined\":\n            case \"function\":\n            case \"unknown\":\n                break;\n            default:\n                if(preComma) {\n                    result.push(',');\n                }\n                result.push(baidu.json.stringify(item));\n                preComma = 1;\n            }\n        }\n        result.push(\"]\");\n        return result.join(\"\");\n    }\n    \n    /**\n     * 处理日期序列化时的补零\n     * @private\n     */\n    function pad(source) {\n        return source < 10 ? '0' + source : source;\n    }\n    \n    /**\n     * 日期序列化\n     * @private\n     */\n    function encodeDate(source){\n        return '\"' + source.getFullYear() + \"-\" \n                + pad(source.getMonth() + 1) + \"-\" \n                + pad(source.getDate()) + \"T\" \n                + pad(source.getHours()) + \":\" \n                + pad(source.getMinutes()) + \":\" \n                + pad(source.getSeconds()) + '\"';\n    }\n    \n    return function (value) {\n        switch (typeof value) {\n        case 'undefined':\n            return 'undefined';\n            \n        case 'number':\n            return isFinite(value) ? String(value) : \"null\";\n            \n        case 'string':\n            return encodeString(value);\n            \n        case 'boolean':\n            return String(value);\n            \n        default:\n            if (value === null) {\n                return 'null';\n            } else if (value instanceof Array) {\n                return encodeArray(value);\n            } else if (value instanceof Date) {\n                return encodeDate(value);\n            } else {\n                var result = ['{'],\n                    encode = baidu.json.stringify,\n                    preComma,\n                    item;\n                    \n                for (var key in value) {\n                    if (Object.prototype.hasOwnProperty.call(value, key)) {\n                        item = value[key];\n                        switch (typeof item) {\n                        case 'undefined':\n                        case 'unknown':\n                        case 'function':\n                            break;\n                        default:\n                            if (preComma) {\n                                result.push(',');\n                            }\n                            preComma = 1;\n                            result.push(encode(key) + ':' + encode(item));\n                        }\n                    }\n                }\n                result.push('}');\n                return result.join('');\n            }\n        }\n    };\n})();\n/*\n * Tangram\n * Copyright 2009 Baidu Inc. All rights reserved.\n * \n * path: baidu/json/encode.js\n * author: erik, cat\n * version: 1.3.4\n * date: 2010/12/23\n */\n\n\n\n/**\n * 将json对象序列化，为过时接口，今后会被baidu.json.stringify代替\n * @name baidu.json.encode\n * @function\n * @grammar baidu.json.encode(value)\n * @param {JSON} value 需要序列化的json对象\n * @meta out\n * @see baidu.json.decode,baidu.json.stringify\n *             \n * @returns {string} 序列化后的字符串\n */\nbaidu.json.encode = baidu.json.stringify;\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/wordimage/wordimage.html",
    "content": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\r\n        \"http://www.w3.org/TR/html4/loose.dtd\">\r\n<html>\r\n<head>\r\n    <title></title>\r\n    <meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"/>\r\n    <script type=\"text/javascript\" src=\"../internal.js\"></script>\r\n    <style type=\"text/css\">\r\n        .wrapper{width: 600px;padding: 10px;height: 352px;overflow: hidden;position: relative;border-bottom: 1px solid #d7d7d7}\r\n        .localPath input{float: left;width: 350px;line-height: 20px;height: 20px;}\r\n        #clipboard{float:left;width: 70px;height: 30px; }\r\n        .description{ color: #0066cc; margin-top: 2px; width: 450px; height: 45px;float: left;line-height: 22px}\r\n        #upload{width: 100px;height: 30px;float: right; margin:10px 2px 0 0;cursor: pointer;}\r\n        #msg{ width: 140px; height: 30px; line-height:25px;float: left;color: red}\r\n    </style>\r\n</head>\r\n<body>\r\n    <div class=\"wrapper\">\r\n        <div class=\"localPath\">\r\n            <input id=\"localPath\" type=\"text\" readonly />\r\n            <div id=\"clipboard\"></div>\r\n            <div id=\"msg\"></div>\r\n        </div>\r\n        <div id=\"flashContainer\"></div>\r\n        <div>\r\n            <div id=\"upload\" style=\"display: none\" ><img id=\"uploadBtn\"></div>\r\n            <div class=\"description\">\r\n                <span style=\"color: red\"><var id=\"lang_resave\"></var>: </span><var id=\"lang_step\"></var>\r\n            </div>\r\n          </div>\r\n    </div>\r\n    <script type=\"text/javascript\" src=\"tangram.js\"></script>\r\n    <script type=\"text/javascript\" src=\"wordimage.js\"></script>\r\n    <script type=\"text/javascript\">\r\n        editor.setOpt({\r\n            wordImageFieldName:\"upfile\",\r\n            compressSide:0,\r\n            maxImageSideLength:900\r\n        });\r\n\r\n            //全局变量\r\n        var imageUrls = [],          //用于保存从服务器返回的图片信息数组\r\n            selectedImageCount = 0,  //当前已选择的但未上传的图片数量\r\n            optImageUrl = editor.getActionUrl(editor.getOpt('imageActionName')),\r\n            optImageFieldName = editor.getOpt('imageFieldName'),\r\n            optImageCompressBorder = editor.getOpt('imageCompressEnable') ? editor.getOpt('imageCompressBorder'):null,\r\n            maxSize = editor.getOpt('imageMaxSize') / 1024,\r\n            extension = editor.getOpt('imageAllowFiles').join(';').replace(/\\./g, '*.');\r\n\r\n        /* 添加额外的GET参数 */\r\n        var params = utils.serializeParam(editor.queryCommandValue('serverparam')) || '',\r\n            urlWidthParams = optImageUrl + (optImageUrl.indexOf('?') == -1 ? '?':'&') + params;\r\n\r\n        utils.domReady(function(){\r\n            //创建Flash相关的参数集合\r\n            var flashOptions = {\r\n                container:\"flashContainer\",                                                    //flash容器id\r\n                url:urlWidthParams,                                           // 上传处理页面的url地址\r\n                ext:editor.queryCommandValue('serverParam') || {},                                 //可向服务器提交的自定义参数列表\r\n                fileType:'{\"description\":\"'+lang.fileType+'\", \"extension\":\"' + extension + '\"}',     //上传文件格式限制\r\n                flashUrl:'imageUploader.swf',                                                  //上传用的flash组件地址\r\n                width:600,          //flash的宽度\r\n                height:272,         //flash的高度\r\n                gridWidth:120,     // 每一个预览图片所占的宽度\r\n                gridHeight:120,    // 每一个预览图片所占的高度\r\n                picWidth:100,      // 单张预览图片的宽度\r\n                picHeight:100,     // 单张预览图片的高度\r\n                uploadDataFieldName: optImageFieldName,    // POST请求中图片数据的key\r\n                picDescFieldName:'pictitle',      // POST请求中图片描述的key\r\n                maxSize: maxSize,                         // 文件的最大体积,单位M\r\n                compressSize:1,                   // 上传前如果图片体积超过该值，会先压缩,单位M\r\n                maxNum:32,                         // 单次最大可上传多少个文件\r\n                compressSide: 0,                 //等比压缩的基准，0为按照最长边，1为按照宽度，2为按照高度\r\n                compressLength: optImageCompressBorder        //能接受的最大边长，超过该值Flash会自动等比压缩\r\n            };\r\n            //回调函数集合，支持传递函数名的字符串、函数句柄以及函数本身三种类型\r\n            var callbacks={\r\n                selectFileCallback: function(selectFiles){                // 选择文件的回调\r\n                    selectedImageCount += selectFiles.length;\r\n                    if(selectedImageCount) baidu.g(\"upload\").style.display = \"\";\r\n                    dialog.buttons[0].setDisabled(true); //初始化时置灰确定按钮\r\n                },\r\n                deleteFileCallback: function(delFiles){                 // 删除文件的回调\r\n                    selectedImageCount -= delFiles.length;\r\n                    if (!selectedImageCount) {\r\n                        baidu.g(\"upload\").style.display = \"none\";\r\n                        dialog.buttons[0].setDisabled(false);         //没有选择图片时重新点亮按钮\r\n                    }\r\n                },\r\n                uploadCompleteCallback: function(data){               // 单个文件上传完成的回调\r\n                    try{var info = eval(\"(\" + data.info + \")\");\r\n                    info && imageUrls.push(info);\r\n                    selectedImageCount--;\r\n                    }catch(e){}\r\n                },\r\n                uploadErrorCallback: function (data){         // 单个文件上传失败的回调,\r\n                    console && console.log(data);\r\n                },\r\n                allCompleteCallback: function(){              // 全部上传完成时的回调\r\n                    dialog.buttons[0].setDisabled(false);    //上传完毕后点亮按钮\r\n                }\r\n                //exceedFileCallback: 'exceedFileCallback',   // 文件超出限制的最大体积时的回调\r\n                //startUploadCallback: startUploadCallback    // 开始上传某个文件时的回调\r\n            };\r\n            wordImage.init(flashOptions,callbacks);\r\n        });\r\n\r\n    </script>\r\n\r\n</body>\r\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/dialogs/wordimage/wordimage.js",
    "content": "/**\r\n * Created by JetBrains PhpStorm.\r\n * User: taoqili\r\n * Date: 12-1-30\r\n * Time: 下午12:50\r\n * To change this template use File | Settings | File Templates.\r\n */\r\n\r\n\r\n\r\nvar wordImage = {};\r\n//(function(){\r\nvar g = baidu.g,\r\n\tflashObj,flashContainer;\r\n\r\nwordImage.init = function(opt, callbacks) {\r\n\tshowLocalPath(\"localPath\");\r\n\t//createCopyButton(\"clipboard\",\"localPath\");\r\n\tcreateFlashUploader(opt, callbacks);\r\n\taddUploadListener();\r\n\taddOkListener();\r\n};\r\n\r\nfunction hideFlash(){\r\n    flashObj = null;\r\n    flashContainer.innerHTML = \"\";\r\n}\r\nfunction addOkListener() {\r\n\tdialog.onok = function() {\r\n\t\tif (!imageUrls.length) return;\r\n\t\tvar urlPrefix = editor.getOpt('imageUrlPrefix'),\r\n            images = domUtils.getElementsByTagName(editor.document,\"img\");\r\n        editor.fireEvent('saveScene');\r\n\t\tfor (var i = 0,img; img = images[i++];) {\r\n\t\t\tvar src = img.getAttribute(\"word_img\");\r\n\t\t\tif (!src) continue;\r\n\t\t\tfor (var j = 0,url; url = imageUrls[j++];) {\r\n\t\t\t\tif (src.indexOf(url.original.replace(\" \",\"\")) != -1) {\r\n\t\t\t\t\timg.src = urlPrefix + url.url;\r\n\t\t\t\t\timg.setAttribute(\"_src\", urlPrefix + url.url);  //同时修改\"_src\"属性\r\n\t\t\t\t\timg.setAttribute(\"title\",url.title);\r\n                    domUtils.removeAttributes(img, [\"word_img\",\"style\",\"width\",\"height\"]);\r\n\t\t\t\t\teditor.fireEvent(\"selectionchange\");\r\n\t\t\t\t\tbreak;\r\n\t\t\t\t}\r\n\t\t\t}\r\n\t\t}\r\n        editor.fireEvent('saveScene');\r\n        hideFlash();\r\n\t};\r\n    dialog.oncancel = function(){\r\n        hideFlash();\r\n    }\r\n}\r\n\r\n/**\r\n * 绑定开始上传事件\r\n */\r\nfunction addUploadListener() {\r\n\tg(\"upload\").onclick = function () {\r\n\t\tflashObj.upload();\r\n\t\tthis.style.display = \"none\";\r\n\t};\r\n}\r\n\r\nfunction showLocalPath(id) {\r\n    //单张编辑\r\n    var img = editor.selection.getRange().getClosedNode();\r\n    var images = editor.execCommand('wordimage');\r\n    if(images.length==1 || img && img.tagName == 'IMG'){\r\n        g(id).value = images[0];\r\n        return;\r\n    }\r\n\tvar path = images[0];\r\n    var leftSlashIndex  = path.lastIndexOf(\"/\")||0,  //不同版本的doc和浏览器都可能影响到这个符号，故直接判断两种\r\n        rightSlashIndex = path.lastIndexOf(\"\\\\\")||0,\r\n        separater = leftSlashIndex > rightSlashIndex ? \"/\":\"\\\\\" ;\r\n\r\n\tpath = path.substring(0, path.lastIndexOf(separater)+1);\r\n\tg(id).value = path;\r\n}\r\n\r\nfunction createFlashUploader(opt, callbacks) {\r\n    //由于lang.flashI18n是静态属性，不可以直接进行修改，否则会影响到后续内容\r\n    var i18n = utils.extend({},lang.flashI18n);\r\n    //处理图片资源地址的编码，补全等问题\r\n    for(var i in i18n){\r\n        if(!(i in {\"lang\":1,\"uploadingTF\":1,\"imageTF\":1,\"textEncoding\":1}) && i18n[i]){\r\n            i18n[i] = encodeURIComponent(editor.options.langPath + editor.options.lang + \"/images/\" + i18n[i]);\r\n        }\r\n    }\r\n    opt = utils.extend(opt,i18n,false);\r\n\tvar option = {\r\n\t\tcreateOptions:{\r\n\t\t\tid:'flash',\r\n\t\t\turl:opt.flashUrl,\r\n\t\t\twidth:opt.width,\r\n\t\t\theight:opt.height,\r\n\t\t\terrorMessage:lang.flashError,\r\n\t\t\twmode:browser.safari ? 'transparent' : 'window',\r\n\t\t\tver:'10.0.0',\r\n\t\t\tvars:opt,\r\n\t\t\tcontainer:opt.container\r\n\t\t}\r\n\t};\r\n\r\n\toption = extendProperty(callbacks, option);\r\n\tflashObj = new baidu.flash.imageUploader(option);\r\n    flashContainer = $G(opt.container);\r\n}\r\n\r\nfunction extendProperty(fromObj, toObj) {\r\n\tfor (var i in fromObj) {\r\n\t\tif (!toObj[i]) {\r\n\t\t\ttoObj[i] = fromObj[i];\r\n\t\t}\r\n\t}\r\n\treturn toObj;\r\n}\r\n\r\n//})();\r\n\r\nfunction getPasteData(id) {\r\n\tbaidu.g(\"msg\").innerHTML = lang.copySuccess + \"</br>\";\r\n\tsetTimeout(function() {\r\n\t\tbaidu.g(\"msg\").innerHTML = \"\";\r\n\t}, 5000);\r\n\treturn baidu.g(id).value;\r\n}\r\n\r\nfunction createCopyButton(id, dataFrom) {\r\n\tbaidu.swf.create({\r\n\t\t\tid:\"copyFlash\",\r\n\t\t\turl:\"fClipboard_ueditor.swf\",\r\n\t\t\twidth:\"58\",\r\n\t\t\theight:\"25\",\r\n\t\t\terrorMessage:\"\",\r\n\t\t\tbgColor:\"#CBCBCB\",\r\n\t\t\twmode:\"transparent\",\r\n\t\t\tver:\"10.0.0\",\r\n\t\t\tvars:{\r\n\t\t\t\ttid:dataFrom\r\n\t\t\t}\r\n\t\t}, id\r\n\t);\r\n\r\n\tvar clipboard = baidu.swf.getMovie(\"copyFlash\");\r\n\tvar clipinterval = setInterval(function() {\r\n\t\tif (clipboard && clipboard.flashInit) {\r\n\t\t\tclearInterval(clipinterval);\r\n\t\t\tclipboard.setHandCursor(true);\r\n\t\t\tclipboard.setContentFuncName(\"getPasteData\");\r\n\t\t\t//clipboard.setMEFuncName(\"mouseEventHandler\");\r\n\t\t}\r\n\t}, 500);\r\n}\r\ncreateCopyButton(\"clipboard\", \"localPath\");"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/index.html",
    "content": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\"\r\n        \"http://www.w3.org/TR/html4/loose.dtd\">\r\n<html>\r\n<head>\r\n    <title>完整demo</title>\r\n    <meta http-equiv=\"Content-Type\" content=\"text/html;charset=utf-8\"/>\r\n    <script type=\"text/javascript\" charset=\"utf-8\" src=\"ueditor.config.js\"></script>\r\n    <script type=\"text/javascript\" charset=\"utf-8\" src=\"ueditor.all.min.js\"> </script>\r\n    <!--建议手动加在语言，避免在ie下有时因为加载语言失败导致编辑器加载失败-->\r\n    <!--这里加载的语言文件会覆盖你在配置项目里添加的语言类型，比如你在配置项目里配置的是英文，这里加载的中文，那最后就是中文-->\r\n    <script type=\"text/javascript\" charset=\"utf-8\" src=\"lang/zh-cn/zh-cn.js\"></script>\r\n\r\n    <style type=\"text/css\">\r\n        div{\r\n            width:100%;\r\n        }\r\n    </style>\r\n</head>\r\n<body>\r\n<div>\r\n    <h1>完整demo</h1>\r\n    <script id=\"editor\" type=\"text/plain\" style=\"width:1024px;height:500px;\"></script>\r\n</div>\r\n<div id=\"btns\">\r\n    <div>\r\n        <button onclick=\"getAllHtml()\">获得整个html的内容</button>\r\n        <button onclick=\"getContent()\">获得内容</button>\r\n        <button onclick=\"setContent()\">写入内容</button>\r\n        <button onclick=\"setContent(true)\">追加内容</button>\r\n        <button onclick=\"getContentTxt()\">获得纯文本</button>\r\n        <button onclick=\"getPlainTxt()\">获得带格式的纯文本</button>\r\n        <button onclick=\"hasContent()\">判断是否有内容</button>\r\n        <button onclick=\"setFocus()\">使编辑器获得焦点</button>\r\n        <button onmousedown=\"isFocus(event)\">编辑器是否获得焦点</button>\r\n        <button onmousedown=\"setblur(event)\" >编辑器失去焦点</button>\r\n\r\n    </div>\r\n    <div>\r\n        <button onclick=\"getText()\">获得当前选中的文本</button>\r\n        <button onclick=\"insertHtml()\">插入给定的内容</button>\r\n        <button id=\"enable\" onclick=\"setEnabled()\">可以编辑</button>\r\n        <button onclick=\"setDisabled()\">不可编辑</button>\r\n        <button onclick=\" UE.getEditor('editor').setHide()\">隐藏编辑器</button>\r\n        <button onclick=\" UE.getEditor('editor').setShow()\">显示编辑器</button>\r\n        <button onclick=\" UE.getEditor('editor').setHeight(300)\">设置高度为300默认关闭了自动长高</button>\r\n    </div>\r\n\r\n    <div>\r\n        <button onclick=\"getLocalData()\" >获取草稿箱内容</button>\r\n        <button onclick=\"clearLocalData()\" >清空草稿箱</button>\r\n    </div>\r\n\r\n</div>\r\n<div>\r\n    <button onclick=\"createEditor()\">\r\n    创建编辑器</button>\r\n    <button onclick=\"deleteEditor()\">\r\n    删除编辑器</button>\r\n</div>\r\n\r\n<script type=\"text/javascript\">\r\n\r\n    //实例化编辑器\r\n    //建议使用工厂方法getEditor创建和引用编辑器实例，如果在某个闭包下引用该编辑器，直接调用UE.getEditor('editor')就能拿到相关的实例\r\n    var ue = UE.getEditor('editor');\r\n\r\n\r\n    function isFocus(e){\r\n        alert(UE.getEditor('editor').isFocus());\r\n        UE.dom.domUtils.preventDefault(e)\r\n    }\r\n    function setblur(e){\r\n        UE.getEditor('editor').blur();\r\n        UE.dom.domUtils.preventDefault(e)\r\n    }\r\n    function insertHtml() {\r\n        var value = prompt('插入html代码', '');\r\n        UE.getEditor('editor').execCommand('insertHtml', value)\r\n    }\r\n    function createEditor() {\r\n        enableBtn();\r\n        UE.getEditor('editor');\r\n    }\r\n    function getAllHtml() {\r\n        alert(UE.getEditor('editor').getAllHtml())\r\n    }\r\n    function getContent() {\r\n        var arr = [];\r\n        arr.push(\"使用editor.getContent()方法可以获得编辑器的内容\");\r\n        arr.push(\"内容为：\");\r\n        arr.push(UE.getEditor('editor').getContent());\r\n        alert(arr.join(\"\\n\"));\r\n    }\r\n    function getPlainTxt() {\r\n        var arr = [];\r\n        arr.push(\"使用editor.getPlainTxt()方法可以获得编辑器的带格式的纯文本内容\");\r\n        arr.push(\"内容为：\");\r\n        arr.push(UE.getEditor('editor').getPlainTxt());\r\n        alert(arr.join('\\n'))\r\n    }\r\n    function setContent(isAppendTo) {\r\n        var arr = [];\r\n        arr.push(\"使用editor.setContent('欢迎使用ueditor')方法可以设置编辑器的内容\");\r\n        UE.getEditor('editor').setContent('欢迎使用ueditor', isAppendTo);\r\n        alert(arr.join(\"\\n\"));\r\n    }\r\n    function setDisabled() {\r\n        UE.getEditor('editor').setDisabled('fullscreen');\r\n        disableBtn(\"enable\");\r\n    }\r\n\r\n    function setEnabled() {\r\n        UE.getEditor('editor').setEnabled();\r\n        enableBtn();\r\n    }\r\n\r\n    function getText() {\r\n        //当你点击按钮时编辑区域已经失去了焦点，如果直接用getText将不会得到内容，所以要在选回来，然后取得内容\r\n        var range = UE.getEditor('editor').selection.getRange();\r\n        range.select();\r\n        var txt = UE.getEditor('editor').selection.getText();\r\n        alert(txt)\r\n    }\r\n\r\n    function getContentTxt() {\r\n        var arr = [];\r\n        arr.push(\"使用editor.getContentTxt()方法可以获得编辑器的纯文本内容\");\r\n        arr.push(\"编辑器的纯文本内容为：\");\r\n        arr.push(UE.getEditor('editor').getContentTxt());\r\n        alert(arr.join(\"\\n\"));\r\n    }\r\n    function hasContent() {\r\n        var arr = [];\r\n        arr.push(\"使用editor.hasContents()方法判断编辑器里是否有内容\");\r\n        arr.push(\"判断结果为：\");\r\n        arr.push(UE.getEditor('editor').hasContents());\r\n        alert(arr.join(\"\\n\"));\r\n    }\r\n    function setFocus() {\r\n        UE.getEditor('editor').focus();\r\n    }\r\n    function deleteEditor() {\r\n        disableBtn();\r\n        UE.getEditor('editor').destroy();\r\n    }\r\n    function disableBtn(str) {\r\n        var div = document.getElementById('btns');\r\n        var btns = UE.dom.domUtils.getElementsByTagName(div, \"button\");\r\n        for (var i = 0, btn; btn = btns[i++];) {\r\n            if (btn.id == str) {\r\n                UE.dom.domUtils.removeAttributes(btn, [\"disabled\"]);\r\n            } else {\r\n                btn.setAttribute(\"disabled\", \"true\");\r\n            }\r\n        }\r\n    }\r\n    function enableBtn() {\r\n        var div = document.getElementById('btns');\r\n        var btns = UE.dom.domUtils.getElementsByTagName(div, \"button\");\r\n        for (var i = 0, btn; btn = btns[i++];) {\r\n            UE.dom.domUtils.removeAttributes(btn, [\"disabled\"]);\r\n        }\r\n    }\r\n\r\n    function getLocalData () {\r\n        alert(UE.getEditor('editor').execCommand( \"getlocaldata\" ));\r\n    }\r\n\r\n    function clearLocalData () {\r\n        UE.getEditor('editor').execCommand( \"clearlocaldata\" );\r\n        alert(\"已清空草稿箱\")\r\n    }\r\n</script>\r\n</body>\r\n</html>"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/lang/en/en.js",
    "content": "/**\r\n * Created with JetBrains PhpStorm.\r\n * User: taoqili\r\n * Date: 12-6-12\r\n * Time: 下午6:57\r\n * To change this template use File | Settings | File Templates.\r\n */\r\nUE.I18N['en'] = {\r\n    'labelMap':{\r\n        'anchor':'Anchor', 'undo':'Undo', 'redo':'Redo', 'bold':'Bold', 'indent':'Indent', 'snapscreen':'SnapScreen',\r\n        'italic':'Italic', 'underline':'Underline', 'strikethrough':'Strikethrough', 'subscript':'SubScript','fontborder':'text border',\r\n        'superscript':'SuperScript', 'formatmatch':'Format Match', 'source':'Source', 'blockquote':'BlockQuote',\r\n        'pasteplain':'PastePlain', 'selectall':'SelectAll', 'print':'Print', 'preview':'Preview',\r\n        'horizontal':'Horizontal', 'removeformat':'RemoveFormat', 'time':'Time', 'date':'Date',\r\n        'unlink':'Unlink', 'insertrow':'InsertRow', 'insertcol':'InsertCol', 'mergeright':'MergeRight', 'mergedown':'MergeDown',\r\n        'deleterow':'DeleteRow', 'deletecol':'DeleteCol', 'splittorows':'SplitToRows','insertcode':'insert code',\r\n        'splittocols':'SplitToCols', 'splittocells':'SplitToCells','deletecaption':'DeleteCaption','inserttitle':'InsertTitle',\r\n        'mergecells':'MergeCells', 'deletetable':'DeleteTable', 'cleardoc':'Clear', 'insertparagraphbeforetable':\"InsertParagraphBeforeTable\",\r\n        'fontfamily':'FontFamily', 'fontsize':'FontSize', 'paragraph':'Paragraph','simpleupload':'Single Image','insertimage':'Multi Image','edittable':'Edit Table', 'edittd':'Edit Td','link':'Link',\r\n        'emotion':'Emotion', 'spechars':'Spechars', 'searchreplace':'SearchReplace', 'map':'BaiduMap', 'gmap':'GoogleMap',\r\n        'insertvideo':'Video', 'help':'Help', 'justifyleft':'JustifyLeft', 'justifyright':'JustifyRight', 'justifycenter':'JustifyCenter',\r\n        'justifyjustify':'Justify', 'forecolor':'FontColor', 'backcolor':'BackColor', 'insertorderedlist':'OL',\r\n        'insertunorderedlist':'UL', 'fullscreen':'FullScreen', 'directionalityltr':'EnterFromLeft', 'directionalityrtl':'EnterFromRight',\r\n        'rowspacingtop':'RowSpacingTop', 'rowspacingbottom':'RowSpacingBottom', 'pagebreak':'PageBreak', 'insertframe':'Iframe', 'imagenone':'Default',\r\n        'imageleft':'ImageLeft', 'imageright':'ImageRight', 'attachment':'Attachment', 'imagecenter':'ImageCenter', 'wordimage':'WordImage',\r\n        'lineheight':'LineHeight','edittip':'EditTip','customstyle':'CustomStyle', 'scrawl':'Scrawl', 'autotypeset':'AutoTypeset',\r\n        'webapp':'WebAPP', 'touppercase':'UpperCase', 'tolowercase':'LowerCase','template':'Template','background':'Background','inserttable':'InsertTable',\r\n        'music':'Music', 'charts': 'charts','drafts': 'Load from Drafts'\r\n    },\r\n    'insertorderedlist':{\r\n        'num':'1,2,3...',\r\n        'num1':'1),2),3)...',\r\n        'num2':'(1),(2),(3)...',\r\n        'cn':'一,二,三....',\r\n        'cn1':'一),二),三)....',\r\n        'cn2':'(一),(二),(三)....',\r\n        'decimal':'1,2,3...',\r\n        'lower-alpha':'a,b,c...',\r\n        'lower-roman':'i,ii,iii...',\r\n        'upper-alpha':'A,B,C...',\r\n        'upper-roman':'I,II,III...'\r\n    },\r\n    'insertunorderedlist':{\r\n        'circle':'○ Circle',\r\n        'disc':'● Circle dot',\r\n        'square':'■ Rectangle ',\r\n        'dash' :'－ Dash',\r\n        'dot' : '。dot'\r\n    },\r\n    'paragraph':{'p':'Paragraph', 'h1':'Title 1', 'h2':'Title 2', 'h3':'Title 3', 'h4':'Title 4', 'h5':'Title 5', 'h6':'Title 6'},\r\n    'fontfamily':{\r\n        'songti':'Sim Sun',\r\n        'kaiti':'Sim Kai',\r\n        'heiti':'Sim Hei',\r\n        'lishu':'Sim Li',\r\n        'yahei': 'Microsoft YaHei',\r\n        'andaleMono':'Andale Mono',\r\n        'arial': 'Arial',\r\n        'arialBlack':'Arial Black',\r\n        'comicSansMs':'Comic Sans MS',\r\n        'impact':'Impact',\r\n        'timesNewRoman':'Times New Roman'\r\n    },\r\n    'customstyle':{\r\n        'tc':'Title center',\r\n        'tl':'Title left',\r\n        'im':'Important',\r\n        'hi':'Highlight'\r\n    },\r\n    'autoupload': {\r\n        'exceedSizeError': 'File Size Exceed',\r\n        'exceedTypeError': 'File Type Not Allow',\r\n        'jsonEncodeError': 'Server Return Format Error',\r\n        'loading':\"loading...\",\r\n        'loadError':\"load error\",\r\n        'errorLoadConfig': 'Server config not loaded, upload can not work.',\r\n    },\r\n    'simpleupload':{\r\n        'exceedSizeError': 'File Size Exceed',\r\n        'exceedTypeError': 'File Type Not Allow',\r\n        'jsonEncodeError': 'Server Return Format Error',\r\n        'loading':\"loading...\",\r\n        'loadError':\"load error\",\r\n        'errorLoadConfig': 'Server config not loaded, upload can not work.',\r\n    },\r\n    'elementPathTip':\"Path\",\r\n    'wordCountTip':\"Word Count\",\r\n    'wordCountMsg':'{#count} characters entered,{#leave} left. ',\r\n    'wordOverFlowMsg':'<span style=\"color:red;\">The number of characters has exceeded allowable maximum values, the server may refuse to save!</span>',\r\n    'ok':\"OK\",\r\n    'cancel':\"Cancel\",\r\n    'closeDialog':\"closeDialog\",\r\n    'tableDrag':\"You must import the file uiUtils.js before drag! \",\r\n    'autofloatMsg':\"The plugin AutoFloat depends on EditorUI!\",\r\n    'loadconfigError': 'Get server config error.',\r\n    'loadconfigFormatError': 'Server config format error.',\r\n    'loadconfigHttpError': 'Get server config http error.',\r\n    'snapScreen_plugin':{\r\n        'browserMsg':\"Only IE supported!\",\r\n        'callBackErrorMsg':\"The callback data is wrong,please check the config!\",\r\n        'uploadErrorMsg':\"Upload error,please check your server environment! \"\r\n    },\r\n    'insertcode':{\r\n        'as3':'ActionScript 3',\r\n        'bash':'Bash/Shell',\r\n        'cpp':'C/C++',\r\n        'css':'CSS',\r\n        'cf':'ColdFusion',\r\n        'c#':'C#',\r\n        'delphi':'Delphi',\r\n        'diff':'Diff',\r\n        'erlang':'Erlang',\r\n        'groovy':'Groovy',\r\n        'html':'HTML',\r\n        'java':'Java',\r\n        'jfx':'JavaFX',\r\n        'js':'JavaScript',\r\n        'pl':'Perl',\r\n        'php':'PHP',\r\n        'plain':'Plain Text',\r\n        'ps':'PowerShell',\r\n        'python':'Python',\r\n        'ruby':'Ruby',\r\n        'scala':'Scala',\r\n        'sql':'SQL',\r\n        'vb':'Visual Basic',\r\n        'xml':'XML'\r\n    },\r\n    'confirmClear':\"Do you confirm to clear the Document?\",\r\n    'contextMenu':{\r\n        'delete':\"Delete\",\r\n        'selectall':\"Select all\",\r\n        'deletecode':\"Delete Code\",\r\n        'cleardoc':\"Clear Document\",\r\n        'confirmclear':\"Do you confirm to clear the Document?\",\r\n        'unlink':\"Unlink\",\r\n        'paragraph':\"Paragraph\",\r\n        'edittable':\"Table property\",\r\n        'aligncell':'Align cell',\r\n        'aligntable':'Table alignment',\r\n        'tableleft':'Left float',\r\n        'tablecenter':'Center',\r\n        'tableright':'Right float',\r\n        'aligntd':'Cell alignment',\r\n        'edittd':\"Cell property\",\r\n        'setbordervisible':'set table edge visible',\r\n        'table':\"Table\",\r\n        'justifyleft':'Justify Left',\r\n        'justifyright':'Justify Right',\r\n        'justifycenter':'Justify Center',\r\n        'justifyjustify':'Default',\r\n        'deletetable':\"Delete table\",\r\n        'insertparagraphbefore':\"InsertedBeforeLine\",\r\n        'insertparagraphafter':'InsertedAfterLine',\r\n        'inserttable':'Insert table',\r\n        'insertcaption':'Insert caption',\r\n        'deletecaption':'Delete Caption',\r\n        'inserttitle':'Insert Title',\r\n        'deletetitle':'Delete Title',\r\n        'inserttitlecol':'Insert Title Col',\r\n        'deletetitlecol':'Delete Title Col',\r\n        'averageDiseRow':'AverageDise Row',\r\n        'averageDisCol':'AverageDis Col',\r\n        'deleterow':\"Delete row\",\r\n        'deletecol':\"Delete col\",\r\n        'insertrow':\"Insert row\",\r\n        'insertcol':\"Insert col\",\r\n        'insertrownext':'Insert Row Next',\r\n        'insertcolnext':'Insert Col Next',\r\n        'mergeright':\"Merge right\",\r\n        'mergeleft':\"Merge left\",\r\n        'mergedown':\"Merge down\",\r\n        'mergecells':\"Merge cells\",\r\n        'splittocells':\"Split to cells\",\r\n        'splittocols':\"Split to Cols\",\r\n        'splittorows':\"Split to Rows\",\r\n        'tablesort':'Table sorting',\r\n        'enablesort':'Sorting Enable',\r\n        'disablesort':'Sorting Disable',\r\n        'reversecurrent':'Reverse current',\r\n        'orderbyasc':'Order By ASCII',\r\n        'reversebyasc':'Reverse By ASCII',\r\n        'orderbynum':'Order By Num',\r\n        'reversebynum':'Reverse By Num',\r\n        'borderbk':'Border shading',\r\n        'setcolor':'interlaced color',\r\n        'unsetcolor':'Cancel interlacedcolor',\r\n        'setbackground':'Background interlaced',\r\n        'unsetbackground':'Cancel Bk interlaced',\r\n        'redandblue':'Blue and red',\r\n        'threecolorgradient':'Three-color gradient',\r\n        'copy':\"Copy(Ctrl + c)\",\r\n        'copymsg':\"Browser does not support. Please use 'Ctrl + c' instead!\",\r\n        'paste':\"Paste(Ctrl + v)\",\r\n        'pastemsg':\"Browser does not support. Please use 'Ctrl + v' instead!\"\r\n    },\r\n    'copymsg': \"Browser does not support. Please use 'Ctrl + c' instead!\",\r\n    'pastemsg': \"Browser does not support. Please use 'Ctrl + v' instead!\",\r\n    'anthorMsg':\"Link\",\r\n    'clearColor':'Clear',\r\n    'standardColor':'Standard color',\r\n    'themeColor':'Theme color',\r\n    'property':'Property',\r\n    'default':'Default',\r\n    'modify':'Modify',\r\n    'justifyleft':'Justify Left',\r\n    'justifyright':'Justify Right',\r\n    'justifycenter':'Justify Center',\r\n    'justify':'Default',\r\n    'clear':'Clear',\r\n    'anchorMsg':'Anchor',\r\n    'delete':'Delete',\r\n    'clickToUpload':\"Click to upload\",\r\n    'unset':'Language hasn\\'t been set!',\r\n    't_row':'row',\r\n    't_col':'col',\r\n    'pasteOpt':'Paste Option',\r\n    'pasteSourceFormat':\"Keep Source Formatting\",\r\n    'tagFormat':'Keep tag',\r\n    'pasteTextFormat':'Keep Text only',\r\n    'more':'More',\r\n    'autoTypeSet':{\r\n        'mergeLine':\"Merge empty line\",\r\n        'delLine':\"Del empty line\",\r\n        'removeFormat':\"Remove format\",\r\n        'indent':\"Indent\",\r\n        'alignment':\"Alignment\",\r\n        'imageFloat':\"Image float\",\r\n        'removeFontsize':\"Remove font size\",\r\n        'removeFontFamily':\"Remove fontFamily\",\r\n        'removeHtml':\"Remove redundant HTML code\",\r\n        'pasteFilter':\"Paste filter\",\r\n        'run':\"Done\",\r\n        'symbol':'Symbol Conversion',\r\n        'bdc2sb':'Full-width to Half-width',\r\n        'tobdc':'Half-width to Full-width'\r\n    },\r\n\r\n    'background':{\r\n        'static':{\r\n            'lang_background_normal':'Normal',\r\n            'lang_background_local':'Online',\r\n            'lang_background_set':'Background Set',\r\n            'lang_background_none':'No Background',\r\n            'lang_background_colored':'Colored Background',\r\n            'lang_background_color':'Color Set',\r\n            'lang_background_netimg':'Net-Image',\r\n            'lang_background_align':'Align Type',\r\n            'lang_background_position':'Position',\r\n            'repeatType':{'options':[\"Center\", \"Repeat-x\", \"Repeat-y\", \"Tile\",\"Custom\"]}\r\n        },\r\n        'noUploadImage':\"No pictures has been uploaded！\",\r\n        'toggleSelect':'Change the active state by click!\\n Image Size: '\r\n    },\r\n    //===============dialog i18N=======================\r\n    'insertimage':{\r\n        'static':{\r\n            'lang_tab_remote':\"Insert\",\r\n            'lang_tab_upload':\"Local\",\r\n            'lang_tab_online':\"Manager\",\r\n            'lang_tab_search':\"Search\",\r\n            'lang_input_url':\"Address:\",\r\n            'lang_input_size':\"Size:\",\r\n            'lang_input_width':\"Width\",\r\n            'lang_input_height':\"Height\",\r\n            'lang_input_border':\"Border:\",\r\n            'lang_input_vhspace':\"Margins:\",\r\n            'lang_input_title':\"Title:\",\r\n            'lang_input_align':'Image Float Style:',\r\n            'lang_imgLoading':\"Loading...\",\r\n            'lang_start_upload':\"Start Upload\",\r\n            'lock':{'title':\"Lock rate\"},\r\n            'searchType':{'title':\"ImageType\", 'options':[\"News\", \"Wallpaper\", \"emotions\", \"photo\"]},\r\n            'searchTxt':{'value':\"Enter the search keyword!\"},\r\n            'searchBtn':{'value':\"Search\"},\r\n            'searchReset':{'value':\"Clear\"},\r\n            'noneAlign':{'title':'None Float'},\r\n            'leftAlign':{'title':'Left Float'},\r\n            'rightAlign':{'title':'Right Float'},\r\n            'centerAlign':{'title':'Center In A Line'}\r\n        },\r\n        'uploadSelectFile':'Select File',\r\n        'uploadAddFile':'Add File',\r\n        'uploadStart':'Start Upload',\r\n        'uploadPause':'Pause Upload',\r\n        'uploadContinue':'Continue Upload',\r\n        'uploadRetry':'Retry Upload',\r\n        'uploadDelete':'Delete',\r\n        'uploadTurnLeft':'Turn Left',\r\n        'uploadTurnRight':'Turn Right',\r\n        'uploadPreview':'Doing Preview',\r\n        'uploadNoPreview':'Can Not Preview',\r\n        'updateStatusReady': 'Selected _ pictures, total _KB.',\r\n        'updateStatusConfirm': '_ uploaded successfully and _ upload failed',\r\n        'updateStatusFinish': 'Total _ pictures (_KB), _  uploaded successfully',\r\n        'updateStatusError': ' and _ upload failed',\r\n        'errorNotSupport': 'WebUploader does not support the browser you are using. Please upgrade your browser or flash player',\r\n        'errorLoadConfig': 'Server config not loaded, upload can not work.',\r\n        'errorExceedSize':'File Size Exceed',\r\n        'errorFileType':'File Type Not Allow',\r\n        'errorInterrupt':'File Upload Interrupted',\r\n        'errorUploadRetry':'Upload Error, Please Retry.',\r\n        'errorHttp':'Http Error',\r\n        'errorServerUpload':'Server Result Error.',\r\n        'remoteLockError':\"Cannot Lock the Proportion between width and height\",\r\n        'numError':\"Please enter the correct Num. e.g 123,400\",\r\n        'imageUrlError':\"The image format may be wrong!\",\r\n        'imageLoadError':\"Error,please check the network or URL！\",\r\n        'searchRemind':\"Enter the search keyword!\",\r\n        'searchLoading':\"Image is loading,please wait...\",\r\n        'searchRetry':\" Sorry,can't find the image,please try again!\"\r\n    },\r\n    'attachment':{\r\n        'static':{\r\n            'lang_tab_upload': 'Upload',\r\n            'lang_tab_online': 'Online',\r\n            'lang_start_upload':\"Start upload\",\r\n            'lang_drop_remind':\"You can drop files here, a single maximum of 300 files\"\r\n        },\r\n        'uploadSelectFile':'Select File',\r\n        'uploadAddFile':'Add File',\r\n        'uploadStart':'Start Upload',\r\n        'uploadPause':'Pause Upload',\r\n        'uploadContinue':'Continue Upload',\r\n        'uploadRetry':'Retry Upload',\r\n        'uploadDelete':'Delete',\r\n        'uploadTurnLeft':'Turn Left',\r\n        'uploadTurnRight':'Turn Right',\r\n        'uploadPreview':'Doing Preview',\r\n        'updateStatusReady': 'Selected _ files, total _KB.',\r\n        'updateStatusConfirm': '_ uploaded successfully and _ upload failed',\r\n        'updateStatusFinish': 'Total _ files (_KB), _  uploaded successfully',\r\n        'updateStatusError': ' and _ upload failed',\r\n        'errorNotSupport': 'WebUploader does not support the browser you are using. Please upgrade your browser or flash player',\r\n        'errorLoadConfig': 'Server config not loaded, upload can not work.',\r\n        'errorExceedSize':'File Size Exceed',\r\n        'errorFileType':'File Type Not Allow',\r\n        'errorInterrupt':'File Upload Interrupted',\r\n        'errorUploadRetry':'Upload Error, Please Retry.',\r\n        'errorHttp':'Http Error',\r\n        'errorServerUpload':'Server Result Error.'\r\n    },\r\n\r\n    'insertvideo':{\r\n        'static':{\r\n            'lang_tab_insertV':\"Video\",\r\n            'lang_tab_searchV':\"Search\",\r\n            'lang_tab_uploadV':\"Upload\",\r\n            'lang_video_url':\" URL \",\r\n            'lang_video_size':\"Video Size\",\r\n            'lang_videoW':\"Width\",\r\n            'lang_videoH':\"Height\",\r\n            'lang_alignment':\"Alignment\",\r\n            'videoSearchTxt':{'value':\"Enter the search keyword!\"},\r\n            'videoType':{'options':[\"All\", \"Hot\", \"Entertainment\", \"Funny\", \"Sports\", \"Science\", \"variety\"]},\r\n            'videoSearchBtn':{'value':\"Search in Baidu\"},\r\n            'videoSearchReset':{'value':\"Clear result\"},\r\n\r\n            'lang_input_fileStatus':' No file uploaded!',\r\n            'startUpload':{'style':\"background:url(upload.png) no-repeat;\"},\r\n\r\n            'lang_upload_size':\"Video Size\",\r\n            'lang_upload_width':\"Width\",\r\n            'lang_upload_height':\"Height\",\r\n            'lang_upload_alignment':\"Alignment\",\r\n            'lang_format_advice':\"Recommends mp4 format.\"\r\n        },\r\n        'numError':\"Please enter the correct Num. e.g 123,400\",\r\n        'floatLeft':\"Float left\",\r\n        'floatRight':\"Float right\",\r\n        'default':\"Default\",\r\n        'block':\"Display in block\",\r\n        'urlError':\"The video url format may be wrong!\",\r\n        'loading':\" &nbsp;The video is loading, please wait…\",\r\n        'clickToSelect':\"Click to select\",\r\n        'goToSource':'Visit source video ',\r\n        'noVideo':\" &nbsp; &nbsp;Sorry,can't find the video,please try again!\",\r\n\r\n        'browseFiles':'Open files',\r\n        'uploadSuccess':'Upload Successful!',\r\n        'delSuccessFile':'Remove from the success of the queue',\r\n        'delFailSaveFile':'Remove the save failed file',\r\n        'statusPrompt':' file(s) uploaded! ',\r\n        'flashVersionError':'The current Flash version is too low, please update FlashPlayer,then try again!',\r\n        'flashLoadingError':'The Flash failed loading! Please check the path or network state',\r\n        'fileUploadReady':'Wait for uploading...',\r\n        'delUploadQueue':'Remove from the uploading queue ',\r\n        'limitPrompt1':'Can not choose more than single',\r\n        'limitPrompt2':'file(s)！Please choose again！',\r\n        'delFailFile':'Remove failure file',\r\n        'fileSizeLimit':'File size exceeds the limit！',\r\n        'emptyFile':'Can not upload an empty file！',\r\n        'fileTypeError':'File type error！',\r\n        'unknownError':'Unknown error！',\r\n        'fileUploading':'Uploading,please wait...',\r\n        'cancelUpload':'Cancel upload',\r\n        'netError':'Network error',\r\n        'failUpload':'Upload failed',\r\n        'serverIOError':'Server IO error！',\r\n        'noAuthority':'No Permission！',\r\n        'fileNumLimit':'Upload limit to the number',\r\n        'failCheck':'Authentication fails, the upload is skipped!',\r\n        'fileCanceling':'Cancel, please wait...',\r\n        'stopUploading':'Upload has stopped...',\r\n\r\n        'uploadSelectFile':'Select File',\r\n        'uploadAddFile':'Add File',\r\n        'uploadStart':'Start Upload',\r\n        'uploadPause':'Pause Upload',\r\n        'uploadContinue':'Continue Upload',\r\n        'uploadRetry':'Retry Upload',\r\n        'uploadDelete':'Delete',\r\n        'uploadTurnLeft':'Turn Left',\r\n        'uploadTurnRight':'Turn Right',\r\n        'uploadPreview':'Doing Preview',\r\n        'updateStatusReady': 'Selected _ files, total _KB.',\r\n        'updateStatusConfirm': '_ uploaded successfully and _ upload failed',\r\n        'updateStatusFinish': 'Total _ files (_KB), _  uploaded successfully',\r\n        'updateStatusError': ' and _ upload failed',\r\n        'errorNotSupport': 'WebUploader does not support the browser you are using. Please upgrade your browser or flash player',\r\n        'errorLoadConfig': 'Server config not loaded, upload can not work.',\r\n        'errorExceedSize':'File Size Exceed',\r\n        'errorFileType':'File Type Not Allow',\r\n        'errorInterrupt':'File Upload Interrupted',\r\n        'errorUploadRetry':'Upload Error, Please Retry.',\r\n        'errorHttp':'Http Error',\r\n        'errorServerUpload':'Server Result Error.'\r\n    },\r\n    'webapp':{\r\n        'tip1':\"This function provided by Baidu APP,please apply for baidu APPKey webmaster first!\",\r\n        'tip2':\"And then open the file ueditor.config.js to set it! \",\r\n        'applyFor':\"APPLY FOR\",\r\n        'anthorApi':\"Baidu API\"\r\n    },\r\n    'template':{\r\n        'static':{\r\n            'lang_template_bkcolor':'Background Color',\r\n            'lang_template_clear' : 'Keep Content',\r\n            'lang_template_select':'Select Template'\r\n        },\r\n        'blank':\"Blank\",\r\n        'blog':\"Blog\",\r\n        'resume':\"Resume\",\r\n        'richText':\"Rich Text\",\r\n        'scrPapers':\"Scientific Papers\"\r\n    },\r\n    scrawl:{\r\n        'static':{\r\n            'lang_input_previousStep':\"Previous\",\r\n            'lang_input_nextsStep':\"Next\",\r\n            'lang_input_clear':'Clear',\r\n            'lang_input_addPic':'AddImage',\r\n            'lang_input_ScalePic':'ScaleImage',\r\n            'lang_input_removePic':'RemoveImage',\r\n            'J_imgTxt':{title:'Add background image'}\r\n        },\r\n        'noScarwl':\"No paint, a white paper...\",\r\n        'scrawlUpLoading':\"Image is uploading, please wait...\",\r\n        'continueBtn':\"Try again\",\r\n        'imageError':\"Image failed to load!\",\r\n        'backgroundUploading':'Image is uploading,please wait...'\r\n    },\r\n    'music':{\r\n        'static':{\r\n            'lang_input_tips':\"Input singer/song/album, search you interested in music!\",\r\n            'J_searchBtn':{value:'Search songs'}\r\n        },\r\n        'emptyTxt':'Not search to the relevant music results, please change a keyword try.',\r\n        'chapter':'Songs',\r\n        'singer':'Singer',\r\n        'special':'Album',\r\n        'listenTest':'Audition'\r\n    },\r\n    anchor:{\r\n        'static':{\r\n            'lang_input_anchorName':'Anchor Name:'\r\n        }\r\n    },\r\n    'charts':{\r\n        'static':{\r\n            'lang_data_source':'Data source:',\r\n            'lang_chart_format': 'Chart format:',\r\n            'lang_data_align': 'Align',\r\n            'lang_chart_align_same': 'Consistent with the X-axis Y-axis',\r\n            'lang_chart_align_reverse': 'X-axis Y-axis opposite',\r\n            'lang_chart_title': 'Title',\r\n            'lang_chart_main_title': 'main title:',\r\n            'lang_chart_sub_title': 'sub title:',\r\n            'lang_chart_x_title': 'X-axis title:',\r\n            'lang_chart_y_title': 'Y-axis title:',\r\n            'lang_chart_tip': 'Prompt',\r\n            'lang_cahrt_tip_prefix': 'prefix:',\r\n            'lang_cahrt_tip_description': '仅饼图有效， 当鼠标移动到饼图中相应的块上时，提示框内的文字的前缀',\r\n            'lang_chart_data_unit': 'Unit',\r\n            'lang_chart_data_unit_title': 'unit:',\r\n            'lang_chart_data_unit_description': '显示在每个数据点上的数据的单位， 比如： 温度的单位 ℃',\r\n            'lang_chart_type': 'Chart type:',\r\n            'lang_prev_btn': 'Previous',\r\n            'lang_next_btn': 'Next'\r\n        }\r\n    },\r\n    emotion:{\r\n        'static':{\r\n            'lang_input_choice':'Choice',\r\n            'lang_input_Tuzki':'Tuzki',\r\n            'lang_input_lvdouwa':'LvDouWa',\r\n            'lang_input_BOBO':'BOBO',\r\n            'lang_input_babyCat':'BabyCat',\r\n            'lang_input_bubble':'Bubble',\r\n            'lang_input_youa':'YouA'\r\n        }\r\n    },\r\n    gmap:{\r\n        'static':{\r\n            'lang_input_address':'Address:',\r\n            'lang_input_search':'Search',\r\n            'address':{value:\"Beijing\"}\r\n        },\r\n        searchError:'Unable to locate the address!'\r\n    },\r\n    help:{\r\n        'static':{\r\n            'lang_input_about':'About',\r\n            'lang_input_shortcuts':'Shortcuts',\r\n            'lang_input_introduction':\"UEditor is developed by Baidu Co.ltd.  It is lightweight, customizable , focusing on user experience and etc. , UEditor is based on open source BSD license , allowing free use and redistribution.\",\r\n            'lang_Txt_shortcuts':'Shortcuts',\r\n            'lang_Txt_func':'Function',\r\n            'lang_Txt_bold':'Bold',\r\n            'lang_Txt_copy':'Copy',\r\n            'lang_Txt_cut':'Cut',\r\n            'lang_Txt_Paste':'Paste',\r\n            'lang_Txt_undo':'Undo',\r\n            'lang_Txt_redo':'Redo',\r\n            'lang_Txt_italic':'Italic',\r\n            'lang_Txt_underline':'Underline',\r\n            'lang_Txt_selectAll':'Select All',\r\n            'lang_Txt_visualEnter':'Submit',\r\n            'lang_Txt_fullscreen':'Fullscreen'\r\n        }\r\n    },\r\n    insertframe:{\r\n        'static':{\r\n            'lang_input_address':'Address：',\r\n            'lang_input_width':'Width：',\r\n            'lang_input_height':'height：',\r\n            'lang_input_isScroll':'Enable scrollbars：',\r\n            'lang_input_frameborder':'Show frame border：',\r\n            'lang_input_alignMode':'Alignment：',\r\n            'align':{title:\"Alignment\", options:[\"Default\", \"Left\", \"Right\", \"Center\"]}\r\n        },\r\n        'enterAddress':'Please enter an address!'\r\n    },\r\n    link:{\r\n        'static':{\r\n            'lang_input_text':'Text：',\r\n            'lang_input_url':'URL：',\r\n            'lang_input_title':'Title：',\r\n            'lang_input_target':'open in new window：'\r\n        },\r\n        'validLink':'Supports only effective when a link is selected',\r\n        'httpPrompt':'The hyperlink you enter should start with \"http|https|ftp://\"!'\r\n    },\r\n    map:{\r\n        'static':{\r\n            lang_city:\"City\",\r\n            lang_address:\"Address\",\r\n            city:{value:\"Beijing\"},\r\n            lang_search:\"Search\",\r\n            lang_dynamicmap:\"Dynamic map\"\r\n        },\r\n        cityMsg:\"Please enter the city name!\",\r\n        errorMsg:\"Can't find the place!\"\r\n    },\r\n    searchreplace:{\r\n        'static':{\r\n            lang_tab_search:\"Search\",\r\n            lang_tab_replace:\"Replace\",\r\n            lang_search1:\"Search\",\r\n            lang_search2:\"Search\",\r\n            lang_replace:\"Replace\",\r\n            lang_searchReg:'Support regular expression ,which starts and ends with a slash ,for example \"/expression/\"',\r\n            lang_searchReg1:'Support regular expression ,which starts and ends with a slash ,for example \"/expression/\"',\r\n            lang_case_sensitive1:\"Case sense\",\r\n            lang_case_sensitive2:\"Case sense\",\r\n            nextFindBtn:{value:\"Next\"},\r\n            preFindBtn:{value:\"Preview\"},\r\n            nextReplaceBtn:{value:\"Next\"},\r\n            preReplaceBtn:{value:\"Preview\"},\r\n            repalceBtn:{value:\"Replace\"},\r\n            repalceAllBtn:{value:\"Replace all\"}\r\n        },\r\n        getEnd:\"Has the search to the bottom!\",\r\n        getStart:\"Has the search to the top!\",\r\n        countMsg:\"Altogether replaced {#count} character(s)!\"\r\n    },\r\n    snapscreen:{\r\n        'static':{\r\n            lang_showMsg:\"You should install the UEditor screenshots program first!\",\r\n            lang_download:\"Download!\",\r\n            lang_step1:\"Step1:Download the program and then run it\",\r\n            lang_step2:\"Step2:After complete install,try to click the button again\"\r\n        }\r\n    },\r\n    spechars:{\r\n        'static':{},\r\n        tsfh:\"Special\",\r\n        lmsz:\"Roman\",\r\n        szfh:\"Numeral\",\r\n        rwfh:\"Japanese\",\r\n        xlzm:\"The Greek\",\r\n        ewzm:\"Russian\",\r\n        pyzm:\"Phonetic\",\r\n        yyyb:\"English\",\r\n        zyzf:\"Others\"\r\n    },\r\n    'edittable':{\r\n        'static':{\r\n            'lang_tableStyle':'Table style',\r\n            'lang_insertCaption':'Add table header row',\r\n            'lang_insertTitle':'Add table title row',\r\n            'lang_insertTitleCol':'Add table title col',\r\n            'lang_tableSize':'Automatically adjust table size',\r\n            'lang_autoSizeContent':'Adaptive by form text',\r\n            'lang_orderbycontent':\"Table of contents sortable\",\r\n            'lang_autoSizePage':'Page width adaptive',\r\n            'lang_example':'Example',\r\n            'lang_borderStyle':'Table Border',\r\n            'lang_color':'Color:'\r\n        },\r\n        captionName:'Caption',\r\n        titleName:'Title',\r\n        cellsName:'text',\r\n        errorMsg:'There are merged cells, can not sort.'\r\n    },\r\n    'edittip':{\r\n        'static':{\r\n            lang_delRow:'Delete entire row',\r\n            lang_delCol:'Delete entire col'\r\n        }\r\n    },\r\n    'edittd':{\r\n        'static':{\r\n            lang_tdBkColor:'Background Color:'\r\n        }\r\n    },\r\n    'formula':{\r\n        'static':{\r\n        }\r\n    },\r\n    wordimage:{\r\n        'static':{\r\n            lang_resave:\"The re-save step\",\r\n            uploadBtn:{src:\"upload.png\", alt:\"Upload\"},\r\n            clipboard:{style:\"background: url(copy.png) -153px -1px no-repeat;\"},\r\n            lang_step:\" 1. Click top button to copy the url and then open the dialog to paste it. 2. Open after choose photos uploaded process.\"\r\n        },\r\n        fileType:\"Image\",\r\n        flashError:\"Flash initialization failed!\",\r\n        netError:\"Network error! Please try again!\",\r\n        copySuccess:\"URL has been copied!\",\r\n\r\n        'flashI18n':{\r\n            lang:encodeURI( '{\"UploadingState\":\"totalNum: ${a},uploadComplete: ${b}\", \"BeforeUpload\":\"waitingNum: ${a}\", \"ExceedSize\":\"Size exceed${a}\", \"ErrorInPreview\":\"Preview failed\", \"DefaultDescription\":\"Description\", \"LoadingImage\":\"Loading...\"}' ),\r\n            uploadingTF:encodeURI( '{\"font\":\"Arial\", \"size\":12, \"color\":\"0x000\", \"bold\":\"true\", \"italic\":\"false\", \"underline\":\"false\"}' ),\r\n            imageTF:encodeURI( '{\"font\":\"Arial\", \"size\":11, \"color\":\"red\", \"bold\":\"false\", \"italic\":\"false\", \"underline\":\"false\"}' ),\r\n            textEncoding:\"utf-8\",\r\n            addImageSkinURL:\"addImage.png\",\r\n            allDeleteBtnUpSkinURL:\"allDeleteBtnUpSkin.png\",\r\n            allDeleteBtnHoverSkinURL:\"allDeleteBtnHoverSkin.png\",\r\n            rotateLeftBtnEnableSkinURL:\"rotateLeftEnable.png\",\r\n            rotateLeftBtnDisableSkinURL:\"rotateLeftDisable.png\",\r\n            rotateRightBtnEnableSkinURL:\"rotateRightEnable.png\",\r\n            rotateRightBtnDisableSkinURL:\"rotateRightDisable.png\",\r\n            deleteBtnEnableSkinURL:\"deleteEnable.png\",\r\n            deleteBtnDisableSkinURL:\"deleteDisable.png\",\r\n            backgroundURL:'',\r\n            listBackgroundURL:'',\r\n            buttonURL:'button.png'\r\n        }\r\n    },\r\n    'autosave': {\r\n        'success':'Local conservation success'\r\n    }\r\n};\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/lang/zh-cn/zh-cn.js",
    "content": "/**\r\n * Created with JetBrains PhpStorm.\r\n * User: taoqili\r\n * Date: 12-6-12\r\n * Time: 下午5:02\r\n * To change this template use File | Settings | File Templates.\r\n */\r\nUE.I18N['zh-cn'] = {\r\n    'labelMap':{\r\n        'anchor':'锚点', 'undo':'撤销', 'redo':'重做', 'bold':'加粗', 'indent':'首行缩进', 'snapscreen':'截图',\r\n        'italic':'斜体', 'underline':'下划线', 'strikethrough':'删除线', 'subscript':'下标','fontborder':'字符边框',\r\n        'superscript':'上标', 'formatmatch':'格式刷', 'source':'源代码', 'blockquote':'引用',\r\n        'pasteplain':'纯文本粘贴模式', 'selectall':'全选', 'print':'打印', 'preview':'预览',\r\n        'horizontal':'分隔线', 'removeformat':'清除格式', 'time':'时间', 'date':'日期',\r\n        'unlink':'取消链接', 'insertrow':'前插入行', 'insertcol':'前插入列', 'mergeright':'右合并单元格', 'mergedown':'下合并单元格',\r\n        'deleterow':'删除行', 'deletecol':'删除列', 'splittorows':'拆分成行',\r\n        'splittocols':'拆分成列', 'splittocells':'完全拆分单元格','deletecaption':'删除表格标题','inserttitle':'插入标题',\r\n        'mergecells':'合并多个单元格', 'deletetable':'删除表格', 'cleardoc':'清空文档','insertparagraphbeforetable':\"表格前插入行\",'insertcode':'代码语言',\r\n        'fontfamily':'字体', 'fontsize':'字号', 'paragraph':'段落格式', 'simpleupload':'单图上传', 'insertimage':'多图上传','edittable':'表格属性','edittd':'单元格属性', 'link':'超链接',\r\n        'emotion':'表情', 'spechars':'特殊字符', 'searchreplace':'查询替换', 'map':'Baidu地图', 'gmap':'Google地图',\r\n        'insertvideo':'视频', 'help':'帮助', 'justifyleft':'居左对齐', 'justifyright':'居右对齐', 'justifycenter':'居中对齐',\r\n        'justifyjustify':'两端对齐', 'forecolor':'字体颜色', 'backcolor':'背景色', 'insertorderedlist':'有序列表',\r\n        'insertunorderedlist':'无序列表', 'fullscreen':'全屏', 'directionalityltr':'从左向右输入', 'directionalityrtl':'从右向左输入',\r\n        'rowspacingtop':'段前距', 'rowspacingbottom':'段后距',  'pagebreak':'分页', 'insertframe':'插入Iframe', 'imagenone':'默认',\r\n        'imageleft':'左浮动', 'imageright':'右浮动', 'attachment':'附件', 'imagecenter':'居中', 'wordimage':'图片转存',\r\n        'lineheight':'行间距','edittip' :'编辑提示','customstyle':'自定义标题', 'autotypeset':'自动排版',\r\n        'webapp':'百度应用','touppercase':'字母大写', 'tolowercase':'字母小写','background':'背景','template':'模板','scrawl':'涂鸦',\r\n        'music':'音乐','inserttable':'插入表格','drafts': '从草稿箱加载', 'charts': '图表'\r\n    },\r\n    'insertorderedlist':{\r\n        'num':'1,2,3...',\r\n        'num1':'1),2),3)...',\r\n        'num2':'(1),(2),(3)...',\r\n        'cn':'一,二,三....',\r\n        'cn1':'一),二),三)....',\r\n        'cn2':'(一),(二),(三)....',\r\n        'decimal':'1,2,3...',\r\n        'lower-alpha':'a,b,c...',\r\n        'lower-roman':'i,ii,iii...',\r\n        'upper-alpha':'A,B,C...',\r\n        'upper-roman':'I,II,III...'\r\n    },\r\n    'insertunorderedlist':{\r\n        'circle':'○ 大圆圈',\r\n        'disc':'● 小黑点',\r\n        'square':'■ 小方块 ',\r\n        'dash' :'— 破折号',\r\n        'dot':' 。 小圆圈'\r\n    },\r\n    'paragraph':{'p':'段落', 'h1':'标题 1', 'h2':'标题 2', 'h3':'标题 3', 'h4':'标题 4', 'h5':'标题 5', 'h6':'标题 6'},\r\n    'fontfamily':{\r\n        'songti':'宋体',\r\n        'kaiti':'楷体',\r\n        'heiti':'黑体',\r\n        'lishu':'隶书',\r\n        'yahei':'微软雅黑',\r\n        'andaleMono':'andale mono',\r\n        'arial': 'arial',\r\n        'arialBlack':'arial black',\r\n        'comicSansMs':'comic sans ms',\r\n        'impact':'impact',\r\n        'timesNewRoman':'times new roman'\r\n    },\r\n    'customstyle':{\r\n        'tc':'标题居中',\r\n        'tl':'标题居左',\r\n        'im':'强调',\r\n        'hi':'明显强调'\r\n    },\r\n    'autoupload': {\r\n        'exceedSizeError': '文件大小超出限制',\r\n        'exceedTypeError': '文件格式不允许',\r\n        'jsonEncodeError': '服务器返回格式错误',\r\n        'loading':\"正在上传...\",\r\n        'loadError':\"上传错误\",\r\n        'errorLoadConfig': '后端配置项没有正常加载，上传插件不能正常使用！'\r\n    },\r\n    'simpleupload':{\r\n        'exceedSizeError': '文件大小超出限制',\r\n        'exceedTypeError': '文件格式不允许',\r\n        'jsonEncodeError': '服务器返回格式错误',\r\n        'loading':\"正在上传...\",\r\n        'loadError':\"上传错误\",\r\n        'errorLoadConfig': '后端配置项没有正常加载，上传插件不能正常使用！'\r\n    },\r\n    'elementPathTip':\"元素路径\",\r\n    'wordCountTip':\"字数统计\",\r\n    'wordCountMsg':'当前已输入{#count}个字符, 您还可以输入{#leave}个字符。 ',\r\n    'wordOverFlowMsg':'<span style=\"color:red;\">字数超出最大允许值，服务器可能拒绝保存！</span>',\r\n    'ok':\"确认\",\r\n    'cancel':\"取消\",\r\n    'closeDialog':\"关闭对话框\",\r\n    'tableDrag':\"表格拖动必须引入uiUtils.js文件！\",\r\n    'autofloatMsg':\"工具栏浮动依赖编辑器UI，您首先需要引入UI文件!\",\r\n    'loadconfigError': '获取后台配置项请求出错，上传功能将不能正常使用！',\r\n    'loadconfigFormatError': '后台配置项返回格式出错，上传功能将不能正常使用！',\r\n    'loadconfigHttpError': '请求后台配置项http错误，上传功能将不能正常使用！',\r\n    'snapScreen_plugin':{\r\n        'browserMsg':\"仅支持IE浏览器！\",\r\n        'callBackErrorMsg':\"服务器返回数据有误，请检查配置项之后重试。\",\r\n        'uploadErrorMsg':\"截图上传失败，请检查服务器端环境! \"\r\n    },\r\n    'insertcode':{\r\n        'as3':'ActionScript 3',\r\n        'bash':'Bash/Shell',\r\n        'cpp':'C/C++',\r\n        'css':'CSS',\r\n        'cf':'ColdFusion',\r\n        'c#':'C#',\r\n        'delphi':'Delphi',\r\n        'diff':'Diff',\r\n        'erlang':'Erlang',\r\n        'groovy':'Groovy',\r\n        'html':'HTML',\r\n        'java':'Java',\r\n        'jfx':'JavaFX',\r\n        'js':'JavaScript',\r\n        'pl':'Perl',\r\n        'php':'PHP',\r\n        'plain':'Plain Text',\r\n        'ps':'PowerShell',\r\n        'python':'Python',\r\n        'ruby':'Ruby',\r\n        'scala':'Scala',\r\n        'sql':'SQL',\r\n        'vb':'Visual Basic',\r\n        'xml':'XML'\r\n    },\r\n    'confirmClear':\"确定清空当前文档么？\",\r\n    'contextMenu':{\r\n        'delete':\"删除\",\r\n        'selectall':\"全选\",\r\n        'deletecode':\"删除代码\",\r\n        'cleardoc':\"清空文档\",\r\n        'confirmclear':\"确定清空当前文档么？\",\r\n        'unlink':\"删除超链接\",\r\n        'paragraph':\"段落格式\",\r\n        'edittable':\"表格属性\",\r\n        'aligntd':\"单元格对齐方式\",\r\n        'aligntable':'表格对齐方式',\r\n        'tableleft':'左浮动',\r\n        'tablecenter':'居中显示',\r\n        'tableright':'右浮动',\r\n        'edittd':\"单元格属性\",\r\n        'setbordervisible':'设置表格边线可见',\r\n        'justifyleft':'左对齐',\r\n        'justifyright':'右对齐',\r\n        'justifycenter':'居中对齐',\r\n        'justifyjustify':'两端对齐',\r\n        'table':\"表格\",\r\n        'inserttable':'插入表格',\r\n        'deletetable':\"删除表格\",\r\n        'insertparagraphbefore':\"前插入段落\",\r\n        'insertparagraphafter':'后插入段落',\r\n        'deleterow':\"删除当前行\",\r\n        'deletecol':\"删除当前列\",\r\n        'insertrow':\"前插入行\",\r\n        'insertcol':\"左插入列\",\r\n        'insertrownext':'后插入行',\r\n        'insertcolnext':'右插入列',\r\n        'insertcaption':'插入表格名称',\r\n        'deletecaption':'删除表格名称',\r\n        'inserttitle':'插入表格标题行',\r\n        'deletetitle':'删除表格标题行',\r\n        'inserttitlecol':'插入表格标题列',\r\n        'deletetitlecol':'删除表格标题列',\r\n        'averageDiseRow':'平均分布各行',\r\n        'averageDisCol':'平均分布各列',\r\n        'mergeright':\"向右合并\",\r\n        'mergeleft':\"向左合并\",\r\n        'mergedown':\"向下合并\",\r\n        'mergecells':\"合并单元格\",\r\n        'splittocells':\"完全拆分单元格\",\r\n        'splittocols':\"拆分成列\",\r\n        'splittorows':\"拆分成行\",\r\n        'tablesort':'表格排序',\r\n        'enablesort':'设置表格可排序',\r\n        'disablesort':'取消表格可排序',\r\n        'reversecurrent':'逆序当前',\r\n        'orderbyasc':'按ASCII字符升序',\r\n        'reversebyasc':'按ASCII字符降序',\r\n        'orderbynum':'按数值大小升序',\r\n        'reversebynum':'按数值大小降序',\r\n        'borderbk':'边框底纹',\r\n        'setcolor':'表格隔行变色',\r\n        'unsetcolor':'取消表格隔行变色',\r\n        'setbackground':'选区背景隔行',\r\n        'unsetbackground':'取消选区背景',\r\n        'redandblue':'红蓝相间',\r\n        'threecolorgradient':'三色渐变',\r\n        'copy':\"复制(Ctrl + c)\",\r\n        'copymsg': \"浏览器不支持,请使用 'Ctrl + c'\",\r\n        'paste':\"粘贴(Ctrl + v)\",\r\n         'pastemsg': \"浏览器不支持,请使用 'Ctrl + v'\"\r\n    },\r\n    'copymsg': \"浏览器不支持,请使用 'Ctrl + c'\",\r\n    'pastemsg': \"浏览器不支持,请使用 'Ctrl + v'\",\r\n    'anthorMsg':\"链接\",\r\n    'clearColor':'清空颜色',\r\n    'standardColor':'标准颜色',\r\n    'themeColor':'主题颜色',\r\n    'property':'属性',\r\n    'default':'默认',\r\n    'modify':'修改',\r\n    'justifyleft':'左对齐',\r\n    'justifyright':'右对齐',\r\n    'justifycenter':'居中',\r\n    'justify':'默认',\r\n    'clear':'清除',\r\n    'anchorMsg':'锚点',\r\n    'delete':'删除',\r\n    'clickToUpload':\"点击上传\",\r\n    'unset':'尚未设置语言文件',\r\n    't_row':'行',\r\n    't_col':'列',\r\n    'more':'更多',\r\n    'pasteOpt':'粘贴选项',\r\n    'pasteSourceFormat':\"保留源格式\",\r\n    'tagFormat':'只保留标签',\r\n    'pasteTextFormat':'只保留文本',\r\n    'autoTypeSet':{\r\n        'mergeLine':\"合并空行\",\r\n        'delLine':\"清除空行\",\r\n        'removeFormat':\"清除格式\",\r\n        'indent':\"首行缩进\",\r\n        'alignment':\"对齐方式\",\r\n        'imageFloat':\"图片浮动\",\r\n        'removeFontsize':\"清除字号\",\r\n        'removeFontFamily':\"清除字体\",\r\n        'removeHtml':\"清除冗余HTML代码\",\r\n        'pasteFilter':\"粘贴过滤\",\r\n        'run':\"执行\",\r\n        'symbol':'符号转换',\r\n        'bdc2sb':'全角转半角',\r\n        'tobdc':'半角转全角'\r\n    },\r\n\r\n    'background':{\r\n        'static':{\r\n            'lang_background_normal':'背景设置',\r\n            'lang_background_local':'在线图片',\r\n            'lang_background_set':'选项',\r\n            'lang_background_none':'无背景色',\r\n            'lang_background_colored':'有背景色',\r\n            'lang_background_color':'颜色设置',\r\n            'lang_background_netimg':'网络图片',\r\n            'lang_background_align':'对齐方式',\r\n            'lang_background_position':'精确定位',\r\n            'repeatType':{'options':[\"居中\", \"横向重复\", \"纵向重复\", \"平铺\",\"自定义\"]}\r\n\r\n        },\r\n        'noUploadImage':\"当前未上传过任何图片！\",\r\n        'toggleSelect':\"单击可切换选中状态\\n原图尺寸: \"\r\n    },\r\n    //===============dialog i18N=======================\r\n    'insertimage':{\r\n        'static':{\r\n            'lang_tab_remote':\"插入图片\", //节点\r\n            'lang_tab_upload':\"本地上传\",\r\n            'lang_tab_online':\"在线管理\",\r\n            'lang_tab_search':\"图片搜索\",\r\n            'lang_input_url':\"地 址：\",\r\n            'lang_input_size':\"大 小：\",\r\n            'lang_input_width':\"宽度\",\r\n            'lang_input_height':\"高度\",\r\n            'lang_input_border':\"边 框：\",\r\n            'lang_input_vhspace':\"边 距：\",\r\n            'lang_input_title':\"描 述：\",\r\n            'lang_input_align':'图片浮动方式：',\r\n            'lang_imgLoading':\"　图片加载中……\",\r\n            'lang_start_upload':\"开始上传\",\r\n            'lock':{'title':\"锁定宽高比例\"}, //属性\r\n            'searchType':{'title':\"图片类型\", 'options':[\"新闻\", \"壁纸\", \"表情\", \"头像\"]}, //select的option\r\n            'searchTxt':{'value':\"请输入搜索关键词\"},\r\n            'searchBtn':{'value':\"百度一下\"},\r\n            'searchReset':{'value':\"清空搜索\"},\r\n            'noneAlign':{'title':'无浮动'},\r\n            'leftAlign':{'title':'左浮动'},\r\n            'rightAlign':{'title':'右浮动'},\r\n            'centerAlign':{'title':'居中独占一行'}\r\n        },\r\n        'uploadSelectFile':'点击选择图片',\r\n        'uploadAddFile':'继续添加',\r\n        'uploadStart':'开始上传',\r\n        'uploadPause':'暂停上传',\r\n        'uploadContinue':'继续上传',\r\n        'uploadRetry':'重试上传',\r\n        'uploadDelete':'删除',\r\n        'uploadTurnLeft':'向左旋转',\r\n        'uploadTurnRight':'向右旋转',\r\n        'uploadPreview':'预览中',\r\n        'uploadNoPreview':'不能预览',\r\n        'updateStatusReady': '选中_张图片，共_KB。',\r\n        'updateStatusConfirm': '已成功上传_张照片，_张照片上传失败',\r\n        'updateStatusFinish': '共_张（_KB），_张上传成功',\r\n        'updateStatusError': '，_张上传失败。',\r\n        'errorNotSupport': 'WebUploader 不支持您的浏览器！如果你使用的是IE浏览器，请尝试升级 flash 播放器。',\r\n        'errorLoadConfig': '后端配置项没有正常加载，上传插件不能正常使用！',\r\n        'errorExceedSize':'文件大小超出',\r\n        'errorFileType':'文件格式不允许',\r\n        'errorInterrupt':'文件传输中断',\r\n        'errorUploadRetry':'上传失败，请重试',\r\n        'errorHttp':'http请求错误',\r\n        'errorServerUpload':'服务器返回出错',\r\n        'remoteLockError':\"宽高不正确,不能所定比例\",\r\n        'numError':\"请输入正确的长度或者宽度值！例如：123，400\",\r\n        'imageUrlError':\"不允许的图片格式或者图片域！\",\r\n        'imageLoadError':\"图片加载失败！请检查链接地址或网络状态！\",\r\n        'searchRemind':\"请输入搜索关键词\",\r\n        'searchLoading':\"图片加载中，请稍后……\",\r\n        'searchRetry':\" :( ，抱歉，没有找到图片！请重试一次！\"\r\n    },\r\n    'attachment':{\r\n        'static':{\r\n            'lang_tab_upload': '上传附件',\r\n            'lang_tab_online': '在线附件',\r\n            'lang_start_upload':\"开始上传\",\r\n            'lang_drop_remind':\"可以将文件拖到这里，单次最多可选100个文件\"\r\n        },\r\n        'uploadSelectFile':'点击选择文件',\r\n        'uploadAddFile':'继续添加',\r\n        'uploadStart':'开始上传',\r\n        'uploadPause':'暂停上传',\r\n        'uploadContinue':'继续上传',\r\n        'uploadRetry':'重试上传',\r\n        'uploadDelete':'删除',\r\n        'uploadTurnLeft':'向左旋转',\r\n        'uploadTurnRight':'向右旋转',\r\n        'uploadPreview':'预览中',\r\n        'updateStatusReady': '选中_个文件，共_KB。',\r\n        'updateStatusConfirm': '已成功上传_个文件，_个文件上传失败',\r\n        'updateStatusFinish': '共_个（_KB），_个上传成功',\r\n        'updateStatusError': '，_张上传失败。',\r\n        'errorNotSupport': 'WebUploader 不支持您的浏览器！如果你使用的是IE浏览器，请尝试升级 flash 播放器。',\r\n        'errorLoadConfig': '后端配置项没有正常加载，上传插件不能正常使用！',\r\n        'errorExceedSize':'文件大小超出',\r\n        'errorFileType':'文件格式不允许',\r\n        'errorInterrupt':'文件传输中断',\r\n        'errorUploadRetry':'上传失败，请重试',\r\n        'errorHttp':'http请求错误',\r\n        'errorServerUpload':'服务器返回出错'\r\n    },\r\n    'insertvideo':{\r\n        'static':{\r\n            'lang_tab_insertV':\"插入视频\",\r\n            'lang_tab_searchV':\"搜索视频\",\r\n            'lang_tab_uploadV':\"上传视频\",\r\n            'lang_video_url':\"视频网址\",\r\n            'lang_video_size':\"视频尺寸\",\r\n            'lang_videoW':\"宽度\",\r\n            'lang_videoH':\"高度\",\r\n            'lang_alignment':\"对齐方式\",\r\n            'videoSearchTxt':{'value':\"请输入搜索关键字！\"},\r\n            'videoType':{'options':[\"全部\", \"热门\", \"娱乐\", \"搞笑\", \"体育\", \"科技\", \"综艺\"]},\r\n            'videoSearchBtn':{'value':\"百度一下\"},\r\n            'videoSearchReset':{'value':\"清空结果\"},\r\n\r\n            'lang_input_fileStatus':' 当前未上传文件',\r\n            'startUpload':{'style':\"background:url(upload.png) no-repeat;\"},\r\n\r\n            'lang_upload_size':\"视频尺寸\",\r\n            'lang_upload_width':\"宽度\",\r\n            'lang_upload_height':\"高度\",\r\n            'lang_upload_alignment':\"对齐方式\",\r\n            'lang_format_advice':\"建议使用mp4格式.\"\r\n\r\n        },\r\n        'numError':\"请输入正确的数值，如123,400\",\r\n        'floatLeft':\"左浮动\",\r\n        'floatRight':\"右浮动\",\r\n        '\"default\"':\"默认\",\r\n        'block':\"独占一行\",\r\n        'urlError':\"输入的视频地址有误，请检查后再试！\",\r\n        'loading':\" &nbsp;视频加载中，请等待……\",\r\n        'clickToSelect':\"点击选中\",\r\n        'goToSource':'访问源视频',\r\n        'noVideo':\" &nbsp; &nbsp;抱歉，找不到对应的视频，请重试！\",\r\n\r\n        'browseFiles':'浏览文件',\r\n        'uploadSuccess':'上传成功!',\r\n        'delSuccessFile':'从成功队列中移除',\r\n        'delFailSaveFile':'移除保存失败文件',\r\n        'statusPrompt':' 个文件已上传！ ',\r\n        'flashVersionError':'当前Flash版本过低，请更新FlashPlayer后重试！',\r\n        'flashLoadingError':'Flash加载失败!请检查路径或网络状态',\r\n        'fileUploadReady':'等待上传……',\r\n        'delUploadQueue':'从上传队列中移除',\r\n        'limitPrompt1':'单次不能选择超过',\r\n        'limitPrompt2':'个文件！请重新选择！',\r\n        'delFailFile':'移除失败文件',\r\n        'fileSizeLimit':'文件大小超出限制！',\r\n        'emptyFile':'空文件无法上传！',\r\n        'fileTypeError':'文件类型不允许！',\r\n        'unknownError':'未知错误！',\r\n        'fileUploading':'上传中，请等待……',\r\n        'cancelUpload':'取消上传',\r\n        'netError':'网络错误',\r\n        'failUpload':'上传失败!',\r\n        'serverIOError':'服务器IO错误！',\r\n        'noAuthority':'无权限！',\r\n        'fileNumLimit':'上传个数限制',\r\n        'failCheck':'验证失败，本次上传被跳过！',\r\n        'fileCanceling':'取消中，请等待……',\r\n        'stopUploading':'上传已停止……',\r\n\r\n        'uploadSelectFile':'点击选择文件',\r\n        'uploadAddFile':'继续添加',\r\n        'uploadStart':'开始上传',\r\n        'uploadPause':'暂停上传',\r\n        'uploadContinue':'继续上传',\r\n        'uploadRetry':'重试上传',\r\n        'uploadDelete':'删除',\r\n        'uploadTurnLeft':'向左旋转',\r\n        'uploadTurnRight':'向右旋转',\r\n        'uploadPreview':'预览中',\r\n        'updateStatusReady': '选中_个文件，共_KB。',\r\n        'updateStatusConfirm': '成功上传_个，_个失败',\r\n        'updateStatusFinish': '共_个(_KB)，_个成功上传',\r\n        'updateStatusError': '，_张上传失败。',\r\n        'errorNotSupport': 'WebUploader 不支持您的浏览器！如果你使用的是IE浏览器，请尝试升级 flash 播放器。',\r\n        'errorLoadConfig': '后端配置项没有正常加载，上传插件不能正常使用！',\r\n        'errorExceedSize':'文件大小超出',\r\n        'errorFileType':'文件格式不允许',\r\n        'errorInterrupt':'文件传输中断',\r\n        'errorUploadRetry':'上传失败，请重试',\r\n        'errorHttp':'http请求错误',\r\n        'errorServerUpload':'服务器返回出错'\r\n    },\r\n    'webapp':{\r\n        'tip1':\"本功能由百度APP提供，如看到此页面，请各位站长首先申请百度APPKey!\",\r\n        'tip2':\"申请完成之后请至ueditor.config.js中配置获得的appkey! \",\r\n        'applyFor':\"点此申请\",\r\n        'anthorApi':\"百度API\"\r\n    },\r\n    'template':{\r\n        'static':{\r\n            'lang_template_bkcolor':'背景颜色',\r\n            'lang_template_clear' : '保留原有内容',\r\n            'lang_template_select' : '选择模板'\r\n        },\r\n        'blank':\"空白文档\",\r\n        'blog':\"博客文章\",\r\n        'resume':\"个人简历\",\r\n        'richText':\"图文混排\",\r\n        'sciPapers':\"科技论文\"\r\n\r\n\r\n    },\r\n    'scrawl':{\r\n        'static':{\r\n            'lang_input_previousStep':\"上一步\",\r\n            'lang_input_nextsStep':\"下一步\",\r\n            'lang_input_clear':'清空',\r\n            'lang_input_addPic':'添加背景',\r\n            'lang_input_ScalePic':'缩放背景',\r\n            'lang_input_removePic':'删除背景',\r\n            'J_imgTxt':{title:'添加背景图片'}\r\n        },\r\n        'noScarwl':\"尚未作画，白纸一张~\",\r\n        'scrawlUpLoading':\"涂鸦上传中,别急哦~\",\r\n        'continueBtn':\"继续\",\r\n        'imageError':\"糟糕，图片读取失败了！\",\r\n        'backgroundUploading':'背景图片上传中,别急哦~'\r\n    },\r\n    'music':{\r\n        'static':{\r\n            'lang_input_tips':\"输入歌手/歌曲/专辑，搜索您感兴趣的音乐！\",\r\n            'J_searchBtn':{value:'搜索歌曲'}\r\n        },\r\n        'emptyTxt':'未搜索到相关音乐结果，请换一个关键词试试。',\r\n        'chapter':'歌曲',\r\n        'singer':'歌手',\r\n        'special':'专辑',\r\n        'listenTest':'试听'\r\n    },\r\n    'anchor':{\r\n        'static':{\r\n            'lang_input_anchorName':'锚点名字：'\r\n        }\r\n    },\r\n    'charts':{\r\n        'static':{\r\n            'lang_data_source':'数据源：',\r\n            'lang_chart_format': '图表格式：',\r\n            'lang_data_align': '数据对齐方式',\r\n            'lang_chart_align_same': '数据源与图表X轴Y轴一致',\r\n            'lang_chart_align_reverse': '数据源与图表X轴Y轴相反',\r\n            'lang_chart_title': '图表标题',\r\n            'lang_chart_main_title': '主标题：',\r\n            'lang_chart_sub_title': '子标题：',\r\n            'lang_chart_x_title': 'X轴标题：',\r\n            'lang_chart_y_title': 'Y轴标题：',\r\n            'lang_chart_tip': '提示文字',\r\n            'lang_cahrt_tip_prefix': '提示文字前缀：',\r\n            'lang_cahrt_tip_description': '仅饼图有效， 当鼠标移动到饼图中相应的块上时，提示框内的文字的前缀',\r\n            'lang_chart_data_unit': '数据单位',\r\n            'lang_chart_data_unit_title': '单位：',\r\n            'lang_chart_data_unit_description': '显示在每个数据点上的数据的单位， 比如： 温度的单位 ℃',\r\n            'lang_chart_type': '图表类型：',\r\n            'lang_prev_btn': '上一个',\r\n            'lang_next_btn': '下一个'\r\n        }\r\n    },\r\n    'emotion':{\r\n        'static':{\r\n            'lang_input_choice':'精选',\r\n            'lang_input_Tuzki':'兔斯基',\r\n            'lang_input_BOBO':'BOBO',\r\n            'lang_input_lvdouwa':'绿豆蛙',\r\n            'lang_input_babyCat':'baby猫',\r\n            'lang_input_bubble':'泡泡',\r\n            'lang_input_youa':'有啊'\r\n        }\r\n    },\r\n    'gmap':{\r\n        'static':{\r\n            'lang_input_address':'地址',\r\n            'lang_input_search':'搜索',\r\n            'address':{value:\"北京\"}\r\n        },\r\n        searchError:'无法定位到该地址!'\r\n    },\r\n    'help':{\r\n        'static':{\r\n            'lang_input_about':'关于UEditor',\r\n            'lang_input_shortcuts':'快捷键',\r\n            'lang_input_introduction':'UEditor是由百度web前端研发部开发的所见即所得富文本web编辑器，具有轻量，可定制，注重用户体验等特点。开源基于BSD协议，允许自由使用和修改代码。',\r\n            'lang_Txt_shortcuts':'快捷键',\r\n            'lang_Txt_func':'功能',\r\n            'lang_Txt_bold':'给选中字设置为加粗',\r\n            'lang_Txt_copy':'复制选中内容',\r\n            'lang_Txt_cut':'剪切选中内容',\r\n            'lang_Txt_Paste':'粘贴',\r\n            'lang_Txt_undo':'重新执行上次操作',\r\n            'lang_Txt_redo':'撤销上一次操作',\r\n            'lang_Txt_italic':'给选中字设置为斜体',\r\n            'lang_Txt_underline':'给选中字加下划线',\r\n            'lang_Txt_selectAll':'全部选中',\r\n            'lang_Txt_visualEnter':'软回车',\r\n            'lang_Txt_fullscreen':'全屏'\r\n        }\r\n    },\r\n    'insertframe':{\r\n        'static':{\r\n            'lang_input_address':'地址：',\r\n            'lang_input_width':'宽度：',\r\n            'lang_input_height':'高度：',\r\n            'lang_input_isScroll':'允许滚动条：',\r\n            'lang_input_frameborder':'显示框架边框：',\r\n            'lang_input_alignMode':'对齐方式：',\r\n            'align':{title:\"对齐方式\", options:[\"默认\", \"左对齐\", \"右对齐\", \"居中\"]}\r\n        },\r\n        'enterAddress':'请输入地址!'\r\n    },\r\n    'link':{\r\n        'static':{\r\n            'lang_input_text':'文本内容：',\r\n            'lang_input_url':'链接地址：',\r\n            'lang_input_title':'标题：',\r\n            'lang_input_target':'是否在新窗口打开：'\r\n        },\r\n        'validLink':'只支持选中一个链接时生效',\r\n        'httpPrompt':'您输入的超链接中不包含http等协议名称，默认将为您添加http://前缀'\r\n    },\r\n    'map':{\r\n        'static':{\r\n            lang_city:\"城市\",\r\n            lang_address:\"地址\",\r\n            city:{value:\"北京\"},\r\n            lang_search:\"搜索\",\r\n            lang_dynamicmap:\"插入动态地图\"\r\n        },\r\n        cityMsg:\"请选择城市\",\r\n        errorMsg:\"抱歉，找不到该位置！\"\r\n    },\r\n    'searchreplace':{\r\n        'static':{\r\n            lang_tab_search:\"查找\",\r\n            lang_tab_replace:\"替换\",\r\n            lang_search1:\"查找\",\r\n            lang_search2:\"查找\",\r\n            lang_replace:\"替换\",\r\n            lang_searchReg:'支持正则表达式，添加前后斜杠标示为正则表达式，例如“/表达式/”',\r\n            lang_searchReg1:'支持正则表达式，添加前后斜杠标示为正则表达式，例如“/表达式/”',\r\n            lang_case_sensitive1:\"区分大小写\",\r\n            lang_case_sensitive2:\"区分大小写\",\r\n            nextFindBtn:{value:\"下一个\"},\r\n            preFindBtn:{value:\"上一个\"},\r\n            nextReplaceBtn:{value:\"下一个\"},\r\n            preReplaceBtn:{value:\"上一个\"},\r\n            repalceBtn:{value:\"替换\"},\r\n            repalceAllBtn:{value:\"全部替换\"}\r\n        },\r\n        getEnd:\"已经搜索到文章末尾！\",\r\n        getStart:\"已经搜索到文章头部\",\r\n        countMsg:\"总共替换了{#count}处！\"\r\n    },\r\n    'snapscreen':{\r\n        'static':{\r\n            lang_showMsg:\"截图功能需要首先安装UEditor截图插件！ \",\r\n            lang_download:\"点此下载\",\r\n            lang_step1:\"第一步，下载UEditor截图插件并运行安装。\",\r\n            lang_step2:\"第二步，插件安装完成后即可使用，如不生效，请重启浏览器后再试！\"\r\n        }\r\n    },\r\n    'spechars':{\r\n        'static':{},\r\n        tsfh:\"特殊字符\",\r\n        lmsz:\"罗马字符\",\r\n        szfh:\"数学字符\",\r\n        rwfh:\"日文字符\",\r\n        xlzm:\"希腊字母\",\r\n        ewzm:\"俄文字符\",\r\n        pyzm:\"拼音字母\",\r\n        yyyb:\"英语音标\",\r\n        zyzf:\"其他\"\r\n    },\r\n    'edittable':{\r\n        'static':{\r\n            'lang_tableStyle':'表格样式',\r\n            'lang_insertCaption':'添加表格名称行',\r\n            'lang_insertTitle':'添加表格标题行',\r\n            'lang_insertTitleCol':'添加表格标题列',\r\n            'lang_orderbycontent':\"使表格内容可排序\",\r\n            'lang_tableSize':'自动调整表格尺寸',\r\n            'lang_autoSizeContent':'按表格文字自适应',\r\n            'lang_autoSizePage':'按页面宽度自适应',\r\n            'lang_example':'示例',\r\n            'lang_borderStyle':'表格边框',\r\n            'lang_color':'颜色:'\r\n        },\r\n        captionName:'表格名称',\r\n        titleName:'标题',\r\n        cellsName:'内容',\r\n        errorMsg:'有合并单元格，不可排序'\r\n    },\r\n    'edittip':{\r\n        'static':{\r\n            lang_delRow:'删除整行',\r\n            lang_delCol:'删除整列'\r\n        }\r\n    },\r\n    'edittd':{\r\n        'static':{\r\n            lang_tdBkColor:'背景颜色:'\r\n        }\r\n    },\r\n    'formula':{\r\n        'static':{\r\n        }\r\n    },\r\n    'wordimage':{\r\n        'static':{\r\n            lang_resave:\"转存步骤\",\r\n            uploadBtn:{src:\"upload.png\",alt:\"上传\"},\r\n            clipboard:{style:\"background: url(copy.png) -153px -1px no-repeat;\"},\r\n            lang_step:\"1、点击顶部复制按钮，将地址复制到剪贴板；2、点击添加照片按钮，在弹出的对话框中使用Ctrl+V粘贴地址；3、点击打开后选择图片上传流程。\"\r\n        },\r\n        'fileType':\"图片\",\r\n        'flashError':\"FLASH初始化失败，请检查FLASH插件是否正确安装！\",\r\n        'netError':\"网络连接错误，请重试！\",\r\n        'copySuccess':\"图片地址已经复制！\",\r\n        'flashI18n':{} //留空默认中文\r\n    },\r\n    'autosave': {\r\n        'saving':'保存中...',\r\n        'success':'本地保存成功'\r\n    }\r\n};\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/php/Uploader.class.php",
    "content": "<?php\r\n\r\n/**\r\n * Created by JetBrains PhpStorm.\r\n * User: taoqili\r\n * Date: 12-7-18\r\n * Time: 上午11: 32\r\n * UEditor编辑器通用上传类\r\n */\r\nclass Uploader\r\n{\r\n    private $fileField; //文件域名\r\n    private $file; //文件上传对象\r\n    private $base64; //文件上传对象\r\n    private $config; //配置信息\r\n    private $oriName; //原始文件名\r\n    private $fileName; //新文件名\r\n    private $fullName; //完整文件名,即从当前配置目录开始的URL\r\n    private $filePath; //完整文件名,即从当前配置目录开始的URL\r\n    private $fileSize; //文件大小\r\n    private $fileType; //文件类型\r\n    private $stateInfo; //上传状态信息,\r\n    private $stateMap = array( //上传状态映射表，国际化用户需考虑此处数据的国际化\r\n        \"SUCCESS\", //上传成功标记，在UEditor中内不可改变，否则flash判断会出错\r\n        \"文件大小超出 upload_max_filesize 限制\",\r\n        \"文件大小超出 MAX_FILE_SIZE 限制\",\r\n        \"文件未被完整上传\",\r\n        \"没有文件被上传\",\r\n        \"上传文件为空\",\r\n        \"ERROR_TMP_FILE\" => \"临时文件错误\",\r\n        \"ERROR_TMP_FILE_NOT_FOUND\" => \"找不到临时文件\",\r\n        \"ERROR_SIZE_EXCEED\" => \"文件大小超出网站限制\",\r\n        \"ERROR_TYPE_NOT_ALLOWED\" => \"文件类型不允许\",\r\n        \"ERROR_CREATE_DIR\" => \"目录创建失败\",\r\n        \"ERROR_DIR_NOT_WRITEABLE\" => \"目录没有写权限\",\r\n        \"ERROR_FILE_MOVE\" => \"文件保存时出错\",\r\n        \"ERROR_FILE_NOT_FOUND\" => \"找不到上传文件\",\r\n        \"ERROR_WRITE_CONTENT\" => \"写入文件内容错误\",\r\n        \"ERROR_UNKNOWN\" => \"未知错误\",\r\n        \"ERROR_DEAD_LINK\" => \"链接不可用\",\r\n        \"ERROR_HTTP_LINK\" => \"链接不是http链接\",\r\n        \"ERROR_HTTP_CONTENTTYPE\" => \"链接contentType不正确\",\r\n        \"INVALID_URL\" => \"非法 URL\",\r\n        \"INVALID_IP\" => \"非法 IP\"\r\n    );\r\n\r\n    /**\r\n     * 构造函数\r\n     * @param string $fileField 表单名称\r\n     * @param array $config 配置项\r\n     * @param bool $base64 是否解析base64编码，可省略。若开启，则$fileField代表的是base64编码的字符串表单名\r\n     */\r\n    public function __construct($fileField, $config, $type = \"upload\")\r\n    {\r\n        $this->fileField = $fileField;\r\n        $this->config = $config;\r\n        $this->type = $type;\r\n        if ($type == \"remote\") {\r\n            $this->saveRemote();\r\n        } else if($type == \"base64\") {\r\n            $this->upBase64();\r\n        } else {\r\n            $this->upFile();\r\n        }\r\n\r\n        $this->stateMap['ERROR_TYPE_NOT_ALLOWED'] = iconv('unicode', 'utf-8', $this->stateMap['ERROR_TYPE_NOT_ALLOWED']);\r\n    }\r\n\r\n    /**\r\n     * 上传文件的主处理方法\r\n     * @return mixed\r\n     */\r\n    private function upFile()\r\n    {\r\n        $file = $this->file = $_FILES[$this->fileField];\r\n        if (!$file) {\r\n            $this->stateInfo = $this->getStateInfo(\"ERROR_FILE_NOT_FOUND\");\r\n            return;\r\n        }\r\n        if ($this->file['error']) {\r\n            $this->stateInfo = $this->getStateInfo($file['error']);\r\n            return;\r\n        } else if (!file_exists($file['tmp_name'])) {\r\n            $this->stateInfo = $this->getStateInfo(\"ERROR_TMP_FILE_NOT_FOUND\");\r\n            return;\r\n        } else if (!is_uploaded_file($file['tmp_name'])) {\r\n            $this->stateInfo = $this->getStateInfo(\"ERROR_TMPFILE\");\r\n            return;\r\n        }\r\n\r\n        $this->oriName = $file['name'];\r\n        $this->fileSize = $file['size'];\r\n        $this->fileType = $this->getFileExt();\r\n        $this->fullName = $this->getFullName();\r\n        $this->filePath = $this->getFilePath();\r\n        $this->fileName = $this->getFileName();\r\n        $dirname = dirname($this->filePath);\r\n\r\n        //检查文件大小是否超出限制\r\n        if (!$this->checkSize()) {\r\n            $this->stateInfo = $this->getStateInfo(\"ERROR_SIZE_EXCEED\");\r\n            return;\r\n        }\r\n\r\n        //检查是否不允许的文件格式\r\n        if (!$this->checkType()) {\r\n            $this->stateInfo = $this->getStateInfo(\"ERROR_TYPE_NOT_ALLOWED\");\r\n            return;\r\n        }\r\n\r\n        //创建目录失败\r\n        if (!file_exists($dirname) && !mkdir($dirname, 0777, true)) {\r\n            $this->stateInfo = $this->getStateInfo(\"ERROR_CREATE_DIR\");\r\n            return;\r\n        } else if (!is_writeable($dirname)) {\r\n            $this->stateInfo = $this->getStateInfo(\"ERROR_DIR_NOT_WRITEABLE\");\r\n            return;\r\n        }\r\n\r\n        //移动文件\r\n        if (!(move_uploaded_file($file[\"tmp_name\"], $this->filePath) && file_exists($this->filePath))) { //移动失败\r\n            $this->stateInfo = $this->getStateInfo(\"ERROR_FILE_MOVE\");\r\n        } else { //移动成功\r\n            $this->stateInfo = $this->stateMap[0];\r\n        }\r\n    }\r\n\r\n    /**\r\n     * 处理base64编码的图片上传\r\n     * @return mixed\r\n     */\r\n    private function upBase64()\r\n    {\r\n        $base64Data = $_POST[$this->fileField];\r\n        $img = base64_decode($base64Data);\r\n\r\n        $this->oriName = $this->config['oriName'];\r\n        $this->fileSize = strlen($img);\r\n        $this->fileType = $this->getFileExt();\r\n        $this->fullName = $this->getFullName();\r\n        $this->filePath = $this->getFilePath();\r\n        $this->fileName = $this->getFileName();\r\n        $dirname = dirname($this->filePath);\r\n\r\n        //检查文件大小是否超出限制\r\n        if (!$this->checkSize()) {\r\n            $this->stateInfo = $this->getStateInfo(\"ERROR_SIZE_EXCEED\");\r\n            return;\r\n        }\r\n\r\n        //创建目录失败\r\n        if (!file_exists($dirname) && !mkdir($dirname, 0777, true)) {\r\n            $this->stateInfo = $this->getStateInfo(\"ERROR_CREATE_DIR\");\r\n            return;\r\n        } else if (!is_writeable($dirname)) {\r\n            $this->stateInfo = $this->getStateInfo(\"ERROR_DIR_NOT_WRITEABLE\");\r\n            return;\r\n        }\r\n\r\n        //移动文件\r\n        if (!(file_put_contents($this->filePath, $img) && file_exists($this->filePath))) { //移动失败\r\n            $this->stateInfo = $this->getStateInfo(\"ERROR_WRITE_CONTENT\");\r\n        } else { //移动成功\r\n            $this->stateInfo = $this->stateMap[0];\r\n        }\r\n\r\n    }\r\n\r\n    /**\r\n     * 拉取远程图片\r\n     * @return mixed\r\n     */\r\n    private function saveRemote()\r\n    {\r\n        $imgUrl = htmlspecialchars($this->fileField);\r\n        $imgUrl = str_replace(\"&amp;\", \"&\", $imgUrl);\r\n\r\n        //http开头验证\r\n        if (strpos($imgUrl, \"http\") !== 0) {\r\n            $this->stateInfo = $this->getStateInfo(\"ERROR_HTTP_LINK\");\r\n            return;\r\n        }\r\n\r\n        preg_match('/(^https*:\\/\\/[^:\\/]+)/', $imgUrl, $matches);\r\n        $host_with_protocol = count($matches) > 1 ? $matches[1] : '';\r\n\r\n        // 判断是否是合法 url\r\n        if (!filter_var($host_with_protocol, FILTER_VALIDATE_URL)) {\r\n            $this->stateInfo = $this->getStateInfo(\"INVALID_URL\");\r\n            return;\r\n        }\r\n\r\n        preg_match('/^https*:\\/\\/(.+)/', $host_with_protocol, $matches);\r\n        $host_without_protocol = count($matches) > 1 ? $matches[1] : '';\r\n\r\n        // 此时提取出来的可能是 ip 也有可能是域名，先获取 ip\r\n        $ip = gethostbyname($host_without_protocol);\r\n        // 判断是否是私有 ip\r\n        if(!filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE)) {\r\n            $this->stateInfo = $this->getStateInfo(\"INVALID_IP\");\r\n            return;\r\n        }\r\n\r\n        //获取请求头并检测死链\r\n        $heads = get_headers($imgUrl, 1);\r\n        if (!(stristr($heads[0], \"200\") && stristr($heads[0], \"OK\"))) {\r\n            $this->stateInfo = $this->getStateInfo(\"ERROR_DEAD_LINK\");\r\n            return;\r\n        }\r\n        //格式验证(扩展名验证和Content-Type验证)\r\n        $fileType = strtolower(strrchr($imgUrl, '.'));\r\n        if (!in_array($fileType, $this->config['allowFiles']) || !isset($heads['Content-Type']) || !stristr($heads['Content-Type'], \"image\")) {\r\n            $this->stateInfo = $this->getStateInfo(\"ERROR_HTTP_CONTENTTYPE\");\r\n            return;\r\n        }\r\n\r\n        //打开输出缓冲区并获取远程图片\r\n        ob_start();\r\n        $context = stream_context_create(\r\n            array('http' => array(\r\n                'follow_location' => false // don't follow redirects\r\n            ))\r\n        );\r\n        readfile($imgUrl, false, $context);\r\n        $img = ob_get_contents();\r\n        ob_end_clean();\r\n        preg_match(\"/[\\/]([^\\/]*)[\\.]?[^\\.\\/]*$/\", $imgUrl, $m);\r\n\r\n        $this->oriName = $m ? $m[1]:\"\";\r\n        $this->fileSize = strlen($img);\r\n        $this->fileType = $this->getFileExt();\r\n        $this->fullName = $this->getFullName();\r\n        $this->filePath = $this->getFilePath();\r\n        $this->fileName = $this->getFileName();\r\n        $dirname = dirname($this->filePath);\r\n\r\n        //检查文件大小是否超出限制\r\n        if (!$this->checkSize()) {\r\n            $this->stateInfo = $this->getStateInfo(\"ERROR_SIZE_EXCEED\");\r\n            return;\r\n        }\r\n\r\n        //创建目录失败\r\n        if (!file_exists($dirname) && !mkdir($dirname, 0777, true)) {\r\n            $this->stateInfo = $this->getStateInfo(\"ERROR_CREATE_DIR\");\r\n            return;\r\n        } else if (!is_writeable($dirname)) {\r\n            $this->stateInfo = $this->getStateInfo(\"ERROR_DIR_NOT_WRITEABLE\");\r\n            return;\r\n        }\r\n\r\n        //移动文件\r\n        if (!(file_put_contents($this->filePath, $img) && file_exists($this->filePath))) { //移动失败\r\n            $this->stateInfo = $this->getStateInfo(\"ERROR_WRITE_CONTENT\");\r\n        } else { //移动成功\r\n            $this->stateInfo = $this->stateMap[0];\r\n        }\r\n\r\n    }\r\n\r\n    /**\r\n     * 上传错误检查\r\n     * @param $errCode\r\n     * @return string\r\n     */\r\n    private function getStateInfo($errCode)\r\n    {\r\n        return !$this->stateMap[$errCode] ? $this->stateMap[\"ERROR_UNKNOWN\"] : $this->stateMap[$errCode];\r\n    }\r\n\r\n    /**\r\n     * 获取文件扩展名\r\n     * @return string\r\n     */\r\n    private function getFileExt()\r\n    {\r\n        return strtolower(strrchr($this->oriName, '.'));\r\n    }\r\n\r\n    /**\r\n     * 重命名文件\r\n     * @return string\r\n     */\r\n    private function getFullName()\r\n    {\r\n        //替换日期事件\r\n        $t = time();\r\n        $d = explode('-', date(\"Y-y-m-d-H-i-s\"));\r\n        $format = $this->config[\"pathFormat\"];\r\n        $format = str_replace(\"{yyyy}\", $d[0], $format);\r\n        $format = str_replace(\"{yy}\", $d[1], $format);\r\n        $format = str_replace(\"{mm}\", $d[2], $format);\r\n        $format = str_replace(\"{dd}\", $d[3], $format);\r\n        $format = str_replace(\"{hh}\", $d[4], $format);\r\n        $format = str_replace(\"{ii}\", $d[5], $format);\r\n        $format = str_replace(\"{ss}\", $d[6], $format);\r\n        $format = str_replace(\"{time}\", $t, $format);\r\n\r\n        //过滤文件名的非法自负,并替换文件名\r\n        $oriName = substr($this->oriName, 0, strrpos($this->oriName, '.'));\r\n        $oriName = preg_replace(\"/[\\|\\?\\\"\\<\\>\\/\\*\\\\\\\\]+/\", '', $oriName);\r\n        $format = str_replace(\"{filename}\", $oriName, $format);\r\n\r\n        //替换随机字符串\r\n        $randNum = rand(1, 10000000000) . rand(1, 10000000000);\r\n        if (preg_match(\"/\\{rand\\:([\\d]*)\\}/i\", $format, $matches)) {\r\n            $format = preg_replace(\"/\\{rand\\:[\\d]*\\}/i\", substr($randNum, 0, $matches[1]), $format);\r\n        }\r\n\r\n        $ext = $this->getFileExt();\r\n        return $format . $ext;\r\n    }\r\n\r\n    /**\r\n     * 获取文件名\r\n     * @return string\r\n     */\r\n    private function getFileName () {\r\n        return substr($this->filePath, strrpos($this->filePath, '/') + 1);\r\n    }\r\n\r\n    /**\r\n     * 获取文件完整路径\r\n     * @return string\r\n     */\r\n    private function getFilePath()\r\n    {\r\n        $fullname = $this->fullName;\r\n        $rootPath = $_SERVER['DOCUMENT_ROOT'];\r\n\r\n        if (substr($fullname, 0, 1) != '/') {\r\n            $fullname = '/' . $fullname;\r\n        }\r\n\r\n        return $rootPath . $fullname;\r\n    }\r\n\r\n    /**\r\n     * 文件类型检测\r\n     * @return bool\r\n     */\r\n    private function checkType()\r\n    {\r\n        return in_array($this->getFileExt(), $this->config[\"allowFiles\"]);\r\n    }\r\n\r\n    /**\r\n     * 文件大小检测\r\n     * @return bool\r\n     */\r\n    private function  checkSize()\r\n    {\r\n        return $this->fileSize <= ($this->config[\"maxSize\"]);\r\n    }\r\n\r\n    /**\r\n     * 获取当前上传成功文件的各项信息\r\n     * @return array\r\n     */\r\n    public function getFileInfo()\r\n    {\r\n        return array(\r\n            \"state\" => $this->stateInfo,\r\n            \"url\" => $this->fullName,\r\n            \"title\" => $this->fileName,\r\n            \"original\" => $this->oriName,\r\n            \"type\" => $this->fileType,\r\n            \"size\" => $this->fileSize\r\n        );\r\n    }\r\n\r\n}"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/php/action_crawler.php",
    "content": "<?php\n/**\n * 抓取远程图片\n * User: Jinqn\n * Date: 14-04-14\n * Time: 下午19:18\n */\nset_time_limit(0);\ninclude(\"Uploader.class.php\");\n\n/* 上传配置 */\n$config = array(\n    \"pathFormat\" => $CONFIG['catcherPathFormat'],\n    \"maxSize\" => $CONFIG['catcherMaxSize'],\n    \"allowFiles\" => $CONFIG['catcherAllowFiles'],\n    \"oriName\" => \"remote.png\"\n);\n$fieldName = $CONFIG['catcherFieldName'];\n\n/* 抓取远程图片 */\n$list = array();\nif (isset($_POST[$fieldName])) {\n    $source = $_POST[$fieldName];\n} else {\n    $source = $_GET[$fieldName];\n}\nforeach ($source as $imgUrl) {\n    $item = new Uploader($imgUrl, $config, \"remote\");\n    $info = $item->getFileInfo();\n    array_push($list, array(\n        \"state\" => $info[\"state\"],\n        \"url\" => $info[\"url\"],\n        \"size\" => $info[\"size\"],\n        \"title\" => htmlspecialchars($info[\"title\"]),\n        \"original\" => htmlspecialchars($info[\"original\"]),\n        \"source\" => htmlspecialchars($imgUrl)\n    ));\n}\n\n/* 返回抓取数据 */\nreturn json_encode(array(\n    'state'=> count($list) ? 'SUCCESS':'ERROR',\n    'list'=> $list\n));"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/php/action_list.php",
    "content": "<?php\n/**\n * 获取已上传的文件列表\n * User: Jinqn\n * Date: 14-04-09\n * Time: 上午10:17\n */\ninclude \"Uploader.class.php\";\n\n/* 判断类型 */\nswitch ($_GET['action']) {\n    /* 列出文件 */\n    case 'listfile':\n        $allowFiles = $CONFIG['fileManagerAllowFiles'];\n        $listSize = $CONFIG['fileManagerListSize'];\n        $path = $CONFIG['fileManagerListPath'];\n        break;\n    /* 列出图片 */\n    case 'listimage':\n    default:\n        $allowFiles = $CONFIG['imageManagerAllowFiles'];\n        $listSize = $CONFIG['imageManagerListSize'];\n        $path = $CONFIG['imageManagerListPath'];\n}\n$allowFiles = substr(str_replace(\".\", \"|\", join(\"\", $allowFiles)), 1);\n\n/* 获取参数 */\n$size = isset($_GET['size']) ? htmlspecialchars($_GET['size']) : $listSize;\n$start = isset($_GET['start']) ? htmlspecialchars($_GET['start']) : 0;\n$end = $start + $size;\n\n/* 获取文件列表 */\n$path = $_SERVER['DOCUMENT_ROOT'] . (substr($path, 0, 1) == \"/\" ? \"\":\"/\") . $path;\n$files = getfiles($path, $allowFiles);\nif (!count($files)) {\n    return json_encode(array(\n        \"state\" => \"no match file\",\n        \"list\" => array(),\n        \"start\" => $start,\n        \"total\" => count($files)\n    ));\n}\n\n/* 获取指定范围的列表 */\n$len = count($files);\nfor ($i = min($end, $len) - 1, $list = array(); $i < $len && $i >= 0 && $i >= $start; $i--){\n    $list[] = $files[$i];\n}\n//倒序\n//for ($i = $end, $list = array(); $i < $len && $i < $end; $i++){\n//    $list[] = $files[$i];\n//}\n\n/* 返回数据 */\n$result = json_encode(array(\n    \"state\" => \"SUCCESS\",\n    \"list\" => $list,\n    \"start\" => $start,\n    \"total\" => count($files)\n));\n\nreturn $result;\n\n\n/**\n * 遍历获取目录下的指定类型的文件\n * @param $path\n * @param array $files\n * @return array\n */\nfunction getfiles($path, $allowFiles, &$files = array())\n{\n    if (!is_dir($path)) return null;\n    if(substr($path, strlen($path) - 1) != '/') $path .= '/';\n    $handle = opendir($path);\n    while (false !== ($file = readdir($handle))) {\n        if ($file != '.' && $file != '..') {\n            $path2 = $path . $file;\n            if (is_dir($path2)) {\n                getfiles($path2, $allowFiles, $files);\n            } else {\n                if (preg_match(\"/\\.(\".$allowFiles.\")$/i\", $file)) {\n                    $files[] = array(\n                        'url'=> substr($path2, strlen($_SERVER['DOCUMENT_ROOT'])),\n                        'mtime'=> filemtime($path2)\n                    );\n                }\n            }\n        }\n    }\n    return $files;\n}"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/php/action_upload.php",
    "content": "<?php\n/**\n * 上传附件和上传视频\n * User: Jinqn\n * Date: 14-04-09\n * Time: 上午10:17\n */\ninclude \"Uploader.class.php\";\n\n/* 上传配置 */\n$base64 = \"upload\";\nswitch (htmlspecialchars($_GET['action'])) {\n    case 'uploadimage':\n        $config = array(\n            \"pathFormat\" => $CONFIG['imagePathFormat'],\n            \"maxSize\" => $CONFIG['imageMaxSize'],\n            \"allowFiles\" => $CONFIG['imageAllowFiles']\n        );\n        $fieldName = $CONFIG['imageFieldName'];\n        break;\n    case 'uploadscrawl':\n        $config = array(\n            \"pathFormat\" => $CONFIG['scrawlPathFormat'],\n            \"maxSize\" => $CONFIG['scrawlMaxSize'],\n            \"allowFiles\" => $CONFIG['scrawlAllowFiles'],\n            \"oriName\" => \"scrawl.png\"\n        );\n        $fieldName = $CONFIG['scrawlFieldName'];\n        $base64 = \"base64\";\n        break;\n    case 'uploadvideo':\n        $config = array(\n            \"pathFormat\" => $CONFIG['videoPathFormat'],\n            \"maxSize\" => $CONFIG['videoMaxSize'],\n            \"allowFiles\" => $CONFIG['videoAllowFiles']\n        );\n        $fieldName = $CONFIG['videoFieldName'];\n        break;\n    case 'uploadfile':\n    default:\n        $config = array(\n            \"pathFormat\" => $CONFIG['filePathFormat'],\n            \"maxSize\" => $CONFIG['fileMaxSize'],\n            \"allowFiles\" => $CONFIG['fileAllowFiles']\n        );\n        $fieldName = $CONFIG['fileFieldName'];\n        break;\n}\n\n/* 生成上传实例对象并完成上传 */\n$up = new Uploader($fieldName, $config, $base64);\n\n/**\n * 得到上传文件所对应的各个参数,数组结构\n * array(\n *     \"state\" => \"\",          //上传状态，上传成功时必须返回\"SUCCESS\"\n *     \"url\" => \"\",            //返回的地址\n *     \"title\" => \"\",          //新文件名\n *     \"original\" => \"\",       //原始文件名\n *     \"type\" => \"\"            //文件类型\n *     \"size\" => \"\",           //文件大小\n * )\n */\n\n/* 返回数据 */\nreturn json_encode($up->getFileInfo());\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/php/config.json",
    "content": "/* 前后端通信相关的配置,注释只允许使用多行方式 */\n{\n    /* 上传图片配置项 */\n    \"imageActionName\": \"uploadimage\", /* 执行上传图片的action名称 */\n    \"imageFieldName\": \"upfile\", /* 提交的图片表单名称 */\n    \"imageMaxSize\": 2048000, /* 上传大小限制，单位B */\n    \"imageAllowFiles\": [\".png\", \".jpg\", \".jpeg\", \".gif\", \".bmp\"], /* 上传图片格式显示 */\n    \"imageCompressEnable\": true, /* 是否压缩图片,默认是true */\n    \"imageCompressBorder\": 1600, /* 图片压缩最长边限制 */\n    \"imageInsertAlign\": \"none\", /* 插入的图片浮动方式 */\n    \"imageUrlPrefix\": \"\", /* 图片访问路径前缀 */\n    \"imagePathFormat\": \"/ueditor/php/upload/image/{yyyy}{mm}{dd}/{time}{rand:6}\", /* 上传保存路径,可以自定义保存路径和文件名格式 */\n                                /* {filename} 会替换成原文件名,配置这项需要注意中文乱码问题 */\n                                /* {rand:6} 会替换成随机数,后面的数字是随机数的位数 */\n                                /* {time} 会替换成时间戳 */\n                                /* {yyyy} 会替换成四位年份 */\n                                /* {yy} 会替换成两位年份 */\n                                /* {mm} 会替换成两位月份 */\n                                /* {dd} 会替换成两位日期 */\n                                /* {hh} 会替换成两位小时 */\n                                /* {ii} 会替换成两位分钟 */\n                                /* {ss} 会替换成两位秒 */\n                                /* 非法字符 \\ : * ? \" < > | */\n                                /* 具请体看线上文档: fex.baidu.com/ueditor/#use-format_upload_filename */\n\n    /* 涂鸦图片上传配置项 */\n    \"scrawlActionName\": \"uploadscrawl\", /* 执行上传涂鸦的action名称 */\n    \"scrawlFieldName\": \"upfile\", /* 提交的图片表单名称 */\n    \"scrawlPathFormat\": \"/ueditor/php/upload/image/{yyyy}{mm}{dd}/{time}{rand:6}\", /* 上传保存路径,可以自定义保存路径和文件名格式 */\n    \"scrawlMaxSize\": 2048000, /* 上传大小限制，单位B */\n    \"scrawlUrlPrefix\": \"\", /* 图片访问路径前缀 */\n    \"scrawlInsertAlign\": \"none\",\n\n    /* 截图工具上传 */\n    \"snapscreenActionName\": \"uploadimage\", /* 执行上传截图的action名称 */\n    \"snapscreenPathFormat\": \"/ueditor/php/upload/image/{yyyy}{mm}{dd}/{time}{rand:6}\", /* 上传保存路径,可以自定义保存路径和文件名格式 */\n    \"snapscreenUrlPrefix\": \"\", /* 图片访问路径前缀 */\n    \"snapscreenInsertAlign\": \"none\", /* 插入的图片浮动方式 */\n\n    /* 抓取远程图片配置 */\n    \"catcherLocalDomain\": [\"127.0.0.1\", \"localhost\", \"img.baidu.com\"],\n    \"catcherActionName\": \"catchimage\", /* 执行抓取远程图片的action名称 */\n    \"catcherFieldName\": \"source\", /* 提交的图片列表表单名称 */\n    \"catcherPathFormat\": \"/ueditor/php/upload/image/{yyyy}{mm}{dd}/{time}{rand:6}\", /* 上传保存路径,可以自定义保存路径和文件名格式 */\n    \"catcherUrlPrefix\": \"\", /* 图片访问路径前缀 */\n    \"catcherMaxSize\": 2048000, /* 上传大小限制，单位B */\n    \"catcherAllowFiles\": [\".png\", \".jpg\", \".jpeg\", \".gif\", \".bmp\"], /* 抓取图片格式显示 */\n\n    /* 上传视频配置 */\n    \"videoActionName\": \"uploadvideo\", /* 执行上传视频的action名称 */\n    \"videoFieldName\": \"upfile\", /* 提交的视频表单名称 */\n    \"videoPathFormat\": \"/ueditor/php/upload/video/{yyyy}{mm}{dd}/{time}{rand:6}\", /* 上传保存路径,可以自定义保存路径和文件名格式 */\n    \"videoUrlPrefix\": \"\", /* 视频访问路径前缀 */\n    \"videoMaxSize\": 102400000, /* 上传大小限制，单位B，默认100MB */\n    \"videoAllowFiles\": [\n        \".flv\", \".swf\", \".mkv\", \".avi\", \".rm\", \".rmvb\", \".mpeg\", \".mpg\",\n        \".ogg\", \".ogv\", \".mov\", \".wmv\", \".mp4\", \".webm\", \".mp3\", \".wav\", \".mid\"], /* 上传视频格式显示 */\n\n    /* 上传文件配置 */\n    \"fileActionName\": \"uploadfile\", /* controller里,执行上传视频的action名称 */\n    \"fileFieldName\": \"upfile\", /* 提交的文件表单名称 */\n    \"filePathFormat\": \"/ueditor/php/upload/file/{yyyy}{mm}{dd}/{time}{rand:6}\", /* 上传保存路径,可以自定义保存路径和文件名格式 */\n    \"fileUrlPrefix\": \"\", /* 文件访问路径前缀 */\n    \"fileMaxSize\": 51200000, /* 上传大小限制，单位B，默认50MB */\n    \"fileAllowFiles\": [\n        \".png\", \".jpg\", \".jpeg\", \".gif\", \".bmp\",\n        \".flv\", \".swf\", \".mkv\", \".avi\", \".rm\", \".rmvb\", \".mpeg\", \".mpg\",\n        \".ogg\", \".ogv\", \".mov\", \".wmv\", \".mp4\", \".webm\", \".mp3\", \".wav\", \".mid\",\n        \".rar\", \".zip\", \".tar\", \".gz\", \".7z\", \".bz2\", \".cab\", \".iso\",\n        \".doc\", \".docx\", \".xls\", \".xlsx\", \".ppt\", \".pptx\", \".pdf\", \".txt\", \".md\", \".xml\"\n    ], /* 上传文件格式显示 */\n\n    /* 列出指定目录下的图片 */\n    \"imageManagerActionName\": \"listimage\", /* 执行图片管理的action名称 */\n    \"imageManagerListPath\": \"/ueditor/php/upload/image/\", /* 指定要列出图片的目录 */\n    \"imageManagerListSize\": 20, /* 每次列出文件数量 */\n    \"imageManagerUrlPrefix\": \"\", /* 图片访问路径前缀 */\n    \"imageManagerInsertAlign\": \"none\", /* 插入的图片浮动方式 */\n    \"imageManagerAllowFiles\": [\".png\", \".jpg\", \".jpeg\", \".gif\", \".bmp\"], /* 列出的文件类型 */\n\n    /* 列出指定目录下的文件 */\n    \"fileManagerActionName\": \"listfile\", /* 执行文件管理的action名称 */\n    \"fileManagerListPath\": \"/ueditor/php/upload/file/\", /* 指定要列出文件的目录 */\n    \"fileManagerUrlPrefix\": \"\", /* 文件访问路径前缀 */\n    \"fileManagerListSize\": 20, /* 每次列出文件数量 */\n    \"fileManagerAllowFiles\": [\n        \".png\", \".jpg\", \".jpeg\", \".gif\", \".bmp\",\n        \".flv\", \".swf\", \".mkv\", \".avi\", \".rm\", \".rmvb\", \".mpeg\", \".mpg\",\n        \".ogg\", \".ogv\", \".mov\", \".wmv\", \".mp4\", \".webm\", \".mp3\", \".wav\", \".mid\",\n        \".rar\", \".zip\", \".tar\", \".gz\", \".7z\", \".bz2\", \".cab\", \".iso\",\n        \".doc\", \".docx\", \".xls\", \".xlsx\", \".ppt\", \".pptx\", \".pdf\", \".txt\", \".md\", \".xml\"\n    ] /* 列出的文件类型 */\n\n}"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/php/controller.php",
    "content": "<?php\n//header('Access-Control-Allow-Origin: http://www.baidu.com'); //设置http://www.baidu.com允许跨域访问\n//header('Access-Control-Allow-Headers: X-Requested-With,X_Requested_With'); //设置允许的跨域header\ndate_default_timezone_set(\"Asia/chongqing\");\nerror_reporting(E_ERROR);\nheader(\"Content-Type: text/html; charset=utf-8\");\n\n$CONFIG = json_decode(preg_replace(\"/\\/\\*[\\s\\S]+?\\*\\//\", \"\", file_get_contents(\"config.json\")), true);\n$action = $_GET['action'];\n\nswitch ($action) {\n    case 'config':\n        $result =  json_encode($CONFIG);\n        break;\n\n    /* 上传图片 */\n    case 'uploadimage':\n    /* 上传涂鸦 */\n    case 'uploadscrawl':\n    /* 上传视频 */\n    case 'uploadvideo':\n    /* 上传文件 */\n    case 'uploadfile':\n        $result = include(\"action_upload.php\");\n        break;\n\n    /* 列出图片 */\n    case 'listimage':\n        $result = include(\"action_list.php\");\n        break;\n    /* 列出文件 */\n    case 'listfile':\n        $result = include(\"action_list.php\");\n        break;\n\n    /* 抓取远程文件 */\n    case 'catchimage':\n        $result = include(\"action_crawler.php\");\n        break;\n\n    default:\n        $result = json_encode(array(\n            'state'=> '请求地址出错'\n        ));\n        break;\n}\n\n/* 输出结果 */\nif (isset($_GET[\"callback\"])) {\n    if (preg_match(\"/^[\\w_]+$/\", $_GET[\"callback\"])) {\n        echo htmlspecialchars($_GET[\"callback\"]) . '(' . $result . ')';\n    } else {\n        echo json_encode(array(\n            'state'=> 'callback参数不合法'\n        ));\n    }\n} else {\n    echo $result;\n}"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/themes/default/css/ueditor.css",
    "content": "/*基础UI构建\r\n*/\r\n/* common layer */\r\n.edui-default .edui-box {\r\n    border: none;\r\n    padding: 0;\r\n    margin: 0;\r\n    overflow: hidden;\r\n}\r\n\r\n.edui-default a.edui-box {\r\n    display: block;\r\n    text-decoration: none;\r\n    color: black;\r\n}\r\n\r\n.edui-default a.edui-box:hover {\r\n    text-decoration: none;\r\n}\r\n\r\n.edui-default a.edui-box:active {\r\n    text-decoration: none;\r\n}\r\n\r\n.edui-default table.edui-box {\r\n    border-collapse: collapse;\r\n}\r\n\r\n.edui-default ul.edui-box {\r\n    list-style-type: none;\r\n}\r\n\r\ndiv.edui-box {\r\n    position: relative;\r\n    display: -moz-inline-box !important;\r\n    display: inline-block !important;\r\n    vertical-align: top;\r\n}\r\n\r\n.edui-default .edui-clearfix {\r\n    zoom: 1\r\n}\r\n\r\n.edui-default .edui-clearfix:after {\r\n    content: '\\20';\r\n    display: block;\r\n    clear: both;\r\n}\r\n\r\n * html div.edui-box {\r\n    display: inline !important;\r\n}\r\n\r\n*:first-child+html div.edui-box {\r\n    display: inline !important;\r\n}\r\n\r\n/* control layout */\r\n.edui-default .edui-button-body, .edui-splitbutton-body, .edui-menubutton-body, .edui-combox-body {\r\n    position: relative;\r\n}\r\n\r\n.edui-default .edui-popup {\r\n    position: absolute;\r\n    -webkit-user-select: none;\r\n    -moz-user-select: none;\r\n}\r\n\r\n.edui-default .edui-popup .edui-shadow {\r\n    position: absolute;\r\n    z-index: -1;\r\n}\r\n\r\n.edui-default .edui-popup .edui-bordereraser {\r\n    position: absolute;\r\n    overflow: hidden;\r\n}\r\n\r\n.edui-default .edui-tablepicker .edui-canvas {\r\n    position: relative;\r\n}\r\n\r\n.edui-default .edui-tablepicker .edui-canvas .edui-overlay {\r\n    position: absolute;\r\n}\r\n\r\n.edui-default .edui-dialog-modalmask, .edui-dialog-dragmask {\r\n    position: absolute;\r\n    left: 0;\r\n    top: 0;\r\n    width: 100%;\r\n    height: 100%;\r\n}\r\n\r\n.edui-default .edui-toolbar {\r\n    position: relative;\r\n}\r\n\r\n/*\r\n * default theme\r\n */\r\n.edui-default .edui-label {\r\n    cursor: default;\r\n}\r\n\r\n.edui-default span.edui-clickable {\r\n    color: blue;\r\n    cursor: pointer;\r\n    text-decoration: underline;\r\n}\r\n\r\n.edui-default span.edui-unclickable {\r\n    color: gray;\r\n    cursor: default;\r\n}\n/* 工具栏 */\r\n.edui-default .edui-toolbar {\r\n    cursor: default;\r\n    -webkit-user-select: none;\r\n    -moz-user-select: none;\r\n    padding: 1px;\r\n    overflow: hidden; /*全屏下单独一行不占位*/\r\n    zoom: 1;\r\n    width:auto;\r\n    height:auto;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-button,\r\n.edui-default .edui-toolbar .edui-splitbutton,\r\n.edui-default .edui-toolbar .edui-menubutton,\r\n.edui-default .edui-toolbar .edui-combox {\r\n    margin: 1px;\r\n}\n/*UI工具栏、编辑区域、底部*/\r\n.edui-default .edui-editor {\r\n    border: 1px solid #d4d4d4;\r\n    background-color: white;\r\n    position: relative;\r\n    overflow: visible;\r\n    -webkit-border-radius: 4px;\r\n    -moz-border-radius: 4px;\r\n    border-radius: 4px;\r\n}\r\n.edui-editor div{\r\n    width:auto;\r\n    height:auto;\r\n}\r\n.edui-default .edui-editor-toolbarbox {\r\n    position: relative;\r\n    zoom: 1;\r\n    -webkit-box-shadow:0 1px 4px rgba(204, 204, 204, 0.6);\r\n    -moz-box-shadow:0 1px 4px rgba(204, 204, 204, 0.6);\r\n    box-shadow:0 1px 4px rgba(204, 204, 204, 0.6);\r\n    border-top-left-radius:2px;\r\n    border-top-right-radius:2px;\r\n}\r\n\r\n.edui-default .edui-editor-toolbarboxouter {\r\n    border-bottom: 1px solid #d4d4d4;\r\n    background-color: #fafafa;\r\n    background-image: -moz-linear-gradient(top, #ffffff, #f2f2f2);\r\n    background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#f2f2f2));\r\n    background-image: -webkit-linear-gradient(top, #ffffff, #f2f2f2);\r\n    background-image: -o-linear-gradient(top, #ffffff, #f2f2f2);\r\n    background-image: linear-gradient(to bottom, #ffffff, #f2f2f2);\r\n    background-repeat: repeat-x;\r\n    /*border: 1px solid #d4d4d4;*/\r\n    -webkit-border-radius: 4px 4px 0 0;\r\n    -moz-border-radius: 4px 4px 0 0;\r\n    border-radius: 4px 4px 0 0;\r\n    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff2f2f2', GradientType=0);\r\n    *zoom: 1;\r\n    -webkit-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);\r\n    -moz-box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);\r\n    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.065);\r\n}\r\n\r\n.edui-default .edui-editor-toolbarboxinner {\r\n    padding: 2px;\r\n}\r\n\r\n.edui-default .edui-editor-iframeholder {\r\n    position: relative;\r\n    /*for fix ie6 toolbarmsg under iframe bug. relative -> static */\r\n    /*_position: static !important;*\r\n}\r\n\r\n.edui-default .edui-editor-iframeholder textarea {\r\n    font-family: consolas, \"Courier New\", \"lucida console\", monospace;\r\n    font-size: 12px;\r\n    line-height: 18px;\r\n}\r\n\r\n.edui-default .edui-editor-bottombar {\r\n    /*border-top: 1px solid #ccc;*/\r\n    /*height: 20px;*/\r\n    /*width: 40%;*/\r\n    /*float: left;*/\r\n    /*overflow: hidden;*/\r\n}\r\n\r\n.edui-default .edui-editor-bottomContainer {\r\n    overflow: hidden;\r\n}\r\n\r\n.edui-default .edui-editor-bottomContainer table {\r\n    width: 100%;\r\n    height: 0;\r\n    overflow: hidden;\r\n    border-spacing: 0;\r\n}\r\n\r\n.edui-default .edui-editor-bottomContainer td {\r\n    white-space: nowrap;\r\n    border-top: 1px solid #ccc;\r\n    line-height: 20px;\r\n    font-size: 12px;\r\n    font-family: Arial, Helvetica, Tahoma, Verdana, Sans-Serif;\r\n}\r\n\r\n.edui-default .edui-editor-wordcount {\r\n    text-align: right;\r\n    margin-right: 5px;\r\n    color: #aaa;\r\n}\r\n.edui-default .edui-editor-scale {\r\n    width: 12px;\r\n}\r\n.edui-default .edui-editor-scale .edui-editor-icon {\r\n    float: right;\r\n    width: 100%;\r\n    height: 12px;\r\n    margin-top: 10px;\r\n    background: url(../images/scale.png) no-repeat;\r\n    cursor: se-resize;\r\n}\r\n.edui-default .edui-editor-breadcrumb {\r\n    margin: 2px 0 0 3px;\r\n}\r\n\r\n.edui-default .edui-editor-breadcrumb span {\r\n    cursor: pointer;\r\n    text-decoration: underline;\r\n    color: blue;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-for-fullscreen {\r\n    float: right;\r\n}\r\n\r\n.edui-default .edui-bubble .edui-popup-content {\r\n    border: 1px solid #DCAC6C;\r\n    background-color: #fff6d9;\r\n    padding: 5px;\r\n    font-size: 10pt;\r\n    font-family: \"宋体\";\r\n}\r\n\r\n.edui-default .edui-bubble .edui-shadow {\r\n    /*box-shadow: 1px 1px 3px #818181;*/\r\n    /*-webkit-box-shadow: 2px 2px 3px #818181;*/\r\n    /*-moz-box-shadow: 2px 2px 3px #818181;*/\r\n    /*filter: progid:DXImageTransform.Microsoft.Blur(PixelRadius = '2', MakeShadow = 'true', ShadowOpacity = '0.5');*/\r\n}\r\n\r\n.edui-default .edui-editor-toolbarmsg {\r\n    background-color: #FFF6D9;\r\n    border-bottom: 1px solid #ccc;\r\n    position: absolute;\r\n    bottom: -25px;\r\n    left: 0;\r\n    z-index: 1009;\r\n    width: 99.9%;\r\n}\r\n\r\n.edui-default .edui-editor-toolbarmsg-upload {\r\n    font-size: 14px;\r\n    color: blue;\r\n    width: 100px;\r\n    height: 16px;\r\n    line-height: 16px;\r\n    cursor: pointer;\r\n    position: absolute;\r\n    top: 5px;\r\n    left: 350px;\r\n}\r\n\r\n.edui-default .edui-editor-toolbarmsg-label {\r\n    font-size: 12px;\r\n    line-height: 16px;\r\n    padding: 4px;\r\n}\r\n\r\n.edui-default .edui-editor-toolbarmsg-close {\r\n    float: right;\r\n    width: 20px;\r\n    height: 16px;\r\n    line-height: 16px;\r\n    cursor: pointer;\r\n    color: red;\r\n}\n/*可选中菜单按钮*/\r\n.edui-default .edui-list .edui-bordereraser {\r\n    display: none;\r\n}\r\n\r\n.edui-default .edui-listitem {\r\n    padding: 1px;\r\n    white-space: nowrap;\r\n}\r\n\r\n.edui-default .edui-list .edui-state-hover {\r\n    position: relative;\r\n    background-color: #fff5d4;\r\n    border: 1px solid #dcac6c;\r\n    padding: 0;\r\n}\r\n\r\n.edui-default .edui-for-fontfamily .edui-listitem-label {\r\n    min-width: 130px;\r\n    _width: 120px;\r\n    font-size: 12px;\r\n    height: 22px;\r\n    line-height: 22px;\r\n    padding-left: 5px;\r\n}\r\n.edui-default .edui-for-insertcode .edui-listitem-label {\r\n    min-width: 120px;\r\n    _width: 120px;\r\n    font-size: 12px;\r\n    height: 22px;\r\n    line-height: 22px;\r\n    padding-left: 5px;\r\n}\r\n.edui-default .edui-for-underline .edui-listitem-label {\r\n    min-width: 120px;\r\n    _width: 120px;\r\n    padding: 3px 5px;\r\n    font-size: 12px;\r\n}\r\n\r\n.edui-default .edui-for-fontsize .edui-listitem-label {\r\n    min-width: 120px;\r\n    _width: 120px;\r\n    padding: 3px 5px;\r\n\r\n}\r\n\r\n.edui-default .edui-for-paragraph .edui-listitem-label {\r\n    min-width: 200px;\r\n    _width: 200px;\r\n    padding: 2px 5px;\r\n}\r\n\r\n.edui-default .edui-for-rowspacingtop .edui-listitem-label,\r\n.edui-default .edui-for-rowspacingbottom .edui-listitem-label {\r\n    min-width: 53px;\r\n    _width: 53px;\r\n    padding: 2px 5px;\r\n}\r\n\r\n.edui-default .edui-for-lineheight .edui-listitem-label {\r\n    min-width: 53px;\r\n    _width: 53px;\r\n    padding: 2px 5px;\r\n}\r\n\r\n.edui-default .edui-for-customstyle .edui-listitem-label {\r\n    min-width: 200px;\r\n    _width: 200px;\r\n    width: 200px !important;\r\n    padding: 2px 5px;\r\n}\n/* 可选中按钮弹出菜单*/\r\n.edui-default .edui-menu {\r\n    z-index: 3000;\r\n}\r\n\r\n.edui-default .edui-menu .edui-popup-content {\r\n    padding: 3px;\r\n}\r\n\r\n.edui-default .edui-menu-body {\r\n    _width: 150px;\r\n    min-width: 170px;\r\n    background: url(\"../images/sparator_v.png\") repeat-y 25px;\r\n}\r\n\r\n.edui-default .edui-menuitem-body {\r\n}\r\n\r\n.edui-default .edui-menuitem {\r\n    height: 20px;\r\n    cursor: default;\r\n    vertical-align: top;\r\n}\r\n\r\n.edui-default .edui-menuitem .edui-icon {\r\n    width: 20px !important;\r\n    height: 20px !important;\r\n    background: url(../images/icons.png) 0 -4000px;\r\n    background: url(../images/icons.gif) 0 -4000px\\9;\r\n}\r\n\r\n.edui-default .edui-menuitem .edui-label {\r\n    font-size: 12px;\r\n    line-height: 20px;\r\n    height: 20px;\r\n    padding-left: 10px;\r\n}\r\n\r\n.edui-default .edui-state-checked .edui-menuitem-body {\r\n    background: url(\"../images/icons-all.gif\") no-repeat 6px -205px;\r\n}\r\n\r\n.edui-default .edui-state-disabled .edui-menuitem-label {\r\n    color: gray;\r\n}\r\n\r\n\n/*不可选中菜单按钮 */\r\n.edui-default .edui-toolbar .edui-combox-body .edui-button-body {\r\n    width: 60px;\r\n    font-size: 12px;\r\n    height: 20px;\r\n    line-height: 20px;\r\n    padding-left: 5px;\r\n    white-space: nowrap;\r\n    margin: 0 3px 0 0;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-combox-body .edui-arrow {\r\n    background: url(../images/icons.png) -741px 0;\r\n    _background: url(../images/icons.gif) -741px 0;\r\n    height: 20px;\r\n    width: 9px;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-combox .edui-combox-body {\r\n    border: 1px solid #CCC;\r\n    background-color: white;\r\n    border-radius: 2px;\r\n    -webkit-border-radius: 2px;\r\n    -moz-border-radius: 2px;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-combox-body .edui-splitborder {\r\n    display: none;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-combox-body .edui-arrow {\r\n    border-left: 1px solid #CCC;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-state-hover .edui-combox-body {\r\n    background-color: #fff5d4;\r\n    border: 1px solid #dcac6c;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-state-hover .edui-combox-body .edui-arrow {\r\n    border-left: 1px solid #dcac6c;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-state-checked .edui-combox-body {\r\n    background-color: #FFE69F;\r\n    border: 1px solid #DCAC6C;\r\n}\r\n\r\n.edui-toolbar .edui-state-checked .edui-combox-body .edui-arrow {\r\n    border-left: 1px solid #DCAC6C;\r\n}\r\n\r\n.edui-toolbar .edui-state-disabled .edui-combox-body {\r\n    background-color: #F0F0EE;\r\n    opacity: 0.3;\r\n    filter: alpha(opacity = 30);\r\n}\r\n\r\n.edui-toolbar .edui-state-opened .edui-combox-body {\r\n    background-color: white;\r\n    border: 1px solid gray;\r\n}\n/*普通按钮样式及状态*/\r\n.edui-default .edui-toolbar .edui-button .edui-icon,\r\n.edui-default .edui-toolbar .edui-menubutton .edui-icon,\r\n.edui-default .edui-toolbar .edui-splitbutton .edui-icon {\r\n    height: 20px !important;\r\n    width: 20px !important;\r\n    background-image: url(../images/icons.png);\r\n    background-image: url(../images/icons.gif) \\9;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-button .edui-button-wrap {\r\n    padding: 1px;\r\n    position: relative;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-button .edui-state-hover .edui-button-wrap {\r\n    background-color: #fff5d4;\r\n    padding: 0;\r\n    border: 1px solid #dcac6c;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-button .edui-state-checked .edui-button-wrap {\r\n    background-color: #ffe69f;\r\n    padding: 0;\r\n    border: 1px solid #dcac6c;\r\n    border-radius: 2px;\r\n    -webkit-border-radius: 2px;\r\n    -moz-border-radius: 2px;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-button .edui-state-active .edui-button-wrap {\r\n    background-color: #ffffff;\r\n    padding: 0;\r\n    border: 1px solid gray;\r\n}\r\n.edui-default .edui-toolbar .edui-state-disabled .edui-label {\r\n    color: #ccc;\r\n}\r\n.edui-default .edui-toolbar .edui-state-disabled .edui-icon {\r\n    opacity: 0.3;\r\n    filter: alpha(opacity = 30);\r\n}\r\n\n/* toolbar icons */\r\n.edui-default .edui-for-undo .edui-icon {\r\n    background-position: -160px 0;\r\n}\r\n\r\n.edui-default  .edui-for-redo .edui-icon {\r\n    background-position: -100px 0;\r\n}\r\n\r\n.edui-default  .edui-for-bold .edui-icon {\r\n    background-position: 0 0;\r\n}\r\n\r\n.edui-default  .edui-for-italic .edui-icon {\r\n    background-position: -60px 0;\r\n}\r\n\r\n.edui-default  .edui-for-fontborder .edui-icon {\r\n    background-position:-160px -40px;\r\n}\r\n.edui-default  .edui-for-underline .edui-icon {\r\n    background-position: -140px 0;\r\n}\r\n\r\n.edui-default  .edui-for-strikethrough .edui-icon {\r\n    background-position: -120px 0;\r\n}\r\n\r\n.edui-default  .edui-for-subscript .edui-icon {\r\n    background-position: -600px 0;\r\n}\r\n\r\n.edui-default  .edui-for-superscript .edui-icon {\r\n    background-position: -620px 0;\r\n}\r\n\r\n.edui-default  .edui-for-blockquote .edui-icon {\r\n    background-position: -220px 0;\r\n}\r\n\r\n.edui-default  .edui-for-forecolor .edui-icon {\r\n    background-position: -720px 0;\r\n}\r\n\r\n.edui-default  .edui-for-backcolor .edui-icon {\r\n    background-position: -760px 0;\r\n}\r\n\r\n.edui-default  .edui-for-inserttable .edui-icon {\r\n    background-position: -580px -20px;\r\n}\r\n\r\n.edui-default  .edui-for-autotypeset .edui-icon {\r\n    background-position: -640px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-justifyleft .edui-icon {\r\n    background-position: -460px 0;\r\n}\r\n\r\n.edui-default  .edui-for-justifycenter .edui-icon {\r\n    background-position: -420px 0;\r\n}\r\n\r\n.edui-default  .edui-for-justifyright .edui-icon {\r\n    background-position: -480px 0;\r\n}\r\n\r\n.edui-default  .edui-for-justifyjustify .edui-icon {\r\n    background-position: -440px 0;\r\n}\r\n\r\n.edui-default  .edui-for-insertorderedlist .edui-icon {\r\n    background-position: -80px 0;\r\n}\r\n\r\n.edui-default  .edui-for-insertunorderedlist .edui-icon {\r\n    background-position: -20px 0;\r\n}\r\n\r\n.edui-default  .edui-for-lineheight .edui-icon {\r\n    background-position: -725px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-rowspacingbottom .edui-icon {\r\n    background-position: -745px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-rowspacingtop .edui-icon {\r\n    background-position: -765px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-horizontal .edui-icon {\r\n    background-position: -360px 0;\r\n}\r\n\r\n.edui-default  .edui-for-link .edui-icon {\r\n    background-position: -500px 0;\r\n}\r\n\r\n.edui-default  .edui-for-code .edui-icon {\r\n    background-position: -440px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-insertimage .edui-icon {\r\n    background-position: -726px -77px;\r\n}\r\n\r\n.edui-default  .edui-for-insertframe .edui-icon {\r\n    background-position: -240px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-emoticon .edui-icon {\r\n    background-position: -60px -20px;\r\n}\r\n\r\n.edui-default  .edui-for-spechars .edui-icon {\r\n    background-position: -240px 0;\r\n}\r\n\r\n.edui-default  .edui-for-help .edui-icon {\r\n    background-position: -340px 0;\r\n}\r\n\r\n.edui-default  .edui-for-print .edui-icon {\r\n    background-position: -440px -20px;\r\n}\r\n\r\n.edui-default  .edui-for-preview .edui-icon {\r\n    background-position: -420px -20px;\r\n}\r\n\r\n.edui-default  .edui-for-selectall .edui-icon {\r\n    background-position: -400px -20px;\r\n}\r\n\r\n.edui-default  .edui-for-searchreplace .edui-icon {\r\n    background-position: -520px -20px;\r\n}\r\n\r\n.edui-default  .edui-for-map .edui-icon {\r\n    background-position: -40px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-gmap .edui-icon {\r\n    background-position: -260px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-insertvideo .edui-icon {\r\n    background-position: -320px -20px;\r\n}\r\n\r\n.edui-default  .edui-for-time .edui-icon {\r\n    background-position: -160px -20px;\r\n}\r\n\r\n.edui-default  .edui-for-date .edui-icon {\r\n    background-position: -140px -20px;\r\n}\r\n\r\n.edui-default  .edui-for-cut .edui-icon {\r\n    background-position: -680px 0;\r\n}\r\n\r\n.edui-default  .edui-for-copy .edui-icon {\r\n    background-position: -700px 0;\r\n}\r\n\r\n.edui-default  .edui-for-paste .edui-icon {\r\n    background-position: -560px 0;\r\n}\r\n\r\n.edui-default  .edui-for-formatmatch .edui-icon {\r\n    background-position: -40px 0;\r\n}\r\n\r\n.edui-default  .edui-for-pasteplain .edui-icon {\r\n    background-position: -360px -20px;\r\n}\r\n\r\n.edui-default  .edui-for-directionalityltr .edui-icon {\r\n    background-position: -20px -20px;\r\n}\r\n\r\n.edui-default  .edui-for-directionalityrtl .edui-icon {\r\n    background-position: -40px -20px;\r\n}\r\n\r\n.edui-default  .edui-for-source .edui-icon {\r\n    background-position: -261px -0px;\r\n}\r\n\r\n.edui-default  .edui-for-removeformat .edui-icon {\r\n    background-position: -580px 0;\r\n}\r\n\r\n.edui-default  .edui-for-unlink .edui-icon {\r\n    background-position: -640px 0;\r\n}\r\n\r\n.edui-default  .edui-for-touppercase .edui-icon {\r\n    background-position: -786px 0;\r\n}\r\n\r\n.edui-default  .edui-for-tolowercase .edui-icon {\r\n    background-position: -806px 0;\r\n}\r\n\r\n.edui-default  .edui-for-insertrow .edui-icon {\r\n    background-position: -478px -76px;\r\n}\r\n\r\n.edui-default  .edui-for-insertrownext .edui-icon {\r\n    background-position: -498px -76px;\r\n}\r\n\r\n.edui-default  .edui-for-insertcol .edui-icon {\r\n    background-position: -455px -76px;\r\n}\r\n\r\n.edui-default  .edui-for-insertcolnext  .edui-icon {\r\n    background-position: -429px -76px;\r\n}\r\n\r\n.edui-default  .edui-for-mergeright .edui-icon {\r\n    background-position: -60px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-mergedown .edui-icon {\r\n    background-position: -80px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-splittorows .edui-icon {\r\n    background-position: -100px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-splittocols .edui-icon {\r\n    background-position: -120px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-insertparagraphbeforetable .edui-icon {\r\n    background-position: -140px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-deleterow .edui-icon {\r\n    background-position: -660px -20px;\r\n}\r\n\r\n.edui-default  .edui-for-deletecol .edui-icon {\r\n    background-position: -640px -20px;\r\n}\r\n\r\n.edui-default  .edui-for-splittocells .edui-icon {\r\n    background-position: -800px -20px;\r\n}\r\n\r\n.edui-default  .edui-for-mergecells .edui-icon {\r\n    background-position: -760px -20px;\r\n}\r\n\r\n.edui-default  .edui-for-deletetable .edui-icon {\r\n    background-position: -620px -20px;\r\n}\r\n\r\n.edui-default  .edui-for-cleardoc .edui-icon {\r\n    background-position: -520px 0;\r\n}\r\n\r\n.edui-default  .edui-for-fullscreen .edui-icon {\r\n    background-position: -100px -20px;\r\n}\r\n\r\n.edui-default  .edui-for-anchor .edui-icon {\r\n    background-position: -200px 0;\r\n}\r\n\r\n.edui-default  .edui-for-pagebreak .edui-icon {\r\n    background-position: -460px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-imagenone .edui-icon {\r\n    background-position: -480px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-imageleft .edui-icon {\r\n    background-position: -500px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-wordimage .edui-icon {\r\n    background-position: -660px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-imageright .edui-icon {\r\n    background-position: -520px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-imagecenter .edui-icon {\r\n    background-position: -540px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-indent .edui-icon {\r\n    background-position: -400px 0;\r\n}\r\n\r\n.edui-default  .edui-for-outdent .edui-icon {\r\n    background-position: -540px 0;\r\n}\r\n\r\n.edui-default  .edui-for-webapp .edui-icon {\r\n    background-position: -601px -40px\r\n}\r\n\r\n.edui-default  .edui-for-table .edui-icon {\r\n    background-position: -580px -20px;\r\n}\r\n\r\n.edui-default  .edui-for-edittable .edui-icon {\r\n    background-position: -420px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-template .edui-icon {\r\n    background-position: -339px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-delete .edui-icon {\r\n    background-position: -360px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-attachment .edui-icon {\r\n    background-position: -620px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-edittd .edui-icon {\r\n    background-position: -700px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-snapscreen .edui-icon {\r\n    background-position: -581px -40px\r\n}\r\n\r\n.edui-default  .edui-for-scrawl .edui-icon {\r\n    background-position: -801px -41px\r\n}\r\n\r\n.edui-default  .edui-for-background .edui-icon {\r\n    background-position: -680px -40px;\r\n}\r\n\r\n.edui-default  .edui-for-music .edui-icon {\r\n    background-position: -18px -40px\r\n}\r\n\r\n.edui-default  .edui-for-formula .edui-icon {\r\n    background-position: -200px -40px\r\n}\r\n\r\n.edui-default  .edui-for-aligntd  .edui-icon {\r\n    background-position: -236px -76px;\r\n}\r\n\r\n.edui-default  .edui-for-insertparagraphtrue  .edui-icon {\r\n    background-position: -625px -76px;\r\n}\r\n\r\n.edui-default  .edui-for-insertparagraph  .edui-icon {\r\n    background-position: -602px -76px;\r\n}\r\n\r\n.edui-default  .edui-for-insertcaption  .edui-icon {\r\n    background-position: -336px -76px;\r\n}\r\n\r\n.edui-default  .edui-for-deletecaption  .edui-icon {\r\n    background-position: -362px -76px;\r\n}\r\n\r\n.edui-default  .edui-for-inserttitle  .edui-icon {\r\n    background-position: -286px -76px;\r\n}\r\n\r\n.edui-default  .edui-for-deletetitle  .edui-icon {\r\n    background-position: -311px -76px;\r\n}\r\n\r\n.edui-default  .edui-for-aligntable  .edui-icon {\r\n    background-position: -440px 0;\r\n}\r\n\r\n.edui-default  .edui-for-tablealignment-left  .edui-icon {\r\n    background-position: -460px 0;\r\n}\r\n\r\n.edui-default  .edui-for-tablealignment-center  .edui-icon {\r\n    background-position: -420px 0;\r\n}\r\n\r\n.edui-default  .edui-for-tablealignment-right  .edui-icon {\r\n    background-position: -480px 0;\r\n}\r\n\r\n.edui-default  .edui-for-drafts  .edui-icon {\r\n    background-position: -560px 0;\r\n}\r\n\r\n.edui-default  .edui-for-charts  .edui-icon {\r\n    background: url( ../images/charts.png ) no-repeat 2px 3px!important;\r\n}\r\n\r\n.edui-default  .edui-for-inserttitlecol  .edui-icon {\r\n    background-position: -673px -76px;\r\n}\r\n\r\n.edui-default  .edui-for-deletetitlecol  .edui-icon {\r\n    background-position: -698px -76px;\r\n}\r\n\r\n.edui-default  .edui-for-simpleupload  .edui-icon {\r\n    background-position: -380px 0px;\r\n}\n/*splitbutton*/\r\n.edui-default .edui-toolbar .edui-splitbutton-body .edui-arrow,\r\n.edui-default .edui-toolbar .edui-menubutton-body .edui-arrow {\r\n    background: url(../images/icons.png) -741px 0;\r\n    _background: url(../images/icons.gif) -741px 0;\r\n    height: 20px;\r\n    width: 9px;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-splitbutton .edui-splitbutton-body,\r\n.edui-default .edui-toolbar .edui-menubutton .edui-menubutton-body {\r\n    padding: 1px;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-splitborder {\r\n    width: 1px;\r\n    height: 20px;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-state-hover .edui-splitborder {\r\n    width: 1px;\r\n    border-left: 0px solid #dcac6c;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-state-active .edui-splitborder {\r\n    width: 0;\r\n    border-left: 1px solid gray;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-state-opened .edui-splitborder {\r\n    width: 1px;\r\n    border: 0;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-splitbutton .edui-state-hover .edui-splitbutton-body,\r\n.edui-default .edui-toolbar .edui-menubutton .edui-state-hover .edui-menubutton-body {\r\n    background-color: #fff5d4;\r\n    border: 1px solid #dcac6c;\r\n    padding: 0;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-splitbutton .edui-state-checked .edui-splitbutton-body,\r\n.edui-default .edui-toolbar .edui-menubutton .edui-state-checked .edui-menubutton-body {\r\n    background-color: #FFE69F;\r\n    border: 1px solid #DCAC6C;\r\n    padding: 0;\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-splitbutton .edui-state-active .edui-splitbutton-body,\r\n.edui-default .edui-toolbar .edui-menubutton .edui-state-active .edui-menubutton-body {\r\n    background-color: #ffffff;\r\n    border: 1px solid gray;\r\n    padding: 0;\r\n}\r\n\r\n.edui-default .edui-state-disabled .edui-arrow {\r\n    opacity: 0.3;\r\n    _filter: alpha(opacity = 30);\r\n}\r\n\r\n.edui-default .edui-toolbar .edui-splitbutton .edui-state-opened .edui-splitbutton-body,\r\n.edui-default .edui-toolbar .edui-menubutton .edui-state-opened .edui-menubutton-body {\r\n    background-color: white;\r\n    border: 1px solid gray;\r\n    padding: 0;\r\n}\r\n\r\n.edui-default .edui-for-insertorderedlist .edui-bordereraser,\r\n.edui-default .edui-for-lineheight .edui-bordereraser,\r\n.edui-default .edui-for-rowspacingtop .edui-bordereraser,\r\n.edui-default .edui-for-rowspacingbottom .edui-bordereraser,\r\n.edui-default .edui-for-insertunorderedlist .edui-bordereraser {\r\n    background-color: white;\r\n}\r\n\r\n/* 解决嵌套导致的图标问题 */\r\n.edui-default .edui-for-insertorderedlist .edui-popup-body .edui-icon,\r\n.edui-default .edui-for-lineheight .edui-popup-body .edui-icon,\r\n.edui-default .edui-for-rowspacingtop .edui-popup-body .edui-icon,\r\n.edui-default .edui-for-rowspacingbottom .edui-popup-body .edui-icon,\r\n.edui-default .edui-for-insertunorderedlist .edui-popup-body .edui-icon {\r\n    /*background-position: 0 -40px;*/\r\n    background-image: none  ;\r\n}\r\n\n/* 弹出菜单 */\r\n.edui-default .edui-popup {\r\n    z-index: 3000;\r\n    background-color: #ffffff;\r\n    width:auto;\r\n    height:auto;\r\n\r\n}\r\n\r\n.edui-default .edui-popup .edui-shadow {\r\n    left: 0;\r\n    top: 0;\r\n    width: 100%;\r\n    height: 100%;\r\n}\r\n\r\n.edui-default .edui-popup-content {\r\n    border:1px solid #ccc;\r\n    border: 1px solid rgba(0, 0, 0, 0.2);\r\n    *border-right-width: 2px;\r\n    *border-bottom-width: 2px;\r\n    -webkit-border-radius: 6px;\r\n    -moz-border-radius: 6px;\r\n    border-radius: 6px;\r\n    -webkit-box-shadow: 0 3px 4px rgba(0, 0, 0, 0.2);\r\n    -moz-box-shadow: 0 3px 4px rgba(0, 0, 0, 0.2);\r\n    box-shadow: 0 3px 4px rgba(0, 0, 0, 0.2);\r\n    -webkit-background-clip: padding-box;\r\n    -moz-background-clip: padding;\r\n    background-clip: padding-box;\r\n    padding: 5px;\r\n    background:#ffffff;\r\n}\r\n\r\n.edui-default .edui-popup .edui-bordereraser {\r\n    background-color: white;\r\n    height: 3px;\r\n}\r\n\r\n.edui-default .edui-menu .edui-bordereraser {\r\n    height: 3px;\r\n}\r\n\r\n.edui-default .edui-anchor-topleft .edui-bordereraser {\r\n    left: 1px;\r\n    top: -2px;\r\n}\r\n\r\n.edui-default .edui-anchor-topright .edui-bordereraser {\r\n    right: 1px;\r\n    top: -2px;\r\n}\r\n\r\n.edui-default .edui-anchor-bottomleft .edui-bordereraser {\r\n    left: 0;\r\n    bottom: -6px;\r\n    height: 7px;\r\n    border-left: 1px solid gray;\r\n    border-right: 1px solid gray;\r\n}\r\n\r\n.edui-default .edui-anchor-bottomright .edui-bordereraser {\r\n    right: 0;\r\n    bottom: -6px;\r\n    height: 7px;\r\n    border-left: 1px solid gray;\r\n    border-right: 1px solid gray;\r\n}\r\n\r\n.edui-popup div{\r\n    width:auto;\r\n    height:auto;\r\n}\n.edui-default .edui-editor-messageholder {\n    display: block;\n    width: 150px;\n    height: auto;\n    border: 0;\n    margin: 0;\n    padding: 0;\n    position: absolute;\n    top: 28px;\n    right: 3px;\n}\n\n.edui-default .edui-message{\n    min-height: 10px;\n    text-shadow: 0 1px 0 rgba(255,255,255,0.5);\n    padding: 0;\n    margin-bottom: 3px;\n    position: relative;\n}\n.edui-default .edui-message-body{\n    border-radius: 3px;\n    padding: 8px 15px 8px 8px;\n    color: #c09853;\n    background-color: #fcf8e3;\n    border: 1px solid #fbeed5;\n}\n.edui-default .edui-message-type-info{\n    color: #3a87ad;\n    background-color: #d9edf7;\n    border-color: #bce8f1\n}\n.edui-default .edui-message-type-success{\n    color: #468847;\n    background-color: #dff0d8;\n    border-color: #d6e9c6\n}\n.edui-default .edui-message-type-danger,\n.edui-default .edui-message-type-error{\n    color: #b94a48;\n    background-color: #f2dede;\n    border-color: #eed3d7\n}\n.edui-default .edui-message .edui-message-closer {\n    display: block;\n    width: 16px;\n    height: 16px;\n    line-height: 16px;\n    position: absolute;\n    top: 0;\n    right: 0;\n    padding: 0;\n    cursor: pointer;\n    background: transparent;\n    border: 0;\n    float: right;\n    font-size: 20px;\n    font-weight: bold;\n    color: #999;\n    text-shadow: 0 1px 0 #fff;\n    font-family: \"Helvetica Neue\",Helvetica,Arial,sans-serif;\n}\n.edui-default .edui-message .edui-message-content {\n    font-size: 10pt;\n    word-wrap: break-word;\n    word-break: normal;\n}\n/* 弹出对话框按钮和对话框大小 */\r\n.edui-default .edui-dialog {\r\n    z-index: 2000;\r\n    position: absolute;\r\n\r\n}\r\n\r\n.edui-dialog div{\r\n    width:auto;\r\n}\r\n\r\n.edui-default .edui-dialog-wrap {\r\n    margin-right: 6px;\r\n    margin-bottom: 6px;\r\n}\r\n\r\n.edui-default .edui-dialog-fullscreen-flag {\r\n    margin-right: 0;\r\n    margin-bottom: 0;\r\n}\r\n\r\n.edui-default .edui-dialog-body {\r\n    position: relative;\r\n    padding:2px 0 0 2px;\r\n    _zoom: 1;\r\n}\r\n\r\n.edui-default .edui-dialog-fullscreen-flag .edui-dialog-body {\r\n    padding: 0;\r\n}\r\n\r\n.edui-default .edui-dialog-shadow {\r\n    position: absolute;\r\n    z-index: -1;\r\n    left: 0;\r\n    top: 0;\r\n    width: 100%;\r\n    height: 100%;\r\n    background-color: #ffffff;\r\n    border: 1px solid #ccc;\r\n    border: 1px solid rgba(0, 0, 0, 0.2);\r\n    *border-right-width: 2px;\r\n    *border-bottom-width: 2px;\r\n    -webkit-border-radius: 6px;\r\n    -moz-border-radius: 6px;\r\n    border-radius: 6px;\r\n    -webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\r\n    -moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\r\n    box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);\r\n    -webkit-background-clip: padding-box;\r\n    -moz-background-clip: padding;\r\n    background-clip: padding-box;\r\n}\r\n\r\n.edui-default .edui-dialog-foot {\r\n    background-color: white;\r\n}\r\n\r\n.edui-default .edui-dialog-titlebar {\r\n    height: 26px;\r\n    border-bottom: 1px solid #c6c6c6;\r\n    background: url(../images/dialog-title-bg.png) repeat-x bottom;\r\n    position: relative;\r\n    cursor: move;\r\n}\r\n.edui-default .edui-dialog-caption {\r\n    font-weight: bold;\r\n    font-size: 12px;\r\n    line-height: 26px;\r\n    padding-left: 5px;\r\n}\r\n\r\n.edui-default .edui-dialog-draghandle {\r\n    height: 26px;\r\n}\r\n\r\n.edui-default .edui-dialog-closebutton {\r\n    position: absolute !important;\r\n    right: 5px;\r\n    top: 3px;\r\n}\r\n\r\n.edui-default .edui-dialog-closebutton .edui-button-body {\r\n    height: 20px;\r\n    width: 20px;\r\n    cursor: pointer;\r\n    background: url(\"../images/icons-all.gif\") no-repeat 0 -59px;\r\n}\r\n\r\n.edui-default .edui-dialog-closebutton .edui-state-hover .edui-button-body {\r\n    background: url(\"../images/icons-all.gif\") no-repeat 0 -89px;\r\n}\r\n\r\n.edui-default .edui-dialog-foot {\r\n    height: 40px;\r\n}\r\n\r\n.edui-default .edui-dialog-buttons {\r\n    position: absolute;\r\n    right: 0;\r\n}\r\n\r\n.edui-default .edui-dialog-buttons .edui-button {\r\n    margin-right: 10px;\r\n}\r\n\r\n.edui-default .edui-dialog-buttons .edui-button .edui-button-body {\r\n    background: url(\"../images/icons-all.gif\") no-repeat;\r\n    height: 24px;\r\n    width: 96px;\r\n    font-size: 12px;\r\n    line-height: 24px;\r\n    text-align: center;\r\n    cursor: default;\r\n}\r\n\r\n.edui-default .edui-dialog-buttons .edui-button .edui-state-hover .edui-button-body {\r\n    background: url(\"../images/icons-all.gif\") no-repeat 0 -30px;\r\n}\r\n\r\n.edui-default .edui-dialog iframe {\r\n    border: 0;\r\n    padding: 0;\r\n    margin: 0;\r\n    vertical-align: top;\r\n}\r\n\r\n.edui-default .edui-dialog-modalmask {\r\n    opacity: 0.3;\r\n    filter: alpha(opacity = 30);\r\n    background-color: #ccc;\r\n    position: absolute;\r\n    /*z-index: 1999;*/\r\n}\r\n\r\n.edui-default .edui-dialog-dragmask {\r\n    position: absolute;\r\n    /*z-index: 2001;*/\r\n    background-color: transparent;\r\n    cursor: move;\r\n}\r\n\r\n.edui-default .edui-dialog-content {\r\n    position: relative;\r\n}\r\n\r\n.edui-default .dialogcontmask {\r\n    cursor: move;\r\n    visibility: hidden;\r\n    display: block;\r\n    position: absolute;\r\n    width: 100%;\r\n    height: 100%;\r\n    opacity: 0;\r\n    filter: alpha(opacity = 0);\r\n}\r\n\r\n/*link-dialog*/\r\n.edui-default .edui-for-link .edui-dialog-content {\r\n    width: 420px;\r\n    height: 200px;\r\n    overflow: hidden;\r\n}\r\n/*background-dialog*/\r\n.edui-default .edui-for-background .edui-dialog-content {\r\n    width: 440px;\r\n    height: 280px;\r\n    overflow: hidden;\r\n}\r\n\r\n/*template-dialog*/\r\n.edui-default .edui-for-template .edui-dialog-content {\r\n    width: 630px;\r\n    height: 390px;\r\n    overflow: hidden;\r\n}\r\n\r\n/*scrawl-dialog*/\r\n.edui-default .edui-for-scrawl .edui-dialog-content {\r\n    width: 515px;\r\n    *width: 506px;\r\n    height: 360px;\r\n}\r\n\r\n/*spechars-dialog*/\r\n.edui-default .edui-for-spechars .edui-dialog-content {\r\n    width: 620px;\r\n    height: 500px;\r\n    *width: 630px;\r\n    *height: 570px;\r\n}\r\n\r\n/*image-dialog*/\r\n.edui-default .edui-for-insertimage .edui-dialog-content {\r\n    width: 650px;\r\n    height: 400px;\r\n    overflow: hidden;\r\n}\r\n/*webapp-dialog*/\r\n.edui-default .edui-for-webapp .edui-dialog-content {\r\n    width: 560px;\r\n    _width: 565px;\r\n    height: 450px;\r\n    overflow: hidden;\r\n}\r\n\r\n/*image-insertframe*/\r\n.edui-default .edui-for-insertframe .edui-dialog-content {\r\n    width: 350px;\r\n    height: 200px;\r\n    overflow: hidden;\r\n}\r\n\r\n/*wordImage-dialog*/\r\n.edui-default .edui-for-wordimage .edui-dialog-content {\r\n    width: 620px;\r\n    height: 380px;\r\n    overflow: hidden;\r\n}\r\n\r\n/*attachment-dialog*/\r\n.edui-default .edui-for-attachment .edui-dialog-content {\r\n    width: 650px;\r\n    height: 400px;\r\n    overflow: hidden;\r\n}\r\n\r\n\r\n/*map-dialog*/\r\n.edui-default .edui-for-map .edui-dialog-content {\r\n    width: 550px;\r\n    height: 400px;\r\n}\r\n\r\n/*gmap-dialog*/\r\n.edui-default .edui-for-gmap .edui-dialog-content {\r\n    width: 550px;\r\n    height: 400px;\r\n}\r\n\r\n/*video-dialog*/\r\n.edui-default .edui-for-insertvideo .edui-dialog-content {\r\n    width: 590px;\r\n    height: 390px;\r\n}\r\n\r\n/*anchor-dialog*/\r\n.edui-default .edui-for-anchor .edui-dialog-content {\r\n    width: 320px;\r\n    height: 60px;\r\n    overflow: hidden;\r\n}\r\n\r\n/*searchreplace-dialog*/\r\n.edui-default .edui-for-searchreplace .edui-dialog-content {\r\n    width: 400px;\r\n    height: 220px;\r\n}\r\n\r\n/*help-dialog*/\r\n.edui-default .edui-for-help .edui-dialog-content {\r\n    width: 400px;\r\n    height: 420px;\r\n}\r\n\r\n/*edittable-dialog*/\r\n.edui-default .edui-for-edittable .edui-dialog-content {\r\n    width: 540px;\r\n    _width:590px;\r\n    height: 335px;\r\n}\r\n\r\n/*edittip-dialog*/\r\n.edui-default .edui-for-edittip .edui-dialog-content {\r\n    width: 225px;\r\n    height: 60px;\r\n}\r\n\r\n/*edittd-dialog*/\r\n.edui-default .edui-for-edittd .edui-dialog-content {\r\n    width: 240px;\r\n    height: 50px;\r\n}\r\n/*snapscreen-dialog*/\r\n.edui-default .edui-for-snapscreen .edui-dialog-content {\r\n    width: 400px;\r\n    height: 220px;\r\n}\r\n\r\n/*music-dialog*/\r\n.edui-default .edui-for-music .edui-dialog-content {\r\n    width: 515px;\r\n    height: 360px;\r\n}\r\n\n/*段落弹出菜单*/\r\n.edui-default .edui-for-paragraph .edui-listitem-label {\r\n    font-family: Tahoma, Verdana, Arial, Helvetica;\r\n}\r\n\r\n.edui-default .edui-for-paragraph .edui-listitem-label .edui-for-p {\r\n    font-size: 22px;\r\n    line-height: 27px;\r\n}\r\n\r\n.edui-default .edui-for-paragraph .edui-listitem-label .edui-for-h1 {\r\n    font-weight: bolder;\r\n    font-size: 32px;\r\n    line-height: 36px;\r\n}\r\n\r\n.edui-default .edui-for-paragraph .edui-listitem-label .edui-for-h2 {\r\n    font-weight: bolder;\r\n    font-size: 27px;\r\n    line-height: 29px;\r\n}\r\n\r\n.edui-default .edui-for-paragraph .edui-listitem-label .edui-for-h3 {\r\n    font-weight: bolder;\r\n    font-size: 19px;\r\n    line-height: 23px;\r\n}\r\n\r\n.edui-default .edui-for-paragraph .edui-listitem-label .edui-for-h4 {\r\n    font-weight: bolder;\r\n    font-size: 16px;\r\n    line-height: 19px\r\n}\r\n\r\n.edui-default .edui-for-paragraph .edui-listitem-label .edui-for-h5 {\r\n    font-weight: bolder;\r\n    font-size: 13px;\r\n    line-height: 16px;\r\n}\r\n\r\n.edui-default .edui-for-paragraph .edui-listitem-label .edui-for-h6 {\r\n    font-weight: bolder;\r\n    font-size: 12px;\r\n    line-height: 14px;\r\n}\n/* 表格弹出菜单 */\r\n.edui-default .edui-for-inserttable .edui-splitborder {\r\n    display: none\r\n}\r\n.edui-default .edui-for-inserttable  .edui-splitbutton-body .edui-arrow {\r\n    width: 0\r\n}\r\n.edui-default .edui-toolbar .edui-for-inserttable  .edui-state-active .edui-splitborder{\r\n    border-left: 1px solid transparent;\r\n}\r\n.edui-default .edui-tablepicker .edui-infoarea {\r\n    height: 14px;\r\n    line-height: 14px;\r\n    font-size: 12px;\r\n    width: 220px;\r\n    margin-bottom: 3px;\r\n    clear: both;\r\n}\r\n\r\n.edui-default .edui-tablepicker .edui-infoarea .edui-label {\r\n    float: left;\r\n}\r\n\r\n.edui-default .edui-dialog-buttons .edui-label {\r\n    line-height: 24px;\r\n}\r\n\r\n.edui-default .edui-tablepicker .edui-infoarea .edui-clickable {\r\n    float: right;\r\n}\r\n\r\n.edui-default .edui-tablepicker .edui-pickarea {\r\n    background: url(\"../images/unhighlighted.gif\") repeat;\r\n    height: 220px;\r\n    width: 220px;\r\n}\r\n\r\n.edui-default .edui-tablepicker .edui-pickarea .edui-overlay {\r\n    background: url(\"../images/highlighted.gif\") repeat;\r\n}\r\n\n/* 颜色弹出菜单 */\r\n.edui-default .edui-colorpicker-topbar {\r\n    height: 27px;\r\n    width: 200px;\r\n    /*border-bottom: 1px gray dashed;*/\r\n}\r\n\r\n.edui-default .edui-colorpicker-preview {\r\n    height: 20px;\r\n    border: 1px inset black;\r\n    margin-left: 1px;\r\n    width: 128px;\r\n    float: left;\r\n}\r\n\r\n.edui-default .edui-colorpicker-nocolor {\r\n    float: right;\r\n    margin-right: 1px;\r\n    font-size: 12px;\r\n    line-height: 14px;\r\n    height: 14px;\r\n    border: 1px solid #333;\r\n    padding: 3px 5px;\r\n    cursor: pointer;\r\n}\r\n\r\n.edui-default .edui-colorpicker-tablefirstrow {\r\n    height: 30px;\r\n}\r\n\r\n.edui-default .edui-colorpicker-colorcell {\r\n    width: 14px;\r\n    height: 14px;\r\n    display: block;\r\n    margin: 0;\r\n    cursor: pointer;\r\n}\r\n\r\n.edui-default .edui-colorpicker-colorcell:hover {\r\n    width: 14px;\r\n    height: 14px;\r\n    margin: 0;\r\n}\r\n.edui-default .edui-colorpicker-advbtn{\r\n    display: block;\r\n    text-align: center;\r\n    cursor: pointer;\r\n    height:20px;\r\n}\r\n.arrow_down{\r\n    background: white url('../images/arrow_down.png') no-repeat center;\r\n}\r\n.arrow_up{\r\n    background: white url('../images/arrow_up.png') no-repeat center;\r\n}\r\n/*高级的样式*/\r\n.edui-colorpicker-adv{\r\n    position: relative;\r\n    overflow: hidden;\r\n    height: 180px;\r\n    display: none;\r\n}\r\n.edui-colorpicker-plant, .edui-colorpicker-hue {\r\n    border: solid 1px #666;\r\n}\r\n.edui-colorpicker-pad {\r\n    width: 150px;\r\n    height: 150px;\r\n    left: 14px;\r\n    top: 13px;\r\n    position: absolute;\r\n    background: red;\r\n    overflow: hidden;\r\n    cursor: crosshair;\r\n}\r\n.edui-colorpicker-cover{\r\n    position: absolute;\r\n    top: 0;\r\n    left: 0;\r\n    width: 150px;\r\n    height: 150px;\r\n    background: url(\"../images/tangram-colorpicker.png\") -160px -200px;\r\n}\r\n.edui-colorpicker-padDot{\r\n    position: absolute;\r\n    top: 0;\r\n    left: 0;\r\n    width: 11px;\r\n    height: 11px;\r\n    overflow: hidden;\r\n    background: url(../images/tangram-colorpicker.png) 0px -200px repeat-x;\r\n    z-index: 1000;\r\n\r\n}\r\n.edui-colorpicker-sliderMain {\r\n    position: absolute;\r\n    left: 171px;\r\n    top: 13px;\r\n    width: 19px;\r\n    height: 152px;\r\n    background: url(../images/tangram-colorpicker.png) -179px -12px no-repeat;\r\n\r\n}\r\n.edui-colorpicker-slider {\r\n    width: 100%;\r\n    height: 100%;\r\n    cursor: pointer;\r\n}\r\n.edui-colorpicker-thumb{\r\n    position: absolute;\r\n    top: 0;\r\n    cursor: pointer;\r\n    height: 3px;\r\n    left: -1px;\r\n    right: -1px;\r\n    border: 1px solid black;\r\n    background: white;\r\n    opacity: .8;\r\n}\n/*自动排版弹出菜单*/\r\n.edui-default .edui-autotypesetpicker .edui-autotypesetpicker-body {\r\n    font-size: 12px;\r\n    margin-bottom: 3px;\r\n    clear: both;\r\n}\r\n\r\n.edui-default .edui-autotypesetpicker-body table {\r\n    border-collapse: separate;\r\n    border-spacing: 2px;\r\n}\r\n\r\n.edui-default .edui-autotypesetpicker-body td {\r\n    font-size: 12px;\r\n    word-wrap:break-word;\r\n}\r\n\r\n.edui-default .edui-autotypesetpicker-body td input {\r\n    margin: 3px 3px 3px 4px;\r\n    *margin: 1px 0 0 0;\r\n}\n/*自动排版弹出菜单*/\n.edui-default .edui-cellalignpicker .edui-cellalignpicker-body {\n    width: 70px;\n    font-size: 12px;\n    cursor: default;\n}\n\n.edui-default .edui-cellalignpicker-body table {\n    border-collapse: separate;\n    border-spacing: 0;\n}\n.edui-default .edui-cellalignpicker-body td{\n    padding: 1px;\n}\n.edui-default .edui-cellalignpicker-body .edui-icon{\n    height: 20px;\n    width: 20px;\n    padding: 1px;\n    background-image: url(../images/table-cell-align.png);\n}\n\n.edui-default .edui-cellalignpicker-body .edui-left{\n    background-position: 0 0;\n}\n\n.edui-default .edui-cellalignpicker-body .edui-center{\n    background-position: -25px 0;\n}\n.edui-default .edui-cellalignpicker-body .edui-right{\n    background-position: -51px 0;\n}\n\n.edui-default .edui-cellalignpicker-body td.edui-state-hover .edui-left{\n    background-position: -73px 0;\n}\n\n.edui-default .edui-cellalignpicker-body td.edui-state-hover .edui-center{\n    background-position: -98px 0;\n}\n\n.edui-default .edui-cellalignpicker-body td.edui-state-hover .edui-right{\n    background-position: -124px 0;\n}\n\n.edui-default .edui-cellalignpicker-body td.edui-cellalign-selected .edui-left {\n    background-position: -146px 0;\n    background-color: #f1f4f5;\n}\n\n.edui-default .edui-cellalignpicker-body td.edui-cellalign-selected .edui-center {\n    background-position: -245px 0;\n}\n\n.edui-default .edui-cellalignpicker-body td.edui-cellalign-selected .edui-right {\n    background-position: -271px 0;\n}\n/*分隔线*/\r\n.edui-default .edui-toolbar .edui-separator {\r\n    width: 2px;\r\n    height: 20px;\r\n    margin: 2px 4px 2px 3px;\r\n    background: url(../images/icons.png) -181px 0;\r\n    background: url(../images/icons.gif) -181px 0 \\9;\r\n}\r\n\n/*颜色按钮 */\r\n.edui-default .edui-toolbar .edui-colorbutton .edui-colorlump {\r\n    position: absolute;\r\n    overflow: hidden;\r\n    bottom: 1px;\r\n    left: 1px;\r\n    width: 18px;\r\n    height: 4px;\r\n}\n/*表情按钮及弹出菜单*/\r\n/*去除了表情的下拉箭头*/\r\n.edui-default .edui-for-emotion .edui-icon {\r\n    background-position: -60px -20px;\r\n}\r\n.edui-default .edui-for-emotion .edui-popup-content iframe\r\n{\r\n    width: 514px;\r\n    height: 380px;\r\n    overflow: hidden;\r\n}\r\n.edui-default .edui-for-emotion .edui-popup-content\r\n{\r\n    position: relative;\r\n    z-index: 555\r\n}\r\n\r\n.edui-default .edui-for-emotion .edui-splitborder {\r\n    display: none\r\n}\r\n\r\n.edui-default .edui-for-emotion .edui-splitbutton-body .edui-arrow\r\n{\r\n    width: 0\r\n}\r\n.edui-default .edui-toolbar .edui-for-emotion  .edui-state-active .edui-splitborder\r\n{\r\n    border-left: 1px solid transparent;\r\n}\n/*contextmenu*/\r\n.edui-default .edui-hassubmenu .edui-arrow {\r\n    height: 20px;\r\n    width: 20px;\r\n    float: right;\r\n    background: url(\"../images/icons-all.gif\") no-repeat 10px -233px;\r\n}\r\n\r\n.edui-default .edui-menu-body .edui-menuitem {\r\n    padding: 1px;\r\n}\r\n\r\n.edui-default .edui-menuseparator {\r\n    margin: 2px 0;\r\n    height: 1px;\r\n    overflow: hidden;\r\n}\r\n\r\n.edui-default .edui-menuseparator-inner {\r\n    border-bottom: 1px solid #e2e3e3;\r\n    margin-left: 29px;\r\n    margin-right: 1px;\r\n}\r\n\r\n.edui-default .edui-menu-body .edui-state-hover {\r\n    padding: 0 !important;\r\n    background-color: #fff5d4;\r\n    border: 1px solid #dcac6c;\r\n}\n/*弹出菜单*/\n.edui-default .edui-shortcutmenu {\n    padding: 2px;\n    width: 190px;\n    height: 50px;\n    background-color: #fff;\n    border: 1px solid #ccc;\n    border-radius: 5px;\n}\n\n/*粘贴弹出菜单*/\n.edui-default .edui-wordpastepop .edui-popup-content{\n    border: none;\n    padding: 0;\n    width: 54px;\n    height: 21px;\n}\n.edui-default  .edui-pasteicon {\n    width: 100%;\n    height: 100%;\n    background-image: url('../images/wordpaste.png');\n    background-position: 0 0;\n}\n\n.edui-default  .edui-pasteicon.edui-state-opened {\n    background-position: 0 -34px;\n}\n\n.edui-default  .edui-pastecontainer {\n    position: relative;\n    visibility: hidden;\n    width: 97px;\n    background: #fff;\n    border: 1px solid #ccc;\n}\n\n.edui-default  .edui-pastecontainer .edui-title {\n    font-weight: bold;\n    background: #F8F8FF;\n    height: 25px;\n    line-height: 25px;\n    font-size: 12px;\n    padding-left: 5px;\n}\n\n.edui-default  .edui-pastecontainer .edui-button {\n    overflow: hidden;\n    margin: 3px 0;\n}\n\n.edui-default  .edui-pastecontainer .edui-button .edui-richtxticon,\n.edui-default  .edui-pastecontainer .edui-button .edui-tagicon,\n.edui-default  .edui-pastecontainer .edui-button .edui-plaintxticon{\n    float: left;\n    cursor: pointer;\n    width: 29px;\n    height: 29px;\n    margin-left: 5px;\n    background-image: url('../images/wordpaste.png');\n    background-repeat: no-repeat;\n}\n.edui-default  .edui-pastecontainer .edui-button .edui-richtxticon {\n    margin-left: 0;\n    background-position: -109px 0;\n}\n.edui-default  .edui-pastecontainer .edui-button .edui-tagicon {\n    background-position: -148px 1px;\n}\n\n.edui-default  .edui-pastecontainer .edui-button .edui-plaintxticon {\n    background-position: -72px 0;\n}\n\n.edui-default  .edui-pastecontainer .edui-button .edui-state-hover .edui-richtxticon {\n    background-position: -109px -34px;\n}\n.edui-default  .edui-pastecontainer .edui-button .edui-state-hover .edui-tagicon{\n    background-position: -148px -34px;\n}\n.edui-default  .edui-pastecontainer .edui-button  .edui-state-hover .edui-plaintxticon{\n    background-position: -72px -34px;\n}"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/themes/default/dialogbase.css",
    "content": "/*弹出对话框页面样式组件\r\n*/\r\n\r\n/*reset\r\n*/\r\nhtml, body, div, span, applet, object, iframe,\r\nh1, h2, h3, h4, h5, h6, p, blockquote, pre,\r\na, abbr, acronym, address, big, cite, code,\r\ndel, dfn, em, font, img, ins, kbd, q, s, samp,\r\nsmall, strike, strong, sub, sup, tt, var,\r\nb, u, i, center,\r\ndl, dt, dd, ol, ul, li,\r\nfieldset, form, label, legend,\r\ntable, caption, tbody, tfoot, thead, tr, th, td {\r\n    margin: 0;\r\n    padding: 0;\r\n    outline: 0;\r\n    font-size: 100%;\r\n}\r\n\r\nbody {\r\n    line-height: 1;\r\n}\r\n\r\nol, ul {\r\n    list-style: none;\r\n}\r\n\r\nblockquote, q {\r\n    quotes: none;\r\n}\r\n\r\nins {\r\n    text-decoration: none;\r\n}\r\n\r\ndel {\r\n    text-decoration: line-through;\r\n}\r\n\r\ntable {\r\n    border-collapse: collapse;\r\n    border-spacing: 0;\r\n}\r\n\r\n/*module\r\n*/\r\nbody {\r\n    background-color: #fff;\r\n    font: 12px/1.5 sans-serif, \"宋体\", \"Arial Narrow\", HELVETICA;\r\n    color: #646464;\r\n}\r\n\r\n/*tab*/\r\n.tabhead {\r\n    position: relative;\r\n    z-index: 10;\r\n}\r\n\r\n.tabhead span {\r\n    display: inline-block;\r\n    padding: 0 5px;\r\n    height: 30px;\r\n    border: 1px solid #ccc;\r\n    background: url(\"images/dialog-title-bg.png\") repeat-x;\r\n    text-align: center;\r\n    line-height: 30px;\r\n    cursor: pointer;\r\n    *margin-right: 5px;\r\n}\r\n\r\n.tabhead span.focus {\r\n    height: 31px;\r\n    border-bottom: none;\r\n    background: #fff;\r\n}\r\n\r\n.tabbody {\r\n    position: relative;\r\n    top: -1px;\r\n    margin: 0 auto;\r\n    border: 1px solid #ccc;\r\n}\r\n\r\n/*button*/\r\na.button {\r\n    display: block;\r\n    text-align: center;\r\n    line-height: 24px;\r\n    text-decoration: none;\r\n    height: 24px;\r\n    width: 95px;\r\n    border: 0;\r\n    color: #838383;\r\n    background: url(../../themes/default/images/icons-all.gif) no-repeat;\r\n}\r\n\r\na.button:hover {\r\n    background-position: 0 -30px;\r\n}"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/themes/iframe.css",
    "content": "/*可以在这里添加你自己的css*/\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/SyntaxHighlighter/shCore.js",
    "content": "// XRegExp 1.5.1\n// (c) 2007-2012 Steven Levithan\n// MIT License\n// <http://xregexp.com>\n// Provides an augmented, extensible, cross-browser implementation of regular expressions,\n// including support for additional syntax, flags, and methods\n\nvar XRegExp;\n\nif (XRegExp) {\n    // Avoid running twice, since that would break references to native globals\n    throw Error(\"can't load XRegExp twice in the same frame\");\n}\n\n// Run within an anonymous function to protect variables and avoid new globals\n(function (undefined) {\n\n    //---------------------------------\n    //  Constructor\n    //---------------------------------\n\n    // Accepts a pattern and flags; returns a new, extended `RegExp` object. Differs from a native\n    // regular expression in that additional syntax and flags are supported and cross-browser\n    // syntax inconsistencies are ameliorated. `XRegExp(/regex/)` clones an existing regex and\n    // converts to type XRegExp\n    XRegExp = function (pattern, flags) {\n        var output = [],\n            currScope = XRegExp.OUTSIDE_CLASS,\n            pos = 0,\n            context, tokenResult, match, chr, regex;\n\n        if (XRegExp.isRegExp(pattern)) {\n            if (flags !== undefined)\n                throw TypeError(\"can't supply flags when constructing one RegExp from another\");\n            return clone(pattern);\n        }\n        // Tokens become part of the regex construction process, so protect against infinite\n        // recursion when an XRegExp is constructed within a token handler or trigger\n        if (isInsideConstructor)\n            throw Error(\"can't call the XRegExp constructor within token definition functions\");\n\n        flags = flags || \"\";\n        context = { // `this` object for custom tokens\n            hasNamedCapture: false,\n            captureNames: [],\n            hasFlag: function (flag) {return flags.indexOf(flag) > -1;},\n            setFlag: function (flag) {flags += flag;}\n        };\n\n        while (pos < pattern.length) {\n            // Check for custom tokens at the current position\n            tokenResult = runTokens(pattern, pos, currScope, context);\n\n            if (tokenResult) {\n                output.push(tokenResult.output);\n                pos += (tokenResult.match[0].length || 1);\n            } else {\n                // Check for native multicharacter metasequences (excluding character classes) at\n                // the current position\n                if (match = nativ.exec.call(nativeTokens[currScope], pattern.slice(pos))) {\n                    output.push(match[0]);\n                    pos += match[0].length;\n                } else {\n                    chr = pattern.charAt(pos);\n                    if (chr === \"[\")\n                        currScope = XRegExp.INSIDE_CLASS;\n                    else if (chr === \"]\")\n                        currScope = XRegExp.OUTSIDE_CLASS;\n                    // Advance position one character\n                    output.push(chr);\n                    pos++;\n                }\n            }\n        }\n\n        regex = RegExp(output.join(\"\"), nativ.replace.call(flags, flagClip, \"\"));\n        regex._xregexp = {\n            source: pattern,\n            captureNames: context.hasNamedCapture ? context.captureNames : null\n        };\n        return regex;\n    };\n\n\n    //---------------------------------\n    //  Public properties\n    //---------------------------------\n\n    XRegExp.version = \"1.5.1\";\n\n    // Token scope bitflags\n    XRegExp.INSIDE_CLASS = 1;\n    XRegExp.OUTSIDE_CLASS = 2;\n\n\n    //---------------------------------\n    //  Private variables\n    //---------------------------------\n\n    var replacementToken = /\\$(?:(\\d\\d?|[$&`'])|{([$\\w]+)})/g,\n        flagClip = /[^gimy]+|([\\s\\S])(?=[\\s\\S]*\\1)/g, // Nonnative and duplicate flags\n        quantifier = /^(?:[?*+]|{\\d+(?:,\\d*)?})\\??/,\n        isInsideConstructor = false,\n        tokens = [],\n    // Copy native globals for reference (\"native\" is an ES3 reserved keyword)\n        nativ = {\n            exec: RegExp.prototype.exec,\n            test: RegExp.prototype.test,\n            match: String.prototype.match,\n            replace: String.prototype.replace,\n            split: String.prototype.split\n        },\n        compliantExecNpcg = nativ.exec.call(/()??/, \"\")[1] === undefined, // check `exec` handling of nonparticipating capturing groups\n        compliantLastIndexIncrement = function () {\n            var x = /^/g;\n            nativ.test.call(x, \"\");\n            return !x.lastIndex;\n        }(),\n        hasNativeY = RegExp.prototype.sticky !== undefined,\n        nativeTokens = {};\n\n    // `nativeTokens` match native multicharacter metasequences only (including deprecated octals,\n    // excluding character classes)\n    nativeTokens[XRegExp.INSIDE_CLASS] = /^(?:\\\\(?:[0-3][0-7]{0,2}|[4-7][0-7]?|x[\\dA-Fa-f]{2}|u[\\dA-Fa-f]{4}|c[A-Za-z]|[\\s\\S]))/;\n    nativeTokens[XRegExp.OUTSIDE_CLASS] = /^(?:\\\\(?:0(?:[0-3][0-7]{0,2}|[4-7][0-7]?)?|[1-9]\\d*|x[\\dA-Fa-f]{2}|u[\\dA-Fa-f]{4}|c[A-Za-z]|[\\s\\S])|\\(\\?[:=!]|[?*+]\\?|{\\d+(?:,\\d*)?}\\??)/;\n\n\n    //---------------------------------\n    //  Public methods\n    //---------------------------------\n\n    // Lets you extend or change XRegExp syntax and create custom flags. This is used internally by\n    // the XRegExp library and can be used to create XRegExp plugins. This function is intended for\n    // users with advanced knowledge of JavaScript's regular expression syntax and behavior. It can\n    // be disabled by `XRegExp.freezeTokens`\n    XRegExp.addToken = function (regex, handler, scope, trigger) {\n        tokens.push({\n            pattern: clone(regex, \"g\" + (hasNativeY ? \"y\" : \"\")),\n            handler: handler,\n            scope: scope || XRegExp.OUTSIDE_CLASS,\n            trigger: trigger || null\n        });\n    };\n\n    // Accepts a pattern and flags; returns an extended `RegExp` object. If the pattern and flag\n    // combination has previously been cached, the cached copy is returned; otherwise the newly\n    // created regex is cached\n    XRegExp.cache = function (pattern, flags) {\n        var key = pattern + \"/\" + (flags || \"\");\n        return XRegExp.cache[key] || (XRegExp.cache[key] = XRegExp(pattern, flags));\n    };\n\n    // Accepts a `RegExp` instance; returns a copy with the `/g` flag set. The copy has a fresh\n    // `lastIndex` (set to zero). If you want to copy a regex without forcing the `global`\n    // property, use `XRegExp(regex)`. Do not use `RegExp(regex)` because it will not preserve\n    // special properties required for named capture\n    XRegExp.copyAsGlobal = function (regex) {\n        return clone(regex, \"g\");\n    };\n\n    // Accepts a string; returns the string with regex metacharacters escaped. The returned string\n    // can safely be used at any point within a regex to match the provided literal string. Escaped\n    // characters are [ ] { } ( ) * + ? - . , \\ ^ $ | # and whitespace\n    XRegExp.escape = function (str) {\n        return str.replace(/[-[\\]{}()*+?.,\\\\^$|#\\s]/g, \"\\\\$&\");\n    };\n\n    // Accepts a string to search, regex to search with, position to start the search within the\n    // string (default: 0), and an optional Boolean indicating whether matches must start at-or-\n    // after the position or at the specified position only. This function ignores the `lastIndex`\n    // of the provided regex in its own handling, but updates the property for compatibility\n    XRegExp.execAt = function (str, regex, pos, anchored) {\n        var r2 = clone(regex, \"g\" + ((anchored && hasNativeY) ? \"y\" : \"\")),\n            match;\n        r2.lastIndex = pos = pos || 0;\n        match = r2.exec(str); // Run the altered `exec` (required for `lastIndex` fix, etc.)\n        if (anchored && match && match.index !== pos)\n            match = null;\n        if (regex.global)\n            regex.lastIndex = match ? r2.lastIndex : 0;\n        return match;\n    };\n\n    // Breaks the unrestorable link to XRegExp's private list of tokens, thereby preventing\n    // syntax and flag changes. Should be run after XRegExp and any plugins are loaded\n    XRegExp.freezeTokens = function () {\n        XRegExp.addToken = function () {\n            throw Error(\"can't run addToken after freezeTokens\");\n        };\n    };\n\n    // Accepts any value; returns a Boolean indicating whether the argument is a `RegExp` object.\n    // Note that this is also `true` for regex literals and regexes created by the `XRegExp`\n    // constructor. This works correctly for variables created in another frame, when `instanceof`\n    // and `constructor` checks would fail to work as intended\n    XRegExp.isRegExp = function (o) {\n        return Object.prototype.toString.call(o) === \"[object RegExp]\";\n    };\n\n    // Executes `callback` once per match within `str`. Provides a simpler and cleaner way to\n    // iterate over regex matches compared to the traditional approaches of subverting\n    // `String.prototype.replace` or repeatedly calling `exec` within a `while` loop\n    XRegExp.iterate = function (str, regex, callback, context) {\n        var r2 = clone(regex, \"g\"),\n            i = -1, match;\n        while (match = r2.exec(str)) { // Run the altered `exec` (required for `lastIndex` fix, etc.)\n            if (regex.global)\n                regex.lastIndex = r2.lastIndex; // Doing this to follow expectations if `lastIndex` is checked within `callback`\n            callback.call(context, match, ++i, str, regex);\n            if (r2.lastIndex === match.index)\n                r2.lastIndex++;\n        }\n        if (regex.global)\n            regex.lastIndex = 0;\n    };\n\n    // Accepts a string and an array of regexes; returns the result of using each successive regex\n    // to search within the matches of the previous regex. The array of regexes can also contain\n    // objects with `regex` and `backref` properties, in which case the named or numbered back-\n    // references specified are passed forward to the next regex or returned. E.g.:\n    // var xregexpImgFileNames = XRegExp.matchChain(html, [\n    //     {regex: /<img\\b([^>]+)>/i, backref: 1}, // <img> tag attributes\n    //     {regex: XRegExp('(?ix) \\\\s src=\" (?<src> [^\"]+ )'), backref: \"src\"}, // src attribute values\n    //     {regex: XRegExp(\"^http://xregexp\\\\.com(/[^#?]+)\", \"i\"), backref: 1}, // xregexp.com paths\n    //     /[^\\/]+$/ // filenames (strip directory paths)\n    // ]);\n    XRegExp.matchChain = function (str, chain) {\n        return function recurseChain (values, level) {\n            var item = chain[level].regex ? chain[level] : {regex: chain[level]},\n                regex = clone(item.regex, \"g\"),\n                matches = [], i;\n            for (i = 0; i < values.length; i++) {\n                XRegExp.iterate(values[i], regex, function (match) {\n                    matches.push(item.backref ? (match[item.backref] || \"\") : match[0]);\n                });\n            }\n            return ((level === chain.length - 1) || !matches.length) ?\n                matches : recurseChain(matches, level + 1);\n        }([str], 0);\n    };\n\n\n    //---------------------------------\n    //  New RegExp prototype methods\n    //---------------------------------\n\n    // Accepts a context object and arguments array; returns the result of calling `exec` with the\n    // first value in the arguments array. the context is ignored but is accepted for congruity\n    // with `Function.prototype.apply`\n    RegExp.prototype.apply = function (context, args) {\n        return this.exec(args[0]);\n    };\n\n    // Accepts a context object and string; returns the result of calling `exec` with the provided\n    // string. the context is ignored but is accepted for congruity with `Function.prototype.call`\n    RegExp.prototype.call = function (context, str) {\n        return this.exec(str);\n    };\n\n\n    //---------------------------------\n    //  Overriden native methods\n    //---------------------------------\n\n    // Adds named capture support (with backreferences returned as `result.name`), and fixes two\n    // cross-browser issues per ES3:\n    // - Captured values for nonparticipating capturing groups should be returned as `undefined`,\n    //   rather than the empty string.\n    // - `lastIndex` should not be incremented after zero-length matches.\n    RegExp.prototype.exec = function (str) {\n        var match, name, r2, origLastIndex;\n        if (!this.global)\n            origLastIndex = this.lastIndex;\n        match = nativ.exec.apply(this, arguments);\n        if (match) {\n            // Fix browsers whose `exec` methods don't consistently return `undefined` for\n            // nonparticipating capturing groups\n            if (!compliantExecNpcg && match.length > 1 && indexOf(match, \"\") > -1) {\n                r2 = RegExp(this.source, nativ.replace.call(getNativeFlags(this), \"g\", \"\"));\n                // Using `str.slice(match.index)` rather than `match[0]` in case lookahead allowed\n                // matching due to characters outside the match\n                nativ.replace.call((str + \"\").slice(match.index), r2, function () {\n                    for (var i = 1; i < arguments.length - 2; i++) {\n                        if (arguments[i] === undefined)\n                            match[i] = undefined;\n                    }\n                });\n            }\n            // Attach named capture properties\n            if (this._xregexp && this._xregexp.captureNames) {\n                for (var i = 1; i < match.length; i++) {\n                    name = this._xregexp.captureNames[i - 1];\n                    if (name)\n                        match[name] = match[i];\n                }\n            }\n            // Fix browsers that increment `lastIndex` after zero-length matches\n            if (!compliantLastIndexIncrement && this.global && !match[0].length && (this.lastIndex > match.index))\n                this.lastIndex--;\n        }\n        if (!this.global)\n            this.lastIndex = origLastIndex; // Fix IE, Opera bug (last tested IE 9.0.5, Opera 11.61 on Windows)\n        return match;\n    };\n\n    // Fix browser bugs in native method\n    RegExp.prototype.test = function (str) {\n        // Use the native `exec` to skip some processing overhead, even though the altered\n        // `exec` would take care of the `lastIndex` fixes\n        var match, origLastIndex;\n        if (!this.global)\n            origLastIndex = this.lastIndex;\n        match = nativ.exec.call(this, str);\n        // Fix browsers that increment `lastIndex` after zero-length matches\n        if (match && !compliantLastIndexIncrement && this.global && !match[0].length && (this.lastIndex > match.index))\n            this.lastIndex--;\n        if (!this.global)\n            this.lastIndex = origLastIndex; // Fix IE, Opera bug (last tested IE 9.0.5, Opera 11.61 on Windows)\n        return !!match;\n    };\n\n    // Adds named capture support and fixes browser bugs in native method\n    String.prototype.match = function (regex) {\n        if (!XRegExp.isRegExp(regex))\n            regex = RegExp(regex); // Native `RegExp`\n        if (regex.global) {\n            var result = nativ.match.apply(this, arguments);\n            regex.lastIndex = 0; // Fix IE bug\n            return result;\n        }\n        return regex.exec(this); // Run the altered `exec`\n    };\n\n    // Adds support for `${n}` tokens for named and numbered backreferences in replacement text,\n    // and provides named backreferences to replacement functions as `arguments[0].name`. Also\n    // fixes cross-browser differences in replacement text syntax when performing a replacement\n    // using a nonregex search value, and the value of replacement regexes' `lastIndex` property\n    // during replacement iterations. Note that this doesn't support SpiderMonkey's proprietary\n    // third (`flags`) parameter\n    String.prototype.replace = function (search, replacement) {\n        var isRegex = XRegExp.isRegExp(search),\n            captureNames, result, str, origLastIndex;\n\n        // There are too many combinations of search/replacement types/values and browser bugs that\n        // preclude passing to native `replace`, so don't try\n        //if (...)\n        //    return nativ.replace.apply(this, arguments);\n\n        if (isRegex) {\n            if (search._xregexp)\n                captureNames = search._xregexp.captureNames; // Array or `null`\n            if (!search.global)\n                origLastIndex = search.lastIndex;\n        } else {\n            search = search + \"\"; // Type conversion\n        }\n\n        if (Object.prototype.toString.call(replacement) === \"[object Function]\") {\n            result = nativ.replace.call(this + \"\", search, function () {\n                if (captureNames) {\n                    // Change the `arguments[0]` string primitive to a String object which can store properties\n                    arguments[0] = new String(arguments[0]);\n                    // Store named backreferences on `arguments[0]`\n                    for (var i = 0; i < captureNames.length; i++) {\n                        if (captureNames[i])\n                            arguments[0][captureNames[i]] = arguments[i + 1];\n                    }\n                }\n                // Update `lastIndex` before calling `replacement` (fix browsers)\n                if (isRegex && search.global)\n                    search.lastIndex = arguments[arguments.length - 2] + arguments[0].length;\n                return replacement.apply(null, arguments);\n            });\n        } else {\n            str = this + \"\"; // Type conversion, so `args[args.length - 1]` will be a string (given nonstring `this`)\n            result = nativ.replace.call(str, search, function () {\n                var args = arguments; // Keep this function's `arguments` available through closure\n                return nativ.replace.call(replacement + \"\", replacementToken, function ($0, $1, $2) {\n                    // Numbered backreference (without delimiters) or special variable\n                    if ($1) {\n                        switch ($1) {\n                            case \"$\": return \"$\";\n                            case \"&\": return args[0];\n                            case \"`\": return args[args.length - 1].slice(0, args[args.length - 2]);\n                            case \"'\": return args[args.length - 1].slice(args[args.length - 2] + args[0].length);\n                            // Numbered backreference\n                            default:\n                                // What does \"$10\" mean?\n                                // - Backreference 10, if 10 or more capturing groups exist\n                                // - Backreference 1 followed by \"0\", if 1-9 capturing groups exist\n                                // - Otherwise, it's the string \"$10\"\n                                // Also note:\n                                // - Backreferences cannot be more than two digits (enforced by `replacementToken`)\n                                // - \"$01\" is equivalent to \"$1\" if a capturing group exists, otherwise it's the string \"$01\"\n                                // - There is no \"$0\" token (\"$&\" is the entire match)\n                                var literalNumbers = \"\";\n                                $1 = +$1; // Type conversion; drop leading zero\n                                if (!$1) // `$1` was \"0\" or \"00\"\n                                    return $0;\n                                while ($1 > args.length - 3) {\n                                    literalNumbers = String.prototype.slice.call($1, -1) + literalNumbers;\n                                    $1 = Math.floor($1 / 10); // Drop the last digit\n                                }\n                                return ($1 ? args[$1] || \"\" : \"$\") + literalNumbers;\n                        }\n                        // Named backreference or delimited numbered backreference\n                    } else {\n                        // What does \"${n}\" mean?\n                        // - Backreference to numbered capture n. Two differences from \"$n\":\n                        //   - n can be more than two digits\n                        //   - Backreference 0 is allowed, and is the entire match\n                        // - Backreference to named capture n, if it exists and is not a number overridden by numbered capture\n                        // - Otherwise, it's the string \"${n}\"\n                        var n = +$2; // Type conversion; drop leading zeros\n                        if (n <= args.length - 3)\n                            return args[n];\n                        n = captureNames ? indexOf(captureNames, $2) : -1;\n                        return n > -1 ? args[n + 1] : $0;\n                    }\n                });\n            });\n        }\n\n        if (isRegex) {\n            if (search.global)\n                search.lastIndex = 0; // Fix IE, Safari bug (last tested IE 9.0.5, Safari 5.1.2 on Windows)\n            else\n                search.lastIndex = origLastIndex; // Fix IE, Opera bug (last tested IE 9.0.5, Opera 11.61 on Windows)\n        }\n\n        return result;\n    };\n\n    // A consistent cross-browser, ES3 compliant `split`\n    String.prototype.split = function (s /* separator */, limit) {\n        // If separator `s` is not a regex, use the native `split`\n        if (!XRegExp.isRegExp(s))\n            return nativ.split.apply(this, arguments);\n\n        var str = this + \"\", // Type conversion\n            output = [],\n            lastLastIndex = 0,\n            match, lastLength;\n\n        // Behavior for `limit`: if it's...\n        // - `undefined`: No limit\n        // - `NaN` or zero: Return an empty array\n        // - A positive number: Use `Math.floor(limit)`\n        // - A negative number: No limit\n        // - Other: Type-convert, then use the above rules\n        if (limit === undefined || +limit < 0) {\n            limit = Infinity;\n        } else {\n            limit = Math.floor(+limit);\n            if (!limit)\n                return [];\n        }\n\n        // This is required if not `s.global`, and it avoids needing to set `s.lastIndex` to zero\n        // and restore it to its original value when we're done using the regex\n        s = XRegExp.copyAsGlobal(s);\n\n        while (match = s.exec(str)) { // Run the altered `exec` (required for `lastIndex` fix, etc.)\n            if (s.lastIndex > lastLastIndex) {\n                output.push(str.slice(lastLastIndex, match.index));\n\n                if (match.length > 1 && match.index < str.length)\n                    Array.prototype.push.apply(output, match.slice(1));\n\n                lastLength = match[0].length;\n                lastLastIndex = s.lastIndex;\n\n                if (output.length >= limit)\n                    break;\n            }\n\n            if (s.lastIndex === match.index)\n                s.lastIndex++;\n        }\n\n        if (lastLastIndex === str.length) {\n            if (!nativ.test.call(s, \"\") || lastLength)\n                output.push(\"\");\n        } else {\n            output.push(str.slice(lastLastIndex));\n        }\n\n        return output.length > limit ? output.slice(0, limit) : output;\n    };\n\n\n    //---------------------------------\n    //  Private helper functions\n    //---------------------------------\n\n    // Supporting function for `XRegExp`, `XRegExp.copyAsGlobal`, etc. Returns a copy of a `RegExp`\n    // instance with a fresh `lastIndex` (set to zero), preserving properties required for named\n    // capture. Also allows adding new flags in the process of copying the regex\n    function clone (regex, additionalFlags) {\n        if (!XRegExp.isRegExp(regex))\n            throw TypeError(\"type RegExp expected\");\n        var x = regex._xregexp;\n        regex = XRegExp(regex.source, getNativeFlags(regex) + (additionalFlags || \"\"));\n        if (x) {\n            regex._xregexp = {\n                source: x.source,\n                captureNames: x.captureNames ? x.captureNames.slice(0) : null\n            };\n        }\n        return regex;\n    }\n\n    function getNativeFlags (regex) {\n        return (regex.global     ? \"g\" : \"\") +\n            (regex.ignoreCase ? \"i\" : \"\") +\n            (regex.multiline  ? \"m\" : \"\") +\n            (regex.extended   ? \"x\" : \"\") + // Proposed for ES4; included in AS3\n            (regex.sticky     ? \"y\" : \"\");\n    }\n\n    function runTokens (pattern, index, scope, context) {\n        var i = tokens.length,\n            result, match, t;\n        // Protect against constructing XRegExps within token handler and trigger functions\n        isInsideConstructor = true;\n        // Must reset `isInsideConstructor`, even if a `trigger` or `handler` throws\n        try {\n            while (i--) { // Run in reverse order\n                t = tokens[i];\n                if ((scope & t.scope) && (!t.trigger || t.trigger.call(context))) {\n                    t.pattern.lastIndex = index;\n                    match = t.pattern.exec(pattern); // Running the altered `exec` here allows use of named backreferences, etc.\n                    if (match && match.index === index) {\n                        result = {\n                            output: t.handler.call(context, match, scope),\n                            match: match\n                        };\n                        break;\n                    }\n                }\n            }\n        } catch (err) {\n            throw err;\n        } finally {\n            isInsideConstructor = false;\n        }\n        return result;\n    }\n\n    function indexOf (array, item, from) {\n        if (Array.prototype.indexOf) // Use the native array method if available\n            return array.indexOf(item, from);\n        for (var i = from || 0; i < array.length; i++) {\n            if (array[i] === item)\n                return i;\n        }\n        return -1;\n    }\n\n\n    //---------------------------------\n    //  Built-in tokens\n    //---------------------------------\n\n    // Augment XRegExp's regular expression syntax and flags. Note that when adding tokens, the\n    // third (`scope`) argument defaults to `XRegExp.OUTSIDE_CLASS`\n\n    // Comment pattern: (?# )\n    XRegExp.addToken(\n        /\\(\\?#[^)]*\\)/,\n        function (match) {\n            // Keep tokens separated unless the following token is a quantifier\n            return nativ.test.call(quantifier, match.input.slice(match.index + match[0].length)) ? \"\" : \"(?:)\";\n        }\n    );\n\n    // Capturing group (match the opening parenthesis only).\n    // Required for support of named capturing groups\n    XRegExp.addToken(\n        /\\((?!\\?)/,\n        function () {\n            this.captureNames.push(null);\n            return \"(\";\n        }\n    );\n\n    // Named capturing group (match the opening delimiter only): (?<name>\n    XRegExp.addToken(\n        /\\(\\?<([$\\w]+)>/,\n        function (match) {\n            this.captureNames.push(match[1]);\n            this.hasNamedCapture = true;\n            return \"(\";\n        }\n    );\n\n    // Named backreference: \\k<name>\n    XRegExp.addToken(\n        /\\\\k<([\\w$]+)>/,\n        function (match) {\n            var index = indexOf(this.captureNames, match[1]);\n            // Keep backreferences separate from subsequent literal numbers. Preserve back-\n            // references to named groups that are undefined at this point as literal strings\n            return index > -1 ?\n                \"\\\\\" + (index + 1) + (isNaN(match.input.charAt(match.index + match[0].length)) ? \"\" : \"(?:)\") :\n                match[0];\n        }\n    );\n\n    // Empty character class: [] or [^]\n    XRegExp.addToken(\n        /\\[\\^?]/,\n        function (match) {\n            // For cross-browser compatibility with ES3, convert [] to \\b\\B and [^] to [\\s\\S].\n            // (?!) should work like \\b\\B, but is unreliable in Firefox\n            return match[0] === \"[]\" ? \"\\\\b\\\\B\" : \"[\\\\s\\\\S]\";\n        }\n    );\n\n    // Mode modifier at the start of the pattern only, with any combination of flags imsx: (?imsx)\n    // Does not support x(?i), (?-i), (?i-m), (?i: ), (?i)(?m), etc.\n    XRegExp.addToken(\n        /^\\(\\?([imsx]+)\\)/,\n        function (match) {\n            this.setFlag(match[1]);\n            return \"\";\n        }\n    );\n\n    // Whitespace and comments, in free-spacing (aka extended) mode only\n    XRegExp.addToken(\n        /(?:\\s+|#.*)+/,\n        function (match) {\n            // Keep tokens separated unless the following token is a quantifier\n            return nativ.test.call(quantifier, match.input.slice(match.index + match[0].length)) ? \"\" : \"(?:)\";\n        },\n        XRegExp.OUTSIDE_CLASS,\n        function () {return this.hasFlag(\"x\");}\n    );\n\n    // Dot, in dotall (aka singleline) mode only\n    XRegExp.addToken(\n        /\\./,\n        function () {return \"[\\\\s\\\\S]\";},\n        XRegExp.OUTSIDE_CLASS,\n        function () {return this.hasFlag(\"s\");}\n    );\n\n\n    //---------------------------------\n    //  Backward compatibility\n    //---------------------------------\n\n    // Uncomment the following block for compatibility with XRegExp 1.0-1.2:\n    /*\n     XRegExp.matchWithinChain = XRegExp.matchChain;\n     RegExp.prototype.addFlags = function (s) {return clone(this, s);};\n     RegExp.prototype.execAll = function (s) {var r = []; XRegExp.iterate(s, this, function (m) {r.push(m);}); return r;};\n     RegExp.prototype.forEachExec = function (s, f, c) {return XRegExp.iterate(s, this, f, c);};\n     RegExp.prototype.validate = function (s) {var r = RegExp(\"^(?:\" + this.source + \")$(?!\\\\s)\", getNativeFlags(this)); if (this.global) this.lastIndex = 0; return s.search(r) === 0;};\n     */\n\n})();\n\n//\n// Begin anonymous function. This is used to contain local scope variables without polutting global scope.\n//\nif (typeof(SyntaxHighlighter) == 'undefined') var SyntaxHighlighter = function() {\n\n// CommonJS\n    if (typeof(require) != 'undefined' && typeof(XRegExp) == 'undefined')\n    {\n        XRegExp = require('XRegExp').XRegExp;\n    }\n\n// Shortcut object which will be assigned to the SyntaxHighlighter variable.\n// This is a shorthand for local reference in order to avoid long namespace\n// references to SyntaxHighlighter.whatever...\n    var sh = {\n        defaults : {\n            /** Additional CSS class names to be added to highlighter elements. */\n            'class-name' : '',\n\n            /** First line number. */\n            'first-line' : 1,\n\n            /**\n             * Pads line numbers. Possible values are:\n             *\n             *   false - don't pad line numbers.\n             *   true  - automaticaly pad numbers with minimum required number of leading zeroes.\n             *   [int] - length up to which pad line numbers.\n             */\n            'pad-line-numbers' : false,\n\n            /** Lines to highlight. */\n            'highlight' : false,\n\n            /** Title to be displayed above the code block. */\n            'title' : null,\n\n            /** Enables or disables smart tabs. */\n            'smart-tabs' : true,\n\n            /** Gets or sets tab size. */\n            'tab-size' : 4,\n\n            /** Enables or disables gutter. */\n            'gutter' : true,\n\n            /** Enables or disables toolbar. */\n            'toolbar' : true,\n\n            /** Enables quick code copy and paste from double click. */\n            'quick-code' : true,\n\n            /** Forces code view to be collapsed. */\n            'collapse' : false,\n\n            /** Enables or disables automatic links. */\n            'auto-links' : false,\n\n            /** Gets or sets light mode. Equavalent to turning off gutter and toolbar. */\n            'light' : false,\n\n            'unindent' : true,\n\n            'html-script' : false\n        },\n\n        config : {\n            space : '&nbsp;',\n\n            /** Enables use of <SCRIPT type=\"syntaxhighlighter\" /> tags. */\n            useScriptTags : true,\n\n            /** Blogger mode flag. */\n            bloggerMode : false,\n\n            stripBrs : false,\n\n            /** Name of the tag that SyntaxHighlighter will automatically look for. */\n            tagName : 'pre',\n\n            strings : {\n                expandSource : 'expand source',\n                help : '?',\n                alert: 'SyntaxHighlighter\\n\\n',\n                noBrush : 'Can\\'t find brush for: ',\n                brushNotHtmlScript : 'Brush wasn\\'t configured for html-script option: ',\n\n                // this is populated by the build script\n                aboutDialog : '@ABOUT@'\n            }\n        },\n\n        /** Internal 'global' variables. */\n        vars : {\n            discoveredBrushes : null,\n            highlighters : {}\n        },\n\n        /** This object is populated by user included external brush files. */\n        brushes : {},\n\n        /** Common regular expressions. */\n        regexLib : {\n            multiLineCComments\t\t\t: /\\/\\*[\\s\\S]*?\\*\\//gm,\n            singleLineCComments\t\t\t: /\\/\\/.*$/gm,\n            singleLinePerlComments\t\t: /#.*$/gm,\n            doubleQuotedString\t\t\t: /\"([^\\\\\"\\n]|\\\\.)*\"/g,\n            singleQuotedString\t\t\t: /'([^\\\\'\\n]|\\\\.)*'/g,\n            multiLineDoubleQuotedString\t: new XRegExp('\"([^\\\\\\\\\"]|\\\\\\\\.)*\"', 'gs'),\n            multiLineSingleQuotedString\t: new XRegExp(\"'([^\\\\\\\\']|\\\\\\\\.)*'\", 'gs'),\n            xmlComments\t\t\t\t\t: /(&lt;|<)!--[\\s\\S]*?--(&gt;|>)/gm,\n            url\t\t\t\t\t\t\t: /\\w+:\\/\\/[\\w-.\\/?%&=:@;#]*/g,\n\n            /** <?= ?> tags. */\n            phpScriptTags \t\t\t\t: { left: /(&lt;|<)\\?(?:=|php)?/g, right: /\\?(&gt;|>)/g, 'eof' : true },\n\n            /** <%= %> tags. */\n            aspScriptTags\t\t\t\t: { left: /(&lt;|<)%=?/g, right: /%(&gt;|>)/g },\n\n            /** <script> tags. */\n            scriptScriptTags\t\t\t: { left: /(&lt;|<)\\s*script.*?(&gt;|>)/gi, right: /(&lt;|<)\\/\\s*script\\s*(&gt;|>)/gi }\n        },\n\n        toolbar: {\n            /**\n             * Generates HTML markup for the toolbar.\n             * @param {Highlighter} highlighter Highlighter instance.\n             * @return {String} Returns HTML markup.\n             */\n            getHtml: function(highlighter)\n            {\n                var html = '<div class=\"toolbar\">',\n                    items = sh.toolbar.items,\n                    list = items.list\n                    ;\n\n                function defaultGetHtml(highlighter, name)\n                {\n                    return sh.toolbar.getButtonHtml(highlighter, name, sh.config.strings[name]);\n                };\n\n                for (var i = 0; i < list.length; i++)\n                    html += (items[list[i]].getHtml || defaultGetHtml)(highlighter, list[i]);\n\n                html += '</div>';\n\n                return html;\n            },\n\n            /**\n             * Generates HTML markup for a regular button in the toolbar.\n             * @param {Highlighter} highlighter Highlighter instance.\n             * @param {String} commandName\t\tCommand name that would be executed.\n             * @param {String} label\t\t\tLabel text to display.\n             * @return {String}\t\t\t\t\tReturns HTML markup.\n             */\n            getButtonHtml: function(highlighter, commandName, label)\n            {\n                return '<span><a href=\"#\" class=\"toolbar_item'\n                    + ' command_' + commandName\n                    + ' ' + commandName\n                    + '\">' + label + '</a></span>'\n                    ;\n            },\n\n            /**\n             * Event handler for a toolbar anchor.\n             */\n            handler: function(e)\n            {\n                var target = e.target,\n                    className = target.className || ''\n                    ;\n\n                function getValue(name)\n                {\n                    var r = new RegExp(name + '_(\\\\w+)'),\n                        match = r.exec(className)\n                        ;\n\n                    return match ? match[1] : null;\n                };\n\n                var highlighter = getHighlighterById(findParentElement(target, '.syntaxhighlighter').id),\n                    commandName = getValue('command')\n                    ;\n\n                // execute the toolbar command\n                if (highlighter && commandName)\n                    sh.toolbar.items[commandName].execute(highlighter);\n\n                // disable default A click behaviour\n                e.preventDefault();\n            },\n\n            /** Collection of toolbar items. */\n            items : {\n                // Ordered lis of items in the toolbar. Can't expect `for (var n in items)` to be consistent.\n                list: ['expandSource', 'help'],\n\n                expandSource: {\n                    getHtml: function(highlighter)\n                    {\n                        if (highlighter.getParam('collapse') != true)\n                            return '';\n\n                        var title = highlighter.getParam('title');\n                        return sh.toolbar.getButtonHtml(highlighter, 'expandSource', title ? title : sh.config.strings.expandSource);\n                    },\n\n                    execute: function(highlighter)\n                    {\n                        var div = getHighlighterDivById(highlighter.id);\n                        removeClass(div, 'collapsed');\n                    }\n                },\n\n                /** Command to display the about dialog window. */\n                help: {\n                    execute: function(highlighter)\n                    {\n                        var wnd = popup('', '_blank', 500, 250, 'scrollbars=0'),\n                            doc = wnd.document\n                            ;\n\n                        doc.write(sh.config.strings.aboutDialog);\n                        doc.close();\n                        wnd.focus();\n                    }\n                }\n            }\n        },\n\n        /**\n         * Finds all elements on the page which should be processes by SyntaxHighlighter.\n         *\n         * @param {Object} globalParams\t\tOptional parameters which override element's\n         * \t\t\t\t\t\t\t\t\tparameters. Only used if element is specified.\n         *\n         * @param {Object} element\tOptional element to highlight. If none is\n         * \t\t\t\t\t\t\tprovided, all elements in the current document\n         * \t\t\t\t\t\t\tare returned which qualify.\n         *\n         * @return {Array}\tReturns list of <code>{ target: DOMElement, params: Object }</code> objects.\n         */\n        findElements: function(globalParams, element)\n        {\n            var elements = element ? [element] : toArray(document.getElementsByTagName(sh.config.tagName)),\n                conf = sh.config,\n                result = []\n                ;\n\n            // support for <SCRIPT TYPE=\"syntaxhighlighter\" /> feature\n            if (conf.useScriptTags)\n                elements = elements.concat(getSyntaxHighlighterScriptTags());\n\n            if (elements.length === 0)\n                return result;\n\n            for (var i = 0; i < elements.length; i++)\n            {\n                var item = {\n                    target: elements[i],\n                    // local params take precedence over globals\n                    params: merge(globalParams, parseParams(elements[i].className))\n                };\n\n                if (item.params['brush'] == null)\n                    continue;\n\n                result.push(item);\n            }\n\n            return result;\n        },\n\n        /**\n         * Shorthand to highlight all elements on the page that are marked as\n         * SyntaxHighlighter source code.\n         *\n         * @param {Object} globalParams\t\tOptional parameters which override element's\n         * \t\t\t\t\t\t\t\t\tparameters. Only used if element is specified.\n         *\n         * @param {Object} element\tOptional element to highlight. If none is\n         * \t\t\t\t\t\t\tprovided, all elements in the current document\n         * \t\t\t\t\t\t\tare highlighted.\n         */\n        highlight: function(globalParams, element)\n        {\n            var elements = this.findElements(globalParams, element),\n                propertyName = 'innerHTML',\n                highlighter = null,\n                conf = sh.config\n                ;\n\n            if (elements.length === 0)\n                return;\n\n            for (var i = 0; i < elements.length; i++)\n            {\n                var element = elements[i],\n                    target = element.target,\n                    params = element.params,\n                    brushName = params.brush,\n                    code\n                    ;\n\n                if (brushName == null)\n                    continue;\n\n                // Instantiate a brush\n                if (params['html-script'] == 'true' || sh.defaults['html-script'] == true)\n                {\n                    highlighter = new sh.HtmlScript(brushName);\n                    brushName = 'htmlscript';\n                }\n                else\n                {\n                    var brush = findBrush(brushName);\n\n                    if (brush)\n                        highlighter = new brush();\n                    else\n                        continue;\n                }\n\n                code = target[propertyName];\n\n                // remove CDATA from <SCRIPT/> tags if it's present\n                if (conf.useScriptTags)\n                    code = stripCData(code);\n\n                // Inject title if the attribute is present\n                if ((target.title || '') != '')\n                    params.title = target.title;\n\n                params['brush'] = brushName;\n                highlighter.init(params);\n                element = highlighter.getDiv(code);\n\n                // carry over ID\n                if ((target.id || '') != '')\n                    element.id = target.id;\n                //by zhanyi 去掉多余的外围div\n                var tmp = element.firstChild.firstChild;\n                tmp.className = element.firstChild.className;\n\n                target.parentNode.replaceChild(tmp, target);\n            }\n        },\n\n        /**\n         * Main entry point for the SyntaxHighlighter.\n         * @param {Object} params Optional params to apply to all highlighted elements.\n         */\n        all: function(params)\n        {\n            attachEvent(\n                window,\n                'load',\n                function() { sh.highlight(params); }\n            );\n        }\n    }; // end of sh\n\n    /**\n     * Checks if target DOM elements has specified CSS class.\n     * @param {DOMElement} target Target DOM element to check.\n     * @param {String} className Name of the CSS class to check for.\n     * @return {Boolean} Returns true if class name is present, false otherwise.\n     */\n    function hasClass(target, className)\n    {\n        return target.className.indexOf(className) != -1;\n    };\n\n    /**\n     * Adds CSS class name to the target DOM element.\n     * @param {DOMElement} target Target DOM element.\n     * @param {String} className New CSS class to add.\n     */\n    function addClass(target, className)\n    {\n        if (!hasClass(target, className))\n            target.className += ' ' + className;\n    };\n\n    /**\n     * Removes CSS class name from the target DOM element.\n     * @param {DOMElement} target Target DOM element.\n     * @param {String} className CSS class to remove.\n     */\n    function removeClass(target, className)\n    {\n        target.className = target.className.replace(className, '');\n    };\n\n    /**\n     * Converts the source to array object. Mostly used for function arguments and\n     * lists returned by getElementsByTagName() which aren't Array objects.\n     * @param {List} source Source list.\n     * @return {Array} Returns array.\n     */\n    function toArray(source)\n    {\n        var result = [];\n\n        for (var i = 0; i < source.length; i++)\n            result.push(source[i]);\n\n        return result;\n    };\n\n    /**\n     * Splits block of text into lines.\n     * @param {String} block Block of text.\n     * @return {Array} Returns array of lines.\n     */\n    function splitLines(block)\n    {\n        return block.split(/\\r?\\n/);\n    }\n\n    /**\n     * Generates HTML ID for the highlighter.\n     * @param {String} highlighterId Highlighter ID.\n     * @return {String} Returns HTML ID.\n     */\n    function getHighlighterId(id)\n    {\n        var prefix = 'highlighter_';\n        return id.indexOf(prefix) == 0 ? id : prefix + id;\n    };\n\n    /**\n     * Finds Highlighter instance by ID.\n     * @param {String} highlighterId Highlighter ID.\n     * @return {Highlighter} Returns instance of the highlighter.\n     */\n    function getHighlighterById(id)\n    {\n        return sh.vars.highlighters[getHighlighterId(id)];\n    };\n\n    /**\n     * Finds highlighter's DIV container.\n     * @param {String} highlighterId Highlighter ID.\n     * @return {Element} Returns highlighter's DIV element.\n     */\n    function getHighlighterDivById(id)\n    {\n        return document.getElementById(getHighlighterId(id));\n    };\n\n    /**\n     * Stores highlighter so that getHighlighterById() can do its thing. Each\n     * highlighter must call this method to preserve itself.\n     * @param {Highilghter} highlighter Highlighter instance.\n     */\n    function storeHighlighter(highlighter)\n    {\n        sh.vars.highlighters[getHighlighterId(highlighter.id)] = highlighter;\n    };\n\n    /**\n     * Looks for a child or parent node which has specified classname.\n     * Equivalent to jQuery's $(container).find(\".className\")\n     * @param {Element} target Target element.\n     * @param {String} search Class name or node name to look for.\n     * @param {Boolean} reverse If set to true, will go up the node tree instead of down.\n     * @return {Element} Returns found child or parent element on null.\n     */\n    function findElement(target, search, reverse /* optional */)\n    {\n        if (target == null)\n            return null;\n\n        var nodes\t\t\t= reverse != true ? target.childNodes : [ target.parentNode ],\n            propertyToFind\t= { '#' : 'id', '.' : 'className' }[search.substr(0, 1)] || 'nodeName',\n            expectedValue,\n            found\n            ;\n\n        expectedValue = propertyToFind != 'nodeName'\n            ? search.substr(1)\n            : search.toUpperCase()\n        ;\n\n        // main return of the found node\n        if ((target[propertyToFind] || '').indexOf(expectedValue) != -1)\n            return target;\n\n        for (var i = 0; nodes && i < nodes.length && found == null; i++)\n            found = findElement(nodes[i], search, reverse);\n\n        return found;\n    };\n\n    /**\n     * Looks for a parent node which has specified classname.\n     * This is an alias to <code>findElement(container, className, true)</code>.\n     * @param {Element} target Target element.\n     * @param {String} className Class name to look for.\n     * @return {Element} Returns found parent element on null.\n     */\n    function findParentElement(target, className)\n    {\n        return findElement(target, className, true);\n    };\n\n    /**\n     * Finds an index of element in the array.\n     * @ignore\n     * @param {Object} searchElement\n     * @param {Number} fromIndex\n     * @return {Number} Returns index of element if found; -1 otherwise.\n     */\n    function indexOf(array, searchElement, fromIndex)\n    {\n        fromIndex = Math.max(fromIndex || 0, 0);\n\n        for (var i = fromIndex; i < array.length; i++)\n            if(array[i] == searchElement)\n                return i;\n\n        return -1;\n    };\n\n    /**\n     * Generates a unique element ID.\n     */\n    function guid(prefix)\n    {\n        return (prefix || '') + Math.round(Math.random() * 1000000).toString();\n    };\n\n    /**\n     * Merges two objects. Values from obj2 override values in obj1.\n     * Function is NOT recursive and works only for one dimensional objects.\n     * @param {Object} obj1 First object.\n     * @param {Object} obj2 Second object.\n     * @return {Object} Returns combination of both objects.\n     */\n    function merge(obj1, obj2)\n    {\n        var result = {}, name;\n\n        for (name in obj1)\n            result[name] = obj1[name];\n\n        for (name in obj2)\n            result[name] = obj2[name];\n\n        return result;\n    };\n\n    /**\n     * Attempts to convert string to boolean.\n     * @param {String} value Input string.\n     * @return {Boolean} Returns true if input was \"true\", false if input was \"false\" and value otherwise.\n     */\n    function toBoolean(value)\n    {\n        var result = { \"true\" : true, \"false\" : false }[value];\n        return result == null ? value : result;\n    };\n\n    /**\n     * Opens up a centered popup window.\n     * @param {String} url\t\tURL to open in the window.\n     * @param {String} name\t\tPopup name.\n     * @param {int} width\t\tPopup width.\n     * @param {int} height\t\tPopup height.\n     * @param {String} options\twindow.open() options.\n     * @return {Window}\t\t\tReturns window instance.\n     */\n    function popup(url, name, width, height, options)\n    {\n        var x = (screen.width - width) / 2,\n            y = (screen.height - height) / 2\n            ;\n\n        options +=\t', left=' + x +\n            ', top=' + y +\n            ', width=' + width +\n            ', height=' + height\n        ;\n        options = options.replace(/^,/, '');\n\n        var win = window.open(url, name, options);\n        win.focus();\n        return win;\n    };\n\n    /**\n     * Adds event handler to the target object.\n     * @param {Object} obj\t\tTarget object.\n     * @param {String} type\t\tName of the event.\n     * @param {Function} func\tHandling function.\n     */\n    function attachEvent(obj, type, func, scope)\n    {\n        function handler(e)\n        {\n            e = e || window.event;\n\n            if (!e.target)\n            {\n                e.target = e.srcElement;\n                e.preventDefault = function()\n                {\n                    this.returnValue = false;\n                };\n            }\n\n            func.call(scope || window, e);\n        };\n\n        if (obj.attachEvent)\n        {\n            obj.attachEvent('on' + type, handler);\n        }\n        else\n        {\n            obj.addEventListener(type, handler, false);\n        }\n    };\n\n    /**\n     * Displays an alert.\n     * @param {String} str String to display.\n     */\n    function alert(str)\n    {\n        window.alert(sh.config.strings.alert + str);\n    };\n\n    /**\n     * Finds a brush by its alias.\n     *\n     * @param {String} alias\t\tBrush alias.\n     * @param {Boolean} showAlert\tSuppresses the alert if false.\n     * @return {Brush}\t\t\t\tReturns bursh constructor if found, null otherwise.\n     */\n    function findBrush(alias, showAlert)\n    {\n        var brushes = sh.vars.discoveredBrushes,\n            result = null\n            ;\n\n        if (brushes == null)\n        {\n            brushes = {};\n\n            // Find all brushes\n            for (var brush in sh.brushes)\n            {\n                var info = sh.brushes[brush],\n                    aliases = info.aliases\n                    ;\n\n                if (aliases == null)\n                    continue;\n\n                // keep the brush name\n                info.brushName = brush.toLowerCase();\n\n                for (var i = 0; i < aliases.length; i++)\n                    brushes[aliases[i]] = brush;\n            }\n\n            sh.vars.discoveredBrushes = brushes;\n        }\n\n        result = sh.brushes[brushes[alias]];\n\n        if (result == null && showAlert)\n            alert(sh.config.strings.noBrush + alias);\n\n        return result;\n    };\n\n    /**\n     * Executes a callback on each line and replaces each line with result from the callback.\n     * @param {Object} str\t\t\tInput string.\n     * @param {Object} callback\t\tCallback function taking one string argument and returning a string.\n     */\n    function eachLine(str, callback)\n    {\n        var lines = splitLines(str);\n\n        for (var i = 0; i < lines.length; i++)\n            lines[i] = callback(lines[i], i);\n\n        // include \\r to enable copy-paste on windows (ie8) without getting everything on one line\n        return lines.join('\\r\\n');\n    };\n\n    /**\n     * This is a special trim which only removes first and last empty lines\n     * and doesn't affect valid leading space on the first line.\n     *\n     * @param {String} str   Input string\n     * @return {String}      Returns string without empty first and last lines.\n     */\n    function trimFirstAndLastLines(str)\n    {\n        return str.replace(/^[ ]*[\\n]+|[\\n]*[ ]*$/g, '');\n    };\n\n    /**\n     * Parses key/value pairs into hash object.\n     *\n     * Understands the following formats:\n     * - name: word;\n     * - name: [word, word];\n     * - name: \"string\";\n     * - name: 'string';\n     *\n     * For example:\n     *   name1: value; name2: [value, value]; name3: 'value'\n     *\n     * @param {String} str    Input string.\n     * @return {Object}       Returns deserialized object.\n     */\n    function parseParams(str)\n    {\n        var match,\n            result = {},\n            arrayRegex = new XRegExp(\"^\\\\[(?<values>(.*?))\\\\]$\"),\n            regex = new XRegExp(\n                \"(?<name>[\\\\w-]+)\" +\n                    \"\\\\s*:\\\\s*\" +\n                    \"(?<value>\" +\n                    \"[\\\\w-%#]+|\" +\t\t// word\n                    \"\\\\[.*?\\\\]|\" +\t\t// [] array\n                    '\".*?\"|' +\t\t\t// \"\" string\n                    \"'.*?'\" +\t\t\t// '' string\n                    \")\\\\s*;?\",\n                \"g\"\n            )\n            ;\n\n        while ((match = regex.exec(str)) != null)\n        {\n            var value = match.value\n                    .replace(/^['\"]|['\"]$/g, '') // strip quotes from end of strings\n                ;\n\n            // try to parse array value\n            if (value != null && arrayRegex.test(value))\n            {\n                var m = arrayRegex.exec(value);\n                value = m.values.length > 0 ? m.values.split(/\\s*,\\s*/) : [];\n            }\n\n            result[match.name] = value;\n        }\n\n        return result;\n    };\n\n    /**\n     * Wraps each line of the string into <code/> tag with given style applied to it.\n     *\n     * @param {String} str   Input string.\n     * @param {String} css   Style name to apply to the string.\n     * @return {String}      Returns input string with each line surrounded by <span/> tag.\n     */\n    function wrapLinesWithCode(str, css)\n    {\n        if (str == null || str.length == 0 || str == '\\n')\n            return str;\n\n        str = str.replace(/</g, '&lt;');\n\n        // Replace two or more sequential spaces with &nbsp; leaving last space untouched.\n        str = str.replace(/ {2,}/g, function(m)\n        {\n            var spaces = '';\n\n            for (var i = 0; i < m.length - 1; i++)\n                spaces += sh.config.space;\n\n            return spaces + ' ';\n        });\n\n        // Split each line and apply <span class=\"...\">...</span> to them so that\n        // leading spaces aren't included.\n        if (css != null)\n            str = eachLine(str, function(line)\n            {\n                if (line.length == 0)\n                    return '';\n\n                var spaces = '';\n\n                line = line.replace(/^(&nbsp;| )+/, function(s)\n                {\n                    spaces = s;\n                    return '';\n                });\n\n                if (line.length == 0)\n                    return spaces;\n\n                return spaces + '<code class=\"' + css + '\">' + line + '</code>';\n            });\n\n        return str;\n    };\n\n    /**\n     * Pads number with zeros until it's length is the same as given length.\n     *\n     * @param {Number} number\tNumber to pad.\n     * @param {Number} length\tMax string length with.\n     * @return {String}\t\t\tReturns a string padded with proper amount of '0'.\n     */\n    function padNumber(number, length)\n    {\n        var result = number.toString();\n\n        while (result.length < length)\n            result = '0' + result;\n\n        return result;\n    };\n\n    /**\n     * Replaces tabs with spaces.\n     *\n     * @param {String} code\t\tSource code.\n     * @param {Number} tabSize\tSize of the tab.\n     * @return {String}\t\t\tReturns code with all tabs replaces by spaces.\n     */\n    function processTabs(code, tabSize)\n    {\n        var tab = '';\n\n        for (var i = 0; i < tabSize; i++)\n            tab += ' ';\n\n        return code.replace(/\\t/g, tab);\n    };\n\n    /**\n     * Replaces tabs with smart spaces.\n     *\n     * @param {String} code    Code to fix the tabs in.\n     * @param {Number} tabSize Number of spaces in a column.\n     * @return {String}        Returns code with all tabs replaces with roper amount of spaces.\n     */\n    function processSmartTabs(code, tabSize)\n    {\n        var lines = splitLines(code),\n            tab = '\\t',\n            spaces = ''\n            ;\n\n        // Create a string with 1000 spaces to copy spaces from...\n        // It's assumed that there would be no indentation longer than that.\n        for (var i = 0; i < 50; i++)\n            spaces += '                    '; // 20 spaces * 50\n\n        // This function inserts specified amount of spaces in the string\n        // where a tab is while removing that given tab.\n        function insertSpaces(line, pos, count)\n        {\n            return line.substr(0, pos)\n                + spaces.substr(0, count)\n                + line.substr(pos + 1, line.length) // pos + 1 will get rid of the tab\n                ;\n        };\n\n        // Go through all the lines and do the 'smart tabs' magic.\n        code = eachLine(code, function(line)\n        {\n            if (line.indexOf(tab) == -1)\n                return line;\n\n            var pos = 0;\n\n            while ((pos = line.indexOf(tab)) != -1)\n            {\n                // This is pretty much all there is to the 'smart tabs' logic.\n                // Based on the position within the line and size of a tab,\n                // calculate the amount of spaces we need to insert.\n                var spaces = tabSize - pos % tabSize;\n                line = insertSpaces(line, pos, spaces);\n            }\n\n            return line;\n        });\n\n        return code;\n    };\n\n    /**\n     * Performs various string fixes based on configuration.\n     */\n    function fixInputString(str)\n    {\n        var br = /<br\\s*\\/?>|&lt;br\\s*\\/?&gt;/gi;\n\n        if (sh.config.bloggerMode == true)\n            str = str.replace(br, '\\n');\n\n        if (sh.config.stripBrs == true)\n            str = str.replace(br, '');\n\n        return str;\n    };\n\n    /**\n     * Removes all white space at the begining and end of a string.\n     *\n     * @param {String} str   String to trim.\n     * @return {String}      Returns string without leading and following white space characters.\n     */\n    function trim(str)\n    {\n        return str.replace(/^\\s+|\\s+$/g, '');\n    };\n\n    /**\n     * Unindents a block of text by the lowest common indent amount.\n     * @param {String} str   Text to unindent.\n     * @return {String}      Returns unindented text block.\n     */\n    function unindent(str)\n    {\n        var lines = splitLines(fixInputString(str)),\n            indents = new Array(),\n            regex = /^\\s*/,\n            min = 1000\n            ;\n\n        // go through every line and check for common number of indents\n        for (var i = 0; i < lines.length && min > 0; i++)\n        {\n            var line = lines[i];\n\n            if (trim(line).length == 0)\n                continue;\n\n            var matches = regex.exec(line);\n\n            // In the event that just one line doesn't have leading white space\n            // we can't unindent anything, so bail completely.\n            if (matches == null)\n                return str;\n\n            min = Math.min(matches[0].length, min);\n        }\n\n        // trim minimum common number of white space from the begining of every line\n        if (min > 0)\n            for (var i = 0; i < lines.length; i++)\n                lines[i] = lines[i].substr(min);\n\n        return lines.join('\\n');\n    };\n\n    /**\n     * Callback method for Array.sort() which sorts matches by\n     * index position and then by length.\n     *\n     * @param {Match} m1\tLeft object.\n     * @param {Match} m2    Right object.\n     * @return {Number}     Returns -1, 0 or -1 as a comparison result.\n     */\n    function matchesSortCallback(m1, m2)\n    {\n        // sort matches by index first\n        if(m1.index < m2.index)\n            return -1;\n        else if(m1.index > m2.index)\n            return 1;\n        else\n        {\n            // if index is the same, sort by length\n            if(m1.length < m2.length)\n                return -1;\n            else if(m1.length > m2.length)\n                return 1;\n        }\n\n        return 0;\n    };\n\n    /**\n     * Executes given regular expression on provided code and returns all\n     * matches that are found.\n     *\n     * @param {String} code    Code to execute regular expression on.\n     * @param {Object} regex   Regular expression item info from <code>regexList</code> collection.\n     * @return {Array}         Returns a list of Match objects.\n     */\n    function getMatches(code, regexInfo)\n    {\n        function defaultAdd(match, regexInfo)\n        {\n            return match[0];\n        };\n\n        var index = 0,\n            match = null,\n            matches = [],\n            func = regexInfo.func ? regexInfo.func : defaultAdd\n            ;\n\n        while((match = regexInfo.regex.exec(code)) != null)\n        {\n            var resultMatch = func(match, regexInfo);\n\n            if (typeof(resultMatch) == 'string')\n                resultMatch = [new sh.Match(resultMatch, match.index, regexInfo.css)];\n\n            matches = matches.concat(resultMatch);\n        }\n\n        return matches;\n    };\n\n    /**\n     * Turns all URLs in the code into <a/> tags.\n     * @param {String} code Input code.\n     * @return {String} Returns code with </a> tags.\n     */\n    function processUrls(code)\n    {\n        var gt = /(.*)((&gt;|&lt;).*)/;\n\n        return code.replace(sh.regexLib.url, function(m)\n        {\n            var suffix = '',\n                match = null\n                ;\n\n            // We include &lt; and &gt; in the URL for the common cases like <http://google.com>\n            // The problem is that they get transformed into &lt;http://google.com&gt;\n            // Where as &gt; easily looks like part of the URL string.\n\n            if (match = gt.exec(m))\n            {\n                m = match[1];\n                suffix = match[2];\n            }\n\n            return '<a href=\"' + m + '\">' + m + '</a>' + suffix;\n        });\n    };\n\n    /**\n     * Finds all <SCRIPT TYPE=\"syntaxhighlighter\" /> elementss.\n     * @return {Array} Returns array of all found SyntaxHighlighter tags.\n     */\n    function getSyntaxHighlighterScriptTags()\n    {\n        var tags = document.getElementsByTagName('script'),\n            result = []\n            ;\n\n        for (var i = 0; i < tags.length; i++)\n            if (tags[i].type == 'syntaxhighlighter')\n                result.push(tags[i]);\n\n        return result;\n    };\n\n    /**\n     * Strips <![CDATA[]]> from <SCRIPT /> content because it should be used\n     * there in most cases for XHTML compliance.\n     * @param {String} original\tInput code.\n     * @return {String} Returns code without leading <![CDATA[]]> tags.\n     */\n    function stripCData(original)\n    {\n        var left = '<![CDATA[',\n            right = ']]>',\n        // for some reason IE inserts some leading blanks here\n            copy = trim(original),\n            changed = false,\n            leftLength = left.length,\n            rightLength = right.length\n            ;\n\n        if (copy.indexOf(left) == 0)\n        {\n            copy = copy.substring(leftLength);\n            changed = true;\n        }\n\n        var copyLength = copy.length;\n\n        if (copy.indexOf(right) == copyLength - rightLength)\n        {\n            copy = copy.substring(0, copyLength - rightLength);\n            changed = true;\n        }\n\n        return changed ? copy : original;\n    };\n\n\n    /**\n     * Quick code mouse double click handler.\n     */\n    function quickCodeHandler(e)\n    {\n        var target = e.target,\n            highlighterDiv = findParentElement(target, '.syntaxhighlighter'),\n            container = findParentElement(target, '.container'),\n            textarea = document.createElement('textarea'),\n            highlighter\n            ;\n\n        if (!container || !highlighterDiv || findElement(container, 'textarea'))\n            return;\n\n        highlighter = getHighlighterById(highlighterDiv.id);\n\n        // add source class name\n        addClass(highlighterDiv, 'source');\n\n        // Have to go over each line and grab it's text, can't just do it on the\n        // container because Firefox loses all \\n where as Webkit doesn't.\n        var lines = container.childNodes,\n            code = []\n            ;\n\n        for (var i = 0; i < lines.length; i++)\n            code.push(lines[i].innerText || lines[i].textContent);\n\n        // using \\r instead of \\r or \\r\\n makes this work equally well on IE, FF and Webkit\n        code = code.join('\\r');\n\n        // For Webkit browsers, replace nbsp with a breaking space\n        code = code.replace(/\\u00a0/g, \" \");\n\n        // inject <textarea/> tag\n        textarea.appendChild(document.createTextNode(code));\n        container.appendChild(textarea);\n\n        // preselect all text\n        textarea.focus();\n        textarea.select();\n\n        // set up handler for lost focus\n        attachEvent(textarea, 'blur', function(e)\n        {\n            textarea.parentNode.removeChild(textarea);\n            removeClass(highlighterDiv, 'source');\n        });\n    };\n\n    /**\n     * Match object.\n     */\n    sh.Match = function(value, index, css)\n    {\n        this.value = value;\n        this.index = index;\n        this.length = value.length;\n        this.css = css;\n        this.brushName = null;\n    };\n\n    sh.Match.prototype.toString = function()\n    {\n        return this.value;\n    };\n\n    /**\n     * Simulates HTML code with a scripting language embedded.\n     *\n     * @param {String} scriptBrushName Brush name of the scripting language.\n     */\n    sh.HtmlScript = function(scriptBrushName)\n    {\n        var brushClass = findBrush(scriptBrushName),\n            scriptBrush,\n            xmlBrush = new sh.brushes.Xml(),\n            bracketsRegex = null,\n            ref = this,\n            methodsToExpose = 'getDiv getHtml init'.split(' ')\n            ;\n\n        if (brushClass == null)\n            return;\n\n        scriptBrush = new brushClass();\n\n        for(var i = 0; i < methodsToExpose.length; i++)\n            // make a closure so we don't lose the name after i changes\n            (function() {\n                var name = methodsToExpose[i];\n\n                ref[name] = function()\n                {\n                    return xmlBrush[name].apply(xmlBrush, arguments);\n                };\n            })();\n\n        if (scriptBrush.htmlScript == null)\n        {\n            alert(sh.config.strings.brushNotHtmlScript + scriptBrushName);\n            return;\n        }\n\n        xmlBrush.regexList.push(\n            { regex: scriptBrush.htmlScript.code, func: process }\n        );\n\n        function offsetMatches(matches, offset)\n        {\n            for (var j = 0; j < matches.length; j++)\n                matches[j].index += offset;\n        }\n\n        function process(match, info)\n        {\n            var code = match.code,\n                matches = [],\n                regexList = scriptBrush.regexList,\n                offset = match.index + match.left.length,\n                htmlScript = scriptBrush.htmlScript,\n                result\n                ;\n\n            // add all matches from the code\n            for (var i = 0; i < regexList.length; i++)\n            {\n                result = getMatches(code, regexList[i]);\n                offsetMatches(result, offset);\n                matches = matches.concat(result);\n            }\n\n            // add left script bracket\n            if (htmlScript.left != null && match.left != null)\n            {\n                result = getMatches(match.left, htmlScript.left);\n                offsetMatches(result, match.index);\n                matches = matches.concat(result);\n            }\n\n            // add right script bracket\n            if (htmlScript.right != null && match.right != null)\n            {\n                result = getMatches(match.right, htmlScript.right);\n                offsetMatches(result, match.index + match[0].lastIndexOf(match.right));\n                matches = matches.concat(result);\n            }\n\n            for (var j = 0; j < matches.length; j++)\n                matches[j].brushName = brushClass.brushName;\n\n            return matches;\n        }\n    };\n\n    /**\n     * Main Highlither class.\n     * @constructor\n     */\n    sh.Highlighter = function()\n    {\n        // not putting any code in here because of the prototype inheritance\n    };\n\n    sh.Highlighter.prototype = {\n        /**\n         * Returns value of the parameter passed to the highlighter.\n         * @param {String} name\t\t\t\tName of the parameter.\n         * @param {Object} defaultValue\t\tDefault value.\n         * @return {Object}\t\t\t\t\tReturns found value or default value otherwise.\n         */\n        getParam: function(name, defaultValue)\n        {\n            var result = this.params[name];\n            return toBoolean(result == null ? defaultValue : result);\n        },\n\n        /**\n         * Shortcut to document.createElement().\n         * @param {String} name\t\tName of the element to create (DIV, A, etc).\n         * @return {HTMLElement}\tReturns new HTML element.\n         */\n        create: function(name)\n        {\n            return document.createElement(name);\n        },\n\n        /**\n         * Applies all regular expression to the code and stores all found\n         * matches in the `this.matches` array.\n         * @param {Array} regexList\t\tList of regular expressions.\n         * @param {String} code\t\t\tSource code.\n         * @return {Array}\t\t\t\tReturns list of matches.\n         */\n        findMatches: function(regexList, code)\n        {\n            var result = [];\n\n            if (regexList != null)\n                for (var i = 0; i < regexList.length; i++)\n                    // BUG: length returns len+1 for array if methods added to prototype chain (oising@gmail.com)\n                    if (typeof (regexList[i]) == \"object\")\n                        result = result.concat(getMatches(code, regexList[i]));\n\n            // sort and remove nested the matches\n            return this.removeNestedMatches(result.sort(matchesSortCallback));\n        },\n\n        /**\n         * Checks to see if any of the matches are inside of other matches.\n         * This process would get rid of highligted strings inside comments,\n         * keywords inside strings and so on.\n         */\n        removeNestedMatches: function(matches)\n        {\n            // Optimized by Jose Prado (http://joseprado.com)\n            for (var i = 0; i < matches.length; i++)\n            {\n                if (matches[i] === null)\n                    continue;\n\n                var itemI = matches[i],\n                    itemIEndPos = itemI.index + itemI.length\n                    ;\n\n                for (var j = i + 1; j < matches.length && matches[i] !== null; j++)\n                {\n                    var itemJ = matches[j];\n\n                    if (itemJ === null)\n                        continue;\n                    else if (itemJ.index > itemIEndPos)\n                        break;\n                    else if (itemJ.index == itemI.index && itemJ.length > itemI.length)\n                        matches[i] = null;\n                    else if (itemJ.index >= itemI.index && itemJ.index < itemIEndPos)\n                        matches[j] = null;\n                }\n            }\n\n            return matches;\n        },\n\n        /**\n         * Creates an array containing integer line numbers starting from the 'first-line' param.\n         * @return {Array} Returns array of integers.\n         */\n        figureOutLineNumbers: function(code)\n        {\n            var lines = [],\n                firstLine = parseInt(this.getParam('first-line'))\n                ;\n\n            eachLine(code, function(line, index)\n            {\n                lines.push(index + firstLine);\n            });\n\n            return lines;\n        },\n\n        /**\n         * Determines if specified line number is in the highlighted list.\n         */\n        isLineHighlighted: function(lineNumber)\n        {\n            var list = this.getParam('highlight', []);\n\n            if (typeof(list) != 'object' && list.push == null)\n                list = [ list ];\n\n            return indexOf(list, lineNumber.toString()) != -1;\n        },\n\n        /**\n         * Generates HTML markup for a single line of code while determining alternating line style.\n         * @param {Integer} lineNumber\tLine number.\n         * @param {String} code Line\tHTML markup.\n         * @return {String}\t\t\t\tReturns HTML markup.\n         */\n        getLineHtml: function(lineIndex, lineNumber, code)\n        {\n            var classes = [\n                'line',\n                'number' + lineNumber,\n                'index' + lineIndex,\n                'alt' + (lineNumber % 2 == 0 ? 1 : 2).toString()\n            ];\n\n            if (this.isLineHighlighted(lineNumber))\n                classes.push('highlighted');\n\n            if (lineNumber == 0)\n                classes.push('break');\n\n            return '<div class=\"' + classes.join(' ') + '\">' + code + '</div>';\n        },\n\n        /**\n         * Generates HTML markup for line number column.\n         * @param {String} code\t\t\tComplete code HTML markup.\n         * @param {Array} lineNumbers\tCalculated line numbers.\n         * @return {String}\t\t\t\tReturns HTML markup.\n         */\n        getLineNumbersHtml: function(code, lineNumbers)\n        {\n            var html = '',\n                count = splitLines(code).length,\n                firstLine = parseInt(this.getParam('first-line')),\n                pad = this.getParam('pad-line-numbers')\n                ;\n\n            if (pad == true)\n                pad = (firstLine + count - 1).toString().length;\n            else if (isNaN(pad) == true)\n                pad = 0;\n\n            for (var i = 0; i < count; i++)\n            {\n                var lineNumber = lineNumbers ? lineNumbers[i] : firstLine + i,\n                    code = lineNumber == 0 ? sh.config.space : padNumber(lineNumber, pad)\n                    ;\n\n                html += this.getLineHtml(i, lineNumber, code);\n            }\n\n            return html;\n        },\n\n        /**\n         * Splits block of text into individual DIV lines.\n         * @param {String} code\t\t\tCode to highlight.\n         * @param {Array} lineNumbers\tCalculated line numbers.\n         * @return {String}\t\t\t\tReturns highlighted code in HTML form.\n         */\n        getCodeLinesHtml: function(html, lineNumbers)\n        {\n            html = trim(html);\n\n            var lines = splitLines(html),\n                padLength = this.getParam('pad-line-numbers'),\n                firstLine = parseInt(this.getParam('first-line')),\n                html = '',\n                brushName = this.getParam('brush')\n                ;\n\n            for (var i = 0; i < lines.length; i++)\n            {\n                var line = lines[i],\n                    indent = /^(&nbsp;|\\s)+/.exec(line),\n                    spaces = null,\n                    lineNumber = lineNumbers ? lineNumbers[i] : firstLine + i;\n                ;\n\n                if (indent != null)\n                {\n                    spaces = indent[0].toString();\n                    line = line.substr(spaces.length);\n                    spaces = spaces.replace(' ', sh.config.space);\n                }\n\n                line = trim(line);\n\n                if (line.length == 0)\n                    line = sh.config.space;\n\n                html += this.getLineHtml(\n                    i,\n                    lineNumber,\n                    (spaces != null ? '<code class=\"' + brushName + ' spaces\">' + spaces + '</code>' : '') + line\n                );\n            }\n\n            return html;\n        },\n\n        /**\n         * Returns HTML for the table title or empty string if title is null.\n         */\n        getTitleHtml: function(title)\n        {\n            return title ? '<caption>' + title + '</caption>' : '';\n        },\n\n        /**\n         * Finds all matches in the source code.\n         * @param {String} code\t\tSource code to process matches in.\n         * @param {Array} matches\tDiscovered regex matches.\n         * @return {String} Returns formatted HTML with processed mathes.\n         */\n        getMatchesHtml: function(code, matches)\n        {\n            var pos = 0,\n                result = '',\n                brushName = this.getParam('brush', '')\n                ;\n\n            function getBrushNameCss(match)\n            {\n                var result = match ? (match.brushName || brushName) : brushName;\n                return result ? result + ' ' : '';\n            };\n\n            // Finally, go through the final list of matches and pull the all\n            // together adding everything in between that isn't a match.\n            for (var i = 0; i < matches.length; i++)\n            {\n                var match = matches[i],\n                    matchBrushName\n                    ;\n\n                if (match === null || match.length === 0)\n                    continue;\n\n                matchBrushName = getBrushNameCss(match);\n\n                result += wrapLinesWithCode(code.substr(pos, match.index - pos), matchBrushName + 'plain')\n                    + wrapLinesWithCode(match.value, matchBrushName + match.css)\n                ;\n\n                pos = match.index + match.length + (match.offset || 0);\n            }\n\n            // don't forget to add whatever's remaining in the string\n            result += wrapLinesWithCode(code.substr(pos), getBrushNameCss() + 'plain');\n\n            return result;\n        },\n\n        /**\n         * Generates HTML markup for the whole syntax highlighter.\n         * @param {String} code Source code.\n         * @return {String} Returns HTML markup.\n         */\n        getHtml: function(code)\n        {\n            var html = '',\n                classes = [ 'syntaxhighlighter' ],\n                tabSize,\n                matches,\n                lineNumbers\n                ;\n\n            // process light mode\n            if (this.getParam('light') == true)\n                this.params.toolbar = this.params.gutter = false;\n\n            className = 'syntaxhighlighter';\n\n            if (this.getParam('collapse') == true)\n                classes.push('collapsed');\n\n            if ((gutter = this.getParam('gutter')) == false)\n                classes.push('nogutter');\n\n            // add custom user style name\n            classes.push(this.getParam('class-name'));\n\n            // add brush alias to the class name for custom CSS\n            classes.push(this.getParam('brush'));\n\n            code = trimFirstAndLastLines(code)\n                .replace(/\\r/g, ' ') // IE lets these buggers through\n            ;\n\n            tabSize = this.getParam('tab-size');\n\n            // replace tabs with spaces\n            code = this.getParam('smart-tabs') == true\n                ? processSmartTabs(code, tabSize)\n                : processTabs(code, tabSize)\n            ;\n\n            // unindent code by the common indentation\n            if (this.getParam('unindent'))\n                code = unindent(code);\n\n            if (gutter)\n                lineNumbers = this.figureOutLineNumbers(code);\n\n            // find matches in the code using brushes regex list\n            matches = this.findMatches(this.regexList, code);\n            // processes found matches into the html\n            html = this.getMatchesHtml(code, matches);\n            // finally, split all lines so that they wrap well\n            html = this.getCodeLinesHtml(html, lineNumbers);\n\n            // finally, process the links\n            if (this.getParam('auto-links'))\n                html = processUrls(html);\n\n            if (typeof(navigator) != 'undefined' && navigator.userAgent && navigator.userAgent.match(/MSIE/))\n                classes.push('ie');\n\n            html =\n                '<div id=\"' + getHighlighterId(this.id) + '\" class=\"' + classes.join(' ') + '\">'\n                    + (this.getParam('toolbar') ? sh.toolbar.getHtml(this) : '')\n                    + '<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\">'\n                    + this.getTitleHtml(this.getParam('title'))\n                    + '<tbody>'\n                    + '<tr>'\n                    + (gutter ? '<td class=\"gutter\">' + this.getLineNumbersHtml(code) + '</td>' : '')\n                    + '<td class=\"code\">'\n                    + '<div class=\"container\">'\n                    + html\n                    + '</div>'\n                    + '</td>'\n                    + '</tr>'\n                    + '</tbody>'\n                    + '</table>'\n                    + '</div>'\n            ;\n\n            return html;\n        },\n\n        /**\n         * Highlights the code and returns complete HTML.\n         * @param {String} code     Code to highlight.\n         * @return {Element}        Returns container DIV element with all markup.\n         */\n        getDiv: function(code)\n        {\n            if (code === null)\n                code = '';\n\n            this.code = code;\n\n            var div = this.create('div');\n\n            // create main HTML\n            div.innerHTML = this.getHtml(code);\n\n            // set up click handlers\n            if (this.getParam('toolbar'))\n                attachEvent(findElement(div, '.toolbar'), 'click', sh.toolbar.handler);\n\n            if (this.getParam('quick-code'))\n                attachEvent(findElement(div, '.code'), 'dblclick', quickCodeHandler);\n\n            return div;\n        },\n\n        /**\n         * Initializes the highlighter/brush.\n         *\n         * Constructor isn't used for initialization so that nothing executes during necessary\n         * `new SyntaxHighlighter.Highlighter()` call when setting up brush inheritence.\n         *\n         * @param {Hash} params Highlighter parameters.\n         */\n        init: function(params)\n        {\n            this.id = guid();\n\n            // register this instance in the highlighters list\n            storeHighlighter(this);\n\n            // local params take precedence over defaults\n            this.params = merge(sh.defaults, params || {})\n\n            // process light mode\n            if (this.getParam('light') == true)\n                this.params.toolbar = this.params.gutter = false;\n        },\n\n        /**\n         * Converts space separated list of keywords into a regular expression string.\n         * @param {String} str    Space separated keywords.\n         * @return {String}       Returns regular expression string.\n         */\n        getKeywords: function(str)\n        {\n            str = str\n                .replace(/^\\s+|\\s+$/g, '')\n                .replace(/\\s+/g, '|')\n            ;\n\n            return '\\\\b(?:' + str + ')\\\\b';\n        },\n\n        /**\n         * Makes a brush compatible with the `html-script` functionality.\n         * @param {Object} regexGroup Object containing `left` and `right` regular expressions.\n         */\n        forHtmlScript: function(regexGroup)\n        {\n            var regex = { 'end' : regexGroup.right.source };\n\n            if(regexGroup.eof)\n                regex.end = \"(?:(?:\" + regex.end + \")|$)\";\n\n            this.htmlScript = {\n                left : { regex: regexGroup.left, css: 'script' },\n                right : { regex: regexGroup.right, css: 'script' },\n                code : new XRegExp(\n                    \"(?<left>\" + regexGroup.left.source + \")\" +\n                        \"(?<code>.*?)\" +\n                        \"(?<right>\" + regex.end + \")\",\n                    \"sgi\"\n                )\n            };\n        }\n    }; // end of Highlighter\n\n    return sh;\n}(); // end of anonymous function\n\n// CommonJS\ntypeof(exports) != 'undefined' ? exports.SyntaxHighlighter = SyntaxHighlighter : null;\n\n;(function()\n{\n    // CommonJS\n    SyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\n\n    function Brush()\n    {\n        // Created by Peter Atoria @ http://iAtoria.com\n\n        var inits \t =  'class interface function package';\n\n        var keywords =\t'-Infinity ...rest Array as AS3 Boolean break case catch const continue Date decodeURI ' +\n                'decodeURIComponent default delete do dynamic each else encodeURI encodeURIComponent escape ' +\n                'extends false final finally flash_proxy for get if implements import in include Infinity ' +\n                'instanceof int internal is isFinite isNaN isXMLName label namespace NaN native new null ' +\n                'Null Number Object object_proxy override parseFloat parseInt private protected public ' +\n                'return set static String super switch this throw true try typeof uint undefined unescape ' +\n                'use void while with'\n            ;\n\n        this.regexList = [\n            { regex: SyntaxHighlighter.regexLib.singleLineCComments,\tcss: 'comments' },\t\t// one line comments\n            { regex: SyntaxHighlighter.regexLib.multiLineCComments,\t\tcss: 'comments' },\t\t// multiline comments\n            { regex: SyntaxHighlighter.regexLib.doubleQuotedString,\t\tcss: 'string' },\t\t// double quoted strings\n            { regex: SyntaxHighlighter.regexLib.singleQuotedString,\t\tcss: 'string' },\t\t// single quoted strings\n            { regex: /\\b([\\d]+(\\.[\\d]+)?|0x[a-f0-9]+)\\b/gi,\t\t\t\tcss: 'value' },\t\t\t// numbers\n            { regex: new RegExp(this.getKeywords(inits), 'gm'),\t\t\tcss: 'color3' },\t\t// initializations\n            { regex: new RegExp(this.getKeywords(keywords), 'gm'),\t\tcss: 'keyword' },\t\t// keywords\n            { regex: new RegExp('var', 'gm'),\t\t\t\t\t\t\tcss: 'variable' },\t\t// variable\n            { regex: new RegExp('trace', 'gm'),\t\t\t\t\t\t\tcss: 'color1' }\t\t\t// trace\n        ];\n\n        this.forHtmlScript(SyntaxHighlighter.regexLib.scriptScriptTags);\n    };\n\n    Brush.prototype\t= new SyntaxHighlighter.Highlighter();\n    Brush.aliases\t= ['actionscript3', 'as3'];\n\n    SyntaxHighlighter.brushes.AS3 = Brush;\n\n    // CommonJS\n    typeof(exports) != 'undefined' ? exports.Brush = Brush : null;\n})();\n\n;(function()\n{\n    // CommonJS\n    SyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\n\n    function Brush()\n    {\n        // AppleScript brush by David Chambers\n        // http://davidchambersdesign.com/\n        var keywords   = 'after before beginning continue copy each end every from return get global in local named of set some that the then times to where whose with without';\n        var ordinals   = 'first second third fourth fifth sixth seventh eighth ninth tenth last front back middle';\n        var specials   = 'activate add alias AppleScript ask attachment boolean class constant delete duplicate empty exists false id integer list make message modal modified new no paragraph pi properties quit real record remove rest result reveal reverse run running save string true word yes';\n\n        this.regexList = [\n\n            { regex: /(--|#).*$/gm,\n                css: 'comments' },\n\n            { regex: /\\(\\*(?:[\\s\\S]*?\\(\\*[\\s\\S]*?\\*\\))*[\\s\\S]*?\\*\\)/gm, // support nested comments\n                css: 'comments' },\n\n            { regex: /\"[\\s\\S]*?\"/gm,\n                css: 'string' },\n\n            { regex: /(?:,|:|¬|'s\\b|\\(|\\)|\\{|\\}|«|\\b\\w*»)/g,\n                css: 'color1' },\n\n            { regex: /(-)?(\\d)+(\\.(\\d)?)?(E\\+(\\d)+)?/g, // numbers\n                css: 'color1' },\n\n            { regex: /(?:&(amp;|gt;|lt;)?|=|� |>|<|≥|>=|≤|<=|\\*|\\+|-|\\/|÷|\\^)/g,\n                css: 'color2' },\n\n            { regex: /\\b(?:and|as|div|mod|not|or|return(?!\\s&)(ing)?|equals|(is(n't| not)? )?equal( to)?|does(n't| not) equal|(is(n't| not)? )?(greater|less) than( or equal( to)?)?|(comes|does(n't| not) come) (after|before)|is(n't| not)?( in)? (back|front) of|is(n't| not)? behind|is(n't| not)?( (in|contained by))?|does(n't| not) contain|contain(s)?|(start|begin|end)(s)? with|((but|end) )?(consider|ignor)ing|prop(erty)?|(a )?ref(erence)?( to)?|repeat (until|while|with)|((end|exit) )?repeat|((else|end) )?if|else|(end )?(script|tell|try)|(on )?error|(put )?into|(of )?(it|me)|its|my|with (timeout( of)?|transaction)|end (timeout|transaction))\\b/g,\n                css: 'keyword' },\n\n            { regex: /\\b\\d+(st|nd|rd|th)\\b/g, // ordinals\n                css: 'keyword' },\n\n            { regex: /\\b(?:about|above|against|around|at|below|beneath|beside|between|by|(apart|aside) from|(instead|out) of|into|on(to)?|over|since|thr(ough|u)|under)\\b/g,\n                css: 'color3' },\n\n            { regex: /\\b(?:adding folder items to|after receiving|choose( ((remote )?application|color|folder|from list|URL))?|clipboard info|set the clipboard to|(the )?clipboard|entire contents|display(ing| (alert|dialog|mode))?|document( (edited|file|nib name))?|file( (name|type))?|(info )?for|giving up after|(name )?extension|quoted form|return(ed)?|second(?! item)(s)?|list (disks|folder)|text item(s| delimiters)?|(Unicode )?text|(disk )?item(s)?|((current|list) )?view|((container|key) )?window|with (data|icon( (caution|note|stop))?|parameter(s)?|prompt|properties|seed|title)|case|diacriticals|hyphens|numeric strings|punctuation|white space|folder creation|application(s( folder)?| (processes|scripts position|support))?|((desktop )?(pictures )?|(documents|downloads|favorites|home|keychain|library|movies|music|public|scripts|sites|system|users|utilities|workflows) )folder|desktop|Folder Action scripts|font(s| panel)?|help|internet plugins|modem scripts|(system )?preferences|printer descriptions|scripting (additions|components)|shared (documents|libraries)|startup (disk|items)|temporary items|trash|on server|in AppleTalk zone|((as|long|short) )?user name|user (ID|locale)|(with )?password|in (bundle( with identifier)?|directory)|(close|open for) access|read|write( permission)?|(g|s)et eof|using( delimiters)?|starting at|default (answer|button|color|country code|entr(y|ies)|identifiers|items|name|location|script editor)|hidden( answer)?|open(ed| (location|untitled))?|error (handling|reporting)|(do( shell)?|load|run|store) script|administrator privileges|altering line endings|get volume settings|(alert|boot|input|mount|output|set) volume|output muted|(fax|random )?number|round(ing)?|up|down|toward zero|to nearest|as taught in school|system (attribute|info)|((AppleScript( Studio)?|system) )?version|(home )?directory|(IPv4|primary Ethernet) address|CPU (type|speed)|physical memory|time (stamp|to GMT)|replacing|ASCII (character|number)|localized string|from table|offset|summarize|beep|delay|say|(empty|multiple) selections allowed|(of|preferred) type|invisibles|showing( package contents)?|editable URL|(File|FTP|News|Media|Web) [Ss]ervers|Telnet hosts|Directory services|Remote applications|waiting until completion|saving( (in|to))?|path (for|to( (((current|frontmost) )?application|resource))?)|POSIX (file|path)|(background|RGB) color|(OK|cancel) button name|cancel button|button(s)?|cubic ((centi)?met(re|er)s|yards|feet|inches)|square ((kilo)?met(re|er)s|miles|yards|feet)|(centi|kilo)?met(re|er)s|miles|yards|feet|inches|lit(re|er)s|gallons|quarts|(kilo)?grams|ounces|pounds|degrees (Celsius|Fahrenheit|Kelvin)|print( (dialog|settings))?|clos(e(able)?|ing)|(de)?miniaturized|miniaturizable|zoom(ed|able)|attribute run|action (method|property|title)|phone|email|((start|end)ing|home) page|((birth|creation|current|custom|modification) )?date|((((phonetic )?(first|last|middle))|computer|host|maiden|related) |nick)?name|aim|icq|jabber|msn|yahoo|address(es)?|save addressbook|should enable action|city|country( code)?|formatte(r|d address)|(palette )?label|state|street|zip|AIM [Hh]andle(s)?|my card|select(ion| all)?|unsaved|(alpha )?value|entr(y|ies)|group|(ICQ|Jabber|MSN) handle|person|people|company|department|icon image|job title|note|organization|suffix|vcard|url|copies|collating|pages (across|down)|request print time|target( printer)?|((GUI Scripting|Script menu) )?enabled|show Computer scripts|(de)?activated|awake from nib|became (key|main)|call method|of (class|object)|center|clicked toolbar item|closed|for document|exposed|(can )?hide|idle|keyboard (down|up)|event( (number|type))?|launch(ed)?|load (image|movie|nib|sound)|owner|log|mouse (down|dragged|entered|exited|moved|up)|move|column|localization|resource|script|register|drag (info|types)|resigned (active|key|main)|resiz(e(d)?|able)|right mouse (down|dragged|up)|scroll wheel|(at )?index|should (close|open( untitled)?|quit( after last window closed)?|zoom)|((proposed|screen) )?bounds|show(n)?|behind|in front of|size (mode|to fit)|update(d| toolbar item)?|was (hidden|miniaturized)|will (become active|close|finish launching|hide|miniaturize|move|open|quit|(resign )?active|((maximum|minimum|proposed) )?size|show|zoom)|bundle|data source|movie|pasteboard|sound|tool(bar| tip)|(color|open|save) panel|coordinate system|frontmost|main( (bundle|menu|window))?|((services|(excluded from )?windows) )?menu|((executable|frameworks|resource|scripts|shared (frameworks|support)) )?path|(selected item )?identifier|data|content(s| view)?|character(s)?|click count|(command|control|option|shift) key down|context|delta (x|y|z)|key( code)?|location|pressure|unmodified characters|types|(first )?responder|playing|(allowed|selectable) identifiers|allows customization|(auto saves )?configuration|visible|image( name)?|menu form representation|tag|user(-| )defaults|associated file name|(auto|needs) display|current field editor|floating|has (resize indicator|shadow)|hides when deactivated|level|minimized (image|title)|opaque|position|release when closed|sheet|title(d)?)\\b/g,\n                css: 'color3' },\n\n            { regex: new RegExp(this.getKeywords(specials), 'gm'), css: 'color3' },\n            { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword' },\n            { regex: new RegExp(this.getKeywords(ordinals), 'gm'), css: 'keyword' }\n        ];\n    };\n\n    Brush.prototype = new SyntaxHighlighter.Highlighter();\n    Brush.aliases = ['applescript'];\n\n    SyntaxHighlighter.brushes.AppleScript = Brush;\n\n    // CommonJS\n    typeof(exports) != 'undefined' ? exports.Brush = Brush : null;\n})();\n;(function()\n{\n\t// CommonJS\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\n\n\tfunction Brush()\n\t{\n\t\tvar keywords =\t'if fi then elif else for do done until while break continue case esac function return in eq ne ge le';\n\t\tvar commands =  'alias apropos awk basename bash bc bg builtin bzip2 cal cat cd cfdisk chgrp chmod chown chroot' +\n\t\t\t\t\t\t'cksum clear cmp comm command cp cron crontab csplit cut date dc dd ddrescue declare df ' +\n\t\t\t\t\t\t'diff diff3 dig dir dircolors dirname dirs du echo egrep eject enable env ethtool eval ' +\n\t\t\t\t\t\t'exec exit expand export expr false fdformat fdisk fg fgrep file find fmt fold format ' +\n\t\t\t\t\t\t'free fsck ftp gawk getopts grep groups gzip hash head history hostname id ifconfig ' +\n\t\t\t\t\t\t'import install join kill less let ln local locate logname logout look lpc lpr lprint ' +\n\t\t\t\t\t\t'lprintd lprintq lprm ls lsof make man mkdir mkfifo mkisofs mknod more mount mtools ' +\n\t\t\t\t\t\t'mv netstat nice nl nohup nslookup open op passwd paste pathchk ping popd pr printcap ' +\n\t\t\t\t\t\t'printenv printf ps pushd pwd quota quotacheck quotactl ram rcp read readonly renice ' +\n\t\t\t\t\t\t'remsync rm rmdir rsync screen scp sdiff sed select seq set sftp shift shopt shutdown ' +\n\t\t\t\t\t\t'sleep sort source split ssh strace su sudo sum symlink sync tail tar tee test time ' +\n\t\t\t\t\t\t'times touch top traceroute trap tr true tsort tty type ulimit umask umount unalias ' +\n\t\t\t\t\t\t'uname unexpand uniq units unset unshar useradd usermod users uuencode uudecode v vdir ' +\n\t\t\t\t\t\t'vi watch wc whereis which who whoami Wget xargs yes'\n\t\t\t\t\t\t;\n\n\t\tthis.regexList = [\n\t\t\t{ regex: /^#!.*$/gm,\t\t\t\t\t\t\t\t\t\t\tcss: 'preprocessor bold' },\n\t\t\t{ regex: /\\/[\\w-\\/]+/gm,\t\t\t\t\t\t\t\t\t\tcss: 'plain' },\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleLinePerlComments,\t\tcss: 'comments' },\t\t// one line comments\n\t\t\t{ regex: SyntaxHighlighter.regexLib.doubleQuotedString,\t\t\tcss: 'string' },\t\t// double quoted strings\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleQuotedString,\t\t\tcss: 'string' },\t\t// single quoted strings\n\t\t\t{ regex: new RegExp(this.getKeywords(keywords), 'gm'),\t\t\tcss: 'keyword' },\t\t// keywords\n\t\t\t{ regex: new RegExp(this.getKeywords(commands), 'gm'),\t\t\tcss: 'functions' }\t\t// commands\n\t\t\t];\n\t}\n\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\n\tBrush.aliases\t= ['bash', 'shell', 'sh'];\n\n\tSyntaxHighlighter.brushes.Bash = Brush;\n\n\t// CommonJS\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\n})();\n;(function()\n{\n\t// CommonJS\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\n\n\tfunction Brush()\n\t{\n\t\t// Contributed by Jen\n\t\t// http://www.jensbits.com/2009/05/14/coldfusion-brush-for-syntaxhighlighter-plus\n\t\n\t\tvar funcs\t=\t'Abs ACos AddSOAPRequestHeader AddSOAPResponseHeader AjaxLink AjaxOnLoad ArrayAppend ArrayAvg ArrayClear ArrayDeleteAt ' + \n\t\t\t\t\t\t'ArrayInsertAt ArrayIsDefined ArrayIsEmpty ArrayLen ArrayMax ArrayMin ArraySet ArraySort ArraySum ArraySwap ArrayToList ' + \n\t\t\t\t\t\t'Asc ASin Atn BinaryDecode BinaryEncode BitAnd BitMaskClear BitMaskRead BitMaskSet BitNot BitOr BitSHLN BitSHRN BitXor ' + \n\t\t\t\t\t\t'Ceiling CharsetDecode CharsetEncode Chr CJustify Compare CompareNoCase Cos CreateDate CreateDateTime CreateObject ' + \n\t\t\t\t\t\t'CreateODBCDate CreateODBCDateTime CreateODBCTime CreateTime CreateTimeSpan CreateUUID DateAdd DateCompare DateConvert ' + \n\t\t\t\t\t\t'DateDiff DateFormat DatePart Day DayOfWeek DayOfWeekAsString DayOfYear DaysInMonth DaysInYear DE DecimalFormat DecrementValue ' + \n\t\t\t\t\t\t'Decrypt DecryptBinary DeleteClientVariable DeserializeJSON DirectoryExists DollarFormat DotNetToCFType Duplicate Encrypt ' + \n\t\t\t\t\t\t'EncryptBinary Evaluate Exp ExpandPath FileClose FileCopy FileDelete FileExists FileIsEOF FileMove FileOpen FileRead ' + \n\t\t\t\t\t\t'FileReadBinary FileReadLine FileSetAccessMode FileSetAttribute FileSetLastModified FileWrite Find FindNoCase FindOneOf ' + \n\t\t\t\t\t\t'FirstDayOfMonth Fix FormatBaseN GenerateSecretKey GetAuthUser GetBaseTagData GetBaseTagList GetBaseTemplatePath ' + \n\t\t\t\t\t\t'GetClientVariablesList GetComponentMetaData GetContextRoot GetCurrentTemplatePath GetDirectoryFromPath GetEncoding ' + \n\t\t\t\t\t\t'GetException GetFileFromPath GetFileInfo GetFunctionList GetGatewayHelper GetHttpRequestData GetHttpTimeString ' + \n\t\t\t\t\t\t'GetK2ServerDocCount GetK2ServerDocCountLimit GetLocale GetLocaleDisplayName GetLocalHostIP GetMetaData GetMetricData ' + \n\t\t\t\t\t\t'GetPageContext GetPrinterInfo GetProfileSections GetProfileString GetReadableImageFormats GetSOAPRequest GetSOAPRequestHeader ' + \n\t\t\t\t\t\t'GetSOAPResponse GetSOAPResponseHeader GetTempDirectory GetTempFile GetTemplatePath GetTickCount GetTimeZoneInfo GetToken ' + \n\t\t\t\t\t\t'GetUserRoles GetWriteableImageFormats Hash Hour HTMLCodeFormat HTMLEditFormat IIf ImageAddBorder ImageBlur ImageClearRect ' + \n\t\t\t\t\t\t'ImageCopy ImageCrop ImageDrawArc ImageDrawBeveledRect ImageDrawCubicCurve ImageDrawLine ImageDrawLines ImageDrawOval ' + \n\t\t\t\t\t\t'ImageDrawPoint ImageDrawQuadraticCurve ImageDrawRect ImageDrawRoundRect ImageDrawText ImageFlip ImageGetBlob ImageGetBufferedImage ' + \n\t\t\t\t\t\t'ImageGetEXIFTag ImageGetHeight ImageGetIPTCTag ImageGetWidth ImageGrayscale ImageInfo ImageNegative ImageNew ImageOverlay ImagePaste ' + \n\t\t\t\t\t\t'ImageRead ImageReadBase64 ImageResize ImageRotate ImageRotateDrawingAxis ImageScaleToFit ImageSetAntialiasing ImageSetBackgroundColor ' + \n\t\t\t\t\t\t'ImageSetDrawingColor ImageSetDrawingStroke ImageSetDrawingTransparency ImageSharpen ImageShear ImageShearDrawingAxis ImageTranslate ' + \n\t\t\t\t\t\t'ImageTranslateDrawingAxis ImageWrite ImageWriteBase64 ImageXORDrawingMode IncrementValue InputBaseN Insert Int IsArray IsBinary ' + \n\t\t\t\t\t\t'IsBoolean IsCustomFunction IsDate IsDDX IsDebugMode IsDefined IsImage IsImageFile IsInstanceOf IsJSON IsLeapYear IsLocalHost ' + \n\t\t\t\t\t\t'IsNumeric IsNumericDate IsObject IsPDFFile IsPDFObject IsQuery IsSimpleValue IsSOAPRequest IsStruct IsUserInAnyRole IsUserInRole ' + \n\t\t\t\t\t\t'IsUserLoggedIn IsValid IsWDDX IsXML IsXmlAttribute IsXmlDoc IsXmlElem IsXmlNode IsXmlRoot JavaCast JSStringFormat LCase Left Len ' + \n\t\t\t\t\t\t'ListAppend ListChangeDelims ListContains ListContainsNoCase ListDeleteAt ListFind ListFindNoCase ListFirst ListGetAt ListInsertAt ' + \n\t\t\t\t\t\t'ListLast ListLen ListPrepend ListQualify ListRest ListSetAt ListSort ListToArray ListValueCount ListValueCountNoCase LJustify Log ' + \n\t\t\t\t\t\t'Log10 LSCurrencyFormat LSDateFormat LSEuroCurrencyFormat LSIsCurrency LSIsDate LSIsNumeric LSNumberFormat LSParseCurrency LSParseDateTime ' + \n\t\t\t\t\t\t'LSParseEuroCurrency LSParseNumber LSTimeFormat LTrim Max Mid Min Minute Month MonthAsString Now NumberFormat ParagraphFormat ParseDateTime ' + \n\t\t\t\t\t\t'Pi PrecisionEvaluate PreserveSingleQuotes Quarter QueryAddColumn QueryAddRow QueryConvertForGrid QueryNew QuerySetCell QuotedValueList Rand ' + \n\t\t\t\t\t\t'Randomize RandRange REFind REFindNoCase ReleaseComObject REMatch REMatchNoCase RemoveChars RepeatString Replace ReplaceList ReplaceNoCase ' + \n\t\t\t\t\t\t'REReplace REReplaceNoCase Reverse Right RJustify Round RTrim Second SendGatewayMessage SerializeJSON SetEncoding SetLocale SetProfileString ' + \n\t\t\t\t\t\t'SetVariable Sgn Sin Sleep SpanExcluding SpanIncluding Sqr StripCR StructAppend StructClear StructCopy StructCount StructDelete StructFind ' + \n\t\t\t\t\t\t'StructFindKey StructFindValue StructGet StructInsert StructIsEmpty StructKeyArray StructKeyExists StructKeyList StructKeyList StructNew ' + \n\t\t\t\t\t\t'StructSort StructUpdate Tan TimeFormat ToBase64 ToBinary ToScript ToString Trim UCase URLDecode URLEncodedFormat URLSessionFormat Val ' + \n\t\t\t\t\t\t'ValueList VerifyClient Week Wrap Wrap WriteOutput XmlChildPos XmlElemNew XmlFormat XmlGetNodeType XmlNew XmlParse XmlSearch XmlTransform ' + \n\t\t\t\t\t\t'XmlValidate Year YesNoFormat';\n\n\t\tvar keywords =\t'cfabort cfajaximport cfajaxproxy cfapplet cfapplication cfargument cfassociate cfbreak cfcache cfcalendar ' + \n\t\t\t\t\t\t'cfcase cfcatch cfchart cfchartdata cfchartseries cfcol cfcollection cfcomponent cfcontent cfcookie cfdbinfo ' + \n\t\t\t\t\t\t'cfdefaultcase cfdirectory cfdiv cfdocument cfdocumentitem cfdocumentsection cfdump cfelse cfelseif cferror ' + \n\t\t\t\t\t\t'cfexchangecalendar cfexchangeconnection cfexchangecontact cfexchangefilter cfexchangemail cfexchangetask ' + \n\t\t\t\t\t\t'cfexecute cfexit cffeed cffile cfflush cfform cfformgroup cfformitem cfftp cffunction cfgrid cfgridcolumn ' + \n\t\t\t\t\t\t'cfgridrow cfgridupdate cfheader cfhtmlhead cfhttp cfhttpparam cfif cfimage cfimport cfinclude cfindex ' + \n\t\t\t\t\t\t'cfinput cfinsert cfinterface cfinvoke cfinvokeargument cflayout cflayoutarea cfldap cflocation cflock cflog ' + \n\t\t\t\t\t\t'cflogin cfloginuser cflogout cfloop cfmail cfmailparam cfmailpart cfmenu cfmenuitem cfmodule cfNTauthenticate ' + \n\t\t\t\t\t\t'cfobject cfobjectcache cfoutput cfparam cfpdf cfpdfform cfpdfformparam cfpdfparam cfpdfsubform cfpod cfpop ' + \n\t\t\t\t\t\t'cfpresentation cfpresentationslide cfpresenter cfprint cfprocessingdirective cfprocparam cfprocresult ' + \n\t\t\t\t\t\t'cfproperty cfquery cfqueryparam cfregistry cfreport cfreportparam cfrethrow cfreturn cfsavecontent cfschedule ' + \n\t\t\t\t\t\t'cfscript cfsearch cfselect cfset cfsetting cfsilent cfslider cfsprydataset cfstoredproc cfswitch cftable ' + \n\t\t\t\t\t\t'cftextarea cfthread cfthrow cftimer cftooltip cftrace cftransaction cftree cftreeitem cftry cfupdate cfwddx ' + \n\t\t\t\t\t\t'cfwindow cfxml cfzip cfzipparam';\n\n\t\tvar operators =\t'all and any between cross in join like not null or outer some';\n\n\t\tthis.regexList = [\n\t\t\t{ regex: new RegExp('--(.*)$', 'gm'),\t\t\t\t\t\tcss: 'comments' },  // one line and multiline comments\n\t\t\t{ regex: SyntaxHighlighter.regexLib.xmlComments,\t\t\tcss: 'comments' },    // single quoted strings\n\t\t\t{ regex: SyntaxHighlighter.regexLib.doubleQuotedString,\t\tcss: 'string' },    // double quoted strings\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleQuotedString,\t\tcss: 'string' },    // single quoted strings\n\t\t\t{ regex: new RegExp(this.getKeywords(funcs), 'gmi'),\t\tcss: 'functions' }, // functions\n\t\t\t{ regex: new RegExp(this.getKeywords(operators), 'gmi'),\tcss: 'color1' },    // operators and such\n\t\t\t{ regex: new RegExp(this.getKeywords(keywords), 'gmi'),\t\tcss: 'keyword' }    // keyword\n\t\t\t];\n\t}\n\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\n\tBrush.aliases\t= ['coldfusion','cf'];\n\t\n\tSyntaxHighlighter.brushes.ColdFusion = Brush;\n\n\t// CommonJS\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\n})();\n;(function()\n{\n\t// CommonJS\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\n\n\tfunction Brush()\n\t{\n\t\t// Copyright 2006 Shin, YoungJin\n\t\n\t\tvar datatypes =\t'ATOM BOOL BOOLEAN BYTE CHAR COLORREF DWORD DWORDLONG DWORD_PTR ' +\n\t\t\t\t\t\t'DWORD32 DWORD64 FLOAT HACCEL HALF_PTR HANDLE HBITMAP HBRUSH ' +\n\t\t\t\t\t\t'HCOLORSPACE HCONV HCONVLIST HCURSOR HDC HDDEDATA HDESK HDROP HDWP ' +\n\t\t\t\t\t\t'HENHMETAFILE HFILE HFONT HGDIOBJ HGLOBAL HHOOK HICON HINSTANCE HKEY ' +\n\t\t\t\t\t\t'HKL HLOCAL HMENU HMETAFILE HMODULE HMONITOR HPALETTE HPEN HRESULT ' +\n\t\t\t\t\t\t'HRGN HRSRC HSZ HWINSTA HWND INT INT_PTR INT32 INT64 LANGID LCID LCTYPE ' +\n\t\t\t\t\t\t'LGRPID LONG LONGLONG LONG_PTR LONG32 LONG64 LPARAM LPBOOL LPBYTE LPCOLORREF ' +\n\t\t\t\t\t\t'LPCSTR LPCTSTR LPCVOID LPCWSTR LPDWORD LPHANDLE LPINT LPLONG LPSTR LPTSTR ' +\n\t\t\t\t\t\t'LPVOID LPWORD LPWSTR LRESULT PBOOL PBOOLEAN PBYTE PCHAR PCSTR PCTSTR PCWSTR ' +\n\t\t\t\t\t\t'PDWORDLONG PDWORD_PTR PDWORD32 PDWORD64 PFLOAT PHALF_PTR PHANDLE PHKEY PINT ' +\n\t\t\t\t\t\t'PINT_PTR PINT32 PINT64 PLCID PLONG PLONGLONG PLONG_PTR PLONG32 PLONG64 POINTER_32 ' +\n\t\t\t\t\t\t'POINTER_64 PSHORT PSIZE_T PSSIZE_T PSTR PTBYTE PTCHAR PTSTR PUCHAR PUHALF_PTR ' +\n\t\t\t\t\t\t'PUINT PUINT_PTR PUINT32 PUINT64 PULONG PULONGLONG PULONG_PTR PULONG32 PULONG64 ' +\n\t\t\t\t\t\t'PUSHORT PVOID PWCHAR PWORD PWSTR SC_HANDLE SC_LOCK SERVICE_STATUS_HANDLE SHORT ' +\n\t\t\t\t\t\t'SIZE_T SSIZE_T TBYTE TCHAR UCHAR UHALF_PTR UINT UINT_PTR UINT32 UINT64 ULONG ' +\n\t\t\t\t\t\t'ULONGLONG ULONG_PTR ULONG32 ULONG64 USHORT USN VOID WCHAR WORD WPARAM WPARAM WPARAM ' +\n\t\t\t\t\t\t'char bool short int __int32 __int64 __int8 __int16 long float double __wchar_t ' +\n\t\t\t\t\t\t'clock_t _complex _dev_t _diskfree_t div_t ldiv_t _exception _EXCEPTION_POINTERS ' +\n\t\t\t\t\t\t'FILE _finddata_t _finddatai64_t _wfinddata_t _wfinddatai64_t __finddata64_t ' +\n\t\t\t\t\t\t'__wfinddata64_t _FPIEEE_RECORD fpos_t _HEAPINFO _HFILE lconv intptr_t ' +\n\t\t\t\t\t\t'jmp_buf mbstate_t _off_t _onexit_t _PNH ptrdiff_t _purecall_handler ' +\n\t\t\t\t\t\t'sig_atomic_t size_t _stat __stat64 _stati64 terminate_function ' +\n\t\t\t\t\t\t'time_t __time64_t _timeb __timeb64 tm uintptr_t _utimbuf ' +\n\t\t\t\t\t\t'va_list wchar_t wctrans_t wctype_t wint_t signed';\n\n\t\tvar keywords =\t'auto break case catch class const decltype __finally __exception __try ' +\n\t\t\t\t\t\t'const_cast continue private public protected __declspec ' +\n\t\t\t\t\t\t'default delete deprecated dllexport dllimport do dynamic_cast ' +\n\t\t\t\t\t\t'else enum explicit extern if for friend goto inline ' +\n\t\t\t\t\t\t'mutable naked namespace new noinline noreturn nothrow ' +\n\t\t\t\t\t\t'register reinterpret_cast return selectany ' +\n\t\t\t\t\t\t'sizeof static static_cast struct switch template this ' +\n\t\t\t\t\t\t'thread throw true false try typedef typeid typename union ' +\n\t\t\t\t\t\t'using uuid virtual void volatile whcar_t while';\n\t\t\t\t\t\n\t\tvar functions =\t'assert isalnum isalpha iscntrl isdigit isgraph islower isprint' +\n\t\t\t\t\t\t'ispunct isspace isupper isxdigit tolower toupper errno localeconv ' +\n\t\t\t\t\t\t'setlocale acos asin atan atan2 ceil cos cosh exp fabs floor fmod ' +\n\t\t\t\t\t\t'frexp ldexp log log10 modf pow sin sinh sqrt tan tanh jmp_buf ' +\n\t\t\t\t\t\t'longjmp setjmp raise signal sig_atomic_t va_arg va_end va_start ' +\n\t\t\t\t\t\t'clearerr fclose feof ferror fflush fgetc fgetpos fgets fopen ' +\n\t\t\t\t\t\t'fprintf fputc fputs fread freopen fscanf fseek fsetpos ftell ' +\n\t\t\t\t\t\t'fwrite getc getchar gets perror printf putc putchar puts remove ' +\n\t\t\t\t\t\t'rename rewind scanf setbuf setvbuf sprintf sscanf tmpfile tmpnam ' +\n\t\t\t\t\t\t'ungetc vfprintf vprintf vsprintf abort abs atexit atof atoi atol ' +\n\t\t\t\t\t\t'bsearch calloc div exit free getenv labs ldiv malloc mblen mbstowcs ' +\n\t\t\t\t\t\t'mbtowc qsort rand realloc srand strtod strtol strtoul system ' +\n\t\t\t\t\t\t'wcstombs wctomb memchr memcmp memcpy memmove memset strcat strchr ' +\n\t\t\t\t\t\t'strcmp strcoll strcpy strcspn strerror strlen strncat strncmp ' +\n\t\t\t\t\t\t'strncpy strpbrk strrchr strspn strstr strtok strxfrm asctime ' +\n\t\t\t\t\t\t'clock ctime difftime gmtime localtime mktime strftime time';\n\n\t\tthis.regexList = [\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleLineCComments,\tcss: 'comments' },\t\t\t// one line comments\n\t\t\t{ regex: SyntaxHighlighter.regexLib.multiLineCComments,\t\tcss: 'comments' },\t\t\t// multiline comments\n\t\t\t{ regex: SyntaxHighlighter.regexLib.doubleQuotedString,\t\tcss: 'string' },\t\t\t// strings\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleQuotedString,\t\tcss: 'string' },\t\t\t// strings\n\t\t\t{ regex: /^ *#.*/gm,\t\t\t\t\t\t\t\t\t\tcss: 'preprocessor' },\n\t\t\t{ regex: new RegExp(this.getKeywords(datatypes), 'gm'),\t\tcss: 'color1 bold' },\n\t\t\t{ regex: new RegExp(this.getKeywords(functions), 'gm'),\t\tcss: 'functions bold' },\n\t\t\t{ regex: new RegExp(this.getKeywords(keywords), 'gm'),\t\tcss: 'keyword bold' }\n\t\t\t];\n\t};\n\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\n\tBrush.aliases\t= ['cpp', 'c'];\n\n\tSyntaxHighlighter.brushes.Cpp = Brush;\n\n\t// CommonJS\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\n})();\n;(function()\n{\n\t// CommonJS\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\n\n\tfunction Brush()\n\t{\n\t\tvar keywords =\t'abstract as base bool break byte case catch char checked class const ' +\n\t\t\t\t\t\t'continue decimal default delegate do double else enum event explicit volatile ' +\n\t\t\t\t\t\t'extern false finally fixed float for foreach get goto if implicit in int ' +\n\t\t\t\t\t\t'interface internal is lock long namespace new null object operator out ' +\n\t\t\t\t\t\t'override params private protected public readonly ref return sbyte sealed set ' +\n\t\t\t\t\t\t'short sizeof stackalloc static string struct switch this throw true try ' +\n\t\t\t\t\t\t'typeof uint ulong unchecked unsafe ushort using virtual void while var ' +\n\t\t\t\t\t\t'from group by into select let where orderby join on equals ascending descending';\n\n\t\tfunction fixComments(match, regexInfo)\n\t\t{\n\t\t\tvar css = (match[0].indexOf(\"///\") == 0)\n\t\t\t\t? 'color1'\n\t\t\t\t: 'comments'\n\t\t\t\t;\n\t\t\t\n\t\t\treturn [new SyntaxHighlighter.Match(match[0], match.index, css)];\n\t\t}\n\n\t\tthis.regexList = [\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleLineCComments,\tfunc : fixComments },\t\t// one line comments\n\t\t\t{ regex: SyntaxHighlighter.regexLib.multiLineCComments,\t\tcss: 'comments' },\t\t\t// multiline comments\n\t\t\t{ regex: /@\"(?:[^\"]|\"\")*\"/g,\t\t\t\t\t\t\t\tcss: 'string' },\t\t\t// @-quoted strings\n\t\t\t{ regex: SyntaxHighlighter.regexLib.doubleQuotedString,\t\tcss: 'string' },\t\t\t// strings\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleQuotedString,\t\tcss: 'string' },\t\t\t// strings\n\t\t\t{ regex: /^\\s*#.*/gm,\t\t\t\t\t\t\t\t\t\tcss: 'preprocessor' },\t\t// preprocessor tags like #region and #endregion\n\t\t\t{ regex: new RegExp(this.getKeywords(keywords), 'gm'),\t\tcss: 'keyword' },\t\t\t// c# keyword\n\t\t\t{ regex: /\\bpartial(?=\\s+(?:class|interface|struct)\\b)/g,\tcss: 'keyword' },\t\t\t// contextual keyword: 'partial'\n\t\t\t{ regex: /\\byield(?=\\s+(?:return|break)\\b)/g,\t\t\t\tcss: 'keyword' }\t\t\t// contextual keyword: 'yield'\n\t\t\t];\n\t\t\n\t\tthis.forHtmlScript(SyntaxHighlighter.regexLib.aspScriptTags);\n\t};\n\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\n\tBrush.aliases\t= ['c#', 'c-sharp', 'csharp'];\n\n\tSyntaxHighlighter.brushes.CSharp = Brush;\n\n\t// CommonJS\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\n})();\n;(function()\n{\n\t// CommonJS\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\n\n\tfunction Brush()\n\t{\n\t\tfunction getKeywordsCSS(str)\n\t\t{\n\t\t\treturn '\\\\b([a-z_]|)' + str.replace(/ /g, '(?=:)\\\\b|\\\\b([a-z_\\\\*]|\\\\*|)') + '(?=:)\\\\b';\n\t\t};\n\t\n\t\tfunction getValuesCSS(str)\n\t\t{\n\t\t\treturn '\\\\b' + str.replace(/ /g, '(?!-)(?!:)\\\\b|\\\\b()') + '\\:\\\\b';\n\t\t};\n\n\t\tvar keywords =\t'ascent azimuth background-attachment background-color background-image background-position ' +\n\t\t\t\t\t\t'background-repeat background baseline bbox border-collapse border-color border-spacing border-style border-top ' +\n\t\t\t\t\t\t'border-right border-bottom border-left border-top-color border-right-color border-bottom-color border-left-color ' +\n\t\t\t\t\t\t'border-top-style border-right-style border-bottom-style border-left-style border-top-width border-right-width ' +\n\t\t\t\t\t\t'border-bottom-width border-left-width border-width border bottom cap-height caption-side centerline clear clip color ' +\n\t\t\t\t\t\t'content counter-increment counter-reset cue-after cue-before cue cursor definition-src descent direction display ' +\n\t\t\t\t\t\t'elevation empty-cells float font-size-adjust font-family font-size font-stretch font-style font-variant font-weight font ' +\n\t\t\t\t\t\t'height left letter-spacing line-height list-style-image list-style-position list-style-type list-style margin-top ' +\n\t\t\t\t\t\t'margin-right margin-bottom margin-left margin marker-offset marks mathline max-height max-width min-height min-width orphans ' +\n\t\t\t\t\t\t'outline-color outline-style outline-width outline overflow padding-top padding-right padding-bottom padding-left padding page ' +\n\t\t\t\t\t\t'page-break-after page-break-before page-break-inside pause pause-after pause-before pitch pitch-range play-during position ' +\n\t\t\t\t\t\t'quotes right richness size slope src speak-header speak-numeral speak-punctuation speak speech-rate stemh stemv stress ' +\n\t\t\t\t\t\t'table-layout text-align top text-decoration text-indent text-shadow text-transform unicode-bidi unicode-range units-per-em ' +\n\t\t\t\t\t\t'vertical-align visibility voice-family volume white-space widows width widths word-spacing x-height z-index';\n\n\t\tvar values =\t'above absolute all always aqua armenian attr aural auto avoid baseline behind below bidi-override black blink block blue bold bolder '+\n\t\t\t\t\t\t'both bottom braille capitalize caption center center-left center-right circle close-quote code collapse compact condensed '+\n\t\t\t\t\t\t'continuous counter counters crop cross crosshair cursive dashed decimal decimal-leading-zero default digits disc dotted double '+\n\t\t\t\t\t\t'embed embossed e-resize expanded extra-condensed extra-expanded fantasy far-left far-right fast faster fixed format fuchsia '+\n\t\t\t\t\t\t'gray green groove handheld hebrew help hidden hide high higher icon inline-table inline inset inside invert italic '+\n\t\t\t\t\t\t'justify landscape large larger left-side left leftwards level lighter lime line-through list-item local loud lower-alpha '+\n\t\t\t\t\t\t'lowercase lower-greek lower-latin lower-roman lower low ltr marker maroon medium message-box middle mix move narrower '+\n\t\t\t\t\t\t'navy ne-resize no-close-quote none no-open-quote no-repeat normal nowrap n-resize nw-resize oblique olive once open-quote outset '+\n\t\t\t\t\t\t'outside overline pointer portrait pre print projection purple red relative repeat repeat-x repeat-y rgb ridge right right-side '+\n\t\t\t\t\t\t'rightwards rtl run-in screen scroll semi-condensed semi-expanded separate se-resize show silent silver slower slow '+\n\t\t\t\t\t\t'small small-caps small-caption smaller soft solid speech spell-out square s-resize static status-bar sub super sw-resize '+\n\t\t\t\t\t\t'table-caption table-cell table-column table-column-group table-footer-group table-header-group table-row table-row-group teal '+\n\t\t\t\t\t\t'text-bottom text-top thick thin top transparent tty tv ultra-condensed ultra-expanded underline upper-alpha uppercase upper-latin '+\n\t\t\t\t\t\t'upper-roman url visible wait white wider w-resize x-fast x-high x-large x-loud x-low x-slow x-small x-soft xx-large xx-small yellow';\n\n\t\tvar fonts =\t\t'[mM]onospace [tT]ahoma [vV]erdana [aA]rial [hH]elvetica [sS]ans-serif [sS]erif [cC]ourier mono sans serif';\n\t\n\t\tthis.regexList = [\n\t\t\t{ regex: SyntaxHighlighter.regexLib.multiLineCComments,\t\tcss: 'comments' },\t// multiline comments\n\t\t\t{ regex: SyntaxHighlighter.regexLib.doubleQuotedString,\t\tcss: 'string' },\t// double quoted strings\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleQuotedString,\t\tcss: 'string' },\t// single quoted strings\n\t\t\t{ regex: /\\#[a-fA-F0-9]{3,6}/g,\t\t\t\t\t\t\t\tcss: 'value' },\t\t// html colors\n\t\t\t{ regex: /(-?\\d+)(\\.\\d+)?(px|em|pt|\\:|\\%|)/g,\t\t\t\tcss: 'value' },\t\t// sizes\n\t\t\t{ regex: /!important/g,\t\t\t\t\t\t\t\t\t\tcss: 'color3' },\t// !important\n\t\t\t{ regex: new RegExp(getKeywordsCSS(keywords), 'gm'),\t\tcss: 'keyword' },\t// keywords\n\t\t\t{ regex: new RegExp(getValuesCSS(values), 'g'),\t\t\t\tcss: 'value' },\t\t// values\n\t\t\t{ regex: new RegExp(this.getKeywords(fonts), 'g'),\t\t\tcss: 'color1' }\t\t// fonts\n\t\t\t];\n\n\t\tthis.forHtmlScript({ \n\t\t\tleft: /(&lt;|<)\\s*style.*?(&gt;|>)/gi, \n\t\t\tright: /(&lt;|<)\\/\\s*style\\s*(&gt;|>)/gi \n\t\t\t});\n\t};\n\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\n\tBrush.aliases\t= ['css'];\n\n\tSyntaxHighlighter.brushes.CSS = Brush;\n\n\t// CommonJS\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\n})();\n;(function()\n{\n\t// CommonJS\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\n\n\tfunction Brush()\n\t{\n\t\tvar keywords =\t'abs addr and ansichar ansistring array as asm begin boolean byte cardinal ' +\n\t\t\t\t\t\t'case char class comp const constructor currency destructor div do double ' +\n\t\t\t\t\t\t'downto else end except exports extended false file finalization finally ' +\n\t\t\t\t\t\t'for function goto if implementation in inherited int64 initialization ' +\n\t\t\t\t\t\t'integer interface is label library longint longword mod nil not object ' +\n\t\t\t\t\t\t'of on or packed pansichar pansistring pchar pcurrency pdatetime pextended ' +\n\t\t\t\t\t\t'pint64 pointer private procedure program property pshortstring pstring ' +\n\t\t\t\t\t\t'pvariant pwidechar pwidestring protected public published raise real real48 ' +\n\t\t\t\t\t\t'record repeat set shl shortint shortstring shr single smallint string then ' +\n\t\t\t\t\t\t'threadvar to true try type unit until uses val var varirnt while widechar ' +\n\t\t\t\t\t\t'widestring with word write writeln xor';\n\n\t\tthis.regexList = [\n\t\t\t{ regex: /\\(\\*[\\s\\S]*?\\*\\)/gm,\t\t\t\t\t\t\t\tcss: 'comments' },  \t// multiline comments (* *)\n\t\t\t{ regex: /{(?!\\$)[\\s\\S]*?}/gm,\t\t\t\t\t\t\t\tcss: 'comments' },  \t// multiline comments { }\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleLineCComments,\tcss: 'comments' },  \t// one line\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleQuotedString,\t\tcss: 'string' },\t\t// strings\n\t\t\t{ regex: /\\{\\$[a-zA-Z]+ .+\\}/g,\t\t\t\t\t\t\t\tcss: 'color1' },\t\t// compiler Directives and Region tags\n\t\t\t{ regex: /\\b[\\d\\.]+\\b/g,\t\t\t\t\t\t\t\t\tcss: 'value' },\t\t\t// numbers 12345\n\t\t\t{ regex: /\\$[a-zA-Z0-9]+\\b/g,\t\t\t\t\t\t\t\tcss: 'value' },\t\t\t// numbers $F5D3\n\t\t\t{ regex: new RegExp(this.getKeywords(keywords), 'gmi'),\t\tcss: 'keyword' }\t\t// keyword\n\t\t\t];\n\t};\n\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\n\tBrush.aliases\t= ['delphi', 'pascal', 'pas'];\n\n\tSyntaxHighlighter.brushes.Delphi = Brush;\n\n\t// CommonJS\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\n})();\n;(function()\n{\n\t// CommonJS\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\n\n\tfunction Brush()\n\t{\n\t\tthis.regexList = [\n\t\t\t{ regex: /^\\+\\+\\+ .*$/gm,\tcss: 'color2' },\t// new file\n\t\t\t{ regex: /^\\-\\-\\- .*$/gm,\tcss: 'color2' },\t// old file\n\t\t\t{ regex: /^\\s.*$/gm,\t\tcss: 'color1' },\t// unchanged\n\t\t\t{ regex: /^@@.*@@.*$/gm,\tcss: 'variable' },\t// location\n\t\t\t{ regex: /^\\+.*$/gm,\t\tcss: 'string' },\t// additions\n\t\t\t{ regex: /^\\-.*$/gm,\t\tcss: 'color3' }\t\t// deletions\n\t\t\t];\n\t};\n\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\n\tBrush.aliases\t= ['diff', 'patch'];\n\n\tSyntaxHighlighter.brushes.Diff = Brush;\n\n\t// CommonJS\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\n})();\n;(function()\n{\n\t// CommonJS\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\n\n\tfunction Brush()\n\t{\n\t\t// Contributed by Jean-Lou Dupont\n\t\t// http://jldupont.blogspot.com/2009/06/erlang-syntax-highlighter.html  \n\n\t\t// According to: http://erlang.org/doc/reference_manual/introduction.html#1.5\n\t\tvar keywords = 'after and andalso band begin bnot bor bsl bsr bxor '+\n\t\t\t'case catch cond div end fun if let not of or orelse '+\n\t\t\t'query receive rem try when xor'+\n\t\t\t// additional\n\t\t\t' module export import define';\n\n\t\tthis.regexList = [\n\t\t\t{ regex: new RegExp(\"[A-Z][A-Za-z0-9_]+\", 'g'), \t\t\tcss: 'constants' },\n\t\t\t{ regex: new RegExp(\"\\\\%.+\", 'gm'), \t\t\t\t\t\tcss: 'comments' },\n\t\t\t{ regex: new RegExp(\"\\\\?[A-Za-z0-9_]+\", 'g'), \t\t\t\tcss: 'preprocessor' },\n\t\t\t{ regex: new RegExp(\"[a-z0-9_]+:[a-z0-9_]+\", 'g'), \t\t\tcss: 'functions' },\n\t\t\t{ regex: SyntaxHighlighter.regexLib.doubleQuotedString,\t\tcss: 'string' },\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleQuotedString,\t\tcss: 'string' },\n\t\t\t{ regex: new RegExp(this.getKeywords(keywords),\t'gm'),\t\tcss: 'keyword' }\n\t\t\t];\n\t};\n\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\n\tBrush.aliases\t= ['erl', 'erlang'];\n\n\tSyntaxHighlighter.brushes.Erland = Brush;\n\n\t// CommonJS\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\n})();\n;(function()\n{\n\t// CommonJS\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\n\n\tfunction Brush()\n\t{\n\t\t// Contributed by Andres Almiray\n\t\t// http://jroller.com/aalmiray/entry/nice_source_code_syntax_highlighter\n\n\t\tvar keywords =\t'as assert break case catch class continue def default do else extends finally ' +\n\t\t\t\t\t\t'if in implements import instanceof interface new package property return switch ' +\n\t\t\t\t\t\t'throw throws try while public protected private static';\n\t\tvar types    =  'void boolean byte char short int long float double';\n\t\tvar constants = 'null';\n\t\tvar methods   = 'allProperties count get size '+\n\t\t\t\t\t\t'collect each eachProperty eachPropertyName eachWithIndex find findAll ' +\n\t\t\t\t\t\t'findIndexOf grep inject max min reverseEach sort ' +\n\t\t\t\t\t\t'asImmutable asSynchronized flatten intersect join pop reverse subMap toList ' +\n\t\t\t\t\t\t'padRight padLeft contains eachMatch toCharacter toLong toUrl tokenize ' +\n\t\t\t\t\t\t'eachFile eachFileRecurse eachB yte eachLine readBytes readLine getText ' +\n\t\t\t\t\t\t'splitEachLine withReader append encodeBase64 decodeBase64 filterLine ' +\n\t\t\t\t\t\t'transformChar transformLine withOutputStream withPrintWriter withStream ' +\n\t\t\t\t\t\t'withStreams withWriter withWriterAppend write writeLine '+\n\t\t\t\t\t\t'dump inspect invokeMethod print println step times upto use waitForOrKill '+\n\t\t\t\t\t\t'getText';\n\n\t\tthis.regexList = [\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleLineCComments,\t\t\t\tcss: 'comments' },\t\t// one line comments\n\t\t\t{ regex: SyntaxHighlighter.regexLib.multiLineCComments,\t\t\t\t\tcss: 'comments' },\t\t// multiline comments\n\t\t\t{ regex: SyntaxHighlighter.regexLib.doubleQuotedString,\t\t\t\t\tcss: 'string' },\t\t// strings\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleQuotedString,\t\t\t\t\tcss: 'string' },\t\t// strings\n\t\t\t{ regex: /\"\"\".*\"\"\"/g,\t\t\t\t\t\t\t\t\t\t\t\t\tcss: 'string' },\t\t// GStrings\n\t\t\t{ regex: new RegExp('\\\\b([\\\\d]+(\\\\.[\\\\d]+)?|0x[a-f0-9]+)\\\\b', 'gi'),\tcss: 'value' },\t\t\t// numbers\n\t\t\t{ regex: new RegExp(this.getKeywords(keywords), 'gm'),\t\t\t\t\tcss: 'keyword' },\t\t// goovy keyword\n\t\t\t{ regex: new RegExp(this.getKeywords(types), 'gm'),\t\t\t\t\t\tcss: 'color1' },\t\t// goovy/java type\n\t\t\t{ regex: new RegExp(this.getKeywords(constants), 'gm'),\t\t\t\t\tcss: 'constants' },\t\t// constants\n\t\t\t{ regex: new RegExp(this.getKeywords(methods), 'gm'),\t\t\t\t\tcss: 'functions' }\t\t// methods\n\t\t\t];\n\n\t\tthis.forHtmlScript(SyntaxHighlighter.regexLib.aspScriptTags);\n\t}\n\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\n\tBrush.aliases\t= ['groovy'];\n\n\tSyntaxHighlighter.brushes.Groovy = Brush;\n\n\t// CommonJS\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\n})();\n;(function()\n{\n\t// CommonJS\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\n\n\tfunction Brush()\n\t{\n\t\tvar keywords =\t'abstract assert boolean break byte case catch char class const ' +\n\t\t\t\t\t\t'continue default do double else enum extends ' +\n\t\t\t\t\t\t'false final finally float for goto if implements import ' +\n\t\t\t\t\t\t'instanceof int interface long native new null ' +\n\t\t\t\t\t\t'package private protected public return ' +\n\t\t\t\t\t\t'short static strictfp super switch synchronized this throw throws true ' +\n\t\t\t\t\t\t'transient try void volatile while';\n\n\t\tthis.regexList = [\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleLineCComments,\tcss: 'comments' },\t\t// one line comments\n\t\t\t{ regex: /\\/\\*([^\\*][\\s\\S]*)?\\*\\//gm,\t\t\t\t\t\tcss: 'comments' },\t \t// multiline comments\n\t\t\t{ regex: /\\/\\*(?!\\*\\/)\\*[\\s\\S]*?\\*\\//gm,\t\t\t\t\tcss: 'preprocessor' },\t// documentation comments\n\t\t\t{ regex: SyntaxHighlighter.regexLib.doubleQuotedString,\t\tcss: 'string' },\t\t// strings\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleQuotedString,\t\tcss: 'string' },\t\t// strings\n\t\t\t{ regex: /\\b([\\d]+(\\.[\\d]+)?|0x[a-f0-9]+)\\b/gi,\t\t\t\tcss: 'value' },\t\t\t// numbers\n\t\t\t{ regex: /(?!\\@interface\\b)\\@[\\$\\w]+\\b/g,\t\t\t\t\tcss: 'color1' },\t\t// annotation @anno\n\t\t\t{ regex: /\\@interface\\b/g,\t\t\t\t\t\t\t\t\tcss: 'color2' },\t\t// @interface keyword\n\t\t\t{ regex: new RegExp(this.getKeywords(keywords), 'gm'),\t\tcss: 'keyword' }\t\t// java keyword\n\t\t\t];\n\n\t\tthis.forHtmlScript({\n\t\t\tleft\t: /(&lt;|<)%[@!=]?/g, \n\t\t\tright\t: /%(&gt;|>)/g \n\t\t});\n\t};\n\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\n\tBrush.aliases\t= ['java'];\n\n\tSyntaxHighlighter.brushes.Java = Brush;\n\n\t// CommonJS\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\n})();\n;(function()\n{\n\t// CommonJS\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\n\n\tfunction Brush()\n\t{\n\t\t// Contributed by Patrick Webster\n\t\t// http://patrickwebster.blogspot.com/2009/04/javafx-brush-for-syntaxhighlighter.html\n\t\tvar datatypes =\t'Boolean Byte Character Double Duration '\n\t\t\t\t\t\t+ 'Float Integer Long Number Short String Void'\n\t\t\t\t\t\t;\n\n\t\tvar keywords = 'abstract after and as assert at before bind bound break catch class '\n\t\t\t\t\t\t+ 'continue def delete else exclusive extends false finally first for from '\n\t\t\t\t\t\t+ 'function if import in indexof init insert instanceof into inverse last '\n\t\t\t\t\t\t+ 'lazy mixin mod nativearray new not null on or override package postinit '\n\t\t\t\t\t\t+ 'protected public public-init public-read replace return reverse sizeof '\n\t\t\t\t\t\t+ 'step super then this throw true try tween typeof var where while with '\n\t\t\t\t\t\t+ 'attribute let private readonly static trigger'\n\t\t\t\t\t\t;\n\n\t\tthis.regexList = [\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleLineCComments,\tcss: 'comments' },\n\t\t\t{ regex: SyntaxHighlighter.regexLib.multiLineCComments,\t\tcss: 'comments' },\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleQuotedString,\t\tcss: 'string' },\n\t\t\t{ regex: SyntaxHighlighter.regexLib.doubleQuotedString,\t\tcss: 'string' },\n\t\t\t{ regex: /(-?\\.?)(\\b(\\d*\\.?\\d+|\\d+\\.?\\d*)(e[+-]?\\d+)?|0x[a-f\\d]+)\\b\\.?/gi, css: 'color2' },\t// numbers\n\t\t\t{ regex: new RegExp(this.getKeywords(datatypes), 'gm'),\t\tcss: 'variable' },\t// datatypes\n\t\t\t{ regex: new RegExp(this.getKeywords(keywords), 'gm'),\t\tcss: 'keyword' }\n\t\t];\n\t\tthis.forHtmlScript(SyntaxHighlighter.regexLib.aspScriptTags);\n\t};\n\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\n\tBrush.aliases\t= ['jfx', 'javafx'];\n\n\tSyntaxHighlighter.brushes.JavaFX = Brush;\n\n\t// CommonJS\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\n})();\n;(function()\n{\n\t// CommonJS\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\n\n\tfunction Brush()\n\t{\n\t\tvar keywords =\t'break case catch continue ' +\n\t\t\t\t\t\t'default delete do else false  ' +\n\t\t\t\t\t\t'for function if in instanceof ' +\n\t\t\t\t\t\t'new null return super switch ' +\n\t\t\t\t\t\t'this throw true try typeof var while with'\n\t\t\t\t\t\t;\n\n\t\tvar r = SyntaxHighlighter.regexLib;\n\t\t\n\t\tthis.regexList = [\n\t\t\t{ regex: r.multiLineDoubleQuotedString,\t\t\t\t\tcss: 'string' },\t\t\t// double quoted strings\n\t\t\t{ regex: r.multiLineSingleQuotedString,\t\t\t\t\tcss: 'string' },\t\t\t// single quoted strings\n\t\t\t{ regex: r.singleLineCComments,\t\t\t\t\t\t\tcss: 'comments' },\t\t\t// one line comments\n\t\t\t{ regex: r.multiLineCComments,\t\t\t\t\t\t\tcss: 'comments' },\t\t\t// multiline comments\n\t\t\t{ regex: /\\s*#.*/gm,\t\t\t\t\t\t\t\t\tcss: 'preprocessor' },\t\t// preprocessor tags like #region and #endregion\n\t\t\t{ regex: new RegExp(this.getKeywords(keywords), 'gm'),\tcss: 'keyword' }\t\t\t// keywords\n\t\t\t];\n\t\n\t\tthis.forHtmlScript(r.scriptScriptTags);\n\t};\n\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\n\tBrush.aliases\t= ['js', 'jscript', 'javascript'];\n\n\tSyntaxHighlighter.brushes.JScript = Brush;\n\n\t// CommonJS\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\n})();\n;(function()\n{\n\t// CommonJS\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\n\n\tfunction Brush()\n\t{\n\t\t// Contributed by David Simmons-Duffin and Marty Kube\n\t\n\t\tvar funcs = \n\t\t\t'abs accept alarm atan2 bind binmode chdir chmod chomp chop chown chr ' + \n\t\t\t'chroot close closedir connect cos crypt defined delete each endgrent ' + \n\t\t\t'endhostent endnetent endprotoent endpwent endservent eof exec exists ' + \n\t\t\t'exp fcntl fileno flock fork format formline getc getgrent getgrgid ' + \n\t\t\t'getgrnam gethostbyaddr gethostbyname gethostent getlogin getnetbyaddr ' + \n\t\t\t'getnetbyname getnetent getpeername getpgrp getppid getpriority ' + \n\t\t\t'getprotobyname getprotobynumber getprotoent getpwent getpwnam getpwuid ' + \n\t\t\t'getservbyname getservbyport getservent getsockname getsockopt glob ' + \n\t\t\t'gmtime grep hex index int ioctl join keys kill lc lcfirst length link ' + \n\t\t\t'listen localtime lock log lstat map mkdir msgctl msgget msgrcv msgsnd ' + \n\t\t\t'oct open opendir ord pack pipe pop pos print printf prototype push ' + \n\t\t\t'quotemeta rand read readdir readline readlink readpipe recv rename ' + \n\t\t\t'reset reverse rewinddir rindex rmdir scalar seek seekdir select semctl ' + \n\t\t\t'semget semop send setgrent sethostent setnetent setpgrp setpriority ' + \n\t\t\t'setprotoent setpwent setservent setsockopt shift shmctl shmget shmread ' + \n\t\t\t'shmwrite shutdown sin sleep socket socketpair sort splice split sprintf ' + \n\t\t\t'sqrt srand stat study substr symlink syscall sysopen sysread sysseek ' + \n\t\t\t'system syswrite tell telldir time times tr truncate uc ucfirst umask ' + \n\t\t\t'undef unlink unpack unshift utime values vec wait waitpid warn write ' +\n\t\t\t// feature\n\t\t\t'say';\n    \n\t\tvar keywords =  \n\t\t\t'bless caller continue dbmclose dbmopen die do dump else elsif eval exit ' +\n\t\t\t'for foreach goto if import last local my next no our package redo ref ' + \n\t\t\t'require return sub tie tied unless untie until use wantarray while ' +\n\t\t\t// feature\n\t\t\t'given when default ' +\n\t\t\t// Try::Tiny\n\t\t\t'try catch finally ' +\n\t\t\t// Moose\n\t\t\t'has extends with before after around override augment';\n    \n\t\tthis.regexList = [\n\t\t\t{ regex: /(<<|&lt;&lt;)((\\w+)|(['\"])(.+?)\\4)[\\s\\S]+?\\n\\3\\5\\n/g,\tcss: 'string' },\t// here doc (maybe html encoded)\n\t\t\t{ regex: /#.*$/gm,\t\t\t\t\t\t\t\t\t\tcss: 'comments' },\n\t\t\t{ regex: /^#!.*\\n/g,\t\t\t\t\t\t\t\t\tcss: 'preprocessor' },\t// shebang\n\t\t\t{ regex: /-?\\w+(?=\\s*=(>|&gt;))/g,\tcss: 'string' }, // fat comma\n\n\t\t\t// is this too much?\n\t\t\t{ regex: /\\bq[qwxr]?\\([\\s\\S]*?\\)/g,\tcss: 'string' }, // quote-like operators ()\n\t\t\t{ regex: /\\bq[qwxr]?\\{[\\s\\S]*?\\}/g,\tcss: 'string' }, // quote-like operators {}\n\t\t\t{ regex: /\\bq[qwxr]?\\[[\\s\\S]*?\\]/g,\tcss: 'string' }, // quote-like operators []\n\t\t\t{ regex: /\\bq[qwxr]?(<|&lt;)[\\s\\S]*?(>|&gt;)/g,\tcss: 'string' }, // quote-like operators <>\n\t\t\t{ regex: /\\bq[qwxr]?([^\\w({<[])[\\s\\S]*?\\1/g,\tcss: 'string' }, // quote-like operators non-paired\n\n\t\t\t{ regex: SyntaxHighlighter.regexLib.doubleQuotedString,\tcss: 'string' },\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleQuotedString,\tcss: 'string' },\n\t\t\t// currently ignoring single quote package separator and utf8 names\n\t\t\t{ regex: /(?:&amp;|[$@%*]|\\$#)[a-zA-Z_](\\w+|::)*/g,   \t\tcss: 'variable' },\n\t\t\t{ regex: /\\b__(?:END|DATA)__\\b[\\s\\S]*$/g,\t\t\t\tcss: 'comments' },\n\t\t\t{ regex: /(^|\\n)=\\w[\\s\\S]*?(\\n=cut\\s*\\n|$)/g,\t\t\t\tcss: 'comments' },\t\t// pod\n\t\t\t{ regex: new RegExp(this.getKeywords(funcs), 'gm'),\t\tcss: 'functions' },\n\t\t\t{ regex: new RegExp(this.getKeywords(keywords), 'gm'),\tcss: 'keyword' }\n\t\t];\n\n\t\tthis.forHtmlScript(SyntaxHighlighter.regexLib.phpScriptTags);\n\t}\n\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\n\tBrush.aliases\t\t= ['perl', 'Perl', 'pl'];\n\n\tSyntaxHighlighter.brushes.Perl = Brush;\n\n\t// CommonJS\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\n})();\n;(function()\n{\n\t// CommonJS\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\n\n\tfunction Brush()\n\t{\n\t\tvar funcs\t=\t'abs acos acosh addcslashes addslashes ' +\n\t\t\t\t\t\t'array_change_key_case array_chunk array_combine array_count_values array_diff '+\n\t\t\t\t\t\t'array_diff_assoc array_diff_key array_diff_uassoc array_diff_ukey array_fill '+\n\t\t\t\t\t\t'array_filter array_flip array_intersect array_intersect_assoc array_intersect_key '+\n\t\t\t\t\t\t'array_intersect_uassoc array_intersect_ukey array_key_exists array_keys array_map '+\n\t\t\t\t\t\t'array_merge array_merge_recursive array_multisort array_pad array_pop array_product '+\n\t\t\t\t\t\t'array_push array_rand array_reduce array_reverse array_search array_shift '+\n\t\t\t\t\t\t'array_slice array_splice array_sum array_udiff array_udiff_assoc '+\n\t\t\t\t\t\t'array_udiff_uassoc array_uintersect array_uintersect_assoc '+\n\t\t\t\t\t\t'array_uintersect_uassoc array_unique array_unshift array_values array_walk '+\n\t\t\t\t\t\t'array_walk_recursive atan atan2 atanh base64_decode base64_encode base_convert '+\n\t\t\t\t\t\t'basename bcadd bccomp bcdiv bcmod bcmul bindec bindtextdomain bzclose bzcompress '+\n\t\t\t\t\t\t'bzdecompress bzerrno bzerror bzerrstr bzflush bzopen bzread bzwrite ceil chdir '+\n\t\t\t\t\t\t'checkdate checkdnsrr chgrp chmod chop chown chr chroot chunk_split class_exists '+\n\t\t\t\t\t\t'closedir closelog copy cos cosh count count_chars date decbin dechex decoct '+\n\t\t\t\t\t\t'deg2rad delete ebcdic2ascii echo empty end ereg ereg_replace eregi eregi_replace error_log '+\n\t\t\t\t\t\t'error_reporting escapeshellarg escapeshellcmd eval exec exit exp explode extension_loaded '+\n\t\t\t\t\t\t'feof fflush fgetc fgetcsv fgets fgetss file_exists file_get_contents file_put_contents '+\n\t\t\t\t\t\t'fileatime filectime filegroup fileinode filemtime fileowner fileperms filesize filetype '+\n\t\t\t\t\t\t'floatval flock floor flush fmod fnmatch fopen fpassthru fprintf fputcsv fputs fread fscanf '+\n\t\t\t\t\t\t'fseek fsockopen fstat ftell ftok getallheaders getcwd getdate getenv gethostbyaddr gethostbyname '+\n\t\t\t\t\t\t'gethostbynamel getimagesize getlastmod getmxrr getmygid getmyinode getmypid getmyuid getopt '+\n\t\t\t\t\t\t'getprotobyname getprotobynumber getrandmax getrusage getservbyname getservbyport gettext '+\n\t\t\t\t\t\t'gettimeofday gettype glob gmdate gmmktime ini_alter ini_get ini_get_all ini_restore ini_set '+\n\t\t\t\t\t\t'interface_exists intval ip2long is_a is_array is_bool is_callable is_dir is_double '+\n\t\t\t\t\t\t'is_executable is_file is_finite is_float is_infinite is_int is_integer is_link is_long '+\n\t\t\t\t\t\t'is_nan is_null is_numeric is_object is_readable is_real is_resource is_scalar is_soap_fault '+\n\t\t\t\t\t\t'is_string is_subclass_of is_uploaded_file is_writable is_writeable mkdir mktime nl2br '+\n\t\t\t\t\t\t'parse_ini_file parse_str parse_url passthru pathinfo print readlink realpath rewind rewinddir rmdir '+\n\t\t\t\t\t\t'round str_ireplace str_pad str_repeat str_replace str_rot13 str_shuffle str_split '+\n\t\t\t\t\t\t'str_word_count strcasecmp strchr strcmp strcoll strcspn strftime strip_tags stripcslashes '+\n\t\t\t\t\t\t'stripos stripslashes stristr strlen strnatcasecmp strnatcmp strncasecmp strncmp strpbrk '+\n\t\t\t\t\t\t'strpos strptime strrchr strrev strripos strrpos strspn strstr strtok strtolower strtotime '+\n\t\t\t\t\t\t'strtoupper strtr strval substr substr_compare';\n\n\t\tvar keywords =\t'abstract and array as break case catch cfunction class clone const continue declare default die do ' +\n\t\t\t\t\t\t'else elseif enddeclare endfor endforeach endif endswitch endwhile extends final for foreach ' +\n\t\t\t\t\t\t'function global goto if implements include include_once interface instanceof insteadof namespace new ' +\n\t\t\t\t\t\t'old_function or private protected public return require require_once static switch ' +\n\t\t\t\t\t\t'trait throw try use var while xor ';\n\t\t\n\t\tvar constants\t= '__FILE__ __LINE__ __METHOD__ __FUNCTION__ __CLASS__';\n\n\t\tthis.regexList = [\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleLineCComments,\tcss: 'comments' },\t\t\t// one line comments\n\t\t\t{ regex: SyntaxHighlighter.regexLib.multiLineCComments,\t\tcss: 'comments' },\t\t\t// multiline comments\n\t\t\t{ regex: SyntaxHighlighter.regexLib.doubleQuotedString,\t\tcss: 'string' },\t\t\t// double quoted strings\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleQuotedString,\t\tcss: 'string' },\t\t\t// single quoted strings\n\t\t\t{ regex: /\\$\\w+/g,\t\t\t\t\t\t\t\t\t\t\tcss: 'variable' },\t\t\t// variables\n\t\t\t{ regex: new RegExp(this.getKeywords(funcs), 'gmi'),\t\tcss: 'functions' },\t\t\t// common functions\n\t\t\t{ regex: new RegExp(this.getKeywords(constants), 'gmi'),\tcss: 'constants' },\t\t\t// constants\n\t\t\t{ regex: new RegExp(this.getKeywords(keywords), 'gm'),\t\tcss: 'keyword' }\t\t\t// keyword\n\t\t\t];\n\n\t\tthis.forHtmlScript(SyntaxHighlighter.regexLib.phpScriptTags);\n\t};\n\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\n\tBrush.aliases\t= ['php'];\n\n\tSyntaxHighlighter.brushes.Php = Brush;\n\n\t// CommonJS\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\n})();\n;(function()\n{\n\t// CommonJS\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\n\n\tfunction Brush()\n\t{\n\t};\n\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\n\tBrush.aliases\t= ['text', 'plain'];\n\n\tSyntaxHighlighter.brushes.Plain = Brush;\n\n\t// CommonJS\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\n})();\n;(function()\n{\n\t// CommonJS\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\n\n\tfunction Brush()\n\t{\n\t\t// Contributed by Joel 'Jaykul' Bennett, http://PoshCode.org | http://HuddledMasses.org\n\t\tvar keywords =\t'while validateset validaterange validatepattern validatelength validatecount ' +\n\t\t\t\t\t\t'until trap switch return ref process param parameter in if global: '+\n\t\t\t\t\t\t'function foreach for finally filter end elseif else dynamicparam do default ' +\n\t\t\t\t\t\t'continue cmdletbinding break begin alias \\\\? % #script #private #local #global '+\n\t\t\t\t\t\t'mandatory parametersetname position valuefrompipeline ' +\n\t\t\t\t\t\t'valuefrompipelinebypropertyname valuefromremainingarguments helpmessage ';\n\n\t\tvar operators =\t' and as band bnot bor bxor casesensitive ccontains ceq cge cgt cle ' +\n\t\t\t\t\t\t'clike clt cmatch cne cnotcontains cnotlike cnotmatch contains ' +\n\t\t\t\t\t\t'creplace eq exact f file ge gt icontains ieq ige igt ile ilike ilt ' +\n\t\t\t\t\t\t'imatch ine inotcontains inotlike inotmatch ireplace is isnot le like ' +\n\t\t\t\t\t\t'lt match ne not notcontains notlike notmatch or regex replace wildcard';\n\t\t\t\t\t\t\n\t\tvar verbs =\t\t'write where wait use update unregister undo trace test tee take suspend ' +\n\t\t\t\t\t\t'stop start split sort skip show set send select scroll resume restore ' +\n\t\t\t\t\t\t'restart resolve resize reset rename remove register receive read push ' +\n\t\t\t\t\t\t'pop ping out new move measure limit join invoke import group get format ' +\n\t\t\t\t\t\t'foreach export expand exit enter enable disconnect disable debug cxnew ' +\n\t\t\t\t\t\t'copy convertto convertfrom convert connect complete compare clear ' +\n\t\t\t\t\t\t'checkpoint aggregate add';\n\n\t\t// I can't find a way to match the comment based help in multi-line comments, because SH won't highlight in highlights, and javascript doesn't support lookbehind\n\t\tvar commenthelp = ' component description example externalhelp forwardhelpcategory forwardhelptargetname forwardhelptargetname functionality inputs link notes outputs parameter remotehelprunspace role synopsis';\n\n\t\tthis.regexList = [\n\t\t\t{ regex: new RegExp('^\\\\s*#[#\\\\s]*\\\\.('+this.getKeywords(commenthelp)+').*$', 'gim'),\t\t\tcss: 'preprocessor help bold' },\t\t// comment-based help\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleLinePerlComments,\t\t\t\t\t\t\t\t\t\tcss: 'comments' },\t\t\t\t\t\t// one line comments\n\t\t\t{ regex: /(&lt;|<)#[\\s\\S]*?#(&gt;|>)/gm,\t\t\t\t\t\t\t\t\t\t\t\t\t\tcss: 'comments here' },\t\t\t\t\t// multi-line comments\n\t\t\t\n\t\t\t{ regex: new RegExp('@\"\\\\n[\\\\s\\\\S]*?\\\\n\"@', 'gm'),\t\t\t\t\t\t\t\t\t\t\t\tcss: 'script string here' },\t\t\t// double quoted here-strings\n\t\t\t{ regex: new RegExp(\"@'\\\\n[\\\\s\\\\S]*?\\\\n'@\", 'gm'),\t\t\t\t\t\t\t\t\t\t\t\tcss: 'script string single here' },\t\t// single quoted here-strings\n\t\t\t{ regex: new RegExp('\"(?:\\\\$\\\\([^\\\\)]*\\\\)|[^\"]|`\"|\"\")*[^`]\"','g'),\t\t\t\t\t\t\t\tcss: 'string' },\t\t\t\t\t\t// double quoted strings\n\t\t\t{ regex: new RegExp(\"'(?:[^']|'')*'\", 'g'),\t\t\t\t\t\t\t\t\t\t\t\t\t\tcss: 'string single' },\t\t\t\t\t// single quoted strings\n\t\t\t\n\t\t\t{ regex: new RegExp('[\\\\$|@|@@](?:(?:global|script|private|env):)?[A-Z0-9_]+', 'gi'),\t\t\tcss: 'variable' },\t\t\t\t\t\t// $variables\n\t\t\t{ regex: new RegExp('(?:\\\\b'+verbs.replace(/ /g, '\\\\b|\\\\b')+')-[a-zA-Z_][a-zA-Z0-9_]*', 'gmi'),\tcss: 'functions' },\t\t\t\t\t\t// functions and cmdlets\n\t\t\t{ regex: new RegExp(this.getKeywords(keywords), 'gmi'),\t\t\t\t\t\t\t\t\t\t\tcss: 'keyword' },\t\t\t\t\t\t// keywords\n\t\t\t{ regex: new RegExp('-'+this.getKeywords(operators), 'gmi'),\t\t\t\t\t\t\t\t\tcss: 'operator value' },\t\t\t\t// operators\n\t\t\t{ regex: new RegExp('\\\\[[A-Z_\\\\[][A-Z0-9_. `,\\\\[\\\\]]*\\\\]', 'gi'),\t\t\t\t\t\t\t\tcss: 'constants' },\t\t\t\t\t\t// .Net [Type]s\n\t\t\t{ regex: new RegExp('\\\\s+-(?!'+this.getKeywords(operators)+')[a-zA-Z_][a-zA-Z0-9_]*', 'gmi'),\tcss: 'color1' },\t\t\t\t\t\t// parameters\t  \n\t\t];\n\t};\n\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\n\tBrush.aliases\t= ['powershell', 'ps', 'posh'];\n\n\tSyntaxHighlighter.brushes.PowerShell = Brush;\n\n\t// CommonJS\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\n})();\n;(function()\n{\n\t// CommonJS\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\n\n\tfunction Brush()\n\t{\n\t\t// Contributed by Gheorghe Milas and Ahmad Sherif\n\t\n\t\tvar keywords =  'and assert break class continue def del elif else ' +\n\t\t\t\t\t\t'except exec finally for from global if import in is ' +\n\t\t\t\t\t\t'lambda not or pass print raise return try yield while';\n\n\t\tvar funcs = '__import__ abs all any apply basestring bin bool buffer callable ' +\n\t\t\t\t\t'chr classmethod cmp coerce compile complex delattr dict dir ' +\n\t\t\t\t\t'divmod enumerate eval execfile file filter float format frozenset ' +\n\t\t\t\t\t'getattr globals hasattr hash help hex id input int intern ' +\n\t\t\t\t\t'isinstance issubclass iter len list locals long map max min next ' +\n\t\t\t\t\t'object oct open ord pow print property range raw_input reduce ' +\n\t\t\t\t\t'reload repr reversed round set setattr slice sorted staticmethod ' +\n\t\t\t\t\t'str sum super tuple type type unichr unicode vars xrange zip';\n\n\t\tvar special =  'None True False self cls class_';\n\n\t\tthis.regexList = [\n\t\t\t\t{ regex: SyntaxHighlighter.regexLib.singleLinePerlComments, css: 'comments' },\n\t\t\t\t{ regex: /^\\s*@\\w+/gm, \t\t\t\t\t\t\t\t\t\tcss: 'decorator' },\n\t\t\t\t{ regex: /(['\\\"]{3})([^\\1])*?\\1/gm, \t\t\t\t\t\tcss: 'comments' },\n\t\t\t\t{ regex: /\"(?!\")(?:\\.|\\\\\\\"|[^\\\"\"\\n])*\"/gm, \t\t\t\t\tcss: 'string' },\n\t\t\t\t{ regex: /'(?!')(?:\\.|(\\\\\\')|[^\\''\\n])*'/gm, \t\t\t\tcss: 'string' },\n\t\t\t\t{ regex: /\\+|\\-|\\*|\\/|\\%|=|==/gm, \t\t\t\t\t\t\tcss: 'keyword' },\n\t\t\t\t{ regex: /\\b\\d+\\.?\\w*/g, \t\t\t\t\t\t\t\t\tcss: 'value' },\n\t\t\t\t{ regex: new RegExp(this.getKeywords(funcs), 'gmi'),\t\tcss: 'functions' },\n\t\t\t\t{ regex: new RegExp(this.getKeywords(keywords), 'gm'), \t\tcss: 'keyword' },\n\t\t\t\t{ regex: new RegExp(this.getKeywords(special), 'gm'), \t\tcss: 'color1' }\n\t\t\t\t];\n\t\t\t\n\t\tthis.forHtmlScript(SyntaxHighlighter.regexLib.aspScriptTags);\n\t};\n\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\n\tBrush.aliases\t= ['py', 'python'];\n\n\tSyntaxHighlighter.brushes.Python = Brush;\n\n\t// CommonJS\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\n})();\n;(function()\n{\n\t// CommonJS\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\n\n\tfunction Brush()\n\t{\n\t\t// Contributed by Erik Peterson.\n\t\n\t\tvar keywords =\t'alias and BEGIN begin break case class def define_method defined do each else elsif ' +\n\t\t\t\t\t\t'END end ensure false for if in module new next nil not or raise redo rescue retry return ' +\n\t\t\t\t\t\t'self super then throw true undef unless until when while yield';\n\n\t\tvar builtins =\t'Array Bignum Binding Class Continuation Dir Exception FalseClass File::Stat File Fixnum Fload ' +\n\t\t\t\t\t\t'Hash Integer IO MatchData Method Module NilClass Numeric Object Proc Range Regexp String Struct::TMS Symbol ' +\n\t\t\t\t\t\t'ThreadGroup Thread Time TrueClass';\n\n\t\tthis.regexList = [\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleLinePerlComments,\tcss: 'comments' },\t\t// one line comments\n\t\t\t{ regex: SyntaxHighlighter.regexLib.doubleQuotedString,\t\tcss: 'string' },\t\t// double quoted strings\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleQuotedString,\t\tcss: 'string' },\t\t// single quoted strings\n\t\t\t{ regex: /\\b[A-Z0-9_]+\\b/g,\t\t\t\t\t\t\t\t\tcss: 'constants' },\t\t// constants\n\t\t\t{ regex: /:[a-z][A-Za-z0-9_]*/g,\t\t\t\t\t\t\tcss: 'color2' },\t\t// symbols\n\t\t\t{ regex: /(\\$|@@|@)\\w+/g,\t\t\t\t\t\t\t\t\tcss: 'variable bold' },\t// $global, @instance, and @@class variables\n\t\t\t{ regex: new RegExp(this.getKeywords(keywords), 'gm'),\t\tcss: 'keyword' },\t\t// keywords\n\t\t\t{ regex: new RegExp(this.getKeywords(builtins), 'gm'),\t\tcss: 'color1' }\t\t\t// builtins\n\t\t\t];\n\n\t\tthis.forHtmlScript(SyntaxHighlighter.regexLib.aspScriptTags);\n\t};\n\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\n\tBrush.aliases\t= ['ruby', 'rails', 'ror', 'rb'];\n\n\tSyntaxHighlighter.brushes.Ruby = Brush;\n\n\t// CommonJS\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\n})();\n;(function()\n{\n\t// CommonJS\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\n\n\tfunction Brush()\n\t{\n\t\tfunction getKeywordsCSS(str)\n\t\t{\n\t\t\treturn '\\\\b([a-z_]|)' + str.replace(/ /g, '(?=:)\\\\b|\\\\b([a-z_\\\\*]|\\\\*|)') + '(?=:)\\\\b';\n\t\t};\n\t\n\t\tfunction getValuesCSS(str)\n\t\t{\n\t\t\treturn '\\\\b' + str.replace(/ /g, '(?!-)(?!:)\\\\b|\\\\b()') + '\\:\\\\b';\n\t\t};\n\n\t\tvar keywords =\t'ascent azimuth background-attachment background-color background-image background-position ' +\n\t\t\t\t\t\t'background-repeat background baseline bbox border-collapse border-color border-spacing border-style border-top ' +\n\t\t\t\t\t\t'border-right border-bottom border-left border-top-color border-right-color border-bottom-color border-left-color ' +\n\t\t\t\t\t\t'border-top-style border-right-style border-bottom-style border-left-style border-top-width border-right-width ' +\n\t\t\t\t\t\t'border-bottom-width border-left-width border-width border bottom cap-height caption-side centerline clear clip color ' +\n\t\t\t\t\t\t'content counter-increment counter-reset cue-after cue-before cue cursor definition-src descent direction display ' +\n\t\t\t\t\t\t'elevation empty-cells float font-size-adjust font-family font-size font-stretch font-style font-variant font-weight font ' +\n\t\t\t\t\t\t'height left letter-spacing line-height list-style-image list-style-position list-style-type list-style margin-top ' +\n\t\t\t\t\t\t'margin-right margin-bottom margin-left margin marker-offset marks mathline max-height max-width min-height min-width orphans ' +\n\t\t\t\t\t\t'outline-color outline-style outline-width outline overflow padding-top padding-right padding-bottom padding-left padding page ' +\n\t\t\t\t\t\t'page-break-after page-break-before page-break-inside pause pause-after pause-before pitch pitch-range play-during position ' +\n\t\t\t\t\t\t'quotes right richness size slope src speak-header speak-numeral speak-punctuation speak speech-rate stemh stemv stress ' +\n\t\t\t\t\t\t'table-layout text-align top text-decoration text-indent text-shadow text-transform unicode-bidi unicode-range units-per-em ' +\n\t\t\t\t\t\t'vertical-align visibility voice-family volume white-space widows width widths word-spacing x-height z-index';\n\t\t\n\t\tvar values =\t'above absolute all always aqua armenian attr aural auto avoid baseline behind below bidi-override black blink block blue bold bolder '+\n\t\t\t\t\t\t'both bottom braille capitalize caption center center-left center-right circle close-quote code collapse compact condensed '+\n\t\t\t\t\t\t'continuous counter counters crop cross crosshair cursive dashed decimal decimal-leading-zero digits disc dotted double '+\n\t\t\t\t\t\t'embed embossed e-resize expanded extra-condensed extra-expanded fantasy far-left far-right fast faster fixed format fuchsia '+\n\t\t\t\t\t\t'gray green groove handheld hebrew help hidden hide high higher icon inline-table inline inset inside invert italic '+\n\t\t\t\t\t\t'justify landscape large larger left-side left leftwards level lighter lime line-through list-item local loud lower-alpha '+\n\t\t\t\t\t\t'lowercase lower-greek lower-latin lower-roman lower low ltr marker maroon medium message-box middle mix move narrower '+\n\t\t\t\t\t\t'navy ne-resize no-close-quote none no-open-quote no-repeat normal nowrap n-resize nw-resize oblique olive once open-quote outset '+\n\t\t\t\t\t\t'outside overline pointer portrait pre print projection purple red relative repeat repeat-x repeat-y rgb ridge right right-side '+\n\t\t\t\t\t\t'rightwards rtl run-in screen scroll semi-condensed semi-expanded separate se-resize show silent silver slower slow '+\n\t\t\t\t\t\t'small small-caps small-caption smaller soft solid speech spell-out square s-resize static status-bar sub super sw-resize '+\n\t\t\t\t\t\t'table-caption table-cell table-column table-column-group table-footer-group table-header-group table-row table-row-group teal '+\n\t\t\t\t\t\t'text-bottom text-top thick thin top transparent tty tv ultra-condensed ultra-expanded underline upper-alpha uppercase upper-latin '+\n\t\t\t\t\t\t'upper-roman url visible wait white wider w-resize x-fast x-high x-large x-loud x-low x-slow x-small x-soft xx-large xx-small yellow';\n\t\t\n\t\tvar fonts =\t\t'[mM]onospace [tT]ahoma [vV]erdana [aA]rial [hH]elvetica [sS]ans-serif [sS]erif [cC]ourier mono sans serif';\n\t\t\n\t\tvar statements\t\t= '!important !default';\n\t\tvar preprocessor\t= '@import @extend @debug @warn @if @for @while @mixin @include';\n\t\t\n\t\tvar r = SyntaxHighlighter.regexLib;\n\t\t\n\t\tthis.regexList = [\n\t\t\t{ regex: r.multiLineCComments,\t\t\t\t\t\t\t\tcss: 'comments' },\t\t// multiline comments\n\t\t\t{ regex: r.singleLineCComments,\t\t\t\t\t\t\t\tcss: 'comments' },\t\t// singleline comments\n\t\t\t{ regex: r.doubleQuotedString,\t\t\t\t\t\t\t\tcss: 'string' },\t\t// double quoted strings\n\t\t\t{ regex: r.singleQuotedString,\t\t\t\t\t\t\t\tcss: 'string' },\t\t// single quoted strings\n\t\t\t{ regex: /\\#[a-fA-F0-9]{3,6}/g,\t\t\t\t\t\t\t\tcss: 'value' },\t\t\t// html colors\n\t\t\t{ regex: /\\b(-?\\d+)(\\.\\d+)?(px|em|pt|\\:|\\%|)\\b/g,\t\t\tcss: 'value' },\t\t\t// sizes\n\t\t\t{ regex: /\\$\\w+/g,\t\t\t\t\t\t\t\t\t\t\tcss: 'variable' },\t\t// variables\n\t\t\t{ regex: new RegExp(this.getKeywords(statements), 'g'),\t\tcss: 'color3' },\t\t// statements\n\t\t\t{ regex: new RegExp(this.getKeywords(preprocessor), 'g'),\tcss: 'preprocessor' },\t// preprocessor\n\t\t\t{ regex: new RegExp(getKeywordsCSS(keywords), 'gm'),\t\tcss: 'keyword' },\t\t// keywords\n\t\t\t{ regex: new RegExp(getValuesCSS(values), 'g'),\t\t\t\tcss: 'value' },\t\t\t// values\n\t\t\t{ regex: new RegExp(this.getKeywords(fonts), 'g'),\t\t\tcss: 'color1' }\t\t\t// fonts\n\t\t\t];\n\t};\n\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\n\tBrush.aliases\t= ['sass', 'scss'];\n\n\tSyntaxHighlighter.brushes.Sass = Brush;\n\n\t// CommonJS\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\n})();\n;(function()\n{\n\t// CommonJS\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\n\n\tfunction Brush()\n\t{\n\t\t// Contributed by Yegor Jbanov and David Bernard.\n\t\n\t\tvar keywords =\t'val sealed case def true trait implicit forSome import match object null finally super ' +\n\t\t\t\t\t\t'override try lazy for var catch throw type extends class while with new final yield abstract ' +\n\t\t\t\t\t\t'else do if return protected private this package false';\n\n\t\tvar keyops =\t'[_:=><%#@]+';\n\n\t\tthis.regexList = [\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleLineCComments,\t\t\tcss: 'comments' },\t// one line comments\n\t\t\t{ regex: SyntaxHighlighter.regexLib.multiLineCComments,\t\t\t\tcss: 'comments' },\t// multiline comments\n\t\t\t{ regex: SyntaxHighlighter.regexLib.multiLineSingleQuotedString,\tcss: 'string' },\t// multi-line strings\n\t\t\t{ regex: SyntaxHighlighter.regexLib.multiLineDoubleQuotedString,    css: 'string' },\t// double-quoted string\n\t\t\t{ regex: SyntaxHighlighter.regexLib.singleQuotedString,\t\t\t\tcss: 'string' },\t// strings\n\t\t\t{ regex: /0x[a-f0-9]+|\\d+(\\.\\d+)?/gi,\t\t\t\t\t\t\t\tcss: 'value' },\t\t// numbers\n\t\t\t{ regex: new RegExp(this.getKeywords(keywords), 'gm'),\t\t\t\tcss: 'keyword' },\t// keywords\n\t\t\t{ regex: new RegExp(keyops, 'gm'),\t\t\t\t\t\t\t\t\tcss: 'keyword' }\t// scala keyword\n\t\t\t];\n\t}\n\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\n\tBrush.aliases\t= ['scala'];\n\n\tSyntaxHighlighter.brushes.Scala = Brush;\n\n\t// CommonJS\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\n})();\n;(function()\n{\n\t// CommonJS\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\n\n\tfunction Brush()\n\t{\n\t\tvar funcs\t=\t'abs avg case cast coalesce convert count current_timestamp ' +\n\t\t\t\t\t\t'current_user day isnull left lower month nullif replace right ' +\n\t\t\t\t\t\t'session_user space substring sum system_user upper user year';\n\n\t\tvar keywords =\t'absolute action add after alter as asc at authorization begin bigint ' +\n\t\t\t\t\t\t'binary bit by cascade char character check checkpoint close collate ' +\n\t\t\t\t\t\t'column commit committed connect connection constraint contains continue ' +\n\t\t\t\t\t\t'create cube current current_date current_time cursor database date ' +\n\t\t\t\t\t\t'deallocate dec decimal declare default delete desc distinct double drop ' +\n\t\t\t\t\t\t'dynamic else end end-exec escape except exec execute false fetch first ' +\n\t\t\t\t\t\t'float for force foreign forward free from full function global goto grant ' +\n\t\t\t\t\t\t'group grouping having hour ignore index inner insensitive insert instead ' +\n\t\t\t\t\t\t'int integer intersect into is isolation key last level load local max min ' +\n\t\t\t\t\t\t'minute modify move name national nchar next no numeric of off on only ' +\n\t\t\t\t\t\t'open option order out output partial password precision prepare primary ' +\n\t\t\t\t\t\t'prior privileges procedure public read real references relative repeatable ' +\n\t\t\t\t\t\t'restrict return returns revoke rollback rollup rows rule schema scroll ' +\n\t\t\t\t\t\t'second section select sequence serializable set size smallint static ' +\n\t\t\t\t\t\t'statistics table temp temporary then time timestamp to top transaction ' +\n\t\t\t\t\t\t'translation trigger true truncate uncommitted union unique update values ' +\n\t\t\t\t\t\t'varchar varying view when where with work';\n\n\t\tvar operators =\t'all and any between cross in join like not null or outer some';\n\n\t\tthis.regexList = [\n\t\t\t{ regex: /--(.*)$/gm,\t\t\t\t\t\t\t\t\t\t\t\tcss: 'comments' },\t\t\t// one line and multiline comments\n\t\t\t{ regex: SyntaxHighlighter.regexLib.multiLineDoubleQuotedString,\tcss: 'string' },\t\t\t// double quoted strings\n\t\t\t{ regex: SyntaxHighlighter.regexLib.multiLineSingleQuotedString,\tcss: 'string' },\t\t\t// single quoted strings\n\t\t\t{ regex: new RegExp(this.getKeywords(funcs), 'gmi'),\t\t\t\tcss: 'color2' },\t\t\t// functions\n\t\t\t{ regex: new RegExp(this.getKeywords(operators), 'gmi'),\t\t\tcss: 'color1' },\t\t\t// operators and such\n\t\t\t{ regex: new RegExp(this.getKeywords(keywords), 'gmi'),\t\t\t\tcss: 'keyword' }\t\t\t// keyword\n\t\t\t];\n\t};\n\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\n\tBrush.aliases\t= ['sql'];\n\n\tSyntaxHighlighter.brushes.Sql = Brush;\n\n\t// CommonJS\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\n})();\n\n;(function()\n{\n\t// CommonJS\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\n\n\tfunction Brush()\n\t{\n\t\tvar keywords =\t'AddHandler AddressOf AndAlso Alias And Ansi As Assembly Auto ' +\n\t\t\t\t\t\t'Boolean ByRef Byte ByVal Call Case Catch CBool CByte CChar CDate ' +\n\t\t\t\t\t\t'CDec CDbl Char CInt Class CLng CObj Const CShort CSng CStr CType ' +\n\t\t\t\t\t\t'Date Decimal Declare Default Delegate Dim DirectCast Do Double Each ' +\n\t\t\t\t\t\t'Else ElseIf End Enum Erase Error Event Exit False Finally For Friend ' +\n\t\t\t\t\t\t'Function Get GetType GoSub GoTo Handles If Implements Imports In ' +\n\t\t\t\t\t\t'Inherits Integer Interface Is Let Lib Like Long Loop Me Mod Module ' +\n\t\t\t\t\t\t'MustInherit MustOverride MyBase MyClass Namespace New Next Not Nothing ' +\n\t\t\t\t\t\t'NotInheritable NotOverridable Object On Option Optional Or OrElse ' +\n\t\t\t\t\t\t'Overloads Overridable Overrides ParamArray Preserve Private Property ' +\n\t\t\t\t\t\t'Protected Public RaiseEvent ReadOnly ReDim REM RemoveHandler Resume ' +\n\t\t\t\t\t\t'Return Select Set Shadows Shared Short Single Static Step Stop String ' +\n\t\t\t\t\t\t'Structure Sub SyncLock Then Throw To True Try TypeOf Unicode Until ' +\n\t\t\t\t\t\t'Variant When While With WithEvents WriteOnly Xor';\n\n\t\tthis.regexList = [\n\t\t\t{ regex: /'.*$/gm,\t\t\t\t\t\t\t\t\t\tcss: 'comments' },\t\t\t// one line comments\n\t\t\t{ regex: SyntaxHighlighter.regexLib.doubleQuotedString,\tcss: 'string' },\t\t\t// strings\n\t\t\t{ regex: /^\\s*#.*$/gm,\t\t\t\t\t\t\t\t\tcss: 'preprocessor' },\t\t// preprocessor tags like #region and #endregion\n\t\t\t{ regex: new RegExp(this.getKeywords(keywords), 'gm'),\tcss: 'keyword' }\t\t\t// vb keyword\n\t\t\t];\n\n\t\tthis.forHtmlScript(SyntaxHighlighter.regexLib.aspScriptTags);\n\t};\n\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\n\tBrush.aliases\t= ['vb', 'vbnet'];\n\n\tSyntaxHighlighter.brushes.Vb = Brush;\n\n\t// CommonJS\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\n})();\n;(function()\n{\n\t// CommonJS\n\tSyntaxHighlighter = SyntaxHighlighter || (typeof require !== 'undefined'? require('shCore').SyntaxHighlighter : null);\n\n\tfunction Brush()\n\t{\n\t\tfunction process(match, regexInfo)\n\t\t{\n\t\t\tvar constructor = SyntaxHighlighter.Match,\n\t\t\t\tcode = match[0],\n\t\t\t\ttag = new XRegExp('(&lt;|<)[\\\\s\\\\/\\\\?]*(?<name>[:\\\\w-\\\\.]+)', 'xg').exec(code),\n\t\t\t\tresult = []\n\t\t\t\t;\n\t\t\n\t\t\tif (match.attributes != null) \n\t\t\t{\n\t\t\t\tvar attributes,\n\t\t\t\t\tregex = new XRegExp('(?<name> [\\\\w:\\\\-\\\\.]+)' +\n\t\t\t\t\t\t\t\t\t\t'\\\\s*=\\\\s*' +\n\t\t\t\t\t\t\t\t\t\t'(?<value> \".*?\"|\\'.*?\\'|\\\\w+)',\n\t\t\t\t\t\t\t\t\t\t'xg');\n\n\t\t\t\twhile ((attributes = regex.exec(code)) != null) \n\t\t\t\t{\n\t\t\t\t\tresult.push(new constructor(attributes.name, match.index + attributes.index, 'color1'));\n\t\t\t\t\tresult.push(new constructor(attributes.value, match.index + attributes.index + attributes[0].indexOf(attributes.value), 'string'));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (tag != null)\n\t\t\t\tresult.push(\n\t\t\t\t\tnew constructor(tag.name, match.index + tag[0].indexOf(tag.name), 'keyword')\n\t\t\t\t);\n\n\t\t\treturn result;\n\t\t}\n\t\n\t\tthis.regexList = [\n\t\t\t{ regex: new XRegExp('(\\\\&lt;|<)\\\\!\\\\[[\\\\w\\\\s]*?\\\\[(.|\\\\s)*?\\\\]\\\\](\\\\&gt;|>)', 'gm'),\t\t\tcss: 'color2' },\t// <![ ... [ ... ]]>\n\t\t\t{ regex: SyntaxHighlighter.regexLib.xmlComments,\t\t\t\t\t\t\t\t\t\t\t\tcss: 'comments' },\t// <!-- ... -->\n\t\t\t{ regex: new XRegExp('(&lt;|<)[\\\\s\\\\/\\\\?]*(\\\\w+)(?<attributes>.*?)[\\\\s\\\\/\\\\?]*(&gt;|>)', 'sg'), func: process }\n\t\t];\n\t};\n\n\tBrush.prototype\t= new SyntaxHighlighter.Highlighter();\n\tBrush.aliases\t= ['xml', 'xhtml', 'xslt', 'html'];\n\n\tSyntaxHighlighter.brushes.Xml = Brush;\n\n\t// CommonJS\n\ttypeof(exports) != 'undefined' ? exports.Brush = Brush : null;\n})();\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/SyntaxHighlighter/shCoreDefault.css",
    "content": ".syntaxhighlighter a,.syntaxhighlighter div,.syntaxhighlighter code,.syntaxhighlighter,.syntaxhighlighter td,.syntaxhighlighter tr,.syntaxhighlighter tbody,.syntaxhighlighter thead,.syntaxhighlighter caption,.syntaxhighlighter textarea{-moz-border-radius:0 0 0 0!important;-webkit-border-radius:0 0 0 0!important;background:none!important;border:0!important;bottom:auto!important;float:none!important;left:auto!important;line-height:1.1em!important;margin:0!important;outline:0!important;overflow:visible!important;padding:0!important;position:static!important;right:auto!important;text-align:left!important;top:auto!important;vertical-align:baseline!important;width:auto!important;box-sizing:content-box!important;font-family:Monaco,Menlo,Consolas,\"Courier New\",monospace;font-weight:normal!important;font-style:normal!important;min-height:inherit!important;min-height:auto!important;font-size:13px!important}.syntaxhighlighter{width:100%!important;margin:.3em 0 .3em 0!important;position:relative!important;overflow:auto!important;background-color:#f5f5f5!important;border:1px solid #ccc!important;border-radius:4px!important;border-collapse:separate!important}.syntaxhighlighter.source{overflow:hidden!important}.syntaxhighlighter .bold{font-weight:bold!important}.syntaxhighlighter .italic{font-style:italic!important}.syntaxhighlighter .gutter div{white-space:pre!important;word-wrap:normal}.syntaxhighlighter caption{text-align:left!important;padding:.5em 0 .5em 1em!important}.syntaxhighlighter td.code{width:100%!important}.syntaxhighlighter td.code .container{position:relative!important}.syntaxhighlighter td.code .container textarea{box-sizing:border-box!important;position:absolute!important;left:0!important;top:0!important;width:100%!important;border:none!important;background:white!important;padding-left:1em!important;overflow:hidden!important;white-space:pre!important}.syntaxhighlighter td.gutter .line{text-align:right!important;padding:0 .5em 0 1em!important}.syntaxhighlighter td.code .line{padding:0 1em!important}.syntaxhighlighter.nogutter td.code .container textarea,.syntaxhighlighter.nogutter td.code .line{padding-left:0!important}.syntaxhighlighter.show{display:block!important}.syntaxhighlighter.collapsed table{display:none!important}.syntaxhighlighter.collapsed .toolbar{padding:.1em .8em 0 .8em!important;font-size:1em!important;position:static!important;width:auto!important}.syntaxhighlighter.collapsed .toolbar span{display:inline!important;margin-right:1em!important}.syntaxhighlighter.collapsed .toolbar span a{padding:0!important;display:none!important}.syntaxhighlighter.collapsed .toolbar span a.expandSource{display:inline!important}.syntaxhighlighter .toolbar{position:absolute!important;right:1px!important;top:1px!important;width:11px!important;height:11px!important;font-size:10px!important;z-index:10!important}.syntaxhighlighter .toolbar span.title{display:inline!important}.syntaxhighlighter .toolbar a{display:block!important;text-align:center!important;text-decoration:none!important;padding-top:1px!important}.syntaxhighlighter .toolbar a.expandSource{display:none!important}.syntaxhighlighter.ie{font-size:.9em!important;padding:1px 0 1px 0!important}.syntaxhighlighter.ie .toolbar{line-height:8px!important}.syntaxhighlighter.ie .toolbar a{padding-top:0!important}.syntaxhighlighter.printing .line.alt1 .content,.syntaxhighlighter.printing .line.alt2 .content,.syntaxhighlighter.printing .line.highlighted .number,.syntaxhighlighter.printing .line.highlighted.alt1 .content,.syntaxhighlighter.printing .line.highlighted.alt2 .content{background:none!important}.syntaxhighlighter.printing .line .number{color:#bbb!important}.syntaxhighlighter.printing .line .content{color:black!important}.syntaxhighlighter.printing .toolbar{display:none!important}.syntaxhighlighter.printing a{text-decoration:none!important}.syntaxhighlighter.printing .plain,.syntaxhighlighter.printing .plain a{color:black!important}.syntaxhighlighter.printing .comments,.syntaxhighlighter.printing .comments a{color:#008200!important}.syntaxhighlighter.printing .string,.syntaxhighlighter.printing .string a{color:blue!important}.syntaxhighlighter.printing .keyword{color:#ff7800!important;font-weight:bold!important}.syntaxhighlighter.printing .preprocessor{color:gray!important}.syntaxhighlighter.printing .variable{color:#a70!important}.syntaxhighlighter.printing .value{color:#090!important}.syntaxhighlighter.printing .functions{color:#ff1493!important}.syntaxhighlighter.printing .constants{color:#06c!important}.syntaxhighlighter.printing .script{font-weight:bold!important}.syntaxhighlighter.printing .color1,.syntaxhighlighter.printing .color1 a{color:gray!important}.syntaxhighlighter.printing .color2,.syntaxhighlighter.printing .color2 a{color:#ff1493!important}.syntaxhighlighter.printing .color3,.syntaxhighlighter.printing .color3 a{color:red!important}.syntaxhighlighter.printing .break,.syntaxhighlighter.printing .break a{color:black!important}.syntaxhighlighter{background-color:#f5f5f5!important}.syntaxhighlighter .line.highlighted.number{color:black!important}.syntaxhighlighter caption{color:black!important}.syntaxhighlighter .gutter{color:#afafaf!important;background-color:#f7f7f9!important;border-right:1px solid #e1e1e8!important;padding:9.5px 0 9.5px 9.5px!important;border-top-left-radius:4px!important;border-bottom-left-radius:4px!important;user-select:none!important;-moz-user-select:none!important;-webkit-user-select:none!important}.syntaxhighlighter .gutter .line.highlighted{background-color:#6ce26c!important;color:white!important}.syntaxhighlighter.printing .line .content{border:none!important}.syntaxhighlighter.collapsed{overflow:visible!important}.syntaxhighlighter.collapsed .toolbar{color:blue!important;background:white!important;border:1px solid #6ce26c!important}.syntaxhighlighter.collapsed .toolbar a{color:blue!important}.syntaxhighlighter.collapsed .toolbar a:hover{color:red!important}.syntaxhighlighter .toolbar{color:white!important;background:#6ce26c!important;border:none!important}.syntaxhighlighter .toolbar a{color:white!important}.syntaxhighlighter .toolbar a:hover{color:black!important}.syntaxhighlighter .plain,.syntaxhighlighter .plain a{color:black!important}.syntaxhighlighter .comments,.syntaxhighlighter .comments a{color:#008200!important}.syntaxhighlighter .string,.syntaxhighlighter .string a{color:blue!important}.syntaxhighlighter .keyword{color:#ff7800!important}.syntaxhighlighter .preprocessor{color:gray!important}.syntaxhighlighter .variable{color:#a70!important}.syntaxhighlighter .value{color:#090!important}.syntaxhighlighter .functions{color:#ff1493!important}.syntaxhighlighter .constants{color:#06c!important}.syntaxhighlighter .script{font-weight:bold!important;color:#ff7800!important;background-color:none!important}.syntaxhighlighter .color1,.syntaxhighlighter .color1 a{color:gray!important}.syntaxhighlighter .color2,.syntaxhighlighter .color2 a{color:#ff1493!important}.syntaxhighlighter .color3,.syntaxhighlighter .color3 a{color:red!important}.syntaxhighlighter .keyword{font-weight:bold!important}"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/codemirror/codemirror.css",
    "content": ".CodeMirror {\n    line-height: 1em;\n    font-family: monospace;\n}\n\n.CodeMirror-scroll {\n    overflow: auto;\n    height: 300px;\n    /* This is needed to prevent an IE[67] bug where the scrolled content\n       is visible outside of the scrolling box. */\n    position: relative;\n}\n\n.CodeMirror-gutter {\n    position: absolute; left: 0; top: 0;\n    z-index: 10;\n    background-color: #f7f7f7;\n    border-right: 1px solid #eee;\n    min-width: 2em;\n    height: 100%;\n}\n.CodeMirror-gutter-text {\n    color: #aaa;\n    text-align: right;\n    padding: .4em .2em .4em .4em;\n    white-space: pre !important;\n}\n.CodeMirror-lines {\n    padding: .4em;\n}\n\n.CodeMirror pre {\n    -moz-border-radius: 0;\n    -webkit-border-radius: 0;\n    -o-border-radius: 0;\n    border-radius: 0;\n    border-width: 0; margin: 0; padding: 0; background: transparent;\n    font-family: inherit;\n    font-size: inherit;\n    padding: 0; margin: 0;\n    white-space: pre;\n    word-wrap: normal;\n}\n\n.CodeMirror-wrap pre {\n    word-wrap: break-word;\n    white-space: pre-wrap;\n}\n.CodeMirror-wrap .CodeMirror-scroll {\n    overflow-x: hidden;\n}\n\n.CodeMirror textarea {\n    outline: none !important;\n}\n\n.CodeMirror pre.CodeMirror-cursor {\n    z-index: 10;\n    position: absolute;\n    visibility: hidden;\n    border-left: 1px solid black;\n}\n.CodeMirror-focused pre.CodeMirror-cursor {\n    visibility: visible;\n}\n\nspan.CodeMirror-selected { background: #d9d9d9; }\n.CodeMirror-focused span.CodeMirror-selected { background: #d2dcf8; }\n\n.CodeMirror-searching {background: #ffa;}\n\n/* Default theme */\n\n.cm-s-default span.cm-keyword {color: #708;}\n.cm-s-default span.cm-atom {color: #219;}\n.cm-s-default span.cm-number {color: #164;}\n.cm-s-default span.cm-def {color: #00f;}\n.cm-s-default span.cm-variable {color: black;}\n.cm-s-default span.cm-variable-2 {color: #05a;}\n.cm-s-default span.cm-variable-3 {color: #085;}\n.cm-s-default span.cm-property {color: black;}\n.cm-s-default span.cm-operator {color: black;}\n.cm-s-default span.cm-comment {color: #a50;}\n.cm-s-default span.cm-string {color: #a11;}\n.cm-s-default span.cm-string-2 {color: #f50;}\n.cm-s-default span.cm-meta {color: #555;}\n.cm-s-default span.cm-error {color: #f00;}\n.cm-s-default span.cm-qualifier {color: #555;}\n.cm-s-default span.cm-builtin {color: #30a;}\n.cm-s-default span.cm-bracket {color: #cc7;}\n.cm-s-default span.cm-tag {color: #170;}\n.cm-s-default span.cm-attribute {color: #00c;}\n.cm-s-default span.cm-header {color: #a0a;}\n.cm-s-default span.cm-quote {color: #090;}\n.cm-s-default span.cm-hr {color: #999;}\n.cm-s-default span.cm-link {color: #00c;}\n\nspan.cm-header, span.cm-strong {font-weight: bold;}\nspan.cm-em {font-style: italic;}\nspan.cm-emstrong {font-style: italic; font-weight: bold;}\nspan.cm-link {text-decoration: underline;}\n\ndiv.CodeMirror span.CodeMirror-matchingbracket {color: #0f0;}\ndiv.CodeMirror span.CodeMirror-nonmatchingbracket {color: #f22;}\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/codemirror/codemirror.js",
    "content": "// CodeMirror version 2.2\r\n//\r\n// All functions that need access to the editor's state live inside\r\n// the CodeMirror function. Below that, at the bottom of the file,\r\n// some utilities are defined.\r\n\r\n// CodeMirror is the only global var we claim\r\nvar CodeMirror = (function() {\r\n    // This is the function that produces an editor instance. It's\r\n    // closure is used to store the editor state.\r\n    function CodeMirror(place, givenOptions) {\r\n        // Determine effective options based on given values and defaults.\r\n        var options = {}, defaults = CodeMirror.defaults;\r\n        for (var opt in defaults)\r\n            if (defaults.hasOwnProperty(opt))\r\n                options[opt] = (givenOptions && givenOptions.hasOwnProperty(opt) ? givenOptions : defaults)[opt];\r\n\r\n        var targetDocument = options[\"document\"];\r\n        // The element in which the editor lives.\r\n        var wrapper = targetDocument.createElement(\"div\");\r\n        wrapper.className = \"CodeMirror\" + (options.lineWrapping ? \" CodeMirror-wrap\" : \"\");\r\n        // This mess creates the base DOM structure for the editor.\r\n        wrapper.innerHTML =\r\n            '<div style=\"overflow: hidden; position: relative; width: 3px; height: 0px;\">' + // Wraps and hides input textarea\r\n                '<textarea style=\"position: absolute; padding: 0; width: 1px;\" wrap=\"off\" ' +\r\n                'autocorrect=\"off\" autocapitalize=\"off\"></textarea></div>' +\r\n                '<div class=\"CodeMirror-scroll\" tabindex=\"-1\">' +\r\n                '<div style=\"position: relative\">' + // Set to the height of the text, causes scrolling\r\n                '<div style=\"position: relative\">' + // Moved around its parent to cover visible view\r\n                '<div class=\"CodeMirror-gutter\"><div class=\"CodeMirror-gutter-text\"></div></div>' +\r\n                // Provides positioning relative to (visible) text origin\r\n                '<div class=\"CodeMirror-lines\"><div style=\"position: relative\">' +\r\n                '<div style=\"position: absolute; width: 100%; height: 0; overflow: hidden; visibility: hidden\"></div>' +\r\n                '<pre class=\"CodeMirror-cursor\">&#160;</pre>' + // Absolutely positioned blinky cursor\r\n                '<div></div>' + // This DIV contains the actual code\r\n                '</div></div></div></div></div>';\r\n        if (place.appendChild) place.appendChild(wrapper); else place(wrapper);\r\n        // I've never seen more elegant code in my life.\r\n        var inputDiv = wrapper.firstChild, input = inputDiv.firstChild,\r\n            scroller = wrapper.lastChild, code = scroller.firstChild,\r\n            mover = code.firstChild, gutter = mover.firstChild, gutterText = gutter.firstChild,\r\n            lineSpace = gutter.nextSibling.firstChild, measure = lineSpace.firstChild,\r\n            cursor = measure.nextSibling, lineDiv = cursor.nextSibling;\r\n        themeChanged();\r\n        // Needed to hide big blue blinking cursor on Mobile Safari\r\n        if (/AppleWebKit/.test(navigator.userAgent) && /Mobile\\/\\w+/.test(navigator.userAgent)) input.style.width = \"0px\";\r\n        if (!webkit) lineSpace.draggable = true;\r\n        if (options.tabindex != null) input.tabIndex = options.tabindex;\r\n        if (!options.gutter && !options.lineNumbers) gutter.style.display = \"none\";\r\n\r\n        // Check for problem with IE innerHTML not working when we have a\r\n        // P (or similar) parent node.\r\n        try { stringWidth(\"x\"); }\r\n        catch (e) {\r\n            if (e.message.match(/runtime/i))\r\n                e = new Error(\"A CodeMirror inside a P-style element does not work in Internet Explorer. (innerHTML bug)\");\r\n            throw e;\r\n        }\r\n\r\n        // Delayed object wrap timeouts, making sure only one is active. blinker holds an interval.\r\n        var poll = new Delayed(), highlight = new Delayed(), blinker;\r\n\r\n        // mode holds a mode API object. doc is the tree of Line objects,\r\n        // work an array of lines that should be parsed, and history the\r\n        // undo history (instance of History constructor).\r\n        var mode, doc = new BranchChunk([new LeafChunk([new Line(\"\")])]), work, focused;\r\n        loadMode();\r\n        // The selection. These are always maintained to point at valid\r\n        // positions. Inverted is used to remember that the user is\r\n        // selecting bottom-to-top.\r\n        var sel = {from: {line: 0, ch: 0}, to: {line: 0, ch: 0}, inverted: false};\r\n        // Selection-related flags. shiftSelecting obviously tracks\r\n        // whether the user is holding shift.\r\n        var shiftSelecting, lastClick, lastDoubleClick, draggingText, overwrite = false;\r\n        // Variables used by startOperation/endOperation to track what\r\n        // happened during the operation.\r\n        var updateInput, userSelChange, changes, textChanged, selectionChanged, leaveInputAlone,\r\n            gutterDirty, callbacks;\r\n        // Current visible range (may be bigger than the view window).\r\n        var displayOffset = 0, showingFrom = 0, showingTo = 0, lastSizeC = 0;\r\n        // bracketHighlighted is used to remember that a backet has been\r\n        // marked.\r\n        var bracketHighlighted;\r\n        // Tracks the maximum line length so that the horizontal scrollbar\r\n        // can be kept static when scrolling.\r\n        var maxLine = \"\", maxWidth, tabText = computeTabText();\r\n\r\n        // Initialize the content.\r\n        operation(function(){setValue(options.value || \"\"); updateInput = false;})();\r\n        var history = new History();\r\n\r\n        // Register our event handlers.\r\n        connect(scroller, \"mousedown\", operation(onMouseDown));\r\n        connect(scroller, \"dblclick\", operation(onDoubleClick));\r\n        connect(lineSpace, \"dragstart\", onDragStart);\r\n        connect(lineSpace, \"selectstart\", e_preventDefault);\r\n        // Gecko browsers fire contextmenu *after* opening the menu, at\r\n        // which point we can't mess with it anymore. Context menu is\r\n        // handled in onMouseDown for Gecko.\r\n        if (!gecko) connect(scroller, \"contextmenu\", onContextMenu);\r\n        connect(scroller, \"scroll\", function() {\r\n            updateDisplay([]);\r\n            if (options.fixedGutter) gutter.style.left = scroller.scrollLeft + \"px\";\r\n            if (options.onScroll) options.onScroll(instance);\r\n        });\r\n        connect(window, \"resize\", function() {updateDisplay(true);});\r\n        connect(input, \"keyup\", operation(onKeyUp));\r\n        connect(input, \"input\", fastPoll);\r\n        connect(input, \"keydown\", operation(onKeyDown));\r\n        connect(input, \"keypress\", operation(onKeyPress));\r\n        connect(input, \"focus\", onFocus);\r\n        connect(input, \"blur\", onBlur);\r\n\r\n        connect(scroller, \"dragenter\", e_stop);\r\n        connect(scroller, \"dragover\", e_stop);\r\n        connect(scroller, \"drop\", operation(onDrop));\r\n        connect(scroller, \"paste\", function(){focusInput(); fastPoll();});\r\n        connect(input, \"paste\", fastPoll);\r\n        connect(input, \"cut\", operation(function(){replaceSelection(\"\");}));\r\n\r\n        // IE throws unspecified error in certain cases, when\r\n        // trying to access activeElement before onload\r\n        var hasFocus; try { hasFocus = (targetDocument.activeElement == input); } catch(e) { }\r\n        if (hasFocus) setTimeout(onFocus, 20);\r\n        else onBlur();\r\n\r\n        function isLine(l) {return l >= 0 && l < doc.size;}\r\n        // The instance object that we'll return. Mostly calls out to\r\n        // local functions in the CodeMirror function. Some do some extra\r\n        // range checking and/or clipping. operation is used to wrap the\r\n        // call so that changes it makes are tracked, and the display is\r\n        // updated afterwards.\r\n        var instance = wrapper.CodeMirror = {\r\n            getValue: getValue,\r\n            setValue: operation(setValue),\r\n            getSelection: getSelection,\r\n            replaceSelection: operation(replaceSelection),\r\n            focus: function(){focusInput(); onFocus(); fastPoll();},\r\n            setOption: function(option, value) {\r\n                var oldVal = options[option];\r\n                options[option] = value;\r\n                if (option == \"mode\" || option == \"indentUnit\") loadMode();\r\n                else if (option == \"readOnly\" && value) {onBlur(); input.blur();}\r\n                else if (option == \"theme\") themeChanged();\r\n                else if (option == \"lineWrapping\" && oldVal != value) operation(wrappingChanged)();\r\n                else if (option == \"tabSize\") operation(tabsChanged)();\r\n                if (option == \"lineNumbers\" || option == \"gutter\" || option == \"firstLineNumber\" || option == \"theme\")\r\n                    operation(gutterChanged)();\r\n            },\r\n            getOption: function(option) {return options[option];},\r\n            undo: operation(undo),\r\n            redo: operation(redo),\r\n            indentLine: operation(function(n, dir) {\r\n                if (isLine(n)) indentLine(n, dir == null ? \"smart\" : dir ? \"add\" : \"subtract\");\r\n            }),\r\n            indentSelection: operation(indentSelected),\r\n            historySize: function() {return {undo: history.done.length, redo: history.undone.length};},\r\n            clearHistory: function() {history = new History();},\r\n            matchBrackets: operation(function(){matchBrackets(true);}),\r\n            getTokenAt: operation(function(pos) {\r\n                pos = clipPos(pos);\r\n                return getLine(pos.line).getTokenAt(mode, getStateBefore(pos.line), pos.ch);\r\n            }),\r\n            getStateAfter: function(line) {\r\n                line = clipLine(line == null ? doc.size - 1: line);\r\n                return getStateBefore(line + 1);\r\n            },\r\n            cursorCoords: function(start){\r\n                if (start == null) start = sel.inverted;\r\n                return pageCoords(start ? sel.from : sel.to);\r\n            },\r\n            charCoords: function(pos){return pageCoords(clipPos(pos));},\r\n            coordsChar: function(coords) {\r\n                var off = eltOffset(lineSpace);\r\n                return coordsChar(coords.x - off.left, coords.y - off.top);\r\n            },\r\n            markText: operation(markText),\r\n            setBookmark: setBookmark,\r\n            setMarker: operation(addGutterMarker),\r\n            clearMarker: operation(removeGutterMarker),\r\n            setLineClass: operation(setLineClass),\r\n            hideLine: operation(function(h) {return setLineHidden(h, true);}),\r\n            showLine: operation(function(h) {return setLineHidden(h, false);}),\r\n            onDeleteLine: function(line, f) {\r\n                if (typeof line == \"number\") {\r\n                    if (!isLine(line)) return null;\r\n                    line = getLine(line);\r\n                }\r\n                (line.handlers || (line.handlers = [])).push(f);\r\n                return line;\r\n            },\r\n            lineInfo: lineInfo,\r\n            addWidget: function(pos, node, scroll, vert, horiz) {\r\n                pos = localCoords(clipPos(pos));\r\n                var top = pos.yBot, left = pos.x;\r\n                node.style.position = \"absolute\";\r\n                code.appendChild(node);\r\n                if (vert == \"over\") top = pos.y;\r\n                else if (vert == \"near\") {\r\n                    var vspace = Math.max(scroller.offsetHeight, doc.height * textHeight()),\r\n                        hspace = Math.max(code.clientWidth, lineSpace.clientWidth) - paddingLeft();\r\n                    if (pos.yBot + node.offsetHeight > vspace && pos.y > node.offsetHeight)\r\n                        top = pos.y - node.offsetHeight;\r\n                    if (left + node.offsetWidth > hspace)\r\n                        left = hspace - node.offsetWidth;\r\n                }\r\n                node.style.top = (top + paddingTop()) + \"px\";\r\n                node.style.left = node.style.right = \"\";\r\n                if (horiz == \"right\") {\r\n                    left = code.clientWidth - node.offsetWidth;\r\n                    node.style.right = \"0px\";\r\n                } else {\r\n                    if (horiz == \"left\") left = 0;\r\n                    else if (horiz == \"middle\") left = (code.clientWidth - node.offsetWidth) / 2;\r\n                    node.style.left = (left + paddingLeft()) + \"px\";\r\n                }\r\n                if (scroll)\r\n                    scrollIntoView(left, top, left + node.offsetWidth, top + node.offsetHeight);\r\n            },\r\n\r\n            lineCount: function() {return doc.size;},\r\n            clipPos: clipPos,\r\n            getCursor: function(start) {\r\n                if (start == null) start = sel.inverted;\r\n                return copyPos(start ? sel.from : sel.to);\r\n            },\r\n            somethingSelected: function() {return !posEq(sel.from, sel.to);},\r\n            setCursor: operation(function(line, ch, user) {\r\n                if (ch == null && typeof line.line == \"number\") setCursor(line.line, line.ch, user);\r\n                else setCursor(line, ch, user);\r\n            }),\r\n            setSelection: operation(function(from, to, user) {\r\n                (user ? setSelectionUser : setSelection)(clipPos(from), clipPos(to || from));\r\n            }),\r\n            getLine: function(line) {if (isLine(line)) return getLine(line).text;},\r\n            getLineHandle: function(line) {if (isLine(line)) return getLine(line);},\r\n            setLine: operation(function(line, text) {\r\n                if (isLine(line)) replaceRange(text, {line: line, ch: 0}, {line: line, ch: getLine(line).text.length});\r\n            }),\r\n            removeLine: operation(function(line) {\r\n                if (isLine(line)) replaceRange(\"\", {line: line, ch: 0}, clipPos({line: line+1, ch: 0}));\r\n            }),\r\n            replaceRange: operation(replaceRange),\r\n            getRange: function(from, to) {return getRange(clipPos(from), clipPos(to));},\r\n\r\n            execCommand: function(cmd) {return commands[cmd](instance);},\r\n            // Stuff used by commands, probably not much use to outside code.\r\n            moveH: operation(moveH),\r\n            deleteH: operation(deleteH),\r\n            moveV: operation(moveV),\r\n            toggleOverwrite: function() {overwrite = !overwrite;},\r\n\r\n            posFromIndex: function(off) {\r\n                var lineNo = 0, ch;\r\n                doc.iter(0, doc.size, function(line) {\r\n                    var sz = line.text.length + 1;\r\n                    if (sz > off) { ch = off; return true; }\r\n                    off -= sz;\r\n                    ++lineNo;\r\n                });\r\n                return clipPos({line: lineNo, ch: ch});\r\n            },\r\n            indexFromPos: function (coords) {\r\n                if (coords.line < 0 || coords.ch < 0) return 0;\r\n                var index = coords.ch;\r\n                doc.iter(0, coords.line, function (line) {\r\n                    index += line.text.length + 1;\r\n                });\r\n                return index;\r\n            },\r\n\r\n            operation: function(f){return operation(f)();},\r\n            refresh: function(){updateDisplay(true);},\r\n            getInputField: function(){return input;},\r\n            getWrapperElement: function(){return wrapper;},\r\n            getScrollerElement: function(){return scroller;},\r\n            getGutterElement: function(){return gutter;}\r\n        };\r\n\r\n        function getLine(n) { return getLineAt(doc, n); }\r\n        function updateLineHeight(line, height) {\r\n            gutterDirty = true;\r\n            var diff = height - line.height;\r\n            for (var n = line; n; n = n.parent) n.height += diff;\r\n        }\r\n\r\n        function setValue(code) {\r\n            var top = {line: 0, ch: 0};\r\n            updateLines(top, {line: doc.size - 1, ch: getLine(doc.size-1).text.length},\r\n                splitLines(code), top, top);\r\n            updateInput = true;\r\n        }\r\n        function getValue(code) {\r\n            var text = [];\r\n            doc.iter(0, doc.size, function(line) { text.push(line.text); });\r\n            return text.join(\"\\n\");\r\n        }\r\n\r\n        function onMouseDown(e) {\r\n            setShift(e.shiftKey);\r\n            // Check whether this is a click in a widget\r\n            for (var n = e_target(e); n != wrapper; n = n.parentNode)\r\n                if (n.parentNode == code && n != mover) return;\r\n\r\n            // See if this is a click in the gutter\r\n            for (var n = e_target(e); n != wrapper; n = n.parentNode)\r\n                if (n.parentNode == gutterText) {\r\n                    if (options.onGutterClick)\r\n                        options.onGutterClick(instance, indexOf(gutterText.childNodes, n) + showingFrom, e);\r\n                    return e_preventDefault(e);\r\n                }\r\n\r\n            var start = posFromMouse(e);\r\n\r\n            switch (e_button(e)) {\r\n                case 3:\r\n                    if (gecko && !mac) onContextMenu(e);\r\n                    return;\r\n                case 2:\r\n                    if (start) setCursor(start.line, start.ch, true);\r\n                    return;\r\n            }\r\n            // For button 1, if it was clicked inside the editor\r\n            // (posFromMouse returning non-null), we have to adjust the\r\n            // selection.\r\n            if (!start) {if (e_target(e) == scroller) e_preventDefault(e); return;}\r\n\r\n            if (!focused) onFocus();\r\n\r\n            var now = +new Date;\r\n            if (lastDoubleClick && lastDoubleClick.time > now - 400 && posEq(lastDoubleClick.pos, start)) {\r\n                e_preventDefault(e);\r\n                setTimeout(focusInput, 20);\r\n                return selectLine(start.line);\r\n            } else if (lastClick && lastClick.time > now - 400 && posEq(lastClick.pos, start)) {\r\n                lastDoubleClick = {time: now, pos: start};\r\n                e_preventDefault(e);\r\n                return selectWordAt(start);\r\n            } else { lastClick = {time: now, pos: start}; }\r\n\r\n            var last = start, going;\r\n            if (dragAndDrop && !posEq(sel.from, sel.to) &&\r\n                !posLess(start, sel.from) && !posLess(sel.to, start)) {\r\n                // Let the drag handler handle this.\r\n                if (webkit) lineSpace.draggable = true;\r\n                var up = connect(targetDocument, \"mouseup\", operation(function(e2) {\r\n                    if (webkit) lineSpace.draggable = false;\r\n                    draggingText = false;\r\n                    up();\r\n                    if (Math.abs(e.clientX - e2.clientX) + Math.abs(e.clientY - e2.clientY) < 10) {\r\n                        e_preventDefault(e2);\r\n                        setCursor(start.line, start.ch, true);\r\n                        focusInput();\r\n                    }\r\n                }), true);\r\n                draggingText = true;\r\n                return;\r\n            }\r\n            e_preventDefault(e);\r\n            setCursor(start.line, start.ch, true);\r\n\r\n            function extend(e) {\r\n                var cur = posFromMouse(e, true);\r\n                if (cur && !posEq(cur, last)) {\r\n                    if (!focused) onFocus();\r\n                    last = cur;\r\n                    setSelectionUser(start, cur);\r\n                    updateInput = false;\r\n                    var visible = visibleLines();\r\n                    if (cur.line >= visible.to || cur.line < visible.from)\r\n                        going = setTimeout(operation(function(){extend(e);}), 150);\r\n                }\r\n            }\r\n\r\n            var move = connect(targetDocument, \"mousemove\", operation(function(e) {\r\n                clearTimeout(going);\r\n                e_preventDefault(e);\r\n                extend(e);\r\n            }), true);\r\n            var up = connect(targetDocument, \"mouseup\", operation(function(e) {\r\n                clearTimeout(going);\r\n                var cur = posFromMouse(e);\r\n                if (cur) setSelectionUser(start, cur);\r\n                e_preventDefault(e);\r\n                focusInput();\r\n                updateInput = true;\r\n                move(); up();\r\n            }), true);\r\n        }\r\n        function onDoubleClick(e) {\r\n            for (var n = e_target(e); n != wrapper; n = n.parentNode)\r\n                if (n.parentNode == gutterText) return e_preventDefault(e);\r\n            var start = posFromMouse(e);\r\n            if (!start) return;\r\n            lastDoubleClick = {time: +new Date, pos: start};\r\n            e_preventDefault(e);\r\n            selectWordAt(start);\r\n        }\r\n        function onDrop(e) {\r\n            e.preventDefault();\r\n            var pos = posFromMouse(e, true), files = e.dataTransfer.files;\r\n            if (!pos || options.readOnly) return;\r\n            if (files && files.length && window.FileReader && window.File) {\r\n                function loadFile(file, i) {\r\n                    var reader = new FileReader;\r\n                    reader.onload = function() {\r\n                        text[i] = reader.result;\r\n                        if (++read == n) {\r\n                            pos = clipPos(pos);\r\n                            operation(function() {\r\n                                var end = replaceRange(text.join(\"\"), pos, pos);\r\n                                setSelectionUser(pos, end);\r\n                            })();\r\n                        }\r\n                    };\r\n                    reader.readAsText(file);\r\n                }\r\n                var n = files.length, text = Array(n), read = 0;\r\n                for (var i = 0; i < n; ++i) loadFile(files[i], i);\r\n            }\r\n            else {\r\n                try {\r\n                    var text = e.dataTransfer.getData(\"Text\");\r\n                    if (text) {\r\n                        var end = replaceRange(text, pos, pos);\r\n                        var curFrom = sel.from, curTo = sel.to;\r\n                        setSelectionUser(pos, end);\r\n                        if (draggingText) replaceRange(\"\", curFrom, curTo);\r\n                        focusInput();\r\n                    }\r\n                }\r\n                catch(e){}\r\n            }\r\n        }\r\n        function onDragStart(e) {\r\n            var txt = getSelection();\r\n            // This will reset escapeElement\r\n            htmlEscape(txt);\r\n            e.dataTransfer.setDragImage(escapeElement, 0, 0);\r\n            e.dataTransfer.setData(\"Text\", txt);\r\n        }\r\n        function handleKeyBinding(e) {\r\n            var name = keyNames[e.keyCode], next = keyMap[options.keyMap].auto, bound, dropShift;\r\n            if (name == null || e.altGraphKey) {\r\n                if (next) options.keyMap = next;\r\n                return null;\r\n            }\r\n            if (e.altKey) name = \"Alt-\" + name;\r\n            if (e.ctrlKey) name = \"Ctrl-\" + name;\r\n            if (e.metaKey) name = \"Cmd-\" + name;\r\n            if (e.shiftKey && (bound = lookupKey(\"Shift-\" + name, options.extraKeys, options.keyMap))) {\r\n                dropShift = true;\r\n            } else {\r\n                bound = lookupKey(name, options.extraKeys, options.keyMap);\r\n            }\r\n            if (typeof bound == \"string\") {\r\n                if (commands.propertyIsEnumerable(bound)) bound = commands[bound];\r\n                else bound = null;\r\n            }\r\n            if (next && (bound || !isModifierKey(e))) options.keyMap = next;\r\n            if (!bound) return false;\r\n            if (dropShift) {\r\n                var prevShift = shiftSelecting;\r\n                shiftSelecting = null;\r\n                bound(instance);\r\n                shiftSelecting = prevShift;\r\n            } else bound(instance);\r\n            e_preventDefault(e);\r\n            return true;\r\n        }\r\n        var lastStoppedKey = null;\r\n        function onKeyDown(e) {\r\n            if (!focused) onFocus();\r\n            var code = e.keyCode;\r\n            // IE does strange things with escape.\r\n            if (ie && code == 27) { e.returnValue = false; }\r\n            setShift(code == 16 || e.shiftKey);\r\n            // First give onKeyEvent option a chance to handle this.\r\n            if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;\r\n            var handled = handleKeyBinding(e);\r\n            if (window.opera) {\r\n                lastStoppedKey = handled ? e.keyCode : null;\r\n                // Opera has no cut event... we try to at least catch the key combo\r\n                if (!handled && (mac ? e.metaKey : e.ctrlKey) && e.keyCode == 88)\r\n                    replaceSelection(\"\");\r\n            }\r\n        }\r\n        function onKeyPress(e) {\r\n            if (window.opera && e.keyCode == lastStoppedKey) {lastStoppedKey = null; e_preventDefault(e); return;}\r\n            if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;\r\n            if (window.opera && !e.which && handleKeyBinding(e)) return;\r\n            if (options.electricChars && mode.electricChars) {\r\n                var ch = String.fromCharCode(e.charCode == null ? e.keyCode : e.charCode);\r\n                if (mode.electricChars.indexOf(ch) > -1)\r\n                    setTimeout(operation(function() {indentLine(sel.to.line, \"smart\");}), 75);\r\n            }\r\n            fastPoll();\r\n        }\r\n        function onKeyUp(e) {\r\n            if (options.onKeyEvent && options.onKeyEvent(instance, addStop(e))) return;\r\n            if (e.keyCode == 16) shiftSelecting = null;\r\n        }\r\n\r\n        function onFocus() {\r\n            if (options.readOnly) return;\r\n            if (!focused) {\r\n                if (options.onFocus) options.onFocus(instance);\r\n                focused = true;\r\n                if (wrapper.className.search(/\\bCodeMirror-focused\\b/) == -1)\r\n                    wrapper.className += \" CodeMirror-focused\";\r\n                if (!leaveInputAlone) resetInput(true);\r\n            }\r\n            slowPoll();\r\n            restartBlink();\r\n        }\r\n        function onBlur() {\r\n            if (focused) {\r\n                if (options.onBlur) options.onBlur(instance);\r\n                focused = false;\r\n                wrapper.className = wrapper.className.replace(\" CodeMirror-focused\", \"\");\r\n            }\r\n            clearInterval(blinker);\r\n            setTimeout(function() {if (!focused) shiftSelecting = null;}, 150);\r\n        }\r\n\r\n        // Replace the range from from to to by the strings in newText.\r\n        // Afterwards, set the selection to selFrom, selTo.\r\n        function updateLines(from, to, newText, selFrom, selTo) {\r\n            if (history) {\r\n                var old = [];\r\n                doc.iter(from.line, to.line + 1, function(line) { old.push(line.text); });\r\n                history.addChange(from.line, newText.length, old);\r\n                while (history.done.length > options.undoDepth) history.done.shift();\r\n            }\r\n            updateLinesNoUndo(from, to, newText, selFrom, selTo);\r\n        }\r\n        function unredoHelper(from, to) {\r\n            var change = from.pop();\r\n            if (change) {\r\n                var replaced = [], end = change.start + change.added;\r\n                doc.iter(change.start, end, function(line) { replaced.push(line.text); });\r\n                to.push({start: change.start, added: change.old.length, old: replaced});\r\n                var pos = clipPos({line: change.start + change.old.length - 1,\r\n                    ch: editEnd(replaced[replaced.length-1], change.old[change.old.length-1])});\r\n                updateLinesNoUndo({line: change.start, ch: 0}, {line: end - 1, ch: getLine(end-1).text.length}, change.old, pos, pos);\r\n                updateInput = true;\r\n            }\r\n        }\r\n        function undo() {unredoHelper(history.done, history.undone);}\r\n        function redo() {unredoHelper(history.undone, history.done);}\r\n\r\n        function updateLinesNoUndo(from, to, newText, selFrom, selTo) {\r\n            var recomputeMaxLength = false, maxLineLength = maxLine.length;\r\n            if (!options.lineWrapping)\r\n                doc.iter(from.line, to.line, function(line) {\r\n                    if (line.text.length == maxLineLength) {recomputeMaxLength = true; return true;}\r\n                });\r\n            if (from.line != to.line || newText.length > 1) gutterDirty = true;\r\n\r\n            var nlines = to.line - from.line, firstLine = getLine(from.line), lastLine = getLine(to.line);\r\n            // First adjust the line structure, taking some care to leave highlighting intact.\r\n            if (from.ch == 0 && to.ch == 0 && newText[newText.length - 1] == \"\") {\r\n                // This is a whole-line replace. Treated specially to make\r\n                // sure line objects move the way they are supposed to.\r\n                var added = [], prevLine = null;\r\n                if (from.line) {\r\n                    prevLine = getLine(from.line - 1);\r\n                    prevLine.fixMarkEnds(lastLine);\r\n                } else lastLine.fixMarkStarts();\r\n                for (var i = 0, e = newText.length - 1; i < e; ++i)\r\n                    added.push(Line.inheritMarks(newText[i], prevLine));\r\n                if (nlines) doc.remove(from.line, nlines, callbacks);\r\n                if (added.length) doc.insert(from.line, added);\r\n            } else if (firstLine == lastLine) {\r\n                if (newText.length == 1)\r\n                    firstLine.replace(from.ch, to.ch, newText[0]);\r\n                else {\r\n                    lastLine = firstLine.split(to.ch, newText[newText.length-1]);\r\n                    firstLine.replace(from.ch, null, newText[0]);\r\n                    firstLine.fixMarkEnds(lastLine);\r\n                    var added = [];\r\n                    for (var i = 1, e = newText.length - 1; i < e; ++i)\r\n                        added.push(Line.inheritMarks(newText[i], firstLine));\r\n                    added.push(lastLine);\r\n                    doc.insert(from.line + 1, added);\r\n                }\r\n            } else if (newText.length == 1) {\r\n                firstLine.replace(from.ch, null, newText[0]);\r\n                lastLine.replace(null, to.ch, \"\");\r\n                firstLine.append(lastLine);\r\n                doc.remove(from.line + 1, nlines, callbacks);\r\n            } else {\r\n                var added = [];\r\n                firstLine.replace(from.ch, null, newText[0]);\r\n                lastLine.replace(null, to.ch, newText[newText.length-1]);\r\n                firstLine.fixMarkEnds(lastLine);\r\n                for (var i = 1, e = newText.length - 1; i < e; ++i)\r\n                    added.push(Line.inheritMarks(newText[i], firstLine));\r\n                if (nlines > 1) doc.remove(from.line + 1, nlines - 1, callbacks);\r\n                doc.insert(from.line + 1, added);\r\n            }\r\n            if (options.lineWrapping) {\r\n                var perLine = scroller.clientWidth / charWidth() - 3;\r\n                doc.iter(from.line, from.line + newText.length, function(line) {\r\n                    if (line.hidden) return;\r\n                    var guess = Math.ceil(line.text.length / perLine) || 1;\r\n                    if (guess != line.height) updateLineHeight(line, guess);\r\n                });\r\n            } else {\r\n                doc.iter(from.line, i + newText.length, function(line) {\r\n                    var l = line.text;\r\n                    if (l.length > maxLineLength) {\r\n                        maxLine = l; maxLineLength = l.length; maxWidth = null;\r\n                        recomputeMaxLength = false;\r\n                    }\r\n                });\r\n                if (recomputeMaxLength) {\r\n                    maxLineLength = 0; maxLine = \"\"; maxWidth = null;\r\n                    doc.iter(0, doc.size, function(line) {\r\n                        var l = line.text;\r\n                        if (l.length > maxLineLength) {\r\n                            maxLineLength = l.length; maxLine = l;\r\n                        }\r\n                    });\r\n                }\r\n            }\r\n\r\n            // Add these lines to the work array, so that they will be\r\n            // highlighted. Adjust work lines if lines were added/removed.\r\n            var newWork = [], lendiff = newText.length - nlines - 1;\r\n            for (var i = 0, l = work.length; i < l; ++i) {\r\n                var task = work[i];\r\n                if (task < from.line) newWork.push(task);\r\n                else if (task > to.line) newWork.push(task + lendiff);\r\n            }\r\n            var hlEnd = from.line + Math.min(newText.length, 500);\r\n            highlightLines(from.line, hlEnd);\r\n            newWork.push(hlEnd);\r\n            work = newWork;\r\n            startWorker(100);\r\n            // Remember that these lines changed, for updating the display\r\n            changes.push({from: from.line, to: to.line + 1, diff: lendiff});\r\n            var changeObj = {from: from, to: to, text: newText};\r\n            if (textChanged) {\r\n                for (var cur = textChanged; cur.next; cur = cur.next) {}\r\n                cur.next = changeObj;\r\n            } else textChanged = changeObj;\r\n\r\n            // Update the selection\r\n            function updateLine(n) {return n <= Math.min(to.line, to.line + lendiff) ? n : n + lendiff;}\r\n            setSelection(selFrom, selTo, updateLine(sel.from.line), updateLine(sel.to.line));\r\n\r\n            // Make sure the scroll-size div has the correct height.\r\n            code.style.height = (doc.height * textHeight() + 2 * paddingTop()) + \"px\";\r\n        }\r\n\r\n        function replaceRange(code, from, to) {\r\n            from = clipPos(from);\r\n            if (!to) to = from; else to = clipPos(to);\r\n            code = splitLines(code);\r\n            function adjustPos(pos) {\r\n                if (posLess(pos, from)) return pos;\r\n                if (!posLess(to, pos)) return end;\r\n                var line = pos.line + code.length - (to.line - from.line) - 1;\r\n                var ch = pos.ch;\r\n                if (pos.line == to.line)\r\n                    ch += code[code.length-1].length - (to.ch - (to.line == from.line ? from.ch : 0));\r\n                return {line: line, ch: ch};\r\n            }\r\n            var end;\r\n            replaceRange1(code, from, to, function(end1) {\r\n                end = end1;\r\n                return {from: adjustPos(sel.from), to: adjustPos(sel.to)};\r\n            });\r\n            return end;\r\n        }\r\n        function replaceSelection(code, collapse) {\r\n            replaceRange1(splitLines(code), sel.from, sel.to, function(end) {\r\n                if (collapse == \"end\") return {from: end, to: end};\r\n                else if (collapse == \"start\") return {from: sel.from, to: sel.from};\r\n                else return {from: sel.from, to: end};\r\n            });\r\n        }\r\n        function replaceRange1(code, from, to, computeSel) {\r\n            var endch = code.length == 1 ? code[0].length + from.ch : code[code.length-1].length;\r\n            var newSel = computeSel({line: from.line + code.length - 1, ch: endch});\r\n            updateLines(from, to, code, newSel.from, newSel.to);\r\n        }\r\n\r\n        function getRange(from, to) {\r\n            var l1 = from.line, l2 = to.line;\r\n            if (l1 == l2) return getLine(l1).text.slice(from.ch, to.ch);\r\n            var code = [getLine(l1).text.slice(from.ch)];\r\n            doc.iter(l1 + 1, l2, function(line) { code.push(line.text); });\r\n            code.push(getLine(l2).text.slice(0, to.ch));\r\n            return code.join(\"\\n\");\r\n        }\r\n        function getSelection() {\r\n            return getRange(sel.from, sel.to);\r\n        }\r\n\r\n        var pollingFast = false; // Ensures slowPoll doesn't cancel fastPoll\r\n        function slowPoll() {\r\n            if (pollingFast) return;\r\n            poll.set(options.pollInterval, function() {\r\n                startOperation();\r\n                readInput();\r\n                if (focused) slowPoll();\r\n                endOperation();\r\n            });\r\n        }\r\n        function fastPoll() {\r\n            var missed = false;\r\n            pollingFast = true;\r\n            function p() {\r\n                startOperation();\r\n                var changed = readInput();\r\n                if (!changed && !missed) {missed = true; poll.set(60, p);}\r\n                else {pollingFast = false; slowPoll();}\r\n                endOperation();\r\n            }\r\n            poll.set(20, p);\r\n        }\r\n\r\n        // Previnput is a hack to work with IME. If we reset the textarea\r\n        // on every change, that breaks IME. So we look for changes\r\n        // compared to the previous content instead. (Modern browsers have\r\n        // events that indicate IME taking place, but these are not widely\r\n        // supported or compatible enough yet to rely on.)\r\n        var prevInput = \"\";\r\n        function readInput() {\r\n            if (leaveInputAlone || !focused || hasSelection(input)) return false;\r\n            var text = input.value;\r\n            if (text == prevInput) return false;\r\n            shiftSelecting = null;\r\n            var same = 0, l = Math.min(prevInput.length, text.length);\r\n            while (same < l && prevInput[same] == text[same]) ++same;\r\n            if (same < prevInput.length)\r\n                sel.from = {line: sel.from.line, ch: sel.from.ch - (prevInput.length - same)};\r\n            else if (overwrite && posEq(sel.from, sel.to))\r\n                sel.to = {line: sel.to.line, ch: Math.min(getLine(sel.to.line).text.length, sel.to.ch + (text.length - same))};\r\n            replaceSelection(text.slice(same), \"end\");\r\n            prevInput = text;\r\n            return true;\r\n        }\r\n        function resetInput(user) {\r\n            if (!posEq(sel.from, sel.to)) {\r\n                prevInput = \"\";\r\n                input.value = getSelection();\r\n                input.select();\r\n            } else if (user) prevInput = input.value = \"\";\r\n        }\r\n\r\n        function focusInput() {\r\n            if (!options.readOnly) input.focus();\r\n        }\r\n\r\n        function scrollEditorIntoView() {\r\n            if (!cursor.getBoundingClientRect) return;\r\n            var rect = cursor.getBoundingClientRect();\r\n            // IE returns bogus coordinates when the instance sits inside of an iframe and the cursor is hidden\r\n            if (ie && rect.top == rect.bottom) return;\r\n            var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight);\r\n            if (rect.top < 0 || rect.bottom > winH) cursor.scrollIntoView();\r\n        }\r\n        function scrollCursorIntoView() {\r\n            var cursor = localCoords(sel.inverted ? sel.from : sel.to);\r\n            var x = options.lineWrapping ? Math.min(cursor.x, lineSpace.offsetWidth) : cursor.x;\r\n            return scrollIntoView(x, cursor.y, x, cursor.yBot);\r\n        }\r\n        function scrollIntoView(x1, y1, x2, y2) {\r\n            var pl = paddingLeft(), pt = paddingTop(), lh = textHeight();\r\n            y1 += pt; y2 += pt; x1 += pl; x2 += pl;\r\n            var screen = scroller.clientHeight, screentop = scroller.scrollTop, scrolled = false, result = true;\r\n            if (y1 < screentop) {scroller.scrollTop = Math.max(0, y1 - 2*lh); scrolled = true;}\r\n            else if (y2 > screentop + screen) {scroller.scrollTop = y2 + lh - screen; scrolled = true;}\r\n\r\n            var screenw = scroller.clientWidth, screenleft = scroller.scrollLeft;\r\n            var gutterw = options.fixedGutter ? gutter.clientWidth : 0;\r\n            if (x1 < screenleft + gutterw) {\r\n                if (x1 < 50) x1 = 0;\r\n                scroller.scrollLeft = Math.max(0, x1 - 10 - gutterw);\r\n                scrolled = true;\r\n            }\r\n            else if (x2 > screenw + screenleft - 3) {\r\n                scroller.scrollLeft = x2 + 10 - screenw;\r\n                scrolled = true;\r\n                if (x2 > code.clientWidth) result = false;\r\n            }\r\n            if (scrolled && options.onScroll) options.onScroll(instance);\r\n            return result;\r\n        }\r\n\r\n        function visibleLines() {\r\n            var lh = textHeight(), top = scroller.scrollTop - paddingTop();\r\n            var from_height = Math.max(0, Math.floor(top / lh));\r\n            var to_height = Math.ceil((top + scroller.clientHeight) / lh);\r\n            return {from: lineAtHeight(doc, from_height),\r\n                to: lineAtHeight(doc, to_height)};\r\n        }\r\n        // Uses a set of changes plus the current scroll position to\r\n        // determine which DOM updates have to be made, and makes the\r\n        // updates.\r\n        function updateDisplay(changes, suppressCallback) {\r\n            if (!scroller.clientWidth) {\r\n                showingFrom = showingTo = displayOffset = 0;\r\n                return;\r\n            }\r\n            // Compute the new visible window\r\n            var visible = visibleLines();\r\n            // Bail out if the visible area is already rendered and nothing changed.\r\n            if (changes !== true && changes.length == 0 && visible.from >= showingFrom && visible.to <= showingTo) return;\r\n            var from = Math.max(visible.from - 100, 0), to = Math.min(doc.size, visible.to + 100);\r\n            if (showingFrom < from && from - showingFrom < 20) from = showingFrom;\r\n            if (showingTo > to && showingTo - to < 20) to = Math.min(doc.size, showingTo);\r\n\r\n            // Create a range of theoretically intact lines, and punch holes\r\n            // in that using the change info.\r\n            var intact = changes === true ? [] :\r\n                computeIntact([{from: showingFrom, to: showingTo, domStart: 0}], changes);\r\n            // Clip off the parts that won't be visible\r\n            var intactLines = 0;\r\n            for (var i = 0; i < intact.length; ++i) {\r\n                var range = intact[i];\r\n                if (range.from < from) {range.domStart += (from - range.from); range.from = from;}\r\n                if (range.to > to) range.to = to;\r\n                if (range.from >= range.to) intact.splice(i--, 1);\r\n                else intactLines += range.to - range.from;\r\n            }\r\n            if (intactLines == to - from) return;\r\n            intact.sort(function(a, b) {return a.domStart - b.domStart;});\r\n\r\n            var th = textHeight(), gutterDisplay = gutter.style.display;\r\n            lineDiv.style.display = gutter.style.display = \"none\";\r\n            patchDisplay(from, to, intact);\r\n            lineDiv.style.display = \"\";\r\n\r\n            // Position the mover div to align with the lines it's supposed\r\n            // to be showing (which will cover the visible display)\r\n            var different = from != showingFrom || to != showingTo || lastSizeC != scroller.clientHeight + th;\r\n            // This is just a bogus formula that detects when the editor is\r\n            // resized or the font size changes.\r\n            if (different) lastSizeC = scroller.clientHeight + th;\r\n            showingFrom = from; showingTo = to;\r\n            displayOffset = heightAtLine(doc, from);\r\n            mover.style.top = (displayOffset * th) + \"px\";\r\n            code.style.height = (doc.height * th + 2 * paddingTop()) + \"px\";\r\n\r\n            // Since this is all rather error prone, it is honoured with the\r\n            // only assertion in the whole file.\r\n            if (lineDiv.childNodes.length != showingTo - showingFrom)\r\n                throw new Error(\"BAD PATCH! \" + JSON.stringify(intact) + \" size=\" + (showingTo - showingFrom) +\r\n                    \" nodes=\" + lineDiv.childNodes.length);\r\n\r\n            if (options.lineWrapping) {\r\n                maxWidth = scroller.clientWidth;\r\n                var curNode = lineDiv.firstChild;\r\n                doc.iter(showingFrom, showingTo, function(line) {\r\n                    if (!line.hidden) {\r\n                        var height = Math.round(curNode.offsetHeight / th) || 1;\r\n                        if (line.height != height) {updateLineHeight(line, height); gutterDirty = true;}\r\n                    }\r\n                    curNode = curNode.nextSibling;\r\n                });\r\n            } else {\r\n                if (maxWidth == null) maxWidth = stringWidth(maxLine);\r\n                if (maxWidth > scroller.clientWidth) {\r\n                    lineSpace.style.width = maxWidth + \"px\";\r\n                    // Needed to prevent odd wrapping/hiding of widgets placed in here.\r\n                    code.style.width = \"\";\r\n                    code.style.width = scroller.scrollWidth + \"px\";\r\n                } else {\r\n                    lineSpace.style.width = code.style.width = \"\";\r\n                }\r\n            }\r\n            gutter.style.display = gutterDisplay;\r\n            if (different || gutterDirty) updateGutter();\r\n            updateCursor();\r\n            if (!suppressCallback && options.onUpdate) options.onUpdate(instance);\r\n            return true;\r\n        }\r\n\r\n        function computeIntact(intact, changes) {\r\n            for (var i = 0, l = changes.length || 0; i < l; ++i) {\r\n                var change = changes[i], intact2 = [], diff = change.diff || 0;\r\n                for (var j = 0, l2 = intact.length; j < l2; ++j) {\r\n                    var range = intact[j];\r\n                    if (change.to <= range.from && change.diff)\r\n                        intact2.push({from: range.from + diff, to: range.to + diff,\r\n                            domStart: range.domStart});\r\n                    else if (change.to <= range.from || change.from >= range.to)\r\n                        intact2.push(range);\r\n                    else {\r\n                        if (change.from > range.from)\r\n                            intact2.push({from: range.from, to: change.from, domStart: range.domStart});\r\n                        if (change.to < range.to)\r\n                            intact2.push({from: change.to + diff, to: range.to + diff,\r\n                                domStart: range.domStart + (change.to - range.from)});\r\n                    }\r\n                }\r\n                intact = intact2;\r\n            }\r\n            return intact;\r\n        }\r\n\r\n        function patchDisplay(from, to, intact) {\r\n            // The first pass removes the DOM nodes that aren't intact.\r\n            if (!intact.length) lineDiv.innerHTML = \"\";\r\n            else {\r\n                function killNode(node) {\r\n                    var tmp = node.nextSibling;\r\n                    node.parentNode.removeChild(node);\r\n                    return tmp;\r\n                }\r\n                var domPos = 0, curNode = lineDiv.firstChild, n;\r\n                for (var i = 0; i < intact.length; ++i) {\r\n                    var cur = intact[i];\r\n                    while (cur.domStart > domPos) {curNode = killNode(curNode); domPos++;}\r\n                    for (var j = 0, e = cur.to - cur.from; j < e; ++j) {curNode = curNode.nextSibling; domPos++;}\r\n                }\r\n                while (curNode) curNode = killNode(curNode);\r\n            }\r\n            // This pass fills in the lines that actually changed.\r\n            var nextIntact = intact.shift(), curNode = lineDiv.firstChild, j = from;\r\n            var sfrom = sel.from.line, sto = sel.to.line, inSel = sfrom < from && sto >= from;\r\n            var scratch = targetDocument.createElement(\"div\"), newElt;\r\n            doc.iter(from, to, function(line) {\r\n                var ch1 = null, ch2 = null;\r\n                if (inSel) {\r\n                    ch1 = 0;\r\n                    if (sto == j) {inSel = false; ch2 = sel.to.ch;}\r\n                } else if (sfrom == j) {\r\n                    if (sto == j) {ch1 = sel.from.ch; ch2 = sel.to.ch;}\r\n                    else {inSel = true; ch1 = sel.from.ch;}\r\n                }\r\n                if (nextIntact && nextIntact.to == j) nextIntact = intact.shift();\r\n                if (!nextIntact || nextIntact.from > j) {\r\n                    if (line.hidden) scratch.innerHTML = \"<pre></pre>\";\r\n                    else scratch.innerHTML = line.getHTML(ch1, ch2, true, tabText);\r\n                    lineDiv.insertBefore(scratch.firstChild, curNode);\r\n                } else {\r\n                    curNode = curNode.nextSibling;\r\n                }\r\n                ++j;\r\n            });\r\n        }\r\n\r\n        function updateGutter() {\r\n            if (!options.gutter && !options.lineNumbers) return;\r\n            var hText = mover.offsetHeight, hEditor = scroller.clientHeight;\r\n            gutter.style.height = (hText - hEditor < 2 ? hEditor : hText) + \"px\";\r\n            var html = [], i = showingFrom;\r\n            doc.iter(showingFrom, Math.max(showingTo, showingFrom + 1), function(line) {\r\n                if (line.hidden) {\r\n                    html.push(\"<pre></pre>\");\r\n                } else {\r\n                    var marker = line.gutterMarker;\r\n                    var text = options.lineNumbers ? i + options.firstLineNumber : null;\r\n                    if (marker && marker.text)\r\n                        text = marker.text.replace(\"%N%\", text != null ? text : \"\");\r\n                    else if (text == null)\r\n                        text = \"\\u00a0\";\r\n                    html.push((marker && marker.style ? '<pre class=\"' + marker.style + '\">' : \"<pre>\"), text);\r\n                    for (var j = 1; j < line.height; ++j) html.push(\"<br/>&#160;\");\r\n                    html.push(\"</pre>\");\r\n                }\r\n                ++i;\r\n            });\r\n            gutter.style.display = \"none\";\r\n            gutterText.innerHTML = html.join(\"\");\r\n            var minwidth = String(doc.size).length, firstNode = gutterText.firstChild, val = eltText(firstNode), pad = \"\";\r\n            while (val.length + pad.length < minwidth) pad += \"\\u00a0\";\r\n            if (pad) firstNode.insertBefore(targetDocument.createTextNode(pad), firstNode.firstChild);\r\n            gutter.style.display = \"\";\r\n            lineSpace.style.marginLeft = gutter.offsetWidth + \"px\";\r\n            gutterDirty = false;\r\n        }\r\n        function updateCursor() {\r\n            var head = sel.inverted ? sel.from : sel.to, lh = textHeight();\r\n            var pos = localCoords(head, true);\r\n            var wrapOff = eltOffset(wrapper), lineOff = eltOffset(lineDiv);\r\n            inputDiv.style.top = (pos.y + lineOff.top - wrapOff.top) + \"px\";\r\n            inputDiv.style.left = (pos.x + lineOff.left - wrapOff.left) + \"px\";\r\n            if (posEq(sel.from, sel.to)) {\r\n                cursor.style.top = pos.y + \"px\";\r\n                cursor.style.left = (options.lineWrapping ? Math.min(pos.x, lineSpace.offsetWidth) : pos.x) + \"px\";\r\n                cursor.style.display = \"\";\r\n            }\r\n            else cursor.style.display = \"none\";\r\n        }\r\n\r\n        function setShift(val) {\r\n            if (val) shiftSelecting = shiftSelecting || (sel.inverted ? sel.to : sel.from);\r\n            else shiftSelecting = null;\r\n        }\r\n        function setSelectionUser(from, to) {\r\n            var sh = shiftSelecting && clipPos(shiftSelecting);\r\n            if (sh) {\r\n                if (posLess(sh, from)) from = sh;\r\n                else if (posLess(to, sh)) to = sh;\r\n            }\r\n            setSelection(from, to);\r\n            userSelChange = true;\r\n        }\r\n        // Update the selection. Last two args are only used by\r\n        // updateLines, since they have to be expressed in the line\r\n        // numbers before the update.\r\n        function setSelection(from, to, oldFrom, oldTo) {\r\n            goalColumn = null;\r\n            if (oldFrom == null) {oldFrom = sel.from.line; oldTo = sel.to.line;}\r\n            if (posEq(sel.from, from) && posEq(sel.to, to)) return;\r\n            if (posLess(to, from)) {var tmp = to; to = from; from = tmp;}\r\n\r\n            // Skip over hidden lines.\r\n            if (from.line != oldFrom) from = skipHidden(from, oldFrom, sel.from.ch);\r\n            if (to.line != oldTo) to = skipHidden(to, oldTo, sel.to.ch);\r\n\r\n            if (posEq(from, to)) sel.inverted = false;\r\n            else if (posEq(from, sel.to)) sel.inverted = false;\r\n            else if (posEq(to, sel.from)) sel.inverted = true;\r\n\r\n            // Some ugly logic used to only mark the lines that actually did\r\n            // see a change in selection as changed, rather than the whole\r\n            // selected range.\r\n            if (posEq(from, to)) {\r\n                if (!posEq(sel.from, sel.to))\r\n                    changes.push({from: oldFrom, to: oldTo + 1});\r\n            }\r\n            else if (posEq(sel.from, sel.to)) {\r\n                changes.push({from: from.line, to: to.line + 1});\r\n            }\r\n            else {\r\n                if (!posEq(from, sel.from)) {\r\n                    if (from.line < oldFrom)\r\n                        changes.push({from: from.line, to: Math.min(to.line, oldFrom) + 1});\r\n                    else\r\n                        changes.push({from: oldFrom, to: Math.min(oldTo, from.line) + 1});\r\n                }\r\n                if (!posEq(to, sel.to)) {\r\n                    if (to.line < oldTo)\r\n                        changes.push({from: Math.max(oldFrom, from.line), to: oldTo + 1});\r\n                    else\r\n                        changes.push({from: Math.max(from.line, oldTo), to: to.line + 1});\r\n                }\r\n            }\r\n            sel.from = from; sel.to = to;\r\n            selectionChanged = true;\r\n        }\r\n        function skipHidden(pos, oldLine, oldCh) {\r\n            function getNonHidden(dir) {\r\n                var lNo = pos.line + dir, end = dir == 1 ? doc.size : -1;\r\n                while (lNo != end) {\r\n                    var line = getLine(lNo);\r\n                    if (!line.hidden) {\r\n                        var ch = pos.ch;\r\n                        if (ch > oldCh || ch > line.text.length) ch = line.text.length;\r\n                        return {line: lNo, ch: ch};\r\n                    }\r\n                    lNo += dir;\r\n                }\r\n            }\r\n            var line = getLine(pos.line);\r\n            if (!line.hidden) return pos;\r\n            if (pos.line >= oldLine) return getNonHidden(1) || getNonHidden(-1);\r\n            else return getNonHidden(-1) || getNonHidden(1);\r\n        }\r\n        function setCursor(line, ch, user) {\r\n            var pos = clipPos({line: line, ch: ch || 0});\r\n            (user ? setSelectionUser : setSelection)(pos, pos);\r\n        }\r\n\r\n        function clipLine(n) {return Math.max(0, Math.min(n, doc.size-1));}\r\n        function clipPos(pos) {\r\n            if (pos.line < 0) return {line: 0, ch: 0};\r\n            if (pos.line >= doc.size) return {line: doc.size-1, ch: getLine(doc.size-1).text.length};\r\n            var ch = pos.ch, linelen = getLine(pos.line).text.length;\r\n            if (ch == null || ch > linelen) return {line: pos.line, ch: linelen};\r\n            else if (ch < 0) return {line: pos.line, ch: 0};\r\n            else return pos;\r\n        }\r\n\r\n        function findPosH(dir, unit) {\r\n            var end = sel.inverted ? sel.from : sel.to, line = end.line, ch = end.ch;\r\n            var lineObj = getLine(line);\r\n            function findNextLine() {\r\n                for (var l = line + dir, e = dir < 0 ? -1 : doc.size; l != e; l += dir) {\r\n                    var lo = getLine(l);\r\n                    if (!lo.hidden) { line = l; lineObj = lo; return true; }\r\n                }\r\n            }\r\n            function moveOnce(boundToLine) {\r\n                if (ch == (dir < 0 ? 0 : lineObj.text.length)) {\r\n                    if (!boundToLine && findNextLine()) ch = dir < 0 ? lineObj.text.length : 0;\r\n                    else return false;\r\n                } else ch += dir;\r\n                return true;\r\n            }\r\n            if (unit == \"char\") moveOnce();\r\n            else if (unit == \"column\") moveOnce(true);\r\n            else if (unit == \"word\") {\r\n                var sawWord = false;\r\n                for (;;) {\r\n                    if (dir < 0) if (!moveOnce()) break;\r\n                    if (isWordChar(lineObj.text.charAt(ch))) sawWord = true;\r\n                    else if (sawWord) {if (dir < 0) {dir = 1; moveOnce();} break;}\r\n                    if (dir > 0) if (!moveOnce()) break;\r\n                }\r\n            }\r\n            return {line: line, ch: ch};\r\n        }\r\n        function moveH(dir, unit) {\r\n            var pos = dir < 0 ? sel.from : sel.to;\r\n            if (shiftSelecting || posEq(sel.from, sel.to)) pos = findPosH(dir, unit);\r\n            setCursor(pos.line, pos.ch, true);\r\n        }\r\n        function deleteH(dir, unit) {\r\n            if (!posEq(sel.from, sel.to)) replaceRange(\"\", sel.from, sel.to);\r\n            else if (dir < 0) replaceRange(\"\", findPosH(dir, unit), sel.to);\r\n            else replaceRange(\"\", sel.from, findPosH(dir, unit));\r\n            userSelChange = true;\r\n        }\r\n        var goalColumn = null;\r\n        function moveV(dir, unit) {\r\n            var dist = 0, pos = localCoords(sel.inverted ? sel.from : sel.to, true);\r\n            if (goalColumn != null) pos.x = goalColumn;\r\n            if (unit == \"page\") dist = scroller.clientHeight;\r\n            else if (unit == \"line\") dist = textHeight();\r\n            var target = coordsChar(pos.x, pos.y + dist * dir + 2);\r\n            setCursor(target.line, target.ch, true);\r\n            goalColumn = pos.x;\r\n        }\r\n\r\n        function selectWordAt(pos) {\r\n            var line = getLine(pos.line).text;\r\n            var start = pos.ch, end = pos.ch;\r\n            while (start > 0 && isWordChar(line.charAt(start - 1))) --start;\r\n            while (end < line.length && isWordChar(line.charAt(end))) ++end;\r\n            setSelectionUser({line: pos.line, ch: start}, {line: pos.line, ch: end});\r\n        }\r\n        function selectLine(line) {\r\n            setSelectionUser({line: line, ch: 0}, {line: line, ch: getLine(line).text.length});\r\n        }\r\n        function indentSelected(mode) {\r\n            if (posEq(sel.from, sel.to)) return indentLine(sel.from.line, mode);\r\n            var e = sel.to.line - (sel.to.ch ? 0 : 1);\r\n            for (var i = sel.from.line; i <= e; ++i) indentLine(i, mode);\r\n        }\r\n\r\n        function indentLine(n, how) {\r\n            if (!how) how = \"add\";\r\n            if (how == \"smart\") {\r\n                if (!mode.indent) how = \"prev\";\r\n                else var state = getStateBefore(n);\r\n            }\r\n\r\n            var line = getLine(n), curSpace = line.indentation(options.tabSize),\r\n                curSpaceString = line.text.match(/^\\s*/)[0], indentation;\r\n            if (how == \"prev\") {\r\n                if (n) indentation = getLine(n-1).indentation(options.tabSize);\r\n                else indentation = 0;\r\n            }\r\n            else if (how == \"smart\") indentation = mode.indent(state, line.text.slice(curSpaceString.length), line.text);\r\n            else if (how == \"add\") indentation = curSpace + options.indentUnit;\r\n            else if (how == \"subtract\") indentation = curSpace - options.indentUnit;\r\n            indentation = Math.max(0, indentation);\r\n            var diff = indentation - curSpace;\r\n\r\n            if (!diff) {\r\n                if (sel.from.line != n && sel.to.line != n) return;\r\n                var indentString = curSpaceString;\r\n            }\r\n            else {\r\n                var indentString = \"\", pos = 0;\r\n                if (options.indentWithTabs)\r\n                    for (var i = Math.floor(indentation / options.tabSize); i; --i) {pos += options.tabSize; indentString += \"\\t\";}\r\n                while (pos < indentation) {++pos; indentString += \" \";}\r\n            }\r\n\r\n            replaceRange(indentString, {line: n, ch: 0}, {line: n, ch: curSpaceString.length});\r\n        }\r\n\r\n        function loadMode() {\r\n            mode = CodeMirror.getMode(options, options.mode);\r\n            doc.iter(0, doc.size, function(line) { line.stateAfter = null; });\r\n            work = [0];\r\n            startWorker();\r\n        }\r\n        function gutterChanged() {\r\n            var visible = options.gutter || options.lineNumbers;\r\n            gutter.style.display = visible ? \"\" : \"none\";\r\n            if (visible) gutterDirty = true;\r\n            else lineDiv.parentNode.style.marginLeft = 0;\r\n        }\r\n        function wrappingChanged(from, to) {\r\n            if (options.lineWrapping) {\r\n                wrapper.className += \" CodeMirror-wrap\";\r\n                var perLine = scroller.clientWidth / charWidth() - 3;\r\n                doc.iter(0, doc.size, function(line) {\r\n                    if (line.hidden) return;\r\n                    var guess = Math.ceil(line.text.length / perLine) || 1;\r\n                    if (guess != 1) updateLineHeight(line, guess);\r\n                });\r\n                lineSpace.style.width = code.style.width = \"\";\r\n            } else {\r\n                wrapper.className = wrapper.className.replace(\" CodeMirror-wrap\", \"\");\r\n                maxWidth = null; maxLine = \"\";\r\n                doc.iter(0, doc.size, function(line) {\r\n                    if (line.height != 1 && !line.hidden) updateLineHeight(line, 1);\r\n                    if (line.text.length > maxLine.length) maxLine = line.text;\r\n                });\r\n            }\r\n            changes.push({from: 0, to: doc.size});\r\n        }\r\n        function computeTabText() {\r\n            for (var str = '<span class=\"cm-tab\">', i = 0; i < options.tabSize; ++i) str += \" \";\r\n            return str + \"</span>\";\r\n        }\r\n        function tabsChanged() {\r\n            tabText = computeTabText();\r\n            updateDisplay(true);\r\n        }\r\n        function themeChanged() {\r\n            scroller.className = scroller.className.replace(/\\s*cm-s-\\w+/g, \"\") +\r\n                options.theme.replace(/(^|\\s)\\s*/g, \" cm-s-\");\r\n        }\r\n\r\n        function TextMarker() { this.set = []; }\r\n        TextMarker.prototype.clear = operation(function() {\r\n            var min = Infinity, max = -Infinity;\r\n            for (var i = 0, e = this.set.length; i < e; ++i) {\r\n                var line = this.set[i], mk = line.marked;\r\n                if (!mk || !line.parent) continue;\r\n                var lineN = lineNo(line);\r\n                min = Math.min(min, lineN); max = Math.max(max, lineN);\r\n                for (var j = 0; j < mk.length; ++j)\r\n                    if (mk[j].set == this.set) mk.splice(j--, 1);\r\n            }\r\n            if (min != Infinity)\r\n                changes.push({from: min, to: max + 1});\r\n        });\r\n        TextMarker.prototype.find = function() {\r\n            var from, to;\r\n            for (var i = 0, e = this.set.length; i < e; ++i) {\r\n                var line = this.set[i], mk = line.marked;\r\n                for (var j = 0; j < mk.length; ++j) {\r\n                    var mark = mk[j];\r\n                    if (mark.set == this.set) {\r\n                        if (mark.from != null || mark.to != null) {\r\n                            var found = lineNo(line);\r\n                            if (found != null) {\r\n                                if (mark.from != null) from = {line: found, ch: mark.from};\r\n                                if (mark.to != null) to = {line: found, ch: mark.to};\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n            return {from: from, to: to};\r\n        };\r\n\r\n        function markText(from, to, className) {\r\n            from = clipPos(from); to = clipPos(to);\r\n            var tm = new TextMarker();\r\n            function add(line, from, to, className) {\r\n                getLine(line).addMark(new MarkedText(from, to, className, tm.set));\r\n            }\r\n            if (from.line == to.line) add(from.line, from.ch, to.ch, className);\r\n            else {\r\n                add(from.line, from.ch, null, className);\r\n                for (var i = from.line + 1, e = to.line; i < e; ++i)\r\n                    add(i, null, null, className);\r\n                add(to.line, null, to.ch, className);\r\n            }\r\n            changes.push({from: from.line, to: to.line + 1});\r\n            return tm;\r\n        }\r\n\r\n        function setBookmark(pos) {\r\n            pos = clipPos(pos);\r\n            var bm = new Bookmark(pos.ch);\r\n            getLine(pos.line).addMark(bm);\r\n            return bm;\r\n        }\r\n\r\n        function addGutterMarker(line, text, className) {\r\n            if (typeof line == \"number\") line = getLine(clipLine(line));\r\n            line.gutterMarker = {text: text, style: className};\r\n            gutterDirty = true;\r\n            return line;\r\n        }\r\n        function removeGutterMarker(line) {\r\n            if (typeof line == \"number\") line = getLine(clipLine(line));\r\n            line.gutterMarker = null;\r\n            gutterDirty = true;\r\n        }\r\n\r\n        function changeLine(handle, op) {\r\n            var no = handle, line = handle;\r\n            if (typeof handle == \"number\") line = getLine(clipLine(handle));\r\n            else no = lineNo(handle);\r\n            if (no == null) return null;\r\n            if (op(line, no)) changes.push({from: no, to: no + 1});\r\n            else return null;\r\n            return line;\r\n        }\r\n        function setLineClass(handle, className) {\r\n            return changeLine(handle, function(line) {\r\n                if (line.className != className) {\r\n                    line.className = className;\r\n                    return true;\r\n                }\r\n            });\r\n        }\r\n        function setLineHidden(handle, hidden) {\r\n            return changeLine(handle, function(line, no) {\r\n                if (line.hidden != hidden) {\r\n                    line.hidden = hidden;\r\n                    updateLineHeight(line, hidden ? 0 : 1);\r\n                    if (hidden && (sel.from.line == no || sel.to.line == no))\r\n                        setSelection(skipHidden(sel.from, sel.from.line, sel.from.ch),\r\n                            skipHidden(sel.to, sel.to.line, sel.to.ch));\r\n                    return (gutterDirty = true);\r\n                }\r\n            });\r\n        }\r\n\r\n        function lineInfo(line) {\r\n            if (typeof line == \"number\") {\r\n                if (!isLine(line)) return null;\r\n                var n = line;\r\n                line = getLine(line);\r\n                if (!line) return null;\r\n            }\r\n            else {\r\n                var n = lineNo(line);\r\n                if (n == null) return null;\r\n            }\r\n            var marker = line.gutterMarker;\r\n            return {line: n, handle: line, text: line.text, markerText: marker && marker.text,\r\n                markerClass: marker && marker.style, lineClass: line.className};\r\n        }\r\n\r\n        function stringWidth(str) {\r\n            measure.innerHTML = \"<pre><span>x</span></pre>\";\r\n            measure.firstChild.firstChild.firstChild.nodeValue = str;\r\n            return measure.firstChild.firstChild.offsetWidth || 10;\r\n        }\r\n        // These are used to go from pixel positions to character\r\n        // positions, taking varying character widths into account.\r\n        function charFromX(line, x) {\r\n            if (x <= 0) return 0;\r\n            var lineObj = getLine(line), text = lineObj.text;\r\n            function getX(len) {\r\n                measure.innerHTML = \"<pre><span>\" + lineObj.getHTML(null, null, false, tabText, len) + \"</span></pre>\";\r\n                return measure.firstChild.firstChild.offsetWidth;\r\n            }\r\n            var from = 0, fromX = 0, to = text.length, toX;\r\n            // Guess a suitable upper bound for our search.\r\n            var estimated = Math.min(to, Math.ceil(x / charWidth()));\r\n            for (;;) {\r\n                var estX = getX(estimated);\r\n                if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2));\r\n                else {toX = estX; to = estimated; break;}\r\n            }\r\n            if (x > toX) return to;\r\n            // Try to guess a suitable lower bound as well.\r\n            estimated = Math.floor(to * 0.8); estX = getX(estimated);\r\n            if (estX < x) {from = estimated; fromX = estX;}\r\n            // Do a binary search between these bounds.\r\n            for (;;) {\r\n                if (to - from <= 1) return (toX - x > x - fromX) ? from : to;\r\n                var middle = Math.ceil((from + to) / 2), middleX = getX(middle);\r\n                if (middleX > x) {to = middle; toX = middleX;}\r\n                else {from = middle; fromX = middleX;}\r\n            }\r\n        }\r\n\r\n        var tempId = Math.floor(Math.random() * 0xffffff).toString(16);\r\n        function measureLine(line, ch) {\r\n            var extra = \"\";\r\n            // Include extra text at the end to make sure the measured line is wrapped in the right way.\r\n            if (options.lineWrapping) {\r\n                var end = line.text.indexOf(\" \", ch + 2);\r\n                extra = htmlEscape(line.text.slice(ch + 1, end < 0 ? line.text.length : end + (ie ? 5 : 0)));\r\n            }\r\n            measure.innerHTML = \"<pre>\" + line.getHTML(null, null, false, tabText, ch) +\r\n                '<span id=\"CodeMirror-temp-' + tempId + '\">' + htmlEscape(line.text.charAt(ch) || \" \") + \"</span>\" +\r\n                extra + \"</pre>\";\r\n            var elt = document.getElementById(\"CodeMirror-temp-\" + tempId);\r\n            var top = elt.offsetTop, left = elt.offsetLeft;\r\n            // Older IEs report zero offsets for spans directly after a wrap\r\n            if (ie && ch && top == 0 && left == 0) {\r\n                var backup = document.createElement(\"span\");\r\n                backup.innerHTML = \"x\";\r\n                elt.parentNode.insertBefore(backup, elt.nextSibling);\r\n                top = backup.offsetTop;\r\n            }\r\n            return {top: top, left: left};\r\n        }\r\n        function localCoords(pos, inLineWrap) {\r\n            var x, lh = textHeight(), y = lh * (heightAtLine(doc, pos.line) - (inLineWrap ? displayOffset : 0));\r\n            if (pos.ch == 0) x = 0;\r\n            else {\r\n                var sp = measureLine(getLine(pos.line), pos.ch);\r\n                x = sp.left;\r\n                if (options.lineWrapping) y += Math.max(0, sp.top);\r\n            }\r\n            return {x: x, y: y, yBot: y + lh};\r\n        }\r\n        // Coords must be lineSpace-local\r\n        function coordsChar(x, y) {\r\n            if (y < 0) y = 0;\r\n            var th = textHeight(), cw = charWidth(), heightPos = displayOffset + Math.floor(y / th);\r\n            var lineNo = lineAtHeight(doc, heightPos);\r\n            if (lineNo >= doc.size) return {line: doc.size - 1, ch: getLine(doc.size - 1).text.length};\r\n            var lineObj = getLine(lineNo), text = lineObj.text;\r\n            var tw = options.lineWrapping, innerOff = tw ? heightPos - heightAtLine(doc, lineNo) : 0;\r\n            if (x <= 0 && innerOff == 0) return {line: lineNo, ch: 0};\r\n            function getX(len) {\r\n                var sp = measureLine(lineObj, len);\r\n                if (tw) {\r\n                    var off = Math.round(sp.top / th);\r\n                    return Math.max(0, sp.left + (off - innerOff) * scroller.clientWidth);\r\n                }\r\n                return sp.left;\r\n            }\r\n            var from = 0, fromX = 0, to = text.length, toX;\r\n            // Guess a suitable upper bound for our search.\r\n            var estimated = Math.min(to, Math.ceil((x + innerOff * scroller.clientWidth * .9) / cw));\r\n            for (;;) {\r\n                var estX = getX(estimated);\r\n                if (estX <= x && estimated < to) estimated = Math.min(to, Math.ceil(estimated * 1.2));\r\n                else {toX = estX; to = estimated; break;}\r\n            }\r\n            if (x > toX) return {line: lineNo, ch: to};\r\n            // Try to guess a suitable lower bound as well.\r\n            estimated = Math.floor(to * 0.8); estX = getX(estimated);\r\n            if (estX < x) {from = estimated; fromX = estX;}\r\n            // Do a binary search between these bounds.\r\n            for (;;) {\r\n                if (to - from <= 1) return {line: lineNo, ch: (toX - x > x - fromX) ? from : to};\r\n                var middle = Math.ceil((from + to) / 2), middleX = getX(middle);\r\n                if (middleX > x) {to = middle; toX = middleX;}\r\n                else {from = middle; fromX = middleX;}\r\n            }\r\n        }\r\n        function pageCoords(pos) {\r\n            var local = localCoords(pos, true), off = eltOffset(lineSpace);\r\n            return {x: off.left + local.x, y: off.top + local.y, yBot: off.top + local.yBot};\r\n        }\r\n\r\n        var cachedHeight, cachedHeightFor, measureText;\r\n        function textHeight() {\r\n            if (measureText == null) {\r\n                measureText = \"<pre>\";\r\n                for (var i = 0; i < 49; ++i) measureText += \"x<br/>\";\r\n                measureText += \"x</pre>\";\r\n            }\r\n            var offsetHeight = lineDiv.clientHeight;\r\n            if (offsetHeight == cachedHeightFor) return cachedHeight;\r\n            cachedHeightFor = offsetHeight;\r\n            measure.innerHTML = measureText;\r\n            cachedHeight = measure.firstChild.offsetHeight / 50 || 1;\r\n            measure.innerHTML = \"\";\r\n            return cachedHeight;\r\n        }\r\n        var cachedWidth, cachedWidthFor = 0;\r\n        function charWidth() {\r\n            if (scroller.clientWidth == cachedWidthFor) return cachedWidth;\r\n            cachedWidthFor = scroller.clientWidth;\r\n            return (cachedWidth = stringWidth(\"x\"));\r\n        }\r\n        function paddingTop() {return lineSpace.offsetTop;}\r\n        function paddingLeft() {return lineSpace.offsetLeft;}\r\n\r\n        function posFromMouse(e, liberal) {\r\n            var offW = eltOffset(scroller, true), x, y;\r\n            // Fails unpredictably on IE[67] when mouse is dragged around quickly.\r\n            try { x = e.clientX; y = e.clientY; } catch (e) { return null; }\r\n            // This is a mess of a heuristic to try and determine whether a\r\n            // scroll-bar was clicked or not, and to return null if one was\r\n            // (and !liberal).\r\n            if (!liberal && (x - offW.left > scroller.clientWidth || y - offW.top > scroller.clientHeight))\r\n                return null;\r\n            var offL = eltOffset(lineSpace, true);\r\n            return coordsChar(x - offL.left, y - offL.top);\r\n        }\r\n        function onContextMenu(e) {\r\n            var pos = posFromMouse(e);\r\n            if (!pos || window.opera) return; // Opera is difficult.\r\n            if (posEq(sel.from, sel.to) || posLess(pos, sel.from) || !posLess(pos, sel.to))\r\n                operation(setCursor)(pos.line, pos.ch);\r\n\r\n            var oldCSS = input.style.cssText;\r\n            inputDiv.style.position = \"absolute\";\r\n            input.style.cssText = \"position: fixed; width: 30px; height: 30px; top: \" + (e.clientY - 5) +\r\n                \"px; left: \" + (e.clientX - 5) + \"px; z-index: 1000; background: white; \" +\r\n                \"border-width: 0; outline: none; overflow: hidden; opacity: .05; filter: alpha(opacity=5);\";\r\n            leaveInputAlone = true;\r\n            var val = input.value = getSelection();\r\n            focusInput();\r\n            input.select();\r\n            function rehide() {\r\n                var newVal = splitLines(input.value).join(\"\\n\");\r\n                if (newVal != val) operation(replaceSelection)(newVal, \"end\");\r\n                inputDiv.style.position = \"relative\";\r\n                input.style.cssText = oldCSS;\r\n                leaveInputAlone = false;\r\n                resetInput(true);\r\n                slowPoll();\r\n            }\r\n\r\n            if (gecko) {\r\n                e_stop(e);\r\n                var mouseup = connect(window, \"mouseup\", function() {\r\n                    mouseup();\r\n                    setTimeout(rehide, 20);\r\n                }, true);\r\n            }\r\n            else {\r\n                setTimeout(rehide, 50);\r\n            }\r\n        }\r\n\r\n        // Cursor-blinking\r\n        function restartBlink() {\r\n            clearInterval(blinker);\r\n            var on = true;\r\n            cursor.style.visibility = \"\";\r\n            blinker = setInterval(function() {\r\n                cursor.style.visibility = (on = !on) ? \"\" : \"hidden\";\r\n            }, 650);\r\n        }\r\n\r\n        var matching = {\"(\": \")>\", \")\": \"(<\", \"[\": \"]>\", \"]\": \"[<\", \"{\": \"}>\", \"}\": \"{<\"};\r\n        function matchBrackets(autoclear) {\r\n            var head = sel.inverted ? sel.from : sel.to, line = getLine(head.line), pos = head.ch - 1;\r\n            var match = (pos >= 0 && matching[line.text.charAt(pos)]) || matching[line.text.charAt(++pos)];\r\n            if (!match) return;\r\n            var ch = match.charAt(0), forward = match.charAt(1) == \">\", d = forward ? 1 : -1, st = line.styles;\r\n            for (var off = pos + 1, i = 0, e = st.length; i < e; i+=2)\r\n                if ((off -= st[i].length) <= 0) {var style = st[i+1]; break;}\r\n\r\n            var stack = [line.text.charAt(pos)], re = /[(){}[\\]]/;\r\n            function scan(line, from, to) {\r\n                if (!line.text) return;\r\n                var st = line.styles, pos = forward ? 0 : line.text.length - 1, cur;\r\n                for (var i = forward ? 0 : st.length - 2, e = forward ? st.length : -2; i != e; i += 2*d) {\r\n                    var text = st[i];\r\n                    if (st[i+1] != null && st[i+1] != style) {pos += d * text.length; continue;}\r\n                    for (var j = forward ? 0 : text.length - 1, te = forward ? text.length : -1; j != te; j += d, pos+=d) {\r\n                        if (pos >= from && pos < to && re.test(cur = text.charAt(j))) {\r\n                            var match = matching[cur];\r\n                            if (match.charAt(1) == \">\" == forward) stack.push(cur);\r\n                            else if (stack.pop() != match.charAt(0)) return {pos: pos, match: false};\r\n                            else if (!stack.length) return {pos: pos, match: true};\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n            for (var i = head.line, e = forward ? Math.min(i + 100, doc.size) : Math.max(-1, i - 100); i != e; i+=d) {\r\n                var line = getLine(i), first = i == head.line;\r\n                var found = scan(line, first && forward ? pos + 1 : 0, first && !forward ? pos : line.text.length);\r\n                if (found) break;\r\n            }\r\n            if (!found) found = {pos: null, match: false};\r\n            var style = found.match ? \"CodeMirror-matchingbracket\" : \"CodeMirror-nonmatchingbracket\";\r\n            var one = markText({line: head.line, ch: pos}, {line: head.line, ch: pos+1}, style),\r\n                two = found.pos != null && markText({line: i, ch: found.pos}, {line: i, ch: found.pos + 1}, style);\r\n            var clear = operation(function(){one.clear(); two && two.clear();});\r\n            if (autoclear) setTimeout(clear, 800);\r\n            else bracketHighlighted = clear;\r\n        }\r\n\r\n        // Finds the line to start with when starting a parse. Tries to\r\n        // find a line with a stateAfter, so that it can start with a\r\n        // valid state. If that fails, it returns the line with the\r\n        // smallest indentation, which tends to need the least context to\r\n        // parse correctly.\r\n        function findStartLine(n) {\r\n            var minindent, minline;\r\n            for (var search = n, lim = n - 40; search > lim; --search) {\r\n                if (search == 0) return 0;\r\n                var line = getLine(search-1);\r\n                if (line.stateAfter) return search;\r\n                var indented = line.indentation(options.tabSize);\r\n                if (minline == null || minindent > indented) {\r\n                    minline = search - 1;\r\n                    minindent = indented;\r\n                }\r\n            }\r\n            return minline;\r\n        }\r\n        function getStateBefore(n) {\r\n            var start = findStartLine(n), state = start && getLine(start-1).stateAfter;\r\n            if (!state) state = startState(mode);\r\n            else state = copyState(mode, state);\r\n            doc.iter(start, n, function(line) {\r\n                line.highlight(mode, state, options.tabSize);\r\n                line.stateAfter = copyState(mode, state);\r\n            });\r\n            if (start < n) changes.push({from: start, to: n});\r\n            if (n < doc.size && !getLine(n).stateAfter) work.push(n);\r\n            return state;\r\n        }\r\n        function highlightLines(start, end) {\r\n            var state = getStateBefore(start);\r\n            doc.iter(start, end, function(line) {\r\n                line.highlight(mode, state, options.tabSize);\r\n                line.stateAfter = copyState(mode, state);\r\n            });\r\n        }\r\n        function highlightWorker() {\r\n            var end = +new Date + options.workTime;\r\n            var foundWork = work.length;\r\n            while (work.length) {\r\n                if (!getLine(showingFrom).stateAfter) var task = showingFrom;\r\n                else var task = work.pop();\r\n                if (task >= doc.size) continue;\r\n                var start = findStartLine(task), state = start && getLine(start-1).stateAfter;\r\n                if (state) state = copyState(mode, state);\r\n                else state = startState(mode);\r\n\r\n                var unchanged = 0, compare = mode.compareStates, realChange = false,\r\n                    i = start, bail = false;\r\n                doc.iter(i, doc.size, function(line) {\r\n                    var hadState = line.stateAfter;\r\n                    if (+new Date > end) {\r\n                        work.push(i);\r\n                        startWorker(options.workDelay);\r\n                        if (realChange) changes.push({from: task, to: i + 1});\r\n                        return (bail = true);\r\n                    }\r\n                    var changed = line.highlight(mode, state, options.tabSize);\r\n                    if (changed) realChange = true;\r\n                    line.stateAfter = copyState(mode, state);\r\n                    if (compare) {\r\n                        if (hadState && compare(hadState, state)) return true;\r\n                    } else {\r\n                        if (changed !== false || !hadState) unchanged = 0;\r\n                        else if (++unchanged > 3 && (!mode.indent || mode.indent(hadState, \"\") == mode.indent(state, \"\")))\r\n                            return true;\r\n                    }\r\n                    ++i;\r\n                });\r\n                if (bail) return;\r\n                if (realChange) changes.push({from: task, to: i + 1});\r\n            }\r\n            if (foundWork && options.onHighlightComplete)\r\n                options.onHighlightComplete(instance);\r\n        }\r\n        function startWorker(time) {\r\n            if (!work.length) return;\r\n            highlight.set(time, operation(highlightWorker));\r\n        }\r\n\r\n        // Operations are used to wrap changes in such a way that each\r\n        // change won't have to update the cursor and display (which would\r\n        // be awkward, slow, and error-prone), but instead updates are\r\n        // batched and then all combined and executed at once.\r\n        function startOperation() {\r\n            updateInput = userSelChange = textChanged = null;\r\n            changes = []; selectionChanged = false; callbacks = [];\r\n        }\r\n        function endOperation() {\r\n            var reScroll = false, updated;\r\n            if (selectionChanged) reScroll = !scrollCursorIntoView();\r\n            if (changes.length) updated = updateDisplay(changes, true);\r\n            else {\r\n                if (selectionChanged) updateCursor();\r\n                if (gutterDirty) updateGutter();\r\n            }\r\n            if (reScroll) scrollCursorIntoView();\r\n            if (selectionChanged) {scrollEditorIntoView(); restartBlink();}\r\n\r\n            if (focused && !leaveInputAlone &&\r\n                (updateInput === true || (updateInput !== false && selectionChanged)))\r\n                resetInput(userSelChange);\r\n\r\n            if (selectionChanged && options.matchBrackets)\r\n                setTimeout(operation(function() {\r\n                    if (bracketHighlighted) {bracketHighlighted(); bracketHighlighted = null;}\r\n                    if (posEq(sel.from, sel.to)) matchBrackets(false);\r\n                }), 20);\r\n            var tc = textChanged, cbs = callbacks; // these can be reset by callbacks\r\n            if (selectionChanged && options.onCursorActivity)\r\n                options.onCursorActivity(instance);\r\n            if (tc && options.onChange && instance)\r\n                options.onChange(instance, tc);\r\n            for (var i = 0; i < cbs.length; ++i) cbs[i](instance);\r\n            if (updated && options.onUpdate) options.onUpdate(instance);\r\n        }\r\n        var nestedOperation = 0;\r\n        function operation(f) {\r\n            return function() {\r\n                if (!nestedOperation++) startOperation();\r\n                try {var result = f.apply(this, arguments);}\r\n                finally {if (!--nestedOperation) endOperation();}\r\n                return result;\r\n            };\r\n        }\r\n\r\n        for (var ext in extensions)\r\n            if (extensions.propertyIsEnumerable(ext) &&\r\n                !instance.propertyIsEnumerable(ext))\r\n                instance[ext] = extensions[ext];\r\n        return instance;\r\n    } // (end of function CodeMirror)\r\n\r\n    // The default configuration options.\r\n    CodeMirror.defaults = {\r\n        value: \"\",\r\n        mode: null,\r\n        theme: \"default\",\r\n        indentUnit: 2,\r\n        indentWithTabs: false,\r\n        tabSize: 4,\r\n        keyMap: \"default\",\r\n        extraKeys: null,\r\n        electricChars: true,\r\n        onKeyEvent: null,\r\n        lineWrapping: false,\r\n        lineNumbers: false,\r\n        gutter: false,\r\n        fixedGutter: false,\r\n        firstLineNumber: 1,\r\n        readOnly: false,\r\n        onChange: null,\r\n        onCursorActivity: null,\r\n        onGutterClick: null,\r\n        onHighlightComplete: null,\r\n        onUpdate: null,\r\n        onFocus: null, onBlur: null, onScroll: null,\r\n        matchBrackets: false,\r\n        workTime: 100,\r\n        workDelay: 200,\r\n        pollInterval: 100,\r\n        undoDepth: 40,\r\n        tabindex: null,\r\n        document: window.document\r\n    };\r\n\r\n    var mac = /Mac/.test(navigator.platform);\r\n    var win = /Win/.test(navigator.platform);\r\n\r\n    // Known modes, by name and by MIME\r\n    var modes = {}, mimeModes = {};\r\n    CodeMirror.defineMode = function(name, mode) {\r\n        if (!CodeMirror.defaults.mode && name != \"null\") CodeMirror.defaults.mode = name;\r\n        modes[name] = mode;\r\n    };\r\n    CodeMirror.defineMIME = function(mime, spec) {\r\n        mimeModes[mime] = spec;\r\n    };\r\n    CodeMirror.getMode = function(options, spec) {\r\n        if (typeof spec == \"string\" && mimeModes.hasOwnProperty(spec))\r\n            spec = mimeModes[spec];\r\n        if (typeof spec == \"string\")\r\n            var mname = spec, config = {};\r\n        else if (spec != null)\r\n            var mname = spec.name, config = spec;\r\n        var mfactory = modes[mname];\r\n        if (!mfactory) {\r\n            if (window.console) console.warn(\"No mode \" + mname + \" found, falling back to plain text.\");\r\n            return CodeMirror.getMode(options, \"text/plain\");\r\n        }\r\n        return mfactory(options, config || {});\r\n    };\r\n    CodeMirror.listModes = function() {\r\n        var list = [];\r\n        for (var m in modes)\r\n            if (modes.propertyIsEnumerable(m)) list.push(m);\r\n        return list;\r\n    };\r\n    CodeMirror.listMIMEs = function() {\r\n        var list = [];\r\n        for (var m in mimeModes)\r\n            if (mimeModes.propertyIsEnumerable(m)) list.push({mime: m, mode: mimeModes[m]});\r\n        return list;\r\n    };\r\n\r\n    var extensions = CodeMirror.extensions = {};\r\n    CodeMirror.defineExtension = function(name, func) {\r\n        extensions[name] = func;\r\n    };\r\n\r\n    var commands = CodeMirror.commands = {\r\n        selectAll: function(cm) {cm.setSelection({line: 0, ch: 0}, {line: cm.lineCount() - 1});},\r\n        killLine: function(cm) {\r\n            var from = cm.getCursor(true), to = cm.getCursor(false), sel = !posEq(from, to);\r\n            if (!sel && cm.getLine(from.line).length == from.ch) cm.replaceRange(\"\", from, {line: from.line + 1, ch: 0});\r\n            else cm.replaceRange(\"\", from, sel ? to : {line: from.line});\r\n        },\r\n        deleteLine: function(cm) {var l = cm.getCursor().line; cm.replaceRange(\"\", {line: l, ch: 0}, {line: l});},\r\n        undo: function(cm) {cm.undo();},\r\n        redo: function(cm) {cm.redo();},\r\n        goDocStart: function(cm) {cm.setCursor(0, 0, true);},\r\n        goDocEnd: function(cm) {cm.setSelection({line: cm.lineCount() - 1}, null, true);},\r\n        goLineStart: function(cm) {cm.setCursor(cm.getCursor().line, 0, true);},\r\n        goLineStartSmart: function(cm) {\r\n            var cur = cm.getCursor();\r\n            var text = cm.getLine(cur.line), firstNonWS = Math.max(0, text.search(/\\S/));\r\n            cm.setCursor(cur.line, cur.ch <= firstNonWS && cur.ch ? 0 : firstNonWS, true);\r\n        },\r\n        goLineEnd: function(cm) {cm.setSelection({line: cm.getCursor().line}, null, true);},\r\n        goLineUp: function(cm) {cm.moveV(-1, \"line\");},\r\n        goLineDown: function(cm) {cm.moveV(1, \"line\");},\r\n        goPageUp: function(cm) {cm.moveV(-1, \"page\");},\r\n        goPageDown: function(cm) {cm.moveV(1, \"page\");},\r\n        goCharLeft: function(cm) {cm.moveH(-1, \"char\");},\r\n        goCharRight: function(cm) {cm.moveH(1, \"char\");},\r\n        goColumnLeft: function(cm) {cm.moveH(-1, \"column\");},\r\n        goColumnRight: function(cm) {cm.moveH(1, \"column\");},\r\n        goWordLeft: function(cm) {cm.moveH(-1, \"word\");},\r\n        goWordRight: function(cm) {cm.moveH(1, \"word\");},\r\n        delCharLeft: function(cm) {cm.deleteH(-1, \"char\");},\r\n        delCharRight: function(cm) {cm.deleteH(1, \"char\");},\r\n        delWordLeft: function(cm) {cm.deleteH(-1, \"word\");},\r\n        delWordRight: function(cm) {cm.deleteH(1, \"word\");},\r\n        indentAuto: function(cm) {cm.indentSelection(\"smart\");},\r\n        indentMore: function(cm) {cm.indentSelection(\"add\");},\r\n        indentLess: function(cm) {cm.indentSelection(\"subtract\");},\r\n        insertTab: function(cm) {cm.replaceSelection(\"\\t\", \"end\");},\r\n        transposeChars: function(cm) {\r\n            var cur = cm.getCursor(), line = cm.getLine(cur.line);\r\n            if (cur.ch > 0 && cur.ch < line.length - 1)\r\n                cm.replaceRange(line.charAt(cur.ch) + line.charAt(cur.ch - 1),\r\n                    {line: cur.line, ch: cur.ch - 1}, {line: cur.line, ch: cur.ch + 1});\r\n        },\r\n        newlineAndIndent: function(cm) {\r\n            cm.replaceSelection(\"\\n\", \"end\");\r\n            cm.indentLine(cm.getCursor().line);\r\n        },\r\n        toggleOverwrite: function(cm) {cm.toggleOverwrite();}\r\n    };\r\n\r\n    var keyMap = CodeMirror.keyMap = {};\r\n    keyMap.basic = {\r\n        \"Left\": \"goCharLeft\", \"Right\": \"goCharRight\", \"Up\": \"goLineUp\", \"Down\": \"goLineDown\",\r\n        \"End\": \"goLineEnd\", \"Home\": \"goLineStartSmart\", \"PageUp\": \"goPageUp\", \"PageDown\": \"goPageDown\",\r\n        \"Delete\": \"delCharRight\", \"Backspace\": \"delCharLeft\", \"Tab\": \"indentMore\", \"Shift-Tab\": \"indentLess\",\r\n        \"Enter\": \"newlineAndIndent\", \"Insert\": \"toggleOverwrite\"\r\n    };\r\n    // Note that the save and find-related commands aren't defined by\r\n    // default. Unknown commands are simply ignored.\r\n    keyMap.pcDefault = {\r\n        \"Ctrl-A\": \"selectAll\", \"Ctrl-D\": \"deleteLine\", \"Ctrl-Z\": \"undo\", \"Shift-Ctrl-Z\": \"redo\", \"Ctrl-Y\": \"redo\",\r\n        \"Ctrl-Home\": \"goDocStart\", \"Alt-Up\": \"goDocStart\", \"Ctrl-End\": \"goDocEnd\", \"Ctrl-Down\": \"goDocEnd\",\r\n        \"Ctrl-Left\": \"goWordLeft\", \"Ctrl-Right\": \"goWordRight\", \"Alt-Left\": \"goLineStart\", \"Alt-Right\": \"goLineEnd\",\r\n        \"Ctrl-Backspace\": \"delWordLeft\", \"Ctrl-Delete\": \"delWordRight\", \"Ctrl-S\": \"save\", \"Ctrl-F\": \"find\",\r\n        \"Ctrl-G\": \"findNext\", \"Shift-Ctrl-G\": \"findPrev\", \"Shift-Ctrl-F\": \"replace\", \"Shift-Ctrl-R\": \"replaceAll\",\r\n        fallthrough: \"basic\"\r\n    };\r\n    keyMap.macDefault = {\r\n        \"Cmd-A\": \"selectAll\", \"Cmd-D\": \"deleteLine\", \"Cmd-Z\": \"undo\", \"Shift-Cmd-Z\": \"redo\", \"Cmd-Y\": \"redo\",\r\n        \"Cmd-Up\": \"goDocStart\", \"Cmd-End\": \"goDocEnd\", \"Cmd-Down\": \"goDocEnd\", \"Alt-Left\": \"goWordLeft\",\r\n        \"Alt-Right\": \"goWordRight\", \"Cmd-Left\": \"goLineStart\", \"Cmd-Right\": \"goLineEnd\", \"Alt-Backspace\": \"delWordLeft\",\r\n        \"Ctrl-Alt-Backspace\": \"delWordRight\", \"Alt-Delete\": \"delWordRight\", \"Cmd-S\": \"save\", \"Cmd-F\": \"find\",\r\n        \"Cmd-G\": \"findNext\", \"Shift-Cmd-G\": \"findPrev\", \"Cmd-Alt-F\": \"replace\", \"Shift-Cmd-Alt-F\": \"replaceAll\",\r\n        fallthrough: [\"basic\", \"emacsy\"]\r\n    };\r\n    keyMap[\"default\"] = mac ? keyMap.macDefault : keyMap.pcDefault;\r\n    keyMap.emacsy = {\r\n        \"Ctrl-F\": \"goCharRight\", \"Ctrl-B\": \"goCharLeft\", \"Ctrl-P\": \"goLineUp\", \"Ctrl-N\": \"goLineDown\",\r\n        \"Alt-F\": \"goWordRight\", \"Alt-B\": \"goWordLeft\", \"Ctrl-A\": \"goLineStart\", \"Ctrl-E\": \"goLineEnd\",\r\n        \"Ctrl-V\": \"goPageUp\", \"Shift-Ctrl-V\": \"goPageDown\", \"Ctrl-D\": \"delCharRight\", \"Ctrl-H\": \"delCharLeft\",\r\n        \"Alt-D\": \"delWordRight\", \"Alt-Backspace\": \"delWordLeft\", \"Ctrl-K\": \"killLine\", \"Ctrl-T\": \"transposeChars\"\r\n    };\r\n\r\n    function lookupKey(name, extraMap, map) {\r\n        function lookup(name, map, ft) {\r\n            var found = map[name];\r\n            if (found != null) return found;\r\n            if (ft == null) ft = map.fallthrough;\r\n            if (ft == null) return map.catchall;\r\n            if (typeof ft == \"string\") return lookup(name, keyMap[ft]);\r\n            for (var i = 0, e = ft.length; i < e; ++i) {\r\n                found = lookup(name, keyMap[ft[i]]);\r\n                if (found != null) return found;\r\n            }\r\n            return null;\r\n        }\r\n        return extraMap ? lookup(name, extraMap, map) : lookup(name, keyMap[map]);\r\n    }\r\n    function isModifierKey(event) {\r\n        var name = keyNames[event.keyCode];\r\n        return name == \"Ctrl\" || name == \"Alt\" || name == \"Shift\" || name == \"Mod\";\r\n    }\r\n\r\n    CodeMirror.fromTextArea = function(textarea, options) {\r\n        if (!options) options = {};\r\n        options.value = textarea.value;\r\n        if (!options.tabindex && textarea.tabindex)\r\n            options.tabindex = textarea.tabindex;\r\n\r\n        function save() {textarea.value = instance.getValue();}\r\n        if (textarea.form) {\r\n            // Deplorable hack to make the submit method do the right thing.\r\n            var rmSubmit = connect(textarea.form, \"submit\", save, true);\r\n            if (typeof textarea.form.submit == \"function\") {\r\n                var realSubmit = textarea.form.submit;\r\n                function wrappedSubmit() {\r\n                    save();\r\n                    textarea.form.submit = realSubmit;\r\n                    textarea.form.submit();\r\n                    textarea.form.submit = wrappedSubmit;\r\n                }\r\n                textarea.form.submit = wrappedSubmit;\r\n            }\r\n        }\r\n\r\n        textarea.style.display = \"none\";\r\n        var instance = CodeMirror(function(node) {\r\n            textarea.parentNode.insertBefore(node, textarea.nextSibling);\r\n        }, options);\r\n        instance.save = save;\r\n        instance.getTextArea = function() { return textarea; };\r\n        instance.toTextArea = function() {\r\n            save();\r\n            textarea.parentNode.removeChild(instance.getWrapperElement());\r\n            textarea.style.display = \"\";\r\n            if (textarea.form) {\r\n                rmSubmit();\r\n                if (typeof textarea.form.submit == \"function\")\r\n                    textarea.form.submit = realSubmit;\r\n            }\r\n        };\r\n        return instance;\r\n    };\r\n\r\n    // Utility functions for working with state. Exported because modes\r\n    // sometimes need to do this.\r\n    function copyState(mode, state) {\r\n        if (state === true) return state;\r\n        if (mode.copyState) return mode.copyState(state);\r\n        var nstate = {};\r\n        for (var n in state) {\r\n            var val = state[n];\r\n            if (val instanceof Array) val = val.concat([]);\r\n            nstate[n] = val;\r\n        }\r\n        return nstate;\r\n    }\r\n    CodeMirror.copyState = copyState;\r\n    function startState(mode, a1, a2) {\r\n        return mode.startState ? mode.startState(a1, a2) : true;\r\n    }\r\n    CodeMirror.startState = startState;\r\n\r\n    // The character stream used by a mode's parser.\r\n    function StringStream(string, tabSize) {\r\n        this.pos = this.start = 0;\r\n        this.string = string;\r\n        this.tabSize = tabSize || 8;\r\n    }\r\n    StringStream.prototype = {\r\n        eol: function() {return this.pos >= this.string.length;},\r\n        sol: function() {return this.pos == 0;},\r\n        peek: function() {return this.string.charAt(this.pos);},\r\n        next: function() {\r\n            if (this.pos < this.string.length)\r\n                return this.string.charAt(this.pos++);\r\n        },\r\n        eat: function(match) {\r\n            var ch = this.string.charAt(this.pos);\r\n            if (typeof match == \"string\") var ok = ch == match;\r\n            else var ok = ch && (match.test ? match.test(ch) : match(ch));\r\n            if (ok) {++this.pos; return ch;}\r\n        },\r\n        eatWhile: function(match) {\r\n            var start = this.pos;\r\n            while (this.eat(match)){}\r\n            return this.pos > start;\r\n        },\r\n        eatSpace: function() {\r\n            var start = this.pos;\r\n            while (/[\\s\\u00a0]/.test(this.string.charAt(this.pos))) ++this.pos;\r\n            return this.pos > start;\r\n        },\r\n        skipToEnd: function() {this.pos = this.string.length;},\r\n        skipTo: function(ch) {\r\n            var found = this.string.indexOf(ch, this.pos);\r\n            if (found > -1) {this.pos = found; return true;}\r\n        },\r\n        backUp: function(n) {this.pos -= n;},\r\n        column: function() {return countColumn(this.string, this.start, this.tabSize);},\r\n        indentation: function() {return countColumn(this.string, null, this.tabSize);},\r\n        match: function(pattern, consume, caseInsensitive) {\r\n            if (typeof pattern == \"string\") {\r\n                function cased(str) {return caseInsensitive ? str.toLowerCase() : str;}\r\n                if (cased(this.string).indexOf(cased(pattern), this.pos) == this.pos) {\r\n                    if (consume !== false) this.pos += pattern.length;\r\n                    return true;\r\n                }\r\n            }\r\n            else {\r\n                var match = this.string.slice(this.pos).match(pattern);\r\n                if (match && consume !== false) this.pos += match[0].length;\r\n                return match;\r\n            }\r\n        },\r\n        current: function(){return this.string.slice(this.start, this.pos);}\r\n    };\r\n    CodeMirror.StringStream = StringStream;\r\n\r\n    function MarkedText(from, to, className, set) {\r\n        this.from = from; this.to = to; this.style = className; this.set = set;\r\n    }\r\n    MarkedText.prototype = {\r\n        attach: function(line) { this.set.push(line); },\r\n        detach: function(line) {\r\n            var ix = indexOf(this.set, line);\r\n            if (ix > -1) this.set.splice(ix, 1);\r\n        },\r\n        split: function(pos, lenBefore) {\r\n            if (this.to <= pos && this.to != null) return null;\r\n            var from = this.from < pos || this.from == null ? null : this.from - pos + lenBefore;\r\n            var to = this.to == null ? null : this.to - pos + lenBefore;\r\n            return new MarkedText(from, to, this.style, this.set);\r\n        },\r\n        dup: function() { return new MarkedText(null, null, this.style, this.set); },\r\n        clipTo: function(fromOpen, from, toOpen, to, diff) {\r\n            if (this.from != null && this.from >= from)\r\n                this.from = Math.max(to, this.from) + diff;\r\n            if (this.to != null && this.to > from)\r\n                this.to = to < this.to ? this.to + diff : from;\r\n            if (fromOpen && to > this.from && (to < this.to || this.to == null))\r\n                this.from = null;\r\n            if (toOpen && (from < this.to || this.to == null) && (from > this.from || this.from == null))\r\n                this.to = null;\r\n        },\r\n        isDead: function() { return this.from != null && this.to != null && this.from >= this.to; },\r\n        sameSet: function(x) { return this.set == x.set; }\r\n    };\r\n\r\n    function Bookmark(pos) {\r\n        this.from = pos; this.to = pos; this.line = null;\r\n    }\r\n    Bookmark.prototype = {\r\n        attach: function(line) { this.line = line; },\r\n        detach: function(line) { if (this.line == line) this.line = null; },\r\n        split: function(pos, lenBefore) {\r\n            if (pos < this.from) {\r\n                this.from = this.to = (this.from - pos) + lenBefore;\r\n                return this;\r\n            }\r\n        },\r\n        isDead: function() { return this.from > this.to; },\r\n        clipTo: function(fromOpen, from, toOpen, to, diff) {\r\n            if ((fromOpen || from < this.from) && (toOpen || to > this.to)) {\r\n                this.from = 0; this.to = -1;\r\n            } else if (this.from > from) {\r\n                this.from = this.to = Math.max(to, this.from) + diff;\r\n            }\r\n        },\r\n        sameSet: function(x) { return false; },\r\n        find: function() {\r\n            if (!this.line || !this.line.parent) return null;\r\n            return {line: lineNo(this.line), ch: this.from};\r\n        },\r\n        clear: function() {\r\n            if (this.line) {\r\n                var found = indexOf(this.line.marked, this);\r\n                if (found != -1) this.line.marked.splice(found, 1);\r\n                this.line = null;\r\n            }\r\n        }\r\n    };\r\n\r\n    // Line objects. These hold state related to a line, including\r\n    // highlighting info (the styles array).\r\n    function Line(text, styles) {\r\n        this.styles = styles || [text, null];\r\n        this.text = text;\r\n        this.height = 1;\r\n        this.marked = this.gutterMarker = this.className = this.handlers = null;\r\n        this.stateAfter = this.parent = this.hidden = null;\r\n    }\r\n    Line.inheritMarks = function(text, orig) {\r\n        var ln = new Line(text), mk = orig && orig.marked;\r\n        if (mk) {\r\n            for (var i = 0; i < mk.length; ++i) {\r\n                if (mk[i].to == null && mk[i].style) {\r\n                    var newmk = ln.marked || (ln.marked = []), mark = mk[i];\r\n                    var nmark = mark.dup(); newmk.push(nmark); nmark.attach(ln);\r\n                }\r\n            }\r\n        }\r\n        return ln;\r\n    }\r\n    Line.prototype = {\r\n        // Replace a piece of a line, keeping the styles around it intact.\r\n        replace: function(from, to_, text) {\r\n            var st = [], mk = this.marked, to = to_ == null ? this.text.length : to_;\r\n            copyStyles(0, from, this.styles, st);\r\n            if (text) st.push(text, null);\r\n            copyStyles(to, this.text.length, this.styles, st);\r\n            this.styles = st;\r\n            this.text = this.text.slice(0, from) + text + this.text.slice(to);\r\n            this.stateAfter = null;\r\n            if (mk) {\r\n                var diff = text.length - (to - from);\r\n                for (var i = 0, mark = mk[i]; i < mk.length; ++i) {\r\n                    mark.clipTo(from == null, from || 0, to_ == null, to, diff);\r\n                    if (mark.isDead()) {mark.detach(this); mk.splice(i--, 1);}\r\n                }\r\n            }\r\n        },\r\n        // Split a part off a line, keeping styles and markers intact.\r\n        split: function(pos, textBefore) {\r\n            var st = [textBefore, null], mk = this.marked;\r\n            copyStyles(pos, this.text.length, this.styles, st);\r\n            var taken = new Line(textBefore + this.text.slice(pos), st);\r\n            if (mk) {\r\n                for (var i = 0; i < mk.length; ++i) {\r\n                    var mark = mk[i];\r\n                    var newmark = mark.split(pos, textBefore.length);\r\n                    if (newmark) {\r\n                        if (!taken.marked) taken.marked = [];\r\n                        taken.marked.push(newmark); newmark.attach(taken);\r\n                    }\r\n                }\r\n            }\r\n            return taken;\r\n        },\r\n        append: function(line) {\r\n            var mylen = this.text.length, mk = line.marked, mymk = this.marked;\r\n            this.text += line.text;\r\n            copyStyles(0, line.text.length, line.styles, this.styles);\r\n            if (mymk) {\r\n                for (var i = 0; i < mymk.length; ++i)\r\n                    if (mymk[i].to == null) mymk[i].to = mylen;\r\n            }\r\n            if (mk && mk.length) {\r\n                if (!mymk) this.marked = mymk = [];\r\n                outer: for (var i = 0; i < mk.length; ++i) {\r\n                    var mark = mk[i];\r\n                    if (!mark.from) {\r\n                        for (var j = 0; j < mymk.length; ++j) {\r\n                            var mymark = mymk[j];\r\n                            if (mymark.to == mylen && mymark.sameSet(mark)) {\r\n                                mymark.to = mark.to == null ? null : mark.to + mylen;\r\n                                if (mymark.isDead()) {\r\n                                    mymark.detach(this);\r\n                                    mk.splice(i--, 1);\r\n                                }\r\n                                continue outer;\r\n                            }\r\n                        }\r\n                    }\r\n                    mymk.push(mark);\r\n                    mark.attach(this);\r\n                    mark.from += mylen;\r\n                    if (mark.to != null) mark.to += mylen;\r\n                }\r\n            }\r\n        },\r\n        fixMarkEnds: function(other) {\r\n            var mk = this.marked, omk = other.marked;\r\n            if (!mk) return;\r\n            for (var i = 0; i < mk.length; ++i) {\r\n                var mark = mk[i], close = mark.to == null;\r\n                if (close && omk) {\r\n                    for (var j = 0; j < omk.length; ++j)\r\n                        if (omk[j].sameSet(mark)) {close = false; break;}\r\n                }\r\n                if (close) mark.to = this.text.length;\r\n            }\r\n        },\r\n        fixMarkStarts: function() {\r\n            var mk = this.marked;\r\n            if (!mk) return;\r\n            for (var i = 0; i < mk.length; ++i)\r\n                if (mk[i].from == null) mk[i].from = 0;\r\n        },\r\n        addMark: function(mark) {\r\n            mark.attach(this);\r\n            if (this.marked == null) this.marked = [];\r\n            this.marked.push(mark);\r\n            this.marked.sort(function(a, b){return (a.from || 0) - (b.from || 0);});\r\n        },\r\n        // Run the given mode's parser over a line, update the styles\r\n        // array, which contains alternating fragments of text and CSS\r\n        // classes.\r\n        highlight: function(mode, state, tabSize) {\r\n            var stream = new StringStream(this.text, tabSize), st = this.styles, pos = 0;\r\n            var changed = false, curWord = st[0], prevWord;\r\n            if (this.text == \"\" && mode.blankLine) mode.blankLine(state);\r\n            while (!stream.eol()) {\r\n                var style = mode.token(stream, state);\r\n                var substr = this.text.slice(stream.start, stream.pos);\r\n                stream.start = stream.pos;\r\n                if (pos && st[pos-1] == style)\r\n                    st[pos-2] += substr;\r\n                else if (substr) {\r\n                    if (!changed && (st[pos+1] != style || (pos && st[pos-2] != prevWord))) changed = true;\r\n                    st[pos++] = substr; st[pos++] = style;\r\n                    prevWord = curWord; curWord = st[pos];\r\n                }\r\n                // Give up when line is ridiculously long\r\n                if (stream.pos > 5000) {\r\n                    st[pos++] = this.text.slice(stream.pos); st[pos++] = null;\r\n                    break;\r\n                }\r\n            }\r\n            if (st.length != pos) {st.length = pos; changed = true;}\r\n            if (pos && st[pos-2] != prevWord) changed = true;\r\n            // Short lines with simple highlights return null, and are\r\n            // counted as changed by the driver because they are likely to\r\n            // highlight the same way in various contexts.\r\n            return changed || (st.length < 5 && this.text.length < 10 ? null : false);\r\n        },\r\n        // Fetch the parser token for a given character. Useful for hacks\r\n        // that want to inspect the mode state (say, for completion).\r\n        getTokenAt: function(mode, state, ch) {\r\n            var txt = this.text, stream = new StringStream(txt);\r\n            while (stream.pos < ch && !stream.eol()) {\r\n                stream.start = stream.pos;\r\n                var style = mode.token(stream, state);\r\n            }\r\n            return {start: stream.start,\r\n                end: stream.pos,\r\n                string: stream.current(),\r\n                className: style || null,\r\n                state: state};\r\n        },\r\n        indentation: function(tabSize) {return countColumn(this.text, null, tabSize);},\r\n        // Produces an HTML fragment for the line, taking selection,\r\n        // marking, and highlighting into account.\r\n        getHTML: function(sfrom, sto, includePre, tabText, endAt) {\r\n            var html = [], first = true;\r\n            if (includePre)\r\n                html.push(this.className ? '<pre class=\"' + this.className + '\">': \"<pre>\");\r\n            function span(text, style) {\r\n                if (!text) return;\r\n                // Work around a bug where, in some compat modes, IE ignores leading spaces\r\n                if (first && ie && text.charAt(0) == \" \") text = \"\\u00a0\" + text.slice(1);\r\n                first = false;\r\n                if (style) html.push('<span class=\"', style, '\">', htmlEscape(text).replace(/\\t/g, tabText), \"</span>\");\r\n                else html.push(htmlEscape(text).replace(/\\t/g, tabText));\r\n            }\r\n            var st = this.styles, allText = this.text, marked = this.marked;\r\n            if (sfrom == sto) sfrom = null;\r\n            var len = allText.length;\r\n            if (endAt != null) len = Math.min(endAt, len);\r\n\r\n            if (!allText && endAt == null)\r\n                span(\" \", sfrom != null && sto == null ? \"CodeMirror-selected\" : null);\r\n            else if (!marked && sfrom == null)\r\n                for (var i = 0, ch = 0; ch < len; i+=2) {\r\n                    var str = st[i], style = st[i+1], l = str.length;\r\n                    if (ch + l > len) str = str.slice(0, len - ch);\r\n                    ch += l;\r\n                    span(str, style && \"cm-\" + style);\r\n                }\r\n            else {\r\n                var pos = 0, i = 0, text = \"\", style, sg = 0;\r\n                var markpos = -1, mark = null;\r\n                function nextMark() {\r\n                    if (marked) {\r\n                        markpos += 1;\r\n                        mark = (markpos < marked.length) ? marked[markpos] : null;\r\n                    }\r\n                }\r\n                nextMark();\r\n                while (pos < len) {\r\n                    var upto = len;\r\n                    var extraStyle = \"\";\r\n                    if (sfrom != null) {\r\n                        if (sfrom > pos) upto = sfrom;\r\n                        else if (sto == null || sto > pos) {\r\n                            extraStyle = \" CodeMirror-selected\";\r\n                            if (sto != null) upto = Math.min(upto, sto);\r\n                        }\r\n                    }\r\n                    while (mark && mark.to != null && mark.to <= pos) nextMark();\r\n                    if (mark) {\r\n                        if (mark.from > pos) upto = Math.min(upto, mark.from);\r\n                        else {\r\n                            extraStyle += \" \" + mark.style;\r\n                            if (mark.to != null) upto = Math.min(upto, mark.to);\r\n                        }\r\n                    }\r\n                    for (;;) {\r\n                        var end = pos + text.length;\r\n                        var appliedStyle = style;\r\n                        if (extraStyle) appliedStyle = style ? style + extraStyle : extraStyle;\r\n                        span(end > upto ? text.slice(0, upto - pos) : text, appliedStyle);\r\n                        if (end >= upto) {text = text.slice(upto - pos); pos = upto; break;}\r\n                        pos = end;\r\n                        text = st[i++]; style = \"cm-\" + st[i++];\r\n                    }\r\n                }\r\n                if (sfrom != null && sto == null) span(\" \", \"CodeMirror-selected\");\r\n            }\r\n            if (includePre) html.push(\"</pre>\");\r\n            return html.join(\"\");\r\n        },\r\n        cleanUp: function() {\r\n            this.parent = null;\r\n            if (this.marked)\r\n                for (var i = 0, e = this.marked.length; i < e; ++i) this.marked[i].detach(this);\r\n        }\r\n    };\r\n    // Utility used by replace and split above\r\n    function copyStyles(from, to, source, dest) {\r\n        for (var i = 0, pos = 0, state = 0; pos < to; i+=2) {\r\n            var part = source[i], end = pos + part.length;\r\n            if (state == 0) {\r\n                if (end > from) dest.push(part.slice(from - pos, Math.min(part.length, to - pos)), source[i+1]);\r\n                if (end >= from) state = 1;\r\n            }\r\n            else if (state == 1) {\r\n                if (end > to) dest.push(part.slice(0, to - pos), source[i+1]);\r\n                else dest.push(part, source[i+1]);\r\n            }\r\n            pos = end;\r\n        }\r\n    }\r\n\r\n    // Data structure that holds the sequence of lines.\r\n    function LeafChunk(lines) {\r\n        this.lines = lines;\r\n        this.parent = null;\r\n        for (var i = 0, e = lines.length, height = 0; i < e; ++i) {\r\n            lines[i].parent = this;\r\n            height += lines[i].height;\r\n        }\r\n        this.height = height;\r\n    }\r\n    LeafChunk.prototype = {\r\n        chunkSize: function() { return this.lines.length; },\r\n        remove: function(at, n, callbacks) {\r\n            for (var i = at, e = at + n; i < e; ++i) {\r\n                var line = this.lines[i];\r\n                this.height -= line.height;\r\n                line.cleanUp();\r\n                if (line.handlers)\r\n                    for (var j = 0; j < line.handlers.length; ++j) callbacks.push(line.handlers[j]);\r\n            }\r\n            this.lines.splice(at, n);\r\n        },\r\n        collapse: function(lines) {\r\n            lines.splice.apply(lines, [lines.length, 0].concat(this.lines));\r\n        },\r\n        insertHeight: function(at, lines, height) {\r\n            this.height += height;\r\n            this.lines.splice.apply(this.lines, [at, 0].concat(lines));\r\n            for (var i = 0, e = lines.length; i < e; ++i) lines[i].parent = this;\r\n        },\r\n        iterN: function(at, n, op) {\r\n            for (var e = at + n; at < e; ++at)\r\n                if (op(this.lines[at])) return true;\r\n        }\r\n    };\r\n    function BranchChunk(children) {\r\n        this.children = children;\r\n        var size = 0, height = 0;\r\n        for (var i = 0, e = children.length; i < e; ++i) {\r\n            var ch = children[i];\r\n            size += ch.chunkSize(); height += ch.height;\r\n            ch.parent = this;\r\n        }\r\n        this.size = size;\r\n        this.height = height;\r\n        this.parent = null;\r\n    }\r\n    BranchChunk.prototype = {\r\n        chunkSize: function() { return this.size; },\r\n        remove: function(at, n, callbacks) {\r\n            this.size -= n;\r\n            for (var i = 0; i < this.children.length; ++i) {\r\n                var child = this.children[i], sz = child.chunkSize();\r\n                if (at < sz) {\r\n                    var rm = Math.min(n, sz - at), oldHeight = child.height;\r\n                    child.remove(at, rm, callbacks);\r\n                    this.height -= oldHeight - child.height;\r\n                    if (sz == rm) { this.children.splice(i--, 1); child.parent = null; }\r\n                    if ((n -= rm) == 0) break;\r\n                    at = 0;\r\n                } else at -= sz;\r\n            }\r\n            if (this.size - n < 25) {\r\n                var lines = [];\r\n                this.collapse(lines);\r\n                this.children = [new LeafChunk(lines)];\r\n            }\r\n        },\r\n        collapse: function(lines) {\r\n            for (var i = 0, e = this.children.length; i < e; ++i) this.children[i].collapse(lines);\r\n        },\r\n        insert: function(at, lines) {\r\n            var height = 0;\r\n            for (var i = 0, e = lines.length; i < e; ++i) height += lines[i].height;\r\n            this.insertHeight(at, lines, height);\r\n        },\r\n        insertHeight: function(at, lines, height) {\r\n            this.size += lines.length;\r\n            this.height += height;\r\n            for (var i = 0, e = this.children.length; i < e; ++i) {\r\n                var child = this.children[i], sz = child.chunkSize();\r\n                if (at <= sz) {\r\n                    child.insertHeight(at, lines, height);\r\n                    if (child.lines && child.lines.length > 50) {\r\n                        while (child.lines.length > 50) {\r\n                            var spilled = child.lines.splice(child.lines.length - 25, 25);\r\n                            var newleaf = new LeafChunk(spilled);\r\n                            child.height -= newleaf.height;\r\n                            this.children.splice(i + 1, 0, newleaf);\r\n                            newleaf.parent = this;\r\n                        }\r\n                        this.maybeSpill();\r\n                    }\r\n                    break;\r\n                }\r\n                at -= sz;\r\n            }\r\n        },\r\n        maybeSpill: function() {\r\n            if (this.children.length <= 10) return;\r\n            var me = this;\r\n            do {\r\n                var spilled = me.children.splice(me.children.length - 5, 5);\r\n                var sibling = new BranchChunk(spilled);\r\n                if (!me.parent) { // Become the parent node\r\n                    var copy = new BranchChunk(me.children);\r\n                    copy.parent = me;\r\n                    me.children = [copy, sibling];\r\n                    me = copy;\r\n                } else {\r\n                    me.size -= sibling.size;\r\n                    me.height -= sibling.height;\r\n                    var myIndex = indexOf(me.parent.children, me);\r\n                    me.parent.children.splice(myIndex + 1, 0, sibling);\r\n                }\r\n                sibling.parent = me.parent;\r\n            } while (me.children.length > 10);\r\n            me.parent.maybeSpill();\r\n        },\r\n        iter: function(from, to, op) { this.iterN(from, to - from, op); },\r\n        iterN: function(at, n, op) {\r\n            for (var i = 0, e = this.children.length; i < e; ++i) {\r\n                var child = this.children[i], sz = child.chunkSize();\r\n                if (at < sz) {\r\n                    var used = Math.min(n, sz - at);\r\n                    if (child.iterN(at, used, op)) return true;\r\n                    if ((n -= used) == 0) break;\r\n                    at = 0;\r\n                } else at -= sz;\r\n            }\r\n        }\r\n    };\r\n\r\n    function getLineAt(chunk, n) {\r\n        while (!chunk.lines) {\r\n            for (var i = 0;; ++i) {\r\n                var child = chunk.children[i], sz = child.chunkSize();\r\n                if (n < sz) { chunk = child; break; }\r\n                n -= sz;\r\n            }\r\n        }\r\n        return chunk.lines[n];\r\n    }\r\n    function lineNo(line) {\r\n        if (line.parent == null) return null;\r\n        var cur = line.parent, no = indexOf(cur.lines, line);\r\n        for (var chunk = cur.parent; chunk; cur = chunk, chunk = chunk.parent) {\r\n            for (var i = 0, e = chunk.children.length; ; ++i) {\r\n                if (chunk.children[i] == cur) break;\r\n                no += chunk.children[i].chunkSize();\r\n            }\r\n        }\r\n        return no;\r\n    }\r\n    function lineAtHeight(chunk, h) {\r\n        var n = 0;\r\n        outer: do {\r\n            for (var i = 0, e = chunk.children.length; i < e; ++i) {\r\n                var child = chunk.children[i], ch = child.height;\r\n                if (h < ch) { chunk = child; continue outer; }\r\n                h -= ch;\r\n                n += child.chunkSize();\r\n            }\r\n            return n;\r\n        } while (!chunk.lines);\r\n        for (var i = 0, e = chunk.lines.length; i < e; ++i) {\r\n            var line = chunk.lines[i], lh = line.height;\r\n            if (h < lh) break;\r\n            h -= lh;\r\n        }\r\n        return n + i;\r\n    }\r\n    function heightAtLine(chunk, n) {\r\n        var h = 0;\r\n        outer: do {\r\n            for (var i = 0, e = chunk.children.length; i < e; ++i) {\r\n                var child = chunk.children[i], sz = child.chunkSize();\r\n                if (n < sz) { chunk = child; continue outer; }\r\n                n -= sz;\r\n                h += child.height;\r\n            }\r\n            return h;\r\n        } while (!chunk.lines);\r\n        for (var i = 0; i < n; ++i) h += chunk.lines[i].height;\r\n        return h;\r\n    }\r\n\r\n    // The history object 'chunks' changes that are made close together\r\n    // and at almost the same time into bigger undoable units.\r\n    function History() {\r\n        this.time = 0;\r\n        this.done = []; this.undone = [];\r\n    }\r\n    History.prototype = {\r\n        addChange: function(start, added, old) {\r\n            this.undone.length = 0;\r\n            var time = +new Date, last = this.done[this.done.length - 1];\r\n            if (time - this.time > 400 || !last ||\r\n                last.start > start + added || last.start + last.added < start - last.added + last.old.length)\r\n                this.done.push({start: start, added: added, old: old});\r\n            else {\r\n                var oldoff = 0;\r\n                if (start < last.start) {\r\n                    for (var i = last.start - start - 1; i >= 0; --i)\r\n                        last.old.unshift(old[i]);\r\n                    last.added += last.start - start;\r\n                    last.start = start;\r\n                }\r\n                else if (last.start < start) {\r\n                    oldoff = start - last.start;\r\n                    added += oldoff;\r\n                }\r\n                for (var i = last.added - oldoff, e = old.length; i < e; ++i)\r\n                    last.old.push(old[i]);\r\n                if (last.added < added) last.added = added;\r\n            }\r\n            this.time = time;\r\n        }\r\n    };\r\n\r\n    function stopMethod() {e_stop(this);}\r\n    // Ensure an event has a stop method.\r\n    function addStop(event) {\r\n        if (!event.stop) event.stop = stopMethod;\r\n        return event;\r\n    }\r\n\r\n    function e_preventDefault(e) {\r\n        if (e.preventDefault) e.preventDefault();\r\n        else e.returnValue = false;\r\n    }\r\n    function e_stopPropagation(e) {\r\n        if (e.stopPropagation) e.stopPropagation();\r\n        else e.cancelBubble = true;\r\n    }\r\n    function e_stop(e) {e_preventDefault(e); e_stopPropagation(e);}\r\n    CodeMirror.e_stop = e_stop;\r\n    CodeMirror.e_preventDefault = e_preventDefault;\r\n    CodeMirror.e_stopPropagation = e_stopPropagation;\r\n\r\n    function e_target(e) {return e.target || e.srcElement;}\r\n    function e_button(e) {\r\n        if (e.which) return e.which;\r\n        else if (e.button & 1) return 1;\r\n        else if (e.button & 2) return 3;\r\n        else if (e.button & 4) return 2;\r\n    }\r\n\r\n    // Event handler registration. If disconnect is true, it'll return a\r\n    // function that unregisters the handler.\r\n    function connect(node, type, handler, disconnect) {\r\n        if (typeof node.addEventListener == \"function\") {\r\n            node.addEventListener(type, handler, false);\r\n            if (disconnect) return function() {node.removeEventListener(type, handler, false);};\r\n        }\r\n        else {\r\n            var wrapHandler = function(event) {handler(event || window.event);};\r\n            node.attachEvent(\"on\" + type, wrapHandler);\r\n            if (disconnect) return function() {node.detachEvent(\"on\" + type, wrapHandler);};\r\n        }\r\n    }\r\n    CodeMirror.connect = connect;\r\n\r\n    function Delayed() {this.id = null;}\r\n    Delayed.prototype = {set: function(ms, f) {clearTimeout(this.id); this.id = setTimeout(f, ms);}};\r\n\r\n    // Detect drag-and-drop\r\n    var dragAndDrop = function() {\r\n        // IE8 has ondragstart and ondrop properties, but doesn't seem to\r\n        // actually support ondragstart the way it's supposed to work.\r\n        if (/MSIE [1-8]\\b/.test(navigator.userAgent)) return false;\r\n        var div = document.createElement('div');\r\n        return \"draggable\" in div;\r\n    }();\r\n\r\n    var gecko = /gecko\\/\\d{7}/i.test(navigator.userAgent);\r\n    var ie = /MSIE \\d/.test(navigator.userAgent);\r\n    var webkit = /WebKit\\//.test(navigator.userAgent);\r\n\r\n    var lineSep = \"\\n\";\r\n    // Feature-detect whether newlines in textareas are converted to \\r\\n\r\n    (function () {\r\n        var te = document.createElement(\"textarea\");\r\n        te.value = \"foo\\nbar\";\r\n        if (te.value.indexOf(\"\\r\") > -1) lineSep = \"\\r\\n\";\r\n    }());\r\n\r\n    // Counts the column offset in a string, taking tabs into account.\r\n    // Used mostly to find indentation.\r\n    function countColumn(string, end, tabSize) {\r\n        if (end == null) {\r\n            end = string.search(/[^\\s\\u00a0]/);\r\n            if (end == -1) end = string.length;\r\n        }\r\n        for (var i = 0, n = 0; i < end; ++i) {\r\n            if (string.charAt(i) == \"\\t\") n += tabSize - (n % tabSize);\r\n            else ++n;\r\n        }\r\n        return n;\r\n    }\r\n\r\n    function computedStyle(elt) {\r\n        if (elt.currentStyle) return elt.currentStyle;\r\n        return window.getComputedStyle(elt, null);\r\n    }\r\n\r\n    // Find the position of an element by following the offsetParent chain.\r\n    // If screen==true, it returns screen (rather than page) coordinates.\r\n    function eltOffset(node, screen) {\r\n        var bod = node.ownerDocument.body;\r\n        var x = 0, y = 0, skipBody = false;\r\n        for (var n = node; n; n = n.offsetParent) {\r\n            var ol = n.offsetLeft, ot = n.offsetTop;\r\n            // Firefox reports weird inverted offsets when the body has a border.\r\n            if (n == bod) { x += Math.abs(ol); y += Math.abs(ot); }\r\n            else { x += ol, y += ot; }\r\n            if (screen && computedStyle(n).position == \"fixed\")\r\n                skipBody = true;\r\n        }\r\n        var e = screen && !skipBody ? null : bod;\r\n        for (var n = node.parentNode; n != e; n = n.parentNode)\r\n            if (n.scrollLeft != null) { x -= n.scrollLeft; y -= n.scrollTop;}\r\n        return {left: x, top: y};\r\n    }\r\n    // Use the faster and saner getBoundingClientRect method when possible.\r\n    if (document.documentElement.getBoundingClientRect != null) eltOffset = function(node, screen) {\r\n        // Take the parts of bounding client rect that we are interested in so we are able to edit if need be,\r\n        // since the returned value cannot be changed externally (they are kept in sync as the element moves within the page)\r\n        try { var box = node.getBoundingClientRect(); box = { top: box.top, left: box.left }; }\r\n        catch(e) { box = {top: 0, left: 0}; }\r\n        if (!screen) {\r\n            // Get the toplevel scroll, working around browser differences.\r\n            if (window.pageYOffset == null) {\r\n                var t = document.documentElement || document.body.parentNode;\r\n                if (t.scrollTop == null) t = document.body;\r\n                box.top += t.scrollTop; box.left += t.scrollLeft;\r\n            } else {\r\n                box.top += window.pageYOffset; box.left += window.pageXOffset;\r\n            }\r\n        }\r\n        return box;\r\n    };\r\n\r\n    // Get a node's text content.\r\n    function eltText(node) {\r\n        return node.textContent || node.innerText || node.nodeValue || \"\";\r\n    }\r\n\r\n    // Operations on {line, ch} objects.\r\n    function posEq(a, b) {return a.line == b.line && a.ch == b.ch;}\r\n    function posLess(a, b) {return a.line < b.line || (a.line == b.line && a.ch < b.ch);}\r\n    function copyPos(x) {return {line: x.line, ch: x.ch};}\r\n\r\n    var escapeElement = document.createElement(\"pre\");\r\n    function htmlEscape(str) {\r\n        escapeElement.textContent = str;\r\n        return escapeElement.innerHTML;\r\n    }\r\n    // Recent (late 2011) Opera betas insert bogus newlines at the start\r\n    // of the textContent, so we strip those.\r\n    if (htmlEscape(\"a\") == \"\\na\")\r\n        htmlEscape = function(str) {\r\n            escapeElement.textContent = str;\r\n            return escapeElement.innerHTML.slice(1);\r\n        };\r\n    // Some IEs don't preserve tabs through innerHTML\r\n    else if (htmlEscape(\"\\t\") != \"\\t\")\r\n        htmlEscape = function(str) {\r\n            escapeElement.innerHTML = \"\";\r\n            escapeElement.appendChild(document.createTextNode(str));\r\n            return escapeElement.innerHTML;\r\n        };\r\n    CodeMirror.htmlEscape = htmlEscape;\r\n\r\n    // Used to position the cursor after an undo/redo by finding the\r\n    // last edited character.\r\n    function editEnd(from, to) {\r\n        if (!to) return from ? from.length : 0;\r\n        if (!from) return to.length;\r\n        for (var i = from.length, j = to.length; i >= 0 && j >= 0; --i, --j)\r\n            if (from.charAt(i) != to.charAt(j)) break;\r\n        return j + 1;\r\n    }\r\n\r\n    function indexOf(collection, elt) {\r\n        if (collection.indexOf) return collection.indexOf(elt);\r\n        for (var i = 0, e = collection.length; i < e; ++i)\r\n            if (collection[i] == elt) return i;\r\n        return -1;\r\n    }\r\n    function isWordChar(ch) {\r\n        return /\\w/.test(ch) || ch.toUpperCase() != ch.toLowerCase();\r\n    }\r\n\r\n    // See if \"\".split is the broken IE version, if so, provide an\r\n    // alternative way to split lines.\r\n    var splitLines = \"\\n\\nb\".split(/\\n/).length != 3 ? function(string) {\r\n        var pos = 0, nl, result = [];\r\n        while ((nl = string.indexOf(\"\\n\", pos)) > -1) {\r\n            result.push(string.slice(pos, string.charAt(nl-1) == \"\\r\" ? nl - 1 : nl));\r\n            pos = nl + 1;\r\n        }\r\n        result.push(string.slice(pos));\r\n        return result;\r\n    } : function(string){return string.split(/\\r?\\n/);};\r\n    CodeMirror.splitLines = splitLines;\r\n\r\n    var hasSelection = window.getSelection ? function(te) {\r\n        try { return te.selectionStart != te.selectionEnd; }\r\n        catch(e) { return false; }\r\n    } : function(te) {\r\n        try {var range = te.ownerDocument.selection.createRange();}\r\n        catch(e) {}\r\n        if (!range || range.parentElement() != te) return false;\r\n        return range.compareEndPoints(\"StartToEnd\", range) != 0;\r\n    };\r\n\r\n    CodeMirror.defineMode(\"null\", function() {\r\n        return {token: function(stream) {stream.skipToEnd();}};\r\n    });\r\n    CodeMirror.defineMIME(\"text/plain\", \"null\");\r\n\r\n    var keyNames = {3: \"Enter\", 8: \"Backspace\", 9: \"Tab\", 13: \"Enter\", 16: \"Shift\", 17: \"Ctrl\", 18: \"Alt\",\r\n        19: \"Pause\", 20: \"CapsLock\", 27: \"Esc\", 32: \"Space\", 33: \"PageUp\", 34: \"PageDown\", 35: \"End\",\r\n        36: \"Home\", 37: \"Left\", 38: \"Up\", 39: \"Right\", 40: \"Down\", 44: \"PrintScrn\", 45: \"Insert\",\r\n        46: \"Delete\", 59: \";\", 91: \"Mod\", 92: \"Mod\", 93: \"Mod\", 186: \";\", 187: \"=\", 188: \",\",\r\n        189: \"-\", 190: \".\", 191: \"/\", 192: \"`\", 219: \"[\", 220: \"\\\\\", 221: \"]\", 222: \"'\", 63276: \"PageUp\",\r\n        63277: \"PageDown\", 63275: \"End\", 63273: \"Home\", 63234: \"Left\", 63232: \"Up\", 63235: \"Right\",\r\n        63233: \"Down\", 63302: \"Insert\", 63272: \"Delete\"};\r\n    CodeMirror.keyNames = keyNames;\r\n    (function() {\r\n        // Number keys\r\n        for (var i = 0; i < 10; i++) keyNames[i + 48] = String(i);\r\n        // Alphabetic keys\r\n        for (var i = 65; i <= 90; i++) keyNames[i] = String.fromCharCode(i);\r\n        // Function keys\r\n        for (var i = 1; i <= 12; i++) keyNames[i + 111] = keyNames[i + 63235] = \"F\" + i;\r\n    })();\r\n\r\n    return CodeMirror;\r\n})();\r\nCodeMirror.defineMode(\"xml\", function(config, parserConfig) {\r\n    var indentUnit = config.indentUnit;\r\n    var Kludges = parserConfig.htmlMode ? {\r\n        autoSelfClosers: {\"br\": true, \"img\": true, \"hr\": true, \"link\": true, \"input\": true,\r\n            \"meta\": true, \"col\": true, \"frame\": true, \"base\": true, \"area\": true},\r\n        doNotIndent: {\"pre\": true},\r\n        allowUnquoted: true\r\n    } : {autoSelfClosers: {}, doNotIndent: {}, allowUnquoted: false};\r\n    var alignCDATA = parserConfig.alignCDATA;\r\n\r\n    // Return variables for tokenizers\r\n    var tagName, type;\r\n\r\n    function inText(stream, state) {\r\n        function chain(parser) {\r\n            state.tokenize = parser;\r\n            return parser(stream, state);\r\n        }\r\n\r\n        var ch = stream.next();\r\n        if (ch == \"<\") {\r\n            if (stream.eat(\"!\")) {\r\n                if (stream.eat(\"[\")) {\r\n                    if (stream.match(\"CDATA[\")) return chain(inBlock(\"atom\", \"]]>\"));\r\n                    else return null;\r\n                }\r\n                else if (stream.match(\"--\")) return chain(inBlock(\"comment\", \"-->\"));\r\n                else if (stream.match(\"DOCTYPE\", true, true)) {\r\n                    stream.eatWhile(/[\\w\\._\\-]/);\r\n                    return chain(doctype(1));\r\n                }\r\n                else return null;\r\n            }\r\n            else if (stream.eat(\"?\")) {\r\n                stream.eatWhile(/[\\w\\._\\-]/);\r\n                state.tokenize = inBlock(\"meta\", \"?>\");\r\n                return \"meta\";\r\n            }\r\n            else {\r\n                type = stream.eat(\"/\") ? \"closeTag\" : \"openTag\";\r\n                stream.eatSpace();\r\n                tagName = \"\";\r\n                var c;\r\n                while ((c = stream.eat(/[^\\s\\u00a0=<>\\\"\\'\\/?]/))) tagName += c;\r\n                state.tokenize = inTag;\r\n                return \"tag\";\r\n            }\r\n        }\r\n        else if (ch == \"&\") {\r\n            stream.eatWhile(/[^;]/);\r\n            stream.eat(\";\");\r\n            return \"atom\";\r\n        }\r\n        else {\r\n            stream.eatWhile(/[^&<]/);\r\n            return null;\r\n        }\r\n    }\r\n\r\n    function inTag(stream, state) {\r\n        var ch = stream.next();\r\n        if (ch == \">\" || (ch == \"/\" && stream.eat(\">\"))) {\r\n            state.tokenize = inText;\r\n            type = ch == \">\" ? \"endTag\" : \"selfcloseTag\";\r\n            return \"tag\";\r\n        }\r\n        else if (ch == \"=\") {\r\n            type = \"equals\";\r\n            return null;\r\n        }\r\n        else if (/[\\'\\\"]/.test(ch)) {\r\n            state.tokenize = inAttribute(ch);\r\n            return state.tokenize(stream, state);\r\n        }\r\n        else {\r\n            stream.eatWhile(/[^\\s\\u00a0=<>\\\"\\'\\/?]/);\r\n            return \"word\";\r\n        }\r\n    }\r\n\r\n    function inAttribute(quote) {\r\n        return function(stream, state) {\r\n            while (!stream.eol()) {\r\n                if (stream.next() == quote) {\r\n                    state.tokenize = inTag;\r\n                    break;\r\n                }\r\n            }\r\n            return \"string\";\r\n        };\r\n    }\r\n\r\n    function inBlock(style, terminator) {\r\n        return function(stream, state) {\r\n            while (!stream.eol()) {\r\n                if (stream.match(terminator)) {\r\n                    state.tokenize = inText;\r\n                    break;\r\n                }\r\n                stream.next();\r\n            }\r\n            return style;\r\n        };\r\n    }\r\n    function doctype(depth) {\r\n        return function(stream, state) {\r\n            var ch;\r\n            while ((ch = stream.next()) != null) {\r\n                if (ch == \"<\") {\r\n                    state.tokenize = doctype(depth + 1);\r\n                    return state.tokenize(stream, state);\r\n                } else if (ch == \">\") {\r\n                    if (depth == 1) {\r\n                        state.tokenize = inText;\r\n                        break;\r\n                    } else {\r\n                        state.tokenize = doctype(depth - 1);\r\n                        return state.tokenize(stream, state);\r\n                    }\r\n                }\r\n            }\r\n            return \"meta\";\r\n        };\r\n    }\r\n\r\n    var curState, setStyle;\r\n    function pass() {\r\n        for (var i = arguments.length - 1; i >= 0; i--) curState.cc.push(arguments[i]);\r\n    }\r\n    function cont() {\r\n        pass.apply(null, arguments);\r\n        return true;\r\n    }\r\n\r\n    function pushContext(tagName, startOfLine) {\r\n        var noIndent = Kludges.doNotIndent.hasOwnProperty(tagName) || (curState.context && curState.context.noIndent);\r\n        curState.context = {\r\n            prev: curState.context,\r\n            tagName: tagName,\r\n            indent: curState.indented,\r\n            startOfLine: startOfLine,\r\n            noIndent: noIndent\r\n        };\r\n    }\r\n    function popContext() {\r\n        if (curState.context) curState.context = curState.context.prev;\r\n    }\r\n\r\n    function element(type) {\r\n        if (type == \"openTag\") {\r\n            curState.tagName = tagName;\r\n            return cont(attributes, endtag(curState.startOfLine));\r\n        } else if (type == \"closeTag\") {\r\n            var err = false;\r\n            if (curState.context) {\r\n                err = curState.context.tagName != tagName;\r\n            } else {\r\n                err = true;\r\n            }\r\n            if (err) setStyle = \"error\";\r\n            return cont(endclosetag(err));\r\n        }\r\n        return cont();\r\n    }\r\n    function endtag(startOfLine) {\r\n        return function(type) {\r\n            if (type == \"selfcloseTag\" ||\r\n                (type == \"endTag\" && Kludges.autoSelfClosers.hasOwnProperty(curState.tagName.toLowerCase())))\r\n                return cont();\r\n            if (type == \"endTag\") {pushContext(curState.tagName, startOfLine); return cont();}\r\n            return cont();\r\n        };\r\n    }\r\n    function endclosetag(err) {\r\n        return function(type) {\r\n            if (err) setStyle = \"error\";\r\n            if (type == \"endTag\") { popContext(); return cont(); }\r\n            setStyle = \"error\";\r\n            return cont(arguments.callee);\r\n        }\r\n    }\r\n\r\n    function attributes(type) {\r\n        if (type == \"word\") {setStyle = \"attribute\"; return cont(attributes);}\r\n        if (type == \"equals\") return cont(attvalue, attributes);\r\n        if (type == \"string\") {setStyle = \"error\"; return cont(attributes);}\r\n        return pass();\r\n    }\r\n    function attvalue(type) {\r\n        if (type == \"word\" && Kludges.allowUnquoted) {setStyle = \"string\"; return cont();}\r\n        if (type == \"string\") return cont(attvaluemaybe);\r\n        return pass();\r\n    }\r\n    function attvaluemaybe(type) {\r\n        if (type == \"string\") return cont(attvaluemaybe);\r\n        else return pass();\r\n    }\r\n\r\n    return {\r\n        startState: function() {\r\n            return {tokenize: inText, cc: [], indented: 0, startOfLine: true, tagName: null, context: null};\r\n        },\r\n\r\n        token: function(stream, state) {\r\n            if (stream.sol()) {\r\n                state.startOfLine = true;\r\n                state.indented = stream.indentation();\r\n            }\r\n            if (stream.eatSpace()) return null;\r\n\r\n            setStyle = type = tagName = null;\r\n            var style = state.tokenize(stream, state);\r\n            state.type = type;\r\n            if ((style || type) && style != \"comment\") {\r\n                curState = state;\r\n                while (true) {\r\n                    var comb = state.cc.pop() || element;\r\n                    if (comb(type || style)) break;\r\n                }\r\n            }\r\n            state.startOfLine = false;\r\n            return setStyle || style;\r\n        },\r\n\r\n        indent: function(state, textAfter, fullLine) {\r\n            var context = state.context;\r\n            if ((state.tokenize != inTag && state.tokenize != inText) ||\r\n                context && context.noIndent)\r\n                return fullLine ? fullLine.match(/^(\\s*)/)[0].length : 0;\r\n            if (alignCDATA && /<!\\[CDATA\\[/.test(textAfter)) return 0;\r\n            if (context && /^<\\//.test(textAfter))\r\n                context = context.prev;\r\n            while (context && !context.startOfLine)\r\n                context = context.prev;\r\n            if (context) return context.indent + indentUnit;\r\n            else return 0;\r\n        },\r\n\r\n        compareStates: function(a, b) {\r\n            if (a.indented != b.indented || a.tokenize != b.tokenize) return false;\r\n            for (var ca = a.context, cb = b.context; ; ca = ca.prev, cb = cb.prev) {\r\n                if (!ca || !cb) return ca == cb;\r\n                if (ca.tagName != cb.tagName) return false;\r\n            }\r\n        },\r\n\r\n        electricChars: \"/\"\r\n    };\r\n});\r\n\r\nCodeMirror.defineMIME(\"application/xml\", \"xml\");\r\nCodeMirror.defineMIME(\"text/html\", {name: \"xml\", htmlMode: true});\r\nCodeMirror.defineMode(\"javascript\", function(config, parserConfig) {\r\n    var indentUnit = config.indentUnit;\r\n    var jsonMode = parserConfig.json;\r\n\r\n    // Tokenizer\r\n\r\n    var keywords = function(){\r\n        function kw(type) {return {type: type, style: \"keyword\"};}\r\n        var A = kw(\"keyword a\"), B = kw(\"keyword b\"), C = kw(\"keyword c\");\r\n        var operator = kw(\"operator\"), atom = {type: \"atom\", style: \"atom\"};\r\n        return {\r\n            \"if\": A, \"while\": A, \"with\": A, \"else\": B, \"do\": B, \"try\": B, \"finally\": B,\r\n            \"return\": C, \"break\": C, \"continue\": C, \"new\": C, \"delete\": C, \"throw\": C,\r\n            \"var\": kw(\"var\"), \"const\": kw(\"var\"), \"let\": kw(\"var\"),\r\n            \"function\": kw(\"function\"), \"catch\": kw(\"catch\"),\r\n            \"for\": kw(\"for\"), \"switch\": kw(\"switch\"), \"case\": kw(\"case\"), \"default\": kw(\"default\"),\r\n            \"in\": operator, \"typeof\": operator, \"instanceof\": operator,\r\n            \"true\": atom, \"false\": atom, \"null\": atom, \"undefined\": atom, \"NaN\": atom, \"Infinity\": atom\r\n        };\r\n    }();\r\n\r\n    var isOperatorChar = /[+\\-*&%=<>!?|]/;\r\n\r\n    function chain(stream, state, f) {\r\n        state.tokenize = f;\r\n        return f(stream, state);\r\n    }\r\n\r\n    function nextUntilUnescaped(stream, end) {\r\n        var escaped = false, next;\r\n        while ((next = stream.next()) != null) {\r\n            if (next == end && !escaped)\r\n                return false;\r\n            escaped = !escaped && next == \"\\\\\";\r\n        }\r\n        return escaped;\r\n    }\r\n\r\n    // Used as scratch variables to communicate multiple values without\r\n    // consing up tons of objects.\r\n    var type, content;\r\n    function ret(tp, style, cont) {\r\n        type = tp; content = cont;\r\n        return style;\r\n    }\r\n\r\n    function jsTokenBase(stream, state) {\r\n        var ch = stream.next();\r\n        if (ch == '\"' || ch == \"'\")\r\n            return chain(stream, state, jsTokenString(ch));\r\n        else if (/[\\[\\]{}\\(\\),;\\:\\.]/.test(ch))\r\n            return ret(ch);\r\n        else if (ch == \"0\" && stream.eat(/x/i)) {\r\n            stream.eatWhile(/[\\da-f]/i);\r\n            return ret(\"number\", \"number\");\r\n        }\r\n        else if (/\\d/.test(ch)) {\r\n            stream.match(/^\\d*(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?/);\r\n            return ret(\"number\", \"number\");\r\n        }\r\n        else if (ch == \"/\") {\r\n            if (stream.eat(\"*\")) {\r\n                return chain(stream, state, jsTokenComment);\r\n            }\r\n            else if (stream.eat(\"/\")) {\r\n                stream.skipToEnd();\r\n                return ret(\"comment\", \"comment\");\r\n            }\r\n            else if (state.reAllowed) {\r\n                nextUntilUnescaped(stream, \"/\");\r\n                stream.eatWhile(/[gimy]/); // 'y' is \"sticky\" option in Mozilla\r\n                return ret(\"regexp\", \"string\");\r\n            }\r\n            else {\r\n                stream.eatWhile(isOperatorChar);\r\n                return ret(\"operator\", null, stream.current());\r\n            }\r\n        }\r\n        else if (ch == \"#\") {\r\n            stream.skipToEnd();\r\n            return ret(\"error\", \"error\");\r\n        }\r\n        else if (isOperatorChar.test(ch)) {\r\n            stream.eatWhile(isOperatorChar);\r\n            return ret(\"operator\", null, stream.current());\r\n        }\r\n        else {\r\n            stream.eatWhile(/[\\w\\$_]/);\r\n            var word = stream.current(), known = keywords.propertyIsEnumerable(word) && keywords[word];\r\n            return (known && state.kwAllowed) ? ret(known.type, known.style, word) :\r\n                ret(\"variable\", \"variable\", word);\r\n        }\r\n    }\r\n\r\n    function jsTokenString(quote) {\r\n        return function(stream, state) {\r\n            if (!nextUntilUnescaped(stream, quote))\r\n                state.tokenize = jsTokenBase;\r\n            return ret(\"string\", \"string\");\r\n        };\r\n    }\r\n\r\n    function jsTokenComment(stream, state) {\r\n        var maybeEnd = false, ch;\r\n        while (ch = stream.next()) {\r\n            if (ch == \"/\" && maybeEnd) {\r\n                state.tokenize = jsTokenBase;\r\n                break;\r\n            }\r\n            maybeEnd = (ch == \"*\");\r\n        }\r\n        return ret(\"comment\", \"comment\");\r\n    }\r\n\r\n    // Parser\r\n\r\n    var atomicTypes = {\"atom\": true, \"number\": true, \"variable\": true, \"string\": true, \"regexp\": true};\r\n\r\n    function JSLexical(indented, column, type, align, prev, info) {\r\n        this.indented = indented;\r\n        this.column = column;\r\n        this.type = type;\r\n        this.prev = prev;\r\n        this.info = info;\r\n        if (align != null) this.align = align;\r\n    }\r\n\r\n    function inScope(state, varname) {\r\n        for (var v = state.localVars; v; v = v.next)\r\n            if (v.name == varname) return true;\r\n    }\r\n\r\n    function parseJS(state, style, type, content, stream) {\r\n        var cc = state.cc;\r\n        // Communicate our context to the combinators.\r\n        // (Less wasteful than consing up a hundred closures on every call.)\r\n        cx.state = state; cx.stream = stream; cx.marked = null, cx.cc = cc;\r\n\r\n        if (!state.lexical.hasOwnProperty(\"align\"))\r\n            state.lexical.align = true;\r\n\r\n        while(true) {\r\n            var combinator = cc.length ? cc.pop() : jsonMode ? expression : statement;\r\n            if (combinator(type, content)) {\r\n                while(cc.length && cc[cc.length - 1].lex)\r\n                    cc.pop()();\r\n                if (cx.marked) return cx.marked;\r\n                if (type == \"variable\" && inScope(state, content)) return \"variable-2\";\r\n                return style;\r\n            }\r\n        }\r\n    }\r\n\r\n    // Combinator utils\r\n\r\n    var cx = {state: null, column: null, marked: null, cc: null};\r\n    function pass() {\r\n        for (var i = arguments.length - 1; i >= 0; i--) cx.cc.push(arguments[i]);\r\n    }\r\n    function cont() {\r\n        pass.apply(null, arguments);\r\n        return true;\r\n    }\r\n    function register(varname) {\r\n        var state = cx.state;\r\n        if (state.context) {\r\n            cx.marked = \"def\";\r\n            for (var v = state.localVars; v; v = v.next)\r\n                if (v.name == varname) return;\r\n            state.localVars = {name: varname, next: state.localVars};\r\n        }\r\n    }\r\n\r\n    // Combinators\r\n\r\n    var defaultVars = {name: \"this\", next: {name: \"arguments\"}};\r\n    function pushcontext() {\r\n        if (!cx.state.context) cx.state.localVars = defaultVars;\r\n        cx.state.context = {prev: cx.state.context, vars: cx.state.localVars};\r\n    }\r\n    function popcontext() {\r\n        cx.state.localVars = cx.state.context.vars;\r\n        cx.state.context = cx.state.context.prev;\r\n    }\r\n    function pushlex(type, info) {\r\n        var result = function() {\r\n            var state = cx.state;\r\n            state.lexical = new JSLexical(state.indented, cx.stream.column(), type, null, state.lexical, info)\r\n        };\r\n        result.lex = true;\r\n        return result;\r\n    }\r\n    function poplex() {\r\n        var state = cx.state;\r\n        if (state.lexical.prev) {\r\n            if (state.lexical.type == \")\")\r\n                state.indented = state.lexical.indented;\r\n            state.lexical = state.lexical.prev;\r\n        }\r\n    }\r\n    poplex.lex = true;\r\n\r\n    function expect(wanted) {\r\n        return function expecting(type) {\r\n            if (type == wanted) return cont();\r\n            else if (wanted == \";\") return pass();\r\n            else return cont(arguments.callee);\r\n        };\r\n    }\r\n\r\n    function statement(type) {\r\n        if (type == \"var\") return cont(pushlex(\"vardef\"), vardef1, expect(\";\"), poplex);\r\n        if (type == \"keyword a\") return cont(pushlex(\"form\"), expression, statement, poplex);\r\n        if (type == \"keyword b\") return cont(pushlex(\"form\"), statement, poplex);\r\n        if (type == \"{\") return cont(pushlex(\"}\"), block, poplex);\r\n        if (type == \";\") return cont();\r\n        if (type == \"function\") return cont(functiondef);\r\n        if (type == \"for\") return cont(pushlex(\"form\"), expect(\"(\"), pushlex(\")\"), forspec1, expect(\")\"),\r\n            poplex, statement, poplex);\r\n        if (type == \"variable\") return cont(pushlex(\"stat\"), maybelabel);\r\n        if (type == \"switch\") return cont(pushlex(\"form\"), expression, pushlex(\"}\", \"switch\"), expect(\"{\"),\r\n            block, poplex, poplex);\r\n        if (type == \"case\") return cont(expression, expect(\":\"));\r\n        if (type == \"default\") return cont(expect(\":\"));\r\n        if (type == \"catch\") return cont(pushlex(\"form\"), pushcontext, expect(\"(\"), funarg, expect(\")\"),\r\n            statement, poplex, popcontext);\r\n        return pass(pushlex(\"stat\"), expression, expect(\";\"), poplex);\r\n    }\r\n    function expression(type) {\r\n        if (atomicTypes.hasOwnProperty(type)) return cont(maybeoperator);\r\n        if (type == \"function\") return cont(functiondef);\r\n        if (type == \"keyword c\") return cont(maybeexpression);\r\n        if (type == \"(\") return cont(pushlex(\")\"), expression, expect(\")\"), poplex, maybeoperator);\r\n        if (type == \"operator\") return cont(expression);\r\n        if (type == \"[\") return cont(pushlex(\"]\"), commasep(expression, \"]\"), poplex, maybeoperator);\r\n        if (type == \"{\") return cont(pushlex(\"}\"), commasep(objprop, \"}\"), poplex, maybeoperator);\r\n        return cont();\r\n    }\r\n    function maybeexpression(type) {\r\n        if (type.match(/[;\\}\\)\\],]/)) return pass();\r\n        return pass(expression);\r\n    }\r\n\r\n    function maybeoperator(type, value) {\r\n        if (type == \"operator\" && /\\+\\+|--/.test(value)) return cont(maybeoperator);\r\n        if (type == \"operator\") return cont(expression);\r\n        if (type == \";\") return;\r\n        if (type == \"(\") return cont(pushlex(\")\"), commasep(expression, \")\"), poplex, maybeoperator);\r\n        if (type == \".\") return cont(property, maybeoperator);\r\n        if (type == \"[\") return cont(pushlex(\"]\"), expression, expect(\"]\"), poplex, maybeoperator);\r\n    }\r\n    function maybelabel(type) {\r\n        if (type == \":\") return cont(poplex, statement);\r\n        return pass(maybeoperator, expect(\";\"), poplex);\r\n    }\r\n    function property(type) {\r\n        if (type == \"variable\") {cx.marked = \"property\"; return cont();}\r\n    }\r\n    function objprop(type) {\r\n        if (type == \"variable\") cx.marked = \"property\";\r\n        if (atomicTypes.hasOwnProperty(type)) return cont(expect(\":\"), expression);\r\n    }\r\n    function commasep(what, end) {\r\n        function proceed(type) {\r\n            if (type == \",\") return cont(what, proceed);\r\n            if (type == end) return cont();\r\n            return cont(expect(end));\r\n        }\r\n        return function commaSeparated(type) {\r\n            if (type == end) return cont();\r\n            else return pass(what, proceed);\r\n        };\r\n    }\r\n    function block(type) {\r\n        if (type == \"}\") return cont();\r\n        return pass(statement, block);\r\n    }\r\n    function vardef1(type, value) {\r\n        if (type == \"variable\"){register(value); return cont(vardef2);}\r\n        return cont();\r\n    }\r\n    function vardef2(type, value) {\r\n        if (value == \"=\") return cont(expression, vardef2);\r\n        if (type == \",\") return cont(vardef1);\r\n    }\r\n    function forspec1(type) {\r\n        if (type == \"var\") return cont(vardef1, forspec2);\r\n        if (type == \";\") return pass(forspec2);\r\n        if (type == \"variable\") return cont(formaybein);\r\n        return pass(forspec2);\r\n    }\r\n    function formaybein(type, value) {\r\n        if (value == \"in\") return cont(expression);\r\n        return cont(maybeoperator, forspec2);\r\n    }\r\n    function forspec2(type, value) {\r\n        if (type == \";\") return cont(forspec3);\r\n        if (value == \"in\") return cont(expression);\r\n        return cont(expression, expect(\";\"), forspec3);\r\n    }\r\n    function forspec3(type) {\r\n        if (type != \")\") cont(expression);\r\n    }\r\n    function functiondef(type, value) {\r\n        if (type == \"variable\") {register(value); return cont(functiondef);}\r\n        if (type == \"(\") return cont(pushlex(\")\"), pushcontext, commasep(funarg, \")\"), poplex, statement, popcontext);\r\n    }\r\n    function funarg(type, value) {\r\n        if (type == \"variable\") {register(value); return cont();}\r\n    }\r\n\r\n    // Interface\r\n\r\n    return {\r\n        startState: function(basecolumn) {\r\n            return {\r\n                tokenize: jsTokenBase,\r\n                reAllowed: true,\r\n                kwAllowed: true,\r\n                cc: [],\r\n                lexical: new JSLexical((basecolumn || 0) - indentUnit, 0, \"block\", false),\r\n                localVars: null,\r\n                context: null,\r\n                indented: 0\r\n            };\r\n        },\r\n\r\n        token: function(stream, state) {\r\n            if (stream.sol()) {\r\n                if (!state.lexical.hasOwnProperty(\"align\"))\r\n                    state.lexical.align = false;\r\n                state.indented = stream.indentation();\r\n            }\r\n            if (stream.eatSpace()) return null;\r\n            var style = state.tokenize(stream, state);\r\n            if (type == \"comment\") return style;\r\n            state.reAllowed = type == \"operator\" || type == \"keyword c\" || type.match(/^[\\[{}\\(,;:]$/);\r\n            state.kwAllowed = type != '.';\r\n            return parseJS(state, style, type, content, stream);\r\n        },\r\n\r\n        indent: function(state, textAfter) {\r\n            if (state.tokenize != jsTokenBase) return 0;\r\n            var firstChar = textAfter && textAfter.charAt(0), lexical = state.lexical,\r\n                type = lexical.type, closing = firstChar == type;\r\n            if (type == \"vardef\") return lexical.indented + 4;\r\n            else if (type == \"form\" && firstChar == \"{\") return lexical.indented;\r\n            else if (type == \"stat\" || type == \"form\") return lexical.indented + indentUnit;\r\n            else if (lexical.info == \"switch\" && !closing)\r\n                return lexical.indented + (/^(?:case|default)\\b/.test(textAfter) ? indentUnit : 2 * indentUnit);\r\n            else if (lexical.align) return lexical.column + (closing ? 0 : 1);\r\n            else return lexical.indented + (closing ? 0 : indentUnit);\r\n        },\r\n\r\n        electricChars: \":{}\"\r\n    };\r\n});\r\n\r\nCodeMirror.defineMIME(\"text/javascript\", \"javascript\");\r\nCodeMirror.defineMIME(\"application/json\", {name: \"javascript\", json: true});\r\n\r\nCodeMirror.defineMode(\"css\", function(config) {\r\n    var indentUnit = config.indentUnit, type;\r\n    function ret(style, tp) {type = tp; return style;}\r\n\r\n    function tokenBase(stream, state) {\r\n        var ch = stream.next();\r\n        if (ch == \"@\") {stream.eatWhile(/[\\w\\\\\\-]/); return ret(\"meta\", stream.current());}\r\n        else if (ch == \"/\" && stream.eat(\"*\")) {\r\n            state.tokenize = tokenCComment;\r\n            return tokenCComment(stream, state);\r\n        }\r\n        else if (ch == \"<\" && stream.eat(\"!\")) {\r\n            state.tokenize = tokenSGMLComment;\r\n            return tokenSGMLComment(stream, state);\r\n        }\r\n        else if (ch == \"=\") ret(null, \"compare\");\r\n        else if ((ch == \"~\" || ch == \"|\") && stream.eat(\"=\")) return ret(null, \"compare\");\r\n        else if (ch == \"\\\"\" || ch == \"'\") {\r\n            state.tokenize = tokenString(ch);\r\n            return state.tokenize(stream, state);\r\n        }\r\n        else if (ch == \"#\") {\r\n            stream.eatWhile(/[\\w\\\\\\-]/);\r\n            return ret(\"atom\", \"hash\");\r\n        }\r\n        else if (ch == \"!\") {\r\n            stream.match(/^\\s*\\w*/);\r\n            return ret(\"keyword\", \"important\");\r\n        }\r\n        else if (/\\d/.test(ch)) {\r\n            stream.eatWhile(/[\\w.%]/);\r\n            return ret(\"number\", \"unit\");\r\n        }\r\n        else if (/[,.+>*\\/]/.test(ch)) {\r\n            return ret(null, \"select-op\");\r\n        }\r\n        else if (/[;{}:\\[\\]]/.test(ch)) {\r\n            return ret(null, ch);\r\n        }\r\n        else {\r\n            stream.eatWhile(/[\\w\\\\\\-]/);\r\n            return ret(\"variable\", \"variable\");\r\n        }\r\n    }\r\n\r\n    function tokenCComment(stream, state) {\r\n        var maybeEnd = false, ch;\r\n        while ((ch = stream.next()) != null) {\r\n            if (maybeEnd && ch == \"/\") {\r\n                state.tokenize = tokenBase;\r\n                break;\r\n            }\r\n            maybeEnd = (ch == \"*\");\r\n        }\r\n        return ret(\"comment\", \"comment\");\r\n    }\r\n\r\n    function tokenSGMLComment(stream, state) {\r\n        var dashes = 0, ch;\r\n        while ((ch = stream.next()) != null) {\r\n            if (dashes >= 2 && ch == \">\") {\r\n                state.tokenize = tokenBase;\r\n                break;\r\n            }\r\n            dashes = (ch == \"-\") ? dashes + 1 : 0;\r\n        }\r\n        return ret(\"comment\", \"comment\");\r\n    }\r\n\r\n    function tokenString(quote) {\r\n        return function(stream, state) {\r\n            var escaped = false, ch;\r\n            while ((ch = stream.next()) != null) {\r\n                if (ch == quote && !escaped)\r\n                    break;\r\n                escaped = !escaped && ch == \"\\\\\";\r\n            }\r\n            if (!escaped) state.tokenize = tokenBase;\r\n            return ret(\"string\", \"string\");\r\n        };\r\n    }\r\n\r\n    return {\r\n        startState: function(base) {\r\n            return {tokenize: tokenBase,\r\n                baseIndent: base || 0,\r\n                stack: []};\r\n        },\r\n\r\n        token: function(stream, state) {\r\n            if (stream.eatSpace()) return null;\r\n            var style = state.tokenize(stream, state);\r\n\r\n            var context = state.stack[state.stack.length-1];\r\n            if (type == \"hash\" && context == \"rule\") style = \"atom\";\r\n            else if (style == \"variable\") {\r\n                if (context == \"rule\") style = \"number\";\r\n                else if (!context || context == \"@media{\") style = \"tag\";\r\n            }\r\n\r\n            if (context == \"rule\" && /^[\\{\\};]$/.test(type))\r\n                state.stack.pop();\r\n            if (type == \"{\") {\r\n                if (context == \"@media\") state.stack[state.stack.length-1] = \"@media{\";\r\n                else state.stack.push(\"{\");\r\n            }\r\n            else if (type == \"}\") state.stack.pop();\r\n            else if (type == \"@media\") state.stack.push(\"@media\");\r\n            else if (context == \"{\" && type != \"comment\") state.stack.push(\"rule\");\r\n            return style;\r\n        },\r\n\r\n        indent: function(state, textAfter) {\r\n            var n = state.stack.length;\r\n            if (/^\\}/.test(textAfter))\r\n                n -= state.stack[state.stack.length-1] == \"rule\" ? 2 : 1;\r\n            return state.baseIndent + n * indentUnit;\r\n        },\r\n\r\n        electricChars: \"}\"\r\n    };\r\n});\r\n\r\nCodeMirror.defineMIME(\"text/css\", \"css\");\r\nCodeMirror.defineMode(\"htmlmixed\", function(config, parserConfig) {\r\n    var htmlMode = CodeMirror.getMode(config, {name: \"xml\", htmlMode: true});\r\n    var jsMode = CodeMirror.getMode(config, \"javascript\");\r\n    var cssMode = CodeMirror.getMode(config, \"css\");\r\n\r\n    function html(stream, state) {\r\n        var style = htmlMode.token(stream, state.htmlState);\r\n        if (style == \"tag\" && stream.current() == \">\" && state.htmlState.context) {\r\n            if (/^script$/i.test(state.htmlState.context.tagName)) {\r\n                state.token = javascript;\r\n                state.localState = jsMode.startState(htmlMode.indent(state.htmlState, \"\"));\r\n                state.mode = \"javascript\";\r\n            }\r\n            else if (/^style$/i.test(state.htmlState.context.tagName)) {\r\n                state.token = css;\r\n                state.localState = cssMode.startState(htmlMode.indent(state.htmlState, \"\"));\r\n                state.mode = \"css\";\r\n            }\r\n        }\r\n        return style;\r\n    }\r\n    function maybeBackup(stream, pat, style) {\r\n        var cur = stream.current();\r\n        var close = cur.search(pat);\r\n        if (close > -1) stream.backUp(cur.length - close);\r\n        return style;\r\n    }\r\n    function javascript(stream, state) {\r\n        if (stream.match(/^<\\/\\s*script\\s*>/i, false)) {\r\n            state.token = html;\r\n            state.curState = null;\r\n            state.mode = \"html\";\r\n            return html(stream, state);\r\n        }\r\n        return maybeBackup(stream, /<\\/\\s*script\\s*>/,\r\n            jsMode.token(stream, state.localState));\r\n    }\r\n    function css(stream, state) {\r\n        if (stream.match(/^<\\/\\s*style\\s*>/i, false)) {\r\n            state.token = html;\r\n            state.localState = null;\r\n            state.mode = \"html\";\r\n            return html(stream, state);\r\n        }\r\n        return maybeBackup(stream, /<\\/\\s*style\\s*>/,\r\n            cssMode.token(stream, state.localState));\r\n    }\r\n\r\n    return {\r\n        startState: function() {\r\n            var state = htmlMode.startState();\r\n            return {token: html, localState: null, mode: \"html\", htmlState: state};\r\n        },\r\n\r\n        copyState: function(state) {\r\n            if (state.localState)\r\n                var local = CodeMirror.copyState(state.token == css ? cssMode : jsMode, state.localState);\r\n            return {token: state.token, localState: local, mode: state.mode,\r\n                htmlState: CodeMirror.copyState(htmlMode, state.htmlState)};\r\n        },\r\n\r\n        token: function(stream, state) {\r\n            return state.token(stream, state);\r\n        },\r\n\r\n        indent: function(state, textAfter) {\r\n            if (state.token == html || /^\\s*<\\//.test(textAfter))\r\n                return htmlMode.indent(state.htmlState, textAfter);\r\n            else if (state.token == javascript)\r\n                return jsMode.indent(state.localState, textAfter);\r\n            else\r\n                return cssMode.indent(state.localState, textAfter);\r\n        },\r\n\r\n        compareStates: function(a, b) {\r\n            return htmlMode.compareStates(a.htmlState, b.htmlState);\r\n        },\r\n\r\n        electricChars: \"/{}:\"\r\n    }\r\n});\r\n\r\nCodeMirror.defineMIME(\"text/html\", \"htmlmixed\");\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/highcharts/adapters/mootools-adapter.js",
    "content": "/*\n Highcharts JS v3.0.6 (2013-10-04)\n MooTools adapter\n\n (c) 2010-2013 Torstein Hønsi\n\n License: www.highcharts.com/license\n*/\n(function(){var e=window,h=document,f=e.MooTools.version.substring(0,3),i=f===\"1.2\"||f===\"1.1\",j=i||f===\"1.3\",g=e.$extend||function(){return Object.append.apply(Object,arguments)};e.HighchartsAdapter={init:function(a){var b=Fx.prototype,c=b.start,d=Fx.Morph.prototype,e=d.compute;b.start=function(b,d){var e=this.element;if(b.d)this.paths=a.init(e,e.d,this.toD);c.apply(this,arguments);return this};d.compute=function(b,c,d){var f=this.paths;if(f)this.element.attr(\"d\",a.step(f[0],f[1],d,this.toD));else return e.apply(this,\narguments)}},adapterRun:function(a,b){if(b===\"width\"||b===\"height\")return parseInt($(a).getStyle(b),10)},getScript:function(a,b){var c=h.getElementsByTagName(\"head\")[0],d=h.createElement(\"script\");d.type=\"text/javascript\";d.src=a;d.onload=b;c.appendChild(d)},animate:function(a,b,c){var d=a.attr,f=c&&c.complete;if(d&&!a.setStyle)a.getStyle=a.attr,a.setStyle=function(){var a=arguments;this.attr.call(this,a[0],a[1][0])},a.$family=function(){return!0};e.HighchartsAdapter.stop(a);c=new Fx.Morph(d?a:$(a),\ng({transition:Fx.Transitions.Quad.easeInOut},c));if(d)c.element=a;if(b.d)c.toD=b.d;f&&c.addEvent(\"complete\",f);c.start(b);a.fx=c},each:function(a,b){return i?$each(a,b):Array.each(a,b)},map:function(a,b){return a.map(b)},grep:function(a,b){return a.filter(b)},inArray:function(a,b,c){return b?b.indexOf(a,c):-1},offset:function(a){a=a.getPosition();return{left:a.x,top:a.y}},extendWithEvents:function(a){a.addEvent||(a.nodeName?$(a):g(a,new Events))},addEvent:function(a,b,c){typeof b===\"string\"&&(b===\n\"unload\"&&(b=\"beforeunload\"),e.HighchartsAdapter.extendWithEvents(a),a.addEvent(b,c))},removeEvent:function(a,b,c){typeof a!==\"string\"&&a.addEvent&&(b?(b===\"unload\"&&(b=\"beforeunload\"),c?a.removeEvent(b,c):a.removeEvents&&a.removeEvents(b)):a.removeEvents())},fireEvent:function(a,b,c,d){b={type:b,target:a};b=j?new Event(b):new DOMEvent(b);b=g(b,c);if(!b.target&&b.event)b.target=b.event.target;b.preventDefault=function(){d=null};a.fireEvent&&a.fireEvent(b.type,b);d&&d(b)},washMouseEvent:function(a){if(a.page)a.pageX=\na.page.x,a.pageY=a.page.y;return a},stop:function(a){a.fx&&a.fx.cancel()}}})();\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/highcharts/adapters/mootools-adapter.src.js",
    "content": "/**\n * @license Highcharts JS v3.0.6 (2013-10-04)\n * MooTools adapter\n *\n * (c) 2010-2013 Torstein Hønsi\n *\n * License: www.highcharts.com/license\n */\n\n// JSLint options:\n/*global Fx, $, $extend, $each, $merge, Events, Event, DOMEvent */\n\n(function () {\n\nvar win = window,\n\tdoc = document,\n\tmooVersion = win.MooTools.version.substring(0, 3), // Get the first three characters of the version number\n\tlegacy = mooVersion === '1.2' || mooVersion === '1.1', // 1.1 && 1.2 considered legacy, 1.3 is not.\n\tlegacyEvent = legacy || mooVersion === '1.3', // In versions 1.1 - 1.3 the event class is named Event, in newer versions it is named DOMEvent.\n\t$extend = win.$extend || function () {\n\t\treturn Object.append.apply(Object, arguments);\n\t};\n\nwin.HighchartsAdapter = {\n\t/**\n\t * Initialize the adapter. This is run once as Highcharts is first run.\n\t * @param {Object} pathAnim The helper object to do animations across adapters.\n\t */\n\tinit: function (pathAnim) {\n\t\tvar fxProto = Fx.prototype,\n\t\t\tfxStart = fxProto.start,\n\t\t\tmorphProto = Fx.Morph.prototype,\n\t\t\tmorphCompute = morphProto.compute;\n\n\t\t// override Fx.start to allow animation of SVG element wrappers\n\t\t/*jslint unparam: true*//* allow unused parameters in fx functions */\n\t\tfxProto.start = function (from, to) {\n\t\t\tvar fx = this,\n\t\t\t\telem = fx.element;\n\n\t\t\t// special for animating paths\n\t\t\tif (from.d) {\n\t\t\t\t//this.fromD = this.element.d.split(' ');\n\t\t\t\tfx.paths = pathAnim.init(\n\t\t\t\t\telem,\n\t\t\t\t\telem.d,\n\t\t\t\t\tfx.toD\n\t\t\t\t);\n\t\t\t}\n\t\t\tfxStart.apply(fx, arguments);\n\n\t\t\treturn this; // chainable\n\t\t};\n\n\t\t// override Fx.step to allow animation of SVG element wrappers\n\t\tmorphProto.compute = function (from, to, delta) {\n\t\t\tvar fx = this,\n\t\t\t\tpaths = fx.paths;\n\n\t\t\tif (paths) {\n\t\t\t\tfx.element.attr(\n\t\t\t\t\t'd',\n\t\t\t\t\tpathAnim.step(paths[0], paths[1], delta, fx.toD)\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\treturn morphCompute.apply(fx, arguments);\n\t\t\t}\n\t\t};\n\t\t/*jslint unparam: false*/\n\t},\n\t\n\t/**\n\t * Run a general method on the framework, following jQuery syntax\n\t * @param {Object} el The HTML element\n\t * @param {String} method Which method to run on the wrapped element\n\t */\n\tadapterRun: function (el, method) {\n\t\t\n\t\t// This currently works for getting inner width and height. If adding\n\t\t// more methods later, we need a conditional implementation for each.\n\t\tif (method === 'width' || method === 'height') {\n\t\t\treturn parseInt($(el).getStyle(method), 10);\n\t\t}\n\t},\n\n\t/**\n\t * Downloads a script and executes a callback when done.\n\t * @param {String} scriptLocation\n\t * @param {Function} callback\n\t */\n\tgetScript: function (scriptLocation, callback) {\n\t\t// We cannot assume that Assets class from mootools-more is available so instead insert a script tag to download script.\n\t\tvar head = doc.getElementsByTagName('head')[0];\n\t\tvar script = doc.createElement('script');\n\n\t\tscript.type = 'text/javascript';\n\t\tscript.src = scriptLocation;\n\t\tscript.onload = callback;\n\n\t\thead.appendChild(script);\n\t},\n\n\t/**\n\t * Animate a HTML element or SVG element wrapper\n\t * @param {Object} el\n\t * @param {Object} params\n\t * @param {Object} options jQuery-like animation options: duration, easing, callback\n\t */\n\tanimate: function (el, params, options) {\n\t\tvar isSVGElement = el.attr,\n\t\t\teffect,\n\t\t\tcomplete = options && options.complete;\n\n\t\tif (isSVGElement && !el.setStyle) {\n\t\t\t// add setStyle and getStyle methods for internal use in Moo\n\t\t\tel.getStyle = el.attr;\n\t\t\tel.setStyle = function () { // property value is given as array in Moo - break it down\n\t\t\t\tvar args = arguments;\n\t\t\t\tthis.attr.call(this, args[0], args[1][0]);\n\t\t\t};\n\t\t\t// dirty hack to trick Moo into handling el as an element wrapper\n\t\t\tel.$family = function () { return true; };\n\t\t}\n\n\t\t// stop running animations\n\t\twin.HighchartsAdapter.stop(el);\n\n\t\t// define and run the effect\n\t\teffect = new Fx.Morph(\n\t\t\tisSVGElement ? el : $(el),\n\t\t\t$extend({\n\t\t\t\ttransition: Fx.Transitions.Quad.easeInOut\n\t\t\t}, options)\n\t\t);\n\n\t\t// Make sure that the element reference is set when animating svg elements\n\t\tif (isSVGElement) {\n\t\t\teffect.element = el;\n\t\t}\n\n\t\t// special treatment for paths\n\t\tif (params.d) {\n\t\t\teffect.toD = params.d;\n\t\t}\n\n\t\t// jQuery-like events\n\t\tif (complete) {\n\t\t\teffect.addEvent('complete', complete);\n\t\t}\n\n\t\t// run\n\t\teffect.start(params);\n\n\t\t// record for use in stop method\n\t\tel.fx = effect;\n\t},\n\n\t/**\n\t * MooTool's each function\n\t *\n\t */\n\teach: function (arr, fn) {\n\t\treturn legacy ?\n\t\t\t$each(arr, fn) :\n\t\t\tArray.each(arr, fn);\n\t},\n\n\t/**\n\t * Map an array\n\t * @param {Array} arr\n\t * @param {Function} fn\n\t */\n\tmap: function (arr, fn) {\n\t\treturn arr.map(fn);\n\t},\n\n\t/**\n\t * Grep or filter an array\n\t * @param {Array} arr\n\t * @param {Function} fn\n\t */\n\tgrep: function (arr, fn) {\n\t\treturn arr.filter(fn);\n\t},\n\t\n\t/**\n\t * Return the index of an item in an array, or -1 if not matched\n\t */\n\tinArray: function (item, arr, from) {\n\t\treturn arr ? arr.indexOf(item, from) : -1;\n\t},\n\n\t/**\n\t * Get the offset of an element relative to the top left corner of the web page\n\t */\n\toffset: function (el) {\n\t\tvar offsets = el.getPosition(); // #1496\n\t\treturn {\n\t\t\tleft: offsets.x,\n\t\t\ttop: offsets.y\n\t\t};\n\t},\n\n\t/**\n\t * Extends an object with Events, if its not done\n\t */\n\textendWithEvents: function (el) {\n\t\t// if the addEvent method is not defined, el is a custom Highcharts object\n\t\t// like series or point\n\t\tif (!el.addEvent) {\n\t\t\tif (el.nodeName) {\n\t\t\t\tel = $(el); // a dynamically generated node\n\t\t\t} else {\n\t\t\t\t$extend(el, new Events()); // a custom object\n\t\t\t}\n\t\t}\n\t},\n\n\t/**\n\t * Add an event listener\n\t * @param {Object} el HTML element or custom object\n\t * @param {String} type Event type\n\t * @param {Function} fn Event handler\n\t */\n\taddEvent: function (el, type, fn) {\n\t\tif (typeof type === 'string') { // chart broke due to el being string, type function\n\n\t\t\tif (type === 'unload') { // Moo self destructs before custom unload events\n\t\t\t\ttype = 'beforeunload';\n\t\t\t}\n\n\t\t\twin.HighchartsAdapter.extendWithEvents(el);\n\n\t\t\tel.addEvent(type, fn);\n\t\t}\n\t},\n\n\tremoveEvent: function (el, type, fn) {\n\t\tif (typeof el === 'string') {\n\t\t\t// el.removeEvents below apperantly calls this method again. Do not quite understand why, so for now just bail out.\n\t\t\treturn;\n\t\t}\n\t\t\n\t\tif (el.addEvent) { // If el doesn't have an addEvent method, there are no events to remove\n\t\t\tif (type) {\n\t\t\t\tif (type === 'unload') { // Moo self destructs before custom unload events\n\t\t\t\t\ttype = 'beforeunload';\n\t\t\t\t}\n\t\n\t\t\t\tif (fn) {\n\t\t\t\t\tel.removeEvent(type, fn);\n\t\t\t\t} else if (el.removeEvents) { // #958\n\t\t\t\t\tel.removeEvents(type);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tel.removeEvents();\n\t\t\t}\n\t\t}\n\t},\n\n\tfireEvent: function (el, event, eventArguments, defaultFunction) {\n\t\tvar eventArgs = {\n\t\t\ttype: event,\n\t\t\ttarget: el\n\t\t};\n\t\t// create an event object that keeps all functions\n\t\tevent = legacyEvent ? new Event(eventArgs) : new DOMEvent(eventArgs);\n\t\tevent = $extend(event, eventArguments);\n\n\t\t// When running an event on the Chart.prototype, MooTools nests the target in event.event\n\t\tif (!event.target && event.event) {\n\t\t\tevent.target = event.event.target;\n\t\t}\n\n\t\t// override the preventDefault function to be able to use\n\t\t// this for custom events\n\t\tevent.preventDefault = function () {\n\t\t\tdefaultFunction = null;\n\t\t};\n\t\t// if fireEvent is not available on the object, there hasn't been added\n\t\t// any events to it above\n\t\tif (el.fireEvent) {\n\t\t\tel.fireEvent(event.type, event);\n\t\t}\n\n\t\t// fire the default if it is passed and it is not prevented above\n\t\tif (defaultFunction) {\n\t\t\tdefaultFunction(event);\n\t\t}\n\t},\n\t\n\t/**\n\t * Set back e.pageX and e.pageY that MooTools has abstracted away. #1165, #1346.\n\t */\n\twashMouseEvent: function (e) {\n\t\tif (e.page) {\n\t\t\te.pageX = e.page.x;\n\t\t\te.pageY = e.page.y;\n\t\t}\n\t\treturn e;\n\t},\n\n\t/**\n\t * Stop running animations on the object\n\t */\n\tstop: function (el) {\n\t\tif (el.fx) {\n\t\t\tel.fx.cancel();\n\t\t}\n\t}\n};\n\n}());\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/highcharts/adapters/prototype-adapter.js",
    "content": "/*\n Highcharts JS v3.0.6 (2013-10-04)\n Prototype adapter\n\n @author Michael Nelson, Torstein Hønsi.\n\n Feel free to use and modify this script.\n Highcharts license: www.highcharts.com/license.\n*/\nvar HighchartsAdapter=function(){var f=typeof Effect!==\"undefined\";return{init:function(a){if(f)Effect.HighchartsTransition=Class.create(Effect.Base,{initialize:function(b,c,d,g){var e;this.element=b;this.key=c;e=b.attr?b.attr(c):$(b).getStyle(c);if(c===\"d\")this.paths=a.init(b,b.d,d),this.toD=d,e=0,d=1;this.start(Object.extend(g||{},{from:e,to:d,attribute:c}))},setup:function(){HighchartsAdapter._extend(this.element);if(!this.element._highchart_animation)this.element._highchart_animation={};this.element._highchart_animation[this.key]=\nthis},update:function(b){var c=this.paths,d=this.element;c&&(b=a.step(c[0],c[1],b,this.toD));d.attr?d.element&&d.attr(this.options.attribute,b):(c={},c[this.options.attribute]=b,$(d).setStyle(c))},finish:function(){this.element&&this.element._highchart_animation&&delete this.element._highchart_animation[this.key]}})},adapterRun:function(a,b){return parseInt($(a).getStyle(b),10)},getScript:function(a,b){var c=$$(\"head\")[0];c&&c.appendChild((new Element(\"script\",{type:\"text/javascript\",src:a})).observe(\"load\",\nb))},addNS:function(a){var b=/^(?:click|mouse(?:down|up|over|move|out))$/;return/^(?:load|unload|abort|error|select|change|submit|reset|focus|blur|resize|scroll)$/.test(a)||b.test(a)?a:\"h:\"+a},addEvent:function(a,b,c){a.addEventListener||a.attachEvent?Event.observe($(a),HighchartsAdapter.addNS(b),c):(HighchartsAdapter._extend(a),a._highcharts_observe(b,c))},animate:function(a,b,c){var d,c=c||{};c.delay=0;c.duration=(c.duration||500)/1E3;c.afterFinish=c.complete;if(f)for(d in b)new Effect.HighchartsTransition($(a),\nd,b[d],c);else{if(a.attr)for(d in b)a.attr(d,b[d]);c.complete&&c.complete()}a.attr||$(a).setStyle(b)},stop:function(a){var b;if(a._highcharts_extended&&a._highchart_animation)for(b in a._highchart_animation)a._highchart_animation[b].cancel()},each:function(a,b){$A(a).each(b)},inArray:function(a,b,c){return b?b.indexOf(a,c):-1},offset:function(a){return $(a).cumulativeOffset()},fireEvent:function(a,b,c,d){a.fire?a.fire(HighchartsAdapter.addNS(b),c):a._highcharts_extended&&(c=c||{},a._highcharts_fire(b,\nc));c&&c.defaultPrevented&&(d=null);d&&d(c)},removeEvent:function(a,b,c){$(a).stopObserving&&(b&&(b=HighchartsAdapter.addNS(b)),$(a).stopObserving(b,c));window===a?Event.stopObserving(a,b,c):(HighchartsAdapter._extend(a),a._highcharts_stop_observing(b,c))},washMouseEvent:function(a){return a},grep:function(a,b){return a.findAll(b)},map:function(a,b){return a.map(b)},_extend:function(a){a._highcharts_extended||Object.extend(a,{_highchart_events:{},_highchart_animation:null,_highcharts_extended:!0,\n_highcharts_observe:function(b,a){this._highchart_events[b]=[this._highchart_events[b],a].compact().flatten()},_highcharts_stop_observing:function(b,a){b?a?this._highchart_events[b]=[this._highchart_events[b]].compact().flatten().without(a):delete this._highchart_events[b]:this._highchart_events={}},_highcharts_fire:function(a,c){var d=this;(this._highchart_events[a]||[]).each(function(a){if(!c.stopped)c.preventDefault=function(){c.defaultPrevented=!0},c.target=d,a.bind(this)(c)===!1&&c.preventDefault()}.bind(this))}})}}}();\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/highcharts/adapters/prototype-adapter.src.js",
    "content": "/**\n * @license Highcharts JS v3.0.6 (2013-10-04)\n * Prototype adapter\n *\n * @author Michael Nelson, Torstein Hønsi.\n *\n * Feel free to use and modify this script.\n * Highcharts license: www.highcharts.com/license.\n */\n\n// JSLint options:\n/*global Effect, Class, Event, Element, $, $$, $A */\n\n// Adapter interface between prototype and the Highcharts charting library\nvar HighchartsAdapter = (function () {\n\nvar hasEffect = typeof Effect !== 'undefined';\n\nreturn {\n\n\t/**\n\t * Initialize the adapter. This is run once as Highcharts is first run.\n\t * @param {Object} pathAnim The helper object to do animations across adapters.\n\t */\n\tinit: function (pathAnim) {\n\t\tif (hasEffect) {\n\t\t\t/**\n\t\t\t * Animation for Highcharts SVG element wrappers only\n\t\t\t * @param {Object} element\n\t\t\t * @param {Object} attribute\n\t\t\t * @param {Object} to\n\t\t\t * @param {Object} options\n\t\t\t */\n\t\t\tEffect.HighchartsTransition = Class.create(Effect.Base, {\n\t\t\t\tinitialize: function (element, attr, to, options) {\n\t\t\t\t\tvar from,\n\t\t\t\t\t\topts;\n\n\t\t\t\t\tthis.element = element;\n\t\t\t\t\tthis.key = attr;\n\t\t\t\t\tfrom = element.attr ? element.attr(attr) : $(element).getStyle(attr);\n\n\t\t\t\t\t// special treatment for paths\n\t\t\t\t\tif (attr === 'd') {\n\t\t\t\t\t\tthis.paths = pathAnim.init(\n\t\t\t\t\t\t\telement,\n\t\t\t\t\t\t\telement.d,\n\t\t\t\t\t\t\tto\n\t\t\t\t\t\t);\n\t\t\t\t\t\tthis.toD = to;\n\n\n\t\t\t\t\t\t// fake values in order to read relative position as a float in update\n\t\t\t\t\t\tfrom = 0;\n\t\t\t\t\t\tto = 1;\n\t\t\t\t\t}\n\n\t\t\t\t\topts = Object.extend((options || {}), {\n\t\t\t\t\t\tfrom: from,\n\t\t\t\t\t\tto: to,\n\t\t\t\t\t\tattribute: attr\n\t\t\t\t\t});\n\t\t\t\t\tthis.start(opts);\n\t\t\t\t},\n\t\t\t\tsetup: function () {\n\t\t\t\t\tHighchartsAdapter._extend(this.element);\n\t\t\t\t\t// If this is the first animation on this object, create the _highcharts_animation helper that\n\t\t\t\t\t// contain pointers to the animation objects.\n\t\t\t\t\tif (!this.element._highchart_animation) {\n\t\t\t\t\t\tthis.element._highchart_animation = {};\n\t\t\t\t\t}\n\n\t\t\t\t\t// Store a reference to this animation instance.\n\t\t\t\t\tthis.element._highchart_animation[this.key] = this;\n\t\t\t\t},\n\t\t\t\tupdate: function (position) {\n\t\t\t\t\tvar paths = this.paths,\n\t\t\t\t\t\telement = this.element,\n\t\t\t\t\t\tobj;\n\n\t\t\t\t\tif (paths) {\n\t\t\t\t\t\tposition = pathAnim.step(paths[0], paths[1], position, this.toD);\n\t\t\t\t\t}\n\n\t\t\t\t\tif (element.attr) { // SVGElement\n\t\t\t\t\t\t\n\t\t\t\t\t\tif (element.element) { // If not, it has been destroyed (#1405)\n\t\t\t\t\t\t\telement.attr(this.options.attribute, position);\n\t\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\t} else { // HTML, #409\n\t\t\t\t\t\tobj = {};\n\t\t\t\t\t\tobj[this.options.attribute] = position;\n\t\t\t\t\t\t$(element).setStyle(obj);\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t},\n\t\t\t\tfinish: function () {\n\t\t\t\t\t// Delete the property that holds this animation now that it is finished.\n\t\t\t\t\t// Both canceled animations and complete ones gets a 'finish' call.\n\t\t\t\t\tif (this.element && this.element._highchart_animation) { // #1405\n\t\t\t\t\t\tdelete this.element._highchart_animation[this.key];\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t},\n\t\n\t/**\n\t * Run a general method on the framework, following jQuery syntax\n\t * @param {Object} el The HTML element\n\t * @param {String} method Which method to run on the wrapped element\n\t */\n\tadapterRun: function (el, method) {\n\t\t\n\t\t// This currently works for getting inner width and height. If adding\n\t\t// more methods later, we need a conditional implementation for each.\n\t\treturn parseInt($(el).getStyle(method), 10);\n\t\t\n\t},\n\n\t/**\n\t * Downloads a script and executes a callback when done.\n\t * @param {String} scriptLocation\n\t * @param {Function} callback\n\t */\n\tgetScript: function (scriptLocation, callback) {\n\t\tvar head = $$('head')[0]; // Returns an array, so pick the first element.\n\t\tif (head) {\n\t\t\t// Append a new 'script' element, set its type and src attributes, add a 'load' handler that calls the callback\n\t\t\thead.appendChild(new Element('script', { type: 'text/javascript', src: scriptLocation}).observe('load', callback));\n\t\t}\n\t},\n\n\t/**\n\t * Custom events in prototype needs to be namespaced. This method adds a namespace 'h:' in front of\n\t * events that are not recognized as native.\n\t */\n\taddNS: function (eventName) {\n\t\tvar HTMLEvents = /^(?:load|unload|abort|error|select|change|submit|reset|focus|blur|resize|scroll)$/,\n\t\t\tMouseEvents = /^(?:click|mouse(?:down|up|over|move|out))$/;\n\t\treturn (HTMLEvents.test(eventName) || MouseEvents.test(eventName)) ?\n\t\t\teventName :\n\t\t\t'h:' + eventName;\n\t},\n\n\t// el needs an event to be attached. el is not necessarily a dom element\n\taddEvent: function (el, event, fn) {\n\t\tif (el.addEventListener || el.attachEvent) {\n\t\t\tEvent.observe($(el), HighchartsAdapter.addNS(event), fn);\n\n\t\t} else {\n\t\t\tHighchartsAdapter._extend(el);\n\t\t\tel._highcharts_observe(event, fn);\n\t\t}\n\t},\n\n\t// motion makes things pretty. use it if effects is loaded, if not... still get to the end result.\n\tanimate: function (el, params, options) {\n\t\tvar key,\n\t\t\tfx;\n\n\t\t// default options\n\t\toptions = options || {};\n\t\toptions.delay = 0;\n\t\toptions.duration = (options.duration || 500) / 1000;\n\t\toptions.afterFinish = options.complete;\n\n\t\t// animate wrappers and DOM elements\n\t\tif (hasEffect) {\n\t\t\tfor (key in params) {\n\t\t\t\t// The fx variable is seemingly thrown away here, but the Effect.setup will add itself to the _highcharts_animation object\n\t\t\t\t// on the element itself so its not really lost.\n\t\t\t\tfx = new Effect.HighchartsTransition($(el), key, params[key], options);\n\t\t\t}\n\t\t} else {\n\t\t\tif (el.attr) { // #409 without effects\n\t\t\t\tfor (key in params) {\n\t\t\t\t\tel.attr(key, params[key]);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (options.complete) {\n\t\t\t\toptions.complete();\n\t\t\t}\n\t\t}\n\n\t\tif (!el.attr) { // HTML element, #409\n\t\t\t$(el).setStyle(params);\n\t\t}\n\t},\n\n\t// this only occurs in higcharts 2.0+\n\tstop: function (el) {\n\t\tvar key;\n\t\tif (el._highcharts_extended && el._highchart_animation) {\n\t\t\tfor (key in el._highchart_animation) {\n\t\t\t\t// Cancel the animation\n\t\t\t\t// The 'finish' function in the Effect object will remove the reference\n\t\t\t\tel._highchart_animation[key].cancel();\n\t\t\t}\n\t\t}\n\t},\n\n\t// um.. each\n\teach: function (arr, fn) {\n\t\t$A(arr).each(fn);\n\t},\n\t\n\tinArray: function (item, arr, from) {\n\t\treturn arr ? arr.indexOf(item, from) : -1;\n\t},\n\n\t/**\n\t * Get the cumulative offset relative to the top left of the page. This method, unlike its\n\t * jQuery and MooTools counterpart, still suffers from issue #208 regarding the position\n\t * of a chart within a fixed container.\n\t */\n\toffset: function (el) {\n\t\treturn $(el).cumulativeOffset();\n\t},\n\n\t// fire an event based on an event name (event) and an object (el).\n\t// again, el may not be a dom element\n\tfireEvent: function (el, event, eventArguments, defaultFunction) {\n\t\tif (el.fire) {\n\t\t\tel.fire(HighchartsAdapter.addNS(event), eventArguments);\n\t\t} else if (el._highcharts_extended) {\n\t\t\teventArguments = eventArguments || {};\n\t\t\tel._highcharts_fire(event, eventArguments);\n\t\t}\n\n\t\tif (eventArguments && eventArguments.defaultPrevented) {\n\t\t\tdefaultFunction = null;\n\t\t}\n\n\t\tif (defaultFunction) {\n\t\t\tdefaultFunction(eventArguments);\n\t\t}\n\t},\n\n\tremoveEvent: function (el, event, handler) {\n\t\tif ($(el).stopObserving) {\n\t\t\tif (event) {\n\t\t\t\tevent = HighchartsAdapter.addNS(event);\n\t\t\t}\n\t\t\t$(el).stopObserving(event, handler);\n\t\t} if (window === el) {\n\t\t\tEvent.stopObserving(el, event, handler);\n\t\t} else {\n\t\t\tHighchartsAdapter._extend(el);\n\t\t\tel._highcharts_stop_observing(event, handler);\n\t\t}\n\t},\n\t\n\twashMouseEvent: function (e) {\n\t\treturn e;\n\t},\n\n\t// um, grep\n\tgrep: function (arr, fn) {\n\t\treturn arr.findAll(fn);\n\t},\n\n\t// um, map\n\tmap: function (arr, fn) {\n\t\treturn arr.map(fn);\n\t},\n\n\t// extend an object to handle highchart events (highchart objects, not svg elements).\n\t// this is a very simple way of handling events but whatever, it works (i think)\n\t_extend: function (object) {\n\t\tif (!object._highcharts_extended) {\n\t\t\tObject.extend(object, {\n\t\t\t\t_highchart_events: {},\n\t\t\t\t_highchart_animation: null,\n\t\t\t\t_highcharts_extended: true,\n\t\t\t\t_highcharts_observe: function (name, fn) {\n\t\t\t\t\tthis._highchart_events[name] = [this._highchart_events[name], fn].compact().flatten();\n\t\t\t\t},\n\t\t\t\t_highcharts_stop_observing: function (name, fn) {\n\t\t\t\t\tif (name) {\n\t\t\t\t\t\tif (fn) {\n\t\t\t\t\t\t\tthis._highchart_events[name] = [this._highchart_events[name]].compact().flatten().without(fn);\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tdelete this._highchart_events[name];\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tthis._highchart_events = {};\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t_highcharts_fire: function (name, args) {\n\t\t\t\t\tvar target = this;\n\t\t\t\t\t(this._highchart_events[name] || []).each(function (fn) {\n\t\t\t\t\t\t// args is never null here\n\t\t\t\t\t\tif (args.stopped) {\n\t\t\t\t\t\t\treturn; // \"throw $break\" wasn't working. i think because of the scope of 'this'.\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Attach a simple preventDefault function to skip default handler if called\n\t\t\t\t\t\targs.preventDefault = function () {\n\t\t\t\t\t\t\targs.defaultPrevented = true;\n\t\t\t\t\t\t};\n\t\t\t\t\t\targs.target = target;\n\n\t\t\t\t\t\t// If the event handler return false, prevent the default handler from executing\n\t\t\t\t\t\tif (fn.bind(this)(args) === false) {\n\t\t\t\t\t\t\targs.preventDefault();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n.bind(this));\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t}\n};\n}());\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/highcharts/adapters/standalone-framework.js",
    "content": "/*\n Highcharts JS v3.0.6 (2013-10-04)\n\n Standalone Highcharts Framework\n\n License: MIT License\n*/\nvar HighchartsAdapter=function(){function o(c){function a(a,b,d){a.removeEventListener(b,d,!1)}function d(a,b,d){d=a.HCProxiedMethods[d.toString()];a.detachEvent(\"on\"+b,d)}function b(b,c){var f=b.HCEvents,i,g,k,j;if(b.removeEventListener)i=a;else if(b.attachEvent)i=d;else return;c?(g={},g[c]=!0):g=f;for(j in g)if(f[j])for(k=f[j].length;k--;)i(b,j,f[j][k])}c.HCExtended||Highcharts.extend(c,{HCExtended:!0,HCEvents:{},bind:function(b,a){var d=this,c=this.HCEvents,g;if(d.addEventListener)d.addEventListener(b,\na,!1);else if(d.attachEvent){g=function(b){a.call(d,b)};if(!d.HCProxiedMethods)d.HCProxiedMethods={};d.HCProxiedMethods[a.toString()]=g;d.attachEvent(\"on\"+b,g)}c[b]===r&&(c[b]=[]);c[b].push(a)},unbind:function(c,h){var f,i;c?(f=this.HCEvents[c]||[],h?(i=HighchartsAdapter.inArray(h,f),i>-1&&(f.splice(i,1),this.HCEvents[c]=f),this.removeEventListener?a(this,c,h):this.attachEvent&&d(this,c,h)):(b(this,c),this.HCEvents[c]=[])):(b(this),this.HCEvents={})},trigger:function(b,a){var d=this.HCEvents[b]||\n[],c=d.length,g,k,j;k=function(){a.defaultPrevented=!0};for(g=0;g<c;g++){j=d[g];if(a.stopped)break;a.preventDefault=k;a.target=this;a.type=b;j.call(this,a)===!1&&a.preventDefault()}}});return c}var r,l=document,p=[],m=[],q,n;Math.easeInOutSine=function(c,a,d,b){return-d/2*(Math.cos(Math.PI*c/b)-1)+a};return{init:function(c){if(!l.defaultView)this._getStyle=function(a,d){var b;return a.style[d]?a.style[d]:(d===\"opacity\"&&(d=\"filter\"),b=a.currentStyle[d.replace(/\\-(\\w)/g,function(a,b){return b.toUpperCase()})],\nd===\"filter\"&&(b=b.replace(/alpha\\(opacity=([0-9]+)\\)/,function(b,a){return a/100})),b===\"\"?1:b)},this.adapterRun=function(a,d){var b={width:\"clientWidth\",height:\"clientHeight\"}[d];if(b)return a.style.zoom=1,a[b]-2*parseInt(HighchartsAdapter._getStyle(a,\"padding\"),10)};if(!Array.prototype.forEach)this.each=function(a,d){for(var b=0,c=a.length;b<c;b++)if(d.call(a[b],a[b],b,a)===!1)return b};if(!Array.prototype.indexOf)this.inArray=function(a,d){var b,c=0;if(d)for(b=d.length;c<b;c++)if(d[c]===a)return c;\nreturn-1};if(!Array.prototype.filter)this.grep=function(a,d){for(var b=[],c=0,h=a.length;c<h;c++)d(a[c],c)&&b.push(a[c]);return b};n=function(a,c,b){this.options=c;this.elem=a;this.prop=b};n.prototype={update:function(){var a;a=this.paths;var d=this.elem,b=d.element;a&&b?d.attr(\"d\",c.step(a[0],a[1],this.now,this.toD)):d.attr?b&&d.attr(this.prop,this.now):(a={},a[d]=this.now+this.unit,Highcharts.css(d,a));this.options.step&&this.options.step.call(this.elem,this.now,this)},custom:function(a,c,b){var e=\nthis,h=function(a){return e.step(a)},f;this.startTime=+new Date;this.start=a;this.end=c;this.unit=b;this.now=this.start;this.pos=this.state=0;h.elem=this.elem;h()&&m.push(h)===1&&(q=setInterval(function(){for(f=0;f<m.length;f++)m[f]()||m.splice(f--,1);m.length||clearInterval(q)},13))},step:function(a){var c=+new Date,b;b=this.options;var e;if(this.elem.stopAnimation)b=!1;else if(a||c>=b.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();a=this.options.curAnim[this.prop]=\n!0;for(e in b.curAnim)b.curAnim[e]!==!0&&(a=!1);a&&b.complete&&b.complete.call(this.elem);b=!1}else e=c-this.startTime,this.state=e/b.duration,this.pos=b.easing(e,0,1,b.duration),this.now=this.start+(this.end-this.start)*this.pos,this.update(),b=!0;return b}};this.animate=function(a,d,b){var e,h=\"\",f,i,g;a.stopAnimation=!1;if(typeof b!==\"object\"||b===null)e=arguments,b={duration:e[2],easing:e[3],complete:e[4]};if(typeof b.duration!==\"number\")b.duration=400;b.easing=Math[b.easing]||Math.easeInOutSine;\nb.curAnim=Highcharts.extend({},d);for(g in d)i=new n(a,b,g),f=null,g===\"d\"?(i.paths=c.init(a,a.d,d.d),i.toD=d.d,e=0,f=1):a.attr?e=a.attr(g):(e=parseFloat(HighchartsAdapter._getStyle(a,g))||0,g!==\"opacity\"&&(h=\"px\")),f||(f=parseFloat(d[g])),i.custom(e,f,h)}},_getStyle:function(c,a){return window.getComputedStyle(c).getPropertyValue(a)},getScript:function(c,a){var d=l.getElementsByTagName(\"head\")[0],b=l.createElement(\"script\");b.type=\"text/javascript\";b.src=c;b.onload=a;d.appendChild(b)},inArray:function(c,\na){return a.indexOf?a.indexOf(c):p.indexOf.call(a,c)},adapterRun:function(c,a){return parseInt(HighchartsAdapter._getStyle(c,a),10)},grep:function(c,a){return p.filter.call(c,a)},map:function(c,a){for(var d=[],b=0,e=c.length;b<e;b++)d[b]=a.call(c[b],c[b],b,c);return d},offset:function(c){for(var a=0,d=0;c;)a+=c.offsetLeft,d+=c.offsetTop,c=c.offsetParent;return{left:a,top:d}},addEvent:function(c,a,d){o(c).bind(a,d)},removeEvent:function(c,a,d){o(c).unbind(a,d)},fireEvent:function(c,a,d,b){var e;l.createEvent&&\n(c.dispatchEvent||c.fireEvent)?(e=l.createEvent(\"Events\"),e.initEvent(a,!0,!0),e.target=c,Highcharts.extend(e,d),c.dispatchEvent?c.dispatchEvent(e):c.fireEvent(a,e)):c.HCExtended===!0&&(d=d||{},c.trigger(a,d));d&&d.defaultPrevented&&(b=null);b&&b(d)},washMouseEvent:function(c){return c},stop:function(c){c.stopAnimation=!0},each:function(c,a){return Array.prototype.forEach.call(c,a)}}}();\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/highcharts/adapters/standalone-framework.src.js",
    "content": "/**\n * @license Highcharts JS v3.0.6 (2013-10-04)\n *\n * Standalone Highcharts Framework\n *\n * License: MIT License\n */\n\n\n/*global Highcharts */\nvar HighchartsAdapter = (function () {\n\nvar UNDEFINED,\n\tdoc = document,\n\temptyArray = [],\n\ttimers = [],\n\ttimerId,\n\tFx;\n\nMath.easeInOutSine = function (t, b, c, d) {\n\treturn -c / 2 * (Math.cos(Math.PI * t / d) - 1) + b;\n};\n\n\n\n/**\n * Extend given object with custom events\n */\nfunction augment(obj) {\n\tfunction removeOneEvent(el, type, fn) {\n\t\tel.removeEventListener(type, fn, false);\n\t}\n\n\tfunction IERemoveOneEvent(el, type, fn) {\n\t\tfn = el.HCProxiedMethods[fn.toString()];\n\t\tel.detachEvent('on' + type, fn);\n\t}\n\n\tfunction removeAllEvents(el, type) {\n\t\tvar events = el.HCEvents,\n\t\t\tremove,\n\t\t\ttypes,\n\t\t\tlen,\n\t\t\tn;\n\n\t\tif (el.removeEventListener) {\n\t\t\tremove = removeOneEvent;\n\t\t} else if (el.attachEvent) {\n\t\t\tremove = IERemoveOneEvent;\n\t\t} else {\n\t\t\treturn; // break on non-DOM events\n\t\t}\n\n\n\t\tif (type) {\n\t\t\ttypes = {};\n\t\t\ttypes[type] = true;\n\t\t} else {\n\t\t\ttypes = events;\n\t\t}\n\n\t\tfor (n in types) {\n\t\t\tif (events[n]) {\n\t\t\t\tlen = events[n].length;\n\t\t\t\twhile (len--) {\n\t\t\t\t\tremove(el, n, events[n][len]);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (!obj.HCExtended) {\n\t\tHighcharts.extend(obj, {\n\t\t\tHCExtended: true,\n\n\t\t\tHCEvents: {},\n\n\t\t\tbind: function (name, fn) {\n\t\t\t\tvar el = this,\n\t\t\t\t\tevents = this.HCEvents,\n\t\t\t\t\twrappedFn;\n\n\t\t\t\t// handle DOM events in modern browsers\n\t\t\t\tif (el.addEventListener) {\n\t\t\t\t\tel.addEventListener(name, fn, false);\n\n\t\t\t\t// handle old IE implementation\n\t\t\t\t} else if (el.attachEvent) {\n\t\t\t\t\t\n\t\t\t\t\twrappedFn = function (e) {\n\t\t\t\t\t\tfn.call(el, e);\n\t\t\t\t\t};\n\n\t\t\t\t\tif (!el.HCProxiedMethods) {\n\t\t\t\t\t\tel.HCProxiedMethods = {};\n\t\t\t\t\t}\n\n\t\t\t\t\t// link wrapped fn with original fn, so we can get this in removeEvent\n\t\t\t\t\tel.HCProxiedMethods[fn.toString()] = wrappedFn;\n\n\t\t\t\t\tel.attachEvent('on' + name, wrappedFn);\n\t\t\t\t}\n\n\n\t\t\t\tif (events[name] === UNDEFINED) {\n\t\t\t\t\tevents[name] = [];\n\t\t\t\t}\n\n\t\t\t\tevents[name].push(fn);\n\t\t\t},\n\n\t\t\tunbind: function (name, fn) {\n\t\t\t\tvar events,\n\t\t\t\t\tindex;\n\n\t\t\t\tif (name) {\n\t\t\t\t\tevents = this.HCEvents[name] || [];\n\t\t\t\t\tif (fn) {\n\t\t\t\t\t\tindex = HighchartsAdapter.inArray(fn, events);\n\t\t\t\t\t\tif (index > -1) {\n\t\t\t\t\t\t\tevents.splice(index, 1);\n\t\t\t\t\t\t\tthis.HCEvents[name] = events;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (this.removeEventListener) {\n\t\t\t\t\t\t\tremoveOneEvent(this, name, fn);\n\t\t\t\t\t\t} else if (this.attachEvent) {\n\t\t\t\t\t\t\tIERemoveOneEvent(this, name, fn);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tremoveAllEvents(this, name);\n\t\t\t\t\t\tthis.HCEvents[name] = [];\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tremoveAllEvents(this);\n\t\t\t\t\tthis.HCEvents = {};\n\t\t\t\t}\n\t\t\t},\n\n\t\t\ttrigger: function (name, args) {\n\t\t\t\tvar events = this.HCEvents[name] || [],\n\t\t\t\t\ttarget = this,\n\t\t\t\t\tlen = events.length,\n\t\t\t\t\ti,\n\t\t\t\t\tpreventDefault,\n\t\t\t\t\tfn;\n\n\t\t\t\t// Attach a simple preventDefault function to skip default handler if called\n\t\t\t\tpreventDefault = function () {\n\t\t\t\t\targs.defaultPrevented = true;\n\t\t\t\t};\n\t\t\t\t\n\t\t\t\tfor (i = 0; i < len; i++) {\n\t\t\t\t\tfn = events[i];\n\n\t\t\t\t\t// args is never null here\n\t\t\t\t\tif (args.stopped) {\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\targs.preventDefault = preventDefault;\n\t\t\t\t\targs.target = target;\n\t\t\t\t\targs.type = name; // #2297\t\n\t\t\t\t\t\n\t\t\t\t\t// If the event handler return false, prevent the default handler from executing\n\t\t\t\t\tif (fn.call(this, args) === false) {\n\t\t\t\t\t\targs.preventDefault();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\n\treturn obj;\n}\n\n\nreturn {\n\t/**\n\t * Initialize the adapter. This is run once as Highcharts is first run.\n\t */\n\tinit: function (pathAnim) {\n\n\t\t/**\n\t\t * Compatibility section to add support for legacy IE. This can be removed if old IE \n\t\t * support is not needed.\n\t\t */\n\t\tif (!doc.defaultView) {\n\t\t\tthis._getStyle = function (el, prop) {\n\t\t\t\tvar val;\n\t\t\t\tif (el.style[prop]) {\n\t\t\t\t\treturn el.style[prop];\n\t\t\t\t} else {\n\t\t\t\t\tif (prop === 'opacity') {\n\t\t\t\t\t\tprop = 'filter';\n\t\t\t\t\t}\n\t\t\t\t\t/*jslint unparam: true*/\n\t\t\t\t\tval = el.currentStyle[prop.replace(/\\-(\\w)/g, function (a, b) { return b.toUpperCase(); })];\n\t\t\t\t\tif (prop === 'filter') {\n\t\t\t\t\t\tval = val.replace(\n\t\t\t\t\t\t\t/alpha\\(opacity=([0-9]+)\\)/, \n\t\t\t\t\t\t\tfunction (a, b) { \n\t\t\t\t\t\t\t\treturn b / 100; \n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t\t/*jslint unparam: false*/\n\t\t\t\t\treturn val === '' ? 1 : val;\n\t\t\t\t} \n\t\t\t};\n\t\t\tthis.adapterRun = function (elem, method) {\n\t\t\t\tvar alias = { width: 'clientWidth', height: 'clientHeight' }[method];\n\n\t\t\t\tif (alias) {\n\t\t\t\t\telem.style.zoom = 1;\n\t\t\t\t\treturn elem[alias] - 2 * parseInt(HighchartsAdapter._getStyle(elem, 'padding'), 10);\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\n\t\tif (!Array.prototype.forEach) {\n\t\t\tthis.each = function (arr, fn) { // legacy\n\t\t\t\tvar i = 0, \n\t\t\t\t\tlen = arr.length;\n\t\t\t\tfor (; i < len; i++) {\n\t\t\t\t\tif (fn.call(arr[i], arr[i], i, arr) === false) {\n\t\t\t\t\t\treturn i;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\n\t\tif (!Array.prototype.indexOf) {\n\t\t\tthis.inArray = function (item, arr) {\n\t\t\t\tvar len, \n\t\t\t\t\ti = 0;\n\n\t\t\t\tif (arr) {\n\t\t\t\t\tlen = arr.length;\n\t\t\t\t\t\n\t\t\t\t\tfor (; i < len; i++) {\n\t\t\t\t\t\tif (arr[i] === item) {\n\t\t\t\t\t\t\treturn i;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn -1;\n\t\t\t};\n\t\t}\n\n\t\tif (!Array.prototype.filter) {\n\t\t\tthis.grep = function (elements, callback) {\n\t\t\t\tvar ret = [],\n\t\t\t\t\ti = 0,\n\t\t\t\t\tlength = elements.length;\n\n\t\t\t\tfor (; i < length; i++) {\n\t\t\t\t\tif (!!callback(elements[i], i)) {\n\t\t\t\t\t\tret.push(elements[i]);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn ret;\n\t\t\t};\n\t\t}\n\n\t\t//--- End compatibility section ---\n\n\n\t\t/**\n\t\t * Start of animation specific code\n\t\t */\n\t\tFx = function (elem, options, prop) {\n\t\t\tthis.options = options;\n\t\t\tthis.elem = elem;\n\t\t\tthis.prop = prop;\n\t\t};\n\t\tFx.prototype = {\n\t\t\t\n\t\t\tupdate: function () {\n\t\t\t\tvar styles,\n\t\t\t\t\tpaths = this.paths,\n\t\t\t\t\telem = this.elem,\n\t\t\t\t\telemelem = elem.element; // if destroyed, it is null\n\n\t\t\t\t// Animating a path definition on SVGElement\n\t\t\t\tif (paths && elemelem) {\n\t\t\t\t\telem.attr('d', pathAnim.step(paths[0], paths[1], this.now, this.toD));\n\t\t\t\t\n\t\t\t\t// Other animations on SVGElement\n\t\t\t\t} else if (elem.attr) {\n\t\t\t\t\tif (elemelem) {\n\t\t\t\t\t\telem.attr(this.prop, this.now);\n\t\t\t\t\t}\n\n\t\t\t\t// HTML styles\n\t\t\t\t} else {\n\t\t\t\t\tstyles = {};\n\t\t\t\t\tstyles[elem] = this.now + this.unit;\n\t\t\t\t\tHighcharts.css(elem, styles);\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tif (this.options.step) {\n\t\t\t\t\tthis.options.step.call(this.elem, this.now, this);\n\t\t\t\t}\n\n\t\t\t},\n\t\t\tcustom: function (from, to, unit) {\n\t\t\t\tvar self = this,\n\t\t\t\t\tt = function (gotoEnd) {\n\t\t\t\t\t\treturn self.step(gotoEnd);\n\t\t\t\t\t},\n\t\t\t\t\ti;\n\n\t\t\t\tthis.startTime = +new Date();\n\t\t\t\tthis.start = from;\n\t\t\t\tthis.end = to;\n\t\t\t\tthis.unit = unit;\n\t\t\t\tthis.now = this.start;\n\t\t\t\tthis.pos = this.state = 0;\n\n\t\t\t\tt.elem = this.elem;\n\n\t\t\t\tif (t() && timers.push(t) === 1) {\n\t\t\t\t\ttimerId = setInterval(function () {\n\t\t\t\t\t\t\n\t\t\t\t\t\tfor (i = 0; i < timers.length; i++) {\n\t\t\t\t\t\t\tif (!timers[i]()) {\n\t\t\t\t\t\t\t\ttimers.splice(i--, 1);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tif (!timers.length) {\n\t\t\t\t\t\t\tclearInterval(timerId);\n\t\t\t\t\t\t}\n\t\t\t\t\t}, 13);\n\t\t\t\t}\n\t\t\t},\n\t\t\t\n\t\t\tstep: function (gotoEnd) {\n\t\t\t\tvar t = +new Date(),\n\t\t\t\t\tret,\n\t\t\t\t\tdone,\n\t\t\t\t\toptions = this.options,\n\t\t\t\t\ti;\n\n\t\t\t\tif (this.elem.stopAnimation) {\n\t\t\t\t\tret = false;\n\n\t\t\t\t} else if (gotoEnd || t >= options.duration + this.startTime) {\n\t\t\t\t\tthis.now = this.end;\n\t\t\t\t\tthis.pos = this.state = 1;\n\t\t\t\t\tthis.update();\n\n\t\t\t\t\tthis.options.curAnim[this.prop] = true;\n\n\t\t\t\t\tdone = true;\n\t\t\t\t\tfor (i in options.curAnim) {\n\t\t\t\t\t\tif (options.curAnim[i] !== true) {\n\t\t\t\t\t\t\tdone = false;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tif (done) {\n\t\t\t\t\t\tif (options.complete) {\n\t\t\t\t\t\t\toptions.complete.call(this.elem);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tret = false;\n\n\t\t\t\t} else {\n\t\t\t\t\tvar n = t - this.startTime;\n\t\t\t\t\tthis.state = n / options.duration;\n\t\t\t\t\tthis.pos = options.easing(n, 0, 1, options.duration);\n\t\t\t\t\tthis.now = this.start + ((this.end - this.start) * this.pos);\n\t\t\t\t\tthis.update();\n\t\t\t\t\tret = true;\n\t\t\t\t}\n\t\t\t\treturn ret;\n\t\t\t}\n\t\t};\n\n\t\t/**\n\t\t * The adapter animate method\n\t\t */\n\t\tthis.animate = function (el, prop, opt) {\n\t\t\tvar start,\n\t\t\t\tunit = '',\n\t\t\t\tend,\n\t\t\t\tfx,\n\t\t\t\targs,\n\t\t\t\tname;\n\n\t\t\tel.stopAnimation = false; // ready for new\n\n\t\t\tif (typeof opt !== 'object' || opt === null) {\n\t\t\t\targs = arguments;\n\t\t\t\topt = {\n\t\t\t\t\tduration: args[2],\n\t\t\t\t\teasing: args[3],\n\t\t\t\t\tcomplete: args[4]\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (typeof opt.duration !== 'number') {\n\t\t\t\topt.duration = 400;\n\t\t\t}\n\t\t\topt.easing = Math[opt.easing] || Math.easeInOutSine;\n\t\t\topt.curAnim = Highcharts.extend({}, prop);\n\t\t\t\n\t\t\tfor (name in prop) {\n\t\t\t\tfx = new Fx(el, opt, name);\n\t\t\t\tend = null;\n\t\t\t\t\n\t\t\t\tif (name === 'd') {\n\t\t\t\t\tfx.paths = pathAnim.init(\n\t\t\t\t\t\tel,\n\t\t\t\t\t\tel.d,\n\t\t\t\t\t\tprop.d\n\t\t\t\t\t);\n\t\t\t\t\tfx.toD = prop.d;\n\t\t\t\t\tstart = 0;\n\t\t\t\t\tend = 1;\n\t\t\t\t} else if (el.attr) {\n\t\t\t\t\tstart = el.attr(name);\n\t\t\t\t} else {\n\t\t\t\t\tstart = parseFloat(HighchartsAdapter._getStyle(el, name)) || 0;\n\t\t\t\t\tif (name !== 'opacity') {\n\t\t\t\t\t\tunit = 'px';\n\t\t\t\t\t}\n\t\t\t\t}\n\t\n\t\t\t\tif (!end) {\n\t\t\t\t\tend = parseFloat(prop[name]);\n\t\t\t\t}\n\t\t\t\tfx.custom(start, end, unit);\n\t\t\t}\t\n\t\t};\n\t},\n\n\t/**\n\t * Internal method to return CSS value for given element and property\n\t */\n\t_getStyle: function (el, prop) {\n\t\treturn window.getComputedStyle(el).getPropertyValue(prop);\n\t},\n\n\t/**\n\t * Downloads a script and executes a callback when done.\n\t * @param {String} scriptLocation\n\t * @param {Function} callback\n\t */\n\tgetScript: function (scriptLocation, callback) {\n\t\t// We cannot assume that Assets class from mootools-more is available so instead insert a script tag to download script.\n\t\tvar head = doc.getElementsByTagName('head')[0],\n\t\t\tscript = doc.createElement('script');\n\n\t\tscript.type = 'text/javascript';\n\t\tscript.src = scriptLocation;\n\t\tscript.onload = callback;\n\n\t\thead.appendChild(script);\n\t},\n\n\t/**\n\t * Return the index of an item in an array, or -1 if not found\n\t */\n\tinArray: function (item, arr) {\n\t\treturn arr.indexOf ? arr.indexOf(item) : emptyArray.indexOf.call(arr, item);\n\t},\n\n\n\t/**\n\t * A direct link to adapter methods\n\t */\n\tadapterRun: function (elem, method) {\n\t\treturn parseInt(HighchartsAdapter._getStyle(elem, method), 10);\n\t},\n\n\t/**\n\t * Filter an array\n\t */\n\tgrep: function (elements, callback) {\n\t\treturn emptyArray.filter.call(elements, callback);\n\t},\n\n\t/**\n\t * Map an array\n\t */\n\tmap: function (arr, fn) {\n\t\tvar results = [], i = 0, len = arr.length;\n\n\t\tfor (; i < len; i++) {\n\t\t\tresults[i] = fn.call(arr[i], arr[i], i, arr);\n\t\t}\n\n\t\treturn results;\n\t},\n\n\toffset: function (el) {\n\t\tvar left = 0,\n\t\t\ttop = 0;\n\n\t\twhile (el) {\n\t\t\tleft += el.offsetLeft;\n\t\t\ttop += el.offsetTop;\n\t\t\tel = el.offsetParent;\n\t\t}\n\n\t\treturn {\n\t\t\tleft: left,\n\t\t\ttop: top\n\t\t};\n\t},\n\n\t/**\n\t * Add an event listener\n\t */\n\taddEvent: function (el, type, fn) {\n\t\taugment(el).bind(type, fn);\n\t},\n\n\t/**\n\t * Remove event added with addEvent\n\t */\n\tremoveEvent: function (el, type, fn) {\n\t\taugment(el).unbind(type, fn);\n\t},\n\n\t/**\n\t * Fire an event on a custom object\n\t */\n\tfireEvent: function (el, type, eventArguments, defaultFunction) {\n\t\tvar e;\n\n\t\tif (doc.createEvent && (el.dispatchEvent || el.fireEvent)) {\n\t\t\te = doc.createEvent('Events');\n\t\t\te.initEvent(type, true, true);\n\t\t\te.target = el;\n\n\t\t\tHighcharts.extend(e, eventArguments);\n\n\t\t\tif (el.dispatchEvent) {\n\t\t\t\tel.dispatchEvent(e);\n\t\t\t} else {\n\t\t\t\tel.fireEvent(type, e);\n\t\t\t}\n\n\t\t} else if (el.HCExtended === true) {\n\t\t\teventArguments = eventArguments || {};\n\t\t\tel.trigger(type, eventArguments);\n\t\t}\n\n\t\tif (eventArguments && eventArguments.defaultPrevented) {\n\t\t\tdefaultFunction = null;\n\t\t}\n\n\t\tif (defaultFunction) {\n\t\t\tdefaultFunction(eventArguments);\n\t\t}\n\t},\n\n\twashMouseEvent: function (e) {\n\t\treturn e;\n\t},\n\n\n\t/**\n\t * Stop running animation\n\t */\n\tstop: function (el) {\n\t\tel.stopAnimation = true;\n\t},\n\n\t/**\n\t * Utility for iterating over an array. Parameters are reversed compared to jQuery.\n\t * @param {Array} arr\n\t * @param {Function} fn\n\t */\n\teach: function (arr, fn) { // modern browsers\n\t\treturn Array.prototype.forEach.call(arr, fn);\n\t}\n};\n}());\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/highcharts/highcharts-more.js",
    "content": "/*\n Highcharts JS v3.0.6 (2013-10-04)\n\n (c) 2009-2013 Torstein Hønsi\n\n License: www.highcharts.com/license\n*/\n(function(j,C){function J(a,b,c){this.init.call(this,a,b,c)}function K(a,b,c){a.call(this,b,c);if(this.chart.polar)this.closeSegment=function(a){var c=this.xAxis.center;a.push(\"L\",c[0],c[1])},this.closedStacks=!0}function L(a,b){var c=this.chart,d=this.options.animation,g=this.group,f=this.markerGroup,e=this.xAxis.center,i=c.plotLeft,n=c.plotTop;if(c.polar){if(c.renderer.isSVG)if(d===!0&&(d={}),b){if(c={translateX:e[0]+i,translateY:e[1]+n,scaleX:0.001,scaleY:0.001},g.attr(c),f)f.attrSetters=g.attrSetters,\nf.attr(c)}else c={translateX:i,translateY:n,scaleX:1,scaleY:1},g.animate(c,d),f&&f.animate(c,d),this.animate=null}else a.call(this,b)}var P=j.arrayMin,Q=j.arrayMax,s=j.each,F=j.extend,p=j.merge,R=j.map,r=j.pick,v=j.pInt,m=j.getOptions().plotOptions,h=j.seriesTypes,x=j.extendClass,M=j.splat,o=j.wrap,N=j.Axis,u=j.Tick,z=j.Series,q=h.column.prototype,t=Math,D=t.round,A=t.floor,S=t.max,w=function(){};F(J.prototype,{init:function(a,b,c){var d=this,g=d.defaultOptions;d.chart=b;if(b.angular)g.background=\n{};d.options=a=p(g,a);(a=a.background)&&s([].concat(M(a)).reverse(),function(a){var b=a.backgroundColor,a=p(d.defaultBackgroundOptions,a);if(b)a.backgroundColor=b;a.color=a.backgroundColor;c.options.plotBands.unshift(a)})},defaultOptions:{center:[\"50%\",\"50%\"],size:\"85%\",startAngle:0},defaultBackgroundOptions:{shape:\"circle\",borderWidth:1,borderColor:\"silver\",backgroundColor:{linearGradient:{x1:0,y1:0,x2:0,y2:1},stops:[[0,\"#FFF\"],[1,\"#DDD\"]]},from:Number.MIN_VALUE,innerRadius:0,to:Number.MAX_VALUE,\nouterRadius:\"105%\"}});var G=N.prototype,u=u.prototype,T={getOffset:w,redraw:function(){this.isDirty=!1},render:function(){this.isDirty=!1},setScale:w,setCategories:w,setTitle:w},O={isRadial:!0,defaultRadialGaugeOptions:{labels:{align:\"center\",x:0,y:null},minorGridLineWidth:0,minorTickInterval:\"auto\",minorTickLength:10,minorTickPosition:\"inside\",minorTickWidth:1,plotBands:[],tickLength:10,tickPosition:\"inside\",tickWidth:2,title:{rotation:0},zIndex:2},defaultRadialXOptions:{gridLineWidth:1,labels:{align:null,\ndistance:15,x:0,y:null},maxPadding:0,minPadding:0,plotBands:[],showLastLabel:!1,tickLength:0},defaultRadialYOptions:{gridLineInterpolation:\"circle\",labels:{align:\"right\",x:-3,y:-2},plotBands:[],showLastLabel:!1,title:{x:4,text:null,rotation:90}},setOptions:function(a){this.options=p(this.defaultOptions,this.defaultRadialOptions,a)},getOffset:function(){G.getOffset.call(this);this.chart.axisOffset[this.side]=0},getLinePath:function(a,b){var c=this.center,b=r(b,c[2]/2-this.offset);return this.chart.renderer.symbols.arc(this.left+\nc[0],this.top+c[1],b,b,{start:this.startAngleRad,end:this.endAngleRad,open:!0,innerR:0})},setAxisTranslation:function(){G.setAxisTranslation.call(this);if(this.center&&(this.transA=this.isCircular?(this.endAngleRad-this.startAngleRad)/(this.max-this.min||1):this.center[2]/2/(this.max-this.min||1),this.isXAxis))this.minPixelPadding=this.transA*this.minPointOffset+(this.reversed?(this.endAngleRad-this.startAngleRad)/4:0)},beforeSetTickPositions:function(){this.autoConnect&&(this.max+=this.categories&&\n1||this.pointRange||this.closestPointRange||0)},setAxisSize:function(){G.setAxisSize.call(this);if(this.isRadial)this.center=this.pane.center=h.pie.prototype.getCenter.call(this.pane),this.len=this.width=this.height=this.isCircular?this.center[2]*(this.endAngleRad-this.startAngleRad)/2:this.center[2]/2},getPosition:function(a,b){if(!this.isCircular)b=this.translate(a),a=this.min;return this.postTranslate(this.translate(a),r(b,this.center[2]/2)-this.offset)},postTranslate:function(a,b){var c=this.chart,\nd=this.center,a=this.startAngleRad+a;return{x:c.plotLeft+d[0]+Math.cos(a)*b,y:c.plotTop+d[1]+Math.sin(a)*b}},getPlotBandPath:function(a,b,c){var d=this.center,g=this.startAngleRad,f=d[2]/2,e=[r(c.outerRadius,\"100%\"),c.innerRadius,r(c.thickness,10)],i=/%$/,n,l=this.isCircular;this.options.gridLineInterpolation===\"polygon\"?d=this.getPlotLinePath(a).concat(this.getPlotLinePath(b,!0)):(l||(e[0]=this.translate(a),e[1]=this.translate(b)),e=R(e,function(a){i.test(a)&&(a=v(a,10)*f/100);return a}),c.shape===\n\"circle\"||!l?(a=-Math.PI/2,b=Math.PI*1.5,n=!0):(a=g+this.translate(a),b=g+this.translate(b)),d=this.chart.renderer.symbols.arc(this.left+d[0],this.top+d[1],e[0],e[0],{start:a,end:b,innerR:r(e[1],e[0]-e[2]),open:n}));return d},getPlotLinePath:function(a,b){var c=this.center,d=this.chart,g=this.getPosition(a),f,e,i;this.isCircular?i=[\"M\",c[0]+d.plotLeft,c[1]+d.plotTop,\"L\",g.x,g.y]:this.options.gridLineInterpolation===\"circle\"?(a=this.translate(a))&&(i=this.getLinePath(0,a)):(f=d.xAxis[0],i=[],a=this.translate(a),\nc=f.tickPositions,f.autoConnect&&(c=c.concat([c[0]])),b&&(c=[].concat(c).reverse()),s(c,function(c,b){e=f.getPosition(c,a);i.push(b?\"L\":\"M\",e.x,e.y)}));return i},getTitlePosition:function(){var a=this.center,b=this.chart,c=this.options.title;return{x:b.plotLeft+a[0]+(c.x||0),y:b.plotTop+a[1]-{high:0.5,middle:0.25,low:0}[c.align]*a[2]+(c.y||0)}}};o(G,\"init\",function(a,b,c){var k;var d=b.angular,g=b.polar,f=c.isX,e=d&&f,i,n;n=b.options;var l=c.pane||0;if(d){if(F(this,e?T:O),i=!f)this.defaultRadialOptions=\nthis.defaultRadialGaugeOptions}else if(g)F(this,O),this.defaultRadialOptions=(i=f)?this.defaultRadialXOptions:p(this.defaultYAxisOptions,this.defaultRadialYOptions);a.call(this,b,c);if(!e&&(d||g)){a=this.options;if(!b.panes)b.panes=[];this.pane=(k=b.panes[l]=b.panes[l]||new J(M(n.pane)[l],b,this),l=k);l=l.options;b.inverted=!1;n.chart.zoomType=null;this.startAngleRad=b=(l.startAngle-90)*Math.PI/180;this.endAngleRad=n=(r(l.endAngle,l.startAngle+360)-90)*Math.PI/180;this.offset=a.offset||0;if((this.isCircular=\ni)&&c.max===C&&n-b===2*Math.PI)this.autoConnect=!0}});o(u,\"getPosition\",function(a,b,c,d,g){var f=this.axis;return f.getPosition?f.getPosition(c):a.call(this,b,c,d,g)});o(u,\"getLabelPosition\",function(a,b,c,d,g,f,e,i,n){var l=this.axis,k=f.y,h=f.align,j=(l.translate(this.pos)+l.startAngleRad+Math.PI/2)/Math.PI*180%360;l.isRadial?(a=l.getPosition(this.pos,l.center[2]/2+r(f.distance,-25)),f.rotation===\"auto\"?d.attr({rotation:j}):k===null&&(k=v(d.styles.lineHeight)*0.9-d.getBBox().height/2),h===null&&\n(h=l.isCircular?j>20&&j<160?\"left\":j>200&&j<340?\"right\":\"center\":\"center\",d.attr({align:h})),a.x+=f.x,a.y+=k):a=a.call(this,b,c,d,g,f,e,i,n);return a});o(u,\"getMarkPath\",function(a,b,c,d,g,f,e){var i=this.axis;i.isRadial?(a=i.getPosition(this.pos,i.center[2]/2+d),b=[\"M\",b,c,\"L\",a.x,a.y]):b=a.call(this,b,c,d,g,f,e);return b});m.arearange=p(m.area,{lineWidth:1,marker:null,threshold:null,tooltip:{pointFormat:'<span style=\"color:{series.color}\">{series.name}</span>: <b>{point.low}</b> - <b>{point.high}</b><br/>'},\ntrackByArea:!0,dataLabels:{verticalAlign:null,xLow:0,xHigh:0,yLow:0,yHigh:0}});h.arearange=j.extendClass(h.area,{type:\"arearange\",pointArrayMap:[\"low\",\"high\"],toYData:function(a){return[a.low,a.high]},pointValKey:\"low\",getSegments:function(){var a=this;s(a.points,function(b){if(!a.options.connectNulls&&(b.low===null||b.high===null))b.y=null;else if(b.low===null&&b.high!==null)b.y=b.high});z.prototype.getSegments.call(this)},translate:function(){var a=this.yAxis;h.area.prototype.translate.apply(this);\ns(this.points,function(b){var c=b.low,d=b.high,g=b.plotY;d===null&&c===null?b.y=null:c===null?(b.plotLow=b.plotY=null,b.plotHigh=a.translate(d,0,1,0,1)):d===null?(b.plotLow=g,b.plotHigh=null):(b.plotLow=g,b.plotHigh=a.translate(d,0,1,0,1))})},getSegmentPath:function(a){var b,c=[],d=a.length,g=z.prototype.getSegmentPath,f,e;e=this.options;var i=e.step;for(b=HighchartsAdapter.grep(a,function(a){return a.plotLow!==null});d--;)f=a[d],f.plotHigh!==null&&c.push({plotX:f.plotX,plotY:f.plotHigh});a=g.call(this,\nb);if(i)i===!0&&(i=\"left\"),e.step={left:\"right\",center:\"center\",right:\"left\"}[i];c=g.call(this,c);e.step=i;e=[].concat(a,c);c[0]=\"L\";this.areaPath=this.areaPath.concat(a,c);return e},drawDataLabels:function(){var a=this.data,b=a.length,c,d=[],g=z.prototype,f=this.options.dataLabels,e,i=this.chart.inverted;if(f.enabled||this._hasPointLabels){for(c=b;c--;)e=a[c],e.y=e.high,e.plotY=e.plotHigh,d[c]=e.dataLabel,e.dataLabel=e.dataLabelUpper,e.below=!1,i?(f.align=\"left\",f.x=f.xHigh):f.y=f.yHigh;g.drawDataLabels.apply(this,\narguments);for(c=b;c--;)e=a[c],e.dataLabelUpper=e.dataLabel,e.dataLabel=d[c],e.y=e.low,e.plotY=e.plotLow,e.below=!0,i?(f.align=\"right\",f.x=f.xLow):f.y=f.yLow;g.drawDataLabels.apply(this,arguments)}},alignDataLabel:h.column.prototype.alignDataLabel,getSymbol:h.column.prototype.getSymbol,drawPoints:w});m.areasplinerange=p(m.arearange);h.areasplinerange=x(h.arearange,{type:\"areasplinerange\",getPointSpline:h.spline.prototype.getPointSpline});m.columnrange=p(m.column,m.arearange,{lineWidth:1,pointRange:null});\nh.columnrange=x(h.arearange,{type:\"columnrange\",translate:function(){var a=this,b=a.yAxis,c;q.translate.apply(a);s(a.points,function(d){var g=d.shapeArgs,f=a.options.minPointLength,e;d.plotHigh=c=b.translate(d.high,0,1,0,1);d.plotLow=d.plotY;e=c;d=d.plotY-c;d<f&&(f-=d,d+=f,e-=f/2);g.height=d;g.y=e})},trackerGroups:[\"group\",\"dataLabels\"],drawGraph:w,pointAttrToOptions:q.pointAttrToOptions,drawPoints:q.drawPoints,drawTracker:q.drawTracker,animate:q.animate,getColumnMetrics:q.getColumnMetrics});m.gauge=\np(m.line,{dataLabels:{enabled:!0,y:15,borderWidth:1,borderColor:\"silver\",borderRadius:3,style:{fontWeight:\"bold\"},verticalAlign:\"top\",zIndex:2},dial:{},pivot:{},tooltip:{headerFormat:\"\"},showInLegend:!1});u={type:\"gauge\",pointClass:j.extendClass(j.Point,{setState:function(a){this.state=a}}),angular:!0,drawGraph:w,fixedBox:!0,trackerGroups:[\"group\",\"dataLabels\"],translate:function(){var a=this.yAxis,b=this.options,c=a.center;this.generatePoints();s(this.points,function(d){var g=p(b.dial,d.dial),f=\nv(r(g.radius,80))*c[2]/200,e=v(r(g.baseLength,70))*f/100,i=v(r(g.rearLength,10))*f/100,n=g.baseWidth||3,l=g.topWidth||1,k=a.startAngleRad+a.translate(d.y,null,null,null,!0);b.wrap===!1&&(k=Math.max(a.startAngleRad,Math.min(a.endAngleRad,k)));k=k*180/Math.PI;d.shapeType=\"path\";d.shapeArgs={d:g.path||[\"M\",-i,-n/2,\"L\",e,-n/2,f,-l/2,f,l/2,e,n/2,-i,n/2,\"z\"],translateX:c[0],translateY:c[1],rotation:k};d.plotX=c[0];d.plotY=c[1]})},drawPoints:function(){var a=this,b=a.yAxis.center,c=a.pivot,d=a.options,g=\nd.pivot,f=a.chart.renderer;s(a.points,function(c){var b=c.graphic,g=c.shapeArgs,l=g.d,k=p(d.dial,c.dial);b?(b.animate(g),g.d=l):c.graphic=f[c.shapeType](g).attr({stroke:k.borderColor||\"none\",\"stroke-width\":k.borderWidth||0,fill:k.backgroundColor||\"black\",rotation:g.rotation}).add(a.group)});c?c.animate({translateX:b[0],translateY:b[1]}):a.pivot=f.circle(0,0,r(g.radius,5)).attr({\"stroke-width\":g.borderWidth||0,stroke:g.borderColor||\"silver\",fill:g.backgroundColor||\"black\"}).translate(b[0],b[1]).add(a.group)},\nanimate:function(a){var b=this;if(!a)s(b.points,function(a){var d=a.graphic;d&&(d.attr({rotation:b.yAxis.startAngleRad*180/Math.PI}),d.animate({rotation:a.shapeArgs.rotation},b.options.animation))}),b.animate=null},render:function(){this.group=this.plotGroup(\"group\",\"series\",this.visible?\"visible\":\"hidden\",this.options.zIndex,this.chart.seriesGroup);h.pie.prototype.render.call(this);this.group.clip(this.chart.clipRect)},setData:h.pie.prototype.setData,drawTracker:h.column.prototype.drawTracker};h.gauge=\nj.extendClass(h.line,u);m.boxplot=p(m.column,{fillColor:\"#FFFFFF\",lineWidth:1,medianWidth:2,states:{hover:{brightness:-0.3}},threshold:null,tooltip:{pointFormat:'<span style=\"color:{series.color};font-weight:bold\">{series.name}</span><br/>Maximum: {point.high}<br/>Upper quartile: {point.q3}<br/>Median: {point.median}<br/>Lower quartile: {point.q1}<br/>Minimum: {point.low}<br/>'},whiskerLength:\"50%\",whiskerWidth:2});h.boxplot=x(h.column,{type:\"boxplot\",pointArrayMap:[\"low\",\"q1\",\"median\",\"q3\",\"high\"],\ntoYData:function(a){return[a.low,a.q1,a.median,a.q3,a.high]},pointValKey:\"high\",pointAttrToOptions:{fill:\"fillColor\",stroke:\"color\",\"stroke-width\":\"lineWidth\"},drawDataLabels:w,translate:function(){var a=this.yAxis,b=this.pointArrayMap;h.column.prototype.translate.apply(this);s(this.points,function(c){s(b,function(b){c[b]!==null&&(c[b+\"Plot\"]=a.translate(c[b],0,1,0,1))})})},drawPoints:function(){var a=this,b=a.points,c=a.options,d=a.chart.renderer,g,f,e,i,n,l,k,h,j,m,o,H,p,E,I,q,w,t,v,u,z,y,x=a.doQuartiles!==\n!1,B=parseInt(a.options.whiskerLength,10)/100;s(b,function(b){j=b.graphic;z=b.shapeArgs;o={};E={};q={};y=b.color||a.color;if(b.plotY!==C)if(g=b.pointAttr[b.selected?\"selected\":\"\"],w=z.width,t=A(z.x),v=t+w,u=D(w/2),f=A(x?b.q1Plot:b.lowPlot),e=A(x?b.q3Plot:b.lowPlot),i=A(b.highPlot),n=A(b.lowPlot),o.stroke=b.stemColor||c.stemColor||y,o[\"stroke-width\"]=r(b.stemWidth,c.stemWidth,c.lineWidth),o.dashstyle=b.stemDashStyle||c.stemDashStyle,E.stroke=b.whiskerColor||c.whiskerColor||y,E[\"stroke-width\"]=r(b.whiskerWidth,\nc.whiskerWidth,c.lineWidth),q.stroke=b.medianColor||c.medianColor||y,q[\"stroke-width\"]=r(b.medianWidth,c.medianWidth,c.lineWidth),k=o[\"stroke-width\"]%2/2,h=t+u+k,m=[\"M\",h,e,\"L\",h,i,\"M\",h,f,\"L\",h,n,\"z\"],x&&(k=g[\"stroke-width\"]%2/2,h=A(h)+k,f=A(f)+k,e=A(e)+k,t+=k,v+=k,H=[\"M\",t,e,\"L\",t,f,\"L\",v,f,\"L\",v,e,\"L\",t,e,\"z\"]),B&&(k=E[\"stroke-width\"]%2/2,i+=k,n+=k,p=[\"M\",h-u*B,i,\"L\",h+u*B,i,\"M\",h-u*B,n,\"L\",h+u*B,n]),k=q[\"stroke-width\"]%2/2,l=D(b.medianPlot)+k,I=[\"M\",t,l,\"L\",v,l,\"z\"],j)b.stem.animate({d:m}),B&&\nb.whiskers.animate({d:p}),x&&b.box.animate({d:H}),b.medianShape.animate({d:I});else{b.graphic=j=d.g().add(a.group);b.stem=d.path(m).attr(o).add(j);if(B)b.whiskers=d.path(p).attr(E).add(j);if(x)b.box=d.path(H).attr(g).add(j);b.medianShape=d.path(I).attr(q).add(j)}})}});m.errorbar=p(m.boxplot,{color:\"#000000\",grouping:!1,linkedTo:\":previous\",tooltip:{pointFormat:m.arearange.tooltip.pointFormat},whiskerWidth:null});h.errorbar=x(h.boxplot,{type:\"errorbar\",pointArrayMap:[\"low\",\"high\"],toYData:function(a){return[a.low,\na.high]},pointValKey:\"high\",doQuartiles:!1,getColumnMetrics:function(){return this.linkedParent&&this.linkedParent.columnMetrics||h.column.prototype.getColumnMetrics.call(this)}});m.waterfall=p(m.column,{lineWidth:1,lineColor:\"#333\",dashStyle:\"dot\",borderColor:\"#333\"});h.waterfall=x(h.column,{type:\"waterfall\",upColorProp:\"fill\",pointArrayMap:[\"low\",\"y\"],pointValKey:\"y\",init:function(a,b){b.stacking=!0;h.column.prototype.init.call(this,a,b)},translate:function(){var a=this.options,b=this.yAxis,c,d,\ng,f,e,i,n,l,k;c=a.threshold;a=a.borderWidth%2/2;h.column.prototype.translate.apply(this);l=c;g=this.points;for(d=0,c=g.length;d<c;d++){f=g[d];e=f.shapeArgs;i=this.getStack(d);k=i.points[this.index];if(isNaN(f.y))f.y=this.yData[d];n=S(l,l+f.y)+k[0];e.y=b.translate(n,0,1);f.isSum||f.isIntermediateSum?(e.y=b.translate(k[1],0,1),e.height=b.translate(k[0],0,1)-e.y):l+=i.total;e.height<0&&(e.y+=e.height,e.height*=-1);f.plotY=e.y=D(e.y)-a;e.height=D(e.height);f.yBottom=e.y+e.height}},processData:function(a){var b=\nthis.yData,c=this.points,d,g=b.length,f=this.options.threshold||0,e,i,h,l,k,j;i=e=h=l=f;for(j=0;j<g;j++)k=b[j],d=c&&c[j]?c[j]:{},k===\"sum\"||d.isSum?b[j]=i:k===\"intermediateSum\"||d.isIntermediateSum?(b[j]=e,e=f):(i+=k,e+=k),h=Math.min(i,h),l=Math.max(i,l);z.prototype.processData.call(this,a);this.dataMin=h;this.dataMax=l},toYData:function(a){if(a.isSum)return\"sum\";else if(a.isIntermediateSum)return\"intermediateSum\";return a.y},getAttribs:function(){h.column.prototype.getAttribs.apply(this,arguments);\nvar a=this.options,b=a.states,c=a.upColor||this.color,a=j.Color(c).brighten(0.1).get(),d=p(this.pointAttr),g=this.upColorProp;d[\"\"][g]=c;d.hover[g]=b.hover.upColor||a;d.select[g]=b.select.upColor||c;s(this.points,function(a){if(a.y>0&&!a.color)a.pointAttr=d,a.color=c})},getGraphPath:function(){var a=this.data,b=a.length,c=D(this.options.lineWidth+this.options.borderWidth)%2/2,d=[],g,f,e;for(e=1;e<b;e++)f=a[e].shapeArgs,g=a[e-1].shapeArgs,f=[\"M\",g.x+g.width,g.y+c,\"L\",f.x,g.y+c],a[e-1].y<0&&(f[2]+=\ng.height,f[5]+=g.height),d=d.concat(f);return d},getExtremes:w,getStack:function(a){var b=this.yAxis.stacks,c=this.stackKey;this.processedYData[a]<this.options.threshold&&(c=\"-\"+c);return b[c][a]},drawGraph:z.prototype.drawGraph});m.bubble=p(m.scatter,{dataLabels:{inside:!0,style:{color:\"white\",textShadow:\"0px 0px 3px black\"},verticalAlign:\"middle\"},marker:{lineColor:null,lineWidth:1},minSize:8,maxSize:\"20%\",tooltip:{pointFormat:\"({point.x}, {point.y}), Size: {point.z}\"},turboThreshold:0,zThreshold:0});\nh.bubble=x(h.scatter,{type:\"bubble\",pointArrayMap:[\"y\",\"z\"],trackerGroups:[\"group\",\"dataLabelsGroup\"],pointAttrToOptions:{stroke:\"lineColor\",\"stroke-width\":\"lineWidth\",fill:\"fillColor\"},applyOpacity:function(a){var b=this.options.marker,c=r(b.fillOpacity,0.5),a=a||b.fillColor||this.color;c!==1&&(a=j.Color(a).setOpacity(c).get(\"rgba\"));return a},convertAttribs:function(){var a=z.prototype.convertAttribs.apply(this,arguments);a.fill=this.applyOpacity(a.fill);return a},getRadii:function(a,b,c,d){var g,\nf,e,i=this.zData,h=[];for(f=0,g=i.length;f<g;f++)e=b-a,e=e>0?(i[f]-a)/(b-a):0.5,h.push(t.ceil(c+e*(d-c))/2);this.radii=h},animate:function(a){var b=this.options.animation;if(!a)s(this.points,function(a){var d=a.graphic,a=a.shapeArgs;d&&a&&(d.attr(\"r\",1),d.animate({r:a.r},b))}),this.animate=null},translate:function(){var a,b=this.data,c,d,g=this.radii;h.scatter.prototype.translate.call(this);for(a=b.length;a--;)c=b[a],d=g?g[a]:0,c.negative=c.z<(this.options.zThreshold||0),d>=this.minPxSize/2?(c.shapeType=\n\"circle\",c.shapeArgs={x:c.plotX,y:c.plotY,r:d},c.dlBox={x:c.plotX-d,y:c.plotY-d,width:2*d,height:2*d}):c.shapeArgs=c.plotY=c.dlBox=C},drawLegendSymbol:function(a,b){var c=v(a.itemStyle.fontSize)/2;b.legendSymbol=this.chart.renderer.circle(c,a.baseline-c,c).attr({zIndex:3}).add(b.legendGroup);b.legendSymbol.isMarker=!0},drawPoints:h.column.prototype.drawPoints,alignDataLabel:h.column.prototype.alignDataLabel});N.prototype.beforePadding=function(){var a=this,b=this.len,c=this.chart,d=0,g=b,f=this.isXAxis,\ne=f?\"xData\":\"yData\",i=this.min,h={},j=t.min(c.plotWidth,c.plotHeight),k=Number.MAX_VALUE,m=-Number.MAX_VALUE,o=this.max-i,p=b/o,q=[];this.tickPositions&&(s(this.series,function(b){var c=b.options;if(b.type===\"bubble\"&&b.visible&&(a.allowZoomOutside=!0,q.push(b),f))s([\"minSize\",\"maxSize\"],function(a){var b=c[a],d=/%$/.test(b),b=v(b);h[a]=d?j*b/100:b}),b.minPxSize=h.minSize,b=b.zData,b.length&&(k=t.min(k,t.max(P(b),c.displayNegative===!1?c.zThreshold:-Number.MAX_VALUE)),m=t.max(m,Q(b)))}),s(q,function(a){var b=\na[e],c=b.length,j;f&&a.getRadii(k,m,h.minSize,h.maxSize);if(o>0)for(;c--;)j=a.radii[c],d=Math.min((b[c]-i)*p-j,d),g=Math.max((b[c]-i)*p+j,g)}),q.length&&o>0&&r(this.options.min,this.userMin)===C&&r(this.options.max,this.userMax)===C&&(g-=b,p*=(b+d-g)/b,this.min+=d/p,this.max+=g/p))};var y=z.prototype,m=j.Pointer.prototype;y.toXY=function(a){var b,c=this.chart;b=a.plotX;var d=a.plotY;a.rectPlotX=b;a.rectPlotY=d;a.clientX=(b/Math.PI*180+this.xAxis.pane.options.startAngle)%360;b=this.xAxis.postTranslate(a.plotX,\nthis.yAxis.len-d);a.plotX=a.polarPlotX=b.x-c.plotLeft;a.plotY=a.polarPlotY=b.y-c.plotTop};y.orderTooltipPoints=function(a){if(this.chart.polar&&(a.sort(function(a,c){return a.clientX-c.clientX}),a[0]))a[0].wrappedClientX=a[0].clientX+360,a.push(a[0])};o(h.area.prototype,\"init\",K);o(h.areaspline.prototype,\"init\",K);o(h.spline.prototype,\"getPointSpline\",function(a,b,c,d){var g,f,e,i,h,j,k;if(this.chart.polar){g=c.plotX;f=c.plotY;a=b[d-1];e=b[d+1];this.connectEnds&&(a||(a=b[b.length-2]),e||(e=b[1]));\nif(a&&e)i=a.plotX,h=a.plotY,b=e.plotX,j=e.plotY,i=(1.5*g+i)/2.5,h=(1.5*f+h)/2.5,e=(1.5*g+b)/2.5,k=(1.5*f+j)/2.5,b=Math.sqrt(Math.pow(i-g,2)+Math.pow(h-f,2)),j=Math.sqrt(Math.pow(e-g,2)+Math.pow(k-f,2)),i=Math.atan2(h-f,i-g),h=Math.atan2(k-f,e-g),k=Math.PI/2+(i+h)/2,Math.abs(i-k)>Math.PI/2&&(k-=Math.PI),i=g+Math.cos(k)*b,h=f+Math.sin(k)*b,e=g+Math.cos(Math.PI+k)*j,k=f+Math.sin(Math.PI+k)*j,c.rightContX=e,c.rightContY=k;d?(c=[\"C\",a.rightContX||a.plotX,a.rightContY||a.plotY,i||g,h||f,g,f],a.rightContX=\na.rightContY=null):c=[\"M\",g,f]}else c=a.call(this,b,c,d);return c});o(y,\"translate\",function(a){a.call(this);if(this.chart.polar&&!this.preventPostTranslate)for(var a=this.points,b=a.length;b--;)this.toXY(a[b])});o(y,\"getSegmentPath\",function(a,b){var c=this.points;if(this.chart.polar&&this.options.connectEnds!==!1&&b[b.length-1]===c[c.length-1]&&c[0].y!==null)this.connectEnds=!0,b=[].concat(b,[c[0]]);return a.call(this,b)});o(y,\"animate\",L);o(q,\"animate\",L);o(y,\"setTooltipPoints\",function(a,b){this.chart.polar&&\nF(this.xAxis,{tooltipLen:360});return a.call(this,b)});o(q,\"translate\",function(a){var b=this.xAxis,c=this.yAxis.len,d=b.center,g=b.startAngleRad,f=this.chart.renderer,e,h;this.preventPostTranslate=!0;a.call(this);if(b.isRadial){b=this.points;for(h=b.length;h--;)e=b[h],a=e.barX+g,e.shapeType=\"path\",e.shapeArgs={d:f.symbols.arc(d[0],d[1],c-e.plotY,null,{start:a,end:a+e.pointWidth,innerR:c-r(e.yBottom,c)})},this.toXY(e)}});o(q,\"alignDataLabel\",function(a,b,c,d,g,f){if(this.chart.polar){a=b.rectPlotX/\nMath.PI*180;if(d.align===null)d.align=a>20&&a<160?\"left\":a>200&&a<340?\"right\":\"center\";if(d.verticalAlign===null)d.verticalAlign=a<45||a>315?\"bottom\":a>135&&a<225?\"top\":\"middle\";y.alignDataLabel.call(this,b,c,d,g,f)}else a.call(this,b,c,d,g,f)});o(m,\"getIndex\",function(a,b){var c,d=this.chart,g;d.polar?(g=d.xAxis[0].center,c=b.chartX-g[0]-d.plotLeft,d=b.chartY-g[1]-d.plotTop,c=180-Math.round(Math.atan2(c,d)/Math.PI*180)):c=a.call(this,b);return c});o(m,\"getCoordinates\",function(a,b){var c=this.chart,\nd={xAxis:[],yAxis:[]};c.polar?s(c.axes,function(a){var f=a.isXAxis,e=a.center,h=b.chartX-e[0]-c.plotLeft,e=b.chartY-e[1]-c.plotTop;d[f?\"xAxis\":\"yAxis\"].push({axis:a,value:a.translate(f?Math.PI-Math.atan2(h,e):Math.sqrt(Math.pow(h,2)+Math.pow(e,2)),!0)})}):d=a.call(this,b);return d})})(Highcharts);\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/highcharts/highcharts-more.src.js",
    "content": "// ==ClosureCompiler==\n// @compilation_level SIMPLE_OPTIMIZATIONS\n\n/**\n * @license Highcharts JS v3.0.6 (2013-10-04)\n *\n * (c) 2009-2013 Torstein Hønsi\n *\n * License: www.highcharts.com/license\n */\n\n// JSLint options:\n/*global Highcharts, HighchartsAdapter, document, window, navigator, setInterval, clearInterval, clearTimeout, setTimeout, location, jQuery, $, console */\n\n(function (Highcharts, UNDEFINED) {\nvar arrayMin = Highcharts.arrayMin,\n\tarrayMax = Highcharts.arrayMax,\n\teach = Highcharts.each,\n\textend = Highcharts.extend,\n\tmerge = Highcharts.merge,\n\tmap = Highcharts.map,\n\tpick = Highcharts.pick,\n\tpInt = Highcharts.pInt,\n\tdefaultPlotOptions = Highcharts.getOptions().plotOptions,\n\tseriesTypes = Highcharts.seriesTypes,\n\textendClass = Highcharts.extendClass,\n\tsplat = Highcharts.splat,\n\twrap = Highcharts.wrap,\n\tAxis = Highcharts.Axis,\n\tTick = Highcharts.Tick,\n\tSeries = Highcharts.Series,\n\tcolProto = seriesTypes.column.prototype,\n\tmath = Math,\n\tmathRound = math.round,\n\tmathFloor = math.floor,\n\tmathMax = math.max,\n\tnoop = function () {};/**\n * The Pane object allows options that are common to a set of X and Y axes.\n * \n * In the future, this can be extended to basic Highcharts and Highstock.\n */\nfunction Pane(options, chart, firstAxis) {\n\tthis.init.call(this, options, chart, firstAxis);\n}\n\n// Extend the Pane prototype\nextend(Pane.prototype, {\n\t\n\t/**\n\t * Initiate the Pane object\n\t */\n\tinit: function (options, chart, firstAxis) {\n\t\tvar pane = this,\n\t\t\tbackgroundOption,\n\t\t\tdefaultOptions = pane.defaultOptions;\n\t\t\n\t\tpane.chart = chart;\n\t\t\n\t\t// Set options\n\t\tif (chart.angular) { // gauges\n\t\t\tdefaultOptions.background = {}; // gets extended by this.defaultBackgroundOptions\n\t\t}\n\t\tpane.options = options = merge(defaultOptions, options);\n\t\t\n\t\tbackgroundOption = options.background;\n\t\t\n\t\t// To avoid having weighty logic to place, update and remove the backgrounds,\n\t\t// push them to the first axis' plot bands and borrow the existing logic there.\n\t\tif (backgroundOption) {\n\t\t\teach([].concat(splat(backgroundOption)).reverse(), function (config) {\n\t\t\t\tvar backgroundColor = config.backgroundColor; // if defined, replace the old one (specific for gradients)\n\t\t\t\tconfig = merge(pane.defaultBackgroundOptions, config);\n\t\t\t\tif (backgroundColor) {\n\t\t\t\t\tconfig.backgroundColor = backgroundColor;\n\t\t\t\t}\n\t\t\t\tconfig.color = config.backgroundColor; // due to naming in plotBands\n\t\t\t\tfirstAxis.options.plotBands.unshift(config);\n\t\t\t});\n\t\t}\n\t},\n\t\n\t/**\n\t * The default options object\n\t */\n\tdefaultOptions: {\n\t\t// background: {conditional},\n\t\tcenter: ['50%', '50%'],\n\t\tsize: '85%',\n\t\tstartAngle: 0\n\t\t//endAngle: startAngle + 360\n\t},\t\n\t\n\t/**\n\t * The default background options\n\t */\n\tdefaultBackgroundOptions: {\n\t\tshape: 'circle',\n\t\tborderWidth: 1,\n\t\tborderColor: 'silver',\n\t\tbackgroundColor: {\n\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\n\t\t\tstops: [\n\t\t\t\t[0, '#FFF'],\n\t\t\t\t[1, '#DDD']\n\t\t\t]\n\t\t},\n\t\tfrom: Number.MIN_VALUE, // corrected to axis min\n\t\tinnerRadius: 0,\n\t\tto: Number.MAX_VALUE, // corrected to axis max\n\t\touterRadius: '105%'\n\t}\n\t\n});\nvar axisProto = Axis.prototype,\n\ttickProto = Tick.prototype;\n\t\n/**\n * Augmented methods for the x axis in order to hide it completely, used for the X axis in gauges\n */\nvar hiddenAxisMixin = {\n\tgetOffset: noop,\n\tredraw: function () {\n\t\tthis.isDirty = false; // prevent setting Y axis dirty\n\t},\n\trender: function () {\n\t\tthis.isDirty = false; // prevent setting Y axis dirty\n\t},\n\tsetScale: noop,\n\tsetCategories: noop,\n\tsetTitle: noop\n};\n\n/**\n * Augmented methods for the value axis\n */\n/*jslint unparam: true*/\nvar radialAxisMixin = {\n\tisRadial: true,\n\t\n\t/**\n\t * The default options extend defaultYAxisOptions\n\t */\n\tdefaultRadialGaugeOptions: {\n\t\tlabels: {\n\t\t\talign: 'center',\n\t\t\tx: 0,\n\t\t\ty: null // auto\n\t\t},\n\t\tminorGridLineWidth: 0,\n\t\tminorTickInterval: 'auto',\n\t\tminorTickLength: 10,\n\t\tminorTickPosition: 'inside',\n\t\tminorTickWidth: 1,\n\t\tplotBands: [],\n\t\ttickLength: 10,\n\t\ttickPosition: 'inside',\n\t\ttickWidth: 2,\n\t\ttitle: {\n\t\t\trotation: 0\n\t\t},\n\t\tzIndex: 2 // behind dials, points in the series group\n\t},\n\t\n\t// Circular axis around the perimeter of a polar chart\n\tdefaultRadialXOptions: {\n\t\tgridLineWidth: 1, // spokes\n\t\tlabels: {\n\t\t\talign: null, // auto\n\t\t\tdistance: 15,\n\t\t\tx: 0,\n\t\t\ty: null // auto\n\t\t},\n\t\tmaxPadding: 0,\n\t\tminPadding: 0,\n\t\tplotBands: [],\n\t\tshowLastLabel: false, \n\t\ttickLength: 0\n\t},\n\t\n\t// Radial axis, like a spoke in a polar chart\n\tdefaultRadialYOptions: {\n\t\tgridLineInterpolation: 'circle',\n\t\tlabels: {\n\t\t\talign: 'right',\n\t\t\tx: -3,\n\t\t\ty: -2\n\t\t},\n\t\tplotBands: [],\n\t\tshowLastLabel: false,\n\t\ttitle: {\n\t\t\tx: 4,\n\t\t\ttext: null,\n\t\t\trotation: 90\n\t\t}\n\t},\n\t\n\t/**\n\t * Merge and set options\n\t */\n\tsetOptions: function (userOptions) {\n\t\t\n\t\tthis.options = merge(\n\t\t\tthis.defaultOptions,\n\t\t\tthis.defaultRadialOptions,\n\t\t\tuserOptions\n\t\t);\n\t\t\n\t},\n\t\n\t/**\n\t * Wrap the getOffset method to return zero offset for title or labels in a radial \n\t * axis\n\t */\n\tgetOffset: function () {\n\t\t// Call the Axis prototype method (the method we're in now is on the instance)\n\t\taxisProto.getOffset.call(this);\n\t\t\n\t\t// Title or label offsets are not counted\n\t\tthis.chart.axisOffset[this.side] = 0;\n\t},\n\n\n\t/**\n\t * Get the path for the axis line. This method is also referenced in the getPlotLinePath\n\t * method.\n\t */\n\tgetLinePath: function (lineWidth, radius) {\n\t\tvar center = this.center;\n\t\tradius = pick(radius, center[2] / 2 - this.offset);\n\t\t\n\t\treturn this.chart.renderer.symbols.arc(\n\t\t\tthis.left + center[0],\n\t\t\tthis.top + center[1],\n\t\t\tradius,\n\t\t\tradius, \n\t\t\t{\n\t\t\t\tstart: this.startAngleRad,\n\t\t\t\tend: this.endAngleRad,\n\t\t\t\topen: true,\n\t\t\t\tinnerR: 0\n\t\t\t}\n\t\t);\n\t},\n\n\t/**\n\t * Override setAxisTranslation by setting the translation to the difference\n\t * in rotation. This allows the translate method to return angle for \n\t * any given value.\n\t */\n\tsetAxisTranslation: function () {\n\t\t\n\t\t// Call uber method\t\t\n\t\taxisProto.setAxisTranslation.call(this);\n\t\t\t\n\t\t// Set transA and minPixelPadding\n\t\tif (this.center) { // it's not defined the first time\n\t\t\tif (this.isCircular) {\n\t\t\t\t\n\t\t\t\tthis.transA = (this.endAngleRad - this.startAngleRad) / \n\t\t\t\t\t((this.max - this.min) || 1);\n\t\t\t\t\t\n\t\t\t\t\n\t\t\t} else { \n\t\t\t\tthis.transA = (this.center[2] / 2) / ((this.max - this.min) || 1);\n\t\t\t}\n\t\t\t\n\t\t\tif (this.isXAxis) {\n\t\t\t\tthis.minPixelPadding = this.transA * this.minPointOffset +\n\t\t\t\t\t(this.reversed ? (this.endAngleRad - this.startAngleRad) / 4 : 0); // ???\n\t\t\t}\n\t\t}\n\t},\n\t\n\t/**\n\t * In case of auto connect, add one closestPointRange to the max value right before\n\t * tickPositions are computed, so that ticks will extend passed the real max.\n\t */\n\tbeforeSetTickPositions: function () {\n\t\tif (this.autoConnect) {\n\t\t\tthis.max += (this.categories && 1) || this.pointRange || this.closestPointRange || 0; // #1197, #2260\n\t\t}\n\t},\n\t\n\t/**\n\t * Override the setAxisSize method to use the arc's circumference as length. This\n\t * allows tickPixelInterval to apply to pixel lengths along the perimeter\n\t */\n\tsetAxisSize: function () {\n\t\t\n\t\taxisProto.setAxisSize.call(this);\n\n\t\tif (this.isRadial) {\n\n\t\t\t// Set the center array\n\t\t\tthis.center = this.pane.center = seriesTypes.pie.prototype.getCenter.call(this.pane);\n\t\t\t\n\t\t\tthis.len = this.width = this.height = this.isCircular ?\n\t\t\t\tthis.center[2] * (this.endAngleRad - this.startAngleRad) / 2 :\n\t\t\t\tthis.center[2] / 2;\n\t\t}\n\t},\n\t\n\t/**\n\t * Returns the x, y coordinate of a point given by a value and a pixel distance\n\t * from center\n\t */\n\tgetPosition: function (value, length) {\n\t\tif (!this.isCircular) {\n\t\t\tlength = this.translate(value);\n\t\t\tvalue = this.min;\t\n\t\t}\n\t\t\n\t\treturn this.postTranslate(\n\t\t\tthis.translate(value),\n\t\t\tpick(length, this.center[2] / 2) - this.offset\n\t\t);\t\t\n\t},\n\t\n\t/**\n\t * Translate from intermediate plotX (angle), plotY (axis.len - radius) to final chart coordinates. \n\t */\n\tpostTranslate: function (angle, radius) {\n\t\t\n\t\tvar chart = this.chart,\n\t\t\tcenter = this.center;\n\t\t\t\n\t\tangle = this.startAngleRad + angle;\n\t\t\n\t\treturn {\n\t\t\tx: chart.plotLeft + center[0] + Math.cos(angle) * radius,\n\t\t\ty: chart.plotTop + center[1] + Math.sin(angle) * radius\n\t\t}; \n\t\t\n\t},\n\t\n\t/**\n\t * Find the path for plot bands along the radial axis\n\t */\n\tgetPlotBandPath: function (from, to, options) {\n\t\tvar center = this.center,\n\t\t\tstartAngleRad = this.startAngleRad,\n\t\t\tfullRadius = center[2] / 2,\n\t\t\tradii = [\n\t\t\t\tpick(options.outerRadius, '100%'),\n\t\t\t\toptions.innerRadius,\n\t\t\t\tpick(options.thickness, 10)\n\t\t\t],\n\t\t\tpercentRegex = /%$/,\n\t\t\tstart,\n\t\t\tend,\n\t\t\topen,\n\t\t\tisCircular = this.isCircular, // X axis in a polar chart\n\t\t\tret;\n\t\t\t\n\t\t// Polygonal plot bands\n\t\tif (this.options.gridLineInterpolation === 'polygon') {\n\t\t\tret = this.getPlotLinePath(from).concat(this.getPlotLinePath(to, true));\n\t\t\n\t\t// Circular grid bands\n\t\t} else {\n\t\t\t\n\t\t\t// Plot bands on Y axis (radial axis) - inner and outer radius depend on to and from\n\t\t\tif (!isCircular) {\n\t\t\t\tradii[0] = this.translate(from);\n\t\t\t\tradii[1] = this.translate(to);\n\t\t\t}\n\t\t\t\n\t\t\t// Convert percentages to pixel values\n\t\t\tradii = map(radii, function (radius) {\n\t\t\t\tif (percentRegex.test(radius)) {\n\t\t\t\t\tradius = (pInt(radius, 10) * fullRadius) / 100;\n\t\t\t\t}\n\t\t\t\treturn radius;\n\t\t\t});\n\t\t\t\n\t\t\t// Handle full circle\n\t\t\tif (options.shape === 'circle' || !isCircular) {\n\t\t\t\tstart = -Math.PI / 2;\n\t\t\t\tend = Math.PI * 1.5;\n\t\t\t\topen = true;\n\t\t\t} else {\n\t\t\t\tstart = startAngleRad + this.translate(from);\n\t\t\t\tend = startAngleRad + this.translate(to);\n\t\t\t}\n\t\t\n\t\t\n\t\t\tret = this.chart.renderer.symbols.arc(\n\t\t\t\tthis.left + center[0],\n\t\t\t\tthis.top + center[1],\n\t\t\t\tradii[0],\n\t\t\t\tradii[0],\n\t\t\t\t{\n\t\t\t\t\tstart: start,\n\t\t\t\t\tend: end,\n\t\t\t\t\tinnerR: pick(radii[1], radii[0] - radii[2]),\n\t\t\t\t\topen: open\n\t\t\t\t}\n\t\t\t);\n\t\t}\n\t\t \n\t\treturn ret;\n\t},\n\t\n\t/**\n\t * Find the path for plot lines perpendicular to the radial axis.\n\t */\n\tgetPlotLinePath: function (value, reverse) {\n\t\tvar axis = this,\n\t\t\tcenter = axis.center,\n\t\t\tchart = axis.chart,\n\t\t\tend = axis.getPosition(value),\n\t\t\txAxis,\n\t\t\txy,\n\t\t\ttickPositions,\n\t\t\tret;\n\t\t\n\t\t// Spokes\n\t\tif (axis.isCircular) {\n\t\t\tret = ['M', center[0] + chart.plotLeft, center[1] + chart.plotTop, 'L', end.x, end.y];\n\t\t\n\t\t// Concentric circles\t\t\t\n\t\t} else if (axis.options.gridLineInterpolation === 'circle') {\n\t\t\tvalue = axis.translate(value);\n\t\t\tif (value) { // a value of 0 is in the center\n\t\t\t\tret = axis.getLinePath(0, value);\n\t\t\t}\n\t\t// Concentric polygons \n\t\t} else {\n\t\t\txAxis = chart.xAxis[0];\n\t\t\tret = [];\n\t\t\tvalue = axis.translate(value);\n\t\t\ttickPositions = xAxis.tickPositions;\n\t\t\tif (xAxis.autoConnect) {\n\t\t\t\ttickPositions = tickPositions.concat([tickPositions[0]]);\n\t\t\t}\n\t\t\t// Reverse the positions for concatenation of polygonal plot bands\n\t\t\tif (reverse) {\n\t\t\t\ttickPositions = [].concat(tickPositions).reverse();\n\t\t\t}\n\t\t\t\t\n\t\t\teach(tickPositions, function (pos, i) {\n\t\t\t\txy = xAxis.getPosition(pos, value);\n\t\t\t\tret.push(i ? 'L' : 'M', xy.x, xy.y);\n\t\t\t});\n\t\t\t\n\t\t}\n\t\treturn ret;\n\t},\n\t\n\t/**\n\t * Find the position for the axis title, by default inside the gauge\n\t */\n\tgetTitlePosition: function () {\n\t\tvar center = this.center,\n\t\t\tchart = this.chart,\n\t\t\ttitleOptions = this.options.title;\n\t\t\n\t\treturn { \n\t\t\tx: chart.plotLeft + center[0] + (titleOptions.x || 0), \n\t\t\ty: chart.plotTop + center[1] - ({ high: 0.5, middle: 0.25, low: 0 }[titleOptions.align] * \n\t\t\t\tcenter[2]) + (titleOptions.y || 0)  \n\t\t};\n\t}\n\t\n};\n/*jslint unparam: false*/\n\n/**\n * Override axisProto.init to mix in special axis instance functions and function overrides\n */\nwrap(axisProto, 'init', function (proceed, chart, userOptions) {\n\tvar axis = this,\n\t\tangular = chart.angular,\n\t\tpolar = chart.polar,\n\t\tisX = userOptions.isX,\n\t\tisHidden = angular && isX,\n\t\tisCircular,\n\t\tstartAngleRad,\n\t\tendAngleRad,\n\t\toptions,\n\t\tchartOptions = chart.options,\n\t\tpaneIndex = userOptions.pane || 0,\n\t\tpane,\n\t\tpaneOptions;\n\t\t\n\t// Before prototype.init\n\tif (angular) {\n\t\textend(this, isHidden ? hiddenAxisMixin : radialAxisMixin);\n\t\tisCircular =  !isX;\n\t\tif (isCircular) {\n\t\t\tthis.defaultRadialOptions = this.defaultRadialGaugeOptions;\n\t\t}\n\t\t\n\t} else if (polar) {\n\t\t//extend(this, userOptions.isX ? radialAxisMixin : radialAxisMixin);\n\t\textend(this, radialAxisMixin);\n\t\tisCircular = isX;\n\t\tthis.defaultRadialOptions = isX ? this.defaultRadialXOptions : merge(this.defaultYAxisOptions, this.defaultRadialYOptions);\n\t\t\n\t}\n\t\n\t// Run prototype.init\n\tproceed.call(this, chart, userOptions);\n\t\n\tif (!isHidden && (angular || polar)) {\n\t\toptions = this.options;\n\t\t\n\t\t// Create the pane and set the pane options.\n\t\tif (!chart.panes) {\n\t\t\tchart.panes = [];\n\t\t}\n\t\tthis.pane = pane = chart.panes[paneIndex] = chart.panes[paneIndex] || new Pane(\n\t\t\tsplat(chartOptions.pane)[paneIndex],\n\t\t\tchart,\n\t\t\taxis\n\t\t);\n\t\tpaneOptions = pane.options;\n\t\t\n\t\t\t\n\t\t// Disable certain features on angular and polar axes\n\t\tchart.inverted = false;\n\t\tchartOptions.chart.zoomType = null;\n\t\t\n\t\t// Start and end angle options are\n\t\t// given in degrees relative to top, while internal computations are\n\t\t// in radians relative to right (like SVG).\n\t\tthis.startAngleRad = startAngleRad = (paneOptions.startAngle - 90) * Math.PI / 180;\n\t\tthis.endAngleRad = endAngleRad = (pick(paneOptions.endAngle, paneOptions.startAngle + 360)  - 90) * Math.PI / 180;\n\t\tthis.offset = options.offset || 0;\n\t\t\n\t\tthis.isCircular = isCircular;\n\t\t\n\t\t// Automatically connect grid lines?\n\t\tif (isCircular && userOptions.max === UNDEFINED && endAngleRad - startAngleRad === 2 * Math.PI) {\n\t\t\tthis.autoConnect = true;\n\t\t}\n\t}\n\t\n});\n\n/**\n * Add special cases within the Tick class' methods for radial axes.\n */\t\nwrap(tickProto, 'getPosition', function (proceed, horiz, pos, tickmarkOffset, old) {\n\tvar axis = this.axis;\n\t\n\treturn axis.getPosition ? \n\t\taxis.getPosition(pos) :\n\t\tproceed.call(this, horiz, pos, tickmarkOffset, old);\t\n});\n\n/**\n * Wrap the getLabelPosition function to find the center position of the label\n * based on the distance option\n */\t\nwrap(tickProto, 'getLabelPosition', function (proceed, x, y, label, horiz, labelOptions, tickmarkOffset, index, step) {\n\tvar axis = this.axis,\n\t\toptionsY = labelOptions.y,\n\t\tret,\n\t\talign = labelOptions.align,\n\t\tangle = ((axis.translate(this.pos) + axis.startAngleRad + Math.PI / 2) / Math.PI * 180) % 360;\n\t\n\tif (axis.isRadial) {\n\t\tret = axis.getPosition(this.pos, (axis.center[2] / 2) + pick(labelOptions.distance, -25));\n\t\t\n\t\t// Automatically rotated\n\t\tif (labelOptions.rotation === 'auto') {\n\t\t\tlabel.attr({ \n\t\t\t\trotation: angle\n\t\t\t});\n\t\t\n\t\t// Vertically centered\n\t\t} else if (optionsY === null) {\n\t\t\toptionsY = pInt(label.styles.lineHeight) * 0.9 - label.getBBox().height / 2;\n\t\t\n\t\t}\n\t\t\n\t\t// Automatic alignment\n\t\tif (align === null) {\n\t\t\tif (axis.isCircular) {\n\t\t\t\tif (angle > 20 && angle < 160) {\n\t\t\t\t\talign = 'left'; // right hemisphere\n\t\t\t\t} else if (angle > 200 && angle < 340) {\n\t\t\t\t\talign = 'right'; // left hemisphere\n\t\t\t\t} else {\n\t\t\t\t\talign = 'center'; // top or bottom\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\talign = 'center';\n\t\t\t}\n\t\t\tlabel.attr({\n\t\t\t\talign: align\n\t\t\t});\n\t\t}\n\t\t\n\t\tret.x += labelOptions.x;\n\t\tret.y += optionsY;\n\t\t\n\t} else {\n\t\tret = proceed.call(this, x, y, label, horiz, labelOptions, tickmarkOffset, index, step);\n\t}\n\treturn ret;\n});\n\n/**\n * Wrap the getMarkPath function to return the path of the radial marker\n */\nwrap(tickProto, 'getMarkPath', function (proceed, x, y, tickLength, tickWidth, horiz, renderer) {\n\tvar axis = this.axis,\n\t\tendPoint,\n\t\tret;\n\t\t\n\tif (axis.isRadial) {\n\t\tendPoint = axis.getPosition(this.pos, axis.center[2] / 2 + tickLength);\n\t\tret = [\n\t\t\t'M',\n\t\t\tx,\n\t\t\ty,\n\t\t\t'L',\n\t\t\tendPoint.x,\n\t\t\tendPoint.y\n\t\t];\n\t} else {\n\t\tret = proceed.call(this, x, y, tickLength, tickWidth, horiz, renderer);\n\t}\n\treturn ret;\n});/* \n * The AreaRangeSeries class\n * \n */\n\n/**\n * Extend the default options with map options\n */\ndefaultPlotOptions.arearange = merge(defaultPlotOptions.area, {\n\tlineWidth: 1,\n\tmarker: null,\n\tthreshold: null,\n\ttooltip: {\n\t\tpointFormat: '<span style=\"color:{series.color}\">{series.name}</span>: <b>{point.low}</b> - <b>{point.high}</b><br/>' \n\t},\n\ttrackByArea: true,\n\tdataLabels: {\n\t\tverticalAlign: null,\n\t\txLow: 0,\n\t\txHigh: 0,\n\t\tyLow: 0,\n\t\tyHigh: 0\t\n\t}\n});\n\n/**\n * Add the series type\n */\nseriesTypes.arearange = Highcharts.extendClass(seriesTypes.area, {\n\ttype: 'arearange',\n\tpointArrayMap: ['low', 'high'],\n\ttoYData: function (point) {\n\t\treturn [point.low, point.high];\n\t},\n\tpointValKey: 'low',\n\t\n\t/**\n\t * Extend getSegments to force null points if the higher value is null. #1703.\n\t */\n\tgetSegments: function () {\n\t\tvar series = this;\n\n\t\teach(series.points, function (point) {\n\t\t\tif (!series.options.connectNulls && (point.low === null || point.high === null)) {\n\t\t\t\tpoint.y = null;\n\t\t\t} else if (point.low === null && point.high !== null) {\n\t\t\t\tpoint.y = point.high;\n\t\t\t}\n\t\t});\n\t\tSeries.prototype.getSegments.call(this);\n\t},\n\t\n\t/**\n\t * Translate data points from raw values x and y to plotX and plotY\n\t */\n\ttranslate: function () {\n\t\tvar series = this,\n\t\t\tyAxis = series.yAxis;\n\n\t\tseriesTypes.area.prototype.translate.apply(series);\n\n\t\t// Set plotLow and plotHigh\n\t\teach(series.points, function (point) {\n\n\t\t\tvar low = point.low,\n\t\t\t\thigh = point.high,\n\t\t\t\tplotY = point.plotY;\n\n\t\t\tif (high === null && low === null) {\n\t\t\t\tpoint.y = null;\n\t\t\t} else if (low === null) {\n\t\t\t\tpoint.plotLow = point.plotY = null;\n\t\t\t\tpoint.plotHigh = yAxis.translate(high, 0, 1, 0, 1);\n\t\t\t} else if (high === null) {\n\t\t\t\tpoint.plotLow = plotY;\n\t\t\t\tpoint.plotHigh = null;\n\t\t\t} else {\n\t\t\t\tpoint.plotLow = plotY;\n\t\t\t\tpoint.plotHigh = yAxis.translate(high, 0, 1, 0, 1);\n\t\t\t}\n\t\t});\n\t},\n\t\n\t/**\n\t * Extend the line series' getSegmentPath method by applying the segment\n\t * path to both lower and higher values of the range\n\t */\n\tgetSegmentPath: function (segment) {\n\t\t\n\t\tvar lowSegment,\n\t\t\thighSegment = [],\n\t\t\ti = segment.length,\n\t\t\tbaseGetSegmentPath = Series.prototype.getSegmentPath,\n\t\t\tpoint,\n\t\t\tlinePath,\n\t\t\tlowerPath,\n\t\t\toptions = this.options,\n\t\t\tstep = options.step,\n\t\t\thigherPath;\n\t\t\t\n\t\t// Remove nulls from low segment\n\t\tlowSegment = HighchartsAdapter.grep(segment, function (point) {\n\t\t\treturn point.plotLow !== null;\n\t\t});\n\t\t\n\t\t// Make a segment with plotX and plotY for the top values\n\t\twhile (i--) {\n\t\t\tpoint = segment[i];\n\t\t\tif (point.plotHigh !== null) {\n\t\t\t\thighSegment.push({\n\t\t\t\t\tplotX: point.plotX,\n\t\t\t\t\tplotY: point.plotHigh\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\t\n\t\t// Get the paths\n\t\tlowerPath = baseGetSegmentPath.call(this, lowSegment);\n\t\tif (step) {\n\t\t\tif (step === true) {\n\t\t\t\tstep = 'left';\n\t\t\t}\n\t\t\toptions.step = { left: 'right', center: 'center', right: 'left' }[step]; // swap for reading in getSegmentPath\n\t\t}\n\t\thigherPath = baseGetSegmentPath.call(this, highSegment);\n\t\toptions.step = step;\n\t\t\n\t\t// Create a line on both top and bottom of the range\n\t\tlinePath = [].concat(lowerPath, higherPath);\n\t\t\n\t\t// For the area path, we need to change the 'move' statement into 'lineTo' or 'curveTo'\n\t\thigherPath[0] = 'L'; // this probably doesn't work for spline\t\t\t\n\t\tthis.areaPath = this.areaPath.concat(lowerPath, higherPath);\n\t\t\n\t\treturn linePath;\n\t},\n\t\n\t/**\n\t * Extend the basic drawDataLabels method by running it for both lower and higher\n\t * values.\n\t */\n\tdrawDataLabels: function () {\n\t\t\n\t\tvar data = this.data,\n\t\t\tlength = data.length,\n\t\t\ti,\n\t\t\toriginalDataLabels = [],\n\t\t\tseriesProto = Series.prototype,\n\t\t\tdataLabelOptions = this.options.dataLabels,\n\t\t\tpoint,\n\t\t\tinverted = this.chart.inverted;\n\t\t\t\n\t\tif (dataLabelOptions.enabled || this._hasPointLabels) {\n\t\t\t\n\t\t\t// Step 1: set preliminary values for plotY and dataLabel and draw the upper labels\n\t\t\ti = length;\n\t\t\twhile (i--) {\n\t\t\t\tpoint = data[i];\n\t\t\t\t\n\t\t\t\t// Set preliminary values\n\t\t\t\tpoint.y = point.high;\n\t\t\t\tpoint.plotY = point.plotHigh;\n\t\t\t\t\n\t\t\t\t// Store original data labels and set preliminary label objects to be picked up \n\t\t\t\t// in the uber method\n\t\t\t\toriginalDataLabels[i] = point.dataLabel;\n\t\t\t\tpoint.dataLabel = point.dataLabelUpper;\n\t\t\t\t\n\t\t\t\t// Set the default offset\n\t\t\t\tpoint.below = false;\n\t\t\t\tif (inverted) {\n\t\t\t\t\tdataLabelOptions.align = 'left';\n\t\t\t\t\tdataLabelOptions.x = dataLabelOptions.xHigh;\t\t\t\t\t\t\t\t\n\t\t\t\t} else {\n\t\t\t\t\tdataLabelOptions.y = dataLabelOptions.yHigh;\n\t\t\t\t}\n\t\t\t}\n\t\t\tseriesProto.drawDataLabels.apply(this, arguments); // #1209\n\t\t\t\n\t\t\t// Step 2: reorganize and handle data labels for the lower values\n\t\t\ti = length;\n\t\t\twhile (i--) {\n\t\t\t\tpoint = data[i];\n\t\t\t\t\n\t\t\t\t// Move the generated labels from step 1, and reassign the original data labels\n\t\t\t\tpoint.dataLabelUpper = point.dataLabel;\n\t\t\t\tpoint.dataLabel = originalDataLabels[i];\n\t\t\t\t\n\t\t\t\t// Reset values\n\t\t\t\tpoint.y = point.low;\n\t\t\t\tpoint.plotY = point.plotLow;\n\t\t\t\t\n\t\t\t\t// Set the default offset\n\t\t\t\tpoint.below = true;\n\t\t\t\tif (inverted) {\n\t\t\t\t\tdataLabelOptions.align = 'right';\n\t\t\t\t\tdataLabelOptions.x = dataLabelOptions.xLow;\n\t\t\t\t} else {\n\t\t\t\t\tdataLabelOptions.y = dataLabelOptions.yLow;\n\t\t\t\t}\n\t\t\t}\n\t\t\tseriesProto.drawDataLabels.apply(this, arguments);\n\t\t}\n\t\n\t},\n\t\n\talignDataLabel: seriesTypes.column.prototype.alignDataLabel,\n\t\n\tgetSymbol: seriesTypes.column.prototype.getSymbol,\n\t\n\tdrawPoints: noop\n});/**\n * The AreaSplineRangeSeries class\n */\n\ndefaultPlotOptions.areasplinerange = merge(defaultPlotOptions.arearange);\n\n/**\n * AreaSplineRangeSeries object\n */\nseriesTypes.areasplinerange = extendClass(seriesTypes.arearange, {\n\ttype: 'areasplinerange',\n\tgetPointSpline: seriesTypes.spline.prototype.getPointSpline\n});/**\n * The ColumnRangeSeries class\n */\ndefaultPlotOptions.columnrange = merge(defaultPlotOptions.column, defaultPlotOptions.arearange, {\n\tlineWidth: 1,\n\tpointRange: null\n});\n\n/**\n * ColumnRangeSeries object\n */\nseriesTypes.columnrange = extendClass(seriesTypes.arearange, {\n\ttype: 'columnrange',\n\t/**\n\t * Translate data points from raw values x and y to plotX and plotY\n\t */\n\ttranslate: function () {\n\t\tvar series = this,\n\t\t\tyAxis = series.yAxis,\n\t\t\tplotHigh;\n\n\t\tcolProto.translate.apply(series);\n\n\t\t// Set plotLow and plotHigh\n\t\teach(series.points, function (point) {\n\t\t\tvar shapeArgs = point.shapeArgs,\n\t\t\t\tminPointLength = series.options.minPointLength,\n\t\t\t\theightDifference,\n\t\t\t\theight,\n\t\t\t\ty;\n\n\t\t\tpoint.plotHigh = plotHigh = yAxis.translate(point.high, 0, 1, 0, 1);\n\t\t\tpoint.plotLow = point.plotY;\n\n\t\t\t// adjust shape\n\t\t\ty = plotHigh;\n\t\t\theight = point.plotY - plotHigh;\n\n\t\t\tif (height < minPointLength) {\n\t\t\t\theightDifference = (minPointLength - height);\n\t\t\t\theight += heightDifference;\n\t\t\t\ty -= heightDifference / 2;\n\t\t\t}\n\t\t\tshapeArgs.height = height;\n\t\t\tshapeArgs.y = y;\n\t\t});\n\t},\n\ttrackerGroups: ['group', 'dataLabels'],\n\tdrawGraph: noop,\n\tpointAttrToOptions: colProto.pointAttrToOptions,\n\tdrawPoints: colProto.drawPoints,\n\tdrawTracker: colProto.drawTracker,\n\tanimate: colProto.animate,\n\tgetColumnMetrics: colProto.getColumnMetrics\n});\n/* \n * The GaugeSeries class\n */\n\n\n\n/**\n * Extend the default options\n */\ndefaultPlotOptions.gauge = merge(defaultPlotOptions.line, {\n\tdataLabels: {\n\t\tenabled: true,\n\t\ty: 15,\n\t\tborderWidth: 1,\n\t\tborderColor: 'silver',\n\t\tborderRadius: 3,\n\t\tstyle: {\n\t\t\tfontWeight: 'bold'\n\t\t},\n\t\tverticalAlign: 'top',\n\t\tzIndex: 2\n\t},\n\tdial: {\n\t\t// radius: '80%',\n\t\t// backgroundColor: 'black',\n\t\t// borderColor: 'silver',\n\t\t// borderWidth: 0,\n\t\t// baseWidth: 3,\n\t\t// topWidth: 1,\n\t\t// baseLength: '70%' // of radius\n\t\t// rearLength: '10%'\n\t},\n\tpivot: {\n\t\t//radius: 5,\n\t\t//borderWidth: 0\n\t\t//borderColor: 'silver',\n\t\t//backgroundColor: 'black'\n\t},\n\ttooltip: {\n\t\theaderFormat: ''\n\t},\n\tshowInLegend: false\n});\n\n/**\n * Extend the point object\n */\nvar GaugePoint = Highcharts.extendClass(Highcharts.Point, {\n\t/**\n\t * Don't do any hover colors or anything\n\t */\n\tsetState: function (state) {\n\t\tthis.state = state;\n\t}\n});\n\n\n/**\n * Add the series type\n */\nvar GaugeSeries = {\n\ttype: 'gauge',\n\tpointClass: GaugePoint,\n\t\n\t// chart.angular will be set to true when a gauge series is present, and this will\n\t// be used on the axes\n\tangular: true, \n\tdrawGraph: noop,\n\tfixedBox: true,\n\ttrackerGroups: ['group', 'dataLabels'],\n\t\n\t/**\n\t * Calculate paths etc\n\t */\n\ttranslate: function () {\n\t\t\n\t\tvar series = this,\n\t\t\tyAxis = series.yAxis,\n\t\t\toptions = series.options,\n\t\t\tcenter = yAxis.center;\n\t\t\t\n\t\tseries.generatePoints();\n\t\t\n\t\teach(series.points, function (point) {\n\t\t\t\n\t\t\tvar dialOptions = merge(options.dial, point.dial),\n\t\t\t\tradius = (pInt(pick(dialOptions.radius, 80)) * center[2]) / 200,\n\t\t\t\tbaseLength = (pInt(pick(dialOptions.baseLength, 70)) * radius) / 100,\n\t\t\t\trearLength = (pInt(pick(dialOptions.rearLength, 10)) * radius) / 100,\n\t\t\t\tbaseWidth = dialOptions.baseWidth || 3,\n\t\t\t\ttopWidth = dialOptions.topWidth || 1,\n\t\t\t\trotation = yAxis.startAngleRad + yAxis.translate(point.y, null, null, null, true);\n\n\t\t\t// Handle the wrap option\n\t\t\tif (options.wrap === false) {\n\t\t\t\trotation = Math.max(yAxis.startAngleRad, Math.min(yAxis.endAngleRad, rotation));\n\t\t\t}\n\t\t\trotation = rotation * 180 / Math.PI;\n\t\t\t\t\n\t\t\tpoint.shapeType = 'path';\n\t\t\tpoint.shapeArgs = {\n\t\t\t\td: dialOptions.path || [\n\t\t\t\t\t'M', \n\t\t\t\t\t-rearLength, -baseWidth / 2, \n\t\t\t\t\t'L', \n\t\t\t\t\tbaseLength, -baseWidth / 2,\n\t\t\t\t\tradius, -topWidth / 2,\n\t\t\t\t\tradius, topWidth / 2,\n\t\t\t\t\tbaseLength, baseWidth / 2,\n\t\t\t\t\t-rearLength, baseWidth / 2,\n\t\t\t\t\t'z'\n\t\t\t\t],\n\t\t\t\ttranslateX: center[0],\n\t\t\t\ttranslateY: center[1],\n\t\t\t\trotation: rotation\n\t\t\t};\n\t\t\t\n\t\t\t// Positions for data label\n\t\t\tpoint.plotX = center[0];\n\t\t\tpoint.plotY = center[1];\n\t\t});\n\t},\n\t\n\t/**\n\t * Draw the points where each point is one needle\n\t */\n\tdrawPoints: function () {\n\t\t\n\t\tvar series = this,\n\t\t\tcenter = series.yAxis.center,\n\t\t\tpivot = series.pivot,\n\t\t\toptions = series.options,\n\t\t\tpivotOptions = options.pivot,\n\t\t\trenderer = series.chart.renderer;\n\t\t\n\t\teach(series.points, function (point) {\n\t\t\t\n\t\t\tvar graphic = point.graphic,\n\t\t\t\tshapeArgs = point.shapeArgs,\n\t\t\t\td = shapeArgs.d,\n\t\t\t\tdialOptions = merge(options.dial, point.dial); // #1233\n\t\t\t\n\t\t\tif (graphic) {\n\t\t\t\tgraphic.animate(shapeArgs);\n\t\t\t\tshapeArgs.d = d; // animate alters it\n\t\t\t} else {\n\t\t\t\tpoint.graphic = renderer[point.shapeType](shapeArgs)\n\t\t\t\t\t.attr({\n\t\t\t\t\t\tstroke: dialOptions.borderColor || 'none',\n\t\t\t\t\t\t'stroke-width': dialOptions.borderWidth || 0,\n\t\t\t\t\t\tfill: dialOptions.backgroundColor || 'black',\n\t\t\t\t\t\trotation: shapeArgs.rotation // required by VML when animation is false\n\t\t\t\t\t})\n\t\t\t\t\t.add(series.group);\n\t\t\t}\n\t\t});\n\t\t\n\t\t// Add or move the pivot\n\t\tif (pivot) {\n\t\t\tpivot.animate({ // #1235\n\t\t\t\ttranslateX: center[0],\n\t\t\t\ttranslateY: center[1]\n\t\t\t});\n\t\t} else {\n\t\t\tseries.pivot = renderer.circle(0, 0, pick(pivotOptions.radius, 5))\n\t\t\t\t.attr({\n\t\t\t\t\t'stroke-width': pivotOptions.borderWidth || 0,\n\t\t\t\t\tstroke: pivotOptions.borderColor || 'silver',\n\t\t\t\t\tfill: pivotOptions.backgroundColor || 'black'\n\t\t\t\t})\n\t\t\t\t.translate(center[0], center[1])\n\t\t\t\t.add(series.group);\n\t\t}\n\t},\n\t\n\t/**\n\t * Animate the arrow up from startAngle\n\t */\n\tanimate: function (init) {\n\t\tvar series = this;\n\n\t\tif (!init) {\n\t\t\teach(series.points, function (point) {\n\t\t\t\tvar graphic = point.graphic;\n\n\t\t\t\tif (graphic) {\n\t\t\t\t\t// start value\n\t\t\t\t\tgraphic.attr({\n\t\t\t\t\t\trotation: series.yAxis.startAngleRad * 180 / Math.PI\n\t\t\t\t\t});\n\n\t\t\t\t\t// animate\n\t\t\t\t\tgraphic.animate({\n\t\t\t\t\t\trotation: point.shapeArgs.rotation\n\t\t\t\t\t}, series.options.animation);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// delete this function to allow it only once\n\t\t\tseries.animate = null;\n\t\t}\n\t},\n\t\n\trender: function () {\n\t\tthis.group = this.plotGroup(\n\t\t\t'group', \n\t\t\t'series', \n\t\t\tthis.visible ? 'visible' : 'hidden', \n\t\t\tthis.options.zIndex, \n\t\t\tthis.chart.seriesGroup\n\t\t);\n\t\tseriesTypes.pie.prototype.render.call(this);\n\t\tthis.group.clip(this.chart.clipRect);\n\t},\n\t\n\tsetData: seriesTypes.pie.prototype.setData,\n\tdrawTracker: seriesTypes.column.prototype.drawTracker\n};\nseriesTypes.gauge = Highcharts.extendClass(seriesTypes.line, GaugeSeries);/* ****************************************************************************\n * Start Box plot series code\t\t\t\t\t\t\t\t\t\t\t      *\n *****************************************************************************/\n\n// Set default options\ndefaultPlotOptions.boxplot = merge(defaultPlotOptions.column, {\n\tfillColor: '#FFFFFF',\n\tlineWidth: 1,\n\t//medianColor: null,\n\tmedianWidth: 2,\n\tstates: {\n\t\thover: {\n\t\t\tbrightness: -0.3\n\t\t}\n\t},\n\t//stemColor: null,\n\t//stemDashStyle: 'solid'\n\t//stemWidth: null,\n\tthreshold: null,\n\ttooltip: {\n\t\tpointFormat: '<span style=\"color:{series.color};font-weight:bold\">{series.name}</span><br/>' +\n\t\t\t'Maximum: {point.high}<br/>' +\n\t\t\t'Upper quartile: {point.q3}<br/>' +\n\t\t\t'Median: {point.median}<br/>' +\n\t\t\t'Lower quartile: {point.q1}<br/>' +\n\t\t\t'Minimum: {point.low}<br/>'\n\t\t\t\n\t},\n\t//whiskerColor: null,\n\twhiskerLength: '50%',\n\twhiskerWidth: 2\n});\n\n// Create the series object\nseriesTypes.boxplot = extendClass(seriesTypes.column, {\n\ttype: 'boxplot',\n\tpointArrayMap: ['low', 'q1', 'median', 'q3', 'high'], // array point configs are mapped to this\n\ttoYData: function (point) { // return a plain array for speedy calculation\n\t\treturn [point.low, point.q1, point.median, point.q3, point.high];\n\t},\n\tpointValKey: 'high', // defines the top of the tracker\n\t\n\t/**\n\t * One-to-one mapping from options to SVG attributes\n\t */\n\tpointAttrToOptions: { // mapping between SVG attributes and the corresponding options\n\t\tfill: 'fillColor',\n\t\tstroke: 'color',\n\t\t'stroke-width': 'lineWidth'\n\t},\n\t\n\t/**\n\t * Disable data labels for box plot\n\t */\n\tdrawDataLabels: noop,\n\n\t/**\n\t * Translate data points from raw values x and y to plotX and plotY\n\t */\n\ttranslate: function () {\n\t\tvar series = this,\n\t\t\tyAxis = series.yAxis,\n\t\t\tpointArrayMap = series.pointArrayMap;\n\n\t\tseriesTypes.column.prototype.translate.apply(series);\n\n\t\t// do the translation on each point dimension\n\t\teach(series.points, function (point) {\n\t\t\teach(pointArrayMap, function (key) {\n\t\t\t\tif (point[key] !== null) {\n\t\t\t\t\tpoint[key + 'Plot'] = yAxis.translate(point[key], 0, 1, 0, 1);\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t},\n\n\t/**\n\t * Draw the data points\n\t */\n\tdrawPoints: function () {\n\t\tvar series = this,  //state = series.state,\n\t\t\tpoints = series.points,\n\t\t\toptions = series.options,\n\t\t\tchart = series.chart,\n\t\t\trenderer = chart.renderer,\n\t\t\tpointAttr,\n\t\t\tq1Plot,\n\t\t\tq3Plot,\n\t\t\thighPlot,\n\t\t\tlowPlot,\n\t\t\tmedianPlot,\n\t\t\tcrispCorr,\n\t\t\tcrispX,\n\t\t\tgraphic,\n\t\t\tstemPath,\n\t\t\tstemAttr,\n\t\t\tboxPath,\n\t\t\twhiskersPath,\n\t\t\twhiskersAttr,\n\t\t\tmedianPath,\n\t\t\tmedianAttr,\n\t\t\twidth,\n\t\t\tleft,\n\t\t\tright,\n\t\t\thalfWidth,\n\t\t\tshapeArgs,\n\t\t\tcolor,\n\t\t\tdoQuartiles = series.doQuartiles !== false, // error bar inherits this series type but doesn't do quartiles\n\t\t\twhiskerLength = parseInt(series.options.whiskerLength, 10) / 100;\n\n\n\t\teach(points, function (point) {\n\n\t\t\tgraphic = point.graphic;\n\t\t\tshapeArgs = point.shapeArgs; // the box\n\t\t\tstemAttr = {};\n\t\t\twhiskersAttr = {};\n\t\t\tmedianAttr = {};\n\t\t\tcolor = point.color || series.color;\n\t\t\t\n\t\t\tif (point.plotY !== UNDEFINED) {\n\n\t\t\t\tpointAttr = point.pointAttr[point.selected ? 'selected' : ''];\n\n\t\t\t\t// crisp vector coordinates\n\t\t\t\twidth = shapeArgs.width;\n\t\t\t\tleft = mathFloor(shapeArgs.x);\n\t\t\t\tright = left + width;\n\t\t\t\thalfWidth = mathRound(width / 2);\n\t\t\t\t//crispX = mathRound(left + halfWidth) + crispCorr;\n\t\t\t\tq1Plot = mathFloor(doQuartiles ? point.q1Plot : point.lowPlot);// + crispCorr;\n\t\t\t\tq3Plot = mathFloor(doQuartiles ? point.q3Plot : point.lowPlot);// + crispCorr;\n\t\t\t\thighPlot = mathFloor(point.highPlot);// + crispCorr;\n\t\t\t\tlowPlot = mathFloor(point.lowPlot);// + crispCorr;\n\t\t\t\t\n\t\t\t\t// Stem attributes\n\t\t\t\tstemAttr.stroke = point.stemColor || options.stemColor || color;\n\t\t\t\tstemAttr['stroke-width'] = pick(point.stemWidth, options.stemWidth, options.lineWidth);\n\t\t\t\tstemAttr.dashstyle = point.stemDashStyle || options.stemDashStyle;\n\t\t\t\t\n\t\t\t\t// Whiskers attributes\n\t\t\t\twhiskersAttr.stroke = point.whiskerColor || options.whiskerColor || color;\n\t\t\t\twhiskersAttr['stroke-width'] = pick(point.whiskerWidth, options.whiskerWidth, options.lineWidth);\n\t\t\t\t\n\t\t\t\t// Median attributes\n\t\t\t\tmedianAttr.stroke = point.medianColor || options.medianColor || color;\n\t\t\t\tmedianAttr['stroke-width'] = pick(point.medianWidth, options.medianWidth, options.lineWidth);\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t// The stem\n\t\t\t\tcrispCorr = (stemAttr['stroke-width'] % 2) / 2;\n\t\t\t\tcrispX = left + halfWidth + crispCorr;\t\t\t\t\n\t\t\t\tstemPath = [\n\t\t\t\t\t// stem up\n\t\t\t\t\t'M',\n\t\t\t\t\tcrispX, q3Plot,\n\t\t\t\t\t'L',\n\t\t\t\t\tcrispX, highPlot,\n\t\t\t\t\t\n\t\t\t\t\t// stem down\n\t\t\t\t\t'M',\n\t\t\t\t\tcrispX, q1Plot,\n\t\t\t\t\t'L',\n\t\t\t\t\tcrispX, lowPlot,\n\t\t\t\t\t'z'\n\t\t\t\t];\n\t\t\t\t\n\t\t\t\t// The box\n\t\t\t\tif (doQuartiles) {\n\t\t\t\t\tcrispCorr = (pointAttr['stroke-width'] % 2) / 2;\n\t\t\t\t\tcrispX = mathFloor(crispX) + crispCorr;\n\t\t\t\t\tq1Plot = mathFloor(q1Plot) + crispCorr;\n\t\t\t\t\tq3Plot = mathFloor(q3Plot) + crispCorr;\n\t\t\t\t\tleft += crispCorr;\n\t\t\t\t\tright += crispCorr;\n\t\t\t\t\tboxPath = [\n\t\t\t\t\t\t'M',\n\t\t\t\t\t\tleft, q3Plot,\n\t\t\t\t\t\t'L',\n\t\t\t\t\t\tleft, q1Plot,\n\t\t\t\t\t\t'L',\n\t\t\t\t\t\tright, q1Plot,\n\t\t\t\t\t\t'L',\n\t\t\t\t\t\tright, q3Plot,\n\t\t\t\t\t\t'L',\n\t\t\t\t\t\tleft, q3Plot,\n\t\t\t\t\t\t'z'\n\t\t\t\t\t];\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t// The whiskers\n\t\t\t\tif (whiskerLength) {\n\t\t\t\t\tcrispCorr = (whiskersAttr['stroke-width'] % 2) / 2;\n\t\t\t\t\thighPlot = highPlot + crispCorr;\n\t\t\t\t\tlowPlot = lowPlot + crispCorr;\n\t\t\t\t\twhiskersPath = [\n\t\t\t\t\t\t// High whisker\n\t\t\t\t\t\t'M',\n\t\t\t\t\t\tcrispX - halfWidth * whiskerLength, \n\t\t\t\t\t\thighPlot,\n\t\t\t\t\t\t'L',\n\t\t\t\t\t\tcrispX + halfWidth * whiskerLength, \n\t\t\t\t\t\thighPlot,\n\t\t\t\t\t\t\n\t\t\t\t\t\t// Low whisker\n\t\t\t\t\t\t'M',\n\t\t\t\t\t\tcrispX - halfWidth * whiskerLength, \n\t\t\t\t\t\tlowPlot,\n\t\t\t\t\t\t'L',\n\t\t\t\t\t\tcrispX + halfWidth * whiskerLength, \n\t\t\t\t\t\tlowPlot\n\t\t\t\t\t];\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t// The median\n\t\t\t\tcrispCorr = (medianAttr['stroke-width'] % 2) / 2;\t\t\t\t\n\t\t\t\tmedianPlot = mathRound(point.medianPlot) + crispCorr;\n\t\t\t\tmedianPath = [\n\t\t\t\t\t'M',\n\t\t\t\t\tleft, \n\t\t\t\t\tmedianPlot,\n\t\t\t\t\t'L',\n\t\t\t\t\tright, \n\t\t\t\t\tmedianPlot,\n\t\t\t\t\t'z'\n\t\t\t\t];\n\t\t\t\t\n\t\t\t\t// Create or update the graphics\n\t\t\t\tif (graphic) { // update\n\t\t\t\t\t\n\t\t\t\t\tpoint.stem.animate({ d: stemPath });\n\t\t\t\t\tif (whiskerLength) {\n\t\t\t\t\t\tpoint.whiskers.animate({ d: whiskersPath });\n\t\t\t\t\t}\n\t\t\t\t\tif (doQuartiles) {\n\t\t\t\t\t\tpoint.box.animate({ d: boxPath });\n\t\t\t\t\t}\n\t\t\t\t\tpoint.medianShape.animate({ d: medianPath });\n\t\t\t\t\t\n\t\t\t\t} else { // create new\n\t\t\t\t\tpoint.graphic = graphic = renderer.g()\n\t\t\t\t\t\t.add(series.group);\n\t\t\t\t\t\n\t\t\t\t\tpoint.stem = renderer.path(stemPath)\n\t\t\t\t\t\t.attr(stemAttr)\n\t\t\t\t\t\t.add(graphic);\n\t\t\t\t\t\t\n\t\t\t\t\tif (whiskerLength) {\n\t\t\t\t\t\tpoint.whiskers = renderer.path(whiskersPath) \n\t\t\t\t\t\t\t.attr(whiskersAttr)\n\t\t\t\t\t\t\t.add(graphic);\n\t\t\t\t\t}\n\t\t\t\t\tif (doQuartiles) {\n\t\t\t\t\t\tpoint.box = renderer.path(boxPath)\n\t\t\t\t\t\t\t.attr(pointAttr)\n\t\t\t\t\t\t\t.add(graphic);\n\t\t\t\t\t}\t\n\t\t\t\t\tpoint.medianShape = renderer.path(medianPath)\n\t\t\t\t\t\t.attr(medianAttr)\n\t\t\t\t\t\t.add(graphic);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t}\n\n\n});\n\n/* ****************************************************************************\n * End Box plot series code\t\t\t\t\t\t\t\t\t\t\t\t*\n *****************************************************************************/\n/* ****************************************************************************\n * Start error bar series code                                                *\n *****************************************************************************/\n\n// 1 - set default options\ndefaultPlotOptions.errorbar = merge(defaultPlotOptions.boxplot, {\n\tcolor: '#000000',\n\tgrouping: false,\n\tlinkedTo: ':previous',\n\ttooltip: {\n\t\tpointFormat: defaultPlotOptions.arearange.tooltip.pointFormat\n\t},\n\twhiskerWidth: null\n});\n\n// 2 - Create the series object\nseriesTypes.errorbar = extendClass(seriesTypes.boxplot, {\n\ttype: 'errorbar',\n\tpointArrayMap: ['low', 'high'], // array point configs are mapped to this\n\ttoYData: function (point) { // return a plain array for speedy calculation\n\t\treturn [point.low, point.high];\n\t},\n\tpointValKey: 'high', // defines the top of the tracker\n\tdoQuartiles: false,\n\n\t/**\n\t * Get the width and X offset, either on top of the linked series column\n\t * or standalone\n\t */\n\tgetColumnMetrics: function () {\n\t\treturn (this.linkedParent && this.linkedParent.columnMetrics) || \n\t\t\tseriesTypes.column.prototype.getColumnMetrics.call(this);\n\t}\n});\n\n/* ****************************************************************************\n * End error bar series code                                                  *\n *****************************************************************************/\n/* ****************************************************************************\n * Start Waterfall series code                                                *\n *****************************************************************************/\n\n// 1 - set default options\ndefaultPlotOptions.waterfall = merge(defaultPlotOptions.column, {\n\tlineWidth: 1,\n\tlineColor: '#333',\n\tdashStyle: 'dot',\n\tborderColor: '#333'\n});\n\n\n// 2 - Create the series object\nseriesTypes.waterfall = extendClass(seriesTypes.column, {\n\ttype: 'waterfall',\n\n\tupColorProp: 'fill',\n\n\tpointArrayMap: ['low', 'y'],\n\n\tpointValKey: 'y',\n\n\t/**\n\t * Init waterfall series, force stacking\n\t */\n\tinit: function (chart, options) {\n\t\t// force stacking\n\t\toptions.stacking = true;\n\n\t\tseriesTypes.column.prototype.init.call(this, chart, options);\n\t},\n\n\n\t/**\n\t * Translate data points from raw values\n\t */\n\ttranslate: function () {\n\t\tvar series = this,\n\t\t\toptions = series.options,\n\t\t\taxis = series.yAxis,\n\t\t\tlen,\n\t\t\ti,\n\t\t\tpoints,\n\t\t\tpoint,\n\t\t\tshapeArgs,\n\t\t\tstack,\n\t\t\ty,\n\t\t\tpreviousY,\n\t\t\tstackPoint,\n\t\t\tthreshold = options.threshold,\n\t\t\tcrispCorr = (options.borderWidth % 2) / 2;\n\n\t\t// run column series translate\n\t\tseriesTypes.column.prototype.translate.apply(this);\n\n\t\tpreviousY = threshold;\n\t\tpoints = series.points;\n\n\t\tfor (i = 0, len = points.length; i < len; i++) {\n\t\t\t// cache current point object\n\t\t\tpoint = points[i];\n\t\t\tshapeArgs = point.shapeArgs;\n\n\t\t\t// get current stack\n\t\t\tstack = series.getStack(i);\n\t\t\tstackPoint = stack.points[series.index];\n\n\t\t\t// override point value for sums\n\t\t\tif (isNaN(point.y)) {\n\t\t\t\tpoint.y = series.yData[i];\n\t\t\t}\n\n\t\t\t// up points\n\t\t\ty = mathMax(previousY, previousY + point.y) + stackPoint[0];\n\t\t\tshapeArgs.y = axis.translate(y, 0, 1);\n\n\n\t\t\t// sum points\n\t\t\tif (point.isSum || point.isIntermediateSum) {\n\t\t\t\tshapeArgs.y = axis.translate(stackPoint[1], 0, 1);\n\t\t\t\tshapeArgs.height = axis.translate(stackPoint[0], 0, 1) - shapeArgs.y;\n\n\t\t\t// if it's not the sum point, update previous stack end position\n\t\t\t} else {\n\t\t\t\tpreviousY += stack.total;\n\t\t\t}\n\n\t\t\t// negative points\n\t\t\tif (shapeArgs.height < 0) {\n\t\t\t\tshapeArgs.y += shapeArgs.height;\n\t\t\t\tshapeArgs.height *= -1;\n\t\t\t}\n\n\t\t\tpoint.plotY = shapeArgs.y = mathRound(shapeArgs.y) - crispCorr;\n\t\t\tshapeArgs.height = mathRound(shapeArgs.height);\n\t\t\tpoint.yBottom = shapeArgs.y + shapeArgs.height;\n\t\t}\n\t},\n\n\t/**\n\t * Call default processData then override yData to reflect waterfall's extremes on yAxis\n\t */\n\tprocessData: function (force) {\n\t\tvar series = this,\n\t\t\toptions = series.options,\n\t\t\tyData = series.yData,\n\t\t\tpoints = series.points,\n\t\t\tpoint,\n\t\t\tdataLength = yData.length,\n\t\t\tthreshold = options.threshold || 0,\n\t\t\tsubSum,\n\t\t\tsum,\n\t\t\tdataMin,\n\t\t\tdataMax,\n\t\t\ty,\n\t\t\ti;\n\n\t\tsum = subSum = dataMin = dataMax = threshold;\n\n\t\tfor (i = 0; i < dataLength; i++) {\n\t\t\ty = yData[i];\n\t\t\tpoint = points && points[i] ? points[i] : {};\n\n\t\t\tif (y === \"sum\" || point.isSum) {\n\t\t\t\tyData[i] = sum;\n\t\t\t} else if (y === \"intermediateSum\" || point.isIntermediateSum) {\n\t\t\t\tyData[i] = subSum;\n\t\t\t\tsubSum = threshold;\n\t\t\t} else {\n\t\t\t\tsum += y;\n\t\t\t\tsubSum += y;\n\t\t\t}\n\t\t\tdataMin = Math.min(sum, dataMin);\n\t\t\tdataMax = Math.max(sum, dataMax);\n\t\t}\n\n\t\tSeries.prototype.processData.call(this, force);\n\n\t\t// Record extremes\n\t\tseries.dataMin = dataMin;\n\t\tseries.dataMax = dataMax;\n\t},\n\n\t/**\n\t * Return y value or string if point is sum\n\t */\n\ttoYData: function (pt) {\n\t\tif (pt.isSum) {\n\t\t\treturn \"sum\";\n\t\t} else if (pt.isIntermediateSum) {\n\t\t\treturn \"intermediateSum\";\n\t\t}\n\n\t\treturn pt.y;\n\t},\n\n\t/**\n\t * Postprocess mapping between options and SVG attributes\n\t */\n\tgetAttribs: function () {\n\t\tseriesTypes.column.prototype.getAttribs.apply(this, arguments);\n\n\t\tvar series = this,\n\t\t\toptions = series.options,\n\t\t\tstateOptions = options.states,\n\t\t\tupColor = options.upColor || series.color,\n\t\t\thoverColor = Highcharts.Color(upColor).brighten(0.1).get(),\n\t\t\tseriesDownPointAttr = merge(series.pointAttr),\n\t\t\tupColorProp = series.upColorProp;\n\n\t\tseriesDownPointAttr[''][upColorProp] = upColor;\n\t\tseriesDownPointAttr.hover[upColorProp] = stateOptions.hover.upColor || hoverColor;\n\t\tseriesDownPointAttr.select[upColorProp] = stateOptions.select.upColor || upColor;\n\n\t\teach(series.points, function (point) {\n\t\t\tif (point.y > 0 && !point.color) {\n\t\t\t\tpoint.pointAttr = seriesDownPointAttr;\n\t\t\t\tpoint.color = upColor;\n\t\t\t}\n\t\t});\n\t},\n\n\t/**\n\t * Draw columns' connector lines\n\t */\n\tgetGraphPath: function () {\n\n\t\tvar data = this.data,\n\t\t\tlength = data.length,\n\t\t\tlineWidth = this.options.lineWidth + this.options.borderWidth,\n\t\t\tnormalizer = mathRound(lineWidth) % 2 / 2,\n\t\t\tpath = [],\n\t\t\tM = 'M',\n\t\t\tL = 'L',\n\t\t\tprevArgs,\n\t\t\tpointArgs,\n\t\t\ti,\n\t\t\td;\n\n\t\tfor (i = 1; i < length; i++) {\n\t\t\tpointArgs = data[i].shapeArgs;\n\t\t\tprevArgs = data[i - 1].shapeArgs;\n\n\t\t\td = [\n\t\t\t\tM,\n\t\t\t\tprevArgs.x + prevArgs.width, prevArgs.y + normalizer,\n\t\t\t\tL,\n\t\t\t\tpointArgs.x, prevArgs.y + normalizer\n\t\t\t];\n\n\t\t\tif (data[i - 1].y < 0) {\n\t\t\t\td[2] += prevArgs.height;\n\t\t\t\td[5] += prevArgs.height;\n\t\t\t}\n\n\t\t\tpath = path.concat(d);\n\t\t}\n\n\t\treturn path;\n\t},\n\n\t/**\n\t * Extremes are recorded in processData\n\t */\n\tgetExtremes: noop,\n\n\t/**\n\t * Return stack for given index\n\t */\n\tgetStack: function (i) {\n\t\tvar axis = this.yAxis,\n\t\t\tstacks = axis.stacks,\n\t\t\tkey = this.stackKey;\n\n\t\tif (this.processedYData[i] < this.options.threshold) {\n\t\t\tkey = '-' + key;\n\t\t}\n\n\t\treturn stacks[key][i];\n\t},\n\n\tdrawGraph: Series.prototype.drawGraph\n});\n\n/* ****************************************************************************\n * End Waterfall series code                                                  *\n *****************************************************************************/\n/* ****************************************************************************\n * Start Bubble series code\t\t\t\t\t\t\t\t\t\t\t          *\n *****************************************************************************/\n\n// 1 - set default options\ndefaultPlotOptions.bubble = merge(defaultPlotOptions.scatter, {\n\tdataLabels: {\n\t\tinside: true,\n\t\tstyle: {\n\t\t\tcolor: 'white',\n\t\t\ttextShadow: '0px 0px 3px black'\n\t\t},\n\t\tverticalAlign: 'middle'\n\t},\n\t// displayNegative: true,\n\tmarker: {\n\t\t// fillOpacity: 0.5,\n\t\tlineColor: null, // inherit from series.color\n\t\tlineWidth: 1\n\t},\n\tminSize: 8,\n\tmaxSize: '20%',\n\t// negativeColor: null,\n\ttooltip: {\n\t\tpointFormat: '({point.x}, {point.y}), Size: {point.z}'\n\t},\n\tturboThreshold: 0,\n\tzThreshold: 0\n});\n\n// 2 - Create the series object\nseriesTypes.bubble = extendClass(seriesTypes.scatter, {\n\ttype: 'bubble',\n\tpointArrayMap: ['y', 'z'],\n\ttrackerGroups: ['group', 'dataLabelsGroup'],\n\t\n\t/**\n\t * Mapping between SVG attributes and the corresponding options\n\t */\n\tpointAttrToOptions: { \n\t\tstroke: 'lineColor',\n\t\t'stroke-width': 'lineWidth',\n\t\tfill: 'fillColor'\n\t},\n\t\n\t/**\n\t * Apply the fillOpacity to all fill positions\n\t */\n\tapplyOpacity: function (fill) {\n\t\tvar markerOptions = this.options.marker,\n\t\t\tfillOpacity = pick(markerOptions.fillOpacity, 0.5);\n\t\t\n\t\t// When called from Legend.colorizeItem, the fill isn't predefined\n\t\tfill = fill || markerOptions.fillColor || this.color; \n\t\t\n\t\tif (fillOpacity !== 1) {\n\t\t\tfill = Highcharts.Color(fill).setOpacity(fillOpacity).get('rgba');\n\t\t}\n\t\treturn fill;\n\t},\n\t\n\t/**\n\t * Extend the convertAttribs method by applying opacity to the fill\n\t */\n\tconvertAttribs: function () {\n\t\tvar obj = Series.prototype.convertAttribs.apply(this, arguments);\n\t\t\n\t\tobj.fill = this.applyOpacity(obj.fill);\n\t\t\n\t\treturn obj;\n\t},\n\n\t/**\n\t * Get the radius for each point based on the minSize, maxSize and each point's Z value. This\n\t * must be done prior to Series.translate because the axis needs to add padding in \n\t * accordance with the point sizes.\n\t */\n\tgetRadii: function (zMin, zMax, minSize, maxSize) {\n\t\tvar len,\n\t\t\ti,\n\t\t\tpos,\n\t\t\tzData = this.zData,\n\t\t\tradii = [],\n\t\t\tzRange;\n\t\t\n\t\t// Set the shape type and arguments to be picked up in drawPoints\n\t\tfor (i = 0, len = zData.length; i < len; i++) {\n\t\t\tzRange = zMax - zMin;\n\t\t\tpos = zRange > 0 ? // relative size, a number between 0 and 1\n\t\t\t\t(zData[i] - zMin) / (zMax - zMin) : \n\t\t\t\t0.5;\n\t\t\tradii.push(math.ceil(minSize + pos * (maxSize - minSize)) / 2);\n\t\t}\n\t\tthis.radii = radii;\n\t},\n\t\n\t/**\n\t * Perform animation on the bubbles\n\t */\n\tanimate: function (init) {\n\t\tvar animation = this.options.animation;\n\t\t\n\t\tif (!init) { // run the animation\n\t\t\teach(this.points, function (point) {\n\t\t\t\tvar graphic = point.graphic,\n\t\t\t\t\tshapeArgs = point.shapeArgs;\n\n\t\t\t\tif (graphic && shapeArgs) {\n\t\t\t\t\t// start values\n\t\t\t\t\tgraphic.attr('r', 1);\n\n\t\t\t\t\t// animate\n\t\t\t\t\tgraphic.animate({\n\t\t\t\t\t\tr: shapeArgs.r\n\t\t\t\t\t}, animation);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// delete this function to allow it only once\n\t\t\tthis.animate = null;\n\t\t}\n\t},\n\t\n\t/**\n\t * Extend the base translate method to handle bubble size\n\t */\n\ttranslate: function () {\n\t\t\n\t\tvar i,\n\t\t\tdata = this.data,\n\t\t\tpoint,\n\t\t\tradius,\n\t\t\tradii = this.radii;\n\t\t\n\t\t// Run the parent method\n\t\tseriesTypes.scatter.prototype.translate.call(this);\n\t\t\n\t\t// Set the shape type and arguments to be picked up in drawPoints\n\t\ti = data.length;\n\t\t\n\t\twhile (i--) {\n\t\t\tpoint = data[i];\n\t\t\tradius = radii ? radii[i] : 0; // #1737\n\n\t\t\t// Flag for negativeColor to be applied in Series.js\n\t\t\tpoint.negative = point.z < (this.options.zThreshold || 0);\n\t\t\t\n\t\t\tif (radius >= this.minPxSize / 2) {\n\t\t\t\t// Shape arguments\n\t\t\t\tpoint.shapeType = 'circle';\n\t\t\t\tpoint.shapeArgs = {\n\t\t\t\t\tx: point.plotX,\n\t\t\t\t\ty: point.plotY,\n\t\t\t\t\tr: radius\n\t\t\t\t};\n\t\t\t\t\n\t\t\t\t// Alignment box for the data label\n\t\t\t\tpoint.dlBox = {\n\t\t\t\t\tx: point.plotX - radius,\n\t\t\t\t\ty: point.plotY - radius,\n\t\t\t\t\twidth: 2 * radius,\n\t\t\t\t\theight: 2 * radius\n\t\t\t\t};\n\t\t\t} else { // below zThreshold\n\t\t\t\tpoint.shapeArgs = point.plotY = point.dlBox = UNDEFINED; // #1691\n\t\t\t}\n\t\t}\n\t},\n\t\n\t/**\n\t * Get the series' symbol in the legend\n\t * \n\t * @param {Object} legend The legend object\n\t * @param {Object} item The series (this) or point\n\t */\n\tdrawLegendSymbol: function (legend, item) {\n\t\tvar radius = pInt(legend.itemStyle.fontSize) / 2;\n\t\t\n\t\titem.legendSymbol = this.chart.renderer.circle(\n\t\t\tradius,\n\t\t\tlegend.baseline - radius,\n\t\t\tradius\n\t\t).attr({\n\t\t\tzIndex: 3\n\t\t}).add(item.legendGroup);\n\t\titem.legendSymbol.isMarker = true;\t\n\t\t\n\t},\n\t\n\tdrawPoints: seriesTypes.column.prototype.drawPoints,\n\talignDataLabel: seriesTypes.column.prototype.alignDataLabel\n});\n\n/**\n * Add logic to pad each axis with the amount of pixels\n * necessary to avoid the bubbles to overflow.\n */\nAxis.prototype.beforePadding = function () {\n\tvar axis = this,\n\t\taxisLength = this.len,\n\t\tchart = this.chart,\n\t\tpxMin = 0, \n\t\tpxMax = axisLength,\n\t\tisXAxis = this.isXAxis,\n\t\tdataKey = isXAxis ? 'xData' : 'yData',\n\t\tmin = this.min,\n\t\textremes = {},\n\t\tsmallestSize = math.min(chart.plotWidth, chart.plotHeight),\n\t\tzMin = Number.MAX_VALUE,\n\t\tzMax = -Number.MAX_VALUE,\n\t\trange = this.max - min,\n\t\ttransA = axisLength / range,\n\t\tactiveSeries = [];\n\n\t// Handle padding on the second pass, or on redraw\n\tif (this.tickPositions) {\n\t\teach(this.series, function (series) {\n\n\t\t\tvar seriesOptions = series.options,\n\t\t\t\tzData;\n\n\t\t\tif (series.type === 'bubble' && series.visible) {\n\n\t\t\t\t// Correction for #1673\n\t\t\t\taxis.allowZoomOutside = true;\n\n\t\t\t\t// Cache it\n\t\t\t\tactiveSeries.push(series);\n\n\t\t\t\tif (isXAxis) { // because X axis is evaluated first\n\t\t\t\t\n\t\t\t\t\t// For each series, translate the size extremes to pixel values\n\t\t\t\t\teach(['minSize', 'maxSize'], function (prop) {\n\t\t\t\t\t\tvar length = seriesOptions[prop],\n\t\t\t\t\t\t\tisPercent = /%$/.test(length);\n\t\t\t\t\t\t\n\t\t\t\t\t\tlength = pInt(length);\n\t\t\t\t\t\textremes[prop] = isPercent ?\n\t\t\t\t\t\t\tsmallestSize * length / 100 :\n\t\t\t\t\t\t\tlength;\n\t\t\t\t\t\t\n\t\t\t\t\t});\n\t\t\t\t\tseries.minPxSize = extremes.minSize;\n\t\t\t\t\t\n\t\t\t\t\t// Find the min and max Z\n\t\t\t\t\tzData = series.zData;\n\t\t\t\t\tif (zData.length) { // #1735\n\t\t\t\t\t\tzMin = math.min(\n\t\t\t\t\t\t\tzMin,\n\t\t\t\t\t\t\tmath.max(\n\t\t\t\t\t\t\t\tarrayMin(zData), \n\t\t\t\t\t\t\t\tseriesOptions.displayNegative === false ? seriesOptions.zThreshold : -Number.MAX_VALUE\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t);\n\t\t\t\t\t\tzMax = math.max(zMax, arrayMax(zData));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\teach(activeSeries, function (series) {\n\n\t\t\tvar data = series[dataKey],\n\t\t\t\ti = data.length,\n\t\t\t\tradius;\n\n\t\t\tif (isXAxis) {\n\t\t\t\tseries.getRadii(zMin, zMax, extremes.minSize, extremes.maxSize);\n\t\t\t}\n\t\t\t\n\t\t\tif (range > 0) {\n\t\t\t\twhile (i--) {\n\t\t\t\t\tradius = series.radii[i];\n\t\t\t\t\tpxMin = Math.min(((data[i] - min) * transA) - radius, pxMin);\n\t\t\t\t\tpxMax = Math.max(((data[i] - min) * transA) + radius, pxMax);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\t\n\t\tif (activeSeries.length && range > 0 && pick(this.options.min, this.userMin) === UNDEFINED && pick(this.options.max, this.userMax) === UNDEFINED) {\n\t\t\tpxMax -= axisLength;\n\t\t\ttransA *= (axisLength + pxMin - pxMax) / axisLength;\n\t\t\tthis.min += pxMin / transA;\n\t\t\tthis.max += pxMax / transA;\n\t\t}\n\t}\n};\n\n/* ****************************************************************************\n * End Bubble series code                                                     *\n *****************************************************************************/\n/**\n * Extensions for polar charts. Additionally, much of the geometry required for polar charts is\n * gathered in RadialAxes.js.\n * \n */\n\nvar seriesProto = Series.prototype,\n\tpointerProto = Highcharts.Pointer.prototype;\n\n\n\n/**\n * Translate a point's plotX and plotY from the internal angle and radius measures to \n * true plotX, plotY coordinates\n */\nseriesProto.toXY = function (point) {\n\tvar xy,\n\t\tchart = this.chart,\n\t\tplotX = point.plotX,\n\t\tplotY = point.plotY;\n\t\n\t// Save rectangular plotX, plotY for later computation\n\tpoint.rectPlotX = plotX;\n\tpoint.rectPlotY = plotY;\n\t\n\t// Record the angle in degrees for use in tooltip\n\tpoint.clientX = ((plotX / Math.PI * 180) + this.xAxis.pane.options.startAngle) % 360;\n\t\n\t// Find the polar plotX and plotY\n\txy = this.xAxis.postTranslate(point.plotX, this.yAxis.len - plotY);\n\tpoint.plotX = point.polarPlotX = xy.x - chart.plotLeft;\n\tpoint.plotY = point.polarPlotY = xy.y - chart.plotTop;\n};\n\n/** \n * Order the tooltip points to get the mouse capture ranges correct. #1915. \n */\nseriesProto.orderTooltipPoints = function (points) {\n\tif (this.chart.polar) {\n\t\tpoints.sort(function (a, b) {\n\t\t\treturn a.clientX - b.clientX;\n\t\t});\n\n\t\t// Wrap mouse tracking around to capture movement on the segment to the left\n\t\t// of the north point (#1469, #2093).\n\t\tif (points[0]) {\n\t\t\tpoints[0].wrappedClientX = points[0].clientX + 360;\n\t\t\tpoints.push(points[0]);\n\t\t}\n\t}\n};\n\n\n/**\n * Add some special init logic to areas and areasplines\n */\nfunction initArea(proceed, chart, options) {\n\tproceed.call(this, chart, options);\n\tif (this.chart.polar) {\n\t\t\n\t\t/**\n\t\t * Overridden method to close a segment path. While in a cartesian plane the area \n\t\t * goes down to the threshold, in the polar chart it goes to the center.\n\t\t */\n\t\tthis.closeSegment = function (path) {\n\t\t\tvar center = this.xAxis.center;\n\t\t\tpath.push(\n\t\t\t\t'L',\n\t\t\t\tcenter[0],\n\t\t\t\tcenter[1]\n\t\t\t);\t\t\t\n\t\t};\n\t\t\n\t\t// Instead of complicated logic to draw an area around the inner area in a stack,\n\t\t// just draw it behind\n\t\tthis.closedStacks = true;\n\t}\n}\nwrap(seriesTypes.area.prototype, 'init', initArea);\nwrap(seriesTypes.areaspline.prototype, 'init', initArea);\n\t\t\n\n/**\n * Overridden method for calculating a spline from one point to the next\n */\nwrap(seriesTypes.spline.prototype, 'getPointSpline', function (proceed, segment, point, i) {\n\t\n\tvar ret,\n\t\tsmoothing = 1.5, // 1 means control points midway between points, 2 means 1/3 from the point, 3 is 1/4 etc;\n\t\tdenom = smoothing + 1,\n\t\tplotX, \n\t\tplotY,\n\t\tlastPoint,\n\t\tnextPoint,\n\t\tlastX,\n\t\tlastY,\n\t\tnextX,\n\t\tnextY,\n\t\tleftContX,\n\t\tleftContY,\n\t\trightContX,\n\t\trightContY,\n\t\tdistanceLeftControlPoint,\n\t\tdistanceRightControlPoint,\n\t\tleftContAngle,\n\t\trightContAngle,\n\t\tjointAngle;\n\t\t\n\t\t\n\tif (this.chart.polar) {\n\t\t\n\t\tplotX = point.plotX;\n\t\tplotY = point.plotY;\n\t\tlastPoint = segment[i - 1];\n\t\tnextPoint = segment[i + 1];\n\t\t\t\n\t\t// Connect ends\n\t\tif (this.connectEnds) {\n\t\t\tif (!lastPoint) {\n\t\t\t\tlastPoint = segment[segment.length - 2]; // not the last but the second last, because the segment is already connected\n\t\t\t}\n\t\t\tif (!nextPoint) {\n\t\t\t\tnextPoint = segment[1];\n\t\t\t}\t\n\t\t}\n\n\t\t// find control points\n\t\tif (lastPoint && nextPoint) {\n\t\t\n\t\t\tlastX = lastPoint.plotX;\n\t\t\tlastY = lastPoint.plotY;\n\t\t\tnextX = nextPoint.plotX;\n\t\t\tnextY = nextPoint.plotY;\n\t\t\tleftContX = (smoothing * plotX + lastX) / denom;\n\t\t\tleftContY = (smoothing * plotY + lastY) / denom;\n\t\t\trightContX = (smoothing * plotX + nextX) / denom;\n\t\t\trightContY = (smoothing * plotY + nextY) / denom;\n\t\t\tdistanceLeftControlPoint = Math.sqrt(Math.pow(leftContX - plotX, 2) + Math.pow(leftContY - plotY, 2));\n\t\t\tdistanceRightControlPoint = Math.sqrt(Math.pow(rightContX - plotX, 2) + Math.pow(rightContY - plotY, 2));\n\t\t\tleftContAngle = Math.atan2(leftContY - plotY, leftContX - plotX);\n\t\t\trightContAngle = Math.atan2(rightContY - plotY, rightContX - plotX);\n\t\t\tjointAngle = (Math.PI / 2) + ((leftContAngle + rightContAngle) / 2);\n\t\t\t\t\n\t\t\t\t\n\t\t\t// Ensure the right direction, jointAngle should be in the same quadrant as leftContAngle\n\t\t\tif (Math.abs(leftContAngle - jointAngle) > Math.PI / 2) {\n\t\t\t\tjointAngle -= Math.PI;\n\t\t\t}\n\t\t\t\n\t\t\t// Find the corrected control points for a spline straight through the point\n\t\t\tleftContX = plotX + Math.cos(jointAngle) * distanceLeftControlPoint;\n\t\t\tleftContY = plotY + Math.sin(jointAngle) * distanceLeftControlPoint;\n\t\t\trightContX = plotX + Math.cos(Math.PI + jointAngle) * distanceRightControlPoint;\n\t\t\trightContY = plotY + Math.sin(Math.PI + jointAngle) * distanceRightControlPoint;\n\t\t\t\n\t\t\t// Record for drawing in next point\n\t\t\tpoint.rightContX = rightContX;\n\t\t\tpoint.rightContY = rightContY;\n\n\t\t}\n\t\t\n\t\t\n\t\t// moveTo or lineTo\n\t\tif (!i) {\n\t\t\tret = ['M', plotX, plotY];\n\t\t} else { // curve from last point to this\n\t\t\tret = [\n\t\t\t\t'C',\n\t\t\t\tlastPoint.rightContX || lastPoint.plotX,\n\t\t\t\tlastPoint.rightContY || lastPoint.plotY,\n\t\t\t\tleftContX || plotX,\n\t\t\t\tleftContY || plotY,\n\t\t\t\tplotX,\n\t\t\t\tplotY\n\t\t\t];\n\t\t\tlastPoint.rightContX = lastPoint.rightContY = null; // reset for updating series later\n\t\t}\n\t\t\n\t\t\n\t} else {\n\t\tret = proceed.call(this, segment, point, i);\n\t}\n\treturn ret;\n});\n\n/**\n * Extend translate. The plotX and plotY values are computed as if the polar chart were a\n * cartesian plane, where plotX denotes the angle in radians and (yAxis.len - plotY) is the pixel distance from\n * center. \n */\nwrap(seriesProto, 'translate', function (proceed) {\n\t\t\n\t// Run uber method\n\tproceed.call(this);\n\t\n\t// Postprocess plot coordinates\n\tif (this.chart.polar && !this.preventPostTranslate) {\n\t\tvar points = this.points,\n\t\t\ti = points.length;\n\t\twhile (i--) {\n\t\t\t// Translate plotX, plotY from angle and radius to true plot coordinates\n\t\t\tthis.toXY(points[i]);\n\t\t}\n\t}\n});\n\n/** \n * Extend getSegmentPath to allow connecting ends across 0 to provide a closed circle in \n * line-like series.\n */\nwrap(seriesProto, 'getSegmentPath', function (proceed, segment) {\n\t\t\n\tvar points = this.points;\n\t\n\t// Connect the path\n\tif (this.chart.polar && this.options.connectEnds !== false && \n\t\t\tsegment[segment.length - 1] === points[points.length - 1] && points[0].y !== null) {\n\t\tthis.connectEnds = true; // re-used in splines\n\t\tsegment = [].concat(segment, [points[0]]);\n\t}\n\t\n\t// Run uber method\n\treturn proceed.call(this, segment);\n\t\n});\n\n\nfunction polarAnimate(proceed, init) {\n\tvar chart = this.chart,\n\t\tanimation = this.options.animation,\n\t\tgroup = this.group,\n\t\tmarkerGroup = this.markerGroup,\n\t\tcenter = this.xAxis.center,\n\t\tplotLeft = chart.plotLeft,\n\t\tplotTop = chart.plotTop,\n\t\tattribs;\n\n\t// Specific animation for polar charts\n\tif (chart.polar) {\n\t\t\n\t\t// Enable animation on polar charts only in SVG. In VML, the scaling is different, plus animation\n\t\t// would be so slow it would't matter.\n\t\tif (chart.renderer.isSVG) {\n\n\t\t\tif (animation === true) {\n\t\t\t\tanimation = {};\n\t\t\t}\n\t\n\t\t\t// Initialize the animation\n\t\t\tif (init) {\n\t\t\t\t\n\t\t\t\t// Scale down the group and place it in the center\n\t\t\t\tattribs = {\n\t\t\t\t\ttranslateX: center[0] + plotLeft,\n\t\t\t\t\ttranslateY: center[1] + plotTop,\n\t\t\t\t\tscaleX: 0.001, // #1499\n\t\t\t\t\tscaleY: 0.001\n\t\t\t\t};\n\t\t\t\t\t\n\t\t\t\tgroup.attr(attribs);\n\t\t\t\tif (markerGroup) {\n\t\t\t\t\tmarkerGroup.attrSetters = group.attrSetters;\n\t\t\t\t\tmarkerGroup.attr(attribs);\n\t\t\t\t}\n\t\t\t\t\n\t\t\t// Run the animation\n\t\t\t} else {\n\t\t\t\tattribs = {\n\t\t\t\t\ttranslateX: plotLeft,\n\t\t\t\t\ttranslateY: plotTop,\n\t\t\t\t\tscaleX: 1,\n\t\t\t\t\tscaleY: 1\n\t\t\t\t};\n\t\t\t\tgroup.animate(attribs, animation);\n\t\t\t\tif (markerGroup) {\n\t\t\t\t\tmarkerGroup.animate(attribs, animation);\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t// Delete this function to allow it only once\n\t\t\t\tthis.animate = null;\n\t\t\t}\n\t\t}\n\t\n\t// For non-polar charts, revert to the basic animation\n\t} else {\n\t\tproceed.call(this, init);\n\t} \n}\n\n// Define the animate method for both regular series and column series and their derivatives\nwrap(seriesProto, 'animate', polarAnimate);\nwrap(colProto, 'animate', polarAnimate);\n\n\n/**\n * Throw in a couple of properties to let setTooltipPoints know we're indexing the points\n * in degrees (0-360), not plot pixel width.\n */\nwrap(seriesProto, 'setTooltipPoints', function (proceed, renew) {\n\t\t\n\tif (this.chart.polar) {\n\t\textend(this.xAxis, {\n\t\t\ttooltipLen: 360 // degrees are the resolution unit of the tooltipPoints array\n\t\t});\t\n\t}\n\t\n\t// Run uber method\n\treturn proceed.call(this, renew);\n});\n\n\n/**\n * Extend the column prototype's translate method\n */\nwrap(colProto, 'translate', function (proceed) {\n\t\t\n\tvar xAxis = this.xAxis,\n\t\tlen = this.yAxis.len,\n\t\tcenter = xAxis.center,\n\t\tstartAngleRad = xAxis.startAngleRad,\n\t\trenderer = this.chart.renderer,\n\t\tstart,\n\t\tpoints,\n\t\tpoint,\n\t\ti;\n\t\n\tthis.preventPostTranslate = true;\n\t\n\t// Run uber method\n\tproceed.call(this);\n\t\n\t// Postprocess plot coordinates\n\tif (xAxis.isRadial) {\n\t\tpoints = this.points;\n\t\ti = points.length;\n\t\twhile (i--) {\n\t\t\tpoint = points[i];\n\t\t\tstart = point.barX + startAngleRad;\n\t\t\tpoint.shapeType = 'path';\n\t\t\tpoint.shapeArgs = {\n\t\t\t\td: renderer.symbols.arc(\n\t\t\t\t\tcenter[0],\n\t\t\t\t\tcenter[1],\n\t\t\t\t\tlen - point.plotY,\n\t\t\t\t\tnull, \n\t\t\t\t\t{\n\t\t\t\t\t\tstart: start,\n\t\t\t\t\t\tend: start + point.pointWidth,\n\t\t\t\t\t\tinnerR: len - pick(point.yBottom, len)\n\t\t\t\t\t}\n\t\t\t\t)\n\t\t\t};\n\t\t\tthis.toXY(point); // provide correct plotX, plotY for tooltip\n\t\t}\n\t}\n});\n\n\n/**\n * Align column data labels outside the columns. #1199.\n */\nwrap(colProto, 'alignDataLabel', function (proceed, point, dataLabel, options, alignTo, isNew) {\n\t\n\tif (this.chart.polar) {\n\t\tvar angle = point.rectPlotX / Math.PI * 180,\n\t\t\talign,\n\t\t\tverticalAlign;\n\t\t\n\t\t// Align nicely outside the perimeter of the columns\n\t\tif (options.align === null) {\n\t\t\tif (angle > 20 && angle < 160) {\n\t\t\t\talign = 'left'; // right hemisphere\n\t\t\t} else if (angle > 200 && angle < 340) {\n\t\t\t\talign = 'right'; // left hemisphere\n\t\t\t} else {\n\t\t\t\talign = 'center'; // top or bottom\n\t\t\t}\n\t\t\toptions.align = align;\n\t\t}\n\t\tif (options.verticalAlign === null) {\n\t\t\tif (angle < 45 || angle > 315) {\n\t\t\t\tverticalAlign = 'bottom'; // top part\n\t\t\t} else if (angle > 135 && angle < 225) {\n\t\t\t\tverticalAlign = 'top'; // bottom part\n\t\t\t} else {\n\t\t\t\tverticalAlign = 'middle'; // left or right\n\t\t\t}\n\t\t\toptions.verticalAlign = verticalAlign;\n\t\t}\n\t\t\n\t\tseriesProto.alignDataLabel.call(this, point, dataLabel, options, alignTo, isNew);\n\t} else {\n\t\tproceed.call(this, point, dataLabel, options, alignTo, isNew);\n\t}\n\t\n});\n\n/**\n * Extend the mouse tracker to return the tooltip position index in terms of\n * degrees rather than pixels\n */\nwrap(pointerProto, 'getIndex', function (proceed, e) {\n\tvar ret,\n\t\tchart = this.chart,\n\t\tcenter,\n\t\tx,\n\t\ty;\n\t\n\tif (chart.polar) {\n\t\tcenter = chart.xAxis[0].center;\n\t\tx = e.chartX - center[0] - chart.plotLeft;\n\t\ty = e.chartY - center[1] - chart.plotTop;\n\t\t\n\t\tret = 180 - Math.round(Math.atan2(x, y) / Math.PI * 180);\n\t\n\t} else {\n\t\n\t\t// Run uber method\n\t\tret = proceed.call(this, e);\n\t}\n\treturn ret;\n});\n\n/**\n * Extend getCoordinates to prepare for polar axis values\n */\nwrap(pointerProto, 'getCoordinates', function (proceed, e) {\n\tvar chart = this.chart,\n\t\tret = {\n\t\t\txAxis: [],\n\t\t\tyAxis: []\n\t\t};\n\t\n\tif (chart.polar) {\t\n\n\t\teach(chart.axes, function (axis) {\n\t\t\tvar isXAxis = axis.isXAxis,\n\t\t\t\tcenter = axis.center,\n\t\t\t\tx = e.chartX - center[0] - chart.plotLeft,\n\t\t\t\ty = e.chartY - center[1] - chart.plotTop;\n\t\t\t\n\t\t\tret[isXAxis ? 'xAxis' : 'yAxis'].push({\n\t\t\t\taxis: axis,\n\t\t\t\tvalue: axis.translate(\n\t\t\t\t\tisXAxis ?\n\t\t\t\t\t\tMath.PI - Math.atan2(x, y) : // angle \n\t\t\t\t\t\tMath.sqrt(Math.pow(x, 2) + Math.pow(y, 2)), // distance from center\n\t\t\t\t\ttrue\n\t\t\t\t)\n\t\t\t});\n\t\t});\n\t\t\n\t} else {\n\t\tret = proceed.call(this, e);\n\t}\n\t\n\treturn ret;\n});\n}(Highcharts));\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/highcharts/highcharts.js",
    "content": "/*\n Highcharts JS v3.0.6 (2013-10-04)\n\n (c) 2009-2013 Torstein Hønsi\n\n License: www.highcharts.com/license\n*/\n(function(){function r(a,b){var c;a||(a={});for(c in b)a[c]=b[c];return a}function x(){var a,b=arguments.length,c={},d=function(a,b){var c,h;typeof a!==\"object\"&&(a={});for(h in b)b.hasOwnProperty(h)&&(c=b[h],a[h]=c&&typeof c===\"object\"&&Object.prototype.toString.call(c)!==\"[object Array]\"&&typeof c.nodeType!==\"number\"?d(a[h]||{},c):b[h]);return a};for(a=0;a<b;a++)c=d(c,arguments[a]);return c}function C(a,b){return parseInt(a,b||10)}function ea(a){return typeof a===\"string\"}function T(a){return typeof a===\n\"object\"}function Ia(a){return Object.prototype.toString.call(a)===\"[object Array]\"}function sa(a){return typeof a===\"number\"}function na(a){return R.log(a)/R.LN10}function fa(a){return R.pow(10,a)}function ga(a,b){for(var c=a.length;c--;)if(a[c]===b){a.splice(c,1);break}}function u(a){return a!==w&&a!==null}function v(a,b,c){var d,e;if(ea(b))u(c)?a.setAttribute(b,c):a&&a.getAttribute&&(e=a.getAttribute(b));else if(u(b)&&T(b))for(d in b)a.setAttribute(d,b[d]);return e}function ja(a){return Ia(a)?\na:[a]}function o(){var a=arguments,b,c,d=a.length;for(b=0;b<d;b++)if(c=a[b],typeof c!==\"undefined\"&&c!==null)return c}function K(a,b){if(ta&&b&&b.opacity!==w)b.filter=\"alpha(opacity=\"+b.opacity*100+\")\";r(a.style,b)}function U(a,b,c,d,e){a=y.createElement(a);b&&r(a,b);e&&K(a,{padding:0,border:S,margin:0});c&&K(a,c);d&&d.appendChild(a);return a}function ha(a,b){var c=function(){};c.prototype=new a;r(c.prototype,b);return c}function Aa(a,b,c,d){var e=M.lang,a=+a||0,f=b===-1?(a.toString().split(\".\")[1]||\n\"\").length:isNaN(b=N(b))?2:b,b=c===void 0?e.decimalPoint:c,d=d===void 0?e.thousandsSep:d,e=a<0?\"-\":\"\",c=String(C(a=N(a).toFixed(f))),g=c.length>3?c.length%3:0;return e+(g?c.substr(0,g)+d:\"\")+c.substr(g).replace(/(\\d{3})(?=\\d)/g,\"$1\"+d)+(f?b+N(a-c).toFixed(f).slice(2):\"\")}function Ba(a,b){return Array((b||2)+1-String(a).length).join(0)+a}function mb(a,b,c){var d=a[b];a[b]=function(){var a=Array.prototype.slice.call(arguments);a.unshift(d);return c.apply(this,a)}}function Ca(a,b){for(var c=\"{\",d=!1,\ne,f,g,h,i,j=[];(c=a.indexOf(c))!==-1;){e=a.slice(0,c);if(d){f=e.split(\":\");g=f.shift().split(\".\");i=g.length;e=b;for(h=0;h<i;h++)e=e[g[h]];if(f.length)f=f.join(\":\"),g=/\\.([0-9])/,h=M.lang,i=void 0,/f$/.test(f)?(i=(i=f.match(g))?i[1]:-1,e=Aa(e,i,h.decimalPoint,f.indexOf(\",\")>-1?h.thousandsSep:\"\")):e=Xa(f,e)}j.push(e);a=a.slice(c+1);c=(d=!d)?\"}\":\"{\"}j.push(a);return j.join(\"\")}function nb(a){return R.pow(10,P(R.log(a)/R.LN10))}function ob(a,b,c,d){var e,c=o(c,1);e=a/c;b||(b=[1,2,2.5,5,10],d&&d.allowDecimals===\n!1&&(c===1?b=[1,2,5,10]:c<=0.1&&(b=[1/c])));for(d=0;d<b.length;d++)if(a=b[d],e<=(b[d]+(b[d+1]||b[d]))/2)break;a*=c;return a}function Cb(a,b){var c=b||[[Db,[1,2,5,10,20,25,50,100,200,500]],[pb,[1,2,5,10,15,30]],[Ya,[1,2,5,10,15,30]],[Qa,[1,2,3,4,6,8,12]],[ua,[1,2]],[Za,[1,2]],[Ra,[1,2,3,4,6]],[Da,null]],d=c[c.length-1],e=D[d[0]],f=d[1],g;for(g=0;g<c.length;g++)if(d=c[g],e=D[d[0]],f=d[1],c[g+1]&&a<=(e*f[f.length-1]+D[c[g+1][0]])/2)break;e===D[Da]&&a<5*e&&(f=[1,2,5]);c=ob(a/e,f,d[0]===Da?nb(a/e):1);\nreturn{unitRange:e,count:c,unitName:d[0]}}function Eb(a,b,c,d){var e=[],f={},g=M.global.useUTC,h,i=new Date(b),j=a.unitRange,k=a.count;if(u(b)){j>=D[pb]&&(i.setMilliseconds(0),i.setSeconds(j>=D[Ya]?0:k*P(i.getSeconds()/k)));if(j>=D[Ya])i[Fb](j>=D[Qa]?0:k*P(i[qb]()/k));if(j>=D[Qa])i[Gb](j>=D[ua]?0:k*P(i[rb]()/k));if(j>=D[ua])i[sb](j>=D[Ra]?1:k*P(i[Sa]()/k));j>=D[Ra]&&(i[Hb](j>=D[Da]?0:k*P(i[$a]()/k)),h=i[ab]());j>=D[Da]&&(h-=h%k,i[Ib](h));if(j===D[Za])i[sb](i[Sa]()-i[tb]()+o(d,1));b=1;h=i[ab]();for(var d=\ni.getTime(),l=i[$a](),m=i[Sa](),p=g?0:(864E5+i.getTimezoneOffset()*6E4)%864E5;d<c;)e.push(d),j===D[Da]?d=bb(h+b*k,0):j===D[Ra]?d=bb(h,l+b*k):!g&&(j===D[ua]||j===D[Za])?d=bb(h,l,m+b*k*(j===D[ua]?1:7)):d+=j*k,b++;e.push(d);n(ub(e,function(a){return j<=D[Qa]&&a%D[ua]===p}),function(a){f[a]=ua})}e.info=r(a,{higherRanks:f,totalRange:j*k});return e}function Jb(){this.symbol=this.color=0}function Kb(a,b){var c=a.length,d,e;for(e=0;e<c;e++)a[e].ss_i=e;a.sort(function(a,c){d=b(a,c);return d===0?a.ss_i-c.ss_i:\nd});for(e=0;e<c;e++)delete a[e].ss_i}function Ja(a){for(var b=a.length,c=a[0];b--;)a[b]<c&&(c=a[b]);return c}function va(a){for(var b=a.length,c=a[0];b--;)a[b]>c&&(c=a[b]);return c}function Ka(a,b){for(var c in a)a[c]&&a[c]!==b&&a[c].destroy&&a[c].destroy(),delete a[c]}function Ta(a){cb||(cb=U(Ea));a&&cb.appendChild(a);cb.innerHTML=\"\"}function ka(a,b){var c=\"Highcharts error #\"+a+\": www.highcharts.com/errors/\"+a;if(b)throw c;else O.console&&console.log(c)}function ia(a){return parseFloat(a.toPrecision(14))}\nfunction La(a,b){Fa=o(a,b.animation)}function Lb(){var a=M.global.useUTC,b=a?\"getUTC\":\"get\",c=a?\"setUTC\":\"set\";bb=a?Date.UTC:function(a,b,c,g,h,i){return(new Date(a,b,o(c,1),o(g,0),o(h,0),o(i,0))).getTime()};qb=b+\"Minutes\";rb=b+\"Hours\";tb=b+\"Day\";Sa=b+\"Date\";$a=b+\"Month\";ab=b+\"FullYear\";Fb=c+\"Minutes\";Gb=c+\"Hours\";sb=c+\"Date\";Hb=c+\"Month\";Ib=c+\"FullYear\"}function wa(){}function Ma(a,b,c,d){this.axis=a;this.pos=b;this.type=c||\"\";this.isNew=!0;!c&&!d&&this.addLabel()}function vb(a,b){this.axis=a;if(b)this.options=\nb,this.id=b.id}function Mb(a,b,c,d,e,f){var g=a.chart.inverted;this.axis=a;this.isNegative=c;this.options=b;this.x=d;this.total=null;this.points={};this.stack=e;this.percent=f===\"percent\";this.alignOptions={align:b.align||(g?c?\"left\":\"right\":\"center\"),verticalAlign:b.verticalAlign||(g?\"middle\":c?\"bottom\":\"top\"),y:o(b.y,g?4:c?14:-6),x:o(b.x,g?c?-6:6:0)};this.textAlign=b.textAlign||(g?c?\"right\":\"left\":\"center\")}function db(){this.init.apply(this,arguments)}function wb(){this.init.apply(this,arguments)}\nfunction xb(a,b){this.init(a,b)}function eb(a,b){this.init(a,b)}function yb(){this.init.apply(this,arguments)}var w,y=document,O=window,R=Math,t=R.round,P=R.floor,xa=R.ceil,s=R.max,I=R.min,N=R.abs,V=R.cos,ca=R.sin,ya=R.PI,Ua=ya*2/360,oa=navigator.userAgent,Nb=O.opera,ta=/msie/i.test(oa)&&!Nb,fb=y.documentMode===8,gb=/AppleWebKit/.test(oa),hb=/Firefox/.test(oa),Ob=/(Mobile|Android|Windows Phone)/.test(oa),za=\"http://www.w3.org/2000/svg\",Z=!!y.createElementNS&&!!y.createElementNS(za,\"svg\").createSVGRect,\nUb=hb&&parseInt(oa.split(\"Firefox/\")[1],10)<4,$=!Z&&!ta&&!!y.createElement(\"canvas\").getContext,Va,ib=y.documentElement.ontouchstart!==w,Pb={},zb=0,cb,M,Xa,Fa,Ab,D,pa=function(){},Ga=[],Ea=\"div\",S=\"none\",Qb=\"rgba(192,192,192,\"+(Z?1.0E-4:0.002)+\")\",Db=\"millisecond\",pb=\"second\",Ya=\"minute\",Qa=\"hour\",ua=\"day\",Za=\"week\",Ra=\"month\",Da=\"year\",Rb=\"stroke-width\",bb,qb,rb,tb,Sa,$a,ab,Fb,Gb,sb,Hb,Ib,W={};O.Highcharts=O.Highcharts?ka(16,!0):{};Xa=function(a,b,c){if(!u(b)||isNaN(b))return\"Invalid date\";var a=\no(a,\"%Y-%m-%d %H:%M:%S\"),d=new Date(b),e,f=d[rb](),g=d[tb](),h=d[Sa](),i=d[$a](),j=d[ab](),k=M.lang,l=k.weekdays,d=r({a:l[g].substr(0,3),A:l[g],d:Ba(h),e:h,b:k.shortMonths[i],B:k.months[i],m:Ba(i+1),y:j.toString().substr(2,2),Y:j,H:Ba(f),I:Ba(f%12||12),l:f%12||12,M:Ba(d[qb]()),p:f<12?\"AM\":\"PM\",P:f<12?\"am\":\"pm\",S:Ba(d.getSeconds()),L:Ba(t(b%1E3),3)},Highcharts.dateFormats);for(e in d)for(;a.indexOf(\"%\"+e)!==-1;)a=a.replace(\"%\"+e,typeof d[e]===\"function\"?d[e](b):d[e]);return c?a.substr(0,1).toUpperCase()+\na.substr(1):a};Jb.prototype={wrapColor:function(a){if(this.color>=a)this.color=0},wrapSymbol:function(a){if(this.symbol>=a)this.symbol=0}};D=function(){for(var a=0,b=arguments,c=b.length,d={};a<c;a++)d[b[a++]]=b[a];return d}(Db,1,pb,1E3,Ya,6E4,Qa,36E5,ua,864E5,Za,6048E5,Ra,26784E5,Da,31556952E3);Ab={init:function(a,b,c){var b=b||\"\",d=a.shift,e=b.indexOf(\"C\")>-1,f=e?7:3,g,b=b.split(\" \"),c=[].concat(c),h,i,j=function(a){for(g=a.length;g--;)a[g]===\"M\"&&a.splice(g+1,0,a[g+1],a[g+2],a[g+1],a[g+2])};e&&\n(j(b),j(c));a.isArea&&(h=b.splice(b.length-6,6),i=c.splice(c.length-6,6));if(d<=c.length/f&&b.length===c.length)for(;d--;)c=[].concat(c).splice(0,f).concat(c);a.shift=0;if(b.length)for(a=c.length;b.length<a;)d=[].concat(b).splice(b.length-f,f),e&&(d[f-6]=d[f-2],d[f-5]=d[f-1]),b=b.concat(d);h&&(b=b.concat(h),c=c.concat(i));return[b,c]},step:function(a,b,c,d){var e=[],f=a.length;if(c===1)e=d;else if(f===b.length&&c<1)for(;f--;)d=parseFloat(a[f]),e[f]=isNaN(d)?a[f]:c*parseFloat(b[f]-d)+d;else e=b;return e}};\n(function(a){O.HighchartsAdapter=O.HighchartsAdapter||a&&{init:function(b){var c=a.fx,d=c.step,e,f=a.Tween,g=f&&f.propHooks;e=a.cssHooks.opacity;a.extend(a.easing,{easeOutQuad:function(a,b,c,d,e){return-d*(b/=e)*(b-2)+c}});a.each([\"cur\",\"_default\",\"width\",\"height\",\"opacity\"],function(a,b){var e=d,k,l;b===\"cur\"?e=c.prototype:b===\"_default\"&&f&&(e=g[b],b=\"set\");(k=e[b])&&(e[b]=function(c){c=a?c:this;if(c.prop!==\"align\")return l=c.elem,l.attr?l.attr(c.prop,b===\"cur\"?w:c.now):k.apply(this,arguments)})});\nmb(e,\"get\",function(a,b,c){return b.attr?b.opacity||0:a.call(this,b,c)});e=function(a){var c=a.elem,d;if(!a.started)d=b.init(c,c.d,c.toD),a.start=d[0],a.end=d[1],a.started=!0;c.attr(\"d\",b.step(a.start,a.end,a.pos,c.toD))};f?g.d={set:e}:d.d=e;this.each=Array.prototype.forEach?function(a,b){return Array.prototype.forEach.call(a,b)}:function(a,b){for(var c=0,d=a.length;c<d;c++)if(b.call(a[c],a[c],c,a)===!1)return c};a.fn.highcharts=function(){var a=\"Chart\",b=arguments,c,d;ea(b[0])&&(a=b[0],b=Array.prototype.slice.call(b,\n1));c=b[0];if(c!==w)c.chart=c.chart||{},c.chart.renderTo=this[0],new Highcharts[a](c,b[1]),d=this;c===w&&(d=Ga[v(this[0],\"data-highcharts-chart\")]);return d}},getScript:a.getScript,inArray:a.inArray,adapterRun:function(b,c){return a(b)[c]()},grep:a.grep,map:function(a,c){for(var d=[],e=0,f=a.length;e<f;e++)d[e]=c.call(a[e],a[e],e,a);return d},offset:function(b){return a(b).offset()},addEvent:function(b,c,d){a(b).bind(c,d)},removeEvent:function(b,c,d){var e=y.removeEventListener?\"removeEventListener\":\n\"detachEvent\";y[e]&&b&&!b[e]&&(b[e]=function(){});a(b).unbind(c,d)},fireEvent:function(b,c,d,e){var f=a.Event(c),g=\"detached\"+c,h;!ta&&d&&(delete d.layerX,delete d.layerY);r(f,d);b[c]&&(b[g]=b[c],b[c]=null);a.each([\"preventDefault\",\"stopPropagation\"],function(a,b){var c=f[b];f[b]=function(){try{c.call(f)}catch(a){b===\"preventDefault\"&&(h=!0)}}});a(b).trigger(f);b[g]&&(b[c]=b[g],b[g]=null);e&&!f.isDefaultPrevented()&&!h&&e(f)},washMouseEvent:function(a){var c=a.originalEvent||a;if(c.pageX===w)c.pageX=\na.pageX,c.pageY=a.pageY;return c},animate:function(b,c,d){var e=a(b);if(!b.style)b.style={};if(c.d)b.toD=c.d,c.d=1;e.stop();c.opacity!==w&&b.attr&&(c.opacity+=\"px\");e.animate(c,d)},stop:function(b){a(b).stop()}}})(O.jQuery);var X=O.HighchartsAdapter,G=X||{};X&&X.init.call(X,Ab);var jb=G.adapterRun,Vb=G.getScript,qa=G.inArray,n=G.each,ub=G.grep,Wb=G.offset,Na=G.map,J=G.addEvent,aa=G.removeEvent,z=G.fireEvent,Xb=G.washMouseEvent,Bb=G.animate,Wa=G.stop,G={enabled:!0,x:0,y:15,style:{color:\"#666\",cursor:\"default\",\nfontSize:\"11px\",lineHeight:\"14px\"}};M={colors:\"#2f7ed8,#0d233a,#8bbc21,#910000,#1aadce,#492970,#f28f43,#77a1e5,#c42525,#a6c96a\".split(\",\"),symbols:[\"circle\",\"diamond\",\"square\",\"triangle\",\"triangle-down\"],lang:{loading:\"Loading...\",months:\"January,February,March,April,May,June,July,August,September,October,November,December\".split(\",\"),shortMonths:\"Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec\".split(\",\"),weekdays:\"Sunday,Monday,Tuesday,Wednesday,Thursday,Friday,Saturday\".split(\",\"),decimalPoint:\".\",\nnumericSymbols:\"k,M,G,T,P,E\".split(\",\"),resetZoom:\"Reset zoom\",resetZoomTitle:\"Reset zoom level 1:1\",thousandsSep:\",\"},global:{useUTC:!0,canvasToolsURL:\"http://code.highcharts.com/3.0.6/modules/canvas-tools.js\",VMLRadialGradientURL:\"http://code.highcharts.com/3.0.6/gfx/vml-radial-gradient.png\"},chart:{borderColor:\"#4572A7\",borderRadius:5,defaultSeriesType:\"line\",ignoreHiddenSeries:!0,spacing:[10,10,15,10],style:{fontFamily:'\"Lucida Grande\", \"Lucida Sans Unicode\", Verdana, Arial, Helvetica, sans-serif',\nfontSize:\"12px\"},backgroundColor:\"#FFFFFF\",plotBorderColor:\"#C0C0C0\",resetZoomButton:{theme:{zIndex:20},position:{align:\"right\",x:-10,y:10}}},title:{text:\"Chart title\",align:\"center\",margin:15,style:{color:\"#274b6d\",fontSize:\"16px\"}},subtitle:{text:\"\",align:\"center\",style:{color:\"#4d759e\"}},plotOptions:{line:{allowPointSelect:!1,showCheckbox:!1,animation:{duration:1E3},events:{},lineWidth:2,marker:{enabled:!0,lineWidth:0,radius:4,lineColor:\"#FFFFFF\",states:{hover:{enabled:!0},select:{fillColor:\"#FFFFFF\",\nlineColor:\"#000000\",lineWidth:2}}},point:{events:{}},dataLabels:x(G,{align:\"center\",enabled:!1,formatter:function(){return this.y===null?\"\":Aa(this.y,-1)},verticalAlign:\"bottom\",y:0}),cropThreshold:300,pointRange:0,showInLegend:!0,states:{hover:{marker:{}},select:{marker:{}}},stickyTracking:!0}},labels:{style:{position:\"absolute\",color:\"#3E576F\"}},legend:{enabled:!0,align:\"center\",layout:\"horizontal\",labelFormatter:function(){return this.name},borderWidth:1,borderColor:\"#909090\",borderRadius:5,navigation:{activeColor:\"#274b6d\",\ninactiveColor:\"#CCC\"},shadow:!1,itemStyle:{cursor:\"pointer\",color:\"#274b6d\",fontSize:\"12px\"},itemHoverStyle:{color:\"#000\"},itemHiddenStyle:{color:\"#CCC\"},itemCheckboxStyle:{position:\"absolute\",width:\"13px\",height:\"13px\"},symbolWidth:16,symbolPadding:5,verticalAlign:\"bottom\",x:0,y:0,title:{style:{fontWeight:\"bold\"}}},loading:{labelStyle:{fontWeight:\"bold\",position:\"relative\",top:\"1em\"},style:{position:\"absolute\",backgroundColor:\"white\",opacity:0.5,textAlign:\"center\"}},tooltip:{enabled:!0,animation:Z,\nbackgroundColor:\"rgba(255, 255, 255, .85)\",borderWidth:1,borderRadius:3,dateTimeLabelFormats:{millisecond:\"%A, %b %e, %H:%M:%S.%L\",second:\"%A, %b %e, %H:%M:%S\",minute:\"%A, %b %e, %H:%M\",hour:\"%A, %b %e, %H:%M\",day:\"%A, %b %e, %Y\",week:\"Week from %A, %b %e, %Y\",month:\"%B %Y\",year:\"%Y\"},headerFormat:'<span style=\"font-size: 10px\">{point.key}</span><br/>',pointFormat:'<span style=\"color:{series.color}\">{series.name}</span>: <b>{point.y}</b><br/>',shadow:!0,snap:Ob?25:10,style:{color:\"#333333\",cursor:\"default\",\nfontSize:\"12px\",padding:\"8px\",whiteSpace:\"nowrap\"}},credits:{enabled:!0,text:\"Highcharts.com\",href:\"http://www.highcharts.com\",position:{align:\"right\",x:-10,verticalAlign:\"bottom\",y:-5},style:{cursor:\"pointer\",color:\"#909090\",fontSize:\"9px\"}}};var Y=M.plotOptions,X=Y.line;Lb();var ra=function(a){var b=[],c,d;(function(a){a&&a.stops?d=Na(a.stops,function(a){return ra(a[1])}):(c=/rgba\\(\\s*([0-9]{1,3})\\s*,\\s*([0-9]{1,3})\\s*,\\s*([0-9]{1,3})\\s*,\\s*([0-9]?(?:\\.[0-9]+)?)\\s*\\)/.exec(a))?b=[C(c[1]),C(c[2]),\nC(c[3]),parseFloat(c[4],10)]:(c=/#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(a))?b=[C(c[1],16),C(c[2],16),C(c[3],16),1]:(c=/rgb\\(\\s*([0-9]{1,3})\\s*,\\s*([0-9]{1,3})\\s*,\\s*([0-9]{1,3})\\s*\\)/.exec(a))&&(b=[C(c[1]),C(c[2]),C(c[3]),1])})(a);return{get:function(c){var f;d?(f=x(a),f.stops=[].concat(f.stops),n(d,function(a,b){f.stops[b]=[f.stops[b][0],a.get(c)]})):f=b&&!isNaN(b[0])?c===\"rgb\"?\"rgb(\"+b[0]+\",\"+b[1]+\",\"+b[2]+\")\":c===\"a\"?b[3]:\"rgba(\"+b.join(\",\")+\")\":a;return f},brighten:function(a){if(d)n(d,\nfunction(b){b.brighten(a)});else if(sa(a)&&a!==0){var c;for(c=0;c<3;c++)b[c]+=C(a*255),b[c]<0&&(b[c]=0),b[c]>255&&(b[c]=255)}return this},rgba:b,setOpacity:function(a){b[3]=a;return this}}};wa.prototype={init:function(a,b){this.element=b===\"span\"?U(b):y.createElementNS(za,b);this.renderer=a;this.attrSetters={}},opacity:1,animate:function(a,b,c){b=o(b,Fa,!0);Wa(this);if(b){b=x(b);if(c)b.complete=c;Bb(this,a,b)}else this.attr(a),c&&c()},attr:function(a,b){var c,d,e,f,g=this.element,h=g.nodeName.toLowerCase(),\ni=this.renderer,j,k=this.attrSetters,l=this.shadows,m,p,q=this;ea(a)&&u(b)&&(c=a,a={},a[c]=b);if(ea(a))c=a,h===\"circle\"?c={x:\"cx\",y:\"cy\"}[c]||c:c===\"strokeWidth\"&&(c=\"stroke-width\"),q=v(g,c)||this[c]||0,c!==\"d\"&&c!==\"visibility\"&&c!==\"fill\"&&(q=parseFloat(q));else{for(c in a)if(j=!1,d=a[c],e=k[c]&&k[c].call(this,d,c),e!==!1){e!==w&&(d=e);if(c===\"d\")d&&d.join&&(d=d.join(\" \")),/(NaN| {2}|^$)/.test(d)&&(d=\"M 0 0\");else if(c===\"x\"&&h===\"text\")for(e=0;e<g.childNodes.length;e++)f=g.childNodes[e],v(f,\"x\")===\nv(g,\"x\")&&v(f,\"x\",d);else if(this.rotation&&(c===\"x\"||c===\"y\"))p=!0;else if(c===\"fill\")d=i.color(d,g,c);else if(h===\"circle\"&&(c===\"x\"||c===\"y\"))c={x:\"cx\",y:\"cy\"}[c]||c;else if(h===\"rect\"&&c===\"r\")v(g,{rx:d,ry:d}),j=!0;else if(c===\"translateX\"||c===\"translateY\"||c===\"rotation\"||c===\"verticalAlign\"||c===\"scaleX\"||c===\"scaleY\")j=p=!0;else if(c===\"stroke\")d=i.color(d,g,c);else if(c===\"dashstyle\")if(c=\"stroke-dasharray\",d=d&&d.toLowerCase(),d===\"solid\")d=S;else{if(d){d=d.replace(\"shortdashdotdot\",\"3,1,1,1,1,1,\").replace(\"shortdashdot\",\n\"3,1,1,1\").replace(\"shortdot\",\"1,1,\").replace(\"shortdash\",\"3,1,\").replace(\"longdash\",\"8,3,\").replace(/dot/g,\"1,3,\").replace(\"dash\",\"4,3,\").replace(/,$/,\"\").split(\",\");for(e=d.length;e--;)d[e]=C(d[e])*o(a[\"stroke-width\"],this[\"stroke-width\"]);d=d.join(\",\")}}else if(c===\"width\")d=C(d);else if(c===\"align\")c=\"text-anchor\",d={left:\"start\",center:\"middle\",right:\"end\"}[d];else if(c===\"title\")e=g.getElementsByTagName(\"title\")[0],e||(e=y.createElementNS(za,\"title\"),g.appendChild(e)),e.textContent=d;c===\"strokeWidth\"&&\n(c=\"stroke-width\");if(c===\"stroke-width\"||c===\"stroke\"){this[c]=d;if(this.stroke&&this[\"stroke-width\"])v(g,\"stroke\",this.stroke),v(g,\"stroke-width\",this[\"stroke-width\"]),this.hasStroke=!0;else if(c===\"stroke-width\"&&d===0&&this.hasStroke)g.removeAttribute(\"stroke\"),this.hasStroke=!1;j=!0}this.symbolName&&/^(x|y|width|height|r|start|end|innerR|anchorX|anchorY)/.test(c)&&(m||(this.symbolAttr(a),m=!0),j=!0);if(l&&/^(width|height|visibility|x|y|d|transform|cx|cy|r)$/.test(c))for(e=l.length;e--;)v(l[e],\nc,c===\"height\"?s(d-(l[e].cutHeight||0),0):d);if((c===\"width\"||c===\"height\")&&h===\"rect\"&&d<0)d=0;this[c]=d;c===\"text\"?(d!==this.textStr&&delete this.bBox,this.textStr=d,this.added&&i.buildText(this)):j||v(g,c,d)}p&&this.updateTransform()}return q},addClass:function(a){var b=this.element,c=v(b,\"class\")||\"\";c.indexOf(a)===-1&&v(b,\"class\",c+\" \"+a);return this},symbolAttr:function(a){var b=this;n(\"x,y,r,start,end,width,height,innerR,anchorX,anchorY\".split(\",\"),function(c){b[c]=o(a[c],b[c])});b.attr({d:b.renderer.symbols[b.symbolName](b.x,\nb.y,b.width,b.height,b)})},clip:function(a){return this.attr(\"clip-path\",a?\"url(\"+this.renderer.url+\"#\"+a.id+\")\":S)},crisp:function(a,b,c,d,e){var f,g={},h={},i,a=a||this.strokeWidth||this.attr&&this.attr(\"stroke-width\")||0;i=t(a)%2/2;h.x=P(b||this.x||0)+i;h.y=P(c||this.y||0)+i;h.width=P((d||this.width||0)-2*i);h.height=P((e||this.height||0)-2*i);h.strokeWidth=a;for(f in h)this[f]!==h[f]&&(this[f]=g[f]=h[f]);return g},css:function(a){var b=this.element,c=a&&a.width&&b.nodeName.toLowerCase()===\"text\",\nd,e=\"\",f=function(a,b){return\"-\"+b.toLowerCase()};if(a&&a.color)a.fill=a.color;this.styles=a=r(this.styles,a);$&&c&&delete a.width;if(ta&&!Z)c&&delete a.width,K(this.element,a);else{for(d in a)e+=d.replace(/([A-Z])/g,f)+\":\"+a[d]+\";\";v(b,\"style\",e)}c&&this.added&&this.renderer.buildText(this);return this},on:function(a,b){var c=this,d=c.element;ib&&a===\"click\"?(d.ontouchstart=function(a){c.touchEventFired=Date.now();a.preventDefault();b.call(d,a)},d.onclick=function(a){(oa.indexOf(\"Android\")===-1||\nDate.now()-(c.touchEventFired||0)>1100)&&b.call(d,a)}):d[\"on\"+a]=b;return this},setRadialReference:function(a){this.element.radialReference=a;return this},translate:function(a,b){return this.attr({translateX:a,translateY:b})},invert:function(){this.inverted=!0;this.updateTransform();return this},htmlCss:function(a){var b=this.element;if(b=a&&b.tagName===\"SPAN\"&&a.width)delete a.width,this.textWidth=b,this.updateTransform();this.styles=r(this.styles,a);K(this.element,a);return this},htmlGetBBox:function(){var a=\nthis.element,b=this.bBox;if(!b){if(a.nodeName===\"text\")a.style.position=\"absolute\";b=this.bBox={x:a.offsetLeft,y:a.offsetTop,width:a.offsetWidth,height:a.offsetHeight}}return b},htmlUpdateTransform:function(){if(this.added){var a=this.renderer,b=this.element,c=this.translateX||0,d=this.translateY||0,e=this.x||0,f=this.y||0,g=this.textAlign||\"left\",h={left:0,center:0.5,right:1}[g],i=g&&g!==\"left\",j=this.shadows;K(b,{marginLeft:c,marginTop:d});j&&n(j,function(a){K(a,{marginLeft:c+1,marginTop:d+1})});\nthis.inverted&&n(b.childNodes,function(c){a.invertChild(c,b)});if(b.tagName===\"SPAN\"){var k,l,j=this.rotation,m;k=0;var p=1,q=0,ba;m=C(this.textWidth);var A=this.xCorr||0,L=this.yCorr||0,Sb=[j,g,b.innerHTML,this.textWidth].join(\",\");if(Sb!==this.cTT){u(j)&&(k=j*Ua,p=V(k),q=ca(k),this.setSpanRotation(j,q,p));k=o(this.elemWidth,b.offsetWidth);l=o(this.elemHeight,b.offsetHeight);if(k>m&&/[ \\-]/.test(b.textContent||b.innerText))K(b,{width:m+\"px\",display:\"block\",whiteSpace:\"normal\"}),k=m;m=a.fontMetrics(b.style.fontSize).b;\nA=p<0&&-k;L=q<0&&-l;ba=p*q<0;A+=q*m*(ba?1-h:h);L-=p*m*(j?ba?h:1-h:1);i&&(A-=k*h*(p<0?-1:1),j&&(L-=l*h*(q<0?-1:1)),K(b,{textAlign:g}));this.xCorr=A;this.yCorr=L}K(b,{left:e+A+\"px\",top:f+L+\"px\"});if(gb)l=b.offsetHeight;this.cTT=Sb}}else this.alignOnAdd=!0},setSpanRotation:function(a){var b={};b[ta?\"-ms-transform\":gb?\"-webkit-transform\":hb?\"MozTransform\":Nb?\"-o-transform\":\"\"]=b.transform=\"rotate(\"+a+\"deg)\";K(this.element,b)},updateTransform:function(){var a=this.translateX||0,b=this.translateY||0,c=\nthis.scaleX,d=this.scaleY,e=this.inverted,f=this.rotation;e&&(a+=this.attr(\"width\"),b+=this.attr(\"height\"));a=[\"translate(\"+a+\",\"+b+\")\"];e?a.push(\"rotate(90) scale(-1,1)\"):f&&a.push(\"rotate(\"+f+\" \"+(this.x||0)+\" \"+(this.y||0)+\")\");(u(c)||u(d))&&a.push(\"scale(\"+o(c,1)+\" \"+o(d,1)+\")\");a.length&&v(this.element,\"transform\",a.join(\" \"))},toFront:function(){var a=this.element;a.parentNode.appendChild(a);return this},align:function(a,b,c){var d,e,f,g,h={};e=this.renderer;f=e.alignedObjects;if(a){if(this.alignOptions=\na,this.alignByTranslate=b,!c||ea(c))this.alignTo=d=c||\"renderer\",ga(f,this),f.push(this),c=null}else a=this.alignOptions,b=this.alignByTranslate,d=this.alignTo;c=o(c,e[d],e);d=a.align;e=a.verticalAlign;f=(c.x||0)+(a.x||0);g=(c.y||0)+(a.y||0);if(d===\"right\"||d===\"center\")f+=(c.width-(a.width||0))/{right:1,center:2}[d];h[b?\"translateX\":\"x\"]=t(f);if(e===\"bottom\"||e===\"middle\")g+=(c.height-(a.height||0))/({bottom:1,middle:2}[e]||1);h[b?\"translateY\":\"y\"]=t(g);this[this.placed?\"animate\":\"attr\"](h);this.placed=\n!0;this.alignAttr=h;return this},getBBox:function(){var a=this.bBox,b=this.renderer,c,d=this.rotation;c=this.element;var e=this.styles,f=d*Ua;if(!a){if(c.namespaceURI===za||b.forExport){try{a=c.getBBox?r({},c.getBBox()):{width:c.offsetWidth,height:c.offsetHeight}}catch(g){}if(!a||a.width<0)a={width:0,height:0}}else a=this.htmlGetBBox();if(b.isSVG){b=a.width;c=a.height;if(ta&&e&&e.fontSize===\"11px\"&&c.toPrecision(3)===\"22.7\")a.height=c=14;if(d)a.width=N(c*ca(f))+N(b*V(f)),a.height=N(c*V(f))+N(b*ca(f))}this.bBox=\na}return a},show:function(){return this.attr({visibility:\"visible\"})},hide:function(){return this.attr({visibility:\"hidden\"})},fadeOut:function(a){var b=this;b.animate({opacity:0},{duration:a||150,complete:function(){b.hide()}})},add:function(a){var b=this.renderer,c=a||b,d=c.element||b.box,e=d.childNodes,f=this.element,g=v(f,\"zIndex\"),h;if(a)this.parentGroup=a;this.parentInverted=a&&a.inverted;this.textStr!==void 0&&b.buildText(this);if(g)c.handleZ=!0,g=C(g);if(c.handleZ)for(c=0;c<e.length;c++)if(a=\ne[c],b=v(a,\"zIndex\"),a!==f&&(C(b)>g||!u(g)&&u(b))){d.insertBefore(f,a);h=!0;break}h||d.appendChild(f);this.added=!0;z(this,\"add\");return this},safeRemoveChild:function(a){var b=a.parentNode;b&&b.removeChild(a)},destroy:function(){var a=this,b=a.element||{},c=a.shadows,d=a.renderer.isSVG&&b.nodeName===\"SPAN\"&&b.parentNode,e,f;b.onclick=b.onmouseout=b.onmouseover=b.onmousemove=b.point=null;Wa(a);if(a.clipPath)a.clipPath=a.clipPath.destroy();if(a.stops){for(f=0;f<a.stops.length;f++)a.stops[f]=a.stops[f].destroy();\na.stops=null}a.safeRemoveChild(b);for(c&&n(c,function(b){a.safeRemoveChild(b)});d&&d.childNodes.length===0;)b=d.parentNode,a.safeRemoveChild(d),d=b;a.alignTo&&ga(a.renderer.alignedObjects,a);for(e in a)delete a[e];return null},shadow:function(a,b,c){var d=[],e,f,g=this.element,h,i,j,k;if(a){i=o(a.width,3);j=(a.opacity||0.15)/i;k=this.parentInverted?\"(-1,-1)\":\"(\"+o(a.offsetX,1)+\", \"+o(a.offsetY,1)+\")\";for(e=1;e<=i;e++){f=g.cloneNode(0);h=i*2+1-2*e;v(f,{isShadow:\"true\",stroke:a.color||\"black\",\"stroke-opacity\":j*\ne,\"stroke-width\":h,transform:\"translate\"+k,fill:S});if(c)v(f,\"height\",s(v(f,\"height\")-h,0)),f.cutHeight=h;b?b.element.appendChild(f):g.parentNode.insertBefore(f,g);d.push(f)}this.shadows=d}return this}};var Ha=function(){this.init.apply(this,arguments)};Ha.prototype={Element:wa,init:function(a,b,c,d){var e=location,f,g;f=this.createElement(\"svg\").attr({version:\"1.1\"});g=f.element;a.appendChild(g);a.innerHTML.indexOf(\"xmlns\")===-1&&v(g,\"xmlns\",za);this.isSVG=!0;this.box=g;this.boxWrapper=f;this.alignedObjects=\n[];this.url=(hb||gb)&&y.getElementsByTagName(\"base\").length?e.href.replace(/#.*?$/,\"\").replace(/([\\('\\)])/g,\"\\\\$1\").replace(/ /g,\"%20\"):\"\";this.createElement(\"desc\").add().element.appendChild(y.createTextNode(\"Created with Highcharts 3.0.6\"));this.defs=this.createElement(\"defs\").add();this.forExport=d;this.gradients={};this.setSize(b,c,!1);var h;if(hb&&a.getBoundingClientRect)this.subPixelFix=b=function(){K(a,{left:0,top:0});h=a.getBoundingClientRect();K(a,{left:xa(h.left)-h.left+\"px\",top:xa(h.top)-\nh.top+\"px\"})},b(),J(O,\"resize\",b)},isHidden:function(){return!this.boxWrapper.getBBox().width},destroy:function(){var a=this.defs;this.box=null;this.boxWrapper=this.boxWrapper.destroy();Ka(this.gradients||{});this.gradients=null;if(a)this.defs=a.destroy();this.subPixelFix&&aa(O,\"resize\",this.subPixelFix);return this.alignedObjects=null},createElement:function(a){var b=new this.Element;b.init(this,a);return b},draw:function(){},buildText:function(a){for(var b=a.element,c=this,d=c.forExport,e=o(a.textStr,\n\"\").toString().replace(/<(b|strong)>/g,'<span style=\"font-weight:bold\">').replace(/<(i|em)>/g,'<span style=\"font-style:italic\">').replace(/<a/g,\"<span\").replace(/<\\/(b|strong|i|em|a)>/g,\"</span>\").split(/<br.*?>/g),f=b.childNodes,g=/style=\"([^\"]+)\"/,h=/href=\"(http[^\"]+)\"/,i=v(b,\"x\"),j=a.styles,k=j&&j.width&&C(j.width),l=j&&j.lineHeight,m=f.length;m--;)b.removeChild(f[m]);k&&!a.added&&this.box.appendChild(b);e[e.length-1]===\"\"&&e.pop();n(e,function(e,f){var m,o=0,e=e.replace(/<span/g,\"|||<span\").replace(/<\\/span>/g,\n\"</span>|||\");m=e.split(\"|||\");n(m,function(e){if(e!==\"\"||m.length===1){var p={},n=y.createElementNS(za,\"tspan\"),s;g.test(e)&&(s=e.match(g)[1].replace(/(;| |^)color([ :])/,\"$1fill$2\"),v(n,\"style\",s));h.test(e)&&!d&&(v(n,\"onclick\",'location.href=\"'+e.match(h)[1]+'\"'),K(n,{cursor:\"pointer\"}));e=(e.replace(/<(.|\\n)*?>/g,\"\")||\" \").replace(/&lt;/g,\"<\").replace(/&gt;/g,\">\");if(e!==\" \"&&(n.appendChild(y.createTextNode(e)),o?p.dx=0:p.x=i,v(n,p),!o&&f&&(!Z&&d&&K(n,{display:\"block\"}),v(n,\"dy\",l||c.fontMetrics(/px$/.test(n.style.fontSize)?\nn.style.fontSize:j.fontSize).h,gb&&n.offsetHeight)),b.appendChild(n),o++,k))for(var e=e.replace(/([^\\^])-/g,\"$1- \").split(\" \"),u,t,p=a._clipHeight,E=[],w=C(l||16),B=1;e.length||E.length;)delete a.bBox,u=a.getBBox(),t=u.width,u=t>k,!u||e.length===1?(e=E,E=[],e.length&&(B++,p&&B*w>p?(e=[\"...\"],a.attr(\"title\",a.textStr)):(n=y.createElementNS(za,\"tspan\"),v(n,{dy:w,x:i}),s&&v(n,\"style\",s),b.appendChild(n),t>k&&(k=t)))):(n.removeChild(n.firstChild),E.unshift(e.pop())),e.length&&n.appendChild(y.createTextNode(e.join(\" \").replace(/- /g,\n\"-\")))}})})},button:function(a,b,c,d,e,f,g,h){var i=this.label(a,b,c,null,null,null,null,null,\"button\"),j=0,k,l,m,p,q,n,a={x1:0,y1:0,x2:0,y2:1},e=x({\"stroke-width\":1,stroke:\"#CCCCCC\",fill:{linearGradient:a,stops:[[0,\"#FEFEFE\"],[1,\"#F6F6F6\"]]},r:2,padding:5,style:{color:\"black\"}},e);m=e.style;delete e.style;f=x(e,{stroke:\"#68A\",fill:{linearGradient:a,stops:[[0,\"#FFF\"],[1,\"#ACF\"]]}},f);p=f.style;delete f.style;g=x(e,{stroke:\"#68A\",fill:{linearGradient:a,stops:[[0,\"#9BD\"],[1,\"#CDF\"]]}},g);q=g.style;\ndelete g.style;h=x(e,{style:{color:\"#CCC\"}},h);n=h.style;delete h.style;J(i.element,ta?\"mouseover\":\"mouseenter\",function(){j!==3&&i.attr(f).css(p)});J(i.element,ta?\"mouseout\":\"mouseleave\",function(){j!==3&&(k=[e,f,g][j],l=[m,p,q][j],i.attr(k).css(l))});i.setState=function(a){(i.state=j=a)?a===2?i.attr(g).css(q):a===3&&i.attr(h).css(n):i.attr(e).css(m)};return i.on(\"click\",function(){j!==3&&d.call(i)}).attr(e).css(r({cursor:\"default\"},m))},crispLine:function(a,b){a[1]===a[4]&&(a[1]=a[4]=t(a[1])-b%\n2/2);a[2]===a[5]&&(a[2]=a[5]=t(a[2])+b%2/2);return a},path:function(a){var b={fill:S};Ia(a)?b.d=a:T(a)&&r(b,a);return this.createElement(\"path\").attr(b)},circle:function(a,b,c){a=T(a)?a:{x:a,y:b,r:c};return this.createElement(\"circle\").attr(a)},arc:function(a,b,c,d,e,f){if(T(a))b=a.y,c=a.r,d=a.innerR,e=a.start,f=a.end,a=a.x;a=this.symbol(\"arc\",a||0,b||0,c||0,c||0,{innerR:d||0,start:e||0,end:f||0});a.r=c;return a},rect:function(a,b,c,d,e,f){e=T(a)?a.r:e;e=this.createElement(\"rect\").attr({rx:e,ry:e,\nfill:S});return e.attr(T(a)?a:e.crisp(f,a,b,s(c,0),s(d,0)))},setSize:function(a,b,c){var d=this.alignedObjects,e=d.length;this.width=a;this.height=b;for(this.boxWrapper[o(c,!0)?\"animate\":\"attr\"]({width:a,height:b});e--;)d[e].align()},g:function(a){var b=this.createElement(\"g\");return u(a)?b.attr({\"class\":\"highcharts-\"+a}):b},image:function(a,b,c,d,e){var f={preserveAspectRatio:S};arguments.length>1&&r(f,{x:b,y:c,width:d,height:e});f=this.createElement(\"image\").attr(f);f.element.setAttributeNS?f.element.setAttributeNS(\"http://www.w3.org/1999/xlink\",\n\"href\",a):f.element.setAttribute(\"hc-svg-href\",a);return f},symbol:function(a,b,c,d,e,f){var g,h=this.symbols[a],h=h&&h(t(b),t(c),d,e,f),i=/^url\\((.*?)\\)$/,j,k;if(h)g=this.path(h),r(g,{symbolName:a,x:b,y:c,width:d,height:e}),f&&r(g,f);else if(i.test(a))k=function(a,b){a.element&&(a.attr({width:b[0],height:b[1]}),a.alignByTranslate||a.translate(t((d-b[0])/2),t((e-b[1])/2)))},j=a.match(i)[1],a=Pb[j],g=this.image(j).attr({x:b,y:c}),g.isImg=!0,a?k(g,a):(g.attr({width:0,height:0}),U(\"img\",{onload:function(){k(g,\nPb[j]=[this.width,this.height])},src:j}));return g},symbols:{circle:function(a,b,c,d){var e=0.166*c;return[\"M\",a+c/2,b,\"C\",a+c+e,b,a+c+e,b+d,a+c/2,b+d,\"C\",a-e,b+d,a-e,b,a+c/2,b,\"Z\"]},square:function(a,b,c,d){return[\"M\",a,b,\"L\",a+c,b,a+c,b+d,a,b+d,\"Z\"]},triangle:function(a,b,c,d){return[\"M\",a+c/2,b,\"L\",a+c,b+d,a,b+d,\"Z\"]},\"triangle-down\":function(a,b,c,d){return[\"M\",a,b,\"L\",a+c,b,a+c/2,b+d,\"Z\"]},diamond:function(a,b,c,d){return[\"M\",a+c/2,b,\"L\",a+c,b+d/2,a+c/2,b+d,a,b+d/2,\"Z\"]},arc:function(a,b,c,d,\ne){var f=e.start,c=e.r||c||d,g=e.end-0.001,d=e.innerR,h=e.open,i=V(f),j=ca(f),k=V(g),g=ca(g),e=e.end-f<ya?0:1;return[\"M\",a+c*i,b+c*j,\"A\",c,c,0,e,1,a+c*k,b+c*g,h?\"M\":\"L\",a+d*k,b+d*g,\"A\",d,d,0,e,0,a+d*i,b+d*j,h?\"\":\"Z\"]}},clipRect:function(a,b,c,d){var e=\"highcharts-\"+zb++,f=this.createElement(\"clipPath\").attr({id:e}).add(this.defs),a=this.rect(a,b,c,d,0).add(f);a.id=e;a.clipPath=f;return a},color:function(a,b,c){var d=this,e,f=/^rgba/,g,h,i,j,k,l,m,p=[];a&&a.linearGradient?g=\"linearGradient\":a&&a.radialGradient&&\n(g=\"radialGradient\");if(g){c=a[g];h=d.gradients;j=a.stops;b=b.radialReference;Ia(c)&&(a[g]=c={x1:c[0],y1:c[1],x2:c[2],y2:c[3],gradientUnits:\"userSpaceOnUse\"});g===\"radialGradient\"&&b&&!u(c.gradientUnits)&&(c=x(c,{cx:b[0]-b[2]/2+c.cx*b[2],cy:b[1]-b[2]/2+c.cy*b[2],r:c.r*b[2],gradientUnits:\"userSpaceOnUse\"}));for(m in c)m!==\"id\"&&p.push(m,c[m]);for(m in j)p.push(j[m]);p=p.join(\",\");h[p]?a=h[p].id:(c.id=a=\"highcharts-\"+zb++,h[p]=i=d.createElement(g).attr(c).add(d.defs),i.stops=[],n(j,function(a){f.test(a[1])?\n(e=ra(a[1]),k=e.get(\"rgb\"),l=e.get(\"a\")):(k=a[1],l=1);a=d.createElement(\"stop\").attr({offset:a[0],\"stop-color\":k,\"stop-opacity\":l}).add(i);i.stops.push(a)}));return\"url(\"+d.url+\"#\"+a+\")\"}else return f.test(a)?(e=ra(a),v(b,c+\"-opacity\",e.get(\"a\")),e.get(\"rgb\")):(b.removeAttribute(c+\"-opacity\"),a)},text:function(a,b,c,d){var e=M.chart.style,f=$||!Z&&this.forExport;if(d&&!this.forExport)return this.html(a,b,c);b=t(o(b,0));c=t(o(c,0));a=this.createElement(\"text\").attr({x:b,y:c,text:a}).css({fontFamily:e.fontFamily,\nfontSize:e.fontSize});f&&a.css({position:\"absolute\"});a.x=b;a.y=c;return a},html:function(a,b,c){var d=M.chart.style,e=this.createElement(\"span\"),f=e.attrSetters,g=e.element,h=e.renderer;f.text=function(a){a!==g.innerHTML&&delete this.bBox;g.innerHTML=a;return!1};f.x=f.y=f.align=function(a,b){b===\"align\"&&(b=\"textAlign\");e[b]=a;e.htmlUpdateTransform();return!1};e.attr({text:a,x:t(b),y:t(c)}).css({position:\"absolute\",whiteSpace:\"nowrap\",fontFamily:d.fontFamily,fontSize:d.fontSize});e.css=e.htmlCss;\nif(h.isSVG)e.add=function(a){var b,c=h.box.parentNode,d=[];if(a){if(b=a.div,!b){for(;a;)d.push(a),a=a.parentGroup;n(d.reverse(),function(a){var d;b=a.div=a.div||U(Ea,{className:v(a.element,\"class\")},{position:\"absolute\",left:(a.translateX||0)+\"px\",top:(a.translateY||0)+\"px\"},b||c);d=b.style;r(a.attrSetters,{translateX:function(a){d.left=a+\"px\"},translateY:function(a){d.top=a+\"px\"},visibility:function(a,b){d[b]=a}})})}}else b=c;b.appendChild(g);e.added=!0;e.alignOnAdd&&e.htmlUpdateTransform();return e};\nreturn e},fontMetrics:function(a){var a=C(a||11),a=a<24?a+4:t(a*1.2),b=t(a*0.8);return{h:a,b:b}},label:function(a,b,c,d,e,f,g,h,i){function j(){var a,b;a=o.element.style;L=(Oa===void 0||la===void 0||q.styles.textAlign)&&o.getBBox();q.width=(Oa||L.width||0)+2*da+kb;q.height=(la||L.height||0)+2*da;v=da+p.fontMetrics(a&&a.fontSize).b;if(C){if(!A)a=t(-s*da),b=h?-v:0,q.box=A=d?p.symbol(d,a,b,q.width,q.height):p.rect(a,b,q.width,q.height,0,lb[Rb]),A.add(q);A.isImg||A.attr(x({width:q.width,height:q.height},\nlb));lb=null}}function k(){var a=q.styles,a=a&&a.textAlign,b=kb+da*(1-s),c;c=h?0:v;if(u(Oa)&&(a===\"center\"||a===\"right\"))b+={center:0.5,right:1}[a]*(Oa-L.width);(b!==o.x||c!==o.y)&&o.attr({x:b,y:c});o.x=b;o.y=c}function l(a,b){A?A.attr(a,b):lb[a]=b}function m(){o.add(q);q.attr({text:a,x:b,y:c});A&&u(e)&&q.attr({anchorX:e,anchorY:f})}var p=this,q=p.g(i),o=p.text(\"\",0,0,g).attr({zIndex:1}),A,L,s=0,da=3,kb=0,Oa,la,E,H,B=0,lb={},v,g=q.attrSetters,C;J(q,\"add\",m);g.width=function(a){Oa=a;return!1};g.height=\nfunction(a){la=a;return!1};g.padding=function(a){u(a)&&a!==da&&(da=a,k());return!1};g.paddingLeft=function(a){u(a)&&a!==kb&&(kb=a,k());return!1};g.align=function(a){s={left:0,center:0.5,right:1}[a];return!1};g.text=function(a,b){o.attr(b,a);j();k();return!1};g[Rb]=function(a,b){C=!0;B=a%2/2;l(b,a);return!1};g.stroke=g.fill=g.r=function(a,b){b===\"fill\"&&(C=!0);l(b,a);return!1};g.anchorX=function(a,b){e=a;l(b,a+B-E);return!1};g.anchorY=function(a,b){f=a;l(b,a-H);return!1};g.x=function(a){q.x=a;a-=s*\n((Oa||L.width)+da);E=t(a);q.attr(\"translateX\",E);return!1};g.y=function(a){H=q.y=t(a);q.attr(\"translateY\",H);return!1};var y=q.css;return r(q,{css:function(a){if(a){var b={},a=x(a);n(\"fontSize,fontWeight,fontFamily,color,lineHeight,width,textDecoration,textShadow\".split(\",\"),function(c){a[c]!==w&&(b[c]=a[c],delete a[c])});o.css(b)}return y.call(q,a)},getBBox:function(){return{width:L.width+2*da,height:L.height+2*da,x:L.x-da,y:L.y-da}},shadow:function(a){A&&A.shadow(a);return q},destroy:function(){aa(q,\n\"add\",m);aa(q.element,\"mouseenter\");aa(q.element,\"mouseleave\");o&&(o=o.destroy());A&&(A=A.destroy());wa.prototype.destroy.call(q);q=p=j=k=l=m=null}})}};Va=Ha;var F;if(!Z&&!$){Highcharts.VMLElement=F={init:function(a,b){var c=[\"<\",b,' filled=\"f\" stroked=\"f\"'],d=[\"position: \",\"absolute\",\";\"],e=b===Ea;(b===\"shape\"||e)&&d.push(\"left:0;top:0;width:1px;height:1px;\");d.push(\"visibility: \",e?\"hidden\":\"visible\");c.push(' style=\"',d.join(\"\"),'\"/>');if(b)c=e||b===\"span\"||b===\"img\"?c.join(\"\"):a.prepVML(c),this.element=\nU(c);this.renderer=a;this.attrSetters={}},add:function(a){var b=this.renderer,c=this.element,d=b.box,d=a?a.element||a:d;a&&a.inverted&&b.invertChild(c,d);d.appendChild(c);this.added=!0;this.alignOnAdd&&!this.deferUpdateTransform&&this.updateTransform();z(this,\"add\");return this},updateTransform:wa.prototype.htmlUpdateTransform,setSpanRotation:function(a,b,c){K(this.element,{filter:a?[\"progid:DXImageTransform.Microsoft.Matrix(M11=\",c,\", M12=\",-b,\", M21=\",b,\", M22=\",c,\", sizingMethod='auto expand')\"].join(\"\"):\nS})},pathToVML:function(a){for(var b=a.length,c=[],d;b--;)if(sa(a[b]))c[b]=t(a[b]*10)-5;else if(a[b]===\"Z\")c[b]=\"x\";else if(c[b]=a[b],a.isArc&&(a[b]===\"wa\"||a[b]===\"at\"))d=a[b]===\"wa\"?1:-1,c[b+5]===c[b+7]&&(c[b+7]-=d),c[b+6]===c[b+8]&&(c[b+8]-=d);return c.join(\" \")||\"x\"},attr:function(a,b){var c,d,e,f=this.element||{},g=f.style,h=f.nodeName,i=this.renderer,j=this.symbolName,k,l=this.shadows,m,p=this.attrSetters,q=this;ea(a)&&u(b)&&(c=a,a={},a[c]=b);if(ea(a))c=a,q=c===\"strokeWidth\"||c===\"stroke-width\"?\nthis.strokeweight:this[c];else for(c in a)if(d=a[c],m=!1,e=p[c]&&p[c].call(this,d,c),e!==!1&&d!==null){e!==w&&(d=e);if(j&&/^(x|y|r|start|end|width|height|innerR|anchorX|anchorY)/.test(c))k||(this.symbolAttr(a),k=!0),m=!0;else if(c===\"d\"){d=d||[];this.d=d.join(\" \");f.path=d=this.pathToVML(d);if(l)for(e=l.length;e--;)l[e].path=l[e].cutOff?this.cutOffPath(d,l[e].cutOff):d;m=!0}else if(c===\"visibility\"){if(l)for(e=l.length;e--;)l[e].style[c]=d;h===\"DIV\"&&(d=d===\"hidden\"?\"-999em\":0,fb||(g[c]=d?\"visible\":\n\"hidden\"),c=\"top\");g[c]=d;m=!0}else if(c===\"zIndex\")d&&(g[c]=d),m=!0;else if(qa(c,[\"x\",\"y\",\"width\",\"height\"])!==-1)this[c]=d,c===\"x\"||c===\"y\"?c={x:\"left\",y:\"top\"}[c]:d=s(0,d),this.updateClipping?(this[c]=d,this.updateClipping()):g[c]=d,m=!0;else if(c===\"class\"&&h===\"DIV\")f.className=d;else if(c===\"stroke\")d=i.color(d,f,c),c=\"strokecolor\";else if(c===\"stroke-width\"||c===\"strokeWidth\")f.stroked=d?!0:!1,c=\"strokeweight\",this[c]=d,sa(d)&&(d+=\"px\");else if(c===\"dashstyle\")(f.getElementsByTagName(\"stroke\")[0]||\nU(i.prepVML([\"<stroke/>\"]),null,null,f))[c]=d||\"solid\",this.dashstyle=d,m=!0;else if(c===\"fill\")if(h===\"SPAN\")g.color=d;else{if(h!==\"IMG\")f.filled=d!==S?!0:!1,d=i.color(d,f,c,this),c=\"fillcolor\"}else if(c===\"opacity\")m=!0;else if(h===\"shape\"&&c===\"rotation\")this[c]=f.style[c]=d,f.style.left=-t(ca(d*Ua)+1)+\"px\",f.style.top=t(V(d*Ua))+\"px\";else if(c===\"translateX\"||c===\"translateY\"||c===\"rotation\")this[c]=d,this.updateTransform(),m=!0;else if(c===\"text\")this.bBox=null,f.innerHTML=d,m=!0;m||(fb?f[c]=\nd:v(f,c,d))}return q},clip:function(a){var b=this,c;a?(c=a.members,ga(c,b),c.push(b),b.destroyClip=function(){ga(c,b)},a=a.getCSS(b)):(b.destroyClip&&b.destroyClip(),a={clip:fb?\"inherit\":\"rect(auto)\"});return b.css(a)},css:wa.prototype.htmlCss,safeRemoveChild:function(a){a.parentNode&&Ta(a)},destroy:function(){this.destroyClip&&this.destroyClip();return wa.prototype.destroy.apply(this)},on:function(a,b){this.element[\"on\"+a]=function(){var a=O.event;a.target=a.srcElement;b(a)};return this},cutOffPath:function(a,\nb){var c,a=a.split(/[ ,]/);c=a.length;if(c===9||c===11)a[c-4]=a[c-2]=C(a[c-2])-10*b;return a.join(\" \")},shadow:function(a,b,c){var d=[],e,f=this.element,g=this.renderer,h,i=f.style,j,k=f.path,l,m,p,q;k&&typeof k.value!==\"string\"&&(k=\"x\");m=k;if(a){p=o(a.width,3);q=(a.opacity||0.15)/p;for(e=1;e<=3;e++){l=p*2+1-2*e;c&&(m=this.cutOffPath(k.value,l+0.5));j=['<shape isShadow=\"true\" strokeweight=\"',l,'\" filled=\"false\" path=\"',m,'\" coordsize=\"10 10\" style=\"',f.style.cssText,'\" />'];h=U(g.prepVML(j),null,\n{left:C(i.left)+o(a.offsetX,1),top:C(i.top)+o(a.offsetY,1)});if(c)h.cutOff=l+1;j=['<stroke color=\"',a.color||\"black\",'\" opacity=\"',q*e,'\"/>'];U(g.prepVML(j),null,null,h);b?b.element.appendChild(h):f.parentNode.insertBefore(h,f);d.push(h)}this.shadows=d}return this}};F=ha(wa,F);var ma={Element:F,isIE8:oa.indexOf(\"MSIE 8.0\")>-1,init:function(a,b,c){var d,e;this.alignedObjects=[];d=this.createElement(Ea);e=d.element;e.style.position=\"relative\";a.appendChild(d.element);this.isVML=!0;this.box=e;this.boxWrapper=\nd;this.setSize(b,c,!1);y.namespaces.hcv||(y.namespaces.add(\"hcv\",\"urn:schemas-microsoft-com:vml\"),(y.styleSheets.length?y.styleSheets[0]:y.createStyleSheet()).cssText+=\"hcv\\\\:fill, hcv\\\\:path, hcv\\\\:shape, hcv\\\\:stroke{ behavior:url(#default#VML); display: inline-block; } \")},isHidden:function(){return!this.box.offsetWidth},clipRect:function(a,b,c,d){var e=this.createElement(),f=T(a);return r(e,{members:[],left:(f?a.x:a)+1,top:(f?a.y:b)+1,width:(f?a.width:c)-1,height:(f?a.height:d)-1,getCSS:function(a){var b=\na.element,c=b.nodeName,a=a.inverted,d=this.top-(c===\"shape\"?b.offsetTop:0),e=this.left,b=e+this.width,f=d+this.height,d={clip:\"rect(\"+t(a?e:d)+\"px,\"+t(a?f:b)+\"px,\"+t(a?b:f)+\"px,\"+t(a?d:e)+\"px)\"};!a&&fb&&c===\"DIV\"&&r(d,{width:b+\"px\",height:f+\"px\"});return d},updateClipping:function(){n(e.members,function(a){a.css(e.getCSS(a))})}})},color:function(a,b,c,d){var e=this,f,g=/^rgba/,h,i,j=S;a&&a.linearGradient?i=\"gradient\":a&&a.radialGradient&&(i=\"pattern\");if(i){var k,l,m=a.linearGradient||a.radialGradient,\np,q,o,A,L,s=\"\",a=a.stops,u,t=[],w=function(){h=['<fill colors=\"'+t.join(\",\")+'\" opacity=\"',o,'\" o:opacity2=\"',q,'\" type=\"',i,'\" ',s,'focus=\"100%\" method=\"any\" />'];U(e.prepVML(h),null,null,b)};p=a[0];u=a[a.length-1];p[0]>0&&a.unshift([0,p[1]]);u[0]<1&&a.push([1,u[1]]);n(a,function(a,b){g.test(a[1])?(f=ra(a[1]),k=f.get(\"rgb\"),l=f.get(\"a\")):(k=a[1],l=1);t.push(a[0]*100+\"% \"+k);b?(o=l,A=k):(q=l,L=k)});if(c===\"fill\")if(i===\"gradient\")c=m.x1||m[0]||0,a=m.y1||m[1]||0,p=m.x2||m[2]||0,m=m.y2||m[3]||0,s='angle=\"'+\n(90-R.atan((m-a)/(p-c))*180/ya)+'\"',w();else{var j=m.r,r=j*2,E=j*2,H=m.cx,B=m.cy,x=b.radialReference,v,j=function(){x&&(v=d.getBBox(),H+=(x[0]-v.x)/v.width-0.5,B+=(x[1]-v.y)/v.height-0.5,r*=x[2]/v.width,E*=x[2]/v.height);s='src=\"'+M.global.VMLRadialGradientURL+'\" size=\"'+r+\",\"+E+'\" origin=\"0.5,0.5\" position=\"'+H+\",\"+B+'\" color2=\"'+L+'\" ';w()};d.added?j():J(d,\"add\",j);j=A}else j=k}else if(g.test(a)&&b.tagName!==\"IMG\")f=ra(a),h=[\"<\",c,' opacity=\"',f.get(\"a\"),'\"/>'],U(this.prepVML(h),null,null,b),j=\nf.get(\"rgb\");else{j=b.getElementsByTagName(c);if(j.length)j[0].opacity=1,j[0].type=\"solid\";j=a}return j},prepVML:function(a){var b=this.isIE8,a=a.join(\"\");b?(a=a.replace(\"/>\",' xmlns=\"urn:schemas-microsoft-com:vml\" />'),a=a.indexOf('style=\"')===-1?a.replace(\"/>\",' style=\"display:inline-block;behavior:url(#default#VML);\" />'):a.replace('style=\"','style=\"display:inline-block;behavior:url(#default#VML);')):a=a.replace(\"<\",\"<hcv:\");return a},text:Ha.prototype.html,path:function(a){var b={coordsize:\"10 10\"};\nIa(a)?b.d=a:T(a)&&r(b,a);return this.createElement(\"shape\").attr(b)},circle:function(a,b,c){var d=this.symbol(\"circle\");if(T(a))c=a.r,b=a.y,a=a.x;d.isCircle=!0;d.r=c;return d.attr({x:a,y:b})},g:function(a){var b;a&&(b={className:\"highcharts-\"+a,\"class\":\"highcharts-\"+a});return this.createElement(Ea).attr(b)},image:function(a,b,c,d,e){var f=this.createElement(\"img\").attr({src:a});arguments.length>1&&f.attr({x:b,y:c,width:d,height:e});return f},rect:function(a,b,c,d,e,f){var g=this.symbol(\"rect\");g.r=\nT(a)?a.r:e;return g.attr(T(a)?a:g.crisp(f,a,b,s(c,0),s(d,0)))},invertChild:function(a,b){var c=b.style;K(a,{flip:\"x\",left:C(c.width)-1,top:C(c.height)-1,rotation:-90})},symbols:{arc:function(a,b,c,d,e){var f=e.start,g=e.end,h=e.r||c||d,c=e.innerR,d=V(f),i=ca(f),j=V(g),k=ca(g);if(g-f===0)return[\"x\"];f=[\"wa\",a-h,b-h,a+h,b+h,a+h*d,b+h*i,a+h*j,b+h*k];e.open&&!c&&f.push(\"e\",\"M\",a,b);f.push(\"at\",a-c,b-c,a+c,b+c,a+c*j,b+c*k,a+c*d,b+c*i,\"x\",\"e\");f.isArc=!0;return f},circle:function(a,b,c,d,e){e&&(c=d=2*e.r);\ne&&e.isCircle&&(a-=c/2,b-=d/2);return[\"wa\",a,b,a+c,b+d,a+c,b+d/2,a+c,b+d/2,\"e\"]},rect:function(a,b,c,d,e){var f=a+c,g=b+d,h;!u(e)||!e.r?f=Ha.prototype.symbols.square.apply(0,arguments):(h=I(e.r,c,d),f=[\"M\",a+h,b,\"L\",f-h,b,\"wa\",f-2*h,b,f,b+2*h,f-h,b,f,b+h,\"L\",f,g-h,\"wa\",f-2*h,g-2*h,f,g,f,g-h,f-h,g,\"L\",a+h,g,\"wa\",a,g-2*h,a+2*h,g,a+h,g,a,g-h,\"L\",a,b+h,\"wa\",a,b,a+2*h,b+2*h,a,b+h,a+h,b,\"x\",\"e\"]);return f}}};Highcharts.VMLRenderer=F=function(){this.init.apply(this,arguments)};F.prototype=x(Ha.prototype,\nma);Va=F}var Tb;if($)Highcharts.CanVGRenderer=F=function(){za=\"http://www.w3.org/1999/xhtml\"},F.prototype.symbols={},Tb=function(){function a(){var a=b.length,d;for(d=0;d<a;d++)b[d]();b=[]}var b=[];return{push:function(c,d){b.length===0&&Vb(d,a);b.push(c)}}}(),Va=F;Ma.prototype={addLabel:function(){var a=this.axis,b=a.options,c=a.chart,d=a.horiz,e=a.categories,f=a.series[0]&&a.series[0].names,g=this.pos,h=b.labels,i=a.tickPositions,d=d&&e&&!h.step&&!h.staggerLines&&!h.rotation&&c.plotWidth/i.length||\n!d&&(c.margin[3]||c.chartWidth*0.33),j=g===i[0],k=g===i[i.length-1],l,f=e?o(e[g],f&&f[g],g):g,e=this.label,m=i.info;a.isDatetimeAxis&&m&&(l=b.dateTimeLabelFormats[m.higherRanks[g]||m.unitName]);this.isFirst=j;this.isLast=k;b=a.labelFormatter.call({axis:a,chart:c,isFirst:j,isLast:k,dateTimeLabelFormat:l,value:a.isLog?ia(fa(f)):f});g=d&&{width:s(1,t(d-2*(h.padding||10)))+\"px\"};g=r(g,h.style);if(u(e))e&&e.attr({text:b}).css(g);else{l={align:a.labelAlign};if(sa(h.rotation))l.rotation=h.rotation;if(d&&\nh.ellipsis)l._clipHeight=a.len/i.length;this.label=u(b)&&h.enabled?c.renderer.text(b,0,0,h.useHTML).attr(l).css(g).add(a.labelGroup):null}},getLabelSize:function(){var a=this.label,b=this.axis;return a?(this.labelBBox=a.getBBox())[b.horiz?\"height\":\"width\"]:0},getLabelSides:function(){var a=this.axis,b=this.labelBBox.width,a=b*{left:0,center:0.5,right:1}[a.labelAlign]-a.options.labels.x;return[-a,b-a]},handleOverflow:function(a,b){var c=!0,d=this.axis,e=d.chart,f=this.isFirst,g=this.isLast,h=b.x,i=\nd.reversed,j=d.tickPositions;if(f||g){var k=this.getLabelSides(),l=k[0],k=k[1],e=e.plotLeft,m=e+d.len,j=(d=d.ticks[j[a+(f?1:-1)]])&&d.label.xy&&d.label.xy.x+d.getLabelSides()[f?0:1];f&&!i||g&&i?h+l<e&&(h=e-l,d&&h+k>j&&(c=!1)):h+k>m&&(h=m-k,d&&h+l<j&&(c=!1));b.x=h}return c},getPosition:function(a,b,c,d){var e=this.axis,f=e.chart,g=d&&f.oldChartHeight||f.chartHeight;return{x:a?e.translate(b+c,null,null,d)+e.transB:e.left+e.offset+(e.opposite?(d&&f.oldChartWidth||f.chartWidth)-e.right-e.left:0),y:a?\ng-e.bottom+e.offset-(e.opposite?e.height:0):g-e.translate(b+c,null,null,d)-e.transB}},getLabelPosition:function(a,b,c,d,e,f,g,h){var i=this.axis,j=i.transA,k=i.reversed,l=i.staggerLines,m=i.chart.renderer.fontMetrics(e.style.fontSize).b,p=e.rotation,a=a+e.x-(f&&d?f*j*(k?-1:1):0),b=b+e.y-(f&&!d?f*j*(k?1:-1):0);p&&i.side===2&&(b-=m-m*V(p*Ua));!u(e.y)&&!p&&(b+=m-c.getBBox().height/2);l&&(b+=g/(h||1)%l*(i.labelOffset/l));return{x:a,y:b}},getMarkPath:function(a,b,c,d,e,f){return f.crispLine([\"M\",a,b,\"L\",\na+(e?0:-c),b+(e?c:0)],d)},render:function(a,b,c){var d=this.axis,e=d.options,f=d.chart.renderer,g=d.horiz,h=this.type,i=this.label,j=this.pos,k=e.labels,l=this.gridLine,m=h?h+\"Grid\":\"grid\",p=h?h+\"Tick\":\"tick\",q=e[m+\"LineWidth\"],n=e[m+\"LineColor\"],A=e[m+\"LineDashStyle\"],s=e[p+\"Length\"],m=e[p+\"Width\"]||0,u=e[p+\"Color\"],t=e[p+\"Position\"],p=this.mark,r=k.step,v=!0,x=d.tickmarkOffset,E=this.getPosition(g,j,x,b),H=E.x,E=E.y,B=g&&H===d.pos+d.len||!g&&E===d.pos?-1:1,C=d.staggerLines;this.isActive=!0;if(q){j=\nd.getPlotLinePath(j+x,q*B,b,!0);if(l===w){l={stroke:n,\"stroke-width\":q};if(A)l.dashstyle=A;if(!h)l.zIndex=1;if(b)l.opacity=0;this.gridLine=l=q?f.path(j).attr(l).add(d.gridGroup):null}if(!b&&l&&j)l[this.isNew?\"attr\":\"animate\"]({d:j,opacity:c})}if(m&&s)t===\"inside\"&&(s=-s),d.opposite&&(s=-s),b=this.getMarkPath(H,E,s,m*B,g,f),p?p.animate({d:b,opacity:c}):this.mark=f.path(b).attr({stroke:u,\"stroke-width\":m,opacity:c}).add(d.axisGroup);if(i&&!isNaN(H))i.xy=E=this.getLabelPosition(H,E,i,g,k,x,a,r),this.isFirst&&\n!this.isLast&&!o(e.showFirstLabel,1)||this.isLast&&!this.isFirst&&!o(e.showLastLabel,1)?v=!1:!C&&g&&k.overflow===\"justify\"&&!this.handleOverflow(a,E)&&(v=!1),r&&a%r&&(v=!1),v&&!isNaN(E.y)?(E.opacity=c,i[this.isNew?\"attr\":\"animate\"](E),this.isNew=!1):i.attr(\"y\",-9999)},destroy:function(){Ka(this,this.axis)}};vb.prototype={render:function(){var a=this,b=a.axis,c=b.horiz,d=(b.pointRange||0)/2,e=a.options,f=e.label,g=a.label,h=e.width,i=e.to,j=e.from,k=u(j)&&u(i),l=e.value,m=e.dashStyle,p=a.svgElem,q=\n[],n,A=e.color,L=e.zIndex,t=e.events,w=b.chart.renderer;b.isLog&&(j=na(j),i=na(i),l=na(l));if(h){if(q=b.getPlotLinePath(l,h),d={stroke:A,\"stroke-width\":h},m)d.dashstyle=m}else if(k){if(j=s(j,b.min-d),i=I(i,b.max+d),q=b.getPlotBandPath(j,i,e),d={fill:A},e.borderWidth)d.stroke=e.borderColor,d[\"stroke-width\"]=e.borderWidth}else return;if(u(L))d.zIndex=L;if(p)q?p.animate({d:q},null,p.onGetPath):(p.hide(),p.onGetPath=function(){p.show()});else if(q&&q.length&&(a.svgElem=p=w.path(q).attr(d).add(),t))for(n in e=\nfunction(b){p.on(b,function(c){t[b].apply(a,[c])})},t)e(n);if(f&&u(f.text)&&q&&q.length&&b.width>0&&b.height>0){f=x({align:c&&k&&\"center\",x:c?!k&&4:10,verticalAlign:!c&&k&&\"middle\",y:c?k?16:10:k?6:-4,rotation:c&&!k&&90},f);if(!g)a.label=g=w.text(f.text,0,0,f.useHTML).attr({align:f.textAlign||f.align,rotation:f.rotation,zIndex:L}).css(f.style).add();b=[q[1],q[4],o(q[6],q[1])];q=[q[2],q[5],o(q[7],q[2])];c=Ja(b);k=Ja(q);g.align(f,!1,{x:c,y:k,width:va(b)-c,height:va(q)-k});g.show()}else g&&g.hide();return a},\ndestroy:function(){ga(this.axis.plotLinesAndBands,this);delete this.axis;Ka(this)}};Mb.prototype={destroy:function(){Ka(this,this.axis)},render:function(a){var b=this.options,c=b.format,c=c?Ca(c,this):b.formatter.call(this);this.label?this.label.attr({text:c,visibility:\"hidden\"}):this.label=this.axis.chart.renderer.text(c,0,0,b.useHTML).css(b.style).attr({align:this.textAlign,rotation:b.rotation,visibility:\"hidden\"}).add(a)},setOffset:function(a,b){var c=this.axis,d=c.chart,e=d.inverted,f=this.isNegative,\ng=c.translate(this.percent?100:this.total,0,0,0,1),c=c.translate(0),c=N(g-c),h=d.xAxis[0].translate(this.x)+a,i=d.plotHeight,f={x:e?f?g:g-c:h,y:e?i-h-b:f?i-g-c:i-g,width:e?c:b,height:e?b:c};if(e=this.label)e.align(this.alignOptions,null,f),f=e.alignAttr,e.attr({visibility:this.options.crop===!1||d.isInsidePlot(f.x,f.y)?Z?\"inherit\":\"visible\":\"hidden\"})}};db.prototype={defaultOptions:{dateTimeLabelFormats:{millisecond:\"%H:%M:%S.%L\",second:\"%H:%M:%S\",minute:\"%H:%M\",hour:\"%H:%M\",day:\"%e. %b\",week:\"%e. %b\",\nmonth:\"%b '%y\",year:\"%Y\"},endOnTick:!1,gridLineColor:\"#C0C0C0\",labels:G,lineColor:\"#C0D0E0\",lineWidth:1,minPadding:0.01,maxPadding:0.01,minorGridLineColor:\"#E0E0E0\",minorGridLineWidth:1,minorTickColor:\"#A0A0A0\",minorTickLength:2,minorTickPosition:\"outside\",startOfWeek:1,startOnTick:!1,tickColor:\"#C0D0E0\",tickLength:5,tickmarkPlacement:\"between\",tickPixelInterval:100,tickPosition:\"outside\",tickWidth:1,title:{align:\"middle\",style:{color:\"#4d759e\",fontWeight:\"bold\"}},type:\"linear\"},defaultYAxisOptions:{endOnTick:!0,\ngridLineWidth:1,tickPixelInterval:72,showLastLabel:!0,labels:{x:-8,y:3},lineWidth:0,maxPadding:0.05,minPadding:0.05,startOnTick:!0,tickWidth:0,title:{rotation:270,text:\"Values\"},stackLabels:{enabled:!1,formatter:function(){return Aa(this.total,-1)},style:G.style}},defaultLeftAxisOptions:{labels:{x:-8,y:null},title:{rotation:270}},defaultRightAxisOptions:{labels:{x:8,y:null},title:{rotation:90}},defaultBottomAxisOptions:{labels:{x:0,y:14},title:{rotation:0}},defaultTopAxisOptions:{labels:{x:0,y:-5},\ntitle:{rotation:0}},init:function(a,b){var c=b.isX;this.horiz=a.inverted?!c:c;this.xOrY=(this.isXAxis=c)?\"x\":\"y\";this.opposite=b.opposite;this.side=this.horiz?this.opposite?0:2:this.opposite?1:3;this.setOptions(b);var d=this.options,e=d.type;this.labelFormatter=d.labels.formatter||this.defaultLabelFormatter;this.userOptions=b;this.minPixelPadding=0;this.chart=a;this.reversed=d.reversed;this.zoomEnabled=d.zoomEnabled!==!1;this.categories=d.categories||e===\"category\";this.isLog=e===\"logarithmic\";this.isDatetimeAxis=\ne===\"datetime\";this.isLinked=u(d.linkedTo);this.tickmarkOffset=this.categories&&d.tickmarkPlacement===\"between\"?0.5:0;this.ticks={};this.minorTicks={};this.plotLinesAndBands=[];this.alternateBands={};this.len=0;this.minRange=this.userMinRange=d.minRange||d.maxZoom;this.range=d.range;this.offset=d.offset||0;this.stacks={};this.oldStacks={};this.stackExtremes={};this.min=this.max=null;var f,d=this.options.events;qa(this,a.axes)===-1&&(a.axes.push(this),a[c?\"xAxis\":\"yAxis\"].push(this));this.series=this.series||\n[];if(a.inverted&&c&&this.reversed===w)this.reversed=!0;this.removePlotLine=this.removePlotBand=this.removePlotBandOrLine;for(f in d)J(this,f,d[f]);if(this.isLog)this.val2lin=na,this.lin2val=fa},setOptions:function(a){this.options=x(this.defaultOptions,this.isXAxis?{}:this.defaultYAxisOptions,[this.defaultTopAxisOptions,this.defaultRightAxisOptions,this.defaultBottomAxisOptions,this.defaultLeftAxisOptions][this.side],x(M[this.isXAxis?\"xAxis\":\"yAxis\"],a))},update:function(a,b){var c=this.chart,a=c.options[this.xOrY+\n\"Axis\"][this.options.index]=x(this.userOptions,a);this.destroy(!0);this._addedPlotLB=this.userMin=this.userMax=w;this.init(c,r(a,{events:w}));c.isDirtyBox=!0;o(b,!0)&&c.redraw()},remove:function(a){var b=this.chart,c=this.xOrY+\"Axis\";n(this.series,function(a){a.remove(!1)});ga(b.axes,this);ga(b[c],this);b.options[c].splice(this.options.index,1);n(b[c],function(a,b){a.options.index=b});this.destroy();b.isDirtyBox=!0;o(a,!0)&&b.redraw()},defaultLabelFormatter:function(){var a=this.axis,b=this.value,\nc=a.categories,d=this.dateTimeLabelFormat,e=M.lang.numericSymbols,f=e&&e.length,g,h=a.options.labels.format,a=a.isLog?b:a.tickInterval;if(h)g=Ca(h,this);else if(c)g=b;else if(d)g=Xa(d,b);else if(f&&a>=1E3)for(;f--&&g===w;)c=Math.pow(1E3,f+1),a>=c&&e[f]!==null&&(g=Aa(b/c,-1)+e[f]);g===w&&(g=b>=1E3?Aa(b,0):Aa(b,-1));return g},getSeriesExtremes:function(){var a=this,b=a.chart;a.hasVisibleSeries=!1;a.dataMin=a.dataMax=null;a.stackExtremes={};a.buildStacks();n(a.series,function(c){if(c.visible||!b.options.chart.ignoreHiddenSeries){var d;\nd=c.options.threshold;var e;a.hasVisibleSeries=!0;a.isLog&&d<=0&&(d=null);if(a.isXAxis){if(d=c.xData,d.length)a.dataMin=I(o(a.dataMin,d[0]),Ja(d)),a.dataMax=s(o(a.dataMax,d[0]),va(d))}else{c.getExtremes();e=c.dataMax;c=c.dataMin;if(u(c)&&u(e))a.dataMin=I(o(a.dataMin,c),c),a.dataMax=s(o(a.dataMax,e),e);if(u(d))if(a.dataMin>=d)a.dataMin=d,a.ignoreMinPadding=!0;else if(a.dataMax<d)a.dataMax=d,a.ignoreMaxPadding=!0}}})},translate:function(a,b,c,d,e,f){var g=this.len,h=1,i=0,j=d?this.oldTransA:this.transA,\nd=d?this.oldMin:this.min,k=this.minPixelPadding,e=(this.options.ordinal||this.isLog&&e)&&this.lin2val;if(!j)j=this.transA;c&&(h*=-1,i=g);this.reversed&&(h*=-1,i-=h*g);b?(a=a*h+i,a-=k,a=a/j+d,e&&(a=this.lin2val(a))):(e&&(a=this.val2lin(a)),f===\"between\"&&(f=0.5),a=h*(a-d)*j+i+h*k+(sa(f)?j*f*this.pointRange:0));return a},toPixels:function(a,b){return this.translate(a,!1,!this.horiz,null,!0)+(b?0:this.pos)},toValue:function(a,b){return this.translate(a-(b?0:this.pos),!0,!this.horiz,null,!0)},getPlotLinePath:function(a,\nb,c,d){var e=this.chart,f=this.left,g=this.top,h,i,j,a=this.translate(a,null,null,c),k=c&&e.oldChartHeight||e.chartHeight,l=c&&e.oldChartWidth||e.chartWidth,m;h=this.transB;c=i=t(a+h);h=j=t(k-a-h);if(isNaN(a))m=!0;else if(this.horiz){if(h=g,j=k-this.bottom,c<f||c>f+this.width)m=!0}else if(c=f,i=l-this.right,h<g||h>g+this.height)m=!0;return m&&!d?null:e.renderer.crispLine([\"M\",c,h,\"L\",i,j],b||0)},getPlotBandPath:function(a,b){var c=this.getPlotLinePath(b),d=this.getPlotLinePath(a);d&&c?d.push(c[4],\nc[5],c[1],c[2]):d=null;return d},getLinearTickPositions:function(a,b,c){for(var d,b=ia(P(b/a)*a),c=ia(xa(c/a)*a),e=[];b<=c;){e.push(b);b=ia(b+a);if(b===d)break;d=b}return e},getLogTickPositions:function(a,b,c,d){var e=this.options,f=this.len,g=[];if(!d)this._minorAutoInterval=null;if(a>=0.5)a=t(a),g=this.getLinearTickPositions(a,b,c);else if(a>=0.08)for(var f=P(b),h,i,j,k,l,e=a>0.3?[1,2,4]:a>0.15?[1,2,4,6,8]:[1,2,3,4,5,6,7,8,9];f<c+1&&!l;f++){i=e.length;for(h=0;h<i&&!l;h++)j=na(fa(f)*e[h]),j>b&&(!d||\nk<=c)&&g.push(k),k>c&&(l=!0),k=j}else if(b=fa(b),c=fa(c),a=e[d?\"minorTickInterval\":\"tickInterval\"],a=o(a===\"auto\"?null:a,this._minorAutoInterval,(c-b)*(e.tickPixelInterval/(d?5:1))/((d?f/this.tickPositions.length:f)||1)),a=ob(a,null,nb(a)),g=Na(this.getLinearTickPositions(a,b,c),na),!d)this._minorAutoInterval=a/5;if(!d)this.tickInterval=a;return g},getMinorTickPositions:function(){var a=this.options,b=this.tickPositions,c=this.minorTickInterval,d=[],e;if(this.isLog){e=b.length;for(a=1;a<e;a++)d=d.concat(this.getLogTickPositions(c,\nb[a-1],b[a],!0))}else if(this.isDatetimeAxis&&a.minorTickInterval===\"auto\")d=d.concat(Eb(Cb(c),this.min,this.max,a.startOfWeek)),d[0]<this.min&&d.shift();else for(b=this.min+(b[0]-this.min)%c;b<=this.max;b+=c)d.push(b);return d},adjustForMinRange:function(){var a=this.options,b=this.min,c=this.max,d,e=this.dataMax-this.dataMin>=this.minRange,f,g,h,i,j;if(this.isXAxis&&this.minRange===w&&!this.isLog)u(a.min)||u(a.max)?this.minRange=null:(n(this.series,function(a){i=a.xData;for(g=j=a.xIncrement?1:i.length-\n1;g>0;g--)if(h=i[g]-i[g-1],f===w||h<f)f=h}),this.minRange=I(f*5,this.dataMax-this.dataMin));if(c-b<this.minRange){var k=this.minRange;d=(k-c+b)/2;d=[b-d,o(a.min,b-d)];if(e)d[2]=this.dataMin;b=va(d);c=[b+k,o(a.max,b+k)];if(e)c[2]=this.dataMax;c=Ja(c);c-b<k&&(d[0]=c-k,d[1]=o(a.min,c-k),b=va(d))}this.min=b;this.max=c},setAxisTranslation:function(a){var b=this.max-this.min,c=0,d,e=0,f=0,g=this.linkedParent,h=this.transA;if(this.isXAxis)g?(e=g.minPointOffset,f=g.pointRangePadding):n(this.series,function(a){var g=\na.pointRange,h=a.options.pointPlacement,l=a.closestPointRange;g>b&&(g=0);c=s(c,g);e=s(e,ea(h)?0:g/2);f=s(f,h===\"on\"?0:g);!a.noSharedTooltip&&u(l)&&(d=u(d)?I(d,l):l)}),g=this.ordinalSlope&&d?this.ordinalSlope/d:1,this.minPointOffset=e*=g,this.pointRangePadding=f*=g,this.pointRange=I(c,b),this.closestPointRange=d;if(a)this.oldTransA=h;this.translationSlope=this.transA=h=this.len/(b+f||1);this.transB=this.horiz?this.left:this.bottom;this.minPixelPadding=h*e},setTickPositions:function(a){var b=this,c=\nb.chart,d=b.options,e=b.isLog,f=b.isDatetimeAxis,g=b.isXAxis,h=b.isLinked,i=b.options.tickPositioner,j=d.maxPadding,k=d.minPadding,l=d.tickInterval,m=d.minTickInterval,p=d.tickPixelInterval,q,ba=b.categories;h?(b.linkedParent=c[g?\"xAxis\":\"yAxis\"][d.linkedTo],c=b.linkedParent.getExtremes(),b.min=o(c.min,c.dataMin),b.max=o(c.max,c.dataMax),d.type!==b.linkedParent.options.type&&ka(11,1)):(b.min=o(b.userMin,d.min,b.dataMin),b.max=o(b.userMax,d.max,b.dataMax));if(e)!a&&I(b.min,o(b.dataMin,b.min))<=0&&\nka(10,1),b.min=ia(na(b.min)),b.max=ia(na(b.max));if(b.range&&(b.userMin=b.min=s(b.min,b.max-b.range),b.userMax=b.max,a))b.range=null;b.beforePadding&&b.beforePadding();b.adjustForMinRange();if(!ba&&!b.usePercentage&&!h&&u(b.min)&&u(b.max)&&(c=b.max-b.min)){if(!u(d.min)&&!u(b.userMin)&&k&&(b.dataMin<0||!b.ignoreMinPadding))b.min-=c*k;if(!u(d.max)&&!u(b.userMax)&&j&&(b.dataMax>0||!b.ignoreMaxPadding))b.max+=c*j}b.min===b.max||b.min===void 0||b.max===void 0?b.tickInterval=1:h&&!l&&p===b.linkedParent.options.tickPixelInterval?\nb.tickInterval=b.linkedParent.tickInterval:(b.tickInterval=o(l,ba?1:(b.max-b.min)*p/s(b.len,p)),!u(l)&&b.len<p&&!this.isRadial&&(q=!0,b.tickInterval/=4));g&&!a&&n(b.series,function(a){a.processData(b.min!==b.oldMin||b.max!==b.oldMax)});b.setAxisTranslation(!0);b.beforeSetTickPositions&&b.beforeSetTickPositions();if(b.postProcessTickInterval)b.tickInterval=b.postProcessTickInterval(b.tickInterval);if(b.pointRange)b.tickInterval=s(b.pointRange,b.tickInterval);if(!l&&b.tickInterval<m)b.tickInterval=\nm;if(!f&&!e&&!l)b.tickInterval=ob(b.tickInterval,null,nb(b.tickInterval),d);b.minorTickInterval=d.minorTickInterval===\"auto\"&&b.tickInterval?b.tickInterval/5:d.minorTickInterval;b.tickPositions=a=d.tickPositions?[].concat(d.tickPositions):i&&i.apply(b,[b.min,b.max]);if(!a)!b.ordinalPositions&&(b.max-b.min)/b.tickInterval>s(2*b.len,200)&&ka(19,!0),a=f?(b.getNonLinearTimeTicks||Eb)(Cb(b.tickInterval,d.units),b.min,b.max,d.startOfWeek,b.ordinalPositions,b.closestPointRange,!0):e?b.getLogTickPositions(b.tickInterval,\nb.min,b.max):b.getLinearTickPositions(b.tickInterval,b.min,b.max),q&&a.splice(1,a.length-2),b.tickPositions=a;if(!h)e=a[0],f=a[a.length-1],h=b.minPointOffset||0,d.startOnTick?b.min=e:b.min-h>e&&a.shift(),d.endOnTick?b.max=f:b.max+h<f&&a.pop(),a.length===1&&(b.min-=0.001,b.max+=0.001)},setMaxTicks:function(){var a=this.chart,b=a.maxTicks||{},c=this.tickPositions,d=this._maxTicksKey=[this.xOrY,this.pos,this.len].join(\"-\");if(!this.isLinked&&!this.isDatetimeAxis&&c&&c.length>(b[d]||0)&&this.options.alignTicks!==\n!1)b[d]=c.length;a.maxTicks=b},adjustTickAmount:function(){var a=this._maxTicksKey,b=this.tickPositions,c=this.chart.maxTicks;if(c&&c[a]&&!this.isDatetimeAxis&&!this.categories&&!this.isLinked&&this.options.alignTicks!==!1){var d=this.tickAmount,e=b.length;this.tickAmount=a=c[a];if(e<a){for(;b.length<a;)b.push(ia(b[b.length-1]+this.tickInterval));this.transA*=(e-1)/(a-1);this.max=b[b.length-1]}if(u(d)&&a!==d)this.isDirty=!0}},setScale:function(){var a=this.stacks,b,c,d,e;this.oldMin=this.min;this.oldMax=\nthis.max;this.oldAxisLength=this.len;this.setAxisSize();e=this.len!==this.oldAxisLength;n(this.series,function(a){if(a.isDirtyData||a.isDirty||a.xAxis.isDirty)d=!0});if(e||d||this.isLinked||this.forceRedraw||this.userMin!==this.oldUserMin||this.userMax!==this.oldUserMax){if(!this.isXAxis)for(b in a)delete a[b];this.forceRedraw=!1;this.getSeriesExtremes();this.setTickPositions();this.oldUserMin=this.userMin;this.oldUserMax=this.userMax;if(!this.isDirty)this.isDirty=e||this.min!==this.oldMin||this.max!==\nthis.oldMax}else if(!this.isXAxis){if(this.oldStacks)a=this.stacks=this.oldStacks;for(b in a)for(c in a[b])a[b][c].cum=a[b][c].total}this.setMaxTicks()},setExtremes:function(a,b,c,d,e){var f=this,g=f.chart,c=o(c,!0),e=r(e,{min:a,max:b});z(f,\"setExtremes\",e,function(){f.userMin=a;f.userMax=b;f.eventArgs=e;f.isDirtyExtremes=!0;c&&g.redraw(d)})},zoom:function(a,b){this.allowZoomOutside||(u(this.dataMin)&&a<=this.dataMin&&(a=w),u(this.dataMax)&&b>=this.dataMax&&(b=w));this.displayBtn=a!==w||b!==w;this.setExtremes(a,\nb,!1,w,{trigger:\"zoom\"});return!0},setAxisSize:function(){var a=this.chart,b=this.options,c=b.offsetLeft||0,d=b.offsetRight||0,e=this.horiz,f,g;this.left=g=o(b.left,a.plotLeft+c);this.top=f=o(b.top,a.plotTop);this.width=c=o(b.width,a.plotWidth-c+d);this.height=b=o(b.height,a.plotHeight);this.bottom=a.chartHeight-b-f;this.right=a.chartWidth-c-g;this.len=s(e?c:b,0);this.pos=e?g:f},getExtremes:function(){var a=this.isLog;return{min:a?ia(fa(this.min)):this.min,max:a?ia(fa(this.max)):this.max,dataMin:this.dataMin,\ndataMax:this.dataMax,userMin:this.userMin,userMax:this.userMax}},getThreshold:function(a){var b=this.isLog,c=b?fa(this.min):this.min,b=b?fa(this.max):this.max;c>a||a===null?a=c:b<a&&(a=b);return this.translate(a,0,1,0,1)},addPlotBand:function(a){this.addPlotBandOrLine(a,\"plotBands\")},addPlotLine:function(a){this.addPlotBandOrLine(a,\"plotLines\")},addPlotBandOrLine:function(a,b){var c=(new vb(this,a)).render(),d=this.userOptions;c&&(b&&(d[b]=d[b]||[],d[b].push(a)),this.plotLinesAndBands.push(c));return c},\nautoLabelAlign:function(a){a=(o(a,0)-this.side*90+720)%360;return a>15&&a<165?\"right\":a>195&&a<345?\"left\":\"center\"},getOffset:function(){var a=this,b=a.chart,c=b.renderer,d=a.options,e=a.tickPositions,f=a.ticks,g=a.horiz,h=a.side,i=b.inverted?[1,0,3,2][h]:h,j,k=0,l,m=0,p=d.title,q=d.labels,ba=0,A=b.axisOffset,L=b.clipOffset,t=[-1,1,1,-1][h],r,v=1,x=o(q.maxStaggerLines,5),la,E,H,B;a.hasData=j=a.hasVisibleSeries||u(a.min)&&u(a.max)&&!!e;a.showAxis=b=j||o(d.showEmpty,!0);a.staggerLines=a.horiz&&q.staggerLines;\nif(!a.axisGroup)a.gridGroup=c.g(\"grid\").attr({zIndex:d.gridZIndex||1}).add(),a.axisGroup=c.g(\"axis\").attr({zIndex:d.zIndex||2}).add(),a.labelGroup=c.g(\"axis-labels\").attr({zIndex:q.zIndex||7}).add();if(j||a.isLinked){a.labelAlign=o(q.align||a.autoLabelAlign(q.rotation));n(e,function(b){f[b]?f[b].addLabel():f[b]=new Ma(a,b)});if(a.horiz&&!a.staggerLines&&x&&!q.rotation){for(r=a.reversed?[].concat(e).reverse():e;v<x;){j=[];la=!1;for(q=0;q<r.length;q++)E=r[q],H=(H=f[E].label&&f[E].label.getBBox())?H.width:\n0,B=q%v,H&&(E=a.translate(E),j[B]!==w&&E<j[B]&&(la=!0),j[B]=E+H);if(la)v++;else break}if(v>1)a.staggerLines=v}n(e,function(b){if(h===0||h===2||{1:\"left\",3:\"right\"}[h]===a.labelAlign)ba=s(f[b].getLabelSize(),ba)});if(a.staggerLines)ba*=a.staggerLines,a.labelOffset=ba}else for(r in f)f[r].destroy(),delete f[r];if(p&&p.text&&p.enabled!==!1){if(!a.axisTitle)a.axisTitle=c.text(p.text,0,0,p.useHTML).attr({zIndex:7,rotation:p.rotation||0,align:p.textAlign||{low:\"left\",middle:\"center\",high:\"right\"}[p.align]}).css(p.style).add(a.axisGroup),\na.axisTitle.isNew=!0;if(b)k=a.axisTitle.getBBox()[g?\"height\":\"width\"],m=o(p.margin,g?5:10),l=p.offset;a.axisTitle[b?\"show\":\"hide\"]()}a.offset=t*o(d.offset,A[h]);a.axisTitleMargin=o(l,ba+m+(h!==2&&ba&&t*d.labels[g?\"y\":\"x\"]));A[h]=s(A[h],a.axisTitleMargin+k+t*a.offset);L[i]=s(L[i],P(d.lineWidth/2)*2)},getLinePath:function(a){var b=this.chart,c=this.opposite,d=this.offset,e=this.horiz,f=this.left+(c?this.width:0)+d,d=b.chartHeight-this.bottom-(c?this.height:0)+d;c&&(a*=-1);return b.renderer.crispLine([\"M\",\ne?this.left:f,e?d:this.top,\"L\",e?b.chartWidth-this.right:f,e?d:b.chartHeight-this.bottom],a)},getTitlePosition:function(){var a=this.horiz,b=this.left,c=this.top,d=this.len,e=this.options.title,f=a?b:c,g=this.opposite,h=this.offset,i=C(e.style.fontSize||12),d={low:f+(a?0:d),middle:f+d/2,high:f+(a?d:0)}[e.align],b=(a?c+this.height:b)+(a?1:-1)*(g?-1:1)*this.axisTitleMargin+(this.side===2?i:0);return{x:a?d:b+(g?this.width:0)+h+(e.x||0),y:a?b-(g?this.height:0)+h:d+(e.y||0)}},render:function(){var a=this,\nb=a.chart,c=b.renderer,d=a.options,e=a.isLog,f=a.isLinked,g=a.tickPositions,h=a.axisTitle,i=a.stacks,j=a.ticks,k=a.minorTicks,l=a.alternateBands,m=d.stackLabels,p=d.alternateGridColor,q=a.tickmarkOffset,o=d.lineWidth,A,s=b.hasRendered&&u(a.oldMin)&&!isNaN(a.oldMin);A=a.hasData;var t=a.showAxis,r,v;n([j,k,l],function(a){for(var b in a)a[b].isActive=!1});if(A||f)if(a.minorTickInterval&&!a.categories&&n(a.getMinorTickPositions(),function(b){k[b]||(k[b]=new Ma(a,b,\"minor\"));s&&k[b].isNew&&k[b].render(null,\n!0);k[b].render(null,!1,1)}),g.length&&(n(g.slice(1).concat([g[0]]),function(b,c){c=c===g.length-1?0:c+1;if(!f||b>=a.min&&b<=a.max)j[b]||(j[b]=new Ma(a,b)),s&&j[b].isNew&&j[b].render(c,!0),j[b].render(c,!1,1)}),q&&a.min===0&&(j[-1]||(j[-1]=new Ma(a,-1,null,!0)),j[-1].render(-1))),p&&n(g,function(b,c){if(c%2===0&&b<a.max)l[b]||(l[b]=new vb(a)),r=b+q,v=g[c+1]!==w?g[c+1]+q:a.max,l[b].options={from:e?fa(r):r,to:e?fa(v):v,color:p},l[b].render(),l[b].isActive=!0}),!a._addedPlotLB)n((d.plotLines||[]).concat(d.plotBands||\n[]),function(b){a.addPlotBandOrLine(b)}),a._addedPlotLB=!0;n([j,k,l],function(a){var c,d,e=[],f=Fa?Fa.duration||500:0,g=function(){for(d=e.length;d--;)a[e[d]]&&!a[e[d]].isActive&&(a[e[d]].destroy(),delete a[e[d]])};for(c in a)if(!a[c].isActive)a[c].render(c,!1,0),a[c].isActive=!1,e.push(c);a===l||!b.hasRendered||!f?g():f&&setTimeout(g,f)});if(o)A=a.getLinePath(o),a.axisLine?a.axisLine.animate({d:A}):a.axisLine=c.path(A).attr({stroke:d.lineColor,\"stroke-width\":o,zIndex:7}).add(a.axisGroup),a.axisLine[t?\n\"show\":\"hide\"]();if(h&&t)h[h.isNew?\"attr\":\"animate\"](a.getTitlePosition()),h.isNew=!1;if(m&&m.enabled){var x,la,d=a.stackTotalGroup;if(!d)a.stackTotalGroup=d=c.g(\"stack-labels\").attr({visibility:\"visible\",zIndex:6}).add();d.translate(b.plotLeft,b.plotTop);for(x in i)for(la in c=i[x],c)c[la].render(d)}a.isDirty=!1},removePlotBandOrLine:function(a){for(var b=this.plotLinesAndBands,c=this.options,d=this.userOptions,e=b.length;e--;)b[e].id===a&&b[e].destroy();n([c.plotLines||[],d.plotLines||[],c.plotBands||\n[],d.plotBands||[]],function(b){for(e=b.length;e--;)b[e].id===a&&ga(b,b[e])})},setTitle:function(a,b){this.update({title:a},b)},redraw:function(){var a=this.chart.pointer;a.reset&&a.reset(!0);this.render();n(this.plotLinesAndBands,function(a){a.render()});n(this.series,function(a){a.isDirty=!0})},buildStacks:function(){var a=this.series,b=a.length;if(!this.isXAxis){for(;b--;)a[b].setStackedPoints();if(this.usePercentage)for(b=0;b<a.length;b++)a[b].setPercentStacks()}},setCategories:function(a,b){this.update({categories:a},\nb)},destroy:function(a){var b=this,c=b.stacks,d,e=b.plotLinesAndBands;a||aa(b);for(d in c)Ka(c[d]),c[d]=null;n([b.ticks,b.minorTicks,b.alternateBands],function(a){Ka(a)});for(a=e.length;a--;)e[a].destroy();n(\"stackTotalGroup,axisLine,axisGroup,gridGroup,labelGroup,axisTitle\".split(\",\"),function(a){b[a]&&(b[a]=b[a].destroy())})}};wb.prototype={init:function(a,b){var c=b.borderWidth,d=b.style,e=C(d.padding);this.chart=a;this.options=b;this.crosshairs=[];this.now={x:0,y:0};this.isHidden=!0;this.label=\na.renderer.label(\"\",0,0,b.shape,null,null,b.useHTML,null,\"tooltip\").attr({padding:e,fill:b.backgroundColor,\"stroke-width\":c,r:b.borderRadius,zIndex:8}).css(d).css({padding:0}).add().attr({y:-999});$||this.label.shadow(b.shadow);this.shared=b.shared},destroy:function(){n(this.crosshairs,function(a){a&&a.destroy()});if(this.label)this.label=this.label.destroy();clearTimeout(this.hideTimer);clearTimeout(this.tooltipTimeout)},move:function(a,b,c,d){var e=this,f=e.now,g=e.options.animation!==!1&&!e.isHidden;\nr(f,{x:g?(2*f.x+a)/3:a,y:g?(f.y+b)/2:b,anchorX:g?(2*f.anchorX+c)/3:c,anchorY:g?(f.anchorY+d)/2:d});e.label.attr(f);if(g&&(N(a-f.x)>1||N(b-f.y)>1))clearTimeout(this.tooltipTimeout),this.tooltipTimeout=setTimeout(function(){e&&e.move(a,b,c,d)},32)},hide:function(){var a=this,b;clearTimeout(this.hideTimer);if(!this.isHidden)b=this.chart.hoverPoints,this.hideTimer=setTimeout(function(){a.label.fadeOut();a.isHidden=!0},o(this.options.hideDelay,500)),b&&n(b,function(a){a.setState()}),this.chart.hoverPoints=\nnull},hideCrosshairs:function(){n(this.crosshairs,function(a){a&&a.hide()})},getAnchor:function(a,b){var c,d=this.chart,e=d.inverted,f=d.plotTop,g=0,h=0,i,a=ja(a);c=a[0].tooltipPos;this.followPointer&&b&&(b.chartX===w&&(b=d.pointer.normalize(b)),c=[b.chartX-d.plotLeft,b.chartY-f]);c||(n(a,function(a){i=a.series.yAxis;g+=a.plotX;h+=(a.plotLow?(a.plotLow+a.plotHigh)/2:a.plotY)+(!e&&i?i.top-f:0)}),g/=a.length,h/=a.length,c=[e?d.plotWidth-h:g,this.shared&&!e&&a.length>1&&b?b.chartY-f:e?d.plotHeight-g:\nh]);return Na(c,t)},getPosition:function(a,b,c){var d=this.chart,e=d.plotLeft,f=d.plotTop,g=d.plotWidth,h=d.plotHeight,i=o(this.options.distance,12),j=c.plotX,c=c.plotY,d=j+e+(d.inverted?i:-a-i),k=c-b+f+15,l;d<7&&(d=e+s(j,0)+i);d+a>e+g&&(d-=d+a-(e+g),k=c-b+f-i,l=!0);k<f+5&&(k=f+5,l&&c>=k&&c<=k+b&&(k=c+f+i));k+b>f+h&&(k=s(f,f+h-b-i));return{x:d,y:k}},defaultFormatter:function(a){var b=this.points||ja(this),c=b[0].series,d;d=[c.tooltipHeaderFormatter(b[0])];n(b,function(a){c=a.series;d.push(c.tooltipFormatter&&\nc.tooltipFormatter(a)||a.point.tooltipFormatter(c.tooltipOptions.pointFormat))});d.push(a.options.footerFormat||\"\");return d.join(\"\")},refresh:function(a,b){var c=this.chart,d=this.label,e=this.options,f,g,h={},i,j=[];i=e.formatter||this.defaultFormatter;var h=c.hoverPoints,k,l=e.crosshairs,m=this.shared;clearTimeout(this.hideTimer);this.followPointer=ja(a)[0].series.tooltipOptions.followPointer;g=this.getAnchor(a,b);f=g[0];g=g[1];m&&(!a.series||!a.series.noSharedTooltip)?(c.hoverPoints=a,h&&n(h,\nfunction(a){a.setState()}),n(a,function(a){a.setState(\"hover\");j.push(a.getLabelConfig())}),h={x:a[0].category,y:a[0].y},h.points=j,a=a[0]):h=a.getLabelConfig();i=i.call(h,this);h=a.series;i===!1?this.hide():(this.isHidden&&(Wa(d),d.attr(\"opacity\",1).show()),d.attr({text:i}),k=e.borderColor||a.color||h.color||\"#606060\",d.attr({stroke:k}),this.updatePosition({plotX:f,plotY:g}),this.isHidden=!1);if(l){l=ja(l);for(d=l.length;d--;)if(m=a.series,e=m[d?\"yAxis\":\"xAxis\"],l[d]&&e)if(h=d?o(a.stackY,a.y):a.x,\ne.isLog&&(h=na(h)),d===1&&m.modifyValue&&(h=m.modifyValue(h)),e=e.getPlotLinePath(h,1),this.crosshairs[d])this.crosshairs[d].attr({d:e,visibility:\"visible\"});else{h={\"stroke-width\":l[d].width||1,stroke:l[d].color||\"#C0C0C0\",zIndex:l[d].zIndex||2};if(l[d].dashStyle)h.dashstyle=l[d].dashStyle;this.crosshairs[d]=c.renderer.path(e).attr(h).add()}}z(c,\"tooltipRefresh\",{text:i,x:f+c.plotLeft,y:g+c.plotTop,borderColor:k})},updatePosition:function(a){var b=this.chart,c=this.label,c=(this.options.positioner||\nthis.getPosition).call(this,c.width,c.height,a);this.move(t(c.x),t(c.y),a.plotX+b.plotLeft,a.plotY+b.plotTop)}};xb.prototype={init:function(a,b){var c=b.chart,d=c.events,e=$?\"\":c.zoomType,c=a.inverted,f;this.options=b;this.chart=a;this.zoomX=f=/x/.test(e);this.zoomY=e=/y/.test(e);this.zoomHor=f&&!c||e&&c;this.zoomVert=e&&!c||f&&c;this.runChartClick=d&&!!d.click;this.pinchDown=[];this.lastValidTouch={};if(b.tooltip.enabled)a.tooltip=new wb(a,b.tooltip);this.setDOMEvents()},normalize:function(a,b){var c,\nd,a=a||O.event;if(!a.target)a.target=a.srcElement;a=Xb(a);d=a.touches?a.touches.item(0):a;if(!b)this.chartPosition=b=Wb(this.chart.container);d.pageX===w?(c=s(a.x,a.clientX-b.left),d=a.y):(c=d.pageX-b.left,d=d.pageY-b.top);return r(a,{chartX:t(c),chartY:t(d)})},getCoordinates:function(a){var b={xAxis:[],yAxis:[]};n(this.chart.axes,function(c){b[c.isXAxis?\"xAxis\":\"yAxis\"].push({axis:c,value:c.toValue(a[c.horiz?\"chartX\":\"chartY\"])})});return b},getIndex:function(a){var b=this.chart;return b.inverted?\nb.plotHeight+b.plotTop-a.chartY:a.chartX-b.plotLeft},runPointActions:function(a){var b=this.chart,c=b.series,d=b.tooltip,e,f=b.hoverPoint,g=b.hoverSeries,h,i,j=b.chartWidth,k=this.getIndex(a);if(d&&this.options.tooltip.shared&&(!g||!g.noSharedTooltip)){e=[];h=c.length;for(i=0;i<h;i++)if(c[i].visible&&c[i].options.enableMouseTracking!==!1&&!c[i].noSharedTooltip&&c[i].tooltipPoints.length&&(b=c[i].tooltipPoints[k])&&b.series)b._dist=N(k-b.clientX),j=I(j,b._dist),e.push(b);for(h=e.length;h--;)e[h]._dist>\nj&&e.splice(h,1);if(e.length&&e[0].clientX!==this.hoverX)d.refresh(e,a),this.hoverX=e[0].clientX}if(g&&g.tracker){if((b=g.tooltipPoints[k])&&b!==f)b.onMouseOver(a)}else d&&d.followPointer&&!d.isHidden&&(a=d.getAnchor([{}],a),d.updatePosition({plotX:a[0],plotY:a[1]}))},reset:function(a){var b=this.chart,c=b.hoverSeries,d=b.hoverPoint,e=b.tooltip,b=e&&e.shared?b.hoverPoints:d;(a=a&&e&&b)&&ja(b)[0].plotX===w&&(a=!1);if(a)e.refresh(b);else{if(d)d.onMouseOut();if(c)c.onMouseOut();e&&(e.hide(),e.hideCrosshairs());\nthis.hoverX=null}},scaleGroups:function(a,b){var c=this.chart,d;n(c.series,function(e){d=a||e.getPlotBox();e.xAxis&&e.xAxis.zoomEnabled&&(e.group.attr(d),e.markerGroup&&(e.markerGroup.attr(d),e.markerGroup.clip(b?c.clipRect:null)),e.dataLabelsGroup&&e.dataLabelsGroup.attr(d))});c.clipRect.attr(b||c.clipBox)},pinchTranslateDirection:function(a,b,c,d,e,f,g){var h=this.chart,i=a?\"x\":\"y\",j=a?\"X\":\"Y\",k=\"chart\"+j,l=a?\"width\":\"height\",m=h[\"plot\"+(a?\"Left\":\"Top\")],p,q,o=1,n=h.inverted,s=h.bounds[a?\"h\":\"v\"],\nt=b.length===1,u=b[0][k],r=c[0][k],w=!t&&b[1][k],v=!t&&c[1][k],x,c=function(){!t&&N(u-w)>20&&(o=N(r-v)/N(u-w));q=(m-r)/o+u;p=h[\"plot\"+(a?\"Width\":\"Height\")]/o};c();b=q;b<s.min?(b=s.min,x=!0):b+p>s.max&&(b=s.max-p,x=!0);x?(r-=0.8*(r-g[i][0]),t||(v-=0.8*(v-g[i][1])),c()):g[i]=[r,v];n||(f[i]=q-m,f[l]=p);f=n?1/o:o;e[l]=p;e[i]=b;d[n?a?\"scaleY\":\"scaleX\":\"scale\"+j]=o;d[\"translate\"+j]=f*m+(r-f*u)},pinch:function(a){var b=this,c=b.chart,d=b.pinchDown,e=c.tooltip&&c.tooltip.options.followTouchMove,f=a.touches,\ng=f.length,h=b.lastValidTouch,i=b.zoomHor||b.pinchHor,j=b.zoomVert||b.pinchVert,k=i||j,l=b.selectionMarker,m={},p=g===1&&(b.inClass(a.target,\"highcharts-tracker\")&&c.runTrackerClick||c.runChartClick),q={};(k||e)&&!p&&a.preventDefault();Na(f,function(a){return b.normalize(a)});if(a.type===\"touchstart\")n(f,function(a,b){d[b]={chartX:a.chartX,chartY:a.chartY}}),h.x=[d[0].chartX,d[1]&&d[1].chartX],h.y=[d[0].chartY,d[1]&&d[1].chartY],n(c.axes,function(a){if(a.zoomEnabled){var b=c.bounds[a.horiz?\"h\":\"v\"],\nd=a.minPixelPadding,e=a.toPixels(a.dataMin),f=a.toPixels(a.dataMax),g=I(e,f),e=s(e,f);b.min=I(a.pos,g-d);b.max=s(a.pos+a.len,e+d)}});else if(d.length){if(!l)b.selectionMarker=l=r({destroy:pa},c.plotBox);i&&b.pinchTranslateDirection(!0,d,f,m,l,q,h);j&&b.pinchTranslateDirection(!1,d,f,m,l,q,h);b.hasPinched=k;b.scaleGroups(m,q);!k&&e&&g===1&&this.runPointActions(b.normalize(a))}},dragStart:function(a){var b=this.chart;b.mouseIsDown=a.type;b.cancelClick=!1;b.mouseDownX=this.mouseDownX=a.chartX;b.mouseDownY=\nthis.mouseDownY=a.chartY},drag:function(a){var b=this.chart,c=b.options.chart,d=a.chartX,e=a.chartY,f=this.zoomHor,g=this.zoomVert,h=b.plotLeft,i=b.plotTop,j=b.plotWidth,k=b.plotHeight,l,m=this.mouseDownX,p=this.mouseDownY;d<h?d=h:d>h+j&&(d=h+j);e<i?e=i:e>i+k&&(e=i+k);this.hasDragged=Math.sqrt(Math.pow(m-d,2)+Math.pow(p-e,2));if(this.hasDragged>10){l=b.isInsidePlot(m-h,p-i);if(b.hasCartesianSeries&&(this.zoomX||this.zoomY)&&l&&!this.selectionMarker)this.selectionMarker=b.renderer.rect(h,i,f?1:j,g?\n1:k,0).attr({fill:c.selectionMarkerFill||\"rgba(69,114,167,0.25)\",zIndex:7}).add();this.selectionMarker&&f&&(d-=m,this.selectionMarker.attr({width:N(d),x:(d>0?0:d)+m}));this.selectionMarker&&g&&(d=e-p,this.selectionMarker.attr({height:N(d),y:(d>0?0:d)+p}));l&&!this.selectionMarker&&c.panning&&b.pan(a,c.panning)}},drop:function(a){var b=this.chart,c=this.hasPinched;if(this.selectionMarker){var d={xAxis:[],yAxis:[],originalEvent:a.originalEvent||a},e=this.selectionMarker,f=e.x,g=e.y,h;if(this.hasDragged||\nc)n(b.axes,function(a){if(a.zoomEnabled){var b=a.horiz,c=a.toValue(b?f:g),b=a.toValue(b?f+e.width:g+e.height);!isNaN(c)&&!isNaN(b)&&(d[a.xOrY+\"Axis\"].push({axis:a,min:I(c,b),max:s(c,b)}),h=!0)}}),h&&z(b,\"selection\",d,function(a){b.zoom(r(a,c?{animation:!1}:null))});this.selectionMarker=this.selectionMarker.destroy();c&&this.scaleGroups()}if(b)K(b.container,{cursor:b._cursor}),b.cancelClick=this.hasDragged>10,b.mouseIsDown=this.hasDragged=this.hasPinched=!1,this.pinchDown=[]},onContainerMouseDown:function(a){a=\nthis.normalize(a);a.preventDefault&&a.preventDefault();this.dragStart(a)},onDocumentMouseUp:function(a){this.drop(a)},onDocumentMouseMove:function(a){var b=this.chart,c=this.chartPosition,d=b.hoverSeries,a=this.normalize(a,c);c&&d&&!this.inClass(a.target,\"highcharts-tracker\")&&!b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-b.plotTop)&&this.reset()},onContainerMouseLeave:function(){this.reset();this.chartPosition=null},onContainerMouseMove:function(a){var b=this.chart,a=this.normalize(a);a.returnValue=\n!1;b.mouseIsDown===\"mousedown\"&&this.drag(a);(this.inClass(a.target,\"highcharts-tracker\")||b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-b.plotTop))&&!b.openMenu&&this.runPointActions(a)},inClass:function(a,b){for(var c;a;){if(c=v(a,\"class\"))if(c.indexOf(b)!==-1)return!0;else if(c.indexOf(\"highcharts-container\")!==-1)return!1;a=a.parentNode}},onTrackerMouseOut:function(a){var b=this.chart.hoverSeries;if(b&&!b.options.stickyTracking&&!this.inClass(a.toElement||a.relatedTarget,\"highcharts-tooltip\"))b.onMouseOut()},\nonContainerClick:function(a){var b=this.chart,c=b.hoverPoint,d=b.plotLeft,e=b.plotTop,f=b.inverted,g,h,i,a=this.normalize(a);a.cancelBubble=!0;if(!b.cancelClick)c&&this.inClass(a.target,\"highcharts-tracker\")?(g=this.chartPosition,h=c.plotX,i=c.plotY,r(c,{pageX:g.left+d+(f?b.plotWidth-i:h),pageY:g.top+e+(f?b.plotHeight-h:i)}),z(c.series,\"click\",r(a,{point:c})),b.hoverPoint&&c.firePointEvent(\"click\",a)):(r(a,this.getCoordinates(a)),b.isInsidePlot(a.chartX-d,a.chartY-e)&&z(b,\"click\",a))},onContainerTouchStart:function(a){var b=\nthis.chart;a.touches.length===1?(a=this.normalize(a),b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-b.plotTop)?(this.runPointActions(a),this.pinch(a)):this.reset()):a.touches.length===2&&this.pinch(a)},onContainerTouchMove:function(a){(a.touches.length===1||a.touches.length===2)&&this.pinch(a)},onDocumentTouchEnd:function(a){this.drop(a)},setDOMEvents:function(){var a=this,b=a.chart.container,c;this._events=c=[[b,\"onmousedown\",\"onContainerMouseDown\"],[b,\"onmousemove\",\"onContainerMouseMove\"],[b,\"onclick\",\n\"onContainerClick\"],[b,\"mouseleave\",\"onContainerMouseLeave\"],[y,\"mousemove\",\"onDocumentMouseMove\"],[y,\"mouseup\",\"onDocumentMouseUp\"]];ib&&c.push([b,\"ontouchstart\",\"onContainerTouchStart\"],[b,\"ontouchmove\",\"onContainerTouchMove\"],[y,\"touchend\",\"onDocumentTouchEnd\"]);n(c,function(b){a[\"_\"+b[2]]=function(c){a[b[2]](c)};b[1].indexOf(\"on\")===0?b[0][b[1]]=a[\"_\"+b[2]]:J(b[0],b[1],a[\"_\"+b[2]])})},destroy:function(){var a=this;n(a._events,function(b){b[1].indexOf(\"on\")===0?b[0][b[1]]=null:aa(b[0],b[1],a[\"_\"+\nb[2]])});delete a._events;clearInterval(a.tooltipTimeout)}};eb.prototype={init:function(a,b){var c=this,d=b.itemStyle,e=o(b.padding,8),f=b.itemMarginTop||0;this.options=b;if(b.enabled)c.baseline=C(d.fontSize)+3+f,c.itemStyle=d,c.itemHiddenStyle=x(d,b.itemHiddenStyle),c.itemMarginTop=f,c.padding=e,c.initialItemX=e,c.initialItemY=e-5,c.maxItemWidth=0,c.chart=a,c.itemHeight=0,c.lastLineHeight=0,c.render(),J(c.chart,\"endResize\",function(){c.positionCheckboxes()})},colorizeItem:function(a,b){var c=this.options,\nd=a.legendItem,e=a.legendLine,f=a.legendSymbol,g=this.itemHiddenStyle.color,c=b?c.itemStyle.color:g,h=b?a.color:g,g=a.options&&a.options.marker,i={stroke:h,fill:h},j;d&&d.css({fill:c,color:c});e&&e.attr({stroke:h});if(f){if(g&&f.isMarker)for(j in g=a.convertAttribs(g),g)d=g[j],d!==w&&(i[j]=d);f.attr(i)}},positionItem:function(a){var b=this.options,c=b.symbolPadding,b=!b.rtl,d=a._legendItemPos,e=d[0],d=d[1],f=a.checkbox;a.legendGroup&&a.legendGroup.translate(b?e:this.legendWidth-e-2*c-4,d);if(f)f.x=\ne,f.y=d},destroyItem:function(a){var b=a.checkbox;n([\"legendItem\",\"legendLine\",\"legendSymbol\",\"legendGroup\"],function(b){a[b]&&(a[b]=a[b].destroy())});b&&Ta(a.checkbox)},destroy:function(){var a=this.group,b=this.box;if(b)this.box=b.destroy();if(a)this.group=a.destroy()},positionCheckboxes:function(a){var b=this.group.alignAttr,c,d=this.clipHeight||this.legendHeight;if(b)c=b.translateY,n(this.allItems,function(e){var f=e.checkbox,g;f&&(g=c+f.y+(a||0)+3,K(f,{left:b.translateX+e.legendItemWidth+f.x-\n20+\"px\",top:g+\"px\",display:g>c-6&&g<c+d-6?\"\":S}))})},renderTitle:function(){var a=this.padding,b=this.options.title,c=0;if(b.text){if(!this.title)this.title=this.chart.renderer.label(b.text,a-3,a-4,null,null,null,null,null,\"legend-title\").attr({zIndex:1}).css(b.style).add(this.group);a=this.title.getBBox();c=a.height;this.offsetWidth=a.width;this.contentGroup.attr({translateY:c})}this.titleHeight=c},renderItem:function(a){var B;var b=this,c=b.chart,d=c.renderer,e=b.options,f=e.layout===\"horizontal\",\ng=e.symbolWidth,h=e.symbolPadding,i=b.itemStyle,j=b.itemHiddenStyle,k=b.padding,l=f?o(e.itemDistance,8):0,m=!e.rtl,p=e.width,q=e.itemMarginBottom||0,n=b.itemMarginTop,A=b.initialItemX,t=a.legendItem,u=a.series||a,r=u.options,w=r.showCheckbox,v=e.useHTML;if(!t&&(a.legendGroup=d.g(\"legend-item\").attr({zIndex:1}).add(b.scrollGroup),u.drawLegendSymbol(b,a),a.legendItem=t=d.text(e.labelFormat?Ca(e.labelFormat,a):e.labelFormatter.call(a),m?g+h:-h,b.baseline,v).css(x(a.visible?i:j)).attr({align:m?\"left\":\n\"right\",zIndex:2}).add(a.legendGroup),(v?t:a.legendGroup).on(\"mouseover\",function(){a.setState(\"hover\");t.css(b.options.itemHoverStyle)}).on(\"mouseout\",function(){t.css(a.visible?i:j);a.setState()}).on(\"click\",function(b){var c=function(){a.setVisible()},b={browserEvent:b};a.firePointEvent?a.firePointEvent(\"legendItemClick\",b,c):z(a,\"legendItemClick\",b,c)}),b.colorizeItem(a,a.visible),r&&w))a.checkbox=U(\"input\",{type:\"checkbox\",checked:a.selected,defaultChecked:a.selected},e.itemCheckboxStyle,c.container),\nJ(a.checkbox,\"click\",function(b){z(a,\"checkboxClick\",{checked:b.target.checked},function(){a.select()})});d=t.getBBox();B=a.legendItemWidth=e.itemWidth||g+h+d.width+l+(w?20:0),e=B;b.itemHeight=g=d.height;if(f&&b.itemX-A+e>(p||c.chartWidth-2*k-A))b.itemX=A,b.itemY+=n+b.lastLineHeight+q,b.lastLineHeight=0;b.maxItemWidth=s(b.maxItemWidth,e);b.lastItemY=n+b.itemY+q;b.lastLineHeight=s(g,b.lastLineHeight);a._legendItemPos=[b.itemX,b.itemY];f?b.itemX+=e:(b.itemY+=n+g+q,b.lastLineHeight=g);b.offsetWidth=\np||s((f?b.itemX-A-l:e)+k,b.offsetWidth)},render:function(){var a=this,b=a.chart,c=b.renderer,d=a.group,e,f,g,h,i=a.box,j=a.options,k=a.padding,l=j.borderWidth,m=j.backgroundColor;a.itemX=a.initialItemX;a.itemY=a.initialItemY;a.offsetWidth=0;a.lastItemY=0;if(!d)a.group=d=c.g(\"legend\").attr({zIndex:7}).add(),a.contentGroup=c.g().attr({zIndex:1}).add(d),a.scrollGroup=c.g().add(a.contentGroup);a.renderTitle();e=[];n(b.series,function(a){var b=a.options;b.showInLegend&&!u(b.linkedTo)&&(e=e.concat(a.legendItems||\n(b.legendType===\"point\"?a.data:a)))});Kb(e,function(a,b){return(a.options&&a.options.legendIndex||0)-(b.options&&b.options.legendIndex||0)});j.reversed&&e.reverse();a.allItems=e;a.display=f=!!e.length;n(e,function(b){a.renderItem(b)});g=j.width||a.offsetWidth;h=a.lastItemY+a.lastLineHeight+a.titleHeight;h=a.handleOverflow(h);if(l||m){g+=k;h+=k;if(i){if(g>0&&h>0)i[i.isNew?\"attr\":\"animate\"](i.crisp(null,null,null,g,h)),i.isNew=!1}else a.box=i=c.rect(0,0,g,h,j.borderRadius,l||0).attr({stroke:j.borderColor,\n\"stroke-width\":l||0,fill:m||S}).add(d).shadow(j.shadow),i.isNew=!0;i[f?\"show\":\"hide\"]()}a.legendWidth=g;a.legendHeight=h;n(e,function(b){a.positionItem(b)});f&&d.align(r({width:g,height:h},j),!0,\"spacingBox\");b.isResizing||this.positionCheckboxes()},handleOverflow:function(a){var b=this,c=this.chart,d=c.renderer,e=this.options,f=e.y,f=c.spacingBox.height+(e.verticalAlign===\"top\"?-f:f)-this.padding,g=e.maxHeight,h=this.clipRect,i=e.navigation,j=o(i.animation,!0),k=i.arrowSize||12,l=this.nav;e.layout===\n\"horizontal\"&&(f/=2);g&&(f=I(f,g));if(a>f&&!e.useHTML){this.clipHeight=c=f-20-this.titleHeight;this.pageCount=xa(a/c);this.currentPage=o(this.currentPage,1);this.fullHeight=a;if(!h)h=b.clipRect=d.clipRect(0,0,9999,0),b.contentGroup.clip(h);h.attr({height:c});if(!l)this.nav=l=d.g().attr({zIndex:1}).add(this.group),this.up=d.symbol(\"triangle\",0,0,k,k).on(\"click\",function(){b.scroll(-1,j)}).add(l),this.pager=d.text(\"\",15,10).css(i.style).add(l),this.down=d.symbol(\"triangle-down\",0,0,k,k).on(\"click\",\nfunction(){b.scroll(1,j)}).add(l);b.scroll(0);a=f}else if(l)h.attr({height:c.chartHeight}),l.hide(),this.scrollGroup.attr({translateY:1}),this.clipHeight=0;return a},scroll:function(a,b){var c=this.pageCount,d=this.currentPage+a,e=this.clipHeight,f=this.options.navigation,g=f.activeColor,h=f.inactiveColor,f=this.pager,i=this.padding;d>c&&(d=c);if(d>0)b!==w&&La(b,this.chart),this.nav.attr({translateX:i,translateY:e+7+this.titleHeight,visibility:\"visible\"}),this.up.attr({fill:d===1?h:g}).css({cursor:d===\n1?\"default\":\"pointer\"}),f.attr({text:d+\"/\"+this.pageCount}),this.down.attr({x:18+this.pager.getBBox().width,fill:d===c?h:g}).css({cursor:d===c?\"default\":\"pointer\"}),e=-I(e*(d-1),this.fullHeight-e+i)+1,this.scrollGroup.animate({translateY:e}),f.attr({text:d+\"/\"+c}),this.currentPage=d,this.positionCheckboxes(e)}};/Trident.*?11\\.0/.test(oa)&&mb(eb.prototype,\"positionItem\",function(a,b){var c=this;setTimeout(function(){a.call(c,b)})});yb.prototype={init:function(a,b){var c,d=a.series;a.series=null;c=\nx(M,a);c.series=a.series=d;d=c.chart;this.margin=this.splashArray(\"margin\",d);this.spacing=this.splashArray(\"spacing\",d);var e=d.events;this.bounds={h:{},v:{}};this.callback=b;this.isResizing=0;this.options=c;this.axes=[];this.series=[];this.hasCartesianSeries=d.showAxes;var f=this,g;f.index=Ga.length;Ga.push(f);d.reflow!==!1&&J(f,\"load\",function(){f.initReflow()});if(e)for(g in e)J(f,g,e[g]);f.xAxis=[];f.yAxis=[];f.animation=$?!1:o(d.animation,!0);f.pointCount=0;f.counters=new Jb;f.firstRender()},\ninitSeries:function(a){var b=this.options.chart;(b=W[a.type||b.type||b.defaultSeriesType])||ka(17,!0);b=new b;b.init(this,a);return b},addSeries:function(a,b,c){var d,e=this;a&&(b=o(b,!0),z(e,\"addSeries\",{options:a},function(){d=e.initSeries(a);e.isDirtyLegend=!0;e.linkSeries();b&&e.redraw(c)}));return d},addAxis:function(a,b,c,d){var e=b?\"xAxis\":\"yAxis\",f=this.options;new db(this,x(a,{index:this[e].length,isX:b}));f[e]=ja(f[e]||{});f[e].push(a);o(c,!0)&&this.redraw(d)},isInsidePlot:function(a,b,\nc){var d=c?b:a,a=c?a:b;return d>=0&&d<=this.plotWidth&&a>=0&&a<=this.plotHeight},adjustTickAmounts:function(){this.options.chart.alignTicks!==!1&&n(this.axes,function(a){a.adjustTickAmount()});this.maxTicks=null},redraw:function(a){var b=this.axes,c=this.series,d=this.pointer,e=this.legend,f=this.isDirtyLegend,g,h,i=this.isDirtyBox,j=c.length,k=j,l=this.renderer,m=l.isHidden(),p=[];La(a,this);m&&this.cloneRenderTo();for(this.layOutTitles();k--;)if(a=c[k],a.options.stacking&&(g=!0,a.isDirty)){h=!0;\nbreak}if(h)for(k=j;k--;)if(a=c[k],a.options.stacking)a.isDirty=!0;n(c,function(a){a.isDirty&&a.options.legendType===\"point\"&&(f=!0)});if(f&&e.options.enabled)e.render(),this.isDirtyLegend=!1;g&&this.getStacks();if(this.hasCartesianSeries){if(!this.isResizing)this.maxTicks=null,n(b,function(a){a.setScale()});this.adjustTickAmounts();this.getMargins();n(b,function(a){a.isDirty&&(i=!0)});n(b,function(a){if(a.isDirtyExtremes)a.isDirtyExtremes=!1,p.push(function(){z(a,\"afterSetExtremes\",r(a.eventArgs,\na.getExtremes()));delete a.eventArgs});(i||g)&&a.redraw()})}i&&this.drawChartBox();n(c,function(a){a.isDirty&&a.visible&&(!a.isCartesian||a.xAxis)&&a.redraw()});d&&d.reset&&d.reset(!0);l.draw();z(this,\"redraw\");m&&this.cloneRenderTo(!0);n(p,function(a){a.call()})},showLoading:function(a){var b=this.options,c=this.loadingDiv,d=b.loading;if(!c)this.loadingDiv=c=U(Ea,{className:\"highcharts-loading\"},r(d.style,{zIndex:10,display:S}),this.container),this.loadingSpan=U(\"span\",null,d.labelStyle,c);this.loadingSpan.innerHTML=\na||b.lang.loading;if(!this.loadingShown)K(c,{opacity:0,display:\"\",left:this.plotLeft+\"px\",top:this.plotTop+\"px\",width:this.plotWidth+\"px\",height:this.plotHeight+\"px\"}),Bb(c,{opacity:d.style.opacity},{duration:d.showDuration||0}),this.loadingShown=!0},hideLoading:function(){var a=this.options,b=this.loadingDiv;b&&Bb(b,{opacity:0},{duration:a.loading.hideDuration||100,complete:function(){K(b,{display:S})}});this.loadingShown=!1},get:function(a){var b=this.axes,c=this.series,d,e;for(d=0;d<b.length;d++)if(b[d].options.id===\na)return b[d];for(d=0;d<c.length;d++)if(c[d].options.id===a)return c[d];for(d=0;d<c.length;d++){e=c[d].points||[];for(b=0;b<e.length;b++)if(e[b].id===a)return e[b]}return null},getAxes:function(){var a=this,b=this.options,c=b.xAxis=ja(b.xAxis||{}),b=b.yAxis=ja(b.yAxis||{});n(c,function(a,b){a.index=b;a.isX=!0});n(b,function(a,b){a.index=b});c=c.concat(b);n(c,function(b){new db(a,b)});a.adjustTickAmounts()},getSelectedPoints:function(){var a=[];n(this.series,function(b){a=a.concat(ub(b.points||[],\nfunction(a){return a.selected}))});return a},getSelectedSeries:function(){return ub(this.series,function(a){return a.selected})},getStacks:function(){var a=this;n(a.yAxis,function(a){if(a.stacks&&a.hasVisibleSeries)a.oldStacks=a.stacks});n(a.series,function(b){if(b.options.stacking&&(b.visible===!0||a.options.chart.ignoreHiddenSeries===!1))b.stackKey=b.type+o(b.options.stack,\"\")})},showResetZoom:function(){var a=this,b=M.lang,c=a.options.chart.resetZoomButton,d=c.theme,e=d.states,f=c.relativeTo===\n\"chart\"?null:\"plotBox\";this.resetZoomButton=a.renderer.button(b.resetZoom,null,null,function(){a.zoomOut()},d,e&&e.hover).attr({align:c.position.align,title:b.resetZoomTitle}).add().align(c.position,!1,f)},zoomOut:function(){var a=this;z(a,\"selection\",{resetSelection:!0},function(){a.zoom()})},zoom:function(a){var b,c=this.pointer,d=!1,e;!a||a.resetSelection?n(this.axes,function(a){b=a.zoom()}):n(a.xAxis.concat(a.yAxis),function(a){var e=a.axis,h=e.isXAxis;if(c[h?\"zoomX\":\"zoomY\"]||c[h?\"pinchX\":\"pinchY\"])b=\ne.zoom(a.min,a.max),e.displayBtn&&(d=!0)});e=this.resetZoomButton;if(d&&!e)this.showResetZoom();else if(!d&&T(e))this.resetZoomButton=e.destroy();b&&this.redraw(o(this.options.chart.animation,a&&a.animation,this.pointCount<100))},pan:function(a,b){var c=this,d=c.hoverPoints,e;d&&n(d,function(a){a.setState()});n(b===\"xy\"?[1,0]:[1],function(b){var d=a[b?\"chartX\":\"chartY\"],h=c[b?\"xAxis\":\"yAxis\"][0],i=c[b?\"mouseDownX\":\"mouseDownY\"],j=(h.pointRange||0)/2,k=h.getExtremes(),l=h.toValue(i-d,!0)+j,i=h.toValue(i+\nc[b?\"plotWidth\":\"plotHeight\"]-d,!0)-j;h.series.length&&l>I(k.dataMin,k.min)&&i<s(k.dataMax,k.max)&&(h.setExtremes(l,i,!1,!1,{trigger:\"pan\"}),e=!0);c[b?\"mouseDownX\":\"mouseDownY\"]=d});e&&c.redraw(!1);K(c.container,{cursor:\"move\"})},setTitle:function(a,b){var f;var c=this,d=c.options,e;e=d.title=x(d.title,a);f=d.subtitle=x(d.subtitle,b),d=f;n([[\"title\",a,e],[\"subtitle\",b,d]],function(a){var b=a[0],d=c[b],e=a[1],a=a[2];d&&e&&(c[b]=d=d.destroy());a&&a.text&&!d&&(c[b]=c.renderer.text(a.text,0,0,a.useHTML).attr({align:a.align,\n\"class\":\"highcharts-\"+b,zIndex:a.zIndex||4}).css(a.style).add())});c.layOutTitles()},layOutTitles:function(){var a=0,b=this.title,c=this.subtitle,d=this.options,e=d.title,d=d.subtitle,f=this.spacingBox.width-44;if(b&&(b.css({width:(e.width||f)+\"px\"}).align(r({y:15},e),!1,\"spacingBox\"),!e.floating&&!e.verticalAlign))a=b.getBBox().height,a>=18&&a<=25&&(a=15);c&&(c.css({width:(d.width||f)+\"px\"}).align(r({y:a+e.margin},d),!1,\"spacingBox\"),!d.floating&&!d.verticalAlign&&(a=xa(a+c.getBBox().height)));this.titleOffset=\na},getChartSize:function(){var a=this.options.chart,b=this.renderToClone||this.renderTo;this.containerWidth=jb(b,\"width\");this.containerHeight=jb(b,\"height\");this.chartWidth=s(0,a.width||this.containerWidth||600);this.chartHeight=s(0,o(a.height,this.containerHeight>19?this.containerHeight:400))},cloneRenderTo:function(a){var b=this.renderToClone,c=this.container;a?b&&(this.renderTo.appendChild(c),Ta(b),delete this.renderToClone):(c&&c.parentNode===this.renderTo&&this.renderTo.removeChild(c),this.renderToClone=\nb=this.renderTo.cloneNode(0),K(b,{position:\"absolute\",top:\"-9999px\",display:\"block\"}),y.body.appendChild(b),c&&b.appendChild(c))},getContainer:function(){var a,b=this.options.chart,c,d,e;this.renderTo=a=b.renderTo;e=\"highcharts-\"+zb++;if(ea(a))this.renderTo=a=y.getElementById(a);a||ka(13,!0);c=C(v(a,\"data-highcharts-chart\"));!isNaN(c)&&Ga[c]&&Ga[c].destroy();v(a,\"data-highcharts-chart\",this.index);a.innerHTML=\"\";a.offsetWidth||this.cloneRenderTo();this.getChartSize();c=this.chartWidth;d=this.chartHeight;\nthis.container=a=U(Ea,{className:\"highcharts-container\"+(b.className?\" \"+b.className:\"\"),id:e},r({position:\"relative\",overflow:\"hidden\",width:c+\"px\",height:d+\"px\",textAlign:\"left\",lineHeight:\"normal\",zIndex:0,\"-webkit-tap-highlight-color\":\"rgba(0,0,0,0)\"},b.style),this.renderToClone||a);this._cursor=a.style.cursor;this.renderer=b.forExport?new Ha(a,c,d,!0):new Va(a,c,d);$&&this.renderer.create(this,a,c,d)},getMargins:function(){var a=this.spacing,b,c=this.legend,d=this.margin,e=this.options.legend,\nf=o(e.margin,10),g=e.x,h=e.y,i=e.align,j=e.verticalAlign,k=this.titleOffset;this.resetMargins();b=this.axisOffset;if(k&&!u(d[0]))this.plotTop=s(this.plotTop,k+this.options.title.margin+a[0]);if(c.display&&!e.floating)if(i===\"right\"){if(!u(d[1]))this.marginRight=s(this.marginRight,c.legendWidth-g+f+a[1])}else if(i===\"left\"){if(!u(d[3]))this.plotLeft=s(this.plotLeft,c.legendWidth+g+f+a[3])}else if(j===\"top\"){if(!u(d[0]))this.plotTop=s(this.plotTop,c.legendHeight+h+f+a[0])}else if(j===\"bottom\"&&!u(d[2]))this.marginBottom=\ns(this.marginBottom,c.legendHeight-h+f+a[2]);this.extraBottomMargin&&(this.marginBottom+=this.extraBottomMargin);this.extraTopMargin&&(this.plotTop+=this.extraTopMargin);this.hasCartesianSeries&&n(this.axes,function(a){a.getOffset()});u(d[3])||(this.plotLeft+=b[3]);u(d[0])||(this.plotTop+=b[0]);u(d[2])||(this.marginBottom+=b[2]);u(d[1])||(this.marginRight+=b[1]);this.setChartSize()},initReflow:function(){function a(a){var g=c.width||jb(d,\"width\"),h=c.height||jb(d,\"height\"),a=a?a.target:O;if(!b.hasUserSize&&\ng&&h&&(a===O||a===y)){if(g!==b.containerWidth||h!==b.containerHeight)clearTimeout(e),b.reflowTimeout=e=setTimeout(function(){if(b.container)b.setSize(g,h,!1),b.hasUserSize=null},100);b.containerWidth=g;b.containerHeight=h}}var b=this,c=b.options.chart,d=b.renderTo,e;b.reflow=a;J(O,\"resize\",a);J(b,\"destroy\",function(){aa(O,\"resize\",a)})},setSize:function(a,b,c){var d=this,e,f,g;d.isResizing+=1;g=function(){d&&z(d,\"endResize\",null,function(){d.isResizing-=1})};La(c,d);d.oldChartHeight=d.chartHeight;\nd.oldChartWidth=d.chartWidth;if(u(a))d.chartWidth=e=s(0,t(a)),d.hasUserSize=!!e;if(u(b))d.chartHeight=f=s(0,t(b));K(d.container,{width:e+\"px\",height:f+\"px\"});d.setChartSize(!0);d.renderer.setSize(e,f,c);d.maxTicks=null;n(d.axes,function(a){a.isDirty=!0;a.setScale()});n(d.series,function(a){a.isDirty=!0});d.isDirtyLegend=!0;d.isDirtyBox=!0;d.getMargins();d.redraw(c);d.oldChartHeight=null;z(d,\"resize\");Fa===!1?g():setTimeout(g,Fa&&Fa.duration||500)},setChartSize:function(a){var b=this.inverted,c=this.renderer,\nd=this.chartWidth,e=this.chartHeight,f=this.options.chart,g=this.spacing,h=this.clipOffset,i,j,k,l;this.plotLeft=i=t(this.plotLeft);this.plotTop=j=t(this.plotTop);this.plotWidth=k=s(0,t(d-i-this.marginRight));this.plotHeight=l=s(0,t(e-j-this.marginBottom));this.plotSizeX=b?l:k;this.plotSizeY=b?k:l;this.plotBorderWidth=f.plotBorderWidth||0;this.spacingBox=c.spacingBox={x:g[3],y:g[0],width:d-g[3]-g[1],height:e-g[0]-g[2]};this.plotBox=c.plotBox={x:i,y:j,width:k,height:l};d=2*P(this.plotBorderWidth/2);\nb=xa(s(d,h[3])/2);c=xa(s(d,h[0])/2);this.clipBox={x:b,y:c,width:P(this.plotSizeX-s(d,h[1])/2-b),height:P(this.plotSizeY-s(d,h[2])/2-c)};a||n(this.axes,function(a){a.setAxisSize();a.setAxisTranslation()})},resetMargins:function(){var a=this.spacing,b=this.margin;this.plotTop=o(b[0],a[0]);this.marginRight=o(b[1],a[1]);this.marginBottom=o(b[2],a[2]);this.plotLeft=o(b[3],a[3]);this.axisOffset=[0,0,0,0];this.clipOffset=[0,0,0,0]},drawChartBox:function(){var a=this.options.chart,b=this.renderer,c=this.chartWidth,\nd=this.chartHeight,e=this.chartBackground,f=this.plotBackground,g=this.plotBorder,h=this.plotBGImage,i=a.borderWidth||0,j=a.backgroundColor,k=a.plotBackgroundColor,l=a.plotBackgroundImage,m=a.plotBorderWidth||0,p,q=this.plotLeft,o=this.plotTop,n=this.plotWidth,s=this.plotHeight,t=this.plotBox,u=this.clipRect,r=this.clipBox;p=i+(a.shadow?8:0);if(i||j)if(e)e.animate(e.crisp(null,null,null,c-p,d-p));else{e={fill:j||S};if(i)e.stroke=a.borderColor,e[\"stroke-width\"]=i;this.chartBackground=b.rect(p/2,p/\n2,c-p,d-p,a.borderRadius,i).attr(e).add().shadow(a.shadow)}if(k)f?f.animate(t):this.plotBackground=b.rect(q,o,n,s,0).attr({fill:k}).add().shadow(a.plotShadow);if(l)h?h.animate(t):this.plotBGImage=b.image(l,q,o,n,s).add();u?u.animate({width:r.width,height:r.height}):this.clipRect=b.clipRect(r);if(m)g?g.animate(g.crisp(null,q,o,n,s)):this.plotBorder=b.rect(q,o,n,s,0,-m).attr({stroke:a.plotBorderColor,\"stroke-width\":m,zIndex:1}).add();this.isDirtyBox=!1},propFromSeries:function(){var a=this,b=a.options.chart,\nc,d=a.options.series,e,f;n([\"inverted\",\"angular\",\"polar\"],function(g){c=W[b.type||b.defaultSeriesType];f=a[g]||b[g]||c&&c.prototype[g];for(e=d&&d.length;!f&&e--;)(c=W[d[e].type])&&c.prototype[g]&&(f=!0);a[g]=f})},linkSeries:function(){var a=this,b=a.series;n(b,function(a){a.linkedSeries.length=0});n(b,function(b){var d=b.options.linkedTo;if(ea(d)&&(d=d===\":previous\"?a.series[b.index-1]:a.get(d)))d.linkedSeries.push(b),b.linkedParent=d})},render:function(){var a=this,b=a.axes,c=a.renderer,d=a.options,\ne=d.labels,f=d.credits,g;a.setTitle();a.legend=new eb(a,d.legend);a.getStacks();n(b,function(a){a.setScale()});a.getMargins();a.maxTicks=null;n(b,function(a){a.setTickPositions(!0);a.setMaxTicks()});a.adjustTickAmounts();a.getMargins();a.drawChartBox();a.hasCartesianSeries&&n(b,function(a){a.render()});if(!a.seriesGroup)a.seriesGroup=c.g(\"series-group\").attr({zIndex:3}).add();n(a.series,function(a){a.translate();a.setTooltipPoints();a.render()});e.items&&n(e.items,function(b){var d=r(e.style,b.style),\nf=C(d.left)+a.plotLeft,g=C(d.top)+a.plotTop+12;delete d.left;delete d.top;c.text(b.html,f,g).attr({zIndex:2}).css(d).add()});if(f.enabled&&!a.credits)g=f.href,a.credits=c.text(f.text,0,0).on(\"click\",function(){if(g)location.href=g}).attr({align:f.position.align,zIndex:8}).css(f.style).add().align(f.position);a.hasRendered=!0},destroy:function(){var a=this,b=a.axes,c=a.series,d=a.container,e,f=d&&d.parentNode;z(a,\"destroy\");Ga[a.index]=w;a.renderTo.removeAttribute(\"data-highcharts-chart\");aa(a);for(e=\nb.length;e--;)b[e]=b[e].destroy();for(e=c.length;e--;)c[e]=c[e].destroy();n(\"title,subtitle,chartBackground,plotBackground,plotBGImage,plotBorder,seriesGroup,clipRect,credits,pointer,scroller,rangeSelector,legend,resetZoomButton,tooltip,renderer\".split(\",\"),function(b){var c=a[b];c&&c.destroy&&(a[b]=c.destroy())});if(d)d.innerHTML=\"\",aa(d),f&&Ta(d);for(e in a)delete a[e]},isReadyToRender:function(){var a=this;return!Z&&O==O.top&&y.readyState!==\"complete\"||$&&!O.canvg?($?Tb.push(function(){a.firstRender()},\na.options.global.canvasToolsURL):y.attachEvent(\"onreadystatechange\",function(){y.detachEvent(\"onreadystatechange\",a.firstRender);y.readyState===\"complete\"&&a.firstRender()}),!1):!0},firstRender:function(){var a=this,b=a.options,c=a.callback;if(a.isReadyToRender())a.getContainer(),z(a,\"init\"),a.resetMargins(),a.setChartSize(),a.propFromSeries(),a.getAxes(),n(b.series||[],function(b){a.initSeries(b)}),a.linkSeries(),z(a,\"beforeRender\"),a.pointer=new xb(a,b),a.render(),a.renderer.draw(),c&&c.apply(a,\n[a]),n(a.callbacks,function(b){b.apply(a,[a])}),a.cloneRenderTo(!0),z(a,\"load\")},splashArray:function(a,b){var c=b[a],c=T(c)?c:[c,c,c,c];return[o(b[a+\"Top\"],c[0]),o(b[a+\"Right\"],c[1]),o(b[a+\"Bottom\"],c[2]),o(b[a+\"Left\"],c[3])]}};yb.prototype.callbacks=[];var Pa=function(){};Pa.prototype={init:function(a,b,c){this.series=a;this.applyOptions(b,c);this.pointAttr={};if(a.options.colorByPoint&&(b=a.options.colors||a.chart.options.colors,this.color=this.color||b[a.colorCounter++],a.colorCounter===b.length))a.colorCounter=\n0;a.chart.pointCount++;return this},applyOptions:function(a,b){var c=this.series,d=c.pointValKey,a=Pa.prototype.optionsToObject.call(this,a);r(this,a);this.options=this.options?r(this.options,a):a;if(d)this.y=this[d];if(this.x===w&&c)this.x=b===w?c.autoIncrement():b;return this},optionsToObject:function(a){var b,c=this.series,d=c.pointArrayMap||[\"y\"],e=d.length,f=0,g=0;if(typeof a===\"number\"||a===null)b={y:a};else if(Ia(a)){b={};if(a.length>e){c=typeof a[0];if(c===\"string\")b.name=a[0];else if(c===\n\"number\")b.x=a[0];f++}for(;g<e;)b[d[g++]]=a[f++]}else if(typeof a===\"object\"){b=a;if(a.dataLabels)c._hasPointLabels=!0;if(a.marker)c._hasPointMarkers=!0}return b},destroy:function(){var a=this.series.chart,b=a.hoverPoints,c;a.pointCount--;if(b&&(this.setState(),ga(b,this),!b.length))a.hoverPoints=null;if(this===a.hoverPoint)this.onMouseOut();if(this.graphic||this.dataLabel)aa(this),this.destroyElements();this.legendItem&&a.legend.destroyItem(this);for(c in this)this[c]=null},destroyElements:function(){for(var a=\n\"graphic,dataLabel,dataLabelUpper,group,connector,shadowGroup\".split(\",\"),b,c=6;c--;)b=a[c],this[b]&&(this[b]=this[b].destroy())},getLabelConfig:function(){return{x:this.category,y:this.y,key:this.name||this.category,series:this.series,point:this,percentage:this.percentage,total:this.total||this.stackTotal}},select:function(a,b){var c=this,d=c.series,e=d.chart,a=o(a,!c.selected);c.firePointEvent(a?\"select\":\"unselect\",{accumulate:b},function(){c.selected=c.options.selected=a;d.options.data[qa(c,d.data)]=\nc.options;c.setState(a&&\"select\");b||n(e.getSelectedPoints(),function(a){if(a.selected&&a!==c)a.selected=a.options.selected=!1,d.options.data[qa(a,d.data)]=a.options,a.setState(\"\"),a.firePointEvent(\"unselect\")})})},onMouseOver:function(a){var b=this.series,c=b.chart,d=c.tooltip,e=c.hoverPoint;if(e&&e!==this)e.onMouseOut();this.firePointEvent(\"mouseOver\");d&&(!d.shared||b.noSharedTooltip)&&d.refresh(this,a);this.setState(\"hover\");c.hoverPoint=this},onMouseOut:function(){var a=this.series.chart,b=a.hoverPoints;\nif(!b||qa(this,b)===-1)this.firePointEvent(\"mouseOut\"),this.setState(),a.hoverPoint=null},tooltipFormatter:function(a){var b=this.series,c=b.tooltipOptions,d=o(c.valueDecimals,\"\"),e=c.valuePrefix||\"\",f=c.valueSuffix||\"\";n(b.pointArrayMap||[\"y\"],function(b){b=\"{point.\"+b;if(e||f)a=a.replace(b+\"}\",e+b+\"}\"+f);a=a.replace(b+\"}\",b+\":,.\"+d+\"f}\")});return Ca(a,{point:this,series:this.series})},update:function(a,b,c){var d=this,e=d.series,f=d.graphic,g,h=e.data,i=e.chart,j=e.options,b=o(b,!0);d.firePointEvent(\"update\",\n{options:a},function(){d.applyOptions(a);if(T(a)&&(e.getAttribs(),f))a.marker&&a.marker.symbol?d.graphic=f.destroy():f.attr(d.pointAttr[d.state||\"\"]);g=qa(d,h);e.xData[g]=d.x;e.yData[g]=e.toYData?e.toYData(d):d.y;e.zData[g]=d.z;j.data[g]=d.options;e.isDirty=e.isDirtyData=!0;if(!e.fixedBox&&e.hasCartesianSeries)i.isDirtyBox=!0;j.legendType===\"point\"&&i.legend.destroyItem(d);b&&i.redraw(c)})},remove:function(a,b){var c=this,d=c.series,e=d.points,f=d.chart,g,h=d.data;La(b,f);a=o(a,!0);c.firePointEvent(\"remove\",\nnull,function(){g=qa(c,h);h.length===e.length&&e.splice(g,1);h.splice(g,1);d.options.data.splice(g,1);d.xData.splice(g,1);d.yData.splice(g,1);d.zData.splice(g,1);c.destroy();d.isDirty=!0;d.isDirtyData=!0;a&&f.redraw()})},firePointEvent:function(a,b,c){var d=this,e=this.series.options;(e.point.events[a]||d.options&&d.options.events&&d.options.events[a])&&this.importEvents();a===\"click\"&&e.allowPointSelect&&(c=function(a){d.select(null,a.ctrlKey||a.metaKey||a.shiftKey)});z(this,a,b,c)},importEvents:function(){if(!this.hasImportedEvents){var a=\nx(this.series.options.point,this.options).events,b;this.events=a;for(b in a)J(this,b,a[b]);this.hasImportedEvents=!0}},setState:function(a){var b=this.plotX,c=this.plotY,d=this.series,e=d.options.states,f=Y[d.type].marker&&d.options.marker,g=f&&!f.enabled,h=f&&f.states[a],i=h&&h.enabled===!1,j=d.stateMarkerGraphic,k=this.marker||{},l=d.chart,m=this.pointAttr,a=a||\"\";if(!(a===this.state||this.selected&&a!==\"select\"||e[a]&&e[a].enabled===!1||a&&(i||g&&!h.enabled))){if(this.graphic)e=f&&this.graphic.symbolName&&\nm[a].r,this.graphic.attr(x(m[a],e?{x:b-e,y:c-e,width:2*e,height:2*e}:{}));else{if(a&&h)e=h.radius,k=k.symbol||d.symbol,j&&j.currentSymbol!==k&&(j=j.destroy()),j?j.attr({x:b-e,y:c-e}):(d.stateMarkerGraphic=j=l.renderer.symbol(k,b-e,c-e,2*e,2*e).attr(m[a]).add(d.markerGroup),j.currentSymbol=k);if(j)j[a&&l.isInsidePlot(b,c)?\"show\":\"hide\"]()}this.state=a}}};var Q=function(){};Q.prototype={isCartesian:!0,type:\"line\",pointClass:Pa,sorted:!0,requireSorting:!0,pointAttrToOptions:{stroke:\"lineColor\",\"stroke-width\":\"lineWidth\",\nfill:\"fillColor\",r:\"radius\"},colorCounter:0,init:function(a,b){var c,d,e=a.series;this.chart=a;this.options=b=this.setOptions(b);this.linkedSeries=[];this.bindAxes();r(this,{name:b.name,state:\"\",pointAttr:{},visible:b.visible!==!1,selected:b.selected===!0});if($)b.animation=!1;d=b.events;for(c in d)J(this,c,d[c]);if(d&&d.click||b.point&&b.point.events&&b.point.events.click||b.allowPointSelect)a.runTrackerClick=!0;this.getColor();this.getSymbol();this.setData(b.data,!1);if(this.isCartesian)a.hasCartesianSeries=\n!0;e.push(this);this._i=e.length-1;Kb(e,function(a,b){return o(a.options.index,a._i)-o(b.options.index,a._i)});n(e,function(a,b){a.index=b;a.name=a.name||\"Series \"+(b+1)})},bindAxes:function(){var a=this,b=a.options,c=a.chart,d;a.isCartesian&&n([\"xAxis\",\"yAxis\"],function(e){n(c[e],function(c){d=c.options;if(b[e]===d.index||b[e]!==w&&b[e]===d.id||b[e]===w&&d.index===0)c.series.push(a),a[e]=c,c.isDirty=!0});a[e]||ka(18,!0)})},autoIncrement:function(){var a=this.options,b=this.xIncrement,b=o(b,a.pointStart,\n0);this.pointInterval=o(this.pointInterval,a.pointInterval,1);this.xIncrement=b+this.pointInterval;return b},getSegments:function(){var a=-1,b=[],c,d=this.points,e=d.length;if(e)if(this.options.connectNulls){for(c=e;c--;)d[c].y===null&&d.splice(c,1);d.length&&(b=[d])}else n(d,function(c,g){c.y===null?(g>a+1&&b.push(d.slice(a+1,g)),a=g):g===e-1&&b.push(d.slice(a+1,g+1))});this.segments=b},setOptions:function(a){var b=this.chart.options,c=b.plotOptions,d=c[this.type];this.userOptions=a;a=x(d,c.series,\na);this.tooltipOptions=x(b.tooltip,a.tooltip);d.marker===null&&delete a.marker;return a},getColor:function(){var a=this.options,b=this.userOptions,c=this.chart.options.colors,d=this.chart.counters,e;e=a.color||Y[this.type].color;if(!e&&!a.colorByPoint)u(b._colorIndex)?a=b._colorIndex:(b._colorIndex=d.color,a=d.color++),e=c[a];this.color=e;d.wrapColor(c.length)},getSymbol:function(){var a=this.userOptions,b=this.options.marker,c=this.chart,d=c.options.symbols,c=c.counters;this.symbol=b.symbol;if(!this.symbol)u(a._symbolIndex)?\na=a._symbolIndex:(a._symbolIndex=c.symbol,a=c.symbol++),this.symbol=d[a];if(/^url/.test(this.symbol))b.radius=0;c.wrapSymbol(d.length)},drawLegendSymbol:function(a){var b=this.options,c=b.marker,d=a.options,e;e=d.symbolWidth;var f=this.chart.renderer,g=this.legendGroup,a=a.baseline-t(f.fontMetrics(d.itemStyle.fontSize).b*0.3);if(b.lineWidth){d={\"stroke-width\":b.lineWidth};if(b.dashStyle)d.dashstyle=b.dashStyle;this.legendLine=f.path([\"M\",0,a,\"L\",e,a]).attr(d).add(g)}if(c&&c.enabled)b=c.radius,this.legendSymbol=\ne=f.symbol(this.symbol,e/2-b,a-b,2*b,2*b).add(g),e.isMarker=!0},addPoint:function(a,b,c,d){var e=this.options,f=this.data,g=this.graph,h=this.area,i=this.chart,j=this.xData,k=this.yData,l=this.zData,m=this.names,p=g&&g.shift||0,q=e.data,s;La(d,i);c&&n([g,h,this.graphNeg,this.areaNeg],function(a){if(a)a.shift=p+1});if(h)h.isArea=!0;b=o(b,!0);d={series:this};this.pointClass.prototype.applyOptions.apply(d,[a]);g=d.x;h=j.length;if(this.requireSorting&&g<j[h-1])for(s=!0;h&&j[h-1]>g;)h--;j.splice(h,0,g);\nk.splice(h,0,this.toYData?this.toYData(d):d.y);l.splice(h,0,d.z);if(m)m[g]=d.name;q.splice(h,0,a);s&&(this.data.splice(h,0,null),this.processData());e.legendType===\"point\"&&this.generatePoints();c&&(f[0]&&f[0].remove?f[0].remove(!1):(f.shift(),j.shift(),k.shift(),l.shift(),q.shift()));this.isDirtyData=this.isDirty=!0;b&&(this.getAttribs(),i.redraw())},setData:function(a,b){var c=this.points,d=this.options,e=this.chart,f=null,g=this.xAxis,h=g&&g.categories&&!g.categories.length?[]:null,i;this.xIncrement=\nnull;this.pointRange=g&&g.categories?1:d.pointRange;this.colorCounter=0;var j=[],k=[],l=[],m=a?a.length:[];i=o(d.turboThreshold,1E3);var p=this.pointArrayMap,p=p&&p.length,q=!!this.toYData;if(i&&m>i){for(i=0;f===null&&i<m;)f=a[i],i++;if(sa(f)){f=o(d.pointStart,0);d=o(d.pointInterval,1);for(i=0;i<m;i++)j[i]=f,k[i]=a[i],f+=d;this.xIncrement=f}else if(Ia(f))if(p)for(i=0;i<m;i++)d=a[i],j[i]=d[0],k[i]=d.slice(1,p+1);else for(i=0;i<m;i++)d=a[i],j[i]=d[0],k[i]=d[1];else ka(12)}else for(i=0;i<m;i++)if(a[i]!==\nw&&(d={series:this},this.pointClass.prototype.applyOptions.apply(d,[a[i]]),j[i]=d.x,k[i]=q?this.toYData(d):d.y,l[i]=d.z,h&&d.name))h[d.x]=d.name;ea(k[0])&&ka(14,!0);this.data=[];this.options.data=a;this.xData=j;this.yData=k;this.zData=l;this.names=h;for(i=c&&c.length||0;i--;)c[i]&&c[i].destroy&&c[i].destroy();if(g)g.minRange=g.userMinRange;this.isDirty=this.isDirtyData=e.isDirtyBox=!0;o(b,!0)&&e.redraw(!1)},remove:function(a,b){var c=this,d=c.chart,a=o(a,!0);if(!c.isRemoving)c.isRemoving=!0,z(c,\"remove\",\nnull,function(){c.destroy();d.isDirtyLegend=d.isDirtyBox=!0;d.linkSeries();a&&d.redraw(b)});c.isRemoving=!1},processData:function(a){var b=this.xData,c=this.yData,d=b.length,e;e=0;var f,g,h=this.xAxis,i=this.options,j=i.cropThreshold,k=this.isCartesian;if(k&&!this.isDirty&&!h.isDirty&&!this.yAxis.isDirty&&!a)return!1;if(k&&this.sorted&&(!j||d>j||this.forceCrop))if(a=h.min,h=h.max,b[d-1]<a||b[0]>h)b=[],c=[];else if(b[0]<a||b[d-1]>h)e=this.cropData(this.xData,this.yData,a,h),b=e.xData,c=e.yData,e=e.start,\nf=!0;for(h=b.length-1;h>=0;h--)d=b[h]-b[h-1],d>0&&(g===w||d<g)?g=d:d<0&&this.requireSorting&&ka(15);this.cropped=f;this.cropStart=e;this.processedXData=b;this.processedYData=c;if(i.pointRange===null)this.pointRange=g||1;this.closestPointRange=g},cropData:function(a,b,c,d){var e=a.length,f=0,g=e,h=o(this.cropShoulder,1),i;for(i=0;i<e;i++)if(a[i]>=c){f=s(0,i-h);break}for(;i<e;i++)if(a[i]>d){g=i+h;break}return{xData:a.slice(f,g),yData:b.slice(f,g),start:f,end:g}},generatePoints:function(){var a=this.options.data,\nb=this.data,c,d=this.processedXData,e=this.processedYData,f=this.pointClass,g=d.length,h=this.cropStart||0,i,j=this.hasGroupedData,k,l=[],m;if(!b&&!j)b=[],b.length=a.length,b=this.data=b;for(m=0;m<g;m++)i=h+m,j?l[m]=(new f).init(this,[d[m]].concat(ja(e[m]))):(b[i]?k=b[i]:a[i]!==w&&(b[i]=k=(new f).init(this,a[i],d[m])),l[m]=k);if(b&&(g!==(c=b.length)||j))for(m=0;m<c;m++)if(m===h&&!j&&(m+=g),b[m])b[m].destroyElements(),b[m].plotX=w;this.data=b;this.points=l},setStackedPoints:function(){if(this.options.stacking&&\n!(this.visible!==!0&&this.chart.options.chart.ignoreHiddenSeries!==!1)){var a=this.processedXData,b=this.processedYData,c=[],d=b.length,e=this.options,f=e.threshold,g=e.stack,e=e.stacking,h=this.stackKey,i=\"-\"+h,j=this.negStacks,k=this.yAxis,l=k.stacks,m=k.oldStacks,p,q,o,n,t;for(o=0;o<d;o++){n=a[o];t=b[o];q=(p=j&&t<f)?i:h;l[q]||(l[q]={});if(!l[q][n])m[q]&&m[q][n]?(l[q][n]=m[q][n],l[q][n].total=null):l[q][n]=new Mb(k,k.options.stackLabels,p,n,g,e);q=l[q][n];q.points[this.index]=[q.cum||0];e===\"percent\"?\n(p=p?h:i,j&&l[p]&&l[p][n]?(p=l[p][n],q.total=p.total=s(p.total,q.total)+N(t)||0):q.total+=N(t)||0):q.total+=t||0;q.cum=(q.cum||0)+(t||0);q.points[this.index].push(q.cum);c[o]=q.cum}if(e===\"percent\")k.usePercentage=!0;this.stackedYData=c;k.oldStacks={}}},setPercentStacks:function(){var a=this,b=a.stackKey,c=a.yAxis.stacks;n([b,\"-\"+b],function(b){var d;for(var e=a.xData.length,f,g;e--;)if(f=a.xData[e],d=(g=c[b]&&c[b][f])&&g.points[a.index],f=d)g=g.total?100/g.total:0,f[0]=ia(f[0]*g),f[1]=ia(f[1]*g),\na.stackedYData[e]=f[1]})},getExtremes:function(){var a=this.yAxis,b=this.processedXData,c=this.stackedYData||this.processedYData,d=c.length,e=[],f=0,g=this.xAxis.getExtremes(),h=g.min,g=g.max,i,j,k,l;for(l=0;l<d;l++)if(j=b[l],k=c[l],i=k!==null&&k!==w&&(!a.isLog||k.length||k>0),j=this.getExtremesFromAll||this.cropped||(b[l+1]||j)>=h&&(b[l-1]||j)<=g,i&&j)if(i=k.length)for(;i--;)k[i]!==null&&(e[f++]=k[i]);else e[f++]=k;this.dataMin=o(void 0,Ja(e));this.dataMax=o(void 0,va(e))},translate:function(){this.processedXData||\nthis.processData();this.generatePoints();for(var a=this.options,b=a.stacking,c=this.xAxis,d=c.categories,e=this.yAxis,f=this.points,g=f.length,h=!!this.modifyValue,i=a.pointPlacement,j=i===\"between\"||sa(i),k=a.threshold,a=0;a<g;a++){var l=f[a],m=l.x,p=l.y,q=l.low,n=e.stacks[(this.negStacks&&p<k?\"-\":\"\")+this.stackKey];if(e.isLog&&p<=0)l.y=p=null;l.plotX=c.translate(m,0,0,0,1,i,this.type===\"flags\");if(b&&this.visible&&n&&n[m])n=n[m],p=n.points[this.index],q=p[0],p=p[1],q===0&&(q=o(k,e.min)),e.isLog&&\nq<=0&&(q=null),l.percentage=b===\"percent\"&&p,l.total=l.stackTotal=n.total,l.stackY=p,n.setOffset(this.pointXOffset||0,this.barW||0);l.yBottom=u(q)?e.translate(q,0,1,0,1):null;h&&(p=this.modifyValue(p,l));l.plotY=typeof p===\"number\"&&p!==Infinity?e.translate(p,0,1,0,1):w;l.clientX=j?c.translate(m,0,0,0,1):l.plotX;l.negative=l.y<(k||0);l.category=d&&d[l.x]!==w?d[l.x]:l.x}this.getSegments()},setTooltipPoints:function(a){var b=[],c,d,e=this.xAxis,f=e&&e.getExtremes(),g=e?e.tooltipLen||e.len:this.chart.plotSizeX,\nh,i,j=[];if(this.options.enableMouseTracking!==!1){if(a)this.tooltipPoints=null;n(this.segments||this.points,function(a){b=b.concat(a)});e&&e.reversed&&(b=b.reverse());this.orderTooltipPoints&&this.orderTooltipPoints(b);a=b.length;for(i=0;i<a;i++)if(e=b[i],c=e.x,c>=f.min&&c<=f.max){h=b[i+1];c=d===w?0:d+1;for(d=b[i+1]?I(s(0,P((e.clientX+(h?h.wrappedClientX||h.clientX:g))/2)),g):g;c>=0&&c<=d;)j[c++]=e}this.tooltipPoints=j}},tooltipHeaderFormatter:function(a){var b=this.tooltipOptions,c=b.xDateFormat,\nd=b.dateTimeLabelFormats,e=this.xAxis,f=e&&e.options.type===\"datetime\",b=b.headerFormat,e=e&&e.closestPointRange,g;if(f&&!c)if(e)for(g in D){if(D[g]>=e){c=d[g];break}}else c=d.day;f&&c&&sa(a.key)&&(b=b.replace(\"{point.key}\",\"{point.key:\"+c+\"}\"));return Ca(b,{point:a,series:this})},onMouseOver:function(){var a=this.chart,b=a.hoverSeries;if(b&&b!==this)b.onMouseOut();this.options.events.mouseOver&&z(this,\"mouseOver\");this.setState(\"hover\");a.hoverSeries=this},onMouseOut:function(){var a=this.options,\nb=this.chart,c=b.tooltip,d=b.hoverPoint;if(d)d.onMouseOut();this&&a.events.mouseOut&&z(this,\"mouseOut\");c&&!a.stickyTracking&&(!c.shared||this.noSharedTooltip)&&c.hide();this.setState();b.hoverSeries=null},animate:function(a){var b=this,c=b.chart,d=c.renderer,e;e=b.options.animation;var f=c.clipBox,g=c.inverted,h;if(e&&!T(e))e=Y[b.type].animation;h=\"_sharedClip\"+e.duration+e.easing;if(a)a=c[h],e=c[h+\"m\"],a||(c[h]=a=d.clipRect(r(f,{width:0})),c[h+\"m\"]=e=d.clipRect(-99,g?-c.plotLeft:-c.plotTop,99,g?\nc.chartWidth:c.chartHeight)),b.group.clip(a),b.markerGroup.clip(e),b.sharedClipKey=h;else{if(a=c[h])a.animate({width:c.plotSizeX},e),c[h+\"m\"].animate({width:c.plotSizeX+99},e);b.animate=null;b.animationTimeout=setTimeout(function(){b.afterAnimate()},e.duration)}},afterAnimate:function(){var a=this.chart,b=this.sharedClipKey,c=this.group;c&&this.options.clip!==!1&&(c.clip(a.clipRect),this.markerGroup.clip());setTimeout(function(){b&&a[b]&&(a[b]=a[b].destroy(),a[b+\"m\"]=a[b+\"m\"].destroy())},100)},drawPoints:function(){var a,\nb=this.points,c=this.chart,d,e,f,g,h,i,j,k,l=this.options.marker,m,p=this.markerGroup;if(l.enabled||this._hasPointMarkers)for(f=b.length;f--;)if(g=b[f],d=P(g.plotX),e=g.plotY,k=g.graphic,i=g.marker||{},a=l.enabled&&i.enabled===w||i.enabled,m=c.isInsidePlot(t(d),e,c.inverted),a&&e!==w&&!isNaN(e)&&g.y!==null)if(a=g.pointAttr[g.selected?\"select\":\"\"],h=a.r,i=o(i.symbol,this.symbol),j=i.indexOf(\"url\")===0,k)k.attr({visibility:m?Z?\"inherit\":\"visible\":\"hidden\"}).animate(r({x:d-h,y:e-h},k.symbolName?{width:2*\nh,height:2*h}:{}));else{if(m&&(h>0||j))g.graphic=c.renderer.symbol(i,d-h,e-h,2*h,2*h).attr(a).add(p)}else if(k)g.graphic=k.destroy()},convertAttribs:function(a,b,c,d){var e=this.pointAttrToOptions,f,g,h={},a=a||{},b=b||{},c=c||{},d=d||{};for(f in e)g=e[f],h[f]=o(a[g],b[f],c[f],d[f]);return h},getAttribs:function(){var a=this,b=a.options,c=Y[a.type].marker?b.marker:b,d=c.states,e=d.hover,f,g=a.color,h={stroke:g,fill:g},i=a.points||[],j=[],k,l=a.pointAttrToOptions,m=b.negativeColor,p=c.lineColor,q;\nb.marker?(e.radius=e.radius||c.radius+2,e.lineWidth=e.lineWidth||c.lineWidth+1):e.color=e.color||ra(e.color||g).brighten(e.brightness).get();j[\"\"]=a.convertAttribs(c,h);n([\"hover\",\"select\"],function(b){j[b]=a.convertAttribs(d[b],j[\"\"])});a.pointAttr=j;for(g=i.length;g--;){h=i[g];if((c=h.options&&h.options.marker||h.options)&&c.enabled===!1)c.radius=0;if(h.negative&&m)h.color=h.fillColor=m;f=b.colorByPoint||h.color;if(h.options)for(q in l)u(c[l[q]])&&(f=!0);if(f){c=c||{};k=[];d=c.states||{};f=d.hover=\nd.hover||{};if(!b.marker)f.color=ra(f.color||h.color).brighten(f.brightness||e.brightness).get();k[\"\"]=a.convertAttribs(r({color:h.color,fillColor:h.color,lineColor:p===null?h.color:w},c),j[\"\"]);k.hover=a.convertAttribs(d.hover,j.hover,k[\"\"]);k.select=a.convertAttribs(d.select,j.select,k[\"\"])}else k=j;h.pointAttr=k}},update:function(a,b){var c=this.chart,d=this.type,e=W[d].prototype,f,a=x(this.userOptions,{animation:!1,index:this.index,pointStart:this.xData[0]},{data:this.options.data},a);this.remove(!1);\nfor(f in e)e.hasOwnProperty(f)&&(this[f]=w);r(this,W[a.type||d].prototype);this.init(c,a);o(b,!0)&&c.redraw(!1)},destroy:function(){var a=this,b=a.chart,c=/AppleWebKit\\/533/.test(oa),d,e,f=a.data||[],g,h,i;z(a,\"destroy\");aa(a);n([\"xAxis\",\"yAxis\"],function(b){if(i=a[b])ga(i.series,a),i.isDirty=i.forceRedraw=!0,i.stacks={}});a.legendItem&&a.chart.legend.destroyItem(a);for(e=f.length;e--;)(g=f[e])&&g.destroy&&g.destroy();a.points=null;clearTimeout(a.animationTimeout);n(\"area,graph,dataLabelsGroup,group,markerGroup,tracker,graphNeg,areaNeg,posClip,negClip\".split(\",\"),\nfunction(b){a[b]&&(d=c&&b===\"group\"?\"hide\":\"destroy\",a[b][d]())});if(b.hoverSeries===a)b.hoverSeries=null;ga(b.series,a);for(h in a)delete a[h]},drawDataLabels:function(){var a=this,b=a.options.dataLabels,c=a.points,d,e,f,g;if(b.enabled||a._hasPointLabels)a.dlProcessOptions&&a.dlProcessOptions(b),g=a.plotGroup(\"dataLabelsGroup\",\"data-labels\",a.visible?\"visible\":\"hidden\",b.zIndex||6),e=b,n(c,function(c){var i,j=c.dataLabel,k,l,m=c.connector,p=!0;d=c.options&&c.options.dataLabels;i=o(d&&d.enabled,e.enabled);\nif(j&&!i)c.dataLabel=j.destroy();else if(i){b=x(e,d);i=b.rotation;k=c.getLabelConfig();f=b.format?Ca(b.format,k):b.formatter.call(k,b);b.style.color=o(b.color,b.style.color,a.color,\"black\");if(j)if(u(f))j.attr({text:f}),p=!1;else{if(c.dataLabel=j=j.destroy(),m)c.connector=m.destroy()}else if(u(f)){j={fill:b.backgroundColor,stroke:b.borderColor,\"stroke-width\":b.borderWidth,r:b.borderRadius||0,rotation:i,padding:b.padding,zIndex:1};for(l in j)j[l]===w&&delete j[l];j=c.dataLabel=a.chart.renderer[i?\"text\":\n\"label\"](f,0,-999,null,null,null,b.useHTML).attr(j).css(b.style).add(g).shadow(b.shadow)}j&&a.alignDataLabel(c,j,b,null,p)}})},alignDataLabel:function(a,b,c,d,e){var f=this.chart,g=f.inverted,h=o(a.plotX,-999),i=o(a.plotY,-999),j=b.getBBox();if(a=this.visible&&f.isInsidePlot(a.plotX,a.plotY,g))d=r({x:g?f.plotWidth-i:h,y:t(g?f.plotHeight-h:i),width:0,height:0},d),r(c,{width:j.width,height:j.height}),c.rotation?(g={align:c.align,x:d.x+c.x+d.width/2,y:d.y+c.y+d.height/2},b[e?\"attr\":\"animate\"](g)):(b.align(c,\nnull,d),g=b.alignAttr,o(c.overflow,\"justify\")===\"justify\"?this.justifyDataLabel(b,c,g,j,d,e):o(c.crop,!0)&&(a=f.isInsidePlot(g.x,g.y)&&f.isInsidePlot(g.x+j.width,g.y+j.height)));a||b.attr({y:-999})},justifyDataLabel:function(a,b,c,d,e,f){var g=this.chart,h=b.align,i=b.verticalAlign,j,k;j=c.x;if(j<0)h===\"right\"?b.align=\"left\":b.x=-j,k=!0;j=c.x+d.width;if(j>g.plotWidth)h===\"left\"?b.align=\"right\":b.x=g.plotWidth-j,k=!0;j=c.y;if(j<0)i===\"bottom\"?b.verticalAlign=\"top\":b.y=-j,k=!0;j=c.y+d.height;if(j>g.plotHeight)i===\n\"top\"?b.verticalAlign=\"bottom\":b.y=g.plotHeight-j,k=!0;if(k)a.placed=!f,a.align(b,null,e)},getSegmentPath:function(a){var b=this,c=[],d=b.options.step;n(a,function(e,f){var g=e.plotX,h=e.plotY,i;b.getPointSpline?c.push.apply(c,b.getPointSpline(a,e,f)):(c.push(f?\"L\":\"M\"),d&&f&&(i=a[f-1],d===\"right\"?c.push(i.plotX,h):d===\"center\"?c.push((i.plotX+g)/2,i.plotY,(i.plotX+g)/2,h):c.push(g,i.plotY)),c.push(e.plotX,e.plotY))});return c},getGraphPath:function(){var a=this,b=[],c,d=[];n(a.segments,function(e){c=\na.getSegmentPath(e);e.length>1?b=b.concat(c):d.push(e[0])});a.singlePoints=d;return a.graphPath=b},drawGraph:function(){var a=this,b=this.options,c=[[\"graph\",b.lineColor||this.color]],d=b.lineWidth,e=b.dashStyle,f=this.getGraphPath(),g=b.negativeColor;g&&c.push([\"graphNeg\",g]);n(c,function(c,g){var j=c[0],k=a[j];if(k)Wa(k),k.animate({d:f});else if(d&&f.length)k={stroke:c[1],\"stroke-width\":d,zIndex:1},e?k.dashstyle=e:k[\"stroke-linecap\"]=k[\"stroke-linejoin\"]=\"round\",a[j]=a.chart.renderer.path(f).attr(k).add(a.group).shadow(!g&&\nb.shadow)})},clipNeg:function(){var a=this.options,b=this.chart,c=b.renderer,d=a.negativeColor||a.negativeFillColor,e,f=this.graph,g=this.area,h=this.posClip,i=this.negClip;e=b.chartWidth;var j=b.chartHeight,k=s(e,j),l=this.yAxis;if(d&&(f||g)){d=t(l.toPixels(a.threshold||0,!0));a={x:0,y:0,width:k,height:d};k={x:0,y:d,width:k,height:k};if(b.inverted)a.height=k.y=b.plotWidth-d,c.isVML&&(a={x:b.plotWidth-d-b.plotLeft,y:0,width:e,height:j},k={x:d+b.plotLeft-e,y:0,width:b.plotLeft+d,height:e});l.reversed?\n(b=k,e=a):(b=a,e=k);h?(h.animate(b),i.animate(e)):(this.posClip=h=c.clipRect(b),this.negClip=i=c.clipRect(e),f&&this.graphNeg&&(f.clip(h),this.graphNeg.clip(i)),g&&(g.clip(h),this.areaNeg.clip(i)))}},invertGroups:function(){function a(){var a={width:b.yAxis.len,height:b.xAxis.len};n([\"group\",\"markerGroup\"],function(c){b[c]&&b[c].attr(a).invert()})}var b=this,c=b.chart;if(b.xAxis)J(c,\"resize\",a),J(b,\"destroy\",function(){aa(c,\"resize\",a)}),a(),b.invertGroups=a},plotGroup:function(a,b,c,d,e){var f=this[a],\ng=!f;g&&(this[a]=f=this.chart.renderer.g(b).attr({visibility:c,zIndex:d||0.1}).add(e));f[g?\"attr\":\"animate\"](this.getPlotBox());return f},getPlotBox:function(){return{translateX:this.xAxis?this.xAxis.left:this.chart.plotLeft,translateY:this.yAxis?this.yAxis.top:this.chart.plotTop,scaleX:1,scaleY:1}},render:function(){var a=this.chart,b,c=this.options,d=c.animation&&!!this.animate&&a.renderer.isSVG,e=this.visible?\"visible\":\"hidden\",f=c.zIndex,g=this.hasRendered,h=a.seriesGroup;b=this.plotGroup(\"group\",\n\"series\",e,f,h);this.markerGroup=this.plotGroup(\"markerGroup\",\"markers\",e,f,h);d&&this.animate(!0);this.getAttribs();b.inverted=this.isCartesian?a.inverted:!1;this.drawGraph&&(this.drawGraph(),this.clipNeg());this.drawDataLabels();this.drawPoints();this.options.enableMouseTracking!==!1&&this.drawTracker();a.inverted&&this.invertGroups();c.clip!==!1&&!this.sharedClipKey&&!g&&b.clip(a.clipRect);d?this.animate():g||this.afterAnimate();this.isDirty=this.isDirtyData=!1;this.hasRendered=!0},redraw:function(){var a=\nthis.chart,b=this.isDirtyData,c=this.group,d=this.xAxis,e=this.yAxis;c&&(a.inverted&&c.attr({width:a.plotWidth,height:a.plotHeight}),c.animate({translateX:o(d&&d.left,a.plotLeft),translateY:o(e&&e.top,a.plotTop)}));this.translate();this.setTooltipPoints(!0);this.render();b&&z(this,\"updatedData\")},setState:function(a){var b=this.options,c=this.graph,d=this.graphNeg,e=b.states,b=b.lineWidth,a=a||\"\";if(this.state!==a)this.state=a,e[a]&&e[a].enabled===!1||(a&&(b=e[a].lineWidth||b+1),c&&!c.dashstyle&&\n(a={\"stroke-width\":b},c.attr(a),d&&d.attr(a)))},setVisible:function(a,b){var c=this,d=c.chart,e=c.legendItem,f,g=d.options.chart.ignoreHiddenSeries,h=c.visible;f=(c.visible=a=c.userOptions.visible=a===w?!h:a)?\"show\":\"hide\";n([\"group\",\"dataLabelsGroup\",\"markerGroup\",\"tracker\"],function(a){if(c[a])c[a][f]()});if(d.hoverSeries===c)c.onMouseOut();e&&d.legend.colorizeItem(c,a);c.isDirty=!0;c.options.stacking&&n(d.series,function(a){if(a.options.stacking&&a.visible)a.isDirty=!0});n(c.linkedSeries,function(b){b.setVisible(a,\n!1)});if(g)d.isDirtyBox=!0;b!==!1&&d.redraw();z(c,f)},show:function(){this.setVisible(!0)},hide:function(){this.setVisible(!1)},select:function(a){this.selected=a=a===w?!this.selected:a;if(this.checkbox)this.checkbox.checked=a;z(this,a?\"select\":\"unselect\")},drawTracker:function(){var a=this,b=a.options,c=b.trackByArea,d=[].concat(c?a.areaPath:a.graphPath),e=d.length,f=a.chart,g=f.pointer,h=f.renderer,i=f.options.tooltip.snap,j=a.tracker,k=b.cursor,l=k&&{cursor:k},k=a.singlePoints,m,p=function(){if(f.hoverSeries!==\na)a.onMouseOver()};if(e&&!c)for(m=e+1;m--;)d[m]===\"M\"&&d.splice(m+1,0,d[m+1]-i,d[m+2],\"L\"),(m&&d[m]===\"M\"||m===e)&&d.splice(m,0,\"L\",d[m-2]+i,d[m-1]);for(m=0;m<k.length;m++)e=k[m],d.push(\"M\",e.plotX-i,e.plotY,\"L\",e.plotX+i,e.plotY);j?j.attr({d:d}):(a.tracker=h.path(d).attr({\"stroke-linejoin\":\"round\",visibility:a.visible?\"visible\":\"hidden\",stroke:Qb,fill:c?Qb:S,\"stroke-width\":b.lineWidth+(c?0:2*i),zIndex:2}).add(a.group),n([a.tracker,a.markerGroup],function(a){a.addClass(\"highcharts-tracker\").on(\"mouseover\",\np).on(\"mouseout\",function(a){g.onTrackerMouseOut(a)}).css(l);if(ib)a.on(\"touchstart\",p)}))}};G=ha(Q);W.line=G;Y.area=x(X,{threshold:0});G=ha(Q,{type:\"area\",getSegments:function(){var a=[],b=[],c=[],d=this.xAxis,e=this.yAxis,f=e.stacks[this.stackKey],g={},h,i,j=this.points,k=this.options.connectNulls,l,m,p;if(this.options.stacking&&!this.cropped){for(m=0;m<j.length;m++)g[j[m].x]=j[m];for(p in f)c.push(+p);c.sort(function(a,b){return a-b});n(c,function(a){if(!k||g[a]&&g[a].y!==null)g[a]?b.push(g[a]):\n(h=d.translate(a),l=f[a].percent?f[a].total?f[a].cum*100/f[a].total:0:f[a].cum,i=e.toPixels(l,!0),b.push({y:null,plotX:h,clientX:h,plotY:i,yBottom:i,onMouseOver:pa}))});b.length&&a.push(b)}else Q.prototype.getSegments.call(this),a=this.segments;this.segments=a},getSegmentPath:function(a){var b=Q.prototype.getSegmentPath.call(this,a),c=[].concat(b),d,e=this.options;d=b.length;var f=this.yAxis.getThreshold(e.threshold),g;d===3&&c.push(\"L\",b[1],b[2]);if(e.stacking&&!this.closedStacks)for(d=a.length-\n1;d>=0;d--)g=o(a[d].yBottom,f),d<a.length-1&&e.step&&c.push(a[d+1].plotX,g),c.push(a[d].plotX,g);else this.closeSegment(c,a,f);this.areaPath=this.areaPath.concat(c);return b},closeSegment:function(a,b,c){a.push(\"L\",b[b.length-1].plotX,c,\"L\",b[0].plotX,c)},drawGraph:function(){this.areaPath=[];Q.prototype.drawGraph.apply(this);var a=this,b=this.areaPath,c=this.options,d=c.negativeColor,e=c.negativeFillColor,f=[[\"area\",this.color,c.fillColor]];(d||e)&&f.push([\"areaNeg\",d,e]);n(f,function(d){var e=d[0],\nf=a[e];f?f.animate({d:b}):a[e]=a.chart.renderer.path(b).attr({fill:o(d[2],ra(d[1]).setOpacity(o(c.fillOpacity,0.75)).get()),zIndex:0}).add(a.group)})},drawLegendSymbol:function(a,b){b.legendSymbol=this.chart.renderer.rect(0,a.baseline-11,a.options.symbolWidth,12,2).attr({zIndex:3}).add(b.legendGroup)}});W.area=G;Y.spline=x(X);F=ha(Q,{type:\"spline\",getPointSpline:function(a,b,c){var d=b.plotX,e=b.plotY,f=a[c-1],g=a[c+1],h,i,j,k;if(f&&g){a=f.plotY;j=g.plotX;var g=g.plotY,l;h=(1.5*d+f.plotX)/2.5;i=(1.5*\ne+a)/2.5;j=(1.5*d+j)/2.5;k=(1.5*e+g)/2.5;l=(k-i)*(j-d)/(j-h)+e-k;i+=l;k+=l;i>a&&i>e?(i=s(a,e),k=2*e-i):i<a&&i<e&&(i=I(a,e),k=2*e-i);k>g&&k>e?(k=s(g,e),i=2*e-k):k<g&&k<e&&(k=I(g,e),i=2*e-k);b.rightContX=j;b.rightContY=k}c?(b=[\"C\",f.rightContX||f.plotX,f.rightContY||f.plotY,h||d,i||e,d,e],f.rightContX=f.rightContY=null):b=[\"M\",d,e];return b}});W.spline=F;Y.areaspline=x(Y.area);ma=G.prototype;F=ha(F,{type:\"areaspline\",closedStacks:!0,getSegmentPath:ma.getSegmentPath,closeSegment:ma.closeSegment,drawGraph:ma.drawGraph,\ndrawLegendSymbol:ma.drawLegendSymbol});W.areaspline=F;Y.column=x(X,{borderColor:\"#FFFFFF\",borderWidth:1,borderRadius:0,groupPadding:0.2,marker:null,pointPadding:0.1,minPointLength:0,cropThreshold:50,pointRange:null,states:{hover:{brightness:0.1,shadow:!1},select:{color:\"#C0C0C0\",borderColor:\"#000000\",shadow:!1}},dataLabels:{align:null,verticalAlign:null,y:null},stickyTracking:!1,threshold:0});F=ha(Q,{type:\"column\",pointAttrToOptions:{stroke:\"borderColor\",\"stroke-width\":\"borderWidth\",fill:\"color\",\nr:\"borderRadius\"},cropShoulder:0,trackerGroups:[\"group\",\"dataLabelsGroup\"],negStacks:!0,init:function(){Q.prototype.init.apply(this,arguments);var a=this,b=a.chart;b.hasRendered&&n(b.series,function(b){if(b.type===a.type)b.isDirty=!0})},getColumnMetrics:function(){var a=this,b=a.options,c=a.xAxis,d=a.yAxis,e=c.reversed,f,g={},h,i=0;b.grouping===!1?i=1:n(a.chart.series,function(b){var c=b.options,e=b.yAxis;if(b.type===a.type&&b.visible&&d.len===e.len&&d.pos===e.pos)c.stacking?(f=b.stackKey,g[f]===\nw&&(g[f]=i++),h=g[f]):c.grouping!==!1&&(h=i++),b.columnIndex=h});var c=I(N(c.transA)*(c.ordinalSlope||b.pointRange||c.closestPointRange||1),c.len),j=c*b.groupPadding,k=(c-2*j)/i,l=b.pointWidth,b=u(l)?(k-l)/2:k*b.pointPadding,l=o(l,k-2*b);return a.columnMetrics={width:l,offset:b+(j+((e?i-(a.columnIndex||0):a.columnIndex)||0)*k-c/2)*(e?-1:1)}},translate:function(){var a=this.chart,b=this.options,c=b.borderWidth,d=this.yAxis,e=this.translatedThreshold=d.getThreshold(b.threshold),f=o(b.minPointLength,\n5),b=this.getColumnMetrics(),g=b.width,h=this.barW=xa(s(g,1+2*c)),i=this.pointXOffset=b.offset,j=-(c%2?0.5:0),k=c%2?0.5:1;a.renderer.isVML&&a.inverted&&(k+=1);Q.prototype.translate.apply(this);n(this.points,function(a){var b=o(a.yBottom,e),c=I(s(-999-b,a.plotY),d.len+999+b),n=a.plotX+i,u=h,r=I(c,b),w,c=s(c,b)-r;N(c)<f&&f&&(c=f,r=t(N(r-e)>f?b-f:e-(d.translate(a.y,0,1,0,1)<=e?f:0)));a.barX=n;a.pointWidth=g;b=N(n)<0.5;u=t(n+u)+j;n=t(n)+j;u-=n;w=N(r)<0.5;c=t(r+c)+k;r=t(r)+k;c-=r;b&&(n+=1,u-=1);w&&(r-=\n1,c+=1);a.shapeType=\"rect\";a.shapeArgs={x:n,y:r,width:u,height:c}})},getSymbol:pa,drawLegendSymbol:G.prototype.drawLegendSymbol,drawGraph:pa,drawPoints:function(){var a=this,b=a.options,c=a.chart.renderer,d;n(a.points,function(e){var f=e.plotY,g=e.graphic;if(f!==w&&!isNaN(f)&&e.y!==null)d=e.shapeArgs,g?(Wa(g),g.animate(x(d))):e.graphic=c[e.shapeType](d).attr(e.pointAttr[e.selected?\"select\":\"\"]).add(a.group).shadow(b.shadow,null,b.stacking&&!b.borderRadius);else if(g)e.graphic=g.destroy()})},drawTracker:function(){var a=\nthis,b=a.chart,c=b.pointer,d=a.options.cursor,e=d&&{cursor:d},f=function(c){var d=c.target,e;if(b.hoverSeries!==a)a.onMouseOver();for(;d&&!e;)e=d.point,d=d.parentNode;if(e!==w&&e!==b.hoverPoint)e.onMouseOver(c)};n(a.points,function(a){if(a.graphic)a.graphic.element.point=a;if(a.dataLabel)a.dataLabel.element.point=a});if(!a._hasTracking)n(a.trackerGroups,function(b){if(a[b]&&(a[b].addClass(\"highcharts-tracker\").on(\"mouseover\",f).on(\"mouseout\",function(a){c.onTrackerMouseOut(a)}).css(e),ib))a[b].on(\"touchstart\",\nf)}),a._hasTracking=!0},alignDataLabel:function(a,b,c,d,e){var f=this.chart,g=f.inverted,h=a.dlBox||a.shapeArgs,i=a.below||a.plotY>o(this.translatedThreshold,f.plotSizeY),j=o(c.inside,!!this.options.stacking);if(h&&(d=x(h),g&&(d={x:f.plotWidth-d.y-d.height,y:f.plotHeight-d.x-d.width,width:d.height,height:d.width}),!j))g?(d.x+=i?0:d.width,d.width=0):(d.y+=i?d.height:0,d.height=0);c.align=o(c.align,!g||j?\"center\":i?\"right\":\"left\");c.verticalAlign=o(c.verticalAlign,g||j?\"middle\":i?\"top\":\"bottom\");Q.prototype.alignDataLabel.call(this,\na,b,c,d,e)},animate:function(a){var b=this.yAxis,c=this.options,d=this.chart.inverted,e={};if(Z)a?(e.scaleY=0.001,a=I(b.pos+b.len,s(b.pos,b.toPixels(c.threshold))),d?e.translateX=a-b.len:e.translateY=a,this.group.attr(e)):(e.scaleY=1,e[d?\"translateX\":\"translateY\"]=b.pos,this.group.animate(e,this.options.animation),this.animate=null)},remove:function(){var a=this,b=a.chart;b.hasRendered&&n(b.series,function(b){if(b.type===a.type)b.isDirty=!0});Q.prototype.remove.apply(a,arguments)}});W.column=F;Y.bar=\nx(Y.column);ma=ha(F,{type:\"bar\",inverted:!0});W.bar=ma;Y.scatter=x(X,{lineWidth:0,tooltip:{headerFormat:'<span style=\"font-size: 10px; color:{series.color}\">{series.name}</span><br/>',pointFormat:\"x: <b>{point.x}</b><br/>y: <b>{point.y}</b><br/>\",followPointer:!0},stickyTracking:!1});ma=ha(Q,{type:\"scatter\",sorted:!1,requireSorting:!1,noSharedTooltip:!0,trackerGroups:[\"markerGroup\"],drawTracker:F.prototype.drawTracker,setTooltipPoints:pa});W.scatter=ma;Y.pie=x(X,{borderColor:\"#FFFFFF\",borderWidth:1,\ncenter:[null,null],clip:!1,colorByPoint:!0,dataLabels:{distance:30,enabled:!0,formatter:function(){return this.point.name}},ignoreHiddenPoint:!0,legendType:\"point\",marker:null,size:null,showInLegend:!1,slicedOffset:10,states:{hover:{brightness:0.1,shadow:!1}},stickyTracking:!1,tooltip:{followPointer:!0}});X={type:\"pie\",isCartesian:!1,pointClass:ha(Pa,{init:function(){Pa.prototype.init.apply(this,arguments);var a=this,b;if(a.y<0)a.y=null;r(a,{visible:a.visible!==!1,name:o(a.name,\"Slice\")});b=function(b){a.slice(b.type===\n\"select\")};J(a,\"select\",b);J(a,\"unselect\",b);return a},setVisible:function(a){var b=this,c=b.series,d=c.chart,e;b.visible=b.options.visible=a=a===w?!b.visible:a;c.options.data[qa(b,c.data)]=b.options;e=a?\"show\":\"hide\";n([\"graphic\",\"dataLabel\",\"connector\",\"shadowGroup\"],function(a){if(b[a])b[a][e]()});b.legendItem&&d.legend.colorizeItem(b,a);if(!c.isDirty&&c.options.ignoreHiddenPoint)c.isDirty=!0,d.redraw()},slice:function(a,b,c){var d=this.series;La(c,d.chart);o(b,!0);this.sliced=this.options.sliced=\na=u(a)?a:!this.sliced;d.options.data[qa(this,d.data)]=this.options;a=a?this.slicedTranslation:{translateX:0,translateY:0};this.graphic.animate(a);this.shadowGroup&&this.shadowGroup.animate(a)}}),requireSorting:!1,noSharedTooltip:!0,trackerGroups:[\"group\",\"dataLabelsGroup\"],pointAttrToOptions:{stroke:\"borderColor\",\"stroke-width\":\"borderWidth\",fill:\"color\"},getColor:pa,animate:function(a){var b=this,c=b.points,d=b.startAngleRad;if(!a)n(c,function(a){var c=a.graphic,a=a.shapeArgs;c&&(c.attr({r:b.center[3]/\n2,start:d,end:d}),c.animate({r:a.r,start:a.start,end:a.end},b.options.animation))}),b.animate=null},setData:function(a,b){Q.prototype.setData.call(this,a,!1);this.processData();this.generatePoints();o(b,!0)&&this.chart.redraw()},generatePoints:function(){var a,b=0,c,d,e,f=this.options.ignoreHiddenPoint;Q.prototype.generatePoints.call(this);c=this.points;d=c.length;for(a=0;a<d;a++)e=c[a],b+=f&&!e.visible?0:e.y;this.total=b;for(a=0;a<d;a++)e=c[a],e.percentage=b>0?e.y/b*100:0,e.total=b},getCenter:function(){var a=\nthis.options,b=this.chart,c=2*(a.slicedOffset||0),d,e=b.plotWidth-2*c,f=b.plotHeight-2*c,b=a.center,a=[o(b[0],\"50%\"),o(b[1],\"50%\"),a.size||\"100%\",a.innerSize||0],g=I(e,f),h;return Na(a,function(a,b){h=/%$/.test(a);d=b<2||b===2&&h;return(h?[e,f,g,g][b]*C(a)/100:a)+(d?c:0)})},translate:function(a){this.generatePoints();var b=0,c=this.options,d=c.slicedOffset,e=d+c.borderWidth,f,g,h,i=c.startAngle||0,j=this.startAngleRad=ya/180*(i-90),i=(this.endAngleRad=ya/180*((c.endAngle||i+360)-90))-j,k=this.points,\nl=c.dataLabels.distance,c=c.ignoreHiddenPoint,m,n=k.length,o;if(!a)this.center=a=this.getCenter();this.getX=function(b,c){h=R.asin((b-a[1])/(a[2]/2+l));return a[0]+(c?-1:1)*V(h)*(a[2]/2+l)};for(m=0;m<n;m++){o=k[m];f=j+b*i;if(!c||o.visible)b+=o.percentage/100;g=j+b*i;o.shapeType=\"arc\";o.shapeArgs={x:a[0],y:a[1],r:a[2]/2,innerR:a[3]/2,start:t(f*1E3)/1E3,end:t(g*1E3)/1E3};h=(g+f)/2;h>0.75*i&&(h-=2*ya);o.slicedTranslation={translateX:t(V(h)*d),translateY:t(ca(h)*d)};f=V(h)*a[2]/2;g=ca(h)*a[2]/2;o.tooltipPos=\n[a[0]+f*0.7,a[1]+g*0.7];o.half=h<-ya/2||h>ya/2?1:0;o.angle=h;e=I(e,l/2);o.labelPos=[a[0]+f+V(h)*l,a[1]+g+ca(h)*l,a[0]+f+V(h)*e,a[1]+g+ca(h)*e,a[0]+f,a[1]+g,l<0?\"center\":o.half?\"right\":\"left\",h]}},setTooltipPoints:pa,drawGraph:null,drawPoints:function(){var a=this,b=a.chart.renderer,c,d,e=a.options.shadow,f,g;if(e&&!a.shadowGroup)a.shadowGroup=b.g(\"shadow\").add(a.group);n(a.points,function(h){d=h.graphic;g=h.shapeArgs;f=h.shadowGroup;if(e&&!f)f=h.shadowGroup=b.g(\"shadow\").add(a.shadowGroup);c=h.sliced?\nh.slicedTranslation:{translateX:0,translateY:0};f&&f.attr(c);d?d.animate(r(g,c)):h.graphic=d=b.arc(g).setRadialReference(a.center).attr(h.pointAttr[h.selected?\"select\":\"\"]).attr({\"stroke-linejoin\":\"round\"}).attr(c).add(a.group).shadow(e,f);h.visible===!1&&h.setVisible(!1)})},sortByAngle:function(a,b){a.sort(function(a,d){return a.angle!==void 0&&(d.angle-a.angle)*b})},drawDataLabels:function(){var a=this,b=a.data,c,d=a.chart,e=a.options.dataLabels,f=o(e.connectorPadding,10),g=o(e.connectorWidth,1),\nh=d.plotWidth,d=d.plotHeight,i,j,k=o(e.softConnector,!0),l=e.distance,m=a.center,p=m[2]/2,q=m[1],u=l>0,r,w,v,x,C=[[],[]],y,z,E,H,B,D=[0,0,0,0],I=function(a,b){return b.y-a.y};if(a.visible&&(e.enabled||a._hasPointLabels)){Q.prototype.drawDataLabels.apply(a);n(b,function(a){a.dataLabel&&C[a.half].push(a)});for(H=0;!x&&b[H];)x=b[H]&&b[H].dataLabel&&(b[H].dataLabel.getBBox().height||21),H++;for(H=2;H--;){var b=[],K=[],G=C[H],J=G.length,F;a.sortByAngle(G,H-0.5);if(l>0){for(B=q-p-l;B<=q+p+l;B+=x)b.push(B);\nw=b.length;if(J>w){c=[].concat(G);c.sort(I);for(B=J;B--;)c[B].rank=B;for(B=J;B--;)G[B].rank>=w&&G.splice(B,1);J=G.length}for(B=0;B<J;B++){c=G[B];v=c.labelPos;c=9999;var O,M;for(M=0;M<w;M++)O=N(b[M]-v[1]),O<c&&(c=O,F=M);if(F<B&&b[B]!==null)F=B;else for(w<J-B+F&&b[B]!==null&&(F=w-J+B);b[F]===null;)F++;K.push({i:F,y:b[F]});b[F]=null}K.sort(I)}for(B=0;B<J;B++){c=G[B];v=c.labelPos;r=c.dataLabel;E=c.visible===!1?\"hidden\":\"visible\";c=v[1];if(l>0){if(w=K.pop(),F=w.i,z=w.y,c>z&&b[F+1]!==null||c<z&&b[F-1]!==\nnull)z=c}else z=c;y=e.justify?m[0]+(H?-1:1)*(p+l):a.getX(F===0||F===b.length-1?c:z,H);r._attr={visibility:E,align:v[6]};r._pos={x:y+e.x+({left:f,right:-f}[v[6]]||0),y:z+e.y-10};r.connX=y;r.connY=z;if(this.options.size===null)w=r.width,y-w<f?D[3]=s(t(w-y+f),D[3]):y+w>h-f&&(D[1]=s(t(y+w-h+f),D[1])),z-x/2<0?D[0]=s(t(-z+x/2),D[0]):z+x/2>d&&(D[2]=s(t(z+x/2-d),D[2]))}}if(va(D)===0||this.verifyDataLabelOverflow(D))this.placeDataLabels(),u&&g&&n(this.points,function(b){i=b.connector;v=b.labelPos;if((r=b.dataLabel)&&\nr._pos)E=r._attr.visibility,y=r.connX,z=r.connY,j=k?[\"M\",y+(v[6]===\"left\"?5:-5),z,\"C\",y,z,2*v[2]-v[4],2*v[3]-v[5],v[2],v[3],\"L\",v[4],v[5]]:[\"M\",y+(v[6]===\"left\"?5:-5),z,\"L\",v[2],v[3],\"L\",v[4],v[5]],i?(i.animate({d:j}),i.attr(\"visibility\",E)):b.connector=i=a.chart.renderer.path(j).attr({\"stroke-width\":g,stroke:e.connectorColor||b.color||\"#606060\",visibility:E}).add(a.group);else if(i)b.connector=i.destroy()})}},verifyDataLabelOverflow:function(a){var b=this.center,c=this.options,d=c.center,e=c=c.minSize||\n80,f;d[0]!==null?e=s(b[2]-s(a[1],a[3]),c):(e=s(b[2]-a[1]-a[3],c),b[0]+=(a[3]-a[1])/2);d[1]!==null?e=s(I(e,b[2]-s(a[0],a[2])),c):(e=s(I(e,b[2]-a[0]-a[2]),c),b[1]+=(a[0]-a[2])/2);e<b[2]?(b[2]=e,this.translate(b),n(this.points,function(a){if(a.dataLabel)a.dataLabel._pos=null}),this.drawDataLabels()):f=!0;return f},placeDataLabels:function(){n(this.points,function(a){var a=a.dataLabel,b;if(a)(b=a._pos)?(a.attr(a._attr),a[a.moved?\"animate\":\"attr\"](b),a.moved=!0):a&&a.attr({y:-999})})},alignDataLabel:pa,\ndrawTracker:F.prototype.drawTracker,drawLegendSymbol:G.prototype.drawLegendSymbol,getSymbol:pa};X=ha(Q,X);W.pie=X;r(Highcharts,{Axis:db,Chart:yb,Color:ra,Legend:eb,Pointer:xb,Point:Pa,Tick:Ma,Tooltip:wb,Renderer:Va,Series:Q,SVGElement:wa,SVGRenderer:Ha,arrayMin:Ja,arrayMax:va,charts:Ga,dateFormat:Xa,format:Ca,pathAnim:Ab,getOptions:function(){return M},hasBidiBug:Ub,isTouchDevice:Ob,numberFormat:Aa,seriesTypes:W,setOptions:function(a){M=x(M,a);Lb();return M},addEvent:J,removeEvent:aa,createElement:U,\ndiscardElement:Ta,css:K,each:n,extend:r,map:Na,merge:x,pick:o,splat:ja,extendClass:ha,pInt:C,wrap:mb,svg:Z,canvas:$,vml:!Z&&!$,product:\"Highcharts\",version:\"3.0.6\"})})();\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/highcharts/highcharts.src.js",
    "content": "// ==ClosureCompiler==\n// @compilation_level SIMPLE_OPTIMIZATIONS\n\n/**\n * @license Highcharts JS v3.0.6 (2013-10-04)\n *\n * (c) 2009-2013 Torstein Hønsi\n *\n * License: www.highcharts.com/license\n */\n\n// JSLint options:\n/*global Highcharts, document, window, navigator, setInterval, clearInterval, clearTimeout, setTimeout, location, jQuery, $, console, each, grep */\n\n(function () {\n// encapsulated variables\nvar UNDEFINED,\n\tdoc = document,\n\twin = window,\n\tmath = Math,\n\tmathRound = math.round,\n\tmathFloor = math.floor,\n\tmathCeil = math.ceil,\n\tmathMax = math.max,\n\tmathMin = math.min,\n\tmathAbs = math.abs,\n\tmathCos = math.cos,\n\tmathSin = math.sin,\n\tmathPI = math.PI,\n\tdeg2rad = mathPI * 2 / 360,\n\n\n\t// some variables\n\tuserAgent = navigator.userAgent,\n\tisOpera = win.opera,\n\tisIE = /msie/i.test(userAgent) && !isOpera,\n\tdocMode8 = doc.documentMode === 8,\n\tisWebKit = /AppleWebKit/.test(userAgent),\n\tisFirefox = /Firefox/.test(userAgent),\n\tisTouchDevice = /(Mobile|Android|Windows Phone)/.test(userAgent),\n\tSVG_NS = 'http://www.w3.org/2000/svg',\n\thasSVG = !!doc.createElementNS && !!doc.createElementNS(SVG_NS, 'svg').createSVGRect,\n\thasBidiBug = isFirefox && parseInt(userAgent.split('Firefox/')[1], 10) < 4, // issue #38\n\tuseCanVG = !hasSVG && !isIE && !!doc.createElement('canvas').getContext,\n\tRenderer,\n\thasTouch = doc.documentElement.ontouchstart !== UNDEFINED,\n\tsymbolSizes = {},\n\tidCounter = 0,\n\tgarbageBin,\n\tdefaultOptions,\n\tdateFormat, // function\n\tglobalAnimation,\n\tpathAnim,\n\ttimeUnits,\n\tnoop = function () {},\n\tcharts = [],\n\tPRODUCT = 'Highcharts',\n\tVERSION = '3.0.6',\n\n\t// some constants for frequently used strings\n\tDIV = 'div',\n\tABSOLUTE = 'absolute',\n\tRELATIVE = 'relative',\n\tHIDDEN = 'hidden',\n\tPREFIX = 'highcharts-',\n\tVISIBLE = 'visible',\n\tPX = 'px',\n\tNONE = 'none',\n\tM = 'M',\n\tL = 'L',\n\t/*\n\t * Empirical lowest possible opacities for TRACKER_FILL\n\t * IE6: 0.002\n\t * IE7: 0.002\n\t * IE8: 0.002\n\t * IE9: 0.00000000001 (unlimited)\n\t * IE10: 0.0001 (exporting only)\n\t * FF: 0.00000000001 (unlimited)\n\t * Chrome: 0.000001\n\t * Safari: 0.000001\n\t * Opera: 0.00000000001 (unlimited)\n\t */\n\tTRACKER_FILL = 'rgba(192,192,192,' + (hasSVG ? 0.0001 : 0.002) + ')', // invisible but clickable\n\t//TRACKER_FILL = 'rgba(192,192,192,0.5)',\n\tNORMAL_STATE = '',\n\tHOVER_STATE = 'hover',\n\tSELECT_STATE = 'select',\n\tMILLISECOND = 'millisecond',\n\tSECOND = 'second',\n\tMINUTE = 'minute',\n\tHOUR = 'hour',\n\tDAY = 'day',\n\tWEEK = 'week',\n\tMONTH = 'month',\n\tYEAR = 'year',\n\n\t// constants for attributes\n\tLINEAR_GRADIENT = 'linearGradient',\n\tSTOPS = 'stops',\n\tSTROKE_WIDTH = 'stroke-width',\n\n\t// time methods, changed based on whether or not UTC is used\n\tmakeTime,\n\tgetMinutes,\n\tgetHours,\n\tgetDay,\n\tgetDate,\n\tgetMonth,\n\tgetFullYear,\n\tsetMinutes,\n\tsetHours,\n\tsetDate,\n\tsetMonth,\n\tsetFullYear,\n\n\n\t// lookup over the types and the associated classes\n\tseriesTypes = {};\n\n// The Highcharts namespace\nwin.Highcharts = win.Highcharts ? error(16, true) : {};\n\n/**\n * Extend an object with the members of another\n * @param {Object} a The object to be extended\n * @param {Object} b The object to add to the first one\n */\nfunction extend(a, b) {\n\tvar n;\n\tif (!a) {\n\t\ta = {};\n\t}\n\tfor (n in b) {\n\t\ta[n] = b[n];\n\t}\n\treturn a;\n}\n\t\n/**\n * Deep merge two or more objects and return a third object.\n * Previously this function redirected to jQuery.extend(true), but this had two limitations.\n * First, it deep merged arrays, which lead to workarounds in Highcharts. Second,\n * it copied properties from extended prototypes. \n */\nfunction merge() {\n\tvar i,\n\t\tlen = arguments.length,\n\t\tret = {},\n\t\tdoCopy = function (copy, original) {\n\t\t\tvar value, key;\n\n\t\t\t// An object is replacing a primitive\n\t\t\tif (typeof copy !== 'object') {\n\t\t\t\tcopy = {};\n\t\t\t}\n\n\t\t\tfor (key in original) {\n\t\t\t\tif (original.hasOwnProperty(key)) {\n\t\t\t\t\tvalue = original[key];\n\n\t\t\t\t\t// Copy the contents of objects, but not arrays or DOM nodes\n\t\t\t\t\tif (value && typeof value === 'object' && Object.prototype.toString.call(value) !== '[object Array]'\n\t\t\t\t\t\t\t&& typeof value.nodeType !== 'number') {\n\t\t\t\t\t\tcopy[key] = doCopy(copy[key] || {}, value);\n\t\t\t\t\n\t\t\t\t\t// Primitives and arrays are copied over directly\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcopy[key] = original[key];\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn copy;\n\t\t};\n\n\t// For each argument, extend the return\n\tfor (i = 0; i < len; i++) {\n\t\tret = doCopy(ret, arguments[i]);\n\t}\n\n\treturn ret;\n}\n\n/**\n * Take an array and turn into a hash with even number arguments as keys and odd numbers as\n * values. Allows creating constants for commonly used style properties, attributes etc.\n * Avoid it in performance critical situations like looping\n */\nfunction hash() {\n\tvar i = 0,\n\t\targs = arguments,\n\t\tlength = args.length,\n\t\tobj = {};\n\tfor (; i < length; i++) {\n\t\tobj[args[i++]] = args[i];\n\t}\n\treturn obj;\n}\n\n/**\n * Shortcut for parseInt\n * @param {Object} s\n * @param {Number} mag Magnitude\n */\nfunction pInt(s, mag) {\n\treturn parseInt(s, mag || 10);\n}\n\n/**\n * Check for string\n * @param {Object} s\n */\nfunction isString(s) {\n\treturn typeof s === 'string';\n}\n\n/**\n * Check for object\n * @param {Object} obj\n */\nfunction isObject(obj) {\n\treturn typeof obj === 'object';\n}\n\n/**\n * Check for array\n * @param {Object} obj\n */\nfunction isArray(obj) {\n\treturn Object.prototype.toString.call(obj) === '[object Array]';\n}\n\n/**\n * Check for number\n * @param {Object} n\n */\nfunction isNumber(n) {\n\treturn typeof n === 'number';\n}\n\nfunction log2lin(num) {\n\treturn math.log(num) / math.LN10;\n}\nfunction lin2log(num) {\n\treturn math.pow(10, num);\n}\n\n/**\n * Remove last occurence of an item from an array\n * @param {Array} arr\n * @param {Mixed} item\n */\nfunction erase(arr, item) {\n\tvar i = arr.length;\n\twhile (i--) {\n\t\tif (arr[i] === item) {\n\t\t\tarr.splice(i, 1);\n\t\t\tbreak;\n\t\t}\n\t}\n\t//return arr;\n}\n\n/**\n * Returns true if the object is not null or undefined. Like MooTools' $.defined.\n * @param {Object} obj\n */\nfunction defined(obj) {\n\treturn obj !== UNDEFINED && obj !== null;\n}\n\n/**\n * Set or get an attribute or an object of attributes. Can't use jQuery attr because\n * it attempts to set expando properties on the SVG element, which is not allowed.\n *\n * @param {Object} elem The DOM element to receive the attribute(s)\n * @param {String|Object} prop The property or an abject of key-value pairs\n * @param {String} value The value if a single property is set\n */\nfunction attr(elem, prop, value) {\n\tvar key,\n\t\tsetAttribute = 'setAttribute',\n\t\tret;\n\n\t// if the prop is a string\n\tif (isString(prop)) {\n\t\t// set the value\n\t\tif (defined(value)) {\n\n\t\t\telem[setAttribute](prop, value);\n\n\t\t// get the value\n\t\t} else if (elem && elem.getAttribute) { // elem not defined when printing pie demo...\n\t\t\tret = elem.getAttribute(prop);\n\t\t}\n\n\t// else if prop is defined, it is a hash of key/value pairs\n\t} else if (defined(prop) && isObject(prop)) {\n\t\tfor (key in prop) {\n\t\t\telem[setAttribute](key, prop[key]);\n\t\t}\n\t}\n\treturn ret;\n}\n/**\n * Check if an element is an array, and if not, make it into an array. Like\n * MooTools' $.splat.\n */\nfunction splat(obj) {\n\treturn isArray(obj) ? obj : [obj];\n}\n\n\n/**\n * Return the first value that is defined. Like MooTools' $.pick.\n */\nfunction pick() {\n\tvar args = arguments,\n\t\ti,\n\t\targ,\n\t\tlength = args.length;\n\tfor (i = 0; i < length; i++) {\n\t\targ = args[i];\n\t\tif (typeof arg !== 'undefined' && arg !== null) {\n\t\t\treturn arg;\n\t\t}\n\t}\n}\n\n/**\n * Set CSS on a given element\n * @param {Object} el\n * @param {Object} styles Style object with camel case property names\n */\nfunction css(el, styles) {\n\tif (isIE) {\n\t\tif (styles && styles.opacity !== UNDEFINED) {\n\t\t\tstyles.filter = 'alpha(opacity=' + (styles.opacity * 100) + ')';\n\t\t}\n\t}\n\textend(el.style, styles);\n}\n\n/**\n * Utility function to create element with attributes and styles\n * @param {Object} tag\n * @param {Object} attribs\n * @param {Object} styles\n * @param {Object} parent\n * @param {Object} nopad\n */\nfunction createElement(tag, attribs, styles, parent, nopad) {\n\tvar el = doc.createElement(tag);\n\tif (attribs) {\n\t\textend(el, attribs);\n\t}\n\tif (nopad) {\n\t\tcss(el, {padding: 0, border: NONE, margin: 0});\n\t}\n\tif (styles) {\n\t\tcss(el, styles);\n\t}\n\tif (parent) {\n\t\tparent.appendChild(el);\n\t}\n\treturn el;\n}\n\n/**\n * Extend a prototyped class by new members\n * @param {Object} parent\n * @param {Object} members\n */\nfunction extendClass(parent, members) {\n\tvar object = function () {};\n\tobject.prototype = new parent();\n\textend(object.prototype, members);\n\treturn object;\n}\n\n/**\n * Format a number and return a string based on input settings\n * @param {Number} number The input number to format\n * @param {Number} decimals The amount of decimals\n * @param {String} decPoint The decimal point, defaults to the one given in the lang options\n * @param {String} thousandsSep The thousands separator, defaults to the one given in the lang options\n */\nfunction numberFormat(number, decimals, decPoint, thousandsSep) {\n\tvar lang = defaultOptions.lang,\n\t\t// http://kevin.vanzonneveld.net/techblog/article/javascript_equivalent_for_phps_number_format/\n\t\tn = +number || 0,\n\t\tc = decimals === -1 ?\n\t\t\t(n.toString().split('.')[1] || '').length : // preserve decimals\n\t\t\t(isNaN(decimals = mathAbs(decimals)) ? 2 : decimals),\n\t\td = decPoint === undefined ? lang.decimalPoint : decPoint,\n\t\tt = thousandsSep === undefined ? lang.thousandsSep : thousandsSep,\n\t\ts = n < 0 ? \"-\" : \"\",\n\t\ti = String(pInt(n = mathAbs(n).toFixed(c))),\n\t\tj = i.length > 3 ? i.length % 3 : 0;\n\n\treturn s + (j ? i.substr(0, j) + t : \"\") + i.substr(j).replace(/(\\d{3})(?=\\d)/g, \"$1\" + t) +\n\t\t(c ? d + mathAbs(n - i).toFixed(c).slice(2) : \"\");\n}\n\n/**\n * Pad a string to a given length by adding 0 to the beginning\n * @param {Number} number\n * @param {Number} length\n */\nfunction pad(number, length) {\n\t// Create an array of the remaining length +1 and join it with 0's\n\treturn new Array((length || 2) + 1 - String(number).length).join(0) + number;\n}\n\n/**\n * Wrap a method with extended functionality, preserving the original function\n * @param {Object} obj The context object that the method belongs to \n * @param {String} method The name of the method to extend\n * @param {Function} func A wrapper function callback. This function is called with the same arguments\n * as the original function, except that the original function is unshifted and passed as the first \n * argument. \n */\nfunction wrap(obj, method, func) {\n\tvar proceed = obj[method];\n\tobj[method] = function () {\n\t\tvar args = Array.prototype.slice.call(arguments);\n\t\targs.unshift(proceed);\n\t\treturn func.apply(this, args);\n\t};\n}\n\n/**\n * Based on http://www.php.net/manual/en/function.strftime.php\n * @param {String} format\n * @param {Number} timestamp\n * @param {Boolean} capitalize\n */\ndateFormat = function (format, timestamp, capitalize) {\n\tif (!defined(timestamp) || isNaN(timestamp)) {\n\t\treturn 'Invalid date';\n\t}\n\tformat = pick(format, '%Y-%m-%d %H:%M:%S');\n\n\tvar date = new Date(timestamp),\n\t\tkey, // used in for constuct below\n\t\t// get the basic time values\n\t\thours = date[getHours](),\n\t\tday = date[getDay](),\n\t\tdayOfMonth = date[getDate](),\n\t\tmonth = date[getMonth](),\n\t\tfullYear = date[getFullYear](),\n\t\tlang = defaultOptions.lang,\n\t\tlangWeekdays = lang.weekdays,\n\n\t\t// List all format keys. Custom formats can be added from the outside. \n\t\treplacements = extend({\n\n\t\t\t// Day\n\t\t\t'a': langWeekdays[day].substr(0, 3), // Short weekday, like 'Mon'\n\t\t\t'A': langWeekdays[day], // Long weekday, like 'Monday'\n\t\t\t'd': pad(dayOfMonth), // Two digit day of the month, 01 to 31\n\t\t\t'e': dayOfMonth, // Day of the month, 1 through 31\n\n\t\t\t// Week (none implemented)\n\t\t\t//'W': weekNumber(),\n\n\t\t\t// Month\n\t\t\t'b': lang.shortMonths[month], // Short month, like 'Jan'\n\t\t\t'B': lang.months[month], // Long month, like 'January'\n\t\t\t'm': pad(month + 1), // Two digit month number, 01 through 12\n\n\t\t\t// Year\n\t\t\t'y': fullYear.toString().substr(2, 2), // Two digits year, like 09 for 2009\n\t\t\t'Y': fullYear, // Four digits year, like 2009\n\n\t\t\t// Time\n\t\t\t'H': pad(hours), // Two digits hours in 24h format, 00 through 23\n\t\t\t'I': pad((hours % 12) || 12), // Two digits hours in 12h format, 00 through 11\n\t\t\t'l': (hours % 12) || 12, // Hours in 12h format, 1 through 12\n\t\t\t'M': pad(date[getMinutes]()), // Two digits minutes, 00 through 59\n\t\t\t'p': hours < 12 ? 'AM' : 'PM', // Upper case AM or PM\n\t\t\t'P': hours < 12 ? 'am' : 'pm', // Lower case AM or PM\n\t\t\t'S': pad(date.getSeconds()), // Two digits seconds, 00 through  59\n\t\t\t'L': pad(mathRound(timestamp % 1000), 3) // Milliseconds (naming from Ruby)\n\t\t}, Highcharts.dateFormats);\n\n\n\t// do the replaces\n\tfor (key in replacements) {\n\t\twhile (format.indexOf('%' + key) !== -1) { // regex would do it in one line, but this is faster\n\t\t\tformat = format.replace('%' + key, typeof replacements[key] === 'function' ? replacements[key](timestamp) : replacements[key]);\n\t\t}\n\t}\n\n\t// Optionally capitalize the string and return\n\treturn capitalize ? format.substr(0, 1).toUpperCase() + format.substr(1) : format;\n};\n\n/** \n * Format a single variable. Similar to sprintf, without the % prefix.\n */\nfunction formatSingle(format, val) {\n\tvar floatRegex = /f$/,\n\t\tdecRegex = /\\.([0-9])/,\n\t\tlang = defaultOptions.lang,\n\t\tdecimals;\n\n\tif (floatRegex.test(format)) { // float\n\t\tdecimals = format.match(decRegex);\n\t\tdecimals = decimals ? decimals[1] : -1;\n\t\tval = numberFormat(\n\t\t\tval,\n\t\t\tdecimals,\n\t\t\tlang.decimalPoint,\n\t\t\tformat.indexOf(',') > -1 ? lang.thousandsSep : ''\n\t\t);\n\t} else {\n\t\tval = dateFormat(format, val);\n\t}\n\treturn val;\n}\n\n/**\n * Format a string according to a subset of the rules of Python's String.format method.\n */\nfunction format(str, ctx) {\n\tvar splitter = '{',\n\t\tisInside = false,\n\t\tsegment,\n\t\tvalueAndFormat,\n\t\tpath,\n\t\ti,\n\t\tlen,\n\t\tret = [],\n\t\tval,\n\t\tindex;\n\t\n\twhile ((index = str.indexOf(splitter)) !== -1) {\n\t\t\n\t\tsegment = str.slice(0, index);\n\t\tif (isInside) { // we're on the closing bracket looking back\n\t\t\t\n\t\t\tvalueAndFormat = segment.split(':');\n\t\t\tpath = valueAndFormat.shift().split('.'); // get first and leave format\n\t\t\tlen = path.length;\n\t\t\tval = ctx;\n\n\t\t\t// Assign deeper paths\n\t\t\tfor (i = 0; i < len; i++) {\n\t\t\t\tval = val[path[i]];\n\t\t\t}\n\n\t\t\t// Format the replacement\n\t\t\tif (valueAndFormat.length) {\n\t\t\t\tval = formatSingle(valueAndFormat.join(':'), val);\n\t\t\t}\n\n\t\t\t// Push the result and advance the cursor\n\t\t\tret.push(val);\n\t\t\t\n\t\t} else {\n\t\t\tret.push(segment);\n\t\t\t\n\t\t}\n\t\tstr = str.slice(index + 1); // the rest\n\t\tisInside = !isInside; // toggle\n\t\tsplitter = isInside ? '}' : '{'; // now look for next matching bracket\n\t}\n\tret.push(str);\n\treturn ret.join('');\n}\n\n/**\n * Get the magnitude of a number\n */\nfunction getMagnitude(num) {\n\treturn math.pow(10, mathFloor(math.log(num) / math.LN10));\n}\n\n/**\n * Take an interval and normalize it to multiples of 1, 2, 2.5 and 5\n * @param {Number} interval\n * @param {Array} multiples\n * @param {Number} magnitude\n * @param {Object} options\n */\nfunction normalizeTickInterval(interval, multiples, magnitude, options) {\n\tvar normalized, i;\n\n\t// round to a tenfold of 1, 2, 2.5 or 5\n\tmagnitude = pick(magnitude, 1);\n\tnormalized = interval / magnitude;\n\n\t// multiples for a linear scale\n\tif (!multiples) {\n\t\tmultiples = [1, 2, 2.5, 5, 10];\n\n\t\t// the allowDecimals option\n\t\tif (options && options.allowDecimals === false) {\n\t\t\tif (magnitude === 1) {\n\t\t\t\tmultiples = [1, 2, 5, 10];\n\t\t\t} else if (magnitude <= 0.1) {\n\t\t\t\tmultiples = [1 / magnitude];\n\t\t\t}\n\t\t}\n\t}\n\n\t// normalize the interval to the nearest multiple\n\tfor (i = 0; i < multiples.length; i++) {\n\t\tinterval = multiples[i];\n\t\tif (normalized <= (multiples[i] + (multiples[i + 1] || multiples[i])) / 2) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// multiply back to the correct magnitude\n\tinterval *= magnitude;\n\n\treturn interval;\n}\n\n/**\n * Get a normalized tick interval for dates. Returns a configuration object with\n * unit range (interval), count and name. Used to prepare data for getTimeTicks. \n * Previously this logic was part of getTimeTicks, but as getTimeTicks now runs\n * of segments in stock charts, the normalizing logic was extracted in order to \n * prevent it for running over again for each segment having the same interval. \n * #662, #697.\n */\nfunction normalizeTimeTickInterval(tickInterval, unitsOption) {\n\tvar units = unitsOption || [[\n\t\t\t\tMILLISECOND, // unit name\n\t\t\t\t[1, 2, 5, 10, 20, 25, 50, 100, 200, 500] // allowed multiples\n\t\t\t], [\n\t\t\t\tSECOND,\n\t\t\t\t[1, 2, 5, 10, 15, 30]\n\t\t\t], [\n\t\t\t\tMINUTE,\n\t\t\t\t[1, 2, 5, 10, 15, 30]\n\t\t\t], [\n\t\t\t\tHOUR,\n\t\t\t\t[1, 2, 3, 4, 6, 8, 12]\n\t\t\t], [\n\t\t\t\tDAY,\n\t\t\t\t[1, 2]\n\t\t\t], [\n\t\t\t\tWEEK,\n\t\t\t\t[1, 2]\n\t\t\t], [\n\t\t\t\tMONTH,\n\t\t\t\t[1, 2, 3, 4, 6]\n\t\t\t], [\n\t\t\t\tYEAR,\n\t\t\t\tnull\n\t\t\t]],\n\t\tunit = units[units.length - 1], // default unit is years\n\t\tinterval = timeUnits[unit[0]],\n\t\tmultiples = unit[1],\n\t\tcount,\n\t\ti;\n\t\t\n\t// loop through the units to find the one that best fits the tickInterval\n\tfor (i = 0; i < units.length; i++) {\n\t\tunit = units[i];\n\t\tinterval = timeUnits[unit[0]];\n\t\tmultiples = unit[1];\n\n\n\t\tif (units[i + 1]) {\n\t\t\t// lessThan is in the middle between the highest multiple and the next unit.\n\t\t\tvar lessThan = (interval * multiples[multiples.length - 1] +\n\t\t\t\t\t\ttimeUnits[units[i + 1][0]]) / 2;\n\n\t\t\t// break and keep the current unit\n\t\t\tif (tickInterval <= lessThan) {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t// prevent 2.5 years intervals, though 25, 250 etc. are allowed\n\tif (interval === timeUnits[YEAR] && tickInterval < 5 * interval) {\n\t\tmultiples = [1, 2, 5];\n\t}\n\n\t// get the count\n\tcount = normalizeTickInterval(\n\t\ttickInterval / interval, \n\t\tmultiples,\n\t\tunit[0] === YEAR ? getMagnitude(tickInterval / interval) : 1 // #1913\n\t);\n\t\n\treturn {\n\t\tunitRange: interval,\n\t\tcount: count,\n\t\tunitName: unit[0]\n\t};\n}\n\n/**\n * Set the tick positions to a time unit that makes sense, for example\n * on the first of each month or on every Monday. Return an array\n * with the time positions. Used in datetime axes as well as for grouping\n * data on a datetime axis.\n *\n * @param {Object} normalizedInterval The interval in axis values (ms) and the count\n * @param {Number} min The minimum in axis values\n * @param {Number} max The maximum in axis values\n * @param {Number} startOfWeek\n */\nfunction getTimeTicks(normalizedInterval, min, max, startOfWeek) {\n\tvar tickPositions = [],\n\t\ti,\n\t\thigherRanks = {},\n\t\tuseUTC = defaultOptions.global.useUTC,\n\t\tminYear, // used in months and years as a basis for Date.UTC()\n\t\tminDate = new Date(min),\n\t\tinterval = normalizedInterval.unitRange,\n\t\tcount = normalizedInterval.count;\n\n\tif (defined(min)) { // #1300\n\t\tif (interval >= timeUnits[SECOND]) { // second\n\t\t\tminDate.setMilliseconds(0);\n\t\t\tminDate.setSeconds(interval >= timeUnits[MINUTE] ? 0 :\n\t\t\t\tcount * mathFloor(minDate.getSeconds() / count));\n\t\t}\n\t\n\t\tif (interval >= timeUnits[MINUTE]) { // minute\n\t\t\tminDate[setMinutes](interval >= timeUnits[HOUR] ? 0 :\n\t\t\t\tcount * mathFloor(minDate[getMinutes]() / count));\n\t\t}\n\t\n\t\tif (interval >= timeUnits[HOUR]) { // hour\n\t\t\tminDate[setHours](interval >= timeUnits[DAY] ? 0 :\n\t\t\t\tcount * mathFloor(minDate[getHours]() / count));\n\t\t}\n\t\n\t\tif (interval >= timeUnits[DAY]) { // day\n\t\t\tminDate[setDate](interval >= timeUnits[MONTH] ? 1 :\n\t\t\t\tcount * mathFloor(minDate[getDate]() / count));\n\t\t}\n\t\n\t\tif (interval >= timeUnits[MONTH]) { // month\n\t\t\tminDate[setMonth](interval >= timeUnits[YEAR] ? 0 :\n\t\t\t\tcount * mathFloor(minDate[getMonth]() / count));\n\t\t\tminYear = minDate[getFullYear]();\n\t\t}\n\t\n\t\tif (interval >= timeUnits[YEAR]) { // year\n\t\t\tminYear -= minYear % count;\n\t\t\tminDate[setFullYear](minYear);\n\t\t}\n\t\n\t\t// week is a special case that runs outside the hierarchy\n\t\tif (interval === timeUnits[WEEK]) {\n\t\t\t// get start of current week, independent of count\n\t\t\tminDate[setDate](minDate[getDate]() - minDate[getDay]() +\n\t\t\t\tpick(startOfWeek, 1));\n\t\t}\n\t\n\t\n\t\t// get tick positions\n\t\ti = 1;\n\t\tminYear = minDate[getFullYear]();\n\t\tvar time = minDate.getTime(),\n\t\t\tminMonth = minDate[getMonth](),\n\t\t\tminDateDate = minDate[getDate](),\n\t\t\ttimezoneOffset = useUTC ? \n\t\t\t\t0 : \n\t\t\t\t(24 * 3600 * 1000 + minDate.getTimezoneOffset() * 60 * 1000) % (24 * 3600 * 1000); // #950\n\t\n\t\t// iterate and add tick positions at appropriate values\n\t\twhile (time < max) {\n\t\t\ttickPositions.push(time);\n\t\n\t\t\t// if the interval is years, use Date.UTC to increase years\n\t\t\tif (interval === timeUnits[YEAR]) {\n\t\t\t\ttime = makeTime(minYear + i * count, 0);\n\t\n\t\t\t// if the interval is months, use Date.UTC to increase months\n\t\t\t} else if (interval === timeUnits[MONTH]) {\n\t\t\t\ttime = makeTime(minYear, minMonth + i * count);\n\t\n\t\t\t// if we're using global time, the interval is not fixed as it jumps\n\t\t\t// one hour at the DST crossover\n\t\t\t} else if (!useUTC && (interval === timeUnits[DAY] || interval === timeUnits[WEEK])) {\n\t\t\t\ttime = makeTime(minYear, minMonth, minDateDate +\n\t\t\t\t\ti * count * (interval === timeUnits[DAY] ? 1 : 7));\n\t\n\t\t\t// else, the interval is fixed and we use simple addition\n\t\t\t} else {\n\t\t\t\ttime += interval * count;\n\t\t\t}\n\t\n\t\t\ti++;\n\t\t}\n\t\n\t\t// push the last time\n\t\ttickPositions.push(time);\n\n\n\t\t// mark new days if the time is dividible by day (#1649, #1760)\n\t\teach(grep(tickPositions, function (time) {\n\t\t\treturn interval <= timeUnits[HOUR] && time % timeUnits[DAY] === timezoneOffset;\n\t\t}), function (time) {\n\t\t\thigherRanks[time] = DAY;\n\t\t});\n\t}\n\n\n\t// record information on the chosen unit - for dynamic label formatter\n\ttickPositions.info = extend(normalizedInterval, {\n\t\thigherRanks: higherRanks,\n\t\ttotalRange: interval * count\n\t});\n\n\treturn tickPositions;\n}\n\n/**\n * Helper class that contains variuos counters that are local to the chart.\n */\nfunction ChartCounters() {\n\tthis.color = 0;\n\tthis.symbol = 0;\n}\n\nChartCounters.prototype =  {\n\t/**\n\t * Wraps the color counter if it reaches the specified length.\n\t */\n\twrapColor: function (length) {\n\t\tif (this.color >= length) {\n\t\t\tthis.color = 0;\n\t\t}\n\t},\n\n\t/**\n\t * Wraps the symbol counter if it reaches the specified length.\n\t */\n\twrapSymbol: function (length) {\n\t\tif (this.symbol >= length) {\n\t\t\tthis.symbol = 0;\n\t\t}\n\t}\n};\n\n\n/**\n * Utility method that sorts an object array and keeping the order of equal items.\n * ECMA script standard does not specify the behaviour when items are equal.\n */\nfunction stableSort(arr, sortFunction) {\n\tvar length = arr.length,\n\t\tsortValue,\n\t\ti;\n\n\t// Add index to each item\n\tfor (i = 0; i < length; i++) {\n\t\tarr[i].ss_i = i; // stable sort index\n\t}\n\n\tarr.sort(function (a, b) {\n\t\tsortValue = sortFunction(a, b);\n\t\treturn sortValue === 0 ? a.ss_i - b.ss_i : sortValue;\n\t});\n\n\t// Remove index from items\n\tfor (i = 0; i < length; i++) {\n\t\tdelete arr[i].ss_i; // stable sort index\n\t}\n}\n\n/**\n * Non-recursive method to find the lowest member of an array. Math.min raises a maximum\n * call stack size exceeded error in Chrome when trying to apply more than 150.000 points. This\n * method is slightly slower, but safe.\n */\nfunction arrayMin(data) {\n\tvar i = data.length,\n\t\tmin = data[0];\n\n\twhile (i--) {\n\t\tif (data[i] < min) {\n\t\t\tmin = data[i];\n\t\t}\n\t}\n\treturn min;\n}\n\n/**\n * Non-recursive method to find the lowest member of an array. Math.min raises a maximum\n * call stack size exceeded error in Chrome when trying to apply more than 150.000 points. This\n * method is slightly slower, but safe.\n */\nfunction arrayMax(data) {\n\tvar i = data.length,\n\t\tmax = data[0];\n\n\twhile (i--) {\n\t\tif (data[i] > max) {\n\t\t\tmax = data[i];\n\t\t}\n\t}\n\treturn max;\n}\n\n/**\n * Utility method that destroys any SVGElement or VMLElement that are properties on the given object.\n * It loops all properties and invokes destroy if there is a destroy method. The property is\n * then delete'ed.\n * @param {Object} The object to destroy properties on\n * @param {Object} Exception, do not destroy this property, only delete it.\n */\nfunction destroyObjectProperties(obj, except) {\n\tvar n;\n\tfor (n in obj) {\n\t\t// If the object is non-null and destroy is defined\n\t\tif (obj[n] && obj[n] !== except && obj[n].destroy) {\n\t\t\t// Invoke the destroy\n\t\t\tobj[n].destroy();\n\t\t}\n\n\t\t// Delete the property from the object.\n\t\tdelete obj[n];\n\t}\n}\n\n\n/**\n * Discard an element by moving it to the bin and delete\n * @param {Object} The HTML node to discard\n */\nfunction discardElement(element) {\n\t// create a garbage bin element, not part of the DOM\n\tif (!garbageBin) {\n\t\tgarbageBin = createElement(DIV);\n\t}\n\n\t// move the node and empty bin\n\tif (element) {\n\t\tgarbageBin.appendChild(element);\n\t}\n\tgarbageBin.innerHTML = '';\n}\n\n/**\n * Provide error messages for debugging, with links to online explanation \n */\nfunction error(code, stop) {\n\tvar msg = 'Highcharts error #' + code + ': www.highcharts.com/errors/' + code;\n\tif (stop) {\n\t\tthrow msg;\n\t} else if (win.console) {\n\t\tconsole.log(msg);\n\t}\n}\n\n/**\n * Fix JS round off float errors\n * @param {Number} num\n */\nfunction correctFloat(num) {\n\treturn parseFloat(\n\t\tnum.toPrecision(14)\n\t);\n}\n\n/**\n * Set the global animation to either a given value, or fall back to the\n * given chart's animation option\n * @param {Object} animation\n * @param {Object} chart\n */\nfunction setAnimation(animation, chart) {\n\tglobalAnimation = pick(animation, chart.animation);\n}\n\n/**\n * The time unit lookup\n */\n/*jslint white: true*/\ntimeUnits = hash(\n\tMILLISECOND, 1,\n\tSECOND, 1000,\n\tMINUTE, 60000,\n\tHOUR, 3600000,\n\tDAY, 24 * 3600000,\n\tWEEK, 7 * 24 * 3600000,\n\tMONTH, 31 * 24 * 3600000,\n\tYEAR, 31556952000\n);\n/*jslint white: false*/\n/**\n * Path interpolation algorithm used across adapters\n */\npathAnim = {\n\t/**\n\t * Prepare start and end values so that the path can be animated one to one\n\t */\n\tinit: function (elem, fromD, toD) {\n\t\tfromD = fromD || '';\n\t\tvar shift = elem.shift,\n\t\t\tbezier = fromD.indexOf('C') > -1,\n\t\t\tnumParams = bezier ? 7 : 3,\n\t\t\tendLength,\n\t\t\tslice,\n\t\t\ti,\n\t\t\tstart = fromD.split(' '),\n\t\t\tend = [].concat(toD), // copy\n\t\t\tstartBaseLine,\n\t\t\tendBaseLine,\n\t\t\tsixify = function (arr) { // in splines make move points have six parameters like bezier curves\n\t\t\t\ti = arr.length;\n\t\t\t\twhile (i--) {\n\t\t\t\t\tif (arr[i] === M) {\n\t\t\t\t\t\tarr.splice(i + 1, 0, arr[i + 1], arr[i + 2], arr[i + 1], arr[i + 2]);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t};\n\n\t\tif (bezier) {\n\t\t\tsixify(start);\n\t\t\tsixify(end);\n\t\t}\n\n\t\t// pull out the base lines before padding\n\t\tif (elem.isArea) {\n\t\t\tstartBaseLine = start.splice(start.length - 6, 6);\n\t\t\tendBaseLine = end.splice(end.length - 6, 6);\n\t\t}\n\n\t\t// if shifting points, prepend a dummy point to the end path\n\t\tif (shift <= end.length / numParams && start.length === end.length) {\n\t\t\twhile (shift--) {\n\t\t\t\tend = [].concat(end).splice(0, numParams).concat(end);\n\t\t\t}\n\t\t}\n\t\telem.shift = 0; // reset for following animations\n\n\t\t// copy and append last point until the length matches the end length\n\t\tif (start.length) {\n\t\t\tendLength = end.length;\n\t\t\twhile (start.length < endLength) {\n\n\t\t\t\t//bezier && sixify(start);\n\t\t\t\tslice = [].concat(start).splice(start.length - numParams, numParams);\n\t\t\t\tif (bezier) { // disable first control point\n\t\t\t\t\tslice[numParams - 6] = slice[numParams - 2];\n\t\t\t\t\tslice[numParams - 5] = slice[numParams - 1];\n\t\t\t\t}\n\t\t\t\tstart = start.concat(slice);\n\t\t\t}\n\t\t}\n\n\t\tif (startBaseLine) { // append the base lines for areas\n\t\t\tstart = start.concat(startBaseLine);\n\t\t\tend = end.concat(endBaseLine);\n\t\t}\n\t\treturn [start, end];\n\t},\n\n\t/**\n\t * Interpolate each value of the path and return the array\n\t */\n\tstep: function (start, end, pos, complete) {\n\t\tvar ret = [],\n\t\t\ti = start.length,\n\t\t\tstartVal;\n\n\t\tif (pos === 1) { // land on the final path without adjustment points appended in the ends\n\t\t\tret = complete;\n\n\t\t} else if (i === end.length && pos < 1) {\n\t\t\twhile (i--) {\n\t\t\t\tstartVal = parseFloat(start[i]);\n\t\t\t\tret[i] =\n\t\t\t\t\tisNaN(startVal) ? // a letter instruction like M or L\n\t\t\t\t\t\tstart[i] :\n\t\t\t\t\t\tpos * (parseFloat(end[i] - startVal)) + startVal;\n\n\t\t\t}\n\t\t} else { // if animation is finished or length not matching, land on right value\n\t\t\tret = end;\n\t\t}\n\t\treturn ret;\n\t}\n};\n\n(function ($) {\n\t/**\n\t * The default HighchartsAdapter for jQuery\n\t */\n\twin.HighchartsAdapter = win.HighchartsAdapter || ($ && {\n\t\t\n\t\t/**\n\t\t * Initialize the adapter by applying some extensions to jQuery\n\t\t */\n\t\tinit: function (pathAnim) {\n\t\t\t\n\t\t\t// extend the animate function to allow SVG animations\n\t\t\tvar Fx = $.fx,\n\t\t\t\tStep = Fx.step,\n\t\t\t\tdSetter,\n\t\t\t\tTween = $.Tween,\n\t\t\t\tpropHooks = Tween && Tween.propHooks,\n\t\t\t\topacityHook = $.cssHooks.opacity;\n\t\t\t\n\t\t\t/*jslint unparam: true*//* allow unused param x in this function */\n\t\t\t$.extend($.easing, {\n\t\t\t\teaseOutQuad: function (x, t, b, c, d) {\n\t\t\t\t\treturn -c * (t /= d) * (t - 2) + b;\n\t\t\t\t}\n\t\t\t});\n\t\t\t/*jslint unparam: false*/\n\t\t\n\t\t\t// extend some methods to check for elem.attr, which means it is a Highcharts SVG object\n\t\t\t$.each(['cur', '_default', 'width', 'height', 'opacity'], function (i, fn) {\n\t\t\t\tvar obj = Step,\n\t\t\t\t\tbase,\n\t\t\t\t\telem;\n\t\t\t\t\t\n\t\t\t\t// Handle different parent objects\n\t\t\t\tif (fn === 'cur') {\n\t\t\t\t\tobj = Fx.prototype; // 'cur', the getter, relates to Fx.prototype\n\t\t\t\t\n\t\t\t\t} else if (fn === '_default' && Tween) { // jQuery 1.8 model\n\t\t\t\t\tobj = propHooks[fn];\n\t\t\t\t\tfn = 'set';\n\t\t\t\t}\n\t\t\n\t\t\t\t// Overwrite the method\n\t\t\t\tbase = obj[fn];\n\t\t\t\tif (base) { // step.width and step.height don't exist in jQuery < 1.7\n\t\t\n\t\t\t\t\t// create the extended function replacement\n\t\t\t\t\tobj[fn] = function (fx) {\n\t\t\n\t\t\t\t\t\t// Fx.prototype.cur does not use fx argument\n\t\t\t\t\t\tfx = i ? fx : this;\n\n\t\t\t\t\t\t// Don't run animations on textual properties like align (#1821)\n\t\t\t\t\t\tif (fx.prop === 'align') {\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\n\t\t\t\t\t\t// shortcut\n\t\t\t\t\t\telem = fx.elem;\n\t\t\n\t\t\t\t\t\t// Fx.prototype.cur returns the current value. The other ones are setters\n\t\t\t\t\t\t// and returning a value has no effect.\n\t\t\t\t\t\treturn elem.attr ? // is SVG element wrapper\n\t\t\t\t\t\t\telem.attr(fx.prop, fn === 'cur' ? UNDEFINED : fx.now) : // apply the SVG wrapper's method\n\t\t\t\t\t\t\tbase.apply(this, arguments); // use jQuery's built-in method\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// Extend the opacity getter, needed for fading opacity with IE9 and jQuery 1.10+\n\t\t\twrap(opacityHook, 'get', function (proceed, elem, computed) {\n\t\t\t\treturn elem.attr ? (elem.opacity || 0) : proceed.call(this, elem, computed);\n\t\t\t});\n\t\t\t\n\t\t\t\n\t\t\t// Define the setter function for d (path definitions)\n\t\t\tdSetter = function (fx) {\n\t\t\t\tvar elem = fx.elem,\n\t\t\t\t\tends;\n\t\t\n\t\t\t\t// Normally start and end should be set in state == 0, but sometimes,\n\t\t\t\t// for reasons unknown, this doesn't happen. Perhaps state == 0 is skipped\n\t\t\t\t// in these cases\n\t\t\t\tif (!fx.started) {\n\t\t\t\t\tends = pathAnim.init(elem, elem.d, elem.toD);\n\t\t\t\t\tfx.start = ends[0];\n\t\t\t\t\tfx.end = ends[1];\n\t\t\t\t\tfx.started = true;\n\t\t\t\t}\n\t\t\n\t\t\n\t\t\t\t// interpolate each value of the path\n\t\t\t\telem.attr('d', pathAnim.step(fx.start, fx.end, fx.pos, elem.toD));\n\t\t\t};\n\t\t\t\n\t\t\t// jQuery 1.8 style\n\t\t\tif (Tween) {\n\t\t\t\tpropHooks.d = {\n\t\t\t\t\tset: dSetter\n\t\t\t\t};\n\t\t\t// pre 1.8\n\t\t\t} else {\n\t\t\t\t// animate paths\n\t\t\t\tStep.d = dSetter;\n\t\t\t}\n\t\t\t\n\t\t\t/**\n\t\t\t * Utility for iterating over an array. Parameters are reversed compared to jQuery.\n\t\t\t * @param {Array} arr\n\t\t\t * @param {Function} fn\n\t\t\t */\n\t\t\tthis.each = Array.prototype.forEach ?\n\t\t\t\tfunction (arr, fn) { // modern browsers\n\t\t\t\t\treturn Array.prototype.forEach.call(arr, fn);\n\t\t\t\t\t\n\t\t\t\t} : \n\t\t\t\tfunction (arr, fn) { // legacy\n\t\t\t\t\tvar i = 0, \n\t\t\t\t\t\tlen = arr.length;\n\t\t\t\t\tfor (; i < len; i++) {\n\t\t\t\t\t\tif (fn.call(arr[i], arr[i], i, arr) === false) {\n\t\t\t\t\t\t\treturn i;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t\n\t\t\t/**\n\t\t\t * Register Highcharts as a plugin in the respective framework\n\t\t\t */\n\t\t\t$.fn.highcharts = function () {\n\t\t\t\tvar constr = 'Chart', // default constructor\n\t\t\t\t\targs = arguments,\n\t\t\t\t\toptions,\n\t\t\t\t\tret,\n\t\t\t\t\tchart;\n\n\t\t\t\tif (isString(args[0])) {\n\t\t\t\t\tconstr = args[0];\n\t\t\t\t\targs = Array.prototype.slice.call(args, 1); \n\t\t\t\t}\n\t\t\t\toptions = args[0];\n\n\t\t\t\t// Create the chart\n\t\t\t\tif (options !== UNDEFINED) {\n\t\t\t\t\t/*jslint unused:false*/\n\t\t\t\t\toptions.chart = options.chart || {};\n\t\t\t\t\toptions.chart.renderTo = this[0];\n\t\t\t\t\tchart = new Highcharts[constr](options, args[1]);\n\t\t\t\t\tret = this;\n\t\t\t\t\t/*jslint unused:true*/\n\t\t\t\t}\n\n\t\t\t\t// When called without parameters or with the return argument, get a predefined chart\n\t\t\t\tif (options === UNDEFINED) {\n\t\t\t\t\tret = charts[attr(this[0], 'data-highcharts-chart')];\n\t\t\t\t}\t\n\n\t\t\t\treturn ret;\n\t\t\t};\n\n\t\t},\n\n\t\t\n\t\t/**\n\t\t * Downloads a script and executes a callback when done.\n\t\t * @param {String} scriptLocation\n\t\t * @param {Function} callback\n\t\t */\n\t\tgetScript: $.getScript,\n\t\t\n\t\t/**\n\t\t * Return the index of an item in an array, or -1 if not found\n\t\t */\n\t\tinArray: $.inArray,\n\t\t\n\t\t/**\n\t\t * A direct link to jQuery methods. MooTools and Prototype adapters must be implemented for each case of method.\n\t\t * @param {Object} elem The HTML element\n\t\t * @param {String} method Which method to run on the wrapped element\n\t\t */\n\t\tadapterRun: function (elem, method) {\n\t\t\treturn $(elem)[method]();\n\t\t},\n\t\n\t\t/**\n\t\t * Filter an array\n\t\t */\n\t\tgrep: $.grep,\n\t\n\t\t/**\n\t\t * Map an array\n\t\t * @param {Array} arr\n\t\t * @param {Function} fn\n\t\t */\n\t\tmap: function (arr, fn) {\n\t\t\t//return jQuery.map(arr, fn);\n\t\t\tvar results = [],\n\t\t\t\ti = 0,\n\t\t\t\tlen = arr.length;\n\t\t\tfor (; i < len; i++) {\n\t\t\t\tresults[i] = fn.call(arr[i], arr[i], i, arr);\n\t\t\t}\n\t\t\treturn results;\n\t\n\t\t},\n\t\n\t\t/**\n\t\t * Get the position of an element relative to the top left of the page\n\t\t */\n\t\toffset: function (el) {\n\t\t\treturn $(el).offset();\n\t\t},\n\t\n\t\t/**\n\t\t * Add an event listener\n\t\t * @param {Object} el A HTML element or custom object\n\t\t * @param {String} event The event type\n\t\t * @param {Function} fn The event handler\n\t\t */\n\t\taddEvent: function (el, event, fn) {\n\t\t\t$(el).bind(event, fn);\n\t\t},\n\t\n\t\t/**\n\t\t * Remove event added with addEvent\n\t\t * @param {Object} el The object\n\t\t * @param {String} eventType The event type. Leave blank to remove all events.\n\t\t * @param {Function} handler The function to remove\n\t\t */\n\t\tremoveEvent: function (el, eventType, handler) {\n\t\t\t// workaround for jQuery issue with unbinding custom events:\n\t\t\t// http://forum.jQuery.com/topic/javascript-error-when-unbinding-a-custom-event-using-jQuery-1-4-2\n\t\t\tvar func = doc.removeEventListener ? 'removeEventListener' : 'detachEvent';\n\t\t\tif (doc[func] && el && !el[func]) {\n\t\t\t\tel[func] = function () {};\n\t\t\t}\n\t\n\t\t\t$(el).unbind(eventType, handler);\n\t\t},\n\t\n\t\t/**\n\t\t * Fire an event on a custom object\n\t\t * @param {Object} el\n\t\t * @param {String} type\n\t\t * @param {Object} eventArguments\n\t\t * @param {Function} defaultFunction\n\t\t */\n\t\tfireEvent: function (el, type, eventArguments, defaultFunction) {\n\t\t\tvar event = $.Event(type),\n\t\t\t\tdetachedType = 'detached' + type,\n\t\t\t\tdefaultPrevented;\n\t\n\t\t\t// Remove warnings in Chrome when accessing layerX and layerY. Although Highcharts\n\t\t\t// never uses these properties, Chrome includes them in the default click event and\n\t\t\t// raises the warning when they are copied over in the extend statement below.\n\t\t\t//\n\t\t\t// To avoid problems in IE (see #1010) where we cannot delete the properties and avoid\n\t\t\t// testing if they are there (warning in chrome) the only option is to test if running IE.\n\t\t\tif (!isIE && eventArguments) {\n\t\t\t\tdelete eventArguments.layerX;\n\t\t\t\tdelete eventArguments.layerY;\n\t\t\t}\n\t\n\t\t\textend(event, eventArguments);\n\t\n\t\t\t// Prevent jQuery from triggering the object method that is named the\n\t\t\t// same as the event. For example, if the event is 'select', jQuery\n\t\t\t// attempts calling el.select and it goes into a loop.\n\t\t\tif (el[type]) {\n\t\t\t\tel[detachedType] = el[type];\n\t\t\t\tel[type] = null;\n\t\t\t}\n\t\n\t\t\t// Wrap preventDefault and stopPropagation in try/catch blocks in\n\t\t\t// order to prevent JS errors when cancelling events on non-DOM\n\t\t\t// objects. #615.\n\t\t\t/*jslint unparam: true*/\n\t\t\t$.each(['preventDefault', 'stopPropagation'], function (i, fn) {\n\t\t\t\tvar base = event[fn];\n\t\t\t\tevent[fn] = function () {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tbase.call(event);\n\t\t\t\t\t} catch (e) {\n\t\t\t\t\t\tif (fn === 'preventDefault') {\n\t\t\t\t\t\t\tdefaultPrevented = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t});\n\t\t\t/*jslint unparam: false*/\n\t\n\t\t\t// trigger it\n\t\t\t$(el).trigger(event);\n\t\n\t\t\t// attach the method\n\t\t\tif (el[detachedType]) {\n\t\t\t\tel[type] = el[detachedType];\n\t\t\t\tel[detachedType] = null;\n\t\t\t}\n\t\n\t\t\tif (defaultFunction && !event.isDefaultPrevented() && !defaultPrevented) {\n\t\t\t\tdefaultFunction(event);\n\t\t\t}\n\t\t},\n\t\t\n\t\t/**\n\t\t * Extension method needed for MooTools\n\t\t */\n\t\twashMouseEvent: function (e) {\n\t\t\tvar ret = e.originalEvent || e;\n\t\t\t\n\t\t\t// computed by jQuery, needed by IE8\n\t\t\tif (ret.pageX === UNDEFINED) { // #1236\n\t\t\t\tret.pageX = e.pageX;\n\t\t\t\tret.pageY = e.pageY;\n\t\t\t}\n\t\t\t\n\t\t\treturn ret;\n\t\t},\n\t\n\t\t/**\n\t\t * Animate a HTML element or SVG element wrapper\n\t\t * @param {Object} el\n\t\t * @param {Object} params\n\t\t * @param {Object} options jQuery-like animation options: duration, easing, callback\n\t\t */\n\t\tanimate: function (el, params, options) {\n\t\t\tvar $el = $(el);\n\t\t\tif (!el.style) {\n\t\t\t\tel.style = {}; // #1881\n\t\t\t}\n\t\t\tif (params.d) {\n\t\t\t\tel.toD = params.d; // keep the array form for paths, used in $.fx.step.d\n\t\t\t\tparams.d = 1; // because in jQuery, animating to an array has a different meaning\n\t\t\t}\n\t\n\t\t\t$el.stop();\n\t\t\tif (params.opacity !== UNDEFINED && el.attr) {\n\t\t\t\tparams.opacity += 'px'; // force jQuery to use same logic as width and height (#2161)\n\t\t\t}\n\t\t\t$el.animate(params, options);\n\t\n\t\t},\n\t\t/**\n\t\t * Stop running animation\n\t\t */\n\t\tstop: function (el) {\n\t\t\t$(el).stop();\n\t\t}\n\t});\n}(win.jQuery));\n\n\n// check for a custom HighchartsAdapter defined prior to this file\nvar globalAdapter = win.HighchartsAdapter,\n\tadapter = globalAdapter || {};\n\t\n// Initialize the adapter\nif (globalAdapter) {\n\tglobalAdapter.init.call(globalAdapter, pathAnim);\n}\n\n\n// Utility functions. If the HighchartsAdapter is not defined, adapter is an empty object\n// and all the utility functions will be null. In that case they are populated by the\n// default adapters below.\nvar adapterRun = adapter.adapterRun,\n\tgetScript = adapter.getScript,\n\tinArray = adapter.inArray,\n\teach = adapter.each,\n\tgrep = adapter.grep,\n\toffset = adapter.offset,\n\tmap = adapter.map,\n\taddEvent = adapter.addEvent,\n\tremoveEvent = adapter.removeEvent,\n\tfireEvent = adapter.fireEvent,\n\twashMouseEvent = adapter.washMouseEvent,\n\tanimate = adapter.animate,\n\tstop = adapter.stop;\n\n\n\n/* ****************************************************************************\n * Handle the options                                                         *\n *****************************************************************************/\nvar\n\ndefaultLabelOptions = {\n\tenabled: true,\n\t// rotation: 0,\n\t// align: 'center',\n\tx: 0,\n\ty: 15,\n\t/*formatter: function () {\n\t\treturn this.value;\n\t},*/\n\tstyle: {\n\t\tcolor: '#666',\n\t\tcursor: 'default',\n\t\tfontSize: '11px',\n\t\tlineHeight: '14px'\n\t}\n};\n\ndefaultOptions = {\n\tcolors: ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', '#492970',\n\t\t'#f28f43', '#77a1e5', '#c42525', '#a6c96a'],\n\tsymbols: ['circle', 'diamond', 'square', 'triangle', 'triangle-down'],\n\tlang: {\n\t\tloading: 'Loading...',\n\t\tmonths: ['January', 'February', 'March', 'April', 'May', 'June', 'July',\n\t\t\t\t'August', 'September', 'October', 'November', 'December'],\n\t\tshortMonths: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'],\n\t\tweekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],\n\t\tdecimalPoint: '.',\n\t\tnumericSymbols: ['k', 'M', 'G', 'T', 'P', 'E'], // SI prefixes used in axis labels\n\t\tresetZoom: 'Reset zoom',\n\t\tresetZoomTitle: 'Reset zoom level 1:1',\n\t\tthousandsSep: ','\n\t},\n\tglobal: {\n\t\tuseUTC: true,\n\t\tcanvasToolsURL: 'http://code.highcharts.com/3.0.6/modules/canvas-tools.js',\n\t\tVMLRadialGradientURL: 'http://code.highcharts.com/3.0.6/gfx/vml-radial-gradient.png'\n\t},\n\tchart: {\n\t\t//animation: true,\n\t\t//alignTicks: false,\n\t\t//reflow: true,\n\t\t//className: null,\n\t\t//events: { load, selection },\n\t\t//margin: [null],\n\t\t//marginTop: null,\n\t\t//marginRight: null,\n\t\t//marginBottom: null,\n\t\t//marginLeft: null,\n\t\tborderColor: '#4572A7',\n\t\t//borderWidth: 0,\n\t\tborderRadius: 5,\n\t\tdefaultSeriesType: 'line',\n\t\tignoreHiddenSeries: true,\n\t\t//inverted: false,\n\t\t//shadow: false,\n\t\tspacing: [10, 10, 15, 10],\n\t\t//spacingTop: 10,\n\t\t//spacingRight: 10,\n\t\t//spacingBottom: 15,\n\t\t//spacingLeft: 10,\n\t\tstyle: {\n\t\t\tfontFamily: '\"Lucida Grande\", \"Lucida Sans Unicode\", Verdana, Arial, Helvetica, sans-serif', // default font\n\t\t\tfontSize: '12px'\n\t\t},\n\t\tbackgroundColor: '#FFFFFF',\n\t\t//plotBackgroundColor: null,\n\t\tplotBorderColor: '#C0C0C0',\n\t\t//plotBorderWidth: 0,\n\t\t//plotShadow: false,\n\t\t//zoomType: ''\n\t\tresetZoomButton: {\n\t\t\ttheme: {\n\t\t\t\tzIndex: 20\n\t\t\t},\n\t\t\tposition: {\n\t\t\t\talign: 'right',\n\t\t\t\tx: -10,\n\t\t\t\t//verticalAlign: 'top',\n\t\t\t\ty: 10\n\t\t\t}\n\t\t\t// relativeTo: 'plot'\n\t\t}\n\t},\n\ttitle: {\n\t\ttext: 'Chart title',\n\t\talign: 'center',\n\t\t// floating: false,\n\t\tmargin: 15,\n\t\t// x: 0,\n\t\t// verticalAlign: 'top',\n\t\t// y: null,\n\t\tstyle: {\n\t\t\tcolor: '#274b6d',//#3E576F',\n\t\t\tfontSize: '16px'\n\t\t}\n\n\t},\n\tsubtitle: {\n\t\ttext: '',\n\t\talign: 'center',\n\t\t// floating: false\n\t\t// x: 0,\n\t\t// verticalAlign: 'top',\n\t\t// y: null,\n\t\tstyle: {\n\t\t\tcolor: '#4d759e'\n\t\t}\n\t},\n\n\tplotOptions: {\n\t\tline: { // base series options\n\t\t\tallowPointSelect: false,\n\t\t\tshowCheckbox: false,\n\t\t\tanimation: {\n\t\t\t\tduration: 1000\n\t\t\t},\n\t\t\t//connectNulls: false,\n\t\t\t//cursor: 'default',\n\t\t\t//clip: true,\n\t\t\t//dashStyle: null,\n\t\t\t//enableMouseTracking: true,\n\t\t\tevents: {},\n\t\t\t//legendIndex: 0,\n\t\t\tlineWidth: 2,\n\t\t\t//shadow: false,\n\t\t\t// stacking: null,\n\t\t\tmarker: {\n\t\t\t\tenabled: true,\n\t\t\t\t//symbol: null,\n\t\t\t\tlineWidth: 0,\n\t\t\t\tradius: 4,\n\t\t\t\tlineColor: '#FFFFFF',\n\t\t\t\t//fillColor: null,\n\t\t\t\tstates: { // states for a single point\n\t\t\t\t\thover: {\n\t\t\t\t\t\tenabled: true\n\t\t\t\t\t\t//radius: base + 2\n\t\t\t\t\t},\n\t\t\t\t\tselect: {\n\t\t\t\t\t\tfillColor: '#FFFFFF',\n\t\t\t\t\t\tlineColor: '#000000',\n\t\t\t\t\t\tlineWidth: 2\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\tpoint: {\n\t\t\t\tevents: {}\n\t\t\t},\n\t\t\tdataLabels: merge(defaultLabelOptions, {\n\t\t\t\talign: 'center',\n\t\t\t\tenabled: false,\n\t\t\t\tformatter: function () {\n\t\t\t\t\treturn this.y === null ? '' : numberFormat(this.y, -1);\n\t\t\t\t},\n\t\t\t\tverticalAlign: 'bottom', // above singular point\n\t\t\t\ty: 0\n\t\t\t\t// backgroundColor: undefined,\n\t\t\t\t// borderColor: undefined,\n\t\t\t\t// borderRadius: undefined,\n\t\t\t\t// borderWidth: undefined,\n\t\t\t\t// padding: 3,\n\t\t\t\t// shadow: false\n\t\t\t}),\n\t\t\tcropThreshold: 300, // draw points outside the plot area when the number of points is less than this\n\t\t\tpointRange: 0,\n\t\t\t//pointStart: 0,\n\t\t\t//pointInterval: 1,\n\t\t\tshowInLegend: true,\n\t\t\tstates: { // states for the entire series\n\t\t\t\thover: {\n\t\t\t\t\t//enabled: false,\n\t\t\t\t\t//lineWidth: base + 1,\n\t\t\t\t\tmarker: {\n\t\t\t\t\t\t// lineWidth: base + 1,\n\t\t\t\t\t\t// radius: base + 1\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tselect: {\n\t\t\t\t\tmarker: {}\n\t\t\t\t}\n\t\t\t},\n\t\t\tstickyTracking: true\n\t\t\t//tooltip: {\n\t\t\t\t//pointFormat: '<span style=\"color:{series.color}\">{series.name}</span>: <b>{point.y}</b>'\n\t\t\t\t//valueDecimals: null,\n\t\t\t\t//xDateFormat: '%A, %b %e, %Y',\n\t\t\t\t//valuePrefix: '',\n\t\t\t\t//ySuffix: ''\t\t\t\t\n\t\t\t//}\n\t\t\t// turboThreshold: 1000\n\t\t\t// zIndex: null\n\t\t}\n\t},\n\tlabels: {\n\t\t//items: [],\n\t\tstyle: {\n\t\t\t//font: defaultFont,\n\t\t\tposition: ABSOLUTE,\n\t\t\tcolor: '#3E576F'\n\t\t}\n\t},\n\tlegend: {\n\t\tenabled: true,\n\t\talign: 'center',\n\t\t//floating: false,\n\t\tlayout: 'horizontal',\n\t\tlabelFormatter: function () {\n\t\t\treturn this.name;\n\t\t},\n\t\tborderWidth: 1,\n\t\tborderColor: '#909090',\n\t\tborderRadius: 5,\n\t\tnavigation: {\n\t\t\t// animation: true,\n\t\t\tactiveColor: '#274b6d',\n\t\t\t// arrowSize: 12\n\t\t\tinactiveColor: '#CCC'\n\t\t\t// style: {} // text styles\n\t\t},\n\t\t// margin: 10,\n\t\t// reversed: false,\n\t\tshadow: false,\n\t\t// backgroundColor: null,\n\t\t/*style: {\n\t\t\tpadding: '5px'\n\t\t},*/\n\t\titemStyle: {\n\t\t\tcursor: 'pointer',\n\t\t\tcolor: '#274b6d',\n\t\t\tfontSize: '12px'\n\t\t},\n\t\titemHoverStyle: {\n\t\t\t//cursor: 'pointer', removed as of #601\n\t\t\tcolor: '#000'\n\t\t},\n\t\titemHiddenStyle: {\n\t\t\tcolor: '#CCC'\n\t\t},\n\t\titemCheckboxStyle: {\n\t\t\tposition: ABSOLUTE,\n\t\t\twidth: '13px', // for IE precision\n\t\t\theight: '13px'\n\t\t},\n\t\t// itemWidth: undefined,\n\t\tsymbolWidth: 16,\n\t\tsymbolPadding: 5,\n\t\tverticalAlign: 'bottom',\n\t\t// width: undefined,\n\t\tx: 0,\n\t\ty: 0,\n\t\ttitle: {\n\t\t\t//text: null,\n\t\t\tstyle: {\n\t\t\t\tfontWeight: 'bold'\n\t\t\t}\n\t\t}\t\t\t\n\t},\n\n\tloading: {\n\t\t// hideDuration: 100,\n\t\tlabelStyle: {\n\t\t\tfontWeight: 'bold',\n\t\t\tposition: RELATIVE,\n\t\t\ttop: '1em'\n\t\t},\n\t\t// showDuration: 0,\n\t\tstyle: {\n\t\t\tposition: ABSOLUTE,\n\t\t\tbackgroundColor: 'white',\n\t\t\topacity: 0.5,\n\t\t\ttextAlign: 'center'\n\t\t}\n\t},\n\n\ttooltip: {\n\t\tenabled: true,\n\t\tanimation: hasSVG,\n\t\t//crosshairs: null,\n\t\tbackgroundColor: 'rgba(255, 255, 255, .85)',\n\t\tborderWidth: 1,\n\t\tborderRadius: 3,\n\t\tdateTimeLabelFormats: { \n\t\t\tmillisecond: '%A, %b %e, %H:%M:%S.%L',\n\t\t\tsecond: '%A, %b %e, %H:%M:%S',\n\t\t\tminute: '%A, %b %e, %H:%M',\n\t\t\thour: '%A, %b %e, %H:%M',\n\t\t\tday: '%A, %b %e, %Y',\n\t\t\tweek: 'Week from %A, %b %e, %Y',\n\t\t\tmonth: '%B %Y',\n\t\t\tyear: '%Y'\n\t\t},\n\t\t//formatter: defaultFormatter,\n\t\theaderFormat: '<span style=\"font-size: 10px\">{point.key}</span><br/>',\n\t\tpointFormat: '<span style=\"color:{series.color}\">{series.name}</span>: <b>{point.y}</b><br/>',\n\t\tshadow: true,\n\t\t//shared: false,\n\t\tsnap: isTouchDevice ? 25 : 10,\n\t\tstyle: {\n\t\t\tcolor: '#333333',\n\t\t\tcursor: 'default',\n\t\t\tfontSize: '12px',\n\t\t\tpadding: '8px',\n\t\t\twhiteSpace: 'nowrap'\n\t\t}\n\t\t//xDateFormat: '%A, %b %e, %Y',\n\t\t//valueDecimals: null,\n\t\t//valuePrefix: '',\n\t\t//valueSuffix: ''\n\t},\n\n\tcredits: {\n\t\tenabled: true,\n\t\ttext: 'Highcharts.com',\n\t\thref: 'http://www.highcharts.com',\n\t\tposition: {\n\t\t\talign: 'right',\n\t\t\tx: -10,\n\t\t\tverticalAlign: 'bottom',\n\t\t\ty: -5\n\t\t},\n\t\tstyle: {\n\t\t\tcursor: 'pointer',\n\t\t\tcolor: '#909090',\n\t\t\tfontSize: '9px'\n\t\t}\n\t}\n};\n\n\n\n\n// Series defaults\nvar defaultPlotOptions = defaultOptions.plotOptions,\n\tdefaultSeriesOptions = defaultPlotOptions.line;\n\n// set the default time methods\nsetTimeMethods();\n\n\n\n/**\n * Set the time methods globally based on the useUTC option. Time method can be either\n * local time or UTC (default).\n */\nfunction setTimeMethods() {\n\tvar useUTC = defaultOptions.global.useUTC,\n\t\tGET = useUTC ? 'getUTC' : 'get',\n\t\tSET = useUTC ? 'setUTC' : 'set';\n\n\tmakeTime = useUTC ? Date.UTC : function (year, month, date, hours, minutes, seconds) {\n\t\treturn new Date(\n\t\t\tyear,\n\t\t\tmonth,\n\t\t\tpick(date, 1),\n\t\t\tpick(hours, 0),\n\t\t\tpick(minutes, 0),\n\t\t\tpick(seconds, 0)\n\t\t).getTime();\n\t};\n\tgetMinutes =  GET + 'Minutes';\n\tgetHours =    GET + 'Hours';\n\tgetDay =      GET + 'Day';\n\tgetDate =     GET + 'Date';\n\tgetMonth =    GET + 'Month';\n\tgetFullYear = GET + 'FullYear';\n\tsetMinutes =  SET + 'Minutes';\n\tsetHours =    SET + 'Hours';\n\tsetDate =     SET + 'Date';\n\tsetMonth =    SET + 'Month';\n\tsetFullYear = SET + 'FullYear';\n\n}\n\n/**\n * Merge the default options with custom options and return the new options structure\n * @param {Object} options The new custom options\n */\nfunction setOptions(options) {\n\t\n\t// Pull out axis options and apply them to the respective default axis options \n\t/*defaultXAxisOptions = merge(defaultXAxisOptions, options.xAxis);\n\tdefaultYAxisOptions = merge(defaultYAxisOptions, options.yAxis);\n\toptions.xAxis = options.yAxis = UNDEFINED;*/\n\t\n\t// Merge in the default options\n\tdefaultOptions = merge(defaultOptions, options);\n\t\n\t// Apply UTC\n\tsetTimeMethods();\n\n\treturn defaultOptions;\n}\n\n/**\n * Get the updated default options. Merely exposing defaultOptions for outside modules\n * isn't enough because the setOptions method creates a new object.\n */\nfunction getOptions() {\n\treturn defaultOptions;\n}\n\n\n/**\n * Handle color operations. The object methods are chainable.\n * @param {String} input The input color in either rbga or hex format\n */\nvar Color = function (input) {\n\t// declare variables\n\tvar rgba = [], result, stops;\n\n\t/**\n\t * Parse the input color to rgba array\n\t * @param {String} input\n\t */\n\tfunction init(input) {\n\n\t\t// Gradients\n\t\tif (input && input.stops) {\n\t\t\tstops = map(input.stops, function (stop) {\n\t\t\t\treturn Color(stop[1]);\n\t\t\t});\n\n\t\t// Solid colors\n\t\t} else {\n\t\t\t// rgba\n\t\t\tresult = /rgba\\(\\s*([0-9]{1,3})\\s*,\\s*([0-9]{1,3})\\s*,\\s*([0-9]{1,3})\\s*,\\s*([0-9]?(?:\\.[0-9]+)?)\\s*\\)/.exec(input);\n\t\t\tif (result) {\n\t\t\t\trgba = [pInt(result[1]), pInt(result[2]), pInt(result[3]), parseFloat(result[4], 10)];\n\t\t\t} else { \n\t\t\t\t// hex\n\t\t\t\tresult = /#([a-fA-F0-9]{2})([a-fA-F0-9]{2})([a-fA-F0-9]{2})/.exec(input);\n\t\t\t\tif (result) {\n\t\t\t\t\trgba = [pInt(result[1], 16), pInt(result[2], 16), pInt(result[3], 16), 1];\n\t\t\t\t} else {\n\t\t\t\t\t// rgb\n\t\t\t\t\tresult = /rgb\\(\\s*([0-9]{1,3})\\s*,\\s*([0-9]{1,3})\\s*,\\s*([0-9]{1,3})\\s*\\)/.exec(input);\n\t\t\t\t\tif (result) {\n\t\t\t\t\t\trgba = [pInt(result[1]), pInt(result[2]), pInt(result[3]), 1];\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\t\t\n\n\t}\n\t/**\n\t * Return the color a specified format\n\t * @param {String} format\n\t */\n\tfunction get(format) {\n\t\tvar ret;\n\n\t\tif (stops) {\n\t\t\tret = merge(input);\n\t\t\tret.stops = [].concat(ret.stops);\n\t\t\teach(stops, function (stop, i) {\n\t\t\t\tret.stops[i] = [ret.stops[i][0], stop.get(format)];\n\t\t\t});\n\n\t\t// it's NaN if gradient colors on a column chart\n\t\t} else if (rgba && !isNaN(rgba[0])) {\n\t\t\tif (format === 'rgb') {\n\t\t\t\tret = 'rgb(' + rgba[0] + ',' + rgba[1] + ',' + rgba[2] + ')';\n\t\t\t} else if (format === 'a') {\n\t\t\t\tret = rgba[3];\n\t\t\t} else {\n\t\t\t\tret = 'rgba(' + rgba.join(',') + ')';\n\t\t\t}\n\t\t} else {\n\t\t\tret = input;\n\t\t}\n\t\treturn ret;\n\t}\n\n\t/**\n\t * Brighten the color\n\t * @param {Number} alpha\n\t */\n\tfunction brighten(alpha) {\n\t\tif (stops) {\n\t\t\teach(stops, function (stop) {\n\t\t\t\tstop.brighten(alpha);\n\t\t\t});\n\t\t\n\t\t} else if (isNumber(alpha) && alpha !== 0) {\n\t\t\tvar i;\n\t\t\tfor (i = 0; i < 3; i++) {\n\t\t\t\trgba[i] += pInt(alpha * 255);\n\n\t\t\t\tif (rgba[i] < 0) {\n\t\t\t\t\trgba[i] = 0;\n\t\t\t\t}\n\t\t\t\tif (rgba[i] > 255) {\n\t\t\t\t\trgba[i] = 255;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn this;\n\t}\n\t/**\n\t * Set the color's opacity to a given alpha value\n\t * @param {Number} alpha\n\t */\n\tfunction setOpacity(alpha) {\n\t\trgba[3] = alpha;\n\t\treturn this;\n\t}\n\n\t// initialize: parse the input\n\tinit(input);\n\n\t// public methods\n\treturn {\n\t\tget: get,\n\t\tbrighten: brighten,\n\t\trgba: rgba,\n\t\tsetOpacity: setOpacity\n\t};\n};\n\n\n/**\n * A wrapper object for SVG elements\n */\nfunction SVGElement() {}\n\nSVGElement.prototype = {\n\t/**\n\t * Initialize the SVG renderer\n\t * @param {Object} renderer\n\t * @param {String} nodeName\n\t */\n\tinit: function (renderer, nodeName) {\n\t\tvar wrapper = this;\n\t\twrapper.element = nodeName === 'span' ?\n\t\t\tcreateElement(nodeName) :\n\t\t\tdoc.createElementNS(SVG_NS, nodeName);\n\t\twrapper.renderer = renderer;\n\t\t/**\n\t\t * A collection of attribute setters. These methods, if defined, are called right before a certain\n\t\t * attribute is set on an element wrapper. Returning false prevents the default attribute\n\t\t * setter to run. Returning a value causes the default setter to set that value. Used in\n\t\t * Renderer.label.\n\t\t */\n\t\twrapper.attrSetters = {};\n\t},\n\t/**\n\t * Default base for animation\n\t */\n\topacity: 1,\n\t/**\n\t * Animate a given attribute\n\t * @param {Object} params\n\t * @param {Number} options The same options as in jQuery animation\n\t * @param {Function} complete Function to perform at the end of animation\n\t */\n\tanimate: function (params, options, complete) {\n\t\tvar animOptions = pick(options, globalAnimation, true);\n\t\tstop(this); // stop regardless of animation actually running, or reverting to .attr (#607)\n\t\tif (animOptions) {\n\t\t\tanimOptions = merge(animOptions);\n\t\t\tif (complete) { // allows using a callback with the global animation without overwriting it\n\t\t\t\tanimOptions.complete = complete;\n\t\t\t}\n\t\t\tanimate(this, params, animOptions);\n\t\t} else {\n\t\t\tthis.attr(params);\n\t\t\tif (complete) {\n\t\t\t\tcomplete();\n\t\t\t}\n\t\t}\n\t},\n\t/**\n\t * Set or get a given attribute\n\t * @param {Object|String} hash\n\t * @param {Mixed|Undefined} val\n\t */\n\tattr: function (hash, val) {\n\t\tvar wrapper = this,\n\t\t\tkey,\n\t\t\tvalue,\n\t\t\tresult,\n\t\t\ti,\n\t\t\tchild,\n\t\t\telement = wrapper.element,\n\t\t\tnodeName = element.nodeName.toLowerCase(), // Android2 requires lower for \"text\"\n\t\t\trenderer = wrapper.renderer,\n\t\t\tskipAttr,\n\t\t\ttitleNode,\n\t\t\tattrSetters = wrapper.attrSetters,\n\t\t\tshadows = wrapper.shadows,\n\t\t\thasSetSymbolSize,\n\t\t\tdoTransform,\n\t\t\tret = wrapper;\n\n\t\t// single key-value pair\n\t\tif (isString(hash) && defined(val)) {\n\t\t\tkey = hash;\n\t\t\thash = {};\n\t\t\thash[key] = val;\n\t\t}\n\n\t\t// used as a getter: first argument is a string, second is undefined\n\t\tif (isString(hash)) {\n\t\t\tkey = hash;\n\t\t\tif (nodeName === 'circle') {\n\t\t\t\tkey = { x: 'cx', y: 'cy' }[key] || key;\n\t\t\t} else if (key === 'strokeWidth') {\n\t\t\t\tkey = 'stroke-width';\n\t\t\t}\n\t\t\tret = attr(element, key) || wrapper[key] || 0;\n\t\t\tif (key !== 'd' && key !== 'visibility' && key !== 'fill') { // 'd' is string in animation step\n\t\t\t\tret = parseFloat(ret);\n\t\t\t}\n\n\t\t// setter\n\t\t} else {\n\n\t\t\tfor (key in hash) {\n\t\t\t\tskipAttr = false; // reset\n\t\t\t\tvalue = hash[key];\n\n\t\t\t\t// check for a specific attribute setter\n\t\t\t\tresult = attrSetters[key] && attrSetters[key].call(wrapper, value, key);\n\n\t\t\t\tif (result !== false) {\n\t\t\t\t\tif (result !== UNDEFINED) {\n\t\t\t\t\t\tvalue = result; // the attribute setter has returned a new value to set\n\t\t\t\t\t}\n\n\n\t\t\t\t\t// paths\n\t\t\t\t\tif (key === 'd') {\n\t\t\t\t\t\tif (value && value.join) { // join path\n\t\t\t\t\t\t\tvalue = value.join(' ');\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (/(NaN| {2}|^$)/.test(value)) {\n\t\t\t\t\t\t\tvalue = 'M 0 0';\n\t\t\t\t\t\t}\n\t\t\t\t\t\t//wrapper.d = value; // shortcut for animations\n\n\t\t\t\t\t// update child tspans x values\n\t\t\t\t\t} else if (key === 'x' && nodeName === 'text') {\n\t\t\t\t\t\tfor (i = 0; i < element.childNodes.length; i++) {\n\t\t\t\t\t\t\tchild = element.childNodes[i];\n\t\t\t\t\t\t\t// if the x values are equal, the tspan represents a linebreak\n\t\t\t\t\t\t\tif (attr(child, 'x') === attr(element, 'x')) {\n\t\t\t\t\t\t\t\t//child.setAttribute('x', value);\n\t\t\t\t\t\t\t\tattr(child, 'x', value);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t} else if (wrapper.rotation && (key === 'x' || key === 'y')) {\n\t\t\t\t\t\tdoTransform = true;\n\n\t\t\t\t\t// apply gradients\n\t\t\t\t\t} else if (key === 'fill') {\n\t\t\t\t\t\tvalue = renderer.color(value, element, key);\n\n\t\t\t\t\t// circle x and y\n\t\t\t\t\t} else if (nodeName === 'circle' && (key === 'x' || key === 'y')) {\n\t\t\t\t\t\tkey = { x: 'cx', y: 'cy' }[key] || key;\n\n\t\t\t\t\t// rectangle border radius\n\t\t\t\t\t} else if (nodeName === 'rect' && key === 'r') {\n\t\t\t\t\t\tattr(element, {\n\t\t\t\t\t\t\trx: value,\n\t\t\t\t\t\t\try: value\n\t\t\t\t\t\t});\n\t\t\t\t\t\tskipAttr = true;\n\n\t\t\t\t\t// translation and text rotation\n\t\t\t\t\t} else if (key === 'translateX' || key === 'translateY' || key === 'rotation' ||\n\t\t\t\t\t\t\tkey === 'verticalAlign' || key === 'scaleX' || key === 'scaleY') {\n\t\t\t\t\t\tdoTransform = true;\n\t\t\t\t\t\tskipAttr = true;\n\n\t\t\t\t\t// apply opacity as subnode (required by legacy WebKit and Batik)\n\t\t\t\t\t} else if (key === 'stroke') {\n\t\t\t\t\t\tvalue = renderer.color(value, element, key);\n\n\t\t\t\t\t// emulate VML's dashstyle implementation\n\t\t\t\t\t} else if (key === 'dashstyle') {\n\t\t\t\t\t\tkey = 'stroke-dasharray';\n\t\t\t\t\t\tvalue = value && value.toLowerCase();\n\t\t\t\t\t\tif (value === 'solid') {\n\t\t\t\t\t\t\tvalue = NONE;\n\t\t\t\t\t\t} else if (value) {\n\t\t\t\t\t\t\tvalue = value\n\t\t\t\t\t\t\t\t.replace('shortdashdotdot', '3,1,1,1,1,1,')\n\t\t\t\t\t\t\t\t.replace('shortdashdot', '3,1,1,1')\n\t\t\t\t\t\t\t\t.replace('shortdot', '1,1,')\n\t\t\t\t\t\t\t\t.replace('shortdash', '3,1,')\n\t\t\t\t\t\t\t\t.replace('longdash', '8,3,')\n\t\t\t\t\t\t\t\t.replace(/dot/g, '1,3,')\n\t\t\t\t\t\t\t\t.replace('dash', '4,3,')\n\t\t\t\t\t\t\t\t.replace(/,$/, '')\n\t\t\t\t\t\t\t\t.split(','); // ending comma\n\n\t\t\t\t\t\t\ti = value.length;\n\t\t\t\t\t\t\twhile (i--) {\n\t\t\t\t\t\t\t\tvalue[i] = pInt(value[i]) * pick(hash['stroke-width'], wrapper['stroke-width']);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tvalue = value.join(',');\n\t\t\t\t\t\t}\n\n\t\t\t\t\t// IE9/MooTools combo: MooTools returns objects instead of numbers and IE9 Beta 2\n\t\t\t\t\t// is unable to cast them. Test again with final IE9.\n\t\t\t\t\t} else if (key === 'width') {\n\t\t\t\t\t\tvalue = pInt(value);\n\n\t\t\t\t\t// Text alignment\n\t\t\t\t\t} else if (key === 'align') {\n\t\t\t\t\t\tkey = 'text-anchor';\n\t\t\t\t\t\tvalue = { left: 'start', center: 'middle', right: 'end' }[value];\n\n\t\t\t\t\t// Title requires a subnode, #431\n\t\t\t\t\t} else if (key === 'title') {\n\t\t\t\t\t\ttitleNode = element.getElementsByTagName('title')[0];\n\t\t\t\t\t\tif (!titleNode) {\n\t\t\t\t\t\t\ttitleNode = doc.createElementNS(SVG_NS, 'title');\n\t\t\t\t\t\t\telement.appendChild(titleNode);\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttitleNode.textContent = value;\n\t\t\t\t\t}\n\n\t\t\t\t\t// jQuery animate changes case\n\t\t\t\t\tif (key === 'strokeWidth') {\n\t\t\t\t\t\tkey = 'stroke-width';\n\t\t\t\t\t}\n\n\t\t\t\t\t// In Chrome/Win < 6 as well as Batik, the stroke attribute can't be set when the stroke-\n\t\t\t\t\t// width is 0. #1369\n\t\t\t\t\tif (key === 'stroke-width' || key === 'stroke') {\n\t\t\t\t\t\twrapper[key] = value;\n\t\t\t\t\t\t// Only apply the stroke attribute if the stroke width is defined and larger than 0\n\t\t\t\t\t\tif (wrapper.stroke && wrapper['stroke-width']) {\n\t\t\t\t\t\t\tattr(element, 'stroke', wrapper.stroke);\n\t\t\t\t\t\t\tattr(element, 'stroke-width', wrapper['stroke-width']);\n\t\t\t\t\t\t\twrapper.hasStroke = true;\n\t\t\t\t\t\t} else if (key === 'stroke-width' && value === 0 && wrapper.hasStroke) {\n\t\t\t\t\t\t\telement.removeAttribute('stroke');\n\t\t\t\t\t\t\twrapper.hasStroke = false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tskipAttr = true;\n\t\t\t\t\t}\n\n\t\t\t\t\t// symbols\n\t\t\t\t\tif (wrapper.symbolName && /^(x|y|width|height|r|start|end|innerR|anchorX|anchorY)/.test(key)) {\n\n\n\t\t\t\t\t\tif (!hasSetSymbolSize) {\n\t\t\t\t\t\t\twrapper.symbolAttr(hash);\n\t\t\t\t\t\t\thasSetSymbolSize = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tskipAttr = true;\n\t\t\t\t\t}\n\n\t\t\t\t\t// let the shadow follow the main element\n\t\t\t\t\tif (shadows && /^(width|height|visibility|x|y|d|transform|cx|cy|r)$/.test(key)) {\n\t\t\t\t\t\ti = shadows.length;\n\t\t\t\t\t\twhile (i--) {\n\t\t\t\t\t\t\tattr(\n\t\t\t\t\t\t\t\tshadows[i],\n\t\t\t\t\t\t\t\tkey,\n\t\t\t\t\t\t\t\tkey === 'height' ?\n\t\t\t\t\t\t\t\t\tmathMax(value - (shadows[i].cutHeight || 0), 0) :\n\t\t\t\t\t\t\t\t\tvalue\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// validate heights\n\t\t\t\t\tif ((key === 'width' || key === 'height') && nodeName === 'rect' && value < 0) {\n\t\t\t\t\t\tvalue = 0;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Record for animation and quick access without polling the DOM\n\t\t\t\t\twrapper[key] = value;\n\n\n\t\t\t\t\tif (key === 'text') {\n\t\t\t\t\t\t// Delete bBox memo when the text changes\n\t\t\t\t\t\tif (value !== wrapper.textStr) {\n\t\t\t\t\t\t\tdelete wrapper.bBox;\n\t\t\t\t\t\t}\n\t\t\t\t\t\twrapper.textStr = value;\n\t\t\t\t\t\tif (wrapper.added) {\n\t\t\t\t\t\t\trenderer.buildText(wrapper);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (!skipAttr) {\n\t\t\t\t\t\tattr(element, key, value);\n\t\t\t\t\t}\n\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t\t// Update transform. Do this outside the loop to prevent redundant updating for batch setting\n\t\t\t// of attributes.\n\t\t\tif (doTransform) {\n\t\t\t\twrapper.updateTransform();\n\t\t\t}\n\n\t\t}\n\n\t\treturn ret;\n\t},\n\n\n\t/**\n\t * Add a class name to an element\n\t */\n\taddClass: function (className) {\n\t\tvar element = this.element,\n\t\t\tcurrentClassName = attr(element, 'class') || '';\n\n\t\tif (currentClassName.indexOf(className) === -1) {\n\t\t\tattr(element, 'class', currentClassName + ' ' + className);\n\t\t}\n\t\treturn this;\n\t},\n\t/* hasClass and removeClass are not (yet) needed\n\thasClass: function (className) {\n\t\treturn attr(this.element, 'class').indexOf(className) !== -1;\n\t},\n\tremoveClass: function (className) {\n\t\tattr(this.element, 'class', attr(this.element, 'class').replace(className, ''));\n\t\treturn this;\n\t},\n\t*/\n\n\t/**\n\t * If one of the symbol size affecting parameters are changed,\n\t * check all the others only once for each call to an element's\n\t * .attr() method\n\t * @param {Object} hash\n\t */\n\tsymbolAttr: function (hash) {\n\t\tvar wrapper = this;\n\n\t\teach(['x', 'y', 'r', 'start', 'end', 'width', 'height', 'innerR', 'anchorX', 'anchorY'], function (key) {\n\t\t\twrapper[key] = pick(hash[key], wrapper[key]);\n\t\t});\n\n\t\twrapper.attr({\n\t\t\td: wrapper.renderer.symbols[wrapper.symbolName](\n\t\t\t\twrapper.x,\n\t\t\t\twrapper.y,\n\t\t\t\twrapper.width,\n\t\t\t\twrapper.height,\n\t\t\t\twrapper\n\t\t\t)\n\t\t});\n\t},\n\n\t/**\n\t * Apply a clipping path to this object\n\t * @param {String} id\n\t */\n\tclip: function (clipRect) {\n\t\treturn this.attr('clip-path', clipRect ? 'url(' + this.renderer.url + '#' + clipRect.id + ')' : NONE);\n\t},\n\n\t/**\n\t * Calculate the coordinates needed for drawing a rectangle crisply and return the\n\t * calculated attributes\n\t * @param {Number} strokeWidth\n\t * @param {Number} x\n\t * @param {Number} y\n\t * @param {Number} width\n\t * @param {Number} height\n\t */\n\tcrisp: function (strokeWidth, x, y, width, height) {\n\n\t\tvar wrapper = this,\n\t\t\tkey,\n\t\t\tattribs = {},\n\t\t\tvalues = {},\n\t\t\tnormalizer;\n\n\t\tstrokeWidth = strokeWidth || wrapper.strokeWidth || (wrapper.attr && wrapper.attr('stroke-width')) || 0;\n\t\tnormalizer = mathRound(strokeWidth) % 2 / 2; // mathRound because strokeWidth can sometimes have roundoff errors\n\n\t\t// normalize for crisp edges\n\t\tvalues.x = mathFloor(x || wrapper.x || 0) + normalizer;\n\t\tvalues.y = mathFloor(y || wrapper.y || 0) + normalizer;\n\t\tvalues.width = mathFloor((width || wrapper.width || 0) - 2 * normalizer);\n\t\tvalues.height = mathFloor((height || wrapper.height || 0) - 2 * normalizer);\n\t\tvalues.strokeWidth = strokeWidth;\n\n\t\tfor (key in values) {\n\t\t\tif (wrapper[key] !== values[key]) { // only set attribute if changed\n\t\t\t\twrapper[key] = attribs[key] = values[key];\n\t\t\t}\n\t\t}\n\n\t\treturn attribs;\n\t},\n\n\t/**\n\t * Set styles for the element\n\t * @param {Object} styles\n\t */\n\tcss: function (styles) {\n\t\t/*jslint unparam: true*//* allow unused param a in the regexp function below */\n\t\tvar elemWrapper = this,\n\t\t\telem = elemWrapper.element,\n\t\t\ttextWidth = styles && styles.width && elem.nodeName.toLowerCase() === 'text',\n\t\t\tn,\n\t\t\tserializedCss = '',\n\t\t\thyphenate = function (a, b) { return '-' + b.toLowerCase(); };\n\t\t/*jslint unparam: false*/\n\n\t\t// convert legacy\n\t\tif (styles && styles.color) {\n\t\t\tstyles.fill = styles.color;\n\t\t}\n\n\t\t// Merge the new styles with the old ones\n\t\tstyles = extend(\n\t\t\telemWrapper.styles,\n\t\t\tstyles\n\t\t);\n\n\t\t// store object\n\t\telemWrapper.styles = styles;\n\n\n\t\t// Don't handle line wrap on canvas\n\t\tif (useCanVG && textWidth) {\n\t\t\tdelete styles.width;\n\t\t}\n\n\t\t// serialize and set style attribute\n\t\tif (isIE && !hasSVG) { // legacy IE doesn't support setting style attribute\n\t\t\tif (textWidth) {\n\t\t\t\tdelete styles.width;\n\t\t\t}\n\t\t\tcss(elemWrapper.element, styles);\n\t\t} else {\n\t\t\tfor (n in styles) {\n\t\t\t\tserializedCss += n.replace(/([A-Z])/g, hyphenate) + ':' + styles[n] + ';';\n\t\t\t}\n\t\t\tattr(elem, 'style', serializedCss); // #1881\n\t\t}\n\n\n\t\t// re-build text\n\t\tif (textWidth && elemWrapper.added) {\n\t\t\telemWrapper.renderer.buildText(elemWrapper);\n\t\t}\n\n\t\treturn elemWrapper;\n\t},\n\n\t/**\n\t * Add an event listener\n\t * @param {String} eventType\n\t * @param {Function} handler\n\t */\n\ton: function (eventType, handler) {\n\t\tvar svgElement = this,\n\t\t\telement = svgElement.element;\n\t\t\n\t\t// touch\n\t\tif (hasTouch && eventType === 'click') {\n\t\t\telement.ontouchstart = function (e) {\t\t\t\n\t\t\t\tsvgElement.touchEventFired = Date.now();\t\t\t\t\n\t\t\t\te.preventDefault();\n\t\t\t\thandler.call(element, e);\n\t\t\t};\n\t\t\telement.onclick = function (e) {\t\t\t\t\t\t\t\t\t\t\t\t\n\t\t\t\tif (userAgent.indexOf('Android') === -1 || Date.now() - (svgElement.touchEventFired || 0) > 1100) { // #2269\n\t\t\t\t\thandler.call(element, e);\n\t\t\t\t}\n\t\t\t};\t\t\t\n\t\t} else {\n\t\t\t// simplest possible event model for internal use\n\t\t\telement['on' + eventType] = handler;\n\t\t}\n\t\treturn this;\n\t},\n\n\t/**\n\t * Set the coordinates needed to draw a consistent radial gradient across\n\t * pie slices regardless of positioning inside the chart. The format is\n\t * [centerX, centerY, diameter] in pixels.\n\t */\n\tsetRadialReference: function (coordinates) {\n\t\tthis.element.radialReference = coordinates;\n\t\treturn this;\n\t},\n\n\t/**\n\t * Move an object and its children by x and y values\n\t * @param {Number} x\n\t * @param {Number} y\n\t */\n\ttranslate: function (x, y) {\n\t\treturn this.attr({\n\t\t\ttranslateX: x,\n\t\t\ttranslateY: y\n\t\t});\n\t},\n\n\t/**\n\t * Invert a group, rotate and flip\n\t */\n\tinvert: function () {\n\t\tvar wrapper = this;\n\t\twrapper.inverted = true;\n\t\twrapper.updateTransform();\n\t\treturn wrapper;\n\t},\n\n\t/**\n\t * Apply CSS to HTML elements. This is used in text within SVG rendering and\n\t * by the VML renderer\n\t */\n\thtmlCss: function (styles) {\n\t\tvar wrapper = this,\n\t\t\telement = wrapper.element,\n\t\t\ttextWidth = styles && element.tagName === 'SPAN' && styles.width;\n\n\t\tif (textWidth) {\n\t\t\tdelete styles.width;\n\t\t\twrapper.textWidth = textWidth;\n\t\t\twrapper.updateTransform();\n\t\t}\n\n\t\twrapper.styles = extend(wrapper.styles, styles);\n\t\tcss(wrapper.element, styles);\n\n\t\treturn wrapper;\n\t},\n\n\n\n\t/**\n\t * VML and useHTML method for calculating the bounding box based on offsets\n\t * @param {Boolean} refresh Whether to force a fresh value from the DOM or to\n\t * use the cached value\n\t *\n\t * @return {Object} A hash containing values for x, y, width and height\n\t */\n\n\thtmlGetBBox: function () {\n\t\tvar wrapper = this,\n\t\t\telement = wrapper.element,\n\t\t\tbBox = wrapper.bBox;\n\n\t\t// faking getBBox in exported SVG in legacy IE\n\t\tif (!bBox) {\n\t\t\t// faking getBBox in exported SVG in legacy IE (is this a duplicate of the fix for #1079?)\n\t\t\tif (element.nodeName === 'text') {\n\t\t\t\telement.style.position = ABSOLUTE;\n\t\t\t}\n\n\t\t\tbBox = wrapper.bBox = {\n\t\t\t\tx: element.offsetLeft,\n\t\t\t\ty: element.offsetTop,\n\t\t\t\twidth: element.offsetWidth,\n\t\t\t\theight: element.offsetHeight\n\t\t\t};\n\t\t}\n\n\t\treturn bBox;\n\t},\n\n\t/**\n\t * VML override private method to update elements based on internal\n\t * properties based on SVG transform\n\t */\n\thtmlUpdateTransform: function () {\n\t\t// aligning non added elements is expensive\n\t\tif (!this.added) {\n\t\t\tthis.alignOnAdd = true;\n\t\t\treturn;\n\t\t}\n\n\t\tvar wrapper = this,\n\t\t\trenderer = wrapper.renderer,\n\t\t\telem = wrapper.element,\n\t\t\ttranslateX = wrapper.translateX || 0,\n\t\t\ttranslateY = wrapper.translateY || 0,\n\t\t\tx = wrapper.x || 0,\n\t\t\ty = wrapper.y || 0,\n\t\t\talign = wrapper.textAlign || 'left',\n\t\t\talignCorrection = { left: 0, center: 0.5, right: 1 }[align],\n\t\t\tnonLeft = align && align !== 'left',\n\t\t\tshadows = wrapper.shadows;\n\n\t\t// apply translate\n\t\tcss(elem, {\n\t\t\tmarginLeft: translateX,\n\t\t\tmarginTop: translateY\n\t\t});\n\t\tif (shadows) { // used in labels/tooltip\n\t\t\teach(shadows, function (shadow) {\n\t\t\t\tcss(shadow, {\n\t\t\t\t\tmarginLeft: translateX + 1,\n\t\t\t\t\tmarginTop: translateY + 1\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\n\t\t// apply inversion\n\t\tif (wrapper.inverted) { // wrapper is a group\n\t\t\teach(elem.childNodes, function (child) {\n\t\t\t\trenderer.invertChild(child, elem);\n\t\t\t});\n\t\t}\n\n\t\tif (elem.tagName === 'SPAN') {\n\n\t\t\tvar width, height,\n\t\t\t\trotation = wrapper.rotation,\n\t\t\t\tbaseline,\n\t\t\t\tradians = 0,\n\t\t\t\tcostheta = 1,\n\t\t\t\tsintheta = 0,\n\t\t\t\tquad,\n\t\t\t\ttextWidth = pInt(wrapper.textWidth),\n\t\t\t\txCorr = wrapper.xCorr || 0,\n\t\t\t\tyCorr = wrapper.yCorr || 0,\n\t\t\t\tcurrentTextTransform = [rotation, align, elem.innerHTML, wrapper.textWidth].join(',');\n\n\t\t\tif (currentTextTransform !== wrapper.cTT) { // do the calculations and DOM access only if properties changed\n\n\t\t\t\tif (defined(rotation)) {\n\n\t\t\t\t\tradians = rotation * deg2rad; // deg to rad\n\t\t\t\t\tcostheta = mathCos(radians);\n\t\t\t\t\tsintheta = mathSin(radians);\n\n\t\t\t\t\twrapper.setSpanRotation(rotation, sintheta, costheta);\n\n\t\t\t\t}\n\n\t\t\t\twidth = pick(wrapper.elemWidth, elem.offsetWidth);\n\t\t\t\theight = pick(wrapper.elemHeight, elem.offsetHeight);\n\n\t\t\t\t// update textWidth\n\t\t\t\tif (width > textWidth && /[ \\-]/.test(elem.textContent || elem.innerText)) { // #983, #1254\n\t\t\t\t\tcss(elem, {\n\t\t\t\t\t\twidth: textWidth + PX,\n\t\t\t\t\t\tdisplay: 'block',\n\t\t\t\t\t\twhiteSpace: 'normal'\n\t\t\t\t\t});\n\t\t\t\t\twidth = textWidth;\n\t\t\t\t}\n\n\t\t\t\t// correct x and y\n\t\t\t\tbaseline = renderer.fontMetrics(elem.style.fontSize).b;\n\t\t\t\txCorr = costheta < 0 && -width;\n\t\t\t\tyCorr = sintheta < 0 && -height;\n\n\t\t\t\t// correct for baseline and corners spilling out after rotation\n\t\t\t\tquad = costheta * sintheta < 0;\n\t\t\t\txCorr += sintheta * baseline * (quad ? 1 - alignCorrection : alignCorrection);\n\t\t\t\tyCorr -= costheta * baseline * (rotation ? (quad ? alignCorrection : 1 - alignCorrection) : 1);\n\n\t\t\t\t// correct for the length/height of the text\n\t\t\t\tif (nonLeft) {\n\t\t\t\t\txCorr -= width * alignCorrection * (costheta < 0 ? -1 : 1);\n\t\t\t\t\tif (rotation) {\n\t\t\t\t\t\tyCorr -= height * alignCorrection * (sintheta < 0 ? -1 : 1);\n\t\t\t\t\t}\n\t\t\t\t\tcss(elem, {\n\t\t\t\t\t\ttextAlign: align\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\t// record correction\n\t\t\t\twrapper.xCorr = xCorr;\n\t\t\t\twrapper.yCorr = yCorr;\n\t\t\t}\n\n\t\t\t// apply position with correction\n\t\t\tcss(elem, {\n\t\t\t\tleft: (x + xCorr) + PX,\n\t\t\t\ttop: (y + yCorr) + PX\n\t\t\t});\n\n\t\t\t// force reflow in webkit to apply the left and top on useHTML element (#1249)\n\t\t\tif (isWebKit) {\n\t\t\t\theight = elem.offsetHeight; // assigned to height for JSLint purpose\n\t\t\t}\n\n\t\t\t// record current text transform\n\t\t\twrapper.cTT = currentTextTransform;\n\t\t}\n\t},\n\n\t/**\n\t * Set the rotation of an individual HTML span\n\t */\n\tsetSpanRotation: function (rotation) {\n\t\tvar rotationStyle = {},\n\t\t\tcssTransformKey = isIE ? '-ms-transform' : isWebKit ? '-webkit-transform' : isFirefox ? 'MozTransform' : isOpera ? '-o-transform' : '';\n\n\t\trotationStyle[cssTransformKey] = rotationStyle.transform = 'rotate(' + rotation + 'deg)';\n\t\tcss(this.element, rotationStyle);\n\t},\n\n\t/**\n\t * Private method to update the transform attribute based on internal\n\t * properties\n\t */\n\tupdateTransform: function () {\n\t\tvar wrapper = this,\n\t\t\ttranslateX = wrapper.translateX || 0,\n\t\t\ttranslateY = wrapper.translateY || 0,\n\t\t\tscaleX = wrapper.scaleX,\n\t\t\tscaleY = wrapper.scaleY,\n\t\t\tinverted = wrapper.inverted,\n\t\t\trotation = wrapper.rotation,\n\t\t\ttransform;\n\n\t\t// flipping affects translate as adjustment for flipping around the group's axis\n\t\tif (inverted) {\n\t\t\ttranslateX += wrapper.attr('width');\n\t\t\ttranslateY += wrapper.attr('height');\n\t\t}\n\n\t\t// Apply translate. Nearly all transformed elements have translation, so instead\n\t\t// of checking for translate = 0, do it always (#1767, #1846).\n\t\ttransform = ['translate(' + translateX + ',' + translateY + ')'];\n\n\t\t// apply rotation\n\t\tif (inverted) {\n\t\t\ttransform.push('rotate(90) scale(-1,1)');\n\t\t} else if (rotation) { // text rotation\n\t\t\ttransform.push('rotate(' + rotation + ' ' + (wrapper.x || 0) + ' ' + (wrapper.y || 0) + ')');\n\t\t}\n\n\t\t// apply scale\n\t\tif (defined(scaleX) || defined(scaleY)) {\n\t\t\ttransform.push('scale(' + pick(scaleX, 1) + ' ' + pick(scaleY, 1) + ')');\n\t\t}\n\n\t\tif (transform.length) {\n\t\t\tattr(wrapper.element, 'transform', transform.join(' '));\n\t\t}\n\t},\n\t/**\n\t * Bring the element to the front\n\t */\n\ttoFront: function () {\n\t\tvar element = this.element;\n\t\telement.parentNode.appendChild(element);\n\t\treturn this;\n\t},\n\n\n\t/**\n\t * Break down alignment options like align, verticalAlign, x and y\n\t * to x and y relative to the chart.\n\t *\n\t * @param {Object} alignOptions\n\t * @param {Boolean} alignByTranslate\n\t * @param {String[Object} box The box to align to, needs a width and height. When the\n\t *        box is a string, it refers to an object in the Renderer. For example, when\n\t *        box is 'spacingBox', it refers to Renderer.spacingBox which holds width, height\n\t *        x and y properties.\n\t *\n\t */\n\talign: function (alignOptions, alignByTranslate, box) {\n\t\tvar align,\n\t\t\tvAlign,\n\t\t\tx,\n\t\t\ty,\n\t\t\tattribs = {},\n\t\t\talignTo,\n\t\t\trenderer = this.renderer,\n\t\t\talignedObjects = renderer.alignedObjects;\n\n\t\t// First call on instanciate\n\t\tif (alignOptions) {\n\t\t\tthis.alignOptions = alignOptions;\n\t\t\tthis.alignByTranslate = alignByTranslate;\n\t\t\tif (!box || isString(box)) { // boxes other than renderer handle this internally\n\t\t\t\tthis.alignTo = alignTo = box || 'renderer';\n\t\t\t\terase(alignedObjects, this); // prevent duplicates, like legendGroup after resize\n\t\t\t\talignedObjects.push(this);\n\t\t\t\tbox = null; // reassign it below\n\t\t\t}\n\n\t\t// When called on resize, no arguments are supplied\n\t\t} else {\n\t\t\talignOptions = this.alignOptions;\n\t\t\talignByTranslate = this.alignByTranslate;\n\t\t\talignTo = this.alignTo;\n\t\t}\n\n\t\tbox = pick(box, renderer[alignTo], renderer);\n\n\t\t// Assign variables\n\t\talign = alignOptions.align;\n\t\tvAlign = alignOptions.verticalAlign;\n\t\tx = (box.x || 0) + (alignOptions.x || 0); // default: left align\n\t\ty = (box.y || 0) + (alignOptions.y || 0); // default: top align\n\n\t\t// Align\n\t\tif (align === 'right' || align === 'center') {\n\t\t\tx += (box.width - (alignOptions.width || 0)) /\n\t\t\t\t\t{ right: 1, center: 2 }[align];\n\t\t}\n\t\tattribs[alignByTranslate ? 'translateX' : 'x'] = mathRound(x);\n\n\n\t\t// Vertical align\n\t\tif (vAlign === 'bottom' || vAlign === 'middle') {\n\t\t\ty += (box.height - (alignOptions.height || 0)) /\n\t\t\t\t\t({ bottom: 1, middle: 2 }[vAlign] || 1);\n\n\t\t}\n\t\tattribs[alignByTranslate ? 'translateY' : 'y'] = mathRound(y);\n\n\t\t// Animate only if already placed\n\t\tthis[this.placed ? 'animate' : 'attr'](attribs);\n\t\tthis.placed = true;\n\t\tthis.alignAttr = attribs;\n\n\t\treturn this;\n\t},\n\n\t/**\n\t * Get the bounding box (width, height, x and y) for the element\n\t */\n\tgetBBox: function () {\n\t\tvar wrapper = this,\n\t\t\tbBox = wrapper.bBox,\n\t\t\trenderer = wrapper.renderer,\n\t\t\twidth,\n\t\t\theight,\n\t\t\trotation = wrapper.rotation,\n\t\t\telement = wrapper.element,\n\t\t\tstyles = wrapper.styles,\n\t\t\trad = rotation * deg2rad;\n\n\t\tif (!bBox) {\n\t\t\t// SVG elements\n\t\t\tif (element.namespaceURI === SVG_NS || renderer.forExport) {\n\t\t\t\ttry { // Fails in Firefox if the container has display: none.\n\n\t\t\t\t\tbBox = element.getBBox ?\n\t\t\t\t\t\t// SVG: use extend because IE9 is not allowed to change width and height in case\n\t\t\t\t\t\t// of rotation (below)\n\t\t\t\t\t\textend({}, element.getBBox()) :\n\t\t\t\t\t\t// Canvas renderer and legacy IE in export mode\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\twidth: element.offsetWidth,\n\t\t\t\t\t\t\theight: element.offsetHeight\n\t\t\t\t\t\t};\n\t\t\t\t} catch (e) {}\n\n\t\t\t\t// If the bBox is not set, the try-catch block above failed. The other condition\n\t\t\t\t// is for Opera that returns a width of -Infinity on hidden elements.\n\t\t\t\tif (!bBox || bBox.width < 0) {\n\t\t\t\t\tbBox = { width: 0, height: 0 };\n\t\t\t\t}\n\n\n\t\t\t// VML Renderer or useHTML within SVG\n\t\t\t} else {\n\n\t\t\t\tbBox = wrapper.htmlGetBBox();\n\n\t\t\t}\n\n\t\t\t// True SVG elements as well as HTML elements in modern browsers using the .useHTML option\n\t\t\t// need to compensated for rotation\n\t\t\tif (renderer.isSVG) {\n\t\t\t\twidth = bBox.width;\n\t\t\t\theight = bBox.height;\n\n\t\t\t\t// Workaround for wrong bounding box in IE9 and IE10 (#1101, #1505, #1669)\n\t\t\t\tif (isIE && styles && styles.fontSize === '11px' && height.toPrecision(3) === '22.7') {\n\t\t\t\t\tbBox.height = height = 14;\n\t\t\t\t}\n\n\t\t\t\t// Adjust for rotated text\n\t\t\t\tif (rotation) {\n\t\t\t\t\tbBox.width = mathAbs(height * mathSin(rad)) + mathAbs(width * mathCos(rad));\n\t\t\t\t\tbBox.height = mathAbs(height * mathCos(rad)) + mathAbs(width * mathSin(rad));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\twrapper.bBox = bBox;\n\t\t}\n\t\treturn bBox;\n\t},\n\n\t/**\n\t * Show the element\n\t */\n\tshow: function () {\n\t\treturn this.attr({ visibility: VISIBLE });\n\t},\n\n\t/**\n\t * Hide the element\n\t */\n\thide: function () {\n\t\treturn this.attr({ visibility: HIDDEN });\n\t},\n\n\tfadeOut: function (duration) {\n\t\tvar elemWrapper = this;\n\t\telemWrapper.animate({\n\t\t\topacity: 0\n\t\t}, {\n\t\t\tduration: duration || 150,\n\t\t\tcomplete: function () {\n\t\t\t\telemWrapper.hide();\n\t\t\t}\n\t\t});\n\t},\n\n\t/**\n\t * Add the element\n\t * @param {Object|Undefined} parent Can be an element, an element wrapper or undefined\n\t *    to append the element to the renderer.box.\n\t */\n\tadd: function (parent) {\n\n\t\tvar renderer = this.renderer,\n\t\t\tparentWrapper = parent || renderer,\n\t\t\tparentNode = parentWrapper.element || renderer.box,\n\t\t\tchildNodes = parentNode.childNodes,\n\t\t\telement = this.element,\n\t\t\tzIndex = attr(element, 'zIndex'),\n\t\t\totherElement,\n\t\t\totherZIndex,\n\t\t\ti,\n\t\t\tinserted;\n\n\t\tif (parent) {\n\t\t\tthis.parentGroup = parent;\n\t\t}\n\n\t\t// mark as inverted\n\t\tthis.parentInverted = parent && parent.inverted;\n\n\t\t// build formatted text\n\t\tif (this.textStr !== undefined) {\n\t\t\trenderer.buildText(this);\n\t\t}\n\n\t\t// mark the container as having z indexed children\n\t\tif (zIndex) {\n\t\t\tparentWrapper.handleZ = true;\n\t\t\tzIndex = pInt(zIndex);\n\t\t}\n\n\t\t// insert according to this and other elements' zIndex\n\t\tif (parentWrapper.handleZ) { // this element or any of its siblings has a z index\n\t\t\tfor (i = 0; i < childNodes.length; i++) {\n\t\t\t\totherElement = childNodes[i];\n\t\t\t\totherZIndex = attr(otherElement, 'zIndex');\n\t\t\t\tif (otherElement !== element && (\n\t\t\t\t\t\t// insert before the first element with a higher zIndex\n\t\t\t\t\t\tpInt(otherZIndex) > zIndex ||\n\t\t\t\t\t\t// if no zIndex given, insert before the first element with a zIndex\n\t\t\t\t\t\t(!defined(zIndex) && defined(otherZIndex))\n\n\t\t\t\t\t\t)) {\n\t\t\t\t\tparentNode.insertBefore(element, otherElement);\n\t\t\t\t\tinserted = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// default: append at the end\n\t\tif (!inserted) {\n\t\t\tparentNode.appendChild(element);\n\t\t}\n\n\t\t// mark as added\n\t\tthis.added = true;\n\n\t\t// fire an event for internal hooks\n\t\tfireEvent(this, 'add');\n\n\t\treturn this;\n\t},\n\n\t/**\n\t * Removes a child either by removeChild or move to garbageBin.\n\t * Issue 490; in VML removeChild results in Orphaned nodes according to sIEve, discardElement does not.\n\t */\n\tsafeRemoveChild: function (element) {\n\t\tvar parentNode = element.parentNode;\n\t\tif (parentNode) {\n\t\t\tparentNode.removeChild(element);\n\t\t}\n\t},\n\n\t/**\n\t * Destroy the element and element wrapper\n\t */\n\tdestroy: function () {\n\t\tvar wrapper = this,\n\t\t\telement = wrapper.element || {},\n\t\t\tshadows = wrapper.shadows,\n\t\t\tparentToClean = wrapper.renderer.isSVG && element.nodeName === 'SPAN' && element.parentNode,\n\t\t\tgrandParent,\n\t\t\tkey,\n\t\t\ti;\n\n\t\t// remove events\n\t\telement.onclick = element.onmouseout = element.onmouseover = element.onmousemove = element.point = null;\n\t\tstop(wrapper); // stop running animations\n\n\t\tif (wrapper.clipPath) {\n\t\t\twrapper.clipPath = wrapper.clipPath.destroy();\n\t\t}\n\n\t\t// Destroy stops in case this is a gradient object\n\t\tif (wrapper.stops) {\n\t\t\tfor (i = 0; i < wrapper.stops.length; i++) {\n\t\t\t\twrapper.stops[i] = wrapper.stops[i].destroy();\n\t\t\t}\n\t\t\twrapper.stops = null;\n\t\t}\n\n\t\t// remove element\n\t\twrapper.safeRemoveChild(element);\n\n\t\t// destroy shadows\n\t\tif (shadows) {\n\t\t\teach(shadows, function (shadow) {\n\t\t\t\twrapper.safeRemoveChild(shadow);\n\t\t\t});\n\t\t}\n\n\t\t// In case of useHTML, clean up empty containers emulating SVG groups (#1960).\n\t\twhile (parentToClean && parentToClean.childNodes.length === 0) {\n\t\t\tgrandParent = parentToClean.parentNode;\n\t\t\twrapper.safeRemoveChild(parentToClean);\n\t\t\tparentToClean = grandParent;\n\t\t}\n\n\t\t// remove from alignObjects\n\t\tif (wrapper.alignTo) {\n\t\t\terase(wrapper.renderer.alignedObjects, wrapper);\n\t\t}\n\n\t\tfor (key in wrapper) {\n\t\t\tdelete wrapper[key];\n\t\t}\n\n\t\treturn null;\n\t},\n\n\t/**\n\t * Add a shadow to the element. Must be done after the element is added to the DOM\n\t * @param {Boolean|Object} shadowOptions\n\t */\n\tshadow: function (shadowOptions, group, cutOff) {\n\t\tvar shadows = [],\n\t\t\ti,\n\t\t\tshadow,\n\t\t\telement = this.element,\n\t\t\tstrokeWidth,\n\t\t\tshadowWidth,\n\t\t\tshadowElementOpacity,\n\n\t\t\t// compensate for inverted plot area\n\t\t\ttransform;\n\n\n\t\tif (shadowOptions) {\n\t\t\tshadowWidth = pick(shadowOptions.width, 3);\n\t\t\tshadowElementOpacity = (shadowOptions.opacity || 0.15) / shadowWidth;\n\t\t\ttransform = this.parentInverted ?\n\t\t\t\t'(-1,-1)' :\n\t\t\t\t'(' + pick(shadowOptions.offsetX, 1) + ', ' + pick(shadowOptions.offsetY, 1) + ')';\n\t\t\tfor (i = 1; i <= shadowWidth; i++) {\n\t\t\t\tshadow = element.cloneNode(0);\n\t\t\t\tstrokeWidth = (shadowWidth * 2) + 1 - (2 * i);\n\t\t\t\tattr(shadow, {\n\t\t\t\t\t'isShadow': 'true',\n\t\t\t\t\t'stroke': shadowOptions.color || 'black',\n\t\t\t\t\t'stroke-opacity': shadowElementOpacity * i,\n\t\t\t\t\t'stroke-width': strokeWidth,\n\t\t\t\t\t'transform': 'translate' + transform,\n\t\t\t\t\t'fill': NONE\n\t\t\t\t});\n\t\t\t\tif (cutOff) {\n\t\t\t\t\tattr(shadow, 'height', mathMax(attr(shadow, 'height') - strokeWidth, 0));\n\t\t\t\t\tshadow.cutHeight = strokeWidth;\n\t\t\t\t}\n\n\t\t\t\tif (group) {\n\t\t\t\t\tgroup.element.appendChild(shadow);\n\t\t\t\t} else {\n\t\t\t\t\telement.parentNode.insertBefore(shadow, element);\n\t\t\t\t}\n\n\t\t\t\tshadows.push(shadow);\n\t\t\t}\n\n\t\t\tthis.shadows = shadows;\n\t\t}\n\t\treturn this;\n\n\t}\n};\n\n\n/**\n * The default SVG renderer\n */\nvar SVGRenderer = function () {\n\tthis.init.apply(this, arguments);\n};\nSVGRenderer.prototype = {\n\tElement: SVGElement,\n\n\t/**\n\t * Initialize the SVGRenderer\n\t * @param {Object} container\n\t * @param {Number} width\n\t * @param {Number} height\n\t * @param {Boolean} forExport\n\t */\n\tinit: function (container, width, height, forExport) {\n\t\tvar renderer = this,\n\t\t\tloc = location,\n\t\t\tboxWrapper,\n\t\t\telement,\n\t\t\tdesc;\n\n\t\tboxWrapper = renderer.createElement('svg')\n\t\t\t.attr({\n\t\t\t\tversion: '1.1'\n\t\t\t});\n\t\telement = boxWrapper.element;\n\t\tcontainer.appendChild(element);\n\n\t\t// For browsers other than IE, add the namespace attribute (#1978)\n\t\tif (container.innerHTML.indexOf('xmlns') === -1) {\n\t\t\tattr(element, 'xmlns', SVG_NS);\n\t\t}\n\n\t\t// object properties\n\t\trenderer.isSVG = true;\n\t\trenderer.box = element;\n\t\trenderer.boxWrapper = boxWrapper;\n\t\trenderer.alignedObjects = [];\n\n\t\t// Page url used for internal references. #24, #672, #1070\n\t\trenderer.url = (isFirefox || isWebKit) && doc.getElementsByTagName('base').length ?\n\t\t\tloc.href\n\t\t\t\t.replace(/#.*?$/, '') // remove the hash\n\t\t\t\t.replace(/([\\('\\)])/g, '\\\\$1') // escape parantheses and quotes\n\t\t\t\t.replace(/ /g, '%20') : // replace spaces (needed for Safari only)\n\t\t\t'';\n\n\t\t// Add description\n\t\tdesc = this.createElement('desc').add();\n\t\tdesc.element.appendChild(doc.createTextNode('Created with ' + PRODUCT + ' ' + VERSION));\n\n\n\t\trenderer.defs = this.createElement('defs').add();\n\t\trenderer.forExport = forExport;\n\t\trenderer.gradients = {}; // Object where gradient SvgElements are stored\n\n\t\trenderer.setSize(width, height, false);\n\n\n\n\t\t// Issue 110 workaround:\n\t\t// In Firefox, if a div is positioned by percentage, its pixel position may land\n\t\t// between pixels. The container itself doesn't display this, but an SVG element\n\t\t// inside this container will be drawn at subpixel precision. In order to draw\n\t\t// sharp lines, this must be compensated for. This doesn't seem to work inside\n\t\t// iframes though (like in jsFiddle).\n\t\tvar subPixelFix, rect;\n\t\tif (isFirefox && container.getBoundingClientRect) {\n\t\t\trenderer.subPixelFix = subPixelFix = function () {\n\t\t\t\tcss(container, { left: 0, top: 0 });\n\t\t\t\trect = container.getBoundingClientRect();\n\t\t\t\tcss(container, {\n\t\t\t\t\tleft: (mathCeil(rect.left) - rect.left) + PX,\n\t\t\t\t\ttop: (mathCeil(rect.top) - rect.top) + PX\n\t\t\t\t});\n\t\t\t};\n\n\t\t\t// run the fix now\n\t\t\tsubPixelFix();\n\n\t\t\t// run it on resize\n\t\t\taddEvent(win, 'resize', subPixelFix);\n\t\t}\n\t},\n\n\t/**\n\t * Detect whether the renderer is hidden. This happens when one of the parent elements\n\t * has display: none. #608.\n\t */\n\tisHidden: function () {\n\t\treturn !this.boxWrapper.getBBox().width;\n\t},\n\n\t/**\n\t * Destroys the renderer and its allocated members.\n\t */\n\tdestroy: function () {\n\t\tvar renderer = this,\n\t\t\trendererDefs = renderer.defs;\n\t\trenderer.box = null;\n\t\trenderer.boxWrapper = renderer.boxWrapper.destroy();\n\n\t\t// Call destroy on all gradient elements\n\t\tdestroyObjectProperties(renderer.gradients || {});\n\t\trenderer.gradients = null;\n\n\t\t// Defs are null in VMLRenderer\n\t\t// Otherwise, destroy them here.\n\t\tif (rendererDefs) {\n\t\t\trenderer.defs = rendererDefs.destroy();\n\t\t}\n\n\t\t// Remove sub pixel fix handler\n\t\t// We need to check that there is a handler, otherwise all functions that are registered for event 'resize' are removed\n\t\t// See issue #982\n\t\tif (renderer.subPixelFix) {\n\t\t\tremoveEvent(win, 'resize', renderer.subPixelFix);\n\t\t}\n\n\t\trenderer.alignedObjects = null;\n\n\t\treturn null;\n\t},\n\n\t/**\n\t * Create a wrapper for an SVG element\n\t * @param {Object} nodeName\n\t */\n\tcreateElement: function (nodeName) {\n\t\tvar wrapper = new this.Element();\n\t\twrapper.init(this, nodeName);\n\t\treturn wrapper;\n\t},\n\n\t/**\n\t * Dummy function for use in canvas renderer\n\t */\n\tdraw: function () {},\n\n\t/**\n\t * Parse a simple HTML string into SVG tspans\n\t *\n\t * @param {Object} textNode The parent text SVG node\n\t */\n\tbuildText: function (wrapper) {\n\t\tvar textNode = wrapper.element,\n\t\t\trenderer = this,\n\t\t\tforExport = renderer.forExport,\n\t\t\tlines = pick(wrapper.textStr, '').toString()\n\t\t\t\t.replace(/<(b|strong)>/g, '<span style=\"font-weight:bold\">')\n\t\t\t\t.replace(/<(i|em)>/g, '<span style=\"font-style:italic\">')\n\t\t\t\t.replace(/<a/g, '<span')\n\t\t\t\t.replace(/<\\/(b|strong|i|em|a)>/g, '</span>')\n\t\t\t\t.split(/<br.*?>/g),\n\t\t\tchildNodes = textNode.childNodes,\n\t\t\tstyleRegex = /style=\"([^\"]+)\"/,\n\t\t\threfRegex = /href=\"(http[^\"]+)\"/,\n\t\t\tparentX = attr(textNode, 'x'),\n\t\t\ttextStyles = wrapper.styles,\n\t\t\twidth = textStyles && textStyles.width && pInt(textStyles.width),\n\t\t\ttextLineHeight = textStyles && textStyles.lineHeight,\n\t\t\ti = childNodes.length;\n\n\t\t/// remove old text\n\t\twhile (i--) {\n\t\t\ttextNode.removeChild(childNodes[i]);\n\t\t}\n\n\t\tif (width && !wrapper.added) {\n\t\t\tthis.box.appendChild(textNode); // attach it to the DOM to read offset width\n\t\t}\n\n\t\t// remove empty line at end\n\t\tif (lines[lines.length - 1] === '') {\n\t\t\tlines.pop();\n\t\t}\n\n\t\t// build the lines\n\t\teach(lines, function (line, lineNo) {\n\t\t\tvar spans, spanNo = 0;\n\n\t\t\tline = line.replace(/<span/g, '|||<span').replace(/<\\/span>/g, '</span>|||');\n\t\t\tspans = line.split('|||');\n\n\t\t\teach(spans, function (span) {\n\t\t\t\tif (span !== '' || spans.length === 1) {\n\t\t\t\t\tvar attributes = {},\n\t\t\t\t\t\ttspan = doc.createElementNS(SVG_NS, 'tspan'),\n\t\t\t\t\t\tspanStyle; // #390\n\t\t\t\t\tif (styleRegex.test(span)) {\n\t\t\t\t\t\tspanStyle = span.match(styleRegex)[1].replace(/(;| |^)color([ :])/, '$1fill$2');\n\t\t\t\t\t\tattr(tspan, 'style', spanStyle);\n\t\t\t\t\t}\n\t\t\t\t\tif (hrefRegex.test(span) && !forExport) { // Not for export - #1529\n\t\t\t\t\t\tattr(tspan, 'onclick', 'location.href=\\\"' + span.match(hrefRegex)[1] + '\\\"');\n\t\t\t\t\t\tcss(tspan, { cursor: 'pointer' });\n\t\t\t\t\t}\n\n\t\t\t\t\tspan = (span.replace(/<(.|\\n)*?>/g, '') || ' ')\n\t\t\t\t\t\t.replace(/&lt;/g, '<')\n\t\t\t\t\t\t.replace(/&gt;/g, '>');\n\n\t\t\t\t\t// Nested tags aren't supported, and cause crash in Safari (#1596)\n\t\t\t\t\tif (span !== ' ') {\n\n\t\t\t\t\t\t// add the text node\n\t\t\t\t\t\ttspan.appendChild(doc.createTextNode(span));\n\n\t\t\t\t\t\tif (!spanNo) { // first span in a line, align it to the left\n\t\t\t\t\t\t\tattributes.x = parentX;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tattributes.dx = 0; // #16\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// add attributes\n\t\t\t\t\t\tattr(tspan, attributes);\n\n\t\t\t\t\t\t// first span on subsequent line, add the line height\n\t\t\t\t\t\tif (!spanNo && lineNo) {\n\n\t\t\t\t\t\t\t// allow getting the right offset height in exporting in IE\n\t\t\t\t\t\t\tif (!hasSVG && forExport) {\n\t\t\t\t\t\t\t\tcss(tspan, { display: 'block' });\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t// Set the line height based on the font size of either\n\t\t\t\t\t\t\t// the text element or the tspan element\n\t\t\t\t\t\t\tattr(\n\t\t\t\t\t\t\t\ttspan,\n\t\t\t\t\t\t\t\t'dy',\n\t\t\t\t\t\t\t\ttextLineHeight || renderer.fontMetrics(\n\t\t\t\t\t\t\t\t\t/px$/.test(tspan.style.fontSize) ?\n\t\t\t\t\t\t\t\t\t\ttspan.style.fontSize :\n\t\t\t\t\t\t\t\t\t\ttextStyles.fontSize\n\t\t\t\t\t\t\t\t).h,\n\t\t\t\t\t\t\t\t// Safari 6.0.2 - too optimized for its own good (#1539)\n\t\t\t\t\t\t\t\t// TODO: revisit this with future versions of Safari\n\t\t\t\t\t\t\t\tisWebKit && tspan.offsetHeight\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Append it\n\t\t\t\t\t\ttextNode.appendChild(tspan);\n\n\t\t\t\t\t\tspanNo++;\n\n\t\t\t\t\t\t// check width and apply soft breaks\n\t\t\t\t\t\tif (width) {\n\t\t\t\t\t\t\tvar words = span.replace(/([^\\^])-/g, '$1- ').split(' '), // #1273\n\t\t\t\t\t\t\t\ttooLong,\n\t\t\t\t\t\t\t\tactualWidth,\n\t\t\t\t\t\t\t\tclipHeight = wrapper._clipHeight,\n\t\t\t\t\t\t\t\trest = [],\n\t\t\t\t\t\t\t\tdy = pInt(textLineHeight || 16),\n\t\t\t\t\t\t\t\tsoftLineNo = 1,\n\t\t\t\t\t\t\t\tbBox;\n\n\t\t\t\t\t\t\twhile (words.length || rest.length) {\n\t\t\t\t\t\t\t\tdelete wrapper.bBox; // delete cache\n\t\t\t\t\t\t\t\tbBox = wrapper.getBBox();\n\t\t\t\t\t\t\t\tactualWidth = bBox.width;\n\t\t\t\t\t\t\t\ttooLong = actualWidth > width;\n\t\t\t\t\t\t\t\tif (!tooLong || words.length === 1) { // new line needed\n\t\t\t\t\t\t\t\t\twords = rest;\n\t\t\t\t\t\t\t\t\trest = [];\n\t\t\t\t\t\t\t\t\tif (words.length) {\n\t\t\t\t\t\t\t\t\t\tsoftLineNo++;\n\n\t\t\t\t\t\t\t\t\t\tif (clipHeight && softLineNo * dy > clipHeight) {\n\t\t\t\t\t\t\t\t\t\t\twords = ['...'];\n\t\t\t\t\t\t\t\t\t\t\twrapper.attr('title', wrapper.textStr);\n\t\t\t\t\t\t\t\t\t\t} else {\n\n\t\t\t\t\t\t\t\t\t\t\ttspan = doc.createElementNS(SVG_NS, 'tspan');\n\t\t\t\t\t\t\t\t\t\t\tattr(tspan, {\n\t\t\t\t\t\t\t\t\t\t\t\tdy: dy,\n\t\t\t\t\t\t\t\t\t\t\t\tx: parentX\n\t\t\t\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\t\t\tif (spanStyle) { // #390\n\t\t\t\t\t\t\t\t\t\t\t\tattr(tspan, 'style', spanStyle);\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\ttextNode.appendChild(tspan);\n\n\t\t\t\t\t\t\t\t\t\t\tif (actualWidth > width) { // a single word is pressing it out\n\t\t\t\t\t\t\t\t\t\t\t\twidth = actualWidth;\n\t\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else { // append to existing line tspan\n\t\t\t\t\t\t\t\t\ttspan.removeChild(tspan.firstChild);\n\t\t\t\t\t\t\t\t\trest.unshift(words.pop());\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif (words.length) {\n\t\t\t\t\t\t\t\t\ttspan.appendChild(doc.createTextNode(words.join(' ').replace(/- /g, '-')));\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t},\n\n\t/**\n\t * Create a button with preset states\n\t * @param {String} text\n\t * @param {Number} x\n\t * @param {Number} y\n\t * @param {Function} callback\n\t * @param {Object} normalState\n\t * @param {Object} hoverState\n\t * @param {Object} pressedState\n\t */\n\tbutton: function (text, x, y, callback, normalState, hoverState, pressedState, disabledState) {\n\t\tvar label = this.label(text, x, y, null, null, null, null, null, 'button'),\n\t\t\tcurState = 0,\n\t\t\tstateOptions,\n\t\t\tstateStyle,\n\t\t\tnormalStyle,\n\t\t\thoverStyle,\n\t\t\tpressedStyle,\n\t\t\tdisabledStyle,\n\t\t\tSTYLE = 'style',\n\t\t\tverticalGradient = { x1: 0, y1: 0, x2: 0, y2: 1 };\n\n\t\t// Normal state - prepare the attributes\n\t\tnormalState = merge({\n\t\t\t'stroke-width': 1,\n\t\t\tstroke: '#CCCCCC',\n\t\t\tfill: {\n\t\t\t\tlinearGradient: verticalGradient,\n\t\t\t\tstops: [\n\t\t\t\t\t[0, '#FEFEFE'],\n\t\t\t\t\t[1, '#F6F6F6']\n\t\t\t\t]\n\t\t\t},\n\t\t\tr: 2,\n\t\t\tpadding: 5,\n\t\t\tstyle: {\n\t\t\t\tcolor: 'black'\n\t\t\t}\n\t\t}, normalState);\n\t\tnormalStyle = normalState[STYLE];\n\t\tdelete normalState[STYLE];\n\n\t\t// Hover state\n\t\thoverState = merge(normalState, {\n\t\t\tstroke: '#68A',\n\t\t\tfill: {\n\t\t\t\tlinearGradient: verticalGradient,\n\t\t\t\tstops: [\n\t\t\t\t\t[0, '#FFF'],\n\t\t\t\t\t[1, '#ACF']\n\t\t\t\t]\n\t\t\t}\n\t\t}, hoverState);\n\t\thoverStyle = hoverState[STYLE];\n\t\tdelete hoverState[STYLE];\n\n\t\t// Pressed state\n\t\tpressedState = merge(normalState, {\n\t\t\tstroke: '#68A',\n\t\t\tfill: {\n\t\t\t\tlinearGradient: verticalGradient,\n\t\t\t\tstops: [\n\t\t\t\t\t[0, '#9BD'],\n\t\t\t\t\t[1, '#CDF']\n\t\t\t\t]\n\t\t\t}\n\t\t}, pressedState);\n\t\tpressedStyle = pressedState[STYLE];\n\t\tdelete pressedState[STYLE];\n\n\t\t// Disabled state\n\t\tdisabledState = merge(normalState, {\n\t\t\tstyle: {\n\t\t\t\tcolor: '#CCC'\n\t\t\t}\n\t\t}, disabledState);\n\t\tdisabledStyle = disabledState[STYLE];\n\t\tdelete disabledState[STYLE];\n\n\t\t// Add the events. IE9 and IE10 need mouseover and mouseout to funciton (#667).\n\t\taddEvent(label.element, isIE ? 'mouseover' : 'mouseenter', function () {\n\t\t\tif (curState !== 3) {\n\t\t\t\tlabel.attr(hoverState)\n\t\t\t\t\t.css(hoverStyle);\n\t\t\t}\n\t\t});\n\t\taddEvent(label.element, isIE ? 'mouseout' : 'mouseleave', function () {\n\t\t\tif (curState !== 3) {\n\t\t\t\tstateOptions = [normalState, hoverState, pressedState][curState];\n\t\t\t\tstateStyle = [normalStyle, hoverStyle, pressedStyle][curState];\n\t\t\t\tlabel.attr(stateOptions)\n\t\t\t\t\t.css(stateStyle);\n\t\t\t}\n\t\t});\n\n\t\tlabel.setState = function (state) {\n\t\t\tlabel.state = curState = state;\n\t\t\tif (!state) {\n\t\t\t\tlabel.attr(normalState)\n\t\t\t\t\t.css(normalStyle);\n\t\t\t} else if (state === 2) {\n\t\t\t\tlabel.attr(pressedState)\n\t\t\t\t\t.css(pressedStyle);\n\t\t\t} else if (state === 3) {\n\t\t\t\tlabel.attr(disabledState)\n\t\t\t\t\t.css(disabledStyle);\n\t\t\t}\n\t\t};\n\n\t\treturn label\n\t\t\t.on('click', function () {\n\t\t\t\tif (curState !== 3) {\n\t\t\t\t\tcallback.call(label);\n\t\t\t\t}\n\t\t\t})\n\t\t\t.attr(normalState)\n\t\t\t.css(extend({ cursor: 'default' }, normalStyle));\n\t},\n\n\t/**\n\t * Make a straight line crisper by not spilling out to neighbour pixels\n\t * @param {Array} points\n\t * @param {Number} width\n\t */\n\tcrispLine: function (points, width) {\n\t\t// points format: [M, 0, 0, L, 100, 0]\n\t\t// normalize to a crisp line\n\t\tif (points[1] === points[4]) {\n\t\t\t// Substract due to #1129. Now bottom and left axis gridlines behave the same.\n\t\t\tpoints[1] = points[4] = mathRound(points[1]) - (width % 2 / 2);\n\t\t}\n\t\tif (points[2] === points[5]) {\n\t\t\tpoints[2] = points[5] = mathRound(points[2]) + (width % 2 / 2);\n\t\t}\n\t\treturn points;\n\t},\n\n\n\t/**\n\t * Draw a path\n\t * @param {Array} path An SVG path in array form\n\t */\n\tpath: function (path) {\n\t\tvar attr = {\n\t\t\tfill: NONE\n\t\t};\n\t\tif (isArray(path)) {\n\t\t\tattr.d = path;\n\t\t} else if (isObject(path)) { // attributes\n\t\t\textend(attr, path);\n\t\t}\n\t\treturn this.createElement('path').attr(attr);\n\t},\n\n\t/**\n\t * Draw and return an SVG circle\n\t * @param {Number} x The x position\n\t * @param {Number} y The y position\n\t * @param {Number} r The radius\n\t */\n\tcircle: function (x, y, r) {\n\t\tvar attr = isObject(x) ?\n\t\t\tx :\n\t\t\t{\n\t\t\t\tx: x,\n\t\t\t\ty: y,\n\t\t\t\tr: r\n\t\t\t};\n\n\t\treturn this.createElement('circle').attr(attr);\n\t},\n\n\t/**\n\t * Draw and return an arc\n\t * @param {Number} x X position\n\t * @param {Number} y Y position\n\t * @param {Number} r Radius\n\t * @param {Number} innerR Inner radius like used in donut charts\n\t * @param {Number} start Starting angle\n\t * @param {Number} end Ending angle\n\t */\n\tarc: function (x, y, r, innerR, start, end) {\n\t\tvar arc;\n\n\t\tif (isObject(x)) {\n\t\t\ty = x.y;\n\t\t\tr = x.r;\n\t\t\tinnerR = x.innerR;\n\t\t\tstart = x.start;\n\t\t\tend = x.end;\n\t\t\tx = x.x;\n\t\t}\n\n\t\t// Arcs are defined as symbols for the ability to set\n\t\t// attributes in attr and animate\n\t\tarc = this.symbol('arc', x || 0, y || 0, r || 0, r || 0, {\n\t\t\tinnerR: innerR || 0,\n\t\t\tstart: start || 0,\n\t\t\tend: end || 0\n\t\t});\n\t\tarc.r = r; // #959\n\t\treturn arc;\n\t},\n\n\t/**\n\t * Draw and return a rectangle\n\t * @param {Number} x Left position\n\t * @param {Number} y Top position\n\t * @param {Number} width\n\t * @param {Number} height\n\t * @param {Number} r Border corner radius\n\t * @param {Number} strokeWidth A stroke width can be supplied to allow crisp drawing\n\t */\n\trect: function (x, y, width, height, r, strokeWidth) {\n\n\t\tr = isObject(x) ? x.r : r;\n\n\t\tvar wrapper = this.createElement('rect').attr({\n\t\t\t\trx: r,\n\t\t\t\try: r,\n\t\t\t\tfill: NONE\n\t\t\t});\n\t\treturn wrapper.attr(\n\t\t\t\tisObject(x) ?\n\t\t\t\t\tx :\n\t\t\t\t\t// do not crispify when an object is passed in (as in column charts)\n\t\t\t\t\twrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0))\n\t\t\t);\n\t},\n\n\t/**\n\t * Resize the box and re-align all aligned elements\n\t * @param {Object} width\n\t * @param {Object} height\n\t * @param {Boolean} animate\n\t *\n\t */\n\tsetSize: function (width, height, animate) {\n\t\tvar renderer = this,\n\t\t\talignedObjects = renderer.alignedObjects,\n\t\t\ti = alignedObjects.length;\n\n\t\trenderer.width = width;\n\t\trenderer.height = height;\n\n\t\trenderer.boxWrapper[pick(animate, true) ? 'animate' : 'attr']({\n\t\t\twidth: width,\n\t\t\theight: height\n\t\t});\n\n\t\twhile (i--) {\n\t\t\talignedObjects[i].align();\n\t\t}\n\t},\n\n\t/**\n\t * Create a group\n\t * @param {String} name The group will be given a class name of 'highcharts-{name}'.\n\t *     This can be used for styling and scripting.\n\t */\n\tg: function (name) {\n\t\tvar elem = this.createElement('g');\n\t\treturn defined(name) ? elem.attr({ 'class': PREFIX + name }) : elem;\n\t},\n\n\t/**\n\t * Display an image\n\t * @param {String} src\n\t * @param {Number} x\n\t * @param {Number} y\n\t * @param {Number} width\n\t * @param {Number} height\n\t */\n\timage: function (src, x, y, width, height) {\n\t\tvar attribs = {\n\t\t\t\tpreserveAspectRatio: NONE\n\t\t\t},\n\t\t\telemWrapper;\n\n\t\t// optional properties\n\t\tif (arguments.length > 1) {\n\t\t\textend(attribs, {\n\t\t\t\tx: x,\n\t\t\t\ty: y,\n\t\t\t\twidth: width,\n\t\t\t\theight: height\n\t\t\t});\n\t\t}\n\n\t\telemWrapper = this.createElement('image').attr(attribs);\n\n\t\t// set the href in the xlink namespace\n\t\tif (elemWrapper.element.setAttributeNS) {\n\t\t\telemWrapper.element.setAttributeNS('http://www.w3.org/1999/xlink',\n\t\t\t\t'href', src);\n\t\t} else {\n\t\t\t// could be exporting in IE\n\t\t\t// using href throws \"not supported\" in ie7 and under, requries regex shim to fix later\n\t\t\telemWrapper.element.setAttribute('hc-svg-href', src);\n\t}\n\n\t\treturn elemWrapper;\n\t},\n\n\t/**\n\t * Draw a symbol out of pre-defined shape paths from the namespace 'symbol' object.\n\t *\n\t * @param {Object} symbol\n\t * @param {Object} x\n\t * @param {Object} y\n\t * @param {Object} radius\n\t * @param {Object} options\n\t */\n\tsymbol: function (symbol, x, y, width, height, options) {\n\n\t\tvar obj,\n\n\t\t\t// get the symbol definition function\n\t\t\tsymbolFn = this.symbols[symbol],\n\n\t\t\t// check if there's a path defined for this symbol\n\t\t\tpath = symbolFn && symbolFn(\n\t\t\t\tmathRound(x),\n\t\t\t\tmathRound(y),\n\t\t\t\twidth,\n\t\t\t\theight,\n\t\t\t\toptions\n\t\t\t),\n\n\t\t\timageElement,\n\t\t\timageRegex = /^url\\((.*?)\\)$/,\n\t\t\timageSrc,\n\t\t\timageSize,\n\t\t\tcenterImage;\n\n\t\tif (path) {\n\n\t\t\tobj = this.path(path);\n\t\t\t// expando properties for use in animate and attr\n\t\t\textend(obj, {\n\t\t\t\tsymbolName: symbol,\n\t\t\t\tx: x,\n\t\t\t\ty: y,\n\t\t\t\twidth: width,\n\t\t\t\theight: height\n\t\t\t});\n\t\t\tif (options) {\n\t\t\t\textend(obj, options);\n\t\t\t}\n\n\n\t\t// image symbols\n\t\t} else if (imageRegex.test(symbol)) {\n\n\t\t\t// On image load, set the size and position\n\t\t\tcenterImage = function (img, size) {\n\t\t\t\tif (img.element) { // it may be destroyed in the meantime (#1390)\n\t\t\t\t\timg.attr({\n\t\t\t\t\t\twidth: size[0],\n\t\t\t\t\t\theight: size[1]\n\t\t\t\t\t});\n\n\t\t\t\t\tif (!img.alignByTranslate) { // #185\n\t\t\t\t\t\timg.translate(\n\t\t\t\t\t\t\tmathRound((width - size[0]) / 2), // #1378\n\t\t\t\t\t\t\tmathRound((height - size[1]) / 2)\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t};\n\n\t\t\timageSrc = symbol.match(imageRegex)[1];\n\t\t\timageSize = symbolSizes[imageSrc];\n\n\t\t\t// Ireate the image synchronously, add attribs async\n\t\t\tobj = this.image(imageSrc)\n\t\t\t\t.attr({\n\t\t\t\t\tx: x,\n\t\t\t\t\ty: y\n\t\t\t\t});\n\t\t\tobj.isImg = true;\n\n\t\t\tif (imageSize) {\n\t\t\t\tcenterImage(obj, imageSize);\n\t\t\t} else {\n\t\t\t\t// Initialize image to be 0 size so export will still function if there's no cached sizes.\n\t\t\t\t//\n\t\t\t\tobj.attr({ width: 0, height: 0 });\n\n\t\t\t\t// Create a dummy JavaScript image to get the width and height. Due to a bug in IE < 8,\n\t\t\t\t// the created element must be assigned to a variable in order to load (#292).\n\t\t\t\timageElement = createElement('img', {\n\t\t\t\t\tonload: function () {\n\t\t\t\t\t\tcenterImage(obj, symbolSizes[imageSrc] = [this.width, this.height]);\n\t\t\t\t\t},\n\t\t\t\t\tsrc: imageSrc\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\treturn obj;\n\t},\n\n\t/**\n\t * An extendable collection of functions for defining symbol paths.\n\t */\n\tsymbols: {\n\t\t'circle': function (x, y, w, h) {\n\t\t\tvar cpw = 0.166 * w;\n\t\t\treturn [\n\t\t\t\tM, x + w / 2, y,\n\t\t\t\t'C', x + w + cpw, y, x + w + cpw, y + h, x + w / 2, y + h,\n\t\t\t\t'C', x - cpw, y + h, x - cpw, y, x + w / 2, y,\n\t\t\t\t'Z'\n\t\t\t];\n\t\t},\n\n\t\t'square': function (x, y, w, h) {\n\t\t\treturn [\n\t\t\t\tM, x, y,\n\t\t\t\tL, x + w, y,\n\t\t\t\tx + w, y + h,\n\t\t\t\tx, y + h,\n\t\t\t\t'Z'\n\t\t\t];\n\t\t},\n\n\t\t'triangle': function (x, y, w, h) {\n\t\t\treturn [\n\t\t\t\tM, x + w / 2, y,\n\t\t\t\tL, x + w, y + h,\n\t\t\t\tx, y + h,\n\t\t\t\t'Z'\n\t\t\t];\n\t\t},\n\n\t\t'triangle-down': function (x, y, w, h) {\n\t\t\treturn [\n\t\t\t\tM, x, y,\n\t\t\t\tL, x + w, y,\n\t\t\t\tx + w / 2, y + h,\n\t\t\t\t'Z'\n\t\t\t];\n\t\t},\n\t\t'diamond': function (x, y, w, h) {\n\t\t\treturn [\n\t\t\t\tM, x + w / 2, y,\n\t\t\t\tL, x + w, y + h / 2,\n\t\t\t\tx + w / 2, y + h,\n\t\t\t\tx, y + h / 2,\n\t\t\t\t'Z'\n\t\t\t];\n\t\t},\n\t\t'arc': function (x, y, w, h, options) {\n\t\t\tvar start = options.start,\n\t\t\t\tradius = options.r || w || h,\n\t\t\t\tend = options.end - 0.001, // to prevent cos and sin of start and end from becoming equal on 360 arcs (related: #1561)\n\t\t\t\tinnerRadius = options.innerR,\n\t\t\t\topen = options.open,\n\t\t\t\tcosStart = mathCos(start),\n\t\t\t\tsinStart = mathSin(start),\n\t\t\t\tcosEnd = mathCos(end),\n\t\t\t\tsinEnd = mathSin(end),\n\t\t\t\tlongArc = options.end - start < mathPI ? 0 : 1;\n\n\t\t\treturn [\n\t\t\t\tM,\n\t\t\t\tx + radius * cosStart,\n\t\t\t\ty + radius * sinStart,\n\t\t\t\t'A', // arcTo\n\t\t\t\tradius, // x radius\n\t\t\t\tradius, // y radius\n\t\t\t\t0, // slanting\n\t\t\t\tlongArc, // long or short arc\n\t\t\t\t1, // clockwise\n\t\t\t\tx + radius * cosEnd,\n\t\t\t\ty + radius * sinEnd,\n\t\t\t\topen ? M : L,\n\t\t\t\tx + innerRadius * cosEnd,\n\t\t\t\ty + innerRadius * sinEnd,\n\t\t\t\t'A', // arcTo\n\t\t\t\tinnerRadius, // x radius\n\t\t\t\tinnerRadius, // y radius\n\t\t\t\t0, // slanting\n\t\t\t\tlongArc, // long or short arc\n\t\t\t\t0, // clockwise\n\t\t\t\tx + innerRadius * cosStart,\n\t\t\t\ty + innerRadius * sinStart,\n\n\t\t\t\topen ? '' : 'Z' // close\n\t\t\t];\n\t\t}\n\t},\n\n\t/**\n\t * Define a clipping rectangle\n\t * @param {String} id\n\t * @param {Number} x\n\t * @param {Number} y\n\t * @param {Number} width\n\t * @param {Number} height\n\t */\n\tclipRect: function (x, y, width, height) {\n\t\tvar wrapper,\n\t\t\tid = PREFIX + idCounter++,\n\n\t\t\tclipPath = this.createElement('clipPath').attr({\n\t\t\t\tid: id\n\t\t\t}).add(this.defs);\n\n\t\twrapper = this.rect(x, y, width, height, 0).add(clipPath);\n\t\twrapper.id = id;\n\t\twrapper.clipPath = clipPath;\n\n\t\treturn wrapper;\n\t},\n\n\n\t/**\n\t * Take a color and return it if it's a string, make it a gradient if it's a\n\t * gradient configuration object. Prior to Highstock, an array was used to define\n\t * a linear gradient with pixel positions relative to the SVG. In newer versions\n\t * we change the coordinates to apply relative to the shape, using coordinates\n\t * 0-1 within the shape. To preserve backwards compatibility, linearGradient\n\t * in this definition is an object of x1, y1, x2 and y2.\n\t *\n\t * @param {Object} color The color or config object\n\t */\n\tcolor: function (color, elem, prop) {\n\t\tvar renderer = this,\n\t\t\tcolorObject,\n\t\t\tregexRgba = /^rgba/,\n\t\t\tgradName,\n\t\t\tgradAttr,\n\t\t\tgradients,\n\t\t\tgradientObject,\n\t\t\tstops,\n\t\t\tstopColor,\n\t\t\tstopOpacity,\n\t\t\tradialReference,\n\t\t\tn,\n\t\t\tid,\n\t\t\tkey = [];\n\n\t\t// Apply linear or radial gradients\n\t\tif (color && color.linearGradient) {\n\t\t\tgradName = 'linearGradient';\n\t\t} else if (color && color.radialGradient) {\n\t\t\tgradName = 'radialGradient';\n\t\t}\n\n\t\tif (gradName) {\n\t\t\tgradAttr = color[gradName];\n\t\t\tgradients = renderer.gradients;\n\t\t\tstops = color.stops;\n\t\t\tradialReference = elem.radialReference;\n\n\t\t\t// Keep < 2.2 kompatibility\n\t\t\tif (isArray(gradAttr)) {\n\t\t\t\tcolor[gradName] = gradAttr = {\n\t\t\t\t\tx1: gradAttr[0],\n\t\t\t\t\ty1: gradAttr[1],\n\t\t\t\t\tx2: gradAttr[2],\n\t\t\t\t\ty2: gradAttr[3],\n\t\t\t\t\tgradientUnits: 'userSpaceOnUse'\n\t\t\t\t};\n\t\t\t}\n\n\t\t\t// Correct the radial gradient for the radial reference system\n\t\t\tif (gradName === 'radialGradient' && radialReference && !defined(gradAttr.gradientUnits)) {\n\t\t\t\tgradAttr = merge(gradAttr, {\n\t\t\t\t\tcx: (radialReference[0] - radialReference[2] / 2) + gradAttr.cx * radialReference[2],\n\t\t\t\t\tcy: (radialReference[1] - radialReference[2] / 2) + gradAttr.cy * radialReference[2],\n\t\t\t\t\tr: gradAttr.r * radialReference[2],\n\t\t\t\t\tgradientUnits: 'userSpaceOnUse'\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Build the unique key to detect whether we need to create a new element (#1282)\n\t\t\tfor (n in gradAttr) {\n\t\t\t\tif (n !== 'id') {\n\t\t\t\t\tkey.push(n, gradAttr[n]);\n\t\t\t\t}\n\t\t\t}\n\t\t\tfor (n in stops) {\n\t\t\t\tkey.push(stops[n]);\n\t\t\t}\n\t\t\tkey = key.join(',');\n\n\t\t\t// Check if a gradient object with the same config object is created within this renderer\n\t\t\tif (gradients[key]) {\n\t\t\t\tid = gradients[key].id;\n\n\t\t\t} else {\n\n\t\t\t\t// Set the id and create the element\n\t\t\t\tgradAttr.id = id = PREFIX + idCounter++;\n\t\t\t\tgradients[key] = gradientObject = renderer.createElement(gradName)\n\t\t\t\t\t.attr(gradAttr)\n\t\t\t\t\t.add(renderer.defs);\n\n\n\t\t\t\t// The gradient needs to keep a list of stops to be able to destroy them\n\t\t\t\tgradientObject.stops = [];\n\t\t\t\teach(stops, function (stop) {\n\t\t\t\t\tvar stopObject;\n\t\t\t\t\tif (regexRgba.test(stop[1])) {\n\t\t\t\t\t\tcolorObject = Color(stop[1]);\n\t\t\t\t\t\tstopColor = colorObject.get('rgb');\n\t\t\t\t\t\tstopOpacity = colorObject.get('a');\n\t\t\t\t\t} else {\n\t\t\t\t\t\tstopColor = stop[1];\n\t\t\t\t\t\tstopOpacity = 1;\n\t\t\t\t\t}\n\t\t\t\t\tstopObject = renderer.createElement('stop').attr({\n\t\t\t\t\t\toffset: stop[0],\n\t\t\t\t\t\t'stop-color': stopColor,\n\t\t\t\t\t\t'stop-opacity': stopOpacity\n\t\t\t\t\t}).add(gradientObject);\n\n\t\t\t\t\t// Add the stop element to the gradient\n\t\t\t\t\tgradientObject.stops.push(stopObject);\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Return the reference to the gradient object\n\t\t\treturn 'url(' + renderer.url + '#' + id + ')';\n\n\t\t// Webkit and Batik can't show rgba.\n\t\t} else if (regexRgba.test(color)) {\n\t\t\tcolorObject = Color(color);\n\t\t\tattr(elem, prop + '-opacity', colorObject.get('a'));\n\n\t\t\treturn colorObject.get('rgb');\n\n\n\t\t} else {\n\t\t\t// Remove the opacity attribute added above. Does not throw if the attribute is not there.\n\t\t\telem.removeAttribute(prop + '-opacity');\n\n\t\t\treturn color;\n\t\t}\n\n\t},\n\n\n\t/**\n\t * Add text to the SVG object\n\t * @param {String} str\n\t * @param {Number} x Left position\n\t * @param {Number} y Top position\n\t * @param {Boolean} useHTML Use HTML to render the text\n\t */\n\ttext: function (str, x, y, useHTML) {\n\n\t\t// declare variables\n\t\tvar renderer = this,\n\t\t\tdefaultChartStyle = defaultOptions.chart.style,\n\t\t\tfakeSVG = useCanVG || (!hasSVG && renderer.forExport),\n\t\t\twrapper;\n\n\t\tif (useHTML && !renderer.forExport) {\n\t\t\treturn renderer.html(str, x, y);\n\t\t}\n\n\t\tx = mathRound(pick(x, 0));\n\t\ty = mathRound(pick(y, 0));\n\n\t\twrapper = renderer.createElement('text')\n\t\t\t.attr({\n\t\t\t\tx: x,\n\t\t\t\ty: y,\n\t\t\t\ttext: str\n\t\t\t})\n\t\t\t.css({\n\t\t\t\tfontFamily: defaultChartStyle.fontFamily,\n\t\t\t\tfontSize: defaultChartStyle.fontSize\n\t\t\t});\n\n\t\t// Prevent wrapping from creating false offsetWidths in export in legacy IE (#1079, #1063)\n\t\tif (fakeSVG) {\n\t\t\twrapper.css({\n\t\t\t\tposition: ABSOLUTE\n\t\t\t});\n\t\t}\n\n\t\twrapper.x = x;\n\t\twrapper.y = y;\n\t\treturn wrapper;\n\t},\n\n\n\t/**\n\t * Create HTML text node. This is used by the VML renderer as well as the SVG\n\t * renderer through the useHTML option.\n\t *\n\t * @param {String} str\n\t * @param {Number} x\n\t * @param {Number} y\n\t */\n\thtml: function (str, x, y) {\n\t\tvar defaultChartStyle = defaultOptions.chart.style,\n\t\t\twrapper = this.createElement('span'),\n\t\t\tattrSetters = wrapper.attrSetters,\n\t\t\telement = wrapper.element,\n\t\t\trenderer = wrapper.renderer;\n\n\t\t// Text setter\n\t\tattrSetters.text = function (value) {\n\t\t\tif (value !== element.innerHTML) {\n\t\t\t\tdelete this.bBox;\n\t\t\t}\n\t\t\telement.innerHTML = value;\n\t\t\treturn false;\n\t\t};\n\n\t\t// Various setters which rely on update transform\n\t\tattrSetters.x = attrSetters.y = attrSetters.align = function (value, key) {\n\t\t\tif (key === 'align') {\n\t\t\t\tkey = 'textAlign'; // Do not overwrite the SVGElement.align method. Same as VML.\n\t\t\t}\n\t\t\twrapper[key] = value;\n\t\t\twrapper.htmlUpdateTransform();\n\t\t\treturn false;\n\t\t};\n\n\t\t// Set the default attributes\n\t\twrapper.attr({\n\t\t\t\ttext: str,\n\t\t\t\tx: mathRound(x),\n\t\t\t\ty: mathRound(y)\n\t\t\t})\n\t\t\t.css({\n\t\t\t\tposition: ABSOLUTE,\n\t\t\t\twhiteSpace: 'nowrap',\n\t\t\t\tfontFamily: defaultChartStyle.fontFamily,\n\t\t\t\tfontSize: defaultChartStyle.fontSize\n\t\t\t});\n\n\t\t// Use the HTML specific .css method\n\t\twrapper.css = wrapper.htmlCss;\n\n\t\t// This is specific for HTML within SVG\n\t\tif (renderer.isSVG) {\n\t\t\twrapper.add = function (svgGroupWrapper) {\n\n\t\t\t\tvar htmlGroup,\n\t\t\t\t\tcontainer = renderer.box.parentNode,\n\t\t\t\t\tparentGroup,\n\t\t\t\t\tparents = [];\n\n\t\t\t\t// Create a mock group to hold the HTML elements\n\t\t\t\tif (svgGroupWrapper) {\n\t\t\t\t\thtmlGroup = svgGroupWrapper.div;\n\t\t\t\t\tif (!htmlGroup) {\n\n\t\t\t\t\t\t// Read the parent chain into an array and read from top down\n\t\t\t\t\t\tparentGroup = svgGroupWrapper;\n\t\t\t\t\t\twhile (parentGroup) {\n\n\t\t\t\t\t\t\tparents.push(parentGroup);\n\n\t\t\t\t\t\t\t// Move up to the next parent group\n\t\t\t\t\t\t\tparentGroup = parentGroup.parentGroup;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Ensure dynamically updating position when any parent is translated\n\t\t\t\t\t\teach(parents.reverse(), function (parentGroup) {\n\t\t\t\t\t\t\tvar htmlGroupStyle;\n\n\t\t\t\t\t\t\t// Create a HTML div and append it to the parent div to emulate\n\t\t\t\t\t\t\t// the SVG group structure\n\t\t\t\t\t\t\thtmlGroup = parentGroup.div = parentGroup.div || createElement(DIV, {\n\t\t\t\t\t\t\t\tclassName: attr(parentGroup.element, 'class')\n\t\t\t\t\t\t\t}, {\n\t\t\t\t\t\t\t\tposition: ABSOLUTE,\n\t\t\t\t\t\t\t\tleft: (parentGroup.translateX || 0) + PX,\n\t\t\t\t\t\t\t\ttop: (parentGroup.translateY || 0) + PX\n\t\t\t\t\t\t\t}, htmlGroup || container); // the top group is appended to container\n\n\t\t\t\t\t\t\t// Shortcut\n\t\t\t\t\t\t\thtmlGroupStyle = htmlGroup.style;\n\n\t\t\t\t\t\t\t// Set listeners to update the HTML div's position whenever the SVG group\n\t\t\t\t\t\t\t// position is changed\n\t\t\t\t\t\t\textend(parentGroup.attrSetters, {\n\t\t\t\t\t\t\t\ttranslateX: function (value) {\n\t\t\t\t\t\t\t\t\thtmlGroupStyle.left = value + PX;\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\ttranslateY: function (value) {\n\t\t\t\t\t\t\t\t\thtmlGroupStyle.top = value + PX;\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\tvisibility: function (value, key) {\n\t\t\t\t\t\t\t\t\thtmlGroupStyle[key] = value;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t});\n\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\thtmlGroup = container;\n\t\t\t\t}\n\n\t\t\t\thtmlGroup.appendChild(element);\n\n\t\t\t\t// Shared with VML:\n\t\t\t\twrapper.added = true;\n\t\t\t\tif (wrapper.alignOnAdd) {\n\t\t\t\t\twrapper.htmlUpdateTransform();\n\t\t\t\t}\n\n\t\t\t\treturn wrapper;\n\t\t\t};\n\t\t}\n\t\treturn wrapper;\n\t},\n\n\t/**\n\t * Utility to return the baseline offset and total line height from the font size\n\t */\n\tfontMetrics: function (fontSize) {\n\t\tfontSize = pInt(fontSize || 11);\n\n\t\t// Empirical values found by comparing font size and bounding box height.\n\t\t// Applies to the default font family. http://jsfiddle.net/highcharts/7xvn7/\n\t\tvar lineHeight = fontSize < 24 ? fontSize + 4 : mathRound(fontSize * 1.2),\n\t\t\tbaseline = mathRound(lineHeight * 0.8);\n\n\t\treturn {\n\t\t\th: lineHeight,\n\t\t\tb: baseline\n\t\t};\n\t},\n\n\t/**\n\t * Add a label, a text item that can hold a colored or gradient background\n\t * as well as a border and shadow.\n\t * @param {string} str\n\t * @param {Number} x\n\t * @param {Number} y\n\t * @param {String} shape\n\t * @param {Number} anchorX In case the shape has a pointer, like a flag, this is the\n\t *    coordinates it should be pinned to\n\t * @param {Number} anchorY\n\t * @param {Boolean} baseline Whether to position the label relative to the text baseline,\n\t *    like renderer.text, or to the upper border of the rectangle.\n\t * @param {String} className Class name for the group\n\t */\n\tlabel: function (str, x, y, shape, anchorX, anchorY, useHTML, baseline, className) {\n\n\t\tvar renderer = this,\n\t\t\twrapper = renderer.g(className),\n\t\t\ttext = renderer.text('', 0, 0, useHTML)\n\t\t\t\t.attr({\n\t\t\t\t\tzIndex: 1\n\t\t\t\t}),\n\t\t\t\t//.add(wrapper),\n\t\t\tbox,\n\t\t\tbBox,\n\t\t\talignFactor = 0,\n\t\t\tpadding = 3,\n\t\t\tpaddingLeft = 0,\n\t\t\twidth,\n\t\t\theight,\n\t\t\twrapperX,\n\t\t\twrapperY,\n\t\t\tcrispAdjust = 0,\n\t\t\tdeferredAttr = {},\n\t\t\tbaselineOffset,\n\t\t\tattrSetters = wrapper.attrSetters,\n\t\t\tneedsBox;\n\n\t\t/**\n\t\t * This function runs after the label is added to the DOM (when the bounding box is\n\t\t * available), and after the text of the label is updated to detect the new bounding\n\t\t * box and reflect it in the border box.\n\t\t */\n\t\tfunction updateBoxSize() {\n\t\t\tvar boxX,\n\t\t\t\tboxY,\n\t\t\t\tstyle = text.element.style;\n\n\t\t\tbBox = (width === undefined || height === undefined || wrapper.styles.textAlign) &&\n\t\t\t\ttext.getBBox();\n\t\t\twrapper.width = (width || bBox.width || 0) + 2 * padding + paddingLeft;\n\t\t\twrapper.height = (height || bBox.height || 0) + 2 * padding;\n\n\t\t\t// update the label-scoped y offset\n\t\t\tbaselineOffset = padding + renderer.fontMetrics(style && style.fontSize).b;\n\n\t\t\tif (needsBox) {\n\n\t\t\t\t// create the border box if it is not already present\n\t\t\t\tif (!box) {\n\t\t\t\t\tboxX = mathRound(-alignFactor * padding);\n\t\t\t\t\tboxY = baseline ? -baselineOffset : 0;\n\n\t\t\t\t\twrapper.box = box = shape ?\n\t\t\t\t\t\trenderer.symbol(shape, boxX, boxY, wrapper.width, wrapper.height) :\n\t\t\t\t\t\trenderer.rect(boxX, boxY, wrapper.width, wrapper.height, 0, deferredAttr[STROKE_WIDTH]);\n\t\t\t\t\tbox.add(wrapper);\n\t\t\t\t}\n\n\t\t\t\t// apply the box attributes\n\t\t\t\tif (!box.isImg) { // #1630\n\t\t\t\t\tbox.attr(merge({\n\t\t\t\t\t\twidth: wrapper.width,\n\t\t\t\t\t\theight: wrapper.height\n\t\t\t\t\t}, deferredAttr));\n\t\t\t\t}\n\t\t\t\tdeferredAttr = null;\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * This function runs after setting text or padding, but only if padding is changed\n\t\t */\n\t\tfunction updateTextPadding() {\n\t\t\tvar styles = wrapper.styles,\n\t\t\t\ttextAlign = styles && styles.textAlign,\n\t\t\t\tx = paddingLeft + padding * (1 - alignFactor),\n\t\t\t\ty;\n\n\t\t\t// determin y based on the baseline\n\t\t\ty = baseline ? 0 : baselineOffset;\n\n\t\t\t// compensate for alignment\n\t\t\tif (defined(width) && (textAlign === 'center' || textAlign === 'right')) {\n\t\t\t\tx += { center: 0.5, right: 1 }[textAlign] * (width - bBox.width);\n\t\t\t}\n\n\t\t\t// update if anything changed\n\t\t\tif (x !== text.x || y !== text.y) {\n\t\t\t\ttext.attr({\n\t\t\t\t\tx: x,\n\t\t\t\t\ty: y\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// record current values\n\t\t\ttext.x = x;\n\t\t\ttext.y = y;\n\t\t}\n\n\t\t/**\n\t\t * Set a box attribute, or defer it if the box is not yet created\n\t\t * @param {Object} key\n\t\t * @param {Object} value\n\t\t */\n\t\tfunction boxAttr(key, value) {\n\t\t\tif (box) {\n\t\t\t\tbox.attr(key, value);\n\t\t\t} else {\n\t\t\t\tdeferredAttr[key] = value;\n\t\t\t}\n\t\t}\n\n\t\tfunction getSizeAfterAdd() {\n\t\t\ttext.add(wrapper);\n\t\t\twrapper.attr({\n\t\t\t\ttext: str, // alignment is available now\n\t\t\t\tx: x,\n\t\t\t\ty: y\n\t\t\t});\n\n\t\t\tif (box && defined(anchorX)) {\n\t\t\t\twrapper.attr({\n\t\t\t\t\tanchorX: anchorX,\n\t\t\t\t\tanchorY: anchorY\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t/**\n\t\t * After the text element is added, get the desired size of the border box\n\t\t * and add it before the text in the DOM.\n\t\t */\n\t\taddEvent(wrapper, 'add', getSizeAfterAdd);\n\n\t\t/*\n\t\t * Add specific attribute setters.\n\t\t */\n\n\t\t// only change local variables\n\t\tattrSetters.width = function (value) {\n\t\t\twidth = value;\n\t\t\treturn false;\n\t\t};\n\t\tattrSetters.height = function (value) {\n\t\t\theight = value;\n\t\t\treturn false;\n\t\t};\n\t\tattrSetters.padding =  function (value) {\n\t\t\tif (defined(value) && value !== padding) {\n\t\t\t\tpadding = value;\n\t\t\t\tupdateTextPadding();\n\t\t\t}\n\t\t\treturn false;\n\t\t};\n\t\tattrSetters.paddingLeft =  function (value) {\n\t\t\tif (defined(value) && value !== paddingLeft) {\n\t\t\t\tpaddingLeft = value;\n\t\t\t\tupdateTextPadding();\n\t\t\t}\n\t\t\treturn false;\n\t\t};\n\n\n\t\t// change local variable and set attribue as well\n\t\tattrSetters.align = function (value) {\n\t\t\talignFactor = { left: 0, center: 0.5, right: 1 }[value];\n\t\t\treturn false; // prevent setting text-anchor on the group\n\t\t};\n\n\t\t// apply these to the box and the text alike\n\t\tattrSetters.text = function (value, key) {\n\t\t\ttext.attr(key, value);\n\t\t\tupdateBoxSize();\n\t\t\tupdateTextPadding();\n\t\t\treturn false;\n\t\t};\n\n\t\t// apply these to the box but not to the text\n\t\tattrSetters[STROKE_WIDTH] = function (value, key) {\n\t\t\tneedsBox = true;\n\t\t\tcrispAdjust = value % 2 / 2;\n\t\t\tboxAttr(key, value);\n\t\t\treturn false;\n\t\t};\n\t\tattrSetters.stroke = attrSetters.fill = attrSetters.r = function (value, key) {\n\t\t\tif (key === 'fill') {\n\t\t\t\tneedsBox = true;\n\t\t\t}\n\t\t\tboxAttr(key, value);\n\t\t\treturn false;\n\t\t};\n\t\tattrSetters.anchorX = function (value, key) {\n\t\t\tanchorX = value;\n\t\t\tboxAttr(key, value + crispAdjust - wrapperX);\n\t\t\treturn false;\n\t\t};\n\t\tattrSetters.anchorY = function (value, key) {\n\t\t\tanchorY = value;\n\t\t\tboxAttr(key, value - wrapperY);\n\t\t\treturn false;\n\t\t};\n\n\t\t// rename attributes\n\t\tattrSetters.x = function (value) {\n\t\t\twrapper.x = value; // for animation getter\n\t\t\tvalue -= alignFactor * ((width || bBox.width) + padding);\n\t\t\twrapperX = mathRound(value);\n\n\t\t\twrapper.attr('translateX', wrapperX);\n\t\t\treturn false;\n\t\t};\n\t\tattrSetters.y = function (value) {\n\t\t\twrapperY = wrapper.y = mathRound(value);\n\t\t\twrapper.attr('translateY', wrapperY);\n\t\t\treturn false;\n\t\t};\n\n\t\t// Redirect certain methods to either the box or the text\n\t\tvar baseCss = wrapper.css;\n\t\treturn extend(wrapper, {\n\t\t\t/**\n\t\t\t * Pick up some properties and apply them to the text instead of the wrapper\n\t\t\t */\n\t\t\tcss: function (styles) {\n\t\t\t\tif (styles) {\n\t\t\t\t\tvar textStyles = {};\n\t\t\t\t\tstyles = merge(styles); // create a copy to avoid altering the original object (#537)\n\t\t\t\t\teach(['fontSize', 'fontWeight', 'fontFamily', 'color', 'lineHeight', 'width', 'textDecoration', 'textShadow'], function (prop) {\n\t\t\t\t\t\tif (styles[prop] !== UNDEFINED) {\n\t\t\t\t\t\t\ttextStyles[prop] = styles[prop];\n\t\t\t\t\t\t\tdelete styles[prop];\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t\ttext.css(textStyles);\n\t\t\t\t}\n\t\t\t\treturn baseCss.call(wrapper, styles);\n\t\t\t},\n\t\t\t/**\n\t\t\t * Return the bounding box of the box, not the group\n\t\t\t */\n\t\t\tgetBBox: function () {\n\t\t\t\treturn {\n\t\t\t\t\twidth: bBox.width + 2 * padding,\n\t\t\t\t\theight: bBox.height + 2 * padding,\n\t\t\t\t\tx: bBox.x - padding,\n\t\t\t\t\ty: bBox.y - padding\n\t\t\t\t};\n\t\t\t},\n\t\t\t/**\n\t\t\t * Apply the shadow to the box\n\t\t\t */\n\t\t\tshadow: function (b) {\n\t\t\t\tif (box) {\n\t\t\t\t\tbox.shadow(b);\n\t\t\t\t}\n\t\t\t\treturn wrapper;\n\t\t\t},\n\t\t\t/**\n\t\t\t * Destroy and release memory.\n\t\t\t */\n\t\t\tdestroy: function () {\n\t\t\t\tremoveEvent(wrapper, 'add', getSizeAfterAdd);\n\n\t\t\t\t// Added by button implementation\n\t\t\t\tremoveEvent(wrapper.element, 'mouseenter');\n\t\t\t\tremoveEvent(wrapper.element, 'mouseleave');\n\n\t\t\t\tif (text) {\n\t\t\t\t\ttext = text.destroy();\n\t\t\t\t}\n\t\t\t\tif (box) {\n\t\t\t\t\tbox = box.destroy();\n\t\t\t\t}\n\t\t\t\t// Call base implementation to destroy the rest\n\t\t\t\tSVGElement.prototype.destroy.call(wrapper);\n\n\t\t\t\t// Release local pointers (#1298)\n\t\t\t\twrapper = renderer = updateBoxSize = updateTextPadding = boxAttr = getSizeAfterAdd = null;\n\t\t\t}\n\t\t});\n\t}\n}; // end SVGRenderer\n\n\n// general renderer\nRenderer = SVGRenderer;\n\n\n/* ****************************************************************************\n *                                                                            *\n * START OF INTERNET EXPLORER <= 8 SPECIFIC CODE                              *\n *                                                                            *\n * For applications and websites that don't need IE support, like platform    *\n * targeted mobile apps and web apps, this code can be removed.               *\n *                                                                            *\n *****************************************************************************/\n\n/**\n * @constructor\n */\nvar VMLRenderer, VMLElement;\nif (!hasSVG && !useCanVG) {\n\n/**\n * The VML element wrapper.\n */\nHighcharts.VMLElement = VMLElement = {\n\n\t/**\n\t * Initialize a new VML element wrapper. It builds the markup as a string\n\t * to minimize DOM traffic.\n\t * @param {Object} renderer\n\t * @param {Object} nodeName\n\t */\n\tinit: function (renderer, nodeName) {\n\t\tvar wrapper = this,\n\t\t\tmarkup =  ['<', nodeName, ' filled=\"f\" stroked=\"f\"'],\n\t\t\tstyle = ['position: ', ABSOLUTE, ';'],\n\t\t\tisDiv = nodeName === DIV;\n\n\t\t// divs and shapes need size\n\t\tif (nodeName === 'shape' || isDiv) {\n\t\t\tstyle.push('left:0;top:0;width:1px;height:1px;');\n\t\t}\n\t\tstyle.push('visibility: ', isDiv ? HIDDEN : VISIBLE);\n\n\t\tmarkup.push(' style=\"', style.join(''), '\"/>');\n\n\t\t// create element with default attributes and style\n\t\tif (nodeName) {\n\t\t\tmarkup = isDiv || nodeName === 'span' || nodeName === 'img' ?\n\t\t\t\tmarkup.join('')\n\t\t\t\t: renderer.prepVML(markup);\n\t\t\twrapper.element = createElement(markup);\n\t\t}\n\n\t\twrapper.renderer = renderer;\n\t\twrapper.attrSetters = {};\n\t},\n\n\t/**\n\t * Add the node to the given parent\n\t * @param {Object} parent\n\t */\n\tadd: function (parent) {\n\t\tvar wrapper = this,\n\t\t\trenderer = wrapper.renderer,\n\t\t\telement = wrapper.element,\n\t\t\tbox = renderer.box,\n\t\t\tinverted = parent && parent.inverted,\n\n\t\t\t// get the parent node\n\t\t\tparentNode = parent ?\n\t\t\t\tparent.element || parent :\n\t\t\t\tbox;\n\n\n\t\t// if the parent group is inverted, apply inversion on all children\n\t\tif (inverted) { // only on groups\n\t\t\trenderer.invertChild(element, parentNode);\n\t\t}\n\n\t\t// append it\n\t\tparentNode.appendChild(element);\n\n\t\t// align text after adding to be able to read offset\n\t\twrapper.added = true;\n\t\tif (wrapper.alignOnAdd && !wrapper.deferUpdateTransform) {\n\t\t\twrapper.updateTransform();\n\t\t}\n\n\t\t// fire an event for internal hooks\n\t\tfireEvent(wrapper, 'add');\n\n\t\treturn wrapper;\n\t},\n\n\t/**\n\t * VML always uses htmlUpdateTransform\n\t */\n\tupdateTransform: SVGElement.prototype.htmlUpdateTransform,\n\n\t/**\n\t * Set the rotation of a span with oldIE's filter\n\t */\n\tsetSpanRotation: function (rotation, sintheta, costheta) {\n\t\t// Adjust for alignment and rotation. Rotation of useHTML content is not yet implemented\n\t\t// but it can probably be implemented for Firefox 3.5+ on user request. FF3.5+\n\t\t// has support for CSS3 transform. The getBBox method also needs to be updated\n\t\t// to compensate for the rotation, like it currently does for SVG.\n\t\t// Test case: http://highcharts.com/tests/?file=text-rotation\n\t\tcss(this.element, {\n\t\t\tfilter: rotation ? ['progid:DXImageTransform.Microsoft.Matrix(M11=', costheta,\n\t\t\t\t', M12=', -sintheta, ', M21=', sintheta, ', M22=', costheta,\n\t\t\t\t', sizingMethod=\\'auto expand\\')'].join('') : NONE\n\t\t});\n\t},\n\n\t/**\n\t * Converts a subset of an SVG path definition to its VML counterpart. Takes an array\n\t * as the parameter and returns a string.\n\t */\n\tpathToVML: function (value) {\n\t\t// convert paths\n\t\tvar i = value.length,\n\t\t\tpath = [],\n\t\t\tclockwise;\n\n\t\twhile (i--) {\n\n\t\t\t// Multiply by 10 to allow subpixel precision.\n\t\t\t// Substracting half a pixel seems to make the coordinates\n\t\t\t// align with SVG, but this hasn't been tested thoroughly\n\t\t\tif (isNumber(value[i])) {\n\t\t\t\tpath[i] = mathRound(value[i] * 10) - 5;\n\t\t\t} else if (value[i] === 'Z') { // close the path\n\t\t\t\tpath[i] = 'x';\n\t\t\t} else {\n\t\t\t\tpath[i] = value[i];\n\n\t\t\t\t// When the start X and end X coordinates of an arc are too close,\n\t\t\t\t// they are rounded to the same value above. In this case, substract 1 from the end X\n\t\t\t\t// position. #760, #1371.\n\t\t\t\tif (value.isArc && (value[i] === 'wa' || value[i] === 'at')) {\n\t\t\t\t\tclockwise = value[i] === 'wa' ? 1 : -1; // #1642\n\t\t\t\t\tif (path[i + 5] === path[i + 7]) {\n\t\t\t\t\t\tpath[i + 7] -= clockwise;\n\t\t\t\t\t}\n\t\t\t\t\t// Start and end Y (#1410)\n\t\t\t\t\tif (path[i + 6] === path[i + 8]) {\n\t\t\t\t\t\tpath[i + 8] -= clockwise;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Loop up again to handle path shortcuts (#2132)\n\t\t/*while (i++ < path.length) {\n\t\t\tif (path[i] === 'H') { // horizontal line to\n\t\t\t\tpath[i] = 'L';\n\t\t\t\tpath.splice(i + 2, 0, path[i - 1]);\n\t\t\t} else if (path[i] === 'V') { // vertical line to\n\t\t\t\tpath[i] = 'L';\n\t\t\t\tpath.splice(i + 1, 0, path[i - 2]);\n\t\t\t}\n\t\t}*/\n\t\treturn path.join(' ') || 'x';\n\t},\n\n\t/**\n\t * Get or set attributes\n\t */\n\tattr: function (hash, val) {\n\t\tvar wrapper = this,\n\t\t\tkey,\n\t\t\tvalue,\n\t\t\ti,\n\t\t\tresult,\n\t\t\telement = wrapper.element || {},\n\t\t\telemStyle = element.style,\n\t\t\tnodeName = element.nodeName,\n\t\t\trenderer = wrapper.renderer,\n\t\t\tsymbolName = wrapper.symbolName,\n\t\t\thasSetSymbolSize,\n\t\t\tshadows = wrapper.shadows,\n\t\t\tskipAttr,\n\t\t\tattrSetters = wrapper.attrSetters,\n\t\t\tret = wrapper;\n\n\t\t// single key-value pair\n\t\tif (isString(hash) && defined(val)) {\n\t\t\tkey = hash;\n\t\t\thash = {};\n\t\t\thash[key] = val;\n\t\t}\n\n\t\t// used as a getter, val is undefined\n\t\tif (isString(hash)) {\n\t\t\tkey = hash;\n\t\t\tif (key === 'strokeWidth' || key === 'stroke-width') {\n\t\t\t\tret = wrapper.strokeweight;\n\t\t\t} else {\n\t\t\t\tret = wrapper[key];\n\t\t\t}\n\n\t\t// setter\n\t\t} else {\n\t\t\tfor (key in hash) {\n\t\t\t\tvalue = hash[key];\n\t\t\t\tskipAttr = false;\n\n\t\t\t\t// check for a specific attribute setter\n\t\t\t\tresult = attrSetters[key] && attrSetters[key].call(wrapper, value, key);\n\n\t\t\t\tif (result !== false && value !== null) { // #620\n\n\t\t\t\t\tif (result !== UNDEFINED) {\n\t\t\t\t\t\tvalue = result; // the attribute setter has returned a new value to set\n\t\t\t\t\t}\n\n\n\t\t\t\t\t// prepare paths\n\t\t\t\t\t// symbols\n\t\t\t\t\tif (symbolName && /^(x|y|r|start|end|width|height|innerR|anchorX|anchorY)/.test(key)) {\n\t\t\t\t\t\t// if one of the symbol size affecting parameters are changed,\n\t\t\t\t\t\t// check all the others only once for each call to an element's\n\t\t\t\t\t\t// .attr() method\n\t\t\t\t\t\tif (!hasSetSymbolSize) {\n\t\t\t\t\t\t\twrapper.symbolAttr(hash);\n\n\t\t\t\t\t\t\thasSetSymbolSize = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tskipAttr = true;\n\n\t\t\t\t\t} else if (key === 'd') {\n\t\t\t\t\t\tvalue = value || [];\n\t\t\t\t\t\twrapper.d = value.join(' '); // used in getter for animation\n\n\t\t\t\t\t\telement.path = value = wrapper.pathToVML(value);\n\n\t\t\t\t\t\t// update shadows\n\t\t\t\t\t\tif (shadows) {\n\t\t\t\t\t\t\ti = shadows.length;\n\t\t\t\t\t\t\twhile (i--) {\n\t\t\t\t\t\t\t\tshadows[i].path = shadows[i].cutOff ? this.cutOffPath(value, shadows[i].cutOff) : value;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tskipAttr = true;\n\n\t\t\t\t\t// handle visibility\n\t\t\t\t\t} else if (key === 'visibility') {\n\n\t\t\t\t\t\t// let the shadow follow the main element\n\t\t\t\t\t\tif (shadows) {\n\t\t\t\t\t\t\ti = shadows.length;\n\t\t\t\t\t\t\twhile (i--) {\n\t\t\t\t\t\t\t\tshadows[i].style[key] = value;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Instead of toggling the visibility CSS property, move the div out of the viewport.\n\t\t\t\t\t\t// This works around #61 and #586\n\t\t\t\t\t\tif (nodeName === 'DIV') {\n\t\t\t\t\t\t\tvalue = value === HIDDEN ? '-999em' : 0;\n\n\t\t\t\t\t\t\t// In order to redraw, IE7 needs the div to be visible when tucked away\n\t\t\t\t\t\t\t// outside the viewport. So the visibility is actually opposite of\n\t\t\t\t\t\t\t// the expected value. This applies to the tooltip only.\n\t\t\t\t\t\t\tif (!docMode8) {\n\t\t\t\t\t\t\t\telemStyle[key] = value ? VISIBLE : HIDDEN;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tkey = 'top';\n\t\t\t\t\t\t}\n\t\t\t\t\t\telemStyle[key] = value;\n\t\t\t\t\t\tskipAttr = true;\n\n\t\t\t\t\t// directly mapped to css\n\t\t\t\t\t} else if (key === 'zIndex') {\n\n\t\t\t\t\t\tif (value) {\n\t\t\t\t\t\t\telemStyle[key] = value;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tskipAttr = true;\n\n\t\t\t\t\t// x, y, width, height\n\t\t\t\t\t} else if (inArray(key, ['x', 'y', 'width', 'height']) !== -1) {\n\n\t\t\t\t\t\twrapper[key] = value; // used in getter\n\n\t\t\t\t\t\tif (key === 'x' || key === 'y') {\n\t\t\t\t\t\t\tkey = { x: 'left', y: 'top' }[key];\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tvalue = mathMax(0, value); // don't set width or height below zero (#311)\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// clipping rectangle special\n\t\t\t\t\t\tif (wrapper.updateClipping) {\n\t\t\t\t\t\t\twrapper[key] = value; // the key is now 'left' or 'top' for 'x' and 'y'\n\t\t\t\t\t\t\twrapper.updateClipping();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// normal\n\t\t\t\t\t\t\telemStyle[key] = value;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tskipAttr = true;\n\n\t\t\t\t\t// class name\n\t\t\t\t\t} else if (key === 'class' && nodeName === 'DIV') {\n\t\t\t\t\t\t// IE8 Standards mode has problems retrieving the className\n\t\t\t\t\t\telement.className = value;\n\n\t\t\t\t\t// stroke\n\t\t\t\t\t} else if (key === 'stroke') {\n\n\t\t\t\t\t\tvalue = renderer.color(value, element, key);\n\n\t\t\t\t\t\tkey = 'strokecolor';\n\n\t\t\t\t\t// stroke width\n\t\t\t\t\t} else if (key === 'stroke-width' || key === 'strokeWidth') {\n\t\t\t\t\t\telement.stroked = value ? true : false;\n\t\t\t\t\t\tkey = 'strokeweight';\n\t\t\t\t\t\twrapper[key] = value; // used in getter, issue #113\n\t\t\t\t\t\tif (isNumber(value)) {\n\t\t\t\t\t\t\tvalue += PX;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t// dashStyle\n\t\t\t\t\t} else if (key === 'dashstyle') {\n\t\t\t\t\t\tvar strokeElem = element.getElementsByTagName('stroke')[0] ||\n\t\t\t\t\t\t\tcreateElement(renderer.prepVML(['<stroke/>']), null, null, element);\n\t\t\t\t\t\tstrokeElem[key] = value || 'solid';\n\t\t\t\t\t\twrapper.dashstyle = value; /* because changing stroke-width will change the dash length\n\t\t\t\t\t\t\tand cause an epileptic effect */\n\t\t\t\t\t\tskipAttr = true;\n\n\t\t\t\t\t// fill\n\t\t\t\t\t} else if (key === 'fill') {\n\n\t\t\t\t\t\tif (nodeName === 'SPAN') { // text color\n\t\t\t\t\t\t\telemStyle.color = value;\n\t\t\t\t\t\t} else if (nodeName !== 'IMG') { // #1336\n\t\t\t\t\t\t\telement.filled = value !== NONE ? true : false;\n\n\t\t\t\t\t\t\tvalue = renderer.color(value, element, key, wrapper);\n\n\t\t\t\t\t\t\tkey = 'fillcolor';\n\t\t\t\t\t\t}\n\n\t\t\t\t\t// opacity: don't bother - animation is too slow and filters introduce artifacts\n\t\t\t\t\t} else if (key === 'opacity') {\n\t\t\t\t\t\t/*css(element, {\n\t\t\t\t\t\t\topacity: value\n\t\t\t\t\t\t});*/\n\t\t\t\t\t\tskipAttr = true;\n\n\t\t\t\t\t// rotation on VML elements\n\t\t\t\t\t} else if (nodeName === 'shape' && key === 'rotation') {\n\n\t\t\t\t\t\twrapper[key] = element.style[key] = value; // style is for #1873\n\n\t\t\t\t\t\t// Correction for the 1x1 size of the shape container. Used in gauge needles.\n\t\t\t\t\t\telement.style.left = -mathRound(mathSin(value * deg2rad) + 1) + PX;\n\t\t\t\t\t\telement.style.top = mathRound(mathCos(value * deg2rad)) + PX;\n\n\t\t\t\t\t// translation for animation\n\t\t\t\t\t} else if (key === 'translateX' || key === 'translateY' || key === 'rotation') {\n\t\t\t\t\t\twrapper[key] = value;\n\t\t\t\t\t\twrapper.updateTransform();\n\n\t\t\t\t\t\tskipAttr = true;\n\n\t\t\t\t\t// text for rotated and non-rotated elements\n\t\t\t\t\t} else if (key === 'text') {\n\t\t\t\t\t\tthis.bBox = null;\n\t\t\t\t\t\telement.innerHTML = value;\n\t\t\t\t\t\tskipAttr = true;\n\t\t\t\t\t}\n\n\n\t\t\t\t\tif (!skipAttr) {\n\t\t\t\t\t\tif (docMode8) { // IE8 setAttribute bug\n\t\t\t\t\t\t\telement[key] = value;\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tattr(element, key, value);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn ret;\n\t},\n\n\t/**\n\t * Set the element's clipping to a predefined rectangle\n\t *\n\t * @param {String} id The id of the clip rectangle\n\t */\n\tclip: function (clipRect) {\n\t\tvar wrapper = this,\n\t\t\tclipMembers,\n\t\t\tcssRet;\n\n\t\tif (clipRect) {\n\t\t\tclipMembers = clipRect.members;\n\t\t\terase(clipMembers, wrapper); // Ensure unique list of elements (#1258)\n\t\t\tclipMembers.push(wrapper);\n\t\t\twrapper.destroyClip = function () {\n\t\t\t\terase(clipMembers, wrapper);\n\t\t\t};\n\t\t\tcssRet = clipRect.getCSS(wrapper);\n\n\t\t} else {\n\t\t\tif (wrapper.destroyClip) {\n\t\t\t\twrapper.destroyClip();\n\t\t\t}\n\t\t\tcssRet = { clip: docMode8 ? 'inherit' : 'rect(auto)' }; // #1214\n\t\t}\n\n\t\treturn wrapper.css(cssRet);\n\n\t},\n\n\t/**\n\t * Set styles for the element\n\t * @param {Object} styles\n\t */\n\tcss: SVGElement.prototype.htmlCss,\n\n\t/**\n\t * Removes a child either by removeChild or move to garbageBin.\n\t * Issue 490; in VML removeChild results in Orphaned nodes according to sIEve, discardElement does not.\n\t */\n\tsafeRemoveChild: function (element) {\n\t\t// discardElement will detach the node from its parent before attaching it\n\t\t// to the garbage bin. Therefore it is important that the node is attached and have parent.\n\t\tif (element.parentNode) {\n\t\t\tdiscardElement(element);\n\t\t}\n\t},\n\n\t/**\n\t * Extend element.destroy by removing it from the clip members array\n\t */\n\tdestroy: function () {\n\t\tif (this.destroyClip) {\n\t\t\tthis.destroyClip();\n\t\t}\n\n\t\treturn SVGElement.prototype.destroy.apply(this);\n\t},\n\n\t/**\n\t * Add an event listener. VML override for normalizing event parameters.\n\t * @param {String} eventType\n\t * @param {Function} handler\n\t */\n\ton: function (eventType, handler) {\n\t\t// simplest possible event model for internal use\n\t\tthis.element['on' + eventType] = function () {\n\t\t\tvar evt = win.event;\n\t\t\tevt.target = evt.srcElement;\n\t\t\thandler(evt);\n\t\t};\n\t\treturn this;\n\t},\n\n\t/**\n\t * In stacked columns, cut off the shadows so that they don't overlap\n\t */\n\tcutOffPath: function (path, length) {\n\n\t\tvar len;\n\n\t\tpath = path.split(/[ ,]/);\n\t\tlen = path.length;\n\n\t\tif (len === 9 || len === 11) {\n\t\t\tpath[len - 4] = path[len - 2] = pInt(path[len - 2]) - 10 * length;\n\t\t}\n\t\treturn path.join(' ');\n\t},\n\n\t/**\n\t * Apply a drop shadow by copying elements and giving them different strokes\n\t * @param {Boolean|Object} shadowOptions\n\t */\n\tshadow: function (shadowOptions, group, cutOff) {\n\t\tvar shadows = [],\n\t\t\ti,\n\t\t\telement = this.element,\n\t\t\trenderer = this.renderer,\n\t\t\tshadow,\n\t\t\telemStyle = element.style,\n\t\t\tmarkup,\n\t\t\tpath = element.path,\n\t\t\tstrokeWidth,\n\t\t\tmodifiedPath,\n\t\t\tshadowWidth,\n\t\t\tshadowElementOpacity;\n\n\t\t// some times empty paths are not strings\n\t\tif (path && typeof path.value !== 'string') {\n\t\t\tpath = 'x';\n\t\t}\n\t\tmodifiedPath = path;\n\n\t\tif (shadowOptions) {\n\t\t\tshadowWidth = pick(shadowOptions.width, 3);\n\t\t\tshadowElementOpacity = (shadowOptions.opacity || 0.15) / shadowWidth;\n\t\t\tfor (i = 1; i <= 3; i++) {\n\n\t\t\t\tstrokeWidth = (shadowWidth * 2) + 1 - (2 * i);\n\n\t\t\t\t// Cut off shadows for stacked column items\n\t\t\t\tif (cutOff) {\n\t\t\t\t\tmodifiedPath = this.cutOffPath(path.value, strokeWidth + 0.5);\n\t\t\t\t}\n\n\t\t\t\tmarkup = ['<shape isShadow=\"true\" strokeweight=\"', strokeWidth,\n\t\t\t\t\t'\" filled=\"false\" path=\"', modifiedPath,\n\t\t\t\t\t'\" coordsize=\"10 10\" style=\"', element.style.cssText, '\" />'];\n\n\t\t\t\tshadow = createElement(renderer.prepVML(markup),\n\t\t\t\t\tnull, {\n\t\t\t\t\t\tleft: pInt(elemStyle.left) + pick(shadowOptions.offsetX, 1),\n\t\t\t\t\t\ttop: pInt(elemStyle.top) + pick(shadowOptions.offsetY, 1)\n\t\t\t\t\t}\n\t\t\t\t);\n\t\t\t\tif (cutOff) {\n\t\t\t\t\tshadow.cutOff = strokeWidth + 1;\n\t\t\t\t}\n\n\t\t\t\t// apply the opacity\n\t\t\t\tmarkup = ['<stroke color=\"', shadowOptions.color || 'black', '\" opacity=\"', shadowElementOpacity * i, '\"/>'];\n\t\t\t\tcreateElement(renderer.prepVML(markup), null, null, shadow);\n\n\n\t\t\t\t// insert it\n\t\t\t\tif (group) {\n\t\t\t\t\tgroup.element.appendChild(shadow);\n\t\t\t\t} else {\n\t\t\t\t\telement.parentNode.insertBefore(shadow, element);\n\t\t\t\t}\n\n\t\t\t\t// record it\n\t\t\t\tshadows.push(shadow);\n\n\t\t\t}\n\n\t\t\tthis.shadows = shadows;\n\t\t}\n\t\treturn this;\n\n\t}\n};\nVMLElement = extendClass(SVGElement, VMLElement);\n\n/**\n * The VML renderer\n */\nvar VMLRendererExtension = { // inherit SVGRenderer\n\n\tElement: VMLElement,\n\tisIE8: userAgent.indexOf('MSIE 8.0') > -1,\n\n\n\t/**\n\t * Initialize the VMLRenderer\n\t * @param {Object} container\n\t * @param {Number} width\n\t * @param {Number} height\n\t */\n\tinit: function (container, width, height) {\n\t\tvar renderer = this,\n\t\t\tboxWrapper,\n\t\t\tbox;\n\n\t\trenderer.alignedObjects = [];\n\n\t\tboxWrapper = renderer.createElement(DIV);\n\t\tbox = boxWrapper.element;\n\t\tbox.style.position = RELATIVE; // for freeform drawing using renderer directly\n\t\tcontainer.appendChild(boxWrapper.element);\n\n\n\t\t// generate the containing box\n\t\trenderer.isVML = true;\n\t\trenderer.box = box;\n\t\trenderer.boxWrapper = boxWrapper;\n\n\n\t\trenderer.setSize(width, height, false);\n\n\t\t// The only way to make IE6 and IE7 print is to use a global namespace. However,\n\t\t// with IE8 the only way to make the dynamic shapes visible in screen and print mode\n\t\t// seems to be to add the xmlns attribute and the behaviour style inline.\n\t\tif (!doc.namespaces.hcv) {\n\n\t\t\tdoc.namespaces.add('hcv', 'urn:schemas-microsoft-com:vml');\n\n\t\t\t// Setup default CSS (#2153)\n\t\t\t(doc.styleSheets.length ? doc.styleSheets[0] : doc.createStyleSheet()).cssText +=\n\t\t\t\t'hcv\\\\:fill, hcv\\\\:path, hcv\\\\:shape, hcv\\\\:stroke' +\n\t\t\t\t'{ behavior:url(#default#VML); display: inline-block; } ';\n\n\t\t}\n\t},\n\n\n\t/**\n\t * Detect whether the renderer is hidden. This happens when one of the parent elements\n\t * has display: none\n\t */\n\tisHidden: function () {\n\t\treturn !this.box.offsetWidth;\n\t},\n\n\t/**\n\t * Define a clipping rectangle. In VML it is accomplished by storing the values\n\t * for setting the CSS style to all associated members.\n\t *\n\t * @param {Number} x\n\t * @param {Number} y\n\t * @param {Number} width\n\t * @param {Number} height\n\t */\n\tclipRect: function (x, y, width, height) {\n\n\t\t// create a dummy element\n\t\tvar clipRect = this.createElement(),\n\t\t\tisObj = isObject(x);\n\n\t\t// mimic a rectangle with its style object for automatic updating in attr\n\t\treturn extend(clipRect, {\n\t\t\tmembers: [],\n\t\t\tleft: (isObj ? x.x : x) + 1,\n\t\t\ttop: (isObj ? x.y : y) + 1,\n\t\t\twidth: (isObj ? x.width : width) - 1,\n\t\t\theight: (isObj ? x.height : height) - 1,\n\t\t\tgetCSS: function (wrapper) {\n\t\t\t\tvar element = wrapper.element,\n\t\t\t\t\tnodeName = element.nodeName,\n\t\t\t\t\tisShape = nodeName === 'shape',\n\t\t\t\t\tinverted = wrapper.inverted,\n\t\t\t\t\trect = this,\n\t\t\t\t\ttop = rect.top - (isShape ? element.offsetTop : 0),\n\t\t\t\t\tleft = rect.left,\n\t\t\t\t\tright = left + rect.width,\n\t\t\t\t\tbottom = top + rect.height,\n\t\t\t\t\tret = {\n\t\t\t\t\t\tclip: 'rect(' +\n\t\t\t\t\t\t\tmathRound(inverted ? left : top) + 'px,' +\n\t\t\t\t\t\t\tmathRound(inverted ? bottom : right) + 'px,' +\n\t\t\t\t\t\t\tmathRound(inverted ? right : bottom) + 'px,' +\n\t\t\t\t\t\t\tmathRound(inverted ? top : left) + 'px)'\n\t\t\t\t\t};\n\n\t\t\t\t// issue 74 workaround\n\t\t\t\tif (!inverted && docMode8 && nodeName === 'DIV') {\n\t\t\t\t\textend(ret, {\n\t\t\t\t\t\twidth: right + PX,\n\t\t\t\t\t\theight: bottom + PX\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn ret;\n\t\t\t},\n\n\t\t\t// used in attr and animation to update the clipping of all members\n\t\t\tupdateClipping: function () {\n\t\t\t\teach(clipRect.members, function (member) {\n\t\t\t\t\tmember.css(clipRect.getCSS(member));\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\n\t},\n\n\n\t/**\n\t * Take a color and return it if it's a string, make it a gradient if it's a\n\t * gradient configuration object, and apply opacity.\n\t *\n\t * @param {Object} color The color or config object\n\t */\n\tcolor: function (color, elem, prop, wrapper) {\n\t\tvar renderer = this,\n\t\t\tcolorObject,\n\t\t\tregexRgba = /^rgba/,\n\t\t\tmarkup,\n\t\t\tfillType,\n\t\t\tret = NONE;\n\n\t\t// Check for linear or radial gradient\n\t\tif (color && color.linearGradient) {\n\t\t\tfillType = 'gradient';\n\t\t} else if (color && color.radialGradient) {\n\t\t\tfillType = 'pattern';\n\t\t}\n\n\n\t\tif (fillType) {\n\n\t\t\tvar stopColor,\n\t\t\t\tstopOpacity,\n\t\t\t\tgradient = color.linearGradient || color.radialGradient,\n\t\t\t\tx1,\n\t\t\t\ty1,\n\t\t\t\tx2,\n\t\t\t\ty2,\n\t\t\t\topacity1,\n\t\t\t\topacity2,\n\t\t\t\tcolor1,\n\t\t\t\tcolor2,\n\t\t\t\tfillAttr = '',\n\t\t\t\tstops = color.stops,\n\t\t\t\tfirstStop,\n\t\t\t\tlastStop,\n\t\t\t\tcolors = [],\n\t\t\t\taddFillNode = function () {\n\t\t\t\t\t// Add the fill subnode. When colors attribute is used, the meanings of opacity and o:opacity2\n\t\t\t\t\t// are reversed.\n\t\t\t\t\tmarkup = ['<fill colors=\"' + colors.join(',') + '\" opacity=\"', opacity2, '\" o:opacity2=\"', opacity1,\n\t\t\t\t\t\t'\" type=\"', fillType, '\" ', fillAttr, 'focus=\"100%\" method=\"any\" />'];\n\t\t\t\t\tcreateElement(renderer.prepVML(markup), null, null, elem);\n\t\t\t\t};\n\n\t\t\t// Extend from 0 to 1\n\t\t\tfirstStop = stops[0];\n\t\t\tlastStop = stops[stops.length - 1];\n\t\t\tif (firstStop[0] > 0) {\n\t\t\t\tstops.unshift([\n\t\t\t\t\t0,\n\t\t\t\t\tfirstStop[1]\n\t\t\t\t]);\n\t\t\t}\n\t\t\tif (lastStop[0] < 1) {\n\t\t\t\tstops.push([\n\t\t\t\t\t1,\n\t\t\t\t\tlastStop[1]\n\t\t\t\t]);\n\t\t\t}\n\n\t\t\t// Compute the stops\n\t\t\teach(stops, function (stop, i) {\n\t\t\t\tif (regexRgba.test(stop[1])) {\n\t\t\t\t\tcolorObject = Color(stop[1]);\n\t\t\t\t\tstopColor = colorObject.get('rgb');\n\t\t\t\t\tstopOpacity = colorObject.get('a');\n\t\t\t\t} else {\n\t\t\t\t\tstopColor = stop[1];\n\t\t\t\t\tstopOpacity = 1;\n\t\t\t\t}\n\n\t\t\t\t// Build the color attribute\n\t\t\t\tcolors.push((stop[0] * 100) + '% ' + stopColor);\n\n\t\t\t\t// Only start and end opacities are allowed, so we use the first and the last\n\t\t\t\tif (!i) {\n\t\t\t\t\topacity1 = stopOpacity;\n\t\t\t\t\tcolor2 = stopColor;\n\t\t\t\t} else {\n\t\t\t\t\topacity2 = stopOpacity;\n\t\t\t\t\tcolor1 = stopColor;\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// Apply the gradient to fills only.\n\t\t\tif (prop === 'fill') {\n\n\t\t\t\t// Handle linear gradient angle\n\t\t\t\tif (fillType === 'gradient') {\n\t\t\t\t\tx1 = gradient.x1 || gradient[0] || 0;\n\t\t\t\t\ty1 = gradient.y1 || gradient[1] || 0;\n\t\t\t\t\tx2 = gradient.x2 || gradient[2] || 0;\n\t\t\t\t\ty2 = gradient.y2 || gradient[3] || 0;\n\t\t\t\t\tfillAttr = 'angle=\"' + (90  - math.atan(\n\t\t\t\t\t\t(y2 - y1) / // y vector\n\t\t\t\t\t\t(x2 - x1) // x vector\n\t\t\t\t\t\t) * 180 / mathPI) + '\"';\n\n\t\t\t\t\taddFillNode();\n\n\t\t\t\t// Radial (circular) gradient\n\t\t\t\t} else {\n\n\t\t\t\t\tvar r = gradient.r,\n\t\t\t\t\t\tsizex = r * 2,\n\t\t\t\t\t\tsizey = r * 2,\n\t\t\t\t\t\tcx = gradient.cx,\n\t\t\t\t\t\tcy = gradient.cy,\n\t\t\t\t\t\tradialReference = elem.radialReference,\n\t\t\t\t\t\tbBox,\n\t\t\t\t\t\tapplyRadialGradient = function () {\n\t\t\t\t\t\t\tif (radialReference) {\n\t\t\t\t\t\t\t\tbBox = wrapper.getBBox();\n\t\t\t\t\t\t\t\tcx += (radialReference[0] - bBox.x) / bBox.width - 0.5;\n\t\t\t\t\t\t\t\tcy += (radialReference[1] - bBox.y) / bBox.height - 0.5;\n\t\t\t\t\t\t\t\tsizex *= radialReference[2] / bBox.width;\n\t\t\t\t\t\t\t\tsizey *= radialReference[2] / bBox.height;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tfillAttr = 'src=\"' + defaultOptions.global.VMLRadialGradientURL + '\" ' +\n\t\t\t\t\t\t\t\t'size=\"' + sizex + ',' + sizey + '\" ' +\n\t\t\t\t\t\t\t\t'origin=\"0.5,0.5\" ' +\n\t\t\t\t\t\t\t\t'position=\"' + cx + ',' + cy + '\" ' +\n\t\t\t\t\t\t\t\t'color2=\"' + color2 + '\" ';\n\n\t\t\t\t\t\t\taddFillNode();\n\t\t\t\t\t\t};\n\n\t\t\t\t\t// Apply radial gradient\n\t\t\t\t\tif (wrapper.added) {\n\t\t\t\t\t\tapplyRadialGradient();\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// We need to know the bounding box to get the size and position right\n\t\t\t\t\t\taddEvent(wrapper, 'add', applyRadialGradient);\n\t\t\t\t\t}\n\n\t\t\t\t\t// The fill element's color attribute is broken in IE8 standards mode, so we\n\t\t\t\t\t// need to set the parent shape's fillcolor attribute instead.\n\t\t\t\t\tret = color1;\n\t\t\t\t}\n\n\t\t\t// Gradients are not supported for VML stroke, return the first color. #722.\n\t\t\t} else {\n\t\t\t\tret = stopColor;\n\t\t\t}\n\n\t\t// if the color is an rgba color, split it and add a fill node\n\t\t// to hold the opacity component\n\t\t} else if (regexRgba.test(color) && elem.tagName !== 'IMG') {\n\n\t\t\tcolorObject = Color(color);\n\n\t\t\tmarkup = ['<', prop, ' opacity=\"', colorObject.get('a'), '\"/>'];\n\t\t\tcreateElement(this.prepVML(markup), null, null, elem);\n\n\t\t\tret = colorObject.get('rgb');\n\n\n\t\t} else {\n\t\t\tvar propNodes = elem.getElementsByTagName(prop); // 'stroke' or 'fill' node\n\t\t\tif (propNodes.length) {\n\t\t\t\tpropNodes[0].opacity = 1;\n\t\t\t\tpropNodes[0].type = 'solid';\n\t\t\t}\n\t\t\tret = color;\n\t\t}\n\n\t\treturn ret;\n\t},\n\n\t/**\n\t * Take a VML string and prepare it for either IE8 or IE6/IE7.\n\t * @param {Array} markup A string array of the VML markup to prepare\n\t */\n\tprepVML: function (markup) {\n\t\tvar vmlStyle = 'display:inline-block;behavior:url(#default#VML);',\n\t\t\tisIE8 = this.isIE8;\n\n\t\tmarkup = markup.join('');\n\n\t\tif (isIE8) { // add xmlns and style inline\n\t\t\tmarkup = markup.replace('/>', ' xmlns=\"urn:schemas-microsoft-com:vml\" />');\n\t\t\tif (markup.indexOf('style=\"') === -1) {\n\t\t\t\tmarkup = markup.replace('/>', ' style=\"' + vmlStyle + '\" />');\n\t\t\t} else {\n\t\t\t\tmarkup = markup.replace('style=\"', 'style=\"' + vmlStyle);\n\t\t\t}\n\n\t\t} else { // add namespace\n\t\t\tmarkup = markup.replace('<', '<hcv:');\n\t\t}\n\n\t\treturn markup;\n\t},\n\n\t/**\n\t * Create rotated and aligned text\n\t * @param {String} str\n\t * @param {Number} x\n\t * @param {Number} y\n\t */\n\ttext: SVGRenderer.prototype.html,\n\n\t/**\n\t * Create and return a path element\n\t * @param {Array} path\n\t */\n\tpath: function (path) {\n\t\tvar attr = {\n\t\t\t// subpixel precision down to 0.1 (width and height = 1px)\n\t\t\tcoordsize: '10 10'\n\t\t};\n\t\tif (isArray(path)) {\n\t\t\tattr.d = path;\n\t\t} else if (isObject(path)) { // attributes\n\t\t\textend(attr, path);\n\t\t}\n\t\t// create the shape\n\t\treturn this.createElement('shape').attr(attr);\n\t},\n\n\t/**\n\t * Create and return a circle element. In VML circles are implemented as\n\t * shapes, which is faster than v:oval\n\t * @param {Number} x\n\t * @param {Number} y\n\t * @param {Number} r\n\t */\n\tcircle: function (x, y, r) {\n\t\tvar circle = this.symbol('circle');\n\t\tif (isObject(x)) {\n\t\t\tr = x.r;\n\t\t\ty = x.y;\n\t\t\tx = x.x;\n\t\t}\n\t\tcircle.isCircle = true; // Causes x and y to mean center (#1682)\n\t\tcircle.r = r;\n\t\treturn circle.attr({ x: x, y: y });\n\t},\n\n\t/**\n\t * Create a group using an outer div and an inner v:group to allow rotating\n\t * and flipping. A simple v:group would have problems with positioning\n\t * child HTML elements and CSS clip.\n\t *\n\t * @param {String} name The name of the group\n\t */\n\tg: function (name) {\n\t\tvar wrapper,\n\t\t\tattribs;\n\n\t\t// set the class name\n\t\tif (name) {\n\t\t\tattribs = { 'className': PREFIX + name, 'class': PREFIX + name };\n\t\t}\n\n\t\t// the div to hold HTML and clipping\n\t\twrapper = this.createElement(DIV).attr(attribs);\n\n\t\treturn wrapper;\n\t},\n\n\t/**\n\t * VML override to create a regular HTML image\n\t * @param {String} src\n\t * @param {Number} x\n\t * @param {Number} y\n\t * @param {Number} width\n\t * @param {Number} height\n\t */\n\timage: function (src, x, y, width, height) {\n\t\tvar obj = this.createElement('img')\n\t\t\t.attr({ src: src });\n\n\t\tif (arguments.length > 1) {\n\t\t\tobj.attr({\n\t\t\t\tx: x,\n\t\t\t\ty: y,\n\t\t\t\twidth: width,\n\t\t\t\theight: height\n\t\t\t});\n\t\t}\n\t\treturn obj;\n\t},\n\n\t/**\n\t * VML uses a shape for rect to overcome bugs and rotation problems\n\t */\n\trect: function (x, y, width, height, r, strokeWidth) {\n\n\t\tvar wrapper = this.symbol('rect');\n\t\twrapper.r = isObject(x) ? x.r : r;\n\n\t\t//return wrapper.attr(wrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0)));\n\t\treturn wrapper.attr(\n\t\t\t\tisObject(x) ?\n\t\t\t\t\tx :\n\t\t\t\t\t// do not crispify when an object is passed in (as in column charts)\n\t\t\t\t\twrapper.crisp(strokeWidth, x, y, mathMax(width, 0), mathMax(height, 0))\n\t\t\t);\n\t},\n\n\t/**\n\t * In the VML renderer, each child of an inverted div (group) is inverted\n\t * @param {Object} element\n\t * @param {Object} parentNode\n\t */\n\tinvertChild: function (element, parentNode) {\n\t\tvar parentStyle = parentNode.style;\n\t\tcss(element, {\n\t\t\tflip: 'x',\n\t\t\tleft: pInt(parentStyle.width) - 1,\n\t\t\ttop: pInt(parentStyle.height) - 1,\n\t\t\trotation: -90\n\t\t});\n\t},\n\n\t/**\n\t * Symbol definitions that override the parent SVG renderer's symbols\n\t *\n\t */\n\tsymbols: {\n\t\t// VML specific arc function\n\t\tarc: function (x, y, w, h, options) {\n\t\t\tvar start = options.start,\n\t\t\t\tend = options.end,\n\t\t\t\tradius = options.r || w || h,\n\t\t\t\tinnerRadius = options.innerR,\n\t\t\t\tcosStart = mathCos(start),\n\t\t\t\tsinStart = mathSin(start),\n\t\t\t\tcosEnd = mathCos(end),\n\t\t\t\tsinEnd = mathSin(end),\n\t\t\t\tret;\n\n\t\t\tif (end - start === 0) { // no angle, don't show it.\n\t\t\t\treturn ['x'];\n\t\t\t}\n\n\t\t\tret = [\n\t\t\t\t'wa', // clockwise arc to\n\t\t\t\tx - radius, // left\n\t\t\t\ty - radius, // top\n\t\t\t\tx + radius, // right\n\t\t\t\ty + radius, // bottom\n\t\t\t\tx + radius * cosStart, // start x\n\t\t\t\ty + radius * sinStart, // start y\n\t\t\t\tx + radius * cosEnd, // end x\n\t\t\t\ty + radius * sinEnd  // end y\n\t\t\t];\n\n\t\t\tif (options.open && !innerRadius) {\n\t\t\t\tret.push(\n\t\t\t\t\t'e',\n\t\t\t\t\tM,\n\t\t\t\t\tx,// - innerRadius,\n\t\t\t\t\ty// - innerRadius\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tret.push(\n\t\t\t\t'at', // anti clockwise arc to\n\t\t\t\tx - innerRadius, // left\n\t\t\t\ty - innerRadius, // top\n\t\t\t\tx + innerRadius, // right\n\t\t\t\ty + innerRadius, // bottom\n\t\t\t\tx + innerRadius * cosEnd, // start x\n\t\t\t\ty + innerRadius * sinEnd, // start y\n\t\t\t\tx + innerRadius * cosStart, // end x\n\t\t\t\ty + innerRadius * sinStart, // end y\n\t\t\t\t'x', // finish path\n\t\t\t\t'e' // close\n\t\t\t);\n\n\t\t\tret.isArc = true;\n\t\t\treturn ret;\n\n\t\t},\n\t\t// Add circle symbol path. This performs significantly faster than v:oval.\n\t\tcircle: function (x, y, w, h, wrapper) {\n\n\t\t\tif (wrapper) {\n\t\t\t\tw = h = 2 * wrapper.r;\n\t\t\t}\n\n\t\t\t// Center correction, #1682\n\t\t\tif (wrapper && wrapper.isCircle) {\n\t\t\t\tx -= w / 2;\n\t\t\t\ty -= h / 2;\n\t\t\t}\n\n\t\t\t// Return the path\n\t\t\treturn [\n\t\t\t\t'wa', // clockwisearcto\n\t\t\t\tx, // left\n\t\t\t\ty, // top\n\t\t\t\tx + w, // right\n\t\t\t\ty + h, // bottom\n\t\t\t\tx + w, // start x\n\t\t\t\ty + h / 2,     // start y\n\t\t\t\tx + w, // end x\n\t\t\t\ty + h / 2,     // end y\n\t\t\t\t//'x', // finish path\n\t\t\t\t'e' // close\n\t\t\t];\n\t\t},\n\t\t/**\n\t\t * Add rectangle symbol path which eases rotation and omits arcsize problems\n\t\t * compared to the built-in VML roundrect shape\n\t\t *\n\t\t * @param {Number} left Left position\n\t\t * @param {Number} top Top position\n\t\t * @param {Number} r Border radius\n\t\t * @param {Object} options Width and height\n\t\t */\n\n\t\trect: function (left, top, width, height, options) {\n\n\t\t\tvar right = left + width,\n\t\t\t\tbottom = top + height,\n\t\t\t\tret,\n\t\t\t\tr;\n\n\t\t\t// No radius, return the more lightweight square\n\t\t\tif (!defined(options) || !options.r) {\n\t\t\t\tret = SVGRenderer.prototype.symbols.square.apply(0, arguments);\n\n\t\t\t// Has radius add arcs for the corners\n\t\t\t} else {\n\n\t\t\t\tr = mathMin(options.r, width, height);\n\t\t\t\tret = [\n\t\t\t\t\tM,\n\t\t\t\t\tleft + r, top,\n\n\t\t\t\t\tL,\n\t\t\t\t\tright - r, top,\n\t\t\t\t\t'wa',\n\t\t\t\t\tright - 2 * r, top,\n\t\t\t\t\tright, top + 2 * r,\n\t\t\t\t\tright - r, top,\n\t\t\t\t\tright, top + r,\n\n\t\t\t\t\tL,\n\t\t\t\t\tright, bottom - r,\n\t\t\t\t\t'wa',\n\t\t\t\t\tright - 2 * r, bottom - 2 * r,\n\t\t\t\t\tright, bottom,\n\t\t\t\t\tright, bottom - r,\n\t\t\t\t\tright - r, bottom,\n\n\t\t\t\t\tL,\n\t\t\t\t\tleft + r, bottom,\n\t\t\t\t\t'wa',\n\t\t\t\t\tleft, bottom - 2 * r,\n\t\t\t\t\tleft + 2 * r, bottom,\n\t\t\t\t\tleft + r, bottom,\n\t\t\t\t\tleft, bottom - r,\n\n\t\t\t\t\tL,\n\t\t\t\t\tleft, top + r,\n\t\t\t\t\t'wa',\n\t\t\t\t\tleft, top,\n\t\t\t\t\tleft + 2 * r, top + 2 * r,\n\t\t\t\t\tleft, top + r,\n\t\t\t\t\tleft + r, top,\n\n\n\t\t\t\t\t'x',\n\t\t\t\t\t'e'\n\t\t\t\t];\n\t\t\t}\n\t\t\treturn ret;\n\t\t}\n\t}\n};\nHighcharts.VMLRenderer = VMLRenderer = function () {\n\tthis.init.apply(this, arguments);\n};\nVMLRenderer.prototype = merge(SVGRenderer.prototype, VMLRendererExtension);\n\n\t// general renderer\n\tRenderer = VMLRenderer;\n}\n\n/* ****************************************************************************\n *                                                                            *\n * END OF INTERNET EXPLORER <= 8 SPECIFIC CODE                                *\n *                                                                            *\n *****************************************************************************/\n/* ****************************************************************************\n *                                                                            *\n * START OF ANDROID < 3 SPECIFIC CODE. THIS CAN BE REMOVED IF YOU'RE NOT      *\n * TARGETING THAT SYSTEM.                                                     *\n *                                                                            *\n *****************************************************************************/\nvar CanVGRenderer,\n\tCanVGController;\n\nif (useCanVG) {\n\t/**\n\t * The CanVGRenderer is empty from start to keep the source footprint small.\n\t * When requested, the CanVGController downloads the rest of the source packaged\n\t * together with the canvg library.\n\t */\n\tHighcharts.CanVGRenderer = CanVGRenderer = function () {\n\t\t// Override the global SVG namespace to fake SVG/HTML that accepts CSS\n\t\tSVG_NS = 'http://www.w3.org/1999/xhtml';\n\t};\n\n\t/**\n\t * Start with an empty symbols object. This is needed when exporting is used (exporting.src.js will add a few symbols), but \n\t * the implementation from SvgRenderer will not be merged in until first render.\n\t */\n\tCanVGRenderer.prototype.symbols = {};\n\n\t/**\n\t * Handles on demand download of canvg rendering support.\n\t */\n\tCanVGController = (function () {\n\t\t// List of renderering calls\n\t\tvar deferredRenderCalls = [];\n\n\t\t/**\n\t\t * When downloaded, we are ready to draw deferred charts.\n\t\t */\n\t\tfunction drawDeferred() {\n\t\t\tvar callLength = deferredRenderCalls.length,\n\t\t\t\tcallIndex;\n\n\t\t\t// Draw all pending render calls\n\t\t\tfor (callIndex = 0; callIndex < callLength; callIndex++) {\n\t\t\t\tdeferredRenderCalls[callIndex]();\n\t\t\t}\n\t\t\t// Clear the list\n\t\t\tdeferredRenderCalls = [];\n\t\t}\n\n\t\treturn {\n\t\t\tpush: function (func, scriptLocation) {\n\t\t\t\t// Only get the script once\n\t\t\t\tif (deferredRenderCalls.length === 0) {\n\t\t\t\t\tgetScript(scriptLocation, drawDeferred);\n\t\t\t\t}\n\t\t\t\t// Register render call\n\t\t\t\tdeferredRenderCalls.push(func);\n\t\t\t}\n\t\t};\n\t}());\n\n\tRenderer = CanVGRenderer;\n} // end CanVGRenderer\n\n/* ****************************************************************************\n *                                                                            *\n * END OF ANDROID < 3 SPECIFIC CODE                                           *\n *                                                                            *\n *****************************************************************************/\n\n/**\n * The Tick class\n */\nfunction Tick(axis, pos, type, noLabel) {\n\tthis.axis = axis;\n\tthis.pos = pos;\n\tthis.type = type || '';\n\tthis.isNew = true;\n\n\tif (!type && !noLabel) {\n\t\tthis.addLabel();\n\t}\n}\n\nTick.prototype = {\n\t/**\n\t * Write the tick label\n\t */\n\taddLabel: function () {\n\t\tvar tick = this,\n\t\t\taxis = tick.axis,\n\t\t\toptions = axis.options,\n\t\t\tchart = axis.chart,\n\t\t\thoriz = axis.horiz,\n\t\t\tcategories = axis.categories,\n\t\t\tnames = axis.series[0] && axis.series[0].names,\n\t\t\tpos = tick.pos,\n\t\t\tlabelOptions = options.labels,\n\t\t\tstr,\n\t\t\ttickPositions = axis.tickPositions,\n\t\t\twidth = (horiz && categories &&\n\t\t\t\t!labelOptions.step && !labelOptions.staggerLines &&\n\t\t\t\t!labelOptions.rotation &&\n\t\t\t\tchart.plotWidth / tickPositions.length) ||\n\t\t\t\t(!horiz && (chart.margin[3] || chart.chartWidth * 0.33)), // #1580, #1931\n\t\t\tisFirst = pos === tickPositions[0],\n\t\t\tisLast = pos === tickPositions[tickPositions.length - 1],\n\t\t\tcss,\n\t\t\tattr,\n\t\t\tvalue = categories ?\n\t\t\t\tpick(categories[pos], names && names[pos], pos) : \n\t\t\t\tpos,\n\t\t\tlabel = tick.label,\n\t\t\ttickPositionInfo = tickPositions.info,\n\t\t\tdateTimeLabelFormat;\n\n\t\t// Set the datetime label format. If a higher rank is set for this position, use that. If not,\n\t\t// use the general format.\n\t\tif (axis.isDatetimeAxis && tickPositionInfo) {\n\t\t\tdateTimeLabelFormat = options.dateTimeLabelFormats[tickPositionInfo.higherRanks[pos] || tickPositionInfo.unitName];\n\t\t}\n\n\t\t// set properties for access in render method\n\t\ttick.isFirst = isFirst;\n\t\ttick.isLast = isLast;\n\n\t\t// get the string\n\t\tstr = axis.labelFormatter.call({\n\t\t\taxis: axis,\n\t\t\tchart: chart,\n\t\t\tisFirst: isFirst,\n\t\t\tisLast: isLast,\n\t\t\tdateTimeLabelFormat: dateTimeLabelFormat,\n\t\t\tvalue: axis.isLog ? correctFloat(lin2log(value)) : value\n\t\t});\n\n\t\t// prepare CSS\n\t\tcss = width && { width: mathMax(1, mathRound(width - 2 * (labelOptions.padding || 10))) + PX };\n\t\tcss = extend(css, labelOptions.style);\n\n\t\t// first call\n\t\tif (!defined(label)) {\n\t\t\tattr = {\n\t\t\t\talign: axis.labelAlign\n\t\t\t};\n\t\t\tif (isNumber(labelOptions.rotation)) {\n\t\t\t\tattr.rotation = labelOptions.rotation;\n\t\t\t}\n\t\t\tif (width && labelOptions.ellipsis) {\n\t\t\t\tattr._clipHeight = axis.len / tickPositions.length;\n\t\t\t}\n\n\t\t\ttick.label =\n\t\t\t\tdefined(str) && labelOptions.enabled ?\n\t\t\t\t\tchart.renderer.text(\n\t\t\t\t\t\t\tstr,\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\tlabelOptions.useHTML\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.attr(attr)\n\t\t\t\t\t\t// without position absolute, IE export sometimes is wrong\n\t\t\t\t\t\t.css(css)\n\t\t\t\t\t\t.add(axis.labelGroup) :\n\t\t\t\t\tnull;\n\n\t\t// update\n\t\t} else if (label) {\n\t\t\tlabel.attr({\n\t\t\t\t\ttext: str\n\t\t\t\t})\n\t\t\t\t.css(css);\n\t\t}\n\t},\n\n\t/**\n\t * Get the offset height or width of the label\n\t */\n\tgetLabelSize: function () {\n\t\tvar label = this.label,\n\t\t\taxis = this.axis;\n\t\treturn label ?\n\t\t\t((this.labelBBox = label.getBBox()))[axis.horiz ? 'height' : 'width'] :\n\t\t\t0;\n\t},\n\n\t/**\n\t * Find how far the labels extend to the right and left of the tick's x position. Used for anti-collision\n\t * detection with overflow logic.\n\t */\n\tgetLabelSides: function () {\n\t\tvar bBox = this.labelBBox, // assume getLabelSize has run at this point\n\t\t\taxis = this.axis,\n\t\t\toptions = axis.options,\n\t\t\tlabelOptions = options.labels,\n\t\t\twidth = bBox.width,\n\t\t\tleftSide = width * { left: 0, center: 0.5, right: 1 }[axis.labelAlign] - labelOptions.x;\n\n\t\treturn [-leftSide, width - leftSide];\n\t},\n\n\t/**\n\t * Handle the label overflow by adjusting the labels to the left and right edge, or\n\t * hide them if they collide into the neighbour label.\n\t */\n\thandleOverflow: function (index, xy) {\n\t\tvar show = true,\n\t\t\taxis = this.axis,\n\t\t\tchart = axis.chart,\n\t\t\tisFirst = this.isFirst,\n\t\t\tisLast = this.isLast,\n\t\t\tx = xy.x,\n\t\t\treversed = axis.reversed,\n\t\t\ttickPositions = axis.tickPositions;\n\n\t\tif (isFirst || isLast) {\n\n\t\t\tvar sides = this.getLabelSides(),\n\t\t\t\tleftSide = sides[0],\n\t\t\t\trightSide = sides[1],\n\t\t\t\tplotLeft = chart.plotLeft,\n\t\t\t\tplotRight = plotLeft + axis.len,\n\t\t\t\tneighbour = axis.ticks[tickPositions[index + (isFirst ? 1 : -1)]],\n\t\t\t\tneighbourEdge = neighbour && neighbour.label.xy && neighbour.label.xy.x + neighbour.getLabelSides()[isFirst ? 0 : 1];\n\n\t\t\tif ((isFirst && !reversed) || (isLast && reversed)) {\n\t\t\t\t// Is the label spilling out to the left of the plot area?\n\t\t\t\tif (x + leftSide < plotLeft) {\n\n\t\t\t\t\t// Align it to plot left\n\t\t\t\t\tx = plotLeft - leftSide;\n\n\t\t\t\t\t// Hide it if it now overlaps the neighbour label\n\t\t\t\t\tif (neighbour && x + rightSide > neighbourEdge) {\n\t\t\t\t\t\tshow = false;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t} else {\n\t\t\t\t// Is the label spilling out to the right of the plot area?\n\t\t\t\tif (x + rightSide > plotRight) {\n\n\t\t\t\t\t// Align it to plot right\n\t\t\t\t\tx = plotRight - rightSide;\n\n\t\t\t\t\t// Hide it if it now overlaps the neighbour label\n\t\t\t\t\tif (neighbour && x + leftSide < neighbourEdge) {\n\t\t\t\t\t\tshow = false;\n\t\t\t\t\t}\n\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Set the modified x position of the label\n\t\t\txy.x = x;\n\t\t}\n\t\treturn show;\n\t},\n\n\t/**\n\t * Get the x and y position for ticks and labels\n\t */\n\tgetPosition: function (horiz, pos, tickmarkOffset, old) {\n\t\tvar axis = this.axis,\n\t\t\tchart = axis.chart,\n\t\t\tcHeight = (old && chart.oldChartHeight) || chart.chartHeight;\n\t\t\n\t\treturn {\n\t\t\tx: horiz ?\n\t\t\t\taxis.translate(pos + tickmarkOffset, null, null, old) + axis.transB :\n\t\t\t\taxis.left + axis.offset + (axis.opposite ? ((old && chart.oldChartWidth) || chart.chartWidth) - axis.right - axis.left : 0),\n\n\t\t\ty: horiz ?\n\t\t\t\tcHeight - axis.bottom + axis.offset - (axis.opposite ? axis.height : 0) :\n\t\t\t\tcHeight - axis.translate(pos + tickmarkOffset, null, null, old) - axis.transB\n\t\t};\n\t\t\n\t},\n\t\n\t/**\n\t * Get the x, y position of the tick label\n\t */\n\tgetLabelPosition: function (x, y, label, horiz, labelOptions, tickmarkOffset, index, step) {\n\t\tvar axis = this.axis,\n\t\t\ttransA = axis.transA,\n\t\t\treversed = axis.reversed,\n\t\t\tstaggerLines = axis.staggerLines,\n\t\t\tbaseline = axis.chart.renderer.fontMetrics(labelOptions.style.fontSize).b,\n\t\t\trotation = labelOptions.rotation;\n\t\t\t\n\t\tx = x + labelOptions.x - (tickmarkOffset && horiz ?\n\t\t\ttickmarkOffset * transA * (reversed ? -1 : 1) : 0);\n\t\ty = y + labelOptions.y - (tickmarkOffset && !horiz ?\n\t\t\ttickmarkOffset * transA * (reversed ? 1 : -1) : 0);\n\n\t\t// Correct for rotation (#1764)\n\t\tif (rotation && axis.side === 2) {\n\t\t\ty -= baseline - baseline * mathCos(rotation * deg2rad);\n\t\t}\n\t\t\n\t\t// Vertically centered\n\t\tif (!defined(labelOptions.y) && !rotation) { // #1951\n\t\t\ty += baseline - label.getBBox().height / 2;\n\t\t}\n\t\t\n\t\t// Correct for staggered labels\n\t\tif (staggerLines) {\n\t\t\ty += (index / (step || 1) % staggerLines) * (axis.labelOffset / staggerLines);\n\t\t}\n\t\t\n\t\treturn {\n\t\t\tx: x,\n\t\t\ty: y\n\t\t};\n\t},\n\t\n\t/**\n\t * Extendible method to return the path of the marker\n\t */\n\tgetMarkPath: function (x, y, tickLength, tickWidth, horiz, renderer) {\n\t\treturn renderer.crispLine([\n\t\t\t\tM,\n\t\t\t\tx,\n\t\t\t\ty,\n\t\t\t\tL,\n\t\t\t\tx + (horiz ? 0 : -tickLength),\n\t\t\t\ty + (horiz ? tickLength : 0)\n\t\t\t], tickWidth);\n\t},\n\n\t/**\n\t * Put everything in place\n\t *\n\t * @param index {Number}\n\t * @param old {Boolean} Use old coordinates to prepare an animation into new position\n\t */\n\trender: function (index, old, opacity) {\n\t\tvar tick = this,\n\t\t\taxis = tick.axis,\n\t\t\toptions = axis.options,\n\t\t\tchart = axis.chart,\n\t\t\trenderer = chart.renderer,\n\t\t\thoriz = axis.horiz,\n\t\t\ttype = tick.type,\n\t\t\tlabel = tick.label,\n\t\t\tpos = tick.pos,\n\t\t\tlabelOptions = options.labels,\n\t\t\tgridLine = tick.gridLine,\n\t\t\tgridPrefix = type ? type + 'Grid' : 'grid',\n\t\t\ttickPrefix = type ? type + 'Tick' : 'tick',\n\t\t\tgridLineWidth = options[gridPrefix + 'LineWidth'],\n\t\t\tgridLineColor = options[gridPrefix + 'LineColor'],\n\t\t\tdashStyle = options[gridPrefix + 'LineDashStyle'],\n\t\t\ttickLength = options[tickPrefix + 'Length'],\n\t\t\ttickWidth = options[tickPrefix + 'Width'] || 0,\n\t\t\ttickColor = options[tickPrefix + 'Color'],\n\t\t\ttickPosition = options[tickPrefix + 'Position'],\n\t\t\tgridLinePath,\n\t\t\tmark = tick.mark,\n\t\t\tmarkPath,\n\t\t\tstep = labelOptions.step,\n\t\t\tattribs,\n\t\t\tshow = true,\n\t\t\ttickmarkOffset = axis.tickmarkOffset,\n\t\t\txy = tick.getPosition(horiz, pos, tickmarkOffset, old),\n\t\t\tx = xy.x,\n\t\t\ty = xy.y,\n\t\t\treverseCrisp = ((horiz && x === axis.pos + axis.len) || (!horiz && y === axis.pos)) ? -1 : 1, // #1480, #1687\n\t\t\tstaggerLines = axis.staggerLines;\n\n\t\tthis.isActive = true;\n\t\t\n\t\t// create the grid line\n\t\tif (gridLineWidth) {\n\t\t\tgridLinePath = axis.getPlotLinePath(pos + tickmarkOffset, gridLineWidth * reverseCrisp, old, true);\n\n\t\t\tif (gridLine === UNDEFINED) {\n\t\t\t\tattribs = {\n\t\t\t\t\tstroke: gridLineColor,\n\t\t\t\t\t'stroke-width': gridLineWidth\n\t\t\t\t};\n\t\t\t\tif (dashStyle) {\n\t\t\t\t\tattribs.dashstyle = dashStyle;\n\t\t\t\t}\n\t\t\t\tif (!type) {\n\t\t\t\t\tattribs.zIndex = 1;\n\t\t\t\t}\n\t\t\t\tif (old) {\n\t\t\t\t\tattribs.opacity = 0;\n\t\t\t\t}\n\t\t\t\ttick.gridLine = gridLine =\n\t\t\t\t\tgridLineWidth ?\n\t\t\t\t\t\trenderer.path(gridLinePath)\n\t\t\t\t\t\t\t.attr(attribs).add(axis.gridGroup) :\n\t\t\t\t\t\tnull;\n\t\t\t}\n\n\t\t\t// If the parameter 'old' is set, the current call will be followed\n\t\t\t// by another call, therefore do not do any animations this time\n\t\t\tif (!old && gridLine && gridLinePath) {\n\t\t\t\tgridLine[tick.isNew ? 'attr' : 'animate']({\n\t\t\t\t\td: gridLinePath,\n\t\t\t\t\topacity: opacity\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// create the tick mark\n\t\tif (tickWidth && tickLength) {\n\n\t\t\t// negate the length\n\t\t\tif (tickPosition === 'inside') {\n\t\t\t\ttickLength = -tickLength;\n\t\t\t}\n\t\t\tif (axis.opposite) {\n\t\t\t\ttickLength = -tickLength;\n\t\t\t}\n\n\t\t\tmarkPath = tick.getMarkPath(x, y, tickLength, tickWidth * reverseCrisp, horiz, renderer);\n\n\t\t\tif (mark) { // updating\n\t\t\t\tmark.animate({\n\t\t\t\t\td: markPath,\n\t\t\t\t\topacity: opacity\n\t\t\t\t});\n\t\t\t} else { // first time\n\t\t\t\ttick.mark = renderer.path(\n\t\t\t\t\tmarkPath\n\t\t\t\t).attr({\n\t\t\t\t\tstroke: tickColor,\n\t\t\t\t\t'stroke-width': tickWidth,\n\t\t\t\t\topacity: opacity\n\t\t\t\t}).add(axis.axisGroup);\n\t\t\t}\n\t\t}\n\n\t\t// the label is created on init - now move it into place\n\t\tif (label && !isNaN(x)) {\n\t\t\tlabel.xy = xy = tick.getLabelPosition(x, y, label, horiz, labelOptions, tickmarkOffset, index, step);\n\n\t\t\t// Apply show first and show last. If the tick is both first and last, it is \n\t\t\t// a single centered tick, in which case we show the label anyway (#2100).\n\t\t\tif ((tick.isFirst && !tick.isLast && !pick(options.showFirstLabel, 1)) ||\n\t\t\t\t\t(tick.isLast && !tick.isFirst && !pick(options.showLastLabel, 1))) {\n\t\t\t\tshow = false;\n\n\t\t\t// Handle label overflow and show or hide accordingly\n\t\t\t} else if (!staggerLines && horiz && labelOptions.overflow === 'justify' && !tick.handleOverflow(index, xy)) {\n\t\t\t\tshow = false;\n\t\t\t}\n\n\t\t\t// apply step\n\t\t\tif (step && index % step) {\n\t\t\t\t// show those indices dividable by step\n\t\t\t\tshow = false;\n\t\t\t}\n\n\t\t\t// Set the new position, and show or hide\n\t\t\tif (show && !isNaN(xy.y)) {\n\t\t\t\txy.opacity = opacity;\n\t\t\t\tlabel[tick.isNew ? 'attr' : 'animate'](xy);\n\t\t\t\ttick.isNew = false;\n\t\t\t} else {\n\t\t\t\tlabel.attr('y', -9999); // #1338\n\t\t\t}\n\t\t}\n\t},\n\n\t/**\n\t * Destructor for the tick prototype\n\t */\n\tdestroy: function () {\n\t\tdestroyObjectProperties(this, this.axis);\n\t}\n};\n\n/**\n * The object wrapper for plot lines and plot bands\n * @param {Object} options\n */\nfunction PlotLineOrBand(axis, options) {\n\tthis.axis = axis;\n\n\tif (options) {\n\t\tthis.options = options;\n\t\tthis.id = options.id;\n\t}\n}\n\nPlotLineOrBand.prototype = {\n\t\n\t/**\n\t * Render the plot line or plot band. If it is already existing,\n\t * move it.\n\t */\n\trender: function () {\n\t\tvar plotLine = this,\n\t\t\taxis = plotLine.axis,\n\t\t\thoriz = axis.horiz,\n\t\t\thalfPointRange = (axis.pointRange || 0) / 2,\n\t\t\toptions = plotLine.options,\n\t\t\toptionsLabel = options.label,\n\t\t\tlabel = plotLine.label,\n\t\t\twidth = options.width,\n\t\t\tto = options.to,\n\t\t\tfrom = options.from,\n\t\t\tisBand = defined(from) && defined(to),\n\t\t\tvalue = options.value,\n\t\t\tdashStyle = options.dashStyle,\n\t\t\tsvgElem = plotLine.svgElem,\n\t\t\tpath = [],\n\t\t\taddEvent,\n\t\t\teventType,\n\t\t\txs,\n\t\t\tys,\n\t\t\tx,\n\t\t\ty,\n\t\t\tcolor = options.color,\n\t\t\tzIndex = options.zIndex,\n\t\t\tevents = options.events,\n\t\t\tattribs,\n\t\t\trenderer = axis.chart.renderer;\n\n\t\t// logarithmic conversion\n\t\tif (axis.isLog) {\n\t\t\tfrom = log2lin(from);\n\t\t\tto = log2lin(to);\n\t\t\tvalue = log2lin(value);\n\t\t}\n\n\t\t// plot line\n\t\tif (width) {\n\t\t\tpath = axis.getPlotLinePath(value, width);\n\t\t\tattribs = {\n\t\t\t\tstroke: color,\n\t\t\t\t'stroke-width': width\n\t\t\t};\n\t\t\tif (dashStyle) {\n\t\t\t\tattribs.dashstyle = dashStyle;\n\t\t\t}\n\t\t} else if (isBand) { // plot band\n\t\t\t\n\t\t\t// keep within plot area\n\t\t\tfrom = mathMax(from, axis.min - halfPointRange);\n\t\t\tto = mathMin(to, axis.max + halfPointRange);\n\t\t\t\n\t\t\tpath = axis.getPlotBandPath(from, to, options);\n\t\t\tattribs = {\n\t\t\t\tfill: color\n\t\t\t};\n\t\t\tif (options.borderWidth) {\n\t\t\t\tattribs.stroke = options.borderColor;\n\t\t\t\tattribs['stroke-width'] = options.borderWidth;\n\t\t\t}\n\t\t} else {\n\t\t\treturn;\n\t\t}\n\t\t// zIndex\n\t\tif (defined(zIndex)) {\n\t\t\tattribs.zIndex = zIndex;\n\t\t}\n\n\t\t// common for lines and bands\n\t\tif (svgElem) {\n\t\t\tif (path) {\n\t\t\t\tsvgElem.animate({\n\t\t\t\t\td: path\n\t\t\t\t}, null, svgElem.onGetPath);\n\t\t\t} else {\n\t\t\t\tsvgElem.hide();\n\t\t\t\tsvgElem.onGetPath = function () {\n\t\t\t\t\tsvgElem.show();\n\t\t\t\t};\n\t\t\t}\n\t\t} else if (path && path.length) {\n\t\t\tplotLine.svgElem = svgElem = renderer.path(path)\n\t\t\t\t.attr(attribs).add();\n\n\t\t\t// events\n\t\t\tif (events) {\n\t\t\t\taddEvent = function (eventType) {\n\t\t\t\t\tsvgElem.on(eventType, function (e) {\n\t\t\t\t\t\tevents[eventType].apply(plotLine, [e]);\n\t\t\t\t\t});\n\t\t\t\t};\n\t\t\t\tfor (eventType in events) {\n\t\t\t\t\taddEvent(eventType);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// the plot band/line label\n\t\tif (optionsLabel && defined(optionsLabel.text) && path && path.length && axis.width > 0 && axis.height > 0) {\n\t\t\t// apply defaults\n\t\t\toptionsLabel = merge({\n\t\t\t\talign: horiz && isBand && 'center',\n\t\t\t\tx: horiz ? !isBand && 4 : 10,\n\t\t\t\tverticalAlign : !horiz && isBand && 'middle',\n\t\t\t\ty: horiz ? isBand ? 16 : 10 : isBand ? 6 : -4,\n\t\t\t\trotation: horiz && !isBand && 90\n\t\t\t}, optionsLabel);\n\n\t\t\t// add the SVG element\n\t\t\tif (!label) {\n\t\t\t\tplotLine.label = label = renderer.text(\n\t\t\t\t\t\toptionsLabel.text,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\t0,\n\t\t\t\t\t\toptionsLabel.useHTML\n\t\t\t\t\t)\n\t\t\t\t\t.attr({\n\t\t\t\t\t\talign: optionsLabel.textAlign || optionsLabel.align,\n\t\t\t\t\t\trotation: optionsLabel.rotation,\n\t\t\t\t\t\tzIndex: zIndex\n\t\t\t\t\t})\n\t\t\t\t\t.css(optionsLabel.style)\n\t\t\t\t\t.add();\n\t\t\t}\n\n\t\t\t// get the bounding box and align the label\n\t\t\txs = [path[1], path[4], pick(path[6], path[1])];\n\t\t\tys = [path[2], path[5], pick(path[7], path[2])];\n\t\t\tx = arrayMin(xs);\n\t\t\ty = arrayMin(ys);\n\n\t\t\tlabel.align(optionsLabel, false, {\n\t\t\t\tx: x,\n\t\t\t\ty: y,\n\t\t\t\twidth: arrayMax(xs) - x,\n\t\t\t\theight: arrayMax(ys) - y\n\t\t\t});\n\t\t\tlabel.show();\n\n\t\t} else if (label) { // move out of sight\n\t\t\tlabel.hide();\n\t\t}\n\n\t\t// chainable\n\t\treturn plotLine;\n\t},\n\n\t/**\n\t * Remove the plot line or band\n\t */\n\tdestroy: function () {\n\t\t// remove it from the lookup\n\t\terase(this.axis.plotLinesAndBands, this);\n\t\t\n\t\tdelete this.axis;\n\t\tdestroyObjectProperties(this);\n\t}\n};\n/**\n * The class for stack items\n */\nfunction StackItem(axis, options, isNegative, x, stackOption, stacking) {\n\t\n\tvar inverted = axis.chart.inverted;\n\n\tthis.axis = axis;\n\n\t// Tells if the stack is negative\n\tthis.isNegative = isNegative;\n\n\t// Save the options to be able to style the label\n\tthis.options = options;\n\n\t// Save the x value to be able to position the label later\n\tthis.x = x;\n\n\t// Initialize total value\n\tthis.total = null;\n\n\t// This will keep each points' extremes stored by series.index\n\tthis.points = {};\n\n\t// Save the stack option on the series configuration object, and whether to treat it as percent\n\tthis.stack = stackOption;\n\tthis.percent = stacking === 'percent';\n\n\t// The align options and text align varies on whether the stack is negative and\n\t// if the chart is inverted or not.\n\t// First test the user supplied value, then use the dynamic.\n\tthis.alignOptions = {\n\t\talign: options.align || (inverted ? (isNegative ? 'left' : 'right') : 'center'),\n\t\tverticalAlign: options.verticalAlign || (inverted ? 'middle' : (isNegative ? 'bottom' : 'top')),\n\t\ty: pick(options.y, inverted ? 4 : (isNegative ? 14 : -6)),\n\t\tx: pick(options.x, inverted ? (isNegative ? -6 : 6) : 0)\n\t};\n\n\tthis.textAlign = options.textAlign || (inverted ? (isNegative ? 'right' : 'left') : 'center');\n}\n\nStackItem.prototype = {\n\tdestroy: function () {\n\t\tdestroyObjectProperties(this, this.axis);\n\t},\n\n\t/**\n\t * Renders the stack total label and adds it to the stack label group.\n\t */\n\trender: function (group) {\n\t\tvar options = this.options,\n\t\t\tformatOption = options.format,\n\t\t\tstr = formatOption ?\n\t\t\t\tformat(formatOption, this) : \n\t\t\t\toptions.formatter.call(this);  // format the text in the label\n\n\t\t// Change the text to reflect the new total and set visibility to hidden in case the serie is hidden\n\t\tif (this.label) {\n\t\t\tthis.label.attr({text: str, visibility: HIDDEN});\n\t\t// Create new label\n\t\t} else {\n\t\t\tthis.label =\n\t\t\t\tthis.axis.chart.renderer.text(str, 0, 0, options.useHTML)\t\t// dummy positions, actual position updated with setOffset method in columnseries\n\t\t\t\t\t.css(options.style)\t\t\t\t// apply style\n\t\t\t\t\t.attr({\n\t\t\t\t\t\talign: this.textAlign,\t\t\t\t// fix the text-anchor\n\t\t\t\t\t\trotation: options.rotation,\t// rotation\n\t\t\t\t\t\tvisibility: HIDDEN\t\t\t\t\t// hidden until setOffset is called\n\t\t\t\t\t})\t\t\t\t\n\t\t\t\t\t.add(group);\t\t\t\t\t\t\t// add to the labels-group\n\t\t}\n\t},\n\n\t/**\n\t * Sets the offset that the stack has from the x value and repositions the label.\n\t */\n\tsetOffset: function (xOffset, xWidth) {\n\t\tvar stackItem = this,\n\t\t\taxis = stackItem.axis,\n\t\t\tchart = axis.chart,\n\t\t\tinverted = chart.inverted,\n\t\t\tneg = this.isNegative,\t\t\t\t\t\t\t// special treatment is needed for negative stacks\n\t\t\ty = axis.translate(this.percent ? 100 : this.total, 0, 0, 0, 1), // stack value translated mapped to chart coordinates\n\t\t\tyZero = axis.translate(0),\t\t\t\t\t\t// stack origin\n\t\t\th = mathAbs(y - yZero),\t\t\t\t\t\t\t// stack height\n\t\t\tx = chart.xAxis[0].translate(this.x) + xOffset,\t// stack x position\n\t\t\tplotHeight = chart.plotHeight,\n\t\t\tstackBox = {\t// this is the box for the complete stack\n\t\t\t\tx: inverted ? (neg ? y : y - h) : x,\n\t\t\t\ty: inverted ? plotHeight - x - xWidth : (neg ? (plotHeight - y - h) : plotHeight - y),\n\t\t\t\twidth: inverted ? h : xWidth,\n\t\t\t\theight: inverted ? xWidth : h\n\t\t\t},\n\t\t\tlabel = this.label,\n\t\t\talignAttr;\n\t\t\n\t\tif (label) {\n\t\t\tlabel.align(this.alignOptions, null, stackBox);\t// align the label to the box\n\t\t\t\t\n\t\t\t// Set visibility (#678)\n\t\t\talignAttr = label.alignAttr;\n\t\t\tlabel.attr({ \n\t\t\t\tvisibility: this.options.crop === false || chart.isInsidePlot(alignAttr.x, alignAttr.y) ? \n\t\t\t\t\t(hasSVG ? 'inherit' : VISIBLE) : \n\t\t\t\t\tHIDDEN\n\t\t\t});\n\t\t}\n\t}\n};\n/**\n * Create a new axis object\n * @param {Object} chart\n * @param {Object} options\n */\nfunction Axis() {\n\tthis.init.apply(this, arguments);\n}\n\nAxis.prototype = {\n\t\n\t/**\n\t * Default options for the X axis - the Y axis has extended defaults \n\t */\n\tdefaultOptions: {\n\t\t// allowDecimals: null,\n\t\t// alternateGridColor: null,\n\t\t// categories: [],\n\t\tdateTimeLabelFormats: {\n\t\t\tmillisecond: '%H:%M:%S.%L',\n\t\t\tsecond: '%H:%M:%S',\n\t\t\tminute: '%H:%M',\n\t\t\thour: '%H:%M',\n\t\t\tday: '%e. %b',\n\t\t\tweek: '%e. %b',\n\t\t\tmonth: '%b \\'%y',\n\t\t\tyear: '%Y'\n\t\t},\n\t\tendOnTick: false,\n\t\tgridLineColor: '#C0C0C0',\n\t\t// gridLineDashStyle: 'solid',\n\t\t// gridLineWidth: 0,\n\t\t// reversed: false,\n\t\n\t\tlabels: defaultLabelOptions,\n\t\t\t// { step: null },\n\t\tlineColor: '#C0D0E0',\n\t\tlineWidth: 1,\n\t\t//linkedTo: null,\n\t\t//max: undefined,\n\t\t//min: undefined,\n\t\tminPadding: 0.01,\n\t\tmaxPadding: 0.01,\n\t\t//minRange: null,\n\t\tminorGridLineColor: '#E0E0E0',\n\t\t// minorGridLineDashStyle: null,\n\t\tminorGridLineWidth: 1,\n\t\tminorTickColor: '#A0A0A0',\n\t\t//minorTickInterval: null,\n\t\tminorTickLength: 2,\n\t\tminorTickPosition: 'outside', // inside or outside\n\t\t//minorTickWidth: 0,\n\t\t//opposite: false,\n\t\t//offset: 0,\n\t\t//plotBands: [{\n\t\t//\tevents: {},\n\t\t//\tzIndex: 1,\n\t\t//\tlabels: { align, x, verticalAlign, y, style, rotation, textAlign }\n\t\t//}],\n\t\t//plotLines: [{\n\t\t//\tevents: {}\n\t\t//  dashStyle: {}\n\t\t//\tzIndex:\n\t\t//\tlabels: { align, x, verticalAlign, y, style, rotation, textAlign }\n\t\t//}],\n\t\t//reversed: false,\n\t\t// showFirstLabel: true,\n\t\t// showLastLabel: true,\n\t\tstartOfWeek: 1,\n\t\tstartOnTick: false,\n\t\ttickColor: '#C0D0E0',\n\t\t//tickInterval: null,\n\t\ttickLength: 5,\n\t\ttickmarkPlacement: 'between', // on or between\n\t\ttickPixelInterval: 100,\n\t\ttickPosition: 'outside',\n\t\ttickWidth: 1,\n\t\ttitle: {\n\t\t\t//text: null,\n\t\t\talign: 'middle', // low, middle or high\n\t\t\t//margin: 0 for horizontal, 10 for vertical axes,\n\t\t\t//rotation: 0,\n\t\t\t//side: 'outside',\n\t\t\tstyle: {\n\t\t\t\tcolor: '#4d759e',\n\t\t\t\t//font: defaultFont.replace('normal', 'bold')\n\t\t\t\tfontWeight: 'bold'\n\t\t\t}\n\t\t\t//x: 0,\n\t\t\t//y: 0\n\t\t},\n\t\ttype: 'linear' // linear, logarithmic or datetime\n\t},\n\t\n\t/**\n\t * This options set extends the defaultOptions for Y axes\n\t */\n\tdefaultYAxisOptions: {\n\t\tendOnTick: true,\n\t\tgridLineWidth: 1,\n\t\ttickPixelInterval: 72,\n\t\tshowLastLabel: true,\n\t\tlabels: {\n\t\t\tx: -8,\n\t\t\ty: 3\n\t\t},\n\t\tlineWidth: 0,\n\t\tmaxPadding: 0.05,\n\t\tminPadding: 0.05,\n\t\tstartOnTick: true,\n\t\ttickWidth: 0,\n\t\ttitle: {\n\t\t\trotation: 270,\n\t\t\ttext: 'Values'\n\t\t},\n\t\tstackLabels: {\n\t\t\tenabled: false,\n\t\t\t//align: dynamic,\n\t\t\t//y: dynamic,\n\t\t\t//x: dynamic,\n\t\t\t//verticalAlign: dynamic,\n\t\t\t//textAlign: dynamic,\n\t\t\t//rotation: 0,\n\t\t\tformatter: function () {\n\t\t\t\treturn numberFormat(this.total, -1);\n\t\t\t},\n\t\t\tstyle: defaultLabelOptions.style\n\t\t}\n\t},\n\t\n\t/**\n\t * These options extend the defaultOptions for left axes\n\t */\n\tdefaultLeftAxisOptions: {\n\t\tlabels: {\n\t\t\tx: -8,\n\t\t\ty: null\n\t\t},\n\t\ttitle: {\n\t\t\trotation: 270\n\t\t}\n\t},\n\t\n\t/**\n\t * These options extend the defaultOptions for right axes\n\t */\n\tdefaultRightAxisOptions: {\n\t\tlabels: {\n\t\t\tx: 8,\n\t\t\ty: null\n\t\t},\n\t\ttitle: {\n\t\t\trotation: 90\n\t\t}\n\t},\n\t\n\t/**\n\t * These options extend the defaultOptions for bottom axes\n\t */\n\tdefaultBottomAxisOptions: {\n\t\tlabels: {\n\t\t\tx: 0,\n\t\t\ty: 14\n\t\t\t// overflow: undefined,\n\t\t\t// staggerLines: null\n\t\t},\n\t\ttitle: {\n\t\t\trotation: 0\n\t\t}\n\t},\n\t/**\n\t * These options extend the defaultOptions for left axes\n\t */\n\tdefaultTopAxisOptions: {\n\t\tlabels: {\n\t\t\tx: 0,\n\t\t\ty: -5\n\t\t\t// overflow: undefined\n\t\t\t// staggerLines: null\n\t\t},\n\t\ttitle: {\n\t\t\trotation: 0\n\t\t}\n\t},\n\t\n\t/**\n\t * Initialize the axis\n\t */\n\tinit: function (chart, userOptions) {\n\t\t\t\n\t\t\n\t\tvar isXAxis = userOptions.isX,\n\t\t\taxis = this;\n\t\n\t\t// Flag, is the axis horizontal\n\t\taxis.horiz = chart.inverted ? !isXAxis : isXAxis;\n\t\t\n\t\t// Flag, isXAxis\n\t\taxis.isXAxis = isXAxis;\n\t\taxis.xOrY = isXAxis ? 'x' : 'y';\n\t\n\t\n\t\taxis.opposite = userOptions.opposite; // needed in setOptions\n\t\taxis.side = axis.horiz ?\n\t\t\t\t(axis.opposite ? 0 : 2) : // top : bottom\n\t\t\t\t(axis.opposite ? 1 : 3);  // right : left\n\t\n\t\taxis.setOptions(userOptions);\n\t\t\n\t\n\t\tvar options = this.options,\n\t\t\ttype = options.type,\n\t\t\tisDatetimeAxis = type === 'datetime';\n\t\n\t\taxis.labelFormatter = options.labels.formatter || axis.defaultLabelFormatter; // can be overwritten by dynamic format\n\t\n\t\n\t\t// Flag, stagger lines or not\n\t\taxis.userOptions = userOptions;\n\t\n\t\t//axis.axisTitleMargin = UNDEFINED,// = options.title.margin,\n\t\taxis.minPixelPadding = 0;\n\t\t//axis.ignoreMinPadding = UNDEFINED; // can be set to true by a column or bar series\n\t\t//axis.ignoreMaxPadding = UNDEFINED;\n\t\n\t\taxis.chart = chart;\n\t\taxis.reversed = options.reversed;\n\t\taxis.zoomEnabled = options.zoomEnabled !== false;\n\t\n\t\t// Initial categories\n\t\taxis.categories = options.categories || type === 'category';\n\t\n\t\t// Elements\n\t\t//axis.axisGroup = UNDEFINED;\n\t\t//axis.gridGroup = UNDEFINED;\n\t\t//axis.axisTitle = UNDEFINED;\n\t\t//axis.axisLine = UNDEFINED;\n\t\n\t\t// Shorthand types\n\t\taxis.isLog = type === 'logarithmic';\n\t\taxis.isDatetimeAxis = isDatetimeAxis;\n\t\n\t\t// Flag, if axis is linked to another axis\n\t\taxis.isLinked = defined(options.linkedTo);\n\t\t// Linked axis.\n\t\t//axis.linkedParent = UNDEFINED;\t\n\t\t\n\t\t// Tick positions\n\t\t//axis.tickPositions = UNDEFINED; // array containing predefined positions\n\t\t// Tick intervals\n\t\t//axis.tickInterval = UNDEFINED;\n\t\t//axis.minorTickInterval = UNDEFINED;\n\t\t\n\t\taxis.tickmarkOffset = (axis.categories && options.tickmarkPlacement === 'between') ? 0.5 : 0;\n\t\n\t\t// Major ticks\n\t\taxis.ticks = {};\n\t\t// Minor ticks\n\t\taxis.minorTicks = {};\n\t\t//axis.tickAmount = UNDEFINED;\n\t\n\t\t// List of plotLines/Bands\n\t\taxis.plotLinesAndBands = [];\n\t\n\t\t// Alternate bands\n\t\taxis.alternateBands = {};\n\t\n\t\t// Axis metrics\n\t\t//axis.left = UNDEFINED;\n\t\t//axis.top = UNDEFINED;\n\t\t//axis.width = UNDEFINED;\n\t\t//axis.height = UNDEFINED;\n\t\t//axis.bottom = UNDEFINED;\n\t\t//axis.right = UNDEFINED;\n\t\t//axis.transA = UNDEFINED;\n\t\t//axis.transB = UNDEFINED;\n\t\t//axis.oldTransA = UNDEFINED;\n\t\taxis.len = 0;\n\t\t//axis.oldMin = UNDEFINED;\n\t\t//axis.oldMax = UNDEFINED;\n\t\t//axis.oldUserMin = UNDEFINED;\n\t\t//axis.oldUserMax = UNDEFINED;\n\t\t//axis.oldAxisLength = UNDEFINED;\n\t\taxis.minRange = axis.userMinRange = options.minRange || options.maxZoom;\n\t\taxis.range = options.range;\n\t\taxis.offset = options.offset || 0;\n\t\n\t\n\t\t// Dictionary for stacks\n\t\taxis.stacks = {};\n\t\taxis.oldStacks = {};\n\n\t\t// Dictionary for stacks max values\n\t\taxis.stackExtremes = {};\n\n\t\t// Min and max in the data\n\t\t//axis.dataMin = UNDEFINED,\n\t\t//axis.dataMax = UNDEFINED,\n\t\n\t\t// The axis range\n\t\taxis.max = null;\n\t\taxis.min = null;\n\t\n\t\t// User set min and max\n\t\t//axis.userMin = UNDEFINED,\n\t\t//axis.userMax = UNDEFINED,\n\n\t\t// Run Axis\n\t\t\n\t\tvar eventType,\n\t\t\tevents = axis.options.events;\n\n\t\t// Register\n\t\tif (inArray(axis, chart.axes) === -1) { // don't add it again on Axis.update()\n\t\t\tchart.axes.push(axis);\n\t\t\tchart[isXAxis ? 'xAxis' : 'yAxis'].push(axis);\n\t\t}\n\n\t\taxis.series = axis.series || []; // populated by Series\n\n\t\t// inverted charts have reversed xAxes as default\n\t\tif (chart.inverted && isXAxis && axis.reversed === UNDEFINED) {\n\t\t\taxis.reversed = true;\n\t\t}\n\n\t\taxis.removePlotBand = axis.removePlotBandOrLine;\n\t\taxis.removePlotLine = axis.removePlotBandOrLine;\n\n\n\t\t// register event listeners\n\t\tfor (eventType in events) {\n\t\t\taddEvent(axis, eventType, events[eventType]);\n\t\t}\n\n\t\t// extend logarithmic axis\n\t\tif (axis.isLog) {\n\t\t\taxis.val2lin = log2lin;\n\t\t\taxis.lin2val = lin2log;\n\t\t}\n\t},\n\t\n\t/**\n\t * Merge and set options\n\t */\n\tsetOptions: function (userOptions) {\n\t\tthis.options = merge(\n\t\t\tthis.defaultOptions,\n\t\t\tthis.isXAxis ? {} : this.defaultYAxisOptions,\n\t\t\t[this.defaultTopAxisOptions, this.defaultRightAxisOptions,\n\t\t\t\tthis.defaultBottomAxisOptions, this.defaultLeftAxisOptions][this.side],\n\t\t\tmerge(\n\t\t\t\tdefaultOptions[this.isXAxis ? 'xAxis' : 'yAxis'], // if set in setOptions (#1053)\n\t\t\t\tuserOptions\n\t\t\t)\n\t\t);\n\t},\n\n\t/**\n\t * Update the axis with a new options structure\n\t */\n\tupdate: function (newOptions, redraw) {\n\t\tvar chart = this.chart;\n\n\t\tnewOptions = chart.options[this.xOrY + 'Axis'][this.options.index] = merge(this.userOptions, newOptions);\n\n\t\tthis.destroy(true);\n\t\tthis._addedPlotLB = this.userMin = this.userMax = UNDEFINED; // #1611, #2306\n\n\t\tthis.init(chart, extend(newOptions, { events: UNDEFINED }));\n\n\t\tchart.isDirtyBox = true;\n\t\tif (pick(redraw, true)) {\n\t\t\tchart.redraw();\n\t\t}\n\t},\t\n\t\n\t/**\n     * Remove the axis from the chart\n     */\n\tremove: function (redraw) {\n\t\tvar chart = this.chart,\n\t\t\tkey = this.xOrY + 'Axis'; // xAxis or yAxis\n\n\t\t// Remove associated series\n\t\teach(this.series, function (series) {\n\t\t\tseries.remove(false);\n\t\t});\n\n\t\t// Remove the axis\n\t\terase(chart.axes, this);\n\t\terase(chart[key], this);\n\t\tchart.options[key].splice(this.options.index, 1);\n\t\teach(chart[key], function (axis, i) { // Re-index, #1706\n\t\t\taxis.options.index = i;\n\t\t});\n\t\tthis.destroy();\n\t\tchart.isDirtyBox = true;\n\n\t\tif (pick(redraw, true)) {\n\t\t\tchart.redraw();\n\t\t}\n\t},\n\t\n\t/** \n\t * The default label formatter. The context is a special config object for the label.\n\t */\n\tdefaultLabelFormatter: function () {\n\t\tvar axis = this.axis,\n\t\t\tvalue = this.value,\n\t\t\tcategories = axis.categories, \n\t\t\tdateTimeLabelFormat = this.dateTimeLabelFormat,\n\t\t\tnumericSymbols = defaultOptions.lang.numericSymbols,\n\t\t\ti = numericSymbols && numericSymbols.length,\n\t\t\tmulti,\n\t\t\tret,\n\t\t\tformatOption = axis.options.labels.format,\n\t\t\t\n\t\t\t// make sure the same symbol is added for all labels on a linear axis\n\t\t\tnumericSymbolDetector = axis.isLog ? value : axis.tickInterval;\n\n\t\tif (formatOption) {\n\t\t\tret = format(formatOption, this);\n\t\t\n\t\t} else if (categories) {\n\t\t\tret = value;\n\t\t\n\t\t} else if (dateTimeLabelFormat) { // datetime axis\n\t\t\tret = dateFormat(dateTimeLabelFormat, value);\n\t\t\n\t\t} else if (i && numericSymbolDetector >= 1000) {\n\t\t\t// Decide whether we should add a numeric symbol like k (thousands) or M (millions).\n\t\t\t// If we are to enable this in tooltip or other places as well, we can move this\n\t\t\t// logic to the numberFormatter and enable it by a parameter.\n\t\t\twhile (i-- && ret === UNDEFINED) {\n\t\t\t\tmulti = Math.pow(1000, i + 1);\n\t\t\t\tif (numericSymbolDetector >= multi && numericSymbols[i] !== null) {\n\t\t\t\t\tret = numberFormat(value / multi, -1) + numericSymbols[i];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t\n\t\tif (ret === UNDEFINED) {\n\t\t\tif (value >= 1000) { // add thousands separators\n\t\t\t\tret = numberFormat(value, 0);\n\n\t\t\t} else { // small numbers\n\t\t\t\tret = numberFormat(value, -1);\n\t\t\t}\n\t\t}\n\t\t\n\t\treturn ret;\n\t},\n\n\t/**\n\t * Get the minimum and maximum for the series of each axis\n\t */\n\tgetSeriesExtremes: function () {\n\t\tvar axis = this,\n\t\t\tchart = axis.chart;\n\n\t\taxis.hasVisibleSeries = false;\n\n\t\t// reset dataMin and dataMax in case we're redrawing\n\t\taxis.dataMin = axis.dataMax = null;\n\n\t\t// reset cached stacking extremes\n\t\taxis.stackExtremes = {};\n\n\t\taxis.buildStacks();\n\n\t\t// loop through this axis' series\n\t\teach(axis.series, function (series) {\n\n\t\t\tif (series.visible || !chart.options.chart.ignoreHiddenSeries) {\n\n\t\t\t\tvar seriesOptions = series.options,\n\t\t\t\t\txData,\n\t\t\t\t\tthreshold = seriesOptions.threshold,\n\t\t\t\t\tseriesDataMin,\n\t\t\t\t\tseriesDataMax;\n\n\t\t\t\taxis.hasVisibleSeries = true;\n\n\t\t\t\t// Validate threshold in logarithmic axes\n\t\t\t\tif (axis.isLog && threshold <= 0) {\n\t\t\t\t\tthreshold = null;\n\t\t\t\t}\n\n\t\t\t\t// Get dataMin and dataMax for X axes\n\t\t\t\tif (axis.isXAxis) {\n\t\t\t\t\txData = series.xData;\n\t\t\t\t\tif (xData.length) {\n\t\t\t\t\t\taxis.dataMin = mathMin(pick(axis.dataMin, xData[0]), arrayMin(xData));\n\t\t\t\t\t\taxis.dataMax = mathMax(pick(axis.dataMax, xData[0]), arrayMax(xData));\n\t\t\t\t\t}\n\n\t\t\t\t// Get dataMin and dataMax for Y axes, as well as handle stacking and processed data\n\t\t\t\t} else {\n\n\t\t\t\t\t// Get this particular series extremes\n\t\t\t\t\tseries.getExtremes();\n\t\t\t\t\tseriesDataMax = series.dataMax;\n\t\t\t\t\tseriesDataMin = series.dataMin;\n\n\t\t\t\t\t// Get the dataMin and dataMax so far. If percentage is used, the min and max are\n\t\t\t\t\t// always 0 and 100. If seriesDataMin and seriesDataMax is null, then series\n\t\t\t\t\t// doesn't have active y data, we continue with nulls\n\t\t\t\t\tif (defined(seriesDataMin) && defined(seriesDataMax)) {\n\t\t\t\t\t\taxis.dataMin = mathMin(pick(axis.dataMin, seriesDataMin), seriesDataMin);\n\t\t\t\t\t\taxis.dataMax = mathMax(pick(axis.dataMax, seriesDataMax), seriesDataMax);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Adjust to threshold\n\t\t\t\t\tif (defined(threshold)) {\n\t\t\t\t\t\tif (axis.dataMin >= threshold) {\n\t\t\t\t\t\t\taxis.dataMin = threshold;\n\t\t\t\t\t\t\taxis.ignoreMinPadding = true;\n\t\t\t\t\t\t} else if (axis.dataMax < threshold) {\n\t\t\t\t\t\t\taxis.dataMax = threshold;\n\t\t\t\t\t\t\taxis.ignoreMaxPadding = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t},\n\n\t/**\n\t * Translate from axis value to pixel position on the chart, or back\n\t *\n\t */\n\ttranslate: function (val, backwards, cvsCoord, old, handleLog, pointPlacement) {\n\t\tvar axis = this,\n\t\t\taxisLength = axis.len,\n\t\t\tsign = 1,\n\t\t\tcvsOffset = 0,\n\t\t\tlocalA = old ? axis.oldTransA : axis.transA,\n\t\t\tlocalMin = old ? axis.oldMin : axis.min,\n\t\t\treturnValue,\n\t\t\tminPixelPadding = axis.minPixelPadding,\n\t\t\tpostTranslate = (axis.options.ordinal || (axis.isLog && handleLog)) && axis.lin2val;\n\n\t\tif (!localA) {\n\t\t\tlocalA = axis.transA;\n\t\t}\n\n\t\t// In vertical axes, the canvas coordinates start from 0 at the top like in \n\t\t// SVG. \n\t\tif (cvsCoord) {\n\t\t\tsign *= -1; // canvas coordinates inverts the value\n\t\t\tcvsOffset = axisLength;\n\t\t}\n\n\t\t// Handle reversed axis\n\t\tif (axis.reversed) { \n\t\t\tsign *= -1;\n\t\t\tcvsOffset -= sign * axisLength;\n\t\t}\n\n\t\t// From pixels to value\n\t\tif (backwards) { // reverse translation\n\t\t\t\n\t\t\tval = val * sign + cvsOffset;\n\t\t\tval -= minPixelPadding;\n\t\t\treturnValue = val / localA + localMin; // from chart pixel to value\n\t\t\tif (postTranslate) { // log and ordinal axes\n\t\t\t\treturnValue = axis.lin2val(returnValue);\n\t\t\t}\n\n\t\t// From value to pixels\n\t\t} else {\n\t\t\tif (postTranslate) { // log and ordinal axes\n\t\t\t\tval = axis.val2lin(val);\n\t\t\t}\n\t\t\tif (pointPlacement === 'between') {\n\t\t\t\tpointPlacement = 0.5;\n\t\t\t}\n\t\t\treturnValue = sign * (val - localMin) * localA + cvsOffset + (sign * minPixelPadding) +\n\t\t\t\t(isNumber(pointPlacement) ? localA * pointPlacement * axis.pointRange : 0);\n\t\t}\n\n\t\treturn returnValue;\n\t},\n\n\t/**\n\t * Utility method to translate an axis value to pixel position. \n\t * @param {Number} value A value in terms of axis units\n\t * @param {Boolean} paneCoordinates Whether to return the pixel coordinate relative to the chart\n\t *        or just the axis/pane itself.\n\t */\n\ttoPixels: function (value, paneCoordinates) {\n\t\treturn this.translate(value, false, !this.horiz, null, true) + (paneCoordinates ? 0 : this.pos);\n\t},\n\n\t/*\n\t * Utility method to translate a pixel position in to an axis value\n\t * @param {Number} pixel The pixel value coordinate\n\t * @param {Boolean} paneCoordiantes Whether the input pixel is relative to the chart or just the\n\t *        axis/pane itself.\n\t */\n\ttoValue: function (pixel, paneCoordinates) {\n\t\treturn this.translate(pixel - (paneCoordinates ? 0 : this.pos), true, !this.horiz, null, true);\n\t},\n\n\t/**\n\t * Create the path for a plot line that goes from the given value on\n\t * this axis, across the plot to the opposite side\n\t * @param {Number} value\n\t * @param {Number} lineWidth Used for calculation crisp line\n\t * @param {Number] old Use old coordinates (for resizing and rescaling)\n\t */\n\tgetPlotLinePath: function (value, lineWidth, old, force) {\n\t\tvar axis = this,\n\t\t\tchart = axis.chart,\n\t\t\taxisLeft = axis.left,\n\t\t\taxisTop = axis.top,\n\t\t\tx1,\n\t\t\ty1,\n\t\t\tx2,\n\t\t\ty2,\n\t\t\ttranslatedValue = axis.translate(value, null, null, old),\n\t\t\tcHeight = (old && chart.oldChartHeight) || chart.chartHeight,\n\t\t\tcWidth = (old && chart.oldChartWidth) || chart.chartWidth,\n\t\t\tskip,\n\t\t\ttransB = axis.transB;\n\n\t\tx1 = x2 = mathRound(translatedValue + transB);\n\t\ty1 = y2 = mathRound(cHeight - translatedValue - transB);\n\n\t\tif (isNaN(translatedValue)) { // no min or max\n\t\t\tskip = true;\n\n\t\t} else if (axis.horiz) {\n\t\t\ty1 = axisTop;\n\t\t\ty2 = cHeight - axis.bottom;\n\t\t\tif (x1 < axisLeft || x1 > axisLeft + axis.width) {\n\t\t\t\tskip = true;\n\t\t\t}\n\t\t} else {\n\t\t\tx1 = axisLeft;\n\t\t\tx2 = cWidth - axis.right;\n\n\t\t\tif (y1 < axisTop || y1 > axisTop + axis.height) {\n\t\t\t\tskip = true;\n\t\t\t}\n\t\t}\n\t\treturn skip && !force ?\n\t\t\tnull :\n\t\t\tchart.renderer.crispLine([M, x1, y1, L, x2, y2], lineWidth || 0);\n\t},\n\t\n\t/**\n\t * Create the path for a plot band\n\t */\n\tgetPlotBandPath: function (from, to) {\n\n\t\tvar toPath = this.getPlotLinePath(to),\n\t\t\tpath = this.getPlotLinePath(from);\n\t\t\t\n\t\tif (path && toPath) {\n\t\t\tpath.push(\n\t\t\t\ttoPath[4],\n\t\t\t\ttoPath[5],\n\t\t\t\ttoPath[1],\n\t\t\t\ttoPath[2]\n\t\t\t);\n\t\t} else { // outside the axis area\n\t\t\tpath = null;\n\t\t}\n\t\t\n\t\treturn path;\n\t},\n\t\n\t/**\n\t * Set the tick positions of a linear axis to round values like whole tens or every five.\n\t */\n\tgetLinearTickPositions: function (tickInterval, min, max) {\n\t\tvar pos,\n\t\t\tlastPos,\n\t\t\troundedMin = correctFloat(mathFloor(min / tickInterval) * tickInterval),\n\t\t\troundedMax = correctFloat(mathCeil(max / tickInterval) * tickInterval),\n\t\t\ttickPositions = [];\n\n\t\t// Populate the intermediate values\n\t\tpos = roundedMin;\n\t\twhile (pos <= roundedMax) {\n\n\t\t\t// Place the tick on the rounded value\n\t\t\ttickPositions.push(pos);\n\n\t\t\t// Always add the raw tickInterval, not the corrected one.\n\t\t\tpos = correctFloat(pos + tickInterval);\n\n\t\t\t// If the interval is not big enough in the current min - max range to actually increase\n\t\t\t// the loop variable, we need to break out to prevent endless loop. Issue #619\n\t\t\tif (pos === lastPos) {\n\t\t\t\tbreak;\n\t\t\t}\n\n\t\t\t// Record the last value\n\t\t\tlastPos = pos;\n\t\t}\n\t\treturn tickPositions;\n\t},\n\t\n\t/**\n\t * Set the tick positions of a logarithmic axis\n\t */\n\tgetLogTickPositions: function (interval, min, max, minor) {\n\t\tvar axis = this,\n\t\t\toptions = axis.options,\n\t\t\taxisLength = axis.len,\n\t\t\t// Since we use this method for both major and minor ticks,\n\t\t\t// use a local variable and return the result\n\t\t\tpositions = []; \n\t\t\n\t\t// Reset\n\t\tif (!minor) {\n\t\t\taxis._minorAutoInterval = null;\n\t\t}\n\t\t\n\t\t// First case: All ticks fall on whole logarithms: 1, 10, 100 etc.\n\t\tif (interval >= 0.5) {\n\t\t\tinterval = mathRound(interval);\n\t\t\tpositions = axis.getLinearTickPositions(interval, min, max);\n\t\t\t\n\t\t// Second case: We need intermediary ticks. For example \n\t\t// 1, 2, 4, 6, 8, 10, 20, 40 etc. \n\t\t} else if (interval >= 0.08) {\n\t\t\tvar roundedMin = mathFloor(min),\n\t\t\t\tintermediate,\n\t\t\t\ti,\n\t\t\t\tj,\n\t\t\t\tlen,\n\t\t\t\tpos,\n\t\t\t\tlastPos,\n\t\t\t\tbreak2;\n\t\t\t\t\n\t\t\tif (interval > 0.3) {\n\t\t\t\tintermediate = [1, 2, 4];\n\t\t\t} else if (interval > 0.15) { // 0.2 equals five minor ticks per 1, 10, 100 etc\n\t\t\t\tintermediate = [1, 2, 4, 6, 8];\n\t\t\t} else { // 0.1 equals ten minor ticks per 1, 10, 100 etc\n\t\t\t\tintermediate = [1, 2, 3, 4, 5, 6, 7, 8, 9];\n\t\t\t}\n\t\t\t\n\t\t\tfor (i = roundedMin; i < max + 1 && !break2; i++) {\n\t\t\t\tlen = intermediate.length;\n\t\t\t\tfor (j = 0; j < len && !break2; j++) {\n\t\t\t\t\tpos = log2lin(lin2log(i) * intermediate[j]);\n\t\t\t\t\t\n\t\t\t\t\tif (pos > min && (!minor || lastPos <= max)) { // #1670\n\t\t\t\t\t\tpositions.push(lastPos);\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tif (lastPos > max) {\n\t\t\t\t\t\tbreak2 = true;\n\t\t\t\t\t}\n\t\t\t\t\tlastPos = pos;\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t// Third case: We are so deep in between whole logarithmic values that\n\t\t// we might as well handle the tick positions like a linear axis. For\n\t\t// example 1.01, 1.02, 1.03, 1.04.\n\t\t} else {\n\t\t\tvar realMin = lin2log(min),\n\t\t\t\trealMax = lin2log(max),\n\t\t\t\ttickIntervalOption = options[minor ? 'minorTickInterval' : 'tickInterval'],\n\t\t\t\tfilteredTickIntervalOption = tickIntervalOption === 'auto' ? null : tickIntervalOption,\n\t\t\t\ttickPixelIntervalOption = options.tickPixelInterval / (minor ? 5 : 1),\n\t\t\t\ttotalPixelLength = minor ? axisLength / axis.tickPositions.length : axisLength;\n\t\t\t\n\t\t\tinterval = pick(\n\t\t\t\tfilteredTickIntervalOption,\n\t\t\t\taxis._minorAutoInterval,\n\t\t\t\t(realMax - realMin) * tickPixelIntervalOption / (totalPixelLength || 1)\n\t\t\t);\n\t\t\t\n\t\t\tinterval = normalizeTickInterval(\n\t\t\t\tinterval, \n\t\t\t\tnull, \n\t\t\t\tgetMagnitude(interval)\n\t\t\t);\n\t\t\t\n\t\t\tpositions = map(axis.getLinearTickPositions(\n\t\t\t\tinterval, \n\t\t\t\trealMin,\n\t\t\t\trealMax\t\n\t\t\t), log2lin);\n\t\t\t\n\t\t\tif (!minor) {\n\t\t\t\taxis._minorAutoInterval = interval / 5;\n\t\t\t}\n\t\t}\n\t\t\n\t\t// Set the axis-level tickInterval variable \n\t\tif (!minor) {\n\t\t\taxis.tickInterval = interval;\n\t\t}\n\t\treturn positions;\n\t},\n\n\t/**\n\t * Return the minor tick positions. For logarithmic axes, reuse the same logic\n\t * as for major ticks.\n\t */\n\tgetMinorTickPositions: function () {\n\t\tvar axis = this,\n\t\t\toptions = axis.options,\n\t\t\ttickPositions = axis.tickPositions,\n\t\t\tminorTickInterval = axis.minorTickInterval,\n\t\t\tminorTickPositions = [],\n\t\t\tpos,\n\t\t\ti,\n\t\t\tlen;\n\t\t\n\t\tif (axis.isLog) {\n\t\t\tlen = tickPositions.length;\n\t\t\tfor (i = 1; i < len; i++) {\n\t\t\t\tminorTickPositions = minorTickPositions.concat(\n\t\t\t\t\taxis.getLogTickPositions(minorTickInterval, tickPositions[i - 1], tickPositions[i], true)\n\t\t\t\t);\t\n\t\t\t}\n\t\t} else if (axis.isDatetimeAxis && options.minorTickInterval === 'auto') { // #1314\n\t\t\tminorTickPositions = minorTickPositions.concat(\n\t\t\t\tgetTimeTicks(\n\t\t\t\t\tnormalizeTimeTickInterval(minorTickInterval),\n\t\t\t\t\taxis.min,\n\t\t\t\t\taxis.max,\n\t\t\t\t\toptions.startOfWeek\n\t\t\t\t)\n\t\t\t);\n\t\t\tif (minorTickPositions[0] < axis.min) {\n\t\t\t\tminorTickPositions.shift();\n\t\t\t}\n\t\t} else {\t\t\t\n\t\t\tfor (pos = axis.min + (tickPositions[0] - axis.min) % minorTickInterval; pos <= axis.max; pos += minorTickInterval) {\n\t\t\t\tminorTickPositions.push(pos);\n\t\t\t}\n\t\t}\n\t\treturn minorTickPositions;\n\t},\n\n\t/**\n\t * Adjust the min and max for the minimum range. Keep in mind that the series data is \n\t * not yet processed, so we don't have information on data cropping and grouping, or \n\t * updated axis.pointRange or series.pointRange. The data can't be processed until\n\t * we have finally established min and max.\n\t */\n\tadjustForMinRange: function () {\n\t\tvar axis = this,\n\t\t\toptions = axis.options,\n\t\t\tmin = axis.min,\n\t\t\tmax = axis.max,\n\t\t\tzoomOffset,\n\t\t\tspaceAvailable = axis.dataMax - axis.dataMin >= axis.minRange,\n\t\t\tclosestDataRange,\n\t\t\ti,\n\t\t\tdistance,\n\t\t\txData,\n\t\t\tloopLength,\n\t\t\tminArgs,\n\t\t\tmaxArgs;\n\n\t\t// Set the automatic minimum range based on the closest point distance\n\t\tif (axis.isXAxis && axis.minRange === UNDEFINED && !axis.isLog) {\n\n\t\t\tif (defined(options.min) || defined(options.max)) {\n\t\t\t\taxis.minRange = null; // don't do this again\n\n\t\t\t} else {\n\n\t\t\t\t// Find the closest distance between raw data points, as opposed to\n\t\t\t\t// closestPointRange that applies to processed points (cropped and grouped)\n\t\t\t\teach(axis.series, function (series) {\n\t\t\t\t\txData = series.xData;\n\t\t\t\t\tloopLength = series.xIncrement ? 1 : xData.length - 1;\n\t\t\t\t\tfor (i = loopLength; i > 0; i--) {\n\t\t\t\t\t\tdistance = xData[i] - xData[i - 1];\n\t\t\t\t\t\tif (closestDataRange === UNDEFINED || distance < closestDataRange) {\n\t\t\t\t\t\t\tclosestDataRange = distance;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\taxis.minRange = mathMin(closestDataRange * 5, axis.dataMax - axis.dataMin);\n\t\t\t}\n\t\t}\n\n\t\t// if minRange is exceeded, adjust\n\t\tif (max - min < axis.minRange) {\n\t\t\tvar minRange = axis.minRange;\n\t\t\tzoomOffset = (minRange - max + min) / 2;\n\n\t\t\t// if min and max options have been set, don't go beyond it\n\t\t\tminArgs = [min - zoomOffset, pick(options.min, min - zoomOffset)];\n\t\t\tif (spaceAvailable) { // if space is available, stay within the data range\n\t\t\t\tminArgs[2] = axis.dataMin;\n\t\t\t}\n\t\t\tmin = arrayMax(minArgs);\n\n\t\t\tmaxArgs = [min + minRange, pick(options.max, min + minRange)];\n\t\t\tif (spaceAvailable) { // if space is availabe, stay within the data range\n\t\t\t\tmaxArgs[2] = axis.dataMax;\n\t\t\t}\n\n\t\t\tmax = arrayMin(maxArgs);\n\n\t\t\t// now if the max is adjusted, adjust the min back\n\t\t\tif (max - min < minRange) {\n\t\t\t\tminArgs[0] = max - minRange;\n\t\t\t\tminArgs[1] = pick(options.min, max - minRange);\n\t\t\t\tmin = arrayMax(minArgs);\n\t\t\t}\n\t\t}\n\t\t\n\t\t// Record modified extremes\n\t\taxis.min = min;\n\t\taxis.max = max;\n\t},\n\n\t/**\n\t * Update translation information\n\t */\n\tsetAxisTranslation: function (saveOld) {\n\t\tvar axis = this,\n\t\t\trange = axis.max - axis.min,\n\t\t\tpointRange = 0,\n\t\t\tclosestPointRange,\n\t\t\tminPointOffset = 0,\n\t\t\tpointRangePadding = 0,\n\t\t\tlinkedParent = axis.linkedParent,\n\t\t\tordinalCorrection,\n\t\t\ttransA = axis.transA;\n\n\t\t// adjust translation for padding\n\t\tif (axis.isXAxis) {\n\t\t\tif (linkedParent) {\n\t\t\t\tminPointOffset = linkedParent.minPointOffset;\n\t\t\t\tpointRangePadding = linkedParent.pointRangePadding;\n\t\t\t\t\n\t\t\t} else {\n\t\t\t\teach(axis.series, function (series) {\n\t\t\t\t\tvar seriesPointRange = series.pointRange,\n\t\t\t\t\t\tpointPlacement = series.options.pointPlacement,\n\t\t\t\t\t\tseriesClosestPointRange = series.closestPointRange;\n\n\t\t\t\t\tif (seriesPointRange > range) { // #1446\n\t\t\t\t\t\tseriesPointRange = 0;\n\t\t\t\t\t}\n\t\t\t\t\tpointRange = mathMax(pointRange, seriesPointRange);\n\t\t\t\t\t\n\t\t\t\t\t// minPointOffset is the value padding to the left of the axis in order to make\n\t\t\t\t\t// room for points with a pointRange, typically columns. When the pointPlacement option\n\t\t\t\t\t// is 'between' or 'on', this padding does not apply.\n\t\t\t\t\tminPointOffset = mathMax(\n\t\t\t\t\t\tminPointOffset, \n\t\t\t\t\t\tisString(pointPlacement) ? 0 : seriesPointRange / 2\n\t\t\t\t\t);\n\t\t\t\t\t\n\t\t\t\t\t// Determine the total padding needed to the length of the axis to make room for the \n\t\t\t\t\t// pointRange. If the series' pointPlacement is 'on', no padding is added.\n\t\t\t\t\tpointRangePadding = mathMax(\n\t\t\t\t\t\tpointRangePadding,\n\t\t\t\t\t\tpointPlacement === 'on' ? 0 : seriesPointRange\n\t\t\t\t\t);\n\n\t\t\t\t\t// Set the closestPointRange\n\t\t\t\t\tif (!series.noSharedTooltip && defined(seriesClosestPointRange)) {\n\t\t\t\t\t\tclosestPointRange = defined(closestPointRange) ?\n\t\t\t\t\t\t\tmathMin(closestPointRange, seriesClosestPointRange) :\n\t\t\t\t\t\t\tseriesClosestPointRange;\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t\t\n\t\t\t// Record minPointOffset and pointRangePadding\n\t\t\tordinalCorrection = axis.ordinalSlope && closestPointRange ? axis.ordinalSlope / closestPointRange : 1; // #988, #1853\n\t\t\taxis.minPointOffset = minPointOffset = minPointOffset * ordinalCorrection;\n\t\t\taxis.pointRangePadding = pointRangePadding = pointRangePadding * ordinalCorrection;\n\n\t\t\t// pointRange means the width reserved for each point, like in a column chart\n\t\t\taxis.pointRange = mathMin(pointRange, range);\n\n\t\t\t// closestPointRange means the closest distance between points. In columns\n\t\t\t// it is mostly equal to pointRange, but in lines pointRange is 0 while closestPointRange\n\t\t\t// is some other value\n\t\t\taxis.closestPointRange = closestPointRange;\n\t\t}\n\n\t\t// Secondary values\n\t\tif (saveOld) {\n\t\t\taxis.oldTransA = transA;\n\t\t}\n\t\taxis.translationSlope = axis.transA = transA = axis.len / ((range + pointRangePadding) || 1);\n\t\taxis.transB = axis.horiz ? axis.left : axis.bottom; // translation addend\n\t\taxis.minPixelPadding = transA * minPointOffset;\n\t},\n\n\t/**\n\t * Set the tick positions to round values and optionally extend the extremes\n\t * to the nearest tick\n\t */\n\tsetTickPositions: function (secondPass) {\n\t\tvar axis = this,\n\t\t\tchart = axis.chart,\n\t\t\toptions = axis.options,\n\t\t\tisLog = axis.isLog,\n\t\t\tisDatetimeAxis = axis.isDatetimeAxis,\n\t\t\tisXAxis = axis.isXAxis,\n\t\t\tisLinked = axis.isLinked,\n\t\t\ttickPositioner = axis.options.tickPositioner,\n\t\t\tmaxPadding = options.maxPadding,\n\t\t\tminPadding = options.minPadding,\n\t\t\tlength,\n\t\t\tlinkedParentExtremes,\n\t\t\ttickIntervalOption = options.tickInterval,\n\t\t\tminTickIntervalOption = options.minTickInterval,\n\t\t\ttickPixelIntervalOption = options.tickPixelInterval,\n\t\t\ttickPositions,\n\t\t\tkeepTwoTicksOnly,\n\t\t\tcategories = axis.categories;\n\n\t\t// linked axis gets the extremes from the parent axis\n\t\tif (isLinked) {\n\t\t\taxis.linkedParent = chart[isXAxis ? 'xAxis' : 'yAxis'][options.linkedTo];\n\t\t\tlinkedParentExtremes = axis.linkedParent.getExtremes();\n\t\t\taxis.min = pick(linkedParentExtremes.min, linkedParentExtremes.dataMin);\n\t\t\taxis.max = pick(linkedParentExtremes.max, linkedParentExtremes.dataMax);\n\t\t\tif (options.type !== axis.linkedParent.options.type) {\n\t\t\t\terror(11, 1); // Can't link axes of different type\n\t\t\t}\n\t\t} else { // initial min and max from the extreme data values\n\t\t\taxis.min = pick(axis.userMin, options.min, axis.dataMin);\n\t\t\taxis.max = pick(axis.userMax, options.max, axis.dataMax);\n\t\t}\n\n\t\tif (isLog) {\n\t\t\tif (!secondPass && mathMin(axis.min, pick(axis.dataMin, axis.min)) <= 0) { // #978\n\t\t\t\terror(10, 1); // Can't plot negative values on log axis\n\t\t\t}\n\t\t\taxis.min = correctFloat(log2lin(axis.min)); // correctFloat cures #934\n\t\t\taxis.max = correctFloat(log2lin(axis.max));\n\t\t}\n\n\t\t// handle zoomed range\n\t\tif (axis.range) {\n\t\t\taxis.userMin = axis.min = mathMax(axis.min, axis.max - axis.range); // #618\n\t\t\taxis.userMax = axis.max;\n\t\t\tif (secondPass) {\n\t\t\t\taxis.range = null;  // don't use it when running setExtremes\n\t\t\t}\n\t\t}\n\t\t\n\t\t// Hook for adjusting this.min and this.max. Used by bubble series.\n\t\tif (axis.beforePadding) {\n\t\t\taxis.beforePadding();\n\t\t}\n\n\t\t// adjust min and max for the minimum range\n\t\taxis.adjustForMinRange();\n\t\t\n\t\t// Pad the values to get clear of the chart's edges. To avoid tickInterval taking the padding\n\t\t// into account, we do this after computing tick interval (#1337).\n\t\tif (!categories && !axis.usePercentage && !isLinked && defined(axis.min) && defined(axis.max)) {\n\t\t\tlength = axis.max - axis.min;\n\t\t\tif (length) {\n\t\t\t\tif (!defined(options.min) && !defined(axis.userMin) && minPadding && (axis.dataMin < 0 || !axis.ignoreMinPadding)) {\n\t\t\t\t\taxis.min -= length * minPadding;\n\t\t\t\t}\n\t\t\t\tif (!defined(options.max) && !defined(axis.userMax)  && maxPadding && (axis.dataMax > 0 || !axis.ignoreMaxPadding)) {\n\t\t\t\t\taxis.max += length * maxPadding;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// get tickInterval\n\t\tif (axis.min === axis.max || axis.min === undefined || axis.max === undefined) {\n\t\t\taxis.tickInterval = 1;\n\t\t} else if (isLinked && !tickIntervalOption &&\n\t\t\t\ttickPixelIntervalOption === axis.linkedParent.options.tickPixelInterval) {\n\t\t\taxis.tickInterval = axis.linkedParent.tickInterval;\n\t\t} else {\n\t\t\taxis.tickInterval = pick(\n\t\t\t\ttickIntervalOption,\n\t\t\t\tcategories ? // for categoried axis, 1 is default, for linear axis use tickPix\n\t\t\t\t\t1 :\n\t\t\t\t\t// don't let it be more than the data range\n\t\t\t\t\t(axis.max - axis.min) * tickPixelIntervalOption / mathMax(axis.len, tickPixelIntervalOption)\n\t\t\t);\n\t\t\t// For squished axes, set only two ticks\n\t\t\tif (!defined(tickIntervalOption) && axis.len < tickPixelIntervalOption && !this.isRadial) {\n\t\t\t\tkeepTwoTicksOnly = true;\n\t\t\t\taxis.tickInterval /= 4; // tick extremes closer to the real values\n\t\t\t}\n\t\t}\n\n\t\t// Now we're finished detecting min and max, crop and group series data. This\n\t\t// is in turn needed in order to find tick positions in ordinal axes. \n\t\tif (isXAxis && !secondPass) {\n\t\t\teach(axis.series, function (series) {\n\t\t\t\tseries.processData(axis.min !== axis.oldMin || axis.max !== axis.oldMax);\n\t\t\t});\n\t\t}\n\n\t\t// set the translation factor used in translate function\n\t\taxis.setAxisTranslation(true);\n\n\t\t// hook for ordinal axes and radial axes\n\t\tif (axis.beforeSetTickPositions) {\n\t\t\taxis.beforeSetTickPositions();\n\t\t}\n\t\t\n\t\t// hook for extensions, used in Highstock ordinal axes\n\t\tif (axis.postProcessTickInterval) {\n\t\t\taxis.tickInterval = axis.postProcessTickInterval(axis.tickInterval);\n\t\t}\n\n\t\t// In column-like charts, don't cramp in more ticks than there are points (#1943)\n\t\tif (axis.pointRange) {\n\t\t\taxis.tickInterval = mathMax(axis.pointRange, axis.tickInterval);\n\t\t}\n\t\t\n\t\t// Before normalizing the tick interval, handle minimum tick interval. This applies only if tickInterval is not defined.\n\t\tif (!tickIntervalOption && axis.tickInterval < minTickIntervalOption) {\n\t\t\taxis.tickInterval = minTickIntervalOption;\n\t\t}\n\n\t\t// for linear axes, get magnitude and normalize the interval\n\t\tif (!isDatetimeAxis && !isLog) { // linear\n\t\t\tif (!tickIntervalOption) {\n\t\t\t\taxis.tickInterval = normalizeTickInterval(axis.tickInterval, null, getMagnitude(axis.tickInterval), options);\n\t\t\t}\n\t\t}\n\n\t\t// get minorTickInterval\n\t\taxis.minorTickInterval = options.minorTickInterval === 'auto' && axis.tickInterval ?\n\t\t\t\taxis.tickInterval / 5 : options.minorTickInterval;\n\n\t\t// find the tick positions\n\t\taxis.tickPositions = tickPositions = options.tickPositions ?\n\t\t\t[].concat(options.tickPositions) : // Work on a copy (#1565)\n\t\t\t(tickPositioner && tickPositioner.apply(axis, [axis.min, axis.max]));\n\t\tif (!tickPositions) {\n\t\t\t\n\t\t\t// Too many ticks\n\t\t\tif (!axis.ordinalPositions && (axis.max - axis.min) / axis.tickInterval > mathMax(2 * axis.len, 200)) {\n\t\t\t\terror(19, true);\n\t\t\t}\n\t\t\t\n\t\t\tif (isDatetimeAxis) {\n\t\t\t\ttickPositions = (axis.getNonLinearTimeTicks || getTimeTicks)(\n\t\t\t\t\tnormalizeTimeTickInterval(axis.tickInterval, options.units),\n\t\t\t\t\taxis.min,\n\t\t\t\t\taxis.max,\n\t\t\t\t\toptions.startOfWeek,\n\t\t\t\t\taxis.ordinalPositions,\n\t\t\t\t\taxis.closestPointRange,\n\t\t\t\t\ttrue\n\t\t\t\t);\n\t\t\t} else if (isLog) {\n\t\t\t\ttickPositions = axis.getLogTickPositions(axis.tickInterval, axis.min, axis.max);\n\t\t\t} else {\n\t\t\t\ttickPositions = axis.getLinearTickPositions(axis.tickInterval, axis.min, axis.max);\n\t\t\t}\n\t\t\tif (keepTwoTicksOnly) {\n\t\t\t\ttickPositions.splice(1, tickPositions.length - 2);\n\t\t\t}\n\n\t\t\taxis.tickPositions = tickPositions;\n\t\t}\n\n\t\tif (!isLinked) {\n\n\t\t\t// reset min/max or remove extremes based on start/end on tick\n\t\t\tvar roundedMin = tickPositions[0],\n\t\t\t\troundedMax = tickPositions[tickPositions.length - 1],\n\t\t\t\tminPointOffset = axis.minPointOffset || 0,\n\t\t\t\tsinglePad;\n\n\t\t\tif (options.startOnTick) {\n\t\t\t\taxis.min = roundedMin;\n\t\t\t} else if (axis.min - minPointOffset > roundedMin) {\n\t\t\t\ttickPositions.shift();\n\t\t\t}\n\n\t\t\tif (options.endOnTick) {\n\t\t\t\taxis.max = roundedMax;\n\t\t\t} else if (axis.max + minPointOffset < roundedMax) {\n\t\t\t\ttickPositions.pop();\n\t\t\t}\n\t\t\t\n\t\t\t// When there is only one point, or all points have the same value on this axis, then min\n\t\t\t// and max are equal and tickPositions.length is 1. In this case, add some padding\n\t\t\t// in order to center the point, but leave it with one tick. #1337.\n\t\t\tif (tickPositions.length === 1) {\n\t\t\t\tsinglePad = 0.001; // The lowest possible number to avoid extra padding on columns\n\t\t\t\taxis.min -= singlePad;\n\t\t\t\taxis.max += singlePad;\n\t\t\t}\n\t\t}\n\t},\n\t\n\t/**\n\t * Set the max ticks of either the x and y axis collection\n\t */\n\tsetMaxTicks: function () {\n\t\t\n\t\tvar chart = this.chart,\n\t\t\tmaxTicks = chart.maxTicks || {},\n\t\t\ttickPositions = this.tickPositions,\n\t\t\tkey = this._maxTicksKey = [this.xOrY, this.pos, this.len].join('-');\n\t\t\n\t\tif (!this.isLinked && !this.isDatetimeAxis && tickPositions && tickPositions.length > (maxTicks[key] || 0) && this.options.alignTicks !== false) {\n\t\t\tmaxTicks[key] = tickPositions.length;\n\t\t}\n\t\tchart.maxTicks = maxTicks;\n\t},\n\n\t/**\n\t * When using multiple axes, adjust the number of ticks to match the highest\n\t * number of ticks in that group\n\t */\n\tadjustTickAmount: function () {\n\t\tvar axis = this,\n\t\t\tchart = axis.chart,\n\t\t\tkey = axis._maxTicksKey,\n\t\t\ttickPositions = axis.tickPositions,\n\t\t\tmaxTicks = chart.maxTicks;\n\n\t\tif (maxTicks && maxTicks[key] && !axis.isDatetimeAxis && !axis.categories && !axis.isLinked && axis.options.alignTicks !== false) { // only apply to linear scale\n\t\t\tvar oldTickAmount = axis.tickAmount,\n\t\t\t\tcalculatedTickAmount = tickPositions.length,\n\t\t\t\ttickAmount;\n\n\t\t\t// set the axis-level tickAmount to use below\n\t\t\taxis.tickAmount = tickAmount = maxTicks[key];\n\n\t\t\tif (calculatedTickAmount < tickAmount) {\n\t\t\t\twhile (tickPositions.length < tickAmount) {\n\t\t\t\t\ttickPositions.push(correctFloat(\n\t\t\t\t\t\ttickPositions[tickPositions.length - 1] + axis.tickInterval\n\t\t\t\t\t));\n\t\t\t\t}\n\t\t\t\taxis.transA *= (calculatedTickAmount - 1) / (tickAmount - 1);\n\t\t\t\taxis.max = tickPositions[tickPositions.length - 1];\n\n\t\t\t}\n\t\t\tif (defined(oldTickAmount) && tickAmount !== oldTickAmount) {\n\t\t\t\taxis.isDirty = true;\n\t\t\t}\n\t\t}\n\t},\n\n\t/**\n\t * Set the scale based on data min and max, user set min and max or options\n\t *\n\t */\n\tsetScale: function () {\n\t\tvar axis = this,\n\t\t\tstacks = axis.stacks,\n\t\t\ttype,\n\t\t\ti,\n\t\t\tisDirtyData,\n\t\t\tisDirtyAxisLength;\n\n\t\taxis.oldMin = axis.min;\n\t\taxis.oldMax = axis.max;\n\t\taxis.oldAxisLength = axis.len;\n\n\t\t// set the new axisLength\n\t\taxis.setAxisSize();\n\t\t//axisLength = horiz ? axisWidth : axisHeight;\n\t\tisDirtyAxisLength = axis.len !== axis.oldAxisLength;\n\n\t\t// is there new data?\n\t\teach(axis.series, function (series) {\n\t\t\tif (series.isDirtyData || series.isDirty ||\n\t\t\t\t\tseries.xAxis.isDirty) { // when x axis is dirty, we need new data extremes for y as well\n\t\t\t\tisDirtyData = true;\n\t\t\t}\n\t\t});\n\n\t\t// do we really need to go through all this?\n\t\tif (isDirtyAxisLength || isDirtyData || axis.isLinked || axis.forceRedraw ||\n\t\t\taxis.userMin !== axis.oldUserMin || axis.userMax !== axis.oldUserMax) {\n\t\t\t\n\t\t\t// reset stacks\n\t\t\tif (!axis.isXAxis) {\n\t\t\t\tfor (type in stacks) {\n\t\t\t\t\tdelete stacks[type];\n\t\t\t\t}\n\t\t\t}\n\n\t\t\taxis.forceRedraw = false;\n\n\t\t\t// get data extremes if needed\n\t\t\taxis.getSeriesExtremes();\n\n\t\t\t// get fixed positions based on tickInterval\n\t\t\taxis.setTickPositions();\n\n\t\t\t// record old values to decide whether a rescale is necessary later on (#540)\n\t\t\taxis.oldUserMin = axis.userMin;\n\t\t\taxis.oldUserMax = axis.userMax;\n\n\t\t\t// Mark as dirty if it is not already set to dirty and extremes have changed. #595.\n\t\t\tif (!axis.isDirty) {\n\t\t\t\taxis.isDirty = isDirtyAxisLength || axis.min !== axis.oldMin || axis.max !== axis.oldMax;\n\t\t\t}\n\t\t} else if (!axis.isXAxis) {\n\t\t\tif (axis.oldStacks) {\n\t\t\t\tstacks = axis.stacks = axis.oldStacks;\n\t\t\t}\n\n\t\t\t// reset stacks\n\t\t\tfor (type in stacks) {\n\t\t\t\tfor (i in stacks[type]) {\n\t\t\t\t\tstacks[type][i].cum = stacks[type][i].total;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t\n\t\t// Set the maximum tick amount\n\t\taxis.setMaxTicks();\n\t},\n\n\t/**\n\t * Set the extremes and optionally redraw\n\t * @param {Number} newMin\n\t * @param {Number} newMax\n\t * @param {Boolean} redraw\n\t * @param {Boolean|Object} animation Whether to apply animation, and optionally animation\n\t *    configuration\n\t * @param {Object} eventArguments \n\t *\n\t */\n\tsetExtremes: function (newMin, newMax, redraw, animation, eventArguments) {\n\t\tvar axis = this,\n\t\t\tchart = axis.chart;\n\n\t\tredraw = pick(redraw, true); // defaults to true\n\n\t\t// Extend the arguments with min and max\n\t\teventArguments = extend(eventArguments, {\n\t\t\tmin: newMin,\n\t\t\tmax: newMax\n\t\t});\n\n\t\t// Fire the event\n\t\tfireEvent(axis, 'setExtremes', eventArguments, function () { // the default event handler\n\n\t\t\taxis.userMin = newMin;\n\t\t\taxis.userMax = newMax;\n\t\t\taxis.eventArgs = eventArguments;\n\n\t\t\t// Mark for running afterSetExtremes\n\t\t\taxis.isDirtyExtremes = true;\n\n\t\t\t// redraw\n\t\t\tif (redraw) {\n\t\t\t\tchart.redraw(animation);\n\t\t\t}\n\t\t});\n\t},\n\t\n\t/**\n\t * Overridable method for zooming chart. Pulled out in a separate method to allow overriding\n\t * in stock charts.\n\t */\n\tzoom: function (newMin, newMax) {\n\n\t\t// Prevent pinch zooming out of range. Check for defined is for #1946.\n\t\tif (!this.allowZoomOutside) {\n\t\t\tif (defined(this.dataMin) && newMin <= this.dataMin) {\n\t\t\t\tnewMin = UNDEFINED;\n\t\t\t}\n\t\t\tif (defined(this.dataMax) && newMax >= this.dataMax) {\n\t\t\t\tnewMax = UNDEFINED;\n\t\t\t}\n\t\t}\n\n\t\t// In full view, displaying the reset zoom button is not required\n\t\tthis.displayBtn = newMin !== UNDEFINED || newMax !== UNDEFINED;\n\t\t\n\t\t// Do it\n\t\tthis.setExtremes(\n\t\t\tnewMin,\n\t\t\tnewMax,\n\t\t\tfalse, \n\t\t\tUNDEFINED, \n\t\t\t{ trigger: 'zoom' }\n\t\t);\n\t\treturn true;\n\t},\n\t\n\t/**\n\t * Update the axis metrics\n\t */\n\tsetAxisSize: function () {\n\t\tvar chart = this.chart,\n\t\t\toptions = this.options,\n\t\t\toffsetLeft = options.offsetLeft || 0,\n\t\t\toffsetRight = options.offsetRight || 0,\n\t\t\thoriz = this.horiz,\n\t\t\twidth,\n\t\t\theight,\n\t\t\ttop,\n\t\t\tleft;\n\n\t\t// Expose basic values to use in Series object and navigator\n\t\tthis.left = left = pick(options.left, chart.plotLeft + offsetLeft);\n\t\tthis.top = top = pick(options.top, chart.plotTop);\n\t\tthis.width = width = pick(options.width, chart.plotWidth - offsetLeft + offsetRight);\n\t\tthis.height = height = pick(options.height, chart.plotHeight);\n\t\tthis.bottom = chart.chartHeight - height - top;\n\t\tthis.right = chart.chartWidth - width - left;\n\n\t\t// Direction agnostic properties\n\t\tthis.len = mathMax(horiz ? width : height, 0); // mathMax fixes #905\n\t\tthis.pos = horiz ? left : top; // distance from SVG origin\n\t},\n\n\t/**\n\t * Get the actual axis extremes\n\t */\n\tgetExtremes: function () {\n\t\tvar axis = this,\n\t\t\tisLog = axis.isLog;\n\n\t\treturn {\n\t\t\tmin: isLog ? correctFloat(lin2log(axis.min)) : axis.min,\n\t\t\tmax: isLog ? correctFloat(lin2log(axis.max)) : axis.max,\n\t\t\tdataMin: axis.dataMin,\n\t\t\tdataMax: axis.dataMax,\n\t\t\tuserMin: axis.userMin,\n\t\t\tuserMax: axis.userMax\n\t\t};\n\t},\n\n\t/**\n\t * Get the zero plane either based on zero or on the min or max value.\n\t * Used in bar and area plots\n\t */\n\tgetThreshold: function (threshold) {\n\t\tvar axis = this,\n\t\t\tisLog = axis.isLog;\n\n\t\tvar realMin = isLog ? lin2log(axis.min) : axis.min,\n\t\t\trealMax = isLog ? lin2log(axis.max) : axis.max;\n\t\t\n\t\tif (realMin > threshold || threshold === null) {\n\t\t\tthreshold = realMin;\n\t\t} else if (realMax < threshold) {\n\t\t\tthreshold = realMax;\n\t\t}\n\n\t\treturn axis.translate(threshold, 0, 1, 0, 1);\n\t},\n\n\taddPlotBand: function (options) {\n\t\tthis.addPlotBandOrLine(options, 'plotBands');\n\t},\n\t\n\taddPlotLine: function (options) {\n\t\tthis.addPlotBandOrLine(options, 'plotLines');\n\t},\n\n\t/**\n\t * Add a plot band or plot line after render time\n\t *\n\t * @param options {Object} The plotBand or plotLine configuration object\n\t */\n\taddPlotBandOrLine: function (options, coll) {\n\t\tvar obj = new PlotLineOrBand(this, options).render(),\n\t\t\tuserOptions = this.userOptions;\n\n\t\tif (obj) { // #2189\n\t\t\t// Add it to the user options for exporting and Axis.update\n\t\t\tif (coll) {\n\t\t\t\tuserOptions[coll] = userOptions[coll] || [];\n\t\t\t\tuserOptions[coll].push(options); \n\t\t\t}\n\t\t\tthis.plotLinesAndBands.push(obj); \n\t\t}\n\t\t\n\t\treturn obj;\n\t},\n\n\t/**\n\t * Compute auto alignment for the axis label based on which side the axis is on \n\t * and the given rotation for the label\n\t */\n\tautoLabelAlign: function (rotation) {\n\t\tvar ret, \n\t\t\tangle = (pick(rotation, 0) - (this.side * 90) + 720) % 360;\n\n\t\tif (angle > 15 && angle < 165) {\n\t\t\tret = 'right';\n\t\t} else if (angle > 195 && angle < 345) {\n\t\t\tret = 'left';\n\t\t} else {\n\t\t\tret = 'center';\n\t\t}\n\t\treturn ret;\n\t},\n\n\t/**\n\t * Render the tick labels to a preliminary position to get their sizes\n\t */\n\tgetOffset: function () {\n\t\tvar axis = this,\n\t\t\tchart = axis.chart,\n\t\t\trenderer = chart.renderer,\n\t\t\toptions = axis.options,\n\t\t\ttickPositions = axis.tickPositions,\n\t\t\tticks = axis.ticks,\n\t\t\thoriz = axis.horiz,\n\t\t\tside = axis.side,\n\t\t\tinvertedSide = chart.inverted ? [1, 0, 3, 2][side] : side,\n\t\t\thasData,\n\t\t\tshowAxis,\n\t\t\ttitleOffset = 0,\n\t\t\ttitleOffsetOption,\n\t\t\ttitleMargin = 0,\n\t\t\taxisTitleOptions = options.title,\n\t\t\tlabelOptions = options.labels,\n\t\t\tlabelOffset = 0, // reset\n\t\t\taxisOffset = chart.axisOffset,\n\t\t\tclipOffset = chart.clipOffset,\n\t\t\tdirectionFactor = [-1, 1, 1, -1][side],\n\t\t\tn,\n\t\t\ti,\n\t\t\tautoStaggerLines = 1,\n\t\t\tmaxStaggerLines = pick(labelOptions.maxStaggerLines, 5),\n\t\t\tsortedPositions,\n\t\t\tlastRight,\n\t\t\toverlap,\n\t\t\tpos,\n\t\t\tbBox,\n\t\t\tx,\n\t\t\tw,\n\t\t\tlineNo;\n\t\t\t\n\t\t// For reuse in Axis.render\n\t\taxis.hasData = hasData = (axis.hasVisibleSeries || (defined(axis.min) && defined(axis.max) && !!tickPositions));\n\t\taxis.showAxis = showAxis = hasData || pick(options.showEmpty, true);\n\n\t\t// Set/reset staggerLines\n\t\taxis.staggerLines = axis.horiz && labelOptions.staggerLines;\n\t\t\n\t\t// Create the axisGroup and gridGroup elements on first iteration\n\t\tif (!axis.axisGroup) {\n\t\t\taxis.gridGroup = renderer.g('grid')\n\t\t\t\t.attr({ zIndex: options.gridZIndex || 1 })\n\t\t\t\t.add();\n\t\t\taxis.axisGroup = renderer.g('axis')\n\t\t\t\t.attr({ zIndex: options.zIndex || 2 })\n\t\t\t\t.add();\n\t\t\taxis.labelGroup = renderer.g('axis-labels')\n\t\t\t\t.attr({ zIndex: labelOptions.zIndex || 7 })\n\t\t\t\t.add();\n\t\t}\n\n\t\tif (hasData || axis.isLinked) {\n\t\t\t\n\t\t\t// Set the explicit or automatic label alignment\n\t\t\taxis.labelAlign = pick(labelOptions.align || axis.autoLabelAlign(labelOptions.rotation));\n\n\t\t\teach(tickPositions, function (pos) {\n\t\t\t\tif (!ticks[pos]) {\n\t\t\t\t\tticks[pos] = new Tick(axis, pos);\n\t\t\t\t} else {\n\t\t\t\t\tticks[pos].addLabel(); // update labels depending on tick interval\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// Handle automatic stagger lines\n\t\t\tif (axis.horiz && !axis.staggerLines && maxStaggerLines && !labelOptions.rotation) {\n\t\t\t\tsortedPositions = axis.reversed ? [].concat(tickPositions).reverse() : tickPositions;\n\t\t\t\twhile (autoStaggerLines < maxStaggerLines) {\n\t\t\t\t\tlastRight = [];\n\t\t\t\t\toverlap = false;\n\t\t\t\t\t\n\t\t\t\t\tfor (i = 0; i < sortedPositions.length; i++) {\n\t\t\t\t\t\tpos = sortedPositions[i];\n\t\t\t\t\t\tbBox = ticks[pos].label && ticks[pos].label.getBBox();\n\t\t\t\t\t\tw = bBox ? bBox.width : 0;\n\t\t\t\t\t\tlineNo = i % autoStaggerLines;\n\t\t\t\t\t\t\n\t\t\t\t\t\tif (w) {\n\t\t\t\t\t\t\tx = axis.translate(pos); // don't handle log\n\t\t\t\t\t\t\tif (lastRight[lineNo] !== UNDEFINED && x < lastRight[lineNo]) {\n\t\t\t\t\t\t\t\toverlap = true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tlastRight[lineNo] = x + w;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (overlap) {\n\t\t\t\t\t\tautoStaggerLines++;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (autoStaggerLines > 1) {\n\t\t\t\t\taxis.staggerLines = autoStaggerLines;\n\t\t\t\t}\n\t\t\t}\n\n\n\t\t\teach(tickPositions, function (pos) {\n\t\t\t\t// left side must be align: right and right side must have align: left for labels\n\t\t\t\tif (side === 0 || side === 2 || { 1: 'left', 3: 'right' }[side] === axis.labelAlign) {\n\n\t\t\t\t\t// get the highest offset\n\t\t\t\t\tlabelOffset = mathMax(\n\t\t\t\t\t\tticks[pos].getLabelSize(),\n\t\t\t\t\t\tlabelOffset\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t});\n\t\t\tif (axis.staggerLines) {\n\t\t\t\tlabelOffset *= axis.staggerLines;\n\t\t\t\taxis.labelOffset = labelOffset;\n\t\t\t}\n\t\t\t\n\n\t\t} else { // doesn't have data\n\t\t\tfor (n in ticks) {\n\t\t\t\tticks[n].destroy();\n\t\t\t\tdelete ticks[n];\n\t\t\t}\n\t\t}\n\n\t\tif (axisTitleOptions && axisTitleOptions.text && axisTitleOptions.enabled !== false) { \n\t\t\tif (!axis.axisTitle) {\n\t\t\t\taxis.axisTitle = renderer.text(\n\t\t\t\t\taxisTitleOptions.text,\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t\taxisTitleOptions.useHTML\n\t\t\t\t)\n\t\t\t\t.attr({\n\t\t\t\t\tzIndex: 7,\n\t\t\t\t\trotation: axisTitleOptions.rotation || 0,\n\t\t\t\t\talign:\n\t\t\t\t\t\taxisTitleOptions.textAlign ||\n\t\t\t\t\t\t{ low: 'left', middle: 'center', high: 'right' }[axisTitleOptions.align]\n\t\t\t\t})\n\t\t\t\t.css(axisTitleOptions.style)\n\t\t\t\t.add(axis.axisGroup);\n\t\t\t\taxis.axisTitle.isNew = true;\n\t\t\t}\n\n\t\t\tif (showAxis) {\n\t\t\t\ttitleOffset = axis.axisTitle.getBBox()[horiz ? 'height' : 'width'];\n\t\t\t\ttitleMargin = pick(axisTitleOptions.margin, horiz ? 5 : 10);\n\t\t\t\ttitleOffsetOption = axisTitleOptions.offset;\n\t\t\t}\n\n\t\t\t// hide or show the title depending on whether showEmpty is set\n\t\t\taxis.axisTitle[showAxis ? 'show' : 'hide']();\n\t\t}\n\t\t\n\t\t// handle automatic or user set offset\n\t\taxis.offset = directionFactor * pick(options.offset, axisOffset[side]);\n\t\t\n\t\taxis.axisTitleMargin =\n\t\t\tpick(titleOffsetOption,\n\t\t\t\tlabelOffset + titleMargin +\n\t\t\t\t(side !== 2 && labelOffset && directionFactor * options.labels[horiz ? 'y' : 'x'])\n\t\t\t);\n\n\t\taxisOffset[side] = mathMax(\n\t\t\taxisOffset[side],\n\t\t\taxis.axisTitleMargin + titleOffset + directionFactor * axis.offset\n\t\t);\n\t\tclipOffset[invertedSide] = mathMax(clipOffset[invertedSide], mathFloor(options.lineWidth / 2) * 2);\n\t},\n\t\n\t/**\n\t * Get the path for the axis line\n\t */\n\tgetLinePath: function (lineWidth) {\n\t\tvar chart = this.chart,\n\t\t\topposite = this.opposite,\n\t\t\toffset = this.offset,\n\t\t\thoriz = this.horiz,\n\t\t\tlineLeft = this.left + (opposite ? this.width : 0) + offset,\n\t\t\tlineTop = chart.chartHeight - this.bottom - (opposite ? this.height : 0) + offset;\n\t\t\t\n\t\tif (opposite) {\n\t\t\tlineWidth *= -1; // crispify the other way - #1480, #1687\n\t\t}\n\n\t\treturn chart.renderer.crispLine([\n\t\t\t\tM,\n\t\t\t\thoriz ?\n\t\t\t\t\tthis.left :\n\t\t\t\t\tlineLeft,\n\t\t\t\thoriz ?\n\t\t\t\t\tlineTop :\n\t\t\t\t\tthis.top,\n\t\t\t\tL,\n\t\t\t\thoriz ?\n\t\t\t\t\tchart.chartWidth - this.right :\n\t\t\t\t\tlineLeft,\n\t\t\t\thoriz ?\n\t\t\t\t\tlineTop :\n\t\t\t\t\tchart.chartHeight - this.bottom\n\t\t\t], lineWidth);\n\t},\n\t\n\t/**\n\t * Position the title\n\t */\n\tgetTitlePosition: function () {\n\t\t// compute anchor points for each of the title align options\n\t\tvar horiz = this.horiz,\n\t\t\taxisLeft = this.left,\n\t\t\taxisTop = this.top,\n\t\t\taxisLength = this.len,\n\t\t\taxisTitleOptions = this.options.title,\t\t\t\n\t\t\tmargin = horiz ? axisLeft : axisTop,\n\t\t\topposite = this.opposite,\n\t\t\toffset = this.offset,\n\t\t\tfontSize = pInt(axisTitleOptions.style.fontSize || 12),\n\t\t\t\n\t\t\t// the position in the length direction of the axis\n\t\t\talongAxis = {\n\t\t\t\tlow: margin + (horiz ? 0 : axisLength),\n\t\t\t\tmiddle: margin + axisLength / 2,\n\t\t\t\thigh: margin + (horiz ? axisLength : 0)\n\t\t\t}[axisTitleOptions.align],\n\t\n\t\t\t// the position in the perpendicular direction of the axis\n\t\t\toffAxis = (horiz ? axisTop + this.height : axisLeft) +\n\t\t\t\t(horiz ? 1 : -1) * // horizontal axis reverses the margin\n\t\t\t\t(opposite ? -1 : 1) * // so does opposite axes\n\t\t\t\tthis.axisTitleMargin +\n\t\t\t\t(this.side === 2 ? fontSize : 0);\n\n\t\treturn {\n\t\t\tx: horiz ?\n\t\t\t\talongAxis :\n\t\t\t\toffAxis + (opposite ? this.width : 0) + offset +\n\t\t\t\t\t(axisTitleOptions.x || 0), // x\n\t\t\ty: horiz ?\n\t\t\t\toffAxis - (opposite ? this.height : 0) + offset :\n\t\t\t\talongAxis + (axisTitleOptions.y || 0) // y\n\t\t};\n\t},\n\t\n\t/**\n\t * Render the axis\n\t */\n\trender: function () {\n\t\tvar axis = this,\n\t\t\tchart = axis.chart,\n\t\t\trenderer = chart.renderer,\n\t\t\toptions = axis.options,\n\t\t\tisLog = axis.isLog,\n\t\t\tisLinked = axis.isLinked,\n\t\t\ttickPositions = axis.tickPositions,\n\t\t\taxisTitle = axis.axisTitle,\n\t\t\tstacks = axis.stacks,\n\t\t\tticks = axis.ticks,\n\t\t\tminorTicks = axis.minorTicks,\n\t\t\talternateBands = axis.alternateBands,\n\t\t\tstackLabelOptions = options.stackLabels,\n\t\t\talternateGridColor = options.alternateGridColor,\n\t\t\ttickmarkOffset = axis.tickmarkOffset,\n\t\t\tlineWidth = options.lineWidth,\n\t\t\tlinePath,\n\t\t\thasRendered = chart.hasRendered,\n\t\t\tslideInTicks = hasRendered && defined(axis.oldMin) && !isNaN(axis.oldMin),\n\t\t\thasData = axis.hasData,\n\t\t\tshowAxis = axis.showAxis,\n\t\t\tfrom,\n\t\t\tto;\n\n\t\t// Mark all elements inActive before we go over and mark the active ones\n\t\teach([ticks, minorTicks, alternateBands], function (coll) {\n\t\t\tvar pos;\n\t\t\tfor (pos in coll) {\n\t\t\t\tcoll[pos].isActive = false;\n\t\t\t}\n\t\t});\n\n\t\t// If the series has data draw the ticks. Else only the line and title\n\t\tif (hasData || isLinked) {\n\n\t\t\t// minor ticks\n\t\t\tif (axis.minorTickInterval && !axis.categories) {\n\t\t\t\teach(axis.getMinorTickPositions(), function (pos) {\n\t\t\t\t\tif (!minorTicks[pos]) {\n\t\t\t\t\t\tminorTicks[pos] = new Tick(axis, pos, 'minor');\n\t\t\t\t\t}\n\n\t\t\t\t\t// render new ticks in old position\n\t\t\t\t\tif (slideInTicks && minorTicks[pos].isNew) {\n\t\t\t\t\t\tminorTicks[pos].render(null, true);\n\t\t\t\t\t}\n\n\t\t\t\t\tminorTicks[pos].render(null, false, 1);\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// Major ticks. Pull out the first item and render it last so that\n\t\t\t// we can get the position of the neighbour label. #808.\n\t\t\tif (tickPositions.length) { // #1300\n\t\t\t\teach(tickPositions.slice(1).concat([tickPositions[0]]), function (pos, i) {\n\t\n\t\t\t\t\t// Reorganize the indices\n\t\t\t\t\ti = (i === tickPositions.length - 1) ? 0 : i + 1;\n\t\n\t\t\t\t\t// linked axes need an extra check to find out if\n\t\t\t\t\tif (!isLinked || (pos >= axis.min && pos <= axis.max)) {\n\t\n\t\t\t\t\t\tif (!ticks[pos]) {\n\t\t\t\t\t\t\tticks[pos] = new Tick(axis, pos);\n\t\t\t\t\t\t}\n\t\n\t\t\t\t\t\t// render new ticks in old position\n\t\t\t\t\t\tif (slideInTicks && ticks[pos].isNew) {\n\t\t\t\t\t\t\tticks[pos].render(i, true);\n\t\t\t\t\t\t}\n\t\n\t\t\t\t\t\tticks[pos].render(i, false, 1);\n\t\t\t\t\t}\n\t\n\t\t\t\t});\n\t\t\t\t// In a categorized axis, the tick marks are displayed between labels. So\n\t\t\t\t// we need to add a tick mark and grid line at the left edge of the X axis.\n\t\t\t\tif (tickmarkOffset && axis.min === 0) {\n\t\t\t\t\tif (!ticks[-1]) {\n\t\t\t\t\t\tticks[-1] = new Tick(axis, -1, null, true);\n\t\t\t\t\t}\n\t\t\t\t\tticks[-1].render(-1);\n\t\t\t\t}\n\t\t\t\t\n\t\t\t}\n\n\t\t\t// alternate grid color\n\t\t\tif (alternateGridColor) {\n\t\t\t\teach(tickPositions, function (pos, i) {\n\t\t\t\t\tif (i % 2 === 0 && pos < axis.max) {\n\t\t\t\t\t\tif (!alternateBands[pos]) {\n\t\t\t\t\t\t\talternateBands[pos] = new PlotLineOrBand(axis);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tfrom = pos + tickmarkOffset; // #949\n\t\t\t\t\t\tto = tickPositions[i + 1] !== UNDEFINED ? tickPositions[i + 1] + tickmarkOffset : axis.max;\n\t\t\t\t\t\talternateBands[pos].options = {\n\t\t\t\t\t\t\tfrom: isLog ? lin2log(from) : from,\n\t\t\t\t\t\t\tto: isLog ? lin2log(to) : to,\n\t\t\t\t\t\t\tcolor: alternateGridColor\n\t\t\t\t\t\t};\n\t\t\t\t\t\talternateBands[pos].render();\n\t\t\t\t\t\talternateBands[pos].isActive = true;\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// custom plot lines and bands\n\t\t\tif (!axis._addedPlotLB) { // only first time\n\t\t\t\teach((options.plotLines || []).concat(options.plotBands || []), function (plotLineOptions) {\n\t\t\t\t\taxis.addPlotBandOrLine(plotLineOptions);\n\t\t\t\t});\n\t\t\t\taxis._addedPlotLB = true;\n\t\t\t}\n\n\t\t} // end if hasData\n\n\t\t// Remove inactive ticks\n\t\teach([ticks, minorTicks, alternateBands], function (coll) {\n\t\t\tvar pos, \n\t\t\t\ti,\n\t\t\t\tforDestruction = [],\n\t\t\t\tdelay = globalAnimation ? globalAnimation.duration || 500 : 0,\n\t\t\t\tdestroyInactiveItems = function () {\n\t\t\t\t\ti = forDestruction.length;\n\t\t\t\t\twhile (i--) {\n\t\t\t\t\t\t// When resizing rapidly, the same items may be destroyed in different timeouts,\n\t\t\t\t\t\t// or the may be reactivated\n\t\t\t\t\t\tif (coll[forDestruction[i]] && !coll[forDestruction[i]].isActive) {\n\t\t\t\t\t\t\tcoll[forDestruction[i]].destroy();\n\t\t\t\t\t\t\tdelete coll[forDestruction[i]];\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t};\n\n\t\t\tfor (pos in coll) {\n\n\t\t\t\tif (!coll[pos].isActive) {\n\t\t\t\t\t// Render to zero opacity\n\t\t\t\t\tcoll[pos].render(pos, false, 0);\n\t\t\t\t\tcoll[pos].isActive = false;\n\t\t\t\t\tforDestruction.push(pos);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// When the objects are finished fading out, destroy them\n\t\t\tif (coll === alternateBands || !chart.hasRendered || !delay) {\n\t\t\t\tdestroyInactiveItems();\n\t\t\t} else if (delay) {\n\t\t\t\tsetTimeout(destroyInactiveItems, delay);\n\t\t\t}\n\t\t});\n\n\t\t// Static items. As the axis group is cleared on subsequent calls\n\t\t// to render, these items are added outside the group.\n\t\t// axis line\n\t\tif (lineWidth) {\n\t\t\tlinePath = axis.getLinePath(lineWidth);\n\t\t\tif (!axis.axisLine) {\n\t\t\t\taxis.axisLine = renderer.path(linePath)\n\t\t\t\t\t.attr({\n\t\t\t\t\t\tstroke: options.lineColor,\n\t\t\t\t\t\t'stroke-width': lineWidth,\n\t\t\t\t\t\tzIndex: 7\n\t\t\t\t\t})\n\t\t\t\t\t.add(axis.axisGroup);\n\t\t\t} else {\n\t\t\t\taxis.axisLine.animate({ d: linePath });\n\t\t\t}\n\n\t\t\t// show or hide the line depending on options.showEmpty\n\t\t\taxis.axisLine[showAxis ? 'show' : 'hide']();\n\t\t}\n\n\t\tif (axisTitle && showAxis) {\n\t\t\t\n\t\t\taxisTitle[axisTitle.isNew ? 'attr' : 'animate'](\n\t\t\t\taxis.getTitlePosition()\n\t\t\t);\n\t\t\taxisTitle.isNew = false;\n\t\t}\n\n\t\t// Stacked totals:\n\t\tif (stackLabelOptions && stackLabelOptions.enabled) {\n\t\t\tvar stackKey, oneStack, stackCategory,\n\t\t\t\tstackTotalGroup = axis.stackTotalGroup;\n\n\t\t\t// Create a separate group for the stack total labels\n\t\t\tif (!stackTotalGroup) {\n\t\t\t\taxis.stackTotalGroup = stackTotalGroup =\n\t\t\t\t\trenderer.g('stack-labels')\n\t\t\t\t\t\t.attr({\n\t\t\t\t\t\t\tvisibility: VISIBLE,\n\t\t\t\t\t\t\tzIndex: 6\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.add();\n\t\t\t}\n\n\t\t\t// plotLeft/Top will change when y axis gets wider so we need to translate the\n\t\t\t// stackTotalGroup at every render call. See bug #506 and #516\n\t\t\tstackTotalGroup.translate(chart.plotLeft, chart.plotTop);\n\n\t\t\t// Render each stack total\n\t\t\tfor (stackKey in stacks) {\n\t\t\t\toneStack = stacks[stackKey];\n\t\t\t\tfor (stackCategory in oneStack) {\n\t\t\t\t\toneStack[stackCategory].render(stackTotalGroup);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// End stacked totals\n\n\t\taxis.isDirty = false;\n\t},\n\n\t/**\n\t * Remove a plot band or plot line from the chart by id\n\t * @param {Object} id\n\t */\n\tremovePlotBandOrLine: function (id) {\n\t\tvar plotLinesAndBands = this.plotLinesAndBands,\n\t\t\toptions = this.options,\n\t\t\tuserOptions = this.userOptions,\n\t\t\ti = plotLinesAndBands.length;\n\t\twhile (i--) {\n\t\t\tif (plotLinesAndBands[i].id === id) {\n\t\t\t\tplotLinesAndBands[i].destroy();\n\t\t\t}\n\t\t}\n\t\teach([options.plotLines || [], userOptions.plotLines || [], options.plotBands || [], userOptions.plotBands || []], function (arr) {\n\t\t\ti = arr.length;\n\t\t\twhile (i--) {\n\t\t\t\tif (arr[i].id === id) {\n\t\t\t\t\terase(arr, arr[i]);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t},\n\n\t/**\n\t * Update the axis title by options\n\t */\n\tsetTitle: function (newTitleOptions, redraw) {\n\t\tthis.update({ title: newTitleOptions }, redraw);\n\t},\n\n\t/**\n\t * Redraw the axis to reflect changes in the data or axis extremes\n\t */\n\tredraw: function () {\n\t\tvar axis = this,\n\t\t\tchart = axis.chart,\n\t\t\tpointer = chart.pointer;\n\n\t\t// hide tooltip and hover states\n\t\tif (pointer.reset) {\n\t\t\tpointer.reset(true);\n\t\t}\n\n\t\t// render the axis\n\t\taxis.render();\n\n\t\t// move plot lines and bands\n\t\teach(axis.plotLinesAndBands, function (plotLine) {\n\t\t\tplotLine.render();\n\t\t});\n\n\t\t// mark associated series as dirty and ready for redraw\n\t\teach(axis.series, function (series) {\n\t\t\tseries.isDirty = true;\n\t\t});\n\n\t},\n\n\t/**\n\t * Build the stacks from top down\n\t */\n\tbuildStacks: function () {\n\t\tvar series = this.series,\n\t\t\ti = series.length;\n\t\tif (!this.isXAxis) {\n\t\t\twhile (i--) {\n\t\t\t\tseries[i].setStackedPoints();\n\t\t\t}\n\t\t\t// Loop up again to compute percent stack\n\t\t\tif (this.usePercentage) {\n\t\t\t\tfor (i = 0; i < series.length; i++) {\n\t\t\t\t\tseries[i].setPercentStacks();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\n\t/**\n\t * Set new axis categories and optionally redraw\n\t * @param {Array} categories\n\t * @param {Boolean} redraw\n\t */\n\tsetCategories: function (categories, redraw) {\n\t\tthis.update({ categories: categories }, redraw);\n\t},\n\n\t/**\n\t * Destroys an Axis instance.\n\t */\n\tdestroy: function (keepEvents) {\n\t\tvar axis = this,\n\t\t\tstacks = axis.stacks,\n\t\t\tstackKey,\n\t\t\tplotLinesAndBands = axis.plotLinesAndBands,\n\t\t\ti;\n\n\t\t// Remove the events\n\t\tif (!keepEvents) {\n\t\t\tremoveEvent(axis);\n\t\t}\n\n\t\t// Destroy each stack total\n\t\tfor (stackKey in stacks) {\n\t\t\tdestroyObjectProperties(stacks[stackKey]);\n\n\t\t\tstacks[stackKey] = null;\n\t\t}\n\n\t\t// Destroy collections\n\t\teach([axis.ticks, axis.minorTicks, axis.alternateBands], function (coll) {\n\t\t\tdestroyObjectProperties(coll);\n\t\t});\n\t\ti = plotLinesAndBands.length;\n\t\twhile (i--) { // #1975\n\t\t\tplotLinesAndBands[i].destroy();\n\t\t}\n\n\t\t// Destroy local variables\n\t\teach(['stackTotalGroup', 'axisLine', 'axisGroup', 'gridGroup', 'labelGroup', 'axisTitle'], function (prop) {\n\t\t\tif (axis[prop]) {\n\t\t\t\taxis[prop] = axis[prop].destroy();\n\t\t\t}\n\t\t});\n\t}\n\n\t\n}; // end Axis\n\n/**\n * The tooltip object\n * @param {Object} chart The chart instance\n * @param {Object} options Tooltip options\n */\nfunction Tooltip() {\n\tthis.init.apply(this, arguments);\n}\n\nTooltip.prototype = {\n\n\tinit: function (chart, options) {\n\n\t\tvar borderWidth = options.borderWidth,\n\t\t\tstyle = options.style,\n\t\t\tpadding = pInt(style.padding);\n\n\t\t// Save the chart and options\n\t\tthis.chart = chart;\n\t\tthis.options = options;\n\n\t\t// Keep track of the current series\n\t\t//this.currentSeries = UNDEFINED;\n\n\t\t// List of crosshairs\n\t\tthis.crosshairs = [];\n\n\t\t// Current values of x and y when animating\n\t\tthis.now = { x: 0, y: 0 };\n\n\t\t// The tooltip is initially hidden\n\t\tthis.isHidden = true;\n\n\n\t\t// create the label\n\t\tthis.label = chart.renderer.label('', 0, 0, options.shape, null, null, options.useHTML, null, 'tooltip')\n\t\t\t.attr({\n\t\t\t\tpadding: padding,\n\t\t\t\tfill: options.backgroundColor,\n\t\t\t\t'stroke-width': borderWidth,\n\t\t\t\tr: options.borderRadius,\n\t\t\t\tzIndex: 8\n\t\t\t})\n\t\t\t.css(style)\n\t\t\t.css({ padding: 0 }) // Remove it from VML, the padding is applied as an attribute instead (#1117)\n\t\t\t.add()\n\t\t\t.attr({ y: -999 }); // #2301\n\n\t\t// When using canVG the shadow shows up as a gray circle\n\t\t// even if the tooltip is hidden.\n\t\tif (!useCanVG) {\n\t\t\tthis.label.shadow(options.shadow);\n\t\t}\n\n\t\t// Public property for getting the shared state.\n\t\tthis.shared = options.shared;\n\t},\n\n\t/**\n\t * Destroy the tooltip and its elements.\n\t */\n\tdestroy: function () {\n\t\teach(this.crosshairs, function (crosshair) {\n\t\t\tif (crosshair) {\n\t\t\t\tcrosshair.destroy();\n\t\t\t}\n\t\t});\n\n\t\t// Destroy and clear local variables\n\t\tif (this.label) {\n\t\t\tthis.label = this.label.destroy();\n\t\t}\n\t\tclearTimeout(this.hideTimer);\n\t\tclearTimeout(this.tooltipTimeout);\n\t},\n\n\t/**\n\t * Provide a soft movement for the tooltip\n\t *\n\t * @param {Number} x\n\t * @param {Number} y\n\t * @private\n\t */\n\tmove: function (x, y, anchorX, anchorY) {\n\t\tvar tooltip = this,\n\t\t\tnow = tooltip.now,\n\t\t\tanimate = tooltip.options.animation !== false && !tooltip.isHidden;\n\n\t\t// get intermediate values for animation\n\t\textend(now, {\n\t\t\tx: animate ? (2 * now.x + x) / 3 : x,\n\t\t\ty: animate ? (now.y + y) / 2 : y,\n\t\t\tanchorX: animate ? (2 * now.anchorX + anchorX) / 3 : anchorX,\n\t\t\tanchorY: animate ? (now.anchorY + anchorY) / 2 : anchorY\n\t\t});\n\n\t\t// move to the intermediate value\n\t\ttooltip.label.attr(now);\n\n\t\t\n\t\t// run on next tick of the mouse tracker\n\t\tif (animate && (mathAbs(x - now.x) > 1 || mathAbs(y - now.y) > 1)) {\n\t\t\n\t\t\t// never allow two timeouts\n\t\t\tclearTimeout(this.tooltipTimeout);\n\t\t\t\n\t\t\t// set the fixed interval ticking for the smooth tooltip\n\t\t\tthis.tooltipTimeout = setTimeout(function () {\n\t\t\t\t// The interval function may still be running during destroy, so check that the chart is really there before calling.\n\t\t\t\tif (tooltip) {\n\t\t\t\t\ttooltip.move(x, y, anchorX, anchorY);\n\t\t\t\t}\n\t\t\t}, 32);\n\t\t\t\n\t\t}\n\t},\n\n\t/**\n\t * Hide the tooltip\n\t */\n\thide: function () {\n\t\tvar tooltip = this,\n\t\t\thoverPoints;\n\t\t\n\t\tclearTimeout(this.hideTimer); // disallow duplicate timers (#1728, #1766)\n\t\tif (!this.isHidden) {\n\t\t\thoverPoints = this.chart.hoverPoints;\n\n\t\t\tthis.hideTimer = setTimeout(function () {\n\t\t\t\ttooltip.label.fadeOut();\n\t\t\t\ttooltip.isHidden = true;\n\t\t\t}, pick(this.options.hideDelay, 500));\n\n\t\t\t// hide previous hoverPoints and set new\n\t\t\tif (hoverPoints) {\n\t\t\t\teach(hoverPoints, function (point) {\n\t\t\t\t\tpoint.setState();\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tthis.chart.hoverPoints = null;\n\t\t}\n\t},\n\n\t/**\n\t * Hide the crosshairs\n\t */\n\thideCrosshairs: function () {\n\t\teach(this.crosshairs, function (crosshair) {\n\t\t\tif (crosshair) {\n\t\t\t\tcrosshair.hide();\n\t\t\t}\n\t\t});\n\t},\n\t\n\t/** \n\t * Extendable method to get the anchor position of the tooltip\n\t * from a point or set of points\n\t */\n\tgetAnchor: function (points, mouseEvent) {\n\t\tvar ret,\n\t\t\tchart = this.chart,\n\t\t\tinverted = chart.inverted,\n\t\t\tplotTop = chart.plotTop,\n\t\t\tplotX = 0,\n\t\t\tplotY = 0,\n\t\t\tyAxis;\n\t\t\n\t\tpoints = splat(points);\n\t\t\n\t\t// Pie uses a special tooltipPos\n\t\tret = points[0].tooltipPos;\n\t\t\n\t\t// When tooltip follows mouse, relate the position to the mouse\n\t\tif (this.followPointer && mouseEvent) {\n\t\t\tif (mouseEvent.chartX === UNDEFINED) {\n\t\t\t\tmouseEvent = chart.pointer.normalize(mouseEvent);\n\t\t\t}\n\t\t\tret = [\n\t\t\t\tmouseEvent.chartX - chart.plotLeft,\n\t\t\t\tmouseEvent.chartY - plotTop\n\t\t\t];\n\t\t}\n\t\t// When shared, use the average position\n\t\tif (!ret) {\n\t\t\teach(points, function (point) {\n\t\t\t\tyAxis = point.series.yAxis;\n\t\t\t\tplotX += point.plotX;\n\t\t\t\tplotY += (point.plotLow ? (point.plotLow + point.plotHigh) / 2 : point.plotY) +\n\t\t\t\t\t(!inverted && yAxis ? yAxis.top - plotTop : 0); // #1151\n\t\t\t});\n\t\t\t\n\t\t\tplotX /= points.length;\n\t\t\tplotY /= points.length;\n\t\t\t\n\t\t\tret = [\n\t\t\t\tinverted ? chart.plotWidth - plotY : plotX,\n\t\t\t\tthis.shared && !inverted && points.length > 1 && mouseEvent ? \n\t\t\t\t\tmouseEvent.chartY - plotTop : // place shared tooltip next to the mouse (#424)\n\t\t\t\t\tinverted ? chart.plotHeight - plotX : plotY\n\t\t\t];\n\t\t}\n\n\t\treturn map(ret, mathRound);\n\t},\n\t\n\t/**\n\t * Place the tooltip in a chart without spilling over\n\t * and not covering the point it self.\n\t */\n\tgetPosition: function (boxWidth, boxHeight, point) {\n\t\t\n\t\t// Set up the variables\n\t\tvar chart = this.chart,\n\t\t\tplotLeft = chart.plotLeft,\n\t\t\tplotTop = chart.plotTop,\n\t\t\tplotWidth = chart.plotWidth,\n\t\t\tplotHeight = chart.plotHeight,\n\t\t\tdistance = pick(this.options.distance, 12),\n\t\t\tpointX = point.plotX,\n\t\t\tpointY = point.plotY,\n\t\t\tx = pointX + plotLeft + (chart.inverted ? distance : -boxWidth - distance),\n\t\t\ty = pointY - boxHeight + plotTop + 15, // 15 means the point is 15 pixels up from the bottom of the tooltip\n\t\t\talignedRight;\n\t\n\t\t// It is too far to the left, adjust it\n\t\tif (x < 7) {\n\t\t\tx = plotLeft + mathMax(pointX, 0) + distance;\n\t\t}\n\t\n\t\t// Test to see if the tooltip is too far to the right,\n\t\t// if it is, move it back to be inside and then up to not cover the point.\n\t\tif ((x + boxWidth) > (plotLeft + plotWidth)) {\n\t\t\tx -= (x + boxWidth) - (plotLeft + plotWidth);\n\t\t\ty = pointY - boxHeight + plotTop - distance;\n\t\t\talignedRight = true;\n\t\t}\n\t\n\t\t// If it is now above the plot area, align it to the top of the plot area\n\t\tif (y < plotTop + 5) {\n\t\t\ty = plotTop + 5;\n\t\n\t\t\t// If the tooltip is still covering the point, move it below instead\n\t\t\tif (alignedRight && pointY >= y && pointY <= (y + boxHeight)) {\n\t\t\t\ty = pointY + plotTop + distance; // below\n\t\t\t}\n\t\t} \n\t\n\t\t// Now if the tooltip is below the chart, move it up. It's better to cover the\n\t\t// point than to disappear outside the chart. #834.\n\t\tif (y + boxHeight > plotTop + plotHeight) {\n\t\t\ty = mathMax(plotTop, plotTop + plotHeight - boxHeight - distance); // below\n\t\t}\n\t\n\t\treturn {x: x, y: y};\n\t},\n\n\t/**\n\t * In case no user defined formatter is given, this will be used. Note that the context\n\t * here is an object holding point, series, x, y etc.\n\t */\n\tdefaultFormatter: function (tooltip) {\n\t\tvar items = this.points || splat(this),\n\t\t\tseries = items[0].series,\n\t\t\ts;\n\n\t\t// build the header\n\t\ts = [series.tooltipHeaderFormatter(items[0])];\n\n\t\t// build the values\n\t\teach(items, function (item) {\n\t\t\tseries = item.series;\n\t\t\ts.push((series.tooltipFormatter && series.tooltipFormatter(item)) ||\n\t\t\t\titem.point.tooltipFormatter(series.tooltipOptions.pointFormat));\n\t\t});\n\n\t\t// footer\n\t\ts.push(tooltip.options.footerFormat || '');\n\n\t\treturn s.join('');\n\t},\n\n\t/**\n\t * Refresh the tooltip's text and position.\n\t * @param {Object} point\n\t */\n\trefresh: function (point, mouseEvent) {\n\t\tvar tooltip = this,\n\t\t\tchart = tooltip.chart,\n\t\t\tlabel = tooltip.label,\n\t\t\toptions = tooltip.options,\n\t\t\tx,\n\t\t\ty,\n\t\t\tanchor,\n\t\t\ttextConfig = {},\n\t\t\ttext,\n\t\t\tpointConfig = [],\n\t\t\tformatter = options.formatter || tooltip.defaultFormatter,\n\t\t\thoverPoints = chart.hoverPoints,\n\t\t\tborderColor,\n\t\t\tcrosshairsOptions = options.crosshairs,\n\t\t\tshared = tooltip.shared,\n\t\t\tcurrentSeries;\n\t\t\t\n\t\tclearTimeout(this.hideTimer);\n\t\t\n\t\t// get the reference point coordinates (pie charts use tooltipPos)\n\t\ttooltip.followPointer = splat(point)[0].series.tooltipOptions.followPointer;\n\t\tanchor = tooltip.getAnchor(point, mouseEvent);\n\t\tx = anchor[0];\n\t\ty = anchor[1];\n\n\t\t// shared tooltip, array is sent over\n\t\tif (shared && !(point.series && point.series.noSharedTooltip)) {\n\t\t\t\n\t\t\t// hide previous hoverPoints and set new\n\t\t\t\n\t\t\tchart.hoverPoints = point;\n\t\t\tif (hoverPoints) {\n\t\t\t\teach(hoverPoints, function (point) {\n\t\t\t\t\tpoint.setState();\n\t\t\t\t});\n\t\t\t}\n\n\t\t\teach(point, function (item) {\n\t\t\t\titem.setState(HOVER_STATE);\n\n\t\t\t\tpointConfig.push(item.getLabelConfig());\n\t\t\t});\n\n\t\t\ttextConfig = {\n\t\t\t\tx: point[0].category,\n\t\t\t\ty: point[0].y\n\t\t\t};\n\t\t\ttextConfig.points = pointConfig;\n\t\t\tpoint = point[0];\n\n\t\t// single point tooltip\n\t\t} else {\n\t\t\ttextConfig = point.getLabelConfig();\n\t\t}\n\t\ttext = formatter.call(textConfig, tooltip);\n\n\t\t// register the current series\n\t\tcurrentSeries = point.series;\n\n\t\t// update the inner HTML\n\t\tif (text === false) {\n\t\t\tthis.hide();\n\t\t} else {\n\n\t\t\t// show it\n\t\t\tif (tooltip.isHidden) {\n\t\t\t\tstop(label);\n\t\t\t\tlabel.attr('opacity', 1).show();\n\t\t\t}\n\n\t\t\t// update text\n\t\t\tlabel.attr({\n\t\t\t\ttext: text\n\t\t\t});\n\n\t\t\t// set the stroke color of the box\n\t\t\tborderColor = options.borderColor || point.color || currentSeries.color || '#606060';\n\t\t\tlabel.attr({\n\t\t\t\tstroke: borderColor\n\t\t\t});\n\t\t\t\n\t\t\ttooltip.updatePosition({ plotX: x, plotY: y });\n\t\t\n\t\t\tthis.isHidden = false;\n\t\t}\n\n\t\t// crosshairs\n\t\tif (crosshairsOptions) {\n\t\t\tcrosshairsOptions = splat(crosshairsOptions); // [x, y]\n\n\t\t\tvar path,\n\t\t\t\ti = crosshairsOptions.length,\n\t\t\t\tattribs,\n\t\t\t\taxis,\n\t\t\t\tval,\n\t\t\t\tseries;\n\n\t\t\twhile (i--) {\n\t\t\t\tseries = point.series;\n\t\t\t\taxis = series[i ? 'yAxis' : 'xAxis'];\n\t\t\t\tif (crosshairsOptions[i] && axis) {\n\t\t\t\t\tval = i ? pick(point.stackY, point.y) : point.x; // #814\n\t\t\t\t\tif (axis.isLog) { // #1671\n\t\t\t\t\t\tval = log2lin(val);\n\t\t\t\t\t}\n\t\t\t\t\tif (i === 1 && series.modifyValue) { // #1205, #2316\n\t\t\t\t\t\tval = series.modifyValue(val);\n\t\t\t\t\t}\n\n\t\t\t\t\tpath = axis.getPlotLinePath(\n\t\t\t\t\t\tval,\n\t\t\t\t\t\t1\n\t\t\t\t\t);\n\n\t\t\t\t\tif (tooltip.crosshairs[i]) {\n\t\t\t\t\t\ttooltip.crosshairs[i].attr({ d: path, visibility: VISIBLE });\n\t\t\t\t\t} else {\n\t\t\t\t\t\tattribs = {\n\t\t\t\t\t\t\t'stroke-width': crosshairsOptions[i].width || 1,\n\t\t\t\t\t\t\tstroke: crosshairsOptions[i].color || '#C0C0C0',\n\t\t\t\t\t\t\tzIndex: crosshairsOptions[i].zIndex || 2\n\t\t\t\t\t\t};\n\t\t\t\t\t\tif (crosshairsOptions[i].dashStyle) {\n\t\t\t\t\t\t\tattribs.dashstyle = crosshairsOptions[i].dashStyle;\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttooltip.crosshairs[i] = chart.renderer.path(path)\n\t\t\t\t\t\t\t.attr(attribs)\n\t\t\t\t\t\t\t.add();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tfireEvent(chart, 'tooltipRefresh', {\n\t\t\t\ttext: text,\n\t\t\t\tx: x + chart.plotLeft,\n\t\t\t\ty: y + chart.plotTop,\n\t\t\t\tborderColor: borderColor\n\t\t\t});\n\t},\n\t\n\t/**\n\t * Find the new position and perform the move\n\t */\n\tupdatePosition: function (point) {\n\t\tvar chart = this.chart,\n\t\t\tlabel = this.label, \n\t\t\tpos = (this.options.positioner || this.getPosition).call(\n\t\t\t\tthis,\n\t\t\t\tlabel.width,\n\t\t\t\tlabel.height,\n\t\t\t\tpoint\n\t\t\t);\n\n\t\t// do the move\n\t\tthis.move(\n\t\t\tmathRound(pos.x), \n\t\t\tmathRound(pos.y), \n\t\t\tpoint.plotX + chart.plotLeft, \n\t\t\tpoint.plotY + chart.plotTop\n\t\t);\n\t}\n};\n/**\n * The mouse tracker object. All methods starting with \"on\" are primary DOM event handlers. \n * Subsequent methods should be named differently from what they are doing.\n * @param {Object} chart The Chart instance\n * @param {Object} options The root options object\n */\nfunction Pointer(chart, options) {\n\tthis.init(chart, options);\n}\n\nPointer.prototype = {\n\t/**\n\t * Initialize Pointer\n\t */\n\tinit: function (chart, options) {\n\t\t\n\t\tvar chartOptions = options.chart,\n\t\t\tchartEvents = chartOptions.events,\n\t\t\tzoomType = useCanVG ? '' : chartOptions.zoomType,\n\t\t\tinverted = chart.inverted,\n\t\t\tzoomX,\n\t\t\tzoomY;\n\n\t\t// Store references\n\t\tthis.options = options;\n\t\tthis.chart = chart;\n\t\t\n\t\t// Zoom status\n\t\tthis.zoomX = zoomX = /x/.test(zoomType);\n\t\tthis.zoomY = zoomY = /y/.test(zoomType);\n\t\tthis.zoomHor = (zoomX && !inverted) || (zoomY && inverted);\n\t\tthis.zoomVert = (zoomY && !inverted) || (zoomX && inverted);\n\n\t\t// Do we need to handle click on a touch device?\n\t\tthis.runChartClick = chartEvents && !!chartEvents.click;\n\n\t\tthis.pinchDown = [];\n\t\tthis.lastValidTouch = {};\n\n\t\tif (options.tooltip.enabled) {\n\t\t\tchart.tooltip = new Tooltip(chart, options.tooltip);\n\t\t}\n\n\t\tthis.setDOMEvents();\n\t}, \n\n\t/**\n\t * Add crossbrowser support for chartX and chartY\n\t * @param {Object} e The event object in standard browsers\n\t */\n\tnormalize: function (e, chartPosition) {\n\t\tvar chartX,\n\t\t\tchartY,\n\t\t\tePos;\n\n\t\t// common IE normalizing\n\t\te = e || win.event;\n\t\tif (!e.target) {\n\t\t\te.target = e.srcElement;\n\t\t}\n\n\t\t// Framework specific normalizing (#1165)\n\t\te = washMouseEvent(e);\n\t\t\n\t\t// iOS\n\t\tePos = e.touches ? e.touches.item(0) : e;\n\n\t\t// Get mouse position\n\t\tif (!chartPosition) {\n\t\t\tthis.chartPosition = chartPosition = offset(this.chart.container);\n\t\t}\n\n\t\t// chartX and chartY\n\t\tif (ePos.pageX === UNDEFINED) { // IE < 9. #886.\n\t\t\tchartX = mathMax(e.x, e.clientX - chartPosition.left); // #2005, #2129: the second case is \n\t\t\t\t// for IE10 quirks mode within framesets\n\t\t\tchartY = e.y;\n\t\t} else {\n\t\t\tchartX = ePos.pageX - chartPosition.left;\n\t\t\tchartY = ePos.pageY - chartPosition.top;\n\t\t}\n\n\t\treturn extend(e, {\n\t\t\tchartX: mathRound(chartX),\n\t\t\tchartY: mathRound(chartY)\n\t\t});\n\t},\n\n\t/**\n\t * Get the click position in terms of axis values.\n\t *\n\t * @param {Object} e A pointer event\n\t */\n\tgetCoordinates: function (e) {\n\t\tvar coordinates = {\n\t\t\t\txAxis: [],\n\t\t\t\tyAxis: []\n\t\t\t};\n\n\t\teach(this.chart.axes, function (axis) {\n\t\t\tcoordinates[axis.isXAxis ? 'xAxis' : 'yAxis'].push({\n\t\t\t\taxis: axis,\n\t\t\t\tvalue: axis.toValue(e[axis.horiz ? 'chartX' : 'chartY'])\n\t\t\t});\n\t\t});\n\t\treturn coordinates;\n\t},\n\t\n\t/**\n\t * Return the index in the tooltipPoints array, corresponding to pixel position in \n\t * the plot area.\n\t */\n\tgetIndex: function (e) {\n\t\tvar chart = this.chart;\n\t\treturn chart.inverted ? \n\t\t\tchart.plotHeight + chart.plotTop - e.chartY : \n\t\t\te.chartX - chart.plotLeft;\n\t},\n\n\t/**\n\t * With line type charts with a single tracker, get the point closest to the mouse.\n\t * Run Point.onMouseOver and display tooltip for the point or points.\n\t */\n\trunPointActions: function (e) {\n\t\tvar pointer = this,\n\t\t\tchart = pointer.chart,\n\t\t\tseries = chart.series,\n\t\t\ttooltip = chart.tooltip,\n\t\t\tpoint,\n\t\t\tpoints,\n\t\t\thoverPoint = chart.hoverPoint,\n\t\t\thoverSeries = chart.hoverSeries,\n\t\t\ti,\n\t\t\tj,\n\t\t\tdistance = chart.chartWidth,\n\t\t\tindex = pointer.getIndex(e),\n\t\t\tanchor;\n\n\t\t// shared tooltip\n\t\tif (tooltip && pointer.options.tooltip.shared && !(hoverSeries && hoverSeries.noSharedTooltip)) {\n\t\t\tpoints = [];\n\n\t\t\t// loop over all series and find the ones with points closest to the mouse\n\t\t\ti = series.length;\n\t\t\tfor (j = 0; j < i; j++) {\n\t\t\t\tif (series[j].visible &&\n\t\t\t\t\t\tseries[j].options.enableMouseTracking !== false &&\n\t\t\t\t\t\t!series[j].noSharedTooltip && series[j].tooltipPoints.length) {\n\t\t\t\t\tpoint = series[j].tooltipPoints[index];\n\t\t\t\t\tif (point && point.series) { // not a dummy point, #1544\n\t\t\t\t\t\tpoint._dist = mathAbs(index - point.clientX);\n\t\t\t\t\t\tdistance = mathMin(distance, point._dist);\n\t\t\t\t\t\tpoints.push(point);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t// remove furthest points\n\t\t\ti = points.length;\n\t\t\twhile (i--) {\n\t\t\t\tif (points[i]._dist > distance) {\n\t\t\t\t\tpoints.splice(i, 1);\n\t\t\t\t}\n\t\t\t}\n\t\t\t// refresh the tooltip if necessary\n\t\t\tif (points.length && (points[0].clientX !== pointer.hoverX)) {\n\t\t\t\ttooltip.refresh(points, e);\n\t\t\t\tpointer.hoverX = points[0].clientX;\n\t\t\t}\n\t\t}\n\n\t\t// separate tooltip and general mouse events\n\t\tif (hoverSeries && hoverSeries.tracker) { // only use for line-type series with common tracker\n\n\t\t\t// get the point\n\t\t\tpoint = hoverSeries.tooltipPoints[index];\n\n\t\t\t// a new point is hovered, refresh the tooltip\n\t\t\tif (point && point !== hoverPoint) {\n\n\t\t\t\t// trigger the events\n\t\t\t\tpoint.onMouseOver(e);\n\n\t\t\t}\n\t\t\t\n\t\t} else if (tooltip && tooltip.followPointer && !tooltip.isHidden) {\n\t\t\tanchor = tooltip.getAnchor([{}], e);\n\t\t\ttooltip.updatePosition({ plotX: anchor[0], plotY: anchor[1] });\n\t\t}\n\t},\n\n\n\n\t/**\n\t * Reset the tracking by hiding the tooltip, the hover series state and the hover point\n\t * \n\t * @param allowMove {Boolean} Instead of destroying the tooltip altogether, allow moving it if possible\n\t */\n\treset: function (allowMove) {\n\t\tvar pointer = this,\n\t\t\tchart = pointer.chart,\n\t\t\thoverSeries = chart.hoverSeries,\n\t\t\thoverPoint = chart.hoverPoint,\n\t\t\ttooltip = chart.tooltip,\n\t\t\ttooltipPoints = tooltip && tooltip.shared ? chart.hoverPoints : hoverPoint;\n\t\t\t\n\t\t// Narrow in allowMove\n\t\tallowMove = allowMove && tooltip && tooltipPoints;\n\t\t\t\n\t\t// Check if the points have moved outside the plot area, #1003\n\t\tif (allowMove && splat(tooltipPoints)[0].plotX === UNDEFINED) {\n\t\t\tallowMove = false;\n\t\t}\t\n\n\t\t// Just move the tooltip, #349\n\t\tif (allowMove) {\n\t\t\ttooltip.refresh(tooltipPoints);\n\n\t\t// Full reset\n\t\t} else {\n\n\t\t\tif (hoverPoint) {\n\t\t\t\thoverPoint.onMouseOut();\n\t\t\t}\n\n\t\t\tif (hoverSeries) {\n\t\t\t\thoverSeries.onMouseOut();\n\t\t\t}\n\n\t\t\tif (tooltip) {\n\t\t\t\ttooltip.hide();\n\t\t\t\ttooltip.hideCrosshairs();\n\t\t\t}\n\n\t\t\tpointer.hoverX = null;\n\n\t\t}\n\t},\n\n\t/**\n\t * Scale series groups to a certain scale and translation\n\t */\n\tscaleGroups: function (attribs, clip) {\n\n\t\tvar chart = this.chart,\n\t\t\tseriesAttribs;\n\n\t\t// Scale each series\n\t\teach(chart.series, function (series) {\n\t\t\tseriesAttribs = attribs || series.getPlotBox(); // #1701\n\t\t\tif (series.xAxis && series.xAxis.zoomEnabled) {\n\t\t\t\tseries.group.attr(seriesAttribs);\n\t\t\t\tif (series.markerGroup) {\n\t\t\t\t\tseries.markerGroup.attr(seriesAttribs);\n\t\t\t\t\tseries.markerGroup.clip(clip ? chart.clipRect : null);\n\t\t\t\t}\n\t\t\t\tif (series.dataLabelsGroup) {\n\t\t\t\t\tseries.dataLabelsGroup.attr(seriesAttribs);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\t\n\t\t// Clip\n\t\tchart.clipRect.attr(clip || chart.clipBox);\n\t},\n\n\t/**\n\t * Run translation operations for each direction (horizontal and vertical) independently\n\t */\n\tpinchTranslateDirection: function (horiz, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch) {\n\t\tvar chart = this.chart,\n\t\t\txy = horiz ? 'x' : 'y',\n\t\t\tXY = horiz ? 'X' : 'Y',\n\t\t\tsChartXY = 'chart' + XY,\n\t\t\twh = horiz ? 'width' : 'height',\n\t\t\tplotLeftTop = chart['plot' + (horiz ? 'Left' : 'Top')],\n\t\t\tselectionWH,\n\t\t\tselectionXY,\n\t\t\tclipXY,\n\t\t\tscale = 1,\n\t\t\tinverted = chart.inverted,\n\t\t\tbounds = chart.bounds[horiz ? 'h' : 'v'],\n\t\t\tsingleTouch = pinchDown.length === 1,\n\t\t\ttouch0Start = pinchDown[0][sChartXY],\n\t\t\ttouch0Now = touches[0][sChartXY],\n\t\t\ttouch1Start = !singleTouch && pinchDown[1][sChartXY],\n\t\t\ttouch1Now = !singleTouch && touches[1][sChartXY],\n\t\t\toutOfBounds,\n\t\t\ttransformScale,\n\t\t\tscaleKey,\n\t\t\tsetScale = function () {\n\t\t\t\tif (!singleTouch && mathAbs(touch0Start - touch1Start) > 20) { // Don't zoom if fingers are too close on this axis\n\t\t\t\t\tscale = mathAbs(touch0Now - touch1Now) / mathAbs(touch0Start - touch1Start);\t\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tclipXY = ((plotLeftTop - touch0Now) / scale) + touch0Start;\n\t\t\t\tselectionWH = chart['plot' + (horiz ? 'Width' : 'Height')] / scale;\n\t\t\t};\n\n\t\t// Set the scale, first pass\n\t\tsetScale();\n\n\t\tselectionXY = clipXY; // the clip position (x or y) is altered if out of bounds, the selection position is not\n\n\t\t// Out of bounds\n\t\tif (selectionXY < bounds.min) {\n\t\t\tselectionXY = bounds.min;\n\t\t\toutOfBounds = true;\n\t\t} else if (selectionXY + selectionWH > bounds.max) {\n\t\t\tselectionXY = bounds.max - selectionWH;\n\t\t\toutOfBounds = true;\n\t\t}\n\t\t\n\t\t// Is the chart dragged off its bounds, determined by dataMin and dataMax?\n\t\tif (outOfBounds) {\n\n\t\t\t// Modify the touchNow position in order to create an elastic drag movement. This indicates\n\t\t\t// to the user that the chart is responsive but can't be dragged further.\n\t\t\ttouch0Now -= 0.8 * (touch0Now - lastValidTouch[xy][0]);\n\t\t\tif (!singleTouch) {\n\t\t\t\ttouch1Now -= 0.8 * (touch1Now - lastValidTouch[xy][1]);\n\t\t\t}\n\n\t\t\t// Set the scale, second pass to adapt to the modified touchNow positions\n\t\t\tsetScale();\n\n\t\t} else {\n\t\t\tlastValidTouch[xy] = [touch0Now, touch1Now];\n\t\t}\n\n\t\t\n\t\t// Set geometry for clipping, selection and transformation\n\t\tif (!inverted) { // TODO: implement clipping for inverted charts\n\t\t\tclip[xy] = clipXY - plotLeftTop;\n\t\t\tclip[wh] = selectionWH;\n\t\t}\n\t\tscaleKey = inverted ? (horiz ? 'scaleY' : 'scaleX') : 'scale' + XY;\n\t\ttransformScale = inverted ? 1 / scale : scale;\n\n\t\tselectionMarker[wh] = selectionWH;\n\t\tselectionMarker[xy] = selectionXY;\n\t\ttransform[scaleKey] = scale;\n\t\ttransform['translate' + XY] = (transformScale * plotLeftTop) + (touch0Now - (transformScale * touch0Start));\n\t},\n\t\n\t/**\n\t * Handle touch events with two touches\n\t */\n\tpinch: function (e) {\n\n\t\tvar self = this,\n\t\t\tchart = self.chart,\n\t\t\tpinchDown = self.pinchDown,\n\t\t\tfollowTouchMove = chart.tooltip && chart.tooltip.options.followTouchMove,\n\t\t\ttouches = e.touches,\n\t\t\ttouchesLength = touches.length,\n\t\t\tlastValidTouch = self.lastValidTouch,\n\t\t\tzoomHor = self.zoomHor || self.pinchHor,\n\t\t\tzoomVert = self.zoomVert || self.pinchVert,\n\t\t\thasZoom = zoomHor || zoomVert,\n\t\t\tselectionMarker = self.selectionMarker,\n\t\t\ttransform = {},\n\t\t\tfireClickEvent = touchesLength === 1 && ((self.inClass(e.target, PREFIX + 'tracker') && \n\t\t\t\tchart.runTrackerClick) || chart.runChartClick),\n\t\t\tclip = {};\n\n\t\t// On touch devices, only proceed to trigger click if a handler is defined\n\t\tif ((hasZoom || followTouchMove) && !fireClickEvent) {\n\t\t\te.preventDefault();\n\t\t}\n\t\t\n\t\t// Normalize each touch\n\t\tmap(touches, function (e) {\n\t\t\treturn self.normalize(e);\n\t\t});\n\t\t\t\n\t\t// Register the touch start position\n\t\tif (e.type === 'touchstart') {\n\t\t\teach(touches, function (e, i) {\n\t\t\t\tpinchDown[i] = { chartX: e.chartX, chartY: e.chartY };\n\t\t\t});\n\t\t\tlastValidTouch.x = [pinchDown[0].chartX, pinchDown[1] && pinchDown[1].chartX];\n\t\t\tlastValidTouch.y = [pinchDown[0].chartY, pinchDown[1] && pinchDown[1].chartY];\n\n\t\t\t// Identify the data bounds in pixels\n\t\t\teach(chart.axes, function (axis) {\n\t\t\t\tif (axis.zoomEnabled) {\n\t\t\t\t\tvar bounds = chart.bounds[axis.horiz ? 'h' : 'v'],\n\t\t\t\t\t\tminPixelPadding = axis.minPixelPadding,\n\t\t\t\t\t\tmin = axis.toPixels(axis.dataMin),\n\t\t\t\t\t\tmax = axis.toPixels(axis.dataMax),\n\t\t\t\t\t\tabsMin = mathMin(min, max),\n\t\t\t\t\t\tabsMax = mathMax(min, max);\n\n\t\t\t\t\t// Store the bounds for use in the touchmove handler\n\t\t\t\t\tbounds.min = mathMin(axis.pos, absMin - minPixelPadding);\n\t\t\t\t\tbounds.max = mathMax(axis.pos + axis.len, absMax + minPixelPadding);\n\t\t\t\t}\n\t\t\t});\n\t\t\n\t\t// Event type is touchmove, handle panning and pinching\n\t\t} else if (pinchDown.length) { // can be 0 when releasing, if touchend fires first\n\t\t\t\n\n\t\t\t// Set the marker\n\t\t\tif (!selectionMarker) {\n\t\t\t\tself.selectionMarker = selectionMarker = extend({\n\t\t\t\t\tdestroy: noop\n\t\t\t\t}, chart.plotBox);\n\t\t\t}\n\n\t\t\t\n\n\t\t\tif (zoomHor) {\n\t\t\t\tself.pinchTranslateDirection(true, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch);\n\t\t\t}\n\t\t\tif (zoomVert) {\n\t\t\t\tself.pinchTranslateDirection(false, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch);\n\t\t\t}\n\n\t\t\tself.hasPinched = hasZoom;\n\n\t\t\t// Scale and translate the groups to provide visual feedback during pinching\n\t\t\tself.scaleGroups(transform, clip);\n\t\t\t\n\t\t\t// Optionally move the tooltip on touchmove\n\t\t\tif (!hasZoom && followTouchMove && touchesLength === 1) {\n\t\t\t\tthis.runPointActions(self.normalize(e));\n\t\t\t}\n\t\t}\n\t},\n\n\t/**\n\t * Start a drag operation\n\t */\n\tdragStart: function (e) {\n\t\tvar chart = this.chart;\n\n\t\t// Record the start position\n\t\tchart.mouseIsDown = e.type;\n\t\tchart.cancelClick = false;\n\t\tchart.mouseDownX = this.mouseDownX = e.chartX;\n\t\tchart.mouseDownY = this.mouseDownY = e.chartY;\n\t},\n\n\t/**\n\t * Perform a drag operation in response to a mousemove event while the mouse is down\n\t */\n\tdrag: function (e) {\n\n\t\tvar chart = this.chart,\n\t\t\tchartOptions = chart.options.chart,\n\t\t\tchartX = e.chartX,\n\t\t\tchartY = e.chartY,\n\t\t\tzoomHor = this.zoomHor,\n\t\t\tzoomVert = this.zoomVert,\n\t\t\tplotLeft = chart.plotLeft,\n\t\t\tplotTop = chart.plotTop,\n\t\t\tplotWidth = chart.plotWidth,\n\t\t\tplotHeight = chart.plotHeight,\n\t\t\tclickedInside,\n\t\t\tsize,\n\t\t\tmouseDownX = this.mouseDownX,\n\t\t\tmouseDownY = this.mouseDownY;\n\n\t\t// If the mouse is outside the plot area, adjust to cooordinates\n\t\t// inside to prevent the selection marker from going outside\n\t\tif (chartX < plotLeft) {\n\t\t\tchartX = plotLeft;\n\t\t} else if (chartX > plotLeft + plotWidth) {\n\t\t\tchartX = plotLeft + plotWidth;\n\t\t}\n\n\t\tif (chartY < plotTop) {\n\t\t\tchartY = plotTop;\n\t\t} else if (chartY > plotTop + plotHeight) {\n\t\t\tchartY = plotTop + plotHeight;\n\t\t}\n\t\t\n\t\t// determine if the mouse has moved more than 10px\n\t\tthis.hasDragged = Math.sqrt(\n\t\t\tMath.pow(mouseDownX - chartX, 2) +\n\t\t\tMath.pow(mouseDownY - chartY, 2)\n\t\t);\n\t\tif (this.hasDragged > 10) {\n\t\t\tclickedInside = chart.isInsidePlot(mouseDownX - plotLeft, mouseDownY - plotTop);\n\n\t\t\t// make a selection\n\t\t\tif (chart.hasCartesianSeries && (this.zoomX || this.zoomY) && clickedInside) {\n\t\t\t\tif (!this.selectionMarker) {\n\t\t\t\t\tthis.selectionMarker = chart.renderer.rect(\n\t\t\t\t\t\tplotLeft,\n\t\t\t\t\t\tplotTop,\n\t\t\t\t\t\tzoomHor ? 1 : plotWidth,\n\t\t\t\t\t\tzoomVert ? 1 : plotHeight,\n\t\t\t\t\t\t0\n\t\t\t\t\t)\n\t\t\t\t\t.attr({\n\t\t\t\t\t\tfill: chartOptions.selectionMarkerFill || 'rgba(69,114,167,0.25)',\n\t\t\t\t\t\tzIndex: 7\n\t\t\t\t\t})\n\t\t\t\t\t.add();\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// adjust the width of the selection marker\n\t\t\tif (this.selectionMarker && zoomHor) {\n\t\t\t\tsize = chartX - mouseDownX;\n\t\t\t\tthis.selectionMarker.attr({\n\t\t\t\t\twidth: mathAbs(size),\n\t\t\t\t\tx: (size > 0 ? 0 : size) + mouseDownX\n\t\t\t\t});\n\t\t\t}\n\t\t\t// adjust the height of the selection marker\n\t\t\tif (this.selectionMarker && zoomVert) {\n\t\t\t\tsize = chartY - mouseDownY;\n\t\t\t\tthis.selectionMarker.attr({\n\t\t\t\t\theight: mathAbs(size),\n\t\t\t\t\ty: (size > 0 ? 0 : size) + mouseDownY\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// panning\n\t\t\tif (clickedInside && !this.selectionMarker && chartOptions.panning) {\n\t\t\t\tchart.pan(e, chartOptions.panning);\n\t\t\t}\n\t\t}\n\t},\n\n\t/**\n\t * On mouse up or touch end across the entire document, drop the selection.\n\t */\n\tdrop: function (e) {\n\t\tvar chart = this.chart,\n\t\t\thasPinched = this.hasPinched;\n\n\t\tif (this.selectionMarker) {\n\t\t\tvar selectionData = {\n\t\t\t\t\txAxis: [],\n\t\t\t\t\tyAxis: [],\n\t\t\t\t\toriginalEvent: e.originalEvent || e\n\t\t\t\t},\n\t\t\t\tselectionBox = this.selectionMarker,\n\t\t\t\tselectionLeft = selectionBox.x,\n\t\t\t\tselectionTop = selectionBox.y,\n\t\t\t\trunZoom;\n\t\t\t// a selection has been made\n\t\t\tif (this.hasDragged || hasPinched) {\n\n\t\t\t\t// record each axis' min and max\n\t\t\t\teach(chart.axes, function (axis) {\n\t\t\t\t\tif (axis.zoomEnabled) {\n\t\t\t\t\t\tvar horiz = axis.horiz,\n\t\t\t\t\t\t\tselectionMin = axis.toValue((horiz ? selectionLeft : selectionTop)),\n\t\t\t\t\t\t\tselectionMax = axis.toValue((horiz ? selectionLeft + selectionBox.width : selectionTop + selectionBox.height));\n\n\t\t\t\t\t\tif (!isNaN(selectionMin) && !isNaN(selectionMax)) { // #859\n\t\t\t\t\t\t\tselectionData[axis.xOrY + 'Axis'].push({\n\t\t\t\t\t\t\t\taxis: axis,\n\t\t\t\t\t\t\t\tmin: mathMin(selectionMin, selectionMax), // for reversed axes,\n\t\t\t\t\t\t\t\tmax: mathMax(selectionMin, selectionMax)\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\trunZoom = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\tif (runZoom) {\n\t\t\t\t\tfireEvent(chart, 'selection', selectionData, function (args) { \n\t\t\t\t\t\tchart.zoom(extend(args, hasPinched ? { animation: false } : null)); \n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t}\n\t\t\tthis.selectionMarker = this.selectionMarker.destroy();\n\n\t\t\t// Reset scaling preview\n\t\t\tif (hasPinched) {\n\t\t\t\tthis.scaleGroups();\n\t\t\t}\n\t\t}\n\n\t\t// Reset all\n\t\tif (chart) { // it may be destroyed on mouse up - #877\n\t\t\tcss(chart.container, { cursor: chart._cursor });\n\t\t\tchart.cancelClick = this.hasDragged > 10; // #370\n\t\t\tchart.mouseIsDown = this.hasDragged = this.hasPinched = false;\n\t\t\tthis.pinchDown = [];\n\t\t}\n\t},\n\n\tonContainerMouseDown: function (e) {\n\n\t\te = this.normalize(e);\n\n\t\t// issue #295, dragging not always working in Firefox\n\t\tif (e.preventDefault) {\n\t\t\te.preventDefault();\n\t\t}\n\t\t\n\t\tthis.dragStart(e);\n\t},\n\n\t\n\n\tonDocumentMouseUp: function (e) {\n\t\tthis.drop(e);\n\t},\n\n\t/**\n\t * Special handler for mouse move that will hide the tooltip when the mouse leaves the plotarea.\n\t * Issue #149 workaround. The mouseleave event does not always fire. \n\t */\n\tonDocumentMouseMove: function (e) {\n\t\tvar chart = this.chart,\n\t\t\tchartPosition = this.chartPosition,\n\t\t\thoverSeries = chart.hoverSeries;\n\n\t\te = this.normalize(e, chartPosition);\n\n\t\t// If we're outside, hide the tooltip\n\t\tif (chartPosition && hoverSeries && !this.inClass(e.target, 'highcharts-tracker') &&\n\t\t\t\t!chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) {\n\t\t\tthis.reset();\n\t\t}\n\t},\n\n\t/**\n\t * When mouse leaves the container, hide the tooltip.\n\t */\n\tonContainerMouseLeave: function () {\n\t\tthis.reset();\n\t\tthis.chartPosition = null; // also reset the chart position, used in #149 fix\n\t},\n\n\t// The mousemove, touchmove and touchstart event handler\n\tonContainerMouseMove: function (e) {\n\n\t\tvar chart = this.chart;\n\n\t\t// normalize\n\t\te = this.normalize(e);\n\n\t\t// #295\n\t\te.returnValue = false;\n\t\t\n\t\t\n\t\tif (chart.mouseIsDown === 'mousedown') {\n\t\t\tthis.drag(e);\n\t\t} \n\t\t\n\t\t// Show the tooltip and run mouse over events (#977)\n\t\tif ((this.inClass(e.target, 'highcharts-tracker') || \n\t\t\t\tchart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) && !chart.openMenu) {\n\t\t\tthis.runPointActions(e);\n\t\t}\n\t},\n\n\t/**\n\t * Utility to detect whether an element has, or has a parent with, a specific\n\t * class name. Used on detection of tracker objects and on deciding whether\n\t * hovering the tooltip should cause the active series to mouse out.\n\t */\n\tinClass: function (element, className) {\n\t\tvar elemClassName;\n\t\twhile (element) {\n\t\t\telemClassName = attr(element, 'class');\n\t\t\tif (elemClassName) {\n\t\t\t\tif (elemClassName.indexOf(className) !== -1) {\n\t\t\t\t\treturn true;\n\t\t\t\t} else if (elemClassName.indexOf(PREFIX + 'container') !== -1) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\telement = element.parentNode;\n\t\t}\t\t\n\t},\n\n\tonTrackerMouseOut: function (e) {\n\t\tvar series = this.chart.hoverSeries;\n\t\tif (series && !series.options.stickyTracking && !this.inClass(e.toElement || e.relatedTarget, PREFIX + 'tooltip')) {\n\t\t\tseries.onMouseOut();\n\t\t}\n\t},\n\n\tonContainerClick: function (e) {\n\t\tvar chart = this.chart,\n\t\t\thoverPoint = chart.hoverPoint, \n\t\t\tplotLeft = chart.plotLeft,\n\t\t\tplotTop = chart.plotTop,\n\t\t\tinverted = chart.inverted,\n\t\t\tchartPosition,\n\t\t\tplotX,\n\t\t\tplotY;\n\t\t\n\t\te = this.normalize(e);\n\t\te.cancelBubble = true; // IE specific\n\n\t\tif (!chart.cancelClick) {\n\t\t\t\n\t\t\t// On tracker click, fire the series and point events. #783, #1583\n\t\t\tif (hoverPoint && this.inClass(e.target, PREFIX + 'tracker')) {\n\t\t\t\tchartPosition = this.chartPosition;\n\t\t\t\tplotX = hoverPoint.plotX;\n\t\t\t\tplotY = hoverPoint.plotY;\n\n\t\t\t\t// add page position info\n\t\t\t\textend(hoverPoint, {\n\t\t\t\t\tpageX: chartPosition.left + plotLeft +\n\t\t\t\t\t\t(inverted ? chart.plotWidth - plotY : plotX),\n\t\t\t\t\tpageY: chartPosition.top + plotTop +\n\t\t\t\t\t\t(inverted ? chart.plotHeight - plotX : plotY)\n\t\t\t\t});\n\t\t\t\n\t\t\t\t// the series click event\n\t\t\t\tfireEvent(hoverPoint.series, 'click', extend(e, {\n\t\t\t\t\tpoint: hoverPoint\n\t\t\t\t}));\n\n\t\t\t\t// the point click event\n\t\t\t\tif (chart.hoverPoint) { // it may be destroyed (#1844)\n\t\t\t\t\thoverPoint.firePointEvent('click', e);\n\t\t\t\t}\n\n\t\t\t// When clicking outside a tracker, fire a chart event\n\t\t\t} else {\n\t\t\t\textend(e, this.getCoordinates(e));\n\n\t\t\t\t// fire a click event in the chart\n\t\t\t\tif (chart.isInsidePlot(e.chartX - plotLeft, e.chartY - plotTop)) {\n\t\t\t\t\tfireEvent(chart, 'click', e);\n\t\t\t\t}\n\t\t\t}\n\n\n\t\t}\n\t},\n\n\tonContainerTouchStart: function (e) {\n\t\tvar chart = this.chart;\n\n\t\tif (e.touches.length === 1) {\n\n\t\t\te = this.normalize(e);\n\n\t\t\tif (chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) {\n\n\t\t\t\t// Prevent the click pseudo event from firing unless it is set in the options\n\t\t\t\t/*if (!chart.runChartClick) {\n\t\t\t\t\te.preventDefault();\n\t\t\t\t}*/\n\t\t\t\n\t\t\t\t// Run mouse events and display tooltip etc\n\t\t\t\tthis.runPointActions(e);\n\n\t\t\t\tthis.pinch(e);\n\n\t\t\t} else {\n\t\t\t\t// Hide the tooltip on touching outside the plot area (#1203)\n\t\t\t\tthis.reset();\n\t\t\t}\n\n\t\t} else if (e.touches.length === 2) {\n\t\t\tthis.pinch(e);\n\t\t}\t\t\n\t},\n\n\tonContainerTouchMove: function (e) {\n\t\tif (e.touches.length === 1 || e.touches.length === 2) {\n\t\t\tthis.pinch(e);\n\t\t}\n\t},\n\n\tonDocumentTouchEnd: function (e) {\n\t\tthis.drop(e);\n\t},\n\n\t/**\n\t * Set the JS DOM events on the container and document. This method should contain\n\t * a one-to-one assignment between methods and their handlers. Any advanced logic should\n\t * be moved to the handler reflecting the event's name.\n\t */\n\tsetDOMEvents: function () {\n\n\t\tvar pointer = this,\n\t\t\tcontainer = pointer.chart.container,\n\t\t\tevents;\n\n\t\tthis._events = events = [\n\t\t\t[container, 'onmousedown', 'onContainerMouseDown'],\n\t\t\t[container, 'onmousemove', 'onContainerMouseMove'],\n\t\t\t[container, 'onclick', 'onContainerClick'],\n\t\t\t[container, 'mouseleave', 'onContainerMouseLeave'],\n\t\t\t[doc, 'mousemove', 'onDocumentMouseMove'],\n\t\t\t[doc, 'mouseup', 'onDocumentMouseUp']\n\t\t];\n\n\t\tif (hasTouch) {\n\t\t\tevents.push(\n\t\t\t\t[container, 'ontouchstart', 'onContainerTouchStart'],\n\t\t\t\t[container, 'ontouchmove', 'onContainerTouchMove'],\n\t\t\t\t[doc, 'touchend', 'onDocumentTouchEnd']\n\t\t\t);\n\t\t}\n\n\t\teach(events, function (eventConfig) {\n\n\t\t\t// First, create the callback function that in turn calls the method on Pointer\n\t\t\tpointer['_' + eventConfig[2]] = function (e) {\n\t\t\t\tpointer[eventConfig[2]](e);\n\t\t\t};\n\n\t\t\t// Now attach the function, either as a direct property or through addEvent\n\t\t\tif (eventConfig[1].indexOf('on') === 0) {\n\t\t\t\teventConfig[0][eventConfig[1]] = pointer['_' + eventConfig[2]];\n\t\t\t} else {\n\t\t\t\taddEvent(eventConfig[0], eventConfig[1], pointer['_' + eventConfig[2]]);\n\t\t\t}\n\t\t});\n\n\t\t\n\t},\n\n\t/**\n\t * Destroys the Pointer object and disconnects DOM events.\n\t */\n\tdestroy: function () {\n\t\tvar pointer = this;\n\n\t\t// Release all DOM events\n\t\teach(pointer._events, function (eventConfig) {\t\n\t\t\tif (eventConfig[1].indexOf('on') === 0) {\n\t\t\t\teventConfig[0][eventConfig[1]] = null; // delete breaks oldIE\n\t\t\t} else {\t\t\n\t\t\t\tremoveEvent(eventConfig[0], eventConfig[1], pointer['_' + eventConfig[2]]);\n\t\t\t}\n\t\t});\n\t\tdelete pointer._events;\n\n\t\t// memory and CPU leak\n\t\tclearInterval(pointer.tooltipTimeout);\n\t}\n};\n/**\n * The overview of the chart's series\n */\nfunction Legend(chart, options) {\n\tthis.init(chart, options);\n}\n\nLegend.prototype = {\n\t\n\t/**\n\t * Initialize the legend\n\t */\n\tinit: function (chart, options) {\n\t\t\n\t\tvar legend = this,\n\t\t\titemStyle = options.itemStyle,\n\t\t\tpadding = pick(options.padding, 8),\n\t\t\titemMarginTop = options.itemMarginTop || 0;\n\t\n\t\tthis.options = options;\n\n\t\tif (!options.enabled) {\n\t\t\treturn;\n\t\t}\n\t\n\t\tlegend.baseline = pInt(itemStyle.fontSize) + 3 + itemMarginTop; // used in Series prototype\n\t\tlegend.itemStyle = itemStyle;\n\t\tlegend.itemHiddenStyle = merge(itemStyle, options.itemHiddenStyle);\n\t\tlegend.itemMarginTop = itemMarginTop;\n\t\tlegend.padding = padding;\n\t\tlegend.initialItemX = padding;\n\t\tlegend.initialItemY = padding - 5; // 5 is the number of pixels above the text\n\t\tlegend.maxItemWidth = 0;\n\t\tlegend.chart = chart;\n\t\tlegend.itemHeight = 0;\n\t\tlegend.lastLineHeight = 0;\n\n\t\t// Render it\n\t\tlegend.render();\n\n\t\t// move checkboxes\n\t\taddEvent(legend.chart, 'endResize', function () { \n\t\t\tlegend.positionCheckboxes();\n\t\t});\n\n\t},\n\n\t/**\n\t * Set the colors for the legend item\n\t * @param {Object} item A Series or Point instance\n\t * @param {Object} visible Dimmed or colored\n\t */\n\tcolorizeItem: function (item, visible) {\n\t\tvar legend = this,\n\t\t\toptions = legend.options,\n\t\t\tlegendItem = item.legendItem,\n\t\t\tlegendLine = item.legendLine,\n\t\t\tlegendSymbol = item.legendSymbol,\n\t\t\thiddenColor = legend.itemHiddenStyle.color,\n\t\t\ttextColor = visible ? options.itemStyle.color : hiddenColor,\n\t\t\tsymbolColor = visible ? item.color : hiddenColor,\n\t\t\tmarkerOptions = item.options && item.options.marker,\n\t\t\tsymbolAttr = {\n\t\t\t\tstroke: symbolColor,\n\t\t\t\tfill: symbolColor\n\t\t\t},\n\t\t\tkey,\n\t\t\tval;\n\t\t\n\t\tif (legendItem) {\n\t\t\tlegendItem.css({ fill: textColor, color: textColor }); // color for #1553, oldIE\n\t\t}\n\t\tif (legendLine) {\n\t\t\tlegendLine.attr({ stroke: symbolColor });\n\t\t}\n\t\t\n\t\tif (legendSymbol) {\n\t\t\t\n\t\t\t// Apply marker options\n\t\t\tif (markerOptions && legendSymbol.isMarker) { // #585\n\t\t\t\tmarkerOptions = item.convertAttribs(markerOptions);\n\t\t\t\tfor (key in markerOptions) {\n\t\t\t\t\tval = markerOptions[key];\n\t\t\t\t\tif (val !== UNDEFINED) {\n\t\t\t\t\t\tsymbolAttr[key] = val;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlegendSymbol.attr(symbolAttr);\n\t\t}\n\t},\n\n\t/**\n\t * Position the legend item\n\t * @param {Object} item A Series or Point instance\n\t */\n\tpositionItem: function (item) {\n\t\tvar legend = this,\n\t\t\toptions = legend.options,\n\t\t\tsymbolPadding = options.symbolPadding,\n\t\t\tltr = !options.rtl,\n\t\t\tlegendItemPos = item._legendItemPos,\n\t\t\titemX = legendItemPos[0],\n\t\t\titemY = legendItemPos[1],\n\t\t\tcheckbox = item.checkbox;\n\n\t\tif (item.legendGroup) {\n\t\t\titem.legendGroup.translate(\n\t\t\t\tltr ? itemX : legend.legendWidth - itemX - 2 * symbolPadding - 4,\n\t\t\t\titemY\n\t\t\t);\n\t\t}\n\n\t\tif (checkbox) {\n\t\t\tcheckbox.x = itemX;\n\t\t\tcheckbox.y = itemY;\n\t\t}\n\t},\n\n\t/**\n\t * Destroy a single legend item\n\t * @param {Object} item The series or point\n\t */\n\tdestroyItem: function (item) {\n\t\tvar checkbox = item.checkbox;\n\n\t\t// destroy SVG elements\n\t\teach(['legendItem', 'legendLine', 'legendSymbol', 'legendGroup'], function (key) {\n\t\t\tif (item[key]) {\n\t\t\t\titem[key] = item[key].destroy();\n\t\t\t}\n\t\t});\n\n\t\tif (checkbox) {\n\t\t\tdiscardElement(item.checkbox);\n\t\t}\n\t},\n\n\t/**\n\t * Destroys the legend.\n\t */\n\tdestroy: function () {\n\t\tvar legend = this,\n\t\t\tlegendGroup = legend.group,\n\t\t\tbox = legend.box;\n\n\t\tif (box) {\n\t\t\tlegend.box = box.destroy();\n\t\t}\n\n\t\tif (legendGroup) {\n\t\t\tlegend.group = legendGroup.destroy();\n\t\t}\n\t},\n\n\t/**\n\t * Position the checkboxes after the width is determined\n\t */\n\tpositionCheckboxes: function (scrollOffset) {\n\t\tvar alignAttr = this.group.alignAttr,\n\t\t\ttranslateY,\n\t\t\tclipHeight = this.clipHeight || this.legendHeight;\n\n\t\tif (alignAttr) {\n\t\t\ttranslateY = alignAttr.translateY;\n\t\t\teach(this.allItems, function (item) {\n\t\t\t\tvar checkbox = item.checkbox,\n\t\t\t\t\ttop;\n\t\t\t\t\n\t\t\t\tif (checkbox) {\n\t\t\t\t\ttop = (translateY + checkbox.y + (scrollOffset || 0) + 3);\n\t\t\t\t\tcss(checkbox, {\n\t\t\t\t\t\tleft: (alignAttr.translateX + item.legendItemWidth + checkbox.x - 20) + PX,\n\t\t\t\t\t\ttop: top + PX,\n\t\t\t\t\t\tdisplay: top > translateY - 6 && top < translateY + clipHeight - 6 ? '' : NONE\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t},\n\t\n\t/**\n\t * Render the legend title on top of the legend\n\t */\n\trenderTitle: function () {\n\t\tvar options = this.options,\n\t\t\tpadding = this.padding,\n\t\t\ttitleOptions = options.title,\n\t\t\ttitleHeight = 0,\n\t\t\tbBox;\n\t\t\n\t\tif (titleOptions.text) {\n\t\t\tif (!this.title) {\n\t\t\t\tthis.title = this.chart.renderer.label(titleOptions.text, padding - 3, padding - 4, null, null, null, null, null, 'legend-title')\n\t\t\t\t\t.attr({ zIndex: 1 })\n\t\t\t\t\t.css(titleOptions.style)\n\t\t\t\t\t.add(this.group);\n\t\t\t}\n\t\t\tbBox = this.title.getBBox();\n\t\t\ttitleHeight = bBox.height;\n\t\t\tthis.offsetWidth = bBox.width; // #1717\n\t\t\tthis.contentGroup.attr({ translateY: titleHeight });\n\t\t}\n\t\tthis.titleHeight = titleHeight;\n\t},\n\n\t/**\n\t * Render a single specific legend item\n\t * @param {Object} item A series or point\n\t */\n\trenderItem: function (item) {\n\t\tvar legend = this,\n\t\t\tchart = legend.chart,\n\t\t\trenderer = chart.renderer,\n\t\t\toptions = legend.options,\n\t\t\thorizontal = options.layout === 'horizontal',\n\t\t\tsymbolWidth = options.symbolWidth,\n\t\t\tsymbolPadding = options.symbolPadding,\n\t\t\titemStyle = legend.itemStyle,\n\t\t\titemHiddenStyle = legend.itemHiddenStyle,\n\t\t\tpadding = legend.padding,\n\t\t\titemDistance = horizontal ? pick(options.itemDistance, 8) : 0,\n\t\t\tltr = !options.rtl,\n\t\t\titemHeight,\n\t\t\twidthOption = options.width,\n\t\t\titemMarginBottom = options.itemMarginBottom || 0,\n\t\t\titemMarginTop = legend.itemMarginTop,\n\t\t\tinitialItemX = legend.initialItemX,\n\t\t\tbBox,\n\t\t\titemWidth,\n\t\t\tli = item.legendItem,\n\t\t\tseries = item.series || item,\n\t\t\titemOptions = series.options,\n\t\t\tshowCheckbox = itemOptions.showCheckbox,\n\t\t\tuseHTML = options.useHTML;\n\n\t\tif (!li) { // generate it once, later move it\n\n\t\t\t// Generate the group box\n\t\t\t// A group to hold the symbol and text. Text is to be appended in Legend class.\n\t\t\titem.legendGroup = renderer.g('legend-item')\n\t\t\t\t.attr({ zIndex: 1 })\n\t\t\t\t.add(legend.scrollGroup);\n\n\t\t\t// Draw the legend symbol inside the group box\n\t\t\tseries.drawLegendSymbol(legend, item);\n\n\t\t\t// Generate the list item text and add it to the group\n\t\t\titem.legendItem = li = renderer.text(\n\t\t\t\t\toptions.labelFormat ? format(options.labelFormat, item) : options.labelFormatter.call(item),\n\t\t\t\t\tltr ? symbolWidth + symbolPadding : -symbolPadding,\n\t\t\t\t\tlegend.baseline,\n\t\t\t\t\tuseHTML\n\t\t\t\t)\n\t\t\t\t.css(merge(item.visible ? itemStyle : itemHiddenStyle)) // merge to prevent modifying original (#1021)\n\t\t\t\t.attr({\n\t\t\t\t\talign: ltr ? 'left' : 'right',\n\t\t\t\t\tzIndex: 2\n\t\t\t\t})\n\t\t\t\t.add(item.legendGroup);\n\n\t\t\t// Set the events on the item group, or in case of useHTML, the item itself (#1249)\n\t\t\t(useHTML ? li : item.legendGroup).on('mouseover', function () {\n\t\t\t\t\titem.setState(HOVER_STATE);\n\t\t\t\t\tli.css(legend.options.itemHoverStyle);\n\t\t\t\t})\n\t\t\t\t.on('mouseout', function () {\n\t\t\t\t\tli.css(item.visible ? itemStyle : itemHiddenStyle);\n\t\t\t\t\titem.setState();\n\t\t\t\t})\n\t\t\t\t.on('click', function (event) {\n\t\t\t\t\tvar strLegendItemClick = 'legendItemClick',\n\t\t\t\t\t\tfnLegendItemClick = function () {\n\t\t\t\t\t\t\titem.setVisible();\n\t\t\t\t\t\t};\n\t\t\t\t\t\t\n\t\t\t\t\t// Pass over the click/touch event. #4.\n\t\t\t\t\tevent = {\n\t\t\t\t\t\tbrowserEvent: event\n\t\t\t\t\t};\n\n\t\t\t\t\t// click the name or symbol\n\t\t\t\t\tif (item.firePointEvent) { // point\n\t\t\t\t\t\titem.firePointEvent(strLegendItemClick, event, fnLegendItemClick);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfireEvent(item, strLegendItemClick, event, fnLegendItemClick);\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t// Colorize the items\n\t\t\tlegend.colorizeItem(item, item.visible);\n\n\t\t\t// add the HTML checkbox on top\n\t\t\tif (itemOptions && showCheckbox) {\n\t\t\t\titem.checkbox = createElement('input', {\n\t\t\t\t\ttype: 'checkbox',\n\t\t\t\t\tchecked: item.selected,\n\t\t\t\t\tdefaultChecked: item.selected // required by IE7\n\t\t\t\t}, options.itemCheckboxStyle, chart.container);\n\n\t\t\t\taddEvent(item.checkbox, 'click', function (event) {\n\t\t\t\t\tvar target = event.target;\n\t\t\t\t\tfireEvent(item, 'checkboxClick', {\n\t\t\t\t\t\t\tchecked: target.checked\n\t\t\t\t\t\t},\n\t\t\t\t\t\tfunction () {\n\t\t\t\t\t\t\titem.select();\n\t\t\t\t\t\t}\n\t\t\t\t\t);\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\t// calculate the positions for the next line\n\t\tbBox = li.getBBox();\n\n\t\titemWidth = item.legendItemWidth =\n\t\t\toptions.itemWidth || symbolWidth + symbolPadding + bBox.width + itemDistance +\n\t\t\t(showCheckbox ? 20 : 0);\n\t\tlegend.itemHeight = itemHeight = bBox.height;\n\n\t\t// if the item exceeds the width, start a new line\n\t\tif (horizontal && legend.itemX - initialItemX + itemWidth >\n\t\t\t\t(widthOption || (chart.chartWidth - 2 * padding - initialItemX))) {\n\t\t\tlegend.itemX = initialItemX;\n\t\t\tlegend.itemY += itemMarginTop + legend.lastLineHeight + itemMarginBottom;\n\t\t\tlegend.lastLineHeight = 0; // reset for next line\n\t\t}\n\n\t\t// If the item exceeds the height, start a new column\n\t\t/*if (!horizontal && legend.itemY + options.y + itemHeight > chart.chartHeight - spacingTop - spacingBottom) {\n\t\t\tlegend.itemY = legend.initialItemY;\n\t\t\tlegend.itemX += legend.maxItemWidth;\n\t\t\tlegend.maxItemWidth = 0;\n\t\t}*/\n\n\t\t// Set the edge positions\n\t\tlegend.maxItemWidth = mathMax(legend.maxItemWidth, itemWidth);\n\t\tlegend.lastItemY = itemMarginTop + legend.itemY + itemMarginBottom;\n\t\tlegend.lastLineHeight = mathMax(itemHeight, legend.lastLineHeight); // #915\n\n\t\t// cache the position of the newly generated or reordered items\n\t\titem._legendItemPos = [legend.itemX, legend.itemY];\n\n\t\t// advance\n\t\tif (horizontal) {\n\t\t\tlegend.itemX += itemWidth;\n\n\t\t} else {\n\t\t\tlegend.itemY += itemMarginTop + itemHeight + itemMarginBottom;\n\t\t\tlegend.lastLineHeight = itemHeight;\n\t\t}\n\n\t\t// the width of the widest item\n\t\tlegend.offsetWidth = widthOption || mathMax(\n\t\t\t(horizontal ? legend.itemX - initialItemX - itemDistance : itemWidth) + padding,\n\t\t\tlegend.offsetWidth\n\t\t);\n\t},\n\n\t/**\n\t * Render the legend. This method can be called both before and after\n\t * chart.render. If called after, it will only rearrange items instead\n\t * of creating new ones.\n\t */\n\trender: function () {\n\t\tvar legend = this,\n\t\t\tchart = legend.chart,\n\t\t\trenderer = chart.renderer,\n\t\t\tlegendGroup = legend.group,\n\t\t\tallItems,\n\t\t\tdisplay,\n\t\t\tlegendWidth,\n\t\t\tlegendHeight,\n\t\t\tbox = legend.box,\n\t\t\toptions = legend.options,\n\t\t\tpadding = legend.padding,\n\t\t\tlegendBorderWidth = options.borderWidth,\n\t\t\tlegendBackgroundColor = options.backgroundColor;\n\n\t\tlegend.itemX = legend.initialItemX;\n\t\tlegend.itemY = legend.initialItemY;\n\t\tlegend.offsetWidth = 0;\n\t\tlegend.lastItemY = 0;\n\n\t\tif (!legendGroup) {\n\t\t\tlegend.group = legendGroup = renderer.g('legend')\n\t\t\t\t.attr({ zIndex: 7 }) \n\t\t\t\t.add();\n\t\t\tlegend.contentGroup = renderer.g()\n\t\t\t\t.attr({ zIndex: 1 }) // above background\n\t\t\t\t.add(legendGroup);\n\t\t\tlegend.scrollGroup = renderer.g()\n\t\t\t\t.add(legend.contentGroup);\n\t\t}\n\t\t\n\t\tlegend.renderTitle();\n\n\t\t// add each series or point\n\t\tallItems = [];\n\t\teach(chart.series, function (serie) {\n\t\t\tvar seriesOptions = serie.options;\n\n\t\t\tif (!seriesOptions.showInLegend || defined(seriesOptions.linkedTo)) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// use points or series for the legend item depending on legendType\n\t\t\tallItems = allItems.concat(\n\t\t\t\t\tserie.legendItems ||\n\t\t\t\t\t(seriesOptions.legendType === 'point' ?\n\t\t\t\t\t\t\tserie.data :\n\t\t\t\t\t\t\tserie)\n\t\t\t);\n\t\t});\n\n\t\t// sort by legendIndex\n\t\tstableSort(allItems, function (a, b) {\n\t\t\treturn ((a.options && a.options.legendIndex) || 0) - ((b.options && b.options.legendIndex) || 0);\n\t\t});\n\n\t\t// reversed legend\n\t\tif (options.reversed) {\n\t\t\tallItems.reverse();\n\t\t}\n\n\t\tlegend.allItems = allItems;\n\t\tlegend.display = display = !!allItems.length;\n\n\t\t// render the items\n\t\teach(allItems, function (item) {\n\t\t\tlegend.renderItem(item); \n\t\t});\n\n\t\t// Draw the border\n\t\tlegendWidth = options.width || legend.offsetWidth;\n\t\tlegendHeight = legend.lastItemY + legend.lastLineHeight + legend.titleHeight;\n\t\t\n\t\t\n\t\tlegendHeight = legend.handleOverflow(legendHeight);\n\n\t\tif (legendBorderWidth || legendBackgroundColor) {\n\t\t\tlegendWidth += padding;\n\t\t\tlegendHeight += padding;\n\n\t\t\tif (!box) {\n\t\t\t\tlegend.box = box = renderer.rect(\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t\tlegendWidth,\n\t\t\t\t\tlegendHeight,\n\t\t\t\t\toptions.borderRadius,\n\t\t\t\t\tlegendBorderWidth || 0\n\t\t\t\t).attr({\n\t\t\t\t\tstroke: options.borderColor,\n\t\t\t\t\t'stroke-width': legendBorderWidth || 0,\n\t\t\t\t\tfill: legendBackgroundColor || NONE\n\t\t\t\t})\n\t\t\t\t.add(legendGroup)\n\t\t\t\t.shadow(options.shadow);\n\t\t\t\tbox.isNew = true;\n\n\t\t\t} else if (legendWidth > 0 && legendHeight > 0) {\n\t\t\t\tbox[box.isNew ? 'attr' : 'animate'](\n\t\t\t\t\tbox.crisp(null, null, null, legendWidth, legendHeight)\n\t\t\t\t);\n\t\t\t\tbox.isNew = false;\n\t\t\t}\n\n\t\t\t// hide the border if no items\n\t\t\tbox[display ? 'show' : 'hide']();\n\t\t}\n\t\t\n\t\tlegend.legendWidth = legendWidth;\n\t\tlegend.legendHeight = legendHeight;\n\n\t\t// Now that the legend width and height are established, put the items in the \n\t\t// final position\n\t\teach(allItems, function (item) {\n\t\t\tlegend.positionItem(item);\n\t\t});\n\n\t\t// 1.x compatibility: positioning based on style\n\t\t/*var props = ['left', 'right', 'top', 'bottom'],\n\t\t\tprop,\n\t\t\ti = 4;\n\t\twhile (i--) {\n\t\t\tprop = props[i];\n\t\t\tif (options.style[prop] && options.style[prop] !== 'auto') {\n\t\t\t\toptions[i < 2 ? 'align' : 'verticalAlign'] = prop;\n\t\t\t\toptions[i < 2 ? 'x' : 'y'] = pInt(options.style[prop]) * (i % 2 ? -1 : 1);\n\t\t\t}\n\t\t}*/\n\n\t\tif (display) {\n\t\t\tlegendGroup.align(extend({\n\t\t\t\twidth: legendWidth,\n\t\t\t\theight: legendHeight\n\t\t\t}, options), true, 'spacingBox');\n\t\t}\n\n\t\tif (!chart.isResizing) {\n\t\t\tthis.positionCheckboxes();\n\t\t}\n\t},\n\t\n\t/**\n\t * Set up the overflow handling by adding navigation with up and down arrows below the\n\t * legend.\n\t */\n\thandleOverflow: function (legendHeight) {\n\t\tvar legend = this,\n\t\t\tchart = this.chart,\n\t\t\trenderer = chart.renderer,\n\t\t\tpageCount,\n\t\t\toptions = this.options,\n\t\t\toptionsY = options.y,\n\t\t\talignTop = options.verticalAlign === 'top',\n\t\t\tspaceHeight = chart.spacingBox.height + (alignTop ? -optionsY : optionsY) - this.padding,\n\t\t\tmaxHeight = options.maxHeight,\n\t\t\tclipHeight,\n\t\t\tclipRect = this.clipRect,\n\t\t\tnavOptions = options.navigation,\n\t\t\tanimation = pick(navOptions.animation, true),\n\t\t\tarrowSize = navOptions.arrowSize || 12,\n\t\t\tnav = this.nav;\n\t\t\t\n\t\t// Adjust the height\n\t\tif (options.layout === 'horizontal') {\n\t\t\tspaceHeight /= 2;\n\t\t}\n\t\tif (maxHeight) {\n\t\t\tspaceHeight = mathMin(spaceHeight, maxHeight);\n\t\t}\n\t\t\n\t\t// Reset the legend height and adjust the clipping rectangle\n\t\tif (legendHeight > spaceHeight && !options.useHTML) {\n\n\t\t\tthis.clipHeight = clipHeight = spaceHeight - 20 - this.titleHeight;\n\t\t\tthis.pageCount = pageCount = mathCeil(legendHeight / clipHeight);\n\t\t\tthis.currentPage = pick(this.currentPage, 1);\n\t\t\tthis.fullHeight = legendHeight;\n\t\t\t\n\t\t\t// Only apply clipping if needed. Clipping causes blurred legend in PDF export (#1787)\n\t\t\tif (!clipRect) {\n\t\t\t\tclipRect = legend.clipRect = renderer.clipRect(0, 0, 9999, 0);\n\t\t\t\tlegend.contentGroup.clip(clipRect);\n\t\t\t}\n\t\t\tclipRect.attr({\n\t\t\t\theight: clipHeight\n\t\t\t});\n\t\t\t\n\t\t\t// Add navigation elements\n\t\t\tif (!nav) {\n\t\t\t\tthis.nav = nav = renderer.g().attr({ zIndex: 1 }).add(this.group);\n\t\t\t\tthis.up = renderer.symbol('triangle', 0, 0, arrowSize, arrowSize)\n\t\t\t\t\t.on('click', function () {\n\t\t\t\t\t\tlegend.scroll(-1, animation);\n\t\t\t\t\t})\n\t\t\t\t\t.add(nav);\n\t\t\t\tthis.pager = renderer.text('', 15, 10)\n\t\t\t\t\t.css(navOptions.style)\n\t\t\t\t\t.add(nav);\n\t\t\t\tthis.down = renderer.symbol('triangle-down', 0, 0, arrowSize, arrowSize)\n\t\t\t\t\t.on('click', function () {\n\t\t\t\t\t\tlegend.scroll(1, animation);\n\t\t\t\t\t})\n\t\t\t\t\t.add(nav);\n\t\t\t}\n\t\t\t\n\t\t\t// Set initial position\n\t\t\tlegend.scroll(0);\n\t\t\t\n\t\t\tlegendHeight = spaceHeight;\n\t\t\t\n\t\t} else if (nav) {\n\t\t\tclipRect.attr({\n\t\t\t\theight: chart.chartHeight\n\t\t\t});\n\t\t\tnav.hide();\n\t\t\tthis.scrollGroup.attr({\n\t\t\t\ttranslateY: 1\n\t\t\t});\n\t\t\tthis.clipHeight = 0; // #1379\n\t\t}\n\t\t\n\t\treturn legendHeight;\n\t},\n\t\n\t/**\n\t * Scroll the legend by a number of pages\n\t * @param {Object} scrollBy\n\t * @param {Object} animation\n\t */\n\tscroll: function (scrollBy, animation) {\n\t\tvar pageCount = this.pageCount,\n\t\t\tcurrentPage = this.currentPage + scrollBy,\n\t\t\tclipHeight = this.clipHeight,\n\t\t\tnavOptions = this.options.navigation,\n\t\t\tactiveColor = navOptions.activeColor,\n\t\t\tinactiveColor = navOptions.inactiveColor,\n\t\t\tpager = this.pager,\n\t\t\tpadding = this.padding,\n\t\t\tscrollOffset;\n\t\t\n\t\t// When resizing while looking at the last page\n\t\tif (currentPage > pageCount) {\n\t\t\tcurrentPage = pageCount;\n\t\t}\n\t\t\n\t\tif (currentPage > 0) {\n\t\t\t\n\t\t\tif (animation !== UNDEFINED) {\n\t\t\t\tsetAnimation(animation, this.chart);\n\t\t\t}\n\t\t\t\n\t\t\tthis.nav.attr({\n\t\t\t\ttranslateX: padding,\n\t\t\t\ttranslateY: clipHeight + 7 + this.titleHeight,\n\t\t\t\tvisibility: VISIBLE\n\t\t\t});\n\t\t\tthis.up.attr({\n\t\t\t\t\tfill: currentPage === 1 ? inactiveColor : activeColor\n\t\t\t\t})\n\t\t\t\t.css({\n\t\t\t\t\tcursor: currentPage === 1 ? 'default' : 'pointer'\n\t\t\t\t});\n\t\t\tpager.attr({\n\t\t\t\ttext: currentPage + '/' + this.pageCount\n\t\t\t});\n\t\t\tthis.down.attr({\n\t\t\t\t\tx: 18 + this.pager.getBBox().width, // adjust to text width\n\t\t\t\t\tfill: currentPage === pageCount ? inactiveColor : activeColor\n\t\t\t\t})\n\t\t\t\t.css({\n\t\t\t\t\tcursor: currentPage === pageCount ? 'default' : 'pointer'\n\t\t\t\t});\n\t\t\t\n\t\t\tscrollOffset = -mathMin(clipHeight * (currentPage - 1), this.fullHeight - clipHeight + padding) + 1;\n\t\t\tthis.scrollGroup.animate({\n\t\t\t\ttranslateY: scrollOffset\n\t\t\t});\n\t\t\tpager.attr({\n\t\t\t\ttext: currentPage + '/' + pageCount\n\t\t\t});\n\t\t\t\n\t\t\t\n\t\t\tthis.currentPage = currentPage;\n\t\t\tthis.positionCheckboxes(scrollOffset);\n\t\t}\n\t\t\t\n\t}\n\t\n};\n\n// Workaround for #2030, horizontal legend items not displaying in IE11 Preview.\n// TODO: When IE11 is released, check again for this bug, and remove the fix\n// or make a better one.\nif (/Trident.*?11\\.0/.test(userAgent)) {\n\twrap(Legend.prototype, 'positionItem', function (proceed, item) {\n\t\tvar legend = this;\n\t\tsetTimeout(function () {\n\t\t\tproceed.call(legend, item);\n\t\t});\n\t});\n}\n\n/**\n * The chart class\n * @param {Object} options\n * @param {Function} callback Function to run when the chart has loaded\n */\nfunction Chart() {\n\tthis.init.apply(this, arguments);\n}\n\nChart.prototype = {\n\n\t/**\n\t * Initialize the chart\n\t */\n\tinit: function (userOptions, callback) {\n\n\t\t// Handle regular options\n\t\tvar options,\n\t\t\tseriesOptions = userOptions.series; // skip merging data points to increase performance\n\n\t\tuserOptions.series = null;\n\t\toptions = merge(defaultOptions, userOptions); // do the merge\n\t\toptions.series = userOptions.series = seriesOptions; // set back the series data\n\n\t\tvar optionsChart = options.chart;\n\t\t\n\t\t// Create margin & spacing array\n\t\tthis.margin = this.splashArray('margin', optionsChart);\n\t\tthis.spacing = this.splashArray('spacing', optionsChart);\n\n\t\tvar chartEvents = optionsChart.events;\n\n\t\t//this.runChartClick = chartEvents && !!chartEvents.click;\n\t\tthis.bounds = { h: {}, v: {} }; // Pixel data bounds for touch zoom\n\n\t\tthis.callback = callback;\n\t\tthis.isResizing = 0;\n\t\tthis.options = options;\n\t\t//chartTitleOptions = UNDEFINED;\n\t\t//chartSubtitleOptions = UNDEFINED;\n\n\t\tthis.axes = [];\n\t\tthis.series = [];\n\t\tthis.hasCartesianSeries = optionsChart.showAxes;\n\t\t//this.axisOffset = UNDEFINED;\n\t\t//this.maxTicks = UNDEFINED; // handle the greatest amount of ticks on grouped axes\n\t\t//this.inverted = UNDEFINED;\n\t\t//this.loadingShown = UNDEFINED;\n\t\t//this.container = UNDEFINED;\n\t\t//this.chartWidth = UNDEFINED;\n\t\t//this.chartHeight = UNDEFINED;\n\t\t//this.marginRight = UNDEFINED;\n\t\t//this.marginBottom = UNDEFINED;\n\t\t//this.containerWidth = UNDEFINED;\n\t\t//this.containerHeight = UNDEFINED;\n\t\t//this.oldChartWidth = UNDEFINED;\n\t\t//this.oldChartHeight = UNDEFINED;\n\n\t\t//this.renderTo = UNDEFINED;\n\t\t//this.renderToClone = UNDEFINED;\n\n\t\t//this.spacingBox = UNDEFINED\n\n\t\t//this.legend = UNDEFINED;\n\n\t\t// Elements\n\t\t//this.chartBackground = UNDEFINED;\n\t\t//this.plotBackground = UNDEFINED;\n\t\t//this.plotBGImage = UNDEFINED;\n\t\t//this.plotBorder = UNDEFINED;\n\t\t//this.loadingDiv = UNDEFINED;\n\t\t//this.loadingSpan = UNDEFINED;\n\n\t\tvar chart = this,\n\t\t\teventType;\n\n\t\t// Add the chart to the global lookup\n\t\tchart.index = charts.length;\n\t\tcharts.push(chart);\n\n\t\t// Set up auto resize\n\t\tif (optionsChart.reflow !== false) {\n\t\t\taddEvent(chart, 'load', function () {\n\t\t\t\tchart.initReflow();\n\t\t\t});\n\t\t}\n\n\t\t// Chart event handlers\n\t\tif (chartEvents) {\n\t\t\tfor (eventType in chartEvents) {\n\t\t\t\taddEvent(chart, eventType, chartEvents[eventType]);\n\t\t\t}\n\t\t}\n\n\t\tchart.xAxis = [];\n\t\tchart.yAxis = [];\n\n\t\t// Expose methods and variables\n\t\tchart.animation = useCanVG ? false : pick(optionsChart.animation, true);\n\t\tchart.pointCount = 0;\n\t\tchart.counters = new ChartCounters();\n\n\t\tchart.firstRender();\n\t},\n\n\t/**\n\t * Initialize an individual series, called internally before render time\n\t */\n\tinitSeries: function (options) {\n\t\tvar chart = this,\n\t\t\toptionsChart = chart.options.chart,\n\t\t\ttype = options.type || optionsChart.type || optionsChart.defaultSeriesType,\n\t\t\tseries,\n\t\t\tconstr = seriesTypes[type];\n\n\t\t// No such series type\n\t\tif (!constr) {\n\t\t\terror(17, true);\n\t\t}\n\n\t\tseries = new constr();\n\t\tseries.init(this, options);\n\t\treturn series;\n\t},\n\n\t/**\n\t * Add a series dynamically after  time\n\t *\n\t * @param {Object} options The config options\n\t * @param {Boolean} redraw Whether to redraw the chart after adding. Defaults to true.\n\t * @param {Boolean|Object} animation Whether to apply animation, and optionally animation\n\t *    configuration\n\t *\n\t * @return {Object} series The newly created series object\n\t */\n\taddSeries: function (options, redraw, animation) {\n\t\tvar series,\n\t\t\tchart = this;\n\n\t\tif (options) {\n\t\t\tredraw = pick(redraw, true); // defaults to true\n\n\t\t\tfireEvent(chart, 'addSeries', { options: options }, function () {\n\t\t\t\tseries = chart.initSeries(options);\n\t\t\t\t\n\t\t\t\tchart.isDirtyLegend = true; // the series array is out of sync with the display\n\t\t\t\tchart.linkSeries();\n\t\t\t\tif (redraw) {\n\t\t\t\t\tchart.redraw(animation);\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\treturn series;\n\t},\n\n\t/**\n     * Add an axis to the chart\n     * @param {Object} options The axis option\n     * @param {Boolean} isX Whether it is an X axis or a value axis\n     */\n\taddAxis: function (options, isX, redraw, animation) {\n\t\tvar key = isX ? 'xAxis' : 'yAxis',\n\t\t\tchartOptions = this.options,\n\t\t\taxis;\n\n\t\t/*jslint unused: false*/\n\t\taxis = new Axis(this, merge(options, {\n\t\t\tindex: this[key].length,\n\t\t\tisX: isX\n\t\t}));\n\t\t/*jslint unused: true*/\n\n\t\t// Push the new axis options to the chart options\n\t\tchartOptions[key] = splat(chartOptions[key] || {});\n\t\tchartOptions[key].push(options);\n\n\t\tif (pick(redraw, true)) {\n\t\t\tthis.redraw(animation);\n\t\t}\n\t},\n\n\t/**\n\t * Check whether a given point is within the plot area\n\t *\n\t * @param {Number} plotX Pixel x relative to the plot area\n\t * @param {Number} plotY Pixel y relative to the plot area\n\t * @param {Boolean} inverted Whether the chart is inverted\n\t */\n\tisInsidePlot: function (plotX, plotY, inverted) {\n\t\tvar x = inverted ? plotY : plotX,\n\t\t\ty = inverted ? plotX : plotY;\n\t\t\t\n\t\treturn x >= 0 &&\n\t\t\tx <= this.plotWidth &&\n\t\t\ty >= 0 &&\n\t\t\ty <= this.plotHeight;\n\t},\n\n\t/**\n\t * Adjust all axes tick amounts\n\t */\n\tadjustTickAmounts: function () {\n\t\tif (this.options.chart.alignTicks !== false) {\n\t\t\teach(this.axes, function (axis) {\n\t\t\t\taxis.adjustTickAmount();\n\t\t\t});\n\t\t}\n\t\tthis.maxTicks = null;\n\t},\n\n\t/**\n\t * Redraw legend, axes or series based on updated data\n\t *\n\t * @param {Boolean|Object} animation Whether to apply animation, and optionally animation\n\t *    configuration\n\t */\n\tredraw: function (animation) {\n\t\tvar chart = this,\n\t\t\taxes = chart.axes,\n\t\t\tseries = chart.series,\n\t\t\tpointer = chart.pointer,\n\t\t\tlegend = chart.legend,\n\t\t\tredrawLegend = chart.isDirtyLegend,\n\t\t\thasStackedSeries,\n\t\t\thasDirtyStacks,\n\t\t\tisDirtyBox = chart.isDirtyBox, // todo: check if it has actually changed?\n\t\t\tseriesLength = series.length,\n\t\t\ti = seriesLength,\n\t\t\tserie,\n\t\t\trenderer = chart.renderer,\n\t\t\tisHiddenChart = renderer.isHidden(),\n\t\t\tafterRedraw = [];\n\t\t\t\n\t\tsetAnimation(animation, chart);\n\t\t\n\t\tif (isHiddenChart) {\n\t\t\tchart.cloneRenderTo();\n\t\t}\n\n\t\t// Adjust title layout (reflow multiline text)\n\t\tchart.layOutTitles();\n\n\t\t// link stacked series\n\t\twhile (i--) {\n\t\t\tserie = series[i];\n\n\t\t\tif (serie.options.stacking) {\n\t\t\t\thasStackedSeries = true;\n\t\t\t\t\n\t\t\t\tif (serie.isDirty) {\n\t\t\t\t\thasDirtyStacks = true;\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (hasDirtyStacks) { // mark others as dirty\n\t\t\ti = seriesLength;\n\t\t\twhile (i--) {\n\t\t\t\tserie = series[i];\n\t\t\t\tif (serie.options.stacking) {\n\t\t\t\t\tserie.isDirty = true;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// handle updated data in the series\n\t\teach(series, function (serie) {\n\t\t\tif (serie.isDirty) { // prepare the data so axis can read it\n\t\t\t\tif (serie.options.legendType === 'point') {\n\t\t\t\t\tredrawLegend = true;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\t// handle added or removed series\n\t\tif (redrawLegend && legend.options.enabled) { // series or pie points are added or removed\n\t\t\t// draw legend graphics\n\t\t\tlegend.render();\n\n\t\t\tchart.isDirtyLegend = false;\n\t\t}\n\n\t\t// reset stacks\n\t\tif (hasStackedSeries) {\n\t\t\tchart.getStacks();\n\t\t}\n\n\n\t\tif (chart.hasCartesianSeries) {\n\t\t\tif (!chart.isResizing) {\n\n\t\t\t\t// reset maxTicks\n\t\t\t\tchart.maxTicks = null;\n\n\t\t\t\t// set axes scales\n\t\t\t\teach(axes, function (axis) {\n\t\t\t\t\taxis.setScale();\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tchart.adjustTickAmounts();\n\t\t\tchart.getMargins();\n\n\t\t\t// If one axis is dirty, all axes must be redrawn (#792, #2169)\n\t\t\teach(axes, function (axis) {\n\t\t\t\tif (axis.isDirty) {\n\t\t\t\t\tisDirtyBox = true;\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// redraw axes\n\t\t\teach(axes, function (axis) {\n\t\t\t\t\n\t\t\t\t// Fire 'afterSetExtremes' only if extremes are set\n\t\t\t\tif (axis.isDirtyExtremes) { // #821\n\t\t\t\t\taxis.isDirtyExtremes = false;\n\t\t\t\t\tafterRedraw.push(function () { // prevent a recursive call to chart.redraw() (#1119)\n\t\t\t\t\t\tfireEvent(axis, 'afterSetExtremes', extend(axis.eventArgs, axis.getExtremes())); // #747, #751\n\t\t\t\t\t\tdelete axis.eventArgs;\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tif (isDirtyBox || hasStackedSeries) {\n\t\t\t\t\taxis.redraw();\n\t\t\t\t}\n\t\t\t});\n\n\n\t\t}\n\t\t// the plot areas size has changed\n\t\tif (isDirtyBox) {\n\t\t\tchart.drawChartBox();\n\t\t}\n\n\n\t\t// redraw affected series\n\t\teach(series, function (serie) {\n\t\t\tif (serie.isDirty && serie.visible &&\n\t\t\t\t\t(!serie.isCartesian || serie.xAxis)) { // issue #153\n\t\t\t\tserie.redraw();\n\t\t\t}\n\t\t});\n\n\t\t// move tooltip or reset\n\t\tif (pointer && pointer.reset) {\n\t\t\tpointer.reset(true);\n\t\t}\n\n\t\t// redraw if canvas\n\t\trenderer.draw();\n\n\t\t// fire the event\n\t\tfireEvent(chart, 'redraw'); // jQuery breaks this when calling it from addEvent. Overwrites chart.redraw\n\t\t\n\t\tif (isHiddenChart) {\n\t\t\tchart.cloneRenderTo(true);\n\t\t}\n\t\t\n\t\t// Fire callbacks that are put on hold until after the redraw\n\t\teach(afterRedraw, function (callback) {\n\t\t\tcallback.call();\n\t\t});\n\t},\n\n\n\n\t/**\n\t * Dim the chart and show a loading text or symbol\n\t * @param {String} str An optional text to show in the loading label instead of the default one\n\t */\n\tshowLoading: function (str) {\n\t\tvar chart = this,\n\t\t\toptions = chart.options,\n\t\t\tloadingDiv = chart.loadingDiv;\n\n\t\tvar loadingOptions = options.loading;\n\n\t\t// create the layer at the first call\n\t\tif (!loadingDiv) {\n\t\t\tchart.loadingDiv = loadingDiv = createElement(DIV, {\n\t\t\t\tclassName: PREFIX + 'loading'\n\t\t\t}, extend(loadingOptions.style, {\n\t\t\t\tzIndex: 10,\n\t\t\t\tdisplay: NONE\n\t\t\t}), chart.container);\n\n\t\t\tchart.loadingSpan = createElement(\n\t\t\t\t'span',\n\t\t\t\tnull,\n\t\t\t\tloadingOptions.labelStyle,\n\t\t\t\tloadingDiv\n\t\t\t);\n\n\t\t}\n\n\t\t// update text\n\t\tchart.loadingSpan.innerHTML = str || options.lang.loading;\n\n\t\t// show it\n\t\tif (!chart.loadingShown) {\n\t\t\tcss(loadingDiv, { \n\t\t\t\topacity: 0, \n\t\t\t\tdisplay: '',\n\t\t\t\tleft: chart.plotLeft + PX,\n\t\t\t\ttop: chart.plotTop + PX,\n\t\t\t\twidth: chart.plotWidth + PX,\n\t\t\t\theight: chart.plotHeight + PX\n\t\t\t});\n\t\t\tanimate(loadingDiv, {\n\t\t\t\topacity: loadingOptions.style.opacity\n\t\t\t}, {\n\t\t\t\tduration: loadingOptions.showDuration || 0\n\t\t\t});\n\t\t\tchart.loadingShown = true;\n\t\t}\n\t},\n\n\t/**\n\t * Hide the loading layer\n\t */\n\thideLoading: function () {\n\t\tvar options = this.options,\n\t\t\tloadingDiv = this.loadingDiv;\n\n\t\tif (loadingDiv) {\n\t\t\tanimate(loadingDiv, {\n\t\t\t\topacity: 0\n\t\t\t}, {\n\t\t\t\tduration: options.loading.hideDuration || 100,\n\t\t\t\tcomplete: function () {\n\t\t\t\t\tcss(loadingDiv, { display: NONE });\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t\tthis.loadingShown = false;\n\t},\n\n\t/**\n\t * Get an axis, series or point object by id.\n\t * @param id {String} The id as given in the configuration options\n\t */\n\tget: function (id) {\n\t\tvar chart = this,\n\t\t\taxes = chart.axes,\n\t\t\tseries = chart.series;\n\n\t\tvar i,\n\t\t\tj,\n\t\t\tpoints;\n\n\t\t// search axes\n\t\tfor (i = 0; i < axes.length; i++) {\n\t\t\tif (axes[i].options.id === id) {\n\t\t\t\treturn axes[i];\n\t\t\t}\n\t\t}\n\n\t\t// search series\n\t\tfor (i = 0; i < series.length; i++) {\n\t\t\tif (series[i].options.id === id) {\n\t\t\t\treturn series[i];\n\t\t\t}\n\t\t}\n\n\t\t// search points\n\t\tfor (i = 0; i < series.length; i++) {\n\t\t\tpoints = series[i].points || [];\n\t\t\tfor (j = 0; j < points.length; j++) {\n\t\t\t\tif (points[j].id === id) {\n\t\t\t\t\treturn points[j];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn null;\n\t},\n\n\t/**\n\t * Create the Axis instances based on the config options\n\t */\n\tgetAxes: function () {\n\t\tvar chart = this,\n\t\t\toptions = this.options,\n\t\t\txAxisOptions = options.xAxis = splat(options.xAxis || {}),\n\t\t\tyAxisOptions = options.yAxis = splat(options.yAxis || {}),\n\t\t\toptionsArray,\n\t\t\taxis;\n\n\t\t// make sure the options are arrays and add some members\n\t\teach(xAxisOptions, function (axis, i) {\n\t\t\taxis.index = i;\n\t\t\taxis.isX = true;\n\t\t});\n\n\t\teach(yAxisOptions, function (axis, i) {\n\t\t\taxis.index = i;\n\t\t});\n\n\t\t// concatenate all axis options into one array\n\t\toptionsArray = xAxisOptions.concat(yAxisOptions);\n\n\t\teach(optionsArray, function (axisOptions) {\n\t\t\taxis = new Axis(chart, axisOptions);\n\t\t});\n\n\t\tchart.adjustTickAmounts();\n\t},\n\n\n\t/**\n\t * Get the currently selected points from all series\n\t */\n\tgetSelectedPoints: function () {\n\t\tvar points = [];\n\t\teach(this.series, function (serie) {\n\t\t\tpoints = points.concat(grep(serie.points || [], function (point) {\n\t\t\t\treturn point.selected;\n\t\t\t}));\n\t\t});\n\t\treturn points;\n\t},\n\n\t/**\n\t * Get the currently selected series\n\t */\n\tgetSelectedSeries: function () {\n\t\treturn grep(this.series, function (serie) {\n\t\t\treturn serie.selected;\n\t\t});\n\t},\n\n\t/**\n\t * Generate stacks for each series and calculate stacks total values\n\t */\n\tgetStacks: function () {\n\t\tvar chart = this;\n\n\t\t// reset stacks for each yAxis\n\t\teach(chart.yAxis, function (axis) {\n\t\t\tif (axis.stacks && axis.hasVisibleSeries) {\n\t\t\t\taxis.oldStacks = axis.stacks;\n\t\t\t}\n\t\t});\n\n\t\teach(chart.series, function (series) {\n\t\t\tif (series.options.stacking && (series.visible === true || chart.options.chart.ignoreHiddenSeries === false)) {\n\t\t\t\tseries.stackKey = series.type + pick(series.options.stack, '');\n\t\t\t}\n\t\t});\n\t},\n\n\t/**\n\t * Display the zoom button\n\t */\n\tshowResetZoom: function () {\n\t\tvar chart = this,\n\t\t\tlang = defaultOptions.lang,\n\t\t\tbtnOptions = chart.options.chart.resetZoomButton,\n\t\t\ttheme = btnOptions.theme,\n\t\t\tstates = theme.states,\n\t\t\talignTo = btnOptions.relativeTo === 'chart' ? null : 'plotBox';\n\t\t\t\n\t\tthis.resetZoomButton = chart.renderer.button(lang.resetZoom, null, null, function () { chart.zoomOut(); }, theme, states && states.hover)\n\t\t\t.attr({\n\t\t\t\talign: btnOptions.position.align,\n\t\t\t\ttitle: lang.resetZoomTitle\n\t\t\t})\n\t\t\t.add()\n\t\t\t.align(btnOptions.position, false, alignTo);\n\t\t\t\n\t},\n\n\t/**\n\t * Zoom out to 1:1\n\t */\n\tzoomOut: function () {\n\t\tvar chart = this;\n\t\tfireEvent(chart, 'selection', { resetSelection: true }, function () { \n\t\t\tchart.zoom();\n\t\t});\n\t},\n\n\t/**\n\t * Zoom into a given portion of the chart given by axis coordinates\n\t * @param {Object} event\n\t */\n\tzoom: function (event) {\n\t\tvar chart = this,\n\t\t\thasZoomed,\n\t\t\tpointer = chart.pointer,\n\t\t\tdisplayButton = false,\n\t\t\tresetZoomButton;\n\n\t\t// If zoom is called with no arguments, reset the axes\n\t\tif (!event || event.resetSelection) {\n\t\t\teach(chart.axes, function (axis) {\n\t\t\t\thasZoomed = axis.zoom();\n\t\t\t});\n\t\t} else { // else, zoom in on all axes\n\t\t\teach(event.xAxis.concat(event.yAxis), function (axisData) {\n\t\t\t\tvar axis = axisData.axis,\n\t\t\t\t\tisXAxis = axis.isXAxis;\n\n\t\t\t\t// don't zoom more than minRange\n\t\t\t\tif (pointer[isXAxis ? 'zoomX' : 'zoomY'] || pointer[isXAxis ? 'pinchX' : 'pinchY']) {\n\t\t\t\t\thasZoomed = axis.zoom(axisData.min, axisData.max);\n\t\t\t\t\tif (axis.displayBtn) {\n\t\t\t\t\t\tdisplayButton = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t\t\n\t\t// Show or hide the Reset zoom button\n\t\tresetZoomButton = chart.resetZoomButton;\n\t\tif (displayButton && !resetZoomButton) {\n\t\t\tchart.showResetZoom();\n\t\t} else if (!displayButton && isObject(resetZoomButton)) {\n\t\t\tchart.resetZoomButton = resetZoomButton.destroy();\n\t\t}\n\t\t\n\n\t\t// Redraw\n\t\tif (hasZoomed) {\n\t\t\tchart.redraw(\n\t\t\t\tpick(chart.options.chart.animation, event && event.animation, chart.pointCount < 100) // animation\n\t\t\t);\n\t\t}\n\t},\n\n\t/**\n\t * Pan the chart by dragging the mouse across the pane. This function is called\n\t * on mouse move, and the distance to pan is computed from chartX compared to\n\t * the first chartX position in the dragging operation.\n\t */\n\tpan: function (e, panning) {\n\n\t\tvar chart = this,\n\t\t\thoverPoints = chart.hoverPoints,\n\t\t\tdoRedraw;\n\n\t\t// remove active points for shared tooltip\n\t\tif (hoverPoints) {\n\t\t\teach(hoverPoints, function (point) {\n\t\t\t\tpoint.setState();\n\t\t\t});\n\t\t}\n\n\t\teach(panning === 'xy' ? [1, 0] : [1], function (isX) { // xy is used in maps\n\t\t\tvar mousePos = e[isX ? 'chartX' : 'chartY'],\n\t\t\t\taxis = chart[isX ? 'xAxis' : 'yAxis'][0],\n\t\t\t\tstartPos = chart[isX ? 'mouseDownX' : 'mouseDownY'],\n\t\t\t\thalfPointRange = (axis.pointRange || 0) / 2,\n\t\t\t\textremes = axis.getExtremes(),\n\t\t\t\tnewMin = axis.toValue(startPos - mousePos, true) + halfPointRange,\n\t\t\t\tnewMax = axis.toValue(startPos + chart[isX ? 'plotWidth' : 'plotHeight'] - mousePos, true) - halfPointRange;\n\n\t\t\tif (axis.series.length && newMin > mathMin(extremes.dataMin, extremes.min) && newMax < mathMax(extremes.dataMax, extremes.max)) {\n\t\t\t\taxis.setExtremes(newMin, newMax, false, false, { trigger: 'pan' });\n\t\t\t\tdoRedraw = true;\n\t\t\t}\n\n\t\t\tchart[isX ? 'mouseDownX' : 'mouseDownY'] = mousePos; // set new reference for next run\n\t\t});\n\n\t\tif (doRedraw) {\n\t\t\tchart.redraw(false);\n\t\t}\n\t\tcss(chart.container, { cursor: 'move' });\n\t},\n\n\t/**\n\t * Show the title and subtitle of the chart\n\t *\n\t * @param titleOptions {Object} New title options\n\t * @param subtitleOptions {Object} New subtitle options\n\t *\n\t */\n\tsetTitle: function (titleOptions, subtitleOptions) {\n\t\tvar chart = this,\n\t\t\toptions = chart.options,\n\t\t\tchartTitleOptions,\n\t\t\tchartSubtitleOptions;\n\n\t\tchartTitleOptions = options.title = merge(options.title, titleOptions);\n\t\tchartSubtitleOptions = options.subtitle = merge(options.subtitle, subtitleOptions);\n\n\t\t// add title and subtitle\n\t\teach([\n\t\t\t['title', titleOptions, chartTitleOptions],\n\t\t\t['subtitle', subtitleOptions, chartSubtitleOptions]\n\t\t], function (arr) {\n\t\t\tvar name = arr[0],\n\t\t\t\ttitle = chart[name],\n\t\t\t\ttitleOptions = arr[1],\n\t\t\t\tchartTitleOptions = arr[2];\n\n\t\t\tif (title && titleOptions) {\n\t\t\t\tchart[name] = title = title.destroy(); // remove old\n\t\t\t}\n\t\t\t\n\t\t\tif (chartTitleOptions && chartTitleOptions.text && !title) {\n\t\t\t\tchart[name] = chart.renderer.text(\n\t\t\t\t\tchartTitleOptions.text,\n\t\t\t\t\t0,\n\t\t\t\t\t0,\n\t\t\t\t\tchartTitleOptions.useHTML\n\t\t\t\t)\n\t\t\t\t.attr({\n\t\t\t\t\talign: chartTitleOptions.align,\n\t\t\t\t\t'class': PREFIX + name,\n\t\t\t\t\tzIndex: chartTitleOptions.zIndex || 4\n\t\t\t\t})\n\t\t\t\t.css(chartTitleOptions.style)\n\t\t\t\t.add();\n\t\t\t}\t\n\t\t});\n\t\tchart.layOutTitles();\n\t},\n\n\t/**\n\t * Lay out the chart titles and cache the full offset height for use in getMargins\n\t */\n\tlayOutTitles: function () {\n\t\tvar titleOffset = 0,\n\t\t\ttitle = this.title,\n\t\t\tsubtitle = this.subtitle,\n\t\t\toptions = this.options,\n\t\t\ttitleOptions = options.title,\n\t\t\tsubtitleOptions = options.subtitle,\n\t\t\tautoWidth = this.spacingBox.width - 44; // 44 makes room for default context button\n\n\t\tif (title) {\n\t\t\ttitle\n\t\t\t\t.css({ width: (titleOptions.width || autoWidth) + PX })\n\t\t\t\t.align(extend({ y: 15 }, titleOptions), false, 'spacingBox');\n\t\t\t\n\t\t\tif (!titleOptions.floating && !titleOptions.verticalAlign) {\n\t\t\t\ttitleOffset = title.getBBox().height;\n\n\t\t\t\t// Adjust for browser consistency + backwards compat after #776 fix\n\t\t\t\tif (titleOffset >= 18 && titleOffset <= 25) {\n\t\t\t\t\ttitleOffset = 15; \n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (subtitle) {\n\t\t\tsubtitle\n\t\t\t\t.css({ width: (subtitleOptions.width || autoWidth) + PX })\n\t\t\t\t.align(extend({ y: titleOffset + titleOptions.margin }, subtitleOptions), false, 'spacingBox');\n\t\t\t\n\t\t\tif (!subtitleOptions.floating && !subtitleOptions.verticalAlign) {\n\t\t\t\ttitleOffset = mathCeil(titleOffset + subtitle.getBBox().height);\n\t\t\t}\n\t\t}\n\n\t\tthis.titleOffset = titleOffset; // used in getMargins\n\t},\n\n\t/**\n\t * Get chart width and height according to options and container size\n\t */\n\tgetChartSize: function () {\n\t\tvar chart = this,\n\t\t\toptionsChart = chart.options.chart,\n\t\t\trenderTo = chart.renderToClone || chart.renderTo;\n\n\t\t// get inner width and height from jQuery (#824)\n\t\tchart.containerWidth = adapterRun(renderTo, 'width');\n\t\tchart.containerHeight = adapterRun(renderTo, 'height');\n\t\t\n\t\tchart.chartWidth = mathMax(0, optionsChart.width || chart.containerWidth || 600); // #1393, 1460\n\t\tchart.chartHeight = mathMax(0, pick(optionsChart.height,\n\t\t\t// the offsetHeight of an empty container is 0 in standard browsers, but 19 in IE7:\n\t\t\tchart.containerHeight > 19 ? chart.containerHeight : 400));\n\t},\n\n\t/**\n\t * Create a clone of the chart's renderTo div and place it outside the viewport to allow\n\t * size computation on chart.render and chart.redraw\n\t */\n\tcloneRenderTo: function (revert) {\n\t\tvar clone = this.renderToClone,\n\t\t\tcontainer = this.container;\n\t\t\n\t\t// Destroy the clone and bring the container back to the real renderTo div\n\t\tif (revert) {\n\t\t\tif (clone) {\n\t\t\t\tthis.renderTo.appendChild(container);\n\t\t\t\tdiscardElement(clone);\n\t\t\t\tdelete this.renderToClone;\n\t\t\t}\n\t\t\n\t\t// Set up the clone\n\t\t} else {\n\t\t\tif (container && container.parentNode === this.renderTo) {\n\t\t\t\tthis.renderTo.removeChild(container); // do not clone this\n\t\t\t}\n\t\t\tthis.renderToClone = clone = this.renderTo.cloneNode(0);\n\t\t\tcss(clone, {\n\t\t\t\tposition: ABSOLUTE,\n\t\t\t\ttop: '-9999px',\n\t\t\t\tdisplay: 'block' // #833\n\t\t\t});\n\t\t\tdoc.body.appendChild(clone);\n\t\t\tif (container) {\n\t\t\t\tclone.appendChild(container);\n\t\t\t}\n\t\t}\n\t},\n\n\t/**\n\t * Get the containing element, determine the size and create the inner container\n\t * div to hold the chart\n\t */\n\tgetContainer: function () {\n\t\tvar chart = this,\n\t\t\tcontainer,\n\t\t\toptionsChart = chart.options.chart,\n\t\t\tchartWidth,\n\t\t\tchartHeight,\n\t\t\trenderTo,\n\t\t\tindexAttrName = 'data-highcharts-chart',\n\t\t\toldChartIndex,\n\t\t\tcontainerId;\n\n\t\tchart.renderTo = renderTo = optionsChart.renderTo;\n\t\tcontainerId = PREFIX + idCounter++;\n\n\t\tif (isString(renderTo)) {\n\t\t\tchart.renderTo = renderTo = doc.getElementById(renderTo);\n\t\t}\n\t\t\n\t\t// Display an error if the renderTo is wrong\n\t\tif (!renderTo) {\n\t\t\terror(13, true);\n\t\t}\n\t\t\n\t\t// If the container already holds a chart, destroy it\n\t\toldChartIndex = pInt(attr(renderTo, indexAttrName));\n\t\tif (!isNaN(oldChartIndex) && charts[oldChartIndex]) {\n\t\t\tcharts[oldChartIndex].destroy();\n\t\t}\t\t\n\t\t\n\t\t// Make a reference to the chart from the div\n\t\tattr(renderTo, indexAttrName, chart.index);\n\n\t\t// remove previous chart\n\t\trenderTo.innerHTML = '';\n\n\t\t// If the container doesn't have an offsetWidth, it has or is a child of a node\n\t\t// that has display:none. We need to temporarily move it out to a visible\n\t\t// state to determine the size, else the legend and tooltips won't render\n\t\t// properly\n\t\tif (!renderTo.offsetWidth) {\n\t\t\tchart.cloneRenderTo();\n\t\t}\n\n\t\t// get the width and height\n\t\tchart.getChartSize();\n\t\tchartWidth = chart.chartWidth;\n\t\tchartHeight = chart.chartHeight;\n\n\t\t// create the inner container\n\t\tchart.container = container = createElement(DIV, {\n\t\t\t\tclassName: PREFIX + 'container' +\n\t\t\t\t\t(optionsChart.className ? ' ' + optionsChart.className : ''),\n\t\t\t\tid: containerId\n\t\t\t}, extend({\n\t\t\t\tposition: RELATIVE,\n\t\t\t\toverflow: HIDDEN, // needed for context menu (avoid scrollbars) and\n\t\t\t\t\t// content overflow in IE\n\t\t\t\twidth: chartWidth + PX,\n\t\t\t\theight: chartHeight + PX,\n\t\t\t\ttextAlign: 'left',\n\t\t\t\tlineHeight: 'normal', // #427\n\t\t\t\tzIndex: 0, // #1072\n\t\t\t\t'-webkit-tap-highlight-color': 'rgba(0,0,0,0)'\n\t\t\t}, optionsChart.style),\n\t\t\tchart.renderToClone || renderTo\n\t\t);\n\n\t\t// cache the cursor (#1650)\n\t\tchart._cursor = container.style.cursor;\n\n\t\tchart.renderer =\n\t\t\toptionsChart.forExport ? // force SVG, used for SVG export\n\t\t\t\tnew SVGRenderer(container, chartWidth, chartHeight, true) :\n\t\t\t\tnew Renderer(container, chartWidth, chartHeight);\n\n\t\tif (useCanVG) {\n\t\t\t// If we need canvg library, extend and configure the renderer\n\t\t\t// to get the tracker for translating mouse events\n\t\t\tchart.renderer.create(chart, container, chartWidth, chartHeight);\n\t\t}\n\t},\n\n\t/**\n\t * Calculate margins by rendering axis labels in a preliminary position. Title,\n\t * subtitle and legend have already been rendered at this stage, but will be\n\t * moved into their final positions\n\t */\n\tgetMargins: function () {\n\t\tvar chart = this,\n\t\t\tspacing = chart.spacing,\n\t\t\taxisOffset,\n\t\t\tlegend = chart.legend,\n\t\t\tmargin = chart.margin,\n\t\t\tlegendOptions = chart.options.legend,\n\t\t\tlegendMargin = pick(legendOptions.margin, 10),\n\t\t\tlegendX = legendOptions.x,\n\t\t\tlegendY = legendOptions.y,\n\t\t\talign = legendOptions.align,\n\t\t\tverticalAlign = legendOptions.verticalAlign,\n\t\t\ttitleOffset = chart.titleOffset;\n\n\t\tchart.resetMargins();\n\t\taxisOffset = chart.axisOffset;\n\n\t\t// Adjust for title and subtitle\n\t\tif (titleOffset && !defined(margin[0])) {\n\t\t\tchart.plotTop = mathMax(chart.plotTop, titleOffset + chart.options.title.margin + spacing[0]);\n\t\t}\n\t\t\n\t\t// Adjust for legend\n\t\tif (legend.display && !legendOptions.floating) {\n\t\t\tif (align === 'right') { // horizontal alignment handled first\n\t\t\t\tif (!defined(margin[1])) {\n\t\t\t\t\tchart.marginRight = mathMax(\n\t\t\t\t\t\tchart.marginRight,\n\t\t\t\t\t\tlegend.legendWidth - legendX + legendMargin + spacing[1]\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t} else if (align === 'left') {\n\t\t\t\tif (!defined(margin[3])) {\n\t\t\t\t\tchart.plotLeft = mathMax(\n\t\t\t\t\t\tchart.plotLeft,\n\t\t\t\t\t\tlegend.legendWidth + legendX + legendMargin + spacing[3]\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t} else if (verticalAlign === 'top') {\n\t\t\t\tif (!defined(margin[0])) {\n\t\t\t\t\tchart.plotTop = mathMax(\n\t\t\t\t\t\tchart.plotTop,\n\t\t\t\t\t\tlegend.legendHeight + legendY + legendMargin + spacing[0]\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t} else if (verticalAlign === 'bottom') {\n\t\t\t\tif (!defined(margin[2])) {\n\t\t\t\t\tchart.marginBottom = mathMax(\n\t\t\t\t\t\tchart.marginBottom,\n\t\t\t\t\t\tlegend.legendHeight - legendY + legendMargin + spacing[2]\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// adjust for scroller\n\t\tif (chart.extraBottomMargin) {\n\t\t\tchart.marginBottom += chart.extraBottomMargin;\n\t\t}\n\t\tif (chart.extraTopMargin) {\n\t\t\tchart.plotTop += chart.extraTopMargin;\n\t\t}\n\n\t\t// pre-render axes to get labels offset width\n\t\tif (chart.hasCartesianSeries) {\n\t\t\teach(chart.axes, function (axis) {\n\t\t\t\taxis.getOffset();\n\t\t\t});\n\t\t}\n\t\t\n\t\tif (!defined(margin[3])) {\n\t\t\tchart.plotLeft += axisOffset[3];\n\t\t}\n\t\tif (!defined(margin[0])) {\n\t\t\tchart.plotTop += axisOffset[0];\n\t\t}\n\t\tif (!defined(margin[2])) {\n\t\t\tchart.marginBottom += axisOffset[2];\n\t\t}\n\t\tif (!defined(margin[1])) {\n\t\t\tchart.marginRight += axisOffset[1];\n\t\t}\n\n\t\tchart.setChartSize();\n\n\t},\n\n\t/**\n\t * Add the event handlers necessary for auto resizing\n\t *\n\t */\n\tinitReflow: function () {\n\t\tvar chart = this,\n\t\t\toptionsChart = chart.options.chart,\n\t\t\trenderTo = chart.renderTo,\n\t\t\treflowTimeout;\n\t\t\t\n\t\tfunction reflow(e) {\n\t\t\tvar width = optionsChart.width || adapterRun(renderTo, 'width'),\n\t\t\t\theight = optionsChart.height || adapterRun(renderTo, 'height'),\n\t\t\t\ttarget = e ? e.target : win; // #805 - MooTools doesn't supply e\n\t\t\t\t\n\t\t\t// Width and height checks for display:none. Target is doc in IE8 and Opera,\n\t\t\t// win in Firefox, Chrome and IE9.\n\t\t\tif (!chart.hasUserSize && width && height && (target === win || target === doc)) {\n\t\t\t\t\n\t\t\t\tif (width !== chart.containerWidth || height !== chart.containerHeight) {\n\t\t\t\t\tclearTimeout(reflowTimeout);\n\t\t\t\t\tchart.reflowTimeout = reflowTimeout = setTimeout(function () {\n\t\t\t\t\t\tif (chart.container) { // It may have been destroyed in the meantime (#1257)\n\t\t\t\t\t\t\tchart.setSize(width, height, false);\n\t\t\t\t\t\t\tchart.hasUserSize = null;\n\t\t\t\t\t\t}\n\t\t\t\t\t}, 100);\n\t\t\t\t}\n\t\t\t\tchart.containerWidth = width;\n\t\t\t\tchart.containerHeight = height;\n\t\t\t}\n\t\t}\n\t\tchart.reflow = reflow;\n\t\taddEvent(win, 'resize', reflow);\n\t\taddEvent(chart, 'destroy', function () {\n\t\t\tremoveEvent(win, 'resize', reflow);\n\t\t});\n\t},\n\n\t/**\n\t * Resize the chart to a given width and height\n\t * @param {Number} width\n\t * @param {Number} height\n\t * @param {Object|Boolean} animation\n\t */\n\tsetSize: function (width, height, animation) {\n\t\tvar chart = this,\n\t\t\tchartWidth,\n\t\t\tchartHeight,\n\t\t\tfireEndResize;\n\n\t\t// Handle the isResizing counter\n\t\tchart.isResizing += 1;\n\t\tfireEndResize = function () {\n\t\t\tif (chart) {\n\t\t\t\tfireEvent(chart, 'endResize', null, function () {\n\t\t\t\t\tchart.isResizing -= 1;\n\t\t\t\t});\n\t\t\t}\n\t\t};\n\n\t\t// set the animation for the current process\n\t\tsetAnimation(animation, chart);\n\n\t\tchart.oldChartHeight = chart.chartHeight;\n\t\tchart.oldChartWidth = chart.chartWidth;\n\t\tif (defined(width)) {\n\t\t\tchart.chartWidth = chartWidth = mathMax(0, mathRound(width));\n\t\t\tchart.hasUserSize = !!chartWidth;\n\t\t}\n\t\tif (defined(height)) {\n\t\t\tchart.chartHeight = chartHeight = mathMax(0, mathRound(height));\n\t\t}\n\n\t\tcss(chart.container, {\n\t\t\twidth: chartWidth + PX,\n\t\t\theight: chartHeight + PX\n\t\t});\n\t\tchart.setChartSize(true);\n\t\tchart.renderer.setSize(chartWidth, chartHeight, animation);\n\n\t\t// handle axes\n\t\tchart.maxTicks = null;\n\t\teach(chart.axes, function (axis) {\n\t\t\taxis.isDirty = true;\n\t\t\taxis.setScale();\n\t\t});\n\n\t\t// make sure non-cartesian series are also handled\n\t\teach(chart.series, function (serie) {\n\t\t\tserie.isDirty = true;\n\t\t});\n\n\t\tchart.isDirtyLegend = true; // force legend redraw\n\t\tchart.isDirtyBox = true; // force redraw of plot and chart border\n\n\t\tchart.getMargins();\n\n\t\tchart.redraw(animation);\n\n\n\t\tchart.oldChartHeight = null;\n\t\tfireEvent(chart, 'resize');\n\n\t\t// fire endResize and set isResizing back\n\t\t// If animation is disabled, fire without delay\n\t\tif (globalAnimation === false) {\n\t\t\tfireEndResize();\n\t\t} else { // else set a timeout with the animation duration\n\t\t\tsetTimeout(fireEndResize, (globalAnimation && globalAnimation.duration) || 500);\n\t\t}\n\t},\n\n\t/**\n\t * Set the public chart properties. This is done before and after the pre-render\n\t * to determine margin sizes\n\t */\n\tsetChartSize: function (skipAxes) {\n\t\tvar chart = this,\n\t\t\tinverted = chart.inverted,\n\t\t\trenderer = chart.renderer,\n\t\t\tchartWidth = chart.chartWidth,\n\t\t\tchartHeight = chart.chartHeight,\n\t\t\toptionsChart = chart.options.chart,\n\t\t\tspacing = chart.spacing,\n\t\t\tclipOffset = chart.clipOffset,\n\t\t\tclipX,\n\t\t\tclipY,\n\t\t\tplotLeft,\n\t\t\tplotTop,\n\t\t\tplotWidth,\n\t\t\tplotHeight,\n\t\t\tplotBorderWidth;\n\n\t\tchart.plotLeft = plotLeft = mathRound(chart.plotLeft);\n\t\tchart.plotTop = plotTop = mathRound(chart.plotTop);\n\t\tchart.plotWidth = plotWidth = mathMax(0, mathRound(chartWidth - plotLeft - chart.marginRight));\n\t\tchart.plotHeight = plotHeight = mathMax(0, mathRound(chartHeight - plotTop - chart.marginBottom));\n\n\t\tchart.plotSizeX = inverted ? plotHeight : plotWidth;\n\t\tchart.plotSizeY = inverted ? plotWidth : plotHeight;\n\t\t\n\t\tchart.plotBorderWidth = optionsChart.plotBorderWidth || 0;\n\n\t\t// Set boxes used for alignment\n\t\tchart.spacingBox = renderer.spacingBox = {\n\t\t\tx: spacing[3],\n\t\t\ty: spacing[0],\n\t\t\twidth: chartWidth - spacing[3] - spacing[1],\n\t\t\theight: chartHeight - spacing[0] - spacing[2]\n\t\t};\n\t\tchart.plotBox = renderer.plotBox = {\n\t\t\tx: plotLeft,\n\t\t\ty: plotTop,\n\t\t\twidth: plotWidth,\n\t\t\theight: plotHeight\n\t\t};\n\n\t\tplotBorderWidth = 2 * mathFloor(chart.plotBorderWidth / 2);\n\t\tclipX = mathCeil(mathMax(plotBorderWidth, clipOffset[3]) / 2);\n\t\tclipY = mathCeil(mathMax(plotBorderWidth, clipOffset[0]) / 2);\n\t\tchart.clipBox = {\n\t\t\tx: clipX, \n\t\t\ty: clipY, \n\t\t\twidth: mathFloor(chart.plotSizeX - mathMax(plotBorderWidth, clipOffset[1]) / 2 - clipX), \n\t\t\theight: mathFloor(chart.plotSizeY - mathMax(plotBorderWidth, clipOffset[2]) / 2 - clipY)\n\t\t};\n\n\t\tif (!skipAxes) {\n\t\t\teach(chart.axes, function (axis) {\n\t\t\t\taxis.setAxisSize();\n\t\t\t\taxis.setAxisTranslation();\n\t\t\t});\n\t\t}\n\t},\n\n\t/**\n\t * Initial margins before auto size margins are applied\n\t */\n\tresetMargins: function () {\n\t\tvar chart = this,\n\t\t\tspacing = chart.spacing,\n\t\t\tmargin = chart.margin;\n\n\t\tchart.plotTop = pick(margin[0], spacing[0]);\n\t\tchart.marginRight = pick(margin[1], spacing[1]);\n\t\tchart.marginBottom = pick(margin[2], spacing[2]);\n\t\tchart.plotLeft = pick(margin[3], spacing[3]);\n\t\tchart.axisOffset = [0, 0, 0, 0]; // top, right, bottom, left\n\t\tchart.clipOffset = [0, 0, 0, 0];\n\t},\n\n\t/**\n\t * Draw the borders and backgrounds for chart and plot area\n\t */\n\tdrawChartBox: function () {\n\t\tvar chart = this,\n\t\t\toptionsChart = chart.options.chart,\n\t\t\trenderer = chart.renderer,\n\t\t\tchartWidth = chart.chartWidth,\n\t\t\tchartHeight = chart.chartHeight,\n\t\t\tchartBackground = chart.chartBackground,\n\t\t\tplotBackground = chart.plotBackground,\n\t\t\tplotBorder = chart.plotBorder,\n\t\t\tplotBGImage = chart.plotBGImage,\n\t\t\tchartBorderWidth = optionsChart.borderWidth || 0,\n\t\t\tchartBackgroundColor = optionsChart.backgroundColor,\n\t\t\tplotBackgroundColor = optionsChart.plotBackgroundColor,\n\t\t\tplotBackgroundImage = optionsChart.plotBackgroundImage,\n\t\t\tplotBorderWidth = optionsChart.plotBorderWidth || 0,\n\t\t\tmgn,\n\t\t\tbgAttr,\n\t\t\tplotLeft = chart.plotLeft,\n\t\t\tplotTop = chart.plotTop,\n\t\t\tplotWidth = chart.plotWidth,\n\t\t\tplotHeight = chart.plotHeight,\n\t\t\tplotBox = chart.plotBox,\n\t\t\tclipRect = chart.clipRect,\n\t\t\tclipBox = chart.clipBox;\n\n\t\t// Chart area\n\t\tmgn = chartBorderWidth + (optionsChart.shadow ? 8 : 0);\n\n\t\tif (chartBorderWidth || chartBackgroundColor) {\n\t\t\tif (!chartBackground) {\n\t\t\t\t\n\t\t\t\tbgAttr = {\n\t\t\t\t\tfill: chartBackgroundColor || NONE\n\t\t\t\t};\n\t\t\t\tif (chartBorderWidth) { // #980\n\t\t\t\t\tbgAttr.stroke = optionsChart.borderColor;\n\t\t\t\t\tbgAttr['stroke-width'] = chartBorderWidth;\n\t\t\t\t}\n\t\t\t\tchart.chartBackground = renderer.rect(mgn / 2, mgn / 2, chartWidth - mgn, chartHeight - mgn,\n\t\t\t\t\t\toptionsChart.borderRadius, chartBorderWidth)\n\t\t\t\t\t.attr(bgAttr)\n\t\t\t\t\t.add()\n\t\t\t\t\t.shadow(optionsChart.shadow);\n\n\t\t\t} else { // resize\n\t\t\t\tchartBackground.animate(\n\t\t\t\t\tchartBackground.crisp(null, null, null, chartWidth - mgn, chartHeight - mgn)\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\n\t\t// Plot background\n\t\tif (plotBackgroundColor) {\n\t\t\tif (!plotBackground) {\n\t\t\t\tchart.plotBackground = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0)\n\t\t\t\t\t.attr({\n\t\t\t\t\t\tfill: plotBackgroundColor\n\t\t\t\t\t})\n\t\t\t\t\t.add()\n\t\t\t\t\t.shadow(optionsChart.plotShadow);\n\t\t\t} else {\n\t\t\t\tplotBackground.animate(plotBox);\n\t\t\t}\n\t\t}\n\t\tif (plotBackgroundImage) {\n\t\t\tif (!plotBGImage) {\n\t\t\t\tchart.plotBGImage = renderer.image(plotBackgroundImage, plotLeft, plotTop, plotWidth, plotHeight)\n\t\t\t\t\t.add();\n\t\t\t} else {\n\t\t\t\tplotBGImage.animate(plotBox);\n\t\t\t}\n\t\t}\n\t\t\n\t\t// Plot clip\n\t\tif (!clipRect) {\n\t\t\tchart.clipRect = renderer.clipRect(clipBox);\n\t\t} else {\n\t\t\tclipRect.animate({\n\t\t\t\twidth: clipBox.width,\n\t\t\t\theight: clipBox.height\n\t\t\t});\n\t\t}\n\n\t\t// Plot area border\n\t\tif (plotBorderWidth) {\n\t\t\tif (!plotBorder) {\n\t\t\t\tchart.plotBorder = renderer.rect(plotLeft, plotTop, plotWidth, plotHeight, 0, -plotBorderWidth)\n\t\t\t\t\t.attr({\n\t\t\t\t\t\tstroke: optionsChart.plotBorderColor,\n\t\t\t\t\t\t'stroke-width': plotBorderWidth,\n\t\t\t\t\t\tzIndex: 1\n\t\t\t\t\t})\n\t\t\t\t\t.add();\n\t\t\t} else {\n\t\t\t\tplotBorder.animate(\n\t\t\t\t\tplotBorder.crisp(null, plotLeft, plotTop, plotWidth, plotHeight)\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\t// reset\n\t\tchart.isDirtyBox = false;\n\t},\n\n\t/**\n\t * Detect whether a certain chart property is needed based on inspecting its options\n\t * and series. This mainly applies to the chart.invert property, and in extensions to \n\t * the chart.angular and chart.polar properties.\n\t */\n\tpropFromSeries: function () {\n\t\tvar chart = this,\n\t\t\toptionsChart = chart.options.chart,\n\t\t\tklass,\n\t\t\tseriesOptions = chart.options.series,\n\t\t\ti,\n\t\t\tvalue;\n\t\t\t\n\t\t\t\n\t\teach(['inverted', 'angular', 'polar'], function (key) {\n\t\t\t\n\t\t\t// The default series type's class\n\t\t\tklass = seriesTypes[optionsChart.type || optionsChart.defaultSeriesType];\n\t\t\t\n\t\t\t// Get the value from available chart-wide properties\n\t\t\tvalue = (\n\t\t\t\tchart[key] || // 1. it is set before\n\t\t\t\toptionsChart[key] || // 2. it is set in the options\n\t\t\t\t(klass && klass.prototype[key]) // 3. it's default series class requires it\n\t\t\t);\n\t\n\t\t\t// 4. Check if any the chart's series require it\n\t\t\ti = seriesOptions && seriesOptions.length;\n\t\t\twhile (!value && i--) {\n\t\t\t\tklass = seriesTypes[seriesOptions[i].type];\n\t\t\t\tif (klass && klass.prototype[key]) {\n\t\t\t\t\tvalue = true;\n\t\t\t\t}\n\t\t\t}\n\t\n\t\t\t// Set the chart property\n\t\t\tchart[key] = value;\t\n\t\t});\n\t\t\n\t},\n\n\t/**\n\t * Link two or more series together. This is done initially from Chart.render,\n\t * and after Chart.addSeries and Series.remove.\n\t */\n\tlinkSeries: function () {\n\t\tvar chart = this,\n\t\t\tchartSeries = chart.series;\n\n\t\t// Reset links\n\t\teach(chartSeries, function (series) {\n\t\t\tseries.linkedSeries.length = 0;\n\t\t});\n\n\t\t// Apply new links\n\t\teach(chartSeries, function (series) {\n\t\t\tvar linkedTo = series.options.linkedTo;\n\t\t\tif (isString(linkedTo)) {\n\t\t\t\tif (linkedTo === ':previous') {\n\t\t\t\t\tlinkedTo = chart.series[series.index - 1];\n\t\t\t\t} else {\n\t\t\t\t\tlinkedTo = chart.get(linkedTo);\n\t\t\t\t}\n\t\t\t\tif (linkedTo) {\n\t\t\t\t\tlinkedTo.linkedSeries.push(series);\n\t\t\t\t\tseries.linkedParent = linkedTo;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t},\n\n\t/**\n\t * Render all graphics for the chart\n\t */\n\trender: function () {\n\t\tvar chart = this,\n\t\t\taxes = chart.axes,\n\t\t\trenderer = chart.renderer,\n\t\t\toptions = chart.options;\n\n\t\tvar labels = options.labels,\n\t\t\tcredits = options.credits,\n\t\t\tcreditsHref;\n\n\t\t// Title\n\t\tchart.setTitle();\n\n\n\t\t// Legend\n\t\tchart.legend = new Legend(chart, options.legend);\n\n\t\tchart.getStacks(); // render stacks\n\n\t\t// Get margins by pre-rendering axes\n\t\t// set axes scales\n\t\teach(axes, function (axis) {\n\t\t\taxis.setScale();\n\t\t});\n\n\t\tchart.getMargins();\n\n\t\tchart.maxTicks = null; // reset for second pass\n\t\teach(axes, function (axis) {\n\t\t\taxis.setTickPositions(true); // update to reflect the new margins\n\t\t\taxis.setMaxTicks();\n\t\t});\n\t\tchart.adjustTickAmounts();\n\t\tchart.getMargins(); // second pass to check for new labels\n\n\n\t\t// Draw the borders and backgrounds\n\t\tchart.drawChartBox();\t\t\n\n\n\t\t// Axes\n\t\tif (chart.hasCartesianSeries) {\n\t\t\teach(axes, function (axis) {\n\t\t\t\taxis.render();\n\t\t\t});\n\t\t}\n\n\t\t// The series\n\t\tif (!chart.seriesGroup) {\n\t\t\tchart.seriesGroup = renderer.g('series-group')\n\t\t\t\t.attr({ zIndex: 3 })\n\t\t\t\t.add();\n\t\t}\n\t\teach(chart.series, function (serie) {\n\t\t\tserie.translate();\n\t\t\tserie.setTooltipPoints();\n\t\t\tserie.render();\n\t\t});\n\n\t\t// Labels\n\t\tif (labels.items) {\n\t\t\teach(labels.items, function (label) {\n\t\t\t\tvar style = extend(labels.style, label.style),\n\t\t\t\t\tx = pInt(style.left) + chart.plotLeft,\n\t\t\t\t\ty = pInt(style.top) + chart.plotTop + 12;\n\n\t\t\t\t// delete to prevent rewriting in IE\n\t\t\t\tdelete style.left;\n\t\t\t\tdelete style.top;\n\n\t\t\t\trenderer.text(\n\t\t\t\t\tlabel.html,\n\t\t\t\t\tx,\n\t\t\t\t\ty\n\t\t\t\t)\n\t\t\t\t.attr({ zIndex: 2 })\n\t\t\t\t.css(style)\n\t\t\t\t.add();\n\n\t\t\t});\n\t\t}\n\n\t\t// Credits\n\t\tif (credits.enabled && !chart.credits) {\n\t\t\tcreditsHref = credits.href;\n\t\t\tchart.credits = renderer.text(\n\t\t\t\tcredits.text,\n\t\t\t\t0,\n\t\t\t\t0\n\t\t\t)\n\t\t\t.on('click', function () {\n\t\t\t\tif (creditsHref) {\n\t\t\t\t\tlocation.href = creditsHref;\n\t\t\t\t}\n\t\t\t})\n\t\t\t.attr({\n\t\t\t\talign: credits.position.align,\n\t\t\t\tzIndex: 8\n\t\t\t})\n\t\t\t.css(credits.style)\n\t\t\t.add()\n\t\t\t.align(credits.position);\n\t\t}\n\n\t\t// Set flag\n\t\tchart.hasRendered = true;\n\n\t},\n\n\t/**\n\t * Clean up memory usage\n\t */\n\tdestroy: function () {\n\t\tvar chart = this,\n\t\t\taxes = chart.axes,\n\t\t\tseries = chart.series,\n\t\t\tcontainer = chart.container,\n\t\t\ti,\n\t\t\tparentNode = container && container.parentNode;\n\t\t\t\n\t\t// fire the chart.destoy event\n\t\tfireEvent(chart, 'destroy');\n\t\t\n\t\t// Delete the chart from charts lookup array\n\t\tcharts[chart.index] = UNDEFINED;\n\t\tchart.renderTo.removeAttribute('data-highcharts-chart');\n\n\t\t// remove events\n\t\tremoveEvent(chart);\n\n\t\t// ==== Destroy collections:\n\t\t// Destroy axes\n\t\ti = axes.length;\n\t\twhile (i--) {\n\t\t\taxes[i] = axes[i].destroy();\n\t\t}\n\n\t\t// Destroy each series\n\t\ti = series.length;\n\t\twhile (i--) {\n\t\t\tseries[i] = series[i].destroy();\n\t\t}\n\n\t\t// ==== Destroy chart properties:\n\t\teach(['title', 'subtitle', 'chartBackground', 'plotBackground', 'plotBGImage', \n\t\t\t\t'plotBorder', 'seriesGroup', 'clipRect', 'credits', 'pointer', 'scroller', \n\t\t\t\t'rangeSelector', 'legend', 'resetZoomButton', 'tooltip', 'renderer'], function (name) {\n\t\t\tvar prop = chart[name];\n\n\t\t\tif (prop && prop.destroy) {\n\t\t\t\tchart[name] = prop.destroy();\n\t\t\t}\n\t\t});\n\n\t\t// remove container and all SVG\n\t\tif (container) { // can break in IE when destroyed before finished loading\n\t\t\tcontainer.innerHTML = '';\n\t\t\tremoveEvent(container);\n\t\t\tif (parentNode) {\n\t\t\t\tdiscardElement(container);\n\t\t\t}\n\n\t\t}\n\n\t\t// clean it all up\n\t\tfor (i in chart) {\n\t\t\tdelete chart[i];\n\t\t}\n\n\t},\n\n\n\t/**\n\t * VML namespaces can't be added until after complete. Listening\n\t * for Perini's doScroll hack is not enough.\n\t */\n\tisReadyToRender: function () {\n\t\tvar chart = this;\n\n\t\t// Note: in spite of JSLint's complaints, win == win.top is required\n\t\t/*jslint eqeq: true*/\n\t\tif ((!hasSVG && (win == win.top && doc.readyState !== 'complete')) || (useCanVG && !win.canvg)) {\n\t\t/*jslint eqeq: false*/\n\t\t\tif (useCanVG) {\n\t\t\t\t// Delay rendering until canvg library is downloaded and ready\n\t\t\t\tCanVGController.push(function () { chart.firstRender(); }, chart.options.global.canvasToolsURL);\n\t\t\t} else {\n\t\t\t\tdoc.attachEvent('onreadystatechange', function () {\n\t\t\t\t\tdoc.detachEvent('onreadystatechange', chart.firstRender);\n\t\t\t\t\tif (doc.readyState === 'complete') {\n\t\t\t\t\t\tchart.firstRender();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t},\n\n\t/**\n\t * Prepare for first rendering after all data are loaded\n\t */\n\tfirstRender: function () {\n\t\tvar chart = this,\n\t\t\toptions = chart.options,\n\t\t\tcallback = chart.callback;\n\n\t\t// Check whether the chart is ready to render\n\t\tif (!chart.isReadyToRender()) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Create the container\n\t\tchart.getContainer();\n\n\t\t// Run an early event after the container and renderer are established\n\t\tfireEvent(chart, 'init');\n\n\t\t\n\t\tchart.resetMargins();\n\t\tchart.setChartSize();\n\n\t\t// Set the common chart properties (mainly invert) from the given series\n\t\tchart.propFromSeries();\n\n\t\t// get axes\n\t\tchart.getAxes();\n\n\t\t// Initialize the series\n\t\teach(options.series || [], function (serieOptions) {\n\t\t\tchart.initSeries(serieOptions);\n\t\t});\n\n\t\tchart.linkSeries();\n\n\t\t// Run an event after axes and series are initialized, but before render. At this stage,\n\t\t// the series data is indexed and cached in the xData and yData arrays, so we can access\n\t\t// those before rendering. Used in Highstock. \n\t\tfireEvent(chart, 'beforeRender'); \n\n\t\t// depends on inverted and on margins being set\n\t\tchart.pointer = new Pointer(chart, options);\n\n\t\tchart.render();\n\n\t\t// add canvas\n\t\tchart.renderer.draw();\n\t\t// run callbacks\n\t\tif (callback) {\n\t\t\tcallback.apply(chart, [chart]);\n\t\t}\n\t\teach(chart.callbacks, function (fn) {\n\t\t\tfn.apply(chart, [chart]);\n\t\t});\n\t\t\n\t\t\n\t\t// If the chart was rendered outside the top container, put it back in\n\t\tchart.cloneRenderTo(true);\n\n\t\tfireEvent(chart, 'load');\n\n\t},\n\n\t/**\n\t* Creates arrays for spacing and margin from given options.\n\t*/\n\tsplashArray: function (target, options) {\n\t\tvar oVar = options[target],\n\t\t\ttArray = isObject(oVar) ? oVar : [oVar, oVar, oVar, oVar];\n\n\t\treturn [pick(options[target + 'Top'], tArray[0]),\n\t\t\t\tpick(options[target + 'Right'], tArray[1]),\n\t\t\t\tpick(options[target + 'Bottom'], tArray[2]),\n\t\t\t\tpick(options[target + 'Left'], tArray[3])];\n\t}\n}; // end Chart\n\n// Hook for exporting module\nChart.prototype.callbacks = [];\n/**\n * The Point object and prototype. Inheritable and used as base for PiePoint\n */\nvar Point = function () {};\nPoint.prototype = {\n\n\t/**\n\t * Initialize the point\n\t * @param {Object} series The series object containing this point\n\t * @param {Object} options The data in either number, array or object format\n\t */\n\tinit: function (series, options, x) {\n\n\t\tvar point = this,\n\t\t\tcolors;\n\t\tpoint.series = series;\n\t\tpoint.applyOptions(options, x);\n\t\tpoint.pointAttr = {};\n\n\t\tif (series.options.colorByPoint) {\n\t\t\tcolors = series.options.colors || series.chart.options.colors;\n\t\t\tpoint.color = point.color || colors[series.colorCounter++];\n\t\t\t// loop back to zero\n\t\t\tif (series.colorCounter === colors.length) {\n\t\t\t\tseries.colorCounter = 0;\n\t\t\t}\n\t\t}\n\n\t\tseries.chart.pointCount++;\n\t\treturn point;\n\t},\n\t/**\n\t * Apply the options containing the x and y data and possible some extra properties.\n\t * This is called on point init or from point.update.\n\t *\n\t * @param {Object} options\n\t */\n\tapplyOptions: function (options, x) {\n\t\tvar point = this,\n\t\t\tseries = point.series,\n\t\t\tpointValKey = series.pointValKey;\n\n\t\toptions = Point.prototype.optionsToObject.call(this, options);\n\n\t\t// copy options directly to point\n\t\textend(point, options);\n\t\tpoint.options = point.options ? extend(point.options, options) : options;\n\t\t\t\n\t\t// For higher dimension series types. For instance, for ranges, point.y is mapped to point.low.\n\t\tif (pointValKey) {\n\t\t\tpoint.y = point[pointValKey];\n\t\t}\n\t\t\n\t\t// If no x is set by now, get auto incremented value. All points must have an\n\t\t// x value, however the y value can be null to create a gap in the series\n\t\tif (point.x === UNDEFINED && series) {\n\t\t\tpoint.x = x === UNDEFINED ? series.autoIncrement() : x;\n\t\t}\n\t\t\n\t\treturn point;\n\t},\n\n\t/**\n\t * Transform number or array configs into objects\n\t */\n\toptionsToObject: function (options) {\n\t\tvar ret,\n\t\t\tseries = this.series,\n\t\t\tpointArrayMap = series.pointArrayMap || ['y'],\n\t\t\tvalueCount = pointArrayMap.length,\n\t\t\tfirstItemType,\n\t\t\ti = 0,\n\t\t\tj = 0;\n\n\t\tif (typeof options === 'number' || options === null) {\n\t\t\tret = { y: options };\n\n\t\t} else if (isArray(options)) {\n\t\t\tret = {};\n\t\t\t// with leading x value\n\t\t\tif (options.length > valueCount) {\n\t\t\t\tfirstItemType = typeof options[0];\n\t\t\t\tif (firstItemType === 'string') {\n\t\t\t\t\tret.name = options[0];\n\t\t\t\t} else if (firstItemType === 'number') {\n\t\t\t\t\tret.x = options[0];\n\t\t\t\t}\n\t\t\t\ti++;\n\t\t\t}\n\t\t\twhile (j < valueCount) {\n\t\t\t\tret[pointArrayMap[j++]] = options[i++];\n\t\t\t}\t\t\t\n\t\t} else if (typeof options === 'object') {\n\t\t\tret = options;\n\n\t\t\t// This is the fastest way to detect if there are individual point dataLabels that need \n\t\t\t// to be considered in drawDataLabels. These can only occur in object configs.\n\t\t\tif (options.dataLabels) {\n\t\t\t\tseries._hasPointLabels = true;\n\t\t\t}\n\n\t\t\t// Same approach as above for markers\n\t\t\tif (options.marker) {\n\t\t\t\tseries._hasPointMarkers = true;\n\t\t\t}\n\t\t}\n\t\treturn ret;\n\t},\n\n\t/**\n\t * Destroy a point to clear memory. Its reference still stays in series.data.\n\t */\n\tdestroy: function () {\n\t\tvar point = this,\n\t\t\tseries = point.series,\n\t\t\tchart = series.chart,\n\t\t\thoverPoints = chart.hoverPoints,\n\t\t\tprop;\n\n\t\tchart.pointCount--;\n\n\t\tif (hoverPoints) {\n\t\t\tpoint.setState();\n\t\t\terase(hoverPoints, point);\n\t\t\tif (!hoverPoints.length) {\n\t\t\t\tchart.hoverPoints = null;\n\t\t\t}\n\n\t\t}\n\t\tif (point === chart.hoverPoint) {\n\t\t\tpoint.onMouseOut();\n\t\t}\n\t\t\n\t\t// remove all events\n\t\tif (point.graphic || point.dataLabel) { // removeEvent and destroyElements are performance expensive\n\t\t\tremoveEvent(point);\n\t\t\tpoint.destroyElements();\n\t\t}\n\n\t\tif (point.legendItem) { // pies have legend items\n\t\t\tchart.legend.destroyItem(point);\n\t\t}\n\n\t\tfor (prop in point) {\n\t\t\tpoint[prop] = null;\n\t\t}\n\n\n\t},\n\n\t/**\n\t * Destroy SVG elements associated with the point\n\t */\n\tdestroyElements: function () {\n\t\tvar point = this,\n\t\t\tprops = ['graphic', 'dataLabel', 'dataLabelUpper', 'group', 'connector', 'shadowGroup'],\n\t\t\tprop,\n\t\t\ti = 6;\n\t\twhile (i--) {\n\t\t\tprop = props[i];\n\t\t\tif (point[prop]) {\n\t\t\t\tpoint[prop] = point[prop].destroy();\n\t\t\t}\n\t\t}\n\t},\n\n\t/**\n\t * Return the configuration hash needed for the data label and tooltip formatters\n\t */\n\tgetLabelConfig: function () {\n\t\tvar point = this;\n\t\treturn {\n\t\t\tx: point.category,\n\t\t\ty: point.y,\n\t\t\tkey: point.name || point.category,\n\t\t\tseries: point.series,\n\t\t\tpoint: point,\n\t\t\tpercentage: point.percentage,\n\t\t\ttotal: point.total || point.stackTotal\n\t\t};\n\t},\n\n\t/**\n\t * Toggle the selection status of a point\n\t * @param {Boolean} selected Whether to select or unselect the point.\n\t * @param {Boolean} accumulate Whether to add to the previous selection. By default,\n\t *     this happens if the control key (Cmd on Mac) was pressed during clicking.\n\t */\n\tselect: function (selected, accumulate) {\n\t\tvar point = this,\n\t\t\tseries = point.series,\n\t\t\tchart = series.chart;\n\n\t\tselected = pick(selected, !point.selected);\n\n\t\t// fire the event with the defalut handler\n\t\tpoint.firePointEvent(selected ? 'select' : 'unselect', { accumulate: accumulate }, function () {\n\t\t\tpoint.selected = point.options.selected = selected;\n\t\t\tseries.options.data[inArray(point, series.data)] = point.options;\n\t\t\t\n\t\t\tpoint.setState(selected && SELECT_STATE);\n\n\t\t\t// unselect all other points unless Ctrl or Cmd + click\n\t\t\tif (!accumulate) {\n\t\t\t\teach(chart.getSelectedPoints(), function (loopPoint) {\n\t\t\t\t\tif (loopPoint.selected && loopPoint !== point) {\n\t\t\t\t\t\tloopPoint.selected = loopPoint.options.selected = false;\n\t\t\t\t\t\tseries.options.data[inArray(loopPoint, series.data)] = loopPoint.options;\n\t\t\t\t\t\tloopPoint.setState(NORMAL_STATE);\n\t\t\t\t\t\tloopPoint.firePointEvent('unselect');\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t});\n\t},\n\n\t/**\n\t * Runs on mouse over the point\n\t */\n\tonMouseOver: function (e) {\n\t\tvar point = this,\n\t\t\tseries = point.series,\n\t\t\tchart = series.chart,\n\t\t\ttooltip = chart.tooltip,\n\t\t\thoverPoint = chart.hoverPoint;\n\n\t\t// set normal state to previous series\n\t\tif (hoverPoint && hoverPoint !== point) {\n\t\t\thoverPoint.onMouseOut();\n\t\t}\n\n\t\t// trigger the event\n\t\tpoint.firePointEvent('mouseOver');\n\n\t\t// update the tooltip\n\t\tif (tooltip && (!tooltip.shared || series.noSharedTooltip)) {\n\t\t\ttooltip.refresh(point, e);\n\t\t}\n\n\t\t// hover this\n\t\tpoint.setState(HOVER_STATE);\n\t\tchart.hoverPoint = point;\n\t},\n\t\n\t/**\n\t * Runs on mouse out from the point\n\t */\n\tonMouseOut: function () {\n\t\tvar chart = this.series.chart,\n\t\t\thoverPoints = chart.hoverPoints;\n\t\t\n\t\tif (!hoverPoints || inArray(this, hoverPoints) === -1) { // #887\n\t\t\tthis.firePointEvent('mouseOut');\n\t\n\t\t\tthis.setState();\n\t\t\tchart.hoverPoint = null;\n\t\t}\n\t},\n\n\t/**\n\t * Extendable method for formatting each point's tooltip line\n\t *\n\t * @return {String} A string to be concatenated in to the common tooltip text\n\t */\n\ttooltipFormatter: function (pointFormat) {\n\t\t\n\t\t// Insert options for valueDecimals, valuePrefix, and valueSuffix\n\t\tvar series = this.series,\n\t\t\tseriesTooltipOptions = series.tooltipOptions,\n\t\t\tvalueDecimals = pick(seriesTooltipOptions.valueDecimals, ''),\n\t\t\tvaluePrefix = seriesTooltipOptions.valuePrefix || '',\n\t\t\tvalueSuffix = seriesTooltipOptions.valueSuffix || '';\n\t\t\t\n\t\t// Loop over the point array map and replace unformatted values with sprintf formatting markup\n\t\teach(series.pointArrayMap || ['y'], function (key) {\n\t\t\tkey = '{point.' + key; // without the closing bracket\n\t\t\tif (valuePrefix || valueSuffix) {\n\t\t\t\tpointFormat = pointFormat.replace(key + '}', valuePrefix + key + '}' + valueSuffix);\n\t\t\t}\n\t\t\tpointFormat = pointFormat.replace(key + '}', key + ':,.' + valueDecimals + 'f}');\n\t\t});\n\t\t\n\t\treturn format(pointFormat, {\n\t\t\tpoint: this,\n\t\t\tseries: this.series\n\t\t});\n\t},\n\n\t/**\n\t * Update the point with new options (typically x/y data) and optionally redraw the series.\n\t *\n\t * @param {Object} options Point options as defined in the series.data array\n\t * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call\n\t * @param {Boolean|Object} animation Whether to apply animation, and optionally animation\n\t *    configuration\n\t *\n\t */\n\tupdate: function (options, redraw, animation) {\n\t\tvar point = this,\n\t\t\tseries = point.series,\n\t\t\tgraphic = point.graphic,\n\t\t\ti,\n\t\t\tdata = series.data,\n\t\t\tchart = series.chart,\n\t\t\tseriesOptions = series.options;\n\n\t\tredraw = pick(redraw, true);\n\n\t\t// fire the event with a default handler of doing the update\n\t\tpoint.firePointEvent('update', { options: options }, function () {\n\n\t\t\tpoint.applyOptions(options);\n\n\t\t\t// update visuals\n\t\t\tif (isObject(options)) {\n\t\t\t\tseries.getAttribs();\n\t\t\t\tif (graphic) {\n\t\t\t\t\tif (options.marker && options.marker.symbol) {\n\t\t\t\t\t\tpoint.graphic = graphic.destroy();\n\t\t\t\t\t} else {\n\t\t\t\t\t\tgraphic.attr(point.pointAttr[point.state || '']);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// record changes in the parallel arrays\n\t\t\ti = inArray(point, data);\n\t\t\tseries.xData[i] = point.x;\n\t\t\tseries.yData[i] = series.toYData ? series.toYData(point) : point.y;\n\t\t\tseries.zData[i] = point.z;\n\t\t\tseriesOptions.data[i] = point.options;\n\n\t\t\t// redraw\n\t\t\tseries.isDirty = series.isDirtyData = true;\n\t\t\tif (!series.fixedBox && series.hasCartesianSeries) { // #1906, #2320\n\t\t\t\tchart.isDirtyBox = true;\n\t\t\t}\n\t\t\t\n\t\t\tif (seriesOptions.legendType === 'point') { // #1831, #1885\n\t\t\t\tchart.legend.destroyItem(point);\n\t\t\t}\n\t\t\tif (redraw) {\n\t\t\t\tchart.redraw(animation);\n\t\t\t}\n\t\t});\n\t},\n\n\t/**\n\t * Remove a point and optionally redraw the series and if necessary the axes\n\t * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call\n\t * @param {Boolean|Object} animation Whether to apply animation, and optionally animation\n\t *    configuration\n\t */\n\tremove: function (redraw, animation) {\n\t\tvar point = this,\n\t\t\tseries = point.series,\n\t\t\tpoints = series.points,\n\t\t\tchart = series.chart,\n\t\t\ti,\n\t\t\tdata = series.data;\n\n\t\tsetAnimation(animation, chart);\n\t\tredraw = pick(redraw, true);\n\n\t\t// fire the event with a default handler of removing the point\n\t\tpoint.firePointEvent('remove', null, function () {\n\n\t\t\t// splice all the parallel arrays\n\t\t\ti = inArray(point, data);\n\t\t\tif (data.length === points.length) {\n\t\t\t\tpoints.splice(i, 1);\t\t\t\n\t\t\t}\n\t\t\tdata.splice(i, 1);\n\t\t\tseries.options.data.splice(i, 1);\n\t\t\tseries.xData.splice(i, 1);\n\t\t\tseries.yData.splice(i, 1);\n\t\t\tseries.zData.splice(i, 1);\n\n\t\t\tpoint.destroy();\n\n\n\t\t\t// redraw\n\t\t\tseries.isDirty = true;\n\t\t\tseries.isDirtyData = true;\n\t\t\tif (redraw) {\n\t\t\t\tchart.redraw();\n\t\t\t}\n\t\t});\n\n\n\t},\n\n\t/**\n\t * Fire an event on the Point object. Must not be renamed to fireEvent, as this\n\t * causes a name clash in MooTools\n\t * @param {String} eventType\n\t * @param {Object} eventArgs Additional event arguments\n\t * @param {Function} defaultFunction Default event handler\n\t */\n\tfirePointEvent: function (eventType, eventArgs, defaultFunction) {\n\t\tvar point = this,\n\t\t\tseries = this.series,\n\t\t\tseriesOptions = series.options;\n\n\t\t// load event handlers on demand to save time on mouseover/out\n\t\tif (seriesOptions.point.events[eventType] || (point.options && point.options.events && point.options.events[eventType])) {\n\t\t\tthis.importEvents();\n\t\t}\n\n\t\t// add default handler if in selection mode\n\t\tif (eventType === 'click' && seriesOptions.allowPointSelect) {\n\t\t\tdefaultFunction = function (event) {\n\t\t\t\t// Control key is for Windows, meta (= Cmd key) for Mac, Shift for Opera\n\t\t\t\tpoint.select(null, event.ctrlKey || event.metaKey || event.shiftKey);\n\t\t\t};\n\t\t}\n\n\t\tfireEvent(this, eventType, eventArgs, defaultFunction);\n\t},\n\t/**\n\t * Import events from the series' and point's options. Only do it on\n\t * demand, to save processing time on hovering.\n\t */\n\timportEvents: function () {\n\t\tif (!this.hasImportedEvents) {\n\t\t\tvar point = this,\n\t\t\t\toptions = merge(point.series.options.point, point.options),\n\t\t\t\tevents = options.events,\n\t\t\t\teventType;\n\n\t\t\tpoint.events = events;\n\n\t\t\tfor (eventType in events) {\n\t\t\t\taddEvent(point, eventType, events[eventType]);\n\t\t\t}\n\t\t\tthis.hasImportedEvents = true;\n\n\t\t}\n\t},\n\n\t/**\n\t * Set the point's state\n\t * @param {String} state\n\t */\n\tsetState: function (state) {\n\t\tvar point = this,\n\t\t\tplotX = point.plotX,\n\t\t\tplotY = point.plotY,\n\t\t\tseries = point.series,\n\t\t\tstateOptions = series.options.states,\n\t\t\tmarkerOptions = defaultPlotOptions[series.type].marker && series.options.marker,\n\t\t\tnormalDisabled = markerOptions && !markerOptions.enabled,\n\t\t\tmarkerStateOptions = markerOptions && markerOptions.states[state],\n\t\t\tstateDisabled = markerStateOptions && markerStateOptions.enabled === false,\n\t\t\tstateMarkerGraphic = series.stateMarkerGraphic,\n\t\t\tpointMarker = point.marker || {},\n\t\t\tchart = series.chart,\n\t\t\tradius,\n\t\t\tnewSymbol,\n\t\t\tpointAttr = point.pointAttr;\n\n\t\tstate = state || NORMAL_STATE; // empty string\n\n\t\tif (\n\t\t\t\t// already has this state\n\t\t\t\tstate === point.state ||\n\t\t\t\t// selected points don't respond to hover\n\t\t\t\t(point.selected && state !== SELECT_STATE) ||\n\t\t\t\t// series' state options is disabled\n\t\t\t\t(stateOptions[state] && stateOptions[state].enabled === false) ||\n\t\t\t\t// point marker's state options is disabled\n\t\t\t\t(state && (stateDisabled || (normalDisabled && !markerStateOptions.enabled)))\n\n\t\t\t) {\n\t\t\treturn;\n\t\t}\n\n\t\t// apply hover styles to the existing point\n\t\tif (point.graphic) {\n\t\t\tradius = markerOptions && point.graphic.symbolName && pointAttr[state].r;\n\t\t\tpoint.graphic.attr(merge(\n\t\t\t\tpointAttr[state],\n\t\t\t\tradius ? { // new symbol attributes (#507, #612)\n\t\t\t\t\tx: plotX - radius,\n\t\t\t\t\ty: plotY - radius,\n\t\t\t\t\twidth: 2 * radius,\n\t\t\t\t\theight: 2 * radius\n\t\t\t\t} : {}\n\t\t\t));\n\t\t} else {\n\t\t\t// if a graphic is not applied to each point in the normal state, create a shared\n\t\t\t// graphic for the hover state\n\t\t\tif (state && markerStateOptions) {\n\t\t\t\tradius = markerStateOptions.radius;\n\t\t\t\tnewSymbol = pointMarker.symbol || series.symbol;\n\n\t\t\t\t// If the point has another symbol than the previous one, throw away the \n\t\t\t\t// state marker graphic and force a new one (#1459)\n\t\t\t\tif (stateMarkerGraphic && stateMarkerGraphic.currentSymbol !== newSymbol) {\t\t\t\t\n\t\t\t\t\tstateMarkerGraphic = stateMarkerGraphic.destroy();\n\t\t\t\t}\n\n\t\t\t\t// Add a new state marker graphic\n\t\t\t\tif (!stateMarkerGraphic) {\n\t\t\t\t\tseries.stateMarkerGraphic = stateMarkerGraphic = chart.renderer.symbol(\n\t\t\t\t\t\tnewSymbol,\n\t\t\t\t\t\tplotX - radius,\n\t\t\t\t\t\tplotY - radius,\n\t\t\t\t\t\t2 * radius,\n\t\t\t\t\t\t2 * radius\n\t\t\t\t\t)\n\t\t\t\t\t.attr(pointAttr[state])\n\t\t\t\t\t.add(series.markerGroup);\n\t\t\t\t\tstateMarkerGraphic.currentSymbol = newSymbol;\n\t\t\t\t\n\t\t\t\t// Move the existing graphic\n\t\t\t\t} else {\n\t\t\t\t\tstateMarkerGraphic.attr({ // #1054\n\t\t\t\t\t\tx: plotX - radius,\n\t\t\t\t\t\ty: plotY - radius\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (stateMarkerGraphic) {\n\t\t\t\tstateMarkerGraphic[state && chart.isInsidePlot(plotX, plotY) ? 'show' : 'hide']();\n\t\t\t}\n\t\t}\n\n\t\tpoint.state = state;\n\t}\n};\n\n/**\n * @classDescription The base function which all other series types inherit from. The data in the series is stored\n * in various arrays.\n *\n * - First, series.options.data contains all the original config options for\n * each point whether added by options or methods like series.addPoint.\n * - Next, series.data contains those values converted to points, but in case the series data length\n * exceeds the cropThreshold, or if the data is grouped, series.data doesn't contain all the points. It\n * only contains the points that have been created on demand.\n * - Then there's series.points that contains all currently visible point objects. In case of cropping,\n * the cropped-away points are not part of this array. The series.points array starts at series.cropStart\n * compared to series.data and series.options.data. If however the series data is grouped, these can't\n * be correlated one to one.\n * - series.xData and series.processedXData contain clean x values, equivalent to series.data and series.points.\n * - series.yData and series.processedYData contain clean x values, equivalent to series.data and series.points.\n *\n * @param {Object} chart\n * @param {Object} options\n */\nvar Series = function () {};\n\nSeries.prototype = {\n\n\tisCartesian: true,\n\ttype: 'line',\n\tpointClass: Point,\n\tsorted: true, // requires the data to be sorted\n\trequireSorting: true,\n\tpointAttrToOptions: { // mapping between SVG attributes and the corresponding options\n\t\tstroke: 'lineColor',\n\t\t'stroke-width': 'lineWidth',\n\t\tfill: 'fillColor',\n\t\tr: 'radius'\n\t},\n\tcolorCounter: 0,\n\tinit: function (chart, options) {\n\t\tvar series = this,\n\t\t\teventType,\n\t\t\tevents,\n\t\t\tchartSeries = chart.series;\n\n\t\tseries.chart = chart;\n\t\tseries.options = options = series.setOptions(options); // merge with plotOptions\n\t\tseries.linkedSeries = [];\n\n\t\t// bind the axes\n\t\tseries.bindAxes();\n\n\t\t// set some variables\n\t\textend(series, {\n\t\t\tname: options.name,\n\t\t\tstate: NORMAL_STATE,\n\t\t\tpointAttr: {},\n\t\t\tvisible: options.visible !== false, // true by default\n\t\t\tselected: options.selected === true // false by default\n\t\t});\n\t\t\n\t\t// special\n\t\tif (useCanVG) {\n\t\t\toptions.animation = false;\n\t\t}\n\n\t\t// register event listeners\n\t\tevents = options.events;\n\t\tfor (eventType in events) {\n\t\t\taddEvent(series, eventType, events[eventType]);\n\t\t}\n\t\tif (\n\t\t\t(events && events.click) ||\n\t\t\t(options.point && options.point.events && options.point.events.click) ||\n\t\t\toptions.allowPointSelect\n\t\t) {\n\t\t\tchart.runTrackerClick = true;\n\t\t}\n\n\t\tseries.getColor();\n\t\tseries.getSymbol();\n\n\t\t// set the data\n\t\tseries.setData(options.data, false);\n\t\t\n\t\t// Mark cartesian\n\t\tif (series.isCartesian) {\n\t\t\tchart.hasCartesianSeries = true;\n\t\t}\n\n\t\t// Register it in the chart\n\t\tchartSeries.push(series);\n\t\tseries._i = chartSeries.length - 1;\n\t\t\n\t\t// Sort series according to index option (#248, #1123)\n\t\tstableSort(chartSeries, function (a, b) {\n\t\t\treturn pick(a.options.index, a._i) - pick(b.options.index, a._i);\n\t\t});\n\t\teach(chartSeries, function (series, i) {\n\t\t\tseries.index = i;\n\t\t\tseries.name = series.name || 'Series ' + (i + 1);\n\t\t});\n\n\t},\n\t\n\t/**\n\t * Set the xAxis and yAxis properties of cartesian series, and register the series\n\t * in the axis.series array\n\t */\n\tbindAxes: function () {\n\t\tvar series = this,\n\t\t\tseriesOptions = series.options,\n\t\t\tchart = series.chart,\n\t\t\taxisOptions;\n\t\t\t\n\t\tif (series.isCartesian) {\n\t\t\t\n\t\t\teach(['xAxis', 'yAxis'], function (AXIS) { // repeat for xAxis and yAxis\n\t\t\t\t\n\t\t\t\teach(chart[AXIS], function (axis) { // loop through the chart's axis objects\n\t\t\t\t\t\n\t\t\t\t\taxisOptions = axis.options;\n\t\t\t\t\t\n\t\t\t\t\t// apply if the series xAxis or yAxis option mathches the number of the \n\t\t\t\t\t// axis, or if undefined, use the first axis\n\t\t\t\t\tif ((seriesOptions[AXIS] === axisOptions.index) ||\n\t\t\t\t\t\t\t(seriesOptions[AXIS] !== UNDEFINED && seriesOptions[AXIS] === axisOptions.id) ||\n\t\t\t\t\t\t\t(seriesOptions[AXIS] === UNDEFINED && axisOptions.index === 0)) {\n\t\t\t\t\t\t\n\t\t\t\t\t\t// register this series in the axis.series lookup\n\t\t\t\t\t\taxis.series.push(series);\n\t\t\t\t\t\t\n\t\t\t\t\t\t// set this series.xAxis or series.yAxis reference\n\t\t\t\t\t\tseries[AXIS] = axis;\n\t\t\t\t\t\t\n\t\t\t\t\t\t// mark dirty for redraw\n\t\t\t\t\t\taxis.isDirty = true;\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\t// The series needs an X and an Y axis\n\t\t\t\tif (!series[AXIS]) {\n\t\t\t\t\terror(18, true);\n\t\t\t\t}\n\n\t\t\t});\n\t\t}\n\t},\n\n\n\t/**\n\t * Return an auto incremented x value based on the pointStart and pointInterval options.\n\t * This is only used if an x value is not given for the point that calls autoIncrement.\n\t */\n\tautoIncrement: function () {\n\t\tvar series = this,\n\t\t\toptions = series.options,\n\t\t\txIncrement = series.xIncrement;\n\n\t\txIncrement = pick(xIncrement, options.pointStart, 0);\n\n\t\tseries.pointInterval = pick(series.pointInterval, options.pointInterval, 1);\n\n\t\tseries.xIncrement = xIncrement + series.pointInterval;\n\t\treturn xIncrement;\n\t},\n\n\t/**\n\t * Divide the series data into segments divided by null values.\n\t */\n\tgetSegments: function () {\n\t\tvar series = this,\n\t\t\tlastNull = -1,\n\t\t\tsegments = [],\n\t\t\ti,\n\t\t\tpoints = series.points,\n\t\t\tpointsLength = points.length;\n\n\t\tif (pointsLength) { // no action required for []\n\t\t\t\n\t\t\t// if connect nulls, just remove null points\n\t\t\tif (series.options.connectNulls) {\n\t\t\t\ti = pointsLength;\n\t\t\t\twhile (i--) {\n\t\t\t\t\tif (points[i].y === null) {\n\t\t\t\t\t\tpoints.splice(i, 1);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (points.length) {\n\t\t\t\t\tsegments = [points];\n\t\t\t\t}\n\t\t\t\t\n\t\t\t// else, split on null points\n\t\t\t} else {\n\t\t\t\teach(points, function (point, i) {\n\t\t\t\t\tif (point.y === null) {\n\t\t\t\t\t\tif (i > lastNull + 1) {\n\t\t\t\t\t\t\tsegments.push(points.slice(lastNull + 1, i));\n\t\t\t\t\t\t}\n\t\t\t\t\t\tlastNull = i;\n\t\t\t\t\t} else if (i === pointsLength - 1) { // last value\n\t\t\t\t\t\tsegments.push(points.slice(lastNull + 1, i + 1));\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\t\n\t\t// register it\n\t\tseries.segments = segments;\n\t},\n\t\n\t/**\n\t * Set the series options by merging from the options tree\n\t * @param {Object} itemOptions\n\t */\n\tsetOptions: function (itemOptions) {\n\t\tvar chart = this.chart,\n\t\t\tchartOptions = chart.options,\n\t\t\tplotOptions = chartOptions.plotOptions,\n\t\t\ttypeOptions = plotOptions[this.type],\n\t\t\toptions;\n\n\t\tthis.userOptions = itemOptions;\n\n\t\toptions = merge(\n\t\t\ttypeOptions,\n\t\t\tplotOptions.series,\n\t\t\titemOptions\n\t\t);\n\t\t\n\t\t// the tooltip options are merged between global and series specific options\n\t\tthis.tooltipOptions = merge(chartOptions.tooltip, options.tooltip);\n\t\t\n\t\t// Delte marker object if not allowed (#1125)\n\t\tif (typeOptions.marker === null) {\n\t\t\tdelete options.marker;\n\t\t}\n\t\t\n\t\treturn options;\n\n\t},\n\t/**\n\t * Get the series' color\n\t */\n\tgetColor: function () {\n\t\tvar options = this.options,\n\t\t\tuserOptions = this.userOptions,\n\t\t\tdefaultColors = this.chart.options.colors,\n\t\t\tcounters = this.chart.counters,\n\t\t\tcolor,\n\t\t\tcolorIndex;\n\n\t\tcolor = options.color || defaultPlotOptions[this.type].color;\n\n\t\tif (!color && !options.colorByPoint) {\n\t\t\tif (defined(userOptions._colorIndex)) { // after Series.update()\n\t\t\t\tcolorIndex = userOptions._colorIndex;\n\t\t\t} else {\n\t\t\t\tuserOptions._colorIndex = counters.color;\n\t\t\t\tcolorIndex = counters.color++;\n\t\t\t}\n\t\t\tcolor = defaultColors[colorIndex];\n\t\t}\n\t\t\n\t\tthis.color = color;\n\t\tcounters.wrapColor(defaultColors.length);\n\t},\n\t/**\n\t * Get the series' symbol\n\t */\n\tgetSymbol: function () {\n\t\tvar series = this,\n\t\t\tuserOptions = series.userOptions,\n\t\t\tseriesMarkerOption = series.options.marker,\n\t\t\tchart = series.chart,\n\t\t\tdefaultSymbols = chart.options.symbols,\n\t\t\tcounters = chart.counters,\n\t\t\tsymbolIndex;\n\n\t\tseries.symbol = seriesMarkerOption.symbol;\n\t\tif (!series.symbol) {\n\t\t\tif (defined(userOptions._symbolIndex)) { // after Series.update()\n\t\t\t\tsymbolIndex = userOptions._symbolIndex;\n\t\t\t} else {\n\t\t\t\tuserOptions._symbolIndex = counters.symbol;\n\t\t\t\tsymbolIndex = counters.symbol++;\n\t\t\t}\n\t\t\tseries.symbol = defaultSymbols[symbolIndex];\n\t\t}\n\n\t\t// don't substract radius in image symbols (#604)\n\t\tif (/^url/.test(series.symbol)) {\n\t\t\tseriesMarkerOption.radius = 0;\n\t\t}\n\t\tcounters.wrapSymbol(defaultSymbols.length);\n\t},\n\n\t/**\n\t * Get the series' symbol in the legend. This method should be overridable to create custom \n\t * symbols through Highcharts.seriesTypes[type].prototype.drawLegendSymbols.\n\t * \n\t * @param {Object} legend The legend object\n\t */\n\tdrawLegendSymbol: function (legend) {\n\t\t\n\t\tvar options = this.options,\n\t\t\tmarkerOptions = options.marker,\n\t\t\tradius,\n\t\t\tlegendOptions = legend.options,\n\t\t\tlegendSymbol,\n\t\t\tsymbolWidth = legendOptions.symbolWidth,\n\t\t\trenderer = this.chart.renderer,\n\t\t\tlegendItemGroup = this.legendGroup,\n\t\t\tverticalCenter = legend.baseline - mathRound(renderer.fontMetrics(legendOptions.itemStyle.fontSize).b * 0.3),\n\t\t\tattr;\n\t\t\t\n\t\t// Draw the line\n\t\tif (options.lineWidth) {\n\t\t\tattr = {\n\t\t\t\t'stroke-width': options.lineWidth\n\t\t\t};\n\t\t\tif (options.dashStyle) {\n\t\t\t\tattr.dashstyle = options.dashStyle;\n\t\t\t}\n\t\t\tthis.legendLine = renderer.path([\n\t\t\t\tM,\n\t\t\t\t0,\n\t\t\t\tverticalCenter,\n\t\t\t\tL,\n\t\t\t\tsymbolWidth,\n\t\t\t\tverticalCenter\n\t\t\t])\n\t\t\t.attr(attr)\n\t\t\t.add(legendItemGroup);\n\t\t}\n\t\t\n\t\t// Draw the marker\n\t\tif (markerOptions && markerOptions.enabled) {\n\t\t\tradius = markerOptions.radius;\n\t\t\tthis.legendSymbol = legendSymbol = renderer.symbol(\n\t\t\t\tthis.symbol,\n\t\t\t\t(symbolWidth / 2) - radius,\n\t\t\t\tverticalCenter - radius,\n\t\t\t\t2 * radius,\n\t\t\t\t2 * radius\n\t\t\t)\n\t\t\t.add(legendItemGroup);\n\t\t\tlegendSymbol.isMarker = true;\n\t\t}\n\t},\n\n\t/**\n\t * Add a point dynamically after chart load time\n\t * @param {Object} options Point options as given in series.data\n\t * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call\n\t * @param {Boolean} shift If shift is true, a point is shifted off the start\n\t *    of the series as one is appended to the end.\n\t * @param {Boolean|Object} animation Whether to apply animation, and optionally animation\n\t *    configuration\n\t */\n\taddPoint: function (options, redraw, shift, animation) {\n\t\tvar series = this,\n\t\t\tseriesOptions = series.options,\n\t\t\tdata = series.data,\n\t\t\tgraph = series.graph,\n\t\t\tarea = series.area,\n\t\t\tchart = series.chart,\n\t\t\txData = series.xData,\n\t\t\tyData = series.yData,\n\t\t\tzData = series.zData,\n\t\t\tnames = series.names,\n\t\t\tcurrentShift = (graph && graph.shift) || 0,\n\t\t\tdataOptions = seriesOptions.data,\n\t\t\tpoint,\n\t\t\tisInTheMiddle,\n\t\t\tx,\n\t\t\ti;\n\n\t\tsetAnimation(animation, chart);\n\n\t\t// Make graph animate sideways\n\t\tif (shift) {\n\t\t\teach([graph, area, series.graphNeg, series.areaNeg], function (shape) {\n\t\t\t\tif (shape) {\n\t\t\t\t\tshape.shift = currentShift + 1;\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t\tif (area) {\n\t\t\tarea.isArea = true; // needed in animation, both with and without shift\n\t\t}\n\t\t\n\t\t// Optional redraw, defaults to true\n\t\tredraw = pick(redraw, true);\n\n\t\t// Get options and push the point to xData, yData and series.options. In series.generatePoints\n\t\t// the Point instance will be created on demand and pushed to the series.data array.\n\t\tpoint = { series: series };\n\t\tseries.pointClass.prototype.applyOptions.apply(point, [options]);\n\t\tx = point.x;\n\n\t\t// Get the insertion point\n\t\ti = xData.length;\n\t\tif (series.requireSorting && x < xData[i - 1]) {\n\t\t\tisInTheMiddle = true;\n\t\t\twhile (i && xData[i - 1] > x) {\n\t\t\t\ti--;\n\t\t\t}\n\t\t}\n\t\t\n\t\txData.splice(i, 0, x);\n\t\tyData.splice(i, 0, series.toYData ? series.toYData(point) : point.y);\n\t\tzData.splice(i, 0, point.z);\n\t\tif (names) {\n\t\t\tnames[x] = point.name;\n\t\t}\n\t\tdataOptions.splice(i, 0, options);\n\n\t\tif (isInTheMiddle) {\n\t\t\tseries.data.splice(i, 0, null);\n\t\t\tseries.processData();\n\t\t}\n\t\t\n\t\t// Generate points to be added to the legend (#1329) \n\t\tif (seriesOptions.legendType === 'point') {\n\t\t\tseries.generatePoints();\n\t\t}\n\n\t\t// Shift the first point off the parallel arrays\n\t\t// todo: consider series.removePoint(i) method\n\t\tif (shift) {\n\t\t\tif (data[0] && data[0].remove) {\n\t\t\t\tdata[0].remove(false);\n\t\t\t} else {\n\t\t\t\tdata.shift();\n\t\t\t\txData.shift();\n\t\t\t\tyData.shift();\n\t\t\t\tzData.shift();\n\t\t\t\tdataOptions.shift();\n\t\t\t}\n\t\t}\n\n\t\t// redraw\n\t\tseries.isDirty = true;\n\t\tseries.isDirtyData = true;\n\t\tif (redraw) {\n\t\t\tseries.getAttribs(); // #1937\n\t\t\tchart.redraw();\n\t\t}\n\t},\n\n\t/**\n\t * Replace the series data with a new set of data\n\t * @param {Object} data\n\t * @param {Object} redraw\n\t */\n\tsetData: function (data, redraw) {\n\t\tvar series = this,\n\t\t\toldData = series.points,\n\t\t\toptions = series.options,\n\t\t\tchart = series.chart,\n\t\t\tfirstPoint = null,\n\t\t\txAxis = series.xAxis,\n\t\t\tnames = xAxis && xAxis.categories && !xAxis.categories.length ? [] : null,\n\t\t\ti;\n\n\t\t// reset properties\n\t\tseries.xIncrement = null;\n\t\tseries.pointRange = xAxis && xAxis.categories ? 1 : options.pointRange;\n\n\t\tseries.colorCounter = 0; // for series with colorByPoint (#1547)\n\t\t\n\t\t// parallel arrays\n\t\tvar xData = [],\n\t\t\tyData = [],\n\t\t\tzData = [],\n\t\t\tdataLength = data ? data.length : [],\n\t\t\tturboThreshold = pick(options.turboThreshold, 1000),\n\t\t\tpt,\n\t\t\tpointArrayMap = series.pointArrayMap,\n\t\t\tvalueCount = pointArrayMap && pointArrayMap.length,\n\t\t\thasToYData = !!series.toYData;\n\n\t\t// In turbo mode, only one- or twodimensional arrays of numbers are allowed. The\n\t\t// first value is tested, and we assume that all the rest are defined the same\n\t\t// way. Although the 'for' loops are similar, they are repeated inside each\n\t\t// if-else conditional for max performance.\n\t\tif (turboThreshold && dataLength > turboThreshold) { \n\t\t\t\n\t\t\t// find the first non-null point\n\t\t\ti = 0;\n\t\t\twhile (firstPoint === null && i < dataLength) {\n\t\t\t\tfirstPoint = data[i];\n\t\t\t\ti++;\n\t\t\t}\n\t\t\n\t\t\n\t\t\tif (isNumber(firstPoint)) { // assume all points are numbers\n\t\t\t\tvar x = pick(options.pointStart, 0),\n\t\t\t\t\tpointInterval = pick(options.pointInterval, 1);\n\n\t\t\t\tfor (i = 0; i < dataLength; i++) {\n\t\t\t\t\txData[i] = x;\n\t\t\t\t\tyData[i] = data[i];\n\t\t\t\t\tx += pointInterval;\n\t\t\t\t}\n\t\t\t\tseries.xIncrement = x;\n\t\t\t} else if (isArray(firstPoint)) { // assume all points are arrays\n\t\t\t\tif (valueCount) { // [x, low, high] or [x, o, h, l, c]\n\t\t\t\t\tfor (i = 0; i < dataLength; i++) {\n\t\t\t\t\t\tpt = data[i];\n\t\t\t\t\t\txData[i] = pt[0];\n\t\t\t\t\t\tyData[i] = pt.slice(1, valueCount + 1);\n\t\t\t\t\t}\n\t\t\t\t} else { // [x, y]\n\t\t\t\t\tfor (i = 0; i < dataLength; i++) {\n\t\t\t\t\t\tpt = data[i];\n\t\t\t\t\t\txData[i] = pt[0];\n\t\t\t\t\t\tyData[i] = pt[1];\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\terror(12); // Highcharts expects configs to be numbers or arrays in turbo mode\n\t\t\t}\n\t\t} else {\n\t\t\tfor (i = 0; i < dataLength; i++) {\n\t\t\t\tif (data[i] !== UNDEFINED) { // stray commas in oldIE\n\t\t\t\t\tpt = { series: series };\n\t\t\t\t\tseries.pointClass.prototype.applyOptions.apply(pt, [data[i]]);\n\t\t\t\t\txData[i] = pt.x;\n\t\t\t\t\tyData[i] = hasToYData ? series.toYData(pt) : pt.y;\n\t\t\t\t\tzData[i] = pt.z;\n\t\t\t\t\tif (names && pt.name) {\n\t\t\t\t\t\tnames[pt.x] = pt.name; // #2046\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t\n\t\t// Forgetting to cast strings to numbers is a common caveat when handling CSV or JSON\t\t\n\t\tif (isString(yData[0])) {\n\t\t\terror(14, true);\n\t\t} \n\n\t\tseries.data = [];\n\t\tseries.options.data = data;\n\t\tseries.xData = xData;\n\t\tseries.yData = yData;\n\t\tseries.zData = zData;\n\t\tseries.names = names;\n\n\t\t// destroy old points\n\t\ti = (oldData && oldData.length) || 0;\n\t\twhile (i--) {\n\t\t\tif (oldData[i] && oldData[i].destroy) {\n\t\t\t\toldData[i].destroy();\n\t\t\t}\n\t\t}\n\n\t\t// reset minRange (#878)\n\t\tif (xAxis) {\n\t\t\txAxis.minRange = xAxis.userMinRange;\n\t\t}\n\n\t\t// redraw\n\t\tseries.isDirty = series.isDirtyData = chart.isDirtyBox = true;\n\t\tif (pick(redraw, true)) {\n\t\t\tchart.redraw(false);\n\t\t}\n\t},\n\n\t/**\n\t * Remove a series and optionally redraw the chart\n\t *\n\t * @param {Boolean} redraw Whether to redraw the chart or wait for an explicit call\n\t * @param {Boolean|Object} animation Whether to apply animation, and optionally animation\n\t *    configuration\n\t */\n\n\tremove: function (redraw, animation) {\n\t\tvar series = this,\n\t\t\tchart = series.chart;\n\t\tredraw = pick(redraw, true);\n\n\t\tif (!series.isRemoving) {  /* prevent triggering native event in jQuery\n\t\t\t\t(calling the remove function from the remove event) */\n\t\t\tseries.isRemoving = true;\n\n\t\t\t// fire the event with a default handler of removing the point\n\t\t\tfireEvent(series, 'remove', null, function () {\n\n\n\t\t\t\t// destroy elements\n\t\t\t\tseries.destroy();\n\n\n\t\t\t\t// redraw\n\t\t\t\tchart.isDirtyLegend = chart.isDirtyBox = true;\n\t\t\t\tchart.linkSeries();\n\t\t\t\t\n\t\t\t\tif (redraw) {\n\t\t\t\t\tchart.redraw(animation);\n\t\t\t\t}\n\t\t\t});\n\n\t\t}\n\t\tseries.isRemoving = false;\n\t},\n\n\t/**\n\t * Process the data by cropping away unused data points if the series is longer\n\t * than the crop threshold. This saves computing time for lage series.\n\t */\n\tprocessData: function (force) {\n\t\tvar series = this,\n\t\t\tprocessedXData = series.xData, // copied during slice operation below\n\t\t\tprocessedYData = series.yData,\n\t\t\tdataLength = processedXData.length,\n\t\t\tcroppedData,\n\t\t\tcropStart = 0,\n\t\t\tcropped,\n\t\t\tdistance,\n\t\t\tclosestPointRange,\n\t\t\txAxis = series.xAxis,\n\t\t\ti, // loop variable\n\t\t\toptions = series.options,\n\t\t\tcropThreshold = options.cropThreshold,\n\t\t\tisCartesian = series.isCartesian;\n\n\t\t// If the series data or axes haven't changed, don't go through this. Return false to pass\n\t\t// the message on to override methods like in data grouping. \n\t\tif (isCartesian && !series.isDirty && !xAxis.isDirty && !series.yAxis.isDirty && !force) {\n\t\t\treturn false;\n\t\t}\n\t\t\n\n\t\t// optionally filter out points outside the plot area\n\t\tif (isCartesian && series.sorted && (!cropThreshold || dataLength > cropThreshold || series.forceCrop)) {\n\t\t\tvar min = xAxis.min,\n\t\t\t\tmax = xAxis.max;\n\n\t\t\t// it's outside current extremes\n\t\t\tif (processedXData[dataLength - 1] < min || processedXData[0] > max) {\n\t\t\t\tprocessedXData = [];\n\t\t\t\tprocessedYData = [];\n\t\t\t\n\t\t\t// only crop if it's actually spilling out\n\t\t\t} else if (processedXData[0] < min || processedXData[dataLength - 1] > max) {\n\t\t\t\tcroppedData = this.cropData(series.xData, series.yData, min, max);\n\t\t\t\tprocessedXData = croppedData.xData;\n\t\t\t\tprocessedYData = croppedData.yData;\n\t\t\t\tcropStart = croppedData.start;\n\t\t\t\tcropped = true;\n\t\t\t}\n\t\t}\n\t\t\n\t\t\n\t\t// Find the closest distance between processed points\n\t\tfor (i = processedXData.length - 1; i >= 0; i--) {\n\t\t\tdistance = processedXData[i] - processedXData[i - 1];\n\t\t\tif (distance > 0 && (closestPointRange === UNDEFINED || distance < closestPointRange)) {\n\t\t\t\tclosestPointRange = distance;\n\n\t\t\t// Unsorted data is not supported by the line tooltip, as well as data grouping and \n\t\t\t// navigation in Stock charts (#725) and width calculation of columns (#1900)\n\t\t\t} else if (distance < 0 && series.requireSorting) {\n\t\t\t\terror(15);\n\t\t\t}\n\t\t}\n\n\t\t// Record the properties\n\t\tseries.cropped = cropped; // undefined or true\n\t\tseries.cropStart = cropStart;\n\t\tseries.processedXData = processedXData;\n\t\tseries.processedYData = processedYData;\n\n\t\tif (options.pointRange === null) { // null means auto, as for columns, candlesticks and OHLC\n\t\t\tseries.pointRange = closestPointRange || 1;\n\t\t}\n\t\tseries.closestPointRange = closestPointRange;\n\t\t\n\t},\n\n\t/**\n\t * Iterate over xData and crop values between min and max. Returns object containing crop start/end\n\t * cropped xData with corresponding part of yData, dataMin and dataMax within the cropped range\n\t */\n\tcropData: function (xData, yData, min, max) {\n\t\tvar dataLength = xData.length,\n\t\t\tcropStart = 0,\n\t\t\tcropEnd = dataLength,\n\t\t\tcropShoulder = pick(this.cropShoulder, 1), // line-type series need one point outside\n\t\t\ti;\n\n\t\t// iterate up to find slice start\n\t\tfor (i = 0; i < dataLength; i++) {\n\t\t\tif (xData[i] >= min) {\n\t\t\t\tcropStart = mathMax(0, i - cropShoulder);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t// proceed to find slice end\n\t\tfor (; i < dataLength; i++) {\n\t\t\tif (xData[i] > max) {\n\t\t\t\tcropEnd = i + cropShoulder;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\txData: xData.slice(cropStart, cropEnd),\n\t\t\tyData: yData.slice(cropStart, cropEnd),\n\t\t\tstart: cropStart,\n\t\t\tend: cropEnd\n\t\t};\n\t},\n\n\n\t/**\n\t * Generate the data point after the data has been processed by cropping away\n\t * unused points and optionally grouped in Highcharts Stock.\n\t */\n\tgeneratePoints: function () {\n\t\tvar series = this,\n\t\t\toptions = series.options,\n\t\t\tdataOptions = options.data,\n\t\t\tdata = series.data,\n\t\t\tdataLength,\n\t\t\tprocessedXData = series.processedXData,\n\t\t\tprocessedYData = series.processedYData,\n\t\t\tpointClass = series.pointClass,\n\t\t\tprocessedDataLength = processedXData.length,\n\t\t\tcropStart = series.cropStart || 0,\n\t\t\tcursor,\n\t\t\thasGroupedData = series.hasGroupedData,\n\t\t\tpoint,\n\t\t\tpoints = [],\n\t\t\ti;\n\n\t\tif (!data && !hasGroupedData) {\n\t\t\tvar arr = [];\n\t\t\tarr.length = dataOptions.length;\n\t\t\tdata = series.data = arr;\n\t\t}\n\n\t\tfor (i = 0; i < processedDataLength; i++) {\n\t\t\tcursor = cropStart + i;\n\t\t\tif (!hasGroupedData) {\n\t\t\t\tif (data[cursor]) {\n\t\t\t\t\tpoint = data[cursor];\n\t\t\t\t} else if (dataOptions[cursor] !== UNDEFINED) { // #970\n\t\t\t\t\tdata[cursor] = point = (new pointClass()).init(series, dataOptions[cursor], processedXData[i]);\n\t\t\t\t}\n\t\t\t\tpoints[i] = point;\n\t\t\t} else {\n\t\t\t\t// splat the y data in case of ohlc data array\n\t\t\t\tpoints[i] = (new pointClass()).init(series, [processedXData[i]].concat(splat(processedYData[i])));\n\t\t\t}\n\t\t}\n\n\t\t// Hide cropped-away points - this only runs when the number of points is above cropThreshold, or when\n\t\t// swithching view from non-grouped data to grouped data (#637)\t\n\t\tif (data && (processedDataLength !== (dataLength = data.length) || hasGroupedData)) {\n\t\t\tfor (i = 0; i < dataLength; i++) {\n\t\t\t\tif (i === cropStart && !hasGroupedData) { // when has grouped data, clear all points\n\t\t\t\t\ti += processedDataLength;\n\t\t\t\t}\n\t\t\t\tif (data[i]) {\n\t\t\t\t\tdata[i].destroyElements();\n\t\t\t\t\tdata[i].plotX = UNDEFINED; // #1003\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tseries.data = data;\n\t\tseries.points = points;\n\t},\n\n\t/**\n\t * Adds series' points value to corresponding stack\n\t */\n\tsetStackedPoints: function () {\n\t\tif (!this.options.stacking || (this.visible !== true && this.chart.options.chart.ignoreHiddenSeries !== false)) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar series = this,\n\t\t\txData = series.processedXData,\n\t\t\tyData = series.processedYData,\n\t\t\tstackedYData = [],\n\t\t\tyDataLength = yData.length,\n\t\t\tseriesOptions = series.options,\n\t\t\tthreshold = seriesOptions.threshold,\n\t\t\tstackOption = seriesOptions.stack,\n\t\t\tstacking = seriesOptions.stacking,\n\t\t\tstackKey = series.stackKey,\n\t\t\tnegKey = '-' + stackKey,\n\t\t\tnegStacks = series.negStacks,\n\t\t\tyAxis = series.yAxis,\n\t\t\tstacks = yAxis.stacks,\n\t\t\toldStacks = yAxis.oldStacks,\n\t\t\tisNegative,\n\t\t\tstack,\n\t\t\tother,\n\t\t\tkey,\n\t\t\ti,\n\t\t\tx,\n\t\t\ty;\n\n\t\t// loop over the non-null y values and read them into a local array\n\t\tfor (i = 0; i < yDataLength; i++) {\n\t\t\tx = xData[i];\n\t\t\ty = yData[i];\n\n\t\t\t// Read stacked values into a stack based on the x value,\n\t\t\t// the sign of y and the stack key. Stacking is also handled for null values (#739)\n\t\t\tisNegative = negStacks && y < threshold;\n\t\t\tkey = isNegative ? negKey : stackKey;\n\n\t\t\t// Create empty object for this stack if it doesn't exist yet\n\t\t\tif (!stacks[key]) {\n\t\t\t\tstacks[key] = {};\n\t\t\t}\n\n\t\t\t// Initialize StackItem for this x\n\t\t\tif (!stacks[key][x]) {\n\t\t\t\tif (oldStacks[key] && oldStacks[key][x]) {\n\t\t\t\t\tstacks[key][x] = oldStacks[key][x];\n\t\t\t\t\tstacks[key][x].total = null;\n\t\t\t\t} else {\n\t\t\t\t\tstacks[key][x] = new StackItem(yAxis, yAxis.options.stackLabels, isNegative, x, stackOption, stacking);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If the StackItem doesn't exist, create it first\n\t\t\tstack = stacks[key][x];\n\t\t\tstack.points[series.index] = [stack.cum || 0];\n\n\t\t\t// Add value to the stack total\n\t\t\tif (stacking === 'percent') {\n\t\t\t\t\n\t\t\t\t// Percent stacked column, totals are the same for the positive and negative stacks\n\t\t\t\tother = isNegative ? stackKey : negKey;\n\t\t\t\tif (negStacks && stacks[other] && stacks[other][x]) {\n\t\t\t\t\tother = stacks[other][x];\n\t\t\t\t\tstack.total = other.total = mathMax(other.total, stack.total) + mathAbs(y) || 0;\n\n\t\t\t\t// Percent stacked areas\t\t\t\t\t\n\t\t\t\t} else {\n\t\t\t\t\tstack.total += mathAbs(y) || 0;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tstack.total += y || 0;\n\t\t\t}\n\n\t\t\tstack.cum = (stack.cum || 0) + (y || 0);\n\n\t\t\tstack.points[series.index].push(stack.cum);\n\t\t\tstackedYData[i] = stack.cum;\n\n\t\t}\n\n\t\tif (stacking === 'percent') {\n\t\t\tyAxis.usePercentage = true;\n\t\t}\n\n\t\tthis.stackedYData = stackedYData; // To be used in getExtremes\n\t\t\n\t\t// Reset old stacks\n\t\tyAxis.oldStacks = {};\n\t},\n\n\t/**\n\t * Iterate over all stacks and compute the absolute values to percent\n\t */\n\tsetPercentStacks: function () {\n\t\tvar series = this,\n\t\t\tstackKey = series.stackKey,\n\t\t\tstacks = series.yAxis.stacks;\n\t\t\n\t\teach([stackKey, '-' + stackKey], function (key) {\n\t\t\tvar i = series.xData.length,\n\t\t\t\tx,\n\t\t\t\tstack,\n\t\t\t\tpointExtremes,\n\t\t\t\ttotalFactor;\n\n\t\t\twhile (i--) {\n\t\t\t\tx = series.xData[i];\n\t\t\t\tstack = stacks[key] && stacks[key][x];\n\t\t\t\tpointExtremes = stack && stack.points[series.index];\n\t\t\t\tif (pointExtremes) {\n\t\t\t\t\ttotalFactor = stack.total ? 100 / stack.total : 0;\n\t\t\t\t\tpointExtremes[0] = correctFloat(pointExtremes[0] * totalFactor); // Y bottom value\n\t\t\t\t\tpointExtremes[1] = correctFloat(pointExtremes[1] * totalFactor); // Y value\n\t\t\t\t\tseries.stackedYData[i] = pointExtremes[1];\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t},\n\n\t/**\n\t * Calculate Y extremes for visible data\n\t */\n\tgetExtremes: function () {\n\t\tvar xAxis = this.xAxis,\n\t\t\tyAxis = this.yAxis,\n\t\t\txData = this.processedXData,\n\t\t\tyData = this.stackedYData || this.processedYData,\n\t\t\tyDataLength = yData.length,\n\t\t\tactiveYData = [],\n\t\t\tactiveCounter = 0,\n\t\t\txExtremes = xAxis.getExtremes(), // #2117, need to compensate for log X axis\n\t\t\txMin = xExtremes.min,\n\t\t\txMax = xExtremes.max,\n\t\t\tvalidValue,\n\t\t\twithinRange,\n\t\t\tdataMin,\n\t\t\tdataMax,\n\t\t\tx,\n\t\t\ty,\n\t\t\ti,\n\t\t\tj;\n\n\t\tfor (i = 0; i < yDataLength; i++) {\n\t\t\t\n\t\t\tx = xData[i];\n\t\t\ty = yData[i];\n\n\t\t\t// For points within the visible range, including the first point outside the\n\t\t\t// visible range, consider y extremes\n\t\t\tvalidValue = y !== null && y !== UNDEFINED && (!yAxis.isLog || (y.length || y > 0));\n\t\t\twithinRange = this.getExtremesFromAll || this.cropped || ((xData[i + 1] || x) >= xMin && \n\t\t\t\t(xData[i - 1] || x) <= xMax);\n\n\t\t\tif (validValue && withinRange) {\n\n\t\t\t\tj = y.length;\n\t\t\t\tif (j) { // array, like ohlc or range data\n\t\t\t\t\twhile (j--) {\n\t\t\t\t\t\tif (y[j] !== null) {\n\t\t\t\t\t\t\tactiveYData[activeCounter++] = y[j];\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tactiveYData[activeCounter++] = y;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tthis.dataMin = pick(dataMin, arrayMin(activeYData));\n\t\tthis.dataMax = pick(dataMax, arrayMax(activeYData));\n\t},\n\n\t/**\n\t * Translate data points from raw data values to chart specific positioning data\n\t * needed later in drawPoints, drawGraph and drawTracker.\n\t */\n\ttranslate: function () {\n\t\tif (!this.processedXData) { // hidden series\n\t\t\tthis.processData();\n\t\t}\n\t\tthis.generatePoints();\n\t\tvar series = this,\n\t\t\toptions = series.options,\n\t\t\tstacking = options.stacking,\n\t\t\txAxis = series.xAxis,\n\t\t\tcategories = xAxis.categories,\n\t\t\tyAxis = series.yAxis,\n\t\t\tpoints = series.points,\n\t\t\tdataLength = points.length,\n\t\t\thasModifyValue = !!series.modifyValue,\n\t\t\ti,\n\t\t\tpointPlacement = options.pointPlacement,\n\t\t\tdynamicallyPlaced = pointPlacement === 'between' || isNumber(pointPlacement),\n\t\t\tthreshold = options.threshold;\n\n\t\t\n\t\t// Translate each point\n\t\tfor (i = 0; i < dataLength; i++) {\n\t\t\tvar point = points[i],\n\t\t\t\txValue = point.x,\n\t\t\t\tyValue = point.y,\n\t\t\t\tyBottom = point.low,\n\t\t\t\tstack = yAxis.stacks[(series.negStacks && yValue < threshold ? '-' : '') + series.stackKey],\n\t\t\t\tpointStack,\n\t\t\t\tstackValues;\n\n\t\t\t// Discard disallowed y values for log axes\n\t\t\tif (yAxis.isLog && yValue <= 0) {\n\t\t\t\tpoint.y = yValue = null;\n\t\t\t}\n\t\t\t\n\t\t\t// Get the plotX translation\n\t\t\tpoint.plotX = xAxis.translate(xValue, 0, 0, 0, 1, pointPlacement, this.type === 'flags'); // Math.round fixes #591\n\t\t\t\n\n\t\t\t// Calculate the bottom y value for stacked series\n\t\t\tif (stacking && series.visible && stack && stack[xValue]) {\n\n\t\t\t\tpointStack = stack[xValue];\n\t\t\t\tstackValues = pointStack.points[series.index];\n\t\t\t\tyBottom = stackValues[0];\n\t\t\t\tyValue = stackValues[1];\n\n\t\t\t\tif (yBottom === 0) {\n\t\t\t\t\tyBottom = pick(threshold, yAxis.min);\n\t\t\t\t}\n\t\t\t\tif (yAxis.isLog && yBottom <= 0) { // #1200, #1232\n\t\t\t\t\tyBottom = null;\n\t\t\t\t}\n\n\t\t\t\tpoint.percentage = stacking === 'percent' && yValue;\n\t\t\t\tpoint.total = point.stackTotal = pointStack.total;\n\t\t\t\tpoint.stackY = yValue;\n\n\t\t\t\t// Place the stack label\n\t\t\t\tpointStack.setOffset(series.pointXOffset || 0, series.barW || 0);\n\t\t\t\t\n\t\t\t}\n\n\t\t\t// Set translated yBottom or remove it\n\t\t\tpoint.yBottom = defined(yBottom) ? \n\t\t\t\tyAxis.translate(yBottom, 0, 1, 0, 1) :\n\t\t\t\tnull;\n\t\t\t\t\n\t\t\t// general hook, used for Highstock compare mode\n\t\t\tif (hasModifyValue) {\n\t\t\t\tyValue = series.modifyValue(yValue, point);\n\t\t\t}\n\n\t\t\t// Set the the plotY value, reset it for redraws\n\t\t\tpoint.plotY = (typeof yValue === 'number' && yValue !== Infinity) ? \n\t\t\t\t//mathRound(yAxis.translate(yValue, 0, 1, 0, 1) * 10) / 10 : // Math.round fixes #591\n\t\t\t\tyAxis.translate(yValue, 0, 1, 0, 1) : \n\t\t\t\tUNDEFINED;\n\t\t\t\n\t\t\t// Set client related positions for mouse tracking\n\t\t\tpoint.clientX = dynamicallyPlaced ? xAxis.translate(xValue, 0, 0, 0, 1) : point.plotX; // #1514\n\t\t\t\t\n\t\t\tpoint.negative = point.y < (threshold || 0);\n\n\t\t\t// some API data\n\t\t\tpoint.category = categories && categories[point.x] !== UNDEFINED ?\n\t\t\t\tcategories[point.x] : point.x;\n\n\n\t\t}\n\n\t\t// now that we have the cropped data, build the segments\n\t\tseries.getSegments();\n\t},\n\t/**\n\t * Memoize tooltip texts and positions\n\t */\n\tsetTooltipPoints: function (renew) {\n\t\tvar series = this,\n\t\t\tpoints = [],\n\t\t\tpointsLength,\n\t\t\tlow,\n\t\t\thigh,\n\t\t\txAxis = series.xAxis,\n\t\t\txExtremes = xAxis && xAxis.getExtremes(),\n\t\t\taxisLength = xAxis ? (xAxis.tooltipLen || xAxis.len) : series.chart.plotSizeX, // tooltipLen and tooltipPosName used in polar\n\t\t\tpoint,\n\t\t\tpointX,\n\t\t\tnextPoint,\n\t\t\ti,\n\t\t\ttooltipPoints = []; // a lookup array for each pixel in the x dimension\n\n\t\t// don't waste resources if tracker is disabled\n\t\tif (series.options.enableMouseTracking === false) {\n\t\t\treturn;\n\t\t}\n\n\t\t// renew\n\t\tif (renew) {\n\t\t\tseries.tooltipPoints = null;\n\t\t}\n\n\t\t// concat segments to overcome null values\n\t\teach(series.segments || series.points, function (segment) {\n\t\t\tpoints = points.concat(segment);\n\t\t});\n\n\t\t// Reverse the points in case the X axis is reversed\n\t\tif (xAxis && xAxis.reversed) {\n\t\t\tpoints = points.reverse();\n\t\t}\n\n\t\t// Polar needs additional shaping\n\t\tif (series.orderTooltipPoints) {\n\t\t\tseries.orderTooltipPoints(points);\n\t\t}\n\n\t\t// Assign each pixel position to the nearest point\n\t\tpointsLength = points.length;\n\t\tfor (i = 0; i < pointsLength; i++) {\n\t\t\tpoint = points[i];\n\t\t\tpointX = point.x;\n\t\t\tif (pointX >= xExtremes.min && pointX <= xExtremes.max) { // #1149\n\t\t\t\tnextPoint = points[i + 1];\n\t\t\t\t\n\t\t\t\t// Set this range's low to the last range's high plus one\n\t\t\t\tlow = high === UNDEFINED ? 0 : high + 1;\n\t\t\t\t// Now find the new high\n\t\t\t\thigh = points[i + 1] ?\n\t\t\t\t\tmathMin(mathMax(0, mathFloor( // #2070\n\t\t\t\t\t\t(point.clientX + (nextPoint ? (nextPoint.wrappedClientX || nextPoint.clientX) : axisLength)) / 2\n\t\t\t\t\t)), axisLength) :\n\t\t\t\t\taxisLength;\n\n\t\t\t\twhile (low >= 0 && low <= high) {\n\t\t\t\t\ttooltipPoints[low++] = point;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tseries.tooltipPoints = tooltipPoints;\n\t},\n\n\t/**\n\t * Format the header of the tooltip\n\t */\n\ttooltipHeaderFormatter: function (point) {\n\t\tvar series = this,\n\t\t\ttooltipOptions = series.tooltipOptions,\n\t\t\txDateFormat = tooltipOptions.xDateFormat,\n\t\t\tdateTimeLabelFormats = tooltipOptions.dateTimeLabelFormats,\n\t\t\txAxis = series.xAxis,\n\t\t\tisDateTime = xAxis && xAxis.options.type === 'datetime',\n\t\t\theaderFormat = tooltipOptions.headerFormat,\n\t\t\tclosestPointRange = xAxis && xAxis.closestPointRange,\n\t\t\tn;\n\t\t\t\n\t\t// Guess the best date format based on the closest point distance (#568)\n\t\tif (isDateTime && !xDateFormat) {\n\t\t\tif (closestPointRange) {\n\t\t\t\tfor (n in timeUnits) {\n\t\t\t\t\tif (timeUnits[n] >= closestPointRange) {\n\t\t\t\t\t\txDateFormat = dateTimeLabelFormats[n];\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\txDateFormat = dateTimeLabelFormats.day;\n\t\t\t}\n\t\t}\n\t\t\n\t\t// Insert the header date format if any\n\t\tif (isDateTime && xDateFormat && isNumber(point.key)) {\n\t\t\theaderFormat = headerFormat.replace('{point.key}', '{point.key:' + xDateFormat + '}');\n\t\t}\n\t\t\n\t\treturn format(headerFormat, {\n\t\t\tpoint: point,\n\t\t\tseries: series\n\t\t});\n\t},\n\n\t/**\n\t * Series mouse over handler\n\t */\n\tonMouseOver: function () {\n\t\tvar series = this,\n\t\t\tchart = series.chart,\n\t\t\thoverSeries = chart.hoverSeries;\n\n\t\t// set normal state to previous series\n\t\tif (hoverSeries && hoverSeries !== series) {\n\t\t\thoverSeries.onMouseOut();\n\t\t}\n\n\t\t// trigger the event, but to save processing time,\n\t\t// only if defined\n\t\tif (series.options.events.mouseOver) {\n\t\t\tfireEvent(series, 'mouseOver');\n\t\t}\n\n\t\t// hover this\n\t\tseries.setState(HOVER_STATE);\n\t\tchart.hoverSeries = series;\n\t},\n\n\t/**\n\t * Series mouse out handler\n\t */\n\tonMouseOut: function () {\n\t\t// trigger the event only if listeners exist\n\t\tvar series = this,\n\t\t\toptions = series.options,\n\t\t\tchart = series.chart,\n\t\t\ttooltip = chart.tooltip,\n\t\t\thoverPoint = chart.hoverPoint;\n\n\t\t// trigger mouse out on the point, which must be in this series\n\t\tif (hoverPoint) {\n\t\t\thoverPoint.onMouseOut();\n\t\t}\n\n\t\t// fire the mouse out event\n\t\tif (series && options.events.mouseOut) {\n\t\t\tfireEvent(series, 'mouseOut');\n\t\t}\n\n\n\t\t// hide the tooltip\n\t\tif (tooltip && !options.stickyTracking && (!tooltip.shared || series.noSharedTooltip)) {\n\t\t\ttooltip.hide();\n\t\t}\n\n\t\t// set normal state\n\t\tseries.setState();\n\t\tchart.hoverSeries = null;\n\t},\n\n\t/**\n\t * Animate in the series\n\t */\n\tanimate: function (init) {\n\t\tvar series = this,\n\t\t\tchart = series.chart,\n\t\t\trenderer = chart.renderer,\n\t\t\tclipRect,\n\t\t\tmarkerClipRect,\n\t\t\tanimation = series.options.animation,\n\t\t\tclipBox = chart.clipBox,\n\t\t\tinverted = chart.inverted,\n\t\t\tsharedClipKey;\n\n\t\t// Animation option is set to true\n\t\tif (animation && !isObject(animation)) {\n\t\t\tanimation = defaultPlotOptions[series.type].animation;\n\t\t}\n\t\tsharedClipKey = '_sharedClip' + animation.duration + animation.easing;\n\n\t\t// Initialize the animation. Set up the clipping rectangle.\n\t\tif (init) { \n\t\t\t\n\t\t\t// If a clipping rectangle with the same properties is currently present in the chart, use that. \n\t\t\tclipRect = chart[sharedClipKey];\n\t\t\tmarkerClipRect = chart[sharedClipKey + 'm'];\n\t\t\tif (!clipRect) {\n\t\t\t\tchart[sharedClipKey] = clipRect = renderer.clipRect(\n\t\t\t\t\textend(clipBox, { width: 0 })\n\t\t\t\t);\n\t\t\t\t\n\t\t\t\tchart[sharedClipKey + 'm'] = markerClipRect = renderer.clipRect(\n\t\t\t\t\t-99, // include the width of the first marker\n\t\t\t\t\tinverted ? -chart.plotLeft : -chart.plotTop, \n\t\t\t\t\t99,\n\t\t\t\t\tinverted ? chart.chartWidth : chart.chartHeight\n\t\t\t\t);\n\t\t\t}\n\t\t\tseries.group.clip(clipRect);\n\t\t\tseries.markerGroup.clip(markerClipRect);\n\t\t\tseries.sharedClipKey = sharedClipKey;\n\n\t\t// Run the animation\n\t\t} else { \n\t\t\tclipRect = chart[sharedClipKey];\n\t\t\tif (clipRect) {\n\t\t\t\tclipRect.animate({\n\t\t\t\t\twidth: chart.plotSizeX\n\t\t\t\t}, animation);\n\t\t\t\tchart[sharedClipKey + 'm'].animate({\n\t\t\t\t\twidth: chart.plotSizeX + 99\n\t\t\t\t}, animation);\n\t\t\t}\n\n\t\t\t// Delete this function to allow it only once\n\t\t\tseries.animate = null;\n\t\t\t\n\t\t\t// Call the afterAnimate function on animation complete (but don't overwrite the animation.complete option\n\t\t\t// which should be available to the user).\n\t\t\tseries.animationTimeout = setTimeout(function () {\n\t\t\t\tseries.afterAnimate();\n\t\t\t}, animation.duration);\n\t\t}\n\t},\n\t\n\t/**\n\t * This runs after animation to land on the final plot clipping\n\t */\n\tafterAnimate: function () {\n\t\tvar chart = this.chart,\n\t\t\tsharedClipKey = this.sharedClipKey,\n\t\t\tgroup = this.group;\n\t\t\t\n\t\tif (group && this.options.clip !== false) {\n\t\t\tgroup.clip(chart.clipRect);\n\t\t\tthis.markerGroup.clip(); // no clip\n\t\t}\n\t\t\n\t\t// Remove the shared clipping rectancgle when all series are shown\t\t\n\t\tsetTimeout(function () {\n\t\t\tif (sharedClipKey && chart[sharedClipKey]) {\n\t\t\t\tchart[sharedClipKey] = chart[sharedClipKey].destroy();\n\t\t\t\tchart[sharedClipKey + 'm'] = chart[sharedClipKey + 'm'].destroy();\n\t\t\t}\n\t\t}, 100);\n\t},\n\n\t/**\n\t * Draw the markers\n\t */\n\tdrawPoints: function () {\n\t\tvar series = this,\n\t\t\tpointAttr,\n\t\t\tpoints = series.points,\n\t\t\tchart = series.chart,\n\t\t\tplotX,\n\t\t\tplotY,\n\t\t\ti,\n\t\t\tpoint,\n\t\t\tradius,\n\t\t\tsymbol,\n\t\t\tisImage,\n\t\t\tgraphic,\n\t\t\toptions = series.options,\n\t\t\tseriesMarkerOptions = options.marker,\n\t\t\tpointMarkerOptions,\n\t\t\tenabled,\n\t\t\tisInside,\n\t\t\tmarkerGroup = series.markerGroup;\n\n\t\tif (seriesMarkerOptions.enabled || series._hasPointMarkers) {\n\t\t\t\n\t\t\ti = points.length;\n\t\t\twhile (i--) {\n\t\t\t\tpoint = points[i];\n\t\t\t\tplotX = mathFloor(point.plotX); // #1843\n\t\t\t\tplotY = point.plotY;\n\t\t\t\tgraphic = point.graphic;\n\t\t\t\tpointMarkerOptions = point.marker || {};\n\t\t\t\tenabled = (seriesMarkerOptions.enabled && pointMarkerOptions.enabled === UNDEFINED) || pointMarkerOptions.enabled;\n\t\t\t\tisInside = chart.isInsidePlot(mathRound(plotX), plotY, chart.inverted); // #1858\n\t\t\t\t\n\t\t\t\t// only draw the point if y is defined\n\t\t\t\tif (enabled && plotY !== UNDEFINED && !isNaN(plotY) && point.y !== null) {\n\n\t\t\t\t\t// shortcuts\n\t\t\t\t\tpointAttr = point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE];\n\t\t\t\t\tradius = pointAttr.r;\n\t\t\t\t\tsymbol = pick(pointMarkerOptions.symbol, series.symbol);\n\t\t\t\t\tisImage = symbol.indexOf('url') === 0;\n\n\t\t\t\t\tif (graphic) { // update\n\t\t\t\t\t\tgraphic\n\t\t\t\t\t\t\t.attr({ // Since the marker group isn't clipped, each individual marker must be toggled\n\t\t\t\t\t\t\t\tvisibility: isInside ? (hasSVG ? 'inherit' : VISIBLE) : HIDDEN\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.animate(extend({\n\t\t\t\t\t\t\t\tx: plotX - radius,\n\t\t\t\t\t\t\t\ty: plotY - radius\n\t\t\t\t\t\t\t}, graphic.symbolName ? { // don't apply to image symbols #507\n\t\t\t\t\t\t\t\twidth: 2 * radius,\n\t\t\t\t\t\t\t\theight: 2 * radius\n\t\t\t\t\t\t\t} : {}));\n\t\t\t\t\t} else if (isInside && (radius > 0 || isImage)) {\n\t\t\t\t\t\tpoint.graphic = graphic = chart.renderer.symbol(\n\t\t\t\t\t\t\tsymbol,\n\t\t\t\t\t\t\tplotX - radius,\n\t\t\t\t\t\t\tplotY - radius,\n\t\t\t\t\t\t\t2 * radius,\n\t\t\t\t\t\t\t2 * radius\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.attr(pointAttr)\n\t\t\t\t\t\t.add(markerGroup);\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t} else if (graphic) {\n\t\t\t\t\tpoint.graphic = graphic.destroy(); // #1269\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t},\n\n\t/**\n\t * Convert state properties from API naming conventions to SVG attributes\n\t *\n\t * @param {Object} options API options object\n\t * @param {Object} base1 SVG attribute object to inherit from\n\t * @param {Object} base2 Second level SVG attribute object to inherit from\n\t */\n\tconvertAttribs: function (options, base1, base2, base3) {\n\t\tvar conversion = this.pointAttrToOptions,\n\t\t\tattr,\n\t\t\toption,\n\t\t\tobj = {};\n\n\t\toptions = options || {};\n\t\tbase1 = base1 || {};\n\t\tbase2 = base2 || {};\n\t\tbase3 = base3 || {};\n\n\t\tfor (attr in conversion) {\n\t\t\toption = conversion[attr];\n\t\t\tobj[attr] = pick(options[option], base1[attr], base2[attr], base3[attr]);\n\t\t}\n\t\treturn obj;\n\t},\n\n\t/**\n\t * Get the state attributes. Each series type has its own set of attributes\n\t * that are allowed to change on a point's state change. Series wide attributes are stored for\n\t * all series, and additionally point specific attributes are stored for all\n\t * points with individual marker options. If such options are not defined for the point,\n\t * a reference to the series wide attributes is stored in point.pointAttr.\n\t */\n\tgetAttribs: function () {\n\t\tvar series = this,\n\t\t\tseriesOptions = series.options,\n\t\t\tnormalOptions = defaultPlotOptions[series.type].marker ? seriesOptions.marker : seriesOptions,\n\t\t\tstateOptions = normalOptions.states,\n\t\t\tstateOptionsHover = stateOptions[HOVER_STATE],\n\t\t\tpointStateOptionsHover,\n\t\t\tseriesColor = series.color,\n\t\t\tnormalDefaults = {\n\t\t\t\tstroke: seriesColor,\n\t\t\t\tfill: seriesColor\n\t\t\t},\n\t\t\tpoints = series.points || [], // #927\n\t\t\ti,\n\t\t\tpoint,\n\t\t\tseriesPointAttr = [],\n\t\t\tpointAttr,\n\t\t\tpointAttrToOptions = series.pointAttrToOptions,\n\t\t\thasPointSpecificOptions,\n\t\t\tnegativeColor = seriesOptions.negativeColor,\n\t\t\tdefaultLineColor = normalOptions.lineColor,\n\t\t\tkey;\n\n\t\t// series type specific modifications\n\t\tif (seriesOptions.marker) { // line, spline, area, areaspline, scatter\n\n\t\t\t// if no hover radius is given, default to normal radius + 2\n\t\t\tstateOptionsHover.radius = stateOptionsHover.radius || normalOptions.radius + 2;\n\t\t\tstateOptionsHover.lineWidth = stateOptionsHover.lineWidth || normalOptions.lineWidth + 1;\n\t\t\t\n\t\t} else { // column, bar, pie\n\n\t\t\t// if no hover color is given, brighten the normal color\n\t\t\tstateOptionsHover.color = stateOptionsHover.color ||\n\t\t\t\tColor(stateOptionsHover.color || seriesColor)\n\t\t\t\t\t.brighten(stateOptionsHover.brightness).get();\n\t\t}\n\n\t\t// general point attributes for the series normal state\n\t\tseriesPointAttr[NORMAL_STATE] = series.convertAttribs(normalOptions, normalDefaults);\n\n\t\t// HOVER_STATE and SELECT_STATE states inherit from normal state except the default radius\n\t\teach([HOVER_STATE, SELECT_STATE], function (state) {\n\t\t\tseriesPointAttr[state] =\n\t\t\t\t\tseries.convertAttribs(stateOptions[state], seriesPointAttr[NORMAL_STATE]);\n\t\t});\n\n\t\t// set it\n\t\tseries.pointAttr = seriesPointAttr;\n\n\n\t\t// Generate the point-specific attribute collections if specific point\n\t\t// options are given. If not, create a referance to the series wide point\n\t\t// attributes\n\t\ti = points.length;\n\t\twhile (i--) {\n\t\t\tpoint = points[i];\n\t\t\tnormalOptions = (point.options && point.options.marker) || point.options;\n\t\t\tif (normalOptions && normalOptions.enabled === false) {\n\t\t\t\tnormalOptions.radius = 0;\n\t\t\t}\n\t\t\t\n\t\t\tif (point.negative && negativeColor) {\n\t\t\t\tpoint.color = point.fillColor = negativeColor;\n\t\t\t}\n\t\t\t\n\t\t\thasPointSpecificOptions = seriesOptions.colorByPoint || point.color; // #868\n\n\t\t\t// check if the point has specific visual options\n\t\t\tif (point.options) {\n\t\t\t\tfor (key in pointAttrToOptions) {\n\t\t\t\t\tif (defined(normalOptions[pointAttrToOptions[key]])) {\n\t\t\t\t\t\thasPointSpecificOptions = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// a specific marker config object is defined for the individual point:\n\t\t\t// create it's own attribute collection\n\t\t\tif (hasPointSpecificOptions) {\n\t\t\t\tnormalOptions = normalOptions || {};\n\t\t\t\tpointAttr = [];\n\t\t\t\tstateOptions = normalOptions.states || {}; // reassign for individual point\n\t\t\t\tpointStateOptionsHover = stateOptions[HOVER_STATE] = stateOptions[HOVER_STATE] || {};\n\n\t\t\t\t// Handle colors for column and pies\n\t\t\t\tif (!seriesOptions.marker) { // column, bar, point\n\t\t\t\t\t// if no hover color is given, brighten the normal color\n\t\t\t\t\tpointStateOptionsHover.color =\n\t\t\t\t\t\tColor(pointStateOptionsHover.color || point.color)\n\t\t\t\t\t\t\t.brighten(pointStateOptionsHover.brightness ||\n\t\t\t\t\t\t\t\tstateOptionsHover.brightness).get();\n\n\t\t\t\t}\n\n\t\t\t\t// normal point state inherits series wide normal state\n\t\t\t\tpointAttr[NORMAL_STATE] = series.convertAttribs(extend({\n\t\t\t\t\tcolor: point.color, // #868\n\t\t\t\t\tfillColor: point.color, // Individual point color or negative color markers (#2219)\n\t\t\t\t\tlineColor: defaultLineColor === null ? point.color : UNDEFINED // Bubbles take point color, line markers use white\n\t\t\t\t}, normalOptions), seriesPointAttr[NORMAL_STATE]);\n\n\t\t\t\t// inherit from point normal and series hover\n\t\t\t\tpointAttr[HOVER_STATE] = series.convertAttribs(\n\t\t\t\t\tstateOptions[HOVER_STATE],\n\t\t\t\t\tseriesPointAttr[HOVER_STATE],\n\t\t\t\t\tpointAttr[NORMAL_STATE]\n\t\t\t\t);\n\t\t\t\t\n\t\t\t\t// inherit from point normal and series hover\n\t\t\t\tpointAttr[SELECT_STATE] = series.convertAttribs(\n\t\t\t\t\tstateOptions[SELECT_STATE],\n\t\t\t\t\tseriesPointAttr[SELECT_STATE],\n\t\t\t\t\tpointAttr[NORMAL_STATE]\n\t\t\t\t);\n\n\n\t\t\t// no marker config object is created: copy a reference to the series-wide\n\t\t\t// attribute collection\n\t\t\t} else {\n\t\t\t\tpointAttr = seriesPointAttr;\n\t\t\t}\n\n\t\t\tpoint.pointAttr = pointAttr;\n\n\t\t}\n\n\t},\n\t/**\n\t * Update the series with a new set of options\n\t */\n\tupdate: function (newOptions, redraw) {\n\t\tvar chart = this.chart,\n\t\t\t// must use user options when changing type because this.options is merged\n\t\t\t// in with type specific plotOptions\n\t\t\toldOptions = this.userOptions,\n\t\t\toldType = this.type,\n\t\t\tproto = seriesTypes[oldType].prototype,\n\t\t\tn;\n\n\t\t// Do the merge, with some forced options\n\t\tnewOptions = merge(oldOptions, {\n\t\t\tanimation: false,\n\t\t\tindex: this.index,\n\t\t\tpointStart: this.xData[0] // when updating after addPoint\n\t\t}, { data: this.options.data }, newOptions);\n\n\t\t// Destroy the series and reinsert methods from the type prototype\n\t\tthis.remove(false);\n\t\tfor (n in proto) { // Overwrite series-type specific methods (#2270)\n\t\t\tif (proto.hasOwnProperty(n)) {\n\t\t\t\tthis[n] = UNDEFINED;\n\t\t\t}\n\t\t}\n\t\textend(this, seriesTypes[newOptions.type || oldType].prototype);\n\t\t\n\n\t\tthis.init(chart, newOptions);\n\t\tif (pick(redraw, true)) {\n\t\t\tchart.redraw(false);\n\t\t}\n\t},\n\n\t/**\n\t * Clear DOM objects and free up memory\n\t */\n\tdestroy: function () {\n\t\tvar series = this,\n\t\t\tchart = series.chart,\n\t\t\tissue134 = /AppleWebKit\\/533/.test(userAgent),\n\t\t\tdestroy,\n\t\t\ti,\n\t\t\tdata = series.data || [],\n\t\t\tpoint,\n\t\t\tprop,\n\t\t\taxis;\n\n\t\t// add event hook\n\t\tfireEvent(series, 'destroy');\n\n\t\t// remove all events\n\t\tremoveEvent(series);\n\t\t\n\t\t// erase from axes\n\t\teach(['xAxis', 'yAxis'], function (AXIS) {\n\t\t\taxis = series[AXIS];\n\t\t\tif (axis) {\n\t\t\t\terase(axis.series, series);\n\t\t\t\taxis.isDirty = axis.forceRedraw = true;\n\t\t\t\taxis.stacks = {}; // Rebuild stacks when updating (#2229)\n\t\t\t}\n\t\t});\n\n\t\t// remove legend items\n\t\tif (series.legendItem) {\n\t\t\tseries.chart.legend.destroyItem(series);\n\t\t}\n\n\t\t// destroy all points with their elements\n\t\ti = data.length;\n\t\twhile (i--) {\n\t\t\tpoint = data[i];\n\t\t\tif (point && point.destroy) {\n\t\t\t\tpoint.destroy();\n\t\t\t}\n\t\t}\n\t\tseries.points = null;\n\n\t\t// Clear the animation timeout if we are destroying the series during initial animation\n\t\tclearTimeout(series.animationTimeout);\n\n\t\t// destroy all SVGElements associated to the series\n\t\teach(['area', 'graph', 'dataLabelsGroup', 'group', 'markerGroup', 'tracker',\n\t\t\t\t'graphNeg', 'areaNeg', 'posClip', 'negClip'], function (prop) {\n\t\t\tif (series[prop]) {\n\n\t\t\t\t// issue 134 workaround\n\t\t\t\tdestroy = issue134 && prop === 'group' ?\n\t\t\t\t\t'hide' :\n\t\t\t\t\t'destroy';\n\n\t\t\t\tseries[prop][destroy]();\n\t\t\t}\n\t\t});\n\n\t\t// remove from hoverSeries\n\t\tif (chart.hoverSeries === series) {\n\t\t\tchart.hoverSeries = null;\n\t\t}\n\t\terase(chart.series, series);\n\n\t\t// clear all members\n\t\tfor (prop in series) {\n\t\t\tdelete series[prop];\n\t\t}\n\t},\n\n\t/**\n\t * Draw the data labels\n\t */\n\tdrawDataLabels: function () {\n\t\t\n\t\tvar series = this,\n\t\t\tseriesOptions = series.options,\n\t\t\toptions = seriesOptions.dataLabels,\n\t\t\tpoints = series.points,\n\t\t\tpointOptions,\n\t\t\tgeneralOptions,\n\t\t\tstr,\n\t\t\tdataLabelsGroup;\n\t\t\n\t\tif (options.enabled || series._hasPointLabels) {\n\t\t\t\t\t\t\n\t\t\t// Process default alignment of data labels for columns\n\t\t\tif (series.dlProcessOptions) {\n\t\t\t\tseries.dlProcessOptions(options);\n\t\t\t}\n\n\t\t\t// Create a separate group for the data labels to avoid rotation\n\t\t\tdataLabelsGroup = series.plotGroup(\n\t\t\t\t'dataLabelsGroup', \n\t\t\t\t'data-labels', \n\t\t\t\tseries.visible ? VISIBLE : HIDDEN, \n\t\t\t\toptions.zIndex || 6\n\t\t\t);\n\t\t\t\n\t\t\t// Make the labels for each point\n\t\t\tgeneralOptions = options;\n\t\t\teach(points, function (point) {\n\t\t\t\t\n\t\t\t\tvar enabled,\n\t\t\t\t\tdataLabel = point.dataLabel,\n\t\t\t\t\tlabelConfig,\n\t\t\t\t\tattr,\n\t\t\t\t\tname,\n\t\t\t\t\trotation,\n\t\t\t\t\tconnector = point.connector,\n\t\t\t\t\tisNew = true;\n\t\t\t\t\n\t\t\t\t// Determine if each data label is enabled\n\t\t\t\tpointOptions = point.options && point.options.dataLabels;\n\t\t\t\tenabled = pick(pointOptions && pointOptions.enabled, generalOptions.enabled); // #2282\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t// If the point is outside the plot area, destroy it. #678, #820\n\t\t\t\tif (dataLabel && !enabled) {\n\t\t\t\t\tpoint.dataLabel = dataLabel.destroy();\n\t\t\t\t\n\t\t\t\t// Individual labels are disabled if the are explicitly disabled \n\t\t\t\t// in the point options, or if they fall outside the plot area.\n\t\t\t\t} else if (enabled) {\n\t\t\t\t\t\n\t\t\t\t\t// Create individual options structure that can be extended without \n\t\t\t\t\t// affecting others\n\t\t\t\t\toptions = merge(generalOptions, pointOptions);\n\n\t\t\t\t\trotation = options.rotation;\n\t\t\t\t\t\n\t\t\t\t\t// Get the string\n\t\t\t\t\tlabelConfig = point.getLabelConfig();\n\t\t\t\t\tstr = options.format ?\n\t\t\t\t\t\tformat(options.format, labelConfig) : \n\t\t\t\t\t\toptions.formatter.call(labelConfig, options);\n\t\t\t\t\t\n\t\t\t\t\t// Determine the color\n\t\t\t\t\toptions.style.color = pick(options.color, options.style.color, series.color, 'black');\n\t\n\t\t\t\t\t\n\t\t\t\t\t// update existing label\n\t\t\t\t\tif (dataLabel) {\n\t\t\t\t\t\t\n\t\t\t\t\t\tif (defined(str)) {\n\t\t\t\t\t\t\tdataLabel\n\t\t\t\t\t\t\t\t.attr({\n\t\t\t\t\t\t\t\t\ttext: str\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\tisNew = false;\n\t\t\t\t\t\t\n\t\t\t\t\t\t} else { // #1437 - the label is shown conditionally\n\t\t\t\t\t\t\tpoint.dataLabel = dataLabel = dataLabel.destroy();\n\t\t\t\t\t\t\tif (connector) {\n\t\t\t\t\t\t\t\tpoint.connector = connector.destroy();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t\n\t\t\t\t\t// create new label\n\t\t\t\t\t} else if (defined(str)) {\n\t\t\t\t\t\tattr = {\n\t\t\t\t\t\t\t//align: align,\n\t\t\t\t\t\t\tfill: options.backgroundColor,\n\t\t\t\t\t\t\tstroke: options.borderColor,\n\t\t\t\t\t\t\t'stroke-width': options.borderWidth,\n\t\t\t\t\t\t\tr: options.borderRadius || 0,\n\t\t\t\t\t\t\trotation: rotation,\n\t\t\t\t\t\t\tpadding: options.padding,\n\t\t\t\t\t\t\tzIndex: 1\n\t\t\t\t\t\t};\n\t\t\t\t\t\t// Remove unused attributes (#947)\n\t\t\t\t\t\tfor (name in attr) {\n\t\t\t\t\t\t\tif (attr[name] === UNDEFINED) {\n\t\t\t\t\t\t\t\tdelete attr[name];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\t\n\t\t\t\t\t\tdataLabel = point.dataLabel = series.chart.renderer[rotation ? 'text' : 'label']( // labels don't support rotation\n\t\t\t\t\t\t\tstr,\n\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t-999,\n\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\toptions.useHTML\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.attr(attr)\n\t\t\t\t\t\t.css(options.style)\n\t\t\t\t\t\t.add(dataLabelsGroup)\n\t\t\t\t\t\t.shadow(options.shadow);\n\t\t\t\t\t\t\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tif (dataLabel) {\n\t\t\t\t\t\t// Now the data label is created and placed at 0,0, so we need to align it\n\t\t\t\t\t\tseries.alignDataLabel(point, dataLabel, options, null, isNew);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t},\n\t\n\t/**\n\t * Align each individual data label\n\t */\n\talignDataLabel: function (point, dataLabel, options, alignTo, isNew) {\n\t\tvar chart = this.chart,\n\t\t\tinverted = chart.inverted,\n\t\t\tplotX = pick(point.plotX, -999),\n\t\t\tplotY = pick(point.plotY, -999),\n\t\t\tbBox = dataLabel.getBBox(),\n\t\t\tvisible = this.visible && chart.isInsidePlot(point.plotX, point.plotY, inverted),\n\t\t\talignAttr; // the final position;\n\t\t\t\t\n\t\tif (visible) {\n\n\t\t\t// The alignment box is a singular point\n\t\t\talignTo = extend({\n\t\t\t\tx: inverted ? chart.plotWidth - plotY : plotX,\n\t\t\t\ty: mathRound(inverted ? chart.plotHeight - plotX : plotY),\n\t\t\t\twidth: 0,\n\t\t\t\theight: 0\n\t\t\t}, alignTo);\n\t\t\t\n\t\t\t// Add the text size for alignment calculation\n\t\t\textend(options, {\n\t\t\t\twidth: bBox.width,\n\t\t\t\theight: bBox.height\n\t\t\t});\n\n\t\t\t// Allow a hook for changing alignment in the last moment, then do the alignment\n\t\t\tif (options.rotation) { // Fancy box alignment isn't supported for rotated text\n\t\t\t\talignAttr = {\n\t\t\t\t\talign: options.align,\n\t\t\t\t\tx: alignTo.x + options.x + alignTo.width / 2,\n\t\t\t\t\ty: alignTo.y + options.y + alignTo.height / 2\n\t\t\t\t};\n\t\t\t\tdataLabel[isNew ? 'attr' : 'animate'](alignAttr);\n\t\t\t} else {\n\t\t\t\tdataLabel.align(options, null, alignTo);\n\t\t\t\talignAttr = dataLabel.alignAttr;\n\n\t\t\t\t// Handle justify or crop\n\t\t\t\tif (pick(options.overflow, 'justify') === 'justify') { // docs: overflow: justify, also crop only applies when not justify\n\t\t\t\t\tthis.justifyDataLabel(dataLabel, options, alignAttr, bBox, alignTo, isNew);\n\t\t\t\t\n\t\t\t\t} else if (pick(options.crop, true)) {\n\t\t\t\t\t// Now check that the data label is within the plot area\n\t\t\t\t\tvisible = chart.isInsidePlot(alignAttr.x, alignAttr.y) && chart.isInsidePlot(alignAttr.x + bBox.width, alignAttr.y + bBox.height);\n\t\t\t\t\n\t\t\t\t}\n\t\t\t}\t\t\n\t\t}\n\n\t\t// Show or hide based on the final aligned position\n\t\tif (!visible) {\n\t\t\tdataLabel.attr({ y: -999 });\n\t\t}\n\t\t\t\t\n\t},\n\t\n\t/**\n\t * If data labels fall partly outside the plot area, align them back in, in a way that\n\t * doesn't hide the point.\n\t */\n\tjustifyDataLabel: function (dataLabel, options, alignAttr, bBox, alignTo, isNew) {\n\t\tvar chart = this.chart,\n\t\t\talign = options.align,\n\t\t\tverticalAlign = options.verticalAlign,\n\t\t\toff,\n\t\t\tjustified;\n\n\t\t// Off left\n\t\toff = alignAttr.x;\n\t\tif (off < 0) {\n\t\t\tif (align === 'right') {\n\t\t\t\toptions.align = 'left';\n\t\t\t} else {\n\t\t\t\toptions.x = -off;\n\t\t\t}\n\t\t\tjustified = true;\n\t\t}\n\n\t\t// Off right\n\t\toff = alignAttr.x + bBox.width;\n\t\tif (off > chart.plotWidth) {\n\t\t\tif (align === 'left') {\n\t\t\t\toptions.align = 'right';\n\t\t\t} else {\n\t\t\t\toptions.x = chart.plotWidth - off;\n\t\t\t}\n\t\t\tjustified = true;\n\t\t}\n\n\t\t// Off top\n\t\toff = alignAttr.y;\n\t\tif (off < 0) {\n\t\t\tif (verticalAlign === 'bottom') {\n\t\t\t\toptions.verticalAlign = 'top';\n\t\t\t} else {\n\t\t\t\toptions.y = -off;\n\t\t\t}\n\t\t\tjustified = true;\n\t\t}\n\n\t\t// Off bottom\n\t\toff = alignAttr.y + bBox.height;\n\t\tif (off > chart.plotHeight) {\n\t\t\tif (verticalAlign === 'top') {\n\t\t\t\toptions.verticalAlign = 'bottom';\n\t\t\t} else {\n\t\t\t\toptions.y = chart.plotHeight - off;\n\t\t\t}\n\t\t\tjustified = true;\n\t\t}\n\t\t\n\t\tif (justified) {\n\t\t\tdataLabel.placed = !isNew;\n\t\t\tdataLabel.align(options, null, alignTo);\n\t\t}\n\t},\n\t\n\t/**\n\t * Return the graph path of a segment\n\t */\n\tgetSegmentPath: function (segment) {\t\t\n\t\tvar series = this,\n\t\t\tsegmentPath = [],\n\t\t\tstep = series.options.step;\n\t\t\t\n\t\t// build the segment line\n\t\teach(segment, function (point, i) {\n\t\t\t\n\t\t\tvar plotX = point.plotX,\n\t\t\t\tplotY = point.plotY,\n\t\t\t\tlastPoint;\n\n\t\t\tif (series.getPointSpline) { // generate the spline as defined in the SplineSeries object\n\t\t\t\tsegmentPath.push.apply(segmentPath, series.getPointSpline(segment, point, i));\n\n\t\t\t} else {\n\n\t\t\t\t// moveTo or lineTo\n\t\t\t\tsegmentPath.push(i ? L : M);\n\n\t\t\t\t// step line?\n\t\t\t\tif (step && i) {\n\t\t\t\t\tlastPoint = segment[i - 1];\n\t\t\t\t\tif (step === 'right') {\n\t\t\t\t\t\tsegmentPath.push(\n\t\t\t\t\t\t\tlastPoint.plotX,\n\t\t\t\t\t\t\tplotY\n\t\t\t\t\t\t);\n\t\t\t\t\t\t\n\t\t\t\t\t} else if (step === 'center') {\n\t\t\t\t\t\tsegmentPath.push(\n\t\t\t\t\t\t\t(lastPoint.plotX + plotX) / 2,\n\t\t\t\t\t\t\tlastPoint.plotY,\n\t\t\t\t\t\t\t(lastPoint.plotX + plotX) / 2,\n\t\t\t\t\t\t\tplotY\n\t\t\t\t\t\t);\n\t\t\t\t\t\t\n\t\t\t\t\t} else {\n\t\t\t\t\t\tsegmentPath.push(\n\t\t\t\t\t\t\tplotX,\n\t\t\t\t\t\t\tlastPoint.plotY\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// normal line to next point\n\t\t\t\tsegmentPath.push(\n\t\t\t\t\tpoint.plotX,\n\t\t\t\t\tpoint.plotY\n\t\t\t\t);\n\t\t\t}\n\t\t});\n\t\t\n\t\treturn segmentPath;\n\t},\n\n\t/**\n\t * Get the graph path\n\t */\n\tgetGraphPath: function () {\n\t\tvar series = this,\n\t\t\tgraphPath = [],\n\t\t\tsegmentPath,\n\t\t\tsinglePoints = []; // used in drawTracker\n\n\t\t// Divide into segments and build graph and area paths\n\t\teach(series.segments, function (segment) {\n\t\t\t\n\t\t\tsegmentPath = series.getSegmentPath(segment);\n\t\t\t\n\t\t\t// add the segment to the graph, or a single point for tracking\n\t\t\tif (segment.length > 1) {\n\t\t\t\tgraphPath = graphPath.concat(segmentPath);\n\t\t\t} else {\n\t\t\t\tsinglePoints.push(segment[0]);\n\t\t\t}\n\t\t});\n\n\t\t// Record it for use in drawGraph and drawTracker, and return graphPath\n\t\tseries.singlePoints = singlePoints;\n\t\tseries.graphPath = graphPath;\n\t\t\n\t\treturn graphPath;\n\t\t\n\t},\n\t\n\t/**\n\t * Draw the actual graph\n\t */\n\tdrawGraph: function () {\n\t\tvar series = this,\n\t\t\toptions = this.options,\n\t\t\tprops = [['graph', options.lineColor || this.color]],\n\t\t\tlineWidth = options.lineWidth,\n\t\t\tdashStyle =  options.dashStyle,\n\t\t\tgraphPath = this.getGraphPath(),\n\t\t\tnegativeColor = options.negativeColor;\n\t\t\t\n\t\tif (negativeColor) {\n\t\t\tprops.push(['graphNeg', negativeColor]);\n\t\t}\n\t\t\n\t\t// draw the graph\n\t\teach(props, function (prop, i) {\n\t\t\tvar graphKey = prop[0],\n\t\t\t\tgraph = series[graphKey],\n\t\t\t\tattribs;\n\t\t\t\n\t\t\tif (graph) {\n\t\t\t\tstop(graph); // cancel running animations, #459\n\t\t\t\tgraph.animate({ d: graphPath });\n\t\n\t\t\t} else if (lineWidth && graphPath.length) { // #1487\n\t\t\t\tattribs = {\n\t\t\t\t\tstroke: prop[1],\n\t\t\t\t\t'stroke-width': lineWidth,\n\t\t\t\t\tzIndex: 1 // #1069\n\t\t\t\t};\n\t\t\t\tif (dashStyle) {\n\t\t\t\t\tattribs.dashstyle = dashStyle;\n\t\t\t\t} else {\n\t\t\t\t\tattribs['stroke-linecap'] = attribs['stroke-linejoin'] = 'round';\n\t\t\t\t}\n\n\t\t\t\tseries[graphKey] = series.chart.renderer.path(graphPath)\n\t\t\t\t\t.attr(attribs)\n\t\t\t\t\t.add(series.group)\n\t\t\t\t\t.shadow(!i && options.shadow);\n\t\t\t}\n\t\t});\n\t},\n\t\n\t/**\n\t * Clip the graphs into the positive and negative coloured graphs\n\t */\n\tclipNeg: function () {\n\t\tvar options = this.options,\n\t\t\tchart = this.chart,\n\t\t\trenderer = chart.renderer,\n\t\t\tnegativeColor = options.negativeColor || options.negativeFillColor,\n\t\t\ttranslatedThreshold,\n\t\t\tposAttr,\n\t\t\tnegAttr,\n\t\t\tgraph = this.graph,\n\t\t\tarea = this.area,\n\t\t\tposClip = this.posClip,\n\t\t\tnegClip = this.negClip,\n\t\t\tchartWidth = chart.chartWidth,\n\t\t\tchartHeight = chart.chartHeight,\n\t\t\tchartSizeMax = mathMax(chartWidth, chartHeight),\n\t\t\tyAxis = this.yAxis,\n\t\t\tabove,\n\t\t\tbelow;\n\t\t\n\t\tif (negativeColor && (graph || area)) {\n\t\t\ttranslatedThreshold = mathRound(yAxis.toPixels(options.threshold || 0, true));\n\t\t\tabove = {\n\t\t\t\tx: 0,\n\t\t\t\ty: 0,\n\t\t\t\twidth: chartSizeMax,\n\t\t\t\theight: translatedThreshold\n\t\t\t};\n\t\t\tbelow = {\n\t\t\t\tx: 0,\n\t\t\t\ty: translatedThreshold,\n\t\t\t\twidth: chartSizeMax,\n\t\t\t\theight: chartSizeMax\n\t\t\t};\n\t\t\t\n\t\t\tif (chart.inverted) {\n\n\t\t\t\tabove.height = below.y = chart.plotWidth - translatedThreshold;\n\t\t\t\tif (renderer.isVML) {\n\t\t\t\t\tabove = {\n\t\t\t\t\t\tx: chart.plotWidth - translatedThreshold - chart.plotLeft,\n\t\t\t\t\t\ty: 0,\n\t\t\t\t\t\twidth: chartWidth,\n\t\t\t\t\t\theight: chartHeight\n\t\t\t\t\t};\n\t\t\t\t\tbelow = {\n\t\t\t\t\t\tx: translatedThreshold + chart.plotLeft - chartWidth,\n\t\t\t\t\t\ty: 0,\n\t\t\t\t\t\twidth: chart.plotLeft + translatedThreshold,\n\t\t\t\t\t\theight: chartWidth\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\tif (yAxis.reversed) {\n\t\t\t\tposAttr = below;\n\t\t\t\tnegAttr = above;\n\t\t\t} else {\n\t\t\t\tposAttr = above;\n\t\t\t\tnegAttr = below;\n\t\t\t}\n\t\t\n\t\t\tif (posClip) { // update\n\t\t\t\tposClip.animate(posAttr);\n\t\t\t\tnegClip.animate(negAttr);\n\t\t\t} else {\n\t\t\t\t\n\t\t\t\tthis.posClip = posClip = renderer.clipRect(posAttr);\n\t\t\t\tthis.negClip = negClip = renderer.clipRect(negAttr);\n\t\t\t\t\n\t\t\t\tif (graph && this.graphNeg) {\n\t\t\t\t\tgraph.clip(posClip);\n\t\t\t\t\tthis.graphNeg.clip(negClip);\t\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tif (area) {\n\t\t\t\t\tarea.clip(posClip);\n\t\t\t\t\tthis.areaNeg.clip(negClip);\n\t\t\t\t} \n\t\t\t} \n\t\t}\t\n\t},\n\n\t/**\n\t * Initialize and perform group inversion on series.group and series.markerGroup\n\t */\n\tinvertGroups: function () {\n\t\tvar series = this,\n\t\t\tchart = series.chart;\n\n\t\t// Pie, go away (#1736)\n\t\tif (!series.xAxis) {\n\t\t\treturn;\n\t\t}\n\t\t\n\t\t// A fixed size is needed for inversion to work\n\t\tfunction setInvert() {\t\t\t\n\t\t\tvar size = {\n\t\t\t\twidth: series.yAxis.len,\n\t\t\t\theight: series.xAxis.len\n\t\t\t};\n\t\t\t\n\t\t\teach(['group', 'markerGroup'], function (groupName) {\n\t\t\t\tif (series[groupName]) {\n\t\t\t\t\tseries[groupName].attr(size).invert();\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\taddEvent(chart, 'resize', setInvert); // do it on resize\n\t\taddEvent(series, 'destroy', function () {\n\t\t\tremoveEvent(chart, 'resize', setInvert);\n\t\t});\n\n\t\t// Do it now\n\t\tsetInvert(); // do it now\n\t\t\n\t\t// On subsequent render and redraw, just do setInvert without setting up events again\n\t\tseries.invertGroups = setInvert;\n\t},\n\t\n\t/**\n\t * General abstraction for creating plot groups like series.group, series.dataLabelsGroup and \n\t * series.markerGroup. On subsequent calls, the group will only be adjusted to the updated plot size.\n\t */\n\tplotGroup: function (prop, name, visibility, zIndex, parent) {\n\t\tvar group = this[prop],\n\t\t\tisNew = !group;\n\t\t\n\t\t// Generate it on first call\n\t\tif (isNew) {\t\n\t\t\tthis[prop] = group = this.chart.renderer.g(name)\n\t\t\t\t.attr({\n\t\t\t\t\tvisibility: visibility,\n\t\t\t\t\tzIndex: zIndex || 0.1 // IE8 needs this\n\t\t\t\t})\n\t\t\t\t.add(parent);\n\t\t}\n\t\t// Place it on first and subsequent (redraw) calls\n\t\tgroup[isNew ? 'attr' : 'animate'](this.getPlotBox());\n\t\treturn group;\t\t\n\t},\n\n\t/**\n\t * Get the translation and scale for the plot area of this series\n\t */\n\tgetPlotBox: function () {\n\t\treturn {\n\t\t\ttranslateX: this.xAxis ? this.xAxis.left : this.chart.plotLeft, \n\t\t\ttranslateY: this.yAxis ? this.yAxis.top : this.chart.plotTop,\n\t\t\tscaleX: 1, // #1623\n\t\t\tscaleY: 1\n\t\t};\n\t},\n\t\n\t/**\n\t * Render the graph and markers\n\t */\n\trender: function () {\n\t\tvar series = this,\n\t\t\tchart = series.chart,\n\t\t\tgroup,\n\t\t\toptions = series.options,\n\t\t\tanimation = options.animation,\n\t\t\tdoAnimation = animation && !!series.animate && \n\t\t\t\tchart.renderer.isSVG, // this animation doesn't work in IE8 quirks when the group div is hidden,\n\t\t\t\t// and looks bad in other oldIE\n\t\t\tvisibility = series.visible ? VISIBLE : HIDDEN,\n\t\t\tzIndex = options.zIndex,\n\t\t\thasRendered = series.hasRendered,\n\t\t\tchartSeriesGroup = chart.seriesGroup;\n\t\t\n\t\t// the group\n\t\tgroup = series.plotGroup(\n\t\t\t'group', \n\t\t\t'series', \n\t\t\tvisibility, \n\t\t\tzIndex, \n\t\t\tchartSeriesGroup\n\t\t);\n\t\t\n\t\tseries.markerGroup = series.plotGroup(\n\t\t\t'markerGroup', \n\t\t\t'markers', \n\t\t\tvisibility, \n\t\t\tzIndex, \n\t\t\tchartSeriesGroup\n\t\t);\n\t\t\n\t\t// initiate the animation\n\t\tif (doAnimation) {\n\t\t\tseries.animate(true);\n\t\t}\n\n\t\t// cache attributes for shapes\n\t\tseries.getAttribs();\n\n\t\t// SVGRenderer needs to know this before drawing elements (#1089, #1795)\n\t\tgroup.inverted = series.isCartesian ? chart.inverted : false;\n\t\t\n\t\t// draw the graph if any\n\t\tif (series.drawGraph) {\n\t\t\tseries.drawGraph();\n\t\t\tseries.clipNeg();\n\t\t}\n\n\t\t// draw the data labels (inn pies they go before the points)\n\t\tseries.drawDataLabels();\n\t\t\n\t\t// draw the points\n\t\tseries.drawPoints();\n\n\n\t\t// draw the mouse tracking area\n\t\tif (series.options.enableMouseTracking !== false) {\n\t\t\tseries.drawTracker();\n\t\t}\n\t\t\n\t\t// Handle inverted series and tracker groups\n\t\tif (chart.inverted) {\n\t\t\tseries.invertGroups();\n\t\t}\n\t\t\n\t\t// Initial clipping, must be defined after inverting groups for VML\n\t\tif (options.clip !== false && !series.sharedClipKey && !hasRendered) {\n\t\t\tgroup.clip(chart.clipRect);\n\t\t}\n\n\t\t// Run the animation\n\t\tif (doAnimation) {\n\t\t\tseries.animate();\n\t\t} else if (!hasRendered) {\n\t\t\tseries.afterAnimate();\n\t\t}\n\n\t\tseries.isDirty = series.isDirtyData = false; // means data is in accordance with what you see\n\t\t// (See #322) series.isDirty = series.isDirtyData = false; // means data is in accordance with what you see\n\t\tseries.hasRendered = true;\n\t},\n\t\n\t/**\n\t * Redraw the series after an update in the axes.\n\t */\n\tredraw: function () {\n\t\tvar series = this,\n\t\t\tchart = series.chart,\n\t\t\twasDirtyData = series.isDirtyData, // cache it here as it is set to false in render, but used after\n\t\t\tgroup = series.group,\n\t\t\txAxis = series.xAxis,\n\t\t\tyAxis = series.yAxis;\n\n\t\t// reposition on resize\n\t\tif (group) {\n\t\t\tif (chart.inverted) {\n\t\t\t\tgroup.attr({\n\t\t\t\t\twidth: chart.plotWidth,\n\t\t\t\t\theight: chart.plotHeight\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tgroup.animate({\n\t\t\t\ttranslateX: pick(xAxis && xAxis.left, chart.plotLeft),\n\t\t\t\ttranslateY: pick(yAxis && yAxis.top, chart.plotTop)\n\t\t\t});\n\t\t}\n\n\t\tseries.translate();\n\t\tseries.setTooltipPoints(true);\n\n\t\tseries.render();\n\t\tif (wasDirtyData) {\n\t\t\tfireEvent(series, 'updatedData');\n\t\t}\n\t},\n\n\t/**\n\t * Set the state of the graph\n\t */\n\tsetState: function (state) {\n\t\tvar series = this,\n\t\t\toptions = series.options,\n\t\t\tgraph = series.graph,\n\t\t\tgraphNeg = series.graphNeg,\n\t\t\tstateOptions = options.states,\n\t\t\tlineWidth = options.lineWidth,\n\t\t\tattribs;\n\n\t\tstate = state || NORMAL_STATE;\n\n\t\tif (series.state !== state) {\n\t\t\tseries.state = state;\n\n\t\t\tif (stateOptions[state] && stateOptions[state].enabled === false) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (state) {\n\t\t\t\tlineWidth = stateOptions[state].lineWidth || lineWidth + 1;\n\t\t\t}\n\n\t\t\tif (graph && !graph.dashstyle) { // hover is turned off for dashed lines in VML\n\t\t\t\tattribs = {\n\t\t\t\t\t'stroke-width': lineWidth\n\t\t\t\t};\n\t\t\t\t// use attr because animate will cause any other animation on the graph to stop\n\t\t\t\tgraph.attr(attribs);\n\t\t\t\tif (graphNeg) {\n\t\t\t\t\tgraphNeg.attr(attribs);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\n\t/**\n\t * Set the visibility of the graph\n\t *\n\t * @param vis {Boolean} True to show the series, false to hide. If UNDEFINED,\n\t *        the visibility is toggled.\n\t */\n\tsetVisible: function (vis, redraw) {\n\t\tvar series = this,\n\t\t\tchart = series.chart,\n\t\t\tlegendItem = series.legendItem,\n\t\t\tshowOrHide,\n\t\t\tignoreHiddenSeries = chart.options.chart.ignoreHiddenSeries,\n\t\t\toldVisibility = series.visible;\n\n\t\t// if called without an argument, toggle visibility\n\t\tseries.visible = vis = series.userOptions.visible = vis === UNDEFINED ? !oldVisibility : vis;\n\t\tshowOrHide = vis ? 'show' : 'hide';\n\n\t\t// show or hide elements\n\t\teach(['group', 'dataLabelsGroup', 'markerGroup', 'tracker'], function (key) {\n\t\t\tif (series[key]) {\n\t\t\t\tseries[key][showOrHide]();\n\t\t\t}\n\t\t});\n\n\t\t\n\t\t// hide tooltip (#1361)\n\t\tif (chart.hoverSeries === series) {\n\t\t\tseries.onMouseOut();\n\t\t}\n\n\n\t\tif (legendItem) {\n\t\t\tchart.legend.colorizeItem(series, vis);\n\t\t}\n\n\n\t\t// rescale or adapt to resized chart\n\t\tseries.isDirty = true;\n\t\t// in a stack, all other series are affected\n\t\tif (series.options.stacking) {\n\t\t\teach(chart.series, function (otherSeries) {\n\t\t\t\tif (otherSeries.options.stacking && otherSeries.visible) {\n\t\t\t\t\totherSeries.isDirty = true;\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\t// show or hide linked series\n\t\teach(series.linkedSeries, function (otherSeries) {\n\t\t\totherSeries.setVisible(vis, false);\n\t\t});\n\n\t\tif (ignoreHiddenSeries) {\n\t\t\tchart.isDirtyBox = true;\n\t\t}\n\t\tif (redraw !== false) {\n\t\t\tchart.redraw();\n\t\t}\n\n\t\tfireEvent(series, showOrHide);\n\t},\n\n\t/**\n\t * Show the graph\n\t */\n\tshow: function () {\n\t\tthis.setVisible(true);\n\t},\n\n\t/**\n\t * Hide the graph\n\t */\n\thide: function () {\n\t\tthis.setVisible(false);\n\t},\n\n\n\t/**\n\t * Set the selected state of the graph\n\t *\n\t * @param selected {Boolean} True to select the series, false to unselect. If\n\t *        UNDEFINED, the selection state is toggled.\n\t */\n\tselect: function (selected) {\n\t\tvar series = this;\n\t\t// if called without an argument, toggle\n\t\tseries.selected = selected = (selected === UNDEFINED) ? !series.selected : selected;\n\n\t\tif (series.checkbox) {\n\t\t\tseries.checkbox.checked = selected;\n\t\t}\n\n\t\tfireEvent(series, selected ? 'select' : 'unselect');\n\t},\n\n\t/**\n\t * Draw the tracker object that sits above all data labels and markers to\n\t * track mouse events on the graph or points. For the line type charts\n\t * the tracker uses the same graphPath, but with a greater stroke width\n\t * for better control.\n\t */\n\tdrawTracker: function () {\n\t\tvar series = this,\n\t\t\toptions = series.options,\n\t\t\ttrackByArea = options.trackByArea,\n\t\t\ttrackerPath = [].concat(trackByArea ? series.areaPath : series.graphPath),\n\t\t\ttrackerPathLength = trackerPath.length,\n\t\t\tchart = series.chart,\n\t\t\tpointer = chart.pointer,\n\t\t\trenderer = chart.renderer,\n\t\t\tsnap = chart.options.tooltip.snap,\n\t\t\ttracker = series.tracker,\n\t\t\tcursor = options.cursor,\n\t\t\tcss = cursor && { cursor: cursor },\n\t\t\tsinglePoints = series.singlePoints,\n\t\t\tsinglePoint,\n\t\t\ti,\n\t\t\tonMouseOver = function () {\n\t\t\t\tif (chart.hoverSeries !== series) {\n\t\t\t\t\tseries.onMouseOver();\n\t\t\t\t}\n\t\t\t};\n\n\t\t// Extend end points. A better way would be to use round linecaps,\n\t\t// but those are not clickable in VML.\n\t\tif (trackerPathLength && !trackByArea) {\n\t\t\ti = trackerPathLength + 1;\n\t\t\twhile (i--) {\n\t\t\t\tif (trackerPath[i] === M) { // extend left side\n\t\t\t\t\ttrackerPath.splice(i + 1, 0, trackerPath[i + 1] - snap, trackerPath[i + 2], L);\n\t\t\t\t}\n\t\t\t\tif ((i && trackerPath[i] === M) || i === trackerPathLength) { // extend right side\n\t\t\t\t\ttrackerPath.splice(i, 0, L, trackerPath[i - 2] + snap, trackerPath[i - 1]);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// handle single points\n\t\tfor (i = 0; i < singlePoints.length; i++) {\n\t\t\tsinglePoint = singlePoints[i];\n\t\t\ttrackerPath.push(M, singlePoint.plotX - snap, singlePoint.plotY,\n\t\t\t\tL, singlePoint.plotX + snap, singlePoint.plotY);\n\t\t}\n\t\t\n\t\t\n\n\t\t// draw the tracker\n\t\tif (tracker) {\n\t\t\ttracker.attr({ d: trackerPath });\n\n\t\t} else { // create\n\t\t\t\t\n\t\t\tseries.tracker = renderer.path(trackerPath)\n\t\t\t\t.attr({\n\t\t\t\t\t'stroke-linejoin': 'round', // #1225\n\t\t\t\t\tvisibility: series.visible ? VISIBLE : HIDDEN,\n\t\t\t\t\tstroke: TRACKER_FILL,\n\t\t\t\t\tfill: trackByArea ? TRACKER_FILL : NONE,\n\t\t\t\t\t'stroke-width' : options.lineWidth + (trackByArea ? 0 : 2 * snap),\n\t\t\t\t\tzIndex: 2\n\t\t\t\t})\n\t\t\t\t.add(series.group);\n\t\t\t\t\n\t\t\t// The tracker is added to the series group, which is clipped, but is covered \n\t\t\t// by the marker group. So the marker group also needs to capture events.\n\t\t\teach([series.tracker, series.markerGroup], function (tracker) {\n\t\t\t\ttracker.addClass(PREFIX + 'tracker')\n\t\t\t\t\t.on('mouseover', onMouseOver)\n\t\t\t\t\t.on('mouseout', function (e) { pointer.onTrackerMouseOut(e); })\n\t\t\t\t\t.css(css);\n\n\t\t\t\tif (hasTouch) {\n\t\t\t\t\ttracker.on('touchstart', onMouseOver);\n\t\t\t\t} \n\t\t\t});\n\t\t}\n\n\t}\n\n}; // end Series prototype\n\n\n/**\n * LineSeries object\n */\nvar LineSeries = extendClass(Series);\nseriesTypes.line = LineSeries;\n\n/**\n * Set the default options for area\n */\ndefaultPlotOptions.area = merge(defaultSeriesOptions, {\n\tthreshold: 0\n\t// trackByArea: false,\n\t// lineColor: null, // overrides color, but lets fillColor be unaltered\n\t// fillOpacity: 0.75,\n\t// fillColor: null\n});\n\n/**\n * AreaSeries object\n */\nvar AreaSeries = extendClass(Series, {\n\ttype: 'area',\n\t\n\t/**\n\t * For stacks, don't split segments on null values. Instead, draw null values with \n\t * no marker. Also insert dummy points for any X position that exists in other series\n\t * in the stack.\n\t */ \n\tgetSegments: function () {\n\t\tvar segments = [],\n\t\t\tsegment = [],\n\t\t\tkeys = [],\n\t\t\txAxis = this.xAxis,\n\t\t\tyAxis = this.yAxis,\n\t\t\tstack = yAxis.stacks[this.stackKey],\n\t\t\tpointMap = {},\n\t\t\tplotX,\n\t\t\tplotY,\n\t\t\tpoints = this.points,\n\t\t\tconnectNulls = this.options.connectNulls,\n\t\t\tval,\n\t\t\ti,\n\t\t\tx;\n\n\t\tif (this.options.stacking && !this.cropped) { // cropped causes artefacts in Stock, and perf issue\n\t\t\t// Create a map where we can quickly look up the points by their X value.\n\t\t\tfor (i = 0; i < points.length; i++) {\n\t\t\t\tpointMap[points[i].x] = points[i];\n\t\t\t}\n\n\t\t\t// Sort the keys (#1651)\n\t\t\tfor (x in stack) {\n\t\t\t\tkeys.push(+x);\n\t\t\t}\n\t\t\tkeys.sort(function (a, b) {\n\t\t\t\treturn a - b;\n\t\t\t});\n\n\t\t\teach(keys, function (x) {\n\t\t\t\tif (connectNulls && (!pointMap[x] || pointMap[x].y === null)) { // #1836\n\t\t\t\t\treturn;\n\n\t\t\t\t// The point exists, push it to the segment\n\t\t\t\t} else if (pointMap[x]) {\n\t\t\t\t\tsegment.push(pointMap[x]);\n\n\t\t\t\t// There is no point for this X value in this series, so we \n\t\t\t\t// insert a dummy point in order for the areas to be drawn\n\t\t\t\t// correctly.\n\t\t\t\t} else {\n\t\t\t\t\tplotX = xAxis.translate(x);\n\t\t\t\t\tval = stack[x].percent ? (stack[x].total ? stack[x].cum * 100 / stack[x].total : 0) : stack[x].cum; // #1991\n\t\t\t\t\tplotY = yAxis.toPixels(val, true);\n\t\t\t\t\tsegment.push({ \n\t\t\t\t\t\ty: null, \n\t\t\t\t\t\tplotX: plotX,\n\t\t\t\t\t\tclientX: plotX, \n\t\t\t\t\t\tplotY: plotY, \n\t\t\t\t\t\tyBottom: plotY,\n\t\t\t\t\t\tonMouseOver: noop\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tif (segment.length) {\n\t\t\t\tsegments.push(segment);\n\t\t\t}\n\n\t\t} else {\n\t\t\tSeries.prototype.getSegments.call(this);\n\t\t\tsegments = this.segments;\n\t\t}\n\n\t\tthis.segments = segments;\n\t},\n\t\n\t/**\n\t * Extend the base Series getSegmentPath method by adding the path for the area.\n\t * This path is pushed to the series.areaPath property.\n\t */\n\tgetSegmentPath: function (segment) {\n\t\t\n\t\tvar segmentPath = Series.prototype.getSegmentPath.call(this, segment), // call base method\n\t\t\tareaSegmentPath = [].concat(segmentPath), // work on a copy for the area path\n\t\t\ti,\n\t\t\toptions = this.options,\n\t\t\tsegLength = segmentPath.length,\n\t\t\ttranslatedThreshold = this.yAxis.getThreshold(options.threshold), // #2181\n\t\t\tyBottom;\n\t\t\n\t\tif (segLength === 3) { // for animation from 1 to two points\n\t\t\tareaSegmentPath.push(L, segmentPath[1], segmentPath[2]);\n\t\t}\n\t\tif (options.stacking && !this.closedStacks) {\n\t\t\t\n\t\t\t// Follow stack back. Todo: implement areaspline. A general solution could be to \n\t\t\t// reverse the entire graphPath of the previous series, though may be hard with\n\t\t\t// splines and with series with different extremes\n\t\t\tfor (i = segment.length - 1; i >= 0; i--) {\n\n\t\t\t\tyBottom = pick(segment[i].yBottom, translatedThreshold);\n\t\t\t\n\t\t\t\t// step line?\n\t\t\t\tif (i < segment.length - 1 && options.step) {\n\t\t\t\t\tareaSegmentPath.push(segment[i + 1].plotX, yBottom);\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tareaSegmentPath.push(segment[i].plotX, yBottom);\n\t\t\t}\n\n\t\t} else { // follow zero line back\n\t\t\tthis.closeSegment(areaSegmentPath, segment, translatedThreshold);\n\t\t}\n\t\tthis.areaPath = this.areaPath.concat(areaSegmentPath);\n\t\treturn segmentPath;\n\t},\n\t\n\t/**\n\t * Extendable method to close the segment path of an area. This is overridden in polar \n\t * charts.\n\t */\n\tcloseSegment: function (path, segment, translatedThreshold) {\n\t\tpath.push(\n\t\t\tL,\n\t\t\tsegment[segment.length - 1].plotX,\n\t\t\ttranslatedThreshold,\n\t\t\tL,\n\t\t\tsegment[0].plotX,\n\t\t\ttranslatedThreshold\n\t\t);\n\t},\n\t\n\t/**\n\t * Draw the graph and the underlying area. This method calls the Series base\n\t * function and adds the area. The areaPath is calculated in the getSegmentPath\n\t * method called from Series.prototype.drawGraph.\n\t */\n\tdrawGraph: function () {\n\t\t\n\t\t// Define or reset areaPath\n\t\tthis.areaPath = [];\n\t\t\n\t\t// Call the base method\n\t\tSeries.prototype.drawGraph.apply(this);\n\t\t\n\t\t// Define local variables\n\t\tvar series = this,\n\t\t\tareaPath = this.areaPath,\n\t\t\toptions = this.options,\n\t\t\tnegativeColor = options.negativeColor,\n\t\t\tnegativeFillColor = options.negativeFillColor,\n\t\t\tprops = [['area', this.color, options.fillColor]]; // area name, main color, fill color\n\t\t\n\t\tif (negativeColor || negativeFillColor) {\n\t\t\tprops.push(['areaNeg', negativeColor, negativeFillColor]);\n\t\t}\n\t\t\n\t\teach(props, function (prop) {\n\t\t\tvar areaKey = prop[0],\n\t\t\t\tarea = series[areaKey];\n\t\t\t\t\n\t\t\t// Create or update the area\n\t\t\tif (area) { // update\n\t\t\t\tarea.animate({ d: areaPath });\n\t\n\t\t\t} else { // create\n\t\t\t\tseries[areaKey] = series.chart.renderer.path(areaPath)\n\t\t\t\t\t.attr({\n\t\t\t\t\t\tfill: pick(\n\t\t\t\t\t\t\tprop[2],\n\t\t\t\t\t\t\tColor(prop[1]).setOpacity(pick(options.fillOpacity, 0.75)).get()\n\t\t\t\t\t\t),\n\t\t\t\t\t\tzIndex: 0 // #1069\n\t\t\t\t\t}).add(series.group);\n\t\t\t}\n\t\t});\n\t},\n\t\n\t/**\n\t * Get the series' symbol in the legend\n\t * \n\t * @param {Object} legend The legend object\n\t * @param {Object} item The series (this) or point\n\t */\n\tdrawLegendSymbol: function (legend, item) {\n\t\t\n\t\titem.legendSymbol = this.chart.renderer.rect(\n\t\t\t0,\n\t\t\tlegend.baseline - 11,\n\t\t\tlegend.options.symbolWidth,\n\t\t\t12,\n\t\t\t2\n\t\t).attr({\n\t\t\tzIndex: 3\n\t\t}).add(item.legendGroup);\t\t\n\t\t\n\t}\n});\n\nseriesTypes.area = AreaSeries;/**\n * Set the default options for spline\n */\ndefaultPlotOptions.spline = merge(defaultSeriesOptions);\n\n/**\n * SplineSeries object\n */\nvar SplineSeries = extendClass(Series, {\n\ttype: 'spline',\n\n\t/**\n\t * Get the spline segment from a given point's previous neighbour to the given point\n\t */\n\tgetPointSpline: function (segment, point, i) {\n\t\tvar smoothing = 1.5, // 1 means control points midway between points, 2 means 1/3 from the point, 3 is 1/4 etc\n\t\t\tdenom = smoothing + 1,\n\t\t\tplotX = point.plotX,\n\t\t\tplotY = point.plotY,\n\t\t\tlastPoint = segment[i - 1],\n\t\t\tnextPoint = segment[i + 1],\n\t\t\tleftContX,\n\t\t\tleftContY,\n\t\t\trightContX,\n\t\t\trightContY,\n\t\t\tret;\n\n\t\t// find control points\n\t\tif (lastPoint && nextPoint) {\n\t\t\n\t\t\tvar lastX = lastPoint.plotX,\n\t\t\t\tlastY = lastPoint.plotY,\n\t\t\t\tnextX = nextPoint.plotX,\n\t\t\t\tnextY = nextPoint.plotY,\n\t\t\t\tcorrection;\n\n\t\t\tleftContX = (smoothing * plotX + lastX) / denom;\n\t\t\tleftContY = (smoothing * plotY + lastY) / denom;\n\t\t\trightContX = (smoothing * plotX + nextX) / denom;\n\t\t\trightContY = (smoothing * plotY + nextY) / denom;\n\n\t\t\t// have the two control points make a straight line through main point\n\t\t\tcorrection = ((rightContY - leftContY) * (rightContX - plotX)) /\n\t\t\t\t(rightContX - leftContX) + plotY - rightContY;\n\n\t\t\tleftContY += correction;\n\t\t\trightContY += correction;\n\n\t\t\t// to prevent false extremes, check that control points are between\n\t\t\t// neighbouring points' y values\n\t\t\tif (leftContY > lastY && leftContY > plotY) {\n\t\t\t\tleftContY = mathMax(lastY, plotY);\n\t\t\t\trightContY = 2 * plotY - leftContY; // mirror of left control point\n\t\t\t} else if (leftContY < lastY && leftContY < plotY) {\n\t\t\t\tleftContY = mathMin(lastY, plotY);\n\t\t\t\trightContY = 2 * plotY - leftContY;\n\t\t\t}\n\t\t\tif (rightContY > nextY && rightContY > plotY) {\n\t\t\t\trightContY = mathMax(nextY, plotY);\n\t\t\t\tleftContY = 2 * plotY - rightContY;\n\t\t\t} else if (rightContY < nextY && rightContY < plotY) {\n\t\t\t\trightContY = mathMin(nextY, plotY);\n\t\t\t\tleftContY = 2 * plotY - rightContY;\n\t\t\t}\n\n\t\t\t// record for drawing in next point\n\t\t\tpoint.rightContX = rightContX;\n\t\t\tpoint.rightContY = rightContY;\n\n\t\t}\n\t\t\n\t\t// Visualize control points for debugging\n\t\t/*\n\t\tif (leftContX) {\n\t\t\tthis.chart.renderer.circle(leftContX + this.chart.plotLeft, leftContY + this.chart.plotTop, 2)\n\t\t\t\t.attr({\n\t\t\t\t\tstroke: 'red',\n\t\t\t\t\t'stroke-width': 1,\n\t\t\t\t\tfill: 'none'\n\t\t\t\t})\n\t\t\t\t.add();\n\t\t\tthis.chart.renderer.path(['M', leftContX + this.chart.plotLeft, leftContY + this.chart.plotTop,\n\t\t\t\t'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop])\n\t\t\t\t.attr({\n\t\t\t\t\tstroke: 'red',\n\t\t\t\t\t'stroke-width': 1\n\t\t\t\t})\n\t\t\t\t.add();\n\t\t\tthis.chart.renderer.circle(rightContX + this.chart.plotLeft, rightContY + this.chart.plotTop, 2)\n\t\t\t\t.attr({\n\t\t\t\t\tstroke: 'green',\n\t\t\t\t\t'stroke-width': 1,\n\t\t\t\t\tfill: 'none'\n\t\t\t\t})\n\t\t\t\t.add();\n\t\t\tthis.chart.renderer.path(['M', rightContX + this.chart.plotLeft, rightContY + this.chart.plotTop,\n\t\t\t\t'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop])\n\t\t\t\t.attr({\n\t\t\t\t\tstroke: 'green',\n\t\t\t\t\t'stroke-width': 1\n\t\t\t\t})\n\t\t\t\t.add();\n\t\t}\n\t\t*/\n\n\t\t// moveTo or lineTo\n\t\tif (!i) {\n\t\t\tret = [M, plotX, plotY];\n\t\t} else { // curve from last point to this\n\t\t\tret = [\n\t\t\t\t'C',\n\t\t\t\tlastPoint.rightContX || lastPoint.plotX,\n\t\t\t\tlastPoint.rightContY || lastPoint.plotY,\n\t\t\t\tleftContX || plotX,\n\t\t\t\tleftContY || plotY,\n\t\t\t\tplotX,\n\t\t\t\tplotY\n\t\t\t];\n\t\t\tlastPoint.rightContX = lastPoint.rightContY = null; // reset for updating series later\n\t\t}\n\t\treturn ret;\n\t}\n});\nseriesTypes.spline = SplineSeries;\n\n/**\n * Set the default options for areaspline\n */\ndefaultPlotOptions.areaspline = merge(defaultPlotOptions.area);\n\n/**\n * AreaSplineSeries object\n */\nvar areaProto = AreaSeries.prototype,\n\tAreaSplineSeries = extendClass(SplineSeries, {\n\t\ttype: 'areaspline',\n\t\tclosedStacks: true, // instead of following the previous graph back, follow the threshold back\n\t\t\n\t\t// Mix in methods from the area series\n\t\tgetSegmentPath: areaProto.getSegmentPath,\n\t\tcloseSegment: areaProto.closeSegment,\n\t\tdrawGraph: areaProto.drawGraph,\n\t\tdrawLegendSymbol: areaProto.drawLegendSymbol\n\t});\nseriesTypes.areaspline = AreaSplineSeries;\n\n/**\n * Set the default options for column\n */\ndefaultPlotOptions.column = merge(defaultSeriesOptions, {\n\tborderColor: '#FFFFFF',\n\tborderWidth: 1,\n\tborderRadius: 0,\n\t//colorByPoint: undefined,\n\tgroupPadding: 0.2,\n\t//grouping: true,\n\tmarker: null, // point options are specified in the base options\n\tpointPadding: 0.1,\n\t//pointWidth: null,\n\tminPointLength: 0,\n\tcropThreshold: 50, // when there are more points, they will not animate out of the chart on xAxis.setExtremes\n\tpointRange: null, // null means auto, meaning 1 in a categorized axis and least distance between points if not categories\n\tstates: {\n\t\thover: {\n\t\t\tbrightness: 0.1,\n\t\t\tshadow: false\n\t\t},\n\t\tselect: {\n\t\t\tcolor: '#C0C0C0',\n\t\t\tborderColor: '#000000',\n\t\t\tshadow: false\n\t\t}\n\t},\n\tdataLabels: {\n\t\talign: null, // auto\n\t\tverticalAlign: null, // auto\n\t\ty: null\n\t},\n\tstickyTracking: false,\n\tthreshold: 0\n});\n\n/**\n * ColumnSeries object\n */\nvar ColumnSeries = extendClass(Series, {\n\ttype: 'column',\n\tpointAttrToOptions: { // mapping between SVG attributes and the corresponding options\n\t\tstroke: 'borderColor',\n\t\t'stroke-width': 'borderWidth',\n\t\tfill: 'color',\n\t\tr: 'borderRadius'\n\t},\n\tcropShoulder: 0,\n\ttrackerGroups: ['group', 'dataLabelsGroup'],\n\tnegStacks: true, // use separate negative stacks, unlike area stacks where a negative \n\t\t// point is substracted from previous (#1910)\n\t\n\t/**\n\t * Initialize the series\n\t */\n\tinit: function () {\n\t\tSeries.prototype.init.apply(this, arguments);\n\n\t\tvar series = this,\n\t\t\tchart = series.chart;\n\n\t\t// if the series is added dynamically, force redraw of other\n\t\t// series affected by a new column\n\t\tif (chart.hasRendered) {\n\t\t\teach(chart.series, function (otherSeries) {\n\t\t\t\tif (otherSeries.type === series.type) {\n\t\t\t\t\totherSeries.isDirty = true;\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t},\n\n\t/**\n\t * Return the width and x offset of the columns adjusted for grouping, groupPadding, pointPadding,\n\t * pointWidth etc. \n\t */\n\tgetColumnMetrics: function () {\n\n\t\tvar series = this,\n\t\t\toptions = series.options,\n\t\t\txAxis = series.xAxis,\n\t\t\tyAxis = series.yAxis,\n\t\t\treversedXAxis = xAxis.reversed,\n\t\t\tstackKey,\n\t\t\tstackGroups = {},\n\t\t\tcolumnIndex,\n\t\t\tcolumnCount = 0;\n\n\t\t// Get the total number of column type series.\n\t\t// This is called on every series. Consider moving this logic to a\n\t\t// chart.orderStacks() function and call it on init, addSeries and removeSeries\n\t\tif (options.grouping === false) {\n\t\t\tcolumnCount = 1;\n\t\t} else {\n\t\t\teach(series.chart.series, function (otherSeries) {\n\t\t\t\tvar otherOptions = otherSeries.options,\n\t\t\t\t\totherYAxis = otherSeries.yAxis;\n\t\t\t\tif (otherSeries.type === series.type && otherSeries.visible &&\n\t\t\t\t\t\tyAxis.len === otherYAxis.len && yAxis.pos === otherYAxis.pos) {  // #642, #2086\n\t\t\t\t\tif (otherOptions.stacking) {\n\t\t\t\t\t\tstackKey = otherSeries.stackKey;\n\t\t\t\t\t\tif (stackGroups[stackKey] === UNDEFINED) {\n\t\t\t\t\t\t\tstackGroups[stackKey] = columnCount++;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcolumnIndex = stackGroups[stackKey];\n\t\t\t\t\t} else if (otherOptions.grouping !== false) { // #1162\n\t\t\t\t\t\tcolumnIndex = columnCount++;\n\t\t\t\t\t}\n\t\t\t\t\totherSeries.columnIndex = columnIndex;\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\tvar categoryWidth = mathMin(\n\t\t\t\tmathAbs(xAxis.transA) * (xAxis.ordinalSlope || options.pointRange || xAxis.closestPointRange || 1), \n\t\t\t\txAxis.len // #1535\n\t\t\t),\n\t\t\tgroupPadding = categoryWidth * options.groupPadding,\n\t\t\tgroupWidth = categoryWidth - 2 * groupPadding,\n\t\t\tpointOffsetWidth = groupWidth / columnCount,\n\t\t\toptionPointWidth = options.pointWidth,\n\t\t\tpointPadding = defined(optionPointWidth) ? (pointOffsetWidth - optionPointWidth) / 2 :\n\t\t\t\tpointOffsetWidth * options.pointPadding,\n\t\t\tpointWidth = pick(optionPointWidth, pointOffsetWidth - 2 * pointPadding), // exact point width, used in polar charts\n\t\t\tcolIndex = (reversedXAxis ? \n\t\t\t\tcolumnCount - (series.columnIndex || 0) : // #1251\n\t\t\t\tseries.columnIndex) || 0,\n\t\t\tpointXOffset = pointPadding + (groupPadding + colIndex *\n\t\t\t\tpointOffsetWidth - (categoryWidth / 2)) *\n\t\t\t\t(reversedXAxis ? -1 : 1);\n\n\t\t// Save it for reading in linked series (Error bars particularly)\n\t\treturn (series.columnMetrics = { \n\t\t\twidth: pointWidth, \n\t\t\toffset: pointXOffset \n\t\t});\n\t\t\t\n\t},\n\n\t/**\n\t * Translate each point to the plot area coordinate system and find shape positions\n\t */\n\ttranslate: function () {\n\t\tvar series = this,\n\t\t\tchart = series.chart,\n\t\t\toptions = series.options,\n\t\t\tborderWidth = options.borderWidth,\n\t\t\tyAxis = series.yAxis,\n\t\t\tthreshold = options.threshold,\n\t\t\ttranslatedThreshold = series.translatedThreshold = yAxis.getThreshold(threshold),\n\t\t\tminPointLength = pick(options.minPointLength, 5),\n\t\t\tmetrics = series.getColumnMetrics(),\n\t\t\tpointWidth = metrics.width,\n\t\t\tseriesBarW = series.barW = mathCeil(mathMax(pointWidth, 1 + 2 * borderWidth)), // rounded and postprocessed for border width\n\t\t\tpointXOffset = series.pointXOffset = metrics.offset,\n\t\t\txCrisp = -(borderWidth % 2 ? 0.5 : 0),\n\t\t\tyCrisp = borderWidth % 2 ? 0.5 : 1;\n\n\t\tif (chart.renderer.isVML && chart.inverted) {\n\t\t\tyCrisp += 1;\n\t\t}\n\n\t\tSeries.prototype.translate.apply(series);\n\n\t\t// record the new values\n\t\teach(series.points, function (point) {\n\t\t\tvar yBottom = pick(point.yBottom, translatedThreshold),\n\t\t\t\tplotY = mathMin(mathMax(-999 - yBottom, point.plotY), yAxis.len + 999 + yBottom), // Don't draw too far outside plot area (#1303, #2241)\n\t\t\t\tbarX = point.plotX + pointXOffset,\n\t\t\t\tbarW = seriesBarW,\n\t\t\t\tbarY = mathMin(plotY, yBottom),\n\t\t\t\tright,\n\t\t\t\tbottom,\n\t\t\t\tfromTop,\n\t\t\t\tfromLeft,\n\t\t\t\tbarH = mathMax(plotY, yBottom) - barY;\n\n\t\t\t// Handle options.minPointLength\n\t\t\tif (mathAbs(barH) < minPointLength) {\n\t\t\t\tif (minPointLength) {\n\t\t\t\t\tbarH = minPointLength;\n\t\t\t\t\tbarY =\n\t\t\t\t\t\tmathRound(mathAbs(barY - translatedThreshold) > minPointLength ? // stacked\n\t\t\t\t\t\t\tyBottom - minPointLength : // keep position\n\t\t\t\t\t\t\ttranslatedThreshold - (yAxis.translate(point.y, 0, 1, 0, 1) <= translatedThreshold ? minPointLength : 0)); // use exact yAxis.translation (#1485)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Cache for access in polar\n\t\t\tpoint.barX = barX;\n\t\t\tpoint.pointWidth = pointWidth;\n\n\n\t\t\t// Round off to obtain crisp edges\n\t\t\tfromLeft = mathAbs(barX) < 0.5;\n\t\t\tright = mathRound(barX + barW) + xCrisp;\n\t\t\tbarX = mathRound(barX) + xCrisp;\n\t\t\tbarW = right - barX;\n\n\t\t\tfromTop = mathAbs(barY) < 0.5;\n\t\t\tbottom = mathRound(barY + barH) + yCrisp;\n\t\t\tbarY = mathRound(barY) + yCrisp;\n\t\t\tbarH = bottom - barY;\n\n\t\t\t// Top and left edges are exceptions\n\t\t\tif (fromLeft) {\n\t\t\t\tbarX += 1;\n\t\t\t\tbarW -= 1;\n\t\t\t}\n\t\t\tif (fromTop) {\n\t\t\t\tbarY -= 1;\n\t\t\t\tbarH += 1;\n\t\t\t}\n\n\t\t\t// Register shape type and arguments to be used in drawPoints\n\t\t\tpoint.shapeType = 'rect';\n\t\t\tpoint.shapeArgs = {\n\t\t\t\tx: barX,\n\t\t\t\ty: barY,\n\t\t\t\twidth: barW,\n\t\t\t\theight: barH\n\t\t\t};\n\t\t});\n\n\t},\n\n\tgetSymbol: noop,\n\t\n\t/**\n\t * Use a solid rectangle like the area series types\n\t */\n\tdrawLegendSymbol: AreaSeries.prototype.drawLegendSymbol,\n\t\n\t\n\t/**\n\t * Columns have no graph\n\t */\n\tdrawGraph: noop,\n\n\t/**\n\t * Draw the columns. For bars, the series.group is rotated, so the same coordinates\n\t * apply for columns and bars. This method is inherited by scatter series.\n\t *\n\t */\n\tdrawPoints: function () {\n\t\tvar series = this,\n\t\t\toptions = series.options,\n\t\t\trenderer = series.chart.renderer,\n\t\t\tshapeArgs;\n\n\n\t\t// draw the columns\n\t\teach(series.points, function (point) {\n\t\t\tvar plotY = point.plotY,\n\t\t\t\tgraphic = point.graphic;\n\n\t\t\tif (plotY !== UNDEFINED && !isNaN(plotY) && point.y !== null) {\n\t\t\t\tshapeArgs = point.shapeArgs;\n\t\t\t\t\n\t\t\t\tif (graphic) { // update\n\t\t\t\t\tstop(graphic);\n\t\t\t\t\tgraphic.animate(merge(shapeArgs));\n\n\t\t\t\t} else {\n\t\t\t\t\tpoint.graphic = graphic = renderer[point.shapeType](shapeArgs)\n\t\t\t\t\t\t.attr(point.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE])\n\t\t\t\t\t\t.add(series.group)\n\t\t\t\t\t\t.shadow(options.shadow, null, options.stacking && !options.borderRadius);\n\t\t\t\t}\n\n\t\t\t} else if (graphic) {\n\t\t\t\tpoint.graphic = graphic.destroy(); // #1269\n\t\t\t}\n\t\t});\n\t},\n\n\t/**\n\t * Add tracking event listener to the series group, so the point graphics\n\t * themselves act as trackers\n\t */\n\tdrawTracker: function () {\n\t\tvar series = this,\n\t\t\tchart = series.chart,\n\t\t\tpointer = chart.pointer,\n\t\t\tcursor = series.options.cursor,\n\t\t\tcss = cursor && { cursor: cursor },\n\t\t\tonMouseOver = function (e) {\n\t\t\t\tvar target = e.target,\n\t\t\t\t\tpoint;\n\n\t\t\t\tif (chart.hoverSeries !== series) {\n\t\t\t\t\tseries.onMouseOver();\n\t\t\t\t}\n\t\t\t\twhile (target && !point) {\n\t\t\t\t\tpoint = target.point;\n\t\t\t\t\ttarget = target.parentNode;\n\t\t\t\t}\n\t\t\t\tif (point !== UNDEFINED && point !== chart.hoverPoint) { // undefined on graph in scatterchart\n\t\t\t\t\tpoint.onMouseOver(e);\n\t\t\t\t}\n\t\t\t};\n\n\t\t// Add reference to the point\n\t\teach(series.points, function (point) {\n\t\t\tif (point.graphic) {\n\t\t\t\tpoint.graphic.element.point = point;\n\t\t\t}\n\t\t\tif (point.dataLabel) {\n\t\t\t\tpoint.dataLabel.element.point = point;\n\t\t\t}\n\t\t});\n\n\t\t// Add the event listeners, we need to do this only once\n\t\tif (!series._hasTracking) {\n\t\t\teach(series.trackerGroups, function (key) {\n\t\t\t\tif (series[key]) { // we don't always have dataLabelsGroup\n\t\t\t\t\tseries[key]\n\t\t\t\t\t\t.addClass(PREFIX + 'tracker')\n\t\t\t\t\t\t.on('mouseover', onMouseOver)\n\t\t\t\t\t\t.on('mouseout', function (e) { pointer.onTrackerMouseOut(e); })\n\t\t\t\t\t\t.css(css);\n\t\t\t\t\tif (hasTouch) {\n\t\t\t\t\t\tseries[key].on('touchstart', onMouseOver);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t\tseries._hasTracking = true;\n\t\t}\n\t},\n\t\n\t/** \n\t * Override the basic data label alignment by adjusting for the position of the column\n\t */\n\talignDataLabel: function (point, dataLabel, options,  alignTo, isNew) {\n\t\tvar chart = this.chart,\n\t\t\tinverted = chart.inverted,\n\t\t\tdlBox = point.dlBox || point.shapeArgs, // data label box for alignment\n\t\t\tbelow = point.below || (point.plotY > pick(this.translatedThreshold, chart.plotSizeY)),\n\t\t\tinside = pick(options.inside, !!this.options.stacking); // draw it inside the box?\n\t\t\n\t\t// Align to the column itself, or the top of it\n\t\tif (dlBox) { // Area range uses this method but not alignTo\n\t\t\talignTo = merge(dlBox);\n\t\t\tif (inverted) {\n\t\t\t\talignTo = {\n\t\t\t\t\tx: chart.plotWidth - alignTo.y - alignTo.height,\n\t\t\t\t\ty: chart.plotHeight - alignTo.x - alignTo.width,\n\t\t\t\t\twidth: alignTo.height,\n\t\t\t\t\theight: alignTo.width\n\t\t\t\t};\n\t\t\t}\n\t\t\t\t\n\t\t\t// Compute the alignment box\n\t\t\tif (!inside) {\n\t\t\t\tif (inverted) {\n\t\t\t\t\talignTo.x += below ? 0 : alignTo.width;\n\t\t\t\t\talignTo.width = 0;\n\t\t\t\t} else {\n\t\t\t\t\talignTo.y += below ? alignTo.height : 0;\n\t\t\t\t\talignTo.height = 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t\n\t\t// When alignment is undefined (typically columns and bars), display the individual \n\t\t// point below or above the point depending on the threshold\n\t\toptions.align = pick(\n\t\t\toptions.align, \n\t\t\t!inverted || inside ? 'center' : below ? 'right' : 'left'\n\t\t);\n\t\toptions.verticalAlign = pick(\n\t\t\toptions.verticalAlign, \n\t\t\tinverted || inside ? 'middle' : below ? 'top' : 'bottom'\n\t\t);\n\t\t\n\t\t// Call the parent method\n\t\tSeries.prototype.alignDataLabel.call(this, point, dataLabel, options, alignTo, isNew);\n\t},\n\n\n\t/**\n\t * Animate the column heights one by one from zero\n\t * @param {Boolean} init Whether to initialize the animation or run it\n\t */\n\tanimate: function (init) {\n\t\tvar series = this,\n\t\t\tyAxis = this.yAxis,\n\t\t\toptions = series.options,\n\t\t\tinverted = this.chart.inverted,\n\t\t\tattr = {},\n\t\t\ttranslatedThreshold;\n\n\t\tif (hasSVG) { // VML is too slow anyway\n\t\t\tif (init) {\n\t\t\t\tattr.scaleY = 0.001;\n\t\t\t\ttranslatedThreshold = mathMin(yAxis.pos + yAxis.len, mathMax(yAxis.pos, yAxis.toPixels(options.threshold)));\n\t\t\t\tif (inverted) {\n\t\t\t\t\tattr.translateX = translatedThreshold - yAxis.len;\n\t\t\t\t} else {\n\t\t\t\t\tattr.translateY = translatedThreshold;\n\t\t\t\t}\n\t\t\t\tseries.group.attr(attr);\n\n\t\t\t} else { // run the animation\n\t\t\t\t\n\t\t\t\tattr.scaleY = 1;\n\t\t\t\tattr[inverted ? 'translateX' : 'translateY'] = yAxis.pos;\n\t\t\t\tseries.group.animate(attr, series.options.animation);\n\n\t\t\t\t// delete this function to allow it only once\n\t\t\t\tseries.animate = null;\n\t\t\t}\n\t\t}\n\t},\n\t\n\t/**\n\t * Remove this series from the chart\n\t */\n\tremove: function () {\n\t\tvar series = this,\n\t\t\tchart = series.chart;\n\n\t\t// column and bar series affects other series of the same type\n\t\t// as they are either stacked or grouped\n\t\tif (chart.hasRendered) {\n\t\t\teach(chart.series, function (otherSeries) {\n\t\t\t\tif (otherSeries.type === series.type) {\n\t\t\t\t\totherSeries.isDirty = true;\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\tSeries.prototype.remove.apply(series, arguments);\n\t}\n});\nseriesTypes.column = ColumnSeries;\n/**\n * Set the default options for bar\n */\ndefaultPlotOptions.bar = merge(defaultPlotOptions.column);\n/**\n * The Bar series class\n */\nvar BarSeries = extendClass(ColumnSeries, {\n\ttype: 'bar',\n\tinverted: true\n});\nseriesTypes.bar = BarSeries;\n\n/**\n * Set the default options for scatter\n */\ndefaultPlotOptions.scatter = merge(defaultSeriesOptions, {\n\tlineWidth: 0,\n\ttooltip: {\n\t\theaderFormat: '<span style=\"font-size: 10px; color:{series.color}\">{series.name}</span><br/>',\n\t\tpointFormat: 'x: <b>{point.x}</b><br/>y: <b>{point.y}</b><br/>',\n\t\tfollowPointer: true\n\t},\n\tstickyTracking: false\n});\n\n/**\n * The scatter series class\n */\nvar ScatterSeries = extendClass(Series, {\n\ttype: 'scatter',\n\tsorted: false,\n\trequireSorting: false,\n\tnoSharedTooltip: true,\n\ttrackerGroups: ['markerGroup'],\n\n\tdrawTracker: ColumnSeries.prototype.drawTracker,\n\t\n\tsetTooltipPoints: noop\n});\nseriesTypes.scatter = ScatterSeries;\n\n/**\n * Set the default options for pie\n */\ndefaultPlotOptions.pie = merge(defaultSeriesOptions, {\n\tborderColor: '#FFFFFF',\n\tborderWidth: 1,\n\tcenter: [null, null],\n\tclip: false,\n\tcolorByPoint: true, // always true for pies\n\tdataLabels: {\n\t\t// align: null,\n\t\t// connectorWidth: 1,\n\t\t// connectorColor: point.color,\n\t\t// connectorPadding: 5,\n\t\tdistance: 30,\n\t\tenabled: true,\n\t\tformatter: function () {\n\t\t\treturn this.point.name;\n\t\t}\n\t\t// softConnector: true,\n\t\t//y: 0\n\t},\n\tignoreHiddenPoint: true,\n\t//innerSize: 0,\n\tlegendType: 'point',\n\tmarker: null, // point options are specified in the base options\n\tsize: null,\n\tshowInLegend: false,\n\tslicedOffset: 10,\n\tstates: {\n\t\thover: {\n\t\t\tbrightness: 0.1,\n\t\t\tshadow: false\n\t\t}\n\t},\n\tstickyTracking: false,\n\ttooltip: {\n\t\tfollowPointer: true\n\t}\n});\n\n/**\n * Extended point object for pies\n */\nvar PiePoint = extendClass(Point, {\n\t/**\n\t * Initiate the pie slice\n\t */\n\tinit: function () {\n\n\t\tPoint.prototype.init.apply(this, arguments);\n\n\t\tvar point = this,\n\t\t\ttoggleSlice;\n\n\t\t// Disallow negative values (#1530)\n\t\tif (point.y < 0) {\n\t\t\tpoint.y = null;\n\t\t}\n\n\t\t//visible: options.visible !== false,\n\t\textend(point, {\n\t\t\tvisible: point.visible !== false,\n\t\t\tname: pick(point.name, 'Slice')\n\t\t});\n\n\t\t// add event listener for select\n\t\ttoggleSlice = function (e) {\n\t\t\tpoint.slice(e.type === 'select');\n\t\t};\n\t\taddEvent(point, 'select', toggleSlice);\n\t\taddEvent(point, 'unselect', toggleSlice);\n\n\t\treturn point;\n\t},\n\n\t/**\n\t * Toggle the visibility of the pie slice\n\t * @param {Boolean} vis Whether to show the slice or not. If undefined, the\n\t *    visibility is toggled\n\t */\n\tsetVisible: function (vis) {\n\t\tvar point = this,\n\t\t\tseries = point.series,\n\t\t\tchart = series.chart,\n\t\t\tmethod;\n\n\t\t// if called without an argument, toggle visibility\n\t\tpoint.visible = point.options.visible = vis = vis === UNDEFINED ? !point.visible : vis;\n\t\tseries.options.data[inArray(point, series.data)] = point.options; // update userOptions.data\n\t\t\n\t\tmethod = vis ? 'show' : 'hide';\n\n\t\t// Show and hide associated elements\n\t\teach(['graphic', 'dataLabel', 'connector', 'shadowGroup'], function (key) {\n\t\t\tif (point[key]) {\n\t\t\t\tpoint[key][method]();\n\t\t\t}\n\t\t});\n\n\t\tif (point.legendItem) {\n\t\t\tchart.legend.colorizeItem(point, vis);\n\t\t}\n\t\t\n\t\t// Handle ignore hidden slices\n\t\tif (!series.isDirty && series.options.ignoreHiddenPoint) {\n\t\t\tseries.isDirty = true;\n\t\t\tchart.redraw();\n\t\t}\n\t},\n\n\t/**\n\t * Set or toggle whether the slice is cut out from the pie\n\t * @param {Boolean} sliced When undefined, the slice state is toggled\n\t * @param {Boolean} redraw Whether to redraw the chart. True by default.\n\t */\n\tslice: function (sliced, redraw, animation) {\n\t\tvar point = this,\n\t\t\tseries = point.series,\n\t\t\tchart = series.chart,\n\t\t\ttranslation;\n\n\t\tsetAnimation(animation, chart);\n\n\t\t// redraw is true by default\n\t\tredraw = pick(redraw, true);\n\n\t\t// if called without an argument, toggle\n\t\tpoint.sliced = point.options.sliced = sliced = defined(sliced) ? sliced : !point.sliced;\n\t\tseries.options.data[inArray(point, series.data)] = point.options; // update userOptions.data\n\n\t\ttranslation = sliced ? point.slicedTranslation : {\n\t\t\ttranslateX: 0,\n\t\t\ttranslateY: 0\n\t\t};\n\n\t\tpoint.graphic.animate(translation);\n\t\t\n\t\tif (point.shadowGroup) {\n\t\t\tpoint.shadowGroup.animate(translation);\n\t\t}\n\n\t}\n});\n\n/**\n * The Pie series class\n */\nvar PieSeries = {\n\ttype: 'pie',\n\tisCartesian: false,\n\tpointClass: PiePoint,\n\trequireSorting: false,\n\tnoSharedTooltip: true,\n\ttrackerGroups: ['group', 'dataLabelsGroup'],\n\tpointAttrToOptions: { // mapping between SVG attributes and the corresponding options\n\t\tstroke: 'borderColor',\n\t\t'stroke-width': 'borderWidth',\n\t\tfill: 'color'\n\t},\n\n\t/**\n\t * Pies have one color each point\n\t */\n\tgetColor: noop,\n\n\t/**\n\t * Animate the pies in\n\t */\n\tanimate: function (init) {\n\t\tvar series = this,\n\t\t\tpoints = series.points,\n\t\t\tstartAngleRad = series.startAngleRad;\n\n\t\tif (!init) {\n\t\t\teach(points, function (point) {\n\t\t\t\tvar graphic = point.graphic,\n\t\t\t\t\targs = point.shapeArgs;\n\n\t\t\t\tif (graphic) {\n\t\t\t\t\t// start values\n\t\t\t\t\tgraphic.attr({\n\t\t\t\t\t\tr: series.center[3] / 2, // animate from inner radius (#779)\n\t\t\t\t\t\tstart: startAngleRad,\n\t\t\t\t\t\tend: startAngleRad\n\t\t\t\t\t});\n\n\t\t\t\t\t// animate\n\t\t\t\t\tgraphic.animate({\n\t\t\t\t\t\tr: args.r,\n\t\t\t\t\t\tstart: args.start,\n\t\t\t\t\t\tend: args.end\n\t\t\t\t\t}, series.options.animation);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// delete this function to allow it only once\n\t\t\tseries.animate = null;\n\t\t}\n\t},\n\n\t/**\n\t * Extend the basic setData method by running processData and generatePoints immediately,\n\t * in order to access the points from the legend.\n\t */\n\tsetData: function (data, redraw) {\n\t\tSeries.prototype.setData.call(this, data, false);\n\t\tthis.processData();\n\t\tthis.generatePoints();\n\t\tif (pick(redraw, true)) {\n\t\t\tthis.chart.redraw();\n\t\t} \n\t},\n\n\t/**\n\t * Extend the generatePoints method by adding total and percentage properties to each point\n\t */\n\tgeneratePoints: function () {\n\t\tvar i,\n\t\t\ttotal = 0,\n\t\t\tpoints,\n\t\t\tlen,\n\t\t\tpoint,\n\t\t\tignoreHiddenPoint = this.options.ignoreHiddenPoint;\n\n\t\tSeries.prototype.generatePoints.call(this);\n\n\t\t// Populate local vars\n\t\tpoints = this.points;\n\t\tlen = points.length;\n\t\t\n\t\t// Get the total sum\n\t\tfor (i = 0; i < len; i++) {\n\t\t\tpoint = points[i];\n\t\t\ttotal += (ignoreHiddenPoint && !point.visible) ? 0 : point.y;\n\t\t}\n\t\tthis.total = total;\n\n\t\t// Set each point's properties\n\t\tfor (i = 0; i < len; i++) {\n\t\t\tpoint = points[i];\n\t\t\tpoint.percentage = total > 0 ? (point.y / total) * 100 : 0;\n\t\t\tpoint.total = total;\n\t\t}\n\t\t\n\t},\n\t\n\t/**\n\t * Get the center of the pie based on the size and center options relative to the  \n\t * plot area. Borrowed by the polar and gauge series types.\n\t */\n\tgetCenter: function () {\n\t\t\n\t\tvar options = this.options,\n\t\t\tchart = this.chart,\n\t\t\tslicingRoom = 2 * (options.slicedOffset || 0),\n\t\t\thandleSlicingRoom,\n\t\t\tplotWidth = chart.plotWidth - 2 * slicingRoom,\n\t\t\tplotHeight = chart.plotHeight - 2 * slicingRoom,\n\t\t\tcenterOption = options.center,\n\t\t\tpositions = [pick(centerOption[0], '50%'), pick(centerOption[1], '50%'), options.size || '100%', options.innerSize || 0],\n\t\t\tsmallestSize = mathMin(plotWidth, plotHeight),\n\t\t\tisPercent;\n\t\t\n\t\treturn map(positions, function (length, i) {\n\t\t\tisPercent = /%$/.test(length);\n\t\t\thandleSlicingRoom = i < 2 || (i === 2 && isPercent);\n\t\t\treturn (isPercent ?\n\t\t\t\t// i == 0: centerX, relative to width\n\t\t\t\t// i == 1: centerY, relative to height\n\t\t\t\t// i == 2: size, relative to smallestSize\n\t\t\t\t// i == 4: innerSize, relative to smallestSize\n\t\t\t\t[plotWidth, plotHeight, smallestSize, smallestSize][i] *\n\t\t\t\t\tpInt(length) / 100 :\n\t\t\t\tlength) + (handleSlicingRoom ? slicingRoom : 0);\n\t\t});\n\t},\n\t\n\t/**\n\t * Do translation for pie slices\n\t */\n\ttranslate: function (positions) {\n\t\tthis.generatePoints();\n\t\t\n\t\tvar series = this,\n\t\t\tcumulative = 0,\n\t\t\tprecision = 1000, // issue #172\n\t\t\toptions = series.options,\n\t\t\tslicedOffset = options.slicedOffset,\n\t\t\tconnectorOffset = slicedOffset + options.borderWidth,\n\t\t\tstart,\n\t\t\tend,\n\t\t\tangle,\n\t\t\tstartAngle = options.startAngle || 0,\n\t\t\tstartAngleRad = series.startAngleRad = mathPI / 180 * (startAngle - 90),\n\t\t\tendAngleRad = series.endAngleRad = mathPI / 180 * ((options.endAngle || (startAngle + 360)) - 90), // docs\n\t\t\tcirc = endAngleRad - startAngleRad, //2 * mathPI,\n\t\t\tpoints = series.points,\n\t\t\tradiusX, // the x component of the radius vector for a given point\n\t\t\tradiusY,\n\t\t\tlabelDistance = options.dataLabels.distance,\n\t\t\tignoreHiddenPoint = options.ignoreHiddenPoint,\n\t\t\ti,\n\t\t\tlen = points.length,\n\t\t\tpoint;\n\n\t\t// Get positions - either an integer or a percentage string must be given.\n\t\t// If positions are passed as a parameter, we're in a recursive loop for adjusting\n\t\t// space for data labels.\n\t\tif (!positions) {\n\t\t\tseries.center = positions = series.getCenter();\n\t\t}\n\n\t\t// utility for getting the x value from a given y, used for anticollision logic in data labels\n\t\tseries.getX = function (y, left) {\n\n\t\t\tangle = math.asin((y - positions[1]) / (positions[2] / 2 + labelDistance));\n\n\t\t\treturn positions[0] +\n\t\t\t\t(left ? -1 : 1) *\n\t\t\t\t(mathCos(angle) * (positions[2] / 2 + labelDistance));\n\t\t};\n\n\t\t// Calculate the geometry for each point\n\t\tfor (i = 0; i < len; i++) {\n\t\t\t\n\t\t\tpoint = points[i];\n\t\t\t\n\t\t\t// set start and end angle\n\t\t\tstart = startAngleRad + (cumulative * circ);\n\t\t\tif (!ignoreHiddenPoint || point.visible) {\n\t\t\t\tcumulative += point.percentage / 100;\n\t\t\t}\n\t\t\tend = startAngleRad + (cumulative * circ);\n\n\t\t\t// set the shape\n\t\t\tpoint.shapeType = 'arc';\n\t\t\tpoint.shapeArgs = {\n\t\t\t\tx: positions[0],\n\t\t\t\ty: positions[1],\n\t\t\t\tr: positions[2] / 2,\n\t\t\t\tinnerR: positions[3] / 2,\n\t\t\t\tstart: mathRound(start * precision) / precision,\n\t\t\t\tend: mathRound(end * precision) / precision\n\t\t\t};\n\n\t\t\t// center for the sliced out slice\n\t\t\tangle = (end + start) / 2;\n\t\t\tif (angle > 0.75 * circ) {\n\t\t\t\tangle -= 2 * mathPI;\n\t\t\t}\n\t\t\tpoint.slicedTranslation = {\n\t\t\t\ttranslateX: mathRound(mathCos(angle) * slicedOffset),\n\t\t\t\ttranslateY: mathRound(mathSin(angle) * slicedOffset)\n\t\t\t};\n\n\t\t\t// set the anchor point for tooltips\n\t\t\tradiusX = mathCos(angle) * positions[2] / 2;\n\t\t\tradiusY = mathSin(angle) * positions[2] / 2;\n\t\t\tpoint.tooltipPos = [\n\t\t\t\tpositions[0] + radiusX * 0.7,\n\t\t\t\tpositions[1] + radiusY * 0.7\n\t\t\t];\n\t\t\t\n\t\t\tpoint.half = angle < -mathPI / 2 || angle > mathPI / 2 ? 1 : 0;\n\t\t\tpoint.angle = angle;\n\n\t\t\t// set the anchor point for data labels\n\t\t\tconnectorOffset = mathMin(connectorOffset, labelDistance / 2); // #1678\n\t\t\tpoint.labelPos = [\n\t\t\t\tpositions[0] + radiusX + mathCos(angle) * labelDistance, // first break of connector\n\t\t\t\tpositions[1] + radiusY + mathSin(angle) * labelDistance, // a/a\n\t\t\t\tpositions[0] + radiusX + mathCos(angle) * connectorOffset, // second break, right outside pie\n\t\t\t\tpositions[1] + radiusY + mathSin(angle) * connectorOffset, // a/a\n\t\t\t\tpositions[0] + radiusX, // landing point for connector\n\t\t\t\tpositions[1] + radiusY, // a/a\n\t\t\t\tlabelDistance < 0 ? // alignment\n\t\t\t\t\t'center' :\n\t\t\t\t\tpoint.half ? 'right' : 'left', // alignment\n\t\t\t\tangle // center angle\n\t\t\t];\n\n\t\t}\n\t},\n\n\tsetTooltipPoints: noop,\n\tdrawGraph: null,\n\n\t/**\n\t * Draw the data points\n\t */\n\tdrawPoints: function () {\n\t\tvar series = this,\n\t\t\tchart = series.chart,\n\t\t\trenderer = chart.renderer,\n\t\t\tgroupTranslation,\n\t\t\t//center,\n\t\t\tgraphic,\n\t\t\t//group,\n\t\t\tshadow = series.options.shadow,\n\t\t\tshadowGroup,\n\t\t\tshapeArgs;\n\n\t\tif (shadow && !series.shadowGroup) {\n\t\t\tseries.shadowGroup = renderer.g('shadow')\n\t\t\t\t.add(series.group);\n\t\t}\n\n\t\t// draw the slices\n\t\teach(series.points, function (point) {\n\t\t\tgraphic = point.graphic;\n\t\t\tshapeArgs = point.shapeArgs;\n\t\t\tshadowGroup = point.shadowGroup;\n\n\t\t\t// put the shadow behind all points\n\t\t\tif (shadow && !shadowGroup) {\n\t\t\t\tshadowGroup = point.shadowGroup = renderer.g('shadow')\n\t\t\t\t\t.add(series.shadowGroup);\n\t\t\t}\n\n\t\t\t// if the point is sliced, use special translation, else use plot area traslation\n\t\t\tgroupTranslation = point.sliced ? point.slicedTranslation : {\n\t\t\t\ttranslateX: 0,\n\t\t\t\ttranslateY: 0\n\t\t\t};\n\n\t\t\t//group.translate(groupTranslation[0], groupTranslation[1]);\n\t\t\tif (shadowGroup) {\n\t\t\t\tshadowGroup.attr(groupTranslation);\n\t\t\t}\n\n\t\t\t// draw the slice\n\t\t\tif (graphic) {\n\t\t\t\tgraphic.animate(extend(shapeArgs, groupTranslation));\n\t\t\t} else {\n\t\t\t\tpoint.graphic = graphic = renderer.arc(shapeArgs)\n\t\t\t\t\t.setRadialReference(series.center)\n\t\t\t\t\t.attr(\n\t\t\t\t\t\tpoint.pointAttr[point.selected ? SELECT_STATE : NORMAL_STATE]\n\t\t\t\t\t)\n\t\t\t\t\t.attr({ 'stroke-linejoin': 'round' })\n\t\t\t\t\t.attr(groupTranslation)\n\t\t\t\t\t.add(series.group)\n\t\t\t\t\t.shadow(shadow, shadowGroup);\t\n\t\t\t}\n\n\t\t\t// detect point specific visibility\n\t\t\tif (point.visible === false) {\n\t\t\t\tpoint.setVisible(false);\n\t\t\t}\n\n\t\t});\n\n\t},\n\n\t/**\n\t * Utility for sorting data labels\n\t */\n\tsortByAngle: function (points, sign) {\n\t\tpoints.sort(function (a, b) {\n\t\t\treturn a.angle !== undefined && (b.angle - a.angle) * sign;\n\t\t});\n\t},\n\n\t/**\n\t * Override the base drawDataLabels method by pie specific functionality\n\t */\n\tdrawDataLabels: function () {\n\t\tvar series = this,\n\t\t\tdata = series.data,\n\t\t\tpoint,\n\t\t\tchart = series.chart,\n\t\t\toptions = series.options.dataLabels,\n\t\t\tconnectorPadding = pick(options.connectorPadding, 10),\n\t\t\tconnectorWidth = pick(options.connectorWidth, 1),\n\t\t\tplotWidth = chart.plotWidth,\n\t\t\tplotHeight = chart.plotHeight,\n\t\t\tconnector,\n\t\t\tconnectorPath,\n\t\t\tsoftConnector = pick(options.softConnector, true),\n\t\t\tdistanceOption = options.distance,\n\t\t\tseriesCenter = series.center,\n\t\t\tradius = seriesCenter[2] / 2,\n\t\t\tcenterY = seriesCenter[1],\n\t\t\toutside = distanceOption > 0,\n\t\t\tdataLabel,\n\t\t\tdataLabelWidth,\n\t\t\tlabelPos,\n\t\t\tlabelHeight,\n\t\t\thalves = [// divide the points into right and left halves for anti collision\n\t\t\t\t[], // right\n\t\t\t\t[]  // left\n\t\t\t],\n\t\t\tx,\n\t\t\ty,\n\t\t\tvisibility,\n\t\t\trankArr,\n\t\t\ti,\n\t\t\tj,\n\t\t\toverflow = [0, 0, 0, 0], // top, right, bottom, left\n\t\t\tsort = function (a, b) {\n\t\t\t\treturn b.y - a.y;\n\t\t\t};\n\n\t\t// get out if not enabled\n\t\tif (!series.visible || (!options.enabled && !series._hasPointLabels)) {\n\t\t\treturn;\n\t\t}\n\n\t\t// run parent method\n\t\tSeries.prototype.drawDataLabels.apply(series);\n\n\t\t// arrange points for detection collision\n\t\teach(data, function (point) {\n\t\t\tif (point.dataLabel) { // it may have been cancelled in the base method (#407)\n\t\t\t\thalves[point.half].push(point);\n\t\t\t}\n\t\t});\n\n\t\t// assume equal label heights\n\t\ti = 0;\n\t\twhile (!labelHeight && data[i]) { // #1569\n\t\t\tlabelHeight = data[i] && data[i].dataLabel && (data[i].dataLabel.getBBox().height || 21); // 21 is for #968\n\t\t\ti++;\n\t\t}\n\n\t\t/* Loop over the points in each half, starting from the top and bottom\n\t\t * of the pie to detect overlapping labels.\n\t\t */\n\t\ti = 2;\n\t\twhile (i--) {\n\n\t\t\tvar slots = [],\n\t\t\t\tslotsLength,\n\t\t\t\tusedSlots = [],\n\t\t\t\tpoints = halves[i],\n\t\t\t\tpos,\n\t\t\t\tlength = points.length,\n\t\t\t\tslotIndex;\n\t\t\t\t\n\t\t\t// Sort by angle\n\t\t\tseries.sortByAngle(points, i - 0.5);\n\n\t\t\t// Only do anti-collision when we are outside the pie and have connectors (#856)\n\t\t\tif (distanceOption > 0) {\n\t\t\t\t\n\t\t\t\t// build the slots\n\t\t\t\tfor (pos = centerY - radius - distanceOption; pos <= centerY + radius + distanceOption; pos += labelHeight) {\n\t\t\t\t\tslots.push(pos);\n\t\t\t\t\t\n\t\t\t\t\t// visualize the slot\n\t\t\t\t\t/*\n\t\t\t\t\tvar slotX = series.getX(pos, i) + chart.plotLeft - (i ? 100 : 0),\n\t\t\t\t\t\tslotY = pos + chart.plotTop;\n\t\t\t\t\tif (!isNaN(slotX)) {\n\t\t\t\t\t\tchart.renderer.rect(slotX, slotY - 7, 100, labelHeight, 1)\n\t\t\t\t\t\t\t.attr({\n\t\t\t\t\t\t\t\t'stroke-width': 1,\n\t\t\t\t\t\t\t\tstroke: 'silver'\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.add();\n\t\t\t\t\t\tchart.renderer.text('Slot '+ (slots.length - 1), slotX, slotY + 4)\n\t\t\t\t\t\t\t.attr({\n\t\t\t\t\t\t\t\tfill: 'silver'\n\t\t\t\t\t\t\t}).add();\n\t\t\t\t\t}\n\t\t\t\t\t*/\n\t\t\t\t}\n\t\t\t\tslotsLength = slots.length;\n\t\n\t\t\t\t// if there are more values than available slots, remove lowest values\n\t\t\t\tif (length > slotsLength) {\n\t\t\t\t\t// create an array for sorting and ranking the points within each quarter\n\t\t\t\t\trankArr = [].concat(points);\n\t\t\t\t\trankArr.sort(sort);\n\t\t\t\t\tj = length;\n\t\t\t\t\twhile (j--) {\n\t\t\t\t\t\trankArr[j].rank = j;\n\t\t\t\t\t}\n\t\t\t\t\tj = length;\n\t\t\t\t\twhile (j--) {\n\t\t\t\t\t\tif (points[j].rank >= slotsLength) {\n\t\t\t\t\t\t\tpoints.splice(j, 1);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tlength = points.length;\n\t\t\t\t}\n\t\n\t\t\t\t// The label goes to the nearest open slot, but not closer to the edge than\n\t\t\t\t// the label's index.\n\t\t\t\tfor (j = 0; j < length; j++) {\n\t\n\t\t\t\t\tpoint = points[j];\n\t\t\t\t\tlabelPos = point.labelPos;\n\t\n\t\t\t\t\tvar closest = 9999,\n\t\t\t\t\t\tdistance,\n\t\t\t\t\t\tslotI;\n\t\n\t\t\t\t\t// find the closest slot index\n\t\t\t\t\tfor (slotI = 0; slotI < slotsLength; slotI++) {\n\t\t\t\t\t\tdistance = mathAbs(slots[slotI] - labelPos[1]);\n\t\t\t\t\t\tif (distance < closest) {\n\t\t\t\t\t\t\tclosest = distance;\n\t\t\t\t\t\t\tslotIndex = slotI;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\n\t\t\t\t\t// if that slot index is closer to the edges of the slots, move it\n\t\t\t\t\t// to the closest appropriate slot\n\t\t\t\t\tif (slotIndex < j && slots[j] !== null) { // cluster at the top\n\t\t\t\t\t\tslotIndex = j;\n\t\t\t\t\t} else if (slotsLength  < length - j + slotIndex && slots[j] !== null) { // cluster at the bottom\n\t\t\t\t\t\tslotIndex = slotsLength - length + j;\n\t\t\t\t\t\twhile (slots[slotIndex] === null) { // make sure it is not taken\n\t\t\t\t\t\t\tslotIndex++;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Slot is taken, find next free slot below. In the next run, the next slice will find the\n\t\t\t\t\t\t// slot above these, because it is the closest one\n\t\t\t\t\t\twhile (slots[slotIndex] === null) { // make sure it is not taken\n\t\t\t\t\t\t\tslotIndex++;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\n\t\t\t\t\tusedSlots.push({ i: slotIndex, y: slots[slotIndex] });\n\t\t\t\t\tslots[slotIndex] = null; // mark as taken\n\t\t\t\t}\n\t\t\t\t// sort them in order to fill in from the top\n\t\t\t\tusedSlots.sort(sort);\n\t\t\t}\n\n\t\t\t// now the used slots are sorted, fill them up sequentially\n\t\t\tfor (j = 0; j < length; j++) {\n\t\t\t\t\n\t\t\t\tvar slot, naturalY;\n\n\t\t\t\tpoint = points[j];\n\t\t\t\tlabelPos = point.labelPos;\n\t\t\t\tdataLabel = point.dataLabel;\n\t\t\t\tvisibility = point.visible === false ? HIDDEN : VISIBLE;\n\t\t\t\tnaturalY = labelPos[1];\n\t\t\t\t\n\t\t\t\tif (distanceOption > 0) {\n\t\t\t\t\tslot = usedSlots.pop();\n\t\t\t\t\tslotIndex = slot.i;\n\n\t\t\t\t\t// if the slot next to currrent slot is free, the y value is allowed\n\t\t\t\t\t// to fall back to the natural position\n\t\t\t\t\ty = slot.y;\n\t\t\t\t\tif ((naturalY > y && slots[slotIndex + 1] !== null) ||\n\t\t\t\t\t\t\t(naturalY < y &&  slots[slotIndex - 1] !== null)) {\n\t\t\t\t\t\ty = naturalY;\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t} else {\n\t\t\t\t\ty = naturalY;\n\t\t\t\t}\n\n\t\t\t\t// get the x - use the natural x position for first and last slot, to prevent the top\n\t\t\t\t// and botton slice connectors from touching each other on either side\n\t\t\t\tx = options.justify ? \n\t\t\t\t\tseriesCenter[0] + (i ? -1 : 1) * (radius + distanceOption) :\n\t\t\t\t\tseries.getX(slotIndex === 0 || slotIndex === slots.length - 1 ? naturalY : y, i);\n\t\t\t\t\n\t\t\t\n\t\t\t\t// Record the placement and visibility\n\t\t\t\tdataLabel._attr = {\n\t\t\t\t\tvisibility: visibility,\n\t\t\t\t\talign: labelPos[6]\n\t\t\t\t};\n\t\t\t\tdataLabel._pos = {\n\t\t\t\t\tx: x + options.x +\n\t\t\t\t\t\t({ left: connectorPadding, right: -connectorPadding }[labelPos[6]] || 0),\n\t\t\t\t\ty: y + options.y - 10 // 10 is for the baseline (label vs text)\n\t\t\t\t};\n\t\t\t\tdataLabel.connX = x;\n\t\t\t\tdataLabel.connY = y;\n\t\t\t\t\n\t\t\t\t\t\t\n\t\t\t\t// Detect overflowing data labels\n\t\t\t\tif (this.options.size === null) {\n\t\t\t\t\tdataLabelWidth = dataLabel.width;\n\t\t\t\t\t// Overflow left\n\t\t\t\t\tif (x - dataLabelWidth < connectorPadding) {\n\t\t\t\t\t\toverflow[3] = mathMax(mathRound(dataLabelWidth - x + connectorPadding), overflow[3]);\n\t\t\t\t\t\t\n\t\t\t\t\t// Overflow right\n\t\t\t\t\t} else if (x + dataLabelWidth > plotWidth - connectorPadding) {\n\t\t\t\t\t\toverflow[1] = mathMax(mathRound(x + dataLabelWidth - plotWidth + connectorPadding), overflow[1]);\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\t// Overflow top\n\t\t\t\t\tif (y - labelHeight / 2 < 0) {\n\t\t\t\t\t\toverflow[0] = mathMax(mathRound(-y + labelHeight / 2), overflow[0]);\n\t\t\t\t\t\t\n\t\t\t\t\t// Overflow left\n\t\t\t\t\t} else if (y + labelHeight / 2 > plotHeight) {\n\t\t\t\t\t\toverflow[2] = mathMax(mathRound(y + labelHeight / 2 - plotHeight), overflow[2]);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} // for each point\n\t\t} // for each half\n\t\t\n\t\t// Do not apply the final placement and draw the connectors until we have verified\n\t\t// that labels are not spilling over. \n\t\tif (arrayMax(overflow) === 0 || this.verifyDataLabelOverflow(overflow)) {\n\t\t\t\n\t\t\t// Place the labels in the final position\n\t\t\tthis.placeDataLabels();\n\t\t\t\n\t\t\t// Draw the connectors\n\t\t\tif (outside && connectorWidth) {\n\t\t\t\teach(this.points, function (point) {\n\t\t\t\t\tconnector = point.connector;\n\t\t\t\t\tlabelPos = point.labelPos;\n\t\t\t\t\tdataLabel = point.dataLabel;\n\t\t\t\t\t\n\t\t\t\t\tif (dataLabel && dataLabel._pos) {\n\t\t\t\t\t\tvisibility = dataLabel._attr.visibility;\n\t\t\t\t\t\tx = dataLabel.connX;\n\t\t\t\t\t\ty = dataLabel.connY;\n\t\t\t\t\t\tconnectorPath = softConnector ? [\n\t\t\t\t\t\t\tM,\n\t\t\t\t\t\t\tx + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label\n\t\t\t\t\t\t\t'C',\n\t\t\t\t\t\t\tx, y, // first break, next to the label\n\t\t\t\t\t\t\t2 * labelPos[2] - labelPos[4], 2 * labelPos[3] - labelPos[5],\n\t\t\t\t\t\t\tlabelPos[2], labelPos[3], // second break\n\t\t\t\t\t\t\tL,\n\t\t\t\t\t\t\tlabelPos[4], labelPos[5] // base\n\t\t\t\t\t\t] : [\n\t\t\t\t\t\t\tM,\n\t\t\t\t\t\t\tx + (labelPos[6] === 'left' ? 5 : -5), y, // end of the string at the label\n\t\t\t\t\t\t\tL,\n\t\t\t\t\t\t\tlabelPos[2], labelPos[3], // second break\n\t\t\t\t\t\t\tL,\n\t\t\t\t\t\t\tlabelPos[4], labelPos[5] // base\n\t\t\t\t\t\t];\n\t\t\n\t\t\t\t\t\tif (connector) {\n\t\t\t\t\t\t\tconnector.animate({ d: connectorPath });\n\t\t\t\t\t\t\tconnector.attr('visibility', visibility);\n\t\t\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tpoint.connector = connector = series.chart.renderer.path(connectorPath).attr({\n\t\t\t\t\t\t\t\t'stroke-width': connectorWidth,\n\t\t\t\t\t\t\t\tstroke: options.connectorColor || point.color || '#606060',\n\t\t\t\t\t\t\t\tvisibility: visibility\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.add(series.group);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else if (connector) {\n\t\t\t\t\t\tpoint.connector = connector.destroy();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\t\t\t\n\t\t}\n\t},\n\t\n\t/**\n\t * Verify whether the data labels are allowed to draw, or we should run more translation and data\n\t * label positioning to keep them inside the plot area. Returns true when data labels are ready \n\t * to draw.\n\t */\n\tverifyDataLabelOverflow: function (overflow) {\n\t\t\n\t\tvar center = this.center,\n\t\t\toptions = this.options,\n\t\t\tcenterOption = options.center,\n\t\t\tminSize = options.minSize || 80,\n\t\t\tnewSize = minSize,\n\t\t\tret;\n\t\t\t\n\t\t// Handle horizontal size and center\n\t\tif (centerOption[0] !== null) { // Fixed center\n\t\t\tnewSize = mathMax(center[2] - mathMax(overflow[1], overflow[3]), minSize);\n\t\t\t\n\t\t} else { // Auto center\n\t\t\tnewSize = mathMax(\n\t\t\t\tcenter[2] - overflow[1] - overflow[3], // horizontal overflow\t\t\t\t\t\n\t\t\t\tminSize\n\t\t\t);\n\t\t\tcenter[0] += (overflow[3] - overflow[1]) / 2; // horizontal center\n\t\t}\n\t\t\n\t\t// Handle vertical size and center\n\t\tif (centerOption[1] !== null) { // Fixed center\n\t\t\tnewSize = mathMax(mathMin(newSize, center[2] - mathMax(overflow[0], overflow[2])), minSize);\n\t\t\t\n\t\t} else { // Auto center\n\t\t\tnewSize = mathMax(\n\t\t\t\tmathMin(\n\t\t\t\t\tnewSize,\t\t\n\t\t\t\t\tcenter[2] - overflow[0] - overflow[2] // vertical overflow\n\t\t\t\t),\n\t\t\t\tminSize\n\t\t\t);\n\t\t\tcenter[1] += (overflow[0] - overflow[2]) / 2; // vertical center\n\t\t}\n\t\t\n\t\t// If the size must be decreased, we need to run translate and drawDataLabels again\n\t\tif (newSize < center[2]) {\n\t\t\tcenter[2] = newSize;\n\t\t\tthis.translate(center);\n\t\t\teach(this.points, function (point) {\n\t\t\t\tif (point.dataLabel) {\n\t\t\t\t\tpoint.dataLabel._pos = null; // reset\n\t\t\t\t}\n\t\t\t});\n\t\t\tthis.drawDataLabels();\n\t\t\t\n\t\t// Else, return true to indicate that the pie and its labels is within the plot area\n\t\t} else {\n\t\t\tret = true;\n\t\t}\n\t\treturn ret;\n\t},\n\t\n\t/**\n\t * Perform the final placement of the data labels after we have verified that they\n\t * fall within the plot area.\n\t */\n\tplaceDataLabels: function () {\n\t\teach(this.points, function (point) {\n\t\t\tvar dataLabel = point.dataLabel,\n\t\t\t\t_pos;\n\t\t\t\n\t\t\tif (dataLabel) {\n\t\t\t\t_pos = dataLabel._pos;\n\t\t\t\tif (_pos) {\n\t\t\t\t\tdataLabel.attr(dataLabel._attr);\t\t\t\n\t\t\t\t\tdataLabel[dataLabel.moved ? 'animate' : 'attr'](_pos);\n\t\t\t\t\tdataLabel.moved = true;\n\t\t\t\t} else if (dataLabel) {\n\t\t\t\t\tdataLabel.attr({ y: -999 });\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t},\n\t\n\talignDataLabel: noop,\n\n\t/**\n\t * Draw point specific tracker objects. Inherit directly from column series.\n\t */\n\tdrawTracker: ColumnSeries.prototype.drawTracker,\n\n\t/**\n\t * Use a simple symbol from column prototype\n\t */\n\tdrawLegendSymbol: AreaSeries.prototype.drawLegendSymbol,\n\n\t/**\n\t * Pies don't have point marker symbols\n\t */\n\tgetSymbol: noop\n\n};\nPieSeries = extendClass(Series, PieSeries);\nseriesTypes.pie = PieSeries;\n\n\n// global variables\nextend(Highcharts, {\n\t\n\t// Constructors\n\tAxis: Axis,\n\tChart: Chart,\n\tColor: Color,\n\tLegend: Legend,\n\tPointer: Pointer,\n\tPoint: Point,\n\tTick: Tick,\n\tTooltip: Tooltip,\n\tRenderer: Renderer,\n\tSeries: Series,\n\tSVGElement: SVGElement,\n\tSVGRenderer: SVGRenderer,\n\t\n\t// Various\n\tarrayMin: arrayMin,\n\tarrayMax: arrayMax,\n\tcharts: charts,\n\tdateFormat: dateFormat,\n\tformat: format,\n\tpathAnim: pathAnim,\n\tgetOptions: getOptions,\n\thasBidiBug: hasBidiBug,\n\tisTouchDevice: isTouchDevice,\n\tnumberFormat: numberFormat,\n\tseriesTypes: seriesTypes,\n\tsetOptions: setOptions,\n\taddEvent: addEvent,\n\tremoveEvent: removeEvent,\n\tcreateElement: createElement,\n\tdiscardElement: discardElement,\n\tcss: css,\n\teach: each,\n\textend: extend,\n\tmap: map,\n\tmerge: merge,\n\tpick: pick,\n\tsplat: splat,\n\textendClass: extendClass,\n\tpInt: pInt,\n\twrap: wrap,\n\tsvg: hasSVG,\n\tcanvas: useCanVG,\n\tvml: !hasSVG && !useCanVG,\n\tproduct: PRODUCT,\n\tversion: VERSION\n});\n}());\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/highcharts/modules/annotations.js",
    "content": "(function(i,C){function m(a){return typeof a===\"number\"}function n(a){return a!==D&&a!==null}var D,p,r,s=i.Chart,t=i.extend,z=i.each;r=[\"path\",\"rect\",\"circle\"];p={top:0,left:0,center:0.5,middle:0.5,bottom:1,right:1};var u=C.inArray,A=i.merge,B=function(){this.init.apply(this,arguments)};B.prototype={init:function(a,d){var c=d.shape&&d.shape.type;this.chart=a;var b,f;f={xAxis:0,yAxis:0,title:{style:{},text:\"\",x:0,y:0},shape:{params:{stroke:\"#000000\",fill:\"transparent\",strokeWidth:2}}};b={circle:{params:{x:0,\ny:0}}};if(b[c])f.shape=A(f.shape,b[c]);this.options=A({},f,d)},render:function(a){var d=this.chart,c=this.chart.renderer,b=this.group,f=this.title,e=this.shape,h=this.options,i=h.title,l=h.shape;if(!b)b=this.group=c.g();if(!e&&l&&u(l.type,r)!==-1)e=this.shape=c[h.shape.type](l.params),e.add(b);if(!f&&i)f=this.title=c.label(i),f.add(b);b.add(d.annotations.group);this.linkObjects();a!==!1&&this.redraw()},redraw:function(){var a=this.options,d=this.chart,c=this.group,b=this.title,f=this.shape,e=this.linkedObject,\nh=d.xAxis[a.xAxis],v=d.yAxis[a.yAxis],l=a.width,w=a.height,x=p[a.anchorY],y=p[a.anchorX],j,o,g,q;if(e)j=e instanceof i.Point?\"point\":e instanceof i.Series?\"series\":null,j===\"point\"?(a.xValue=e.x,a.yValue=e.y,o=e.series):j===\"series\"&&(o=e),c.visibility!==o.group.visibility&&c.attr({visibility:o.group.visibility});e=n(a.xValue)?h.toPixels(a.xValue+h.minPointOffset)-h.minPixelPadding:a.x;j=n(a.yValue)?v.toPixels(a.yValue):a.y;if(!isNaN(e)&&!isNaN(j)&&m(e)&&m(j)){b&&(b.attr(a.title),b.css(a.title.style));\nif(f){b=t({},a.shape.params);if(a.units===\"values\"){for(g in b)u(g,[\"width\",\"x\"])>-1?b[g]=h.translate(b[g]):u(g,[\"height\",\"y\"])>-1&&(b[g]=v.translate(b[g]));b.width&&(b.width-=h.toPixels(0)-h.left);b.x&&(b.x+=h.minPixelPadding);if(a.shape.type===\"path\"){g=b.d;o=e;for(var r=j,s=g.length,k=0;k<s;)typeof g[k]===\"number\"&&typeof g[k+1]===\"number\"?(g[k]=h.toPixels(g[k])-o,g[k+1]=v.toPixels(g[k+1])-r,k+=2):k+=1}}a.shape.type===\"circle\"&&(b.x+=b.r,b.y+=b.r);f.attr(b)}c.bBox=null;if(!m(l))q=c.getBBox(),l=\nq.width;if(!m(w))q||(q=c.getBBox()),w=q.height;if(!m(y))y=p.center;if(!m(x))x=p.center;e-=l*y;j-=w*x;d.animation&&n(c.translateX)&&n(c.translateY)?c.animate({translateX:e,translateY:j}):c.translate(e,j)}},destroy:function(){var a=this,d=this.chart.annotations.allItems,c=d.indexOf(a);c>-1&&d.splice(c,1);z([\"title\",\"shape\",\"group\"],function(b){a[b]&&(a[b].destroy(),a[b]=null)});a.group=a.title=a.shape=a.chart=a.options=null},update:function(a,d){t(this.options,a);this.linkObjects();this.render(d)},\nlinkObjects:function(){var a=this.chart,d=this.linkedObject,c=d&&(d.id||d.options.id),b=this.options.linkedTo;if(n(b)){if(!n(d)||b!==c)this.linkedObject=a.get(b)}else this.linkedObject=null}};t(s.prototype,{annotations:{add:function(a,d){var c=this.allItems,b=this.chart,f,e;Object.prototype.toString.call(a)===\"[object Array]\"||(a=[a]);for(e=a.length;e--;)f=new B(b,a[e]),c.push(f),f.render(d)},redraw:function(){z(this.allItems,function(a){a.redraw()})}}});s.prototype.callbacks.push(function(a){var d=\na.options.annotations,c;c=a.renderer.g(\"annotations\");c.attr({zIndex:7});c.add();a.annotations.allItems=[];a.annotations.chart=a;a.annotations.group=c;Object.prototype.toString.call(d)===\"[object Array]\"&&d.length>0&&a.annotations.add(a.options.annotations);i.addEvent(a,\"redraw\",function(){a.annotations.redraw()})})})(Highcharts,HighchartsAdapter);\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/highcharts/modules/annotations.src.js",
    "content": "(function (Highcharts, HighchartsAdapter) {\n\nvar UNDEFINED,\n\tALIGN_FACTOR,\n\tALLOWED_SHAPES,\n\tChart = Highcharts.Chart,\n\textend = Highcharts.extend,\n\teach = Highcharts.each;\n\nALLOWED_SHAPES = [\"path\", \"rect\", \"circle\"];\n\nALIGN_FACTOR = {\n\ttop: 0,\n\tleft: 0,\n\tcenter: 0.5,\n\tmiddle: 0.5,\n\tbottom: 1,\n\tright: 1\n};\n\n\n// Highcharts helper methods\nvar inArray = HighchartsAdapter.inArray,\n\tmerge = Highcharts.merge;\n\nfunction defaultOptions(shapeType) {\n\tvar shapeOptions,\n\t\toptions;\n\n\toptions = {\n\t\txAxis: 0,\n\t\tyAxis: 0,\n\t\ttitle: {\n\t\t\tstyle: {},\n\t\t\ttext: \"\",\n\t\t\tx: 0,\n\t\t\ty: 0\n\t\t},\n\t\tshape: {\n\t\t\tparams: {\n\t\t\t\tstroke: \"#000000\",\n\t\t\t\tfill: \"transparent\",\n\t\t\t\tstrokeWidth: 2\n\t\t\t}\n\t\t}\n\t};\n\n\tshapeOptions = {\n\t\tcircle: {\n\t\t\tparams: {\n\t\t\t\tx: 0,\n\t\t\t\ty: 0\n\t\t\t}\n\t\t}\n\t};\n\n\tif (shapeOptions[shapeType]) {\n\t\toptions.shape = merge(options.shape, shapeOptions[shapeType]);\n\t}\n\n\treturn options;\n}\n\nfunction isArray(obj) {\n\treturn Object.prototype.toString.call(obj) === '[object Array]';\n}\n\nfunction isNumber(n) {\n\treturn typeof n === 'number';\n}\n\nfunction defined(obj) {\n\treturn obj !== UNDEFINED && obj !== null;\n}\n\nfunction translatePath(d, xAxis, yAxis, xOffset, yOffset) {\n\tvar len = d.length,\n\t\ti = 0;\n\n\twhile (i < len) {\n\t\tif (typeof d[i] === 'number' && typeof d[i + 1] === 'number') {\n\t\t\td[i] = xAxis.toPixels(d[i]) - xOffset;\n\t\t\td[i + 1] = yAxis.toPixels(d[i + 1]) - yOffset;\n\t\t\ti += 2;\n\t\t} else {\n\t\t\ti += 1;\n\t\t}\n\t}\n\n\treturn d;\n}\n\n\n// Define annotation prototype\nvar Annotation = function () {\n\tthis.init.apply(this, arguments);\n};\nAnnotation.prototype = {\n\t/* \n\t * Initialize the annotation\n\t */\n\tinit: function (chart, options) {\n\t\tvar shapeType = options.shape && options.shape.type;\n\n\t\tthis.chart = chart;\n\t\tthis.options = merge({}, defaultOptions(shapeType), options);\n\t},\n\n\t/*\n\t * Render the annotation\n\t */\n\trender: function (redraw) {\n\t\tvar annotation = this,\n\t\t\tchart = this.chart,\n\t\t\trenderer = annotation.chart.renderer,\n\t\t\tgroup = annotation.group,\n\t\t\ttitle = annotation.title,\n\t\t\tshape = annotation.shape,\n\t\t\toptions = annotation.options,\n\t\t\ttitleOptions = options.title,\n\t\t\tshapeOptions = options.shape;\n\n\t\tif (!group) {\n\t\t\tgroup = annotation.group = renderer.g();\n\t\t}\n\n\n\t\tif (!shape && shapeOptions && inArray(shapeOptions.type, ALLOWED_SHAPES) !== -1) {\n\t\t\tshape = annotation.shape = renderer[options.shape.type](shapeOptions.params);\n\t\t\tshape.add(group);\n\t\t}\n\n\t\tif (!title && titleOptions) {\n\t\t\ttitle = annotation.title = renderer.label(titleOptions);\n\t\t\ttitle.add(group);\n\t\t}\n\n\t\tgroup.add(chart.annotations.group);\n\n\t\t// link annotations to point or series\n\t\tannotation.linkObjects();\n\n\t\tif (redraw !== false) {\n\t\t\tannotation.redraw();\n\t\t}\n\t},\n\n\t/*\n\t * Redraw the annotation title or shape after options update\n\t */\n\tredraw: function () {\n\t\tvar options = this.options,\n\t\t\tchart = this.chart,\n\t\t\tgroup = this.group,\n\t\t\ttitle = this.title,\n\t\t\tshape = this.shape,\n\t\t\tlinkedTo = this.linkedObject,\n\t\t\txAxis = chart.xAxis[options.xAxis],\n\t\t\tyAxis = chart.yAxis[options.yAxis],\n\t\t\twidth = options.width,\n\t\t\theight = options.height,\n\t\t\tanchorY = ALIGN_FACTOR[options.anchorY],\n\t\t\tanchorX = ALIGN_FACTOR[options.anchorX],\n\t\t\tresetBBox = false,\n\t\t\tshapeParams,\n\t\t\tlinkType,\n\t\t\tseries,\n\t\t\tparam,\n\t\t\tbbox,\n\t\t\tx,\n\t\t\ty;\n\n\t\tif (linkedTo) {\n\t\t\tlinkType = (linkedTo instanceof Highcharts.Point) ? 'point' :\n\t\t\t\t\t\t(linkedTo instanceof Highcharts.Series) ? 'series' : null;\n\n\t\t\tif (linkType === 'point') {\n\t\t\t\toptions.xValue = linkedTo.x;\n\t\t\t\toptions.yValue = linkedTo.y;\n\t\t\t\tseries = linkedTo.series;\n\t\t\t} else if (linkType === 'series') {\n\t\t\t\tseries = linkedTo;\n\t\t\t}\n\n\t\t\tif (group.visibility !== series.group.visibility) {\n\t\t\t\tgroup.attr({\n\t\t\t\t\tvisibility: series.group.visibility\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\n\t\t// Based on given options find annotation pixel position\n\t\tx = (defined(options.xValue) ? xAxis.toPixels(options.xValue + xAxis.minPointOffset) - xAxis.minPixelPadding : options.x);\n\t\ty = defined(options.yValue) ? yAxis.toPixels(options.yValue) : options.y;\n\n\t\tif (isNaN(x) || isNaN(y) || !isNumber(x) || !isNumber(y)) {\n\t\t\treturn;\n\t\t}\n\n\n\t\tif (title) {\n\t\t\ttitle.attr(options.title);\n\t\t\ttitle.css(options.title.style);\n\t\t\tresetBBox = true;\n\t\t}\n\n\t\tif (shape) {\n\t\t\tshapeParams = extend({}, options.shape.params);\n\n\t\t\tif (options.units === 'values') {\n\t\t\t\tfor (param in shapeParams) {\n\t\t\t\t\tif (inArray(param, ['width', 'x']) > -1) {\n\t\t\t\t\t\tshapeParams[param] = xAxis.translate(shapeParams[param]);\n\t\t\t\t\t} else if (inArray(param, ['height', 'y']) > -1) {\n\t\t\t\t\t\tshapeParams[param] = yAxis.translate(shapeParams[param]);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (shapeParams.width) {\n\t\t\t\t\tshapeParams.width -= xAxis.toPixels(0) - xAxis.left;\n\t\t\t\t}\n\n\t\t\t\tif (shapeParams.x) {\n\t\t\t\t\tshapeParams.x += xAxis.minPixelPadding;\n\t\t\t\t}\n\n\t\t\t\tif (options.shape.type === 'path') {\n\t\t\t\t\ttranslatePath(shapeParams.d, xAxis, yAxis, x, y);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// move the center of the circle to shape x/y\n\t\t\tif (options.shape.type === 'circle') {\n\t\t\t\tshapeParams.x += shapeParams.r;\n\t\t\t\tshapeParams.y += shapeParams.r;\n\t\t\t}\n\n\t\t\tresetBBox = true;\n\t\t\tshape.attr(shapeParams);\n\t\t}\n\n\t\tgroup.bBox = null;\n\n\t\t// If annotation width or height is not defined in options use bounding box size\n\t\tif (!isNumber(width)) {\n\t\t\tbbox = group.getBBox();\n\t\t\twidth = bbox.width;\n\t\t}\n\n\t\tif (!isNumber(height)) {\n\t\t\t// get bbox only if it wasn't set before\n\t\t\tif (!bbox) {\n\t\t\t\tbbox = group.getBBox();\n\t\t\t}\n\n\t\t\theight = bbox.height;\n\t\t}\n\n\t\t// Calculate anchor point\n\t\tif (!isNumber(anchorX)) {\n\t\t\tanchorX = ALIGN_FACTOR.center;\n\t\t}\n\n\t\tif (!isNumber(anchorY)) {\n\t\t\tanchorY = ALIGN_FACTOR.center;\n\t\t}\n\n\t\t// Translate group according to its dimension and anchor point\n\t\tx = x - width * anchorX;\n\t\ty = y - height * anchorY;\n\n\t\tif (chart.animation && defined(group.translateX) && defined(group.translateY)) {\n\t\t\tgroup.animate({\n\t\t\t\ttranslateX: x,\n\t\t\t\ttranslateY: y\n\t\t\t});\n\t\t} else {\n\t\t\tgroup.translate(x, y);\n\t\t}\n\t},\n\n\t/*\n\t * Destroy the annotation\n\t */\n\tdestroy: function () {\n\t\tvar annotation = this,\n\t\t\tchart = this.chart,\n\t\t\tallItems = chart.annotations.allItems,\n\t\t\tindex = allItems.indexOf(annotation);\n\n\t\tif (index > -1) {\n\t\t\tallItems.splice(index, 1);\n\t\t}\n\n\t\teach(['title', 'shape', 'group'], function (element) {\n\t\t\tif (annotation[element]) {\n\t\t\t\tannotation[element].destroy();\n\t\t\t\tannotation[element] = null;\n\t\t\t}\n\t\t});\n\n\t\tannotation.group = annotation.title = annotation.shape = annotation.chart = annotation.options = null;\n\t},\n\n\t/*\n\t * Update the annotation with a given options\n\t */\n\tupdate: function (options, redraw) {\n\t\textend(this.options, options);\n\n\t\t// update link to point or series\n\t\tthis.linkObjects();\n\n\t\tthis.render(redraw);\n\t},\n\n\tlinkObjects: function () {\n\t\tvar annotation = this,\n\t\t\tchart = annotation.chart,\n\t\t\tlinkedTo = annotation.linkedObject,\n\t\t\tlinkedId = linkedTo && (linkedTo.id || linkedTo.options.id),\n\t\t\toptions = annotation.options,\n\t\t\tid = options.linkedTo;\n\n\t\tif (!defined(id)) {\n\t\t\tannotation.linkedObject = null;\n\t\t} else if (!defined(linkedTo) || id !== linkedId) {\n\t\t\tannotation.linkedObject = chart.get(id);\n\t\t}\n\t}\n};\n\n\n// Add annotations methods to chart prototype\nextend(Chart.prototype, {\n\tannotations: {\n\t\t/*\n\t\t * Unified method for adding annotations to the chart\n\t\t */\n\t\tadd: function (options, redraw) {\n\t\t\tvar annotations = this.allItems,\n\t\t\t\tchart = this.chart,\n\t\t\t\titem,\n\t\t\t\tlen;\n\n\t\t\tif (!isArray(options)) {\n\t\t\t\toptions = [options];\n\t\t\t}\n\n\t\t\tlen = options.length;\n\n\t\t\twhile (len--) {\n\t\t\t\titem = new Annotation(chart, options[len]);\n\t\t\t\tannotations.push(item);\n\t\t\t\titem.render(redraw);\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Redraw all annotations, method used in chart events\n\t\t */\n\t\tredraw: function () {\n\t\t\teach(this.allItems, function (annotation) {\n\t\t\t\tannotation.redraw();\n\t\t\t});\n\t\t}\n\t}\n});\n\n\n// Initialize on chart load\nChart.prototype.callbacks.push(function (chart) {\n\tvar options = chart.options.annotations,\n\t\tgroup;\n\n\tgroup = chart.renderer.g(\"annotations\");\n\tgroup.attr({\n\t\tzIndex: 7\n\t});\n\tgroup.add();\n\n\t// initialize empty array for annotations\n\tchart.annotations.allItems = [];\n\n\t// link chart object to annotations\n\tchart.annotations.chart = chart;\n\n\t// link annotations group element to the chart\n\tchart.annotations.group = group;\n\n\tif (isArray(options) && options.length > 0) {\n\t\tchart.annotations.add(chart.options.annotations);\n\t}\n\n\t// update annotations after chart redraw\n\tHighcharts.addEvent(chart, 'redraw', function () {\n\t\tchart.annotations.redraw();\n\t});\n});\n}(Highcharts, HighchartsAdapter));\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/highcharts/modules/canvas-tools.js",
    "content": "/*\n A class to parse color values\n @author Stoyan Stefanov <sstoo@gmail.com>\n @link   http://www.phpied.com/rgb-color-parser-in-javascript/\n Use it if you like it\n\n canvg.js - Javascript SVG parser and renderer on Canvas\n MIT Licensed \n Gabe Lerner (gabelerner@gmail.com)\n http://code.google.com/p/canvg/\n\n Requires: rgbcolor.js - http://www.phpied.com/rgb-color-parser-in-javascript/\n\n Highcharts JS v3.0.6 (2013-10-04)\n CanVGRenderer Extension module\n\n (c) 2011-2012 Torstein Hønsi, Erik Olsson\n\n License: www.highcharts.com/license\n*/\nfunction RGBColor(m){this.ok=!1;m.charAt(0)==\"#\"&&(m=m.substr(1,6));var m=m.replace(/ /g,\"\"),m=m.toLowerCase(),a={aliceblue:\"f0f8ff\",antiquewhite:\"faebd7\",aqua:\"00ffff\",aquamarine:\"7fffd4\",azure:\"f0ffff\",beige:\"f5f5dc\",bisque:\"ffe4c4\",black:\"000000\",blanchedalmond:\"ffebcd\",blue:\"0000ff\",blueviolet:\"8a2be2\",brown:\"a52a2a\",burlywood:\"deb887\",cadetblue:\"5f9ea0\",chartreuse:\"7fff00\",chocolate:\"d2691e\",coral:\"ff7f50\",cornflowerblue:\"6495ed\",cornsilk:\"fff8dc\",crimson:\"dc143c\",cyan:\"00ffff\",darkblue:\"00008b\",\ndarkcyan:\"008b8b\",darkgoldenrod:\"b8860b\",darkgray:\"a9a9a9\",darkgreen:\"006400\",darkkhaki:\"bdb76b\",darkmagenta:\"8b008b\",darkolivegreen:\"556b2f\",darkorange:\"ff8c00\",darkorchid:\"9932cc\",darkred:\"8b0000\",darksalmon:\"e9967a\",darkseagreen:\"8fbc8f\",darkslateblue:\"483d8b\",darkslategray:\"2f4f4f\",darkturquoise:\"00ced1\",darkviolet:\"9400d3\",deeppink:\"ff1493\",deepskyblue:\"00bfff\",dimgray:\"696969\",dodgerblue:\"1e90ff\",feldspar:\"d19275\",firebrick:\"b22222\",floralwhite:\"fffaf0\",forestgreen:\"228b22\",fuchsia:\"ff00ff\",\ngainsboro:\"dcdcdc\",ghostwhite:\"f8f8ff\",gold:\"ffd700\",goldenrod:\"daa520\",gray:\"808080\",green:\"008000\",greenyellow:\"adff2f\",honeydew:\"f0fff0\",hotpink:\"ff69b4\",indianred:\"cd5c5c\",indigo:\"4b0082\",ivory:\"fffff0\",khaki:\"f0e68c\",lavender:\"e6e6fa\",lavenderblush:\"fff0f5\",lawngreen:\"7cfc00\",lemonchiffon:\"fffacd\",lightblue:\"add8e6\",lightcoral:\"f08080\",lightcyan:\"e0ffff\",lightgoldenrodyellow:\"fafad2\",lightgrey:\"d3d3d3\",lightgreen:\"90ee90\",lightpink:\"ffb6c1\",lightsalmon:\"ffa07a\",lightseagreen:\"20b2aa\",lightskyblue:\"87cefa\",\nlightslateblue:\"8470ff\",lightslategray:\"778899\",lightsteelblue:\"b0c4de\",lightyellow:\"ffffe0\",lime:\"00ff00\",limegreen:\"32cd32\",linen:\"faf0e6\",magenta:\"ff00ff\",maroon:\"800000\",mediumaquamarine:\"66cdaa\",mediumblue:\"0000cd\",mediumorchid:\"ba55d3\",mediumpurple:\"9370d8\",mediumseagreen:\"3cb371\",mediumslateblue:\"7b68ee\",mediumspringgreen:\"00fa9a\",mediumturquoise:\"48d1cc\",mediumvioletred:\"c71585\",midnightblue:\"191970\",mintcream:\"f5fffa\",mistyrose:\"ffe4e1\",moccasin:\"ffe4b5\",navajowhite:\"ffdead\",navy:\"000080\",\noldlace:\"fdf5e6\",olive:\"808000\",olivedrab:\"6b8e23\",orange:\"ffa500\",orangered:\"ff4500\",orchid:\"da70d6\",palegoldenrod:\"eee8aa\",palegreen:\"98fb98\",paleturquoise:\"afeeee\",palevioletred:\"d87093\",papayawhip:\"ffefd5\",peachpuff:\"ffdab9\",peru:\"cd853f\",pink:\"ffc0cb\",plum:\"dda0dd\",powderblue:\"b0e0e6\",purple:\"800080\",red:\"ff0000\",rosybrown:\"bc8f8f\",royalblue:\"4169e1\",saddlebrown:\"8b4513\",salmon:\"fa8072\",sandybrown:\"f4a460\",seagreen:\"2e8b57\",seashell:\"fff5ee\",sienna:\"a0522d\",silver:\"c0c0c0\",skyblue:\"87ceeb\",slateblue:\"6a5acd\",\nslategray:\"708090\",snow:\"fffafa\",springgreen:\"00ff7f\",steelblue:\"4682b4\",tan:\"d2b48c\",teal:\"008080\",thistle:\"d8bfd8\",tomato:\"ff6347\",turquoise:\"40e0d0\",violet:\"ee82ee\",violetred:\"d02090\",wheat:\"f5deb3\",white:\"ffffff\",whitesmoke:\"f5f5f5\",yellow:\"ffff00\",yellowgreen:\"9acd32\"},c;for(c in a)m==c&&(m=a[c]);var d=[{re:/^rgb\\((\\d{1,3}),\\s*(\\d{1,3}),\\s*(\\d{1,3})\\)$/,example:[\"rgb(123, 234, 45)\",\"rgb(255,234,245)\"],process:function(b){return[parseInt(b[1]),parseInt(b[2]),parseInt(b[3])]}},{re:/^(\\w{2})(\\w{2})(\\w{2})$/,\nexample:[\"#00ff00\",\"336699\"],process:function(b){return[parseInt(b[1],16),parseInt(b[2],16),parseInt(b[3],16)]}},{re:/^(\\w{1})(\\w{1})(\\w{1})$/,example:[\"#fb0\",\"f0f\"],process:function(b){return[parseInt(b[1]+b[1],16),parseInt(b[2]+b[2],16),parseInt(b[3]+b[3],16)]}}];for(c=0;c<d.length;c++){var b=d[c].process,k=d[c].re.exec(m);if(k)channels=b(k),this.r=channels[0],this.g=channels[1],this.b=channels[2],this.ok=!0}this.r=this.r<0||isNaN(this.r)?0:this.r>255?255:this.r;this.g=this.g<0||isNaN(this.g)?0:\nthis.g>255?255:this.g;this.b=this.b<0||isNaN(this.b)?0:this.b>255?255:this.b;this.toRGB=function(){return\"rgb(\"+this.r+\", \"+this.g+\", \"+this.b+\")\"};this.toHex=function(){var b=this.r.toString(16),a=this.g.toString(16),d=this.b.toString(16);b.length==1&&(b=\"0\"+b);a.length==1&&(a=\"0\"+a);d.length==1&&(d=\"0\"+d);return\"#\"+b+a+d};this.getHelpXML=function(){for(var b=[],k=0;k<d.length;k++)for(var c=d[k].example,j=0;j<c.length;j++)b[b.length]=c[j];for(var h in a)b[b.length]=h;c=document.createElement(\"ul\");\nc.setAttribute(\"id\",\"rgbcolor-examples\");for(k=0;k<b.length;k++)try{var l=document.createElement(\"li\"),o=new RGBColor(b[k]),n=document.createElement(\"div\");n.style.cssText=\"margin: 3px; border: 1px solid black; background:\"+o.toHex()+\"; color:\"+o.toHex();n.appendChild(document.createTextNode(\"test\"));var q=document.createTextNode(\" \"+b[k]+\" -> \"+o.toRGB()+\" -> \"+o.toHex());l.appendChild(n);l.appendChild(q);c.appendChild(l)}catch(p){}return c}}\nif(!window.console)window.console={},window.console.log=function(){},window.console.dir=function(){};if(!Array.prototype.indexOf)Array.prototype.indexOf=function(m){for(var a=0;a<this.length;a++)if(this[a]==m)return a;return-1};\n(function(){function m(){var a={FRAMERATE:30,MAX_VIRTUAL_PIXELS:3E4};a.init=function(c){a.Definitions={};a.Styles={};a.Animations=[];a.Images=[];a.ctx=c;a.ViewPort=new function(){this.viewPorts=[];this.Clear=function(){this.viewPorts=[]};this.SetCurrent=function(a,b){this.viewPorts.push({width:a,height:b})};this.RemoveCurrent=function(){this.viewPorts.pop()};this.Current=function(){return this.viewPorts[this.viewPorts.length-1]};this.width=function(){return this.Current().width};this.height=function(){return this.Current().height};\nthis.ComputeSize=function(a){return a!=null&&typeof a==\"number\"?a:a==\"x\"?this.width():a==\"y\"?this.height():Math.sqrt(Math.pow(this.width(),2)+Math.pow(this.height(),2))/Math.sqrt(2)}}};a.init();a.ImagesLoaded=function(){for(var c=0;c<a.Images.length;c++)if(!a.Images[c].loaded)return!1;return!0};a.trim=function(a){return a.replace(/^\\s+|\\s+$/g,\"\")};a.compressSpaces=function(a){return a.replace(/[\\s\\r\\t\\n]+/gm,\" \")};a.ajax=function(a){var d;return(d=window.XMLHttpRequest?new XMLHttpRequest:new ActiveXObject(\"Microsoft.XMLHTTP\"))?\n(d.open(\"GET\",a,!1),d.send(null),d.responseText):null};a.parseXml=function(a){if(window.DOMParser)return(new DOMParser).parseFromString(a,\"text/xml\");else{var a=a.replace(/<!DOCTYPE svg[^>]*>/,\"\"),d=new ActiveXObject(\"Microsoft.XMLDOM\");d.async=\"false\";d.loadXML(a);return d}};a.Property=function(c,d){this.name=c;this.value=d;this.hasValue=function(){return this.value!=null&&this.value!==\"\"};this.numValue=function(){if(!this.hasValue())return 0;var b=parseFloat(this.value);(this.value+\"\").match(/%$/)&&\n(b/=100);return b};this.valueOrDefault=function(b){return this.hasValue()?this.value:b};this.numValueOrDefault=function(b){return this.hasValue()?this.numValue():b};var b=this;this.Color={addOpacity:function(d){var c=b.value;if(d!=null&&d!=\"\"){var f=new RGBColor(b.value);f.ok&&(c=\"rgba(\"+f.r+\", \"+f.g+\", \"+f.b+\", \"+d+\")\")}return new a.Property(b.name,c)}};this.Definition={getDefinition:function(){var d=b.value.replace(/^(url\\()?#([^\\)]+)\\)?$/,\"$2\");return a.Definitions[d]},isUrl:function(){return b.value.indexOf(\"url(\")==\n0},getFillStyle:function(b){var d=this.getDefinition();return d!=null&&d.createGradient?d.createGradient(a.ctx,b):d!=null&&d.createPattern?d.createPattern(a.ctx,b):null}};this.Length={DPI:function(){return 96},EM:function(b){var d=12,c=new a.Property(\"fontSize\",a.Font.Parse(a.ctx.font).fontSize);c.hasValue()&&(d=c.Length.toPixels(b));return d},toPixels:function(d){if(!b.hasValue())return 0;var c=b.value+\"\";return c.match(/em$/)?b.numValue()*this.EM(d):c.match(/ex$/)?b.numValue()*this.EM(d)/2:c.match(/px$/)?\nb.numValue():c.match(/pt$/)?b.numValue()*1.25:c.match(/pc$/)?b.numValue()*15:c.match(/cm$/)?b.numValue()*this.DPI(d)/2.54:c.match(/mm$/)?b.numValue()*this.DPI(d)/25.4:c.match(/in$/)?b.numValue()*this.DPI(d):c.match(/%$/)?b.numValue()*a.ViewPort.ComputeSize(d):b.numValue()}};this.Time={toMilliseconds:function(){if(!b.hasValue())return 0;var a=b.value+\"\";if(a.match(/s$/))return b.numValue()*1E3;a.match(/ms$/);return b.numValue()}};this.Angle={toRadians:function(){if(!b.hasValue())return 0;var a=b.value+\n\"\";return a.match(/deg$/)?b.numValue()*(Math.PI/180):a.match(/grad$/)?b.numValue()*(Math.PI/200):a.match(/rad$/)?b.numValue():b.numValue()*(Math.PI/180)}}};a.Font=new function(){this.Styles=[\"normal\",\"italic\",\"oblique\",\"inherit\"];this.Variants=[\"normal\",\"small-caps\",\"inherit\"];this.Weights=\"normal,bold,bolder,lighter,100,200,300,400,500,600,700,800,900,inherit\".split(\",\");this.CreateFont=function(d,b,c,e,f,g){g=g!=null?this.Parse(g):this.CreateFont(\"\",\"\",\"\",\"\",\"\",a.ctx.font);return{fontFamily:f||\ng.fontFamily,fontSize:e||g.fontSize,fontStyle:d||g.fontStyle,fontWeight:c||g.fontWeight,fontVariant:b||g.fontVariant,toString:function(){return[this.fontStyle,this.fontVariant,this.fontWeight,this.fontSize,this.fontFamily].join(\" \")}}};var c=this;this.Parse=function(d){for(var b={},d=a.trim(a.compressSpaces(d||\"\")).split(\" \"),k=!1,e=!1,f=!1,g=!1,j=\"\",h=0;h<d.length;h++)if(!e&&c.Styles.indexOf(d[h])!=-1){if(d[h]!=\"inherit\")b.fontStyle=d[h];e=!0}else if(!g&&c.Variants.indexOf(d[h])!=-1){if(d[h]!=\"inherit\")b.fontVariant=\nd[h];e=g=!0}else if(!f&&c.Weights.indexOf(d[h])!=-1){if(d[h]!=\"inherit\")b.fontWeight=d[h];e=g=f=!0}else if(k)d[h]!=\"inherit\"&&(j+=d[h]);else{if(d[h]!=\"inherit\")b.fontSize=d[h].split(\"/\")[0];e=g=f=k=!0}if(j!=\"\")b.fontFamily=j;return b}};a.ToNumberArray=function(c){for(var c=a.trim(a.compressSpaces((c||\"\").replace(/,/g,\" \"))).split(\" \"),d=0;d<c.length;d++)c[d]=parseFloat(c[d]);return c};a.Point=function(a,d){this.x=a;this.y=d;this.angleTo=function(b){return Math.atan2(b.y-this.y,b.x-this.x)};this.applyTransform=\nfunction(b){var a=this.x*b[1]+this.y*b[3]+b[5];this.x=this.x*b[0]+this.y*b[2]+b[4];this.y=a}};a.CreatePoint=function(c){c=a.ToNumberArray(c);return new a.Point(c[0],c[1])};a.CreatePath=function(c){for(var c=a.ToNumberArray(c),d=[],b=0;b<c.length;b+=2)d.push(new a.Point(c[b],c[b+1]));return d};a.BoundingBox=function(a,d,b,k){this.y2=this.x2=this.y1=this.x1=Number.NaN;this.x=function(){return this.x1};this.y=function(){return this.y1};this.width=function(){return this.x2-this.x1};this.height=function(){return this.y2-\nthis.y1};this.addPoint=function(b,a){if(b!=null){if(isNaN(this.x1)||isNaN(this.x2))this.x2=this.x1=b;if(b<this.x1)this.x1=b;if(b>this.x2)this.x2=b}if(a!=null){if(isNaN(this.y1)||isNaN(this.y2))this.y2=this.y1=a;if(a<this.y1)this.y1=a;if(a>this.y2)this.y2=a}};this.addX=function(b){this.addPoint(b,null)};this.addY=function(b){this.addPoint(null,b)};this.addBoundingBox=function(b){this.addPoint(b.x1,b.y1);this.addPoint(b.x2,b.y2)};this.addQuadraticCurve=function(b,a,d,c,k,l){d=b+2/3*(d-b);c=a+2/3*(c-\na);this.addBezierCurve(b,a,d,d+1/3*(k-b),c,c+1/3*(l-a),k,l)};this.addBezierCurve=function(b,a,d,c,k,l,o,n){var q=[b,a],p=[d,c],t=[k,l],m=[o,n];this.addPoint(q[0],q[1]);this.addPoint(m[0],m[1]);for(i=0;i<=1;i++)b=function(b){return Math.pow(1-b,3)*q[i]+3*Math.pow(1-b,2)*b*p[i]+3*(1-b)*Math.pow(b,2)*t[i]+Math.pow(b,3)*m[i]},a=6*q[i]-12*p[i]+6*t[i],d=-3*q[i]+9*p[i]-9*t[i]+3*m[i],c=3*p[i]-3*q[i],d==0?a!=0&&(a=-c/a,0<a&&a<1&&(i==0&&this.addX(b(a)),i==1&&this.addY(b(a)))):(c=Math.pow(a,2)-4*c*d,c<0||(k=\n(-a+Math.sqrt(c))/(2*d),0<k&&k<1&&(i==0&&this.addX(b(k)),i==1&&this.addY(b(k))),a=(-a-Math.sqrt(c))/(2*d),0<a&&a<1&&(i==0&&this.addX(b(a)),i==1&&this.addY(b(a)))))};this.isPointInBox=function(b,a){return this.x1<=b&&b<=this.x2&&this.y1<=a&&a<=this.y2};this.addPoint(a,d);this.addPoint(b,k)};a.Transform=function(c){var d=this;this.Type={};this.Type.translate=function(b){this.p=a.CreatePoint(b);this.apply=function(b){b.translate(this.p.x||0,this.p.y||0)};this.applyToPoint=function(b){b.applyTransform([1,\n0,0,1,this.p.x||0,this.p.y||0])}};this.Type.rotate=function(b){b=a.ToNumberArray(b);this.angle=new a.Property(\"angle\",b[0]);this.cx=b[1]||0;this.cy=b[2]||0;this.apply=function(b){b.translate(this.cx,this.cy);b.rotate(this.angle.Angle.toRadians());b.translate(-this.cx,-this.cy)};this.applyToPoint=function(b){var a=this.angle.Angle.toRadians();b.applyTransform([1,0,0,1,this.p.x||0,this.p.y||0]);b.applyTransform([Math.cos(a),Math.sin(a),-Math.sin(a),Math.cos(a),0,0]);b.applyTransform([1,0,0,1,-this.p.x||\n0,-this.p.y||0])}};this.Type.scale=function(b){this.p=a.CreatePoint(b);this.apply=function(b){b.scale(this.p.x||1,this.p.y||this.p.x||1)};this.applyToPoint=function(b){b.applyTransform([this.p.x||0,0,0,this.p.y||0,0,0])}};this.Type.matrix=function(b){this.m=a.ToNumberArray(b);this.apply=function(b){b.transform(this.m[0],this.m[1],this.m[2],this.m[3],this.m[4],this.m[5])};this.applyToPoint=function(b){b.applyTransform(this.m)}};this.Type.SkewBase=function(b){this.base=d.Type.matrix;this.base(b);this.angle=\nnew a.Property(\"angle\",b)};this.Type.SkewBase.prototype=new this.Type.matrix;this.Type.skewX=function(b){this.base=d.Type.SkewBase;this.base(b);this.m=[1,0,Math.tan(this.angle.Angle.toRadians()),1,0,0]};this.Type.skewX.prototype=new this.Type.SkewBase;this.Type.skewY=function(b){this.base=d.Type.SkewBase;this.base(b);this.m=[1,Math.tan(this.angle.Angle.toRadians()),0,1,0,0]};this.Type.skewY.prototype=new this.Type.SkewBase;this.transforms=[];this.apply=function(b){for(var a=0;a<this.transforms.length;a++)this.transforms[a].apply(b)};\nthis.applyToPoint=function(b){for(var a=0;a<this.transforms.length;a++)this.transforms[a].applyToPoint(b)};for(var c=a.trim(a.compressSpaces(c)).split(/\\s(?=[a-z])/),b=0;b<c.length;b++){var k=c[b].split(\"(\")[0],e=c[b].split(\"(\")[1].replace(\")\",\"\");this.transforms.push(new this.Type[k](e))}};a.AspectRatio=function(c,d,b,k,e,f,g,j,h,l){var d=a.compressSpaces(d),d=d.replace(/^defer\\s/,\"\"),o=d.split(\" \")[0]||\"xMidYMid\",d=d.split(\" \")[1]||\"meet\",n=b/k,q=e/f,p=Math.min(n,q),m=Math.max(n,q);d==\"meet\"&&(k*=\np,f*=p);d==\"slice\"&&(k*=m,f*=m);h=new a.Property(\"refX\",h);l=new a.Property(\"refY\",l);h.hasValue()&&l.hasValue()?c.translate(-p*h.Length.toPixels(\"x\"),-p*l.Length.toPixels(\"y\")):(o.match(/^xMid/)&&(d==\"meet\"&&p==q||d==\"slice\"&&m==q)&&c.translate(b/2-k/2,0),o.match(/YMid$/)&&(d==\"meet\"&&p==n||d==\"slice\"&&m==n)&&c.translate(0,e/2-f/2),o.match(/^xMax/)&&(d==\"meet\"&&p==q||d==\"slice\"&&m==q)&&c.translate(b-k,0),o.match(/YMax$/)&&(d==\"meet\"&&p==n||d==\"slice\"&&m==n)&&c.translate(0,e-f));o==\"none\"?c.scale(n,\nq):d==\"meet\"?c.scale(p,p):d==\"slice\"&&c.scale(m,m);c.translate(g==null?0:-g,j==null?0:-j)};a.Element={};a.Element.ElementBase=function(c){this.attributes={};this.styles={};this.children=[];this.attribute=function(b,d){var c=this.attributes[b];if(c!=null)return c;c=new a.Property(b,\"\");d==!0&&(this.attributes[b]=c);return c};this.style=function(b,d){var c=this.styles[b];if(c!=null)return c;c=this.attribute(b);if(c!=null&&c.hasValue())return c;c=this.parent;if(c!=null&&(c=c.style(b),c!=null&&c.hasValue()))return c;\nc=new a.Property(b,\"\");d==!0&&(this.styles[b]=c);return c};this.render=function(b){if(this.style(\"display\").value!=\"none\"&&this.attribute(\"visibility\").value!=\"hidden\"){b.save();this.setContext(b);if(this.attribute(\"mask\").hasValue()){var a=this.attribute(\"mask\").Definition.getDefinition();a!=null&&a.apply(b,this)}else this.style(\"filter\").hasValue()?(a=this.style(\"filter\").Definition.getDefinition(),a!=null&&a.apply(b,this)):this.renderChildren(b);this.clearContext(b);b.restore()}};this.setContext=\nfunction(){};this.clearContext=function(){};this.renderChildren=function(b){for(var a=0;a<this.children.length;a++)this.children[a].render(b)};this.addChild=function(b,d){var c=b;d&&(c=a.CreateElement(b));c.parent=this;this.children.push(c)};if(c!=null&&c.nodeType==1){for(var d=0;d<c.childNodes.length;d++){var b=c.childNodes[d];b.nodeType==1&&this.addChild(b,!0)}for(d=0;d<c.attributes.length;d++)b=c.attributes[d],this.attributes[b.nodeName]=new a.Property(b.nodeName,b.nodeValue);b=a.Styles[c.nodeName];\nif(b!=null)for(var k in b)this.styles[k]=b[k];if(this.attribute(\"class\").hasValue())for(var d=a.compressSpaces(this.attribute(\"class\").value).split(\" \"),e=0;e<d.length;e++){b=a.Styles[\".\"+d[e]];if(b!=null)for(k in b)this.styles[k]=b[k];b=a.Styles[c.nodeName+\".\"+d[e]];if(b!=null)for(k in b)this.styles[k]=b[k]}if(this.attribute(\"style\").hasValue()){b=this.attribute(\"style\").value.split(\";\");for(d=0;d<b.length;d++)a.trim(b[d])!=\"\"&&(c=b[d].split(\":\"),k=a.trim(c[0]),c=a.trim(c[1]),this.styles[k]=new a.Property(k,\nc))}this.attribute(\"id\").hasValue()&&a.Definitions[this.attribute(\"id\").value]==null&&(a.Definitions[this.attribute(\"id\").value]=this)}};a.Element.RenderedElementBase=function(c){this.base=a.Element.ElementBase;this.base(c);this.setContext=function(d){if(this.style(\"fill\").Definition.isUrl()){var b=this.style(\"fill\").Definition.getFillStyle(this);if(b!=null)d.fillStyle=b}else if(this.style(\"fill\").hasValue())b=this.style(\"fill\"),this.style(\"fill-opacity\").hasValue()&&(b=b.Color.addOpacity(this.style(\"fill-opacity\").value)),\nd.fillStyle=b.value==\"none\"?\"rgba(0,0,0,0)\":b.value;if(this.style(\"stroke\").Definition.isUrl()){if(b=this.style(\"stroke\").Definition.getFillStyle(this),b!=null)d.strokeStyle=b}else if(this.style(\"stroke\").hasValue())b=this.style(\"stroke\"),this.style(\"stroke-opacity\").hasValue()&&(b=b.Color.addOpacity(this.style(\"stroke-opacity\").value)),d.strokeStyle=b.value==\"none\"?\"rgba(0,0,0,0)\":b.value;if(this.style(\"stroke-width\").hasValue())d.lineWidth=this.style(\"stroke-width\").Length.toPixels();if(this.style(\"stroke-linecap\").hasValue())d.lineCap=\nthis.style(\"stroke-linecap\").value;if(this.style(\"stroke-linejoin\").hasValue())d.lineJoin=this.style(\"stroke-linejoin\").value;if(this.style(\"stroke-miterlimit\").hasValue())d.miterLimit=this.style(\"stroke-miterlimit\").value;if(typeof d.font!=\"undefined\")d.font=a.Font.CreateFont(this.style(\"font-style\").value,this.style(\"font-variant\").value,this.style(\"font-weight\").value,this.style(\"font-size\").hasValue()?this.style(\"font-size\").Length.toPixels()+\"px\":\"\",this.style(\"font-family\").value).toString();\nthis.attribute(\"transform\").hasValue()&&(new a.Transform(this.attribute(\"transform\").value)).apply(d);this.attribute(\"clip-path\").hasValue()&&(b=this.attribute(\"clip-path\").Definition.getDefinition(),b!=null&&b.apply(d));if(this.style(\"opacity\").hasValue())d.globalAlpha=this.style(\"opacity\").numValue()}};a.Element.RenderedElementBase.prototype=new a.Element.ElementBase;a.Element.PathElementBase=function(c){this.base=a.Element.RenderedElementBase;this.base(c);this.path=function(d){d!=null&&d.beginPath();\nreturn new a.BoundingBox};this.renderChildren=function(d){this.path(d);a.Mouse.checkPath(this,d);d.fillStyle!=\"\"&&d.fill();d.strokeStyle!=\"\"&&d.stroke();var b=this.getMarkers();if(b!=null){if(this.style(\"marker-start\").Definition.isUrl()){var c=this.style(\"marker-start\").Definition.getDefinition();c.render(d,b[0][0],b[0][1])}if(this.style(\"marker-mid\").Definition.isUrl())for(var c=this.style(\"marker-mid\").Definition.getDefinition(),e=1;e<b.length-1;e++)c.render(d,b[e][0],b[e][1]);this.style(\"marker-end\").Definition.isUrl()&&\n(c=this.style(\"marker-end\").Definition.getDefinition(),c.render(d,b[b.length-1][0],b[b.length-1][1]))}};this.getBoundingBox=function(){return this.path()};this.getMarkers=function(){return null}};a.Element.PathElementBase.prototype=new a.Element.RenderedElementBase;a.Element.svg=function(c){this.base=a.Element.RenderedElementBase;this.base(c);this.baseClearContext=this.clearContext;this.clearContext=function(d){this.baseClearContext(d);a.ViewPort.RemoveCurrent()};this.baseSetContext=this.setContext;\nthis.setContext=function(d){d.strokeStyle=\"rgba(0,0,0,0)\";d.lineCap=\"butt\";d.lineJoin=\"miter\";d.miterLimit=4;this.baseSetContext(d);this.attribute(\"x\").hasValue()&&this.attribute(\"y\").hasValue()&&d.translate(this.attribute(\"x\").Length.toPixels(\"x\"),this.attribute(\"y\").Length.toPixels(\"y\"));var b=a.ViewPort.width(),c=a.ViewPort.height();if(typeof this.root==\"undefined\"&&this.attribute(\"width\").hasValue()&&this.attribute(\"height\").hasValue()){var b=this.attribute(\"width\").Length.toPixels(\"x\"),c=this.attribute(\"height\").Length.toPixels(\"y\"),\ne=0,f=0;this.attribute(\"refX\").hasValue()&&this.attribute(\"refY\").hasValue()&&(e=-this.attribute(\"refX\").Length.toPixels(\"x\"),f=-this.attribute(\"refY\").Length.toPixels(\"y\"));d.beginPath();d.moveTo(e,f);d.lineTo(b,f);d.lineTo(b,c);d.lineTo(e,c);d.closePath();d.clip()}a.ViewPort.SetCurrent(b,c);if(this.attribute(\"viewBox\").hasValue()){var e=a.ToNumberArray(this.attribute(\"viewBox\").value),f=e[0],g=e[1],b=e[2],c=e[3];a.AspectRatio(d,this.attribute(\"preserveAspectRatio\").value,a.ViewPort.width(),b,a.ViewPort.height(),\nc,f,g,this.attribute(\"refX\").value,this.attribute(\"refY\").value);a.ViewPort.RemoveCurrent();a.ViewPort.SetCurrent(e[2],e[3])}}};a.Element.svg.prototype=new a.Element.RenderedElementBase;a.Element.rect=function(c){this.base=a.Element.PathElementBase;this.base(c);this.path=function(d){var b=this.attribute(\"x\").Length.toPixels(\"x\"),c=this.attribute(\"y\").Length.toPixels(\"y\"),e=this.attribute(\"width\").Length.toPixels(\"x\"),f=this.attribute(\"height\").Length.toPixels(\"y\"),g=this.attribute(\"rx\").Length.toPixels(\"x\"),\nj=this.attribute(\"ry\").Length.toPixels(\"y\");this.attribute(\"rx\").hasValue()&&!this.attribute(\"ry\").hasValue()&&(j=g);this.attribute(\"ry\").hasValue()&&!this.attribute(\"rx\").hasValue()&&(g=j);d!=null&&(d.beginPath(),d.moveTo(b+g,c),d.lineTo(b+e-g,c),d.quadraticCurveTo(b+e,c,b+e,c+j),d.lineTo(b+e,c+f-j),d.quadraticCurveTo(b+e,c+f,b+e-g,c+f),d.lineTo(b+g,c+f),d.quadraticCurveTo(b,c+f,b,c+f-j),d.lineTo(b,c+j),d.quadraticCurveTo(b,c,b+g,c),d.closePath());return new a.BoundingBox(b,c,b+e,c+f)}};a.Element.rect.prototype=\nnew a.Element.PathElementBase;a.Element.circle=function(c){this.base=a.Element.PathElementBase;this.base(c);this.path=function(d){var b=this.attribute(\"cx\").Length.toPixels(\"x\"),c=this.attribute(\"cy\").Length.toPixels(\"y\"),e=this.attribute(\"r\").Length.toPixels();d!=null&&(d.beginPath(),d.arc(b,c,e,0,Math.PI*2,!0),d.closePath());return new a.BoundingBox(b-e,c-e,b+e,c+e)}};a.Element.circle.prototype=new a.Element.PathElementBase;a.Element.ellipse=function(c){this.base=a.Element.PathElementBase;this.base(c);\nthis.path=function(d){var b=4*((Math.sqrt(2)-1)/3),c=this.attribute(\"rx\").Length.toPixels(\"x\"),e=this.attribute(\"ry\").Length.toPixels(\"y\"),f=this.attribute(\"cx\").Length.toPixels(\"x\"),g=this.attribute(\"cy\").Length.toPixels(\"y\");d!=null&&(d.beginPath(),d.moveTo(f,g-e),d.bezierCurveTo(f+b*c,g-e,f+c,g-b*e,f+c,g),d.bezierCurveTo(f+c,g+b*e,f+b*c,g+e,f,g+e),d.bezierCurveTo(f-b*c,g+e,f-c,g+b*e,f-c,g),d.bezierCurveTo(f-c,g-b*e,f-b*c,g-e,f,g-e),d.closePath());return new a.BoundingBox(f-c,g-e,f+c,g+e)}};a.Element.ellipse.prototype=\nnew a.Element.PathElementBase;a.Element.line=function(c){this.base=a.Element.PathElementBase;this.base(c);this.getPoints=function(){return[new a.Point(this.attribute(\"x1\").Length.toPixels(\"x\"),this.attribute(\"y1\").Length.toPixels(\"y\")),new a.Point(this.attribute(\"x2\").Length.toPixels(\"x\"),this.attribute(\"y2\").Length.toPixels(\"y\"))]};this.path=function(d){var b=this.getPoints();d!=null&&(d.beginPath(),d.moveTo(b[0].x,b[0].y),d.lineTo(b[1].x,b[1].y));return new a.BoundingBox(b[0].x,b[0].y,b[1].x,b[1].y)};\nthis.getMarkers=function(){var a=this.getPoints(),b=a[0].angleTo(a[1]);return[[a[0],b],[a[1],b]]}};a.Element.line.prototype=new a.Element.PathElementBase;a.Element.polyline=function(c){this.base=a.Element.PathElementBase;this.base(c);this.points=a.CreatePath(this.attribute(\"points\").value);this.path=function(d){var b=new a.BoundingBox(this.points[0].x,this.points[0].y);d!=null&&(d.beginPath(),d.moveTo(this.points[0].x,this.points[0].y));for(var c=1;c<this.points.length;c++)b.addPoint(this.points[c].x,\nthis.points[c].y),d!=null&&d.lineTo(this.points[c].x,this.points[c].y);return b};this.getMarkers=function(){for(var a=[],b=0;b<this.points.length-1;b++)a.push([this.points[b],this.points[b].angleTo(this.points[b+1])]);a.push([this.points[this.points.length-1],a[a.length-1][1]]);return a}};a.Element.polyline.prototype=new a.Element.PathElementBase;a.Element.polygon=function(c){this.base=a.Element.polyline;this.base(c);this.basePath=this.path;this.path=function(a){var b=this.basePath(a);a!=null&&(a.lineTo(this.points[0].x,\nthis.points[0].y),a.closePath());return b}};a.Element.polygon.prototype=new a.Element.polyline;a.Element.path=function(c){this.base=a.Element.PathElementBase;this.base(c);c=this.attribute(\"d\").value;c=c.replace(/,/gm,\" \");c=c.replace(/([MmZzLlHhVvCcSsQqTtAa])([MmZzLlHhVvCcSsQqTtAa])/gm,\"$1 $2\");c=c.replace(/([MmZzLlHhVvCcSsQqTtAa])([MmZzLlHhVvCcSsQqTtAa])/gm,\"$1 $2\");c=c.replace(/([MmZzLlHhVvCcSsQqTtAa])([^\\s])/gm,\"$1 $2\");c=c.replace(/([^\\s])([MmZzLlHhVvCcSsQqTtAa])/gm,\"$1 $2\");c=c.replace(/([0-9])([+\\-])/gm,\n\"$1 $2\");c=c.replace(/(\\.[0-9]*)(\\.)/gm,\"$1 $2\");c=c.replace(/([Aa](\\s+[0-9]+){3})\\s+([01])\\s*([01])/gm,\"$1 $3 $4 \");c=a.compressSpaces(c);c=a.trim(c);this.PathParser=new function(d){this.tokens=d.split(\" \");this.reset=function(){this.i=-1;this.previousCommand=this.command=\"\";this.start=new a.Point(0,0);this.control=new a.Point(0,0);this.current=new a.Point(0,0);this.points=[];this.angles=[]};this.isEnd=function(){return this.i>=this.tokens.length-1};this.isCommandOrEnd=function(){return this.isEnd()?\n!0:this.tokens[this.i+1].match(/^[A-Za-z]$/)!=null};this.isRelativeCommand=function(){return this.command==this.command.toLowerCase()};this.getToken=function(){this.i+=1;return this.tokens[this.i]};this.getScalar=function(){return parseFloat(this.getToken())};this.nextCommand=function(){this.previousCommand=this.command;this.command=this.getToken()};this.getPoint=function(){return this.makeAbsolute(new a.Point(this.getScalar(),this.getScalar()))};this.getAsControlPoint=function(){var b=this.getPoint();\nreturn this.control=b};this.getAsCurrentPoint=function(){var b=this.getPoint();return this.current=b};this.getReflectedControlPoint=function(){return this.previousCommand.toLowerCase()!=\"c\"&&this.previousCommand.toLowerCase()!=\"s\"?this.current:new a.Point(2*this.current.x-this.control.x,2*this.current.y-this.control.y)};this.makeAbsolute=function(b){if(this.isRelativeCommand())b.x=this.current.x+b.x,b.y=this.current.y+b.y;return b};this.addMarker=function(b,a,d){d!=null&&this.angles.length>0&&this.angles[this.angles.length-\n1]==null&&(this.angles[this.angles.length-1]=this.points[this.points.length-1].angleTo(d));this.addMarkerAngle(b,a==null?null:a.angleTo(b))};this.addMarkerAngle=function(b,a){this.points.push(b);this.angles.push(a)};this.getMarkerPoints=function(){return this.points};this.getMarkerAngles=function(){for(var b=0;b<this.angles.length;b++)if(this.angles[b]==null)for(var a=b+1;a<this.angles.length;a++)if(this.angles[a]!=null){this.angles[b]=this.angles[a];break}return this.angles}}(c);this.path=function(d){var b=\nthis.PathParser;b.reset();var c=new a.BoundingBox;for(d!=null&&d.beginPath();!b.isEnd();)switch(b.nextCommand(),b.command.toUpperCase()){case \"M\":var e=b.getAsCurrentPoint();b.addMarker(e);c.addPoint(e.x,e.y);d!=null&&d.moveTo(e.x,e.y);for(b.start=b.current;!b.isCommandOrEnd();)e=b.getAsCurrentPoint(),b.addMarker(e,b.start),c.addPoint(e.x,e.y),d!=null&&d.lineTo(e.x,e.y);break;case \"L\":for(;!b.isCommandOrEnd();){var f=b.current,e=b.getAsCurrentPoint();b.addMarker(e,f);c.addPoint(e.x,e.y);d!=null&&\nd.lineTo(e.x,e.y)}break;case \"H\":for(;!b.isCommandOrEnd();)e=new a.Point((b.isRelativeCommand()?b.current.x:0)+b.getScalar(),b.current.y),b.addMarker(e,b.current),b.current=e,c.addPoint(b.current.x,b.current.y),d!=null&&d.lineTo(b.current.x,b.current.y);break;case \"V\":for(;!b.isCommandOrEnd();)e=new a.Point(b.current.x,(b.isRelativeCommand()?b.current.y:0)+b.getScalar()),b.addMarker(e,b.current),b.current=e,c.addPoint(b.current.x,b.current.y),d!=null&&d.lineTo(b.current.x,b.current.y);break;case \"C\":for(;!b.isCommandOrEnd();){var g=\nb.current,f=b.getPoint(),j=b.getAsControlPoint(),e=b.getAsCurrentPoint();b.addMarker(e,j,f);c.addBezierCurve(g.x,g.y,f.x,f.y,j.x,j.y,e.x,e.y);d!=null&&d.bezierCurveTo(f.x,f.y,j.x,j.y,e.x,e.y)}break;case \"S\":for(;!b.isCommandOrEnd();)g=b.current,f=b.getReflectedControlPoint(),j=b.getAsControlPoint(),e=b.getAsCurrentPoint(),b.addMarker(e,j,f),c.addBezierCurve(g.x,g.y,f.x,f.y,j.x,j.y,e.x,e.y),d!=null&&d.bezierCurveTo(f.x,f.y,j.x,j.y,e.x,e.y);break;case \"Q\":for(;!b.isCommandOrEnd();)g=b.current,j=b.getAsControlPoint(),\ne=b.getAsCurrentPoint(),b.addMarker(e,j,j),c.addQuadraticCurve(g.x,g.y,j.x,j.y,e.x,e.y),d!=null&&d.quadraticCurveTo(j.x,j.y,e.x,e.y);break;case \"T\":for(;!b.isCommandOrEnd();)g=b.current,j=b.getReflectedControlPoint(),b.control=j,e=b.getAsCurrentPoint(),b.addMarker(e,j,j),c.addQuadraticCurve(g.x,g.y,j.x,j.y,e.x,e.y),d!=null&&d.quadraticCurveTo(j.x,j.y,e.x,e.y);break;case \"A\":for(;!b.isCommandOrEnd();){var g=b.current,h=b.getScalar(),l=b.getScalar(),f=b.getScalar()*(Math.PI/180),o=b.getScalar(),j=b.getScalar(),\ne=b.getAsCurrentPoint(),n=new a.Point(Math.cos(f)*(g.x-e.x)/2+Math.sin(f)*(g.y-e.y)/2,-Math.sin(f)*(g.x-e.x)/2+Math.cos(f)*(g.y-e.y)/2),q=Math.pow(n.x,2)/Math.pow(h,2)+Math.pow(n.y,2)/Math.pow(l,2);q>1&&(h*=Math.sqrt(q),l*=Math.sqrt(q));o=(o==j?-1:1)*Math.sqrt((Math.pow(h,2)*Math.pow(l,2)-Math.pow(h,2)*Math.pow(n.y,2)-Math.pow(l,2)*Math.pow(n.x,2))/(Math.pow(h,2)*Math.pow(n.y,2)+Math.pow(l,2)*Math.pow(n.x,2)));isNaN(o)&&(o=0);var p=new a.Point(o*h*n.y/l,o*-l*n.x/h),g=new a.Point((g.x+e.x)/2+Math.cos(f)*\np.x-Math.sin(f)*p.y,(g.y+e.y)/2+Math.sin(f)*p.x+Math.cos(f)*p.y),m=function(b,a){return(b[0]*a[0]+b[1]*a[1])/(Math.sqrt(Math.pow(b[0],2)+Math.pow(b[1],2))*Math.sqrt(Math.pow(a[0],2)+Math.pow(a[1],2)))},s=function(b,a){return(b[0]*a[1]<b[1]*a[0]?-1:1)*Math.acos(m(b,a))},o=s([1,0],[(n.x-p.x)/h,(n.y-p.y)/l]),q=[(n.x-p.x)/h,(n.y-p.y)/l],p=[(-n.x-p.x)/h,(-n.y-p.y)/l],n=s(q,p);if(m(q,p)<=-1)n=Math.PI;m(q,p)>=1&&(n=0);j==0&&n>0&&(n-=2*Math.PI);j==1&&n<0&&(n+=2*Math.PI);q=new a.Point(g.x-h*Math.cos((o+n)/\n2),g.y-l*Math.sin((o+n)/2));b.addMarkerAngle(q,(o+n)/2+(j==0?1:-1)*Math.PI/2);b.addMarkerAngle(e,n+(j==0?1:-1)*Math.PI/2);c.addPoint(e.x,e.y);d!=null&&(m=h>l?h:l,e=h>l?1:h/l,h=h>l?l/h:1,d.translate(g.x,g.y),d.rotate(f),d.scale(e,h),d.arc(0,0,m,o,o+n,1-j),d.scale(1/e,1/h),d.rotate(-f),d.translate(-g.x,-g.y))}break;case \"Z\":d!=null&&d.closePath(),b.current=b.start}return c};this.getMarkers=function(){for(var a=this.PathParser.getMarkerPoints(),b=this.PathParser.getMarkerAngles(),c=[],e=0;e<a.length;e++)c.push([a[e],\nb[e]]);return c}};a.Element.path.prototype=new a.Element.PathElementBase;a.Element.pattern=function(c){this.base=a.Element.ElementBase;this.base(c);this.createPattern=function(d){var b=new a.Element.svg;b.attributes.viewBox=new a.Property(\"viewBox\",this.attribute(\"viewBox\").value);b.attributes.x=new a.Property(\"x\",this.attribute(\"x\").value);b.attributes.y=new a.Property(\"y\",this.attribute(\"y\").value);b.attributes.width=new a.Property(\"width\",this.attribute(\"width\").value);b.attributes.height=new a.Property(\"height\",\nthis.attribute(\"height\").value);b.children=this.children;var c=document.createElement(\"canvas\");c.width=this.attribute(\"width\").Length.toPixels(\"x\");c.height=this.attribute(\"height\").Length.toPixels(\"y\");b.render(c.getContext(\"2d\"));return d.createPattern(c,\"repeat\")}};a.Element.pattern.prototype=new a.Element.ElementBase;a.Element.marker=function(c){this.base=a.Element.ElementBase;this.base(c);this.baseRender=this.render;this.render=function(d,b,c){d.translate(b.x,b.y);this.attribute(\"orient\").valueOrDefault(\"auto\")==\n\"auto\"&&d.rotate(c);this.attribute(\"markerUnits\").valueOrDefault(\"strokeWidth\")==\"strokeWidth\"&&d.scale(d.lineWidth,d.lineWidth);d.save();var e=new a.Element.svg;e.attributes.viewBox=new a.Property(\"viewBox\",this.attribute(\"viewBox\").value);e.attributes.refX=new a.Property(\"refX\",this.attribute(\"refX\").value);e.attributes.refY=new a.Property(\"refY\",this.attribute(\"refY\").value);e.attributes.width=new a.Property(\"width\",this.attribute(\"markerWidth\").value);e.attributes.height=new a.Property(\"height\",\nthis.attribute(\"markerHeight\").value);e.attributes.fill=new a.Property(\"fill\",this.attribute(\"fill\").valueOrDefault(\"black\"));e.attributes.stroke=new a.Property(\"stroke\",this.attribute(\"stroke\").valueOrDefault(\"none\"));e.children=this.children;e.render(d);d.restore();this.attribute(\"markerUnits\").valueOrDefault(\"strokeWidth\")==\"strokeWidth\"&&d.scale(1/d.lineWidth,1/d.lineWidth);this.attribute(\"orient\").valueOrDefault(\"auto\")==\"auto\"&&d.rotate(-c);d.translate(-b.x,-b.y)}};a.Element.marker.prototype=\nnew a.Element.ElementBase;a.Element.defs=function(c){this.base=a.Element.ElementBase;this.base(c);this.render=function(){}};a.Element.defs.prototype=new a.Element.ElementBase;a.Element.GradientBase=function(c){this.base=a.Element.ElementBase;this.base(c);this.gradientUnits=this.attribute(\"gradientUnits\").valueOrDefault(\"objectBoundingBox\");this.stops=[];for(c=0;c<this.children.length;c++)this.stops.push(this.children[c]);this.getGradient=function(){};this.createGradient=function(d,b){var c=this;this.attribute(\"xlink:href\").hasValue()&&\n(c=this.attribute(\"xlink:href\").Definition.getDefinition());for(var e=this.getGradient(d,b),f=0;f<c.stops.length;f++)e.addColorStop(c.stops[f].offset,c.stops[f].color);if(this.attribute(\"gradientTransform\").hasValue()){c=a.ViewPort.viewPorts[0];f=new a.Element.rect;f.attributes.x=new a.Property(\"x\",-a.MAX_VIRTUAL_PIXELS/3);f.attributes.y=new a.Property(\"y\",-a.MAX_VIRTUAL_PIXELS/3);f.attributes.width=new a.Property(\"width\",a.MAX_VIRTUAL_PIXELS);f.attributes.height=new a.Property(\"height\",a.MAX_VIRTUAL_PIXELS);\nvar g=new a.Element.g;g.attributes.transform=new a.Property(\"transform\",this.attribute(\"gradientTransform\").value);g.children=[f];f=new a.Element.svg;f.attributes.x=new a.Property(\"x\",0);f.attributes.y=new a.Property(\"y\",0);f.attributes.width=new a.Property(\"width\",c.width);f.attributes.height=new a.Property(\"height\",c.height);f.children=[g];g=document.createElement(\"canvas\");g.width=c.width;g.height=c.height;c=g.getContext(\"2d\");c.fillStyle=e;f.render(c);return c.createPattern(g,\"no-repeat\")}return e}};\na.Element.GradientBase.prototype=new a.Element.ElementBase;a.Element.linearGradient=function(c){this.base=a.Element.GradientBase;this.base(c);this.getGradient=function(a,b){var c=b.getBoundingBox(),e=this.gradientUnits==\"objectBoundingBox\"?c.x()+c.width()*this.attribute(\"x1\").numValue():this.attribute(\"x1\").Length.toPixels(\"x\"),f=this.gradientUnits==\"objectBoundingBox\"?c.y()+c.height()*this.attribute(\"y1\").numValue():this.attribute(\"y1\").Length.toPixels(\"y\"),g=this.gradientUnits==\"objectBoundingBox\"?\nc.x()+c.width()*this.attribute(\"x2\").numValue():this.attribute(\"x2\").Length.toPixels(\"x\"),c=this.gradientUnits==\"objectBoundingBox\"?c.y()+c.height()*this.attribute(\"y2\").numValue():this.attribute(\"y2\").Length.toPixels(\"y\");return a.createLinearGradient(e,f,g,c)}};a.Element.linearGradient.prototype=new a.Element.GradientBase;a.Element.radialGradient=function(c){this.base=a.Element.GradientBase;this.base(c);this.getGradient=function(a,b){var c=b.getBoundingBox(),e=this.gradientUnits==\"objectBoundingBox\"?\nc.x()+c.width()*this.attribute(\"cx\").numValue():this.attribute(\"cx\").Length.toPixels(\"x\"),f=this.gradientUnits==\"objectBoundingBox\"?c.y()+c.height()*this.attribute(\"cy\").numValue():this.attribute(\"cy\").Length.toPixels(\"y\"),g=e,j=f;this.attribute(\"fx\").hasValue()&&(g=this.gradientUnits==\"objectBoundingBox\"?c.x()+c.width()*this.attribute(\"fx\").numValue():this.attribute(\"fx\").Length.toPixels(\"x\"));this.attribute(\"fy\").hasValue()&&(j=this.gradientUnits==\"objectBoundingBox\"?c.y()+c.height()*this.attribute(\"fy\").numValue():\nthis.attribute(\"fy\").Length.toPixels(\"y\"));c=this.gradientUnits==\"objectBoundingBox\"?(c.width()+c.height())/2*this.attribute(\"r\").numValue():this.attribute(\"r\").Length.toPixels();return a.createRadialGradient(g,j,0,e,f,c)}};a.Element.radialGradient.prototype=new a.Element.GradientBase;a.Element.stop=function(c){this.base=a.Element.ElementBase;this.base(c);this.offset=this.attribute(\"offset\").numValue();c=this.style(\"stop-color\");this.style(\"stop-opacity\").hasValue()&&(c=c.Color.addOpacity(this.style(\"stop-opacity\").value));\nthis.color=c.value};a.Element.stop.prototype=new a.Element.ElementBase;a.Element.AnimateBase=function(c){this.base=a.Element.ElementBase;this.base(c);a.Animations.push(this);this.duration=0;this.begin=this.attribute(\"begin\").Time.toMilliseconds();this.maxDuration=this.begin+this.attribute(\"dur\").Time.toMilliseconds();this.getProperty=function(){var a=this.attribute(\"attributeType\").value,b=this.attribute(\"attributeName\").value;return a==\"CSS\"?this.parent.style(b,!0):this.parent.attribute(b,!0)};this.initialValue=\nnull;this.removed=!1;this.calcValue=function(){return\"\"};this.update=function(a){if(this.initialValue==null)this.initialValue=this.getProperty().value;if(this.duration>this.maxDuration)if(this.attribute(\"repeatCount\").value==\"indefinite\")this.duration=0;else return this.attribute(\"fill\").valueOrDefault(\"remove\")==\"remove\"&&!this.removed?(this.removed=!0,this.getProperty().value=this.initialValue,!0):!1;this.duration+=a;a=!1;if(this.begin<this.duration)a=this.calcValue(),this.attribute(\"type\").hasValue()&&\n(a=this.attribute(\"type\").value+\"(\"+a+\")\"),this.getProperty().value=a,a=!0;return a};this.progress=function(){return(this.duration-this.begin)/(this.maxDuration-this.begin)}};a.Element.AnimateBase.prototype=new a.Element.ElementBase;a.Element.animate=function(c){this.base=a.Element.AnimateBase;this.base(c);this.calcValue=function(){var a=this.attribute(\"from\").numValue(),b=this.attribute(\"to\").numValue();return a+(b-a)*this.progress()}};a.Element.animate.prototype=new a.Element.AnimateBase;a.Element.animateColor=\nfunction(c){this.base=a.Element.AnimateBase;this.base(c);this.calcValue=function(){var a=new RGBColor(this.attribute(\"from\").value),b=new RGBColor(this.attribute(\"to\").value);if(a.ok&&b.ok){var c=a.r+(b.r-a.r)*this.progress(),e=a.g+(b.g-a.g)*this.progress(),a=a.b+(b.b-a.b)*this.progress();return\"rgb(\"+parseInt(c,10)+\",\"+parseInt(e,10)+\",\"+parseInt(a,10)+\")\"}return this.attribute(\"from\").value}};a.Element.animateColor.prototype=new a.Element.AnimateBase;a.Element.animateTransform=function(c){this.base=\na.Element.animate;this.base(c)};a.Element.animateTransform.prototype=new a.Element.animate;a.Element.font=function(c){this.base=a.Element.ElementBase;this.base(c);this.horizAdvX=this.attribute(\"horiz-adv-x\").numValue();this.isArabic=this.isRTL=!1;this.missingGlyph=this.fontFace=null;this.glyphs=[];for(c=0;c<this.children.length;c++){var d=this.children[c];if(d.type==\"font-face\")this.fontFace=d,d.style(\"font-family\").hasValue()&&(a.Definitions[d.style(\"font-family\").value]=this);else if(d.type==\"missing-glyph\")this.missingGlyph=\nd;else if(d.type==\"glyph\")d.arabicForm!=\"\"?(this.isArabic=this.isRTL=!0,typeof this.glyphs[d.unicode]==\"undefined\"&&(this.glyphs[d.unicode]=[]),this.glyphs[d.unicode][d.arabicForm]=d):this.glyphs[d.unicode]=d}};a.Element.font.prototype=new a.Element.ElementBase;a.Element.fontface=function(c){this.base=a.Element.ElementBase;this.base(c);this.ascent=this.attribute(\"ascent\").value;this.descent=this.attribute(\"descent\").value;this.unitsPerEm=this.attribute(\"units-per-em\").numValue()};a.Element.fontface.prototype=\nnew a.Element.ElementBase;a.Element.missingglyph=function(c){this.base=a.Element.path;this.base(c);this.horizAdvX=0};a.Element.missingglyph.prototype=new a.Element.path;a.Element.glyph=function(c){this.base=a.Element.path;this.base(c);this.horizAdvX=this.attribute(\"horiz-adv-x\").numValue();this.unicode=this.attribute(\"unicode\").value;this.arabicForm=this.attribute(\"arabic-form\").value};a.Element.glyph.prototype=new a.Element.path;a.Element.text=function(c){this.base=a.Element.RenderedElementBase;\nthis.base(c);if(c!=null){this.children=[];for(var d=0;d<c.childNodes.length;d++){var b=c.childNodes[d];b.nodeType==1?this.addChild(b,!0):b.nodeType==3&&this.addChild(new a.Element.tspan(b),!1)}}this.baseSetContext=this.setContext;this.setContext=function(b){this.baseSetContext(b);if(this.style(\"dominant-baseline\").hasValue())b.textBaseline=this.style(\"dominant-baseline\").value;if(this.style(\"alignment-baseline\").hasValue())b.textBaseline=this.style(\"alignment-baseline\").value};this.renderChildren=\nfunction(b){for(var a=this.style(\"text-anchor\").valueOrDefault(\"start\"),c=this.attribute(\"x\").Length.toPixels(\"x\"),d=this.attribute(\"y\").Length.toPixels(\"y\"),j=0;j<this.children.length;j++){var h=this.children[j];h.attribute(\"x\").hasValue()?h.x=h.attribute(\"x\").Length.toPixels(\"x\"):(h.attribute(\"dx\").hasValue()&&(c+=h.attribute(\"dx\").Length.toPixels(\"x\")),h.x=c);c=h.measureText(b);if(a!=\"start\"&&(j==0||h.attribute(\"x\").hasValue())){for(var l=c,o=j+1;o<this.children.length;o++){var n=this.children[o];\nif(n.attribute(\"x\").hasValue())break;l+=n.measureText(b)}h.x-=a==\"end\"?l:l/2}c=h.x+c;h.attribute(\"y\").hasValue()?h.y=h.attribute(\"y\").Length.toPixels(\"y\"):(h.attribute(\"dy\").hasValue()&&(d+=h.attribute(\"dy\").Length.toPixels(\"y\")),h.y=d);d=h.y;h.render(b)}}};a.Element.text.prototype=new a.Element.RenderedElementBase;a.Element.TextElementBase=function(c){this.base=a.Element.RenderedElementBase;this.base(c);this.getGlyph=function(a,b,c){var e=b[c],f=null;if(a.isArabic){var g=\"isolated\";if((c==0||b[c-\n1]==\" \")&&c<b.length-2&&b[c+1]!=\" \")g=\"terminal\";c>0&&b[c-1]!=\" \"&&c<b.length-2&&b[c+1]!=\" \"&&(g=\"medial\");if(c>0&&b[c-1]!=\" \"&&(c==b.length-1||b[c+1]==\" \"))g=\"initial\";typeof a.glyphs[e]!=\"undefined\"&&(f=a.glyphs[e][g],f==null&&a.glyphs[e].type==\"glyph\"&&(f=a.glyphs[e]))}else f=a.glyphs[e];if(f==null)f=a.missingGlyph;return f};this.renderChildren=function(c){var b=this.parent.style(\"font-family\").Definition.getDefinition();if(b!=null){var k=this.parent.style(\"font-size\").numValueOrDefault(a.Font.Parse(a.ctx.font).fontSize),\ne=this.parent.style(\"font-style\").valueOrDefault(a.Font.Parse(a.ctx.font).fontStyle),f=this.getText();b.isRTL&&(f=f.split(\"\").reverse().join(\"\"));for(var g=a.ToNumberArray(this.parent.attribute(\"dx\").value),j=0;j<f.length;j++){var h=this.getGlyph(b,f,j),l=k/b.fontFace.unitsPerEm;c.translate(this.x,this.y);c.scale(l,-l);var o=c.lineWidth;c.lineWidth=c.lineWidth*b.fontFace.unitsPerEm/k;e==\"italic\"&&c.transform(1,0,0.4,1,0,0);h.render(c);e==\"italic\"&&c.transform(1,0,-0.4,1,0,0);c.lineWidth=o;c.scale(1/\nl,-1/l);c.translate(-this.x,-this.y);this.x+=k*(h.horizAdvX||b.horizAdvX)/b.fontFace.unitsPerEm;typeof g[j]!=\"undefined\"&&!isNaN(g[j])&&(this.x+=g[j])}}else c.strokeStyle!=\"\"&&c.strokeText(a.compressSpaces(this.getText()),this.x,this.y),c.fillStyle!=\"\"&&c.fillText(a.compressSpaces(this.getText()),this.x,this.y)};this.getText=function(){};this.measureText=function(c){var b=this.parent.style(\"font-family\").Definition.getDefinition();if(b!=null){var c=this.parent.style(\"font-size\").numValueOrDefault(a.Font.Parse(a.ctx.font).fontSize),\nk=0,e=this.getText();b.isRTL&&(e=e.split(\"\").reverse().join(\"\"));for(var f=a.ToNumberArray(this.parent.attribute(\"dx\").value),g=0;g<e.length;g++){var j=this.getGlyph(b,e,g);k+=(j.horizAdvX||b.horizAdvX)*c/b.fontFace.unitsPerEm;typeof f[g]!=\"undefined\"&&!isNaN(f[g])&&(k+=f[g])}return k}b=a.compressSpaces(this.getText());if(!c.measureText)return b.length*10;c.save();this.setContext(c);b=c.measureText(b).width;c.restore();return b}};a.Element.TextElementBase.prototype=new a.Element.RenderedElementBase;\na.Element.tspan=function(c){this.base=a.Element.TextElementBase;this.base(c);this.text=c.nodeType==3?c.nodeValue:c.childNodes.length>0?c.childNodes[0].nodeValue:c.text;this.getText=function(){return this.text}};a.Element.tspan.prototype=new a.Element.TextElementBase;a.Element.tref=function(c){this.base=a.Element.TextElementBase;this.base(c);this.getText=function(){var a=this.attribute(\"xlink:href\").Definition.getDefinition();if(a!=null)return a.children[0].getText()}};a.Element.tref.prototype=new a.Element.TextElementBase;\na.Element.a=function(c){this.base=a.Element.TextElementBase;this.base(c);this.hasText=!0;for(var d=0;d<c.childNodes.length;d++)if(c.childNodes[d].nodeType!=3)this.hasText=!1;this.text=this.hasText?c.childNodes[0].nodeValue:\"\";this.getText=function(){return this.text};this.baseRenderChildren=this.renderChildren;this.renderChildren=function(b){if(this.hasText){this.baseRenderChildren(b);var c=new a.Property(\"fontSize\",a.Font.Parse(a.ctx.font).fontSize);a.Mouse.checkBoundingBox(this,new a.BoundingBox(this.x,\nthis.y-c.Length.toPixels(\"y\"),this.x+this.measureText(b),this.y))}else c=new a.Element.g,c.children=this.children,c.parent=this,c.render(b)};this.onclick=function(){window.open(this.attribute(\"xlink:href\").value)};this.onmousemove=function(){a.ctx.canvas.style.cursor=\"pointer\"}};a.Element.a.prototype=new a.Element.TextElementBase;a.Element.image=function(c){this.base=a.Element.RenderedElementBase;this.base(c);a.Images.push(this);this.img=document.createElement(\"img\");this.loaded=!1;var d=this;this.img.onload=\nfunction(){d.loaded=!0};this.img.src=this.attribute(\"xlink:href\").value;this.renderChildren=function(b){var c=this.attribute(\"x\").Length.toPixels(\"x\"),d=this.attribute(\"y\").Length.toPixels(\"y\"),f=this.attribute(\"width\").Length.toPixels(\"x\"),g=this.attribute(\"height\").Length.toPixels(\"y\");f==0||g==0||(b.save(),b.translate(c,d),a.AspectRatio(b,this.attribute(\"preserveAspectRatio\").value,f,this.img.width,g,this.img.height,0,0),b.drawImage(this.img,0,0),b.restore())}};a.Element.image.prototype=new a.Element.RenderedElementBase;\na.Element.g=function(c){this.base=a.Element.RenderedElementBase;this.base(c);this.getBoundingBox=function(){for(var c=new a.BoundingBox,b=0;b<this.children.length;b++)c.addBoundingBox(this.children[b].getBoundingBox());return c}};a.Element.g.prototype=new a.Element.RenderedElementBase;a.Element.symbol=function(c){this.base=a.Element.RenderedElementBase;this.base(c);this.baseSetContext=this.setContext;this.setContext=function(c){this.baseSetContext(c);if(this.attribute(\"viewBox\").hasValue()){var b=\na.ToNumberArray(this.attribute(\"viewBox\").value),k=b[0],e=b[1];width=b[2];height=b[3];a.AspectRatio(c,this.attribute(\"preserveAspectRatio\").value,this.attribute(\"width\").Length.toPixels(\"x\"),width,this.attribute(\"height\").Length.toPixels(\"y\"),height,k,e);a.ViewPort.SetCurrent(b[2],b[3])}}};a.Element.symbol.prototype=new a.Element.RenderedElementBase;a.Element.style=function(c){this.base=a.Element.ElementBase;this.base(c);for(var c=c.childNodes[0].nodeValue+(c.childNodes.length>1?c.childNodes[1].nodeValue:\n\"\"),c=c.replace(/(\\/\\*([^*]|[\\r\\n]|(\\*+([^*\\/]|[\\r\\n])))*\\*+\\/)|(^[\\s]*\\/\\/.*)/gm,\"\"),c=a.compressSpaces(c),c=c.split(\"}\"),d=0;d<c.length;d++)if(a.trim(c[d])!=\"\")for(var b=c[d].split(\"{\"),k=b[0].split(\",\"),b=b[1].split(\";\"),e=0;e<k.length;e++){var f=a.trim(k[e]);if(f!=\"\"){for(var g={},j=0;j<b.length;j++){var h=b[j].indexOf(\":\"),l=b[j].substr(0,h),h=b[j].substr(h+1,b[j].length-h);l!=null&&h!=null&&(g[a.trim(l)]=new a.Property(a.trim(l),a.trim(h)))}a.Styles[f]=g;if(f==\"@font-face\"){f=g[\"font-family\"].value.replace(/\"/g,\n\"\");g=g.src.value.split(\",\");for(j=0;j<g.length;j++)if(g[j].indexOf('format(\"svg\")')>0){l=g[j].indexOf(\"url\");h=g[j].indexOf(\")\",l);l=g[j].substr(l+5,h-l-6);l=a.parseXml(a.ajax(l)).getElementsByTagName(\"font\");for(h=0;h<l.length;h++){var o=a.CreateElement(l[h]);a.Definitions[f]=o}}}}}};a.Element.style.prototype=new a.Element.ElementBase;a.Element.use=function(c){this.base=a.Element.RenderedElementBase;this.base(c);this.baseSetContext=this.setContext;this.setContext=function(a){this.baseSetContext(a);\nthis.attribute(\"x\").hasValue()&&a.translate(this.attribute(\"x\").Length.toPixels(\"x\"),0);this.attribute(\"y\").hasValue()&&a.translate(0,this.attribute(\"y\").Length.toPixels(\"y\"))};this.getDefinition=function(){var a=this.attribute(\"xlink:href\").Definition.getDefinition();if(this.attribute(\"width\").hasValue())a.attribute(\"width\",!0).value=this.attribute(\"width\").value;if(this.attribute(\"height\").hasValue())a.attribute(\"height\",!0).value=this.attribute(\"height\").value;return a};this.path=function(a){var b=\nthis.getDefinition();b!=null&&b.path(a)};this.renderChildren=function(a){var b=this.getDefinition();b!=null&&b.render(a)}};a.Element.use.prototype=new a.Element.RenderedElementBase;a.Element.mask=function(c){this.base=a.Element.ElementBase;this.base(c);this.apply=function(a,b){var c=this.attribute(\"x\").Length.toPixels(\"x\"),e=this.attribute(\"y\").Length.toPixels(\"y\"),f=this.attribute(\"width\").Length.toPixels(\"x\"),g=this.attribute(\"height\").Length.toPixels(\"y\"),j=b.attribute(\"mask\").value;b.attribute(\"mask\").value=\n\"\";var h=document.createElement(\"canvas\");h.width=c+f;h.height=e+g;var l=h.getContext(\"2d\");this.renderChildren(l);var o=document.createElement(\"canvas\");o.width=c+f;o.height=e+g;var n=o.getContext(\"2d\");b.render(n);n.globalCompositeOperation=\"destination-in\";n.fillStyle=l.createPattern(h,\"no-repeat\");n.fillRect(0,0,c+f,e+g);a.fillStyle=n.createPattern(o,\"no-repeat\");a.fillRect(0,0,c+f,e+g);b.attribute(\"mask\").value=j};this.render=function(){}};a.Element.mask.prototype=new a.Element.ElementBase;a.Element.clipPath=\nfunction(c){this.base=a.Element.ElementBase;this.base(c);this.apply=function(a){for(var b=0;b<this.children.length;b++)this.children[b].path&&(this.children[b].path(a),a.clip())};this.render=function(){}};a.Element.clipPath.prototype=new a.Element.ElementBase;a.Element.filter=function(c){this.base=a.Element.ElementBase;this.base(c);this.apply=function(a,b){var c=b.getBoundingBox(),e=this.attribute(\"x\").Length.toPixels(\"x\"),f=this.attribute(\"y\").Length.toPixels(\"y\");if(e==0||f==0)e=c.x1,f=c.y1;var g=\nthis.attribute(\"width\").Length.toPixels(\"x\"),j=this.attribute(\"height\").Length.toPixels(\"y\");if(g==0||j==0)g=c.width(),j=c.height();c=b.style(\"filter\").value;b.style(\"filter\").value=\"\";var h=0.2*g,l=0.2*j,o=document.createElement(\"canvas\");o.width=g+2*h;o.height=j+2*l;var n=o.getContext(\"2d\");n.translate(-e+h,-f+l);b.render(n);for(var q=0;q<this.children.length;q++)this.children[q].apply(n,0,0,g+2*h,j+2*l);a.drawImage(o,0,0,g+2*h,j+2*l,e-h,f-l,g+2*h,j+2*l);b.style(\"filter\",!0).value=c};this.render=\nfunction(){}};a.Element.filter.prototype=new a.Element.ElementBase;a.Element.feGaussianBlur=function(c){function d(a,c,d,f,g){for(var j=0;j<g;j++)for(var h=0;h<f;h++)for(var l=a[j*f*4+h*4+3]/255,o=0;o<4;o++){for(var n=d[0]*(l==0?255:a[j*f*4+h*4+o])*(l==0||o==3?1:l),q=1;q<d.length;q++){var p=Math.max(h-q,0),m=a[j*f*4+p*4+3]/255,p=Math.min(h+q,f-1),p=a[j*f*4+p*4+3]/255,s=d[q],r;m==0?r=255:(r=Math.max(h-q,0),r=a[j*f*4+r*4+o]);m=r*(m==0||o==3?1:m);p==0?r=255:(r=Math.min(h+q,f-1),r=a[j*f*4+r*4+o]);n+=\ns*(m+r*(p==0||o==3?1:p))}c[h*g*4+j*4+o]=n}}this.base=a.Element.ElementBase;this.base(c);this.apply=function(a,c,e,f,g){var e=this.attribute(\"stdDeviation\").numValue(),c=a.getImageData(0,0,f,g),e=Math.max(e,0.01),j=Math.ceil(e*4)+1;mask=[];for(var h=0;h<j;h++)mask[h]=Math.exp(-0.5*(h/e)*(h/e));e=mask;j=0;for(h=1;h<e.length;h++)j+=Math.abs(e[h]);j=2*j+Math.abs(e[0]);for(h=0;h<e.length;h++)e[h]/=j;tmp=[];d(c.data,tmp,e,f,g);d(tmp,c.data,e,g,f);a.clearRect(0,0,f,g);a.putImageData(c,0,0)}};a.Element.filter.prototype=\nnew a.Element.feGaussianBlur;a.Element.title=function(){};a.Element.title.prototype=new a.Element.ElementBase;a.Element.desc=function(){};a.Element.desc.prototype=new a.Element.ElementBase;a.Element.MISSING=function(a){console.log(\"ERROR: Element '\"+a.nodeName+\"' not yet implemented.\")};a.Element.MISSING.prototype=new a.Element.ElementBase;a.CreateElement=function(c){var d=c.nodeName.replace(/^[^:]+:/,\"\"),d=d.replace(/\\-/g,\"\"),b=null,b=typeof a.Element[d]!=\"undefined\"?new a.Element[d](c):new a.Element.MISSING(c);\nb.type=c.nodeName;return b};a.load=function(c,d){a.loadXml(c,a.ajax(d))};a.loadXml=function(c,d){a.loadXmlDoc(c,a.parseXml(d))};a.loadXmlDoc=function(c,d){a.init(c);var b=function(a){for(var b=c.canvas;b;)a.x-=b.offsetLeft,a.y-=b.offsetTop,b=b.offsetParent;window.scrollX&&(a.x+=window.scrollX);window.scrollY&&(a.y+=window.scrollY);return a};if(a.opts.ignoreMouse!=!0)c.canvas.onclick=function(c){c=b(new a.Point(c!=null?c.clientX:event.clientX,c!=null?c.clientY:event.clientY));a.Mouse.onclick(c.x,c.y)},\nc.canvas.onmousemove=function(c){c=b(new a.Point(c!=null?c.clientX:event.clientX,c!=null?c.clientY:event.clientY));a.Mouse.onmousemove(c.x,c.y)};var k=a.CreateElement(d.documentElement),e=k.root=!0,f=function(){a.ViewPort.Clear();c.canvas.parentNode&&a.ViewPort.SetCurrent(c.canvas.parentNode.clientWidth,c.canvas.parentNode.clientHeight);if(a.opts.ignoreDimensions!=!0){if(k.style(\"width\").hasValue())c.canvas.width=k.style(\"width\").Length.toPixels(\"x\"),c.canvas.style.width=c.canvas.width+\"px\";if(k.style(\"height\").hasValue())c.canvas.height=\nk.style(\"height\").Length.toPixels(\"y\"),c.canvas.style.height=c.canvas.height+\"px\"}var b=c.canvas.clientWidth||c.canvas.width,d=c.canvas.clientHeight||c.canvas.height;a.ViewPort.SetCurrent(b,d);if(a.opts!=null&&a.opts.offsetX!=null)k.attribute(\"x\",!0).value=a.opts.offsetX;if(a.opts!=null&&a.opts.offsetY!=null)k.attribute(\"y\",!0).value=a.opts.offsetY;if(a.opts!=null&&a.opts.scaleWidth!=null&&a.opts.scaleHeight!=null){var f=1,g=1;k.attribute(\"width\").hasValue()&&(f=k.attribute(\"width\").Length.toPixels(\"x\")/\na.opts.scaleWidth);k.attribute(\"height\").hasValue()&&(g=k.attribute(\"height\").Length.toPixels(\"y\")/a.opts.scaleHeight);k.attribute(\"width\",!0).value=a.opts.scaleWidth;k.attribute(\"height\",!0).value=a.opts.scaleHeight;k.attribute(\"viewBox\",!0).value=\"0 0 \"+b*f+\" \"+d*g;k.attribute(\"preserveAspectRatio\",!0).value=\"none\"}a.opts.ignoreClear!=!0&&c.clearRect(0,0,b,d);k.render(c);e&&(e=!1,a.opts!=null&&typeof a.opts.renderCallback==\"function\"&&a.opts.renderCallback())},g=!0;a.ImagesLoaded()&&(g=!1,f());\na.intervalID=setInterval(function(){var b=!1;g&&a.ImagesLoaded()&&(g=!1,b=!0);a.opts.ignoreMouse!=!0&&(b|=a.Mouse.hasEvents());if(a.opts.ignoreAnimation!=!0)for(var c=0;c<a.Animations.length;c++)b|=a.Animations[c].update(1E3/a.FRAMERATE);a.opts!=null&&typeof a.opts.forceRedraw==\"function\"&&a.opts.forceRedraw()==!0&&(b=!0);b&&(f(),a.Mouse.runEvents())},1E3/a.FRAMERATE)};a.stop=function(){a.intervalID&&clearInterval(a.intervalID)};a.Mouse=new function(){this.events=[];this.hasEvents=function(){return this.events.length!=\n0};this.onclick=function(a,d){this.events.push({type:\"onclick\",x:a,y:d,run:function(a){if(a.onclick)a.onclick()}})};this.onmousemove=function(a,d){this.events.push({type:\"onmousemove\",x:a,y:d,run:function(a){if(a.onmousemove)a.onmousemove()}})};this.eventElements=[];this.checkPath=function(a,d){for(var b=0;b<this.events.length;b++){var k=this.events[b];d.isPointInPath&&d.isPointInPath(k.x,k.y)&&(this.eventElements[b]=a)}};this.checkBoundingBox=function(a,d){for(var b=0;b<this.events.length;b++){var k=\nthis.events[b];d.isPointInBox(k.x,k.y)&&(this.eventElements[b]=a)}};this.runEvents=function(){a.ctx.canvas.style.cursor=\"\";for(var c=0;c<this.events.length;c++)for(var d=this.events[c],b=this.eventElements[c];b;)d.run(b),b=b.parent;this.events=[];this.eventElements=[]}};return a}this.canvg=function(a,c,d){if(a==null&&c==null&&d==null)for(var c=document.getElementsByTagName(\"svg\"),b=0;b<c.length;b++){a=c[b];d=document.createElement(\"canvas\");d.width=a.clientWidth;d.height=a.clientHeight;a.parentNode.insertBefore(d,\na);a.parentNode.removeChild(a);var k=document.createElement(\"div\");k.appendChild(a);canvg(d,k.innerHTML)}else d=d||{},typeof a==\"string\"&&(a=document.getElementById(a)),a.svg==null?(b=m(),a.svg=b):(b=a.svg,b.stop()),b.opts=d,a=a.getContext(\"2d\"),typeof c.documentElement!=\"undefined\"?b.loadXmlDoc(a,c):c.substr(0,1)==\"<\"?b.loadXml(a,c):b.load(a,c)}})();\nif(CanvasRenderingContext2D)CanvasRenderingContext2D.prototype.drawSvg=function(m,a,c,d,b){canvg(this.canvas,m,{ignoreMouse:!0,ignoreAnimation:!0,ignoreDimensions:!0,ignoreClear:!0,offsetX:a,offsetY:c,scaleWidth:d,scaleHeight:b})};\n(function(m){var a=m.css,c=m.CanVGRenderer,d=m.SVGRenderer,b=m.extend,k=m.merge,e=m.addEvent,f=m.createElement,g=m.discardElement;b(c.prototype,d.prototype);b(c.prototype,{create:function(a,b,c,d){this.setContainer(b,c,d);this.configure(a)},setContainer:function(a,b,c){var d=a.style,e=a.parentNode,g=d.left,d=d.top,k=a.offsetWidth,m=a.offsetHeight,s={visibility:\"hidden\",position:\"absolute\"};this.init.apply(this,[a,b,c]);this.canvas=f(\"canvas\",{width:k,height:m},{position:\"relative\",left:g,top:d},a);\nthis.ttLine=f(\"div\",null,s,e);this.ttDiv=f(\"div\",null,s,e);this.ttTimer=void 0;this.hiddenSvg=a=f(\"div\",{width:k,height:m},{visibility:\"hidden\",left:g,top:d},e);a.appendChild(this.box)},configure:function(b){var c=this,d=b.options.tooltip,f=d.borderWidth,g=c.ttDiv,m=d.style,p=c.ttLine,t=parseInt(m.padding,10),m=k(m,{padding:t+\"px\",\"background-color\":d.backgroundColor,\"border-style\":\"solid\",\"border-width\":f+\"px\",\"border-radius\":d.borderRadius+\"px\"});d.shadow&&(m=k(m,{\"box-shadow\":\"1px 1px 3px gray\",\n\"-webkit-box-shadow\":\"1px 1px 3px gray\"}));a(g,m);a(p,{\"border-left\":\"1px solid darkgray\"});e(b,\"tooltipRefresh\",function(d){var e=b.container,f=e.offsetLeft,e=e.offsetTop,k;g.innerHTML=d.text;k=b.tooltip.getPosition(g.offsetWidth,g.offsetHeight,{plotX:d.x,plotY:d.y});a(g,{visibility:\"visible\",left:k.x+\"px\",top:k.y+\"px\",\"border-color\":d.borderColor});a(p,{visibility:\"visible\",left:f+d.x+\"px\",top:e+b.plotTop+\"px\",height:b.plotHeight+\"px\"});c.ttTimer!==void 0&&clearTimeout(c.ttTimer);c.ttTimer=setTimeout(function(){a(g,\n{visibility:\"hidden\"});a(p,{visibility:\"hidden\"})},3E3)})},destroy:function(){g(this.canvas);this.ttTimer!==void 0&&clearTimeout(this.ttTimer);g(this.ttLine);g(this.ttDiv);g(this.hiddenSvg);return d.prototype.destroy.apply(this)},color:function(a,b,c){a&&a.linearGradient&&(a=a.stops[a.stops.length-1][1]);return d.prototype.color.call(this,a,b,c)},draw:function(){window.canvg(this.canvas,this.hiddenSvg.innerHTML)}})})(Highcharts);\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/highcharts/modules/canvas-tools.src.js",
    "content": "/**\n * @license A class to parse color values\n * @author Stoyan Stefanov <sstoo@gmail.com>\n * @link   http://www.phpied.com/rgb-color-parser-in-javascript/\n * Use it if you like it\n *\n */\nfunction RGBColor(color_string)\n{\n    this.ok = false;\n\n    // strip any leading #\n    if (color_string.charAt(0) == '#') { // remove # if any\n        color_string = color_string.substr(1,6);\n    }\n\n    color_string = color_string.replace(/ /g,'');\n    color_string = color_string.toLowerCase();\n\n    // before getting into regexps, try simple matches\n    // and overwrite the input\n    var simple_colors = {\n        aliceblue: 'f0f8ff',\n        antiquewhite: 'faebd7',\n        aqua: '00ffff',\n        aquamarine: '7fffd4',\n        azure: 'f0ffff',\n        beige: 'f5f5dc',\n        bisque: 'ffe4c4',\n        black: '000000',\n        blanchedalmond: 'ffebcd',\n        blue: '0000ff',\n        blueviolet: '8a2be2',\n        brown: 'a52a2a',\n        burlywood: 'deb887',\n        cadetblue: '5f9ea0',\n        chartreuse: '7fff00',\n        chocolate: 'd2691e',\n        coral: 'ff7f50',\n        cornflowerblue: '6495ed',\n        cornsilk: 'fff8dc',\n        crimson: 'dc143c',\n        cyan: '00ffff',\n        darkblue: '00008b',\n        darkcyan: '008b8b',\n        darkgoldenrod: 'b8860b',\n        darkgray: 'a9a9a9',\n        darkgreen: '006400',\n        darkkhaki: 'bdb76b',\n        darkmagenta: '8b008b',\n        darkolivegreen: '556b2f',\n        darkorange: 'ff8c00',\n        darkorchid: '9932cc',\n        darkred: '8b0000',\n        darksalmon: 'e9967a',\n        darkseagreen: '8fbc8f',\n        darkslateblue: '483d8b',\n        darkslategray: '2f4f4f',\n        darkturquoise: '00ced1',\n        darkviolet: '9400d3',\n        deeppink: 'ff1493',\n        deepskyblue: '00bfff',\n        dimgray: '696969',\n        dodgerblue: '1e90ff',\n        feldspar: 'd19275',\n        firebrick: 'b22222',\n        floralwhite: 'fffaf0',\n        forestgreen: '228b22',\n        fuchsia: 'ff00ff',\n        gainsboro: 'dcdcdc',\n        ghostwhite: 'f8f8ff',\n        gold: 'ffd700',\n        goldenrod: 'daa520',\n        gray: '808080',\n        green: '008000',\n        greenyellow: 'adff2f',\n        honeydew: 'f0fff0',\n        hotpink: 'ff69b4',\n        indianred : 'cd5c5c',\n        indigo : '4b0082',\n        ivory: 'fffff0',\n        khaki: 'f0e68c',\n        lavender: 'e6e6fa',\n        lavenderblush: 'fff0f5',\n        lawngreen: '7cfc00',\n        lemonchiffon: 'fffacd',\n        lightblue: 'add8e6',\n        lightcoral: 'f08080',\n        lightcyan: 'e0ffff',\n        lightgoldenrodyellow: 'fafad2',\n        lightgrey: 'd3d3d3',\n        lightgreen: '90ee90',\n        lightpink: 'ffb6c1',\n        lightsalmon: 'ffa07a',\n        lightseagreen: '20b2aa',\n        lightskyblue: '87cefa',\n        lightslateblue: '8470ff',\n        lightslategray: '778899',\n        lightsteelblue: 'b0c4de',\n        lightyellow: 'ffffe0',\n        lime: '00ff00',\n        limegreen: '32cd32',\n        linen: 'faf0e6',\n        magenta: 'ff00ff',\n        maroon: '800000',\n        mediumaquamarine: '66cdaa',\n        mediumblue: '0000cd',\n        mediumorchid: 'ba55d3',\n        mediumpurple: '9370d8',\n        mediumseagreen: '3cb371',\n        mediumslateblue: '7b68ee',\n        mediumspringgreen: '00fa9a',\n        mediumturquoise: '48d1cc',\n        mediumvioletred: 'c71585',\n        midnightblue: '191970',\n        mintcream: 'f5fffa',\n        mistyrose: 'ffe4e1',\n        moccasin: 'ffe4b5',\n        navajowhite: 'ffdead',\n        navy: '000080',\n        oldlace: 'fdf5e6',\n        olive: '808000',\n        olivedrab: '6b8e23',\n        orange: 'ffa500',\n        orangered: 'ff4500',\n        orchid: 'da70d6',\n        palegoldenrod: 'eee8aa',\n        palegreen: '98fb98',\n        paleturquoise: 'afeeee',\n        palevioletred: 'd87093',\n        papayawhip: 'ffefd5',\n        peachpuff: 'ffdab9',\n        peru: 'cd853f',\n        pink: 'ffc0cb',\n        plum: 'dda0dd',\n        powderblue: 'b0e0e6',\n        purple: '800080',\n        red: 'ff0000',\n        rosybrown: 'bc8f8f',\n        royalblue: '4169e1',\n        saddlebrown: '8b4513',\n        salmon: 'fa8072',\n        sandybrown: 'f4a460',\n        seagreen: '2e8b57',\n        seashell: 'fff5ee',\n        sienna: 'a0522d',\n        silver: 'c0c0c0',\n        skyblue: '87ceeb',\n        slateblue: '6a5acd',\n        slategray: '708090',\n        snow: 'fffafa',\n        springgreen: '00ff7f',\n        steelblue: '4682b4',\n        tan: 'd2b48c',\n        teal: '008080',\n        thistle: 'd8bfd8',\n        tomato: 'ff6347',\n        turquoise: '40e0d0',\n        violet: 'ee82ee',\n        violetred: 'd02090',\n        wheat: 'f5deb3',\n        white: 'ffffff',\n        whitesmoke: 'f5f5f5',\n        yellow: 'ffff00',\n        yellowgreen: '9acd32'\n    };\n    for (var key in simple_colors) {\n        if (color_string == key) {\n            color_string = simple_colors[key];\n        }\n    }\n    // emd of simple type-in colors\n\n    // array of color definition objects\n    var color_defs = [\n        {\n            re: /^rgb\\((\\d{1,3}),\\s*(\\d{1,3}),\\s*(\\d{1,3})\\)$/,\n            example: ['rgb(123, 234, 45)', 'rgb(255,234,245)'],\n            process: function (bits){\n                return [\n                    parseInt(bits[1]),\n                    parseInt(bits[2]),\n                    parseInt(bits[3])\n                ];\n            }\n        },\n        {\n            re: /^(\\w{2})(\\w{2})(\\w{2})$/,\n            example: ['#00ff00', '336699'],\n            process: function (bits){\n                return [\n                    parseInt(bits[1], 16),\n                    parseInt(bits[2], 16),\n                    parseInt(bits[3], 16)\n                ];\n            }\n        },\n        {\n            re: /^(\\w{1})(\\w{1})(\\w{1})$/,\n            example: ['#fb0', 'f0f'],\n            process: function (bits){\n                return [\n                    parseInt(bits[1] + bits[1], 16),\n                    parseInt(bits[2] + bits[2], 16),\n                    parseInt(bits[3] + bits[3], 16)\n                ];\n            }\n        }\n    ];\n\n    // search through the definitions to find a match\n    for (var i = 0; i < color_defs.length; i++) {\n        var re = color_defs[i].re;\n        var processor = color_defs[i].process;\n        var bits = re.exec(color_string);\n        if (bits) {\n            channels = processor(bits);\n            this.r = channels[0];\n            this.g = channels[1];\n            this.b = channels[2];\n            this.ok = true;\n        }\n\n    }\n\n    // validate/cleanup values\n    this.r = (this.r < 0 || isNaN(this.r)) ? 0 : ((this.r > 255) ? 255 : this.r);\n    this.g = (this.g < 0 || isNaN(this.g)) ? 0 : ((this.g > 255) ? 255 : this.g);\n    this.b = (this.b < 0 || isNaN(this.b)) ? 0 : ((this.b > 255) ? 255 : this.b);\n\n    // some getters\n    this.toRGB = function () {\n        return 'rgb(' + this.r + ', ' + this.g + ', ' + this.b + ')';\n    }\n    this.toHex = function () {\n        var r = this.r.toString(16);\n        var g = this.g.toString(16);\n        var b = this.b.toString(16);\n        if (r.length == 1) r = '0' + r;\n        if (g.length == 1) g = '0' + g;\n        if (b.length == 1) b = '0' + b;\n        return '#' + r + g + b;\n    }\n\n    // help\n    this.getHelpXML = function () {\n\n        var examples = new Array();\n        // add regexps\n        for (var i = 0; i < color_defs.length; i++) {\n            var example = color_defs[i].example;\n            for (var j = 0; j < example.length; j++) {\n                examples[examples.length] = example[j];\n            }\n        }\n        // add type-in colors\n        for (var sc in simple_colors) {\n            examples[examples.length] = sc;\n        }\n\n        var xml = document.createElement('ul');\n        xml.setAttribute('id', 'rgbcolor-examples');\n        for (var i = 0; i < examples.length; i++) {\n            try {\n                var list_item = document.createElement('li');\n                var list_color = new RGBColor(examples[i]);\n                var example_div = document.createElement('div');\n                example_div.style.cssText =\n                        'margin: 3px; '\n                        + 'border: 1px solid black; '\n                        + 'background:' + list_color.toHex() + '; '\n                        + 'color:' + list_color.toHex()\n                ;\n                example_div.appendChild(document.createTextNode('test'));\n                var list_item_value = document.createTextNode(\n                    ' ' + examples[i] + ' -> ' + list_color.toRGB() + ' -> ' + list_color.toHex()\n                );\n                list_item.appendChild(example_div);\n                list_item.appendChild(list_item_value);\n                xml.appendChild(list_item);\n\n            } catch(e){}\n        }\n        return xml;\n\n    }\n\n}\n\n/**\n * @license canvg.js - Javascript SVG parser and renderer on Canvas\n * MIT Licensed \n * Gabe Lerner (gabelerner@gmail.com)\n * http://code.google.com/p/canvg/\n *\n * Requires: rgbcolor.js - http://www.phpied.com/rgb-color-parser-in-javascript/\n *\n */\nif(!window.console) {\n\twindow.console = {};\n\twindow.console.log = function(str) {};\n\twindow.console.dir = function(str) {};\n}\n\nif(!Array.prototype.indexOf){\n\tArray.prototype.indexOf = function(obj){\n\t\tfor(var i=0; i<this.length; i++){\n\t\t\tif(this[i]==obj){\n\t\t\t\treturn i;\n\t\t\t}\n\t\t}\n\t\treturn -1;\n\t}\n}\n\n(function(){\n\t// canvg(target, s)\n\t// empty parameters: replace all 'svg' elements on page with 'canvas' elements\n\t// target: canvas element or the id of a canvas element\n\t// s: svg string, url to svg file, or xml document\n\t// opts: optional hash of options\n\t//\t\t ignoreMouse: true => ignore mouse events\n\t//\t\t ignoreAnimation: true => ignore animations\n\t//\t\t ignoreDimensions: true => does not try to resize canvas\n\t//\t\t ignoreClear: true => does not clear canvas\n\t//\t\t offsetX: int => draws at a x offset\n\t//\t\t offsetY: int => draws at a y offset\n\t//\t\t scaleWidth: int => scales horizontally to width\n\t//\t\t scaleHeight: int => scales vertically to height\n\t//\t\t renderCallback: function => will call the function after the first render is completed\n\t//\t\t forceRedraw: function => will call the function on every frame, if it returns true, will redraw\n\tthis.canvg = function (target, s, opts) {\n\t\t// no parameters\n\t\tif (target == null && s == null && opts == null) {\n\t\t\tvar svgTags = document.getElementsByTagName('svg');\n\t\t\tfor (var i=0; i<svgTags.length; i++) {\n\t\t\t\tvar svgTag = svgTags[i];\n\t\t\t\tvar c = document.createElement('canvas');\n\t\t\t\tc.width = svgTag.clientWidth;\n\t\t\t\tc.height = svgTag.clientHeight;\n\t\t\t\tsvgTag.parentNode.insertBefore(c, svgTag);\n\t\t\t\tsvgTag.parentNode.removeChild(svgTag);\n\t\t\t\tvar div = document.createElement('div');\n\t\t\t\tdiv.appendChild(svgTag);\n\t\t\t\tcanvg(c, div.innerHTML);\n\t\t\t}\n\t\t\treturn;\n\t\t}\t\n\t\topts = opts || {};\n\t\n\t\tif (typeof target == 'string') {\n\t\t\ttarget = document.getElementById(target);\n\t\t}\n\t\t\n\t\t// reuse class per canvas\n\t\tvar svg;\n\t\tif (target.svg == null) {\n\t\t\tsvg = build();\n\t\t\ttarget.svg = svg;\n\t\t}\n\t\telse {\n\t\t\tsvg = target.svg;\n\t\t\tsvg.stop();\n\t\t}\n\t\tsvg.opts = opts;\n\t\t\n\t\tvar ctx = target.getContext('2d');\n\t\tif (typeof(s.documentElement) != 'undefined') {\n\t\t\t// load from xml doc\n\t\t\tsvg.loadXmlDoc(ctx, s);\n\t\t}\n\t\telse if (s.substr(0,1) == '<') {\n\t\t\t// load from xml string\n\t\t\tsvg.loadXml(ctx, s);\n\t\t}\n\t\telse {\n\t\t\t// load from url\n\t\t\tsvg.load(ctx, s);\n\t\t}\n\t}\n\n\tfunction build() {\n\t\tvar svg = { };\n\t\t\n\t\tsvg.FRAMERATE = 30;\n\t\tsvg.MAX_VIRTUAL_PIXELS = 30000;\n\t\t\n\t\t// globals\n\t\tsvg.init = function(ctx) {\n\t\t\tsvg.Definitions = {};\n\t\t\tsvg.Styles = {};\n\t\t\tsvg.Animations = [];\n\t\t\tsvg.Images = [];\n\t\t\tsvg.ctx = ctx;\n\t\t\tsvg.ViewPort = new (function () {\n\t\t\t\tthis.viewPorts = [];\n\t\t\t\tthis.Clear = function() { this.viewPorts = []; }\n\t\t\t\tthis.SetCurrent = function(width, height) { this.viewPorts.push({ width: width, height: height }); }\n\t\t\t\tthis.RemoveCurrent = function() { this.viewPorts.pop(); }\n\t\t\t\tthis.Current = function() { return this.viewPorts[this.viewPorts.length - 1]; }\n\t\t\t\tthis.width = function() { return this.Current().width; }\n\t\t\t\tthis.height = function() { return this.Current().height; }\n\t\t\t\tthis.ComputeSize = function(d) {\n\t\t\t\t\tif (d != null && typeof(d) == 'number') return d;\n\t\t\t\t\tif (d == 'x') return this.width();\n\t\t\t\t\tif (d == 'y') return this.height();\n\t\t\t\t\treturn Math.sqrt(Math.pow(this.width(), 2) + Math.pow(this.height(), 2)) / Math.sqrt(2);\t\t\t\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t\tsvg.init();\n\t\t\n\t\t// images loaded\n\t\tsvg.ImagesLoaded = function() { \n\t\t\tfor (var i=0; i<svg.Images.length; i++) {\n\t\t\t\tif (!svg.Images[i].loaded) return false;\n\t\t\t}\n\t\t\treturn true;\n\t\t}\n\n\t\t// trim\n\t\tsvg.trim = function(s) { return s.replace(/^\\s+|\\s+$/g, ''); }\n\t\t\n\t\t// compress spaces\n\t\tsvg.compressSpaces = function(s) { return s.replace(/[\\s\\r\\t\\n]+/gm,' '); }\n\t\t\n\t\t// ajax\n\t\tsvg.ajax = function(url) {\n\t\t\tvar AJAX;\n\t\t\tif(window.XMLHttpRequest){AJAX=new XMLHttpRequest();}\n\t\t\telse{AJAX=new ActiveXObject('Microsoft.XMLHTTP');}\n\t\t\tif(AJAX){\n\t\t\t   AJAX.open('GET',url,false);\n\t\t\t   AJAX.send(null);\n\t\t\t   return AJAX.responseText;\n\t\t\t}\n\t\t\treturn null;\n\t\t} \n\t\t\n\t\t// parse xml\n\t\tsvg.parseXml = function(xml) {\n\t\t\tif (window.DOMParser)\n\t\t\t{\n\t\t\t\tvar parser = new DOMParser();\n\t\t\t\treturn parser.parseFromString(xml, 'text/xml');\n\t\t\t}\n\t\t\telse \n\t\t\t{\n\t\t\t\txml = xml.replace(/<!DOCTYPE svg[^>]*>/, '');\n\t\t\t\tvar xmlDoc = new ActiveXObject('Microsoft.XMLDOM');\n\t\t\t\txmlDoc.async = 'false';\n\t\t\t\txmlDoc.loadXML(xml); \n\t\t\t\treturn xmlDoc;\n\t\t\t}\t\t\n\t\t}\n\t\t\n\t\tsvg.Property = function(name, value) {\n\t\t\tthis.name = name;\n\t\t\tthis.value = value;\n\t\t\t\n\t\t\tthis.hasValue = function() {\n\t\t\t\treturn (this.value != null && this.value !== '');\n\t\t\t}\n\t\t\t\t\t\t\t\n\t\t\t// return the numerical value of the property\n\t\t\tthis.numValue = function() {\n\t\t\t\tif (!this.hasValue()) return 0;\n\t\t\t\t\n\t\t\t\tvar n = parseFloat(this.value);\n\t\t\t\tif ((this.value + '').match(/%$/)) {\n\t\t\t\t\tn = n / 100.0;\n\t\t\t\t}\n\t\t\t\treturn n;\n\t\t\t}\n\t\t\t\n\t\t\tthis.valueOrDefault = function(def) {\n\t\t\t\tif (this.hasValue()) return this.value;\n\t\t\t\treturn def;\n\t\t\t}\n\t\t\t\n\t\t\tthis.numValueOrDefault = function(def) {\n\t\t\t\tif (this.hasValue()) return this.numValue();\n\t\t\t\treturn def;\n\t\t\t}\n\t\t\t\n\t\t\t/* EXTENSIONS */\n\t\t\tvar that = this;\n\t\t\t\n\t\t\t// color extensions\n\t\t\tthis.Color = {\n\t\t\t\t// augment the current color value with the opacity\n\t\t\t\taddOpacity: function(opacity) {\n\t\t\t\t\tvar newValue = that.value;\n\t\t\t\t\tif (opacity != null && opacity != '') {\n\t\t\t\t\t\tvar color = new RGBColor(that.value);\n\t\t\t\t\t\tif (color.ok) {\n\t\t\t\t\t\t\tnewValue = 'rgba(' + color.r + ', ' + color.g + ', ' + color.b + ', ' + opacity + ')';\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn new svg.Property(that.name, newValue);\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\t// definition extensions\n\t\t\tthis.Definition = {\n\t\t\t\t// get the definition from the definitions table\n\t\t\t\tgetDefinition: function() {\n\t\t\t\t\tvar name = that.value.replace(/^(url\\()?#([^\\)]+)\\)?$/, '$2');\n\t\t\t\t\treturn svg.Definitions[name];\n\t\t\t\t},\n\t\t\t\t\n\t\t\t\tisUrl: function() {\n\t\t\t\t\treturn that.value.indexOf('url(') == 0\n\t\t\t\t},\n\t\t\t\t\n\t\t\t\tgetFillStyle: function(e) {\n\t\t\t\t\tvar def = this.getDefinition();\n\t\t\t\t\t\n\t\t\t\t\t// gradient\n\t\t\t\t\tif (def != null && def.createGradient) {\n\t\t\t\t\t\treturn def.createGradient(svg.ctx, e);\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\t// pattern\n\t\t\t\t\tif (def != null && def.createPattern) {\n\t\t\t\t\t\treturn def.createPattern(svg.ctx, e);\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\treturn null;\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\t// length extensions\n\t\t\tthis.Length = {\n\t\t\t\tDPI: function(viewPort) {\n\t\t\t\t\treturn 96.0; // TODO: compute?\n\t\t\t\t},\n\t\t\t\t\n\t\t\t\tEM: function(viewPort) {\n\t\t\t\t\tvar em = 12;\n\t\t\t\t\t\n\t\t\t\t\tvar fontSize = new svg.Property('fontSize', svg.Font.Parse(svg.ctx.font).fontSize);\n\t\t\t\t\tif (fontSize.hasValue()) em = fontSize.Length.toPixels(viewPort);\n\t\t\t\t\t\n\t\t\t\t\treturn em;\n\t\t\t\t},\n\t\t\t\n\t\t\t\t// get the length as pixels\n\t\t\t\ttoPixels: function(viewPort) {\n\t\t\t\t\tif (!that.hasValue()) return 0;\n\t\t\t\t\tvar s = that.value+'';\n\t\t\t\t\tif (s.match(/em$/)) return that.numValue() * this.EM(viewPort);\n\t\t\t\t\tif (s.match(/ex$/)) return that.numValue() * this.EM(viewPort) / 2.0;\n\t\t\t\t\tif (s.match(/px$/)) return that.numValue();\n\t\t\t\t\tif (s.match(/pt$/)) return that.numValue() * 1.25;\n\t\t\t\t\tif (s.match(/pc$/)) return that.numValue() * 15;\n\t\t\t\t\tif (s.match(/cm$/)) return that.numValue() * this.DPI(viewPort) / 2.54;\n\t\t\t\t\tif (s.match(/mm$/)) return that.numValue() * this.DPI(viewPort) / 25.4;\n\t\t\t\t\tif (s.match(/in$/)) return that.numValue() * this.DPI(viewPort);\n\t\t\t\t\tif (s.match(/%$/)) return that.numValue() * svg.ViewPort.ComputeSize(viewPort);\n\t\t\t\t\treturn that.numValue();\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\t// time extensions\n\t\t\tthis.Time = {\n\t\t\t\t// get the time as milliseconds\n\t\t\t\ttoMilliseconds: function() {\n\t\t\t\t\tif (!that.hasValue()) return 0;\n\t\t\t\t\tvar s = that.value+'';\n\t\t\t\t\tif (s.match(/s$/)) return that.numValue() * 1000;\n\t\t\t\t\tif (s.match(/ms$/)) return that.numValue();\n\t\t\t\t\treturn that.numValue();\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\t// angle extensions\n\t\t\tthis.Angle = {\n\t\t\t\t// get the angle as radians\n\t\t\t\ttoRadians: function() {\n\t\t\t\t\tif (!that.hasValue()) return 0;\n\t\t\t\t\tvar s = that.value+'';\n\t\t\t\t\tif (s.match(/deg$/)) return that.numValue() * (Math.PI / 180.0);\n\t\t\t\t\tif (s.match(/grad$/)) return that.numValue() * (Math.PI / 200.0);\n\t\t\t\t\tif (s.match(/rad$/)) return that.numValue();\n\t\t\t\t\treturn that.numValue() * (Math.PI / 180.0);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t\n\t\t// fonts\n\t\tsvg.Font = new (function() {\n\t\t\tthis.Styles = ['normal','italic','oblique','inherit'];\n\t\t\tthis.Variants = ['normal','small-caps','inherit'];\n\t\t\tthis.Weights = ['normal','bold','bolder','lighter','100','200','300','400','500','600','700','800','900','inherit'];\n\t\t\t\n\t\t\tthis.CreateFont = function(fontStyle, fontVariant, fontWeight, fontSize, fontFamily, inherit) { \n\t\t\t\tvar f = inherit != null ? this.Parse(inherit) : this.CreateFont('', '', '', '', '', svg.ctx.font);\n\t\t\t\treturn { \n\t\t\t\t\tfontFamily: fontFamily || f.fontFamily, \n\t\t\t\t\tfontSize: fontSize || f.fontSize, \n\t\t\t\t\tfontStyle: fontStyle || f.fontStyle, \n\t\t\t\t\tfontWeight: fontWeight || f.fontWeight, \n\t\t\t\t\tfontVariant: fontVariant || f.fontVariant,\n\t\t\t\t\ttoString: function () { return [this.fontStyle, this.fontVariant, this.fontWeight, this.fontSize, this.fontFamily].join(' ') } \n\t\t\t\t} \n\t\t\t}\n\t\t\t\n\t\t\tvar that = this;\n\t\t\tthis.Parse = function(s) {\n\t\t\t\tvar f = {};\n\t\t\t\tvar d = svg.trim(svg.compressSpaces(s || '')).split(' ');\n\t\t\t\tvar set = { fontSize: false, fontStyle: false, fontWeight: false, fontVariant: false }\n\t\t\t\tvar ff = '';\n\t\t\t\tfor (var i=0; i<d.length; i++) {\n\t\t\t\t\tif (!set.fontStyle && that.Styles.indexOf(d[i]) != -1) { if (d[i] != 'inherit') f.fontStyle = d[i]; set.fontStyle = true; }\n\t\t\t\t\telse if (!set.fontVariant && that.Variants.indexOf(d[i]) != -1) { if (d[i] != 'inherit') f.fontVariant = d[i]; set.fontStyle = set.fontVariant = true;\t}\n\t\t\t\t\telse if (!set.fontWeight && that.Weights.indexOf(d[i]) != -1) {\tif (d[i] != 'inherit') f.fontWeight = d[i]; set.fontStyle = set.fontVariant = set.fontWeight = true; }\n\t\t\t\t\telse if (!set.fontSize) { if (d[i] != 'inherit') f.fontSize = d[i].split('/')[0]; set.fontStyle = set.fontVariant = set.fontWeight = set.fontSize = true; }\n\t\t\t\t\telse { if (d[i] != 'inherit') ff += d[i]; }\n\t\t\t\t} if (ff != '') f.fontFamily = ff;\n\t\t\t\treturn f;\n\t\t\t}\n\t\t});\n\t\t\n\t\t// points and paths\n\t\tsvg.ToNumberArray = function(s) {\n\t\t\tvar a = svg.trim(svg.compressSpaces((s || '').replace(/,/g, ' '))).split(' ');\n\t\t\tfor (var i=0; i<a.length; i++) {\n\t\t\t\ta[i] = parseFloat(a[i]);\n\t\t\t}\n\t\t\treturn a;\n\t\t}\t\t\n\t\tsvg.Point = function(x, y) {\n\t\t\tthis.x = x;\n\t\t\tthis.y = y;\n\t\t\t\n\t\t\tthis.angleTo = function(p) {\n\t\t\t\treturn Math.atan2(p.y - this.y, p.x - this.x);\n\t\t\t}\n\t\t\t\n\t\t\tthis.applyTransform = function(v) {\n\t\t\t\tvar xp = this.x * v[0] + this.y * v[2] + v[4];\n\t\t\t\tvar yp = this.x * v[1] + this.y * v[3] + v[5];\n\t\t\t\tthis.x = xp;\n\t\t\t\tthis.y = yp;\n\t\t\t}\n\t\t}\n\t\tsvg.CreatePoint = function(s) {\n\t\t\tvar a = svg.ToNumberArray(s);\n\t\t\treturn new svg.Point(a[0], a[1]);\n\t\t}\n\t\tsvg.CreatePath = function(s) {\n\t\t\tvar a = svg.ToNumberArray(s);\n\t\t\tvar path = [];\n\t\t\tfor (var i=0; i<a.length; i+=2) {\n\t\t\t\tpath.push(new svg.Point(a[i], a[i+1]));\n\t\t\t}\n\t\t\treturn path;\n\t\t}\n\t\t\n\t\t// bounding box\n\t\tsvg.BoundingBox = function(x1, y1, x2, y2) { // pass in initial points if you want\n\t\t\tthis.x1 = Number.NaN;\n\t\t\tthis.y1 = Number.NaN;\n\t\t\tthis.x2 = Number.NaN;\n\t\t\tthis.y2 = Number.NaN;\n\t\t\t\n\t\t\tthis.x = function() { return this.x1; }\n\t\t\tthis.y = function() { return this.y1; }\n\t\t\tthis.width = function() { return this.x2 - this.x1; }\n\t\t\tthis.height = function() { return this.y2 - this.y1; }\n\t\t\t\n\t\t\tthis.addPoint = function(x, y) {\t\n\t\t\t\tif (x != null) {\n\t\t\t\t\tif (isNaN(this.x1) || isNaN(this.x2)) {\n\t\t\t\t\t\tthis.x1 = x;\n\t\t\t\t\t\tthis.x2 = x;\n\t\t\t\t\t}\n\t\t\t\t\tif (x < this.x1) this.x1 = x;\n\t\t\t\t\tif (x > this.x2) this.x2 = x;\n\t\t\t\t}\n\t\t\t\n\t\t\t\tif (y != null) {\n\t\t\t\t\tif (isNaN(this.y1) || isNaN(this.y2)) {\n\t\t\t\t\t\tthis.y1 = y;\n\t\t\t\t\t\tthis.y2 = y;\n\t\t\t\t\t}\n\t\t\t\t\tif (y < this.y1) this.y1 = y;\n\t\t\t\t\tif (y > this.y2) this.y2 = y;\n\t\t\t\t}\n\t\t\t}\t\t\t\n\t\t\tthis.addX = function(x) { this.addPoint(x, null); }\n\t\t\tthis.addY = function(y) { this.addPoint(null, y); }\n\t\t\t\n\t\t\tthis.addBoundingBox = function(bb) {\n\t\t\t\tthis.addPoint(bb.x1, bb.y1);\n\t\t\t\tthis.addPoint(bb.x2, bb.y2);\n\t\t\t}\n\t\t\t\n\t\t\tthis.addQuadraticCurve = function(p0x, p0y, p1x, p1y, p2x, p2y) {\n\t\t\t\tvar cp1x = p0x + 2/3 * (p1x - p0x); // CP1 = QP0 + 2/3 *(QP1-QP0)\n\t\t\t\tvar cp1y = p0y + 2/3 * (p1y - p0y); // CP1 = QP0 + 2/3 *(QP1-QP0)\n\t\t\t\tvar cp2x = cp1x + 1/3 * (p2x - p0x); // CP2 = CP1 + 1/3 *(QP2-QP0)\n\t\t\t\tvar cp2y = cp1y + 1/3 * (p2y - p0y); // CP2 = CP1 + 1/3 *(QP2-QP0)\n\t\t\t\tthis.addBezierCurve(p0x, p0y, cp1x, cp2x, cp1y,\tcp2y, p2x, p2y);\n\t\t\t}\n\t\t\t\n\t\t\tthis.addBezierCurve = function(p0x, p0y, p1x, p1y, p2x, p2y, p3x, p3y) {\n\t\t\t\t// from http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html\n\t\t\t\tvar p0 = [p0x, p0y], p1 = [p1x, p1y], p2 = [p2x, p2y], p3 = [p3x, p3y];\n\t\t\t\tthis.addPoint(p0[0], p0[1]);\n\t\t\t\tthis.addPoint(p3[0], p3[1]);\n\t\t\t\t\n\t\t\t\tfor (i=0; i<=1; i++) {\n\t\t\t\t\tvar f = function(t) { \n\t\t\t\t\t\treturn Math.pow(1-t, 3) * p0[i]\n\t\t\t\t\t\t+ 3 * Math.pow(1-t, 2) * t * p1[i]\n\t\t\t\t\t\t+ 3 * (1-t) * Math.pow(t, 2) * p2[i]\n\t\t\t\t\t\t+ Math.pow(t, 3) * p3[i];\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tvar b = 6 * p0[i] - 12 * p1[i] + 6 * p2[i];\n\t\t\t\t\tvar a = -3 * p0[i] + 9 * p1[i] - 9 * p2[i] + 3 * p3[i];\n\t\t\t\t\tvar c = 3 * p1[i] - 3 * p0[i];\n\t\t\t\t\t\n\t\t\t\t\tif (a == 0) {\n\t\t\t\t\t\tif (b == 0) continue;\n\t\t\t\t\t\tvar t = -c / b;\n\t\t\t\t\t\tif (0 < t && t < 1) {\n\t\t\t\t\t\t\tif (i == 0) this.addX(f(t));\n\t\t\t\t\t\t\tif (i == 1) this.addY(f(t));\n\t\t\t\t\t\t}\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tvar b2ac = Math.pow(b, 2) - 4 * c * a;\n\t\t\t\t\tif (b2ac < 0) continue;\n\t\t\t\t\tvar t1 = (-b + Math.sqrt(b2ac)) / (2 * a);\n\t\t\t\t\tif (0 < t1 && t1 < 1) {\n\t\t\t\t\t\tif (i == 0) this.addX(f(t1));\n\t\t\t\t\t\tif (i == 1) this.addY(f(t1));\n\t\t\t\t\t}\n\t\t\t\t\tvar t2 = (-b - Math.sqrt(b2ac)) / (2 * a);\n\t\t\t\t\tif (0 < t2 && t2 < 1) {\n\t\t\t\t\t\tif (i == 0) this.addX(f(t2));\n\t\t\t\t\t\tif (i == 1) this.addY(f(t2));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\tthis.isPointInBox = function(x, y) {\n\t\t\t\treturn (this.x1 <= x && x <= this.x2 && this.y1 <= y && y <= this.y2);\n\t\t\t}\n\t\t\t\n\t\t\tthis.addPoint(x1, y1);\n\t\t\tthis.addPoint(x2, y2);\n\t\t}\n\t\t\n\t\t// transforms\n\t\tsvg.Transform = function(v) {\t\n\t\t\tvar that = this;\n\t\t\tthis.Type = {}\n\t\t\n\t\t\t// translate\n\t\t\tthis.Type.translate = function(s) {\n\t\t\t\tthis.p = svg.CreatePoint(s);\t\t\t\n\t\t\t\tthis.apply = function(ctx) {\n\t\t\t\t\tctx.translate(this.p.x || 0.0, this.p.y || 0.0);\n\t\t\t\t}\n\t\t\t\tthis.applyToPoint = function(p) {\n\t\t\t\t\tp.applyTransform([1, 0, 0, 1, this.p.x || 0.0, this.p.y || 0.0]);\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\t// rotate\n\t\t\tthis.Type.rotate = function(s) {\n\t\t\t\tvar a = svg.ToNumberArray(s);\n\t\t\t\tthis.angle = new svg.Property('angle', a[0]);\n\t\t\t\tthis.cx = a[1] || 0;\n\t\t\t\tthis.cy = a[2] || 0;\n\t\t\t\tthis.apply = function(ctx) {\n\t\t\t\t\tctx.translate(this.cx, this.cy);\n\t\t\t\t\tctx.rotate(this.angle.Angle.toRadians());\n\t\t\t\t\tctx.translate(-this.cx, -this.cy);\n\t\t\t\t}\n\t\t\t\tthis.applyToPoint = function(p) {\n\t\t\t\t\tvar a = this.angle.Angle.toRadians();\n\t\t\t\t\tp.applyTransform([1, 0, 0, 1, this.p.x || 0.0, this.p.y || 0.0]);\n\t\t\t\t\tp.applyTransform([Math.cos(a), Math.sin(a), -Math.sin(a), Math.cos(a), 0, 0]);\n\t\t\t\t\tp.applyTransform([1, 0, 0, 1, -this.p.x || 0.0, -this.p.y || 0.0]);\n\t\t\t\t}\t\t\t\n\t\t\t}\n\t\t\t\n\t\t\tthis.Type.scale = function(s) {\n\t\t\t\tthis.p = svg.CreatePoint(s);\n\t\t\t\tthis.apply = function(ctx) {\n\t\t\t\t\tctx.scale(this.p.x || 1.0, this.p.y || this.p.x || 1.0);\n\t\t\t\t}\n\t\t\t\tthis.applyToPoint = function(p) {\n\t\t\t\t\tp.applyTransform([this.p.x || 0.0, 0, 0, this.p.y || 0.0, 0, 0]);\n\t\t\t\t}\t\t\t\t\n\t\t\t}\n\t\t\t\n\t\t\tthis.Type.matrix = function(s) {\n\t\t\t\tthis.m = svg.ToNumberArray(s);\n\t\t\t\tthis.apply = function(ctx) {\n\t\t\t\t\tctx.transform(this.m[0], this.m[1], this.m[2], this.m[3], this.m[4], this.m[5]);\n\t\t\t\t}\n\t\t\t\tthis.applyToPoint = function(p) {\n\t\t\t\t\tp.applyTransform(this.m);\n\t\t\t\t}\t\t\t\t\t\n\t\t\t}\n\t\t\t\n\t\t\tthis.Type.SkewBase = function(s) {\n\t\t\t\tthis.base = that.Type.matrix;\n\t\t\t\tthis.base(s);\n\t\t\t\tthis.angle = new svg.Property('angle', s);\n\t\t\t}\n\t\t\tthis.Type.SkewBase.prototype = new this.Type.matrix;\n\t\t\t\n\t\t\tthis.Type.skewX = function(s) {\n\t\t\t\tthis.base = that.Type.SkewBase;\n\t\t\t\tthis.base(s);\n\t\t\t\tthis.m = [1, 0, Math.tan(this.angle.Angle.toRadians()), 1, 0, 0];\n\t\t\t}\n\t\t\tthis.Type.skewX.prototype = new this.Type.SkewBase;\n\t\t\t\n\t\t\tthis.Type.skewY = function(s) {\n\t\t\t\tthis.base = that.Type.SkewBase;\n\t\t\t\tthis.base(s);\n\t\t\t\tthis.m = [1, Math.tan(this.angle.Angle.toRadians()), 0, 1, 0, 0];\n\t\t\t}\n\t\t\tthis.Type.skewY.prototype = new this.Type.SkewBase;\n\t\t\n\t\t\tthis.transforms = [];\n\t\t\t\n\t\t\tthis.apply = function(ctx) {\n\t\t\t\tfor (var i=0; i<this.transforms.length; i++) {\n\t\t\t\t\tthis.transforms[i].apply(ctx);\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\tthis.applyToPoint = function(p) {\n\t\t\t\tfor (var i=0; i<this.transforms.length; i++) {\n\t\t\t\t\tthis.transforms[i].applyToPoint(p);\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\tvar data = svg.trim(svg.compressSpaces(v)).split(/\\s(?=[a-z])/);\n\t\t\tfor (var i=0; i<data.length; i++) {\n\t\t\t\tvar type = data[i].split('(')[0];\n\t\t\t\tvar s = data[i].split('(')[1].replace(')','');\n\t\t\t\tvar transform = new this.Type[type](s);\n\t\t\t\tthis.transforms.push(transform);\n\t\t\t}\n\t\t}\n\t\t\n\t\t// aspect ratio\n\t\tsvg.AspectRatio = function(ctx, aspectRatio, width, desiredWidth, height, desiredHeight, minX, minY, refX, refY) {\n\t\t\t// aspect ratio - http://www.w3.org/TR/SVG/coords.html#PreserveAspectRatioAttribute\n\t\t\taspectRatio = svg.compressSpaces(aspectRatio);\n\t\t\taspectRatio = aspectRatio.replace(/^defer\\s/,''); // ignore defer\n\t\t\tvar align = aspectRatio.split(' ')[0] || 'xMidYMid';\n\t\t\tvar meetOrSlice = aspectRatio.split(' ')[1] || 'meet';\t\t\t\t\t\n\t\n\t\t\t// calculate scale\n\t\t\tvar scaleX = width / desiredWidth;\n\t\t\tvar scaleY = height / desiredHeight;\n\t\t\tvar scaleMin = Math.min(scaleX, scaleY);\n\t\t\tvar scaleMax = Math.max(scaleX, scaleY);\n\t\t\tif (meetOrSlice == 'meet') { desiredWidth *= scaleMin; desiredHeight *= scaleMin; }\n\t\t\tif (meetOrSlice == 'slice') { desiredWidth *= scaleMax; desiredHeight *= scaleMax; }\t\n\t\t\t\n\t\t\trefX = new svg.Property('refX', refX);\n\t\t\trefY = new svg.Property('refY', refY);\n\t\t\tif (refX.hasValue() && refY.hasValue()) {\t\t\t\t\n\t\t\t\tctx.translate(-scaleMin * refX.Length.toPixels('x'), -scaleMin * refY.Length.toPixels('y'));\n\t\t\t} \n\t\t\telse {\t\t\t\t\t\n\t\t\t\t// align\n\t\t\t\tif (align.match(/^xMid/) && ((meetOrSlice == 'meet' && scaleMin == scaleY) || (meetOrSlice == 'slice' && scaleMax == scaleY))) ctx.translate(width / 2.0 - desiredWidth / 2.0, 0); \n\t\t\t\tif (align.match(/YMid$/) && ((meetOrSlice == 'meet' && scaleMin == scaleX) || (meetOrSlice == 'slice' && scaleMax == scaleX))) ctx.translate(0, height / 2.0 - desiredHeight / 2.0); \n\t\t\t\tif (align.match(/^xMax/) && ((meetOrSlice == 'meet' && scaleMin == scaleY) || (meetOrSlice == 'slice' && scaleMax == scaleY))) ctx.translate(width - desiredWidth, 0); \n\t\t\t\tif (align.match(/YMax$/) && ((meetOrSlice == 'meet' && scaleMin == scaleX) || (meetOrSlice == 'slice' && scaleMax == scaleX))) ctx.translate(0, height - desiredHeight); \n\t\t\t}\n\t\t\t\n\t\t\t// scale\n\t\t\tif (align == 'none') ctx.scale(scaleX, scaleY);\n\t\t\telse if (meetOrSlice == 'meet') ctx.scale(scaleMin, scaleMin); \n\t\t\telse if (meetOrSlice == 'slice') ctx.scale(scaleMax, scaleMax); \t\n\t\t\t\n\t\t\t// translate\n\t\t\tctx.translate(minX == null ? 0 : -minX, minY == null ? 0 : -minY);\t\t\t\n\t\t}\n\t\t\n\t\t// elements\n\t\tsvg.Element = {}\n\t\t\n\t\tsvg.Element.ElementBase = function(node) {\t\n\t\t\tthis.attributes = {};\n\t\t\tthis.styles = {};\n\t\t\tthis.children = [];\n\t\t\t\n\t\t\t// get or create attribute\n\t\t\tthis.attribute = function(name, createIfNotExists) {\n\t\t\t\tvar a = this.attributes[name];\n\t\t\t\tif (a != null) return a;\n\t\t\t\t\t\t\t\n\t\t\t\ta = new svg.Property(name, '');\n\t\t\t\tif (createIfNotExists == true) this.attributes[name] = a;\n\t\t\t\treturn a;\n\t\t\t}\n\t\t\t\n\t\t\t// get or create style, crawls up node tree\n\t\t\tthis.style = function(name, createIfNotExists) {\n\t\t\t\tvar s = this.styles[name];\n\t\t\t\tif (s != null) return s;\n\t\t\t\t\n\t\t\t\tvar a = this.attribute(name);\n\t\t\t\tif (a != null && a.hasValue()) {\n\t\t\t\t\treturn a;\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tvar p = this.parent;\n\t\t\t\tif (p != null) {\n\t\t\t\t\tvar ps = p.style(name);\n\t\t\t\t\tif (ps != null && ps.hasValue()) {\n\t\t\t\t\t\treturn ps;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\ts = new svg.Property(name, '');\n\t\t\t\tif (createIfNotExists == true) this.styles[name] = s;\n\t\t\t\treturn s;\n\t\t\t}\n\t\t\t\n\t\t\t// base render\n\t\t\tthis.render = function(ctx) {\n\t\t\t\t// don't render display=none\n\t\t\t\tif (this.style('display').value == 'none') return;\n\t\t\t\t\n\t\t\t\t// don't render visibility=hidden\n\t\t\t\tif (this.attribute('visibility').value == 'hidden') return;\n\t\t\t\n\t\t\t\tctx.save();\n\t\t\t\t\tthis.setContext(ctx);\n\t\t\t\t\t\t// mask\n\t\t\t\t\t\tif (this.attribute('mask').hasValue()) {\n\t\t\t\t\t\t\tvar mask = this.attribute('mask').Definition.getDefinition();\n\t\t\t\t\t\t\tif (mask != null) mask.apply(ctx, this);\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse if (this.style('filter').hasValue()) {\n\t\t\t\t\t\t\tvar filter = this.style('filter').Definition.getDefinition();\n\t\t\t\t\t\t\tif (filter != null) filter.apply(ctx, this);\n\t\t\t\t\t\t}\n\t\t\t\t\t\telse this.renderChildren(ctx);\t\t\t\t\n\t\t\t\t\tthis.clearContext(ctx);\n\t\t\t\tctx.restore();\n\t\t\t}\n\t\t\t\n\t\t\t// base set context\n\t\t\tthis.setContext = function(ctx) {\n\t\t\t\t// OVERRIDE ME!\n\t\t\t}\n\t\t\t\n\t\t\t// base clear context\n\t\t\tthis.clearContext = function(ctx) {\n\t\t\t\t// OVERRIDE ME!\n\t\t\t}\t\t\t\n\t\t\t\n\t\t\t// base render children\n\t\t\tthis.renderChildren = function(ctx) {\n\t\t\t\tfor (var i=0; i<this.children.length; i++) {\n\t\t\t\t\tthis.children[i].render(ctx);\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\tthis.addChild = function(childNode, create) {\n\t\t\t\tvar child = childNode;\n\t\t\t\tif (create) child = svg.CreateElement(childNode);\n\t\t\t\tchild.parent = this;\n\t\t\t\tthis.children.push(child);\t\t\t\n\t\t\t}\n\t\t\t\t\n\t\t\tif (node != null && node.nodeType == 1) { //ELEMENT_NODE\n\t\t\t\t// add children\n\t\t\t\tfor (var i=0; i<node.childNodes.length; i++) {\n\t\t\t\t\tvar childNode = node.childNodes[i];\n\t\t\t\t\tif (childNode.nodeType == 1) this.addChild(childNode, true); //ELEMENT_NODE\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t// add attributes\n\t\t\t\tfor (var i=0; i<node.attributes.length; i++) {\n\t\t\t\t\tvar attribute = node.attributes[i];\n\t\t\t\t\tthis.attributes[attribute.nodeName] = new svg.Property(attribute.nodeName, attribute.nodeValue);\n\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t// add tag styles\n\t\t\t\tvar styles = svg.Styles[node.nodeName];\n\t\t\t\tif (styles != null) {\n\t\t\t\t\tfor (var name in styles) {\n\t\t\t\t\t\tthis.styles[name] = styles[name];\n\t\t\t\t\t}\n\t\t\t\t}\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t// add class styles\n\t\t\t\tif (this.attribute('class').hasValue()) {\n\t\t\t\t\tvar classes = svg.compressSpaces(this.attribute('class').value).split(' ');\n\t\t\t\t\tfor (var j=0; j<classes.length; j++) {\n\t\t\t\t\t\tstyles = svg.Styles['.'+classes[j]];\n\t\t\t\t\t\tif (styles != null) {\n\t\t\t\t\t\t\tfor (var name in styles) {\n\t\t\t\t\t\t\t\tthis.styles[name] = styles[name];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tstyles = svg.Styles[node.nodeName+'.'+classes[j]];\n\t\t\t\t\t\tif (styles != null) {\n\t\t\t\t\t\t\tfor (var name in styles) {\n\t\t\t\t\t\t\t\tthis.styles[name] = styles[name];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t// add inline styles\n\t\t\t\tif (this.attribute('style').hasValue()) {\n\t\t\t\t\tvar styles = this.attribute('style').value.split(';');\n\t\t\t\t\tfor (var i=0; i<styles.length; i++) {\n\t\t\t\t\t\tif (svg.trim(styles[i]) != '') {\n\t\t\t\t\t\t\tvar style = styles[i].split(':');\n\t\t\t\t\t\t\tvar name = svg.trim(style[0]);\n\t\t\t\t\t\t\tvar value = svg.trim(style[1]);\n\t\t\t\t\t\t\tthis.styles[name] = new svg.Property(name, value);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\t\n\n\t\t\t\t// add id\n\t\t\t\tif (this.attribute('id').hasValue()) {\n\t\t\t\t\tif (svg.Definitions[this.attribute('id').value] == null) {\n\t\t\t\t\t\tsvg.Definitions[this.attribute('id').value] = this;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t\n\t\tsvg.Element.RenderedElementBase = function(node) {\n\t\t\tthis.base = svg.Element.ElementBase;\n\t\t\tthis.base(node);\n\t\t\t\n\t\t\tthis.setContext = function(ctx) {\n\t\t\t\t// fill\n\t\t\t\tif (this.style('fill').Definition.isUrl()) {\n\t\t\t\t\tvar fs = this.style('fill').Definition.getFillStyle(this);\n\t\t\t\t\tif (fs != null) ctx.fillStyle = fs;\n\t\t\t\t}\n\t\t\t\telse if (this.style('fill').hasValue()) {\n\t\t\t\t\tvar fillStyle = this.style('fill');\n\t\t\t\t\tif (this.style('fill-opacity').hasValue()) fillStyle = fillStyle.Color.addOpacity(this.style('fill-opacity').value);\n\t\t\t\t\tctx.fillStyle = (fillStyle.value == 'none' ? 'rgba(0,0,0,0)' : fillStyle.value);\n\t\t\t\t}\n\t\t\t\t\t\t\t\t\t\n\t\t\t\t// stroke\n\t\t\t\tif (this.style('stroke').Definition.isUrl()) {\n\t\t\t\t\tvar fs = this.style('stroke').Definition.getFillStyle(this);\n\t\t\t\t\tif (fs != null) ctx.strokeStyle = fs;\n\t\t\t\t}\n\t\t\t\telse if (this.style('stroke').hasValue()) {\n\t\t\t\t\tvar strokeStyle = this.style('stroke');\n\t\t\t\t\tif (this.style('stroke-opacity').hasValue()) strokeStyle = strokeStyle.Color.addOpacity(this.style('stroke-opacity').value);\n\t\t\t\t\tctx.strokeStyle = (strokeStyle.value == 'none' ? 'rgba(0,0,0,0)' : strokeStyle.value);\n\t\t\t\t}\n\t\t\t\tif (this.style('stroke-width').hasValue()) ctx.lineWidth = this.style('stroke-width').Length.toPixels();\n\t\t\t\tif (this.style('stroke-linecap').hasValue()) ctx.lineCap = this.style('stroke-linecap').value;\n\t\t\t\tif (this.style('stroke-linejoin').hasValue()) ctx.lineJoin = this.style('stroke-linejoin').value;\n\t\t\t\tif (this.style('stroke-miterlimit').hasValue()) ctx.miterLimit = this.style('stroke-miterlimit').value;\n\n\t\t\t\t// font\n\t\t\t\tif (typeof(ctx.font) != 'undefined') {\n\t\t\t\t\tctx.font = svg.Font.CreateFont( \n\t\t\t\t\t\tthis.style('font-style').value, \n\t\t\t\t\t\tthis.style('font-variant').value, \n\t\t\t\t\t\tthis.style('font-weight').value, \n\t\t\t\t\t\tthis.style('font-size').hasValue() ? this.style('font-size').Length.toPixels() + 'px' : '', \n\t\t\t\t\t\tthis.style('font-family').value).toString();\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t// transform\n\t\t\t\tif (this.attribute('transform').hasValue()) { \n\t\t\t\t\tvar transform = new svg.Transform(this.attribute('transform').value);\n\t\t\t\t\ttransform.apply(ctx);\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t// clip\n\t\t\t\tif (this.attribute('clip-path').hasValue()) {\n\t\t\t\t\tvar clip = this.attribute('clip-path').Definition.getDefinition();\n\t\t\t\t\tif (clip != null) clip.apply(ctx);\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t// opacity\n\t\t\t\tif (this.style('opacity').hasValue()) {\n\t\t\t\t\tctx.globalAlpha = this.style('opacity').numValue();\n\t\t\t\t}\n\t\t\t}\t\t\n\t\t}\n\t\tsvg.Element.RenderedElementBase.prototype = new svg.Element.ElementBase;\n\t\t\n\t\tsvg.Element.PathElementBase = function(node) {\n\t\t\tthis.base = svg.Element.RenderedElementBase;\n\t\t\tthis.base(node);\n\t\t\t\n\t\t\tthis.path = function(ctx) {\n\t\t\t\tif (ctx != null) ctx.beginPath();\n\t\t\t\treturn new svg.BoundingBox();\n\t\t\t}\n\t\t\t\n\t\t\tthis.renderChildren = function(ctx) {\n\t\t\t\tthis.path(ctx);\n\t\t\t\tsvg.Mouse.checkPath(this, ctx);\n\t\t\t\tif (ctx.fillStyle != '') ctx.fill();\n\t\t\t\tif (ctx.strokeStyle != '') ctx.stroke();\n\t\t\t\t\n\t\t\t\tvar markers = this.getMarkers();\n\t\t\t\tif (markers != null) {\n\t\t\t\t\tif (this.style('marker-start').Definition.isUrl()) {\n\t\t\t\t\t\tvar marker = this.style('marker-start').Definition.getDefinition();\n\t\t\t\t\t\tmarker.render(ctx, markers[0][0], markers[0][1]);\n\t\t\t\t\t}\n\t\t\t\t\tif (this.style('marker-mid').Definition.isUrl()) {\n\t\t\t\t\t\tvar marker = this.style('marker-mid').Definition.getDefinition();\n\t\t\t\t\t\tfor (var i=1;i<markers.length-1;i++) {\n\t\t\t\t\t\t\tmarker.render(ctx, markers[i][0], markers[i][1]);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif (this.style('marker-end').Definition.isUrl()) {\n\t\t\t\t\t\tvar marker = this.style('marker-end').Definition.getDefinition();\n\t\t\t\t\t\tmarker.render(ctx, markers[markers.length-1][0], markers[markers.length-1][1]);\n\t\t\t\t\t}\n\t\t\t\t}\t\t\t\t\t\n\t\t\t}\n\t\t\t\n\t\t\tthis.getBoundingBox = function() {\n\t\t\t\treturn this.path();\n\t\t\t}\n\t\t\t\n\t\t\tthis.getMarkers = function() {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t}\n\t\tsvg.Element.PathElementBase.prototype = new svg.Element.RenderedElementBase;\n\t\t\n\t\t// svg element\n\t\tsvg.Element.svg = function(node) {\n\t\t\tthis.base = svg.Element.RenderedElementBase;\n\t\t\tthis.base(node);\n\t\t\t\n\t\t\tthis.baseClearContext = this.clearContext;\n\t\t\tthis.clearContext = function(ctx) {\n\t\t\t\tthis.baseClearContext(ctx);\n\t\t\t\tsvg.ViewPort.RemoveCurrent();\n\t\t\t}\n\t\t\t\n\t\t\tthis.baseSetContext = this.setContext;\n\t\t\tthis.setContext = function(ctx) {\n\t\t\t\t// initial values\n\t\t\t\tctx.strokeStyle = 'rgba(0,0,0,0)';\n\t\t\t\tctx.lineCap = 'butt';\n\t\t\t\tctx.lineJoin = 'miter';\n\t\t\t\tctx.miterLimit = 4;\t\t\t\n\t\t\t\n\t\t\t\tthis.baseSetContext(ctx);\n\t\t\t\t\n\t\t\t\t// create new view port\n\t\t\t\tif (this.attribute('x').hasValue() && this.attribute('y').hasValue()) {\n\t\t\t\t\tctx.translate(this.attribute('x').Length.toPixels('x'), this.attribute('y').Length.toPixels('y'));\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tvar width = svg.ViewPort.width();\n\t\t\t\tvar height = svg.ViewPort.height();\n\t\t\t\tif (typeof(this.root) == 'undefined' && this.attribute('width').hasValue() && this.attribute('height').hasValue()) {\n\t\t\t\t\twidth = this.attribute('width').Length.toPixels('x');\n\t\t\t\t\theight = this.attribute('height').Length.toPixels('y');\n\t\t\t\t\t\n\t\t\t\t\tvar x = 0;\n\t\t\t\t\tvar y = 0;\n\t\t\t\t\tif (this.attribute('refX').hasValue() && this.attribute('refY').hasValue()) {\n\t\t\t\t\t\tx = -this.attribute('refX').Length.toPixels('x');\n\t\t\t\t\t\ty = -this.attribute('refY').Length.toPixels('y');\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tctx.beginPath();\n\t\t\t\t\tctx.moveTo(x, y);\n\t\t\t\t\tctx.lineTo(width, y);\n\t\t\t\t\tctx.lineTo(width, height);\n\t\t\t\t\tctx.lineTo(x, height);\n\t\t\t\t\tctx.closePath();\n\t\t\t\t\tctx.clip();\n\t\t\t\t}\n\t\t\t\tsvg.ViewPort.SetCurrent(width, height);\t\n\t\t\t\t\t\t\n\t\t\t\t// viewbox\n\t\t\t\tif (this.attribute('viewBox').hasValue()) {\t\t\t\t\n\t\t\t\t\tvar viewBox = svg.ToNumberArray(this.attribute('viewBox').value);\n\t\t\t\t\tvar minX = viewBox[0];\n\t\t\t\t\tvar minY = viewBox[1];\n\t\t\t\t\twidth = viewBox[2];\n\t\t\t\t\theight = viewBox[3];\n\t\t\t\t\t\n\t\t\t\t\tsvg.AspectRatio(ctx,\n\t\t\t\t\t\t\t\t\tthis.attribute('preserveAspectRatio').value, \n\t\t\t\t\t\t\t\t\tsvg.ViewPort.width(), \n\t\t\t\t\t\t\t\t\twidth,\n\t\t\t\t\t\t\t\t\tsvg.ViewPort.height(),\n\t\t\t\t\t\t\t\t\theight,\n\t\t\t\t\t\t\t\t\tminX,\n\t\t\t\t\t\t\t\t\tminY,\n\t\t\t\t\t\t\t\t\tthis.attribute('refX').value,\n\t\t\t\t\t\t\t\t\tthis.attribute('refY').value);\n\t\t\t\t\t\t\t\t\t\t\n\t\t\t\t\tsvg.ViewPort.RemoveCurrent();\t\n\t\t\t\t\tsvg.ViewPort.SetCurrent(viewBox[2], viewBox[3]);\t\t\t\t\t\t\n\t\t\t\t}\t\t\t\t\n\t\t\t}\n\t\t}\n\t\tsvg.Element.svg.prototype = new svg.Element.RenderedElementBase;\n\n\t\t// rect element\n\t\tsvg.Element.rect = function(node) {\n\t\t\tthis.base = svg.Element.PathElementBase;\n\t\t\tthis.base(node);\n\t\t\t\n\t\t\tthis.path = function(ctx) {\n\t\t\t\tvar x = this.attribute('x').Length.toPixels('x');\n\t\t\t\tvar y = this.attribute('y').Length.toPixels('y');\n\t\t\t\tvar width = this.attribute('width').Length.toPixels('x');\n\t\t\t\tvar height = this.attribute('height').Length.toPixels('y');\n\t\t\t\tvar rx = this.attribute('rx').Length.toPixels('x');\n\t\t\t\tvar ry = this.attribute('ry').Length.toPixels('y');\n\t\t\t\tif (this.attribute('rx').hasValue() && !this.attribute('ry').hasValue()) ry = rx;\n\t\t\t\tif (this.attribute('ry').hasValue() && !this.attribute('rx').hasValue()) rx = ry;\n\t\t\t\t\n\t\t\t\tif (ctx != null) {\n\t\t\t\t\tctx.beginPath();\n\t\t\t\t\tctx.moveTo(x + rx, y);\n\t\t\t\t\tctx.lineTo(x + width - rx, y);\n\t\t\t\t\tctx.quadraticCurveTo(x + width, y, x + width, y + ry)\n\t\t\t\t\tctx.lineTo(x + width, y + height - ry);\n\t\t\t\t\tctx.quadraticCurveTo(x + width, y + height, x + width - rx, y + height)\n\t\t\t\t\tctx.lineTo(x + rx, y + height);\n\t\t\t\t\tctx.quadraticCurveTo(x, y + height, x, y + height - ry)\n\t\t\t\t\tctx.lineTo(x, y + ry);\n\t\t\t\t\tctx.quadraticCurveTo(x, y, x + rx, y)\n\t\t\t\t\tctx.closePath();\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\treturn new svg.BoundingBox(x, y, x + width, y + height);\n\t\t\t}\n\t\t}\n\t\tsvg.Element.rect.prototype = new svg.Element.PathElementBase;\n\t\t\n\t\t// circle element\n\t\tsvg.Element.circle = function(node) {\n\t\t\tthis.base = svg.Element.PathElementBase;\n\t\t\tthis.base(node);\n\t\t\t\n\t\t\tthis.path = function(ctx) {\n\t\t\t\tvar cx = this.attribute('cx').Length.toPixels('x');\n\t\t\t\tvar cy = this.attribute('cy').Length.toPixels('y');\n\t\t\t\tvar r = this.attribute('r').Length.toPixels();\n\t\t\t\n\t\t\t\tif (ctx != null) {\n\t\t\t\t\tctx.beginPath();\n\t\t\t\t\tctx.arc(cx, cy, r, 0, Math.PI * 2, true); \n\t\t\t\t\tctx.closePath();\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\treturn new svg.BoundingBox(cx - r, cy - r, cx + r, cy + r);\n\t\t\t}\n\t\t}\n\t\tsvg.Element.circle.prototype = new svg.Element.PathElementBase;\t\n\n\t\t// ellipse element\n\t\tsvg.Element.ellipse = function(node) {\n\t\t\tthis.base = svg.Element.PathElementBase;\n\t\t\tthis.base(node);\n\t\t\t\n\t\t\tthis.path = function(ctx) {\n\t\t\t\tvar KAPPA = 4 * ((Math.sqrt(2) - 1) / 3);\n\t\t\t\tvar rx = this.attribute('rx').Length.toPixels('x');\n\t\t\t\tvar ry = this.attribute('ry').Length.toPixels('y');\n\t\t\t\tvar cx = this.attribute('cx').Length.toPixels('x');\n\t\t\t\tvar cy = this.attribute('cy').Length.toPixels('y');\n\t\t\t\t\n\t\t\t\tif (ctx != null) {\n\t\t\t\t\tctx.beginPath();\n\t\t\t\t\tctx.moveTo(cx, cy - ry);\n\t\t\t\t\tctx.bezierCurveTo(cx + (KAPPA * rx), cy - ry,  cx + rx, cy - (KAPPA * ry), cx + rx, cy);\n\t\t\t\t\tctx.bezierCurveTo(cx + rx, cy + (KAPPA * ry), cx + (KAPPA * rx), cy + ry, cx, cy + ry);\n\t\t\t\t\tctx.bezierCurveTo(cx - (KAPPA * rx), cy + ry, cx - rx, cy + (KAPPA * ry), cx - rx, cy);\n\t\t\t\t\tctx.bezierCurveTo(cx - rx, cy - (KAPPA * ry), cx - (KAPPA * rx), cy - ry, cx, cy - ry);\n\t\t\t\t\tctx.closePath();\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\treturn new svg.BoundingBox(cx - rx, cy - ry, cx + rx, cy + ry);\n\t\t\t}\n\t\t}\n\t\tsvg.Element.ellipse.prototype = new svg.Element.PathElementBase;\t\t\t\n\t\t\n\t\t// line element\n\t\tsvg.Element.line = function(node) {\n\t\t\tthis.base = svg.Element.PathElementBase;\n\t\t\tthis.base(node);\n\t\t\t\n\t\t\tthis.getPoints = function() {\n\t\t\t\treturn [\n\t\t\t\t\tnew svg.Point(this.attribute('x1').Length.toPixels('x'), this.attribute('y1').Length.toPixels('y')),\n\t\t\t\t\tnew svg.Point(this.attribute('x2').Length.toPixels('x'), this.attribute('y2').Length.toPixels('y'))];\n\t\t\t}\n\t\t\t\t\t\t\t\t\n\t\t\tthis.path = function(ctx) {\n\t\t\t\tvar points = this.getPoints();\n\t\t\t\t\n\t\t\t\tif (ctx != null) {\n\t\t\t\t\tctx.beginPath();\n\t\t\t\t\tctx.moveTo(points[0].x, points[0].y);\n\t\t\t\t\tctx.lineTo(points[1].x, points[1].y);\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\treturn new svg.BoundingBox(points[0].x, points[0].y, points[1].x, points[1].y);\n\t\t\t}\n\t\t\t\n\t\t\tthis.getMarkers = function() {\n\t\t\t\tvar points = this.getPoints();\t\n\t\t\t\tvar a = points[0].angleTo(points[1]);\n\t\t\t\treturn [[points[0], a], [points[1], a]];\n\t\t\t}\n\t\t}\n\t\tsvg.Element.line.prototype = new svg.Element.PathElementBase;\t\t\n\t\t\t\t\n\t\t// polyline element\n\t\tsvg.Element.polyline = function(node) {\n\t\t\tthis.base = svg.Element.PathElementBase;\n\t\t\tthis.base(node);\n\t\t\t\n\t\t\tthis.points = svg.CreatePath(this.attribute('points').value);\n\t\t\tthis.path = function(ctx) {\n\t\t\t\tvar bb = new svg.BoundingBox(this.points[0].x, this.points[0].y);\n\t\t\t\tif (ctx != null) {\n\t\t\t\t\tctx.beginPath();\n\t\t\t\t\tctx.moveTo(this.points[0].x, this.points[0].y);\n\t\t\t\t}\n\t\t\t\tfor (var i=1; i<this.points.length; i++) {\n\t\t\t\t\tbb.addPoint(this.points[i].x, this.points[i].y);\n\t\t\t\t\tif (ctx != null) ctx.lineTo(this.points[i].x, this.points[i].y);\n\t\t\t\t}\n\t\t\t\treturn bb;\n\t\t\t}\n\t\t\t\n\t\t\tthis.getMarkers = function() {\n\t\t\t\tvar markers = [];\n\t\t\t\tfor (var i=0; i<this.points.length - 1; i++) {\n\t\t\t\t\tmarkers.push([this.points[i], this.points[i].angleTo(this.points[i+1])]);\n\t\t\t\t}\n\t\t\t\tmarkers.push([this.points[this.points.length-1], markers[markers.length-1][1]]);\n\t\t\t\treturn markers;\n\t\t\t}\t\t\t\n\t\t}\n\t\tsvg.Element.polyline.prototype = new svg.Element.PathElementBase;\t\t\t\t\n\t\t\t\t\n\t\t// polygon element\n\t\tsvg.Element.polygon = function(node) {\n\t\t\tthis.base = svg.Element.polyline;\n\t\t\tthis.base(node);\n\t\t\t\n\t\t\tthis.basePath = this.path;\n\t\t\tthis.path = function(ctx) {\n\t\t\t\tvar bb = this.basePath(ctx);\n\t\t\t\tif (ctx != null) {\n\t\t\t\t\tctx.lineTo(this.points[0].x, this.points[0].y);\n\t\t\t\t\tctx.closePath();\n\t\t\t\t}\n\t\t\t\treturn bb;\n\t\t\t}\n\t\t}\n\t\tsvg.Element.polygon.prototype = new svg.Element.polyline;\n\n\t\t// path element\n\t\tsvg.Element.path = function(node) {\n\t\t\tthis.base = svg.Element.PathElementBase;\n\t\t\tthis.base(node);\n\t\t\t\t\t\n\t\t\tvar d = this.attribute('d').value;\n\t\t\t// TODO: convert to real lexer based on http://www.w3.org/TR/SVG11/paths.html#PathDataBNF\n\t\t\td = d.replace(/,/gm,' '); // get rid of all commas\n\t\t\td = d.replace(/([MmZzLlHhVvCcSsQqTtAa])([MmZzLlHhVvCcSsQqTtAa])/gm,'$1 $2'); // separate commands from commands\n\t\t\td = d.replace(/([MmZzLlHhVvCcSsQqTtAa])([MmZzLlHhVvCcSsQqTtAa])/gm,'$1 $2'); // separate commands from commands\n\t\t\td = d.replace(/([MmZzLlHhVvCcSsQqTtAa])([^\\s])/gm,'$1 $2'); // separate commands from points\n\t\t\td = d.replace(/([^\\s])([MmZzLlHhVvCcSsQqTtAa])/gm,'$1 $2'); // separate commands from points\n\t\t\td = d.replace(/([0-9])([+\\-])/gm,'$1 $2'); // separate digits when no comma\n\t\t\td = d.replace(/(\\.[0-9]*)(\\.)/gm,'$1 $2'); // separate digits when no comma\n\t\t\td = d.replace(/([Aa](\\s+[0-9]+){3})\\s+([01])\\s*([01])/gm,'$1 $3 $4 '); // shorthand elliptical arc path syntax\n\t\t\td = svg.compressSpaces(d); // compress multiple spaces\n\t\t\td = svg.trim(d);\n\t\t\tthis.PathParser = new (function(d) {\n\t\t\t\tthis.tokens = d.split(' ');\n\t\t\t\t\n\t\t\t\tthis.reset = function() {\n\t\t\t\t\tthis.i = -1;\n\t\t\t\t\tthis.command = '';\n\t\t\t\t\tthis.previousCommand = '';\n\t\t\t\t\tthis.start = new svg.Point(0, 0);\n\t\t\t\t\tthis.control = new svg.Point(0, 0);\n\t\t\t\t\tthis.current = new svg.Point(0, 0);\n\t\t\t\t\tthis.points = [];\n\t\t\t\t\tthis.angles = [];\n\t\t\t\t}\n\t\t\t\t\t\t\t\t\n\t\t\t\tthis.isEnd = function() {\n\t\t\t\t\treturn this.i >= this.tokens.length - 1;\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tthis.isCommandOrEnd = function() {\n\t\t\t\t\tif (this.isEnd()) return true;\n\t\t\t\t\treturn this.tokens[this.i + 1].match(/^[A-Za-z]$/) != null;\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tthis.isRelativeCommand = function() {\n\t\t\t\t\treturn this.command == this.command.toLowerCase();\n\t\t\t\t}\n\t\t\t\t\t\t\t\n\t\t\t\tthis.getToken = function() {\n\t\t\t\t\tthis.i = this.i + 1;\n\t\t\t\t\treturn this.tokens[this.i];\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tthis.getScalar = function() {\n\t\t\t\t\treturn parseFloat(this.getToken());\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tthis.nextCommand = function() {\n\t\t\t\t\tthis.previousCommand = this.command;\n\t\t\t\t\tthis.command = this.getToken();\n\t\t\t\t}\t\t\t\t\n\t\t\t\t\n\t\t\t\tthis.getPoint = function() {\n\t\t\t\t\tvar p = new svg.Point(this.getScalar(), this.getScalar());\n\t\t\t\t\treturn this.makeAbsolute(p);\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tthis.getAsControlPoint = function() {\n\t\t\t\t\tvar p = this.getPoint();\n\t\t\t\t\tthis.control = p;\n\t\t\t\t\treturn p;\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tthis.getAsCurrentPoint = function() {\n\t\t\t\t\tvar p = this.getPoint();\n\t\t\t\t\tthis.current = p;\n\t\t\t\t\treturn p;\t\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tthis.getReflectedControlPoint = function() {\n\t\t\t\t\tif (this.previousCommand.toLowerCase() != 'c' && this.previousCommand.toLowerCase() != 's') {\n\t\t\t\t\t\treturn this.current;\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\t// reflect point\n\t\t\t\t\tvar p = new svg.Point(2 * this.current.x - this.control.x, 2 * this.current.y - this.control.y);\t\t\t\t\t\n\t\t\t\t\treturn p;\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tthis.makeAbsolute = function(p) {\n\t\t\t\t\tif (this.isRelativeCommand()) {\n\t\t\t\t\t\tp.x = this.current.x + p.x;\n\t\t\t\t\t\tp.y = this.current.y + p.y;\n\t\t\t\t\t}\n\t\t\t\t\treturn p;\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tthis.addMarker = function(p, from, priorTo) {\n\t\t\t\t\t// if the last angle isn't filled in because we didn't have this point yet ...\n\t\t\t\t\tif (priorTo != null && this.angles.length > 0 && this.angles[this.angles.length-1] == null) {\n\t\t\t\t\t\tthis.angles[this.angles.length-1] = this.points[this.points.length-1].angleTo(priorTo);\n\t\t\t\t\t}\n\t\t\t\t\tthis.addMarkerAngle(p, from == null ? null : from.angleTo(p));\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tthis.addMarkerAngle = function(p, a) {\n\t\t\t\t\tthis.points.push(p);\n\t\t\t\t\tthis.angles.push(a);\n\t\t\t\t}\t\t\t\t\n\t\t\t\t\n\t\t\t\tthis.getMarkerPoints = function() { return this.points; }\n\t\t\t\tthis.getMarkerAngles = function() {\n\t\t\t\t\tfor (var i=0; i<this.angles.length; i++) {\n\t\t\t\t\t\tif (this.angles[i] == null) {\n\t\t\t\t\t\t\tfor (var j=i+1; j<this.angles.length; j++) {\n\t\t\t\t\t\t\t\tif (this.angles[j] != null) {\n\t\t\t\t\t\t\t\t\tthis.angles[i] = this.angles[j];\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn this.angles;\n\t\t\t\t}\n\t\t\t})(d);\n\n\t\t\tthis.path = function(ctx) {\n\t\t\t\tvar pp = this.PathParser;\n\t\t\t\tpp.reset();\n\n\t\t\t\tvar bb = new svg.BoundingBox();\n\t\t\t\tif (ctx != null) ctx.beginPath();\n\t\t\t\twhile (!pp.isEnd()) {\n\t\t\t\t\tpp.nextCommand();\n\t\t\t\t\tswitch (pp.command.toUpperCase()) {\n\t\t\t\t\tcase 'M':\n\t\t\t\t\t\tvar p = pp.getAsCurrentPoint();\n\t\t\t\t\t\tpp.addMarker(p);\n\t\t\t\t\t\tbb.addPoint(p.x, p.y);\n\t\t\t\t\t\tif (ctx != null) ctx.moveTo(p.x, p.y);\n\t\t\t\t\t\tpp.start = pp.current;\n\t\t\t\t\t\twhile (!pp.isCommandOrEnd()) {\n\t\t\t\t\t\t\tvar p = pp.getAsCurrentPoint();\n\t\t\t\t\t\t\tpp.addMarker(p, pp.start);\n\t\t\t\t\t\t\tbb.addPoint(p.x, p.y);\n\t\t\t\t\t\t\tif (ctx != null) ctx.lineTo(p.x, p.y);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'L':\n\t\t\t\t\t\twhile (!pp.isCommandOrEnd()) {\n\t\t\t\t\t\t\tvar c = pp.current;\n\t\t\t\t\t\t\tvar p = pp.getAsCurrentPoint();\n\t\t\t\t\t\t\tpp.addMarker(p, c);\n\t\t\t\t\t\t\tbb.addPoint(p.x, p.y);\n\t\t\t\t\t\t\tif (ctx != null) ctx.lineTo(p.x, p.y);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'H':\n\t\t\t\t\t\twhile (!pp.isCommandOrEnd()) {\n\t\t\t\t\t\t\tvar newP = new svg.Point((pp.isRelativeCommand() ? pp.current.x : 0) + pp.getScalar(), pp.current.y);\n\t\t\t\t\t\t\tpp.addMarker(newP, pp.current);\n\t\t\t\t\t\t\tpp.current = newP;\n\t\t\t\t\t\t\tbb.addPoint(pp.current.x, pp.current.y);\n\t\t\t\t\t\t\tif (ctx != null) ctx.lineTo(pp.current.x, pp.current.y);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'V':\n\t\t\t\t\t\twhile (!pp.isCommandOrEnd()) {\n\t\t\t\t\t\t\tvar newP = new svg.Point(pp.current.x, (pp.isRelativeCommand() ? pp.current.y : 0) + pp.getScalar());\n\t\t\t\t\t\t\tpp.addMarker(newP, pp.current);\n\t\t\t\t\t\t\tpp.current = newP;\n\t\t\t\t\t\t\tbb.addPoint(pp.current.x, pp.current.y);\n\t\t\t\t\t\t\tif (ctx != null) ctx.lineTo(pp.current.x, pp.current.y);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'C':\n\t\t\t\t\t\twhile (!pp.isCommandOrEnd()) {\n\t\t\t\t\t\t\tvar curr = pp.current;\n\t\t\t\t\t\t\tvar p1 = pp.getPoint();\n\t\t\t\t\t\t\tvar cntrl = pp.getAsControlPoint();\n\t\t\t\t\t\t\tvar cp = pp.getAsCurrentPoint();\n\t\t\t\t\t\t\tpp.addMarker(cp, cntrl, p1);\n\t\t\t\t\t\t\tbb.addBezierCurve(curr.x, curr.y, p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);\n\t\t\t\t\t\t\tif (ctx != null) ctx.bezierCurveTo(p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'S':\n\t\t\t\t\t\twhile (!pp.isCommandOrEnd()) {\n\t\t\t\t\t\t\tvar curr = pp.current;\n\t\t\t\t\t\t\tvar p1 = pp.getReflectedControlPoint();\n\t\t\t\t\t\t\tvar cntrl = pp.getAsControlPoint();\n\t\t\t\t\t\t\tvar cp = pp.getAsCurrentPoint();\n\t\t\t\t\t\t\tpp.addMarker(cp, cntrl, p1);\n\t\t\t\t\t\t\tbb.addBezierCurve(curr.x, curr.y, p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);\n\t\t\t\t\t\t\tif (ctx != null) ctx.bezierCurveTo(p1.x, p1.y, cntrl.x, cntrl.y, cp.x, cp.y);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'Q':\n\t\t\t\t\t\twhile (!pp.isCommandOrEnd()) {\n\t\t\t\t\t\t\tvar curr = pp.current;\n\t\t\t\t\t\t\tvar cntrl = pp.getAsControlPoint();\n\t\t\t\t\t\t\tvar cp = pp.getAsCurrentPoint();\n\t\t\t\t\t\t\tpp.addMarker(cp, cntrl, cntrl);\n\t\t\t\t\t\t\tbb.addQuadraticCurve(curr.x, curr.y, cntrl.x, cntrl.y, cp.x, cp.y);\n\t\t\t\t\t\t\tif (ctx != null) ctx.quadraticCurveTo(cntrl.x, cntrl.y, cp.x, cp.y);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'T':\n\t\t\t\t\t\twhile (!pp.isCommandOrEnd()) {\n\t\t\t\t\t\t\tvar curr = pp.current;\n\t\t\t\t\t\t\tvar cntrl = pp.getReflectedControlPoint();\n\t\t\t\t\t\t\tpp.control = cntrl;\n\t\t\t\t\t\t\tvar cp = pp.getAsCurrentPoint();\n\t\t\t\t\t\t\tpp.addMarker(cp, cntrl, cntrl);\n\t\t\t\t\t\t\tbb.addQuadraticCurve(curr.x, curr.y, cntrl.x, cntrl.y, cp.x, cp.y);\n\t\t\t\t\t\t\tif (ctx != null) ctx.quadraticCurveTo(cntrl.x, cntrl.y, cp.x, cp.y);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'A':\n\t\t\t\t\t\twhile (!pp.isCommandOrEnd()) {\n\t\t\t\t\t\t    var curr = pp.current;\n\t\t\t\t\t\t\tvar rx = pp.getScalar();\n\t\t\t\t\t\t\tvar ry = pp.getScalar();\n\t\t\t\t\t\t\tvar xAxisRotation = pp.getScalar() * (Math.PI / 180.0);\n\t\t\t\t\t\t\tvar largeArcFlag = pp.getScalar();\n\t\t\t\t\t\t\tvar sweepFlag = pp.getScalar();\n\t\t\t\t\t\t\tvar cp = pp.getAsCurrentPoint();\n\n\t\t\t\t\t\t\t// Conversion from endpoint to center parameterization\n\t\t\t\t\t\t\t// http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes\n\t\t\t\t\t\t\t// x1', y1'\n\t\t\t\t\t\t\tvar currp = new svg.Point(\n\t\t\t\t\t\t\t\tMath.cos(xAxisRotation) * (curr.x - cp.x) / 2.0 + Math.sin(xAxisRotation) * (curr.y - cp.y) / 2.0,\n\t\t\t\t\t\t\t\t-Math.sin(xAxisRotation) * (curr.x - cp.x) / 2.0 + Math.cos(xAxisRotation) * (curr.y - cp.y) / 2.0\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t// adjust radii\n\t\t\t\t\t\t\tvar l = Math.pow(currp.x,2)/Math.pow(rx,2)+Math.pow(currp.y,2)/Math.pow(ry,2);\n\t\t\t\t\t\t\tif (l > 1) {\n\t\t\t\t\t\t\t\trx *= Math.sqrt(l);\n\t\t\t\t\t\t\t\try *= Math.sqrt(l);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// cx', cy'\n\t\t\t\t\t\t\tvar s = (largeArcFlag == sweepFlag ? -1 : 1) * Math.sqrt(\n\t\t\t\t\t\t\t\t((Math.pow(rx,2)*Math.pow(ry,2))-(Math.pow(rx,2)*Math.pow(currp.y,2))-(Math.pow(ry,2)*Math.pow(currp.x,2))) /\n\t\t\t\t\t\t\t\t(Math.pow(rx,2)*Math.pow(currp.y,2)+Math.pow(ry,2)*Math.pow(currp.x,2))\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tif (isNaN(s)) s = 0;\n\t\t\t\t\t\t\tvar cpp = new svg.Point(s * rx * currp.y / ry, s * -ry * currp.x / rx);\n\t\t\t\t\t\t\t// cx, cy\n\t\t\t\t\t\t\tvar centp = new svg.Point(\n\t\t\t\t\t\t\t\t(curr.x + cp.x) / 2.0 + Math.cos(xAxisRotation) * cpp.x - Math.sin(xAxisRotation) * cpp.y,\n\t\t\t\t\t\t\t\t(curr.y + cp.y) / 2.0 + Math.sin(xAxisRotation) * cpp.x + Math.cos(xAxisRotation) * cpp.y\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t// vector magnitude\n\t\t\t\t\t\t\tvar m = function(v) { return Math.sqrt(Math.pow(v[0],2) + Math.pow(v[1],2)); }\n\t\t\t\t\t\t\t// ratio between two vectors\n\t\t\t\t\t\t\tvar r = function(u, v) { return (u[0]*v[0]+u[1]*v[1]) / (m(u)*m(v)) }\n\t\t\t\t\t\t\t// angle between two vectors\n\t\t\t\t\t\t\tvar a = function(u, v) { return (u[0]*v[1] < u[1]*v[0] ? -1 : 1) * Math.acos(r(u,v)); }\n\t\t\t\t\t\t\t// initial angle\n\t\t\t\t\t\t\tvar a1 = a([1,0], [(currp.x-cpp.x)/rx,(currp.y-cpp.y)/ry]);\n\t\t\t\t\t\t\t// angle delta\n\t\t\t\t\t\t\tvar u = [(currp.x-cpp.x)/rx,(currp.y-cpp.y)/ry];\n\t\t\t\t\t\t\tvar v = [(-currp.x-cpp.x)/rx,(-currp.y-cpp.y)/ry];\n\t\t\t\t\t\t\tvar ad = a(u, v);\n\t\t\t\t\t\t\tif (r(u,v) <= -1) ad = Math.PI;\n\t\t\t\t\t\t\tif (r(u,v) >= 1) ad = 0;\n\n\t\t\t\t\t\t\tif (sweepFlag == 0 && ad > 0) ad = ad - 2 * Math.PI;\n\t\t\t\t\t\t\tif (sweepFlag == 1 && ad < 0) ad = ad + 2 * Math.PI;\n\n\t\t\t\t\t\t\t// for markers\n\t\t\t\t\t\t\tvar halfWay = new svg.Point(\n\t\t\t\t\t\t\t\tcentp.x - rx * Math.cos((a1 + ad) / 2),\n\t\t\t\t\t\t\t\tcentp.y - ry * Math.sin((a1 + ad) / 2)\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\tpp.addMarkerAngle(halfWay, (a1 + ad) / 2 + (sweepFlag == 0 ? 1 : -1) * Math.PI / 2);\n\t\t\t\t\t\t\tpp.addMarkerAngle(cp, ad + (sweepFlag == 0 ? 1 : -1) * Math.PI / 2);\n\n\t\t\t\t\t\t\tbb.addPoint(cp.x, cp.y); // TODO: this is too naive, make it better\n\t\t\t\t\t\t\tif (ctx != null) {\n\t\t\t\t\t\t\t\tvar r = rx > ry ? rx : ry;\n\t\t\t\t\t\t\t\tvar sx = rx > ry ? 1 : rx / ry;\n\t\t\t\t\t\t\t\tvar sy = rx > ry ? ry / rx : 1;\n\n\t\t\t\t\t\t\t\tctx.translate(centp.x, centp.y);\n\t\t\t\t\t\t\t\tctx.rotate(xAxisRotation);\n\t\t\t\t\t\t\t\tctx.scale(sx, sy);\n\t\t\t\t\t\t\t\tctx.arc(0, 0, r, a1, a1 + ad, 1 - sweepFlag);\n\t\t\t\t\t\t\t\tctx.scale(1/sx, 1/sy);\n\t\t\t\t\t\t\t\tctx.rotate(-xAxisRotation);\n\t\t\t\t\t\t\t\tctx.translate(-centp.x, -centp.y);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase 'Z':\n\t\t\t\t\t\tif (ctx != null) ctx.closePath();\n\t\t\t\t\t\tpp.current = pp.start;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn bb;\n\t\t\t}\n\n\t\t\tthis.getMarkers = function() {\n\t\t\t\tvar points = this.PathParser.getMarkerPoints();\n\t\t\t\tvar angles = this.PathParser.getMarkerAngles();\n\t\t\t\t\n\t\t\t\tvar markers = [];\n\t\t\t\tfor (var i=0; i<points.length; i++) {\n\t\t\t\t\tmarkers.push([points[i], angles[i]]);\n\t\t\t\t}\n\t\t\t\treturn markers;\n\t\t\t}\n\t\t}\n\t\tsvg.Element.path.prototype = new svg.Element.PathElementBase;\n\t\t\n\t\t// pattern element\n\t\tsvg.Element.pattern = function(node) {\n\t\t\tthis.base = svg.Element.ElementBase;\n\t\t\tthis.base(node);\n\t\t\t\n\t\t\tthis.createPattern = function(ctx, element) {\n\t\t\t\t// render me using a temporary svg element\n\t\t\t\tvar tempSvg = new svg.Element.svg();\n\t\t\t\ttempSvg.attributes['viewBox'] = new svg.Property('viewBox', this.attribute('viewBox').value);\n\t\t\t\ttempSvg.attributes['x'] = new svg.Property('x', this.attribute('x').value);\n\t\t\t\ttempSvg.attributes['y'] = new svg.Property('y', this.attribute('y').value);\n\t\t\t\ttempSvg.attributes['width'] = new svg.Property('width', this.attribute('width').value);\n\t\t\t\ttempSvg.attributes['height'] = new svg.Property('height', this.attribute('height').value);\n\t\t\t\ttempSvg.children = this.children;\n\t\t\t\t\n\t\t\t\tvar c = document.createElement('canvas');\n\t\t\t\tc.width = this.attribute('width').Length.toPixels('x');\n\t\t\t\tc.height = this.attribute('height').Length.toPixels('y');\n\t\t\t\ttempSvg.render(c.getContext('2d'));\t\t\n\t\t\t\treturn ctx.createPattern(c, 'repeat');\n\t\t\t}\n\t\t}\n\t\tsvg.Element.pattern.prototype = new svg.Element.ElementBase;\n\t\t\n\t\t// marker element\n\t\tsvg.Element.marker = function(node) {\n\t\t\tthis.base = svg.Element.ElementBase;\n\t\t\tthis.base(node);\n\t\t\t\n\t\t\tthis.baseRender = this.render;\n\t\t\tthis.render = function(ctx, point, angle) {\n\t\t\t\tctx.translate(point.x, point.y);\n\t\t\t\tif (this.attribute('orient').valueOrDefault('auto') == 'auto') ctx.rotate(angle);\n\t\t\t\tif (this.attribute('markerUnits').valueOrDefault('strokeWidth') == 'strokeWidth') ctx.scale(ctx.lineWidth, ctx.lineWidth);\n\t\t\t\tctx.save();\n\t\t\t\t\t\t\t\n\t\t\t\t// render me using a temporary svg element\n\t\t\t\tvar tempSvg = new svg.Element.svg();\n\t\t\t\ttempSvg.attributes['viewBox'] = new svg.Property('viewBox', this.attribute('viewBox').value);\n\t\t\t\ttempSvg.attributes['refX'] = new svg.Property('refX', this.attribute('refX').value);\n\t\t\t\ttempSvg.attributes['refY'] = new svg.Property('refY', this.attribute('refY').value);\n\t\t\t\ttempSvg.attributes['width'] = new svg.Property('width', this.attribute('markerWidth').value);\n\t\t\t\ttempSvg.attributes['height'] = new svg.Property('height', this.attribute('markerHeight').value);\n\t\t\t\ttempSvg.attributes['fill'] = new svg.Property('fill', this.attribute('fill').valueOrDefault('black'));\n\t\t\t\ttempSvg.attributes['stroke'] = new svg.Property('stroke', this.attribute('stroke').valueOrDefault('none'));\n\t\t\t\ttempSvg.children = this.children;\n\t\t\t\ttempSvg.render(ctx);\n\t\t\t\t\n\t\t\t\tctx.restore();\n\t\t\t\tif (this.attribute('markerUnits').valueOrDefault('strokeWidth') == 'strokeWidth') ctx.scale(1/ctx.lineWidth, 1/ctx.lineWidth);\n\t\t\t\tif (this.attribute('orient').valueOrDefault('auto') == 'auto') ctx.rotate(-angle);\n\t\t\t\tctx.translate(-point.x, -point.y);\n\t\t\t}\n\t\t}\n\t\tsvg.Element.marker.prototype = new svg.Element.ElementBase;\n\t\t\n\t\t// definitions element\n\t\tsvg.Element.defs = function(node) {\n\t\t\tthis.base = svg.Element.ElementBase;\n\t\t\tthis.base(node);\t\n\t\t\t\n\t\t\tthis.render = function(ctx) {\n\t\t\t\t// NOOP\n\t\t\t}\n\t\t}\n\t\tsvg.Element.defs.prototype = new svg.Element.ElementBase;\n\t\t\n\t\t// base for gradients\n\t\tsvg.Element.GradientBase = function(node) {\n\t\t\tthis.base = svg.Element.ElementBase;\n\t\t\tthis.base(node);\n\t\t\t\n\t\t\tthis.gradientUnits = this.attribute('gradientUnits').valueOrDefault('objectBoundingBox');\n\t\t\t\n\t\t\tthis.stops = [];\t\t\t\n\t\t\tfor (var i=0; i<this.children.length; i++) {\n\t\t\t\tvar child = this.children[i];\n\t\t\t\tthis.stops.push(child);\n\t\t\t}\t\n\t\t\t\n\t\t\tthis.getGradient = function() {\n\t\t\t\t// OVERRIDE ME!\n\t\t\t}\t\t\t\n\n\t\t\tthis.createGradient = function(ctx, element) {\n\t\t\t\tvar stopsContainer = this;\n\t\t\t\tif (this.attribute('xlink:href').hasValue()) {\n\t\t\t\t\tstopsContainer = this.attribute('xlink:href').Definition.getDefinition();\n\t\t\t\t}\n\t\t\t\n\t\t\t\tvar g = this.getGradient(ctx, element);\n\t\t\t\tfor (var i=0; i<stopsContainer.stops.length; i++) {\n\t\t\t\t\tg.addColorStop(stopsContainer.stops[i].offset, stopsContainer.stops[i].color);\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tif (this.attribute('gradientTransform').hasValue()) {\n\t\t\t\t\t// render as transformed pattern on temporary canvas\n\t\t\t\t\tvar rootView = svg.ViewPort.viewPorts[0];\n\t\t\t\t\t\n\t\t\t\t\tvar rect = new svg.Element.rect();\n\t\t\t\t\trect.attributes['x'] = new svg.Property('x', -svg.MAX_VIRTUAL_PIXELS/3.0);\n\t\t\t\t\trect.attributes['y'] = new svg.Property('y', -svg.MAX_VIRTUAL_PIXELS/3.0);\n\t\t\t\t\trect.attributes['width'] = new svg.Property('width', svg.MAX_VIRTUAL_PIXELS);\n\t\t\t\t\trect.attributes['height'] = new svg.Property('height', svg.MAX_VIRTUAL_PIXELS);\n\t\t\t\t\t\n\t\t\t\t\tvar group = new svg.Element.g();\n\t\t\t\t\tgroup.attributes['transform'] = new svg.Property('transform', this.attribute('gradientTransform').value);\n\t\t\t\t\tgroup.children = [ rect ];\n\t\t\t\t\t\n\t\t\t\t\tvar tempSvg = new svg.Element.svg();\n\t\t\t\t\ttempSvg.attributes['x'] = new svg.Property('x', 0);\n\t\t\t\t\ttempSvg.attributes['y'] = new svg.Property('y', 0);\n\t\t\t\t\ttempSvg.attributes['width'] = new svg.Property('width', rootView.width);\n\t\t\t\t\ttempSvg.attributes['height'] = new svg.Property('height', rootView.height);\n\t\t\t\t\ttempSvg.children = [ group ];\n\t\t\t\t\t\n\t\t\t\t\tvar c = document.createElement('canvas');\n\t\t\t\t\tc.width = rootView.width;\n\t\t\t\t\tc.height = rootView.height;\n\t\t\t\t\tvar tempCtx = c.getContext('2d');\n\t\t\t\t\ttempCtx.fillStyle = g;\n\t\t\t\t\ttempSvg.render(tempCtx);\t\t\n\t\t\t\t\treturn tempCtx.createPattern(c, 'no-repeat');\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\treturn g;\t\t\t\t\n\t\t\t}\n\t\t}\n\t\tsvg.Element.GradientBase.prototype = new svg.Element.ElementBase;\n\t\t\n\t\t// linear gradient element\n\t\tsvg.Element.linearGradient = function(node) {\n\t\t\tthis.base = svg.Element.GradientBase;\n\t\t\tthis.base(node);\n\t\t\t\n\t\t\tthis.getGradient = function(ctx, element) {\n\t\t\t\tvar bb = element.getBoundingBox();\n\t\t\t\t\n\t\t\t\tvar x1 = (this.gradientUnits == 'objectBoundingBox' \n\t\t\t\t\t? bb.x() + bb.width() * this.attribute('x1').numValue() \n\t\t\t\t\t: this.attribute('x1').Length.toPixels('x'));\n\t\t\t\tvar y1 = (this.gradientUnits == 'objectBoundingBox' \n\t\t\t\t\t? bb.y() + bb.height() * this.attribute('y1').numValue()\n\t\t\t\t\t: this.attribute('y1').Length.toPixels('y'));\n\t\t\t\tvar x2 = (this.gradientUnits == 'objectBoundingBox' \n\t\t\t\t\t? bb.x() + bb.width() * this.attribute('x2').numValue()\n\t\t\t\t\t: this.attribute('x2').Length.toPixels('x'));\n\t\t\t\tvar y2 = (this.gradientUnits == 'objectBoundingBox' \n\t\t\t\t\t? bb.y() + bb.height() * this.attribute('y2').numValue()\n\t\t\t\t\t: this.attribute('y2').Length.toPixels('y'));\n\n\t\t\t\treturn ctx.createLinearGradient(x1, y1, x2, y2);\n\t\t\t}\n\t\t}\n\t\tsvg.Element.linearGradient.prototype = new svg.Element.GradientBase;\n\t\t\n\t\t// radial gradient element\n\t\tsvg.Element.radialGradient = function(node) {\n\t\t\tthis.base = svg.Element.GradientBase;\n\t\t\tthis.base(node);\n\t\t\t\n\t\t\tthis.getGradient = function(ctx, element) {\n\t\t\t\tvar bb = element.getBoundingBox();\n\t\t\t\t\n\t\t\t\tvar cx = (this.gradientUnits == 'objectBoundingBox' \n\t\t\t\t\t? bb.x() + bb.width() * this.attribute('cx').numValue() \n\t\t\t\t\t: this.attribute('cx').Length.toPixels('x'));\n\t\t\t\tvar cy = (this.gradientUnits == 'objectBoundingBox' \n\t\t\t\t\t? bb.y() + bb.height() * this.attribute('cy').numValue() \n\t\t\t\t\t: this.attribute('cy').Length.toPixels('y'));\n\t\t\t\t\n\t\t\t\tvar fx = cx;\n\t\t\t\tvar fy = cy;\n\t\t\t\tif (this.attribute('fx').hasValue()) {\n\t\t\t\t\tfx = (this.gradientUnits == 'objectBoundingBox' \n\t\t\t\t\t? bb.x() + bb.width() * this.attribute('fx').numValue() \n\t\t\t\t\t: this.attribute('fx').Length.toPixels('x'));\n\t\t\t\t}\n\t\t\t\tif (this.attribute('fy').hasValue()) {\n\t\t\t\t\tfy = (this.gradientUnits == 'objectBoundingBox' \n\t\t\t\t\t? bb.y() + bb.height() * this.attribute('fy').numValue() \n\t\t\t\t\t: this.attribute('fy').Length.toPixels('y'));\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\tvar r = (this.gradientUnits == 'objectBoundingBox' \n\t\t\t\t\t? (bb.width() + bb.height()) / 2.0 * this.attribute('r').numValue()\n\t\t\t\t\t: this.attribute('r').Length.toPixels());\n\t\t\t\t\n\t\t\t\treturn ctx.createRadialGradient(fx, fy, 0, cx, cy, r);\n\t\t\t}\n\t\t}\n\t\tsvg.Element.radialGradient.prototype = new svg.Element.GradientBase;\n\t\t\n\t\t// gradient stop element\n\t\tsvg.Element.stop = function(node) {\n\t\t\tthis.base = svg.Element.ElementBase;\n\t\t\tthis.base(node);\n\t\t\t\n\t\t\tthis.offset = this.attribute('offset').numValue();\n\t\t\t\n\t\t\tvar stopColor = this.style('stop-color');\n\t\t\tif (this.style('stop-opacity').hasValue()) stopColor = stopColor.Color.addOpacity(this.style('stop-opacity').value);\n\t\t\tthis.color = stopColor.value;\n\t\t}\n\t\tsvg.Element.stop.prototype = new svg.Element.ElementBase;\n\t\t\n\t\t// animation base element\n\t\tsvg.Element.AnimateBase = function(node) {\n\t\t\tthis.base = svg.Element.ElementBase;\n\t\t\tthis.base(node);\n\t\t\t\n\t\t\tsvg.Animations.push(this);\n\t\t\t\n\t\t\tthis.duration = 0.0;\n\t\t\tthis.begin = this.attribute('begin').Time.toMilliseconds();\n\t\t\tthis.maxDuration = this.begin + this.attribute('dur').Time.toMilliseconds();\n\t\t\t\n\t\t\tthis.getProperty = function() {\n\t\t\t\tvar attributeType = this.attribute('attributeType').value;\n\t\t\t\tvar attributeName = this.attribute('attributeName').value;\n\t\t\t\t\n\t\t\t\tif (attributeType == 'CSS') {\n\t\t\t\t\treturn this.parent.style(attributeName, true);\n\t\t\t\t}\n\t\t\t\treturn this.parent.attribute(attributeName, true);\t\t\t\n\t\t\t};\n\t\t\t\n\t\t\tthis.initialValue = null;\n\t\t\tthis.removed = false;\t\t\t\n\n\t\t\tthis.calcValue = function() {\n\t\t\t\t// OVERRIDE ME!\n\t\t\t\treturn '';\n\t\t\t}\n\t\t\t\n\t\t\tthis.update = function(delta) {\t\n\t\t\t\t// set initial value\n\t\t\t\tif (this.initialValue == null) {\n\t\t\t\t\tthis.initialValue = this.getProperty().value;\n\t\t\t\t}\n\t\t\t\n\t\t\t\t// if we're past the end time\n\t\t\t\tif (this.duration > this.maxDuration) {\n\t\t\t\t\t// loop for indefinitely repeating animations\n\t\t\t\t\tif (this.attribute('repeatCount').value == 'indefinite') {\n\t\t\t\t\t\tthis.duration = 0.0\n\t\t\t\t\t}\n\t\t\t\t\telse if (this.attribute('fill').valueOrDefault('remove') == 'remove' && !this.removed) {\n\t\t\t\t\t\tthis.removed = true;\n\t\t\t\t\t\tthis.getProperty().value = this.initialValue;\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\treturn false; // no updates made\n\t\t\t\t\t}\n\t\t\t\t}\t\t\t\n\t\t\t\tthis.duration = this.duration + delta;\n\t\t\t\n\t\t\t\t// if we're past the begin time\n\t\t\t\tvar updated = false;\n\t\t\t\tif (this.begin < this.duration) {\n\t\t\t\t\tvar newValue = this.calcValue(); // tween\n\t\t\t\t\t\n\t\t\t\t\tif (this.attribute('type').hasValue()) {\n\t\t\t\t\t\t// for transform, etc.\n\t\t\t\t\t\tvar type = this.attribute('type').value;\n\t\t\t\t\t\tnewValue = type + '(' + newValue + ')';\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tthis.getProperty().value = newValue;\n\t\t\t\t\tupdated = true;\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\treturn updated;\n\t\t\t}\n\t\t\t\n\t\t\t// fraction of duration we've covered\n\t\t\tthis.progress = function() {\n\t\t\t\treturn ((this.duration - this.begin) / (this.maxDuration - this.begin));\n\t\t\t}\t\t\t\n\t\t}\n\t\tsvg.Element.AnimateBase.prototype = new svg.Element.ElementBase;\n\t\t\n\t\t// animate element\n\t\tsvg.Element.animate = function(node) {\n\t\t\tthis.base = svg.Element.AnimateBase;\n\t\t\tthis.base(node);\n\t\t\t\n\t\t\tthis.calcValue = function() {\n\t\t\t\tvar from = this.attribute('from').numValue();\n\t\t\t\tvar to = this.attribute('to').numValue();\n\t\t\t\t\n\t\t\t\t// tween value linearly\n\t\t\t\treturn from + (to - from) * this.progress(); \n\t\t\t};\n\t\t}\n\t\tsvg.Element.animate.prototype = new svg.Element.AnimateBase;\n\t\t\t\n\t\t// animate color element\n\t\tsvg.Element.animateColor = function(node) {\n\t\t\tthis.base = svg.Element.AnimateBase;\n\t\t\tthis.base(node);\n\n\t\t\tthis.calcValue = function() {\n\t\t\t\tvar from = new RGBColor(this.attribute('from').value);\n\t\t\t\tvar to = new RGBColor(this.attribute('to').value);\n\t\t\t\t\n\t\t\t\tif (from.ok && to.ok) {\n\t\t\t\t\t// tween color linearly\n\t\t\t\t\tvar r = from.r + (to.r - from.r) * this.progress();\n\t\t\t\t\tvar g = from.g + (to.g - from.g) * this.progress();\n\t\t\t\t\tvar b = from.b + (to.b - from.b) * this.progress();\n\t\t\t\t\treturn 'rgb('+parseInt(r,10)+','+parseInt(g,10)+','+parseInt(b,10)+')';\n\t\t\t\t}\n\t\t\t\treturn this.attribute('from').value;\n\t\t\t};\n\t\t}\n\t\tsvg.Element.animateColor.prototype = new svg.Element.AnimateBase;\n\t\t\n\t\t// animate transform element\n\t\tsvg.Element.animateTransform = function(node) {\n\t\t\tthis.base = svg.Element.animate;\n\t\t\tthis.base(node);\n\t\t}\n\t\tsvg.Element.animateTransform.prototype = new svg.Element.animate;\n\t\t\n\t\t// font element\n\t\tsvg.Element.font = function(node) {\n\t\t\tthis.base = svg.Element.ElementBase;\n\t\t\tthis.base(node);\n\n\t\t\tthis.horizAdvX = this.attribute('horiz-adv-x').numValue();\t\t\t\n\t\t\t\n\t\t\tthis.isRTL = false;\n\t\t\tthis.isArabic = false;\n\t\t\tthis.fontFace = null;\n\t\t\tthis.missingGlyph = null;\n\t\t\tthis.glyphs = [];\t\t\t\n\t\t\tfor (var i=0; i<this.children.length; i++) {\n\t\t\t\tvar child = this.children[i];\n\t\t\t\tif (child.type == 'font-face') {\n\t\t\t\t\tthis.fontFace = child;\n\t\t\t\t\tif (child.style('font-family').hasValue()) {\n\t\t\t\t\t\tsvg.Definitions[child.style('font-family').value] = this;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse if (child.type == 'missing-glyph') this.missingGlyph = child;\n\t\t\t\telse if (child.type == 'glyph') {\n\t\t\t\t\tif (child.arabicForm != '') {\n\t\t\t\t\t\tthis.isRTL = true;\n\t\t\t\t\t\tthis.isArabic = true;\n\t\t\t\t\t\tif (typeof(this.glyphs[child.unicode]) == 'undefined') this.glyphs[child.unicode] = [];\n\t\t\t\t\t\tthis.glyphs[child.unicode][child.arabicForm] = child;\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tthis.glyphs[child.unicode] = child;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\t\n\t\t}\n\t\tsvg.Element.font.prototype = new svg.Element.ElementBase;\n\t\t\n\t\t// font-face element\n\t\tsvg.Element.fontface = function(node) {\n\t\t\tthis.base = svg.Element.ElementBase;\n\t\t\tthis.base(node);\t\n\t\t\t\n\t\t\tthis.ascent = this.attribute('ascent').value;\n\t\t\tthis.descent = this.attribute('descent').value;\n\t\t\tthis.unitsPerEm = this.attribute('units-per-em').numValue();\t\t\t\t\n\t\t}\n\t\tsvg.Element.fontface.prototype = new svg.Element.ElementBase;\n\t\t\n\t\t// missing-glyph element\n\t\tsvg.Element.missingglyph = function(node) {\n\t\t\tthis.base = svg.Element.path;\n\t\t\tthis.base(node);\t\n\t\t\t\n\t\t\tthis.horizAdvX = 0;\n\t\t}\n\t\tsvg.Element.missingglyph.prototype = new svg.Element.path;\n\t\t\n\t\t// glyph element\n\t\tsvg.Element.glyph = function(node) {\n\t\t\tthis.base = svg.Element.path;\n\t\t\tthis.base(node);\t\n\t\t\t\n\t\t\tthis.horizAdvX = this.attribute('horiz-adv-x').numValue();\n\t\t\tthis.unicode = this.attribute('unicode').value;\n\t\t\tthis.arabicForm = this.attribute('arabic-form').value;\n\t\t}\n\t\tsvg.Element.glyph.prototype = new svg.Element.path;\n\t\t\n\t\t// text element\n\t\tsvg.Element.text = function(node) {\n\t\t\tthis.base = svg.Element.RenderedElementBase;\n\t\t\tthis.base(node);\n\t\t\t\n\t\t\tif (node != null) {\n\t\t\t\t// add children\n\t\t\t\tthis.children = [];\n\t\t\t\tfor (var i=0; i<node.childNodes.length; i++) {\n\t\t\t\t\tvar childNode = node.childNodes[i];\n\t\t\t\t\tif (childNode.nodeType == 1) { // capture tspan and tref nodes\n\t\t\t\t\t\tthis.addChild(childNode, true);\n\t\t\t\t\t}\n\t\t\t\t\telse if (childNode.nodeType == 3) { // capture text\n\t\t\t\t\t\tthis.addChild(new svg.Element.tspan(childNode), false);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\tthis.baseSetContext = this.setContext;\n\t\t\tthis.setContext = function(ctx) {\n\t\t\t\tthis.baseSetContext(ctx);\n\t\t\t\tif (this.style('dominant-baseline').hasValue()) ctx.textBaseline = this.style('dominant-baseline').value;\n\t\t\t\tif (this.style('alignment-baseline').hasValue()) ctx.textBaseline = this.style('alignment-baseline').value;\n\t\t\t}\n\t\t\t\n\t\t\tthis.renderChildren = function(ctx) {\n\t\t\t\tvar textAnchor = this.style('text-anchor').valueOrDefault('start');\n\t\t\t\tvar x = this.attribute('x').Length.toPixels('x');\n\t\t\t\tvar y = this.attribute('y').Length.toPixels('y');\n\t\t\t\tfor (var i=0; i<this.children.length; i++) {\n\t\t\t\t\tvar child = this.children[i];\n\t\t\t\t\n\t\t\t\t\tif (child.attribute('x').hasValue()) {\n\t\t\t\t\t\tchild.x = child.attribute('x').Length.toPixels('x');\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tif (child.attribute('dx').hasValue()) x += child.attribute('dx').Length.toPixels('x');\n\t\t\t\t\t\tchild.x = x;\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\tvar childLength = child.measureText(ctx);\n\t\t\t\t\tif (textAnchor != 'start' && (i==0 || child.attribute('x').hasValue())) { // new group?\n\t\t\t\t\t\t// loop through rest of children\n\t\t\t\t\t\tvar groupLength = childLength;\n\t\t\t\t\t\tfor (var j=i+1; j<this.children.length; j++) {\n\t\t\t\t\t\t\tvar childInGroup = this.children[j];\n\t\t\t\t\t\t\tif (childInGroup.attribute('x').hasValue()) break; // new group\n\t\t\t\t\t\t\tgroupLength += childInGroup.measureText(ctx);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tchild.x -= (textAnchor == 'end' ? groupLength : groupLength / 2.0);\n\t\t\t\t\t}\n\t\t\t\t\tx = child.x + childLength;\n\t\t\t\t\t\n\t\t\t\t\tif (child.attribute('y').hasValue()) {\n\t\t\t\t\t\tchild.y = child.attribute('y').Length.toPixels('y');\n\t\t\t\t\t}\n\t\t\t\t\telse {\n\t\t\t\t\t\tif (child.attribute('dy').hasValue()) y += child.attribute('dy').Length.toPixels('y');\n\t\t\t\t\t\tchild.y = y;\n\t\t\t\t\t}\t\n\t\t\t\t\ty = child.y;\n\t\t\t\t\t\n\t\t\t\t\tchild.render(ctx);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tsvg.Element.text.prototype = new svg.Element.RenderedElementBase;\n\t\t\n\t\t// text base\n\t\tsvg.Element.TextElementBase = function(node) {\n\t\t\tthis.base = svg.Element.RenderedElementBase;\n\t\t\tthis.base(node);\n\t\t\t\n\t\t\tthis.getGlyph = function(font, text, i) {\n\t\t\t\tvar c = text[i];\n\t\t\t\tvar glyph = null;\n\t\t\t\tif (font.isArabic) {\n\t\t\t\t\tvar arabicForm = 'isolated';\n\t\t\t\t\tif ((i==0 || text[i-1]==' ') && i<text.length-2 && text[i+1]!=' ') arabicForm = 'terminal'; \n\t\t\t\t\tif (i>0 && text[i-1]!=' ' && i<text.length-2 && text[i+1]!=' ') arabicForm = 'medial';\n\t\t\t\t\tif (i>0 && text[i-1]!=' ' && (i == text.length-1 || text[i+1]==' ')) arabicForm = 'initial';\n\t\t\t\t\tif (typeof(font.glyphs[c]) != 'undefined') {\n\t\t\t\t\t\tglyph = font.glyphs[c][arabicForm];\n\t\t\t\t\t\tif (glyph == null && font.glyphs[c].type == 'glyph') glyph = font.glyphs[c];\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\tglyph = font.glyphs[c];\n\t\t\t\t}\n\t\t\t\tif (glyph == null) glyph = font.missingGlyph;\n\t\t\t\treturn glyph;\n\t\t\t}\n\t\t\t\n\t\t\tthis.renderChildren = function(ctx) {\n\t\t\t\tvar customFont = this.parent.style('font-family').Definition.getDefinition();\n\t\t\t\tif (customFont != null) {\n\t\t\t\t\tvar fontSize = this.parent.style('font-size').numValueOrDefault(svg.Font.Parse(svg.ctx.font).fontSize);\n\t\t\t\t\tvar fontStyle = this.parent.style('font-style').valueOrDefault(svg.Font.Parse(svg.ctx.font).fontStyle);\n\t\t\t\t\tvar text = this.getText();\n\t\t\t\t\tif (customFont.isRTL) text = text.split(\"\").reverse().join(\"\");\n\t\t\t\t\t\n\t\t\t\t\tvar dx = svg.ToNumberArray(this.parent.attribute('dx').value);\n\t\t\t\t\tfor (var i=0; i<text.length; i++) {\n\t\t\t\t\t\tvar glyph = this.getGlyph(customFont, text, i);\n\t\t\t\t\t\tvar scale = fontSize / customFont.fontFace.unitsPerEm;\n\t\t\t\t\t\tctx.translate(this.x, this.y);\n\t\t\t\t\t\tctx.scale(scale, -scale);\n\t\t\t\t\t\tvar lw = ctx.lineWidth;\n\t\t\t\t\t\tctx.lineWidth = ctx.lineWidth * customFont.fontFace.unitsPerEm / fontSize;\n\t\t\t\t\t\tif (fontStyle == 'italic') ctx.transform(1, 0, .4, 1, 0, 0);\n\t\t\t\t\t\tglyph.render(ctx);\n\t\t\t\t\t\tif (fontStyle == 'italic') ctx.transform(1, 0, -.4, 1, 0, 0);\n\t\t\t\t\t\tctx.lineWidth = lw;\n\t\t\t\t\t\tctx.scale(1/scale, -1/scale);\n\t\t\t\t\t\tctx.translate(-this.x, -this.y);\t\n\t\t\t\t\t\t\n\t\t\t\t\t\tthis.x += fontSize * (glyph.horizAdvX || customFont.horizAdvX) / customFont.fontFace.unitsPerEm;\n\t\t\t\t\t\tif (typeof(dx[i]) != 'undefined' && !isNaN(dx[i])) {\n\t\t\t\t\t\t\tthis.x += dx[i];\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\n\t\t\t\tif (ctx.strokeStyle != '') ctx.strokeText(svg.compressSpaces(this.getText()), this.x, this.y);\n\t\t\t\tif (ctx.fillStyle != '') ctx.fillText(svg.compressSpaces(this.getText()), this.x, this.y);\n\t\t\t}\n\t\t\t\n\t\t\tthis.getText = function() {\n\t\t\t\t// OVERRIDE ME\n\t\t\t}\n\t\t\t\n\t\t\tthis.measureText = function(ctx) {\n\t\t\t\tvar customFont = this.parent.style('font-family').Definition.getDefinition();\n\t\t\t\tif (customFont != null) {\n\t\t\t\t\tvar fontSize = this.parent.style('font-size').numValueOrDefault(svg.Font.Parse(svg.ctx.font).fontSize);\n\t\t\t\t\tvar measure = 0;\n\t\t\t\t\tvar text = this.getText();\n\t\t\t\t\tif (customFont.isRTL) text = text.split(\"\").reverse().join(\"\");\n\t\t\t\t\tvar dx = svg.ToNumberArray(this.parent.attribute('dx').value);\n\t\t\t\t\tfor (var i=0; i<text.length; i++) {\n\t\t\t\t\t\tvar glyph = this.getGlyph(customFont, text, i);\n\t\t\t\t\t\tmeasure += (glyph.horizAdvX || customFont.horizAdvX) * fontSize / customFont.fontFace.unitsPerEm;\n\t\t\t\t\t\tif (typeof(dx[i]) != 'undefined' && !isNaN(dx[i])) {\n\t\t\t\t\t\t\tmeasure += dx[i];\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn measure;\n\t\t\t\t}\n\t\t\t\n\t\t\t\tvar textToMeasure = svg.compressSpaces(this.getText());\n\t\t\t\tif (!ctx.measureText) return textToMeasure.length * 10;\n\t\t\t\t\n\t\t\t\tctx.save();\n\t\t\t\tthis.setContext(ctx);\n\t\t\t\tvar width = ctx.measureText(textToMeasure).width;\n\t\t\t\tctx.restore();\n\t\t\t\treturn width;\n\t\t\t}\n\t\t}\n\t\tsvg.Element.TextElementBase.prototype = new svg.Element.RenderedElementBase;\n\t\t\n\t\t// tspan \n\t\tsvg.Element.tspan = function(node) {\n\t\t\tthis.base = svg.Element.TextElementBase;\n\t\t\tthis.base(node);\n\t\t\t\n\t\t\tthis.text = node.nodeType == 3 ? node.nodeValue : // text\n\t\t\t\t\t\tnode.childNodes.length > 0 ? node.childNodes[0].nodeValue : // element\n\t\t\t\t\t\tnode.text;\n\t\t\tthis.getText = function() {\n\t\t\t\treturn this.text;\n\t\t\t}\n\t\t}\n\t\tsvg.Element.tspan.prototype = new svg.Element.TextElementBase;\n\t\t\n\t\t// tref\n\t\tsvg.Element.tref = function(node) {\n\t\t\tthis.base = svg.Element.TextElementBase;\n\t\t\tthis.base(node);\n\t\t\t\n\t\t\tthis.getText = function() {\n\t\t\t\tvar element = this.attribute('xlink:href').Definition.getDefinition();\n\t\t\t\tif (element != null) return element.children[0].getText();\n\t\t\t}\n\t\t}\n\t\tsvg.Element.tref.prototype = new svg.Element.TextElementBase;\t\t\n\t\t\n\t\t// a element\n\t\tsvg.Element.a = function(node) {\n\t\t\tthis.base = svg.Element.TextElementBase;\n\t\t\tthis.base(node);\n\t\t\t\n\t\t\tthis.hasText = true;\n\t\t\tfor (var i=0; i<node.childNodes.length; i++) {\n\t\t\t\tif (node.childNodes[i].nodeType != 3) this.hasText = false;\n\t\t\t}\n\t\t\t\n\t\t\t// this might contain text\n\t\t\tthis.text = this.hasText ? node.childNodes[0].nodeValue : '';\n\t\t\tthis.getText = function() {\n\t\t\t\treturn this.text;\n\t\t\t}\t\t\n\n\t\t\tthis.baseRenderChildren = this.renderChildren;\n\t\t\tthis.renderChildren = function(ctx) {\n\t\t\t\tif (this.hasText) {\n\t\t\t\t\t// render as text element\n\t\t\t\t\tthis.baseRenderChildren(ctx);\n\t\t\t\t\tvar fontSize = new svg.Property('fontSize', svg.Font.Parse(svg.ctx.font).fontSize);\n\t\t\t\t\tsvg.Mouse.checkBoundingBox(this, new svg.BoundingBox(this.x, this.y - fontSize.Length.toPixels('y'), this.x + this.measureText(ctx), this.y));\t\t\t\t\t\n\t\t\t\t}\n\t\t\t\telse {\n\t\t\t\t\t// render as temporary group\n\t\t\t\t\tvar g = new svg.Element.g();\n\t\t\t\t\tg.children = this.children;\n\t\t\t\t\tg.parent = this;\n\t\t\t\t\tg.render(ctx);\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\tthis.onclick = function() {\n\t\t\t\twindow.open(this.attribute('xlink:href').value);\n\t\t\t}\n\t\t\t\n\t\t\tthis.onmousemove = function() {\n\t\t\t\tsvg.ctx.canvas.style.cursor = 'pointer';\n\t\t\t}\n\t\t}\n\t\tsvg.Element.a.prototype = new svg.Element.TextElementBase;\t\t\n\t\t\n\t\t// image element\n\t\tsvg.Element.image = function(node) {\n\t\t\tthis.base = svg.Element.RenderedElementBase;\n\t\t\tthis.base(node);\n\t\t\t\n\t\t\tsvg.Images.push(this);\n\t\t\tthis.img = document.createElement('img');\n\t\t\tthis.loaded = false;\n\t\t\tvar that = this;\n\t\t\tthis.img.onload = function() { that.loaded = true; }\n\t\t\tthis.img.src = this.attribute('xlink:href').value;\n\t\t\t\n\t\t\tthis.renderChildren = function(ctx) {\n\t\t\t\tvar x = this.attribute('x').Length.toPixels('x');\n\t\t\t\tvar y = this.attribute('y').Length.toPixels('y');\n\t\t\t\t\n\t\t\t\tvar width = this.attribute('width').Length.toPixels('x');\n\t\t\t\tvar height = this.attribute('height').Length.toPixels('y');\t\t\t\n\t\t\t\tif (width == 0 || height == 0) return;\n\t\t\t\n\t\t\t\tctx.save();\n\t\t\t\tctx.translate(x, y);\n\t\t\t\tsvg.AspectRatio(ctx,\n\t\t\t\t\t\t\t\tthis.attribute('preserveAspectRatio').value,\n\t\t\t\t\t\t\t\twidth,\n\t\t\t\t\t\t\t\tthis.img.width,\n\t\t\t\t\t\t\t\theight,\n\t\t\t\t\t\t\t\tthis.img.height,\n\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t0);\t\n\t\t\t\tctx.drawImage(this.img, 0, 0);\t\t\t\n\t\t\t\tctx.restore();\n\t\t\t}\n\t\t}\n\t\tsvg.Element.image.prototype = new svg.Element.RenderedElementBase;\n\t\t\n\t\t// group element\n\t\tsvg.Element.g = function(node) {\n\t\t\tthis.base = svg.Element.RenderedElementBase;\n\t\t\tthis.base(node);\n\t\t\t\n\t\t\tthis.getBoundingBox = function() {\n\t\t\t\tvar bb = new svg.BoundingBox();\n\t\t\t\tfor (var i=0; i<this.children.length; i++) {\n\t\t\t\t\tbb.addBoundingBox(this.children[i].getBoundingBox());\n\t\t\t\t}\n\t\t\t\treturn bb;\n\t\t\t};\n\t\t}\n\t\tsvg.Element.g.prototype = new svg.Element.RenderedElementBase;\n\n\t\t// symbol element\n\t\tsvg.Element.symbol = function(node) {\n\t\t\tthis.base = svg.Element.RenderedElementBase;\n\t\t\tthis.base(node);\n\t\t\t\n\t\t\tthis.baseSetContext = this.setContext;\n\t\t\tthis.setContext = function(ctx) {\t\t\n\t\t\t\tthis.baseSetContext(ctx);\n\t\t\t\t\n\t\t\t\t// viewbox\n\t\t\t\tif (this.attribute('viewBox').hasValue()) {\t\t\t\t\n\t\t\t\t\tvar viewBox = svg.ToNumberArray(this.attribute('viewBox').value);\n\t\t\t\t\tvar minX = viewBox[0];\n\t\t\t\t\tvar minY = viewBox[1];\n\t\t\t\t\twidth = viewBox[2];\n\t\t\t\t\theight = viewBox[3];\n\t\t\t\t\t\n\t\t\t\t\tsvg.AspectRatio(ctx,\n\t\t\t\t\t\t\t\t\tthis.attribute('preserveAspectRatio').value, \n\t\t\t\t\t\t\t\t\tthis.attribute('width').Length.toPixels('x'),\n\t\t\t\t\t\t\t\t\twidth,\n\t\t\t\t\t\t\t\t\tthis.attribute('height').Length.toPixels('y'),\n\t\t\t\t\t\t\t\t\theight,\n\t\t\t\t\t\t\t\t\tminX,\n\t\t\t\t\t\t\t\t\tminY);\n\n\t\t\t\t\tsvg.ViewPort.SetCurrent(viewBox[2], viewBox[3]);\t\t\t\t\t\t\n\t\t\t\t}\n\t\t\t}\t\t\t\n\t\t}\n\t\tsvg.Element.symbol.prototype = new svg.Element.RenderedElementBase;\t\t\n\t\t\t\n\t\t// style element\n\t\tsvg.Element.style = function(node) { \n\t\t\tthis.base = svg.Element.ElementBase;\n\t\t\tthis.base(node);\n\t\t\t\n\t\t\t// text, or spaces then CDATA\n\t\t\tvar css = node.childNodes[0].nodeValue + (node.childNodes.length > 1 ? node.childNodes[1].nodeValue : '');\n\t\t\tcss = css.replace(/(\\/\\*([^*]|[\\r\\n]|(\\*+([^*\\/]|[\\r\\n])))*\\*+\\/)|(^[\\s]*\\/\\/.*)/gm, ''); // remove comments\n\t\t\tcss = svg.compressSpaces(css); // replace whitespace\n\t\t\tvar cssDefs = css.split('}');\n\t\t\tfor (var i=0; i<cssDefs.length; i++) {\n\t\t\t\tif (svg.trim(cssDefs[i]) != '') {\n\t\t\t\t\tvar cssDef = cssDefs[i].split('{');\n\t\t\t\t\tvar cssClasses = cssDef[0].split(',');\n\t\t\t\t\tvar cssProps = cssDef[1].split(';');\n\t\t\t\t\tfor (var j=0; j<cssClasses.length; j++) {\n\t\t\t\t\t\tvar cssClass = svg.trim(cssClasses[j]);\n\t\t\t\t\t\tif (cssClass != '') {\n\t\t\t\t\t\t\tvar props = {};\n\t\t\t\t\t\t\tfor (var k=0; k<cssProps.length; k++) {\n\t\t\t\t\t\t\t\tvar prop = cssProps[k].indexOf(':');\n\t\t\t\t\t\t\t\tvar name = cssProps[k].substr(0, prop);\n\t\t\t\t\t\t\t\tvar value = cssProps[k].substr(prop + 1, cssProps[k].length - prop);\n\t\t\t\t\t\t\t\tif (name != null && value != null) {\n\t\t\t\t\t\t\t\t\tprops[svg.trim(name)] = new svg.Property(svg.trim(name), svg.trim(value));\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tsvg.Styles[cssClass] = props;\n\t\t\t\t\t\t\tif (cssClass == '@font-face') {\n\t\t\t\t\t\t\t\tvar fontFamily = props['font-family'].value.replace(/\"/g,'');\n\t\t\t\t\t\t\t\tvar srcs = props['src'].value.split(',');\n\t\t\t\t\t\t\t\tfor (var s=0; s<srcs.length; s++) {\n\t\t\t\t\t\t\t\t\tif (srcs[s].indexOf('format(\"svg\")') > 0) {\n\t\t\t\t\t\t\t\t\t\tvar urlStart = srcs[s].indexOf('url');\n\t\t\t\t\t\t\t\t\t\tvar urlEnd = srcs[s].indexOf(')', urlStart);\n\t\t\t\t\t\t\t\t\t\tvar url = srcs[s].substr(urlStart + 5, urlEnd - urlStart - 6);\n\t\t\t\t\t\t\t\t\t\tvar doc = svg.parseXml(svg.ajax(url));\n\t\t\t\t\t\t\t\t\t\tvar fonts = doc.getElementsByTagName('font');\n\t\t\t\t\t\t\t\t\t\tfor (var f=0; f<fonts.length; f++) {\n\t\t\t\t\t\t\t\t\t\t\tvar font = svg.CreateElement(fonts[f]);\n\t\t\t\t\t\t\t\t\t\t\tsvg.Definitions[fontFamily] = font;\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tsvg.Element.style.prototype = new svg.Element.ElementBase;\n\t\t\n\t\t// use element \n\t\tsvg.Element.use = function(node) {\n\t\t\tthis.base = svg.Element.RenderedElementBase;\n\t\t\tthis.base(node);\n\t\t\t\n\t\t\tthis.baseSetContext = this.setContext;\n\t\t\tthis.setContext = function(ctx) {\n\t\t\t\tthis.baseSetContext(ctx);\n\t\t\t\tif (this.attribute('x').hasValue()) ctx.translate(this.attribute('x').Length.toPixels('x'), 0);\n\t\t\t\tif (this.attribute('y').hasValue()) ctx.translate(0, this.attribute('y').Length.toPixels('y'));\n\t\t\t}\n\t\t\t\n\t\t\tthis.getDefinition = function() {\n\t\t\t\tvar element = this.attribute('xlink:href').Definition.getDefinition();\n\t\t\t\tif (this.attribute('width').hasValue()) element.attribute('width', true).value = this.attribute('width').value;\n\t\t\t\tif (this.attribute('height').hasValue()) element.attribute('height', true).value = this.attribute('height').value;\n\t\t\t\treturn element;\n\t\t\t}\n\t\t\t\n\t\t\tthis.path = function(ctx) {\n\t\t\t\tvar element = this.getDefinition();\n\t\t\t\tif (element != null) element.path(ctx);\n\t\t\t}\n\t\t\t\n\t\t\tthis.renderChildren = function(ctx) {\n\t\t\t\tvar element = this.getDefinition();\n\t\t\t\tif (element != null) element.render(ctx);\n\t\t\t}\n\t\t}\n\t\tsvg.Element.use.prototype = new svg.Element.RenderedElementBase;\n\t\t\n\t\t// mask element\n\t\tsvg.Element.mask = function(node) {\n\t\t\tthis.base = svg.Element.ElementBase;\n\t\t\tthis.base(node);\n\t\t\t\t\t\t\n\t\t\tthis.apply = function(ctx, element) {\n\t\t\t\t// render as temp svg\t\n\t\t\t\tvar x = this.attribute('x').Length.toPixels('x');\n\t\t\t\tvar y = this.attribute('y').Length.toPixels('y');\n\t\t\t\tvar width = this.attribute('width').Length.toPixels('x');\n\t\t\t\tvar height = this.attribute('height').Length.toPixels('y');\n\t\t\t\t\n\t\t\t\t// temporarily remove mask to avoid recursion\n\t\t\t\tvar mask = element.attribute('mask').value;\n\t\t\t\telement.attribute('mask').value = '';\n\t\t\t\t\n\t\t\t\t\tvar cMask = document.createElement('canvas');\n\t\t\t\t\tcMask.width = x + width;\n\t\t\t\t\tcMask.height = y + height;\n\t\t\t\t\tvar maskCtx = cMask.getContext('2d');\n\t\t\t\t\tthis.renderChildren(maskCtx);\n\t\t\t\t\n\t\t\t\t\tvar c = document.createElement('canvas');\n\t\t\t\t\tc.width = x + width;\n\t\t\t\t\tc.height = y + height;\n\t\t\t\t\tvar tempCtx = c.getContext('2d');\n\t\t\t\t\telement.render(tempCtx);\n\t\t\t\t\ttempCtx.globalCompositeOperation = 'destination-in';\n\t\t\t\t\ttempCtx.fillStyle = maskCtx.createPattern(cMask, 'no-repeat');\n\t\t\t\t\ttempCtx.fillRect(0, 0, x + width, y + height);\n\t\t\t\t\t\n\t\t\t\t\tctx.fillStyle = tempCtx.createPattern(c, 'no-repeat');\n\t\t\t\t\tctx.fillRect(0, 0, x + width, y + height);\n\t\t\t\t\t\n\t\t\t\t// reassign mask\n\t\t\t\telement.attribute('mask').value = mask;\t\n\t\t\t}\n\t\t\t\n\t\t\tthis.render = function(ctx) {\n\t\t\t\t// NO RENDER\n\t\t\t}\n\t\t}\n\t\tsvg.Element.mask.prototype = new svg.Element.ElementBase;\n\t\t\n\t\t// clip element\n\t\tsvg.Element.clipPath = function(node) {\n\t\t\tthis.base = svg.Element.ElementBase;\n\t\t\tthis.base(node);\n\t\t\t\n\t\t\tthis.apply = function(ctx) {\n\t\t\t\tfor (var i=0; i<this.children.length; i++) {\n\t\t\t\t\tif (this.children[i].path) {\n\t\t\t\t\t\tthis.children[i].path(ctx);\n\t\t\t\t\t\tctx.clip();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\tthis.render = function(ctx) {\n\t\t\t\t// NO RENDER\n\t\t\t}\n\t\t}\n\t\tsvg.Element.clipPath.prototype = new svg.Element.ElementBase;\n\n\t\t// filters\n\t\tsvg.Element.filter = function(node) {\n\t\t\tthis.base = svg.Element.ElementBase;\n\t\t\tthis.base(node);\n\t\t\t\t\t\t\n\t\t\tthis.apply = function(ctx, element) {\n\t\t\t\t// render as temp svg\t\n\t\t\t\tvar bb = element.getBoundingBox();\n\t\t\t\tvar x = this.attribute('x').Length.toPixels('x');\n\t\t\t\tvar y = this.attribute('y').Length.toPixels('y');\n\t\t\t\tif (x == 0 || y == 0) {\n\t\t\t\t\tx = bb.x1;\n\t\t\t\t\ty = bb.y1;\n\t\t\t\t}\n\t\t\t\tvar width = this.attribute('width').Length.toPixels('x');\n\t\t\t\tvar height = this.attribute('height').Length.toPixels('y');\n\t\t\t\tif (width == 0 || height == 0) {\n\t\t\t\t\twidth = bb.width();\n\t\t\t\t\theight = bb.height();\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t// temporarily remove filter to avoid recursion\n\t\t\t\tvar filter = element.style('filter').value;\n\t\t\t\telement.style('filter').value = '';\n\t\t\t\t\n\t\t\t\t// max filter distance\n\t\t\t\tvar extraPercent = .20;\n\t\t\t\tvar px = extraPercent * width;\n\t\t\t\tvar py = extraPercent * height;\n\t\t\t\t\n\t\t\t\tvar c = document.createElement('canvas');\n\t\t\t\tc.width = width + 2*px;\n\t\t\t\tc.height = height + 2*py;\n\t\t\t\tvar tempCtx = c.getContext('2d');\n\t\t\t\ttempCtx.translate(-x + px, -y + py);\n\t\t\t\telement.render(tempCtx);\n\t\t\t\n\t\t\t\t// apply filters\n\t\t\t\tfor (var i=0; i<this.children.length; i++) {\n\t\t\t\t\tthis.children[i].apply(tempCtx, 0, 0, width + 2*px, height + 2*py);\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t// render on me\n\t\t\t\tctx.drawImage(c, 0, 0, width + 2*px, height + 2*py, x - px, y - py, width + 2*px, height + 2*py);\n\t\t\t\t\n\t\t\t\t// reassign filter\n\t\t\t\telement.style('filter', true).value = filter;\t\n\t\t\t}\n\t\t\t\n\t\t\tthis.render = function(ctx) {\n\t\t\t\t// NO RENDER\n\t\t\t}\t\t\n\t\t}\n\t\tsvg.Element.filter.prototype = new svg.Element.ElementBase;\n\t\t\n\t\tsvg.Element.feGaussianBlur = function(node) {\n\t\t\tthis.base = svg.Element.ElementBase;\n\t\t\tthis.base(node);\t\n\t\t\t\n\t\t\tfunction make_fgauss(sigma) {\n\t\t\t\tsigma = Math.max(sigma, 0.01);\t\t\t      \n\t\t\t\tvar len = Math.ceil(sigma * 4.0) + 1;                     \n\t\t\t\tmask = [];                               \n\t\t\t\tfor (var i = 0; i < len; i++) {                             \n\t\t\t\t\tmask[i] = Math.exp(-0.5 * (i / sigma) * (i / sigma));                                           \n\t\t\t\t}                                                           \n\t\t\t\treturn mask; \n\t\t\t}\n\t\t\t\n\t\t\tfunction normalize(mask) {\n\t\t\t\tvar sum = 0;\n\t\t\t\tfor (var i = 1; i < mask.length; i++) {\n\t\t\t\t\tsum += Math.abs(mask[i]);\n\t\t\t\t}\n\t\t\t\tsum = 2 * sum + Math.abs(mask[0]);\n\t\t\t\tfor (var i = 0; i < mask.length; i++) {\n\t\t\t\t\tmask[i] /= sum;\n\t\t\t\t}\n\t\t\t\treturn mask;\n\t\t\t}\n\t\t\t\n\t\t\tfunction convolve_even(src, dst, mask, width, height) {\n\t\t\t  for (var y = 0; y < height; y++) {\n\t\t\t\tfor (var x = 0; x < width; x++) {\n\t\t\t\t  var a = imGet(src, x, y, width, height, 3)/255;\n\t\t\t\t  for (var rgba = 0; rgba < 4; rgba++) {\t\t\t\t\t  \n\t\t\t\t\t  var sum = mask[0] * (a==0?255:imGet(src, x, y, width, height, rgba)) * (a==0||rgba==3?1:a);\n\t\t\t\t\t  for (var i = 1; i < mask.length; i++) {\n\t\t\t\t\t\tvar a1 = imGet(src, Math.max(x-i,0), y, width, height, 3)/255;\n\t\t\t\t\t    var a2 = imGet(src, Math.min(x+i, width-1), y, width, height, 3)/255;\n\t\t\t\t\t\tsum += mask[i] * \n\t\t\t\t\t\t  ((a1==0?255:imGet(src, Math.max(x-i,0), y, width, height, rgba)) * (a1==0||rgba==3?1:a1) + \n\t\t\t\t\t\t   (a2==0?255:imGet(src, Math.min(x+i, width-1), y, width, height, rgba)) * (a2==0||rgba==3?1:a2));\n\t\t\t\t\t  }\n\t\t\t\t\t  imSet(dst, y, x, height, width, rgba, sum);\n\t\t\t\t  }\t\t\t  \n\t\t\t\t}\n\t\t\t  }\n\t\t\t}\t\t\n\n\t\t\tfunction imGet(img, x, y, width, height, rgba) {\n\t\t\t\treturn img[y*width*4 + x*4 + rgba];\n\t\t\t}\n\t\t\t\n\t\t\tfunction imSet(img, x, y, width, height, rgba, val) {\n\t\t\t\timg[y*width*4 + x*4 + rgba] = val;\n\t\t\t}\n\t\t\t\t\t\t\n\t\t\tfunction blur(ctx, width, height, sigma)\n\t\t\t{\n\t\t\t\tvar srcData = ctx.getImageData(0, 0, width, height);\n\t\t\t\tvar mask = make_fgauss(sigma);\n\t\t\t\tmask = normalize(mask);\n\t\t\t\ttmp = [];\n\t\t\t\tconvolve_even(srcData.data, tmp, mask, width, height);\n\t\t\t\tconvolve_even(tmp, srcData.data, mask, height, width);\n\t\t\t\tctx.clearRect(0, 0, width, height);\n\t\t\t\tctx.putImageData(srcData, 0, 0);\n\t\t\t}\t\t\t\n\t\t\n\t\t\tthis.apply = function(ctx, x, y, width, height) {\n\t\t\t\t// assuming x==0 && y==0 for now\n\t\t\t\tblur(ctx, width, height, this.attribute('stdDeviation').numValue());\n\t\t\t}\n\t\t}\n\t\tsvg.Element.filter.prototype = new svg.Element.feGaussianBlur;\n\t\t\n\t\t// title element, do nothing\n\t\tsvg.Element.title = function(node) {\n\t\t}\n\t\tsvg.Element.title.prototype = new svg.Element.ElementBase;\n\n\t\t// desc element, do nothing\n\t\tsvg.Element.desc = function(node) {\n\t\t}\n\t\tsvg.Element.desc.prototype = new svg.Element.ElementBase;\t\t\n\t\t\n\t\tsvg.Element.MISSING = function(node) {\n\t\t\tconsole.log('ERROR: Element \\'' + node.nodeName + '\\' not yet implemented.');\n\t\t}\n\t\tsvg.Element.MISSING.prototype = new svg.Element.ElementBase;\n\t\t\n\t\t// element factory\n\t\tsvg.CreateElement = function(node) {\t\n\t\t\tvar className = node.nodeName.replace(/^[^:]+:/,''); // remove namespace\n\t\t\tclassName = className.replace(/\\-/g,''); // remove dashes\n\t\t\tvar e = null;\n\t\t\tif (typeof(svg.Element[className]) != 'undefined') {\n\t\t\t\te = new svg.Element[className](node);\n\t\t\t}\n\t\t\telse {\n\t\t\t\te = new svg.Element.MISSING(node);\n\t\t\t}\n\n\t\t\te.type = node.nodeName;\n\t\t\treturn e;\n\t\t}\n\t\t\t\t\n\t\t// load from url\n\t\tsvg.load = function(ctx, url) {\n\t\t\tsvg.loadXml(ctx, svg.ajax(url));\n\t\t}\n\t\t\n\t\t// load from xml\n\t\tsvg.loadXml = function(ctx, xml) {\n\t\t\tsvg.loadXmlDoc(ctx, svg.parseXml(xml));\n\t\t}\n\t\t\n\t\tsvg.loadXmlDoc = function(ctx, dom) {\n\t\t\tsvg.init(ctx);\n\t\t\t\n\t\t\tvar mapXY = function(p) {\n\t\t\t\tvar e = ctx.canvas;\n\t\t\t\twhile (e) {\n\t\t\t\t\tp.x -= e.offsetLeft;\n\t\t\t\t\tp.y -= e.offsetTop;\n\t\t\t\t\te = e.offsetParent;\n\t\t\t\t}\n\t\t\t\tif (window.scrollX) p.x += window.scrollX;\n\t\t\t\tif (window.scrollY) p.y += window.scrollY;\n\t\t\t\treturn p;\n\t\t\t}\n\t\t\t\n\t\t\t// bind mouse\n\t\t\tif (svg.opts['ignoreMouse'] != true) {\n\t\t\t\tctx.canvas.onclick = function(e) {\n\t\t\t\t\tvar p = mapXY(new svg.Point(e != null ? e.clientX : event.clientX, e != null ? e.clientY : event.clientY));\n\t\t\t\t\tsvg.Mouse.onclick(p.x, p.y);\n\t\t\t\t};\n\t\t\t\tctx.canvas.onmousemove = function(e) {\n\t\t\t\t\tvar p = mapXY(new svg.Point(e != null ? e.clientX : event.clientX, e != null ? e.clientY : event.clientY));\n\t\t\t\t\tsvg.Mouse.onmousemove(p.x, p.y);\n\t\t\t\t};\n\t\t\t}\n\t\t\n\t\t\tvar e = svg.CreateElement(dom.documentElement);\n\t\t\te.root = true;\n\t\t\t\t\t\n\t\t\t// render loop\n\t\t\tvar isFirstRender = true;\n\t\t\tvar draw = function() {\n\t\t\t\tsvg.ViewPort.Clear();\n\t\t\t\tif (ctx.canvas.parentNode) svg.ViewPort.SetCurrent(ctx.canvas.parentNode.clientWidth, ctx.canvas.parentNode.clientHeight);\n\t\t\t\n\t\t\t\tif (svg.opts['ignoreDimensions'] != true) {\n\t\t\t\t\t// set canvas size\n\t\t\t\t\tif (e.style('width').hasValue()) {\n\t\t\t\t\t\tctx.canvas.width = e.style('width').Length.toPixels('x');\n\t\t\t\t\t\tctx.canvas.style.width = ctx.canvas.width + 'px';\n\t\t\t\t\t}\n\t\t\t\t\tif (e.style('height').hasValue()) {\n\t\t\t\t\t\tctx.canvas.height = e.style('height').Length.toPixels('y');\n\t\t\t\t\t\tctx.canvas.style.height = ctx.canvas.height + 'px';\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tvar cWidth = ctx.canvas.clientWidth || ctx.canvas.width;\n\t\t\t\tvar cHeight = ctx.canvas.clientHeight || ctx.canvas.height;\n\t\t\t\tsvg.ViewPort.SetCurrent(cWidth, cHeight);\t\t\n\t\t\t\t\n\t\t\t\tif (svg.opts != null && svg.opts['offsetX'] != null) e.attribute('x', true).value = svg.opts['offsetX'];\n\t\t\t\tif (svg.opts != null && svg.opts['offsetY'] != null) e.attribute('y', true).value = svg.opts['offsetY'];\n\t\t\t\tif (svg.opts != null && svg.opts['scaleWidth'] != null && svg.opts['scaleHeight'] != null) {\n\t\t\t\t\tvar xRatio = 1, yRatio = 1;\n\t\t\t\t\tif (e.attribute('width').hasValue()) xRatio = e.attribute('width').Length.toPixels('x') / svg.opts['scaleWidth'];\n\t\t\t\t\tif (e.attribute('height').hasValue()) yRatio = e.attribute('height').Length.toPixels('y') / svg.opts['scaleHeight'];\n\t\t\t\t\n\t\t\t\t\te.attribute('width', true).value = svg.opts['scaleWidth'];\n\t\t\t\t\te.attribute('height', true).value = svg.opts['scaleHeight'];\t\t\t\n\t\t\t\t\te.attribute('viewBox', true).value = '0 0 ' + (cWidth * xRatio) + ' ' + (cHeight * yRatio);\n\t\t\t\t\te.attribute('preserveAspectRatio', true).value = 'none';\n\t\t\t\t}\n\t\t\t\n\t\t\t\t// clear and render\n\t\t\t\tif (svg.opts['ignoreClear'] != true) {\n\t\t\t\t\tctx.clearRect(0, 0, cWidth, cHeight);\n\t\t\t\t}\n\t\t\t\te.render(ctx);\n\t\t\t\tif (isFirstRender) {\n\t\t\t\t\tisFirstRender = false;\n\t\t\t\t\tif (svg.opts != null && typeof(svg.opts['renderCallback']) == 'function') svg.opts['renderCallback']();\n\t\t\t\t}\t\t\t\n\t\t\t}\n\t\t\t\n\t\t\tvar waitingForImages = true;\n\t\t\tif (svg.ImagesLoaded()) {\n\t\t\t\twaitingForImages = false;\n\t\t\t\tdraw();\n\t\t\t}\n\t\t\tsvg.intervalID = setInterval(function() { \n\t\t\t\tvar needUpdate = false;\n\t\t\t\t\n\t\t\t\tif (waitingForImages && svg.ImagesLoaded()) {\n\t\t\t\t\twaitingForImages = false;\n\t\t\t\t\tneedUpdate = true;\n\t\t\t\t}\n\t\t\t\n\t\t\t\t// need update from mouse events?\n\t\t\t\tif (svg.opts['ignoreMouse'] != true) {\n\t\t\t\t\tneedUpdate = needUpdate | svg.Mouse.hasEvents();\n\t\t\t\t}\n\t\t\t\n\t\t\t\t// need update from animations?\n\t\t\t\tif (svg.opts['ignoreAnimation'] != true) {\n\t\t\t\t\tfor (var i=0; i<svg.Animations.length; i++) {\n\t\t\t\t\t\tneedUpdate = needUpdate | svg.Animations[i].update(1000 / svg.FRAMERATE);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t// need update from redraw?\n\t\t\t\tif (svg.opts != null && typeof(svg.opts['forceRedraw']) == 'function') {\n\t\t\t\t\tif (svg.opts['forceRedraw']() == true) needUpdate = true;\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t// render if needed\n\t\t\t\tif (needUpdate) {\n\t\t\t\t\tdraw();\t\t\t\t\n\t\t\t\t\tsvg.Mouse.runEvents(); // run and clear our events\n\t\t\t\t}\n\t\t\t}, 1000 / svg.FRAMERATE);\n\t\t}\n\t\t\n\t\tsvg.stop = function() {\n\t\t\tif (svg.intervalID) {\n\t\t\t\tclearInterval(svg.intervalID);\n\t\t\t}\n\t\t}\n\t\t\n\t\tsvg.Mouse = new (function() {\n\t\t\tthis.events = [];\n\t\t\tthis.hasEvents = function() { return this.events.length != 0; }\n\t\t\n\t\t\tthis.onclick = function(x, y) {\n\t\t\t\tthis.events.push({ type: 'onclick', x: x, y: y, \n\t\t\t\t\trun: function(e) { if (e.onclick) e.onclick(); }\n\t\t\t\t});\n\t\t\t}\n\t\t\t\n\t\t\tthis.onmousemove = function(x, y) {\n\t\t\t\tthis.events.push({ type: 'onmousemove', x: x, y: y,\n\t\t\t\t\trun: function(e) { if (e.onmousemove) e.onmousemove(); }\n\t\t\t\t});\n\t\t\t}\t\t\t\n\t\t\t\n\t\t\tthis.eventElements = [];\n\t\t\t\n\t\t\tthis.checkPath = function(element, ctx) {\n\t\t\t\tfor (var i=0; i<this.events.length; i++) {\n\t\t\t\t\tvar e = this.events[i];\n\t\t\t\t\tif (ctx.isPointInPath && ctx.isPointInPath(e.x, e.y)) this.eventElements[i] = element;\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\tthis.checkBoundingBox = function(element, bb) {\n\t\t\t\tfor (var i=0; i<this.events.length; i++) {\n\t\t\t\t\tvar e = this.events[i];\n\t\t\t\t\tif (bb.isPointInBox(e.x, e.y)) this.eventElements[i] = element;\n\t\t\t\t}\t\t\t\n\t\t\t}\n\t\t\t\n\t\t\tthis.runEvents = function() {\n\t\t\t\tsvg.ctx.canvas.style.cursor = '';\n\t\t\t\t\n\t\t\t\tfor (var i=0; i<this.events.length; i++) {\n\t\t\t\t\tvar e = this.events[i];\n\t\t\t\t\tvar element = this.eventElements[i];\n\t\t\t\t\twhile (element) {\n\t\t\t\t\t\te.run(element);\n\t\t\t\t\t\telement = element.parent;\n\t\t\t\t\t}\n\t\t\t\t}\t\t\n\t\t\t\n\t\t\t\t// done running, clear\n\t\t\t\tthis.events = []; \n\t\t\t\tthis.eventElements = [];\n\t\t\t}\n\t\t});\n\t\t\n\t\treturn svg;\n\t}\n})();\n\nif (CanvasRenderingContext2D) {\n\tCanvasRenderingContext2D.prototype.drawSvg = function(s, dx, dy, dw, dh) {\n\t\tcanvg(this.canvas, s, { \n\t\t\tignoreMouse: true, \n\t\t\tignoreAnimation: true, \n\t\t\tignoreDimensions: true, \n\t\t\tignoreClear: true, \n\t\t\toffsetX: dx, \n\t\t\toffsetY: dy, \n\t\t\tscaleWidth: dw, \n\t\t\tscaleHeight: dh\n\t\t});\n\t}\n}/**\n * @license Highcharts JS v3.0.6 (2013-10-04)\n * CanVGRenderer Extension module\n *\n * (c) 2011-2012 Torstein Hønsi, Erik Olsson\n *\n * License: www.highcharts.com/license\n */\n\n// JSLint options:\n/*global Highcharts */\n\n(function (Highcharts) { // encapsulate\n\tvar UNDEFINED,\n\t\tDIV = 'div',\n\t\tABSOLUTE = 'absolute',\n\t\tRELATIVE = 'relative',\n\t\tHIDDEN = 'hidden',\n\t\tVISIBLE = 'visible',\n\t\tPX = 'px',\n\t\tcss = Highcharts.css,\n\t\tCanVGRenderer = Highcharts.CanVGRenderer,\n\t\tSVGRenderer = Highcharts.SVGRenderer,\n\t\textend = Highcharts.extend,\n\t\tmerge = Highcharts.merge,\n\t\taddEvent = Highcharts.addEvent,\n\t\tcreateElement = Highcharts.createElement,\n\t\tdiscardElement = Highcharts.discardElement;\n\n\t// Extend CanVG renderer on demand, inherit from SVGRenderer\n\textend(CanVGRenderer.prototype, SVGRenderer.prototype);\n\n\t// Add additional functionality:\n\textend(CanVGRenderer.prototype, {\n\t\tcreate: function (chart, container, chartWidth, chartHeight) {\n\t\t\tthis.setContainer(container, chartWidth, chartHeight);\n\t\t\tthis.configure(chart);\n\t\t},\n\t\tsetContainer: function (container, chartWidth, chartHeight) {\n\t\t\tvar containerStyle = container.style,\n\t\t\t\tcontainerParent = container.parentNode,\n\t\t\t\tcontainerLeft = containerStyle.left,\n\t\t\t\tcontainerTop = containerStyle.top,\n\t\t\t\tcontainerOffsetWidth = container.offsetWidth,\n\t\t\t\tcontainerOffsetHeight = container.offsetHeight,\n\t\t\t\tcanvas,\n\t\t\t\tinitialHiddenStyle = { visibility: HIDDEN, position: ABSOLUTE };\n\n\t\t\tthis.init.apply(this, [container, chartWidth, chartHeight]);\n\n\t\t\t// add the canvas above it\n\t\t\tcanvas = createElement('canvas', {\n\t\t\t\twidth: containerOffsetWidth,\n\t\t\t\theight: containerOffsetHeight\n\t\t\t}, {\n\t\t\t\tposition: RELATIVE,\n\t\t\t\tleft: containerLeft,\n\t\t\t\ttop: containerTop\n\t\t\t}, container);\n\t\t\tthis.canvas = canvas;\n\n\t\t\t// Create the tooltip line and div, they are placed as siblings to\n\t\t\t// the container (and as direct childs to the div specified in the html page)\n\t\t\tthis.ttLine = createElement(DIV, null, initialHiddenStyle, containerParent);\n\t\t\tthis.ttDiv = createElement(DIV, null, initialHiddenStyle, containerParent);\n\t\t\tthis.ttTimer = UNDEFINED;\n\n\t\t\t// Move away the svg node to a new div inside the container's parent so we can hide it.\n\t\t\tvar hiddenSvg = createElement(DIV, {\n\t\t\t\twidth: containerOffsetWidth,\n\t\t\t\theight: containerOffsetHeight\n\t\t\t}, {\n\t\t\t\tvisibility: HIDDEN,\n\t\t\t\tleft: containerLeft,\n\t\t\t\ttop: containerTop\n\t\t\t}, containerParent);\n\t\t\tthis.hiddenSvg = hiddenSvg;\n\t\t\thiddenSvg.appendChild(this.box);\n\t\t},\n\n\t\t/**\n\t\t * Configures the renderer with the chart. Attach a listener to the event tooltipRefresh.\n\t\t **/\n\t\tconfigure: function (chart) {\n\t\t\tvar renderer = this,\n\t\t\t\toptions = chart.options.tooltip,\n\t\t\t\tborderWidth = options.borderWidth,\n\t\t\t\ttooltipDiv = renderer.ttDiv,\n\t\t\t\ttooltipDivStyle = options.style,\n\t\t\t\ttooltipLine = renderer.ttLine,\n\t\t\t\tpadding = parseInt(tooltipDivStyle.padding, 10);\n\n\t\t\t// Add border styling from options to the style\n\t\t\ttooltipDivStyle = merge(tooltipDivStyle, {\n\t\t\t\tpadding: padding + PX,\n\t\t\t\t'background-color': options.backgroundColor,\n\t\t\t\t'border-style': 'solid',\n\t\t\t\t'border-width': borderWidth + PX,\n\t\t\t\t'border-radius': options.borderRadius + PX\n\t\t\t});\n\n\t\t\t// Optionally add shadow\n\t\t\tif (options.shadow) {\n\t\t\t\ttooltipDivStyle = merge(tooltipDivStyle, {\n\t\t\t\t\t'box-shadow': '1px 1px 3px gray', // w3c\n\t\t\t\t\t'-webkit-box-shadow': '1px 1px 3px gray' // webkit\n\t\t\t\t});\n\t\t\t}\n\t\t\tcss(tooltipDiv, tooltipDivStyle);\n\n\t\t\t// Set simple style on the line\n\t\t\tcss(tooltipLine, {\n\t\t\t\t'border-left': '1px solid darkgray'\n\t\t\t});\n\n\t\t\t// This event is triggered when a new tooltip should be shown\n\t\t\taddEvent(chart, 'tooltipRefresh', function (args) {\n\t\t\t\tvar chartContainer = chart.container,\n\t\t\t\t\toffsetLeft = chartContainer.offsetLeft,\n\t\t\t\t\toffsetTop = chartContainer.offsetTop,\n\t\t\t\t\tposition;\n\n\t\t\t\t// Set the content of the tooltip\n\t\t\t\ttooltipDiv.innerHTML = args.text;\n\n\t\t\t\t// Compute the best position for the tooltip based on the divs size and container size.\n\t\t\t\tposition = chart.tooltip.getPosition(tooltipDiv.offsetWidth, tooltipDiv.offsetHeight, {plotX: args.x, plotY: args.y});\n\n\t\t\t\tcss(tooltipDiv, {\n\t\t\t\t\tvisibility: VISIBLE,\n\t\t\t\t\tleft: position.x + PX,\n\t\t\t\t\ttop: position.y + PX,\n\t\t\t\t\t'border-color': args.borderColor\n\t\t\t\t});\n\n\t\t\t\t// Position the tooltip line\n\t\t\t\tcss(tooltipLine, {\n\t\t\t\t\tvisibility: VISIBLE,\n\t\t\t\t\tleft: offsetLeft + args.x + PX,\n\t\t\t\t\ttop: offsetTop + chart.plotTop + PX,\n\t\t\t\t\theight: chart.plotHeight  + PX\n\t\t\t\t});\n\n\t\t\t\t// This timeout hides the tooltip after 3 seconds\n\t\t\t\t// First clear any existing timer\n\t\t\t\tif (renderer.ttTimer !== UNDEFINED) {\n\t\t\t\t\tclearTimeout(renderer.ttTimer);\n\t\t\t\t}\n\n\t\t\t\t// Start a new timer that hides tooltip and line\n\t\t\t\trenderer.ttTimer = setTimeout(function () {\n\t\t\t\t\tcss(tooltipDiv, { visibility: HIDDEN });\n\t\t\t\t\tcss(tooltipLine, { visibility: HIDDEN });\n\t\t\t\t}, 3000);\n\t\t\t});\n\t\t},\n\n\t\t/**\n\t\t * Extend SVGRenderer.destroy to also destroy the elements added by CanVGRenderer.\n\t\t */\n\t\tdestroy: function () {\n\t\t\tvar renderer = this;\n\n\t\t\t// Remove the canvas\n\t\t\tdiscardElement(renderer.canvas);\n\n\t\t\t// Kill the timer\n\t\t\tif (renderer.ttTimer !== UNDEFINED) {\n\t\t\t\tclearTimeout(renderer.ttTimer);\n\t\t\t}\n\n\t\t\t// Remove the divs for tooltip and line\n\t\t\tdiscardElement(renderer.ttLine);\n\t\t\tdiscardElement(renderer.ttDiv);\n\t\t\tdiscardElement(renderer.hiddenSvg);\n\n\t\t\t// Continue with base class\n\t\t\treturn SVGRenderer.prototype.destroy.apply(renderer);\n\t\t},\n\n\t\t/**\n\t\t * Take a color and return it if it's a string, do not make it a gradient even if it is a\n\t\t * gradient. Currently canvg cannot render gradients (turns out black),\n\t\t * see: http://code.google.com/p/canvg/issues/detail?id=104\n\t\t *\n\t\t * @param {Object} color The color or config object\n\t\t */\n\t\tcolor: function (color, elem, prop) {\n\t\t\tif (color && color.linearGradient) {\n\t\t\t\t// Pick the end color and forward to base implementation\n\t\t\t\tcolor = color.stops[color.stops.length - 1][1];\n\t\t\t}\n\t\t\treturn SVGRenderer.prototype.color.call(this, color, elem, prop);\n\t\t},\n\n\t\t/**\n\t\t * Draws the SVG on the canvas or adds a draw invokation to the deferred list.\n\t\t */\n\t\tdraw: function () {\n\t\t\tvar renderer = this;\n\t\t\twindow.canvg(renderer.canvas, renderer.hiddenSvg.innerHTML);\n\t\t}\n\t});\n}(Highcharts));\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/highcharts/modules/data.js",
    "content": "/*\n Data plugin for Highcharts\n\n (c) 2012-2013 Torstein Hønsi\n Last revision 2013-06-07\n\n License: www.highcharts.com/license\n*/\n(function(h){var k=h.each,m=function(b,a){this.init(b,a)};h.extend(m.prototype,{init:function(b,a){this.options=b;this.chartOptions=a;this.columns=b.columns||this.rowsToColumns(b.rows)||[];this.columns.length?this.dataFound():(this.parseCSV(),this.parseTable(),this.parseGoogleSpreadsheet())},getColumnDistribution:function(){var b=this.chartOptions,a=b&&b.chart&&b.chart.type,c=[];k(b&&b.series||[],function(b){c.push((h.seriesTypes[b.type||a||\"line\"].prototype.pointArrayMap||[0]).length)});this.valueCount=\n{global:(h.seriesTypes[a||\"line\"].prototype.pointArrayMap||[0]).length,individual:c}},dataFound:function(){this.parseTypes();this.findHeaderRow();this.parsed();this.complete()},parseCSV:function(){var b=this,a=this.options,c=a.csv,d=this.columns,f=a.startRow||0,i=a.endRow||Number.MAX_VALUE,j=a.startColumn||0,e=a.endColumn||Number.MAX_VALUE,g=0;c&&(c=c.replace(/\\r\\n/g,\"\\n\").replace(/\\r/g,\"\\n\").split(a.lineDelimiter||\"\\n\"),k(c,function(c,h){var n=b.trim(c),p=n.indexOf(\"#\")===0;h>=f&&h<=i&&!p&&n!==\"\"&&\n(n=c.split(a.itemDelimiter||\",\"),k(n,function(b,a){a>=j&&a<=e&&(d[a-j]||(d[a-j]=[]),d[a-j][g]=b)}),g+=1)}),this.dataFound())},parseTable:function(){var b=this.options,a=b.table,c=this.columns,d=b.startRow||0,f=b.endRow||Number.MAX_VALUE,i=b.startColumn||0,j=b.endColumn||Number.MAX_VALUE,e;a&&(typeof a===\"string\"&&(a=document.getElementById(a)),k(a.getElementsByTagName(\"tr\"),function(a,b){e=0;b>=d&&b<=f&&k(a.childNodes,function(a){if((a.tagName===\"TD\"||a.tagName===\"TH\")&&e>=i&&e<=j)c[e]||(c[e]=[]),\nc[e][b-d]=a.innerHTML,e+=1})}),this.dataFound())},parseGoogleSpreadsheet:function(){var b=this,a=this.options,c=a.googleSpreadsheetKey,d=this.columns,f=a.startRow||0,i=a.endRow||Number.MAX_VALUE,j=a.startColumn||0,e=a.endColumn||Number.MAX_VALUE,g,h;c&&jQuery.getJSON(\"https://spreadsheets.google.com/feeds/cells/\"+c+\"/\"+(a.googleSpreadsheetWorksheet||\"od6\")+\"/public/values?alt=json-in-script&callback=?\",function(a){var a=a.feed.entry,c,k=a.length,m=0,o=0,l;for(l=0;l<k;l++)c=a[l],m=Math.max(m,c.gs$cell.col),\no=Math.max(o,c.gs$cell.row);for(l=0;l<m;l++)if(l>=j&&l<=e)d[l-j]=[],d[l-j].length=Math.min(o,i-f);for(l=0;l<k;l++)if(c=a[l],g=c.gs$cell.row-1,h=c.gs$cell.col-1,h>=j&&h<=e&&g>=f&&g<=i)d[h-j][g-f]=c.content.$t;b.dataFound()})},findHeaderRow:function(){k(this.columns,function(){});this.headerRow=0},trim:function(b){return typeof b===\"string\"?b.replace(/^\\s+|\\s+$/g,\"\"):b},parseTypes:function(){for(var b=this.columns,a=b.length,c,d,f,i;a--;)for(c=b[a].length;c--;)d=b[a][c],f=parseFloat(d),i=this.trim(d),\ni==f?(b[a][c]=f,f>31536E6?b[a].isDatetime=!0:b[a].isNumeric=!0):(d=this.parseDate(d),a===0&&typeof d===\"number\"&&!isNaN(d)?(b[a][c]=d,b[a].isDatetime=!0):b[a][c]=i===\"\"?null:i)},dateFormats:{\"YYYY-mm-dd\":{regex:\"^([0-9]{4})-([0-9]{2})-([0-9]{2})$\",parser:function(b){return Date.UTC(+b[1],b[2]-1,+b[3])}}},parseDate:function(b){var a=this.options.parseDate,c,d,f;a&&(c=a(b));if(typeof b===\"string\")for(d in this.dateFormats)a=this.dateFormats[d],(f=b.match(a.regex))&&(c=a.parser(f));return c},rowsToColumns:function(b){var a,\nc,d,f,i;if(b){i=[];c=b.length;for(a=0;a<c;a++){f=b[a].length;for(d=0;d<f;d++)i[d]||(i[d]=[]),i[d][a]=b[a][d]}}return i},parsed:function(){this.options.parsed&&this.options.parsed.call(this,this.columns)},complete:function(){var b=this.columns,a,c,d=this.options,f,i,j,e,g,k;if(d.complete){this.getColumnDistribution();b.length>1&&(a=b.shift(),this.headerRow===0&&a.shift(),a.isDatetime?c=\"datetime\":a.isNumeric||(c=\"category\"));for(e=0;e<b.length;e++)if(this.headerRow===0)b[e].name=b[e].shift();i=[];\nfor(e=0,k=0;e<b.length;k++){f=h.pick(this.valueCount.individual[k],this.valueCount.global);j=[];for(g=0;g<b[e].length;g++)j[g]=[a[g],b[e][g]!==void 0?b[e][g]:null],f>1&&j[g].push(b[e+1][g]!==void 0?b[e+1][g]:null),f>2&&j[g].push(b[e+2][g]!==void 0?b[e+2][g]:null),f>3&&j[g].push(b[e+3][g]!==void 0?b[e+3][g]:null),f>4&&j[g].push(b[e+4][g]!==void 0?b[e+4][g]:null);i[k]={name:b[e].name,data:j};e+=f}d.complete({xAxis:{type:c},series:i})}}});h.Data=m;h.data=function(b,a){return new m(b,a)};h.wrap(h.Chart.prototype,\n\"init\",function(b,a,c){var d=this;a&&a.data?h.data(h.extend(a.data,{complete:function(f){a.series&&k(a.series,function(b,c){a.series[c]=h.merge(b,f.series[c])});a=h.merge(f,a);b.call(d,a,c)}}),a):b.call(d,a,c)})})(Highcharts);\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/highcharts/modules/data.src.js",
    "content": "/**\n * @license Data plugin for Highcharts\n *\n * (c) 2012-2013 Torstein Hønsi\n * Last revision 2013-06-07\n *\n * License: www.highcharts.com/license\n */\n\n/*\n * The Highcharts Data plugin is a utility to ease parsing of input sources like\n * CSV, HTML tables or grid views into basic configuration options for use \n * directly in the Highcharts constructor.\n *\n * Demo: http://jsfiddle.net/highcharts/SnLFj/\n *\n * --- OPTIONS ---\n *\n * - columns : Array<Array<Mixed>>\n * A two-dimensional array representing the input data on tabular form. This input can\n * be used when the data is already parsed, for example from a grid view component.\n * Each cell can be a string or number. If not switchRowsAndColumns is set, the columns\n * are interpreted as series. See also the rows option.\n *\n * - complete : Function(chartOptions)\n * The callback that is evaluated when the data is finished loading, optionally from an \n * external source, and parsed. The first argument passed is a finished chart options\n * object, containing series and an xAxis with categories if applicable. Thise options\n * can be extended with additional options and passed directly to the chart constructor.\n *\n * - csv : String\n * A comma delimited string to be parsed. Related options are startRow, endRow, startColumn\n * and endColumn to delimit what part of the table is used. The lineDelimiter and \n * itemDelimiter options define the CSV delimiter formats.\n * \n * - endColumn : Integer\n * In tabular input data, the first row (indexed by 0) to use. Defaults to the last \n * column containing data.\n *\n * - endRow : Integer\n * In tabular input data, the last row (indexed by 0) to use. Defaults to the last row\n * containing data.\n *\n * - googleSpreadsheetKey : String \n * A Google Spreadsheet key. See https://developers.google.com/gdata/samples/spreadsheet_sample\n * for general information on GS.\n *\n * - googleSpreadsheetWorksheet : String \n * The Google Spreadsheet worksheet. The available id's can be read from \n * https://spreadsheets.google.com/feeds/worksheets/{key}/public/basic\n *\n * - itemDelimiter : String\n * Item or cell delimiter for parsing CSV. Defaults to \",\".\n *\n * - lineDelimiter : String\n * Line delimiter for parsing CSV. Defaults to \"\\n\".\n *\n * - parsed : Function\n * A callback function to access the parsed columns, the two-dimentional input data\n * array directly, before they are interpreted into series data and categories.\n *\n * - parseDate : Function\n * A callback function to parse string representations of dates into JavaScript timestamps.\n * Return an integer on success.\n *\n * - rows : Array<Array<Mixed>>\n * The same as the columns input option, but defining rows intead of columns.\n *\n * - startColumn : Integer\n * In tabular input data, the first column (indexed by 0) to use. \n *\n * - startRow : Integer\n * In tabular input data, the first row (indexed by 0) to use.\n *\n * - table : String|HTMLElement\n * A HTML table or the id of such to be parsed as input data. Related options ara startRow,\n * endRow, startColumn and endColumn to delimit what part of the table is used.\n */\n\n// JSLint options:\n/*global jQuery */\n\n(function (Highcharts) {\t\n\t\n\t// Utilities\n\tvar each = Highcharts.each;\n\t\n\t\n\t// The Data constructor\n\tvar Data = function (dataOptions, chartOptions) {\n\t\tthis.init(dataOptions, chartOptions);\n\t};\n\t\n\t// Set the prototype properties\n\tHighcharts.extend(Data.prototype, {\n\t\t\n\t/**\n\t * Initialize the Data object with the given options\n\t */\n\tinit: function (options, chartOptions) {\n\t\tthis.options = options;\n\t\tthis.chartOptions = chartOptions;\n\t\tthis.columns = options.columns || this.rowsToColumns(options.rows) || [];\n\n\t\t// No need to parse or interpret anything\n\t\tif (this.columns.length) {\n\t\t\tthis.dataFound();\n\n\t\t// Parse and interpret\n\t\t} else {\n\n\t\t\t// Parse a CSV string if options.csv is given\n\t\t\tthis.parseCSV();\n\t\t\t\n\t\t\t// Parse a HTML table if options.table is given\n\t\t\tthis.parseTable();\n\n\t\t\t// Parse a Google Spreadsheet \n\t\t\tthis.parseGoogleSpreadsheet();\t\n\t\t}\n\n\t},\n\n\t/**\n\t * Get the column distribution. For example, a line series takes a single column for \n\t * Y values. A range series takes two columns for low and high values respectively,\n\t * and an OHLC series takes four columns.\n\t */\n\tgetColumnDistribution: function () {\n\t\tvar chartOptions = this.chartOptions,\n\t\t\tgetValueCount = function (type) {\n\t\t\t\treturn (Highcharts.seriesTypes[type || 'line'].prototype.pointArrayMap || [0]).length;\n\t\t\t},\n\t\t\tglobalType = chartOptions && chartOptions.chart && chartOptions.chart.type,\n\t\t\tindividualCounts = [];\n\n\t\teach((chartOptions && chartOptions.series) || [], function (series) {\n\t\t\tindividualCounts.push(getValueCount(series.type || globalType));\n\t\t});\n\n\t\tthis.valueCount = {\n\t\t\tglobal: getValueCount(globalType),\n\t\t\tindividual: individualCounts\n\t\t};\n\t},\n\n\n\tdataFound: function () {\n\t\t// Interpret the values into right types\n\t\tthis.parseTypes();\n\t\t\n\t\t// Use first row for series names?\n\t\tthis.findHeaderRow();\n\t\t\n\t\t// Handle columns if a handleColumns callback is given\n\t\tthis.parsed();\n\t\t\n\t\t// Complete if a complete callback is given\n\t\tthis.complete();\n\t\t\n\t},\n\t\n\t/**\n\t * Parse a CSV input string\n\t */\n\tparseCSV: function () {\n\t\tvar self = this,\n\t\t\toptions = this.options,\n\t\t\tcsv = options.csv,\n\t\t\tcolumns = this.columns,\n\t\t\tstartRow = options.startRow || 0,\n\t\t\tendRow = options.endRow || Number.MAX_VALUE,\n\t\t\tstartColumn = options.startColumn || 0,\n\t\t\tendColumn = options.endColumn || Number.MAX_VALUE,\n\t\t\tlines,\n\t\t\tactiveRowNo = 0;\n\t\t\t\n\t\tif (csv) {\n\t\t\t\n\t\t\tlines = csv\n\t\t\t\t.replace(/\\r\\n/g, \"\\n\") // Unix\n\t\t\t\t.replace(/\\r/g, \"\\n\") // Mac\n\t\t\t\t.split(options.lineDelimiter || \"\\n\");\n\t\t\t\n\t\t\teach(lines, function (line, rowNo) {\n\t\t\t\tvar trimmed = self.trim(line),\n\t\t\t\t\tisComment = trimmed.indexOf('#') === 0,\n\t\t\t\t\tisBlank = trimmed === '',\n\t\t\t\t\titems;\n\t\t\t\t\n\t\t\t\tif (rowNo >= startRow && rowNo <= endRow && !isComment && !isBlank) {\n\t\t\t\t\titems = line.split(options.itemDelimiter || ',');\n\t\t\t\t\teach(items, function (item, colNo) {\n\t\t\t\t\t\tif (colNo >= startColumn && colNo <= endColumn) {\n\t\t\t\t\t\t\tif (!columns[colNo - startColumn]) {\n\t\t\t\t\t\t\t\tcolumns[colNo - startColumn] = [];\t\t\t\t\t\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\tcolumns[colNo - startColumn][activeRowNo] = item;\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t\tactiveRowNo += 1;\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tthis.dataFound();\n\t\t}\n\t},\n\t\n\t/**\n\t * Parse a HTML table\n\t */\n\tparseTable: function () {\n\t\tvar options = this.options,\n\t\t\ttable = options.table,\n\t\t\tcolumns = this.columns,\n\t\t\tstartRow = options.startRow || 0,\n\t\t\tendRow = options.endRow || Number.MAX_VALUE,\n\t\t\tstartColumn = options.startColumn || 0,\n\t\t\tendColumn = options.endColumn || Number.MAX_VALUE,\n\t\t\tcolNo;\n\t\t\t\n\t\tif (table) {\n\t\t\t\n\t\t\tif (typeof table === 'string') {\n\t\t\t\ttable = document.getElementById(table);\n\t\t\t}\n\t\t\t\n\t\t\teach(table.getElementsByTagName('tr'), function (tr, rowNo) {\n\t\t\t\tcolNo = 0; \n\t\t\t\tif (rowNo >= startRow && rowNo <= endRow) {\n\t\t\t\t\teach(tr.childNodes, function (item) {\n\t\t\t\t\t\tif ((item.tagName === 'TD' || item.tagName === 'TH') && colNo >= startColumn && colNo <= endColumn) {\n\t\t\t\t\t\t\tif (!columns[colNo]) {\n\t\t\t\t\t\t\t\tcolumns[colNo] = [];\t\t\t\t\t\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tcolumns[colNo][rowNo - startRow] = item.innerHTML;\n\t\t\t\t\t\t\t\n\t\t\t\t\t\t\tcolNo += 1;\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tthis.dataFound(); // continue\n\t\t}\n\t},\n\n\t/**\n\t * TODO: \n\t * - switchRowsAndColumns\n\t */\n\tparseGoogleSpreadsheet: function () {\n\t\tvar self = this,\n\t\t\toptions = this.options,\n\t\t\tgoogleSpreadsheetKey = options.googleSpreadsheetKey,\n\t\t\tcolumns = this.columns,\n\t\t\tstartRow = options.startRow || 0,\n\t\t\tendRow = options.endRow || Number.MAX_VALUE,\n\t\t\tstartColumn = options.startColumn || 0,\n\t\t\tendColumn = options.endColumn || Number.MAX_VALUE,\n\t\t\tgr, // google row\n\t\t\tgc; // google column\n\n\t\tif (googleSpreadsheetKey) {\n\t\t\tjQuery.getJSON('https://spreadsheets.google.com/feeds/cells/' + \n\t\t\t\t  googleSpreadsheetKey + '/' + (options.googleSpreadsheetWorksheet || 'od6') +\n\t\t\t\t\t  '/public/values?alt=json-in-script&callback=?',\n\t\t\t\t\t  function (json) {\n\t\t\t\t\t\n\t\t\t\t// Prepare the data from the spreadsheat\n\t\t\t\tvar cells = json.feed.entry,\n\t\t\t\t\tcell,\n\t\t\t\t\tcellCount = cells.length,\n\t\t\t\t\tcolCount = 0,\n\t\t\t\t\trowCount = 0,\n\t\t\t\t\ti;\n\t\t\t\n\t\t\t\t// First, find the total number of columns and rows that \n\t\t\t\t// are actually filled with data\n\t\t\t\tfor (i = 0; i < cellCount; i++) {\n\t\t\t\t\tcell = cells[i];\n\t\t\t\t\tcolCount = Math.max(colCount, cell.gs$cell.col);\n\t\t\t\t\trowCount = Math.max(rowCount, cell.gs$cell.row);\t\t\t\n\t\t\t\t}\n\t\t\t\n\t\t\t\t// Set up arrays containing the column data\n\t\t\t\tfor (i = 0; i < colCount; i++) {\n\t\t\t\t\tif (i >= startColumn && i <= endColumn) {\n\t\t\t\t\t\t// Create new columns with the length of either end-start or rowCount\n\t\t\t\t\t\tcolumns[i - startColumn] = [];\n\n\t\t\t\t\t\t// Setting the length to avoid jslint warning\n\t\t\t\t\t\tcolumns[i - startColumn].length = Math.min(rowCount, endRow - startRow);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t// Loop over the cells and assign the value to the right\n\t\t\t\t// place in the column arrays\n\t\t\t\tfor (i = 0; i < cellCount; i++) {\n\t\t\t\t\tcell = cells[i];\n\t\t\t\t\tgr = cell.gs$cell.row - 1; // rows start at 1\n\t\t\t\t\tgc = cell.gs$cell.col - 1; // columns start at 1\n\n\t\t\t\t\t// If both row and col falls inside start and end\n\t\t\t\t\t// set the transposed cell value in the newly created columns\n\t\t\t\t\tif (gc >= startColumn && gc <= endColumn &&\n\t\t\t\t\t\tgr >= startRow && gr <= endRow) {\n\t\t\t\t\t\tcolumns[gc - startColumn][gr - startRow] = cell.content.$t;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tself.dataFound();\n\t\t\t});\n\t\t}\n\t},\n\t\n\t/**\n\t * Find the header row. For now, we just check whether the first row contains\n\t * numbers or strings. Later we could loop down and find the first row with \n\t * numbers.\n\t */\n\tfindHeaderRow: function () {\n\t\tvar headerRow = 0;\n\t\teach(this.columns, function (column) {\n\t\t\tif (typeof column[0] !== 'string') {\n\t\t\t\theaderRow = null;\n\t\t\t}\n\t\t});\n\t\tthis.headerRow = 0;\t\t\t\n\t},\n\t\n\t/**\n\t * Trim a string from whitespace\n\t */\n\ttrim: function (str) {\n\t\treturn typeof str === 'string' ? str.replace(/^\\s+|\\s+$/g, '') : str;\n\t},\n\t\n\t/**\n\t * Parse numeric cells in to number types and date types in to true dates.\n\t * @param {Object} columns\n\t */\n\tparseTypes: function () {\n\t\tvar columns = this.columns,\n\t\t\tcol = columns.length, \n\t\t\trow,\n\t\t\tval,\n\t\t\tfloatVal,\n\t\t\ttrimVal,\n\t\t\tdateVal;\n\t\t\t\n\t\twhile (col--) {\n\t\t\trow = columns[col].length;\n\t\t\twhile (row--) {\n\t\t\t\tval = columns[col][row];\n\t\t\t\tfloatVal = parseFloat(val);\n\t\t\t\ttrimVal = this.trim(val);\n\n\t\t\t\t/*jslint eqeq: true*/\n\t\t\t\tif (trimVal == floatVal) { // is numeric\n\t\t\t\t/*jslint eqeq: false*/\n\t\t\t\t\tcolumns[col][row] = floatVal;\n\t\t\t\t\t\n\t\t\t\t\t// If the number is greater than milliseconds in a year, assume datetime\n\t\t\t\t\tif (floatVal > 365 * 24 * 3600 * 1000) {\n\t\t\t\t\t\tcolumns[col].isDatetime = true;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tcolumns[col].isNumeric = true;\n\t\t\t\t\t}\t\t\t\t\t\n\t\t\t\t\n\t\t\t\t} else { // string, continue to determine if it is a date string or really a string\n\t\t\t\t\tdateVal = this.parseDate(val);\n\t\t\t\t\t\n\t\t\t\t\tif (col === 0 && typeof dateVal === 'number' && !isNaN(dateVal)) { // is date\n\t\t\t\t\t\tcolumns[col][row] = dateVal;\n\t\t\t\t\t\tcolumns[col].isDatetime = true;\n\t\t\t\t\t\n\t\t\t\t\t} else { // string\n\t\t\t\t\t\tcolumns[col][row] = trimVal === '' ? null : trimVal;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t\n\t\t\t}\n\t\t}\n\t},\n\t//*\n\tdateFormats: {\n\t\t'YYYY-mm-dd': {\n\t\t\tregex: '^([0-9]{4})-([0-9]{2})-([0-9]{2})$',\n\t\t\tparser: function (match) {\n\t\t\t\treturn Date.UTC(+match[1], match[2] - 1, +match[3]);\n\t\t\t}\n\t\t}\n\t},\n\t// */\n\t/**\n\t * Parse a date and return it as a number. Overridable through options.parseDate.\n\t */\n\tparseDate: function (val) {\n\t\tvar parseDate = this.options.parseDate,\n\t\t\tret,\n\t\t\tkey,\n\t\t\tformat,\n\t\t\tmatch;\n\n\t\tif (parseDate) {\n\t\t\tret = parseDate(val);\n\t\t}\n\t\t\t\n\t\tif (typeof val === 'string') {\n\t\t\tfor (key in this.dateFormats) {\n\t\t\t\tformat = this.dateFormats[key];\n\t\t\t\tmatch = val.match(format.regex);\n\t\t\t\tif (match) {\n\t\t\t\t\tret = format.parser(match);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn ret;\n\t},\n\t\n\t/**\n\t * Reorganize rows into columns\n\t */\n\trowsToColumns: function (rows) {\n\t\tvar row,\n\t\t\trowsLength,\n\t\t\tcol,\n\t\t\tcolsLength,\n\t\t\tcolumns;\n\n\t\tif (rows) {\n\t\t\tcolumns = [];\n\t\t\trowsLength = rows.length;\n\t\t\tfor (row = 0; row < rowsLength; row++) {\n\t\t\t\tcolsLength = rows[row].length;\n\t\t\t\tfor (col = 0; col < colsLength; col++) {\n\t\t\t\t\tif (!columns[col]) {\n\t\t\t\t\t\tcolumns[col] = [];\n\t\t\t\t\t}\n\t\t\t\t\tcolumns[col][row] = rows[row][col];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn columns;\n\t},\n\t\n\t/**\n\t * A hook for working directly on the parsed columns\n\t */\n\tparsed: function () {\n\t\tif (this.options.parsed) {\n\t\t\tthis.options.parsed.call(this, this.columns);\n\t\t}\n\t},\n\t\n\t/**\n\t * If a complete callback function is provided in the options, interpret the \n\t * columns into a Highcharts options object.\n\t */\n\tcomplete: function () {\n\t\t\n\t\tvar columns = this.columns,\n\t\t\tfirstCol,\n\t\t\ttype,\n\t\t\toptions = this.options,\n\t\t\tvalueCount,\n\t\t\tseries,\n\t\t\tdata,\n\t\t\ti,\n\t\t\tj,\n\t\t\tseriesIndex;\n\t\t\t\n\t\t\n\t\tif (options.complete) {\n\n\t\t\tthis.getColumnDistribution();\n\t\t\t\n\t\t\t// Use first column for X data or categories?\n\t\t\tif (columns.length > 1) {\n\t\t\t\tfirstCol = columns.shift();\n\t\t\t\tif (this.headerRow === 0) {\n\t\t\t\t\tfirstCol.shift(); // remove the first cell\n\t\t\t\t}\n\t\t\t\t\n\t\t\t\t\n\t\t\t\tif (firstCol.isDatetime) {\n\t\t\t\t\ttype = 'datetime';\n\t\t\t\t} else if (!firstCol.isNumeric) {\n\t\t\t\t\ttype = 'category';\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Get the names and shift the top row\n\t\t\tfor (i = 0; i < columns.length; i++) {\n\t\t\t\tif (this.headerRow === 0) {\n\t\t\t\t\tcolumns[i].name = columns[i].shift();\n\t\t\t\t}\n\t\t\t}\n\t\t\t\n\t\t\t// Use the next columns for series\n\t\t\tseries = [];\n\t\t\tfor (i = 0, seriesIndex = 0; i < columns.length; seriesIndex++) {\n\n\t\t\t\t// This series' value count\n\t\t\t\tvalueCount = Highcharts.pick(this.valueCount.individual[seriesIndex], this.valueCount.global);\n\t\t\t\t\n\t\t\t\t// Iterate down the cells of each column and add data to the series\n\t\t\t\tdata = [];\n\t\t\t\tfor (j = 0; j < columns[i].length; j++) {\n\t\t\t\t\tdata[j] = [\n\t\t\t\t\t\tfirstCol[j], \n\t\t\t\t\t\tcolumns[i][j] !== undefined ? columns[i][j] : null\n\t\t\t\t\t];\n\t\t\t\t\tif (valueCount > 1) {\n\t\t\t\t\t\tdata[j].push(columns[i + 1][j] !== undefined ? columns[i + 1][j] : null);\n\t\t\t\t\t}\n\t\t\t\t\tif (valueCount > 2) {\n\t\t\t\t\t\tdata[j].push(columns[i + 2][j] !== undefined ? columns[i + 2][j] : null);\n\t\t\t\t\t}\n\t\t\t\t\tif (valueCount > 3) {\n\t\t\t\t\t\tdata[j].push(columns[i + 3][j] !== undefined ? columns[i + 3][j] : null);\n\t\t\t\t\t}\n\t\t\t\t\tif (valueCount > 4) {\n\t\t\t\t\t\tdata[j].push(columns[i + 4][j] !== undefined ? columns[i + 4][j] : null);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Add the series\n\t\t\t\tseries[seriesIndex] = {\n\t\t\t\t\tname: columns[i].name,\n\t\t\t\t\tdata: data\n\t\t\t\t};\n\n\t\t\t\ti += valueCount;\n\t\t\t}\n\t\t\t\n\t\t\t// Do the callback\n\t\t\toptions.complete({\n\t\t\t\txAxis: {\n\t\t\t\t\ttype: type\n\t\t\t\t},\n\t\t\t\tseries: series\n\t\t\t});\n\t\t}\n\t}\n\t});\n\t\n\t// Register the Data prototype and data function on Highcharts\n\tHighcharts.Data = Data;\n\tHighcharts.data = function (options, chartOptions) {\n\t\treturn new Data(options, chartOptions);\n\t};\n\n\t// Extend Chart.init so that the Chart constructor accepts a new configuration\n\t// option group, data.\n\tHighcharts.wrap(Highcharts.Chart.prototype, 'init', function (proceed, userOptions, callback) {\n\t\tvar chart = this;\n\n\t\tif (userOptions && userOptions.data) {\n\t\t\tHighcharts.data(Highcharts.extend(userOptions.data, {\n\t\t\t\tcomplete: function (dataOptions) {\n\t\t\t\t\t\n\t\t\t\t\t// Merge series configs\n\t\t\t\t\tif (userOptions.series) {\n\t\t\t\t\t\teach(userOptions.series, function (series, i) {\n\t\t\t\t\t\t\tuserOptions.series[i] = Highcharts.merge(series, dataOptions.series[i]);\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\n\t\t\t\t\t// Do the merge\n\t\t\t\t\tuserOptions = Highcharts.merge(dataOptions, userOptions);\n\n\t\t\t\t\tproceed.call(chart, userOptions, callback);\n\t\t\t\t}\n\t\t\t}), userOptions);\n\t\t} else {\n\t\t\tproceed.call(chart, userOptions, callback);\n\t\t}\n\t});\n\n}(Highcharts));\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/highcharts/modules/drilldown.js",
    "content": "(function(e){function q(b,a,c){return\"rgba(\"+[Math.round(b[0]+(a[0]-b[0])*c),Math.round(b[1]+(a[1]-b[1])*c),Math.round(b[2]+(a[2]-b[2])*c),b[3]+(a[3]-b[3])*c].join(\",\")+\")\"}var m=function(){},j=e.getOptions(),g=e.each,n=e.extend,o=e.wrap,h=e.Chart,i=e.seriesTypes,k=i.pie,l=i.column,r=HighchartsAdapter.fireEvent;n(j.lang,{drillUpText:\"◁ Back to {series.name}\"});j.drilldown={activeAxisLabelStyle:{cursor:\"pointer\",color:\"#039\",fontWeight:\"bold\",textDecoration:\"underline\"},activeDataLabelStyle:{cursor:\"pointer\",\ncolor:\"#039\",fontWeight:\"bold\",textDecoration:\"underline\"},animation:{duration:500},drillUpButton:{position:{align:\"right\",x:-10,y:10}}};e.SVGRenderer.prototype.Element.prototype.fadeIn=function(){this.attr({opacity:0.1,visibility:\"visible\"}).animate({opacity:1},{duration:250})};h.prototype.drilldownLevels=[];h.prototype.addSeriesAsDrilldown=function(b,a){var c=b.series,d=c.xAxis,f=c.yAxis,e;e=b.color||c.color;var g,a=n({color:e},a);g=HighchartsAdapter.inArray(this,c.points);this.drilldownLevels.push({seriesOptions:c.userOptions,\nshapeArgs:b.shapeArgs,bBox:b.graphic.getBBox(),color:e,newSeries:a,pointOptions:c.options.data[g],pointIndex:g,oldExtremes:{xMin:d&&d.userMin,xMax:d&&d.userMax,yMin:f&&f.userMin,yMax:f&&f.userMax}});e=this.addSeries(a,!1);if(d)d.oldPos=d.pos,d.userMin=d.userMax=null,f.userMin=f.userMax=null;if(c.type===e.type)e.animate=e.animateDrilldown||m,e.options.animation=!0;c.remove(!1);this.redraw();this.showDrillUpButton()};h.prototype.getDrilldownBackText=function(){return this.options.lang.drillUpText.replace(\"{series.name}\",\nthis.drilldownLevels[this.drilldownLevels.length-1].seriesOptions.name)};h.prototype.showDrillUpButton=function(){var b=this,a=this.getDrilldownBackText(),c=b.options.drilldown.drillUpButton;this.drillUpButton?this.drillUpButton.attr({text:a}).align():this.drillUpButton=this.renderer.button(a,null,null,function(){b.drillUp()}).attr(n({align:c.position.align,zIndex:9},c.theme)).add().align(c.position,!1,c.relativeTo||\"plotBox\")};h.prototype.drillUp=function(){var b=this.drilldownLevels.pop(),a=this.series[0],\nc=b.oldExtremes,d=this.addSeries(b.seriesOptions,!1);r(this,\"drillup\",{seriesOptions:b.seriesOptions});if(d.type===a.type)d.drilldownLevel=b,d.animate=d.animateDrillupTo||m,d.options.animation=!0,a.animateDrillupFrom&&a.animateDrillupFrom(b);a.remove(!1);d.xAxis&&(d.xAxis.setExtremes(c.xMin,c.xMax,!1),d.yAxis.setExtremes(c.yMin,c.yMax,!1));this.redraw();this.drilldownLevels.length===0?this.drillUpButton=this.drillUpButton.destroy():this.drillUpButton.attr({text:this.getDrilldownBackText()}).align()};\nk.prototype.animateDrilldown=function(b){var a=this.chart.drilldownLevels[this.chart.drilldownLevels.length-1],c=this.chart.options.drilldown.animation,d=a.shapeArgs,f=d.start,s=(d.end-f)/this.points.length,h=e.Color(a.color).rgba;b||g(this.points,function(a,b){var g=e.Color(a.color).rgba;a.graphic.attr(e.merge(d,{start:f+b*s,end:f+(b+1)*s})).animate(a.shapeArgs,e.merge(c,{step:function(a,d){d.prop===\"start\"&&this.attr({fill:q(h,g,d.pos)})}}))})};k.prototype.animateDrillupTo=l.prototype.animateDrillupTo=\nfunction(b){if(!b){var a=this,c=a.drilldownLevel;g(this.points,function(a){a.graphic.hide();a.dataLabel&&a.dataLabel.hide();a.connector&&a.connector.hide()});setTimeout(function(){g(a.points,function(a,b){var e=b===c.pointIndex?\"show\":\"fadeIn\";a.graphic[e]();if(a.dataLabel)a.dataLabel[e]();if(a.connector)a.connector[e]()})},Math.max(this.chart.options.drilldown.animation.duration-50,0));this.animate=m}};l.prototype.animateDrilldown=function(b){var a=this.chart.drilldownLevels[this.chart.drilldownLevels.length-\n1].shapeArgs,c=this.chart.options.drilldown.animation;b||(a.x+=this.xAxis.oldPos-this.xAxis.pos,g(this.points,function(b){b.graphic.attr(a).animate(b.shapeArgs,c)}))};l.prototype.animateDrillupFrom=k.prototype.animateDrillupFrom=function(b){var a=this.chart.options.drilldown.animation,c=this.group;delete this.group;g(this.points,function(d){var f=d.graphic,g=e.Color(d.color).rgba;delete d.graphic;f.animate(b.shapeArgs,e.merge(a,{step:function(a,c){c.prop===\"start\"&&this.attr({fill:q(g,e.Color(b.color).rgba,\nc.pos)})},complete:function(){f.destroy();c&&(c=c.destroy())}}))})};e.Point.prototype.doDrilldown=function(){for(var b=this.series.chart,a=b.options.drilldown,c=a.series.length,d;c--&&!d;)a.series[c].id===this.drilldown&&(d=a.series[c]);r(b,\"drilldown\",{point:this,seriesOptions:d});d&&b.addSeriesAsDrilldown(this,d)};o(e.Point.prototype,\"init\",function(b,a,c,d){var f=b.call(this,a,c,d),b=a.chart,a=(a=a.xAxis&&a.xAxis.ticks[d])&&a.label;if(f.drilldown){if(e.addEvent(f,\"click\",function(){f.doDrilldown()}),\na){if(!a._basicStyle)a._basicStyle=a.element.getAttribute(\"style\");a.addClass(\"highcharts-drilldown-axis-label\").css(b.options.drilldown.activeAxisLabelStyle).on(\"click\",function(){f.doDrilldown&&f.doDrilldown()})}}else a&&a._basicStyle&&a.element.setAttribute(\"style\",a._basicStyle);return f});o(e.Series.prototype,\"drawDataLabels\",function(b){var a=this.chart.options.drilldown.activeDataLabelStyle;b.call(this);g(this.points,function(b){if(b.drilldown&&b.dataLabel)b.dataLabel.attr({\"class\":\"highcharts-drilldown-data-label\"}).css(a).on(\"click\",\nfunction(){b.doDrilldown()})})});l.prototype.supportsDrilldown=!0;k.prototype.supportsDrilldown=!0;var p,j=function(b){b.call(this);g(this.points,function(a){a.drilldown&&a.graphic&&a.graphic.attr({\"class\":\"highcharts-drilldown-point\"}).css({cursor:\"pointer\"})})};for(p in i)i[p].prototype.supportsDrilldown&&o(i[p].prototype,\"drawTracker\",j)})(Highcharts);\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/highcharts/modules/drilldown.src.js",
    "content": "/**\n * Highcharts Drilldown plugin\n * \n * Author: Torstein Honsi\n * Last revision: 2013-02-18\n * License: MIT License\n *\n * Demo: http://jsfiddle.net/highcharts/Vf3yT/\n */\n\n/*global HighchartsAdapter*/\n(function (H) {\n\n\t\"use strict\";\n\n\tvar noop = function () {},\n\t\tdefaultOptions = H.getOptions(),\n\t\teach = H.each,\n\t\textend = H.extend,\n\t\twrap = H.wrap,\n\t\tChart = H.Chart,\n\t\tseriesTypes = H.seriesTypes,\n\t\tPieSeries = seriesTypes.pie,\n\t\tColumnSeries = seriesTypes.column,\n\t\tfireEvent = HighchartsAdapter.fireEvent;\n\n\t// Utilities\n\tfunction tweenColors(startColor, endColor, pos) {\n\t\tvar rgba = [\n\t\t\t\tMath.round(startColor[0] + (endColor[0] - startColor[0]) * pos),\n\t\t\t\tMath.round(startColor[1] + (endColor[1] - startColor[1]) * pos),\n\t\t\t\tMath.round(startColor[2] + (endColor[2] - startColor[2]) * pos),\n\t\t\t\tstartColor[3] + (endColor[3] - startColor[3]) * pos\n\t\t\t];\n\t\treturn 'rgba(' + rgba.join(',') + ')';\n\t}\n\n\t// Add language\n\textend(defaultOptions.lang, {\n\t\tdrillUpText: '◁ Back to {series.name}'\n\t});\n\tdefaultOptions.drilldown = {\n\t\tactiveAxisLabelStyle: {\n\t\t\tcursor: 'pointer',\n\t\t\tcolor: '#039',\n\t\t\tfontWeight: 'bold',\n\t\t\ttextDecoration: 'underline'\t\t\t\n\t\t},\n\t\tactiveDataLabelStyle: {\n\t\t\tcursor: 'pointer',\n\t\t\tcolor: '#039',\n\t\t\tfontWeight: 'bold',\n\t\t\ttextDecoration: 'underline'\t\t\t\n\t\t},\n\t\tanimation: {\n\t\t\tduration: 500\n\t\t},\n\t\tdrillUpButton: {\n\t\t\tposition: { \n\t\t\t\talign: 'right',\n\t\t\t\tx: -10,\n\t\t\t\ty: 10\n\t\t\t}\n\t\t\t// relativeTo: 'plotBox'\n\t\t\t// theme\n\t\t}\n\t};\t\n\n\t/**\n\t * A general fadeIn method\n\t */\n\tH.SVGRenderer.prototype.Element.prototype.fadeIn = function () {\n\t\tthis\n\t\t.attr({\n\t\t\topacity: 0.1,\n\t\t\tvisibility: 'visible'\n\t\t})\n\t\t.animate({\n\t\t\topacity: 1\n\t\t}, {\n\t\t\tduration: 250\n\t\t});\n\t};\n\n\t// Extend the Chart prototype\n\tChart.prototype.drilldownLevels = [];\n\n\tChart.prototype.addSeriesAsDrilldown = function (point, ddOptions) {\n\t\tvar oldSeries = point.series,\n\t\t\txAxis = oldSeries.xAxis,\n\t\t\tyAxis = oldSeries.yAxis,\n\t\t\tnewSeries,\n\t\t\tcolor = point.color || oldSeries.color,\n\t\t\tpointIndex,\n\t\t\tlevel;\n\t\t\t\n\t\tddOptions = extend({\n\t\t\tcolor: color\n\t\t}, ddOptions);\n\t\tpointIndex = HighchartsAdapter.inArray(this, oldSeries.points);\n\t\tlevel = {\n\t\t\tseriesOptions: oldSeries.userOptions,\n\t\t\tshapeArgs: point.shapeArgs,\n\t\t\tbBox: point.graphic.getBBox(),\n\t\t\tcolor: color,\n\t\t\tnewSeries: ddOptions,\n\t\t\tpointOptions: oldSeries.options.data[pointIndex],\n\t\t\tpointIndex: pointIndex,\n\t\t\toldExtremes: {\n\t\t\t\txMin: xAxis && xAxis.userMin,\n\t\t\t\txMax: xAxis && xAxis.userMax,\n\t\t\t\tyMin: yAxis && yAxis.userMin,\n\t\t\t\tyMax: yAxis && yAxis.userMax\n\t\t\t}\n\t\t};\n\n\t\tthis.drilldownLevels.push(level);\n\n\t\tnewSeries = this.addSeries(ddOptions, false);\n\t\tif (xAxis) {\n\t\t\txAxis.oldPos = xAxis.pos;\n\t\t\txAxis.userMin = xAxis.userMax = null;\n\t\t\tyAxis.userMin = yAxis.userMax = null;\n\t\t}\n\n\t\t// Run fancy cross-animation on supported and equal types\n\t\tif (oldSeries.type === newSeries.type) {\n\t\t\tnewSeries.animate = newSeries.animateDrilldown || noop;\n\t\t\tnewSeries.options.animation = true;\n\t\t}\n\t\t\n\t\toldSeries.remove(false);\n\t\t\n\t\tthis.redraw();\n\t\tthis.showDrillUpButton();\n\t};\n\n\tChart.prototype.getDrilldownBackText = function () {\n\t\tvar lastLevel = this.drilldownLevels[this.drilldownLevels.length - 1];\n\n\t\treturn this.options.lang.drillUpText.replace('{series.name}', lastLevel.seriesOptions.name);\n\n\t};\n\n\tChart.prototype.showDrillUpButton = function () {\n\t\tvar chart = this,\n\t\t\tbackText = this.getDrilldownBackText(),\n\t\t\tbuttonOptions = chart.options.drilldown.drillUpButton;\n\t\t\t\n\n\t\tif (!this.drillUpButton) {\n\t\t\tthis.drillUpButton = this.renderer.button(\n\t\t\t\tbackText,\n\t\t\t\tnull,\n\t\t\t\tnull,\n\t\t\t\tfunction () {\n\t\t\t\t\tchart.drillUp(); \n\t\t\t\t}\n\t\t\t)\n\t\t\t.attr(extend({\n\t\t\t\talign: buttonOptions.position.align,\n\t\t\t\tzIndex: 9\n\t\t\t}, buttonOptions.theme))\n\t\t\t.add()\n\t\t\t.align(buttonOptions.position, false, buttonOptions.relativeTo || 'plotBox');\n\t\t} else {\n\t\t\tthis.drillUpButton.attr({\n\t\t\t\ttext: backText\n\t\t\t})\n\t\t\t.align();\n\t\t}\n\t};\n\n\tChart.prototype.drillUp = function () {\n\t\tvar chart = this,\n\t\t\tlevel = chart.drilldownLevels.pop(),\n\t\t\toldSeries = chart.series[0],\n\t\t\toldExtremes = level.oldExtremes,\n\t\t\tnewSeries = chart.addSeries(level.seriesOptions, false);\n\t\t\n\t\tfireEvent(chart, 'drillup', { seriesOptions: level.seriesOptions });\n\n\t\tif (newSeries.type === oldSeries.type) {\n\t\t\tnewSeries.drilldownLevel = level;\n\t\t\tnewSeries.animate = newSeries.animateDrillupTo || noop;\n\t\t\tnewSeries.options.animation = true;\n\n\t\t\tif (oldSeries.animateDrillupFrom) {\n\t\t\t\toldSeries.animateDrillupFrom(level);\n\t\t\t}\n\t\t}\n\n\t\toldSeries.remove(false);\n\n\t\t// Reset the zoom level of the upper series\n\t\tif (newSeries.xAxis) {\n\t\t\tnewSeries.xAxis.setExtremes(oldExtremes.xMin, oldExtremes.xMax, false);\n\t\t\tnewSeries.yAxis.setExtremes(oldExtremes.yMin, oldExtremes.yMax, false);\n\t\t}\n\n\n\t\tthis.redraw();\n\n\t\tif (this.drilldownLevels.length === 0) {\n\t\t\tthis.drillUpButton = this.drillUpButton.destroy();\n\t\t} else {\n\t\t\tthis.drillUpButton.attr({\n\t\t\t\ttext: this.getDrilldownBackText()\n\t\t\t})\n\t\t\t.align();\n\t\t}\n\t};\n\n\tPieSeries.prototype.animateDrilldown = function (init) {\n\t\tvar level = this.chart.drilldownLevels[this.chart.drilldownLevels.length - 1],\n\t\t\tanimationOptions = this.chart.options.drilldown.animation,\n\t\t\tanimateFrom = level.shapeArgs,\n\t\t\tstart = animateFrom.start,\n\t\t\tangle = animateFrom.end - start,\n\t\t\tstartAngle = angle / this.points.length,\n\t\t\tstartColor = H.Color(level.color).rgba;\n\n\t\tif (!init) {\n\t\t\teach(this.points, function (point, i) {\n\t\t\t\tvar endColor = H.Color(point.color).rgba;\n\n\t\t\t\t/*jslint unparam: true*/\n\t\t\t\tpoint.graphic\n\t\t\t\t\t.attr(H.merge(animateFrom, {\n\t\t\t\t\t\tstart: start + i * startAngle,\n\t\t\t\t\t\tend: start + (i + 1) * startAngle\n\t\t\t\t\t}))\n\t\t\t\t\t.animate(point.shapeArgs, H.merge(animationOptions, {\n\t\t\t\t\t\tstep: function (val, fx) {\n\t\t\t\t\t\t\tif (fx.prop === 'start') {\n\t\t\t\t\t\t\t\tthis.attr({\n\t\t\t\t\t\t\t\t\tfill: tweenColors(startColor, endColor, fx.pos)\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}));\n\t\t\t\t/*jslint unparam: false*/\n\t\t\t});\n\t\t}\n\t};\n\n\n\t/**\n\t * When drilling up, keep the upper series invisible until the lower series has\n\t * moved into place\n\t */\n\tPieSeries.prototype.animateDrillupTo = \n\t\t\tColumnSeries.prototype.animateDrillupTo = function (init) {\n\t\tif (!init) {\n\t\t\tvar newSeries = this,\n\t\t\t\tlevel = newSeries.drilldownLevel;\n\n\t\t\teach(this.points, function (point) {\n\t\t\t\tpoint.graphic.hide();\n\t\t\t\tif (point.dataLabel) {\n\t\t\t\t\tpoint.dataLabel.hide();\n\t\t\t\t}\n\t\t\t\tif (point.connector) {\n\t\t\t\t\tpoint.connector.hide();\n\t\t\t\t}\n\t\t\t});\n\n\n\t\t\t// Do dummy animation on first point to get to complete\n\t\t\tsetTimeout(function () {\n\t\t\t\teach(newSeries.points, function (point, i) {  \n\t\t\t\t\t// Fade in other points\t\t\t  \n\t\t\t\t\tvar verb = i === level.pointIndex ? 'show' : 'fadeIn';\n\t\t\t\t\tpoint.graphic[verb]();\n\t\t\t\t\tif (point.dataLabel) {\n\t\t\t\t\t\tpoint.dataLabel[verb]();\n\t\t\t\t\t}\n\t\t\t\t\tif (point.connector) {\n\t\t\t\t\t\tpoint.connector[verb]();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}, Math.max(this.chart.options.drilldown.animation.duration - 50, 0));\n\n\t\t\t// Reset\n\t\t\tthis.animate = noop;\n\t\t}\n\n\t};\n\t\n\tColumnSeries.prototype.animateDrilldown = function (init) {\n\t\tvar animateFrom = this.chart.drilldownLevels[this.chart.drilldownLevels.length - 1].shapeArgs,\n\t\t\tanimationOptions = this.chart.options.drilldown.animation;\n\t\t\t\n\t\tif (!init) {\n\n\t\t\tanimateFrom.x += (this.xAxis.oldPos - this.xAxis.pos);\n\t\n\t\t\teach(this.points, function (point) {\n\t\t\t\tpoint.graphic\n\t\t\t\t\t.attr(animateFrom)\n\t\t\t\t\t.animate(point.shapeArgs, animationOptions);\n\t\t\t});\n\t\t}\n\t\t\n\t};\n\n\t/**\n\t * When drilling up, pull out the individual point graphics from the lower series\n\t * and animate them into the origin point in the upper series.\n\t */\n\tColumnSeries.prototype.animateDrillupFrom = \n\t\tPieSeries.prototype.animateDrillupFrom =\n\tfunction (level) {\n\t\tvar animationOptions = this.chart.options.drilldown.animation,\n\t\t\tgroup = this.group;\n\n\t\tdelete this.group;\n\t\teach(this.points, function (point) {\n\t\t\tvar graphic = point.graphic,\n\t\t\t\tstartColor = H.Color(point.color).rgba;\n\n\t\t\tdelete point.graphic;\n\n\t\t\t/*jslint unparam: true*/\n\t\t\tgraphic.animate(level.shapeArgs, H.merge(animationOptions, {\n\n\t\t\t\tstep: function (val, fx) {\n\t\t\t\t\tif (fx.prop === 'start') {\n\t\t\t\t\t\tthis.attr({\n\t\t\t\t\t\t\tfill: tweenColors(startColor, H.Color(level.color).rgba, fx.pos)\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tcomplete: function () {\n\t\t\t\t\tgraphic.destroy();\n\t\t\t\t\tif (group) {\n\t\t\t\t\t\tgroup = group.destroy();\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}));\n\t\t\t/*jslint unparam: false*/\n\t\t});\n\t};\n\t\n\tH.Point.prototype.doDrilldown = function () {\n\t\tvar series = this.series,\n\t\t\tchart = series.chart,\n\t\t\tdrilldown = chart.options.drilldown,\n\t\t\ti = drilldown.series.length,\n\t\t\tseriesOptions;\n\t\t\n\t\twhile (i-- && !seriesOptions) {\n\t\t\tif (drilldown.series[i].id === this.drilldown) {\n\t\t\t\tseriesOptions = drilldown.series[i];\n\t\t\t}\n\t\t}\n\n\t\t// Fire the event. If seriesOptions is undefined, the implementer can check for \n\t\t// seriesOptions, and call addSeriesAsDrilldown async if necessary.\n\t\tfireEvent(chart, 'drilldown', { \n\t\t\tpoint: this,\n\t\t\tseriesOptions: seriesOptions\n\t\t});\n\t\t\n\t\tif (seriesOptions) {\n\t\t\tchart.addSeriesAsDrilldown(this, seriesOptions);\n\t\t}\n\n\t};\n\t\n\twrap(H.Point.prototype, 'init', function (proceed, series, options, x) {\n\t\tvar point = proceed.call(this, series, options, x),\n\t\t\tchart = series.chart,\n\t\t\ttick = series.xAxis && series.xAxis.ticks[x],\n\t\t\ttickLabel = tick && tick.label;\n\t\t\n\t\tif (point.drilldown) {\n\t\t\t\n\t\t\t// Add the click event to the point label\n\t\t\tH.addEvent(point, 'click', function () {\n\t\t\t\tpoint.doDrilldown();\n\t\t\t});\n\t\t\t\n\t\t\t// Make axis labels clickable\n\t\t\tif (tickLabel) {\n\t\t\t\tif (!tickLabel._basicStyle) {\n\t\t\t\t\ttickLabel._basicStyle = tickLabel.element.getAttribute('style');\n\t\t\t\t}\n\t\t\t\ttickLabel\n\t\t\t\t\t.addClass('highcharts-drilldown-axis-label')\n\t\t\t\t\t.css(chart.options.drilldown.activeAxisLabelStyle)\n\t\t\t\t\t.on('click', function () {\n\t\t\t\t\t\tif (point.doDrilldown) {\n\t\t\t\t\t\t\tpoint.doDrilldown();\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t\t\n\t\t\t}\n\t\t} else if (tickLabel && tickLabel._basicStyle) {\n\t\t\ttickLabel.element.setAttribute('style', tickLabel._basicStyle);\n\t\t}\n\t\t\n\t\treturn point;\n\t});\n\n\twrap(H.Series.prototype, 'drawDataLabels', function (proceed) {\n\t\tvar css = this.chart.options.drilldown.activeDataLabelStyle;\n\n\t\tproceed.call(this);\n\n\t\teach(this.points, function (point) {\n\t\t\tif (point.drilldown && point.dataLabel) {\n\t\t\t\tpoint.dataLabel\n\t\t\t\t\t.attr({\n\t\t\t\t\t\t'class': 'highcharts-drilldown-data-label'\n\t\t\t\t\t})\n\t\t\t\t\t.css(css)\n\t\t\t\t\t.on('click', function () {\n\t\t\t\t\t\tpoint.doDrilldown();\n\t\t\t\t\t});\n\t\t\t}\n\t\t});\n\t});\n\n\t// Mark the trackers with a pointer \n\tColumnSeries.prototype.supportsDrilldown = true;\n\tPieSeries.prototype.supportsDrilldown = true;\n\tvar type, \n\t\tdrawTrackerWrapper = function (proceed) {\n\t\t\tproceed.call(this);\n\t\t\teach(this.points, function (point) {\n\t\t\t\tif (point.drilldown && point.graphic) {\n\t\t\t\t\tpoint.graphic\n\t\t\t\t\t\t.attr({\n\t\t\t\t\t\t\t'class': 'highcharts-drilldown-point'\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.css({ cursor: 'pointer' });\n\t\t\t\t}\n\t\t\t});\n\t\t};\n\tfor (type in seriesTypes) {\n\t\tif (seriesTypes[type].prototype.supportsDrilldown) {\n\t\t\twrap(seriesTypes[type].prototype, 'drawTracker', drawTrackerWrapper);\n\t\t}\n\t}\n\t\t\n}(Highcharts));\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/highcharts/modules/exporting.js",
    "content": "/*\n Highcharts JS v3.0.6 (2013-10-04)\n Exporting module\n\n (c) 2010-2013 Torstein Hønsi\n\n License: www.highcharts.com/license\n*/\n(function(f){var A=f.Chart,t=f.addEvent,C=f.removeEvent,k=f.createElement,n=f.discardElement,u=f.css,o=f.merge,r=f.each,p=f.extend,D=Math.max,j=document,B=window,E=f.isTouchDevice,F=f.Renderer.prototype.symbols,x=f.getOptions(),y;p(x.lang,{printChart:\"Print chart\",downloadPNG:\"Download PNG image\",downloadJPEG:\"Download JPEG image\",downloadPDF:\"Download PDF document\",downloadSVG:\"Download SVG vector image\",contextButtonTitle:\"Chart context menu\"});x.navigation={menuStyle:{border:\"1px solid #A0A0A0\",\nbackground:\"#FFFFFF\",padding:\"5px 0\"},menuItemStyle:{padding:\"0 10px\",background:\"none\",color:\"#303030\",fontSize:E?\"14px\":\"11px\"},menuItemHoverStyle:{background:\"#4572A5\",color:\"#FFFFFF\"},buttonOptions:{symbolFill:\"#E0E0E0\",symbolSize:14,symbolStroke:\"#666\",symbolStrokeWidth:3,symbolX:12.5,symbolY:10.5,align:\"right\",buttonSpacing:3,height:22,theme:{fill:\"white\",stroke:\"none\"},verticalAlign:\"top\",width:24}};x.exporting={type:\"image/png\",url:\"http://export.highcharts.com/\",buttons:{contextButton:{menuClassName:\"highcharts-contextmenu\",\nsymbol:\"menu\",_titleKey:\"contextButtonTitle\",menuItems:[{textKey:\"printChart\",onclick:function(){this.print()}},{separator:!0},{textKey:\"downloadPNG\",onclick:function(){this.exportChart()}},{textKey:\"downloadJPEG\",onclick:function(){this.exportChart({type:\"image/jpeg\"})}},{textKey:\"downloadPDF\",onclick:function(){this.exportChart({type:\"application/pdf\"})}},{textKey:\"downloadSVG\",onclick:function(){this.exportChart({type:\"image/svg+xml\"})}}]}}};f.post=function(c,a){var d,b;b=k(\"form\",{method:\"post\",\naction:c,enctype:\"multipart/form-data\"},{display:\"none\"},j.body);for(d in a)k(\"input\",{type:\"hidden\",name:d,value:a[d]},null,b);b.submit();n(b)};p(A.prototype,{getSVG:function(c){var a=this,d,b,z,h,g=o(a.options,c);if(!j.createElementNS)j.createElementNS=function(a,b){return j.createElement(b)};c=k(\"div\",null,{position:\"absolute\",top:\"-9999em\",width:a.chartWidth+\"px\",height:a.chartHeight+\"px\"},j.body);b=a.renderTo.style.width;h=a.renderTo.style.height;b=g.exporting.sourceWidth||g.chart.width||/px$/.test(b)&&\nparseInt(b,10)||600;h=g.exporting.sourceHeight||g.chart.height||/px$/.test(h)&&parseInt(h,10)||400;p(g.chart,{animation:!1,renderTo:c,forExport:!0,width:b,height:h});g.exporting.enabled=!1;g.series=[];r(a.series,function(a){z=o(a.options,{animation:!1,showCheckbox:!1,visible:a.visible});z.isInternal||g.series.push(z)});d=new f.Chart(g,a.callback);r([\"xAxis\",\"yAxis\"],function(b){r(a[b],function(a,c){var g=d[b][c],f=a.getExtremes(),h=f.userMin,f=f.userMax;g&&(h!==void 0||f!==void 0)&&g.setExtremes(h,\nf,!0,!1)})});b=d.container.innerHTML;g=null;d.destroy();n(c);b=b.replace(/zIndex=\"[^\"]+\"/g,\"\").replace(/isShadow=\"[^\"]+\"/g,\"\").replace(/symbolName=\"[^\"]+\"/g,\"\").replace(/jQuery[0-9]+=\"[^\"]+\"/g,\"\").replace(/url\\([^#]+#/g,\"url(#\").replace(/<svg /,'<svg xmlns:xlink=\"http://www.w3.org/1999/xlink\" ').replace(/ href=/g,\" xlink:href=\").replace(/\\n/,\" \").replace(/<\\/svg>.*?$/,\"</svg>\").replace(/&nbsp;/g,\" \").replace(/&shy;/g,\"­\").replace(/<IMG /g,\"<image \").replace(/height=([^\" ]+)/g,'height=\"$1\"').replace(/width=([^\" ]+)/g,\n'width=\"$1\"').replace(/hc-svg-href=\"([^\"]+)\">/g,'xlink:href=\"$1\"/>').replace(/id=([^\" >]+)/g,'id=\"$1\"').replace(/class=([^\" >]+)/g,'class=\"$1\"').replace(/ transform /g,\" \").replace(/:(path|rect)/g,\"$1\").replace(/style=\"([^\"]+)\"/g,function(a){return a.toLowerCase()});return b=b.replace(/(url\\(#highcharts-[0-9]+)&quot;/g,\"$1\").replace(/&quot;/g,\"'\")},exportChart:function(c,a){var c=c||{},d=this.options.exporting,d=this.getSVG(o({chart:{borderRadius:0}},d.chartOptions,a,{exporting:{sourceWidth:c.sourceWidth||\nd.sourceWidth,sourceHeight:c.sourceHeight||d.sourceHeight}})),c=o(this.options.exporting,c);f.post(c.url,{filename:c.filename||\"chart\",type:c.type,width:c.width||0,scale:c.scale||2,svg:d})},print:function(){var c=this,a=c.container,d=[],b=a.parentNode,f=j.body,h=f.childNodes;if(!c.isPrinting)c.isPrinting=!0,r(h,function(a,b){if(a.nodeType===1)d[b]=a.style.display,a.style.display=\"none\"}),f.appendChild(a),B.focus(),B.print(),setTimeout(function(){b.appendChild(a);r(h,function(a,b){if(a.nodeType===\n1)a.style.display=d[b]});c.isPrinting=!1},1E3)},contextMenu:function(c,a,d,b,f,h,g){var e=this,j=e.options.navigation,q=j.menuItemStyle,l=e.chartWidth,m=e.chartHeight,o=\"cache-\"+c,i=e[o],s=D(f,h),v,w,n;if(!i)e[o]=i=k(\"div\",{className:c},{position:\"absolute\",zIndex:1E3,padding:s+\"px\"},e.container),v=k(\"div\",null,p({MozBoxShadow:\"3px 3px 10px #888\",WebkitBoxShadow:\"3px 3px 10px #888\",boxShadow:\"3px 3px 10px #888\"},j.menuStyle),i),w=function(){u(i,{display:\"none\"});g&&g.setState(0);e.openMenu=!1},t(i,\n\"mouseleave\",function(){n=setTimeout(w,500)}),t(i,\"mouseenter\",function(){clearTimeout(n)}),t(document,\"mousedown\",function(a){e.pointer.inClass(a.target,c)||w()}),r(a,function(a){if(a){var b=a.separator?k(\"hr\",null,null,v):k(\"div\",{onmouseover:function(){u(this,j.menuItemHoverStyle)},onmouseout:function(){u(this,q)},onclick:function(){w();a.onclick.apply(e,arguments)},innerHTML:a.text||e.options.lang[a.textKey]},p({cursor:\"pointer\"},q),v);e.exportDivElements.push(b)}}),e.exportDivElements.push(v,\ni),e.exportMenuWidth=i.offsetWidth,e.exportMenuHeight=i.offsetHeight;a={display:\"block\"};d+e.exportMenuWidth>l?a.right=l-d-f-s+\"px\":a.left=d-s+\"px\";b+h+e.exportMenuHeight>m&&g.alignOptions.verticalAlign!==\"top\"?a.bottom=m-b-s+\"px\":a.top=b+h-s+\"px\";u(i,a);e.openMenu=!0},addButton:function(c){var a=this,d=a.renderer,b=o(a.options.navigation.buttonOptions,c),j=b.onclick,h=b.menuItems,g,e,k={stroke:b.symbolStroke,fill:b.symbolFill},q=b.symbolSize||12;if(!a.btnCount)a.btnCount=0;if(!a.exportDivElements)a.exportDivElements=\n[],a.exportSVGElements=[];if(b.enabled!==!1){var l=b.theme,m=l.states,n=m&&m.hover,m=m&&m.select,i;delete l.states;j?i=function(){j.apply(a,arguments)}:h&&(i=function(){a.contextMenu(e.menuClassName,h,e.translateX,e.translateY,e.width,e.height,e);e.setState(2)});b.text&&b.symbol?l.paddingLeft=f.pick(l.paddingLeft,25):b.text||p(l,{width:b.width,height:b.height,padding:0});e=d.button(b.text,0,0,i,l,n,m).attr({title:a.options.lang[b._titleKey],\"stroke-linecap\":\"round\"});e.menuClassName=c.menuClassName||\n\"highcharts-menu-\"+a.btnCount++;b.symbol&&(g=d.symbol(b.symbol,b.symbolX-q/2,b.symbolY-q/2,q,q).attr(p(k,{\"stroke-width\":b.symbolStrokeWidth||1,zIndex:1})).add(e));e.add().align(p(b,{width:e.width,x:f.pick(b.x,y)}),!0,\"spacingBox\");y+=(e.width+b.buttonSpacing)*(b.align===\"right\"?-1:1);a.exportSVGElements.push(e,g)}},destroyExport:function(c){var c=c.target,a,d;for(a=0;a<c.exportSVGElements.length;a++)if(d=c.exportSVGElements[a])d.onclick=d.ontouchstart=null,c.exportSVGElements[a]=d.destroy();for(a=\n0;a<c.exportDivElements.length;a++)d=c.exportDivElements[a],C(d,\"mouseleave\"),c.exportDivElements[a]=d.onmouseout=d.onmouseover=d.ontouchstart=d.onclick=null,n(d)}});F.menu=function(c,a,d,b){return[\"M\",c,a+2.5,\"L\",c+d,a+2.5,\"M\",c,a+b/2+0.5,\"L\",c+d,a+b/2+0.5,\"M\",c,a+b-1.5,\"L\",c+d,a+b-1.5]};A.prototype.callbacks.push(function(c){var a,d=c.options.exporting,b=d.buttons;y=0;if(d.enabled!==!1){for(a in b)c.addButton(b[a]);t(c,\"destroy\",c.destroyExport)}})})(Highcharts);\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/highcharts/modules/exporting.src.js",
    "content": "/**\n * @license Highcharts JS v3.0.6 (2013-10-04)\n * Exporting module\n *\n * (c) 2010-2013 Torstein Hønsi\n *\n * License: www.highcharts.com/license\n */\n\n// JSLint options:\n/*global Highcharts, document, window, Math, setTimeout */\n\n(function (Highcharts) { // encapsulate\n\n// create shortcuts\nvar Chart = Highcharts.Chart,\n\taddEvent = Highcharts.addEvent,\n\tremoveEvent = Highcharts.removeEvent,\n\tcreateElement = Highcharts.createElement,\n\tdiscardElement = Highcharts.discardElement,\n\tcss = Highcharts.css,\n\tmerge = Highcharts.merge,\n\teach = Highcharts.each,\n\textend = Highcharts.extend,\n\tmath = Math,\n\tmathMax = math.max,\n\tdoc = document,\n\twin = window,\n\tisTouchDevice = Highcharts.isTouchDevice,\n\tM = 'M',\n\tL = 'L',\n\tDIV = 'div',\n\tHIDDEN = 'hidden',\n\tNONE = 'none',\n\tPREFIX = 'highcharts-',\n\tABSOLUTE = 'absolute',\n\tPX = 'px',\n\tUNDEFINED,\n\tsymbols = Highcharts.Renderer.prototype.symbols,\n\tdefaultOptions = Highcharts.getOptions(),\n\tbuttonOffset;\n\n\t// Add language\n\textend(defaultOptions.lang, {\n\t\tprintChart: 'Print chart',\n\t\tdownloadPNG: 'Download PNG image',\n\t\tdownloadJPEG: 'Download JPEG image',\n\t\tdownloadPDF: 'Download PDF document',\n\t\tdownloadSVG: 'Download SVG vector image',\n\t\tcontextButtonTitle: 'Chart context menu'\n\t});\n\n// Buttons and menus are collected in a separate config option set called 'navigation'.\n// This can be extended later to add control buttons like zoom and pan right click menus.\ndefaultOptions.navigation = {\n\tmenuStyle: {\n\t\tborder: '1px solid #A0A0A0',\n\t\tbackground: '#FFFFFF',\n\t\tpadding: '5px 0'\n\t},\n\tmenuItemStyle: {\n\t\tpadding: '0 10px',\n\t\tbackground: NONE,\n\t\tcolor: '#303030',\n\t\tfontSize: isTouchDevice ? '14px' : '11px'\n\t},\n\tmenuItemHoverStyle: {\n\t\tbackground: '#4572A5',\n\t\tcolor: '#FFFFFF'\n\t},\n\n\tbuttonOptions: {\n\t\tsymbolFill: '#E0E0E0',\n\t\tsymbolSize: 14,\n\t\tsymbolStroke: '#666',\n\t\tsymbolStrokeWidth: 3,\n\t\tsymbolX: 12.5,\n\t\tsymbolY: 10.5,\n\t\talign: 'right',\n\t\tbuttonSpacing: 3, \n\t\theight: 22,\n\t\t// text: null,\n\t\ttheme: {\n\t\t\tfill: 'white', // capture hover\n\t\t\tstroke: 'none'\n\t\t},\n\t\tverticalAlign: 'top',\n\t\twidth: 24\n\t}\n};\n\n\n\n// Add the export related options\ndefaultOptions.exporting = {\n\t//enabled: true,\n\t//filename: 'chart',\n\ttype: 'image/png',\n\turl: 'http://export.highcharts.com/',\n\t//width: undefined,\n\t//scale: 2\n\tbuttons: {\n\t\tcontextButton: {\n\t\t\tmenuClassName: PREFIX + 'contextmenu',\n\t\t\t//x: -10,\n\t\t\tsymbol: 'menu',\n\t\t\t_titleKey: 'contextButtonTitle',\n\t\t\tmenuItems: [{\n\t\t\t\ttextKey: 'printChart',\n\t\t\t\tonclick: function () {\n\t\t\t\t\tthis.print();\n\t\t\t\t}\n\t\t\t}, {\n\t\t\t\tseparator: true\n\t\t\t}, {\n\t\t\t\ttextKey: 'downloadPNG',\n\t\t\t\tonclick: function () {\n\t\t\t\t\tthis.exportChart();\n\t\t\t\t}\n\t\t\t}, {\n\t\t\t\ttextKey: 'downloadJPEG',\n\t\t\t\tonclick: function () {\n\t\t\t\t\tthis.exportChart({\n\t\t\t\t\t\ttype: 'image/jpeg'\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, {\n\t\t\t\ttextKey: 'downloadPDF',\n\t\t\t\tonclick: function () {\n\t\t\t\t\tthis.exportChart({\n\t\t\t\t\t\ttype: 'application/pdf'\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}, {\n\t\t\t\ttextKey: 'downloadSVG',\n\t\t\t\tonclick: function () {\n\t\t\t\t\tthis.exportChart({\n\t\t\t\t\t\ttype: 'image/svg+xml'\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\t\t\t// Enable this block to add \"View SVG\" to the dropdown menu\n\t\t\t/*\n\t\t\t,{\n\n\t\t\t\ttext: 'View SVG',\n\t\t\t\tonclick: function () {\n\t\t\t\t\tvar svg = this.getSVG()\n\t\t\t\t\t\t.replace(/</g, '\\n&lt;')\n\t\t\t\t\t\t.replace(/>/g, '&gt;');\n\n\t\t\t\t\tdoc.body.innerHTML = '<pre>' + svg + '</pre>';\n\t\t\t\t}\n\t\t\t} // */\n\t\t\t]\n\t\t}\n\t}\n};\n\n// Add the Highcharts.post utility\nHighcharts.post = function (url, data) {\n\tvar name,\n\t\tform;\n\t\n\t// create the form\n\tform = createElement('form', {\n\t\tmethod: 'post',\n\t\taction: url,\n\t\tenctype: 'multipart/form-data'\n\t}, {\n\t\tdisplay: NONE\n\t}, doc.body);\n\n\t// add the data\n\tfor (name in data) {\n\t\tcreateElement('input', {\n\t\t\ttype: HIDDEN,\n\t\t\tname: name,\n\t\t\tvalue: data[name]\n\t\t}, null, form);\n\t}\n\n\t// submit\n\tform.submit();\n\n\t// clean up\n\tdiscardElement(form);\n};\n\nextend(Chart.prototype, {\n\n\t/**\n\t * Return an SVG representation of the chart\n\t *\n\t * @param additionalOptions {Object} Additional chart options for the generated SVG representation\n\t */\n\tgetSVG: function (additionalOptions) {\n\t\tvar chart = this,\n\t\t\tchartCopy,\n\t\t\tsandbox,\n\t\t\tsvg,\n\t\t\tseriesOptions,\n\t\t\tsourceWidth,\n\t\t\tsourceHeight,\n\t\t\tcssWidth,\n\t\t\tcssHeight,\n\t\t\toptions = merge(chart.options, additionalOptions); // copy the options and add extra options\n\n\t\t// IE compatibility hack for generating SVG content that it doesn't really understand\n\t\tif (!doc.createElementNS) {\n\t\t\t/*jslint unparam: true*//* allow unused parameter ns in function below */\n\t\t\tdoc.createElementNS = function (ns, tagName) {\n\t\t\t\treturn doc.createElement(tagName);\n\t\t\t};\n\t\t\t/*jslint unparam: false*/\n\t\t}\n\n\t\t// create a sandbox where a new chart will be generated\n\t\tsandbox = createElement(DIV, null, {\n\t\t\tposition: ABSOLUTE,\n\t\t\ttop: '-9999em',\n\t\t\twidth: chart.chartWidth + PX,\n\t\t\theight: chart.chartHeight + PX\n\t\t}, doc.body);\n\t\t\n\t\t// get the source size\n\t\tcssWidth = chart.renderTo.style.width;\n\t\tcssHeight = chart.renderTo.style.height;\n\t\tsourceWidth = options.exporting.sourceWidth ||\n\t\t\toptions.chart.width ||\n\t\t\t(/px$/.test(cssWidth) && parseInt(cssWidth, 10)) ||\n\t\t\t600;\n\t\tsourceHeight = options.exporting.sourceHeight ||\n\t\t\toptions.chart.height ||\n\t\t\t(/px$/.test(cssHeight) && parseInt(cssHeight, 10)) ||\n\t\t\t400;\n\n\t\t// override some options\n\t\textend(options.chart, {\n\t\t\tanimation: false,\n\t\t\trenderTo: sandbox,\n\t\t\tforExport: true,\n\t\t\twidth: sourceWidth,\n\t\t\theight: sourceHeight\n\t\t});\n\t\toptions.exporting.enabled = false; // hide buttons in print\n\t\t\n\t\t// prepare for replicating the chart\n\t\toptions.series = [];\n\t\teach(chart.series, function (serie) {\n\t\t\tseriesOptions = merge(serie.options, {\n\t\t\t\tanimation: false, // turn off animation\n\t\t\t\tshowCheckbox: false,\n\t\t\t\tvisible: serie.visible\n\t\t\t});\n\n\t\t\tif (!seriesOptions.isInternal) { // used for the navigator series that has its own option set\n\t\t\t\toptions.series.push(seriesOptions);\n\t\t\t}\n\t\t});\n\n\t\t// generate the chart copy\n\t\tchartCopy = new Highcharts.Chart(options, chart.callback);\n\n\t\t// reflect axis extremes in the export\n\t\teach(['xAxis', 'yAxis'], function (axisType) {\n\t\t\teach(chart[axisType], function (axis, i) {\n\t\t\t\tvar axisCopy = chartCopy[axisType][i],\n\t\t\t\t\textremes = axis.getExtremes(),\n\t\t\t\t\tuserMin = extremes.userMin,\n\t\t\t\t\tuserMax = extremes.userMax;\n\n\t\t\t\tif (axisCopy && (userMin !== UNDEFINED || userMax !== UNDEFINED)) {\n\t\t\t\t\taxisCopy.setExtremes(userMin, userMax, true, false);\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\n\t\t// get the SVG from the container's innerHTML\n\t\tsvg = chartCopy.container.innerHTML;\n\n\t\t// free up memory\n\t\toptions = null;\n\t\tchartCopy.destroy();\n\t\tdiscardElement(sandbox);\n\n\t\t// sanitize\n\t\tsvg = svg\n\t\t\t.replace(/zIndex=\"[^\"]+\"/g, '')\n\t\t\t.replace(/isShadow=\"[^\"]+\"/g, '')\n\t\t\t.replace(/symbolName=\"[^\"]+\"/g, '')\n\t\t\t.replace(/jQuery[0-9]+=\"[^\"]+\"/g, '')\n\t\t\t.replace(/url\\([^#]+#/g, 'url(#')\n\t\t\t.replace(/<svg /, '<svg xmlns:xlink=\"http://www.w3.org/1999/xlink\" ')\n\t\t\t.replace(/ href=/g, ' xlink:href=')\n\t\t\t.replace(/\\n/, ' ')\n\t\t\t.replace(/<\\/svg>.*?$/, '</svg>') // any HTML added to the container after the SVG (#894)\n\t\t\t/* This fails in IE < 8\n\t\t\t.replace(/([0-9]+)\\.([0-9]+)/g, function(s1, s2, s3) { // round off to save weight\n\t\t\t\treturn s2 +'.'+ s3[0];\n\t\t\t})*/\n\n\t\t\t// Replace HTML entities, issue #347\n\t\t\t.replace(/&nbsp;/g, '\\u00A0') // no-break space\n\t\t\t.replace(/&shy;/g,  '\\u00AD') // soft hyphen\n\n\t\t\t// IE specific\n\t\t\t.replace(/<IMG /g, '<image ')\n\t\t\t.replace(/height=([^\" ]+)/g, 'height=\"$1\"')\n\t\t\t.replace(/width=([^\" ]+)/g, 'width=\"$1\"')\n\t\t\t.replace(/hc-svg-href=\"([^\"]+)\">/g, 'xlink:href=\"$1\"/>')\n\t\t\t.replace(/id=([^\" >]+)/g, 'id=\"$1\"')\n\t\t\t.replace(/class=([^\" >]+)/g, 'class=\"$1\"')\n\t\t\t.replace(/ transform /g, ' ')\n\t\t\t.replace(/:(path|rect)/g, '$1')\n\t\t\t.replace(/style=\"([^\"]+)\"/g, function (s) {\n\t\t\t\treturn s.toLowerCase();\n\t\t\t});\n\n\t\t// IE9 beta bugs with innerHTML. Test again with final IE9.\n\t\tsvg = svg.replace(/(url\\(#highcharts-[0-9]+)&quot;/g, '$1')\n\t\t\t.replace(/&quot;/g, \"'\");\n\n\t\treturn svg;\n\t},\n\n\t/**\n\t * Submit the SVG representation of the chart to the server\n\t * @param {Object} options Exporting options. Possible members are url, type and width.\n\t * @param {Object} chartOptions Additional chart options for the SVG representation of the chart\n\t */\n\texportChart: function (options, chartOptions) {\n\t\toptions = options || {};\n\t\t\n\t\tvar chart = this,\n\t\t\tchartExportingOptions = chart.options.exporting,\n\t\t\tsvg = chart.getSVG(merge(\n\t\t\t\t{ chart: { borderRadius: 0 } },\n\t\t\t\tchartExportingOptions.chartOptions,\n\t\t\t\tchartOptions, \n\t\t\t\t{\n\t\t\t\t\texporting: {\n\t\t\t\t\t\tsourceWidth: options.sourceWidth || chartExportingOptions.sourceWidth,\n\t\t\t\t\t\tsourceHeight: options.sourceHeight || chartExportingOptions.sourceHeight\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t));\n\n\t\t// merge the options\n\t\toptions = merge(chart.options.exporting, options);\n\t\t\n\t\t// do the post\n\t\tHighcharts.post(options.url, {\n\t\t\tfilename: options.filename || 'chart',\n\t\t\ttype: options.type,\n\t\t\twidth: options.width || 0, // IE8 fails to post undefined correctly, so use 0\n\t\t\tscale: options.scale || 2,\n\t\t\tsvg: svg\n\t\t});\n\n\t},\n\t\n\t/**\n\t * Print the chart\n\t */\n\tprint: function () {\n\n\t\tvar chart = this,\n\t\t\tcontainer = chart.container,\n\t\t\torigDisplay = [],\n\t\t\torigParent = container.parentNode,\n\t\t\tbody = doc.body,\n\t\t\tchildNodes = body.childNodes;\n\n\t\tif (chart.isPrinting) { // block the button while in printing mode\n\t\t\treturn;\n\t\t}\n\n\t\tchart.isPrinting = true;\n\n\t\t// hide all body content\n\t\teach(childNodes, function (node, i) {\n\t\t\tif (node.nodeType === 1) {\n\t\t\t\torigDisplay[i] = node.style.display;\n\t\t\t\tnode.style.display = NONE;\n\t\t\t}\n\t\t});\n\n\t\t// pull out the chart\n\t\tbody.appendChild(container);\n\n\t\t// print\n\t\twin.focus(); // #1510\n\t\twin.print();\n\n\t\t// allow the browser to prepare before reverting\n\t\tsetTimeout(function () {\n\n\t\t\t// put the chart back in\n\t\t\torigParent.appendChild(container);\n\n\t\t\t// restore all body content\n\t\t\teach(childNodes, function (node, i) {\n\t\t\t\tif (node.nodeType === 1) {\n\t\t\t\t\tnode.style.display = origDisplay[i];\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tchart.isPrinting = false;\n\n\t\t}, 1000);\n\n\t},\n\n\t/**\n\t * Display a popup menu for choosing the export type\n\t *\n\t * @param {String} className An identifier for the menu\n\t * @param {Array} items A collection with text and onclicks for the items\n\t * @param {Number} x The x position of the opener button\n\t * @param {Number} y The y position of the opener button\n\t * @param {Number} width The width of the opener button\n\t * @param {Number} height The height of the opener button\n\t */\n\tcontextMenu: function (className, items, x, y, width, height, button) {\n\t\tvar chart = this,\n\t\t\tnavOptions = chart.options.navigation,\n\t\t\tmenuItemStyle = navOptions.menuItemStyle,\n\t\t\tchartWidth = chart.chartWidth,\n\t\t\tchartHeight = chart.chartHeight,\n\t\t\tcacheName = 'cache-' + className,\n\t\t\tmenu = chart[cacheName],\n\t\t\tmenuPadding = mathMax(width, height), // for mouse leave detection\n\t\t\tboxShadow = '3px 3px 10px #888',\n\t\t\tinnerMenu,\n\t\t\thide,\n\t\t\thideTimer,\n\t\t\tmenuStyle;\n\n\t\t// create the menu only the first time\n\t\tif (!menu) {\n\n\t\t\t// create a HTML element above the SVG\n\t\t\tchart[cacheName] = menu = createElement(DIV, {\n\t\t\t\tclassName: className\n\t\t\t}, {\n\t\t\t\tposition: ABSOLUTE,\n\t\t\t\tzIndex: 1000,\n\t\t\t\tpadding: menuPadding + PX\n\t\t\t}, chart.container);\n\n\t\t\tinnerMenu = createElement(DIV, null,\n\t\t\t\textend({\n\t\t\t\t\tMozBoxShadow: boxShadow,\n\t\t\t\t\tWebkitBoxShadow: boxShadow,\n\t\t\t\t\tboxShadow: boxShadow\n\t\t\t\t}, navOptions.menuStyle), menu);\n\n\t\t\t// hide on mouse out\n\t\t\thide = function () {\n\t\t\t\tcss(menu, { display: NONE });\n\t\t\t\tif (button) {\n\t\t\t\t\tbutton.setState(0);\n\t\t\t\t}\n\t\t\t\tchart.openMenu = false;\n\t\t\t};\n\n\t\t\t// Hide the menu some time after mouse leave (#1357)\n\t\t\taddEvent(menu, 'mouseleave', function () {\n\t\t\t\thideTimer = setTimeout(hide, 500);\n\t\t\t});\n\t\t\taddEvent(menu, 'mouseenter', function () {\n\t\t\t\tclearTimeout(hideTimer);\n\t\t\t});\n\t\t\t// Hide it on clicking or touching outside the menu (#2258)\n\t\t\taddEvent(document, 'mousedown', function (e) {\n\t\t\t\tif (!chart.pointer.inClass(e.target, className)) {\n\t\t\t\t\thide();\n\t\t\t\t}\n\t\t\t});\n\n\n\t\t\t// create the items\n\t\t\teach(items, function (item) {\n\t\t\t\tif (item) {\n\t\t\t\t\tvar element = item.separator ? \n\t\t\t\t\t\tcreateElement('hr', null, null, innerMenu) :\n\t\t\t\t\t\tcreateElement(DIV, {\n\t\t\t\t\t\t\tonmouseover: function () {\n\t\t\t\t\t\t\t\tcss(this, navOptions.menuItemHoverStyle);\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tonmouseout: function () {\n\t\t\t\t\t\t\t\tcss(this, menuItemStyle);\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tonclick: function () {\n\t\t\t\t\t\t\t\thide();\n\t\t\t\t\t\t\t\titem.onclick.apply(chart, arguments);\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\tinnerHTML: item.text || chart.options.lang[item.textKey]\n\t\t\t\t\t\t}, extend({\n\t\t\t\t\t\t\tcursor: 'pointer'\n\t\t\t\t\t\t}, menuItemStyle), innerMenu);\n\n\n\t\t\t\t\t// Keep references to menu divs to be able to destroy them\n\t\t\t\t\tchart.exportDivElements.push(element);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// Keep references to menu and innerMenu div to be able to destroy them\n\t\t\tchart.exportDivElements.push(innerMenu, menu);\n\n\t\t\tchart.exportMenuWidth = menu.offsetWidth;\n\t\t\tchart.exportMenuHeight = menu.offsetHeight;\n\t\t}\n\n\t\tmenuStyle = { display: 'block' };\n\n\t\t// if outside right, right align it\n\t\tif (x + chart.exportMenuWidth > chartWidth) {\n\t\t\tmenuStyle.right = (chartWidth - x - width - menuPadding) + PX;\n\t\t} else {\n\t\t\tmenuStyle.left = (x - menuPadding) + PX;\n\t\t}\n\t\t// if outside bottom, bottom align it\n\t\tif (y + height + chart.exportMenuHeight > chartHeight && button.alignOptions.verticalAlign !== 'top') {\n\t\t\tmenuStyle.bottom = (chartHeight - y - menuPadding)  + PX;\n\t\t} else {\n\t\t\tmenuStyle.top = (y + height - menuPadding) + PX;\n\t\t}\n\n\t\tcss(menu, menuStyle);\n\t\tchart.openMenu = true;\n\t},\n\n\t/**\n\t * Add the export button to the chart\n\t */\n\taddButton: function (options) {\n\t\tvar chart = this,\n\t\t\trenderer = chart.renderer,\n\t\t\tbtnOptions = merge(chart.options.navigation.buttonOptions, options),\n\t\t\tonclick = btnOptions.onclick,\n\t\t\tmenuItems = btnOptions.menuItems,\n\t\t\tsymbol,\n\t\t\tbutton,\n\t\t\tsymbolAttr = {\n\t\t\t\tstroke: btnOptions.symbolStroke,\n\t\t\t\tfill: btnOptions.symbolFill\n\t\t\t},\n\t\t\tsymbolSize = btnOptions.symbolSize || 12;\n\t\tif (!chart.btnCount) {\n\t\t\tchart.btnCount = 0;\n\t\t}\n\n\t\t// Keeps references to the button elements\n\t\tif (!chart.exportDivElements) {\n\t\t\tchart.exportDivElements = [];\n\t\t\tchart.exportSVGElements = [];\n\t\t}\n\n\t\tif (btnOptions.enabled === false) {\n\t\t\treturn;\n\t\t}\n\n\n\t\tvar attr = btnOptions.theme,\n\t\t\tstates = attr.states,\n\t\t\thover = states && states.hover,\n\t\t\tselect = states && states.select,\n\t\t\tcallback;\n\n\t\tdelete attr.states;\n\n\t\tif (onclick) {\n\t\t\tcallback = function () {\n\t\t\t\tonclick.apply(chart, arguments);\n\t\t\t};\n\n\t\t} else if (menuItems) {\n\t\t\tcallback = function () {\n\t\t\t\tchart.contextMenu(\n\t\t\t\t\tbutton.menuClassName, \n\t\t\t\t\tmenuItems, \n\t\t\t\t\tbutton.translateX, \n\t\t\t\t\tbutton.translateY, \n\t\t\t\t\tbutton.width, \n\t\t\t\t\tbutton.height,\n\t\t\t\t\tbutton\n\t\t\t\t);\n\t\t\t\tbutton.setState(2);\n\t\t\t};\n\t\t}\n\n\n\t\tif (btnOptions.text && btnOptions.symbol) {\n\t\t\tattr.paddingLeft = Highcharts.pick(attr.paddingLeft, 25);\n\t\t\n\t\t} else if (!btnOptions.text) {\n\t\t\textend(attr, {\n\t\t\t\twidth: btnOptions.width,\n\t\t\t\theight: btnOptions.height,\n\t\t\t\tpadding: 0\n\t\t\t});\n\t\t}\n\n\t\tbutton = renderer.button(btnOptions.text, 0, 0, callback, attr, hover, select)\n\t\t\t.attr({\n\t\t\t\ttitle: chart.options.lang[btnOptions._titleKey],\n\t\t\t\t'stroke-linecap': 'round'\n\t\t\t});\n\t\tbutton.menuClassName = options.menuClassName || PREFIX + 'menu-' + chart.btnCount++;\n\n\t\tif (btnOptions.symbol) {\n\t\t\tsymbol = renderer.symbol(\n\t\t\t\t\tbtnOptions.symbol,\n\t\t\t\t\tbtnOptions.symbolX - (symbolSize / 2),\n\t\t\t\t\tbtnOptions.symbolY - (symbolSize / 2),\n\t\t\t\t\tsymbolSize,\t\t\t\t\n\t\t\t\t\tsymbolSize\n\t\t\t\t)\n\t\t\t\t.attr(extend(symbolAttr, {\n\t\t\t\t\t'stroke-width': btnOptions.symbolStrokeWidth || 1,\n\t\t\t\t\tzIndex: 1\n\t\t\t\t})).add(button);\n\t\t}\n\n\t\tbutton.add()\n\t\t\t.align(extend(btnOptions, {\n\t\t\t\twidth: button.width,\n\t\t\t\tx: Highcharts.pick(btnOptions.x, buttonOffset) // #1654\n\t\t\t}), true, 'spacingBox');\n\n\t\tbuttonOffset += (button.width + btnOptions.buttonSpacing) * (btnOptions.align === 'right' ? -1 : 1);\n\n\t\tchart.exportSVGElements.push(button, symbol);\n\n\t},\n\n\t/**\n\t * Destroy the buttons.\n\t */\n\tdestroyExport: function (e) {\n\t\tvar chart = e.target,\n\t\t\ti,\n\t\t\telem;\n\n\t\t// Destroy the extra buttons added\n\t\tfor (i = 0; i < chart.exportSVGElements.length; i++) {\n\t\t\telem = chart.exportSVGElements[i];\n\t\t\t\n\t\t\t// Destroy and null the svg/vml elements\n\t\t\tif (elem) { // #1822\n\t\t\t\telem.onclick = elem.ontouchstart = null;\n\t\t\t\tchart.exportSVGElements[i] = elem.destroy();\n\t\t\t}\n\t\t}\n\n\t\t// Destroy the divs for the menu\n\t\tfor (i = 0; i < chart.exportDivElements.length; i++) {\n\t\t\telem = chart.exportDivElements[i];\n\n\t\t\t// Remove the event handler\n\t\t\tremoveEvent(elem, 'mouseleave');\n\n\t\t\t// Remove inline events\n\t\t\tchart.exportDivElements[i] = elem.onmouseout = elem.onmouseover = elem.ontouchstart = elem.onclick = null;\n\n\t\t\t// Destroy the div by moving to garbage bin\n\t\t\tdiscardElement(elem);\n\t\t}\n\t}\n});\n\n\nsymbols.menu = function (x, y, width, height) {\n\tvar arr = [\n\t\tM, x, y + 2.5,\n\t\tL, x + width, y + 2.5,\n\t\tM, x, y + height / 2 + 0.5,\n\t\tL, x + width, y + height / 2 + 0.5,\n\t\tM, x, y + height - 1.5,\n\t\tL, x + width, y + height - 1.5\n\t];\n\treturn arr;\n};\n\n// Add the buttons on chart load\nChart.prototype.callbacks.push(function (chart) {\n\tvar n,\n\t\texportingOptions = chart.options.exporting,\n\t\tbuttons = exportingOptions.buttons;\n\n\tbuttonOffset = 0;\n\n\tif (exportingOptions.enabled !== false) {\n\n\t\tfor (n in buttons) {\n\t\t\tchart.addButton(buttons[n]);\n\t\t}\n\n\t\t// Destroy the export elements at chart destroy\n\t\taddEvent(chart, 'destroy', chart.destroyExport);\n\t}\n\n});\n\n\n}(Highcharts));\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/highcharts/modules/funnel.js",
    "content": "/*\n \n Highcharts funnel module, Beta\n\n (c) 2010-2012 Torstein Hønsi\n\n License: www.highcharts.com/license\n*/\n(function(d){var u=d.getOptions().plotOptions,p=d.seriesTypes,D=d.merge,z=function(){},A=d.each;u.funnel=D(u.pie,{center:[\"50%\",\"50%\"],width:\"90%\",neckWidth:\"30%\",height:\"100%\",neckHeight:\"25%\",dataLabels:{connectorWidth:1,connectorColor:\"#606060\"},size:!0,states:{select:{color:\"#C0C0C0\",borderColor:\"#000000\",shadow:!1}}});p.funnel=d.extendClass(p.pie,{type:\"funnel\",animate:z,translate:function(){var a=function(k,a){return/%$/.test(k)?a*parseInt(k,10)/100:parseInt(k,10)},g=0,e=this.chart,f=e.plotWidth,\ne=e.plotHeight,h=0,c=this.options,C=c.center,b=a(C[0],f),d=a(C[0],e),p=a(c.width,f),i,q,j=a(c.height,e),r=a(c.neckWidth,f),s=a(c.neckHeight,e),v=j-s,a=this.data,w,x,u=c.dataLabels.position===\"left\"?1:0,y,m,B,n,l,t,o;this.getWidthAt=q=function(k){return k>j-s||j===s?r:r+(p-r)*((j-s-k)/(j-s))};this.getX=function(k,a){return b+(a?-1:1)*(q(k)/2+c.dataLabels.distance)};this.center=[b,d,j];this.centerX=b;A(a,function(a){g+=a.y});A(a,function(a){o=null;x=g?a.y/g:0;m=d-j/2+h*j;l=m+x*j;i=q(m);y=b-i/2;B=y+\ni;i=q(l);n=b-i/2;t=n+i;m>v?(y=n=b-r/2,B=t=b+r/2):l>v&&(o=l,i=q(v),n=b-i/2,t=n+i,l=v);w=[\"M\",y,m,\"L\",B,m,t,l];o&&w.push(t,o,n,o);w.push(n,l,\"Z\");a.shapeType=\"path\";a.shapeArgs={d:w};a.percentage=x*100;a.plotX=b;a.plotY=(m+(o||l))/2;a.tooltipPos=[b,a.plotY];a.slice=z;a.half=u;h+=x});this.setTooltipPoints()},drawPoints:function(){var a=this,g=a.options,e=a.chart.renderer;A(a.data,function(f){var h=f.graphic,c=f.shapeArgs;h?h.animate(c):f.graphic=e.path(c).attr({fill:f.color,stroke:g.borderColor,\"stroke-width\":g.borderWidth}).add(a.group)})},\nsortByAngle:z,drawDataLabels:function(){var a=this.data,g=this.options.dataLabels.distance,e,f,h,c=a.length,d,b;for(this.center[2]-=2*g;c--;)h=a[c],f=(e=h.half)?1:-1,b=h.plotY,d=this.getX(b,e),h.labelPos=[0,b,d+(g-5)*f,b,d+g*f,b,e?\"right\":\"left\",0];p.pie.prototype.drawDataLabels.call(this)}})})(Highcharts);\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/highcharts/modules/funnel.src.js",
    "content": "/**\n * @license \n * Highcharts funnel module, Beta\n *\n * (c) 2010-2012 Torstein Hønsi\n *\n * License: www.highcharts.com/license\n */\n\n/*global Highcharts */\n(function (Highcharts) {\n\t\n'use strict';\n\n// create shortcuts\nvar defaultOptions = Highcharts.getOptions(),\n\tdefaultPlotOptions = defaultOptions.plotOptions,\n\tseriesTypes = Highcharts.seriesTypes,\n\tmerge = Highcharts.merge,\n\tnoop = function () {},\n\teach = Highcharts.each;\n\n// set default options\ndefaultPlotOptions.funnel = merge(defaultPlotOptions.pie, {\n\tcenter: ['50%', '50%'],\n\twidth: '90%',\n\tneckWidth: '30%',\n\theight: '100%',\n\tneckHeight: '25%',\n\n\tdataLabels: {\n\t\t//position: 'right',\n\t\tconnectorWidth: 1,\n\t\tconnectorColor: '#606060'\n\t},\n\tsize: true, // to avoid adapting to data label size in Pie.drawDataLabels\n\tstates: {\n\t\tselect: {\n\t\t\tcolor: '#C0C0C0',\n\t\t\tborderColor: '#000000',\n\t\t\tshadow: false\n\t\t}\n\t}\t\n});\n\n\nseriesTypes.funnel = Highcharts.extendClass(seriesTypes.pie, {\n\t\n\ttype: 'funnel',\n\tanimate: noop,\n\n\t/**\n\t * Overrides the pie translate method\n\t */\n\ttranslate: function () {\n\t\t\n\t\tvar \n\t\t\t// Get positions - either an integer or a percentage string must be given\n\t\t\tgetLength = function (length, relativeTo) {\n\t\t\t\treturn (/%$/).test(length) ?\n\t\t\t\t\trelativeTo * parseInt(length, 10) / 100 :\n\t\t\t\t\tparseInt(length, 10);\n\t\t\t},\n\t\t\t\n\t\t\tsum = 0,\n\t\t\tseries = this,\n\t\t\tchart = series.chart,\n\t\t\tplotWidth = chart.plotWidth,\n\t\t\tplotHeight = chart.plotHeight,\n\t\t\tcumulative = 0, // start at top\n\t\t\toptions = series.options,\n\t\t\tcenter = options.center,\n\t\t\tcenterX = getLength(center[0], plotWidth),\n\t\t\tcenterY = getLength(center[0], plotHeight),\n\t\t\twidth = getLength(options.width, plotWidth),\n\t\t\ttempWidth,\n\t\t\tgetWidthAt,\n\t\t\theight = getLength(options.height, plotHeight),\n\t\t\tneckWidth = getLength(options.neckWidth, plotWidth),\n\t\t\tneckHeight = getLength(options.neckHeight, plotHeight),\n\t\t\tneckY = height - neckHeight,\n\t\t\tdata = series.data,\n\t\t\tpath,\n\t\t\tfraction,\n\t\t\thalf = options.dataLabels.position === 'left' ? 1 : 0,\n\n\t\t\tx1, \n\t\t\ty1, \n\t\t\tx2, \n\t\t\tx3, \n\t\t\ty3, \n\t\t\tx4, \n\t\t\ty5;\n\n\t\t// Return the width at a specific y coordinate\n\t\tseries.getWidthAt = getWidthAt = function (y) {\n\t\t\treturn y > height - neckHeight || height === neckHeight ?\n\t\t\t\tneckWidth :\n\t\t\t\tneckWidth + (width - neckWidth) * ((height - neckHeight - y) / (height - neckHeight));\n\t\t};\n\t\tseries.getX = function (y, half) {\n\t\t\treturn centerX + (half ? -1 : 1) * ((getWidthAt(y) / 2) + options.dataLabels.distance);\n\t\t};\n\n\t\t// Expose\n\t\tseries.center = [centerX, centerY, height];\n\t\tseries.centerX = centerX;\n\n\t\t/*\n\t\t * Individual point coordinate naming:\n\t\t *\n\t\t * x1,y1 _________________ x2,y1\n\t\t *  \\                         /\n\t\t *   \\                       /\n\t\t *    \\                     /\n\t\t *     \\                   /\n\t\t *      \\                 /\n\t\t *     x3,y3 _________ x4,y3\n\t\t *\n\t\t * Additional for the base of the neck:\n\t\t *\n\t\t *       |               |\n\t\t *       |               |\n\t\t *       |               |\n\t\t *     x3,y5 _________ x4,y5\n\t\t */\n\n\n\n\n\t\t// get the total sum\n\t\teach(data, function (point) {\n\t\t\tsum += point.y;\n\t\t});\n\n\t\teach(data, function (point) {\n\t\t\t// set start and end positions\n\t\t\ty5 = null;\n\t\t\tfraction = sum ? point.y / sum : 0;\n\t\t\ty1 = centerY - height / 2 + cumulative * height;\n\t\t\ty3 = y1 + fraction * height;\n\t\t\t//tempWidth = neckWidth + (width - neckWidth) * ((height - neckHeight - y1) / (height - neckHeight));\n\t\t\ttempWidth = getWidthAt(y1);\n\t\t\tx1 = centerX - tempWidth / 2;\n\t\t\tx2 = x1 + tempWidth;\n\t\t\ttempWidth = getWidthAt(y3);\n\t\t\tx3 = centerX - tempWidth / 2;\n\t\t\tx4 = x3 + tempWidth;\n\n\t\t\t// the entire point is within the neck\n\t\t\tif (y1 > neckY) {\n\t\t\t\tx1 = x3 = centerX - neckWidth / 2;\n\t\t\t\tx2 = x4 = centerX + neckWidth / 2;\n\t\t\t\n\t\t\t// the base of the neck\n\t\t\t} else if (y3 > neckY) {\n\t\t\t\ty5 = y3;\n\n\t\t\t\ttempWidth = getWidthAt(neckY);\n\t\t\t\tx3 = centerX - tempWidth / 2;\n\t\t\t\tx4 = x3 + tempWidth;\n\n\t\t\t\ty3 = neckY;\n\t\t\t}\n\n\t\t\t// save the path\n\t\t\tpath = [\n\t\t\t\t'M',\n\t\t\t\tx1, y1,\n\t\t\t\t'L',\n\t\t\t\tx2, y1,\n\t\t\t\tx4, y3\n\t\t\t];\n\t\t\tif (y5) {\n\t\t\t\tpath.push(x4, y5, x3, y5);\n\t\t\t}\n\t\t\tpath.push(x3, y3, 'Z');\n\n\t\t\t// prepare for using shared dr\n\t\t\tpoint.shapeType = 'path';\n\t\t\tpoint.shapeArgs = { d: path };\n\n\n\t\t\t// for tooltips and data labels\n\t\t\tpoint.percentage = fraction * 100;\n\t\t\tpoint.plotX = centerX;\n\t\t\tpoint.plotY = (y1 + (y5 || y3)) / 2;\n\n\t\t\t// Placement of tooltips and data labels\n\t\t\tpoint.tooltipPos = [\n\t\t\t\tcenterX,\n\t\t\t\tpoint.plotY\n\t\t\t];\n\n\t\t\t// Slice is a noop on funnel points\n\t\t\tpoint.slice = noop;\n\t\t\t\n\t\t\t// Mimicking pie data label placement logic\n\t\t\tpoint.half = half;\n\n\t\t\tcumulative += fraction;\n\t\t});\n\n\n\t\tseries.setTooltipPoints();\n\t},\n\t/**\n\t * Draw a single point (wedge)\n\t * @param {Object} point The point object\n\t * @param {Object} color The color of the point\n\t * @param {Number} brightness The brightness relative to the color\n\t */\n\tdrawPoints: function () {\n\t\tvar series = this,\n\t\t\toptions = series.options,\n\t\t\tchart = series.chart,\n\t\t\trenderer = chart.renderer;\n\n\t\teach(series.data, function (point) {\n\t\t\t\n\t\t\tvar graphic = point.graphic,\n\t\t\t\tshapeArgs = point.shapeArgs;\n\n\t\t\tif (!graphic) { // Create the shapes\n\t\t\t\tpoint.graphic = renderer.path(shapeArgs).\n\t\t\t\t\tattr({\n\t\t\t\t\t\tfill: point.color,\n\t\t\t\t\t\tstroke: options.borderColor,\n\t\t\t\t\t\t'stroke-width': options.borderWidth\n\t\t\t\t\t}).\n\t\t\t\t\tadd(series.group);\n\t\t\t\t\t\n\t\t\t} else { // Update the shapes\n\t\t\t\tgraphic.animate(shapeArgs);\n\t\t\t}\n\t\t});\n\t},\n\n\t/**\n\t * Funnel items don't have angles (#2289)\n\t */\n\tsortByAngle: noop,\n\t\n\t/**\n\t * Extend the pie data label method\n\t */\n\tdrawDataLabels: function () {\n\t\tvar data = this.data,\n\t\t\tlabelDistance = this.options.dataLabels.distance,\n\t\t\tleftSide,\n\t\t\tsign,\n\t\t\tpoint,\n\t\t\ti = data.length,\n\t\t\tx,\n\t\t\ty;\n\t\t\n\t\t// In the original pie label anticollision logic, the slots are distributed\n\t\t// from one labelDistance above to one labelDistance below the pie. In funnels\n\t\t// we don't want this.\n\t\tthis.center[2] -= 2 * labelDistance;\n\t\t\n\t\t// Set the label position array for each point.\n\t\twhile (i--) {\n\t\t\tpoint = data[i];\n\t\t\tleftSide = point.half;\n\t\t\tsign = leftSide ? 1 : -1;\n\t\t\ty = point.plotY;\n\t\t\tx = this.getX(y, leftSide);\n\t\t\t\t\n\t\t\t// set the anchor point for data labels\n\t\t\tpoint.labelPos = [\n\t\t\t\t0, // first break of connector\n\t\t\t\ty, // a/a\n\t\t\t\tx + (labelDistance - 5) * sign, // second break, right outside point shape\n\t\t\t\ty, // a/a\n\t\t\t\tx + labelDistance * sign, // landing point for connector\n\t\t\t\ty, // a/a\n\t\t\t\tleftSide ? 'right' : 'left', // alignment\n\t\t\t\t0 // center angle\n\t\t\t];\n\t\t}\n\t\t\n\t\tseriesTypes.pie.prototype.drawDataLabels.call(this);\n\t}\n\n});\n\n\n}(Highcharts));\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/highcharts/modules/heatmap.js",
    "content": "(function(b){var k=b.seriesTypes,l=b.each;k.heatmap=b.extendClass(k.map,{colorKey:\"z\",useMapGeometry:!1,pointArrayMap:[\"y\",\"z\"],translate:function(){var c=this,b=c.options,i=Number.MAX_VALUE,j=Number.MIN_VALUE;c.generatePoints();l(c.data,function(a){var e=a.x,f=a.y,d=a.z,g=(b.colsize||1)/2,h=(b.rowsize||1)/2;a.path=[\"M\",e-g,f-h,\"L\",e+g,f-h,\"L\",e+g,f+h,\"L\",e-g,f+h,\"Z\"];a.shapeType=\"path\";a.shapeArgs={d:c.translatePath(a.path)};typeof d===\"number\"&&(d>j?j=d:d<i&&(i=d))});c.translateColors(i,j)},getBox:function(){}})})(Highcharts);\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/highcharts/modules/heatmap.src.js",
    "content": "(function (Highcharts) {\n\tvar seriesTypes = Highcharts.seriesTypes,\n\t\teach = Highcharts.each;\n\t\n\tseriesTypes.heatmap = Highcharts.extendClass(seriesTypes.map, {\n\t\tcolorKey: 'z',\n\t\tuseMapGeometry: false,\n\t\tpointArrayMap: ['y', 'z'],\n\t\ttranslate: function () {\n\t\t\tvar series = this,\n\t\t\t\toptions = series.options,\n\t\t\t\tdataMin = Number.MAX_VALUE,\n\t\t\t\tdataMax = Number.MIN_VALUE;\n\n\t\t\tseries.generatePoints();\n\t\n\t\t\teach(series.data, function (point) {\n\t\t\t\tvar x = point.x,\n\t\t\t\t\ty = point.y,\n\t\t\t\t\tvalue = point.z,\n\t\t\t\t\txPad = (options.colsize || 1) / 2,\n\t\t\t\t\tyPad = (options.rowsize || 1) / 2;\n\n\t\t\t\tpoint.path = [\n\t\t\t\t\t'M', x - xPad, y - yPad,\n\t\t\t\t\t'L', x + xPad, y - yPad,\n\t\t\t\t\t'L', x + xPad, y + yPad,\n\t\t\t\t\t'L', x - xPad, y + yPad,\n\t\t\t\t\t'Z'\n\t\t\t\t];\n\t\t\t\t\n\t\t\t\tpoint.shapeType = 'path';\n\t\t\t\tpoint.shapeArgs = {\n\t\t\t\t\td: series.translatePath(point.path)\n\t\t\t\t};\n\t\t\t\t\n\t\t\t\tif (typeof value === 'number') {\n\t\t\t\t\tif (value > dataMax) {\n\t\t\t\t\t\tdataMax = value;\n\t\t\t\t\t} else if (value < dataMin) {\n\t\t\t\t\t\tdataMin = value;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t\t\n\t\t\tseries.translateColors(dataMin, dataMax);\n\t\t},\n\t\t\n\t\tgetBox: function () {}\n\t\t\t\n\t});\n\t\n}(Highcharts));\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/highcharts/modules/map.js",
    "content": "/*\n Map plugin v0.1 for Highcharts\n\n (c) 2011-2013 Torstein Hønsi\n\n License: www.highcharts.com/license\n*/\n(function(g){function x(a,b,c){for(var d=4,e=[];d--;)e[d]=Math.round(b.rgba[d]+(a.rgba[d]-b.rgba[d])*(1-c));return\"rgba(\"+e.join(\",\")+\")\"}var r=g.Axis,y=g.Chart,s=g.Point,z=g.Pointer,l=g.each,v=g.extend,p=g.merge,n=g.pick,A=g.numberFormat,B=g.getOptions(),k=g.seriesTypes,q=B.plotOptions,t=g.wrap,u=g.Color,w=function(){};B.mapNavigation={buttonOptions:{align:\"right\",verticalAlign:\"bottom\",x:0,width:18,height:18,style:{fontSize:\"15px\",fontWeight:\"bold\",textAlign:\"center\"}},buttons:{zoomIn:{onclick:function(){this.mapZoom(0.5)},\ntext:\"+\",y:-32},zoomOut:{onclick:function(){this.mapZoom(2)},text:\"-\",y:0}}};g.splitPath=function(a){var b,a=a.replace(/([A-Za-z])/g,\" $1 \"),a=a.replace(/^\\s*/,\"\").replace(/\\s*$/,\"\"),a=a.split(/[ ,]+/);for(b=0;b<a.length;b++)/[a-zA-Z]/.test(a[b])||(a[b]=parseFloat(a[b]));return a};g.maps={};t(r.prototype,\"getSeriesExtremes\",function(a){var b=this.isXAxis,c,d,e=[];l(this.series,function(a,b){if(a.useMapGeometry)e[b]=a.xData,a.xData=[]});a.call(this);c=n(this.dataMin,Number.MAX_VALUE);d=n(this.dataMax,\nNumber.MIN_VALUE);l(this.series,function(a,i){if(a.useMapGeometry)c=Math.min(c,a[b?\"minX\":\"minY\"]),d=Math.max(d,a[b?\"maxX\":\"maxY\"]),a.xData=e[i]});this.dataMin=c;this.dataMax=d});t(r.prototype,\"setAxisTranslation\",function(a){var b=this.chart,c=b.plotWidth/b.plotHeight,d=this.isXAxis,e=b.xAxis[0];a.call(this);if(b.options.chart.type===\"map\"&&!d&&e.transA!==void 0)this.transA=e.transA=Math.min(this.transA,e.transA),a=(e.max-e.min)/(this.max-this.min),e=a>c?this:e,c=(e.max-e.min)*e.transA,e.minPixelPadding=\n(e.len-c)/2});t(y.prototype,\"render\",function(a){var b=this,c=b.options.mapNavigation;a.call(b);b.renderMapNavigation();c.zoomOnDoubleClick&&g.addEvent(b.container,\"dblclick\",function(a){b.pointer.onContainerDblClick(a)});c.zoomOnMouseWheel&&g.addEvent(b.container,document.onmousewheel===void 0?\"DOMMouseScroll\":\"mousewheel\",function(a){b.pointer.onContainerMouseWheel(a)})});v(z.prototype,{onContainerDblClick:function(a){var b=this.chart,a=this.normalize(a);b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-\nb.plotTop)&&b.mapZoom(0.5,b.xAxis[0].toValue(a.chartX),b.yAxis[0].toValue(a.chartY))},onContainerMouseWheel:function(a){var b=this.chart,c,a=this.normalize(a);c=a.detail||-(a.wheelDelta/120);b.isInsidePlot(a.chartX-b.plotLeft,a.chartY-b.plotTop)&&b.mapZoom(c>0?2:0.5,b.xAxis[0].toValue(a.chartX),b.yAxis[0].toValue(a.chartY))}});t(z.prototype,\"init\",function(a,b,c){a.call(this,b,c);if(c.mapNavigation.enableTouchZoom)this.pinchX=this.pinchHor=this.pinchY=this.pinchVert=!0});v(y.prototype,{renderMapNavigation:function(){var a=\nthis,b=this.options.mapNavigation,c=b.buttons,d,e,f,i=function(){this.handler.call(a)};if(b.enableButtons)for(d in c)if(c.hasOwnProperty(d))f=p(b.buttonOptions,c[d]),e=a.renderer.button(f.text,0,0,i).attr({width:f.width,height:f.height}).css(f.style).add(),e.handler=f.onclick,e.align(v(f,{width:e.width,height:e.height}),null,\"spacingBox\")},fitToBox:function(a,b){l([[\"x\",\"width\"],[\"y\",\"height\"]],function(c){var d=c[0],c=c[1];a[d]+a[c]>b[d]+b[c]&&(a[c]>b[c]?(a[c]=b[c],a[d]=b[d]):a[d]=b[d]+b[c]-a[c]);\na[c]>b[c]&&(a[c]=b[c]);a[d]<b[d]&&(a[d]=b[d])});return a},mapZoom:function(a,b,c){if(!this.isMapZooming){var d=this,e=d.xAxis[0],f=e.max-e.min,i=n(b,e.min+f/2),b=f*a,f=d.yAxis[0],h=f.max-f.min,c=n(c,f.min+h/2);a*=h;i-=b/2;h=c-a/2;c=n(d.options.chart.animation,!0);b=d.fitToBox({x:i,y:h,width:b,height:a},{x:e.dataMin,y:f.dataMin,width:e.dataMax-e.dataMin,height:f.dataMax-f.dataMin});e.setExtremes(b.x,b.x+b.width,!1);f.setExtremes(b.y,b.y+b.height,!1);if(e=c?c.duration||500:0)d.isMapZooming=!0,setTimeout(function(){d.isMapZooming=\n!1},e);d.redraw()}}});q.map=p(q.scatter,{animation:!1,nullColor:\"#F8F8F8\",borderColor:\"silver\",borderWidth:1,marker:null,stickyTracking:!1,dataLabels:{verticalAlign:\"middle\"},turboThreshold:0,tooltip:{followPointer:!0,pointFormat:\"{point.name}: {point.y}<br/>\"},states:{normal:{animation:!0}}});r=g.extendClass(s,{applyOptions:function(a,b){var c=s.prototype.applyOptions.call(this,a,b);if(c.path&&typeof c.path===\"string\")c.path=c.options.path=g.splitPath(c.path);return c},onMouseOver:function(){clearTimeout(this.colorInterval);\ns.prototype.onMouseOver.call(this)},onMouseOut:function(){var a=this,b=+new Date,c=u(a.options.color),d=u(a.pointAttr.hover.fill),e=a.series.options.states.normal.animation,f=e&&(e.duration||500);if(f&&c.rgba.length===4&&d.rgba.length===4)delete a.pointAttr[\"\"].fill,clearTimeout(a.colorInterval),a.colorInterval=setInterval(function(){var e=(new Date-b)/f,h=a.graphic;e>1&&(e=1);h&&h.attr(\"fill\",x(d,c,e));e>=1&&clearTimeout(a.colorInterval)},13);s.prototype.onMouseOut.call(a)}});k.map=g.extendClass(k.scatter,\n{type:\"map\",pointAttrToOptions:{stroke:\"borderColor\",\"stroke-width\":\"borderWidth\",fill:\"color\"},colorKey:\"y\",pointClass:r,trackerGroups:[\"group\",\"markerGroup\",\"dataLabelsGroup\"],getSymbol:w,supportsDrilldown:!0,getExtremesFromAll:!0,useMapGeometry:!0,init:function(a){var b=this,c=a.options.legend.valueDecimals,d=[],e,f,i,h,j,o,m;o=a.options.legend.layout===\"horizontal\";g.Series.prototype.init.apply(this,arguments);j=b.options.colorRange;if(h=b.options.valueRanges)l(h,function(a){f=a.from;i=a.to;e=\n\"\";f===void 0?e=\"< \":i===void 0&&(e=\"> \");f!==void 0&&(e+=A(f,c));f!==void 0&&i!==void 0&&(e+=\" - \");i!==void 0&&(e+=A(i,c));d.push(g.extend({chart:b.chart,name:e,options:{},drawLegendSymbol:k.area.prototype.drawLegendSymbol,visible:!0,setState:function(){},setVisible:function(){}},a))}),b.legendItems=d;else if(j)f=j.from,i=j.to,h=j.fromLabel,j=j.toLabel,m=o?[0,0,1,0]:[0,1,0,0],o||(o=h,h=j,j=o),o={linearGradient:{x1:m[0],y1:m[1],x2:m[2],y2:m[3]},stops:[[0,f],[1,i]]},d=[{chart:b.chart,options:{},fromLabel:h,\ntoLabel:j,color:o,drawLegendSymbol:this.drawLegendSymbolGradient,visible:!0,setState:function(){},setVisible:function(){}}],b.legendItems=d},drawLegendSymbol:k.area.prototype.drawLegendSymbol,drawLegendSymbolGradient:function(a,b){var c=a.options.symbolPadding,d=n(a.options.padding,8),e,f,i=this.chart.renderer.fontMetrics(a.options.itemStyle.fontSize).h,h=a.options.layout===\"horizontal\",j;j=n(a.options.rectangleLength,200);h?(e=-(c/2),f=0):(e=-j+a.baseline-c/2,f=d+i);b.fromText=this.chart.renderer.text(b.fromLabel,\nf,e).attr({zIndex:2}).add(b.legendGroup);f=b.fromText.getBBox();b.legendSymbol=this.chart.renderer.rect(h?f.x+f.width+c:f.x-i-c,f.y,h?j:i,h?i:j,2).attr({zIndex:1}).add(b.legendGroup);j=b.legendSymbol.getBBox();b.toText=this.chart.renderer.text(b.toLabel,j.x+j.width+c,h?e:j.y+j.height-c).attr({zIndex:2}).add(b.legendGroup);e=b.toText.getBBox();h?(a.offsetWidth=f.width+j.width+e.width+c*2+d,a.itemY=i+d):(a.offsetWidth=Math.max(f.width,e.width)+c+j.width+d,a.itemY=j.height+d,a.itemX=c)},getBox:function(a){var b=\nNumber.MIN_VALUE,c=Number.MAX_VALUE,d=Number.MIN_VALUE,e=Number.MAX_VALUE;l(a||this.options.data,function(a){for(var i=a.path,h=i.length,j=!1,g=Number.MIN_VALUE,m=Number.MAX_VALUE,k=Number.MIN_VALUE,l=Number.MAX_VALUE;h--;)typeof i[h]===\"number\"&&!isNaN(i[h])&&(j?(g=Math.max(g,i[h]),m=Math.min(m,i[h])):(k=Math.max(k,i[h]),l=Math.min(l,i[h])),j=!j);a._maxX=g;a._minX=m;a._maxY=k;a._minY=l;b=Math.max(b,g);c=Math.min(c,m);d=Math.max(d,k);e=Math.min(e,l)});this.minY=e;this.maxY=d;this.minX=c;this.maxX=\nb},translatePath:function(a){var b=!1,c=this.xAxis,d=this.yAxis,e,a=[].concat(a);for(e=a.length;e--;)typeof a[e]===\"number\"&&(a[e]=b?Math.round(c.translate(a[e])):Math.round(d.len-d.translate(a[e])),b=!b);return a},setData:function(){g.Series.prototype.setData.apply(this,arguments);this.getBox()},translate:function(){var a=this,b=Number.MAX_VALUE,c=Number.MIN_VALUE;a.generatePoints();l(a.data,function(d){d.shapeType=\"path\";d.shapeArgs={d:a.translatePath(d.path)};if(typeof d.y===\"number\")if(d.y>c)c=\nd.y;else if(d.y<b)b=d.y});a.translateColors(b,c)},translateColors:function(a,b){var c=this.options,d=c.valueRanges,e=c.colorRange,f=this.colorKey,i,h;e&&(i=u(e.from),h=u(e.to));l(this.data,function(g){var k=g[f],m,l,n;if(d)for(n=d.length;n--;){if(m=d[n],i=m.from,h=m.to,(i===void 0||k>=i)&&(h===void 0||k<=h)){l=m.color;break}}else e&&k!==void 0&&(m=1-(b-k)/(b-a),l=k===null?c.nullColor:x(i,h,m));if(l)g.color=null,g.options.color=l})},drawGraph:w,drawDataLabels:w,drawPoints:function(){var a=this.xAxis,\nb=this.yAxis,c=this.colorKey;l(this.data,function(a){a.plotY=1;if(a[c]===null)a[c]=0,a.isNull=!0});k.column.prototype.drawPoints.apply(this);l(this.data,function(d){var e=d.dataLabels,f=a.toPixels(d._minX,!0),g=a.toPixels(d._maxX,!0),h=b.toPixels(d._minY,!0),j=b.toPixels(d._maxY,!0);d.plotX=Math.round(f+(g-f)*n(e&&e.anchorX,0.5));d.plotY=Math.round(h+(j-h)*n(e&&e.anchorY,0.5));d.isNull&&(d[c]=null)});g.Series.prototype.drawDataLabels.call(this)},animateDrilldown:function(a){var b=this.chart.plotBox,\nc=this.chart.drilldownLevels[this.chart.drilldownLevels.length-1],d=c.bBox,e=this.chart.options.drilldown.animation;if(!a)a=Math.min(d.width/b.width,d.height/b.height),c.shapeArgs={scaleX:a,scaleY:a,translateX:d.x,translateY:d.y},l(this.points,function(a){a.graphic.attr(c.shapeArgs).animate({scaleX:1,scaleY:1,translateX:0,translateY:0},e)}),delete this.animate},animateDrillupFrom:function(a){k.column.prototype.animateDrillupFrom.call(this,a)},animateDrillupTo:function(a){k.column.prototype.animateDrillupTo.call(this,\na)}});q.mapline=p(q.map,{lineWidth:1,backgroundColor:\"none\"});k.mapline=g.extendClass(k.map,{type:\"mapline\",pointAttrToOptions:{stroke:\"color\",\"stroke-width\":\"lineWidth\",fill:\"backgroundColor\"},drawLegendSymbol:k.line.prototype.drawLegendSymbol});q.mappoint=p(q.scatter,{dataLabels:{enabled:!0,format:\"{point.name}\",color:\"black\",style:{textShadow:\"0 0 5px white\"}}});k.mappoint=g.extendClass(k.scatter,{type:\"mappoint\"});g.Map=function(a,b){var c={endOnTick:!1,gridLineWidth:0,labels:{enabled:!1},lineWidth:0,\nminPadding:0,maxPadding:0,startOnTick:!1,tickWidth:0,title:null},d;d=a.series;a.series=null;a=p({chart:{type:\"map\",panning:\"xy\"},xAxis:c,yAxis:p(c,{reversed:!0})},a,{chart:{inverted:!1}});a.series=d;return new g.Chart(a,b)}})(Highcharts);\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/highcharts/modules/map.src.js",
    "content": "/**\n * @license Map plugin v0.1 for Highcharts\n *\n * (c) 2011-2013 Torstein Hønsi\n *\n * License: www.highcharts.com/license\n */\n\n/* \n * See www.highcharts.com/studies/world-map.htm for use case.\n *\n * To do:\n * - Optimize long variable names and alias adapter methods and Highcharts namespace variables\n * - Zoom and pan GUI\n */\n(function (Highcharts) {\n\tvar UNDEFINED,\n\t\tAxis = Highcharts.Axis,\n\t\tChart = Highcharts.Chart,\n\t\tPoint = Highcharts.Point,\n\t\tPointer = Highcharts.Pointer,\n\t\teach = Highcharts.each,\n\t\textend = Highcharts.extend,\n\t\tmerge = Highcharts.merge,\n\t\tpick = Highcharts.pick,\n\t\tnumberFormat = Highcharts.numberFormat,\n\t\tdefaultOptions = Highcharts.getOptions(),\n\t\tseriesTypes = Highcharts.seriesTypes,\n\t\tplotOptions = defaultOptions.plotOptions,\n\t\twrap = Highcharts.wrap,\n\t\tColor = Highcharts.Color,\n\t\tnoop = function () {};\n\n\t\n\n\t/*\n\t * Return an intermediate color between two colors, according to pos where 0\n\t * is the from color and 1 is the to color\n\t */\n\tfunction tweenColors(from, to, pos) {\n\t\tvar i = 4,\n\t\t\trgba = [];\n\n\t\twhile (i--) {\n\t\t\trgba[i] = Math.round(\n\t\t\t\tto.rgba[i] + (from.rgba[i] - to.rgba[i]) * (1 - pos)\n\t\t\t);\n\t\t}\n\t\treturn 'rgba(' + rgba.join(',') + ')';\n\t}\n\n\t// Set the default map navigation options\n\tdefaultOptions.mapNavigation = {\n\t\tbuttonOptions: {\n\t\t\talign: 'right',\n\t\t\tverticalAlign: 'bottom',\n\t\t\tx: 0,\n\t\t\twidth: 18,\n\t\t\theight: 18,\n\t\t\tstyle: {\n\t\t\t\tfontSize: '15px',\n\t\t\t\tfontWeight: 'bold',\n\t\t\t\ttextAlign: 'center'\n\t\t\t}\n\t\t},\n\t\tbuttons: {\n\t\t\tzoomIn: {\n\t\t\t\tonclick: function () {\n\t\t\t\t\tthis.mapZoom(0.5);\n\t\t\t\t},\n\t\t\t\ttext: '+',\n\t\t\t\ty: -32\n\t\t\t},\n\t\t\tzoomOut: {\n\t\t\t\tonclick: function () {\n\t\t\t\t\tthis.mapZoom(2);\n\t\t\t\t},\n\t\t\t\ttext: '-',\n\t\t\t\ty: 0\n\t\t\t}\n\t\t}\n\t\t// enableButtons: false,\n\t\t// enableTouchZoom: false,\n\t\t// zoomOnDoubleClick: false,\n\t\t// zoomOnMouseWheel: false\n\n\t};\n\t\n\t/**\n\t * Utility for reading SVG paths directly.\n\t */\n\tHighcharts.splitPath = function (path) {\n\t\tvar i;\n\n\t\t// Move letters apart\n\t\tpath = path.replace(/([A-Za-z])/g, ' $1 ');\n\t\t// Trim\n\t\tpath = path.replace(/^\\s*/, \"\").replace(/\\s*$/, \"\");\n\t\t\n\t\t// Split on spaces and commas\n\t\tpath = path.split(/[ ,]+/);\n\t\t\n\t\t// Parse numbers\n\t\tfor (i = 0; i < path.length; i++) {\n\t\t\tif (!/[a-zA-Z]/.test(path[i])) {\n\t\t\t\tpath[i] = parseFloat(path[i]);\n\t\t\t}\n\t\t}\n\t\treturn path;\n\t};\n\n\t// A placeholder for map definitions\n\tHighcharts.maps = {};\n\t\n\t/**\n\t * Override to use the extreme coordinates from the SVG shape, not the\n\t * data values\n\t */\n\twrap(Axis.prototype, 'getSeriesExtremes', function (proceed) {\n\t\tvar isXAxis = this.isXAxis,\n\t\t\tdataMin,\n\t\t\tdataMax,\n\t\t\txData = [];\n\n\t\t// Remove the xData array and cache it locally so that the proceed method doesn't use it\n\t\teach(this.series, function (series, i) {\n\t\t\tif (series.useMapGeometry) {\n\t\t\t\txData[i] = series.xData;\n\t\t\t\tseries.xData = [];\n\t\t\t}\n\t\t});\n\n\t\t// Call base to reach normal cartesian series (like mappoint)\n\t\tproceed.call(this);\n\n\t\t// Run extremes logic for map and mapline\n\t\tdataMin = pick(this.dataMin, Number.MAX_VALUE);\n\t\tdataMax = pick(this.dataMax, Number.MIN_VALUE);\n\t\teach(this.series, function (series, i) {\n\t\t\tif (series.useMapGeometry) {\n\t\t\t\tdataMin = Math.min(dataMin, series[isXAxis ? 'minX' : 'minY']);\n\t\t\t\tdataMax = Math.max(dataMax, series[isXAxis ? 'maxX' : 'maxY']);\n\t\t\t\tseries.xData = xData[i]; // Reset xData array\n\t\t\t}\n\t\t});\n\t\t\n\t\tthis.dataMin = dataMin;\n\t\tthis.dataMax = dataMax;\n\t});\n\t\n\t/**\n\t * Override axis translation to make sure the aspect ratio is always kept\n\t */\n\twrap(Axis.prototype, 'setAxisTranslation', function (proceed) {\n\t\tvar chart = this.chart,\n\t\t\tmapRatio,\n\t\t\tplotRatio = chart.plotWidth / chart.plotHeight,\n\t\t\tisXAxis = this.isXAxis,\n\t\t\tadjustedAxisLength,\n\t\t\txAxis = chart.xAxis[0],\n\t\t\tpadAxis;\n\t\t\n\t\t// Run the parent method\n\t\tproceed.call(this);\n\t\t\n\t\t// On Y axis, handle both\n\t\tif (chart.options.chart.type === 'map' && !isXAxis && xAxis.transA !== UNDEFINED) {\n\t\t\t\n\t\t\t// Use the same translation for both axes\n\t\t\tthis.transA = xAxis.transA = Math.min(this.transA, xAxis.transA);\n\t\t\t\n\t\t\tmapRatio = (xAxis.max - xAxis.min) / (this.max - this.min);\n\t\t\t\n\t\t\t// What axis to pad to put the map in the middle\n\t\t\tpadAxis = mapRatio > plotRatio ? this : xAxis;\n\t\t\t\n\t\t\t// Pad it\n\t\t\tadjustedAxisLength = (padAxis.max - padAxis.min) * padAxis.transA;\n\t\t\tpadAxis.minPixelPadding = (padAxis.len - adjustedAxisLength) / 2;\n\t\t}\n\t});\n\n\n\t//--- Start zooming and panning features\n\n\twrap(Chart.prototype, 'render', function (proceed) {\n\t\tvar chart = this,\n\t\t\tmapNavigation = chart.options.mapNavigation;\n\n\t\tproceed.call(chart);\n\n\t\t// Render the plus and minus buttons\n\t\tchart.renderMapNavigation();\n\n\t\t// Add the double click event\n\t\tif (mapNavigation.zoomOnDoubleClick) {\n\t\t\tHighcharts.addEvent(chart.container, 'dblclick', function (e) {\n\t\t\t\tchart.pointer.onContainerDblClick(e);\n\t\t\t});\n\t\t}\n\n\t\t// Add the mousewheel event\n\t\tif (mapNavigation.zoomOnMouseWheel) {\n\t\t\tHighcharts.addEvent(chart.container, document.onmousewheel === undefined ? 'DOMMouseScroll' : 'mousewheel', function (e) {\n\t\t\t\tchart.pointer.onContainerMouseWheel(e);\n\t\t\t});\n\t\t}\n\t});\n\n\t// Extend the Pointer\n\textend(Pointer.prototype, {\n\n\t\t/**\n\t\t * The event handler for the doubleclick event\n\t\t */\n\t\tonContainerDblClick: function (e) {\n\t\t\tvar chart = this.chart;\n\n\t\t\te = this.normalize(e);\n\n\t\t\tif (chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) {\n\t\t\t\tchart.mapZoom(\n\t\t\t\t\t0.5,\n\t\t\t\t\tchart.xAxis[0].toValue(e.chartX),\n\t\t\t\t\tchart.yAxis[0].toValue(e.chartY)\n\t\t\t\t);\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * The event handler for the mouse scroll event\n\t\t */\n\t\tonContainerMouseWheel: function (e) {\n\t\t\tvar chart = this.chart,\n\t\t\t\tdelta;\n\n\t\t\te = this.normalize(e);\n\n\t\t\t// Firefox uses e.detail, WebKit and IE uses wheelDelta\n\t\t\tdelta = e.detail || -(e.wheelDelta / 120);\n\t\t\tif (chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop)) {\n\t\t\t\tchart.mapZoom(\n\t\t\t\t\tdelta > 0 ? 2 : 0.5,\n\t\t\t\t\tchart.xAxis[0].toValue(e.chartX),\n\t\t\t\t\tchart.yAxis[0].toValue(e.chartY)\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t});\n\t// Implement the pinchType option\n\twrap(Pointer.prototype, 'init', function (proceed, chart, options) {\n\n\t\tproceed.call(this, chart, options);\n\n\t\t// Pinch status\n\t\tif (options.mapNavigation.enableTouchZoom) {\n\t\t\tthis.pinchX = this.pinchHor = \n\t\t\t\tthis.pinchY = this.pinchVert = true;\n\t\t}\n\t});\n\n\t// Add events to the Chart object itself\n\textend(Chart.prototype, {\n\t\trenderMapNavigation: function () {\n\t\t\tvar chart = this,\n\t\t\t\toptions = this.options.mapNavigation,\n\t\t\t\tbuttons = options.buttons,\n\t\t\t\tn,\n\t\t\t\tbutton,\n\t\t\t\tbuttonOptions,\n\t\t\t\touterHandler = function () { \n\t\t\t\t\tthis.handler.call(chart); \n\t\t\t\t};\n\n\t\t\tif (options.enableButtons) {\n\t\t\t\tfor (n in buttons) {\n\t\t\t\t\tif (buttons.hasOwnProperty(n)) {\n\t\t\t\t\t\tbuttonOptions = merge(options.buttonOptions, buttons[n]);\n\n\t\t\t\t\t\tbutton = chart.renderer.button(buttonOptions.text, 0, 0, outerHandler)\n\t\t\t\t\t\t\t.attr({\n\t\t\t\t\t\t\t\twidth: buttonOptions.width,\n\t\t\t\t\t\t\t\theight: buttonOptions.height\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t.css(buttonOptions.style)\n\t\t\t\t\t\t\t.add();\n\t\t\t\t\t\tbutton.handler = buttonOptions.onclick;\n\t\t\t\t\t\tbutton.align(extend(buttonOptions, { width: button.width, height: button.height }), null, 'spacingBox');\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Fit an inner box to an outer. If the inner box overflows left or right, align it to the sides of the\n\t\t * outer. If it overflows both sides, fit it within the outer. This is a pattern that occurs more places\n\t\t * in Highcharts, perhaps it should be elevated to a common utility function.\n\t\t */\n\t\tfitToBox: function (inner, outer) {\n\t\t\teach([['x', 'width'], ['y', 'height']], function (dim) {\n\t\t\t\tvar pos = dim[0],\n\t\t\t\t\tsize = dim[1];\n\t\t\t\tif (inner[pos] + inner[size] > outer[pos] + outer[size]) { // right overflow\n\t\t\t\t\tif (inner[size] > outer[size]) { // the general size is greater, fit fully to outer\n\t\t\t\t\t\tinner[size] = outer[size];\n\t\t\t\t\t\tinner[pos] = outer[pos];\n\t\t\t\t\t} else { // align right\n\t\t\t\t\t\tinner[pos] = outer[pos] + outer[size] - inner[size];\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tif (inner[size] > outer[size]) {\n\t\t\t\t\tinner[size] = outer[size];\n\t\t\t\t}\n\t\t\t\tif (inner[pos] < outer[pos]) {\n\t\t\t\t\tinner[pos] = outer[pos];\n\t\t\t\t}\n\t\t\t\t\n\t\t\t});\n\n\t\t\treturn inner;\n\t\t},\n\n\t\t/**\n\t\t * Zoom the map in or out by a certain amount. Less than 1 zooms in, greater than 1 zooms out.\n\t\t */\n\t\tmapZoom: function (howMuch, centerXArg, centerYArg) {\n\n\t\t\tif (this.isMapZooming) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tvar chart = this,\n\t\t\t\txAxis = chart.xAxis[0],\n\t\t\t\txRange = xAxis.max - xAxis.min,\n\t\t\t\tcenterX = pick(centerXArg, xAxis.min + xRange / 2),\n\t\t\t\tnewXRange = xRange * howMuch,\n\t\t\t\tyAxis = chart.yAxis[0],\n\t\t\t\tyRange = yAxis.max - yAxis.min,\n\t\t\t\tcenterY = pick(centerYArg, yAxis.min + yRange / 2),\n\t\t\t\tnewYRange = yRange * howMuch,\n\t\t\t\tnewXMin = centerX - newXRange / 2,\n\t\t\t\tnewYMin = centerY - newYRange / 2,\n\t\t\t\tanimation = pick(chart.options.chart.animation, true),\n\t\t\t\tdelay,\n\t\t\t\tnewExt = chart.fitToBox({\n\t\t\t\t\tx: newXMin,\n\t\t\t\t\ty: newYMin,\n\t\t\t\t\twidth: newXRange,\n\t\t\t\t\theight: newYRange\n\t\t\t\t}, {\n\t\t\t\t\tx: xAxis.dataMin,\n\t\t\t\t\ty: yAxis.dataMin,\n\t\t\t\t\twidth: xAxis.dataMax - xAxis.dataMin,\n\t\t\t\t\theight: yAxis.dataMax - yAxis.dataMin\n\t\t\t\t});\n\n\t\t\txAxis.setExtremes(newExt.x, newExt.x + newExt.width, false);\n\t\t\tyAxis.setExtremes(newExt.y, newExt.y + newExt.height, false);\n\n\t\t\t// Prevent zooming until this one is finished animating\n\t\t\tdelay = animation ? animation.duration || 500 : 0;\n\t\t\tif (delay) {\n\t\t\t\tchart.isMapZooming = true;\n\t\t\t\tsetTimeout(function () {\n\t\t\t\t\tchart.isMapZooming = false;\n\t\t\t\t}, delay);\n\t\t\t}\n\n\t\t\tchart.redraw();\n\t\t}\n\t});\n\t\n\t/**\n\t * Extend the default options with map options\n\t */\n\tplotOptions.map = merge(plotOptions.scatter, {\n\t\tanimation: false, // makes the complex shapes slow\n\t\tnullColor: '#F8F8F8',\n\t\tborderColor: 'silver',\n\t\tborderWidth: 1,\n\t\tmarker: null,\n\t\tstickyTracking: false,\n\t\tdataLabels: {\n\t\t\tverticalAlign: 'middle'\n\t\t},\n\t\tturboThreshold: 0,\n\t\ttooltip: {\n\t\t\tfollowPointer: true,\n\t\t\tpointFormat: '{point.name}: {point.y}<br/>'\n\t\t},\n\t\tstates: {\n\t\t\tnormal: {\n\t\t\t\tanimation: true\n\t\t\t}\n\t\t}\n\t});\n\n\tvar MapAreaPoint = Highcharts.extendClass(Point, {\n\t\t/**\n\t\t * Extend the Point object to split paths\n\t\t */\n\t\tapplyOptions: function (options, x) {\n\n\t\t\tvar point = Point.prototype.applyOptions.call(this, options, x);\n\n\t\t\tif (point.path && typeof point.path === 'string') {\n\t\t\t\tpoint.path = point.options.path = Highcharts.splitPath(point.path);\n\t\t\t}\n\n\t\t\treturn point;\n\t\t},\n\t\t/**\n\t\t * Stop the fade-out \n\t\t */\n\t\tonMouseOver: function () {\n\t\t\tclearTimeout(this.colorInterval);\n\t\t\tPoint.prototype.onMouseOver.call(this);\n\t\t},\n\t\t/**\n\t\t * Custom animation for tweening out the colors. Animation reduces blinking when hovering\n\t\t * over islands and coast lines. We run a custom implementation of animation becuase we\n\t\t * need to be able to run this independently from other animations like zoom redraw. Also,\n\t\t * adding color animation to the adapters would introduce almost the same amount of code.\n\t\t */\n\t\tonMouseOut: function () {\n\t\t\tvar point = this,\n\t\t\t\tstart = +new Date(),\n\t\t\t\tnormalColor = Color(point.options.color),\n\t\t\t\thoverColor = Color(point.pointAttr.hover.fill),\n\t\t\t\tanimation = point.series.options.states.normal.animation,\n\t\t\t\tduration = animation && (animation.duration || 500);\n\n\t\t\tif (duration && normalColor.rgba.length === 4 && hoverColor.rgba.length === 4) {\n\t\t\t\tdelete point.pointAttr[''].fill; // avoid resetting it in Point.setState\n\n\t\t\t\tclearTimeout(point.colorInterval);\n\t\t\t\tpoint.colorInterval = setInterval(function () {\n\t\t\t\t\tvar pos = (new Date() - start) / duration,\n\t\t\t\t\t\tgraphic = point.graphic;\n\t\t\t\t\tif (pos > 1) {\n\t\t\t\t\t\tpos = 1;\n\t\t\t\t\t}\n\t\t\t\t\tif (graphic) {\n\t\t\t\t\t\tgraphic.attr('fill', tweenColors(hoverColor, normalColor, pos));\n\t\t\t\t\t}\n\t\t\t\t\tif (pos >= 1) {\n\t\t\t\t\t\tclearTimeout(point.colorInterval);\n\t\t\t\t\t}\n\t\t\t\t}, 13);\n\t\t\t}\n\t\t\tPoint.prototype.onMouseOut.call(point);\n\t\t}\n\t});\n\n\t/**\n\t * Add the series type\n\t */\n\tseriesTypes.map = Highcharts.extendClass(seriesTypes.scatter, {\n\t\ttype: 'map',\n\t\tpointAttrToOptions: { // mapping between SVG attributes and the corresponding options\n\t\t\tstroke: 'borderColor',\n\t\t\t'stroke-width': 'borderWidth',\n\t\t\tfill: 'color'\n\t\t},\n\t\tcolorKey: 'y',\n\t\tpointClass: MapAreaPoint,\n\t\ttrackerGroups: ['group', 'markerGroup', 'dataLabelsGroup'],\n\t\tgetSymbol: noop,\n\t\tsupportsDrilldown: true,\n\t\tgetExtremesFromAll: true,\n\t\tuseMapGeometry: true, // get axis extremes from paths, not values\n\t\tinit: function (chart) {\n\t\t\tvar series = this,\n\t\t\t\tvalueDecimals = chart.options.legend.valueDecimals,\n\t\t\t\tlegendItems = [],\n\t\t\t\tname,\n\t\t\t\tfrom,\n\t\t\t\tto,\n\t\t\t\tfromLabel,\n\t\t\t\ttoLabel,\n\t\t\t\tcolorRange,\n\t\t\t\tvalueRanges,\n\t\t\t\tgradientColor,\n\t\t\t\tgrad,\n\t\t\t\ttmpLabel,\n\t\t\t\thorizontal = chart.options.legend.layout === 'horizontal';\n\n\t\t\t\n\t\t\tHighcharts.Series.prototype.init.apply(this, arguments);\n\t\t\tcolorRange = series.options.colorRange;\n\t\t\tvalueRanges = series.options.valueRanges;\n\n\t\t\tif (valueRanges) {\n\t\t\t\teach(valueRanges, function (range) {\n\t\t\t\t\tfrom = range.from;\n\t\t\t\t\tto = range.to;\n\t\t\t\t\t\n\t\t\t\t\t// Assemble the default name. This can be overridden by legend.options.labelFormatter\n\t\t\t\t\tname = '';\n\t\t\t\t\tif (from === UNDEFINED) {\n\t\t\t\t\t\tname = '< ';\n\t\t\t\t\t} else if (to === UNDEFINED) {\n\t\t\t\t\t\tname = '> ';\n\t\t\t\t\t}\n\t\t\t\t\tif (from !== UNDEFINED) {\n\t\t\t\t\t\tname += numberFormat(from, valueDecimals);\n\t\t\t\t\t}\n\t\t\t\t\tif (from !== UNDEFINED && to !== UNDEFINED) {\n\t\t\t\t\t\tname += ' - ';\n\t\t\t\t\t}\n\t\t\t\t\tif (to !== UNDEFINED) {\n\t\t\t\t\t\tname += numberFormat(to, valueDecimals);\n\t\t\t\t\t}\n\t\t\t\t\t\n\t\t\t\t\t// Add a mock object to the legend items\n\t\t\t\t\tlegendItems.push(Highcharts.extend({\n\t\t\t\t\t\tchart: series.chart,\n\t\t\t\t\t\tname: name,\n\t\t\t\t\t\toptions: {},\n\t\t\t\t\t\tdrawLegendSymbol: seriesTypes.area.prototype.drawLegendSymbol,\n\t\t\t\t\t\tvisible: true,\n\t\t\t\t\t\tsetState: function () {},\n\t\t\t\t\t\tsetVisible: function () {}\n\t\t\t\t\t}, range));\n\t\t\t\t});\n\t\t\t\tseries.legendItems = legendItems;\n\n\t\t\t} else if (colorRange) {\n\n\t\t\t\tfrom = colorRange.from;\n\t\t\t\tto = colorRange.to;\n\t\t\t\tfromLabel = colorRange.fromLabel;\n\t\t\t\ttoLabel = colorRange.toLabel;\n\n\t\t\t\t// Flips linearGradient variables and label text.\n\t\t\t\tgrad = horizontal ? [0, 0, 1, 0] : [0, 1, 0, 0]; \n\t\t\t\tif (!horizontal) {\n\t\t\t\t\ttmpLabel = fromLabel;\n\t\t\t\t\tfromLabel = toLabel;\n\t\t\t\t\ttoLabel = tmpLabel;\n\t\t\t\t} \n\n\t\t\t\t// Creates color gradient.\n\t\t\t\tgradientColor = {\n\t\t\t\t\tlinearGradient: { x1: grad[0], y1: grad[1], x2: grad[2], y2: grad[3] },\n\t\t\t\t\tstops: \n\t\t\t\t\t[\n\t\t\t\t\t\t[0, from],\n\t\t\t\t\t\t[1, to]\n\t\t\t\t\t]\n\t\t\t\t};\n\n\t\t\t\t// Add a mock object to the legend items.\n\t\t\t\tlegendItems = [{\n\t\t\t\t\tchart: series.chart,\n\t\t\t\t\toptions: {},\n\t\t\t\t\tfromLabel: fromLabel,\n\t\t\t\t\ttoLabel: toLabel,\n\t\t\t\t\tcolor: gradientColor,\n\t\t\t\t\tdrawLegendSymbol: this.drawLegendSymbolGradient,\n\t\t\t\t\tvisible: true,\n\t\t\t\t\tsetState: function () {},\n\t\t\t\t\tsetVisible: function () {}\n\t\t\t\t}];\n\n\t\t\t\tseries.legendItems = legendItems;\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * If neither valueRanges nor colorRanges are defined, use basic area symbol.\n\t\t */\n\t\tdrawLegendSymbol: seriesTypes.area.prototype.drawLegendSymbol,\n\n\t\t/**\n\t\t * Gets the series' symbol in the legend and extended legend with more information.\n\t\t * \n\t\t * @param {Object} legend The legend object\n\t\t * @param {Object} item The series (this) or point\n\t\t */\n\t\tdrawLegendSymbolGradient: function (legend, item) {\n\t\t\tvar spacing = legend.options.symbolPadding,\n\t\t\t\tpadding = pick(legend.options.padding, 8),\n\t\t\t\tpositionY,\n\t\t\t\tpositionX,\n\t\t\t\tgradientSize = this.chart.renderer.fontMetrics(legend.options.itemStyle.fontSize).h,\n\t\t\t\thorizontal = legend.options.layout === 'horizontal',\n\t\t\t\tbox1,\n\t\t\t\tbox2,\n\t\t\t\tbox3,\n\t\t\t\trectangleLength = pick(legend.options.rectangleLength, 200);\n\n\t\t\t// Set local variables based on option.\n\t\t\tif (horizontal) {\n\t\t\t\tpositionY = -(spacing / 2);\n\t\t\t\tpositionX = 0;\n\t\t\t} else {\n\t\t\t\tpositionY = -rectangleLength + legend.baseline - (spacing / 2);\n\t\t\t\tpositionX = padding + gradientSize;\n\t\t\t}\n\n\t\t\t// Creates the from text.\n\t\t\titem.fromText = this.chart.renderer.text(\n\t\t\t\t\titem.fromLabel,\t// Text.\n\t\t\t\t\tpositionX,\t\t// Lower left x.\n\t\t\t\t\tpositionY\t\t// Lower left y.\n\t\t\t\t).attr({\n\t\t\t\t\tzIndex: 2\n\t\t\t\t}).add(item.legendGroup);\n\t\t\tbox1 = item.fromText.getBBox();\n\n\t\t\t// Creates legend symbol.\n\t\t\t// Ternary changes variables based on option.\n\t\t\titem.legendSymbol = this.chart.renderer.rect(\n\t\t\t\thorizontal ? box1.x + box1.width + spacing : box1.x - gradientSize - spacing,\t\t// Upper left x.\n\t\t\t\tbox1.y,\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// Upper left y.\n\t\t\t\thorizontal ? rectangleLength : gradientSize,\t\t\t\t\t\t\t\t\t\t\t// Width.\n\t\t\t\thorizontal ? gradientSize : rectangleLength,\t\t\t\t\t\t\t\t\t\t// Height.\n\t\t\t\t2\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t// Corner radius.\n\t\t\t).attr({\n\t\t\t\tzIndex: 1\n\t\t\t}).add(item.legendGroup);\n\t\t\tbox2 = item.legendSymbol.getBBox();\n\n\t\t\t// Creates the to text.\n\t\t\t// Vertical coordinate changed based on option.\n\t\t\titem.toText = this.chart.renderer.text(\n\t\t\t\t\titem.toLabel,\n\t\t\t\t\tbox2.x + box2.width + spacing,\n\t\t\t\t\thorizontal ? positionY : box2.y + box2.height - spacing\n\t\t\t\t).attr({\n\t\t\t\t\tzIndex: 2\n\t\t\t\t}).add(item.legendGroup);\n\t\t\tbox3 = item.toText.getBBox();\n\n\t\t\t// Changes legend box settings based on option.\n\t\t\tif (horizontal) {\n\t\t\t\tlegend.offsetWidth = box1.width + box2.width + box3.width + (spacing * 2) + padding;\n\t\t\t\tlegend.itemY = gradientSize + padding;\n\t\t\t} else {\n\t\t\t\tlegend.offsetWidth = Math.max(box1.width, box3.width) + (spacing) + box2.width + padding;\n\t\t\t\tlegend.itemY = box2.height + padding;\n\t\t\t\tlegend.itemX = spacing;\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Get the bounding box of all paths in the map combined.\n\t\t */\n\t\tgetBox: function (paths) {\n\t\t\tvar maxX = Number.MIN_VALUE, \n\t\t\t\tminX =  Number.MAX_VALUE, \n\t\t\t\tmaxY = Number.MIN_VALUE, \n\t\t\t\tminY =  Number.MAX_VALUE;\n\t\t\t\n\t\t\t\n\t\t\t// Find the bounding box\n\t\t\teach(paths || this.options.data, function (point) {\n\t\t\t\tvar path = point.path,\n\t\t\t\t\ti = path.length,\n\t\t\t\t\teven = false, // while loop reads from the end\n\t\t\t\t\tpointMaxX = Number.MIN_VALUE, \n\t\t\t\t\tpointMinX =  Number.MAX_VALUE, \n\t\t\t\t\tpointMaxY = Number.MIN_VALUE, \n\t\t\t\t\tpointMinY =  Number.MAX_VALUE;\n\t\t\t\t\t\n\t\t\t\twhile (i--) {\n\t\t\t\t\tif (typeof path[i] === 'number' && !isNaN(path[i])) {\n\t\t\t\t\t\tif (even) { // even = x\n\t\t\t\t\t\t\tpointMaxX = Math.max(pointMaxX, path[i]);\n\t\t\t\t\t\t\tpointMinX = Math.min(pointMinX, path[i]);\n\t\t\t\t\t\t} else { // odd = Y\n\t\t\t\t\t\t\tpointMaxY = Math.max(pointMaxY, path[i]);\n\t\t\t\t\t\t\tpointMinY = Math.min(pointMinY, path[i]);\n\t\t\t\t\t\t}\n\t\t\t\t\t\teven = !even;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\t// Cache point bounding box for use to position data labels\n\t\t\t\tpoint._maxX = pointMaxX;\n\t\t\t\tpoint._minX = pointMinX;\n\t\t\t\tpoint._maxY = pointMaxY;\n\t\t\t\tpoint._minY = pointMinY;\n\n\t\t\t\tmaxX = Math.max(maxX, pointMaxX);\n\t\t\t\tminX = Math.min(minX, pointMinX);\n\t\t\t\tmaxY = Math.max(maxY, pointMaxY);\n\t\t\t\tminY = Math.min(minY, pointMinY);\n\t\t\t});\n\t\t\tthis.minY = minY;\n\t\t\tthis.maxY = maxY;\n\t\t\tthis.minX = minX;\n\t\t\tthis.maxX = maxX;\n\t\t\t\n\t\t},\n\t\t\n\t\t\n\t\t\n\t\t/**\n\t\t * Translate the path so that it automatically fits into the plot area box\n\t\t * @param {Object} path\n\t\t */\n\t\ttranslatePath: function (path) {\n\t\t\t\n\t\t\tvar series = this,\n\t\t\t\teven = false, // while loop reads from the end\n\t\t\t\txAxis = series.xAxis,\n\t\t\t\tyAxis = series.yAxis,\n\t\t\t\ti;\n\t\t\t\t\n\t\t\t// Preserve the original\n\t\t\tpath = [].concat(path);\n\t\t\t\t\n\t\t\t// Do the translation\n\t\t\ti = path.length;\n\t\t\twhile (i--) {\n\t\t\t\tif (typeof path[i] === 'number') {\n\t\t\t\t\tif (even) { // even = x\n\t\t\t\t\t\tpath[i] = Math.round(xAxis.translate(path[i]));\n\t\t\t\t\t} else { // odd = Y\n\t\t\t\t\t\tpath[i] = Math.round(yAxis.len - yAxis.translate(path[i]));\n\t\t\t\t\t}\n\t\t\t\t\teven = !even;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn path;\n\t\t},\n\t\t\n\t\tsetData: function () {\n\t\t\tHighcharts.Series.prototype.setData.apply(this, arguments);\n\t\t\tthis.getBox();\n\t\t},\n\t\t\n\t\t/**\n\t\t * Add the path option for data points. Find the max value for color calculation.\n\t\t */\n\t\ttranslate: function () {\n\t\t\tvar series = this,\n\t\t\t\tdataMin = Number.MAX_VALUE,\n\t\t\t\tdataMax = Number.MIN_VALUE;\n\t\n\t\t\tseries.generatePoints();\n\t\n\t\t\teach(series.data, function (point) {\n\t\t\t\t\n\t\t\t\tpoint.shapeType = 'path';\n\t\t\t\tpoint.shapeArgs = {\n\t\t\t\t\td: series.translatePath(point.path)\n\t\t\t\t};\n\t\t\t\t\n\t\t\t\t// TODO: do point colors in drawPoints instead of point.init\n\t\t\t\tif (typeof point.y === 'number') {\n\t\t\t\t\tif (point.y > dataMax) {\n\t\t\t\t\t\tdataMax = point.y;\n\t\t\t\t\t} else if (point.y < dataMin) {\n\t\t\t\t\t\tdataMin = point.y;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t});\n\t\t\t\n\t\t\tseries.translateColors(dataMin, dataMax);\n\t\t},\n\t\t\n\t\t/**\n\t\t * In choropleth maps, the color is a result of the value, so this needs translation too\n\t\t */\n\t\ttranslateColors: function (dataMin, dataMax) {\n\t\t\t\n\t\t\tvar seriesOptions = this.options,\n\t\t\t\tvalueRanges = seriesOptions.valueRanges,\n\t\t\t\tcolorRange = seriesOptions.colorRange,\n\t\t\t\tcolorKey = this.colorKey,\n\t\t\t\tfrom,\n\t\t\t\tto;\n\n\t\t\tif (colorRange) {\n\t\t\t\tfrom = Color(colorRange.from);\n\t\t\t\tto = Color(colorRange.to);\n\t\t\t}\n\t\t\t\n\t\t\teach(this.data, function (point) {\n\t\t\t\tvar value = point[colorKey],\n\t\t\t\t\trange,\n\t\t\t\t\tcolor,\n\t\t\t\t\ti,\n\t\t\t\t\tpos;\n\n\t\t\t\tif (valueRanges) {\n\t\t\t\t\ti = valueRanges.length;\n\t\t\t\t\twhile (i--) {\n\t\t\t\t\t\trange = valueRanges[i];\n\t\t\t\t\t\tfrom = range.from;\n\t\t\t\t\t\tto = range.to;\n\t\t\t\t\t\tif ((from === UNDEFINED || value >= from) && (to === UNDEFINED || value <= to)) {\n\t\t\t\t\t\t\tcolor = range.color;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\n\t\t\t\t\t}\n\t\t\t\t} else if (colorRange && value !== undefined) {\n\n\t\t\t\t\tpos = 1 - ((dataMax - value) / (dataMax - dataMin));\n\t\t\t\t\tcolor = value === null ? seriesOptions.nullColor : tweenColors(from, to, pos);\n\t\t\t\t}\n\n\t\t\t\tif (color) {\n\t\t\t\t\tpoint.color = null; // reset from previous drilldowns, use of the same data options\n\t\t\t\t\tpoint.options.color = color;\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\t\t\n\t\tdrawGraph: noop,\n\t\t\n\t\t/**\n\t\t * We need the points' bounding boxes in order to draw the data labels, so \n\t\t * we skip it now and call if from drawPoints instead.\n\t\t */\n\t\tdrawDataLabels: noop,\n\t\t\n\t\t/** \n\t\t * Use the drawPoints method of column, that is able to handle simple shapeArgs.\n\t\t * Extend it by assigning the tooltip position.\n\t\t */\n\t\tdrawPoints: function () {\n\t\t\tvar series = this,\n\t\t\t\txAxis = series.xAxis,\n\t\t\t\tyAxis = series.yAxis,\n\t\t\t\tcolorKey = series.colorKey;\n\t\t\t\n\t\t\t// Make points pass test in drawing\n\t\t\teach(series.data, function (point) {\n\t\t\t\tpoint.plotY = 1; // pass null test in column.drawPoints\n\t\t\t\tif (point[colorKey] === null) {\n\t\t\t\t\tpoint[colorKey] = 0;\n\t\t\t\t\tpoint.isNull = true;\n\t\t\t\t}\n\t\t\t});\n\t\t\t\n\t\t\t// Draw them\n\t\t\tseriesTypes.column.prototype.drawPoints.apply(series);\n\t\t\t\n\t\t\teach(series.data, function (point) {\n\n\t\t\t\tvar dataLabels = point.dataLabels,\n\t\t\t\t\tminX = xAxis.toPixels(point._minX, true),\n\t\t\t\t\tmaxX = xAxis.toPixels(point._maxX, true),\n\t\t\t\t\tminY = yAxis.toPixels(point._minY, true),\n\t\t\t\t\tmaxY = yAxis.toPixels(point._maxY, true);\n\n\t\t\t\tpoint.plotX = Math.round(minX + (maxX - minX) * pick(dataLabels && dataLabels.anchorX, 0.5));\n\t\t\t\tpoint.plotY = Math.round(minY + (maxY - minY) * pick(dataLabels && dataLabels.anchorY, 0.5)); \n\t\t\t\t\n\t\t\t\t\n\t\t\t\t// Reset escaped null points\n\t\t\t\tif (point.isNull) {\n\t\t\t\t\tpoint[colorKey] = null;\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// Now draw the data labels\n\t\t\tHighcharts.Series.prototype.drawDataLabels.call(series);\n\t\t\t\n\t\t},\n\n\t\t/**\n\t\t * Animate in the new series from the clicked point in the old series.\n\t\t * Depends on the drilldown.js module\n\t\t */\n\t\tanimateDrilldown: function (init) {\n\t\t\tvar toBox = this.chart.plotBox,\n\t\t\t\tlevel = this.chart.drilldownLevels[this.chart.drilldownLevels.length - 1],\n\t\t\t\tfromBox = level.bBox,\n\t\t\t\tanimationOptions = this.chart.options.drilldown.animation,\n\t\t\t\tscale;\n\t\t\t\t\n\t\t\tif (!init) {\n\n\t\t\t\tscale = Math.min(fromBox.width / toBox.width, fromBox.height / toBox.height);\n\t\t\t\tlevel.shapeArgs = {\n\t\t\t\t\tscaleX: scale,\n\t\t\t\t\tscaleY: scale,\n\t\t\t\t\ttranslateX: fromBox.x,\n\t\t\t\t\ttranslateY: fromBox.y\n\t\t\t\t};\n\t\t\t\t\n\t\t\t\t// TODO: Animate this.group instead\n\t\t\t\teach(this.points, function (point) {\n\n\t\t\t\t\tpoint.graphic\n\t\t\t\t\t\t.attr(level.shapeArgs)\n\t\t\t\t\t\t.animate({\n\t\t\t\t\t\t\tscaleX: 1,\n\t\t\t\t\t\t\tscaleY: 1,\n\t\t\t\t\t\t\ttranslateX: 0,\n\t\t\t\t\t\t\ttranslateY: 0\n\t\t\t\t\t\t}, animationOptions);\n\n\t\t\t\t});\n\n\t\t\t\tdelete this.animate;\n\t\t\t}\n\t\t\t\n\t\t},\n\n\t\t/**\n\t\t * When drilling up, pull out the individual point graphics from the lower series\n\t\t * and animate them into the origin point in the upper series.\n\t\t */\n\t\tanimateDrillupFrom: function (level) {\n\t\t\tseriesTypes.column.prototype.animateDrillupFrom.call(this, level);\n\t\t},\n\n\n\t\t/**\n\t\t * When drilling up, keep the upper series invisible until the lower series has\n\t\t * moved into place\n\t\t */\n\t\tanimateDrillupTo: function (init) {\n\t\t\tseriesTypes.column.prototype.animateDrillupTo.call(this, init);\n\t\t}\n\t});\n\n\n\t// The mapline series type\n\tplotOptions.mapline = merge(plotOptions.map, {\n\t\tlineWidth: 1,\n\t\tbackgroundColor: 'none'\n\t});\n\tseriesTypes.mapline = Highcharts.extendClass(seriesTypes.map, {\n\t\ttype: 'mapline',\n\t\tpointAttrToOptions: { // mapping between SVG attributes and the corresponding options\n\t\t\tstroke: 'color',\n\t\t\t'stroke-width': 'lineWidth',\n\t\t\tfill: 'backgroundColor'\n\t\t},\n\t\tdrawLegendSymbol: seriesTypes.line.prototype.drawLegendSymbol\n\t});\n\n\t// The mappoint series type\n\tplotOptions.mappoint = merge(plotOptions.scatter, {\n\t\tdataLabels: {\n\t\t\tenabled: true,\n\t\t\tformat: '{point.name}',\n\t\t\tcolor: 'black',\n\t\t\tstyle: {\n\t\t\t\ttextShadow: '0 0 5px white'\n\t\t\t}\n\t\t}\n\t});\n\tseriesTypes.mappoint = Highcharts.extendClass(seriesTypes.scatter, {\n\t\ttype: 'mappoint'\n\t});\n\t\n\n\t\n\t/**\n\t * A wrapper for Chart with all the default values for a Map\n\t */\n\tHighcharts.Map = function (options, callback) {\n\t\t\n\t\tvar hiddenAxis = {\n\t\t\t\tendOnTick: false,\n\t\t\t\tgridLineWidth: 0,\n\t\t\t\tlabels: {\n\t\t\t\t\tenabled: false\n\t\t\t\t},\n\t\t\t\tlineWidth: 0,\n\t\t\t\tminPadding: 0,\n\t\t\t\tmaxPadding: 0,\n\t\t\t\tstartOnTick: false,\n\t\t\t\ttickWidth: 0,\n\t\t\t\ttitle: null\n\t\t\t},\n\t\t\tseriesOptions;\n\t\t\n\t\t// Don't merge the data\n\t\tseriesOptions = options.series;\n\t\toptions.series = null;\n\t\t\n\t\toptions = merge({\n\t\t\tchart: {\n\t\t\t\ttype: 'map',\n\t\t\t\tpanning: 'xy'\n\t\t\t},\n\t\t\txAxis: hiddenAxis,\n\t\t\tyAxis: merge(hiddenAxis, { reversed: true })\t\n\t\t},\n\t\toptions, // user's options\n\t\n\t\t{ // forced options\n\t\t\tchart: {\n\t\t\t\tinverted: false\n\t\t\t}\n\t\t});\n\t\n\t\toptions.series = seriesOptions;\n\t\n\t\n\t\treturn new Highcharts.Chart(options, callback);\n\t};\n}(Highcharts));\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/highcharts/modules/no-data-to-display.js",
    "content": "/*\n Highcharts JS v3.0.6 (2013-10-04)\n Plugin for displaying a message when there is no data visible in chart.\n\n (c) 2010-2013 Highsoft AS\n Author: Øystein Moseng\n\n License: www.highcharts.com/license\n*/\n(function(c){function f(){return!!this.points.length}function g(){this.hasData()?this.hideNoData():this.showNoData()}var d=c.seriesTypes,e=c.Chart.prototype,h=c.getOptions(),i=c.extend;i(h.lang,{noData:\"No data to display\"});h.noData={position:{x:0,y:0,align:\"center\",verticalAlign:\"middle\"},attr:{},style:{fontWeight:\"bold\",fontSize:\"12px\",color:\"#60606a\"}};d.pie.prototype.hasData=f;if(d.gauge)d.gauge.prototype.hasData=f;if(d.waterfall)d.waterfall.prototype.hasData=f;c.Series.prototype.hasData=function(){return this.dataMax!==\nvoid 0&&this.dataMin!==void 0};e.showNoData=function(a){var b=this.options,a=a||b.lang.noData,b=b.noData;if(!this.noDataLabel)this.noDataLabel=this.renderer.label(a,0,0,null,null,null,null,null,\"no-data\").attr(b.attr).css(b.style).add(),this.noDataLabel.align(i(this.noDataLabel.getBBox(),b.position),!1,\"plotBox\")};e.hideNoData=function(){if(this.noDataLabel)this.noDataLabel=this.noDataLabel.destroy()};e.hasData=function(){for(var a=this.series,b=a.length;b--;)if(a[b].hasData()&&!a[b].options.isInternal)return!0;\nreturn!1};e.callbacks.push(function(a){c.addEvent(a,\"load\",g);c.addEvent(a,\"redraw\",g)})})(Highcharts);\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/highcharts/modules/no-data-to-display.src.js",
    "content": "/**\n * @license Highcharts JS v3.0.6 (2013-10-04)\n * Plugin for displaying a message when there is no data visible in chart.\n *\n * (c) 2010-2013 Highsoft AS\n * Author: Øystein Moseng\n *\n * License: www.highcharts.com/license\n */\n\n(function (H) { // docs\n\t\n\tvar seriesTypes = H.seriesTypes,\n\t\tchartPrototype = H.Chart.prototype,\n\t\tdefaultOptions = H.getOptions(),\n\t\textend = H.extend;\n\n\t// Add language option\n\textend(defaultOptions.lang, {\n\t\tnoData: 'No data to display'\n\t});\n\t\n\t// Add default display options for message\n\tdefaultOptions.noData = {\n\t\tposition: {\n\t\t\tx: 0,\n\t\t\ty: 0,\t\t\t\n\t\t\talign: 'center',\n\t\t\tverticalAlign: 'middle'\n\t\t},\n\t\tattr: {\t\t\t\t\t\t\n\t\t},\n\t\tstyle: {\t\n\t\t\tfontWeight: 'bold',\t\t\n\t\t\tfontSize: '12px',\n\t\t\tcolor: '#60606a'\t\t\n\t\t}\n\t};\n\n\t/**\n\t * Define hasData functions for series. These return true if there are data points on this series within the plot area\n\t */\t\n\tfunction hasDataPie() {\n\t\treturn !!this.points.length; /* != 0 */\n\t}\n\n\tseriesTypes.pie.prototype.hasData = hasDataPie;\n\n\tif (seriesTypes.gauge) {\n\t\tseriesTypes.gauge.prototype.hasData = hasDataPie;\n\t}\n\n\tif (seriesTypes.waterfall) {\n\t\tseriesTypes.waterfall.prototype.hasData = hasDataPie;\n\t}\n\n\tH.Series.prototype.hasData = function () {\n\t\treturn this.dataMax !== undefined && this.dataMin !== undefined;\n\t};\n\t\n\t/**\n\t * Display a no-data message.\n\t *\n\t * @param {String} str An optional message to show in place of the default one \n\t */\n\tchartPrototype.showNoData = function (str) {\n\t\tvar chart = this,\n\t\t\toptions = chart.options,\n\t\t\ttext = str || options.lang.noData,\n\t\t\tnoDataOptions = options.noData;\n\n\t\tif (!chart.noDataLabel) {\n\t\t\tchart.noDataLabel = chart.renderer.label(text, 0, 0, null, null, null, null, null, 'no-data')\n\t\t\t\t.attr(noDataOptions.attr)\n\t\t\t\t.css(noDataOptions.style)\n\t\t\t\t.add();\n\t\t\tchart.noDataLabel.align(extend(chart.noDataLabel.getBBox(), noDataOptions.position), false, 'plotBox');\n\t\t}\n\t};\n\n\t/**\n\t * Hide no-data message\t\n\t */\t\n\tchartPrototype.hideNoData = function () {\n\t\tvar chart = this;\n\t\tif (chart.noDataLabel) {\n\t\t\tchart.noDataLabel = chart.noDataLabel.destroy();\n\t\t}\n\t};\n\n\t/**\n\t * Returns true if there are data points within the plot area now\n\t */\t\n\tchartPrototype.hasData = function () {\n\t\tvar chart = this,\n\t\t\tseries = chart.series,\n\t\t\ti = series.length;\n\n\t\twhile (i--) {\n\t\t\tif (series[i].hasData() && !series[i].options.isInternal) { \n\t\t\t\treturn true;\n\t\t\t}\t\n\t\t}\n\n\t\treturn false;\n\t};\n\n\t/**\n\t * Show no-data message if there is no data in sight. Otherwise, hide it.\n\t */\n\tfunction handleNoData() {\n\t\tvar chart = this;\n\t\tif (chart.hasData()) {\n\t\t\tchart.hideNoData();\n\t\t} else {\n\t\t\tchart.showNoData();\n\t\t}\n\t}\n\n\t/**\n\t * Add event listener to handle automatic display of no-data message\n\t */\n\tchartPrototype.callbacks.push(function (chart) {\n\t\tH.addEvent(chart, 'load', handleNoData);\n\t\tH.addEvent(chart, 'redraw', handleNoData);\n\t});\n\n}(Highcharts));\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/highcharts/themes/dark-blue.js",
    "content": "/**\n * Dark blue theme for Highcharts JS\n * @author Torstein Hønsi\n */\n\nHighcharts.theme = {\n\tcolors: [\"#DDDF0D\", \"#55BF3B\", \"#DF5353\", \"#7798BF\", \"#aaeeee\", \"#ff0066\", \"#eeaaee\",\n\t\t\"#55BF3B\", \"#DF5353\", \"#7798BF\", \"#aaeeee\"],\n\tchart: {\n\t\tbackgroundColor: {\n\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 1, y2: 1 },\n\t\t\tstops: [\n\t\t\t\t[0, 'rgb(48, 48, 96)'],\n\t\t\t\t[1, 'rgb(0, 0, 0)']\n\t\t\t]\n\t\t},\n\t\tborderColor: '#000000',\n\t\tborderWidth: 2,\n\t\tclassName: 'dark-container',\n\t\tplotBackgroundColor: 'rgba(255, 255, 255, .1)',\n\t\tplotBorderColor: '#CCCCCC',\n\t\tplotBorderWidth: 1\n\t},\n\ttitle: {\n\t\tstyle: {\n\t\t\tcolor: '#C0C0C0',\n\t\t\tfont: 'bold 16px \"Trebuchet MS\", Verdana, sans-serif'\n\t\t}\n\t},\n\tsubtitle: {\n\t\tstyle: {\n\t\t\tcolor: '#666666',\n\t\t\tfont: 'bold 12px \"Trebuchet MS\", Verdana, sans-serif'\n\t\t}\n\t},\n\txAxis: {\n\t\tgridLineColor: '#333333',\n\t\tgridLineWidth: 1,\n\t\tlabels: {\n\t\t\tstyle: {\n\t\t\t\tcolor: '#A0A0A0'\n\t\t\t}\n\t\t},\n\t\tlineColor: '#A0A0A0',\n\t\ttickColor: '#A0A0A0',\n\t\ttitle: {\n\t\t\tstyle: {\n\t\t\t\tcolor: '#CCC',\n\t\t\t\tfontWeight: 'bold',\n\t\t\t\tfontSize: '12px',\n\t\t\t\tfontFamily: 'Trebuchet MS, Verdana, sans-serif'\n\n\t\t\t}\n\t\t}\n\t},\n\tyAxis: {\n\t\tgridLineColor: '#333333',\n\t\tlabels: {\n\t\t\tstyle: {\n\t\t\t\tcolor: '#A0A0A0'\n\t\t\t}\n\t\t},\n\t\tlineColor: '#A0A0A0',\n\t\tminorTickInterval: null,\n\t\ttickColor: '#A0A0A0',\n\t\ttickWidth: 1,\n\t\ttitle: {\n\t\t\tstyle: {\n\t\t\t\tcolor: '#CCC',\n\t\t\t\tfontWeight: 'bold',\n\t\t\t\tfontSize: '12px',\n\t\t\t\tfontFamily: 'Trebuchet MS, Verdana, sans-serif'\n\t\t\t}\n\t\t}\n\t},\n\ttooltip: {\n\t\tbackgroundColor: 'rgba(0, 0, 0, 0.75)',\n\t\tstyle: {\n\t\t\tcolor: '#F0F0F0'\n\t\t}\n\t},\n\ttoolbar: {\n\t\titemStyle: {\n\t\t\tcolor: 'silver'\n\t\t}\n\t},\n\tplotOptions: {\n\t\tline: {\n\t\t\tdataLabels: {\n\t\t\t\tcolor: '#CCC'\n\t\t\t},\n\t\t\tmarker: {\n\t\t\t\tlineColor: '#333'\n\t\t\t}\n\t\t},\n\t\tspline: {\n\t\t\tmarker: {\n\t\t\t\tlineColor: '#333'\n\t\t\t}\n\t\t},\n\t\tscatter: {\n\t\t\tmarker: {\n\t\t\t\tlineColor: '#333'\n\t\t\t}\n\t\t},\n\t\tcandlestick: {\n\t\t\tlineColor: 'white'\n\t\t}\n\t},\n\tlegend: {\n\t\titemStyle: {\n\t\t\tfont: '9pt Trebuchet MS, Verdana, sans-serif',\n\t\t\tcolor: '#A0A0A0'\n\t\t},\n\t\titemHoverStyle: {\n\t\t\tcolor: '#FFF'\n\t\t},\n\t\titemHiddenStyle: {\n\t\t\tcolor: '#444'\n\t\t}\n\t},\n\tcredits: {\n\t\tstyle: {\n\t\t\tcolor: '#666'\n\t\t}\n\t},\n\tlabels: {\n\t\tstyle: {\n\t\t\tcolor: '#CCC'\n\t\t}\n\t},\n\n\tnavigation: {\n\t\tbuttonOptions: {\n\t\t\tsymbolStroke: '#DDDDDD',\n\t\t\thoverSymbolStroke: '#FFFFFF',\n\t\t\ttheme: {\n\t\t\t\tfill: {\n\t\t\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\n\t\t\t\t\tstops: [\n\t\t\t\t\t\t[0.4, '#606060'],\n\t\t\t\t\t\t[0.6, '#333333']\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\tstroke: '#000000'\n\t\t\t}\n\t\t}\n\t},\n\n\t// scroll charts\n\trangeSelector: {\n\t\tbuttonTheme: {\n\t\t\tfill: {\n\t\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\n\t\t\t\tstops: [\n\t\t\t\t\t[0.4, '#888'],\n\t\t\t\t\t[0.6, '#555']\n\t\t\t\t]\n\t\t\t},\n\t\t\tstroke: '#000000',\n\t\t\tstyle: {\n\t\t\t\tcolor: '#CCC',\n\t\t\t\tfontWeight: 'bold'\n\t\t\t},\n\t\t\tstates: {\n\t\t\t\thover: {\n\t\t\t\t\tfill: {\n\t\t\t\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\n\t\t\t\t\t\tstops: [\n\t\t\t\t\t\t\t[0.4, '#BBB'],\n\t\t\t\t\t\t\t[0.6, '#888']\n\t\t\t\t\t\t]\n\t\t\t\t\t},\n\t\t\t\t\tstroke: '#000000',\n\t\t\t\t\tstyle: {\n\t\t\t\t\t\tcolor: 'white'\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tselect: {\n\t\t\t\t\tfill: {\n\t\t\t\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\n\t\t\t\t\t\tstops: [\n\t\t\t\t\t\t\t[0.1, '#000'],\n\t\t\t\t\t\t\t[0.3, '#333']\n\t\t\t\t\t\t]\n\t\t\t\t\t},\n\t\t\t\t\tstroke: '#000000',\n\t\t\t\t\tstyle: {\n\t\t\t\t\t\tcolor: 'yellow'\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tinputStyle: {\n\t\t\tbackgroundColor: '#333',\n\t\t\tcolor: 'silver'\n\t\t},\n\t\tlabelStyle: {\n\t\t\tcolor: 'silver'\n\t\t}\n\t},\n\n\tnavigator: {\n\t\thandles: {\n\t\t\tbackgroundColor: '#666',\n\t\t\tborderColor: '#AAA'\n\t\t},\n\t\toutlineColor: '#CCC',\n\t\tmaskFill: 'rgba(16, 16, 16, 0.5)',\n\t\tseries: {\n\t\t\tcolor: '#7798BF',\n\t\t\tlineColor: '#A6C7ED'\n\t\t}\n\t},\n\n\tscrollbar: {\n\t\tbarBackgroundColor: {\n\t\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\n\t\t\t\tstops: [\n\t\t\t\t\t[0.4, '#888'],\n\t\t\t\t\t[0.6, '#555']\n\t\t\t\t]\n\t\t\t},\n\t\tbarBorderColor: '#CCC',\n\t\tbuttonArrowColor: '#CCC',\n\t\tbuttonBackgroundColor: {\n\t\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\n\t\t\t\tstops: [\n\t\t\t\t\t[0.4, '#888'],\n\t\t\t\t\t[0.6, '#555']\n\t\t\t\t]\n\t\t\t},\n\t\tbuttonBorderColor: '#CCC',\n\t\trifleColor: '#FFF',\n\t\ttrackBackgroundColor: {\n\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\n\t\t\tstops: [\n\t\t\t\t[0, '#000'],\n\t\t\t\t[1, '#333']\n\t\t\t]\n\t\t},\n\t\ttrackBorderColor: '#666'\n\t},\n\n\t// special colors for some of the\n\tlegendBackgroundColor: 'rgba(0, 0, 0, 0.5)',\n\tlegendBackgroundColorSolid: 'rgb(35, 35, 70)',\n\tdataLabelsColor: '#444',\n\ttextColor: '#C0C0C0',\n\tmaskColor: 'rgba(255,255,255,0.3)'\n};\n\n// Apply the theme\nvar highchartsOptions = Highcharts.setOptions(Highcharts.theme);\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/highcharts/themes/dark-green.js",
    "content": "/**\n * Dark blue theme for Highcharts JS\n * @author Torstein Hønsi\n */\n\nHighcharts.theme = {\n\tcolors: [\"#DDDF0D\", \"#55BF3B\", \"#DF5353\", \"#7798BF\", \"#aaeeee\", \"#ff0066\", \"#eeaaee\",\n\t\t\"#55BF3B\", \"#DF5353\", \"#7798BF\", \"#aaeeee\"],\n\tchart: {\n\t\tbackgroundColor: {\n\t\t\tlinearGradient: [0, 0, 250, 500],\n\t\t\tstops: [\n\t\t\t\t[0, 'rgb(48, 96, 48)'],\n\t\t\t\t[1, 'rgb(0, 0, 0)']\n\t\t\t]\n\t\t},\n\t\tborderColor: '#000000',\n\t\tborderWidth: 2,\n\t\tclassName: 'dark-container',\n\t\tplotBackgroundColor: 'rgba(255, 255, 255, .1)',\n\t\tplotBorderColor: '#CCCCCC',\n\t\tplotBorderWidth: 1\n\t},\n\ttitle: {\n\t\tstyle: {\n\t\t\tcolor: '#C0C0C0',\n\t\t\tfont: 'bold 16px \"Trebuchet MS\", Verdana, sans-serif'\n\t\t}\n\t},\n\tsubtitle: {\n\t\tstyle: {\n\t\t\tcolor: '#666666',\n\t\t\tfont: 'bold 12px \"Trebuchet MS\", Verdana, sans-serif'\n\t\t}\n\t},\n\txAxis: {\n\t\tgridLineColor: '#333333',\n\t\tgridLineWidth: 1,\n\t\tlabels: {\n\t\t\tstyle: {\n\t\t\t\tcolor: '#A0A0A0'\n\t\t\t}\n\t\t},\n\t\tlineColor: '#A0A0A0',\n\t\ttickColor: '#A0A0A0',\n\t\ttitle: {\n\t\t\tstyle: {\n\t\t\t\tcolor: '#CCC',\n\t\t\t\tfontWeight: 'bold',\n\t\t\t\tfontSize: '12px',\n\t\t\t\tfontFamily: 'Trebuchet MS, Verdana, sans-serif'\n\n\t\t\t}\n\t\t}\n\t},\n\tyAxis: {\n\t\tgridLineColor: '#333333',\n\t\tlabels: {\n\t\t\tstyle: {\n\t\t\t\tcolor: '#A0A0A0'\n\t\t\t}\n\t\t},\n\t\tlineColor: '#A0A0A0',\n\t\tminorTickInterval: null,\n\t\ttickColor: '#A0A0A0',\n\t\ttickWidth: 1,\n\t\ttitle: {\n\t\t\tstyle: {\n\t\t\t\tcolor: '#CCC',\n\t\t\t\tfontWeight: 'bold',\n\t\t\t\tfontSize: '12px',\n\t\t\t\tfontFamily: 'Trebuchet MS, Verdana, sans-serif'\n\t\t\t}\n\t\t}\n\t},\n\ttooltip: {\n\t\tbackgroundColor: 'rgba(0, 0, 0, 0.75)',\n\t\tstyle: {\n\t\t\tcolor: '#F0F0F0'\n\t\t}\n\t},\n\ttoolbar: {\n\t\titemStyle: {\n\t\t\tcolor: 'silver'\n\t\t}\n\t},\n\tplotOptions: {\n\t\tline: {\n\t\t\tdataLabels: {\n\t\t\t\tcolor: '#CCC'\n\t\t\t},\n\t\t\tmarker: {\n\t\t\t\tlineColor: '#333'\n\t\t\t}\n\t\t},\n\t\tspline: {\n\t\t\tmarker: {\n\t\t\t\tlineColor: '#333'\n\t\t\t}\n\t\t},\n\t\tscatter: {\n\t\t\tmarker: {\n\t\t\t\tlineColor: '#333'\n\t\t\t}\n\t\t},\n\t\tcandlestick: {\n\t\t\tlineColor: 'white'\n\t\t}\n\t},\n\tlegend: {\n\t\titemStyle: {\n\t\t\tfont: '9pt Trebuchet MS, Verdana, sans-serif',\n\t\t\tcolor: '#A0A0A0'\n\t\t},\n\t\titemHoverStyle: {\n\t\t\tcolor: '#FFF'\n\t\t},\n\t\titemHiddenStyle: {\n\t\t\tcolor: '#444'\n\t\t}\n\t},\n\tcredits: {\n\t\tstyle: {\n\t\t\tcolor: '#666'\n\t\t}\n\t},\n\tlabels: {\n\t\tstyle: {\n\t\t\tcolor: '#CCC'\n\t\t}\n\t},\n\n\n\tnavigation: {\n\t\tbuttonOptions: {\n\t\t\tsymbolStroke: '#DDDDDD',\n\t\t\thoverSymbolStroke: '#FFFFFF',\n\t\t\ttheme: {\n\t\t\t\tfill: {\n\t\t\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\n\t\t\t\t\tstops: [\n\t\t\t\t\t\t[0.4, '#606060'],\n\t\t\t\t\t\t[0.6, '#333333']\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\tstroke: '#000000'\n\t\t\t}\n\t\t}\n\t},\n\n\t// scroll charts\n\trangeSelector: {\n\t\tbuttonTheme: {\n\t\t\tfill: {\n\t\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\n\t\t\t\tstops: [\n\t\t\t\t\t[0.4, '#888'],\n\t\t\t\t\t[0.6, '#555']\n\t\t\t\t]\n\t\t\t},\n\t\t\tstroke: '#000000',\n\t\t\tstyle: {\n\t\t\t\tcolor: '#CCC',\n\t\t\t\tfontWeight: 'bold'\n\t\t\t},\n\t\t\tstates: {\n\t\t\t\thover: {\n\t\t\t\t\tfill: {\n\t\t\t\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\n\t\t\t\t\t\tstops: [\n\t\t\t\t\t\t\t[0.4, '#BBB'],\n\t\t\t\t\t\t\t[0.6, '#888']\n\t\t\t\t\t\t]\n\t\t\t\t\t},\n\t\t\t\t\tstroke: '#000000',\n\t\t\t\t\tstyle: {\n\t\t\t\t\t\tcolor: 'white'\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tselect: {\n\t\t\t\t\tfill: {\n\t\t\t\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\n\t\t\t\t\t\tstops: [\n\t\t\t\t\t\t\t[0.1, '#000'],\n\t\t\t\t\t\t\t[0.3, '#333']\n\t\t\t\t\t\t]\n\t\t\t\t\t},\n\t\t\t\t\tstroke: '#000000',\n\t\t\t\t\tstyle: {\n\t\t\t\t\t\tcolor: 'yellow'\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tinputStyle: {\n\t\t\tbackgroundColor: '#333',\n\t\t\tcolor: 'silver'\n\t\t},\n\t\tlabelStyle: {\n\t\t\tcolor: 'silver'\n\t\t}\n\t},\n\n\tnavigator: {\n\t\thandles: {\n\t\t\tbackgroundColor: '#666',\n\t\t\tborderColor: '#AAA'\n\t\t},\n\t\toutlineColor: '#CCC',\n\t\tmaskFill: 'rgba(16, 16, 16, 0.5)',\n\t\tseries: {\n\t\t\tcolor: '#7798BF',\n\t\t\tlineColor: '#A6C7ED'\n\t\t}\n\t},\n\n\tscrollbar: {\n\t\tbarBackgroundColor: {\n\t\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\n\t\t\t\tstops: [\n\t\t\t\t\t[0.4, '#888'],\n\t\t\t\t\t[0.6, '#555']\n\t\t\t\t]\n\t\t\t},\n\t\tbarBorderColor: '#CCC',\n\t\tbuttonArrowColor: '#CCC',\n\t\tbuttonBackgroundColor: {\n\t\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\n\t\t\t\tstops: [\n\t\t\t\t\t[0.4, '#888'],\n\t\t\t\t\t[0.6, '#555']\n\t\t\t\t]\n\t\t\t},\n\t\tbuttonBorderColor: '#CCC',\n\t\trifleColor: '#FFF',\n\t\ttrackBackgroundColor: {\n\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\n\t\t\tstops: [\n\t\t\t\t[0, '#000'],\n\t\t\t\t[1, '#333']\n\t\t\t]\n\t\t},\n\t\ttrackBorderColor: '#666'\n\t},\n\n\t// special colors for some of the\n\tlegendBackgroundColor: 'rgba(0, 0, 0, 0.5)',\n\tlegendBackgroundColorSolid: 'rgb(35, 35, 70)',\n\tdataLabelsColor: '#444',\n\ttextColor: '#C0C0C0',\n\tmaskColor: 'rgba(255,255,255,0.3)'\n};\n\n// Apply the theme\nvar highchartsOptions = Highcharts.setOptions(Highcharts.theme);\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/highcharts/themes/gray.js",
    "content": "/**\n * Gray theme for Highcharts JS\n * @author Torstein Hønsi\n */\n\nHighcharts.theme = {\n\tcolors: [\"#DDDF0D\", \"#7798BF\", \"#55BF3B\", \"#DF5353\", \"#aaeeee\", \"#ff0066\", \"#eeaaee\",\n\t\t\"#55BF3B\", \"#DF5353\", \"#7798BF\", \"#aaeeee\"],\n\tchart: {\n\t\tbackgroundColor: {\n\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\n\t\t\tstops: [\n\t\t\t\t[0, 'rgb(96, 96, 96)'],\n\t\t\t\t[1, 'rgb(16, 16, 16)']\n\t\t\t]\n\t\t},\n\t\tborderWidth: 0,\n\t\tborderRadius: 15,\n\t\tplotBackgroundColor: null,\n\t\tplotShadow: false,\n\t\tplotBorderWidth: 0\n\t},\n\ttitle: {\n\t\tstyle: {\n\t\t\tcolor: '#FFF',\n\t\t\tfont: '16px Lucida Grande, Lucida Sans Unicode, Verdana, Arial, Helvetica, sans-serif'\n\t\t}\n\t},\n\tsubtitle: {\n\t\tstyle: {\n\t\t\tcolor: '#DDD',\n\t\t\tfont: '12px Lucida Grande, Lucida Sans Unicode, Verdana, Arial, Helvetica, sans-serif'\n\t\t}\n\t},\n\txAxis: {\n\t\tgridLineWidth: 0,\n\t\tlineColor: '#999',\n\t\ttickColor: '#999',\n\t\tlabels: {\n\t\t\tstyle: {\n\t\t\t\tcolor: '#999',\n\t\t\t\tfontWeight: 'bold'\n\t\t\t}\n\t\t},\n\t\ttitle: {\n\t\t\tstyle: {\n\t\t\t\tcolor: '#AAA',\n\t\t\t\tfont: 'bold 12px Lucida Grande, Lucida Sans Unicode, Verdana, Arial, Helvetica, sans-serif'\n\t\t\t}\n\t\t}\n\t},\n\tyAxis: {\n\t\talternateGridColor: null,\n\t\tminorTickInterval: null,\n\t\tgridLineColor: 'rgba(255, 255, 255, .1)',\n\t\tminorGridLineColor: 'rgba(255,255,255,0.07)',\n\t\tlineWidth: 0,\n\t\ttickWidth: 0,\n\t\tlabels: {\n\t\t\tstyle: {\n\t\t\t\tcolor: '#999',\n\t\t\t\tfontWeight: 'bold'\n\t\t\t}\n\t\t},\n\t\ttitle: {\n\t\t\tstyle: {\n\t\t\t\tcolor: '#AAA',\n\t\t\t\tfont: 'bold 12px Lucida Grande, Lucida Sans Unicode, Verdana, Arial, Helvetica, sans-serif'\n\t\t\t}\n\t\t}\n\t},\n\tlegend: {\n\t\titemStyle: {\n\t\t\tcolor: '#CCC'\n\t\t},\n\t\titemHoverStyle: {\n\t\t\tcolor: '#FFF'\n\t\t},\n\t\titemHiddenStyle: {\n\t\t\tcolor: '#333'\n\t\t}\n\t},\n\tlabels: {\n\t\tstyle: {\n\t\t\tcolor: '#CCC'\n\t\t}\n\t},\n\ttooltip: {\n\t\tbackgroundColor: {\n\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\n\t\t\tstops: [\n\t\t\t\t[0, 'rgba(96, 96, 96, .8)'],\n\t\t\t\t[1, 'rgba(16, 16, 16, .8)']\n\t\t\t]\n\t\t},\n\t\tborderWidth: 0,\n\t\tstyle: {\n\t\t\tcolor: '#FFF'\n\t\t}\n\t},\n\n\n\tplotOptions: {\n\t\tseries: {\n\t\t\tshadow: true\n\t\t},\n\t\tline: {\n\t\t\tdataLabels: {\n\t\t\t\tcolor: '#CCC'\n\t\t\t},\n\t\t\tmarker: {\n\t\t\t\tlineColor: '#333'\n\t\t\t}\n\t\t},\n\t\tspline: {\n\t\t\tmarker: {\n\t\t\t\tlineColor: '#333'\n\t\t\t}\n\t\t},\n\t\tscatter: {\n\t\t\tmarker: {\n\t\t\t\tlineColor: '#333'\n\t\t\t}\n\t\t},\n\t\tcandlestick: {\n\t\t\tlineColor: 'white'\n\t\t}\n\t},\n\n\ttoolbar: {\n\t\titemStyle: {\n\t\t\tcolor: '#CCC'\n\t\t}\n\t},\n\n\tnavigation: {\n\t\tbuttonOptions: {\n\t\t\tsymbolStroke: '#DDDDDD',\n\t\t\thoverSymbolStroke: '#FFFFFF',\n\t\t\ttheme: {\n\t\t\t\tfill: {\n\t\t\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\n\t\t\t\t\tstops: [\n\t\t\t\t\t\t[0.4, '#606060'],\n\t\t\t\t\t\t[0.6, '#333333']\n\t\t\t\t\t]\n\t\t\t\t},\n\t\t\t\tstroke: '#000000'\n\t\t\t}\n\t\t}\n\t},\n\n\t// scroll charts\n\trangeSelector: {\n\t\tbuttonTheme: {\n\t\t\tfill: {\n\t\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\n\t\t\t\tstops: [\n\t\t\t\t\t[0.4, '#888'],\n\t\t\t\t\t[0.6, '#555']\n\t\t\t\t]\n\t\t\t},\n\t\t\tstroke: '#000000',\n\t\t\tstyle: {\n\t\t\t\tcolor: '#CCC',\n\t\t\t\tfontWeight: 'bold'\n\t\t\t},\n\t\t\tstates: {\n\t\t\t\thover: {\n\t\t\t\t\tfill: {\n\t\t\t\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\n\t\t\t\t\t\tstops: [\n\t\t\t\t\t\t\t[0.4, '#BBB'],\n\t\t\t\t\t\t\t[0.6, '#888']\n\t\t\t\t\t\t]\n\t\t\t\t\t},\n\t\t\t\t\tstroke: '#000000',\n\t\t\t\t\tstyle: {\n\t\t\t\t\t\tcolor: 'white'\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tselect: {\n\t\t\t\t\tfill: {\n\t\t\t\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\n\t\t\t\t\t\tstops: [\n\t\t\t\t\t\t\t[0.1, '#000'],\n\t\t\t\t\t\t\t[0.3, '#333']\n\t\t\t\t\t\t]\n\t\t\t\t\t},\n\t\t\t\t\tstroke: '#000000',\n\t\t\t\t\tstyle: {\n\t\t\t\t\t\tcolor: 'yellow'\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\tinputStyle: {\n\t\t\tbackgroundColor: '#333',\n\t\t\tcolor: 'silver'\n\t\t},\n\t\tlabelStyle: {\n\t\t\tcolor: 'silver'\n\t\t}\n\t},\n\n\tnavigator: {\n\t\thandles: {\n\t\t\tbackgroundColor: '#666',\n\t\t\tborderColor: '#AAA'\n\t\t},\n\t\toutlineColor: '#CCC',\n\t\tmaskFill: 'rgba(16, 16, 16, 0.5)',\n\t\tseries: {\n\t\t\tcolor: '#7798BF',\n\t\t\tlineColor: '#A6C7ED'\n\t\t}\n\t},\n\n\tscrollbar: {\n\t\tbarBackgroundColor: {\n\t\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\n\t\t\t\tstops: [\n\t\t\t\t\t[0.4, '#888'],\n\t\t\t\t\t[0.6, '#555']\n\t\t\t\t]\n\t\t\t},\n\t\tbarBorderColor: '#CCC',\n\t\tbuttonArrowColor: '#CCC',\n\t\tbuttonBackgroundColor: {\n\t\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\n\t\t\t\tstops: [\n\t\t\t\t\t[0.4, '#888'],\n\t\t\t\t\t[0.6, '#555']\n\t\t\t\t]\n\t\t\t},\n\t\tbuttonBorderColor: '#CCC',\n\t\trifleColor: '#FFF',\n\t\ttrackBackgroundColor: {\n\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 },\n\t\t\tstops: [\n\t\t\t\t[0, '#000'],\n\t\t\t\t[1, '#333']\n\t\t\t]\n\t\t},\n\t\ttrackBorderColor: '#666'\n\t},\n\n\t// special colors for some of the demo examples\n\tlegendBackgroundColor: 'rgba(48, 48, 48, 0.8)',\n\tlegendBackgroundColorSolid: 'rgb(70, 70, 70)',\n\tdataLabelsColor: '#444',\n\ttextColor: '#E0E0E0',\n\tmaskColor: 'rgba(255,255,255,0.3)'\n};\n\n// Apply the theme\nvar highchartsOptions = Highcharts.setOptions(Highcharts.theme);\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/highcharts/themes/grid.js",
    "content": "/**\n * Grid theme for Highcharts JS\n * @author Torstein Hønsi\n */\n\nHighcharts.theme = {\n\tcolors: ['#058DC7', '#50B432', '#ED561B', '#DDDF00', '#24CBE5', '#64E572', '#FF9655', '#FFF263', '#6AF9C4'],\n\tchart: {\n\t\tbackgroundColor: {\n\t\t\tlinearGradient: { x1: 0, y1: 0, x2: 1, y2: 1 },\n\t\t\tstops: [\n\t\t\t\t[0, 'rgb(255, 255, 255)'],\n\t\t\t\t[1, 'rgb(240, 240, 255)']\n\t\t\t]\n\t\t},\n\t\tborderWidth: 2,\n\t\tplotBackgroundColor: 'rgba(255, 255, 255, .9)',\n\t\tplotShadow: true,\n\t\tplotBorderWidth: 1\n\t},\n\ttitle: {\n\t\tstyle: {\n\t\t\tcolor: '#000',\n\t\t\tfont: 'bold 16px \"Trebuchet MS\", Verdana, sans-serif'\n\t\t}\n\t},\n\tsubtitle: {\n\t\tstyle: {\n\t\t\tcolor: '#666666',\n\t\t\tfont: 'bold 12px \"Trebuchet MS\", Verdana, sans-serif'\n\t\t}\n\t},\n\txAxis: {\n\t\tgridLineWidth: 1,\n\t\tlineColor: '#000',\n\t\ttickColor: '#000',\n\t\tlabels: {\n\t\t\tstyle: {\n\t\t\t\tcolor: '#000',\n\t\t\t\tfont: '11px Trebuchet MS, Verdana, sans-serif'\n\t\t\t}\n\t\t},\n\t\ttitle: {\n\t\t\tstyle: {\n\t\t\t\tcolor: '#333',\n\t\t\t\tfontWeight: 'bold',\n\t\t\t\tfontSize: '12px',\n\t\t\t\tfontFamily: 'Trebuchet MS, Verdana, sans-serif'\n\n\t\t\t}\n\t\t}\n\t},\n\tyAxis: {\n\t\tminorTickInterval: 'auto',\n\t\tlineColor: '#000',\n\t\tlineWidth: 1,\n\t\ttickWidth: 1,\n\t\ttickColor: '#000',\n\t\tlabels: {\n\t\t\tstyle: {\n\t\t\t\tcolor: '#000',\n\t\t\t\tfont: '11px Trebuchet MS, Verdana, sans-serif'\n\t\t\t}\n\t\t},\n\t\ttitle: {\n\t\t\tstyle: {\n\t\t\t\tcolor: '#333',\n\t\t\t\tfontWeight: 'bold',\n\t\t\t\tfontSize: '12px',\n\t\t\t\tfontFamily: 'Trebuchet MS, Verdana, sans-serif'\n\t\t\t}\n\t\t}\n\t},\n\tlegend: {\n\t\titemStyle: {\n\t\t\tfont: '9pt Trebuchet MS, Verdana, sans-serif',\n\t\t\tcolor: 'black'\n\n\t\t},\n\t\titemHoverStyle: {\n\t\t\tcolor: '#039'\n\t\t},\n\t\titemHiddenStyle: {\n\t\t\tcolor: 'gray'\n\t\t}\n\t},\n\tlabels: {\n\t\tstyle: {\n\t\t\tcolor: '#99b'\n\t\t}\n\t},\n\n\tnavigation: {\n\t\tbuttonOptions: {\n\t\t\ttheme: {\n\t\t\t\tstroke: '#CCCCCC'\n\t\t\t}\n\t\t}\n\t}\n};\n\n// Apply the theme\nvar highchartsOptions = Highcharts.setOptions(Highcharts.theme);\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/highcharts/themes/skies.js",
    "content": "/**\n * Skies theme for Highcharts JS\n * @author Torstein Hønsi\n */\n\nHighcharts.theme = {\n\tcolors: [\"#514F78\", \"#42A07B\", \"#9B5E4A\", \"#72727F\", \"#1F949A\", \"#82914E\", \"#86777F\", \"#42A07B\"],\n\tchart: {\n\t\tclassName: 'skies',\n\t\tborderWidth: 0,\n\t\tplotShadow: true,\n\t\tplotBackgroundImage: 'http://www.highcharts.com/demo/gfx/skies.jpg',\n\t\tplotBackgroundColor: {\n\t\t\tlinearGradient: [0, 0, 250, 500],\n\t\t\tstops: [\n\t\t\t\t[0, 'rgba(255, 255, 255, 1)'],\n\t\t\t\t[1, 'rgba(255, 255, 255, 0)']\n\t\t\t]\n\t\t},\n\t\tplotBorderWidth: 1\n\t},\n\ttitle: {\n\t\tstyle: {\n\t\t\tcolor: '#3E576F',\n\t\t\tfont: '16px Lucida Grande, Lucida Sans Unicode, Verdana, Arial, Helvetica, sans-serif'\n\t\t}\n\t},\n\tsubtitle: {\n\t\tstyle: {\n\t\t\tcolor: '#6D869F',\n\t\t\tfont: '12px Lucida Grande, Lucida Sans Unicode, Verdana, Arial, Helvetica, sans-serif'\n\t\t}\n\t},\n\txAxis: {\n\t\tgridLineWidth: 0,\n\t\tlineColor: '#C0D0E0',\n\t\ttickColor: '#C0D0E0',\n\t\tlabels: {\n\t\t\tstyle: {\n\t\t\t\tcolor: '#666',\n\t\t\t\tfontWeight: 'bold'\n\t\t\t}\n\t\t},\n\t\ttitle: {\n\t\t\tstyle: {\n\t\t\t\tcolor: '#666',\n\t\t\t\tfont: '12px Lucida Grande, Lucida Sans Unicode, Verdana, Arial, Helvetica, sans-serif'\n\t\t\t}\n\t\t}\n\t},\n\tyAxis: {\n\t\talternateGridColor: 'rgba(255, 255, 255, .5)',\n\t\tlineColor: '#C0D0E0',\n\t\ttickColor: '#C0D0E0',\n\t\ttickWidth: 1,\n\t\tlabels: {\n\t\t\tstyle: {\n\t\t\t\tcolor: '#666',\n\t\t\t\tfontWeight: 'bold'\n\t\t\t}\n\t\t},\n\t\ttitle: {\n\t\t\tstyle: {\n\t\t\t\tcolor: '#666',\n\t\t\t\tfont: '12px Lucida Grande, Lucida Sans Unicode, Verdana, Arial, Helvetica, sans-serif'\n\t\t\t}\n\t\t}\n\t},\n\tlegend: {\n\t\titemStyle: {\n\t\t\tfont: '9pt Trebuchet MS, Verdana, sans-serif',\n\t\t\tcolor: '#3E576F'\n\t\t},\n\t\titemHoverStyle: {\n\t\t\tcolor: 'black'\n\t\t},\n\t\titemHiddenStyle: {\n\t\t\tcolor: 'silver'\n\t\t}\n\t},\n\tlabels: {\n\t\tstyle: {\n\t\t\tcolor: '#3E576F'\n\t\t}\n\t}\n};\n\n// Apply the theme\nvar highchartsOptions = Highcharts.setOptions(Highcharts.theme);\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/jquery-1.10.2.js",
    "content": "/*!\n * jQuery JavaScript Library v1.10.2\n * http://jquery.com/\n *\n * Includes Sizzle.js\n * http://sizzlejs.com/\n *\n * Copyright 2005, 2013 jQuery Foundation, Inc. and other contributors\n * Released under the MIT license\n * http://jquery.org/license\n *\n * Date: 2013-07-03T13:48Z\n */\n(function( window, undefined ) {\n\n// Can't do this because several apps including ASP.NET trace\n// the stack via arguments.caller.callee and Firefox dies if\n// you try to trace through \"use strict\" call chains. (#13335)\n// Support: Firefox 18+\n//\"use strict\";\nvar\n\t// The deferred used on DOM ready\n\treadyList,\n\n\t// A central reference to the root jQuery(document)\n\trootjQuery,\n\n\t// Support: IE<10\n\t// For `typeof xmlNode.method` instead of `xmlNode.method !== undefined`\n\tcore_strundefined = typeof undefined,\n\n\t// Use the correct document accordingly with window argument (sandbox)\n\tlocation = window.location,\n\tdocument = window.document,\n\tdocElem = document.documentElement,\n\n\t// Map over jQuery in case of overwrite\n\t_jQuery = window.jQuery,\n\n\t// Map over the $ in case of overwrite\n\t_$ = window.$,\n\n\t// [[Class]] -> type pairs\n\tclass2type = {},\n\n\t// List of deleted data cache ids, so we can reuse them\n\tcore_deletedIds = [],\n\n\tcore_version = \"1.10.2\",\n\n\t// Save a reference to some core methods\n\tcore_concat = core_deletedIds.concat,\n\tcore_push = core_deletedIds.push,\n\tcore_slice = core_deletedIds.slice,\n\tcore_indexOf = core_deletedIds.indexOf,\n\tcore_toString = class2type.toString,\n\tcore_hasOwn = class2type.hasOwnProperty,\n\tcore_trim = core_version.trim,\n\n\t// Define a local copy of jQuery\n\tjQuery = function( selector, context ) {\n\t\t// The jQuery object is actually just the init constructor 'enhanced'\n\t\treturn new jQuery.fn.init( selector, context, rootjQuery );\n\t},\n\n\t// Used for matching numbers\n\tcore_pnum = /[+-]?(?:\\d*\\.|)\\d+(?:[eE][+-]?\\d+|)/.source,\n\n\t// Used for splitting on whitespace\n\tcore_rnotwhite = /\\S+/g,\n\n\t// Make sure we trim BOM and NBSP (here's looking at you, Safari 5.0 and IE)\n\trtrim = /^[\\s\\uFEFF\\xA0]+|[\\s\\uFEFF\\xA0]+$/g,\n\n\t// A simple way to check for HTML strings\n\t// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)\n\t// Strict HTML recognition (#11290: must start with <)\n\trquickExpr = /^(?:\\s*(<[\\w\\W]+>)[^>]*|#([\\w-]*))$/,\n\n\t// Match a standalone tag\n\trsingleTag = /^<(\\w+)\\s*\\/?>(?:<\\/\\1>|)$/,\n\n\t// JSON RegExp\n\trvalidchars = /^[\\],:{}\\s]*$/,\n\trvalidbraces = /(?:^|:|,)(?:\\s*\\[)+/g,\n\trvalidescape = /\\\\(?:[\"\\\\\\/bfnrt]|u[\\da-fA-F]{4})/g,\n\trvalidtokens = /\"[^\"\\\\\\r\\n]*\"|true|false|null|-?(?:\\d+\\.|)\\d+(?:[eE][+-]?\\d+|)/g,\n\n\t// Matches dashed string for camelizing\n\trmsPrefix = /^-ms-/,\n\trdashAlpha = /-([\\da-z])/gi,\n\n\t// Used by jQuery.camelCase as callback to replace()\n\tfcamelCase = function( all, letter ) {\n\t\treturn letter.toUpperCase();\n\t},\n\n\t// The ready event handler\n\tcompleted = function( event ) {\n\n\t\t// readyState === \"complete\" is good enough for us to call the dom ready in oldIE\n\t\tif ( document.addEventListener || event.type === \"load\" || document.readyState === \"complete\" ) {\n\t\t\tdetach();\n\t\t\tjQuery.ready();\n\t\t}\n\t},\n\t// Clean-up method for dom ready events\n\tdetach = function() {\n\t\tif ( document.addEventListener ) {\n\t\t\tdocument.removeEventListener( \"DOMContentLoaded\", completed, false );\n\t\t\twindow.removeEventListener( \"load\", completed, false );\n\n\t\t} else {\n\t\t\tdocument.detachEvent( \"onreadystatechange\", completed );\n\t\t\twindow.detachEvent( \"onload\", completed );\n\t\t}\n\t};\n\njQuery.fn = jQuery.prototype = {\n\t// The current version of jQuery being used\n\tjquery: core_version,\n\n\tconstructor: jQuery,\n\tinit: function( selector, context, rootjQuery ) {\n\t\tvar match, elem;\n\n\t\t// HANDLE: $(\"\"), $(null), $(undefined), $(false)\n\t\tif ( !selector ) {\n\t\t\treturn this;\n\t\t}\n\n\t\t// Handle HTML strings\n\t\tif ( typeof selector === \"string\" ) {\n\t\t\tif ( selector.charAt(0) === \"<\" && selector.charAt( selector.length - 1 ) === \">\" && selector.length >= 3 ) {\n\t\t\t\t// Assume that strings that start and end with <> are HTML and skip the regex check\n\t\t\t\tmatch = [ null, selector, null ];\n\n\t\t\t} else {\n\t\t\t\tmatch = rquickExpr.exec( selector );\n\t\t\t}\n\n\t\t\t// Match html or make sure no context is specified for #id\n\t\t\tif ( match && (match[1] || !context) ) {\n\n\t\t\t\t// HANDLE: $(html) -> $(array)\n\t\t\t\tif ( match[1] ) {\n\t\t\t\t\tcontext = context instanceof jQuery ? context[0] : context;\n\n\t\t\t\t\t// scripts is true for back-compat\n\t\t\t\t\tjQuery.merge( this, jQuery.parseHTML(\n\t\t\t\t\t\tmatch[1],\n\t\t\t\t\t\tcontext && context.nodeType ? context.ownerDocument || context : document,\n\t\t\t\t\t\ttrue\n\t\t\t\t\t) );\n\n\t\t\t\t\t// HANDLE: $(html, props)\n\t\t\t\t\tif ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {\n\t\t\t\t\t\tfor ( match in context ) {\n\t\t\t\t\t\t\t// Properties of context are called as methods if possible\n\t\t\t\t\t\t\tif ( jQuery.isFunction( this[ match ] ) ) {\n\t\t\t\t\t\t\t\tthis[ match ]( context[ match ] );\n\n\t\t\t\t\t\t\t// ...and otherwise set as attributes\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tthis.attr( match, context[ match ] );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\treturn this;\n\n\t\t\t\t// HANDLE: $(#id)\n\t\t\t\t} else {\n\t\t\t\t\telem = document.getElementById( match[2] );\n\n\t\t\t\t\t// Check parentNode to catch when Blackberry 4.6 returns\n\t\t\t\t\t// nodes that are no longer in the document #6963\n\t\t\t\t\tif ( elem && elem.parentNode ) {\n\t\t\t\t\t\t// Handle the case where IE and Opera return items\n\t\t\t\t\t\t// by name instead of ID\n\t\t\t\t\t\tif ( elem.id !== match[2] ) {\n\t\t\t\t\t\t\treturn rootjQuery.find( selector );\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Otherwise, we inject the element directly into the jQuery object\n\t\t\t\t\t\tthis.length = 1;\n\t\t\t\t\t\tthis[0] = elem;\n\t\t\t\t\t}\n\n\t\t\t\t\tthis.context = document;\n\t\t\t\t\tthis.selector = selector;\n\t\t\t\t\treturn this;\n\t\t\t\t}\n\n\t\t\t// HANDLE: $(expr, $(...))\n\t\t\t} else if ( !context || context.jquery ) {\n\t\t\t\treturn ( context || rootjQuery ).find( selector );\n\n\t\t\t// HANDLE: $(expr, context)\n\t\t\t// (which is just equivalent to: $(context).find(expr)\n\t\t\t} else {\n\t\t\t\treturn this.constructor( context ).find( selector );\n\t\t\t}\n\n\t\t// HANDLE: $(DOMElement)\n\t\t} else if ( selector.nodeType ) {\n\t\t\tthis.context = this[0] = selector;\n\t\t\tthis.length = 1;\n\t\t\treturn this;\n\n\t\t// HANDLE: $(function)\n\t\t// Shortcut for document ready\n\t\t} else if ( jQuery.isFunction( selector ) ) {\n\t\t\treturn rootjQuery.ready( selector );\n\t\t}\n\n\t\tif ( selector.selector !== undefined ) {\n\t\t\tthis.selector = selector.selector;\n\t\t\tthis.context = selector.context;\n\t\t}\n\n\t\treturn jQuery.makeArray( selector, this );\n\t},\n\n\t// Start with an empty selector\n\tselector: \"\",\n\n\t// The default length of a jQuery object is 0\n\tlength: 0,\n\n\ttoArray: function() {\n\t\treturn core_slice.call( this );\n\t},\n\n\t// Get the Nth element in the matched element set OR\n\t// Get the whole matched element set as a clean array\n\tget: function( num ) {\n\t\treturn num == null ?\n\n\t\t\t// Return a 'clean' array\n\t\t\tthis.toArray() :\n\n\t\t\t// Return just the object\n\t\t\t( num < 0 ? this[ this.length + num ] : this[ num ] );\n\t},\n\n\t// Take an array of elements and push it onto the stack\n\t// (returning the new matched element set)\n\tpushStack: function( elems ) {\n\n\t\t// Build a new jQuery matched element set\n\t\tvar ret = jQuery.merge( this.constructor(), elems );\n\n\t\t// Add the old object onto the stack (as a reference)\n\t\tret.prevObject = this;\n\t\tret.context = this.context;\n\n\t\t// Return the newly-formed element set\n\t\treturn ret;\n\t},\n\n\t// Execute a callback for every element in the matched set.\n\t// (You can seed the arguments with an array of args, but this is\n\t// only used internally.)\n\teach: function( callback, args ) {\n\t\treturn jQuery.each( this, callback, args );\n\t},\n\n\tready: function( fn ) {\n\t\t// Add the callback\n\t\tjQuery.ready.promise().done( fn );\n\n\t\treturn this;\n\t},\n\n\tslice: function() {\n\t\treturn this.pushStack( core_slice.apply( this, arguments ) );\n\t},\n\n\tfirst: function() {\n\t\treturn this.eq( 0 );\n\t},\n\n\tlast: function() {\n\t\treturn this.eq( -1 );\n\t},\n\n\teq: function( i ) {\n\t\tvar len = this.length,\n\t\t\tj = +i + ( i < 0 ? len : 0 );\n\t\treturn this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );\n\t},\n\n\tmap: function( callback ) {\n\t\treturn this.pushStack( jQuery.map(this, function( elem, i ) {\n\t\t\treturn callback.call( elem, i, elem );\n\t\t}));\n\t},\n\n\tend: function() {\n\t\treturn this.prevObject || this.constructor(null);\n\t},\n\n\t// For internal use only.\n\t// Behaves like an Array's method, not like a jQuery method.\n\tpush: core_push,\n\tsort: [].sort,\n\tsplice: [].splice\n};\n\n// Give the init function the jQuery prototype for later instantiation\njQuery.fn.init.prototype = jQuery.fn;\n\njQuery.extend = jQuery.fn.extend = function() {\n\tvar src, copyIsArray, copy, name, options, clone,\n\t\ttarget = arguments[0] || {},\n\t\ti = 1,\n\t\tlength = arguments.length,\n\t\tdeep = false;\n\n\t// Handle a deep copy situation\n\tif ( typeof target === \"boolean\" ) {\n\t\tdeep = target;\n\t\ttarget = arguments[1] || {};\n\t\t// skip the boolean and the target\n\t\ti = 2;\n\t}\n\n\t// Handle case when target is a string or something (possible in deep copy)\n\tif ( typeof target !== \"object\" && !jQuery.isFunction(target) ) {\n\t\ttarget = {};\n\t}\n\n\t// extend jQuery itself if only one argument is passed\n\tif ( length === i ) {\n\t\ttarget = this;\n\t\t--i;\n\t}\n\n\tfor ( ; i < length; i++ ) {\n\t\t// Only deal with non-null/undefined values\n\t\tif ( (options = arguments[ i ]) != null ) {\n\t\t\t// Extend the base object\n\t\t\tfor ( name in options ) {\n\t\t\t\tsrc = target[ name ];\n\t\t\t\tcopy = options[ name ];\n\n\t\t\t\t// Prevent never-ending loop\n\t\t\t\tif ( target === copy ) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\t// Recurse if we're merging plain objects or arrays\n\t\t\t\tif ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {\n\t\t\t\t\tif ( copyIsArray ) {\n\t\t\t\t\t\tcopyIsArray = false;\n\t\t\t\t\t\tclone = src && jQuery.isArray(src) ? src : [];\n\n\t\t\t\t\t} else {\n\t\t\t\t\t\tclone = src && jQuery.isPlainObject(src) ? src : {};\n\t\t\t\t\t}\n\n\t\t\t\t\t// Never move original objects, clone them\n\t\t\t\t\ttarget[ name ] = jQuery.extend( deep, clone, copy );\n\n\t\t\t\t// Don't bring in undefined values\n\t\t\t\t} else if ( copy !== undefined ) {\n\t\t\t\t\ttarget[ name ] = copy;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Return the modified object\n\treturn target;\n};\n\njQuery.extend({\n\t// Unique for each copy of jQuery on the page\n\t// Non-digits removed to match rinlinejQuery\n\texpando: \"jQuery\" + ( core_version + Math.random() ).replace( /\\D/g, \"\" ),\n\n\tnoConflict: function( deep ) {\n\t\tif ( window.$ === jQuery ) {\n\t\t\twindow.$ = _$;\n\t\t}\n\n\t\tif ( deep && window.jQuery === jQuery ) {\n\t\t\twindow.jQuery = _jQuery;\n\t\t}\n\n\t\treturn jQuery;\n\t},\n\n\t// Is the DOM ready to be used? Set to true once it occurs.\n\tisReady: false,\n\n\t// A counter to track how many items to wait for before\n\t// the ready event fires. See #6781\n\treadyWait: 1,\n\n\t// Hold (or release) the ready event\n\tholdReady: function( hold ) {\n\t\tif ( hold ) {\n\t\t\tjQuery.readyWait++;\n\t\t} else {\n\t\t\tjQuery.ready( true );\n\t\t}\n\t},\n\n\t// Handle when the DOM is ready\n\tready: function( wait ) {\n\n\t\t// Abort if there are pending holds or we're already ready\n\t\tif ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).\n\t\tif ( !document.body ) {\n\t\t\treturn setTimeout( jQuery.ready );\n\t\t}\n\n\t\t// Remember that the DOM is ready\n\t\tjQuery.isReady = true;\n\n\t\t// If a normal DOM Ready event fired, decrement, and wait if need be\n\t\tif ( wait !== true && --jQuery.readyWait > 0 ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// If there are functions bound, to execute\n\t\treadyList.resolveWith( document, [ jQuery ] );\n\n\t\t// Trigger any bound ready events\n\t\tif ( jQuery.fn.trigger ) {\n\t\t\tjQuery( document ).trigger(\"ready\").off(\"ready\");\n\t\t}\n\t},\n\n\t// See test/unit/core.js for details concerning isFunction.\n\t// Since version 1.3, DOM methods and functions like alert\n\t// aren't supported. They return false on IE (#2968).\n\tisFunction: function( obj ) {\n\t\treturn jQuery.type(obj) === \"function\";\n\t},\n\n\tisArray: Array.isArray || function( obj ) {\n\t\treturn jQuery.type(obj) === \"array\";\n\t},\n\n\tisWindow: function( obj ) {\n\t\t/* jshint eqeqeq: false */\n\t\treturn obj != null && obj == obj.window;\n\t},\n\n\tisNumeric: function( obj ) {\n\t\treturn !isNaN( parseFloat(obj) ) && isFinite( obj );\n\t},\n\n\ttype: function( obj ) {\n\t\tif ( obj == null ) {\n\t\t\treturn String( obj );\n\t\t}\n\t\treturn typeof obj === \"object\" || typeof obj === \"function\" ?\n\t\t\tclass2type[ core_toString.call(obj) ] || \"object\" :\n\t\t\ttypeof obj;\n\t},\n\n\tisPlainObject: function( obj ) {\n\t\tvar key;\n\n\t\t// Must be an Object.\n\t\t// Because of IE, we also have to check the presence of the constructor property.\n\t\t// Make sure that DOM nodes and window objects don't pass through, as well\n\t\tif ( !obj || jQuery.type(obj) !== \"object\" || obj.nodeType || jQuery.isWindow( obj ) ) {\n\t\t\treturn false;\n\t\t}\n\n\t\ttry {\n\t\t\t// Not own constructor property must be Object\n\t\t\tif ( obj.constructor &&\n\t\t\t\t!core_hasOwn.call(obj, \"constructor\") &&\n\t\t\t\t!core_hasOwn.call(obj.constructor.prototype, \"isPrototypeOf\") ) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t} catch ( e ) {\n\t\t\t// IE8,9 Will throw exceptions on certain host objects #9897\n\t\t\treturn false;\n\t\t}\n\n\t\t// Support: IE<9\n\t\t// Handle iteration over inherited properties before own properties.\n\t\tif ( jQuery.support.ownLast ) {\n\t\t\tfor ( key in obj ) {\n\t\t\t\treturn core_hasOwn.call( obj, key );\n\t\t\t}\n\t\t}\n\n\t\t// Own properties are enumerated firstly, so to speed up,\n\t\t// if last one is own, then all properties are own.\n\t\tfor ( key in obj ) {}\n\n\t\treturn key === undefined || core_hasOwn.call( obj, key );\n\t},\n\n\tisEmptyObject: function( obj ) {\n\t\tvar name;\n\t\tfor ( name in obj ) {\n\t\t\treturn false;\n\t\t}\n\t\treturn true;\n\t},\n\n\terror: function( msg ) {\n\t\tthrow new Error( msg );\n\t},\n\n\t// data: string of html\n\t// context (optional): If specified, the fragment will be created in this context, defaults to document\n\t// keepScripts (optional): If true, will include scripts passed in the html string\n\tparseHTML: function( data, context, keepScripts ) {\n\t\tif ( !data || typeof data !== \"string\" ) {\n\t\t\treturn null;\n\t\t}\n\t\tif ( typeof context === \"boolean\" ) {\n\t\t\tkeepScripts = context;\n\t\t\tcontext = false;\n\t\t}\n\t\tcontext = context || document;\n\n\t\tvar parsed = rsingleTag.exec( data ),\n\t\t\tscripts = !keepScripts && [];\n\n\t\t// Single tag\n\t\tif ( parsed ) {\n\t\t\treturn [ context.createElement( parsed[1] ) ];\n\t\t}\n\n\t\tparsed = jQuery.buildFragment( [ data ], context, scripts );\n\t\tif ( scripts ) {\n\t\t\tjQuery( scripts ).remove();\n\t\t}\n\t\treturn jQuery.merge( [], parsed.childNodes );\n\t},\n\n\tparseJSON: function( data ) {\n\t\t// Attempt to parse using the native JSON parser first\n\t\tif ( window.JSON && window.JSON.parse ) {\n\t\t\treturn window.JSON.parse( data );\n\t\t}\n\n\t\tif ( data === null ) {\n\t\t\treturn data;\n\t\t}\n\n\t\tif ( typeof data === \"string\" ) {\n\n\t\t\t// Make sure leading/trailing whitespace is removed (IE can't handle it)\n\t\t\tdata = jQuery.trim( data );\n\n\t\t\tif ( data ) {\n\t\t\t\t// Make sure the incoming data is actual JSON\n\t\t\t\t// Logic borrowed from http://json.org/json2.js\n\t\t\t\tif ( rvalidchars.test( data.replace( rvalidescape, \"@\" )\n\t\t\t\t\t.replace( rvalidtokens, \"]\" )\n\t\t\t\t\t.replace( rvalidbraces, \"\")) ) {\n\n\t\t\t\t\treturn ( new Function( \"return \" + data ) )();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tjQuery.error( \"Invalid JSON: \" + data );\n\t},\n\n\t// Cross-browser xml parsing\n\tparseXML: function( data ) {\n\t\tvar xml, tmp;\n\t\tif ( !data || typeof data !== \"string\" ) {\n\t\t\treturn null;\n\t\t}\n\t\ttry {\n\t\t\tif ( window.DOMParser ) { // Standard\n\t\t\t\ttmp = new DOMParser();\n\t\t\t\txml = tmp.parseFromString( data , \"text/xml\" );\n\t\t\t} else { // IE\n\t\t\t\txml = new ActiveXObject( \"Microsoft.XMLDOM\" );\n\t\t\t\txml.async = \"false\";\n\t\t\t\txml.loadXML( data );\n\t\t\t}\n\t\t} catch( e ) {\n\t\t\txml = undefined;\n\t\t}\n\t\tif ( !xml || !xml.documentElement || xml.getElementsByTagName( \"parsererror\" ).length ) {\n\t\t\tjQuery.error( \"Invalid XML: \" + data );\n\t\t}\n\t\treturn xml;\n\t},\n\n\tnoop: function() {},\n\n\t// Evaluates a script in a global context\n\t// Workarounds based on findings by Jim Driscoll\n\t// http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context\n\tglobalEval: function( data ) {\n\t\tif ( data && jQuery.trim( data ) ) {\n\t\t\t// We use execScript on Internet Explorer\n\t\t\t// We use an anonymous function so that context is window\n\t\t\t// rather than jQuery in Firefox\n\t\t\t( window.execScript || function( data ) {\n\t\t\t\twindow[ \"eval\" ].call( window, data );\n\t\t\t} )( data );\n\t\t}\n\t},\n\n\t// Convert dashed to camelCase; used by the css and data modules\n\t// Microsoft forgot to hump their vendor prefix (#9572)\n\tcamelCase: function( string ) {\n\t\treturn string.replace( rmsPrefix, \"ms-\" ).replace( rdashAlpha, fcamelCase );\n\t},\n\n\tnodeName: function( elem, name ) {\n\t\treturn elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();\n\t},\n\n\t// args is for internal usage only\n\teach: function( obj, callback, args ) {\n\t\tvar value,\n\t\t\ti = 0,\n\t\t\tlength = obj.length,\n\t\t\tisArray = isArraylike( obj );\n\n\t\tif ( args ) {\n\t\t\tif ( isArray ) {\n\t\t\t\tfor ( ; i < length; i++ ) {\n\t\t\t\t\tvalue = callback.apply( obj[ i ], args );\n\n\t\t\t\t\tif ( value === false ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfor ( i in obj ) {\n\t\t\t\t\tvalue = callback.apply( obj[ i ], args );\n\n\t\t\t\t\tif ( value === false ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t// A special, fast, case for the most common use of each\n\t\t} else {\n\t\t\tif ( isArray ) {\n\t\t\t\tfor ( ; i < length; i++ ) {\n\t\t\t\t\tvalue = callback.call( obj[ i ], i, obj[ i ] );\n\n\t\t\t\t\tif ( value === false ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfor ( i in obj ) {\n\t\t\t\t\tvalue = callback.call( obj[ i ], i, obj[ i ] );\n\n\t\t\t\t\tif ( value === false ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn obj;\n\t},\n\n\t// Use native String.trim function wherever possible\n\ttrim: core_trim && !core_trim.call(\"\\uFEFF\\xA0\") ?\n\t\tfunction( text ) {\n\t\t\treturn text == null ?\n\t\t\t\t\"\" :\n\t\t\t\tcore_trim.call( text );\n\t\t} :\n\n\t\t// Otherwise use our own trimming functionality\n\t\tfunction( text ) {\n\t\t\treturn text == null ?\n\t\t\t\t\"\" :\n\t\t\t\t( text + \"\" ).replace( rtrim, \"\" );\n\t\t},\n\n\t// results is for internal usage only\n\tmakeArray: function( arr, results ) {\n\t\tvar ret = results || [];\n\n\t\tif ( arr != null ) {\n\t\t\tif ( isArraylike( Object(arr) ) ) {\n\t\t\t\tjQuery.merge( ret,\n\t\t\t\t\ttypeof arr === \"string\" ?\n\t\t\t\t\t[ arr ] : arr\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tcore_push.call( ret, arr );\n\t\t\t}\n\t\t}\n\n\t\treturn ret;\n\t},\n\n\tinArray: function( elem, arr, i ) {\n\t\tvar len;\n\n\t\tif ( arr ) {\n\t\t\tif ( core_indexOf ) {\n\t\t\t\treturn core_indexOf.call( arr, elem, i );\n\t\t\t}\n\n\t\t\tlen = arr.length;\n\t\t\ti = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;\n\n\t\t\tfor ( ; i < len; i++ ) {\n\t\t\t\t// Skip accessing in sparse arrays\n\t\t\t\tif ( i in arr && arr[ i ] === elem ) {\n\t\t\t\t\treturn i;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn -1;\n\t},\n\n\tmerge: function( first, second ) {\n\t\tvar l = second.length,\n\t\t\ti = first.length,\n\t\t\tj = 0;\n\n\t\tif ( typeof l === \"number\" ) {\n\t\t\tfor ( ; j < l; j++ ) {\n\t\t\t\tfirst[ i++ ] = second[ j ];\n\t\t\t}\n\t\t} else {\n\t\t\twhile ( second[j] !== undefined ) {\n\t\t\t\tfirst[ i++ ] = second[ j++ ];\n\t\t\t}\n\t\t}\n\n\t\tfirst.length = i;\n\n\t\treturn first;\n\t},\n\n\tgrep: function( elems, callback, inv ) {\n\t\tvar retVal,\n\t\t\tret = [],\n\t\t\ti = 0,\n\t\t\tlength = elems.length;\n\t\tinv = !!inv;\n\n\t\t// Go through the array, only saving the items\n\t\t// that pass the validator function\n\t\tfor ( ; i < length; i++ ) {\n\t\t\tretVal = !!callback( elems[ i ], i );\n\t\t\tif ( inv !== retVal ) {\n\t\t\t\tret.push( elems[ i ] );\n\t\t\t}\n\t\t}\n\n\t\treturn ret;\n\t},\n\n\t// arg is for internal usage only\n\tmap: function( elems, callback, arg ) {\n\t\tvar value,\n\t\t\ti = 0,\n\t\t\tlength = elems.length,\n\t\t\tisArray = isArraylike( elems ),\n\t\t\tret = [];\n\n\t\t// Go through the array, translating each of the items to their\n\t\tif ( isArray ) {\n\t\t\tfor ( ; i < length; i++ ) {\n\t\t\t\tvalue = callback( elems[ i ], i, arg );\n\n\t\t\t\tif ( value != null ) {\n\t\t\t\t\tret[ ret.length ] = value;\n\t\t\t\t}\n\t\t\t}\n\n\t\t// Go through every key on the object,\n\t\t} else {\n\t\t\tfor ( i in elems ) {\n\t\t\t\tvalue = callback( elems[ i ], i, arg );\n\n\t\t\t\tif ( value != null ) {\n\t\t\t\t\tret[ ret.length ] = value;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Flatten any nested arrays\n\t\treturn core_concat.apply( [], ret );\n\t},\n\n\t// A global GUID counter for objects\n\tguid: 1,\n\n\t// Bind a function to a context, optionally partially applying any\n\t// arguments.\n\tproxy: function( fn, context ) {\n\t\tvar args, proxy, tmp;\n\n\t\tif ( typeof context === \"string\" ) {\n\t\t\ttmp = fn[ context ];\n\t\t\tcontext = fn;\n\t\t\tfn = tmp;\n\t\t}\n\n\t\t// Quick check to determine if target is callable, in the spec\n\t\t// this throws a TypeError, but we will just return undefined.\n\t\tif ( !jQuery.isFunction( fn ) ) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\t// Simulated bind\n\t\targs = core_slice.call( arguments, 2 );\n\t\tproxy = function() {\n\t\t\treturn fn.apply( context || this, args.concat( core_slice.call( arguments ) ) );\n\t\t};\n\n\t\t// Set the guid of unique handler to the same of original handler, so it can be removed\n\t\tproxy.guid = fn.guid = fn.guid || jQuery.guid++;\n\n\t\treturn proxy;\n\t},\n\n\t// Multifunctional method to get and set values of a collection\n\t// The value/s can optionally be executed if it's a function\n\taccess: function( elems, fn, key, value, chainable, emptyGet, raw ) {\n\t\tvar i = 0,\n\t\t\tlength = elems.length,\n\t\t\tbulk = key == null;\n\n\t\t// Sets many values\n\t\tif ( jQuery.type( key ) === \"object\" ) {\n\t\t\tchainable = true;\n\t\t\tfor ( i in key ) {\n\t\t\t\tjQuery.access( elems, fn, i, key[i], true, emptyGet, raw );\n\t\t\t}\n\n\t\t// Sets one value\n\t\t} else if ( value !== undefined ) {\n\t\t\tchainable = true;\n\n\t\t\tif ( !jQuery.isFunction( value ) ) {\n\t\t\t\traw = true;\n\t\t\t}\n\n\t\t\tif ( bulk ) {\n\t\t\t\t// Bulk operations run against the entire set\n\t\t\t\tif ( raw ) {\n\t\t\t\t\tfn.call( elems, value );\n\t\t\t\t\tfn = null;\n\n\t\t\t\t// ...except when executing function values\n\t\t\t\t} else {\n\t\t\t\t\tbulk = fn;\n\t\t\t\t\tfn = function( elem, key, value ) {\n\t\t\t\t\t\treturn bulk.call( jQuery( elem ), value );\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( fn ) {\n\t\t\t\tfor ( ; i < length; i++ ) {\n\t\t\t\t\tfn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn chainable ?\n\t\t\telems :\n\n\t\t\t// Gets\n\t\t\tbulk ?\n\t\t\t\tfn.call( elems ) :\n\t\t\t\tlength ? fn( elems[0], key ) : emptyGet;\n\t},\n\n\tnow: function() {\n\t\treturn ( new Date() ).getTime();\n\t},\n\n\t// A method for quickly swapping in/out CSS properties to get correct calculations.\n\t// Note: this method belongs to the css module but it's needed here for the support module.\n\t// If support gets modularized, this method should be moved back to the css module.\n\tswap: function( elem, options, callback, args ) {\n\t\tvar ret, name,\n\t\t\told = {};\n\n\t\t// Remember the old values, and insert the new ones\n\t\tfor ( name in options ) {\n\t\t\told[ name ] = elem.style[ name ];\n\t\t\telem.style[ name ] = options[ name ];\n\t\t}\n\n\t\tret = callback.apply( elem, args || [] );\n\n\t\t// Revert the old values\n\t\tfor ( name in options ) {\n\t\t\telem.style[ name ] = old[ name ];\n\t\t}\n\n\t\treturn ret;\n\t}\n});\n\njQuery.ready.promise = function( obj ) {\n\tif ( !readyList ) {\n\n\t\treadyList = jQuery.Deferred();\n\n\t\t// Catch cases where $(document).ready() is called after the browser event has already occurred.\n\t\t// we once tried to use readyState \"interactive\" here, but it caused issues like the one\n\t\t// discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15\n\t\tif ( document.readyState === \"complete\" ) {\n\t\t\t// Handle it asynchronously to allow scripts the opportunity to delay ready\n\t\t\tsetTimeout( jQuery.ready );\n\n\t\t// Standards-based browsers support DOMContentLoaded\n\t\t} else if ( document.addEventListener ) {\n\t\t\t// Use the handy event callback\n\t\t\tdocument.addEventListener( \"DOMContentLoaded\", completed, false );\n\n\t\t\t// A fallback to window.onload, that will always work\n\t\t\twindow.addEventListener( \"load\", completed, false );\n\n\t\t// If IE event model is used\n\t\t} else {\n\t\t\t// Ensure firing before onload, maybe late but safe also for iframes\n\t\t\tdocument.attachEvent( \"onreadystatechange\", completed );\n\n\t\t\t// A fallback to window.onload, that will always work\n\t\t\twindow.attachEvent( \"onload\", completed );\n\n\t\t\t// If IE and not a frame\n\t\t\t// continually check to see if the document is ready\n\t\t\tvar top = false;\n\n\t\t\ttry {\n\t\t\t\ttop = window.frameElement == null && document.documentElement;\n\t\t\t} catch(e) {}\n\n\t\t\tif ( top && top.doScroll ) {\n\t\t\t\t(function doScrollCheck() {\n\t\t\t\t\tif ( !jQuery.isReady ) {\n\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t// Use the trick by Diego Perini\n\t\t\t\t\t\t\t// http://javascript.nwbox.com/IEContentLoaded/\n\t\t\t\t\t\t\ttop.doScroll(\"left\");\n\t\t\t\t\t\t} catch(e) {\n\t\t\t\t\t\t\treturn setTimeout( doScrollCheck, 50 );\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// detach all dom ready events\n\t\t\t\t\t\tdetach();\n\n\t\t\t\t\t\t// and execute any waiting functions\n\t\t\t\t\t\tjQuery.ready();\n\t\t\t\t\t}\n\t\t\t\t})();\n\t\t\t}\n\t\t}\n\t}\n\treturn readyList.promise( obj );\n};\n\n// Populate the class2type map\njQuery.each(\"Boolean Number String Function Array Date RegExp Object Error\".split(\" \"), function(i, name) {\n\tclass2type[ \"[object \" + name + \"]\" ] = name.toLowerCase();\n});\n\nfunction isArraylike( obj ) {\n\tvar length = obj.length,\n\t\ttype = jQuery.type( obj );\n\n\tif ( jQuery.isWindow( obj ) ) {\n\t\treturn false;\n\t}\n\n\tif ( obj.nodeType === 1 && length ) {\n\t\treturn true;\n\t}\n\n\treturn type === \"array\" || type !== \"function\" &&\n\t\t( length === 0 ||\n\t\ttypeof length === \"number\" && length > 0 && ( length - 1 ) in obj );\n}\n\n// All jQuery objects should point back to these\nrootjQuery = jQuery(document);\n/*!\n * Sizzle CSS Selector Engine v1.10.2\n * http://sizzlejs.com/\n *\n * Copyright 2013 jQuery Foundation, Inc. and other contributors\n * Released under the MIT license\n * http://jquery.org/license\n *\n * Date: 2013-07-03\n */\n(function( window, undefined ) {\n\nvar i,\n\tsupport,\n\tcachedruns,\n\tExpr,\n\tgetText,\n\tisXML,\n\tcompile,\n\toutermostContext,\n\tsortInput,\n\n\t// Local document vars\n\tsetDocument,\n\tdocument,\n\tdocElem,\n\tdocumentIsHTML,\n\trbuggyQSA,\n\trbuggyMatches,\n\tmatches,\n\tcontains,\n\n\t// Instance-specific data\n\texpando = \"sizzle\" + -(new Date()),\n\tpreferredDoc = window.document,\n\tdirruns = 0,\n\tdone = 0,\n\tclassCache = createCache(),\n\ttokenCache = createCache(),\n\tcompilerCache = createCache(),\n\thasDuplicate = false,\n\tsortOrder = function( a, b ) {\n\t\tif ( a === b ) {\n\t\t\thasDuplicate = true;\n\t\t\treturn 0;\n\t\t}\n\t\treturn 0;\n\t},\n\n\t// General-purpose constants\n\tstrundefined = typeof undefined,\n\tMAX_NEGATIVE = 1 << 31,\n\n\t// Instance methods\n\thasOwn = ({}).hasOwnProperty,\n\tarr = [],\n\tpop = arr.pop,\n\tpush_native = arr.push,\n\tpush = arr.push,\n\tslice = arr.slice,\n\t// Use a stripped-down indexOf if we can't use a native one\n\tindexOf = arr.indexOf || function( elem ) {\n\t\tvar i = 0,\n\t\t\tlen = this.length;\n\t\tfor ( ; i < len; i++ ) {\n\t\t\tif ( this[i] === elem ) {\n\t\t\t\treturn i;\n\t\t\t}\n\t\t}\n\t\treturn -1;\n\t},\n\n\tbooleans = \"checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped\",\n\n\t// Regular expressions\n\n\t// Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace\n\twhitespace = \"[\\\\x20\\\\t\\\\r\\\\n\\\\f]\",\n\t// http://www.w3.org/TR/css3-syntax/#characters\n\tcharacterEncoding = \"(?:\\\\\\\\.|[\\\\w-]|[^\\\\x00-\\\\xa0])+\",\n\n\t// Loosely modeled on CSS identifier characters\n\t// An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors\n\t// Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier\n\tidentifier = characterEncoding.replace( \"w\", \"w#\" ),\n\n\t// Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors\n\tattributes = \"\\\\[\" + whitespace + \"*(\" + characterEncoding + \")\" + whitespace +\n\t\t\"*(?:([*^$|!~]?=)\" + whitespace + \"*(?:(['\\\"])((?:\\\\\\\\.|[^\\\\\\\\])*?)\\\\3|(\" + identifier + \")|)|)\" + whitespace + \"*\\\\]\",\n\n\t// Prefer arguments quoted,\n\t//   then not containing pseudos/brackets,\n\t//   then attribute selectors/non-parenthetical expressions,\n\t//   then anything else\n\t// These preferences are here to reduce the number of selectors\n\t//   needing tokenize in the PSEUDO preFilter\n\tpseudos = \":(\" + characterEncoding + \")(?:\\\\(((['\\\"])((?:\\\\\\\\.|[^\\\\\\\\])*?)\\\\3|((?:\\\\\\\\.|[^\\\\\\\\()[\\\\]]|\" + attributes.replace( 3, 8 ) + \")*)|.*)\\\\)|)\",\n\n\t// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter\n\trtrim = new RegExp( \"^\" + whitespace + \"+|((?:^|[^\\\\\\\\])(?:\\\\\\\\.)*)\" + whitespace + \"+$\", \"g\" ),\n\n\trcomma = new RegExp( \"^\" + whitespace + \"*,\" + whitespace + \"*\" ),\n\trcombinators = new RegExp( \"^\" + whitespace + \"*([>+~]|\" + whitespace + \")\" + whitespace + \"*\" ),\n\n\trsibling = new RegExp( whitespace + \"*[+~]\" ),\n\trattributeQuotes = new RegExp( \"=\" + whitespace + \"*([^\\\\]'\\\"]*)\" + whitespace + \"*\\\\]\", \"g\" ),\n\n\trpseudo = new RegExp( pseudos ),\n\tridentifier = new RegExp( \"^\" + identifier + \"$\" ),\n\n\tmatchExpr = {\n\t\t\"ID\": new RegExp( \"^#(\" + characterEncoding + \")\" ),\n\t\t\"CLASS\": new RegExp( \"^\\\\.(\" + characterEncoding + \")\" ),\n\t\t\"TAG\": new RegExp( \"^(\" + characterEncoding.replace( \"w\", \"w*\" ) + \")\" ),\n\t\t\"ATTR\": new RegExp( \"^\" + attributes ),\n\t\t\"PSEUDO\": new RegExp( \"^\" + pseudos ),\n\t\t\"CHILD\": new RegExp( \"^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\\\(\" + whitespace +\n\t\t\t\"*(even|odd|(([+-]|)(\\\\d*)n|)\" + whitespace + \"*(?:([+-]|)\" + whitespace +\n\t\t\t\"*(\\\\d+)|))\" + whitespace + \"*\\\\)|)\", \"i\" ),\n\t\t\"bool\": new RegExp( \"^(?:\" + booleans + \")$\", \"i\" ),\n\t\t// For use in libraries implementing .is()\n\t\t// We use this for POS matching in `select`\n\t\t\"needsContext\": new RegExp( \"^\" + whitespace + \"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\\\(\" +\n\t\t\twhitespace + \"*((?:-\\\\d)?\\\\d*)\" + whitespace + \"*\\\\)|)(?=[^-]|$)\", \"i\" )\n\t},\n\n\trnative = /^[^{]+\\{\\s*\\[native \\w/,\n\n\t// Easily-parseable/retrievable ID or TAG or CLASS selectors\n\trquickExpr = /^(?:#([\\w-]+)|(\\w+)|\\.([\\w-]+))$/,\n\n\trinputs = /^(?:input|select|textarea|button)$/i,\n\trheader = /^h\\d$/i,\n\n\trescape = /'|\\\\/g,\n\n\t// CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters\n\trunescape = new RegExp( \"\\\\\\\\([\\\\da-f]{1,6}\" + whitespace + \"?|(\" + whitespace + \")|.)\", \"ig\" ),\n\tfunescape = function( _, escaped, escapedWhitespace ) {\n\t\tvar high = \"0x\" + escaped - 0x10000;\n\t\t// NaN means non-codepoint\n\t\t// Support: Firefox\n\t\t// Workaround erroneous numeric interpretation of +\"0x\"\n\t\treturn high !== high || escapedWhitespace ?\n\t\t\tescaped :\n\t\t\t// BMP codepoint\n\t\t\thigh < 0 ?\n\t\t\t\tString.fromCharCode( high + 0x10000 ) :\n\t\t\t\t// Supplemental Plane codepoint (surrogate pair)\n\t\t\t\tString.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );\n\t};\n\n// Optimize for push.apply( _, NodeList )\ntry {\n\tpush.apply(\n\t\t(arr = slice.call( preferredDoc.childNodes )),\n\t\tpreferredDoc.childNodes\n\t);\n\t// Support: Android<4.0\n\t// Detect silently failing push.apply\n\tarr[ preferredDoc.childNodes.length ].nodeType;\n} catch ( e ) {\n\tpush = { apply: arr.length ?\n\n\t\t// Leverage slice if possible\n\t\tfunction( target, els ) {\n\t\t\tpush_native.apply( target, slice.call(els) );\n\t\t} :\n\n\t\t// Support: IE<9\n\t\t// Otherwise append directly\n\t\tfunction( target, els ) {\n\t\t\tvar j = target.length,\n\t\t\t\ti = 0;\n\t\t\t// Can't trust NodeList.length\n\t\t\twhile ( (target[j++] = els[i++]) ) {}\n\t\t\ttarget.length = j - 1;\n\t\t}\n\t};\n}\n\nfunction Sizzle( selector, context, results, seed ) {\n\tvar match, elem, m, nodeType,\n\t\t// QSA vars\n\t\ti, groups, old, nid, newContext, newSelector;\n\n\tif ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {\n\t\tsetDocument( context );\n\t}\n\n\tcontext = context || document;\n\tresults = results || [];\n\n\tif ( !selector || typeof selector !== \"string\" ) {\n\t\treturn results;\n\t}\n\n\tif ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) {\n\t\treturn [];\n\t}\n\n\tif ( documentIsHTML && !seed ) {\n\n\t\t// Shortcuts\n\t\tif ( (match = rquickExpr.exec( selector )) ) {\n\t\t\t// Speed-up: Sizzle(\"#ID\")\n\t\t\tif ( (m = match[1]) ) {\n\t\t\t\tif ( nodeType === 9 ) {\n\t\t\t\t\telem = context.getElementById( m );\n\t\t\t\t\t// Check parentNode to catch when Blackberry 4.6 returns\n\t\t\t\t\t// nodes that are no longer in the document #6963\n\t\t\t\t\tif ( elem && elem.parentNode ) {\n\t\t\t\t\t\t// Handle the case where IE, Opera, and Webkit return items\n\t\t\t\t\t\t// by name instead of ID\n\t\t\t\t\t\tif ( elem.id === m ) {\n\t\t\t\t\t\t\tresults.push( elem );\n\t\t\t\t\t\t\treturn results;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\treturn results;\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// Context is not a document\n\t\t\t\t\tif ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&\n\t\t\t\t\t\tcontains( context, elem ) && elem.id === m ) {\n\t\t\t\t\t\tresults.push( elem );\n\t\t\t\t\t\treturn results;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t// Speed-up: Sizzle(\"TAG\")\n\t\t\t} else if ( match[2] ) {\n\t\t\t\tpush.apply( results, context.getElementsByTagName( selector ) );\n\t\t\t\treturn results;\n\n\t\t\t// Speed-up: Sizzle(\".CLASS\")\n\t\t\t} else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) {\n\t\t\t\tpush.apply( results, context.getElementsByClassName( m ) );\n\t\t\t\treturn results;\n\t\t\t}\n\t\t}\n\n\t\t// QSA path\n\t\tif ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {\n\t\t\tnid = old = expando;\n\t\t\tnewContext = context;\n\t\t\tnewSelector = nodeType === 9 && selector;\n\n\t\t\t// qSA works strangely on Element-rooted queries\n\t\t\t// We can work around this by specifying an extra ID on the root\n\t\t\t// and working up from there (Thanks to Andrew Dupont for the technique)\n\t\t\t// IE 8 doesn't work on object elements\n\t\t\tif ( nodeType === 1 && context.nodeName.toLowerCase() !== \"object\" ) {\n\t\t\t\tgroups = tokenize( selector );\n\n\t\t\t\tif ( (old = context.getAttribute(\"id\")) ) {\n\t\t\t\t\tnid = old.replace( rescape, \"\\\\$&\" );\n\t\t\t\t} else {\n\t\t\t\t\tcontext.setAttribute( \"id\", nid );\n\t\t\t\t}\n\t\t\t\tnid = \"[id='\" + nid + \"'] \";\n\n\t\t\t\ti = groups.length;\n\t\t\t\twhile ( i-- ) {\n\t\t\t\t\tgroups[i] = nid + toSelector( groups[i] );\n\t\t\t\t}\n\t\t\t\tnewContext = rsibling.test( selector ) && context.parentNode || context;\n\t\t\t\tnewSelector = groups.join(\",\");\n\t\t\t}\n\n\t\t\tif ( newSelector ) {\n\t\t\t\ttry {\n\t\t\t\t\tpush.apply( results,\n\t\t\t\t\t\tnewContext.querySelectorAll( newSelector )\n\t\t\t\t\t);\n\t\t\t\t\treturn results;\n\t\t\t\t} catch(qsaError) {\n\t\t\t\t} finally {\n\t\t\t\t\tif ( !old ) {\n\t\t\t\t\t\tcontext.removeAttribute(\"id\");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// All others\n\treturn select( selector.replace( rtrim, \"$1\" ), context, results, seed );\n}\n\n/**\n * Create key-value caches of limited size\n * @returns {Function(string, Object)} Returns the Object data after storing it on itself with\n *\tproperty name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)\n *\tdeleting the oldest entry\n */\nfunction createCache() {\n\tvar keys = [];\n\n\tfunction cache( key, value ) {\n\t\t// Use (key + \" \") to avoid collision with native prototype properties (see Issue #157)\n\t\tif ( keys.push( key += \" \" ) > Expr.cacheLength ) {\n\t\t\t// Only keep the most recent entries\n\t\t\tdelete cache[ keys.shift() ];\n\t\t}\n\t\treturn (cache[ key ] = value);\n\t}\n\treturn cache;\n}\n\n/**\n * Mark a function for special use by Sizzle\n * @param {Function} fn The function to mark\n */\nfunction markFunction( fn ) {\n\tfn[ expando ] = true;\n\treturn fn;\n}\n\n/**\n * Support testing using an element\n * @param {Function} fn Passed the created div and expects a boolean result\n */\nfunction assert( fn ) {\n\tvar div = document.createElement(\"div\");\n\n\ttry {\n\t\treturn !!fn( div );\n\t} catch (e) {\n\t\treturn false;\n\t} finally {\n\t\t// Remove from its parent by default\n\t\tif ( div.parentNode ) {\n\t\t\tdiv.parentNode.removeChild( div );\n\t\t}\n\t\t// release memory in IE\n\t\tdiv = null;\n\t}\n}\n\n/**\n * Adds the same handler for all of the specified attrs\n * @param {String} attrs Pipe-separated list of attributes\n * @param {Function} handler The method that will be applied\n */\nfunction addHandle( attrs, handler ) {\n\tvar arr = attrs.split(\"|\"),\n\t\ti = attrs.length;\n\n\twhile ( i-- ) {\n\t\tExpr.attrHandle[ arr[i] ] = handler;\n\t}\n}\n\n/**\n * Checks document order of two siblings\n * @param {Element} a\n * @param {Element} b\n * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b\n */\nfunction siblingCheck( a, b ) {\n\tvar cur = b && a,\n\t\tdiff = cur && a.nodeType === 1 && b.nodeType === 1 &&\n\t\t\t( ~b.sourceIndex || MAX_NEGATIVE ) -\n\t\t\t( ~a.sourceIndex || MAX_NEGATIVE );\n\n\t// Use IE sourceIndex if available on both nodes\n\tif ( diff ) {\n\t\treturn diff;\n\t}\n\n\t// Check if b follows a\n\tif ( cur ) {\n\t\twhile ( (cur = cur.nextSibling) ) {\n\t\t\tif ( cur === b ) {\n\t\t\t\treturn -1;\n\t\t\t}\n\t\t}\n\t}\n\n\treturn a ? 1 : -1;\n}\n\n/**\n * Returns a function to use in pseudos for input types\n * @param {String} type\n */\nfunction createInputPseudo( type ) {\n\treturn function( elem ) {\n\t\tvar name = elem.nodeName.toLowerCase();\n\t\treturn name === \"input\" && elem.type === type;\n\t};\n}\n\n/**\n * Returns a function to use in pseudos for buttons\n * @param {String} type\n */\nfunction createButtonPseudo( type ) {\n\treturn function( elem ) {\n\t\tvar name = elem.nodeName.toLowerCase();\n\t\treturn (name === \"input\" || name === \"button\") && elem.type === type;\n\t};\n}\n\n/**\n * Returns a function to use in pseudos for positionals\n * @param {Function} fn\n */\nfunction createPositionalPseudo( fn ) {\n\treturn markFunction(function( argument ) {\n\t\targument = +argument;\n\t\treturn markFunction(function( seed, matches ) {\n\t\t\tvar j,\n\t\t\t\tmatchIndexes = fn( [], seed.length, argument ),\n\t\t\t\ti = matchIndexes.length;\n\n\t\t\t// Match elements found at the specified indexes\n\t\t\twhile ( i-- ) {\n\t\t\t\tif ( seed[ (j = matchIndexes[i]) ] ) {\n\t\t\t\t\tseed[j] = !(matches[j] = seed[j]);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t});\n}\n\n/**\n * Detect xml\n * @param {Element|Object} elem An element or a document\n */\nisXML = Sizzle.isXML = function( elem ) {\n\t// documentElement is verified for cases where it doesn't yet exist\n\t// (such as loading iframes in IE - #4833)\n\tvar documentElement = elem && (elem.ownerDocument || elem).documentElement;\n\treturn documentElement ? documentElement.nodeName !== \"HTML\" : false;\n};\n\n// Expose support vars for convenience\nsupport = Sizzle.support = {};\n\n/**\n * Sets document-related variables once based on the current document\n * @param {Element|Object} [doc] An element or document object to use to set the document\n * @returns {Object} Returns the current document\n */\nsetDocument = Sizzle.setDocument = function( node ) {\n\tvar doc = node ? node.ownerDocument || node : preferredDoc,\n\t\tparent = doc.defaultView;\n\n\t// If no document and documentElement is available, return\n\tif ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {\n\t\treturn document;\n\t}\n\n\t// Set our document\n\tdocument = doc;\n\tdocElem = doc.documentElement;\n\n\t// Support tests\n\tdocumentIsHTML = !isXML( doc );\n\n\t// Support: IE>8\n\t// If iframe document is assigned to \"document\" variable and if iframe has been reloaded,\n\t// IE will throw \"permission denied\" error when accessing \"document\" variable, see jQuery #13936\n\t// IE6-8 do not support the defaultView property so parent will be undefined\n\tif ( parent && parent.attachEvent && parent !== parent.top ) {\n\t\tparent.attachEvent( \"onbeforeunload\", function() {\n\t\t\tsetDocument();\n\t\t});\n\t}\n\n\t/* Attributes\n\t---------------------------------------------------------------------- */\n\n\t// Support: IE<8\n\t// Verify that getAttribute really returns attributes and not properties (excepting IE8 booleans)\n\tsupport.attributes = assert(function( div ) {\n\t\tdiv.className = \"i\";\n\t\treturn !div.getAttribute(\"className\");\n\t});\n\n\t/* getElement(s)By*\n\t---------------------------------------------------------------------- */\n\n\t// Check if getElementsByTagName(\"*\") returns only elements\n\tsupport.getElementsByTagName = assert(function( div ) {\n\t\tdiv.appendChild( doc.createComment(\"\") );\n\t\treturn !div.getElementsByTagName(\"*\").length;\n\t});\n\n\t// Check if getElementsByClassName can be trusted\n\tsupport.getElementsByClassName = assert(function( div ) {\n\t\tdiv.innerHTML = \"<div class='a'></div><div class='a i'></div>\";\n\n\t\t// Support: Safari<4\n\t\t// Catch class over-caching\n\t\tdiv.firstChild.className = \"i\";\n\t\t// Support: Opera<10\n\t\t// Catch gEBCN failure to find non-leading classes\n\t\treturn div.getElementsByClassName(\"i\").length === 2;\n\t});\n\n\t// Support: IE<10\n\t// Check if getElementById returns elements by name\n\t// The broken getElementById methods don't pick up programatically-set names,\n\t// so use a roundabout getElementsByName test\n\tsupport.getById = assert(function( div ) {\n\t\tdocElem.appendChild( div ).id = expando;\n\t\treturn !doc.getElementsByName || !doc.getElementsByName( expando ).length;\n\t});\n\n\t// ID find and filter\n\tif ( support.getById ) {\n\t\tExpr.find[\"ID\"] = function( id, context ) {\n\t\t\tif ( typeof context.getElementById !== strundefined && documentIsHTML ) {\n\t\t\t\tvar m = context.getElementById( id );\n\t\t\t\t// Check parentNode to catch when Blackberry 4.6 returns\n\t\t\t\t// nodes that are no longer in the document #6963\n\t\t\t\treturn m && m.parentNode ? [m] : [];\n\t\t\t}\n\t\t};\n\t\tExpr.filter[\"ID\"] = function( id ) {\n\t\t\tvar attrId = id.replace( runescape, funescape );\n\t\t\treturn function( elem ) {\n\t\t\t\treturn elem.getAttribute(\"id\") === attrId;\n\t\t\t};\n\t\t};\n\t} else {\n\t\t// Support: IE6/7\n\t\t// getElementById is not reliable as a find shortcut\n\t\tdelete Expr.find[\"ID\"];\n\n\t\tExpr.filter[\"ID\"] =  function( id ) {\n\t\t\tvar attrId = id.replace( runescape, funescape );\n\t\t\treturn function( elem ) {\n\t\t\t\tvar node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode(\"id\");\n\t\t\t\treturn node && node.value === attrId;\n\t\t\t};\n\t\t};\n\t}\n\n\t// Tag\n\tExpr.find[\"TAG\"] = support.getElementsByTagName ?\n\t\tfunction( tag, context ) {\n\t\t\tif ( typeof context.getElementsByTagName !== strundefined ) {\n\t\t\t\treturn context.getElementsByTagName( tag );\n\t\t\t}\n\t\t} :\n\t\tfunction( tag, context ) {\n\t\t\tvar elem,\n\t\t\t\ttmp = [],\n\t\t\t\ti = 0,\n\t\t\t\tresults = context.getElementsByTagName( tag );\n\n\t\t\t// Filter out possible comments\n\t\t\tif ( tag === \"*\" ) {\n\t\t\t\twhile ( (elem = results[i++]) ) {\n\t\t\t\t\tif ( elem.nodeType === 1 ) {\n\t\t\t\t\t\ttmp.push( elem );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn tmp;\n\t\t\t}\n\t\t\treturn results;\n\t\t};\n\n\t// Class\n\tExpr.find[\"CLASS\"] = support.getElementsByClassName && function( className, context ) {\n\t\tif ( typeof context.getElementsByClassName !== strundefined && documentIsHTML ) {\n\t\t\treturn context.getElementsByClassName( className );\n\t\t}\n\t};\n\n\t/* QSA/matchesSelector\n\t---------------------------------------------------------------------- */\n\n\t// QSA and matchesSelector support\n\n\t// matchesSelector(:active) reports false when true (IE9/Opera 11.5)\n\trbuggyMatches = [];\n\n\t// qSa(:focus) reports false when true (Chrome 21)\n\t// We allow this because of a bug in IE8/9 that throws an error\n\t// whenever `document.activeElement` is accessed on an iframe\n\t// So, we allow :focus to pass through QSA all the time to avoid the IE error\n\t// See http://bugs.jquery.com/ticket/13378\n\trbuggyQSA = [];\n\n\tif ( (support.qsa = rnative.test( doc.querySelectorAll )) ) {\n\t\t// Build QSA regex\n\t\t// Regex strategy adopted from Diego Perini\n\t\tassert(function( div ) {\n\t\t\t// Select is set to empty string on purpose\n\t\t\t// This is to test IE's treatment of not explicitly\n\t\t\t// setting a boolean content attribute,\n\t\t\t// since its presence should be enough\n\t\t\t// http://bugs.jquery.com/ticket/12359\n\t\t\tdiv.innerHTML = \"<select><option selected=''></option></select>\";\n\n\t\t\t// Support: IE8\n\t\t\t// Boolean attributes and \"value\" are not treated correctly\n\t\t\tif ( !div.querySelectorAll(\"[selected]\").length ) {\n\t\t\t\trbuggyQSA.push( \"\\\\[\" + whitespace + \"*(?:value|\" + booleans + \")\" );\n\t\t\t}\n\n\t\t\t// Webkit/Opera - :checked should return selected option elements\n\t\t\t// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked\n\t\t\t// IE8 throws error here and will not see later tests\n\t\t\tif ( !div.querySelectorAll(\":checked\").length ) {\n\t\t\t\trbuggyQSA.push(\":checked\");\n\t\t\t}\n\t\t});\n\n\t\tassert(function( div ) {\n\n\t\t\t// Support: Opera 10-12/IE8\n\t\t\t// ^= $= *= and empty values\n\t\t\t// Should not select anything\n\t\t\t// Support: Windows 8 Native Apps\n\t\t\t// The type attribute is restricted during .innerHTML assignment\n\t\t\tvar input = doc.createElement(\"input\");\n\t\t\tinput.setAttribute( \"type\", \"hidden\" );\n\t\t\tdiv.appendChild( input ).setAttribute( \"t\", \"\" );\n\n\t\t\tif ( div.querySelectorAll(\"[t^='']\").length ) {\n\t\t\t\trbuggyQSA.push( \"[*^$]=\" + whitespace + \"*(?:''|\\\"\\\")\" );\n\t\t\t}\n\n\t\t\t// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)\n\t\t\t// IE8 throws error here and will not see later tests\n\t\t\tif ( !div.querySelectorAll(\":enabled\").length ) {\n\t\t\t\trbuggyQSA.push( \":enabled\", \":disabled\" );\n\t\t\t}\n\n\t\t\t// Opera 10-11 does not throw on post-comma invalid pseudos\n\t\t\tdiv.querySelectorAll(\"*,:x\");\n\t\t\trbuggyQSA.push(\",.*:\");\n\t\t});\n\t}\n\n\tif ( (support.matchesSelector = rnative.test( (matches = docElem.webkitMatchesSelector ||\n\t\tdocElem.mozMatchesSelector ||\n\t\tdocElem.oMatchesSelector ||\n\t\tdocElem.msMatchesSelector) )) ) {\n\n\t\tassert(function( div ) {\n\t\t\t// Check to see if it's possible to do matchesSelector\n\t\t\t// on a disconnected node (IE 9)\n\t\t\tsupport.disconnectedMatch = matches.call( div, \"div\" );\n\n\t\t\t// This should fail with an exception\n\t\t\t// Gecko does not error, returns false instead\n\t\t\tmatches.call( div, \"[s!='']:x\" );\n\t\t\trbuggyMatches.push( \"!=\", pseudos );\n\t\t});\n\t}\n\n\trbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join(\"|\") );\n\trbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join(\"|\") );\n\n\t/* Contains\n\t---------------------------------------------------------------------- */\n\n\t// Element contains another\n\t// Purposefully does not implement inclusive descendent\n\t// As in, an element does not contain itself\n\tcontains = rnative.test( docElem.contains ) || docElem.compareDocumentPosition ?\n\t\tfunction( a, b ) {\n\t\t\tvar adown = a.nodeType === 9 ? a.documentElement : a,\n\t\t\t\tbup = b && b.parentNode;\n\t\t\treturn a === bup || !!( bup && bup.nodeType === 1 && (\n\t\t\t\tadown.contains ?\n\t\t\t\t\tadown.contains( bup ) :\n\t\t\t\t\ta.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16\n\t\t\t));\n\t\t} :\n\t\tfunction( a, b ) {\n\t\t\tif ( b ) {\n\t\t\t\twhile ( (b = b.parentNode) ) {\n\t\t\t\t\tif ( b === a ) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn false;\n\t\t};\n\n\t/* Sorting\n\t---------------------------------------------------------------------- */\n\n\t// Document order sorting\n\tsortOrder = docElem.compareDocumentPosition ?\n\tfunction( a, b ) {\n\n\t\t// Flag for duplicate removal\n\t\tif ( a === b ) {\n\t\t\thasDuplicate = true;\n\t\t\treturn 0;\n\t\t}\n\n\t\tvar compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition( b );\n\n\t\tif ( compare ) {\n\t\t\t// Disconnected nodes\n\t\t\tif ( compare & 1 ||\n\t\t\t\t(!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) {\n\n\t\t\t\t// Choose the first element that is related to our preferred document\n\t\t\t\tif ( a === doc || contains(preferredDoc, a) ) {\n\t\t\t\t\treturn -1;\n\t\t\t\t}\n\t\t\t\tif ( b === doc || contains(preferredDoc, b) ) {\n\t\t\t\t\treturn 1;\n\t\t\t\t}\n\n\t\t\t\t// Maintain original order\n\t\t\t\treturn sortInput ?\n\t\t\t\t\t( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :\n\t\t\t\t\t0;\n\t\t\t}\n\n\t\t\treturn compare & 4 ? -1 : 1;\n\t\t}\n\n\t\t// Not directly comparable, sort on existence of method\n\t\treturn a.compareDocumentPosition ? -1 : 1;\n\t} :\n\tfunction( a, b ) {\n\t\tvar cur,\n\t\t\ti = 0,\n\t\t\taup = a.parentNode,\n\t\t\tbup = b.parentNode,\n\t\t\tap = [ a ],\n\t\t\tbp = [ b ];\n\n\t\t// Exit early if the nodes are identical\n\t\tif ( a === b ) {\n\t\t\thasDuplicate = true;\n\t\t\treturn 0;\n\n\t\t// Parentless nodes are either documents or disconnected\n\t\t} else if ( !aup || !bup ) {\n\t\t\treturn a === doc ? -1 :\n\t\t\t\tb === doc ? 1 :\n\t\t\t\taup ? -1 :\n\t\t\t\tbup ? 1 :\n\t\t\t\tsortInput ?\n\t\t\t\t( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :\n\t\t\t\t0;\n\n\t\t// If the nodes are siblings, we can do a quick check\n\t\t} else if ( aup === bup ) {\n\t\t\treturn siblingCheck( a, b );\n\t\t}\n\n\t\t// Otherwise we need full lists of their ancestors for comparison\n\t\tcur = a;\n\t\twhile ( (cur = cur.parentNode) ) {\n\t\t\tap.unshift( cur );\n\t\t}\n\t\tcur = b;\n\t\twhile ( (cur = cur.parentNode) ) {\n\t\t\tbp.unshift( cur );\n\t\t}\n\n\t\t// Walk down the tree looking for a discrepancy\n\t\twhile ( ap[i] === bp[i] ) {\n\t\t\ti++;\n\t\t}\n\n\t\treturn i ?\n\t\t\t// Do a sibling check if the nodes have a common ancestor\n\t\t\tsiblingCheck( ap[i], bp[i] ) :\n\n\t\t\t// Otherwise nodes in our document sort first\n\t\t\tap[i] === preferredDoc ? -1 :\n\t\t\tbp[i] === preferredDoc ? 1 :\n\t\t\t0;\n\t};\n\n\treturn doc;\n};\n\nSizzle.matches = function( expr, elements ) {\n\treturn Sizzle( expr, null, null, elements );\n};\n\nSizzle.matchesSelector = function( elem, expr ) {\n\t// Set document vars if needed\n\tif ( ( elem.ownerDocument || elem ) !== document ) {\n\t\tsetDocument( elem );\n\t}\n\n\t// Make sure that attribute selectors are quoted\n\texpr = expr.replace( rattributeQuotes, \"='$1']\" );\n\n\tif ( support.matchesSelector && documentIsHTML &&\n\t\t( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&\n\t\t( !rbuggyQSA     || !rbuggyQSA.test( expr ) ) ) {\n\n\t\ttry {\n\t\t\tvar ret = matches.call( elem, expr );\n\n\t\t\t// IE 9's matchesSelector returns false on disconnected nodes\n\t\t\tif ( ret || support.disconnectedMatch ||\n\t\t\t\t\t// As well, disconnected nodes are said to be in a document\n\t\t\t\t\t// fragment in IE 9\n\t\t\t\t\telem.document && elem.document.nodeType !== 11 ) {\n\t\t\t\treturn ret;\n\t\t\t}\n\t\t} catch(e) {}\n\t}\n\n\treturn Sizzle( expr, document, null, [elem] ).length > 0;\n};\n\nSizzle.contains = function( context, elem ) {\n\t// Set document vars if needed\n\tif ( ( context.ownerDocument || context ) !== document ) {\n\t\tsetDocument( context );\n\t}\n\treturn contains( context, elem );\n};\n\nSizzle.attr = function( elem, name ) {\n\t// Set document vars if needed\n\tif ( ( elem.ownerDocument || elem ) !== document ) {\n\t\tsetDocument( elem );\n\t}\n\n\tvar fn = Expr.attrHandle[ name.toLowerCase() ],\n\t\t// Don't get fooled by Object.prototype properties (jQuery #13807)\n\t\tval = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?\n\t\t\tfn( elem, name, !documentIsHTML ) :\n\t\t\tundefined;\n\n\treturn val === undefined ?\n\t\tsupport.attributes || !documentIsHTML ?\n\t\t\telem.getAttribute( name ) :\n\t\t\t(val = elem.getAttributeNode(name)) && val.specified ?\n\t\t\t\tval.value :\n\t\t\t\tnull :\n\t\tval;\n};\n\nSizzle.error = function( msg ) {\n\tthrow new Error( \"Syntax error, unrecognized expression: \" + msg );\n};\n\n/**\n * Document sorting and removing duplicates\n * @param {ArrayLike} results\n */\nSizzle.uniqueSort = function( results ) {\n\tvar elem,\n\t\tduplicates = [],\n\t\tj = 0,\n\t\ti = 0;\n\n\t// Unless we *know* we can detect duplicates, assume their presence\n\thasDuplicate = !support.detectDuplicates;\n\tsortInput = !support.sortStable && results.slice( 0 );\n\tresults.sort( sortOrder );\n\n\tif ( hasDuplicate ) {\n\t\twhile ( (elem = results[i++]) ) {\n\t\t\tif ( elem === results[ i ] ) {\n\t\t\t\tj = duplicates.push( i );\n\t\t\t}\n\t\t}\n\t\twhile ( j-- ) {\n\t\t\tresults.splice( duplicates[ j ], 1 );\n\t\t}\n\t}\n\n\treturn results;\n};\n\n/**\n * Utility function for retrieving the text value of an array of DOM nodes\n * @param {Array|Element} elem\n */\ngetText = Sizzle.getText = function( elem ) {\n\tvar node,\n\t\tret = \"\",\n\t\ti = 0,\n\t\tnodeType = elem.nodeType;\n\n\tif ( !nodeType ) {\n\t\t// If no nodeType, this is expected to be an array\n\t\tfor ( ; (node = elem[i]); i++ ) {\n\t\t\t// Do not traverse comment nodes\n\t\t\tret += getText( node );\n\t\t}\n\t} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {\n\t\t// Use textContent for elements\n\t\t// innerText usage removed for consistency of new lines (see #11153)\n\t\tif ( typeof elem.textContent === \"string\" ) {\n\t\t\treturn elem.textContent;\n\t\t} else {\n\t\t\t// Traverse its children\n\t\t\tfor ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {\n\t\t\t\tret += getText( elem );\n\t\t\t}\n\t\t}\n\t} else if ( nodeType === 3 || nodeType === 4 ) {\n\t\treturn elem.nodeValue;\n\t}\n\t// Do not include comment or processing instruction nodes\n\n\treturn ret;\n};\n\nExpr = Sizzle.selectors = {\n\n\t// Can be adjusted by the user\n\tcacheLength: 50,\n\n\tcreatePseudo: markFunction,\n\n\tmatch: matchExpr,\n\n\tattrHandle: {},\n\n\tfind: {},\n\n\trelative: {\n\t\t\">\": { dir: \"parentNode\", first: true },\n\t\t\" \": { dir: \"parentNode\" },\n\t\t\"+\": { dir: \"previousSibling\", first: true },\n\t\t\"~\": { dir: \"previousSibling\" }\n\t},\n\n\tpreFilter: {\n\t\t\"ATTR\": function( match ) {\n\t\t\tmatch[1] = match[1].replace( runescape, funescape );\n\n\t\t\t// Move the given value to match[3] whether quoted or unquoted\n\t\t\tmatch[3] = ( match[4] || match[5] || \"\" ).replace( runescape, funescape );\n\n\t\t\tif ( match[2] === \"~=\" ) {\n\t\t\t\tmatch[3] = \" \" + match[3] + \" \";\n\t\t\t}\n\n\t\t\treturn match.slice( 0, 4 );\n\t\t},\n\n\t\t\"CHILD\": function( match ) {\n\t\t\t/* matches from matchExpr[\"CHILD\"]\n\t\t\t\t1 type (only|nth|...)\n\t\t\t\t2 what (child|of-type)\n\t\t\t\t3 argument (even|odd|\\d*|\\d*n([+-]\\d+)?|...)\n\t\t\t\t4 xn-component of xn+y argument ([+-]?\\d*n|)\n\t\t\t\t5 sign of xn-component\n\t\t\t\t6 x of xn-component\n\t\t\t\t7 sign of y-component\n\t\t\t\t8 y of y-component\n\t\t\t*/\n\t\t\tmatch[1] = match[1].toLowerCase();\n\n\t\t\tif ( match[1].slice( 0, 3 ) === \"nth\" ) {\n\t\t\t\t// nth-* requires argument\n\t\t\t\tif ( !match[3] ) {\n\t\t\t\t\tSizzle.error( match[0] );\n\t\t\t\t}\n\n\t\t\t\t// numeric x and y parameters for Expr.filter.CHILD\n\t\t\t\t// remember that false/true cast respectively to 0/1\n\t\t\t\tmatch[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === \"even\" || match[3] === \"odd\" ) );\n\t\t\t\tmatch[5] = +( ( match[7] + match[8] ) || match[3] === \"odd\" );\n\n\t\t\t// other types prohibit arguments\n\t\t\t} else if ( match[3] ) {\n\t\t\t\tSizzle.error( match[0] );\n\t\t\t}\n\n\t\t\treturn match;\n\t\t},\n\n\t\t\"PSEUDO\": function( match ) {\n\t\t\tvar excess,\n\t\t\t\tunquoted = !match[5] && match[2];\n\n\t\t\tif ( matchExpr[\"CHILD\"].test( match[0] ) ) {\n\t\t\t\treturn null;\n\t\t\t}\n\n\t\t\t// Accept quoted arguments as-is\n\t\t\tif ( match[3] && match[4] !== undefined ) {\n\t\t\t\tmatch[2] = match[4];\n\n\t\t\t// Strip excess characters from unquoted arguments\n\t\t\t} else if ( unquoted && rpseudo.test( unquoted ) &&\n\t\t\t\t// Get excess from tokenize (recursively)\n\t\t\t\t(excess = tokenize( unquoted, true )) &&\n\t\t\t\t// advance to the next closing parenthesis\n\t\t\t\t(excess = unquoted.indexOf( \")\", unquoted.length - excess ) - unquoted.length) ) {\n\n\t\t\t\t// excess is a negative index\n\t\t\t\tmatch[0] = match[0].slice( 0, excess );\n\t\t\t\tmatch[2] = unquoted.slice( 0, excess );\n\t\t\t}\n\n\t\t\t// Return only captures needed by the pseudo filter method (type and argument)\n\t\t\treturn match.slice( 0, 3 );\n\t\t}\n\t},\n\n\tfilter: {\n\n\t\t\"TAG\": function( nodeNameSelector ) {\n\t\t\tvar nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();\n\t\t\treturn nodeNameSelector === \"*\" ?\n\t\t\t\tfunction() { return true; } :\n\t\t\t\tfunction( elem ) {\n\t\t\t\t\treturn elem.nodeName && elem.nodeName.toLowerCase() === nodeName;\n\t\t\t\t};\n\t\t},\n\n\t\t\"CLASS\": function( className ) {\n\t\t\tvar pattern = classCache[ className + \" \" ];\n\n\t\t\treturn pattern ||\n\t\t\t\t(pattern = new RegExp( \"(^|\" + whitespace + \")\" + className + \"(\" + whitespace + \"|$)\" )) &&\n\t\t\t\tclassCache( className, function( elem ) {\n\t\t\t\t\treturn pattern.test( typeof elem.className === \"string\" && elem.className || typeof elem.getAttribute !== strundefined && elem.getAttribute(\"class\") || \"\" );\n\t\t\t\t});\n\t\t},\n\n\t\t\"ATTR\": function( name, operator, check ) {\n\t\t\treturn function( elem ) {\n\t\t\t\tvar result = Sizzle.attr( elem, name );\n\n\t\t\t\tif ( result == null ) {\n\t\t\t\t\treturn operator === \"!=\";\n\t\t\t\t}\n\t\t\t\tif ( !operator ) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\n\t\t\t\tresult += \"\";\n\n\t\t\t\treturn operator === \"=\" ? result === check :\n\t\t\t\t\toperator === \"!=\" ? result !== check :\n\t\t\t\t\toperator === \"^=\" ? check && result.indexOf( check ) === 0 :\n\t\t\t\t\toperator === \"*=\" ? check && result.indexOf( check ) > -1 :\n\t\t\t\t\toperator === \"$=\" ? check && result.slice( -check.length ) === check :\n\t\t\t\t\toperator === \"~=\" ? ( \" \" + result + \" \" ).indexOf( check ) > -1 :\n\t\t\t\t\toperator === \"|=\" ? result === check || result.slice( 0, check.length + 1 ) === check + \"-\" :\n\t\t\t\t\tfalse;\n\t\t\t};\n\t\t},\n\n\t\t\"CHILD\": function( type, what, argument, first, last ) {\n\t\t\tvar simple = type.slice( 0, 3 ) !== \"nth\",\n\t\t\t\tforward = type.slice( -4 ) !== \"last\",\n\t\t\t\tofType = what === \"of-type\";\n\n\t\t\treturn first === 1 && last === 0 ?\n\n\t\t\t\t// Shortcut for :nth-*(n)\n\t\t\t\tfunction( elem ) {\n\t\t\t\t\treturn !!elem.parentNode;\n\t\t\t\t} :\n\n\t\t\t\tfunction( elem, context, xml ) {\n\t\t\t\t\tvar cache, outerCache, node, diff, nodeIndex, start,\n\t\t\t\t\t\tdir = simple !== forward ? \"nextSibling\" : \"previousSibling\",\n\t\t\t\t\t\tparent = elem.parentNode,\n\t\t\t\t\t\tname = ofType && elem.nodeName.toLowerCase(),\n\t\t\t\t\t\tuseCache = !xml && !ofType;\n\n\t\t\t\t\tif ( parent ) {\n\n\t\t\t\t\t\t// :(first|last|only)-(child|of-type)\n\t\t\t\t\t\tif ( simple ) {\n\t\t\t\t\t\t\twhile ( dir ) {\n\t\t\t\t\t\t\t\tnode = elem;\n\t\t\t\t\t\t\t\twhile ( (node = node[ dir ]) ) {\n\t\t\t\t\t\t\t\t\tif ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) {\n\t\t\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t// Reverse direction for :only-* (if we haven't yet done so)\n\t\t\t\t\t\t\t\tstart = dir = type === \"only\" && !start && \"nextSibling\";\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tstart = [ forward ? parent.firstChild : parent.lastChild ];\n\n\t\t\t\t\t\t// non-xml :nth-child(...) stores cache data on `parent`\n\t\t\t\t\t\tif ( forward && useCache ) {\n\t\t\t\t\t\t\t// Seek `elem` from a previously-cached index\n\t\t\t\t\t\t\touterCache = parent[ expando ] || (parent[ expando ] = {});\n\t\t\t\t\t\t\tcache = outerCache[ type ] || [];\n\t\t\t\t\t\t\tnodeIndex = cache[0] === dirruns && cache[1];\n\t\t\t\t\t\t\tdiff = cache[0] === dirruns && cache[2];\n\t\t\t\t\t\t\tnode = nodeIndex && parent.childNodes[ nodeIndex ];\n\n\t\t\t\t\t\t\twhile ( (node = ++nodeIndex && node && node[ dir ] ||\n\n\t\t\t\t\t\t\t\t// Fallback to seeking `elem` from the start\n\t\t\t\t\t\t\t\t(diff = nodeIndex = 0) || start.pop()) ) {\n\n\t\t\t\t\t\t\t\t// When found, cache indexes on `parent` and break\n\t\t\t\t\t\t\t\tif ( node.nodeType === 1 && ++diff && node === elem ) {\n\t\t\t\t\t\t\t\t\touterCache[ type ] = [ dirruns, nodeIndex, diff ];\n\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Use previously-cached element index if available\n\t\t\t\t\t\t} else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) {\n\t\t\t\t\t\t\tdiff = cache[1];\n\n\t\t\t\t\t\t// xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...)\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Use the same loop as above to seek `elem` from the start\n\t\t\t\t\t\t\twhile ( (node = ++nodeIndex && node && node[ dir ] ||\n\t\t\t\t\t\t\t\t(diff = nodeIndex = 0) || start.pop()) ) {\n\n\t\t\t\t\t\t\t\tif ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) {\n\t\t\t\t\t\t\t\t\t// Cache the index of each encountered element\n\t\t\t\t\t\t\t\t\tif ( useCache ) {\n\t\t\t\t\t\t\t\t\t\t(node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ];\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\tif ( node === elem ) {\n\t\t\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Incorporate the offset, then check against cycle size\n\t\t\t\t\t\tdiff -= last;\n\t\t\t\t\t\treturn diff === first || ( diff % first === 0 && diff / first >= 0 );\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t},\n\n\t\t\"PSEUDO\": function( pseudo, argument ) {\n\t\t\t// pseudo-class names are case-insensitive\n\t\t\t// http://www.w3.org/TR/selectors/#pseudo-classes\n\t\t\t// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters\n\t\t\t// Remember that setFilters inherits from pseudos\n\t\t\tvar args,\n\t\t\t\tfn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||\n\t\t\t\t\tSizzle.error( \"unsupported pseudo: \" + pseudo );\n\n\t\t\t// The user may use createPseudo to indicate that\n\t\t\t// arguments are needed to create the filter function\n\t\t\t// just as Sizzle does\n\t\t\tif ( fn[ expando ] ) {\n\t\t\t\treturn fn( argument );\n\t\t\t}\n\n\t\t\t// But maintain support for old signatures\n\t\t\tif ( fn.length > 1 ) {\n\t\t\t\targs = [ pseudo, pseudo, \"\", argument ];\n\t\t\t\treturn Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?\n\t\t\t\t\tmarkFunction(function( seed, matches ) {\n\t\t\t\t\t\tvar idx,\n\t\t\t\t\t\t\tmatched = fn( seed, argument ),\n\t\t\t\t\t\t\ti = matched.length;\n\t\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\t\tidx = indexOf.call( seed, matched[i] );\n\t\t\t\t\t\t\tseed[ idx ] = !( matches[ idx ] = matched[i] );\n\t\t\t\t\t\t}\n\t\t\t\t\t}) :\n\t\t\t\t\tfunction( elem ) {\n\t\t\t\t\t\treturn fn( elem, 0, args );\n\t\t\t\t\t};\n\t\t\t}\n\n\t\t\treturn fn;\n\t\t}\n\t},\n\n\tpseudos: {\n\t\t// Potentially complex pseudos\n\t\t\"not\": markFunction(function( selector ) {\n\t\t\t// Trim the selector passed to compile\n\t\t\t// to avoid treating leading and trailing\n\t\t\t// spaces as combinators\n\t\t\tvar input = [],\n\t\t\t\tresults = [],\n\t\t\t\tmatcher = compile( selector.replace( rtrim, \"$1\" ) );\n\n\t\t\treturn matcher[ expando ] ?\n\t\t\t\tmarkFunction(function( seed, matches, context, xml ) {\n\t\t\t\t\tvar elem,\n\t\t\t\t\t\tunmatched = matcher( seed, null, xml, [] ),\n\t\t\t\t\t\ti = seed.length;\n\n\t\t\t\t\t// Match elements unmatched by `matcher`\n\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\tif ( (elem = unmatched[i]) ) {\n\t\t\t\t\t\t\tseed[i] = !(matches[i] = elem);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}) :\n\t\t\t\tfunction( elem, context, xml ) {\n\t\t\t\t\tinput[0] = elem;\n\t\t\t\t\tmatcher( input, null, xml, results );\n\t\t\t\t\treturn !results.pop();\n\t\t\t\t};\n\t\t}),\n\n\t\t\"has\": markFunction(function( selector ) {\n\t\t\treturn function( elem ) {\n\t\t\t\treturn Sizzle( selector, elem ).length > 0;\n\t\t\t};\n\t\t}),\n\n\t\t\"contains\": markFunction(function( text ) {\n\t\t\treturn function( elem ) {\n\t\t\t\treturn ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;\n\t\t\t};\n\t\t}),\n\n\t\t// \"Whether an element is represented by a :lang() selector\n\t\t// is based solely on the element's language value\n\t\t// being equal to the identifier C,\n\t\t// or beginning with the identifier C immediately followed by \"-\".\n\t\t// The matching of C against the element's language value is performed case-insensitively.\n\t\t// The identifier C does not have to be a valid language name.\"\n\t\t// http://www.w3.org/TR/selectors/#lang-pseudo\n\t\t\"lang\": markFunction( function( lang ) {\n\t\t\t// lang value must be a valid identifier\n\t\t\tif ( !ridentifier.test(lang || \"\") ) {\n\t\t\t\tSizzle.error( \"unsupported lang: \" + lang );\n\t\t\t}\n\t\t\tlang = lang.replace( runescape, funescape ).toLowerCase();\n\t\t\treturn function( elem ) {\n\t\t\t\tvar elemLang;\n\t\t\t\tdo {\n\t\t\t\t\tif ( (elemLang = documentIsHTML ?\n\t\t\t\t\t\telem.lang :\n\t\t\t\t\t\telem.getAttribute(\"xml:lang\") || elem.getAttribute(\"lang\")) ) {\n\n\t\t\t\t\t\telemLang = elemLang.toLowerCase();\n\t\t\t\t\t\treturn elemLang === lang || elemLang.indexOf( lang + \"-\" ) === 0;\n\t\t\t\t\t}\n\t\t\t\t} while ( (elem = elem.parentNode) && elem.nodeType === 1 );\n\t\t\t\treturn false;\n\t\t\t};\n\t\t}),\n\n\t\t// Miscellaneous\n\t\t\"target\": function( elem ) {\n\t\t\tvar hash = window.location && window.location.hash;\n\t\t\treturn hash && hash.slice( 1 ) === elem.id;\n\t\t},\n\n\t\t\"root\": function( elem ) {\n\t\t\treturn elem === docElem;\n\t\t},\n\n\t\t\"focus\": function( elem ) {\n\t\t\treturn elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);\n\t\t},\n\n\t\t// Boolean properties\n\t\t\"enabled\": function( elem ) {\n\t\t\treturn elem.disabled === false;\n\t\t},\n\n\t\t\"disabled\": function( elem ) {\n\t\t\treturn elem.disabled === true;\n\t\t},\n\n\t\t\"checked\": function( elem ) {\n\t\t\t// In CSS3, :checked should return both checked and selected elements\n\t\t\t// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked\n\t\t\tvar nodeName = elem.nodeName.toLowerCase();\n\t\t\treturn (nodeName === \"input\" && !!elem.checked) || (nodeName === \"option\" && !!elem.selected);\n\t\t},\n\n\t\t\"selected\": function( elem ) {\n\t\t\t// Accessing this property makes selected-by-default\n\t\t\t// options in Safari work properly\n\t\t\tif ( elem.parentNode ) {\n\t\t\t\telem.parentNode.selectedIndex;\n\t\t\t}\n\n\t\t\treturn elem.selected === true;\n\t\t},\n\n\t\t// Contents\n\t\t\"empty\": function( elem ) {\n\t\t\t// http://www.w3.org/TR/selectors/#empty-pseudo\n\t\t\t// :empty is only affected by element nodes and content nodes(including text(3), cdata(4)),\n\t\t\t//   not comment, processing instructions, or others\n\t\t\t// Thanks to Diego Perini for the nodeName shortcut\n\t\t\t//   Greater than \"@\" means alpha characters (specifically not starting with \"#\" or \"?\")\n\t\t\tfor ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {\n\t\t\t\tif ( elem.nodeName > \"@\" || elem.nodeType === 3 || elem.nodeType === 4 ) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t},\n\n\t\t\"parent\": function( elem ) {\n\t\t\treturn !Expr.pseudos[\"empty\"]( elem );\n\t\t},\n\n\t\t// Element/input types\n\t\t\"header\": function( elem ) {\n\t\t\treturn rheader.test( elem.nodeName );\n\t\t},\n\n\t\t\"input\": function( elem ) {\n\t\t\treturn rinputs.test( elem.nodeName );\n\t\t},\n\n\t\t\"button\": function( elem ) {\n\t\t\tvar name = elem.nodeName.toLowerCase();\n\t\t\treturn name === \"input\" && elem.type === \"button\" || name === \"button\";\n\t\t},\n\n\t\t\"text\": function( elem ) {\n\t\t\tvar attr;\n\t\t\t// IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)\n\t\t\t// use getAttribute instead to test this case\n\t\t\treturn elem.nodeName.toLowerCase() === \"input\" &&\n\t\t\t\telem.type === \"text\" &&\n\t\t\t\t( (attr = elem.getAttribute(\"type\")) == null || attr.toLowerCase() === elem.type );\n\t\t},\n\n\t\t// Position-in-collection\n\t\t\"first\": createPositionalPseudo(function() {\n\t\t\treturn [ 0 ];\n\t\t}),\n\n\t\t\"last\": createPositionalPseudo(function( matchIndexes, length ) {\n\t\t\treturn [ length - 1 ];\n\t\t}),\n\n\t\t\"eq\": createPositionalPseudo(function( matchIndexes, length, argument ) {\n\t\t\treturn [ argument < 0 ? argument + length : argument ];\n\t\t}),\n\n\t\t\"even\": createPositionalPseudo(function( matchIndexes, length ) {\n\t\t\tvar i = 0;\n\t\t\tfor ( ; i < length; i += 2 ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t}),\n\n\t\t\"odd\": createPositionalPseudo(function( matchIndexes, length ) {\n\t\t\tvar i = 1;\n\t\t\tfor ( ; i < length; i += 2 ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t}),\n\n\t\t\"lt\": createPositionalPseudo(function( matchIndexes, length, argument ) {\n\t\t\tvar i = argument < 0 ? argument + length : argument;\n\t\t\tfor ( ; --i >= 0; ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t}),\n\n\t\t\"gt\": createPositionalPseudo(function( matchIndexes, length, argument ) {\n\t\t\tvar i = argument < 0 ? argument + length : argument;\n\t\t\tfor ( ; ++i < length; ) {\n\t\t\t\tmatchIndexes.push( i );\n\t\t\t}\n\t\t\treturn matchIndexes;\n\t\t})\n\t}\n};\n\nExpr.pseudos[\"nth\"] = Expr.pseudos[\"eq\"];\n\n// Add button/input type pseudos\nfor ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {\n\tExpr.pseudos[ i ] = createInputPseudo( i );\n}\nfor ( i in { submit: true, reset: true } ) {\n\tExpr.pseudos[ i ] = createButtonPseudo( i );\n}\n\n// Easy API for creating new setFilters\nfunction setFilters() {}\nsetFilters.prototype = Expr.filters = Expr.pseudos;\nExpr.setFilters = new setFilters();\n\nfunction tokenize( selector, parseOnly ) {\n\tvar matched, match, tokens, type,\n\t\tsoFar, groups, preFilters,\n\t\tcached = tokenCache[ selector + \" \" ];\n\n\tif ( cached ) {\n\t\treturn parseOnly ? 0 : cached.slice( 0 );\n\t}\n\n\tsoFar = selector;\n\tgroups = [];\n\tpreFilters = Expr.preFilter;\n\n\twhile ( soFar ) {\n\n\t\t// Comma and first run\n\t\tif ( !matched || (match = rcomma.exec( soFar )) ) {\n\t\t\tif ( match ) {\n\t\t\t\t// Don't consume trailing commas as valid\n\t\t\t\tsoFar = soFar.slice( match[0].length ) || soFar;\n\t\t\t}\n\t\t\tgroups.push( tokens = [] );\n\t\t}\n\n\t\tmatched = false;\n\n\t\t// Combinators\n\t\tif ( (match = rcombinators.exec( soFar )) ) {\n\t\t\tmatched = match.shift();\n\t\t\ttokens.push({\n\t\t\t\tvalue: matched,\n\t\t\t\t// Cast descendant combinators to space\n\t\t\t\ttype: match[0].replace( rtrim, \" \" )\n\t\t\t});\n\t\t\tsoFar = soFar.slice( matched.length );\n\t\t}\n\n\t\t// Filters\n\t\tfor ( type in Expr.filter ) {\n\t\t\tif ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||\n\t\t\t\t(match = preFilters[ type ]( match ))) ) {\n\t\t\t\tmatched = match.shift();\n\t\t\t\ttokens.push({\n\t\t\t\t\tvalue: matched,\n\t\t\t\t\ttype: type,\n\t\t\t\t\tmatches: match\n\t\t\t\t});\n\t\t\t\tsoFar = soFar.slice( matched.length );\n\t\t\t}\n\t\t}\n\n\t\tif ( !matched ) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t// Return the length of the invalid excess\n\t// if we're just parsing\n\t// Otherwise, throw an error or return tokens\n\treturn parseOnly ?\n\t\tsoFar.length :\n\t\tsoFar ?\n\t\t\tSizzle.error( selector ) :\n\t\t\t// Cache the tokens\n\t\t\ttokenCache( selector, groups ).slice( 0 );\n}\n\nfunction toSelector( tokens ) {\n\tvar i = 0,\n\t\tlen = tokens.length,\n\t\tselector = \"\";\n\tfor ( ; i < len; i++ ) {\n\t\tselector += tokens[i].value;\n\t}\n\treturn selector;\n}\n\nfunction addCombinator( matcher, combinator, base ) {\n\tvar dir = combinator.dir,\n\t\tcheckNonElements = base && dir === \"parentNode\",\n\t\tdoneName = done++;\n\n\treturn combinator.first ?\n\t\t// Check against closest ancestor/preceding element\n\t\tfunction( elem, context, xml ) {\n\t\t\twhile ( (elem = elem[ dir ]) ) {\n\t\t\t\tif ( elem.nodeType === 1 || checkNonElements ) {\n\t\t\t\t\treturn matcher( elem, context, xml );\n\t\t\t\t}\n\t\t\t}\n\t\t} :\n\n\t\t// Check against all ancestor/preceding elements\n\t\tfunction( elem, context, xml ) {\n\t\t\tvar data, cache, outerCache,\n\t\t\t\tdirkey = dirruns + \" \" + doneName;\n\n\t\t\t// We can't set arbitrary data on XML nodes, so they don't benefit from dir caching\n\t\t\tif ( xml ) {\n\t\t\t\twhile ( (elem = elem[ dir ]) ) {\n\t\t\t\t\tif ( elem.nodeType === 1 || checkNonElements ) {\n\t\t\t\t\t\tif ( matcher( elem, context, xml ) ) {\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\twhile ( (elem = elem[ dir ]) ) {\n\t\t\t\t\tif ( elem.nodeType === 1 || checkNonElements ) {\n\t\t\t\t\t\touterCache = elem[ expando ] || (elem[ expando ] = {});\n\t\t\t\t\t\tif ( (cache = outerCache[ dir ]) && cache[0] === dirkey ) {\n\t\t\t\t\t\t\tif ( (data = cache[1]) === true || data === cachedruns ) {\n\t\t\t\t\t\t\t\treturn data === true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tcache = outerCache[ dir ] = [ dirkey ];\n\t\t\t\t\t\t\tcache[1] = matcher( elem, context, xml ) || cachedruns;\n\t\t\t\t\t\t\tif ( cache[1] === true ) {\n\t\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t};\n}\n\nfunction elementMatcher( matchers ) {\n\treturn matchers.length > 1 ?\n\t\tfunction( elem, context, xml ) {\n\t\t\tvar i = matchers.length;\n\t\t\twhile ( i-- ) {\n\t\t\t\tif ( !matchers[i]( elem, context, xml ) ) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn true;\n\t\t} :\n\t\tmatchers[0];\n}\n\nfunction condense( unmatched, map, filter, context, xml ) {\n\tvar elem,\n\t\tnewUnmatched = [],\n\t\ti = 0,\n\t\tlen = unmatched.length,\n\t\tmapped = map != null;\n\n\tfor ( ; i < len; i++ ) {\n\t\tif ( (elem = unmatched[i]) ) {\n\t\t\tif ( !filter || filter( elem, context, xml ) ) {\n\t\t\t\tnewUnmatched.push( elem );\n\t\t\t\tif ( mapped ) {\n\t\t\t\t\tmap.push( i );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn newUnmatched;\n}\n\nfunction setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {\n\tif ( postFilter && !postFilter[ expando ] ) {\n\t\tpostFilter = setMatcher( postFilter );\n\t}\n\tif ( postFinder && !postFinder[ expando ] ) {\n\t\tpostFinder = setMatcher( postFinder, postSelector );\n\t}\n\treturn markFunction(function( seed, results, context, xml ) {\n\t\tvar temp, i, elem,\n\t\t\tpreMap = [],\n\t\t\tpostMap = [],\n\t\t\tpreexisting = results.length,\n\n\t\t\t// Get initial elements from seed or context\n\t\t\telems = seed || multipleContexts( selector || \"*\", context.nodeType ? [ context ] : context, [] ),\n\n\t\t\t// Prefilter to get matcher input, preserving a map for seed-results synchronization\n\t\t\tmatcherIn = preFilter && ( seed || !selector ) ?\n\t\t\t\tcondense( elems, preMap, preFilter, context, xml ) :\n\t\t\t\telems,\n\n\t\t\tmatcherOut = matcher ?\n\t\t\t\t// If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,\n\t\t\t\tpostFinder || ( seed ? preFilter : preexisting || postFilter ) ?\n\n\t\t\t\t\t// ...intermediate processing is necessary\n\t\t\t\t\t[] :\n\n\t\t\t\t\t// ...otherwise use results directly\n\t\t\t\t\tresults :\n\t\t\t\tmatcherIn;\n\n\t\t// Find primary matches\n\t\tif ( matcher ) {\n\t\t\tmatcher( matcherIn, matcherOut, context, xml );\n\t\t}\n\n\t\t// Apply postFilter\n\t\tif ( postFilter ) {\n\t\t\ttemp = condense( matcherOut, postMap );\n\t\t\tpostFilter( temp, [], context, xml );\n\n\t\t\t// Un-match failing elements by moving them back to matcherIn\n\t\t\ti = temp.length;\n\t\t\twhile ( i-- ) {\n\t\t\t\tif ( (elem = temp[i]) ) {\n\t\t\t\t\tmatcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif ( seed ) {\n\t\t\tif ( postFinder || preFilter ) {\n\t\t\t\tif ( postFinder ) {\n\t\t\t\t\t// Get the final matcherOut by condensing this intermediate into postFinder contexts\n\t\t\t\t\ttemp = [];\n\t\t\t\t\ti = matcherOut.length;\n\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\tif ( (elem = matcherOut[i]) ) {\n\t\t\t\t\t\t\t// Restore matcherIn since elem is not yet a final match\n\t\t\t\t\t\t\ttemp.push( (matcherIn[i] = elem) );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tpostFinder( null, (matcherOut = []), temp, xml );\n\t\t\t\t}\n\n\t\t\t\t// Move matched elements from seed to results to keep them synchronized\n\t\t\t\ti = matcherOut.length;\n\t\t\t\twhile ( i-- ) {\n\t\t\t\t\tif ( (elem = matcherOut[i]) &&\n\t\t\t\t\t\t(temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) {\n\n\t\t\t\t\t\tseed[temp] = !(results[temp] = elem);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t// Add elements to results, through postFinder if defined\n\t\t} else {\n\t\t\tmatcherOut = condense(\n\t\t\t\tmatcherOut === results ?\n\t\t\t\t\tmatcherOut.splice( preexisting, matcherOut.length ) :\n\t\t\t\t\tmatcherOut\n\t\t\t);\n\t\t\tif ( postFinder ) {\n\t\t\t\tpostFinder( null, results, matcherOut, xml );\n\t\t\t} else {\n\t\t\t\tpush.apply( results, matcherOut );\n\t\t\t}\n\t\t}\n\t});\n}\n\nfunction matcherFromTokens( tokens ) {\n\tvar checkContext, matcher, j,\n\t\tlen = tokens.length,\n\t\tleadingRelative = Expr.relative[ tokens[0].type ],\n\t\timplicitRelative = leadingRelative || Expr.relative[\" \"],\n\t\ti = leadingRelative ? 1 : 0,\n\n\t\t// The foundational matcher ensures that elements are reachable from top-level context(s)\n\t\tmatchContext = addCombinator( function( elem ) {\n\t\t\treturn elem === checkContext;\n\t\t}, implicitRelative, true ),\n\t\tmatchAnyContext = addCombinator( function( elem ) {\n\t\t\treturn indexOf.call( checkContext, elem ) > -1;\n\t\t}, implicitRelative, true ),\n\t\tmatchers = [ function( elem, context, xml ) {\n\t\t\treturn ( !leadingRelative && ( xml || context !== outermostContext ) ) || (\n\t\t\t\t(checkContext = context).nodeType ?\n\t\t\t\t\tmatchContext( elem, context, xml ) :\n\t\t\t\t\tmatchAnyContext( elem, context, xml ) );\n\t\t} ];\n\n\tfor ( ; i < len; i++ ) {\n\t\tif ( (matcher = Expr.relative[ tokens[i].type ]) ) {\n\t\t\tmatchers = [ addCombinator(elementMatcher( matchers ), matcher) ];\n\t\t} else {\n\t\t\tmatcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );\n\n\t\t\t// Return special upon seeing a positional matcher\n\t\t\tif ( matcher[ expando ] ) {\n\t\t\t\t// Find the next relative operator (if any) for proper handling\n\t\t\t\tj = ++i;\n\t\t\t\tfor ( ; j < len; j++ ) {\n\t\t\t\t\tif ( Expr.relative[ tokens[j].type ] ) {\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn setMatcher(\n\t\t\t\t\ti > 1 && elementMatcher( matchers ),\n\t\t\t\t\ti > 1 && toSelector(\n\t\t\t\t\t\t// If the preceding token was a descendant combinator, insert an implicit any-element `*`\n\t\t\t\t\t\ttokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === \" \" ? \"*\" : \"\" })\n\t\t\t\t\t).replace( rtrim, \"$1\" ),\n\t\t\t\t\tmatcher,\n\t\t\t\t\ti < j && matcherFromTokens( tokens.slice( i, j ) ),\n\t\t\t\t\tj < len && matcherFromTokens( (tokens = tokens.slice( j )) ),\n\t\t\t\t\tj < len && toSelector( tokens )\n\t\t\t\t);\n\t\t\t}\n\t\t\tmatchers.push( matcher );\n\t\t}\n\t}\n\n\treturn elementMatcher( matchers );\n}\n\nfunction matcherFromGroupMatchers( elementMatchers, setMatchers ) {\n\t// A counter to specify which element is currently being matched\n\tvar matcherCachedRuns = 0,\n\t\tbySet = setMatchers.length > 0,\n\t\tbyElement = elementMatchers.length > 0,\n\t\tsuperMatcher = function( seed, context, xml, results, expandContext ) {\n\t\t\tvar elem, j, matcher,\n\t\t\t\tsetMatched = [],\n\t\t\t\tmatchedCount = 0,\n\t\t\t\ti = \"0\",\n\t\t\t\tunmatched = seed && [],\n\t\t\t\toutermost = expandContext != null,\n\t\t\t\tcontextBackup = outermostContext,\n\t\t\t\t// We must always have either seed elements or context\n\t\t\t\telems = seed || byElement && Expr.find[\"TAG\"]( \"*\", expandContext && context.parentNode || context ),\n\t\t\t\t// Use integer dirruns iff this is the outermost matcher\n\t\t\t\tdirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1);\n\n\t\t\tif ( outermost ) {\n\t\t\t\toutermostContext = context !== document && context;\n\t\t\t\tcachedruns = matcherCachedRuns;\n\t\t\t}\n\n\t\t\t// Add elements passing elementMatchers directly to results\n\t\t\t// Keep `i` a string if there are no elements so `matchedCount` will be \"00\" below\n\t\t\tfor ( ; (elem = elems[i]) != null; i++ ) {\n\t\t\t\tif ( byElement && elem ) {\n\t\t\t\t\tj = 0;\n\t\t\t\t\twhile ( (matcher = elementMatchers[j++]) ) {\n\t\t\t\t\t\tif ( matcher( elem, context, xml ) ) {\n\t\t\t\t\t\t\tresults.push( elem );\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif ( outermost ) {\n\t\t\t\t\t\tdirruns = dirrunsUnique;\n\t\t\t\t\t\tcachedruns = ++matcherCachedRuns;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Track unmatched elements for set filters\n\t\t\t\tif ( bySet ) {\n\t\t\t\t\t// They will have gone through all possible matchers\n\t\t\t\t\tif ( (elem = !matcher && elem) ) {\n\t\t\t\t\t\tmatchedCount--;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Lengthen the array for every element, matched or not\n\t\t\t\t\tif ( seed ) {\n\t\t\t\t\t\tunmatched.push( elem );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Apply set filters to unmatched elements\n\t\t\tmatchedCount += i;\n\t\t\tif ( bySet && i !== matchedCount ) {\n\t\t\t\tj = 0;\n\t\t\t\twhile ( (matcher = setMatchers[j++]) ) {\n\t\t\t\t\tmatcher( unmatched, setMatched, context, xml );\n\t\t\t\t}\n\n\t\t\t\tif ( seed ) {\n\t\t\t\t\t// Reintegrate element matches to eliminate the need for sorting\n\t\t\t\t\tif ( matchedCount > 0 ) {\n\t\t\t\t\t\twhile ( i-- ) {\n\t\t\t\t\t\t\tif ( !(unmatched[i] || setMatched[i]) ) {\n\t\t\t\t\t\t\t\tsetMatched[i] = pop.call( results );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Discard index placeholder values to get only actual matches\n\t\t\t\t\tsetMatched = condense( setMatched );\n\t\t\t\t}\n\n\t\t\t\t// Add matches to results\n\t\t\t\tpush.apply( results, setMatched );\n\n\t\t\t\t// Seedless set matches succeeding multiple successful matchers stipulate sorting\n\t\t\t\tif ( outermost && !seed && setMatched.length > 0 &&\n\t\t\t\t\t( matchedCount + setMatchers.length ) > 1 ) {\n\n\t\t\t\t\tSizzle.uniqueSort( results );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Override manipulation of globals by nested matchers\n\t\t\tif ( outermost ) {\n\t\t\t\tdirruns = dirrunsUnique;\n\t\t\t\toutermostContext = contextBackup;\n\t\t\t}\n\n\t\t\treturn unmatched;\n\t\t};\n\n\treturn bySet ?\n\t\tmarkFunction( superMatcher ) :\n\t\tsuperMatcher;\n}\n\ncompile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) {\n\tvar i,\n\t\tsetMatchers = [],\n\t\telementMatchers = [],\n\t\tcached = compilerCache[ selector + \" \" ];\n\n\tif ( !cached ) {\n\t\t// Generate a function of recursive functions that can be used to check each element\n\t\tif ( !group ) {\n\t\t\tgroup = tokenize( selector );\n\t\t}\n\t\ti = group.length;\n\t\twhile ( i-- ) {\n\t\t\tcached = matcherFromTokens( group[i] );\n\t\t\tif ( cached[ expando ] ) {\n\t\t\t\tsetMatchers.push( cached );\n\t\t\t} else {\n\t\t\t\telementMatchers.push( cached );\n\t\t\t}\n\t\t}\n\n\t\t// Cache the compiled function\n\t\tcached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );\n\t}\n\treturn cached;\n};\n\nfunction multipleContexts( selector, contexts, results ) {\n\tvar i = 0,\n\t\tlen = contexts.length;\n\tfor ( ; i < len; i++ ) {\n\t\tSizzle( selector, contexts[i], results );\n\t}\n\treturn results;\n}\n\nfunction select( selector, context, results, seed ) {\n\tvar i, tokens, token, type, find,\n\t\tmatch = tokenize( selector );\n\n\tif ( !seed ) {\n\t\t// Try to minimize operations if there is only one group\n\t\tif ( match.length === 1 ) {\n\n\t\t\t// Take a shortcut and set the context if the root selector is an ID\n\t\t\ttokens = match[0] = match[0].slice( 0 );\n\t\t\tif ( tokens.length > 2 && (token = tokens[0]).type === \"ID\" &&\n\t\t\t\t\tsupport.getById && context.nodeType === 9 && documentIsHTML &&\n\t\t\t\t\tExpr.relative[ tokens[1].type ] ) {\n\n\t\t\t\tcontext = ( Expr.find[\"ID\"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];\n\t\t\t\tif ( !context ) {\n\t\t\t\t\treturn results;\n\t\t\t\t}\n\t\t\t\tselector = selector.slice( tokens.shift().value.length );\n\t\t\t}\n\n\t\t\t// Fetch a seed set for right-to-left matching\n\t\t\ti = matchExpr[\"needsContext\"].test( selector ) ? 0 : tokens.length;\n\t\t\twhile ( i-- ) {\n\t\t\t\ttoken = tokens[i];\n\n\t\t\t\t// Abort if we hit a combinator\n\t\t\t\tif ( Expr.relative[ (type = token.type) ] ) {\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tif ( (find = Expr.find[ type ]) ) {\n\t\t\t\t\t// Search, expanding context for leading sibling combinators\n\t\t\t\t\tif ( (seed = find(\n\t\t\t\t\t\ttoken.matches[0].replace( runescape, funescape ),\n\t\t\t\t\t\trsibling.test( tokens[0].type ) && context.parentNode || context\n\t\t\t\t\t)) ) {\n\n\t\t\t\t\t\t// If seed is empty or no tokens remain, we can return early\n\t\t\t\t\t\ttokens.splice( i, 1 );\n\t\t\t\t\t\tselector = seed.length && toSelector( tokens );\n\t\t\t\t\t\tif ( !selector ) {\n\t\t\t\t\t\t\tpush.apply( results, seed );\n\t\t\t\t\t\t\treturn results;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Compile and execute a filtering function\n\t// Provide `match` to avoid retokenization if we modified the selector above\n\tcompile( selector, match )(\n\t\tseed,\n\t\tcontext,\n\t\t!documentIsHTML,\n\t\tresults,\n\t\trsibling.test( selector )\n\t);\n\treturn results;\n}\n\n// One-time assignments\n\n// Sort stability\nsupport.sortStable = expando.split(\"\").sort( sortOrder ).join(\"\") === expando;\n\n// Support: Chrome<14\n// Always assume duplicates if they aren't passed to the comparison function\nsupport.detectDuplicates = hasDuplicate;\n\n// Initialize against the default document\nsetDocument();\n\n// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)\n// Detached nodes confoundingly follow *each other*\nsupport.sortDetached = assert(function( div1 ) {\n\t// Should return 1, but returns 4 (following)\n\treturn div1.compareDocumentPosition( document.createElement(\"div\") ) & 1;\n});\n\n// Support: IE<8\n// Prevent attribute/property \"interpolation\"\n// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx\nif ( !assert(function( div ) {\n\tdiv.innerHTML = \"<a href='#'></a>\";\n\treturn div.firstChild.getAttribute(\"href\") === \"#\" ;\n}) ) {\n\taddHandle( \"type|href|height|width\", function( elem, name, isXML ) {\n\t\tif ( !isXML ) {\n\t\t\treturn elem.getAttribute( name, name.toLowerCase() === \"type\" ? 1 : 2 );\n\t\t}\n\t});\n}\n\n// Support: IE<9\n// Use defaultValue in place of getAttribute(\"value\")\nif ( !support.attributes || !assert(function( div ) {\n\tdiv.innerHTML = \"<input/>\";\n\tdiv.firstChild.setAttribute( \"value\", \"\" );\n\treturn div.firstChild.getAttribute( \"value\" ) === \"\";\n}) ) {\n\taddHandle( \"value\", function( elem, name, isXML ) {\n\t\tif ( !isXML && elem.nodeName.toLowerCase() === \"input\" ) {\n\t\t\treturn elem.defaultValue;\n\t\t}\n\t});\n}\n\n// Support: IE<9\n// Use getAttributeNode to fetch booleans when getAttribute lies\nif ( !assert(function( div ) {\n\treturn div.getAttribute(\"disabled\") == null;\n}) ) {\n\taddHandle( booleans, function( elem, name, isXML ) {\n\t\tvar val;\n\t\tif ( !isXML ) {\n\t\t\treturn (val = elem.getAttributeNode( name )) && val.specified ?\n\t\t\t\tval.value :\n\t\t\t\telem[ name ] === true ? name.toLowerCase() : null;\n\t\t}\n\t});\n}\n\njQuery.find = Sizzle;\njQuery.expr = Sizzle.selectors;\njQuery.expr[\":\"] = jQuery.expr.pseudos;\njQuery.unique = Sizzle.uniqueSort;\njQuery.text = Sizzle.getText;\njQuery.isXMLDoc = Sizzle.isXML;\njQuery.contains = Sizzle.contains;\n\n\n})( window );\n// String to Object options format cache\nvar optionsCache = {};\n\n// Convert String-formatted options into Object-formatted ones and store in cache\nfunction createOptions( options ) {\n\tvar object = optionsCache[ options ] = {};\n\tjQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) {\n\t\tobject[ flag ] = true;\n\t});\n\treturn object;\n}\n\n/*\n * Create a callback list using the following parameters:\n *\n *\toptions: an optional list of space-separated options that will change how\n *\t\t\tthe callback list behaves or a more traditional option object\n *\n * By default a callback list will act like an event callback list and can be\n * \"fired\" multiple times.\n *\n * Possible options:\n *\n *\tonce:\t\t\twill ensure the callback list can only be fired once (like a Deferred)\n *\n *\tmemory:\t\t\twill keep track of previous values and will call any callback added\n *\t\t\t\t\tafter the list has been fired right away with the latest \"memorized\"\n *\t\t\t\t\tvalues (like a Deferred)\n *\n *\tunique:\t\t\twill ensure a callback can only be added once (no duplicate in the list)\n *\n *\tstopOnFalse:\tinterrupt callings when a callback returns false\n *\n */\njQuery.Callbacks = function( options ) {\n\n\t// Convert options from String-formatted to Object-formatted if needed\n\t// (we check in cache first)\n\toptions = typeof options === \"string\" ?\n\t\t( optionsCache[ options ] || createOptions( options ) ) :\n\t\tjQuery.extend( {}, options );\n\n\tvar // Flag to know if list is currently firing\n\t\tfiring,\n\t\t// Last fire value (for non-forgettable lists)\n\t\tmemory,\n\t\t// Flag to know if list was already fired\n\t\tfired,\n\t\t// End of the loop when firing\n\t\tfiringLength,\n\t\t// Index of currently firing callback (modified by remove if needed)\n\t\tfiringIndex,\n\t\t// First callback to fire (used internally by add and fireWith)\n\t\tfiringStart,\n\t\t// Actual callback list\n\t\tlist = [],\n\t\t// Stack of fire calls for repeatable lists\n\t\tstack = !options.once && [],\n\t\t// Fire callbacks\n\t\tfire = function( data ) {\n\t\t\tmemory = options.memory && data;\n\t\t\tfired = true;\n\t\t\tfiringIndex = firingStart || 0;\n\t\t\tfiringStart = 0;\n\t\t\tfiringLength = list.length;\n\t\t\tfiring = true;\n\t\t\tfor ( ; list && firingIndex < firingLength; firingIndex++ ) {\n\t\t\t\tif ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {\n\t\t\t\t\tmemory = false; // To prevent further calls using add\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tfiring = false;\n\t\t\tif ( list ) {\n\t\t\t\tif ( stack ) {\n\t\t\t\t\tif ( stack.length ) {\n\t\t\t\t\t\tfire( stack.shift() );\n\t\t\t\t\t}\n\t\t\t\t} else if ( memory ) {\n\t\t\t\t\tlist = [];\n\t\t\t\t} else {\n\t\t\t\t\tself.disable();\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\t\t// Actual Callbacks object\n\t\tself = {\n\t\t\t// Add a callback or a collection of callbacks to the list\n\t\t\tadd: function() {\n\t\t\t\tif ( list ) {\n\t\t\t\t\t// First, we save the current length\n\t\t\t\t\tvar start = list.length;\n\t\t\t\t\t(function add( args ) {\n\t\t\t\t\t\tjQuery.each( args, function( _, arg ) {\n\t\t\t\t\t\t\tvar type = jQuery.type( arg );\n\t\t\t\t\t\t\tif ( type === \"function\" ) {\n\t\t\t\t\t\t\t\tif ( !options.unique || !self.has( arg ) ) {\n\t\t\t\t\t\t\t\t\tlist.push( arg );\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t} else if ( arg && arg.length && type !== \"string\" ) {\n\t\t\t\t\t\t\t\t// Inspect recursively\n\t\t\t\t\t\t\t\tadd( arg );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t})( arguments );\n\t\t\t\t\t// Do we need to add the callbacks to the\n\t\t\t\t\t// current firing batch?\n\t\t\t\t\tif ( firing ) {\n\t\t\t\t\t\tfiringLength = list.length;\n\t\t\t\t\t// With memory, if we're not firing then\n\t\t\t\t\t// we should call right away\n\t\t\t\t\t} else if ( memory ) {\n\t\t\t\t\t\tfiringStart = start;\n\t\t\t\t\t\tfire( memory );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Remove a callback from the list\n\t\t\tremove: function() {\n\t\t\t\tif ( list ) {\n\t\t\t\t\tjQuery.each( arguments, function( _, arg ) {\n\t\t\t\t\t\tvar index;\n\t\t\t\t\t\twhile( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {\n\t\t\t\t\t\t\tlist.splice( index, 1 );\n\t\t\t\t\t\t\t// Handle firing indexes\n\t\t\t\t\t\t\tif ( firing ) {\n\t\t\t\t\t\t\t\tif ( index <= firingLength ) {\n\t\t\t\t\t\t\t\t\tfiringLength--;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tif ( index <= firingIndex ) {\n\t\t\t\t\t\t\t\t\tfiringIndex--;\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Check if a given callback is in the list.\n\t\t\t// If no argument is given, return whether or not list has callbacks attached.\n\t\t\thas: function( fn ) {\n\t\t\t\treturn fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );\n\t\t\t},\n\t\t\t// Remove all callbacks from the list\n\t\t\tempty: function() {\n\t\t\t\tlist = [];\n\t\t\t\tfiringLength = 0;\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Have the list do nothing anymore\n\t\t\tdisable: function() {\n\t\t\t\tlist = stack = memory = undefined;\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Is it disabled?\n\t\t\tdisabled: function() {\n\t\t\t\treturn !list;\n\t\t\t},\n\t\t\t// Lock the list in its current state\n\t\t\tlock: function() {\n\t\t\t\tstack = undefined;\n\t\t\t\tif ( !memory ) {\n\t\t\t\t\tself.disable();\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Is it locked?\n\t\t\tlocked: function() {\n\t\t\t\treturn !stack;\n\t\t\t},\n\t\t\t// Call all callbacks with the given context and arguments\n\t\t\tfireWith: function( context, args ) {\n\t\t\t\tif ( list && ( !fired || stack ) ) {\n\t\t\t\t\targs = args || [];\n\t\t\t\t\targs = [ context, args.slice ? args.slice() : args ];\n\t\t\t\t\tif ( firing ) {\n\t\t\t\t\t\tstack.push( args );\n\t\t\t\t\t} else {\n\t\t\t\t\t\tfire( args );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// Call all the callbacks with the given arguments\n\t\t\tfire: function() {\n\t\t\t\tself.fireWith( this, arguments );\n\t\t\t\treturn this;\n\t\t\t},\n\t\t\t// To know if the callbacks have already been called at least once\n\t\t\tfired: function() {\n\t\t\t\treturn !!fired;\n\t\t\t}\n\t\t};\n\n\treturn self;\n};\njQuery.extend({\n\n\tDeferred: function( func ) {\n\t\tvar tuples = [\n\t\t\t\t// action, add listener, listener list, final state\n\t\t\t\t[ \"resolve\", \"done\", jQuery.Callbacks(\"once memory\"), \"resolved\" ],\n\t\t\t\t[ \"reject\", \"fail\", jQuery.Callbacks(\"once memory\"), \"rejected\" ],\n\t\t\t\t[ \"notify\", \"progress\", jQuery.Callbacks(\"memory\") ]\n\t\t\t],\n\t\t\tstate = \"pending\",\n\t\t\tpromise = {\n\t\t\t\tstate: function() {\n\t\t\t\t\treturn state;\n\t\t\t\t},\n\t\t\t\talways: function() {\n\t\t\t\t\tdeferred.done( arguments ).fail( arguments );\n\t\t\t\t\treturn this;\n\t\t\t\t},\n\t\t\t\tthen: function( /* fnDone, fnFail, fnProgress */ ) {\n\t\t\t\t\tvar fns = arguments;\n\t\t\t\t\treturn jQuery.Deferred(function( newDefer ) {\n\t\t\t\t\t\tjQuery.each( tuples, function( i, tuple ) {\n\t\t\t\t\t\t\tvar action = tuple[ 0 ],\n\t\t\t\t\t\t\t\tfn = jQuery.isFunction( fns[ i ] ) && fns[ i ];\n\t\t\t\t\t\t\t// deferred[ done | fail | progress ] for forwarding actions to newDefer\n\t\t\t\t\t\t\tdeferred[ tuple[1] ](function() {\n\t\t\t\t\t\t\t\tvar returned = fn && fn.apply( this, arguments );\n\t\t\t\t\t\t\t\tif ( returned && jQuery.isFunction( returned.promise ) ) {\n\t\t\t\t\t\t\t\t\treturned.promise()\n\t\t\t\t\t\t\t\t\t\t.done( newDefer.resolve )\n\t\t\t\t\t\t\t\t\t\t.fail( newDefer.reject )\n\t\t\t\t\t\t\t\t\t\t.progress( newDefer.notify );\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tnewDefer[ action + \"With\" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t});\n\t\t\t\t\t\tfns = null;\n\t\t\t\t\t}).promise();\n\t\t\t\t},\n\t\t\t\t// Get a promise for this deferred\n\t\t\t\t// If obj is provided, the promise aspect is added to the object\n\t\t\t\tpromise: function( obj ) {\n\t\t\t\t\treturn obj != null ? jQuery.extend( obj, promise ) : promise;\n\t\t\t\t}\n\t\t\t},\n\t\t\tdeferred = {};\n\n\t\t// Keep pipe for back-compat\n\t\tpromise.pipe = promise.then;\n\n\t\t// Add list-specific methods\n\t\tjQuery.each( tuples, function( i, tuple ) {\n\t\t\tvar list = tuple[ 2 ],\n\t\t\t\tstateString = tuple[ 3 ];\n\n\t\t\t// promise[ done | fail | progress ] = list.add\n\t\t\tpromise[ tuple[1] ] = list.add;\n\n\t\t\t// Handle state\n\t\t\tif ( stateString ) {\n\t\t\t\tlist.add(function() {\n\t\t\t\t\t// state = [ resolved | rejected ]\n\t\t\t\t\tstate = stateString;\n\n\t\t\t\t// [ reject_list | resolve_list ].disable; progress_list.lock\n\t\t\t\t}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );\n\t\t\t}\n\n\t\t\t// deferred[ resolve | reject | notify ]\n\t\t\tdeferred[ tuple[0] ] = function() {\n\t\t\t\tdeferred[ tuple[0] + \"With\" ]( this === deferred ? promise : this, arguments );\n\t\t\t\treturn this;\n\t\t\t};\n\t\t\tdeferred[ tuple[0] + \"With\" ] = list.fireWith;\n\t\t});\n\n\t\t// Make the deferred a promise\n\t\tpromise.promise( deferred );\n\n\t\t// Call given func if any\n\t\tif ( func ) {\n\t\t\tfunc.call( deferred, deferred );\n\t\t}\n\n\t\t// All done!\n\t\treturn deferred;\n\t},\n\n\t// Deferred helper\n\twhen: function( subordinate /* , ..., subordinateN */ ) {\n\t\tvar i = 0,\n\t\t\tresolveValues = core_slice.call( arguments ),\n\t\t\tlength = resolveValues.length,\n\n\t\t\t// the count of uncompleted subordinates\n\t\t\tremaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,\n\n\t\t\t// the master Deferred. If resolveValues consist of only a single Deferred, just use that.\n\t\t\tdeferred = remaining === 1 ? subordinate : jQuery.Deferred(),\n\n\t\t\t// Update function for both resolve and progress values\n\t\t\tupdateFunc = function( i, contexts, values ) {\n\t\t\t\treturn function( value ) {\n\t\t\t\t\tcontexts[ i ] = this;\n\t\t\t\t\tvalues[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;\n\t\t\t\t\tif( values === progressValues ) {\n\t\t\t\t\t\tdeferred.notifyWith( contexts, values );\n\t\t\t\t\t} else if ( !( --remaining ) ) {\n\t\t\t\t\t\tdeferred.resolveWith( contexts, values );\n\t\t\t\t\t}\n\t\t\t\t};\n\t\t\t},\n\n\t\t\tprogressValues, progressContexts, resolveContexts;\n\n\t\t// add listeners to Deferred subordinates; treat others as resolved\n\t\tif ( length > 1 ) {\n\t\t\tprogressValues = new Array( length );\n\t\t\tprogressContexts = new Array( length );\n\t\t\tresolveContexts = new Array( length );\n\t\t\tfor ( ; i < length; i++ ) {\n\t\t\t\tif ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {\n\t\t\t\t\tresolveValues[ i ].promise()\n\t\t\t\t\t\t.done( updateFunc( i, resolveContexts, resolveValues ) )\n\t\t\t\t\t\t.fail( deferred.reject )\n\t\t\t\t\t\t.progress( updateFunc( i, progressContexts, progressValues ) );\n\t\t\t\t} else {\n\t\t\t\t\t--remaining;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// if we're not waiting on anything, resolve the master\n\t\tif ( !remaining ) {\n\t\t\tdeferred.resolveWith( resolveContexts, resolveValues );\n\t\t}\n\n\t\treturn deferred.promise();\n\t}\n});\njQuery.support = (function( support ) {\n\n\tvar all, a, input, select, fragment, opt, eventName, isSupported, i,\n\t\tdiv = document.createElement(\"div\");\n\n\t// Setup\n\tdiv.setAttribute( \"className\", \"t\" );\n\tdiv.innerHTML = \"  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>\";\n\n\t// Finish early in limited (non-browser) environments\n\tall = div.getElementsByTagName(\"*\") || [];\n\ta = div.getElementsByTagName(\"a\")[ 0 ];\n\tif ( !a || !a.style || !all.length ) {\n\t\treturn support;\n\t}\n\n\t// First batch of tests\n\tselect = document.createElement(\"select\");\n\topt = select.appendChild( document.createElement(\"option\") );\n\tinput = div.getElementsByTagName(\"input\")[ 0 ];\n\n\ta.style.cssText = \"top:1px;float:left;opacity:.5\";\n\n\t// Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7)\n\tsupport.getSetAttribute = div.className !== \"t\";\n\n\t// IE strips leading whitespace when .innerHTML is used\n\tsupport.leadingWhitespace = div.firstChild.nodeType === 3;\n\n\t// Make sure that tbody elements aren't automatically inserted\n\t// IE will insert them into empty tables\n\tsupport.tbody = !div.getElementsByTagName(\"tbody\").length;\n\n\t// Make sure that link elements get serialized correctly by innerHTML\n\t// This requires a wrapper element in IE\n\tsupport.htmlSerialize = !!div.getElementsByTagName(\"link\").length;\n\n\t// Get the style information from getAttribute\n\t// (IE uses .cssText instead)\n\tsupport.style = /top/.test( a.getAttribute(\"style\") );\n\n\t// Make sure that URLs aren't manipulated\n\t// (IE normalizes it by default)\n\tsupport.hrefNormalized = a.getAttribute(\"href\") === \"/a\";\n\n\t// Make sure that element opacity exists\n\t// (IE uses filter instead)\n\t// Use a regex to work around a WebKit issue. See #5145\n\tsupport.opacity = /^0.5/.test( a.style.opacity );\n\n\t// Verify style float existence\n\t// (IE uses styleFloat instead of cssFloat)\n\tsupport.cssFloat = !!a.style.cssFloat;\n\n\t// Check the default checkbox/radio value (\"\" on WebKit; \"on\" elsewhere)\n\tsupport.checkOn = !!input.value;\n\n\t// Make sure that a selected-by-default option has a working selected property.\n\t// (WebKit defaults to false instead of true, IE too, if it's in an optgroup)\n\tsupport.optSelected = opt.selected;\n\n\t// Tests for enctype support on a form (#6743)\n\tsupport.enctype = !!document.createElement(\"form\").enctype;\n\n\t// Makes sure cloning an html5 element does not cause problems\n\t// Where outerHTML is undefined, this still works\n\tsupport.html5Clone = document.createElement(\"nav\").cloneNode( true ).outerHTML !== \"<:nav></:nav>\";\n\n\t// Will be defined later\n\tsupport.inlineBlockNeedsLayout = false;\n\tsupport.shrinkWrapBlocks = false;\n\tsupport.pixelPosition = false;\n\tsupport.deleteExpando = true;\n\tsupport.noCloneEvent = true;\n\tsupport.reliableMarginRight = true;\n\tsupport.boxSizingReliable = true;\n\n\t// Make sure checked status is properly cloned\n\tinput.checked = true;\n\tsupport.noCloneChecked = input.cloneNode( true ).checked;\n\n\t// Make sure that the options inside disabled selects aren't marked as disabled\n\t// (WebKit marks them as disabled)\n\tselect.disabled = true;\n\tsupport.optDisabled = !opt.disabled;\n\n\t// Support: IE<9\n\ttry {\n\t\tdelete div.test;\n\t} catch( e ) {\n\t\tsupport.deleteExpando = false;\n\t}\n\n\t// Check if we can trust getAttribute(\"value\")\n\tinput = document.createElement(\"input\");\n\tinput.setAttribute( \"value\", \"\" );\n\tsupport.input = input.getAttribute( \"value\" ) === \"\";\n\n\t// Check if an input maintains its value after becoming a radio\n\tinput.value = \"t\";\n\tinput.setAttribute( \"type\", \"radio\" );\n\tsupport.radioValue = input.value === \"t\";\n\n\t// #11217 - WebKit loses check when the name is after the checked attribute\n\tinput.setAttribute( \"checked\", \"t\" );\n\tinput.setAttribute( \"name\", \"t\" );\n\n\tfragment = document.createDocumentFragment();\n\tfragment.appendChild( input );\n\n\t// Check if a disconnected checkbox will retain its checked\n\t// value of true after appended to the DOM (IE6/7)\n\tsupport.appendChecked = input.checked;\n\n\t// WebKit doesn't clone checked state correctly in fragments\n\tsupport.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;\n\n\t// Support: IE<9\n\t// Opera does not clone events (and typeof div.attachEvent === undefined).\n\t// IE9-10 clones events bound via attachEvent, but they don't trigger with .click()\n\tif ( div.attachEvent ) {\n\t\tdiv.attachEvent( \"onclick\", function() {\n\t\t\tsupport.noCloneEvent = false;\n\t\t});\n\n\t\tdiv.cloneNode( true ).click();\n\t}\n\n\t// Support: IE<9 (lack submit/change bubble), Firefox 17+ (lack focusin event)\n\t// Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP)\n\tfor ( i in { submit: true, change: true, focusin: true }) {\n\t\tdiv.setAttribute( eventName = \"on\" + i, \"t\" );\n\n\t\tsupport[ i + \"Bubbles\" ] = eventName in window || div.attributes[ eventName ].expando === false;\n\t}\n\n\tdiv.style.backgroundClip = \"content-box\";\n\tdiv.cloneNode( true ).style.backgroundClip = \"\";\n\tsupport.clearCloneStyle = div.style.backgroundClip === \"content-box\";\n\n\t// Support: IE<9\n\t// Iteration over object's inherited properties before its own.\n\tfor ( i in jQuery( support ) ) {\n\t\tbreak;\n\t}\n\tsupport.ownLast = i !== \"0\";\n\n\t// Run tests that need a body at doc ready\n\tjQuery(function() {\n\t\tvar container, marginDiv, tds,\n\t\t\tdivReset = \"padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;\",\n\t\t\tbody = document.getElementsByTagName(\"body\")[0];\n\n\t\tif ( !body ) {\n\t\t\t// Return for frameset docs that don't have a body\n\t\t\treturn;\n\t\t}\n\n\t\tcontainer = document.createElement(\"div\");\n\t\tcontainer.style.cssText = \"border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px\";\n\n\t\tbody.appendChild( container ).appendChild( div );\n\n\t\t// Support: IE8\n\t\t// Check if table cells still have offsetWidth/Height when they are set\n\t\t// to display:none and there are still other visible table cells in a\n\t\t// table row; if so, offsetWidth/Height are not reliable for use when\n\t\t// determining if an element has been hidden directly using\n\t\t// display:none (it is still safe to use offsets if a parent element is\n\t\t// hidden; don safety goggles and see bug #4512 for more information).\n\t\tdiv.innerHTML = \"<table><tr><td></td><td>t</td></tr></table>\";\n\t\ttds = div.getElementsByTagName(\"td\");\n\t\ttds[ 0 ].style.cssText = \"padding:0;margin:0;border:0;display:none\";\n\t\tisSupported = ( tds[ 0 ].offsetHeight === 0 );\n\n\t\ttds[ 0 ].style.display = \"\";\n\t\ttds[ 1 ].style.display = \"none\";\n\n\t\t// Support: IE8\n\t\t// Check if empty table cells still have offsetWidth/Height\n\t\tsupport.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 );\n\n\t\t// Check box-sizing and margin behavior.\n\t\tdiv.innerHTML = \"\";\n\t\tdiv.style.cssText = \"box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;\";\n\n\t\t// Workaround failing boxSizing test due to offsetWidth returning wrong value\n\t\t// with some non-1 values of body zoom, ticket #13543\n\t\tjQuery.swap( body, body.style.zoom != null ? { zoom: 1 } : {}, function() {\n\t\t\tsupport.boxSizing = div.offsetWidth === 4;\n\t\t});\n\n\t\t// Use window.getComputedStyle because jsdom on node.js will break without it.\n\t\tif ( window.getComputedStyle ) {\n\t\t\tsupport.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== \"1%\";\n\t\t\tsupport.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: \"4px\" } ).width === \"4px\";\n\n\t\t\t// Check if div with explicit width and no margin-right incorrectly\n\t\t\t// gets computed margin-right based on width of container. (#3333)\n\t\t\t// Fails in WebKit before Feb 2011 nightlies\n\t\t\t// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right\n\t\t\tmarginDiv = div.appendChild( document.createElement(\"div\") );\n\t\t\tmarginDiv.style.cssText = div.style.cssText = divReset;\n\t\t\tmarginDiv.style.marginRight = marginDiv.style.width = \"0\";\n\t\t\tdiv.style.width = \"1px\";\n\n\t\t\tsupport.reliableMarginRight =\n\t\t\t\t!parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight );\n\t\t}\n\n\t\tif ( typeof div.style.zoom !== core_strundefined ) {\n\t\t\t// Support: IE<8\n\t\t\t// Check if natively block-level elements act like inline-block\n\t\t\t// elements when setting their display to 'inline' and giving\n\t\t\t// them layout\n\t\t\tdiv.innerHTML = \"\";\n\t\t\tdiv.style.cssText = divReset + \"width:1px;padding:1px;display:inline;zoom:1\";\n\t\t\tsupport.inlineBlockNeedsLayout = ( div.offsetWidth === 3 );\n\n\t\t\t// Support: IE6\n\t\t\t// Check if elements with layout shrink-wrap their children\n\t\t\tdiv.style.display = \"block\";\n\t\t\tdiv.innerHTML = \"<div></div>\";\n\t\t\tdiv.firstChild.style.width = \"5px\";\n\t\t\tsupport.shrinkWrapBlocks = ( div.offsetWidth !== 3 );\n\n\t\t\tif ( support.inlineBlockNeedsLayout ) {\n\t\t\t\t// Prevent IE 6 from affecting layout for positioned elements #11048\n\t\t\t\t// Prevent IE from shrinking the body in IE 7 mode #12869\n\t\t\t\t// Support: IE<8\n\t\t\t\tbody.style.zoom = 1;\n\t\t\t}\n\t\t}\n\n\t\tbody.removeChild( container );\n\n\t\t// Null elements to avoid leaks in IE\n\t\tcontainer = div = tds = marginDiv = null;\n\t});\n\n\t// Null elements to avoid leaks in IE\n\tall = select = fragment = opt = a = input = null;\n\n\treturn support;\n})({});\n\nvar rbrace = /(?:\\{[\\s\\S]*\\}|\\[[\\s\\S]*\\])$/,\n\trmultiDash = /([A-Z])/g;\n\nfunction internalData( elem, name, data, pvt /* Internal Use Only */ ){\n\tif ( !jQuery.acceptData( elem ) ) {\n\t\treturn;\n\t}\n\n\tvar ret, thisCache,\n\t\tinternalKey = jQuery.expando,\n\n\t\t// We have to handle DOM nodes and JS objects differently because IE6-7\n\t\t// can't GC object references properly across the DOM-JS boundary\n\t\tisNode = elem.nodeType,\n\n\t\t// Only DOM nodes need the global jQuery cache; JS object data is\n\t\t// attached directly to the object so GC can occur automatically\n\t\tcache = isNode ? jQuery.cache : elem,\n\n\t\t// Only defining an ID for JS objects if its cache already exists allows\n\t\t// the code to shortcut on the same path as a DOM node with no cache\n\t\tid = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;\n\n\t// Avoid doing any more work than we need to when trying to get data on an\n\t// object that has no data at all\n\tif ( (!id || !cache[id] || (!pvt && !cache[id].data)) && data === undefined && typeof name === \"string\" ) {\n\t\treturn;\n\t}\n\n\tif ( !id ) {\n\t\t// Only DOM nodes need a new unique ID for each element since their data\n\t\t// ends up in the global cache\n\t\tif ( isNode ) {\n\t\t\tid = elem[ internalKey ] = core_deletedIds.pop() || jQuery.guid++;\n\t\t} else {\n\t\t\tid = internalKey;\n\t\t}\n\t}\n\n\tif ( !cache[ id ] ) {\n\t\t// Avoid exposing jQuery metadata on plain JS objects when the object\n\t\t// is serialized using JSON.stringify\n\t\tcache[ id ] = isNode ? {} : { toJSON: jQuery.noop };\n\t}\n\n\t// An object can be passed to jQuery.data instead of a key/value pair; this gets\n\t// shallow copied over onto the existing cache\n\tif ( typeof name === \"object\" || typeof name === \"function\" ) {\n\t\tif ( pvt ) {\n\t\t\tcache[ id ] = jQuery.extend( cache[ id ], name );\n\t\t} else {\n\t\t\tcache[ id ].data = jQuery.extend( cache[ id ].data, name );\n\t\t}\n\t}\n\n\tthisCache = cache[ id ];\n\n\t// jQuery data() is stored in a separate object inside the object's internal data\n\t// cache in order to avoid key collisions between internal data and user-defined\n\t// data.\n\tif ( !pvt ) {\n\t\tif ( !thisCache.data ) {\n\t\t\tthisCache.data = {};\n\t\t}\n\n\t\tthisCache = thisCache.data;\n\t}\n\n\tif ( data !== undefined ) {\n\t\tthisCache[ jQuery.camelCase( name ) ] = data;\n\t}\n\n\t// Check for both converted-to-camel and non-converted data property names\n\t// If a data property was specified\n\tif ( typeof name === \"string\" ) {\n\n\t\t// First Try to find as-is property data\n\t\tret = thisCache[ name ];\n\n\t\t// Test for null|undefined property data\n\t\tif ( ret == null ) {\n\n\t\t\t// Try to find the camelCased property\n\t\t\tret = thisCache[ jQuery.camelCase( name ) ];\n\t\t}\n\t} else {\n\t\tret = thisCache;\n\t}\n\n\treturn ret;\n}\n\nfunction internalRemoveData( elem, name, pvt ) {\n\tif ( !jQuery.acceptData( elem ) ) {\n\t\treturn;\n\t}\n\n\tvar thisCache, i,\n\t\tisNode = elem.nodeType,\n\n\t\t// See jQuery.data for more information\n\t\tcache = isNode ? jQuery.cache : elem,\n\t\tid = isNode ? elem[ jQuery.expando ] : jQuery.expando;\n\n\t// If there is already no cache entry for this object, there is no\n\t// purpose in continuing\n\tif ( !cache[ id ] ) {\n\t\treturn;\n\t}\n\n\tif ( name ) {\n\n\t\tthisCache = pvt ? cache[ id ] : cache[ id ].data;\n\n\t\tif ( thisCache ) {\n\n\t\t\t// Support array or space separated string names for data keys\n\t\t\tif ( !jQuery.isArray( name ) ) {\n\n\t\t\t\t// try the string as a key before any manipulation\n\t\t\t\tif ( name in thisCache ) {\n\t\t\t\t\tname = [ name ];\n\t\t\t\t} else {\n\n\t\t\t\t\t// split the camel cased version by spaces unless a key with the spaces exists\n\t\t\t\t\tname = jQuery.camelCase( name );\n\t\t\t\t\tif ( name in thisCache ) {\n\t\t\t\t\t\tname = [ name ];\n\t\t\t\t\t} else {\n\t\t\t\t\t\tname = name.split(\" \");\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// If \"name\" is an array of keys...\n\t\t\t\t// When data is initially created, via (\"key\", \"val\") signature,\n\t\t\t\t// keys will be converted to camelCase.\n\t\t\t\t// Since there is no way to tell _how_ a key was added, remove\n\t\t\t\t// both plain key and camelCase key. #12786\n\t\t\t\t// This will only penalize the array argument path.\n\t\t\t\tname = name.concat( jQuery.map( name, jQuery.camelCase ) );\n\t\t\t}\n\n\t\t\ti = name.length;\n\t\t\twhile ( i-- ) {\n\t\t\t\tdelete thisCache[ name[i] ];\n\t\t\t}\n\n\t\t\t// If there is no data left in the cache, we want to continue\n\t\t\t// and let the cache object itself get destroyed\n\t\t\tif ( pvt ? !isEmptyDataObject(thisCache) : !jQuery.isEmptyObject(thisCache) ) {\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n\t// See jQuery.data for more information\n\tif ( !pvt ) {\n\t\tdelete cache[ id ].data;\n\n\t\t// Don't destroy the parent cache unless the internal data object\n\t\t// had been the only thing left in it\n\t\tif ( !isEmptyDataObject( cache[ id ] ) ) {\n\t\t\treturn;\n\t\t}\n\t}\n\n\t// Destroy the cache\n\tif ( isNode ) {\n\t\tjQuery.cleanData( [ elem ], true );\n\n\t// Use delete when supported for expandos or `cache` is not a window per isWindow (#10080)\n\t/* jshint eqeqeq: false */\n\t} else if ( jQuery.support.deleteExpando || cache != cache.window ) {\n\t\t/* jshint eqeqeq: true */\n\t\tdelete cache[ id ];\n\n\t// When all else fails, null\n\t} else {\n\t\tcache[ id ] = null;\n\t}\n}\n\njQuery.extend({\n\tcache: {},\n\n\t// The following elements throw uncatchable exceptions if you\n\t// attempt to add expando properties to them.\n\tnoData: {\n\t\t\"applet\": true,\n\t\t\"embed\": true,\n\t\t// Ban all objects except for Flash (which handle expandos)\n\t\t\"object\": \"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000\"\n\t},\n\n\thasData: function( elem ) {\n\t\telem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];\n\t\treturn !!elem && !isEmptyDataObject( elem );\n\t},\n\n\tdata: function( elem, name, data ) {\n\t\treturn internalData( elem, name, data );\n\t},\n\n\tremoveData: function( elem, name ) {\n\t\treturn internalRemoveData( elem, name );\n\t},\n\n\t// For internal use only.\n\t_data: function( elem, name, data ) {\n\t\treturn internalData( elem, name, data, true );\n\t},\n\n\t_removeData: function( elem, name ) {\n\t\treturn internalRemoveData( elem, name, true );\n\t},\n\n\t// A method for determining if a DOM node can handle the data expando\n\tacceptData: function( elem ) {\n\t\t// Do not set data on non-element because it will not be cleared (#8335).\n\t\tif ( elem.nodeType && elem.nodeType !== 1 && elem.nodeType !== 9 ) {\n\t\t\treturn false;\n\t\t}\n\n\t\tvar noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ];\n\n\t\t// nodes accept data unless otherwise specified; rejection can be conditional\n\t\treturn !noData || noData !== true && elem.getAttribute(\"classid\") === noData;\n\t}\n});\n\njQuery.fn.extend({\n\tdata: function( key, value ) {\n\t\tvar attrs, name,\n\t\t\tdata = null,\n\t\t\ti = 0,\n\t\t\telem = this[0];\n\n\t\t// Special expections of .data basically thwart jQuery.access,\n\t\t// so implement the relevant behavior ourselves\n\n\t\t// Gets all values\n\t\tif ( key === undefined ) {\n\t\t\tif ( this.length ) {\n\t\t\t\tdata = jQuery.data( elem );\n\n\t\t\t\tif ( elem.nodeType === 1 && !jQuery._data( elem, \"parsedAttrs\" ) ) {\n\t\t\t\t\tattrs = elem.attributes;\n\t\t\t\t\tfor ( ; i < attrs.length; i++ ) {\n\t\t\t\t\t\tname = attrs[i].name;\n\n\t\t\t\t\t\tif ( name.indexOf(\"data-\") === 0 ) {\n\t\t\t\t\t\t\tname = jQuery.camelCase( name.slice(5) );\n\n\t\t\t\t\t\t\tdataAttr( elem, name, data[ name ] );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tjQuery._data( elem, \"parsedAttrs\", true );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn data;\n\t\t}\n\n\t\t// Sets multiple values\n\t\tif ( typeof key === \"object\" ) {\n\t\t\treturn this.each(function() {\n\t\t\t\tjQuery.data( this, key );\n\t\t\t});\n\t\t}\n\n\t\treturn arguments.length > 1 ?\n\n\t\t\t// Sets one value\n\t\t\tthis.each(function() {\n\t\t\t\tjQuery.data( this, key, value );\n\t\t\t}) :\n\n\t\t\t// Gets one value\n\t\t\t// Try to fetch any internally stored data first\n\t\t\telem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : null;\n\t},\n\n\tremoveData: function( key ) {\n\t\treturn this.each(function() {\n\t\t\tjQuery.removeData( this, key );\n\t\t});\n\t}\n});\n\nfunction dataAttr( elem, key, data ) {\n\t// If nothing was found internally, try to fetch any\n\t// data from the HTML5 data-* attribute\n\tif ( data === undefined && elem.nodeType === 1 ) {\n\n\t\tvar name = \"data-\" + key.replace( rmultiDash, \"-$1\" ).toLowerCase();\n\n\t\tdata = elem.getAttribute( name );\n\n\t\tif ( typeof data === \"string\" ) {\n\t\t\ttry {\n\t\t\t\tdata = data === \"true\" ? true :\n\t\t\t\t\tdata === \"false\" ? false :\n\t\t\t\t\tdata === \"null\" ? null :\n\t\t\t\t\t// Only convert to a number if it doesn't change the string\n\t\t\t\t\t+data + \"\" === data ? +data :\n\t\t\t\t\trbrace.test( data ) ? jQuery.parseJSON( data ) :\n\t\t\t\t\t\tdata;\n\t\t\t} catch( e ) {}\n\n\t\t\t// Make sure we set the data so it isn't changed later\n\t\t\tjQuery.data( elem, key, data );\n\n\t\t} else {\n\t\t\tdata = undefined;\n\t\t}\n\t}\n\n\treturn data;\n}\n\n// checks a cache object for emptiness\nfunction isEmptyDataObject( obj ) {\n\tvar name;\n\tfor ( name in obj ) {\n\n\t\t// if the public data object is empty, the private is still empty\n\t\tif ( name === \"data\" && jQuery.isEmptyObject( obj[name] ) ) {\n\t\t\tcontinue;\n\t\t}\n\t\tif ( name !== \"toJSON\" ) {\n\t\t\treturn false;\n\t\t}\n\t}\n\n\treturn true;\n}\njQuery.extend({\n\tqueue: function( elem, type, data ) {\n\t\tvar queue;\n\n\t\tif ( elem ) {\n\t\t\ttype = ( type || \"fx\" ) + \"queue\";\n\t\t\tqueue = jQuery._data( elem, type );\n\n\t\t\t// Speed up dequeue by getting out quickly if this is just a lookup\n\t\t\tif ( data ) {\n\t\t\t\tif ( !queue || jQuery.isArray(data) ) {\n\t\t\t\t\tqueue = jQuery._data( elem, type, jQuery.makeArray(data) );\n\t\t\t\t} else {\n\t\t\t\t\tqueue.push( data );\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn queue || [];\n\t\t}\n\t},\n\n\tdequeue: function( elem, type ) {\n\t\ttype = type || \"fx\";\n\n\t\tvar queue = jQuery.queue( elem, type ),\n\t\t\tstartLength = queue.length,\n\t\t\tfn = queue.shift(),\n\t\t\thooks = jQuery._queueHooks( elem, type ),\n\t\t\tnext = function() {\n\t\t\t\tjQuery.dequeue( elem, type );\n\t\t\t};\n\n\t\t// If the fx queue is dequeued, always remove the progress sentinel\n\t\tif ( fn === \"inprogress\" ) {\n\t\t\tfn = queue.shift();\n\t\t\tstartLength--;\n\t\t}\n\n\t\tif ( fn ) {\n\n\t\t\t// Add a progress sentinel to prevent the fx queue from being\n\t\t\t// automatically dequeued\n\t\t\tif ( type === \"fx\" ) {\n\t\t\t\tqueue.unshift( \"inprogress\" );\n\t\t\t}\n\n\t\t\t// clear up the last queue stop function\n\t\t\tdelete hooks.stop;\n\t\t\tfn.call( elem, next, hooks );\n\t\t}\n\n\t\tif ( !startLength && hooks ) {\n\t\t\thooks.empty.fire();\n\t\t}\n\t},\n\n\t// not intended for public consumption - generates a queueHooks object, or returns the current one\n\t_queueHooks: function( elem, type ) {\n\t\tvar key = type + \"queueHooks\";\n\t\treturn jQuery._data( elem, key ) || jQuery._data( elem, key, {\n\t\t\tempty: jQuery.Callbacks(\"once memory\").add(function() {\n\t\t\t\tjQuery._removeData( elem, type + \"queue\" );\n\t\t\t\tjQuery._removeData( elem, key );\n\t\t\t})\n\t\t});\n\t}\n});\n\njQuery.fn.extend({\n\tqueue: function( type, data ) {\n\t\tvar setter = 2;\n\n\t\tif ( typeof type !== \"string\" ) {\n\t\t\tdata = type;\n\t\t\ttype = \"fx\";\n\t\t\tsetter--;\n\t\t}\n\n\t\tif ( arguments.length < setter ) {\n\t\t\treturn jQuery.queue( this[0], type );\n\t\t}\n\n\t\treturn data === undefined ?\n\t\t\tthis :\n\t\t\tthis.each(function() {\n\t\t\t\tvar queue = jQuery.queue( this, type, data );\n\n\t\t\t\t// ensure a hooks for this queue\n\t\t\t\tjQuery._queueHooks( this, type );\n\n\t\t\t\tif ( type === \"fx\" && queue[0] !== \"inprogress\" ) {\n\t\t\t\t\tjQuery.dequeue( this, type );\n\t\t\t\t}\n\t\t\t});\n\t},\n\tdequeue: function( type ) {\n\t\treturn this.each(function() {\n\t\t\tjQuery.dequeue( this, type );\n\t\t});\n\t},\n\t// Based off of the plugin by Clint Helfers, with permission.\n\t// http://blindsignals.com/index.php/2009/07/jquery-delay/\n\tdelay: function( time, type ) {\n\t\ttime = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;\n\t\ttype = type || \"fx\";\n\n\t\treturn this.queue( type, function( next, hooks ) {\n\t\t\tvar timeout = setTimeout( next, time );\n\t\t\thooks.stop = function() {\n\t\t\t\tclearTimeout( timeout );\n\t\t\t};\n\t\t});\n\t},\n\tclearQueue: function( type ) {\n\t\treturn this.queue( type || \"fx\", [] );\n\t},\n\t// Get a promise resolved when queues of a certain type\n\t// are emptied (fx is the type by default)\n\tpromise: function( type, obj ) {\n\t\tvar tmp,\n\t\t\tcount = 1,\n\t\t\tdefer = jQuery.Deferred(),\n\t\t\telements = this,\n\t\t\ti = this.length,\n\t\t\tresolve = function() {\n\t\t\t\tif ( !( --count ) ) {\n\t\t\t\t\tdefer.resolveWith( elements, [ elements ] );\n\t\t\t\t}\n\t\t\t};\n\n\t\tif ( typeof type !== \"string\" ) {\n\t\t\tobj = type;\n\t\t\ttype = undefined;\n\t\t}\n\t\ttype = type || \"fx\";\n\n\t\twhile( i-- ) {\n\t\t\ttmp = jQuery._data( elements[ i ], type + \"queueHooks\" );\n\t\t\tif ( tmp && tmp.empty ) {\n\t\t\t\tcount++;\n\t\t\t\ttmp.empty.add( resolve );\n\t\t\t}\n\t\t}\n\t\tresolve();\n\t\treturn defer.promise( obj );\n\t}\n});\nvar nodeHook, boolHook,\n\trclass = /[\\t\\r\\n\\f]/g,\n\trreturn = /\\r/g,\n\trfocusable = /^(?:input|select|textarea|button|object)$/i,\n\trclickable = /^(?:a|area)$/i,\n\truseDefault = /^(?:checked|selected)$/i,\n\tgetSetAttribute = jQuery.support.getSetAttribute,\n\tgetSetInput = jQuery.support.input;\n\njQuery.fn.extend({\n\tattr: function( name, value ) {\n\t\treturn jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );\n\t},\n\n\tremoveAttr: function( name ) {\n\t\treturn this.each(function() {\n\t\t\tjQuery.removeAttr( this, name );\n\t\t});\n\t},\n\n\tprop: function( name, value ) {\n\t\treturn jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );\n\t},\n\n\tremoveProp: function( name ) {\n\t\tname = jQuery.propFix[ name ] || name;\n\t\treturn this.each(function() {\n\t\t\t// try/catch handles cases where IE balks (such as removing a property on window)\n\t\t\ttry {\n\t\t\t\tthis[ name ] = undefined;\n\t\t\t\tdelete this[ name ];\n\t\t\t} catch( e ) {}\n\t\t});\n\t},\n\n\taddClass: function( value ) {\n\t\tvar classes, elem, cur, clazz, j,\n\t\t\ti = 0,\n\t\t\tlen = this.length,\n\t\t\tproceed = typeof value === \"string\" && value;\n\n\t\tif ( jQuery.isFunction( value ) ) {\n\t\t\treturn this.each(function( j ) {\n\t\t\t\tjQuery( this ).addClass( value.call( this, j, this.className ) );\n\t\t\t});\n\t\t}\n\n\t\tif ( proceed ) {\n\t\t\t// The disjunction here is for better compressibility (see removeClass)\n\t\t\tclasses = ( value || \"\" ).match( core_rnotwhite ) || [];\n\n\t\t\tfor ( ; i < len; i++ ) {\n\t\t\t\telem = this[ i ];\n\t\t\t\tcur = elem.nodeType === 1 && ( elem.className ?\n\t\t\t\t\t( \" \" + elem.className + \" \" ).replace( rclass, \" \" ) :\n\t\t\t\t\t\" \"\n\t\t\t\t);\n\n\t\t\t\tif ( cur ) {\n\t\t\t\t\tj = 0;\n\t\t\t\t\twhile ( (clazz = classes[j++]) ) {\n\t\t\t\t\t\tif ( cur.indexOf( \" \" + clazz + \" \" ) < 0 ) {\n\t\t\t\t\t\t\tcur += clazz + \" \";\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telem.className = jQuery.trim( cur );\n\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t},\n\n\tremoveClass: function( value ) {\n\t\tvar classes, elem, cur, clazz, j,\n\t\t\ti = 0,\n\t\t\tlen = this.length,\n\t\t\tproceed = arguments.length === 0 || typeof value === \"string\" && value;\n\n\t\tif ( jQuery.isFunction( value ) ) {\n\t\t\treturn this.each(function( j ) {\n\t\t\t\tjQuery( this ).removeClass( value.call( this, j, this.className ) );\n\t\t\t});\n\t\t}\n\t\tif ( proceed ) {\n\t\t\tclasses = ( value || \"\" ).match( core_rnotwhite ) || [];\n\n\t\t\tfor ( ; i < len; i++ ) {\n\t\t\t\telem = this[ i ];\n\t\t\t\t// This expression is here for better compressibility (see addClass)\n\t\t\t\tcur = elem.nodeType === 1 && ( elem.className ?\n\t\t\t\t\t( \" \" + elem.className + \" \" ).replace( rclass, \" \" ) :\n\t\t\t\t\t\"\"\n\t\t\t\t);\n\n\t\t\t\tif ( cur ) {\n\t\t\t\t\tj = 0;\n\t\t\t\t\twhile ( (clazz = classes[j++]) ) {\n\t\t\t\t\t\t// Remove *all* instances\n\t\t\t\t\t\twhile ( cur.indexOf( \" \" + clazz + \" \" ) >= 0 ) {\n\t\t\t\t\t\t\tcur = cur.replace( \" \" + clazz + \" \", \" \" );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\telem.className = value ? jQuery.trim( cur ) : \"\";\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t},\n\n\ttoggleClass: function( value, stateVal ) {\n\t\tvar type = typeof value;\n\n\t\tif ( typeof stateVal === \"boolean\" && type === \"string\" ) {\n\t\t\treturn stateVal ? this.addClass( value ) : this.removeClass( value );\n\t\t}\n\n\t\tif ( jQuery.isFunction( value ) ) {\n\t\t\treturn this.each(function( i ) {\n\t\t\t\tjQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );\n\t\t\t});\n\t\t}\n\n\t\treturn this.each(function() {\n\t\t\tif ( type === \"string\" ) {\n\t\t\t\t// toggle individual class names\n\t\t\t\tvar className,\n\t\t\t\t\ti = 0,\n\t\t\t\t\tself = jQuery( this ),\n\t\t\t\t\tclassNames = value.match( core_rnotwhite ) || [];\n\n\t\t\t\twhile ( (className = classNames[ i++ ]) ) {\n\t\t\t\t\t// check each className given, space separated list\n\t\t\t\t\tif ( self.hasClass( className ) ) {\n\t\t\t\t\t\tself.removeClass( className );\n\t\t\t\t\t} else {\n\t\t\t\t\t\tself.addClass( className );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t// Toggle whole class name\n\t\t\t} else if ( type === core_strundefined || type === \"boolean\" ) {\n\t\t\t\tif ( this.className ) {\n\t\t\t\t\t// store className if set\n\t\t\t\t\tjQuery._data( this, \"__className__\", this.className );\n\t\t\t\t}\n\n\t\t\t\t// If the element has a class name or if we're passed \"false\",\n\t\t\t\t// then remove the whole classname (if there was one, the above saved it).\n\t\t\t\t// Otherwise bring back whatever was previously saved (if anything),\n\t\t\t\t// falling back to the empty string if nothing was stored.\n\t\t\t\tthis.className = this.className || value === false ? \"\" : jQuery._data( this, \"__className__\" ) || \"\";\n\t\t\t}\n\t\t});\n\t},\n\n\thasClass: function( selector ) {\n\t\tvar className = \" \" + selector + \" \",\n\t\t\ti = 0,\n\t\t\tl = this.length;\n\t\tfor ( ; i < l; i++ ) {\n\t\t\tif ( this[i].nodeType === 1 && (\" \" + this[i].className + \" \").replace(rclass, \" \").indexOf( className ) >= 0 ) {\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n\t},\n\n\tval: function( value ) {\n\t\tvar ret, hooks, isFunction,\n\t\t\telem = this[0];\n\n\t\tif ( !arguments.length ) {\n\t\t\tif ( elem ) {\n\t\t\t\thooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];\n\n\t\t\t\tif ( hooks && \"get\" in hooks && (ret = hooks.get( elem, \"value\" )) !== undefined ) {\n\t\t\t\t\treturn ret;\n\t\t\t\t}\n\n\t\t\t\tret = elem.value;\n\n\t\t\t\treturn typeof ret === \"string\" ?\n\t\t\t\t\t// handle most common string cases\n\t\t\t\t\tret.replace(rreturn, \"\") :\n\t\t\t\t\t// handle cases where value is null/undef or number\n\t\t\t\t\tret == null ? \"\" : ret;\n\t\t\t}\n\n\t\t\treturn;\n\t\t}\n\n\t\tisFunction = jQuery.isFunction( value );\n\n\t\treturn this.each(function( i ) {\n\t\t\tvar val;\n\n\t\t\tif ( this.nodeType !== 1 ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif ( isFunction ) {\n\t\t\t\tval = value.call( this, i, jQuery( this ).val() );\n\t\t\t} else {\n\t\t\t\tval = value;\n\t\t\t}\n\n\t\t\t// Treat null/undefined as \"\"; convert numbers to string\n\t\t\tif ( val == null ) {\n\t\t\t\tval = \"\";\n\t\t\t} else if ( typeof val === \"number\" ) {\n\t\t\t\tval += \"\";\n\t\t\t} else if ( jQuery.isArray( val ) ) {\n\t\t\t\tval = jQuery.map(val, function ( value ) {\n\t\t\t\t\treturn value == null ? \"\" : value + \"\";\n\t\t\t\t});\n\t\t\t}\n\n\t\t\thooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];\n\n\t\t\t// If set returns undefined, fall back to normal setting\n\t\t\tif ( !hooks || !(\"set\" in hooks) || hooks.set( this, val, \"value\" ) === undefined ) {\n\t\t\t\tthis.value = val;\n\t\t\t}\n\t\t});\n\t}\n});\n\njQuery.extend({\n\tvalHooks: {\n\t\toption: {\n\t\t\tget: function( elem ) {\n\t\t\t\t// Use proper attribute retrieval(#6932, #12072)\n\t\t\t\tvar val = jQuery.find.attr( elem, \"value\" );\n\t\t\t\treturn val != null ?\n\t\t\t\t\tval :\n\t\t\t\t\telem.text;\n\t\t\t}\n\t\t},\n\t\tselect: {\n\t\t\tget: function( elem ) {\n\t\t\t\tvar value, option,\n\t\t\t\t\toptions = elem.options,\n\t\t\t\t\tindex = elem.selectedIndex,\n\t\t\t\t\tone = elem.type === \"select-one\" || index < 0,\n\t\t\t\t\tvalues = one ? null : [],\n\t\t\t\t\tmax = one ? index + 1 : options.length,\n\t\t\t\t\ti = index < 0 ?\n\t\t\t\t\t\tmax :\n\t\t\t\t\t\tone ? index : 0;\n\n\t\t\t\t// Loop through all the selected options\n\t\t\t\tfor ( ; i < max; i++ ) {\n\t\t\t\t\toption = options[ i ];\n\n\t\t\t\t\t// oldIE doesn't update selected after form reset (#2551)\n\t\t\t\t\tif ( ( option.selected || i === index ) &&\n\t\t\t\t\t\t\t// Don't return options that are disabled or in a disabled optgroup\n\t\t\t\t\t\t\t( jQuery.support.optDisabled ? !option.disabled : option.getAttribute(\"disabled\") === null ) &&\n\t\t\t\t\t\t\t( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, \"optgroup\" ) ) ) {\n\n\t\t\t\t\t\t// Get the specific value for the option\n\t\t\t\t\t\tvalue = jQuery( option ).val();\n\n\t\t\t\t\t\t// We don't need an array for one selects\n\t\t\t\t\t\tif ( one ) {\n\t\t\t\t\t\t\treturn value;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Multi-Selects return an array\n\t\t\t\t\t\tvalues.push( value );\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn values;\n\t\t\t},\n\n\t\t\tset: function( elem, value ) {\n\t\t\t\tvar optionSet, option,\n\t\t\t\t\toptions = elem.options,\n\t\t\t\t\tvalues = jQuery.makeArray( value ),\n\t\t\t\t\ti = options.length;\n\n\t\t\t\twhile ( i-- ) {\n\t\t\t\t\toption = options[ i ];\n\t\t\t\t\tif ( (option.selected = jQuery.inArray( jQuery(option).val(), values ) >= 0) ) {\n\t\t\t\t\t\toptionSet = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// force browsers to behave consistently when non-matching value is set\n\t\t\t\tif ( !optionSet ) {\n\t\t\t\t\telem.selectedIndex = -1;\n\t\t\t\t}\n\t\t\t\treturn values;\n\t\t\t}\n\t\t}\n\t},\n\n\tattr: function( elem, name, value ) {\n\t\tvar hooks, ret,\n\t\t\tnType = elem.nodeType;\n\n\t\t// don't get/set attributes on text, comment and attribute nodes\n\t\tif ( !elem || nType === 3 || nType === 8 || nType === 2 ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Fallback to prop when attributes are not supported\n\t\tif ( typeof elem.getAttribute === core_strundefined ) {\n\t\t\treturn jQuery.prop( elem, name, value );\n\t\t}\n\n\t\t// All attributes are lowercase\n\t\t// Grab necessary hook if one is defined\n\t\tif ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {\n\t\t\tname = name.toLowerCase();\n\t\t\thooks = jQuery.attrHooks[ name ] ||\n\t\t\t\t( jQuery.expr.match.bool.test( name ) ? boolHook : nodeHook );\n\t\t}\n\n\t\tif ( value !== undefined ) {\n\n\t\t\tif ( value === null ) {\n\t\t\t\tjQuery.removeAttr( elem, name );\n\n\t\t\t} else if ( hooks && \"set\" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {\n\t\t\t\treturn ret;\n\n\t\t\t} else {\n\t\t\t\telem.setAttribute( name, value + \"\" );\n\t\t\t\treturn value;\n\t\t\t}\n\n\t\t} else if ( hooks && \"get\" in hooks && (ret = hooks.get( elem, name )) !== null ) {\n\t\t\treturn ret;\n\n\t\t} else {\n\t\t\tret = jQuery.find.attr( elem, name );\n\n\t\t\t// Non-existent attributes return null, we normalize to undefined\n\t\t\treturn ret == null ?\n\t\t\t\tundefined :\n\t\t\t\tret;\n\t\t}\n\t},\n\n\tremoveAttr: function( elem, value ) {\n\t\tvar name, propName,\n\t\t\ti = 0,\n\t\t\tattrNames = value && value.match( core_rnotwhite );\n\n\t\tif ( attrNames && elem.nodeType === 1 ) {\n\t\t\twhile ( (name = attrNames[i++]) ) {\n\t\t\t\tpropName = jQuery.propFix[ name ] || name;\n\n\t\t\t\t// Boolean attributes get special treatment (#10870)\n\t\t\t\tif ( jQuery.expr.match.bool.test( name ) ) {\n\t\t\t\t\t// Set corresponding property to false\n\t\t\t\t\tif ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) {\n\t\t\t\t\t\telem[ propName ] = false;\n\t\t\t\t\t// Support: IE<9\n\t\t\t\t\t// Also clear defaultChecked/defaultSelected (if appropriate)\n\t\t\t\t\t} else {\n\t\t\t\t\t\telem[ jQuery.camelCase( \"default-\" + name ) ] =\n\t\t\t\t\t\t\telem[ propName ] = false;\n\t\t\t\t\t}\n\n\t\t\t\t// See #9699 for explanation of this approach (setting first, then removal)\n\t\t\t\t} else {\n\t\t\t\t\tjQuery.attr( elem, name, \"\" );\n\t\t\t\t}\n\n\t\t\t\telem.removeAttribute( getSetAttribute ? name : propName );\n\t\t\t}\n\t\t}\n\t},\n\n\tattrHooks: {\n\t\ttype: {\n\t\t\tset: function( elem, value ) {\n\t\t\t\tif ( !jQuery.support.radioValue && value === \"radio\" && jQuery.nodeName(elem, \"input\") ) {\n\t\t\t\t\t// Setting the type on a radio button after the value resets the value in IE6-9\n\t\t\t\t\t// Reset value to default in case type is set after value during creation\n\t\t\t\t\tvar val = elem.value;\n\t\t\t\t\telem.setAttribute( \"type\", value );\n\t\t\t\t\tif ( val ) {\n\t\t\t\t\t\telem.value = val;\n\t\t\t\t\t}\n\t\t\t\t\treturn value;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\n\tpropFix: {\n\t\t\"for\": \"htmlFor\",\n\t\t\"class\": \"className\"\n\t},\n\n\tprop: function( elem, name, value ) {\n\t\tvar ret, hooks, notxml,\n\t\t\tnType = elem.nodeType;\n\n\t\t// don't get/set properties on text, comment and attribute nodes\n\t\tif ( !elem || nType === 3 || nType === 8 || nType === 2 ) {\n\t\t\treturn;\n\t\t}\n\n\t\tnotxml = nType !== 1 || !jQuery.isXMLDoc( elem );\n\n\t\tif ( notxml ) {\n\t\t\t// Fix name and attach hooks\n\t\t\tname = jQuery.propFix[ name ] || name;\n\t\t\thooks = jQuery.propHooks[ name ];\n\t\t}\n\n\t\tif ( value !== undefined ) {\n\t\t\treturn hooks && \"set\" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ?\n\t\t\t\tret :\n\t\t\t\t( elem[ name ] = value );\n\n\t\t} else {\n\t\t\treturn hooks && \"get\" in hooks && (ret = hooks.get( elem, name )) !== null ?\n\t\t\t\tret :\n\t\t\t\telem[ name ];\n\t\t}\n\t},\n\n\tpropHooks: {\n\t\ttabIndex: {\n\t\t\tget: function( elem ) {\n\t\t\t\t// elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set\n\t\t\t\t// http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/\n\t\t\t\t// Use proper attribute retrieval(#12072)\n\t\t\t\tvar tabindex = jQuery.find.attr( elem, \"tabindex\" );\n\n\t\t\t\treturn tabindex ?\n\t\t\t\t\tparseInt( tabindex, 10 ) :\n\t\t\t\t\trfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?\n\t\t\t\t\t\t0 :\n\t\t\t\t\t\t-1;\n\t\t\t}\n\t\t}\n\t}\n});\n\n// Hooks for boolean attributes\nboolHook = {\n\tset: function( elem, value, name ) {\n\t\tif ( value === false ) {\n\t\t\t// Remove boolean attributes when set to false\n\t\t\tjQuery.removeAttr( elem, name );\n\t\t} else if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) {\n\t\t\t// IE<8 needs the *property* name\n\t\t\telem.setAttribute( !getSetAttribute && jQuery.propFix[ name ] || name, name );\n\n\t\t// Use defaultChecked and defaultSelected for oldIE\n\t\t} else {\n\t\t\telem[ jQuery.camelCase( \"default-\" + name ) ] = elem[ name ] = true;\n\t\t}\n\n\t\treturn name;\n\t}\n};\njQuery.each( jQuery.expr.match.bool.source.match( /\\w+/g ), function( i, name ) {\n\tvar getter = jQuery.expr.attrHandle[ name ] || jQuery.find.attr;\n\n\tjQuery.expr.attrHandle[ name ] = getSetInput && getSetAttribute || !ruseDefault.test( name ) ?\n\t\tfunction( elem, name, isXML ) {\n\t\t\tvar fn = jQuery.expr.attrHandle[ name ],\n\t\t\t\tret = isXML ?\n\t\t\t\t\tundefined :\n\t\t\t\t\t/* jshint eqeqeq: false */\n\t\t\t\t\t(jQuery.expr.attrHandle[ name ] = undefined) !=\n\t\t\t\t\t\tgetter( elem, name, isXML ) ?\n\n\t\t\t\t\t\tname.toLowerCase() :\n\t\t\t\t\t\tnull;\n\t\t\tjQuery.expr.attrHandle[ name ] = fn;\n\t\t\treturn ret;\n\t\t} :\n\t\tfunction( elem, name, isXML ) {\n\t\t\treturn isXML ?\n\t\t\t\tundefined :\n\t\t\t\telem[ jQuery.camelCase( \"default-\" + name ) ] ?\n\t\t\t\t\tname.toLowerCase() :\n\t\t\t\t\tnull;\n\t\t};\n});\n\n// fix oldIE attroperties\nif ( !getSetInput || !getSetAttribute ) {\n\tjQuery.attrHooks.value = {\n\t\tset: function( elem, value, name ) {\n\t\t\tif ( jQuery.nodeName( elem, \"input\" ) ) {\n\t\t\t\t// Does not return so that setAttribute is also used\n\t\t\t\telem.defaultValue = value;\n\t\t\t} else {\n\t\t\t\t// Use nodeHook if defined (#1954); otherwise setAttribute is fine\n\t\t\t\treturn nodeHook && nodeHook.set( elem, value, name );\n\t\t\t}\n\t\t}\n\t};\n}\n\n// IE6/7 do not support getting/setting some attributes with get/setAttribute\nif ( !getSetAttribute ) {\n\n\t// Use this for any attribute in IE6/7\n\t// This fixes almost every IE6/7 issue\n\tnodeHook = {\n\t\tset: function( elem, value, name ) {\n\t\t\t// Set the existing or create a new attribute node\n\t\t\tvar ret = elem.getAttributeNode( name );\n\t\t\tif ( !ret ) {\n\t\t\t\telem.setAttributeNode(\n\t\t\t\t\t(ret = elem.ownerDocument.createAttribute( name ))\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tret.value = value += \"\";\n\n\t\t\t// Break association with cloned elements by also using setAttribute (#9646)\n\t\t\treturn name === \"value\" || value === elem.getAttribute( name ) ?\n\t\t\t\tvalue :\n\t\t\t\tundefined;\n\t\t}\n\t};\n\tjQuery.expr.attrHandle.id = jQuery.expr.attrHandle.name = jQuery.expr.attrHandle.coords =\n\t\t// Some attributes are constructed with empty-string values when not defined\n\t\tfunction( elem, name, isXML ) {\n\t\t\tvar ret;\n\t\t\treturn isXML ?\n\t\t\t\tundefined :\n\t\t\t\t(ret = elem.getAttributeNode( name )) && ret.value !== \"\" ?\n\t\t\t\t\tret.value :\n\t\t\t\t\tnull;\n\t\t};\n\tjQuery.valHooks.button = {\n\t\tget: function( elem, name ) {\n\t\t\tvar ret = elem.getAttributeNode( name );\n\t\t\treturn ret && ret.specified ?\n\t\t\t\tret.value :\n\t\t\t\tundefined;\n\t\t},\n\t\tset: nodeHook.set\n\t};\n\n\t// Set contenteditable to false on removals(#10429)\n\t// Setting to empty string throws an error as an invalid value\n\tjQuery.attrHooks.contenteditable = {\n\t\tset: function( elem, value, name ) {\n\t\t\tnodeHook.set( elem, value === \"\" ? false : value, name );\n\t\t}\n\t};\n\n\t// Set width and height to auto instead of 0 on empty string( Bug #8150 )\n\t// This is for removals\n\tjQuery.each([ \"width\", \"height\" ], function( i, name ) {\n\t\tjQuery.attrHooks[ name ] = {\n\t\t\tset: function( elem, value ) {\n\t\t\t\tif ( value === \"\" ) {\n\t\t\t\t\telem.setAttribute( name, \"auto\" );\n\t\t\t\t\treturn value;\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t});\n}\n\n\n// Some attributes require a special call on IE\n// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx\nif ( !jQuery.support.hrefNormalized ) {\n\t// href/src property should get the full normalized URL (#10299/#12915)\n\tjQuery.each([ \"href\", \"src\" ], function( i, name ) {\n\t\tjQuery.propHooks[ name ] = {\n\t\t\tget: function( elem ) {\n\t\t\t\treturn elem.getAttribute( name, 4 );\n\t\t\t}\n\t\t};\n\t});\n}\n\nif ( !jQuery.support.style ) {\n\tjQuery.attrHooks.style = {\n\t\tget: function( elem ) {\n\t\t\t// Return undefined in the case of empty string\n\t\t\t// Note: IE uppercases css property names, but if we were to .toLowerCase()\n\t\t\t// .cssText, that would destroy case senstitivity in URL's, like in \"background\"\n\t\t\treturn elem.style.cssText || undefined;\n\t\t},\n\t\tset: function( elem, value ) {\n\t\t\treturn ( elem.style.cssText = value + \"\" );\n\t\t}\n\t};\n}\n\n// Safari mis-reports the default selected property of an option\n// Accessing the parent's selectedIndex property fixes it\nif ( !jQuery.support.optSelected ) {\n\tjQuery.propHooks.selected = {\n\t\tget: function( elem ) {\n\t\t\tvar parent = elem.parentNode;\n\n\t\t\tif ( parent ) {\n\t\t\t\tparent.selectedIndex;\n\n\t\t\t\t// Make sure that it also works with optgroups, see #5701\n\t\t\t\tif ( parent.parentNode ) {\n\t\t\t\t\tparent.parentNode.selectedIndex;\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn null;\n\t\t}\n\t};\n}\n\njQuery.each([\n\t\"tabIndex\",\n\t\"readOnly\",\n\t\"maxLength\",\n\t\"cellSpacing\",\n\t\"cellPadding\",\n\t\"rowSpan\",\n\t\"colSpan\",\n\t\"useMap\",\n\t\"frameBorder\",\n\t\"contentEditable\"\n], function() {\n\tjQuery.propFix[ this.toLowerCase() ] = this;\n});\n\n// IE6/7 call enctype encoding\nif ( !jQuery.support.enctype ) {\n\tjQuery.propFix.enctype = \"encoding\";\n}\n\n// Radios and checkboxes getter/setter\njQuery.each([ \"radio\", \"checkbox\" ], function() {\n\tjQuery.valHooks[ this ] = {\n\t\tset: function( elem, value ) {\n\t\t\tif ( jQuery.isArray( value ) ) {\n\t\t\t\treturn ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );\n\t\t\t}\n\t\t}\n\t};\n\tif ( !jQuery.support.checkOn ) {\n\t\tjQuery.valHooks[ this ].get = function( elem ) {\n\t\t\t// Support: Webkit\n\t\t\t// \"\" is returned instead of \"on\" if a value isn't specified\n\t\t\treturn elem.getAttribute(\"value\") === null ? \"on\" : elem.value;\n\t\t};\n\t}\n});\nvar rformElems = /^(?:input|select|textarea)$/i,\n\trkeyEvent = /^key/,\n\trmouseEvent = /^(?:mouse|contextmenu)|click/,\n\trfocusMorph = /^(?:focusinfocus|focusoutblur)$/,\n\trtypenamespace = /^([^.]*)(?:\\.(.+)|)$/;\n\nfunction returnTrue() {\n\treturn true;\n}\n\nfunction returnFalse() {\n\treturn false;\n}\n\nfunction safeActiveElement() {\n\ttry {\n\t\treturn document.activeElement;\n\t} catch ( err ) { }\n}\n\n/*\n * Helper functions for managing events -- not part of the public interface.\n * Props to Dean Edwards' addEvent library for many of the ideas.\n */\njQuery.event = {\n\n\tglobal: {},\n\n\tadd: function( elem, types, handler, data, selector ) {\n\t\tvar tmp, events, t, handleObjIn,\n\t\t\tspecial, eventHandle, handleObj,\n\t\t\thandlers, type, namespaces, origType,\n\t\t\telemData = jQuery._data( elem );\n\n\t\t// Don't attach events to noData or text/comment nodes (but allow plain objects)\n\t\tif ( !elemData ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Caller can pass in an object of custom data in lieu of the handler\n\t\tif ( handler.handler ) {\n\t\t\thandleObjIn = handler;\n\t\t\thandler = handleObjIn.handler;\n\t\t\tselector = handleObjIn.selector;\n\t\t}\n\n\t\t// Make sure that the handler has a unique ID, used to find/remove it later\n\t\tif ( !handler.guid ) {\n\t\t\thandler.guid = jQuery.guid++;\n\t\t}\n\n\t\t// Init the element's event structure and main handler, if this is the first\n\t\tif ( !(events = elemData.events) ) {\n\t\t\tevents = elemData.events = {};\n\t\t}\n\t\tif ( !(eventHandle = elemData.handle) ) {\n\t\t\teventHandle = elemData.handle = function( e ) {\n\t\t\t\t// Discard the second event of a jQuery.event.trigger() and\n\t\t\t\t// when an event is called after a page has unloaded\n\t\t\t\treturn typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ?\n\t\t\t\t\tjQuery.event.dispatch.apply( eventHandle.elem, arguments ) :\n\t\t\t\t\tundefined;\n\t\t\t};\n\t\t\t// Add elem as a property of the handle fn to prevent a memory leak with IE non-native events\n\t\t\teventHandle.elem = elem;\n\t\t}\n\n\t\t// Handle multiple events separated by a space\n\t\ttypes = ( types || \"\" ).match( core_rnotwhite ) || [\"\"];\n\t\tt = types.length;\n\t\twhile ( t-- ) {\n\t\t\ttmp = rtypenamespace.exec( types[t] ) || [];\n\t\t\ttype = origType = tmp[1];\n\t\t\tnamespaces = ( tmp[2] || \"\" ).split( \".\" ).sort();\n\n\t\t\t// There *must* be a type, no attaching namespace-only handlers\n\t\t\tif ( !type ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\t// If event changes its type, use the special event handlers for the changed type\n\t\t\tspecial = jQuery.event.special[ type ] || {};\n\n\t\t\t// If selector defined, determine special event api type, otherwise given type\n\t\t\ttype = ( selector ? special.delegateType : special.bindType ) || type;\n\n\t\t\t// Update special based on newly reset type\n\t\t\tspecial = jQuery.event.special[ type ] || {};\n\n\t\t\t// handleObj is passed to all event handlers\n\t\t\thandleObj = jQuery.extend({\n\t\t\t\ttype: type,\n\t\t\t\torigType: origType,\n\t\t\t\tdata: data,\n\t\t\t\thandler: handler,\n\t\t\t\tguid: handler.guid,\n\t\t\t\tselector: selector,\n\t\t\t\tneedsContext: selector && jQuery.expr.match.needsContext.test( selector ),\n\t\t\t\tnamespace: namespaces.join(\".\")\n\t\t\t}, handleObjIn );\n\n\t\t\t// Init the event handler queue if we're the first\n\t\t\tif ( !(handlers = events[ type ]) ) {\n\t\t\t\thandlers = events[ type ] = [];\n\t\t\t\thandlers.delegateCount = 0;\n\n\t\t\t\t// Only use addEventListener/attachEvent if the special events handler returns false\n\t\t\t\tif ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {\n\t\t\t\t\t// Bind the global event handler to the element\n\t\t\t\t\tif ( elem.addEventListener ) {\n\t\t\t\t\t\telem.addEventListener( type, eventHandle, false );\n\n\t\t\t\t\t} else if ( elem.attachEvent ) {\n\t\t\t\t\t\telem.attachEvent( \"on\" + type, eventHandle );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif ( special.add ) {\n\t\t\t\tspecial.add.call( elem, handleObj );\n\n\t\t\t\tif ( !handleObj.handler.guid ) {\n\t\t\t\t\thandleObj.handler.guid = handler.guid;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Add to the element's handler list, delegates in front\n\t\t\tif ( selector ) {\n\t\t\t\thandlers.splice( handlers.delegateCount++, 0, handleObj );\n\t\t\t} else {\n\t\t\t\thandlers.push( handleObj );\n\t\t\t}\n\n\t\t\t// Keep track of which events have ever been used, for event optimization\n\t\t\tjQuery.event.global[ type ] = true;\n\t\t}\n\n\t\t// Nullify elem to prevent memory leaks in IE\n\t\telem = null;\n\t},\n\n\t// Detach an event or set of events from an element\n\tremove: function( elem, types, handler, selector, mappedTypes ) {\n\t\tvar j, handleObj, tmp,\n\t\t\torigCount, t, events,\n\t\t\tspecial, handlers, type,\n\t\t\tnamespaces, origType,\n\t\t\telemData = jQuery.hasData( elem ) && jQuery._data( elem );\n\n\t\tif ( !elemData || !(events = elemData.events) ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Once for each type.namespace in types; type may be omitted\n\t\ttypes = ( types || \"\" ).match( core_rnotwhite ) || [\"\"];\n\t\tt = types.length;\n\t\twhile ( t-- ) {\n\t\t\ttmp = rtypenamespace.exec( types[t] ) || [];\n\t\t\ttype = origType = tmp[1];\n\t\t\tnamespaces = ( tmp[2] || \"\" ).split( \".\" ).sort();\n\n\t\t\t// Unbind all events (on this namespace, if provided) for the element\n\t\t\tif ( !type ) {\n\t\t\t\tfor ( type in events ) {\n\t\t\t\t\tjQuery.event.remove( elem, type + types[ t ], handler, selector, true );\n\t\t\t\t}\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tspecial = jQuery.event.special[ type ] || {};\n\t\t\ttype = ( selector ? special.delegateType : special.bindType ) || type;\n\t\t\thandlers = events[ type ] || [];\n\t\t\ttmp = tmp[2] && new RegExp( \"(^|\\\\.)\" + namespaces.join(\"\\\\.(?:.*\\\\.|)\") + \"(\\\\.|$)\" );\n\n\t\t\t// Remove matching events\n\t\t\torigCount = j = handlers.length;\n\t\t\twhile ( j-- ) {\n\t\t\t\thandleObj = handlers[ j ];\n\n\t\t\t\tif ( ( mappedTypes || origType === handleObj.origType ) &&\n\t\t\t\t\t( !handler || handler.guid === handleObj.guid ) &&\n\t\t\t\t\t( !tmp || tmp.test( handleObj.namespace ) ) &&\n\t\t\t\t\t( !selector || selector === handleObj.selector || selector === \"**\" && handleObj.selector ) ) {\n\t\t\t\t\thandlers.splice( j, 1 );\n\n\t\t\t\t\tif ( handleObj.selector ) {\n\t\t\t\t\t\thandlers.delegateCount--;\n\t\t\t\t\t}\n\t\t\t\t\tif ( special.remove ) {\n\t\t\t\t\t\tspecial.remove.call( elem, handleObj );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Remove generic event handler if we removed something and no more handlers exist\n\t\t\t// (avoids potential for endless recursion during removal of special event handlers)\n\t\t\tif ( origCount && !handlers.length ) {\n\t\t\t\tif ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {\n\t\t\t\t\tjQuery.removeEvent( elem, type, elemData.handle );\n\t\t\t\t}\n\n\t\t\t\tdelete events[ type ];\n\t\t\t}\n\t\t}\n\n\t\t// Remove the expando if it's no longer used\n\t\tif ( jQuery.isEmptyObject( events ) ) {\n\t\t\tdelete elemData.handle;\n\n\t\t\t// removeData also checks for emptiness and clears the expando if empty\n\t\t\t// so use it instead of delete\n\t\t\tjQuery._removeData( elem, \"events\" );\n\t\t}\n\t},\n\n\ttrigger: function( event, data, elem, onlyHandlers ) {\n\t\tvar handle, ontype, cur,\n\t\t\tbubbleType, special, tmp, i,\n\t\t\teventPath = [ elem || document ],\n\t\t\ttype = core_hasOwn.call( event, \"type\" ) ? event.type : event,\n\t\t\tnamespaces = core_hasOwn.call( event, \"namespace\" ) ? event.namespace.split(\".\") : [];\n\n\t\tcur = tmp = elem = elem || document;\n\n\t\t// Don't do events on text and comment nodes\n\t\tif ( elem.nodeType === 3 || elem.nodeType === 8 ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// focus/blur morphs to focusin/out; ensure we're not firing them right now\n\t\tif ( rfocusMorph.test( type + jQuery.event.triggered ) ) {\n\t\t\treturn;\n\t\t}\n\n\t\tif ( type.indexOf(\".\") >= 0 ) {\n\t\t\t// Namespaced trigger; create a regexp to match event type in handle()\n\t\t\tnamespaces = type.split(\".\");\n\t\t\ttype = namespaces.shift();\n\t\t\tnamespaces.sort();\n\t\t}\n\t\tontype = type.indexOf(\":\") < 0 && \"on\" + type;\n\n\t\t// Caller can pass in a jQuery.Event object, Object, or just an event type string\n\t\tevent = event[ jQuery.expando ] ?\n\t\t\tevent :\n\t\t\tnew jQuery.Event( type, typeof event === \"object\" && event );\n\n\t\t// Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)\n\t\tevent.isTrigger = onlyHandlers ? 2 : 3;\n\t\tevent.namespace = namespaces.join(\".\");\n\t\tevent.namespace_re = event.namespace ?\n\t\t\tnew RegExp( \"(^|\\\\.)\" + namespaces.join(\"\\\\.(?:.*\\\\.|)\") + \"(\\\\.|$)\" ) :\n\t\t\tnull;\n\n\t\t// Clean up the event in case it is being reused\n\t\tevent.result = undefined;\n\t\tif ( !event.target ) {\n\t\t\tevent.target = elem;\n\t\t}\n\n\t\t// Clone any incoming data and prepend the event, creating the handler arg list\n\t\tdata = data == null ?\n\t\t\t[ event ] :\n\t\t\tjQuery.makeArray( data, [ event ] );\n\n\t\t// Allow special events to draw outside the lines\n\t\tspecial = jQuery.event.special[ type ] || {};\n\t\tif ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Determine event propagation path in advance, per W3C events spec (#9951)\n\t\t// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)\n\t\tif ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {\n\n\t\t\tbubbleType = special.delegateType || type;\n\t\t\tif ( !rfocusMorph.test( bubbleType + type ) ) {\n\t\t\t\tcur = cur.parentNode;\n\t\t\t}\n\t\t\tfor ( ; cur; cur = cur.parentNode ) {\n\t\t\t\teventPath.push( cur );\n\t\t\t\ttmp = cur;\n\t\t\t}\n\n\t\t\t// Only add window if we got to document (e.g., not plain obj or detached DOM)\n\t\t\tif ( tmp === (elem.ownerDocument || document) ) {\n\t\t\t\teventPath.push( tmp.defaultView || tmp.parentWindow || window );\n\t\t\t}\n\t\t}\n\n\t\t// Fire handlers on the event path\n\t\ti = 0;\n\t\twhile ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) {\n\n\t\t\tevent.type = i > 1 ?\n\t\t\t\tbubbleType :\n\t\t\t\tspecial.bindType || type;\n\n\t\t\t// jQuery handler\n\t\t\thandle = ( jQuery._data( cur, \"events\" ) || {} )[ event.type ] && jQuery._data( cur, \"handle\" );\n\t\t\tif ( handle ) {\n\t\t\t\thandle.apply( cur, data );\n\t\t\t}\n\n\t\t\t// Native handler\n\t\t\thandle = ontype && cur[ ontype ];\n\t\t\tif ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) {\n\t\t\t\tevent.preventDefault();\n\t\t\t}\n\t\t}\n\t\tevent.type = type;\n\n\t\t// If nobody prevented the default action, do it now\n\t\tif ( !onlyHandlers && !event.isDefaultPrevented() ) {\n\n\t\t\tif ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) &&\n\t\t\t\tjQuery.acceptData( elem ) ) {\n\n\t\t\t\t// Call a native DOM method on the target with the same name name as the event.\n\t\t\t\t// Can't use an .isFunction() check here because IE6/7 fails that test.\n\t\t\t\t// Don't do default actions on window, that's where global variables be (#6170)\n\t\t\t\tif ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) {\n\n\t\t\t\t\t// Don't re-trigger an onFOO event when we call its FOO() method\n\t\t\t\t\ttmp = elem[ ontype ];\n\n\t\t\t\t\tif ( tmp ) {\n\t\t\t\t\t\telem[ ontype ] = null;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Prevent re-triggering of the same event, since we already bubbled it above\n\t\t\t\t\tjQuery.event.triggered = type;\n\t\t\t\t\ttry {\n\t\t\t\t\t\telem[ type ]();\n\t\t\t\t\t} catch ( e ) {\n\t\t\t\t\t\t// IE<9 dies on focus/blur to hidden element (#1486,#12518)\n\t\t\t\t\t\t// only reproducible on winXP IE8 native, not IE9 in IE8 mode\n\t\t\t\t\t}\n\t\t\t\t\tjQuery.event.triggered = undefined;\n\n\t\t\t\t\tif ( tmp ) {\n\t\t\t\t\t\telem[ ontype ] = tmp;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn event.result;\n\t},\n\n\tdispatch: function( event ) {\n\n\t\t// Make a writable jQuery.Event from the native event object\n\t\tevent = jQuery.event.fix( event );\n\n\t\tvar i, ret, handleObj, matched, j,\n\t\t\thandlerQueue = [],\n\t\t\targs = core_slice.call( arguments ),\n\t\t\thandlers = ( jQuery._data( this, \"events\" ) || {} )[ event.type ] || [],\n\t\t\tspecial = jQuery.event.special[ event.type ] || {};\n\n\t\t// Use the fix-ed jQuery.Event rather than the (read-only) native event\n\t\targs[0] = event;\n\t\tevent.delegateTarget = this;\n\n\t\t// Call the preDispatch hook for the mapped type, and let it bail if desired\n\t\tif ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Determine handlers\n\t\thandlerQueue = jQuery.event.handlers.call( this, event, handlers );\n\n\t\t// Run delegates first; they may want to stop propagation beneath us\n\t\ti = 0;\n\t\twhile ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) {\n\t\t\tevent.currentTarget = matched.elem;\n\n\t\t\tj = 0;\n\t\t\twhile ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) {\n\n\t\t\t\t// Triggered event must either 1) have no namespace, or\n\t\t\t\t// 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).\n\t\t\t\tif ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) {\n\n\t\t\t\t\tevent.handleObj = handleObj;\n\t\t\t\t\tevent.data = handleObj.data;\n\n\t\t\t\t\tret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )\n\t\t\t\t\t\t\t.apply( matched.elem, args );\n\n\t\t\t\t\tif ( ret !== undefined ) {\n\t\t\t\t\t\tif ( (event.result = ret) === false ) {\n\t\t\t\t\t\t\tevent.preventDefault();\n\t\t\t\t\t\t\tevent.stopPropagation();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Call the postDispatch hook for the mapped type\n\t\tif ( special.postDispatch ) {\n\t\t\tspecial.postDispatch.call( this, event );\n\t\t}\n\n\t\treturn event.result;\n\t},\n\n\thandlers: function( event, handlers ) {\n\t\tvar sel, handleObj, matches, i,\n\t\t\thandlerQueue = [],\n\t\t\tdelegateCount = handlers.delegateCount,\n\t\t\tcur = event.target;\n\n\t\t// Find delegate handlers\n\t\t// Black-hole SVG <use> instance trees (#13180)\n\t\t// Avoid non-left-click bubbling in Firefox (#3861)\n\t\tif ( delegateCount && cur.nodeType && (!event.button || event.type !== \"click\") ) {\n\n\t\t\t/* jshint eqeqeq: false */\n\t\t\tfor ( ; cur != this; cur = cur.parentNode || this ) {\n\t\t\t\t/* jshint eqeqeq: true */\n\n\t\t\t\t// Don't check non-elements (#13208)\n\t\t\t\t// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)\n\t\t\t\tif ( cur.nodeType === 1 && (cur.disabled !== true || event.type !== \"click\") ) {\n\t\t\t\t\tmatches = [];\n\t\t\t\t\tfor ( i = 0; i < delegateCount; i++ ) {\n\t\t\t\t\t\thandleObj = handlers[ i ];\n\n\t\t\t\t\t\t// Don't conflict with Object.prototype properties (#13203)\n\t\t\t\t\t\tsel = handleObj.selector + \" \";\n\n\t\t\t\t\t\tif ( matches[ sel ] === undefined ) {\n\t\t\t\t\t\t\tmatches[ sel ] = handleObj.needsContext ?\n\t\t\t\t\t\t\t\tjQuery( sel, this ).index( cur ) >= 0 :\n\t\t\t\t\t\t\t\tjQuery.find( sel, this, null, [ cur ] ).length;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif ( matches[ sel ] ) {\n\t\t\t\t\t\t\tmatches.push( handleObj );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\tif ( matches.length ) {\n\t\t\t\t\t\thandlerQueue.push({ elem: cur, handlers: matches });\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Add the remaining (directly-bound) handlers\n\t\tif ( delegateCount < handlers.length ) {\n\t\t\thandlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) });\n\t\t}\n\n\t\treturn handlerQueue;\n\t},\n\n\tfix: function( event ) {\n\t\tif ( event[ jQuery.expando ] ) {\n\t\t\treturn event;\n\t\t}\n\n\t\t// Create a writable copy of the event object and normalize some properties\n\t\tvar i, prop, copy,\n\t\t\ttype = event.type,\n\t\t\toriginalEvent = event,\n\t\t\tfixHook = this.fixHooks[ type ];\n\n\t\tif ( !fixHook ) {\n\t\t\tthis.fixHooks[ type ] = fixHook =\n\t\t\t\trmouseEvent.test( type ) ? this.mouseHooks :\n\t\t\t\trkeyEvent.test( type ) ? this.keyHooks :\n\t\t\t\t{};\n\t\t}\n\t\tcopy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;\n\n\t\tevent = new jQuery.Event( originalEvent );\n\n\t\ti = copy.length;\n\t\twhile ( i-- ) {\n\t\t\tprop = copy[ i ];\n\t\t\tevent[ prop ] = originalEvent[ prop ];\n\t\t}\n\n\t\t// Support: IE<9\n\t\t// Fix target property (#1925)\n\t\tif ( !event.target ) {\n\t\t\tevent.target = originalEvent.srcElement || document;\n\t\t}\n\n\t\t// Support: Chrome 23+, Safari?\n\t\t// Target should not be a text node (#504, #13143)\n\t\tif ( event.target.nodeType === 3 ) {\n\t\t\tevent.target = event.target.parentNode;\n\t\t}\n\n\t\t// Support: IE<9\n\t\t// For mouse/key events, metaKey==false if it's undefined (#3368, #11328)\n\t\tevent.metaKey = !!event.metaKey;\n\n\t\treturn fixHook.filter ? fixHook.filter( event, originalEvent ) : event;\n\t},\n\n\t// Includes some event props shared by KeyEvent and MouseEvent\n\tprops: \"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which\".split(\" \"),\n\n\tfixHooks: {},\n\n\tkeyHooks: {\n\t\tprops: \"char charCode key keyCode\".split(\" \"),\n\t\tfilter: function( event, original ) {\n\n\t\t\t// Add which for key events\n\t\t\tif ( event.which == null ) {\n\t\t\t\tevent.which = original.charCode != null ? original.charCode : original.keyCode;\n\t\t\t}\n\n\t\t\treturn event;\n\t\t}\n\t},\n\n\tmouseHooks: {\n\t\tprops: \"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement\".split(\" \"),\n\t\tfilter: function( event, original ) {\n\t\t\tvar body, eventDoc, doc,\n\t\t\t\tbutton = original.button,\n\t\t\t\tfromElement = original.fromElement;\n\n\t\t\t// Calculate pageX/Y if missing and clientX/Y available\n\t\t\tif ( event.pageX == null && original.clientX != null ) {\n\t\t\t\teventDoc = event.target.ownerDocument || document;\n\t\t\t\tdoc = eventDoc.documentElement;\n\t\t\t\tbody = eventDoc.body;\n\n\t\t\t\tevent.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );\n\t\t\t\tevent.pageY = original.clientY + ( doc && doc.scrollTop  || body && body.scrollTop  || 0 ) - ( doc && doc.clientTop  || body && body.clientTop  || 0 );\n\t\t\t}\n\n\t\t\t// Add relatedTarget, if necessary\n\t\t\tif ( !event.relatedTarget && fromElement ) {\n\t\t\t\tevent.relatedTarget = fromElement === event.target ? original.toElement : fromElement;\n\t\t\t}\n\n\t\t\t// Add which for click: 1 === left; 2 === middle; 3 === right\n\t\t\t// Note: button is not normalized, so don't use it\n\t\t\tif ( !event.which && button !== undefined ) {\n\t\t\t\tevent.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );\n\t\t\t}\n\n\t\t\treturn event;\n\t\t}\n\t},\n\n\tspecial: {\n\t\tload: {\n\t\t\t// Prevent triggered image.load events from bubbling to window.load\n\t\t\tnoBubble: true\n\t\t},\n\t\tfocus: {\n\t\t\t// Fire native event if possible so blur/focus sequence is correct\n\t\t\ttrigger: function() {\n\t\t\t\tif ( this !== safeActiveElement() && this.focus ) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tthis.focus();\n\t\t\t\t\t\treturn false;\n\t\t\t\t\t} catch ( e ) {\n\t\t\t\t\t\t// Support: IE<9\n\t\t\t\t\t\t// If we error on focus to hidden element (#1486, #12518),\n\t\t\t\t\t\t// let .trigger() run the handlers\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t},\n\t\t\tdelegateType: \"focusin\"\n\t\t},\n\t\tblur: {\n\t\t\ttrigger: function() {\n\t\t\t\tif ( this === safeActiveElement() && this.blur ) {\n\t\t\t\t\tthis.blur();\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t},\n\t\t\tdelegateType: \"focusout\"\n\t\t},\n\t\tclick: {\n\t\t\t// For checkbox, fire native event so checked state will be right\n\t\t\ttrigger: function() {\n\t\t\t\tif ( jQuery.nodeName( this, \"input\" ) && this.type === \"checkbox\" && this.click ) {\n\t\t\t\t\tthis.click();\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t},\n\n\t\t\t// For cross-browser consistency, don't fire native .click() on links\n\t\t\t_default: function( event ) {\n\t\t\t\treturn jQuery.nodeName( event.target, \"a\" );\n\t\t\t}\n\t\t},\n\n\t\tbeforeunload: {\n\t\t\tpostDispatch: function( event ) {\n\n\t\t\t\t// Even when returnValue equals to undefined Firefox will still show alert\n\t\t\t\tif ( event.result !== undefined ) {\n\t\t\t\t\tevent.originalEvent.returnValue = event.result;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\n\tsimulate: function( type, elem, event, bubble ) {\n\t\t// Piggyback on a donor event to simulate a different one.\n\t\t// Fake originalEvent to avoid donor's stopPropagation, but if the\n\t\t// simulated event prevents default then we do the same on the donor.\n\t\tvar e = jQuery.extend(\n\t\t\tnew jQuery.Event(),\n\t\t\tevent,\n\t\t\t{\n\t\t\t\ttype: type,\n\t\t\t\tisSimulated: true,\n\t\t\t\toriginalEvent: {}\n\t\t\t}\n\t\t);\n\t\tif ( bubble ) {\n\t\t\tjQuery.event.trigger( e, null, elem );\n\t\t} else {\n\t\t\tjQuery.event.dispatch.call( elem, e );\n\t\t}\n\t\tif ( e.isDefaultPrevented() ) {\n\t\t\tevent.preventDefault();\n\t\t}\n\t}\n};\n\njQuery.removeEvent = document.removeEventListener ?\n\tfunction( elem, type, handle ) {\n\t\tif ( elem.removeEventListener ) {\n\t\t\telem.removeEventListener( type, handle, false );\n\t\t}\n\t} :\n\tfunction( elem, type, handle ) {\n\t\tvar name = \"on\" + type;\n\n\t\tif ( elem.detachEvent ) {\n\n\t\t\t// #8545, #7054, preventing memory leaks for custom events in IE6-8\n\t\t\t// detachEvent needed property on element, by name of that event, to properly expose it to GC\n\t\t\tif ( typeof elem[ name ] === core_strundefined ) {\n\t\t\t\telem[ name ] = null;\n\t\t\t}\n\n\t\t\telem.detachEvent( name, handle );\n\t\t}\n\t};\n\njQuery.Event = function( src, props ) {\n\t// Allow instantiation without the 'new' keyword\n\tif ( !(this instanceof jQuery.Event) ) {\n\t\treturn new jQuery.Event( src, props );\n\t}\n\n\t// Event object\n\tif ( src && src.type ) {\n\t\tthis.originalEvent = src;\n\t\tthis.type = src.type;\n\n\t\t// Events bubbling up the document may have been marked as prevented\n\t\t// by a handler lower down the tree; reflect the correct value.\n\t\tthis.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false ||\n\t\t\tsrc.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse;\n\n\t// Event type\n\t} else {\n\t\tthis.type = src;\n\t}\n\n\t// Put explicitly provided properties onto the event object\n\tif ( props ) {\n\t\tjQuery.extend( this, props );\n\t}\n\n\t// Create a timestamp if incoming event doesn't have one\n\tthis.timeStamp = src && src.timeStamp || jQuery.now();\n\n\t// Mark it as fixed\n\tthis[ jQuery.expando ] = true;\n};\n\n// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding\n// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html\njQuery.Event.prototype = {\n\tisDefaultPrevented: returnFalse,\n\tisPropagationStopped: returnFalse,\n\tisImmediatePropagationStopped: returnFalse,\n\n\tpreventDefault: function() {\n\t\tvar e = this.originalEvent;\n\n\t\tthis.isDefaultPrevented = returnTrue;\n\t\tif ( !e ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// If preventDefault exists, run it on the original event\n\t\tif ( e.preventDefault ) {\n\t\t\te.preventDefault();\n\n\t\t// Support: IE\n\t\t// Otherwise set the returnValue property of the original event to false\n\t\t} else {\n\t\t\te.returnValue = false;\n\t\t}\n\t},\n\tstopPropagation: function() {\n\t\tvar e = this.originalEvent;\n\n\t\tthis.isPropagationStopped = returnTrue;\n\t\tif ( !e ) {\n\t\t\treturn;\n\t\t}\n\t\t// If stopPropagation exists, run it on the original event\n\t\tif ( e.stopPropagation ) {\n\t\t\te.stopPropagation();\n\t\t}\n\n\t\t// Support: IE\n\t\t// Set the cancelBubble property of the original event to true\n\t\te.cancelBubble = true;\n\t},\n\tstopImmediatePropagation: function() {\n\t\tthis.isImmediatePropagationStopped = returnTrue;\n\t\tthis.stopPropagation();\n\t}\n};\n\n// Create mouseenter/leave events using mouseover/out and event-time checks\njQuery.each({\n\tmouseenter: \"mouseover\",\n\tmouseleave: \"mouseout\"\n}, function( orig, fix ) {\n\tjQuery.event.special[ orig ] = {\n\t\tdelegateType: fix,\n\t\tbindType: fix,\n\n\t\thandle: function( event ) {\n\t\t\tvar ret,\n\t\t\t\ttarget = this,\n\t\t\t\trelated = event.relatedTarget,\n\t\t\t\thandleObj = event.handleObj;\n\n\t\t\t// For mousenter/leave call the handler if related is outside the target.\n\t\t\t// NB: No relatedTarget if the mouse left/entered the browser window\n\t\t\tif ( !related || (related !== target && !jQuery.contains( target, related )) ) {\n\t\t\t\tevent.type = handleObj.origType;\n\t\t\t\tret = handleObj.handler.apply( this, arguments );\n\t\t\t\tevent.type = fix;\n\t\t\t}\n\t\t\treturn ret;\n\t\t}\n\t};\n});\n\n// IE submit delegation\nif ( !jQuery.support.submitBubbles ) {\n\n\tjQuery.event.special.submit = {\n\t\tsetup: function() {\n\t\t\t// Only need this for delegated form submit events\n\t\t\tif ( jQuery.nodeName( this, \"form\" ) ) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// Lazy-add a submit handler when a descendant form may potentially be submitted\n\t\t\tjQuery.event.add( this, \"click._submit keypress._submit\", function( e ) {\n\t\t\t\t// Node name check avoids a VML-related crash in IE (#9807)\n\t\t\t\tvar elem = e.target,\n\t\t\t\t\tform = jQuery.nodeName( elem, \"input\" ) || jQuery.nodeName( elem, \"button\" ) ? elem.form : undefined;\n\t\t\t\tif ( form && !jQuery._data( form, \"submitBubbles\" ) ) {\n\t\t\t\t\tjQuery.event.add( form, \"submit._submit\", function( event ) {\n\t\t\t\t\t\tevent._submit_bubble = true;\n\t\t\t\t\t});\n\t\t\t\t\tjQuery._data( form, \"submitBubbles\", true );\n\t\t\t\t}\n\t\t\t});\n\t\t\t// return undefined since we don't need an event listener\n\t\t},\n\n\t\tpostDispatch: function( event ) {\n\t\t\t// If form was submitted by the user, bubble the event up the tree\n\t\t\tif ( event._submit_bubble ) {\n\t\t\t\tdelete event._submit_bubble;\n\t\t\t\tif ( this.parentNode && !event.isTrigger ) {\n\t\t\t\t\tjQuery.event.simulate( \"submit\", this.parentNode, event, true );\n\t\t\t\t}\n\t\t\t}\n\t\t},\n\n\t\tteardown: function() {\n\t\t\t// Only need this for delegated form submit events\n\t\t\tif ( jQuery.nodeName( this, \"form\" ) ) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\t// Remove delegated handlers; cleanData eventually reaps submit handlers attached above\n\t\t\tjQuery.event.remove( this, \"._submit\" );\n\t\t}\n\t};\n}\n\n// IE change delegation and checkbox/radio fix\nif ( !jQuery.support.changeBubbles ) {\n\n\tjQuery.event.special.change = {\n\n\t\tsetup: function() {\n\n\t\t\tif ( rformElems.test( this.nodeName ) ) {\n\t\t\t\t// IE doesn't fire change on a check/radio until blur; trigger it on click\n\t\t\t\t// after a propertychange. Eat the blur-change in special.change.handle.\n\t\t\t\t// This still fires onchange a second time for check/radio after blur.\n\t\t\t\tif ( this.type === \"checkbox\" || this.type === \"radio\" ) {\n\t\t\t\t\tjQuery.event.add( this, \"propertychange._change\", function( event ) {\n\t\t\t\t\t\tif ( event.originalEvent.propertyName === \"checked\" ) {\n\t\t\t\t\t\t\tthis._just_changed = true;\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t\tjQuery.event.add( this, \"click._change\", function( event ) {\n\t\t\t\t\t\tif ( this._just_changed && !event.isTrigger ) {\n\t\t\t\t\t\t\tthis._just_changed = false;\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Allow triggered, simulated change events (#11500)\n\t\t\t\t\t\tjQuery.event.simulate( \"change\", this, event, true );\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\t// Delegated event; lazy-add a change handler on descendant inputs\n\t\t\tjQuery.event.add( this, \"beforeactivate._change\", function( e ) {\n\t\t\t\tvar elem = e.target;\n\n\t\t\t\tif ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, \"changeBubbles\" ) ) {\n\t\t\t\t\tjQuery.event.add( elem, \"change._change\", function( event ) {\n\t\t\t\t\t\tif ( this.parentNode && !event.isSimulated && !event.isTrigger ) {\n\t\t\t\t\t\t\tjQuery.event.simulate( \"change\", this.parentNode, event, true );\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t\tjQuery._data( elem, \"changeBubbles\", true );\n\t\t\t\t}\n\t\t\t});\n\t\t},\n\n\t\thandle: function( event ) {\n\t\t\tvar elem = event.target;\n\n\t\t\t// Swallow native change events from checkbox/radio, we already triggered them above\n\t\t\tif ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== \"radio\" && elem.type !== \"checkbox\") ) {\n\t\t\t\treturn event.handleObj.handler.apply( this, arguments );\n\t\t\t}\n\t\t},\n\n\t\tteardown: function() {\n\t\t\tjQuery.event.remove( this, \"._change\" );\n\n\t\t\treturn !rformElems.test( this.nodeName );\n\t\t}\n\t};\n}\n\n// Create \"bubbling\" focus and blur events\nif ( !jQuery.support.focusinBubbles ) {\n\tjQuery.each({ focus: \"focusin\", blur: \"focusout\" }, function( orig, fix ) {\n\n\t\t// Attach a single capturing handler while someone wants focusin/focusout\n\t\tvar attaches = 0,\n\t\t\thandler = function( event ) {\n\t\t\t\tjQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );\n\t\t\t};\n\n\t\tjQuery.event.special[ fix ] = {\n\t\t\tsetup: function() {\n\t\t\t\tif ( attaches++ === 0 ) {\n\t\t\t\t\tdocument.addEventListener( orig, handler, true );\n\t\t\t\t}\n\t\t\t},\n\t\t\tteardown: function() {\n\t\t\t\tif ( --attaches === 0 ) {\n\t\t\t\t\tdocument.removeEventListener( orig, handler, true );\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t});\n}\n\njQuery.fn.extend({\n\n\ton: function( types, selector, data, fn, /*INTERNAL*/ one ) {\n\t\tvar type, origFn;\n\n\t\t// Types can be a map of types/handlers\n\t\tif ( typeof types === \"object\" ) {\n\t\t\t// ( types-Object, selector, data )\n\t\t\tif ( typeof selector !== \"string\" ) {\n\t\t\t\t// ( types-Object, data )\n\t\t\t\tdata = data || selector;\n\t\t\t\tselector = undefined;\n\t\t\t}\n\t\t\tfor ( type in types ) {\n\t\t\t\tthis.on( type, selector, data, types[ type ], one );\n\t\t\t}\n\t\t\treturn this;\n\t\t}\n\n\t\tif ( data == null && fn == null ) {\n\t\t\t// ( types, fn )\n\t\t\tfn = selector;\n\t\t\tdata = selector = undefined;\n\t\t} else if ( fn == null ) {\n\t\t\tif ( typeof selector === \"string\" ) {\n\t\t\t\t// ( types, selector, fn )\n\t\t\t\tfn = data;\n\t\t\t\tdata = undefined;\n\t\t\t} else {\n\t\t\t\t// ( types, data, fn )\n\t\t\t\tfn = data;\n\t\t\t\tdata = selector;\n\t\t\t\tselector = undefined;\n\t\t\t}\n\t\t}\n\t\tif ( fn === false ) {\n\t\t\tfn = returnFalse;\n\t\t} else if ( !fn ) {\n\t\t\treturn this;\n\t\t}\n\n\t\tif ( one === 1 ) {\n\t\t\torigFn = fn;\n\t\t\tfn = function( event ) {\n\t\t\t\t// Can use an empty set, since event contains the info\n\t\t\t\tjQuery().off( event );\n\t\t\t\treturn origFn.apply( this, arguments );\n\t\t\t};\n\t\t\t// Use same guid so caller can remove using origFn\n\t\t\tfn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );\n\t\t}\n\t\treturn this.each( function() {\n\t\t\tjQuery.event.add( this, types, fn, data, selector );\n\t\t});\n\t},\n\tone: function( types, selector, data, fn ) {\n\t\treturn this.on( types, selector, data, fn, 1 );\n\t},\n\toff: function( types, selector, fn ) {\n\t\tvar handleObj, type;\n\t\tif ( types && types.preventDefault && types.handleObj ) {\n\t\t\t// ( event )  dispatched jQuery.Event\n\t\t\thandleObj = types.handleObj;\n\t\t\tjQuery( types.delegateTarget ).off(\n\t\t\t\thandleObj.namespace ? handleObj.origType + \".\" + handleObj.namespace : handleObj.origType,\n\t\t\t\thandleObj.selector,\n\t\t\t\thandleObj.handler\n\t\t\t);\n\t\t\treturn this;\n\t\t}\n\t\tif ( typeof types === \"object\" ) {\n\t\t\t// ( types-object [, selector] )\n\t\t\tfor ( type in types ) {\n\t\t\t\tthis.off( type, selector, types[ type ] );\n\t\t\t}\n\t\t\treturn this;\n\t\t}\n\t\tif ( selector === false || typeof selector === \"function\" ) {\n\t\t\t// ( types [, fn] )\n\t\t\tfn = selector;\n\t\t\tselector = undefined;\n\t\t}\n\t\tif ( fn === false ) {\n\t\t\tfn = returnFalse;\n\t\t}\n\t\treturn this.each(function() {\n\t\t\tjQuery.event.remove( this, types, fn, selector );\n\t\t});\n\t},\n\n\ttrigger: function( type, data ) {\n\t\treturn this.each(function() {\n\t\t\tjQuery.event.trigger( type, data, this );\n\t\t});\n\t},\n\ttriggerHandler: function( type, data ) {\n\t\tvar elem = this[0];\n\t\tif ( elem ) {\n\t\t\treturn jQuery.event.trigger( type, data, elem, true );\n\t\t}\n\t}\n});\nvar isSimple = /^.[^:#\\[\\.,]*$/,\n\trparentsprev = /^(?:parents|prev(?:Until|All))/,\n\trneedsContext = jQuery.expr.match.needsContext,\n\t// methods guaranteed to produce a unique set when starting from a unique set\n\tguaranteedUnique = {\n\t\tchildren: true,\n\t\tcontents: true,\n\t\tnext: true,\n\t\tprev: true\n\t};\n\njQuery.fn.extend({\n\tfind: function( selector ) {\n\t\tvar i,\n\t\t\tret = [],\n\t\t\tself = this,\n\t\t\tlen = self.length;\n\n\t\tif ( typeof selector !== \"string\" ) {\n\t\t\treturn this.pushStack( jQuery( selector ).filter(function() {\n\t\t\t\tfor ( i = 0; i < len; i++ ) {\n\t\t\t\t\tif ( jQuery.contains( self[ i ], this ) ) {\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}) );\n\t\t}\n\n\t\tfor ( i = 0; i < len; i++ ) {\n\t\t\tjQuery.find( selector, self[ i ], ret );\n\t\t}\n\n\t\t// Needed because $( selector, context ) becomes $( context ).find( selector )\n\t\tret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret );\n\t\tret.selector = this.selector ? this.selector + \" \" + selector : selector;\n\t\treturn ret;\n\t},\n\n\thas: function( target ) {\n\t\tvar i,\n\t\t\ttargets = jQuery( target, this ),\n\t\t\tlen = targets.length;\n\n\t\treturn this.filter(function() {\n\t\t\tfor ( i = 0; i < len; i++ ) {\n\t\t\t\tif ( jQuery.contains( this, targets[i] ) ) {\n\t\t\t\t\treturn true;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t},\n\n\tnot: function( selector ) {\n\t\treturn this.pushStack( winnow(this, selector || [], true) );\n\t},\n\n\tfilter: function( selector ) {\n\t\treturn this.pushStack( winnow(this, selector || [], false) );\n\t},\n\n\tis: function( selector ) {\n\t\treturn !!winnow(\n\t\t\tthis,\n\n\t\t\t// If this is a positional/relative selector, check membership in the returned set\n\t\t\t// so $(\"p:first\").is(\"p:last\") won't return true for a doc with two \"p\".\n\t\t\ttypeof selector === \"string\" && rneedsContext.test( selector ) ?\n\t\t\t\tjQuery( selector ) :\n\t\t\t\tselector || [],\n\t\t\tfalse\n\t\t).length;\n\t},\n\n\tclosest: function( selectors, context ) {\n\t\tvar cur,\n\t\t\ti = 0,\n\t\t\tl = this.length,\n\t\t\tret = [],\n\t\t\tpos = rneedsContext.test( selectors ) || typeof selectors !== \"string\" ?\n\t\t\t\tjQuery( selectors, context || this.context ) :\n\t\t\t\t0;\n\n\t\tfor ( ; i < l; i++ ) {\n\t\t\tfor ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) {\n\t\t\t\t// Always skip document fragments\n\t\t\t\tif ( cur.nodeType < 11 && (pos ?\n\t\t\t\t\tpos.index(cur) > -1 :\n\n\t\t\t\t\t// Don't pass non-elements to Sizzle\n\t\t\t\t\tcur.nodeType === 1 &&\n\t\t\t\t\t\tjQuery.find.matchesSelector(cur, selectors)) ) {\n\n\t\t\t\t\tcur = ret.push( cur );\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn this.pushStack( ret.length > 1 ? jQuery.unique( ret ) : ret );\n\t},\n\n\t// Determine the position of an element within\n\t// the matched set of elements\n\tindex: function( elem ) {\n\n\t\t// No argument, return index in parent\n\t\tif ( !elem ) {\n\t\t\treturn ( this[0] && this[0].parentNode ) ? this.first().prevAll().length : -1;\n\t\t}\n\n\t\t// index in selector\n\t\tif ( typeof elem === \"string\" ) {\n\t\t\treturn jQuery.inArray( this[0], jQuery( elem ) );\n\t\t}\n\n\t\t// Locate the position of the desired element\n\t\treturn jQuery.inArray(\n\t\t\t// If it receives a jQuery object, the first element is used\n\t\t\telem.jquery ? elem[0] : elem, this );\n\t},\n\n\tadd: function( selector, context ) {\n\t\tvar set = typeof selector === \"string\" ?\n\t\t\t\tjQuery( selector, context ) :\n\t\t\t\tjQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ),\n\t\t\tall = jQuery.merge( this.get(), set );\n\n\t\treturn this.pushStack( jQuery.unique(all) );\n\t},\n\n\taddBack: function( selector ) {\n\t\treturn this.add( selector == null ?\n\t\t\tthis.prevObject : this.prevObject.filter(selector)\n\t\t);\n\t}\n});\n\nfunction sibling( cur, dir ) {\n\tdo {\n\t\tcur = cur[ dir ];\n\t} while ( cur && cur.nodeType !== 1 );\n\n\treturn cur;\n}\n\njQuery.each({\n\tparent: function( elem ) {\n\t\tvar parent = elem.parentNode;\n\t\treturn parent && parent.nodeType !== 11 ? parent : null;\n\t},\n\tparents: function( elem ) {\n\t\treturn jQuery.dir( elem, \"parentNode\" );\n\t},\n\tparentsUntil: function( elem, i, until ) {\n\t\treturn jQuery.dir( elem, \"parentNode\", until );\n\t},\n\tnext: function( elem ) {\n\t\treturn sibling( elem, \"nextSibling\" );\n\t},\n\tprev: function( elem ) {\n\t\treturn sibling( elem, \"previousSibling\" );\n\t},\n\tnextAll: function( elem ) {\n\t\treturn jQuery.dir( elem, \"nextSibling\" );\n\t},\n\tprevAll: function( elem ) {\n\t\treturn jQuery.dir( elem, \"previousSibling\" );\n\t},\n\tnextUntil: function( elem, i, until ) {\n\t\treturn jQuery.dir( elem, \"nextSibling\", until );\n\t},\n\tprevUntil: function( elem, i, until ) {\n\t\treturn jQuery.dir( elem, \"previousSibling\", until );\n\t},\n\tsiblings: function( elem ) {\n\t\treturn jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );\n\t},\n\tchildren: function( elem ) {\n\t\treturn jQuery.sibling( elem.firstChild );\n\t},\n\tcontents: function( elem ) {\n\t\treturn jQuery.nodeName( elem, \"iframe\" ) ?\n\t\t\telem.contentDocument || elem.contentWindow.document :\n\t\t\tjQuery.merge( [], elem.childNodes );\n\t}\n}, function( name, fn ) {\n\tjQuery.fn[ name ] = function( until, selector ) {\n\t\tvar ret = jQuery.map( this, fn, until );\n\n\t\tif ( name.slice( -5 ) !== \"Until\" ) {\n\t\t\tselector = until;\n\t\t}\n\n\t\tif ( selector && typeof selector === \"string\" ) {\n\t\t\tret = jQuery.filter( selector, ret );\n\t\t}\n\n\t\tif ( this.length > 1 ) {\n\t\t\t// Remove duplicates\n\t\t\tif ( !guaranteedUnique[ name ] ) {\n\t\t\t\tret = jQuery.unique( ret );\n\t\t\t}\n\n\t\t\t// Reverse order for parents* and prev-derivatives\n\t\t\tif ( rparentsprev.test( name ) ) {\n\t\t\t\tret = ret.reverse();\n\t\t\t}\n\t\t}\n\n\t\treturn this.pushStack( ret );\n\t};\n});\n\njQuery.extend({\n\tfilter: function( expr, elems, not ) {\n\t\tvar elem = elems[ 0 ];\n\n\t\tif ( not ) {\n\t\t\texpr = \":not(\" + expr + \")\";\n\t\t}\n\n\t\treturn elems.length === 1 && elem.nodeType === 1 ?\n\t\t\tjQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] :\n\t\t\tjQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {\n\t\t\t\treturn elem.nodeType === 1;\n\t\t\t}));\n\t},\n\n\tdir: function( elem, dir, until ) {\n\t\tvar matched = [],\n\t\t\tcur = elem[ dir ];\n\n\t\twhile ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {\n\t\t\tif ( cur.nodeType === 1 ) {\n\t\t\t\tmatched.push( cur );\n\t\t\t}\n\t\t\tcur = cur[dir];\n\t\t}\n\t\treturn matched;\n\t},\n\n\tsibling: function( n, elem ) {\n\t\tvar r = [];\n\n\t\tfor ( ; n; n = n.nextSibling ) {\n\t\t\tif ( n.nodeType === 1 && n !== elem ) {\n\t\t\t\tr.push( n );\n\t\t\t}\n\t\t}\n\n\t\treturn r;\n\t}\n});\n\n// Implement the identical functionality for filter and not\nfunction winnow( elements, qualifier, not ) {\n\tif ( jQuery.isFunction( qualifier ) ) {\n\t\treturn jQuery.grep( elements, function( elem, i ) {\n\t\t\t/* jshint -W018 */\n\t\t\treturn !!qualifier.call( elem, i, elem ) !== not;\n\t\t});\n\n\t}\n\n\tif ( qualifier.nodeType ) {\n\t\treturn jQuery.grep( elements, function( elem ) {\n\t\t\treturn ( elem === qualifier ) !== not;\n\t\t});\n\n\t}\n\n\tif ( typeof qualifier === \"string\" ) {\n\t\tif ( isSimple.test( qualifier ) ) {\n\t\t\treturn jQuery.filter( qualifier, elements, not );\n\t\t}\n\n\t\tqualifier = jQuery.filter( qualifier, elements );\n\t}\n\n\treturn jQuery.grep( elements, function( elem ) {\n\t\treturn ( jQuery.inArray( elem, qualifier ) >= 0 ) !== not;\n\t});\n}\nfunction createSafeFragment( document ) {\n\tvar list = nodeNames.split( \"|\" ),\n\t\tsafeFrag = document.createDocumentFragment();\n\n\tif ( safeFrag.createElement ) {\n\t\twhile ( list.length ) {\n\t\t\tsafeFrag.createElement(\n\t\t\t\tlist.pop()\n\t\t\t);\n\t\t}\n\t}\n\treturn safeFrag;\n}\n\nvar nodeNames = \"abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|\" +\n\t\t\"header|hgroup|mark|meter|nav|output|progress|section|summary|time|video\",\n\trinlinejQuery = / jQuery\\d+=\"(?:null|\\d+)\"/g,\n\trnoshimcache = new RegExp(\"<(?:\" + nodeNames + \")[\\\\s/>]\", \"i\"),\n\trleadingWhitespace = /^\\s+/,\n\trxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\\w:]+)[^>]*)\\/>/gi,\n\trtagName = /<([\\w:]+)/,\n\trtbody = /<tbody/i,\n\trhtml = /<|&#?\\w+;/,\n\trnoInnerhtml = /<(?:script|style|link)/i,\n\tmanipulation_rcheckableType = /^(?:checkbox|radio)$/i,\n\t// checked=\"checked\" or checked\n\trchecked = /checked\\s*(?:[^=]|=\\s*.checked.)/i,\n\trscriptType = /^$|\\/(?:java|ecma)script/i,\n\trscriptTypeMasked = /^true\\/(.*)/,\n\trcleanScript = /^\\s*<!(?:\\[CDATA\\[|--)|(?:\\]\\]|--)>\\s*$/g,\n\n\t// We have to close these tags to support XHTML (#13200)\n\twrapMap = {\n\t\toption: [ 1, \"<select multiple='multiple'>\", \"</select>\" ],\n\t\tlegend: [ 1, \"<fieldset>\", \"</fieldset>\" ],\n\t\tarea: [ 1, \"<map>\", \"</map>\" ],\n\t\tparam: [ 1, \"<object>\", \"</object>\" ],\n\t\tthead: [ 1, \"<table>\", \"</table>\" ],\n\t\ttr: [ 2, \"<table><tbody>\", \"</tbody></table>\" ],\n\t\tcol: [ 2, \"<table><tbody></tbody><colgroup>\", \"</colgroup></table>\" ],\n\t\ttd: [ 3, \"<table><tbody><tr>\", \"</tr></tbody></table>\" ],\n\n\t\t// IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags,\n\t\t// unless wrapped in a div with non-breaking characters in front of it.\n\t\t_default: jQuery.support.htmlSerialize ? [ 0, \"\", \"\" ] : [ 1, \"X<div>\", \"</div>\"  ]\n\t},\n\tsafeFragment = createSafeFragment( document ),\n\tfragmentDiv = safeFragment.appendChild( document.createElement(\"div\") );\n\nwrapMap.optgroup = wrapMap.option;\nwrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;\nwrapMap.th = wrapMap.td;\n\njQuery.fn.extend({\n\ttext: function( value ) {\n\t\treturn jQuery.access( this, function( value ) {\n\t\t\treturn value === undefined ?\n\t\t\t\tjQuery.text( this ) :\n\t\t\t\tthis.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) );\n\t\t}, null, value, arguments.length );\n\t},\n\n\tappend: function() {\n\t\treturn this.domManip( arguments, function( elem ) {\n\t\t\tif ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {\n\t\t\t\tvar target = manipulationTarget( this, elem );\n\t\t\t\ttarget.appendChild( elem );\n\t\t\t}\n\t\t});\n\t},\n\n\tprepend: function() {\n\t\treturn this.domManip( arguments, function( elem ) {\n\t\t\tif ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {\n\t\t\t\tvar target = manipulationTarget( this, elem );\n\t\t\t\ttarget.insertBefore( elem, target.firstChild );\n\t\t\t}\n\t\t});\n\t},\n\n\tbefore: function() {\n\t\treturn this.domManip( arguments, function( elem ) {\n\t\t\tif ( this.parentNode ) {\n\t\t\t\tthis.parentNode.insertBefore( elem, this );\n\t\t\t}\n\t\t});\n\t},\n\n\tafter: function() {\n\t\treturn this.domManip( arguments, function( elem ) {\n\t\t\tif ( this.parentNode ) {\n\t\t\t\tthis.parentNode.insertBefore( elem, this.nextSibling );\n\t\t\t}\n\t\t});\n\t},\n\n\t// keepData is for internal use only--do not document\n\tremove: function( selector, keepData ) {\n\t\tvar elem,\n\t\t\telems = selector ? jQuery.filter( selector, this ) : this,\n\t\t\ti = 0;\n\n\t\tfor ( ; (elem = elems[i]) != null; i++ ) {\n\n\t\t\tif ( !keepData && elem.nodeType === 1 ) {\n\t\t\t\tjQuery.cleanData( getAll( elem ) );\n\t\t\t}\n\n\t\t\tif ( elem.parentNode ) {\n\t\t\t\tif ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) {\n\t\t\t\t\tsetGlobalEval( getAll( elem, \"script\" ) );\n\t\t\t\t}\n\t\t\t\telem.parentNode.removeChild( elem );\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t},\n\n\tempty: function() {\n\t\tvar elem,\n\t\t\ti = 0;\n\n\t\tfor ( ; (elem = this[i]) != null; i++ ) {\n\t\t\t// Remove element nodes and prevent memory leaks\n\t\t\tif ( elem.nodeType === 1 ) {\n\t\t\t\tjQuery.cleanData( getAll( elem, false ) );\n\t\t\t}\n\n\t\t\t// Remove any remaining nodes\n\t\t\twhile ( elem.firstChild ) {\n\t\t\t\telem.removeChild( elem.firstChild );\n\t\t\t}\n\n\t\t\t// If this is a select, ensure that it displays empty (#12336)\n\t\t\t// Support: IE<9\n\t\t\tif ( elem.options && jQuery.nodeName( elem, \"select\" ) ) {\n\t\t\t\telem.options.length = 0;\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t},\n\n\tclone: function( dataAndEvents, deepDataAndEvents ) {\n\t\tdataAndEvents = dataAndEvents == null ? false : dataAndEvents;\n\t\tdeepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;\n\n\t\treturn this.map( function () {\n\t\t\treturn jQuery.clone( this, dataAndEvents, deepDataAndEvents );\n\t\t});\n\t},\n\n\thtml: function( value ) {\n\t\treturn jQuery.access( this, function( value ) {\n\t\t\tvar elem = this[0] || {},\n\t\t\t\ti = 0,\n\t\t\t\tl = this.length;\n\n\t\t\tif ( value === undefined ) {\n\t\t\t\treturn elem.nodeType === 1 ?\n\t\t\t\t\telem.innerHTML.replace( rinlinejQuery, \"\" ) :\n\t\t\t\t\tundefined;\n\t\t\t}\n\n\t\t\t// See if we can take a shortcut and just use innerHTML\n\t\t\tif ( typeof value === \"string\" && !rnoInnerhtml.test( value ) &&\n\t\t\t\t( jQuery.support.htmlSerialize || !rnoshimcache.test( value )  ) &&\n\t\t\t\t( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) &&\n\t\t\t\t!wrapMap[ ( rtagName.exec( value ) || [\"\", \"\"] )[1].toLowerCase() ] ) {\n\n\t\t\t\tvalue = value.replace( rxhtmlTag, \"<$1></$2>\" );\n\n\t\t\t\ttry {\n\t\t\t\t\tfor (; i < l; i++ ) {\n\t\t\t\t\t\t// Remove element nodes and prevent memory leaks\n\t\t\t\t\t\telem = this[i] || {};\n\t\t\t\t\t\tif ( elem.nodeType === 1 ) {\n\t\t\t\t\t\t\tjQuery.cleanData( getAll( elem, false ) );\n\t\t\t\t\t\t\telem.innerHTML = value;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\telem = 0;\n\n\t\t\t\t// If using innerHTML throws an exception, use the fallback method\n\t\t\t\t} catch(e) {}\n\t\t\t}\n\n\t\t\tif ( elem ) {\n\t\t\t\tthis.empty().append( value );\n\t\t\t}\n\t\t}, null, value, arguments.length );\n\t},\n\n\treplaceWith: function() {\n\t\tvar\n\t\t\t// Snapshot the DOM in case .domManip sweeps something relevant into its fragment\n\t\t\targs = jQuery.map( this, function( elem ) {\n\t\t\t\treturn [ elem.nextSibling, elem.parentNode ];\n\t\t\t}),\n\t\t\ti = 0;\n\n\t\t// Make the changes, replacing each context element with the new content\n\t\tthis.domManip( arguments, function( elem ) {\n\t\t\tvar next = args[ i++ ],\n\t\t\t\tparent = args[ i++ ];\n\n\t\t\tif ( parent ) {\n\t\t\t\t// Don't use the snapshot next if it has moved (#13810)\n\t\t\t\tif ( next && next.parentNode !== parent ) {\n\t\t\t\t\tnext = this.nextSibling;\n\t\t\t\t}\n\t\t\t\tjQuery( this ).remove();\n\t\t\t\tparent.insertBefore( elem, next );\n\t\t\t}\n\t\t// Allow new content to include elements from the context set\n\t\t}, true );\n\n\t\t// Force removal if there was no new content (e.g., from empty arguments)\n\t\treturn i ? this : this.remove();\n\t},\n\n\tdetach: function( selector ) {\n\t\treturn this.remove( selector, true );\n\t},\n\n\tdomManip: function( args, callback, allowIntersection ) {\n\n\t\t// Flatten any nested arrays\n\t\targs = core_concat.apply( [], args );\n\n\t\tvar first, node, hasScripts,\n\t\t\tscripts, doc, fragment,\n\t\t\ti = 0,\n\t\t\tl = this.length,\n\t\t\tset = this,\n\t\t\tiNoClone = l - 1,\n\t\t\tvalue = args[0],\n\t\t\tisFunction = jQuery.isFunction( value );\n\n\t\t// We can't cloneNode fragments that contain checked, in WebKit\n\t\tif ( isFunction || !( l <= 1 || typeof value !== \"string\" || jQuery.support.checkClone || !rchecked.test( value ) ) ) {\n\t\t\treturn this.each(function( index ) {\n\t\t\t\tvar self = set.eq( index );\n\t\t\t\tif ( isFunction ) {\n\t\t\t\t\targs[0] = value.call( this, index, self.html() );\n\t\t\t\t}\n\t\t\t\tself.domManip( args, callback, allowIntersection );\n\t\t\t});\n\t\t}\n\n\t\tif ( l ) {\n\t\t\tfragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, !allowIntersection && this );\n\t\t\tfirst = fragment.firstChild;\n\n\t\t\tif ( fragment.childNodes.length === 1 ) {\n\t\t\t\tfragment = first;\n\t\t\t}\n\n\t\t\tif ( first ) {\n\t\t\t\tscripts = jQuery.map( getAll( fragment, \"script\" ), disableScript );\n\t\t\t\thasScripts = scripts.length;\n\n\t\t\t\t// Use the original fragment for the last item instead of the first because it can end up\n\t\t\t\t// being emptied incorrectly in certain situations (#8070).\n\t\t\t\tfor ( ; i < l; i++ ) {\n\t\t\t\t\tnode = fragment;\n\n\t\t\t\t\tif ( i !== iNoClone ) {\n\t\t\t\t\t\tnode = jQuery.clone( node, true, true );\n\n\t\t\t\t\t\t// Keep references to cloned scripts for later restoration\n\t\t\t\t\t\tif ( hasScripts ) {\n\t\t\t\t\t\t\tjQuery.merge( scripts, getAll( node, \"script\" ) );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tcallback.call( this[i], node, i );\n\t\t\t\t}\n\n\t\t\t\tif ( hasScripts ) {\n\t\t\t\t\tdoc = scripts[ scripts.length - 1 ].ownerDocument;\n\n\t\t\t\t\t// Reenable scripts\n\t\t\t\t\tjQuery.map( scripts, restoreScript );\n\n\t\t\t\t\t// Evaluate executable scripts on first document insertion\n\t\t\t\t\tfor ( i = 0; i < hasScripts; i++ ) {\n\t\t\t\t\t\tnode = scripts[ i ];\n\t\t\t\t\t\tif ( rscriptType.test( node.type || \"\" ) &&\n\t\t\t\t\t\t\t!jQuery._data( node, \"globalEval\" ) && jQuery.contains( doc, node ) ) {\n\n\t\t\t\t\t\t\tif ( node.src ) {\n\t\t\t\t\t\t\t\t// Hope ajax is available...\n\t\t\t\t\t\t\t\tjQuery._evalUrl( node.src );\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tjQuery.globalEval( ( node.text || node.textContent || node.innerHTML || \"\" ).replace( rcleanScript, \"\" ) );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Fix #11809: Avoid leaking memory\n\t\t\t\tfragment = first = null;\n\t\t\t}\n\t\t}\n\n\t\treturn this;\n\t}\n});\n\n// Support: IE<8\n// Manipulating tables requires a tbody\nfunction manipulationTarget( elem, content ) {\n\treturn jQuery.nodeName( elem, \"table\" ) &&\n\t\tjQuery.nodeName( content.nodeType === 1 ? content : content.firstChild, \"tr\" ) ?\n\n\t\telem.getElementsByTagName(\"tbody\")[0] ||\n\t\t\telem.appendChild( elem.ownerDocument.createElement(\"tbody\") ) :\n\t\telem;\n}\n\n// Replace/restore the type attribute of script elements for safe DOM manipulation\nfunction disableScript( elem ) {\n\telem.type = (jQuery.find.attr( elem, \"type\" ) !== null) + \"/\" + elem.type;\n\treturn elem;\n}\nfunction restoreScript( elem ) {\n\tvar match = rscriptTypeMasked.exec( elem.type );\n\tif ( match ) {\n\t\telem.type = match[1];\n\t} else {\n\t\telem.removeAttribute(\"type\");\n\t}\n\treturn elem;\n}\n\n// Mark scripts as having already been evaluated\nfunction setGlobalEval( elems, refElements ) {\n\tvar elem,\n\t\ti = 0;\n\tfor ( ; (elem = elems[i]) != null; i++ ) {\n\t\tjQuery._data( elem, \"globalEval\", !refElements || jQuery._data( refElements[i], \"globalEval\" ) );\n\t}\n}\n\nfunction cloneCopyEvent( src, dest ) {\n\n\tif ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {\n\t\treturn;\n\t}\n\n\tvar type, i, l,\n\t\toldData = jQuery._data( src ),\n\t\tcurData = jQuery._data( dest, oldData ),\n\t\tevents = oldData.events;\n\n\tif ( events ) {\n\t\tdelete curData.handle;\n\t\tcurData.events = {};\n\n\t\tfor ( type in events ) {\n\t\t\tfor ( i = 0, l = events[ type ].length; i < l; i++ ) {\n\t\t\t\tjQuery.event.add( dest, type, events[ type ][ i ] );\n\t\t\t}\n\t\t}\n\t}\n\n\t// make the cloned public data object a copy from the original\n\tif ( curData.data ) {\n\t\tcurData.data = jQuery.extend( {}, curData.data );\n\t}\n}\n\nfunction fixCloneNodeIssues( src, dest ) {\n\tvar nodeName, e, data;\n\n\t// We do not need to do anything for non-Elements\n\tif ( dest.nodeType !== 1 ) {\n\t\treturn;\n\t}\n\n\tnodeName = dest.nodeName.toLowerCase();\n\n\t// IE6-8 copies events bound via attachEvent when using cloneNode.\n\tif ( !jQuery.support.noCloneEvent && dest[ jQuery.expando ] ) {\n\t\tdata = jQuery._data( dest );\n\n\t\tfor ( e in data.events ) {\n\t\t\tjQuery.removeEvent( dest, e, data.handle );\n\t\t}\n\n\t\t// Event data gets referenced instead of copied if the expando gets copied too\n\t\tdest.removeAttribute( jQuery.expando );\n\t}\n\n\t// IE blanks contents when cloning scripts, and tries to evaluate newly-set text\n\tif ( nodeName === \"script\" && dest.text !== src.text ) {\n\t\tdisableScript( dest ).text = src.text;\n\t\trestoreScript( dest );\n\n\t// IE6-10 improperly clones children of object elements using classid.\n\t// IE10 throws NoModificationAllowedError if parent is null, #12132.\n\t} else if ( nodeName === \"object\" ) {\n\t\tif ( dest.parentNode ) {\n\t\t\tdest.outerHTML = src.outerHTML;\n\t\t}\n\n\t\t// This path appears unavoidable for IE9. When cloning an object\n\t\t// element in IE9, the outerHTML strategy above is not sufficient.\n\t\t// If the src has innerHTML and the destination does not,\n\t\t// copy the src.innerHTML into the dest.innerHTML. #10324\n\t\tif ( jQuery.support.html5Clone && ( src.innerHTML && !jQuery.trim(dest.innerHTML) ) ) {\n\t\t\tdest.innerHTML = src.innerHTML;\n\t\t}\n\n\t} else if ( nodeName === \"input\" && manipulation_rcheckableType.test( src.type ) ) {\n\t\t// IE6-8 fails to persist the checked state of a cloned checkbox\n\t\t// or radio button. Worse, IE6-7 fail to give the cloned element\n\t\t// a checked appearance if the defaultChecked value isn't also set\n\n\t\tdest.defaultChecked = dest.checked = src.checked;\n\n\t\t// IE6-7 get confused and end up setting the value of a cloned\n\t\t// checkbox/radio button to an empty string instead of \"on\"\n\t\tif ( dest.value !== src.value ) {\n\t\t\tdest.value = src.value;\n\t\t}\n\n\t// IE6-8 fails to return the selected option to the default selected\n\t// state when cloning options\n\t} else if ( nodeName === \"option\" ) {\n\t\tdest.defaultSelected = dest.selected = src.defaultSelected;\n\n\t// IE6-8 fails to set the defaultValue to the correct value when\n\t// cloning other types of input fields\n\t} else if ( nodeName === \"input\" || nodeName === \"textarea\" ) {\n\t\tdest.defaultValue = src.defaultValue;\n\t}\n}\n\njQuery.each({\n\tappendTo: \"append\",\n\tprependTo: \"prepend\",\n\tinsertBefore: \"before\",\n\tinsertAfter: \"after\",\n\treplaceAll: \"replaceWith\"\n}, function( name, original ) {\n\tjQuery.fn[ name ] = function( selector ) {\n\t\tvar elems,\n\t\t\ti = 0,\n\t\t\tret = [],\n\t\t\tinsert = jQuery( selector ),\n\t\t\tlast = insert.length - 1;\n\n\t\tfor ( ; i <= last; i++ ) {\n\t\t\telems = i === last ? this : this.clone(true);\n\t\t\tjQuery( insert[i] )[ original ]( elems );\n\n\t\t\t// Modern browsers can apply jQuery collections as arrays, but oldIE needs a .get()\n\t\t\tcore_push.apply( ret, elems.get() );\n\t\t}\n\n\t\treturn this.pushStack( ret );\n\t};\n});\n\nfunction getAll( context, tag ) {\n\tvar elems, elem,\n\t\ti = 0,\n\t\tfound = typeof context.getElementsByTagName !== core_strundefined ? context.getElementsByTagName( tag || \"*\" ) :\n\t\t\ttypeof context.querySelectorAll !== core_strundefined ? context.querySelectorAll( tag || \"*\" ) :\n\t\t\tundefined;\n\n\tif ( !found ) {\n\t\tfor ( found = [], elems = context.childNodes || context; (elem = elems[i]) != null; i++ ) {\n\t\t\tif ( !tag || jQuery.nodeName( elem, tag ) ) {\n\t\t\t\tfound.push( elem );\n\t\t\t} else {\n\t\t\t\tjQuery.merge( found, getAll( elem, tag ) );\n\t\t\t}\n\t\t}\n\t}\n\n\treturn tag === undefined || tag && jQuery.nodeName( context, tag ) ?\n\t\tjQuery.merge( [ context ], found ) :\n\t\tfound;\n}\n\n// Used in buildFragment, fixes the defaultChecked property\nfunction fixDefaultChecked( elem ) {\n\tif ( manipulation_rcheckableType.test( elem.type ) ) {\n\t\telem.defaultChecked = elem.checked;\n\t}\n}\n\njQuery.extend({\n\tclone: function( elem, dataAndEvents, deepDataAndEvents ) {\n\t\tvar destElements, node, clone, i, srcElements,\n\t\t\tinPage = jQuery.contains( elem.ownerDocument, elem );\n\n\t\tif ( jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( \"<\" + elem.nodeName + \">\" ) ) {\n\t\t\tclone = elem.cloneNode( true );\n\n\t\t// IE<=8 does not properly clone detached, unknown element nodes\n\t\t} else {\n\t\t\tfragmentDiv.innerHTML = elem.outerHTML;\n\t\t\tfragmentDiv.removeChild( clone = fragmentDiv.firstChild );\n\t\t}\n\n\t\tif ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) &&\n\t\t\t\t(elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {\n\n\t\t\t// We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2\n\t\t\tdestElements = getAll( clone );\n\t\t\tsrcElements = getAll( elem );\n\n\t\t\t// Fix all IE cloning issues\n\t\t\tfor ( i = 0; (node = srcElements[i]) != null; ++i ) {\n\t\t\t\t// Ensure that the destination node is not null; Fixes #9587\n\t\t\t\tif ( destElements[i] ) {\n\t\t\t\t\tfixCloneNodeIssues( node, destElements[i] );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Copy the events from the original to the clone\n\t\tif ( dataAndEvents ) {\n\t\t\tif ( deepDataAndEvents ) {\n\t\t\t\tsrcElements = srcElements || getAll( elem );\n\t\t\t\tdestElements = destElements || getAll( clone );\n\n\t\t\t\tfor ( i = 0; (node = srcElements[i]) != null; i++ ) {\n\t\t\t\t\tcloneCopyEvent( node, destElements[i] );\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tcloneCopyEvent( elem, clone );\n\t\t\t}\n\t\t}\n\n\t\t// Preserve script evaluation history\n\t\tdestElements = getAll( clone, \"script\" );\n\t\tif ( destElements.length > 0 ) {\n\t\t\tsetGlobalEval( destElements, !inPage && getAll( elem, \"script\" ) );\n\t\t}\n\n\t\tdestElements = srcElements = node = null;\n\n\t\t// Return the cloned set\n\t\treturn clone;\n\t},\n\n\tbuildFragment: function( elems, context, scripts, selection ) {\n\t\tvar j, elem, contains,\n\t\t\ttmp, tag, tbody, wrap,\n\t\t\tl = elems.length,\n\n\t\t\t// Ensure a safe fragment\n\t\t\tsafe = createSafeFragment( context ),\n\n\t\t\tnodes = [],\n\t\t\ti = 0;\n\n\t\tfor ( ; i < l; i++ ) {\n\t\t\telem = elems[ i ];\n\n\t\t\tif ( elem || elem === 0 ) {\n\n\t\t\t\t// Add nodes directly\n\t\t\t\tif ( jQuery.type( elem ) === \"object\" ) {\n\t\t\t\t\tjQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );\n\n\t\t\t\t// Convert non-html into a text node\n\t\t\t\t} else if ( !rhtml.test( elem ) ) {\n\t\t\t\t\tnodes.push( context.createTextNode( elem ) );\n\n\t\t\t\t// Convert html into DOM nodes\n\t\t\t\t} else {\n\t\t\t\t\ttmp = tmp || safe.appendChild( context.createElement(\"div\") );\n\n\t\t\t\t\t// Deserialize a standard representation\n\t\t\t\t\ttag = ( rtagName.exec( elem ) || [\"\", \"\"] )[1].toLowerCase();\n\t\t\t\t\twrap = wrapMap[ tag ] || wrapMap._default;\n\n\t\t\t\t\ttmp.innerHTML = wrap[1] + elem.replace( rxhtmlTag, \"<$1></$2>\" ) + wrap[2];\n\n\t\t\t\t\t// Descend through wrappers to the right content\n\t\t\t\t\tj = wrap[0];\n\t\t\t\t\twhile ( j-- ) {\n\t\t\t\t\t\ttmp = tmp.lastChild;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Manually add leading whitespace removed by IE\n\t\t\t\t\tif ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {\n\t\t\t\t\t\tnodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[0] ) );\n\t\t\t\t\t}\n\n\t\t\t\t\t// Remove IE's autoinserted <tbody> from table fragments\n\t\t\t\t\tif ( !jQuery.support.tbody ) {\n\n\t\t\t\t\t\t// String was a <table>, *may* have spurious <tbody>\n\t\t\t\t\t\telem = tag === \"table\" && !rtbody.test( elem ) ?\n\t\t\t\t\t\t\ttmp.firstChild :\n\n\t\t\t\t\t\t\t// String was a bare <thead> or <tfoot>\n\t\t\t\t\t\t\twrap[1] === \"<table>\" && !rtbody.test( elem ) ?\n\t\t\t\t\t\t\t\ttmp :\n\t\t\t\t\t\t\t\t0;\n\n\t\t\t\t\t\tj = elem && elem.childNodes.length;\n\t\t\t\t\t\twhile ( j-- ) {\n\t\t\t\t\t\t\tif ( jQuery.nodeName( (tbody = elem.childNodes[j]), \"tbody\" ) && !tbody.childNodes.length ) {\n\t\t\t\t\t\t\t\telem.removeChild( tbody );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tjQuery.merge( nodes, tmp.childNodes );\n\n\t\t\t\t\t// Fix #12392 for WebKit and IE > 9\n\t\t\t\t\ttmp.textContent = \"\";\n\n\t\t\t\t\t// Fix #12392 for oldIE\n\t\t\t\t\twhile ( tmp.firstChild ) {\n\t\t\t\t\t\ttmp.removeChild( tmp.firstChild );\n\t\t\t\t\t}\n\n\t\t\t\t\t// Remember the top-level container for proper cleanup\n\t\t\t\t\ttmp = safe.lastChild;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Fix #11356: Clear elements from fragment\n\t\tif ( tmp ) {\n\t\t\tsafe.removeChild( tmp );\n\t\t}\n\n\t\t// Reset defaultChecked for any radios and checkboxes\n\t\t// about to be appended to the DOM in IE 6/7 (#8060)\n\t\tif ( !jQuery.support.appendChecked ) {\n\t\t\tjQuery.grep( getAll( nodes, \"input\" ), fixDefaultChecked );\n\t\t}\n\n\t\ti = 0;\n\t\twhile ( (elem = nodes[ i++ ]) ) {\n\n\t\t\t// #4087 - If origin and destination elements are the same, and this is\n\t\t\t// that element, do not do anything\n\t\t\tif ( selection && jQuery.inArray( elem, selection ) !== -1 ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tcontains = jQuery.contains( elem.ownerDocument, elem );\n\n\t\t\t// Append to fragment\n\t\t\ttmp = getAll( safe.appendChild( elem ), \"script\" );\n\n\t\t\t// Preserve script evaluation history\n\t\t\tif ( contains ) {\n\t\t\t\tsetGlobalEval( tmp );\n\t\t\t}\n\n\t\t\t// Capture executables\n\t\t\tif ( scripts ) {\n\t\t\t\tj = 0;\n\t\t\t\twhile ( (elem = tmp[ j++ ]) ) {\n\t\t\t\t\tif ( rscriptType.test( elem.type || \"\" ) ) {\n\t\t\t\t\t\tscripts.push( elem );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\ttmp = null;\n\n\t\treturn safe;\n\t},\n\n\tcleanData: function( elems, /* internal */ acceptData ) {\n\t\tvar elem, type, id, data,\n\t\t\ti = 0,\n\t\t\tinternalKey = jQuery.expando,\n\t\t\tcache = jQuery.cache,\n\t\t\tdeleteExpando = jQuery.support.deleteExpando,\n\t\t\tspecial = jQuery.event.special;\n\n\t\tfor ( ; (elem = elems[i]) != null; i++ ) {\n\n\t\t\tif ( acceptData || jQuery.acceptData( elem ) ) {\n\n\t\t\t\tid = elem[ internalKey ];\n\t\t\t\tdata = id && cache[ id ];\n\n\t\t\t\tif ( data ) {\n\t\t\t\t\tif ( data.events ) {\n\t\t\t\t\t\tfor ( type in data.events ) {\n\t\t\t\t\t\t\tif ( special[ type ] ) {\n\t\t\t\t\t\t\t\tjQuery.event.remove( elem, type );\n\n\t\t\t\t\t\t\t// This is a shortcut to avoid jQuery.event.remove's overhead\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tjQuery.removeEvent( elem, type, data.handle );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Remove cache only if it was not already removed by jQuery.event.remove\n\t\t\t\t\tif ( cache[ id ] ) {\n\n\t\t\t\t\t\tdelete cache[ id ];\n\n\t\t\t\t\t\t// IE does not allow us to delete expando properties from nodes,\n\t\t\t\t\t\t// nor does it have a removeAttribute function on Document nodes;\n\t\t\t\t\t\t// we must handle all of these cases\n\t\t\t\t\t\tif ( deleteExpando ) {\n\t\t\t\t\t\t\tdelete elem[ internalKey ];\n\n\t\t\t\t\t\t} else if ( typeof elem.removeAttribute !== core_strundefined ) {\n\t\t\t\t\t\t\telem.removeAttribute( internalKey );\n\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\telem[ internalKey ] = null;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\tcore_deletedIds.push( id );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\n\t_evalUrl: function( url ) {\n\t\treturn jQuery.ajax({\n\t\t\turl: url,\n\t\t\ttype: \"GET\",\n\t\t\tdataType: \"script\",\n\t\t\tasync: false,\n\t\t\tglobal: false,\n\t\t\t\"throws\": true\n\t\t});\n\t}\n});\njQuery.fn.extend({\n\twrapAll: function( html ) {\n\t\tif ( jQuery.isFunction( html ) ) {\n\t\t\treturn this.each(function(i) {\n\t\t\t\tjQuery(this).wrapAll( html.call(this, i) );\n\t\t\t});\n\t\t}\n\n\t\tif ( this[0] ) {\n\t\t\t// The elements to wrap the target around\n\t\t\tvar wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);\n\n\t\t\tif ( this[0].parentNode ) {\n\t\t\t\twrap.insertBefore( this[0] );\n\t\t\t}\n\n\t\t\twrap.map(function() {\n\t\t\t\tvar elem = this;\n\n\t\t\t\twhile ( elem.firstChild && elem.firstChild.nodeType === 1 ) {\n\t\t\t\t\telem = elem.firstChild;\n\t\t\t\t}\n\n\t\t\t\treturn elem;\n\t\t\t}).append( this );\n\t\t}\n\n\t\treturn this;\n\t},\n\n\twrapInner: function( html ) {\n\t\tif ( jQuery.isFunction( html ) ) {\n\t\t\treturn this.each(function(i) {\n\t\t\t\tjQuery(this).wrapInner( html.call(this, i) );\n\t\t\t});\n\t\t}\n\n\t\treturn this.each(function() {\n\t\t\tvar self = jQuery( this ),\n\t\t\t\tcontents = self.contents();\n\n\t\t\tif ( contents.length ) {\n\t\t\t\tcontents.wrapAll( html );\n\n\t\t\t} else {\n\t\t\t\tself.append( html );\n\t\t\t}\n\t\t});\n\t},\n\n\twrap: function( html ) {\n\t\tvar isFunction = jQuery.isFunction( html );\n\n\t\treturn this.each(function(i) {\n\t\t\tjQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );\n\t\t});\n\t},\n\n\tunwrap: function() {\n\t\treturn this.parent().each(function() {\n\t\t\tif ( !jQuery.nodeName( this, \"body\" ) ) {\n\t\t\t\tjQuery( this ).replaceWith( this.childNodes );\n\t\t\t}\n\t\t}).end();\n\t}\n});\nvar iframe, getStyles, curCSS,\n\tralpha = /alpha\\([^)]*\\)/i,\n\tropacity = /opacity\\s*=\\s*([^)]*)/,\n\trposition = /^(top|right|bottom|left)$/,\n\t// swappable if display is none or starts with table except \"table\", \"table-cell\", or \"table-caption\"\n\t// see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display\n\trdisplayswap = /^(none|table(?!-c[ea]).+)/,\n\trmargin = /^margin/,\n\trnumsplit = new RegExp( \"^(\" + core_pnum + \")(.*)$\", \"i\" ),\n\trnumnonpx = new RegExp( \"^(\" + core_pnum + \")(?!px)[a-z%]+$\", \"i\" ),\n\trrelNum = new RegExp( \"^([+-])=(\" + core_pnum + \")\", \"i\" ),\n\telemdisplay = { BODY: \"block\" },\n\n\tcssShow = { position: \"absolute\", visibility: \"hidden\", display: \"block\" },\n\tcssNormalTransform = {\n\t\tletterSpacing: 0,\n\t\tfontWeight: 400\n\t},\n\n\tcssExpand = [ \"Top\", \"Right\", \"Bottom\", \"Left\" ],\n\tcssPrefixes = [ \"Webkit\", \"O\", \"Moz\", \"ms\" ];\n\n// return a css property mapped to a potentially vendor prefixed property\nfunction vendorPropName( style, name ) {\n\n\t// shortcut for names that are not vendor prefixed\n\tif ( name in style ) {\n\t\treturn name;\n\t}\n\n\t// check for vendor prefixed names\n\tvar capName = name.charAt(0).toUpperCase() + name.slice(1),\n\t\torigName = name,\n\t\ti = cssPrefixes.length;\n\n\twhile ( i-- ) {\n\t\tname = cssPrefixes[ i ] + capName;\n\t\tif ( name in style ) {\n\t\t\treturn name;\n\t\t}\n\t}\n\n\treturn origName;\n}\n\nfunction isHidden( elem, el ) {\n\t// isHidden might be called from jQuery#filter function;\n\t// in that case, element will be second argument\n\telem = el || elem;\n\treturn jQuery.css( elem, \"display\" ) === \"none\" || !jQuery.contains( elem.ownerDocument, elem );\n}\n\nfunction showHide( elements, show ) {\n\tvar display, elem, hidden,\n\t\tvalues = [],\n\t\tindex = 0,\n\t\tlength = elements.length;\n\n\tfor ( ; index < length; index++ ) {\n\t\telem = elements[ index ];\n\t\tif ( !elem.style ) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tvalues[ index ] = jQuery._data( elem, \"olddisplay\" );\n\t\tdisplay = elem.style.display;\n\t\tif ( show ) {\n\t\t\t// Reset the inline display of this element to learn if it is\n\t\t\t// being hidden by cascaded rules or not\n\t\t\tif ( !values[ index ] && display === \"none\" ) {\n\t\t\t\telem.style.display = \"\";\n\t\t\t}\n\n\t\t\t// Set elements which have been overridden with display: none\n\t\t\t// in a stylesheet to whatever the default browser style is\n\t\t\t// for such an element\n\t\t\tif ( elem.style.display === \"\" && isHidden( elem ) ) {\n\t\t\t\tvalues[ index ] = jQuery._data( elem, \"olddisplay\", css_defaultDisplay(elem.nodeName) );\n\t\t\t}\n\t\t} else {\n\n\t\t\tif ( !values[ index ] ) {\n\t\t\t\thidden = isHidden( elem );\n\n\t\t\t\tif ( display && display !== \"none\" || !hidden ) {\n\t\t\t\t\tjQuery._data( elem, \"olddisplay\", hidden ? display : jQuery.css( elem, \"display\" ) );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Set the display of most of the elements in a second loop\n\t// to avoid the constant reflow\n\tfor ( index = 0; index < length; index++ ) {\n\t\telem = elements[ index ];\n\t\tif ( !elem.style ) {\n\t\t\tcontinue;\n\t\t}\n\t\tif ( !show || elem.style.display === \"none\" || elem.style.display === \"\" ) {\n\t\t\telem.style.display = show ? values[ index ] || \"\" : \"none\";\n\t\t}\n\t}\n\n\treturn elements;\n}\n\njQuery.fn.extend({\n\tcss: function( name, value ) {\n\t\treturn jQuery.access( this, function( elem, name, value ) {\n\t\t\tvar len, styles,\n\t\t\t\tmap = {},\n\t\t\t\ti = 0;\n\n\t\t\tif ( jQuery.isArray( name ) ) {\n\t\t\t\tstyles = getStyles( elem );\n\t\t\t\tlen = name.length;\n\n\t\t\t\tfor ( ; i < len; i++ ) {\n\t\t\t\t\tmap[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );\n\t\t\t\t}\n\n\t\t\t\treturn map;\n\t\t\t}\n\n\t\t\treturn value !== undefined ?\n\t\t\t\tjQuery.style( elem, name, value ) :\n\t\t\t\tjQuery.css( elem, name );\n\t\t}, name, value, arguments.length > 1 );\n\t},\n\tshow: function() {\n\t\treturn showHide( this, true );\n\t},\n\thide: function() {\n\t\treturn showHide( this );\n\t},\n\ttoggle: function( state ) {\n\t\tif ( typeof state === \"boolean\" ) {\n\t\t\treturn state ? this.show() : this.hide();\n\t\t}\n\n\t\treturn this.each(function() {\n\t\t\tif ( isHidden( this ) ) {\n\t\t\t\tjQuery( this ).show();\n\t\t\t} else {\n\t\t\t\tjQuery( this ).hide();\n\t\t\t}\n\t\t});\n\t}\n});\n\njQuery.extend({\n\t// Add in style property hooks for overriding the default\n\t// behavior of getting and setting a style property\n\tcssHooks: {\n\t\topacity: {\n\t\t\tget: function( elem, computed ) {\n\t\t\t\tif ( computed ) {\n\t\t\t\t\t// We should always get a number back from opacity\n\t\t\t\t\tvar ret = curCSS( elem, \"opacity\" );\n\t\t\t\t\treturn ret === \"\" ? \"1\" : ret;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t},\n\n\t// Don't automatically add \"px\" to these possibly-unitless properties\n\tcssNumber: {\n\t\t\"columnCount\": true,\n\t\t\"fillOpacity\": true,\n\t\t\"fontWeight\": true,\n\t\t\"lineHeight\": true,\n\t\t\"opacity\": true,\n\t\t\"order\": true,\n\t\t\"orphans\": true,\n\t\t\"widows\": true,\n\t\t\"zIndex\": true,\n\t\t\"zoom\": true\n\t},\n\n\t// Add in properties whose names you wish to fix before\n\t// setting or getting the value\n\tcssProps: {\n\t\t// normalize float css property\n\t\t\"float\": jQuery.support.cssFloat ? \"cssFloat\" : \"styleFloat\"\n\t},\n\n\t// Get and set the style property on a DOM Node\n\tstyle: function( elem, name, value, extra ) {\n\t\t// Don't set styles on text and comment nodes\n\t\tif ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {\n\t\t\treturn;\n\t\t}\n\n\t\t// Make sure that we're working with the right name\n\t\tvar ret, type, hooks,\n\t\t\torigName = jQuery.camelCase( name ),\n\t\t\tstyle = elem.style;\n\n\t\tname = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) );\n\n\t\t// gets hook for the prefixed version\n\t\t// followed by the unprefixed version\n\t\thooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];\n\n\t\t// Check if we're setting a value\n\t\tif ( value !== undefined ) {\n\t\t\ttype = typeof value;\n\n\t\t\t// convert relative number strings (+= or -=) to relative numbers. #7345\n\t\t\tif ( type === \"string\" && (ret = rrelNum.exec( value )) ) {\n\t\t\t\tvalue = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) );\n\t\t\t\t// Fixes bug #9237\n\t\t\t\ttype = \"number\";\n\t\t\t}\n\n\t\t\t// Make sure that NaN and null values aren't set. See: #7116\n\t\t\tif ( value == null || type === \"number\" && isNaN( value ) ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// If a number was passed in, add 'px' to the (except for certain CSS properties)\n\t\t\tif ( type === \"number\" && !jQuery.cssNumber[ origName ] ) {\n\t\t\t\tvalue += \"px\";\n\t\t\t}\n\n\t\t\t// Fixes #8908, it can be done more correctly by specifing setters in cssHooks,\n\t\t\t// but it would mean to define eight (for every problematic property) identical functions\n\t\t\tif ( !jQuery.support.clearCloneStyle && value === \"\" && name.indexOf(\"background\") === 0 ) {\n\t\t\t\tstyle[ name ] = \"inherit\";\n\t\t\t}\n\n\t\t\t// If a hook was provided, use that value, otherwise just set the specified value\n\t\t\tif ( !hooks || !(\"set\" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) {\n\n\t\t\t\t// Wrapped to prevent IE from throwing errors when 'invalid' values are provided\n\t\t\t\t// Fixes bug #5509\n\t\t\t\ttry {\n\t\t\t\t\tstyle[ name ] = value;\n\t\t\t\t} catch(e) {}\n\t\t\t}\n\n\t\t} else {\n\t\t\t// If a hook was provided get the non-computed value from there\n\t\t\tif ( hooks && \"get\" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {\n\t\t\t\treturn ret;\n\t\t\t}\n\n\t\t\t// Otherwise just get the value from the style object\n\t\t\treturn style[ name ];\n\t\t}\n\t},\n\n\tcss: function( elem, name, extra, styles ) {\n\t\tvar num, val, hooks,\n\t\t\torigName = jQuery.camelCase( name );\n\n\t\t// Make sure that we're working with the right name\n\t\tname = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) );\n\n\t\t// gets hook for the prefixed version\n\t\t// followed by the unprefixed version\n\t\thooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];\n\n\t\t// If a hook was provided get the computed value from there\n\t\tif ( hooks && \"get\" in hooks ) {\n\t\t\tval = hooks.get( elem, true, extra );\n\t\t}\n\n\t\t// Otherwise, if a way to get the computed value exists, use that\n\t\tif ( val === undefined ) {\n\t\t\tval = curCSS( elem, name, styles );\n\t\t}\n\n\t\t//convert \"normal\" to computed value\n\t\tif ( val === \"normal\" && name in cssNormalTransform ) {\n\t\t\tval = cssNormalTransform[ name ];\n\t\t}\n\n\t\t// Return, converting to number if forced or a qualifier was provided and val looks numeric\n\t\tif ( extra === \"\" || extra ) {\n\t\t\tnum = parseFloat( val );\n\t\t\treturn extra === true || jQuery.isNumeric( num ) ? num || 0 : val;\n\t\t}\n\t\treturn val;\n\t}\n});\n\n// NOTE: we've included the \"window\" in window.getComputedStyle\n// because jsdom on node.js will break without it.\nif ( window.getComputedStyle ) {\n\tgetStyles = function( elem ) {\n\t\treturn window.getComputedStyle( elem, null );\n\t};\n\n\tcurCSS = function( elem, name, _computed ) {\n\t\tvar width, minWidth, maxWidth,\n\t\t\tcomputed = _computed || getStyles( elem ),\n\n\t\t\t// getPropertyValue is only needed for .css('filter') in IE9, see #12537\n\t\t\tret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined,\n\t\t\tstyle = elem.style;\n\n\t\tif ( computed ) {\n\n\t\t\tif ( ret === \"\" && !jQuery.contains( elem.ownerDocument, elem ) ) {\n\t\t\t\tret = jQuery.style( elem, name );\n\t\t\t}\n\n\t\t\t// A tribute to the \"awesome hack by Dean Edwards\"\n\t\t\t// Chrome < 17 and Safari 5.0 uses \"computed value\" instead of \"used value\" for margin-right\n\t\t\t// Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels\n\t\t\t// this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values\n\t\t\tif ( rnumnonpx.test( ret ) && rmargin.test( name ) ) {\n\n\t\t\t\t// Remember the original values\n\t\t\t\twidth = style.width;\n\t\t\t\tminWidth = style.minWidth;\n\t\t\t\tmaxWidth = style.maxWidth;\n\n\t\t\t\t// Put in the new values to get a computed value out\n\t\t\t\tstyle.minWidth = style.maxWidth = style.width = ret;\n\t\t\t\tret = computed.width;\n\n\t\t\t\t// Revert the changed values\n\t\t\t\tstyle.width = width;\n\t\t\t\tstyle.minWidth = minWidth;\n\t\t\t\tstyle.maxWidth = maxWidth;\n\t\t\t}\n\t\t}\n\n\t\treturn ret;\n\t};\n} else if ( document.documentElement.currentStyle ) {\n\tgetStyles = function( elem ) {\n\t\treturn elem.currentStyle;\n\t};\n\n\tcurCSS = function( elem, name, _computed ) {\n\t\tvar left, rs, rsLeft,\n\t\t\tcomputed = _computed || getStyles( elem ),\n\t\t\tret = computed ? computed[ name ] : undefined,\n\t\t\tstyle = elem.style;\n\n\t\t// Avoid setting ret to empty string here\n\t\t// so we don't default to auto\n\t\tif ( ret == null && style && style[ name ] ) {\n\t\t\tret = style[ name ];\n\t\t}\n\n\t\t// From the awesome hack by Dean Edwards\n\t\t// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291\n\n\t\t// If we're not dealing with a regular pixel number\n\t\t// but a number that has a weird ending, we need to convert it to pixels\n\t\t// but not position css attributes, as those are proportional to the parent element instead\n\t\t// and we can't measure the parent instead because it might trigger a \"stacking dolls\" problem\n\t\tif ( rnumnonpx.test( ret ) && !rposition.test( name ) ) {\n\n\t\t\t// Remember the original values\n\t\t\tleft = style.left;\n\t\t\trs = elem.runtimeStyle;\n\t\t\trsLeft = rs && rs.left;\n\n\t\t\t// Put in the new values to get a computed value out\n\t\t\tif ( rsLeft ) {\n\t\t\t\trs.left = elem.currentStyle.left;\n\t\t\t}\n\t\t\tstyle.left = name === \"fontSize\" ? \"1em\" : ret;\n\t\t\tret = style.pixelLeft + \"px\";\n\n\t\t\t// Revert the changed values\n\t\t\tstyle.left = left;\n\t\t\tif ( rsLeft ) {\n\t\t\t\trs.left = rsLeft;\n\t\t\t}\n\t\t}\n\n\t\treturn ret === \"\" ? \"auto\" : ret;\n\t};\n}\n\nfunction setPositiveNumber( elem, value, subtract ) {\n\tvar matches = rnumsplit.exec( value );\n\treturn matches ?\n\t\t// Guard against undefined \"subtract\", e.g., when used as in cssHooks\n\t\tMath.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || \"px\" ) :\n\t\tvalue;\n}\n\nfunction augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {\n\tvar i = extra === ( isBorderBox ? \"border\" : \"content\" ) ?\n\t\t// If we already have the right measurement, avoid augmentation\n\t\t4 :\n\t\t// Otherwise initialize for horizontal or vertical properties\n\t\tname === \"width\" ? 1 : 0,\n\n\t\tval = 0;\n\n\tfor ( ; i < 4; i += 2 ) {\n\t\t// both box models exclude margin, so add it if we want it\n\t\tif ( extra === \"margin\" ) {\n\t\t\tval += jQuery.css( elem, extra + cssExpand[ i ], true, styles );\n\t\t}\n\n\t\tif ( isBorderBox ) {\n\t\t\t// border-box includes padding, so remove it if we want content\n\t\t\tif ( extra === \"content\" ) {\n\t\t\t\tval -= jQuery.css( elem, \"padding\" + cssExpand[ i ], true, styles );\n\t\t\t}\n\n\t\t\t// at this point, extra isn't border nor margin, so remove border\n\t\t\tif ( extra !== \"margin\" ) {\n\t\t\t\tval -= jQuery.css( elem, \"border\" + cssExpand[ i ] + \"Width\", true, styles );\n\t\t\t}\n\t\t} else {\n\t\t\t// at this point, extra isn't content, so add padding\n\t\t\tval += jQuery.css( elem, \"padding\" + cssExpand[ i ], true, styles );\n\n\t\t\t// at this point, extra isn't content nor padding, so add border\n\t\t\tif ( extra !== \"padding\" ) {\n\t\t\t\tval += jQuery.css( elem, \"border\" + cssExpand[ i ] + \"Width\", true, styles );\n\t\t\t}\n\t\t}\n\t}\n\n\treturn val;\n}\n\nfunction getWidthOrHeight( elem, name, extra ) {\n\n\t// Start with offset property, which is equivalent to the border-box value\n\tvar valueIsBorderBox = true,\n\t\tval = name === \"width\" ? elem.offsetWidth : elem.offsetHeight,\n\t\tstyles = getStyles( elem ),\n\t\tisBorderBox = jQuery.support.boxSizing && jQuery.css( elem, \"boxSizing\", false, styles ) === \"border-box\";\n\n\t// some non-html elements return undefined for offsetWidth, so check for null/undefined\n\t// svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285\n\t// MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668\n\tif ( val <= 0 || val == null ) {\n\t\t// Fall back to computed then uncomputed css if necessary\n\t\tval = curCSS( elem, name, styles );\n\t\tif ( val < 0 || val == null ) {\n\t\t\tval = elem.style[ name ];\n\t\t}\n\n\t\t// Computed unit is not pixels. Stop here and return.\n\t\tif ( rnumnonpx.test(val) ) {\n\t\t\treturn val;\n\t\t}\n\n\t\t// we need the check for style in case a browser which returns unreliable values\n\t\t// for getComputedStyle silently falls back to the reliable elem.style\n\t\tvalueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] );\n\n\t\t// Normalize \"\", auto, and prepare for extra\n\t\tval = parseFloat( val ) || 0;\n\t}\n\n\t// use the active box-sizing model to add/subtract irrelevant styles\n\treturn ( val +\n\t\taugmentWidthOrHeight(\n\t\t\telem,\n\t\t\tname,\n\t\t\textra || ( isBorderBox ? \"border\" : \"content\" ),\n\t\t\tvalueIsBorderBox,\n\t\t\tstyles\n\t\t)\n\t) + \"px\";\n}\n\n// Try to determine the default display value of an element\nfunction css_defaultDisplay( nodeName ) {\n\tvar doc = document,\n\t\tdisplay = elemdisplay[ nodeName ];\n\n\tif ( !display ) {\n\t\tdisplay = actualDisplay( nodeName, doc );\n\n\t\t// If the simple way fails, read from inside an iframe\n\t\tif ( display === \"none\" || !display ) {\n\t\t\t// Use the already-created iframe if possible\n\t\t\tiframe = ( iframe ||\n\t\t\t\tjQuery(\"<iframe frameborder='0' width='0' height='0'/>\")\n\t\t\t\t.css( \"cssText\", \"display:block !important\" )\n\t\t\t).appendTo( doc.documentElement );\n\n\t\t\t// Always write a new HTML skeleton so Webkit and Firefox don't choke on reuse\n\t\t\tdoc = ( iframe[0].contentWindow || iframe[0].contentDocument ).document;\n\t\t\tdoc.write(\"<!doctype html><html><body>\");\n\t\t\tdoc.close();\n\n\t\t\tdisplay = actualDisplay( nodeName, doc );\n\t\t\tiframe.detach();\n\t\t}\n\n\t\t// Store the correct default display\n\t\telemdisplay[ nodeName ] = display;\n\t}\n\n\treturn display;\n}\n\n// Called ONLY from within css_defaultDisplay\nfunction actualDisplay( name, doc ) {\n\tvar elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ),\n\t\tdisplay = jQuery.css( elem[0], \"display\" );\n\telem.remove();\n\treturn display;\n}\n\njQuery.each([ \"height\", \"width\" ], function( i, name ) {\n\tjQuery.cssHooks[ name ] = {\n\t\tget: function( elem, computed, extra ) {\n\t\t\tif ( computed ) {\n\t\t\t\t// certain elements can have dimension info if we invisibly show them\n\t\t\t\t// however, it must have a current display style that would benefit from this\n\t\t\t\treturn elem.offsetWidth === 0 && rdisplayswap.test( jQuery.css( elem, \"display\" ) ) ?\n\t\t\t\t\tjQuery.swap( elem, cssShow, function() {\n\t\t\t\t\t\treturn getWidthOrHeight( elem, name, extra );\n\t\t\t\t\t}) :\n\t\t\t\t\tgetWidthOrHeight( elem, name, extra );\n\t\t\t}\n\t\t},\n\n\t\tset: function( elem, value, extra ) {\n\t\t\tvar styles = extra && getStyles( elem );\n\t\t\treturn setPositiveNumber( elem, value, extra ?\n\t\t\t\taugmentWidthOrHeight(\n\t\t\t\t\telem,\n\t\t\t\t\tname,\n\t\t\t\t\textra,\n\t\t\t\t\tjQuery.support.boxSizing && jQuery.css( elem, \"boxSizing\", false, styles ) === \"border-box\",\n\t\t\t\t\tstyles\n\t\t\t\t) : 0\n\t\t\t);\n\t\t}\n\t};\n});\n\nif ( !jQuery.support.opacity ) {\n\tjQuery.cssHooks.opacity = {\n\t\tget: function( elem, computed ) {\n\t\t\t// IE uses filters for opacity\n\t\t\treturn ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || \"\" ) ?\n\t\t\t\t( 0.01 * parseFloat( RegExp.$1 ) ) + \"\" :\n\t\t\t\tcomputed ? \"1\" : \"\";\n\t\t},\n\n\t\tset: function( elem, value ) {\n\t\t\tvar style = elem.style,\n\t\t\t\tcurrentStyle = elem.currentStyle,\n\t\t\t\topacity = jQuery.isNumeric( value ) ? \"alpha(opacity=\" + value * 100 + \")\" : \"\",\n\t\t\t\tfilter = currentStyle && currentStyle.filter || style.filter || \"\";\n\n\t\t\t// IE has trouble with opacity if it does not have layout\n\t\t\t// Force it by setting the zoom level\n\t\t\tstyle.zoom = 1;\n\n\t\t\t// if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652\n\t\t\t// if value === \"\", then remove inline opacity #12685\n\t\t\tif ( ( value >= 1 || value === \"\" ) &&\n\t\t\t\t\tjQuery.trim( filter.replace( ralpha, \"\" ) ) === \"\" &&\n\t\t\t\t\tstyle.removeAttribute ) {\n\n\t\t\t\t// Setting style.filter to null, \"\" & \" \" still leave \"filter:\" in the cssText\n\t\t\t\t// if \"filter:\" is present at all, clearType is disabled, we want to avoid this\n\t\t\t\t// style.removeAttribute is IE Only, but so apparently is this code path...\n\t\t\t\tstyle.removeAttribute( \"filter\" );\n\n\t\t\t\t// if there is no filter style applied in a css rule or unset inline opacity, we are done\n\t\t\t\tif ( value === \"\" || currentStyle && !currentStyle.filter ) {\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// otherwise, set new filter values\n\t\t\tstyle.filter = ralpha.test( filter ) ?\n\t\t\t\tfilter.replace( ralpha, opacity ) :\n\t\t\t\tfilter + \" \" + opacity;\n\t\t}\n\t};\n}\n\n// These hooks cannot be added until DOM ready because the support test\n// for it is not run until after DOM ready\njQuery(function() {\n\tif ( !jQuery.support.reliableMarginRight ) {\n\t\tjQuery.cssHooks.marginRight = {\n\t\t\tget: function( elem, computed ) {\n\t\t\t\tif ( computed ) {\n\t\t\t\t\t// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right\n\t\t\t\t\t// Work around by temporarily setting element display to inline-block\n\t\t\t\t\treturn jQuery.swap( elem, { \"display\": \"inline-block\" },\n\t\t\t\t\t\tcurCSS, [ elem, \"marginRight\" ] );\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n\n\t// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084\n\t// getComputedStyle returns percent when specified for top/left/bottom/right\n\t// rather than make the css module depend on the offset module, we just check for it here\n\tif ( !jQuery.support.pixelPosition && jQuery.fn.position ) {\n\t\tjQuery.each( [ \"top\", \"left\" ], function( i, prop ) {\n\t\t\tjQuery.cssHooks[ prop ] = {\n\t\t\t\tget: function( elem, computed ) {\n\t\t\t\t\tif ( computed ) {\n\t\t\t\t\t\tcomputed = curCSS( elem, prop );\n\t\t\t\t\t\t// if curCSS returns percentage, fallback to offset\n\t\t\t\t\t\treturn rnumnonpx.test( computed ) ?\n\t\t\t\t\t\t\tjQuery( elem ).position()[ prop ] + \"px\" :\n\t\t\t\t\t\t\tcomputed;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t};\n\t\t});\n\t}\n\n});\n\nif ( jQuery.expr && jQuery.expr.filters ) {\n\tjQuery.expr.filters.hidden = function( elem ) {\n\t\t// Support: Opera <= 12.12\n\t\t// Opera reports offsetWidths and offsetHeights less than zero on some elements\n\t\treturn elem.offsetWidth <= 0 && elem.offsetHeight <= 0 ||\n\t\t\t(!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || jQuery.css( elem, \"display\" )) === \"none\");\n\t};\n\n\tjQuery.expr.filters.visible = function( elem ) {\n\t\treturn !jQuery.expr.filters.hidden( elem );\n\t};\n}\n\n// These hooks are used by animate to expand properties\njQuery.each({\n\tmargin: \"\",\n\tpadding: \"\",\n\tborder: \"Width\"\n}, function( prefix, suffix ) {\n\tjQuery.cssHooks[ prefix + suffix ] = {\n\t\texpand: function( value ) {\n\t\t\tvar i = 0,\n\t\t\t\texpanded = {},\n\n\t\t\t\t// assumes a single number if not a string\n\t\t\t\tparts = typeof value === \"string\" ? value.split(\" \") : [ value ];\n\n\t\t\tfor ( ; i < 4; i++ ) {\n\t\t\t\texpanded[ prefix + cssExpand[ i ] + suffix ] =\n\t\t\t\t\tparts[ i ] || parts[ i - 2 ] || parts[ 0 ];\n\t\t\t}\n\n\t\t\treturn expanded;\n\t\t}\n\t};\n\n\tif ( !rmargin.test( prefix ) ) {\n\t\tjQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;\n\t}\n});\nvar r20 = /%20/g,\n\trbracket = /\\[\\]$/,\n\trCRLF = /\\r?\\n/g,\n\trsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,\n\trsubmittable = /^(?:input|select|textarea|keygen)/i;\n\njQuery.fn.extend({\n\tserialize: function() {\n\t\treturn jQuery.param( this.serializeArray() );\n\t},\n\tserializeArray: function() {\n\t\treturn this.map(function(){\n\t\t\t// Can add propHook for \"elements\" to filter or add form elements\n\t\t\tvar elements = jQuery.prop( this, \"elements\" );\n\t\t\treturn elements ? jQuery.makeArray( elements ) : this;\n\t\t})\n\t\t.filter(function(){\n\t\t\tvar type = this.type;\n\t\t\t// Use .is(\":disabled\") so that fieldset[disabled] works\n\t\t\treturn this.name && !jQuery( this ).is( \":disabled\" ) &&\n\t\t\t\trsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&\n\t\t\t\t( this.checked || !manipulation_rcheckableType.test( type ) );\n\t\t})\n\t\t.map(function( i, elem ){\n\t\t\tvar val = jQuery( this ).val();\n\n\t\t\treturn val == null ?\n\t\t\t\tnull :\n\t\t\t\tjQuery.isArray( val ) ?\n\t\t\t\t\tjQuery.map( val, function( val ){\n\t\t\t\t\t\treturn { name: elem.name, value: val.replace( rCRLF, \"\\r\\n\" ) };\n\t\t\t\t\t}) :\n\t\t\t\t\t{ name: elem.name, value: val.replace( rCRLF, \"\\r\\n\" ) };\n\t\t}).get();\n\t}\n});\n\n//Serialize an array of form elements or a set of\n//key/values into a query string\njQuery.param = function( a, traditional ) {\n\tvar prefix,\n\t\ts = [],\n\t\tadd = function( key, value ) {\n\t\t\t// If value is a function, invoke it and return its value\n\t\t\tvalue = jQuery.isFunction( value ) ? value() : ( value == null ? \"\" : value );\n\t\t\ts[ s.length ] = encodeURIComponent( key ) + \"=\" + encodeURIComponent( value );\n\t\t};\n\n\t// Set traditional to true for jQuery <= 1.3.2 behavior.\n\tif ( traditional === undefined ) {\n\t\ttraditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;\n\t}\n\n\t// If an array was passed in, assume that it is an array of form elements.\n\tif ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {\n\t\t// Serialize the form elements\n\t\tjQuery.each( a, function() {\n\t\t\tadd( this.name, this.value );\n\t\t});\n\n\t} else {\n\t\t// If traditional, encode the \"old\" way (the way 1.3.2 or older\n\t\t// did it), otherwise encode params recursively.\n\t\tfor ( prefix in a ) {\n\t\t\tbuildParams( prefix, a[ prefix ], traditional, add );\n\t\t}\n\t}\n\n\t// Return the resulting serialization\n\treturn s.join( \"&\" ).replace( r20, \"+\" );\n};\n\nfunction buildParams( prefix, obj, traditional, add ) {\n\tvar name;\n\n\tif ( jQuery.isArray( obj ) ) {\n\t\t// Serialize array item.\n\t\tjQuery.each( obj, function( i, v ) {\n\t\t\tif ( traditional || rbracket.test( prefix ) ) {\n\t\t\t\t// Treat each array item as a scalar.\n\t\t\t\tadd( prefix, v );\n\n\t\t\t} else {\n\t\t\t\t// Item is non-scalar (array or object), encode its numeric index.\n\t\t\t\tbuildParams( prefix + \"[\" + ( typeof v === \"object\" ? i : \"\" ) + \"]\", v, traditional, add );\n\t\t\t}\n\t\t});\n\n\t} else if ( !traditional && jQuery.type( obj ) === \"object\" ) {\n\t\t// Serialize object item.\n\t\tfor ( name in obj ) {\n\t\t\tbuildParams( prefix + \"[\" + name + \"]\", obj[ name ], traditional, add );\n\t\t}\n\n\t} else {\n\t\t// Serialize scalar item.\n\t\tadd( prefix, obj );\n\t}\n}\njQuery.each( (\"blur focus focusin focusout load resize scroll unload click dblclick \" +\n\t\"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave \" +\n\t\"change select submit keydown keypress keyup error contextmenu\").split(\" \"), function( i, name ) {\n\n\t// Handle event binding\n\tjQuery.fn[ name ] = function( data, fn ) {\n\t\treturn arguments.length > 0 ?\n\t\t\tthis.on( name, null, data, fn ) :\n\t\t\tthis.trigger( name );\n\t};\n});\n\njQuery.fn.extend({\n\thover: function( fnOver, fnOut ) {\n\t\treturn this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );\n\t},\n\n\tbind: function( types, data, fn ) {\n\t\treturn this.on( types, null, data, fn );\n\t},\n\tunbind: function( types, fn ) {\n\t\treturn this.off( types, null, fn );\n\t},\n\n\tdelegate: function( selector, types, data, fn ) {\n\t\treturn this.on( types, selector, data, fn );\n\t},\n\tundelegate: function( selector, types, fn ) {\n\t\t// ( namespace ) or ( selector, types [, fn] )\n\t\treturn arguments.length === 1 ? this.off( selector, \"**\" ) : this.off( types, selector || \"**\", fn );\n\t}\n});\nvar\n\t// Document location\n\tajaxLocParts,\n\tajaxLocation,\n\tajax_nonce = jQuery.now(),\n\n\tajax_rquery = /\\?/,\n\trhash = /#.*$/,\n\trts = /([?&])_=[^&]*/,\n\trheaders = /^(.*?):[ \\t]*([^\\r\\n]*)\\r?$/mg, // IE leaves an \\r character at EOL\n\t// #7653, #8125, #8152: local protocol detection\n\trlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,\n\trnoContent = /^(?:GET|HEAD)$/,\n\trprotocol = /^\\/\\//,\n\trurl = /^([\\w.+-]+:)(?:\\/\\/([^\\/?#:]*)(?::(\\d+)|)|)/,\n\n\t// Keep a copy of the old load method\n\t_load = jQuery.fn.load,\n\n\t/* Prefilters\n\t * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)\n\t * 2) These are called:\n\t *    - BEFORE asking for a transport\n\t *    - AFTER param serialization (s.data is a string if s.processData is true)\n\t * 3) key is the dataType\n\t * 4) the catchall symbol \"*\" can be used\n\t * 5) execution will start with transport dataType and THEN continue down to \"*\" if needed\n\t */\n\tprefilters = {},\n\n\t/* Transports bindings\n\t * 1) key is the dataType\n\t * 2) the catchall symbol \"*\" can be used\n\t * 3) selection will start with transport dataType and THEN go to \"*\" if needed\n\t */\n\ttransports = {},\n\n\t// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression\n\tallTypes = \"*/\".concat(\"*\");\n\n// #8138, IE may throw an exception when accessing\n// a field from window.location if document.domain has been set\ntry {\n\tajaxLocation = location.href;\n} catch( e ) {\n\t// Use the href attribute of an A element\n\t// since IE will modify it given document.location\n\tajaxLocation = document.createElement( \"a\" );\n\tajaxLocation.href = \"\";\n\tajaxLocation = ajaxLocation.href;\n}\n\n// Segment location into parts\najaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];\n\n// Base \"constructor\" for jQuery.ajaxPrefilter and jQuery.ajaxTransport\nfunction addToPrefiltersOrTransports( structure ) {\n\n\t// dataTypeExpression is optional and defaults to \"*\"\n\treturn function( dataTypeExpression, func ) {\n\n\t\tif ( typeof dataTypeExpression !== \"string\" ) {\n\t\t\tfunc = dataTypeExpression;\n\t\t\tdataTypeExpression = \"*\";\n\t\t}\n\n\t\tvar dataType,\n\t\t\ti = 0,\n\t\t\tdataTypes = dataTypeExpression.toLowerCase().match( core_rnotwhite ) || [];\n\n\t\tif ( jQuery.isFunction( func ) ) {\n\t\t\t// For each dataType in the dataTypeExpression\n\t\t\twhile ( (dataType = dataTypes[i++]) ) {\n\t\t\t\t// Prepend if requested\n\t\t\t\tif ( dataType[0] === \"+\" ) {\n\t\t\t\t\tdataType = dataType.slice( 1 ) || \"*\";\n\t\t\t\t\t(structure[ dataType ] = structure[ dataType ] || []).unshift( func );\n\n\t\t\t\t// Otherwise append\n\t\t\t\t} else {\n\t\t\t\t\t(structure[ dataType ] = structure[ dataType ] || []).push( func );\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t};\n}\n\n// Base inspection function for prefilters and transports\nfunction inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {\n\n\tvar inspected = {},\n\t\tseekingTransport = ( structure === transports );\n\n\tfunction inspect( dataType ) {\n\t\tvar selected;\n\t\tinspected[ dataType ] = true;\n\t\tjQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {\n\t\t\tvar dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );\n\t\t\tif( typeof dataTypeOrTransport === \"string\" && !seekingTransport && !inspected[ dataTypeOrTransport ] ) {\n\t\t\t\toptions.dataTypes.unshift( dataTypeOrTransport );\n\t\t\t\tinspect( dataTypeOrTransport );\n\t\t\t\treturn false;\n\t\t\t} else if ( seekingTransport ) {\n\t\t\t\treturn !( selected = dataTypeOrTransport );\n\t\t\t}\n\t\t});\n\t\treturn selected;\n\t}\n\n\treturn inspect( options.dataTypes[ 0 ] ) || !inspected[ \"*\" ] && inspect( \"*\" );\n}\n\n// A special extend for ajax options\n// that takes \"flat\" options (not to be deep extended)\n// Fixes #9887\nfunction ajaxExtend( target, src ) {\n\tvar deep, key,\n\t\tflatOptions = jQuery.ajaxSettings.flatOptions || {};\n\n\tfor ( key in src ) {\n\t\tif ( src[ key ] !== undefined ) {\n\t\t\t( flatOptions[ key ] ? target : ( deep || (deep = {}) ) )[ key ] = src[ key ];\n\t\t}\n\t}\n\tif ( deep ) {\n\t\tjQuery.extend( true, target, deep );\n\t}\n\n\treturn target;\n}\n\njQuery.fn.load = function( url, params, callback ) {\n\tif ( typeof url !== \"string\" && _load ) {\n\t\treturn _load.apply( this, arguments );\n\t}\n\n\tvar selector, response, type,\n\t\tself = this,\n\t\toff = url.indexOf(\" \");\n\n\tif ( off >= 0 ) {\n\t\tselector = url.slice( off, url.length );\n\t\turl = url.slice( 0, off );\n\t}\n\n\t// If it's a function\n\tif ( jQuery.isFunction( params ) ) {\n\n\t\t// We assume that it's the callback\n\t\tcallback = params;\n\t\tparams = undefined;\n\n\t// Otherwise, build a param string\n\t} else if ( params && typeof params === \"object\" ) {\n\t\ttype = \"POST\";\n\t}\n\n\t// If we have elements to modify, make the request\n\tif ( self.length > 0 ) {\n\t\tjQuery.ajax({\n\t\t\turl: url,\n\n\t\t\t// if \"type\" variable is undefined, then \"GET\" method will be used\n\t\t\ttype: type,\n\t\t\tdataType: \"html\",\n\t\t\tdata: params\n\t\t}).done(function( responseText ) {\n\n\t\t\t// Save response for use in complete callback\n\t\t\tresponse = arguments;\n\n\t\t\tself.html( selector ?\n\n\t\t\t\t// If a selector was specified, locate the right elements in a dummy div\n\t\t\t\t// Exclude scripts to avoid IE 'Permission Denied' errors\n\t\t\t\tjQuery(\"<div>\").append( jQuery.parseHTML( responseText ) ).find( selector ) :\n\n\t\t\t\t// Otherwise use the full result\n\t\t\t\tresponseText );\n\n\t\t}).complete( callback && function( jqXHR, status ) {\n\t\t\tself.each( callback, response || [ jqXHR.responseText, status, jqXHR ] );\n\t\t});\n\t}\n\n\treturn this;\n};\n\n// Attach a bunch of functions for handling common AJAX events\njQuery.each( [ \"ajaxStart\", \"ajaxStop\", \"ajaxComplete\", \"ajaxError\", \"ajaxSuccess\", \"ajaxSend\" ], function( i, type ){\n\tjQuery.fn[ type ] = function( fn ){\n\t\treturn this.on( type, fn );\n\t};\n});\n\njQuery.extend({\n\n\t// Counter for holding the number of active queries\n\tactive: 0,\n\n\t// Last-Modified header cache for next request\n\tlastModified: {},\n\tetag: {},\n\n\tajaxSettings: {\n\t\turl: ajaxLocation,\n\t\ttype: \"GET\",\n\t\tisLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),\n\t\tglobal: true,\n\t\tprocessData: true,\n\t\tasync: true,\n\t\tcontentType: \"application/x-www-form-urlencoded; charset=UTF-8\",\n\t\t/*\n\t\ttimeout: 0,\n\t\tdata: null,\n\t\tdataType: null,\n\t\tusername: null,\n\t\tpassword: null,\n\t\tcache: null,\n\t\tthrows: false,\n\t\ttraditional: false,\n\t\theaders: {},\n\t\t*/\n\n\t\taccepts: {\n\t\t\t\"*\": allTypes,\n\t\t\ttext: \"text/plain\",\n\t\t\thtml: \"text/html\",\n\t\t\txml: \"application/xml, text/xml\",\n\t\t\tjson: \"application/json, text/javascript\"\n\t\t},\n\n\t\tcontents: {\n\t\t\txml: /xml/,\n\t\t\thtml: /html/,\n\t\t\tjson: /json/\n\t\t},\n\n\t\tresponseFields: {\n\t\t\txml: \"responseXML\",\n\t\t\ttext: \"responseText\",\n\t\t\tjson: \"responseJSON\"\n\t\t},\n\n\t\t// Data converters\n\t\t// Keys separate source (or catchall \"*\") and destination types with a single space\n\t\tconverters: {\n\n\t\t\t// Convert anything to text\n\t\t\t\"* text\": String,\n\n\t\t\t// Text to html (true = no transformation)\n\t\t\t\"text html\": true,\n\n\t\t\t// Evaluate text as a json expression\n\t\t\t\"text json\": jQuery.parseJSON,\n\n\t\t\t// Parse text as xml\n\t\t\t\"text xml\": jQuery.parseXML\n\t\t},\n\n\t\t// For options that shouldn't be deep extended:\n\t\t// you can add your own custom options here if\n\t\t// and when you create one that shouldn't be\n\t\t// deep extended (see ajaxExtend)\n\t\tflatOptions: {\n\t\t\turl: true,\n\t\t\tcontext: true\n\t\t}\n\t},\n\n\t// Creates a full fledged settings object into target\n\t// with both ajaxSettings and settings fields.\n\t// If target is omitted, writes into ajaxSettings.\n\tajaxSetup: function( target, settings ) {\n\t\treturn settings ?\n\n\t\t\t// Building a settings object\n\t\t\tajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :\n\n\t\t\t// Extending ajaxSettings\n\t\t\tajaxExtend( jQuery.ajaxSettings, target );\n\t},\n\n\tajaxPrefilter: addToPrefiltersOrTransports( prefilters ),\n\tajaxTransport: addToPrefiltersOrTransports( transports ),\n\n\t// Main method\n\tajax: function( url, options ) {\n\n\t\t// If url is an object, simulate pre-1.5 signature\n\t\tif ( typeof url === \"object\" ) {\n\t\t\toptions = url;\n\t\t\turl = undefined;\n\t\t}\n\n\t\t// Force options to be an object\n\t\toptions = options || {};\n\n\t\tvar // Cross-domain detection vars\n\t\t\tparts,\n\t\t\t// Loop variable\n\t\t\ti,\n\t\t\t// URL without anti-cache param\n\t\t\tcacheURL,\n\t\t\t// Response headers as string\n\t\t\tresponseHeadersString,\n\t\t\t// timeout handle\n\t\t\ttimeoutTimer,\n\n\t\t\t// To know if global events are to be dispatched\n\t\t\tfireGlobals,\n\n\t\t\ttransport,\n\t\t\t// Response headers\n\t\t\tresponseHeaders,\n\t\t\t// Create the final options object\n\t\t\ts = jQuery.ajaxSetup( {}, options ),\n\t\t\t// Callbacks context\n\t\t\tcallbackContext = s.context || s,\n\t\t\t// Context for global events is callbackContext if it is a DOM node or jQuery collection\n\t\t\tglobalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ?\n\t\t\t\tjQuery( callbackContext ) :\n\t\t\t\tjQuery.event,\n\t\t\t// Deferreds\n\t\t\tdeferred = jQuery.Deferred(),\n\t\t\tcompleteDeferred = jQuery.Callbacks(\"once memory\"),\n\t\t\t// Status-dependent callbacks\n\t\t\tstatusCode = s.statusCode || {},\n\t\t\t// Headers (they are sent all at once)\n\t\t\trequestHeaders = {},\n\t\t\trequestHeadersNames = {},\n\t\t\t// The jqXHR state\n\t\t\tstate = 0,\n\t\t\t// Default abort message\n\t\t\tstrAbort = \"canceled\",\n\t\t\t// Fake xhr\n\t\t\tjqXHR = {\n\t\t\t\treadyState: 0,\n\n\t\t\t\t// Builds headers hashtable if needed\n\t\t\t\tgetResponseHeader: function( key ) {\n\t\t\t\t\tvar match;\n\t\t\t\t\tif ( state === 2 ) {\n\t\t\t\t\t\tif ( !responseHeaders ) {\n\t\t\t\t\t\t\tresponseHeaders = {};\n\t\t\t\t\t\t\twhile ( (match = rheaders.exec( responseHeadersString )) ) {\n\t\t\t\t\t\t\t\tresponseHeaders[ match[1].toLowerCase() ] = match[ 2 ];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tmatch = responseHeaders[ key.toLowerCase() ];\n\t\t\t\t\t}\n\t\t\t\t\treturn match == null ? null : match;\n\t\t\t\t},\n\n\t\t\t\t// Raw string\n\t\t\t\tgetAllResponseHeaders: function() {\n\t\t\t\t\treturn state === 2 ? responseHeadersString : null;\n\t\t\t\t},\n\n\t\t\t\t// Caches the header\n\t\t\t\tsetRequestHeader: function( name, value ) {\n\t\t\t\t\tvar lname = name.toLowerCase();\n\t\t\t\t\tif ( !state ) {\n\t\t\t\t\t\tname = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;\n\t\t\t\t\t\trequestHeaders[ name ] = value;\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t},\n\n\t\t\t\t// Overrides response content-type header\n\t\t\t\toverrideMimeType: function( type ) {\n\t\t\t\t\tif ( !state ) {\n\t\t\t\t\t\ts.mimeType = type;\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t},\n\n\t\t\t\t// Status-dependent callbacks\n\t\t\t\tstatusCode: function( map ) {\n\t\t\t\t\tvar code;\n\t\t\t\t\tif ( map ) {\n\t\t\t\t\t\tif ( state < 2 ) {\n\t\t\t\t\t\t\tfor ( code in map ) {\n\t\t\t\t\t\t\t\t// Lazy-add the new callback in a way that preserves old ones\n\t\t\t\t\t\t\t\tstatusCode[ code ] = [ statusCode[ code ], map[ code ] ];\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t// Execute the appropriate callbacks\n\t\t\t\t\t\t\tjqXHR.always( map[ jqXHR.status ] );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn this;\n\t\t\t\t},\n\n\t\t\t\t// Cancel the request\n\t\t\t\tabort: function( statusText ) {\n\t\t\t\t\tvar finalText = statusText || strAbort;\n\t\t\t\t\tif ( transport ) {\n\t\t\t\t\t\ttransport.abort( finalText );\n\t\t\t\t\t}\n\t\t\t\t\tdone( 0, finalText );\n\t\t\t\t\treturn this;\n\t\t\t\t}\n\t\t\t};\n\n\t\t// Attach deferreds\n\t\tdeferred.promise( jqXHR ).complete = completeDeferred.add;\n\t\tjqXHR.success = jqXHR.done;\n\t\tjqXHR.error = jqXHR.fail;\n\n\t\t// Remove hash character (#7531: and string promotion)\n\t\t// Add protocol if not provided (#5866: IE7 issue with protocol-less urls)\n\t\t// Handle falsy url in the settings object (#10093: consistency with old signature)\n\t\t// We also use the url parameter if available\n\t\ts.url = ( ( url || s.url || ajaxLocation ) + \"\" ).replace( rhash, \"\" ).replace( rprotocol, ajaxLocParts[ 1 ] + \"//\" );\n\n\t\t// Alias method option to type as per ticket #12004\n\t\ts.type = options.method || options.type || s.method || s.type;\n\n\t\t// Extract dataTypes list\n\t\ts.dataTypes = jQuery.trim( s.dataType || \"*\" ).toLowerCase().match( core_rnotwhite ) || [\"\"];\n\n\t\t// A cross-domain request is in order when we have a protocol:host:port mismatch\n\t\tif ( s.crossDomain == null ) {\n\t\t\tparts = rurl.exec( s.url.toLowerCase() );\n\t\t\ts.crossDomain = !!( parts &&\n\t\t\t\t( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] ||\n\t\t\t\t\t( parts[ 3 ] || ( parts[ 1 ] === \"http:\" ? \"80\" : \"443\" ) ) !==\n\t\t\t\t\t\t( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === \"http:\" ? \"80\" : \"443\" ) ) )\n\t\t\t);\n\t\t}\n\n\t\t// Convert data if not already a string\n\t\tif ( s.data && s.processData && typeof s.data !== \"string\" ) {\n\t\t\ts.data = jQuery.param( s.data, s.traditional );\n\t\t}\n\n\t\t// Apply prefilters\n\t\tinspectPrefiltersOrTransports( prefilters, s, options, jqXHR );\n\n\t\t// If request was aborted inside a prefilter, stop there\n\t\tif ( state === 2 ) {\n\t\t\treturn jqXHR;\n\t\t}\n\n\t\t// We can fire global events as of now if asked to\n\t\tfireGlobals = s.global;\n\n\t\t// Watch for a new set of requests\n\t\tif ( fireGlobals && jQuery.active++ === 0 ) {\n\t\t\tjQuery.event.trigger(\"ajaxStart\");\n\t\t}\n\n\t\t// Uppercase the type\n\t\ts.type = s.type.toUpperCase();\n\n\t\t// Determine if request has content\n\t\ts.hasContent = !rnoContent.test( s.type );\n\n\t\t// Save the URL in case we're toying with the If-Modified-Since\n\t\t// and/or If-None-Match header later on\n\t\tcacheURL = s.url;\n\n\t\t// More options handling for requests with no content\n\t\tif ( !s.hasContent ) {\n\n\t\t\t// If data is available, append data to url\n\t\t\tif ( s.data ) {\n\t\t\t\tcacheURL = ( s.url += ( ajax_rquery.test( cacheURL ) ? \"&\" : \"?\" ) + s.data );\n\t\t\t\t// #9682: remove data so that it's not used in an eventual retry\n\t\t\t\tdelete s.data;\n\t\t\t}\n\n\t\t\t// Add anti-cache in url if needed\n\t\t\tif ( s.cache === false ) {\n\t\t\t\ts.url = rts.test( cacheURL ) ?\n\n\t\t\t\t\t// If there is already a '_' parameter, set its value\n\t\t\t\t\tcacheURL.replace( rts, \"$1_=\" + ajax_nonce++ ) :\n\n\t\t\t\t\t// Otherwise add one to the end\n\t\t\t\t\tcacheURL + ( ajax_rquery.test( cacheURL ) ? \"&\" : \"?\" ) + \"_=\" + ajax_nonce++;\n\t\t\t}\n\t\t}\n\n\t\t// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.\n\t\tif ( s.ifModified ) {\n\t\t\tif ( jQuery.lastModified[ cacheURL ] ) {\n\t\t\t\tjqXHR.setRequestHeader( \"If-Modified-Since\", jQuery.lastModified[ cacheURL ] );\n\t\t\t}\n\t\t\tif ( jQuery.etag[ cacheURL ] ) {\n\t\t\t\tjqXHR.setRequestHeader( \"If-None-Match\", jQuery.etag[ cacheURL ] );\n\t\t\t}\n\t\t}\n\n\t\t// Set the correct header, if data is being sent\n\t\tif ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {\n\t\t\tjqXHR.setRequestHeader( \"Content-Type\", s.contentType );\n\t\t}\n\n\t\t// Set the Accepts header for the server, depending on the dataType\n\t\tjqXHR.setRequestHeader(\n\t\t\t\"Accept\",\n\t\t\ts.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?\n\t\t\t\ts.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== \"*\" ? \", \" + allTypes + \"; q=0.01\" : \"\" ) :\n\t\t\t\ts.accepts[ \"*\" ]\n\t\t);\n\n\t\t// Check for headers option\n\t\tfor ( i in s.headers ) {\n\t\t\tjqXHR.setRequestHeader( i, s.headers[ i ] );\n\t\t}\n\n\t\t// Allow custom headers/mimetypes and early abort\n\t\tif ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {\n\t\t\t// Abort if not done already and return\n\t\t\treturn jqXHR.abort();\n\t\t}\n\n\t\t// aborting is no longer a cancellation\n\t\tstrAbort = \"abort\";\n\n\t\t// Install callbacks on deferreds\n\t\tfor ( i in { success: 1, error: 1, complete: 1 } ) {\n\t\t\tjqXHR[ i ]( s[ i ] );\n\t\t}\n\n\t\t// Get transport\n\t\ttransport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );\n\n\t\t// If no transport, we auto-abort\n\t\tif ( !transport ) {\n\t\t\tdone( -1, \"No Transport\" );\n\t\t} else {\n\t\t\tjqXHR.readyState = 1;\n\n\t\t\t// Send global event\n\t\t\tif ( fireGlobals ) {\n\t\t\t\tglobalEventContext.trigger( \"ajaxSend\", [ jqXHR, s ] );\n\t\t\t}\n\t\t\t// Timeout\n\t\t\tif ( s.async && s.timeout > 0 ) {\n\t\t\t\ttimeoutTimer = setTimeout(function() {\n\t\t\t\t\tjqXHR.abort(\"timeout\");\n\t\t\t\t}, s.timeout );\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tstate = 1;\n\t\t\t\ttransport.send( requestHeaders, done );\n\t\t\t} catch ( e ) {\n\t\t\t\t// Propagate exception as error if not done\n\t\t\t\tif ( state < 2 ) {\n\t\t\t\t\tdone( -1, e );\n\t\t\t\t// Simply rethrow otherwise\n\t\t\t\t} else {\n\t\t\t\t\tthrow e;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Callback for when everything is done\n\t\tfunction done( status, nativeStatusText, responses, headers ) {\n\t\t\tvar isSuccess, success, error, response, modified,\n\t\t\t\tstatusText = nativeStatusText;\n\n\t\t\t// Called once\n\t\t\tif ( state === 2 ) {\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// State is \"done\" now\n\t\t\tstate = 2;\n\n\t\t\t// Clear timeout if it exists\n\t\t\tif ( timeoutTimer ) {\n\t\t\t\tclearTimeout( timeoutTimer );\n\t\t\t}\n\n\t\t\t// Dereference transport for early garbage collection\n\t\t\t// (no matter how long the jqXHR object will be used)\n\t\t\ttransport = undefined;\n\n\t\t\t// Cache response headers\n\t\t\tresponseHeadersString = headers || \"\";\n\n\t\t\t// Set readyState\n\t\t\tjqXHR.readyState = status > 0 ? 4 : 0;\n\n\t\t\t// Determine if successful\n\t\t\tisSuccess = status >= 200 && status < 300 || status === 304;\n\n\t\t\t// Get response data\n\t\t\tif ( responses ) {\n\t\t\t\tresponse = ajaxHandleResponses( s, jqXHR, responses );\n\t\t\t}\n\n\t\t\t// Convert no matter what (that way responseXXX fields are always set)\n\t\t\tresponse = ajaxConvert( s, response, jqXHR, isSuccess );\n\n\t\t\t// If successful, handle type chaining\n\t\t\tif ( isSuccess ) {\n\n\t\t\t\t// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.\n\t\t\t\tif ( s.ifModified ) {\n\t\t\t\t\tmodified = jqXHR.getResponseHeader(\"Last-Modified\");\n\t\t\t\t\tif ( modified ) {\n\t\t\t\t\t\tjQuery.lastModified[ cacheURL ] = modified;\n\t\t\t\t\t}\n\t\t\t\t\tmodified = jqXHR.getResponseHeader(\"etag\");\n\t\t\t\t\tif ( modified ) {\n\t\t\t\t\t\tjQuery.etag[ cacheURL ] = modified;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// if no content\n\t\t\t\tif ( status === 204 || s.type === \"HEAD\" ) {\n\t\t\t\t\tstatusText = \"nocontent\";\n\n\t\t\t\t// if not modified\n\t\t\t\t} else if ( status === 304 ) {\n\t\t\t\t\tstatusText = \"notmodified\";\n\n\t\t\t\t// If we have data, let's convert it\n\t\t\t\t} else {\n\t\t\t\t\tstatusText = response.state;\n\t\t\t\t\tsuccess = response.data;\n\t\t\t\t\terror = response.error;\n\t\t\t\t\tisSuccess = !error;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// We extract error from statusText\n\t\t\t\t// then normalize statusText and status for non-aborts\n\t\t\t\terror = statusText;\n\t\t\t\tif ( status || !statusText ) {\n\t\t\t\t\tstatusText = \"error\";\n\t\t\t\t\tif ( status < 0 ) {\n\t\t\t\t\t\tstatus = 0;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Set data for the fake xhr object\n\t\t\tjqXHR.status = status;\n\t\t\tjqXHR.statusText = ( nativeStatusText || statusText ) + \"\";\n\n\t\t\t// Success/Error\n\t\t\tif ( isSuccess ) {\n\t\t\t\tdeferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );\n\t\t\t} else {\n\t\t\t\tdeferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );\n\t\t\t}\n\n\t\t\t// Status-dependent callbacks\n\t\t\tjqXHR.statusCode( statusCode );\n\t\t\tstatusCode = undefined;\n\n\t\t\tif ( fireGlobals ) {\n\t\t\t\tglobalEventContext.trigger( isSuccess ? \"ajaxSuccess\" : \"ajaxError\",\n\t\t\t\t\t[ jqXHR, s, isSuccess ? success : error ] );\n\t\t\t}\n\n\t\t\t// Complete\n\t\t\tcompleteDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );\n\n\t\t\tif ( fireGlobals ) {\n\t\t\t\tglobalEventContext.trigger( \"ajaxComplete\", [ jqXHR, s ] );\n\t\t\t\t// Handle the global AJAX counter\n\t\t\t\tif ( !( --jQuery.active ) ) {\n\t\t\t\t\tjQuery.event.trigger(\"ajaxStop\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn jqXHR;\n\t},\n\n\tgetJSON: function( url, data, callback ) {\n\t\treturn jQuery.get( url, data, callback, \"json\" );\n\t},\n\n\tgetScript: function( url, callback ) {\n\t\treturn jQuery.get( url, undefined, callback, \"script\" );\n\t}\n});\n\njQuery.each( [ \"get\", \"post\" ], function( i, method ) {\n\tjQuery[ method ] = function( url, data, callback, type ) {\n\t\t// shift arguments if data argument was omitted\n\t\tif ( jQuery.isFunction( data ) ) {\n\t\t\ttype = type || callback;\n\t\t\tcallback = data;\n\t\t\tdata = undefined;\n\t\t}\n\n\t\treturn jQuery.ajax({\n\t\t\turl: url,\n\t\t\ttype: method,\n\t\t\tdataType: type,\n\t\t\tdata: data,\n\t\t\tsuccess: callback\n\t\t});\n\t};\n});\n\n/* Handles responses to an ajax request:\n * - finds the right dataType (mediates between content-type and expected dataType)\n * - returns the corresponding response\n */\nfunction ajaxHandleResponses( s, jqXHR, responses ) {\n\tvar firstDataType, ct, finalDataType, type,\n\t\tcontents = s.contents,\n\t\tdataTypes = s.dataTypes;\n\n\t// Remove auto dataType and get content-type in the process\n\twhile( dataTypes[ 0 ] === \"*\" ) {\n\t\tdataTypes.shift();\n\t\tif ( ct === undefined ) {\n\t\t\tct = s.mimeType || jqXHR.getResponseHeader(\"Content-Type\");\n\t\t}\n\t}\n\n\t// Check if we're dealing with a known content-type\n\tif ( ct ) {\n\t\tfor ( type in contents ) {\n\t\t\tif ( contents[ type ] && contents[ type ].test( ct ) ) {\n\t\t\t\tdataTypes.unshift( type );\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\t// Check to see if we have a response for the expected dataType\n\tif ( dataTypes[ 0 ] in responses ) {\n\t\tfinalDataType = dataTypes[ 0 ];\n\t} else {\n\t\t// Try convertible dataTypes\n\t\tfor ( type in responses ) {\n\t\t\tif ( !dataTypes[ 0 ] || s.converters[ type + \" \" + dataTypes[0] ] ) {\n\t\t\t\tfinalDataType = type;\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tif ( !firstDataType ) {\n\t\t\t\tfirstDataType = type;\n\t\t\t}\n\t\t}\n\t\t// Or just use first one\n\t\tfinalDataType = finalDataType || firstDataType;\n\t}\n\n\t// If we found a dataType\n\t// We add the dataType to the list if needed\n\t// and return the corresponding response\n\tif ( finalDataType ) {\n\t\tif ( finalDataType !== dataTypes[ 0 ] ) {\n\t\t\tdataTypes.unshift( finalDataType );\n\t\t}\n\t\treturn responses[ finalDataType ];\n\t}\n}\n\n/* Chain conversions given the request and the original response\n * Also sets the responseXXX fields on the jqXHR instance\n */\nfunction ajaxConvert( s, response, jqXHR, isSuccess ) {\n\tvar conv2, current, conv, tmp, prev,\n\t\tconverters = {},\n\t\t// Work with a copy of dataTypes in case we need to modify it for conversion\n\t\tdataTypes = s.dataTypes.slice();\n\n\t// Create converters map with lowercased keys\n\tif ( dataTypes[ 1 ] ) {\n\t\tfor ( conv in s.converters ) {\n\t\t\tconverters[ conv.toLowerCase() ] = s.converters[ conv ];\n\t\t}\n\t}\n\n\tcurrent = dataTypes.shift();\n\n\t// Convert to each sequential dataType\n\twhile ( current ) {\n\n\t\tif ( s.responseFields[ current ] ) {\n\t\t\tjqXHR[ s.responseFields[ current ] ] = response;\n\t\t}\n\n\t\t// Apply the dataFilter if provided\n\t\tif ( !prev && isSuccess && s.dataFilter ) {\n\t\t\tresponse = s.dataFilter( response, s.dataType );\n\t\t}\n\n\t\tprev = current;\n\t\tcurrent = dataTypes.shift();\n\n\t\tif ( current ) {\n\n\t\t\t// There's only work to do if current dataType is non-auto\n\t\t\tif ( current === \"*\" ) {\n\n\t\t\t\tcurrent = prev;\n\n\t\t\t// Convert response if prev dataType is non-auto and differs from current\n\t\t\t} else if ( prev !== \"*\" && prev !== current ) {\n\n\t\t\t\t// Seek a direct converter\n\t\t\t\tconv = converters[ prev + \" \" + current ] || converters[ \"* \" + current ];\n\n\t\t\t\t// If none found, seek a pair\n\t\t\t\tif ( !conv ) {\n\t\t\t\t\tfor ( conv2 in converters ) {\n\n\t\t\t\t\t\t// If conv2 outputs current\n\t\t\t\t\t\ttmp = conv2.split( \" \" );\n\t\t\t\t\t\tif ( tmp[ 1 ] === current ) {\n\n\t\t\t\t\t\t\t// If prev can be converted to accepted input\n\t\t\t\t\t\t\tconv = converters[ prev + \" \" + tmp[ 0 ] ] ||\n\t\t\t\t\t\t\t\tconverters[ \"* \" + tmp[ 0 ] ];\n\t\t\t\t\t\t\tif ( conv ) {\n\t\t\t\t\t\t\t\t// Condense equivalence converters\n\t\t\t\t\t\t\t\tif ( conv === true ) {\n\t\t\t\t\t\t\t\t\tconv = converters[ conv2 ];\n\n\t\t\t\t\t\t\t\t// Otherwise, insert the intermediate dataType\n\t\t\t\t\t\t\t\t} else if ( converters[ conv2 ] !== true ) {\n\t\t\t\t\t\t\t\t\tcurrent = tmp[ 0 ];\n\t\t\t\t\t\t\t\t\tdataTypes.unshift( tmp[ 1 ] );\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Apply converter (if not an equivalence)\n\t\t\t\tif ( conv !== true ) {\n\n\t\t\t\t\t// Unless errors are allowed to bubble, catch and return them\n\t\t\t\t\tif ( conv && s[ \"throws\" ] ) {\n\t\t\t\t\t\tresponse = conv( response );\n\t\t\t\t\t} else {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tresponse = conv( response );\n\t\t\t\t\t\t} catch ( e ) {\n\t\t\t\t\t\t\treturn { state: \"parsererror\", error: conv ? e : \"No conversion from \" + prev + \" to \" + current };\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn { state: \"success\", data: response };\n}\n// Install script dataType\njQuery.ajaxSetup({\n\taccepts: {\n\t\tscript: \"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript\"\n\t},\n\tcontents: {\n\t\tscript: /(?:java|ecma)script/\n\t},\n\tconverters: {\n\t\t\"text script\": function( text ) {\n\t\t\tjQuery.globalEval( text );\n\t\t\treturn text;\n\t\t}\n\t}\n});\n\n// Handle cache's special case and global\njQuery.ajaxPrefilter( \"script\", function( s ) {\n\tif ( s.cache === undefined ) {\n\t\ts.cache = false;\n\t}\n\tif ( s.crossDomain ) {\n\t\ts.type = \"GET\";\n\t\ts.global = false;\n\t}\n});\n\n// Bind script tag hack transport\njQuery.ajaxTransport( \"script\", function(s) {\n\n\t// This transport only deals with cross domain requests\n\tif ( s.crossDomain ) {\n\n\t\tvar script,\n\t\t\thead = document.head || jQuery(\"head\")[0] || document.documentElement;\n\n\t\treturn {\n\n\t\t\tsend: function( _, callback ) {\n\n\t\t\t\tscript = document.createElement(\"script\");\n\n\t\t\t\tscript.async = true;\n\n\t\t\t\tif ( s.scriptCharset ) {\n\t\t\t\t\tscript.charset = s.scriptCharset;\n\t\t\t\t}\n\n\t\t\t\tscript.src = s.url;\n\n\t\t\t\t// Attach handlers for all browsers\n\t\t\t\tscript.onload = script.onreadystatechange = function( _, isAbort ) {\n\n\t\t\t\t\tif ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {\n\n\t\t\t\t\t\t// Handle memory leak in IE\n\t\t\t\t\t\tscript.onload = script.onreadystatechange = null;\n\n\t\t\t\t\t\t// Remove the script\n\t\t\t\t\t\tif ( script.parentNode ) {\n\t\t\t\t\t\t\tscript.parentNode.removeChild( script );\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Dereference the script\n\t\t\t\t\t\tscript = null;\n\n\t\t\t\t\t\t// Callback if not abort\n\t\t\t\t\t\tif ( !isAbort ) {\n\t\t\t\t\t\t\tcallback( 200, \"success\" );\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t};\n\n\t\t\t\t// Circumvent IE6 bugs with base elements (#2709 and #4378) by prepending\n\t\t\t\t// Use native DOM manipulation to avoid our domManip AJAX trickery\n\t\t\t\thead.insertBefore( script, head.firstChild );\n\t\t\t},\n\n\t\t\tabort: function() {\n\t\t\t\tif ( script ) {\n\t\t\t\t\tscript.onload( undefined, true );\n\t\t\t\t}\n\t\t\t}\n\t\t};\n\t}\n});\nvar oldCallbacks = [],\n\trjsonp = /(=)\\?(?=&|$)|\\?\\?/;\n\n// Default jsonp settings\njQuery.ajaxSetup({\n\tjsonp: \"callback\",\n\tjsonpCallback: function() {\n\t\tvar callback = oldCallbacks.pop() || ( jQuery.expando + \"_\" + ( ajax_nonce++ ) );\n\t\tthis[ callback ] = true;\n\t\treturn callback;\n\t}\n});\n\n// Detect, normalize options and install callbacks for jsonp requests\njQuery.ajaxPrefilter( \"json jsonp\", function( s, originalSettings, jqXHR ) {\n\n\tvar callbackName, overwritten, responseContainer,\n\t\tjsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?\n\t\t\t\"url\" :\n\t\t\ttypeof s.data === \"string\" && !( s.contentType || \"\" ).indexOf(\"application/x-www-form-urlencoded\") && rjsonp.test( s.data ) && \"data\"\n\t\t);\n\n\t// Handle iff the expected data type is \"jsonp\" or we have a parameter to set\n\tif ( jsonProp || s.dataTypes[ 0 ] === \"jsonp\" ) {\n\n\t\t// Get callback name, remembering preexisting value associated with it\n\t\tcallbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?\n\t\t\ts.jsonpCallback() :\n\t\t\ts.jsonpCallback;\n\n\t\t// Insert callback into url or form data\n\t\tif ( jsonProp ) {\n\t\t\ts[ jsonProp ] = s[ jsonProp ].replace( rjsonp, \"$1\" + callbackName );\n\t\t} else if ( s.jsonp !== false ) {\n\t\t\ts.url += ( ajax_rquery.test( s.url ) ? \"&\" : \"?\" ) + s.jsonp + \"=\" + callbackName;\n\t\t}\n\n\t\t// Use data converter to retrieve json after script execution\n\t\ts.converters[\"script json\"] = function() {\n\t\t\tif ( !responseContainer ) {\n\t\t\t\tjQuery.error( callbackName + \" was not called\" );\n\t\t\t}\n\t\t\treturn responseContainer[ 0 ];\n\t\t};\n\n\t\t// force json dataType\n\t\ts.dataTypes[ 0 ] = \"json\";\n\n\t\t// Install callback\n\t\toverwritten = window[ callbackName ];\n\t\twindow[ callbackName ] = function() {\n\t\t\tresponseContainer = arguments;\n\t\t};\n\n\t\t// Clean-up function (fires after converters)\n\t\tjqXHR.always(function() {\n\t\t\t// Restore preexisting value\n\t\t\twindow[ callbackName ] = overwritten;\n\n\t\t\t// Save back as free\n\t\t\tif ( s[ callbackName ] ) {\n\t\t\t\t// make sure that re-using the options doesn't screw things around\n\t\t\t\ts.jsonpCallback = originalSettings.jsonpCallback;\n\n\t\t\t\t// save the callback name for future use\n\t\t\t\toldCallbacks.push( callbackName );\n\t\t\t}\n\n\t\t\t// Call if it was a function and we have a response\n\t\t\tif ( responseContainer && jQuery.isFunction( overwritten ) ) {\n\t\t\t\toverwritten( responseContainer[ 0 ] );\n\t\t\t}\n\n\t\t\tresponseContainer = overwritten = undefined;\n\t\t});\n\n\t\t// Delegate to script\n\t\treturn \"script\";\n\t}\n});\nvar xhrCallbacks, xhrSupported,\n\txhrId = 0,\n\t// #5280: Internet Explorer will keep connections alive if we don't abort on unload\n\txhrOnUnloadAbort = window.ActiveXObject && function() {\n\t\t// Abort all pending requests\n\t\tvar key;\n\t\tfor ( key in xhrCallbacks ) {\n\t\t\txhrCallbacks[ key ]( undefined, true );\n\t\t}\n\t};\n\n// Functions to create xhrs\nfunction createStandardXHR() {\n\ttry {\n\t\treturn new window.XMLHttpRequest();\n\t} catch( e ) {}\n}\n\nfunction createActiveXHR() {\n\ttry {\n\t\treturn new window.ActiveXObject(\"Microsoft.XMLHTTP\");\n\t} catch( e ) {}\n}\n\n// Create the request object\n// (This is still attached to ajaxSettings for backward compatibility)\njQuery.ajaxSettings.xhr = window.ActiveXObject ?\n\t/* Microsoft failed to properly\n\t * implement the XMLHttpRequest in IE7 (can't request local files),\n\t * so we use the ActiveXObject when it is available\n\t * Additionally XMLHttpRequest can be disabled in IE7/IE8 so\n\t * we need a fallback.\n\t */\n\tfunction() {\n\t\treturn !this.isLocal && createStandardXHR() || createActiveXHR();\n\t} :\n\t// For all other browsers, use the standard XMLHttpRequest object\n\tcreateStandardXHR;\n\n// Determine support properties\nxhrSupported = jQuery.ajaxSettings.xhr();\njQuery.support.cors = !!xhrSupported && ( \"withCredentials\" in xhrSupported );\nxhrSupported = jQuery.support.ajax = !!xhrSupported;\n\n// Create transport if the browser can provide an xhr\nif ( xhrSupported ) {\n\n\tjQuery.ajaxTransport(function( s ) {\n\t\t// Cross domain only allowed if supported through XMLHttpRequest\n\t\tif ( !s.crossDomain || jQuery.support.cors ) {\n\n\t\t\tvar callback;\n\n\t\t\treturn {\n\t\t\t\tsend: function( headers, complete ) {\n\n\t\t\t\t\t// Get a new xhr\n\t\t\t\t\tvar handle, i,\n\t\t\t\t\t\txhr = s.xhr();\n\n\t\t\t\t\t// Open the socket\n\t\t\t\t\t// Passing null username, generates a login popup on Opera (#2865)\n\t\t\t\t\tif ( s.username ) {\n\t\t\t\t\t\txhr.open( s.type, s.url, s.async, s.username, s.password );\n\t\t\t\t\t} else {\n\t\t\t\t\t\txhr.open( s.type, s.url, s.async );\n\t\t\t\t\t}\n\n\t\t\t\t\t// Apply custom fields if provided\n\t\t\t\t\tif ( s.xhrFields ) {\n\t\t\t\t\t\tfor ( i in s.xhrFields ) {\n\t\t\t\t\t\t\txhr[ i ] = s.xhrFields[ i ];\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// Override mime type if needed\n\t\t\t\t\tif ( s.mimeType && xhr.overrideMimeType ) {\n\t\t\t\t\t\txhr.overrideMimeType( s.mimeType );\n\t\t\t\t\t}\n\n\t\t\t\t\t// X-Requested-With header\n\t\t\t\t\t// For cross-domain requests, seeing as conditions for a preflight are\n\t\t\t\t\t// akin to a jigsaw puzzle, we simply never set it to be sure.\n\t\t\t\t\t// (it can always be set on a per-request basis or even using ajaxSetup)\n\t\t\t\t\t// For same-domain requests, won't change header if already provided.\n\t\t\t\t\tif ( !s.crossDomain && !headers[\"X-Requested-With\"] ) {\n\t\t\t\t\t\theaders[\"X-Requested-With\"] = \"XMLHttpRequest\";\n\t\t\t\t\t}\n\n\t\t\t\t\t// Need an extra try/catch for cross domain requests in Firefox 3\n\t\t\t\t\ttry {\n\t\t\t\t\t\tfor ( i in headers ) {\n\t\t\t\t\t\t\txhr.setRequestHeader( i, headers[ i ] );\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch( err ) {}\n\n\t\t\t\t\t// Do send the request\n\t\t\t\t\t// This may raise an exception which is actually\n\t\t\t\t\t// handled in jQuery.ajax (so no try/catch here)\n\t\t\t\t\txhr.send( ( s.hasContent && s.data ) || null );\n\n\t\t\t\t\t// Listener\n\t\t\t\t\tcallback = function( _, isAbort ) {\n\t\t\t\t\t\tvar status, responseHeaders, statusText, responses;\n\n\t\t\t\t\t\t// Firefox throws exceptions when accessing properties\n\t\t\t\t\t\t// of an xhr when a network error occurred\n\t\t\t\t\t\t// http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE)\n\t\t\t\t\t\ttry {\n\n\t\t\t\t\t\t\t// Was never called and is aborted or complete\n\t\t\t\t\t\t\tif ( callback && ( isAbort || xhr.readyState === 4 ) ) {\n\n\t\t\t\t\t\t\t\t// Only called once\n\t\t\t\t\t\t\t\tcallback = undefined;\n\n\t\t\t\t\t\t\t\t// Do not keep as active anymore\n\t\t\t\t\t\t\t\tif ( handle ) {\n\t\t\t\t\t\t\t\t\txhr.onreadystatechange = jQuery.noop;\n\t\t\t\t\t\t\t\t\tif ( xhrOnUnloadAbort ) {\n\t\t\t\t\t\t\t\t\t\tdelete xhrCallbacks[ handle ];\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t// If it's an abort\n\t\t\t\t\t\t\t\tif ( isAbort ) {\n\t\t\t\t\t\t\t\t\t// Abort it manually if needed\n\t\t\t\t\t\t\t\t\tif ( xhr.readyState !== 4 ) {\n\t\t\t\t\t\t\t\t\t\txhr.abort();\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\tresponses = {};\n\t\t\t\t\t\t\t\t\tstatus = xhr.status;\n\t\t\t\t\t\t\t\t\tresponseHeaders = xhr.getAllResponseHeaders();\n\n\t\t\t\t\t\t\t\t\t// When requesting binary data, IE6-9 will throw an exception\n\t\t\t\t\t\t\t\t\t// on any attempt to access responseText (#11426)\n\t\t\t\t\t\t\t\t\tif ( typeof xhr.responseText === \"string\" ) {\n\t\t\t\t\t\t\t\t\t\tresponses.text = xhr.responseText;\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t// Firefox throws an exception when accessing\n\t\t\t\t\t\t\t\t\t// statusText for faulty cross-domain requests\n\t\t\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\t\t\tstatusText = xhr.statusText;\n\t\t\t\t\t\t\t\t\t} catch( e ) {\n\t\t\t\t\t\t\t\t\t\t// We normalize with Webkit giving an empty statusText\n\t\t\t\t\t\t\t\t\t\tstatusText = \"\";\n\t\t\t\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t\t\t// Filter status for non standard behaviors\n\n\t\t\t\t\t\t\t\t\t// If the request is local and we have data: assume a success\n\t\t\t\t\t\t\t\t\t// (success with no data won't get notified, that's the best we\n\t\t\t\t\t\t\t\t\t// can do given current implementations)\n\t\t\t\t\t\t\t\t\tif ( !status && s.isLocal && !s.crossDomain ) {\n\t\t\t\t\t\t\t\t\t\tstatus = responses.text ? 200 : 404;\n\t\t\t\t\t\t\t\t\t// IE - #1450: sometimes returns 1223 when it should be 204\n\t\t\t\t\t\t\t\t\t} else if ( status === 1223 ) {\n\t\t\t\t\t\t\t\t\t\tstatus = 204;\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch( firefoxAccessException ) {\n\t\t\t\t\t\t\tif ( !isAbort ) {\n\t\t\t\t\t\t\t\tcomplete( -1, firefoxAccessException );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t// Call complete if needed\n\t\t\t\t\t\tif ( responses ) {\n\t\t\t\t\t\t\tcomplete( status, statusText, responses, responseHeaders );\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\tif ( !s.async ) {\n\t\t\t\t\t\t// if we're in sync mode we fire the callback\n\t\t\t\t\t\tcallback();\n\t\t\t\t\t} else if ( xhr.readyState === 4 ) {\n\t\t\t\t\t\t// (IE6 & IE7) if it's in cache and has been\n\t\t\t\t\t\t// retrieved directly we need to fire the callback\n\t\t\t\t\t\tsetTimeout( callback );\n\t\t\t\t\t} else {\n\t\t\t\t\t\thandle = ++xhrId;\n\t\t\t\t\t\tif ( xhrOnUnloadAbort ) {\n\t\t\t\t\t\t\t// Create the active xhrs callbacks list if needed\n\t\t\t\t\t\t\t// and attach the unload handler\n\t\t\t\t\t\t\tif ( !xhrCallbacks ) {\n\t\t\t\t\t\t\t\txhrCallbacks = {};\n\t\t\t\t\t\t\t\tjQuery( window ).unload( xhrOnUnloadAbort );\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// Add to list of active xhrs callbacks\n\t\t\t\t\t\t\txhrCallbacks[ handle ] = callback;\n\t\t\t\t\t\t}\n\t\t\t\t\t\txhr.onreadystatechange = callback;\n\t\t\t\t\t}\n\t\t\t\t},\n\n\t\t\t\tabort: function() {\n\t\t\t\t\tif ( callback ) {\n\t\t\t\t\t\tcallback( undefined, true );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\t});\n}\nvar fxNow, timerId,\n\trfxtypes = /^(?:toggle|show|hide)$/,\n\trfxnum = new RegExp( \"^(?:([+-])=|)(\" + core_pnum + \")([a-z%]*)$\", \"i\" ),\n\trrun = /queueHooks$/,\n\tanimationPrefilters = [ defaultPrefilter ],\n\ttweeners = {\n\t\t\"*\": [function( prop, value ) {\n\t\t\tvar tween = this.createTween( prop, value ),\n\t\t\t\ttarget = tween.cur(),\n\t\t\t\tparts = rfxnum.exec( value ),\n\t\t\t\tunit = parts && parts[ 3 ] || ( jQuery.cssNumber[ prop ] ? \"\" : \"px\" ),\n\n\t\t\t\t// Starting value computation is required for potential unit mismatches\n\t\t\t\tstart = ( jQuery.cssNumber[ prop ] || unit !== \"px\" && +target ) &&\n\t\t\t\t\trfxnum.exec( jQuery.css( tween.elem, prop ) ),\n\t\t\t\tscale = 1,\n\t\t\t\tmaxIterations = 20;\n\n\t\t\tif ( start && start[ 3 ] !== unit ) {\n\t\t\t\t// Trust units reported by jQuery.css\n\t\t\t\tunit = unit || start[ 3 ];\n\n\t\t\t\t// Make sure we update the tween properties later on\n\t\t\t\tparts = parts || [];\n\n\t\t\t\t// Iteratively approximate from a nonzero starting point\n\t\t\t\tstart = +target || 1;\n\n\t\t\t\tdo {\n\t\t\t\t\t// If previous iteration zeroed out, double until we get *something*\n\t\t\t\t\t// Use a string for doubling factor so we don't accidentally see scale as unchanged below\n\t\t\t\t\tscale = scale || \".5\";\n\n\t\t\t\t\t// Adjust and apply\n\t\t\t\t\tstart = start / scale;\n\t\t\t\t\tjQuery.style( tween.elem, prop, start + unit );\n\n\t\t\t\t// Update scale, tolerating zero or NaN from tween.cur()\n\t\t\t\t// And breaking the loop if scale is unchanged or perfect, or if we've just had enough\n\t\t\t\t} while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations );\n\t\t\t}\n\n\t\t\t// Update tween properties\n\t\t\tif ( parts ) {\n\t\t\t\tstart = tween.start = +start || +target || 0;\n\t\t\t\ttween.unit = unit;\n\t\t\t\t// If a +=/-= token was provided, we're doing a relative animation\n\t\t\t\ttween.end = parts[ 1 ] ?\n\t\t\t\t\tstart + ( parts[ 1 ] + 1 ) * parts[ 2 ] :\n\t\t\t\t\t+parts[ 2 ];\n\t\t\t}\n\n\t\t\treturn tween;\n\t\t}]\n\t};\n\n// Animations created synchronously will run synchronously\nfunction createFxNow() {\n\tsetTimeout(function() {\n\t\tfxNow = undefined;\n\t});\n\treturn ( fxNow = jQuery.now() );\n}\n\nfunction createTween( value, prop, animation ) {\n\tvar tween,\n\t\tcollection = ( tweeners[ prop ] || [] ).concat( tweeners[ \"*\" ] ),\n\t\tindex = 0,\n\t\tlength = collection.length;\n\tfor ( ; index < length; index++ ) {\n\t\tif ( (tween = collection[ index ].call( animation, prop, value )) ) {\n\n\t\t\t// we're done with this property\n\t\t\treturn tween;\n\t\t}\n\t}\n}\n\nfunction Animation( elem, properties, options ) {\n\tvar result,\n\t\tstopped,\n\t\tindex = 0,\n\t\tlength = animationPrefilters.length,\n\t\tdeferred = jQuery.Deferred().always( function() {\n\t\t\t// don't match elem in the :animated selector\n\t\t\tdelete tick.elem;\n\t\t}),\n\t\ttick = function() {\n\t\t\tif ( stopped ) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tvar currentTime = fxNow || createFxNow(),\n\t\t\t\tremaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),\n\t\t\t\t// archaic crash bug won't allow us to use 1 - ( 0.5 || 0 ) (#12497)\n\t\t\t\ttemp = remaining / animation.duration || 0,\n\t\t\t\tpercent = 1 - temp,\n\t\t\t\tindex = 0,\n\t\t\t\tlength = animation.tweens.length;\n\n\t\t\tfor ( ; index < length ; index++ ) {\n\t\t\t\tanimation.tweens[ index ].run( percent );\n\t\t\t}\n\n\t\t\tdeferred.notifyWith( elem, [ animation, percent, remaining ]);\n\n\t\t\tif ( percent < 1 && length ) {\n\t\t\t\treturn remaining;\n\t\t\t} else {\n\t\t\t\tdeferred.resolveWith( elem, [ animation ] );\n\t\t\t\treturn false;\n\t\t\t}\n\t\t},\n\t\tanimation = deferred.promise({\n\t\t\telem: elem,\n\t\t\tprops: jQuery.extend( {}, properties ),\n\t\t\topts: jQuery.extend( true, { specialEasing: {} }, options ),\n\t\t\toriginalProperties: properties,\n\t\t\toriginalOptions: options,\n\t\t\tstartTime: fxNow || createFxNow(),\n\t\t\tduration: options.duration,\n\t\t\ttweens: [],\n\t\t\tcreateTween: function( prop, end ) {\n\t\t\t\tvar tween = jQuery.Tween( elem, animation.opts, prop, end,\n\t\t\t\t\t\tanimation.opts.specialEasing[ prop ] || animation.opts.easing );\n\t\t\t\tanimation.tweens.push( tween );\n\t\t\t\treturn tween;\n\t\t\t},\n\t\t\tstop: function( gotoEnd ) {\n\t\t\t\tvar index = 0,\n\t\t\t\t\t// if we are going to the end, we want to run all the tweens\n\t\t\t\t\t// otherwise we skip this part\n\t\t\t\t\tlength = gotoEnd ? animation.tweens.length : 0;\n\t\t\t\tif ( stopped ) {\n\t\t\t\t\treturn this;\n\t\t\t\t}\n\t\t\t\tstopped = true;\n\t\t\t\tfor ( ; index < length ; index++ ) {\n\t\t\t\t\tanimation.tweens[ index ].run( 1 );\n\t\t\t\t}\n\n\t\t\t\t// resolve when we played the last frame\n\t\t\t\t// otherwise, reject\n\t\t\t\tif ( gotoEnd ) {\n\t\t\t\t\tdeferred.resolveWith( elem, [ animation, gotoEnd ] );\n\t\t\t\t} else {\n\t\t\t\t\tdeferred.rejectWith( elem, [ animation, gotoEnd ] );\n\t\t\t\t}\n\t\t\t\treturn this;\n\t\t\t}\n\t\t}),\n\t\tprops = animation.props;\n\n\tpropFilter( props, animation.opts.specialEasing );\n\n\tfor ( ; index < length ; index++ ) {\n\t\tresult = animationPrefilters[ index ].call( animation, elem, props, animation.opts );\n\t\tif ( result ) {\n\t\t\treturn result;\n\t\t}\n\t}\n\n\tjQuery.map( props, createTween, animation );\n\n\tif ( jQuery.isFunction( animation.opts.start ) ) {\n\t\tanimation.opts.start.call( elem, animation );\n\t}\n\n\tjQuery.fx.timer(\n\t\tjQuery.extend( tick, {\n\t\t\telem: elem,\n\t\t\tanim: animation,\n\t\t\tqueue: animation.opts.queue\n\t\t})\n\t);\n\n\t// attach callbacks from options\n\treturn animation.progress( animation.opts.progress )\n\t\t.done( animation.opts.done, animation.opts.complete )\n\t\t.fail( animation.opts.fail )\n\t\t.always( animation.opts.always );\n}\n\nfunction propFilter( props, specialEasing ) {\n\tvar index, name, easing, value, hooks;\n\n\t// camelCase, specialEasing and expand cssHook pass\n\tfor ( index in props ) {\n\t\tname = jQuery.camelCase( index );\n\t\teasing = specialEasing[ name ];\n\t\tvalue = props[ index ];\n\t\tif ( jQuery.isArray( value ) ) {\n\t\t\teasing = value[ 1 ];\n\t\t\tvalue = props[ index ] = value[ 0 ];\n\t\t}\n\n\t\tif ( index !== name ) {\n\t\t\tprops[ name ] = value;\n\t\t\tdelete props[ index ];\n\t\t}\n\n\t\thooks = jQuery.cssHooks[ name ];\n\t\tif ( hooks && \"expand\" in hooks ) {\n\t\t\tvalue = hooks.expand( value );\n\t\t\tdelete props[ name ];\n\n\t\t\t// not quite $.extend, this wont overwrite keys already present.\n\t\t\t// also - reusing 'index' from above because we have the correct \"name\"\n\t\t\tfor ( index in value ) {\n\t\t\t\tif ( !( index in props ) ) {\n\t\t\t\t\tprops[ index ] = value[ index ];\n\t\t\t\t\tspecialEasing[ index ] = easing;\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tspecialEasing[ name ] = easing;\n\t\t}\n\t}\n}\n\njQuery.Animation = jQuery.extend( Animation, {\n\n\ttweener: function( props, callback ) {\n\t\tif ( jQuery.isFunction( props ) ) {\n\t\t\tcallback = props;\n\t\t\tprops = [ \"*\" ];\n\t\t} else {\n\t\t\tprops = props.split(\" \");\n\t\t}\n\n\t\tvar prop,\n\t\t\tindex = 0,\n\t\t\tlength = props.length;\n\n\t\tfor ( ; index < length ; index++ ) {\n\t\t\tprop = props[ index ];\n\t\t\ttweeners[ prop ] = tweeners[ prop ] || [];\n\t\t\ttweeners[ prop ].unshift( callback );\n\t\t}\n\t},\n\n\tprefilter: function( callback, prepend ) {\n\t\tif ( prepend ) {\n\t\t\tanimationPrefilters.unshift( callback );\n\t\t} else {\n\t\t\tanimationPrefilters.push( callback );\n\t\t}\n\t}\n});\n\nfunction defaultPrefilter( elem, props, opts ) {\n\t/* jshint validthis: true */\n\tvar prop, value, toggle, tween, hooks, oldfire,\n\t\tanim = this,\n\t\torig = {},\n\t\tstyle = elem.style,\n\t\thidden = elem.nodeType && isHidden( elem ),\n\t\tdataShow = jQuery._data( elem, \"fxshow\" );\n\n\t// handle queue: false promises\n\tif ( !opts.queue ) {\n\t\thooks = jQuery._queueHooks( elem, \"fx\" );\n\t\tif ( hooks.unqueued == null ) {\n\t\t\thooks.unqueued = 0;\n\t\t\toldfire = hooks.empty.fire;\n\t\t\thooks.empty.fire = function() {\n\t\t\t\tif ( !hooks.unqueued ) {\n\t\t\t\t\toldfire();\n\t\t\t\t}\n\t\t\t};\n\t\t}\n\t\thooks.unqueued++;\n\n\t\tanim.always(function() {\n\t\t\t// doing this makes sure that the complete handler will be called\n\t\t\t// before this completes\n\t\t\tanim.always(function() {\n\t\t\t\thooks.unqueued--;\n\t\t\t\tif ( !jQuery.queue( elem, \"fx\" ).length ) {\n\t\t\t\t\thooks.empty.fire();\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t}\n\n\t// height/width overflow pass\n\tif ( elem.nodeType === 1 && ( \"height\" in props || \"width\" in props ) ) {\n\t\t// Make sure that nothing sneaks out\n\t\t// Record all 3 overflow attributes because IE does not\n\t\t// change the overflow attribute when overflowX and\n\t\t// overflowY are set to the same value\n\t\topts.overflow = [ style.overflow, style.overflowX, style.overflowY ];\n\n\t\t// Set display property to inline-block for height/width\n\t\t// animations on inline elements that are having width/height animated\n\t\tif ( jQuery.css( elem, \"display\" ) === \"inline\" &&\n\t\t\t\tjQuery.css( elem, \"float\" ) === \"none\" ) {\n\n\t\t\t// inline-level elements accept inline-block;\n\t\t\t// block-level elements need to be inline with layout\n\t\t\tif ( !jQuery.support.inlineBlockNeedsLayout || css_defaultDisplay( elem.nodeName ) === \"inline\" ) {\n\t\t\t\tstyle.display = \"inline-block\";\n\n\t\t\t} else {\n\t\t\t\tstyle.zoom = 1;\n\t\t\t}\n\t\t}\n\t}\n\n\tif ( opts.overflow ) {\n\t\tstyle.overflow = \"hidden\";\n\t\tif ( !jQuery.support.shrinkWrapBlocks ) {\n\t\t\tanim.always(function() {\n\t\t\t\tstyle.overflow = opts.overflow[ 0 ];\n\t\t\t\tstyle.overflowX = opts.overflow[ 1 ];\n\t\t\t\tstyle.overflowY = opts.overflow[ 2 ];\n\t\t\t});\n\t\t}\n\t}\n\n\n\t// show/hide pass\n\tfor ( prop in props ) {\n\t\tvalue = props[ prop ];\n\t\tif ( rfxtypes.exec( value ) ) {\n\t\t\tdelete props[ prop ];\n\t\t\ttoggle = toggle || value === \"toggle\";\n\t\t\tif ( value === ( hidden ? \"hide\" : \"show\" ) ) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\torig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );\n\t\t}\n\t}\n\n\tif ( !jQuery.isEmptyObject( orig ) ) {\n\t\tif ( dataShow ) {\n\t\t\tif ( \"hidden\" in dataShow ) {\n\t\t\t\thidden = dataShow.hidden;\n\t\t\t}\n\t\t} else {\n\t\t\tdataShow = jQuery._data( elem, \"fxshow\", {} );\n\t\t}\n\n\t\t// store state if its toggle - enables .stop().toggle() to \"reverse\"\n\t\tif ( toggle ) {\n\t\t\tdataShow.hidden = !hidden;\n\t\t}\n\t\tif ( hidden ) {\n\t\t\tjQuery( elem ).show();\n\t\t} else {\n\t\t\tanim.done(function() {\n\t\t\t\tjQuery( elem ).hide();\n\t\t\t});\n\t\t}\n\t\tanim.done(function() {\n\t\t\tvar prop;\n\t\t\tjQuery._removeData( elem, \"fxshow\" );\n\t\t\tfor ( prop in orig ) {\n\t\t\t\tjQuery.style( elem, prop, orig[ prop ] );\n\t\t\t}\n\t\t});\n\t\tfor ( prop in orig ) {\n\t\t\ttween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );\n\n\t\t\tif ( !( prop in dataShow ) ) {\n\t\t\t\tdataShow[ prop ] = tween.start;\n\t\t\t\tif ( hidden ) {\n\t\t\t\t\ttween.end = tween.start;\n\t\t\t\t\ttween.start = prop === \"width\" || prop === \"height\" ? 1 : 0;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n}\n\nfunction Tween( elem, options, prop, end, easing ) {\n\treturn new Tween.prototype.init( elem, options, prop, end, easing );\n}\njQuery.Tween = Tween;\n\nTween.prototype = {\n\tconstructor: Tween,\n\tinit: function( elem, options, prop, end, easing, unit ) {\n\t\tthis.elem = elem;\n\t\tthis.prop = prop;\n\t\tthis.easing = easing || \"swing\";\n\t\tthis.options = options;\n\t\tthis.start = this.now = this.cur();\n\t\tthis.end = end;\n\t\tthis.unit = unit || ( jQuery.cssNumber[ prop ] ? \"\" : \"px\" );\n\t},\n\tcur: function() {\n\t\tvar hooks = Tween.propHooks[ this.prop ];\n\n\t\treturn hooks && hooks.get ?\n\t\t\thooks.get( this ) :\n\t\t\tTween.propHooks._default.get( this );\n\t},\n\trun: function( percent ) {\n\t\tvar eased,\n\t\t\thooks = Tween.propHooks[ this.prop ];\n\n\t\tif ( this.options.duration ) {\n\t\t\tthis.pos = eased = jQuery.easing[ this.easing ](\n\t\t\t\tpercent, this.options.duration * percent, 0, 1, this.options.duration\n\t\t\t);\n\t\t} else {\n\t\t\tthis.pos = eased = percent;\n\t\t}\n\t\tthis.now = ( this.end - this.start ) * eased + this.start;\n\n\t\tif ( this.options.step ) {\n\t\t\tthis.options.step.call( this.elem, this.now, this );\n\t\t}\n\n\t\tif ( hooks && hooks.set ) {\n\t\t\thooks.set( this );\n\t\t} else {\n\t\t\tTween.propHooks._default.set( this );\n\t\t}\n\t\treturn this;\n\t}\n};\n\nTween.prototype.init.prototype = Tween.prototype;\n\nTween.propHooks = {\n\t_default: {\n\t\tget: function( tween ) {\n\t\t\tvar result;\n\n\t\t\tif ( tween.elem[ tween.prop ] != null &&\n\t\t\t\t(!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) {\n\t\t\t\treturn tween.elem[ tween.prop ];\n\t\t\t}\n\n\t\t\t// passing an empty string as a 3rd parameter to .css will automatically\n\t\t\t// attempt a parseFloat and fallback to a string if the parse fails\n\t\t\t// so, simple values such as \"10px\" are parsed to Float.\n\t\t\t// complex values such as \"rotate(1rad)\" are returned as is.\n\t\t\tresult = jQuery.css( tween.elem, tween.prop, \"\" );\n\t\t\t// Empty strings, null, undefined and \"auto\" are converted to 0.\n\t\t\treturn !result || result === \"auto\" ? 0 : result;\n\t\t},\n\t\tset: function( tween ) {\n\t\t\t// use step hook for back compat - use cssHook if its there - use .style if its\n\t\t\t// available and use plain properties where available\n\t\t\tif ( jQuery.fx.step[ tween.prop ] ) {\n\t\t\t\tjQuery.fx.step[ tween.prop ]( tween );\n\t\t\t} else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) {\n\t\t\t\tjQuery.style( tween.elem, tween.prop, tween.now + tween.unit );\n\t\t\t} else {\n\t\t\t\ttween.elem[ tween.prop ] = tween.now;\n\t\t\t}\n\t\t}\n\t}\n};\n\n// Support: IE <=9\n// Panic based approach to setting things on disconnected nodes\n\nTween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {\n\tset: function( tween ) {\n\t\tif ( tween.elem.nodeType && tween.elem.parentNode ) {\n\t\t\ttween.elem[ tween.prop ] = tween.now;\n\t\t}\n\t}\n};\n\njQuery.each([ \"toggle\", \"show\", \"hide\" ], function( i, name ) {\n\tvar cssFn = jQuery.fn[ name ];\n\tjQuery.fn[ name ] = function( speed, easing, callback ) {\n\t\treturn speed == null || typeof speed === \"boolean\" ?\n\t\t\tcssFn.apply( this, arguments ) :\n\t\t\tthis.animate( genFx( name, true ), speed, easing, callback );\n\t};\n});\n\njQuery.fn.extend({\n\tfadeTo: function( speed, to, easing, callback ) {\n\n\t\t// show any hidden elements after setting opacity to 0\n\t\treturn this.filter( isHidden ).css( \"opacity\", 0 ).show()\n\n\t\t\t// animate to the value specified\n\t\t\t.end().animate({ opacity: to }, speed, easing, callback );\n\t},\n\tanimate: function( prop, speed, easing, callback ) {\n\t\tvar empty = jQuery.isEmptyObject( prop ),\n\t\t\toptall = jQuery.speed( speed, easing, callback ),\n\t\t\tdoAnimation = function() {\n\t\t\t\t// Operate on a copy of prop so per-property easing won't be lost\n\t\t\t\tvar anim = Animation( this, jQuery.extend( {}, prop ), optall );\n\n\t\t\t\t// Empty animations, or finishing resolves immediately\n\t\t\t\tif ( empty || jQuery._data( this, \"finish\" ) ) {\n\t\t\t\t\tanim.stop( true );\n\t\t\t\t}\n\t\t\t};\n\t\t\tdoAnimation.finish = doAnimation;\n\n\t\treturn empty || optall.queue === false ?\n\t\t\tthis.each( doAnimation ) :\n\t\t\tthis.queue( optall.queue, doAnimation );\n\t},\n\tstop: function( type, clearQueue, gotoEnd ) {\n\t\tvar stopQueue = function( hooks ) {\n\t\t\tvar stop = hooks.stop;\n\t\t\tdelete hooks.stop;\n\t\t\tstop( gotoEnd );\n\t\t};\n\n\t\tif ( typeof type !== \"string\" ) {\n\t\t\tgotoEnd = clearQueue;\n\t\t\tclearQueue = type;\n\t\t\ttype = undefined;\n\t\t}\n\t\tif ( clearQueue && type !== false ) {\n\t\t\tthis.queue( type || \"fx\", [] );\n\t\t}\n\n\t\treturn this.each(function() {\n\t\t\tvar dequeue = true,\n\t\t\t\tindex = type != null && type + \"queueHooks\",\n\t\t\t\ttimers = jQuery.timers,\n\t\t\t\tdata = jQuery._data( this );\n\n\t\t\tif ( index ) {\n\t\t\t\tif ( data[ index ] && data[ index ].stop ) {\n\t\t\t\t\tstopQueue( data[ index ] );\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tfor ( index in data ) {\n\t\t\t\t\tif ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {\n\t\t\t\t\t\tstopQueue( data[ index ] );\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor ( index = timers.length; index--; ) {\n\t\t\t\tif ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {\n\t\t\t\t\ttimers[ index ].anim.stop( gotoEnd );\n\t\t\t\t\tdequeue = false;\n\t\t\t\t\ttimers.splice( index, 1 );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// start the next in the queue if the last step wasn't forced\n\t\t\t// timers currently will call their complete callbacks, which will dequeue\n\t\t\t// but only if they were gotoEnd\n\t\t\tif ( dequeue || !gotoEnd ) {\n\t\t\t\tjQuery.dequeue( this, type );\n\t\t\t}\n\t\t});\n\t},\n\tfinish: function( type ) {\n\t\tif ( type !== false ) {\n\t\t\ttype = type || \"fx\";\n\t\t}\n\t\treturn this.each(function() {\n\t\t\tvar index,\n\t\t\t\tdata = jQuery._data( this ),\n\t\t\t\tqueue = data[ type + \"queue\" ],\n\t\t\t\thooks = data[ type + \"queueHooks\" ],\n\t\t\t\ttimers = jQuery.timers,\n\t\t\t\tlength = queue ? queue.length : 0;\n\n\t\t\t// enable finishing flag on private data\n\t\t\tdata.finish = true;\n\n\t\t\t// empty the queue first\n\t\t\tjQuery.queue( this, type, [] );\n\n\t\t\tif ( hooks && hooks.stop ) {\n\t\t\t\thooks.stop.call( this, true );\n\t\t\t}\n\n\t\t\t// look for any active animations, and finish them\n\t\t\tfor ( index = timers.length; index--; ) {\n\t\t\t\tif ( timers[ index ].elem === this && timers[ index ].queue === type ) {\n\t\t\t\t\ttimers[ index ].anim.stop( true );\n\t\t\t\t\ttimers.splice( index, 1 );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// look for any animations in the old queue and finish them\n\t\t\tfor ( index = 0; index < length; index++ ) {\n\t\t\t\tif ( queue[ index ] && queue[ index ].finish ) {\n\t\t\t\t\tqueue[ index ].finish.call( this );\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// turn off finishing flag\n\t\t\tdelete data.finish;\n\t\t});\n\t}\n});\n\n// Generate parameters to create a standard animation\nfunction genFx( type, includeWidth ) {\n\tvar which,\n\t\tattrs = { height: type },\n\t\ti = 0;\n\n\t// if we include width, step value is 1 to do all cssExpand values,\n\t// if we don't include width, step value is 2 to skip over Left and Right\n\tincludeWidth = includeWidth? 1 : 0;\n\tfor( ; i < 4 ; i += 2 - includeWidth ) {\n\t\twhich = cssExpand[ i ];\n\t\tattrs[ \"margin\" + which ] = attrs[ \"padding\" + which ] = type;\n\t}\n\n\tif ( includeWidth ) {\n\t\tattrs.opacity = attrs.width = type;\n\t}\n\n\treturn attrs;\n}\n\n// Generate shortcuts for custom animations\njQuery.each({\n\tslideDown: genFx(\"show\"),\n\tslideUp: genFx(\"hide\"),\n\tslideToggle: genFx(\"toggle\"),\n\tfadeIn: { opacity: \"show\" },\n\tfadeOut: { opacity: \"hide\" },\n\tfadeToggle: { opacity: \"toggle\" }\n}, function( name, props ) {\n\tjQuery.fn[ name ] = function( speed, easing, callback ) {\n\t\treturn this.animate( props, speed, easing, callback );\n\t};\n});\n\njQuery.speed = function( speed, easing, fn ) {\n\tvar opt = speed && typeof speed === \"object\" ? jQuery.extend( {}, speed ) : {\n\t\tcomplete: fn || !fn && easing ||\n\t\t\tjQuery.isFunction( speed ) && speed,\n\t\tduration: speed,\n\t\teasing: fn && easing || easing && !jQuery.isFunction( easing ) && easing\n\t};\n\n\topt.duration = jQuery.fx.off ? 0 : typeof opt.duration === \"number\" ? opt.duration :\n\t\topt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;\n\n\t// normalize opt.queue - true/undefined/null -> \"fx\"\n\tif ( opt.queue == null || opt.queue === true ) {\n\t\topt.queue = \"fx\";\n\t}\n\n\t// Queueing\n\topt.old = opt.complete;\n\n\topt.complete = function() {\n\t\tif ( jQuery.isFunction( opt.old ) ) {\n\t\t\topt.old.call( this );\n\t\t}\n\n\t\tif ( opt.queue ) {\n\t\t\tjQuery.dequeue( this, opt.queue );\n\t\t}\n\t};\n\n\treturn opt;\n};\n\njQuery.easing = {\n\tlinear: function( p ) {\n\t\treturn p;\n\t},\n\tswing: function( p ) {\n\t\treturn 0.5 - Math.cos( p*Math.PI ) / 2;\n\t}\n};\n\njQuery.timers = [];\njQuery.fx = Tween.prototype.init;\njQuery.fx.tick = function() {\n\tvar timer,\n\t\ttimers = jQuery.timers,\n\t\ti = 0;\n\n\tfxNow = jQuery.now();\n\n\tfor ( ; i < timers.length; i++ ) {\n\t\ttimer = timers[ i ];\n\t\t// Checks the timer has not already been removed\n\t\tif ( !timer() && timers[ i ] === timer ) {\n\t\t\ttimers.splice( i--, 1 );\n\t\t}\n\t}\n\n\tif ( !timers.length ) {\n\t\tjQuery.fx.stop();\n\t}\n\tfxNow = undefined;\n};\n\njQuery.fx.timer = function( timer ) {\n\tif ( timer() && jQuery.timers.push( timer ) ) {\n\t\tjQuery.fx.start();\n\t}\n};\n\njQuery.fx.interval = 13;\n\njQuery.fx.start = function() {\n\tif ( !timerId ) {\n\t\ttimerId = setInterval( jQuery.fx.tick, jQuery.fx.interval );\n\t}\n};\n\njQuery.fx.stop = function() {\n\tclearInterval( timerId );\n\ttimerId = null;\n};\n\njQuery.fx.speeds = {\n\tslow: 600,\n\tfast: 200,\n\t// Default speed\n\t_default: 400\n};\n\n// Back Compat <1.8 extension point\njQuery.fx.step = {};\n\nif ( jQuery.expr && jQuery.expr.filters ) {\n\tjQuery.expr.filters.animated = function( elem ) {\n\t\treturn jQuery.grep(jQuery.timers, function( fn ) {\n\t\t\treturn elem === fn.elem;\n\t\t}).length;\n\t};\n}\njQuery.fn.offset = function( options ) {\n\tif ( arguments.length ) {\n\t\treturn options === undefined ?\n\t\t\tthis :\n\t\t\tthis.each(function( i ) {\n\t\t\t\tjQuery.offset.setOffset( this, options, i );\n\t\t\t});\n\t}\n\n\tvar docElem, win,\n\t\tbox = { top: 0, left: 0 },\n\t\telem = this[ 0 ],\n\t\tdoc = elem && elem.ownerDocument;\n\n\tif ( !doc ) {\n\t\treturn;\n\t}\n\n\tdocElem = doc.documentElement;\n\n\t// Make sure it's not a disconnected DOM node\n\tif ( !jQuery.contains( docElem, elem ) ) {\n\t\treturn box;\n\t}\n\n\t// If we don't have gBCR, just use 0,0 rather than error\n\t// BlackBerry 5, iOS 3 (original iPhone)\n\tif ( typeof elem.getBoundingClientRect !== core_strundefined ) {\n\t\tbox = elem.getBoundingClientRect();\n\t}\n\twin = getWindow( doc );\n\treturn {\n\t\ttop: box.top  + ( win.pageYOffset || docElem.scrollTop )  - ( docElem.clientTop  || 0 ),\n\t\tleft: box.left + ( win.pageXOffset || docElem.scrollLeft ) - ( docElem.clientLeft || 0 )\n\t};\n};\n\njQuery.offset = {\n\n\tsetOffset: function( elem, options, i ) {\n\t\tvar position = jQuery.css( elem, \"position\" );\n\n\t\t// set position first, in-case top/left are set even on static elem\n\t\tif ( position === \"static\" ) {\n\t\t\telem.style.position = \"relative\";\n\t\t}\n\n\t\tvar curElem = jQuery( elem ),\n\t\t\tcurOffset = curElem.offset(),\n\t\t\tcurCSSTop = jQuery.css( elem, \"top\" ),\n\t\t\tcurCSSLeft = jQuery.css( elem, \"left\" ),\n\t\t\tcalculatePosition = ( position === \"absolute\" || position === \"fixed\" ) && jQuery.inArray(\"auto\", [curCSSTop, curCSSLeft]) > -1,\n\t\t\tprops = {}, curPosition = {}, curTop, curLeft;\n\n\t\t// need to be able to calculate position if either top or left is auto and position is either absolute or fixed\n\t\tif ( calculatePosition ) {\n\t\t\tcurPosition = curElem.position();\n\t\t\tcurTop = curPosition.top;\n\t\t\tcurLeft = curPosition.left;\n\t\t} else {\n\t\t\tcurTop = parseFloat( curCSSTop ) || 0;\n\t\t\tcurLeft = parseFloat( curCSSLeft ) || 0;\n\t\t}\n\n\t\tif ( jQuery.isFunction( options ) ) {\n\t\t\toptions = options.call( elem, i, curOffset );\n\t\t}\n\n\t\tif ( options.top != null ) {\n\t\t\tprops.top = ( options.top - curOffset.top ) + curTop;\n\t\t}\n\t\tif ( options.left != null ) {\n\t\t\tprops.left = ( options.left - curOffset.left ) + curLeft;\n\t\t}\n\n\t\tif ( \"using\" in options ) {\n\t\t\toptions.using.call( elem, props );\n\t\t} else {\n\t\t\tcurElem.css( props );\n\t\t}\n\t}\n};\n\n\njQuery.fn.extend({\n\n\tposition: function() {\n\t\tif ( !this[ 0 ] ) {\n\t\t\treturn;\n\t\t}\n\n\t\tvar offsetParent, offset,\n\t\t\tparentOffset = { top: 0, left: 0 },\n\t\t\telem = this[ 0 ];\n\n\t\t// fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is it's only offset parent\n\t\tif ( jQuery.css( elem, \"position\" ) === \"fixed\" ) {\n\t\t\t// we assume that getBoundingClientRect is available when computed position is fixed\n\t\t\toffset = elem.getBoundingClientRect();\n\t\t} else {\n\t\t\t// Get *real* offsetParent\n\t\t\toffsetParent = this.offsetParent();\n\n\t\t\t// Get correct offsets\n\t\t\toffset = this.offset();\n\t\t\tif ( !jQuery.nodeName( offsetParent[ 0 ], \"html\" ) ) {\n\t\t\t\tparentOffset = offsetParent.offset();\n\t\t\t}\n\n\t\t\t// Add offsetParent borders\n\t\t\tparentOffset.top  += jQuery.css( offsetParent[ 0 ], \"borderTopWidth\", true );\n\t\t\tparentOffset.left += jQuery.css( offsetParent[ 0 ], \"borderLeftWidth\", true );\n\t\t}\n\n\t\t// Subtract parent offsets and element margins\n\t\t// note: when an element has margin: auto the offsetLeft and marginLeft\n\t\t// are the same in Safari causing offset.left to incorrectly be 0\n\t\treturn {\n\t\t\ttop:  offset.top  - parentOffset.top - jQuery.css( elem, \"marginTop\", true ),\n\t\t\tleft: offset.left - parentOffset.left - jQuery.css( elem, \"marginLeft\", true)\n\t\t};\n\t},\n\n\toffsetParent: function() {\n\t\treturn this.map(function() {\n\t\t\tvar offsetParent = this.offsetParent || docElem;\n\t\t\twhile ( offsetParent && ( !jQuery.nodeName( offsetParent, \"html\" ) && jQuery.css( offsetParent, \"position\") === \"static\" ) ) {\n\t\t\t\toffsetParent = offsetParent.offsetParent;\n\t\t\t}\n\t\t\treturn offsetParent || docElem;\n\t\t});\n\t}\n});\n\n\n// Create scrollLeft and scrollTop methods\njQuery.each( {scrollLeft: \"pageXOffset\", scrollTop: \"pageYOffset\"}, function( method, prop ) {\n\tvar top = /Y/.test( prop );\n\n\tjQuery.fn[ method ] = function( val ) {\n\t\treturn jQuery.access( this, function( elem, method, val ) {\n\t\t\tvar win = getWindow( elem );\n\n\t\t\tif ( val === undefined ) {\n\t\t\t\treturn win ? (prop in win) ? win[ prop ] :\n\t\t\t\t\twin.document.documentElement[ method ] :\n\t\t\t\t\telem[ method ];\n\t\t\t}\n\n\t\t\tif ( win ) {\n\t\t\t\twin.scrollTo(\n\t\t\t\t\t!top ? val : jQuery( win ).scrollLeft(),\n\t\t\t\t\ttop ? val : jQuery( win ).scrollTop()\n\t\t\t\t);\n\n\t\t\t} else {\n\t\t\t\telem[ method ] = val;\n\t\t\t}\n\t\t}, method, val, arguments.length, null );\n\t};\n});\n\nfunction getWindow( elem ) {\n\treturn jQuery.isWindow( elem ) ?\n\t\telem :\n\t\telem.nodeType === 9 ?\n\t\t\telem.defaultView || elem.parentWindow :\n\t\t\tfalse;\n}\n// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods\njQuery.each( { Height: \"height\", Width: \"width\" }, function( name, type ) {\n\tjQuery.each( { padding: \"inner\" + name, content: type, \"\": \"outer\" + name }, function( defaultExtra, funcName ) {\n\t\t// margin is only for outerHeight, outerWidth\n\t\tjQuery.fn[ funcName ] = function( margin, value ) {\n\t\t\tvar chainable = arguments.length && ( defaultExtra || typeof margin !== \"boolean\" ),\n\t\t\t\textra = defaultExtra || ( margin === true || value === true ? \"margin\" : \"border\" );\n\n\t\t\treturn jQuery.access( this, function( elem, type, value ) {\n\t\t\t\tvar doc;\n\n\t\t\t\tif ( jQuery.isWindow( elem ) ) {\n\t\t\t\t\t// As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there\n\t\t\t\t\t// isn't a whole lot we can do. See pull request at this URL for discussion:\n\t\t\t\t\t// https://github.com/jquery/jquery/pull/764\n\t\t\t\t\treturn elem.document.documentElement[ \"client\" + name ];\n\t\t\t\t}\n\n\t\t\t\t// Get document width or height\n\t\t\t\tif ( elem.nodeType === 9 ) {\n\t\t\t\t\tdoc = elem.documentElement;\n\n\t\t\t\t\t// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height], whichever is greatest\n\t\t\t\t\t// unfortunately, this causes bug #3838 in IE6/8 only, but there is currently no good, small way to fix it.\n\t\t\t\t\treturn Math.max(\n\t\t\t\t\t\telem.body[ \"scroll\" + name ], doc[ \"scroll\" + name ],\n\t\t\t\t\t\telem.body[ \"offset\" + name ], doc[ \"offset\" + name ],\n\t\t\t\t\t\tdoc[ \"client\" + name ]\n\t\t\t\t\t);\n\t\t\t\t}\n\n\t\t\t\treturn value === undefined ?\n\t\t\t\t\t// Get width or height on the element, requesting but not forcing parseFloat\n\t\t\t\t\tjQuery.css( elem, type, extra ) :\n\n\t\t\t\t\t// Set width or height on the element\n\t\t\t\t\tjQuery.style( elem, type, value, extra );\n\t\t\t}, type, chainable ? margin : undefined, chainable, null );\n\t\t};\n\t});\n});\n// Limit scope pollution from any deprecated API\n// (function() {\n\n// The number of elements contained in the matched element set\njQuery.fn.size = function() {\n\treturn this.length;\n};\n\njQuery.fn.andSelf = jQuery.fn.addBack;\n\n// })();\nif ( typeof module === \"object\" && module && typeof module.exports === \"object\" ) {\n\t// Expose jQuery as module.exports in loaders that implement the Node\n\t// module pattern (including browserify). Do not create the global, since\n\t// the user will be storing it themselves locally, and globals are frowned\n\t// upon in the Node module world.\n\tmodule.exports = jQuery;\n} else {\n\t// Otherwise expose jQuery to the global object as usual\n\twindow.jQuery = window.$ = jQuery;\n\n\t// Register as a named AMD module, since jQuery can be concatenated with other\n\t// files that may use define, but not via a proper concatenation script that\n\t// understands anonymous AMD modules. A named AMD is safest and most robust\n\t// way to register. Lowercase jquery is used because AMD module names are\n\t// derived from file names, and jQuery is normally delivered in a lowercase\n\t// file name. Do this after creating the global so that if an AMD module wants\n\t// to call noConflict to hide this version of jQuery, it will work.\n\tif ( typeof define === \"function\" && define.amd ) {\n\t\tdefine( \"jquery\", [], function () { return jQuery; } );\n\t}\n}\n\n})( window );\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/video-js/video-js.css",
    "content": "/*!\nVideo.js Default Styles (http://videojs.com)\nVersion 4.3.0\nCreate your own skin at http://designer.videojs.com\n*/\n/* SKIN\n================================================================================\nThe main class name for all skin-specific styles. To make your own skin,\nreplace all occurances of 'vjs-default-skin' with a new name. Then add your new\nskin name to your video tag instead of the default skin.\ne.g. <video class=\"video-js my-skin-name\">\n*/\n.vjs-default-skin {\n  color: #cccccc;\n}\n/* Custom Icon Font\n--------------------------------------------------------------------------------\nThe control icons are from a custom font. Each icon corresponds to a character\n(e.g. \"\\e001\"). Font icons allow for easy scaling and coloring of icons.\n*/\n@font-face {\n  font-family: 'VideoJS';\n  src: url('font/vjs.eot');\n  src: url('font/vjs.eot?#iefix') format('embedded-opentype'), url('font/vjs.woff') format('woff'), url('font/vjs.ttf') format('truetype');\n  font-weight: normal;\n  font-style: normal;\n}\n/* Base UI Component Classes\n--------------------------------------------------------------------------------\n*/\n/* Slider - used for Volume bar and Seek bar */\n.vjs-default-skin .vjs-slider {\n  /* Replace browser focus hightlight with handle highlight */\n  outline: 0;\n  position: relative;\n  cursor: pointer;\n  padding: 0;\n  /* background-color-with-alpha */\n  background-color: #333333;\n  background-color: rgba(51, 51, 51, 0.9);\n}\n.vjs-default-skin .vjs-slider:focus {\n  /* box-shadow */\n  -webkit-box-shadow: 0 0 2em #ffffff;\n  -moz-box-shadow: 0 0 2em #ffffff;\n  box-shadow: 0 0 2em #ffffff;\n}\n.vjs-default-skin .vjs-slider-handle {\n  position: absolute;\n  /* Needed for IE6 */\n  left: 0;\n  top: 0;\n}\n.vjs-default-skin .vjs-slider-handle:before {\n  content: \"\\e009\";\n  font-family: VideoJS;\n  font-size: 1em;\n  line-height: 1;\n  text-align: center;\n  text-shadow: 0em 0em 1em #fff;\n  position: absolute;\n  top: 0;\n  left: 0;\n  /* Rotate the square icon to make a diamond */\n  /* transform */\n  -webkit-transform: rotate(-45deg);\n  -moz-transform: rotate(-45deg);\n  -ms-transform: rotate(-45deg);\n  -o-transform: rotate(-45deg);\n  transform: rotate(-45deg);\n}\n/* Control Bar\n--------------------------------------------------------------------------------\nThe default control bar that is a container for most of the controls.\n*/\n.vjs-default-skin .vjs-control-bar {\n  /* Start hidden */\n  display: none;\n  position: absolute;\n  /* Place control bar at the bottom of the player box/video.\n     If you want more margin below the control bar, add more height. */\n  bottom: 0;\n  /* Use left/right to stretch to 100% width of player div */\n  left: 0;\n  right: 0;\n  /* Height includes any margin you want above or below control items */\n  height: 3.0em;\n  /* background-color-with-alpha */\n  background-color: #07141e;\n  background-color: rgba(7, 20, 30, 0.7);\n}\n/* Show the control bar only once the video has started playing */\n.vjs-default-skin.vjs-has-started .vjs-control-bar {\n  display: block;\n  /* Visibility needed to make sure things hide in older browsers too. */\n\n  visibility: visible;\n  opacity: 1;\n  /* transition */\n  -webkit-transition: visibility 0.1s, opacity 0.1s;\n  -moz-transition: visibility 0.1s, opacity 0.1s;\n  -o-transition: visibility 0.1s, opacity 0.1s;\n  transition: visibility 0.1s, opacity 0.1s;\n}\n/* Hide the control bar when the video is playing and the user is inactive  */\n.vjs-default-skin.vjs-has-started.vjs-user-inactive.vjs-playing .vjs-control-bar {\n  display: block;\n  visibility: hidden;\n  opacity: 0;\n  /* transition */\n  -webkit-transition: visibility 1s, opacity 1s;\n  -moz-transition: visibility 1s, opacity 1s;\n  -o-transition: visibility 1s, opacity 1s;\n  transition: visibility 1s, opacity 1s;\n}\n.vjs-default-skin.vjs-controls-disabled .vjs-control-bar {\n  display: none;\n}\n.vjs-default-skin.vjs-using-native-controls .vjs-control-bar {\n  display: none;\n}\n/* IE8 is flakey with fonts, and you have to change the actual content to force\nfonts to show/hide properly.\n  - \"\\9\" IE8 hack didn't work for this\n  - Found in XP IE8 from http://modern.ie. Does not show up in \"IE8 mode\" in IE9\n*/\n@media \\0screen {\n  .vjs-default-skin.vjs-user-inactive.vjs-playing .vjs-control-bar :before {\n    content: \"\";\n  }\n}\n/* General styles for individual controls. */\n.vjs-default-skin .vjs-control {\n  outline: none;\n  position: relative;\n  float: left;\n  text-align: center;\n  margin: 0;\n  padding: 0;\n  height: 3.0em;\n  width: 4em;\n}\n/* FontAwsome button icons */\n.vjs-default-skin .vjs-control:before {\n  font-family: VideoJS;\n  font-size: 1.5em;\n  line-height: 2;\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  text-align: center;\n  text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.5);\n}\n/* Replacement for focus outline */\n.vjs-default-skin .vjs-control:focus:before,\n.vjs-default-skin .vjs-control:hover:before {\n  text-shadow: 0em 0em 1em #ffffff;\n}\n.vjs-default-skin .vjs-control:focus {\n  /*  outline: 0; */\n  /* keyboard-only users cannot see the focus on several of the UI elements when\n  this is set to 0 */\n\n}\n/* Hide control text visually, but have it available for screenreaders */\n.vjs-default-skin .vjs-control-text {\n  /* hide-visually */\n  border: 0;\n  clip: rect(0 0 0 0);\n  height: 1px;\n  margin: -1px;\n  overflow: hidden;\n  padding: 0;\n  position: absolute;\n  width: 1px;\n}\n/* Play/Pause\n--------------------------------------------------------------------------------\n*/\n.vjs-default-skin .vjs-play-control {\n  width: 5em;\n  cursor: pointer;\n}\n.vjs-default-skin .vjs-play-control:before {\n  content: \"\\e001\";\n}\n.vjs-default-skin.vjs-playing .vjs-play-control:before {\n  content: \"\\e002\";\n}\n/* Volume/Mute\n-------------------------------------------------------------------------------- */\n.vjs-default-skin .vjs-mute-control,\n.vjs-default-skin .vjs-volume-menu-button {\n  cursor: pointer;\n  float: right;\n}\n.vjs-default-skin .vjs-mute-control:before,\n.vjs-default-skin .vjs-volume-menu-button:before {\n  content: \"\\e006\";\n}\n.vjs-default-skin .vjs-mute-control.vjs-vol-0:before,\n.vjs-default-skin .vjs-volume-menu-button.vjs-vol-0:before {\n  content: \"\\e003\";\n}\n.vjs-default-skin .vjs-mute-control.vjs-vol-1:before,\n.vjs-default-skin .vjs-volume-menu-button.vjs-vol-1:before {\n  content: \"\\e004\";\n}\n.vjs-default-skin .vjs-mute-control.vjs-vol-2:before,\n.vjs-default-skin .vjs-volume-menu-button.vjs-vol-2:before {\n  content: \"\\e005\";\n}\n.vjs-default-skin .vjs-volume-control {\n  width: 5em;\n  float: right;\n}\n.vjs-default-skin .vjs-volume-bar {\n  width: 5em;\n  height: 0.6em;\n  margin: 1.1em auto 0;\n}\n.vjs-default-skin .vjs-volume-menu-button .vjs-menu-content {\n  height: 2.9em;\n}\n.vjs-default-skin .vjs-volume-level {\n  position: absolute;\n  top: 0;\n  left: 0;\n  height: 0.5em;\n  background: #66a8cc url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAP0lEQVQIHWWMAQoAIAgDR/QJ/Ub//04+w7ZICBwcOg5FZi5iBB82AGzixEglJrd4TVK5XUJpskSTEvpdFzX9AB2pGziSQcvAAAAAAElFTkSuQmCC) -50% 0 repeat;\n}\n.vjs-default-skin .vjs-volume-bar .vjs-volume-handle {\n  width: 0.5em;\n  height: 0.5em;\n}\n.vjs-default-skin .vjs-volume-handle:before {\n  font-size: 0.9em;\n  top: -0.2em;\n  left: -0.2em;\n  width: 1em;\n  height: 1em;\n}\n.vjs-default-skin .vjs-volume-menu-button .vjs-menu .vjs-menu-content {\n  width: 6em;\n  left: -4em;\n}\n/* Progress\n--------------------------------------------------------------------------------\n*/\n.vjs-default-skin .vjs-progress-control {\n  position: absolute;\n  left: 0;\n  right: 0;\n  width: auto;\n  font-size: 0.3em;\n  height: 1em;\n  /* Set above the rest of the controls. */\n  top: -1em;\n  /* Shrink the bar slower than it grows. */\n  /* transition */\n  -webkit-transition: all 0.4s;\n  -moz-transition: all 0.4s;\n  -o-transition: all 0.4s;\n  transition: all 0.4s;\n}\n/* On hover, make the progress bar grow to something that's more clickable.\n    This simply changes the overall font for the progress bar, and this\n    updates both the em-based widths and heights, as wells as the icon font */\n.vjs-default-skin:hover .vjs-progress-control {\n  font-size: .9em;\n  /* Even though we're not changing the top/height, we need to include them in\n      the transition so they're handled correctly. */\n\n  /* transition */\n  -webkit-transition: all 0.2s;\n  -moz-transition: all 0.2s;\n  -o-transition: all 0.2s;\n  transition: all 0.2s;\n}\n/* Box containing play and load progresses. Also acts as seek scrubber. */\n.vjs-default-skin .vjs-progress-holder {\n  height: 100%;\n}\n/* Progress Bars */\n.vjs-default-skin .vjs-progress-holder .vjs-play-progress,\n.vjs-default-skin .vjs-progress-holder .vjs-load-progress {\n  position: absolute;\n  display: block;\n  height: 100%;\n  margin: 0;\n  padding: 0;\n  /* Needed for IE6 */\n  left: 0;\n  top: 0;\n}\n.vjs-default-skin .vjs-play-progress {\n  /*\n    Using a data URI to create the white diagonal lines with a transparent\n      background. Surprisingly works in IE8.\n      Created using http://www.patternify.com\n    Changing the first color value will change the bar color.\n    Also using a paralax effect to make the lines move backwards.\n      The -50% left position makes that happen.\n  */\n\n  background: #66a8cc url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAYAAADgzO9IAAAAP0lEQVQIHWWMAQoAIAgDR/QJ/Ub//04+w7ZICBwcOg5FZi5iBB82AGzixEglJrd4TVK5XUJpskSTEvpdFzX9AB2pGziSQcvAAAAAAElFTkSuQmCC) -50% 0 repeat;\n}\n.vjs-default-skin .vjs-load-progress {\n  background: #646464 /* IE8- Fallback */;\n  background: rgba(255, 255, 255, 0.4);\n}\n.vjs-default-skin .vjs-seek-handle {\n  width: 1.5em;\n  height: 100%;\n}\n.vjs-default-skin .vjs-seek-handle:before {\n  padding-top: 0.1em /* Minor adjustment */;\n}\n/* Time Display\n--------------------------------------------------------------------------------\n*/\n.vjs-default-skin .vjs-time-controls {\n  font-size: 1em;\n  /* Align vertically by making the line height the same as the control bar */\n  line-height: 3em;\n}\n.vjs-default-skin .vjs-current-time {\n  float: left;\n}\n.vjs-default-skin .vjs-duration {\n  float: left;\n}\n/* Remaining time is in the HTML, but not included in default design */\n.vjs-default-skin .vjs-remaining-time {\n  display: none;\n  float: left;\n}\n.vjs-time-divider {\n  float: left;\n  line-height: 3em;\n}\n/* Fullscreen\n--------------------------------------------------------------------------------\n*/\n.vjs-default-skin .vjs-fullscreen-control {\n  width: 3.8em;\n  cursor: pointer;\n  float: right;\n}\n.vjs-default-skin .vjs-fullscreen-control:before {\n  content: \"\\e000\";\n}\n/* Switch to the exit icon when the player is in fullscreen */\n.vjs-default-skin.vjs-fullscreen .vjs-fullscreen-control:before {\n  content: \"\\e00b\";\n}\n/* Big Play Button (play button at start)\n--------------------------------------------------------------------------------\nPositioning of the play button in the center or other corners can be done more\neasily in the skin designer. http://designer.videojs.com/\n*/\n.vjs-default-skin .vjs-big-play-button {\n  left: 0.5em;\n  top: 0.5em;\n  font-size: 3em;\n  display: block;\n  z-index: 2;\n  position: absolute;\n  width: 4em;\n  height: 2.6em;\n  text-align: center;\n  vertical-align: middle;\n  cursor: pointer;\n  opacity: 1;\n  /* Need a slightly gray bg so it can be seen on black backgrounds */\n  /* background-color-with-alpha */\n  background-color: #07141e;\n  background-color: rgba(7, 20, 30, 0.7);\n  border: 0.1em solid #3b4249;\n  /* border-radius */\n  -webkit-border-radius: 0.8em;\n  -moz-border-radius: 0.8em;\n  border-radius: 0.8em;\n  /* box-shadow */\n  -webkit-box-shadow: 0px 0px 1em rgba(255, 255, 255, 0.25);\n  -moz-box-shadow: 0px 0px 1em rgba(255, 255, 255, 0.25);\n  box-shadow: 0px 0px 1em rgba(255, 255, 255, 0.25);\n  /* transition */\n  -webkit-transition: all 0.4s;\n  -moz-transition: all 0.4s;\n  -o-transition: all 0.4s;\n  transition: all 0.4s;\n}\n/* Optionally center */\n.vjs-default-skin.vjs-big-play-centered .vjs-big-play-button {\n  /* Center it horizontally */\n  left: 50%;\n  margin-left: -2.1em;\n  /* Center it vertically */\n  top: 50%;\n  margin-top: -1.4000000000000001em;\n}\n/* Hide if controls are disabled */\n.vjs-default-skin.vjs-controls-disabled .vjs-big-play-button {\n  display: none;\n}\n/* Hide when video starts playing */\n.vjs-default-skin.vjs-has-started .vjs-big-play-button {\n  display: none;\n}\n/* Hide on mobile devices. Remove when we stop using native controls\n    by default on mobile  */\n.vjs-default-skin.vjs-using-native-controls .vjs-big-play-button {\n  display: none;\n}\n.vjs-default-skin:hover .vjs-big-play-button,\n.vjs-default-skin .vjs-big-play-button:focus {\n  outline: 0;\n  border-color: #fff;\n  /* IE8 needs a non-glow hover state */\n  background-color: #505050;\n  background-color: rgba(50, 50, 50, 0.75);\n  /* box-shadow */\n  -webkit-box-shadow: 0 0 3em #ffffff;\n  -moz-box-shadow: 0 0 3em #ffffff;\n  box-shadow: 0 0 3em #ffffff;\n  /* transition */\n  -webkit-transition: all 0s;\n  -moz-transition: all 0s;\n  -o-transition: all 0s;\n  transition: all 0s;\n}\n.vjs-default-skin .vjs-big-play-button:before {\n  content: \"\\e001\";\n  font-family: VideoJS;\n  /* In order to center the play icon vertically we need to set the line height\n     to the same as the button height */\n\n  line-height: 2.6em;\n  text-shadow: 0.05em 0.05em 0.1em #000;\n  text-align: center /* Needed for IE8 */;\n  position: absolute;\n  left: 0;\n  width: 100%;\n  height: 100%;\n}\n/* Loading Spinner\n--------------------------------------------------------------------------------\n*/\n.vjs-loading-spinner {\n  display: none;\n  position: absolute;\n  top: 50%;\n  left: 50%;\n  font-size: 4em;\n  line-height: 1;\n  width: 1em;\n  height: 1em;\n  margin-left: -0.5em;\n  margin-top: -0.5em;\n  opacity: 0.75;\n  /* animation */\n  -webkit-animation: spin 1.5s infinite linear;\n  -moz-animation: spin 1.5s infinite linear;\n  -o-animation: spin 1.5s infinite linear;\n  animation: spin 1.5s infinite linear;\n}\n.vjs-default-skin .vjs-loading-spinner:before {\n  content: \"\\e01e\";\n  font-family: VideoJS;\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 1em;\n  height: 1em;\n  text-align: center;\n  text-shadow: 0em 0em 0.1em #000;\n}\n@-moz-keyframes spin {\n  0% {\n    -moz-transform: rotate(0deg);\n  }\n  100% {\n    -moz-transform: rotate(359deg);\n  }\n}\n@-webkit-keyframes spin {\n  0% {\n    -webkit-transform: rotate(0deg);\n  }\n  100% {\n    -webkit-transform: rotate(359deg);\n  }\n}\n@-o-keyframes spin {\n  0% {\n    -o-transform: rotate(0deg);\n  }\n  100% {\n    -o-transform: rotate(359deg);\n  }\n}\n@keyframes spin {\n  0% {\n    transform: rotate(0deg);\n  }\n  100% {\n    transform: rotate(359deg);\n  }\n}\n/* Menu Buttons (Captions/Subtitles/etc.)\n--------------------------------------------------------------------------------\n*/\n.vjs-default-skin .vjs-menu-button {\n  float: right;\n  cursor: pointer;\n}\n.vjs-default-skin .vjs-menu {\n  display: none;\n  position: absolute;\n  bottom: 0;\n  left: 0em;\n  /* (Width of vjs-menu - width of button) / 2 */\n\n  width: 0em;\n  height: 0em;\n  margin-bottom: 3em;\n  border-left: 2em solid transparent;\n  border-right: 2em solid transparent;\n  border-top: 1.55em solid #000000;\n  /* Same width top as ul bottom */\n\n  border-top-color: rgba(7, 40, 50, 0.5);\n  /* Same as ul background */\n\n}\n/* Button Pop-up Menu */\n.vjs-default-skin .vjs-menu-button .vjs-menu .vjs-menu-content {\n  display: block;\n  padding: 0;\n  margin: 0;\n  position: absolute;\n  width: 10em;\n  bottom: 1.5em;\n  /* Same bottom as vjs-menu border-top */\n\n  max-height: 15em;\n  overflow: auto;\n  left: -5em;\n  /* Width of menu - width of button / 2 */\n\n  /* background-color-with-alpha */\n  background-color: #07141e;\n  background-color: rgba(7, 20, 30, 0.7);\n  /* box-shadow */\n  -webkit-box-shadow: -0.2em -0.2em 0.3em rgba(255, 255, 255, 0.2);\n  -moz-box-shadow: -0.2em -0.2em 0.3em rgba(255, 255, 255, 0.2);\n  box-shadow: -0.2em -0.2em 0.3em rgba(255, 255, 255, 0.2);\n}\n.vjs-default-skin .vjs-menu-button:hover .vjs-menu {\n  display: block;\n}\n.vjs-default-skin .vjs-menu-button ul li {\n  list-style: none;\n  margin: 0;\n  padding: 0.3em 0 0.3em 0;\n  line-height: 1.4em;\n  font-size: 1.2em;\n  text-align: center;\n  text-transform: lowercase;\n}\n.vjs-default-skin .vjs-menu-button ul li.vjs-selected {\n  background-color: #000;\n}\n.vjs-default-skin .vjs-menu-button ul li:focus,\n.vjs-default-skin .vjs-menu-button ul li:hover,\n.vjs-default-skin .vjs-menu-button ul li.vjs-selected:focus,\n.vjs-default-skin .vjs-menu-button ul li.vjs-selected:hover {\n  outline: 0;\n  color: #111;\n  /* background-color-with-alpha */\n  background-color: #ffffff;\n  background-color: rgba(255, 255, 255, 0.75);\n  /* box-shadow */\n  -webkit-box-shadow: 0 0 1em #ffffff;\n  -moz-box-shadow: 0 0 1em #ffffff;\n  box-shadow: 0 0 1em #ffffff;\n}\n.vjs-default-skin .vjs-menu-button ul li.vjs-menu-title {\n  text-align: center;\n  text-transform: uppercase;\n  font-size: 1em;\n  line-height: 2em;\n  padding: 0;\n  margin: 0 0 0.3em 0;\n  font-weight: bold;\n  cursor: default;\n}\n/* Subtitles Button */\n.vjs-default-skin .vjs-subtitles-button:before {\n  content: \"\\e00c\";\n}\n/* Captions Button */\n.vjs-default-skin .vjs-captions-button:before {\n  content: \"\\e008\";\n}\n/* Replacement for focus outline */\n.vjs-default-skin .vjs-captions-button:focus .vjs-control-content:before,\n.vjs-default-skin .vjs-captions-button:hover .vjs-control-content:before {\n  /* box-shadow */\n  -webkit-box-shadow: 0 0 1em #ffffff;\n  -moz-box-shadow: 0 0 1em #ffffff;\n  box-shadow: 0 0 1em #ffffff;\n}\n/*\nREQUIRED STYLES (be careful overriding)\n================================================================================\nWhen loading the player, the video tag is replaced with a DIV,\nthat will hold the video tag or object tag for other playback methods.\nThe div contains the video playback element (Flash or HTML5) and controls,\nand sets the width and height of the video.\n\n** If you want to add some kind of border/padding (e.g. a frame), or special\npositioning, use another containing element. Otherwise you risk messing up\ncontrol positioning and full window mode. **\n*/\n.video-js {\n  background-color: #000;\n  position: relative;\n  padding: 0;\n  /* Start with 10px for base font size so other dimensions can be em based and\n     easily calculable. */\n\n  font-size: 10px;\n  /* Allow poster to be vertially aligned. */\n\n  vertical-align: middle;\n  /*  display: table-cell; */\n  /*This works in Safari but not Firefox.*/\n\n  /* Provide some basic defaults for fonts */\n\n  font-weight: normal;\n  font-style: normal;\n  /* Avoiding helvetica: issue #376 */\n\n  font-family: Arial, sans-serif;\n  /* Turn off user selection (text highlighting) by default.\n     The majority of player components will not be text blocks.\n     Text areas will need to turn user selection back on. */\n\n  /* user-select */\n  -webkit-user-select: none;\n  -moz-user-select: none;\n  -ms-user-select: none;\n  user-select: none;\n}\n/* Playback technology elements expand to the width/height of the containing div\n    <video> or <object> */\n.video-js .vjs-tech {\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n}\n/* Fix for Firefox 9 fullscreen (only if it is enabled). Not needed when\n   checking fullScreenEnabled. */\n.video-js:-moz-full-screen {\n  position: absolute;\n}\n/* Fullscreen Styles */\nbody.vjs-full-window {\n  padding: 0;\n  margin: 0;\n  height: 100%;\n  /* Fix for IE6 full-window. http://www.cssplay.co.uk/layouts/fixed.html */\n  overflow-y: auto;\n}\n.video-js.vjs-fullscreen {\n  position: fixed;\n  overflow: hidden;\n  z-index: 1000;\n  left: 0;\n  top: 0;\n  bottom: 0;\n  right: 0;\n  width: 100% !important;\n  height: 100% !important;\n  /* IE6 full-window (underscore hack) */\n  _position: absolute;\n}\n.video-js:-webkit-full-screen {\n  width: 100% !important;\n  height: 100% !important;\n}\n.video-js.vjs-fullscreen.vjs-user-inactive {\n  cursor: none;\n}\n/* Poster Styles */\n.vjs-poster {\n  background-repeat: no-repeat;\n  background-position: 50% 50%;\n  background-size: contain;\n  cursor: pointer;\n  height: 100%;\n  margin: 0;\n  padding: 0;\n  position: relative;\n  width: 100%;\n}\n.vjs-poster img {\n  display: block;\n  margin: 0 auto;\n  max-height: 100%;\n  padding: 0;\n  width: 100%;\n}\n/* Hide the poster when native controls are used otherwise it covers them */\n.video-js.vjs-using-native-controls .vjs-poster {\n  display: none;\n}\n/* Text Track Styles */\n/* Overall track holder for both captions and subtitles */\n.video-js .vjs-text-track-display {\n  text-align: center;\n  position: absolute;\n  bottom: 4em;\n  /* Leave padding on left and right */\n  left: 1em;\n  right: 1em;\n}\n/* Individual tracks */\n.video-js .vjs-text-track {\n  display: none;\n  font-size: 1.4em;\n  text-align: center;\n  margin-bottom: 0.1em;\n  /* Transparent black background, or fallback to all black (oldIE) */\n  /* background-color-with-alpha */\n  background-color: #000000;\n  background-color: rgba(0, 0, 0, 0.5);\n}\n.video-js .vjs-subtitles {\n  color: #ffffff /* Subtitles are white */;\n}\n.video-js .vjs-captions {\n  color: #ffcc66 /* Captions are yellow */;\n}\n.vjs-tt-cue {\n  display: block;\n}\n/* Hide disabled or unsupported controls */\n.vjs-default-skin .vjs-hidden {\n  display: none;\n}\n.vjs-lock-showing {\n  display: block !important;\n  opacity: 1;\n  visibility: visible;\n}\n/* -----------------------------------------------------------------------------\nThe original source of this file lives at\nhttps://github.com/videojs/video.js/blob/master/src/css/video-js.less */\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/video-js/video.dev.js",
    "content": "/**\n * @fileoverview Main function src.\n */\n\n// HTML5 Shiv. Must be in <head> to support older browsers.\ndocument.createElement('video');\ndocument.createElement('audio');\ndocument.createElement('track');\n\n/**\n * Doubles as the main function for users to create a player instance and also\n * the main library object.\n *\n * **ALIASES** videojs, _V_ (deprecated)\n *\n * The `vjs` function can be used to initialize or retrieve a player.\n *\n *     var myPlayer = vjs('my_video_id');\n *\n * @param  {String|Element} id      Video element or video element ID\n * @param  {Object=} options        Optional options object for config/settings\n * @param  {Function=} ready        Optional ready callback\n * @return {vjs.Player}             A player instance\n * @namespace\n */\nvar vjs = function(id, options, ready){\n  var tag; // Element of ID\n\n  // Allow for element or ID to be passed in\n  // String ID\n  if (typeof id === 'string') {\n\n    // Adjust for jQuery ID syntax\n    if (id.indexOf('#') === 0) {\n      id = id.slice(1);\n    }\n\n    // If a player instance has already been created for this ID return it.\n    if (vjs.players[id]) {\n      return vjs.players[id];\n\n    // Otherwise get element for ID\n    } else {\n      tag = vjs.el(id);\n    }\n\n  // ID is a media element\n  } else {\n    tag = id;\n  }\n\n  // Check for a useable element\n  if (!tag || !tag.nodeName) { // re: nodeName, could be a box div also\n    throw new TypeError('The element or ID supplied is not valid. (videojs)'); // Returns\n  }\n\n  // Element may have a player attr referring to an already created player instance.\n  // If not, set up a new player and return the instance.\n  return tag['player'] || new vjs.Player(tag, options, ready);\n};\n\n// Extended name, also available externally, window.videojs\nvar videojs = vjs;\nwindow.videojs = window.vjs = vjs;\n\n// CDN Version. Used to target right flash swf.\nvjs.CDN_VERSION = '4.3';\nvjs.ACCESS_PROTOCOL = ('https:' == document.location.protocol ? 'https://' : 'http://');\n\n/**\n * Global Player instance options, surfaced from vjs.Player.prototype.options_\n * vjs.options = vjs.Player.prototype.options_\n * All options should use string keys so they avoid\n * renaming by closure compiler\n * @type {Object}\n */\nvjs.options = {\n  // Default order of fallback technology\n  'techOrder': ['html5','flash'],\n  // techOrder: ['flash','html5'],\n\n  'html5': {},\n  'flash': {},\n\n  // Default of web browser is 300x150. Should rely on source width/height.\n  'width': 300,\n  'height': 150,\n  // defaultVolume: 0.85,\n  'defaultVolume': 0.00, // The freakin seaguls are driving me crazy!\n\n  // Included control sets\n  'children': {\n    'mediaLoader': {},\n    'posterImage': {},\n    'textTrackDisplay': {},\n    'loadingSpinner': {},\n    'bigPlayButton': {},\n    'controlBar': {}\n  },\n\n  // Default message to show when a video cannot be played.\n  'notSupportedMessage': 'Sorry, no compatible source and playback ' +\n      'technology were found for this video. Try using another browser ' +\n      'like <a href=\"http://bit.ly/ccMUEC\">Chrome</a> or download the ' +\n      'latest <a href=\"http://adobe.ly/mwfN1\">Adobe Flash Player</a>.'\n};\n\n// Set CDN Version of swf\n// The added (+) blocks the replace from changing this 4.3 string\nif (vjs.CDN_VERSION !== 'GENERATED'+'_CDN_VSN') {\n  videojs.options['flash']['swf'] = vjs.ACCESS_PROTOCOL + 'vjs.zencdn.net/'+vjs.CDN_VERSION+'/video-js.swf';\n}\n\n/**\n * Global player list\n * @type {Object}\n */\nvjs.players = {};\n/**\n * Core Object/Class for objects that use inheritance + contstructors\n *\n * To create a class that can be subclassed itself, extend the CoreObject class.\n *\n *     var Animal = CoreObject.extend();\n *     var Horse = Animal.extend();\n *\n * The constructor can be defined through the init property of an object argument.\n *\n *     var Animal = CoreObject.extend({\n *       init: function(name, sound){\n *         this.name = name;\n *       }\n *     });\n *\n * Other methods and properties can be added the same way, or directly to the\n * prototype.\n *\n *    var Animal = CoreObject.extend({\n *       init: function(name){\n *         this.name = name;\n *       },\n *       getName: function(){\n *         return this.name;\n *       },\n *       sound: '...'\n *    });\n *\n *    Animal.prototype.makeSound = function(){\n *      alert(this.sound);\n *    };\n *\n * To create an instance of a class, use the create method.\n *\n *    var fluffy = Animal.create('Fluffy');\n *    fluffy.getName(); // -> Fluffy\n *\n * Methods and properties can be overridden in subclasses.\n *\n *     var Horse = Animal.extend({\n *       sound: 'Neighhhhh!'\n *     });\n *\n *     var horsey = Horse.create('Horsey');\n *     horsey.getName(); // -> Horsey\n *     horsey.makeSound(); // -> Alert: Neighhhhh!\n *\n * @class\n * @constructor\n */\nvjs.CoreObject = vjs['CoreObject'] = function(){};\n// Manually exporting vjs['CoreObject'] here for Closure Compiler\n// because of the use of the extend/create class methods\n// If we didn't do this, those functions would get flattend to something like\n// `a = ...` and `this.prototype` would refer to the global object instead of\n// CoreObject\n\n/**\n * Create a new object that inherits from this Object\n *\n *     var Animal = CoreObject.extend();\n *     var Horse = Animal.extend();\n *\n * @param {Object} props Functions and properties to be applied to the\n *                       new object's prototype\n * @return {vjs.CoreObject} An object that inherits from CoreObject\n * @this {*}\n */\nvjs.CoreObject.extend = function(props){\n  var init, subObj;\n\n  props = props || {};\n  // Set up the constructor using the supplied init method\n  // or using the init of the parent object\n  // Make sure to check the unobfuscated version for external libs\n  init = props['init'] || props.init || this.prototype['init'] || this.prototype.init || function(){};\n  // In Resig's simple class inheritance (previously used) the constructor\n  //  is a function that calls `this.init.apply(arguments)`\n  // However that would prevent us from using `ParentObject.call(this);`\n  //  in a Child constuctor because the `this` in `this.init`\n  //  would still refer to the Child and cause an inifinite loop.\n  // We would instead have to do\n  //    `ParentObject.prototype.init.apply(this, argumnents);`\n  //  Bleh. We're not creating a _super() function, so it's good to keep\n  //  the parent constructor reference simple.\n  subObj = function(){\n    init.apply(this, arguments);\n  };\n\n  // Inherit from this object's prototype\n  subObj.prototype = vjs.obj.create(this.prototype);\n  // Reset the constructor property for subObj otherwise\n  // instances of subObj would have the constructor of the parent Object\n  subObj.prototype.constructor = subObj;\n\n  // Make the class extendable\n  subObj.extend = vjs.CoreObject.extend;\n  // Make a function for creating instances\n  subObj.create = vjs.CoreObject.create;\n\n  // Extend subObj's prototype with functions and other properties from props\n  for (var name in props) {\n    if (props.hasOwnProperty(name)) {\n      subObj.prototype[name] = props[name];\n    }\n  }\n\n  return subObj;\n};\n\n/**\n * Create a new instace of this Object class\n *\n *     var myAnimal = Animal.create();\n *\n * @return {vjs.CoreObject} An instance of a CoreObject subclass\n * @this {*}\n */\nvjs.CoreObject.create = function(){\n  // Create a new object that inherits from this object's prototype\n  var inst = vjs.obj.create(this.prototype);\n\n  // Apply this constructor function to the new object\n  this.apply(inst, arguments);\n\n  // Return the new object\n  return inst;\n};\n/**\n * @fileoverview Event System (John Resig - Secrets of a JS Ninja http://jsninja.com/)\n * (Original book version wasn't completely usable, so fixed some things and made Closure Compiler compatible)\n * This should work very similarly to jQuery's events, however it's based off the book version which isn't as\n * robust as jquery's, so there's probably some differences.\n */\n\n/**\n * Add an event listener to element\n * It stores the handler function in a separate cache object\n * and adds a generic handler to the element's event,\n * along with a unique id (guid) to the element.\n * @param  {Element|Object}   elem Element or object to bind listeners to\n * @param  {String}   type Type of event to bind to.\n * @param  {Function} fn   Event listener.\n * @private\n */\nvjs.on = function(elem, type, fn){\n  var data = vjs.getData(elem);\n\n  // We need a place to store all our handler data\n  if (!data.handlers) data.handlers = {};\n\n  if (!data.handlers[type]) data.handlers[type] = [];\n\n  if (!fn.guid) fn.guid = vjs.guid++;\n\n  data.handlers[type].push(fn);\n\n  if (!data.dispatcher) {\n    data.disabled = false;\n\n    data.dispatcher = function (event){\n\n      if (data.disabled) return;\n      event = vjs.fixEvent(event);\n\n      var handlers = data.handlers[event.type];\n\n      if (handlers) {\n        // Copy handlers so if handlers are added/removed during the process it doesn't throw everything off.\n        var handlersCopy = handlers.slice(0);\n\n        for (var m = 0, n = handlersCopy.length; m < n; m++) {\n          if (event.isImmediatePropagationStopped()) {\n            break;\n          } else {\n            handlersCopy[m].call(elem, event);\n          }\n        }\n      }\n    };\n  }\n\n  if (data.handlers[type].length == 1) {\n    if (document.addEventListener) {\n      elem.addEventListener(type, data.dispatcher, false);\n    } else if (document.attachEvent) {\n      elem.attachEvent('on' + type, data.dispatcher);\n    }\n  }\n};\n\n/**\n * Removes event listeners from an element\n * @param  {Element|Object}   elem Object to remove listeners from\n * @param  {String=}   type Type of listener to remove. Don't include to remove all events from element.\n * @param  {Function} fn   Specific listener to remove. Don't incldue to remove listeners for an event type.\n * @private\n */\nvjs.off = function(elem, type, fn) {\n  // Don't want to add a cache object through getData if not needed\n  if (!vjs.hasData(elem)) return;\n\n  var data = vjs.getData(elem);\n\n  // If no events exist, nothing to unbind\n  if (!data.handlers) { return; }\n\n  // Utility function\n  var removeType = function(t){\n     data.handlers[t] = [];\n     vjs.cleanUpEvents(elem,t);\n  };\n\n  // Are we removing all bound events?\n  if (!type) {\n    for (var t in data.handlers) removeType(t);\n    return;\n  }\n\n  var handlers = data.handlers[type];\n\n  // If no handlers exist, nothing to unbind\n  if (!handlers) return;\n\n  // If no listener was provided, remove all listeners for type\n  if (!fn) {\n    removeType(type);\n    return;\n  }\n\n  // We're only removing a single handler\n  if (fn.guid) {\n    for (var n = 0; n < handlers.length; n++) {\n      if (handlers[n].guid === fn.guid) {\n        handlers.splice(n--, 1);\n      }\n    }\n  }\n\n  vjs.cleanUpEvents(elem, type);\n};\n\n/**\n * Clean up the listener cache and dispatchers\n * @param  {Element|Object} elem Element to clean up\n * @param  {String} type Type of event to clean up\n * @private\n */\nvjs.cleanUpEvents = function(elem, type) {\n  var data = vjs.getData(elem);\n\n  // Remove the events of a particular type if there are none left\n  if (data.handlers[type].length === 0) {\n    delete data.handlers[type];\n    // data.handlers[type] = null;\n    // Setting to null was causing an error with data.handlers\n\n    // Remove the meta-handler from the element\n    if (document.removeEventListener) {\n      elem.removeEventListener(type, data.dispatcher, false);\n    } else if (document.detachEvent) {\n      elem.detachEvent('on' + type, data.dispatcher);\n    }\n  }\n\n  // Remove the events object if there are no types left\n  if (vjs.isEmpty(data.handlers)) {\n    delete data.handlers;\n    delete data.dispatcher;\n    delete data.disabled;\n\n    // data.handlers = null;\n    // data.dispatcher = null;\n    // data.disabled = null;\n  }\n\n  // Finally remove the expando if there is no data left\n  if (vjs.isEmpty(data)) {\n    vjs.removeData(elem);\n  }\n};\n\n/**\n * Fix a native event to have standard property values\n * @param  {Object} event Event object to fix\n * @return {Object}\n * @private\n */\nvjs.fixEvent = function(event) {\n\n  function returnTrue() { return true; }\n  function returnFalse() { return false; }\n\n  // Test if fixing up is needed\n  // Used to check if !event.stopPropagation instead of isPropagationStopped\n  // But native events return true for stopPropagation, but don't have\n  // other expected methods like isPropagationStopped. Seems to be a problem\n  // with the Javascript Ninja code. So we're just overriding all events now.\n  if (!event || !event.isPropagationStopped) {\n    var old = event || window.event;\n\n    event = {};\n    // Clone the old object so that we can modify the values event = {};\n    // IE8 Doesn't like when you mess with native event properties\n    // Firefox returns false for event.hasOwnProperty('type') and other props\n    //  which makes copying more difficult.\n    // TODO: Probably best to create a whitelist of event props\n    for (var key in old) {\n      // Safari 6.0.3 warns you if you try to copy deprecated layerX/Y\n      if (key !== 'layerX' && key !== 'layerY') {\n        event[key] = old[key];\n      }\n    }\n\n    // The event occurred on this element\n    if (!event.target) {\n      event.target = event.srcElement || document;\n    }\n\n    // Handle which other element the event is related to\n    event.relatedTarget = event.fromElement === event.target ?\n      event.toElement :\n      event.fromElement;\n\n    // Stop the default browser action\n    event.preventDefault = function () {\n      if (old.preventDefault) {\n        old.preventDefault();\n      }\n      event.returnValue = false;\n      event.isDefaultPrevented = returnTrue;\n    };\n\n    event.isDefaultPrevented = returnFalse;\n\n    // Stop the event from bubbling\n    event.stopPropagation = function () {\n      if (old.stopPropagation) {\n        old.stopPropagation();\n      }\n      event.cancelBubble = true;\n      event.isPropagationStopped = returnTrue;\n    };\n\n    event.isPropagationStopped = returnFalse;\n\n    // Stop the event from bubbling and executing other handlers\n    event.stopImmediatePropagation = function () {\n      if (old.stopImmediatePropagation) {\n        old.stopImmediatePropagation();\n      }\n      event.isImmediatePropagationStopped = returnTrue;\n      event.stopPropagation();\n    };\n\n    event.isImmediatePropagationStopped = returnFalse;\n\n    // Handle mouse position\n    if (event.clientX != null) {\n      var doc = document.documentElement, body = document.body;\n\n      event.pageX = event.clientX +\n        (doc && doc.scrollLeft || body && body.scrollLeft || 0) -\n        (doc && doc.clientLeft || body && body.clientLeft || 0);\n      event.pageY = event.clientY +\n        (doc && doc.scrollTop || body && body.scrollTop || 0) -\n        (doc && doc.clientTop || body && body.clientTop || 0);\n    }\n\n    // Handle key presses\n    event.which = event.charCode || event.keyCode;\n\n    // Fix button for mouse clicks:\n    // 0 == left; 1 == middle; 2 == right\n    if (event.button != null) {\n      event.button = (event.button & 1 ? 0 :\n        (event.button & 4 ? 1 :\n          (event.button & 2 ? 2 : 0)));\n    }\n  }\n\n  // Returns fixed-up instance\n  return event;\n};\n\n/**\n * Trigger an event for an element\n * @param  {Element|Object} elem  Element to trigger an event on\n * @param  {String} event Type of event to trigger\n * @private\n */\nvjs.trigger = function(elem, event) {\n  // Fetches element data and a reference to the parent (for bubbling).\n  // Don't want to add a data object to cache for every parent,\n  // so checking hasData first.\n  var elemData = (vjs.hasData(elem)) ? vjs.getData(elem) : {};\n  var parent = elem.parentNode || elem.ownerDocument;\n      // type = event.type || event,\n      // handler;\n\n  // If an event name was passed as a string, creates an event out of it\n  if (typeof event === 'string') {\n    event = { type:event, target:elem };\n  }\n  // Normalizes the event properties.\n  event = vjs.fixEvent(event);\n\n  // If the passed element has a dispatcher, executes the established handlers.\n  if (elemData.dispatcher) {\n    elemData.dispatcher.call(elem, event);\n  }\n\n  // Unless explicitly stopped or the event does not bubble (e.g. media events)\n    // recursively calls this function to bubble the event up the DOM.\n    if (parent && !event.isPropagationStopped() && event.bubbles !== false) {\n    vjs.trigger(parent, event);\n\n  // If at the top of the DOM, triggers the default action unless disabled.\n  } else if (!parent && !event.isDefaultPrevented()) {\n    var targetData = vjs.getData(event.target);\n\n    // Checks if the target has a default action for this event.\n    if (event.target[event.type]) {\n      // Temporarily disables event dispatching on the target as we have already executed the handler.\n      targetData.disabled = true;\n      // Executes the default action.\n      if (typeof event.target[event.type] === 'function') {\n        event.target[event.type]();\n      }\n      // Re-enables event dispatching.\n      targetData.disabled = false;\n    }\n  }\n\n  // Inform the triggerer if the default was prevented by returning false\n  return !event.isDefaultPrevented();\n  /* Original version of js ninja events wasn't complete.\n   * We've since updated to the latest version, but keeping this around\n   * for now just in case.\n   */\n  // // Added in attion to book. Book code was broke.\n  // event = typeof event === 'object' ?\n  //   event[vjs.expando] ?\n  //     event :\n  //     new vjs.Event(type, event) :\n  //   new vjs.Event(type);\n\n  // event.type = type;\n  // if (handler) {\n  //   handler.call(elem, event);\n  // }\n\n  // // Clean up the event in case it is being reused\n  // event.result = undefined;\n  // event.target = elem;\n};\n\n/**\n * Trigger a listener only once for an event\n * @param  {Element|Object}   elem Element or object to\n * @param  {String}   type\n * @param  {Function} fn\n * @private\n */\nvjs.one = function(elem, type, fn) {\n  var func = function(){\n    vjs.off(elem, type, func);\n    fn.apply(this, arguments);\n  };\n  func.guid = fn.guid = fn.guid || vjs.guid++;\n  vjs.on(elem, type, func);\n};\nvar hasOwnProp = Object.prototype.hasOwnProperty;\n\n/**\n * Creates an element and applies properties.\n * @param  {String=} tagName    Name of tag to be created.\n * @param  {Object=} properties Element properties to be applied.\n * @return {Element}\n * @private\n */\nvjs.createEl = function(tagName, properties){\n  var el, propName;\n\n  el = document.createElement(tagName || 'div');\n\n  for (propName in properties){\n    if (hasOwnProp.call(properties, propName)) {\n      //el[propName] = properties[propName];\n      // Not remembering why we were checking for dash\n      // but using setAttribute means you have to use getAttribute\n\n      // The check for dash checks for the aria-* attributes, like aria-label, aria-valuemin.\n      // The additional check for \"role\" is because the default method for adding attributes does not\n      // add the attribute \"role\". My guess is because it's not a valid attribute in some namespaces, although\n      // browsers handle the attribute just fine. The W3C allows for aria-* attributes to be used in pre-HTML5 docs.\n      // http://www.w3.org/TR/wai-aria-primer/#ariahtml. Using setAttribute gets around this problem.\n\n       if (propName.indexOf('aria-') !== -1 || propName=='role') {\n         el.setAttribute(propName, properties[propName]);\n       } else {\n         el[propName] = properties[propName];\n       }\n    }\n  }\n  return el;\n};\n\n/**\n * Uppercase the first letter of a string\n * @param  {String} string String to be uppercased\n * @return {String}\n * @private\n */\nvjs.capitalize = function(string){\n  return string.charAt(0).toUpperCase() + string.slice(1);\n};\n\n/**\n * Object functions container\n * @type {Object}\n * @private\n */\nvjs.obj = {};\n\n/**\n * Object.create shim for prototypal inheritance\n *\n * https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/create\n *\n * @function\n * @param  {Object}   obj Object to use as prototype\n * @private\n */\n vjs.obj.create = Object.create || function(obj){\n  //Create a new function called 'F' which is just an empty object.\n  function F() {}\n\n  //the prototype of the 'F' function should point to the\n  //parameter of the anonymous function.\n  F.prototype = obj;\n\n  //create a new constructor function based off of the 'F' function.\n  return new F();\n};\n\n/**\n * Loop through each property in an object and call a function\n * whose arguments are (key,value)\n * @param  {Object}   obj Object of properties\n * @param  {Function} fn  Function to be called on each property.\n * @this {*}\n * @private\n */\nvjs.obj.each = function(obj, fn, context){\n  for (var key in obj) {\n    if (hasOwnProp.call(obj, key)) {\n      fn.call(context || this, key, obj[key]);\n    }\n  }\n};\n\n/**\n * Merge two objects together and return the original.\n * @param  {Object} obj1\n * @param  {Object} obj2\n * @return {Object}\n * @private\n */\nvjs.obj.merge = function(obj1, obj2){\n  if (!obj2) { return obj1; }\n  for (var key in obj2){\n    if (hasOwnProp.call(obj2, key)) {\n      obj1[key] = obj2[key];\n    }\n  }\n  return obj1;\n};\n\n/**\n * Merge two objects, and merge any properties that are objects\n * instead of just overwriting one. Uses to merge options hashes\n * where deeper default settings are important.\n * @param  {Object} obj1 Object to override\n * @param  {Object} obj2 Overriding object\n * @return {Object}      New object. Obj1 and Obj2 will be untouched.\n * @private\n */\nvjs.obj.deepMerge = function(obj1, obj2){\n  var key, val1, val2;\n\n  // make a copy of obj1 so we're not ovewriting original values.\n  // like prototype.options_ and all sub options objects\n  obj1 = vjs.obj.copy(obj1);\n\n  for (key in obj2){\n    if (hasOwnProp.call(obj2, key)) {\n      val1 = obj1[key];\n      val2 = obj2[key];\n\n      // Check if both properties are pure objects and do a deep merge if so\n      if (vjs.obj.isPlain(val1) && vjs.obj.isPlain(val2)) {\n        obj1[key] = vjs.obj.deepMerge(val1, val2);\n      } else {\n        obj1[key] = obj2[key];\n      }\n    }\n  }\n  return obj1;\n};\n\n/**\n * Make a copy of the supplied object\n * @param  {Object} obj Object to copy\n * @return {Object}     Copy of object\n * @private\n */\nvjs.obj.copy = function(obj){\n  return vjs.obj.merge({}, obj);\n};\n\n/**\n * Check if an object is plain, and not a dom node or any object sub-instance\n * @param  {Object} obj Object to check\n * @return {Boolean}     True if plain, false otherwise\n * @private\n */\nvjs.obj.isPlain = function(obj){\n  return !!obj\n    && typeof obj === 'object'\n    && obj.toString() === '[object Object]'\n    && obj.constructor === Object;\n};\n\n/**\n * Bind (a.k.a proxy or Context). A simple method for changing the context of a function\n   It also stores a unique id on the function so it can be easily removed from events\n * @param  {*}   context The object to bind as scope\n * @param  {Function} fn      The function to be bound to a scope\n * @param  {Number=}   uid     An optional unique ID for the function to be set\n * @return {Function}\n * @private\n */\nvjs.bind = function(context, fn, uid) {\n  // Make sure the function has a unique ID\n  if (!fn.guid) { fn.guid = vjs.guid++; }\n\n  // Create the new function that changes the context\n  var ret = function() {\n    return fn.apply(context, arguments);\n  };\n\n  // Allow for the ability to individualize this function\n  // Needed in the case where multiple objects might share the same prototype\n  // IF both items add an event listener with the same function, then you try to remove just one\n  // it will remove both because they both have the same guid.\n  // when using this, you need to use the bind method when you remove the listener as well.\n  // currently used in text tracks\n  ret.guid = (uid) ? uid + '_' + fn.guid : fn.guid;\n\n  return ret;\n};\n\n/**\n * Element Data Store. Allows for binding data to an element without putting it directly on the element.\n * Ex. Event listneres are stored here.\n * (also from jsninja.com, slightly modified and updated for closure compiler)\n * @type {Object}\n * @private\n */\nvjs.cache = {};\n\n/**\n * Unique ID for an element or function\n * @type {Number}\n * @private\n */\nvjs.guid = 1;\n\n/**\n * Unique attribute name to store an element's guid in\n * @type {String}\n * @constant\n * @private\n */\nvjs.expando = 'vdata' + (new Date()).getTime();\n\n/**\n * Returns the cache object where data for an element is stored\n * @param  {Element} el Element to store data for.\n * @return {Object}\n * @private\n */\nvjs.getData = function(el){\n  var id = el[vjs.expando];\n  if (!id) {\n    id = el[vjs.expando] = vjs.guid++;\n    vjs.cache[id] = {};\n  }\n  return vjs.cache[id];\n};\n\n/**\n * Returns the cache object where data for an element is stored\n * @param  {Element} el Element to store data for.\n * @return {Object}\n * @private\n */\nvjs.hasData = function(el){\n  var id = el[vjs.expando];\n  return !(!id || vjs.isEmpty(vjs.cache[id]));\n};\n\n/**\n * Delete data for the element from the cache and the guid attr from getElementById\n * @param  {Element} el Remove data for an element\n * @private\n */\nvjs.removeData = function(el){\n  var id = el[vjs.expando];\n  if (!id) { return; }\n  // Remove all stored data\n  // Changed to = null\n  // http://coding.smashingmagazine.com/2012/11/05/writing-fast-memory-efficient-javascript/\n  // vjs.cache[id] = null;\n  delete vjs.cache[id];\n\n  // Remove the expando property from the DOM node\n  try {\n    delete el[vjs.expando];\n  } catch(e) {\n    if (el.removeAttribute) {\n      el.removeAttribute(vjs.expando);\n    } else {\n      // IE doesn't appear to support removeAttribute on the document element\n      el[vjs.expando] = null;\n    }\n  }\n};\n\n/**\n * Check if an object is empty\n * @param  {Object}  obj The object to check for emptiness\n * @return {Boolean}\n * @private\n */\nvjs.isEmpty = function(obj) {\n  for (var prop in obj) {\n    // Inlude null properties as empty.\n    if (obj[prop] !== null) {\n      return false;\n    }\n  }\n  return true;\n};\n\n/**\n * Add a CSS class name to an element\n * @param {Element} element    Element to add class name to\n * @param {String} classToAdd Classname to add\n * @private\n */\nvjs.addClass = function(element, classToAdd){\n  if ((' '+element.className+' ').indexOf(' '+classToAdd+' ') == -1) {\n    element.className = element.className === '' ? classToAdd : element.className + ' ' + classToAdd;\n  }\n};\n\n/**\n * Remove a CSS class name from an element\n * @param {Element} element    Element to remove from class name\n * @param {String} classToAdd Classname to remove\n * @private\n */\nvjs.removeClass = function(element, classToRemove){\n  var classNames, i;\n\n  if (element.className.indexOf(classToRemove) == -1) { return; }\n\n  classNames = element.className.split(' ');\n\n  // no arr.indexOf in ie8, and we don't want to add a big shim\n  for (i = classNames.length - 1; i >= 0; i--) {\n    if (classNames[i] === classToRemove) {\n      classNames.splice(i,1);\n    }\n  }\n\n  element.className = classNames.join(' ');\n};\n\n/**\n * Element for testing browser HTML5 video capabilities\n * @type {Element}\n * @constant\n * @private\n */\nvjs.TEST_VID = vjs.createEl('video');\n\n/**\n * Useragent for browser testing.\n * @type {String}\n * @constant\n * @private\n */\nvjs.USER_AGENT = navigator.userAgent;\n\n/**\n * Device is an iPhone\n * @type {Boolean}\n * @constant\n * @private\n */\nvjs.IS_IPHONE = (/iPhone/i).test(vjs.USER_AGENT);\nvjs.IS_IPAD = (/iPad/i).test(vjs.USER_AGENT);\nvjs.IS_IPOD = (/iPod/i).test(vjs.USER_AGENT);\nvjs.IS_IOS = vjs.IS_IPHONE || vjs.IS_IPAD || vjs.IS_IPOD;\n\nvjs.IOS_VERSION = (function(){\n  var match = vjs.USER_AGENT.match(/OS (\\d+)_/i);\n  if (match && match[1]) { return match[1]; }\n})();\n\nvjs.IS_ANDROID = (/Android/i).test(vjs.USER_AGENT);\nvjs.ANDROID_VERSION = (function() {\n  // This matches Android Major.Minor.Patch versions\n  // ANDROID_VERSION is Major.Minor as a Number, if Minor isn't available, then only Major is returned\n  var match = vjs.USER_AGENT.match(/Android (\\d+)(?:\\.(\\d+))?(?:\\.(\\d+))*/i),\n    major,\n    minor;\n\n  if (!match) {\n    return null;\n  }\n\n  major = match[1] && parseFloat(match[1]);\n  minor = match[2] && parseFloat(match[2]);\n\n  if (major && minor) {\n    return parseFloat(match[1] + '.' + match[2]);\n  } else if (major) {\n    return major;\n  } else {\n    return null;\n  }\n})();\n// Old Android is defined as Version older than 2.3, and requiring a webkit version of the android browser\nvjs.IS_OLD_ANDROID = vjs.IS_ANDROID && (/webkit/i).test(vjs.USER_AGENT) && vjs.ANDROID_VERSION < 2.3;\n\nvjs.IS_FIREFOX = (/Firefox/i).test(vjs.USER_AGENT);\nvjs.IS_CHROME = (/Chrome/i).test(vjs.USER_AGENT);\n\nvjs.TOUCH_ENABLED = !!(('ontouchstart' in window) || window.DocumentTouch && document instanceof window.DocumentTouch);\n\n/**\n * Get an element's attribute values, as defined on the HTML tag\n * Attributs are not the same as properties. They're defined on the tag\n * or with setAttribute (which shouldn't be used with HTML)\n * This will return true or false for boolean attributes.\n * @param  {Element} tag Element from which to get tag attributes\n * @return {Object}\n * @private\n */\nvjs.getAttributeValues = function(tag){\n  var obj, knownBooleans, attrs, attrName, attrVal;\n\n  obj = {};\n\n  // known boolean attributes\n  // we can check for matching boolean properties, but older browsers\n  // won't know about HTML5 boolean attributes that we still read from\n  knownBooleans = ','+'autoplay,controls,loop,muted,default'+',';\n\n  if (tag && tag.attributes && tag.attributes.length > 0) {\n    attrs = tag.attributes;\n\n    for (var i = attrs.length - 1; i >= 0; i--) {\n      attrName = attrs[i].name;\n      attrVal = attrs[i].value;\n\n      // check for known booleans\n      // the matching element property will return a value for typeof\n      if (typeof tag[attrName] === 'boolean' || knownBooleans.indexOf(','+attrName+',') !== -1) {\n        // the value of an included boolean attribute is typically an empty\n        // string ('') which would equal false if we just check for a false value.\n        // we also don't want support bad code like autoplay='false'\n        attrVal = (attrVal !== null) ? true : false;\n      }\n\n      obj[attrName] = attrVal;\n    }\n  }\n\n  return obj;\n};\n\n/**\n * Get the computed style value for an element\n * From http://robertnyman.com/2006/04/24/get-the-rendered-style-of-an-element/\n * @param  {Element} el        Element to get style value for\n * @param  {String} strCssRule Style name\n * @return {String}            Style value\n * @private\n */\nvjs.getComputedDimension = function(el, strCssRule){\n  var strValue = '';\n  if(document.defaultView && document.defaultView.getComputedStyle){\n    strValue = document.defaultView.getComputedStyle(el, '').getPropertyValue(strCssRule);\n\n  } else if(el.currentStyle){\n    // IE8 Width/Height support\n    strValue = el['client'+strCssRule.substr(0,1).toUpperCase() + strCssRule.substr(1)] + 'px';\n  }\n  return strValue;\n};\n\n/**\n * Insert an element as the first child node of another\n * @param  {Element} child   Element to insert\n * @param  {[type]} parent Element to insert child into\n * @private\n */\nvjs.insertFirst = function(child, parent){\n  if (parent.firstChild) {\n    parent.insertBefore(child, parent.firstChild);\n  } else {\n    parent.appendChild(child);\n  }\n};\n\n/**\n * Object to hold browser support information\n * @type {Object}\n * @private\n */\nvjs.support = {};\n\n/**\n * Shorthand for document.getElementById()\n * Also allows for CSS (jQuery) ID syntax. But nothing other than IDs.\n * @param  {String} id  Element ID\n * @return {Element}    Element with supplied ID\n * @private\n */\nvjs.el = function(id){\n  if (id.indexOf('#') === 0) {\n    id = id.slice(1);\n  }\n\n  return document.getElementById(id);\n};\n\n/**\n * Format seconds as a time string, H:MM:SS or M:SS\n * Supplying a guide (in seconds) will force a number of leading zeros\n * to cover the length of the guide\n * @param  {Number} seconds Number of seconds to be turned into a string\n * @param  {Number} guide   Number (in seconds) to model the string after\n * @return {String}         Time formatted as H:MM:SS or M:SS\n * @private\n */\nvjs.formatTime = function(seconds, guide) {\n  // Default to using seconds as guide\n  guide = guide || seconds;\n  var s = Math.floor(seconds % 60),\n      m = Math.floor(seconds / 60 % 60),\n      h = Math.floor(seconds / 3600),\n      gm = Math.floor(guide / 60 % 60),\n      gh = Math.floor(guide / 3600);\n\n  // handle invalid times\n  if (isNaN(seconds) || seconds === Infinity) {\n    // '-' is false for all relational operators (e.g. <, >=) so this setting\n    // will add the minimum number of fields specified by the guide\n    h = m = s = '-';\n  }\n\n  // Check if we need to show hours\n  h = (h > 0 || gh > 0) ? h + ':' : '';\n\n  // If hours are showing, we may need to add a leading zero.\n  // Always show at least one digit of minutes.\n  m = (((h || gm >= 10) && m < 10) ? '0' + m : m) + ':';\n\n  // Check if leading zero is need for seconds\n  s = (s < 10) ? '0' + s : s;\n\n  return h + m + s;\n};\n\n// Attempt to block the ability to select text while dragging controls\nvjs.blockTextSelection = function(){\n  document.body.focus();\n  document.onselectstart = function () { return false; };\n};\n// Turn off text selection blocking\nvjs.unblockTextSelection = function(){ document.onselectstart = function () { return true; }; };\n\n/**\n * Trim whitespace from the ends of a string.\n * @param  {String} string String to trim\n * @return {String}        Trimmed string\n * @private\n */\nvjs.trim = function(str){\n  return (str+'').replace(/^\\s+|\\s+$/g, '');\n};\n\n/**\n * Should round off a number to a decimal place\n * @param  {Number} num Number to round\n * @param  {Number} dec Number of decimal places to round to\n * @return {Number}     Rounded number\n * @private\n */\nvjs.round = function(num, dec) {\n  if (!dec) { dec = 0; }\n  return Math.round(num*Math.pow(10,dec))/Math.pow(10,dec);\n};\n\n/**\n * Should create a fake TimeRange object\n * Mimics an HTML5 time range instance, which has functions that\n * return the start and end times for a range\n * TimeRanges are returned by the buffered() method\n * @param  {Number} start Start time in seconds\n * @param  {Number} end   End time in seconds\n * @return {Object}       Fake TimeRange object\n * @private\n */\nvjs.createTimeRange = function(start, end){\n  return {\n    length: 1,\n    start: function() { return start; },\n    end: function() { return end; }\n  };\n};\n\n/**\n * Simple http request for retrieving external files (e.g. text tracks)\n * @param  {String} url           URL of resource\n * @param  {Function=} onSuccess  Success callback\n * @param  {Function=} onError    Error callback\n * @private\n */\nvjs.get = function(url, onSuccess, onError){\n  var local, request;\n\n  if (typeof XMLHttpRequest === 'undefined') {\n    window.XMLHttpRequest = function () {\n      try { return new window.ActiveXObject('Msxml2.XMLHTTP.6.0'); } catch (e) {}\n      try { return new window.ActiveXObject('Msxml2.XMLHTTP.3.0'); } catch (f) {}\n      try { return new window.ActiveXObject('Msxml2.XMLHTTP'); } catch (g) {}\n      throw new Error('This browser does not support XMLHttpRequest.');\n    };\n  }\n\n  request = new XMLHttpRequest();\n  try {\n    request.open('GET', url);\n  } catch(e) {\n    onError(e);\n  }\n\n  local = (url.indexOf('file:') === 0 || (window.location.href.indexOf('file:') === 0 && url.indexOf('http') === -1));\n\n  request.onreadystatechange = function() {\n    if (request.readyState === 4) {\n      if (request.status === 200 || local && request.status === 0) {\n        onSuccess(request.responseText);\n      } else {\n        if (onError) {\n          onError();\n        }\n      }\n    }\n  };\n\n  try {\n    request.send();\n  } catch(e) {\n    if (onError) {\n      onError(e);\n    }\n  }\n};\n\n/**\n * Add to local storage (may removeable)\n * @private\n */\nvjs.setLocalStorage = function(key, value){\n  try {\n    // IE was throwing errors referencing the var anywhere without this\n    var localStorage = window.localStorage || false;\n    if (!localStorage) { return; }\n    localStorage[key] = value;\n  } catch(e) {\n    if (e.code == 22 || e.code == 1014) { // Webkit == 22 / Firefox == 1014\n      vjs.log('LocalStorage Full (VideoJS)', e);\n    } else {\n      if (e.code == 18) {\n        vjs.log('LocalStorage not allowed (VideoJS)', e);\n      } else {\n        vjs.log('LocalStorage Error (VideoJS)', e);\n      }\n    }\n  }\n};\n\n/**\n * Get abosolute version of relative URL. Used to tell flash correct URL.\n * http://stackoverflow.com/questions/470832/getting-an-absolute-url-from-a-relative-one-ie6-issue\n * @param  {String} url URL to make absolute\n * @return {String}     Absolute URL\n * @private\n */\nvjs.getAbsoluteURL = function(url){\n\n  // Check if absolute URL\n  if (!url.match(/^https?:\\/\\//)) {\n    // Convert to absolute URL. Flash hosted off-site needs an absolute URL.\n    url = vjs.createEl('div', {\n      innerHTML: '<a href=\"'+url+'\">x</a>'\n    }).firstChild.href;\n  }\n\n  return url;\n};\n\n// usage: log('inside coolFunc',this,arguments);\n// http://paulirish.com/2009/log-a-lightweight-wrapper-for-consolelog/\nvjs.log = function(){\n  vjs.log.history = vjs.log.history || [];   // store logs to an array for reference\n  vjs.log.history.push(arguments);\n  if(window.console){\n    window.console.log(Array.prototype.slice.call(arguments));\n  }\n};\n\n// Offset Left\n// getBoundingClientRect technique from John Resig http://ejohn.org/blog/getboundingclientrect-is-awesome/\nvjs.findPosition = function(el) {\n    var box, docEl, body, clientLeft, scrollLeft, left, clientTop, scrollTop, top;\n\n    if (el.getBoundingClientRect && el.parentNode) {\n      box = el.getBoundingClientRect();\n    }\n\n    if (!box) {\n      return {\n        left: 0,\n        top: 0\n      };\n    }\n\n    docEl = document.documentElement;\n    body = document.body;\n\n    clientLeft = docEl.clientLeft || body.clientLeft || 0;\n    scrollLeft = window.pageXOffset || body.scrollLeft;\n    left = box.left + scrollLeft - clientLeft;\n\n    clientTop = docEl.clientTop || body.clientTop || 0;\n    scrollTop = window.pageYOffset || body.scrollTop;\n    top = box.top + scrollTop - clientTop;\n\n    return {\n      left: left,\n      top: top\n    };\n};\n/**\n * @fileoverview Player Component - Base class for all UI objects\n *\n */\n\n/**\n * Base UI Component class\n *\n * Components are embeddable UI objects that are represented by both a\n * javascript object and an element in the DOM. They can be children of other\n * components, and can have many children themselves.\n *\n *     // adding a button to the player\n *     var button = player.addChild('button');\n *     button.el(); // -> button element\n *\n *     <div class=\"video-js\">\n *       <div class=\"vjs-button\">Button</div>\n *     </div>\n *\n * Components are also event emitters.\n *\n *     button.on('click', function(){\n *       console.log('Button Clicked!');\n *     });\n *\n *     button.trigger('customevent');\n *\n * @param {Object} player  Main Player\n * @param {Object=} options\n * @class\n * @constructor\n * @extends vjs.CoreObject\n */\nvjs.Component = vjs.CoreObject.extend({\n  /**\n   * the constructor funciton for the class\n   *\n   * @constructor\n   */\n  init: function(player, options, ready){\n    this.player_ = player;\n\n    // Make a copy of prototype.options_ to protect against overriding global defaults\n    this.options_ = vjs.obj.copy(this.options_);\n\n    // Updated options with supplied options\n    options = this.options(options);\n\n    // Get ID from options, element, or create using player ID and unique ID\n    this.id_ = options['id'] || ((options['el'] && options['el']['id']) ? options['el']['id'] : player.id() + '_component_' + vjs.guid++ );\n\n    this.name_ = options['name'] || null;\n\n    // Create element if one wasn't provided in options\n    this.el_ = options['el'] || this.createEl();\n\n    this.children_ = [];\n    this.childIndex_ = {};\n    this.childNameIndex_ = {};\n\n    // Add any child components in options\n    this.initChildren();\n\n    this.ready(ready);\n    // Don't want to trigger ready here or it will before init is actually\n    // finished for all children that run this constructor\n  }\n});\n\n/**\n * Dispose of the component and all child components\n */\nvjs.Component.prototype.dispose = function(){\n  this.trigger('dispose');\n\n  // Dispose all children.\n  if (this.children_) {\n    for (var i = this.children_.length - 1; i >= 0; i--) {\n      if (this.children_[i].dispose) {\n        this.children_[i].dispose();\n      }\n    }\n  }\n\n  // Delete child references\n  this.children_ = null;\n  this.childIndex_ = null;\n  this.childNameIndex_ = null;\n\n  // Remove all event listeners.\n  this.off();\n\n  // Remove element from DOM\n  if (this.el_.parentNode) {\n    this.el_.parentNode.removeChild(this.el_);\n  }\n\n  vjs.removeData(this.el_);\n  this.el_ = null;\n};\n\n/**\n * Reference to main player instance\n *\n * @type {vjs.Player}\n * @private\n */\nvjs.Component.prototype.player_ = true;\n\n/**\n * Return the component's player\n *\n * @return {vjs.Player}\n */\nvjs.Component.prototype.player = function(){\n  return this.player_;\n};\n\n/**\n * The component's options object\n *\n * @type {Object}\n * @private\n */\nvjs.Component.prototype.options_;\n\n/**\n * Deep merge of options objects\n *\n * Whenever a property is an object on both options objects\n * the two properties will be merged using vjs.obj.deepMerge.\n *\n * This is used for merging options for child components. We\n * want it to be easy to override individual options on a child\n * component without having to rewrite all the other default options.\n *\n *     Parent.prototype.options_ = {\n *       children: {\n *         'childOne': { 'foo': 'bar', 'asdf': 'fdsa' },\n *         'childTwo': {},\n *         'childThree': {}\n *       }\n *     }\n *     newOptions = {\n *       children: {\n *         'childOne': { 'foo': 'baz', 'abc': '123' }\n *         'childTwo': null,\n *         'childFour': {}\n *       }\n *     }\n *\n *     this.options(newOptions);\n *\n * RESULT\n *\n *     {\n *       children: {\n *         'childOne': { 'foo': 'baz', 'asdf': 'fdsa', 'abc': '123' },\n *         'childTwo': null, // Disabled. Won't be initialized.\n *         'childThree': {},\n *         'childFour': {}\n *       }\n *     }\n *\n * @param  {Object} obj Object whose values will be overwritten\n * @return {Object}     NEW merged object. Does not return obj1.\n */\nvjs.Component.prototype.options = function(obj){\n  if (obj === undefined) return this.options_;\n\n  return this.options_ = vjs.obj.deepMerge(this.options_, obj);\n};\n\n/**\n * The DOM element for the component\n *\n * @type {Element}\n * @private\n */\nvjs.Component.prototype.el_;\n\n/**\n * Create the component's DOM element\n *\n * @param  {String=} tagName  Element's node type. e.g. 'div'\n * @param  {Object=} attributes An object of element attributes that should be set on the element\n * @return {Element}\n */\nvjs.Component.prototype.createEl = function(tagName, attributes){\n  return vjs.createEl(tagName, attributes);\n};\n\n/**\n * Get the component's DOM element\n *\n *     var domEl = myComponent.el();\n *\n * @return {Element}\n */\nvjs.Component.prototype.el = function(){\n  return this.el_;\n};\n\n/**\n * An optional element where, if defined, children will be inserted instead of\n * directly in `el_`\n *\n * @type {Element}\n * @private\n */\nvjs.Component.prototype.contentEl_;\n\n/**\n * Return the component's DOM element for embedding content.\n * Will either be el_ or a new element defined in createEl.\n *\n * @return {Element}\n */\nvjs.Component.prototype.contentEl = function(){\n  return this.contentEl_ || this.el_;\n};\n\n/**\n * The ID for the component\n *\n * @type {String}\n * @private\n */\nvjs.Component.prototype.id_;\n\n/**\n * Get the component's ID\n *\n *     var id = myComponent.id();\n *\n * @return {String}\n */\nvjs.Component.prototype.id = function(){\n  return this.id_;\n};\n\n/**\n * The name for the component. Often used to reference the component.\n *\n * @type {String}\n * @private\n */\nvjs.Component.prototype.name_;\n\n/**\n * Get the component's name. The name is often used to reference the component.\n *\n *     var name = myComponent.name();\n *\n * @return {String}\n */\nvjs.Component.prototype.name = function(){\n  return this.name_;\n};\n\n/**\n * Array of child components\n *\n * @type {Array}\n * @private\n */\nvjs.Component.prototype.children_;\n\n/**\n * Get an array of all child components\n *\n *     var kids = myComponent.children();\n *\n * @return {Array} The children\n */\nvjs.Component.prototype.children = function(){\n  return this.children_;\n};\n\n/**\n * Object of child components by ID\n *\n * @type {Object}\n * @private\n */\nvjs.Component.prototype.childIndex_;\n\n/**\n * Returns a child component with the provided ID\n *\n * @return {vjs.Component}\n */\nvjs.Component.prototype.getChildById = function(id){\n  return this.childIndex_[id];\n};\n\n/**\n * Object of child components by name\n *\n * @type {Object}\n * @private\n */\nvjs.Component.prototype.childNameIndex_;\n\n/**\n * Returns a child component with the provided ID\n *\n * @return {vjs.Component}\n */\nvjs.Component.prototype.getChild = function(name){\n  return this.childNameIndex_[name];\n};\n\n/**\n * Adds a child component inside this component\n *\n *     myComponent.el();\n *     // -> <div class='my-component'></div>\n *     myComonent.children();\n *     // [empty array]\n *\n *     var myButton = myComponent.addChild('MyButton');\n *     // -> <div class='my-component'><div class=\"my-button\">myButton<div></div>\n *     // -> myButton === myComonent.children()[0];\n *\n * Pass in options for child constructors and options for children of the child\n *\n *    var myButton = myComponent.addChild('MyButton', {\n *      text: 'Press Me',\n *      children: {\n *        buttonChildExample: {\n *          buttonChildOption: true\n *        }\n *      }\n *    });\n *\n * @param {String|vjs.Component} child The class name or instance of a child to add\n * @param {Object=} options Options, including options to be passed to children of the child.\n * @return {vjs.Component} The child component (created by this process if a string was used)\n * @suppress {accessControls|checkRegExp|checkTypes|checkVars|const|constantProperty|deprecated|duplicate|es5Strict|fileoverviewTags|globalThis|invalidCasts|missingProperties|nonStandardJsDocs|strictModuleDepCheck|undefinedNames|undefinedVars|unknownDefines|uselessCode|visibility}\n */\nvjs.Component.prototype.addChild = function(child, options){\n  var component, componentClass, componentName, componentId;\n\n  // If string, create new component with options\n  if (typeof child === 'string') {\n\n    componentName = child;\n\n    // Make sure options is at least an empty object to protect against errors\n    options = options || {};\n\n    // Assume name of set is a lowercased name of the UI Class (PlayButton, etc.)\n    componentClass = options['componentClass'] || vjs.capitalize(componentName);\n\n    // Set name through options\n    options['name'] = componentName;\n\n    // Create a new object & element for this controls set\n    // If there's no .player_, this is a player\n    // Closure Compiler throws an 'incomplete alias' warning if we use the vjs variable directly.\n    // Every class should be exported, so this should never be a problem here.\n    component = new window['videojs'][componentClass](this.player_ || this, options);\n\n  // child is a component instance\n  } else {\n    component = child;\n  }\n\n  this.children_.push(component);\n\n  if (typeof component.id === 'function') {\n    this.childIndex_[component.id()] = component;\n  }\n\n  // If a name wasn't used to create the component, check if we can use the\n  // name function of the component\n  componentName = componentName || (component.name && component.name());\n\n  if (componentName) {\n    this.childNameIndex_[componentName] = component;\n  }\n\n  // Add the UI object's element to the container div (box)\n  // Having an element is not required\n  if (typeof component['el'] === 'function' && component['el']()) {\n    this.contentEl().appendChild(component['el']());\n  }\n\n  // Return so it can stored on parent object if desired.\n  return component;\n};\n\n/**\n * Remove a child component from this component's list of children, and the\n * child component's element from this component's element\n *\n * @param  {vjs.Component} component Component to remove\n */\nvjs.Component.prototype.removeChild = function(component){\n  if (typeof component === 'string') {\n    component = this.getChild(component);\n  }\n\n  if (!component || !this.children_) return;\n\n  var childFound = false;\n  for (var i = this.children_.length - 1; i >= 0; i--) {\n    if (this.children_[i] === component) {\n      childFound = true;\n      this.children_.splice(i,1);\n      break;\n    }\n  }\n\n  if (!childFound) return;\n\n  this.childIndex_[component.id] = null;\n  this.childNameIndex_[component.name] = null;\n\n  var compEl = component.el();\n  if (compEl && compEl.parentNode === this.contentEl()) {\n    this.contentEl().removeChild(component.el());\n  }\n};\n\n/**\n * Add and initialize default child components from options\n *\n *     // when an instance of MyComponent is created, all children in options\n *     // will be added to the instance by their name strings and options\n *     MyComponent.prototype.options_.children = {\n *       myChildComponent: {\n *         myChildOption: true\n *       }\n *     }\n */\nvjs.Component.prototype.initChildren = function(){\n  var options = this.options_;\n\n  if (options && options['children']) {\n    var self = this;\n\n    // Loop through components and add them to the player\n    vjs.obj.each(options['children'], function(name, opts){\n      // Allow for disabling default components\n      // e.g. vjs.options['children']['posterImage'] = false\n      if (opts === false) return;\n\n      // Allow waiting to add components until a specific event is called\n      var tempAdd = function(){\n        // Set property name on player. Could cause conflicts with other prop names, but it's worth making refs easy.\n        self[name] = self.addChild(name, opts);\n      };\n\n      if (opts['loadEvent']) {\n        // this.one(opts.loadEvent, tempAdd)\n      } else {\n        tempAdd();\n      }\n    });\n  }\n};\n\n/**\n * Allows sub components to stack CSS class names\n *\n * @return {String} The constructed class name\n */\nvjs.Component.prototype.buildCSSClass = function(){\n    // Child classes can include a function that does:\n    // return 'CLASS NAME' + this._super();\n    return '';\n};\n\n/* Events\n============================================================================= */\n\n/**\n * Add an event listener to this component's element\n *\n *     var myFunc = function(){\n *       var myPlayer = this;\n *       // Do something when the event is fired\n *     };\n *\n *     myPlayer.on(\"eventName\", myFunc);\n *\n * The context will be the component.\n *\n * @param  {String}   type The event type e.g. 'click'\n * @param  {Function} fn   The event listener\n * @return {vjs.Component} self\n */\nvjs.Component.prototype.on = function(type, fn){\n  vjs.on(this.el_, type, vjs.bind(this, fn));\n  return this;\n};\n\n/**\n * Remove an event listener from the component's element\n *\n *     myComponent.off(\"eventName\", myFunc);\n *\n * @param  {String=}   type Event type. Without type it will remove all listeners.\n * @param  {Function=} fn   Event listener. Without fn it will remove all listeners for a type.\n * @return {vjs.Component}\n */\nvjs.Component.prototype.off = function(type, fn){\n  vjs.off(this.el_, type, fn);\n  return this;\n};\n\n/**\n * Add an event listener to be triggered only once and then removed\n *\n * @param  {String}   type Event type\n * @param  {Function} fn   Event listener\n * @return {vjs.Component}\n */\nvjs.Component.prototype.one = function(type, fn) {\n  vjs.one(this.el_, type, vjs.bind(this, fn));\n  return this;\n};\n\n/**\n * Trigger an event on an element\n *\n *     myComponent.trigger('eventName');\n *\n * @param  {String}       type  The event type to trigger, e.g. 'click'\n * @param  {Event|Object} event The event object to be passed to the listener\n * @return {vjs.Component}      self\n */\nvjs.Component.prototype.trigger = function(type, event){\n  vjs.trigger(this.el_, type, event);\n  return this;\n};\n\n/* Ready\n================================================================================ */\n/**\n * Is the component loaded\n * This can mean different things depending on the component.\n *\n * @private\n * @type {Boolean}\n */\nvjs.Component.prototype.isReady_;\n\n/**\n * Trigger ready as soon as initialization is finished\n *\n * Allows for delaying ready. Override on a sub class prototype.\n * If you set this.isReadyOnInitFinish_ it will affect all components.\n * Specially used when waiting for the Flash player to asynchrnously load.\n *\n * @type {Boolean}\n * @private\n */\nvjs.Component.prototype.isReadyOnInitFinish_ = true;\n\n/**\n * List of ready listeners\n *\n * @type {Array}\n * @private\n */\nvjs.Component.prototype.readyQueue_;\n\n/**\n * Bind a listener to the component's ready state\n *\n * Different from event listeners in that if the ready event has already happend\n * it will trigger the function immediately.\n *\n * @param  {Function} fn Ready listener\n * @return {vjs.Component}\n */\nvjs.Component.prototype.ready = function(fn){\n  if (fn) {\n    if (this.isReady_) {\n      fn.call(this);\n    } else {\n      if (this.readyQueue_ === undefined) {\n        this.readyQueue_ = [];\n      }\n      this.readyQueue_.push(fn);\n    }\n  }\n  return this;\n};\n\n/**\n * Trigger the ready listeners\n *\n * @return {vjs.Component}\n */\nvjs.Component.prototype.triggerReady = function(){\n  this.isReady_ = true;\n\n  var readyQueue = this.readyQueue_;\n\n  if (readyQueue && readyQueue.length > 0) {\n\n    for (var i = 0, j = readyQueue.length; i < j; i++) {\n      readyQueue[i].call(this);\n    }\n\n    // Reset Ready Queue\n    this.readyQueue_ = [];\n\n    // Allow for using event listeners also, in case you want to do something everytime a source is ready.\n    this.trigger('ready');\n  }\n};\n\n/* Display\n============================================================================= */\n\n/**\n * Add a CSS class name to the component's element\n *\n * @param {String} classToAdd Classname to add\n * @return {vjs.Component}\n */\nvjs.Component.prototype.addClass = function(classToAdd){\n  vjs.addClass(this.el_, classToAdd);\n  return this;\n};\n\n/**\n * Remove a CSS class name from the component's element\n *\n * @param {String} classToRemove Classname to remove\n * @return {vjs.Component}\n */\nvjs.Component.prototype.removeClass = function(classToRemove){\n  vjs.removeClass(this.el_, classToRemove);\n  return this;\n};\n\n/**\n * Show the component element if hidden\n *\n * @return {vjs.Component}\n */\nvjs.Component.prototype.show = function(){\n  this.el_.style.display = 'block';\n  return this;\n};\n\n/**\n * Hide the component element if hidden\n *\n * @return {vjs.Component}\n */\nvjs.Component.prototype.hide = function(){\n  this.el_.style.display = 'none';\n  return this;\n};\n\n/**\n * Lock an item in its visible state\n * To be used with fadeIn/fadeOut.\n *\n * @return {vjs.Component}\n * @private\n */\nvjs.Component.prototype.lockShowing = function(){\n  this.addClass('vjs-lock-showing');\n  return this;\n};\n\n/**\n * Unlock an item to be hidden\n * To be used with fadeIn/fadeOut.\n *\n * @return {vjs.Component}\n * @private\n */\nvjs.Component.prototype.unlockShowing = function(){\n  this.removeClass('vjs-lock-showing');\n  return this;\n};\n\n/**\n * Disable component by making it unshowable\n */\nvjs.Component.prototype.disable = function(){\n  this.hide();\n  this.show = function(){};\n};\n\n/**\n * Set or get the width of the component (CSS values)\n *\n * Video tag width/height only work in pixels. No percents.\n * But allowing limited percents use. e.g. width() will return number+%, not computed width\n *\n * @param  {Number|String=} num   Optional width number\n * @param  {Boolean} skipListeners Skip the 'resize' event trigger\n * @return {vjs.Component} Returns 'this' if width was set\n * @return {Number|String} Returns the width if nothing was set\n */\nvjs.Component.prototype.width = function(num, skipListeners){\n  return this.dimension('width', num, skipListeners);\n};\n\n/**\n * Get or set the height of the component (CSS values)\n *\n * @param  {Number|String=} num     New component height\n * @param  {Boolean=} skipListeners Skip the resize event trigger\n * @return {vjs.Component} The component if the height was set\n * @return {Number|String} The height if it wasn't set\n */\nvjs.Component.prototype.height = function(num, skipListeners){\n  return this.dimension('height', num, skipListeners);\n};\n\n/**\n * Set both width and height at the same time\n *\n * @param  {Number|String} width\n * @param  {Number|String} height\n * @return {vjs.Component} The component\n */\nvjs.Component.prototype.dimensions = function(width, height){\n  // Skip resize listeners on width for optimization\n  return this.width(width, true).height(height);\n};\n\n/**\n * Get or set width or height\n *\n * This is the shared code for the width() and height() methods.\n * All for an integer, integer + 'px' or integer + '%';\n *\n * Known issue: Hidden elements officially have a width of 0. We're defaulting\n * to the style.width value and falling back to computedStyle which has the\n * hidden element issue. Info, but probably not an efficient fix:\n * http://www.foliotek.com/devblog/getting-the-width-of-a-hidden-element-with-jquery-using-width/\n *\n * @param  {String} widthOrHeight  'width' or 'height'\n * @param  {Number|String=} num     New dimension\n * @param  {Boolean=} skipListeners Skip resize event trigger\n * @return {vjs.Component} The component if a dimension was set\n * @return {Number|String} The dimension if nothing was set\n * @private\n */\nvjs.Component.prototype.dimension = function(widthOrHeight, num, skipListeners){\n  if (num !== undefined) {\n\n    // Check if using css width/height (% or px) and adjust\n    if ((''+num).indexOf('%') !== -1 || (''+num).indexOf('px') !== -1) {\n      this.el_.style[widthOrHeight] = num;\n    } else if (num === 'auto') {\n      this.el_.style[widthOrHeight] = '';\n    } else {\n      this.el_.style[widthOrHeight] = num+'px';\n    }\n\n    // skipListeners allows us to avoid triggering the resize event when setting both width and height\n    if (!skipListeners) { this.trigger('resize'); }\n\n    // Return component\n    return this;\n  }\n\n  // Not setting a value, so getting it\n  // Make sure element exists\n  if (!this.el_) return 0;\n\n  // Get dimension value from style\n  var val = this.el_.style[widthOrHeight];\n  var pxIndex = val.indexOf('px');\n  if (pxIndex !== -1) {\n    // Return the pixel value with no 'px'\n    return parseInt(val.slice(0,pxIndex), 10);\n\n  // No px so using % or no style was set, so falling back to offsetWidth/height\n  // If component has display:none, offset will return 0\n  // TODO: handle display:none and no dimension style using px\n  } else {\n\n    return parseInt(this.el_['offset'+vjs.capitalize(widthOrHeight)], 10);\n\n    // ComputedStyle version.\n    // Only difference is if the element is hidden it will return\n    // the percent value (e.g. '100%'')\n    // instead of zero like offsetWidth returns.\n    // var val = vjs.getComputedStyleValue(this.el_, widthOrHeight);\n    // var pxIndex = val.indexOf('px');\n\n    // if (pxIndex !== -1) {\n    //   return val.slice(0, pxIndex);\n    // } else {\n    //   return val;\n    // }\n  }\n};\n\n/**\n * Fired when the width and/or height of the component changes\n * @event resize\n */\nvjs.Component.prototype.onResize;\n\n/**\n * Emit 'tap' events when touch events are supported\n *\n * This is used to support toggling the controls through a tap on the video.\n *\n * We're requireing them to be enabled because otherwise every component would\n * have this extra overhead unnecessarily, on mobile devices where extra\n * overhead is especially bad.\n * @private\n */\nvjs.Component.prototype.emitTapEvents = function(){\n  var touchStart, touchTime, couldBeTap, noTap;\n\n  // Track the start time so we can determine how long the touch lasted\n  touchStart = 0;\n\n  this.on('touchstart', function(event) {\n    // Record start time so we can detect a tap vs. \"touch and hold\"\n    touchStart = new Date().getTime();\n    // Reset couldBeTap tracking\n    couldBeTap = true;\n  });\n\n  noTap = function(){\n    couldBeTap = false;\n  };\n  // TODO: Listen to the original target. http://youtu.be/DujfpXOKUp8?t=13m8s\n  this.on('touchmove', noTap);\n  this.on('touchleave', noTap);\n  this.on('touchcancel', noTap);\n\n  // When the touch ends, measure how long it took and trigger the appropriate\n  // event\n  this.on('touchend', function() {\n    // Proceed only if the touchmove/leave/cancel event didn't happen\n    if (couldBeTap === true) {\n      // Measure how long the touch lasted\n      touchTime = new Date().getTime() - touchStart;\n      // The touch needs to be quick in order to consider it a tap\n      if (touchTime < 250) {\n        this.trigger('tap');\n        // It may be good to copy the touchend event object and change the\n        // type to tap, if the other event properties aren't exact after\n        // vjs.fixEvent runs (e.g. event.target)\n      }\n    }\n  });\n};\n/* Button - Base class for all buttons\n================================================================================ */\n/**\n * Base class for all buttons\n * @param {vjs.Player|Object} player\n * @param {Object=} options\n * @class\n * @constructor\n */\nvjs.Button = vjs.Component.extend({\n  /**\n   * @constructor\n   * @inheritDoc\n   */\n  init: function(player, options){\n    vjs.Component.call(this, player, options);\n\n    var touchstart = false;\n    this.on('touchstart', function(event) {\n      // Stop click and other mouse events from triggering also\n      event.preventDefault();\n      touchstart = true;\n    });\n    this.on('touchmove', function() {\n      touchstart = false;\n    });\n    var self = this;\n    this.on('touchend', function(event) {\n      if (touchstart) {\n        self.onClick(event);\n      }\n      event.preventDefault();\n    });\n\n    this.on('click', this.onClick);\n    this.on('focus', this.onFocus);\n    this.on('blur', this.onBlur);\n  }\n});\n\nvjs.Button.prototype.createEl = function(type, props){\n  // Add standard Aria and Tabindex info\n  props = vjs.obj.merge({\n    className: this.buildCSSClass(),\n    innerHTML: '<div class=\"vjs-control-content\"><span class=\"vjs-control-text\">' + (this.buttonText || 'Need Text') + '</span></div>',\n    role: 'button',\n    'aria-live': 'polite', // let the screen reader user know that the text of the button may change\n    tabIndex: 0\n  }, props);\n\n  return vjs.Component.prototype.createEl.call(this, type, props);\n};\n\nvjs.Button.prototype.buildCSSClass = function(){\n  // TODO: Change vjs-control to vjs-button?\n  return 'vjs-control ' + vjs.Component.prototype.buildCSSClass.call(this);\n};\n\n  // Click - Override with specific functionality for button\nvjs.Button.prototype.onClick = function(){};\n\n  // Focus - Add keyboard functionality to element\nvjs.Button.prototype.onFocus = function(){\n  vjs.on(document, 'keyup', vjs.bind(this, this.onKeyPress));\n};\n\n  // KeyPress (document level) - Trigger click when keys are pressed\nvjs.Button.prototype.onKeyPress = function(event){\n  // Check for space bar (32) or enter (13) keys\n  if (event.which == 32 || event.which == 13) {\n    event.preventDefault();\n    this.onClick();\n  }\n};\n\n// Blur - Remove keyboard triggers\nvjs.Button.prototype.onBlur = function(){\n  vjs.off(document, 'keyup', vjs.bind(this, this.onKeyPress));\n};\n/* Slider\n================================================================================ */\n/**\n * The base functionality for sliders like the volume bar and seek bar\n *\n * @param {vjs.Player|Object} player\n * @param {Object=} options\n * @constructor\n */\nvjs.Slider = vjs.Component.extend({\n  /** @constructor */\n  init: function(player, options){\n    vjs.Component.call(this, player, options);\n\n    // Set property names to bar and handle to match with the child Slider class is looking for\n    this.bar = this.getChild(this.options_['barName']);\n    this.handle = this.getChild(this.options_['handleName']);\n\n    player.on(this.playerEvent, vjs.bind(this, this.update));\n\n    this.on('mousedown', this.onMouseDown);\n    this.on('touchstart', this.onMouseDown);\n    this.on('focus', this.onFocus);\n    this.on('blur', this.onBlur);\n    this.on('click', this.onClick);\n\n    this.player_.on('controlsvisible', vjs.bind(this, this.update));\n\n    // This is actually to fix the volume handle position. http://twitter.com/#!/gerritvanaaken/status/159046254519787520\n    // this.player_.one('timeupdate', vjs.bind(this, this.update));\n\n    player.ready(vjs.bind(this, this.update));\n\n    this.boundEvents = {};\n  }\n});\n\nvjs.Slider.prototype.createEl = function(type, props) {\n  props = props || {};\n  // Add the slider element class to all sub classes\n  props.className = props.className + ' vjs-slider';\n  props = vjs.obj.merge({\n    role: 'slider',\n    'aria-valuenow': 0,\n    'aria-valuemin': 0,\n    'aria-valuemax': 100,\n    tabIndex: 0\n  }, props);\n\n  return vjs.Component.prototype.createEl.call(this, type, props);\n};\n\nvjs.Slider.prototype.onMouseDown = function(event){\n  event.preventDefault();\n  vjs.blockTextSelection();\n\n  this.boundEvents.move = vjs.bind(this, this.onMouseMove);\n  this.boundEvents.end = vjs.bind(this, this.onMouseUp);\n\n  vjs.on(document, 'mousemove', this.boundEvents.move);\n  vjs.on(document, 'mouseup', this.boundEvents.end);\n  vjs.on(document, 'touchmove', this.boundEvents.move);\n  vjs.on(document, 'touchend', this.boundEvents.end);\n\n  this.onMouseMove(event);\n};\n\nvjs.Slider.prototype.onMouseUp = function() {\n  vjs.unblockTextSelection();\n  vjs.off(document, 'mousemove', this.boundEvents.move, false);\n  vjs.off(document, 'mouseup', this.boundEvents.end, false);\n  vjs.off(document, 'touchmove', this.boundEvents.move, false);\n  vjs.off(document, 'touchend', this.boundEvents.end, false);\n\n  this.update();\n};\n\nvjs.Slider.prototype.update = function(){\n  // In VolumeBar init we have a setTimeout for update that pops and update to the end of the\n  // execution stack. The player is destroyed before then update will cause an error\n  if (!this.el_) return;\n\n  // If scrubbing, we could use a cached value to make the handle keep up with the user's mouse.\n  // On HTML5 browsers scrubbing is really smooth, but some flash players are slow, so we might want to utilize this later.\n  // var progress =  (this.player_.scrubbing) ? this.player_.getCache().currentTime / this.player_.duration() : this.player_.currentTime() / this.player_.duration();\n\n  var barProgress,\n      progress = this.getPercent(),\n      handle = this.handle,\n      bar = this.bar;\n\n  // Protect against no duration and other division issues\n  if (isNaN(progress)) { progress = 0; }\n\n  barProgress = progress;\n\n  // If there is a handle, we need to account for the handle in our calculation for progress bar\n  // so that it doesn't fall short of or extend past the handle.\n  if (handle) {\n\n    var box = this.el_,\n        boxWidth = box.offsetWidth,\n\n        handleWidth = handle.el().offsetWidth,\n\n        // The width of the handle in percent of the containing box\n        // In IE, widths may not be ready yet causing NaN\n        handlePercent = (handleWidth) ? handleWidth / boxWidth : 0,\n\n        // Get the adjusted size of the box, considering that the handle's center never touches the left or right side.\n        // There is a margin of half the handle's width on both sides.\n        boxAdjustedPercent = 1 - handlePercent,\n\n        // Adjust the progress that we'll use to set widths to the new adjusted box width\n        adjustedProgress = progress * boxAdjustedPercent;\n\n    // The bar does reach the left side, so we need to account for this in the bar's width\n    barProgress = adjustedProgress + (handlePercent / 2);\n\n    // Move the handle from the left based on the adjected progress\n    handle.el().style.left = vjs.round(adjustedProgress * 100, 2) + '%';\n  }\n\n  // Set the new bar width\n  bar.el().style.width = vjs.round(barProgress * 100, 2) + '%';\n};\n\nvjs.Slider.prototype.calculateDistance = function(event){\n  var el, box, boxX, boxY, boxW, boxH, handle, pageX, pageY;\n\n  el = this.el_;\n  box = vjs.findPosition(el);\n  boxW = boxH = el.offsetWidth;\n  handle = this.handle;\n\n  if (this.options_.vertical) {\n    boxY = box.top;\n\n    if (event.changedTouches) {\n      pageY = event.changedTouches[0].pageY;\n    } else {\n      pageY = event.pageY;\n    }\n\n    if (handle) {\n      var handleH = handle.el().offsetHeight;\n      // Adjusted X and Width, so handle doesn't go outside the bar\n      boxY = boxY + (handleH / 2);\n      boxH = boxH - handleH;\n    }\n\n    // Percent that the click is through the adjusted area\n    return Math.max(0, Math.min(1, ((boxY - pageY) + boxH) / boxH));\n\n  } else {\n    boxX = box.left;\n\n    if (event.changedTouches) {\n      pageX = event.changedTouches[0].pageX;\n    } else {\n      pageX = event.pageX;\n    }\n\n    if (handle) {\n      var handleW = handle.el().offsetWidth;\n\n      // Adjusted X and Width, so handle doesn't go outside the bar\n      boxX = boxX + (handleW / 2);\n      boxW = boxW - handleW;\n    }\n\n    // Percent that the click is through the adjusted area\n    return Math.max(0, Math.min(1, (pageX - boxX) / boxW));\n  }\n};\n\nvjs.Slider.prototype.onFocus = function(){\n  vjs.on(document, 'keyup', vjs.bind(this, this.onKeyPress));\n};\n\nvjs.Slider.prototype.onKeyPress = function(event){\n  if (event.which == 37) { // Left Arrow\n    event.preventDefault();\n    this.stepBack();\n  } else if (event.which == 39) { // Right Arrow\n    event.preventDefault();\n    this.stepForward();\n  }\n};\n\nvjs.Slider.prototype.onBlur = function(){\n  vjs.off(document, 'keyup', vjs.bind(this, this.onKeyPress));\n};\n\n/**\n * Listener for click events on slider, used to prevent clicks\n *   from bubbling up to parent elements like button menus.\n * @param  {Object} event Event object\n */\nvjs.Slider.prototype.onClick = function(event){\n  event.stopImmediatePropagation();\n  event.preventDefault();\n};\n\n/**\n * SeekBar Behavior includes play progress bar, and seek handle\n * Needed so it can determine seek position based on handle position/size\n * @param {vjs.Player|Object} player\n * @param {Object=} options\n * @constructor\n */\nvjs.SliderHandle = vjs.Component.extend();\n\n/**\n * Default value of the slider\n *\n * @type {Number}\n * @private\n */\nvjs.SliderHandle.prototype.defaultValue = 0;\n\n/** @inheritDoc */\nvjs.SliderHandle.prototype.createEl = function(type, props) {\n  props = props || {};\n  // Add the slider element class to all sub classes\n  props.className = props.className + ' vjs-slider-handle';\n  props = vjs.obj.merge({\n    innerHTML: '<span class=\"vjs-control-text\">'+this.defaultValue+'</span>'\n  }, props);\n\n  return vjs.Component.prototype.createEl.call(this, 'div', props);\n};\n/* Menu\n================================================================================ */\n/**\n * The Menu component is used to build pop up menus, including subtitle and\n * captions selection menus.\n *\n * @param {vjs.Player|Object} player\n * @param {Object=} options\n * @class\n * @constructor\n */\nvjs.Menu = vjs.Component.extend();\n\n/**\n * Add a menu item to the menu\n * @param {Object|String} component Component or component type to add\n */\nvjs.Menu.prototype.addItem = function(component){\n  this.addChild(component);\n  component.on('click', vjs.bind(this, function(){\n    this.unlockShowing();\n  }));\n};\n\n/** @inheritDoc */\nvjs.Menu.prototype.createEl = function(){\n  var contentElType = this.options().contentElType || 'ul';\n  this.contentEl_ = vjs.createEl(contentElType, {\n    className: 'vjs-menu-content'\n  });\n  var el = vjs.Component.prototype.createEl.call(this, 'div', {\n    append: this.contentEl_,\n    className: 'vjs-menu'\n  });\n  el.appendChild(this.contentEl_);\n\n  // Prevent clicks from bubbling up. Needed for Menu Buttons,\n  // where a click on the parent is significant\n  vjs.on(el, 'click', function(event){\n    event.preventDefault();\n    event.stopImmediatePropagation();\n  });\n\n  return el;\n};\n\n/**\n * The component for a menu item. `<li>`\n *\n * @param {vjs.Player|Object} player\n * @param {Object=} options\n * @class\n * @constructor\n */\nvjs.MenuItem = vjs.Button.extend({\n  /** @constructor */\n  init: function(player, options){\n    vjs.Button.call(this, player, options);\n    this.selected(options['selected']);\n  }\n});\n\n/** @inheritDoc */\nvjs.MenuItem.prototype.createEl = function(type, props){\n  return vjs.Button.prototype.createEl.call(this, 'li', vjs.obj.merge({\n    className: 'vjs-menu-item',\n    innerHTML: this.options_['label']\n  }, props));\n};\n\n/**\n * Handle a click on the menu item, and set it to selected\n */\nvjs.MenuItem.prototype.onClick = function(){\n  this.selected(true);\n};\n\n/**\n * Set this menu item as selected or not\n * @param  {Boolean} selected\n */\nvjs.MenuItem.prototype.selected = function(selected){\n  if (selected) {\n    this.addClass('vjs-selected');\n    this.el_.setAttribute('aria-selected',true);\n  } else {\n    this.removeClass('vjs-selected');\n    this.el_.setAttribute('aria-selected',false);\n  }\n};\n\n\n/**\n * A button class with a popup menu\n * @param {vjs.Player|Object} player\n * @param {Object=} options\n * @constructor\n */\nvjs.MenuButton = vjs.Button.extend({\n  /** @constructor */\n  init: function(player, options){\n    vjs.Button.call(this, player, options);\n\n    this.menu = this.createMenu();\n\n    // Add list to element\n    this.addChild(this.menu);\n\n    // Automatically hide empty menu buttons\n    if (this.items && this.items.length === 0) {\n      this.hide();\n    }\n\n    this.on('keyup', this.onKeyPress);\n    this.el_.setAttribute('aria-haspopup', true);\n    this.el_.setAttribute('role', 'button');\n  }\n});\n\n/**\n * Track the state of the menu button\n * @type {Boolean}\n * @private\n */\nvjs.MenuButton.prototype.buttonPressed_ = false;\n\nvjs.MenuButton.prototype.createMenu = function(){\n  var menu = new vjs.Menu(this.player_);\n\n  // Add a title list item to the top\n  if (this.options().title) {\n    menu.el().appendChild(vjs.createEl('li', {\n      className: 'vjs-menu-title',\n      innerHTML: vjs.capitalize(this.kind_),\n      tabindex: -1\n    }));\n  }\n\n  this.items = this['createItems']();\n\n  if (this.items) {\n    // Add menu items to the menu\n    for (var i = 0; i < this.items.length; i++) {\n      menu.addItem(this.items[i]);\n    }\n  }\n\n  return menu;\n};\n\n/**\n * Create the list of menu items. Specific to each subclass.\n */\nvjs.MenuButton.prototype.createItems = function(){};\n\n/** @inheritDoc */\nvjs.MenuButton.prototype.buildCSSClass = function(){\n  return this.className + ' vjs-menu-button ' + vjs.Button.prototype.buildCSSClass.call(this);\n};\n\n// Focus - Add keyboard functionality to element\n// This function is not needed anymore. Instead, the keyboard functionality is handled by\n// treating the button as triggering a submenu. When the button is pressed, the submenu\n// appears. Pressing the button again makes the submenu disappear.\nvjs.MenuButton.prototype.onFocus = function(){};\n// Can't turn off list display that we turned on with focus, because list would go away.\nvjs.MenuButton.prototype.onBlur = function(){};\n\nvjs.MenuButton.prototype.onClick = function(){\n  // When you click the button it adds focus, which will show the menu indefinitely.\n  // So we'll remove focus when the mouse leaves the button.\n  // Focus is needed for tab navigation.\n  this.one('mouseout', vjs.bind(this, function(){\n    this.menu.unlockShowing();\n    this.el_.blur();\n  }));\n  if (this.buttonPressed_){\n    this.unpressButton();\n  } else {\n    this.pressButton();\n  }\n};\n\nvjs.MenuButton.prototype.onKeyPress = function(event){\n  event.preventDefault();\n\n  // Check for space bar (32) or enter (13) keys\n  if (event.which == 32 || event.which == 13) {\n    if (this.buttonPressed_){\n      this.unpressButton();\n    } else {\n      this.pressButton();\n    }\n  // Check for escape (27) key\n  } else if (event.which == 27){\n    if (this.buttonPressed_){\n      this.unpressButton();\n    }\n  }\n};\n\nvjs.MenuButton.prototype.pressButton = function(){\n  this.buttonPressed_ = true;\n  this.menu.lockShowing();\n  this.el_.setAttribute('aria-pressed', true);\n  if (this.items && this.items.length > 0) {\n    this.items[0].el().focus(); // set the focus to the title of the submenu\n  }\n};\n\nvjs.MenuButton.prototype.unpressButton = function(){\n  this.buttonPressed_ = false;\n  this.menu.unlockShowing();\n  this.el_.setAttribute('aria-pressed', false);\n};\n\n/**\n * An instance of the `vjs.Player` class is created when any of the Video.js setup methods are used to initialize a video.\n *\n * ```js\n * var myPlayer = videojs('example_video_1');\n * ```\n *\n * In the follwing example, the `data-setup` attribute tells the Video.js library to create a player instance when the library is ready.\n *\n * ```html\n * <video id=\"example_video_1\" data-setup='{}' controls>\n *   <source src=\"my-source.mp4\" type=\"video/mp4\">\n * </video>\n * ```\n *\n * After an instance has been created it can be accessed globally using `Video('example_video_1')`.\n *\n * @class\n * @extends vjs.Component\n */\nvjs.Player = vjs.Component.extend({\n\n  /**\n   * player's constructor function\n   *\n   * @constructs\n   * @method init\n   * @param {Element} tag        The original video tag used for configuring options\n   * @param {Object=} options    Player options\n   * @param {Function=} ready    Ready callback function\n   */\n  init: function(tag, options, ready){\n    this.tag = tag; // Store the original tag used to set options\n\n    // Set Options\n    // The options argument overrides options set in the video tag\n    // which overrides globally set options.\n    // This latter part coincides with the load order\n    // (tag must exist before Player)\n    options = vjs.obj.merge(this.getTagSettings(tag), options);\n\n    // Cache for video property values.\n    this.cache_ = {};\n\n    // Set poster\n    this.poster_ = options['poster'];\n    // Set controls\n    this.controls_ = options['controls'];\n    // Original tag settings stored in options\n    // now remove immediately so native controls don't flash.\n    // May be turned back on by HTML5 tech if nativeControlsForTouch is true\n    tag.controls = false;\n\n    // Run base component initializing with new options.\n    // Builds the element through createEl()\n    // Inits and embeds any child components in opts\n    vjs.Component.call(this, this, options, ready);\n\n    // Update controls className. Can't do this when the controls are initially\n    // set because the element doesn't exist yet.\n    if (this.controls()) {\n      this.addClass('vjs-controls-enabled');\n    } else {\n      this.addClass('vjs-controls-disabled');\n    }\n\n    // TODO: Make this smarter. Toggle user state between touching/mousing\n    // using events, since devices can have both touch and mouse events.\n    // if (vjs.TOUCH_ENABLED) {\n    //   this.addClass('vjs-touch-enabled');\n    // }\n\n    // Firstplay event implimentation. Not sold on the event yet.\n    // Could probably just check currentTime==0?\n    this.one('play', function(e){\n      var fpEvent = { type: 'firstplay', target: this.el_ };\n      // Using vjs.trigger so we can check if default was prevented\n      var keepGoing = vjs.trigger(this.el_, fpEvent);\n\n      if (!keepGoing) {\n        e.preventDefault();\n        e.stopPropagation();\n        e.stopImmediatePropagation();\n      }\n    });\n\n    this.on('ended', this.onEnded);\n    this.on('play', this.onPlay);\n    this.on('firstplay', this.onFirstPlay);\n    this.on('pause', this.onPause);\n    this.on('progress', this.onProgress);\n    this.on('durationchange', this.onDurationChange);\n    this.on('error', this.onError);\n    this.on('fullscreenchange', this.onFullscreenChange);\n\n    // Make player easily findable by ID\n    vjs.players[this.id_] = this;\n\n    if (options['plugins']) {\n      vjs.obj.each(options['plugins'], function(key, val){\n        this[key](val);\n      }, this);\n    }\n\n    this.listenForUserActivity();\n  }\n});\n\n/**\n * Player instance options, surfaced using vjs.options\n * vjs.options = vjs.Player.prototype.options_\n * Make changes in vjs.options, not here.\n * All options should use string keys so they avoid\n * renaming by closure compiler\n * @type {Object}\n * @private\n */\nvjs.Player.prototype.options_ = vjs.options;\n\n/**\n * Destroys the video player and does any necessary cleanup\n *\n *     myPlayer.dispose();\n *\n * This is especially helpful if you are dynamically adding and removing videos\n * to/from the DOM.\n */\nvjs.Player.prototype.dispose = function(){\n  this.trigger('dispose');\n  // prevent dispose from being called twice\n  this.off('dispose');\n\n  // Kill reference to this player\n  vjs.players[this.id_] = null;\n  if (this.tag && this.tag['player']) { this.tag['player'] = null; }\n  if (this.el_ && this.el_['player']) { this.el_['player'] = null; }\n\n  // Ensure that tracking progress and time progress will stop and plater deleted\n  this.stopTrackingProgress();\n  this.stopTrackingCurrentTime();\n\n  if (this.tech) { this.tech.dispose(); }\n\n  // Component dispose\n  vjs.Component.prototype.dispose.call(this);\n};\n\nvjs.Player.prototype.getTagSettings = function(tag){\n  var options = {\n    'sources': [],\n    'tracks': []\n  };\n\n  vjs.obj.merge(options, vjs.getAttributeValues(tag));\n\n  // Get tag children settings\n  if (tag.hasChildNodes()) {\n    var children, child, childName, i, j;\n\n    children = tag.childNodes;\n\n    for (i=0,j=children.length; i<j; i++) {\n      child = children[i];\n      // Change case needed: http://ejohn.org/blog/nodename-case-sensitivity/\n      childName = child.nodeName.toLowerCase();\n      if (childName === 'source') {\n        options['sources'].push(vjs.getAttributeValues(child));\n      } else if (childName === 'track') {\n        options['tracks'].push(vjs.getAttributeValues(child));\n      }\n    }\n  }\n\n  return options;\n};\n\nvjs.Player.prototype.createEl = function(){\n  var el = this.el_ = vjs.Component.prototype.createEl.call(this, 'div');\n  var tag = this.tag;\n\n  // Remove width/height attrs from tag so CSS can make it 100% width/height\n  tag.removeAttribute('width');\n  tag.removeAttribute('height');\n  // Empty video tag tracks so the built-in player doesn't use them also.\n  // This may not be fast enough to stop HTML5 browsers from reading the tags\n  // so we'll need to turn off any default tracks if we're manually doing\n  // captions and subtitles. videoElement.textTracks\n  if (tag.hasChildNodes()) {\n    var nodes, nodesLength, i, node, nodeName, removeNodes;\n\n    nodes = tag.childNodes;\n    nodesLength = nodes.length;\n    removeNodes = [];\n\n    while (nodesLength--) {\n      node = nodes[nodesLength];\n      nodeName = node.nodeName.toLowerCase();\n      if (nodeName === 'track') {\n        removeNodes.push(node);\n      }\n    }\n\n    for (i=0; i<removeNodes.length; i++) {\n      tag.removeChild(removeNodes[i]);\n    }\n  }\n\n  // Make sure tag ID exists\n  tag.id = tag.id || 'vjs_video_' + vjs.guid++;\n\n  // Give video tag ID and class to player div\n  // ID will now reference player box, not the video tag\n  el.id = tag.id;\n  el.className = tag.className;\n\n  // Update tag id/class for use as HTML5 playback tech\n  // Might think we should do this after embedding in container so .vjs-tech class\n  // doesn't flash 100% width/height, but class only applies with .video-js parent\n  tag.id += '_html5_api';\n  tag.className = 'vjs-tech';\n\n  // Make player findable on elements\n  tag['player'] = el['player'] = this;\n  // Default state of video is paused\n  this.addClass('vjs-paused');\n\n  // Make box use width/height of tag, or rely on default implementation\n  // Enforce with CSS since width/height attrs don't work on divs\n  this.width(this.options_['width'], true); // (true) Skip resize listener on load\n  this.height(this.options_['height'], true);\n\n  // Wrap video tag in div (el/box) container\n  if (tag.parentNode) {\n    tag.parentNode.insertBefore(el, tag);\n  }\n  vjs.insertFirst(tag, el); // Breaks iPhone, fixed in HTML5 setup.\n\n  return el;\n};\n\n// /* Media Technology (tech)\n// ================================================================================ */\n// Load/Create an instance of playback technlogy including element and API methods\n// And append playback element in player div.\nvjs.Player.prototype.loadTech = function(techName, source){\n\n  // Pause and remove current playback technology\n  if (this.tech) {\n    this.unloadTech();\n\n  // if this is the first time loading, HTML5 tag will exist but won't be initialized\n  // so we need to remove it if we're not loading HTML5\n  } else if (techName !== 'Html5' && this.tag) {\n    vjs.Html5.disposeMediaElement(this.tag);\n    this.tag = null;\n  }\n\n  this.techName = techName;\n\n  // Turn off API access because we're loading a new tech that might load asynchronously\n  this.isReady_ = false;\n\n  var techReady = function(){\n    this.player_.triggerReady();\n\n    // Manually track progress in cases where the browser/flash player doesn't report it.\n    if (!this.features['progressEvents']) {\n      this.player_.manualProgressOn();\n    }\n\n    // Manually track timeudpates in cases where the browser/flash player doesn't report it.\n    if (!this.features['timeupdateEvents']) {\n      this.player_.manualTimeUpdatesOn();\n    }\n  };\n\n  // Grab tech-specific options from player options and add source and parent element to use.\n  var techOptions = vjs.obj.merge({ 'source': source, 'parentEl': this.el_ }, this.options_[techName.toLowerCase()]);\n\n  if (source) {\n    if (source.src == this.cache_.src && this.cache_.currentTime > 0) {\n      techOptions['startTime'] = this.cache_.currentTime;\n    }\n\n    this.cache_.src = source.src;\n  }\n\n  // Initialize tech instance\n  this.tech = new window['videojs'][techName](this, techOptions);\n\n  this.tech.ready(techReady);\n};\n\nvjs.Player.prototype.unloadTech = function(){\n  this.isReady_ = false;\n  this.tech.dispose();\n\n  // Turn off any manual progress or timeupdate tracking\n  if (this.manualProgress) { this.manualProgressOff(); }\n\n  if (this.manualTimeUpdates) { this.manualTimeUpdatesOff(); }\n\n  this.tech = false;\n};\n\n// There's many issues around changing the size of a Flash (or other plugin) object.\n// First is a plugin reload issue in Firefox that has been around for 11 years: https://bugzilla.mozilla.org/show_bug.cgi?id=90268\n// Then with the new fullscreen API, Mozilla and webkit browsers will reload the flash object after going to fullscreen.\n// To get around this, we're unloading the tech, caching source and currentTime values, and reloading the tech once the plugin is resized.\n// reloadTech: function(betweenFn){\n//   vjs.log('unloadingTech')\n//   this.unloadTech();\n//   vjs.log('unloadedTech')\n//   if (betweenFn) { betweenFn.call(); }\n//   vjs.log('LoadingTech')\n//   this.loadTech(this.techName, { src: this.cache_.src })\n//   vjs.log('loadedTech')\n// },\n\n/* Fallbacks for unsupported event types\n================================================================================ */\n// Manually trigger progress events based on changes to the buffered amount\n// Many flash players and older HTML5 browsers don't send progress or progress-like events\nvjs.Player.prototype.manualProgressOn = function(){\n  this.manualProgress = true;\n\n  // Trigger progress watching when a source begins loading\n  this.trackProgress();\n\n  // Watch for a native progress event call on the tech element\n  // In HTML5, some older versions don't support the progress event\n  // So we're assuming they don't, and turning off manual progress if they do.\n  // As opposed to doing user agent detection\n  this.tech.one('progress', function(){\n\n    // Update known progress support for this playback technology\n    this.features['progressEvents'] = true;\n\n    // Turn off manual progress tracking\n    this.player_.manualProgressOff();\n  });\n};\n\nvjs.Player.prototype.manualProgressOff = function(){\n  this.manualProgress = false;\n  this.stopTrackingProgress();\n};\n\nvjs.Player.prototype.trackProgress = function(){\n\n  this.progressInterval = setInterval(vjs.bind(this, function(){\n    // Don't trigger unless buffered amount is greater than last time\n    // log(this.cache_.bufferEnd, this.buffered().end(0), this.duration())\n    /* TODO: update for multiple buffered regions */\n    if (this.cache_.bufferEnd < this.buffered().end(0)) {\n      this.trigger('progress');\n    } else if (this.bufferedPercent() == 1) {\n      this.stopTrackingProgress();\n      this.trigger('progress'); // Last update\n    }\n  }), 500);\n};\nvjs.Player.prototype.stopTrackingProgress = function(){ clearInterval(this.progressInterval); };\n\n/*! Time Tracking -------------------------------------------------------------- */\nvjs.Player.prototype.manualTimeUpdatesOn = function(){\n  this.manualTimeUpdates = true;\n\n  this.on('play', this.trackCurrentTime);\n  this.on('pause', this.stopTrackingCurrentTime);\n  // timeupdate is also called by .currentTime whenever current time is set\n\n  // Watch for native timeupdate event\n  this.tech.one('timeupdate', function(){\n    // Update known progress support for this playback technology\n    this.features['timeupdateEvents'] = true;\n    // Turn off manual progress tracking\n    this.player_.manualTimeUpdatesOff();\n  });\n};\n\nvjs.Player.prototype.manualTimeUpdatesOff = function(){\n  this.manualTimeUpdates = false;\n  this.stopTrackingCurrentTime();\n  this.off('play', this.trackCurrentTime);\n  this.off('pause', this.stopTrackingCurrentTime);\n};\n\nvjs.Player.prototype.trackCurrentTime = function(){\n  if (this.currentTimeInterval) { this.stopTrackingCurrentTime(); }\n  this.currentTimeInterval = setInterval(vjs.bind(this, function(){\n    this.trigger('timeupdate');\n  }), 250); // 42 = 24 fps // 250 is what Webkit uses // FF uses 15\n};\n\n// Turn off play progress tracking (when paused or dragging)\nvjs.Player.prototype.stopTrackingCurrentTime = function(){ clearInterval(this.currentTimeInterval); };\n\n// /* Player event handlers (how the player reacts to certain events)\n// ================================================================================ */\n\n/**\n * Fired when the user agent begins looking for media data\n * @event loadstart\n */\nvjs.Player.prototype.onLoadStart;\n\n/**\n * Fired when the player has initial duration and dimension information\n * @event loadedmetadata\n */\nvjs.Player.prototype.onLoadedMetaData;\n\n/**\n * Fired when the player has downloaded data at the current playback position\n * @event loadeddata\n */\nvjs.Player.prototype.onLoadedData;\n\n/**\n * Fired when the player has finished downloading the source data\n * @event loadedalldata\n */\nvjs.Player.prototype.onLoadedAllData;\n\n/**\n * Fired whenever the media begins or resumes playback\n * @event play\n */\nvjs.Player.prototype.onPlay = function(){\n  vjs.removeClass(this.el_, 'vjs-paused');\n  vjs.addClass(this.el_, 'vjs-playing');\n};\n\n/**\n * Fired the first time a video is played\n *\n * Not part of the HLS spec, and we're not sure if this is the best\n * implementation yet, so use sparingly. If you don't have a reason to\n * prevent playback, use `myPlayer.one('play');` instead.\n *\n * @event firstplay\n */\nvjs.Player.prototype.onFirstPlay = function(){\n    //If the first starttime attribute is specified\n    //then we will start at the given offset in seconds\n    if(this.options_['starttime']){\n      this.currentTime(this.options_['starttime']);\n    }\n\n    this.addClass('vjs-has-started');\n};\n\n/**\n * Fired whenever the media has been paused\n * @event pause\n */\nvjs.Player.prototype.onPause = function(){\n  vjs.removeClass(this.el_, 'vjs-playing');\n  vjs.addClass(this.el_, 'vjs-paused');\n};\n\n/**\n * Fired when the current playback position has changed\n *\n * During playback this is fired every 15-250 milliseconds, depnding on the\n * playback technology in use.\n * @event timeupdate\n */\nvjs.Player.prototype.onTimeUpdate;\n\n/**\n * Fired while the user agent is downloading media data\n * @event progress\n */\nvjs.Player.prototype.onProgress = function(){\n  // Add custom event for when source is finished downloading.\n  if (this.bufferedPercent() == 1) {\n    this.trigger('loadedalldata');\n  }\n};\n\n/**\n * Fired when the end of the media resource is reached (currentTime == duration)\n * @event ended\n */\nvjs.Player.prototype.onEnded = function(){\n  if (this.options_['loop']) {\n    this.currentTime(0);\n    this.play();\n  }\n};\n\n/**\n * Fired when the duration of the media resource is first known or changed\n * @event durationchange\n */\nvjs.Player.prototype.onDurationChange = function(){\n  // Allows for cacheing value instead of asking player each time.\n  this.duration(this.techGet('duration'));\n};\n\n/**\n * Fired when the volume changes\n * @event volumechange\n */\nvjs.Player.prototype.onVolumeChange;\n\n/**\n * Fired when the player switches in or out of fullscreen mode\n * @event fullscreenchange\n */\nvjs.Player.prototype.onFullscreenChange = function() {\n  if (this.isFullScreen) {\n    this.addClass('vjs-fullscreen');\n  } else {\n    this.removeClass('vjs-fullscreen');\n  }\n};\n\n/**\n * Fired when there is an error in playback\n * @event error\n */\nvjs.Player.prototype.onError = function(e) {\n  vjs.log('Video Error', e);\n};\n\n// /* Player API\n// ================================================================================ */\n\n/**\n * Object for cached values.\n * @private\n */\nvjs.Player.prototype.cache_;\n\nvjs.Player.prototype.getCache = function(){\n  return this.cache_;\n};\n\n// Pass values to the playback tech\nvjs.Player.prototype.techCall = function(method, arg){\n  // If it's not ready yet, call method when it is\n  if (this.tech && !this.tech.isReady_) {\n    this.tech.ready(function(){\n      this[method](arg);\n    });\n\n  // Otherwise call method now\n  } else {\n    try {\n      this.tech[method](arg);\n    } catch(e) {\n      vjs.log(e);\n      throw e;\n    }\n  }\n};\n\n// Get calls can't wait for the tech, and sometimes don't need to.\nvjs.Player.prototype.techGet = function(method){\n\n  if (this.tech && this.tech.isReady_) {\n\n    // Flash likes to die and reload when you hide or reposition it.\n    // In these cases the object methods go away and we get errors.\n    // When that happens we'll catch the errors and inform tech that it's not ready any more.\n    try {\n      return this.tech[method]();\n    } catch(e) {\n      // When building additional tech libs, an expected method may not be defined yet\n      if (this.tech[method] === undefined) {\n        vjs.log('Video.js: ' + method + ' method not defined for '+this.techName+' playback technology.', e);\n      } else {\n        // When a method isn't available on the object it throws a TypeError\n        if (e.name == 'TypeError') {\n          vjs.log('Video.js: ' + method + ' unavailable on '+this.techName+' playback technology element.', e);\n          this.tech.isReady_ = false;\n        } else {\n          vjs.log(e);\n        }\n      }\n      throw e;\n    }\n  }\n\n  return;\n};\n\n/**\n * start media playback\n *\n *     myPlayer.play();\n *\n * @return {vjs.Player} self\n */\nvjs.Player.prototype.play = function(){\n  this.techCall('play');\n  return this;\n};\n\n/**\n * Pause the video playback\n *\n *     myPlayer.pause();\n *\n * @return {vjs.Player} self\n */\nvjs.Player.prototype.pause = function(){\n  this.techCall('pause');\n  return this;\n};\n\n/**\n * Check if the player is paused\n *\n *     var isPaused = myPlayer.paused();\n *     var isPlaying = !myPlayer.paused();\n *\n * @return {Boolean} false if the media is currently playing, or true otherwise\n */\nvjs.Player.prototype.paused = function(){\n  // The initial state of paused should be true (in Safari it's actually false)\n  return (this.techGet('paused') === false) ? false : true;\n};\n\n/**\n * Get or set the current time (in seconds)\n *\n *     // get\n *     var whereYouAt = myPlayer.currentTime();\n *\n *     // set\n *     myPlayer.currentTime(120); // 2 minutes into the video\n *\n * @param  {Number|String=} seconds The time to seek to\n * @return {Number}        The time in seconds, when not setting\n * @return {vjs.Player}    self, when the current time is set\n */\nvjs.Player.prototype.currentTime = function(seconds){\n  if (seconds !== undefined) {\n\n    // cache the last set value for smoother scrubbing\n    this.cache_.lastSetCurrentTime = seconds;\n\n    this.techCall('setCurrentTime', seconds);\n\n    // improve the accuracy of manual timeupdates\n    if (this.manualTimeUpdates) { this.trigger('timeupdate'); }\n\n    return this;\n  }\n\n  // cache last currentTime and return\n  // default to 0 seconds\n  return this.cache_.currentTime = (this.techGet('currentTime') || 0);\n};\n\n/**\n * Get the length in time of the video in seconds\n *\n *     var lengthOfVideo = myPlayer.duration();\n *\n * **NOTE**: The video must have started loading before the duration can be\n * known, and in the case of Flash, may not be known until the video starts\n * playing.\n *\n * @return {Number} The duration of the video in seconds\n */\nvjs.Player.prototype.duration = function(seconds){\n  if (seconds !== undefined) {\n\n    // cache the last set value for optimiized scrubbing (esp. Flash)\n    this.cache_.duration = parseFloat(seconds);\n\n    return this;\n  }\n\n  if (this.cache_.duration === undefined) {\n    this.onDurationChange();\n  }\n\n  return this.cache_.duration;\n};\n\n// Calculates how much time is left. Not in spec, but useful.\nvjs.Player.prototype.remainingTime = function(){\n  return this.duration() - this.currentTime();\n};\n\n// http://dev.w3.org/html5/spec/video.html#dom-media-buffered\n// Buffered returns a timerange object.\n// Kind of like an array of portions of the video that have been downloaded.\n// So far no browsers return more than one range (portion)\n\n/**\n * Get a TimeRange object with the times of the video that have been downloaded\n *\n * If you just want the percent of the video that's been downloaded,\n * use bufferedPercent.\n *\n *     // Number of different ranges of time have been buffered. Usually 1.\n *     numberOfRanges = bufferedTimeRange.length,\n *\n *     // Time in seconds when the first range starts. Usually 0.\n *     firstRangeStart = bufferedTimeRange.start(0),\n *\n *     // Time in seconds when the first range ends\n *     firstRangeEnd = bufferedTimeRange.end(0),\n *\n *     // Length in seconds of the first time range\n *     firstRangeLength = firstRangeEnd - firstRangeStart;\n *\n * @return {Object} A mock TimeRange object (following HTML spec)\n */\nvjs.Player.prototype.buffered = function(){\n  var buffered = this.techGet('buffered'),\n      start = 0,\n      buflast = buffered.length - 1,\n      // Default end to 0 and store in values\n      end = this.cache_.bufferEnd = this.cache_.bufferEnd || 0;\n\n  if (buffered && buflast >= 0 && buffered.end(buflast) !== end) {\n    end = buffered.end(buflast);\n    // Storing values allows them be overridden by setBufferedFromProgress\n    this.cache_.bufferEnd = end;\n  }\n\n  return vjs.createTimeRange(start, end);\n};\n\n/**\n * Get the percent (as a decimal) of the video that's been downloaded\n *\n *     var howMuchIsDownloaded = myPlayer.bufferedPercent();\n *\n * 0 means none, 1 means all.\n * (This method isn't in the HTML5 spec, but it's very convenient)\n *\n * @return {Number} A decimal between 0 and 1 representing the percent\n */\nvjs.Player.prototype.bufferedPercent = function(){\n  return (this.duration()) ? this.buffered().end(0) / this.duration() : 0;\n};\n\n/**\n * Get or set the current volume of the media\n *\n *     // get\n *     var howLoudIsIt = myPlayer.volume();\n *\n *     // set\n *     myPlayer.volume(0.5); // Set volume to half\n *\n * 0 is off (muted), 1.0 is all the way up, 0.5 is half way.\n *\n * @param  {Number} percentAsDecimal The new volume as a decimal percent\n * @return {Number}                  The current volume, when getting\n * @return {vjs.Player}              self, when setting\n */\nvjs.Player.prototype.volume = function(percentAsDecimal){\n  var vol;\n\n  if (percentAsDecimal !== undefined) {\n    vol = Math.max(0, Math.min(1, parseFloat(percentAsDecimal))); // Force value to between 0 and 1\n    this.cache_.volume = vol;\n    this.techCall('setVolume', vol);\n    vjs.setLocalStorage('volume', vol);\n    return this;\n  }\n\n  // Default to 1 when returning current volume.\n  vol = parseFloat(this.techGet('volume'));\n  return (isNaN(vol)) ? 1 : vol;\n};\n\n\n/**\n * Get the current muted state, or turn mute on or off\n *\n *     // get\n *     var isVolumeMuted = myPlayer.muted();\n *\n *     // set\n *     myPlayer.muted(true); // mute the volume\n *\n * @param  {Boolean=} muted True to mute, false to unmute\n * @return {Boolean} True if mute is on, false if not, when getting\n * @return {vjs.Player} self, when setting mute\n */\nvjs.Player.prototype.muted = function(muted){\n  if (muted !== undefined) {\n    this.techCall('setMuted', muted);\n    return this;\n  }\n  return this.techGet('muted') || false; // Default to false\n};\n\n// Check if current tech can support native fullscreen (e.g. with built in controls lik iOS, so not our flash swf)\nvjs.Player.prototype.supportsFullScreen = function(){ return this.techGet('supportsFullScreen') || false; };\n\n/**\n * Increase the size of the video to full screen\n *\n *     myPlayer.requestFullScreen();\n *\n * In some browsers, full screen is not supported natively, so it enters\n * \"full window mode\", where the video fills the browser window.\n * In browsers and devices that support native full screen, sometimes the\n * browser's default controls will be shown, and not the Video.js custom skin.\n * This includes most mobile devices (iOS, Android) and older versions of\n * Safari.\n *\n * @return {vjs.Player} self\n */\nvjs.Player.prototype.requestFullScreen = function(){\n  var requestFullScreen = vjs.support.requestFullScreen;\n  this.isFullScreen = true;\n\n  if (requestFullScreen) {\n    // the browser supports going fullscreen at the element level so we can\n    // take the controls fullscreen as well as the video\n\n    // Trigger fullscreenchange event after change\n    // We have to specifically add this each time, and remove\n    // when cancelling fullscreen. Otherwise if there's multiple\n    // players on a page, they would all be reacting to the same fullscreen\n    // events\n    vjs.on(document, requestFullScreen.eventName, vjs.bind(this, function(e){\n      this.isFullScreen = document[requestFullScreen.isFullScreen];\n\n      // If cancelling fullscreen, remove event listener.\n      if (this.isFullScreen === false) {\n        vjs.off(document, requestFullScreen.eventName, arguments.callee);\n      }\n\n      this.trigger('fullscreenchange');\n    }));\n\n    this.el_[requestFullScreen.requestFn]();\n\n  } else if (this.tech.supportsFullScreen()) {\n    // we can't take the video.js controls fullscreen but we can go fullscreen\n    // with native controls\n    this.techCall('enterFullScreen');\n  } else {\n    // fullscreen isn't supported so we'll just stretch the video element to\n    // fill the viewport\n    this.enterFullWindow();\n    this.trigger('fullscreenchange');\n  }\n\n  return this;\n};\n\n/**\n * Return the video to its normal size after having been in full screen mode\n *\n *     myPlayer.cancelFullScreen();\n *\n * @return {vjs.Player} self\n */\nvjs.Player.prototype.cancelFullScreen = function(){\n  var requestFullScreen = vjs.support.requestFullScreen;\n  this.isFullScreen = false;\n\n  // Check for browser element fullscreen support\n  if (requestFullScreen) {\n    document[requestFullScreen.cancelFn]();\n  } else if (this.tech.supportsFullScreen()) {\n   this.techCall('exitFullScreen');\n  } else {\n   this.exitFullWindow();\n   this.trigger('fullscreenchange');\n  }\n\n  return this;\n};\n\n// When fullscreen isn't supported we can stretch the video container to as wide as the browser will let us.\nvjs.Player.prototype.enterFullWindow = function(){\n  this.isFullWindow = true;\n\n  // Storing original doc overflow value to return to when fullscreen is off\n  this.docOrigOverflow = document.documentElement.style.overflow;\n\n  // Add listener for esc key to exit fullscreen\n  vjs.on(document, 'keydown', vjs.bind(this, this.fullWindowOnEscKey));\n\n  // Hide any scroll bars\n  document.documentElement.style.overflow = 'hidden';\n\n  // Apply fullscreen styles\n  vjs.addClass(document.body, 'vjs-full-window');\n\n  this.trigger('enterFullWindow');\n};\nvjs.Player.prototype.fullWindowOnEscKey = function(event){\n  if (event.keyCode === 27) {\n    if (this.isFullScreen === true) {\n      this.cancelFullScreen();\n    } else {\n      this.exitFullWindow();\n    }\n  }\n};\n\nvjs.Player.prototype.exitFullWindow = function(){\n  this.isFullWindow = false;\n  vjs.off(document, 'keydown', this.fullWindowOnEscKey);\n\n  // Unhide scroll bars.\n  document.documentElement.style.overflow = this.docOrigOverflow;\n\n  // Remove fullscreen styles\n  vjs.removeClass(document.body, 'vjs-full-window');\n\n  // Resize the box, controller, and poster to original sizes\n  // this.positionAll();\n  this.trigger('exitFullWindow');\n};\n\nvjs.Player.prototype.selectSource = function(sources){\n\n  // Loop through each playback technology in the options order\n  for (var i=0,j=this.options_['techOrder'];i<j.length;i++) {\n    var techName = vjs.capitalize(j[i]),\n        tech = window['videojs'][techName];\n\n    // Check if the browser supports this technology\n    if (tech.isSupported()) {\n      // Loop through each source object\n      for (var a=0,b=sources;a<b.length;a++) {\n        var source = b[a];\n\n        // Check if source can be played with this technology\n        if (tech['canPlaySource'](source)) {\n          return { source: source, tech: techName };\n        }\n      }\n    }\n  }\n\n  return false;\n};\n\n/**\n * The source function updates the video source\n *\n * There are three types of variables you can pass as the argument.\n *\n * **URL String**: A URL to the the video file. Use this method if you are sure\n * the current playback technology (HTML5/Flash) can support the source you\n * provide. Currently only MP4 files can be used in both HTML5 and Flash.\n *\n *     myPlayer.src(\"http://www.example.com/path/to/video.mp4\");\n *\n * **Source Object (or element):** A javascript object containing information\n * about the source file. Use this method if you want the player to determine if\n * it can support the file using the type information.\n *\n *     myPlayer.src({ type: \"video/mp4\", src: \"http://www.example.com/path/to/video.mp4\" });\n *\n * **Array of Source Objects:** To provide multiple versions of the source so\n * that it can be played using HTML5 across browsers you can use an array of\n * source objects. Video.js will detect which version is supported and load that\n * file.\n *\n *     myPlayer.src([\n *       { type: \"video/mp4\", src: \"http://www.example.com/path/to/video.mp4\" },\n *       { type: \"video/webm\", src: \"http://www.example.com/path/to/video.webm\" },\n *       { type: \"video/ogg\", src: \"http://www.example.com/path/to/video.ogv\" }\n *     ]);\n *\n * @param  {String|Object|Array=} source The source URL, object, or array of sources\n * @return {vjs.Player} self\n */\nvjs.Player.prototype.src = function(source){\n  // Case: Array of source objects to choose from and pick the best to play\n  if (source instanceof Array) {\n\n    var sourceTech = this.selectSource(source),\n        techName;\n\n    if (sourceTech) {\n        source = sourceTech.source;\n        techName = sourceTech.tech;\n\n      // If this technology is already loaded, set source\n      if (techName == this.techName) {\n        this.src(source); // Passing the source object\n      // Otherwise load this technology with chosen source\n      } else {\n        this.loadTech(techName, source);\n      }\n    } else {\n      this.el_.appendChild(vjs.createEl('p', {\n        innerHTML: this.options()['notSupportedMessage']\n      }));\n    }\n\n  // Case: Source object { src: '', type: '' ... }\n  } else if (source instanceof Object) {\n\n    if (window['videojs'][this.techName]['canPlaySource'](source)) {\n      this.src(source.src);\n    } else {\n      // Send through tech loop to check for a compatible technology.\n      this.src([source]);\n    }\n\n  // Case: URL String (http://myvideo...)\n  } else {\n    // Cache for getting last set source\n    this.cache_.src = source;\n\n    if (!this.isReady_) {\n      this.ready(function(){\n        this.src(source);\n      });\n    } else {\n      this.techCall('src', source);\n      if (this.options_['preload'] == 'auto') {\n        this.load();\n      }\n      if (this.options_['autoplay']) {\n        this.play();\n      }\n    }\n  }\n  return this;\n};\n\n// Begin loading the src data\n// http://dev.w3.org/html5/spec/video.html#dom-media-load\nvjs.Player.prototype.load = function(){\n  this.techCall('load');\n  return this;\n};\n\n// http://dev.w3.org/html5/spec/video.html#dom-media-currentsrc\nvjs.Player.prototype.currentSrc = function(){\n  return this.techGet('currentSrc') || this.cache_.src || '';\n};\n\n// Attributes/Options\nvjs.Player.prototype.preload = function(value){\n  if (value !== undefined) {\n    this.techCall('setPreload', value);\n    this.options_['preload'] = value;\n    return this;\n  }\n  return this.techGet('preload');\n};\nvjs.Player.prototype.autoplay = function(value){\n  if (value !== undefined) {\n    this.techCall('setAutoplay', value);\n    this.options_['autoplay'] = value;\n    return this;\n  }\n  return this.techGet('autoplay', value);\n};\nvjs.Player.prototype.loop = function(value){\n  if (value !== undefined) {\n    this.techCall('setLoop', value);\n    this.options_['loop'] = value;\n    return this;\n  }\n  return this.techGet('loop');\n};\n\n/**\n * the url of the poster image source\n * @type {String}\n * @private\n */\nvjs.Player.prototype.poster_;\n\n/**\n * get or set the poster image source url\n *\n * ##### EXAMPLE:\n *\n *     // getting\n *     var currentPoster = myPlayer.poster();\n *\n *     // setting\n *     myPlayer.poster('http://example.com/myImage.jpg');\n *\n * @param  {String=} [src] Poster image source URL\n * @return {String} poster URL when getting\n * @return {vjs.Player} self when setting\n */\nvjs.Player.prototype.poster = function(src){\n  if (src !== undefined) {\n    this.poster_ = src;\n    return this;\n  }\n  return this.poster_;\n};\n\n/**\n * Whether or not the controls are showing\n * @type {Boolean}\n * @private\n */\nvjs.Player.prototype.controls_;\n\n/**\n * Get or set whether or not the controls are showing.\n * @param  {Boolean} controls Set controls to showing or not\n * @return {Boolean}    Controls are showing\n */\nvjs.Player.prototype.controls = function(bool){\n  if (bool !== undefined) {\n    bool = !!bool; // force boolean\n    // Don't trigger a change event unless it actually changed\n    if (this.controls_ !== bool) {\n      this.controls_ = bool;\n      if (bool) {\n        this.removeClass('vjs-controls-disabled');\n        this.addClass('vjs-controls-enabled');\n        this.trigger('controlsenabled');\n      } else {\n        this.removeClass('vjs-controls-enabled');\n        this.addClass('vjs-controls-disabled');\n        this.trigger('controlsdisabled');\n      }\n    }\n    return this;\n  }\n  return this.controls_;\n};\n\nvjs.Player.prototype.usingNativeControls_;\n\n/**\n * Toggle native controls on/off. Native controls are the controls built into\n * devices (e.g. default iPhone controls), Flash, or other techs\n * (e.g. Vimeo Controls)\n *\n * **This should only be set by the current tech, because only the tech knows\n * if it can support native controls**\n *\n * @param  {Boolean} bool    True signals that native controls are on\n * @return {vjs.Player}      Returns the player\n * @private\n */\nvjs.Player.prototype.usingNativeControls = function(bool){\n  if (bool !== undefined) {\n    bool = !!bool; // force boolean\n    // Don't trigger a change event unless it actually changed\n    if (this.usingNativeControls_ !== bool) {\n      this.usingNativeControls_ = bool;\n      if (bool) {\n        this.addClass('vjs-using-native-controls');\n\n        /**\n         * player is using the native device controls\n         *\n         * @event usingnativecontrols\n         * @memberof vjs.Player\n         * @instance\n         * @private\n         */\n        this.trigger('usingnativecontrols');\n      } else {\n        this.removeClass('vjs-using-native-controls');\n\n        /**\n         * player is using the custom HTML controls\n         *\n         * @event usingcustomcontrols\n         * @memberof vjs.Player\n         * @instance\n         * @private\n         */\n        this.trigger('usingcustomcontrols');\n      }\n    }\n    return this;\n  }\n  return this.usingNativeControls_;\n};\n\nvjs.Player.prototype.error = function(){ return this.techGet('error'); };\nvjs.Player.prototype.ended = function(){ return this.techGet('ended'); };\nvjs.Player.prototype.seeking = function(){ return this.techGet('seeking'); };\n\n// When the player is first initialized, trigger activity so components\n// like the control bar show themselves if needed\nvjs.Player.prototype.userActivity_ = true;\nvjs.Player.prototype.reportUserActivity = function(event){\n  this.userActivity_ = true;\n};\n\nvjs.Player.prototype.userActive_ = true;\nvjs.Player.prototype.userActive = function(bool){\n  if (bool !== undefined) {\n    bool = !!bool;\n    if (bool !== this.userActive_) {\n      this.userActive_ = bool;\n      if (bool) {\n        // If the user was inactive and is now active we want to reset the\n        // inactivity timer\n        this.userActivity_ = true;\n        this.removeClass('vjs-user-inactive');\n        this.addClass('vjs-user-active');\n        this.trigger('useractive');\n      } else {\n        // We're switching the state to inactive manually, so erase any other\n        // activity\n        this.userActivity_ = false;\n\n        // Chrome/Safari/IE have bugs where when you change the cursor it can\n        // trigger a mousemove event. This causes an issue when you're hiding\n        // the cursor when the user is inactive, and a mousemove signals user\n        // activity. Making it impossible to go into inactive mode. Specifically\n        // this happens in fullscreen when we really need to hide the cursor.\n        //\n        // When this gets resolved in ALL browsers it can be removed\n        // https://code.google.com/p/chromium/issues/detail?id=103041\n        this.tech.one('mousemove', function(e){\n          e.stopPropagation();\n          e.preventDefault();\n        });\n        this.removeClass('vjs-user-active');\n        this.addClass('vjs-user-inactive');\n        this.trigger('userinactive');\n      }\n    }\n    return this;\n  }\n  return this.userActive_;\n};\n\nvjs.Player.prototype.listenForUserActivity = function(){\n  var onMouseActivity, onMouseDown, mouseInProgress, onMouseUp,\n      activityCheck, inactivityTimeout;\n\n  onMouseActivity = this.reportUserActivity;\n\n  onMouseDown = function() {\n    onMouseActivity();\n    // For as long as the they are touching the device or have their mouse down,\n    // we consider them active even if they're not moving their finger or mouse.\n    // So we want to continue to update that they are active\n    clearInterval(mouseInProgress);\n    // Setting userActivity=true now and setting the interval to the same time\n    // as the activityCheck interval (250) should ensure we never miss the\n    // next activityCheck\n    mouseInProgress = setInterval(vjs.bind(this, onMouseActivity), 250);\n  };\n\n  onMouseUp = function(event) {\n    onMouseActivity();\n    // Stop the interval that maintains activity if the mouse/touch is down\n    clearInterval(mouseInProgress);\n  };\n\n  // Any mouse movement will be considered user activity\n  this.on('mousedown', onMouseDown);\n  this.on('mousemove', onMouseActivity);\n  this.on('mouseup', onMouseUp);\n\n  // Listen for keyboard navigation\n  // Shouldn't need to use inProgress interval because of key repeat\n  this.on('keydown', onMouseActivity);\n  this.on('keyup', onMouseActivity);\n\n  // Consider any touch events that bubble up to be activity\n  // Certain touches on the tech will be blocked from bubbling because they\n  // toggle controls\n  this.on('touchstart', onMouseDown);\n  this.on('touchmove', onMouseActivity);\n  this.on('touchend', onMouseUp);\n  this.on('touchcancel', onMouseUp);\n\n  // Run an interval every 250 milliseconds instead of stuffing everything into\n  // the mousemove/touchmove function itself, to prevent performance degradation.\n  // `this.reportUserActivity` simply sets this.userActivity_ to true, which\n  // then gets picked up by this loop\n  // http://ejohn.org/blog/learning-from-twitter/\n  activityCheck = setInterval(vjs.bind(this, function() {\n    // Check to see if mouse/touch activity has happened\n    if (this.userActivity_) {\n      // Reset the activity tracker\n      this.userActivity_ = false;\n\n      // If the user state was inactive, set the state to active\n      this.userActive(true);\n\n      // Clear any existing inactivity timeout to start the timer over\n      clearTimeout(inactivityTimeout);\n\n      // In X seconds, if no more activity has occurred the user will be\n      // considered inactive\n      inactivityTimeout = setTimeout(vjs.bind(this, function() {\n        // Protect against the case where the inactivityTimeout can trigger just\n        // before the next user activity is picked up by the activityCheck loop\n        // causing a flicker\n        if (!this.userActivity_) {\n          this.userActive(false);\n        }\n      }), 2000);\n    }\n  }), 250);\n\n  // Clean up the intervals when we kill the player\n  this.on('dispose', function(){\n    clearInterval(activityCheck);\n    clearTimeout(inactivityTimeout);\n  });\n};\n\n// Methods to add support for\n// networkState: function(){ return this.techCall('networkState'); },\n// readyState: function(){ return this.techCall('readyState'); },\n// seeking: function(){ return this.techCall('seeking'); },\n// initialTime: function(){ return this.techCall('initialTime'); },\n// startOffsetTime: function(){ return this.techCall('startOffsetTime'); },\n// played: function(){ return this.techCall('played'); },\n// seekable: function(){ return this.techCall('seekable'); },\n// videoTracks: function(){ return this.techCall('videoTracks'); },\n// audioTracks: function(){ return this.techCall('audioTracks'); },\n// videoWidth: function(){ return this.techCall('videoWidth'); },\n// videoHeight: function(){ return this.techCall('videoHeight'); },\n// defaultPlaybackRate: function(){ return this.techCall('defaultPlaybackRate'); },\n// playbackRate: function(){ return this.techCall('playbackRate'); },\n// mediaGroup: function(){ return this.techCall('mediaGroup'); },\n// controller: function(){ return this.techCall('controller'); },\n// defaultMuted: function(){ return this.techCall('defaultMuted'); }\n\n// TODO\n// currentSrcList: the array of sources including other formats and bitrates\n// playList: array of source lists in order of playback\n\n// RequestFullscreen API\n(function(){\n  var prefix, requestFS, div;\n\n  div = document.createElement('div');\n\n  requestFS = {};\n\n  // Current W3C Spec\n  // http://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html#api\n  // Mozilla Draft: https://wiki.mozilla.org/Gecko:FullScreenAPI#fullscreenchange_event\n  // New: https://dvcs.w3.org/hg/fullscreen/raw-file/529a67b8d9f3/Overview.html\n  if (div.cancelFullscreen !== undefined) {\n    requestFS.requestFn = 'requestFullscreen';\n    requestFS.cancelFn = 'exitFullscreen';\n    requestFS.eventName = 'fullscreenchange';\n    requestFS.isFullScreen = 'fullScreen';\n\n  // Webkit (Chrome/Safari) and Mozilla (Firefox) have working implementations\n  // that use prefixes and vary slightly from the new W3C spec. Specifically,\n  // using 'exit' instead of 'cancel', and lowercasing the 'S' in Fullscreen.\n  // Other browsers don't have any hints of which version they might follow yet,\n  // so not going to try to predict by looping through all prefixes.\n  } else {\n\n    if (document.mozCancelFullScreen) {\n      prefix = 'moz';\n      requestFS.isFullScreen = prefix + 'FullScreen';\n    } else {\n      prefix = 'webkit';\n      requestFS.isFullScreen = prefix + 'IsFullScreen';\n    }\n\n    if (div[prefix + 'RequestFullScreen']) {\n      requestFS.requestFn = prefix + 'RequestFullScreen';\n      requestFS.cancelFn = prefix + 'CancelFullScreen';\n    }\n    requestFS.eventName = prefix + 'fullscreenchange';\n  }\n\n  if (document[requestFS.cancelFn]) {\n    vjs.support.requestFullScreen = requestFS;\n  }\n\n})();\n\n\n/**\n * Container of main controls\n * @param {vjs.Player|Object} player\n * @param {Object=} options\n * @class\n * @constructor\n * @extends vjs.Component\n */\nvjs.ControlBar = vjs.Component.extend();\n\nvjs.ControlBar.prototype.options_ = {\n  loadEvent: 'play',\n  children: {\n    'playToggle': {},\n    'currentTimeDisplay': {},\n    'timeDivider': {},\n    'durationDisplay': {},\n    'remainingTimeDisplay': {},\n    'progressControl': {},\n    'fullscreenToggle': {},\n    'volumeControl': {},\n    'muteToggle': {}\n    // 'volumeMenuButton': {}\n  }\n};\n\nvjs.ControlBar.prototype.createEl = function(){\n  return vjs.createEl('div', {\n    className: 'vjs-control-bar'\n  });\n};\n/**\n * Button to toggle between play and pause\n * @param {vjs.Player|Object} player\n * @param {Object=} options\n * @class\n * @constructor\n */\nvjs.PlayToggle = vjs.Button.extend({\n  /** @constructor */\n  init: function(player, options){\n    vjs.Button.call(this, player, options);\n\n    player.on('play', vjs.bind(this, this.onPlay));\n    player.on('pause', vjs.bind(this, this.onPause));\n  }\n});\n\nvjs.PlayToggle.prototype.buttonText = 'Play';\n\nvjs.PlayToggle.prototype.buildCSSClass = function(){\n  return 'vjs-play-control ' + vjs.Button.prototype.buildCSSClass.call(this);\n};\n\n// OnClick - Toggle between play and pause\nvjs.PlayToggle.prototype.onClick = function(){\n  if (this.player_.paused()) {\n    this.player_.play();\n  } else {\n    this.player_.pause();\n  }\n};\n\n  // OnPlay - Add the vjs-playing class to the element so it can change appearance\nvjs.PlayToggle.prototype.onPlay = function(){\n  vjs.removeClass(this.el_, 'vjs-paused');\n  vjs.addClass(this.el_, 'vjs-playing');\n  this.el_.children[0].children[0].innerHTML = 'Pause'; // change the button text to \"Pause\"\n};\n\n  // OnPause - Add the vjs-paused class to the element so it can change appearance\nvjs.PlayToggle.prototype.onPause = function(){\n  vjs.removeClass(this.el_, 'vjs-playing');\n  vjs.addClass(this.el_, 'vjs-paused');\n  this.el_.children[0].children[0].innerHTML = 'Play'; // change the button text to \"Play\"\n};\n/**\n * Displays the current time\n * @param {vjs.Player|Object} player\n * @param {Object=} options\n * @constructor\n */\nvjs.CurrentTimeDisplay = vjs.Component.extend({\n  /** @constructor */\n  init: function(player, options){\n    vjs.Component.call(this, player, options);\n\n    player.on('timeupdate', vjs.bind(this, this.updateContent));\n  }\n});\n\nvjs.CurrentTimeDisplay.prototype.createEl = function(){\n  var el = vjs.Component.prototype.createEl.call(this, 'div', {\n    className: 'vjs-current-time vjs-time-controls vjs-control'\n  });\n\n  this.content = vjs.createEl('div', {\n    className: 'vjs-current-time-display',\n    innerHTML: '<span class=\"vjs-control-text\">Current Time </span>' + '0:00', // label the current time for screen reader users\n    'aria-live': 'off' // tell screen readers not to automatically read the time as it changes\n  });\n\n  el.appendChild(vjs.createEl('div').appendChild(this.content));\n  return el;\n};\n\nvjs.CurrentTimeDisplay.prototype.updateContent = function(){\n  // Allows for smooth scrubbing, when player can't keep up.\n  var time = (this.player_.scrubbing) ? this.player_.getCache().currentTime : this.player_.currentTime();\n  this.content.innerHTML = '<span class=\"vjs-control-text\">Current Time </span>' + vjs.formatTime(time, this.player_.duration());\n};\n\n/**\n * Displays the duration\n * @param {vjs.Player|Object} player\n * @param {Object=} options\n * @constructor\n */\nvjs.DurationDisplay = vjs.Component.extend({\n  /** @constructor */\n  init: function(player, options){\n    vjs.Component.call(this, player, options);\n\n    player.on('timeupdate', vjs.bind(this, this.updateContent)); // this might need to be changes to 'durationchange' instead of 'timeupdate' eventually, however the durationchange event fires before this.player_.duration() is set, so the value cannot be written out using this method. Once the order of durationchange and this.player_.duration() being set is figured out, this can be updated.\n  }\n});\n\nvjs.DurationDisplay.prototype.createEl = function(){\n  var el = vjs.Component.prototype.createEl.call(this, 'div', {\n    className: 'vjs-duration vjs-time-controls vjs-control'\n  });\n\n  this.content = vjs.createEl('div', {\n    className: 'vjs-duration-display',\n    innerHTML: '<span class=\"vjs-control-text\">Duration Time </span>' + '0:00', // label the duration time for screen reader users\n    'aria-live': 'off' // tell screen readers not to automatically read the time as it changes\n  });\n\n  el.appendChild(vjs.createEl('div').appendChild(this.content));\n  return el;\n};\n\nvjs.DurationDisplay.prototype.updateContent = function(){\n  var duration = this.player_.duration();\n  if (duration) {\n      this.content.innerHTML = '<span class=\"vjs-control-text\">Duration Time </span>' + vjs.formatTime(duration); // label the duration time for screen reader users\n  }\n};\n\n/**\n * The separator between the current time and duration\n *\n * Can be hidden if it's not needed in the design.\n *\n * @param {vjs.Player|Object} player\n * @param {Object=} options\n * @constructor\n */\nvjs.TimeDivider = vjs.Component.extend({\n  /** @constructor */\n  init: function(player, options){\n    vjs.Component.call(this, player, options);\n  }\n});\n\nvjs.TimeDivider.prototype.createEl = function(){\n  return vjs.Component.prototype.createEl.call(this, 'div', {\n    className: 'vjs-time-divider',\n    innerHTML: '<div><span>/</span></div>'\n  });\n};\n\n/**\n * Displays the time left in the video\n * @param {vjs.Player|Object} player\n * @param {Object=} options\n * @constructor\n */\nvjs.RemainingTimeDisplay = vjs.Component.extend({\n  /** @constructor */\n  init: function(player, options){\n    vjs.Component.call(this, player, options);\n\n    player.on('timeupdate', vjs.bind(this, this.updateContent));\n  }\n});\n\nvjs.RemainingTimeDisplay.prototype.createEl = function(){\n  var el = vjs.Component.prototype.createEl.call(this, 'div', {\n    className: 'vjs-remaining-time vjs-time-controls vjs-control'\n  });\n\n  this.content = vjs.createEl('div', {\n    className: 'vjs-remaining-time-display',\n    innerHTML: '<span class=\"vjs-control-text\">Remaining Time </span>' + '-0:00', // label the remaining time for screen reader users\n    'aria-live': 'off' // tell screen readers not to automatically read the time as it changes\n  });\n\n  el.appendChild(vjs.createEl('div').appendChild(this.content));\n  return el;\n};\n\nvjs.RemainingTimeDisplay.prototype.updateContent = function(){\n  if (this.player_.duration()) {\n    this.content.innerHTML = '<span class=\"vjs-control-text\">Remaining Time </span>' + '-'+ vjs.formatTime(this.player_.remainingTime());\n  }\n\n  // Allows for smooth scrubbing, when player can't keep up.\n  // var time = (this.player_.scrubbing) ? this.player_.getCache().currentTime : this.player_.currentTime();\n  // this.content.innerHTML = vjs.formatTime(time, this.player_.duration());\n};\n/**\n * Toggle fullscreen video\n * @param {vjs.Player|Object} player\n * @param {Object=} options\n * @class\n * @extends vjs.Button\n */\nvjs.FullscreenToggle = vjs.Button.extend({\n  /**\n   * @constructor\n   * @memberof vjs.FullscreenToggle\n   * @instance\n   */\n  init: function(player, options){\n    vjs.Button.call(this, player, options);\n  }\n});\n\nvjs.FullscreenToggle.prototype.buttonText = 'Fullscreen';\n\nvjs.FullscreenToggle.prototype.buildCSSClass = function(){\n  return 'vjs-fullscreen-control ' + vjs.Button.prototype.buildCSSClass.call(this);\n};\n\nvjs.FullscreenToggle.prototype.onClick = function(){\n  if (!this.player_.isFullScreen) {\n    this.player_.requestFullScreen();\n    this.el_.children[0].children[0].innerHTML = 'Non-Fullscreen'; // change the button text to \"Non-Fullscreen\"\n  } else {\n    this.player_.cancelFullScreen();\n    this.el_.children[0].children[0].innerHTML = 'Fullscreen'; // change the button to \"Fullscreen\"\n  }\n};\n/**\n * The Progress Control component contains the seek bar, load progress,\n * and play progress\n *\n * @param {vjs.Player|Object} player\n * @param {Object=} options\n * @constructor\n */\nvjs.ProgressControl = vjs.Component.extend({\n  /** @constructor */\n  init: function(player, options){\n    vjs.Component.call(this, player, options);\n  }\n});\n\nvjs.ProgressControl.prototype.options_ = {\n  children: {\n    'seekBar': {}\n  }\n};\n\nvjs.ProgressControl.prototype.createEl = function(){\n  return vjs.Component.prototype.createEl.call(this, 'div', {\n    className: 'vjs-progress-control vjs-control'\n  });\n};\n\n/**\n * Seek Bar and holder for the progress bars\n *\n * @param {vjs.Player|Object} player\n * @param {Object=} options\n * @constructor\n */\nvjs.SeekBar = vjs.Slider.extend({\n  /** @constructor */\n  init: function(player, options){\n    vjs.Slider.call(this, player, options);\n    player.on('timeupdate', vjs.bind(this, this.updateARIAAttributes));\n    player.ready(vjs.bind(this, this.updateARIAAttributes));\n  }\n});\n\nvjs.SeekBar.prototype.options_ = {\n  children: {\n    'loadProgressBar': {},\n    'playProgressBar': {},\n    'seekHandle': {}\n  },\n  'barName': 'playProgressBar',\n  'handleName': 'seekHandle'\n};\n\nvjs.SeekBar.prototype.playerEvent = 'timeupdate';\n\nvjs.SeekBar.prototype.createEl = function(){\n  return vjs.Slider.prototype.createEl.call(this, 'div', {\n    className: 'vjs-progress-holder',\n    'aria-label': 'video progress bar'\n  });\n};\n\nvjs.SeekBar.prototype.updateARIAAttributes = function(){\n    // Allows for smooth scrubbing, when player can't keep up.\n    var time = (this.player_.scrubbing) ? this.player_.getCache().currentTime : this.player_.currentTime();\n    this.el_.setAttribute('aria-valuenow',vjs.round(this.getPercent()*100, 2)); // machine readable value of progress bar (percentage complete)\n    this.el_.setAttribute('aria-valuetext',vjs.formatTime(time, this.player_.duration())); // human readable value of progress bar (time complete)\n};\n\nvjs.SeekBar.prototype.getPercent = function(){\n  var currentTime;\n  // Flash RTMP provider will not report the correct time\n  // immediately after a seek. This isn't noticeable if you're\n  // seeking while the video is playing, but it is if you seek\n  // while the video is paused.\n  if (this.player_.techName === 'Flash' && this.player_.seeking()) {\n    var cache = this.player_.getCache();\n    if (cache.lastSetCurrentTime) {\n      currentTime = cache.lastSetCurrentTime;\n    }\n    else {\n      currentTime = this.player_.currentTime();\n    }\n  }\n  else {\n    currentTime = this.player_.currentTime();\n  }\n\n  return currentTime / this.player_.duration();\n};\n\nvjs.SeekBar.prototype.onMouseDown = function(event){\n  vjs.Slider.prototype.onMouseDown.call(this, event);\n\n  this.player_.scrubbing = true;\n\n  this.videoWasPlaying = !this.player_.paused();\n  this.player_.pause();\n};\n\nvjs.SeekBar.prototype.onMouseMove = function(event){\n  var newTime = this.calculateDistance(event) * this.player_.duration();\n\n  // Don't let video end while scrubbing.\n  if (newTime == this.player_.duration()) { newTime = newTime - 0.1; }\n\n  // Set new time (tell player to seek to new time)\n  this.player_.currentTime(newTime);\n};\n\nvjs.SeekBar.prototype.onMouseUp = function(event){\n    debugger\n  vjs.Slider.prototype.onMouseUp.call(this, event);\n\n  this.player_.scrubbing = false;\n  if (this.videoWasPlaying) {\n      debugger\n    this.player_.play();\n  }\n};\n\nvjs.SeekBar.prototype.stepForward = function(){\n  this.player_.currentTime(this.player_.currentTime() + 5); // more quickly fast forward for keyboard-only users\n};\n\nvjs.SeekBar.prototype.stepBack = function(){\n  this.player_.currentTime(this.player_.currentTime() - 5); // more quickly rewind for keyboard-only users\n};\n\n\n/**\n * Shows load progress\n *\n * @param {vjs.Player|Object} player\n * @param {Object=} options\n * @constructor\n */\nvjs.LoadProgressBar = vjs.Component.extend({\n  /** @constructor */\n  init: function(player, options){\n    vjs.Component.call(this, player, options);\n    player.on('progress', vjs.bind(this, this.update));\n  }\n});\n\nvjs.LoadProgressBar.prototype.createEl = function(){\n  return vjs.Component.prototype.createEl.call(this, 'div', {\n    className: 'vjs-load-progress',\n    innerHTML: '<span class=\"vjs-control-text\">Loaded: 0%</span>'\n  });\n};\n\nvjs.LoadProgressBar.prototype.update = function(){\n  if (this.el_.style) { this.el_.style.width = vjs.round(this.player_.bufferedPercent() * 100, 2) + '%'; }\n};\n\n\n/**\n * Shows play progress\n *\n * @param {vjs.Player|Object} player\n * @param {Object=} options\n * @constructor\n */\nvjs.PlayProgressBar = vjs.Component.extend({\n  /** @constructor */\n  init: function(player, options){\n    vjs.Component.call(this, player, options);\n  }\n});\n\nvjs.PlayProgressBar.prototype.createEl = function(){\n  return vjs.Component.prototype.createEl.call(this, 'div', {\n    className: 'vjs-play-progress',\n    innerHTML: '<span class=\"vjs-control-text\">Progress: 0%</span>'\n  });\n};\n\n/**\n * The Seek Handle shows the current position of the playhead during playback,\n * and can be dragged to adjust the playhead.\n *\n * @param {vjs.Player|Object} player\n * @param {Object=} options\n * @constructor\n */\nvjs.SeekHandle = vjs.SliderHandle.extend();\n\n/**\n * The default value for the handle content, which may be read by screen readers\n *\n * @type {String}\n * @private\n */\nvjs.SeekHandle.prototype.defaultValue = '00:00';\n\n/** @inheritDoc */\nvjs.SeekHandle.prototype.createEl = function(){\n  return vjs.SliderHandle.prototype.createEl.call(this, 'div', {\n    className: 'vjs-seek-handle'\n  });\n};\n/**\n * The component for controlling the volume level\n *\n * @param {vjs.Player|Object} player\n * @param {Object=} options\n * @constructor\n */\nvjs.VolumeControl = vjs.Component.extend({\n  /** @constructor */\n  init: function(player, options){\n    vjs.Component.call(this, player, options);\n\n    // hide volume controls when they're not supported by the current tech\n    if (player.tech && player.tech.features && player.tech.features['volumeControl'] === false) {\n      this.addClass('vjs-hidden');\n    }\n    player.on('loadstart', vjs.bind(this, function(){\n      if (player.tech.features && player.tech.features['volumeControl'] === false) {\n        this.addClass('vjs-hidden');\n      } else {\n        this.removeClass('vjs-hidden');\n      }\n    }));\n  }\n});\n\nvjs.VolumeControl.prototype.options_ = {\n  children: {\n    'volumeBar': {}\n  }\n};\n\nvjs.VolumeControl.prototype.createEl = function(){\n  return vjs.Component.prototype.createEl.call(this, 'div', {\n    className: 'vjs-volume-control vjs-control'\n  });\n};\n\n/**\n * The bar that contains the volume level and can be clicked on to adjust the level\n *\n * @param {vjs.Player|Object} player\n * @param {Object=} options\n * @constructor\n */\nvjs.VolumeBar = vjs.Slider.extend({\n  /** @constructor */\n  init: function(player, options){\n    vjs.Slider.call(this, player, options);\n    player.on('volumechange', vjs.bind(this, this.updateARIAAttributes));\n    player.ready(vjs.bind(this, this.updateARIAAttributes));\n    setTimeout(vjs.bind(this, this.update), 0); // update when elements is in DOM\n  }\n});\n\nvjs.VolumeBar.prototype.updateARIAAttributes = function(){\n  // Current value of volume bar as a percentage\n  this.el_.setAttribute('aria-valuenow',vjs.round(this.player_.volume()*100, 2));\n  this.el_.setAttribute('aria-valuetext',vjs.round(this.player_.volume()*100, 2)+'%');\n};\n\nvjs.VolumeBar.prototype.options_ = {\n  children: {\n    'volumeLevel': {},\n    'volumeHandle': {}\n  },\n  'barName': 'volumeLevel',\n  'handleName': 'volumeHandle'\n};\n\nvjs.VolumeBar.prototype.playerEvent = 'volumechange';\n\nvjs.VolumeBar.prototype.createEl = function(){\n  return vjs.Slider.prototype.createEl.call(this, 'div', {\n    className: 'vjs-volume-bar',\n    'aria-label': 'volume level'\n  });\n};\n\nvjs.VolumeBar.prototype.onMouseMove = function(event) {\n  if (this.player_.muted()) {\n    this.player_.muted(false);\n  }\n\n  this.player_.volume(this.calculateDistance(event));\n};\n\nvjs.VolumeBar.prototype.getPercent = function(){\n  if (this.player_.muted()) {\n    return 0;\n  } else {\n    return this.player_.volume();\n  }\n};\n\nvjs.VolumeBar.prototype.stepForward = function(){\n  this.player_.volume(this.player_.volume() + 0.1);\n};\n\nvjs.VolumeBar.prototype.stepBack = function(){\n  this.player_.volume(this.player_.volume() - 0.1);\n};\n\n/**\n * Shows volume level\n *\n * @param {vjs.Player|Object} player\n * @param {Object=} options\n * @constructor\n */\nvjs.VolumeLevel = vjs.Component.extend({\n  /** @constructor */\n  init: function(player, options){\n    vjs.Component.call(this, player, options);\n  }\n});\n\nvjs.VolumeLevel.prototype.createEl = function(){\n  return vjs.Component.prototype.createEl.call(this, 'div', {\n    className: 'vjs-volume-level',\n    innerHTML: '<span class=\"vjs-control-text\"></span>'\n  });\n};\n\n/**\n * The volume handle can be dragged to adjust the volume level\n *\n * @param {vjs.Player|Object} player\n * @param {Object=} options\n * @constructor\n */\n vjs.VolumeHandle = vjs.SliderHandle.extend();\n\n vjs.VolumeHandle.prototype.defaultValue = '00:00';\n\n /** @inheritDoc */\n vjs.VolumeHandle.prototype.createEl = function(){\n   return vjs.SliderHandle.prototype.createEl.call(this, 'div', {\n     className: 'vjs-volume-handle'\n   });\n };\n/**\n * A button component for muting the audio\n *\n * @param {vjs.Player|Object} player\n * @param {Object=} options\n * @constructor\n */\nvjs.MuteToggle = vjs.Button.extend({\n  /** @constructor */\n  init: function(player, options){\n    vjs.Button.call(this, player, options);\n\n    player.on('volumechange', vjs.bind(this, this.update));\n\n    // hide mute toggle if the current tech doesn't support volume control\n    if (player.tech && player.tech.features && player.tech.features['volumeControl'] === false) {\n      this.addClass('vjs-hidden');\n    }\n    player.on('loadstart', vjs.bind(this, function(){\n      if (player.tech.features && player.tech.features['volumeControl'] === false) {\n        this.addClass('vjs-hidden');\n      } else {\n        this.removeClass('vjs-hidden');\n      }\n    }));\n  }\n});\n\nvjs.MuteToggle.prototype.createEl = function(){\n  return vjs.Button.prototype.createEl.call(this, 'div', {\n    className: 'vjs-mute-control vjs-control',\n    innerHTML: '<div><span class=\"vjs-control-text\">Mute</span></div>'\n  });\n};\n\nvjs.MuteToggle.prototype.onClick = function(){\n  this.player_.muted( this.player_.muted() ? false : true );\n};\n\nvjs.MuteToggle.prototype.update = function(){\n  var vol = this.player_.volume(),\n      level = 3;\n\n  if (vol === 0 || this.player_.muted()) {\n    level = 0;\n  } else if (vol < 0.33) {\n    level = 1;\n  } else if (vol < 0.67) {\n    level = 2;\n  }\n\n  // Don't rewrite the button text if the actual text doesn't change.\n  // This causes unnecessary and confusing information for screen reader users.\n  // This check is needed because this function gets called every time the volume level is changed.\n  if(this.player_.muted()){\n      if(this.el_.children[0].children[0].innerHTML!='Unmute'){\n          this.el_.children[0].children[0].innerHTML = 'Unmute'; // change the button text to \"Unmute\"\n      }\n  } else {\n      if(this.el_.children[0].children[0].innerHTML!='Mute'){\n          this.el_.children[0].children[0].innerHTML = 'Mute'; // change the button text to \"Mute\"\n      }\n  }\n\n  /* TODO improve muted icon classes */\n  for (var i = 0; i < 4; i++) {\n    vjs.removeClass(this.el_, 'vjs-vol-'+i);\n  }\n  vjs.addClass(this.el_, 'vjs-vol-'+level);\n};\n/**\n * Menu button with a popup for showing the volume slider.\n * @constructor\n */\nvjs.VolumeMenuButton = vjs.MenuButton.extend({\n  /** @constructor */\n  init: function(player, options){\n    vjs.MenuButton.call(this, player, options);\n\n    // Same listeners as MuteToggle\n    player.on('volumechange', vjs.bind(this, this.update));\n\n    // hide mute toggle if the current tech doesn't support volume control\n    if (player.tech && player.tech.features && player.tech.features.volumeControl === false) {\n      this.addClass('vjs-hidden');\n    }\n    player.on('loadstart', vjs.bind(this, function(){\n      if (player.tech.features && player.tech.features.volumeControl === false) {\n        this.addClass('vjs-hidden');\n      } else {\n        this.removeClass('vjs-hidden');\n      }\n    }));\n    this.addClass('vjs-menu-button');\n  }\n});\n\nvjs.VolumeMenuButton.prototype.createMenu = function(){\n  var menu = new vjs.Menu(this.player_, {\n    contentElType: 'div'\n  });\n  var vc = new vjs.VolumeBar(this.player_, vjs.obj.merge({vertical: true}, this.options_.volumeBar));\n  menu.addChild(vc);\n  return menu;\n};\n\nvjs.VolumeMenuButton.prototype.onClick = function(){\n  vjs.MuteToggle.prototype.onClick.call(this);\n  vjs.MenuButton.prototype.onClick.call(this);\n};\n\nvjs.VolumeMenuButton.prototype.createEl = function(){\n  return vjs.Button.prototype.createEl.call(this, 'div', {\n    className: 'vjs-volume-menu-button vjs-menu-button vjs-control',\n    innerHTML: '<div><span class=\"vjs-control-text\">Mute</span></div>'\n  });\n};\nvjs.VolumeMenuButton.prototype.update = vjs.MuteToggle.prototype.update;\n/* Poster Image\n================================================================================ */\n/**\n * The component that handles showing the poster image.\n *\n * @param {vjs.Player|Object} player\n * @param {Object=} options\n * @constructor\n */\nvjs.PosterImage = vjs.Button.extend({\n  /** @constructor */\n  init: function(player, options){\n    vjs.Button.call(this, player, options);\n\n    if (!player.poster() || !player.controls()) {\n      this.hide();\n    }\n\n    player.on('play', vjs.bind(this, this.hide));\n  }\n});\n\nvjs.PosterImage.prototype.createEl = function(){\n  var el = vjs.createEl('div', {\n        className: 'vjs-poster',\n\n        // Don't want poster to be tabbable.\n        tabIndex: -1\n      }),\n      poster = this.player_.poster();\n\n  if (poster) {\n    if ('backgroundSize' in el.style) {\n      el.style.backgroundImage = 'url(\"' + poster + '\")';\n    } else {\n      el.appendChild(vjs.createEl('img', { src: poster }));\n    }\n  }\n\n  return el;\n};\n\nvjs.PosterImage.prototype.onClick = function(){\n  // Only accept clicks when controls are enabled\n  if (this.player().controls()) {\n    this.player_.play();\n  }\n};\n/* Loading Spinner\n================================================================================ */\n/**\n * Loading spinner for waiting events\n * @param {vjs.Player|Object} player\n * @param {Object=} options\n * @class\n * @constructor\n */\nvjs.LoadingSpinner = vjs.Component.extend({\n  /** @constructor */\n  init: function(player, options){\n    vjs.Component.call(this, player, options);\n\n    player.on('canplay', vjs.bind(this, this.hide));\n    player.on('canplaythrough', vjs.bind(this, this.hide));\n    player.on('playing', vjs.bind(this, this.hide));\n    player.on('seeked', vjs.bind(this, this.hide));\n\n    player.on('seeking', vjs.bind(this, this.show));\n\n    // in some browsers seeking does not trigger the 'playing' event,\n    // so we also need to trap 'seeked' if we are going to set a\n    // 'seeking' event\n    player.on('seeked', vjs.bind(this, this.hide));\n\n    player.on('error', vjs.bind(this, this.show));\n\n    // Not showing spinner on stalled any more. Browsers may stall and then not trigger any events that would remove the spinner.\n    // Checked in Chrome 16 and Safari 5.1.2. http://help.videojs.com/discussions/problems/883-why-is-the-download-progress-showing\n    // player.on('stalled', vjs.bind(this, this.show));\n\n    player.on('waiting', vjs.bind(this, this.show));\n  }\n});\n\nvjs.LoadingSpinner.prototype.createEl = function(){\n  return vjs.Component.prototype.createEl.call(this, 'div', {\n    className: 'vjs-loading-spinner'\n  });\n};\n/* Big Play Button\n================================================================================ */\n/**\n * Initial play button. Shows before the video has played. The hiding of the\n * big play button is done via CSS and player states.\n * @param {vjs.Player|Object} player\n * @param {Object=} options\n * @class\n * @constructor\n */\nvjs.BigPlayButton = vjs.Button.extend();\n\nvjs.BigPlayButton.prototype.createEl = function(){\n  return vjs.Button.prototype.createEl.call(this, 'div', {\n    className: 'vjs-big-play-button',\n    innerHTML: '<span aria-hidden=\"true\"></span>',\n    'aria-label': 'play video'\n  });\n};\n\nvjs.BigPlayButton.prototype.onClick = function(){\n  this.player_.play();\n};\n/**\n * @fileoverview Media Technology Controller - Base class for media playback\n * technology controllers like Flash and HTML5\n */\n\n/**\n * Base class for media (HTML5 Video, Flash) controllers\n * @param {vjs.Player|Object} player  Central player instance\n * @param {Object=} options Options object\n * @constructor\n */\nvjs.MediaTechController = vjs.Component.extend({\n  /** @constructor */\n  init: function(player, options, ready){\n    vjs.Component.call(this, player, options, ready);\n\n    this.initControlsListeners();\n  }\n});\n\n/**\n * Set up click and touch listeners for the playback element\n * On desktops, a click on the video itself will toggle playback,\n * on a mobile device a click on the video toggles controls.\n * (toggling controls is done by toggling the user state between active and\n * inactive)\n *\n * A tap can signal that a user has become active, or has become inactive\n * e.g. a quick tap on an iPhone movie should reveal the controls. Another\n * quick tap should hide them again (signaling the user is in an inactive\n * viewing state)\n *\n * In addition to this, we still want the user to be considered inactive after\n * a few seconds of inactivity.\n *\n * Note: the only part of iOS interaction we can't mimic with this setup\n * is a touch and hold on the video element counting as activity in order to\n * keep the controls showing, but that shouldn't be an issue. A touch and hold on\n * any controls will still keep the user active\n */\nvjs.MediaTechController.prototype.initControlsListeners = function(){\n  var player, tech, activateControls, deactivateControls;\n\n  tech = this;\n  player = this.player();\n\n  var activateControls = function(){\n    if (player.controls() && !player.usingNativeControls()) {\n      tech.addControlsListeners();\n    }\n  };\n\n  deactivateControls = vjs.bind(tech, tech.removeControlsListeners);\n\n  // Set up event listeners once the tech is ready and has an element to apply\n  // listeners to\n  this.ready(activateControls);\n  player.on('controlsenabled', activateControls);\n  player.on('controlsdisabled', deactivateControls);\n};\n\nvjs.MediaTechController.prototype.addControlsListeners = function(){\n  var preventBubble, userWasActive;\n\n  // Some browsers (Chrome & IE) don't trigger a click on a flash swf, but do\n  // trigger mousedown/up.\n  // http://stackoverflow.com/questions/1444562/javascript-onclick-event-over-flash-object\n  // Any touch events are set to block the mousedown event from happening\n  this.on('mousedown', this.onClick);\n\n  // We need to block touch events on the video element from bubbling up,\n  // otherwise they'll signal activity prematurely. The specific use case is\n  // when the video is playing and the controls have faded out. In this case\n  // only a tap (fast touch) should toggle the user active state and turn the\n  // controls back on. A touch and move or touch and hold should not trigger\n  // the controls (per iOS as an example at least)\n  //\n  // We always want to stop propagation on touchstart because touchstart\n  // at the player level starts the touchInProgress interval. We can still\n  // report activity on the other events, but won't let them bubble for\n  // consistency. We don't want to bubble a touchend without a touchstart.\n  this.on('touchstart', function(event) {\n    // Stop the mouse events from also happening\n    event.preventDefault();\n    event.stopPropagation();\n    // Record if the user was active now so we don't have to keep polling it\n    userWasActive = this.player_.userActive();\n  });\n\n  preventBubble = function(event){\n    event.stopPropagation();\n    if (userWasActive) {\n      this.player_.reportUserActivity();\n    }\n  };\n\n  // Treat all touch events the same for consistency\n  this.on('touchmove', preventBubble);\n  this.on('touchleave', preventBubble);\n  this.on('touchcancel', preventBubble);\n  this.on('touchend', preventBubble);\n\n  // Turn on component tap events\n  this.emitTapEvents();\n\n  // The tap listener needs to come after the touchend listener because the tap\n  // listener cancels out any reportedUserActivity when setting userActive(false)\n  this.on('tap', this.onTap);\n};\n\n/**\n * Remove the listeners used for click and tap controls. This is needed for\n * toggling to controls disabled, where a tap/touch should do nothing.\n */\nvjs.MediaTechController.prototype.removeControlsListeners = function(){\n  // We don't want to just use `this.off()` because there might be other needed\n  // listeners added by techs that extend this.\n  this.off('tap');\n  this.off('touchstart');\n  this.off('touchmove');\n  this.off('touchleave');\n  this.off('touchcancel');\n  this.off('touchend');\n  this.off('click');\n  this.off('mousedown');\n};\n\n/**\n * Handle a click on the media element. By default will play/pause the media.\n */\nvjs.MediaTechController.prototype.onClick = function(event){\n  // We're using mousedown to detect clicks thanks to Flash, but mousedown\n  // will also be triggered with right-clicks, so we need to prevent that\n  if (event.button !== 0) return;\n\n  // When controls are disabled a click should not toggle playback because\n  // the click is considered a control\n  if (this.player().controls()) {\n    if (this.player().paused()) {\n      this.player().play();\n    } else {\n      this.player().pause();\n    }\n  }\n};\n\n/**\n * Handle a tap on the media element. By default it will toggle the user\n * activity state, which hides and shows the controls.\n */\n\nvjs.MediaTechController.prototype.onTap = function(){\n  this.player().userActive(!this.player().userActive());\n};\n\nvjs.MediaTechController.prototype.features = {\n  'volumeControl': true,\n\n  // Resizing plugins using request fullscreen reloads the plugin\n  'fullscreenResize': false,\n\n  // Optional events that we can manually mimic with timers\n  // currently not triggered by video-js-swf\n  'progressEvents': false,\n  'timeupdateEvents': false\n};\n\nvjs.media = {};\n\n/**\n * List of default API methods for any MediaTechController\n * @type {String}\n */\nvjs.media.ApiMethods = 'play,pause,paused,currentTime,setCurrentTime,duration,buffered,volume,setVolume,muted,setMuted,width,height,supportsFullScreen,enterFullScreen,src,load,currentSrc,preload,setPreload,autoplay,setAutoplay,loop,setLoop,error,networkState,readyState,seeking,initialTime,startOffsetTime,played,seekable,ended,videoTracks,audioTracks,videoWidth,videoHeight,textTracks,defaultPlaybackRate,playbackRate,mediaGroup,controller,controls,defaultMuted'.split(',');\n// Create placeholder methods for each that warn when a method isn't supported by the current playback technology\n\nfunction createMethod(methodName){\n  return function(){\n    throw new Error('The \"'+methodName+'\" method is not available on the playback technology\\'s API');\n  };\n}\n\nfor (var i = vjs.media.ApiMethods.length - 1; i >= 0; i--) {\n  var methodName = vjs.media.ApiMethods[i];\n  vjs.MediaTechController.prototype[vjs.media.ApiMethods[i]] = createMethod(methodName);\n}\n/**\n * @fileoverview HTML5 Media Controller - Wrapper for HTML5 Media API\n */\n\n/**\n * HTML5 Media Controller - Wrapper for HTML5 Media API\n * @param {vjs.Player|Object} player\n * @param {Object=} options\n * @param {Function=} ready\n * @constructor\n */\nvjs.Html5 = vjs.MediaTechController.extend({\n  /** @constructor */\n  init: function(player, options, ready){\n    // volume cannot be changed from 1 on iOS\n    this.features['volumeControl'] = vjs.Html5.canControlVolume();\n\n    // In iOS, if you move a video element in the DOM, it breaks video playback.\n    this.features['movingMediaElementInDOM'] = !vjs.IS_IOS;\n\n    // HTML video is able to automatically resize when going to fullscreen\n    this.features['fullscreenResize'] = true;\n\n    vjs.MediaTechController.call(this, player, options, ready);\n\n    var source = options['source'];\n\n    // If the element source is already set, we may have missed the loadstart event, and want to trigger it.\n    // We don't want to set the source again and interrupt playback.\n    if (source && this.el_.currentSrc === source.src && this.el_.networkState > 0) {\n      player.trigger('loadstart');\n\n    // Otherwise set the source if one was provided.\n    } else if (source) {\n      this.el_.src = source.src;\n    }\n\n    // Determine if native controls should be used\n    // Our goal should be to get the custom controls on mobile solid everywhere\n    // so we can remove this all together. Right now this will block custom\n    // controls on touch enabled laptops like the Chrome Pixel\n    if (vjs.TOUCH_ENABLED && player.options()['nativeControlsForTouch'] !== false) {\n      this.useNativeControls();\n    }\n\n    // Chrome and Safari both have issues with autoplay.\n    // In Safari (5.1.1), when we move the video element into the container div, autoplay doesn't work.\n    // In Chrome (15), if you have autoplay + a poster + no controls, the video gets hidden (but audio plays)\n    // This fixes both issues. Need to wait for API, so it updates displays correctly\n    player.ready(function(){\n      if (this.tag && this.options_['autoplay'] && this.paused()) {\n        delete this.tag['poster']; // Chrome Fix. Fixed in Chrome v16.\n        this.play();\n      }\n    });\n\n    this.setupTriggers();\n    this.triggerReady();\n  }\n});\n\nvjs.Html5.prototype.dispose = function(){\n  vjs.MediaTechController.prototype.dispose.call(this);\n};\n\nvjs.Html5.prototype.createEl = function(){\n  var player = this.player_,\n      // If possible, reuse original tag for HTML5 playback technology element\n      el = player.tag,\n      newEl,\n      clone;\n\n  // Check if this browser supports moving the element into the box.\n  // On the iPhone video will break if you move the element,\n  // So we have to create a brand new element.\n  if (!el || this.features['movingMediaElementInDOM'] === false) {\n\n    // If the original tag is still there, clone and remove it.\n    if (el) {\n      clone = el.cloneNode(false);\n      vjs.Html5.disposeMediaElement(el);\n      el = clone;\n      player.tag = null;\n    } else {\n      el = vjs.createEl('video', {\n        id:player.id() + '_html5_api',\n        className:'vjs-tech'\n      });\n    }\n    // associate the player with the new tag\n    el['player'] = player;\n\n    vjs.insertFirst(el, player.el());\n  }\n\n  // Update specific tag settings, in case they were overridden\n  var attrs = ['autoplay','preload','loop','muted'];\n  for (var i = attrs.length - 1; i >= 0; i--) {\n    var attr = attrs[i];\n    if (player.options_[attr] !== null) {\n      el[attr] = player.options_[attr];\n    }\n  }\n\n  return el;\n  // jenniisawesome = true;\n};\n\n// Make video events trigger player events\n// May seem verbose here, but makes other APIs possible.\nvjs.Html5.prototype.setupTriggers = function(){\n  for (var i = vjs.Html5.Events.length - 1; i >= 0; i--) {\n    vjs.on(this.el_, vjs.Html5.Events[i], vjs.bind(this.player_, this.eventHandler));\n  }\n};\n// Triggers removed using this.off when disposed\n\nvjs.Html5.prototype.eventHandler = function(e){\n  this.trigger(e);\n\n  // No need for media events to bubble up.\n  e.stopPropagation();\n};\n\nvjs.Html5.prototype.useNativeControls = function(){\n  var tech, player, controlsOn, controlsOff, cleanUp;\n\n  tech = this;\n  player = this.player();\n\n  // If the player controls are enabled turn on the native controls\n  tech.setControls(player.controls());\n\n  // Update the native controls when player controls state is updated\n  controlsOn = function(){\n    tech.setControls(true);\n  };\n  controlsOff = function(){\n    tech.setControls(false);\n  };\n  player.on('controlsenabled', controlsOn);\n  player.on('controlsdisabled', controlsOff);\n\n  // Clean up when not using native controls anymore\n  cleanUp = function(){\n    player.off('controlsenabled', controlsOn);\n    player.off('controlsdisabled', controlsOff);\n  };\n  tech.on('dispose', cleanUp);\n  player.on('usingcustomcontrols', cleanUp);\n\n  // Update the state of the player to using native controls\n  player.usingNativeControls(true);\n};\n\n\nvjs.Html5.prototype.play = function(){ this.el_.play(); };\nvjs.Html5.prototype.pause = function(){ this.el_.pause(); };\nvjs.Html5.prototype.paused = function(){ return this.el_.paused; };\n\nvjs.Html5.prototype.currentTime = function(){ return this.el_.currentTime; };\nvjs.Html5.prototype.setCurrentTime = function(seconds){\n  try {\n    this.el_.currentTime = seconds;\n  } catch(e) {\n    vjs.log(e, 'Video is not ready. (Video.js)');\n    // this.warning(VideoJS.warnings.videoNotReady);\n  }\n};\n\nvjs.Html5.prototype.duration = function(){ return this.el_.duration || 0; };\nvjs.Html5.prototype.buffered = function(){ return this.el_.buffered; };\n\nvjs.Html5.prototype.volume = function(){ return this.el_.volume; };\nvjs.Html5.prototype.setVolume = function(percentAsDecimal){ this.el_.volume = percentAsDecimal; };\nvjs.Html5.prototype.muted = function(){ return this.el_.muted; };\nvjs.Html5.prototype.setMuted = function(muted){ this.el_.muted = muted; };\n\nvjs.Html5.prototype.width = function(){ return this.el_.offsetWidth; };\nvjs.Html5.prototype.height = function(){ return this.el_.offsetHeight; };\n\nvjs.Html5.prototype.supportsFullScreen = function(){\n  if (typeof this.el_.webkitEnterFullScreen == 'function') {\n\n    // Seems to be broken in Chromium/Chrome && Safari in Leopard\n    if (/Android/.test(vjs.USER_AGENT) || !/Chrome|Mac OS X 10.5/.test(vjs.USER_AGENT)) {\n      return true;\n    }\n  }\n  return false;\n};\n\nvjs.Html5.prototype.enterFullScreen = function(){\n  var video = this.el_;\n  if (video.paused && video.networkState <= video.HAVE_METADATA) {\n    // attempt to prime the video element for programmatic access\n    // this isn't necessary on the desktop but shouldn't hurt\n    this.el_.play();\n\n    // playing and pausing synchronously during the transition to fullscreen\n    // can get iOS ~6.1 devices into a play/pause loop\n    setTimeout(function(){\n      video.pause();\n      video.webkitEnterFullScreen();\n    }, 0);\n  } else {\n    video.webkitEnterFullScreen();\n  }\n};\nvjs.Html5.prototype.exitFullScreen = function(){\n  this.el_.webkitExitFullScreen();\n};\nvjs.Html5.prototype.src = function(src){ this.el_.src = src; };\nvjs.Html5.prototype.load = function(){ this.el_.load(); };\nvjs.Html5.prototype.currentSrc = function(){ return this.el_.currentSrc; };\n\nvjs.Html5.prototype.preload = function(){ return this.el_.preload; };\nvjs.Html5.prototype.setPreload = function(val){ this.el_.preload = val; };\n\nvjs.Html5.prototype.autoplay = function(){ return this.el_.autoplay; };\nvjs.Html5.prototype.setAutoplay = function(val){ this.el_.autoplay = val; };\n\nvjs.Html5.prototype.controls = function(){ return this.el_.controls; }\nvjs.Html5.prototype.setControls = function(val){ this.el_.controls = !!val; }\n\nvjs.Html5.prototype.loop = function(){ return this.el_.loop; };\nvjs.Html5.prototype.setLoop = function(val){ this.el_.loop = val; };\n\nvjs.Html5.prototype.error = function(){ return this.el_.error; };\nvjs.Html5.prototype.seeking = function(){ return this.el_.seeking; };\nvjs.Html5.prototype.ended = function(){ return this.el_.ended; };\nvjs.Html5.prototype.defaultMuted = function(){ return this.el_.defaultMuted; };\n\n/* HTML5 Support Testing ---------------------------------------------------- */\n\nvjs.Html5.isSupported = function(){\n  return !!vjs.TEST_VID.canPlayType;\n};\n\nvjs.Html5.canPlaySource = function(srcObj){\n  // IE9 on Windows 7 without MediaPlayer throws an error here\n  // https://github.com/videojs/video.js/issues/519\n  try {\n    return !!vjs.TEST_VID.canPlayType(srcObj.type);\n  } catch(e) {\n    return '';\n  }\n  // TODO: Check Type\n  // If no Type, check ext\n  // Check Media Type\n};\n\nvjs.Html5.canControlVolume = function(){\n  var volume =  vjs.TEST_VID.volume;\n  vjs.TEST_VID.volume = (volume / 2) + 0.1;\n  return volume !== vjs.TEST_VID.volume;\n};\n\n// List of all HTML5 events (various uses).\nvjs.Html5.Events = 'loadstart,suspend,abort,error,emptied,stalled,loadedmetadata,loadeddata,canplay,canplaythrough,playing,waiting,seeking,seeked,ended,durationchange,timeupdate,progress,play,pause,ratechange,volumechange'.split(',');\n\nvjs.Html5.disposeMediaElement = function(el){\n  if (!el) { return; }\n\n  el['player'] = null;\n\n  if (el.parentNode) {\n    el.parentNode.removeChild(el);\n  }\n\n  // remove any child track or source nodes to prevent their loading\n  while(el.hasChildNodes()) {\n    el.removeChild(el.firstChild);\n  }\n\n  // remove any src reference. not setting `src=''` because that causes a warning\n  // in firefox\n  el.removeAttribute('src');\n\n  // force the media element to update its loading state by calling load()\n  if (typeof el.load === 'function') {\n    el.load();\n  }\n};\n\n// HTML5 Feature detection and Device Fixes --------------------------------- //\n\n  // Override Android 2.2 and less canPlayType method which is broken\nif (vjs.IS_OLD_ANDROID) {\n  document.createElement('video').constructor.prototype.canPlayType = function(type){\n    return (type && type.toLowerCase().indexOf('video/mp4') != -1) ? 'maybe' : '';\n  };\n}\n/**\n * @fileoverview VideoJS-SWF - Custom Flash Player with HTML5-ish API\n * https://github.com/zencoder/video-js-swf\n * Not using setupTriggers. Using global onEvent func to distribute events\n */\n\n/**\n * Flash Media Controller - Wrapper for fallback SWF API\n *\n * @param {vjs.Player} player\n * @param {Object=} options\n * @param {Function=} ready\n * @constructor\n */\nvjs.Flash = vjs.MediaTechController.extend({\n  /** @constructor */\n  init: function(player, options, ready){\n    vjs.MediaTechController.call(this, player, options, ready);\n\n    var source = options['source'],\n\n        // Which element to embed in\n        parentEl = options['parentEl'],\n\n        // Create a temporary element to be replaced by swf object\n        placeHolder = this.el_ = vjs.createEl('div', { id: player.id() + '_temp_flash' }),\n\n        // Generate ID for swf object\n        objId = player.id()+'_flash_api',\n\n        // Store player options in local var for optimization\n        // TODO: switch to using player methods instead of options\n        // e.g. player.autoplay();\n        playerOptions = player.options_,\n\n        // Merge default flashvars with ones passed in to init\n        flashVars = vjs.obj.merge({\n\n          // SWF Callback Functions\n          'readyFunction': 'videojs.Flash.onReady',\n          'eventProxyFunction': 'videojs.Flash.onEvent',\n          'errorEventProxyFunction': 'videojs.Flash.onError',\n\n          // Player Settings\n          'autoplay': playerOptions.autoplay,\n          'preload': playerOptions.preload,\n          'loop': playerOptions.loop,\n          'muted': playerOptions.muted\n\n        }, options['flashVars']),\n\n        // Merge default parames with ones passed in\n        params = vjs.obj.merge({\n          'wmode': 'opaque', // Opaque is needed to overlay controls, but can affect playback performance\n          'bgcolor': '#000000' // Using bgcolor prevents a white flash when the object is loading\n        }, options['params']),\n\n        // Merge default attributes with ones passed in\n        attributes = vjs.obj.merge({\n          'id': objId,\n          'name': objId, // Both ID and Name needed or swf to identifty itself\n          'class': 'vjs-tech'\n        }, options['attributes'])\n    ;\n\n    // If source was supplied pass as a flash var.\n    if (source) {\n      if (source.type && vjs.Flash.isStreamingType(source.type)) {\n        var parts = vjs.Flash.streamToParts(source.src);\n        flashVars['rtmpConnection'] = encodeURIComponent(parts.connection);\n        flashVars['rtmpStream'] = encodeURIComponent(parts.stream);\n      }\n      else {\n        flashVars['src'] = encodeURIComponent(vjs.getAbsoluteURL(source.src));\n      }\n    }\n\n    // Add placeholder to player div\n    vjs.insertFirst(placeHolder, parentEl);\n\n    // Having issues with Flash reloading on certain page actions (hide/resize/fullscreen) in certain browsers\n    // This allows resetting the playhead when we catch the reload\n    if (options['startTime']) {\n      this.ready(function(){\n        this.load();\n        this.play();\n        this.currentTime(options['startTime']);\n      });\n    }\n\n    // Flash iFrame Mode\n    // In web browsers there are multiple instances where changing the parent element or visibility of a plugin causes the plugin to reload.\n    // - Firefox just about always. https://bugzilla.mozilla.org/show_bug.cgi?id=90268 (might be fixed by version 13)\n    // - Webkit when hiding the plugin\n    // - Webkit and Firefox when using requestFullScreen on a parent element\n    // Loading the flash plugin into a dynamically generated iFrame gets around most of these issues.\n    // Issues that remain include hiding the element and requestFullScreen in Firefox specifically\n\n    // There's on particularly annoying issue with this method which is that Firefox throws a security error on an offsite Flash object loaded into a dynamically created iFrame.\n    // Even though the iframe was inserted into a page on the web, Firefox + Flash considers it a local app trying to access an internet file.\n    // I tried mulitple ways of setting the iframe src attribute but couldn't find a src that worked well. Tried a real/fake source, in/out of domain.\n    // Also tried a method from stackoverflow that caused a security error in all browsers. http://stackoverflow.com/questions/2486901/how-to-set-document-domain-for-a-dynamically-generated-iframe\n    // In the end the solution I found to work was setting the iframe window.location.href right before doing a document.write of the Flash object.\n    // The only downside of this it seems to trigger another http request to the original page (no matter what's put in the href). Not sure why that is.\n\n    // NOTE (2012-01-29): Cannot get Firefox to load the remote hosted SWF into a dynamically created iFrame\n    // Firefox 9 throws a security error, unleess you call location.href right before doc.write.\n    //    Not sure why that even works, but it causes the browser to look like it's continuously trying to load the page.\n    // Firefox 3.6 keeps calling the iframe onload function anytime I write to it, causing an endless loop.\n\n    if (options['iFrameMode'] === true && !vjs.IS_FIREFOX) {\n\n      // Create iFrame with vjs-tech class so it's 100% width/height\n      var iFrm = vjs.createEl('iframe', {\n        'id': objId + '_iframe',\n        'name': objId + '_iframe',\n        'className': 'vjs-tech',\n        'scrolling': 'no',\n        'marginWidth': 0,\n        'marginHeight': 0,\n        'frameBorder': 0\n      });\n\n      // Update ready function names in flash vars for iframe window\n      flashVars['readyFunction'] = 'ready';\n      flashVars['eventProxyFunction'] = 'events';\n      flashVars['errorEventProxyFunction'] = 'errors';\n\n      // Tried multiple methods to get this to work in all browsers\n\n      // Tried embedding the flash object in the page first, and then adding a place holder to the iframe, then replacing the placeholder with the page object.\n      // The goal here was to try to load the swf URL in the parent page first and hope that got around the firefox security error\n      // var newObj = vjs.Flash.embed(options['swf'], placeHolder, flashVars, params, attributes);\n      // (in onload)\n      //  var temp = vjs.createEl('a', { id:'asdf', innerHTML: 'asdf' } );\n      //  iDoc.body.appendChild(temp);\n\n      // Tried embedding the flash object through javascript in the iframe source.\n      // This works in webkit but still triggers the firefox security error\n      // iFrm.src = 'javascript: document.write('\"+vjs.Flash.getEmbedCode(options['swf'], flashVars, params, attributes)+\"');\";\n\n      // Tried an actual local iframe just to make sure that works, but it kills the easiness of the CDN version if you require the user to host an iframe\n      // We should add an option to host the iframe locally though, because it could help a lot of issues.\n      // iFrm.src = \"iframe.html\";\n\n      // Wait until iFrame has loaded to write into it.\n      vjs.on(iFrm, 'load', vjs.bind(this, function(){\n\n        var iDoc,\n            iWin = iFrm.contentWindow;\n\n        // The one working method I found was to use the iframe's document.write() to create the swf object\n        // This got around the security issue in all browsers except firefox.\n        // I did find a hack where if I call the iframe's window.location.href='', it would get around the security error\n        // However, the main page would look like it was loading indefinitely (URL bar loading spinner would never stop)\n        // Plus Firefox 3.6 didn't work no matter what I tried.\n        // if (vjs.USER_AGENT.match('Firefox')) {\n        //   iWin.location.href = '';\n        // }\n\n        // Get the iFrame's document depending on what the browser supports\n        iDoc = iFrm.contentDocument ? iFrm.contentDocument : iFrm.contentWindow.document;\n\n        // Tried ensuring both document domains were the same, but they already were, so that wasn't the issue.\n        // Even tried adding /. that was mentioned in a browser security writeup\n        // document.domain = document.domain+'/.';\n        // iDoc.domain = document.domain+'/.';\n\n        // Tried adding the object to the iframe doc's innerHTML. Security error in all browsers.\n        // iDoc.body.innerHTML = swfObjectHTML;\n\n        // Tried appending the object to the iframe doc's body. Security error in all browsers.\n        // iDoc.body.appendChild(swfObject);\n\n        // Using document.write actually got around the security error that browsers were throwing.\n        // Again, it's a dynamically generated (same domain) iframe, loading an external Flash swf.\n        // Not sure why that's a security issue, but apparently it is.\n        iDoc.write(vjs.Flash.getEmbedCode(options['swf'], flashVars, params, attributes));\n\n        // Setting variables on the window needs to come after the doc write because otherwise they can get reset in some browsers\n        // So far no issues with swf ready event being called before it's set on the window.\n        iWin['player'] = this.player_;\n\n        // Create swf ready function for iFrame window\n        iWin['ready'] = vjs.bind(this.player_, function(currSwf){\n          var el = iDoc.getElementById(currSwf),\n              player = this,\n              tech = player.tech;\n\n          // Update reference to playback technology element\n          tech.el_ = el;\n\n          // Make sure swf is actually ready. Sometimes the API isn't actually yet.\n          vjs.Flash.checkReady(tech);\n        });\n\n        // Create event listener for all swf events\n        iWin['events'] = vjs.bind(this.player_, function(swfID, eventName){\n          var player = this;\n          if (player && player.techName === 'flash') {\n            player.trigger(eventName);\n          }\n        });\n\n        // Create error listener for all swf errors\n        iWin['errors'] = vjs.bind(this.player_, function(swfID, eventName){\n          vjs.log('Flash Error', eventName);\n        });\n\n      }));\n\n      // Replace placeholder with iFrame (it will load now)\n      placeHolder.parentNode.replaceChild(iFrm, placeHolder);\n\n    // If not using iFrame mode, embed as normal object\n    } else {\n      vjs.Flash.embed(options['swf'], placeHolder, flashVars, params, attributes);\n    }\n  }\n});\n\nvjs.Flash.prototype.dispose = function(){\n  vjs.MediaTechController.prototype.dispose.call(this);\n};\n\nvjs.Flash.prototype.play = function(){\n  this.el_.vjs_play();\n};\n\nvjs.Flash.prototype.pause = function(){\n  this.el_.vjs_pause();\n};\n\nvjs.Flash.prototype.src = function(src){\n  if (vjs.Flash.isStreamingSrc(src)) {\n    src = vjs.Flash.streamToParts(src);\n    this.setRtmpConnection(src.connection);\n    this.setRtmpStream(src.stream);\n  }\n  else {\n    // Make sure source URL is abosolute.\n    src = vjs.getAbsoluteURL(src);\n    this.el_.vjs_src(src);\n  }\n\n  // Currently the SWF doesn't autoplay if you load a source later.\n  // e.g. Load player w/ no source, wait 2s, set src.\n  if (this.player_.autoplay()) {\n    var tech = this;\n    setTimeout(function(){ tech.play(); }, 0);\n  }\n};\n\nvjs.Flash.prototype.currentSrc = function(){\n  var src = this.el_.vjs_getProperty('currentSrc');\n  // no src, check and see if RTMP\n  if (src == null) {\n    var connection = this.rtmpConnection(),\n        stream = this.rtmpStream();\n\n    if (connection && stream) {\n      src = vjs.Flash.streamFromParts(connection, stream);\n    }\n  }\n  return src;\n};\n\nvjs.Flash.prototype.load = function(){\n  this.el_.vjs_load();\n};\n\nvjs.Flash.prototype.poster = function(){\n  this.el_.vjs_getProperty('poster');\n};\n\nvjs.Flash.prototype.buffered = function(){\n  return vjs.createTimeRange(0, this.el_.vjs_getProperty('buffered'));\n};\n\nvjs.Flash.prototype.supportsFullScreen = function(){\n  return false; // Flash does not allow fullscreen through javascript\n};\n\nvjs.Flash.prototype.enterFullScreen = function(){\n  return false;\n};\n\n\n// Create setters and getters for attributes\nvar api = vjs.Flash.prototype,\n    readWrite = 'rtmpConnection,rtmpStream,preload,currentTime,defaultPlaybackRate,playbackRate,autoplay,loop,mediaGroup,controller,controls,volume,muted,defaultMuted'.split(','),\n    readOnly = 'error,currentSrc,networkState,readyState,seeking,initialTime,duration,startOffsetTime,paused,played,seekable,ended,videoTracks,audioTracks,videoWidth,videoHeight,textTracks'.split(',');\n    // Overridden: buffered\n\n/**\n * @this {*}\n * @private\n */\nvar createSetter = function(attr){\n  var attrUpper = attr.charAt(0).toUpperCase() + attr.slice(1);\n  api['set'+attrUpper] = function(val){ return this.el_.vjs_setProperty(attr, val); };\n};\n\n/**\n * @this {*}\n * @private\n */\nvar createGetter = function(attr){\n  api[attr] = function(){ return this.el_.vjs_getProperty(attr); };\n};\n\n(function(){\n  var i;\n  // Create getter and setters for all read/write attributes\n  for (i = 0; i < readWrite.length; i++) {\n    createGetter(readWrite[i]);\n    createSetter(readWrite[i]);\n  }\n\n  // Create getters for read-only attributes\n  for (i = 0; i < readOnly.length; i++) {\n    createGetter(readOnly[i]);\n  }\n})();\n\n/* Flash Support Testing -------------------------------------------------------- */\n\nvjs.Flash.isSupported = function(){\n  return vjs.Flash.version()[0] >= 10;\n  // return swfobject.hasFlashPlayerVersion('10');\n};\n\nvjs.Flash.canPlaySource = function(srcObj){\n  var type;\n\n  if (!srcObj.type) {\n    return '';\n  }\n\n  type = srcObj.type.replace(/;.*/,'').toLowerCase();\n  if (type in vjs.Flash.formats || type in vjs.Flash.streamingFormats) {\n    return 'maybe';\n  }\n};\n\nvjs.Flash.formats = {\n  'video/flv': 'FLV',\n  'video/x-flv': 'FLV',\n  'video/mp4': 'MP4',\n  'video/m4v': 'MP4'\n};\n\nvjs.Flash.streamingFormats = {\n  'rtmp/mp4': 'MP4',\n  'rtmp/flv': 'FLV'\n};\n\nvjs.Flash['onReady'] = function(currSwf){\n  var el = vjs.el(currSwf);\n\n  // Get player from box\n  // On firefox reloads, el might already have a player\n  var player = el['player'] || el.parentNode['player'],\n      tech = player.tech;\n\n  // Reference player on tech element\n  el['player'] = player;\n\n  // Update reference to playback technology element\n  tech.el_ = el;\n\n  vjs.Flash.checkReady(tech);\n};\n\n// The SWF isn't alwasy ready when it says it is. Sometimes the API functions still need to be added to the object.\n// If it's not ready, we set a timeout to check again shortly.\nvjs.Flash.checkReady = function(tech){\n\n  // Check if API property exists\n  if (tech.el().vjs_getProperty) {\n\n    // If so, tell tech it's ready\n    tech.triggerReady();\n\n  // Otherwise wait longer.\n  } else {\n\n    setTimeout(function(){\n      vjs.Flash.checkReady(tech);\n    }, 50);\n\n  }\n};\n\n// Trigger events from the swf on the player\nvjs.Flash['onEvent'] = function(swfID, eventName){\n  var player = vjs.el(swfID)['player'];\n  player.trigger(eventName);\n};\n\n// Log errors from the swf\nvjs.Flash['onError'] = function(swfID, err){\n  var player = vjs.el(swfID)['player'];\n  player.trigger('error');\n  vjs.log('Flash Error', err, swfID);\n};\n\n// Flash Version Check\nvjs.Flash.version = function(){\n  var version = '0,0,0';\n\n  // IE\n  try {\n    version = new window.ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version').replace(/\\D+/g, ',').match(/^,?(.+),?$/)[1];\n\n  // other browsers\n  } catch(e) {\n    try {\n      if (navigator.mimeTypes['application/x-shockwave-flash'].enabledPlugin){\n        version = (navigator.plugins['Shockwave Flash 2.0'] || navigator.plugins['Shockwave Flash']).description.replace(/\\D+/g, ',').match(/^,?(.+),?$/)[1];\n      }\n    } catch(err) {}\n  }\n  return version.split(',');\n};\n\n// Flash embedding method. Only used in non-iframe mode\nvjs.Flash.embed = function(swf, placeHolder, flashVars, params, attributes){\n  var code = vjs.Flash.getEmbedCode(swf, flashVars, params, attributes),\n\n      // Get element by embedding code and retrieving created element\n      obj = vjs.createEl('div', { innerHTML: code }).childNodes[0],\n\n      par = placeHolder.parentNode\n  ;\n\n  placeHolder.parentNode.replaceChild(obj, placeHolder);\n\n  // IE6 seems to have an issue where it won't initialize the swf object after injecting it.\n  // This is a dumb fix\n  var newObj = par.childNodes[0];\n  setTimeout(function(){\n    newObj.style.display = 'block';\n  }, 1000);\n\n  return obj;\n\n};\n\nvjs.Flash.getEmbedCode = function(swf, flashVars, params, attributes){\n\n  var objTag = '<object type=\"application/x-shockwave-flash\"',\n      flashVarsString = '',\n      paramsString = '',\n      attrsString = '';\n\n  // Convert flash vars to string\n  if (flashVars) {\n    vjs.obj.each(flashVars, function(key, val){\n      flashVarsString += (key + '=' + val + '&amp;');\n    });\n  }\n\n  // Add swf, flashVars, and other default params\n  params = vjs.obj.merge({\n    'movie': swf,\n    'flashvars': flashVarsString,\n    'allowScriptAccess': 'always', // Required to talk to swf\n    'allowNetworking': 'all' // All should be default, but having security issues.\n  }, params);\n\n  // Create param tags string\n  vjs.obj.each(params, function(key, val){\n    paramsString += '<param name=\"'+key+'\" value=\"'+val+'\" />';\n  });\n\n  attributes = vjs.obj.merge({\n    // Add swf to attributes (need both for IE and Others to work)\n    'data': swf,\n\n    // Default to 100% width/height\n    'width': '100%',\n    'height': '100%'\n\n  }, attributes);\n\n  // Create Attributes string\n  vjs.obj.each(attributes, function(key, val){\n    attrsString += (key + '=\"' + val + '\" ');\n  });\n\n  return objTag + attrsString + '>' + paramsString + '</object>';\n};\n\nvjs.Flash.streamFromParts = function(connection, stream) {\n  return connection + '&' + stream;\n};\n\nvjs.Flash.streamToParts = function(src) {\n  var parts = {\n    connection: '',\n    stream: ''\n  };\n\n  if (! src) {\n    return parts;\n  }\n\n  // Look for the normal URL separator we expect, '&'.\n  // If found, we split the URL into two pieces around the\n  // first '&'.\n  var connEnd = src.indexOf('&');\n  var streamBegin;\n  if (connEnd !== -1) {\n    streamBegin = connEnd + 1;\n  }\n  else {\n    // If there's not a '&', we use the last '/' as the delimiter.\n    connEnd = streamBegin = src.lastIndexOf('/') + 1;\n    if (connEnd === 0) {\n      // really, there's not a '/'?\n      connEnd = streamBegin = src.length;\n    }\n  }\n  parts.connection = src.substring(0, connEnd);\n  parts.stream = src.substring(streamBegin, src.length);\n\n  return parts;\n};\n\nvjs.Flash.isStreamingType = function(srcType) {\n  return srcType in vjs.Flash.streamingFormats;\n};\n\n// RTMP has four variations, any string starting\n// with one of these protocols should be valid\nvjs.Flash.RTMP_RE = /^rtmp[set]?:\\/\\//i;\n\nvjs.Flash.isStreamingSrc = function(src) {\n  return vjs.Flash.RTMP_RE.test(src);\n};\n/**\n * The Media Loader is the component that decides which playback technology to load\n * when the player is initialized.\n *\n * @constructor\n */\nvjs.MediaLoader = vjs.Component.extend({\n  /** @constructor */\n  init: function(player, options, ready){\n    vjs.Component.call(this, player, options, ready);\n\n    // If there are no sources when the player is initialized,\n    // load the first supported playback technology.\n    if (!player.options_['sources'] || player.options_['sources'].length === 0) {\n      for (var i=0,j=player.options_['techOrder']; i<j.length; i++) {\n        var techName = vjs.capitalize(j[i]),\n            tech = window['videojs'][techName];\n\n        // Check if the browser supports this technology\n        if (tech && tech.isSupported()) {\n          player.loadTech(techName);\n          break;\n        }\n      }\n    } else {\n      // // Loop through playback technologies (HTML5, Flash) and check for support.\n      // // Then load the best source.\n      // // A few assumptions here:\n      // //   All playback technologies respect preload false.\n      player.src(player.options_['sources']);\n    }\n  }\n});\n/**\n * @fileoverview Text Tracks\n * Text tracks are tracks of timed text events.\n * Captions - text displayed over the video for the hearing impared\n * Subtitles - text displayed over the video for those who don't understand langauge in the video\n * Chapters - text displayed in a menu allowing the user to jump to particular points (chapters) in the video\n * Descriptions (not supported yet) - audio descriptions that are read back to the user by a screen reading device\n */\n\n// Player Additions - Functions add to the player object for easier access to tracks\n\n/**\n * List of associated text tracks\n * @type {Array}\n * @private\n */\nvjs.Player.prototype.textTracks_;\n\n/**\n * Get an array of associated text tracks. captions, subtitles, chapters, descriptions\n * http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-texttracks\n * @return {Array}           Array of track objects\n * @private\n */\nvjs.Player.prototype.textTracks = function(){\n  this.textTracks_ = this.textTracks_ || [];\n  return this.textTracks_;\n};\n\n/**\n * Add a text track\n * In addition to the W3C settings we allow adding additional info through options.\n * http://www.w3.org/html/wg/drafts/html/master/embedded-content-0.html#dom-media-addtexttrack\n * @param {String}  kind        Captions, subtitles, chapters, descriptions, or metadata\n * @param {String=} label       Optional label\n * @param {String=} language    Optional language\n * @param {Object=} options     Additional track options, like src\n * @private\n */\nvjs.Player.prototype.addTextTrack = function(kind, label, language, options){\n  var tracks = this.textTracks_ = this.textTracks_ || [];\n  options = options || {};\n\n  options['kind'] = kind;\n  options['label'] = label;\n  options['language'] = language;\n\n  // HTML5 Spec says default to subtitles.\n  // Uppercase first letter to match class names\n  var Kind = vjs.capitalize(kind || 'subtitles');\n\n  // Create correct texttrack class. CaptionsTrack, etc.\n  var track = new window['videojs'][Kind + 'Track'](this, options);\n\n  tracks.push(track);\n\n  // If track.dflt() is set, start showing immediately\n  // TODO: Add a process to deterime the best track to show for the specific kind\n  // Incase there are mulitple defaulted tracks of the same kind\n  // Or the user has a set preference of a specific language that should override the default\n  // if (track.dflt()) {\n  //   this.ready(vjs.bind(track, track.show));\n  // }\n\n  return track;\n};\n\n/**\n * Add an array of text tracks. captions, subtitles, chapters, descriptions\n * Track objects will be stored in the player.textTracks() array\n * @param {Array} trackList Array of track elements or objects (fake track elements)\n * @private\n */\nvjs.Player.prototype.addTextTracks = function(trackList){\n  var trackObj;\n\n  for (var i = 0; i < trackList.length; i++) {\n    trackObj = trackList[i];\n    this.addTextTrack(trackObj['kind'], trackObj['label'], trackObj['language'], trackObj);\n  }\n\n  return this;\n};\n\n// Show a text track\n// disableSameKind: disable all other tracks of the same kind. Value should be a track kind (captions, etc.)\nvjs.Player.prototype.showTextTrack = function(id, disableSameKind){\n  var tracks = this.textTracks_,\n      i = 0,\n      j = tracks.length,\n      track, showTrack, kind;\n\n  // Find Track with same ID\n  for (;i<j;i++) {\n    track = tracks[i];\n    if (track.id() === id) {\n      track.show();\n      showTrack = track;\n\n    // Disable tracks of the same kind\n    } else if (disableSameKind && track.kind() == disableSameKind && track.mode() > 0) {\n      track.disable();\n    }\n  }\n\n  // Get track kind from shown track or disableSameKind\n  kind = (showTrack) ? showTrack.kind() : ((disableSameKind) ? disableSameKind : false);\n\n  // Trigger trackchange event, captionstrackchange, subtitlestrackchange, etc.\n  if (kind) {\n    this.trigger(kind+'trackchange');\n  }\n\n  return this;\n};\n\n/**\n * The base class for all text tracks\n *\n * Handles the parsing, hiding, and showing of text track cues\n *\n * @param {vjs.Player|Object} player\n * @param {Object=} options\n * @constructor\n */\nvjs.TextTrack = vjs.Component.extend({\n  /** @constructor */\n  init: function(player, options){\n    vjs.Component.call(this, player, options);\n\n    // Apply track info to track object\n    // Options will often be a track element\n\n    // Build ID if one doesn't exist\n    this.id_ = options['id'] || ('vjs_' + options['kind'] + '_' + options['language'] + '_' + vjs.guid++);\n    this.src_ = options['src'];\n    // 'default' is a reserved keyword in js so we use an abbreviated version\n    this.dflt_ = options['default'] || options['dflt'];\n    this.title_ = options['title'];\n    this.language_ = options['srclang'];\n    this.label_ = options['label'];\n    this.cues_ = [];\n    this.activeCues_ = [];\n    this.readyState_ = 0;\n    this.mode_ = 0;\n\n    this.player_.on('fullscreenchange', vjs.bind(this, this.adjustFontSize));\n  }\n});\n\n/**\n * Track kind value. Captions, subtitles, etc.\n * @private\n */\nvjs.TextTrack.prototype.kind_;\n\n/**\n * Get the track kind value\n * @return {String}\n */\nvjs.TextTrack.prototype.kind = function(){\n  return this.kind_;\n};\n\n/**\n * Track src value\n * @private\n */\nvjs.TextTrack.prototype.src_;\n\n/**\n * Get the track src value\n * @return {String}\n */\nvjs.TextTrack.prototype.src = function(){\n  return this.src_;\n};\n\n/**\n * Track default value\n * If default is used, subtitles/captions to start showing\n * @private\n */\nvjs.TextTrack.prototype.dflt_;\n\n/**\n * Get the track default value. ('default' is a reserved keyword)\n * @return {Boolean}\n */\nvjs.TextTrack.prototype.dflt = function(){\n  return this.dflt_;\n};\n\n/**\n * Track title value\n * @private\n */\nvjs.TextTrack.prototype.title_;\n\n/**\n * Get the track title value\n * @return {String}\n */\nvjs.TextTrack.prototype.title = function(){\n  return this.title_;\n};\n\n/**\n * Language - two letter string to represent track language, e.g. 'en' for English\n * Spec def: readonly attribute DOMString language;\n * @private\n */\nvjs.TextTrack.prototype.language_;\n\n/**\n * Get the track language value\n * @return {String}\n */\nvjs.TextTrack.prototype.language = function(){\n  return this.language_;\n};\n\n/**\n * Track label e.g. 'English'\n * Spec def: readonly attribute DOMString label;\n * @private\n */\nvjs.TextTrack.prototype.label_;\n\n/**\n * Get the track label value\n * @return {String}\n */\nvjs.TextTrack.prototype.label = function(){\n  return this.label_;\n};\n\n/**\n * All cues of the track. Cues have a startTime, endTime, text, and other properties.\n * Spec def: readonly attribute TextTrackCueList cues;\n * @private\n */\nvjs.TextTrack.prototype.cues_;\n\n/**\n * Get the track cues\n * @return {Array}\n */\nvjs.TextTrack.prototype.cues = function(){\n  return this.cues_;\n};\n\n/**\n * ActiveCues is all cues that are currently showing\n * Spec def: readonly attribute TextTrackCueList activeCues;\n * @private\n */\nvjs.TextTrack.prototype.activeCues_;\n\n/**\n * Get the track active cues\n * @return {Array}\n */\nvjs.TextTrack.prototype.activeCues = function(){\n  return this.activeCues_;\n};\n\n/**\n * ReadyState describes if the text file has been loaded\n * const unsigned short NONE = 0;\n * const unsigned short LOADING = 1;\n * const unsigned short LOADED = 2;\n * const unsigned short ERROR = 3;\n * readonly attribute unsigned short readyState;\n * @private\n */\nvjs.TextTrack.prototype.readyState_;\n\n/**\n * Get the track readyState\n * @return {Number}\n */\nvjs.TextTrack.prototype.readyState = function(){\n  return this.readyState_;\n};\n\n/**\n * Mode describes if the track is showing, hidden, or disabled\n * const unsigned short OFF = 0;\n * const unsigned short HIDDEN = 1; (still triggering cuechange events, but not visible)\n * const unsigned short SHOWING = 2;\n * attribute unsigned short mode;\n * @private\n */\nvjs.TextTrack.prototype.mode_;\n\n/**\n * Get the track mode\n * @return {Number}\n */\nvjs.TextTrack.prototype.mode = function(){\n  return this.mode_;\n};\n\n/**\n * Change the font size of the text track to make it larger when playing in fullscreen mode\n * and restore it to its normal size when not in fullscreen mode.\n */\nvjs.TextTrack.prototype.adjustFontSize = function(){\n    if (this.player_.isFullScreen) {\n        // Scale the font by the same factor as increasing the video width to the full screen window width.\n        // Additionally, multiply that factor by 1.4, which is the default font size for\n        // the caption track (from the CSS)\n        this.el_.style.fontSize = screen.width / this.player_.width() * 1.4 * 100 + '%';\n    } else {\n        // Change the font size of the text track back to its original non-fullscreen size\n        this.el_.style.fontSize = '';\n    }\n};\n\n/**\n * Create basic div to hold cue text\n * @return {Element}\n */\nvjs.TextTrack.prototype.createEl = function(){\n  return vjs.Component.prototype.createEl.call(this, 'div', {\n    className: 'vjs-' + this.kind_ + ' vjs-text-track'\n  });\n};\n\n/**\n * Show: Mode Showing (2)\n * Indicates that the text track is active. If no attempt has yet been made to obtain the track's cues, the user agent will perform such an attempt momentarily.\n * The user agent is maintaining a list of which cues are active, and events are being fired accordingly.\n * In addition, for text tracks whose kind is subtitles or captions, the cues are being displayed over the video as appropriate;\n * for text tracks whose kind is descriptions, the user agent is making the cues available to the user in a non-visual fashion;\n * and for text tracks whose kind is chapters, the user agent is making available to the user a mechanism by which the user can navigate to any point in the media resource by selecting a cue.\n * The showing by default state is used in conjunction with the default attribute on track elements to indicate that the text track was enabled due to that attribute.\n * This allows the user agent to override the state if a later track is discovered that is more appropriate per the user's preferences.\n */\nvjs.TextTrack.prototype.show = function(){\n  this.activate();\n\n  this.mode_ = 2;\n\n  // Show element.\n  vjs.Component.prototype.show.call(this);\n};\n\n/**\n * Hide: Mode Hidden (1)\n * Indicates that the text track is active, but that the user agent is not actively displaying the cues.\n * If no attempt has yet been made to obtain the track's cues, the user agent will perform such an attempt momentarily.\n * The user agent is maintaining a list of which cues are active, and events are being fired accordingly.\n */\nvjs.TextTrack.prototype.hide = function(){\n  // When hidden, cues are still triggered. Disable to stop triggering.\n  this.activate();\n\n  this.mode_ = 1;\n\n  // Hide element.\n  vjs.Component.prototype.hide.call(this);\n};\n\n/**\n * Disable: Mode Off/Disable (0)\n * Indicates that the text track is not active. Other than for the purposes of exposing the track in the DOM, the user agent is ignoring the text track.\n * No cues are active, no events are fired, and the user agent will not attempt to obtain the track's cues.\n */\nvjs.TextTrack.prototype.disable = function(){\n  // If showing, hide.\n  if (this.mode_ == 2) { this.hide(); }\n\n  // Stop triggering cues\n  this.deactivate();\n\n  // Switch Mode to Off\n  this.mode_ = 0;\n};\n\n/**\n * Turn on cue tracking. Tracks that are showing OR hidden are active.\n */\nvjs.TextTrack.prototype.activate = function(){\n  // Load text file if it hasn't been yet.\n  if (this.readyState_ === 0) { this.load(); }\n\n  // Only activate if not already active.\n  if (this.mode_ === 0) {\n    // Update current cue on timeupdate\n    // Using unique ID for bind function so other tracks don't remove listener\n    this.player_.on('timeupdate', vjs.bind(this, this.update, this.id_));\n\n    // Reset cue time on media end\n    this.player_.on('ended', vjs.bind(this, this.reset, this.id_));\n\n    // Add to display\n    if (this.kind_ === 'captions' || this.kind_ === 'subtitles') {\n      this.player_.getChild('textTrackDisplay').addChild(this);\n    }\n  }\n};\n\n/**\n * Turn off cue tracking.\n */\nvjs.TextTrack.prototype.deactivate = function(){\n  // Using unique ID for bind function so other tracks don't remove listener\n  this.player_.off('timeupdate', vjs.bind(this, this.update, this.id_));\n  this.player_.off('ended', vjs.bind(this, this.reset, this.id_));\n  this.reset(); // Reset\n\n  // Remove from display\n  this.player_.getChild('textTrackDisplay').removeChild(this);\n};\n\n// A readiness state\n// One of the following:\n//\n// Not loaded\n// Indicates that the text track is known to exist (e.g. it has been declared with a track element), but its cues have not been obtained.\n//\n// Loading\n// Indicates that the text track is loading and there have been no fatal errors encountered so far. Further cues might still be added to the track.\n//\n// Loaded\n// Indicates that the text track has been loaded with no fatal errors. No new cues will be added to the track except if the text track corresponds to a MutableTextTrack object.\n//\n// Failed to load\n// Indicates that the text track was enabled, but when the user agent attempted to obtain it, this failed in some way (e.g. URL could not be resolved, network error, unknown text track format). Some or all of the cues are likely missing and will not be obtained.\nvjs.TextTrack.prototype.load = function(){\n\n  // Only load if not loaded yet.\n  if (this.readyState_ === 0) {\n    this.readyState_ = 1;\n    vjs.get(this.src_, vjs.bind(this, this.parseCues), vjs.bind(this, this.onError));\n  }\n\n};\n\nvjs.TextTrack.prototype.onError = function(err){\n  this.error = err;\n  this.readyState_ = 3;\n  this.trigger('error');\n};\n\n// Parse the WebVTT text format for cue times.\n// TODO: Separate parser into own class so alternative timed text formats can be used. (TTML, DFXP)\nvjs.TextTrack.prototype.parseCues = function(srcContent) {\n  var cue, time, text,\n      lines = srcContent.split('\\n'),\n      line = '', id;\n\n  for (var i=1, j=lines.length; i<j; i++) {\n    // Line 0 should be 'WEBVTT', so skipping i=0\n\n    line = vjs.trim(lines[i]); // Trim whitespace and linebreaks\n\n    if (line) { // Loop until a line with content\n\n      // First line could be an optional cue ID\n      // Check if line has the time separator\n      if (line.indexOf('-->') == -1) {\n        id = line;\n        // Advance to next line for timing.\n        line = vjs.trim(lines[++i]);\n      } else {\n        id = this.cues_.length;\n      }\n\n      // First line - Number\n      cue = {\n        id: id, // Cue Number\n        index: this.cues_.length // Position in Array\n      };\n\n      // Timing line\n      time = line.split(' --> ');\n      cue.startTime = this.parseCueTime(time[0]);\n      cue.endTime = this.parseCueTime(time[1]);\n\n      // Additional lines - Cue Text\n      text = [];\n\n      // Loop until a blank line or end of lines\n      // Assumeing trim('') returns false for blank lines\n      while (lines[++i] && (line = vjs.trim(lines[i]))) {\n        text.push(line);\n      }\n\n      cue.text = text.join('<br/>');\n\n      // Add this cue\n      this.cues_.push(cue);\n    }\n  }\n\n  this.readyState_ = 2;\n  this.trigger('loaded');\n};\n\n\nvjs.TextTrack.prototype.parseCueTime = function(timeText) {\n  var parts = timeText.split(':'),\n      time = 0,\n      hours, minutes, other, seconds, ms;\n\n  // Check if optional hours place is included\n  // 00:00:00.000 vs. 00:00.000\n  if (parts.length == 3) {\n    hours = parts[0];\n    minutes = parts[1];\n    other = parts[2];\n  } else {\n    hours = 0;\n    minutes = parts[0];\n    other = parts[1];\n  }\n\n  // Break other (seconds, milliseconds, and flags) by spaces\n  // TODO: Make additional cue layout settings work with flags\n  other = other.split(/\\s+/);\n  // Remove seconds. Seconds is the first part before any spaces.\n  seconds = other.splice(0,1)[0];\n  // Could use either . or , for decimal\n  seconds = seconds.split(/\\.|,/);\n  // Get milliseconds\n  ms = parseFloat(seconds[1]);\n  seconds = seconds[0];\n\n  // hours => seconds\n  time += parseFloat(hours) * 3600;\n  // minutes => seconds\n  time += parseFloat(minutes) * 60;\n  // Add seconds\n  time += parseFloat(seconds);\n  // Add milliseconds\n  if (ms) { time += ms/1000; }\n\n  return time;\n};\n\n// Update active cues whenever timeupdate events are triggered on the player.\nvjs.TextTrack.prototype.update = function(){\n  if (this.cues_.length > 0) {\n\n    // Get curent player time\n    var time = this.player_.currentTime();\n\n    // Check if the new time is outside the time box created by the the last update.\n    if (this.prevChange === undefined || time < this.prevChange || this.nextChange <= time) {\n      var cues = this.cues_,\n\n          // Create a new time box for this state.\n          newNextChange = this.player_.duration(), // Start at beginning of the timeline\n          newPrevChange = 0, // Start at end\n\n          reverse = false, // Set the direction of the loop through the cues. Optimized the cue check.\n          newCues = [], // Store new active cues.\n\n          // Store where in the loop the current active cues are, to provide a smart starting point for the next loop.\n          firstActiveIndex, lastActiveIndex,\n          cue, i; // Loop vars\n\n      // Check if time is going forwards or backwards (scrubbing/rewinding)\n      // If we know the direction we can optimize the starting position and direction of the loop through the cues array.\n      if (time >= this.nextChange || this.nextChange === undefined) { // NextChange should happen\n        // Forwards, so start at the index of the first active cue and loop forward\n        i = (this.firstActiveIndex !== undefined) ? this.firstActiveIndex : 0;\n      } else {\n        // Backwards, so start at the index of the last active cue and loop backward\n        reverse = true;\n        i = (this.lastActiveIndex !== undefined) ? this.lastActiveIndex : cues.length - 1;\n      }\n\n      while (true) { // Loop until broken\n        cue = cues[i];\n\n        // Cue ended at this point\n        if (cue.endTime <= time) {\n          newPrevChange = Math.max(newPrevChange, cue.endTime);\n\n          if (cue.active) {\n            cue.active = false;\n          }\n\n          // No earlier cues should have an active start time.\n          // Nevermind. Assume first cue could have a duration the same as the video.\n          // In that case we need to loop all the way back to the beginning.\n          // if (reverse && cue.startTime) { break; }\n\n        // Cue hasn't started\n        } else if (time < cue.startTime) {\n          newNextChange = Math.min(newNextChange, cue.startTime);\n\n          if (cue.active) {\n            cue.active = false;\n          }\n\n          // No later cues should have an active start time.\n          if (!reverse) { break; }\n\n        // Cue is current\n        } else {\n\n          if (reverse) {\n            // Add cue to front of array to keep in time order\n            newCues.splice(0,0,cue);\n\n            // If in reverse, the first current cue is our lastActiveCue\n            if (lastActiveIndex === undefined) { lastActiveIndex = i; }\n            firstActiveIndex = i;\n          } else {\n            // Add cue to end of array\n            newCues.push(cue);\n\n            // If forward, the first current cue is our firstActiveIndex\n            if (firstActiveIndex === undefined) { firstActiveIndex = i; }\n            lastActiveIndex = i;\n          }\n\n          newNextChange = Math.min(newNextChange, cue.endTime);\n          newPrevChange = Math.max(newPrevChange, cue.startTime);\n\n          cue.active = true;\n        }\n\n        if (reverse) {\n          // Reverse down the array of cues, break if at first\n          if (i === 0) { break; } else { i--; }\n        } else {\n          // Walk up the array fo cues, break if at last\n          if (i === cues.length - 1) { break; } else { i++; }\n        }\n\n      }\n\n      this.activeCues_ = newCues;\n      this.nextChange = newNextChange;\n      this.prevChange = newPrevChange;\n      this.firstActiveIndex = firstActiveIndex;\n      this.lastActiveIndex = lastActiveIndex;\n\n      this.updateDisplay();\n\n      this.trigger('cuechange');\n    }\n  }\n};\n\n// Add cue HTML to display\nvjs.TextTrack.prototype.updateDisplay = function(){\n  var cues = this.activeCues_,\n      html = '',\n      i=0,j=cues.length;\n\n  for (;i<j;i++) {\n    html += '<span class=\"vjs-tt-cue\">'+cues[i].text+'</span>';\n  }\n\n  this.el_.innerHTML = html;\n};\n\n// Set all loop helper values back\nvjs.TextTrack.prototype.reset = function(){\n  this.nextChange = 0;\n  this.prevChange = this.player_.duration();\n  this.firstActiveIndex = 0;\n  this.lastActiveIndex = 0;\n};\n\n// Create specific track types\n/**\n * The track component for managing the hiding and showing of captions\n *\n * @constructor\n */\nvjs.CaptionsTrack = vjs.TextTrack.extend();\nvjs.CaptionsTrack.prototype.kind_ = 'captions';\n// Exporting here because Track creation requires the track kind\n// to be available on global object. e.g. new window['videojs'][Kind + 'Track']\n\n/**\n * The track component for managing the hiding and showing of subtitles\n *\n * @constructor\n */\nvjs.SubtitlesTrack = vjs.TextTrack.extend();\nvjs.SubtitlesTrack.prototype.kind_ = 'subtitles';\n\n/**\n * The track component for managing the hiding and showing of chapters\n *\n * @constructor\n */\nvjs.ChaptersTrack = vjs.TextTrack.extend();\nvjs.ChaptersTrack.prototype.kind_ = 'chapters';\n\n\n/* Text Track Display\n============================================================================= */\n// Global container for both subtitle and captions text. Simple div container.\n\n/**\n * The component for displaying text track cues\n *\n * @constructor\n */\nvjs.TextTrackDisplay = vjs.Component.extend({\n  /** @constructor */\n  init: function(player, options, ready){\n    vjs.Component.call(this, player, options, ready);\n\n    // This used to be called during player init, but was causing an error\n    // if a track should show by default and the display hadn't loaded yet.\n    // Should probably be moved to an external track loader when we support\n    // tracks that don't need a display.\n    if (player.options_['tracks'] && player.options_['tracks'].length > 0) {\n      this.player_.addTextTracks(player.options_['tracks']);\n    }\n  }\n});\n\nvjs.TextTrackDisplay.prototype.createEl = function(){\n  return vjs.Component.prototype.createEl.call(this, 'div', {\n    className: 'vjs-text-track-display'\n  });\n};\n\n\n/**\n * The specific menu item type for selecting a language within a text track kind\n *\n * @constructor\n */\nvjs.TextTrackMenuItem = vjs.MenuItem.extend({\n  /** @constructor */\n  init: function(player, options){\n    var track = this.track = options['track'];\n\n    // Modify options for parent MenuItem class's init.\n    options['label'] = track.label();\n    options['selected'] = track.dflt();\n    vjs.MenuItem.call(this, player, options);\n\n    this.player_.on(track.kind() + 'trackchange', vjs.bind(this, this.update));\n  }\n});\n\nvjs.TextTrackMenuItem.prototype.onClick = function(){\n  vjs.MenuItem.prototype.onClick.call(this);\n  this.player_.showTextTrack(this.track.id_, this.track.kind());\n};\n\nvjs.TextTrackMenuItem.prototype.update = function(){\n  this.selected(this.track.mode() == 2);\n};\n\n/**\n * A special menu item for turning of a specific type of text track\n *\n * @constructor\n */\nvjs.OffTextTrackMenuItem = vjs.TextTrackMenuItem.extend({\n  /** @constructor */\n  init: function(player, options){\n    // Create pseudo track info\n    // Requires options['kind']\n    options['track'] = {\n      kind: function() { return options['kind']; },\n      player: player,\n      label: function(){ return options['kind'] + ' off'; },\n      dflt: function(){ return false; },\n      mode: function(){ return false; }\n    };\n    vjs.TextTrackMenuItem.call(this, player, options);\n    this.selected(true);\n  }\n});\n\nvjs.OffTextTrackMenuItem.prototype.onClick = function(){\n  vjs.TextTrackMenuItem.prototype.onClick.call(this);\n  this.player_.showTextTrack(this.track.id_, this.track.kind());\n};\n\nvjs.OffTextTrackMenuItem.prototype.update = function(){\n  var tracks = this.player_.textTracks(),\n      i=0, j=tracks.length, track,\n      off = true;\n\n  for (;i<j;i++) {\n    track = tracks[i];\n    if (track.kind() == this.track.kind() && track.mode() == 2) {\n      off = false;\n    }\n  }\n\n  this.selected(off);\n};\n\n/**\n * The base class for buttons that toggle specific text track types (e.g. subtitles)\n *\n * @constructor\n */\nvjs.TextTrackButton = vjs.MenuButton.extend({\n  /** @constructor */\n  init: function(player, options){\n    vjs.MenuButton.call(this, player, options);\n\n    if (this.items.length <= 1) {\n      this.hide();\n    }\n  }\n});\n\n// vjs.TextTrackButton.prototype.buttonPressed = false;\n\n// vjs.TextTrackButton.prototype.createMenu = function(){\n//   var menu = new vjs.Menu(this.player_);\n\n//   // Add a title list item to the top\n//   // menu.el().appendChild(vjs.createEl('li', {\n//   //   className: 'vjs-menu-title',\n//   //   innerHTML: vjs.capitalize(this.kind_),\n//   //   tabindex: -1\n//   // }));\n\n//   this.items = this.createItems();\n\n//   // Add menu items to the menu\n//   for (var i = 0; i < this.items.length; i++) {\n//     menu.addItem(this.items[i]);\n//   }\n\n//   // Add list to element\n//   this.addChild(menu);\n\n//   return menu;\n// };\n\n// Create a menu item for each text track\nvjs.TextTrackButton.prototype.createItems = function(){\n  var items = [], track;\n\n  // Add an OFF menu item to turn all tracks off\n  items.push(new vjs.OffTextTrackMenuItem(this.player_, { 'kind': this.kind_ }));\n\n  for (var i = 0; i < this.player_.textTracks().length; i++) {\n    track = this.player_.textTracks()[i];\n    if (track.kind() === this.kind_) {\n      items.push(new vjs.TextTrackMenuItem(this.player_, {\n        'track': track\n      }));\n    }\n  }\n\n  return items;\n};\n\n/**\n * The button component for toggling and selecting captions\n *\n * @constructor\n */\nvjs.CaptionsButton = vjs.TextTrackButton.extend({\n  /** @constructor */\n  init: function(player, options, ready){\n    vjs.TextTrackButton.call(this, player, options, ready);\n    this.el_.setAttribute('aria-label','Captions Menu');\n  }\n});\nvjs.CaptionsButton.prototype.kind_ = 'captions';\nvjs.CaptionsButton.prototype.buttonText = 'Captions';\nvjs.CaptionsButton.prototype.className = 'vjs-captions-button';\n\n/**\n * The button component for toggling and selecting subtitles\n *\n * @constructor\n */\nvjs.SubtitlesButton = vjs.TextTrackButton.extend({\n  /** @constructor */\n  init: function(player, options, ready){\n    vjs.TextTrackButton.call(this, player, options, ready);\n    this.el_.setAttribute('aria-label','Subtitles Menu');\n  }\n});\nvjs.SubtitlesButton.prototype.kind_ = 'subtitles';\nvjs.SubtitlesButton.prototype.buttonText = 'Subtitles';\nvjs.SubtitlesButton.prototype.className = 'vjs-subtitles-button';\n\n// Chapters act much differently than other text tracks\n// Cues are navigation vs. other tracks of alternative languages\n/**\n * The button component for toggling and selecting chapters\n *\n * @constructor\n */\nvjs.ChaptersButton = vjs.TextTrackButton.extend({\n  /** @constructor */\n  init: function(player, options, ready){\n    vjs.TextTrackButton.call(this, player, options, ready);\n    this.el_.setAttribute('aria-label','Chapters Menu');\n  }\n});\nvjs.ChaptersButton.prototype.kind_ = 'chapters';\nvjs.ChaptersButton.prototype.buttonText = 'Chapters';\nvjs.ChaptersButton.prototype.className = 'vjs-chapters-button';\n\n// Create a menu item for each text track\nvjs.ChaptersButton.prototype.createItems = function(){\n  var items = [], track;\n\n  for (var i = 0; i < this.player_.textTracks().length; i++) {\n    track = this.player_.textTracks()[i];\n    if (track.kind() === this.kind_) {\n      items.push(new vjs.TextTrackMenuItem(this.player_, {\n        'track': track\n      }));\n    }\n  }\n\n  return items;\n};\n\nvjs.ChaptersButton.prototype.createMenu = function(){\n  var tracks = this.player_.textTracks(),\n      i = 0,\n      j = tracks.length,\n      track, chaptersTrack,\n      items = this.items = [];\n\n  for (;i<j;i++) {\n    track = tracks[i];\n    if (track.kind() == this.kind_ && track.dflt()) {\n      if (track.readyState() < 2) {\n        this.chaptersTrack = track;\n        track.on('loaded', vjs.bind(this, this.createMenu));\n        return;\n      } else {\n        chaptersTrack = track;\n        break;\n      }\n    }\n  }\n\n  var menu = this.menu = new vjs.Menu(this.player_);\n\n  menu.el_.appendChild(vjs.createEl('li', {\n    className: 'vjs-menu-title',\n    innerHTML: vjs.capitalize(this.kind_),\n    tabindex: -1\n  }));\n\n  if (chaptersTrack) {\n    var cues = chaptersTrack.cues_, cue, mi;\n    i = 0;\n    j = cues.length;\n\n    for (;i<j;i++) {\n      cue = cues[i];\n\n      mi = new vjs.ChaptersTrackMenuItem(this.player_, {\n        'track': chaptersTrack,\n        'cue': cue\n      });\n\n      items.push(mi);\n\n      menu.addChild(mi);\n    }\n  }\n\n  if (this.items.length > 0) {\n    this.show();\n  }\n\n  return menu;\n};\n\n\n/**\n * @constructor\n */\nvjs.ChaptersTrackMenuItem = vjs.MenuItem.extend({\n  /** @constructor */\n  init: function(player, options){\n    var track = this.track = options['track'],\n        cue = this.cue = options['cue'],\n        currentTime = player.currentTime();\n\n    // Modify options for parent MenuItem class's init.\n    options['label'] = cue.text;\n    options['selected'] = (cue.startTime <= currentTime && currentTime < cue.endTime);\n    vjs.MenuItem.call(this, player, options);\n\n    track.on('cuechange', vjs.bind(this, this.update));\n  }\n});\n\nvjs.ChaptersTrackMenuItem.prototype.onClick = function(){\n  vjs.MenuItem.prototype.onClick.call(this);\n  this.player_.currentTime(this.cue.startTime);\n  this.update(this.cue.startTime);\n};\n\nvjs.ChaptersTrackMenuItem.prototype.update = function(){\n  var cue = this.cue,\n      currentTime = this.player_.currentTime();\n\n  // vjs.log(currentTime, cue.startTime);\n  this.selected(cue.startTime <= currentTime && currentTime < cue.endTime);\n};\n\n// Add Buttons to controlBar\nvjs.obj.merge(vjs.ControlBar.prototype.options_['children'], {\n  'subtitlesButton': {},\n  'captionsButton': {},\n  'chaptersButton': {}\n});\n\n// vjs.Cue = vjs.Component.extend({\n//   /** @constructor */\n//   init: function(player, options){\n//     vjs.Component.call(this, player, options);\n//   }\n// });\n/**\n * @fileoverview Add JSON support\n * @suppress {undefinedVars}\n * (Compiler doesn't like JSON not being declared)\n */\n\n/**\n * Javascript JSON implementation\n * (Parse Method Only)\n * https://github.com/douglascrockford/JSON-js/blob/master/json2.js\n * Only using for parse method when parsing data-setup attribute JSON.\n * @suppress {undefinedVars}\n * @namespace\n * @private\n */\nvjs.JSON;\n\nif (typeof window.JSON !== 'undefined' && window.JSON.parse === 'function') {\n  vjs.JSON = window.JSON;\n\n} else {\n  vjs.JSON = {};\n\n  var cx = /[\\u0000\\u00ad\\u0600-\\u0604\\u070f\\u17b4\\u17b5\\u200c-\\u200f\\u2028-\\u202f\\u2060-\\u206f\\ufeff\\ufff0-\\uffff]/g;\n\n  /**\n   * parse the json\n   *\n   * @memberof vjs.JSON\n   * @return {Object|Array} The parsed JSON\n   */\n  vjs.JSON.parse = function (text, reviver) {\n      var j;\n\n      function walk(holder, key) {\n          var k, v, value = holder[key];\n          if (value && typeof value === 'object') {\n              for (k in value) {\n                  if (Object.prototype.hasOwnProperty.call(value, k)) {\n                      v = walk(value, k);\n                      if (v !== undefined) {\n                          value[k] = v;\n                      } else {\n                          delete value[k];\n                      }\n                  }\n              }\n          }\n          return reviver.call(holder, key, value);\n      }\n      text = String(text);\n      cx.lastIndex = 0;\n      if (cx.test(text)) {\n          text = text.replace(cx, function (a) {\n              return '\\\\u' +\n                  ('0000' + a.charCodeAt(0).toString(16)).slice(-4);\n          });\n      }\n\n      if (/^[\\],:{}\\s]*$/\n              .test(text.replace(/\\\\(?:[\"\\\\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')\n                  .replace(/\"[^\"\\\\\\n\\r]*\"|true|false|null|-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?/g, ']')\n                  .replace(/(?:^|:|,)(?:\\s*\\[)+/g, ''))) {\n\n          j = eval('(' + text + ')');\n\n          return typeof reviver === 'function' ?\n              walk({'': j}, '') : j;\n      }\n\n      throw new SyntaxError('JSON.parse(): invalid or malformed JSON data');\n  };\n}\n/**\n * @fileoverview Functions for automatically setting up a player\n * based on the data-setup attribute of the video tag\n */\n\n// Automatically set up any tags that have a data-setup attribute\nvjs.autoSetup = function(){\n  var options, vid, player,\n      vids = document.getElementsByTagName('video');\n\n  // Check if any media elements exist\n  if (vids && vids.length > 0) {\n\n    for (var i=0,j=vids.length; i<j; i++) {\n      vid = vids[i];\n\n      // Check if element exists, has getAttribute func.\n      // IE seems to consider typeof el.getAttribute == 'object' instead of 'function' like expected, at least when loading the player immediately.\n      if (vid && vid.getAttribute) {\n\n        // Make sure this player hasn't already been set up.\n        if (vid['player'] === undefined) {\n          options = vid.getAttribute('data-setup');\n\n          // Check if data-setup attr exists.\n          // We only auto-setup if they've added the data-setup attr.\n          if (options !== null) {\n\n            // Parse options JSON\n            // If empty string, make it a parsable json object.\n            options = vjs.JSON.parse(options || '{}');\n\n            // Create new video.js instance.\n            player = videojs(vid, options);\n          }\n        }\n\n      // If getAttribute isn't defined, we need to wait for the DOM.\n      } else {\n        vjs.autoSetupTimeout(1);\n        break;\n      }\n    }\n\n  // No videos were found, so keep looping unless page is finisehd loading.\n  } else if (!vjs.windowLoaded) {\n    vjs.autoSetupTimeout(1);\n  }\n};\n\n// Pause to let the DOM keep processing\nvjs.autoSetupTimeout = function(wait){\n  setTimeout(vjs.autoSetup, wait);\n};\n\nif (document.readyState === 'complete') {\n  vjs.windowLoaded = true;\n} else {\n  vjs.one(window, 'load', function(){\n    vjs.windowLoaded = true;\n  });\n}\n\n// Run Auto-load players\n// You have to wait at least once in case this script is loaded after your video in the DOM (weird behavior only with minified version)\nvjs.autoSetupTimeout(1);\n/**\n * the method for registering a video.js plugin\n *\n * @param  {String} name The name of the plugin\n * @param  {Function} init The function that is run when the player inits\n */\nvjs.plugin = function(name, init){\n  vjs.Player.prototype[name] = init;\n};\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/video-js/video.js",
    "content": "/*! Video.js v4.3.0 Copyright 2013 Brightcove, Inc. https://github.com/videojs/video.js/blob/master/LICENSE */ (function() {var b=void 0,f=!0,h=null,l=!1;function m(){return function(){}}function p(a){return function(){return this[a]}}function s(a){return function(){return a}}var t;document.createElement(\"video\");document.createElement(\"audio\");document.createElement(\"track\");function u(a,c,d){if(\"string\"===typeof a){0===a.indexOf(\"#\")&&(a=a.slice(1));if(u.xa[a])return u.xa[a];a=u.w(a)}if(!a||!a.nodeName)throw new TypeError(\"The element or ID supplied is not valid. (videojs)\");return a.player||new u.s(a,c,d)}var v=u;\nwindow.Td=window.Ud=u;u.Tb=\"4.3\";u.Fc=\"https:\"==document.location.protocol?\"https://\":\"http://\";u.options={techOrder:[\"html5\",\"flash\"],html5:{},flash:{},width:300,height:150,defaultVolume:0,children:{mediaLoader:{},posterImage:{},textTrackDisplay:{},loadingSpinner:{},bigPlayButton:{},controlBar:{}},notSupportedMessage:'Sorry, no compatible source and playback technology were found for this video. Try using another browser like <a href=\"http://bit.ly/ccMUEC\">Chrome</a> or download the latest <a href=\"http://adobe.ly/mwfN1\">Adobe Flash Player</a>.'};\n\"GENERATED_CDN_VSN\"!==u.Tb&&(v.options.flash.swf=u.Fc+\"vjs.zencdn.net/\"+u.Tb+\"/video-js.swf\");u.xa={};u.la=u.CoreObject=m();u.la.extend=function(a){var c,d;a=a||{};c=a.init||a.i||this.prototype.init||this.prototype.i||m();d=function(){c.apply(this,arguments)};d.prototype=u.k.create(this.prototype);d.prototype.constructor=d;d.extend=u.la.extend;d.create=u.la.create;for(var e in a)a.hasOwnProperty(e)&&(d.prototype[e]=a[e]);return d};\nu.la.create=function(){var a=u.k.create(this.prototype);this.apply(a,arguments);return a};u.d=function(a,c,d){var e=u.getData(a);e.z||(e.z={});e.z[c]||(e.z[c]=[]);d.t||(d.t=u.t++);e.z[c].push(d);e.W||(e.disabled=l,e.W=function(c){if(!e.disabled){c=u.kc(c);var d=e.z[c.type];if(d)for(var d=d.slice(0),k=0,q=d.length;k<q&&!c.pc();k++)d[k].call(a,c)}});1==e.z[c].length&&(document.addEventListener?a.addEventListener(c,e.W,l):document.attachEvent&&a.attachEvent(\"on\"+c,e.W))};\nu.o=function(a,c,d){if(u.oc(a)){var e=u.getData(a);if(e.z)if(c){var g=e.z[c];if(g){if(d){if(d.t)for(e=0;e<g.length;e++)g[e].t===d.t&&g.splice(e--,1)}else e.z[c]=[];u.gc(a,c)}}else for(g in e.z)c=g,e.z[c]=[],u.gc(a,c)}};u.gc=function(a,c){var d=u.getData(a);0===d.z[c].length&&(delete d.z[c],document.removeEventListener?a.removeEventListener(c,d.W,l):document.detachEvent&&a.detachEvent(\"on\"+c,d.W));u.Bb(d.z)&&(delete d.z,delete d.W,delete d.disabled);u.Bb(d)&&u.vc(a)};\nu.kc=function(a){function c(){return f}function d(){return l}if(!a||!a.Cb){var e=a||window.event;a={};for(var g in e)\"layerX\"!==g&&\"layerY\"!==g&&(a[g]=e[g]);a.target||(a.target=a.srcElement||document);a.relatedTarget=a.fromElement===a.target?a.toElement:a.fromElement;a.preventDefault=function(){e.preventDefault&&e.preventDefault();a.returnValue=l;a.Ab=c};a.Ab=d;a.stopPropagation=function(){e.stopPropagation&&e.stopPropagation();a.cancelBubble=f;a.Cb=c};a.Cb=d;a.stopImmediatePropagation=function(){e.stopImmediatePropagation&&\ne.stopImmediatePropagation();a.pc=c;a.stopPropagation()};a.pc=d;if(a.clientX!=h){g=document.documentElement;var j=document.body;a.pageX=a.clientX+(g&&g.scrollLeft||j&&j.scrollLeft||0)-(g&&g.clientLeft||j&&j.clientLeft||0);a.pageY=a.clientY+(g&&g.scrollTop||j&&j.scrollTop||0)-(g&&g.clientTop||j&&j.clientTop||0)}a.which=a.charCode||a.keyCode;a.button!=h&&(a.button=a.button&1?0:a.button&4?1:a.button&2?2:0)}return a};\nu.j=function(a,c){var d=u.oc(a)?u.getData(a):{},e=a.parentNode||a.ownerDocument;\"string\"===typeof c&&(c={type:c,target:a});c=u.kc(c);d.W&&d.W.call(a,c);if(e&&!c.Cb()&&c.bubbles!==l)u.j(e,c);else if(!e&&!c.Ab()&&(d=u.getData(c.target),c.target[c.type])){d.disabled=f;if(\"function\"===typeof c.target[c.type])c.target[c.type]();d.disabled=l}return!c.Ab()};u.U=function(a,c,d){function e(){u.o(a,c,e);d.apply(this,arguments)}e.t=d.t=d.t||u.t++;u.d(a,c,e)};var w=Object.prototype.hasOwnProperty;\nu.e=function(a,c){var d,e;d=document.createElement(a||\"div\");for(e in c)w.call(c,e)&&(-1!==e.indexOf(\"aria-\")||\"role\"==e?d.setAttribute(e,c[e]):d[e]=c[e]);return d};u.$=function(a){return a.charAt(0).toUpperCase()+a.slice(1)};u.k={};u.k.create=Object.create||function(a){function c(){}c.prototype=a;return new c};u.k.ua=function(a,c,d){for(var e in a)w.call(a,e)&&c.call(d||this,e,a[e])};u.k.B=function(a,c){if(!c)return a;for(var d in c)w.call(c,d)&&(a[d]=c[d]);return a};\nu.k.ic=function(a,c){var d,e,g;a=u.k.copy(a);for(d in c)w.call(c,d)&&(e=a[d],g=c[d],a[d]=u.k.qc(e)&&u.k.qc(g)?u.k.ic(e,g):c[d]);return a};u.k.copy=function(a){return u.k.B({},a)};u.k.qc=function(a){return!!a&&\"object\"===typeof a&&\"[object Object]\"===a.toString()&&a.constructor===Object};u.bind=function(a,c,d){function e(){return c.apply(a,arguments)}c.t||(c.t=u.t++);e.t=d?d+\"_\"+c.t:c.t;return e};u.ra={};u.t=1;u.expando=\"vdata\"+(new Date).getTime();\nu.getData=function(a){var c=a[u.expando];c||(c=a[u.expando]=u.t++,u.ra[c]={});return u.ra[c]};u.oc=function(a){a=a[u.expando];return!(!a||u.Bb(u.ra[a]))};u.vc=function(a){var c=a[u.expando];if(c){delete u.ra[c];try{delete a[u.expando]}catch(d){a.removeAttribute?a.removeAttribute(u.expando):a[u.expando]=h}}};u.Bb=function(a){for(var c in a)if(a[c]!==h)return l;return f};u.n=function(a,c){-1==(\" \"+a.className+\" \").indexOf(\" \"+c+\" \")&&(a.className=\"\"===a.className?c:a.className+\" \"+c)};\nu.u=function(a,c){var d,e;if(-1!=a.className.indexOf(c)){d=a.className.split(\" \");for(e=d.length-1;0<=e;e--)d[e]===c&&d.splice(e,1);a.className=d.join(\" \")}};u.na=u.e(\"video\");u.F=navigator.userAgent;u.Mc=/iPhone/i.test(u.F);u.Lc=/iPad/i.test(u.F);u.Nc=/iPod/i.test(u.F);u.Kc=u.Mc||u.Lc||u.Nc;var aa=u,x;var y=u.F.match(/OS (\\d+)_/i);x=y&&y[1]?y[1]:b;aa.Fd=x;u.Ic=/Android/i.test(u.F);var ba=u,z;var A=u.F.match(/Android (\\d+)(?:\\.(\\d+))?(?:\\.(\\d+))*/i),B,C;\nA?(B=A[1]&&parseFloat(A[1]),C=A[2]&&parseFloat(A[2]),z=B&&C?parseFloat(A[1]+\".\"+A[2]):B?B:h):z=h;ba.Gc=z;u.Oc=u.Ic&&/webkit/i.test(u.F)&&2.3>u.Gc;u.Jc=/Firefox/i.test(u.F);u.Gd=/Chrome/i.test(u.F);u.ac=!!(\"ontouchstart\"in window||window.Hc&&document instanceof window.Hc);\nu.xb=function(a){var c,d,e,g;c={};if(a&&a.attributes&&0<a.attributes.length){d=a.attributes;for(var j=d.length-1;0<=j;j--){e=d[j].name;g=d[j].value;if(\"boolean\"===typeof a[e]||-1!==\",autoplay,controls,loop,muted,default,\".indexOf(\",\"+e+\",\"))g=g!==h?f:l;c[e]=g}}return c};\nu.Kd=function(a,c){var d=\"\";document.defaultView&&document.defaultView.getComputedStyle?d=document.defaultView.getComputedStyle(a,\"\").getPropertyValue(c):a.currentStyle&&(d=a[\"client\"+c.substr(0,1).toUpperCase()+c.substr(1)]+\"px\");return d};u.zb=function(a,c){c.firstChild?c.insertBefore(a,c.firstChild):c.appendChild(a)};u.Pb={};u.w=function(a){0===a.indexOf(\"#\")&&(a=a.slice(1));return document.getElementById(a)};\nu.La=function(a,c){c=c||a;var d=Math.floor(a%60),e=Math.floor(a/60%60),g=Math.floor(a/3600),j=Math.floor(c/60%60),k=Math.floor(c/3600);if(isNaN(a)||Infinity===a)g=e=d=\"-\";g=0<g||0<k?g+\":\":\"\";return g+(((g||10<=j)&&10>e?\"0\"+e:e)+\":\")+(10>d?\"0\"+d:d)};u.Tc=function(){document.body.focus();document.onselectstart=s(l)};u.Bd=function(){document.onselectstart=s(f)};u.trim=function(a){return(a+\"\").replace(/^\\s+|\\s+$/g,\"\")};u.round=function(a,c){c||(c=0);return Math.round(a*Math.pow(10,c))/Math.pow(10,c)};\nu.tb=function(a,c){return{length:1,start:function(){return a},end:function(){return c}}};\nu.get=function(a,c,d){var e,g;\"undefined\"===typeof XMLHttpRequest&&(window.XMLHttpRequest=function(){try{return new window.ActiveXObject(\"Msxml2.XMLHTTP.6.0\")}catch(a){}try{return new window.ActiveXObject(\"Msxml2.XMLHTTP.3.0\")}catch(c){}try{return new window.ActiveXObject(\"Msxml2.XMLHTTP\")}catch(d){}throw Error(\"This browser does not support XMLHttpRequest.\");});g=new XMLHttpRequest;try{g.open(\"GET\",a)}catch(j){d(j)}e=0===a.indexOf(\"file:\")||0===window.location.href.indexOf(\"file:\")&&-1===a.indexOf(\"http\");\ng.onreadystatechange=function(){4===g.readyState&&(200===g.status||e&&0===g.status?c(g.responseText):d&&d())};try{g.send()}catch(k){d&&d(k)}};u.td=function(a){try{var c=window.localStorage||l;c&&(c.volume=a)}catch(d){22==d.code||1014==d.code?u.log(\"LocalStorage Full (VideoJS)\",d):18==d.code?u.log(\"LocalStorage not allowed (VideoJS)\",d):u.log(\"LocalStorage Error (VideoJS)\",d)}};u.mc=function(a){a.match(/^https?:\\/\\//)||(a=u.e(\"div\",{innerHTML:'<a href=\"'+a+'\">x</a>'}).firstChild.href);return a};\nu.log=function(){u.log.history=u.log.history||[];u.log.history.push(arguments);window.console&&window.console.log(Array.prototype.slice.call(arguments))};u.ad=function(a){var c,d;a.getBoundingClientRect&&a.parentNode&&(c=a.getBoundingClientRect());if(!c)return{left:0,top:0};a=document.documentElement;d=document.body;return{left:c.left+(window.pageXOffset||d.scrollLeft)-(a.clientLeft||d.clientLeft||0),top:c.top+(window.pageYOffset||d.scrollTop)-(a.clientTop||d.clientTop||0)}};\nu.c=u.la.extend({i:function(a,c,d){this.b=a;this.g=u.k.copy(this.g);c=this.options(c);this.Q=c.id||(c.el&&c.el.id?c.el.id:a.id()+\"_component_\"+u.t++);this.gd=c.name||h;this.a=c.el||this.e();this.G=[];this.qb={};this.V={};if((a=this.g)&&a.children){var e=this;u.k.ua(a.children,function(a,c){c!==l&&!c.loadEvent&&(e[a]=e.Z(a,c))})}this.L(d)}});t=u.c.prototype;\nt.D=function(){this.j(\"dispose\");if(this.G)for(var a=this.G.length-1;0<=a;a--)this.G[a].D&&this.G[a].D();this.V=this.qb=this.G=h;this.o();this.a.parentNode&&this.a.parentNode.removeChild(this.a);u.vc(this.a);this.a=h};t.b=f;t.K=p(\"b\");t.options=function(a){return a===b?this.g:this.g=u.k.ic(this.g,a)};t.e=function(a,c){return u.e(a,c)};t.w=p(\"a\");t.id=p(\"Q\");t.name=p(\"gd\");t.children=p(\"G\");\nt.Z=function(a,c){var d,e;\"string\"===typeof a?(e=a,c=c||{},d=c.componentClass||u.$(e),c.name=e,d=new window.videojs[d](this.b||this,c)):d=a;this.G.push(d);\"function\"===typeof d.id&&(this.qb[d.id()]=d);(e=e||d.name&&d.name())&&(this.V[e]=d);\"function\"===typeof d.el&&d.el()&&(this.sa||this.a).appendChild(d.el());return d};\nt.removeChild=function(a){\"string\"===typeof a&&(a=this.V[a]);if(a&&this.G){for(var c=l,d=this.G.length-1;0<=d;d--)if(this.G[d]===a){c=f;this.G.splice(d,1);break}c&&(this.qb[a.id]=h,this.V[a.name]=h,(c=a.w())&&c.parentNode===(this.sa||this.a)&&(this.sa||this.a).removeChild(a.w()))}};t.T=s(\"\");t.d=function(a,c){u.d(this.a,a,u.bind(this,c));return this};t.o=function(a,c){u.o(this.a,a,c);return this};t.U=function(a,c){u.U(this.a,a,u.bind(this,c));return this};t.j=function(a,c){u.j(this.a,a,c);return this};\nt.L=function(a){a&&(this.aa?a.call(this):(this.Sa===b&&(this.Sa=[]),this.Sa.push(a)));return this};t.Ua=function(){this.aa=f;var a=this.Sa;if(a&&0<a.length){for(var c=0,d=a.length;c<d;c++)a[c].call(this);this.Sa=[];this.j(\"ready\")}};t.n=function(a){u.n(this.a,a);return this};t.u=function(a){u.u(this.a,a);return this};t.show=function(){this.a.style.display=\"block\";return this};t.C=function(){this.a.style.display=\"none\";return this};function D(a){a.u(\"vjs-lock-showing\")}\nt.disable=function(){this.C();this.show=m()};t.width=function(a,c){return E(this,\"width\",a,c)};t.height=function(a,c){return E(this,\"height\",a,c)};t.Xc=function(a,c){return this.width(a,f).height(c)};function E(a,c,d,e){if(d!==b)return a.a.style[c]=-1!==(\"\"+d).indexOf(\"%\")||-1!==(\"\"+d).indexOf(\"px\")?d:\"auto\"===d?\"\":d+\"px\",e||a.j(\"resize\"),a;if(!a.a)return 0;d=a.a.style[c];e=d.indexOf(\"px\");return-1!==e?parseInt(d.slice(0,e),10):parseInt(a.a[\"offset\"+u.$(c)],10)}\nu.q=u.c.extend({i:function(a,c){u.c.call(this,a,c);var d=l;this.d(\"touchstart\",function(a){a.preventDefault();d=f});this.d(\"touchmove\",function(){d=l});var e=this;this.d(\"touchend\",function(a){d&&e.p(a);a.preventDefault()});this.d(\"click\",this.p);this.d(\"focus\",this.Oa);this.d(\"blur\",this.Na)}});t=u.q.prototype;\nt.e=function(a,c){c=u.k.B({className:this.T(),innerHTML:'<div class=\"vjs-control-content\"><span class=\"vjs-control-text\">'+(this.qa||\"Need Text\")+\"</span></div>\",qd:\"button\",\"aria-live\":\"polite\",tabIndex:0},c);return u.c.prototype.e.call(this,a,c)};t.T=function(){return\"vjs-control \"+u.c.prototype.T.call(this)};t.p=m();t.Oa=function(){u.d(document,\"keyup\",u.bind(this,this.ba))};t.ba=function(a){if(32==a.which||13==a.which)a.preventDefault(),this.p()};\nt.Na=function(){u.o(document,\"keyup\",u.bind(this,this.ba))};u.O=u.c.extend({i:function(a,c){u.c.call(this,a,c);this.Sc=this.V[this.g.barName];this.handle=this.V[this.g.handleName];a.d(this.tc,u.bind(this,this.update));this.d(\"mousedown\",this.Pa);this.d(\"touchstart\",this.Pa);this.d(\"focus\",this.Oa);this.d(\"blur\",this.Na);this.d(\"click\",this.p);this.b.d(\"controlsvisible\",u.bind(this,this.update));a.L(u.bind(this,this.update));this.P={}}});t=u.O.prototype;\nt.e=function(a,c){c=c||{};c.className+=\" vjs-slider\";c=u.k.B({qd:\"slider\",\"aria-valuenow\":0,\"aria-valuemin\":0,\"aria-valuemax\":100,tabIndex:0},c);return u.c.prototype.e.call(this,a,c)};t.Pa=function(a){a.preventDefault();u.Tc();this.P.move=u.bind(this,this.Hb);this.P.end=u.bind(this,this.Ib);u.d(document,\"mousemove\",this.P.move);u.d(document,\"mouseup\",this.P.end);u.d(document,\"touchmove\",this.P.move);u.d(document,\"touchend\",this.P.end);this.Hb(a)};\nt.Ib=function(){u.Bd();u.o(document,\"mousemove\",this.P.move,l);u.o(document,\"mouseup\",this.P.end,l);u.o(document,\"touchmove\",this.P.move,l);u.o(document,\"touchend\",this.P.end,l);this.update()};t.update=function(){if(this.a){var a,c=this.yb(),d=this.handle,e=this.Sc;isNaN(c)&&(c=0);a=c;if(d){a=this.a.offsetWidth;var g=d.w().offsetWidth;a=g?g/a:0;c*=1-a;a=c+a/2;d.w().style.left=u.round(100*c,2)+\"%\"}e.w().style.width=u.round(100*a,2)+\"%\"}};\nfunction F(a,c){var d,e,g,j;d=a.a;e=u.ad(d);j=g=d.offsetWidth;d=a.handle;if(a.g.Cd)return j=e.top,e=c.changedTouches?c.changedTouches[0].pageY:c.pageY,d&&(d=d.w().offsetHeight,j+=d/2,g-=d),Math.max(0,Math.min(1,(j-e+g)/g));g=e.left;e=c.changedTouches?c.changedTouches[0].pageX:c.pageX;d&&(d=d.w().offsetWidth,g+=d/2,j-=d);return Math.max(0,Math.min(1,(e-g)/j))}t.Oa=function(){u.d(document,\"keyup\",u.bind(this,this.ba))};\nt.ba=function(a){37==a.which?(a.preventDefault(),this.yc()):39==a.which&&(a.preventDefault(),this.zc())};t.Na=function(){u.o(document,\"keyup\",u.bind(this,this.ba))};t.p=function(a){a.stopImmediatePropagation();a.preventDefault()};u.ea=u.c.extend();u.ea.prototype.defaultValue=0;u.ea.prototype.e=function(a,c){c=c||{};c.className+=\" vjs-slider-handle\";c=u.k.B({innerHTML:'<span class=\"vjs-control-text\">'+this.defaultValue+\"</span>\"},c);return u.c.prototype.e.call(this,\"div\",c)};u.ma=u.c.extend();\nfunction ca(a,c){a.Z(c);c.d(\"click\",u.bind(a,function(){D(this)}))}u.ma.prototype.e=function(){var a=this.options().Vc||\"ul\";this.sa=u.e(a,{className:\"vjs-menu-content\"});a=u.c.prototype.e.call(this,\"div\",{append:this.sa,className:\"vjs-menu\"});a.appendChild(this.sa);u.d(a,\"click\",function(a){a.preventDefault();a.stopImmediatePropagation()});return a};u.N=u.q.extend({i:function(a,c){u.q.call(this,a,c);this.selected(c.selected)}});\nu.N.prototype.e=function(a,c){return u.q.prototype.e.call(this,\"li\",u.k.B({className:\"vjs-menu-item\",innerHTML:this.g.label},c))};u.N.prototype.p=function(){this.selected(f)};u.N.prototype.selected=function(a){a?(this.n(\"vjs-selected\"),this.a.setAttribute(\"aria-selected\",f)):(this.u(\"vjs-selected\"),this.a.setAttribute(\"aria-selected\",l))};\nu.R=u.q.extend({i:function(a,c){u.q.call(this,a,c);this.wa=this.Ka();this.Z(this.wa);this.I&&0===this.I.length&&this.C();this.d(\"keyup\",this.ba);this.a.setAttribute(\"aria-haspopup\",f);this.a.setAttribute(\"role\",\"button\")}});t=u.R.prototype;t.pa=l;t.Ka=function(){var a=new u.ma(this.b);this.options().title&&a.w().appendChild(u.e(\"li\",{className:\"vjs-menu-title\",innerHTML:u.$(this.A),zd:-1}));if(this.I=this.createItems())for(var c=0;c<this.I.length;c++)ca(a,this.I[c]);return a};t.ta=m();\nt.T=function(){return this.className+\" vjs-menu-button \"+u.q.prototype.T.call(this)};t.Oa=m();t.Na=m();t.p=function(){this.U(\"mouseout\",u.bind(this,function(){D(this.wa);this.a.blur()}));this.pa?G(this):H(this)};t.ba=function(a){a.preventDefault();32==a.which||13==a.which?this.pa?G(this):H(this):27==a.which&&this.pa&&G(this)};function H(a){a.pa=f;a.wa.n(\"vjs-lock-showing\");a.a.setAttribute(\"aria-pressed\",f);a.I&&0<a.I.length&&a.I[0].w().focus()}\nfunction G(a){a.pa=l;D(a.wa);a.a.setAttribute(\"aria-pressed\",l)}\nu.s=u.c.extend({i:function(a,c,d){this.M=a;c=u.k.B(da(a),c);this.v={};this.uc=c.poster;this.sb=c.controls;a.controls=l;u.c.call(this,this,c,d);this.controls()?this.n(\"vjs-controls-enabled\"):this.n(\"vjs-controls-disabled\");this.U(\"play\",function(a){u.j(this.a,{type:\"firstplay\",target:this.a})||(a.preventDefault(),a.stopPropagation(),a.stopImmediatePropagation())});this.d(\"ended\",this.hd);this.d(\"play\",this.Kb);this.d(\"firstplay\",this.jd);this.d(\"pause\",this.Jb);this.d(\"progress\",this.ld);this.d(\"durationchange\",\nthis.sc);this.d(\"error\",this.Gb);this.d(\"fullscreenchange\",this.kd);u.xa[this.Q]=this;c.plugins&&u.k.ua(c.plugins,function(a,c){this[a](c)},this);var e,g,j,k;e=this.Mb;a=function(){e();clearInterval(g);g=setInterval(u.bind(this,e),250)};c=function(){e();clearInterval(g)};this.d(\"mousedown\",a);this.d(\"mousemove\",e);this.d(\"mouseup\",c);this.d(\"keydown\",e);this.d(\"keyup\",e);this.d(\"touchstart\",a);this.d(\"touchmove\",e);this.d(\"touchend\",c);this.d(\"touchcancel\",c);j=setInterval(u.bind(this,function(){this.ka&&\n(this.ka=l,this.ja(f),clearTimeout(k),k=setTimeout(u.bind(this,function(){this.ka||this.ja(l)}),2E3))}),250);this.d(\"dispose\",function(){clearInterval(j);clearTimeout(k)})}});t=u.s.prototype;t.g=u.options;t.D=function(){this.j(\"dispose\");this.o(\"dispose\");u.xa[this.Q]=h;this.M&&this.M.player&&(this.M.player=h);this.a&&this.a.player&&(this.a.player=h);clearInterval(this.Ra);this.za();this.h&&this.h.D();u.c.prototype.D.call(this)};\nfunction da(a){var c={sources:[],tracks:[]};u.k.B(c,u.xb(a));if(a.hasChildNodes()){var d,e,g,j;a=a.childNodes;g=0;for(j=a.length;g<j;g++)d=a[g],e=d.nodeName.toLowerCase(),\"source\"===e?c.sources.push(u.xb(d)):\"track\"===e&&c.tracks.push(u.xb(d))}return c}\nt.e=function(){var a=this.a=u.c.prototype.e.call(this,\"div\"),c=this.M;c.removeAttribute(\"width\");c.removeAttribute(\"height\");if(c.hasChildNodes()){var d,e,g,j,k;d=c.childNodes;e=d.length;for(k=[];e--;)g=d[e],j=g.nodeName.toLowerCase(),\"track\"===j&&k.push(g);for(d=0;d<k.length;d++)c.removeChild(k[d])}c.id=c.id||\"vjs_video_\"+u.t++;a.id=c.id;a.className=c.className;c.id+=\"_html5_api\";c.className=\"vjs-tech\";c.player=a.player=this;this.n(\"vjs-paused\");this.width(this.g.width,f);this.height(this.g.height,\nf);c.parentNode&&c.parentNode.insertBefore(a,c);u.zb(c,a);return a};\nfunction I(a,c,d){a.h?(a.aa=l,a.h.D(),a.Eb&&(a.Eb=l,clearInterval(a.Ra)),a.Fb&&J(a),a.h=l):\"Html5\"!==c&&a.M&&(u.l.jc(a.M),a.M=h);a.ia=c;a.aa=l;var e=u.k.B({source:d,parentEl:a.a},a.g[c.toLowerCase()]);d&&(d.src==a.v.src&&0<a.v.currentTime&&(e.startTime=a.v.currentTime),a.v.src=d.src);a.h=new window.videojs[c](a,e);a.h.L(function(){this.b.Ua();if(!this.m.progressEvents){var a=this.b;a.Eb=f;a.Ra=setInterval(u.bind(a,function(){this.v.lb<this.buffered().end(0)?this.j(\"progress\"):1==this.Ja()&&(clearInterval(this.Ra),\nthis.j(\"progress\"))}),500);a.h.U(\"progress\",function(){this.m.progressEvents=f;var a=this.b;a.Eb=l;clearInterval(a.Ra)})}this.m.timeupdateEvents||(a=this.b,a.Fb=f,a.d(\"play\",a.Cc),a.d(\"pause\",a.za),a.h.U(\"timeupdate\",function(){this.m.timeupdateEvents=f;J(this.b)}))})}function J(a){a.Fb=l;a.za();a.o(\"play\",a.Cc);a.o(\"pause\",a.za)}t.Cc=function(){this.hc&&this.za();this.hc=setInterval(u.bind(this,function(){this.j(\"timeupdate\")}),250)};t.za=function(){clearInterval(this.hc)};\nt.Kb=function(){u.u(this.a,\"vjs-paused\");u.n(this.a,\"vjs-playing\")};t.jd=function(){this.g.starttime&&this.currentTime(this.g.starttime);this.n(\"vjs-has-started\")};t.Jb=function(){u.u(this.a,\"vjs-playing\");u.n(this.a,\"vjs-paused\")};t.ld=function(){1==this.Ja()&&this.j(\"loadedalldata\")};t.hd=function(){this.g.loop&&(this.currentTime(0),this.play())};t.sc=function(){this.duration(K(this,\"duration\"))};t.kd=function(){this.H?this.n(\"vjs-fullscreen\"):this.u(\"vjs-fullscreen\")};\nt.Gb=function(a){u.log(\"Video Error\",a)};function L(a,c,d){if(a.h&&!a.h.aa)a.h.L(function(){this[c](d)});else try{a.h[c](d)}catch(e){throw u.log(e),e;}}function K(a,c){if(a.h&&a.h.aa)try{return a.h[c]()}catch(d){throw a.h[c]===b?u.log(\"Video.js: \"+c+\" method not defined for \"+a.ia+\" playback technology.\",d):\"TypeError\"==d.name?(u.log(\"Video.js: \"+c+\" unavailable on \"+a.ia+\" playback technology element.\",d),a.h.aa=l):u.log(d),d;}}t.play=function(){L(this,\"play\");return this};\nt.pause=function(){L(this,\"pause\");return this};t.paused=function(){return K(this,\"paused\")===l?l:f};t.currentTime=function(a){return a!==b?(this.v.rc=a,L(this,\"setCurrentTime\",a),this.Fb&&this.j(\"timeupdate\"),this):this.v.currentTime=K(this,\"currentTime\")||0};t.duration=function(a){if(a!==b)return this.v.duration=parseFloat(a),this;this.v.duration===b&&this.sc();return this.v.duration};\nt.buffered=function(){var a=K(this,\"buffered\"),c=a.length-1,d=this.v.lb=this.v.lb||0;a&&(0<=c&&a.end(c)!==d)&&(d=a.end(c),this.v.lb=d);return u.tb(0,d)};t.Ja=function(){return this.duration()?this.buffered().end(0)/this.duration():0};t.volume=function(a){if(a!==b)return a=Math.max(0,Math.min(1,parseFloat(a))),this.v.volume=a,L(this,\"setVolume\",a),u.td(a),this;a=parseFloat(K(this,\"volume\"));return isNaN(a)?1:a};t.muted=function(a){return a!==b?(L(this,\"setMuted\",a),this):K(this,\"muted\")||l};\nt.Ta=function(){return K(this,\"supportsFullScreen\")||l};\nt.ya=function(){var a=u.Pb.ya;this.H=f;a?(u.d(document,a.vb,u.bind(this,function(c){this.H=document[a.H];this.H===l&&u.o(document,a.vb,arguments.callee);this.j(\"fullscreenchange\")})),this.a[a.wc]()):this.h.Ta()?L(this,\"enterFullScreen\"):(this.cd=f,this.Yc=document.documentElement.style.overflow,u.d(document,\"keydown\",u.bind(this,this.lc)),document.documentElement.style.overflow=\"hidden\",u.n(document.body,\"vjs-full-window\"),this.j(\"enterFullWindow\"),this.j(\"fullscreenchange\"));return this};\nt.ob=function(){var a=u.Pb.ya;this.H=l;if(a)document[a.nb]();else this.h.Ta()?L(this,\"exitFullScreen\"):(M(this),this.j(\"fullscreenchange\"));return this};t.lc=function(a){27===a.keyCode&&(this.H===f?this.ob():M(this))};function M(a){a.cd=l;u.o(document,\"keydown\",a.lc);document.documentElement.style.overflow=a.Yc;u.u(document.body,\"vjs-full-window\");a.j(\"exitFullWindow\")}\nt.src=function(a){if(a instanceof Array){var c;a:{c=a;for(var d=0,e=this.g.techOrder;d<e.length;d++){var g=u.$(e[d]),j=window.videojs[g];if(j.isSupported())for(var k=0,q=c;k<q.length;k++){var n=q[k];if(j.canPlaySource(n)){c={source:n,h:g};break a}}}c=l}c?(a=c.source,c=c.h,c==this.ia?this.src(a):I(this,c,a)):this.a.appendChild(u.e(\"p\",{innerHTML:this.options().notSupportedMessage}))}else a instanceof Object?window.videojs[this.ia].canPlaySource(a)?this.src(a.src):this.src([a]):(this.v.src=a,this.aa?\n(L(this,\"src\",a),\"auto\"==this.g.preload&&this.load(),this.g.autoplay&&this.play()):this.L(function(){this.src(a)}));return this};t.load=function(){L(this,\"load\");return this};t.currentSrc=function(){return K(this,\"currentSrc\")||this.v.src||\"\"};t.Qa=function(a){return a!==b?(L(this,\"setPreload\",a),this.g.preload=a,this):K(this,\"preload\")};t.autoplay=function(a){return a!==b?(L(this,\"setAutoplay\",a),this.g.autoplay=a,this):K(this,\"autoplay\")};\nt.loop=function(a){return a!==b?(L(this,\"setLoop\",a),this.g.loop=a,this):K(this,\"loop\")};t.poster=function(a){return a!==b?(this.uc=a,this):this.uc};t.controls=function(a){return a!==b?(a=!!a,this.sb!==a&&((this.sb=a)?(this.u(\"vjs-controls-disabled\"),this.n(\"vjs-controls-enabled\"),this.j(\"controlsenabled\")):(this.u(\"vjs-controls-enabled\"),this.n(\"vjs-controls-disabled\"),this.j(\"controlsdisabled\"))),this):this.sb};u.s.prototype.Sb;t=u.s.prototype;\nt.Rb=function(a){return a!==b?(a=!!a,this.Sb!==a&&((this.Sb=a)?(this.n(\"vjs-using-native-controls\"),this.j(\"usingnativecontrols\")):(this.u(\"vjs-using-native-controls\"),this.j(\"usingcustomcontrols\"))),this):this.Sb};t.error=function(){return K(this,\"error\")};t.seeking=function(){return K(this,\"seeking\")};t.ka=f;t.Mb=function(){this.ka=f};t.Qb=f;\nt.ja=function(a){return a!==b?(a=!!a,a!==this.Qb&&((this.Qb=a)?(this.ka=f,this.u(\"vjs-user-inactive\"),this.n(\"vjs-user-active\"),this.j(\"useractive\")):(this.ka=l,this.h.U(\"mousemove\",function(a){a.stopPropagation();a.preventDefault()}),this.u(\"vjs-user-active\"),this.n(\"vjs-user-inactive\"),this.j(\"userinactive\"))),this):this.Qb};var N,O,P;P=document.createElement(\"div\");O={};\nP.Hd!==b?(O.wc=\"requestFullscreen\",O.nb=\"exitFullscreen\",O.vb=\"fullscreenchange\",O.H=\"fullScreen\"):(document.mozCancelFullScreen?(N=\"moz\",O.H=N+\"FullScreen\"):(N=\"webkit\",O.H=N+\"IsFullScreen\"),P[N+\"RequestFullScreen\"]&&(O.wc=N+\"RequestFullScreen\",O.nb=N+\"CancelFullScreen\"),O.vb=N+\"fullscreenchange\");document[O.nb]&&(u.Pb.ya=O);u.Fa=u.c.extend();\nu.Fa.prototype.g={Md:\"play\",children:{playToggle:{},currentTimeDisplay:{},timeDivider:{},durationDisplay:{},remainingTimeDisplay:{},progressControl:{},fullscreenToggle:{},volumeControl:{},muteToggle:{}}};u.Fa.prototype.e=function(){return u.e(\"div\",{className:\"vjs-control-bar\"})};u.Yb=u.q.extend({i:function(a,c){u.q.call(this,a,c);a.d(\"play\",u.bind(this,this.Kb));a.d(\"pause\",u.bind(this,this.Jb))}});t=u.Yb.prototype;t.qa=\"Play\";t.T=function(){return\"vjs-play-control \"+u.q.prototype.T.call(this)};\nt.p=function(){this.b.paused()?this.b.play():this.b.pause()};t.Kb=function(){u.u(this.a,\"vjs-paused\");u.n(this.a,\"vjs-playing\");this.a.children[0].children[0].innerHTML=\"Pause\"};t.Jb=function(){u.u(this.a,\"vjs-playing\");u.n(this.a,\"vjs-paused\");this.a.children[0].children[0].innerHTML=\"Play\"};u.Ya=u.c.extend({i:function(a,c){u.c.call(this,a,c);a.d(\"timeupdate\",u.bind(this,this.Ca))}});\nu.Ya.prototype.e=function(){var a=u.c.prototype.e.call(this,\"div\",{className:\"vjs-current-time vjs-time-controls vjs-control\"});this.content=u.e(\"div\",{className:\"vjs-current-time-display\",innerHTML:'<span class=\"vjs-control-text\">Current Time </span>0:00',\"aria-live\":\"off\"});a.appendChild(u.e(\"div\").appendChild(this.content));return a};\nu.Ya.prototype.Ca=function(){var a=this.b.Nb?this.b.v.currentTime:this.b.currentTime();this.content.innerHTML='<span class=\"vjs-control-text\">Current Time </span>'+u.La(a,this.b.duration())};u.Za=u.c.extend({i:function(a,c){u.c.call(this,a,c);a.d(\"timeupdate\",u.bind(this,this.Ca))}});\nu.Za.prototype.e=function(){var a=u.c.prototype.e.call(this,\"div\",{className:\"vjs-duration vjs-time-controls vjs-control\"});this.content=u.e(\"div\",{className:\"vjs-duration-display\",innerHTML:'<span class=\"vjs-control-text\">Duration Time </span>0:00',\"aria-live\":\"off\"});a.appendChild(u.e(\"div\").appendChild(this.content));return a};u.Za.prototype.Ca=function(){var a=this.b.duration();a&&(this.content.innerHTML='<span class=\"vjs-control-text\">Duration Time </span>'+u.La(a))};\nu.cc=u.c.extend({i:function(a,c){u.c.call(this,a,c)}});u.cc.prototype.e=function(){return u.c.prototype.e.call(this,\"div\",{className:\"vjs-time-divider\",innerHTML:\"<div><span>/</span></div>\"})};u.fb=u.c.extend({i:function(a,c){u.c.call(this,a,c);a.d(\"timeupdate\",u.bind(this,this.Ca))}});\nu.fb.prototype.e=function(){var a=u.c.prototype.e.call(this,\"div\",{className:\"vjs-remaining-time vjs-time-controls vjs-control\"});this.content=u.e(\"div\",{className:\"vjs-remaining-time-display\",innerHTML:'<span class=\"vjs-control-text\">Remaining Time </span>-0:00',\"aria-live\":\"off\"});a.appendChild(u.e(\"div\").appendChild(this.content));return a};u.fb.prototype.Ca=function(){this.b.duration()&&(this.content.innerHTML='<span class=\"vjs-control-text\">Remaining Time </span>-'+u.La(this.b.duration()-this.b.currentTime()))};\nu.Ga=u.q.extend({i:function(a,c){u.q.call(this,a,c)}});u.Ga.prototype.qa=\"Fullscreen\";u.Ga.prototype.T=function(){return\"vjs-fullscreen-control \"+u.q.prototype.T.call(this)};u.Ga.prototype.p=function(){this.b.H?(this.b.ob(),this.a.children[0].children[0].innerHTML=\"Fullscreen\"):(this.b.ya(),this.a.children[0].children[0].innerHTML=\"Non-Fullscreen\")};u.eb=u.c.extend({i:function(a,c){u.c.call(this,a,c)}});u.eb.prototype.g={children:{seekBar:{}}};\nu.eb.prototype.e=function(){return u.c.prototype.e.call(this,\"div\",{className:\"vjs-progress-control vjs-control\"})};u.Zb=u.O.extend({i:function(a,c){u.O.call(this,a,c);a.d(\"timeupdate\",u.bind(this,this.Ba));a.L(u.bind(this,this.Ba))}});t=u.Zb.prototype;t.g={children:{loadProgressBar:{},playProgressBar:{},seekHandle:{}},barName:\"playProgressBar\",handleName:\"seekHandle\"};t.tc=\"timeupdate\";t.e=function(){return u.O.prototype.e.call(this,\"div\",{className:\"vjs-progress-holder\",\"aria-label\":\"video progress bar\"})};\nt.Ba=function(){var a=this.b.Nb?this.b.v.currentTime:this.b.currentTime();this.a.setAttribute(\"aria-valuenow\",u.round(100*this.yb(),2));this.a.setAttribute(\"aria-valuetext\",u.La(a,this.b.duration()))};t.yb=function(){var a;\"Flash\"===this.b.ia&&this.b.seeking()?(a=this.b.v,a=a.rc?a.rc:this.b.currentTime()):a=this.b.currentTime();return a/this.b.duration()};t.Pa=function(a){u.O.prototype.Pa.call(this,a);this.b.Nb=f;this.Dd=!this.b.paused();this.b.pause()};\nt.Hb=function(a){a=F(this,a)*this.b.duration();a==this.b.duration()&&(a-=0.1);this.b.currentTime(a)};t.Ib=function(a){u.O.prototype.Ib.call(this,a);this.b.Nb=l;this.Dd&&this.b.play()};t.zc=function(){this.b.currentTime(this.b.currentTime()+5)};t.yc=function(){this.b.currentTime(this.b.currentTime()-5)};u.ab=u.c.extend({i:function(a,c){u.c.call(this,a,c);a.d(\"progress\",u.bind(this,this.update))}});u.ab.prototype.e=function(){return u.c.prototype.e.call(this,\"div\",{className:\"vjs-load-progress\",innerHTML:'<span class=\"vjs-control-text\">Loaded: 0%</span>'})};\nu.ab.prototype.update=function(){this.a.style&&(this.a.style.width=u.round(100*this.b.Ja(),2)+\"%\")};u.Xb=u.c.extend({i:function(a,c){u.c.call(this,a,c)}});u.Xb.prototype.e=function(){return u.c.prototype.e.call(this,\"div\",{className:\"vjs-play-progress\",innerHTML:'<span class=\"vjs-control-text\">Progress: 0%</span>'})};u.gb=u.ea.extend();u.gb.prototype.defaultValue=\"00:00\";u.gb.prototype.e=function(){return u.ea.prototype.e.call(this,\"div\",{className:\"vjs-seek-handle\"})};\nu.ib=u.c.extend({i:function(a,c){u.c.call(this,a,c);a.h&&(a.h.m&&a.h.m.volumeControl===l)&&this.n(\"vjs-hidden\");a.d(\"loadstart\",u.bind(this,function(){a.h.m&&a.h.m.volumeControl===l?this.n(\"vjs-hidden\"):this.u(\"vjs-hidden\")}))}});u.ib.prototype.g={children:{volumeBar:{}}};u.ib.prototype.e=function(){return u.c.prototype.e.call(this,\"div\",{className:\"vjs-volume-control vjs-control\"})};\nu.hb=u.O.extend({i:function(a,c){u.O.call(this,a,c);a.d(\"volumechange\",u.bind(this,this.Ba));a.L(u.bind(this,this.Ba));setTimeout(u.bind(this,this.update),0)}});t=u.hb.prototype;t.Ba=function(){this.a.setAttribute(\"aria-valuenow\",u.round(100*this.b.volume(),2));this.a.setAttribute(\"aria-valuetext\",u.round(100*this.b.volume(),2)+\"%\")};t.g={children:{volumeLevel:{},volumeHandle:{}},barName:\"volumeLevel\",handleName:\"volumeHandle\"};t.tc=\"volumechange\";\nt.e=function(){return u.O.prototype.e.call(this,\"div\",{className:\"vjs-volume-bar\",\"aria-label\":\"volume level\"})};t.Hb=function(a){this.b.muted()&&this.b.muted(l);this.b.volume(F(this,a))};t.yb=function(){return this.b.muted()?0:this.b.volume()};t.zc=function(){this.b.volume(this.b.volume()+0.1)};t.yc=function(){this.b.volume(this.b.volume()-0.1)};u.dc=u.c.extend({i:function(a,c){u.c.call(this,a,c)}});\nu.dc.prototype.e=function(){return u.c.prototype.e.call(this,\"div\",{className:\"vjs-volume-level\",innerHTML:'<span class=\"vjs-control-text\"></span>'})};u.jb=u.ea.extend();u.jb.prototype.defaultValue=\"00:00\";u.jb.prototype.e=function(){return u.ea.prototype.e.call(this,\"div\",{className:\"vjs-volume-handle\"})};\nu.da=u.q.extend({i:function(a,c){u.q.call(this,a,c);a.d(\"volumechange\",u.bind(this,this.update));a.h&&(a.h.m&&a.h.m.volumeControl===l)&&this.n(\"vjs-hidden\");a.d(\"loadstart\",u.bind(this,function(){a.h.m&&a.h.m.volumeControl===l?this.n(\"vjs-hidden\"):this.u(\"vjs-hidden\")}))}});u.da.prototype.e=function(){return u.q.prototype.e.call(this,\"div\",{className:\"vjs-mute-control vjs-control\",innerHTML:'<div><span class=\"vjs-control-text\">Mute</span></div>'})};\nu.da.prototype.p=function(){this.b.muted(this.b.muted()?l:f)};u.da.prototype.update=function(){var a=this.b.volume(),c=3;0===a||this.b.muted()?c=0:0.33>a?c=1:0.67>a&&(c=2);this.b.muted()?\"Unmute\"!=this.a.children[0].children[0].innerHTML&&(this.a.children[0].children[0].innerHTML=\"Unmute\"):\"Mute\"!=this.a.children[0].children[0].innerHTML&&(this.a.children[0].children[0].innerHTML=\"Mute\");for(a=0;4>a;a++)u.u(this.a,\"vjs-vol-\"+a);u.n(this.a,\"vjs-vol-\"+c)};\nu.oa=u.R.extend({i:function(a,c){u.R.call(this,a,c);a.d(\"volumechange\",u.bind(this,this.update));a.h&&(a.h.m&&a.h.m.Dc===l)&&this.n(\"vjs-hidden\");a.d(\"loadstart\",u.bind(this,function(){a.h.m&&a.h.m.Dc===l?this.n(\"vjs-hidden\"):this.u(\"vjs-hidden\")}));this.n(\"vjs-menu-button\")}});u.oa.prototype.Ka=function(){var a=new u.ma(this.b,{Vc:\"div\"}),c=new u.hb(this.b,u.k.B({Cd:f},this.g.Vd));a.Z(c);return a};u.oa.prototype.p=function(){u.da.prototype.p.call(this);u.R.prototype.p.call(this)};\nu.oa.prototype.e=function(){return u.q.prototype.e.call(this,\"div\",{className:\"vjs-volume-menu-button vjs-menu-button vjs-control\",innerHTML:'<div><span class=\"vjs-control-text\">Mute</span></div>'})};u.oa.prototype.update=u.da.prototype.update;u.cb=u.q.extend({i:function(a,c){u.q.call(this,a,c);(!a.poster()||!a.controls())&&this.C();a.d(\"play\",u.bind(this,this.C))}});\nu.cb.prototype.e=function(){var a=u.e(\"div\",{className:\"vjs-poster\",tabIndex:-1}),c=this.b.poster();c&&(\"backgroundSize\"in a.style?a.style.backgroundImage='url(\"'+c+'\")':a.appendChild(u.e(\"img\",{src:c})));return a};u.cb.prototype.p=function(){this.K().controls()&&this.b.play()};\nu.Wb=u.c.extend({i:function(a,c){u.c.call(this,a,c);a.d(\"canplay\",u.bind(this,this.C));a.d(\"canplaythrough\",u.bind(this,this.C));a.d(\"playing\",u.bind(this,this.C));a.d(\"seeked\",u.bind(this,this.C));a.d(\"seeking\",u.bind(this,this.show));a.d(\"seeked\",u.bind(this,this.C));a.d(\"error\",u.bind(this,this.show));a.d(\"waiting\",u.bind(this,this.show))}});u.Wb.prototype.e=function(){return u.c.prototype.e.call(this,\"div\",{className:\"vjs-loading-spinner\"})};u.Wa=u.q.extend();\nu.Wa.prototype.e=function(){return u.q.prototype.e.call(this,\"div\",{className:\"vjs-big-play-button\",innerHTML:'<span aria-hidden=\"true\"></span>',\"aria-label\":\"play video\"})};u.Wa.prototype.p=function(){this.b.play()};\nu.r=u.c.extend({i:function(a,c,d){u.c.call(this,a,c,d);var e,g;g=this;e=this.K();a=function(){if(e.controls()&&!e.Rb()){var a,c;g.d(\"mousedown\",g.p);g.d(\"touchstart\",function(a){a.preventDefault();a.stopPropagation();c=this.b.ja()});a=function(a){a.stopPropagation();c&&this.b.Mb()};g.d(\"touchmove\",a);g.d(\"touchleave\",a);g.d(\"touchcancel\",a);g.d(\"touchend\",a);var d,n,r;d=0;g.d(\"touchstart\",function(){d=(new Date).getTime();r=f});a=function(){r=l};g.d(\"touchmove\",a);g.d(\"touchleave\",a);g.d(\"touchcancel\",\na);g.d(\"touchend\",function(){r===f&&(n=(new Date).getTime()-d,250>n&&this.j(\"tap\"))});g.d(\"tap\",g.md)}};c=u.bind(g,g.pd);this.L(a);e.d(\"controlsenabled\",a);e.d(\"controlsdisabled\",c)}});u.r.prototype.pd=function(){this.o(\"tap\");this.o(\"touchstart\");this.o(\"touchmove\");this.o(\"touchleave\");this.o(\"touchcancel\");this.o(\"touchend\");this.o(\"click\");this.o(\"mousedown\")};u.r.prototype.p=function(a){0===a.button&&this.K().controls()&&(this.K().paused()?this.K().play():this.K().pause())};\nu.r.prototype.md=function(){this.K().ja(!this.K().ja())};u.r.prototype.m={volumeControl:f,fullscreenResize:l,progressEvents:l,timeupdateEvents:l};u.media={};u.media.Va=\"play pause paused currentTime setCurrentTime duration buffered volume setVolume muted setMuted width height supportsFullScreen enterFullScreen src load currentSrc preload setPreload autoplay setAutoplay loop setLoop error networkState readyState seeking initialTime startOffsetTime played seekable ended videoTracks audioTracks videoWidth videoHeight textTracks defaultPlaybackRate playbackRate mediaGroup controller controls defaultMuted\".split(\" \");\nfunction ea(){var a=u.media.Va[i];return function(){throw Error('The \"'+a+\"\\\" method is not available on the playback technology's API\");}}for(var i=u.media.Va.length-1;0<=i;i--)u.r.prototype[u.media.Va[i]]=ea();\nu.l=u.r.extend({i:function(a,c,d){this.m.volumeControl=u.l.Uc();this.m.movingMediaElementInDOM=!u.Kc;this.m.fullscreenResize=f;u.r.call(this,a,c,d);(c=c.source)&&this.a.currentSrc===c.src&&0<this.a.networkState?a.j(\"loadstart\"):c&&(this.a.src=c.src);if(u.ac&&a.options().nativeControlsForTouch!==l){var e,g,j,k;e=this;g=this.K();c=g.controls();e.a.controls=!!c;j=function(){e.a.controls=f};k=function(){e.a.controls=l};g.d(\"controlsenabled\",j);g.d(\"controlsdisabled\",k);c=function(){g.o(\"controlsenabled\",\nj);g.o(\"controlsdisabled\",k)};e.d(\"dispose\",c);g.d(\"usingcustomcontrols\",c);g.Rb(f)}a.L(function(){this.M&&(this.g.autoplay&&this.paused())&&(delete this.M.poster,this.play())});for(a=u.l.$a.length-1;0<=a;a--)u.d(this.a,u.l.$a[a],u.bind(this.b,this.$c));this.Ua()}});t=u.l.prototype;t.D=function(){u.r.prototype.D.call(this)};\nt.e=function(){var a=this.b,c=a.M,d;if(!c||this.m.movingMediaElementInDOM===l)c?(d=c.cloneNode(l),u.l.jc(c),c=d,a.M=h):c=u.e(\"video\",{id:a.id()+\"_html5_api\",className:\"vjs-tech\"}),c.player=a,u.zb(c,a.w());d=[\"autoplay\",\"preload\",\"loop\",\"muted\"];for(var e=d.length-1;0<=e;e--){var g=d[e];a.g[g]!==h&&(c[g]=a.g[g])}return c};t.$c=function(a){this.j(a);a.stopPropagation()};t.play=function(){this.a.play()};t.pause=function(){this.a.pause()};t.paused=function(){return this.a.paused};t.currentTime=function(){return this.a.currentTime};\nt.sd=function(a){try{this.a.currentTime=a}catch(c){u.log(c,\"Video is not ready. (Video.js)\")}};t.duration=function(){return this.a.duration||0};t.buffered=function(){return this.a.buffered};t.volume=function(){return this.a.volume};t.xd=function(a){this.a.volume=a};t.muted=function(){return this.a.muted};t.vd=function(a){this.a.muted=a};t.width=function(){return this.a.offsetWidth};t.height=function(){return this.a.offsetHeight};\nt.Ta=function(){return\"function\"==typeof this.a.webkitEnterFullScreen&&(/Android/.test(u.F)||!/Chrome|Mac OS X 10.5/.test(u.F))?f:l};t.src=function(a){this.a.src=a};t.load=function(){this.a.load()};t.currentSrc=function(){return this.a.currentSrc};t.Qa=function(){return this.a.Qa};t.wd=function(a){this.a.Qa=a};t.autoplay=function(){return this.a.autoplay};t.rd=function(a){this.a.autoplay=a};t.controls=function(){return this.a.controls};t.loop=function(){return this.a.loop};\nt.ud=function(a){this.a.loop=a};t.error=function(){return this.a.error};t.seeking=function(){return this.a.seeking};u.l.isSupported=function(){return!!u.na.canPlayType};u.l.mb=function(a){try{return!!u.na.canPlayType(a.type)}catch(c){return\"\"}};u.l.Uc=function(){var a=u.na.volume;u.na.volume=a/2+0.1;return a!==u.na.volume};u.l.$a=\"loadstart suspend abort error emptied stalled loadedmetadata loadeddata canplay canplaythrough playing waiting seeking seeked ended durationchange timeupdate progress play pause ratechange volumechange\".split(\" \");\nu.l.jc=function(a){if(a){a.player=h;for(a.parentNode&&a.parentNode.removeChild(a);a.hasChildNodes();)a.removeChild(a.firstChild);a.removeAttribute(\"src\");\"function\"===typeof a.load&&a.load()}};u.Oc&&(document.createElement(\"video\").constructor.prototype.canPlayType=function(a){return a&&-1!=a.toLowerCase().indexOf(\"video/mp4\")?\"maybe\":\"\"});\nu.f=u.r.extend({i:function(a,c,d){u.r.call(this,a,c,d);var e=c.source;d=c.parentEl;var g=this.a=u.e(\"div\",{id:a.id()+\"_temp_flash\"}),j=a.id()+\"_flash_api\";a=a.g;var k=u.k.B({readyFunction:\"videojs.Flash.onReady\",eventProxyFunction:\"videojs.Flash.onEvent\",errorEventProxyFunction:\"videojs.Flash.onError\",autoplay:a.autoplay,preload:a.Qa,loop:a.loop,muted:a.muted},c.flashVars),q=u.k.B({wmode:\"opaque\",bgcolor:\"#000000\"},c.params),n=u.k.B({id:j,name:j,\"class\":\"vjs-tech\"},c.attributes);e&&(e.type&&u.f.ed(e.type)?\n(a=u.f.Ac(e.src),k.rtmpConnection=encodeURIComponent(a.rb),k.rtmpStream=encodeURIComponent(a.Ob)):k.src=encodeURIComponent(u.mc(e.src)));u.zb(g,d);c.startTime&&this.L(function(){this.load();this.play();this.currentTime(c.startTime)});if(c.iFrameMode===f&&!u.Jc){var r=u.e(\"iframe\",{id:j+\"_iframe\",name:j+\"_iframe\",className:\"vjs-tech\",scrolling:\"no\",marginWidth:0,marginHeight:0,frameBorder:0});k.readyFunction=\"ready\";k.eventProxyFunction=\"events\";k.errorEventProxyFunction=\"errors\";u.d(r,\"load\",u.bind(this,\nfunction(){var a,d=r.contentWindow;a=r.contentDocument?r.contentDocument:r.contentWindow.document;a.write(u.f.nc(c.swf,k,q,n));d.player=this.b;d.ready=u.bind(this.b,function(c){var d=this.h;d.a=a.getElementById(c);u.f.pb(d)});d.events=u.bind(this.b,function(a,c){this&&\"flash\"===this.ia&&this.j(c)});d.errors=u.bind(this.b,function(a,c){u.log(\"Flash Error\",c)})}));g.parentNode.replaceChild(r,g)}else u.f.Zc(c.swf,g,k,q,n)}});t=u.f.prototype;t.D=function(){u.r.prototype.D.call(this)};t.play=function(){this.a.vjs_play()};\nt.pause=function(){this.a.vjs_pause()};t.src=function(a){u.f.dd(a)?(a=u.f.Ac(a),this.Qd(a.rb),this.Rd(a.Ob)):(a=u.mc(a),this.a.vjs_src(a));if(this.b.autoplay()){var c=this;setTimeout(function(){c.play()},0)}};t.currentSrc=function(){var a=this.a.vjs_getProperty(\"currentSrc\");if(a==h){var c=this.Od(),d=this.Pd();c&&d&&(a=u.f.yd(c,d))}return a};t.load=function(){this.a.vjs_load()};t.poster=function(){this.a.vjs_getProperty(\"poster\")};t.buffered=function(){return u.tb(0,this.a.vjs_getProperty(\"buffered\"))};\nt.Ta=s(l);var Q=u.f.prototype,R=\"rtmpConnection rtmpStream preload currentTime defaultPlaybackRate playbackRate autoplay loop mediaGroup controller controls volume muted defaultMuted\".split(\" \"),S=\"error currentSrc networkState readyState seeking initialTime duration startOffsetTime paused played seekable ended videoTracks audioTracks videoWidth videoHeight textTracks\".split(\" \");\nfunction fa(){var a=R[T],c=a.charAt(0).toUpperCase()+a.slice(1);Q[\"set\"+c]=function(c){return this.a.vjs_setProperty(a,c)}}function U(a){Q[a]=function(){return this.a.vjs_getProperty(a)}}var T;for(T=0;T<R.length;T++)U(R[T]),fa();for(T=0;T<S.length;T++)U(S[T]);u.f.isSupported=function(){return 10<=u.f.version()[0]};u.f.mb=function(a){if(!a.type)return\"\";a=a.type.replace(/;.*/,\"\").toLowerCase();if(a in u.f.bd||a in u.f.Bc)return\"maybe\"};\nu.f.bd={\"video/flv\":\"FLV\",\"video/x-flv\":\"FLV\",\"video/mp4\":\"MP4\",\"video/m4v\":\"MP4\"};u.f.Bc={\"rtmp/mp4\":\"MP4\",\"rtmp/flv\":\"FLV\"};u.f.onReady=function(a){a=u.w(a);var c=a.player||a.parentNode.player,d=c.h;a.player=c;d.a=a;u.f.pb(d)};u.f.pb=function(a){a.w().vjs_getProperty?a.Ua():setTimeout(function(){u.f.pb(a)},50)};u.f.onEvent=function(a,c){u.w(a).player.j(c)};u.f.onError=function(a,c){u.w(a).player.j(\"error\");u.log(\"Flash Error\",c,a)};\nu.f.version=function(){var a=\"0,0,0\";try{a=(new window.ActiveXObject(\"ShockwaveFlash.ShockwaveFlash\")).GetVariable(\"$version\").replace(/\\D+/g,\",\").match(/^,?(.+),?$/)[1]}catch(c){try{navigator.mimeTypes[\"application/x-shockwave-flash\"].enabledPlugin&&(a=(navigator.plugins[\"Shockwave Flash 2.0\"]||navigator.plugins[\"Shockwave Flash\"]).description.replace(/\\D+/g,\",\").match(/^,?(.+),?$/)[1])}catch(d){}}return a.split(\",\")};\nu.f.Zc=function(a,c,d,e,g){a=u.f.nc(a,d,e,g);a=u.e(\"div\",{innerHTML:a}).childNodes[0];d=c.parentNode;c.parentNode.replaceChild(a,c);var j=d.childNodes[0];setTimeout(function(){j.style.display=\"block\"},1E3)};\nu.f.nc=function(a,c,d,e){var g=\"\",j=\"\",k=\"\";c&&u.k.ua(c,function(a,c){g+=a+\"=\"+c+\"&amp;\"});d=u.k.B({movie:a,flashvars:g,allowScriptAccess:\"always\",allowNetworking:\"all\"},d);u.k.ua(d,function(a,c){j+='<param name=\"'+a+'\" value=\"'+c+'\" />'});e=u.k.B({data:a,width:\"100%\",height:\"100%\"},e);u.k.ua(e,function(a,c){k+=a+'=\"'+c+'\" '});return'<object type=\"application/x-shockwave-flash\"'+k+\">\"+j+\"</object>\"};u.f.yd=function(a,c){return a+\"&\"+c};\nu.f.Ac=function(a){var c={rb:\"\",Ob:\"\"};if(!a)return c;var d=a.indexOf(\"&\"),e;-1!==d?e=d+1:(d=e=a.lastIndexOf(\"/\")+1,0===d&&(d=e=a.length));c.rb=a.substring(0,d);c.Ob=a.substring(e,a.length);return c};u.f.ed=function(a){return a in u.f.Bc};u.f.Qc=/^rtmp[set]?:\\/\\//i;u.f.dd=function(a){return u.f.Qc.test(a)};\nu.Pc=u.c.extend({i:function(a,c,d){u.c.call(this,a,c,d);if(!a.g.sources||0===a.g.sources.length){c=0;for(d=a.g.techOrder;c<d.length;c++){var e=u.$(d[c]),g=window.videojs[e];if(g&&g.isSupported()){I(a,e);break}}}else a.src(a.g.sources)}});function V(a){a.Aa=a.Aa||[];return a.Aa}function W(a,c,d){for(var e=a.Aa,g=0,j=e.length,k,q;g<j;g++)k=e[g],k.id()===c?(k.show(),q=k):d&&(k.J()==d&&0<k.mode())&&k.disable();(c=q?q.J():d?d:l)&&a.j(c+\"trackchange\")}\nu.X=u.c.extend({i:function(a,c){u.c.call(this,a,c);this.Q=c.id||\"vjs_\"+c.kind+\"_\"+c.language+\"_\"+u.t++;this.xc=c.src;this.Wc=c[\"default\"]||c.dflt;this.Ad=c.title;this.Ld=c.srclang;this.fd=c.label;this.fa=[];this.ec=[];this.ga=this.ha=0;this.b.d(\"fullscreenchange\",u.bind(this,this.Rc))}});t=u.X.prototype;t.J=p(\"A\");t.src=p(\"xc\");t.ub=p(\"Wc\");t.title=p(\"Ad\");t.label=p(\"fd\");t.readyState=p(\"ha\");t.mode=p(\"ga\");t.Rc=function(){this.a.style.fontSize=this.b.H?140*(screen.width/this.b.width())+\"%\":\"\"};\nt.e=function(){return u.c.prototype.e.call(this,\"div\",{className:\"vjs-\"+this.A+\" vjs-text-track\"})};t.show=function(){X(this);this.ga=2;u.c.prototype.show.call(this)};t.C=function(){X(this);this.ga=1;u.c.prototype.C.call(this)};t.disable=function(){2==this.ga&&this.C();this.b.o(\"timeupdate\",u.bind(this,this.update,this.Q));this.b.o(\"ended\",u.bind(this,this.reset,this.Q));this.reset();this.b.V.textTrackDisplay.removeChild(this);this.ga=0};\nfunction X(a){0===a.ha&&a.load();0===a.ga&&(a.b.d(\"timeupdate\",u.bind(a,a.update,a.Q)),a.b.d(\"ended\",u.bind(a,a.reset,a.Q)),(\"captions\"===a.A||\"subtitles\"===a.A)&&a.b.V.textTrackDisplay.Z(a))}t.load=function(){0===this.ha&&(this.ha=1,u.get(this.xc,u.bind(this,this.nd),u.bind(this,this.Gb)))};t.Gb=function(a){this.error=a;this.ha=3;this.j(\"error\")};\nt.nd=function(a){var c,d;a=a.split(\"\\n\");for(var e=\"\",g=1,j=a.length;g<j;g++)if(e=u.trim(a[g])){-1==e.indexOf(\"--\\x3e\")?(c=e,e=u.trim(a[++g])):c=this.fa.length;c={id:c,index:this.fa.length};d=e.split(\" --\\x3e \");c.startTime=Y(d[0]);c.va=Y(d[1]);for(d=[];a[++g]&&(e=u.trim(a[g]));)d.push(e);c.text=d.join(\"<br/>\");this.fa.push(c)}this.ha=2;this.j(\"loaded\")};\nfunction Y(a){var c=a.split(\":\");a=0;var d,e,g;3==c.length?(d=c[0],e=c[1],c=c[2]):(d=0,e=c[0],c=c[1]);c=c.split(/\\s+/);c=c.splice(0,1)[0];c=c.split(/\\.|,/);g=parseFloat(c[1]);c=c[0];a+=3600*parseFloat(d);a+=60*parseFloat(e);a+=parseFloat(c);g&&(a+=g/1E3);return a}\nt.update=function(){if(0<this.fa.length){var a=this.b.currentTime();if(this.Lb===b||a<this.Lb||this.Ma<=a){var c=this.fa,d=this.b.duration(),e=0,g=l,j=[],k,q,n,r;a>=this.Ma||this.Ma===b?r=this.wb!==b?this.wb:0:(g=f,r=this.Db!==b?this.Db:c.length-1);for(;;){n=c[r];if(n.va<=a)e=Math.max(e,n.va),n.Ia&&(n.Ia=l);else if(a<n.startTime){if(d=Math.min(d,n.startTime),n.Ia&&(n.Ia=l),!g)break}else g?(j.splice(0,0,n),q===b&&(q=r),k=r):(j.push(n),k===b&&(k=r),q=r),d=Math.min(d,n.va),e=Math.max(e,n.startTime),\nn.Ia=f;if(g)if(0===r)break;else r--;else if(r===c.length-1)break;else r++}this.ec=j;this.Ma=d;this.Lb=e;this.wb=k;this.Db=q;a=this.ec;c=\"\";d=0;for(e=a.length;d<e;d++)c+='<span class=\"vjs-tt-cue\">'+a[d].text+\"</span>\";this.a.innerHTML=c;this.j(\"cuechange\")}}};t.reset=function(){this.Ma=0;this.Lb=this.b.duration();this.Db=this.wb=0};u.Ub=u.X.extend();u.Ub.prototype.A=\"captions\";u.$b=u.X.extend();u.$b.prototype.A=\"subtitles\";u.Vb=u.X.extend();u.Vb.prototype.A=\"chapters\";\nu.bc=u.c.extend({i:function(a,c,d){u.c.call(this,a,c,d);if(a.g.tracks&&0<a.g.tracks.length){c=this.b;a=a.g.tracks;var e;for(d=0;d<a.length;d++){e=a[d];var g=c,j=e.kind,k=e.label,q=e.language,n=e;e=g.Aa=g.Aa||[];n=n||{};n.kind=j;n.label=k;n.language=q;j=u.$(j||\"subtitles\");g=new window.videojs[j+\"Track\"](g,n);e.push(g)}}}});u.bc.prototype.e=function(){return u.c.prototype.e.call(this,\"div\",{className:\"vjs-text-track-display\"})};\nu.Y=u.N.extend({i:function(a,c){var d=this.ca=c.track;c.label=d.label();c.selected=d.ub();u.N.call(this,a,c);this.b.d(d.J()+\"trackchange\",u.bind(this,this.update))}});u.Y.prototype.p=function(){u.N.prototype.p.call(this);W(this.b,this.ca.Q,this.ca.J())};u.Y.prototype.update=function(){this.selected(2==this.ca.mode())};u.bb=u.Y.extend({i:function(a,c){c.track={J:function(){return c.kind},K:a,label:function(){return c.kind+\" off\"},ub:s(l),mode:s(l)};u.Y.call(this,a,c);this.selected(f)}});\nu.bb.prototype.p=function(){u.Y.prototype.p.call(this);W(this.b,this.ca.Q,this.ca.J())};u.bb.prototype.update=function(){for(var a=V(this.b),c=0,d=a.length,e,g=f;c<d;c++)e=a[c],e.J()==this.ca.J()&&2==e.mode()&&(g=l);this.selected(g)};u.S=u.R.extend({i:function(a,c){u.R.call(this,a,c);1>=this.I.length&&this.C()}});u.S.prototype.ta=function(){var a=[],c;a.push(new u.bb(this.b,{kind:this.A}));for(var d=0;d<V(this.b).length;d++)c=V(this.b)[d],c.J()===this.A&&a.push(new u.Y(this.b,{track:c}));return a};\nu.Da=u.S.extend({i:function(a,c,d){u.S.call(this,a,c,d);this.a.setAttribute(\"aria-label\",\"Captions Menu\")}});u.Da.prototype.A=\"captions\";u.Da.prototype.qa=\"Captions\";u.Da.prototype.className=\"vjs-captions-button\";u.Ha=u.S.extend({i:function(a,c,d){u.S.call(this,a,c,d);this.a.setAttribute(\"aria-label\",\"Subtitles Menu\")}});u.Ha.prototype.A=\"subtitles\";u.Ha.prototype.qa=\"Subtitles\";u.Ha.prototype.className=\"vjs-subtitles-button\";\nu.Ea=u.S.extend({i:function(a,c,d){u.S.call(this,a,c,d);this.a.setAttribute(\"aria-label\",\"Chapters Menu\")}});t=u.Ea.prototype;t.A=\"chapters\";t.qa=\"Chapters\";t.className=\"vjs-chapters-button\";t.ta=function(){for(var a=[],c,d=0;d<V(this.b).length;d++)c=V(this.b)[d],c.J()===this.A&&a.push(new u.Y(this.b,{track:c}));return a};\nt.Ka=function(){for(var a=V(this.b),c=0,d=a.length,e,g,j=this.I=[];c<d;c++)if(e=a[c],e.J()==this.A&&e.ub()){if(2>e.readyState()){this.Id=e;e.d(\"loaded\",u.bind(this,this.Ka));return}g=e;break}a=this.wa=new u.ma(this.b);a.a.appendChild(u.e(\"li\",{className:\"vjs-menu-title\",innerHTML:u.$(this.A),zd:-1}));if(g){e=g.fa;for(var k,c=0,d=e.length;c<d;c++)k=e[c],k=new u.Xa(this.b,{track:g,cue:k}),j.push(k),a.Z(k)}0<this.I.length&&this.show();return a};\nu.Xa=u.N.extend({i:function(a,c){var d=this.ca=c.track,e=this.cue=c.cue,g=a.currentTime();c.label=e.text;c.selected=e.startTime<=g&&g<e.va;u.N.call(this,a,c);d.d(\"cuechange\",u.bind(this,this.update))}});u.Xa.prototype.p=function(){u.N.prototype.p.call(this);this.b.currentTime(this.cue.startTime);this.update(this.cue.startTime)};u.Xa.prototype.update=function(){var a=this.cue,c=this.b.currentTime();this.selected(a.startTime<=c&&c<a.va)};\nu.k.B(u.Fa.prototype.g.children,{subtitlesButton:{},captionsButton:{},chaptersButton:{}});\nif(\"undefined\"!==typeof window.JSON&&\"function\"===window.JSON.parse)u.JSON=window.JSON;else{u.JSON={};var Z=/[\\u0000\\u00ad\\u0600-\\u0604\\u070f\\u17b4\\u17b5\\u200c-\\u200f\\u2028-\\u202f\\u2060-\\u206f\\ufeff\\ufff0-\\uffff]/g;u.JSON.parse=function(a,c){function d(a,e){var k,q,n=a[e];if(n&&\"object\"===typeof n)for(k in n)Object.prototype.hasOwnProperty.call(n,k)&&(q=d(n,k),q!==b?n[k]=q:delete n[k]);return c.call(a,e,n)}var e;a=String(a);Z.lastIndex=0;Z.test(a)&&(a=a.replace(Z,function(a){return\"\\\\u\"+(\"0000\"+a.charCodeAt(0).toString(16)).slice(-4)}));\nif(/^[\\],:{}\\s]*$/.test(a.replace(/\\\\(?:[\"\\\\\\/bfnrt]|u[0-9a-fA-F]{4})/g,\"@\").replace(/\"[^\"\\\\\\n\\r]*\"|true|false|null|-?\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d+)?/g,\"]\").replace(/(?:^|:|,)(?:\\s*\\[)+/g,\"\")))return e=eval(\"(\"+a+\")\"),\"function\"===typeof c?d({\"\":e},\"\"):e;throw new SyntaxError(\"JSON.parse(): invalid or malformed JSON data\");}}\nu.fc=function(){var a,c,d=document.getElementsByTagName(\"video\");if(d&&0<d.length)for(var e=0,g=d.length;e<g;e++)if((c=d[e])&&c.getAttribute)c.player===b&&(a=c.getAttribute(\"data-setup\"),a!==h&&(a=u.JSON.parse(a||\"{}\"),v(c,a)));else{u.kb();break}else u.Ec||u.kb()};u.kb=function(){setTimeout(u.fc,1)};\"complete\"===document.readyState?u.Ec=f:u.U(window,\"load\",function(){u.Ec=f});u.kb();u.od=function(a,c){u.s.prototype[a]=c};var ga=this;ga.Ed=f;function $(a,c){var d=a.split(\".\"),e=ga;!(d[0]in e)&&e.execScript&&e.execScript(\"var \"+d[0]);for(var g;d.length&&(g=d.shift());)!d.length&&c!==b?e[g]=c:e=e[g]?e[g]:e[g]={}};$(\"videojs\",u);$(\"_V_\",u);$(\"videojs.options\",u.options);$(\"videojs.players\",u.xa);$(\"videojs.TOUCH_ENABLED\",u.ac);$(\"videojs.cache\",u.ra);$(\"videojs.Component\",u.c);u.c.prototype.player=u.c.prototype.K;u.c.prototype.dispose=u.c.prototype.D;u.c.prototype.createEl=u.c.prototype.e;u.c.prototype.el=u.c.prototype.w;u.c.prototype.addChild=u.c.prototype.Z;u.c.prototype.children=u.c.prototype.children;u.c.prototype.on=u.c.prototype.d;u.c.prototype.off=u.c.prototype.o;u.c.prototype.one=u.c.prototype.U;\nu.c.prototype.trigger=u.c.prototype.j;u.c.prototype.triggerReady=u.c.prototype.Ua;u.c.prototype.show=u.c.prototype.show;u.c.prototype.hide=u.c.prototype.C;u.c.prototype.width=u.c.prototype.width;u.c.prototype.height=u.c.prototype.height;u.c.prototype.dimensions=u.c.prototype.Xc;u.c.prototype.ready=u.c.prototype.L;u.c.prototype.addClass=u.c.prototype.n;u.c.prototype.removeClass=u.c.prototype.u;$(\"videojs.Player\",u.s);u.s.prototype.dispose=u.s.prototype.D;u.s.prototype.requestFullScreen=u.s.prototype.ya;\nu.s.prototype.cancelFullScreen=u.s.prototype.ob;u.s.prototype.bufferedPercent=u.s.prototype.Ja;u.s.prototype.usingNativeControls=u.s.prototype.Rb;u.s.prototype.reportUserActivity=u.s.prototype.Mb;u.s.prototype.userActive=u.s.prototype.ja;$(\"videojs.MediaLoader\",u.Pc);$(\"videojs.TextTrackDisplay\",u.bc);$(\"videojs.ControlBar\",u.Fa);$(\"videojs.Button\",u.q);$(\"videojs.PlayToggle\",u.Yb);$(\"videojs.FullscreenToggle\",u.Ga);$(\"videojs.BigPlayButton\",u.Wa);$(\"videojs.LoadingSpinner\",u.Wb);\n$(\"videojs.CurrentTimeDisplay\",u.Ya);$(\"videojs.DurationDisplay\",u.Za);$(\"videojs.TimeDivider\",u.cc);$(\"videojs.RemainingTimeDisplay\",u.fb);$(\"videojs.Slider\",u.O);$(\"videojs.ProgressControl\",u.eb);$(\"videojs.SeekBar\",u.Zb);$(\"videojs.LoadProgressBar\",u.ab);$(\"videojs.PlayProgressBar\",u.Xb);$(\"videojs.SeekHandle\",u.gb);$(\"videojs.VolumeControl\",u.ib);$(\"videojs.VolumeBar\",u.hb);$(\"videojs.VolumeLevel\",u.dc);$(\"videojs.VolumeMenuButton\",u.oa);$(\"videojs.VolumeHandle\",u.jb);$(\"videojs.MuteToggle\",u.da);\n$(\"videojs.PosterImage\",u.cb);$(\"videojs.Menu\",u.ma);$(\"videojs.MenuItem\",u.N);$(\"videojs.MenuButton\",u.R);u.R.prototype.createItems=u.R.prototype.ta;u.S.prototype.createItems=u.S.prototype.ta;u.Ea.prototype.createItems=u.Ea.prototype.ta;$(\"videojs.SubtitlesButton\",u.Ha);$(\"videojs.CaptionsButton\",u.Da);$(\"videojs.ChaptersButton\",u.Ea);$(\"videojs.MediaTechController\",u.r);u.r.prototype.features=u.r.prototype.m;u.r.prototype.m.volumeControl=u.r.prototype.m.Dc;u.r.prototype.m.fullscreenResize=u.r.prototype.m.Jd;\nu.r.prototype.m.progressEvents=u.r.prototype.m.Nd;u.r.prototype.m.timeupdateEvents=u.r.prototype.m.Sd;$(\"videojs.Html5\",u.l);u.l.Events=u.l.$a;u.l.isSupported=u.l.isSupported;u.l.canPlaySource=u.l.mb;u.l.prototype.setCurrentTime=u.l.prototype.sd;u.l.prototype.setVolume=u.l.prototype.xd;u.l.prototype.setMuted=u.l.prototype.vd;u.l.prototype.setPreload=u.l.prototype.wd;u.l.prototype.setAutoplay=u.l.prototype.rd;u.l.prototype.setLoop=u.l.prototype.ud;$(\"videojs.Flash\",u.f);u.f.isSupported=u.f.isSupported;\nu.f.canPlaySource=u.f.mb;u.f.onReady=u.f.onReady;$(\"videojs.TextTrack\",u.X);u.X.prototype.label=u.X.prototype.label;$(\"videojs.CaptionsTrack\",u.Ub);$(\"videojs.SubtitlesTrack\",u.$b);$(\"videojs.ChaptersTrack\",u.Vb);$(\"videojs.autoSetup\",u.fc);$(\"videojs.plugin\",u.od);$(\"videojs.createTimeRange\",u.tb);})();\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/webuploader/webuploader.css",
    "content": ".webuploader-container {\n\tposition: relative;\n}\n.webuploader-element-invisible {\n\tposition: absolute !important;\n\tclip: rect(1px 1px 1px 1px); /* IE6, IE7 */\n    clip: rect(1px,1px,1px,1px);\n}\n.webuploader-pick {\n\tposition: relative;\n\tdisplay: inline-block;\n\tcursor: pointer;\n\tbackground: #00b7ee;\n\tpadding: 10px 15px;\n\tcolor: #fff;\n\ttext-align: center;\n\tborder-radius: 3px;\n\toverflow: hidden;\n}\n.webuploader-pick-hover {\n\tbackground: #00a2d4;\n}\n\n.webuploader-pick-disable {\n\topacity: 0.6;\n\tpointer-events:none;\n}\n\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/webuploader/webuploader.custom.js",
    "content": "/*! WebUploader 0.1.2 */\n\n\n/**\n * @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。\n *\n * AMD API 内部的简单不完全实现，请忽略。只有当WebUploader被合并成一个文件的时候才会引入。\n */\n(function( root, factory ) {\n    var modules = {},\n\n        // 内部require, 简单不完全实现。\n        // https://github.com/amdjs/amdjs-api/wiki/require\n        _require = function( deps, callback ) {\n            var args, len, i;\n\n            // 如果deps不是数组，则直接返回指定module\n            if ( typeof deps === 'string' ) {\n                return getModule( deps );\n            } else {\n                args = [];\n                for( len = deps.length, i = 0; i < len; i++ ) {\n                    args.push( getModule( deps[ i ] ) );\n                }\n\n                return callback.apply( null, args );\n            }\n        },\n\n        // 内部define，暂时不支持不指定id.\n        _define = function( id, deps, factory ) {\n            if ( arguments.length === 2 ) {\n                factory = deps;\n                deps = null;\n            }\n\n            _require( deps || [], function() {\n                setModule( id, factory, arguments );\n            });\n        },\n\n        // 设置module, 兼容CommonJs写法。\n        setModule = function( id, factory, args ) {\n            var module = {\n                    exports: factory\n                },\n                returned;\n\n            if ( typeof factory === 'function' ) {\n                args.length || (args = [ _require, module.exports, module ]);\n                returned = factory.apply( null, args );\n                returned !== undefined && (module.exports = returned);\n            }\n\n            modules[ id ] = module.exports;\n        },\n\n        // 根据id获取module\n        getModule = function( id ) {\n            var module = modules[ id ] || root[ id ];\n\n            if ( !module ) {\n                throw new Error( '`' + id + '` is undefined' );\n            }\n\n            return module;\n        },\n\n        // 将所有modules，将路径ids装换成对象。\n        exportsTo = function( obj ) {\n            var key, host, parts, part, last, ucFirst;\n\n            // make the first character upper case.\n            ucFirst = function( str ) {\n                return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 ));\n            };\n\n            for ( key in modules ) {\n                host = obj;\n\n                if ( !modules.hasOwnProperty( key ) ) {\n                    continue;\n                }\n\n                parts = key.split('/');\n                last = ucFirst( parts.pop() );\n\n                while( (part = ucFirst( parts.shift() )) ) {\n                    host[ part ] = host[ part ] || {};\n                    host = host[ part ];\n                }\n\n                host[ last ] = modules[ key ];\n            }\n        },\n\n        exports = factory( root, _define, _require ),\n        origin;\n\n    // exports every module.\n    exportsTo( exports );\n\n    if ( typeof module === 'object' && typeof module.exports === 'object' ) {\n\n        // For CommonJS and CommonJS-like environments where a proper window is present,\n        module.exports = exports;\n    } else if ( typeof define === 'function' && define.amd ) {\n\n        // Allow using this built library as an AMD module\n        // in another project. That other project will only\n        // see this AMD call, not the internal modules in\n        // the closure below.\n        define([], exports );\n    } else {\n\n        // Browser globals case. Just assign the\n        // result to a property on the global.\n        origin = root.WebUploader;\n        root.WebUploader = exports;\n        root.WebUploader.noConflict = function() {\n            root.WebUploader = origin;\n        };\n    }\n})( this, function( window, define, require ) {\n\n\n    /**\n     * @fileOverview jQuery or Zepto\n     */\n    define('dollar-third',[],function() {\n        return window.jQuery || window.Zepto;\n    });\n    /**\n     * @fileOverview Dom 操作相关\n     */\n    define('dollar',[\n        'dollar-third'\n    ], function( _ ) {\n        return _;\n    });\n    /**\n     * @fileOverview 使用jQuery的Promise\n     */\n    define('promise-third',[\n        'dollar'\n    ], function( $ ) {\n        return {\n            Deferred: $.Deferred,\n            when: $.when,\n    \n            isPromise: function( anything ) {\n                return anything && typeof anything.then === 'function';\n            }\n        };\n    });\n    /**\n     * @fileOverview Promise/A+\n     */\n    define('promise',[\n        'promise-third'\n    ], function( _ ) {\n        return _;\n    });\n    /**\n     * @fileOverview 基础类方法。\n     */\n    \n    /**\n     * Web Uploader内部类的详细说明，以下提及的功能类，都可以在`WebUploader`这个变量中访问到。\n     *\n     * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id.\n     * 默认module id该文件的路径，而此路径将会转化成名字空间存放在WebUploader中。如：\n     *\n     * * module `base`：WebUploader.Base\n     * * module `file`: WebUploader.File\n     * * module `lib/dnd`: WebUploader.Lib.Dnd\n     * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd\n     *\n     *\n     * 以下文档将可能省略`WebUploader`前缀。\n     * @module WebUploader\n     * @title WebUploader API文档\n     */\n    define('base',[\n        'dollar',\n        'promise'\n    ], function( $, promise ) {\n    \n        var noop = function() {},\n            call = Function.call;\n    \n        // http://jsperf.com/uncurrythis\n        // 反科里化\n        function uncurryThis( fn ) {\n            return function() {\n                return call.apply( fn, arguments );\n            };\n        }\n    \n        function bindFn( fn, context ) {\n            return function() {\n                return fn.apply( context, arguments );\n            };\n        }\n    \n        function createObject( proto ) {\n            var f;\n    \n            if ( Object.create ) {\n                return Object.create( proto );\n            } else {\n                f = function() {};\n                f.prototype = proto;\n                return new f();\n            }\n        }\n    \n    \n        /**\n         * 基础类，提供一些简单常用的方法。\n         * @class Base\n         */\n        return {\n    \n            /**\n             * @property {String} version 当前版本号。\n             */\n            version: '0.1.2',\n    \n            /**\n             * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。\n             */\n            $: $,\n    \n            Deferred: promise.Deferred,\n    \n            isPromise: promise.isPromise,\n    \n            when: promise.when,\n    \n            /**\n             * @description  简单的浏览器检查结果。\n             *\n             * * `webkit`  webkit版本号，如果浏览器为非webkit内核，此属性为`undefined`。\n             * * `chrome`  chrome浏览器版本号，如果浏览器为chrome，此属性为`undefined`。\n             * * `ie`  ie浏览器版本号，如果浏览器为非ie，此属性为`undefined`。**暂不支持ie10+**\n             * * `firefox`  firefox浏览器版本号，如果浏览器为非firefox，此属性为`undefined`。\n             * * `safari`  safari浏览器版本号，如果浏览器为非safari，此属性为`undefined`。\n             * * `opera`  opera浏览器版本号，如果浏览器为非opera，此属性为`undefined`。\n             *\n             * @property {Object} [browser]\n             */\n            browser: (function( ua ) {\n                var ret = {},\n                    webkit = ua.match( /WebKit\\/([\\d.]+)/ ),\n                    chrome = ua.match( /Chrome\\/([\\d.]+)/ ) ||\n                        ua.match( /CriOS\\/([\\d.]+)/ ),\n    \n                    ie = ua.match( /MSIE\\s([\\d\\.]+)/ ) ||\n                        ua.match(/(?:trident)(?:.*rv:([\\w.]+))?/i),\n                    firefox = ua.match( /Firefox\\/([\\d.]+)/ ),\n                    safari = ua.match( /Safari\\/([\\d.]+)/ ),\n                    opera = ua.match( /OPR\\/([\\d.]+)/ );\n    \n                webkit && (ret.webkit = parseFloat( webkit[ 1 ] ));\n                chrome && (ret.chrome = parseFloat( chrome[ 1 ] ));\n                ie && (ret.ie = parseFloat( ie[ 1 ] ));\n                firefox && (ret.firefox = parseFloat( firefox[ 1 ] ));\n                safari && (ret.safari = parseFloat( safari[ 1 ] ));\n                opera && (ret.opera = parseFloat( opera[ 1 ] ));\n    \n                return ret;\n            })( navigator.userAgent ),\n    \n            /**\n             * @description  操作系统检查结果。\n             *\n             * * `android`  如果在android浏览器环境下，此值为对应的android版本号，否则为`undefined`。\n             * * `ios` 如果在ios浏览器环境下，此值为对应的ios版本号，否则为`undefined`。\n             * @property {Object} [os]\n             */\n            os: (function( ua ) {\n                var ret = {},\n    \n                    // osx = !!ua.match( /\\(Macintosh\\; Intel / ),\n                    android = ua.match( /(?:Android);?[\\s\\/]+([\\d.]+)?/ ),\n                    ios = ua.match( /(?:iPad|iPod|iPhone).*OS\\s([\\d_]+)/ );\n    \n                // osx && (ret.osx = true);\n                android && (ret.android = parseFloat( android[ 1 ] ));\n                ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) ));\n    \n                return ret;\n            })( navigator.userAgent ),\n    \n            /**\n             * 实现类与类之间的继承。\n             * @method inherits\n             * @grammar Base.inherits( super ) => child\n             * @grammar Base.inherits( super, protos ) => child\n             * @grammar Base.inherits( super, protos, statics ) => child\n             * @param  {Class} super 父类\n             * @param  {Object | Function} [protos] 子类或者对象。如果对象中包含constructor，子类将是用此属性值。\n             * @param  {Function} [protos.constructor] 子类构造器，不指定的话将创建个临时的直接执行父类构造器的方法。\n             * @param  {Object} [statics] 静态属性或方法。\n             * @return {Class} 返回子类。\n             * @example\n             * function Person() {\n             *     console.log( 'Super' );\n             * }\n             * Person.prototype.hello = function() {\n             *     console.log( 'hello' );\n             * };\n             *\n             * var Manager = Base.inherits( Person, {\n             *     world: function() {\n             *         console.log( 'World' );\n             *     }\n             * });\n             *\n             * // 因为没有指定构造器，父类的构造器将会执行。\n             * var instance = new Manager();    // => Super\n             *\n             * // 继承子父类的方法\n             * instance.hello();    // => hello\n             * instance.world();    // => World\n             *\n             * // 子类的__super__属性指向父类\n             * console.log( Manager.__super__ === Person );    // => true\n             */\n            inherits: function( Super, protos, staticProtos ) {\n                var child;\n    \n                if ( typeof protos === 'function' ) {\n                    child = protos;\n                    protos = null;\n                } else if ( protos && protos.hasOwnProperty('constructor') ) {\n                    child = protos.constructor;\n                } else {\n                    child = function() {\n                        return Super.apply( this, arguments );\n                    };\n                }\n    \n                // 复制静态方法\n                $.extend( true, child, Super, staticProtos || {} );\n    \n                /* jshint camelcase: false */\n    \n                // 让子类的__super__属性指向父类。\n                child.__super__ = Super.prototype;\n    \n                // 构建原型，添加原型方法或属性。\n                // 暂时用Object.create实现。\n                child.prototype = createObject( Super.prototype );\n                protos && $.extend( true, child.prototype, protos );\n    \n                return child;\n            },\n    \n            /**\n             * 一个不做任何事情的方法。可以用来赋值给默认的callback.\n             * @method noop\n             */\n            noop: noop,\n    \n            /**\n             * 返回一个新的方法，此方法将已指定的`context`来执行。\n             * @grammar Base.bindFn( fn, context ) => Function\n             * @method bindFn\n             * @example\n             * var doSomething = function() {\n             *         console.log( this.name );\n             *     },\n             *     obj = {\n             *         name: 'Object Name'\n             *     },\n             *     aliasFn = Base.bind( doSomething, obj );\n             *\n             *  aliasFn();    // => Object Name\n             *\n             */\n            bindFn: bindFn,\n    \n            /**\n             * 引用Console.log如果存在的话，否则引用一个[空函数loop](#WebUploader:Base.log)。\n             * @grammar Base.log( args... ) => undefined\n             * @method log\n             */\n            log: (function() {\n                if ( window.console ) {\n                    return bindFn( console.log, console );\n                }\n                return noop;\n            })(),\n    \n            nextTick: (function() {\n    \n                return function( cb ) {\n                    setTimeout( cb, 1 );\n                };\n    \n                // @bug 当浏览器不在当前窗口时就停了。\n                // var next = window.requestAnimationFrame ||\n                //     window.webkitRequestAnimationFrame ||\n                //     window.mozRequestAnimationFrame ||\n                //     function( cb ) {\n                //         window.setTimeout( cb, 1000 / 60 );\n                //     };\n    \n                // // fix: Uncaught TypeError: Illegal invocation\n                // return bindFn( next, window );\n            })(),\n    \n            /**\n             * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。\n             * 将用来将非数组对象转化成数组对象。\n             * @grammar Base.slice( target, start[, end] ) => Array\n             * @method slice\n             * @example\n             * function doSomthing() {\n             *     var args = Base.slice( arguments, 1 );\n             *     console.log( args );\n             * }\n             *\n             * doSomthing( 'ignored', 'arg2', 'arg3' );    // => Array [\"arg2\", \"arg3\"]\n             */\n            slice: uncurryThis( [].slice ),\n    \n            /**\n             * 生成唯一的ID\n             * @method guid\n             * @grammar Base.guid() => String\n             * @grammar Base.guid( prefx ) => String\n             */\n            guid: (function() {\n                var counter = 0;\n    \n                return function( prefix ) {\n                    var guid = (+new Date()).toString( 32 ),\n                        i = 0;\n    \n                    for ( ; i < 5; i++ ) {\n                        guid += Math.floor( Math.random() * 65535 ).toString( 32 );\n                    }\n    \n                    return (prefix || 'wu_') + guid + (counter++).toString( 32 );\n                };\n            })(),\n    \n            /**\n             * 格式化文件大小, 输出成带单位的字符串\n             * @method formatSize\n             * @grammar Base.formatSize( size ) => String\n             * @grammar Base.formatSize( size, pointLength ) => String\n             * @grammar Base.formatSize( size, pointLength, units ) => String\n             * @param {Number} size 文件大小\n             * @param {Number} [pointLength=2] 精确到的小数点数。\n             * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节，到千字节，一直往上指定。如果单位数组里面只指定了到了K(千字节)，同时文件大小大于M, 此方法的输出将还是显示成多少K.\n             * @example\n             * console.log( Base.formatSize( 100 ) );    // => 100B\n             * console.log( Base.formatSize( 1024 ) );    // => 1.00K\n             * console.log( Base.formatSize( 1024, 0 ) );    // => 1K\n             * console.log( Base.formatSize( 1024 * 1024 ) );    // => 1.00M\n             * console.log( Base.formatSize( 1024 * 1024 * 1024 ) );    // => 1.00G\n             * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) );    // => 1024MB\n             */\n            formatSize: function( size, pointLength, units ) {\n                var unit;\n    \n                units = units || [ 'B', 'K', 'M', 'G', 'TB' ];\n    \n                while ( (unit = units.shift()) && size > 1024 ) {\n                    size = size / 1024;\n                }\n    \n                return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) +\n                        unit;\n            }\n        };\n    });\n    /**\n     * 事件处理类，可以独立使用，也可以扩展给对象使用。\n     * @fileOverview Mediator\n     */\n    define('mediator',[\n        'base'\n    ], function( Base ) {\n        var $ = Base.$,\n            slice = [].slice,\n            separator = /\\s+/,\n            protos;\n    \n        // 根据条件过滤出事件handlers.\n        function findHandlers( arr, name, callback, context ) {\n            return $.grep( arr, function( handler ) {\n                return handler &&\n                        (!name || handler.e === name) &&\n                        (!callback || handler.cb === callback ||\n                        handler.cb._cb === callback) &&\n                        (!context || handler.ctx === context);\n            });\n        }\n    \n        function eachEvent( events, callback, iterator ) {\n            // 不支持对象，只支持多个event用空格隔开\n            $.each( (events || '').split( separator ), function( _, key ) {\n                iterator( key, callback );\n            });\n        }\n    \n        function triggerHanders( events, args ) {\n            var stoped = false,\n                i = -1,\n                len = events.length,\n                handler;\n    \n            while ( ++i < len ) {\n                handler = events[ i ];\n    \n                if ( handler.cb.apply( handler.ctx2, args ) === false ) {\n                    stoped = true;\n                    break;\n                }\n            }\n    \n            return !stoped;\n        }\n    \n        protos = {\n    \n            /**\n             * 绑定事件。\n             *\n             * `callback`方法在执行时，arguments将会来源于trigger的时候携带的参数。如\n             * ```javascript\n             * var obj = {};\n             *\n             * // 使得obj有事件行为\n             * Mediator.installTo( obj );\n             *\n             * obj.on( 'testa', function( arg1, arg2 ) {\n             *     console.log( arg1, arg2 ); // => 'arg1', 'arg2'\n             * });\n             *\n             * obj.trigger( 'testa', 'arg1', 'arg2' );\n             * ```\n             *\n             * 如果`callback`中，某一个方法`return false`了，则后续的其他`callback`都不会被执行到。\n             * 切会影响到`trigger`方法的返回值，为`false`。\n             *\n             * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处，\n             * 就是第一个参数为`type`，记录当前是什么事件在触发。此类`callback`的优先级比脚低，会再正常`callback`执行完后触发。\n             * ```javascript\n             * obj.on( 'all', function( type, arg1, arg2 ) {\n             *     console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2'\n             * });\n             * ```\n             *\n             * @method on\n             * @grammar on( name, callback[, context] ) => self\n             * @param  {String}   name     事件名，支持多个事件用空格隔开\n             * @param  {Function} callback 事件处理器\n             * @param  {Object}   [context]  事件处理器的上下文。\n             * @return {self} 返回自身，方便链式\n             * @chainable\n             * @class Mediator\n             */\n            on: function( name, callback, context ) {\n                var me = this,\n                    set;\n    \n                if ( !callback ) {\n                    return this;\n                }\n    \n                set = this._events || (this._events = []);\n    \n                eachEvent( name, callback, function( name, callback ) {\n                    var handler = { e: name };\n    \n                    handler.cb = callback;\n                    handler.ctx = context;\n                    handler.ctx2 = context || me;\n                    handler.id = set.length;\n    \n                    set.push( handler );\n                });\n    \n                return this;\n            },\n    \n            /**\n             * 绑定事件，且当handler执行完后，自动解除绑定。\n             * @method once\n             * @grammar once( name, callback[, context] ) => self\n             * @param  {String}   name     事件名\n             * @param  {Function} callback 事件处理器\n             * @param  {Object}   [context]  事件处理器的上下文。\n             * @return {self} 返回自身，方便链式\n             * @chainable\n             */\n            once: function( name, callback, context ) {\n                var me = this;\n    \n                if ( !callback ) {\n                    return me;\n                }\n    \n                eachEvent( name, callback, function( name, callback ) {\n                    var once = function() {\n                            me.off( name, once );\n                            return callback.apply( context || me, arguments );\n                        };\n    \n                    once._cb = callback;\n                    me.on( name, once, context );\n                });\n    \n                return me;\n            },\n    \n            /**\n             * 解除事件绑定\n             * @method off\n             * @grammar off( [name[, callback[, context] ] ] ) => self\n             * @param  {String}   [name]     事件名\n             * @param  {Function} [callback] 事件处理器\n             * @param  {Object}   [context]  事件处理器的上下文。\n             * @return {self} 返回自身，方便链式\n             * @chainable\n             */\n            off: function( name, cb, ctx ) {\n                var events = this._events;\n    \n                if ( !events ) {\n                    return this;\n                }\n    \n                if ( !name && !cb && !ctx ) {\n                    this._events = [];\n                    return this;\n                }\n    \n                eachEvent( name, cb, function( name, cb ) {\n                    $.each( findHandlers( events, name, cb, ctx ), function() {\n                        delete events[ this.id ];\n                    });\n                });\n    \n                return this;\n            },\n    \n            /**\n             * 触发事件\n             * @method trigger\n             * @grammar trigger( name[, args...] ) => self\n             * @param  {String}   type     事件名\n             * @param  {*} [...] 任意参数\n             * @return {Boolean} 如果handler中return false了，则返回false, 否则返回true\n             */\n            trigger: function( type ) {\n                var args, events, allEvents;\n    \n                if ( !this._events || !type ) {\n                    return this;\n                }\n    \n                args = slice.call( arguments, 1 );\n                events = findHandlers( this._events, type );\n                allEvents = findHandlers( this._events, 'all' );\n    \n                return triggerHanders( events, args ) &&\n                        triggerHanders( allEvents, arguments );\n            }\n        };\n    \n        /**\n         * 中介者，它本身是个单例，但可以通过[installTo](#WebUploader:Mediator:installTo)方法，使任何对象具备事件行为。\n         * 主要目的是负责模块与模块之间的合作，降低耦合度。\n         *\n         * @class Mediator\n         */\n        return $.extend({\n    \n            /**\n             * 可以通过这个接口，使任何对象具备事件功能。\n             * @method installTo\n             * @param  {Object} obj 需要具备事件行为的对象。\n             * @return {Object} 返回obj.\n             */\n            installTo: function( obj ) {\n                return $.extend( obj, protos );\n            }\n    \n        }, protos );\n    });\n    /**\n     * @fileOverview Uploader上传类\n     */\n    define('uploader',[\n        'base',\n        'mediator'\n    ], function( Base, Mediator ) {\n    \n        var $ = Base.$;\n    \n        /**\n         * 上传入口类。\n         * @class Uploader\n         * @constructor\n         * @grammar new Uploader( opts ) => Uploader\n         * @example\n         * var uploader = WebUploader.Uploader({\n         *     swf: 'path_of_swf/Uploader.swf',\n         *\n         *     // 开起分片上传。\n         *     chunked: true\n         * });\n         */\n        function Uploader( opts ) {\n            this.options = $.extend( true, {}, Uploader.options, opts );\n            this._init( this.options );\n        }\n    \n        // default Options\n        // widgets中有相应扩展\n        Uploader.options = {};\n        Mediator.installTo( Uploader.prototype );\n    \n        // 批量添加纯命令式方法。\n        $.each({\n            upload: 'start-upload',\n            stop: 'stop-upload',\n            getFile: 'get-file',\n            getFiles: 'get-files',\n            addFile: 'add-file',\n            addFiles: 'add-file',\n            sort: 'sort-files',\n            removeFile: 'remove-file',\n            skipFile: 'skip-file',\n            retry: 'retry',\n            isInProgress: 'is-in-progress',\n            makeThumb: 'make-thumb',\n            getDimension: 'get-dimension',\n            addButton: 'add-btn',\n            getRuntimeType: 'get-runtime-type',\n            refresh: 'refresh',\n            disable: 'disable',\n            enable: 'enable',\n            reset: 'reset'\n        }, function( fn, command ) {\n            Uploader.prototype[ fn ] = function() {\n                return this.request( command, arguments );\n            };\n        });\n    \n        $.extend( Uploader.prototype, {\n            state: 'pending',\n    \n            _init: function( opts ) {\n                var me = this;\n    \n                me.request( 'init', opts, function() {\n                    me.state = 'ready';\n                    me.trigger('ready');\n                });\n            },\n    \n            /**\n             * 获取或者设置Uploader配置项。\n             * @method option\n             * @grammar option( key ) => *\n             * @grammar option( key, val ) => self\n             * @example\n             *\n             * // 初始状态图片上传前不会压缩\n             * var uploader = new WebUploader.Uploader({\n             *     resize: null;\n             * });\n             *\n             * // 修改后图片上传前，尝试将图片压缩到1600 * 1600\n             * uploader.options( 'resize', {\n             *     width: 1600,\n             *     height: 1600\n             * });\n             */\n            option: function( key, val ) {\n                var opts = this.options;\n    \n                // setter\n                if ( arguments.length > 1 ) {\n    \n                    if ( $.isPlainObject( val ) &&\n                            $.isPlainObject( opts[ key ] ) ) {\n                        $.extend( opts[ key ], val );\n                    } else {\n                        opts[ key ] = val;\n                    }\n    \n                } else {    // getter\n                    return key ? opts[ key ] : opts;\n                }\n            },\n    \n            /**\n             * 获取文件统计信息。返回一个包含一下信息的对象。\n             * * `successNum` 上传成功的文件数\n             * * `uploadFailNum` 上传失败的文件数\n             * * `cancelNum` 被删除的文件数\n             * * `invalidNum` 无效的文件数\n             * * `queueNum` 还在队列中的文件数\n             * @method getStats\n             * @grammar getStats() => Object\n             */\n            getStats: function() {\n                // return this._mgr.getStats.apply( this._mgr, arguments );\n                var stats = this.request('get-stats');\n    \n                return {\n                    successNum: stats.numOfSuccess,\n    \n                    // who care?\n                    // queueFailNum: 0,\n                    cancelNum: stats.numOfCancel,\n                    invalidNum: stats.numOfInvalid,\n                    uploadFailNum: stats.numOfUploadFailed,\n                    queueNum: stats.numOfQueue\n                };\n            },\n    \n            // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器\n            trigger: function( type/*, args...*/ ) {\n                var args = [].slice.call( arguments, 1 ),\n                    opts = this.options,\n                    name = 'on' + type.substring( 0, 1 ).toUpperCase() +\n                        type.substring( 1 );\n    \n                if (\n                        // 调用通过on方法注册的handler.\n                        Mediator.trigger.apply( this, arguments ) === false ||\n    \n                        // 调用opts.onEvent\n                        $.isFunction( opts[ name ] ) &&\n                        opts[ name ].apply( this, args ) === false ||\n    \n                        // 调用this.onEvent\n                        $.isFunction( this[ name ] ) &&\n                        this[ name ].apply( this, args ) === false ||\n    \n                        // 广播所有uploader的事件。\n                        Mediator.trigger.apply( Mediator,\n                        [ this, type ].concat( args ) ) === false ) {\n    \n                    return false;\n                }\n    \n                return true;\n            },\n    \n            // widgets/widget.js将补充此方法的详细文档。\n            request: Base.noop\n        });\n    \n        /**\n         * 创建Uploader实例，等同于new Uploader( opts );\n         * @method create\n         * @class Base\n         * @static\n         * @grammar Base.create( opts ) => Uploader\n         */\n        Base.create = Uploader.create = function( opts ) {\n            return new Uploader( opts );\n        };\n    \n        // 暴露Uploader，可以通过它来扩展业务逻辑。\n        Base.Uploader = Uploader;\n    \n        return Uploader;\n    });\n    /**\n     * @fileOverview Runtime管理器，负责Runtime的选择, 连接\n     */\n    define('runtime/runtime',[\n        'base',\n        'mediator'\n    ], function( Base, Mediator ) {\n    \n        var $ = Base.$,\n            factories = {},\n    \n            // 获取对象的第一个key\n            getFirstKey = function( obj ) {\n                for ( var key in obj ) {\n                    if ( obj.hasOwnProperty( key ) ) {\n                        return key;\n                    }\n                }\n                return null;\n            };\n    \n        // 接口类。\n        function Runtime( options ) {\n            this.options = $.extend({\n                container: document.body\n            }, options );\n            this.uid = Base.guid('rt_');\n        }\n    \n        $.extend( Runtime.prototype, {\n    \n            getContainer: function() {\n                var opts = this.options,\n                    parent, container;\n    \n                if ( this._container ) {\n                    return this._container;\n                }\n    \n                parent = $( opts.container || document.body );\n                container = $( document.createElement('div') );\n    \n                container.attr( 'id', 'rt_' + this.uid );\n                container.css({\n                    position: 'absolute',\n                    top: '0px',\n                    left: '0px',\n                    width: '1px',\n                    height: '1px',\n                    overflow: 'hidden'\n                });\n    \n                parent.append( container );\n                parent.addClass('webuploader-container');\n                this._container = container;\n                return container;\n            },\n    \n            init: Base.noop,\n            exec: Base.noop,\n    \n            destroy: function() {\n                if ( this._container ) {\n                    this._container.parentNode.removeChild( this.__container );\n                }\n    \n                this.off();\n            }\n        });\n    \n        Runtime.orders = 'html5,flash';\n    \n    \n        /**\n         * 添加Runtime实现。\n         * @param {String} type    类型\n         * @param {Runtime} factory 具体Runtime实现。\n         */\n        Runtime.addRuntime = function( type, factory ) {\n            factories[ type ] = factory;\n        };\n    \n        Runtime.hasRuntime = function( type ) {\n            return !!(type ? factories[ type ] : getFirstKey( factories ));\n        };\n    \n        Runtime.create = function( opts, orders ) {\n            var type, runtime;\n    \n            orders = orders || Runtime.orders;\n            $.each( orders.split( /\\s*,\\s*/g ), function() {\n                if ( factories[ this ] ) {\n                    type = this;\n                    return false;\n                }\n            });\n    \n            type = type || getFirstKey( factories );\n    \n            if ( !type ) {\n                throw new Error('Runtime Error');\n            }\n    \n            runtime = new factories[ type ]( opts );\n            return runtime;\n        };\n    \n        Mediator.installTo( Runtime.prototype );\n        return Runtime;\n    });\n    \n    /**\n     * @fileOverview Runtime管理器，负责Runtime的选择, 连接\n     */\n    define('runtime/client',[\n        'base',\n        'mediator',\n        'runtime/runtime'\n    ], function( Base, Mediator, Runtime ) {\n    \n        var cache;\n    \n        cache = (function() {\n            var obj = {};\n    \n            return {\n                add: function( runtime ) {\n                    obj[ runtime.uid ] = runtime;\n                },\n    \n                get: function( ruid, standalone ) {\n                    var i;\n    \n                    if ( ruid ) {\n                        return obj[ ruid ];\n                    }\n    \n                    for ( i in obj ) {\n                        // 有些类型不能重用，比如filepicker.\n                        if ( standalone && obj[ i ].__standalone ) {\n                            continue;\n                        }\n    \n                        return obj[ i ];\n                    }\n    \n                    return null;\n                },\n    \n                remove: function( runtime ) {\n                    delete obj[ runtime.uid ];\n                }\n            };\n        })();\n    \n        function RuntimeClient( component, standalone ) {\n            var deferred = Base.Deferred(),\n                runtime;\n    \n            this.uid = Base.guid('client_');\n    \n            // 允许runtime没有初始化之前，注册一些方法在初始化后执行。\n            this.runtimeReady = function( cb ) {\n                return deferred.done( cb );\n            };\n    \n            this.connectRuntime = function( opts, cb ) {\n    \n                // already connected.\n                if ( runtime ) {\n                    throw new Error('already connected!');\n                }\n    \n                deferred.done( cb );\n    \n                if ( typeof opts === 'string' && cache.get( opts ) ) {\n                    runtime = cache.get( opts );\n                }\n    \n                // 像filePicker只能独立存在，不能公用。\n                runtime = runtime || cache.get( null, standalone );\n    \n                // 需要创建\n                if ( !runtime ) {\n                    runtime = Runtime.create( opts, opts.runtimeOrder );\n                    runtime.__promise = deferred.promise();\n                    runtime.once( 'ready', deferred.resolve );\n                    runtime.init();\n                    cache.add( runtime );\n                    runtime.__client = 1;\n                } else {\n                    // 来自cache\n                    Base.$.extend( runtime.options, opts );\n                    runtime.__promise.then( deferred.resolve );\n                    runtime.__client++;\n                }\n    \n                standalone && (runtime.__standalone = standalone);\n                return runtime;\n            };\n    \n            this.getRuntime = function() {\n                return runtime;\n            };\n    \n            this.disconnectRuntime = function() {\n                if ( !runtime ) {\n                    return;\n                }\n    \n                runtime.__client--;\n    \n                if ( runtime.__client <= 0 ) {\n                    cache.remove( runtime );\n                    delete runtime.__promise;\n                    runtime.destroy();\n                }\n    \n                runtime = null;\n            };\n    \n            this.exec = function() {\n                if ( !runtime ) {\n                    return;\n                }\n    \n                var args = Base.slice( arguments );\n                component && args.unshift( component );\n    \n                return runtime.exec.apply( this, args );\n            };\n    \n            this.getRuid = function() {\n                return runtime && runtime.uid;\n            };\n    \n            this.destroy = (function( destroy ) {\n                return function() {\n                    destroy && destroy.apply( this, arguments );\n                    this.trigger('destroy');\n                    this.off();\n                    this.exec('destroy');\n                    this.disconnectRuntime();\n                };\n            })( this.destroy );\n        }\n    \n        Mediator.installTo( RuntimeClient.prototype );\n        return RuntimeClient;\n    });\n    /**\n     * @fileOverview Blob\n     */\n    define('lib/blob',[\n        'base',\n        'runtime/client'\n    ], function( Base, RuntimeClient ) {\n    \n        function Blob( ruid, source ) {\n            var me = this;\n    \n            me.source = source;\n            me.ruid = ruid;\n    \n            RuntimeClient.call( me, 'Blob' );\n    \n            this.uid = source.uid || this.uid;\n            this.type = source.type || '';\n            this.size = source.size || 0;\n    \n            if ( ruid ) {\n                me.connectRuntime( ruid );\n            }\n        }\n    \n        Base.inherits( RuntimeClient, {\n            constructor: Blob,\n    \n            slice: function( start, end ) {\n                return this.exec( 'slice', start, end );\n            },\n    \n            getSource: function() {\n                return this.source;\n            }\n        });\n    \n        return Blob;\n    });\n    /**\n     * 为了统一化Flash的File和HTML5的File而存在。\n     * 以至于要调用Flash里面的File，也可以像调用HTML5版本的File一下。\n     * @fileOverview File\n     */\n    define('lib/file',[\n        'base',\n        'lib/blob'\n    ], function( Base, Blob ) {\n    \n        var uid = 1,\n            rExt = /\\.([^.]+)$/;\n    \n        function File( ruid, file ) {\n            var ext;\n    \n            Blob.apply( this, arguments );\n            this.name = file.name || ('untitled' + uid++);\n            ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : '';\n    \n            // todo 支持其他类型文件的转换。\n    \n            // 如果有mimetype, 但是文件名里面没有找出后缀规律\n            if ( !ext && this.type ) {\n                ext = /\\/(jpg|jpeg|png|gif|bmp)$/i.exec( this.type ) ?\n                        RegExp.$1.toLowerCase() : '';\n                this.name += '.' + ext;\n            }\n    \n            // 如果没有指定mimetype, 但是知道文件后缀。\n            if ( !this.type &&  ~'jpg,jpeg,png,gif,bmp'.indexOf( ext ) ) {\n                this.type = 'image/' + (ext === 'jpg' ? 'jpeg' : ext);\n            }\n    \n            this.ext = ext;\n            this.lastModifiedDate = file.lastModifiedDate ||\n                    (new Date()).toLocaleString();\n        }\n    \n        return Base.inherits( Blob, File );\n    });\n    \n    /**\n     * @fileOverview 错误信息\n     */\n    define('lib/filepicker',[\n        'base',\n        'runtime/client',\n        'lib/file'\n    ], function( Base, RuntimeClent, File ) {\n    \n        var $ = Base.$;\n    \n        function FilePicker( opts ) {\n            opts = this.options = $.extend({}, FilePicker.options, opts );\n    \n            opts.container = $( opts.id );\n    \n            if ( !opts.container.length ) {\n                throw new Error('按钮指定错误');\n            }\n    \n            opts.innerHTML = opts.innerHTML || opts.label ||\n                    opts.container.html() || '';\n    \n            opts.button = $( opts.button || document.createElement('div') );\n            opts.button.html( opts.innerHTML );\n            opts.container.html( opts.button );\n    \n            RuntimeClent.call( this, 'FilePicker', true );\n        }\n    \n        FilePicker.options = {\n            button: null,\n            container: null,\n            label: null,\n            innerHTML: null,\n            multiple: true,\n            accept: null,\n            name: 'file'\n        };\n    \n        Base.inherits( RuntimeClent, {\n            constructor: FilePicker,\n    \n            init: function() {\n                var me = this,\n                    opts = me.options,\n                    button = opts.button;\n    \n                button.addClass('webuploader-pick');\n    \n                me.on( 'all', function( type ) {\n                    var files;\n    \n                    switch ( type ) {\n                        case 'mouseenter':\n                            button.addClass('webuploader-pick-hover');\n                            break;\n    \n                        case 'mouseleave':\n                            button.removeClass('webuploader-pick-hover');\n                            break;\n    \n                        case 'change':\n                            files = me.exec('getFiles');\n                            me.trigger( 'select', $.map( files, function( file ) {\n                                file = new File( me.getRuid(), file );\n    \n                                // 记录来源。\n                                file._refer = opts.container;\n                                return file;\n                            }), opts.container );\n                            break;\n                    }\n                });\n    \n                me.connectRuntime( opts, function() {\n                    me.refresh();\n                    me.exec( 'init', opts );\n                    me.trigger('ready');\n                });\n    \n                $( window ).on( 'resize', function() {\n                    me.refresh();\n                });\n            },\n    \n            refresh: function() {\n                var shimContainer = this.getRuntime().getContainer(),\n                    button = this.options.button,\n                    width = button.outerWidth ?\n                            button.outerWidth() : button.width(),\n    \n                    height = button.outerHeight ?\n                            button.outerHeight() : button.height(),\n    \n                    pos = button.offset();\n    \n                width && height && shimContainer.css({\n                    bottom: 'auto',\n                    right: 'auto',\n                    width: width + 'px',\n                    height: height + 'px'\n                }).offset( pos );\n            },\n    \n            enable: function() {\n                var btn = this.options.button;\n    \n                btn.removeClass('webuploader-pick-disable');\n                this.refresh();\n            },\n    \n            disable: function() {\n                var btn = this.options.button;\n    \n                this.getRuntime().getContainer().css({\n                    top: '-99999px'\n                });\n    \n                btn.addClass('webuploader-pick-disable');\n            },\n    \n            destroy: function() {\n                if ( this.runtime ) {\n                    this.exec('destroy');\n                    this.disconnectRuntime();\n                }\n            }\n        });\n    \n        return FilePicker;\n    });\n    \n    /**\n     * @fileOverview 组件基类。\n     */\n    define('widgets/widget',[\n        'base',\n        'uploader'\n    ], function( Base, Uploader ) {\n    \n        var $ = Base.$,\n            _init = Uploader.prototype._init,\n            IGNORE = {},\n            widgetClass = [];\n    \n        function isArrayLike( obj ) {\n            if ( !obj ) {\n                return false;\n            }\n    \n            var length = obj.length,\n                type = $.type( obj );\n    \n            if ( obj.nodeType === 1 && length ) {\n                return true;\n            }\n    \n            return type === 'array' || type !== 'function' && type !== 'string' &&\n                    (length === 0 || typeof length === 'number' && length > 0 &&\n                    (length - 1) in obj);\n        }\n    \n        function Widget( uploader ) {\n            this.owner = uploader;\n            this.options = uploader.options;\n        }\n    \n        $.extend( Widget.prototype, {\n    \n            init: Base.noop,\n    \n            // 类Backbone的事件监听声明，监听uploader实例上的事件\n            // widget直接无法监听事件，事件只能通过uploader来传递\n            invoke: function( apiName, args ) {\n    \n                /*\n                    {\n                        'make-thumb': 'makeThumb'\n                    }\n                 */\n                var map = this.responseMap;\n    \n                // 如果无API响应声明则忽略\n                if ( !map || !(apiName in map) || !(map[ apiName ] in this) ||\n                        !$.isFunction( this[ map[ apiName ] ] ) ) {\n    \n                    return IGNORE;\n                }\n    \n                return this[ map[ apiName ] ].apply( this, args );\n    \n            },\n    \n            /**\n             * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。\n             * @method request\n             * @grammar request( command, args ) => * | Promise\n             * @grammar request( command, args, callback ) => Promise\n             * @for  Uploader\n             */\n            request: function() {\n                return this.owner.request.apply( this.owner, arguments );\n            }\n        });\n    \n        // 扩展Uploader.\n        $.extend( Uploader.prototype, {\n    \n            // 覆写_init用来初始化widgets\n            _init: function() {\n                var me = this,\n                    widgets = me._widgets = [];\n    \n                $.each( widgetClass, function( _, klass ) {\n                    widgets.push( new klass( me ) );\n                });\n    \n                return _init.apply( me, arguments );\n            },\n    \n            request: function( apiName, args, callback ) {\n                var i = 0,\n                    widgets = this._widgets,\n                    len = widgets.length,\n                    rlts = [],\n                    dfds = [],\n                    widget, rlt, promise, key;\n    \n                args = isArrayLike( args ) ? args : [ args ];\n    \n                for ( ; i < len; i++ ) {\n                    widget = widgets[ i ];\n                    rlt = widget.invoke( apiName, args );\n    \n                    if ( rlt !== IGNORE ) {\n    \n                        // Deferred对象\n                        if ( Base.isPromise( rlt ) ) {\n                            dfds.push( rlt );\n                        } else {\n                            rlts.push( rlt );\n                        }\n                    }\n                }\n    \n                // 如果有callback，则用异步方式。\n                if ( callback || dfds.length ) {\n                    promise = Base.when.apply( Base, dfds );\n                    key = promise.pipe ? 'pipe' : 'then';\n    \n                    // 很重要不能删除。删除了会死循环。\n                    // 保证执行顺序。让callback总是在下一个tick中执行。\n                    return promise[ key ](function() {\n                                var deferred = Base.Deferred(),\n                                    args = arguments;\n    \n                                setTimeout(function() {\n                                    deferred.resolve.apply( deferred, args );\n                                }, 1 );\n    \n                                return deferred.promise();\n                            })[ key ]( callback || Base.noop );\n                } else {\n                    return rlts[ 0 ];\n                }\n            }\n        });\n    \n        /**\n         * 添加组件\n         * @param  {object} widgetProto 组件原型，构造函数通过constructor属性定义\n         * @param  {object} responseMap API名称与函数实现的映射\n         * @example\n         *     Uploader.register( {\n         *         init: function( options ) {},\n         *         makeThumb: function() {}\n         *     }, {\n         *         'make-thumb': 'makeThumb'\n         *     } );\n         */\n        Uploader.register = Widget.register = function( responseMap, widgetProto ) {\n            var map = { init: 'init' },\n                klass;\n    \n            if ( arguments.length === 1 ) {\n                widgetProto = responseMap;\n                widgetProto.responseMap = map;\n            } else {\n                widgetProto.responseMap = $.extend( map, responseMap );\n            }\n    \n            klass = Base.inherits( Widget, widgetProto );\n            widgetClass.push( klass );\n    \n            return klass;\n        };\n    \n        return Widget;\n    });\n    /**\n     * @fileOverview 文件选择相关\n     */\n    define('widgets/filepicker',[\n        'base',\n        'uploader',\n        'lib/filepicker',\n        'widgets/widget'\n    ], function( Base, Uploader, FilePicker ) {\n        var $ = Base.$;\n    \n        $.extend( Uploader.options, {\n    \n            /**\n             * @property {Selector | Object} [pick=undefined]\n             * @namespace options\n             * @for Uploader\n             * @description 指定选择文件的按钮容器，不指定则不创建按钮。\n             *\n             * * `id` {Seletor} 指定选择文件的按钮容器，不指定则不创建按钮。\n             * * `label` {String} 请采用 `innerHTML` 代替\n             * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。\n             * * `multiple` {Boolean} 是否开起同时选择多个文件能力。\n             */\n            pick: null,\n    \n            /**\n             * @property {Arroy} [accept=null]\n             * @namespace options\n             * @for Uploader\n             * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表，所以这里需要分开指定。\n             *\n             * * `title` {String} 文字描述\n             * * `extensions` {String} 允许的文件后缀，不带点，多个用逗号分割。\n             * * `mimeTypes` {String} 多个用逗号分割。\n             *\n             * 如：\n             *\n             * ```\n             * {\n             *     title: 'Images',\n             *     extensions: 'gif,jpg,jpeg,bmp,png',\n             *     mimeTypes: 'image/*'\n             * }\n             * ```\n             */\n            accept: null/*{\n                title: 'Images',\n                extensions: 'gif,jpg,jpeg,bmp,png',\n                mimeTypes: 'image/*'\n            }*/\n        });\n    \n        return Uploader.register({\n            'add-btn': 'addButton',\n            refresh: 'refresh',\n            disable: 'disable',\n            enable: 'enable'\n        }, {\n    \n            init: function( opts ) {\n                this.pickers = [];\n                return opts.pick && this.addButton( opts.pick );\n            },\n    \n            refresh: function() {\n                $.each( this.pickers, function() {\n                    this.refresh();\n                });\n            },\n    \n            /**\n             * @method addButton\n             * @for Uploader\n             * @grammar addButton( pick ) => Promise\n             * @description\n             * 添加文件选择按钮，如果一个按钮不够，需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。\n             * @example\n             * uploader.addButton({\n             *     id: '#btnContainer',\n             *     innerHTML: '选择文件'\n             * });\n             */\n            addButton: function( pick ) {\n                var me = this,\n                    opts = me.options,\n                    accept = opts.accept,\n                    options, picker, deferred;\n    \n                if ( !pick ) {\n                    return;\n                }\n    \n                deferred = Base.Deferred();\n                $.isPlainObject( pick ) || (pick = {\n                    id: pick\n                });\n    \n                options = $.extend({}, pick, {\n                    accept: $.isPlainObject( accept ) ? [ accept ] : accept,\n                    swf: opts.swf,\n                    runtimeOrder: opts.runtimeOrder\n                });\n    \n                picker = new FilePicker( options );\n    \n                picker.once( 'ready', deferred.resolve );\n                picker.on( 'select', function( files ) {\n                    me.owner.request( 'add-file', [ files ]);\n                });\n                picker.init();\n    \n                this.pickers.push( picker );\n    \n                return deferred.promise();\n            },\n    \n            disable: function() {\n                $.each( this.pickers, function() {\n                    this.disable();\n                });\n            },\n    \n            enable: function() {\n                $.each( this.pickers, function() {\n                    this.enable();\n                });\n            }\n        });\n    });\n    /**\n     * @fileOverview Image\n     */\n    define('lib/image',[\n        'base',\n        'runtime/client',\n        'lib/blob'\n    ], function( Base, RuntimeClient, Blob ) {\n        var $ = Base.$;\n    \n        // 构造器。\n        function Image( opts ) {\n            this.options = $.extend({}, Image.options, opts );\n            RuntimeClient.call( this, 'Image' );\n    \n            this.on( 'load', function() {\n                this._info = this.exec('info');\n                this._meta = this.exec('meta');\n            });\n        }\n    \n        // 默认选项。\n        Image.options = {\n    \n            // 默认的图片处理质量\n            quality: 90,\n    \n            // 是否裁剪\n            crop: false,\n    \n            // 是否保留头部信息\n            preserveHeaders: true,\n    \n            // 是否允许放大。\n            allowMagnify: true\n        };\n    \n        // 继承RuntimeClient.\n        Base.inherits( RuntimeClient, {\n            constructor: Image,\n    \n            info: function( val ) {\n    \n                // setter\n                if ( val ) {\n                    this._info = val;\n                    return this;\n                }\n    \n                // getter\n                return this._info;\n            },\n    \n            meta: function( val ) {\n    \n                // setter\n                if ( val ) {\n                    this._meta = val;\n                    return this;\n                }\n    \n                // getter\n                return this._meta;\n            },\n    \n            loadFromBlob: function( blob ) {\n                var me = this,\n                    ruid = blob.getRuid();\n    \n                this.connectRuntime( ruid, function() {\n                    me.exec( 'init', me.options );\n                    me.exec( 'loadFromBlob', blob );\n                });\n            },\n    \n            resize: function() {\n                var args = Base.slice( arguments );\n                return this.exec.apply( this, [ 'resize' ].concat( args ) );\n            },\n    \n            getAsDataUrl: function( type ) {\n                return this.exec( 'getAsDataUrl', type );\n            },\n    \n            getAsBlob: function( type ) {\n                var blob = this.exec( 'getAsBlob', type );\n    \n                return new Blob( this.getRuid(), blob );\n            }\n        });\n    \n        return Image;\n    });\n    /**\n     * @fileOverview 图片操作, 负责预览图片和上传前压缩图片\n     */\n    define('widgets/image',[\n        'base',\n        'uploader',\n        'lib/image',\n        'widgets/widget'\n    ], function( Base, Uploader, Image ) {\n    \n        var $ = Base.$,\n            throttle;\n    \n        // 根据要处理的文件大小来节流，一次不能处理太多，会卡。\n        throttle = (function( max ) {\n            var occupied = 0,\n                waiting = [],\n                tick = function() {\n                    var item;\n    \n                    while ( waiting.length && occupied < max ) {\n                        item = waiting.shift();\n                        occupied += item[ 0 ];\n                        item[ 1 ]();\n                    }\n                };\n    \n            return function( emiter, size, cb ) {\n                waiting.push([ size, cb ]);\n                emiter.once( 'destroy', function() {\n                    occupied -= size;\n                    setTimeout( tick, 1 );\n                });\n                setTimeout( tick, 1 );\n            };\n        })( 5 * 1024 * 1024 );\n    \n        $.extend( Uploader.options, {\n    \n            /**\n             * @property {Object} [thumb]\n             * @namespace options\n             * @for Uploader\n             * @description 配置生成缩略图的选项。\n             *\n             * 默认为：\n             *\n             * ```javascript\n             * {\n             *     width: 110,\n             *     height: 110,\n             *\n             *     // 图片质量，只有type为`image/jpeg`的时候才有效。\n             *     quality: 70,\n             *\n             *     // 是否允许放大，如果想要生成小图的时候不失真，此选项应该设置为false.\n             *     allowMagnify: true,\n             *\n             *     // 是否允许裁剪。\n             *     crop: true,\n             *\n             *     // 是否保留头部meta信息。\n             *     preserveHeaders: false,\n             *\n             *     // 为空的话则保留原有图片格式。\n             *     // 否则强制转换成指定的类型。\n             *     type: 'image/jpeg'\n             * }\n             * ```\n             */\n            thumb: {\n                width: 110,\n                height: 110,\n                quality: 70,\n                allowMagnify: true,\n                crop: true,\n                preserveHeaders: false,\n    \n                // 为空的话则保留原有图片格式。\n                // 否则强制转换成指定的类型。\n                // IE 8下面 base64 大小不能超过 32K 否则预览失败，而非 jpeg 编码的图片很可\n                // 能会超过 32k, 所以这里设置成预览的时候都是 image/jpeg\n                type: 'image/jpeg'\n            },\n    \n            /**\n             * @property {Object} [compress]\n             * @namespace options\n             * @for Uploader\n             * @description 配置压缩的图片的选项。如果此选项为`false`, 则图片在上传前不进行压缩。\n             *\n             * 默认为：\n             *\n             * ```javascript\n             * {\n             *     width: 1600,\n             *     height: 1600,\n             *\n             *     // 图片质量，只有type为`image/jpeg`的时候才有效。\n             *     quality: 90,\n             *\n             *     // 是否允许放大，如果想要生成小图的时候不失真，此选项应该设置为false.\n             *     allowMagnify: false,\n             *\n             *     // 是否允许裁剪。\n             *     crop: false,\n             *\n             *     // 是否保留头部meta信息。\n             *     preserveHeaders: true\n             * }\n             * ```\n             */\n            compress: {\n                width: 1600,\n                height: 1600,\n                quality: 90,\n                allowMagnify: false,\n                crop: false,\n                preserveHeaders: true\n            }\n        });\n    \n        return Uploader.register({\n            'make-thumb': 'makeThumb',\n            'before-send-file': 'compressImage'\n        }, {\n    \n    \n            /**\n             * 生成缩略图，此过程为异步，所以需要传入`callback`。\n             * 通常情况在图片加入队里后调用此方法来生成预览图以增强交互效果。\n             *\n             * `callback`中可以接收到两个参数。\n             * * 第一个为error，如果生成缩略图有错误，此error将为真。\n             * * 第二个为ret, 缩略图的Data URL值。\n             *\n             * **注意**\n             * Date URL在IE6/7中不支持，所以不用调用此方法了，直接显示一张暂不支持预览图片好了。\n             *\n             *\n             * @method makeThumb\n             * @grammar makeThumb( file, callback ) => undefined\n             * @grammar makeThumb( file, callback, width, height ) => undefined\n             * @for Uploader\n             * @example\n             *\n             * uploader.on( 'fileQueued', function( file ) {\n             *     var $li = ...;\n             *\n             *     uploader.makeThumb( file, function( error, ret ) {\n             *         if ( error ) {\n             *             $li.text('预览错误');\n             *         } else {\n             *             $li.append('<img alt=\"\" src=\"' + ret + '\" />');\n             *         }\n             *     });\n             *\n             * });\n             */\n            makeThumb: function( file, cb, width, height ) {\n                var opts, image;\n    \n                file = this.request( 'get-file', file );\n    \n                // 只预览图片格式。\n                if ( !file.type.match( /^image/ ) ) {\n                    cb( true );\n                    return;\n                }\n    \n                opts = $.extend({}, this.options.thumb );\n    \n                // 如果传入的是object.\n                if ( $.isPlainObject( width ) ) {\n                    opts = $.extend( opts, width );\n                    width = null;\n                }\n    \n                width = width || opts.width;\n                height = height || opts.height;\n    \n                image = new Image( opts );\n    \n                image.once( 'load', function() {\n                    file._info = file._info || image.info();\n                    file._meta = file._meta || image.meta();\n                    image.resize( width, height );\n                });\n    \n                image.once( 'complete', function() {\n                    cb( false, image.getAsDataUrl( opts.type ) );\n                    image.destroy();\n                });\n    \n                image.once( 'error', function() {\n                    cb( true );\n                    image.destroy();\n                });\n    \n                throttle( image, file.source.size, function() {\n                    file._info && image.info( file._info );\n                    file._meta && image.meta( file._meta );\n                    image.loadFromBlob( file.source );\n                });\n            },\n    \n            compressImage: function( file ) {\n                var opts = this.options.compress || this.options.resize,\n                    compressSize = opts && opts.compressSize || 300 * 1024,\n                    image, deferred;\n    \n                file = this.request( 'get-file', file );\n    \n                // 只预览图片格式。\n                if ( !opts || !~'image/jpeg,image/jpg'.indexOf( file.type ) ||\n                        file.size < compressSize ||\n                        file._compressed ) {\n                    return;\n                }\n    \n                opts = $.extend({}, opts );\n                deferred = Base.Deferred();\n    \n                image = new Image( opts );\n    \n                deferred.always(function() {\n                    image.destroy();\n                    image = null;\n                });\n                image.once( 'error', deferred.reject );\n                image.once( 'load', function() {\n                    file._info = file._info || image.info();\n                    file._meta = file._meta || image.meta();\n                    image.resize( opts.width, opts.height );\n                });\n    \n                image.once( 'complete', function() {\n                    var blob, size;\n    \n                    // 移动端 UC / qq 浏览器的无图模式下\n                    // ctx.getImageData 处理大图的时候会报 Exception\n                    // INDEX_SIZE_ERR: DOM Exception 1\n                    try {\n                        blob = image.getAsBlob( opts.type );\n    \n                        size = file.size;\n    \n                        // 如果压缩后，比原来还大则不用压缩后的。\n                        if ( blob.size < size ) {\n                            // file.source.destroy && file.source.destroy();\n                            file.source = blob;\n                            file.size = blob.size;\n    \n                            file.trigger( 'resize', blob.size, size );\n                        }\n    \n                        // 标记，避免重复压缩。\n                        file._compressed = true;\n                        deferred.resolve();\n                    } catch ( e ) {\n                        // 出错了直接继续，让其上传原始图片\n                        deferred.resolve();\n                    }\n                });\n    \n                file._info && image.info( file._info );\n                file._meta && image.meta( file._meta );\n    \n                image.loadFromBlob( file.source );\n                return deferred.promise();\n            }\n        });\n    });\n    /**\n     * @fileOverview 文件属性封装\n     */\n    define('file',[\n        'base',\n        'mediator'\n    ], function( Base, Mediator ) {\n    \n        var $ = Base.$,\n            idPrefix = 'WU_FILE_',\n            idSuffix = 0,\n            rExt = /\\.([^.]+)$/,\n            statusMap = {};\n    \n        function gid() {\n            return idPrefix + idSuffix++;\n        }\n    \n        /**\n         * 文件类\n         * @class File\n         * @constructor 构造函数\n         * @grammar new File( source ) => File\n         * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。\n         */\n        function WUFile( source ) {\n    \n            /**\n             * 文件名，包括扩展名（后缀）\n             * @property name\n             * @type {string}\n             */\n            this.name = source.name || 'Untitled';\n    \n            /**\n             * 文件体积（字节）\n             * @property size\n             * @type {uint}\n             * @default 0\n             */\n            this.size = source.size || 0;\n    \n            /**\n             * 文件MIMETYPE类型，与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny)\n             * @property type\n             * @type {string}\n             * @default 'application'\n             */\n            this.type = source.type || 'application';\n    \n            /**\n             * 文件最后修改日期\n             * @property lastModifiedDate\n             * @type {int}\n             * @default 当前时间戳\n             */\n            this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1);\n    \n            /**\n             * 文件ID，每个对象具有唯一ID，与文件名无关\n             * @property id\n             * @type {string}\n             */\n            this.id = gid();\n    \n            /**\n             * 文件扩展名，通过文件名获取，例如test.png的扩展名为png\n             * @property ext\n             * @type {string}\n             */\n            this.ext = rExt.exec( this.name ) ? RegExp.$1 : '';\n    \n    \n            /**\n             * 状态文字说明。在不同的status语境下有不同的用途。\n             * @property statusText\n             * @type {string}\n             */\n            this.statusText = '';\n    \n            // 存储文件状态，防止通过属性直接修改\n            statusMap[ this.id ] = WUFile.Status.INITED;\n    \n            this.source = source;\n            this.loaded = 0;\n    \n            this.on( 'error', function( msg ) {\n                this.setStatus( WUFile.Status.ERROR, msg );\n            });\n        }\n    \n        $.extend( WUFile.prototype, {\n    \n            /**\n             * 设置状态，状态变化时会触发`change`事件。\n             * @method setStatus\n             * @grammar setStatus( status[, statusText] );\n             * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status)\n             * @param {String} [statusText=''] 状态说明，常在error时使用，用http, abort,server等来标记是由于什么原因导致文件错误。\n             */\n            setStatus: function( status, text ) {\n    \n                var prevStatus = statusMap[ this.id ];\n    \n                typeof text !== 'undefined' && (this.statusText = text);\n    \n                if ( status !== prevStatus ) {\n                    statusMap[ this.id ] = status;\n                    /**\n                     * 文件状态变化\n                     * @event statuschange\n                     */\n                    this.trigger( 'statuschange', status, prevStatus );\n                }\n    \n            },\n    \n            /**\n             * 获取文件状态\n             * @return {File.Status}\n             * @example\n                     文件状态具体包括以下几种类型：\n                     {\n                         // 初始化\n                        INITED:     0,\n                        // 已入队列\n                        QUEUED:     1,\n                        // 正在上传\n                        PROGRESS:     2,\n                        // 上传出错\n                        ERROR:         3,\n                        // 上传成功\n                        COMPLETE:     4,\n                        // 上传取消\n                        CANCELLED:     5\n                    }\n             */\n            getStatus: function() {\n                return statusMap[ this.id ];\n            },\n    \n            /**\n             * 获取文件原始信息。\n             * @return {*}\n             */\n            getSource: function() {\n                return this.source;\n            },\n    \n            destory: function() {\n                delete statusMap[ this.id ];\n            }\n        });\n    \n        Mediator.installTo( WUFile.prototype );\n    \n        /**\n         * 文件状态值，具体包括以下几种类型：\n         * * `inited` 初始状态\n         * * `queued` 已经进入队列, 等待上传\n         * * `progress` 上传中\n         * * `complete` 上传完成。\n         * * `error` 上传出错，可重试\n         * * `interrupt` 上传中断，可续传。\n         * * `invalid` 文件不合格，不能重试上传。会自动从队列中移除。\n         * * `cancelled` 文件被移除。\n         * @property {Object} Status\n         * @namespace File\n         * @class File\n         * @static\n         */\n        WUFile.Status = {\n            INITED:     'inited',    // 初始状态\n            QUEUED:     'queued',    // 已经进入队列, 等待上传\n            PROGRESS:   'progress',    // 上传中\n            ERROR:      'error',    // 上传出错，可重试\n            COMPLETE:   'complete',    // 上传完成。\n            CANCELLED:  'cancelled',    // 上传取消。\n            INTERRUPT:  'interrupt',    // 上传中断，可续传。\n            INVALID:    'invalid'    // 文件不合格，不能重试上传。\n        };\n    \n        return WUFile;\n    });\n    \n    /**\n     * @fileOverview 文件队列\n     */\n    define('queue',[\n        'base',\n        'mediator',\n        'file'\n    ], function( Base, Mediator, WUFile ) {\n    \n        var $ = Base.$,\n            STATUS = WUFile.Status;\n    \n        /**\n         * 文件队列, 用来存储各个状态中的文件。\n         * @class Queue\n         * @extends Mediator\n         */\n        function Queue() {\n    \n            /**\n             * 统计文件数。\n             * * `numOfQueue` 队列中的文件数。\n             * * `numOfSuccess` 上传成功的文件数\n             * * `numOfCancel` 被移除的文件数\n             * * `numOfProgress` 正在上传中的文件数\n             * * `numOfUploadFailed` 上传错误的文件数。\n             * * `numOfInvalid` 无效的文件数。\n             * @property {Object} stats\n             */\n            this.stats = {\n                numOfQueue: 0,\n                numOfSuccess: 0,\n                numOfCancel: 0,\n                numOfProgress: 0,\n                numOfUploadFailed: 0,\n                numOfInvalid: 0\n            };\n    \n            // 上传队列，仅包括等待上传的文件\n            this._queue = [];\n    \n            // 存储所有文件\n            this._map = {};\n        }\n    \n        $.extend( Queue.prototype, {\n    \n            /**\n             * 将新文件加入对队列尾部\n             *\n             * @method append\n             * @param  {File} file   文件对象\n             */\n            append: function( file ) {\n                this._queue.push( file );\n                this._fileAdded( file );\n                return this;\n            },\n    \n            /**\n             * 将新文件加入对队列头部\n             *\n             * @method prepend\n             * @param  {File} file   文件对象\n             */\n            prepend: function( file ) {\n                this._queue.unshift( file );\n                this._fileAdded( file );\n                return this;\n            },\n    \n            /**\n             * 获取文件对象\n             *\n             * @method getFile\n             * @param  {String} fileId   文件ID\n             * @return {File}\n             */\n            getFile: function( fileId ) {\n                if ( typeof fileId !== 'string' ) {\n                    return fileId;\n                }\n                return this._map[ fileId ];\n            },\n    \n            /**\n             * 从队列中取出一个指定状态的文件。\n             * @grammar fetch( status ) => File\n             * @method fetch\n             * @param {String} status [文件状态值](#WebUploader:File:File.Status)\n             * @return {File} [File](#WebUploader:File)\n             */\n            fetch: function( status ) {\n                var len = this._queue.length,\n                    i, file;\n    \n                status = status || STATUS.QUEUED;\n    \n                for ( i = 0; i < len; i++ ) {\n                    file = this._queue[ i ];\n    \n                    if ( status === file.getStatus() ) {\n                        return file;\n                    }\n                }\n    \n                return null;\n            },\n    \n            /**\n             * 对队列进行排序，能够控制文件上传顺序。\n             * @grammar sort( fn ) => undefined\n             * @method sort\n             * @param {Function} fn 排序方法\n             */\n            sort: function( fn ) {\n                if ( typeof fn === 'function' ) {\n                    this._queue.sort( fn );\n                }\n            },\n    \n            /**\n             * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。\n             * @grammar getFiles( [status1[, status2 ...]] ) => Array\n             * @method getFiles\n             * @param {String} [status] [文件状态值](#WebUploader:File:File.Status)\n             */\n            getFiles: function() {\n                var sts = [].slice.call( arguments, 0 ),\n                    ret = [],\n                    i = 0,\n                    len = this._queue.length,\n                    file;\n    \n                for ( ; i < len; i++ ) {\n                    file = this._queue[ i ];\n    \n                    if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) {\n                        continue;\n                    }\n    \n                    ret.push( file );\n                }\n    \n                return ret;\n            },\n    \n            _fileAdded: function( file ) {\n                var me = this,\n                    existing = this._map[ file.id ];\n    \n                if ( !existing ) {\n                    this._map[ file.id ] = file;\n    \n                    file.on( 'statuschange', function( cur, pre ) {\n                        me._onFileStatusChange( cur, pre );\n                    });\n                }\n    \n                file.setStatus( STATUS.QUEUED );\n            },\n    \n            _onFileStatusChange: function( curStatus, preStatus ) {\n                var stats = this.stats;\n    \n                switch ( preStatus ) {\n                    case STATUS.PROGRESS:\n                        stats.numOfProgress--;\n                        break;\n    \n                    case STATUS.QUEUED:\n                        stats.numOfQueue --;\n                        break;\n    \n                    case STATUS.ERROR:\n                        stats.numOfUploadFailed--;\n                        break;\n    \n                    case STATUS.INVALID:\n                        stats.numOfInvalid--;\n                        break;\n                }\n    \n                switch ( curStatus ) {\n                    case STATUS.QUEUED:\n                        stats.numOfQueue++;\n                        break;\n    \n                    case STATUS.PROGRESS:\n                        stats.numOfProgress++;\n                        break;\n    \n                    case STATUS.ERROR:\n                        stats.numOfUploadFailed++;\n                        break;\n    \n                    case STATUS.COMPLETE:\n                        stats.numOfSuccess++;\n                        break;\n    \n                    case STATUS.CANCELLED:\n                        stats.numOfCancel++;\n                        break;\n    \n                    case STATUS.INVALID:\n                        stats.numOfInvalid++;\n                        break;\n                }\n            }\n    \n        });\n    \n        Mediator.installTo( Queue.prototype );\n    \n        return Queue;\n    });\n    /**\n     * @fileOverview 队列\n     */\n    define('widgets/queue',[\n        'base',\n        'uploader',\n        'queue',\n        'file',\n        'lib/file',\n        'runtime/client',\n        'widgets/widget'\n    ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) {\n    \n        var $ = Base.$,\n            rExt = /\\.\\w+$/,\n            Status = WUFile.Status;\n    \n        return Uploader.register({\n            'sort-files': 'sortFiles',\n            'add-file': 'addFiles',\n            'get-file': 'getFile',\n            'fetch-file': 'fetchFile',\n            'get-stats': 'getStats',\n            'get-files': 'getFiles',\n            'remove-file': 'removeFile',\n            'retry': 'retry',\n            'reset': 'reset',\n            'accept-file': 'acceptFile'\n        }, {\n    \n            init: function( opts ) {\n                var me = this,\n                    deferred, len, i, item, arr, accept, runtime;\n    \n                if ( $.isPlainObject( opts.accept ) ) {\n                    opts.accept = [ opts.accept ];\n                }\n    \n                // accept中的中生成匹配正则。\n                if ( opts.accept ) {\n                    arr = [];\n    \n                    for ( i = 0, len = opts.accept.length; i < len; i++ ) {\n                        item = opts.accept[ i ].extensions;\n                        item && arr.push( item );\n                    }\n    \n                    if ( arr.length ) {\n                        accept = '\\\\.' + arr.join(',')\n                                .replace( /,/g, '$|\\\\.' )\n                                .replace( /\\*/g, '.*' ) + '$';\n                    }\n    \n                    me.accept = new RegExp( accept, 'i' );\n                }\n    \n                me.queue = new Queue();\n                me.stats = me.queue.stats;\n    \n                // 如果当前不是html5运行时，那就算了。\n                // 不执行后续操作\n                if ( this.request('predict-runtime-type') !== 'html5' ) {\n                    return;\n                }\n    \n                // 创建一个 html5 运行时的 placeholder\n                // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。\n                deferred = Base.Deferred();\n                runtime = new RuntimeClient('Placeholder');\n                runtime.connectRuntime({\n                    runtimeOrder: 'html5'\n                }, function() {\n                    me._ruid = runtime.getRuid();\n                    deferred.resolve();\n                });\n                return deferred.promise();\n            },\n    \n    \n            // 为了支持外部直接添加一个原生File对象。\n            _wrapFile: function( file ) {\n                if ( !(file instanceof WUFile) ) {\n    \n                    if ( !(file instanceof File) ) {\n                        if ( !this._ruid ) {\n                            throw new Error('Can\\'t add external files.');\n                        }\n                        file = new File( this._ruid, file );\n                    }\n    \n                    file = new WUFile( file );\n                }\n    \n                return file;\n            },\n    \n            // 判断文件是否可以被加入队列\n            acceptFile: function( file ) {\n                var invalid = !file || file.size < 6 || this.accept &&\n    \n                        // 如果名字中有后缀，才做后缀白名单处理。\n                        rExt.exec( file.name ) && !this.accept.test( file.name );\n    \n                return !invalid;\n            },\n    \n    \n            /**\n             * @event beforeFileQueued\n             * @param {File} file File对象\n             * @description 当文件被加入队列之前触发，此事件的handler返回值为`false`，则此文件不会被添加进入队列。\n             * @for  Uploader\n             */\n    \n            /**\n             * @event fileQueued\n             * @param {File} file File对象\n             * @description 当文件被加入队列以后触发。\n             * @for  Uploader\n             */\n    \n            _addFile: function( file ) {\n                var me = this;\n    \n                file = me._wrapFile( file );\n    \n                // 不过类型判断允许不允许，先派送 `beforeFileQueued`\n                if ( !me.owner.trigger( 'beforeFileQueued', file ) ) {\n                    return;\n                }\n    \n                // 类型不匹配，则派送错误事件，并返回。\n                if ( !me.acceptFile( file ) ) {\n                    me.owner.trigger( 'error', 'Q_TYPE_DENIED', file );\n                    return;\n                }\n    \n                me.queue.append( file );\n                me.owner.trigger( 'fileQueued', file );\n                return file;\n            },\n    \n            getFile: function( fileId ) {\n                return this.queue.getFile( fileId );\n            },\n    \n            /**\n             * @event filesQueued\n             * @param {File} files 数组，内容为原始File(lib/File）对象。\n             * @description 当一批文件添加进队列以后触发。\n             * @for  Uploader\n             */\n    \n            /**\n             * @method addFiles\n             * @grammar addFiles( file ) => undefined\n             * @grammar addFiles( [file1, file2 ...] ) => undefined\n             * @param {Array of File or File} [files] Files 对象 数组\n             * @description 添加文件到队列\n             * @for  Uploader\n             */\n            addFiles: function( files ) {\n                var me = this;\n    \n                if ( !files.length ) {\n                    files = [ files ];\n                }\n    \n                files = $.map( files, function( file ) {\n                    return me._addFile( file );\n                });\n    \n                me.owner.trigger( 'filesQueued', files );\n    \n                if ( me.options.auto ) {\n                    me.request('start-upload');\n                }\n            },\n    \n            getStats: function() {\n                return this.stats;\n            },\n    \n            /**\n             * @event fileDequeued\n             * @param {File} file File对象\n             * @description 当文件被移除队列后触发。\n             * @for  Uploader\n             */\n    \n            /**\n             * @method removeFile\n             * @grammar removeFile( file ) => undefined\n             * @grammar removeFile( id ) => undefined\n             * @param {File|id} file File对象或这File对象的id\n             * @description 移除某一文件。\n             * @for  Uploader\n             * @example\n             *\n             * $li.on('click', '.remove-this', function() {\n             *     uploader.removeFile( file );\n             * })\n             */\n            removeFile: function( file ) {\n                var me = this;\n    \n                file = file.id ? file : me.queue.getFile( file );\n    \n                file.setStatus( Status.CANCELLED );\n                me.owner.trigger( 'fileDequeued', file );\n            },\n    \n            /**\n             * @method getFiles\n             * @grammar getFiles() => Array\n             * @grammar getFiles( status1, status2, status... ) => Array\n             * @description 返回指定状态的文件集合，不传参数将返回所有状态的文件。\n             * @for  Uploader\n             * @example\n             * console.log( uploader.getFiles() );    // => all files\n             * console.log( uploader.getFiles('error') )    // => all error files.\n             */\n            getFiles: function() {\n                return this.queue.getFiles.apply( this.queue, arguments );\n            },\n    \n            fetchFile: function() {\n                return this.queue.fetch.apply( this.queue, arguments );\n            },\n    \n            /**\n             * @method retry\n             * @grammar retry() => undefined\n             * @grammar retry( file ) => undefined\n             * @description 重试上传，重试指定文件，或者从出错的文件开始重新上传。\n             * @for  Uploader\n             * @example\n             * function retry() {\n             *     uploader.retry();\n             * }\n             */\n            retry: function( file, noForceStart ) {\n                var me = this,\n                    files, i, len;\n    \n                if ( file ) {\n                    file = file.id ? file : me.queue.getFile( file );\n                    file.setStatus( Status.QUEUED );\n                    noForceStart || me.request('start-upload');\n                    return;\n                }\n    \n                files = me.queue.getFiles( Status.ERROR );\n                i = 0;\n                len = files.length;\n    \n                for ( ; i < len; i++ ) {\n                    file = files[ i ];\n                    file.setStatus( Status.QUEUED );\n                }\n    \n                me.request('start-upload');\n            },\n    \n            /**\n             * @method sort\n             * @grammar sort( fn ) => undefined\n             * @description 排序队列中的文件，在上传之前调整可以控制上传顺序。\n             * @for  Uploader\n             */\n            sortFiles: function() {\n                return this.queue.sort.apply( this.queue, arguments );\n            },\n    \n            /**\n             * @method reset\n             * @grammar reset() => undefined\n             * @description 重置uploader。目前只重置了队列。\n             * @for  Uploader\n             * @example\n             * uploader.reset();\n             */\n            reset: function() {\n                this.queue = new Queue();\n                this.stats = this.queue.stats;\n            }\n        });\n    \n    });\n    /**\n     * @fileOverview 添加获取Runtime相关信息的方法。\n     */\n    define('widgets/runtime',[\n        'uploader',\n        'runtime/runtime',\n        'widgets/widget'\n    ], function( Uploader, Runtime ) {\n    \n        Uploader.support = function() {\n            return Runtime.hasRuntime.apply( Runtime, arguments );\n        };\n    \n        return Uploader.register({\n            'predict-runtime-type': 'predictRuntmeType'\n        }, {\n    \n            init: function() {\n                if ( !this.predictRuntmeType() ) {\n                    throw Error('Runtime Error');\n                }\n            },\n    \n            /**\n             * 预测Uploader将采用哪个`Runtime`\n             * @grammar predictRuntmeType() => String\n             * @method predictRuntmeType\n             * @for  Uploader\n             */\n            predictRuntmeType: function() {\n                var orders = this.options.runtimeOrder || Runtime.orders,\n                    type = this.type,\n                    i, len;\n    \n                if ( !type ) {\n                    orders = orders.split( /\\s*,\\s*/g );\n    \n                    for ( i = 0, len = orders.length; i < len; i++ ) {\n                        if ( Runtime.hasRuntime( orders[ i ] ) ) {\n                            this.type = type = orders[ i ];\n                            break;\n                        }\n                    }\n                }\n    \n                return type;\n            }\n        });\n    });\n    /**\n     * @fileOverview Transport\n     */\n    define('lib/transport',[\n        'base',\n        'runtime/client',\n        'mediator'\n    ], function( Base, RuntimeClient, Mediator ) {\n    \n        var $ = Base.$;\n    \n        function Transport( opts ) {\n            var me = this;\n    \n            opts = me.options = $.extend( true, {}, Transport.options, opts || {} );\n            RuntimeClient.call( this, 'Transport' );\n    \n            this._blob = null;\n            this._formData = opts.formData || {};\n            this._headers = opts.headers || {};\n    \n            this.on( 'progress', this._timeout );\n            this.on( 'load error', function() {\n                me.trigger( 'progress', 1 );\n                clearTimeout( me._timer );\n            });\n        }\n    \n        Transport.options = {\n            server: '',\n            method: 'POST',\n    \n            // 跨域时，是否允许携带cookie, 只有html5 runtime才有效\n            withCredentials: false,\n            fileVal: 'file',\n            timeout: 2 * 60 * 1000,    // 2分钟\n            formData: {},\n            headers: {},\n            sendAsBinary: false\n        };\n    \n        $.extend( Transport.prototype, {\n    \n            // 添加Blob, 只能添加一次，最后一次有效。\n            appendBlob: function( key, blob, filename ) {\n                var me = this,\n                    opts = me.options;\n    \n                if ( me.getRuid() ) {\n                    me.disconnectRuntime();\n                }\n    \n                // 连接到blob归属的同一个runtime.\n                me.connectRuntime( blob.ruid, function() {\n                    me.exec('init');\n                });\n    \n                me._blob = blob;\n                opts.fileVal = key || opts.fileVal;\n                opts.filename = filename || opts.filename;\n            },\n    \n            // 添加其他字段\n            append: function( key, value ) {\n                if ( typeof key === 'object' ) {\n                    $.extend( this._formData, key );\n                } else {\n                    this._formData[ key ] = value;\n                }\n            },\n    \n            setRequestHeader: function( key, value ) {\n                if ( typeof key === 'object' ) {\n                    $.extend( this._headers, key );\n                } else {\n                    this._headers[ key ] = value;\n                }\n            },\n    \n            send: function( method ) {\n                this.exec( 'send', method );\n                this._timeout();\n            },\n    \n            abort: function() {\n                clearTimeout( this._timer );\n                return this.exec('abort');\n            },\n    \n            destroy: function() {\n                this.trigger('destroy');\n                this.off();\n                this.exec('destroy');\n                this.disconnectRuntime();\n            },\n    \n            getResponse: function() {\n                return this.exec('getResponse');\n            },\n    \n            getResponseAsJson: function() {\n                return this.exec('getResponseAsJson');\n            },\n    \n            getStatus: function() {\n                return this.exec('getStatus');\n            },\n    \n            _timeout: function() {\n                var me = this,\n                    duration = me.options.timeout;\n    \n                if ( !duration ) {\n                    return;\n                }\n    \n                clearTimeout( me._timer );\n                me._timer = setTimeout(function() {\n                    me.abort();\n                    me.trigger( 'error', 'timeout' );\n                }, duration );\n            }\n    \n        });\n    \n        // 让Transport具备事件功能。\n        Mediator.installTo( Transport.prototype );\n    \n        return Transport;\n    });\n    /**\n     * @fileOverview 负责文件上传相关。\n     */\n    define('widgets/upload',[\n        'base',\n        'uploader',\n        'file',\n        'lib/transport',\n        'widgets/widget'\n    ], function( Base, Uploader, WUFile, Transport ) {\n    \n        var $ = Base.$,\n            isPromise = Base.isPromise,\n            Status = WUFile.Status;\n    \n        // 添加默认配置项\n        $.extend( Uploader.options, {\n    \n    \n            /**\n             * @property {Boolean} [prepareNextFile=false]\n             * @namespace options\n             * @for Uploader\n             * @description 是否允许在文件传输时提前把下一个文件准备好。\n             * 对于一个文件的准备工作比较耗时，比如图片压缩，md5序列化。\n             * 如果能提前在当前文件传输期处理，可以节省总体耗时。\n             */\n            prepareNextFile: false,\n    \n            /**\n             * @property {Boolean} [chunked=false]\n             * @namespace options\n             * @for Uploader\n             * @description 是否要分片处理大文件上传。\n             */\n            chunked: false,\n    \n            /**\n             * @property {Boolean} [chunkSize=5242880]\n             * @namespace options\n             * @for Uploader\n             * @description 如果要分片，分多大一片？ 默认大小为5M.\n             */\n            chunkSize: 5 * 1024 * 1024,\n    \n            /**\n             * @property {Boolean} [chunkRetry=2]\n             * @namespace options\n             * @for Uploader\n             * @description 如果某个分片由于网络问题出错，允许自动重传多少次？\n             */\n            chunkRetry: 2,\n    \n            /**\n             * @property {Boolean} [threads=3]\n             * @namespace options\n             * @for Uploader\n             * @description 上传并发数。允许同时最大上传进程数。\n             */\n            threads: 3,\n    \n    \n            /**\n             * @property {Object} [formData]\n             * @namespace options\n             * @for Uploader\n             * @description 文件上传请求的参数表，每次发送都会发送此对象中的参数。\n             */\n            formData: null\n    \n            /**\n             * @property {Object} [fileVal='file']\n             * @namespace options\n             * @for Uploader\n             * @description 设置文件上传域的name。\n             */\n    \n            /**\n             * @property {Object} [method='POST']\n             * @namespace options\n             * @for Uploader\n             * @description 文件上传方式，`POST`或者`GET`。\n             */\n    \n            /**\n             * @property {Object} [sendAsBinary=false]\n             * @namespace options\n             * @for Uploader\n             * @description 是否已二进制的流的方式发送文件，这样整个上传内容`php://input`都为文件内容，\n             * 其他参数在$_GET数组中。\n             */\n        });\n    \n        // 负责将文件切片。\n        function CuteFile( file, chunkSize ) {\n            var pending = [],\n                blob = file.source,\n                total = blob.size,\n                chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1,\n                start = 0,\n                index = 0,\n                len;\n    \n            while ( index < chunks ) {\n                len = Math.min( chunkSize, total - start );\n    \n                pending.push({\n                    file: file,\n                    start: start,\n                    end: chunkSize ? (start + len) : total,\n                    total: total,\n                    chunks: chunks,\n                    chunk: index++\n                });\n                start += len;\n            }\n    \n            file.blocks = pending.concat();\n            file.remaning = pending.length;\n    \n            return {\n                file: file,\n    \n                has: function() {\n                    return !!pending.length;\n                },\n    \n                fetch: function() {\n                    return pending.shift();\n                }\n            };\n        }\n    \n        Uploader.register({\n            'start-upload': 'start',\n            'stop-upload': 'stop',\n            'skip-file': 'skipFile',\n            'is-in-progress': 'isInProgress'\n        }, {\n    \n            init: function() {\n                var owner = this.owner;\n    \n                this.runing = false;\n    \n                // 记录当前正在传的数据，跟threads相关\n                this.pool = [];\n    \n                // 缓存即将上传的文件。\n                this.pending = [];\n    \n                // 跟踪还有多少分片没有完成上传。\n                this.remaning = 0;\n                this.__tick = Base.bindFn( this._tick, this );\n    \n                owner.on( 'uploadComplete', function( file ) {\n                    // 把其他块取消了。\n                    file.blocks && $.each( file.blocks, function( _, v ) {\n                        v.transport && (v.transport.abort(), v.transport.destroy());\n                        delete v.transport;\n                    });\n    \n                    delete file.blocks;\n                    delete file.remaning;\n                });\n            },\n    \n            /**\n             * @event startUpload\n             * @description 当开始上传流程时触发。\n             * @for  Uploader\n             */\n    \n            /**\n             * 开始上传。此方法可以从初始状态调用开始上传流程，也可以从暂停状态调用，继续上传流程。\n             * @grammar upload() => undefined\n             * @method upload\n             * @for  Uploader\n             */\n            start: function() {\n                var me = this;\n    \n                // 移出invalid的文件\n                $.each( me.request( 'get-files', Status.INVALID ), function() {\n                    me.request( 'remove-file', this );\n                });\n    \n                if ( me.runing ) {\n                    return;\n                }\n    \n                me.runing = true;\n    \n                // 如果有暂停的，则续传\n                $.each( me.pool, function( _, v ) {\n                    var file = v.file;\n    \n                    if ( file.getStatus() === Status.INTERRUPT ) {\n                        file.setStatus( Status.PROGRESS );\n                        me._trigged = false;\n                        v.transport && v.transport.send();\n                    }\n                });\n    \n                me._trigged = false;\n                me.owner.trigger('startUpload');\n                Base.nextTick( me.__tick );\n            },\n    \n            /**\n             * @event stopUpload\n             * @description 当开始上传流程暂停时触发。\n             * @for  Uploader\n             */\n    \n            /**\n             * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。\n             * @grammar stop() => undefined\n             * @grammar stop( true ) => undefined\n             * @method stop\n             * @for  Uploader\n             */\n            stop: function( interrupt ) {\n                var me = this;\n    \n                if ( me.runing === false ) {\n                    return;\n                }\n    \n                me.runing = false;\n    \n                interrupt && $.each( me.pool, function( _, v ) {\n                    v.transport && v.transport.abort();\n                    v.file.setStatus( Status.INTERRUPT );\n                });\n    \n                me.owner.trigger('stopUpload');\n            },\n    \n            /**\n             * 判断`Uplaode`r是否正在上传中。\n             * @grammar isInProgress() => Boolean\n             * @method isInProgress\n             * @for  Uploader\n             */\n            isInProgress: function() {\n                return !!this.runing;\n            },\n    \n            getStats: function() {\n                return this.request('get-stats');\n            },\n    \n            /**\n             * 掉过一个文件上传，直接标记指定文件为已上传状态。\n             * @grammar skipFile( file ) => undefined\n             * @method skipFile\n             * @for  Uploader\n             */\n            skipFile: function( file, status ) {\n                file = this.request( 'get-file', file );\n    \n                file.setStatus( status || Status.COMPLETE );\n                file.skipped = true;\n    \n                // 如果正在上传。\n                file.blocks && $.each( file.blocks, function( _, v ) {\n                    var _tr = v.transport;\n    \n                    if ( _tr ) {\n                        _tr.abort();\n                        _tr.destroy();\n                        delete v.transport;\n                    }\n                });\n    \n                this.owner.trigger( 'uploadSkip', file );\n            },\n    \n            /**\n             * @event uploadFinished\n             * @description 当所有文件上传结束时触发。\n             * @for  Uploader\n             */\n            _tick: function() {\n                var me = this,\n                    opts = me.options,\n                    fn, val;\n    \n                // 上一个promise还没有结束，则等待完成后再执行。\n                if ( me._promise ) {\n                    return me._promise.always( me.__tick );\n                }\n    \n                // 还有位置，且还有文件要处理的话。\n                if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) {\n                    me._trigged = false;\n    \n                    fn = function( val ) {\n                        me._promise = null;\n    \n                        // 有可能是reject过来的，所以要检测val的类型。\n                        val && val.file && me._startSend( val );\n                        Base.nextTick( me.__tick );\n                    };\n    \n                    me._promise = isPromise( val ) ? val.always( fn ) : fn( val );\n    \n                // 没有要上传的了，且没有正在传输的了。\n                } else if ( !me.remaning && !me.getStats().numOfQueue ) {\n                    me.runing = false;\n    \n                    me._trigged || Base.nextTick(function() {\n                        me.owner.trigger('uploadFinished');\n                    });\n                    me._trigged = true;\n                }\n            },\n    \n            _nextBlock: function() {\n                var me = this,\n                    act = me._act,\n                    opts = me.options,\n                    next, done;\n    \n                // 如果当前文件还有没有需要传输的，则直接返回剩下的。\n                if ( act && act.has() &&\n                        act.file.getStatus() === Status.PROGRESS ) {\n    \n                    // 是否提前准备下一个文件\n                    if ( opts.prepareNextFile && !me.pending.length ) {\n                        me._prepareNextFile();\n                    }\n    \n                    return act.fetch();\n    \n                // 否则，如果正在运行，则准备下一个文件，并等待完成后返回下个分片。\n                } else if ( me.runing ) {\n    \n                    // 如果缓存中有，则直接在缓存中取，没有则去queue中取。\n                    if ( !me.pending.length && me.getStats().numOfQueue ) {\n                        me._prepareNextFile();\n                    }\n    \n                    next = me.pending.shift();\n                    done = function( file ) {\n                        if ( !file ) {\n                            return null;\n                        }\n    \n                        act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 );\n                        me._act = act;\n                        return act.fetch();\n                    };\n    \n                    // 文件可能还在prepare中，也有可能已经完全准备好了。\n                    return isPromise( next ) ?\n                            next[ next.pipe ? 'pipe' : 'then']( done ) :\n                            done( next );\n                }\n            },\n    \n    \n            /**\n             * @event uploadStart\n             * @param {File} file File对象\n             * @description 某个文件开始上传前触发，一个文件只会触发一次。\n             * @for  Uploader\n             */\n            _prepareNextFile: function() {\n                var me = this,\n                    file = me.request('fetch-file'),\n                    pending = me.pending,\n                    promise;\n    \n                if ( file ) {\n                    promise = me.request( 'before-send-file', file, function() {\n    \n                        // 有可能文件被skip掉了。文件被skip掉后，状态坑定不是Queued.\n                        if ( file.getStatus() === Status.QUEUED ) {\n                            me.owner.trigger( 'uploadStart', file );\n                            file.setStatus( Status.PROGRESS );\n                            return file;\n                        }\n    \n                        return me._finishFile( file );\n                    });\n    \n                    // 如果还在pending中，则替换成文件本身。\n                    promise.done(function() {\n                        var idx = $.inArray( promise, pending );\n    \n                        ~idx && pending.splice( idx, 1, file );\n                    });\n    \n                    // befeore-send-file的钩子就有错误发生。\n                    promise.fail(function( reason ) {\n                        file.setStatus( Status.ERROR, reason );\n                        me.owner.trigger( 'uploadError', file, reason );\n                        me.owner.trigger( 'uploadComplete', file );\n                    });\n    \n                    pending.push( promise );\n                }\n            },\n    \n            // 让出位置了，可以让其他分片开始上传\n            _popBlock: function( block ) {\n                var idx = $.inArray( block, this.pool );\n    \n                this.pool.splice( idx, 1 );\n                block.file.remaning--;\n                this.remaning--;\n            },\n    \n            // 开始上传，可以被掉过。如果promise被reject了，则表示跳过此分片。\n            _startSend: function( block ) {\n                var me = this,\n                    file = block.file,\n                    promise;\n    \n                me.pool.push( block );\n                me.remaning++;\n    \n                // 如果没有分片，则直接使用原始的。\n                // 不会丢失content-type信息。\n                block.blob = block.chunks === 1 ? file.source :\n                        file.source.slice( block.start, block.end );\n    \n                // hook, 每个分片发送之前可能要做些异步的事情。\n                promise = me.request( 'before-send', block, function() {\n    \n                    // 有可能文件已经上传出错了，所以不需要再传输了。\n                    if ( file.getStatus() === Status.PROGRESS ) {\n                        me._doSend( block );\n                    } else {\n                        me._popBlock( block );\n                        Base.nextTick( me.__tick );\n                    }\n                });\n    \n                // 如果为fail了，则跳过此分片。\n                promise.fail(function() {\n                    if ( file.remaning === 1 ) {\n                        me._finishFile( file ).always(function() {\n                            block.percentage = 1;\n                            me._popBlock( block );\n                            me.owner.trigger( 'uploadComplete', file );\n                            Base.nextTick( me.__tick );\n                        });\n                    } else {\n                        block.percentage = 1;\n                        me._popBlock( block );\n                        Base.nextTick( me.__tick );\n                    }\n                });\n            },\n    \n    \n            /**\n             * @event uploadBeforeSend\n             * @param {Object} object\n             * @param {Object} data 默认的上传参数，可以扩展此对象来控制上传参数。\n             * @description 当某个文件的分块在发送前触发，主要用来询问是否要添加附带参数，大文件在开起分片上传的前提下此事件可能会触发多次。\n             * @for  Uploader\n             */\n    \n            /**\n             * @event uploadAccept\n             * @param {Object} object\n             * @param {Object} ret 服务端的返回数据，json格式，如果服务端不是json格式，从ret._raw中取数据，自行解析。\n             * @description 当某个文件上传到服务端响应后，会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。\n             * @for  Uploader\n             */\n    \n            /**\n             * @event uploadProgress\n             * @param {File} file File对象\n             * @param {Number} percentage 上传进度\n             * @description 上传过程中触发，携带上传进度。\n             * @for  Uploader\n             */\n    \n    \n            /**\n             * @event uploadError\n             * @param {File} file File对象\n             * @param {String} reason 出错的code\n             * @description 当文件上传出错时触发。\n             * @for  Uploader\n             */\n    \n            /**\n             * @event uploadSuccess\n             * @param {File} file File对象\n             * @param {Object} response 服务端返回的数据\n             * @description 当文件上传成功时触发。\n             * @for  Uploader\n             */\n    \n            /**\n             * @event uploadComplete\n             * @param {File} [file] File对象\n             * @description 不管成功或者失败，文件上传完成时触发。\n             * @for  Uploader\n             */\n    \n            // 做上传操作。\n            _doSend: function( block ) {\n                var me = this,\n                    owner = me.owner,\n                    opts = me.options,\n                    file = block.file,\n                    tr = new Transport( opts ),\n                    data = $.extend({}, opts.formData ),\n                    headers = $.extend({}, opts.headers ),\n                    requestAccept, ret;\n    \n                block.transport = tr;\n    \n                tr.on( 'destroy', function() {\n                    delete block.transport;\n                    me._popBlock( block );\n                    Base.nextTick( me.__tick );\n                });\n    \n                // 广播上传进度。以文件为单位。\n                tr.on( 'progress', function( percentage ) {\n                    var totalPercent = 0,\n                        uploaded = 0;\n    \n                    // 可能没有abort掉，progress还是执行进来了。\n                    // if ( !file.blocks ) {\n                    //     return;\n                    // }\n    \n                    totalPercent = block.percentage = percentage;\n    \n                    if ( block.chunks > 1 ) {    // 计算文件的整体速度。\n                        $.each( file.blocks, function( _, v ) {\n                            uploaded += (v.percentage || 0) * (v.end - v.start);\n                        });\n    \n                        totalPercent = uploaded / file.size;\n                    }\n    \n                    owner.trigger( 'uploadProgress', file, totalPercent || 0 );\n                });\n    \n                // 用来询问，是否返回的结果是有错误的。\n                requestAccept = function( reject ) {\n                    var fn;\n    \n                    ret = tr.getResponseAsJson() || {};\n                    ret._raw = tr.getResponse();\n                    fn = function( value ) {\n                        reject = value;\n                    };\n    \n                    // 服务端响应了，不代表成功了，询问是否响应正确。\n                    if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) {\n                        reject = reject || 'server';\n                    }\n    \n                    return reject;\n                };\n    \n                // 尝试重试，然后广播文件上传出错。\n                tr.on( 'error', function( type, flag ) {\n                    block.retried = block.retried || 0;\n    \n                    // 自动重试\n                    if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) &&\n                            block.retried < opts.chunkRetry ) {\n    \n                        block.retried++;\n                        tr.send();\n    \n                    } else {\n    \n                        // http status 500 ~ 600\n                        if ( !flag && type === 'server' ) {\n                            type = requestAccept( type );\n                        }\n    \n                        file.setStatus( Status.ERROR, type );\n                        owner.trigger( 'uploadError', file, type );\n                        owner.trigger( 'uploadComplete', file );\n                    }\n                });\n    \n                // 上传成功\n                tr.on( 'load', function() {\n                    var reason;\n    \n                    // 如果非预期，转向上传出错。\n                    if ( (reason = requestAccept()) ) {\n                        tr.trigger( 'error', reason, true );\n                        return;\n                    }\n    \n                    // 全部上传完成。\n                    if ( file.remaning === 1 ) {\n                        me._finishFile( file, ret );\n                    } else {\n                        tr.destroy();\n                    }\n                });\n    \n                // 配置默认的上传字段。\n                data = $.extend( data, {\n                    id: file.id,\n                    name: file.name,\n                    type: file.type,\n                    lastModifiedDate: file.lastModifiedDate,\n                    size: file.size\n                });\n    \n                block.chunks > 1 && $.extend( data, {\n                    chunks: block.chunks,\n                    chunk: block.chunk\n                });\n    \n                // 在发送之间可以添加字段什么的。。。\n                // 如果默认的字段不够使用，可以通过监听此事件来扩展\n                owner.trigger( 'uploadBeforeSend', block, data, headers );\n    \n                // 开始发送。\n                tr.appendBlob( opts.fileVal, block.blob, file.name );\n                tr.append( data );\n                tr.setRequestHeader( headers );\n                tr.send();\n            },\n    \n            // 完成上传。\n            _finishFile: function( file, ret, hds ) {\n                var owner = this.owner;\n    \n                return owner\n                        .request( 'after-send-file', arguments, function() {\n                            file.setStatus( Status.COMPLETE );\n                            owner.trigger( 'uploadSuccess', file, ret, hds );\n                        })\n                        .fail(function( reason ) {\n    \n                            // 如果外部已经标记为invalid什么的，不再改状态。\n                            if ( file.getStatus() === Status.PROGRESS ) {\n                                file.setStatus( Status.ERROR, reason );\n                            }\n    \n                            owner.trigger( 'uploadError', file, reason );\n                        })\n                        .always(function() {\n                            owner.trigger( 'uploadComplete', file );\n                        });\n            }\n    \n        });\n    });\n    /**\n     * @fileOverview Runtime管理器，负责Runtime的选择, 连接\n     */\n    define('runtime/compbase',[],function() {\n    \n        function CompBase( owner, runtime ) {\n    \n            this.owner = owner;\n            this.options = owner.options;\n    \n            this.getRuntime = function() {\n                return runtime;\n            };\n    \n            this.getRuid = function() {\n                return runtime.uid;\n            };\n    \n            this.trigger = function() {\n                return owner.trigger.apply( owner, arguments );\n            };\n        }\n    \n        return CompBase;\n    });\n    /**\n     * @fileOverview Html5Runtime\n     */\n    define('runtime/html5/runtime',[\n        'base',\n        'runtime/runtime',\n        'runtime/compbase'\n    ], function( Base, Runtime, CompBase ) {\n    \n        var type = 'html5',\n            components = {};\n    \n        function Html5Runtime() {\n            var pool = {},\n                me = this,\n                destory = this.destory;\n    \n            Runtime.apply( me, arguments );\n            me.type = type;\n    \n    \n            // 这个方法的调用者，实际上是RuntimeClient\n            me.exec = function( comp, fn/*, args...*/) {\n                var client = this,\n                    uid = client.uid,\n                    args = Base.slice( arguments, 2 ),\n                    instance;\n    \n                if ( components[ comp ] ) {\n                    instance = pool[ uid ] = pool[ uid ] ||\n                            new components[ comp ]( client, me );\n    \n                    if ( instance[ fn ] ) {\n                        return instance[ fn ].apply( instance, args );\n                    }\n                }\n            };\n    \n            me.destory = function() {\n                // @todo 删除池子中的所有实例\n                return destory && destory.apply( this, arguments );\n            };\n        }\n    \n        Base.inherits( Runtime, {\n            constructor: Html5Runtime,\n    \n            // 不需要连接其他程序，直接执行callback\n            init: function() {\n                var me = this;\n                setTimeout(function() {\n                    me.trigger('ready');\n                }, 1 );\n            }\n    \n        });\n    \n        // 注册Components\n        Html5Runtime.register = function( name, component ) {\n            var klass = components[ name ] = Base.inherits( CompBase, component );\n            return klass;\n        };\n    \n        // 注册html5运行时。\n        // 只有在支持的前提下注册。\n        if ( window.Blob && window.FileReader && window.DataView ) {\n            Runtime.addRuntime( type, Html5Runtime );\n        }\n    \n        return Html5Runtime;\n    });\n    /**\n     * @fileOverview Blob Html实现\n     */\n    define('runtime/html5/blob',[\n        'runtime/html5/runtime',\n        'lib/blob'\n    ], function( Html5Runtime, Blob ) {\n    \n        return Html5Runtime.register( 'Blob', {\n            slice: function( start, end ) {\n                var blob = this.owner.source,\n                    slice = blob.slice || blob.webkitSlice || blob.mozSlice;\n    \n                blob = slice.call( blob, start, end );\n    \n                return new Blob( this.getRuid(), blob );\n            }\n        });\n    });\n    /**\n     * @fileOverview FilePicker\n     */\n    define('runtime/html5/filepicker',[\n        'base',\n        'runtime/html5/runtime'\n    ], function( Base, Html5Runtime ) {\n    \n        var $ = Base.$;\n    \n        return Html5Runtime.register( 'FilePicker', {\n            init: function() {\n                var container = this.getRuntime().getContainer(),\n                    me = this,\n                    owner = me.owner,\n                    opts = me.options,\n                    lable = $( document.createElement('label') ),\n                    input = $( document.createElement('input') ),\n                    arr, i, len, mouseHandler;\n    \n                input.attr( 'type', 'file' );\n                input.attr( 'name', opts.name );\n                input.addClass('webuploader-element-invisible');\n    \n                lable.on( 'click', function() {\n                    input.trigger('click');\n                });\n    \n                lable.css({\n                    opacity: 0,\n                    width: '100%',\n                    height: '100%',\n                    display: 'block',\n                    cursor: 'pointer',\n                    background: '#ffffff'\n                });\n    \n                if ( opts.multiple ) {\n                    input.attr( 'multiple', 'multiple' );\n                }\n    \n                // @todo Firefox不支持单独指定后缀\n                if ( opts.accept && opts.accept.length > 0 ) {\n                    arr = [];\n    \n                    for ( i = 0, len = opts.accept.length; i < len; i++ ) {\n                        arr.push( opts.accept[ i ].mimeTypes );\n                    }\n    \n                    input.attr( 'accept', arr.join(',') );\n                }\n    \n                container.append( input );\n                container.append( lable );\n    \n                mouseHandler = function( e ) {\n                    owner.trigger( e.type );\n                };\n    \n                input.on( 'change', function( e ) {\n                    var fn = arguments.callee,\n                        clone;\n    \n                    me.files = e.target.files;\n    \n                    // reset input\n                    clone = this.cloneNode( true );\n                    this.parentNode.replaceChild( clone, this );\n    \n                    input.off();\n                    input = $( clone ).on( 'change', fn )\n                            .on( 'mouseenter mouseleave', mouseHandler );\n    \n                    owner.trigger('change');\n                });\n    \n                lable.on( 'mouseenter mouseleave', mouseHandler );\n    \n            },\n    \n    \n            getFiles: function() {\n                return this.files;\n            },\n    \n            destroy: function() {\n                // todo\n            }\n        });\n    });\n    /**\n     * Terms:\n     *\n     * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer\n     * @fileOverview Image控件\n     */\n    define('runtime/html5/util',[\n        'base'\n    ], function( Base ) {\n    \n        var urlAPI = window.createObjectURL && window ||\n                window.URL && URL.revokeObjectURL && URL ||\n                window.webkitURL,\n            createObjectURL = Base.noop,\n            revokeObjectURL = createObjectURL;\n    \n        if ( urlAPI ) {\n    \n            // 更安全的方式调用，比如android里面就能把context改成其他的对象。\n            createObjectURL = function() {\n                return urlAPI.createObjectURL.apply( urlAPI, arguments );\n            };\n    \n            revokeObjectURL = function() {\n                return urlAPI.revokeObjectURL.apply( urlAPI, arguments );\n            };\n        }\n    \n        return {\n            createObjectURL: createObjectURL,\n            revokeObjectURL: revokeObjectURL,\n    \n            dataURL2Blob: function( dataURI ) {\n                var byteStr, intArray, ab, i, mimetype, parts;\n    \n                parts = dataURI.split(',');\n    \n                if ( ~parts[ 0 ].indexOf('base64') ) {\n                    byteStr = atob( parts[ 1 ] );\n                } else {\n                    byteStr = decodeURIComponent( parts[ 1 ] );\n                }\n    \n                ab = new ArrayBuffer( byteStr.length );\n                intArray = new Uint8Array( ab );\n    \n                for ( i = 0; i < byteStr.length; i++ ) {\n                    intArray[ i ] = byteStr.charCodeAt( i );\n                }\n    \n                mimetype = parts[ 0 ].split(':')[ 1 ].split(';')[ 0 ];\n    \n                return this.arrayBufferToBlob( ab, mimetype );\n            },\n    \n            dataURL2ArrayBuffer: function( dataURI ) {\n                var byteStr, intArray, i, parts;\n    \n                parts = dataURI.split(',');\n    \n                if ( ~parts[ 0 ].indexOf('base64') ) {\n                    byteStr = atob( parts[ 1 ] );\n                } else {\n                    byteStr = decodeURIComponent( parts[ 1 ] );\n                }\n    \n                intArray = new Uint8Array( byteStr.length );\n    \n                for ( i = 0; i < byteStr.length; i++ ) {\n                    intArray[ i ] = byteStr.charCodeAt( i );\n                }\n    \n                return intArray.buffer;\n            },\n    \n            arrayBufferToBlob: function( buffer, type ) {\n                var builder = window.BlobBuilder || window.WebKitBlobBuilder,\n                    bb;\n    \n                // android不支持直接new Blob, 只能借助blobbuilder.\n                if ( builder ) {\n                    bb = new builder();\n                    bb.append( buffer );\n                    return bb.getBlob( type );\n                }\n    \n                return new Blob([ buffer ], type ? { type: type } : {} );\n            },\n    \n            // 抽出来主要是为了解决android下面canvas.toDataUrl不支持jpeg.\n            // 你得到的结果是png.\n            canvasToDataUrl: function( canvas, type, quality ) {\n                return canvas.toDataURL( type, quality / 100 );\n            },\n    \n            // imagemeat会复写这个方法，如果用户选择加载那个文件了的话。\n            parseMeta: function( blob, callback ) {\n                callback( false, {});\n            },\n    \n            // imagemeat会复写这个方法，如果用户选择加载那个文件了的话。\n            updateImageHead: function( data ) {\n                return data;\n            }\n        };\n    });\n    /**\n     * Terms:\n     *\n     * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer\n     * @fileOverview Image控件\n     */\n    define('runtime/html5/imagemeta',[\n        'runtime/html5/util'\n    ], function( Util ) {\n    \n        var api;\n    \n        api = {\n            parsers: {\n                0xffe1: []\n            },\n    \n            maxMetaDataSize: 262144,\n    \n            parse: function( blob, cb ) {\n                var me = this,\n                    fr = new FileReader();\n    \n                fr.onload = function() {\n                    cb( false, me._parse( this.result ) );\n                    fr = fr.onload = fr.onerror = null;\n                };\n    \n                fr.onerror = function( e ) {\n                    cb( e.message );\n                    fr = fr.onload = fr.onerror = null;\n                };\n    \n                blob = blob.slice( 0, me.maxMetaDataSize );\n                fr.readAsArrayBuffer( blob.getSource() );\n            },\n    \n            _parse: function( buffer, noParse ) {\n                if ( buffer.byteLength < 6 ) {\n                    return;\n                }\n    \n                var dataview = new DataView( buffer ),\n                    offset = 2,\n                    maxOffset = dataview.byteLength - 4,\n                    headLength = offset,\n                    ret = {},\n                    markerBytes, markerLength, parsers, i;\n    \n                if ( dataview.getUint16( 0 ) === 0xffd8 ) {\n    \n                    while ( offset < maxOffset ) {\n                        markerBytes = dataview.getUint16( offset );\n    \n                        if ( markerBytes >= 0xffe0 && markerBytes <= 0xffef ||\n                                markerBytes === 0xfffe ) {\n    \n                            markerLength = dataview.getUint16( offset + 2 ) + 2;\n    \n                            if ( offset + markerLength > dataview.byteLength ) {\n                                break;\n                            }\n    \n                            parsers = api.parsers[ markerBytes ];\n    \n                            if ( !noParse && parsers ) {\n                                for ( i = 0; i < parsers.length; i += 1 ) {\n                                    parsers[ i ].call( api, dataview, offset,\n                                            markerLength, ret );\n                                }\n                            }\n    \n                            offset += markerLength;\n                            headLength = offset;\n                        } else {\n                            break;\n                        }\n                    }\n    \n                    if ( headLength > 6 ) {\n                        if ( buffer.slice ) {\n                            ret.imageHead = buffer.slice( 2, headLength );\n                        } else {\n                            // Workaround for IE10, which does not yet\n                            // support ArrayBuffer.slice:\n                            ret.imageHead = new Uint8Array( buffer )\n                                    .subarray( 2, headLength );\n                        }\n                    }\n                }\n    \n                return ret;\n            },\n    \n            updateImageHead: function( buffer, head ) {\n                var data = this._parse( buffer, true ),\n                    buf1, buf2, bodyoffset;\n    \n    \n                bodyoffset = 2;\n                if ( data.imageHead ) {\n                    bodyoffset = 2 + data.imageHead.byteLength;\n                }\n    \n                if ( buffer.slice ) {\n                    buf2 = buffer.slice( bodyoffset );\n                } else {\n                    buf2 = new Uint8Array( buffer ).subarray( bodyoffset );\n                }\n    \n                buf1 = new Uint8Array( head.byteLength + 2 + buf2.byteLength );\n    \n                buf1[ 0 ] = 0xFF;\n                buf1[ 1 ] = 0xD8;\n                buf1.set( new Uint8Array( head ), 2 );\n                buf1.set( new Uint8Array( buf2 ), head.byteLength + 2 );\n    \n                return buf1.buffer;\n            }\n        };\n    \n        Util.parseMeta = function() {\n            return api.parse.apply( api, arguments );\n        };\n    \n        Util.updateImageHead = function() {\n            return api.updateImageHead.apply( api, arguments );\n        };\n    \n        return api;\n    });\n    /**\n     * 代码来自于：https://github.com/blueimp/JavaScript-Load-Image\n     * 暂时项目中只用了orientation.\n     *\n     * 去除了 Exif Sub IFD Pointer, GPS Info IFD Pointer, Exif Thumbnail.\n     * @fileOverview EXIF解析\n     */\n    \n    // Sample\n    // ====================================\n    // Make : Apple\n    // Model : iPhone 4S\n    // Orientation : 1\n    // XResolution : 72 [72/1]\n    // YResolution : 72 [72/1]\n    // ResolutionUnit : 2\n    // Software : QuickTime 7.7.1\n    // DateTime : 2013:09:01 22:53:55\n    // ExifIFDPointer : 190\n    // ExposureTime : 0.058823529411764705 [1/17]\n    // FNumber : 2.4 [12/5]\n    // ExposureProgram : Normal program\n    // ISOSpeedRatings : 800\n    // ExifVersion : 0220\n    // DateTimeOriginal : 2013:09:01 22:52:51\n    // DateTimeDigitized : 2013:09:01 22:52:51\n    // ComponentsConfiguration : YCbCr\n    // ShutterSpeedValue : 4.058893515764426\n    // ApertureValue : 2.5260688216892597 [4845/1918]\n    // BrightnessValue : -0.3126686601998395\n    // MeteringMode : Pattern\n    // Flash : Flash did not fire, compulsory flash mode\n    // FocalLength : 4.28 [107/25]\n    // SubjectArea : [4 values]\n    // FlashpixVersion : 0100\n    // ColorSpace : 1\n    // PixelXDimension : 2448\n    // PixelYDimension : 3264\n    // SensingMethod : One-chip color area sensor\n    // ExposureMode : 0\n    // WhiteBalance : Auto white balance\n    // FocalLengthIn35mmFilm : 35\n    // SceneCaptureType : Standard\n    define('runtime/html5/imagemeta/exif',[\n        'base',\n        'runtime/html5/imagemeta'\n    ], function( Base, ImageMeta ) {\n    \n        var EXIF = {};\n    \n        EXIF.ExifMap = function() {\n            return this;\n        };\n    \n        EXIF.ExifMap.prototype.map = {\n            'Orientation': 0x0112\n        };\n    \n        EXIF.ExifMap.prototype.get = function( id ) {\n            return this[ id ] || this[ this.map[ id ] ];\n        };\n    \n        EXIF.exifTagTypes = {\n            // byte, 8-bit unsigned int:\n            1: {\n                getValue: function( dataView, dataOffset ) {\n                    return dataView.getUint8( dataOffset );\n                },\n                size: 1\n            },\n    \n            // ascii, 8-bit byte:\n            2: {\n                getValue: function( dataView, dataOffset ) {\n                    return String.fromCharCode( dataView.getUint8( dataOffset ) );\n                },\n                size: 1,\n                ascii: true\n            },\n    \n            // short, 16 bit int:\n            3: {\n                getValue: function( dataView, dataOffset, littleEndian ) {\n                    return dataView.getUint16( dataOffset, littleEndian );\n                },\n                size: 2\n            },\n    \n            // long, 32 bit int:\n            4: {\n                getValue: function( dataView, dataOffset, littleEndian ) {\n                    return dataView.getUint32( dataOffset, littleEndian );\n                },\n                size: 4\n            },\n    \n            // rational = two long values,\n            // first is numerator, second is denominator:\n            5: {\n                getValue: function( dataView, dataOffset, littleEndian ) {\n                    return dataView.getUint32( dataOffset, littleEndian ) /\n                        dataView.getUint32( dataOffset + 4, littleEndian );\n                },\n                size: 8\n            },\n    \n            // slong, 32 bit signed int:\n            9: {\n                getValue: function( dataView, dataOffset, littleEndian ) {\n                    return dataView.getInt32( dataOffset, littleEndian );\n                },\n                size: 4\n            },\n    \n            // srational, two slongs, first is numerator, second is denominator:\n            10: {\n                getValue: function( dataView, dataOffset, littleEndian ) {\n                    return dataView.getInt32( dataOffset, littleEndian ) /\n                        dataView.getInt32( dataOffset + 4, littleEndian );\n                },\n                size: 8\n            }\n        };\n    \n        // undefined, 8-bit byte, value depending on field:\n        EXIF.exifTagTypes[ 7 ] = EXIF.exifTagTypes[ 1 ];\n    \n        EXIF.getExifValue = function( dataView, tiffOffset, offset, type, length,\n                littleEndian ) {\n    \n            var tagType = EXIF.exifTagTypes[ type ],\n                tagSize, dataOffset, values, i, str, c;\n    \n            if ( !tagType ) {\n                Base.log('Invalid Exif data: Invalid tag type.');\n                return;\n            }\n    \n            tagSize = tagType.size * length;\n    \n            // Determine if the value is contained in the dataOffset bytes,\n            // or if the value at the dataOffset is a pointer to the actual data:\n            dataOffset = tagSize > 4 ? tiffOffset + dataView.getUint32( offset + 8,\n                    littleEndian ) : (offset + 8);\n    \n            if ( dataOffset + tagSize > dataView.byteLength ) {\n                Base.log('Invalid Exif data: Invalid data offset.');\n                return;\n            }\n    \n            if ( length === 1 ) {\n                return tagType.getValue( dataView, dataOffset, littleEndian );\n            }\n    \n            values = [];\n    \n            for ( i = 0; i < length; i += 1 ) {\n                values[ i ] = tagType.getValue( dataView,\n                        dataOffset + i * tagType.size, littleEndian );\n            }\n    \n            if ( tagType.ascii ) {\n                str = '';\n    \n                // Concatenate the chars:\n                for ( i = 0; i < values.length; i += 1 ) {\n                    c = values[ i ];\n    \n                    // Ignore the terminating NULL byte(s):\n                    if ( c === '\\u0000' ) {\n                        break;\n                    }\n                    str += c;\n                }\n    \n                return str;\n            }\n            return values;\n        };\n    \n        EXIF.parseExifTag = function( dataView, tiffOffset, offset, littleEndian,\n                data ) {\n    \n            var tag = dataView.getUint16( offset, littleEndian );\n            data.exif[ tag ] = EXIF.getExifValue( dataView, tiffOffset, offset,\n                    dataView.getUint16( offset + 2, littleEndian ),    // tag type\n                    dataView.getUint32( offset + 4, littleEndian ),    // tag length\n                    littleEndian );\n        };\n    \n        EXIF.parseExifTags = function( dataView, tiffOffset, dirOffset,\n                littleEndian, data ) {\n    \n            var tagsNumber, dirEndOffset, i;\n    \n            if ( dirOffset + 6 > dataView.byteLength ) {\n                Base.log('Invalid Exif data: Invalid directory offset.');\n                return;\n            }\n    \n            tagsNumber = dataView.getUint16( dirOffset, littleEndian );\n            dirEndOffset = dirOffset + 2 + 12 * tagsNumber;\n    \n            if ( dirEndOffset + 4 > dataView.byteLength ) {\n                Base.log('Invalid Exif data: Invalid directory size.');\n                return;\n            }\n    \n            for ( i = 0; i < tagsNumber; i += 1 ) {\n                this.parseExifTag( dataView, tiffOffset,\n                        dirOffset + 2 + 12 * i,    // tag offset\n                        littleEndian, data );\n            }\n    \n            // Return the offset to the next directory:\n            return dataView.getUint32( dirEndOffset, littleEndian );\n        };\n    \n        // EXIF.getExifThumbnail = function(dataView, offset, length) {\n        //     var hexData,\n        //         i,\n        //         b;\n        //     if (!length || offset + length > dataView.byteLength) {\n        //         Base.log('Invalid Exif data: Invalid thumbnail data.');\n        //         return;\n        //     }\n        //     hexData = [];\n        //     for (i = 0; i < length; i += 1) {\n        //         b = dataView.getUint8(offset + i);\n        //         hexData.push((b < 16 ? '0' : '') + b.toString(16));\n        //     }\n        //     return 'data:image/jpeg,%' + hexData.join('%');\n        // };\n    \n        EXIF.parseExifData = function( dataView, offset, length, data ) {\n    \n            var tiffOffset = offset + 10,\n                littleEndian, dirOffset;\n    \n            // Check for the ASCII code for \"Exif\" (0x45786966):\n            if ( dataView.getUint32( offset + 4 ) !== 0x45786966 ) {\n                // No Exif data, might be XMP data instead\n                return;\n            }\n            if ( tiffOffset + 8 > dataView.byteLength ) {\n                Base.log('Invalid Exif data: Invalid segment size.');\n                return;\n            }\n    \n            // Check for the two null bytes:\n            if ( dataView.getUint16( offset + 8 ) !== 0x0000 ) {\n                Base.log('Invalid Exif data: Missing byte alignment offset.');\n                return;\n            }\n    \n            // Check the byte alignment:\n            switch ( dataView.getUint16( tiffOffset ) ) {\n                case 0x4949:\n                    littleEndian = true;\n                    break;\n    \n                case 0x4D4D:\n                    littleEndian = false;\n                    break;\n    \n                default:\n                    Base.log('Invalid Exif data: Invalid byte alignment marker.');\n                    return;\n            }\n    \n            // Check for the TIFF tag marker (0x002A):\n            if ( dataView.getUint16( tiffOffset + 2, littleEndian ) !== 0x002A ) {\n                Base.log('Invalid Exif data: Missing TIFF marker.');\n                return;\n            }\n    \n            // Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal:\n            dirOffset = dataView.getUint32( tiffOffset + 4, littleEndian );\n            // Create the exif object to store the tags:\n            data.exif = new EXIF.ExifMap();\n            // Parse the tags of the main image directory and retrieve the\n            // offset to the next directory, usually the thumbnail directory:\n            dirOffset = EXIF.parseExifTags( dataView, tiffOffset,\n                    tiffOffset + dirOffset, littleEndian, data );\n    \n            // 尝试读取缩略图\n            // if ( dirOffset ) {\n            //     thumbnailData = {exif: {}};\n            //     dirOffset = EXIF.parseExifTags(\n            //         dataView,\n            //         tiffOffset,\n            //         tiffOffset + dirOffset,\n            //         littleEndian,\n            //         thumbnailData\n            //     );\n    \n            //     // Check for JPEG Thumbnail offset:\n            //     if (thumbnailData.exif[0x0201]) {\n            //         data.exif.Thumbnail = EXIF.getExifThumbnail(\n            //             dataView,\n            //             tiffOffset + thumbnailData.exif[0x0201],\n            //             thumbnailData.exif[0x0202] // Thumbnail data length\n            //         );\n            //     }\n            // }\n        };\n    \n        ImageMeta.parsers[ 0xffe1 ].push( EXIF.parseExifData );\n        return EXIF;\n    });\n    /**\n     * @fileOverview Image\n     */\n    define('runtime/html5/image',[\n        'base',\n        'runtime/html5/runtime',\n        'runtime/html5/util'\n    ], function( Base, Html5Runtime, Util ) {\n    \n        var BLANK = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D';\n    \n        return Html5Runtime.register( 'Image', {\n    \n            // flag: 标记是否被修改过。\n            modified: false,\n    \n            init: function() {\n                var me = this,\n                    img = new Image();\n    \n                img.onload = function() {\n    \n                    me._info = {\n                        type: me.type,\n                        width: this.width,\n                        height: this.height\n                    };\n    \n                    // 读取meta信息。\n                    if ( !me._metas && 'image/jpeg' === me.type ) {\n                        Util.parseMeta( me._blob, function( error, ret ) {\n                            me._metas = ret;\n                            me.owner.trigger('load');\n                        });\n                    } else {\n                        me.owner.trigger('load');\n                    }\n                };\n    \n                img.onerror = function() {\n                    me.owner.trigger('error');\n                };\n    \n                me._img = img;\n            },\n    \n            loadFromBlob: function( blob ) {\n                var me = this,\n                    img = me._img;\n    \n                me._blob = blob;\n                me.type = blob.type;\n                img.src = Util.createObjectURL( blob.getSource() );\n                me.owner.once( 'load', function() {\n                    Util.revokeObjectURL( img.src );\n                });\n            },\n    \n            resize: function( width, height ) {\n                var canvas = this._canvas ||\n                        (this._canvas = document.createElement('canvas'));\n    \n                this._resize( this._img, canvas, width, height );\n                this._blob = null;    // 没用了，可以删掉了。\n                this.modified = true;\n                this.owner.trigger('complete');\n            },\n    \n            getAsBlob: function( type ) {\n                var blob = this._blob,\n                    opts = this.options,\n                    canvas;\n    \n                type = type || this.type;\n    \n                // blob需要重新生成。\n                if ( this.modified || this.type !== type ) {\n                    canvas = this._canvas;\n    \n                    if ( type === 'image/jpeg' ) {\n    \n                        blob = Util.canvasToDataUrl( canvas, 'image/jpeg',\n                                opts.quality );\n    \n                        if ( opts.preserveHeaders && this._metas &&\n                                this._metas.imageHead ) {\n    \n                            blob = Util.dataURL2ArrayBuffer( blob );\n                            blob = Util.updateImageHead( blob,\n                                    this._metas.imageHead );\n                            blob = Util.arrayBufferToBlob( blob, type );\n                            return blob;\n                        }\n                    } else {\n                        blob = Util.canvasToDataUrl( canvas, type );\n                    }\n    \n                    blob = Util.dataURL2Blob( blob );\n                }\n    \n                return blob;\n            },\n    \n            getAsDataUrl: function( type ) {\n                var opts = this.options;\n    \n                type = type || this.type;\n    \n                if ( type === 'image/jpeg' ) {\n                    return Util.canvasToDataUrl( this._canvas, type, opts.quality );\n                } else {\n                    return this._canvas.toDataURL( type );\n                }\n            },\n    \n            getOrientation: function() {\n                return this._metas && this._metas.exif &&\n                        this._metas.exif.get('Orientation') || 1;\n            },\n    \n            info: function( val ) {\n    \n                // setter\n                if ( val ) {\n                    this._info = val;\n                    return this;\n                }\n    \n                // getter\n                return this._info;\n            },\n    \n            meta: function( val ) {\n    \n                // setter\n                if ( val ) {\n                    this._meta = val;\n                    return this;\n                }\n    \n                // getter\n                return this._meta;\n            },\n    \n            destroy: function() {\n                var canvas = this._canvas;\n                this._img.onload = null;\n    \n                if ( canvas ) {\n                    canvas.getContext('2d')\n                            .clearRect( 0, 0, canvas.width, canvas.height );\n                    canvas.width = canvas.height = 0;\n                    this._canvas = null;\n                }\n    \n                // 释放内存。非常重要，否则释放不了image的内存。\n                this._img.src = BLANK;\n                this._img = this._blob = null;\n            },\n    \n            _resize: function( img, cvs, width, height ) {\n                var opts = this.options,\n                    naturalWidth = img.width,\n                    naturalHeight = img.height,\n                    orientation = this.getOrientation(),\n                    scale, w, h, x, y;\n    \n                // values that require 90 degree rotation\n                if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) {\n    \n                    // 交换width, height的值。\n                    width ^= height;\n                    height ^= width;\n                    width ^= height;\n                }\n    \n                scale = Math[ opts.crop ? 'max' : 'min' ]( width / naturalWidth,\n                        height / naturalHeight );\n    \n                // 不允许放大。\n                opts.allowMagnify || (scale = Math.min( 1, scale ));\n    \n                w = naturalWidth * scale;\n                h = naturalHeight * scale;\n    \n                if ( opts.crop ) {\n                    cvs.width = width;\n                    cvs.height = height;\n                } else {\n                    cvs.width = w;\n                    cvs.height = h;\n                }\n    \n                x = (cvs.width - w) / 2;\n                y = (cvs.height - h) / 2;\n    \n                opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation );\n    \n                this._renderImageToCanvas( cvs, img, x, y, w, h );\n            },\n    \n            _rotate2Orientaion: function( canvas, orientation ) {\n                var width = canvas.width,\n                    height = canvas.height,\n                    ctx = canvas.getContext('2d');\n    \n                switch ( orientation ) {\n                    case 5:\n                    case 6:\n                    case 7:\n                    case 8:\n                        canvas.width = height;\n                        canvas.height = width;\n                        break;\n                }\n    \n                switch ( orientation ) {\n                    case 2:    // horizontal flip\n                        ctx.translate( width, 0 );\n                        ctx.scale( -1, 1 );\n                        break;\n    \n                    case 3:    // 180 rotate left\n                        ctx.translate( width, height );\n                        ctx.rotate( Math.PI );\n                        break;\n    \n                    case 4:    // vertical flip\n                        ctx.translate( 0, height );\n                        ctx.scale( 1, -1 );\n                        break;\n    \n                    case 5:    // vertical flip + 90 rotate right\n                        ctx.rotate( 0.5 * Math.PI );\n                        ctx.scale( 1, -1 );\n                        break;\n    \n                    case 6:    // 90 rotate right\n                        ctx.rotate( 0.5 * Math.PI );\n                        ctx.translate( 0, -height );\n                        break;\n    \n                    case 7:    // horizontal flip + 90 rotate right\n                        ctx.rotate( 0.5 * Math.PI );\n                        ctx.translate( width, -height );\n                        ctx.scale( -1, 1 );\n                        break;\n    \n                    case 8:    // 90 rotate left\n                        ctx.rotate( -0.5 * Math.PI );\n                        ctx.translate( -width, 0 );\n                        break;\n                }\n            },\n    \n            // https://github.com/stomita/ios-imagefile-megapixel/\n            // blob/master/src/megapix-image.js\n            _renderImageToCanvas: (function() {\n    \n                // 如果不是ios, 不需要这么复杂！\n                if ( !Base.os.ios ) {\n                    return function( canvas, img, x, y, w, h ) {\n                        canvas.getContext('2d').drawImage( img, x, y, w, h );\n                    };\n                }\n    \n                /**\n                 * Detecting vertical squash in loaded image.\n                 * Fixes a bug which squash image vertically while drawing into\n                 * canvas for some images.\n                 */\n                function detectVerticalSquash( img, iw, ih ) {\n                    var canvas = document.createElement('canvas'),\n                        ctx = canvas.getContext('2d'),\n                        sy = 0,\n                        ey = ih,\n                        py = ih,\n                        data, alpha, ratio;\n    \n    \n                    canvas.width = 1;\n                    canvas.height = ih;\n                    ctx.drawImage( img, 0, 0 );\n                    data = ctx.getImageData( 0, 0, 1, ih ).data;\n    \n                    // search image edge pixel position in case\n                    // it is squashed vertically.\n                    while ( py > sy ) {\n                        alpha = data[ (py - 1) * 4 + 3 ];\n    \n                        if ( alpha === 0 ) {\n                            ey = py;\n                        } else {\n                            sy = py;\n                        }\n    \n                        py = (ey + sy) >> 1;\n                    }\n    \n                    ratio = (py / ih);\n                    return (ratio === 0) ? 1 : ratio;\n                }\n    \n                // fix ie7 bug\n                // http://stackoverflow.com/questions/11929099/\n                // html5-canvas-drawimage-ratio-bug-ios\n                if ( Base.os.ios >= 7 ) {\n                    return function( canvas, img, x, y, w, h ) {\n                        var iw = img.naturalWidth,\n                            ih = img.naturalHeight,\n                            vertSquashRatio = detectVerticalSquash( img, iw, ih );\n    \n                        return canvas.getContext('2d').drawImage( img, 0, 0,\n                            iw * vertSquashRatio, ih * vertSquashRatio,\n                            x, y, w, h );\n                    };\n                }\n    \n                /**\n                 * Detect subsampling in loaded image.\n                 * In iOS, larger images than 2M pixels may be\n                 * subsampled in rendering.\n                 */\n                function detectSubsampling( img ) {\n                    var iw = img.naturalWidth,\n                        ih = img.naturalHeight,\n                        canvas, ctx;\n    \n                    // subsampling may happen overmegapixel image\n                    if ( iw * ih > 1024 * 1024 ) {\n                        canvas = document.createElement('canvas');\n                        canvas.width = canvas.height = 1;\n                        ctx = canvas.getContext('2d');\n                        ctx.drawImage( img, -iw + 1, 0 );\n    \n                        // subsampled image becomes half smaller in rendering size.\n                        // check alpha channel value to confirm image is covering\n                        // edge pixel or not. if alpha value is 0\n                        // image is not covering, hence subsampled.\n                        return ctx.getImageData( 0, 0, 1, 1 ).data[ 3 ] === 0;\n                    } else {\n                        return false;\n                    }\n                }\n    \n    \n                return function( canvas, img, x, y, width, height ) {\n                    var iw = img.naturalWidth,\n                        ih = img.naturalHeight,\n                        ctx = canvas.getContext('2d'),\n                        subsampled = detectSubsampling( img ),\n                        doSquash = this.type === 'image/jpeg',\n                        d = 1024,\n                        sy = 0,\n                        dy = 0,\n                        tmpCanvas, tmpCtx, vertSquashRatio, dw, dh, sx, dx;\n    \n                    if ( subsampled ) {\n                        iw /= 2;\n                        ih /= 2;\n                    }\n    \n                    ctx.save();\n                    tmpCanvas = document.createElement('canvas');\n                    tmpCanvas.width = tmpCanvas.height = d;\n    \n                    tmpCtx = tmpCanvas.getContext('2d');\n                    vertSquashRatio = doSquash ?\n                            detectVerticalSquash( img, iw, ih ) : 1;\n    \n                    dw = Math.ceil( d * width / iw );\n                    dh = Math.ceil( d * height / ih / vertSquashRatio );\n    \n                    while ( sy < ih ) {\n                        sx = 0;\n                        dx = 0;\n                        while ( sx < iw ) {\n                            tmpCtx.clearRect( 0, 0, d, d );\n                            tmpCtx.drawImage( img, -sx, -sy );\n                            ctx.drawImage( tmpCanvas, 0, 0, d, d,\n                                    x + dx, y + dy, dw, dh );\n                            sx += d;\n                            dx += dw;\n                        }\n                        sy += d;\n                        dy += dh;\n                    }\n                    ctx.restore();\n                    tmpCanvas = tmpCtx = null;\n                };\n            })()\n        });\n    });\n    /**\n     * 这个方式性能不行，但是可以解决android里面的toDataUrl的bug\n     * android里面toDataUrl('image/jpege')得到的结果却是png.\n     *\n     * 所以这里没辙，只能借助这个工具\n     * @fileOverview jpeg encoder\n     */\n    define('runtime/html5/jpegencoder',[], function( require, exports, module ) {\n    \n        /*\n          Copyright (c) 2008, Adobe Systems Incorporated\n          All rights reserved.\n    \n          Redistribution and use in source and binary forms, with or without\n          modification, are permitted provided that the following conditions are\n          met:\n    \n          * Redistributions of source code must retain the above copyright notice,\n            this list of conditions and the following disclaimer.\n    \n          * Redistributions in binary form must reproduce the above copyright\n            notice, this list of conditions and the following disclaimer in the\n            documentation and/or other materials provided with the distribution.\n    \n          * Neither the name of Adobe Systems Incorporated nor the names of its\n            contributors may be used to endorse or promote products derived from\n            this software without specific prior written permission.\n    \n          THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n          IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n          THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n          PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n          CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n          EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n          PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n          PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n          LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n          NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n          SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n        */\n        /*\n        JPEG encoder ported to JavaScript and optimized by Andreas Ritter, www.bytestrom.eu, 11/2009\n    \n        Basic GUI blocking jpeg encoder\n        */\n    \n        function JPEGEncoder(quality) {\n          var self = this;\n            var fround = Math.round;\n            var ffloor = Math.floor;\n            var YTable = new Array(64);\n            var UVTable = new Array(64);\n            var fdtbl_Y = new Array(64);\n            var fdtbl_UV = new Array(64);\n            var YDC_HT;\n            var UVDC_HT;\n            var YAC_HT;\n            var UVAC_HT;\n    \n            var bitcode = new Array(65535);\n            var category = new Array(65535);\n            var outputfDCTQuant = new Array(64);\n            var DU = new Array(64);\n            var byteout = [];\n            var bytenew = 0;\n            var bytepos = 7;\n    \n            var YDU = new Array(64);\n            var UDU = new Array(64);\n            var VDU = new Array(64);\n            var clt = new Array(256);\n            var RGB_YUV_TABLE = new Array(2048);\n            var currentQuality;\n    \n            var ZigZag = [\n                     0, 1, 5, 6,14,15,27,28,\n                     2, 4, 7,13,16,26,29,42,\n                     3, 8,12,17,25,30,41,43,\n                     9,11,18,24,31,40,44,53,\n                    10,19,23,32,39,45,52,54,\n                    20,22,33,38,46,51,55,60,\n                    21,34,37,47,50,56,59,61,\n                    35,36,48,49,57,58,62,63\n                ];\n    \n            var std_dc_luminance_nrcodes = [0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0];\n            var std_dc_luminance_values = [0,1,2,3,4,5,6,7,8,9,10,11];\n            var std_ac_luminance_nrcodes = [0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d];\n            var std_ac_luminance_values = [\n                    0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,\n                    0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,\n                    0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08,\n                    0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,\n                    0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,\n                    0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28,\n                    0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,\n                    0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,\n                    0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,\n                    0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,\n                    0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,\n                    0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89,\n                    0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,\n                    0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,\n                    0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,\n                    0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,\n                    0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,\n                    0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2,\n                    0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,\n                    0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,\n                    0xf9,0xfa\n                ];\n    \n            var std_dc_chrominance_nrcodes = [0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0];\n            var std_dc_chrominance_values = [0,1,2,3,4,5,6,7,8,9,10,11];\n            var std_ac_chrominance_nrcodes = [0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77];\n            var std_ac_chrominance_values = [\n                    0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,\n                    0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,\n                    0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,\n                    0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,\n                    0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,\n                    0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26,\n                    0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,\n                    0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,\n                    0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,\n                    0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,\n                    0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,\n                    0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87,\n                    0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,\n                    0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,\n                    0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,\n                    0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,\n                    0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,\n                    0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,\n                    0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,\n                    0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,\n                    0xf9,0xfa\n                ];\n    \n            function initQuantTables(sf){\n                    var YQT = [\n                        16, 11, 10, 16, 24, 40, 51, 61,\n                        12, 12, 14, 19, 26, 58, 60, 55,\n                        14, 13, 16, 24, 40, 57, 69, 56,\n                        14, 17, 22, 29, 51, 87, 80, 62,\n                        18, 22, 37, 56, 68,109,103, 77,\n                        24, 35, 55, 64, 81,104,113, 92,\n                        49, 64, 78, 87,103,121,120,101,\n                        72, 92, 95, 98,112,100,103, 99\n                    ];\n    \n                    for (var i = 0; i < 64; i++) {\n                        var t = ffloor((YQT[i]*sf+50)/100);\n                        if (t < 1) {\n                            t = 1;\n                        } else if (t > 255) {\n                            t = 255;\n                        }\n                        YTable[ZigZag[i]] = t;\n                    }\n                    var UVQT = [\n                        17, 18, 24, 47, 99, 99, 99, 99,\n                        18, 21, 26, 66, 99, 99, 99, 99,\n                        24, 26, 56, 99, 99, 99, 99, 99,\n                        47, 66, 99, 99, 99, 99, 99, 99,\n                        99, 99, 99, 99, 99, 99, 99, 99,\n                        99, 99, 99, 99, 99, 99, 99, 99,\n                        99, 99, 99, 99, 99, 99, 99, 99,\n                        99, 99, 99, 99, 99, 99, 99, 99\n                    ];\n                    for (var j = 0; j < 64; j++) {\n                        var u = ffloor((UVQT[j]*sf+50)/100);\n                        if (u < 1) {\n                            u = 1;\n                        } else if (u > 255) {\n                            u = 255;\n                        }\n                        UVTable[ZigZag[j]] = u;\n                    }\n                    var aasf = [\n                        1.0, 1.387039845, 1.306562965, 1.175875602,\n                        1.0, 0.785694958, 0.541196100, 0.275899379\n                    ];\n                    var k = 0;\n                    for (var row = 0; row < 8; row++)\n                    {\n                        for (var col = 0; col < 8; col++)\n                        {\n                            fdtbl_Y[k]  = (1.0 / (YTable [ZigZag[k]] * aasf[row] * aasf[col] * 8.0));\n                            fdtbl_UV[k] = (1.0 / (UVTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0));\n                            k++;\n                        }\n                    }\n                }\n    \n                function computeHuffmanTbl(nrcodes, std_table){\n                    var codevalue = 0;\n                    var pos_in_table = 0;\n                    var HT = new Array();\n                    for (var k = 1; k <= 16; k++) {\n                        for (var j = 1; j <= nrcodes[k]; j++) {\n                            HT[std_table[pos_in_table]] = [];\n                            HT[std_table[pos_in_table]][0] = codevalue;\n                            HT[std_table[pos_in_table]][1] = k;\n                            pos_in_table++;\n                            codevalue++;\n                        }\n                        codevalue*=2;\n                    }\n                    return HT;\n                }\n    \n                function initHuffmanTbl()\n                {\n                    YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes,std_dc_luminance_values);\n                    UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes,std_dc_chrominance_values);\n                    YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes,std_ac_luminance_values);\n                    UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes,std_ac_chrominance_values);\n                }\n    \n                function initCategoryNumber()\n                {\n                    var nrlower = 1;\n                    var nrupper = 2;\n                    for (var cat = 1; cat <= 15; cat++) {\n                        //Positive numbers\n                        for (var nr = nrlower; nr<nrupper; nr++) {\n                            category[32767+nr] = cat;\n                            bitcode[32767+nr] = [];\n                            bitcode[32767+nr][1] = cat;\n                            bitcode[32767+nr][0] = nr;\n                        }\n                        //Negative numbers\n                        for (var nrneg =-(nrupper-1); nrneg<=-nrlower; nrneg++) {\n                            category[32767+nrneg] = cat;\n                            bitcode[32767+nrneg] = [];\n                            bitcode[32767+nrneg][1] = cat;\n                            bitcode[32767+nrneg][0] = nrupper-1+nrneg;\n                        }\n                        nrlower <<= 1;\n                        nrupper <<= 1;\n                    }\n                }\n    \n                function initRGBYUVTable() {\n                    for(var i = 0; i < 256;i++) {\n                        RGB_YUV_TABLE[i]            =  19595 * i;\n                        RGB_YUV_TABLE[(i+ 256)>>0]  =  38470 * i;\n                        RGB_YUV_TABLE[(i+ 512)>>0]  =   7471 * i + 0x8000;\n                        RGB_YUV_TABLE[(i+ 768)>>0]  = -11059 * i;\n                        RGB_YUV_TABLE[(i+1024)>>0]  = -21709 * i;\n                        RGB_YUV_TABLE[(i+1280)>>0]  =  32768 * i + 0x807FFF;\n                        RGB_YUV_TABLE[(i+1536)>>0]  = -27439 * i;\n                        RGB_YUV_TABLE[(i+1792)>>0]  = - 5329 * i;\n                    }\n                }\n    \n                // IO functions\n                function writeBits(bs)\n                {\n                    var value = bs[0];\n                    var posval = bs[1]-1;\n                    while ( posval >= 0 ) {\n                        if (value & (1 << posval) ) {\n                            bytenew |= (1 << bytepos);\n                        }\n                        posval--;\n                        bytepos--;\n                        if (bytepos < 0) {\n                            if (bytenew == 0xFF) {\n                                writeByte(0xFF);\n                                writeByte(0);\n                            }\n                            else {\n                                writeByte(bytenew);\n                            }\n                            bytepos=7;\n                            bytenew=0;\n                        }\n                    }\n                }\n    \n                function writeByte(value)\n                {\n                    byteout.push(clt[value]); // write char directly instead of converting later\n                }\n    \n                function writeWord(value)\n                {\n                    writeByte((value>>8)&0xFF);\n                    writeByte((value   )&0xFF);\n                }\n    \n                // DCT & quantization core\n                function fDCTQuant(data, fdtbl)\n                {\n                    var d0, d1, d2, d3, d4, d5, d6, d7;\n                    /* Pass 1: process rows. */\n                    var dataOff=0;\n                    var i;\n                    var I8 = 8;\n                    var I64 = 64;\n                    for (i=0; i<I8; ++i)\n                    {\n                        d0 = data[dataOff];\n                        d1 = data[dataOff+1];\n                        d2 = data[dataOff+2];\n                        d3 = data[dataOff+3];\n                        d4 = data[dataOff+4];\n                        d5 = data[dataOff+5];\n                        d6 = data[dataOff+6];\n                        d7 = data[dataOff+7];\n    \n                        var tmp0 = d0 + d7;\n                        var tmp7 = d0 - d7;\n                        var tmp1 = d1 + d6;\n                        var tmp6 = d1 - d6;\n                        var tmp2 = d2 + d5;\n                        var tmp5 = d2 - d5;\n                        var tmp3 = d3 + d4;\n                        var tmp4 = d3 - d4;\n    \n                        /* Even part */\n                        var tmp10 = tmp0 + tmp3;    /* phase 2 */\n                        var tmp13 = tmp0 - tmp3;\n                        var tmp11 = tmp1 + tmp2;\n                        var tmp12 = tmp1 - tmp2;\n    \n                        data[dataOff] = tmp10 + tmp11; /* phase 3 */\n                        data[dataOff+4] = tmp10 - tmp11;\n    \n                        var z1 = (tmp12 + tmp13) * 0.707106781; /* c4 */\n                        data[dataOff+2] = tmp13 + z1; /* phase 5 */\n                        data[dataOff+6] = tmp13 - z1;\n    \n                        /* Odd part */\n                        tmp10 = tmp4 + tmp5; /* phase 2 */\n                        tmp11 = tmp5 + tmp6;\n                        tmp12 = tmp6 + tmp7;\n    \n                        /* The rotator is modified from fig 4-8 to avoid extra negations. */\n                        var z5 = (tmp10 - tmp12) * 0.382683433; /* c6 */\n                        var z2 = 0.541196100 * tmp10 + z5; /* c2-c6 */\n                        var z4 = 1.306562965 * tmp12 + z5; /* c2+c6 */\n                        var z3 = tmp11 * 0.707106781; /* c4 */\n    \n                        var z11 = tmp7 + z3;    /* phase 5 */\n                        var z13 = tmp7 - z3;\n    \n                        data[dataOff+5] = z13 + z2; /* phase 6 */\n                        data[dataOff+3] = z13 - z2;\n                        data[dataOff+1] = z11 + z4;\n                        data[dataOff+7] = z11 - z4;\n    \n                        dataOff += 8; /* advance pointer to next row */\n                    }\n    \n                    /* Pass 2: process columns. */\n                    dataOff = 0;\n                    for (i=0; i<I8; ++i)\n                    {\n                        d0 = data[dataOff];\n                        d1 = data[dataOff + 8];\n                        d2 = data[dataOff + 16];\n                        d3 = data[dataOff + 24];\n                        d4 = data[dataOff + 32];\n                        d5 = data[dataOff + 40];\n                        d6 = data[dataOff + 48];\n                        d7 = data[dataOff + 56];\n    \n                        var tmp0p2 = d0 + d7;\n                        var tmp7p2 = d0 - d7;\n                        var tmp1p2 = d1 + d6;\n                        var tmp6p2 = d1 - d6;\n                        var tmp2p2 = d2 + d5;\n                        var tmp5p2 = d2 - d5;\n                        var tmp3p2 = d3 + d4;\n                        var tmp4p2 = d3 - d4;\n    \n                        /* Even part */\n                        var tmp10p2 = tmp0p2 + tmp3p2;  /* phase 2 */\n                        var tmp13p2 = tmp0p2 - tmp3p2;\n                        var tmp11p2 = tmp1p2 + tmp2p2;\n                        var tmp12p2 = tmp1p2 - tmp2p2;\n    \n                        data[dataOff] = tmp10p2 + tmp11p2; /* phase 3 */\n                        data[dataOff+32] = tmp10p2 - tmp11p2;\n    \n                        var z1p2 = (tmp12p2 + tmp13p2) * 0.707106781; /* c4 */\n                        data[dataOff+16] = tmp13p2 + z1p2; /* phase 5 */\n                        data[dataOff+48] = tmp13p2 - z1p2;\n    \n                        /* Odd part */\n                        tmp10p2 = tmp4p2 + tmp5p2; /* phase 2 */\n                        tmp11p2 = tmp5p2 + tmp6p2;\n                        tmp12p2 = tmp6p2 + tmp7p2;\n    \n                        /* The rotator is modified from fig 4-8 to avoid extra negations. */\n                        var z5p2 = (tmp10p2 - tmp12p2) * 0.382683433; /* c6 */\n                        var z2p2 = 0.541196100 * tmp10p2 + z5p2; /* c2-c6 */\n                        var z4p2 = 1.306562965 * tmp12p2 + z5p2; /* c2+c6 */\n                        var z3p2 = tmp11p2 * 0.707106781; /* c4 */\n    \n                        var z11p2 = tmp7p2 + z3p2;  /* phase 5 */\n                        var z13p2 = tmp7p2 - z3p2;\n    \n                        data[dataOff+40] = z13p2 + z2p2; /* phase 6 */\n                        data[dataOff+24] = z13p2 - z2p2;\n                        data[dataOff+ 8] = z11p2 + z4p2;\n                        data[dataOff+56] = z11p2 - z4p2;\n    \n                        dataOff++; /* advance pointer to next column */\n                    }\n    \n                    // Quantize/descale the coefficients\n                    var fDCTQuant;\n                    for (i=0; i<I64; ++i)\n                    {\n                        // Apply the quantization and scaling factor & Round to nearest integer\n                        fDCTQuant = data[i]*fdtbl[i];\n                        outputfDCTQuant[i] = (fDCTQuant > 0.0) ? ((fDCTQuant + 0.5)|0) : ((fDCTQuant - 0.5)|0);\n                        //outputfDCTQuant[i] = fround(fDCTQuant);\n    \n                    }\n                    return outputfDCTQuant;\n                }\n    \n                function writeAPP0()\n                {\n                    writeWord(0xFFE0); // marker\n                    writeWord(16); // length\n                    writeByte(0x4A); // J\n                    writeByte(0x46); // F\n                    writeByte(0x49); // I\n                    writeByte(0x46); // F\n                    writeByte(0); // = \"JFIF\",'\\0'\n                    writeByte(1); // versionhi\n                    writeByte(1); // versionlo\n                    writeByte(0); // xyunits\n                    writeWord(1); // xdensity\n                    writeWord(1); // ydensity\n                    writeByte(0); // thumbnwidth\n                    writeByte(0); // thumbnheight\n                }\n    \n                function writeSOF0(width, height)\n                {\n                    writeWord(0xFFC0); // marker\n                    writeWord(17);   // length, truecolor YUV JPG\n                    writeByte(8);    // precision\n                    writeWord(height);\n                    writeWord(width);\n                    writeByte(3);    // nrofcomponents\n                    writeByte(1);    // IdY\n                    writeByte(0x11); // HVY\n                    writeByte(0);    // QTY\n                    writeByte(2);    // IdU\n                    writeByte(0x11); // HVU\n                    writeByte(1);    // QTU\n                    writeByte(3);    // IdV\n                    writeByte(0x11); // HVV\n                    writeByte(1);    // QTV\n                }\n    \n                function writeDQT()\n                {\n                    writeWord(0xFFDB); // marker\n                    writeWord(132);    // length\n                    writeByte(0);\n                    for (var i=0; i<64; i++) {\n                        writeByte(YTable[i]);\n                    }\n                    writeByte(1);\n                    for (var j=0; j<64; j++) {\n                        writeByte(UVTable[j]);\n                    }\n                }\n    \n                function writeDHT()\n                {\n                    writeWord(0xFFC4); // marker\n                    writeWord(0x01A2); // length\n    \n                    writeByte(0); // HTYDCinfo\n                    for (var i=0; i<16; i++) {\n                        writeByte(std_dc_luminance_nrcodes[i+1]);\n                    }\n                    for (var j=0; j<=11; j++) {\n                        writeByte(std_dc_luminance_values[j]);\n                    }\n    \n                    writeByte(0x10); // HTYACinfo\n                    for (var k=0; k<16; k++) {\n                        writeByte(std_ac_luminance_nrcodes[k+1]);\n                    }\n                    for (var l=0; l<=161; l++) {\n                        writeByte(std_ac_luminance_values[l]);\n                    }\n    \n                    writeByte(1); // HTUDCinfo\n                    for (var m=0; m<16; m++) {\n                        writeByte(std_dc_chrominance_nrcodes[m+1]);\n                    }\n                    for (var n=0; n<=11; n++) {\n                        writeByte(std_dc_chrominance_values[n]);\n                    }\n    \n                    writeByte(0x11); // HTUACinfo\n                    for (var o=0; o<16; o++) {\n                        writeByte(std_ac_chrominance_nrcodes[o+1]);\n                    }\n                    for (var p=0; p<=161; p++) {\n                        writeByte(std_ac_chrominance_values[p]);\n                    }\n                }\n    \n                function writeSOS()\n                {\n                    writeWord(0xFFDA); // marker\n                    writeWord(12); // length\n                    writeByte(3); // nrofcomponents\n                    writeByte(1); // IdY\n                    writeByte(0); // HTY\n                    writeByte(2); // IdU\n                    writeByte(0x11); // HTU\n                    writeByte(3); // IdV\n                    writeByte(0x11); // HTV\n                    writeByte(0); // Ss\n                    writeByte(0x3f); // Se\n                    writeByte(0); // Bf\n                }\n    \n                function processDU(CDU, fdtbl, DC, HTDC, HTAC){\n                    var EOB = HTAC[0x00];\n                    var M16zeroes = HTAC[0xF0];\n                    var pos;\n                    var I16 = 16;\n                    var I63 = 63;\n                    var I64 = 64;\n                    var DU_DCT = fDCTQuant(CDU, fdtbl);\n                    //ZigZag reorder\n                    for (var j=0;j<I64;++j) {\n                        DU[ZigZag[j]]=DU_DCT[j];\n                    }\n                    var Diff = DU[0] - DC; DC = DU[0];\n                    //Encode DC\n                    if (Diff==0) {\n                        writeBits(HTDC[0]); // Diff might be 0\n                    } else {\n                        pos = 32767+Diff;\n                        writeBits(HTDC[category[pos]]);\n                        writeBits(bitcode[pos]);\n                    }\n                    //Encode ACs\n                    var end0pos = 63; // was const... which is crazy\n                    for (; (end0pos>0)&&(DU[end0pos]==0); end0pos--) {};\n                    //end0pos = first element in reverse order !=0\n                    if ( end0pos == 0) {\n                        writeBits(EOB);\n                        return DC;\n                    }\n                    var i = 1;\n                    var lng;\n                    while ( i <= end0pos ) {\n                        var startpos = i;\n                        for (; (DU[i]==0) && (i<=end0pos); ++i) {}\n                        var nrzeroes = i-startpos;\n                        if ( nrzeroes >= I16 ) {\n                            lng = nrzeroes>>4;\n                            for (var nrmarker=1; nrmarker <= lng; ++nrmarker)\n                                writeBits(M16zeroes);\n                            nrzeroes = nrzeroes&0xF;\n                        }\n                        pos = 32767+DU[i];\n                        writeBits(HTAC[(nrzeroes<<4)+category[pos]]);\n                        writeBits(bitcode[pos]);\n                        i++;\n                    }\n                    if ( end0pos != I63 ) {\n                        writeBits(EOB);\n                    }\n                    return DC;\n                }\n    \n                function initCharLookupTable(){\n                    var sfcc = String.fromCharCode;\n                    for(var i=0; i < 256; i++){ ///// ACHTUNG // 255\n                        clt[i] = sfcc(i);\n                    }\n                }\n    \n                this.encode = function(image,quality) // image data object\n                {\n                    // var time_start = new Date().getTime();\n    \n                    if(quality) setQuality(quality);\n    \n                    // Initialize bit writer\n                    byteout = new Array();\n                    bytenew=0;\n                    bytepos=7;\n    \n                    // Add JPEG headers\n                    writeWord(0xFFD8); // SOI\n                    writeAPP0();\n                    writeDQT();\n                    writeSOF0(image.width,image.height);\n                    writeDHT();\n                    writeSOS();\n    \n    \n                    // Encode 8x8 macroblocks\n                    var DCY=0;\n                    var DCU=0;\n                    var DCV=0;\n    \n                    bytenew=0;\n                    bytepos=7;\n    \n    \n                    this.encode.displayName = \"_encode_\";\n    \n                    var imageData = image.data;\n                    var width = image.width;\n                    var height = image.height;\n    \n                    var quadWidth = width*4;\n                    var tripleWidth = width*3;\n    \n                    var x, y = 0;\n                    var r, g, b;\n                    var start,p, col,row,pos;\n                    while(y < height){\n                        x = 0;\n                        while(x < quadWidth){\n                        start = quadWidth * y + x;\n                        p = start;\n                        col = -1;\n                        row = 0;\n    \n                        for(pos=0; pos < 64; pos++){\n                            row = pos >> 3;// /8\n                            col = ( pos & 7 ) * 4; // %8\n                            p = start + ( row * quadWidth ) + col;\n    \n                            if(y+row >= height){ // padding bottom\n                                p-= (quadWidth*(y+1+row-height));\n                            }\n    \n                            if(x+col >= quadWidth){ // padding right\n                                p-= ((x+col) - quadWidth +4)\n                            }\n    \n                            r = imageData[ p++ ];\n                            g = imageData[ p++ ];\n                            b = imageData[ p++ ];\n    \n    \n                            /* // calculate YUV values dynamically\n                            YDU[pos]=((( 0.29900)*r+( 0.58700)*g+( 0.11400)*b))-128; //-0x80\n                            UDU[pos]=(((-0.16874)*r+(-0.33126)*g+( 0.50000)*b));\n                            VDU[pos]=((( 0.50000)*r+(-0.41869)*g+(-0.08131)*b));\n                            */\n    \n                            // use lookup table (slightly faster)\n                            YDU[pos] = ((RGB_YUV_TABLE[r]             + RGB_YUV_TABLE[(g +  256)>>0] + RGB_YUV_TABLE[(b +  512)>>0]) >> 16)-128;\n                            UDU[pos] = ((RGB_YUV_TABLE[(r +  768)>>0] + RGB_YUV_TABLE[(g + 1024)>>0] + RGB_YUV_TABLE[(b + 1280)>>0]) >> 16)-128;\n                            VDU[pos] = ((RGB_YUV_TABLE[(r + 1280)>>0] + RGB_YUV_TABLE[(g + 1536)>>0] + RGB_YUV_TABLE[(b + 1792)>>0]) >> 16)-128;\n    \n                        }\n    \n                        DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT);\n                        DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);\n                        DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);\n                        x+=32;\n                        }\n                        y+=8;\n                    }\n    \n    \n                    ////////////////////////////////////////////////////////////////\n    \n                    // Do the bit alignment of the EOI marker\n                    if ( bytepos >= 0 ) {\n                        var fillbits = [];\n                        fillbits[1] = bytepos+1;\n                        fillbits[0] = (1<<(bytepos+1))-1;\n                        writeBits(fillbits);\n                    }\n    \n                    writeWord(0xFFD9); //EOI\n    \n                    var jpegDataUri = 'data:image/jpeg;base64,' + btoa(byteout.join(''));\n    \n                    byteout = [];\n    \n                    // benchmarking\n                    // var duration = new Date().getTime() - time_start;\n                    // console.log('Encoding time: '+ currentQuality + 'ms');\n                    //\n    \n                    return jpegDataUri\n            }\n    \n            function setQuality(quality){\n                if (quality <= 0) {\n                    quality = 1;\n                }\n                if (quality > 100) {\n                    quality = 100;\n                }\n    \n                if(currentQuality == quality) return // don't recalc if unchanged\n    \n                var sf = 0;\n                if (quality < 50) {\n                    sf = Math.floor(5000 / quality);\n                } else {\n                    sf = Math.floor(200 - quality*2);\n                }\n    \n                initQuantTables(sf);\n                currentQuality = quality;\n                // console.log('Quality set to: '+quality +'%');\n            }\n    \n            function init(){\n                // var time_start = new Date().getTime();\n                if(!quality) quality = 50;\n                // Create tables\n                initCharLookupTable()\n                initHuffmanTbl();\n                initCategoryNumber();\n                initRGBYUVTable();\n    \n                setQuality(quality);\n                // var duration = new Date().getTime() - time_start;\n                // console.log('Initialization '+ duration + 'ms');\n            }\n    \n            init();\n    \n        };\n    \n        JPEGEncoder.encode = function( data, quality ) {\n            var encoder = new JPEGEncoder( quality );\n    \n            return encoder.encode( data );\n        }\n    \n        return JPEGEncoder;\n    });\n    /**\n     * @fileOverview Fix android canvas.toDataUrl bug.\n     */\n    define('runtime/html5/androidpatch',[\n        'runtime/html5/util',\n        'runtime/html5/jpegencoder',\n        'base'\n    ], function( Util, encoder, Base ) {\n        var origin = Util.canvasToDataUrl,\n            supportJpeg;\n    \n        Util.canvasToDataUrl = function( canvas, type, quality ) {\n            var ctx, w, h, fragement, parts;\n    \n            // 非android手机直接跳过。\n            if ( !Base.os.android ) {\n                return origin.apply( null, arguments );\n            }\n    \n            // 检测是否canvas支持jpeg导出，根据数据格式来判断。\n            // JPEG 前两位分别是：255, 216\n            if ( type === 'image/jpeg' && typeof supportJpeg === 'undefined' ) {\n                fragement = origin.apply( null, arguments );\n    \n                parts = fragement.split(',');\n    \n                if ( ~parts[ 0 ].indexOf('base64') ) {\n                    fragement = atob( parts[ 1 ] );\n                } else {\n                    fragement = decodeURIComponent( parts[ 1 ] );\n                }\n    \n                fragement = fragement.substring( 0, 2 );\n    \n                supportJpeg = fragement.charCodeAt( 0 ) === 255 &&\n                        fragement.charCodeAt( 1 ) === 216;\n            }\n    \n            // 只有在android环境下才修复\n            if ( type === 'image/jpeg' && !supportJpeg ) {\n                w = canvas.width;\n                h = canvas.height;\n                ctx = canvas.getContext('2d');\n    \n                return encoder.encode( ctx.getImageData( 0, 0, w, h ), quality );\n            }\n    \n            return origin.apply( null, arguments );\n        };\n    });\n    /**\n     * @fileOverview Transport\n     * @todo 支持chunked传输，优势：\n     * 可以将大文件分成小块，挨个传输，可以提高大文件成功率，当失败的时候，也只需要重传那小部分，\n     * 而不需要重头再传一次。另外断点续传也需要用chunked方式。\n     */\n    define('runtime/html5/transport',[\n        'base',\n        'runtime/html5/runtime'\n    ], function( Base, Html5Runtime ) {\n    \n        var noop = Base.noop,\n            $ = Base.$;\n    \n        return Html5Runtime.register( 'Transport', {\n            init: function() {\n                this._status = 0;\n                this._response = null;\n            },\n    \n            send: function() {\n                var owner = this.owner,\n                    opts = this.options,\n                    xhr = this._initAjax(),\n                    blob = owner._blob,\n                    server = opts.server,\n                    formData, binary, fr;\n    \n                if ( opts.sendAsBinary ) {\n                    server += (/\\?/.test( server ) ? '&' : '?') +\n                            $.param( owner._formData );\n    \n                    binary = blob.getSource();\n                } else {\n                    formData = new FormData();\n                    $.each( owner._formData, function( k, v ) {\n                        formData.append( k, v );\n                    });\n    \n                    formData.append( opts.fileVal, blob.getSource(),\n                            opts.filename || owner._formData.name || '' );\n                }\n    \n                if ( opts.withCredentials && 'withCredentials' in xhr ) {\n                    xhr.open( opts.method, server, true );\n                    xhr.withCredentials = true;\n                } else {\n                    xhr.open( opts.method, server );\n                }\n    \n                this._setRequestHeader( xhr, opts.headers );\n    \n                if ( binary ) {\n                    xhr.overrideMimeType('application/octet-stream');\n    \n                    // android直接发送blob会导致服务端接收到的是空文件。\n                    // bug详情。\n                    // https://code.google.com/p/android/issues/detail?id=39882\n                    // 所以先用fileReader读取出来再通过arraybuffer的方式发送。\n                    if ( Base.os.android ) {\n                        fr = new FileReader();\n    \n                        fr.onload = function() {\n                            xhr.send( this.result );\n                            fr = fr.onload = null;\n                        };\n    \n                        fr.readAsArrayBuffer( binary );\n                    } else {\n                        xhr.send( binary );\n                    }\n                } else {\n                    xhr.send( formData );\n                }\n            },\n    \n            getResponse: function() {\n                return this._response;\n            },\n    \n            getResponseAsJson: function() {\n                return this._parseJson( this._response );\n            },\n    \n            getStatus: function() {\n                return this._status;\n            },\n    \n            abort: function() {\n                var xhr = this._xhr;\n    \n                if ( xhr ) {\n                    xhr.upload.onprogress = noop;\n                    xhr.onreadystatechange = noop;\n                    xhr.abort();\n    \n                    this._xhr = xhr = null;\n                }\n            },\n    \n            destroy: function() {\n                this.abort();\n            },\n    \n            _initAjax: function() {\n                var me = this,\n                    xhr = new XMLHttpRequest(),\n                    opts = this.options;\n    \n                if ( opts.withCredentials && !('withCredentials' in xhr) &&\n                        typeof XDomainRequest !== 'undefined' ) {\n                    xhr = new XDomainRequest();\n                }\n    \n                xhr.upload.onprogress = function( e ) {\n                    var percentage = 0;\n    \n                    if ( e.lengthComputable ) {\n                        percentage = e.loaded / e.total;\n                    }\n    \n                    return me.trigger( 'progress', percentage );\n                };\n    \n                xhr.onreadystatechange = function() {\n    \n                    if ( xhr.readyState !== 4 ) {\n                        return;\n                    }\n    \n                    xhr.upload.onprogress = noop;\n                    xhr.onreadystatechange = noop;\n                    me._xhr = null;\n                    me._status = xhr.status;\n    \n                    if ( xhr.status >= 200 && xhr.status < 300 ) {\n                        me._response = xhr.responseText;\n                        return me.trigger('load');\n                    } else if ( xhr.status >= 500 && xhr.status < 600 ) {\n                        me._response = xhr.responseText;\n                        return me.trigger( 'error', 'server' );\n                    }\n    \n    \n                    return me.trigger( 'error', me._status ? 'http' : 'abort' );\n                };\n    \n                me._xhr = xhr;\n                return xhr;\n            },\n    \n            _setRequestHeader: function( xhr, headers ) {\n                $.each( headers, function( key, val ) {\n                    xhr.setRequestHeader( key, val );\n                });\n            },\n    \n            _parseJson: function( str ) {\n                var json;\n    \n                try {\n                    json = JSON.parse( str );\n                } catch ( ex ) {\n                    json = {};\n                }\n    \n                return json;\n            }\n        });\n    });\n    define('webuploader',[\n        'base',\n        'widgets/filepicker',\n        'widgets/image',\n        'widgets/queue',\n        'widgets/runtime',\n        'widgets/upload',\n        'runtime/html5/blob',\n        'runtime/html5/filepicker',\n        'runtime/html5/imagemeta/exif',\n        'runtime/html5/image',\n        'runtime/html5/androidpatch',\n        'runtime/html5/transport'\n    ], function( Base ) {\n        return Base;\n    });\n    return require('webuploader');\n});\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/webuploader/webuploader.flashonly.js",
    "content": "/*! WebUploader 0.1.2 */\n\n\n/**\n * @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。\n *\n * AMD API 内部的简单不完全实现，请忽略。只有当WebUploader被合并成一个文件的时候才会引入。\n */\n(function( root, factory ) {\n    var modules = {},\n\n        // 内部require, 简单不完全实现。\n        // https://github.com/amdjs/amdjs-api/wiki/require\n        _require = function( deps, callback ) {\n            var args, len, i;\n\n            // 如果deps不是数组，则直接返回指定module\n            if ( typeof deps === 'string' ) {\n                return getModule( deps );\n            } else {\n                args = [];\n                for( len = deps.length, i = 0; i < len; i++ ) {\n                    args.push( getModule( deps[ i ] ) );\n                }\n\n                return callback.apply( null, args );\n            }\n        },\n\n        // 内部define，暂时不支持不指定id.\n        _define = function( id, deps, factory ) {\n            if ( arguments.length === 2 ) {\n                factory = deps;\n                deps = null;\n            }\n\n            _require( deps || [], function() {\n                setModule( id, factory, arguments );\n            });\n        },\n\n        // 设置module, 兼容CommonJs写法。\n        setModule = function( id, factory, args ) {\n            var module = {\n                    exports: factory\n                },\n                returned;\n\n            if ( typeof factory === 'function' ) {\n                args.length || (args = [ _require, module.exports, module ]);\n                returned = factory.apply( null, args );\n                returned !== undefined && (module.exports = returned);\n            }\n\n            modules[ id ] = module.exports;\n        },\n\n        // 根据id获取module\n        getModule = function( id ) {\n            var module = modules[ id ] || root[ id ];\n\n            if ( !module ) {\n                throw new Error( '`' + id + '` is undefined' );\n            }\n\n            return module;\n        },\n\n        // 将所有modules，将路径ids装换成对象。\n        exportsTo = function( obj ) {\n            var key, host, parts, part, last, ucFirst;\n\n            // make the first character upper case.\n            ucFirst = function( str ) {\n                return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 ));\n            };\n\n            for ( key in modules ) {\n                host = obj;\n\n                if ( !modules.hasOwnProperty( key ) ) {\n                    continue;\n                }\n\n                parts = key.split('/');\n                last = ucFirst( parts.pop() );\n\n                while( (part = ucFirst( parts.shift() )) ) {\n                    host[ part ] = host[ part ] || {};\n                    host = host[ part ];\n                }\n\n                host[ last ] = modules[ key ];\n            }\n        },\n\n        exports = factory( root, _define, _require ),\n        origin;\n\n    // exports every module.\n    exportsTo( exports );\n\n    if ( typeof module === 'object' && typeof module.exports === 'object' ) {\n\n        // For CommonJS and CommonJS-like environments where a proper window is present,\n        module.exports = exports;\n    } else if ( typeof define === 'function' && define.amd ) {\n\n        // Allow using this built library as an AMD module\n        // in another project. That other project will only\n        // see this AMD call, not the internal modules in\n        // the closure below.\n        define([], exports );\n    } else {\n\n        // Browser globals case. Just assign the\n        // result to a property on the global.\n        origin = root.WebUploader;\n        root.WebUploader = exports;\n        root.WebUploader.noConflict = function() {\n            root.WebUploader = origin;\n        };\n    }\n})( this, function( window, define, require ) {\n\n\n    /**\n     * @fileOverview jQuery or Zepto\n     */\n    define('dollar-third',[],function() {\n        return window.jQuery || window.Zepto;\n    });\n    /**\n     * @fileOverview Dom 操作相关\n     */\n    define('dollar',[\n        'dollar-third'\n    ], function( _ ) {\n        return _;\n    });\n    /**\n     * @fileOverview 使用jQuery的Promise\n     */\n    define('promise-third',[\n        'dollar'\n    ], function( $ ) {\n        return {\n            Deferred: $.Deferred,\n            when: $.when,\n    \n            isPromise: function( anything ) {\n                return anything && typeof anything.then === 'function';\n            }\n        };\n    });\n    /**\n     * @fileOverview Promise/A+\n     */\n    define('promise',[\n        'promise-third'\n    ], function( _ ) {\n        return _;\n    });\n    /**\n     * @fileOverview 基础类方法。\n     */\n    \n    /**\n     * Web Uploader内部类的详细说明，以下提及的功能类，都可以在`WebUploader`这个变量中访问到。\n     *\n     * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id.\n     * 默认module id该文件的路径，而此路径将会转化成名字空间存放在WebUploader中。如：\n     *\n     * * module `base`：WebUploader.Base\n     * * module `file`: WebUploader.File\n     * * module `lib/dnd`: WebUploader.Lib.Dnd\n     * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd\n     *\n     *\n     * 以下文档将可能省略`WebUploader`前缀。\n     * @module WebUploader\n     * @title WebUploader API文档\n     */\n    define('base',[\n        'dollar',\n        'promise'\n    ], function( $, promise ) {\n    \n        var noop = function() {},\n            call = Function.call;\n    \n        // http://jsperf.com/uncurrythis\n        // 反科里化\n        function uncurryThis( fn ) {\n            return function() {\n                return call.apply( fn, arguments );\n            };\n        }\n    \n        function bindFn( fn, context ) {\n            return function() {\n                return fn.apply( context, arguments );\n            };\n        }\n    \n        function createObject( proto ) {\n            var f;\n    \n            if ( Object.create ) {\n                return Object.create( proto );\n            } else {\n                f = function() {};\n                f.prototype = proto;\n                return new f();\n            }\n        }\n    \n    \n        /**\n         * 基础类，提供一些简单常用的方法。\n         * @class Base\n         */\n        return {\n    \n            /**\n             * @property {String} version 当前版本号。\n             */\n            version: '0.1.2',\n    \n            /**\n             * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。\n             */\n            $: $,\n    \n            Deferred: promise.Deferred,\n    \n            isPromise: promise.isPromise,\n    \n            when: promise.when,\n    \n            /**\n             * @description  简单的浏览器检查结果。\n             *\n             * * `webkit`  webkit版本号，如果浏览器为非webkit内核，此属性为`undefined`。\n             * * `chrome`  chrome浏览器版本号，如果浏览器为chrome，此属性为`undefined`。\n             * * `ie`  ie浏览器版本号，如果浏览器为非ie，此属性为`undefined`。**暂不支持ie10+**\n             * * `firefox`  firefox浏览器版本号，如果浏览器为非firefox，此属性为`undefined`。\n             * * `safari`  safari浏览器版本号，如果浏览器为非safari，此属性为`undefined`。\n             * * `opera`  opera浏览器版本号，如果浏览器为非opera，此属性为`undefined`。\n             *\n             * @property {Object} [browser]\n             */\n            browser: (function( ua ) {\n                var ret = {},\n                    webkit = ua.match( /WebKit\\/([\\d.]+)/ ),\n                    chrome = ua.match( /Chrome\\/([\\d.]+)/ ) ||\n                        ua.match( /CriOS\\/([\\d.]+)/ ),\n    \n                    ie = ua.match( /MSIE\\s([\\d\\.]+)/ ) ||\n                        ua.match(/(?:trident)(?:.*rv:([\\w.]+))?/i),\n                    firefox = ua.match( /Firefox\\/([\\d.]+)/ ),\n                    safari = ua.match( /Safari\\/([\\d.]+)/ ),\n                    opera = ua.match( /OPR\\/([\\d.]+)/ );\n    \n                webkit && (ret.webkit = parseFloat( webkit[ 1 ] ));\n                chrome && (ret.chrome = parseFloat( chrome[ 1 ] ));\n                ie && (ret.ie = parseFloat( ie[ 1 ] ));\n                firefox && (ret.firefox = parseFloat( firefox[ 1 ] ));\n                safari && (ret.safari = parseFloat( safari[ 1 ] ));\n                opera && (ret.opera = parseFloat( opera[ 1 ] ));\n    \n                return ret;\n            })( navigator.userAgent ),\n    \n            /**\n             * @description  操作系统检查结果。\n             *\n             * * `android`  如果在android浏览器环境下，此值为对应的android版本号，否则为`undefined`。\n             * * `ios` 如果在ios浏览器环境下，此值为对应的ios版本号，否则为`undefined`。\n             * @property {Object} [os]\n             */\n            os: (function( ua ) {\n                var ret = {},\n    \n                    // osx = !!ua.match( /\\(Macintosh\\; Intel / ),\n                    android = ua.match( /(?:Android);?[\\s\\/]+([\\d.]+)?/ ),\n                    ios = ua.match( /(?:iPad|iPod|iPhone).*OS\\s([\\d_]+)/ );\n    \n                // osx && (ret.osx = true);\n                android && (ret.android = parseFloat( android[ 1 ] ));\n                ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) ));\n    \n                return ret;\n            })( navigator.userAgent ),\n    \n            /**\n             * 实现类与类之间的继承。\n             * @method inherits\n             * @grammar Base.inherits( super ) => child\n             * @grammar Base.inherits( super, protos ) => child\n             * @grammar Base.inherits( super, protos, statics ) => child\n             * @param  {Class} super 父类\n             * @param  {Object | Function} [protos] 子类或者对象。如果对象中包含constructor，子类将是用此属性值。\n             * @param  {Function} [protos.constructor] 子类构造器，不指定的话将创建个临时的直接执行父类构造器的方法。\n             * @param  {Object} [statics] 静态属性或方法。\n             * @return {Class} 返回子类。\n             * @example\n             * function Person() {\n             *     console.log( 'Super' );\n             * }\n             * Person.prototype.hello = function() {\n             *     console.log( 'hello' );\n             * };\n             *\n             * var Manager = Base.inherits( Person, {\n             *     world: function() {\n             *         console.log( 'World' );\n             *     }\n             * });\n             *\n             * // 因为没有指定构造器，父类的构造器将会执行。\n             * var instance = new Manager();    // => Super\n             *\n             * // 继承子父类的方法\n             * instance.hello();    // => hello\n             * instance.world();    // => World\n             *\n             * // 子类的__super__属性指向父类\n             * console.log( Manager.__super__ === Person );    // => true\n             */\n            inherits: function( Super, protos, staticProtos ) {\n                var child;\n    \n                if ( typeof protos === 'function' ) {\n                    child = protos;\n                    protos = null;\n                } else if ( protos && protos.hasOwnProperty('constructor') ) {\n                    child = protos.constructor;\n                } else {\n                    child = function() {\n                        return Super.apply( this, arguments );\n                    };\n                }\n    \n                // 复制静态方法\n                $.extend( true, child, Super, staticProtos || {} );\n    \n                /* jshint camelcase: false */\n    \n                // 让子类的__super__属性指向父类。\n                child.__super__ = Super.prototype;\n    \n                // 构建原型，添加原型方法或属性。\n                // 暂时用Object.create实现。\n                child.prototype = createObject( Super.prototype );\n                protos && $.extend( true, child.prototype, protos );\n    \n                return child;\n            },\n    \n            /**\n             * 一个不做任何事情的方法。可以用来赋值给默认的callback.\n             * @method noop\n             */\n            noop: noop,\n    \n            /**\n             * 返回一个新的方法，此方法将已指定的`context`来执行。\n             * @grammar Base.bindFn( fn, context ) => Function\n             * @method bindFn\n             * @example\n             * var doSomething = function() {\n             *         console.log( this.name );\n             *     },\n             *     obj = {\n             *         name: 'Object Name'\n             *     },\n             *     aliasFn = Base.bind( doSomething, obj );\n             *\n             *  aliasFn();    // => Object Name\n             *\n             */\n            bindFn: bindFn,\n    \n            /**\n             * 引用Console.log如果存在的话，否则引用一个[空函数loop](#WebUploader:Base.log)。\n             * @grammar Base.log( args... ) => undefined\n             * @method log\n             */\n            log: (function() {\n                if ( window.console ) {\n                    return bindFn( console.log, console );\n                }\n                return noop;\n            })(),\n    \n            nextTick: (function() {\n    \n                return function( cb ) {\n                    setTimeout( cb, 1 );\n                };\n    \n                // @bug 当浏览器不在当前窗口时就停了。\n                // var next = window.requestAnimationFrame ||\n                //     window.webkitRequestAnimationFrame ||\n                //     window.mozRequestAnimationFrame ||\n                //     function( cb ) {\n                //         window.setTimeout( cb, 1000 / 60 );\n                //     };\n    \n                // // fix: Uncaught TypeError: Illegal invocation\n                // return bindFn( next, window );\n            })(),\n    \n            /**\n             * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。\n             * 将用来将非数组对象转化成数组对象。\n             * @grammar Base.slice( target, start[, end] ) => Array\n             * @method slice\n             * @example\n             * function doSomthing() {\n             *     var args = Base.slice( arguments, 1 );\n             *     console.log( args );\n             * }\n             *\n             * doSomthing( 'ignored', 'arg2', 'arg3' );    // => Array [\"arg2\", \"arg3\"]\n             */\n            slice: uncurryThis( [].slice ),\n    \n            /**\n             * 生成唯一的ID\n             * @method guid\n             * @grammar Base.guid() => String\n             * @grammar Base.guid( prefx ) => String\n             */\n            guid: (function() {\n                var counter = 0;\n    \n                return function( prefix ) {\n                    var guid = (+new Date()).toString( 32 ),\n                        i = 0;\n    \n                    for ( ; i < 5; i++ ) {\n                        guid += Math.floor( Math.random() * 65535 ).toString( 32 );\n                    }\n    \n                    return (prefix || 'wu_') + guid + (counter++).toString( 32 );\n                };\n            })(),\n    \n            /**\n             * 格式化文件大小, 输出成带单位的字符串\n             * @method formatSize\n             * @grammar Base.formatSize( size ) => String\n             * @grammar Base.formatSize( size, pointLength ) => String\n             * @grammar Base.formatSize( size, pointLength, units ) => String\n             * @param {Number} size 文件大小\n             * @param {Number} [pointLength=2] 精确到的小数点数。\n             * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节，到千字节，一直往上指定。如果单位数组里面只指定了到了K(千字节)，同时文件大小大于M, 此方法的输出将还是显示成多少K.\n             * @example\n             * console.log( Base.formatSize( 100 ) );    // => 100B\n             * console.log( Base.formatSize( 1024 ) );    // => 1.00K\n             * console.log( Base.formatSize( 1024, 0 ) );    // => 1K\n             * console.log( Base.formatSize( 1024 * 1024 ) );    // => 1.00M\n             * console.log( Base.formatSize( 1024 * 1024 * 1024 ) );    // => 1.00G\n             * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) );    // => 1024MB\n             */\n            formatSize: function( size, pointLength, units ) {\n                var unit;\n    \n                units = units || [ 'B', 'K', 'M', 'G', 'TB' ];\n    \n                while ( (unit = units.shift()) && size > 1024 ) {\n                    size = size / 1024;\n                }\n    \n                return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) +\n                        unit;\n            }\n        };\n    });\n    /**\n     * 事件处理类，可以独立使用，也可以扩展给对象使用。\n     * @fileOverview Mediator\n     */\n    define('mediator',[\n        'base'\n    ], function( Base ) {\n        var $ = Base.$,\n            slice = [].slice,\n            separator = /\\s+/,\n            protos;\n    \n        // 根据条件过滤出事件handlers.\n        function findHandlers( arr, name, callback, context ) {\n            return $.grep( arr, function( handler ) {\n                return handler &&\n                        (!name || handler.e === name) &&\n                        (!callback || handler.cb === callback ||\n                        handler.cb._cb === callback) &&\n                        (!context || handler.ctx === context);\n            });\n        }\n    \n        function eachEvent( events, callback, iterator ) {\n            // 不支持对象，只支持多个event用空格隔开\n            $.each( (events || '').split( separator ), function( _, key ) {\n                iterator( key, callback );\n            });\n        }\n    \n        function triggerHanders( events, args ) {\n            var stoped = false,\n                i = -1,\n                len = events.length,\n                handler;\n    \n            while ( ++i < len ) {\n                handler = events[ i ];\n    \n                if ( handler.cb.apply( handler.ctx2, args ) === false ) {\n                    stoped = true;\n                    break;\n                }\n            }\n    \n            return !stoped;\n        }\n    \n        protos = {\n    \n            /**\n             * 绑定事件。\n             *\n             * `callback`方法在执行时，arguments将会来源于trigger的时候携带的参数。如\n             * ```javascript\n             * var obj = {};\n             *\n             * // 使得obj有事件行为\n             * Mediator.installTo( obj );\n             *\n             * obj.on( 'testa', function( arg1, arg2 ) {\n             *     console.log( arg1, arg2 ); // => 'arg1', 'arg2'\n             * });\n             *\n             * obj.trigger( 'testa', 'arg1', 'arg2' );\n             * ```\n             *\n             * 如果`callback`中，某一个方法`return false`了，则后续的其他`callback`都不会被执行到。\n             * 切会影响到`trigger`方法的返回值，为`false`。\n             *\n             * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处，\n             * 就是第一个参数为`type`，记录当前是什么事件在触发。此类`callback`的优先级比脚低，会再正常`callback`执行完后触发。\n             * ```javascript\n             * obj.on( 'all', function( type, arg1, arg2 ) {\n             *     console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2'\n             * });\n             * ```\n             *\n             * @method on\n             * @grammar on( name, callback[, context] ) => self\n             * @param  {String}   name     事件名，支持多个事件用空格隔开\n             * @param  {Function} callback 事件处理器\n             * @param  {Object}   [context]  事件处理器的上下文。\n             * @return {self} 返回自身，方便链式\n             * @chainable\n             * @class Mediator\n             */\n            on: function( name, callback, context ) {\n                var me = this,\n                    set;\n    \n                if ( !callback ) {\n                    return this;\n                }\n    \n                set = this._events || (this._events = []);\n    \n                eachEvent( name, callback, function( name, callback ) {\n                    var handler = { e: name };\n    \n                    handler.cb = callback;\n                    handler.ctx = context;\n                    handler.ctx2 = context || me;\n                    handler.id = set.length;\n    \n                    set.push( handler );\n                });\n    \n                return this;\n            },\n    \n            /**\n             * 绑定事件，且当handler执行完后，自动解除绑定。\n             * @method once\n             * @grammar once( name, callback[, context] ) => self\n             * @param  {String}   name     事件名\n             * @param  {Function} callback 事件处理器\n             * @param  {Object}   [context]  事件处理器的上下文。\n             * @return {self} 返回自身，方便链式\n             * @chainable\n             */\n            once: function( name, callback, context ) {\n                var me = this;\n    \n                if ( !callback ) {\n                    return me;\n                }\n    \n                eachEvent( name, callback, function( name, callback ) {\n                    var once = function() {\n                            me.off( name, once );\n                            return callback.apply( context || me, arguments );\n                        };\n    \n                    once._cb = callback;\n                    me.on( name, once, context );\n                });\n    \n                return me;\n            },\n    \n            /**\n             * 解除事件绑定\n             * @method off\n             * @grammar off( [name[, callback[, context] ] ] ) => self\n             * @param  {String}   [name]     事件名\n             * @param  {Function} [callback] 事件处理器\n             * @param  {Object}   [context]  事件处理器的上下文。\n             * @return {self} 返回自身，方便链式\n             * @chainable\n             */\n            off: function( name, cb, ctx ) {\n                var events = this._events;\n    \n                if ( !events ) {\n                    return this;\n                }\n    \n                if ( !name && !cb && !ctx ) {\n                    this._events = [];\n                    return this;\n                }\n    \n                eachEvent( name, cb, function( name, cb ) {\n                    $.each( findHandlers( events, name, cb, ctx ), function() {\n                        delete events[ this.id ];\n                    });\n                });\n    \n                return this;\n            },\n    \n            /**\n             * 触发事件\n             * @method trigger\n             * @grammar trigger( name[, args...] ) => self\n             * @param  {String}   type     事件名\n             * @param  {*} [...] 任意参数\n             * @return {Boolean} 如果handler中return false了，则返回false, 否则返回true\n             */\n            trigger: function( type ) {\n                var args, events, allEvents;\n    \n                if ( !this._events || !type ) {\n                    return this;\n                }\n    \n                args = slice.call( arguments, 1 );\n                events = findHandlers( this._events, type );\n                allEvents = findHandlers( this._events, 'all' );\n    \n                return triggerHanders( events, args ) &&\n                        triggerHanders( allEvents, arguments );\n            }\n        };\n    \n        /**\n         * 中介者，它本身是个单例，但可以通过[installTo](#WebUploader:Mediator:installTo)方法，使任何对象具备事件行为。\n         * 主要目的是负责模块与模块之间的合作，降低耦合度。\n         *\n         * @class Mediator\n         */\n        return $.extend({\n    \n            /**\n             * 可以通过这个接口，使任何对象具备事件功能。\n             * @method installTo\n             * @param  {Object} obj 需要具备事件行为的对象。\n             * @return {Object} 返回obj.\n             */\n            installTo: function( obj ) {\n                return $.extend( obj, protos );\n            }\n    \n        }, protos );\n    });\n    /**\n     * @fileOverview Uploader上传类\n     */\n    define('uploader',[\n        'base',\n        'mediator'\n    ], function( Base, Mediator ) {\n    \n        var $ = Base.$;\n    \n        /**\n         * 上传入口类。\n         * @class Uploader\n         * @constructor\n         * @grammar new Uploader( opts ) => Uploader\n         * @example\n         * var uploader = WebUploader.Uploader({\n         *     swf: 'path_of_swf/Uploader.swf',\n         *\n         *     // 开起分片上传。\n         *     chunked: true\n         * });\n         */\n        function Uploader( opts ) {\n            this.options = $.extend( true, {}, Uploader.options, opts );\n            this._init( this.options );\n        }\n    \n        // default Options\n        // widgets中有相应扩展\n        Uploader.options = {};\n        Mediator.installTo( Uploader.prototype );\n    \n        // 批量添加纯命令式方法。\n        $.each({\n            upload: 'start-upload',\n            stop: 'stop-upload',\n            getFile: 'get-file',\n            getFiles: 'get-files',\n            addFile: 'add-file',\n            addFiles: 'add-file',\n            sort: 'sort-files',\n            removeFile: 'remove-file',\n            skipFile: 'skip-file',\n            retry: 'retry',\n            isInProgress: 'is-in-progress',\n            makeThumb: 'make-thumb',\n            getDimension: 'get-dimension',\n            addButton: 'add-btn',\n            getRuntimeType: 'get-runtime-type',\n            refresh: 'refresh',\n            disable: 'disable',\n            enable: 'enable',\n            reset: 'reset'\n        }, function( fn, command ) {\n            Uploader.prototype[ fn ] = function() {\n                return this.request( command, arguments );\n            };\n        });\n    \n        $.extend( Uploader.prototype, {\n            state: 'pending',\n    \n            _init: function( opts ) {\n                var me = this;\n    \n                me.request( 'init', opts, function() {\n                    me.state = 'ready';\n                    me.trigger('ready');\n                });\n            },\n    \n            /**\n             * 获取或者设置Uploader配置项。\n             * @method option\n             * @grammar option( key ) => *\n             * @grammar option( key, val ) => self\n             * @example\n             *\n             * // 初始状态图片上传前不会压缩\n             * var uploader = new WebUploader.Uploader({\n             *     resize: null;\n             * });\n             *\n             * // 修改后图片上传前，尝试将图片压缩到1600 * 1600\n             * uploader.options( 'resize', {\n             *     width: 1600,\n             *     height: 1600\n             * });\n             */\n            option: function( key, val ) {\n                var opts = this.options;\n    \n                // setter\n                if ( arguments.length > 1 ) {\n    \n                    if ( $.isPlainObject( val ) &&\n                            $.isPlainObject( opts[ key ] ) ) {\n                        $.extend( opts[ key ], val );\n                    } else {\n                        opts[ key ] = val;\n                    }\n    \n                } else {    // getter\n                    return key ? opts[ key ] : opts;\n                }\n            },\n    \n            /**\n             * 获取文件统计信息。返回一个包含一下信息的对象。\n             * * `successNum` 上传成功的文件数\n             * * `uploadFailNum` 上传失败的文件数\n             * * `cancelNum` 被删除的文件数\n             * * `invalidNum` 无效的文件数\n             * * `queueNum` 还在队列中的文件数\n             * @method getStats\n             * @grammar getStats() => Object\n             */\n            getStats: function() {\n                // return this._mgr.getStats.apply( this._mgr, arguments );\n                var stats = this.request('get-stats');\n    \n                return {\n                    successNum: stats.numOfSuccess,\n    \n                    // who care?\n                    // queueFailNum: 0,\n                    cancelNum: stats.numOfCancel,\n                    invalidNum: stats.numOfInvalid,\n                    uploadFailNum: stats.numOfUploadFailed,\n                    queueNum: stats.numOfQueue\n                };\n            },\n    \n            // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器\n            trigger: function( type/*, args...*/ ) {\n                var args = [].slice.call( arguments, 1 ),\n                    opts = this.options,\n                    name = 'on' + type.substring( 0, 1 ).toUpperCase() +\n                        type.substring( 1 );\n    \n                if (\n                        // 调用通过on方法注册的handler.\n                        Mediator.trigger.apply( this, arguments ) === false ||\n    \n                        // 调用opts.onEvent\n                        $.isFunction( opts[ name ] ) &&\n                        opts[ name ].apply( this, args ) === false ||\n    \n                        // 调用this.onEvent\n                        $.isFunction( this[ name ] ) &&\n                        this[ name ].apply( this, args ) === false ||\n    \n                        // 广播所有uploader的事件。\n                        Mediator.trigger.apply( Mediator,\n                        [ this, type ].concat( args ) ) === false ) {\n    \n                    return false;\n                }\n    \n                return true;\n            },\n    \n            // widgets/widget.js将补充此方法的详细文档。\n            request: Base.noop\n        });\n    \n        /**\n         * 创建Uploader实例，等同于new Uploader( opts );\n         * @method create\n         * @class Base\n         * @static\n         * @grammar Base.create( opts ) => Uploader\n         */\n        Base.create = Uploader.create = function( opts ) {\n            return new Uploader( opts );\n        };\n    \n        // 暴露Uploader，可以通过它来扩展业务逻辑。\n        Base.Uploader = Uploader;\n    \n        return Uploader;\n    });\n    /**\n     * @fileOverview Runtime管理器，负责Runtime的选择, 连接\n     */\n    define('runtime/runtime',[\n        'base',\n        'mediator'\n    ], function( Base, Mediator ) {\n    \n        var $ = Base.$,\n            factories = {},\n    \n            // 获取对象的第一个key\n            getFirstKey = function( obj ) {\n                for ( var key in obj ) {\n                    if ( obj.hasOwnProperty( key ) ) {\n                        return key;\n                    }\n                }\n                return null;\n            };\n    \n        // 接口类。\n        function Runtime( options ) {\n            this.options = $.extend({\n                container: document.body\n            }, options );\n            this.uid = Base.guid('rt_');\n        }\n    \n        $.extend( Runtime.prototype, {\n    \n            getContainer: function() {\n                var opts = this.options,\n                    parent, container;\n    \n                if ( this._container ) {\n                    return this._container;\n                }\n    \n                parent = $( opts.container || document.body );\n                container = $( document.createElement('div') );\n    \n                container.attr( 'id', 'rt_' + this.uid );\n                container.css({\n                    position: 'absolute',\n                    top: '0px',\n                    left: '0px',\n                    width: '1px',\n                    height: '1px',\n                    overflow: 'hidden'\n                });\n    \n                parent.append( container );\n                parent.addClass('webuploader-container');\n                this._container = container;\n                return container;\n            },\n    \n            init: Base.noop,\n            exec: Base.noop,\n    \n            destroy: function() {\n                if ( this._container ) {\n                    this._container.parentNode.removeChild( this.__container );\n                }\n    \n                this.off();\n            }\n        });\n    \n        Runtime.orders = 'html5,flash';\n    \n    \n        /**\n         * 添加Runtime实现。\n         * @param {String} type    类型\n         * @param {Runtime} factory 具体Runtime实现。\n         */\n        Runtime.addRuntime = function( type, factory ) {\n            factories[ type ] = factory;\n        };\n    \n        Runtime.hasRuntime = function( type ) {\n            return !!(type ? factories[ type ] : getFirstKey( factories ));\n        };\n    \n        Runtime.create = function( opts, orders ) {\n            var type, runtime;\n    \n            orders = orders || Runtime.orders;\n            $.each( orders.split( /\\s*,\\s*/g ), function() {\n                if ( factories[ this ] ) {\n                    type = this;\n                    return false;\n                }\n            });\n    \n            type = type || getFirstKey( factories );\n    \n            if ( !type ) {\n                throw new Error('Runtime Error');\n            }\n    \n            runtime = new factories[ type ]( opts );\n            return runtime;\n        };\n    \n        Mediator.installTo( Runtime.prototype );\n        return Runtime;\n    });\n    \n    /**\n     * @fileOverview Runtime管理器，负责Runtime的选择, 连接\n     */\n    define('runtime/client',[\n        'base',\n        'mediator',\n        'runtime/runtime'\n    ], function( Base, Mediator, Runtime ) {\n    \n        var cache;\n    \n        cache = (function() {\n            var obj = {};\n    \n            return {\n                add: function( runtime ) {\n                    obj[ runtime.uid ] = runtime;\n                },\n    \n                get: function( ruid, standalone ) {\n                    var i;\n    \n                    if ( ruid ) {\n                        return obj[ ruid ];\n                    }\n    \n                    for ( i in obj ) {\n                        // 有些类型不能重用，比如filepicker.\n                        if ( standalone && obj[ i ].__standalone ) {\n                            continue;\n                        }\n    \n                        return obj[ i ];\n                    }\n    \n                    return null;\n                },\n    \n                remove: function( runtime ) {\n                    delete obj[ runtime.uid ];\n                }\n            };\n        })();\n    \n        function RuntimeClient( component, standalone ) {\n            var deferred = Base.Deferred(),\n                runtime;\n    \n            this.uid = Base.guid('client_');\n    \n            // 允许runtime没有初始化之前，注册一些方法在初始化后执行。\n            this.runtimeReady = function( cb ) {\n                return deferred.done( cb );\n            };\n    \n            this.connectRuntime = function( opts, cb ) {\n    \n                // already connected.\n                if ( runtime ) {\n                    throw new Error('already connected!');\n                }\n    \n                deferred.done( cb );\n    \n                if ( typeof opts === 'string' && cache.get( opts ) ) {\n                    runtime = cache.get( opts );\n                }\n    \n                // 像filePicker只能独立存在，不能公用。\n                runtime = runtime || cache.get( null, standalone );\n    \n                // 需要创建\n                if ( !runtime ) {\n                    runtime = Runtime.create( opts, opts.runtimeOrder );\n                    runtime.__promise = deferred.promise();\n                    runtime.once( 'ready', deferred.resolve );\n                    runtime.init();\n                    cache.add( runtime );\n                    runtime.__client = 1;\n                } else {\n                    // 来自cache\n                    Base.$.extend( runtime.options, opts );\n                    runtime.__promise.then( deferred.resolve );\n                    runtime.__client++;\n                }\n    \n                standalone && (runtime.__standalone = standalone);\n                return runtime;\n            };\n    \n            this.getRuntime = function() {\n                return runtime;\n            };\n    \n            this.disconnectRuntime = function() {\n                if ( !runtime ) {\n                    return;\n                }\n    \n                runtime.__client--;\n    \n                if ( runtime.__client <= 0 ) {\n                    cache.remove( runtime );\n                    delete runtime.__promise;\n                    runtime.destroy();\n                }\n    \n                runtime = null;\n            };\n    \n            this.exec = function() {\n                if ( !runtime ) {\n                    return;\n                }\n    \n                var args = Base.slice( arguments );\n                component && args.unshift( component );\n    \n                return runtime.exec.apply( this, args );\n            };\n    \n            this.getRuid = function() {\n                return runtime && runtime.uid;\n            };\n    \n            this.destroy = (function( destroy ) {\n                return function() {\n                    destroy && destroy.apply( this, arguments );\n                    this.trigger('destroy');\n                    this.off();\n                    this.exec('destroy');\n                    this.disconnectRuntime();\n                };\n            })( this.destroy );\n        }\n    \n        Mediator.installTo( RuntimeClient.prototype );\n        return RuntimeClient;\n    });\n    /**\n     * @fileOverview Blob\n     */\n    define('lib/blob',[\n        'base',\n        'runtime/client'\n    ], function( Base, RuntimeClient ) {\n    \n        function Blob( ruid, source ) {\n            var me = this;\n    \n            me.source = source;\n            me.ruid = ruid;\n    \n            RuntimeClient.call( me, 'Blob' );\n    \n            this.uid = source.uid || this.uid;\n            this.type = source.type || '';\n            this.size = source.size || 0;\n    \n            if ( ruid ) {\n                me.connectRuntime( ruid );\n            }\n        }\n    \n        Base.inherits( RuntimeClient, {\n            constructor: Blob,\n    \n            slice: function( start, end ) {\n                return this.exec( 'slice', start, end );\n            },\n    \n            getSource: function() {\n                return this.source;\n            }\n        });\n    \n        return Blob;\n    });\n    /**\n     * 为了统一化Flash的File和HTML5的File而存在。\n     * 以至于要调用Flash里面的File，也可以像调用HTML5版本的File一下。\n     * @fileOverview File\n     */\n    define('lib/file',[\n        'base',\n        'lib/blob'\n    ], function( Base, Blob ) {\n    \n        var uid = 1,\n            rExt = /\\.([^.]+)$/;\n    \n        function File( ruid, file ) {\n            var ext;\n    \n            Blob.apply( this, arguments );\n            this.name = file.name || ('untitled' + uid++);\n            ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : '';\n    \n            // todo 支持其他类型文件的转换。\n    \n            // 如果有mimetype, 但是文件名里面没有找出后缀规律\n            if ( !ext && this.type ) {\n                ext = /\\/(jpg|jpeg|png|gif|bmp)$/i.exec( this.type ) ?\n                        RegExp.$1.toLowerCase() : '';\n                this.name += '.' + ext;\n            }\n    \n            // 如果没有指定mimetype, 但是知道文件后缀。\n            if ( !this.type &&  ~'jpg,jpeg,png,gif,bmp'.indexOf( ext ) ) {\n                this.type = 'image/' + (ext === 'jpg' ? 'jpeg' : ext);\n            }\n    \n            this.ext = ext;\n            this.lastModifiedDate = file.lastModifiedDate ||\n                    (new Date()).toLocaleString();\n        }\n    \n        return Base.inherits( Blob, File );\n    });\n    \n    /**\n     * @fileOverview 错误信息\n     */\n    define('lib/filepicker',[\n        'base',\n        'runtime/client',\n        'lib/file'\n    ], function( Base, RuntimeClent, File ) {\n    \n        var $ = Base.$;\n    \n        function FilePicker( opts ) {\n            opts = this.options = $.extend({}, FilePicker.options, opts );\n    \n            opts.container = $( opts.id );\n    \n            if ( !opts.container.length ) {\n                throw new Error('按钮指定错误');\n            }\n    \n            opts.innerHTML = opts.innerHTML || opts.label ||\n                    opts.container.html() || '';\n    \n            opts.button = $( opts.button || document.createElement('div') );\n            opts.button.html( opts.innerHTML );\n            opts.container.html( opts.button );\n    \n            RuntimeClent.call( this, 'FilePicker', true );\n        }\n    \n        FilePicker.options = {\n            button: null,\n            container: null,\n            label: null,\n            innerHTML: null,\n            multiple: true,\n            accept: null,\n            name: 'file'\n        };\n    \n        Base.inherits( RuntimeClent, {\n            constructor: FilePicker,\n    \n            init: function() {\n                var me = this,\n                    opts = me.options,\n                    button = opts.button;\n    \n                button.addClass('webuploader-pick');\n    \n                me.on( 'all', function( type ) {\n                    var files;\n    \n                    switch ( type ) {\n                        case 'mouseenter':\n                            button.addClass('webuploader-pick-hover');\n                            break;\n    \n                        case 'mouseleave':\n                            button.removeClass('webuploader-pick-hover');\n                            break;\n    \n                        case 'change':\n                            files = me.exec('getFiles');\n                            me.trigger( 'select', $.map( files, function( file ) {\n                                file = new File( me.getRuid(), file );\n    \n                                // 记录来源。\n                                file._refer = opts.container;\n                                return file;\n                            }), opts.container );\n                            break;\n                    }\n                });\n    \n                me.connectRuntime( opts, function() {\n                    me.refresh();\n                    me.exec( 'init', opts );\n                    me.trigger('ready');\n                });\n    \n                $( window ).on( 'resize', function() {\n                    me.refresh();\n                });\n            },\n    \n            refresh: function() {\n                var shimContainer = this.getRuntime().getContainer(),\n                    button = this.options.button,\n                    width = button.outerWidth ?\n                            button.outerWidth() : button.width(),\n    \n                    height = button.outerHeight ?\n                            button.outerHeight() : button.height(),\n    \n                    pos = button.offset();\n    \n                width && height && shimContainer.css({\n                    bottom: 'auto',\n                    right: 'auto',\n                    width: width + 'px',\n                    height: height + 'px'\n                }).offset( pos );\n            },\n    \n            enable: function() {\n                var btn = this.options.button;\n    \n                btn.removeClass('webuploader-pick-disable');\n                this.refresh();\n            },\n    \n            disable: function() {\n                var btn = this.options.button;\n    \n                this.getRuntime().getContainer().css({\n                    top: '-99999px'\n                });\n    \n                btn.addClass('webuploader-pick-disable');\n            },\n    \n            destroy: function() {\n                if ( this.runtime ) {\n                    this.exec('destroy');\n                    this.disconnectRuntime();\n                }\n            }\n        });\n    \n        return FilePicker;\n    });\n    \n    /**\n     * @fileOverview 组件基类。\n     */\n    define('widgets/widget',[\n        'base',\n        'uploader'\n    ], function( Base, Uploader ) {\n    \n        var $ = Base.$,\n            _init = Uploader.prototype._init,\n            IGNORE = {},\n            widgetClass = [];\n    \n        function isArrayLike( obj ) {\n            if ( !obj ) {\n                return false;\n            }\n    \n            var length = obj.length,\n                type = $.type( obj );\n    \n            if ( obj.nodeType === 1 && length ) {\n                return true;\n            }\n    \n            return type === 'array' || type !== 'function' && type !== 'string' &&\n                    (length === 0 || typeof length === 'number' && length > 0 &&\n                    (length - 1) in obj);\n        }\n    \n        function Widget( uploader ) {\n            this.owner = uploader;\n            this.options = uploader.options;\n        }\n    \n        $.extend( Widget.prototype, {\n    \n            init: Base.noop,\n    \n            // 类Backbone的事件监听声明，监听uploader实例上的事件\n            // widget直接无法监听事件，事件只能通过uploader来传递\n            invoke: function( apiName, args ) {\n    \n                /*\n                    {\n                        'make-thumb': 'makeThumb'\n                    }\n                 */\n                var map = this.responseMap;\n    \n                // 如果无API响应声明则忽略\n                if ( !map || !(apiName in map) || !(map[ apiName ] in this) ||\n                        !$.isFunction( this[ map[ apiName ] ] ) ) {\n    \n                    return IGNORE;\n                }\n    \n                return this[ map[ apiName ] ].apply( this, args );\n    \n            },\n    \n            /**\n             * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。\n             * @method request\n             * @grammar request( command, args ) => * | Promise\n             * @grammar request( command, args, callback ) => Promise\n             * @for  Uploader\n             */\n            request: function() {\n                return this.owner.request.apply( this.owner, arguments );\n            }\n        });\n    \n        // 扩展Uploader.\n        $.extend( Uploader.prototype, {\n    \n            // 覆写_init用来初始化widgets\n            _init: function() {\n                var me = this,\n                    widgets = me._widgets = [];\n    \n                $.each( widgetClass, function( _, klass ) {\n                    widgets.push( new klass( me ) );\n                });\n    \n                return _init.apply( me, arguments );\n            },\n    \n            request: function( apiName, args, callback ) {\n                var i = 0,\n                    widgets = this._widgets,\n                    len = widgets.length,\n                    rlts = [],\n                    dfds = [],\n                    widget, rlt, promise, key;\n    \n                args = isArrayLike( args ) ? args : [ args ];\n    \n                for ( ; i < len; i++ ) {\n                    widget = widgets[ i ];\n                    rlt = widget.invoke( apiName, args );\n    \n                    if ( rlt !== IGNORE ) {\n    \n                        // Deferred对象\n                        if ( Base.isPromise( rlt ) ) {\n                            dfds.push( rlt );\n                        } else {\n                            rlts.push( rlt );\n                        }\n                    }\n                }\n    \n                // 如果有callback，则用异步方式。\n                if ( callback || dfds.length ) {\n                    promise = Base.when.apply( Base, dfds );\n                    key = promise.pipe ? 'pipe' : 'then';\n    \n                    // 很重要不能删除。删除了会死循环。\n                    // 保证执行顺序。让callback总是在下一个tick中执行。\n                    return promise[ key ](function() {\n                                var deferred = Base.Deferred(),\n                                    args = arguments;\n    \n                                setTimeout(function() {\n                                    deferred.resolve.apply( deferred, args );\n                                }, 1 );\n    \n                                return deferred.promise();\n                            })[ key ]( callback || Base.noop );\n                } else {\n                    return rlts[ 0 ];\n                }\n            }\n        });\n    \n        /**\n         * 添加组件\n         * @param  {object} widgetProto 组件原型，构造函数通过constructor属性定义\n         * @param  {object} responseMap API名称与函数实现的映射\n         * @example\n         *     Uploader.register( {\n         *         init: function( options ) {},\n         *         makeThumb: function() {}\n         *     }, {\n         *         'make-thumb': 'makeThumb'\n         *     } );\n         */\n        Uploader.register = Widget.register = function( responseMap, widgetProto ) {\n            var map = { init: 'init' },\n                klass;\n    \n            if ( arguments.length === 1 ) {\n                widgetProto = responseMap;\n                widgetProto.responseMap = map;\n            } else {\n                widgetProto.responseMap = $.extend( map, responseMap );\n            }\n    \n            klass = Base.inherits( Widget, widgetProto );\n            widgetClass.push( klass );\n    \n            return klass;\n        };\n    \n        return Widget;\n    });\n    /**\n     * @fileOverview 文件选择相关\n     */\n    define('widgets/filepicker',[\n        'base',\n        'uploader',\n        'lib/filepicker',\n        'widgets/widget'\n    ], function( Base, Uploader, FilePicker ) {\n        var $ = Base.$;\n    \n        $.extend( Uploader.options, {\n    \n            /**\n             * @property {Selector | Object} [pick=undefined]\n             * @namespace options\n             * @for Uploader\n             * @description 指定选择文件的按钮容器，不指定则不创建按钮。\n             *\n             * * `id` {Seletor} 指定选择文件的按钮容器，不指定则不创建按钮。\n             * * `label` {String} 请采用 `innerHTML` 代替\n             * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。\n             * * `multiple` {Boolean} 是否开起同时选择多个文件能力。\n             */\n            pick: null,\n    \n            /**\n             * @property {Arroy} [accept=null]\n             * @namespace options\n             * @for Uploader\n             * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表，所以这里需要分开指定。\n             *\n             * * `title` {String} 文字描述\n             * * `extensions` {String} 允许的文件后缀，不带点，多个用逗号分割。\n             * * `mimeTypes` {String} 多个用逗号分割。\n             *\n             * 如：\n             *\n             * ```\n             * {\n             *     title: 'Images',\n             *     extensions: 'gif,jpg,jpeg,bmp,png',\n             *     mimeTypes: 'image/*'\n             * }\n             * ```\n             */\n            accept: null/*{\n                title: 'Images',\n                extensions: 'gif,jpg,jpeg,bmp,png',\n                mimeTypes: 'image/*'\n            }*/\n        });\n    \n        return Uploader.register({\n            'add-btn': 'addButton',\n            refresh: 'refresh',\n            disable: 'disable',\n            enable: 'enable'\n        }, {\n    \n            init: function( opts ) {\n                this.pickers = [];\n                return opts.pick && this.addButton( opts.pick );\n            },\n    \n            refresh: function() {\n                $.each( this.pickers, function() {\n                    this.refresh();\n                });\n            },\n    \n            /**\n             * @method addButton\n             * @for Uploader\n             * @grammar addButton( pick ) => Promise\n             * @description\n             * 添加文件选择按钮，如果一个按钮不够，需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。\n             * @example\n             * uploader.addButton({\n             *     id: '#btnContainer',\n             *     innerHTML: '选择文件'\n             * });\n             */\n            addButton: function( pick ) {\n                var me = this,\n                    opts = me.options,\n                    accept = opts.accept,\n                    options, picker, deferred;\n    \n                if ( !pick ) {\n                    return;\n                }\n    \n                deferred = Base.Deferred();\n                $.isPlainObject( pick ) || (pick = {\n                    id: pick\n                });\n    \n                options = $.extend({}, pick, {\n                    accept: $.isPlainObject( accept ) ? [ accept ] : accept,\n                    swf: opts.swf,\n                    runtimeOrder: opts.runtimeOrder\n                });\n    \n                picker = new FilePicker( options );\n    \n                picker.once( 'ready', deferred.resolve );\n                picker.on( 'select', function( files ) {\n                    me.owner.request( 'add-file', [ files ]);\n                });\n                picker.init();\n    \n                this.pickers.push( picker );\n    \n                return deferred.promise();\n            },\n    \n            disable: function() {\n                $.each( this.pickers, function() {\n                    this.disable();\n                });\n            },\n    \n            enable: function() {\n                $.each( this.pickers, function() {\n                    this.enable();\n                });\n            }\n        });\n    });\n    /**\n     * @fileOverview Image\n     */\n    define('lib/image',[\n        'base',\n        'runtime/client',\n        'lib/blob'\n    ], function( Base, RuntimeClient, Blob ) {\n        var $ = Base.$;\n    \n        // 构造器。\n        function Image( opts ) {\n            this.options = $.extend({}, Image.options, opts );\n            RuntimeClient.call( this, 'Image' );\n    \n            this.on( 'load', function() {\n                this._info = this.exec('info');\n                this._meta = this.exec('meta');\n            });\n        }\n    \n        // 默认选项。\n        Image.options = {\n    \n            // 默认的图片处理质量\n            quality: 90,\n    \n            // 是否裁剪\n            crop: false,\n    \n            // 是否保留头部信息\n            preserveHeaders: true,\n    \n            // 是否允许放大。\n            allowMagnify: true\n        };\n    \n        // 继承RuntimeClient.\n        Base.inherits( RuntimeClient, {\n            constructor: Image,\n    \n            info: function( val ) {\n    \n                // setter\n                if ( val ) {\n                    this._info = val;\n                    return this;\n                }\n    \n                // getter\n                return this._info;\n            },\n    \n            meta: function( val ) {\n    \n                // setter\n                if ( val ) {\n                    this._meta = val;\n                    return this;\n                }\n    \n                // getter\n                return this._meta;\n            },\n    \n            loadFromBlob: function( blob ) {\n                var me = this,\n                    ruid = blob.getRuid();\n    \n                this.connectRuntime( ruid, function() {\n                    me.exec( 'init', me.options );\n                    me.exec( 'loadFromBlob', blob );\n                });\n            },\n    \n            resize: function() {\n                var args = Base.slice( arguments );\n                return this.exec.apply( this, [ 'resize' ].concat( args ) );\n            },\n    \n            getAsDataUrl: function( type ) {\n                return this.exec( 'getAsDataUrl', type );\n            },\n    \n            getAsBlob: function( type ) {\n                var blob = this.exec( 'getAsBlob', type );\n    \n                return new Blob( this.getRuid(), blob );\n            }\n        });\n    \n        return Image;\n    });\n    /**\n     * @fileOverview 图片操作, 负责预览图片和上传前压缩图片\n     */\n    define('widgets/image',[\n        'base',\n        'uploader',\n        'lib/image',\n        'widgets/widget'\n    ], function( Base, Uploader, Image ) {\n    \n        var $ = Base.$,\n            throttle;\n    \n        // 根据要处理的文件大小来节流，一次不能处理太多，会卡。\n        throttle = (function( max ) {\n            var occupied = 0,\n                waiting = [],\n                tick = function() {\n                    var item;\n    \n                    while ( waiting.length && occupied < max ) {\n                        item = waiting.shift();\n                        occupied += item[ 0 ];\n                        item[ 1 ]();\n                    }\n                };\n    \n            return function( emiter, size, cb ) {\n                waiting.push([ size, cb ]);\n                emiter.once( 'destroy', function() {\n                    occupied -= size;\n                    setTimeout( tick, 1 );\n                });\n                setTimeout( tick, 1 );\n            };\n        })( 5 * 1024 * 1024 );\n    \n        $.extend( Uploader.options, {\n    \n            /**\n             * @property {Object} [thumb]\n             * @namespace options\n             * @for Uploader\n             * @description 配置生成缩略图的选项。\n             *\n             * 默认为：\n             *\n             * ```javascript\n             * {\n             *     width: 110,\n             *     height: 110,\n             *\n             *     // 图片质量，只有type为`image/jpeg`的时候才有效。\n             *     quality: 70,\n             *\n             *     // 是否允许放大，如果想要生成小图的时候不失真，此选项应该设置为false.\n             *     allowMagnify: true,\n             *\n             *     // 是否允许裁剪。\n             *     crop: true,\n             *\n             *     // 是否保留头部meta信息。\n             *     preserveHeaders: false,\n             *\n             *     // 为空的话则保留原有图片格式。\n             *     // 否则强制转换成指定的类型。\n             *     type: 'image/jpeg'\n             * }\n             * ```\n             */\n            thumb: {\n                width: 110,\n                height: 110,\n                quality: 70,\n                allowMagnify: true,\n                crop: true,\n                preserveHeaders: false,\n    \n                // 为空的话则保留原有图片格式。\n                // 否则强制转换成指定的类型。\n                // IE 8下面 base64 大小不能超过 32K 否则预览失败，而非 jpeg 编码的图片很可\n                // 能会超过 32k, 所以这里设置成预览的时候都是 image/jpeg\n                type: 'image/jpeg'\n            },\n    \n            /**\n             * @property {Object} [compress]\n             * @namespace options\n             * @for Uploader\n             * @description 配置压缩的图片的选项。如果此选项为`false`, 则图片在上传前不进行压缩。\n             *\n             * 默认为：\n             *\n             * ```javascript\n             * {\n             *     width: 1600,\n             *     height: 1600,\n             *\n             *     // 图片质量，只有type为`image/jpeg`的时候才有效。\n             *     quality: 90,\n             *\n             *     // 是否允许放大，如果想要生成小图的时候不失真，此选项应该设置为false.\n             *     allowMagnify: false,\n             *\n             *     // 是否允许裁剪。\n             *     crop: false,\n             *\n             *     // 是否保留头部meta信息。\n             *     preserveHeaders: true\n             * }\n             * ```\n             */\n            compress: {\n                width: 1600,\n                height: 1600,\n                quality: 90,\n                allowMagnify: false,\n                crop: false,\n                preserveHeaders: true\n            }\n        });\n    \n        return Uploader.register({\n            'make-thumb': 'makeThumb',\n            'before-send-file': 'compressImage'\n        }, {\n    \n    \n            /**\n             * 生成缩略图，此过程为异步，所以需要传入`callback`。\n             * 通常情况在图片加入队里后调用此方法来生成预览图以增强交互效果。\n             *\n             * `callback`中可以接收到两个参数。\n             * * 第一个为error，如果生成缩略图有错误，此error将为真。\n             * * 第二个为ret, 缩略图的Data URL值。\n             *\n             * **注意**\n             * Date URL在IE6/7中不支持，所以不用调用此方法了，直接显示一张暂不支持预览图片好了。\n             *\n             *\n             * @method makeThumb\n             * @grammar makeThumb( file, callback ) => undefined\n             * @grammar makeThumb( file, callback, width, height ) => undefined\n             * @for Uploader\n             * @example\n             *\n             * uploader.on( 'fileQueued', function( file ) {\n             *     var $li = ...;\n             *\n             *     uploader.makeThumb( file, function( error, ret ) {\n             *         if ( error ) {\n             *             $li.text('预览错误');\n             *         } else {\n             *             $li.append('<img alt=\"\" src=\"' + ret + '\" />');\n             *         }\n             *     });\n             *\n             * });\n             */\n            makeThumb: function( file, cb, width, height ) {\n                var opts, image;\n    \n                file = this.request( 'get-file', file );\n    \n                // 只预览图片格式。\n                if ( !file.type.match( /^image/ ) ) {\n                    cb( true );\n                    return;\n                }\n    \n                opts = $.extend({}, this.options.thumb );\n    \n                // 如果传入的是object.\n                if ( $.isPlainObject( width ) ) {\n                    opts = $.extend( opts, width );\n                    width = null;\n                }\n    \n                width = width || opts.width;\n                height = height || opts.height;\n    \n                image = new Image( opts );\n    \n                image.once( 'load', function() {\n                    file._info = file._info || image.info();\n                    file._meta = file._meta || image.meta();\n                    image.resize( width, height );\n                });\n    \n                image.once( 'complete', function() {\n                    cb( false, image.getAsDataUrl( opts.type ) );\n                    image.destroy();\n                });\n    \n                image.once( 'error', function() {\n                    cb( true );\n                    image.destroy();\n                });\n    \n                throttle( image, file.source.size, function() {\n                    file._info && image.info( file._info );\n                    file._meta && image.meta( file._meta );\n                    image.loadFromBlob( file.source );\n                });\n            },\n    \n            compressImage: function( file ) {\n                var opts = this.options.compress || this.options.resize,\n                    compressSize = opts && opts.compressSize || 300 * 1024,\n                    image, deferred;\n    \n                file = this.request( 'get-file', file );\n    \n                // 只预览图片格式。\n                if ( !opts || !~'image/jpeg,image/jpg'.indexOf( file.type ) ||\n                        file.size < compressSize ||\n                        file._compressed ) {\n                    return;\n                }\n    \n                opts = $.extend({}, opts );\n                deferred = Base.Deferred();\n    \n                image = new Image( opts );\n    \n                deferred.always(function() {\n                    image.destroy();\n                    image = null;\n                });\n                image.once( 'error', deferred.reject );\n                image.once( 'load', function() {\n                    file._info = file._info || image.info();\n                    file._meta = file._meta || image.meta();\n                    image.resize( opts.width, opts.height );\n                });\n    \n                image.once( 'complete', function() {\n                    var blob, size;\n    \n                    // 移动端 UC / qq 浏览器的无图模式下\n                    // ctx.getImageData 处理大图的时候会报 Exception\n                    // INDEX_SIZE_ERR: DOM Exception 1\n                    try {\n                        blob = image.getAsBlob( opts.type );\n    \n                        size = file.size;\n    \n                        // 如果压缩后，比原来还大则不用压缩后的。\n                        if ( blob.size < size ) {\n                            // file.source.destroy && file.source.destroy();\n                            file.source = blob;\n                            file.size = blob.size;\n    \n                            file.trigger( 'resize', blob.size, size );\n                        }\n    \n                        // 标记，避免重复压缩。\n                        file._compressed = true;\n                        deferred.resolve();\n                    } catch ( e ) {\n                        // 出错了直接继续，让其上传原始图片\n                        deferred.resolve();\n                    }\n                });\n    \n                file._info && image.info( file._info );\n                file._meta && image.meta( file._meta );\n    \n                image.loadFromBlob( file.source );\n                return deferred.promise();\n            }\n        });\n    });\n    /**\n     * @fileOverview 文件属性封装\n     */\n    define('file',[\n        'base',\n        'mediator'\n    ], function( Base, Mediator ) {\n    \n        var $ = Base.$,\n            idPrefix = 'WU_FILE_',\n            idSuffix = 0,\n            rExt = /\\.([^.]+)$/,\n            statusMap = {};\n    \n        function gid() {\n            return idPrefix + idSuffix++;\n        }\n    \n        /**\n         * 文件类\n         * @class File\n         * @constructor 构造函数\n         * @grammar new File( source ) => File\n         * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。\n         */\n        function WUFile( source ) {\n    \n            /**\n             * 文件名，包括扩展名（后缀）\n             * @property name\n             * @type {string}\n             */\n            this.name = source.name || 'Untitled';\n    \n            /**\n             * 文件体积（字节）\n             * @property size\n             * @type {uint}\n             * @default 0\n             */\n            this.size = source.size || 0;\n    \n            /**\n             * 文件MIMETYPE类型，与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny)\n             * @property type\n             * @type {string}\n             * @default 'application'\n             */\n            this.type = source.type || 'application';\n    \n            /**\n             * 文件最后修改日期\n             * @property lastModifiedDate\n             * @type {int}\n             * @default 当前时间戳\n             */\n            this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1);\n    \n            /**\n             * 文件ID，每个对象具有唯一ID，与文件名无关\n             * @property id\n             * @type {string}\n             */\n            this.id = gid();\n    \n            /**\n             * 文件扩展名，通过文件名获取，例如test.png的扩展名为png\n             * @property ext\n             * @type {string}\n             */\n            this.ext = rExt.exec( this.name ) ? RegExp.$1 : '';\n    \n    \n            /**\n             * 状态文字说明。在不同的status语境下有不同的用途。\n             * @property statusText\n             * @type {string}\n             */\n            this.statusText = '';\n    \n            // 存储文件状态，防止通过属性直接修改\n            statusMap[ this.id ] = WUFile.Status.INITED;\n    \n            this.source = source;\n            this.loaded = 0;\n    \n            this.on( 'error', function( msg ) {\n                this.setStatus( WUFile.Status.ERROR, msg );\n            });\n        }\n    \n        $.extend( WUFile.prototype, {\n    \n            /**\n             * 设置状态，状态变化时会触发`change`事件。\n             * @method setStatus\n             * @grammar setStatus( status[, statusText] );\n             * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status)\n             * @param {String} [statusText=''] 状态说明，常在error时使用，用http, abort,server等来标记是由于什么原因导致文件错误。\n             */\n            setStatus: function( status, text ) {\n    \n                var prevStatus = statusMap[ this.id ];\n    \n                typeof text !== 'undefined' && (this.statusText = text);\n    \n                if ( status !== prevStatus ) {\n                    statusMap[ this.id ] = status;\n                    /**\n                     * 文件状态变化\n                     * @event statuschange\n                     */\n                    this.trigger( 'statuschange', status, prevStatus );\n                }\n    \n            },\n    \n            /**\n             * 获取文件状态\n             * @return {File.Status}\n             * @example\n                     文件状态具体包括以下几种类型：\n                     {\n                         // 初始化\n                        INITED:     0,\n                        // 已入队列\n                        QUEUED:     1,\n                        // 正在上传\n                        PROGRESS:     2,\n                        // 上传出错\n                        ERROR:         3,\n                        // 上传成功\n                        COMPLETE:     4,\n                        // 上传取消\n                        CANCELLED:     5\n                    }\n             */\n            getStatus: function() {\n                return statusMap[ this.id ];\n            },\n    \n            /**\n             * 获取文件原始信息。\n             * @return {*}\n             */\n            getSource: function() {\n                return this.source;\n            },\n    \n            destory: function() {\n                delete statusMap[ this.id ];\n            }\n        });\n    \n        Mediator.installTo( WUFile.prototype );\n    \n        /**\n         * 文件状态值，具体包括以下几种类型：\n         * * `inited` 初始状态\n         * * `queued` 已经进入队列, 等待上传\n         * * `progress` 上传中\n         * * `complete` 上传完成。\n         * * `error` 上传出错，可重试\n         * * `interrupt` 上传中断，可续传。\n         * * `invalid` 文件不合格，不能重试上传。会自动从队列中移除。\n         * * `cancelled` 文件被移除。\n         * @property {Object} Status\n         * @namespace File\n         * @class File\n         * @static\n         */\n        WUFile.Status = {\n            INITED:     'inited',    // 初始状态\n            QUEUED:     'queued',    // 已经进入队列, 等待上传\n            PROGRESS:   'progress',    // 上传中\n            ERROR:      'error',    // 上传出错，可重试\n            COMPLETE:   'complete',    // 上传完成。\n            CANCELLED:  'cancelled',    // 上传取消。\n            INTERRUPT:  'interrupt',    // 上传中断，可续传。\n            INVALID:    'invalid'    // 文件不合格，不能重试上传。\n        };\n    \n        return WUFile;\n    });\n    \n    /**\n     * @fileOverview 文件队列\n     */\n    define('queue',[\n        'base',\n        'mediator',\n        'file'\n    ], function( Base, Mediator, WUFile ) {\n    \n        var $ = Base.$,\n            STATUS = WUFile.Status;\n    \n        /**\n         * 文件队列, 用来存储各个状态中的文件。\n         * @class Queue\n         * @extends Mediator\n         */\n        function Queue() {\n    \n            /**\n             * 统计文件数。\n             * * `numOfQueue` 队列中的文件数。\n             * * `numOfSuccess` 上传成功的文件数\n             * * `numOfCancel` 被移除的文件数\n             * * `numOfProgress` 正在上传中的文件数\n             * * `numOfUploadFailed` 上传错误的文件数。\n             * * `numOfInvalid` 无效的文件数。\n             * @property {Object} stats\n             */\n            this.stats = {\n                numOfQueue: 0,\n                numOfSuccess: 0,\n                numOfCancel: 0,\n                numOfProgress: 0,\n                numOfUploadFailed: 0,\n                numOfInvalid: 0\n            };\n    \n            // 上传队列，仅包括等待上传的文件\n            this._queue = [];\n    \n            // 存储所有文件\n            this._map = {};\n        }\n    \n        $.extend( Queue.prototype, {\n    \n            /**\n             * 将新文件加入对队列尾部\n             *\n             * @method append\n             * @param  {File} file   文件对象\n             */\n            append: function( file ) {\n                this._queue.push( file );\n                this._fileAdded( file );\n                return this;\n            },\n    \n            /**\n             * 将新文件加入对队列头部\n             *\n             * @method prepend\n             * @param  {File} file   文件对象\n             */\n            prepend: function( file ) {\n                this._queue.unshift( file );\n                this._fileAdded( file );\n                return this;\n            },\n    \n            /**\n             * 获取文件对象\n             *\n             * @method getFile\n             * @param  {String} fileId   文件ID\n             * @return {File}\n             */\n            getFile: function( fileId ) {\n                if ( typeof fileId !== 'string' ) {\n                    return fileId;\n                }\n                return this._map[ fileId ];\n            },\n    \n            /**\n             * 从队列中取出一个指定状态的文件。\n             * @grammar fetch( status ) => File\n             * @method fetch\n             * @param {String} status [文件状态值](#WebUploader:File:File.Status)\n             * @return {File} [File](#WebUploader:File)\n             */\n            fetch: function( status ) {\n                var len = this._queue.length,\n                    i, file;\n    \n                status = status || STATUS.QUEUED;\n    \n                for ( i = 0; i < len; i++ ) {\n                    file = this._queue[ i ];\n    \n                    if ( status === file.getStatus() ) {\n                        return file;\n                    }\n                }\n    \n                return null;\n            },\n    \n            /**\n             * 对队列进行排序，能够控制文件上传顺序。\n             * @grammar sort( fn ) => undefined\n             * @method sort\n             * @param {Function} fn 排序方法\n             */\n            sort: function( fn ) {\n                if ( typeof fn === 'function' ) {\n                    this._queue.sort( fn );\n                }\n            },\n    \n            /**\n             * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。\n             * @grammar getFiles( [status1[, status2 ...]] ) => Array\n             * @method getFiles\n             * @param {String} [status] [文件状态值](#WebUploader:File:File.Status)\n             */\n            getFiles: function() {\n                var sts = [].slice.call( arguments, 0 ),\n                    ret = [],\n                    i = 0,\n                    len = this._queue.length,\n                    file;\n    \n                for ( ; i < len; i++ ) {\n                    file = this._queue[ i ];\n    \n                    if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) {\n                        continue;\n                    }\n    \n                    ret.push( file );\n                }\n    \n                return ret;\n            },\n    \n            _fileAdded: function( file ) {\n                var me = this,\n                    existing = this._map[ file.id ];\n    \n                if ( !existing ) {\n                    this._map[ file.id ] = file;\n    \n                    file.on( 'statuschange', function( cur, pre ) {\n                        me._onFileStatusChange( cur, pre );\n                    });\n                }\n    \n                file.setStatus( STATUS.QUEUED );\n            },\n    \n            _onFileStatusChange: function( curStatus, preStatus ) {\n                var stats = this.stats;\n    \n                switch ( preStatus ) {\n                    case STATUS.PROGRESS:\n                        stats.numOfProgress--;\n                        break;\n    \n                    case STATUS.QUEUED:\n                        stats.numOfQueue --;\n                        break;\n    \n                    case STATUS.ERROR:\n                        stats.numOfUploadFailed--;\n                        break;\n    \n                    case STATUS.INVALID:\n                        stats.numOfInvalid--;\n                        break;\n                }\n    \n                switch ( curStatus ) {\n                    case STATUS.QUEUED:\n                        stats.numOfQueue++;\n                        break;\n    \n                    case STATUS.PROGRESS:\n                        stats.numOfProgress++;\n                        break;\n    \n                    case STATUS.ERROR:\n                        stats.numOfUploadFailed++;\n                        break;\n    \n                    case STATUS.COMPLETE:\n                        stats.numOfSuccess++;\n                        break;\n    \n                    case STATUS.CANCELLED:\n                        stats.numOfCancel++;\n                        break;\n    \n                    case STATUS.INVALID:\n                        stats.numOfInvalid++;\n                        break;\n                }\n            }\n    \n        });\n    \n        Mediator.installTo( Queue.prototype );\n    \n        return Queue;\n    });\n    /**\n     * @fileOverview 队列\n     */\n    define('widgets/queue',[\n        'base',\n        'uploader',\n        'queue',\n        'file',\n        'lib/file',\n        'runtime/client',\n        'widgets/widget'\n    ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) {\n    \n        var $ = Base.$,\n            rExt = /\\.\\w+$/,\n            Status = WUFile.Status;\n    \n        return Uploader.register({\n            'sort-files': 'sortFiles',\n            'add-file': 'addFiles',\n            'get-file': 'getFile',\n            'fetch-file': 'fetchFile',\n            'get-stats': 'getStats',\n            'get-files': 'getFiles',\n            'remove-file': 'removeFile',\n            'retry': 'retry',\n            'reset': 'reset',\n            'accept-file': 'acceptFile'\n        }, {\n    \n            init: function( opts ) {\n                var me = this,\n                    deferred, len, i, item, arr, accept, runtime;\n    \n                if ( $.isPlainObject( opts.accept ) ) {\n                    opts.accept = [ opts.accept ];\n                }\n    \n                // accept中的中生成匹配正则。\n                if ( opts.accept ) {\n                    arr = [];\n    \n                    for ( i = 0, len = opts.accept.length; i < len; i++ ) {\n                        item = opts.accept[ i ].extensions;\n                        item && arr.push( item );\n                    }\n    \n                    if ( arr.length ) {\n                        accept = '\\\\.' + arr.join(',')\n                                .replace( /,/g, '$|\\\\.' )\n                                .replace( /\\*/g, '.*' ) + '$';\n                    }\n    \n                    me.accept = new RegExp( accept, 'i' );\n                }\n    \n                me.queue = new Queue();\n                me.stats = me.queue.stats;\n    \n                // 如果当前不是html5运行时，那就算了。\n                // 不执行后续操作\n                if ( this.request('predict-runtime-type') !== 'html5' ) {\n                    return;\n                }\n    \n                // 创建一个 html5 运行时的 placeholder\n                // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。\n                deferred = Base.Deferred();\n                runtime = new RuntimeClient('Placeholder');\n                runtime.connectRuntime({\n                    runtimeOrder: 'html5'\n                }, function() {\n                    me._ruid = runtime.getRuid();\n                    deferred.resolve();\n                });\n                return deferred.promise();\n            },\n    \n    \n            // 为了支持外部直接添加一个原生File对象。\n            _wrapFile: function( file ) {\n                if ( !(file instanceof WUFile) ) {\n    \n                    if ( !(file instanceof File) ) {\n                        if ( !this._ruid ) {\n                            throw new Error('Can\\'t add external files.');\n                        }\n                        file = new File( this._ruid, file );\n                    }\n    \n                    file = new WUFile( file );\n                }\n    \n                return file;\n            },\n    \n            // 判断文件是否可以被加入队列\n            acceptFile: function( file ) {\n                var invalid = !file || file.size < 6 || this.accept &&\n    \n                        // 如果名字中有后缀，才做后缀白名单处理。\n                        rExt.exec( file.name ) && !this.accept.test( file.name );\n    \n                return !invalid;\n            },\n    \n    \n            /**\n             * @event beforeFileQueued\n             * @param {File} file File对象\n             * @description 当文件被加入队列之前触发，此事件的handler返回值为`false`，则此文件不会被添加进入队列。\n             * @for  Uploader\n             */\n    \n            /**\n             * @event fileQueued\n             * @param {File} file File对象\n             * @description 当文件被加入队列以后触发。\n             * @for  Uploader\n             */\n    \n            _addFile: function( file ) {\n                var me = this;\n    \n                file = me._wrapFile( file );\n    \n                // 不过类型判断允许不允许，先派送 `beforeFileQueued`\n                if ( !me.owner.trigger( 'beforeFileQueued', file ) ) {\n                    return;\n                }\n    \n                // 类型不匹配，则派送错误事件，并返回。\n                if ( !me.acceptFile( file ) ) {\n                    me.owner.trigger( 'error', 'Q_TYPE_DENIED', file );\n                    return;\n                }\n    \n                me.queue.append( file );\n                me.owner.trigger( 'fileQueued', file );\n                return file;\n            },\n    \n            getFile: function( fileId ) {\n                return this.queue.getFile( fileId );\n            },\n    \n            /**\n             * @event filesQueued\n             * @param {File} files 数组，内容为原始File(lib/File）对象。\n             * @description 当一批文件添加进队列以后触发。\n             * @for  Uploader\n             */\n    \n            /**\n             * @method addFiles\n             * @grammar addFiles( file ) => undefined\n             * @grammar addFiles( [file1, file2 ...] ) => undefined\n             * @param {Array of File or File} [files] Files 对象 数组\n             * @description 添加文件到队列\n             * @for  Uploader\n             */\n            addFiles: function( files ) {\n                var me = this;\n    \n                if ( !files.length ) {\n                    files = [ files ];\n                }\n    \n                files = $.map( files, function( file ) {\n                    return me._addFile( file );\n                });\n    \n                me.owner.trigger( 'filesQueued', files );\n    \n                if ( me.options.auto ) {\n                    me.request('start-upload');\n                }\n            },\n    \n            getStats: function() {\n                return this.stats;\n            },\n    \n            /**\n             * @event fileDequeued\n             * @param {File} file File对象\n             * @description 当文件被移除队列后触发。\n             * @for  Uploader\n             */\n    \n            /**\n             * @method removeFile\n             * @grammar removeFile( file ) => undefined\n             * @grammar removeFile( id ) => undefined\n             * @param {File|id} file File对象或这File对象的id\n             * @description 移除某一文件。\n             * @for  Uploader\n             * @example\n             *\n             * $li.on('click', '.remove-this', function() {\n             *     uploader.removeFile( file );\n             * })\n             */\n            removeFile: function( file ) {\n                var me = this;\n    \n                file = file.id ? file : me.queue.getFile( file );\n    \n                file.setStatus( Status.CANCELLED );\n                me.owner.trigger( 'fileDequeued', file );\n            },\n    \n            /**\n             * @method getFiles\n             * @grammar getFiles() => Array\n             * @grammar getFiles( status1, status2, status... ) => Array\n             * @description 返回指定状态的文件集合，不传参数将返回所有状态的文件。\n             * @for  Uploader\n             * @example\n             * console.log( uploader.getFiles() );    // => all files\n             * console.log( uploader.getFiles('error') )    // => all error files.\n             */\n            getFiles: function() {\n                return this.queue.getFiles.apply( this.queue, arguments );\n            },\n    \n            fetchFile: function() {\n                return this.queue.fetch.apply( this.queue, arguments );\n            },\n    \n            /**\n             * @method retry\n             * @grammar retry() => undefined\n             * @grammar retry( file ) => undefined\n             * @description 重试上传，重试指定文件，或者从出错的文件开始重新上传。\n             * @for  Uploader\n             * @example\n             * function retry() {\n             *     uploader.retry();\n             * }\n             */\n            retry: function( file, noForceStart ) {\n                var me = this,\n                    files, i, len;\n    \n                if ( file ) {\n                    file = file.id ? file : me.queue.getFile( file );\n                    file.setStatus( Status.QUEUED );\n                    noForceStart || me.request('start-upload');\n                    return;\n                }\n    \n                files = me.queue.getFiles( Status.ERROR );\n                i = 0;\n                len = files.length;\n    \n                for ( ; i < len; i++ ) {\n                    file = files[ i ];\n                    file.setStatus( Status.QUEUED );\n                }\n    \n                me.request('start-upload');\n            },\n    \n            /**\n             * @method sort\n             * @grammar sort( fn ) => undefined\n             * @description 排序队列中的文件，在上传之前调整可以控制上传顺序。\n             * @for  Uploader\n             */\n            sortFiles: function() {\n                return this.queue.sort.apply( this.queue, arguments );\n            },\n    \n            /**\n             * @method reset\n             * @grammar reset() => undefined\n             * @description 重置uploader。目前只重置了队列。\n             * @for  Uploader\n             * @example\n             * uploader.reset();\n             */\n            reset: function() {\n                this.queue = new Queue();\n                this.stats = this.queue.stats;\n            }\n        });\n    \n    });\n    /**\n     * @fileOverview 添加获取Runtime相关信息的方法。\n     */\n    define('widgets/runtime',[\n        'uploader',\n        'runtime/runtime',\n        'widgets/widget'\n    ], function( Uploader, Runtime ) {\n    \n        Uploader.support = function() {\n            return Runtime.hasRuntime.apply( Runtime, arguments );\n        };\n    \n        return Uploader.register({\n            'predict-runtime-type': 'predictRuntmeType'\n        }, {\n    \n            init: function() {\n                if ( !this.predictRuntmeType() ) {\n                    throw Error('Runtime Error');\n                }\n            },\n    \n            /**\n             * 预测Uploader将采用哪个`Runtime`\n             * @grammar predictRuntmeType() => String\n             * @method predictRuntmeType\n             * @for  Uploader\n             */\n            predictRuntmeType: function() {\n                var orders = this.options.runtimeOrder || Runtime.orders,\n                    type = this.type,\n                    i, len;\n    \n                if ( !type ) {\n                    orders = orders.split( /\\s*,\\s*/g );\n    \n                    for ( i = 0, len = orders.length; i < len; i++ ) {\n                        if ( Runtime.hasRuntime( orders[ i ] ) ) {\n                            this.type = type = orders[ i ];\n                            break;\n                        }\n                    }\n                }\n    \n                return type;\n            }\n        });\n    });\n    /**\n     * @fileOverview Transport\n     */\n    define('lib/transport',[\n        'base',\n        'runtime/client',\n        'mediator'\n    ], function( Base, RuntimeClient, Mediator ) {\n    \n        var $ = Base.$;\n    \n        function Transport( opts ) {\n            var me = this;\n    \n            opts = me.options = $.extend( true, {}, Transport.options, opts || {} );\n            RuntimeClient.call( this, 'Transport' );\n    \n            this._blob = null;\n            this._formData = opts.formData || {};\n            this._headers = opts.headers || {};\n    \n            this.on( 'progress', this._timeout );\n            this.on( 'load error', function() {\n                me.trigger( 'progress', 1 );\n                clearTimeout( me._timer );\n            });\n        }\n    \n        Transport.options = {\n            server: '',\n            method: 'POST',\n    \n            // 跨域时，是否允许携带cookie, 只有html5 runtime才有效\n            withCredentials: false,\n            fileVal: 'file',\n            timeout: 2 * 60 * 1000,    // 2分钟\n            formData: {},\n            headers: {},\n            sendAsBinary: false\n        };\n    \n        $.extend( Transport.prototype, {\n    \n            // 添加Blob, 只能添加一次，最后一次有效。\n            appendBlob: function( key, blob, filename ) {\n                var me = this,\n                    opts = me.options;\n    \n                if ( me.getRuid() ) {\n                    me.disconnectRuntime();\n                }\n    \n                // 连接到blob归属的同一个runtime.\n                me.connectRuntime( blob.ruid, function() {\n                    me.exec('init');\n                });\n    \n                me._blob = blob;\n                opts.fileVal = key || opts.fileVal;\n                opts.filename = filename || opts.filename;\n            },\n    \n            // 添加其他字段\n            append: function( key, value ) {\n                if ( typeof key === 'object' ) {\n                    $.extend( this._formData, key );\n                } else {\n                    this._formData[ key ] = value;\n                }\n            },\n    \n            setRequestHeader: function( key, value ) {\n                if ( typeof key === 'object' ) {\n                    $.extend( this._headers, key );\n                } else {\n                    this._headers[ key ] = value;\n                }\n            },\n    \n            send: function( method ) {\n                this.exec( 'send', method );\n                this._timeout();\n            },\n    \n            abort: function() {\n                clearTimeout( this._timer );\n                return this.exec('abort');\n            },\n    \n            destroy: function() {\n                this.trigger('destroy');\n                this.off();\n                this.exec('destroy');\n                this.disconnectRuntime();\n            },\n    \n            getResponse: function() {\n                return this.exec('getResponse');\n            },\n    \n            getResponseAsJson: function() {\n                return this.exec('getResponseAsJson');\n            },\n    \n            getStatus: function() {\n                return this.exec('getStatus');\n            },\n    \n            _timeout: function() {\n                var me = this,\n                    duration = me.options.timeout;\n    \n                if ( !duration ) {\n                    return;\n                }\n    \n                clearTimeout( me._timer );\n                me._timer = setTimeout(function() {\n                    me.abort();\n                    me.trigger( 'error', 'timeout' );\n                }, duration );\n            }\n    \n        });\n    \n        // 让Transport具备事件功能。\n        Mediator.installTo( Transport.prototype );\n    \n        return Transport;\n    });\n    /**\n     * @fileOverview 负责文件上传相关。\n     */\n    define('widgets/upload',[\n        'base',\n        'uploader',\n        'file',\n        'lib/transport',\n        'widgets/widget'\n    ], function( Base, Uploader, WUFile, Transport ) {\n    \n        var $ = Base.$,\n            isPromise = Base.isPromise,\n            Status = WUFile.Status;\n    \n        // 添加默认配置项\n        $.extend( Uploader.options, {\n    \n    \n            /**\n             * @property {Boolean} [prepareNextFile=false]\n             * @namespace options\n             * @for Uploader\n             * @description 是否允许在文件传输时提前把下一个文件准备好。\n             * 对于一个文件的准备工作比较耗时，比如图片压缩，md5序列化。\n             * 如果能提前在当前文件传输期处理，可以节省总体耗时。\n             */\n            prepareNextFile: false,\n    \n            /**\n             * @property {Boolean} [chunked=false]\n             * @namespace options\n             * @for Uploader\n             * @description 是否要分片处理大文件上传。\n             */\n            chunked: false,\n    \n            /**\n             * @property {Boolean} [chunkSize=5242880]\n             * @namespace options\n             * @for Uploader\n             * @description 如果要分片，分多大一片？ 默认大小为5M.\n             */\n            chunkSize: 5 * 1024 * 1024,\n    \n            /**\n             * @property {Boolean} [chunkRetry=2]\n             * @namespace options\n             * @for Uploader\n             * @description 如果某个分片由于网络问题出错，允许自动重传多少次？\n             */\n            chunkRetry: 2,\n    \n            /**\n             * @property {Boolean} [threads=3]\n             * @namespace options\n             * @for Uploader\n             * @description 上传并发数。允许同时最大上传进程数。\n             */\n            threads: 3,\n    \n    \n            /**\n             * @property {Object} [formData]\n             * @namespace options\n             * @for Uploader\n             * @description 文件上传请求的参数表，每次发送都会发送此对象中的参数。\n             */\n            formData: null\n    \n            /**\n             * @property {Object} [fileVal='file']\n             * @namespace options\n             * @for Uploader\n             * @description 设置文件上传域的name。\n             */\n    \n            /**\n             * @property {Object} [method='POST']\n             * @namespace options\n             * @for Uploader\n             * @description 文件上传方式，`POST`或者`GET`。\n             */\n    \n            /**\n             * @property {Object} [sendAsBinary=false]\n             * @namespace options\n             * @for Uploader\n             * @description 是否已二进制的流的方式发送文件，这样整个上传内容`php://input`都为文件内容，\n             * 其他参数在$_GET数组中。\n             */\n        });\n    \n        // 负责将文件切片。\n        function CuteFile( file, chunkSize ) {\n            var pending = [],\n                blob = file.source,\n                total = blob.size,\n                chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1,\n                start = 0,\n                index = 0,\n                len;\n    \n            while ( index < chunks ) {\n                len = Math.min( chunkSize, total - start );\n    \n                pending.push({\n                    file: file,\n                    start: start,\n                    end: chunkSize ? (start + len) : total,\n                    total: total,\n                    chunks: chunks,\n                    chunk: index++\n                });\n                start += len;\n            }\n    \n            file.blocks = pending.concat();\n            file.remaning = pending.length;\n    \n            return {\n                file: file,\n    \n                has: function() {\n                    return !!pending.length;\n                },\n    \n                fetch: function() {\n                    return pending.shift();\n                }\n            };\n        }\n    \n        Uploader.register({\n            'start-upload': 'start',\n            'stop-upload': 'stop',\n            'skip-file': 'skipFile',\n            'is-in-progress': 'isInProgress'\n        }, {\n    \n            init: function() {\n                var owner = this.owner;\n    \n                this.runing = false;\n    \n                // 记录当前正在传的数据，跟threads相关\n                this.pool = [];\n    \n                // 缓存即将上传的文件。\n                this.pending = [];\n    \n                // 跟踪还有多少分片没有完成上传。\n                this.remaning = 0;\n                this.__tick = Base.bindFn( this._tick, this );\n    \n                owner.on( 'uploadComplete', function( file ) {\n                    // 把其他块取消了。\n                    file.blocks && $.each( file.blocks, function( _, v ) {\n                        v.transport && (v.transport.abort(), v.transport.destroy());\n                        delete v.transport;\n                    });\n    \n                    delete file.blocks;\n                    delete file.remaning;\n                });\n            },\n    \n            /**\n             * @event startUpload\n             * @description 当开始上传流程时触发。\n             * @for  Uploader\n             */\n    \n            /**\n             * 开始上传。此方法可以从初始状态调用开始上传流程，也可以从暂停状态调用，继续上传流程。\n             * @grammar upload() => undefined\n             * @method upload\n             * @for  Uploader\n             */\n            start: function() {\n                var me = this;\n    \n                // 移出invalid的文件\n                $.each( me.request( 'get-files', Status.INVALID ), function() {\n                    me.request( 'remove-file', this );\n                });\n    \n                if ( me.runing ) {\n                    return;\n                }\n    \n                me.runing = true;\n    \n                // 如果有暂停的，则续传\n                $.each( me.pool, function( _, v ) {\n                    var file = v.file;\n    \n                    if ( file.getStatus() === Status.INTERRUPT ) {\n                        file.setStatus( Status.PROGRESS );\n                        me._trigged = false;\n                        v.transport && v.transport.send();\n                    }\n                });\n    \n                me._trigged = false;\n                me.owner.trigger('startUpload');\n                Base.nextTick( me.__tick );\n            },\n    \n            /**\n             * @event stopUpload\n             * @description 当开始上传流程暂停时触发。\n             * @for  Uploader\n             */\n    \n            /**\n             * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。\n             * @grammar stop() => undefined\n             * @grammar stop( true ) => undefined\n             * @method stop\n             * @for  Uploader\n             */\n            stop: function( interrupt ) {\n                var me = this;\n    \n                if ( me.runing === false ) {\n                    return;\n                }\n    \n                me.runing = false;\n    \n                interrupt && $.each( me.pool, function( _, v ) {\n                    v.transport && v.transport.abort();\n                    v.file.setStatus( Status.INTERRUPT );\n                });\n    \n                me.owner.trigger('stopUpload');\n            },\n    \n            /**\n             * 判断`Uplaode`r是否正在上传中。\n             * @grammar isInProgress() => Boolean\n             * @method isInProgress\n             * @for  Uploader\n             */\n            isInProgress: function() {\n                return !!this.runing;\n            },\n    \n            getStats: function() {\n                return this.request('get-stats');\n            },\n    \n            /**\n             * 掉过一个文件上传，直接标记指定文件为已上传状态。\n             * @grammar skipFile( file ) => undefined\n             * @method skipFile\n             * @for  Uploader\n             */\n            skipFile: function( file, status ) {\n                file = this.request( 'get-file', file );\n    \n                file.setStatus( status || Status.COMPLETE );\n                file.skipped = true;\n    \n                // 如果正在上传。\n                file.blocks && $.each( file.blocks, function( _, v ) {\n                    var _tr = v.transport;\n    \n                    if ( _tr ) {\n                        _tr.abort();\n                        _tr.destroy();\n                        delete v.transport;\n                    }\n                });\n    \n                this.owner.trigger( 'uploadSkip', file );\n            },\n    \n            /**\n             * @event uploadFinished\n             * @description 当所有文件上传结束时触发。\n             * @for  Uploader\n             */\n            _tick: function() {\n                var me = this,\n                    opts = me.options,\n                    fn, val;\n    \n                // 上一个promise还没有结束，则等待完成后再执行。\n                if ( me._promise ) {\n                    return me._promise.always( me.__tick );\n                }\n    \n                // 还有位置，且还有文件要处理的话。\n                if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) {\n                    me._trigged = false;\n    \n                    fn = function( val ) {\n                        me._promise = null;\n    \n                        // 有可能是reject过来的，所以要检测val的类型。\n                        val && val.file && me._startSend( val );\n                        Base.nextTick( me.__tick );\n                    };\n    \n                    me._promise = isPromise( val ) ? val.always( fn ) : fn( val );\n    \n                // 没有要上传的了，且没有正在传输的了。\n                } else if ( !me.remaning && !me.getStats().numOfQueue ) {\n                    me.runing = false;\n    \n                    me._trigged || Base.nextTick(function() {\n                        me.owner.trigger('uploadFinished');\n                    });\n                    me._trigged = true;\n                }\n            },\n    \n            _nextBlock: function() {\n                var me = this,\n                    act = me._act,\n                    opts = me.options,\n                    next, done;\n    \n                // 如果当前文件还有没有需要传输的，则直接返回剩下的。\n                if ( act && act.has() &&\n                        act.file.getStatus() === Status.PROGRESS ) {\n    \n                    // 是否提前准备下一个文件\n                    if ( opts.prepareNextFile && !me.pending.length ) {\n                        me._prepareNextFile();\n                    }\n    \n                    return act.fetch();\n    \n                // 否则，如果正在运行，则准备下一个文件，并等待完成后返回下个分片。\n                } else if ( me.runing ) {\n    \n                    // 如果缓存中有，则直接在缓存中取，没有则去queue中取。\n                    if ( !me.pending.length && me.getStats().numOfQueue ) {\n                        me._prepareNextFile();\n                    }\n    \n                    next = me.pending.shift();\n                    done = function( file ) {\n                        if ( !file ) {\n                            return null;\n                        }\n    \n                        act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 );\n                        me._act = act;\n                        return act.fetch();\n                    };\n    \n                    // 文件可能还在prepare中，也有可能已经完全准备好了。\n                    return isPromise( next ) ?\n                            next[ next.pipe ? 'pipe' : 'then']( done ) :\n                            done( next );\n                }\n            },\n    \n    \n            /**\n             * @event uploadStart\n             * @param {File} file File对象\n             * @description 某个文件开始上传前触发，一个文件只会触发一次。\n             * @for  Uploader\n             */\n            _prepareNextFile: function() {\n                var me = this,\n                    file = me.request('fetch-file'),\n                    pending = me.pending,\n                    promise;\n    \n                if ( file ) {\n                    promise = me.request( 'before-send-file', file, function() {\n    \n                        // 有可能文件被skip掉了。文件被skip掉后，状态坑定不是Queued.\n                        if ( file.getStatus() === Status.QUEUED ) {\n                            me.owner.trigger( 'uploadStart', file );\n                            file.setStatus( Status.PROGRESS );\n                            return file;\n                        }\n    \n                        return me._finishFile( file );\n                    });\n    \n                    // 如果还在pending中，则替换成文件本身。\n                    promise.done(function() {\n                        var idx = $.inArray( promise, pending );\n    \n                        ~idx && pending.splice( idx, 1, file );\n                    });\n    \n                    // befeore-send-file的钩子就有错误发生。\n                    promise.fail(function( reason ) {\n                        file.setStatus( Status.ERROR, reason );\n                        me.owner.trigger( 'uploadError', file, reason );\n                        me.owner.trigger( 'uploadComplete', file );\n                    });\n    \n                    pending.push( promise );\n                }\n            },\n    \n            // 让出位置了，可以让其他分片开始上传\n            _popBlock: function( block ) {\n                var idx = $.inArray( block, this.pool );\n    \n                this.pool.splice( idx, 1 );\n                block.file.remaning--;\n                this.remaning--;\n            },\n    \n            // 开始上传，可以被掉过。如果promise被reject了，则表示跳过此分片。\n            _startSend: function( block ) {\n                var me = this,\n                    file = block.file,\n                    promise;\n    \n                me.pool.push( block );\n                me.remaning++;\n    \n                // 如果没有分片，则直接使用原始的。\n                // 不会丢失content-type信息。\n                block.blob = block.chunks === 1 ? file.source :\n                        file.source.slice( block.start, block.end );\n    \n                // hook, 每个分片发送之前可能要做些异步的事情。\n                promise = me.request( 'before-send', block, function() {\n    \n                    // 有可能文件已经上传出错了，所以不需要再传输了。\n                    if ( file.getStatus() === Status.PROGRESS ) {\n                        me._doSend( block );\n                    } else {\n                        me._popBlock( block );\n                        Base.nextTick( me.__tick );\n                    }\n                });\n    \n                // 如果为fail了，则跳过此分片。\n                promise.fail(function() {\n                    if ( file.remaning === 1 ) {\n                        me._finishFile( file ).always(function() {\n                            block.percentage = 1;\n                            me._popBlock( block );\n                            me.owner.trigger( 'uploadComplete', file );\n                            Base.nextTick( me.__tick );\n                        });\n                    } else {\n                        block.percentage = 1;\n                        me._popBlock( block );\n                        Base.nextTick( me.__tick );\n                    }\n                });\n            },\n    \n    \n            /**\n             * @event uploadBeforeSend\n             * @param {Object} object\n             * @param {Object} data 默认的上传参数，可以扩展此对象来控制上传参数。\n             * @description 当某个文件的分块在发送前触发，主要用来询问是否要添加附带参数，大文件在开起分片上传的前提下此事件可能会触发多次。\n             * @for  Uploader\n             */\n    \n            /**\n             * @event uploadAccept\n             * @param {Object} object\n             * @param {Object} ret 服务端的返回数据，json格式，如果服务端不是json格式，从ret._raw中取数据，自行解析。\n             * @description 当某个文件上传到服务端响应后，会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。\n             * @for  Uploader\n             */\n    \n            /**\n             * @event uploadProgress\n             * @param {File} file File对象\n             * @param {Number} percentage 上传进度\n             * @description 上传过程中触发，携带上传进度。\n             * @for  Uploader\n             */\n    \n    \n            /**\n             * @event uploadError\n             * @param {File} file File对象\n             * @param {String} reason 出错的code\n             * @description 当文件上传出错时触发。\n             * @for  Uploader\n             */\n    \n            /**\n             * @event uploadSuccess\n             * @param {File} file File对象\n             * @param {Object} response 服务端返回的数据\n             * @description 当文件上传成功时触发。\n             * @for  Uploader\n             */\n    \n            /**\n             * @event uploadComplete\n             * @param {File} [file] File对象\n             * @description 不管成功或者失败，文件上传完成时触发。\n             * @for  Uploader\n             */\n    \n            // 做上传操作。\n            _doSend: function( block ) {\n                var me = this,\n                    owner = me.owner,\n                    opts = me.options,\n                    file = block.file,\n                    tr = new Transport( opts ),\n                    data = $.extend({}, opts.formData ),\n                    headers = $.extend({}, opts.headers ),\n                    requestAccept, ret;\n    \n                block.transport = tr;\n    \n                tr.on( 'destroy', function() {\n                    delete block.transport;\n                    me._popBlock( block );\n                    Base.nextTick( me.__tick );\n                });\n    \n                // 广播上传进度。以文件为单位。\n                tr.on( 'progress', function( percentage ) {\n                    var totalPercent = 0,\n                        uploaded = 0;\n    \n                    // 可能没有abort掉，progress还是执行进来了。\n                    // if ( !file.blocks ) {\n                    //     return;\n                    // }\n    \n                    totalPercent = block.percentage = percentage;\n    \n                    if ( block.chunks > 1 ) {    // 计算文件的整体速度。\n                        $.each( file.blocks, function( _, v ) {\n                            uploaded += (v.percentage || 0) * (v.end - v.start);\n                        });\n    \n                        totalPercent = uploaded / file.size;\n                    }\n    \n                    owner.trigger( 'uploadProgress', file, totalPercent || 0 );\n                });\n    \n                // 用来询问，是否返回的结果是有错误的。\n                requestAccept = function( reject ) {\n                    var fn;\n    \n                    ret = tr.getResponseAsJson() || {};\n                    ret._raw = tr.getResponse();\n                    fn = function( value ) {\n                        reject = value;\n                    };\n    \n                    // 服务端响应了，不代表成功了，询问是否响应正确。\n                    if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) {\n                        reject = reject || 'server';\n                    }\n    \n                    return reject;\n                };\n    \n                // 尝试重试，然后广播文件上传出错。\n                tr.on( 'error', function( type, flag ) {\n                    block.retried = block.retried || 0;\n    \n                    // 自动重试\n                    if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) &&\n                            block.retried < opts.chunkRetry ) {\n    \n                        block.retried++;\n                        tr.send();\n    \n                    } else {\n    \n                        // http status 500 ~ 600\n                        if ( !flag && type === 'server' ) {\n                            type = requestAccept( type );\n                        }\n    \n                        file.setStatus( Status.ERROR, type );\n                        owner.trigger( 'uploadError', file, type );\n                        owner.trigger( 'uploadComplete', file );\n                    }\n                });\n    \n                // 上传成功\n                tr.on( 'load', function() {\n                    var reason;\n    \n                    // 如果非预期，转向上传出错。\n                    if ( (reason = requestAccept()) ) {\n                        tr.trigger( 'error', reason, true );\n                        return;\n                    }\n    \n                    // 全部上传完成。\n                    if ( file.remaning === 1 ) {\n                        me._finishFile( file, ret );\n                    } else {\n                        tr.destroy();\n                    }\n                });\n    \n                // 配置默认的上传字段。\n                data = $.extend( data, {\n                    id: file.id,\n                    name: file.name,\n                    type: file.type,\n                    lastModifiedDate: file.lastModifiedDate,\n                    size: file.size\n                });\n    \n                block.chunks > 1 && $.extend( data, {\n                    chunks: block.chunks,\n                    chunk: block.chunk\n                });\n    \n                // 在发送之间可以添加字段什么的。。。\n                // 如果默认的字段不够使用，可以通过监听此事件来扩展\n                owner.trigger( 'uploadBeforeSend', block, data, headers );\n    \n                // 开始发送。\n                tr.appendBlob( opts.fileVal, block.blob, file.name );\n                tr.append( data );\n                tr.setRequestHeader( headers );\n                tr.send();\n            },\n    \n            // 完成上传。\n            _finishFile: function( file, ret, hds ) {\n                var owner = this.owner;\n    \n                return owner\n                        .request( 'after-send-file', arguments, function() {\n                            file.setStatus( Status.COMPLETE );\n                            owner.trigger( 'uploadSuccess', file, ret, hds );\n                        })\n                        .fail(function( reason ) {\n    \n                            // 如果外部已经标记为invalid什么的，不再改状态。\n                            if ( file.getStatus() === Status.PROGRESS ) {\n                                file.setStatus( Status.ERROR, reason );\n                            }\n    \n                            owner.trigger( 'uploadError', file, reason );\n                        })\n                        .always(function() {\n                            owner.trigger( 'uploadComplete', file );\n                        });\n            }\n    \n        });\n    });\n    /**\n     * @fileOverview 各种验证，包括文件总大小是否超出、单文件是否超出和文件是否重复。\n     */\n    \n    define('widgets/validator',[\n        'base',\n        'uploader',\n        'file',\n        'widgets/widget'\n    ], function( Base, Uploader, WUFile ) {\n    \n        var $ = Base.$,\n            validators = {},\n            api;\n    \n        /**\n         * @event error\n         * @param {String} type 错误类型。\n         * @description 当validate不通过时，会以派送错误事件的形式通知调用者。通过`upload.on('error', handler)`可以捕获到此类错误，目前有以下错误会在特定的情况下派送错来。\n         *\n         * * `Q_EXCEED_NUM_LIMIT` 在设置了`fileNumLimit`且尝试给`uploader`添加的文件数量超出这个值时派送。\n         * * `Q_EXCEED_SIZE_LIMIT` 在设置了`Q_EXCEED_SIZE_LIMIT`且尝试给`uploader`添加的文件总大小超出这个值时派送。\n         * @for  Uploader\n         */\n    \n        // 暴露给外面的api\n        api = {\n    \n            // 添加验证器\n            addValidator: function( type, cb ) {\n                validators[ type ] = cb;\n            },\n    \n            // 移除验证器\n            removeValidator: function( type ) {\n                delete validators[ type ];\n            }\n        };\n    \n        // 在Uploader初始化的时候启动Validators的初始化\n        Uploader.register({\n            init: function() {\n                var me = this;\n                $.each( validators, function() {\n                    this.call( me.owner );\n                });\n            }\n        });\n    \n        /**\n         * @property {int} [fileNumLimit=undefined]\n         * @namespace options\n         * @for Uploader\n         * @description 验证文件总数量, 超出则不允许加入队列。\n         */\n        api.addValidator( 'fileNumLimit', function() {\n            var uploader = this,\n                opts = uploader.options,\n                count = 0,\n                max = opts.fileNumLimit >> 0,\n                flag = true;\n    \n            if ( !max ) {\n                return;\n            }\n    \n            uploader.on( 'beforeFileQueued', function( file ) {\n    \n                if ( count >= max && flag ) {\n                    flag = false;\n                    this.trigger( 'error', 'Q_EXCEED_NUM_LIMIT', max, file );\n                    setTimeout(function() {\n                        flag = true;\n                    }, 1 );\n                }\n    \n                return count >= max ? false : true;\n            });\n    \n            uploader.on( 'fileQueued', function() {\n                count++;\n            });\n    \n            uploader.on( 'fileDequeued', function() {\n                count--;\n            });\n    \n            uploader.on( 'uploadFinished', function() {\n                count = 0;\n            });\n        });\n    \n    \n        /**\n         * @property {int} [fileSizeLimit=undefined]\n         * @namespace options\n         * @for Uploader\n         * @description 验证文件总大小是否超出限制, 超出则不允许加入队列。\n         */\n        api.addValidator( 'fileSizeLimit', function() {\n            var uploader = this,\n                opts = uploader.options,\n                count = 0,\n                max = opts.fileSizeLimit >> 0,\n                flag = true;\n    \n            if ( !max ) {\n                return;\n            }\n    \n            uploader.on( 'beforeFileQueued', function( file ) {\n                var invalid = count + file.size > max;\n    \n                if ( invalid && flag ) {\n                    flag = false;\n                    this.trigger( 'error', 'Q_EXCEED_SIZE_LIMIT', max, file );\n                    setTimeout(function() {\n                        flag = true;\n                    }, 1 );\n                }\n    \n                return invalid ? false : true;\n            });\n    \n            uploader.on( 'fileQueued', function( file ) {\n                count += file.size;\n            });\n    \n            uploader.on( 'fileDequeued', function( file ) {\n                count -= file.size;\n            });\n    \n            uploader.on( 'uploadFinished', function() {\n                count = 0;\n            });\n        });\n    \n        /**\n         * @property {int} [fileSingleSizeLimit=undefined]\n         * @namespace options\n         * @for Uploader\n         * @description 验证单个文件大小是否超出限制, 超出则不允许加入队列。\n         */\n        api.addValidator( 'fileSingleSizeLimit', function() {\n            var uploader = this,\n                opts = uploader.options,\n                max = opts.fileSingleSizeLimit;\n    \n            if ( !max ) {\n                return;\n            }\n    \n            uploader.on( 'beforeFileQueued', function( file ) {\n    \n                if ( file.size > max ) {\n                    file.setStatus( WUFile.Status.INVALID, 'exceed_size' );\n                    this.trigger( 'error', 'F_EXCEED_SIZE', file );\n                    return false;\n                }\n    \n            });\n    \n        });\n    \n        /**\n         * @property {int} [duplicate=undefined]\n         * @namespace options\n         * @for Uploader\n         * @description 去重， 根据文件名字、文件大小和最后修改时间来生成hash Key.\n         */\n        api.addValidator( 'duplicate', function() {\n            var uploader = this,\n                opts = uploader.options,\n                mapping = {};\n    \n            if ( opts.duplicate ) {\n                return;\n            }\n    \n            function hashString( str ) {\n                var hash = 0,\n                    i = 0,\n                    len = str.length,\n                    _char;\n    \n                for ( ; i < len; i++ ) {\n                    _char = str.charCodeAt( i );\n                    hash = _char + (hash << 6) + (hash << 16) - hash;\n                }\n    \n                return hash;\n            }\n    \n            uploader.on( 'beforeFileQueued', function( file ) {\n                var hash = file.__hash || (file.__hash = hashString( file.name +\n                        file.size + file.lastModifiedDate ));\n    \n                // 已经重复了\n                if ( mapping[ hash ] ) {\n                    this.trigger( 'error', 'F_DUPLICATE', file );\n                    return false;\n                }\n            });\n    \n            uploader.on( 'fileQueued', function( file ) {\n                var hash = file.__hash;\n    \n                hash && (mapping[ hash ] = true);\n            });\n    \n            uploader.on( 'fileDequeued', function( file ) {\n                var hash = file.__hash;\n    \n                hash && (delete mapping[ hash ]);\n            });\n        });\n    \n        return api;\n    });\n    \n    /**\n     * @fileOverview Runtime管理器，负责Runtime的选择, 连接\n     */\n    define('runtime/compbase',[],function() {\n    \n        function CompBase( owner, runtime ) {\n    \n            this.owner = owner;\n            this.options = owner.options;\n    \n            this.getRuntime = function() {\n                return runtime;\n            };\n    \n            this.getRuid = function() {\n                return runtime.uid;\n            };\n    \n            this.trigger = function() {\n                return owner.trigger.apply( owner, arguments );\n            };\n        }\n    \n        return CompBase;\n    });\n    /**\n     * @fileOverview FlashRuntime\n     */\n    define('runtime/flash/runtime',[\n        'base',\n        'runtime/runtime',\n        'runtime/compbase'\n    ], function( Base, Runtime, CompBase ) {\n    \n        var $ = Base.$,\n            type = 'flash',\n            components = {};\n    \n    \n        function getFlashVersion() {\n            var version;\n    \n            try {\n                version = navigator.plugins[ 'Shockwave Flash' ];\n                version = version.description;\n            } catch ( ex ) {\n                try {\n                    version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash')\n                            .GetVariable('$version');\n                } catch ( ex2 ) {\n                    version = '0.0';\n                }\n            }\n            version = version.match( /\\d+/g );\n            return parseFloat( version[ 0 ] + '.' + version[ 1 ], 10 );\n        }\n    \n        function FlashRuntime() {\n            var pool = {},\n                clients = {},\n                destory = this.destory,\n                me = this,\n                jsreciver = Base.guid('webuploader_');\n    \n            Runtime.apply( me, arguments );\n            me.type = type;\n    \n    \n            // 这个方法的调用者，实际上是RuntimeClient\n            me.exec = function( comp, fn/*, args...*/ ) {\n                var client = this,\n                    uid = client.uid,\n                    args = Base.slice( arguments, 2 ),\n                    instance;\n    \n                clients[ uid ] = client;\n    \n                if ( components[ comp ] ) {\n                    if ( !pool[ uid ] ) {\n                        pool[ uid ] = new components[ comp ]( client, me );\n                    }\n    \n                    instance = pool[ uid ];\n    \n                    if ( instance[ fn ] ) {\n                        return instance[ fn ].apply( instance, args );\n                    }\n                }\n    \n                return me.flashExec.apply( client, arguments );\n            };\n    \n            function handler( evt, obj ) {\n                var type = evt.type || evt,\n                    parts, uid;\n    \n                parts = type.split('::');\n                uid = parts[ 0 ];\n                type = parts[ 1 ];\n    \n                // console.log.apply( console, arguments );\n    \n                if ( type === 'Ready' && uid === me.uid ) {\n                    me.trigger('ready');\n                } else if ( clients[ uid ] ) {\n                    clients[ uid ].trigger( type.toLowerCase(), evt, obj );\n                }\n    \n                // Base.log( evt, obj );\n            }\n    \n            // flash的接受器。\n            window[ jsreciver ] = function() {\n                var args = arguments;\n    \n                // 为了能捕获得到。\n                setTimeout(function() {\n                    handler.apply( null, args );\n                }, 1 );\n            };\n    \n            this.jsreciver = jsreciver;\n    \n            this.destory = function() {\n                // @todo 删除池子中的所有实例\n                return destory && destory.apply( this, arguments );\n            };\n    \n            this.flashExec = function( comp, fn ) {\n                var flash = me.getFlash(),\n                    args = Base.slice( arguments, 2 );\n    \n                return flash.exec( this.uid, comp, fn, args );\n            };\n    \n            // @todo\n        }\n    \n        Base.inherits( Runtime, {\n            constructor: FlashRuntime,\n    \n            init: function() {\n                var container = this.getContainer(),\n                    opts = this.options,\n                    html;\n    \n                // if not the minimal height, shims are not initialized\n                // in older browsers (e.g FF3.6, IE6,7,8, Safari 4.0,5.0, etc)\n                container.css({\n                    position: 'absolute',\n                    top: '-8px',\n                    left: '-8px',\n                    width: '9px',\n                    height: '9px',\n                    overflow: 'hidden'\n                });\n    \n                // insert flash object\n                html = '<object id=\"' + this.uid + '\" type=\"application/' +\n                        'x-shockwave-flash\" data=\"' +  opts.swf + '\" ';\n    \n                if ( Base.browser.ie ) {\n                    html += 'classid=\"clsid:d27cdb6e-ae6d-11cf-96b8-444553540000\" ';\n                }\n    \n                html += 'width=\"100%\" height=\"100%\" style=\"outline:0\">'  +\n                    '<param name=\"movie\" value=\"' + opts.swf + '\" />' +\n                    '<param name=\"flashvars\" value=\"uid=' + this.uid +\n                    '&jsreciver=' + this.jsreciver + '\" />' +\n                    '<param name=\"wmode\" value=\"transparent\" />' +\n                    '<param name=\"allowscriptaccess\" value=\"always\" />' +\n                '</object>';\n    \n                container.html( html );\n            },\n    \n            getFlash: function() {\n                if ( this._flash ) {\n                    return this._flash;\n                }\n    \n                this._flash = $( '#' + this.uid ).get( 0 );\n                return this._flash;\n            }\n    \n        });\n    \n        FlashRuntime.register = function( name, component ) {\n            component = components[ name ] = Base.inherits( CompBase, $.extend({\n    \n                // @todo fix this later\n                flashExec: function() {\n                    var owner = this.owner,\n                        runtime = this.getRuntime();\n    \n                    return runtime.flashExec.apply( owner, arguments );\n                }\n            }, component ) );\n    \n            return component;\n        };\n    \n        if ( getFlashVersion() >= 11.4 ) {\n            Runtime.addRuntime( type, FlashRuntime );\n        }\n    \n        return FlashRuntime;\n    });\n    /**\n     * @fileOverview FilePicker\n     */\n    define('runtime/flash/filepicker',[\n        'base',\n        'runtime/flash/runtime'\n    ], function( Base, FlashRuntime ) {\n        var $ = Base.$;\n    \n        return FlashRuntime.register( 'FilePicker', {\n            init: function( opts ) {\n                var copy = $.extend({}, opts ),\n                    len, i;\n    \n                // 修复Flash再没有设置title的情况下无法弹出flash文件选择框的bug.\n                len = copy.accept && copy.accept.length;\n                for (  i = 0; i < len; i++ ) {\n                    if ( !copy.accept[ i ].title ) {\n                        copy.accept[ i ].title = 'Files';\n                    }\n                }\n    \n                delete copy.button;\n                delete copy.container;\n    \n                this.flashExec( 'FilePicker', 'init', copy );\n            },\n    \n            destroy: function() {\n                // todo\n            }\n        });\n    });\n    /**\n     * @fileOverview 图片压缩\n     */\n    define('runtime/flash/image',[\n        'runtime/flash/runtime'\n    ], function( FlashRuntime ) {\n    \n        return FlashRuntime.register( 'Image', {\n            // init: function( options ) {\n            //     var owner = this.owner;\n    \n            //     this.flashExec( 'Image', 'init', options );\n            //     owner.on( 'load', function() {\n            //         debugger;\n            //     });\n            // },\n    \n            loadFromBlob: function( blob ) {\n                var owner = this.owner;\n    \n                owner.info() && this.flashExec( 'Image', 'info', owner.info() );\n                owner.meta() && this.flashExec( 'Image', 'meta', owner.meta() );\n    \n                this.flashExec( 'Image', 'loadFromBlob', blob.uid );\n            }\n        });\n    });\n    /**\n     * @fileOverview  Transport flash实现\n     */\n    define('runtime/flash/transport',[\n        'base',\n        'runtime/flash/runtime',\n        'runtime/client'\n    ], function( Base, FlashRuntime, RuntimeClient ) {\n        var $ = Base.$;\n    \n        return FlashRuntime.register( 'Transport', {\n            init: function() {\n                this._status = 0;\n                this._response = null;\n                this._responseJson = null;\n            },\n    \n            send: function() {\n                var owner = this.owner,\n                    opts = this.options,\n                    xhr = this._initAjax(),\n                    blob = owner._blob,\n                    server = opts.server,\n                    binary;\n    \n                xhr.connectRuntime( blob.ruid );\n    \n                if ( opts.sendAsBinary ) {\n                    server += (/\\?/.test( server ) ? '&' : '?') +\n                            $.param( owner._formData );\n    \n                    binary = blob.uid;\n                } else {\n                    $.each( owner._formData, function( k, v ) {\n                        xhr.exec( 'append', k, v );\n                    });\n    \n                    xhr.exec( 'appendBlob', opts.fileVal, blob.uid,\n                            opts.filename || owner._formData.name || '' );\n                }\n    \n                this._setRequestHeader( xhr, opts.headers );\n                xhr.exec( 'send', {\n                    method: opts.method,\n                    url: server\n                }, binary );\n            },\n    \n            getStatus: function() {\n                return this._status;\n            },\n    \n            getResponse: function() {\n                return this._response;\n            },\n    \n            getResponseAsJson: function() {\n                return this._responseJson;\n            },\n    \n            abort: function() {\n                var xhr = this._xhr;\n    \n                if ( xhr ) {\n                    xhr.exec('abort');\n                    xhr.destroy();\n                    this._xhr = xhr = null;\n                }\n            },\n    \n            destroy: function() {\n                this.abort();\n            },\n    \n            _initAjax: function() {\n                var me = this,\n                    xhr = new RuntimeClient('XMLHttpRequest');\n    \n                xhr.on( 'uploadprogress progress', function( e ) {\n                    return me.trigger( 'progress', e.loaded / e.total );\n                });\n    \n                xhr.on( 'load', function() {\n                    var status = xhr.exec('getStatus'),\n                        err = '';\n    \n                    xhr.off();\n                    me._xhr = null;\n    \n                    if ( status >= 200 && status < 300 ) {\n                        me._response = xhr.exec('getResponse');\n                        me._responseJson = xhr.exec('getResponseAsJson');\n                    } else if ( status >= 500 && status < 600 ) {\n                        me._response = xhr.exec('getResponse');\n                        me._responseJson = xhr.exec('getResponseAsJson');\n                        err = 'server';\n                    } else {\n                        err = 'http';\n                    }\n    \n                    xhr.destroy();\n                    xhr = null;\n    \n                    return err ? me.trigger( 'error', err ) : me.trigger('load');\n                });\n    \n                xhr.on( 'error', function() {\n                    xhr.off();\n                    me._xhr = null;\n                    me.trigger( 'error', 'http' );\n                });\n    \n                me._xhr = xhr;\n                return xhr;\n            },\n    \n            _setRequestHeader: function( xhr, headers ) {\n                $.each( headers, function( key, val ) {\n                    xhr.exec( 'setRequestHeader', key, val );\n                });\n            }\n        });\n    });\n    /**\n     * @fileOverview 只有flash实现的文件版本。\n     */\n    define('preset/flashonly',[\n        'base',\n    \n        // widgets\n        'widgets/filepicker',\n        'widgets/image',\n        'widgets/queue',\n        'widgets/runtime',\n        'widgets/upload',\n        'widgets/validator',\n    \n        // runtimes\n    \n        // flash\n        'runtime/flash/filepicker',\n        'runtime/flash/image',\n        'runtime/flash/transport'\n    ], function( Base ) {\n        return Base;\n    });\n    define('webuploader',[\n        'preset/flashonly'\n    ], function( preset ) {\n        return preset;\n    });\n    return require('webuploader');\n});\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/webuploader/webuploader.html5only.js",
    "content": "/*! WebUploader 0.1.2 */\n\n\n/**\n * @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。\n *\n * AMD API 内部的简单不完全实现，请忽略。只有当WebUploader被合并成一个文件的时候才会引入。\n */\n(function( root, factory ) {\n    var modules = {},\n\n        // 内部require, 简单不完全实现。\n        // https://github.com/amdjs/amdjs-api/wiki/require\n        _require = function( deps, callback ) {\n            var args, len, i;\n\n            // 如果deps不是数组，则直接返回指定module\n            if ( typeof deps === 'string' ) {\n                return getModule( deps );\n            } else {\n                args = [];\n                for( len = deps.length, i = 0; i < len; i++ ) {\n                    args.push( getModule( deps[ i ] ) );\n                }\n\n                return callback.apply( null, args );\n            }\n        },\n\n        // 内部define，暂时不支持不指定id.\n        _define = function( id, deps, factory ) {\n            if ( arguments.length === 2 ) {\n                factory = deps;\n                deps = null;\n            }\n\n            _require( deps || [], function() {\n                setModule( id, factory, arguments );\n            });\n        },\n\n        // 设置module, 兼容CommonJs写法。\n        setModule = function( id, factory, args ) {\n            var module = {\n                    exports: factory\n                },\n                returned;\n\n            if ( typeof factory === 'function' ) {\n                args.length || (args = [ _require, module.exports, module ]);\n                returned = factory.apply( null, args );\n                returned !== undefined && (module.exports = returned);\n            }\n\n            modules[ id ] = module.exports;\n        },\n\n        // 根据id获取module\n        getModule = function( id ) {\n            var module = modules[ id ] || root[ id ];\n\n            if ( !module ) {\n                throw new Error( '`' + id + '` is undefined' );\n            }\n\n            return module;\n        },\n\n        // 将所有modules，将路径ids装换成对象。\n        exportsTo = function( obj ) {\n            var key, host, parts, part, last, ucFirst;\n\n            // make the first character upper case.\n            ucFirst = function( str ) {\n                return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 ));\n            };\n\n            for ( key in modules ) {\n                host = obj;\n\n                if ( !modules.hasOwnProperty( key ) ) {\n                    continue;\n                }\n\n                parts = key.split('/');\n                last = ucFirst( parts.pop() );\n\n                while( (part = ucFirst( parts.shift() )) ) {\n                    host[ part ] = host[ part ] || {};\n                    host = host[ part ];\n                }\n\n                host[ last ] = modules[ key ];\n            }\n        },\n\n        exports = factory( root, _define, _require ),\n        origin;\n\n    // exports every module.\n    exportsTo( exports );\n\n    if ( typeof module === 'object' && typeof module.exports === 'object' ) {\n\n        // For CommonJS and CommonJS-like environments where a proper window is present,\n        module.exports = exports;\n    } else if ( typeof define === 'function' && define.amd ) {\n\n        // Allow using this built library as an AMD module\n        // in another project. That other project will only\n        // see this AMD call, not the internal modules in\n        // the closure below.\n        define([], exports );\n    } else {\n\n        // Browser globals case. Just assign the\n        // result to a property on the global.\n        origin = root.WebUploader;\n        root.WebUploader = exports;\n        root.WebUploader.noConflict = function() {\n            root.WebUploader = origin;\n        };\n    }\n})( this, function( window, define, require ) {\n\n\n    /**\n     * @fileOverview jQuery or Zepto\n     */\n    define('dollar-third',[],function() {\n        return window.jQuery || window.Zepto;\n    });\n    /**\n     * @fileOverview Dom 操作相关\n     */\n    define('dollar',[\n        'dollar-third'\n    ], function( _ ) {\n        return _;\n    });\n    /**\n     * @fileOverview 使用jQuery的Promise\n     */\n    define('promise-third',[\n        'dollar'\n    ], function( $ ) {\n        return {\n            Deferred: $.Deferred,\n            when: $.when,\n    \n            isPromise: function( anything ) {\n                return anything && typeof anything.then === 'function';\n            }\n        };\n    });\n    /**\n     * @fileOverview Promise/A+\n     */\n    define('promise',[\n        'promise-third'\n    ], function( _ ) {\n        return _;\n    });\n    /**\n     * @fileOverview 基础类方法。\n     */\n    \n    /**\n     * Web Uploader内部类的详细说明，以下提及的功能类，都可以在`WebUploader`这个变量中访问到。\n     *\n     * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id.\n     * 默认module id该文件的路径，而此路径将会转化成名字空间存放在WebUploader中。如：\n     *\n     * * module `base`：WebUploader.Base\n     * * module `file`: WebUploader.File\n     * * module `lib/dnd`: WebUploader.Lib.Dnd\n     * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd\n     *\n     *\n     * 以下文档将可能省略`WebUploader`前缀。\n     * @module WebUploader\n     * @title WebUploader API文档\n     */\n    define('base',[\n        'dollar',\n        'promise'\n    ], function( $, promise ) {\n    \n        var noop = function() {},\n            call = Function.call;\n    \n        // http://jsperf.com/uncurrythis\n        // 反科里化\n        function uncurryThis( fn ) {\n            return function() {\n                return call.apply( fn, arguments );\n            };\n        }\n    \n        function bindFn( fn, context ) {\n            return function() {\n                return fn.apply( context, arguments );\n            };\n        }\n    \n        function createObject( proto ) {\n            var f;\n    \n            if ( Object.create ) {\n                return Object.create( proto );\n            } else {\n                f = function() {};\n                f.prototype = proto;\n                return new f();\n            }\n        }\n    \n    \n        /**\n         * 基础类，提供一些简单常用的方法。\n         * @class Base\n         */\n        return {\n    \n            /**\n             * @property {String} version 当前版本号。\n             */\n            version: '0.1.2',\n    \n            /**\n             * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。\n             */\n            $: $,\n    \n            Deferred: promise.Deferred,\n    \n            isPromise: promise.isPromise,\n    \n            when: promise.when,\n    \n            /**\n             * @description  简单的浏览器检查结果。\n             *\n             * * `webkit`  webkit版本号，如果浏览器为非webkit内核，此属性为`undefined`。\n             * * `chrome`  chrome浏览器版本号，如果浏览器为chrome，此属性为`undefined`。\n             * * `ie`  ie浏览器版本号，如果浏览器为非ie，此属性为`undefined`。**暂不支持ie10+**\n             * * `firefox`  firefox浏览器版本号，如果浏览器为非firefox，此属性为`undefined`。\n             * * `safari`  safari浏览器版本号，如果浏览器为非safari，此属性为`undefined`。\n             * * `opera`  opera浏览器版本号，如果浏览器为非opera，此属性为`undefined`。\n             *\n             * @property {Object} [browser]\n             */\n            browser: (function( ua ) {\n                var ret = {},\n                    webkit = ua.match( /WebKit\\/([\\d.]+)/ ),\n                    chrome = ua.match( /Chrome\\/([\\d.]+)/ ) ||\n                        ua.match( /CriOS\\/([\\d.]+)/ ),\n    \n                    ie = ua.match( /MSIE\\s([\\d\\.]+)/ ) ||\n                        ua.match(/(?:trident)(?:.*rv:([\\w.]+))?/i),\n                    firefox = ua.match( /Firefox\\/([\\d.]+)/ ),\n                    safari = ua.match( /Safari\\/([\\d.]+)/ ),\n                    opera = ua.match( /OPR\\/([\\d.]+)/ );\n    \n                webkit && (ret.webkit = parseFloat( webkit[ 1 ] ));\n                chrome && (ret.chrome = parseFloat( chrome[ 1 ] ));\n                ie && (ret.ie = parseFloat( ie[ 1 ] ));\n                firefox && (ret.firefox = parseFloat( firefox[ 1 ] ));\n                safari && (ret.safari = parseFloat( safari[ 1 ] ));\n                opera && (ret.opera = parseFloat( opera[ 1 ] ));\n    \n                return ret;\n            })( navigator.userAgent ),\n    \n            /**\n             * @description  操作系统检查结果。\n             *\n             * * `android`  如果在android浏览器环境下，此值为对应的android版本号，否则为`undefined`。\n             * * `ios` 如果在ios浏览器环境下，此值为对应的ios版本号，否则为`undefined`。\n             * @property {Object} [os]\n             */\n            os: (function( ua ) {\n                var ret = {},\n    \n                    // osx = !!ua.match( /\\(Macintosh\\; Intel / ),\n                    android = ua.match( /(?:Android);?[\\s\\/]+([\\d.]+)?/ ),\n                    ios = ua.match( /(?:iPad|iPod|iPhone).*OS\\s([\\d_]+)/ );\n    \n                // osx && (ret.osx = true);\n                android && (ret.android = parseFloat( android[ 1 ] ));\n                ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) ));\n    \n                return ret;\n            })( navigator.userAgent ),\n    \n            /**\n             * 实现类与类之间的继承。\n             * @method inherits\n             * @grammar Base.inherits( super ) => child\n             * @grammar Base.inherits( super, protos ) => child\n             * @grammar Base.inherits( super, protos, statics ) => child\n             * @param  {Class} super 父类\n             * @param  {Object | Function} [protos] 子类或者对象。如果对象中包含constructor，子类将是用此属性值。\n             * @param  {Function} [protos.constructor] 子类构造器，不指定的话将创建个临时的直接执行父类构造器的方法。\n             * @param  {Object} [statics] 静态属性或方法。\n             * @return {Class} 返回子类。\n             * @example\n             * function Person() {\n             *     console.log( 'Super' );\n             * }\n             * Person.prototype.hello = function() {\n             *     console.log( 'hello' );\n             * };\n             *\n             * var Manager = Base.inherits( Person, {\n             *     world: function() {\n             *         console.log( 'World' );\n             *     }\n             * });\n             *\n             * // 因为没有指定构造器，父类的构造器将会执行。\n             * var instance = new Manager();    // => Super\n             *\n             * // 继承子父类的方法\n             * instance.hello();    // => hello\n             * instance.world();    // => World\n             *\n             * // 子类的__super__属性指向父类\n             * console.log( Manager.__super__ === Person );    // => true\n             */\n            inherits: function( Super, protos, staticProtos ) {\n                var child;\n    \n                if ( typeof protos === 'function' ) {\n                    child = protos;\n                    protos = null;\n                } else if ( protos && protos.hasOwnProperty('constructor') ) {\n                    child = protos.constructor;\n                } else {\n                    child = function() {\n                        return Super.apply( this, arguments );\n                    };\n                }\n    \n                // 复制静态方法\n                $.extend( true, child, Super, staticProtos || {} );\n    \n                /* jshint camelcase: false */\n    \n                // 让子类的__super__属性指向父类。\n                child.__super__ = Super.prototype;\n    \n                // 构建原型，添加原型方法或属性。\n                // 暂时用Object.create实现。\n                child.prototype = createObject( Super.prototype );\n                protos && $.extend( true, child.prototype, protos );\n    \n                return child;\n            },\n    \n            /**\n             * 一个不做任何事情的方法。可以用来赋值给默认的callback.\n             * @method noop\n             */\n            noop: noop,\n    \n            /**\n             * 返回一个新的方法，此方法将已指定的`context`来执行。\n             * @grammar Base.bindFn( fn, context ) => Function\n             * @method bindFn\n             * @example\n             * var doSomething = function() {\n             *         console.log( this.name );\n             *     },\n             *     obj = {\n             *         name: 'Object Name'\n             *     },\n             *     aliasFn = Base.bind( doSomething, obj );\n             *\n             *  aliasFn();    // => Object Name\n             *\n             */\n            bindFn: bindFn,\n    \n            /**\n             * 引用Console.log如果存在的话，否则引用一个[空函数loop](#WebUploader:Base.log)。\n             * @grammar Base.log( args... ) => undefined\n             * @method log\n             */\n            log: (function() {\n                if ( window.console ) {\n                    return bindFn( console.log, console );\n                }\n                return noop;\n            })(),\n    \n            nextTick: (function() {\n    \n                return function( cb ) {\n                    setTimeout( cb, 1 );\n                };\n    \n                // @bug 当浏览器不在当前窗口时就停了。\n                // var next = window.requestAnimationFrame ||\n                //     window.webkitRequestAnimationFrame ||\n                //     window.mozRequestAnimationFrame ||\n                //     function( cb ) {\n                //         window.setTimeout( cb, 1000 / 60 );\n                //     };\n    \n                // // fix: Uncaught TypeError: Illegal invocation\n                // return bindFn( next, window );\n            })(),\n    \n            /**\n             * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。\n             * 将用来将非数组对象转化成数组对象。\n             * @grammar Base.slice( target, start[, end] ) => Array\n             * @method slice\n             * @example\n             * function doSomthing() {\n             *     var args = Base.slice( arguments, 1 );\n             *     console.log( args );\n             * }\n             *\n             * doSomthing( 'ignored', 'arg2', 'arg3' );    // => Array [\"arg2\", \"arg3\"]\n             */\n            slice: uncurryThis( [].slice ),\n    \n            /**\n             * 生成唯一的ID\n             * @method guid\n             * @grammar Base.guid() => String\n             * @grammar Base.guid( prefx ) => String\n             */\n            guid: (function() {\n                var counter = 0;\n    \n                return function( prefix ) {\n                    var guid = (+new Date()).toString( 32 ),\n                        i = 0;\n    \n                    for ( ; i < 5; i++ ) {\n                        guid += Math.floor( Math.random() * 65535 ).toString( 32 );\n                    }\n    \n                    return (prefix || 'wu_') + guid + (counter++).toString( 32 );\n                };\n            })(),\n    \n            /**\n             * 格式化文件大小, 输出成带单位的字符串\n             * @method formatSize\n             * @grammar Base.formatSize( size ) => String\n             * @grammar Base.formatSize( size, pointLength ) => String\n             * @grammar Base.formatSize( size, pointLength, units ) => String\n             * @param {Number} size 文件大小\n             * @param {Number} [pointLength=2] 精确到的小数点数。\n             * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节，到千字节，一直往上指定。如果单位数组里面只指定了到了K(千字节)，同时文件大小大于M, 此方法的输出将还是显示成多少K.\n             * @example\n             * console.log( Base.formatSize( 100 ) );    // => 100B\n             * console.log( Base.formatSize( 1024 ) );    // => 1.00K\n             * console.log( Base.formatSize( 1024, 0 ) );    // => 1K\n             * console.log( Base.formatSize( 1024 * 1024 ) );    // => 1.00M\n             * console.log( Base.formatSize( 1024 * 1024 * 1024 ) );    // => 1.00G\n             * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) );    // => 1024MB\n             */\n            formatSize: function( size, pointLength, units ) {\n                var unit;\n    \n                units = units || [ 'B', 'K', 'M', 'G', 'TB' ];\n    \n                while ( (unit = units.shift()) && size > 1024 ) {\n                    size = size / 1024;\n                }\n    \n                return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) +\n                        unit;\n            }\n        };\n    });\n    /**\n     * 事件处理类，可以独立使用，也可以扩展给对象使用。\n     * @fileOverview Mediator\n     */\n    define('mediator',[\n        'base'\n    ], function( Base ) {\n        var $ = Base.$,\n            slice = [].slice,\n            separator = /\\s+/,\n            protos;\n    \n        // 根据条件过滤出事件handlers.\n        function findHandlers( arr, name, callback, context ) {\n            return $.grep( arr, function( handler ) {\n                return handler &&\n                        (!name || handler.e === name) &&\n                        (!callback || handler.cb === callback ||\n                        handler.cb._cb === callback) &&\n                        (!context || handler.ctx === context);\n            });\n        }\n    \n        function eachEvent( events, callback, iterator ) {\n            // 不支持对象，只支持多个event用空格隔开\n            $.each( (events || '').split( separator ), function( _, key ) {\n                iterator( key, callback );\n            });\n        }\n    \n        function triggerHanders( events, args ) {\n            var stoped = false,\n                i = -1,\n                len = events.length,\n                handler;\n    \n            while ( ++i < len ) {\n                handler = events[ i ];\n    \n                if ( handler.cb.apply( handler.ctx2, args ) === false ) {\n                    stoped = true;\n                    break;\n                }\n            }\n    \n            return !stoped;\n        }\n    \n        protos = {\n    \n            /**\n             * 绑定事件。\n             *\n             * `callback`方法在执行时，arguments将会来源于trigger的时候携带的参数。如\n             * ```javascript\n             * var obj = {};\n             *\n             * // 使得obj有事件行为\n             * Mediator.installTo( obj );\n             *\n             * obj.on( 'testa', function( arg1, arg2 ) {\n             *     console.log( arg1, arg2 ); // => 'arg1', 'arg2'\n             * });\n             *\n             * obj.trigger( 'testa', 'arg1', 'arg2' );\n             * ```\n             *\n             * 如果`callback`中，某一个方法`return false`了，则后续的其他`callback`都不会被执行到。\n             * 切会影响到`trigger`方法的返回值，为`false`。\n             *\n             * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处，\n             * 就是第一个参数为`type`，记录当前是什么事件在触发。此类`callback`的优先级比脚低，会再正常`callback`执行完后触发。\n             * ```javascript\n             * obj.on( 'all', function( type, arg1, arg2 ) {\n             *     console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2'\n             * });\n             * ```\n             *\n             * @method on\n             * @grammar on( name, callback[, context] ) => self\n             * @param  {String}   name     事件名，支持多个事件用空格隔开\n             * @param  {Function} callback 事件处理器\n             * @param  {Object}   [context]  事件处理器的上下文。\n             * @return {self} 返回自身，方便链式\n             * @chainable\n             * @class Mediator\n             */\n            on: function( name, callback, context ) {\n                var me = this,\n                    set;\n    \n                if ( !callback ) {\n                    return this;\n                }\n    \n                set = this._events || (this._events = []);\n    \n                eachEvent( name, callback, function( name, callback ) {\n                    var handler = { e: name };\n    \n                    handler.cb = callback;\n                    handler.ctx = context;\n                    handler.ctx2 = context || me;\n                    handler.id = set.length;\n    \n                    set.push( handler );\n                });\n    \n                return this;\n            },\n    \n            /**\n             * 绑定事件，且当handler执行完后，自动解除绑定。\n             * @method once\n             * @grammar once( name, callback[, context] ) => self\n             * @param  {String}   name     事件名\n             * @param  {Function} callback 事件处理器\n             * @param  {Object}   [context]  事件处理器的上下文。\n             * @return {self} 返回自身，方便链式\n             * @chainable\n             */\n            once: function( name, callback, context ) {\n                var me = this;\n    \n                if ( !callback ) {\n                    return me;\n                }\n    \n                eachEvent( name, callback, function( name, callback ) {\n                    var once = function() {\n                            me.off( name, once );\n                            return callback.apply( context || me, arguments );\n                        };\n    \n                    once._cb = callback;\n                    me.on( name, once, context );\n                });\n    \n                return me;\n            },\n    \n            /**\n             * 解除事件绑定\n             * @method off\n             * @grammar off( [name[, callback[, context] ] ] ) => self\n             * @param  {String}   [name]     事件名\n             * @param  {Function} [callback] 事件处理器\n             * @param  {Object}   [context]  事件处理器的上下文。\n             * @return {self} 返回自身，方便链式\n             * @chainable\n             */\n            off: function( name, cb, ctx ) {\n                var events = this._events;\n    \n                if ( !events ) {\n                    return this;\n                }\n    \n                if ( !name && !cb && !ctx ) {\n                    this._events = [];\n                    return this;\n                }\n    \n                eachEvent( name, cb, function( name, cb ) {\n                    $.each( findHandlers( events, name, cb, ctx ), function() {\n                        delete events[ this.id ];\n                    });\n                });\n    \n                return this;\n            },\n    \n            /**\n             * 触发事件\n             * @method trigger\n             * @grammar trigger( name[, args...] ) => self\n             * @param  {String}   type     事件名\n             * @param  {*} [...] 任意参数\n             * @return {Boolean} 如果handler中return false了，则返回false, 否则返回true\n             */\n            trigger: function( type ) {\n                var args, events, allEvents;\n    \n                if ( !this._events || !type ) {\n                    return this;\n                }\n    \n                args = slice.call( arguments, 1 );\n                events = findHandlers( this._events, type );\n                allEvents = findHandlers( this._events, 'all' );\n    \n                return triggerHanders( events, args ) &&\n                        triggerHanders( allEvents, arguments );\n            }\n        };\n    \n        /**\n         * 中介者，它本身是个单例，但可以通过[installTo](#WebUploader:Mediator:installTo)方法，使任何对象具备事件行为。\n         * 主要目的是负责模块与模块之间的合作，降低耦合度。\n         *\n         * @class Mediator\n         */\n        return $.extend({\n    \n            /**\n             * 可以通过这个接口，使任何对象具备事件功能。\n             * @method installTo\n             * @param  {Object} obj 需要具备事件行为的对象。\n             * @return {Object} 返回obj.\n             */\n            installTo: function( obj ) {\n                return $.extend( obj, protos );\n            }\n    \n        }, protos );\n    });\n    /**\n     * @fileOverview Uploader上传类\n     */\n    define('uploader',[\n        'base',\n        'mediator'\n    ], function( Base, Mediator ) {\n    \n        var $ = Base.$;\n    \n        /**\n         * 上传入口类。\n         * @class Uploader\n         * @constructor\n         * @grammar new Uploader( opts ) => Uploader\n         * @example\n         * var uploader = WebUploader.Uploader({\n         *     swf: 'path_of_swf/Uploader.swf',\n         *\n         *     // 开起分片上传。\n         *     chunked: true\n         * });\n         */\n        function Uploader( opts ) {\n            this.options = $.extend( true, {}, Uploader.options, opts );\n            this._init( this.options );\n        }\n    \n        // default Options\n        // widgets中有相应扩展\n        Uploader.options = {};\n        Mediator.installTo( Uploader.prototype );\n    \n        // 批量添加纯命令式方法。\n        $.each({\n            upload: 'start-upload',\n            stop: 'stop-upload',\n            getFile: 'get-file',\n            getFiles: 'get-files',\n            addFile: 'add-file',\n            addFiles: 'add-file',\n            sort: 'sort-files',\n            removeFile: 'remove-file',\n            skipFile: 'skip-file',\n            retry: 'retry',\n            isInProgress: 'is-in-progress',\n            makeThumb: 'make-thumb',\n            getDimension: 'get-dimension',\n            addButton: 'add-btn',\n            getRuntimeType: 'get-runtime-type',\n            refresh: 'refresh',\n            disable: 'disable',\n            enable: 'enable',\n            reset: 'reset'\n        }, function( fn, command ) {\n            Uploader.prototype[ fn ] = function() {\n                return this.request( command, arguments );\n            };\n        });\n    \n        $.extend( Uploader.prototype, {\n            state: 'pending',\n    \n            _init: function( opts ) {\n                var me = this;\n    \n                me.request( 'init', opts, function() {\n                    me.state = 'ready';\n                    me.trigger('ready');\n                });\n            },\n    \n            /**\n             * 获取或者设置Uploader配置项。\n             * @method option\n             * @grammar option( key ) => *\n             * @grammar option( key, val ) => self\n             * @example\n             *\n             * // 初始状态图片上传前不会压缩\n             * var uploader = new WebUploader.Uploader({\n             *     resize: null;\n             * });\n             *\n             * // 修改后图片上传前，尝试将图片压缩到1600 * 1600\n             * uploader.options( 'resize', {\n             *     width: 1600,\n             *     height: 1600\n             * });\n             */\n            option: function( key, val ) {\n                var opts = this.options;\n    \n                // setter\n                if ( arguments.length > 1 ) {\n    \n                    if ( $.isPlainObject( val ) &&\n                            $.isPlainObject( opts[ key ] ) ) {\n                        $.extend( opts[ key ], val );\n                    } else {\n                        opts[ key ] = val;\n                    }\n    \n                } else {    // getter\n                    return key ? opts[ key ] : opts;\n                }\n            },\n    \n            /**\n             * 获取文件统计信息。返回一个包含一下信息的对象。\n             * * `successNum` 上传成功的文件数\n             * * `uploadFailNum` 上传失败的文件数\n             * * `cancelNum` 被删除的文件数\n             * * `invalidNum` 无效的文件数\n             * * `queueNum` 还在队列中的文件数\n             * @method getStats\n             * @grammar getStats() => Object\n             */\n            getStats: function() {\n                // return this._mgr.getStats.apply( this._mgr, arguments );\n                var stats = this.request('get-stats');\n    \n                return {\n                    successNum: stats.numOfSuccess,\n    \n                    // who care?\n                    // queueFailNum: 0,\n                    cancelNum: stats.numOfCancel,\n                    invalidNum: stats.numOfInvalid,\n                    uploadFailNum: stats.numOfUploadFailed,\n                    queueNum: stats.numOfQueue\n                };\n            },\n    \n            // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器\n            trigger: function( type/*, args...*/ ) {\n                var args = [].slice.call( arguments, 1 ),\n                    opts = this.options,\n                    name = 'on' + type.substring( 0, 1 ).toUpperCase() +\n                        type.substring( 1 );\n    \n                if (\n                        // 调用通过on方法注册的handler.\n                        Mediator.trigger.apply( this, arguments ) === false ||\n    \n                        // 调用opts.onEvent\n                        $.isFunction( opts[ name ] ) &&\n                        opts[ name ].apply( this, args ) === false ||\n    \n                        // 调用this.onEvent\n                        $.isFunction( this[ name ] ) &&\n                        this[ name ].apply( this, args ) === false ||\n    \n                        // 广播所有uploader的事件。\n                        Mediator.trigger.apply( Mediator,\n                        [ this, type ].concat( args ) ) === false ) {\n    \n                    return false;\n                }\n    \n                return true;\n            },\n    \n            // widgets/widget.js将补充此方法的详细文档。\n            request: Base.noop\n        });\n    \n        /**\n         * 创建Uploader实例，等同于new Uploader( opts );\n         * @method create\n         * @class Base\n         * @static\n         * @grammar Base.create( opts ) => Uploader\n         */\n        Base.create = Uploader.create = function( opts ) {\n            return new Uploader( opts );\n        };\n    \n        // 暴露Uploader，可以通过它来扩展业务逻辑。\n        Base.Uploader = Uploader;\n    \n        return Uploader;\n    });\n    /**\n     * @fileOverview Runtime管理器，负责Runtime的选择, 连接\n     */\n    define('runtime/runtime',[\n        'base',\n        'mediator'\n    ], function( Base, Mediator ) {\n    \n        var $ = Base.$,\n            factories = {},\n    \n            // 获取对象的第一个key\n            getFirstKey = function( obj ) {\n                for ( var key in obj ) {\n                    if ( obj.hasOwnProperty( key ) ) {\n                        return key;\n                    }\n                }\n                return null;\n            };\n    \n        // 接口类。\n        function Runtime( options ) {\n            this.options = $.extend({\n                container: document.body\n            }, options );\n            this.uid = Base.guid('rt_');\n        }\n    \n        $.extend( Runtime.prototype, {\n    \n            getContainer: function() {\n                var opts = this.options,\n                    parent, container;\n    \n                if ( this._container ) {\n                    return this._container;\n                }\n    \n                parent = $( opts.container || document.body );\n                container = $( document.createElement('div') );\n    \n                container.attr( 'id', 'rt_' + this.uid );\n                container.css({\n                    position: 'absolute',\n                    top: '0px',\n                    left: '0px',\n                    width: '1px',\n                    height: '1px',\n                    overflow: 'hidden'\n                });\n    \n                parent.append( container );\n                parent.addClass('webuploader-container');\n                this._container = container;\n                return container;\n            },\n    \n            init: Base.noop,\n            exec: Base.noop,\n    \n            destroy: function() {\n                if ( this._container ) {\n                    this._container.parentNode.removeChild( this.__container );\n                }\n    \n                this.off();\n            }\n        });\n    \n        Runtime.orders = 'html5,flash';\n    \n    \n        /**\n         * 添加Runtime实现。\n         * @param {String} type    类型\n         * @param {Runtime} factory 具体Runtime实现。\n         */\n        Runtime.addRuntime = function( type, factory ) {\n            factories[ type ] = factory;\n        };\n    \n        Runtime.hasRuntime = function( type ) {\n            return !!(type ? factories[ type ] : getFirstKey( factories ));\n        };\n    \n        Runtime.create = function( opts, orders ) {\n            var type, runtime;\n    \n            orders = orders || Runtime.orders;\n            $.each( orders.split( /\\s*,\\s*/g ), function() {\n                if ( factories[ this ] ) {\n                    type = this;\n                    return false;\n                }\n            });\n    \n            type = type || getFirstKey( factories );\n    \n            if ( !type ) {\n                throw new Error('Runtime Error');\n            }\n    \n            runtime = new factories[ type ]( opts );\n            return runtime;\n        };\n    \n        Mediator.installTo( Runtime.prototype );\n        return Runtime;\n    });\n    \n    /**\n     * @fileOverview Runtime管理器，负责Runtime的选择, 连接\n     */\n    define('runtime/client',[\n        'base',\n        'mediator',\n        'runtime/runtime'\n    ], function( Base, Mediator, Runtime ) {\n    \n        var cache;\n    \n        cache = (function() {\n            var obj = {};\n    \n            return {\n                add: function( runtime ) {\n                    obj[ runtime.uid ] = runtime;\n                },\n    \n                get: function( ruid, standalone ) {\n                    var i;\n    \n                    if ( ruid ) {\n                        return obj[ ruid ];\n                    }\n    \n                    for ( i in obj ) {\n                        // 有些类型不能重用，比如filepicker.\n                        if ( standalone && obj[ i ].__standalone ) {\n                            continue;\n                        }\n    \n                        return obj[ i ];\n                    }\n    \n                    return null;\n                },\n    \n                remove: function( runtime ) {\n                    delete obj[ runtime.uid ];\n                }\n            };\n        })();\n    \n        function RuntimeClient( component, standalone ) {\n            var deferred = Base.Deferred(),\n                runtime;\n    \n            this.uid = Base.guid('client_');\n    \n            // 允许runtime没有初始化之前，注册一些方法在初始化后执行。\n            this.runtimeReady = function( cb ) {\n                return deferred.done( cb );\n            };\n    \n            this.connectRuntime = function( opts, cb ) {\n    \n                // already connected.\n                if ( runtime ) {\n                    throw new Error('already connected!');\n                }\n    \n                deferred.done( cb );\n    \n                if ( typeof opts === 'string' && cache.get( opts ) ) {\n                    runtime = cache.get( opts );\n                }\n    \n                // 像filePicker只能独立存在，不能公用。\n                runtime = runtime || cache.get( null, standalone );\n    \n                // 需要创建\n                if ( !runtime ) {\n                    runtime = Runtime.create( opts, opts.runtimeOrder );\n                    runtime.__promise = deferred.promise();\n                    runtime.once( 'ready', deferred.resolve );\n                    runtime.init();\n                    cache.add( runtime );\n                    runtime.__client = 1;\n                } else {\n                    // 来自cache\n                    Base.$.extend( runtime.options, opts );\n                    runtime.__promise.then( deferred.resolve );\n                    runtime.__client++;\n                }\n    \n                standalone && (runtime.__standalone = standalone);\n                return runtime;\n            };\n    \n            this.getRuntime = function() {\n                return runtime;\n            };\n    \n            this.disconnectRuntime = function() {\n                if ( !runtime ) {\n                    return;\n                }\n    \n                runtime.__client--;\n    \n                if ( runtime.__client <= 0 ) {\n                    cache.remove( runtime );\n                    delete runtime.__promise;\n                    runtime.destroy();\n                }\n    \n                runtime = null;\n            };\n    \n            this.exec = function() {\n                if ( !runtime ) {\n                    return;\n                }\n    \n                var args = Base.slice( arguments );\n                component && args.unshift( component );\n    \n                return runtime.exec.apply( this, args );\n            };\n    \n            this.getRuid = function() {\n                return runtime && runtime.uid;\n            };\n    \n            this.destroy = (function( destroy ) {\n                return function() {\n                    destroy && destroy.apply( this, arguments );\n                    this.trigger('destroy');\n                    this.off();\n                    this.exec('destroy');\n                    this.disconnectRuntime();\n                };\n            })( this.destroy );\n        }\n    \n        Mediator.installTo( RuntimeClient.prototype );\n        return RuntimeClient;\n    });\n    /**\n     * @fileOverview 错误信息\n     */\n    define('lib/dnd',[\n        'base',\n        'mediator',\n        'runtime/client'\n    ], function( Base, Mediator, RuntimeClent ) {\n    \n        var $ = Base.$;\n    \n        function DragAndDrop( opts ) {\n            opts = this.options = $.extend({}, DragAndDrop.options, opts );\n    \n            opts.container = $( opts.container );\n    \n            if ( !opts.container.length ) {\n                return;\n            }\n    \n            RuntimeClent.call( this, 'DragAndDrop' );\n        }\n    \n        DragAndDrop.options = {\n            accept: null,\n            disableGlobalDnd: false\n        };\n    \n        Base.inherits( RuntimeClent, {\n            constructor: DragAndDrop,\n    \n            init: function() {\n                var me = this;\n    \n                me.connectRuntime( me.options, function() {\n                    me.exec('init');\n                    me.trigger('ready');\n                });\n            },\n    \n            destroy: function() {\n                this.disconnectRuntime();\n            }\n        });\n    \n        Mediator.installTo( DragAndDrop.prototype );\n    \n        return DragAndDrop;\n    });\n    /**\n     * @fileOverview 组件基类。\n     */\n    define('widgets/widget',[\n        'base',\n        'uploader'\n    ], function( Base, Uploader ) {\n    \n        var $ = Base.$,\n            _init = Uploader.prototype._init,\n            IGNORE = {},\n            widgetClass = [];\n    \n        function isArrayLike( obj ) {\n            if ( !obj ) {\n                return false;\n            }\n    \n            var length = obj.length,\n                type = $.type( obj );\n    \n            if ( obj.nodeType === 1 && length ) {\n                return true;\n            }\n    \n            return type === 'array' || type !== 'function' && type !== 'string' &&\n                    (length === 0 || typeof length === 'number' && length > 0 &&\n                    (length - 1) in obj);\n        }\n    \n        function Widget( uploader ) {\n            this.owner = uploader;\n            this.options = uploader.options;\n        }\n    \n        $.extend( Widget.prototype, {\n    \n            init: Base.noop,\n    \n            // 类Backbone的事件监听声明，监听uploader实例上的事件\n            // widget直接无法监听事件，事件只能通过uploader来传递\n            invoke: function( apiName, args ) {\n    \n                /*\n                    {\n                        'make-thumb': 'makeThumb'\n                    }\n                 */\n                var map = this.responseMap;\n    \n                // 如果无API响应声明则忽略\n                if ( !map || !(apiName in map) || !(map[ apiName ] in this) ||\n                        !$.isFunction( this[ map[ apiName ] ] ) ) {\n    \n                    return IGNORE;\n                }\n    \n                return this[ map[ apiName ] ].apply( this, args );\n    \n            },\n    \n            /**\n             * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。\n             * @method request\n             * @grammar request( command, args ) => * | Promise\n             * @grammar request( command, args, callback ) => Promise\n             * @for  Uploader\n             */\n            request: function() {\n                return this.owner.request.apply( this.owner, arguments );\n            }\n        });\n    \n        // 扩展Uploader.\n        $.extend( Uploader.prototype, {\n    \n            // 覆写_init用来初始化widgets\n            _init: function() {\n                var me = this,\n                    widgets = me._widgets = [];\n    \n                $.each( widgetClass, function( _, klass ) {\n                    widgets.push( new klass( me ) );\n                });\n    \n                return _init.apply( me, arguments );\n            },\n    \n            request: function( apiName, args, callback ) {\n                var i = 0,\n                    widgets = this._widgets,\n                    len = widgets.length,\n                    rlts = [],\n                    dfds = [],\n                    widget, rlt, promise, key;\n    \n                args = isArrayLike( args ) ? args : [ args ];\n    \n                for ( ; i < len; i++ ) {\n                    widget = widgets[ i ];\n                    rlt = widget.invoke( apiName, args );\n    \n                    if ( rlt !== IGNORE ) {\n    \n                        // Deferred对象\n                        if ( Base.isPromise( rlt ) ) {\n                            dfds.push( rlt );\n                        } else {\n                            rlts.push( rlt );\n                        }\n                    }\n                }\n    \n                // 如果有callback，则用异步方式。\n                if ( callback || dfds.length ) {\n                    promise = Base.when.apply( Base, dfds );\n                    key = promise.pipe ? 'pipe' : 'then';\n    \n                    // 很重要不能删除。删除了会死循环。\n                    // 保证执行顺序。让callback总是在下一个tick中执行。\n                    return promise[ key ](function() {\n                                var deferred = Base.Deferred(),\n                                    args = arguments;\n    \n                                setTimeout(function() {\n                                    deferred.resolve.apply( deferred, args );\n                                }, 1 );\n    \n                                return deferred.promise();\n                            })[ key ]( callback || Base.noop );\n                } else {\n                    return rlts[ 0 ];\n                }\n            }\n        });\n    \n        /**\n         * 添加组件\n         * @param  {object} widgetProto 组件原型，构造函数通过constructor属性定义\n         * @param  {object} responseMap API名称与函数实现的映射\n         * @example\n         *     Uploader.register( {\n         *         init: function( options ) {},\n         *         makeThumb: function() {}\n         *     }, {\n         *         'make-thumb': 'makeThumb'\n         *     } );\n         */\n        Uploader.register = Widget.register = function( responseMap, widgetProto ) {\n            var map = { init: 'init' },\n                klass;\n    \n            if ( arguments.length === 1 ) {\n                widgetProto = responseMap;\n                widgetProto.responseMap = map;\n            } else {\n                widgetProto.responseMap = $.extend( map, responseMap );\n            }\n    \n            klass = Base.inherits( Widget, widgetProto );\n            widgetClass.push( klass );\n    \n            return klass;\n        };\n    \n        return Widget;\n    });\n    /**\n     * @fileOverview DragAndDrop Widget。\n     */\n    define('widgets/filednd',[\n        'base',\n        'uploader',\n        'lib/dnd',\n        'widgets/widget'\n    ], function( Base, Uploader, Dnd ) {\n        var $ = Base.$;\n    \n        Uploader.options.dnd = '';\n    \n        /**\n         * @property {Selector} [dnd=undefined]  指定Drag And Drop拖拽的容器，如果不指定，则不启动。\n         * @namespace options\n         * @for Uploader\n         */\n    \n        /**\n         * @event dndAccept\n         * @param {DataTransferItemList} items DataTransferItem\n         * @description 阻止此事件可以拒绝某些类型的文件拖入进来。目前只有 chrome 提供这样的 API，且只能通过 mime-type 验证。\n         * @for  Uploader\n         */\n        return Uploader.register({\n            init: function( opts ) {\n    \n                if ( !opts.dnd ||\n                        this.request('predict-runtime-type') !== 'html5' ) {\n                    return;\n                }\n    \n                var me = this,\n                    deferred = Base.Deferred(),\n                    options = $.extend({}, {\n                        disableGlobalDnd: opts.disableGlobalDnd,\n                        container: opts.dnd,\n                        accept: opts.accept\n                    }),\n                    dnd;\n    \n                dnd = new Dnd( options );\n    \n                dnd.once( 'ready', deferred.resolve );\n                dnd.on( 'drop', function( files ) {\n                    me.request( 'add-file', [ files ]);\n                });\n    \n                // 检测文件是否全部允许添加。\n                dnd.on( 'accept', function( items ) {\n                    return me.owner.trigger( 'dndAccept', items );\n                });\n    \n                dnd.init();\n    \n                return deferred.promise();\n            }\n        });\n    });\n    \n    /**\n     * @fileOverview 错误信息\n     */\n    define('lib/filepaste',[\n        'base',\n        'mediator',\n        'runtime/client'\n    ], function( Base, Mediator, RuntimeClent ) {\n    \n        var $ = Base.$;\n    \n        function FilePaste( opts ) {\n            opts = this.options = $.extend({}, opts );\n            opts.container = $( opts.container || document.body );\n            RuntimeClent.call( this, 'FilePaste' );\n        }\n    \n        Base.inherits( RuntimeClent, {\n            constructor: FilePaste,\n    \n            init: function() {\n                var me = this;\n    \n                me.connectRuntime( me.options, function() {\n                    me.exec('init');\n                    me.trigger('ready');\n                });\n            },\n    \n            destroy: function() {\n                this.exec('destroy');\n                this.disconnectRuntime();\n                this.off();\n            }\n        });\n    \n        Mediator.installTo( FilePaste.prototype );\n    \n        return FilePaste;\n    });\n    /**\n     * @fileOverview 组件基类。\n     */\n    define('widgets/filepaste',[\n        'base',\n        'uploader',\n        'lib/filepaste',\n        'widgets/widget'\n    ], function( Base, Uploader, FilePaste ) {\n        var $ = Base.$;\n    \n        /**\n         * @property {Selector} [paste=undefined]  指定监听paste事件的容器，如果不指定，不启用此功能。此功能为通过粘贴来添加截屏的图片。建议设置为`document.body`.\n         * @namespace options\n         * @for Uploader\n         */\n        return Uploader.register({\n            init: function( opts ) {\n    \n                if ( !opts.paste ||\n                        this.request('predict-runtime-type') !== 'html5' ) {\n                    return;\n                }\n    \n                var me = this,\n                    deferred = Base.Deferred(),\n                    options = $.extend({}, {\n                        container: opts.paste,\n                        accept: opts.accept\n                    }),\n                    paste;\n    \n                paste = new FilePaste( options );\n    \n                paste.once( 'ready', deferred.resolve );\n                paste.on( 'paste', function( files ) {\n                    me.owner.request( 'add-file', [ files ]);\n                });\n                paste.init();\n    \n                return deferred.promise();\n            }\n        });\n    });\n    /**\n     * @fileOverview Blob\n     */\n    define('lib/blob',[\n        'base',\n        'runtime/client'\n    ], function( Base, RuntimeClient ) {\n    \n        function Blob( ruid, source ) {\n            var me = this;\n    \n            me.source = source;\n            me.ruid = ruid;\n    \n            RuntimeClient.call( me, 'Blob' );\n    \n            this.uid = source.uid || this.uid;\n            this.type = source.type || '';\n            this.size = source.size || 0;\n    \n            if ( ruid ) {\n                me.connectRuntime( ruid );\n            }\n        }\n    \n        Base.inherits( RuntimeClient, {\n            constructor: Blob,\n    \n            slice: function( start, end ) {\n                return this.exec( 'slice', start, end );\n            },\n    \n            getSource: function() {\n                return this.source;\n            }\n        });\n    \n        return Blob;\n    });\n    /**\n     * 为了统一化Flash的File和HTML5的File而存在。\n     * 以至于要调用Flash里面的File，也可以像调用HTML5版本的File一下。\n     * @fileOverview File\n     */\n    define('lib/file',[\n        'base',\n        'lib/blob'\n    ], function( Base, Blob ) {\n    \n        var uid = 1,\n            rExt = /\\.([^.]+)$/;\n    \n        function File( ruid, file ) {\n            var ext;\n    \n            Blob.apply( this, arguments );\n            this.name = file.name || ('untitled' + uid++);\n            ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : '';\n    \n            // todo 支持其他类型文件的转换。\n    \n            // 如果有mimetype, 但是文件名里面没有找出后缀规律\n            if ( !ext && this.type ) {\n                ext = /\\/(jpg|jpeg|png|gif|bmp)$/i.exec( this.type ) ?\n                        RegExp.$1.toLowerCase() : '';\n                this.name += '.' + ext;\n            }\n    \n            // 如果没有指定mimetype, 但是知道文件后缀。\n            if ( !this.type &&  ~'jpg,jpeg,png,gif,bmp'.indexOf( ext ) ) {\n                this.type = 'image/' + (ext === 'jpg' ? 'jpeg' : ext);\n            }\n    \n            this.ext = ext;\n            this.lastModifiedDate = file.lastModifiedDate ||\n                    (new Date()).toLocaleString();\n        }\n    \n        return Base.inherits( Blob, File );\n    });\n    \n    /**\n     * @fileOverview 错误信息\n     */\n    define('lib/filepicker',[\n        'base',\n        'runtime/client',\n        'lib/file'\n    ], function( Base, RuntimeClent, File ) {\n    \n        var $ = Base.$;\n    \n        function FilePicker( opts ) {\n            opts = this.options = $.extend({}, FilePicker.options, opts );\n    \n            opts.container = $( opts.id );\n    \n            if ( !opts.container.length ) {\n                throw new Error('按钮指定错误');\n            }\n    \n            opts.innerHTML = opts.innerHTML || opts.label ||\n                    opts.container.html() || '';\n    \n            opts.button = $( opts.button || document.createElement('div') );\n            opts.button.html( opts.innerHTML );\n            opts.container.html( opts.button );\n    \n            RuntimeClent.call( this, 'FilePicker', true );\n        }\n    \n        FilePicker.options = {\n            button: null,\n            container: null,\n            label: null,\n            innerHTML: null,\n            multiple: true,\n            accept: null,\n            name: 'file'\n        };\n    \n        Base.inherits( RuntimeClent, {\n            constructor: FilePicker,\n    \n            init: function() {\n                var me = this,\n                    opts = me.options,\n                    button = opts.button;\n    \n                button.addClass('webuploader-pick');\n    \n                me.on( 'all', function( type ) {\n                    var files;\n    \n                    switch ( type ) {\n                        case 'mouseenter':\n                            button.addClass('webuploader-pick-hover');\n                            break;\n    \n                        case 'mouseleave':\n                            button.removeClass('webuploader-pick-hover');\n                            break;\n    \n                        case 'change':\n                            files = me.exec('getFiles');\n                            me.trigger( 'select', $.map( files, function( file ) {\n                                file = new File( me.getRuid(), file );\n    \n                                // 记录来源。\n                                file._refer = opts.container;\n                                return file;\n                            }), opts.container );\n                            break;\n                    }\n                });\n    \n                me.connectRuntime( opts, function() {\n                    me.refresh();\n                    me.exec( 'init', opts );\n                    me.trigger('ready');\n                });\n    \n                $( window ).on( 'resize', function() {\n                    me.refresh();\n                });\n            },\n    \n            refresh: function() {\n                var shimContainer = this.getRuntime().getContainer(),\n                    button = this.options.button,\n                    width = button.outerWidth ?\n                            button.outerWidth() : button.width(),\n    \n                    height = button.outerHeight ?\n                            button.outerHeight() : button.height(),\n    \n                    pos = button.offset();\n    \n                width && height && shimContainer.css({\n                    bottom: 'auto',\n                    right: 'auto',\n                    width: width + 'px',\n                    height: height + 'px'\n                }).offset( pos );\n            },\n    \n            enable: function() {\n                var btn = this.options.button;\n    \n                btn.removeClass('webuploader-pick-disable');\n                this.refresh();\n            },\n    \n            disable: function() {\n                var btn = this.options.button;\n    \n                this.getRuntime().getContainer().css({\n                    top: '-99999px'\n                });\n    \n                btn.addClass('webuploader-pick-disable');\n            },\n    \n            destroy: function() {\n                if ( this.runtime ) {\n                    this.exec('destroy');\n                    this.disconnectRuntime();\n                }\n            }\n        });\n    \n        return FilePicker;\n    });\n    \n    /**\n     * @fileOverview 文件选择相关\n     */\n    define('widgets/filepicker',[\n        'base',\n        'uploader',\n        'lib/filepicker',\n        'widgets/widget'\n    ], function( Base, Uploader, FilePicker ) {\n        var $ = Base.$;\n    \n        $.extend( Uploader.options, {\n    \n            /**\n             * @property {Selector | Object} [pick=undefined]\n             * @namespace options\n             * @for Uploader\n             * @description 指定选择文件的按钮容器，不指定则不创建按钮。\n             *\n             * * `id` {Seletor} 指定选择文件的按钮容器，不指定则不创建按钮。\n             * * `label` {String} 请采用 `innerHTML` 代替\n             * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。\n             * * `multiple` {Boolean} 是否开起同时选择多个文件能力。\n             */\n            pick: null,\n    \n            /**\n             * @property {Arroy} [accept=null]\n             * @namespace options\n             * @for Uploader\n             * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表，所以这里需要分开指定。\n             *\n             * * `title` {String} 文字描述\n             * * `extensions` {String} 允许的文件后缀，不带点，多个用逗号分割。\n             * * `mimeTypes` {String} 多个用逗号分割。\n             *\n             * 如：\n             *\n             * ```\n             * {\n             *     title: 'Images',\n             *     extensions: 'gif,jpg,jpeg,bmp,png',\n             *     mimeTypes: 'image/*'\n             * }\n             * ```\n             */\n            accept: null/*{\n                title: 'Images',\n                extensions: 'gif,jpg,jpeg,bmp,png',\n                mimeTypes: 'image/*'\n            }*/\n        });\n    \n        return Uploader.register({\n            'add-btn': 'addButton',\n            refresh: 'refresh',\n            disable: 'disable',\n            enable: 'enable'\n        }, {\n    \n            init: function( opts ) {\n                this.pickers = [];\n                return opts.pick && this.addButton( opts.pick );\n            },\n    \n            refresh: function() {\n                $.each( this.pickers, function() {\n                    this.refresh();\n                });\n            },\n    \n            /**\n             * @method addButton\n             * @for Uploader\n             * @grammar addButton( pick ) => Promise\n             * @description\n             * 添加文件选择按钮，如果一个按钮不够，需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。\n             * @example\n             * uploader.addButton({\n             *     id: '#btnContainer',\n             *     innerHTML: '选择文件'\n             * });\n             */\n            addButton: function( pick ) {\n                var me = this,\n                    opts = me.options,\n                    accept = opts.accept,\n                    options, picker, deferred;\n    \n                if ( !pick ) {\n                    return;\n                }\n    \n                deferred = Base.Deferred();\n                $.isPlainObject( pick ) || (pick = {\n                    id: pick\n                });\n    \n                options = $.extend({}, pick, {\n                    accept: $.isPlainObject( accept ) ? [ accept ] : accept,\n                    swf: opts.swf,\n                    runtimeOrder: opts.runtimeOrder\n                });\n    \n                picker = new FilePicker( options );\n    \n                picker.once( 'ready', deferred.resolve );\n                picker.on( 'select', function( files ) {\n                    me.owner.request( 'add-file', [ files ]);\n                });\n                picker.init();\n    \n                this.pickers.push( picker );\n    \n                return deferred.promise();\n            },\n    \n            disable: function() {\n                $.each( this.pickers, function() {\n                    this.disable();\n                });\n            },\n    \n            enable: function() {\n                $.each( this.pickers, function() {\n                    this.enable();\n                });\n            }\n        });\n    });\n    /**\n     * @fileOverview Image\n     */\n    define('lib/image',[\n        'base',\n        'runtime/client',\n        'lib/blob'\n    ], function( Base, RuntimeClient, Blob ) {\n        var $ = Base.$;\n    \n        // 构造器。\n        function Image( opts ) {\n            this.options = $.extend({}, Image.options, opts );\n            RuntimeClient.call( this, 'Image' );\n    \n            this.on( 'load', function() {\n                this._info = this.exec('info');\n                this._meta = this.exec('meta');\n            });\n        }\n    \n        // 默认选项。\n        Image.options = {\n    \n            // 默认的图片处理质量\n            quality: 90,\n    \n            // 是否裁剪\n            crop: false,\n    \n            // 是否保留头部信息\n            preserveHeaders: true,\n    \n            // 是否允许放大。\n            allowMagnify: true\n        };\n    \n        // 继承RuntimeClient.\n        Base.inherits( RuntimeClient, {\n            constructor: Image,\n    \n            info: function( val ) {\n    \n                // setter\n                if ( val ) {\n                    this._info = val;\n                    return this;\n                }\n    \n                // getter\n                return this._info;\n            },\n    \n            meta: function( val ) {\n    \n                // setter\n                if ( val ) {\n                    this._meta = val;\n                    return this;\n                }\n    \n                // getter\n                return this._meta;\n            },\n    \n            loadFromBlob: function( blob ) {\n                var me = this,\n                    ruid = blob.getRuid();\n    \n                this.connectRuntime( ruid, function() {\n                    me.exec( 'init', me.options );\n                    me.exec( 'loadFromBlob', blob );\n                });\n            },\n    \n            resize: function() {\n                var args = Base.slice( arguments );\n                return this.exec.apply( this, [ 'resize' ].concat( args ) );\n            },\n    \n            getAsDataUrl: function( type ) {\n                return this.exec( 'getAsDataUrl', type );\n            },\n    \n            getAsBlob: function( type ) {\n                var blob = this.exec( 'getAsBlob', type );\n    \n                return new Blob( this.getRuid(), blob );\n            }\n        });\n    \n        return Image;\n    });\n    /**\n     * @fileOverview 图片操作, 负责预览图片和上传前压缩图片\n     */\n    define('widgets/image',[\n        'base',\n        'uploader',\n        'lib/image',\n        'widgets/widget'\n    ], function( Base, Uploader, Image ) {\n    \n        var $ = Base.$,\n            throttle;\n    \n        // 根据要处理的文件大小来节流，一次不能处理太多，会卡。\n        throttle = (function( max ) {\n            var occupied = 0,\n                waiting = [],\n                tick = function() {\n                    var item;\n    \n                    while ( waiting.length && occupied < max ) {\n                        item = waiting.shift();\n                        occupied += item[ 0 ];\n                        item[ 1 ]();\n                    }\n                };\n    \n            return function( emiter, size, cb ) {\n                waiting.push([ size, cb ]);\n                emiter.once( 'destroy', function() {\n                    occupied -= size;\n                    setTimeout( tick, 1 );\n                });\n                setTimeout( tick, 1 );\n            };\n        })( 5 * 1024 * 1024 );\n    \n        $.extend( Uploader.options, {\n    \n            /**\n             * @property {Object} [thumb]\n             * @namespace options\n             * @for Uploader\n             * @description 配置生成缩略图的选项。\n             *\n             * 默认为：\n             *\n             * ```javascript\n             * {\n             *     width: 110,\n             *     height: 110,\n             *\n             *     // 图片质量，只有type为`image/jpeg`的时候才有效。\n             *     quality: 70,\n             *\n             *     // 是否允许放大，如果想要生成小图的时候不失真，此选项应该设置为false.\n             *     allowMagnify: true,\n             *\n             *     // 是否允许裁剪。\n             *     crop: true,\n             *\n             *     // 是否保留头部meta信息。\n             *     preserveHeaders: false,\n             *\n             *     // 为空的话则保留原有图片格式。\n             *     // 否则强制转换成指定的类型。\n             *     type: 'image/jpeg'\n             * }\n             * ```\n             */\n            thumb: {\n                width: 110,\n                height: 110,\n                quality: 70,\n                allowMagnify: true,\n                crop: true,\n                preserveHeaders: false,\n    \n                // 为空的话则保留原有图片格式。\n                // 否则强制转换成指定的类型。\n                // IE 8下面 base64 大小不能超过 32K 否则预览失败，而非 jpeg 编码的图片很可\n                // 能会超过 32k, 所以这里设置成预览的时候都是 image/jpeg\n                type: 'image/jpeg'\n            },\n    \n            /**\n             * @property {Object} [compress]\n             * @namespace options\n             * @for Uploader\n             * @description 配置压缩的图片的选项。如果此选项为`false`, 则图片在上传前不进行压缩。\n             *\n             * 默认为：\n             *\n             * ```javascript\n             * {\n             *     width: 1600,\n             *     height: 1600,\n             *\n             *     // 图片质量，只有type为`image/jpeg`的时候才有效。\n             *     quality: 90,\n             *\n             *     // 是否允许放大，如果想要生成小图的时候不失真，此选项应该设置为false.\n             *     allowMagnify: false,\n             *\n             *     // 是否允许裁剪。\n             *     crop: false,\n             *\n             *     // 是否保留头部meta信息。\n             *     preserveHeaders: true\n             * }\n             * ```\n             */\n            compress: {\n                width: 1600,\n                height: 1600,\n                quality: 90,\n                allowMagnify: false,\n                crop: false,\n                preserveHeaders: true\n            }\n        });\n    \n        return Uploader.register({\n            'make-thumb': 'makeThumb',\n            'before-send-file': 'compressImage'\n        }, {\n    \n    \n            /**\n             * 生成缩略图，此过程为异步，所以需要传入`callback`。\n             * 通常情况在图片加入队里后调用此方法来生成预览图以增强交互效果。\n             *\n             * `callback`中可以接收到两个参数。\n             * * 第一个为error，如果生成缩略图有错误，此error将为真。\n             * * 第二个为ret, 缩略图的Data URL值。\n             *\n             * **注意**\n             * Date URL在IE6/7中不支持，所以不用调用此方法了，直接显示一张暂不支持预览图片好了。\n             *\n             *\n             * @method makeThumb\n             * @grammar makeThumb( file, callback ) => undefined\n             * @grammar makeThumb( file, callback, width, height ) => undefined\n             * @for Uploader\n             * @example\n             *\n             * uploader.on( 'fileQueued', function( file ) {\n             *     var $li = ...;\n             *\n             *     uploader.makeThumb( file, function( error, ret ) {\n             *         if ( error ) {\n             *             $li.text('预览错误');\n             *         } else {\n             *             $li.append('<img alt=\"\" src=\"' + ret + '\" />');\n             *         }\n             *     });\n             *\n             * });\n             */\n            makeThumb: function( file, cb, width, height ) {\n                var opts, image;\n    \n                file = this.request( 'get-file', file );\n    \n                // 只预览图片格式。\n                if ( !file.type.match( /^image/ ) ) {\n                    cb( true );\n                    return;\n                }\n    \n                opts = $.extend({}, this.options.thumb );\n    \n                // 如果传入的是object.\n                if ( $.isPlainObject( width ) ) {\n                    opts = $.extend( opts, width );\n                    width = null;\n                }\n    \n                width = width || opts.width;\n                height = height || opts.height;\n    \n                image = new Image( opts );\n    \n                image.once( 'load', function() {\n                    file._info = file._info || image.info();\n                    file._meta = file._meta || image.meta();\n                    image.resize( width, height );\n                });\n    \n                image.once( 'complete', function() {\n                    cb( false, image.getAsDataUrl( opts.type ) );\n                    image.destroy();\n                });\n    \n                image.once( 'error', function() {\n                    cb( true );\n                    image.destroy();\n                });\n    \n                throttle( image, file.source.size, function() {\n                    file._info && image.info( file._info );\n                    file._meta && image.meta( file._meta );\n                    image.loadFromBlob( file.source );\n                });\n            },\n    \n            compressImage: function( file ) {\n                var opts = this.options.compress || this.options.resize,\n                    compressSize = opts && opts.compressSize || 300 * 1024,\n                    image, deferred;\n    \n                file = this.request( 'get-file', file );\n    \n                // 只预览图片格式。\n                if ( !opts || !~'image/jpeg,image/jpg'.indexOf( file.type ) ||\n                        file.size < compressSize ||\n                        file._compressed ) {\n                    return;\n                }\n    \n                opts = $.extend({}, opts );\n                deferred = Base.Deferred();\n    \n                image = new Image( opts );\n    \n                deferred.always(function() {\n                    image.destroy();\n                    image = null;\n                });\n                image.once( 'error', deferred.reject );\n                image.once( 'load', function() {\n                    file._info = file._info || image.info();\n                    file._meta = file._meta || image.meta();\n                    image.resize( opts.width, opts.height );\n                });\n    \n                image.once( 'complete', function() {\n                    var blob, size;\n    \n                    // 移动端 UC / qq 浏览器的无图模式下\n                    // ctx.getImageData 处理大图的时候会报 Exception\n                    // INDEX_SIZE_ERR: DOM Exception 1\n                    try {\n                        blob = image.getAsBlob( opts.type );\n    \n                        size = file.size;\n    \n                        // 如果压缩后，比原来还大则不用压缩后的。\n                        if ( blob.size < size ) {\n                            // file.source.destroy && file.source.destroy();\n                            file.source = blob;\n                            file.size = blob.size;\n    \n                            file.trigger( 'resize', blob.size, size );\n                        }\n    \n                        // 标记，避免重复压缩。\n                        file._compressed = true;\n                        deferred.resolve();\n                    } catch ( e ) {\n                        // 出错了直接继续，让其上传原始图片\n                        deferred.resolve();\n                    }\n                });\n    \n                file._info && image.info( file._info );\n                file._meta && image.meta( file._meta );\n    \n                image.loadFromBlob( file.source );\n                return deferred.promise();\n            }\n        });\n    });\n    /**\n     * @fileOverview 文件属性封装\n     */\n    define('file',[\n        'base',\n        'mediator'\n    ], function( Base, Mediator ) {\n    \n        var $ = Base.$,\n            idPrefix = 'WU_FILE_',\n            idSuffix = 0,\n            rExt = /\\.([^.]+)$/,\n            statusMap = {};\n    \n        function gid() {\n            return idPrefix + idSuffix++;\n        }\n    \n        /**\n         * 文件类\n         * @class File\n         * @constructor 构造函数\n         * @grammar new File( source ) => File\n         * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。\n         */\n        function WUFile( source ) {\n    \n            /**\n             * 文件名，包括扩展名（后缀）\n             * @property name\n             * @type {string}\n             */\n            this.name = source.name || 'Untitled';\n    \n            /**\n             * 文件体积（字节）\n             * @property size\n             * @type {uint}\n             * @default 0\n             */\n            this.size = source.size || 0;\n    \n            /**\n             * 文件MIMETYPE类型，与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny)\n             * @property type\n             * @type {string}\n             * @default 'application'\n             */\n            this.type = source.type || 'application';\n    \n            /**\n             * 文件最后修改日期\n             * @property lastModifiedDate\n             * @type {int}\n             * @default 当前时间戳\n             */\n            this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1);\n    \n            /**\n             * 文件ID，每个对象具有唯一ID，与文件名无关\n             * @property id\n             * @type {string}\n             */\n            this.id = gid();\n    \n            /**\n             * 文件扩展名，通过文件名获取，例如test.png的扩展名为png\n             * @property ext\n             * @type {string}\n             */\n            this.ext = rExt.exec( this.name ) ? RegExp.$1 : '';\n    \n    \n            /**\n             * 状态文字说明。在不同的status语境下有不同的用途。\n             * @property statusText\n             * @type {string}\n             */\n            this.statusText = '';\n    \n            // 存储文件状态，防止通过属性直接修改\n            statusMap[ this.id ] = WUFile.Status.INITED;\n    \n            this.source = source;\n            this.loaded = 0;\n    \n            this.on( 'error', function( msg ) {\n                this.setStatus( WUFile.Status.ERROR, msg );\n            });\n        }\n    \n        $.extend( WUFile.prototype, {\n    \n            /**\n             * 设置状态，状态变化时会触发`change`事件。\n             * @method setStatus\n             * @grammar setStatus( status[, statusText] );\n             * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status)\n             * @param {String} [statusText=''] 状态说明，常在error时使用，用http, abort,server等来标记是由于什么原因导致文件错误。\n             */\n            setStatus: function( status, text ) {\n    \n                var prevStatus = statusMap[ this.id ];\n    \n                typeof text !== 'undefined' && (this.statusText = text);\n    \n                if ( status !== prevStatus ) {\n                    statusMap[ this.id ] = status;\n                    /**\n                     * 文件状态变化\n                     * @event statuschange\n                     */\n                    this.trigger( 'statuschange', status, prevStatus );\n                }\n    \n            },\n    \n            /**\n             * 获取文件状态\n             * @return {File.Status}\n             * @example\n                     文件状态具体包括以下几种类型：\n                     {\n                         // 初始化\n                        INITED:     0,\n                        // 已入队列\n                        QUEUED:     1,\n                        // 正在上传\n                        PROGRESS:     2,\n                        // 上传出错\n                        ERROR:         3,\n                        // 上传成功\n                        COMPLETE:     4,\n                        // 上传取消\n                        CANCELLED:     5\n                    }\n             */\n            getStatus: function() {\n                return statusMap[ this.id ];\n            },\n    \n            /**\n             * 获取文件原始信息。\n             * @return {*}\n             */\n            getSource: function() {\n                return this.source;\n            },\n    \n            destory: function() {\n                delete statusMap[ this.id ];\n            }\n        });\n    \n        Mediator.installTo( WUFile.prototype );\n    \n        /**\n         * 文件状态值，具体包括以下几种类型：\n         * * `inited` 初始状态\n         * * `queued` 已经进入队列, 等待上传\n         * * `progress` 上传中\n         * * `complete` 上传完成。\n         * * `error` 上传出错，可重试\n         * * `interrupt` 上传中断，可续传。\n         * * `invalid` 文件不合格，不能重试上传。会自动从队列中移除。\n         * * `cancelled` 文件被移除。\n         * @property {Object} Status\n         * @namespace File\n         * @class File\n         * @static\n         */\n        WUFile.Status = {\n            INITED:     'inited',    // 初始状态\n            QUEUED:     'queued',    // 已经进入队列, 等待上传\n            PROGRESS:   'progress',    // 上传中\n            ERROR:      'error',    // 上传出错，可重试\n            COMPLETE:   'complete',    // 上传完成。\n            CANCELLED:  'cancelled',    // 上传取消。\n            INTERRUPT:  'interrupt',    // 上传中断，可续传。\n            INVALID:    'invalid'    // 文件不合格，不能重试上传。\n        };\n    \n        return WUFile;\n    });\n    \n    /**\n     * @fileOverview 文件队列\n     */\n    define('queue',[\n        'base',\n        'mediator',\n        'file'\n    ], function( Base, Mediator, WUFile ) {\n    \n        var $ = Base.$,\n            STATUS = WUFile.Status;\n    \n        /**\n         * 文件队列, 用来存储各个状态中的文件。\n         * @class Queue\n         * @extends Mediator\n         */\n        function Queue() {\n    \n            /**\n             * 统计文件数。\n             * * `numOfQueue` 队列中的文件数。\n             * * `numOfSuccess` 上传成功的文件数\n             * * `numOfCancel` 被移除的文件数\n             * * `numOfProgress` 正在上传中的文件数\n             * * `numOfUploadFailed` 上传错误的文件数。\n             * * `numOfInvalid` 无效的文件数。\n             * @property {Object} stats\n             */\n            this.stats = {\n                numOfQueue: 0,\n                numOfSuccess: 0,\n                numOfCancel: 0,\n                numOfProgress: 0,\n                numOfUploadFailed: 0,\n                numOfInvalid: 0\n            };\n    \n            // 上传队列，仅包括等待上传的文件\n            this._queue = [];\n    \n            // 存储所有文件\n            this._map = {};\n        }\n    \n        $.extend( Queue.prototype, {\n    \n            /**\n             * 将新文件加入对队列尾部\n             *\n             * @method append\n             * @param  {File} file   文件对象\n             */\n            append: function( file ) {\n                this._queue.push( file );\n                this._fileAdded( file );\n                return this;\n            },\n    \n            /**\n             * 将新文件加入对队列头部\n             *\n             * @method prepend\n             * @param  {File} file   文件对象\n             */\n            prepend: function( file ) {\n                this._queue.unshift( file );\n                this._fileAdded( file );\n                return this;\n            },\n    \n            /**\n             * 获取文件对象\n             *\n             * @method getFile\n             * @param  {String} fileId   文件ID\n             * @return {File}\n             */\n            getFile: function( fileId ) {\n                if ( typeof fileId !== 'string' ) {\n                    return fileId;\n                }\n                return this._map[ fileId ];\n            },\n    \n            /**\n             * 从队列中取出一个指定状态的文件。\n             * @grammar fetch( status ) => File\n             * @method fetch\n             * @param {String} status [文件状态值](#WebUploader:File:File.Status)\n             * @return {File} [File](#WebUploader:File)\n             */\n            fetch: function( status ) {\n                var len = this._queue.length,\n                    i, file;\n    \n                status = status || STATUS.QUEUED;\n    \n                for ( i = 0; i < len; i++ ) {\n                    file = this._queue[ i ];\n    \n                    if ( status === file.getStatus() ) {\n                        return file;\n                    }\n                }\n    \n                return null;\n            },\n    \n            /**\n             * 对队列进行排序，能够控制文件上传顺序。\n             * @grammar sort( fn ) => undefined\n             * @method sort\n             * @param {Function} fn 排序方法\n             */\n            sort: function( fn ) {\n                if ( typeof fn === 'function' ) {\n                    this._queue.sort( fn );\n                }\n            },\n    \n            /**\n             * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。\n             * @grammar getFiles( [status1[, status2 ...]] ) => Array\n             * @method getFiles\n             * @param {String} [status] [文件状态值](#WebUploader:File:File.Status)\n             */\n            getFiles: function() {\n                var sts = [].slice.call( arguments, 0 ),\n                    ret = [],\n                    i = 0,\n                    len = this._queue.length,\n                    file;\n    \n                for ( ; i < len; i++ ) {\n                    file = this._queue[ i ];\n    \n                    if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) {\n                        continue;\n                    }\n    \n                    ret.push( file );\n                }\n    \n                return ret;\n            },\n    \n            _fileAdded: function( file ) {\n                var me = this,\n                    existing = this._map[ file.id ];\n    \n                if ( !existing ) {\n                    this._map[ file.id ] = file;\n    \n                    file.on( 'statuschange', function( cur, pre ) {\n                        me._onFileStatusChange( cur, pre );\n                    });\n                }\n    \n                file.setStatus( STATUS.QUEUED );\n            },\n    \n            _onFileStatusChange: function( curStatus, preStatus ) {\n                var stats = this.stats;\n    \n                switch ( preStatus ) {\n                    case STATUS.PROGRESS:\n                        stats.numOfProgress--;\n                        break;\n    \n                    case STATUS.QUEUED:\n                        stats.numOfQueue --;\n                        break;\n    \n                    case STATUS.ERROR:\n                        stats.numOfUploadFailed--;\n                        break;\n    \n                    case STATUS.INVALID:\n                        stats.numOfInvalid--;\n                        break;\n                }\n    \n                switch ( curStatus ) {\n                    case STATUS.QUEUED:\n                        stats.numOfQueue++;\n                        break;\n    \n                    case STATUS.PROGRESS:\n                        stats.numOfProgress++;\n                        break;\n    \n                    case STATUS.ERROR:\n                        stats.numOfUploadFailed++;\n                        break;\n    \n                    case STATUS.COMPLETE:\n                        stats.numOfSuccess++;\n                        break;\n    \n                    case STATUS.CANCELLED:\n                        stats.numOfCancel++;\n                        break;\n    \n                    case STATUS.INVALID:\n                        stats.numOfInvalid++;\n                        break;\n                }\n            }\n    \n        });\n    \n        Mediator.installTo( Queue.prototype );\n    \n        return Queue;\n    });\n    /**\n     * @fileOverview 队列\n     */\n    define('widgets/queue',[\n        'base',\n        'uploader',\n        'queue',\n        'file',\n        'lib/file',\n        'runtime/client',\n        'widgets/widget'\n    ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) {\n    \n        var $ = Base.$,\n            rExt = /\\.\\w+$/,\n            Status = WUFile.Status;\n    \n        return Uploader.register({\n            'sort-files': 'sortFiles',\n            'add-file': 'addFiles',\n            'get-file': 'getFile',\n            'fetch-file': 'fetchFile',\n            'get-stats': 'getStats',\n            'get-files': 'getFiles',\n            'remove-file': 'removeFile',\n            'retry': 'retry',\n            'reset': 'reset',\n            'accept-file': 'acceptFile'\n        }, {\n    \n            init: function( opts ) {\n                var me = this,\n                    deferred, len, i, item, arr, accept, runtime;\n    \n                if ( $.isPlainObject( opts.accept ) ) {\n                    opts.accept = [ opts.accept ];\n                }\n    \n                // accept中的中生成匹配正则。\n                if ( opts.accept ) {\n                    arr = [];\n    \n                    for ( i = 0, len = opts.accept.length; i < len; i++ ) {\n                        item = opts.accept[ i ].extensions;\n                        item && arr.push( item );\n                    }\n    \n                    if ( arr.length ) {\n                        accept = '\\\\.' + arr.join(',')\n                                .replace( /,/g, '$|\\\\.' )\n                                .replace( /\\*/g, '.*' ) + '$';\n                    }\n    \n                    me.accept = new RegExp( accept, 'i' );\n                }\n    \n                me.queue = new Queue();\n                me.stats = me.queue.stats;\n    \n                // 如果当前不是html5运行时，那就算了。\n                // 不执行后续操作\n                if ( this.request('predict-runtime-type') !== 'html5' ) {\n                    return;\n                }\n    \n                // 创建一个 html5 运行时的 placeholder\n                // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。\n                deferred = Base.Deferred();\n                runtime = new RuntimeClient('Placeholder');\n                runtime.connectRuntime({\n                    runtimeOrder: 'html5'\n                }, function() {\n                    me._ruid = runtime.getRuid();\n                    deferred.resolve();\n                });\n                return deferred.promise();\n            },\n    \n    \n            // 为了支持外部直接添加一个原生File对象。\n            _wrapFile: function( file ) {\n                if ( !(file instanceof WUFile) ) {\n    \n                    if ( !(file instanceof File) ) {\n                        if ( !this._ruid ) {\n                            throw new Error('Can\\'t add external files.');\n                        }\n                        file = new File( this._ruid, file );\n                    }\n    \n                    file = new WUFile( file );\n                }\n    \n                return file;\n            },\n    \n            // 判断文件是否可以被加入队列\n            acceptFile: function( file ) {\n                var invalid = !file || file.size < 6 || this.accept &&\n    \n                        // 如果名字中有后缀，才做后缀白名单处理。\n                        rExt.exec( file.name ) && !this.accept.test( file.name );\n    \n                return !invalid;\n            },\n    \n    \n            /**\n             * @event beforeFileQueued\n             * @param {File} file File对象\n             * @description 当文件被加入队列之前触发，此事件的handler返回值为`false`，则此文件不会被添加进入队列。\n             * @for  Uploader\n             */\n    \n            /**\n             * @event fileQueued\n             * @param {File} file File对象\n             * @description 当文件被加入队列以后触发。\n             * @for  Uploader\n             */\n    \n            _addFile: function( file ) {\n                var me = this;\n    \n                file = me._wrapFile( file );\n    \n                // 不过类型判断允许不允许，先派送 `beforeFileQueued`\n                if ( !me.owner.trigger( 'beforeFileQueued', file ) ) {\n                    return;\n                }\n    \n                // 类型不匹配，则派送错误事件，并返回。\n                if ( !me.acceptFile( file ) ) {\n                    me.owner.trigger( 'error', 'Q_TYPE_DENIED', file );\n                    return;\n                }\n    \n                me.queue.append( file );\n                me.owner.trigger( 'fileQueued', file );\n                return file;\n            },\n    \n            getFile: function( fileId ) {\n                return this.queue.getFile( fileId );\n            },\n    \n            /**\n             * @event filesQueued\n             * @param {File} files 数组，内容为原始File(lib/File）对象。\n             * @description 当一批文件添加进队列以后触发。\n             * @for  Uploader\n             */\n    \n            /**\n             * @method addFiles\n             * @grammar addFiles( file ) => undefined\n             * @grammar addFiles( [file1, file2 ...] ) => undefined\n             * @param {Array of File or File} [files] Files 对象 数组\n             * @description 添加文件到队列\n             * @for  Uploader\n             */\n            addFiles: function( files ) {\n                var me = this;\n    \n                if ( !files.length ) {\n                    files = [ files ];\n                }\n    \n                files = $.map( files, function( file ) {\n                    return me._addFile( file );\n                });\n    \n                me.owner.trigger( 'filesQueued', files );\n    \n                if ( me.options.auto ) {\n                    me.request('start-upload');\n                }\n            },\n    \n            getStats: function() {\n                return this.stats;\n            },\n    \n            /**\n             * @event fileDequeued\n             * @param {File} file File对象\n             * @description 当文件被移除队列后触发。\n             * @for  Uploader\n             */\n    \n            /**\n             * @method removeFile\n             * @grammar removeFile( file ) => undefined\n             * @grammar removeFile( id ) => undefined\n             * @param {File|id} file File对象或这File对象的id\n             * @description 移除某一文件。\n             * @for  Uploader\n             * @example\n             *\n             * $li.on('click', '.remove-this', function() {\n             *     uploader.removeFile( file );\n             * })\n             */\n            removeFile: function( file ) {\n                var me = this;\n    \n                file = file.id ? file : me.queue.getFile( file );\n    \n                file.setStatus( Status.CANCELLED );\n                me.owner.trigger( 'fileDequeued', file );\n            },\n    \n            /**\n             * @method getFiles\n             * @grammar getFiles() => Array\n             * @grammar getFiles( status1, status2, status... ) => Array\n             * @description 返回指定状态的文件集合，不传参数将返回所有状态的文件。\n             * @for  Uploader\n             * @example\n             * console.log( uploader.getFiles() );    // => all files\n             * console.log( uploader.getFiles('error') )    // => all error files.\n             */\n            getFiles: function() {\n                return this.queue.getFiles.apply( this.queue, arguments );\n            },\n    \n            fetchFile: function() {\n                return this.queue.fetch.apply( this.queue, arguments );\n            },\n    \n            /**\n             * @method retry\n             * @grammar retry() => undefined\n             * @grammar retry( file ) => undefined\n             * @description 重试上传，重试指定文件，或者从出错的文件开始重新上传。\n             * @for  Uploader\n             * @example\n             * function retry() {\n             *     uploader.retry();\n             * }\n             */\n            retry: function( file, noForceStart ) {\n                var me = this,\n                    files, i, len;\n    \n                if ( file ) {\n                    file = file.id ? file : me.queue.getFile( file );\n                    file.setStatus( Status.QUEUED );\n                    noForceStart || me.request('start-upload');\n                    return;\n                }\n    \n                files = me.queue.getFiles( Status.ERROR );\n                i = 0;\n                len = files.length;\n    \n                for ( ; i < len; i++ ) {\n                    file = files[ i ];\n                    file.setStatus( Status.QUEUED );\n                }\n    \n                me.request('start-upload');\n            },\n    \n            /**\n             * @method sort\n             * @grammar sort( fn ) => undefined\n             * @description 排序队列中的文件，在上传之前调整可以控制上传顺序。\n             * @for  Uploader\n             */\n            sortFiles: function() {\n                return this.queue.sort.apply( this.queue, arguments );\n            },\n    \n            /**\n             * @method reset\n             * @grammar reset() => undefined\n             * @description 重置uploader。目前只重置了队列。\n             * @for  Uploader\n             * @example\n             * uploader.reset();\n             */\n            reset: function() {\n                this.queue = new Queue();\n                this.stats = this.queue.stats;\n            }\n        });\n    \n    });\n    /**\n     * @fileOverview 添加获取Runtime相关信息的方法。\n     */\n    define('widgets/runtime',[\n        'uploader',\n        'runtime/runtime',\n        'widgets/widget'\n    ], function( Uploader, Runtime ) {\n    \n        Uploader.support = function() {\n            return Runtime.hasRuntime.apply( Runtime, arguments );\n        };\n    \n        return Uploader.register({\n            'predict-runtime-type': 'predictRuntmeType'\n        }, {\n    \n            init: function() {\n                if ( !this.predictRuntmeType() ) {\n                    throw Error('Runtime Error');\n                }\n            },\n    \n            /**\n             * 预测Uploader将采用哪个`Runtime`\n             * @grammar predictRuntmeType() => String\n             * @method predictRuntmeType\n             * @for  Uploader\n             */\n            predictRuntmeType: function() {\n                var orders = this.options.runtimeOrder || Runtime.orders,\n                    type = this.type,\n                    i, len;\n    \n                if ( !type ) {\n                    orders = orders.split( /\\s*,\\s*/g );\n    \n                    for ( i = 0, len = orders.length; i < len; i++ ) {\n                        if ( Runtime.hasRuntime( orders[ i ] ) ) {\n                            this.type = type = orders[ i ];\n                            break;\n                        }\n                    }\n                }\n    \n                return type;\n            }\n        });\n    });\n    /**\n     * @fileOverview Transport\n     */\n    define('lib/transport',[\n        'base',\n        'runtime/client',\n        'mediator'\n    ], function( Base, RuntimeClient, Mediator ) {\n    \n        var $ = Base.$;\n    \n        function Transport( opts ) {\n            var me = this;\n    \n            opts = me.options = $.extend( true, {}, Transport.options, opts || {} );\n            RuntimeClient.call( this, 'Transport' );\n    \n            this._blob = null;\n            this._formData = opts.formData || {};\n            this._headers = opts.headers || {};\n    \n            this.on( 'progress', this._timeout );\n            this.on( 'load error', function() {\n                me.trigger( 'progress', 1 );\n                clearTimeout( me._timer );\n            });\n        }\n    \n        Transport.options = {\n            server: '',\n            method: 'POST',\n    \n            // 跨域时，是否允许携带cookie, 只有html5 runtime才有效\n            withCredentials: false,\n            fileVal: 'file',\n            timeout: 2 * 60 * 1000,    // 2分钟\n            formData: {},\n            headers: {},\n            sendAsBinary: false\n        };\n    \n        $.extend( Transport.prototype, {\n    \n            // 添加Blob, 只能添加一次，最后一次有效。\n            appendBlob: function( key, blob, filename ) {\n                var me = this,\n                    opts = me.options;\n    \n                if ( me.getRuid() ) {\n                    me.disconnectRuntime();\n                }\n    \n                // 连接到blob归属的同一个runtime.\n                me.connectRuntime( blob.ruid, function() {\n                    me.exec('init');\n                });\n    \n                me._blob = blob;\n                opts.fileVal = key || opts.fileVal;\n                opts.filename = filename || opts.filename;\n            },\n    \n            // 添加其他字段\n            append: function( key, value ) {\n                if ( typeof key === 'object' ) {\n                    $.extend( this._formData, key );\n                } else {\n                    this._formData[ key ] = value;\n                }\n            },\n    \n            setRequestHeader: function( key, value ) {\n                if ( typeof key === 'object' ) {\n                    $.extend( this._headers, key );\n                } else {\n                    this._headers[ key ] = value;\n                }\n            },\n    \n            send: function( method ) {\n                this.exec( 'send', method );\n                this._timeout();\n            },\n    \n            abort: function() {\n                clearTimeout( this._timer );\n                return this.exec('abort');\n            },\n    \n            destroy: function() {\n                this.trigger('destroy');\n                this.off();\n                this.exec('destroy');\n                this.disconnectRuntime();\n            },\n    \n            getResponse: function() {\n                return this.exec('getResponse');\n            },\n    \n            getResponseAsJson: function() {\n                return this.exec('getResponseAsJson');\n            },\n    \n            getStatus: function() {\n                return this.exec('getStatus');\n            },\n    \n            _timeout: function() {\n                var me = this,\n                    duration = me.options.timeout;\n    \n                if ( !duration ) {\n                    return;\n                }\n    \n                clearTimeout( me._timer );\n                me._timer = setTimeout(function() {\n                    me.abort();\n                    me.trigger( 'error', 'timeout' );\n                }, duration );\n            }\n    \n        });\n    \n        // 让Transport具备事件功能。\n        Mediator.installTo( Transport.prototype );\n    \n        return Transport;\n    });\n    /**\n     * @fileOverview 负责文件上传相关。\n     */\n    define('widgets/upload',[\n        'base',\n        'uploader',\n        'file',\n        'lib/transport',\n        'widgets/widget'\n    ], function( Base, Uploader, WUFile, Transport ) {\n    \n        var $ = Base.$,\n            isPromise = Base.isPromise,\n            Status = WUFile.Status;\n    \n        // 添加默认配置项\n        $.extend( Uploader.options, {\n    \n    \n            /**\n             * @property {Boolean} [prepareNextFile=false]\n             * @namespace options\n             * @for Uploader\n             * @description 是否允许在文件传输时提前把下一个文件准备好。\n             * 对于一个文件的准备工作比较耗时，比如图片压缩，md5序列化。\n             * 如果能提前在当前文件传输期处理，可以节省总体耗时。\n             */\n            prepareNextFile: false,\n    \n            /**\n             * @property {Boolean} [chunked=false]\n             * @namespace options\n             * @for Uploader\n             * @description 是否要分片处理大文件上传。\n             */\n            chunked: false,\n    \n            /**\n             * @property {Boolean} [chunkSize=5242880]\n             * @namespace options\n             * @for Uploader\n             * @description 如果要分片，分多大一片？ 默认大小为5M.\n             */\n            chunkSize: 5 * 1024 * 1024,\n    \n            /**\n             * @property {Boolean} [chunkRetry=2]\n             * @namespace options\n             * @for Uploader\n             * @description 如果某个分片由于网络问题出错，允许自动重传多少次？\n             */\n            chunkRetry: 2,\n    \n            /**\n             * @property {Boolean} [threads=3]\n             * @namespace options\n             * @for Uploader\n             * @description 上传并发数。允许同时最大上传进程数。\n             */\n            threads: 3,\n    \n    \n            /**\n             * @property {Object} [formData]\n             * @namespace options\n             * @for Uploader\n             * @description 文件上传请求的参数表，每次发送都会发送此对象中的参数。\n             */\n            formData: null\n    \n            /**\n             * @property {Object} [fileVal='file']\n             * @namespace options\n             * @for Uploader\n             * @description 设置文件上传域的name。\n             */\n    \n            /**\n             * @property {Object} [method='POST']\n             * @namespace options\n             * @for Uploader\n             * @description 文件上传方式，`POST`或者`GET`。\n             */\n    \n            /**\n             * @property {Object} [sendAsBinary=false]\n             * @namespace options\n             * @for Uploader\n             * @description 是否已二进制的流的方式发送文件，这样整个上传内容`php://input`都为文件内容，\n             * 其他参数在$_GET数组中。\n             */\n        });\n    \n        // 负责将文件切片。\n        function CuteFile( file, chunkSize ) {\n            var pending = [],\n                blob = file.source,\n                total = blob.size,\n                chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1,\n                start = 0,\n                index = 0,\n                len;\n    \n            while ( index < chunks ) {\n                len = Math.min( chunkSize, total - start );\n    \n                pending.push({\n                    file: file,\n                    start: start,\n                    end: chunkSize ? (start + len) : total,\n                    total: total,\n                    chunks: chunks,\n                    chunk: index++\n                });\n                start += len;\n            }\n    \n            file.blocks = pending.concat();\n            file.remaning = pending.length;\n    \n            return {\n                file: file,\n    \n                has: function() {\n                    return !!pending.length;\n                },\n    \n                fetch: function() {\n                    return pending.shift();\n                }\n            };\n        }\n    \n        Uploader.register({\n            'start-upload': 'start',\n            'stop-upload': 'stop',\n            'skip-file': 'skipFile',\n            'is-in-progress': 'isInProgress'\n        }, {\n    \n            init: function() {\n                var owner = this.owner;\n    \n                this.runing = false;\n    \n                // 记录当前正在传的数据，跟threads相关\n                this.pool = [];\n    \n                // 缓存即将上传的文件。\n                this.pending = [];\n    \n                // 跟踪还有多少分片没有完成上传。\n                this.remaning = 0;\n                this.__tick = Base.bindFn( this._tick, this );\n    \n                owner.on( 'uploadComplete', function( file ) {\n                    // 把其他块取消了。\n                    file.blocks && $.each( file.blocks, function( _, v ) {\n                        v.transport && (v.transport.abort(), v.transport.destroy());\n                        delete v.transport;\n                    });\n    \n                    delete file.blocks;\n                    delete file.remaning;\n                });\n            },\n    \n            /**\n             * @event startUpload\n             * @description 当开始上传流程时触发。\n             * @for  Uploader\n             */\n    \n            /**\n             * 开始上传。此方法可以从初始状态调用开始上传流程，也可以从暂停状态调用，继续上传流程。\n             * @grammar upload() => undefined\n             * @method upload\n             * @for  Uploader\n             */\n            start: function() {\n                var me = this;\n    \n                // 移出invalid的文件\n                $.each( me.request( 'get-files', Status.INVALID ), function() {\n                    me.request( 'remove-file', this );\n                });\n    \n                if ( me.runing ) {\n                    return;\n                }\n    \n                me.runing = true;\n    \n                // 如果有暂停的，则续传\n                $.each( me.pool, function( _, v ) {\n                    var file = v.file;\n    \n                    if ( file.getStatus() === Status.INTERRUPT ) {\n                        file.setStatus( Status.PROGRESS );\n                        me._trigged = false;\n                        v.transport && v.transport.send();\n                    }\n                });\n    \n                me._trigged = false;\n                me.owner.trigger('startUpload');\n                Base.nextTick( me.__tick );\n            },\n    \n            /**\n             * @event stopUpload\n             * @description 当开始上传流程暂停时触发。\n             * @for  Uploader\n             */\n    \n            /**\n             * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。\n             * @grammar stop() => undefined\n             * @grammar stop( true ) => undefined\n             * @method stop\n             * @for  Uploader\n             */\n            stop: function( interrupt ) {\n                var me = this;\n    \n                if ( me.runing === false ) {\n                    return;\n                }\n    \n                me.runing = false;\n    \n                interrupt && $.each( me.pool, function( _, v ) {\n                    v.transport && v.transport.abort();\n                    v.file.setStatus( Status.INTERRUPT );\n                });\n    \n                me.owner.trigger('stopUpload');\n            },\n    \n            /**\n             * 判断`Uplaode`r是否正在上传中。\n             * @grammar isInProgress() => Boolean\n             * @method isInProgress\n             * @for  Uploader\n             */\n            isInProgress: function() {\n                return !!this.runing;\n            },\n    \n            getStats: function() {\n                return this.request('get-stats');\n            },\n    \n            /**\n             * 掉过一个文件上传，直接标记指定文件为已上传状态。\n             * @grammar skipFile( file ) => undefined\n             * @method skipFile\n             * @for  Uploader\n             */\n            skipFile: function( file, status ) {\n                file = this.request( 'get-file', file );\n    \n                file.setStatus( status || Status.COMPLETE );\n                file.skipped = true;\n    \n                // 如果正在上传。\n                file.blocks && $.each( file.blocks, function( _, v ) {\n                    var _tr = v.transport;\n    \n                    if ( _tr ) {\n                        _tr.abort();\n                        _tr.destroy();\n                        delete v.transport;\n                    }\n                });\n    \n                this.owner.trigger( 'uploadSkip', file );\n            },\n    \n            /**\n             * @event uploadFinished\n             * @description 当所有文件上传结束时触发。\n             * @for  Uploader\n             */\n            _tick: function() {\n                var me = this,\n                    opts = me.options,\n                    fn, val;\n    \n                // 上一个promise还没有结束，则等待完成后再执行。\n                if ( me._promise ) {\n                    return me._promise.always( me.__tick );\n                }\n    \n                // 还有位置，且还有文件要处理的话。\n                if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) {\n                    me._trigged = false;\n    \n                    fn = function( val ) {\n                        me._promise = null;\n    \n                        // 有可能是reject过来的，所以要检测val的类型。\n                        val && val.file && me._startSend( val );\n                        Base.nextTick( me.__tick );\n                    };\n    \n                    me._promise = isPromise( val ) ? val.always( fn ) : fn( val );\n    \n                // 没有要上传的了，且没有正在传输的了。\n                } else if ( !me.remaning && !me.getStats().numOfQueue ) {\n                    me.runing = false;\n    \n                    me._trigged || Base.nextTick(function() {\n                        me.owner.trigger('uploadFinished');\n                    });\n                    me._trigged = true;\n                }\n            },\n    \n            _nextBlock: function() {\n                var me = this,\n                    act = me._act,\n                    opts = me.options,\n                    next, done;\n    \n                // 如果当前文件还有没有需要传输的，则直接返回剩下的。\n                if ( act && act.has() &&\n                        act.file.getStatus() === Status.PROGRESS ) {\n    \n                    // 是否提前准备下一个文件\n                    if ( opts.prepareNextFile && !me.pending.length ) {\n                        me._prepareNextFile();\n                    }\n    \n                    return act.fetch();\n    \n                // 否则，如果正在运行，则准备下一个文件，并等待完成后返回下个分片。\n                } else if ( me.runing ) {\n    \n                    // 如果缓存中有，则直接在缓存中取，没有则去queue中取。\n                    if ( !me.pending.length && me.getStats().numOfQueue ) {\n                        me._prepareNextFile();\n                    }\n    \n                    next = me.pending.shift();\n                    done = function( file ) {\n                        if ( !file ) {\n                            return null;\n                        }\n    \n                        act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 );\n                        me._act = act;\n                        return act.fetch();\n                    };\n    \n                    // 文件可能还在prepare中，也有可能已经完全准备好了。\n                    return isPromise( next ) ?\n                            next[ next.pipe ? 'pipe' : 'then']( done ) :\n                            done( next );\n                }\n            },\n    \n    \n            /**\n             * @event uploadStart\n             * @param {File} file File对象\n             * @description 某个文件开始上传前触发，一个文件只会触发一次。\n             * @for  Uploader\n             */\n            _prepareNextFile: function() {\n                var me = this,\n                    file = me.request('fetch-file'),\n                    pending = me.pending,\n                    promise;\n    \n                if ( file ) {\n                    promise = me.request( 'before-send-file', file, function() {\n    \n                        // 有可能文件被skip掉了。文件被skip掉后，状态坑定不是Queued.\n                        if ( file.getStatus() === Status.QUEUED ) {\n                            me.owner.trigger( 'uploadStart', file );\n                            file.setStatus( Status.PROGRESS );\n                            return file;\n                        }\n    \n                        return me._finishFile( file );\n                    });\n    \n                    // 如果还在pending中，则替换成文件本身。\n                    promise.done(function() {\n                        var idx = $.inArray( promise, pending );\n    \n                        ~idx && pending.splice( idx, 1, file );\n                    });\n    \n                    // befeore-send-file的钩子就有错误发生。\n                    promise.fail(function( reason ) {\n                        file.setStatus( Status.ERROR, reason );\n                        me.owner.trigger( 'uploadError', file, reason );\n                        me.owner.trigger( 'uploadComplete', file );\n                    });\n    \n                    pending.push( promise );\n                }\n            },\n    \n            // 让出位置了，可以让其他分片开始上传\n            _popBlock: function( block ) {\n                var idx = $.inArray( block, this.pool );\n    \n                this.pool.splice( idx, 1 );\n                block.file.remaning--;\n                this.remaning--;\n            },\n    \n            // 开始上传，可以被掉过。如果promise被reject了，则表示跳过此分片。\n            _startSend: function( block ) {\n                var me = this,\n                    file = block.file,\n                    promise;\n    \n                me.pool.push( block );\n                me.remaning++;\n    \n                // 如果没有分片，则直接使用原始的。\n                // 不会丢失content-type信息。\n                block.blob = block.chunks === 1 ? file.source :\n                        file.source.slice( block.start, block.end );\n    \n                // hook, 每个分片发送之前可能要做些异步的事情。\n                promise = me.request( 'before-send', block, function() {\n    \n                    // 有可能文件已经上传出错了，所以不需要再传输了。\n                    if ( file.getStatus() === Status.PROGRESS ) {\n                        me._doSend( block );\n                    } else {\n                        me._popBlock( block );\n                        Base.nextTick( me.__tick );\n                    }\n                });\n    \n                // 如果为fail了，则跳过此分片。\n                promise.fail(function() {\n                    if ( file.remaning === 1 ) {\n                        me._finishFile( file ).always(function() {\n                            block.percentage = 1;\n                            me._popBlock( block );\n                            me.owner.trigger( 'uploadComplete', file );\n                            Base.nextTick( me.__tick );\n                        });\n                    } else {\n                        block.percentage = 1;\n                        me._popBlock( block );\n                        Base.nextTick( me.__tick );\n                    }\n                });\n            },\n    \n    \n            /**\n             * @event uploadBeforeSend\n             * @param {Object} object\n             * @param {Object} data 默认的上传参数，可以扩展此对象来控制上传参数。\n             * @description 当某个文件的分块在发送前触发，主要用来询问是否要添加附带参数，大文件在开起分片上传的前提下此事件可能会触发多次。\n             * @for  Uploader\n             */\n    \n            /**\n             * @event uploadAccept\n             * @param {Object} object\n             * @param {Object} ret 服务端的返回数据，json格式，如果服务端不是json格式，从ret._raw中取数据，自行解析。\n             * @description 当某个文件上传到服务端响应后，会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。\n             * @for  Uploader\n             */\n    \n            /**\n             * @event uploadProgress\n             * @param {File} file File对象\n             * @param {Number} percentage 上传进度\n             * @description 上传过程中触发，携带上传进度。\n             * @for  Uploader\n             */\n    \n    \n            /**\n             * @event uploadError\n             * @param {File} file File对象\n             * @param {String} reason 出错的code\n             * @description 当文件上传出错时触发。\n             * @for  Uploader\n             */\n    \n            /**\n             * @event uploadSuccess\n             * @param {File} file File对象\n             * @param {Object} response 服务端返回的数据\n             * @description 当文件上传成功时触发。\n             * @for  Uploader\n             */\n    \n            /**\n             * @event uploadComplete\n             * @param {File} [file] File对象\n             * @description 不管成功或者失败，文件上传完成时触发。\n             * @for  Uploader\n             */\n    \n            // 做上传操作。\n            _doSend: function( block ) {\n                var me = this,\n                    owner = me.owner,\n                    opts = me.options,\n                    file = block.file,\n                    tr = new Transport( opts ),\n                    data = $.extend({}, opts.formData ),\n                    headers = $.extend({}, opts.headers ),\n                    requestAccept, ret;\n    \n                block.transport = tr;\n    \n                tr.on( 'destroy', function() {\n                    delete block.transport;\n                    me._popBlock( block );\n                    Base.nextTick( me.__tick );\n                });\n    \n                // 广播上传进度。以文件为单位。\n                tr.on( 'progress', function( percentage ) {\n                    var totalPercent = 0,\n                        uploaded = 0;\n    \n                    // 可能没有abort掉，progress还是执行进来了。\n                    // if ( !file.blocks ) {\n                    //     return;\n                    // }\n    \n                    totalPercent = block.percentage = percentage;\n    \n                    if ( block.chunks > 1 ) {    // 计算文件的整体速度。\n                        $.each( file.blocks, function( _, v ) {\n                            uploaded += (v.percentage || 0) * (v.end - v.start);\n                        });\n    \n                        totalPercent = uploaded / file.size;\n                    }\n    \n                    owner.trigger( 'uploadProgress', file, totalPercent || 0 );\n                });\n    \n                // 用来询问，是否返回的结果是有错误的。\n                requestAccept = function( reject ) {\n                    var fn;\n    \n                    ret = tr.getResponseAsJson() || {};\n                    ret._raw = tr.getResponse();\n                    fn = function( value ) {\n                        reject = value;\n                    };\n    \n                    // 服务端响应了，不代表成功了，询问是否响应正确。\n                    if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) {\n                        reject = reject || 'server';\n                    }\n    \n                    return reject;\n                };\n    \n                // 尝试重试，然后广播文件上传出错。\n                tr.on( 'error', function( type, flag ) {\n                    block.retried = block.retried || 0;\n    \n                    // 自动重试\n                    if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) &&\n                            block.retried < opts.chunkRetry ) {\n    \n                        block.retried++;\n                        tr.send();\n    \n                    } else {\n    \n                        // http status 500 ~ 600\n                        if ( !flag && type === 'server' ) {\n                            type = requestAccept( type );\n                        }\n    \n                        file.setStatus( Status.ERROR, type );\n                        owner.trigger( 'uploadError', file, type );\n                        owner.trigger( 'uploadComplete', file );\n                    }\n                });\n    \n                // 上传成功\n                tr.on( 'load', function() {\n                    var reason;\n    \n                    // 如果非预期，转向上传出错。\n                    if ( (reason = requestAccept()) ) {\n                        tr.trigger( 'error', reason, true );\n                        return;\n                    }\n    \n                    // 全部上传完成。\n                    if ( file.remaning === 1 ) {\n                        me._finishFile( file, ret );\n                    } else {\n                        tr.destroy();\n                    }\n                });\n    \n                // 配置默认的上传字段。\n                data = $.extend( data, {\n                    id: file.id,\n                    name: file.name,\n                    type: file.type,\n                    lastModifiedDate: file.lastModifiedDate,\n                    size: file.size\n                });\n    \n                block.chunks > 1 && $.extend( data, {\n                    chunks: block.chunks,\n                    chunk: block.chunk\n                });\n    \n                // 在发送之间可以添加字段什么的。。。\n                // 如果默认的字段不够使用，可以通过监听此事件来扩展\n                owner.trigger( 'uploadBeforeSend', block, data, headers );\n    \n                // 开始发送。\n                tr.appendBlob( opts.fileVal, block.blob, file.name );\n                tr.append( data );\n                tr.setRequestHeader( headers );\n                tr.send();\n            },\n    \n            // 完成上传。\n            _finishFile: function( file, ret, hds ) {\n                var owner = this.owner;\n    \n                return owner\n                        .request( 'after-send-file', arguments, function() {\n                            file.setStatus( Status.COMPLETE );\n                            owner.trigger( 'uploadSuccess', file, ret, hds );\n                        })\n                        .fail(function( reason ) {\n    \n                            // 如果外部已经标记为invalid什么的，不再改状态。\n                            if ( file.getStatus() === Status.PROGRESS ) {\n                                file.setStatus( Status.ERROR, reason );\n                            }\n    \n                            owner.trigger( 'uploadError', file, reason );\n                        })\n                        .always(function() {\n                            owner.trigger( 'uploadComplete', file );\n                        });\n            }\n    \n        });\n    });\n    /**\n     * @fileOverview 各种验证，包括文件总大小是否超出、单文件是否超出和文件是否重复。\n     */\n    \n    define('widgets/validator',[\n        'base',\n        'uploader',\n        'file',\n        'widgets/widget'\n    ], function( Base, Uploader, WUFile ) {\n    \n        var $ = Base.$,\n            validators = {},\n            api;\n    \n        /**\n         * @event error\n         * @param {String} type 错误类型。\n         * @description 当validate不通过时，会以派送错误事件的形式通知调用者。通过`upload.on('error', handler)`可以捕获到此类错误，目前有以下错误会在特定的情况下派送错来。\n         *\n         * * `Q_EXCEED_NUM_LIMIT` 在设置了`fileNumLimit`且尝试给`uploader`添加的文件数量超出这个值时派送。\n         * * `Q_EXCEED_SIZE_LIMIT` 在设置了`Q_EXCEED_SIZE_LIMIT`且尝试给`uploader`添加的文件总大小超出这个值时派送。\n         * @for  Uploader\n         */\n    \n        // 暴露给外面的api\n        api = {\n    \n            // 添加验证器\n            addValidator: function( type, cb ) {\n                validators[ type ] = cb;\n            },\n    \n            // 移除验证器\n            removeValidator: function( type ) {\n                delete validators[ type ];\n            }\n        };\n    \n        // 在Uploader初始化的时候启动Validators的初始化\n        Uploader.register({\n            init: function() {\n                var me = this;\n                $.each( validators, function() {\n                    this.call( me.owner );\n                });\n            }\n        });\n    \n        /**\n         * @property {int} [fileNumLimit=undefined]\n         * @namespace options\n         * @for Uploader\n         * @description 验证文件总数量, 超出则不允许加入队列。\n         */\n        api.addValidator( 'fileNumLimit', function() {\n            var uploader = this,\n                opts = uploader.options,\n                count = 0,\n                max = opts.fileNumLimit >> 0,\n                flag = true;\n    \n            if ( !max ) {\n                return;\n            }\n    \n            uploader.on( 'beforeFileQueued', function( file ) {\n    \n                if ( count >= max && flag ) {\n                    flag = false;\n                    this.trigger( 'error', 'Q_EXCEED_NUM_LIMIT', max, file );\n                    setTimeout(function() {\n                        flag = true;\n                    }, 1 );\n                }\n    \n                return count >= max ? false : true;\n            });\n    \n            uploader.on( 'fileQueued', function() {\n                count++;\n            });\n    \n            uploader.on( 'fileDequeued', function() {\n                count--;\n            });\n    \n            uploader.on( 'uploadFinished', function() {\n                count = 0;\n            });\n        });\n    \n    \n        /**\n         * @property {int} [fileSizeLimit=undefined]\n         * @namespace options\n         * @for Uploader\n         * @description 验证文件总大小是否超出限制, 超出则不允许加入队列。\n         */\n        api.addValidator( 'fileSizeLimit', function() {\n            var uploader = this,\n                opts = uploader.options,\n                count = 0,\n                max = opts.fileSizeLimit >> 0,\n                flag = true;\n    \n            if ( !max ) {\n                return;\n            }\n    \n            uploader.on( 'beforeFileQueued', function( file ) {\n                var invalid = count + file.size > max;\n    \n                if ( invalid && flag ) {\n                    flag = false;\n                    this.trigger( 'error', 'Q_EXCEED_SIZE_LIMIT', max, file );\n                    setTimeout(function() {\n                        flag = true;\n                    }, 1 );\n                }\n    \n                return invalid ? false : true;\n            });\n    \n            uploader.on( 'fileQueued', function( file ) {\n                count += file.size;\n            });\n    \n            uploader.on( 'fileDequeued', function( file ) {\n                count -= file.size;\n            });\n    \n            uploader.on( 'uploadFinished', function() {\n                count = 0;\n            });\n        });\n    \n        /**\n         * @property {int} [fileSingleSizeLimit=undefined]\n         * @namespace options\n         * @for Uploader\n         * @description 验证单个文件大小是否超出限制, 超出则不允许加入队列。\n         */\n        api.addValidator( 'fileSingleSizeLimit', function() {\n            var uploader = this,\n                opts = uploader.options,\n                max = opts.fileSingleSizeLimit;\n    \n            if ( !max ) {\n                return;\n            }\n    \n            uploader.on( 'beforeFileQueued', function( file ) {\n    \n                if ( file.size > max ) {\n                    file.setStatus( WUFile.Status.INVALID, 'exceed_size' );\n                    this.trigger( 'error', 'F_EXCEED_SIZE', file );\n                    return false;\n                }\n    \n            });\n    \n        });\n    \n        /**\n         * @property {int} [duplicate=undefined]\n         * @namespace options\n         * @for Uploader\n         * @description 去重， 根据文件名字、文件大小和最后修改时间来生成hash Key.\n         */\n        api.addValidator( 'duplicate', function() {\n            var uploader = this,\n                opts = uploader.options,\n                mapping = {};\n    \n            if ( opts.duplicate ) {\n                return;\n            }\n    \n            function hashString( str ) {\n                var hash = 0,\n                    i = 0,\n                    len = str.length,\n                    _char;\n    \n                for ( ; i < len; i++ ) {\n                    _char = str.charCodeAt( i );\n                    hash = _char + (hash << 6) + (hash << 16) - hash;\n                }\n    \n                return hash;\n            }\n    \n            uploader.on( 'beforeFileQueued', function( file ) {\n                var hash = file.__hash || (file.__hash = hashString( file.name +\n                        file.size + file.lastModifiedDate ));\n    \n                // 已经重复了\n                if ( mapping[ hash ] ) {\n                    this.trigger( 'error', 'F_DUPLICATE', file );\n                    return false;\n                }\n            });\n    \n            uploader.on( 'fileQueued', function( file ) {\n                var hash = file.__hash;\n    \n                hash && (mapping[ hash ] = true);\n            });\n    \n            uploader.on( 'fileDequeued', function( file ) {\n                var hash = file.__hash;\n    \n                hash && (delete mapping[ hash ]);\n            });\n        });\n    \n        return api;\n    });\n    \n    /**\n     * @fileOverview Runtime管理器，负责Runtime的选择, 连接\n     */\n    define('runtime/compbase',[],function() {\n    \n        function CompBase( owner, runtime ) {\n    \n            this.owner = owner;\n            this.options = owner.options;\n    \n            this.getRuntime = function() {\n                return runtime;\n            };\n    \n            this.getRuid = function() {\n                return runtime.uid;\n            };\n    \n            this.trigger = function() {\n                return owner.trigger.apply( owner, arguments );\n            };\n        }\n    \n        return CompBase;\n    });\n    /**\n     * @fileOverview Html5Runtime\n     */\n    define('runtime/html5/runtime',[\n        'base',\n        'runtime/runtime',\n        'runtime/compbase'\n    ], function( Base, Runtime, CompBase ) {\n    \n        var type = 'html5',\n            components = {};\n    \n        function Html5Runtime() {\n            var pool = {},\n                me = this,\n                destory = this.destory;\n    \n            Runtime.apply( me, arguments );\n            me.type = type;\n    \n    \n            // 这个方法的调用者，实际上是RuntimeClient\n            me.exec = function( comp, fn/*, args...*/) {\n                var client = this,\n                    uid = client.uid,\n                    args = Base.slice( arguments, 2 ),\n                    instance;\n    \n                if ( components[ comp ] ) {\n                    instance = pool[ uid ] = pool[ uid ] ||\n                            new components[ comp ]( client, me );\n    \n                    if ( instance[ fn ] ) {\n                        return instance[ fn ].apply( instance, args );\n                    }\n                }\n            };\n    \n            me.destory = function() {\n                // @todo 删除池子中的所有实例\n                return destory && destory.apply( this, arguments );\n            };\n        }\n    \n        Base.inherits( Runtime, {\n            constructor: Html5Runtime,\n    \n            // 不需要连接其他程序，直接执行callback\n            init: function() {\n                var me = this;\n                setTimeout(function() {\n                    me.trigger('ready');\n                }, 1 );\n            }\n    \n        });\n    \n        // 注册Components\n        Html5Runtime.register = function( name, component ) {\n            var klass = components[ name ] = Base.inherits( CompBase, component );\n            return klass;\n        };\n    \n        // 注册html5运行时。\n        // 只有在支持的前提下注册。\n        if ( window.Blob && window.FileReader && window.DataView ) {\n            Runtime.addRuntime( type, Html5Runtime );\n        }\n    \n        return Html5Runtime;\n    });\n    /**\n     * @fileOverview Blob Html实现\n     */\n    define('runtime/html5/blob',[\n        'runtime/html5/runtime',\n        'lib/blob'\n    ], function( Html5Runtime, Blob ) {\n    \n        return Html5Runtime.register( 'Blob', {\n            slice: function( start, end ) {\n                var blob = this.owner.source,\n                    slice = blob.slice || blob.webkitSlice || blob.mozSlice;\n    \n                blob = slice.call( blob, start, end );\n    \n                return new Blob( this.getRuid(), blob );\n            }\n        });\n    });\n    /**\n     * @fileOverview FilePaste\n     */\n    define('runtime/html5/dnd',[\n        'base',\n        'runtime/html5/runtime',\n        'lib/file'\n    ], function( Base, Html5Runtime, File ) {\n    \n        var $ = Base.$,\n            prefix = 'webuploader-dnd-';\n    \n        return Html5Runtime.register( 'DragAndDrop', {\n            init: function() {\n                var elem = this.elem = this.options.container;\n    \n                this.dragEnterHandler = Base.bindFn( this._dragEnterHandler, this );\n                this.dragOverHandler = Base.bindFn( this._dragOverHandler, this );\n                this.dragLeaveHandler = Base.bindFn( this._dragLeaveHandler, this );\n                this.dropHandler = Base.bindFn( this._dropHandler, this );\n                this.dndOver = false;\n    \n                elem.on( 'dragenter', this.dragEnterHandler );\n                elem.on( 'dragover', this.dragOverHandler );\n                elem.on( 'dragleave', this.dragLeaveHandler );\n                elem.on( 'drop', this.dropHandler );\n    \n                if ( this.options.disableGlobalDnd ) {\n                    $( document ).on( 'dragover', this.dragOverHandler );\n                    $( document ).on( 'drop', this.dropHandler );\n                }\n            },\n    \n            _dragEnterHandler: function( e ) {\n                var me = this,\n                    denied = me._denied || false,\n                    items;\n    \n                e = e.originalEvent || e;\n    \n                if ( !me.dndOver ) {\n                    me.dndOver = true;\n    \n                    // 注意只有 chrome 支持。\n                    items = e.dataTransfer.items;\n    \n                    if ( items && items.length ) {\n                        me._denied = denied = !me.trigger( 'accept', items );\n                    }\n    \n                    me.elem.addClass( prefix + 'over' );\n                    me.elem[ denied ? 'addClass' :\n                            'removeClass' ]( prefix + 'denied' );\n                }\n    \n    \n                e.dataTransfer.dropEffect = denied ? 'none' : 'copy';\n    \n                return false;\n            },\n    \n            _dragOverHandler: function( e ) {\n                // 只处理框内的。\n                var parentElem = this.elem.parent().get( 0 );\n                if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) {\n                    return false;\n                }\n    \n                clearTimeout( this._leaveTimer );\n                this._dragEnterHandler.call( this, e );\n    \n                return false;\n            },\n    \n            _dragLeaveHandler: function() {\n                var me = this,\n                    handler;\n    \n                handler = function() {\n                    me.dndOver = false;\n                    me.elem.removeClass( prefix + 'over ' + prefix + 'denied' );\n                };\n    \n                clearTimeout( me._leaveTimer );\n                me._leaveTimer = setTimeout( handler, 100 );\n                return false;\n            },\n    \n            _dropHandler: function( e ) {\n                var me = this,\n                    ruid = me.getRuid(),\n                    parentElem = me.elem.parent().get( 0 );\n    \n                // 只处理框内的。\n                if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) {\n                    return false;\n                }\n    \n                me._getTansferFiles( e, function( results ) {\n                    me.trigger( 'drop', $.map( results, function( file ) {\n                        return new File( ruid, file );\n                    }) );\n                });\n    \n                me.dndOver = false;\n                me.elem.removeClass( prefix + 'over' );\n                return false;\n            },\n    \n            // 如果传入 callback 则去查看文件夹，否则只管当前文件夹。\n            _getTansferFiles: function( e, callback ) {\n                var results  = [],\n                    promises = [],\n                    items, files, dataTransfer, file, item, i, len, canAccessFolder;\n    \n                e = e.originalEvent || e;\n    \n                dataTransfer = e.dataTransfer;\n                items = dataTransfer.items;\n                files = dataTransfer.files;\n    \n                canAccessFolder = !!(items && items[ 0 ].webkitGetAsEntry);\n    \n                for ( i = 0, len = files.length; i < len; i++ ) {\n                    file = files[ i ];\n                    item = items && items[ i ];\n    \n                    if ( canAccessFolder && item.webkitGetAsEntry().isDirectory ) {\n    \n                        promises.push( this._traverseDirectoryTree(\n                                item.webkitGetAsEntry(), results ) );\n                    } else {\n                        results.push( file );\n                    }\n                }\n    \n                Base.when.apply( Base, promises ).done(function() {\n    \n                    if ( !results.length ) {\n                        return;\n                    }\n    \n                    callback( results );\n                });\n            },\n    \n            _traverseDirectoryTree: function( entry, results ) {\n                var deferred = Base.Deferred(),\n                    me = this;\n    \n                if ( entry.isFile ) {\n                    entry.file(function( file ) {\n                        results.push( file );\n                        deferred.resolve();\n                    });\n                } else if ( entry.isDirectory ) {\n                    entry.createReader().readEntries(function( entries ) {\n                        var len = entries.length,\n                            promises = [],\n                            arr = [],    // 为了保证顺序。\n                            i;\n    \n                        for ( i = 0; i < len; i++ ) {\n                            promises.push( me._traverseDirectoryTree(\n                                    entries[ i ], arr ) );\n                        }\n    \n                        Base.when.apply( Base, promises ).then(function() {\n                            results.push.apply( results, arr );\n                            deferred.resolve();\n                        }, deferred.reject );\n                    });\n                }\n    \n                return deferred.promise();\n            },\n    \n            destroy: function() {\n                var elem = this.elem;\n    \n                elem.off( 'dragenter', this.dragEnterHandler );\n                elem.off( 'dragover', this.dragEnterHandler );\n                elem.off( 'dragleave', this.dragLeaveHandler );\n                elem.off( 'drop', this.dropHandler );\n    \n                if ( this.options.disableGlobalDnd ) {\n                    $( document ).off( 'dragover', this.dragOverHandler );\n                    $( document ).off( 'drop', this.dropHandler );\n                }\n            }\n        });\n    });\n    \n    /**\n     * @fileOverview FilePaste\n     */\n    define('runtime/html5/filepaste',[\n        'base',\n        'runtime/html5/runtime',\n        'lib/file'\n    ], function( Base, Html5Runtime, File ) {\n    \n        return Html5Runtime.register( 'FilePaste', {\n            init: function() {\n                var opts = this.options,\n                    elem = this.elem = opts.container,\n                    accept = '.*',\n                    arr, i, len, item;\n    \n                // accetp的mimeTypes中生成匹配正则。\n                if ( opts.accept ) {\n                    arr = [];\n    \n                    for ( i = 0, len = opts.accept.length; i < len; i++ ) {\n                        item = opts.accept[ i ].mimeTypes;\n                        item && arr.push( item );\n                    }\n    \n                    if ( arr.length ) {\n                        accept = arr.join(',');\n                        accept = accept.replace( /,/g, '|' ).replace( /\\*/g, '.*' );\n                    }\n                }\n                this.accept = accept = new RegExp( accept, 'i' );\n                this.hander = Base.bindFn( this._pasteHander, this );\n                elem.on( 'paste', this.hander );\n            },\n    \n            _pasteHander: function( e ) {\n                var allowed = [],\n                    ruid = this.getRuid(),\n                    items, item, blob, i, len;\n    \n                e = e.originalEvent || e;\n                items = e.clipboardData.items;\n    \n                for ( i = 0, len = items.length; i < len; i++ ) {\n                    item = items[ i ];\n    \n                    if ( item.kind !== 'file' || !(blob = item.getAsFile()) ) {\n                        continue;\n                    }\n    \n                    allowed.push( new File( ruid, blob ) );\n                }\n    \n                if ( allowed.length ) {\n                    // 不阻止非文件粘贴（文字粘贴）的事件冒泡\n                    e.preventDefault();\n                    e.stopPropagation();\n                    this.trigger( 'paste', allowed );\n                }\n            },\n    \n            destroy: function() {\n                this.elem.off( 'paste', this.hander );\n            }\n        });\n    });\n    \n    /**\n     * @fileOverview FilePicker\n     */\n    define('runtime/html5/filepicker',[\n        'base',\n        'runtime/html5/runtime'\n    ], function( Base, Html5Runtime ) {\n    \n        var $ = Base.$;\n    \n        return Html5Runtime.register( 'FilePicker', {\n            init: function() {\n                var container = this.getRuntime().getContainer(),\n                    me = this,\n                    owner = me.owner,\n                    opts = me.options,\n                    lable = $( document.createElement('label') ),\n                    input = $( document.createElement('input') ),\n                    arr, i, len, mouseHandler;\n    \n                input.attr( 'type', 'file' );\n                input.attr( 'name', opts.name );\n                input.addClass('webuploader-element-invisible');\n    \n                lable.on( 'click', function() {\n                    input.trigger('click');\n                });\n    \n                lable.css({\n                    opacity: 0,\n                    width: '100%',\n                    height: '100%',\n                    display: 'block',\n                    cursor: 'pointer',\n                    background: '#ffffff'\n                });\n    \n                if ( opts.multiple ) {\n                    input.attr( 'multiple', 'multiple' );\n                }\n    \n                // @todo Firefox不支持单独指定后缀\n                if ( opts.accept && opts.accept.length > 0 ) {\n                    arr = [];\n    \n                    for ( i = 0, len = opts.accept.length; i < len; i++ ) {\n                        arr.push( opts.accept[ i ].mimeTypes );\n                    }\n    \n                    input.attr( 'accept', arr.join(',') );\n                }\n    \n                container.append( input );\n                container.append( lable );\n    \n                mouseHandler = function( e ) {\n                    owner.trigger( e.type );\n                };\n    \n                input.on( 'change', function( e ) {\n                    var fn = arguments.callee,\n                        clone;\n    \n                    me.files = e.target.files;\n    \n                    // reset input\n                    clone = this.cloneNode( true );\n                    this.parentNode.replaceChild( clone, this );\n    \n                    input.off();\n                    input = $( clone ).on( 'change', fn )\n                            .on( 'mouseenter mouseleave', mouseHandler );\n    \n                    owner.trigger('change');\n                });\n    \n                lable.on( 'mouseenter mouseleave', mouseHandler );\n    \n            },\n    \n    \n            getFiles: function() {\n                return this.files;\n            },\n    \n            destroy: function() {\n                // todo\n            }\n        });\n    });\n    /**\n     * Terms:\n     *\n     * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer\n     * @fileOverview Image控件\n     */\n    define('runtime/html5/util',[\n        'base'\n    ], function( Base ) {\n    \n        var urlAPI = window.createObjectURL && window ||\n                window.URL && URL.revokeObjectURL && URL ||\n                window.webkitURL,\n            createObjectURL = Base.noop,\n            revokeObjectURL = createObjectURL;\n    \n        if ( urlAPI ) {\n    \n            // 更安全的方式调用，比如android里面就能把context改成其他的对象。\n            createObjectURL = function() {\n                return urlAPI.createObjectURL.apply( urlAPI, arguments );\n            };\n    \n            revokeObjectURL = function() {\n                return urlAPI.revokeObjectURL.apply( urlAPI, arguments );\n            };\n        }\n    \n        return {\n            createObjectURL: createObjectURL,\n            revokeObjectURL: revokeObjectURL,\n    \n            dataURL2Blob: function( dataURI ) {\n                var byteStr, intArray, ab, i, mimetype, parts;\n    \n                parts = dataURI.split(',');\n    \n                if ( ~parts[ 0 ].indexOf('base64') ) {\n                    byteStr = atob( parts[ 1 ] );\n                } else {\n                    byteStr = decodeURIComponent( parts[ 1 ] );\n                }\n    \n                ab = new ArrayBuffer( byteStr.length );\n                intArray = new Uint8Array( ab );\n    \n                for ( i = 0; i < byteStr.length; i++ ) {\n                    intArray[ i ] = byteStr.charCodeAt( i );\n                }\n    \n                mimetype = parts[ 0 ].split(':')[ 1 ].split(';')[ 0 ];\n    \n                return this.arrayBufferToBlob( ab, mimetype );\n            },\n    \n            dataURL2ArrayBuffer: function( dataURI ) {\n                var byteStr, intArray, i, parts;\n    \n                parts = dataURI.split(',');\n    \n                if ( ~parts[ 0 ].indexOf('base64') ) {\n                    byteStr = atob( parts[ 1 ] );\n                } else {\n                    byteStr = decodeURIComponent( parts[ 1 ] );\n                }\n    \n                intArray = new Uint8Array( byteStr.length );\n    \n                for ( i = 0; i < byteStr.length; i++ ) {\n                    intArray[ i ] = byteStr.charCodeAt( i );\n                }\n    \n                return intArray.buffer;\n            },\n    \n            arrayBufferToBlob: function( buffer, type ) {\n                var builder = window.BlobBuilder || window.WebKitBlobBuilder,\n                    bb;\n    \n                // android不支持直接new Blob, 只能借助blobbuilder.\n                if ( builder ) {\n                    bb = new builder();\n                    bb.append( buffer );\n                    return bb.getBlob( type );\n                }\n    \n                return new Blob([ buffer ], type ? { type: type } : {} );\n            },\n    \n            // 抽出来主要是为了解决android下面canvas.toDataUrl不支持jpeg.\n            // 你得到的结果是png.\n            canvasToDataUrl: function( canvas, type, quality ) {\n                return canvas.toDataURL( type, quality / 100 );\n            },\n    \n            // imagemeat会复写这个方法，如果用户选择加载那个文件了的话。\n            parseMeta: function( blob, callback ) {\n                callback( false, {});\n            },\n    \n            // imagemeat会复写这个方法，如果用户选择加载那个文件了的话。\n            updateImageHead: function( data ) {\n                return data;\n            }\n        };\n    });\n    /**\n     * Terms:\n     *\n     * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer\n     * @fileOverview Image控件\n     */\n    define('runtime/html5/imagemeta',[\n        'runtime/html5/util'\n    ], function( Util ) {\n    \n        var api;\n    \n        api = {\n            parsers: {\n                0xffe1: []\n            },\n    \n            maxMetaDataSize: 262144,\n    \n            parse: function( blob, cb ) {\n                var me = this,\n                    fr = new FileReader();\n    \n                fr.onload = function() {\n                    cb( false, me._parse( this.result ) );\n                    fr = fr.onload = fr.onerror = null;\n                };\n    \n                fr.onerror = function( e ) {\n                    cb( e.message );\n                    fr = fr.onload = fr.onerror = null;\n                };\n    \n                blob = blob.slice( 0, me.maxMetaDataSize );\n                fr.readAsArrayBuffer( blob.getSource() );\n            },\n    \n            _parse: function( buffer, noParse ) {\n                if ( buffer.byteLength < 6 ) {\n                    return;\n                }\n    \n                var dataview = new DataView( buffer ),\n                    offset = 2,\n                    maxOffset = dataview.byteLength - 4,\n                    headLength = offset,\n                    ret = {},\n                    markerBytes, markerLength, parsers, i;\n    \n                if ( dataview.getUint16( 0 ) === 0xffd8 ) {\n    \n                    while ( offset < maxOffset ) {\n                        markerBytes = dataview.getUint16( offset );\n    \n                        if ( markerBytes >= 0xffe0 && markerBytes <= 0xffef ||\n                                markerBytes === 0xfffe ) {\n    \n                            markerLength = dataview.getUint16( offset + 2 ) + 2;\n    \n                            if ( offset + markerLength > dataview.byteLength ) {\n                                break;\n                            }\n    \n                            parsers = api.parsers[ markerBytes ];\n    \n                            if ( !noParse && parsers ) {\n                                for ( i = 0; i < parsers.length; i += 1 ) {\n                                    parsers[ i ].call( api, dataview, offset,\n                                            markerLength, ret );\n                                }\n                            }\n    \n                            offset += markerLength;\n                            headLength = offset;\n                        } else {\n                            break;\n                        }\n                    }\n    \n                    if ( headLength > 6 ) {\n                        if ( buffer.slice ) {\n                            ret.imageHead = buffer.slice( 2, headLength );\n                        } else {\n                            // Workaround for IE10, which does not yet\n                            // support ArrayBuffer.slice:\n                            ret.imageHead = new Uint8Array( buffer )\n                                    .subarray( 2, headLength );\n                        }\n                    }\n                }\n    \n                return ret;\n            },\n    \n            updateImageHead: function( buffer, head ) {\n                var data = this._parse( buffer, true ),\n                    buf1, buf2, bodyoffset;\n    \n    \n                bodyoffset = 2;\n                if ( data.imageHead ) {\n                    bodyoffset = 2 + data.imageHead.byteLength;\n                }\n    \n                if ( buffer.slice ) {\n                    buf2 = buffer.slice( bodyoffset );\n                } else {\n                    buf2 = new Uint8Array( buffer ).subarray( bodyoffset );\n                }\n    \n                buf1 = new Uint8Array( head.byteLength + 2 + buf2.byteLength );\n    \n                buf1[ 0 ] = 0xFF;\n                buf1[ 1 ] = 0xD8;\n                buf1.set( new Uint8Array( head ), 2 );\n                buf1.set( new Uint8Array( buf2 ), head.byteLength + 2 );\n    \n                return buf1.buffer;\n            }\n        };\n    \n        Util.parseMeta = function() {\n            return api.parse.apply( api, arguments );\n        };\n    \n        Util.updateImageHead = function() {\n            return api.updateImageHead.apply( api, arguments );\n        };\n    \n        return api;\n    });\n    /**\n     * 代码来自于：https://github.com/blueimp/JavaScript-Load-Image\n     * 暂时项目中只用了orientation.\n     *\n     * 去除了 Exif Sub IFD Pointer, GPS Info IFD Pointer, Exif Thumbnail.\n     * @fileOverview EXIF解析\n     */\n    \n    // Sample\n    // ====================================\n    // Make : Apple\n    // Model : iPhone 4S\n    // Orientation : 1\n    // XResolution : 72 [72/1]\n    // YResolution : 72 [72/1]\n    // ResolutionUnit : 2\n    // Software : QuickTime 7.7.1\n    // DateTime : 2013:09:01 22:53:55\n    // ExifIFDPointer : 190\n    // ExposureTime : 0.058823529411764705 [1/17]\n    // FNumber : 2.4 [12/5]\n    // ExposureProgram : Normal program\n    // ISOSpeedRatings : 800\n    // ExifVersion : 0220\n    // DateTimeOriginal : 2013:09:01 22:52:51\n    // DateTimeDigitized : 2013:09:01 22:52:51\n    // ComponentsConfiguration : YCbCr\n    // ShutterSpeedValue : 4.058893515764426\n    // ApertureValue : 2.5260688216892597 [4845/1918]\n    // BrightnessValue : -0.3126686601998395\n    // MeteringMode : Pattern\n    // Flash : Flash did not fire, compulsory flash mode\n    // FocalLength : 4.28 [107/25]\n    // SubjectArea : [4 values]\n    // FlashpixVersion : 0100\n    // ColorSpace : 1\n    // PixelXDimension : 2448\n    // PixelYDimension : 3264\n    // SensingMethod : One-chip color area sensor\n    // ExposureMode : 0\n    // WhiteBalance : Auto white balance\n    // FocalLengthIn35mmFilm : 35\n    // SceneCaptureType : Standard\n    define('runtime/html5/imagemeta/exif',[\n        'base',\n        'runtime/html5/imagemeta'\n    ], function( Base, ImageMeta ) {\n    \n        var EXIF = {};\n    \n        EXIF.ExifMap = function() {\n            return this;\n        };\n    \n        EXIF.ExifMap.prototype.map = {\n            'Orientation': 0x0112\n        };\n    \n        EXIF.ExifMap.prototype.get = function( id ) {\n            return this[ id ] || this[ this.map[ id ] ];\n        };\n    \n        EXIF.exifTagTypes = {\n            // byte, 8-bit unsigned int:\n            1: {\n                getValue: function( dataView, dataOffset ) {\n                    return dataView.getUint8( dataOffset );\n                },\n                size: 1\n            },\n    \n            // ascii, 8-bit byte:\n            2: {\n                getValue: function( dataView, dataOffset ) {\n                    return String.fromCharCode( dataView.getUint8( dataOffset ) );\n                },\n                size: 1,\n                ascii: true\n            },\n    \n            // short, 16 bit int:\n            3: {\n                getValue: function( dataView, dataOffset, littleEndian ) {\n                    return dataView.getUint16( dataOffset, littleEndian );\n                },\n                size: 2\n            },\n    \n            // long, 32 bit int:\n            4: {\n                getValue: function( dataView, dataOffset, littleEndian ) {\n                    return dataView.getUint32( dataOffset, littleEndian );\n                },\n                size: 4\n            },\n    \n            // rational = two long values,\n            // first is numerator, second is denominator:\n            5: {\n                getValue: function( dataView, dataOffset, littleEndian ) {\n                    return dataView.getUint32( dataOffset, littleEndian ) /\n                        dataView.getUint32( dataOffset + 4, littleEndian );\n                },\n                size: 8\n            },\n    \n            // slong, 32 bit signed int:\n            9: {\n                getValue: function( dataView, dataOffset, littleEndian ) {\n                    return dataView.getInt32( dataOffset, littleEndian );\n                },\n                size: 4\n            },\n    \n            // srational, two slongs, first is numerator, second is denominator:\n            10: {\n                getValue: function( dataView, dataOffset, littleEndian ) {\n                    return dataView.getInt32( dataOffset, littleEndian ) /\n                        dataView.getInt32( dataOffset + 4, littleEndian );\n                },\n                size: 8\n            }\n        };\n    \n        // undefined, 8-bit byte, value depending on field:\n        EXIF.exifTagTypes[ 7 ] = EXIF.exifTagTypes[ 1 ];\n    \n        EXIF.getExifValue = function( dataView, tiffOffset, offset, type, length,\n                littleEndian ) {\n    \n            var tagType = EXIF.exifTagTypes[ type ],\n                tagSize, dataOffset, values, i, str, c;\n    \n            if ( !tagType ) {\n                Base.log('Invalid Exif data: Invalid tag type.');\n                return;\n            }\n    \n            tagSize = tagType.size * length;\n    \n            // Determine if the value is contained in the dataOffset bytes,\n            // or if the value at the dataOffset is a pointer to the actual data:\n            dataOffset = tagSize > 4 ? tiffOffset + dataView.getUint32( offset + 8,\n                    littleEndian ) : (offset + 8);\n    \n            if ( dataOffset + tagSize > dataView.byteLength ) {\n                Base.log('Invalid Exif data: Invalid data offset.');\n                return;\n            }\n    \n            if ( length === 1 ) {\n                return tagType.getValue( dataView, dataOffset, littleEndian );\n            }\n    \n            values = [];\n    \n            for ( i = 0; i < length; i += 1 ) {\n                values[ i ] = tagType.getValue( dataView,\n                        dataOffset + i * tagType.size, littleEndian );\n            }\n    \n            if ( tagType.ascii ) {\n                str = '';\n    \n                // Concatenate the chars:\n                for ( i = 0; i < values.length; i += 1 ) {\n                    c = values[ i ];\n    \n                    // Ignore the terminating NULL byte(s):\n                    if ( c === '\\u0000' ) {\n                        break;\n                    }\n                    str += c;\n                }\n    \n                return str;\n            }\n            return values;\n        };\n    \n        EXIF.parseExifTag = function( dataView, tiffOffset, offset, littleEndian,\n                data ) {\n    \n            var tag = dataView.getUint16( offset, littleEndian );\n            data.exif[ tag ] = EXIF.getExifValue( dataView, tiffOffset, offset,\n                    dataView.getUint16( offset + 2, littleEndian ),    // tag type\n                    dataView.getUint32( offset + 4, littleEndian ),    // tag length\n                    littleEndian );\n        };\n    \n        EXIF.parseExifTags = function( dataView, tiffOffset, dirOffset,\n                littleEndian, data ) {\n    \n            var tagsNumber, dirEndOffset, i;\n    \n            if ( dirOffset + 6 > dataView.byteLength ) {\n                Base.log('Invalid Exif data: Invalid directory offset.');\n                return;\n            }\n    \n            tagsNumber = dataView.getUint16( dirOffset, littleEndian );\n            dirEndOffset = dirOffset + 2 + 12 * tagsNumber;\n    \n            if ( dirEndOffset + 4 > dataView.byteLength ) {\n                Base.log('Invalid Exif data: Invalid directory size.');\n                return;\n            }\n    \n            for ( i = 0; i < tagsNumber; i += 1 ) {\n                this.parseExifTag( dataView, tiffOffset,\n                        dirOffset + 2 + 12 * i,    // tag offset\n                        littleEndian, data );\n            }\n    \n            // Return the offset to the next directory:\n            return dataView.getUint32( dirEndOffset, littleEndian );\n        };\n    \n        // EXIF.getExifThumbnail = function(dataView, offset, length) {\n        //     var hexData,\n        //         i,\n        //         b;\n        //     if (!length || offset + length > dataView.byteLength) {\n        //         Base.log('Invalid Exif data: Invalid thumbnail data.');\n        //         return;\n        //     }\n        //     hexData = [];\n        //     for (i = 0; i < length; i += 1) {\n        //         b = dataView.getUint8(offset + i);\n        //         hexData.push((b < 16 ? '0' : '') + b.toString(16));\n        //     }\n        //     return 'data:image/jpeg,%' + hexData.join('%');\n        // };\n    \n        EXIF.parseExifData = function( dataView, offset, length, data ) {\n    \n            var tiffOffset = offset + 10,\n                littleEndian, dirOffset;\n    \n            // Check for the ASCII code for \"Exif\" (0x45786966):\n            if ( dataView.getUint32( offset + 4 ) !== 0x45786966 ) {\n                // No Exif data, might be XMP data instead\n                return;\n            }\n            if ( tiffOffset + 8 > dataView.byteLength ) {\n                Base.log('Invalid Exif data: Invalid segment size.');\n                return;\n            }\n    \n            // Check for the two null bytes:\n            if ( dataView.getUint16( offset + 8 ) !== 0x0000 ) {\n                Base.log('Invalid Exif data: Missing byte alignment offset.');\n                return;\n            }\n    \n            // Check the byte alignment:\n            switch ( dataView.getUint16( tiffOffset ) ) {\n                case 0x4949:\n                    littleEndian = true;\n                    break;\n    \n                case 0x4D4D:\n                    littleEndian = false;\n                    break;\n    \n                default:\n                    Base.log('Invalid Exif data: Invalid byte alignment marker.');\n                    return;\n            }\n    \n            // Check for the TIFF tag marker (0x002A):\n            if ( dataView.getUint16( tiffOffset + 2, littleEndian ) !== 0x002A ) {\n                Base.log('Invalid Exif data: Missing TIFF marker.');\n                return;\n            }\n    \n            // Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal:\n            dirOffset = dataView.getUint32( tiffOffset + 4, littleEndian );\n            // Create the exif object to store the tags:\n            data.exif = new EXIF.ExifMap();\n            // Parse the tags of the main image directory and retrieve the\n            // offset to the next directory, usually the thumbnail directory:\n            dirOffset = EXIF.parseExifTags( dataView, tiffOffset,\n                    tiffOffset + dirOffset, littleEndian, data );\n    \n            // 尝试读取缩略图\n            // if ( dirOffset ) {\n            //     thumbnailData = {exif: {}};\n            //     dirOffset = EXIF.parseExifTags(\n            //         dataView,\n            //         tiffOffset,\n            //         tiffOffset + dirOffset,\n            //         littleEndian,\n            //         thumbnailData\n            //     );\n    \n            //     // Check for JPEG Thumbnail offset:\n            //     if (thumbnailData.exif[0x0201]) {\n            //         data.exif.Thumbnail = EXIF.getExifThumbnail(\n            //             dataView,\n            //             tiffOffset + thumbnailData.exif[0x0201],\n            //             thumbnailData.exif[0x0202] // Thumbnail data length\n            //         );\n            //     }\n            // }\n        };\n    \n        ImageMeta.parsers[ 0xffe1 ].push( EXIF.parseExifData );\n        return EXIF;\n    });\n    /**\n     * @fileOverview Image\n     */\n    define('runtime/html5/image',[\n        'base',\n        'runtime/html5/runtime',\n        'runtime/html5/util'\n    ], function( Base, Html5Runtime, Util ) {\n    \n        var BLANK = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D';\n    \n        return Html5Runtime.register( 'Image', {\n    \n            // flag: 标记是否被修改过。\n            modified: false,\n    \n            init: function() {\n                var me = this,\n                    img = new Image();\n    \n                img.onload = function() {\n    \n                    me._info = {\n                        type: me.type,\n                        width: this.width,\n                        height: this.height\n                    };\n    \n                    // 读取meta信息。\n                    if ( !me._metas && 'image/jpeg' === me.type ) {\n                        Util.parseMeta( me._blob, function( error, ret ) {\n                            me._metas = ret;\n                            me.owner.trigger('load');\n                        });\n                    } else {\n                        me.owner.trigger('load');\n                    }\n                };\n    \n                img.onerror = function() {\n                    me.owner.trigger('error');\n                };\n    \n                me._img = img;\n            },\n    \n            loadFromBlob: function( blob ) {\n                var me = this,\n                    img = me._img;\n    \n                me._blob = blob;\n                me.type = blob.type;\n                img.src = Util.createObjectURL( blob.getSource() );\n                me.owner.once( 'load', function() {\n                    Util.revokeObjectURL( img.src );\n                });\n            },\n    \n            resize: function( width, height ) {\n                var canvas = this._canvas ||\n                        (this._canvas = document.createElement('canvas'));\n    \n                this._resize( this._img, canvas, width, height );\n                this._blob = null;    // 没用了，可以删掉了。\n                this.modified = true;\n                this.owner.trigger('complete');\n            },\n    \n            getAsBlob: function( type ) {\n                var blob = this._blob,\n                    opts = this.options,\n                    canvas;\n    \n                type = type || this.type;\n    \n                // blob需要重新生成。\n                if ( this.modified || this.type !== type ) {\n                    canvas = this._canvas;\n    \n                    if ( type === 'image/jpeg' ) {\n    \n                        blob = Util.canvasToDataUrl( canvas, 'image/jpeg',\n                                opts.quality );\n    \n                        if ( opts.preserveHeaders && this._metas &&\n                                this._metas.imageHead ) {\n    \n                            blob = Util.dataURL2ArrayBuffer( blob );\n                            blob = Util.updateImageHead( blob,\n                                    this._metas.imageHead );\n                            blob = Util.arrayBufferToBlob( blob, type );\n                            return blob;\n                        }\n                    } else {\n                        blob = Util.canvasToDataUrl( canvas, type );\n                    }\n    \n                    blob = Util.dataURL2Blob( blob );\n                }\n    \n                return blob;\n            },\n    \n            getAsDataUrl: function( type ) {\n                var opts = this.options;\n    \n                type = type || this.type;\n    \n                if ( type === 'image/jpeg' ) {\n                    return Util.canvasToDataUrl( this._canvas, type, opts.quality );\n                } else {\n                    return this._canvas.toDataURL( type );\n                }\n            },\n    \n            getOrientation: function() {\n                return this._metas && this._metas.exif &&\n                        this._metas.exif.get('Orientation') || 1;\n            },\n    \n            info: function( val ) {\n    \n                // setter\n                if ( val ) {\n                    this._info = val;\n                    return this;\n                }\n    \n                // getter\n                return this._info;\n            },\n    \n            meta: function( val ) {\n    \n                // setter\n                if ( val ) {\n                    this._meta = val;\n                    return this;\n                }\n    \n                // getter\n                return this._meta;\n            },\n    \n            destroy: function() {\n                var canvas = this._canvas;\n                this._img.onload = null;\n    \n                if ( canvas ) {\n                    canvas.getContext('2d')\n                            .clearRect( 0, 0, canvas.width, canvas.height );\n                    canvas.width = canvas.height = 0;\n                    this._canvas = null;\n                }\n    \n                // 释放内存。非常重要，否则释放不了image的内存。\n                this._img.src = BLANK;\n                this._img = this._blob = null;\n            },\n    \n            _resize: function( img, cvs, width, height ) {\n                var opts = this.options,\n                    naturalWidth = img.width,\n                    naturalHeight = img.height,\n                    orientation = this.getOrientation(),\n                    scale, w, h, x, y;\n    \n                // values that require 90 degree rotation\n                if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) {\n    \n                    // 交换width, height的值。\n                    width ^= height;\n                    height ^= width;\n                    width ^= height;\n                }\n    \n                scale = Math[ opts.crop ? 'max' : 'min' ]( width / naturalWidth,\n                        height / naturalHeight );\n    \n                // 不允许放大。\n                opts.allowMagnify || (scale = Math.min( 1, scale ));\n    \n                w = naturalWidth * scale;\n                h = naturalHeight * scale;\n    \n                if ( opts.crop ) {\n                    cvs.width = width;\n                    cvs.height = height;\n                } else {\n                    cvs.width = w;\n                    cvs.height = h;\n                }\n    \n                x = (cvs.width - w) / 2;\n                y = (cvs.height - h) / 2;\n    \n                opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation );\n    \n                this._renderImageToCanvas( cvs, img, x, y, w, h );\n            },\n    \n            _rotate2Orientaion: function( canvas, orientation ) {\n                var width = canvas.width,\n                    height = canvas.height,\n                    ctx = canvas.getContext('2d');\n    \n                switch ( orientation ) {\n                    case 5:\n                    case 6:\n                    case 7:\n                    case 8:\n                        canvas.width = height;\n                        canvas.height = width;\n                        break;\n                }\n    \n                switch ( orientation ) {\n                    case 2:    // horizontal flip\n                        ctx.translate( width, 0 );\n                        ctx.scale( -1, 1 );\n                        break;\n    \n                    case 3:    // 180 rotate left\n                        ctx.translate( width, height );\n                        ctx.rotate( Math.PI );\n                        break;\n    \n                    case 4:    // vertical flip\n                        ctx.translate( 0, height );\n                        ctx.scale( 1, -1 );\n                        break;\n    \n                    case 5:    // vertical flip + 90 rotate right\n                        ctx.rotate( 0.5 * Math.PI );\n                        ctx.scale( 1, -1 );\n                        break;\n    \n                    case 6:    // 90 rotate right\n                        ctx.rotate( 0.5 * Math.PI );\n                        ctx.translate( 0, -height );\n                        break;\n    \n                    case 7:    // horizontal flip + 90 rotate right\n                        ctx.rotate( 0.5 * Math.PI );\n                        ctx.translate( width, -height );\n                        ctx.scale( -1, 1 );\n                        break;\n    \n                    case 8:    // 90 rotate left\n                        ctx.rotate( -0.5 * Math.PI );\n                        ctx.translate( -width, 0 );\n                        break;\n                }\n            },\n    \n            // https://github.com/stomita/ios-imagefile-megapixel/\n            // blob/master/src/megapix-image.js\n            _renderImageToCanvas: (function() {\n    \n                // 如果不是ios, 不需要这么复杂！\n                if ( !Base.os.ios ) {\n                    return function( canvas, img, x, y, w, h ) {\n                        canvas.getContext('2d').drawImage( img, x, y, w, h );\n                    };\n                }\n    \n                /**\n                 * Detecting vertical squash in loaded image.\n                 * Fixes a bug which squash image vertically while drawing into\n                 * canvas for some images.\n                 */\n                function detectVerticalSquash( img, iw, ih ) {\n                    var canvas = document.createElement('canvas'),\n                        ctx = canvas.getContext('2d'),\n                        sy = 0,\n                        ey = ih,\n                        py = ih,\n                        data, alpha, ratio;\n    \n    \n                    canvas.width = 1;\n                    canvas.height = ih;\n                    ctx.drawImage( img, 0, 0 );\n                    data = ctx.getImageData( 0, 0, 1, ih ).data;\n    \n                    // search image edge pixel position in case\n                    // it is squashed vertically.\n                    while ( py > sy ) {\n                        alpha = data[ (py - 1) * 4 + 3 ];\n    \n                        if ( alpha === 0 ) {\n                            ey = py;\n                        } else {\n                            sy = py;\n                        }\n    \n                        py = (ey + sy) >> 1;\n                    }\n    \n                    ratio = (py / ih);\n                    return (ratio === 0) ? 1 : ratio;\n                }\n    \n                // fix ie7 bug\n                // http://stackoverflow.com/questions/11929099/\n                // html5-canvas-drawimage-ratio-bug-ios\n                if ( Base.os.ios >= 7 ) {\n                    return function( canvas, img, x, y, w, h ) {\n                        var iw = img.naturalWidth,\n                            ih = img.naturalHeight,\n                            vertSquashRatio = detectVerticalSquash( img, iw, ih );\n    \n                        return canvas.getContext('2d').drawImage( img, 0, 0,\n                            iw * vertSquashRatio, ih * vertSquashRatio,\n                            x, y, w, h );\n                    };\n                }\n    \n                /**\n                 * Detect subsampling in loaded image.\n                 * In iOS, larger images than 2M pixels may be\n                 * subsampled in rendering.\n                 */\n                function detectSubsampling( img ) {\n                    var iw = img.naturalWidth,\n                        ih = img.naturalHeight,\n                        canvas, ctx;\n    \n                    // subsampling may happen overmegapixel image\n                    if ( iw * ih > 1024 * 1024 ) {\n                        canvas = document.createElement('canvas');\n                        canvas.width = canvas.height = 1;\n                        ctx = canvas.getContext('2d');\n                        ctx.drawImage( img, -iw + 1, 0 );\n    \n                        // subsampled image becomes half smaller in rendering size.\n                        // check alpha channel value to confirm image is covering\n                        // edge pixel or not. if alpha value is 0\n                        // image is not covering, hence subsampled.\n                        return ctx.getImageData( 0, 0, 1, 1 ).data[ 3 ] === 0;\n                    } else {\n                        return false;\n                    }\n                }\n    \n    \n                return function( canvas, img, x, y, width, height ) {\n                    var iw = img.naturalWidth,\n                        ih = img.naturalHeight,\n                        ctx = canvas.getContext('2d'),\n                        subsampled = detectSubsampling( img ),\n                        doSquash = this.type === 'image/jpeg',\n                        d = 1024,\n                        sy = 0,\n                        dy = 0,\n                        tmpCanvas, tmpCtx, vertSquashRatio, dw, dh, sx, dx;\n    \n                    if ( subsampled ) {\n                        iw /= 2;\n                        ih /= 2;\n                    }\n    \n                    ctx.save();\n                    tmpCanvas = document.createElement('canvas');\n                    tmpCanvas.width = tmpCanvas.height = d;\n    \n                    tmpCtx = tmpCanvas.getContext('2d');\n                    vertSquashRatio = doSquash ?\n                            detectVerticalSquash( img, iw, ih ) : 1;\n    \n                    dw = Math.ceil( d * width / iw );\n                    dh = Math.ceil( d * height / ih / vertSquashRatio );\n    \n                    while ( sy < ih ) {\n                        sx = 0;\n                        dx = 0;\n                        while ( sx < iw ) {\n                            tmpCtx.clearRect( 0, 0, d, d );\n                            tmpCtx.drawImage( img, -sx, -sy );\n                            ctx.drawImage( tmpCanvas, 0, 0, d, d,\n                                    x + dx, y + dy, dw, dh );\n                            sx += d;\n                            dx += dw;\n                        }\n                        sy += d;\n                        dy += dh;\n                    }\n                    ctx.restore();\n                    tmpCanvas = tmpCtx = null;\n                };\n            })()\n        });\n    });\n    /**\n     * @fileOverview Transport\n     * @todo 支持chunked传输，优势：\n     * 可以将大文件分成小块，挨个传输，可以提高大文件成功率，当失败的时候，也只需要重传那小部分，\n     * 而不需要重头再传一次。另外断点续传也需要用chunked方式。\n     */\n    define('runtime/html5/transport',[\n        'base',\n        'runtime/html5/runtime'\n    ], function( Base, Html5Runtime ) {\n    \n        var noop = Base.noop,\n            $ = Base.$;\n    \n        return Html5Runtime.register( 'Transport', {\n            init: function() {\n                this._status = 0;\n                this._response = null;\n            },\n    \n            send: function() {\n                var owner = this.owner,\n                    opts = this.options,\n                    xhr = this._initAjax(),\n                    blob = owner._blob,\n                    server = opts.server,\n                    formData, binary, fr;\n    \n                if ( opts.sendAsBinary ) {\n                    server += (/\\?/.test( server ) ? '&' : '?') +\n                            $.param( owner._formData );\n    \n                    binary = blob.getSource();\n                } else {\n                    formData = new FormData();\n                    $.each( owner._formData, function( k, v ) {\n                        formData.append( k, v );\n                    });\n    \n                    formData.append( opts.fileVal, blob.getSource(),\n                            opts.filename || owner._formData.name || '' );\n                }\n    \n                if ( opts.withCredentials && 'withCredentials' in xhr ) {\n                    xhr.open( opts.method, server, true );\n                    xhr.withCredentials = true;\n                } else {\n                    xhr.open( opts.method, server );\n                }\n    \n                this._setRequestHeader( xhr, opts.headers );\n    \n                if ( binary ) {\n                    xhr.overrideMimeType('application/octet-stream');\n    \n                    // android直接发送blob会导致服务端接收到的是空文件。\n                    // bug详情。\n                    // https://code.google.com/p/android/issues/detail?id=39882\n                    // 所以先用fileReader读取出来再通过arraybuffer的方式发送。\n                    if ( Base.os.android ) {\n                        fr = new FileReader();\n    \n                        fr.onload = function() {\n                            xhr.send( this.result );\n                            fr = fr.onload = null;\n                        };\n    \n                        fr.readAsArrayBuffer( binary );\n                    } else {\n                        xhr.send( binary );\n                    }\n                } else {\n                    xhr.send( formData );\n                }\n            },\n    \n            getResponse: function() {\n                return this._response;\n            },\n    \n            getResponseAsJson: function() {\n                return this._parseJson( this._response );\n            },\n    \n            getStatus: function() {\n                return this._status;\n            },\n    \n            abort: function() {\n                var xhr = this._xhr;\n    \n                if ( xhr ) {\n                    xhr.upload.onprogress = noop;\n                    xhr.onreadystatechange = noop;\n                    xhr.abort();\n    \n                    this._xhr = xhr = null;\n                }\n            },\n    \n            destroy: function() {\n                this.abort();\n            },\n    \n            _initAjax: function() {\n                var me = this,\n                    xhr = new XMLHttpRequest(),\n                    opts = this.options;\n    \n                if ( opts.withCredentials && !('withCredentials' in xhr) &&\n                        typeof XDomainRequest !== 'undefined' ) {\n                    xhr = new XDomainRequest();\n                }\n    \n                xhr.upload.onprogress = function( e ) {\n                    var percentage = 0;\n    \n                    if ( e.lengthComputable ) {\n                        percentage = e.loaded / e.total;\n                    }\n    \n                    return me.trigger( 'progress', percentage );\n                };\n    \n                xhr.onreadystatechange = function() {\n    \n                    if ( xhr.readyState !== 4 ) {\n                        return;\n                    }\n    \n                    xhr.upload.onprogress = noop;\n                    xhr.onreadystatechange = noop;\n                    me._xhr = null;\n                    me._status = xhr.status;\n    \n                    if ( xhr.status >= 200 && xhr.status < 300 ) {\n                        me._response = xhr.responseText;\n                        return me.trigger('load');\n                    } else if ( xhr.status >= 500 && xhr.status < 600 ) {\n                        me._response = xhr.responseText;\n                        return me.trigger( 'error', 'server' );\n                    }\n    \n    \n                    return me.trigger( 'error', me._status ? 'http' : 'abort' );\n                };\n    \n                me._xhr = xhr;\n                return xhr;\n            },\n    \n            _setRequestHeader: function( xhr, headers ) {\n                $.each( headers, function( key, val ) {\n                    xhr.setRequestHeader( key, val );\n                });\n            },\n    \n            _parseJson: function( str ) {\n                var json;\n    \n                try {\n                    json = JSON.parse( str );\n                } catch ( ex ) {\n                    json = {};\n                }\n    \n                return json;\n            }\n        });\n    });\n    /**\n     * @fileOverview 只有html5实现的文件版本。\n     */\n    define('preset/html5only',[\n        'base',\n    \n        // widgets\n        'widgets/filednd',\n        'widgets/filepaste',\n        'widgets/filepicker',\n        'widgets/image',\n        'widgets/queue',\n        'widgets/runtime',\n        'widgets/upload',\n        'widgets/validator',\n    \n        // runtimes\n        // html5\n        'runtime/html5/blob',\n        'runtime/html5/dnd',\n        'runtime/html5/filepaste',\n        'runtime/html5/filepicker',\n        'runtime/html5/imagemeta/exif',\n        'runtime/html5/image',\n        'runtime/html5/transport'\n    ], function( Base ) {\n        return Base;\n    });\n    define('webuploader',[\n        'preset/html5only'\n    ], function( preset ) {\n        return preset;\n    });\n    return require('webuploader');\n});\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/webuploader/webuploader.js",
    "content": "/*! WebUploader 0.1.2 */\n\n\n/**\n * @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。\n *\n * AMD API 内部的简单不完全实现，请忽略。只有当WebUploader被合并成一个文件的时候才会引入。\n */\n(function( root, factory ) {\n    var modules = {},\n\n        // 内部require, 简单不完全实现。\n        // https://github.com/amdjs/amdjs-api/wiki/require\n        _require = function( deps, callback ) {\n            var args, len, i;\n\n            // 如果deps不是数组，则直接返回指定module\n            if ( typeof deps === 'string' ) {\n                return getModule( deps );\n            } else {\n                args = [];\n                for( len = deps.length, i = 0; i < len; i++ ) {\n                    args.push( getModule( deps[ i ] ) );\n                }\n\n                return callback.apply( null, args );\n            }\n        },\n\n        // 内部define，暂时不支持不指定id.\n        _define = function( id, deps, factory ) {\n            if ( arguments.length === 2 ) {\n                factory = deps;\n                deps = null;\n            }\n\n            _require( deps || [], function() {\n                setModule( id, factory, arguments );\n            });\n        },\n\n        // 设置module, 兼容CommonJs写法。\n        setModule = function( id, factory, args ) {\n            var module = {\n                    exports: factory\n                },\n                returned;\n\n            if ( typeof factory === 'function' ) {\n                args.length || (args = [ _require, module.exports, module ]);\n                returned = factory.apply( null, args );\n                returned !== undefined && (module.exports = returned);\n            }\n\n            modules[ id ] = module.exports;\n        },\n\n        // 根据id获取module\n        getModule = function( id ) {\n            var module = modules[ id ] || root[ id ];\n\n            if ( !module ) {\n                throw new Error( '`' + id + '` is undefined' );\n            }\n\n            return module;\n        },\n\n        // 将所有modules，将路径ids装换成对象。\n        exportsTo = function( obj ) {\n            var key, host, parts, part, last, ucFirst;\n\n            // make the first character upper case.\n            ucFirst = function( str ) {\n                return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 ));\n            };\n\n            for ( key in modules ) {\n                host = obj;\n\n                if ( !modules.hasOwnProperty( key ) ) {\n                    continue;\n                }\n\n                parts = key.split('/');\n                last = ucFirst( parts.pop() );\n\n                while( (part = ucFirst( parts.shift() )) ) {\n                    host[ part ] = host[ part ] || {};\n                    host = host[ part ];\n                }\n\n                host[ last ] = modules[ key ];\n            }\n        },\n\n        exports = factory( root, _define, _require ),\n        origin;\n\n    // exports every module.\n    exportsTo( exports );\n\n    if ( typeof module === 'object' && typeof module.exports === 'object' ) {\n\n        // For CommonJS and CommonJS-like environments where a proper window is present,\n        module.exports = exports;\n    } else if ( typeof define === 'function' && define.amd ) {\n\n        // Allow using this built library as an AMD module\n        // in another project. That other project will only\n        // see this AMD call, not the internal modules in\n        // the closure below.\n        define([], exports );\n    } else {\n\n        // Browser globals case. Just assign the\n        // result to a property on the global.\n        origin = root.WebUploader;\n        root.WebUploader = exports;\n        root.WebUploader.noConflict = function() {\n            root.WebUploader = origin;\n        };\n    }\n})( this, function( window, define, require ) {\n\n\n    /**\n     * @fileOverview jQuery or Zepto\n     */\n    define('dollar-third',[],function() {\n        return window.jQuery || window.Zepto;\n    });\n    /**\n     * @fileOverview Dom 操作相关\n     */\n    define('dollar',[\n        'dollar-third'\n    ], function( _ ) {\n        return _;\n    });\n    /**\n     * @fileOverview 使用jQuery的Promise\n     */\n    define('promise-third',[\n        'dollar'\n    ], function( $ ) {\n        return {\n            Deferred: $.Deferred,\n            when: $.when,\n    \n            isPromise: function( anything ) {\n                return anything && typeof anything.then === 'function';\n            }\n        };\n    });\n    /**\n     * @fileOverview Promise/A+\n     */\n    define('promise',[\n        'promise-third'\n    ], function( _ ) {\n        return _;\n    });\n    /**\n     * @fileOverview 基础类方法。\n     */\n    \n    /**\n     * Web Uploader内部类的详细说明，以下提及的功能类，都可以在`WebUploader`这个变量中访问到。\n     *\n     * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id.\n     * 默认module id该文件的路径，而此路径将会转化成名字空间存放在WebUploader中。如：\n     *\n     * * module `base`：WebUploader.Base\n     * * module `file`: WebUploader.File\n     * * module `lib/dnd`: WebUploader.Lib.Dnd\n     * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd\n     *\n     *\n     * 以下文档将可能省略`WebUploader`前缀。\n     * @module WebUploader\n     * @title WebUploader API文档\n     */\n    define('base',[\n        'dollar',\n        'promise'\n    ], function( $, promise ) {\n    \n        var noop = function() {},\n            call = Function.call;\n    \n        // http://jsperf.com/uncurrythis\n        // 反科里化\n        function uncurryThis( fn ) {\n            return function() {\n                return call.apply( fn, arguments );\n            };\n        }\n    \n        function bindFn( fn, context ) {\n            return function() {\n                return fn.apply( context, arguments );\n            };\n        }\n    \n        function createObject( proto ) {\n            var f;\n    \n            if ( Object.create ) {\n                return Object.create( proto );\n            } else {\n                f = function() {};\n                f.prototype = proto;\n                return new f();\n            }\n        }\n    \n    \n        /**\n         * 基础类，提供一些简单常用的方法。\n         * @class Base\n         */\n        return {\n    \n            /**\n             * @property {String} version 当前版本号。\n             */\n            version: '0.1.2',\n    \n            /**\n             * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。\n             */\n            $: $,\n    \n            Deferred: promise.Deferred,\n    \n            isPromise: promise.isPromise,\n    \n            when: promise.when,\n    \n            /**\n             * @description  简单的浏览器检查结果。\n             *\n             * * `webkit`  webkit版本号，如果浏览器为非webkit内核，此属性为`undefined`。\n             * * `chrome`  chrome浏览器版本号，如果浏览器为chrome，此属性为`undefined`。\n             * * `ie`  ie浏览器版本号，如果浏览器为非ie，此属性为`undefined`。**暂不支持ie10+**\n             * * `firefox`  firefox浏览器版本号，如果浏览器为非firefox，此属性为`undefined`。\n             * * `safari`  safari浏览器版本号，如果浏览器为非safari，此属性为`undefined`。\n             * * `opera`  opera浏览器版本号，如果浏览器为非opera，此属性为`undefined`。\n             *\n             * @property {Object} [browser]\n             */\n            browser: (function( ua ) {\n                var ret = {},\n                    webkit = ua.match( /WebKit\\/([\\d.]+)/ ),\n                    chrome = ua.match( /Chrome\\/([\\d.]+)/ ) ||\n                        ua.match( /CriOS\\/([\\d.]+)/ ),\n    \n                    ie = ua.match( /MSIE\\s([\\d\\.]+)/ ) ||\n                        ua.match(/(?:trident)(?:.*rv:([\\w.]+))?/i),\n                    firefox = ua.match( /Firefox\\/([\\d.]+)/ ),\n                    safari = ua.match( /Safari\\/([\\d.]+)/ ),\n                    opera = ua.match( /OPR\\/([\\d.]+)/ );\n    \n                webkit && (ret.webkit = parseFloat( webkit[ 1 ] ));\n                chrome && (ret.chrome = parseFloat( chrome[ 1 ] ));\n                ie && (ret.ie = parseFloat( ie[ 1 ] ));\n                firefox && (ret.firefox = parseFloat( firefox[ 1 ] ));\n                safari && (ret.safari = parseFloat( safari[ 1 ] ));\n                opera && (ret.opera = parseFloat( opera[ 1 ] ));\n    \n                return ret;\n            })( navigator.userAgent ),\n    \n            /**\n             * @description  操作系统检查结果。\n             *\n             * * `android`  如果在android浏览器环境下，此值为对应的android版本号，否则为`undefined`。\n             * * `ios` 如果在ios浏览器环境下，此值为对应的ios版本号，否则为`undefined`。\n             * @property {Object} [os]\n             */\n            os: (function( ua ) {\n                var ret = {},\n    \n                    // osx = !!ua.match( /\\(Macintosh\\; Intel / ),\n                    android = ua.match( /(?:Android);?[\\s\\/]+([\\d.]+)?/ ),\n                    ios = ua.match( /(?:iPad|iPod|iPhone).*OS\\s([\\d_]+)/ );\n    \n                // osx && (ret.osx = true);\n                android && (ret.android = parseFloat( android[ 1 ] ));\n                ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) ));\n    \n                return ret;\n            })( navigator.userAgent ),\n    \n            /**\n             * 实现类与类之间的继承。\n             * @method inherits\n             * @grammar Base.inherits( super ) => child\n             * @grammar Base.inherits( super, protos ) => child\n             * @grammar Base.inherits( super, protos, statics ) => child\n             * @param  {Class} super 父类\n             * @param  {Object | Function} [protos] 子类或者对象。如果对象中包含constructor，子类将是用此属性值。\n             * @param  {Function} [protos.constructor] 子类构造器，不指定的话将创建个临时的直接执行父类构造器的方法。\n             * @param  {Object} [statics] 静态属性或方法。\n             * @return {Class} 返回子类。\n             * @example\n             * function Person() {\n             *     console.log( 'Super' );\n             * }\n             * Person.prototype.hello = function() {\n             *     console.log( 'hello' );\n             * };\n             *\n             * var Manager = Base.inherits( Person, {\n             *     world: function() {\n             *         console.log( 'World' );\n             *     }\n             * });\n             *\n             * // 因为没有指定构造器，父类的构造器将会执行。\n             * var instance = new Manager();    // => Super\n             *\n             * // 继承子父类的方法\n             * instance.hello();    // => hello\n             * instance.world();    // => World\n             *\n             * // 子类的__super__属性指向父类\n             * console.log( Manager.__super__ === Person );    // => true\n             */\n            inherits: function( Super, protos, staticProtos ) {\n                var child;\n    \n                if ( typeof protos === 'function' ) {\n                    child = protos;\n                    protos = null;\n                } else if ( protos && protos.hasOwnProperty('constructor') ) {\n                    child = protos.constructor;\n                } else {\n                    child = function() {\n                        return Super.apply( this, arguments );\n                    };\n                }\n    \n                // 复制静态方法\n                $.extend( true, child, Super, staticProtos || {} );\n    \n                /* jshint camelcase: false */\n    \n                // 让子类的__super__属性指向父类。\n                child.__super__ = Super.prototype;\n    \n                // 构建原型，添加原型方法或属性。\n                // 暂时用Object.create实现。\n                child.prototype = createObject( Super.prototype );\n                protos && $.extend( true, child.prototype, protos );\n    \n                return child;\n            },\n    \n            /**\n             * 一个不做任何事情的方法。可以用来赋值给默认的callback.\n             * @method noop\n             */\n            noop: noop,\n    \n            /**\n             * 返回一个新的方法，此方法将已指定的`context`来执行。\n             * @grammar Base.bindFn( fn, context ) => Function\n             * @method bindFn\n             * @example\n             * var doSomething = function() {\n             *         console.log( this.name );\n             *     },\n             *     obj = {\n             *         name: 'Object Name'\n             *     },\n             *     aliasFn = Base.bind( doSomething, obj );\n             *\n             *  aliasFn();    // => Object Name\n             *\n             */\n            bindFn: bindFn,\n    \n            /**\n             * 引用Console.log如果存在的话，否则引用一个[空函数loop](#WebUploader:Base.log)。\n             * @grammar Base.log( args... ) => undefined\n             * @method log\n             */\n            log: (function() {\n                if ( window.console ) {\n                    return bindFn( console.log, console );\n                }\n                return noop;\n            })(),\n    \n            nextTick: (function() {\n    \n                return function( cb ) {\n                    setTimeout( cb, 1 );\n                };\n    \n                // @bug 当浏览器不在当前窗口时就停了。\n                // var next = window.requestAnimationFrame ||\n                //     window.webkitRequestAnimationFrame ||\n                //     window.mozRequestAnimationFrame ||\n                //     function( cb ) {\n                //         window.setTimeout( cb, 1000 / 60 );\n                //     };\n    \n                // // fix: Uncaught TypeError: Illegal invocation\n                // return bindFn( next, window );\n            })(),\n    \n            /**\n             * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。\n             * 将用来将非数组对象转化成数组对象。\n             * @grammar Base.slice( target, start[, end] ) => Array\n             * @method slice\n             * @example\n             * function doSomthing() {\n             *     var args = Base.slice( arguments, 1 );\n             *     console.log( args );\n             * }\n             *\n             * doSomthing( 'ignored', 'arg2', 'arg3' );    // => Array [\"arg2\", \"arg3\"]\n             */\n            slice: uncurryThis( [].slice ),\n    \n            /**\n             * 生成唯一的ID\n             * @method guid\n             * @grammar Base.guid() => String\n             * @grammar Base.guid( prefx ) => String\n             */\n            guid: (function() {\n                var counter = 0;\n    \n                return function( prefix ) {\n                    var guid = (+new Date()).toString( 32 ),\n                        i = 0;\n    \n                    for ( ; i < 5; i++ ) {\n                        guid += Math.floor( Math.random() * 65535 ).toString( 32 );\n                    }\n    \n                    return (prefix || 'wu_') + guid + (counter++).toString( 32 );\n                };\n            })(),\n    \n            /**\n             * 格式化文件大小, 输出成带单位的字符串\n             * @method formatSize\n             * @grammar Base.formatSize( size ) => String\n             * @grammar Base.formatSize( size, pointLength ) => String\n             * @grammar Base.formatSize( size, pointLength, units ) => String\n             * @param {Number} size 文件大小\n             * @param {Number} [pointLength=2] 精确到的小数点数。\n             * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节，到千字节，一直往上指定。如果单位数组里面只指定了到了K(千字节)，同时文件大小大于M, 此方法的输出将还是显示成多少K.\n             * @example\n             * console.log( Base.formatSize( 100 ) );    // => 100B\n             * console.log( Base.formatSize( 1024 ) );    // => 1.00K\n             * console.log( Base.formatSize( 1024, 0 ) );    // => 1K\n             * console.log( Base.formatSize( 1024 * 1024 ) );    // => 1.00M\n             * console.log( Base.formatSize( 1024 * 1024 * 1024 ) );    // => 1.00G\n             * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) );    // => 1024MB\n             */\n            formatSize: function( size, pointLength, units ) {\n                var unit;\n    \n                units = units || [ 'B', 'K', 'M', 'G', 'TB' ];\n    \n                while ( (unit = units.shift()) && size > 1024 ) {\n                    size = size / 1024;\n                }\n    \n                return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) +\n                        unit;\n            }\n        };\n    });\n    /**\n     * 事件处理类，可以独立使用，也可以扩展给对象使用。\n     * @fileOverview Mediator\n     */\n    define('mediator',[\n        'base'\n    ], function( Base ) {\n        var $ = Base.$,\n            slice = [].slice,\n            separator = /\\s+/,\n            protos;\n    \n        // 根据条件过滤出事件handlers.\n        function findHandlers( arr, name, callback, context ) {\n            return $.grep( arr, function( handler ) {\n                return handler &&\n                        (!name || handler.e === name) &&\n                        (!callback || handler.cb === callback ||\n                        handler.cb._cb === callback) &&\n                        (!context || handler.ctx === context);\n            });\n        }\n    \n        function eachEvent( events, callback, iterator ) {\n            // 不支持对象，只支持多个event用空格隔开\n            $.each( (events || '').split( separator ), function( _, key ) {\n                iterator( key, callback );\n            });\n        }\n    \n        function triggerHanders( events, args ) {\n            var stoped = false,\n                i = -1,\n                len = events.length,\n                handler;\n    \n            while ( ++i < len ) {\n                handler = events[ i ];\n    \n                if ( handler.cb.apply( handler.ctx2, args ) === false ) {\n                    stoped = true;\n                    break;\n                }\n            }\n    \n            return !stoped;\n        }\n    \n        protos = {\n    \n            /**\n             * 绑定事件。\n             *\n             * `callback`方法在执行时，arguments将会来源于trigger的时候携带的参数。如\n             * ```javascript\n             * var obj = {};\n             *\n             * // 使得obj有事件行为\n             * Mediator.installTo( obj );\n             *\n             * obj.on( 'testa', function( arg1, arg2 ) {\n             *     console.log( arg1, arg2 ); // => 'arg1', 'arg2'\n             * });\n             *\n             * obj.trigger( 'testa', 'arg1', 'arg2' );\n             * ```\n             *\n             * 如果`callback`中，某一个方法`return false`了，则后续的其他`callback`都不会被执行到。\n             * 切会影响到`trigger`方法的返回值，为`false`。\n             *\n             * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处，\n             * 就是第一个参数为`type`，记录当前是什么事件在触发。此类`callback`的优先级比脚低，会再正常`callback`执行完后触发。\n             * ```javascript\n             * obj.on( 'all', function( type, arg1, arg2 ) {\n             *     console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2'\n             * });\n             * ```\n             *\n             * @method on\n             * @grammar on( name, callback[, context] ) => self\n             * @param  {String}   name     事件名，支持多个事件用空格隔开\n             * @param  {Function} callback 事件处理器\n             * @param  {Object}   [context]  事件处理器的上下文。\n             * @return {self} 返回自身，方便链式\n             * @chainable\n             * @class Mediator\n             */\n            on: function( name, callback, context ) {\n                var me = this,\n                    set;\n    \n                if ( !callback ) {\n                    return this;\n                }\n    \n                set = this._events || (this._events = []);\n    \n                eachEvent( name, callback, function( name, callback ) {\n                    var handler = { e: name };\n    \n                    handler.cb = callback;\n                    handler.ctx = context;\n                    handler.ctx2 = context || me;\n                    handler.id = set.length;\n    \n                    set.push( handler );\n                });\n    \n                return this;\n            },\n    \n            /**\n             * 绑定事件，且当handler执行完后，自动解除绑定。\n             * @method once\n             * @grammar once( name, callback[, context] ) => self\n             * @param  {String}   name     事件名\n             * @param  {Function} callback 事件处理器\n             * @param  {Object}   [context]  事件处理器的上下文。\n             * @return {self} 返回自身，方便链式\n             * @chainable\n             */\n            once: function( name, callback, context ) {\n                var me = this;\n    \n                if ( !callback ) {\n                    return me;\n                }\n    \n                eachEvent( name, callback, function( name, callback ) {\n                    var once = function() {\n                            me.off( name, once );\n                            return callback.apply( context || me, arguments );\n                        };\n    \n                    once._cb = callback;\n                    me.on( name, once, context );\n                });\n    \n                return me;\n            },\n    \n            /**\n             * 解除事件绑定\n             * @method off\n             * @grammar off( [name[, callback[, context] ] ] ) => self\n             * @param  {String}   [name]     事件名\n             * @param  {Function} [callback] 事件处理器\n             * @param  {Object}   [context]  事件处理器的上下文。\n             * @return {self} 返回自身，方便链式\n             * @chainable\n             */\n            off: function( name, cb, ctx ) {\n                var events = this._events;\n    \n                if ( !events ) {\n                    return this;\n                }\n    \n                if ( !name && !cb && !ctx ) {\n                    this._events = [];\n                    return this;\n                }\n    \n                eachEvent( name, cb, function( name, cb ) {\n                    $.each( findHandlers( events, name, cb, ctx ), function() {\n                        delete events[ this.id ];\n                    });\n                });\n    \n                return this;\n            },\n    \n            /**\n             * 触发事件\n             * @method trigger\n             * @grammar trigger( name[, args...] ) => self\n             * @param  {String}   type     事件名\n             * @param  {*} [...] 任意参数\n             * @return {Boolean} 如果handler中return false了，则返回false, 否则返回true\n             */\n            trigger: function( type ) {\n                var args, events, allEvents;\n    \n                if ( !this._events || !type ) {\n                    return this;\n                }\n    \n                args = slice.call( arguments, 1 );\n                events = findHandlers( this._events, type );\n                allEvents = findHandlers( this._events, 'all' );\n    \n                return triggerHanders( events, args ) &&\n                        triggerHanders( allEvents, arguments );\n            }\n        };\n    \n        /**\n         * 中介者，它本身是个单例，但可以通过[installTo](#WebUploader:Mediator:installTo)方法，使任何对象具备事件行为。\n         * 主要目的是负责模块与模块之间的合作，降低耦合度。\n         *\n         * @class Mediator\n         */\n        return $.extend({\n    \n            /**\n             * 可以通过这个接口，使任何对象具备事件功能。\n             * @method installTo\n             * @param  {Object} obj 需要具备事件行为的对象。\n             * @return {Object} 返回obj.\n             */\n            installTo: function( obj ) {\n                return $.extend( obj, protos );\n            }\n    \n        }, protos );\n    });\n    /**\n     * @fileOverview Uploader上传类\n     */\n    define('uploader',[\n        'base',\n        'mediator'\n    ], function( Base, Mediator ) {\n    \n        var $ = Base.$;\n    \n        /**\n         * 上传入口类。\n         * @class Uploader\n         * @constructor\n         * @grammar new Uploader( opts ) => Uploader\n         * @example\n         * var uploader = WebUploader.Uploader({\n         *     swf: 'path_of_swf/Uploader.swf',\n         *\n         *     // 开起分片上传。\n         *     chunked: true\n         * });\n         */\n        function Uploader( opts ) {\n            this.options = $.extend( true, {}, Uploader.options, opts );\n            this._init( this.options );\n        }\n    \n        // default Options\n        // widgets中有相应扩展\n        Uploader.options = {};\n        Mediator.installTo( Uploader.prototype );\n    \n        // 批量添加纯命令式方法。\n        $.each({\n            upload: 'start-upload',\n            stop: 'stop-upload',\n            getFile: 'get-file',\n            getFiles: 'get-files',\n            addFile: 'add-file',\n            addFiles: 'add-file',\n            sort: 'sort-files',\n            removeFile: 'remove-file',\n            skipFile: 'skip-file',\n            retry: 'retry',\n            isInProgress: 'is-in-progress',\n            makeThumb: 'make-thumb',\n            getDimension: 'get-dimension',\n            addButton: 'add-btn',\n            getRuntimeType: 'get-runtime-type',\n            refresh: 'refresh',\n            disable: 'disable',\n            enable: 'enable',\n            reset: 'reset'\n        }, function( fn, command ) {\n            Uploader.prototype[ fn ] = function() {\n                return this.request( command, arguments );\n            };\n        });\n    \n        $.extend( Uploader.prototype, {\n            state: 'pending',\n    \n            _init: function( opts ) {\n                var me = this;\n    \n                me.request( 'init', opts, function() {\n                    me.state = 'ready';\n                    me.trigger('ready');\n                });\n            },\n    \n            /**\n             * 获取或者设置Uploader配置项。\n             * @method option\n             * @grammar option( key ) => *\n             * @grammar option( key, val ) => self\n             * @example\n             *\n             * // 初始状态图片上传前不会压缩\n             * var uploader = new WebUploader.Uploader({\n             *     resize: null;\n             * });\n             *\n             * // 修改后图片上传前，尝试将图片压缩到1600 * 1600\n             * uploader.options( 'resize', {\n             *     width: 1600,\n             *     height: 1600\n             * });\n             */\n            option: function( key, val ) {\n                var opts = this.options;\n    \n                // setter\n                if ( arguments.length > 1 ) {\n    \n                    if ( $.isPlainObject( val ) &&\n                            $.isPlainObject( opts[ key ] ) ) {\n                        $.extend( opts[ key ], val );\n                    } else {\n                        opts[ key ] = val;\n                    }\n    \n                } else {    // getter\n                    return key ? opts[ key ] : opts;\n                }\n            },\n    \n            /**\n             * 获取文件统计信息。返回一个包含一下信息的对象。\n             * * `successNum` 上传成功的文件数\n             * * `uploadFailNum` 上传失败的文件数\n             * * `cancelNum` 被删除的文件数\n             * * `invalidNum` 无效的文件数\n             * * `queueNum` 还在队列中的文件数\n             * @method getStats\n             * @grammar getStats() => Object\n             */\n            getStats: function() {\n                // return this._mgr.getStats.apply( this._mgr, arguments );\n                var stats = this.request('get-stats');\n    \n                return {\n                    successNum: stats.numOfSuccess,\n    \n                    // who care?\n                    // queueFailNum: 0,\n                    cancelNum: stats.numOfCancel,\n                    invalidNum: stats.numOfInvalid,\n                    uploadFailNum: stats.numOfUploadFailed,\n                    queueNum: stats.numOfQueue\n                };\n            },\n    \n            // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器\n            trigger: function( type/*, args...*/ ) {\n                var args = [].slice.call( arguments, 1 ),\n                    opts = this.options,\n                    name = 'on' + type.substring( 0, 1 ).toUpperCase() +\n                        type.substring( 1 );\n    \n                if (\n                        // 调用通过on方法注册的handler.\n                        Mediator.trigger.apply( this, arguments ) === false ||\n    \n                        // 调用opts.onEvent\n                        $.isFunction( opts[ name ] ) &&\n                        opts[ name ].apply( this, args ) === false ||\n    \n                        // 调用this.onEvent\n                        $.isFunction( this[ name ] ) &&\n                        this[ name ].apply( this, args ) === false ||\n    \n                        // 广播所有uploader的事件。\n                        Mediator.trigger.apply( Mediator,\n                        [ this, type ].concat( args ) ) === false ) {\n    \n                    return false;\n                }\n    \n                return true;\n            },\n    \n            // widgets/widget.js将补充此方法的详细文档。\n            request: Base.noop\n        });\n    \n        /**\n         * 创建Uploader实例，等同于new Uploader( opts );\n         * @method create\n         * @class Base\n         * @static\n         * @grammar Base.create( opts ) => Uploader\n         */\n        Base.create = Uploader.create = function( opts ) {\n            return new Uploader( opts );\n        };\n    \n        // 暴露Uploader，可以通过它来扩展业务逻辑。\n        Base.Uploader = Uploader;\n    \n        return Uploader;\n    });\n    /**\n     * @fileOverview Runtime管理器，负责Runtime的选择, 连接\n     */\n    define('runtime/runtime',[\n        'base',\n        'mediator'\n    ], function( Base, Mediator ) {\n    \n        var $ = Base.$,\n            factories = {},\n    \n            // 获取对象的第一个key\n            getFirstKey = function( obj ) {\n                for ( var key in obj ) {\n                    if ( obj.hasOwnProperty( key ) ) {\n                        return key;\n                    }\n                }\n                return null;\n            };\n    \n        // 接口类。\n        function Runtime( options ) {\n            this.options = $.extend({\n                container: document.body\n            }, options );\n            this.uid = Base.guid('rt_');\n        }\n    \n        $.extend( Runtime.prototype, {\n    \n            getContainer: function() {\n                var opts = this.options,\n                    parent, container;\n    \n                if ( this._container ) {\n                    return this._container;\n                }\n    \n                parent = $( opts.container || document.body );\n                container = $( document.createElement('div') );\n    \n                container.attr( 'id', 'rt_' + this.uid );\n                container.css({\n                    position: 'absolute',\n                    top: '0px',\n                    left: '0px',\n                    width: '1px',\n                    height: '1px',\n                    overflow: 'hidden'\n                });\n    \n                parent.append( container );\n                parent.addClass('webuploader-container');\n                this._container = container;\n                return container;\n            },\n    \n            init: Base.noop,\n            exec: Base.noop,\n    \n            destroy: function() {\n                if ( this._container ) {\n                    this._container.parentNode.removeChild( this.__container );\n                }\n    \n                this.off();\n            }\n        });\n    \n        Runtime.orders = 'html5,flash';\n    \n    \n        /**\n         * 添加Runtime实现。\n         * @param {String} type    类型\n         * @param {Runtime} factory 具体Runtime实现。\n         */\n        Runtime.addRuntime = function( type, factory ) {\n            factories[ type ] = factory;\n        };\n    \n        Runtime.hasRuntime = function( type ) {\n            return !!(type ? factories[ type ] : getFirstKey( factories ));\n        };\n    \n        Runtime.create = function( opts, orders ) {\n            var type, runtime;\n    \n            orders = orders || Runtime.orders;\n            $.each( orders.split( /\\s*,\\s*/g ), function() {\n                if ( factories[ this ] ) {\n                    type = this;\n                    return false;\n                }\n            });\n    \n            type = type || getFirstKey( factories );\n    \n            if ( !type ) {\n                throw new Error('Runtime Error');\n            }\n    \n            runtime = new factories[ type ]( opts );\n            return runtime;\n        };\n    \n        Mediator.installTo( Runtime.prototype );\n        return Runtime;\n    });\n    \n    /**\n     * @fileOverview Runtime管理器，负责Runtime的选择, 连接\n     */\n    define('runtime/client',[\n        'base',\n        'mediator',\n        'runtime/runtime'\n    ], function( Base, Mediator, Runtime ) {\n    \n        var cache;\n    \n        cache = (function() {\n            var obj = {};\n    \n            return {\n                add: function( runtime ) {\n                    obj[ runtime.uid ] = runtime;\n                },\n    \n                get: function( ruid, standalone ) {\n                    var i;\n    \n                    if ( ruid ) {\n                        return obj[ ruid ];\n                    }\n    \n                    for ( i in obj ) {\n                        // 有些类型不能重用，比如filepicker.\n                        if ( standalone && obj[ i ].__standalone ) {\n                            continue;\n                        }\n    \n                        return obj[ i ];\n                    }\n    \n                    return null;\n                },\n    \n                remove: function( runtime ) {\n                    delete obj[ runtime.uid ];\n                }\n            };\n        })();\n    \n        function RuntimeClient( component, standalone ) {\n            var deferred = Base.Deferred(),\n                runtime;\n    \n            this.uid = Base.guid('client_');\n    \n            // 允许runtime没有初始化之前，注册一些方法在初始化后执行。\n            this.runtimeReady = function( cb ) {\n                return deferred.done( cb );\n            };\n    \n            this.connectRuntime = function( opts, cb ) {\n    \n                // already connected.\n                if ( runtime ) {\n                    throw new Error('already connected!');\n                }\n    \n                deferred.done( cb );\n    \n                if ( typeof opts === 'string' && cache.get( opts ) ) {\n                    runtime = cache.get( opts );\n                }\n    \n                // 像filePicker只能独立存在，不能公用。\n                runtime = runtime || cache.get( null, standalone );\n    \n                // 需要创建\n                if ( !runtime ) {\n                    runtime = Runtime.create( opts, opts.runtimeOrder );\n                    runtime.__promise = deferred.promise();\n                    runtime.once( 'ready', deferred.resolve );\n                    runtime.init();\n                    cache.add( runtime );\n                    runtime.__client = 1;\n                } else {\n                    // 来自cache\n                    Base.$.extend( runtime.options, opts );\n                    runtime.__promise.then( deferred.resolve );\n                    runtime.__client++;\n                }\n    \n                standalone && (runtime.__standalone = standalone);\n                return runtime;\n            };\n    \n            this.getRuntime = function() {\n                return runtime;\n            };\n    \n            this.disconnectRuntime = function() {\n                if ( !runtime ) {\n                    return;\n                }\n    \n                runtime.__client--;\n    \n                if ( runtime.__client <= 0 ) {\n                    cache.remove( runtime );\n                    delete runtime.__promise;\n                    runtime.destroy();\n                }\n    \n                runtime = null;\n            };\n    \n            this.exec = function() {\n                if ( !runtime ) {\n                    return;\n                }\n    \n                var args = Base.slice( arguments );\n                component && args.unshift( component );\n    \n                return runtime.exec.apply( this, args );\n            };\n    \n            this.getRuid = function() {\n                return runtime && runtime.uid;\n            };\n    \n            this.destroy = (function( destroy ) {\n                return function() {\n                    destroy && destroy.apply( this, arguments );\n                    this.trigger('destroy');\n                    this.off();\n                    this.exec('destroy');\n                    this.disconnectRuntime();\n                };\n            })( this.destroy );\n        }\n    \n        Mediator.installTo( RuntimeClient.prototype );\n        return RuntimeClient;\n    });\n    /**\n     * @fileOverview 错误信息\n     */\n    define('lib/dnd',[\n        'base',\n        'mediator',\n        'runtime/client'\n    ], function( Base, Mediator, RuntimeClent ) {\n    \n        var $ = Base.$;\n    \n        function DragAndDrop( opts ) {\n            opts = this.options = $.extend({}, DragAndDrop.options, opts );\n    \n            opts.container = $( opts.container );\n    \n            if ( !opts.container.length ) {\n                return;\n            }\n    \n            RuntimeClent.call( this, 'DragAndDrop' );\n        }\n    \n        DragAndDrop.options = {\n            accept: null,\n            disableGlobalDnd: false\n        };\n    \n        Base.inherits( RuntimeClent, {\n            constructor: DragAndDrop,\n    \n            init: function() {\n                var me = this;\n    \n                me.connectRuntime( me.options, function() {\n                    me.exec('init');\n                    me.trigger('ready');\n                });\n            },\n    \n            destroy: function() {\n                this.disconnectRuntime();\n            }\n        });\n    \n        Mediator.installTo( DragAndDrop.prototype );\n    \n        return DragAndDrop;\n    });\n    /**\n     * @fileOverview 组件基类。\n     */\n    define('widgets/widget',[\n        'base',\n        'uploader'\n    ], function( Base, Uploader ) {\n    \n        var $ = Base.$,\n            _init = Uploader.prototype._init,\n            IGNORE = {},\n            widgetClass = [];\n    \n        function isArrayLike( obj ) {\n            if ( !obj ) {\n                return false;\n            }\n    \n            var length = obj.length,\n                type = $.type( obj );\n    \n            if ( obj.nodeType === 1 && length ) {\n                return true;\n            }\n    \n            return type === 'array' || type !== 'function' && type !== 'string' &&\n                    (length === 0 || typeof length === 'number' && length > 0 &&\n                    (length - 1) in obj);\n        }\n    \n        function Widget( uploader ) {\n            this.owner = uploader;\n            this.options = uploader.options;\n        }\n    \n        $.extend( Widget.prototype, {\n    \n            init: Base.noop,\n    \n            // 类Backbone的事件监听声明，监听uploader实例上的事件\n            // widget直接无法监听事件，事件只能通过uploader来传递\n            invoke: function( apiName, args ) {\n    \n                /*\n                    {\n                        'make-thumb': 'makeThumb'\n                    }\n                 */\n                var map = this.responseMap;\n    \n                // 如果无API响应声明则忽略\n                if ( !map || !(apiName in map) || !(map[ apiName ] in this) ||\n                        !$.isFunction( this[ map[ apiName ] ] ) ) {\n    \n                    return IGNORE;\n                }\n    \n                return this[ map[ apiName ] ].apply( this, args );\n    \n            },\n    \n            /**\n             * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。\n             * @method request\n             * @grammar request( command, args ) => * | Promise\n             * @grammar request( command, args, callback ) => Promise\n             * @for  Uploader\n             */\n            request: function() {\n                return this.owner.request.apply( this.owner, arguments );\n            }\n        });\n    \n        // 扩展Uploader.\n        $.extend( Uploader.prototype, {\n    \n            // 覆写_init用来初始化widgets\n            _init: function() {\n                var me = this,\n                    widgets = me._widgets = [];\n    \n                $.each( widgetClass, function( _, klass ) {\n                    widgets.push( new klass( me ) );\n                });\n    \n                return _init.apply( me, arguments );\n            },\n    \n            request: function( apiName, args, callback ) {\n                var i = 0,\n                    widgets = this._widgets,\n                    len = widgets.length,\n                    rlts = [],\n                    dfds = [],\n                    widget, rlt, promise, key;\n    \n                args = isArrayLike( args ) ? args : [ args ];\n    \n                for ( ; i < len; i++ ) {\n                    widget = widgets[ i ];\n                    rlt = widget.invoke( apiName, args );\n    \n                    if ( rlt !== IGNORE ) {\n    \n                        // Deferred对象\n                        if ( Base.isPromise( rlt ) ) {\n                            dfds.push( rlt );\n                        } else {\n                            rlts.push( rlt );\n                        }\n                    }\n                }\n    \n                // 如果有callback，则用异步方式。\n                if ( callback || dfds.length ) {\n                    promise = Base.when.apply( Base, dfds );\n                    key = promise.pipe ? 'pipe' : 'then';\n    \n                    // 很重要不能删除。删除了会死循环。\n                    // 保证执行顺序。让callback总是在下一个tick中执行。\n                    return promise[ key ](function() {\n                                var deferred = Base.Deferred(),\n                                    args = arguments;\n    \n                                setTimeout(function() {\n                                    deferred.resolve.apply( deferred, args );\n                                }, 1 );\n    \n                                return deferred.promise();\n                            })[ key ]( callback || Base.noop );\n                } else {\n                    return rlts[ 0 ];\n                }\n            }\n        });\n    \n        /**\n         * 添加组件\n         * @param  {object} widgetProto 组件原型，构造函数通过constructor属性定义\n         * @param  {object} responseMap API名称与函数实现的映射\n         * @example\n         *     Uploader.register( {\n         *         init: function( options ) {},\n         *         makeThumb: function() {}\n         *     }, {\n         *         'make-thumb': 'makeThumb'\n         *     } );\n         */\n        Uploader.register = Widget.register = function( responseMap, widgetProto ) {\n            var map = { init: 'init' },\n                klass;\n    \n            if ( arguments.length === 1 ) {\n                widgetProto = responseMap;\n                widgetProto.responseMap = map;\n            } else {\n                widgetProto.responseMap = $.extend( map, responseMap );\n            }\n    \n            klass = Base.inherits( Widget, widgetProto );\n            widgetClass.push( klass );\n    \n            return klass;\n        };\n    \n        return Widget;\n    });\n    /**\n     * @fileOverview DragAndDrop Widget。\n     */\n    define('widgets/filednd',[\n        'base',\n        'uploader',\n        'lib/dnd',\n        'widgets/widget'\n    ], function( Base, Uploader, Dnd ) {\n        var $ = Base.$;\n    \n        Uploader.options.dnd = '';\n    \n        /**\n         * @property {Selector} [dnd=undefined]  指定Drag And Drop拖拽的容器，如果不指定，则不启动。\n         * @namespace options\n         * @for Uploader\n         */\n    \n        /**\n         * @event dndAccept\n         * @param {DataTransferItemList} items DataTransferItem\n         * @description 阻止此事件可以拒绝某些类型的文件拖入进来。目前只有 chrome 提供这样的 API，且只能通过 mime-type 验证。\n         * @for  Uploader\n         */\n        return Uploader.register({\n            init: function( opts ) {\n    \n                if ( !opts.dnd ||\n                        this.request('predict-runtime-type') !== 'html5' ) {\n                    return;\n                }\n    \n                var me = this,\n                    deferred = Base.Deferred(),\n                    options = $.extend({}, {\n                        disableGlobalDnd: opts.disableGlobalDnd,\n                        container: opts.dnd,\n                        accept: opts.accept\n                    }),\n                    dnd;\n    \n                dnd = new Dnd( options );\n    \n                dnd.once( 'ready', deferred.resolve );\n                dnd.on( 'drop', function( files ) {\n                    me.request( 'add-file', [ files ]);\n                });\n    \n                // 检测文件是否全部允许添加。\n                dnd.on( 'accept', function( items ) {\n                    return me.owner.trigger( 'dndAccept', items );\n                });\n    \n                dnd.init();\n    \n                return deferred.promise();\n            }\n        });\n    });\n    \n    /**\n     * @fileOverview 错误信息\n     */\n    define('lib/filepaste',[\n        'base',\n        'mediator',\n        'runtime/client'\n    ], function( Base, Mediator, RuntimeClent ) {\n    \n        var $ = Base.$;\n    \n        function FilePaste( opts ) {\n            opts = this.options = $.extend({}, opts );\n            opts.container = $( opts.container || document.body );\n            RuntimeClent.call( this, 'FilePaste' );\n        }\n    \n        Base.inherits( RuntimeClent, {\n            constructor: FilePaste,\n    \n            init: function() {\n                var me = this;\n    \n                me.connectRuntime( me.options, function() {\n                    me.exec('init');\n                    me.trigger('ready');\n                });\n            },\n    \n            destroy: function() {\n                this.exec('destroy');\n                this.disconnectRuntime();\n                this.off();\n            }\n        });\n    \n        Mediator.installTo( FilePaste.prototype );\n    \n        return FilePaste;\n    });\n    /**\n     * @fileOverview 组件基类。\n     */\n    define('widgets/filepaste',[\n        'base',\n        'uploader',\n        'lib/filepaste',\n        'widgets/widget'\n    ], function( Base, Uploader, FilePaste ) {\n        var $ = Base.$;\n    \n        /**\n         * @property {Selector} [paste=undefined]  指定监听paste事件的容器，如果不指定，不启用此功能。此功能为通过粘贴来添加截屏的图片。建议设置为`document.body`.\n         * @namespace options\n         * @for Uploader\n         */\n        return Uploader.register({\n            init: function( opts ) {\n    \n                if ( !opts.paste ||\n                        this.request('predict-runtime-type') !== 'html5' ) {\n                    return;\n                }\n    \n                var me = this,\n                    deferred = Base.Deferred(),\n                    options = $.extend({}, {\n                        container: opts.paste,\n                        accept: opts.accept\n                    }),\n                    paste;\n    \n                paste = new FilePaste( options );\n    \n                paste.once( 'ready', deferred.resolve );\n                paste.on( 'paste', function( files ) {\n                    me.owner.request( 'add-file', [ files ]);\n                });\n                paste.init();\n    \n                return deferred.promise();\n            }\n        });\n    });\n    /**\n     * @fileOverview Blob\n     */\n    define('lib/blob',[\n        'base',\n        'runtime/client'\n    ], function( Base, RuntimeClient ) {\n    \n        function Blob( ruid, source ) {\n            var me = this;\n    \n            me.source = source;\n            me.ruid = ruid;\n    \n            RuntimeClient.call( me, 'Blob' );\n    \n            this.uid = source.uid || this.uid;\n            this.type = source.type || '';\n            this.size = source.size || 0;\n    \n            if ( ruid ) {\n                me.connectRuntime( ruid );\n            }\n        }\n    \n        Base.inherits( RuntimeClient, {\n            constructor: Blob,\n    \n            slice: function( start, end ) {\n                return this.exec( 'slice', start, end );\n            },\n    \n            getSource: function() {\n                return this.source;\n            }\n        });\n    \n        return Blob;\n    });\n    /**\n     * 为了统一化Flash的File和HTML5的File而存在。\n     * 以至于要调用Flash里面的File，也可以像调用HTML5版本的File一下。\n     * @fileOverview File\n     */\n    define('lib/file',[\n        'base',\n        'lib/blob'\n    ], function( Base, Blob ) {\n    \n        var uid = 1,\n            rExt = /\\.([^.]+)$/;\n    \n        function File( ruid, file ) {\n            var ext;\n    \n            Blob.apply( this, arguments );\n            this.name = file.name || ('untitled' + uid++);\n            ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : '';\n    \n            // todo 支持其他类型文件的转换。\n    \n            // 如果有mimetype, 但是文件名里面没有找出后缀规律\n            if ( !ext && this.type ) {\n                ext = /\\/(jpg|jpeg|png|gif|bmp)$/i.exec( this.type ) ?\n                        RegExp.$1.toLowerCase() : '';\n                this.name += '.' + ext;\n            }\n    \n            // 如果没有指定mimetype, 但是知道文件后缀。\n            if ( !this.type &&  ~'jpg,jpeg,png,gif,bmp'.indexOf( ext ) ) {\n                this.type = 'image/' + (ext === 'jpg' ? 'jpeg' : ext);\n            }\n    \n            this.ext = ext;\n            this.lastModifiedDate = file.lastModifiedDate ||\n                    (new Date()).toLocaleString();\n        }\n    \n        return Base.inherits( Blob, File );\n    });\n    \n    /**\n     * @fileOverview 错误信息\n     */\n    define('lib/filepicker',[\n        'base',\n        'runtime/client',\n        'lib/file'\n    ], function( Base, RuntimeClent, File ) {\n    \n        var $ = Base.$;\n    \n        function FilePicker( opts ) {\n            opts = this.options = $.extend({}, FilePicker.options, opts );\n    \n            opts.container = $( opts.id );\n    \n            if ( !opts.container.length ) {\n                throw new Error('按钮指定错误');\n            }\n    \n            opts.innerHTML = opts.innerHTML || opts.label ||\n                    opts.container.html() || '';\n    \n            opts.button = $( opts.button || document.createElement('div') );\n            opts.button.html( opts.innerHTML );\n            opts.container.html( opts.button );\n    \n            RuntimeClent.call( this, 'FilePicker', true );\n        }\n    \n        FilePicker.options = {\n            button: null,\n            container: null,\n            label: null,\n            innerHTML: null,\n            multiple: true,\n            accept: null,\n            name: 'file'\n        };\n    \n        Base.inherits( RuntimeClent, {\n            constructor: FilePicker,\n    \n            init: function() {\n                var me = this,\n                    opts = me.options,\n                    button = opts.button;\n    \n                button.addClass('webuploader-pick');\n    \n                me.on( 'all', function( type ) {\n                    var files;\n    \n                    switch ( type ) {\n                        case 'mouseenter':\n                            button.addClass('webuploader-pick-hover');\n                            break;\n    \n                        case 'mouseleave':\n                            button.removeClass('webuploader-pick-hover');\n                            break;\n    \n                        case 'change':\n                            files = me.exec('getFiles');\n                            me.trigger( 'select', $.map( files, function( file ) {\n                                file = new File( me.getRuid(), file );\n    \n                                // 记录来源。\n                                file._refer = opts.container;\n                                return file;\n                            }), opts.container );\n                            break;\n                    }\n                });\n    \n                me.connectRuntime( opts, function() {\n                    me.refresh();\n                    me.exec( 'init', opts );\n                    me.trigger('ready');\n                });\n    \n                $( window ).on( 'resize', function() {\n                    me.refresh();\n                });\n            },\n    \n            refresh: function() {\n                var shimContainer = this.getRuntime().getContainer(),\n                    button = this.options.button,\n                    width = button.outerWidth ?\n                            button.outerWidth() : button.width(),\n    \n                    height = button.outerHeight ?\n                            button.outerHeight() : button.height(),\n    \n                    pos = button.offset();\n    \n                width && height && shimContainer.css({\n                    bottom: 'auto',\n                    right: 'auto',\n                    width: width + 'px',\n                    height: height + 'px'\n                }).offset( pos );\n            },\n    \n            enable: function() {\n                var btn = this.options.button;\n    \n                btn.removeClass('webuploader-pick-disable');\n                this.refresh();\n            },\n    \n            disable: function() {\n                var btn = this.options.button;\n    \n                this.getRuntime().getContainer().css({\n                    top: '-99999px'\n                });\n    \n                btn.addClass('webuploader-pick-disable');\n            },\n    \n            destroy: function() {\n                if ( this.runtime ) {\n                    this.exec('destroy');\n                    this.disconnectRuntime();\n                }\n            }\n        });\n    \n        return FilePicker;\n    });\n    \n    /**\n     * @fileOverview 文件选择相关\n     */\n    define('widgets/filepicker',[\n        'base',\n        'uploader',\n        'lib/filepicker',\n        'widgets/widget'\n    ], function( Base, Uploader, FilePicker ) {\n        var $ = Base.$;\n    \n        $.extend( Uploader.options, {\n    \n            /**\n             * @property {Selector | Object} [pick=undefined]\n             * @namespace options\n             * @for Uploader\n             * @description 指定选择文件的按钮容器，不指定则不创建按钮。\n             *\n             * * `id` {Seletor} 指定选择文件的按钮容器，不指定则不创建按钮。\n             * * `label` {String} 请采用 `innerHTML` 代替\n             * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。\n             * * `multiple` {Boolean} 是否开起同时选择多个文件能力。\n             */\n            pick: null,\n    \n            /**\n             * @property {Arroy} [accept=null]\n             * @namespace options\n             * @for Uploader\n             * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表，所以这里需要分开指定。\n             *\n             * * `title` {String} 文字描述\n             * * `extensions` {String} 允许的文件后缀，不带点，多个用逗号分割。\n             * * `mimeTypes` {String} 多个用逗号分割。\n             *\n             * 如：\n             *\n             * ```\n             * {\n             *     title: 'Images',\n             *     extensions: 'gif,jpg,jpeg,bmp,png',\n             *     mimeTypes: 'image/*'\n             * }\n             * ```\n             */\n            accept: null/*{\n                title: 'Images',\n                extensions: 'gif,jpg,jpeg,bmp,png',\n                mimeTypes: 'image/*'\n            }*/\n        });\n    \n        return Uploader.register({\n            'add-btn': 'addButton',\n            refresh: 'refresh',\n            disable: 'disable',\n            enable: 'enable'\n        }, {\n    \n            init: function( opts ) {\n                this.pickers = [];\n                return opts.pick && this.addButton( opts.pick );\n            },\n    \n            refresh: function() {\n                $.each( this.pickers, function() {\n                    this.refresh();\n                });\n            },\n    \n            /**\n             * @method addButton\n             * @for Uploader\n             * @grammar addButton( pick ) => Promise\n             * @description\n             * 添加文件选择按钮，如果一个按钮不够，需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。\n             * @example\n             * uploader.addButton({\n             *     id: '#btnContainer',\n             *     innerHTML: '选择文件'\n             * });\n             */\n            addButton: function( pick ) {\n                var me = this,\n                    opts = me.options,\n                    accept = opts.accept,\n                    options, picker, deferred;\n    \n                if ( !pick ) {\n                    return;\n                }\n    \n                deferred = Base.Deferred();\n                $.isPlainObject( pick ) || (pick = {\n                    id: pick\n                });\n    \n                options = $.extend({}, pick, {\n                    accept: $.isPlainObject( accept ) ? [ accept ] : accept,\n                    swf: opts.swf,\n                    runtimeOrder: opts.runtimeOrder\n                });\n    \n                picker = new FilePicker( options );\n    \n                picker.once( 'ready', deferred.resolve );\n                picker.on( 'select', function( files ) {\n                    me.owner.request( 'add-file', [ files ]);\n                });\n                picker.init();\n    \n                this.pickers.push( picker );\n    \n                return deferred.promise();\n            },\n    \n            disable: function() {\n                $.each( this.pickers, function() {\n                    this.disable();\n                });\n            },\n    \n            enable: function() {\n                $.each( this.pickers, function() {\n                    this.enable();\n                });\n            }\n        });\n    });\n    /**\n     * @fileOverview Image\n     */\n    define('lib/image',[\n        'base',\n        'runtime/client',\n        'lib/blob'\n    ], function( Base, RuntimeClient, Blob ) {\n        var $ = Base.$;\n    \n        // 构造器。\n        function Image( opts ) {\n            this.options = $.extend({}, Image.options, opts );\n            RuntimeClient.call( this, 'Image' );\n    \n            this.on( 'load', function() {\n                this._info = this.exec('info');\n                this._meta = this.exec('meta');\n            });\n        }\n    \n        // 默认选项。\n        Image.options = {\n    \n            // 默认的图片处理质量\n            quality: 90,\n    \n            // 是否裁剪\n            crop: false,\n    \n            // 是否保留头部信息\n            preserveHeaders: true,\n    \n            // 是否允许放大。\n            allowMagnify: true\n        };\n    \n        // 继承RuntimeClient.\n        Base.inherits( RuntimeClient, {\n            constructor: Image,\n    \n            info: function( val ) {\n    \n                // setter\n                if ( val ) {\n                    this._info = val;\n                    return this;\n                }\n    \n                // getter\n                return this._info;\n            },\n    \n            meta: function( val ) {\n    \n                // setter\n                if ( val ) {\n                    this._meta = val;\n                    return this;\n                }\n    \n                // getter\n                return this._meta;\n            },\n    \n            loadFromBlob: function( blob ) {\n                var me = this,\n                    ruid = blob.getRuid();\n    \n                this.connectRuntime( ruid, function() {\n                    me.exec( 'init', me.options );\n                    me.exec( 'loadFromBlob', blob );\n                });\n            },\n    \n            resize: function() {\n                var args = Base.slice( arguments );\n                return this.exec.apply( this, [ 'resize' ].concat( args ) );\n            },\n    \n            getAsDataUrl: function( type ) {\n                return this.exec( 'getAsDataUrl', type );\n            },\n    \n            getAsBlob: function( type ) {\n                var blob = this.exec( 'getAsBlob', type );\n    \n                return new Blob( this.getRuid(), blob );\n            }\n        });\n    \n        return Image;\n    });\n    /**\n     * @fileOverview 图片操作, 负责预览图片和上传前压缩图片\n     */\n    define('widgets/image',[\n        'base',\n        'uploader',\n        'lib/image',\n        'widgets/widget'\n    ], function( Base, Uploader, Image ) {\n    \n        var $ = Base.$,\n            throttle;\n    \n        // 根据要处理的文件大小来节流，一次不能处理太多，会卡。\n        throttle = (function( max ) {\n            var occupied = 0,\n                waiting = [],\n                tick = function() {\n                    var item;\n    \n                    while ( waiting.length && occupied < max ) {\n                        item = waiting.shift();\n                        occupied += item[ 0 ];\n                        item[ 1 ]();\n                    }\n                };\n    \n            return function( emiter, size, cb ) {\n                waiting.push([ size, cb ]);\n                emiter.once( 'destroy', function() {\n                    occupied -= size;\n                    setTimeout( tick, 1 );\n                });\n                setTimeout( tick, 1 );\n            };\n        })( 5 * 1024 * 1024 );\n    \n        $.extend( Uploader.options, {\n    \n            /**\n             * @property {Object} [thumb]\n             * @namespace options\n             * @for Uploader\n             * @description 配置生成缩略图的选项。\n             *\n             * 默认为：\n             *\n             * ```javascript\n             * {\n             *     width: 110,\n             *     height: 110,\n             *\n             *     // 图片质量，只有type为`image/jpeg`的时候才有效。\n             *     quality: 70,\n             *\n             *     // 是否允许放大，如果想要生成小图的时候不失真，此选项应该设置为false.\n             *     allowMagnify: true,\n             *\n             *     // 是否允许裁剪。\n             *     crop: true,\n             *\n             *     // 是否保留头部meta信息。\n             *     preserveHeaders: false,\n             *\n             *     // 为空的话则保留原有图片格式。\n             *     // 否则强制转换成指定的类型。\n             *     type: 'image/jpeg'\n             * }\n             * ```\n             */\n            thumb: {\n                width: 110,\n                height: 110,\n                quality: 70,\n                allowMagnify: true,\n                crop: true,\n                preserveHeaders: false,\n    \n                // 为空的话则保留原有图片格式。\n                // 否则强制转换成指定的类型。\n                // IE 8下面 base64 大小不能超过 32K 否则预览失败，而非 jpeg 编码的图片很可\n                // 能会超过 32k, 所以这里设置成预览的时候都是 image/jpeg\n                type: 'image/jpeg'\n            },\n    \n            /**\n             * @property {Object} [compress]\n             * @namespace options\n             * @for Uploader\n             * @description 配置压缩的图片的选项。如果此选项为`false`, 则图片在上传前不进行压缩。\n             *\n             * 默认为：\n             *\n             * ```javascript\n             * {\n             *     width: 1600,\n             *     height: 1600,\n             *\n             *     // 图片质量，只有type为`image/jpeg`的时候才有效。\n             *     quality: 90,\n             *\n             *     // 是否允许放大，如果想要生成小图的时候不失真，此选项应该设置为false.\n             *     allowMagnify: false,\n             *\n             *     // 是否允许裁剪。\n             *     crop: false,\n             *\n             *     // 是否保留头部meta信息。\n             *     preserveHeaders: true\n             * }\n             * ```\n             */\n            compress: {\n                width: 1600,\n                height: 1600,\n                quality: 90,\n                allowMagnify: false,\n                crop: false,\n                preserveHeaders: true\n            }\n        });\n    \n        return Uploader.register({\n            'make-thumb': 'makeThumb',\n            'before-send-file': 'compressImage'\n        }, {\n    \n    \n            /**\n             * 生成缩略图，此过程为异步，所以需要传入`callback`。\n             * 通常情况在图片加入队里后调用此方法来生成预览图以增强交互效果。\n             *\n             * `callback`中可以接收到两个参数。\n             * * 第一个为error，如果生成缩略图有错误，此error将为真。\n             * * 第二个为ret, 缩略图的Data URL值。\n             *\n             * **注意**\n             * Date URL在IE6/7中不支持，所以不用调用此方法了，直接显示一张暂不支持预览图片好了。\n             *\n             *\n             * @method makeThumb\n             * @grammar makeThumb( file, callback ) => undefined\n             * @grammar makeThumb( file, callback, width, height ) => undefined\n             * @for Uploader\n             * @example\n             *\n             * uploader.on( 'fileQueued', function( file ) {\n             *     var $li = ...;\n             *\n             *     uploader.makeThumb( file, function( error, ret ) {\n             *         if ( error ) {\n             *             $li.text('预览错误');\n             *         } else {\n             *             $li.append('<img alt=\"\" src=\"' + ret + '\" />');\n             *         }\n             *     });\n             *\n             * });\n             */\n            makeThumb: function( file, cb, width, height ) {\n                var opts, image;\n    \n                file = this.request( 'get-file', file );\n    \n                // 只预览图片格式。\n                if ( !file.type.match( /^image/ ) ) {\n                    cb( true );\n                    return;\n                }\n    \n                opts = $.extend({}, this.options.thumb );\n    \n                // 如果传入的是object.\n                if ( $.isPlainObject( width ) ) {\n                    opts = $.extend( opts, width );\n                    width = null;\n                }\n    \n                width = width || opts.width;\n                height = height || opts.height;\n    \n                image = new Image( opts );\n    \n                image.once( 'load', function() {\n                    file._info = file._info || image.info();\n                    file._meta = file._meta || image.meta();\n                    image.resize( width, height );\n                });\n    \n                image.once( 'complete', function() {\n                    cb( false, image.getAsDataUrl( opts.type ) );\n                    image.destroy();\n                });\n    \n                image.once( 'error', function() {\n                    cb( true );\n                    image.destroy();\n                });\n    \n                throttle( image, file.source.size, function() {\n                    file._info && image.info( file._info );\n                    file._meta && image.meta( file._meta );\n                    image.loadFromBlob( file.source );\n                });\n            },\n    \n            compressImage: function( file ) {\n                var opts = this.options.compress || this.options.resize,\n                    compressSize = opts && opts.compressSize || 300 * 1024,\n                    image, deferred;\n    \n                file = this.request( 'get-file', file );\n    \n                // 只预览图片格式。\n                if ( !opts || !~'image/jpeg,image/jpg'.indexOf( file.type ) ||\n                        file.size < compressSize ||\n                        file._compressed ) {\n                    return;\n                }\n    \n                opts = $.extend({}, opts );\n                deferred = Base.Deferred();\n    \n                image = new Image( opts );\n    \n                deferred.always(function() {\n                    image.destroy();\n                    image = null;\n                });\n                image.once( 'error', deferred.reject );\n                image.once( 'load', function() {\n                    file._info = file._info || image.info();\n                    file._meta = file._meta || image.meta();\n                    image.resize( opts.width, opts.height );\n                });\n    \n                image.once( 'complete', function() {\n                    var blob, size;\n    \n                    // 移动端 UC / qq 浏览器的无图模式下\n                    // ctx.getImageData 处理大图的时候会报 Exception\n                    // INDEX_SIZE_ERR: DOM Exception 1\n                    try {\n                        blob = image.getAsBlob( opts.type );\n    \n                        size = file.size;\n    \n                        // 如果压缩后，比原来还大则不用压缩后的。\n                        if ( blob.size < size ) {\n                            // file.source.destroy && file.source.destroy();\n                            file.source = blob;\n                            file.size = blob.size;\n    \n                            file.trigger( 'resize', blob.size, size );\n                        }\n    \n                        // 标记，避免重复压缩。\n                        file._compressed = true;\n                        deferred.resolve();\n                    } catch ( e ) {\n                        // 出错了直接继续，让其上传原始图片\n                        deferred.resolve();\n                    }\n                });\n    \n                file._info && image.info( file._info );\n                file._meta && image.meta( file._meta );\n    \n                image.loadFromBlob( file.source );\n                return deferred.promise();\n            }\n        });\n    });\n    /**\n     * @fileOverview 文件属性封装\n     */\n    define('file',[\n        'base',\n        'mediator'\n    ], function( Base, Mediator ) {\n    \n        var $ = Base.$,\n            idPrefix = 'WU_FILE_',\n            idSuffix = 0,\n            rExt = /\\.([^.]+)$/,\n            statusMap = {};\n    \n        function gid() {\n            return idPrefix + idSuffix++;\n        }\n    \n        /**\n         * 文件类\n         * @class File\n         * @constructor 构造函数\n         * @grammar new File( source ) => File\n         * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。\n         */\n        function WUFile( source ) {\n    \n            /**\n             * 文件名，包括扩展名（后缀）\n             * @property name\n             * @type {string}\n             */\n            this.name = source.name || 'Untitled';\n    \n            /**\n             * 文件体积（字节）\n             * @property size\n             * @type {uint}\n             * @default 0\n             */\n            this.size = source.size || 0;\n    \n            /**\n             * 文件MIMETYPE类型，与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny)\n             * @property type\n             * @type {string}\n             * @default 'application'\n             */\n            this.type = source.type || 'application';\n    \n            /**\n             * 文件最后修改日期\n             * @property lastModifiedDate\n             * @type {int}\n             * @default 当前时间戳\n             */\n            this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1);\n    \n            /**\n             * 文件ID，每个对象具有唯一ID，与文件名无关\n             * @property id\n             * @type {string}\n             */\n            this.id = gid();\n    \n            /**\n             * 文件扩展名，通过文件名获取，例如test.png的扩展名为png\n             * @property ext\n             * @type {string}\n             */\n            this.ext = rExt.exec( this.name ) ? RegExp.$1 : '';\n    \n    \n            /**\n             * 状态文字说明。在不同的status语境下有不同的用途。\n             * @property statusText\n             * @type {string}\n             */\n            this.statusText = '';\n    \n            // 存储文件状态，防止通过属性直接修改\n            statusMap[ this.id ] = WUFile.Status.INITED;\n    \n            this.source = source;\n            this.loaded = 0;\n    \n            this.on( 'error', function( msg ) {\n                this.setStatus( WUFile.Status.ERROR, msg );\n            });\n        }\n    \n        $.extend( WUFile.prototype, {\n    \n            /**\n             * 设置状态，状态变化时会触发`change`事件。\n             * @method setStatus\n             * @grammar setStatus( status[, statusText] );\n             * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status)\n             * @param {String} [statusText=''] 状态说明，常在error时使用，用http, abort,server等来标记是由于什么原因导致文件错误。\n             */\n            setStatus: function( status, text ) {\n    \n                var prevStatus = statusMap[ this.id ];\n    \n                typeof text !== 'undefined' && (this.statusText = text);\n    \n                if ( status !== prevStatus ) {\n                    statusMap[ this.id ] = status;\n                    /**\n                     * 文件状态变化\n                     * @event statuschange\n                     */\n                    this.trigger( 'statuschange', status, prevStatus );\n                }\n    \n            },\n    \n            /**\n             * 获取文件状态\n             * @return {File.Status}\n             * @example\n                     文件状态具体包括以下几种类型：\n                     {\n                         // 初始化\n                        INITED:     0,\n                        // 已入队列\n                        QUEUED:     1,\n                        // 正在上传\n                        PROGRESS:     2,\n                        // 上传出错\n                        ERROR:         3,\n                        // 上传成功\n                        COMPLETE:     4,\n                        // 上传取消\n                        CANCELLED:     5\n                    }\n             */\n            getStatus: function() {\n                return statusMap[ this.id ];\n            },\n    \n            /**\n             * 获取文件原始信息。\n             * @return {*}\n             */\n            getSource: function() {\n                return this.source;\n            },\n    \n            destory: function() {\n                delete statusMap[ this.id ];\n            }\n        });\n    \n        Mediator.installTo( WUFile.prototype );\n    \n        /**\n         * 文件状态值，具体包括以下几种类型：\n         * * `inited` 初始状态\n         * * `queued` 已经进入队列, 等待上传\n         * * `progress` 上传中\n         * * `complete` 上传完成。\n         * * `error` 上传出错，可重试\n         * * `interrupt` 上传中断，可续传。\n         * * `invalid` 文件不合格，不能重试上传。会自动从队列中移除。\n         * * `cancelled` 文件被移除。\n         * @property {Object} Status\n         * @namespace File\n         * @class File\n         * @static\n         */\n        WUFile.Status = {\n            INITED:     'inited',    // 初始状态\n            QUEUED:     'queued',    // 已经进入队列, 等待上传\n            PROGRESS:   'progress',    // 上传中\n            ERROR:      'error',    // 上传出错，可重试\n            COMPLETE:   'complete',    // 上传完成。\n            CANCELLED:  'cancelled',    // 上传取消。\n            INTERRUPT:  'interrupt',    // 上传中断，可续传。\n            INVALID:    'invalid'    // 文件不合格，不能重试上传。\n        };\n    \n        return WUFile;\n    });\n    \n    /**\n     * @fileOverview 文件队列\n     */\n    define('queue',[\n        'base',\n        'mediator',\n        'file'\n    ], function( Base, Mediator, WUFile ) {\n    \n        var $ = Base.$,\n            STATUS = WUFile.Status;\n    \n        /**\n         * 文件队列, 用来存储各个状态中的文件。\n         * @class Queue\n         * @extends Mediator\n         */\n        function Queue() {\n    \n            /**\n             * 统计文件数。\n             * * `numOfQueue` 队列中的文件数。\n             * * `numOfSuccess` 上传成功的文件数\n             * * `numOfCancel` 被移除的文件数\n             * * `numOfProgress` 正在上传中的文件数\n             * * `numOfUploadFailed` 上传错误的文件数。\n             * * `numOfInvalid` 无效的文件数。\n             * @property {Object} stats\n             */\n            this.stats = {\n                numOfQueue: 0,\n                numOfSuccess: 0,\n                numOfCancel: 0,\n                numOfProgress: 0,\n                numOfUploadFailed: 0,\n                numOfInvalid: 0\n            };\n    \n            // 上传队列，仅包括等待上传的文件\n            this._queue = [];\n    \n            // 存储所有文件\n            this._map = {};\n        }\n    \n        $.extend( Queue.prototype, {\n    \n            /**\n             * 将新文件加入对队列尾部\n             *\n             * @method append\n             * @param  {File} file   文件对象\n             */\n            append: function( file ) {\n                this._queue.push( file );\n                this._fileAdded( file );\n                return this;\n            },\n    \n            /**\n             * 将新文件加入对队列头部\n             *\n             * @method prepend\n             * @param  {File} file   文件对象\n             */\n            prepend: function( file ) {\n                this._queue.unshift( file );\n                this._fileAdded( file );\n                return this;\n            },\n    \n            /**\n             * 获取文件对象\n             *\n             * @method getFile\n             * @param  {String} fileId   文件ID\n             * @return {File}\n             */\n            getFile: function( fileId ) {\n                if ( typeof fileId !== 'string' ) {\n                    return fileId;\n                }\n                return this._map[ fileId ];\n            },\n    \n            /**\n             * 从队列中取出一个指定状态的文件。\n             * @grammar fetch( status ) => File\n             * @method fetch\n             * @param {String} status [文件状态值](#WebUploader:File:File.Status)\n             * @return {File} [File](#WebUploader:File)\n             */\n            fetch: function( status ) {\n                var len = this._queue.length,\n                    i, file;\n    \n                status = status || STATUS.QUEUED;\n    \n                for ( i = 0; i < len; i++ ) {\n                    file = this._queue[ i ];\n    \n                    if ( status === file.getStatus() ) {\n                        return file;\n                    }\n                }\n    \n                return null;\n            },\n    \n            /**\n             * 对队列进行排序，能够控制文件上传顺序。\n             * @grammar sort( fn ) => undefined\n             * @method sort\n             * @param {Function} fn 排序方法\n             */\n            sort: function( fn ) {\n                if ( typeof fn === 'function' ) {\n                    this._queue.sort( fn );\n                }\n            },\n    \n            /**\n             * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。\n             * @grammar getFiles( [status1[, status2 ...]] ) => Array\n             * @method getFiles\n             * @param {String} [status] [文件状态值](#WebUploader:File:File.Status)\n             */\n            getFiles: function() {\n                var sts = [].slice.call( arguments, 0 ),\n                    ret = [],\n                    i = 0,\n                    len = this._queue.length,\n                    file;\n    \n                for ( ; i < len; i++ ) {\n                    file = this._queue[ i ];\n    \n                    if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) {\n                        continue;\n                    }\n    \n                    ret.push( file );\n                }\n    \n                return ret;\n            },\n    \n            _fileAdded: function( file ) {\n                var me = this,\n                    existing = this._map[ file.id ];\n    \n                if ( !existing ) {\n                    this._map[ file.id ] = file;\n    \n                    file.on( 'statuschange', function( cur, pre ) {\n                        me._onFileStatusChange( cur, pre );\n                    });\n                }\n    \n                file.setStatus( STATUS.QUEUED );\n            },\n    \n            _onFileStatusChange: function( curStatus, preStatus ) {\n                var stats = this.stats;\n    \n                switch ( preStatus ) {\n                    case STATUS.PROGRESS:\n                        stats.numOfProgress--;\n                        break;\n    \n                    case STATUS.QUEUED:\n                        stats.numOfQueue --;\n                        break;\n    \n                    case STATUS.ERROR:\n                        stats.numOfUploadFailed--;\n                        break;\n    \n                    case STATUS.INVALID:\n                        stats.numOfInvalid--;\n                        break;\n                }\n    \n                switch ( curStatus ) {\n                    case STATUS.QUEUED:\n                        stats.numOfQueue++;\n                        break;\n    \n                    case STATUS.PROGRESS:\n                        stats.numOfProgress++;\n                        break;\n    \n                    case STATUS.ERROR:\n                        stats.numOfUploadFailed++;\n                        break;\n    \n                    case STATUS.COMPLETE:\n                        stats.numOfSuccess++;\n                        break;\n    \n                    case STATUS.CANCELLED:\n                        stats.numOfCancel++;\n                        break;\n    \n                    case STATUS.INVALID:\n                        stats.numOfInvalid++;\n                        break;\n                }\n            }\n    \n        });\n    \n        Mediator.installTo( Queue.prototype );\n    \n        return Queue;\n    });\n    /**\n     * @fileOverview 队列\n     */\n    define('widgets/queue',[\n        'base',\n        'uploader',\n        'queue',\n        'file',\n        'lib/file',\n        'runtime/client',\n        'widgets/widget'\n    ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) {\n    \n        var $ = Base.$,\n            rExt = /\\.\\w+$/,\n            Status = WUFile.Status;\n    \n        return Uploader.register({\n            'sort-files': 'sortFiles',\n            'add-file': 'addFiles',\n            'get-file': 'getFile',\n            'fetch-file': 'fetchFile',\n            'get-stats': 'getStats',\n            'get-files': 'getFiles',\n            'remove-file': 'removeFile',\n            'retry': 'retry',\n            'reset': 'reset',\n            'accept-file': 'acceptFile'\n        }, {\n    \n            init: function( opts ) {\n                var me = this,\n                    deferred, len, i, item, arr, accept, runtime;\n    \n                if ( $.isPlainObject( opts.accept ) ) {\n                    opts.accept = [ opts.accept ];\n                }\n    \n                // accept中的中生成匹配正则。\n                if ( opts.accept ) {\n                    arr = [];\n    \n                    for ( i = 0, len = opts.accept.length; i < len; i++ ) {\n                        item = opts.accept[ i ].extensions;\n                        item && arr.push( item );\n                    }\n    \n                    if ( arr.length ) {\n                        accept = '\\\\.' + arr.join(',')\n                                .replace( /,/g, '$|\\\\.' )\n                                .replace( /\\*/g, '.*' ) + '$';\n                    }\n    \n                    me.accept = new RegExp( accept, 'i' );\n                }\n    \n                me.queue = new Queue();\n                me.stats = me.queue.stats;\n    \n                // 如果当前不是html5运行时，那就算了。\n                // 不执行后续操作\n                if ( this.request('predict-runtime-type') !== 'html5' ) {\n                    return;\n                }\n    \n                // 创建一个 html5 运行时的 placeholder\n                // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。\n                deferred = Base.Deferred();\n                runtime = new RuntimeClient('Placeholder');\n                runtime.connectRuntime({\n                    runtimeOrder: 'html5'\n                }, function() {\n                    me._ruid = runtime.getRuid();\n                    deferred.resolve();\n                });\n                return deferred.promise();\n            },\n    \n    \n            // 为了支持外部直接添加一个原生File对象。\n            _wrapFile: function( file ) {\n                if ( !(file instanceof WUFile) ) {\n    \n                    if ( !(file instanceof File) ) {\n                        if ( !this._ruid ) {\n                            throw new Error('Can\\'t add external files.');\n                        }\n                        file = new File( this._ruid, file );\n                    }\n    \n                    file = new WUFile( file );\n                }\n    \n                return file;\n            },\n    \n            // 判断文件是否可以被加入队列\n            acceptFile: function( file ) {\n                var invalid = !file || file.size < 6 || this.accept &&\n    \n                        // 如果名字中有后缀，才做后缀白名单处理。\n                        rExt.exec( file.name ) && !this.accept.test( file.name );\n    \n                return !invalid;\n            },\n    \n    \n            /**\n             * @event beforeFileQueued\n             * @param {File} file File对象\n             * @description 当文件被加入队列之前触发，此事件的handler返回值为`false`，则此文件不会被添加进入队列。\n             * @for  Uploader\n             */\n    \n            /**\n             * @event fileQueued\n             * @param {File} file File对象\n             * @description 当文件被加入队列以后触发。\n             * @for  Uploader\n             */\n    \n            _addFile: function( file ) {\n                var me = this;\n    \n                file = me._wrapFile( file );\n    \n                // 不过类型判断允许不允许，先派送 `beforeFileQueued`\n                if ( !me.owner.trigger( 'beforeFileQueued', file ) ) {\n                    return;\n                }\n    \n                // 类型不匹配，则派送错误事件，并返回。\n                if ( !me.acceptFile( file ) ) {\n                    me.owner.trigger( 'error', 'Q_TYPE_DENIED', file );\n                    return;\n                }\n    \n                me.queue.append( file );\n                me.owner.trigger( 'fileQueued', file );\n                return file;\n            },\n    \n            getFile: function( fileId ) {\n                return this.queue.getFile( fileId );\n            },\n    \n            /**\n             * @event filesQueued\n             * @param {File} files 数组，内容为原始File(lib/File）对象。\n             * @description 当一批文件添加进队列以后触发。\n             * @for  Uploader\n             */\n    \n            /**\n             * @method addFiles\n             * @grammar addFiles( file ) => undefined\n             * @grammar addFiles( [file1, file2 ...] ) => undefined\n             * @param {Array of File or File} [files] Files 对象 数组\n             * @description 添加文件到队列\n             * @for  Uploader\n             */\n            addFiles: function( files ) {\n                var me = this;\n    \n                if ( !files.length ) {\n                    files = [ files ];\n                }\n    \n                files = $.map( files, function( file ) {\n                    return me._addFile( file );\n                });\n    \n                me.owner.trigger( 'filesQueued', files );\n    \n                if ( me.options.auto ) {\n                    me.request('start-upload');\n                }\n            },\n    \n            getStats: function() {\n                return this.stats;\n            },\n    \n            /**\n             * @event fileDequeued\n             * @param {File} file File对象\n             * @description 当文件被移除队列后触发。\n             * @for  Uploader\n             */\n    \n            /**\n             * @method removeFile\n             * @grammar removeFile( file ) => undefined\n             * @grammar removeFile( id ) => undefined\n             * @param {File|id} file File对象或这File对象的id\n             * @description 移除某一文件。\n             * @for  Uploader\n             * @example\n             *\n             * $li.on('click', '.remove-this', function() {\n             *     uploader.removeFile( file );\n             * })\n             */\n            removeFile: function( file ) {\n                var me = this;\n    \n                file = file.id ? file : me.queue.getFile( file );\n    \n                file.setStatus( Status.CANCELLED );\n                me.owner.trigger( 'fileDequeued', file );\n            },\n    \n            /**\n             * @method getFiles\n             * @grammar getFiles() => Array\n             * @grammar getFiles( status1, status2, status... ) => Array\n             * @description 返回指定状态的文件集合，不传参数将返回所有状态的文件。\n             * @for  Uploader\n             * @example\n             * console.log( uploader.getFiles() );    // => all files\n             * console.log( uploader.getFiles('error') )    // => all error files.\n             */\n            getFiles: function() {\n                return this.queue.getFiles.apply( this.queue, arguments );\n            },\n    \n            fetchFile: function() {\n                return this.queue.fetch.apply( this.queue, arguments );\n            },\n    \n            /**\n             * @method retry\n             * @grammar retry() => undefined\n             * @grammar retry( file ) => undefined\n             * @description 重试上传，重试指定文件，或者从出错的文件开始重新上传。\n             * @for  Uploader\n             * @example\n             * function retry() {\n             *     uploader.retry();\n             * }\n             */\n            retry: function( file, noForceStart ) {\n                var me = this,\n                    files, i, len;\n    \n                if ( file ) {\n                    file = file.id ? file : me.queue.getFile( file );\n                    file.setStatus( Status.QUEUED );\n                    noForceStart || me.request('start-upload');\n                    return;\n                }\n    \n                files = me.queue.getFiles( Status.ERROR );\n                i = 0;\n                len = files.length;\n    \n                for ( ; i < len; i++ ) {\n                    file = files[ i ];\n                    file.setStatus( Status.QUEUED );\n                }\n    \n                me.request('start-upload');\n            },\n    \n            /**\n             * @method sort\n             * @grammar sort( fn ) => undefined\n             * @description 排序队列中的文件，在上传之前调整可以控制上传顺序。\n             * @for  Uploader\n             */\n            sortFiles: function() {\n                return this.queue.sort.apply( this.queue, arguments );\n            },\n    \n            /**\n             * @method reset\n             * @grammar reset() => undefined\n             * @description 重置uploader。目前只重置了队列。\n             * @for  Uploader\n             * @example\n             * uploader.reset();\n             */\n            reset: function() {\n                this.queue = new Queue();\n                this.stats = this.queue.stats;\n            }\n        });\n    \n    });\n    /**\n     * @fileOverview 添加获取Runtime相关信息的方法。\n     */\n    define('widgets/runtime',[\n        'uploader',\n        'runtime/runtime',\n        'widgets/widget'\n    ], function( Uploader, Runtime ) {\n    \n        Uploader.support = function() {\n            return Runtime.hasRuntime.apply( Runtime, arguments );\n        };\n    \n        return Uploader.register({\n            'predict-runtime-type': 'predictRuntmeType'\n        }, {\n    \n            init: function() {\n                if ( !this.predictRuntmeType() ) {\n                    throw Error('Runtime Error');\n                }\n            },\n    \n            /**\n             * 预测Uploader将采用哪个`Runtime`\n             * @grammar predictRuntmeType() => String\n             * @method predictRuntmeType\n             * @for  Uploader\n             */\n            predictRuntmeType: function() {\n                var orders = this.options.runtimeOrder || Runtime.orders,\n                    type = this.type,\n                    i, len;\n    \n                if ( !type ) {\n                    orders = orders.split( /\\s*,\\s*/g );\n    \n                    for ( i = 0, len = orders.length; i < len; i++ ) {\n                        if ( Runtime.hasRuntime( orders[ i ] ) ) {\n                            this.type = type = orders[ i ];\n                            break;\n                        }\n                    }\n                }\n    \n                return type;\n            }\n        });\n    });\n    /**\n     * @fileOverview Transport\n     */\n    define('lib/transport',[\n        'base',\n        'runtime/client',\n        'mediator'\n    ], function( Base, RuntimeClient, Mediator ) {\n    \n        var $ = Base.$;\n    \n        function Transport( opts ) {\n            var me = this;\n    \n            opts = me.options = $.extend( true, {}, Transport.options, opts || {} );\n            RuntimeClient.call( this, 'Transport' );\n    \n            this._blob = null;\n            this._formData = opts.formData || {};\n            this._headers = opts.headers || {};\n    \n            this.on( 'progress', this._timeout );\n            this.on( 'load error', function() {\n                me.trigger( 'progress', 1 );\n                clearTimeout( me._timer );\n            });\n        }\n    \n        Transport.options = {\n            server: '',\n            method: 'POST',\n    \n            // 跨域时，是否允许携带cookie, 只有html5 runtime才有效\n            withCredentials: false,\n            fileVal: 'file',\n            timeout: 2 * 60 * 1000,    // 2分钟\n            formData: {},\n            headers: {},\n            sendAsBinary: false\n        };\n    \n        $.extend( Transport.prototype, {\n    \n            // 添加Blob, 只能添加一次，最后一次有效。\n            appendBlob: function( key, blob, filename ) {\n                var me = this,\n                    opts = me.options;\n    \n                if ( me.getRuid() ) {\n                    me.disconnectRuntime();\n                }\n    \n                // 连接到blob归属的同一个runtime.\n                me.connectRuntime( blob.ruid, function() {\n                    me.exec('init');\n                });\n    \n                me._blob = blob;\n                opts.fileVal = key || opts.fileVal;\n                opts.filename = filename || opts.filename;\n            },\n    \n            // 添加其他字段\n            append: function( key, value ) {\n                if ( typeof key === 'object' ) {\n                    $.extend( this._formData, key );\n                } else {\n                    this._formData[ key ] = value;\n                }\n            },\n    \n            setRequestHeader: function( key, value ) {\n                if ( typeof key === 'object' ) {\n                    $.extend( this._headers, key );\n                } else {\n                    this._headers[ key ] = value;\n                }\n            },\n    \n            send: function( method ) {\n                this.exec( 'send', method );\n                this._timeout();\n            },\n    \n            abort: function() {\n                clearTimeout( this._timer );\n                return this.exec('abort');\n            },\n    \n            destroy: function() {\n                this.trigger('destroy');\n                this.off();\n                this.exec('destroy');\n                this.disconnectRuntime();\n            },\n    \n            getResponse: function() {\n                return this.exec('getResponse');\n            },\n    \n            getResponseAsJson: function() {\n                return this.exec('getResponseAsJson');\n            },\n    \n            getStatus: function() {\n                return this.exec('getStatus');\n            },\n    \n            _timeout: function() {\n                var me = this,\n                    duration = me.options.timeout;\n    \n                if ( !duration ) {\n                    return;\n                }\n    \n                clearTimeout( me._timer );\n                me._timer = setTimeout(function() {\n                    me.abort();\n                    me.trigger( 'error', 'timeout' );\n                }, duration );\n            }\n    \n        });\n    \n        // 让Transport具备事件功能。\n        Mediator.installTo( Transport.prototype );\n    \n        return Transport;\n    });\n    /**\n     * @fileOverview 负责文件上传相关。\n     */\n    define('widgets/upload',[\n        'base',\n        'uploader',\n        'file',\n        'lib/transport',\n        'widgets/widget'\n    ], function( Base, Uploader, WUFile, Transport ) {\n    \n        var $ = Base.$,\n            isPromise = Base.isPromise,\n            Status = WUFile.Status;\n    \n        // 添加默认配置项\n        $.extend( Uploader.options, {\n    \n    \n            /**\n             * @property {Boolean} [prepareNextFile=false]\n             * @namespace options\n             * @for Uploader\n             * @description 是否允许在文件传输时提前把下一个文件准备好。\n             * 对于一个文件的准备工作比较耗时，比如图片压缩，md5序列化。\n             * 如果能提前在当前文件传输期处理，可以节省总体耗时。\n             */\n            prepareNextFile: false,\n    \n            /**\n             * @property {Boolean} [chunked=false]\n             * @namespace options\n             * @for Uploader\n             * @description 是否要分片处理大文件上传。\n             */\n            chunked: false,\n    \n            /**\n             * @property {Boolean} [chunkSize=5242880]\n             * @namespace options\n             * @for Uploader\n             * @description 如果要分片，分多大一片？ 默认大小为5M.\n             */\n            chunkSize: 5 * 1024 * 1024,\n    \n            /**\n             * @property {Boolean} [chunkRetry=2]\n             * @namespace options\n             * @for Uploader\n             * @description 如果某个分片由于网络问题出错，允许自动重传多少次？\n             */\n            chunkRetry: 2,\n    \n            /**\n             * @property {Boolean} [threads=3]\n             * @namespace options\n             * @for Uploader\n             * @description 上传并发数。允许同时最大上传进程数。\n             */\n            threads: 3,\n    \n    \n            /**\n             * @property {Object} [formData]\n             * @namespace options\n             * @for Uploader\n             * @description 文件上传请求的参数表，每次发送都会发送此对象中的参数。\n             */\n            formData: null\n    \n            /**\n             * @property {Object} [fileVal='file']\n             * @namespace options\n             * @for Uploader\n             * @description 设置文件上传域的name。\n             */\n    \n            /**\n             * @property {Object} [method='POST']\n             * @namespace options\n             * @for Uploader\n             * @description 文件上传方式，`POST`或者`GET`。\n             */\n    \n            /**\n             * @property {Object} [sendAsBinary=false]\n             * @namespace options\n             * @for Uploader\n             * @description 是否已二进制的流的方式发送文件，这样整个上传内容`php://input`都为文件内容，\n             * 其他参数在$_GET数组中。\n             */\n        });\n    \n        // 负责将文件切片。\n        function CuteFile( file, chunkSize ) {\n            var pending = [],\n                blob = file.source,\n                total = blob.size,\n                chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1,\n                start = 0,\n                index = 0,\n                len;\n    \n            while ( index < chunks ) {\n                len = Math.min( chunkSize, total - start );\n    \n                pending.push({\n                    file: file,\n                    start: start,\n                    end: chunkSize ? (start + len) : total,\n                    total: total,\n                    chunks: chunks,\n                    chunk: index++\n                });\n                start += len;\n            }\n    \n            file.blocks = pending.concat();\n            file.remaning = pending.length;\n    \n            return {\n                file: file,\n    \n                has: function() {\n                    return !!pending.length;\n                },\n    \n                fetch: function() {\n                    return pending.shift();\n                }\n            };\n        }\n    \n        Uploader.register({\n            'start-upload': 'start',\n            'stop-upload': 'stop',\n            'skip-file': 'skipFile',\n            'is-in-progress': 'isInProgress'\n        }, {\n    \n            init: function() {\n                var owner = this.owner;\n    \n                this.runing = false;\n    \n                // 记录当前正在传的数据，跟threads相关\n                this.pool = [];\n    \n                // 缓存即将上传的文件。\n                this.pending = [];\n    \n                // 跟踪还有多少分片没有完成上传。\n                this.remaning = 0;\n                this.__tick = Base.bindFn( this._tick, this );\n    \n                owner.on( 'uploadComplete', function( file ) {\n                    // 把其他块取消了。\n                    file.blocks && $.each( file.blocks, function( _, v ) {\n                        v.transport && (v.transport.abort(), v.transport.destroy());\n                        delete v.transport;\n                    });\n    \n                    delete file.blocks;\n                    delete file.remaning;\n                });\n            },\n    \n            /**\n             * @event startUpload\n             * @description 当开始上传流程时触发。\n             * @for  Uploader\n             */\n    \n            /**\n             * 开始上传。此方法可以从初始状态调用开始上传流程，也可以从暂停状态调用，继续上传流程。\n             * @grammar upload() => undefined\n             * @method upload\n             * @for  Uploader\n             */\n            start: function() {\n                var me = this;\n    \n                // 移出invalid的文件\n                $.each( me.request( 'get-files', Status.INVALID ), function() {\n                    me.request( 'remove-file', this );\n                });\n    \n                if ( me.runing ) {\n                    return;\n                }\n    \n                me.runing = true;\n    \n                // 如果有暂停的，则续传\n                $.each( me.pool, function( _, v ) {\n                    var file = v.file;\n    \n                    if ( file.getStatus() === Status.INTERRUPT ) {\n                        file.setStatus( Status.PROGRESS );\n                        me._trigged = false;\n                        v.transport && v.transport.send();\n                    }\n                });\n    \n                me._trigged = false;\n                me.owner.trigger('startUpload');\n                Base.nextTick( me.__tick );\n            },\n    \n            /**\n             * @event stopUpload\n             * @description 当开始上传流程暂停时触发。\n             * @for  Uploader\n             */\n    \n            /**\n             * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。\n             * @grammar stop() => undefined\n             * @grammar stop( true ) => undefined\n             * @method stop\n             * @for  Uploader\n             */\n            stop: function( interrupt ) {\n                var me = this;\n    \n                if ( me.runing === false ) {\n                    return;\n                }\n    \n                me.runing = false;\n    \n                interrupt && $.each( me.pool, function( _, v ) {\n                    v.transport && v.transport.abort();\n                    v.file.setStatus( Status.INTERRUPT );\n                });\n    \n                me.owner.trigger('stopUpload');\n            },\n    \n            /**\n             * 判断`Uplaode`r是否正在上传中。\n             * @grammar isInProgress() => Boolean\n             * @method isInProgress\n             * @for  Uploader\n             */\n            isInProgress: function() {\n                return !!this.runing;\n            },\n    \n            getStats: function() {\n                return this.request('get-stats');\n            },\n    \n            /**\n             * 掉过一个文件上传，直接标记指定文件为已上传状态。\n             * @grammar skipFile( file ) => undefined\n             * @method skipFile\n             * @for  Uploader\n             */\n            skipFile: function( file, status ) {\n                file = this.request( 'get-file', file );\n    \n                file.setStatus( status || Status.COMPLETE );\n                file.skipped = true;\n    \n                // 如果正在上传。\n                file.blocks && $.each( file.blocks, function( _, v ) {\n                    var _tr = v.transport;\n    \n                    if ( _tr ) {\n                        _tr.abort();\n                        _tr.destroy();\n                        delete v.transport;\n                    }\n                });\n    \n                this.owner.trigger( 'uploadSkip', file );\n            },\n    \n            /**\n             * @event uploadFinished\n             * @description 当所有文件上传结束时触发。\n             * @for  Uploader\n             */\n            _tick: function() {\n                var me = this,\n                    opts = me.options,\n                    fn, val;\n    \n                // 上一个promise还没有结束，则等待完成后再执行。\n                if ( me._promise ) {\n                    return me._promise.always( me.__tick );\n                }\n    \n                // 还有位置，且还有文件要处理的话。\n                if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) {\n                    me._trigged = false;\n    \n                    fn = function( val ) {\n                        me._promise = null;\n    \n                        // 有可能是reject过来的，所以要检测val的类型。\n                        val && val.file && me._startSend( val );\n                        Base.nextTick( me.__tick );\n                    };\n    \n                    me._promise = isPromise( val ) ? val.always( fn ) : fn( val );\n    \n                // 没有要上传的了，且没有正在传输的了。\n                } else if ( !me.remaning && !me.getStats().numOfQueue ) {\n                    me.runing = false;\n    \n                    me._trigged || Base.nextTick(function() {\n                        me.owner.trigger('uploadFinished');\n                    });\n                    me._trigged = true;\n                }\n            },\n    \n            _nextBlock: function() {\n                var me = this,\n                    act = me._act,\n                    opts = me.options,\n                    next, done;\n    \n                // 如果当前文件还有没有需要传输的，则直接返回剩下的。\n                if ( act && act.has() &&\n                        act.file.getStatus() === Status.PROGRESS ) {\n    \n                    // 是否提前准备下一个文件\n                    if ( opts.prepareNextFile && !me.pending.length ) {\n                        me._prepareNextFile();\n                    }\n    \n                    return act.fetch();\n    \n                // 否则，如果正在运行，则准备下一个文件，并等待完成后返回下个分片。\n                } else if ( me.runing ) {\n    \n                    // 如果缓存中有，则直接在缓存中取，没有则去queue中取。\n                    if ( !me.pending.length && me.getStats().numOfQueue ) {\n                        me._prepareNextFile();\n                    }\n    \n                    next = me.pending.shift();\n                    done = function( file ) {\n                        if ( !file ) {\n                            return null;\n                        }\n    \n                        act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 );\n                        me._act = act;\n                        return act.fetch();\n                    };\n    \n                    // 文件可能还在prepare中，也有可能已经完全准备好了。\n                    return isPromise( next ) ?\n                            next[ next.pipe ? 'pipe' : 'then']( done ) :\n                            done( next );\n                }\n            },\n    \n    \n            /**\n             * @event uploadStart\n             * @param {File} file File对象\n             * @description 某个文件开始上传前触发，一个文件只会触发一次。\n             * @for  Uploader\n             */\n            _prepareNextFile: function() {\n                var me = this,\n                    file = me.request('fetch-file'),\n                    pending = me.pending,\n                    promise;\n    \n                if ( file ) {\n                    promise = me.request( 'before-send-file', file, function() {\n    \n                        // 有可能文件被skip掉了。文件被skip掉后，状态坑定不是Queued.\n                        if ( file.getStatus() === Status.QUEUED ) {\n                            me.owner.trigger( 'uploadStart', file );\n                            file.setStatus( Status.PROGRESS );\n                            return file;\n                        }\n    \n                        return me._finishFile( file );\n                    });\n    \n                    // 如果还在pending中，则替换成文件本身。\n                    promise.done(function() {\n                        var idx = $.inArray( promise, pending );\n    \n                        ~idx && pending.splice( idx, 1, file );\n                    });\n    \n                    // befeore-send-file的钩子就有错误发生。\n                    promise.fail(function( reason ) {\n                        file.setStatus( Status.ERROR, reason );\n                        me.owner.trigger( 'uploadError', file, reason );\n                        me.owner.trigger( 'uploadComplete', file );\n                    });\n    \n                    pending.push( promise );\n                }\n            },\n    \n            // 让出位置了，可以让其他分片开始上传\n            _popBlock: function( block ) {\n                var idx = $.inArray( block, this.pool );\n    \n                this.pool.splice( idx, 1 );\n                block.file.remaning--;\n                this.remaning--;\n            },\n    \n            // 开始上传，可以被掉过。如果promise被reject了，则表示跳过此分片。\n            _startSend: function( block ) {\n                var me = this,\n                    file = block.file,\n                    promise;\n    \n                me.pool.push( block );\n                me.remaning++;\n    \n                // 如果没有分片，则直接使用原始的。\n                // 不会丢失content-type信息。\n                block.blob = block.chunks === 1 ? file.source :\n                        file.source.slice( block.start, block.end );\n    \n                // hook, 每个分片发送之前可能要做些异步的事情。\n                promise = me.request( 'before-send', block, function() {\n    \n                    // 有可能文件已经上传出错了，所以不需要再传输了。\n                    if ( file.getStatus() === Status.PROGRESS ) {\n                        me._doSend( block );\n                    } else {\n                        me._popBlock( block );\n                        Base.nextTick( me.__tick );\n                    }\n                });\n    \n                // 如果为fail了，则跳过此分片。\n                promise.fail(function() {\n                    if ( file.remaning === 1 ) {\n                        me._finishFile( file ).always(function() {\n                            block.percentage = 1;\n                            me._popBlock( block );\n                            me.owner.trigger( 'uploadComplete', file );\n                            Base.nextTick( me.__tick );\n                        });\n                    } else {\n                        block.percentage = 1;\n                        me._popBlock( block );\n                        Base.nextTick( me.__tick );\n                    }\n                });\n            },\n    \n    \n            /**\n             * @event uploadBeforeSend\n             * @param {Object} object\n             * @param {Object} data 默认的上传参数，可以扩展此对象来控制上传参数。\n             * @description 当某个文件的分块在发送前触发，主要用来询问是否要添加附带参数，大文件在开起分片上传的前提下此事件可能会触发多次。\n             * @for  Uploader\n             */\n    \n            /**\n             * @event uploadAccept\n             * @param {Object} object\n             * @param {Object} ret 服务端的返回数据，json格式，如果服务端不是json格式，从ret._raw中取数据，自行解析。\n             * @description 当某个文件上传到服务端响应后，会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。\n             * @for  Uploader\n             */\n    \n            /**\n             * @event uploadProgress\n             * @param {File} file File对象\n             * @param {Number} percentage 上传进度\n             * @description 上传过程中触发，携带上传进度。\n             * @for  Uploader\n             */\n    \n    \n            /**\n             * @event uploadError\n             * @param {File} file File对象\n             * @param {String} reason 出错的code\n             * @description 当文件上传出错时触发。\n             * @for  Uploader\n             */\n    \n            /**\n             * @event uploadSuccess\n             * @param {File} file File对象\n             * @param {Object} response 服务端返回的数据\n             * @description 当文件上传成功时触发。\n             * @for  Uploader\n             */\n    \n            /**\n             * @event uploadComplete\n             * @param {File} [file] File对象\n             * @description 不管成功或者失败，文件上传完成时触发。\n             * @for  Uploader\n             */\n    \n            // 做上传操作。\n            _doSend: function( block ) {\n                var me = this,\n                    owner = me.owner,\n                    opts = me.options,\n                    file = block.file,\n                    tr = new Transport( opts ),\n                    data = $.extend({}, opts.formData ),\n                    headers = $.extend({}, opts.headers ),\n                    requestAccept, ret;\n    \n                block.transport = tr;\n    \n                tr.on( 'destroy', function() {\n                    delete block.transport;\n                    me._popBlock( block );\n                    Base.nextTick( me.__tick );\n                });\n    \n                // 广播上传进度。以文件为单位。\n                tr.on( 'progress', function( percentage ) {\n                    var totalPercent = 0,\n                        uploaded = 0;\n    \n                    // 可能没有abort掉，progress还是执行进来了。\n                    // if ( !file.blocks ) {\n                    //     return;\n                    // }\n    \n                    totalPercent = block.percentage = percentage;\n    \n                    if ( block.chunks > 1 ) {    // 计算文件的整体速度。\n                        $.each( file.blocks, function( _, v ) {\n                            uploaded += (v.percentage || 0) * (v.end - v.start);\n                        });\n    \n                        totalPercent = uploaded / file.size;\n                    }\n    \n                    owner.trigger( 'uploadProgress', file, totalPercent || 0 );\n                });\n    \n                // 用来询问，是否返回的结果是有错误的。\n                requestAccept = function( reject ) {\n                    var fn;\n    \n                    ret = tr.getResponseAsJson() || {};\n                    ret._raw = tr.getResponse();\n                    fn = function( value ) {\n                        reject = value;\n                    };\n    \n                    // 服务端响应了，不代表成功了，询问是否响应正确。\n                    if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) {\n                        reject = reject || 'server';\n                    }\n    \n                    return reject;\n                };\n    \n                // 尝试重试，然后广播文件上传出错。\n                tr.on( 'error', function( type, flag ) {\n                    block.retried = block.retried || 0;\n    \n                    // 自动重试\n                    if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) &&\n                            block.retried < opts.chunkRetry ) {\n    \n                        block.retried++;\n                        tr.send();\n    \n                    } else {\n    \n                        // http status 500 ~ 600\n                        if ( !flag && type === 'server' ) {\n                            type = requestAccept( type );\n                        }\n    \n                        file.setStatus( Status.ERROR, type );\n                        owner.trigger( 'uploadError', file, type );\n                        owner.trigger( 'uploadComplete', file );\n                    }\n                });\n    \n                // 上传成功\n                tr.on( 'load', function() {\n                    var reason;\n    \n                    // 如果非预期，转向上传出错。\n                    if ( (reason = requestAccept()) ) {\n                        tr.trigger( 'error', reason, true );\n                        return;\n                    }\n    \n                    // 全部上传完成。\n                    if ( file.remaning === 1 ) {\n                        me._finishFile( file, ret );\n                    } else {\n                        tr.destroy();\n                    }\n                });\n    \n                // 配置默认的上传字段。\n                data = $.extend( data, {\n                    id: file.id,\n                    name: file.name,\n                    type: file.type,\n                    lastModifiedDate: file.lastModifiedDate,\n                    size: file.size\n                });\n    \n                block.chunks > 1 && $.extend( data, {\n                    chunks: block.chunks,\n                    chunk: block.chunk\n                });\n    \n                // 在发送之间可以添加字段什么的。。。\n                // 如果默认的字段不够使用，可以通过监听此事件来扩展\n                owner.trigger( 'uploadBeforeSend', block, data, headers );\n    \n                // 开始发送。\n                tr.appendBlob( opts.fileVal, block.blob, file.name );\n                tr.append( data );\n                tr.setRequestHeader( headers );\n                tr.send();\n            },\n    \n            // 完成上传。\n            _finishFile: function( file, ret, hds ) {\n                var owner = this.owner;\n    \n                return owner\n                        .request( 'after-send-file', arguments, function() {\n                            file.setStatus( Status.COMPLETE );\n                            owner.trigger( 'uploadSuccess', file, ret, hds );\n                        })\n                        .fail(function( reason ) {\n    \n                            // 如果外部已经标记为invalid什么的，不再改状态。\n                            if ( file.getStatus() === Status.PROGRESS ) {\n                                file.setStatus( Status.ERROR, reason );\n                            }\n    \n                            owner.trigger( 'uploadError', file, reason );\n                        })\n                        .always(function() {\n                            owner.trigger( 'uploadComplete', file );\n                        });\n            }\n    \n        });\n    });\n    /**\n     * @fileOverview 各种验证，包括文件总大小是否超出、单文件是否超出和文件是否重复。\n     */\n    \n    define('widgets/validator',[\n        'base',\n        'uploader',\n        'file',\n        'widgets/widget'\n    ], function( Base, Uploader, WUFile ) {\n    \n        var $ = Base.$,\n            validators = {},\n            api;\n    \n        /**\n         * @event error\n         * @param {String} type 错误类型。\n         * @description 当validate不通过时，会以派送错误事件的形式通知调用者。通过`upload.on('error', handler)`可以捕获到此类错误，目前有以下错误会在特定的情况下派送错来。\n         *\n         * * `Q_EXCEED_NUM_LIMIT` 在设置了`fileNumLimit`且尝试给`uploader`添加的文件数量超出这个值时派送。\n         * * `Q_EXCEED_SIZE_LIMIT` 在设置了`Q_EXCEED_SIZE_LIMIT`且尝试给`uploader`添加的文件总大小超出这个值时派送。\n         * @for  Uploader\n         */\n    \n        // 暴露给外面的api\n        api = {\n    \n            // 添加验证器\n            addValidator: function( type, cb ) {\n                validators[ type ] = cb;\n            },\n    \n            // 移除验证器\n            removeValidator: function( type ) {\n                delete validators[ type ];\n            }\n        };\n    \n        // 在Uploader初始化的时候启动Validators的初始化\n        Uploader.register({\n            init: function() {\n                var me = this;\n                $.each( validators, function() {\n                    this.call( me.owner );\n                });\n            }\n        });\n    \n        /**\n         * @property {int} [fileNumLimit=undefined]\n         * @namespace options\n         * @for Uploader\n         * @description 验证文件总数量, 超出则不允许加入队列。\n         */\n        api.addValidator( 'fileNumLimit', function() {\n            var uploader = this,\n                opts = uploader.options,\n                count = 0,\n                max = opts.fileNumLimit >> 0,\n                flag = true;\n    \n            if ( !max ) {\n                return;\n            }\n    \n            uploader.on( 'beforeFileQueued', function( file ) {\n    \n                if ( count >= max && flag ) {\n                    flag = false;\n                    this.trigger( 'error', 'Q_EXCEED_NUM_LIMIT', max, file );\n                    setTimeout(function() {\n                        flag = true;\n                    }, 1 );\n                }\n    \n                return count >= max ? false : true;\n            });\n    \n            uploader.on( 'fileQueued', function() {\n                count++;\n            });\n    \n            uploader.on( 'fileDequeued', function() {\n                count--;\n            });\n    \n            uploader.on( 'uploadFinished', function() {\n                count = 0;\n            });\n        });\n    \n    \n        /**\n         * @property {int} [fileSizeLimit=undefined]\n         * @namespace options\n         * @for Uploader\n         * @description 验证文件总大小是否超出限制, 超出则不允许加入队列。\n         */\n        api.addValidator( 'fileSizeLimit', function() {\n            var uploader = this,\n                opts = uploader.options,\n                count = 0,\n                max = opts.fileSizeLimit >> 0,\n                flag = true;\n    \n            if ( !max ) {\n                return;\n            }\n    \n            uploader.on( 'beforeFileQueued', function( file ) {\n                var invalid = count + file.size > max;\n    \n                if ( invalid && flag ) {\n                    flag = false;\n                    this.trigger( 'error', 'Q_EXCEED_SIZE_LIMIT', max, file );\n                    setTimeout(function() {\n                        flag = true;\n                    }, 1 );\n                }\n    \n                return invalid ? false : true;\n            });\n    \n            uploader.on( 'fileQueued', function( file ) {\n                count += file.size;\n            });\n    \n            uploader.on( 'fileDequeued', function( file ) {\n                count -= file.size;\n            });\n    \n            uploader.on( 'uploadFinished', function() {\n                count = 0;\n            });\n        });\n    \n        /**\n         * @property {int} [fileSingleSizeLimit=undefined]\n         * @namespace options\n         * @for Uploader\n         * @description 验证单个文件大小是否超出限制, 超出则不允许加入队列。\n         */\n        api.addValidator( 'fileSingleSizeLimit', function() {\n            var uploader = this,\n                opts = uploader.options,\n                max = opts.fileSingleSizeLimit;\n    \n            if ( !max ) {\n                return;\n            }\n    \n            uploader.on( 'beforeFileQueued', function( file ) {\n    \n                if ( file.size > max ) {\n                    file.setStatus( WUFile.Status.INVALID, 'exceed_size' );\n                    this.trigger( 'error', 'F_EXCEED_SIZE', file );\n                    return false;\n                }\n    \n            });\n    \n        });\n    \n        /**\n         * @property {int} [duplicate=undefined]\n         * @namespace options\n         * @for Uploader\n         * @description 去重， 根据文件名字、文件大小和最后修改时间来生成hash Key.\n         */\n        api.addValidator( 'duplicate', function() {\n            var uploader = this,\n                opts = uploader.options,\n                mapping = {};\n    \n            if ( opts.duplicate ) {\n                return;\n            }\n    \n            function hashString( str ) {\n                var hash = 0,\n                    i = 0,\n                    len = str.length,\n                    _char;\n    \n                for ( ; i < len; i++ ) {\n                    _char = str.charCodeAt( i );\n                    hash = _char + (hash << 6) + (hash << 16) - hash;\n                }\n    \n                return hash;\n            }\n    \n            uploader.on( 'beforeFileQueued', function( file ) {\n                var hash = file.__hash || (file.__hash = hashString( file.name +\n                        file.size + file.lastModifiedDate ));\n    \n                // 已经重复了\n                if ( mapping[ hash ] ) {\n                    this.trigger( 'error', 'F_DUPLICATE', file );\n                    return false;\n                }\n            });\n    \n            uploader.on( 'fileQueued', function( file ) {\n                var hash = file.__hash;\n    \n                hash && (mapping[ hash ] = true);\n            });\n    \n            uploader.on( 'fileDequeued', function( file ) {\n                var hash = file.__hash;\n    \n                hash && (delete mapping[ hash ]);\n            });\n        });\n    \n        return api;\n    });\n    \n    /**\n     * @fileOverview Runtime管理器，负责Runtime的选择, 连接\n     */\n    define('runtime/compbase',[],function() {\n    \n        function CompBase( owner, runtime ) {\n    \n            this.owner = owner;\n            this.options = owner.options;\n    \n            this.getRuntime = function() {\n                return runtime;\n            };\n    \n            this.getRuid = function() {\n                return runtime.uid;\n            };\n    \n            this.trigger = function() {\n                return owner.trigger.apply( owner, arguments );\n            };\n        }\n    \n        return CompBase;\n    });\n    /**\n     * @fileOverview Html5Runtime\n     */\n    define('runtime/html5/runtime',[\n        'base',\n        'runtime/runtime',\n        'runtime/compbase'\n    ], function( Base, Runtime, CompBase ) {\n    \n        var type = 'html5',\n            components = {};\n    \n        function Html5Runtime() {\n            var pool = {},\n                me = this,\n                destory = this.destory;\n    \n            Runtime.apply( me, arguments );\n            me.type = type;\n    \n    \n            // 这个方法的调用者，实际上是RuntimeClient\n            me.exec = function( comp, fn/*, args...*/) {\n                var client = this,\n                    uid = client.uid,\n                    args = Base.slice( arguments, 2 ),\n                    instance;\n    \n                if ( components[ comp ] ) {\n                    instance = pool[ uid ] = pool[ uid ] ||\n                            new components[ comp ]( client, me );\n    \n                    if ( instance[ fn ] ) {\n                        return instance[ fn ].apply( instance, args );\n                    }\n                }\n            };\n    \n            me.destory = function() {\n                // @todo 删除池子中的所有实例\n                return destory && destory.apply( this, arguments );\n            };\n        }\n    \n        Base.inherits( Runtime, {\n            constructor: Html5Runtime,\n    \n            // 不需要连接其他程序，直接执行callback\n            init: function() {\n                var me = this;\n                setTimeout(function() {\n                    me.trigger('ready');\n                }, 1 );\n            }\n    \n        });\n    \n        // 注册Components\n        Html5Runtime.register = function( name, component ) {\n            var klass = components[ name ] = Base.inherits( CompBase, component );\n            return klass;\n        };\n    \n        // 注册html5运行时。\n        // 只有在支持的前提下注册。\n        if ( window.Blob && window.FileReader && window.DataView ) {\n            Runtime.addRuntime( type, Html5Runtime );\n        }\n    \n        return Html5Runtime;\n    });\n    /**\n     * @fileOverview Blob Html实现\n     */\n    define('runtime/html5/blob',[\n        'runtime/html5/runtime',\n        'lib/blob'\n    ], function( Html5Runtime, Blob ) {\n    \n        return Html5Runtime.register( 'Blob', {\n            slice: function( start, end ) {\n                var blob = this.owner.source,\n                    slice = blob.slice || blob.webkitSlice || blob.mozSlice;\n    \n                blob = slice.call( blob, start, end );\n    \n                return new Blob( this.getRuid(), blob );\n            }\n        });\n    });\n    /**\n     * @fileOverview FilePaste\n     */\n    define('runtime/html5/dnd',[\n        'base',\n        'runtime/html5/runtime',\n        'lib/file'\n    ], function( Base, Html5Runtime, File ) {\n    \n        var $ = Base.$,\n            prefix = 'webuploader-dnd-';\n    \n        return Html5Runtime.register( 'DragAndDrop', {\n            init: function() {\n                var elem = this.elem = this.options.container;\n    \n                this.dragEnterHandler = Base.bindFn( this._dragEnterHandler, this );\n                this.dragOverHandler = Base.bindFn( this._dragOverHandler, this );\n                this.dragLeaveHandler = Base.bindFn( this._dragLeaveHandler, this );\n                this.dropHandler = Base.bindFn( this._dropHandler, this );\n                this.dndOver = false;\n    \n                elem.on( 'dragenter', this.dragEnterHandler );\n                elem.on( 'dragover', this.dragOverHandler );\n                elem.on( 'dragleave', this.dragLeaveHandler );\n                elem.on( 'drop', this.dropHandler );\n    \n                if ( this.options.disableGlobalDnd ) {\n                    $( document ).on( 'dragover', this.dragOverHandler );\n                    $( document ).on( 'drop', this.dropHandler );\n                }\n            },\n    \n            _dragEnterHandler: function( e ) {\n                var me = this,\n                    denied = me._denied || false,\n                    items;\n    \n                e = e.originalEvent || e;\n    \n                if ( !me.dndOver ) {\n                    me.dndOver = true;\n    \n                    // 注意只有 chrome 支持。\n                    items = e.dataTransfer.items;\n    \n                    if ( items && items.length ) {\n                        me._denied = denied = !me.trigger( 'accept', items );\n                    }\n    \n                    me.elem.addClass( prefix + 'over' );\n                    me.elem[ denied ? 'addClass' :\n                            'removeClass' ]( prefix + 'denied' );\n                }\n    \n    \n                e.dataTransfer.dropEffect = denied ? 'none' : 'copy';\n    \n                return false;\n            },\n    \n            _dragOverHandler: function( e ) {\n                // 只处理框内的。\n                var parentElem = this.elem.parent().get( 0 );\n                if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) {\n                    return false;\n                }\n    \n                clearTimeout( this._leaveTimer );\n                this._dragEnterHandler.call( this, e );\n    \n                return false;\n            },\n    \n            _dragLeaveHandler: function() {\n                var me = this,\n                    handler;\n    \n                handler = function() {\n                    me.dndOver = false;\n                    me.elem.removeClass( prefix + 'over ' + prefix + 'denied' );\n                };\n    \n                clearTimeout( me._leaveTimer );\n                me._leaveTimer = setTimeout( handler, 100 );\n                return false;\n            },\n    \n            _dropHandler: function( e ) {\n                var me = this,\n                    ruid = me.getRuid(),\n                    parentElem = me.elem.parent().get( 0 );\n    \n                // 只处理框内的。\n                if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) {\n                    return false;\n                }\n    \n                me._getTansferFiles( e, function( results ) {\n                    me.trigger( 'drop', $.map( results, function( file ) {\n                        return new File( ruid, file );\n                    }) );\n                });\n    \n                me.dndOver = false;\n                me.elem.removeClass( prefix + 'over' );\n                return false;\n            },\n    \n            // 如果传入 callback 则去查看文件夹，否则只管当前文件夹。\n            _getTansferFiles: function( e, callback ) {\n                var results  = [],\n                    promises = [],\n                    items, files, dataTransfer, file, item, i, len, canAccessFolder;\n    \n                e = e.originalEvent || e;\n    \n                dataTransfer = e.dataTransfer;\n                items = dataTransfer.items;\n                files = dataTransfer.files;\n    \n                canAccessFolder = !!(items && items[ 0 ].webkitGetAsEntry);\n    \n                for ( i = 0, len = files.length; i < len; i++ ) {\n                    file = files[ i ];\n                    item = items && items[ i ];\n    \n                    if ( canAccessFolder && item.webkitGetAsEntry().isDirectory ) {\n    \n                        promises.push( this._traverseDirectoryTree(\n                                item.webkitGetAsEntry(), results ) );\n                    } else {\n                        results.push( file );\n                    }\n                }\n    \n                Base.when.apply( Base, promises ).done(function() {\n    \n                    if ( !results.length ) {\n                        return;\n                    }\n    \n                    callback( results );\n                });\n            },\n    \n            _traverseDirectoryTree: function( entry, results ) {\n                var deferred = Base.Deferred(),\n                    me = this;\n    \n                if ( entry.isFile ) {\n                    entry.file(function( file ) {\n                        results.push( file );\n                        deferred.resolve();\n                    });\n                } else if ( entry.isDirectory ) {\n                    entry.createReader().readEntries(function( entries ) {\n                        var len = entries.length,\n                            promises = [],\n                            arr = [],    // 为了保证顺序。\n                            i;\n    \n                        for ( i = 0; i < len; i++ ) {\n                            promises.push( me._traverseDirectoryTree(\n                                    entries[ i ], arr ) );\n                        }\n    \n                        Base.when.apply( Base, promises ).then(function() {\n                            results.push.apply( results, arr );\n                            deferred.resolve();\n                        }, deferred.reject );\n                    });\n                }\n    \n                return deferred.promise();\n            },\n    \n            destroy: function() {\n                var elem = this.elem;\n    \n                elem.off( 'dragenter', this.dragEnterHandler );\n                elem.off( 'dragover', this.dragEnterHandler );\n                elem.off( 'dragleave', this.dragLeaveHandler );\n                elem.off( 'drop', this.dropHandler );\n    \n                if ( this.options.disableGlobalDnd ) {\n                    $( document ).off( 'dragover', this.dragOverHandler );\n                    $( document ).off( 'drop', this.dropHandler );\n                }\n            }\n        });\n    });\n    \n    /**\n     * @fileOverview FilePaste\n     */\n    define('runtime/html5/filepaste',[\n        'base',\n        'runtime/html5/runtime',\n        'lib/file'\n    ], function( Base, Html5Runtime, File ) {\n    \n        return Html5Runtime.register( 'FilePaste', {\n            init: function() {\n                var opts = this.options,\n                    elem = this.elem = opts.container,\n                    accept = '.*',\n                    arr, i, len, item;\n    \n                // accetp的mimeTypes中生成匹配正则。\n                if ( opts.accept ) {\n                    arr = [];\n    \n                    for ( i = 0, len = opts.accept.length; i < len; i++ ) {\n                        item = opts.accept[ i ].mimeTypes;\n                        item && arr.push( item );\n                    }\n    \n                    if ( arr.length ) {\n                        accept = arr.join(',');\n                        accept = accept.replace( /,/g, '|' ).replace( /\\*/g, '.*' );\n                    }\n                }\n                this.accept = accept = new RegExp( accept, 'i' );\n                this.hander = Base.bindFn( this._pasteHander, this );\n                elem.on( 'paste', this.hander );\n            },\n    \n            _pasteHander: function( e ) {\n                var allowed = [],\n                    ruid = this.getRuid(),\n                    items, item, blob, i, len;\n    \n                e = e.originalEvent || e;\n                items = e.clipboardData.items;\n    \n                for ( i = 0, len = items.length; i < len; i++ ) {\n                    item = items[ i ];\n    \n                    if ( item.kind !== 'file' || !(blob = item.getAsFile()) ) {\n                        continue;\n                    }\n    \n                    allowed.push( new File( ruid, blob ) );\n                }\n    \n                if ( allowed.length ) {\n                    // 不阻止非文件粘贴（文字粘贴）的事件冒泡\n                    e.preventDefault();\n                    e.stopPropagation();\n                    this.trigger( 'paste', allowed );\n                }\n            },\n    \n            destroy: function() {\n                this.elem.off( 'paste', this.hander );\n            }\n        });\n    });\n    \n    /**\n     * @fileOverview FilePicker\n     */\n    define('runtime/html5/filepicker',[\n        'base',\n        'runtime/html5/runtime'\n    ], function( Base, Html5Runtime ) {\n    \n        var $ = Base.$;\n    \n        return Html5Runtime.register( 'FilePicker', {\n            init: function() {\n                var container = this.getRuntime().getContainer(),\n                    me = this,\n                    owner = me.owner,\n                    opts = me.options,\n                    lable = $( document.createElement('label') ),\n                    input = $( document.createElement('input') ),\n                    arr, i, len, mouseHandler;\n    \n                input.attr( 'type', 'file' );\n                input.attr( 'name', opts.name );\n                input.addClass('webuploader-element-invisible');\n    \n                lable.on( 'click', function() {\n                    input.trigger('click');\n                });\n    \n                lable.css({\n                    opacity: 0,\n                    width: '100%',\n                    height: '100%',\n                    display: 'block',\n                    cursor: 'pointer',\n                    background: '#ffffff'\n                });\n    \n                if ( opts.multiple ) {\n                    input.attr( 'multiple', 'multiple' );\n                }\n    \n                // @todo Firefox不支持单独指定后缀\n                if ( opts.accept && opts.accept.length > 0 ) {\n                    arr = [];\n    \n                    for ( i = 0, len = opts.accept.length; i < len; i++ ) {\n                        arr.push( opts.accept[ i ].mimeTypes );\n                    }\n    \n                    input.attr( 'accept', arr.join(',') );\n                }\n    \n                container.append( input );\n                container.append( lable );\n    \n                mouseHandler = function( e ) {\n                    owner.trigger( e.type );\n                };\n    \n                input.on( 'change', function( e ) {\n                    var fn = arguments.callee,\n                        clone;\n    \n                    me.files = e.target.files;\n    \n                    // reset input\n                    clone = this.cloneNode( true );\n                    this.parentNode.replaceChild( clone, this );\n    \n                    input.off();\n                    input = $( clone ).on( 'change', fn )\n                            .on( 'mouseenter mouseleave', mouseHandler );\n    \n                    owner.trigger('change');\n                });\n    \n                lable.on( 'mouseenter mouseleave', mouseHandler );\n    \n            },\n    \n    \n            getFiles: function() {\n                return this.files;\n            },\n    \n            destroy: function() {\n                // todo\n            }\n        });\n    });\n    /**\n     * Terms:\n     *\n     * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer\n     * @fileOverview Image控件\n     */\n    define('runtime/html5/util',[\n        'base'\n    ], function( Base ) {\n    \n        var urlAPI = window.createObjectURL && window ||\n                window.URL && URL.revokeObjectURL && URL ||\n                window.webkitURL,\n            createObjectURL = Base.noop,\n            revokeObjectURL = createObjectURL;\n    \n        if ( urlAPI ) {\n    \n            // 更安全的方式调用，比如android里面就能把context改成其他的对象。\n            createObjectURL = function() {\n                return urlAPI.createObjectURL.apply( urlAPI, arguments );\n            };\n    \n            revokeObjectURL = function() {\n                return urlAPI.revokeObjectURL.apply( urlAPI, arguments );\n            };\n        }\n    \n        return {\n            createObjectURL: createObjectURL,\n            revokeObjectURL: revokeObjectURL,\n    \n            dataURL2Blob: function( dataURI ) {\n                var byteStr, intArray, ab, i, mimetype, parts;\n    \n                parts = dataURI.split(',');\n    \n                if ( ~parts[ 0 ].indexOf('base64') ) {\n                    byteStr = atob( parts[ 1 ] );\n                } else {\n                    byteStr = decodeURIComponent( parts[ 1 ] );\n                }\n    \n                ab = new ArrayBuffer( byteStr.length );\n                intArray = new Uint8Array( ab );\n    \n                for ( i = 0; i < byteStr.length; i++ ) {\n                    intArray[ i ] = byteStr.charCodeAt( i );\n                }\n    \n                mimetype = parts[ 0 ].split(':')[ 1 ].split(';')[ 0 ];\n    \n                return this.arrayBufferToBlob( ab, mimetype );\n            },\n    \n            dataURL2ArrayBuffer: function( dataURI ) {\n                var byteStr, intArray, i, parts;\n    \n                parts = dataURI.split(',');\n    \n                if ( ~parts[ 0 ].indexOf('base64') ) {\n                    byteStr = atob( parts[ 1 ] );\n                } else {\n                    byteStr = decodeURIComponent( parts[ 1 ] );\n                }\n    \n                intArray = new Uint8Array( byteStr.length );\n    \n                for ( i = 0; i < byteStr.length; i++ ) {\n                    intArray[ i ] = byteStr.charCodeAt( i );\n                }\n    \n                return intArray.buffer;\n            },\n    \n            arrayBufferToBlob: function( buffer, type ) {\n                var builder = window.BlobBuilder || window.WebKitBlobBuilder,\n                    bb;\n    \n                // android不支持直接new Blob, 只能借助blobbuilder.\n                if ( builder ) {\n                    bb = new builder();\n                    bb.append( buffer );\n                    return bb.getBlob( type );\n                }\n    \n                return new Blob([ buffer ], type ? { type: type } : {} );\n            },\n    \n            // 抽出来主要是为了解决android下面canvas.toDataUrl不支持jpeg.\n            // 你得到的结果是png.\n            canvasToDataUrl: function( canvas, type, quality ) {\n                return canvas.toDataURL( type, quality / 100 );\n            },\n    \n            // imagemeat会复写这个方法，如果用户选择加载那个文件了的话。\n            parseMeta: function( blob, callback ) {\n                callback( false, {});\n            },\n    \n            // imagemeat会复写这个方法，如果用户选择加载那个文件了的话。\n            updateImageHead: function( data ) {\n                return data;\n            }\n        };\n    });\n    /**\n     * Terms:\n     *\n     * Uint8Array, FileReader, BlobBuilder, atob, ArrayBuffer\n     * @fileOverview Image控件\n     */\n    define('runtime/html5/imagemeta',[\n        'runtime/html5/util'\n    ], function( Util ) {\n    \n        var api;\n    \n        api = {\n            parsers: {\n                0xffe1: []\n            },\n    \n            maxMetaDataSize: 262144,\n    \n            parse: function( blob, cb ) {\n                var me = this,\n                    fr = new FileReader();\n    \n                fr.onload = function() {\n                    cb( false, me._parse( this.result ) );\n                    fr = fr.onload = fr.onerror = null;\n                };\n    \n                fr.onerror = function( e ) {\n                    cb( e.message );\n                    fr = fr.onload = fr.onerror = null;\n                };\n    \n                blob = blob.slice( 0, me.maxMetaDataSize );\n                fr.readAsArrayBuffer( blob.getSource() );\n            },\n    \n            _parse: function( buffer, noParse ) {\n                if ( buffer.byteLength < 6 ) {\n                    return;\n                }\n    \n                var dataview = new DataView( buffer ),\n                    offset = 2,\n                    maxOffset = dataview.byteLength - 4,\n                    headLength = offset,\n                    ret = {},\n                    markerBytes, markerLength, parsers, i;\n    \n                if ( dataview.getUint16( 0 ) === 0xffd8 ) {\n    \n                    while ( offset < maxOffset ) {\n                        markerBytes = dataview.getUint16( offset );\n    \n                        if ( markerBytes >= 0xffe0 && markerBytes <= 0xffef ||\n                                markerBytes === 0xfffe ) {\n    \n                            markerLength = dataview.getUint16( offset + 2 ) + 2;\n    \n                            if ( offset + markerLength > dataview.byteLength ) {\n                                break;\n                            }\n    \n                            parsers = api.parsers[ markerBytes ];\n    \n                            if ( !noParse && parsers ) {\n                                for ( i = 0; i < parsers.length; i += 1 ) {\n                                    parsers[ i ].call( api, dataview, offset,\n                                            markerLength, ret );\n                                }\n                            }\n    \n                            offset += markerLength;\n                            headLength = offset;\n                        } else {\n                            break;\n                        }\n                    }\n    \n                    if ( headLength > 6 ) {\n                        if ( buffer.slice ) {\n                            ret.imageHead = buffer.slice( 2, headLength );\n                        } else {\n                            // Workaround for IE10, which does not yet\n                            // support ArrayBuffer.slice:\n                            ret.imageHead = new Uint8Array( buffer )\n                                    .subarray( 2, headLength );\n                        }\n                    }\n                }\n    \n                return ret;\n            },\n    \n            updateImageHead: function( buffer, head ) {\n                var data = this._parse( buffer, true ),\n                    buf1, buf2, bodyoffset;\n    \n    \n                bodyoffset = 2;\n                if ( data.imageHead ) {\n                    bodyoffset = 2 + data.imageHead.byteLength;\n                }\n    \n                if ( buffer.slice ) {\n                    buf2 = buffer.slice( bodyoffset );\n                } else {\n                    buf2 = new Uint8Array( buffer ).subarray( bodyoffset );\n                }\n    \n                buf1 = new Uint8Array( head.byteLength + 2 + buf2.byteLength );\n    \n                buf1[ 0 ] = 0xFF;\n                buf1[ 1 ] = 0xD8;\n                buf1.set( new Uint8Array( head ), 2 );\n                buf1.set( new Uint8Array( buf2 ), head.byteLength + 2 );\n    \n                return buf1.buffer;\n            }\n        };\n    \n        Util.parseMeta = function() {\n            return api.parse.apply( api, arguments );\n        };\n    \n        Util.updateImageHead = function() {\n            return api.updateImageHead.apply( api, arguments );\n        };\n    \n        return api;\n    });\n    /**\n     * 代码来自于：https://github.com/blueimp/JavaScript-Load-Image\n     * 暂时项目中只用了orientation.\n     *\n     * 去除了 Exif Sub IFD Pointer, GPS Info IFD Pointer, Exif Thumbnail.\n     * @fileOverview EXIF解析\n     */\n    \n    // Sample\n    // ====================================\n    // Make : Apple\n    // Model : iPhone 4S\n    // Orientation : 1\n    // XResolution : 72 [72/1]\n    // YResolution : 72 [72/1]\n    // ResolutionUnit : 2\n    // Software : QuickTime 7.7.1\n    // DateTime : 2013:09:01 22:53:55\n    // ExifIFDPointer : 190\n    // ExposureTime : 0.058823529411764705 [1/17]\n    // FNumber : 2.4 [12/5]\n    // ExposureProgram : Normal program\n    // ISOSpeedRatings : 800\n    // ExifVersion : 0220\n    // DateTimeOriginal : 2013:09:01 22:52:51\n    // DateTimeDigitized : 2013:09:01 22:52:51\n    // ComponentsConfiguration : YCbCr\n    // ShutterSpeedValue : 4.058893515764426\n    // ApertureValue : 2.5260688216892597 [4845/1918]\n    // BrightnessValue : -0.3126686601998395\n    // MeteringMode : Pattern\n    // Flash : Flash did not fire, compulsory flash mode\n    // FocalLength : 4.28 [107/25]\n    // SubjectArea : [4 values]\n    // FlashpixVersion : 0100\n    // ColorSpace : 1\n    // PixelXDimension : 2448\n    // PixelYDimension : 3264\n    // SensingMethod : One-chip color area sensor\n    // ExposureMode : 0\n    // WhiteBalance : Auto white balance\n    // FocalLengthIn35mmFilm : 35\n    // SceneCaptureType : Standard\n    define('runtime/html5/imagemeta/exif',[\n        'base',\n        'runtime/html5/imagemeta'\n    ], function( Base, ImageMeta ) {\n    \n        var EXIF = {};\n    \n        EXIF.ExifMap = function() {\n            return this;\n        };\n    \n        EXIF.ExifMap.prototype.map = {\n            'Orientation': 0x0112\n        };\n    \n        EXIF.ExifMap.prototype.get = function( id ) {\n            return this[ id ] || this[ this.map[ id ] ];\n        };\n    \n        EXIF.exifTagTypes = {\n            // byte, 8-bit unsigned int:\n            1: {\n                getValue: function( dataView, dataOffset ) {\n                    return dataView.getUint8( dataOffset );\n                },\n                size: 1\n            },\n    \n            // ascii, 8-bit byte:\n            2: {\n                getValue: function( dataView, dataOffset ) {\n                    return String.fromCharCode( dataView.getUint8( dataOffset ) );\n                },\n                size: 1,\n                ascii: true\n            },\n    \n            // short, 16 bit int:\n            3: {\n                getValue: function( dataView, dataOffset, littleEndian ) {\n                    return dataView.getUint16( dataOffset, littleEndian );\n                },\n                size: 2\n            },\n    \n            // long, 32 bit int:\n            4: {\n                getValue: function( dataView, dataOffset, littleEndian ) {\n                    return dataView.getUint32( dataOffset, littleEndian );\n                },\n                size: 4\n            },\n    \n            // rational = two long values,\n            // first is numerator, second is denominator:\n            5: {\n                getValue: function( dataView, dataOffset, littleEndian ) {\n                    return dataView.getUint32( dataOffset, littleEndian ) /\n                        dataView.getUint32( dataOffset + 4, littleEndian );\n                },\n                size: 8\n            },\n    \n            // slong, 32 bit signed int:\n            9: {\n                getValue: function( dataView, dataOffset, littleEndian ) {\n                    return dataView.getInt32( dataOffset, littleEndian );\n                },\n                size: 4\n            },\n    \n            // srational, two slongs, first is numerator, second is denominator:\n            10: {\n                getValue: function( dataView, dataOffset, littleEndian ) {\n                    return dataView.getInt32( dataOffset, littleEndian ) /\n                        dataView.getInt32( dataOffset + 4, littleEndian );\n                },\n                size: 8\n            }\n        };\n    \n        // undefined, 8-bit byte, value depending on field:\n        EXIF.exifTagTypes[ 7 ] = EXIF.exifTagTypes[ 1 ];\n    \n        EXIF.getExifValue = function( dataView, tiffOffset, offset, type, length,\n                littleEndian ) {\n    \n            var tagType = EXIF.exifTagTypes[ type ],\n                tagSize, dataOffset, values, i, str, c;\n    \n            if ( !tagType ) {\n                Base.log('Invalid Exif data: Invalid tag type.');\n                return;\n            }\n    \n            tagSize = tagType.size * length;\n    \n            // Determine if the value is contained in the dataOffset bytes,\n            // or if the value at the dataOffset is a pointer to the actual data:\n            dataOffset = tagSize > 4 ? tiffOffset + dataView.getUint32( offset + 8,\n                    littleEndian ) : (offset + 8);\n    \n            if ( dataOffset + tagSize > dataView.byteLength ) {\n                Base.log('Invalid Exif data: Invalid data offset.');\n                return;\n            }\n    \n            if ( length === 1 ) {\n                return tagType.getValue( dataView, dataOffset, littleEndian );\n            }\n    \n            values = [];\n    \n            for ( i = 0; i < length; i += 1 ) {\n                values[ i ] = tagType.getValue( dataView,\n                        dataOffset + i * tagType.size, littleEndian );\n            }\n    \n            if ( tagType.ascii ) {\n                str = '';\n    \n                // Concatenate the chars:\n                for ( i = 0; i < values.length; i += 1 ) {\n                    c = values[ i ];\n    \n                    // Ignore the terminating NULL byte(s):\n                    if ( c === '\\u0000' ) {\n                        break;\n                    }\n                    str += c;\n                }\n    \n                return str;\n            }\n            return values;\n        };\n    \n        EXIF.parseExifTag = function( dataView, tiffOffset, offset, littleEndian,\n                data ) {\n    \n            var tag = dataView.getUint16( offset, littleEndian );\n            data.exif[ tag ] = EXIF.getExifValue( dataView, tiffOffset, offset,\n                    dataView.getUint16( offset + 2, littleEndian ),    // tag type\n                    dataView.getUint32( offset + 4, littleEndian ),    // tag length\n                    littleEndian );\n        };\n    \n        EXIF.parseExifTags = function( dataView, tiffOffset, dirOffset,\n                littleEndian, data ) {\n    \n            var tagsNumber, dirEndOffset, i;\n    \n            if ( dirOffset + 6 > dataView.byteLength ) {\n                Base.log('Invalid Exif data: Invalid directory offset.');\n                return;\n            }\n    \n            tagsNumber = dataView.getUint16( dirOffset, littleEndian );\n            dirEndOffset = dirOffset + 2 + 12 * tagsNumber;\n    \n            if ( dirEndOffset + 4 > dataView.byteLength ) {\n                Base.log('Invalid Exif data: Invalid directory size.');\n                return;\n            }\n    \n            for ( i = 0; i < tagsNumber; i += 1 ) {\n                this.parseExifTag( dataView, tiffOffset,\n                        dirOffset + 2 + 12 * i,    // tag offset\n                        littleEndian, data );\n            }\n    \n            // Return the offset to the next directory:\n            return dataView.getUint32( dirEndOffset, littleEndian );\n        };\n    \n        // EXIF.getExifThumbnail = function(dataView, offset, length) {\n        //     var hexData,\n        //         i,\n        //         b;\n        //     if (!length || offset + length > dataView.byteLength) {\n        //         Base.log('Invalid Exif data: Invalid thumbnail data.');\n        //         return;\n        //     }\n        //     hexData = [];\n        //     for (i = 0; i < length; i += 1) {\n        //         b = dataView.getUint8(offset + i);\n        //         hexData.push((b < 16 ? '0' : '') + b.toString(16));\n        //     }\n        //     return 'data:image/jpeg,%' + hexData.join('%');\n        // };\n    \n        EXIF.parseExifData = function( dataView, offset, length, data ) {\n    \n            var tiffOffset = offset + 10,\n                littleEndian, dirOffset;\n    \n            // Check for the ASCII code for \"Exif\" (0x45786966):\n            if ( dataView.getUint32( offset + 4 ) !== 0x45786966 ) {\n                // No Exif data, might be XMP data instead\n                return;\n            }\n            if ( tiffOffset + 8 > dataView.byteLength ) {\n                Base.log('Invalid Exif data: Invalid segment size.');\n                return;\n            }\n    \n            // Check for the two null bytes:\n            if ( dataView.getUint16( offset + 8 ) !== 0x0000 ) {\n                Base.log('Invalid Exif data: Missing byte alignment offset.');\n                return;\n            }\n    \n            // Check the byte alignment:\n            switch ( dataView.getUint16( tiffOffset ) ) {\n                case 0x4949:\n                    littleEndian = true;\n                    break;\n    \n                case 0x4D4D:\n                    littleEndian = false;\n                    break;\n    \n                default:\n                    Base.log('Invalid Exif data: Invalid byte alignment marker.');\n                    return;\n            }\n    \n            // Check for the TIFF tag marker (0x002A):\n            if ( dataView.getUint16( tiffOffset + 2, littleEndian ) !== 0x002A ) {\n                Base.log('Invalid Exif data: Missing TIFF marker.');\n                return;\n            }\n    \n            // Retrieve the directory offset bytes, usually 0x00000008 or 8 decimal:\n            dirOffset = dataView.getUint32( tiffOffset + 4, littleEndian );\n            // Create the exif object to store the tags:\n            data.exif = new EXIF.ExifMap();\n            // Parse the tags of the main image directory and retrieve the\n            // offset to the next directory, usually the thumbnail directory:\n            dirOffset = EXIF.parseExifTags( dataView, tiffOffset,\n                    tiffOffset + dirOffset, littleEndian, data );\n    \n            // 尝试读取缩略图\n            // if ( dirOffset ) {\n            //     thumbnailData = {exif: {}};\n            //     dirOffset = EXIF.parseExifTags(\n            //         dataView,\n            //         tiffOffset,\n            //         tiffOffset + dirOffset,\n            //         littleEndian,\n            //         thumbnailData\n            //     );\n    \n            //     // Check for JPEG Thumbnail offset:\n            //     if (thumbnailData.exif[0x0201]) {\n            //         data.exif.Thumbnail = EXIF.getExifThumbnail(\n            //             dataView,\n            //             tiffOffset + thumbnailData.exif[0x0201],\n            //             thumbnailData.exif[0x0202] // Thumbnail data length\n            //         );\n            //     }\n            // }\n        };\n    \n        ImageMeta.parsers[ 0xffe1 ].push( EXIF.parseExifData );\n        return EXIF;\n    });\n    /**\n     * 这个方式性能不行，但是可以解决android里面的toDataUrl的bug\n     * android里面toDataUrl('image/jpege')得到的结果却是png.\n     *\n     * 所以这里没辙，只能借助这个工具\n     * @fileOverview jpeg encoder\n     */\n    define('runtime/html5/jpegencoder',[], function( require, exports, module ) {\n    \n        /*\n          Copyright (c) 2008, Adobe Systems Incorporated\n          All rights reserved.\n    \n          Redistribution and use in source and binary forms, with or without\n          modification, are permitted provided that the following conditions are\n          met:\n    \n          * Redistributions of source code must retain the above copyright notice,\n            this list of conditions and the following disclaimer.\n    \n          * Redistributions in binary form must reproduce the above copyright\n            notice, this list of conditions and the following disclaimer in the\n            documentation and/or other materials provided with the distribution.\n    \n          * Neither the name of Adobe Systems Incorporated nor the names of its\n            contributors may be used to endorse or promote products derived from\n            this software without specific prior written permission.\n    \n          THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS\n          IS\" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,\n          THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\n          PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR\n          CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,\n          EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,\n          PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR\n          PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF\n          LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING\n          NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\n          SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n        */\n        /*\n        JPEG encoder ported to JavaScript and optimized by Andreas Ritter, www.bytestrom.eu, 11/2009\n    \n        Basic GUI blocking jpeg encoder\n        */\n    \n        function JPEGEncoder(quality) {\n          var self = this;\n            var fround = Math.round;\n            var ffloor = Math.floor;\n            var YTable = new Array(64);\n            var UVTable = new Array(64);\n            var fdtbl_Y = new Array(64);\n            var fdtbl_UV = new Array(64);\n            var YDC_HT;\n            var UVDC_HT;\n            var YAC_HT;\n            var UVAC_HT;\n    \n            var bitcode = new Array(65535);\n            var category = new Array(65535);\n            var outputfDCTQuant = new Array(64);\n            var DU = new Array(64);\n            var byteout = [];\n            var bytenew = 0;\n            var bytepos = 7;\n    \n            var YDU = new Array(64);\n            var UDU = new Array(64);\n            var VDU = new Array(64);\n            var clt = new Array(256);\n            var RGB_YUV_TABLE = new Array(2048);\n            var currentQuality;\n    \n            var ZigZag = [\n                     0, 1, 5, 6,14,15,27,28,\n                     2, 4, 7,13,16,26,29,42,\n                     3, 8,12,17,25,30,41,43,\n                     9,11,18,24,31,40,44,53,\n                    10,19,23,32,39,45,52,54,\n                    20,22,33,38,46,51,55,60,\n                    21,34,37,47,50,56,59,61,\n                    35,36,48,49,57,58,62,63\n                ];\n    \n            var std_dc_luminance_nrcodes = [0,0,1,5,1,1,1,1,1,1,0,0,0,0,0,0,0];\n            var std_dc_luminance_values = [0,1,2,3,4,5,6,7,8,9,10,11];\n            var std_ac_luminance_nrcodes = [0,0,2,1,3,3,2,4,3,5,5,4,4,0,0,1,0x7d];\n            var std_ac_luminance_values = [\n                    0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12,\n                    0x21,0x31,0x41,0x06,0x13,0x51,0x61,0x07,\n                    0x22,0x71,0x14,0x32,0x81,0x91,0xa1,0x08,\n                    0x23,0x42,0xb1,0xc1,0x15,0x52,0xd1,0xf0,\n                    0x24,0x33,0x62,0x72,0x82,0x09,0x0a,0x16,\n                    0x17,0x18,0x19,0x1a,0x25,0x26,0x27,0x28,\n                    0x29,0x2a,0x34,0x35,0x36,0x37,0x38,0x39,\n                    0x3a,0x43,0x44,0x45,0x46,0x47,0x48,0x49,\n                    0x4a,0x53,0x54,0x55,0x56,0x57,0x58,0x59,\n                    0x5a,0x63,0x64,0x65,0x66,0x67,0x68,0x69,\n                    0x6a,0x73,0x74,0x75,0x76,0x77,0x78,0x79,\n                    0x7a,0x83,0x84,0x85,0x86,0x87,0x88,0x89,\n                    0x8a,0x92,0x93,0x94,0x95,0x96,0x97,0x98,\n                    0x99,0x9a,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,\n                    0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,0xb5,0xb6,\n                    0xb7,0xb8,0xb9,0xba,0xc2,0xc3,0xc4,0xc5,\n                    0xc6,0xc7,0xc8,0xc9,0xca,0xd2,0xd3,0xd4,\n                    0xd5,0xd6,0xd7,0xd8,0xd9,0xda,0xe1,0xe2,\n                    0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xea,\n                    0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,\n                    0xf9,0xfa\n                ];\n    \n            var std_dc_chrominance_nrcodes = [0,0,3,1,1,1,1,1,1,1,1,1,0,0,0,0,0];\n            var std_dc_chrominance_values = [0,1,2,3,4,5,6,7,8,9,10,11];\n            var std_ac_chrominance_nrcodes = [0,0,2,1,2,4,4,3,4,7,5,4,4,0,1,2,0x77];\n            var std_ac_chrominance_values = [\n                    0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21,\n                    0x31,0x06,0x12,0x41,0x51,0x07,0x61,0x71,\n                    0x13,0x22,0x32,0x81,0x08,0x14,0x42,0x91,\n                    0xa1,0xb1,0xc1,0x09,0x23,0x33,0x52,0xf0,\n                    0x15,0x62,0x72,0xd1,0x0a,0x16,0x24,0x34,\n                    0xe1,0x25,0xf1,0x17,0x18,0x19,0x1a,0x26,\n                    0x27,0x28,0x29,0x2a,0x35,0x36,0x37,0x38,\n                    0x39,0x3a,0x43,0x44,0x45,0x46,0x47,0x48,\n                    0x49,0x4a,0x53,0x54,0x55,0x56,0x57,0x58,\n                    0x59,0x5a,0x63,0x64,0x65,0x66,0x67,0x68,\n                    0x69,0x6a,0x73,0x74,0x75,0x76,0x77,0x78,\n                    0x79,0x7a,0x82,0x83,0x84,0x85,0x86,0x87,\n                    0x88,0x89,0x8a,0x92,0x93,0x94,0x95,0x96,\n                    0x97,0x98,0x99,0x9a,0xa2,0xa3,0xa4,0xa5,\n                    0xa6,0xa7,0xa8,0xa9,0xaa,0xb2,0xb3,0xb4,\n                    0xb5,0xb6,0xb7,0xb8,0xb9,0xba,0xc2,0xc3,\n                    0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xca,0xd2,\n                    0xd3,0xd4,0xd5,0xd6,0xd7,0xd8,0xd9,0xda,\n                    0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,\n                    0xea,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,\n                    0xf9,0xfa\n                ];\n    \n            function initQuantTables(sf){\n                    var YQT = [\n                        16, 11, 10, 16, 24, 40, 51, 61,\n                        12, 12, 14, 19, 26, 58, 60, 55,\n                        14, 13, 16, 24, 40, 57, 69, 56,\n                        14, 17, 22, 29, 51, 87, 80, 62,\n                        18, 22, 37, 56, 68,109,103, 77,\n                        24, 35, 55, 64, 81,104,113, 92,\n                        49, 64, 78, 87,103,121,120,101,\n                        72, 92, 95, 98,112,100,103, 99\n                    ];\n    \n                    for (var i = 0; i < 64; i++) {\n                        var t = ffloor((YQT[i]*sf+50)/100);\n                        if (t < 1) {\n                            t = 1;\n                        } else if (t > 255) {\n                            t = 255;\n                        }\n                        YTable[ZigZag[i]] = t;\n                    }\n                    var UVQT = [\n                        17, 18, 24, 47, 99, 99, 99, 99,\n                        18, 21, 26, 66, 99, 99, 99, 99,\n                        24, 26, 56, 99, 99, 99, 99, 99,\n                        47, 66, 99, 99, 99, 99, 99, 99,\n                        99, 99, 99, 99, 99, 99, 99, 99,\n                        99, 99, 99, 99, 99, 99, 99, 99,\n                        99, 99, 99, 99, 99, 99, 99, 99,\n                        99, 99, 99, 99, 99, 99, 99, 99\n                    ];\n                    for (var j = 0; j < 64; j++) {\n                        var u = ffloor((UVQT[j]*sf+50)/100);\n                        if (u < 1) {\n                            u = 1;\n                        } else if (u > 255) {\n                            u = 255;\n                        }\n                        UVTable[ZigZag[j]] = u;\n                    }\n                    var aasf = [\n                        1.0, 1.387039845, 1.306562965, 1.175875602,\n                        1.0, 0.785694958, 0.541196100, 0.275899379\n                    ];\n                    var k = 0;\n                    for (var row = 0; row < 8; row++)\n                    {\n                        for (var col = 0; col < 8; col++)\n                        {\n                            fdtbl_Y[k]  = (1.0 / (YTable [ZigZag[k]] * aasf[row] * aasf[col] * 8.0));\n                            fdtbl_UV[k] = (1.0 / (UVTable[ZigZag[k]] * aasf[row] * aasf[col] * 8.0));\n                            k++;\n                        }\n                    }\n                }\n    \n                function computeHuffmanTbl(nrcodes, std_table){\n                    var codevalue = 0;\n                    var pos_in_table = 0;\n                    var HT = new Array();\n                    for (var k = 1; k <= 16; k++) {\n                        for (var j = 1; j <= nrcodes[k]; j++) {\n                            HT[std_table[pos_in_table]] = [];\n                            HT[std_table[pos_in_table]][0] = codevalue;\n                            HT[std_table[pos_in_table]][1] = k;\n                            pos_in_table++;\n                            codevalue++;\n                        }\n                        codevalue*=2;\n                    }\n                    return HT;\n                }\n    \n                function initHuffmanTbl()\n                {\n                    YDC_HT = computeHuffmanTbl(std_dc_luminance_nrcodes,std_dc_luminance_values);\n                    UVDC_HT = computeHuffmanTbl(std_dc_chrominance_nrcodes,std_dc_chrominance_values);\n                    YAC_HT = computeHuffmanTbl(std_ac_luminance_nrcodes,std_ac_luminance_values);\n                    UVAC_HT = computeHuffmanTbl(std_ac_chrominance_nrcodes,std_ac_chrominance_values);\n                }\n    \n                function initCategoryNumber()\n                {\n                    var nrlower = 1;\n                    var nrupper = 2;\n                    for (var cat = 1; cat <= 15; cat++) {\n                        //Positive numbers\n                        for (var nr = nrlower; nr<nrupper; nr++) {\n                            category[32767+nr] = cat;\n                            bitcode[32767+nr] = [];\n                            bitcode[32767+nr][1] = cat;\n                            bitcode[32767+nr][0] = nr;\n                        }\n                        //Negative numbers\n                        for (var nrneg =-(nrupper-1); nrneg<=-nrlower; nrneg++) {\n                            category[32767+nrneg] = cat;\n                            bitcode[32767+nrneg] = [];\n                            bitcode[32767+nrneg][1] = cat;\n                            bitcode[32767+nrneg][0] = nrupper-1+nrneg;\n                        }\n                        nrlower <<= 1;\n                        nrupper <<= 1;\n                    }\n                }\n    \n                function initRGBYUVTable() {\n                    for(var i = 0; i < 256;i++) {\n                        RGB_YUV_TABLE[i]            =  19595 * i;\n                        RGB_YUV_TABLE[(i+ 256)>>0]  =  38470 * i;\n                        RGB_YUV_TABLE[(i+ 512)>>0]  =   7471 * i + 0x8000;\n                        RGB_YUV_TABLE[(i+ 768)>>0]  = -11059 * i;\n                        RGB_YUV_TABLE[(i+1024)>>0]  = -21709 * i;\n                        RGB_YUV_TABLE[(i+1280)>>0]  =  32768 * i + 0x807FFF;\n                        RGB_YUV_TABLE[(i+1536)>>0]  = -27439 * i;\n                        RGB_YUV_TABLE[(i+1792)>>0]  = - 5329 * i;\n                    }\n                }\n    \n                // IO functions\n                function writeBits(bs)\n                {\n                    var value = bs[0];\n                    var posval = bs[1]-1;\n                    while ( posval >= 0 ) {\n                        if (value & (1 << posval) ) {\n                            bytenew |= (1 << bytepos);\n                        }\n                        posval--;\n                        bytepos--;\n                        if (bytepos < 0) {\n                            if (bytenew == 0xFF) {\n                                writeByte(0xFF);\n                                writeByte(0);\n                            }\n                            else {\n                                writeByte(bytenew);\n                            }\n                            bytepos=7;\n                            bytenew=0;\n                        }\n                    }\n                }\n    \n                function writeByte(value)\n                {\n                    byteout.push(clt[value]); // write char directly instead of converting later\n                }\n    \n                function writeWord(value)\n                {\n                    writeByte((value>>8)&0xFF);\n                    writeByte((value   )&0xFF);\n                }\n    \n                // DCT & quantization core\n                function fDCTQuant(data, fdtbl)\n                {\n                    var d0, d1, d2, d3, d4, d5, d6, d7;\n                    /* Pass 1: process rows. */\n                    var dataOff=0;\n                    var i;\n                    var I8 = 8;\n                    var I64 = 64;\n                    for (i=0; i<I8; ++i)\n                    {\n                        d0 = data[dataOff];\n                        d1 = data[dataOff+1];\n                        d2 = data[dataOff+2];\n                        d3 = data[dataOff+3];\n                        d4 = data[dataOff+4];\n                        d5 = data[dataOff+5];\n                        d6 = data[dataOff+6];\n                        d7 = data[dataOff+7];\n    \n                        var tmp0 = d0 + d7;\n                        var tmp7 = d0 - d7;\n                        var tmp1 = d1 + d6;\n                        var tmp6 = d1 - d6;\n                        var tmp2 = d2 + d5;\n                        var tmp5 = d2 - d5;\n                        var tmp3 = d3 + d4;\n                        var tmp4 = d3 - d4;\n    \n                        /* Even part */\n                        var tmp10 = tmp0 + tmp3;    /* phase 2 */\n                        var tmp13 = tmp0 - tmp3;\n                        var tmp11 = tmp1 + tmp2;\n                        var tmp12 = tmp1 - tmp2;\n    \n                        data[dataOff] = tmp10 + tmp11; /* phase 3 */\n                        data[dataOff+4] = tmp10 - tmp11;\n    \n                        var z1 = (tmp12 + tmp13) * 0.707106781; /* c4 */\n                        data[dataOff+2] = tmp13 + z1; /* phase 5 */\n                        data[dataOff+6] = tmp13 - z1;\n    \n                        /* Odd part */\n                        tmp10 = tmp4 + tmp5; /* phase 2 */\n                        tmp11 = tmp5 + tmp6;\n                        tmp12 = tmp6 + tmp7;\n    \n                        /* The rotator is modified from fig 4-8 to avoid extra negations. */\n                        var z5 = (tmp10 - tmp12) * 0.382683433; /* c6 */\n                        var z2 = 0.541196100 * tmp10 + z5; /* c2-c6 */\n                        var z4 = 1.306562965 * tmp12 + z5; /* c2+c6 */\n                        var z3 = tmp11 * 0.707106781; /* c4 */\n    \n                        var z11 = tmp7 + z3;    /* phase 5 */\n                        var z13 = tmp7 - z3;\n    \n                        data[dataOff+5] = z13 + z2; /* phase 6 */\n                        data[dataOff+3] = z13 - z2;\n                        data[dataOff+1] = z11 + z4;\n                        data[dataOff+7] = z11 - z4;\n    \n                        dataOff += 8; /* advance pointer to next row */\n                    }\n    \n                    /* Pass 2: process columns. */\n                    dataOff = 0;\n                    for (i=0; i<I8; ++i)\n                    {\n                        d0 = data[dataOff];\n                        d1 = data[dataOff + 8];\n                        d2 = data[dataOff + 16];\n                        d3 = data[dataOff + 24];\n                        d4 = data[dataOff + 32];\n                        d5 = data[dataOff + 40];\n                        d6 = data[dataOff + 48];\n                        d7 = data[dataOff + 56];\n    \n                        var tmp0p2 = d0 + d7;\n                        var tmp7p2 = d0 - d7;\n                        var tmp1p2 = d1 + d6;\n                        var tmp6p2 = d1 - d6;\n                        var tmp2p2 = d2 + d5;\n                        var tmp5p2 = d2 - d5;\n                        var tmp3p2 = d3 + d4;\n                        var tmp4p2 = d3 - d4;\n    \n                        /* Even part */\n                        var tmp10p2 = tmp0p2 + tmp3p2;  /* phase 2 */\n                        var tmp13p2 = tmp0p2 - tmp3p2;\n                        var tmp11p2 = tmp1p2 + tmp2p2;\n                        var tmp12p2 = tmp1p2 - tmp2p2;\n    \n                        data[dataOff] = tmp10p2 + tmp11p2; /* phase 3 */\n                        data[dataOff+32] = tmp10p2 - tmp11p2;\n    \n                        var z1p2 = (tmp12p2 + tmp13p2) * 0.707106781; /* c4 */\n                        data[dataOff+16] = tmp13p2 + z1p2; /* phase 5 */\n                        data[dataOff+48] = tmp13p2 - z1p2;\n    \n                        /* Odd part */\n                        tmp10p2 = tmp4p2 + tmp5p2; /* phase 2 */\n                        tmp11p2 = tmp5p2 + tmp6p2;\n                        tmp12p2 = tmp6p2 + tmp7p2;\n    \n                        /* The rotator is modified from fig 4-8 to avoid extra negations. */\n                        var z5p2 = (tmp10p2 - tmp12p2) * 0.382683433; /* c6 */\n                        var z2p2 = 0.541196100 * tmp10p2 + z5p2; /* c2-c6 */\n                        var z4p2 = 1.306562965 * tmp12p2 + z5p2; /* c2+c6 */\n                        var z3p2 = tmp11p2 * 0.707106781; /* c4 */\n    \n                        var z11p2 = tmp7p2 + z3p2;  /* phase 5 */\n                        var z13p2 = tmp7p2 - z3p2;\n    \n                        data[dataOff+40] = z13p2 + z2p2; /* phase 6 */\n                        data[dataOff+24] = z13p2 - z2p2;\n                        data[dataOff+ 8] = z11p2 + z4p2;\n                        data[dataOff+56] = z11p2 - z4p2;\n    \n                        dataOff++; /* advance pointer to next column */\n                    }\n    \n                    // Quantize/descale the coefficients\n                    var fDCTQuant;\n                    for (i=0; i<I64; ++i)\n                    {\n                        // Apply the quantization and scaling factor & Round to nearest integer\n                        fDCTQuant = data[i]*fdtbl[i];\n                        outputfDCTQuant[i] = (fDCTQuant > 0.0) ? ((fDCTQuant + 0.5)|0) : ((fDCTQuant - 0.5)|0);\n                        //outputfDCTQuant[i] = fround(fDCTQuant);\n    \n                    }\n                    return outputfDCTQuant;\n                }\n    \n                function writeAPP0()\n                {\n                    writeWord(0xFFE0); // marker\n                    writeWord(16); // length\n                    writeByte(0x4A); // J\n                    writeByte(0x46); // F\n                    writeByte(0x49); // I\n                    writeByte(0x46); // F\n                    writeByte(0); // = \"JFIF\",'\\0'\n                    writeByte(1); // versionhi\n                    writeByte(1); // versionlo\n                    writeByte(0); // xyunits\n                    writeWord(1); // xdensity\n                    writeWord(1); // ydensity\n                    writeByte(0); // thumbnwidth\n                    writeByte(0); // thumbnheight\n                }\n    \n                function writeSOF0(width, height)\n                {\n                    writeWord(0xFFC0); // marker\n                    writeWord(17);   // length, truecolor YUV JPG\n                    writeByte(8);    // precision\n                    writeWord(height);\n                    writeWord(width);\n                    writeByte(3);    // nrofcomponents\n                    writeByte(1);    // IdY\n                    writeByte(0x11); // HVY\n                    writeByte(0);    // QTY\n                    writeByte(2);    // IdU\n                    writeByte(0x11); // HVU\n                    writeByte(1);    // QTU\n                    writeByte(3);    // IdV\n                    writeByte(0x11); // HVV\n                    writeByte(1);    // QTV\n                }\n    \n                function writeDQT()\n                {\n                    writeWord(0xFFDB); // marker\n                    writeWord(132);    // length\n                    writeByte(0);\n                    for (var i=0; i<64; i++) {\n                        writeByte(YTable[i]);\n                    }\n                    writeByte(1);\n                    for (var j=0; j<64; j++) {\n                        writeByte(UVTable[j]);\n                    }\n                }\n    \n                function writeDHT()\n                {\n                    writeWord(0xFFC4); // marker\n                    writeWord(0x01A2); // length\n    \n                    writeByte(0); // HTYDCinfo\n                    for (var i=0; i<16; i++) {\n                        writeByte(std_dc_luminance_nrcodes[i+1]);\n                    }\n                    for (var j=0; j<=11; j++) {\n                        writeByte(std_dc_luminance_values[j]);\n                    }\n    \n                    writeByte(0x10); // HTYACinfo\n                    for (var k=0; k<16; k++) {\n                        writeByte(std_ac_luminance_nrcodes[k+1]);\n                    }\n                    for (var l=0; l<=161; l++) {\n                        writeByte(std_ac_luminance_values[l]);\n                    }\n    \n                    writeByte(1); // HTUDCinfo\n                    for (var m=0; m<16; m++) {\n                        writeByte(std_dc_chrominance_nrcodes[m+1]);\n                    }\n                    for (var n=0; n<=11; n++) {\n                        writeByte(std_dc_chrominance_values[n]);\n                    }\n    \n                    writeByte(0x11); // HTUACinfo\n                    for (var o=0; o<16; o++) {\n                        writeByte(std_ac_chrominance_nrcodes[o+1]);\n                    }\n                    for (var p=0; p<=161; p++) {\n                        writeByte(std_ac_chrominance_values[p]);\n                    }\n                }\n    \n                function writeSOS()\n                {\n                    writeWord(0xFFDA); // marker\n                    writeWord(12); // length\n                    writeByte(3); // nrofcomponents\n                    writeByte(1); // IdY\n                    writeByte(0); // HTY\n                    writeByte(2); // IdU\n                    writeByte(0x11); // HTU\n                    writeByte(3); // IdV\n                    writeByte(0x11); // HTV\n                    writeByte(0); // Ss\n                    writeByte(0x3f); // Se\n                    writeByte(0); // Bf\n                }\n    \n                function processDU(CDU, fdtbl, DC, HTDC, HTAC){\n                    var EOB = HTAC[0x00];\n                    var M16zeroes = HTAC[0xF0];\n                    var pos;\n                    var I16 = 16;\n                    var I63 = 63;\n                    var I64 = 64;\n                    var DU_DCT = fDCTQuant(CDU, fdtbl);\n                    //ZigZag reorder\n                    for (var j=0;j<I64;++j) {\n                        DU[ZigZag[j]]=DU_DCT[j];\n                    }\n                    var Diff = DU[0] - DC; DC = DU[0];\n                    //Encode DC\n                    if (Diff==0) {\n                        writeBits(HTDC[0]); // Diff might be 0\n                    } else {\n                        pos = 32767+Diff;\n                        writeBits(HTDC[category[pos]]);\n                        writeBits(bitcode[pos]);\n                    }\n                    //Encode ACs\n                    var end0pos = 63; // was const... which is crazy\n                    for (; (end0pos>0)&&(DU[end0pos]==0); end0pos--) {};\n                    //end0pos = first element in reverse order !=0\n                    if ( end0pos == 0) {\n                        writeBits(EOB);\n                        return DC;\n                    }\n                    var i = 1;\n                    var lng;\n                    while ( i <= end0pos ) {\n                        var startpos = i;\n                        for (; (DU[i]==0) && (i<=end0pos); ++i) {}\n                        var nrzeroes = i-startpos;\n                        if ( nrzeroes >= I16 ) {\n                            lng = nrzeroes>>4;\n                            for (var nrmarker=1; nrmarker <= lng; ++nrmarker)\n                                writeBits(M16zeroes);\n                            nrzeroes = nrzeroes&0xF;\n                        }\n                        pos = 32767+DU[i];\n                        writeBits(HTAC[(nrzeroes<<4)+category[pos]]);\n                        writeBits(bitcode[pos]);\n                        i++;\n                    }\n                    if ( end0pos != I63 ) {\n                        writeBits(EOB);\n                    }\n                    return DC;\n                }\n    \n                function initCharLookupTable(){\n                    var sfcc = String.fromCharCode;\n                    for(var i=0; i < 256; i++){ ///// ACHTUNG // 255\n                        clt[i] = sfcc(i);\n                    }\n                }\n    \n                this.encode = function(image,quality) // image data object\n                {\n                    // var time_start = new Date().getTime();\n    \n                    if(quality) setQuality(quality);\n    \n                    // Initialize bit writer\n                    byteout = new Array();\n                    bytenew=0;\n                    bytepos=7;\n    \n                    // Add JPEG headers\n                    writeWord(0xFFD8); // SOI\n                    writeAPP0();\n                    writeDQT();\n                    writeSOF0(image.width,image.height);\n                    writeDHT();\n                    writeSOS();\n    \n    \n                    // Encode 8x8 macroblocks\n                    var DCY=0;\n                    var DCU=0;\n                    var DCV=0;\n    \n                    bytenew=0;\n                    bytepos=7;\n    \n    \n                    this.encode.displayName = \"_encode_\";\n    \n                    var imageData = image.data;\n                    var width = image.width;\n                    var height = image.height;\n    \n                    var quadWidth = width*4;\n                    var tripleWidth = width*3;\n    \n                    var x, y = 0;\n                    var r, g, b;\n                    var start,p, col,row,pos;\n                    while(y < height){\n                        x = 0;\n                        while(x < quadWidth){\n                        start = quadWidth * y + x;\n                        p = start;\n                        col = -1;\n                        row = 0;\n    \n                        for(pos=0; pos < 64; pos++){\n                            row = pos >> 3;// /8\n                            col = ( pos & 7 ) * 4; // %8\n                            p = start + ( row * quadWidth ) + col;\n    \n                            if(y+row >= height){ // padding bottom\n                                p-= (quadWidth*(y+1+row-height));\n                            }\n    \n                            if(x+col >= quadWidth){ // padding right\n                                p-= ((x+col) - quadWidth +4)\n                            }\n    \n                            r = imageData[ p++ ];\n                            g = imageData[ p++ ];\n                            b = imageData[ p++ ];\n    \n    \n                            /* // calculate YUV values dynamically\n                            YDU[pos]=((( 0.29900)*r+( 0.58700)*g+( 0.11400)*b))-128; //-0x80\n                            UDU[pos]=(((-0.16874)*r+(-0.33126)*g+( 0.50000)*b));\n                            VDU[pos]=((( 0.50000)*r+(-0.41869)*g+(-0.08131)*b));\n                            */\n    \n                            // use lookup table (slightly faster)\n                            YDU[pos] = ((RGB_YUV_TABLE[r]             + RGB_YUV_TABLE[(g +  256)>>0] + RGB_YUV_TABLE[(b +  512)>>0]) >> 16)-128;\n                            UDU[pos] = ((RGB_YUV_TABLE[(r +  768)>>0] + RGB_YUV_TABLE[(g + 1024)>>0] + RGB_YUV_TABLE[(b + 1280)>>0]) >> 16)-128;\n                            VDU[pos] = ((RGB_YUV_TABLE[(r + 1280)>>0] + RGB_YUV_TABLE[(g + 1536)>>0] + RGB_YUV_TABLE[(b + 1792)>>0]) >> 16)-128;\n    \n                        }\n    \n                        DCY = processDU(YDU, fdtbl_Y, DCY, YDC_HT, YAC_HT);\n                        DCU = processDU(UDU, fdtbl_UV, DCU, UVDC_HT, UVAC_HT);\n                        DCV = processDU(VDU, fdtbl_UV, DCV, UVDC_HT, UVAC_HT);\n                        x+=32;\n                        }\n                        y+=8;\n                    }\n    \n    \n                    ////////////////////////////////////////////////////////////////\n    \n                    // Do the bit alignment of the EOI marker\n                    if ( bytepos >= 0 ) {\n                        var fillbits = [];\n                        fillbits[1] = bytepos+1;\n                        fillbits[0] = (1<<(bytepos+1))-1;\n                        writeBits(fillbits);\n                    }\n    \n                    writeWord(0xFFD9); //EOI\n    \n                    var jpegDataUri = 'data:image/jpeg;base64,' + btoa(byteout.join(''));\n    \n                    byteout = [];\n    \n                    // benchmarking\n                    // var duration = new Date().getTime() - time_start;\n                    // console.log('Encoding time: '+ currentQuality + 'ms');\n                    //\n    \n                    return jpegDataUri\n            }\n    \n            function setQuality(quality){\n                if (quality <= 0) {\n                    quality = 1;\n                }\n                if (quality > 100) {\n                    quality = 100;\n                }\n    \n                if(currentQuality == quality) return // don't recalc if unchanged\n    \n                var sf = 0;\n                if (quality < 50) {\n                    sf = Math.floor(5000 / quality);\n                } else {\n                    sf = Math.floor(200 - quality*2);\n                }\n    \n                initQuantTables(sf);\n                currentQuality = quality;\n                // console.log('Quality set to: '+quality +'%');\n            }\n    \n            function init(){\n                // var time_start = new Date().getTime();\n                if(!quality) quality = 50;\n                // Create tables\n                initCharLookupTable()\n                initHuffmanTbl();\n                initCategoryNumber();\n                initRGBYUVTable();\n    \n                setQuality(quality);\n                // var duration = new Date().getTime() - time_start;\n                // console.log('Initialization '+ duration + 'ms');\n            }\n    \n            init();\n    \n        };\n    \n        JPEGEncoder.encode = function( data, quality ) {\n            var encoder = new JPEGEncoder( quality );\n    \n            return encoder.encode( data );\n        }\n    \n        return JPEGEncoder;\n    });\n    /**\n     * @fileOverview Fix android canvas.toDataUrl bug.\n     */\n    define('runtime/html5/androidpatch',[\n        'runtime/html5/util',\n        'runtime/html5/jpegencoder',\n        'base'\n    ], function( Util, encoder, Base ) {\n        var origin = Util.canvasToDataUrl,\n            supportJpeg;\n    \n        Util.canvasToDataUrl = function( canvas, type, quality ) {\n            var ctx, w, h, fragement, parts;\n    \n            // 非android手机直接跳过。\n            if ( !Base.os.android ) {\n                return origin.apply( null, arguments );\n            }\n    \n            // 检测是否canvas支持jpeg导出，根据数据格式来判断。\n            // JPEG 前两位分别是：255, 216\n            if ( type === 'image/jpeg' && typeof supportJpeg === 'undefined' ) {\n                fragement = origin.apply( null, arguments );\n    \n                parts = fragement.split(',');\n    \n                if ( ~parts[ 0 ].indexOf('base64') ) {\n                    fragement = atob( parts[ 1 ] );\n                } else {\n                    fragement = decodeURIComponent( parts[ 1 ] );\n                }\n    \n                fragement = fragement.substring( 0, 2 );\n    \n                supportJpeg = fragement.charCodeAt( 0 ) === 255 &&\n                        fragement.charCodeAt( 1 ) === 216;\n            }\n    \n            // 只有在android环境下才修复\n            if ( type === 'image/jpeg' && !supportJpeg ) {\n                w = canvas.width;\n                h = canvas.height;\n                ctx = canvas.getContext('2d');\n    \n                return encoder.encode( ctx.getImageData( 0, 0, w, h ), quality );\n            }\n    \n            return origin.apply( null, arguments );\n        };\n    });\n    /**\n     * @fileOverview Image\n     */\n    define('runtime/html5/image',[\n        'base',\n        'runtime/html5/runtime',\n        'runtime/html5/util'\n    ], function( Base, Html5Runtime, Util ) {\n    \n        var BLANK = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs%3D';\n    \n        return Html5Runtime.register( 'Image', {\n    \n            // flag: 标记是否被修改过。\n            modified: false,\n    \n            init: function() {\n                var me = this,\n                    img = new Image();\n    \n                img.onload = function() {\n    \n                    me._info = {\n                        type: me.type,\n                        width: this.width,\n                        height: this.height\n                    };\n    \n                    // 读取meta信息。\n                    if ( !me._metas && 'image/jpeg' === me.type ) {\n                        Util.parseMeta( me._blob, function( error, ret ) {\n                            me._metas = ret;\n                            me.owner.trigger('load');\n                        });\n                    } else {\n                        me.owner.trigger('load');\n                    }\n                };\n    \n                img.onerror = function() {\n                    me.owner.trigger('error');\n                };\n    \n                me._img = img;\n            },\n    \n            loadFromBlob: function( blob ) {\n                var me = this,\n                    img = me._img;\n    \n                me._blob = blob;\n                me.type = blob.type;\n                img.src = Util.createObjectURL( blob.getSource() );\n                me.owner.once( 'load', function() {\n                    Util.revokeObjectURL( img.src );\n                });\n            },\n    \n            resize: function( width, height ) {\n                var canvas = this._canvas ||\n                        (this._canvas = document.createElement('canvas'));\n    \n                this._resize( this._img, canvas, width, height );\n                this._blob = null;    // 没用了，可以删掉了。\n                this.modified = true;\n                this.owner.trigger('complete');\n            },\n    \n            getAsBlob: function( type ) {\n                var blob = this._blob,\n                    opts = this.options,\n                    canvas;\n    \n                type = type || this.type;\n    \n                // blob需要重新生成。\n                if ( this.modified || this.type !== type ) {\n                    canvas = this._canvas;\n    \n                    if ( type === 'image/jpeg' ) {\n    \n                        blob = Util.canvasToDataUrl( canvas, 'image/jpeg',\n                                opts.quality );\n    \n                        if ( opts.preserveHeaders && this._metas &&\n                                this._metas.imageHead ) {\n    \n                            blob = Util.dataURL2ArrayBuffer( blob );\n                            blob = Util.updateImageHead( blob,\n                                    this._metas.imageHead );\n                            blob = Util.arrayBufferToBlob( blob, type );\n                            return blob;\n                        }\n                    } else {\n                        blob = Util.canvasToDataUrl( canvas, type );\n                    }\n    \n                    blob = Util.dataURL2Blob( blob );\n                }\n    \n                return blob;\n            },\n    \n            getAsDataUrl: function( type ) {\n                var opts = this.options;\n    \n                type = type || this.type;\n    \n                if ( type === 'image/jpeg' ) {\n                    return Util.canvasToDataUrl( this._canvas, type, opts.quality );\n                } else {\n                    return this._canvas.toDataURL( type );\n                }\n            },\n    \n            getOrientation: function() {\n                return this._metas && this._metas.exif &&\n                        this._metas.exif.get('Orientation') || 1;\n            },\n    \n            info: function( val ) {\n    \n                // setter\n                if ( val ) {\n                    this._info = val;\n                    return this;\n                }\n    \n                // getter\n                return this._info;\n            },\n    \n            meta: function( val ) {\n    \n                // setter\n                if ( val ) {\n                    this._meta = val;\n                    return this;\n                }\n    \n                // getter\n                return this._meta;\n            },\n    \n            destroy: function() {\n                var canvas = this._canvas;\n                this._img.onload = null;\n    \n                if ( canvas ) {\n                    canvas.getContext('2d')\n                            .clearRect( 0, 0, canvas.width, canvas.height );\n                    canvas.width = canvas.height = 0;\n                    this._canvas = null;\n                }\n    \n                // 释放内存。非常重要，否则释放不了image的内存。\n                this._img.src = BLANK;\n                this._img = this._blob = null;\n            },\n    \n            _resize: function( img, cvs, width, height ) {\n                var opts = this.options,\n                    naturalWidth = img.width,\n                    naturalHeight = img.height,\n                    orientation = this.getOrientation(),\n                    scale, w, h, x, y;\n    \n                // values that require 90 degree rotation\n                if ( ~[ 5, 6, 7, 8 ].indexOf( orientation ) ) {\n    \n                    // 交换width, height的值。\n                    width ^= height;\n                    height ^= width;\n                    width ^= height;\n                }\n    \n                scale = Math[ opts.crop ? 'max' : 'min' ]( width / naturalWidth,\n                        height / naturalHeight );\n    \n                // 不允许放大。\n                opts.allowMagnify || (scale = Math.min( 1, scale ));\n    \n                w = naturalWidth * scale;\n                h = naturalHeight * scale;\n    \n                if ( opts.crop ) {\n                    cvs.width = width;\n                    cvs.height = height;\n                } else {\n                    cvs.width = w;\n                    cvs.height = h;\n                }\n    \n                x = (cvs.width - w) / 2;\n                y = (cvs.height - h) / 2;\n    \n                opts.preserveHeaders || this._rotate2Orientaion( cvs, orientation );\n    \n                this._renderImageToCanvas( cvs, img, x, y, w, h );\n            },\n    \n            _rotate2Orientaion: function( canvas, orientation ) {\n                var width = canvas.width,\n                    height = canvas.height,\n                    ctx = canvas.getContext('2d');\n    \n                switch ( orientation ) {\n                    case 5:\n                    case 6:\n                    case 7:\n                    case 8:\n                        canvas.width = height;\n                        canvas.height = width;\n                        break;\n                }\n    \n                switch ( orientation ) {\n                    case 2:    // horizontal flip\n                        ctx.translate( width, 0 );\n                        ctx.scale( -1, 1 );\n                        break;\n    \n                    case 3:    // 180 rotate left\n                        ctx.translate( width, height );\n                        ctx.rotate( Math.PI );\n                        break;\n    \n                    case 4:    // vertical flip\n                        ctx.translate( 0, height );\n                        ctx.scale( 1, -1 );\n                        break;\n    \n                    case 5:    // vertical flip + 90 rotate right\n                        ctx.rotate( 0.5 * Math.PI );\n                        ctx.scale( 1, -1 );\n                        break;\n    \n                    case 6:    // 90 rotate right\n                        ctx.rotate( 0.5 * Math.PI );\n                        ctx.translate( 0, -height );\n                        break;\n    \n                    case 7:    // horizontal flip + 90 rotate right\n                        ctx.rotate( 0.5 * Math.PI );\n                        ctx.translate( width, -height );\n                        ctx.scale( -1, 1 );\n                        break;\n    \n                    case 8:    // 90 rotate left\n                        ctx.rotate( -0.5 * Math.PI );\n                        ctx.translate( -width, 0 );\n                        break;\n                }\n            },\n    \n            // https://github.com/stomita/ios-imagefile-megapixel/\n            // blob/master/src/megapix-image.js\n            _renderImageToCanvas: (function() {\n    \n                // 如果不是ios, 不需要这么复杂！\n                if ( !Base.os.ios ) {\n                    return function( canvas, img, x, y, w, h ) {\n                        canvas.getContext('2d').drawImage( img, x, y, w, h );\n                    };\n                }\n    \n                /**\n                 * Detecting vertical squash in loaded image.\n                 * Fixes a bug which squash image vertically while drawing into\n                 * canvas for some images.\n                 */\n                function detectVerticalSquash( img, iw, ih ) {\n                    var canvas = document.createElement('canvas'),\n                        ctx = canvas.getContext('2d'),\n                        sy = 0,\n                        ey = ih,\n                        py = ih,\n                        data, alpha, ratio;\n    \n    \n                    canvas.width = 1;\n                    canvas.height = ih;\n                    ctx.drawImage( img, 0, 0 );\n                    data = ctx.getImageData( 0, 0, 1, ih ).data;\n    \n                    // search image edge pixel position in case\n                    // it is squashed vertically.\n                    while ( py > sy ) {\n                        alpha = data[ (py - 1) * 4 + 3 ];\n    \n                        if ( alpha === 0 ) {\n                            ey = py;\n                        } else {\n                            sy = py;\n                        }\n    \n                        py = (ey + sy) >> 1;\n                    }\n    \n                    ratio = (py / ih);\n                    return (ratio === 0) ? 1 : ratio;\n                }\n    \n                // fix ie7 bug\n                // http://stackoverflow.com/questions/11929099/\n                // html5-canvas-drawimage-ratio-bug-ios\n                if ( Base.os.ios >= 7 ) {\n                    return function( canvas, img, x, y, w, h ) {\n                        var iw = img.naturalWidth,\n                            ih = img.naturalHeight,\n                            vertSquashRatio = detectVerticalSquash( img, iw, ih );\n    \n                        return canvas.getContext('2d').drawImage( img, 0, 0,\n                            iw * vertSquashRatio, ih * vertSquashRatio,\n                            x, y, w, h );\n                    };\n                }\n    \n                /**\n                 * Detect subsampling in loaded image.\n                 * In iOS, larger images than 2M pixels may be\n                 * subsampled in rendering.\n                 */\n                function detectSubsampling( img ) {\n                    var iw = img.naturalWidth,\n                        ih = img.naturalHeight,\n                        canvas, ctx;\n    \n                    // subsampling may happen overmegapixel image\n                    if ( iw * ih > 1024 * 1024 ) {\n                        canvas = document.createElement('canvas');\n                        canvas.width = canvas.height = 1;\n                        ctx = canvas.getContext('2d');\n                        ctx.drawImage( img, -iw + 1, 0 );\n    \n                        // subsampled image becomes half smaller in rendering size.\n                        // check alpha channel value to confirm image is covering\n                        // edge pixel or not. if alpha value is 0\n                        // image is not covering, hence subsampled.\n                        return ctx.getImageData( 0, 0, 1, 1 ).data[ 3 ] === 0;\n                    } else {\n                        return false;\n                    }\n                }\n    \n    \n                return function( canvas, img, x, y, width, height ) {\n                    var iw = img.naturalWidth,\n                        ih = img.naturalHeight,\n                        ctx = canvas.getContext('2d'),\n                        subsampled = detectSubsampling( img ),\n                        doSquash = this.type === 'image/jpeg',\n                        d = 1024,\n                        sy = 0,\n                        dy = 0,\n                        tmpCanvas, tmpCtx, vertSquashRatio, dw, dh, sx, dx;\n    \n                    if ( subsampled ) {\n                        iw /= 2;\n                        ih /= 2;\n                    }\n    \n                    ctx.save();\n                    tmpCanvas = document.createElement('canvas');\n                    tmpCanvas.width = tmpCanvas.height = d;\n    \n                    tmpCtx = tmpCanvas.getContext('2d');\n                    vertSquashRatio = doSquash ?\n                            detectVerticalSquash( img, iw, ih ) : 1;\n    \n                    dw = Math.ceil( d * width / iw );\n                    dh = Math.ceil( d * height / ih / vertSquashRatio );\n    \n                    while ( sy < ih ) {\n                        sx = 0;\n                        dx = 0;\n                        while ( sx < iw ) {\n                            tmpCtx.clearRect( 0, 0, d, d );\n                            tmpCtx.drawImage( img, -sx, -sy );\n                            ctx.drawImage( tmpCanvas, 0, 0, d, d,\n                                    x + dx, y + dy, dw, dh );\n                            sx += d;\n                            dx += dw;\n                        }\n                        sy += d;\n                        dy += dh;\n                    }\n                    ctx.restore();\n                    tmpCanvas = tmpCtx = null;\n                };\n            })()\n        });\n    });\n    /**\n     * @fileOverview Transport\n     * @todo 支持chunked传输，优势：\n     * 可以将大文件分成小块，挨个传输，可以提高大文件成功率，当失败的时候，也只需要重传那小部分，\n     * 而不需要重头再传一次。另外断点续传也需要用chunked方式。\n     */\n    define('runtime/html5/transport',[\n        'base',\n        'runtime/html5/runtime'\n    ], function( Base, Html5Runtime ) {\n    \n        var noop = Base.noop,\n            $ = Base.$;\n    \n        return Html5Runtime.register( 'Transport', {\n            init: function() {\n                this._status = 0;\n                this._response = null;\n            },\n    \n            send: function() {\n                var owner = this.owner,\n                    opts = this.options,\n                    xhr = this._initAjax(),\n                    blob = owner._blob,\n                    server = opts.server,\n                    formData, binary, fr;\n    \n                if ( opts.sendAsBinary ) {\n                    server += (/\\?/.test( server ) ? '&' : '?') +\n                            $.param( owner._formData );\n    \n                    binary = blob.getSource();\n                } else {\n                    formData = new FormData();\n                    $.each( owner._formData, function( k, v ) {\n                        formData.append( k, v );\n                    });\n    \n                    formData.append( opts.fileVal, blob.getSource(),\n                            opts.filename || owner._formData.name || '' );\n                }\n    \n                if ( opts.withCredentials && 'withCredentials' in xhr ) {\n                    xhr.open( opts.method, server, true );\n                    xhr.withCredentials = true;\n                } else {\n                    xhr.open( opts.method, server );\n                }\n    \n                this._setRequestHeader( xhr, opts.headers );\n    \n                if ( binary ) {\n                    xhr.overrideMimeType('application/octet-stream');\n    \n                    // android直接发送blob会导致服务端接收到的是空文件。\n                    // bug详情。\n                    // https://code.google.com/p/android/issues/detail?id=39882\n                    // 所以先用fileReader读取出来再通过arraybuffer的方式发送。\n                    if ( Base.os.android ) {\n                        fr = new FileReader();\n    \n                        fr.onload = function() {\n                            xhr.send( this.result );\n                            fr = fr.onload = null;\n                        };\n    \n                        fr.readAsArrayBuffer( binary );\n                    } else {\n                        xhr.send( binary );\n                    }\n                } else {\n                    xhr.send( formData );\n                }\n            },\n    \n            getResponse: function() {\n                return this._response;\n            },\n    \n            getResponseAsJson: function() {\n                return this._parseJson( this._response );\n            },\n    \n            getStatus: function() {\n                return this._status;\n            },\n    \n            abort: function() {\n                var xhr = this._xhr;\n    \n                if ( xhr ) {\n                    xhr.upload.onprogress = noop;\n                    xhr.onreadystatechange = noop;\n                    xhr.abort();\n    \n                    this._xhr = xhr = null;\n                }\n            },\n    \n            destroy: function() {\n                this.abort();\n            },\n    \n            _initAjax: function() {\n                var me = this,\n                    xhr = new XMLHttpRequest(),\n                    opts = this.options;\n    \n                if ( opts.withCredentials && !('withCredentials' in xhr) &&\n                        typeof XDomainRequest !== 'undefined' ) {\n                    xhr = new XDomainRequest();\n                }\n    \n                xhr.upload.onprogress = function( e ) {\n                    var percentage = 0;\n    \n                    if ( e.lengthComputable ) {\n                        percentage = e.loaded / e.total;\n                    }\n    \n                    return me.trigger( 'progress', percentage );\n                };\n    \n                xhr.onreadystatechange = function() {\n    \n                    if ( xhr.readyState !== 4 ) {\n                        return;\n                    }\n    \n                    xhr.upload.onprogress = noop;\n                    xhr.onreadystatechange = noop;\n                    me._xhr = null;\n                    me._status = xhr.status;\n    \n                    if ( xhr.status >= 200 && xhr.status < 300 ) {\n                        me._response = xhr.responseText;\n                        return me.trigger('load');\n                    } else if ( xhr.status >= 500 && xhr.status < 600 ) {\n                        me._response = xhr.responseText;\n                        return me.trigger( 'error', 'server' );\n                    }\n    \n    \n                    return me.trigger( 'error', me._status ? 'http' : 'abort' );\n                };\n    \n                me._xhr = xhr;\n                return xhr;\n            },\n    \n            _setRequestHeader: function( xhr, headers ) {\n                $.each( headers, function( key, val ) {\n                    xhr.setRequestHeader( key, val );\n                });\n            },\n    \n            _parseJson: function( str ) {\n                var json;\n    \n                try {\n                    json = JSON.parse( str );\n                } catch ( ex ) {\n                    json = {};\n                }\n    \n                return json;\n            }\n        });\n    });\n    /**\n     * @fileOverview FlashRuntime\n     */\n    define('runtime/flash/runtime',[\n        'base',\n        'runtime/runtime',\n        'runtime/compbase'\n    ], function( Base, Runtime, CompBase ) {\n    \n        var $ = Base.$,\n            type = 'flash',\n            components = {};\n    \n    \n        function getFlashVersion() {\n            var version;\n    \n            try {\n                version = navigator.plugins[ 'Shockwave Flash' ];\n                version = version.description;\n            } catch ( ex ) {\n                try {\n                    version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash')\n                            .GetVariable('$version');\n                } catch ( ex2 ) {\n                    version = '0.0';\n                }\n            }\n            version = version.match( /\\d+/g );\n            return parseFloat( version[ 0 ] + '.' + version[ 1 ], 10 );\n        }\n    \n        function FlashRuntime() {\n            var pool = {},\n                clients = {},\n                destory = this.destory,\n                me = this,\n                jsreciver = Base.guid('webuploader_');\n    \n            Runtime.apply( me, arguments );\n            me.type = type;\n    \n    \n            // 这个方法的调用者，实际上是RuntimeClient\n            me.exec = function( comp, fn/*, args...*/ ) {\n                var client = this,\n                    uid = client.uid,\n                    args = Base.slice( arguments, 2 ),\n                    instance;\n    \n                clients[ uid ] = client;\n    \n                if ( components[ comp ] ) {\n                    if ( !pool[ uid ] ) {\n                        pool[ uid ] = new components[ comp ]( client, me );\n                    }\n    \n                    instance = pool[ uid ];\n    \n                    if ( instance[ fn ] ) {\n                        return instance[ fn ].apply( instance, args );\n                    }\n                }\n    \n                return me.flashExec.apply( client, arguments );\n            };\n    \n            function handler( evt, obj ) {\n                var type = evt.type || evt,\n                    parts, uid;\n    \n                parts = type.split('::');\n                uid = parts[ 0 ];\n                type = parts[ 1 ];\n    \n                // console.log.apply( console, arguments );\n    \n                if ( type === 'Ready' && uid === me.uid ) {\n                    me.trigger('ready');\n                } else if ( clients[ uid ] ) {\n                    clients[ uid ].trigger( type.toLowerCase(), evt, obj );\n                }\n    \n                // Base.log( evt, obj );\n            }\n    \n            // flash的接受器。\n            window[ jsreciver ] = function() {\n                var args = arguments;\n    \n                // 为了能捕获得到。\n                setTimeout(function() {\n                    handler.apply( null, args );\n                }, 1 );\n            };\n    \n            this.jsreciver = jsreciver;\n    \n            this.destory = function() {\n                // @todo 删除池子中的所有实例\n                return destory && destory.apply( this, arguments );\n            };\n    \n            this.flashExec = function( comp, fn ) {\n                var flash = me.getFlash(),\n                    args = Base.slice( arguments, 2 );\n    \n                return flash.exec( this.uid, comp, fn, args );\n            };\n    \n            // @todo\n        }\n    \n        Base.inherits( Runtime, {\n            constructor: FlashRuntime,\n    \n            init: function() {\n                var container = this.getContainer(),\n                    opts = this.options,\n                    html;\n    \n                // if not the minimal height, shims are not initialized\n                // in older browsers (e.g FF3.6, IE6,7,8, Safari 4.0,5.0, etc)\n                container.css({\n                    position: 'absolute',\n                    top: '-8px',\n                    left: '-8px',\n                    width: '9px',\n                    height: '9px',\n                    overflow: 'hidden'\n                });\n    \n                // insert flash object\n                html = '<object id=\"' + this.uid + '\" type=\"application/' +\n                        'x-shockwave-flash\" data=\"' +  opts.swf + '\" ';\n    \n                if ( Base.browser.ie ) {\n                    html += 'classid=\"clsid:d27cdb6e-ae6d-11cf-96b8-444553540000\" ';\n                }\n    \n                html += 'width=\"100%\" height=\"100%\" style=\"outline:0\">'  +\n                    '<param name=\"movie\" value=\"' + opts.swf + '\" />' +\n                    '<param name=\"flashvars\" value=\"uid=' + this.uid +\n                    '&jsreciver=' + this.jsreciver + '\" />' +\n                    '<param name=\"wmode\" value=\"transparent\" />' +\n                    '<param name=\"allowscriptaccess\" value=\"always\" />' +\n                '</object>';\n    \n                container.html( html );\n            },\n    \n            getFlash: function() {\n                if ( this._flash ) {\n                    return this._flash;\n                }\n    \n                this._flash = $( '#' + this.uid ).get( 0 );\n                return this._flash;\n            }\n    \n        });\n    \n        FlashRuntime.register = function( name, component ) {\n            component = components[ name ] = Base.inherits( CompBase, $.extend({\n    \n                // @todo fix this later\n                flashExec: function() {\n                    var owner = this.owner,\n                        runtime = this.getRuntime();\n    \n                    return runtime.flashExec.apply( owner, arguments );\n                }\n            }, component ) );\n    \n            return component;\n        };\n    \n        if ( getFlashVersion() >= 11.4 ) {\n            Runtime.addRuntime( type, FlashRuntime );\n        }\n    \n        return FlashRuntime;\n    });\n    /**\n     * @fileOverview FilePicker\n     */\n    define('runtime/flash/filepicker',[\n        'base',\n        'runtime/flash/runtime'\n    ], function( Base, FlashRuntime ) {\n        var $ = Base.$;\n    \n        return FlashRuntime.register( 'FilePicker', {\n            init: function( opts ) {\n                var copy = $.extend({}, opts ),\n                    len, i;\n    \n                // 修复Flash再没有设置title的情况下无法弹出flash文件选择框的bug.\n                len = copy.accept && copy.accept.length;\n                for (  i = 0; i < len; i++ ) {\n                    if ( !copy.accept[ i ].title ) {\n                        copy.accept[ i ].title = 'Files';\n                    }\n                }\n    \n                delete copy.button;\n                delete copy.container;\n    \n                this.flashExec( 'FilePicker', 'init', copy );\n            },\n    \n            destroy: function() {\n                // todo\n            }\n        });\n    });\n    /**\n     * @fileOverview 图片压缩\n     */\n    define('runtime/flash/image',[\n        'runtime/flash/runtime'\n    ], function( FlashRuntime ) {\n    \n        return FlashRuntime.register( 'Image', {\n            // init: function( options ) {\n            //     var owner = this.owner;\n    \n            //     this.flashExec( 'Image', 'init', options );\n            //     owner.on( 'load', function() {\n            //         debugger;\n            //     });\n            // },\n    \n            loadFromBlob: function( blob ) {\n                var owner = this.owner;\n    \n                owner.info() && this.flashExec( 'Image', 'info', owner.info() );\n                owner.meta() && this.flashExec( 'Image', 'meta', owner.meta() );\n    \n                this.flashExec( 'Image', 'loadFromBlob', blob.uid );\n            }\n        });\n    });\n    /**\n     * @fileOverview  Transport flash实现\n     */\n    define('runtime/flash/transport',[\n        'base',\n        'runtime/flash/runtime',\n        'runtime/client'\n    ], function( Base, FlashRuntime, RuntimeClient ) {\n        var $ = Base.$;\n    \n        return FlashRuntime.register( 'Transport', {\n            init: function() {\n                this._status = 0;\n                this._response = null;\n                this._responseJson = null;\n            },\n    \n            send: function() {\n                var owner = this.owner,\n                    opts = this.options,\n                    xhr = this._initAjax(),\n                    blob = owner._blob,\n                    server = opts.server,\n                    binary;\n    \n                xhr.connectRuntime( blob.ruid );\n    \n                if ( opts.sendAsBinary ) {\n                    server += (/\\?/.test( server ) ? '&' : '?') +\n                            $.param( owner._formData );\n    \n                    binary = blob.uid;\n                } else {\n                    $.each( owner._formData, function( k, v ) {\n                        xhr.exec( 'append', k, v );\n                    });\n    \n                    xhr.exec( 'appendBlob', opts.fileVal, blob.uid,\n                            opts.filename || owner._formData.name || '' );\n                }\n    \n                this._setRequestHeader( xhr, opts.headers );\n                xhr.exec( 'send', {\n                    method: opts.method,\n                    url: server\n                }, binary );\n            },\n    \n            getStatus: function() {\n                return this._status;\n            },\n    \n            getResponse: function() {\n                return this._response;\n            },\n    \n            getResponseAsJson: function() {\n                return this._responseJson;\n            },\n    \n            abort: function() {\n                var xhr = this._xhr;\n    \n                if ( xhr ) {\n                    xhr.exec('abort');\n                    xhr.destroy();\n                    this._xhr = xhr = null;\n                }\n            },\n    \n            destroy: function() {\n                this.abort();\n            },\n    \n            _initAjax: function() {\n                var me = this,\n                    xhr = new RuntimeClient('XMLHttpRequest');\n    \n                xhr.on( 'uploadprogress progress', function( e ) {\n                    return me.trigger( 'progress', e.loaded / e.total );\n                });\n    \n                xhr.on( 'load', function() {\n                    var status = xhr.exec('getStatus'),\n                        err = '';\n    \n                    xhr.off();\n                    me._xhr = null;\n    \n                    if ( status >= 200 && status < 300 ) {\n                        me._response = xhr.exec('getResponse');\n                        me._responseJson = xhr.exec('getResponseAsJson');\n                    } else if ( status >= 500 && status < 600 ) {\n                        me._response = xhr.exec('getResponse');\n                        me._responseJson = xhr.exec('getResponseAsJson');\n                        err = 'server';\n                    } else {\n                        err = 'http';\n                    }\n    \n                    xhr.destroy();\n                    xhr = null;\n    \n                    return err ? me.trigger( 'error', err ) : me.trigger('load');\n                });\n    \n                xhr.on( 'error', function() {\n                    xhr.off();\n                    me._xhr = null;\n                    me.trigger( 'error', 'http' );\n                });\n    \n                me._xhr = xhr;\n                return xhr;\n            },\n    \n            _setRequestHeader: function( xhr, headers ) {\n                $.each( headers, function( key, val ) {\n                    xhr.exec( 'setRequestHeader', key, val );\n                });\n            }\n        });\n    });\n    /**\n     * @fileOverview 完全版本。\n     */\n    define('preset/all',[\n        'base',\n    \n        // widgets\n        'widgets/filednd',\n        'widgets/filepaste',\n        'widgets/filepicker',\n        'widgets/image',\n        'widgets/queue',\n        'widgets/runtime',\n        'widgets/upload',\n        'widgets/validator',\n    \n        // runtimes\n        // html5\n        'runtime/html5/blob',\n        'runtime/html5/dnd',\n        'runtime/html5/filepaste',\n        'runtime/html5/filepicker',\n        'runtime/html5/imagemeta/exif',\n        'runtime/html5/androidpatch',\n        'runtime/html5/image',\n        'runtime/html5/transport',\n    \n        // flash\n        'runtime/flash/filepicker',\n        'runtime/flash/image',\n        'runtime/flash/transport'\n    ], function( Base ) {\n        return Base;\n    });\n    define('webuploader',[\n        'preset/all'\n    ], function( preset ) {\n        return preset;\n    });\n    return require('webuploader');\n});\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/webuploader/webuploader.withoutimage.js",
    "content": "/*! WebUploader 0.1.2 */\n\n\n/**\n * @fileOverview 让内部各个部件的代码可以用[amd](https://github.com/amdjs/amdjs-api/wiki/AMD)模块定义方式组织起来。\n *\n * AMD API 内部的简单不完全实现，请忽略。只有当WebUploader被合并成一个文件的时候才会引入。\n */\n(function( root, factory ) {\n    var modules = {},\n\n        // 内部require, 简单不完全实现。\n        // https://github.com/amdjs/amdjs-api/wiki/require\n        _require = function( deps, callback ) {\n            var args, len, i;\n\n            // 如果deps不是数组，则直接返回指定module\n            if ( typeof deps === 'string' ) {\n                return getModule( deps );\n            } else {\n                args = [];\n                for( len = deps.length, i = 0; i < len; i++ ) {\n                    args.push( getModule( deps[ i ] ) );\n                }\n\n                return callback.apply( null, args );\n            }\n        },\n\n        // 内部define，暂时不支持不指定id.\n        _define = function( id, deps, factory ) {\n            if ( arguments.length === 2 ) {\n                factory = deps;\n                deps = null;\n            }\n\n            _require( deps || [], function() {\n                setModule( id, factory, arguments );\n            });\n        },\n\n        // 设置module, 兼容CommonJs写法。\n        setModule = function( id, factory, args ) {\n            var module = {\n                    exports: factory\n                },\n                returned;\n\n            if ( typeof factory === 'function' ) {\n                args.length || (args = [ _require, module.exports, module ]);\n                returned = factory.apply( null, args );\n                returned !== undefined && (module.exports = returned);\n            }\n\n            modules[ id ] = module.exports;\n        },\n\n        // 根据id获取module\n        getModule = function( id ) {\n            var module = modules[ id ] || root[ id ];\n\n            if ( !module ) {\n                throw new Error( '`' + id + '` is undefined' );\n            }\n\n            return module;\n        },\n\n        // 将所有modules，将路径ids装换成对象。\n        exportsTo = function( obj ) {\n            var key, host, parts, part, last, ucFirst;\n\n            // make the first character upper case.\n            ucFirst = function( str ) {\n                return str && (str.charAt( 0 ).toUpperCase() + str.substr( 1 ));\n            };\n\n            for ( key in modules ) {\n                host = obj;\n\n                if ( !modules.hasOwnProperty( key ) ) {\n                    continue;\n                }\n\n                parts = key.split('/');\n                last = ucFirst( parts.pop() );\n\n                while( (part = ucFirst( parts.shift() )) ) {\n                    host[ part ] = host[ part ] || {};\n                    host = host[ part ];\n                }\n\n                host[ last ] = modules[ key ];\n            }\n        },\n\n        exports = factory( root, _define, _require ),\n        origin;\n\n    // exports every module.\n    exportsTo( exports );\n\n    if ( typeof module === 'object' && typeof module.exports === 'object' ) {\n\n        // For CommonJS and CommonJS-like environments where a proper window is present,\n        module.exports = exports;\n    } else if ( typeof define === 'function' && define.amd ) {\n\n        // Allow using this built library as an AMD module\n        // in another project. That other project will only\n        // see this AMD call, not the internal modules in\n        // the closure below.\n        define([], exports );\n    } else {\n\n        // Browser globals case. Just assign the\n        // result to a property on the global.\n        origin = root.WebUploader;\n        root.WebUploader = exports;\n        root.WebUploader.noConflict = function() {\n            root.WebUploader = origin;\n        };\n    }\n})( this, function( window, define, require ) {\n\n\n    /**\n     * @fileOverview jQuery or Zepto\n     */\n    define('dollar-third',[],function() {\n        return window.jQuery || window.Zepto;\n    });\n    /**\n     * @fileOverview Dom 操作相关\n     */\n    define('dollar',[\n        'dollar-third'\n    ], function( _ ) {\n        return _;\n    });\n    /**\n     * @fileOverview 使用jQuery的Promise\n     */\n    define('promise-third',[\n        'dollar'\n    ], function( $ ) {\n        return {\n            Deferred: $.Deferred,\n            when: $.when,\n    \n            isPromise: function( anything ) {\n                return anything && typeof anything.then === 'function';\n            }\n        };\n    });\n    /**\n     * @fileOverview Promise/A+\n     */\n    define('promise',[\n        'promise-third'\n    ], function( _ ) {\n        return _;\n    });\n    /**\n     * @fileOverview 基础类方法。\n     */\n    \n    /**\n     * Web Uploader内部类的详细说明，以下提及的功能类，都可以在`WebUploader`这个变量中访问到。\n     *\n     * As you know, Web Uploader的每个文件都是用过[AMD](https://github.com/amdjs/amdjs-api/wiki/AMD)规范中的`define`组织起来的, 每个Module都会有个module id.\n     * 默认module id该文件的路径，而此路径将会转化成名字空间存放在WebUploader中。如：\n     *\n     * * module `base`：WebUploader.Base\n     * * module `file`: WebUploader.File\n     * * module `lib/dnd`: WebUploader.Lib.Dnd\n     * * module `runtime/html5/dnd`: WebUploader.Runtime.Html5.Dnd\n     *\n     *\n     * 以下文档将可能省略`WebUploader`前缀。\n     * @module WebUploader\n     * @title WebUploader API文档\n     */\n    define('base',[\n        'dollar',\n        'promise'\n    ], function( $, promise ) {\n    \n        var noop = function() {},\n            call = Function.call;\n    \n        // http://jsperf.com/uncurrythis\n        // 反科里化\n        function uncurryThis( fn ) {\n            return function() {\n                return call.apply( fn, arguments );\n            };\n        }\n    \n        function bindFn( fn, context ) {\n            return function() {\n                return fn.apply( context, arguments );\n            };\n        }\n    \n        function createObject( proto ) {\n            var f;\n    \n            if ( Object.create ) {\n                return Object.create( proto );\n            } else {\n                f = function() {};\n                f.prototype = proto;\n                return new f();\n            }\n        }\n    \n    \n        /**\n         * 基础类，提供一些简单常用的方法。\n         * @class Base\n         */\n        return {\n    \n            /**\n             * @property {String} version 当前版本号。\n             */\n            version: '0.1.2',\n    \n            /**\n             * @property {jQuery|Zepto} $ 引用依赖的jQuery或者Zepto对象。\n             */\n            $: $,\n    \n            Deferred: promise.Deferred,\n    \n            isPromise: promise.isPromise,\n    \n            when: promise.when,\n    \n            /**\n             * @description  简单的浏览器检查结果。\n             *\n             * * `webkit`  webkit版本号，如果浏览器为非webkit内核，此属性为`undefined`。\n             * * `chrome`  chrome浏览器版本号，如果浏览器为chrome，此属性为`undefined`。\n             * * `ie`  ie浏览器版本号，如果浏览器为非ie，此属性为`undefined`。**暂不支持ie10+**\n             * * `firefox`  firefox浏览器版本号，如果浏览器为非firefox，此属性为`undefined`。\n             * * `safari`  safari浏览器版本号，如果浏览器为非safari，此属性为`undefined`。\n             * * `opera`  opera浏览器版本号，如果浏览器为非opera，此属性为`undefined`。\n             *\n             * @property {Object} [browser]\n             */\n            browser: (function( ua ) {\n                var ret = {},\n                    webkit = ua.match( /WebKit\\/([\\d.]+)/ ),\n                    chrome = ua.match( /Chrome\\/([\\d.]+)/ ) ||\n                        ua.match( /CriOS\\/([\\d.]+)/ ),\n    \n                    ie = ua.match( /MSIE\\s([\\d\\.]+)/ ) ||\n                        ua.match(/(?:trident)(?:.*rv:([\\w.]+))?/i),\n                    firefox = ua.match( /Firefox\\/([\\d.]+)/ ),\n                    safari = ua.match( /Safari\\/([\\d.]+)/ ),\n                    opera = ua.match( /OPR\\/([\\d.]+)/ );\n    \n                webkit && (ret.webkit = parseFloat( webkit[ 1 ] ));\n                chrome && (ret.chrome = parseFloat( chrome[ 1 ] ));\n                ie && (ret.ie = parseFloat( ie[ 1 ] ));\n                firefox && (ret.firefox = parseFloat( firefox[ 1 ] ));\n                safari && (ret.safari = parseFloat( safari[ 1 ] ));\n                opera && (ret.opera = parseFloat( opera[ 1 ] ));\n    \n                return ret;\n            })( navigator.userAgent ),\n    \n            /**\n             * @description  操作系统检查结果。\n             *\n             * * `android`  如果在android浏览器环境下，此值为对应的android版本号，否则为`undefined`。\n             * * `ios` 如果在ios浏览器环境下，此值为对应的ios版本号，否则为`undefined`。\n             * @property {Object} [os]\n             */\n            os: (function( ua ) {\n                var ret = {},\n    \n                    // osx = !!ua.match( /\\(Macintosh\\; Intel / ),\n                    android = ua.match( /(?:Android);?[\\s\\/]+([\\d.]+)?/ ),\n                    ios = ua.match( /(?:iPad|iPod|iPhone).*OS\\s([\\d_]+)/ );\n    \n                // osx && (ret.osx = true);\n                android && (ret.android = parseFloat( android[ 1 ] ));\n                ios && (ret.ios = parseFloat( ios[ 1 ].replace( /_/g, '.' ) ));\n    \n                return ret;\n            })( navigator.userAgent ),\n    \n            /**\n             * 实现类与类之间的继承。\n             * @method inherits\n             * @grammar Base.inherits( super ) => child\n             * @grammar Base.inherits( super, protos ) => child\n             * @grammar Base.inherits( super, protos, statics ) => child\n             * @param  {Class} super 父类\n             * @param  {Object | Function} [protos] 子类或者对象。如果对象中包含constructor，子类将是用此属性值。\n             * @param  {Function} [protos.constructor] 子类构造器，不指定的话将创建个临时的直接执行父类构造器的方法。\n             * @param  {Object} [statics] 静态属性或方法。\n             * @return {Class} 返回子类。\n             * @example\n             * function Person() {\n             *     console.log( 'Super' );\n             * }\n             * Person.prototype.hello = function() {\n             *     console.log( 'hello' );\n             * };\n             *\n             * var Manager = Base.inherits( Person, {\n             *     world: function() {\n             *         console.log( 'World' );\n             *     }\n             * });\n             *\n             * // 因为没有指定构造器，父类的构造器将会执行。\n             * var instance = new Manager();    // => Super\n             *\n             * // 继承子父类的方法\n             * instance.hello();    // => hello\n             * instance.world();    // => World\n             *\n             * // 子类的__super__属性指向父类\n             * console.log( Manager.__super__ === Person );    // => true\n             */\n            inherits: function( Super, protos, staticProtos ) {\n                var child;\n    \n                if ( typeof protos === 'function' ) {\n                    child = protos;\n                    protos = null;\n                } else if ( protos && protos.hasOwnProperty('constructor') ) {\n                    child = protos.constructor;\n                } else {\n                    child = function() {\n                        return Super.apply( this, arguments );\n                    };\n                }\n    \n                // 复制静态方法\n                $.extend( true, child, Super, staticProtos || {} );\n    \n                /* jshint camelcase: false */\n    \n                // 让子类的__super__属性指向父类。\n                child.__super__ = Super.prototype;\n    \n                // 构建原型，添加原型方法或属性。\n                // 暂时用Object.create实现。\n                child.prototype = createObject( Super.prototype );\n                protos && $.extend( true, child.prototype, protos );\n    \n                return child;\n            },\n    \n            /**\n             * 一个不做任何事情的方法。可以用来赋值给默认的callback.\n             * @method noop\n             */\n            noop: noop,\n    \n            /**\n             * 返回一个新的方法，此方法将已指定的`context`来执行。\n             * @grammar Base.bindFn( fn, context ) => Function\n             * @method bindFn\n             * @example\n             * var doSomething = function() {\n             *         console.log( this.name );\n             *     },\n             *     obj = {\n             *         name: 'Object Name'\n             *     },\n             *     aliasFn = Base.bind( doSomething, obj );\n             *\n             *  aliasFn();    // => Object Name\n             *\n             */\n            bindFn: bindFn,\n    \n            /**\n             * 引用Console.log如果存在的话，否则引用一个[空函数loop](#WebUploader:Base.log)。\n             * @grammar Base.log( args... ) => undefined\n             * @method log\n             */\n            log: (function() {\n                if ( window.console ) {\n                    return bindFn( console.log, console );\n                }\n                return noop;\n            })(),\n    \n            nextTick: (function() {\n    \n                return function( cb ) {\n                    setTimeout( cb, 1 );\n                };\n    \n                // @bug 当浏览器不在当前窗口时就停了。\n                // var next = window.requestAnimationFrame ||\n                //     window.webkitRequestAnimationFrame ||\n                //     window.mozRequestAnimationFrame ||\n                //     function( cb ) {\n                //         window.setTimeout( cb, 1000 / 60 );\n                //     };\n    \n                // // fix: Uncaught TypeError: Illegal invocation\n                // return bindFn( next, window );\n            })(),\n    \n            /**\n             * 被[uncurrythis](http://www.2ality.com/2011/11/uncurrying-this.html)的数组slice方法。\n             * 将用来将非数组对象转化成数组对象。\n             * @grammar Base.slice( target, start[, end] ) => Array\n             * @method slice\n             * @example\n             * function doSomthing() {\n             *     var args = Base.slice( arguments, 1 );\n             *     console.log( args );\n             * }\n             *\n             * doSomthing( 'ignored', 'arg2', 'arg3' );    // => Array [\"arg2\", \"arg3\"]\n             */\n            slice: uncurryThis( [].slice ),\n    \n            /**\n             * 生成唯一的ID\n             * @method guid\n             * @grammar Base.guid() => String\n             * @grammar Base.guid( prefx ) => String\n             */\n            guid: (function() {\n                var counter = 0;\n    \n                return function( prefix ) {\n                    var guid = (+new Date()).toString( 32 ),\n                        i = 0;\n    \n                    for ( ; i < 5; i++ ) {\n                        guid += Math.floor( Math.random() * 65535 ).toString( 32 );\n                    }\n    \n                    return (prefix || 'wu_') + guid + (counter++).toString( 32 );\n                };\n            })(),\n    \n            /**\n             * 格式化文件大小, 输出成带单位的字符串\n             * @method formatSize\n             * @grammar Base.formatSize( size ) => String\n             * @grammar Base.formatSize( size, pointLength ) => String\n             * @grammar Base.formatSize( size, pointLength, units ) => String\n             * @param {Number} size 文件大小\n             * @param {Number} [pointLength=2] 精确到的小数点数。\n             * @param {Array} [units=[ 'B', 'K', 'M', 'G', 'TB' ]] 单位数组。从字节，到千字节，一直往上指定。如果单位数组里面只指定了到了K(千字节)，同时文件大小大于M, 此方法的输出将还是显示成多少K.\n             * @example\n             * console.log( Base.formatSize( 100 ) );    // => 100B\n             * console.log( Base.formatSize( 1024 ) );    // => 1.00K\n             * console.log( Base.formatSize( 1024, 0 ) );    // => 1K\n             * console.log( Base.formatSize( 1024 * 1024 ) );    // => 1.00M\n             * console.log( Base.formatSize( 1024 * 1024 * 1024 ) );    // => 1.00G\n             * console.log( Base.formatSize( 1024 * 1024 * 1024, 0, ['B', 'KB', 'MB'] ) );    // => 1024MB\n             */\n            formatSize: function( size, pointLength, units ) {\n                var unit;\n    \n                units = units || [ 'B', 'K', 'M', 'G', 'TB' ];\n    \n                while ( (unit = units.shift()) && size > 1024 ) {\n                    size = size / 1024;\n                }\n    \n                return (unit === 'B' ? size : size.toFixed( pointLength || 2 )) +\n                        unit;\n            }\n        };\n    });\n    /**\n     * 事件处理类，可以独立使用，也可以扩展给对象使用。\n     * @fileOverview Mediator\n     */\n    define('mediator',[\n        'base'\n    ], function( Base ) {\n        var $ = Base.$,\n            slice = [].slice,\n            separator = /\\s+/,\n            protos;\n    \n        // 根据条件过滤出事件handlers.\n        function findHandlers( arr, name, callback, context ) {\n            return $.grep( arr, function( handler ) {\n                return handler &&\n                        (!name || handler.e === name) &&\n                        (!callback || handler.cb === callback ||\n                        handler.cb._cb === callback) &&\n                        (!context || handler.ctx === context);\n            });\n        }\n    \n        function eachEvent( events, callback, iterator ) {\n            // 不支持对象，只支持多个event用空格隔开\n            $.each( (events || '').split( separator ), function( _, key ) {\n                iterator( key, callback );\n            });\n        }\n    \n        function triggerHanders( events, args ) {\n            var stoped = false,\n                i = -1,\n                len = events.length,\n                handler;\n    \n            while ( ++i < len ) {\n                handler = events[ i ];\n    \n                if ( handler.cb.apply( handler.ctx2, args ) === false ) {\n                    stoped = true;\n                    break;\n                }\n            }\n    \n            return !stoped;\n        }\n    \n        protos = {\n    \n            /**\n             * 绑定事件。\n             *\n             * `callback`方法在执行时，arguments将会来源于trigger的时候携带的参数。如\n             * ```javascript\n             * var obj = {};\n             *\n             * // 使得obj有事件行为\n             * Mediator.installTo( obj );\n             *\n             * obj.on( 'testa', function( arg1, arg2 ) {\n             *     console.log( arg1, arg2 ); // => 'arg1', 'arg2'\n             * });\n             *\n             * obj.trigger( 'testa', 'arg1', 'arg2' );\n             * ```\n             *\n             * 如果`callback`中，某一个方法`return false`了，则后续的其他`callback`都不会被执行到。\n             * 切会影响到`trigger`方法的返回值，为`false`。\n             *\n             * `on`还可以用来添加一个特殊事件`all`, 这样所有的事件触发都会响应到。同时此类`callback`中的arguments有一个不同处，\n             * 就是第一个参数为`type`，记录当前是什么事件在触发。此类`callback`的优先级比脚低，会再正常`callback`执行完后触发。\n             * ```javascript\n             * obj.on( 'all', function( type, arg1, arg2 ) {\n             *     console.log( type, arg1, arg2 ); // => 'testa', 'arg1', 'arg2'\n             * });\n             * ```\n             *\n             * @method on\n             * @grammar on( name, callback[, context] ) => self\n             * @param  {String}   name     事件名，支持多个事件用空格隔开\n             * @param  {Function} callback 事件处理器\n             * @param  {Object}   [context]  事件处理器的上下文。\n             * @return {self} 返回自身，方便链式\n             * @chainable\n             * @class Mediator\n             */\n            on: function( name, callback, context ) {\n                var me = this,\n                    set;\n    \n                if ( !callback ) {\n                    return this;\n                }\n    \n                set = this._events || (this._events = []);\n    \n                eachEvent( name, callback, function( name, callback ) {\n                    var handler = { e: name };\n    \n                    handler.cb = callback;\n                    handler.ctx = context;\n                    handler.ctx2 = context || me;\n                    handler.id = set.length;\n    \n                    set.push( handler );\n                });\n    \n                return this;\n            },\n    \n            /**\n             * 绑定事件，且当handler执行完后，自动解除绑定。\n             * @method once\n             * @grammar once( name, callback[, context] ) => self\n             * @param  {String}   name     事件名\n             * @param  {Function} callback 事件处理器\n             * @param  {Object}   [context]  事件处理器的上下文。\n             * @return {self} 返回自身，方便链式\n             * @chainable\n             */\n            once: function( name, callback, context ) {\n                var me = this;\n    \n                if ( !callback ) {\n                    return me;\n                }\n    \n                eachEvent( name, callback, function( name, callback ) {\n                    var once = function() {\n                            me.off( name, once );\n                            return callback.apply( context || me, arguments );\n                        };\n    \n                    once._cb = callback;\n                    me.on( name, once, context );\n                });\n    \n                return me;\n            },\n    \n            /**\n             * 解除事件绑定\n             * @method off\n             * @grammar off( [name[, callback[, context] ] ] ) => self\n             * @param  {String}   [name]     事件名\n             * @param  {Function} [callback] 事件处理器\n             * @param  {Object}   [context]  事件处理器的上下文。\n             * @return {self} 返回自身，方便链式\n             * @chainable\n             */\n            off: function( name, cb, ctx ) {\n                var events = this._events;\n    \n                if ( !events ) {\n                    return this;\n                }\n    \n                if ( !name && !cb && !ctx ) {\n                    this._events = [];\n                    return this;\n                }\n    \n                eachEvent( name, cb, function( name, cb ) {\n                    $.each( findHandlers( events, name, cb, ctx ), function() {\n                        delete events[ this.id ];\n                    });\n                });\n    \n                return this;\n            },\n    \n            /**\n             * 触发事件\n             * @method trigger\n             * @grammar trigger( name[, args...] ) => self\n             * @param  {String}   type     事件名\n             * @param  {*} [...] 任意参数\n             * @return {Boolean} 如果handler中return false了，则返回false, 否则返回true\n             */\n            trigger: function( type ) {\n                var args, events, allEvents;\n    \n                if ( !this._events || !type ) {\n                    return this;\n                }\n    \n                args = slice.call( arguments, 1 );\n                events = findHandlers( this._events, type );\n                allEvents = findHandlers( this._events, 'all' );\n    \n                return triggerHanders( events, args ) &&\n                        triggerHanders( allEvents, arguments );\n            }\n        };\n    \n        /**\n         * 中介者，它本身是个单例，但可以通过[installTo](#WebUploader:Mediator:installTo)方法，使任何对象具备事件行为。\n         * 主要目的是负责模块与模块之间的合作，降低耦合度。\n         *\n         * @class Mediator\n         */\n        return $.extend({\n    \n            /**\n             * 可以通过这个接口，使任何对象具备事件功能。\n             * @method installTo\n             * @param  {Object} obj 需要具备事件行为的对象。\n             * @return {Object} 返回obj.\n             */\n            installTo: function( obj ) {\n                return $.extend( obj, protos );\n            }\n    \n        }, protos );\n    });\n    /**\n     * @fileOverview Uploader上传类\n     */\n    define('uploader',[\n        'base',\n        'mediator'\n    ], function( Base, Mediator ) {\n    \n        var $ = Base.$;\n    \n        /**\n         * 上传入口类。\n         * @class Uploader\n         * @constructor\n         * @grammar new Uploader( opts ) => Uploader\n         * @example\n         * var uploader = WebUploader.Uploader({\n         *     swf: 'path_of_swf/Uploader.swf',\n         *\n         *     // 开起分片上传。\n         *     chunked: true\n         * });\n         */\n        function Uploader( opts ) {\n            this.options = $.extend( true, {}, Uploader.options, opts );\n            this._init( this.options );\n        }\n    \n        // default Options\n        // widgets中有相应扩展\n        Uploader.options = {};\n        Mediator.installTo( Uploader.prototype );\n    \n        // 批量添加纯命令式方法。\n        $.each({\n            upload: 'start-upload',\n            stop: 'stop-upload',\n            getFile: 'get-file',\n            getFiles: 'get-files',\n            addFile: 'add-file',\n            addFiles: 'add-file',\n            sort: 'sort-files',\n            removeFile: 'remove-file',\n            skipFile: 'skip-file',\n            retry: 'retry',\n            isInProgress: 'is-in-progress',\n            makeThumb: 'make-thumb',\n            getDimension: 'get-dimension',\n            addButton: 'add-btn',\n            getRuntimeType: 'get-runtime-type',\n            refresh: 'refresh',\n            disable: 'disable',\n            enable: 'enable',\n            reset: 'reset'\n        }, function( fn, command ) {\n            Uploader.prototype[ fn ] = function() {\n                return this.request( command, arguments );\n            };\n        });\n    \n        $.extend( Uploader.prototype, {\n            state: 'pending',\n    \n            _init: function( opts ) {\n                var me = this;\n    \n                me.request( 'init', opts, function() {\n                    me.state = 'ready';\n                    me.trigger('ready');\n                });\n            },\n    \n            /**\n             * 获取或者设置Uploader配置项。\n             * @method option\n             * @grammar option( key ) => *\n             * @grammar option( key, val ) => self\n             * @example\n             *\n             * // 初始状态图片上传前不会压缩\n             * var uploader = new WebUploader.Uploader({\n             *     resize: null;\n             * });\n             *\n             * // 修改后图片上传前，尝试将图片压缩到1600 * 1600\n             * uploader.options( 'resize', {\n             *     width: 1600,\n             *     height: 1600\n             * });\n             */\n            option: function( key, val ) {\n                var opts = this.options;\n    \n                // setter\n                if ( arguments.length > 1 ) {\n    \n                    if ( $.isPlainObject( val ) &&\n                            $.isPlainObject( opts[ key ] ) ) {\n                        $.extend( opts[ key ], val );\n                    } else {\n                        opts[ key ] = val;\n                    }\n    \n                } else {    // getter\n                    return key ? opts[ key ] : opts;\n                }\n            },\n    \n            /**\n             * 获取文件统计信息。返回一个包含一下信息的对象。\n             * * `successNum` 上传成功的文件数\n             * * `uploadFailNum` 上传失败的文件数\n             * * `cancelNum` 被删除的文件数\n             * * `invalidNum` 无效的文件数\n             * * `queueNum` 还在队列中的文件数\n             * @method getStats\n             * @grammar getStats() => Object\n             */\n            getStats: function() {\n                // return this._mgr.getStats.apply( this._mgr, arguments );\n                var stats = this.request('get-stats');\n    \n                return {\n                    successNum: stats.numOfSuccess,\n    \n                    // who care?\n                    // queueFailNum: 0,\n                    cancelNum: stats.numOfCancel,\n                    invalidNum: stats.numOfInvalid,\n                    uploadFailNum: stats.numOfUploadFailed,\n                    queueNum: stats.numOfQueue\n                };\n            },\n    \n            // 需要重写此方法来来支持opts.onEvent和instance.onEvent的处理器\n            trigger: function( type/*, args...*/ ) {\n                var args = [].slice.call( arguments, 1 ),\n                    opts = this.options,\n                    name = 'on' + type.substring( 0, 1 ).toUpperCase() +\n                        type.substring( 1 );\n    \n                if (\n                        // 调用通过on方法注册的handler.\n                        Mediator.trigger.apply( this, arguments ) === false ||\n    \n                        // 调用opts.onEvent\n                        $.isFunction( opts[ name ] ) &&\n                        opts[ name ].apply( this, args ) === false ||\n    \n                        // 调用this.onEvent\n                        $.isFunction( this[ name ] ) &&\n                        this[ name ].apply( this, args ) === false ||\n    \n                        // 广播所有uploader的事件。\n                        Mediator.trigger.apply( Mediator,\n                        [ this, type ].concat( args ) ) === false ) {\n    \n                    return false;\n                }\n    \n                return true;\n            },\n    \n            // widgets/widget.js将补充此方法的详细文档。\n            request: Base.noop\n        });\n    \n        /**\n         * 创建Uploader实例，等同于new Uploader( opts );\n         * @method create\n         * @class Base\n         * @static\n         * @grammar Base.create( opts ) => Uploader\n         */\n        Base.create = Uploader.create = function( opts ) {\n            return new Uploader( opts );\n        };\n    \n        // 暴露Uploader，可以通过它来扩展业务逻辑。\n        Base.Uploader = Uploader;\n    \n        return Uploader;\n    });\n    /**\n     * @fileOverview Runtime管理器，负责Runtime的选择, 连接\n     */\n    define('runtime/runtime',[\n        'base',\n        'mediator'\n    ], function( Base, Mediator ) {\n    \n        var $ = Base.$,\n            factories = {},\n    \n            // 获取对象的第一个key\n            getFirstKey = function( obj ) {\n                for ( var key in obj ) {\n                    if ( obj.hasOwnProperty( key ) ) {\n                        return key;\n                    }\n                }\n                return null;\n            };\n    \n        // 接口类。\n        function Runtime( options ) {\n            this.options = $.extend({\n                container: document.body\n            }, options );\n            this.uid = Base.guid('rt_');\n        }\n    \n        $.extend( Runtime.prototype, {\n    \n            getContainer: function() {\n                var opts = this.options,\n                    parent, container;\n    \n                if ( this._container ) {\n                    return this._container;\n                }\n    \n                parent = $( opts.container || document.body );\n                container = $( document.createElement('div') );\n    \n                container.attr( 'id', 'rt_' + this.uid );\n                container.css({\n                    position: 'absolute',\n                    top: '0px',\n                    left: '0px',\n                    width: '1px',\n                    height: '1px',\n                    overflow: 'hidden'\n                });\n    \n                parent.append( container );\n                parent.addClass('webuploader-container');\n                this._container = container;\n                return container;\n            },\n    \n            init: Base.noop,\n            exec: Base.noop,\n    \n            destroy: function() {\n                if ( this._container ) {\n                    this._container.parentNode.removeChild( this.__container );\n                }\n    \n                this.off();\n            }\n        });\n    \n        Runtime.orders = 'html5,flash';\n    \n    \n        /**\n         * 添加Runtime实现。\n         * @param {String} type    类型\n         * @param {Runtime} factory 具体Runtime实现。\n         */\n        Runtime.addRuntime = function( type, factory ) {\n            factories[ type ] = factory;\n        };\n    \n        Runtime.hasRuntime = function( type ) {\n            return !!(type ? factories[ type ] : getFirstKey( factories ));\n        };\n    \n        Runtime.create = function( opts, orders ) {\n            var type, runtime;\n    \n            orders = orders || Runtime.orders;\n            $.each( orders.split( /\\s*,\\s*/g ), function() {\n                if ( factories[ this ] ) {\n                    type = this;\n                    return false;\n                }\n            });\n    \n            type = type || getFirstKey( factories );\n    \n            if ( !type ) {\n                throw new Error('Runtime Error');\n            }\n    \n            runtime = new factories[ type ]( opts );\n            return runtime;\n        };\n    \n        Mediator.installTo( Runtime.prototype );\n        return Runtime;\n    });\n    \n    /**\n     * @fileOverview Runtime管理器，负责Runtime的选择, 连接\n     */\n    define('runtime/client',[\n        'base',\n        'mediator',\n        'runtime/runtime'\n    ], function( Base, Mediator, Runtime ) {\n    \n        var cache;\n    \n        cache = (function() {\n            var obj = {};\n    \n            return {\n                add: function( runtime ) {\n                    obj[ runtime.uid ] = runtime;\n                },\n    \n                get: function( ruid, standalone ) {\n                    var i;\n    \n                    if ( ruid ) {\n                        return obj[ ruid ];\n                    }\n    \n                    for ( i in obj ) {\n                        // 有些类型不能重用，比如filepicker.\n                        if ( standalone && obj[ i ].__standalone ) {\n                            continue;\n                        }\n    \n                        return obj[ i ];\n                    }\n    \n                    return null;\n                },\n    \n                remove: function( runtime ) {\n                    delete obj[ runtime.uid ];\n                }\n            };\n        })();\n    \n        function RuntimeClient( component, standalone ) {\n            var deferred = Base.Deferred(),\n                runtime;\n    \n            this.uid = Base.guid('client_');\n    \n            // 允许runtime没有初始化之前，注册一些方法在初始化后执行。\n            this.runtimeReady = function( cb ) {\n                return deferred.done( cb );\n            };\n    \n            this.connectRuntime = function( opts, cb ) {\n    \n                // already connected.\n                if ( runtime ) {\n                    throw new Error('already connected!');\n                }\n    \n                deferred.done( cb );\n    \n                if ( typeof opts === 'string' && cache.get( opts ) ) {\n                    runtime = cache.get( opts );\n                }\n    \n                // 像filePicker只能独立存在，不能公用。\n                runtime = runtime || cache.get( null, standalone );\n    \n                // 需要创建\n                if ( !runtime ) {\n                    runtime = Runtime.create( opts, opts.runtimeOrder );\n                    runtime.__promise = deferred.promise();\n                    runtime.once( 'ready', deferred.resolve );\n                    runtime.init();\n                    cache.add( runtime );\n                    runtime.__client = 1;\n                } else {\n                    // 来自cache\n                    Base.$.extend( runtime.options, opts );\n                    runtime.__promise.then( deferred.resolve );\n                    runtime.__client++;\n                }\n    \n                standalone && (runtime.__standalone = standalone);\n                return runtime;\n            };\n    \n            this.getRuntime = function() {\n                return runtime;\n            };\n    \n            this.disconnectRuntime = function() {\n                if ( !runtime ) {\n                    return;\n                }\n    \n                runtime.__client--;\n    \n                if ( runtime.__client <= 0 ) {\n                    cache.remove( runtime );\n                    delete runtime.__promise;\n                    runtime.destroy();\n                }\n    \n                runtime = null;\n            };\n    \n            this.exec = function() {\n                if ( !runtime ) {\n                    return;\n                }\n    \n                var args = Base.slice( arguments );\n                component && args.unshift( component );\n    \n                return runtime.exec.apply( this, args );\n            };\n    \n            this.getRuid = function() {\n                return runtime && runtime.uid;\n            };\n    \n            this.destroy = (function( destroy ) {\n                return function() {\n                    destroy && destroy.apply( this, arguments );\n                    this.trigger('destroy');\n                    this.off();\n                    this.exec('destroy');\n                    this.disconnectRuntime();\n                };\n            })( this.destroy );\n        }\n    \n        Mediator.installTo( RuntimeClient.prototype );\n        return RuntimeClient;\n    });\n    /**\n     * @fileOverview 错误信息\n     */\n    define('lib/dnd',[\n        'base',\n        'mediator',\n        'runtime/client'\n    ], function( Base, Mediator, RuntimeClent ) {\n    \n        var $ = Base.$;\n    \n        function DragAndDrop( opts ) {\n            opts = this.options = $.extend({}, DragAndDrop.options, opts );\n    \n            opts.container = $( opts.container );\n    \n            if ( !opts.container.length ) {\n                return;\n            }\n    \n            RuntimeClent.call( this, 'DragAndDrop' );\n        }\n    \n        DragAndDrop.options = {\n            accept: null,\n            disableGlobalDnd: false\n        };\n    \n        Base.inherits( RuntimeClent, {\n            constructor: DragAndDrop,\n    \n            init: function() {\n                var me = this;\n    \n                me.connectRuntime( me.options, function() {\n                    me.exec('init');\n                    me.trigger('ready');\n                });\n            },\n    \n            destroy: function() {\n                this.disconnectRuntime();\n            }\n        });\n    \n        Mediator.installTo( DragAndDrop.prototype );\n    \n        return DragAndDrop;\n    });\n    /**\n     * @fileOverview 组件基类。\n     */\n    define('widgets/widget',[\n        'base',\n        'uploader'\n    ], function( Base, Uploader ) {\n    \n        var $ = Base.$,\n            _init = Uploader.prototype._init,\n            IGNORE = {},\n            widgetClass = [];\n    \n        function isArrayLike( obj ) {\n            if ( !obj ) {\n                return false;\n            }\n    \n            var length = obj.length,\n                type = $.type( obj );\n    \n            if ( obj.nodeType === 1 && length ) {\n                return true;\n            }\n    \n            return type === 'array' || type !== 'function' && type !== 'string' &&\n                    (length === 0 || typeof length === 'number' && length > 0 &&\n                    (length - 1) in obj);\n        }\n    \n        function Widget( uploader ) {\n            this.owner = uploader;\n            this.options = uploader.options;\n        }\n    \n        $.extend( Widget.prototype, {\n    \n            init: Base.noop,\n    \n            // 类Backbone的事件监听声明，监听uploader实例上的事件\n            // widget直接无法监听事件，事件只能通过uploader来传递\n            invoke: function( apiName, args ) {\n    \n                /*\n                    {\n                        'make-thumb': 'makeThumb'\n                    }\n                 */\n                var map = this.responseMap;\n    \n                // 如果无API响应声明则忽略\n                if ( !map || !(apiName in map) || !(map[ apiName ] in this) ||\n                        !$.isFunction( this[ map[ apiName ] ] ) ) {\n    \n                    return IGNORE;\n                }\n    \n                return this[ map[ apiName ] ].apply( this, args );\n    \n            },\n    \n            /**\n             * 发送命令。当传入`callback`或者`handler`中返回`promise`时。返回一个当所有`handler`中的promise都完成后完成的新`promise`。\n             * @method request\n             * @grammar request( command, args ) => * | Promise\n             * @grammar request( command, args, callback ) => Promise\n             * @for  Uploader\n             */\n            request: function() {\n                return this.owner.request.apply( this.owner, arguments );\n            }\n        });\n    \n        // 扩展Uploader.\n        $.extend( Uploader.prototype, {\n    \n            // 覆写_init用来初始化widgets\n            _init: function() {\n                var me = this,\n                    widgets = me._widgets = [];\n    \n                $.each( widgetClass, function( _, klass ) {\n                    widgets.push( new klass( me ) );\n                });\n    \n                return _init.apply( me, arguments );\n            },\n    \n            request: function( apiName, args, callback ) {\n                var i = 0,\n                    widgets = this._widgets,\n                    len = widgets.length,\n                    rlts = [],\n                    dfds = [],\n                    widget, rlt, promise, key;\n    \n                args = isArrayLike( args ) ? args : [ args ];\n    \n                for ( ; i < len; i++ ) {\n                    widget = widgets[ i ];\n                    rlt = widget.invoke( apiName, args );\n    \n                    if ( rlt !== IGNORE ) {\n    \n                        // Deferred对象\n                        if ( Base.isPromise( rlt ) ) {\n                            dfds.push( rlt );\n                        } else {\n                            rlts.push( rlt );\n                        }\n                    }\n                }\n    \n                // 如果有callback，则用异步方式。\n                if ( callback || dfds.length ) {\n                    promise = Base.when.apply( Base, dfds );\n                    key = promise.pipe ? 'pipe' : 'then';\n    \n                    // 很重要不能删除。删除了会死循环。\n                    // 保证执行顺序。让callback总是在下一个tick中执行。\n                    return promise[ key ](function() {\n                                var deferred = Base.Deferred(),\n                                    args = arguments;\n    \n                                setTimeout(function() {\n                                    deferred.resolve.apply( deferred, args );\n                                }, 1 );\n    \n                                return deferred.promise();\n                            })[ key ]( callback || Base.noop );\n                } else {\n                    return rlts[ 0 ];\n                }\n            }\n        });\n    \n        /**\n         * 添加组件\n         * @param  {object} widgetProto 组件原型，构造函数通过constructor属性定义\n         * @param  {object} responseMap API名称与函数实现的映射\n         * @example\n         *     Uploader.register( {\n         *         init: function( options ) {},\n         *         makeThumb: function() {}\n         *     }, {\n         *         'make-thumb': 'makeThumb'\n         *     } );\n         */\n        Uploader.register = Widget.register = function( responseMap, widgetProto ) {\n            var map = { init: 'init' },\n                klass;\n    \n            if ( arguments.length === 1 ) {\n                widgetProto = responseMap;\n                widgetProto.responseMap = map;\n            } else {\n                widgetProto.responseMap = $.extend( map, responseMap );\n            }\n    \n            klass = Base.inherits( Widget, widgetProto );\n            widgetClass.push( klass );\n    \n            return klass;\n        };\n    \n        return Widget;\n    });\n    /**\n     * @fileOverview DragAndDrop Widget。\n     */\n    define('widgets/filednd',[\n        'base',\n        'uploader',\n        'lib/dnd',\n        'widgets/widget'\n    ], function( Base, Uploader, Dnd ) {\n        var $ = Base.$;\n    \n        Uploader.options.dnd = '';\n    \n        /**\n         * @property {Selector} [dnd=undefined]  指定Drag And Drop拖拽的容器，如果不指定，则不启动。\n         * @namespace options\n         * @for Uploader\n         */\n    \n        /**\n         * @event dndAccept\n         * @param {DataTransferItemList} items DataTransferItem\n         * @description 阻止此事件可以拒绝某些类型的文件拖入进来。目前只有 chrome 提供这样的 API，且只能通过 mime-type 验证。\n         * @for  Uploader\n         */\n        return Uploader.register({\n            init: function( opts ) {\n    \n                if ( !opts.dnd ||\n                        this.request('predict-runtime-type') !== 'html5' ) {\n                    return;\n                }\n    \n                var me = this,\n                    deferred = Base.Deferred(),\n                    options = $.extend({}, {\n                        disableGlobalDnd: opts.disableGlobalDnd,\n                        container: opts.dnd,\n                        accept: opts.accept\n                    }),\n                    dnd;\n    \n                dnd = new Dnd( options );\n    \n                dnd.once( 'ready', deferred.resolve );\n                dnd.on( 'drop', function( files ) {\n                    me.request( 'add-file', [ files ]);\n                });\n    \n                // 检测文件是否全部允许添加。\n                dnd.on( 'accept', function( items ) {\n                    return me.owner.trigger( 'dndAccept', items );\n                });\n    \n                dnd.init();\n    \n                return deferred.promise();\n            }\n        });\n    });\n    \n    /**\n     * @fileOverview 错误信息\n     */\n    define('lib/filepaste',[\n        'base',\n        'mediator',\n        'runtime/client'\n    ], function( Base, Mediator, RuntimeClent ) {\n    \n        var $ = Base.$;\n    \n        function FilePaste( opts ) {\n            opts = this.options = $.extend({}, opts );\n            opts.container = $( opts.container || document.body );\n            RuntimeClent.call( this, 'FilePaste' );\n        }\n    \n        Base.inherits( RuntimeClent, {\n            constructor: FilePaste,\n    \n            init: function() {\n                var me = this;\n    \n                me.connectRuntime( me.options, function() {\n                    me.exec('init');\n                    me.trigger('ready');\n                });\n            },\n    \n            destroy: function() {\n                this.exec('destroy');\n                this.disconnectRuntime();\n                this.off();\n            }\n        });\n    \n        Mediator.installTo( FilePaste.prototype );\n    \n        return FilePaste;\n    });\n    /**\n     * @fileOverview 组件基类。\n     */\n    define('widgets/filepaste',[\n        'base',\n        'uploader',\n        'lib/filepaste',\n        'widgets/widget'\n    ], function( Base, Uploader, FilePaste ) {\n        var $ = Base.$;\n    \n        /**\n         * @property {Selector} [paste=undefined]  指定监听paste事件的容器，如果不指定，不启用此功能。此功能为通过粘贴来添加截屏的图片。建议设置为`document.body`.\n         * @namespace options\n         * @for Uploader\n         */\n        return Uploader.register({\n            init: function( opts ) {\n    \n                if ( !opts.paste ||\n                        this.request('predict-runtime-type') !== 'html5' ) {\n                    return;\n                }\n    \n                var me = this,\n                    deferred = Base.Deferred(),\n                    options = $.extend({}, {\n                        container: opts.paste,\n                        accept: opts.accept\n                    }),\n                    paste;\n    \n                paste = new FilePaste( options );\n    \n                paste.once( 'ready', deferred.resolve );\n                paste.on( 'paste', function( files ) {\n                    me.owner.request( 'add-file', [ files ]);\n                });\n                paste.init();\n    \n                return deferred.promise();\n            }\n        });\n    });\n    /**\n     * @fileOverview Blob\n     */\n    define('lib/blob',[\n        'base',\n        'runtime/client'\n    ], function( Base, RuntimeClient ) {\n    \n        function Blob( ruid, source ) {\n            var me = this;\n    \n            me.source = source;\n            me.ruid = ruid;\n    \n            RuntimeClient.call( me, 'Blob' );\n    \n            this.uid = source.uid || this.uid;\n            this.type = source.type || '';\n            this.size = source.size || 0;\n    \n            if ( ruid ) {\n                me.connectRuntime( ruid );\n            }\n        }\n    \n        Base.inherits( RuntimeClient, {\n            constructor: Blob,\n    \n            slice: function( start, end ) {\n                return this.exec( 'slice', start, end );\n            },\n    \n            getSource: function() {\n                return this.source;\n            }\n        });\n    \n        return Blob;\n    });\n    /**\n     * 为了统一化Flash的File和HTML5的File而存在。\n     * 以至于要调用Flash里面的File，也可以像调用HTML5版本的File一下。\n     * @fileOverview File\n     */\n    define('lib/file',[\n        'base',\n        'lib/blob'\n    ], function( Base, Blob ) {\n    \n        var uid = 1,\n            rExt = /\\.([^.]+)$/;\n    \n        function File( ruid, file ) {\n            var ext;\n    \n            Blob.apply( this, arguments );\n            this.name = file.name || ('untitled' + uid++);\n            ext = rExt.exec( file.name ) ? RegExp.$1.toLowerCase() : '';\n    \n            // todo 支持其他类型文件的转换。\n    \n            // 如果有mimetype, 但是文件名里面没有找出后缀规律\n            if ( !ext && this.type ) {\n                ext = /\\/(jpg|jpeg|png|gif|bmp)$/i.exec( this.type ) ?\n                        RegExp.$1.toLowerCase() : '';\n                this.name += '.' + ext;\n            }\n    \n            // 如果没有指定mimetype, 但是知道文件后缀。\n            if ( !this.type &&  ~'jpg,jpeg,png,gif,bmp'.indexOf( ext ) ) {\n                this.type = 'image/' + (ext === 'jpg' ? 'jpeg' : ext);\n            }\n    \n            this.ext = ext;\n            this.lastModifiedDate = file.lastModifiedDate ||\n                    (new Date()).toLocaleString();\n        }\n    \n        return Base.inherits( Blob, File );\n    });\n    \n    /**\n     * @fileOverview 错误信息\n     */\n    define('lib/filepicker',[\n        'base',\n        'runtime/client',\n        'lib/file'\n    ], function( Base, RuntimeClent, File ) {\n    \n        var $ = Base.$;\n    \n        function FilePicker( opts ) {\n            opts = this.options = $.extend({}, FilePicker.options, opts );\n    \n            opts.container = $( opts.id );\n    \n            if ( !opts.container.length ) {\n                throw new Error('按钮指定错误');\n            }\n    \n            opts.innerHTML = opts.innerHTML || opts.label ||\n                    opts.container.html() || '';\n    \n            opts.button = $( opts.button || document.createElement('div') );\n            opts.button.html( opts.innerHTML );\n            opts.container.html( opts.button );\n    \n            RuntimeClent.call( this, 'FilePicker', true );\n        }\n    \n        FilePicker.options = {\n            button: null,\n            container: null,\n            label: null,\n            innerHTML: null,\n            multiple: true,\n            accept: null,\n            name: 'file'\n        };\n    \n        Base.inherits( RuntimeClent, {\n            constructor: FilePicker,\n    \n            init: function() {\n                var me = this,\n                    opts = me.options,\n                    button = opts.button;\n    \n                button.addClass('webuploader-pick');\n    \n                me.on( 'all', function( type ) {\n                    var files;\n    \n                    switch ( type ) {\n                        case 'mouseenter':\n                            button.addClass('webuploader-pick-hover');\n                            break;\n    \n                        case 'mouseleave':\n                            button.removeClass('webuploader-pick-hover');\n                            break;\n    \n                        case 'change':\n                            files = me.exec('getFiles');\n                            me.trigger( 'select', $.map( files, function( file ) {\n                                file = new File( me.getRuid(), file );\n    \n                                // 记录来源。\n                                file._refer = opts.container;\n                                return file;\n                            }), opts.container );\n                            break;\n                    }\n                });\n    \n                me.connectRuntime( opts, function() {\n                    me.refresh();\n                    me.exec( 'init', opts );\n                    me.trigger('ready');\n                });\n    \n                $( window ).on( 'resize', function() {\n                    me.refresh();\n                });\n            },\n    \n            refresh: function() {\n                var shimContainer = this.getRuntime().getContainer(),\n                    button = this.options.button,\n                    width = button.outerWidth ?\n                            button.outerWidth() : button.width(),\n    \n                    height = button.outerHeight ?\n                            button.outerHeight() : button.height(),\n    \n                    pos = button.offset();\n    \n                width && height && shimContainer.css({\n                    bottom: 'auto',\n                    right: 'auto',\n                    width: width + 'px',\n                    height: height + 'px'\n                }).offset( pos );\n            },\n    \n            enable: function() {\n                var btn = this.options.button;\n    \n                btn.removeClass('webuploader-pick-disable');\n                this.refresh();\n            },\n    \n            disable: function() {\n                var btn = this.options.button;\n    \n                this.getRuntime().getContainer().css({\n                    top: '-99999px'\n                });\n    \n                btn.addClass('webuploader-pick-disable');\n            },\n    \n            destroy: function() {\n                if ( this.runtime ) {\n                    this.exec('destroy');\n                    this.disconnectRuntime();\n                }\n            }\n        });\n    \n        return FilePicker;\n    });\n    \n    /**\n     * @fileOverview 文件选择相关\n     */\n    define('widgets/filepicker',[\n        'base',\n        'uploader',\n        'lib/filepicker',\n        'widgets/widget'\n    ], function( Base, Uploader, FilePicker ) {\n        var $ = Base.$;\n    \n        $.extend( Uploader.options, {\n    \n            /**\n             * @property {Selector | Object} [pick=undefined]\n             * @namespace options\n             * @for Uploader\n             * @description 指定选择文件的按钮容器，不指定则不创建按钮。\n             *\n             * * `id` {Seletor} 指定选择文件的按钮容器，不指定则不创建按钮。\n             * * `label` {String} 请采用 `innerHTML` 代替\n             * * `innerHTML` {String} 指定按钮文字。不指定时优先从指定的容器中看是否自带文字。\n             * * `multiple` {Boolean} 是否开起同时选择多个文件能力。\n             */\n            pick: null,\n    \n            /**\n             * @property {Arroy} [accept=null]\n             * @namespace options\n             * @for Uploader\n             * @description 指定接受哪些类型的文件。 由于目前还有ext转mimeType表，所以这里需要分开指定。\n             *\n             * * `title` {String} 文字描述\n             * * `extensions` {String} 允许的文件后缀，不带点，多个用逗号分割。\n             * * `mimeTypes` {String} 多个用逗号分割。\n             *\n             * 如：\n             *\n             * ```\n             * {\n             *     title: 'Images',\n             *     extensions: 'gif,jpg,jpeg,bmp,png',\n             *     mimeTypes: 'image/*'\n             * }\n             * ```\n             */\n            accept: null/*{\n                title: 'Images',\n                extensions: 'gif,jpg,jpeg,bmp,png',\n                mimeTypes: 'image/*'\n            }*/\n        });\n    \n        return Uploader.register({\n            'add-btn': 'addButton',\n            refresh: 'refresh',\n            disable: 'disable',\n            enable: 'enable'\n        }, {\n    \n            init: function( opts ) {\n                this.pickers = [];\n                return opts.pick && this.addButton( opts.pick );\n            },\n    \n            refresh: function() {\n                $.each( this.pickers, function() {\n                    this.refresh();\n                });\n            },\n    \n            /**\n             * @method addButton\n             * @for Uploader\n             * @grammar addButton( pick ) => Promise\n             * @description\n             * 添加文件选择按钮，如果一个按钮不够，需要调用此方法来添加。参数跟[options.pick](#WebUploader:Uploader:options)一致。\n             * @example\n             * uploader.addButton({\n             *     id: '#btnContainer',\n             *     innerHTML: '选择文件'\n             * });\n             */\n            addButton: function( pick ) {\n                var me = this,\n                    opts = me.options,\n                    accept = opts.accept,\n                    options, picker, deferred;\n    \n                if ( !pick ) {\n                    return;\n                }\n    \n                deferred = Base.Deferred();\n                $.isPlainObject( pick ) || (pick = {\n                    id: pick\n                });\n    \n                options = $.extend({}, pick, {\n                    accept: $.isPlainObject( accept ) ? [ accept ] : accept,\n                    swf: opts.swf,\n                    runtimeOrder: opts.runtimeOrder\n                });\n    \n                picker = new FilePicker( options );\n    \n                picker.once( 'ready', deferred.resolve );\n                picker.on( 'select', function( files ) {\n                    me.owner.request( 'add-file', [ files ]);\n                });\n                picker.init();\n    \n                this.pickers.push( picker );\n    \n                return deferred.promise();\n            },\n    \n            disable: function() {\n                $.each( this.pickers, function() {\n                    this.disable();\n                });\n            },\n    \n            enable: function() {\n                $.each( this.pickers, function() {\n                    this.enable();\n                });\n            }\n        });\n    });\n    /**\n     * @fileOverview 文件属性封装\n     */\n    define('file',[\n        'base',\n        'mediator'\n    ], function( Base, Mediator ) {\n    \n        var $ = Base.$,\n            idPrefix = 'WU_FILE_',\n            idSuffix = 0,\n            rExt = /\\.([^.]+)$/,\n            statusMap = {};\n    \n        function gid() {\n            return idPrefix + idSuffix++;\n        }\n    \n        /**\n         * 文件类\n         * @class File\n         * @constructor 构造函数\n         * @grammar new File( source ) => File\n         * @param {Lib.File} source [lib.File](#Lib.File)实例, 此source对象是带有Runtime信息的。\n         */\n        function WUFile( source ) {\n    \n            /**\n             * 文件名，包括扩展名（后缀）\n             * @property name\n             * @type {string}\n             */\n            this.name = source.name || 'Untitled';\n    \n            /**\n             * 文件体积（字节）\n             * @property size\n             * @type {uint}\n             * @default 0\n             */\n            this.size = source.size || 0;\n    \n            /**\n             * 文件MIMETYPE类型，与文件类型的对应关系请参考[http://t.cn/z8ZnFny](http://t.cn/z8ZnFny)\n             * @property type\n             * @type {string}\n             * @default 'application'\n             */\n            this.type = source.type || 'application';\n    \n            /**\n             * 文件最后修改日期\n             * @property lastModifiedDate\n             * @type {int}\n             * @default 当前时间戳\n             */\n            this.lastModifiedDate = source.lastModifiedDate || (new Date() * 1);\n    \n            /**\n             * 文件ID，每个对象具有唯一ID，与文件名无关\n             * @property id\n             * @type {string}\n             */\n            this.id = gid();\n    \n            /**\n             * 文件扩展名，通过文件名获取，例如test.png的扩展名为png\n             * @property ext\n             * @type {string}\n             */\n            this.ext = rExt.exec( this.name ) ? RegExp.$1 : '';\n    \n    \n            /**\n             * 状态文字说明。在不同的status语境下有不同的用途。\n             * @property statusText\n             * @type {string}\n             */\n            this.statusText = '';\n    \n            // 存储文件状态，防止通过属性直接修改\n            statusMap[ this.id ] = WUFile.Status.INITED;\n    \n            this.source = source;\n            this.loaded = 0;\n    \n            this.on( 'error', function( msg ) {\n                this.setStatus( WUFile.Status.ERROR, msg );\n            });\n        }\n    \n        $.extend( WUFile.prototype, {\n    \n            /**\n             * 设置状态，状态变化时会触发`change`事件。\n             * @method setStatus\n             * @grammar setStatus( status[, statusText] );\n             * @param {File.Status|String} status [文件状态值](#WebUploader:File:File.Status)\n             * @param {String} [statusText=''] 状态说明，常在error时使用，用http, abort,server等来标记是由于什么原因导致文件错误。\n             */\n            setStatus: function( status, text ) {\n    \n                var prevStatus = statusMap[ this.id ];\n    \n                typeof text !== 'undefined' && (this.statusText = text);\n    \n                if ( status !== prevStatus ) {\n                    statusMap[ this.id ] = status;\n                    /**\n                     * 文件状态变化\n                     * @event statuschange\n                     */\n                    this.trigger( 'statuschange', status, prevStatus );\n                }\n    \n            },\n    \n            /**\n             * 获取文件状态\n             * @return {File.Status}\n             * @example\n                     文件状态具体包括以下几种类型：\n                     {\n                         // 初始化\n                        INITED:     0,\n                        // 已入队列\n                        QUEUED:     1,\n                        // 正在上传\n                        PROGRESS:     2,\n                        // 上传出错\n                        ERROR:         3,\n                        // 上传成功\n                        COMPLETE:     4,\n                        // 上传取消\n                        CANCELLED:     5\n                    }\n             */\n            getStatus: function() {\n                return statusMap[ this.id ];\n            },\n    \n            /**\n             * 获取文件原始信息。\n             * @return {*}\n             */\n            getSource: function() {\n                return this.source;\n            },\n    \n            destory: function() {\n                delete statusMap[ this.id ];\n            }\n        });\n    \n        Mediator.installTo( WUFile.prototype );\n    \n        /**\n         * 文件状态值，具体包括以下几种类型：\n         * * `inited` 初始状态\n         * * `queued` 已经进入队列, 等待上传\n         * * `progress` 上传中\n         * * `complete` 上传完成。\n         * * `error` 上传出错，可重试\n         * * `interrupt` 上传中断，可续传。\n         * * `invalid` 文件不合格，不能重试上传。会自动从队列中移除。\n         * * `cancelled` 文件被移除。\n         * @property {Object} Status\n         * @namespace File\n         * @class File\n         * @static\n         */\n        WUFile.Status = {\n            INITED:     'inited',    // 初始状态\n            QUEUED:     'queued',    // 已经进入队列, 等待上传\n            PROGRESS:   'progress',    // 上传中\n            ERROR:      'error',    // 上传出错，可重试\n            COMPLETE:   'complete',    // 上传完成。\n            CANCELLED:  'cancelled',    // 上传取消。\n            INTERRUPT:  'interrupt',    // 上传中断，可续传。\n            INVALID:    'invalid'    // 文件不合格，不能重试上传。\n        };\n    \n        return WUFile;\n    });\n    \n    /**\n     * @fileOverview 文件队列\n     */\n    define('queue',[\n        'base',\n        'mediator',\n        'file'\n    ], function( Base, Mediator, WUFile ) {\n    \n        var $ = Base.$,\n            STATUS = WUFile.Status;\n    \n        /**\n         * 文件队列, 用来存储各个状态中的文件。\n         * @class Queue\n         * @extends Mediator\n         */\n        function Queue() {\n    \n            /**\n             * 统计文件数。\n             * * `numOfQueue` 队列中的文件数。\n             * * `numOfSuccess` 上传成功的文件数\n             * * `numOfCancel` 被移除的文件数\n             * * `numOfProgress` 正在上传中的文件数\n             * * `numOfUploadFailed` 上传错误的文件数。\n             * * `numOfInvalid` 无效的文件数。\n             * @property {Object} stats\n             */\n            this.stats = {\n                numOfQueue: 0,\n                numOfSuccess: 0,\n                numOfCancel: 0,\n                numOfProgress: 0,\n                numOfUploadFailed: 0,\n                numOfInvalid: 0\n            };\n    \n            // 上传队列，仅包括等待上传的文件\n            this._queue = [];\n    \n            // 存储所有文件\n            this._map = {};\n        }\n    \n        $.extend( Queue.prototype, {\n    \n            /**\n             * 将新文件加入对队列尾部\n             *\n             * @method append\n             * @param  {File} file   文件对象\n             */\n            append: function( file ) {\n                this._queue.push( file );\n                this._fileAdded( file );\n                return this;\n            },\n    \n            /**\n             * 将新文件加入对队列头部\n             *\n             * @method prepend\n             * @param  {File} file   文件对象\n             */\n            prepend: function( file ) {\n                this._queue.unshift( file );\n                this._fileAdded( file );\n                return this;\n            },\n    \n            /**\n             * 获取文件对象\n             *\n             * @method getFile\n             * @param  {String} fileId   文件ID\n             * @return {File}\n             */\n            getFile: function( fileId ) {\n                if ( typeof fileId !== 'string' ) {\n                    return fileId;\n                }\n                return this._map[ fileId ];\n            },\n    \n            /**\n             * 从队列中取出一个指定状态的文件。\n             * @grammar fetch( status ) => File\n             * @method fetch\n             * @param {String} status [文件状态值](#WebUploader:File:File.Status)\n             * @return {File} [File](#WebUploader:File)\n             */\n            fetch: function( status ) {\n                var len = this._queue.length,\n                    i, file;\n    \n                status = status || STATUS.QUEUED;\n    \n                for ( i = 0; i < len; i++ ) {\n                    file = this._queue[ i ];\n    \n                    if ( status === file.getStatus() ) {\n                        return file;\n                    }\n                }\n    \n                return null;\n            },\n    \n            /**\n             * 对队列进行排序，能够控制文件上传顺序。\n             * @grammar sort( fn ) => undefined\n             * @method sort\n             * @param {Function} fn 排序方法\n             */\n            sort: function( fn ) {\n                if ( typeof fn === 'function' ) {\n                    this._queue.sort( fn );\n                }\n            },\n    \n            /**\n             * 获取指定类型的文件列表, 列表中每一个成员为[File](#WebUploader:File)对象。\n             * @grammar getFiles( [status1[, status2 ...]] ) => Array\n             * @method getFiles\n             * @param {String} [status] [文件状态值](#WebUploader:File:File.Status)\n             */\n            getFiles: function() {\n                var sts = [].slice.call( arguments, 0 ),\n                    ret = [],\n                    i = 0,\n                    len = this._queue.length,\n                    file;\n    \n                for ( ; i < len; i++ ) {\n                    file = this._queue[ i ];\n    \n                    if ( sts.length && !~$.inArray( file.getStatus(), sts ) ) {\n                        continue;\n                    }\n    \n                    ret.push( file );\n                }\n    \n                return ret;\n            },\n    \n            _fileAdded: function( file ) {\n                var me = this,\n                    existing = this._map[ file.id ];\n    \n                if ( !existing ) {\n                    this._map[ file.id ] = file;\n    \n                    file.on( 'statuschange', function( cur, pre ) {\n                        me._onFileStatusChange( cur, pre );\n                    });\n                }\n    \n                file.setStatus( STATUS.QUEUED );\n            },\n    \n            _onFileStatusChange: function( curStatus, preStatus ) {\n                var stats = this.stats;\n    \n                switch ( preStatus ) {\n                    case STATUS.PROGRESS:\n                        stats.numOfProgress--;\n                        break;\n    \n                    case STATUS.QUEUED:\n                        stats.numOfQueue --;\n                        break;\n    \n                    case STATUS.ERROR:\n                        stats.numOfUploadFailed--;\n                        break;\n    \n                    case STATUS.INVALID:\n                        stats.numOfInvalid--;\n                        break;\n                }\n    \n                switch ( curStatus ) {\n                    case STATUS.QUEUED:\n                        stats.numOfQueue++;\n                        break;\n    \n                    case STATUS.PROGRESS:\n                        stats.numOfProgress++;\n                        break;\n    \n                    case STATUS.ERROR:\n                        stats.numOfUploadFailed++;\n                        break;\n    \n                    case STATUS.COMPLETE:\n                        stats.numOfSuccess++;\n                        break;\n    \n                    case STATUS.CANCELLED:\n                        stats.numOfCancel++;\n                        break;\n    \n                    case STATUS.INVALID:\n                        stats.numOfInvalid++;\n                        break;\n                }\n            }\n    \n        });\n    \n        Mediator.installTo( Queue.prototype );\n    \n        return Queue;\n    });\n    /**\n     * @fileOverview 队列\n     */\n    define('widgets/queue',[\n        'base',\n        'uploader',\n        'queue',\n        'file',\n        'lib/file',\n        'runtime/client',\n        'widgets/widget'\n    ], function( Base, Uploader, Queue, WUFile, File, RuntimeClient ) {\n    \n        var $ = Base.$,\n            rExt = /\\.\\w+$/,\n            Status = WUFile.Status;\n    \n        return Uploader.register({\n            'sort-files': 'sortFiles',\n            'add-file': 'addFiles',\n            'get-file': 'getFile',\n            'fetch-file': 'fetchFile',\n            'get-stats': 'getStats',\n            'get-files': 'getFiles',\n            'remove-file': 'removeFile',\n            'retry': 'retry',\n            'reset': 'reset',\n            'accept-file': 'acceptFile'\n        }, {\n    \n            init: function( opts ) {\n                var me = this,\n                    deferred, len, i, item, arr, accept, runtime;\n    \n                if ( $.isPlainObject( opts.accept ) ) {\n                    opts.accept = [ opts.accept ];\n                }\n    \n                // accept中的中生成匹配正则。\n                if ( opts.accept ) {\n                    arr = [];\n    \n                    for ( i = 0, len = opts.accept.length; i < len; i++ ) {\n                        item = opts.accept[ i ].extensions;\n                        item && arr.push( item );\n                    }\n    \n                    if ( arr.length ) {\n                        accept = '\\\\.' + arr.join(',')\n                                .replace( /,/g, '$|\\\\.' )\n                                .replace( /\\*/g, '.*' ) + '$';\n                    }\n    \n                    me.accept = new RegExp( accept, 'i' );\n                }\n    \n                me.queue = new Queue();\n                me.stats = me.queue.stats;\n    \n                // 如果当前不是html5运行时，那就算了。\n                // 不执行后续操作\n                if ( this.request('predict-runtime-type') !== 'html5' ) {\n                    return;\n                }\n    \n                // 创建一个 html5 运行时的 placeholder\n                // 以至于外部添加原生 File 对象的时候能正确包裹一下供 webuploader 使用。\n                deferred = Base.Deferred();\n                runtime = new RuntimeClient('Placeholder');\n                runtime.connectRuntime({\n                    runtimeOrder: 'html5'\n                }, function() {\n                    me._ruid = runtime.getRuid();\n                    deferred.resolve();\n                });\n                return deferred.promise();\n            },\n    \n    \n            // 为了支持外部直接添加一个原生File对象。\n            _wrapFile: function( file ) {\n                if ( !(file instanceof WUFile) ) {\n    \n                    if ( !(file instanceof File) ) {\n                        if ( !this._ruid ) {\n                            throw new Error('Can\\'t add external files.');\n                        }\n                        file = new File( this._ruid, file );\n                    }\n    \n                    file = new WUFile( file );\n                }\n    \n                return file;\n            },\n    \n            // 判断文件是否可以被加入队列\n            acceptFile: function( file ) {\n                var invalid = !file || file.size < 6 || this.accept &&\n    \n                        // 如果名字中有后缀，才做后缀白名单处理。\n                        rExt.exec( file.name ) && !this.accept.test( file.name );\n    \n                return !invalid;\n            },\n    \n    \n            /**\n             * @event beforeFileQueued\n             * @param {File} file File对象\n             * @description 当文件被加入队列之前触发，此事件的handler返回值为`false`，则此文件不会被添加进入队列。\n             * @for  Uploader\n             */\n    \n            /**\n             * @event fileQueued\n             * @param {File} file File对象\n             * @description 当文件被加入队列以后触发。\n             * @for  Uploader\n             */\n    \n            _addFile: function( file ) {\n                var me = this;\n    \n                file = me._wrapFile( file );\n    \n                // 不过类型判断允许不允许，先派送 `beforeFileQueued`\n                if ( !me.owner.trigger( 'beforeFileQueued', file ) ) {\n                    return;\n                }\n    \n                // 类型不匹配，则派送错误事件，并返回。\n                if ( !me.acceptFile( file ) ) {\n                    me.owner.trigger( 'error', 'Q_TYPE_DENIED', file );\n                    return;\n                }\n    \n                me.queue.append( file );\n                me.owner.trigger( 'fileQueued', file );\n                return file;\n            },\n    \n            getFile: function( fileId ) {\n                return this.queue.getFile( fileId );\n            },\n    \n            /**\n             * @event filesQueued\n             * @param {File} files 数组，内容为原始File(lib/File）对象。\n             * @description 当一批文件添加进队列以后触发。\n             * @for  Uploader\n             */\n    \n            /**\n             * @method addFiles\n             * @grammar addFiles( file ) => undefined\n             * @grammar addFiles( [file1, file2 ...] ) => undefined\n             * @param {Array of File or File} [files] Files 对象 数组\n             * @description 添加文件到队列\n             * @for  Uploader\n             */\n            addFiles: function( files ) {\n                var me = this;\n    \n                if ( !files.length ) {\n                    files = [ files ];\n                }\n    \n                files = $.map( files, function( file ) {\n                    return me._addFile( file );\n                });\n    \n                me.owner.trigger( 'filesQueued', files );\n    \n                if ( me.options.auto ) {\n                    me.request('start-upload');\n                }\n            },\n    \n            getStats: function() {\n                return this.stats;\n            },\n    \n            /**\n             * @event fileDequeued\n             * @param {File} file File对象\n             * @description 当文件被移除队列后触发。\n             * @for  Uploader\n             */\n    \n            /**\n             * @method removeFile\n             * @grammar removeFile( file ) => undefined\n             * @grammar removeFile( id ) => undefined\n             * @param {File|id} file File对象或这File对象的id\n             * @description 移除某一文件。\n             * @for  Uploader\n             * @example\n             *\n             * $li.on('click', '.remove-this', function() {\n             *     uploader.removeFile( file );\n             * })\n             */\n            removeFile: function( file ) {\n                var me = this;\n    \n                file = file.id ? file : me.queue.getFile( file );\n    \n                file.setStatus( Status.CANCELLED );\n                me.owner.trigger( 'fileDequeued', file );\n            },\n    \n            /**\n             * @method getFiles\n             * @grammar getFiles() => Array\n             * @grammar getFiles( status1, status2, status... ) => Array\n             * @description 返回指定状态的文件集合，不传参数将返回所有状态的文件。\n             * @for  Uploader\n             * @example\n             * console.log( uploader.getFiles() );    // => all files\n             * console.log( uploader.getFiles('error') )    // => all error files.\n             */\n            getFiles: function() {\n                return this.queue.getFiles.apply( this.queue, arguments );\n            },\n    \n            fetchFile: function() {\n                return this.queue.fetch.apply( this.queue, arguments );\n            },\n    \n            /**\n             * @method retry\n             * @grammar retry() => undefined\n             * @grammar retry( file ) => undefined\n             * @description 重试上传，重试指定文件，或者从出错的文件开始重新上传。\n             * @for  Uploader\n             * @example\n             * function retry() {\n             *     uploader.retry();\n             * }\n             */\n            retry: function( file, noForceStart ) {\n                var me = this,\n                    files, i, len;\n    \n                if ( file ) {\n                    file = file.id ? file : me.queue.getFile( file );\n                    file.setStatus( Status.QUEUED );\n                    noForceStart || me.request('start-upload');\n                    return;\n                }\n    \n                files = me.queue.getFiles( Status.ERROR );\n                i = 0;\n                len = files.length;\n    \n                for ( ; i < len; i++ ) {\n                    file = files[ i ];\n                    file.setStatus( Status.QUEUED );\n                }\n    \n                me.request('start-upload');\n            },\n    \n            /**\n             * @method sort\n             * @grammar sort( fn ) => undefined\n             * @description 排序队列中的文件，在上传之前调整可以控制上传顺序。\n             * @for  Uploader\n             */\n            sortFiles: function() {\n                return this.queue.sort.apply( this.queue, arguments );\n            },\n    \n            /**\n             * @method reset\n             * @grammar reset() => undefined\n             * @description 重置uploader。目前只重置了队列。\n             * @for  Uploader\n             * @example\n             * uploader.reset();\n             */\n            reset: function() {\n                this.queue = new Queue();\n                this.stats = this.queue.stats;\n            }\n        });\n    \n    });\n    /**\n     * @fileOverview 添加获取Runtime相关信息的方法。\n     */\n    define('widgets/runtime',[\n        'uploader',\n        'runtime/runtime',\n        'widgets/widget'\n    ], function( Uploader, Runtime ) {\n    \n        Uploader.support = function() {\n            return Runtime.hasRuntime.apply( Runtime, arguments );\n        };\n    \n        return Uploader.register({\n            'predict-runtime-type': 'predictRuntmeType'\n        }, {\n    \n            init: function() {\n                if ( !this.predictRuntmeType() ) {\n                    throw Error('Runtime Error');\n                }\n            },\n    \n            /**\n             * 预测Uploader将采用哪个`Runtime`\n             * @grammar predictRuntmeType() => String\n             * @method predictRuntmeType\n             * @for  Uploader\n             */\n            predictRuntmeType: function() {\n                var orders = this.options.runtimeOrder || Runtime.orders,\n                    type = this.type,\n                    i, len;\n    \n                if ( !type ) {\n                    orders = orders.split( /\\s*,\\s*/g );\n    \n                    for ( i = 0, len = orders.length; i < len; i++ ) {\n                        if ( Runtime.hasRuntime( orders[ i ] ) ) {\n                            this.type = type = orders[ i ];\n                            break;\n                        }\n                    }\n                }\n    \n                return type;\n            }\n        });\n    });\n    /**\n     * @fileOverview Transport\n     */\n    define('lib/transport',[\n        'base',\n        'runtime/client',\n        'mediator'\n    ], function( Base, RuntimeClient, Mediator ) {\n    \n        var $ = Base.$;\n    \n        function Transport( opts ) {\n            var me = this;\n    \n            opts = me.options = $.extend( true, {}, Transport.options, opts || {} );\n            RuntimeClient.call( this, 'Transport' );\n    \n            this._blob = null;\n            this._formData = opts.formData || {};\n            this._headers = opts.headers || {};\n    \n            this.on( 'progress', this._timeout );\n            this.on( 'load error', function() {\n                me.trigger( 'progress', 1 );\n                clearTimeout( me._timer );\n            });\n        }\n    \n        Transport.options = {\n            server: '',\n            method: 'POST',\n    \n            // 跨域时，是否允许携带cookie, 只有html5 runtime才有效\n            withCredentials: false,\n            fileVal: 'file',\n            timeout: 2 * 60 * 1000,    // 2分钟\n            formData: {},\n            headers: {},\n            sendAsBinary: false\n        };\n    \n        $.extend( Transport.prototype, {\n    \n            // 添加Blob, 只能添加一次，最后一次有效。\n            appendBlob: function( key, blob, filename ) {\n                var me = this,\n                    opts = me.options;\n    \n                if ( me.getRuid() ) {\n                    me.disconnectRuntime();\n                }\n    \n                // 连接到blob归属的同一个runtime.\n                me.connectRuntime( blob.ruid, function() {\n                    me.exec('init');\n                });\n    \n                me._blob = blob;\n                opts.fileVal = key || opts.fileVal;\n                opts.filename = filename || opts.filename;\n            },\n    \n            // 添加其他字段\n            append: function( key, value ) {\n                if ( typeof key === 'object' ) {\n                    $.extend( this._formData, key );\n                } else {\n                    this._formData[ key ] = value;\n                }\n            },\n    \n            setRequestHeader: function( key, value ) {\n                if ( typeof key === 'object' ) {\n                    $.extend( this._headers, key );\n                } else {\n                    this._headers[ key ] = value;\n                }\n            },\n    \n            send: function( method ) {\n                this.exec( 'send', method );\n                this._timeout();\n            },\n    \n            abort: function() {\n                clearTimeout( this._timer );\n                return this.exec('abort');\n            },\n    \n            destroy: function() {\n                this.trigger('destroy');\n                this.off();\n                this.exec('destroy');\n                this.disconnectRuntime();\n            },\n    \n            getResponse: function() {\n                return this.exec('getResponse');\n            },\n    \n            getResponseAsJson: function() {\n                return this.exec('getResponseAsJson');\n            },\n    \n            getStatus: function() {\n                return this.exec('getStatus');\n            },\n    \n            _timeout: function() {\n                var me = this,\n                    duration = me.options.timeout;\n    \n                if ( !duration ) {\n                    return;\n                }\n    \n                clearTimeout( me._timer );\n                me._timer = setTimeout(function() {\n                    me.abort();\n                    me.trigger( 'error', 'timeout' );\n                }, duration );\n            }\n    \n        });\n    \n        // 让Transport具备事件功能。\n        Mediator.installTo( Transport.prototype );\n    \n        return Transport;\n    });\n    /**\n     * @fileOverview 负责文件上传相关。\n     */\n    define('widgets/upload',[\n        'base',\n        'uploader',\n        'file',\n        'lib/transport',\n        'widgets/widget'\n    ], function( Base, Uploader, WUFile, Transport ) {\n    \n        var $ = Base.$,\n            isPromise = Base.isPromise,\n            Status = WUFile.Status;\n    \n        // 添加默认配置项\n        $.extend( Uploader.options, {\n    \n    \n            /**\n             * @property {Boolean} [prepareNextFile=false]\n             * @namespace options\n             * @for Uploader\n             * @description 是否允许在文件传输时提前把下一个文件准备好。\n             * 对于一个文件的准备工作比较耗时，比如图片压缩，md5序列化。\n             * 如果能提前在当前文件传输期处理，可以节省总体耗时。\n             */\n            prepareNextFile: false,\n    \n            /**\n             * @property {Boolean} [chunked=false]\n             * @namespace options\n             * @for Uploader\n             * @description 是否要分片处理大文件上传。\n             */\n            chunked: false,\n    \n            /**\n             * @property {Boolean} [chunkSize=5242880]\n             * @namespace options\n             * @for Uploader\n             * @description 如果要分片，分多大一片？ 默认大小为5M.\n             */\n            chunkSize: 5 * 1024 * 1024,\n    \n            /**\n             * @property {Boolean} [chunkRetry=2]\n             * @namespace options\n             * @for Uploader\n             * @description 如果某个分片由于网络问题出错，允许自动重传多少次？\n             */\n            chunkRetry: 2,\n    \n            /**\n             * @property {Boolean} [threads=3]\n             * @namespace options\n             * @for Uploader\n             * @description 上传并发数。允许同时最大上传进程数。\n             */\n            threads: 3,\n    \n    \n            /**\n             * @property {Object} [formData]\n             * @namespace options\n             * @for Uploader\n             * @description 文件上传请求的参数表，每次发送都会发送此对象中的参数。\n             */\n            formData: null\n    \n            /**\n             * @property {Object} [fileVal='file']\n             * @namespace options\n             * @for Uploader\n             * @description 设置文件上传域的name。\n             */\n    \n            /**\n             * @property {Object} [method='POST']\n             * @namespace options\n             * @for Uploader\n             * @description 文件上传方式，`POST`或者`GET`。\n             */\n    \n            /**\n             * @property {Object} [sendAsBinary=false]\n             * @namespace options\n             * @for Uploader\n             * @description 是否已二进制的流的方式发送文件，这样整个上传内容`php://input`都为文件内容，\n             * 其他参数在$_GET数组中。\n             */\n        });\n    \n        // 负责将文件切片。\n        function CuteFile( file, chunkSize ) {\n            var pending = [],\n                blob = file.source,\n                total = blob.size,\n                chunks = chunkSize ? Math.ceil( total / chunkSize ) : 1,\n                start = 0,\n                index = 0,\n                len;\n    \n            while ( index < chunks ) {\n                len = Math.min( chunkSize, total - start );\n    \n                pending.push({\n                    file: file,\n                    start: start,\n                    end: chunkSize ? (start + len) : total,\n                    total: total,\n                    chunks: chunks,\n                    chunk: index++\n                });\n                start += len;\n            }\n    \n            file.blocks = pending.concat();\n            file.remaning = pending.length;\n    \n            return {\n                file: file,\n    \n                has: function() {\n                    return !!pending.length;\n                },\n    \n                fetch: function() {\n                    return pending.shift();\n                }\n            };\n        }\n    \n        Uploader.register({\n            'start-upload': 'start',\n            'stop-upload': 'stop',\n            'skip-file': 'skipFile',\n            'is-in-progress': 'isInProgress'\n        }, {\n    \n            init: function() {\n                var owner = this.owner;\n    \n                this.runing = false;\n    \n                // 记录当前正在传的数据，跟threads相关\n                this.pool = [];\n    \n                // 缓存即将上传的文件。\n                this.pending = [];\n    \n                // 跟踪还有多少分片没有完成上传。\n                this.remaning = 0;\n                this.__tick = Base.bindFn( this._tick, this );\n    \n                owner.on( 'uploadComplete', function( file ) {\n                    // 把其他块取消了。\n                    file.blocks && $.each( file.blocks, function( _, v ) {\n                        v.transport && (v.transport.abort(), v.transport.destroy());\n                        delete v.transport;\n                    });\n    \n                    delete file.blocks;\n                    delete file.remaning;\n                });\n            },\n    \n            /**\n             * @event startUpload\n             * @description 当开始上传流程时触发。\n             * @for  Uploader\n             */\n    \n            /**\n             * 开始上传。此方法可以从初始状态调用开始上传流程，也可以从暂停状态调用，继续上传流程。\n             * @grammar upload() => undefined\n             * @method upload\n             * @for  Uploader\n             */\n            start: function() {\n                var me = this;\n    \n                // 移出invalid的文件\n                $.each( me.request( 'get-files', Status.INVALID ), function() {\n                    me.request( 'remove-file', this );\n                });\n    \n                if ( me.runing ) {\n                    return;\n                }\n    \n                me.runing = true;\n    \n                // 如果有暂停的，则续传\n                $.each( me.pool, function( _, v ) {\n                    var file = v.file;\n    \n                    if ( file.getStatus() === Status.INTERRUPT ) {\n                        file.setStatus( Status.PROGRESS );\n                        me._trigged = false;\n                        v.transport && v.transport.send();\n                    }\n                });\n    \n                me._trigged = false;\n                me.owner.trigger('startUpload');\n                Base.nextTick( me.__tick );\n            },\n    \n            /**\n             * @event stopUpload\n             * @description 当开始上传流程暂停时触发。\n             * @for  Uploader\n             */\n    \n            /**\n             * 暂停上传。第一个参数为是否中断上传当前正在上传的文件。\n             * @grammar stop() => undefined\n             * @grammar stop( true ) => undefined\n             * @method stop\n             * @for  Uploader\n             */\n            stop: function( interrupt ) {\n                var me = this;\n    \n                if ( me.runing === false ) {\n                    return;\n                }\n    \n                me.runing = false;\n    \n                interrupt && $.each( me.pool, function( _, v ) {\n                    v.transport && v.transport.abort();\n                    v.file.setStatus( Status.INTERRUPT );\n                });\n    \n                me.owner.trigger('stopUpload');\n            },\n    \n            /**\n             * 判断`Uplaode`r是否正在上传中。\n             * @grammar isInProgress() => Boolean\n             * @method isInProgress\n             * @for  Uploader\n             */\n            isInProgress: function() {\n                return !!this.runing;\n            },\n    \n            getStats: function() {\n                return this.request('get-stats');\n            },\n    \n            /**\n             * 掉过一个文件上传，直接标记指定文件为已上传状态。\n             * @grammar skipFile( file ) => undefined\n             * @method skipFile\n             * @for  Uploader\n             */\n            skipFile: function( file, status ) {\n                file = this.request( 'get-file', file );\n    \n                file.setStatus( status || Status.COMPLETE );\n                file.skipped = true;\n    \n                // 如果正在上传。\n                file.blocks && $.each( file.blocks, function( _, v ) {\n                    var _tr = v.transport;\n    \n                    if ( _tr ) {\n                        _tr.abort();\n                        _tr.destroy();\n                        delete v.transport;\n                    }\n                });\n    \n                this.owner.trigger( 'uploadSkip', file );\n            },\n    \n            /**\n             * @event uploadFinished\n             * @description 当所有文件上传结束时触发。\n             * @for  Uploader\n             */\n            _tick: function() {\n                var me = this,\n                    opts = me.options,\n                    fn, val;\n    \n                // 上一个promise还没有结束，则等待完成后再执行。\n                if ( me._promise ) {\n                    return me._promise.always( me.__tick );\n                }\n    \n                // 还有位置，且还有文件要处理的话。\n                if ( me.pool.length < opts.threads && (val = me._nextBlock()) ) {\n                    me._trigged = false;\n    \n                    fn = function( val ) {\n                        me._promise = null;\n    \n                        // 有可能是reject过来的，所以要检测val的类型。\n                        val && val.file && me._startSend( val );\n                        Base.nextTick( me.__tick );\n                    };\n    \n                    me._promise = isPromise( val ) ? val.always( fn ) : fn( val );\n    \n                // 没有要上传的了，且没有正在传输的了。\n                } else if ( !me.remaning && !me.getStats().numOfQueue ) {\n                    me.runing = false;\n    \n                    me._trigged || Base.nextTick(function() {\n                        me.owner.trigger('uploadFinished');\n                    });\n                    me._trigged = true;\n                }\n            },\n    \n            _nextBlock: function() {\n                var me = this,\n                    act = me._act,\n                    opts = me.options,\n                    next, done;\n    \n                // 如果当前文件还有没有需要传输的，则直接返回剩下的。\n                if ( act && act.has() &&\n                        act.file.getStatus() === Status.PROGRESS ) {\n    \n                    // 是否提前准备下一个文件\n                    if ( opts.prepareNextFile && !me.pending.length ) {\n                        me._prepareNextFile();\n                    }\n    \n                    return act.fetch();\n    \n                // 否则，如果正在运行，则准备下一个文件，并等待完成后返回下个分片。\n                } else if ( me.runing ) {\n    \n                    // 如果缓存中有，则直接在缓存中取，没有则去queue中取。\n                    if ( !me.pending.length && me.getStats().numOfQueue ) {\n                        me._prepareNextFile();\n                    }\n    \n                    next = me.pending.shift();\n                    done = function( file ) {\n                        if ( !file ) {\n                            return null;\n                        }\n    \n                        act = CuteFile( file, opts.chunked ? opts.chunkSize : 0 );\n                        me._act = act;\n                        return act.fetch();\n                    };\n    \n                    // 文件可能还在prepare中，也有可能已经完全准备好了。\n                    return isPromise( next ) ?\n                            next[ next.pipe ? 'pipe' : 'then']( done ) :\n                            done( next );\n                }\n            },\n    \n    \n            /**\n             * @event uploadStart\n             * @param {File} file File对象\n             * @description 某个文件开始上传前触发，一个文件只会触发一次。\n             * @for  Uploader\n             */\n            _prepareNextFile: function() {\n                var me = this,\n                    file = me.request('fetch-file'),\n                    pending = me.pending,\n                    promise;\n    \n                if ( file ) {\n                    promise = me.request( 'before-send-file', file, function() {\n    \n                        // 有可能文件被skip掉了。文件被skip掉后，状态坑定不是Queued.\n                        if ( file.getStatus() === Status.QUEUED ) {\n                            me.owner.trigger( 'uploadStart', file );\n                            file.setStatus( Status.PROGRESS );\n                            return file;\n                        }\n    \n                        return me._finishFile( file );\n                    });\n    \n                    // 如果还在pending中，则替换成文件本身。\n                    promise.done(function() {\n                        var idx = $.inArray( promise, pending );\n    \n                        ~idx && pending.splice( idx, 1, file );\n                    });\n    \n                    // befeore-send-file的钩子就有错误发生。\n                    promise.fail(function( reason ) {\n                        file.setStatus( Status.ERROR, reason );\n                        me.owner.trigger( 'uploadError', file, reason );\n                        me.owner.trigger( 'uploadComplete', file );\n                    });\n    \n                    pending.push( promise );\n                }\n            },\n    \n            // 让出位置了，可以让其他分片开始上传\n            _popBlock: function( block ) {\n                var idx = $.inArray( block, this.pool );\n    \n                this.pool.splice( idx, 1 );\n                block.file.remaning--;\n                this.remaning--;\n            },\n    \n            // 开始上传，可以被掉过。如果promise被reject了，则表示跳过此分片。\n            _startSend: function( block ) {\n                var me = this,\n                    file = block.file,\n                    promise;\n    \n                me.pool.push( block );\n                me.remaning++;\n    \n                // 如果没有分片，则直接使用原始的。\n                // 不会丢失content-type信息。\n                block.blob = block.chunks === 1 ? file.source :\n                        file.source.slice( block.start, block.end );\n    \n                // hook, 每个分片发送之前可能要做些异步的事情。\n                promise = me.request( 'before-send', block, function() {\n    \n                    // 有可能文件已经上传出错了，所以不需要再传输了。\n                    if ( file.getStatus() === Status.PROGRESS ) {\n                        me._doSend( block );\n                    } else {\n                        me._popBlock( block );\n                        Base.nextTick( me.__tick );\n                    }\n                });\n    \n                // 如果为fail了，则跳过此分片。\n                promise.fail(function() {\n                    if ( file.remaning === 1 ) {\n                        me._finishFile( file ).always(function() {\n                            block.percentage = 1;\n                            me._popBlock( block );\n                            me.owner.trigger( 'uploadComplete', file );\n                            Base.nextTick( me.__tick );\n                        });\n                    } else {\n                        block.percentage = 1;\n                        me._popBlock( block );\n                        Base.nextTick( me.__tick );\n                    }\n                });\n            },\n    \n    \n            /**\n             * @event uploadBeforeSend\n             * @param {Object} object\n             * @param {Object} data 默认的上传参数，可以扩展此对象来控制上传参数。\n             * @description 当某个文件的分块在发送前触发，主要用来询问是否要添加附带参数，大文件在开起分片上传的前提下此事件可能会触发多次。\n             * @for  Uploader\n             */\n    \n            /**\n             * @event uploadAccept\n             * @param {Object} object\n             * @param {Object} ret 服务端的返回数据，json格式，如果服务端不是json格式，从ret._raw中取数据，自行解析。\n             * @description 当某个文件上传到服务端响应后，会派送此事件来询问服务端响应是否有效。如果此事件handler返回值为`false`, 则此文件将派送`server`类型的`uploadError`事件。\n             * @for  Uploader\n             */\n    \n            /**\n             * @event uploadProgress\n             * @param {File} file File对象\n             * @param {Number} percentage 上传进度\n             * @description 上传过程中触发，携带上传进度。\n             * @for  Uploader\n             */\n    \n    \n            /**\n             * @event uploadError\n             * @param {File} file File对象\n             * @param {String} reason 出错的code\n             * @description 当文件上传出错时触发。\n             * @for  Uploader\n             */\n    \n            /**\n             * @event uploadSuccess\n             * @param {File} file File对象\n             * @param {Object} response 服务端返回的数据\n             * @description 当文件上传成功时触发。\n             * @for  Uploader\n             */\n    \n            /**\n             * @event uploadComplete\n             * @param {File} [file] File对象\n             * @description 不管成功或者失败，文件上传完成时触发。\n             * @for  Uploader\n             */\n    \n            // 做上传操作。\n            _doSend: function( block ) {\n                var me = this,\n                    owner = me.owner,\n                    opts = me.options,\n                    file = block.file,\n                    tr = new Transport( opts ),\n                    data = $.extend({}, opts.formData ),\n                    headers = $.extend({}, opts.headers ),\n                    requestAccept, ret;\n    \n                block.transport = tr;\n    \n                tr.on( 'destroy', function() {\n                    delete block.transport;\n                    me._popBlock( block );\n                    Base.nextTick( me.__tick );\n                });\n    \n                // 广播上传进度。以文件为单位。\n                tr.on( 'progress', function( percentage ) {\n                    var totalPercent = 0,\n                        uploaded = 0;\n    \n                    // 可能没有abort掉，progress还是执行进来了。\n                    // if ( !file.blocks ) {\n                    //     return;\n                    // }\n    \n                    totalPercent = block.percentage = percentage;\n    \n                    if ( block.chunks > 1 ) {    // 计算文件的整体速度。\n                        $.each( file.blocks, function( _, v ) {\n                            uploaded += (v.percentage || 0) * (v.end - v.start);\n                        });\n    \n                        totalPercent = uploaded / file.size;\n                    }\n    \n                    owner.trigger( 'uploadProgress', file, totalPercent || 0 );\n                });\n    \n                // 用来询问，是否返回的结果是有错误的。\n                requestAccept = function( reject ) {\n                    var fn;\n    \n                    ret = tr.getResponseAsJson() || {};\n                    ret._raw = tr.getResponse();\n                    fn = function( value ) {\n                        reject = value;\n                    };\n    \n                    // 服务端响应了，不代表成功了，询问是否响应正确。\n                    if ( !owner.trigger( 'uploadAccept', block, ret, fn ) ) {\n                        reject = reject || 'server';\n                    }\n    \n                    return reject;\n                };\n    \n                // 尝试重试，然后广播文件上传出错。\n                tr.on( 'error', function( type, flag ) {\n                    block.retried = block.retried || 0;\n    \n                    // 自动重试\n                    if ( block.chunks > 1 && ~'http,abort'.indexOf( type ) &&\n                            block.retried < opts.chunkRetry ) {\n    \n                        block.retried++;\n                        tr.send();\n    \n                    } else {\n    \n                        // http status 500 ~ 600\n                        if ( !flag && type === 'server' ) {\n                            type = requestAccept( type );\n                        }\n    \n                        file.setStatus( Status.ERROR, type );\n                        owner.trigger( 'uploadError', file, type );\n                        owner.trigger( 'uploadComplete', file );\n                    }\n                });\n    \n                // 上传成功\n                tr.on( 'load', function() {\n                    var reason;\n    \n                    // 如果非预期，转向上传出错。\n                    if ( (reason = requestAccept()) ) {\n                        tr.trigger( 'error', reason, true );\n                        return;\n                    }\n    \n                    // 全部上传完成。\n                    if ( file.remaning === 1 ) {\n                        me._finishFile( file, ret );\n                    } else {\n                        tr.destroy();\n                    }\n                });\n    \n                // 配置默认的上传字段。\n                data = $.extend( data, {\n                    id: file.id,\n                    name: file.name,\n                    type: file.type,\n                    lastModifiedDate: file.lastModifiedDate,\n                    size: file.size\n                });\n    \n                block.chunks > 1 && $.extend( data, {\n                    chunks: block.chunks,\n                    chunk: block.chunk\n                });\n    \n                // 在发送之间可以添加字段什么的。。。\n                // 如果默认的字段不够使用，可以通过监听此事件来扩展\n                owner.trigger( 'uploadBeforeSend', block, data, headers );\n    \n                // 开始发送。\n                tr.appendBlob( opts.fileVal, block.blob, file.name );\n                tr.append( data );\n                tr.setRequestHeader( headers );\n                tr.send();\n            },\n    \n            // 完成上传。\n            _finishFile: function( file, ret, hds ) {\n                var owner = this.owner;\n    \n                return owner\n                        .request( 'after-send-file', arguments, function() {\n                            file.setStatus( Status.COMPLETE );\n                            owner.trigger( 'uploadSuccess', file, ret, hds );\n                        })\n                        .fail(function( reason ) {\n    \n                            // 如果外部已经标记为invalid什么的，不再改状态。\n                            if ( file.getStatus() === Status.PROGRESS ) {\n                                file.setStatus( Status.ERROR, reason );\n                            }\n    \n                            owner.trigger( 'uploadError', file, reason );\n                        })\n                        .always(function() {\n                            owner.trigger( 'uploadComplete', file );\n                        });\n            }\n    \n        });\n    });\n    /**\n     * @fileOverview 各种验证，包括文件总大小是否超出、单文件是否超出和文件是否重复。\n     */\n    \n    define('widgets/validator',[\n        'base',\n        'uploader',\n        'file',\n        'widgets/widget'\n    ], function( Base, Uploader, WUFile ) {\n    \n        var $ = Base.$,\n            validators = {},\n            api;\n    \n        /**\n         * @event error\n         * @param {String} type 错误类型。\n         * @description 当validate不通过时，会以派送错误事件的形式通知调用者。通过`upload.on('error', handler)`可以捕获到此类错误，目前有以下错误会在特定的情况下派送错来。\n         *\n         * * `Q_EXCEED_NUM_LIMIT` 在设置了`fileNumLimit`且尝试给`uploader`添加的文件数量超出这个值时派送。\n         * * `Q_EXCEED_SIZE_LIMIT` 在设置了`Q_EXCEED_SIZE_LIMIT`且尝试给`uploader`添加的文件总大小超出这个值时派送。\n         * @for  Uploader\n         */\n    \n        // 暴露给外面的api\n        api = {\n    \n            // 添加验证器\n            addValidator: function( type, cb ) {\n                validators[ type ] = cb;\n            },\n    \n            // 移除验证器\n            removeValidator: function( type ) {\n                delete validators[ type ];\n            }\n        };\n    \n        // 在Uploader初始化的时候启动Validators的初始化\n        Uploader.register({\n            init: function() {\n                var me = this;\n                $.each( validators, function() {\n                    this.call( me.owner );\n                });\n            }\n        });\n    \n        /**\n         * @property {int} [fileNumLimit=undefined]\n         * @namespace options\n         * @for Uploader\n         * @description 验证文件总数量, 超出则不允许加入队列。\n         */\n        api.addValidator( 'fileNumLimit', function() {\n            var uploader = this,\n                opts = uploader.options,\n                count = 0,\n                max = opts.fileNumLimit >> 0,\n                flag = true;\n    \n            if ( !max ) {\n                return;\n            }\n    \n            uploader.on( 'beforeFileQueued', function( file ) {\n    \n                if ( count >= max && flag ) {\n                    flag = false;\n                    this.trigger( 'error', 'Q_EXCEED_NUM_LIMIT', max, file );\n                    setTimeout(function() {\n                        flag = true;\n                    }, 1 );\n                }\n    \n                return count >= max ? false : true;\n            });\n    \n            uploader.on( 'fileQueued', function() {\n                count++;\n            });\n    \n            uploader.on( 'fileDequeued', function() {\n                count--;\n            });\n    \n            uploader.on( 'uploadFinished', function() {\n                count = 0;\n            });\n        });\n    \n    \n        /**\n         * @property {int} [fileSizeLimit=undefined]\n         * @namespace options\n         * @for Uploader\n         * @description 验证文件总大小是否超出限制, 超出则不允许加入队列。\n         */\n        api.addValidator( 'fileSizeLimit', function() {\n            var uploader = this,\n                opts = uploader.options,\n                count = 0,\n                max = opts.fileSizeLimit >> 0,\n                flag = true;\n    \n            if ( !max ) {\n                return;\n            }\n    \n            uploader.on( 'beforeFileQueued', function( file ) {\n                var invalid = count + file.size > max;\n    \n                if ( invalid && flag ) {\n                    flag = false;\n                    this.trigger( 'error', 'Q_EXCEED_SIZE_LIMIT', max, file );\n                    setTimeout(function() {\n                        flag = true;\n                    }, 1 );\n                }\n    \n                return invalid ? false : true;\n            });\n    \n            uploader.on( 'fileQueued', function( file ) {\n                count += file.size;\n            });\n    \n            uploader.on( 'fileDequeued', function( file ) {\n                count -= file.size;\n            });\n    \n            uploader.on( 'uploadFinished', function() {\n                count = 0;\n            });\n        });\n    \n        /**\n         * @property {int} [fileSingleSizeLimit=undefined]\n         * @namespace options\n         * @for Uploader\n         * @description 验证单个文件大小是否超出限制, 超出则不允许加入队列。\n         */\n        api.addValidator( 'fileSingleSizeLimit', function() {\n            var uploader = this,\n                opts = uploader.options,\n                max = opts.fileSingleSizeLimit;\n    \n            if ( !max ) {\n                return;\n            }\n    \n            uploader.on( 'beforeFileQueued', function( file ) {\n    \n                if ( file.size > max ) {\n                    file.setStatus( WUFile.Status.INVALID, 'exceed_size' );\n                    this.trigger( 'error', 'F_EXCEED_SIZE', file );\n                    return false;\n                }\n    \n            });\n    \n        });\n    \n        /**\n         * @property {int} [duplicate=undefined]\n         * @namespace options\n         * @for Uploader\n         * @description 去重， 根据文件名字、文件大小和最后修改时间来生成hash Key.\n         */\n        api.addValidator( 'duplicate', function() {\n            var uploader = this,\n                opts = uploader.options,\n                mapping = {};\n    \n            if ( opts.duplicate ) {\n                return;\n            }\n    \n            function hashString( str ) {\n                var hash = 0,\n                    i = 0,\n                    len = str.length,\n                    _char;\n    \n                for ( ; i < len; i++ ) {\n                    _char = str.charCodeAt( i );\n                    hash = _char + (hash << 6) + (hash << 16) - hash;\n                }\n    \n                return hash;\n            }\n    \n            uploader.on( 'beforeFileQueued', function( file ) {\n                var hash = file.__hash || (file.__hash = hashString( file.name +\n                        file.size + file.lastModifiedDate ));\n    \n                // 已经重复了\n                if ( mapping[ hash ] ) {\n                    this.trigger( 'error', 'F_DUPLICATE', file );\n                    return false;\n                }\n            });\n    \n            uploader.on( 'fileQueued', function( file ) {\n                var hash = file.__hash;\n    \n                hash && (mapping[ hash ] = true);\n            });\n    \n            uploader.on( 'fileDequeued', function( file ) {\n                var hash = file.__hash;\n    \n                hash && (delete mapping[ hash ]);\n            });\n        });\n    \n        return api;\n    });\n    \n    /**\n     * @fileOverview Runtime管理器，负责Runtime的选择, 连接\n     */\n    define('runtime/compbase',[],function() {\n    \n        function CompBase( owner, runtime ) {\n    \n            this.owner = owner;\n            this.options = owner.options;\n    \n            this.getRuntime = function() {\n                return runtime;\n            };\n    \n            this.getRuid = function() {\n                return runtime.uid;\n            };\n    \n            this.trigger = function() {\n                return owner.trigger.apply( owner, arguments );\n            };\n        }\n    \n        return CompBase;\n    });\n    /**\n     * @fileOverview Html5Runtime\n     */\n    define('runtime/html5/runtime',[\n        'base',\n        'runtime/runtime',\n        'runtime/compbase'\n    ], function( Base, Runtime, CompBase ) {\n    \n        var type = 'html5',\n            components = {};\n    \n        function Html5Runtime() {\n            var pool = {},\n                me = this,\n                destory = this.destory;\n    \n            Runtime.apply( me, arguments );\n            me.type = type;\n    \n    \n            // 这个方法的调用者，实际上是RuntimeClient\n            me.exec = function( comp, fn/*, args...*/) {\n                var client = this,\n                    uid = client.uid,\n                    args = Base.slice( arguments, 2 ),\n                    instance;\n    \n                if ( components[ comp ] ) {\n                    instance = pool[ uid ] = pool[ uid ] ||\n                            new components[ comp ]( client, me );\n    \n                    if ( instance[ fn ] ) {\n                        return instance[ fn ].apply( instance, args );\n                    }\n                }\n            };\n    \n            me.destory = function() {\n                // @todo 删除池子中的所有实例\n                return destory && destory.apply( this, arguments );\n            };\n        }\n    \n        Base.inherits( Runtime, {\n            constructor: Html5Runtime,\n    \n            // 不需要连接其他程序，直接执行callback\n            init: function() {\n                var me = this;\n                setTimeout(function() {\n                    me.trigger('ready');\n                }, 1 );\n            }\n    \n        });\n    \n        // 注册Components\n        Html5Runtime.register = function( name, component ) {\n            var klass = components[ name ] = Base.inherits( CompBase, component );\n            return klass;\n        };\n    \n        // 注册html5运行时。\n        // 只有在支持的前提下注册。\n        if ( window.Blob && window.FileReader && window.DataView ) {\n            Runtime.addRuntime( type, Html5Runtime );\n        }\n    \n        return Html5Runtime;\n    });\n    /**\n     * @fileOverview Blob Html实现\n     */\n    define('runtime/html5/blob',[\n        'runtime/html5/runtime',\n        'lib/blob'\n    ], function( Html5Runtime, Blob ) {\n    \n        return Html5Runtime.register( 'Blob', {\n            slice: function( start, end ) {\n                var blob = this.owner.source,\n                    slice = blob.slice || blob.webkitSlice || blob.mozSlice;\n    \n                blob = slice.call( blob, start, end );\n    \n                return new Blob( this.getRuid(), blob );\n            }\n        });\n    });\n    /**\n     * @fileOverview FilePaste\n     */\n    define('runtime/html5/dnd',[\n        'base',\n        'runtime/html5/runtime',\n        'lib/file'\n    ], function( Base, Html5Runtime, File ) {\n    \n        var $ = Base.$,\n            prefix = 'webuploader-dnd-';\n    \n        return Html5Runtime.register( 'DragAndDrop', {\n            init: function() {\n                var elem = this.elem = this.options.container;\n    \n                this.dragEnterHandler = Base.bindFn( this._dragEnterHandler, this );\n                this.dragOverHandler = Base.bindFn( this._dragOverHandler, this );\n                this.dragLeaveHandler = Base.bindFn( this._dragLeaveHandler, this );\n                this.dropHandler = Base.bindFn( this._dropHandler, this );\n                this.dndOver = false;\n    \n                elem.on( 'dragenter', this.dragEnterHandler );\n                elem.on( 'dragover', this.dragOverHandler );\n                elem.on( 'dragleave', this.dragLeaveHandler );\n                elem.on( 'drop', this.dropHandler );\n    \n                if ( this.options.disableGlobalDnd ) {\n                    $( document ).on( 'dragover', this.dragOverHandler );\n                    $( document ).on( 'drop', this.dropHandler );\n                }\n            },\n    \n            _dragEnterHandler: function( e ) {\n                var me = this,\n                    denied = me._denied || false,\n                    items;\n    \n                e = e.originalEvent || e;\n    \n                if ( !me.dndOver ) {\n                    me.dndOver = true;\n    \n                    // 注意只有 chrome 支持。\n                    items = e.dataTransfer.items;\n    \n                    if ( items && items.length ) {\n                        me._denied = denied = !me.trigger( 'accept', items );\n                    }\n    \n                    me.elem.addClass( prefix + 'over' );\n                    me.elem[ denied ? 'addClass' :\n                            'removeClass' ]( prefix + 'denied' );\n                }\n    \n    \n                e.dataTransfer.dropEffect = denied ? 'none' : 'copy';\n    \n                return false;\n            },\n    \n            _dragOverHandler: function( e ) {\n                // 只处理框内的。\n                var parentElem = this.elem.parent().get( 0 );\n                if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) {\n                    return false;\n                }\n    \n                clearTimeout( this._leaveTimer );\n                this._dragEnterHandler.call( this, e );\n    \n                return false;\n            },\n    \n            _dragLeaveHandler: function() {\n                var me = this,\n                    handler;\n    \n                handler = function() {\n                    me.dndOver = false;\n                    me.elem.removeClass( prefix + 'over ' + prefix + 'denied' );\n                };\n    \n                clearTimeout( me._leaveTimer );\n                me._leaveTimer = setTimeout( handler, 100 );\n                return false;\n            },\n    \n            _dropHandler: function( e ) {\n                var me = this,\n                    ruid = me.getRuid(),\n                    parentElem = me.elem.parent().get( 0 );\n    \n                // 只处理框内的。\n                if ( parentElem && !$.contains( parentElem, e.currentTarget ) ) {\n                    return false;\n                }\n    \n                me._getTansferFiles( e, function( results ) {\n                    me.trigger( 'drop', $.map( results, function( file ) {\n                        return new File( ruid, file );\n                    }) );\n                });\n    \n                me.dndOver = false;\n                me.elem.removeClass( prefix + 'over' );\n                return false;\n            },\n    \n            // 如果传入 callback 则去查看文件夹，否则只管当前文件夹。\n            _getTansferFiles: function( e, callback ) {\n                var results  = [],\n                    promises = [],\n                    items, files, dataTransfer, file, item, i, len, canAccessFolder;\n    \n                e = e.originalEvent || e;\n    \n                dataTransfer = e.dataTransfer;\n                items = dataTransfer.items;\n                files = dataTransfer.files;\n    \n                canAccessFolder = !!(items && items[ 0 ].webkitGetAsEntry);\n    \n                for ( i = 0, len = files.length; i < len; i++ ) {\n                    file = files[ i ];\n                    item = items && items[ i ];\n    \n                    if ( canAccessFolder && item.webkitGetAsEntry().isDirectory ) {\n    \n                        promises.push( this._traverseDirectoryTree(\n                                item.webkitGetAsEntry(), results ) );\n                    } else {\n                        results.push( file );\n                    }\n                }\n    \n                Base.when.apply( Base, promises ).done(function() {\n    \n                    if ( !results.length ) {\n                        return;\n                    }\n    \n                    callback( results );\n                });\n            },\n    \n            _traverseDirectoryTree: function( entry, results ) {\n                var deferred = Base.Deferred(),\n                    me = this;\n    \n                if ( entry.isFile ) {\n                    entry.file(function( file ) {\n                        results.push( file );\n                        deferred.resolve();\n                    });\n                } else if ( entry.isDirectory ) {\n                    entry.createReader().readEntries(function( entries ) {\n                        var len = entries.length,\n                            promises = [],\n                            arr = [],    // 为了保证顺序。\n                            i;\n    \n                        for ( i = 0; i < len; i++ ) {\n                            promises.push( me._traverseDirectoryTree(\n                                    entries[ i ], arr ) );\n                        }\n    \n                        Base.when.apply( Base, promises ).then(function() {\n                            results.push.apply( results, arr );\n                            deferred.resolve();\n                        }, deferred.reject );\n                    });\n                }\n    \n                return deferred.promise();\n            },\n    \n            destroy: function() {\n                var elem = this.elem;\n    \n                elem.off( 'dragenter', this.dragEnterHandler );\n                elem.off( 'dragover', this.dragEnterHandler );\n                elem.off( 'dragleave', this.dragLeaveHandler );\n                elem.off( 'drop', this.dropHandler );\n    \n                if ( this.options.disableGlobalDnd ) {\n                    $( document ).off( 'dragover', this.dragOverHandler );\n                    $( document ).off( 'drop', this.dropHandler );\n                }\n            }\n        });\n    });\n    \n    /**\n     * @fileOverview FilePaste\n     */\n    define('runtime/html5/filepaste',[\n        'base',\n        'runtime/html5/runtime',\n        'lib/file'\n    ], function( Base, Html5Runtime, File ) {\n    \n        return Html5Runtime.register( 'FilePaste', {\n            init: function() {\n                var opts = this.options,\n                    elem = this.elem = opts.container,\n                    accept = '.*',\n                    arr, i, len, item;\n    \n                // accetp的mimeTypes中生成匹配正则。\n                if ( opts.accept ) {\n                    arr = [];\n    \n                    for ( i = 0, len = opts.accept.length; i < len; i++ ) {\n                        item = opts.accept[ i ].mimeTypes;\n                        item && arr.push( item );\n                    }\n    \n                    if ( arr.length ) {\n                        accept = arr.join(',');\n                        accept = accept.replace( /,/g, '|' ).replace( /\\*/g, '.*' );\n                    }\n                }\n                this.accept = accept = new RegExp( accept, 'i' );\n                this.hander = Base.bindFn( this._pasteHander, this );\n                elem.on( 'paste', this.hander );\n            },\n    \n            _pasteHander: function( e ) {\n                var allowed = [],\n                    ruid = this.getRuid(),\n                    items, item, blob, i, len;\n    \n                e = e.originalEvent || e;\n                items = e.clipboardData.items;\n    \n                for ( i = 0, len = items.length; i < len; i++ ) {\n                    item = items[ i ];\n    \n                    if ( item.kind !== 'file' || !(blob = item.getAsFile()) ) {\n                        continue;\n                    }\n    \n                    allowed.push( new File( ruid, blob ) );\n                }\n    \n                if ( allowed.length ) {\n                    // 不阻止非文件粘贴（文字粘贴）的事件冒泡\n                    e.preventDefault();\n                    e.stopPropagation();\n                    this.trigger( 'paste', allowed );\n                }\n            },\n    \n            destroy: function() {\n                this.elem.off( 'paste', this.hander );\n            }\n        });\n    });\n    \n    /**\n     * @fileOverview FilePicker\n     */\n    define('runtime/html5/filepicker',[\n        'base',\n        'runtime/html5/runtime'\n    ], function( Base, Html5Runtime ) {\n    \n        var $ = Base.$;\n    \n        return Html5Runtime.register( 'FilePicker', {\n            init: function() {\n                var container = this.getRuntime().getContainer(),\n                    me = this,\n                    owner = me.owner,\n                    opts = me.options,\n                    lable = $( document.createElement('label') ),\n                    input = $( document.createElement('input') ),\n                    arr, i, len, mouseHandler;\n    \n                input.attr( 'type', 'file' );\n                input.attr( 'name', opts.name );\n                input.addClass('webuploader-element-invisible');\n    \n                lable.on( 'click', function() {\n                    input.trigger('click');\n                });\n    \n                lable.css({\n                    opacity: 0,\n                    width: '100%',\n                    height: '100%',\n                    display: 'block',\n                    cursor: 'pointer',\n                    background: '#ffffff'\n                });\n    \n                if ( opts.multiple ) {\n                    input.attr( 'multiple', 'multiple' );\n                }\n    \n                // @todo Firefox不支持单独指定后缀\n                if ( opts.accept && opts.accept.length > 0 ) {\n                    arr = [];\n    \n                    for ( i = 0, len = opts.accept.length; i < len; i++ ) {\n                        arr.push( opts.accept[ i ].mimeTypes );\n                    }\n    \n                    input.attr( 'accept', arr.join(',') );\n                }\n    \n                container.append( input );\n                container.append( lable );\n    \n                mouseHandler = function( e ) {\n                    owner.trigger( e.type );\n                };\n    \n                input.on( 'change', function( e ) {\n                    var fn = arguments.callee,\n                        clone;\n    \n                    me.files = e.target.files;\n    \n                    // reset input\n                    clone = this.cloneNode( true );\n                    this.parentNode.replaceChild( clone, this );\n    \n                    input.off();\n                    input = $( clone ).on( 'change', fn )\n                            .on( 'mouseenter mouseleave', mouseHandler );\n    \n                    owner.trigger('change');\n                });\n    \n                lable.on( 'mouseenter mouseleave', mouseHandler );\n    \n            },\n    \n    \n            getFiles: function() {\n                return this.files;\n            },\n    \n            destroy: function() {\n                // todo\n            }\n        });\n    });\n    /**\n     * @fileOverview Transport\n     * @todo 支持chunked传输，优势：\n     * 可以将大文件分成小块，挨个传输，可以提高大文件成功率，当失败的时候，也只需要重传那小部分，\n     * 而不需要重头再传一次。另外断点续传也需要用chunked方式。\n     */\n    define('runtime/html5/transport',[\n        'base',\n        'runtime/html5/runtime'\n    ], function( Base, Html5Runtime ) {\n    \n        var noop = Base.noop,\n            $ = Base.$;\n    \n        return Html5Runtime.register( 'Transport', {\n            init: function() {\n                this._status = 0;\n                this._response = null;\n            },\n    \n            send: function() {\n                var owner = this.owner,\n                    opts = this.options,\n                    xhr = this._initAjax(),\n                    blob = owner._blob,\n                    server = opts.server,\n                    formData, binary, fr;\n    \n                if ( opts.sendAsBinary ) {\n                    server += (/\\?/.test( server ) ? '&' : '?') +\n                            $.param( owner._formData );\n    \n                    binary = blob.getSource();\n                } else {\n                    formData = new FormData();\n                    $.each( owner._formData, function( k, v ) {\n                        formData.append( k, v );\n                    });\n    \n                    formData.append( opts.fileVal, blob.getSource(),\n                            opts.filename || owner._formData.name || '' );\n                }\n    \n                if ( opts.withCredentials && 'withCredentials' in xhr ) {\n                    xhr.open( opts.method, server, true );\n                    xhr.withCredentials = true;\n                } else {\n                    xhr.open( opts.method, server );\n                }\n    \n                this._setRequestHeader( xhr, opts.headers );\n    \n                if ( binary ) {\n                    xhr.overrideMimeType('application/octet-stream');\n    \n                    // android直接发送blob会导致服务端接收到的是空文件。\n                    // bug详情。\n                    // https://code.google.com/p/android/issues/detail?id=39882\n                    // 所以先用fileReader读取出来再通过arraybuffer的方式发送。\n                    if ( Base.os.android ) {\n                        fr = new FileReader();\n    \n                        fr.onload = function() {\n                            xhr.send( this.result );\n                            fr = fr.onload = null;\n                        };\n    \n                        fr.readAsArrayBuffer( binary );\n                    } else {\n                        xhr.send( binary );\n                    }\n                } else {\n                    xhr.send( formData );\n                }\n            },\n    \n            getResponse: function() {\n                return this._response;\n            },\n    \n            getResponseAsJson: function() {\n                return this._parseJson( this._response );\n            },\n    \n            getStatus: function() {\n                return this._status;\n            },\n    \n            abort: function() {\n                var xhr = this._xhr;\n    \n                if ( xhr ) {\n                    xhr.upload.onprogress = noop;\n                    xhr.onreadystatechange = noop;\n                    xhr.abort();\n    \n                    this._xhr = xhr = null;\n                }\n            },\n    \n            destroy: function() {\n                this.abort();\n            },\n    \n            _initAjax: function() {\n                var me = this,\n                    xhr = new XMLHttpRequest(),\n                    opts = this.options;\n    \n                if ( opts.withCredentials && !('withCredentials' in xhr) &&\n                        typeof XDomainRequest !== 'undefined' ) {\n                    xhr = new XDomainRequest();\n                }\n    \n                xhr.upload.onprogress = function( e ) {\n                    var percentage = 0;\n    \n                    if ( e.lengthComputable ) {\n                        percentage = e.loaded / e.total;\n                    }\n    \n                    return me.trigger( 'progress', percentage );\n                };\n    \n                xhr.onreadystatechange = function() {\n    \n                    if ( xhr.readyState !== 4 ) {\n                        return;\n                    }\n    \n                    xhr.upload.onprogress = noop;\n                    xhr.onreadystatechange = noop;\n                    me._xhr = null;\n                    me._status = xhr.status;\n    \n                    if ( xhr.status >= 200 && xhr.status < 300 ) {\n                        me._response = xhr.responseText;\n                        return me.trigger('load');\n                    } else if ( xhr.status >= 500 && xhr.status < 600 ) {\n                        me._response = xhr.responseText;\n                        return me.trigger( 'error', 'server' );\n                    }\n    \n    \n                    return me.trigger( 'error', me._status ? 'http' : 'abort' );\n                };\n    \n                me._xhr = xhr;\n                return xhr;\n            },\n    \n            _setRequestHeader: function( xhr, headers ) {\n                $.each( headers, function( key, val ) {\n                    xhr.setRequestHeader( key, val );\n                });\n            },\n    \n            _parseJson: function( str ) {\n                var json;\n    \n                try {\n                    json = JSON.parse( str );\n                } catch ( ex ) {\n                    json = {};\n                }\n    \n                return json;\n            }\n        });\n    });\n    /**\n     * @fileOverview FlashRuntime\n     */\n    define('runtime/flash/runtime',[\n        'base',\n        'runtime/runtime',\n        'runtime/compbase'\n    ], function( Base, Runtime, CompBase ) {\n    \n        var $ = Base.$,\n            type = 'flash',\n            components = {};\n    \n    \n        function getFlashVersion() {\n            var version;\n    \n            try {\n                version = navigator.plugins[ 'Shockwave Flash' ];\n                version = version.description;\n            } catch ( ex ) {\n                try {\n                    version = new ActiveXObject('ShockwaveFlash.ShockwaveFlash')\n                            .GetVariable('$version');\n                } catch ( ex2 ) {\n                    version = '0.0';\n                }\n            }\n            version = version.match( /\\d+/g );\n            return parseFloat( version[ 0 ] + '.' + version[ 1 ], 10 );\n        }\n    \n        function FlashRuntime() {\n            var pool = {},\n                clients = {},\n                destory = this.destory,\n                me = this,\n                jsreciver = Base.guid('webuploader_');\n    \n            Runtime.apply( me, arguments );\n            me.type = type;\n    \n    \n            // 这个方法的调用者，实际上是RuntimeClient\n            me.exec = function( comp, fn/*, args...*/ ) {\n                var client = this,\n                    uid = client.uid,\n                    args = Base.slice( arguments, 2 ),\n                    instance;\n    \n                clients[ uid ] = client;\n    \n                if ( components[ comp ] ) {\n                    if ( !pool[ uid ] ) {\n                        pool[ uid ] = new components[ comp ]( client, me );\n                    }\n    \n                    instance = pool[ uid ];\n    \n                    if ( instance[ fn ] ) {\n                        return instance[ fn ].apply( instance, args );\n                    }\n                }\n    \n                return me.flashExec.apply( client, arguments );\n            };\n    \n            function handler( evt, obj ) {\n                var type = evt.type || evt,\n                    parts, uid;\n    \n                parts = type.split('::');\n                uid = parts[ 0 ];\n                type = parts[ 1 ];\n    \n                // console.log.apply( console, arguments );\n    \n                if ( type === 'Ready' && uid === me.uid ) {\n                    me.trigger('ready');\n                } else if ( clients[ uid ] ) {\n                    clients[ uid ].trigger( type.toLowerCase(), evt, obj );\n                }\n    \n                // Base.log( evt, obj );\n            }\n    \n            // flash的接受器。\n            window[ jsreciver ] = function() {\n                var args = arguments;\n    \n                // 为了能捕获得到。\n                setTimeout(function() {\n                    handler.apply( null, args );\n                }, 1 );\n            };\n    \n            this.jsreciver = jsreciver;\n    \n            this.destory = function() {\n                // @todo 删除池子中的所有实例\n                return destory && destory.apply( this, arguments );\n            };\n    \n            this.flashExec = function( comp, fn ) {\n                var flash = me.getFlash(),\n                    args = Base.slice( arguments, 2 );\n    \n                return flash.exec( this.uid, comp, fn, args );\n            };\n    \n            // @todo\n        }\n    \n        Base.inherits( Runtime, {\n            constructor: FlashRuntime,\n    \n            init: function() {\n                var container = this.getContainer(),\n                    opts = this.options,\n                    html;\n    \n                // if not the minimal height, shims are not initialized\n                // in older browsers (e.g FF3.6, IE6,7,8, Safari 4.0,5.0, etc)\n                container.css({\n                    position: 'absolute',\n                    top: '-8px',\n                    left: '-8px',\n                    width: '9px',\n                    height: '9px',\n                    overflow: 'hidden'\n                });\n    \n                // insert flash object\n                html = '<object id=\"' + this.uid + '\" type=\"application/' +\n                        'x-shockwave-flash\" data=\"' +  opts.swf + '\" ';\n    \n                if ( Base.browser.ie ) {\n                    html += 'classid=\"clsid:d27cdb6e-ae6d-11cf-96b8-444553540000\" ';\n                }\n    \n                html += 'width=\"100%\" height=\"100%\" style=\"outline:0\">'  +\n                    '<param name=\"movie\" value=\"' + opts.swf + '\" />' +\n                    '<param name=\"flashvars\" value=\"uid=' + this.uid +\n                    '&jsreciver=' + this.jsreciver + '\" />' +\n                    '<param name=\"wmode\" value=\"transparent\" />' +\n                    '<param name=\"allowscriptaccess\" value=\"always\" />' +\n                '</object>';\n    \n                container.html( html );\n            },\n    \n            getFlash: function() {\n                if ( this._flash ) {\n                    return this._flash;\n                }\n    \n                this._flash = $( '#' + this.uid ).get( 0 );\n                return this._flash;\n            }\n    \n        });\n    \n        FlashRuntime.register = function( name, component ) {\n            component = components[ name ] = Base.inherits( CompBase, $.extend({\n    \n                // @todo fix this later\n                flashExec: function() {\n                    var owner = this.owner,\n                        runtime = this.getRuntime();\n    \n                    return runtime.flashExec.apply( owner, arguments );\n                }\n            }, component ) );\n    \n            return component;\n        };\n    \n        if ( getFlashVersion() >= 11.4 ) {\n            Runtime.addRuntime( type, FlashRuntime );\n        }\n    \n        return FlashRuntime;\n    });\n    /**\n     * @fileOverview FilePicker\n     */\n    define('runtime/flash/filepicker',[\n        'base',\n        'runtime/flash/runtime'\n    ], function( Base, FlashRuntime ) {\n        var $ = Base.$;\n    \n        return FlashRuntime.register( 'FilePicker', {\n            init: function( opts ) {\n                var copy = $.extend({}, opts ),\n                    len, i;\n    \n                // 修复Flash再没有设置title的情况下无法弹出flash文件选择框的bug.\n                len = copy.accept && copy.accept.length;\n                for (  i = 0; i < len; i++ ) {\n                    if ( !copy.accept[ i ].title ) {\n                        copy.accept[ i ].title = 'Files';\n                    }\n                }\n    \n                delete copy.button;\n                delete copy.container;\n    \n                this.flashExec( 'FilePicker', 'init', copy );\n            },\n    \n            destroy: function() {\n                // todo\n            }\n        });\n    });\n    /**\n     * @fileOverview  Transport flash实现\n     */\n    define('runtime/flash/transport',[\n        'base',\n        'runtime/flash/runtime',\n        'runtime/client'\n    ], function( Base, FlashRuntime, RuntimeClient ) {\n        var $ = Base.$;\n    \n        return FlashRuntime.register( 'Transport', {\n            init: function() {\n                this._status = 0;\n                this._response = null;\n                this._responseJson = null;\n            },\n    \n            send: function() {\n                var owner = this.owner,\n                    opts = this.options,\n                    xhr = this._initAjax(),\n                    blob = owner._blob,\n                    server = opts.server,\n                    binary;\n    \n                xhr.connectRuntime( blob.ruid );\n    \n                if ( opts.sendAsBinary ) {\n                    server += (/\\?/.test( server ) ? '&' : '?') +\n                            $.param( owner._formData );\n    \n                    binary = blob.uid;\n                } else {\n                    $.each( owner._formData, function( k, v ) {\n                        xhr.exec( 'append', k, v );\n                    });\n    \n                    xhr.exec( 'appendBlob', opts.fileVal, blob.uid,\n                            opts.filename || owner._formData.name || '' );\n                }\n    \n                this._setRequestHeader( xhr, opts.headers );\n                xhr.exec( 'send', {\n                    method: opts.method,\n                    url: server\n                }, binary );\n            },\n    \n            getStatus: function() {\n                return this._status;\n            },\n    \n            getResponse: function() {\n                return this._response;\n            },\n    \n            getResponseAsJson: function() {\n                return this._responseJson;\n            },\n    \n            abort: function() {\n                var xhr = this._xhr;\n    \n                if ( xhr ) {\n                    xhr.exec('abort');\n                    xhr.destroy();\n                    this._xhr = xhr = null;\n                }\n            },\n    \n            destroy: function() {\n                this.abort();\n            },\n    \n            _initAjax: function() {\n                var me = this,\n                    xhr = new RuntimeClient('XMLHttpRequest');\n    \n                xhr.on( 'uploadprogress progress', function( e ) {\n                    return me.trigger( 'progress', e.loaded / e.total );\n                });\n    \n                xhr.on( 'load', function() {\n                    var status = xhr.exec('getStatus'),\n                        err = '';\n    \n                    xhr.off();\n                    me._xhr = null;\n    \n                    if ( status >= 200 && status < 300 ) {\n                        me._response = xhr.exec('getResponse');\n                        me._responseJson = xhr.exec('getResponseAsJson');\n                    } else if ( status >= 500 && status < 600 ) {\n                        me._response = xhr.exec('getResponse');\n                        me._responseJson = xhr.exec('getResponseAsJson');\n                        err = 'server';\n                    } else {\n                        err = 'http';\n                    }\n    \n                    xhr.destroy();\n                    xhr = null;\n    \n                    return err ? me.trigger( 'error', err ) : me.trigger('load');\n                });\n    \n                xhr.on( 'error', function() {\n                    xhr.off();\n                    me._xhr = null;\n                    me.trigger( 'error', 'http' );\n                });\n    \n                me._xhr = xhr;\n                return xhr;\n            },\n    \n            _setRequestHeader: function( xhr, headers ) {\n                $.each( headers, function( key, val ) {\n                    xhr.exec( 'setRequestHeader', key, val );\n                });\n            }\n        });\n    });\n    /**\n     * @fileOverview 没有图像处理的版本。\n     */\n    define('preset/withoutimage',[\n        'base',\n    \n        // widgets\n        'widgets/filednd',\n        'widgets/filepaste',\n        'widgets/filepicker',\n        'widgets/queue',\n        'widgets/runtime',\n        'widgets/upload',\n        'widgets/validator',\n    \n        // runtimes\n        // html5\n        'runtime/html5/blob',\n        'runtime/html5/dnd',\n        'runtime/html5/filepaste',\n        'runtime/html5/filepicker',\n        'runtime/html5/transport',\n    \n        // flash\n        'runtime/flash/filepicker',\n        'runtime/flash/transport'\n    ], function( Base ) {\n        return Base;\n    });\n    define('webuploader',[\n        'preset/withoutimage'\n    ], function( preset ) {\n        return preset;\n    });\n    return require('webuploader');\n});\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/third-party/zeroclipboard/ZeroClipboard.js",
    "content": "/*!\n* ZeroClipboard\n* The ZeroClipboard library provides an easy way to copy text to the clipboard using an invisible Adobe Flash movie and a JavaScript interface.\n* Copyright (c) 2014 Jon Rohan, James M. Greene\n* Licensed MIT\n* http://zeroclipboard.org/\n* v2.0.0-beta.5\n*/\n(function(window) {\n  \"use strict\";\n  var _currentElement;\n  var _flashState = {\n    bridge: null,\n    version: \"0.0.0\",\n    pluginType: \"unknown\",\n    disabled: null,\n    outdated: null,\n    unavailable: null,\n    deactivated: null,\n    overdue: null,\n    ready: null\n  };\n  var _clipData = {};\n  var _clipDataFormatMap = null;\n  var _clientIdCounter = 0;\n  var _clientMeta = {};\n  var _elementIdCounter = 0;\n  var _elementMeta = {};\n  var _swfPath = function() {\n    var i, jsDir, tmpJsPath, jsPath, swfPath = \"ZeroClipboard.swf\";\n    if (!(document.currentScript && (jsPath = document.currentScript.src))) {\n      var scripts = document.getElementsByTagName(\"script\");\n      if (\"readyState\" in scripts[0]) {\n        for (i = scripts.length; i--; ) {\n          if (scripts[i].readyState === \"interactive\" && (jsPath = scripts[i].src)) {\n            break;\n          }\n        }\n      } else if (document.readyState === \"loading\") {\n        jsPath = scripts[scripts.length - 1].src;\n      } else {\n        for (i = scripts.length; i--; ) {\n          tmpJsPath = scripts[i].src;\n          if (!tmpJsPath) {\n            jsDir = null;\n            break;\n          }\n          tmpJsPath = tmpJsPath.split(\"#\")[0].split(\"?\")[0];\n          tmpJsPath = tmpJsPath.slice(0, tmpJsPath.lastIndexOf(\"/\") + 1);\n          if (jsDir == null) {\n            jsDir = tmpJsPath;\n          } else if (jsDir !== tmpJsPath) {\n            jsDir = null;\n            break;\n          }\n        }\n        if (jsDir !== null) {\n          jsPath = jsDir;\n        }\n      }\n    }\n    if (jsPath) {\n      jsPath = jsPath.split(\"#\")[0].split(\"?\")[0];\n      swfPath = jsPath.slice(0, jsPath.lastIndexOf(\"/\") + 1) + swfPath;\n    }\n    return swfPath;\n  }();\n  var _camelizeCssPropName = function() {\n    var matcherRegex = /\\-([a-z])/g, replacerFn = function(match, group) {\n      return group.toUpperCase();\n    };\n    return function(prop) {\n      return prop.replace(matcherRegex, replacerFn);\n    };\n  }();\n  var _getStyle = function(el, prop) {\n    var value, camelProp, tagName;\n    if (window.getComputedStyle) {\n      value = window.getComputedStyle(el, null).getPropertyValue(prop);\n    } else {\n      camelProp = _camelizeCssPropName(prop);\n      if (el.currentStyle) {\n        value = el.currentStyle[camelProp];\n      } else {\n        value = el.style[camelProp];\n      }\n    }\n    if (prop === \"cursor\") {\n      if (!value || value === \"auto\") {\n        tagName = el.tagName.toLowerCase();\n        if (tagName === \"a\") {\n          return \"pointer\";\n        }\n      }\n    }\n    return value;\n  };\n  var _elementMouseOver = function(event) {\n    if (!event) {\n      event = window.event;\n    }\n    var target;\n    if (this !== window) {\n      target = this;\n    } else if (event.target) {\n      target = event.target;\n    } else if (event.srcElement) {\n      target = event.srcElement;\n    }\n    ZeroClipboard.activate(target);\n  };\n  var _addEventHandler = function(element, method, func) {\n    if (!element || element.nodeType !== 1) {\n      return;\n    }\n    if (element.addEventListener) {\n      element.addEventListener(method, func, false);\n    } else if (element.attachEvent) {\n      element.attachEvent(\"on\" + method, func);\n    }\n  };\n  var _removeEventHandler = function(element, method, func) {\n    if (!element || element.nodeType !== 1) {\n      return;\n    }\n    if (element.removeEventListener) {\n      element.removeEventListener(method, func, false);\n    } else if (element.detachEvent) {\n      element.detachEvent(\"on\" + method, func);\n    }\n  };\n  var _addClass = function(element, value) {\n    if (!element || element.nodeType !== 1) {\n      return element;\n    }\n    if (element.classList) {\n      if (!element.classList.contains(value)) {\n        element.classList.add(value);\n      }\n      return element;\n    }\n    if (value && typeof value === \"string\") {\n      var classNames = (value || \"\").split(/\\s+/);\n      if (element.nodeType === 1) {\n        if (!element.className) {\n          element.className = value;\n        } else {\n          var className = \" \" + element.className + \" \", setClass = element.className;\n          for (var c = 0, cl = classNames.length; c < cl; c++) {\n            if (className.indexOf(\" \" + classNames[c] + \" \") < 0) {\n              setClass += \" \" + classNames[c];\n            }\n          }\n          element.className = setClass.replace(/^\\s+|\\s+$/g, \"\");\n        }\n      }\n    }\n    return element;\n  };\n  var _removeClass = function(element, value) {\n    if (!element || element.nodeType !== 1) {\n      return element;\n    }\n    if (element.classList) {\n      if (element.classList.contains(value)) {\n        element.classList.remove(value);\n      }\n      return element;\n    }\n    if (value && typeof value === \"string\" || value === undefined) {\n      var classNames = (value || \"\").split(/\\s+/);\n      if (element.nodeType === 1 && element.className) {\n        if (value) {\n          var className = (\" \" + element.className + \" \").replace(/[\\n\\t]/g, \" \");\n          for (var c = 0, cl = classNames.length; c < cl; c++) {\n            className = className.replace(\" \" + classNames[c] + \" \", \" \");\n          }\n          element.className = className.replace(/^\\s+|\\s+$/g, \"\");\n        } else {\n          element.className = \"\";\n        }\n      }\n    }\n    return element;\n  };\n  var _getZoomFactor = function() {\n    var rect, physicalWidth, logicalWidth, zoomFactor = 1;\n    if (typeof document.body.getBoundingClientRect === \"function\") {\n      rect = document.body.getBoundingClientRect();\n      physicalWidth = rect.right - rect.left;\n      logicalWidth = document.body.offsetWidth;\n      zoomFactor = Math.round(physicalWidth / logicalWidth * 100) / 100;\n    }\n    return zoomFactor;\n  };\n  var _getDOMObjectPosition = function(obj, defaultZIndex) {\n    var info = {\n      left: 0,\n      top: 0,\n      width: 0,\n      height: 0,\n      zIndex: _getSafeZIndex(defaultZIndex) - 1\n    };\n    if (obj.getBoundingClientRect) {\n      var rect = obj.getBoundingClientRect();\n      var pageXOffset, pageYOffset, zoomFactor;\n      if (\"pageXOffset\" in window && \"pageYOffset\" in window) {\n        pageXOffset = window.pageXOffset;\n        pageYOffset = window.pageYOffset;\n      } else {\n        zoomFactor = _getZoomFactor();\n        pageXOffset = Math.round(document.documentElement.scrollLeft / zoomFactor);\n        pageYOffset = Math.round(document.documentElement.scrollTop / zoomFactor);\n      }\n      var leftBorderWidth = document.documentElement.clientLeft || 0;\n      var topBorderWidth = document.documentElement.clientTop || 0;\n      info.left = rect.left + pageXOffset - leftBorderWidth;\n      info.top = rect.top + pageYOffset - topBorderWidth;\n      info.width = \"width\" in rect ? rect.width : rect.right - rect.left;\n      info.height = \"height\" in rect ? rect.height : rect.bottom - rect.top;\n    }\n    return info;\n  };\n  var _cacheBust = function(path, options) {\n    var cacheBust = options == null || options && options.cacheBust === true;\n    if (cacheBust) {\n      return (path.indexOf(\"?\") === -1 ? \"?\" : \"&\") + \"noCache=\" + new Date().getTime();\n    } else {\n      return \"\";\n    }\n  };\n  var _vars = function(options) {\n    var i, len, domain, domains, str = \"\", trustedOriginsExpanded = [];\n    if (options.trustedDomains) {\n      if (typeof options.trustedDomains === \"string\") {\n        domains = [ options.trustedDomains ];\n      } else if (typeof options.trustedDomains === \"object\" && \"length\" in options.trustedDomains) {\n        domains = options.trustedDomains;\n      }\n    }\n    if (domains && domains.length) {\n      for (i = 0, len = domains.length; i < len; i++) {\n        if (domains.hasOwnProperty(i) && domains[i] && typeof domains[i] === \"string\") {\n          domain = _extractDomain(domains[i]);\n          if (!domain) {\n            continue;\n          }\n          if (domain === \"*\") {\n            trustedOriginsExpanded = [ domain ];\n            break;\n          }\n          trustedOriginsExpanded.push.apply(trustedOriginsExpanded, [ domain, \"//\" + domain, window.location.protocol + \"//\" + domain ]);\n        }\n      }\n    }\n    if (trustedOriginsExpanded.length) {\n      str += \"trustedOrigins=\" + encodeURIComponent(trustedOriginsExpanded.join(\",\"));\n    }\n    if (options.forceEnhancedClipboard === true) {\n      str += (str ? \"&\" : \"\") + \"forceEnhancedClipboard=true\";\n    }\n    return str;\n  };\n  var _inArray = function(elem, array, fromIndex) {\n    if (typeof array.indexOf === \"function\") {\n      return array.indexOf(elem, fromIndex);\n    }\n    var i, len = array.length;\n    if (typeof fromIndex === \"undefined\") {\n      fromIndex = 0;\n    } else if (fromIndex < 0) {\n      fromIndex = len + fromIndex;\n    }\n    for (i = fromIndex; i < len; i++) {\n      if (array.hasOwnProperty(i) && array[i] === elem) {\n        return i;\n      }\n    }\n    return -1;\n  };\n  var _prepClip = function(elements) {\n    if (typeof elements === \"string\") {\n      throw new TypeError(\"ZeroClipboard doesn't accept query strings.\");\n    }\n    return typeof elements.length !== \"number\" ? [ elements ] : elements;\n  };\n  var _dispatchCallback = function(func, context, args, async) {\n    if (async) {\n      window.setTimeout(function() {\n        func.apply(context, args);\n      }, 0);\n    } else {\n      func.apply(context, args);\n    }\n  };\n  var _getSafeZIndex = function(val) {\n    var zIndex, tmp;\n    if (val) {\n      if (typeof val === \"number\" && val > 0) {\n        zIndex = val;\n      } else if (typeof val === \"string\" && (tmp = parseInt(val, 10)) && !isNaN(tmp) && tmp > 0) {\n        zIndex = tmp;\n      }\n    }\n    if (!zIndex) {\n      if (typeof _globalConfig.zIndex === \"number\" && _globalConfig.zIndex > 0) {\n        zIndex = _globalConfig.zIndex;\n      } else if (typeof _globalConfig.zIndex === \"string\" && (tmp = parseInt(_globalConfig.zIndex, 10)) && !isNaN(tmp) && tmp > 0) {\n        zIndex = tmp;\n      }\n    }\n    return zIndex || 0;\n  };\n  var _extend = function() {\n    var i, len, arg, prop, src, copy, target = arguments[0] || {};\n    for (i = 1, len = arguments.length; i < len; i++) {\n      if ((arg = arguments[i]) != null) {\n        for (prop in arg) {\n          if (arg.hasOwnProperty(prop)) {\n            src = target[prop];\n            copy = arg[prop];\n            if (target === copy) {\n              continue;\n            }\n            if (copy !== undefined) {\n              target[prop] = copy;\n            }\n          }\n        }\n      }\n    }\n    return target;\n  };\n  var _extractDomain = function(originOrUrl) {\n    if (originOrUrl == null || originOrUrl === \"\") {\n      return null;\n    }\n    originOrUrl = originOrUrl.replace(/^\\s+|\\s+$/g, \"\");\n    if (originOrUrl === \"\") {\n      return null;\n    }\n    var protocolIndex = originOrUrl.indexOf(\"//\");\n    originOrUrl = protocolIndex === -1 ? originOrUrl : originOrUrl.slice(protocolIndex + 2);\n    var pathIndex = originOrUrl.indexOf(\"/\");\n    originOrUrl = pathIndex === -1 ? originOrUrl : protocolIndex === -1 || pathIndex === 0 ? null : originOrUrl.slice(0, pathIndex);\n    if (originOrUrl && originOrUrl.slice(-4).toLowerCase() === \".swf\") {\n      return null;\n    }\n    return originOrUrl || null;\n  };\n  var _determineScriptAccess = function() {\n    var _extractAllDomains = function(origins, resultsArray) {\n      var i, len, tmp;\n      if (origins == null || resultsArray[0] === \"*\") {\n        return;\n      }\n      if (typeof origins === \"string\") {\n        origins = [ origins ];\n      }\n      if (!(typeof origins === \"object\" && typeof origins.length === \"number\")) {\n        return;\n      }\n      for (i = 0, len = origins.length; i < len; i++) {\n        if (origins.hasOwnProperty(i) && (tmp = _extractDomain(origins[i]))) {\n          if (tmp === \"*\") {\n            resultsArray.length = 0;\n            resultsArray.push(\"*\");\n            break;\n          }\n          if (_inArray(tmp, resultsArray) === -1) {\n            resultsArray.push(tmp);\n          }\n        }\n      }\n    };\n    return function(currentDomain, configOptions) {\n      var swfDomain = _extractDomain(configOptions.swfPath);\n      if (swfDomain === null) {\n        swfDomain = currentDomain;\n      }\n      var trustedDomains = [];\n      _extractAllDomains(configOptions.trustedOrigins, trustedDomains);\n      _extractAllDomains(configOptions.trustedDomains, trustedDomains);\n      var len = trustedDomains.length;\n      if (len > 0) {\n        if (len === 1 && trustedDomains[0] === \"*\") {\n          return \"always\";\n        }\n        if (_inArray(currentDomain, trustedDomains) !== -1) {\n          if (len === 1 && currentDomain === swfDomain) {\n            return \"sameDomain\";\n          }\n          return \"always\";\n        }\n      }\n      return \"never\";\n    };\n  }();\n  var _objectKeys = function(obj) {\n    if (obj == null) {\n      return [];\n    }\n    if (Object.keys) {\n      return Object.keys(obj);\n    }\n    var keys = [];\n    for (var prop in obj) {\n      if (obj.hasOwnProperty(prop)) {\n        keys.push(prop);\n      }\n    }\n    return keys;\n  };\n  var _deleteOwnProperties = function(obj) {\n    if (obj) {\n      for (var prop in obj) {\n        if (obj.hasOwnProperty(prop)) {\n          delete obj[prop];\n        }\n      }\n    }\n    return obj;\n  };\n  var _safeActiveElement = function() {\n    try {\n      return document.activeElement;\n    } catch (err) {}\n    return null;\n  };\n  var _pick = function(obj, keys) {\n    var newObj = {};\n    for (var i = 0, len = keys.length; i < len; i++) {\n      if (keys[i] in obj) {\n        newObj[keys[i]] = obj[keys[i]];\n      }\n    }\n    return newObj;\n  };\n  var _omit = function(obj, keys) {\n    var newObj = {};\n    for (var prop in obj) {\n      if (_inArray(prop, keys) === -1) {\n        newObj[prop] = obj[prop];\n      }\n    }\n    return newObj;\n  };\n  var _mapClipDataToFlash = function(clipData) {\n    var newClipData = {}, formatMap = {};\n    if (!(typeof clipData === \"object\" && clipData)) {\n      return;\n    }\n    for (var dataFormat in clipData) {\n      if (dataFormat && clipData.hasOwnProperty(dataFormat) && typeof clipData[dataFormat] === \"string\" && clipData[dataFormat]) {\n        switch (dataFormat.toLowerCase()) {\n         case \"text/plain\":\n         case \"text\":\n         case \"air:text\":\n         case \"flash:text\":\n          newClipData.text = clipData[dataFormat];\n          formatMap.text = dataFormat;\n          break;\n\n         case \"text/html\":\n         case \"html\":\n         case \"air:html\":\n         case \"flash:html\":\n          newClipData.html = clipData[dataFormat];\n          formatMap.html = dataFormat;\n          break;\n\n         case \"application/rtf\":\n         case \"text/rtf\":\n         case \"rtf\":\n         case \"richtext\":\n         case \"air:rtf\":\n         case \"flash:rtf\":\n          newClipData.rtf = clipData[dataFormat];\n          formatMap.rtf = dataFormat;\n          break;\n\n         default:\n          break;\n        }\n      }\n    }\n    return {\n      data: newClipData,\n      formatMap: formatMap\n    };\n  };\n  var _mapClipResultsFromFlash = function(clipResults, formatMap) {\n    if (!(typeof clipResults === \"object\" && clipResults && typeof formatMap === \"object\" && formatMap)) {\n      return clipResults;\n    }\n    var newResults = {};\n    for (var prop in clipResults) {\n      if (clipResults.hasOwnProperty(prop)) {\n        if (prop !== \"success\" && prop !== \"data\") {\n          newResults[prop] = clipResults[prop];\n          continue;\n        }\n        newResults[prop] = {};\n        var tmpHash = clipResults[prop];\n        for (var dataFormat in tmpHash) {\n          if (dataFormat && tmpHash.hasOwnProperty(dataFormat) && formatMap.hasOwnProperty(dataFormat)) {\n            newResults[prop][formatMap[dataFormat]] = tmpHash[dataFormat];\n          }\n        }\n      }\n    }\n    return newResults;\n  };\n  var _args = function(arraySlice) {\n    return function(args) {\n      return arraySlice.call(args, 0);\n    };\n  }(window.Array.prototype.slice);\n  var _detectFlashSupport = function() {\n    var plugin, ax, mimeType, hasFlash = false, isActiveX = false, isPPAPI = false, flashVersion = \"\";\n    function parseFlashVersion(desc) {\n      var matches = desc.match(/[\\d]+/g);\n      matches.length = 3;\n      return matches.join(\".\");\n    }\n    function isPepperFlash(flashPlayerFileName) {\n      return !!flashPlayerFileName && (flashPlayerFileName = flashPlayerFileName.toLowerCase()) && (/^(pepflashplayer\\.dll|libpepflashplayer\\.so|pepperflashplayer\\.plugin)$/.test(flashPlayerFileName) || flashPlayerFileName.slice(-13) === \"chrome.plugin\");\n    }\n    function inspectPlugin(plugin) {\n      if (plugin) {\n        hasFlash = true;\n        if (plugin.version) {\n          flashVersion = parseFlashVersion(plugin.version);\n        }\n        if (!flashVersion && plugin.description) {\n          flashVersion = parseFlashVersion(plugin.description);\n        }\n        if (plugin.filename) {\n          isPPAPI = isPepperFlash(plugin.filename);\n        }\n      }\n    }\n    if (navigator.plugins && navigator.plugins.length) {\n      plugin = navigator.plugins[\"Shockwave Flash\"];\n      inspectPlugin(plugin);\n      if (navigator.plugins[\"Shockwave Flash 2.0\"]) {\n        hasFlash = true;\n        flashVersion = \"2.0.0.11\";\n      }\n    } else if (navigator.mimeTypes && navigator.mimeTypes.length) {\n      mimeType = navigator.mimeTypes[\"application/x-shockwave-flash\"];\n      plugin = mimeType && mimeType.enabledPlugin;\n      inspectPlugin(plugin);\n    } else if (typeof ActiveXObject !== \"undefined\") {\n      isActiveX = true;\n      try {\n        ax = new ActiveXObject(\"ShockwaveFlash.ShockwaveFlash.7\");\n        hasFlash = true;\n        flashVersion = parseFlashVersion(ax.GetVariable(\"$version\"));\n      } catch (e1) {\n        try {\n          ax = new ActiveXObject(\"ShockwaveFlash.ShockwaveFlash.6\");\n          hasFlash = true;\n          flashVersion = \"6.0.21\";\n        } catch (e2) {\n          try {\n            ax = new ActiveXObject(\"ShockwaveFlash.ShockwaveFlash\");\n            hasFlash = true;\n            flashVersion = parseFlashVersion(ax.GetVariable(\"$version\"));\n          } catch (e3) {\n            isActiveX = false;\n          }\n        }\n      }\n    }\n    _flashState.disabled = hasFlash !== true;\n    _flashState.outdated = flashVersion && parseFloat(flashVersion) < 11;\n    _flashState.version = flashVersion || \"0.0.0\";\n    _flashState.pluginType = isPPAPI ? \"pepper\" : isActiveX ? \"activex\" : hasFlash ? \"netscape\" : \"unknown\";\n  };\n  _detectFlashSupport();\n  var ZeroClipboard = function(elements) {\n    if (!(this instanceof ZeroClipboard)) {\n      return new ZeroClipboard(elements);\n    }\n    this.id = \"\" + _clientIdCounter++;\n    _clientMeta[this.id] = {\n      instance: this,\n      elements: [],\n      handlers: {}\n    };\n    if (elements) {\n      this.clip(elements);\n    }\n    if (typeof _flashState.ready !== \"boolean\") {\n      _flashState.ready = false;\n    }\n    if (!ZeroClipboard.isFlashUnusable() && _flashState.bridge === null) {\n      var _client = this;\n      var maxWait = _globalConfig.flashLoadTimeout;\n      if (typeof maxWait === \"number\" && maxWait >= 0) {\n        setTimeout(function() {\n          if (typeof _flashState.deactivated !== \"boolean\") {\n            _flashState.deactivated = true;\n          }\n          if (_flashState.deactivated === true) {\n            ZeroClipboard.emit({\n              type: \"error\",\n              name: \"flash-deactivated\",\n              client: _client\n            });\n          }\n        }, maxWait);\n      }\n      _flashState.overdue = false;\n      _bridge();\n    }\n  };\n  ZeroClipboard.prototype.setText = function(text) {\n    ZeroClipboard.setData(\"text/plain\", text);\n    return this;\n  };\n  ZeroClipboard.prototype.setHtml = function(html) {\n    ZeroClipboard.setData(\"text/html\", html);\n    return this;\n  };\n  ZeroClipboard.prototype.setRichText = function(richText) {\n    ZeroClipboard.setData(\"application/rtf\", richText);\n    return this;\n  };\n  ZeroClipboard.prototype.setData = function() {\n    ZeroClipboard.setData.apply(ZeroClipboard, _args(arguments));\n    return this;\n  };\n  ZeroClipboard.prototype.clearData = function() {\n    ZeroClipboard.clearData.apply(ZeroClipboard, _args(arguments));\n    return this;\n  };\n  ZeroClipboard.prototype.setSize = function(width, height) {\n    _setSize(width, height);\n    return this;\n  };\n  var _setHandCursor = function(enabled) {\n    if (_flashState.ready === true && _flashState.bridge && typeof _flashState.bridge.setHandCursor === \"function\") {\n      _flashState.bridge.setHandCursor(enabled);\n    } else {\n      _flashState.ready = false;\n    }\n  };\n  ZeroClipboard.prototype.destroy = function() {\n    this.unclip();\n    this.off();\n    delete _clientMeta[this.id];\n  };\n  var _getAllClients = function() {\n    var i, len, client, clients = [], clientIds = _objectKeys(_clientMeta);\n    for (i = 0, len = clientIds.length; i < len; i++) {\n      client = _clientMeta[clientIds[i]].instance;\n      if (client && client instanceof ZeroClipboard) {\n        clients.push(client);\n      }\n    }\n    return clients;\n  };\n  ZeroClipboard.version = \"2.0.0-beta.5\";\n  var _globalConfig = {\n    swfPath: _swfPath,\n    trustedDomains: window.location.host ? [ window.location.host ] : [],\n    cacheBust: true,\n    forceHandCursor: false,\n    forceEnhancedClipboard: false,\n    zIndex: 999999999,\n    debug: false,\n    title: null,\n    autoActivate: true,\n    flashLoadTimeout: 3e4\n  };\n  ZeroClipboard.isFlashUnusable = function() {\n    return !!(_flashState.disabled || _flashState.outdated || _flashState.unavailable || _flashState.deactivated);\n  };\n  ZeroClipboard.config = function(options) {\n    if (typeof options === \"object\" && options !== null) {\n      _extend(_globalConfig, options);\n    }\n    if (typeof options === \"string\" && options) {\n      if (_globalConfig.hasOwnProperty(options)) {\n        return _globalConfig[options];\n      }\n      return;\n    }\n    var copy = {};\n    for (var prop in _globalConfig) {\n      if (_globalConfig.hasOwnProperty(prop)) {\n        if (typeof _globalConfig[prop] === \"object\" && _globalConfig[prop] !== null) {\n          if (\"length\" in _globalConfig[prop]) {\n            copy[prop] = _globalConfig[prop].slice(0);\n          } else {\n            copy[prop] = _extend({}, _globalConfig[prop]);\n          }\n        } else {\n          copy[prop] = _globalConfig[prop];\n        }\n      }\n    }\n    return copy;\n  };\n  ZeroClipboard.destroy = function() {\n    ZeroClipboard.deactivate();\n    for (var clientId in _clientMeta) {\n      if (_clientMeta.hasOwnProperty(clientId) && _clientMeta[clientId]) {\n        var client = _clientMeta[clientId].instance;\n        if (client && typeof client.destroy === \"function\") {\n          client.destroy();\n        }\n      }\n    }\n    var flashBridge = _flashState.bridge;\n    if (flashBridge) {\n      var htmlBridge = _getHtmlBridge(flashBridge);\n      if (htmlBridge) {\n        if (_flashState.pluginType === \"activex\" && \"readyState\" in flashBridge) {\n          flashBridge.style.display = \"none\";\n          (function removeSwfFromIE() {\n            if (flashBridge.readyState === 4) {\n              for (var prop in flashBridge) {\n                if (typeof flashBridge[prop] === \"function\") {\n                  flashBridge[prop] = null;\n                }\n              }\n              flashBridge.parentNode.removeChild(flashBridge);\n              if (htmlBridge.parentNode) {\n                htmlBridge.parentNode.removeChild(htmlBridge);\n              }\n            } else {\n              setTimeout(removeSwfFromIE, 10);\n            }\n          })();\n        } else {\n          flashBridge.parentNode.removeChild(flashBridge);\n          if (htmlBridge.parentNode) {\n            htmlBridge.parentNode.removeChild(htmlBridge);\n          }\n        }\n      }\n      _flashState.ready = null;\n      _flashState.bridge = null;\n      _flashState.deactivated = null;\n    }\n    ZeroClipboard.clearData();\n  };\n  ZeroClipboard.activate = function(element) {\n    if (_currentElement) {\n      _removeClass(_currentElement, _globalConfig.hoverClass);\n      _removeClass(_currentElement, _globalConfig.activeClass);\n    }\n    _currentElement = element;\n    _addClass(element, _globalConfig.hoverClass);\n    _reposition();\n    var newTitle = _globalConfig.title || element.getAttribute(\"title\");\n    if (newTitle) {\n      var htmlBridge = _getHtmlBridge(_flashState.bridge);\n      if (htmlBridge) {\n        htmlBridge.setAttribute(\"title\", newTitle);\n      }\n    }\n    var useHandCursor = _globalConfig.forceHandCursor === true || _getStyle(element, \"cursor\") === \"pointer\";\n    _setHandCursor(useHandCursor);\n  };\n  ZeroClipboard.deactivate = function() {\n    var htmlBridge = _getHtmlBridge(_flashState.bridge);\n    if (htmlBridge) {\n      htmlBridge.removeAttribute(\"title\");\n      htmlBridge.style.left = \"0px\";\n      htmlBridge.style.top = \"-9999px\";\n      _setSize(1, 1);\n    }\n    if (_currentElement) {\n      _removeClass(_currentElement, _globalConfig.hoverClass);\n      _removeClass(_currentElement, _globalConfig.activeClass);\n      _currentElement = null;\n    }\n  };\n  ZeroClipboard.state = function() {\n    return {\n      browser: _pick(window.navigator, [ \"userAgent\", \"platform\", \"appName\" ]),\n      flash: _omit(_flashState, [ \"bridge\" ]),\n      zeroclipboard: {\n        version: ZeroClipboard.version,\n        config: ZeroClipboard.config()\n      }\n    };\n  };\n  ZeroClipboard.setData = function(format, data) {\n    var dataObj;\n    if (typeof format === \"object\" && format && typeof data === \"undefined\") {\n      dataObj = format;\n      ZeroClipboard.clearData();\n    } else if (typeof format === \"string\" && format) {\n      dataObj = {};\n      dataObj[format] = data;\n    } else {\n      return;\n    }\n    for (var dataFormat in dataObj) {\n      if (dataFormat && dataObj.hasOwnProperty(dataFormat) && typeof dataObj[dataFormat] === \"string\" && dataObj[dataFormat]) {\n        _clipData[dataFormat] = dataObj[dataFormat];\n      }\n    }\n  };\n  ZeroClipboard.clearData = function(format) {\n    if (typeof format === \"undefined\") {\n      _deleteOwnProperties(_clipData);\n      _clipDataFormatMap = null;\n    } else if (typeof format === \"string\" && _clipData.hasOwnProperty(format)) {\n      delete _clipData[format];\n    }\n  };\n  var _bridge = function() {\n    var flashBridge, len;\n    var container = document.getElementById(\"global-zeroclipboard-html-bridge\");\n    if (!container) {\n      var allowScriptAccess = _determineScriptAccess(window.location.host, _globalConfig);\n      var allowNetworking = allowScriptAccess === \"never\" ? \"none\" : \"all\";\n      var flashvars = _vars(_globalConfig);\n      var swfUrl = _globalConfig.swfPath + _cacheBust(_globalConfig.swfPath, _globalConfig);\n      container = _createHtmlBridge();\n      var divToBeReplaced = document.createElement(\"div\");\n      container.appendChild(divToBeReplaced);\n      document.body.appendChild(container);\n      var tmpDiv = document.createElement(\"div\");\n      var oldIE = _flashState.pluginType === \"activex\";\n      tmpDiv.innerHTML = '<object id=\"global-zeroclipboard-flash-bridge\" name=\"global-zeroclipboard-flash-bridge\" ' + 'width=\"100%\" height=\"100%\" ' + (oldIE ? 'classid=\"clsid:d27cdb6e-ae6d-11cf-96b8-444553540000\"' : 'type=\"application/x-shockwave-flash\" data=\"' + swfUrl + '\"') + \">\" + (oldIE ? '<param name=\"movie\" value=\"' + swfUrl + '\"/>' : \"\") + '<param name=\"allowScriptAccess\" value=\"' + allowScriptAccess + '\"/>' + '<param name=\"allowNetworking\" value=\"' + allowNetworking + '\"/>' + '<param name=\"menu\" value=\"false\"/>' + '<param name=\"wmode\" value=\"transparent\"/>' + '<param name=\"flashvars\" value=\"' + flashvars + '\"/>' + \"</object>\";\n      flashBridge = tmpDiv.firstChild;\n      tmpDiv = null;\n      flashBridge.ZeroClipboard = ZeroClipboard;\n      container.replaceChild(flashBridge, divToBeReplaced);\n    }\n    if (!flashBridge) {\n      flashBridge = document[\"global-zeroclipboard-flash-bridge\"];\n      if (flashBridge && (len = flashBridge.length)) {\n        flashBridge = flashBridge[len - 1];\n      }\n      if (!flashBridge) {\n        flashBridge = container.firstChild;\n      }\n    }\n    _flashState.bridge = flashBridge || null;\n  };\n  var _createHtmlBridge = function() {\n    var container = document.createElement(\"div\");\n    container.id = \"global-zeroclipboard-html-bridge\";\n    container.className = \"global-zeroclipboard-container\";\n    container.style.position = \"absolute\";\n    container.style.left = \"0px\";\n    container.style.top = \"-9999px\";\n    container.style.width = \"1px\";\n    container.style.height = \"1px\";\n    container.style.zIndex = \"\" + _getSafeZIndex(_globalConfig.zIndex);\n    return container;\n  };\n  var _getHtmlBridge = function(flashBridge) {\n    var htmlBridge = flashBridge && flashBridge.parentNode;\n    while (htmlBridge && htmlBridge.nodeName === \"OBJECT\" && htmlBridge.parentNode) {\n      htmlBridge = htmlBridge.parentNode;\n    }\n    return htmlBridge || null;\n  };\n  var _reposition = function() {\n    if (_currentElement) {\n      var pos = _getDOMObjectPosition(_currentElement, _globalConfig.zIndex);\n      var htmlBridge = _getHtmlBridge(_flashState.bridge);\n      if (htmlBridge) {\n        htmlBridge.style.top = pos.top + \"px\";\n        htmlBridge.style.left = pos.left + \"px\";\n        htmlBridge.style.width = pos.width + \"px\";\n        htmlBridge.style.height = pos.height + \"px\";\n        htmlBridge.style.zIndex = pos.zIndex + 1;\n      }\n      _setSize(pos.width, pos.height);\n    }\n  };\n  var _setSize = function(width, height) {\n    var htmlBridge = _getHtmlBridge(_flashState.bridge);\n    if (htmlBridge) {\n      htmlBridge.style.width = width + \"px\";\n      htmlBridge.style.height = height + \"px\";\n    }\n  };\n  ZeroClipboard.emit = function(event) {\n    var eventType, eventObj, performCallbackAsync, clients, i, len, eventCopy, returnVal, tmp;\n    if (typeof event === \"string\" && event) {\n      eventType = event;\n    }\n    if (typeof event === \"object\" && event && typeof event.type === \"string\" && event.type) {\n      eventType = event.type;\n      eventObj = event;\n    }\n    if (!eventType) {\n      return;\n    }\n    event = _createEvent(eventType, eventObj);\n    _preprocessEvent(event);\n    if (event.type === \"ready\" && _flashState.overdue === true) {\n      return ZeroClipboard.emit({\n        type: \"error\",\n        name: \"flash-overdue\"\n      });\n    }\n    performCallbackAsync = !/^(before)?copy$/.test(event.type);\n    if (event.client) {\n      _dispatchClientCallbacks.call(event.client, event, performCallbackAsync);\n    } else {\n      clients = event.target && event.target !== window && _globalConfig.autoActivate === true ? _getAllClientsClippedToElement(event.target) : _getAllClients();\n      for (i = 0, len = clients.length; i < len; i++) {\n        eventCopy = _extend({}, event, {\n          client: clients[i]\n        });\n        _dispatchClientCallbacks.call(clients[i], eventCopy, performCallbackAsync);\n      }\n    }\n    if (event.type === \"copy\") {\n      tmp = _mapClipDataToFlash(_clipData);\n      returnVal = tmp.data;\n      _clipDataFormatMap = tmp.formatMap;\n    }\n    return returnVal;\n  };\n  var _dispatchClientCallbacks = function(event, async) {\n    var handlers = _clientMeta[this.id] && _clientMeta[this.id].handlers[event.type];\n    if (handlers && handlers.length) {\n      var i, len, func, context, originalContext = this;\n      for (i = 0, len = handlers.length; i < len; i++) {\n        func = handlers[i];\n        context = originalContext;\n        if (typeof func === \"string\" && typeof window[func] === \"function\") {\n          func = window[func];\n        }\n        if (typeof func === \"object\" && func && typeof func.handleEvent === \"function\") {\n          context = func;\n          func = func.handleEvent;\n        }\n        if (typeof func === \"function\") {\n          _dispatchCallback(func, context, [ event ], async);\n        }\n      }\n    }\n    return this;\n  };\n  var _eventMessages = {\n    ready: \"Flash communication is established\",\n    error: {\n      \"flash-disabled\": \"Flash is disabled or not installed\",\n      \"flash-outdated\": \"Flash is too outdated to support ZeroClipboard\",\n      \"flash-unavailable\": \"Flash is unable to communicate bidirectionally with JavaScript\",\n      \"flash-deactivated\": \"Flash is too outdated for your browser and/or is configured as click-to-activate\",\n      \"flash-overdue\": \"Flash communication was established but NOT within the acceptable time limit\"\n    }\n  };\n  var _createEvent = function(eventType, event) {\n    if (!(eventType || event && event.type)) {\n      return;\n    }\n    event = event || {};\n    eventType = (eventType || event.type).toLowerCase();\n    _extend(event, {\n      type: eventType,\n      target: event.target || _currentElement || null,\n      relatedTarget: event.relatedTarget || null,\n      currentTarget: _flashState && _flashState.bridge || null\n    });\n    var msg = _eventMessages[event.type];\n    if (event.type === \"error\" && event.name && msg) {\n      msg = msg[event.name];\n    }\n    if (msg) {\n      event.message = msg;\n    }\n    if (event.type === \"ready\") {\n      _extend(event, {\n        target: null,\n        version: _flashState.version\n      });\n    }\n    if (event.type === \"error\") {\n      event.target = null;\n      if (/^flash-(outdated|unavailable|deactivated|overdue)$/.test(event.name)) {\n        _extend(event, {\n          version: _flashState.version,\n          minimumVersion: \"11.0.0\"\n        });\n      }\n    }\n    if (event.type === \"copy\") {\n      event.clipboardData = {\n        setData: ZeroClipboard.setData,\n        clearData: ZeroClipboard.clearData\n      };\n    }\n    if (event.type === \"aftercopy\") {\n      event = _mapClipResultsFromFlash(event, _clipDataFormatMap);\n    }\n    if (event.target && !event.relatedTarget) {\n      event.relatedTarget = _getRelatedTarget(event.target);\n    }\n    return event;\n  };\n  var _getRelatedTarget = function(targetEl) {\n    var relatedTargetId = targetEl && targetEl.getAttribute && targetEl.getAttribute(\"data-clipboard-target\");\n    return relatedTargetId ? document.getElementById(relatedTargetId) : null;\n  };\n  var _preprocessEvent = function(event) {\n    var element = event.target || _currentElement;\n    switch (event.type) {\n     case \"error\":\n      if (_inArray(event.name, [ \"flash-disabled\", \"flash-outdated\", \"flash-deactivated\", \"flash-overdue\" ])) {\n        _extend(_flashState, {\n          disabled: event.name === \"flash-disabled\",\n          outdated: event.name === \"flash-outdated\",\n          unavailable: event.name === \"flash-unavailable\",\n          deactivated: event.name === \"flash-deactivated\",\n          overdue: event.name === \"flash-overdue\",\n          ready: false\n        });\n      }\n      break;\n\n     case \"ready\":\n      var wasDeactivated = _flashState.deactivated === true;\n      _extend(_flashState, {\n        disabled: false,\n        outdated: false,\n        unavailable: false,\n        deactivated: false,\n        overdue: wasDeactivated,\n        ready: !wasDeactivated\n      });\n      break;\n\n     case \"copy\":\n      var textContent, htmlContent, targetEl = event.relatedTarget;\n      if (!(_clipData[\"text/html\"] || _clipData[\"text/plain\"]) && targetEl && (htmlContent = targetEl.value || targetEl.outerHTML || targetEl.innerHTML) && (textContent = targetEl.value || targetEl.textContent || targetEl.innerText)) {\n        event.clipboardData.clearData();\n        event.clipboardData.setData(\"text/plain\", textContent);\n        if (htmlContent !== textContent) {\n          event.clipboardData.setData(\"text/html\", htmlContent);\n        }\n      } else if (!_clipData[\"text/plain\"] && event.target && (textContent = event.target.getAttribute(\"data-clipboard-text\"))) {\n        event.clipboardData.clearData();\n        event.clipboardData.setData(\"text/plain\", textContent);\n      }\n      break;\n\n     case \"aftercopy\":\n      ZeroClipboard.clearData();\n      if (element && element !== _safeActiveElement() && element.focus) {\n        element.focus();\n      }\n      break;\n\n     case \"mouseover\":\n      _addClass(element, _globalConfig.hoverClass);\n      break;\n\n     case \"mouseout\":\n      if (_globalConfig.autoActivate === true) {\n        ZeroClipboard.deactivate();\n      }\n      break;\n\n     case \"mousedown\":\n      _addClass(element, _globalConfig.activeClass);\n      break;\n\n     case \"mouseup\":\n      _removeClass(element, _globalConfig.activeClass);\n      break;\n    }\n  };\n  ZeroClipboard.prototype.on = function(eventName, func) {\n    var i, len, events, added = {}, handlers = _clientMeta[this.id] && _clientMeta[this.id].handlers;\n    if (typeof eventName === \"string\" && eventName) {\n      events = eventName.toLowerCase().split(/\\s+/);\n    } else if (typeof eventName === \"object\" && eventName && typeof func === \"undefined\") {\n      for (i in eventName) {\n        if (eventName.hasOwnProperty(i) && typeof i === \"string\" && i && typeof eventName[i] === \"function\") {\n          this.on(i, eventName[i]);\n        }\n      }\n    }\n    if (events && events.length) {\n      for (i = 0, len = events.length; i < len; i++) {\n        eventName = events[i].replace(/^on/, \"\");\n        added[eventName] = true;\n        if (!handlers[eventName]) {\n          handlers[eventName] = [];\n        }\n        handlers[eventName].push(func);\n      }\n      if (added.ready && _flashState.ready) {\n        ZeroClipboard.emit({\n          type: \"ready\",\n          client: this\n        });\n      }\n      if (added.error) {\n        var errorTypes = [ \"disabled\", \"outdated\", \"unavailable\", \"deactivated\", \"overdue\" ];\n        for (i = 0, len = errorTypes.length; i < len; i++) {\n          if (_flashState[errorTypes[i]]) {\n            ZeroClipboard.emit({\n              type: \"error\",\n              name: \"flash-\" + errorTypes[i],\n              client: this\n            });\n            break;\n          }\n        }\n      }\n    }\n    return this;\n  };\n  ZeroClipboard.prototype.off = function(eventName, func) {\n    var i, len, foundIndex, events, perEventHandlers, handlers = _clientMeta[this.id] && _clientMeta[this.id].handlers;\n    if (arguments.length === 0) {\n      events = _objectKeys(handlers);\n    } else if (typeof eventName === \"string\" && eventName) {\n      events = eventName.split(/\\s+/);\n    } else if (typeof eventName === \"object\" && eventName && typeof func === \"undefined\") {\n      for (i in eventName) {\n        if (eventName.hasOwnProperty(i) && typeof i === \"string\" && i && typeof eventName[i] === \"function\") {\n          this.off(i, eventName[i]);\n        }\n      }\n    }\n    if (events && events.length) {\n      for (i = 0, len = events.length; i < len; i++) {\n        eventName = events[i].toLowerCase().replace(/^on/, \"\");\n        perEventHandlers = handlers[eventName];\n        if (perEventHandlers && perEventHandlers.length) {\n          if (func) {\n            foundIndex = _inArray(func, perEventHandlers);\n            while (foundIndex !== -1) {\n              perEventHandlers.splice(foundIndex, 1);\n              foundIndex = _inArray(func, perEventHandlers, foundIndex);\n            }\n          } else {\n            handlers[eventName].length = 0;\n          }\n        }\n      }\n    }\n    return this;\n  };\n  ZeroClipboard.prototype.handlers = function(eventName) {\n    var prop, copy = null, handlers = _clientMeta[this.id] && _clientMeta[this.id].handlers;\n    if (handlers) {\n      if (typeof eventName === \"string\" && eventName) {\n        return handlers[eventName] ? handlers[eventName].slice(0) : null;\n      }\n      copy = {};\n      for (prop in handlers) {\n        if (handlers.hasOwnProperty(prop) && handlers[prop]) {\n          copy[prop] = handlers[prop].slice(0);\n        }\n      }\n    }\n    return copy;\n  };\n  ZeroClipboard.prototype.clip = function(elements) {\n    elements = _prepClip(elements);\n    for (var i = 0; i < elements.length; i++) {\n      if (elements.hasOwnProperty(i) && elements[i] && elements[i].nodeType === 1) {\n        if (!elements[i].zcClippingId) {\n          elements[i].zcClippingId = \"zcClippingId_\" + _elementIdCounter++;\n          _elementMeta[elements[i].zcClippingId] = [ this.id ];\n          if (_globalConfig.autoActivate === true) {\n            _addEventHandler(elements[i], \"mouseover\", _elementMouseOver);\n          }\n        } else if (_inArray(this.id, _elementMeta[elements[i].zcClippingId]) === -1) {\n          _elementMeta[elements[i].zcClippingId].push(this.id);\n        }\n        var clippedElements = _clientMeta[this.id].elements;\n        if (_inArray(elements[i], clippedElements) === -1) {\n          clippedElements.push(elements[i]);\n        }\n      }\n    }\n    return this;\n  };\n  ZeroClipboard.prototype.unclip = function(elements) {\n    var meta = _clientMeta[this.id];\n    if (!meta) {\n      return this;\n    }\n    var clippedElements = meta.elements;\n    var arrayIndex;\n    if (typeof elements === \"undefined\") {\n      elements = clippedElements.slice(0);\n    } else {\n      elements = _prepClip(elements);\n    }\n    for (var i = elements.length; i--; ) {\n      if (elements.hasOwnProperty(i) && elements[i] && elements[i].nodeType === 1) {\n        arrayIndex = 0;\n        while ((arrayIndex = _inArray(elements[i], clippedElements, arrayIndex)) !== -1) {\n          clippedElements.splice(arrayIndex, 1);\n        }\n        var clientIds = _elementMeta[elements[i].zcClippingId];\n        if (clientIds) {\n          arrayIndex = 0;\n          while ((arrayIndex = _inArray(this.id, clientIds, arrayIndex)) !== -1) {\n            clientIds.splice(arrayIndex, 1);\n          }\n          if (clientIds.length === 0) {\n            if (_globalConfig.autoActivate === true) {\n              _removeEventHandler(elements[i], \"mouseover\", _elementMouseOver);\n            }\n            delete elements[i].zcClippingId;\n          }\n        }\n      }\n    }\n    return this;\n  };\n  ZeroClipboard.prototype.elements = function() {\n    var meta = _clientMeta[this.id];\n    return meta && meta.elements ? meta.elements.slice(0) : [];\n  };\n  var _getAllClientsClippedToElement = function(element) {\n    var elementMetaId, clientIds, i, len, client, clients = [];\n    if (element && element.nodeType === 1 && (elementMetaId = element.zcClippingId) && _elementMeta.hasOwnProperty(elementMetaId)) {\n      clientIds = _elementMeta[elementMetaId];\n      if (clientIds && clientIds.length) {\n        for (i = 0, len = clientIds.length; i < len; i++) {\n          client = _clientMeta[clientIds[i]].instance;\n          if (client && client instanceof ZeroClipboard) {\n            clients.push(client);\n          }\n        }\n      }\n    }\n    return clients;\n  };\n  _globalConfig.hoverClass = \"zeroclipboard-is-hover\";\n  _globalConfig.activeClass = \"zeroclipboard-is-active\";\n  if (typeof define === \"function\" && define.amd) {\n    define(function() {\n      return ZeroClipboard;\n    });\n  } else if (typeof module === \"object\" && module && typeof module.exports === \"object\" && module.exports) {\n    module.exports = ZeroClipboard;\n  } else {\n    window.ZeroClipboard = ZeroClipboard;\n  }\n})(function() {\n  return this;\n}());"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/ueditor.all.js",
    "content": "/*!\r\n * UEditor\r\n * version: ueditor\r\n * build: Wed Dec 26 2018 17:25:05 GMT+0800 (CST)\r\n */\r\n\r\n(function(){\r\n\r\n// editor.js\r\nUEDITOR_CONFIG = window.UEDITOR_CONFIG || {};\r\n\r\nvar baidu = window.baidu || {};\r\n\r\nwindow.baidu = baidu;\r\n\r\nwindow.UE = baidu.editor =  window.UE || {};\r\n\r\nUE.plugins = {};\r\n\r\nUE.commands = {};\r\n\r\nUE.instants = {};\r\n\r\nUE.I18N = {};\r\n\r\nUE._customizeUI = {};\r\n\r\nUE.version = \"1.4.3\";\r\n\r\nvar dom = UE.dom = {};\r\n\r\n// core/browser.js\r\n/**\r\n * 浏览器判断模块\r\n * @file\r\n * @module UE.browser\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 提供浏览器检测的模块\r\n * @unfile\r\n * @module UE.browser\r\n */\r\nvar browser = UE.browser = function(){\r\n    var agent = navigator.userAgent.toLowerCase(),\r\n        opera = window.opera,\r\n        browser = {\r\n        /**\r\n         * @property {boolean} ie 检测当前浏览器是否为IE\r\n         * @example\r\n         * ```javascript\r\n         * if ( UE.browser.ie ) {\r\n         *     console.log( '当前浏览器是IE' );\r\n         * }\r\n         * ```\r\n         */\r\n        ie\t\t:  /(msie\\s|trident.*rv:)([\\w.]+)/.test(agent),\r\n\r\n        /**\r\n         * @property {boolean} opera 检测当前浏览器是否为Opera\r\n         * @example\r\n         * ```javascript\r\n         * if ( UE.browser.opera ) {\r\n         *     console.log( '当前浏览器是Opera' );\r\n         * }\r\n         * ```\r\n         */\r\n        opera\t: ( !!opera && opera.version ),\r\n\r\n        /**\r\n         * @property {boolean} webkit 检测当前浏览器是否是webkit内核的浏览器\r\n         * @example\r\n         * ```javascript\r\n         * if ( UE.browser.webkit ) {\r\n         *     console.log( '当前浏览器是webkit内核浏览器' );\r\n         * }\r\n         * ```\r\n         */\r\n        webkit\t: ( agent.indexOf( ' applewebkit/' ) > -1 ),\r\n\r\n        /**\r\n         * @property {boolean} mac 检测当前浏览器是否是运行在mac平台下\r\n         * @example\r\n         * ```javascript\r\n         * if ( UE.browser.mac ) {\r\n         *     console.log( '当前浏览器运行在mac平台下' );\r\n         * }\r\n         * ```\r\n         */\r\n        mac\t: ( agent.indexOf( 'macintosh' ) > -1 ),\r\n\r\n        /**\r\n         * @property {boolean} quirks 检测当前浏览器是否处于“怪异模式”下\r\n         * @example\r\n         * ```javascript\r\n         * if ( UE.browser.quirks ) {\r\n         *     console.log( '当前浏览器运行处于“怪异模式”' );\r\n         * }\r\n         * ```\r\n         */\r\n        quirks : ( document.compatMode == 'BackCompat' )\r\n    };\r\n\r\n    /**\r\n    * @property {boolean} gecko 检测当前浏览器内核是否是gecko内核\r\n    * @example\r\n    * ```javascript\r\n    * if ( UE.browser.gecko ) {\r\n    *     console.log( '当前浏览器内核是gecko内核' );\r\n    * }\r\n    * ```\r\n    */\r\n    browser.gecko =( navigator.product == 'Gecko' && !browser.webkit && !browser.opera && !browser.ie);\r\n\r\n    var version = 0;\r\n\r\n    // Internet Explorer 6.0+\r\n    if ( browser.ie ){\r\n\r\n        var v1 =  agent.match(/(?:msie\\s([\\w.]+))/);\r\n        var v2 = agent.match(/(?:trident.*rv:([\\w.]+))/);\r\n        if(v1 && v2 && v1[1] && v2[1]){\r\n            version = Math.max(v1[1]*1,v2[1]*1);\r\n        }else if(v1 && v1[1]){\r\n            version = v1[1]*1;\r\n        }else if(v2 && v2[1]){\r\n            version = v2[1]*1;\r\n        }else{\r\n            version = 0;\r\n        }\r\n\r\n        browser.ie11Compat = document.documentMode == 11;\r\n        /**\r\n         * @property { boolean } ie9Compat 检测浏览器模式是否为 IE9 兼容模式\r\n         * @warning 如果浏览器不是IE， 则该值为undefined\r\n         * @example\r\n         * ```javascript\r\n         * if ( UE.browser.ie9Compat ) {\r\n         *     console.log( '当前浏览器运行在IE9兼容模式下' );\r\n         * }\r\n         * ```\r\n         */\r\n        browser.ie9Compat = document.documentMode == 9;\r\n\r\n        /**\r\n         * @property { boolean } ie8 检测浏览器是否是IE8浏览器\r\n         * @warning 如果浏览器不是IE， 则该值为undefined\r\n         * @example\r\n         * ```javascript\r\n         * if ( UE.browser.ie8 ) {\r\n         *     console.log( '当前浏览器是IE8浏览器' );\r\n         * }\r\n         * ```\r\n         */\r\n        browser.ie8 = !!document.documentMode;\r\n\r\n        /**\r\n         * @property { boolean } ie8Compat 检测浏览器模式是否为 IE8 兼容模式\r\n         * @warning 如果浏览器不是IE， 则该值为undefined\r\n         * @example\r\n         * ```javascript\r\n         * if ( UE.browser.ie8Compat ) {\r\n         *     console.log( '当前浏览器运行在IE8兼容模式下' );\r\n         * }\r\n         * ```\r\n         */\r\n        browser.ie8Compat = document.documentMode == 8;\r\n\r\n        /**\r\n         * @property { boolean } ie7Compat 检测浏览器模式是否为 IE7 兼容模式\r\n         * @warning 如果浏览器不是IE， 则该值为undefined\r\n         * @example\r\n         * ```javascript\r\n         * if ( UE.browser.ie7Compat ) {\r\n         *     console.log( '当前浏览器运行在IE7兼容模式下' );\r\n         * }\r\n         * ```\r\n         */\r\n        browser.ie7Compat = ( ( version == 7 && !document.documentMode )\r\n                || document.documentMode == 7 );\r\n\r\n        /**\r\n         * @property { boolean } ie6Compat 检测浏览器模式是否为 IE6 模式 或者怪异模式\r\n         * @warning 如果浏览器不是IE， 则该值为undefined\r\n         * @example\r\n         * ```javascript\r\n         * if ( UE.browser.ie6Compat ) {\r\n         *     console.log( '当前浏览器运行在IE6模式或者怪异模式下' );\r\n         * }\r\n         * ```\r\n         */\r\n        browser.ie6Compat = ( version < 7 || browser.quirks );\r\n\r\n        browser.ie9above = version > 8;\r\n\r\n        browser.ie9below = version < 9;\r\n\r\n        browser.ie11above = version > 10;\r\n\r\n        browser.ie11below = version < 11;\r\n\r\n    }\r\n\r\n    // Gecko.\r\n    if ( browser.gecko ){\r\n        var geckoRelease = agent.match( /rv:([\\d\\.]+)/ );\r\n        if ( geckoRelease )\r\n        {\r\n            geckoRelease = geckoRelease[1].split( '.' );\r\n            version = geckoRelease[0] * 10000 + ( geckoRelease[1] || 0 ) * 100 + ( geckoRelease[2] || 0 ) * 1;\r\n        }\r\n    }\r\n\r\n    /**\r\n     * @property { Number } chrome 检测当前浏览器是否为Chrome, 如果是，则返回Chrome的大版本号\r\n     * @warning 如果浏览器不是chrome， 则该值为undefined\r\n     * @example\r\n     * ```javascript\r\n     * if ( UE.browser.chrome ) {\r\n     *     console.log( '当前浏览器是Chrome' );\r\n     * }\r\n     * ```\r\n     */\r\n    if (/chrome\\/(\\d+\\.\\d)/i.test(agent)) {\r\n        browser.chrome = + RegExp['\\x241'];\r\n    }\r\n\r\n    /**\r\n     * @property { Number } safari 检测当前浏览器是否为Safari, 如果是，则返回Safari的大版本号\r\n     * @warning 如果浏览器不是safari， 则该值为undefined\r\n     * @example\r\n     * ```javascript\r\n     * if ( UE.browser.safari ) {\r\n     *     console.log( '当前浏览器是Safari' );\r\n     * }\r\n     * ```\r\n     */\r\n    if(/(\\d+\\.\\d)?(?:\\.\\d)?\\s+safari\\/?(\\d+\\.\\d+)?/i.test(agent) && !/chrome/i.test(agent)){\r\n    \tbrowser.safari = + (RegExp['\\x241'] || RegExp['\\x242']);\r\n    }\r\n\r\n\r\n    // Opera 9.50+\r\n    if ( browser.opera )\r\n        version = parseFloat( opera.version() );\r\n\r\n    // WebKit 522+ (Safari 3+)\r\n    if ( browser.webkit )\r\n        version = parseFloat( agent.match( / applewebkit\\/(\\d+)/ )[1] );\r\n\r\n    /**\r\n     * @property { Number } version 检测当前浏览器版本号\r\n     * @remind\r\n     * <ul>\r\n     *     <li>IE系列返回值为5,6,7,8,9,10等</li>\r\n     *     <li>gecko系列会返回10900，158900等</li>\r\n     *     <li>webkit系列会返回其build号 (如 522等)</li>\r\n     * </ul>\r\n     * @example\r\n     * ```javascript\r\n     * console.log( '当前浏览器版本号是： ' + UE.browser.version );\r\n     * ```\r\n     */\r\n    browser.version = version;\r\n\r\n    /**\r\n     * @property { boolean } isCompatible 检测当前浏览器是否能够与UEditor良好兼容\r\n     * @example\r\n     * ```javascript\r\n     * if ( UE.browser.isCompatible ) {\r\n     *     console.log( '浏览器与UEditor能够良好兼容' );\r\n     * }\r\n     * ```\r\n     */\r\n    browser.isCompatible =\r\n        !browser.mobile && (\r\n        ( browser.ie && version >= 6 ) ||\r\n        ( browser.gecko && version >= 10801 ) ||\r\n        ( browser.opera && version >= 9.5 ) ||\r\n        ( browser.air && version >= 1 ) ||\r\n        ( browser.webkit && version >= 522 ) ||\r\n        false );\r\n    return browser;\r\n}();\r\n//快捷方式\r\nvar ie = browser.ie,\r\n    webkit = browser.webkit,\r\n    gecko = browser.gecko,\r\n    opera = browser.opera;\r\n\r\n// core/utils.js\r\n/**\r\n * 工具函数包\r\n * @file\r\n * @module UE.utils\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * UEditor封装使用的静态工具函数\r\n * @module UE.utils\r\n * @unfile\r\n */\r\n\r\nvar utils = UE.utils = {\r\n\r\n    /**\r\n     * 用给定的迭代器遍历对象\r\n     * @method each\r\n     * @param { Object } obj 需要遍历的对象\r\n     * @param { Function } iterator 迭代器， 该方法接受两个参数， 第一个参数是当前所处理的value， 第二个参数是当前遍历对象的key\r\n     * @example\r\n     * ```javascript\r\n     * var demoObj = {\r\n     *     key1: 1,\r\n     *     key2: 2\r\n     * };\r\n     *\r\n     * //output: key1: 1, key2: 2\r\n     * UE.utils.each( demoObj, funciton ( value, key ) {\r\n     *\r\n     *     console.log( key + \":\" + value );\r\n     *\r\n     * } );\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 用给定的迭代器遍历数组或类数组对象\r\n     * @method each\r\n     * @param { Array } array 需要遍历的数组或者类数组\r\n     * @param { Function } iterator 迭代器， 该方法接受两个参数， 第一个参数是当前所处理的value， 第二个参数是当前遍历对象的key\r\n     * @example\r\n     * ```javascript\r\n     * var divs = document.getElmentByTagNames( \"div\" );\r\n     *\r\n     * //output: 0: DIV, 1: DIV ...\r\n     * UE.utils.each( divs, funciton ( value, key ) {\r\n     *\r\n     *     console.log( key + \":\" + value.tagName );\r\n     *\r\n     * } );\r\n     * ```\r\n     */\r\n    each : function(obj, iterator, context) {\r\n        if (obj == null) return;\r\n        if (obj.length === +obj.length) {\r\n            for (var i = 0, l = obj.length; i < l; i++) {\r\n                if(iterator.call(context, obj[i], i, obj) === false)\r\n                    return false;\r\n            }\r\n        } else {\r\n            for (var key in obj) {\r\n                if (obj.hasOwnProperty(key)) {\r\n                    if(iterator.call(context, obj[key], key, obj) === false)\r\n                        return false;\r\n                }\r\n            }\r\n        }\r\n    },\r\n\r\n    /**\r\n     * 以给定对象作为原型创建一个新对象\r\n     * @method makeInstance\r\n     * @param { Object } protoObject 该对象将作为新创建对象的原型\r\n     * @return { Object } 新的对象， 该对象的原型是给定的protoObject对象\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * var protoObject = { sayHello: function () { console.log('Hello UEditor!'); } };\r\n     *\r\n     * var newObject = UE.utils.makeInstance( protoObject );\r\n     * //output: Hello UEditor!\r\n     * newObject.sayHello();\r\n     * ```\r\n     */\r\n    makeInstance:function (obj) {\r\n        var noop = new Function();\r\n        noop.prototype = obj;\r\n        obj = new noop;\r\n        noop.prototype = null;\r\n        return obj;\r\n    },\r\n\r\n    /**\r\n     * 将source对象中的属性扩展到target对象上\r\n     * @method extend\r\n     * @remind 该方法将强制把source对象上的属性复制到target对象上\r\n     * @see UE.utils.extend(Object,Object,Boolean)\r\n     * @param { Object } target 目标对象， 新的属性将附加到该对象上\r\n     * @param { Object } source 源对象， 该对象的属性会被附加到target对象上\r\n     * @return { Object } 返回target对象\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * var target = { name: 'target', sex: 1 },\r\n     *      source = { name: 'source', age: 17 };\r\n     *\r\n     * UE.utils.extend( target, source );\r\n     *\r\n     * //output: { name: 'source', sex: 1, age: 17 }\r\n     * console.log( target );\r\n     *\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 将source对象中的属性扩展到target对象上， 根据指定的isKeepTarget值决定是否保留目标对象中与\r\n     * 源对象属性名相同的属性值。\r\n     * @method extend\r\n     * @param { Object } target 目标对象， 新的属性将附加到该对象上\r\n     * @param { Object } source 源对象， 该对象的属性会被附加到target对象上\r\n     * @param { Boolean } isKeepTarget 是否保留目标对象中与源对象中属性名相同的属性\r\n     * @return { Object } 返回target对象\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * var target = { name: 'target', sex: 1 },\r\n     *      source = { name: 'source', age: 17 };\r\n     *\r\n     * UE.utils.extend( target, source, true );\r\n     *\r\n     * //output: { name: 'target', sex: 1, age: 17 }\r\n     * console.log( target );\r\n     *\r\n     * ```\r\n     */\r\n    extend:function (t, s, b) {\r\n        if (s) {\r\n            for (var k in s) {\r\n                if (!b || !t.hasOwnProperty(k)) {\r\n                    t[k] = s[k];\r\n                }\r\n            }\r\n        }\r\n        return t;\r\n    },\r\n\r\n    /**\r\n     * 将给定的多个对象的属性复制到目标对象target上\r\n     * @method extend2\r\n     * @remind 该方法将强制把源对象上的属性复制到target对象上\r\n     * @remind 该方法支持两个及以上的参数， 从第二个参数开始， 其属性都会被复制到第一个参数上。 如果遇到同名的属性，\r\n     *          将会覆盖掉之前的值。\r\n     * @param { Object } target 目标对象， 新的属性将附加到该对象上\r\n     * @param { Object... } source 源对象， 支持多个对象， 该对象的属性会被附加到target对象上\r\n     * @return { Object } 返回target对象\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * var target = {},\r\n     *     source1 = { name: 'source', age: 17 },\r\n     *     source2 = { title: 'dev' };\r\n     *\r\n     * UE.utils.extend2( target, source1, source2 );\r\n     *\r\n     * //output: { name: 'source', age: 17, title: 'dev' }\r\n     * console.log( target );\r\n     *\r\n     * ```\r\n     */\r\n    extend2:function (t) {\r\n        var a = arguments;\r\n        for (var i = 1; i < a.length; i++) {\r\n            var x = a[i];\r\n            for (var k in x) {\r\n                if (!t.hasOwnProperty(k)) {\r\n                    t[k] = x[k];\r\n                }\r\n            }\r\n        }\r\n        return t;\r\n    },\r\n\r\n    /**\r\n     * 模拟继承机制， 使得subClass继承自superClass\r\n     * @method inherits\r\n     * @param { Object } subClass 子类对象\r\n     * @param { Object } superClass 超类对象\r\n     * @warning 该方法只能让subClass继承超类的原型， subClass对象自身的属性和方法不会被继承\r\n     * @return { Object } 继承superClass后的子类对象\r\n     * @example\r\n     * ```javascript\r\n     * function SuperClass(){\r\n     *     this.name = \"小李\";\r\n     * }\r\n     *\r\n     * SuperClass.prototype = {\r\n     *     hello:function(str){\r\n     *         console.log(this.name + str);\r\n     *     }\r\n     * }\r\n     *\r\n     * function SubClass(){\r\n     *     this.name = \"小张\";\r\n     * }\r\n     *\r\n     * UE.utils.inherits(SubClass,SuperClass);\r\n     *\r\n     * var sub = new SubClass();\r\n     * //output: '小张早上好!\r\n     * sub.hello(\"早上好!\");\r\n     * ```\r\n     */\r\n    inherits:function (subClass, superClass) {\r\n        var oldP = subClass.prototype,\r\n            newP = utils.makeInstance(superClass.prototype);\r\n        utils.extend(newP, oldP, true);\r\n        subClass.prototype = newP;\r\n        return (newP.constructor = subClass);\r\n    },\r\n\r\n    /**\r\n     * 用指定的context对象作为函数fn的上下文\r\n     * @method bind\r\n     * @param { Function } fn 需要绑定上下文的函数对象\r\n     * @param { Object } content 函数fn新的上下文对象\r\n     * @return { Function } 一个新的函数， 该函数作为原始函数fn的代理， 将完成fn的上下文调换工作。\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * var name = 'window',\r\n     *     newTest = null;\r\n     *\r\n     * function test () {\r\n     *     console.log( this.name );\r\n     * }\r\n     *\r\n     * newTest = UE.utils.bind( test, { name: 'object' } );\r\n     *\r\n     * //output: object\r\n     * newTest();\r\n     *\r\n     * //output: window\r\n     * test();\r\n     *\r\n     * ```\r\n     */\r\n    bind:function (fn, context) {\r\n        return function () {\r\n            return fn.apply(context, arguments);\r\n        };\r\n    },\r\n\r\n    /**\r\n     * 创建延迟指定时间后执行的函数fn\r\n     * @method defer\r\n     * @param { Function } fn 需要延迟执行的函数对象\r\n     * @param { int } delay 延迟的时间， 单位是毫秒\r\n     * @warning 该方法的时间控制是不精确的，仅仅只能保证函数的执行是在给定的时间之后，\r\n     *           而不能保证刚好到达延迟时间时执行。\r\n     * @return { Function } 目标函数fn的代理函数， 只有执行该函数才能起到延时效果\r\n     * @example\r\n     * ```javascript\r\n     * var start = 0;\r\n     *\r\n     * function test(){\r\n     *     console.log( new Date() - start );\r\n     * }\r\n     *\r\n     * var testDefer = UE.utils.defer( test, 1000 );\r\n     * //\r\n     * start = new Date();\r\n     * //output: (大约在1000毫秒之后输出) 1000\r\n     * testDefer();\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 创建延迟指定时间后执行的函数fn, 如果在延迟时间内再次执行该方法， 将会根据指定的exclusion的值，\r\n     * 决定是否取消前一次函数的执行， 如果exclusion的值为true， 则取消执行，反之，将继续执行前一个方法。\r\n     * @method defer\r\n     * @param { Function } fn 需要延迟执行的函数对象\r\n     * @param { int } delay 延迟的时间， 单位是毫秒\r\n     * @param { Boolean } exclusion 如果在延迟时间内再次执行该函数，该值将决定是否取消执行前一次函数的执行，\r\n     *                     值为true表示取消执行， 反之则将在执行前一次函数之后才执行本次函数调用。\r\n     * @warning 该方法的时间控制是不精确的，仅仅只能保证函数的执行是在给定的时间之后，\r\n     *           而不能保证刚好到达延迟时间时执行。\r\n     * @return { Function } 目标函数fn的代理函数， 只有执行该函数才能起到延时效果\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * function test(){\r\n     *     console.log(1);\r\n     * }\r\n     *\r\n     * var testDefer = UE.utils.defer( test, 1000, true );\r\n     *\r\n     * //output: (两次调用仅有一次输出) 1\r\n     * testDefer();\r\n     * testDefer();\r\n     * ```\r\n     */\r\n    defer:function (fn, delay, exclusion) {\r\n        var timerID;\r\n        return function () {\r\n            if (exclusion) {\r\n                clearTimeout(timerID);\r\n            }\r\n            timerID = setTimeout(fn, delay);\r\n        };\r\n    },\r\n\r\n    /**\r\n     * 获取元素item在数组array中首次出现的位置, 如果未找到item， 则返回-1\r\n     * @method indexOf\r\n     * @remind 该方法的匹配过程使用的是恒等“===”\r\n     * @param { Array } array 需要查找的数组对象\r\n     * @param { * } item 需要在目标数组中查找的值\r\n     * @return { int } 返回item在目标数组array中首次出现的位置， 如果在数组中未找到item， 则返回-1\r\n     * @example\r\n     * ```javascript\r\n     * var item = 1,\r\n     *     arr = [ 3, 4, 6, 8, 1, 1, 2 ];\r\n     *\r\n     * //output: 4\r\n     * console.log( UE.utils.indexOf( arr, item ) );\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 获取元素item数组array中首次出现的位置, 如果未找到item， 则返回-1。通过start的值可以指定搜索的起始位置。\r\n     * @method indexOf\r\n     * @remind 该方法的匹配过程使用的是恒等“===”\r\n     * @param { Array } array 需要查找的数组对象\r\n     * @param { * } item 需要在目标数组中查找的值\r\n     * @param { int } start 搜索的起始位置\r\n     * @return { int } 返回item在目标数组array中的start位置之后首次出现的位置， 如果在数组中未找到item， 则返回-1\r\n     * @example\r\n     * ```javascript\r\n     * var item = 1,\r\n     *     arr = [ 3, 4, 6, 8, 1, 2, 8, 3, 2, 1, 1, 4 ];\r\n     *\r\n     * //output: 9\r\n     * console.log( UE.utils.indexOf( arr, item, 5 ) );\r\n     * ```\r\n     */\r\n    indexOf:function (array, item, start) {\r\n        var index = -1;\r\n        start = this.isNumber(start) ? start : 0;\r\n        this.each(array, function (v, i) {\r\n            if (i >= start && v === item) {\r\n                index = i;\r\n                return false;\r\n            }\r\n        });\r\n        return index;\r\n    },\r\n\r\n    /**\r\n     * 移除数组array中所有的元素item\r\n     * @method removeItem\r\n     * @param { Array } array 要移除元素的目标数组\r\n     * @param { * } item 将要被移除的元素\r\n     * @remind 该方法的匹配过程使用的是恒等“===”\r\n     * @example\r\n     * ```javascript\r\n     * var arr = [ 4, 5, 7, 1, 3, 4, 6 ];\r\n     *\r\n     * UE.utils.removeItem( arr, 4 );\r\n     * //output: [ 5, 7, 1, 3, 6 ]\r\n     * console.log( arr );\r\n     *\r\n     * ```\r\n     */\r\n    removeItem:function (array, item) {\r\n        for (var i = 0, l = array.length; i < l; i++) {\r\n            if (array[i] === item) {\r\n                array.splice(i, 1);\r\n                i--;\r\n            }\r\n        }\r\n    },\r\n\r\n    /**\r\n     * 删除字符串str的首尾空格\r\n     * @method trim\r\n     * @param { String } str 需要删除首尾空格的字符串\r\n     * @return { String } 删除了首尾的空格后的字符串\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * var str = \" UEdtior \";\r\n     *\r\n     * //output: 9\r\n     * console.log( str.length );\r\n     *\r\n     * //output: 7\r\n     * console.log( UE.utils.trim( \" UEdtior \" ).length );\r\n     *\r\n     * //output: 9\r\n     * console.log( str.length );\r\n     *\r\n     *  ```\r\n     */\r\n    trim:function (str) {\r\n        return str.replace(/(^[ \\t\\n\\r]+)|([ \\t\\n\\r]+$)/g, '');\r\n    },\r\n\r\n    /**\r\n     * 将字符串str以','分隔成数组后，将该数组转换成哈希对象， 其生成的hash对象的key为数组中的元素， value为1\r\n     * @method listToMap\r\n     * @warning 该方法在生成的hash对象中，会为每一个key同时生成一个另一个全大写的key。\r\n     * @param { String } str 该字符串将被以','分割为数组， 然后进行转化\r\n     * @return { Object } 转化之后的hash对象\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * //output: Object {UEdtior: 1, UEDTIOR: 1, Hello: 1, HELLO: 1}\r\n     * console.log( UE.utils.listToMap( 'UEdtior,Hello' ) );\r\n     *\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 将字符串数组转换成哈希对象， 其生成的hash对象的key为数组中的元素， value为1\r\n     * @method listToMap\r\n     * @warning 该方法在生成的hash对象中，会为每一个key同时生成一个另一个全大写的key。\r\n     * @param { Array } arr 字符串数组\r\n     * @return { Object } 转化之后的hash对象\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * //output: Object {UEdtior: 1, UEDTIOR: 1, Hello: 1, HELLO: 1}\r\n     * console.log( UE.utils.listToMap( [ 'UEdtior', 'Hello' ] ) );\r\n     *\r\n     * ```\r\n     */\r\n    listToMap:function (list) {\r\n        if (!list)return {};\r\n        list = utils.isArray(list) ? list : list.split(',');\r\n        for (var i = 0, ci, obj = {}; ci = list[i++];) {\r\n            obj[ci.toUpperCase()] = obj[ci] = 1;\r\n        }\r\n        return obj;\r\n    },\r\n\r\n    /**\r\n     * 将str中的html符号转义,将转义“'，&，<，\"，>”五个字符\r\n     * @method unhtml\r\n     * @param { String } str 需要转义的字符串\r\n     * @return { String } 转义后的字符串\r\n     * @example\r\n     * ```javascript\r\n     * var html = '<body>&</body>';\r\n     *\r\n     * //output: &lt;body&gt;&amp;&lt;/body&gt;\r\n     * console.log( UE.utils.unhtml( html ) );\r\n     *\r\n     * ```\r\n     */\r\n    unhtml:function (str, reg) {\r\n        return str ? str.replace(reg || /[&<\">'](?:(amp|lt|quot|gt|#39|nbsp|#\\d+);)?/g, function (a, b) {\r\n            if (b) {\r\n                return a;\r\n            } else {\r\n                return {\r\n                    '<':'&lt;',\r\n                    '&':'&amp;',\r\n                    '\"':'&quot;',\r\n                    '>':'&gt;',\r\n                    \"'\":'&#39;'\r\n                }[a]\r\n            }\r\n\r\n        }) : '';\r\n    },\r\n    /**\r\n     * 将url中的html字符转义， 仅转义  ', \", <, > 四个字符\r\n     * @param  { String } str 需要转义的字符串\r\n     * @param  { RegExp } reg 自定义的正则\r\n     * @return { String }     转义后的字符串\r\n     */\r\n    unhtmlForUrl:function (str, reg) {\r\n        return str ? str.replace(reg || /[<\">']/g, function (a) {\r\n            return {\r\n                '<':'&lt;',\r\n                '&':'&amp;',\r\n                '\"':'&quot;',\r\n                '>':'&gt;',\r\n                \"'\":'&#39;'\r\n            }[a]\r\n\r\n        }) : '';\r\n    },\r\n\r\n    /**\r\n     * 将str中的转义字符还原成html字符\r\n     * @see UE.utils.unhtml(String);\r\n     * @method html\r\n     * @param { String } str 需要逆转义的字符串\r\n     * @return { String } 逆转义后的字符串\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * var str = '&lt;body&gt;&amp;&lt;/body&gt;';\r\n     *\r\n     * //output: <body>&</body>\r\n     * console.log( UE.utils.html( str ) );\r\n     *\r\n     * ```\r\n     */\r\n    html:function (str) {\r\n        return str ? str.replace(/&((g|l|quo)t|amp|#39|nbsp);/g, function (m) {\r\n            return {\r\n                '&lt;':'<',\r\n                '&amp;':'&',\r\n                '&quot;':'\"',\r\n                '&gt;':'>',\r\n                '&#39;':\"'\",\r\n                '&nbsp;':' '\r\n            }[m]\r\n        }) : '';\r\n    },\r\n\r\n    /**\r\n     * 将css样式转换为驼峰的形式\r\n     * @method cssStyleToDomStyle\r\n     * @param { String } cssName 需要转换的css样式名\r\n     * @return { String } 转换成驼峰形式后的css样式名\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * var str = 'border-top';\r\n     *\r\n     * //output: borderTop\r\n     * console.log( UE.utils.cssStyleToDomStyle( str ) );\r\n     *\r\n     * ```\r\n     */\r\n    cssStyleToDomStyle:function () {\r\n        var test = document.createElement('div').style,\r\n            cache = {\r\n                'float':test.cssFloat != undefined ? 'cssFloat' : test.styleFloat != undefined ? 'styleFloat' : 'float'\r\n            };\r\n\r\n        return function (cssName) {\r\n            return cache[cssName] || (cache[cssName] = cssName.toLowerCase().replace(/-./g, function (match) {\r\n                return match.charAt(1).toUpperCase();\r\n            }));\r\n        };\r\n    }(),\r\n\r\n    /**\r\n     * 动态加载文件到doc中\r\n     * @method loadFile\r\n     * @param { DomDocument } document 需要加载资源文件的文档对象\r\n     * @param { Object } options 加载资源文件的属性集合， 取值请参考代码示例\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * UE.utils.loadFile( document, {\r\n     *     src:\"test.js\",\r\n     *     tag:\"script\",\r\n     *     type:\"text/javascript\",\r\n     *     defer:\"defer\"\r\n     * } );\r\n     *\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 动态加载文件到doc中，加载成功后执行的回调函数fn\r\n     * @method loadFile\r\n     * @param { DomDocument } document 需要加载资源文件的文档对象\r\n     * @param { Object } options 加载资源文件的属性集合， 该集合支持的值是script标签和style标签支持的所有属性。\r\n     * @param { Function } fn 资源文件加载成功之后执行的回调\r\n     * @warning 对于在同一个文档中多次加载同一URL的文件， 该方法会在第一次加载之后缓存该请求，\r\n     *           在此之后的所有同一URL的请求， 将会直接触发回调。\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * UE.utils.loadFile( document, {\r\n     *     src:\"test.js\",\r\n     *     tag:\"script\",\r\n     *     type:\"text/javascript\",\r\n     *     defer:\"defer\"\r\n     * }, function () {\r\n     *     console.log('加载成功');\r\n     * } );\r\n     *\r\n     * ```\r\n     */\r\n    loadFile:function () {\r\n        var tmpList = [];\r\n\r\n        function getItem(doc, obj) {\r\n            try {\r\n                for (var i = 0, ci; ci = tmpList[i++];) {\r\n                    if (ci.doc === doc && ci.url == (obj.src || obj.href)) {\r\n                        return ci;\r\n                    }\r\n                }\r\n            } catch (e) {\r\n                return null;\r\n            }\r\n\r\n        }\r\n\r\n        return function (doc, obj, fn) {\r\n            var item = getItem(doc, obj);\r\n            if (item) {\r\n                if (item.ready) {\r\n                    fn && fn();\r\n                } else {\r\n                    item.funs.push(fn)\r\n                }\r\n                return;\r\n            }\r\n            tmpList.push({\r\n                doc:doc,\r\n                url:obj.src || obj.href,\r\n                funs:[fn]\r\n            });\r\n            if (!doc.body) {\r\n                var html = [];\r\n                for (var p in obj) {\r\n                    if (p == 'tag')continue;\r\n                    html.push(p + '=\"' + obj[p] + '\"')\r\n                }\r\n                doc.write('<' + obj.tag + ' ' + html.join(' ') + ' ></' + obj.tag + '>');\r\n                return;\r\n            }\r\n            if (obj.id && doc.getElementById(obj.id)) {\r\n                return;\r\n            }\r\n            var element = doc.createElement(obj.tag);\r\n            delete obj.tag;\r\n            for (var p in obj) {\r\n                element.setAttribute(p, obj[p]);\r\n            }\r\n            element.onload = element.onreadystatechange = function () {\r\n                if (!this.readyState || /loaded|complete/.test(this.readyState)) {\r\n                    item = getItem(doc, obj);\r\n                    if (item.funs.length > 0) {\r\n                        item.ready = 1;\r\n                        for (var fi; fi = item.funs.pop();) {\r\n                            fi();\r\n                        }\r\n                    }\r\n                    element.onload = element.onreadystatechange = null;\r\n                }\r\n            };\r\n            element.onerror = function () {\r\n                throw Error('The load ' + (obj.href || obj.src) + ' fails,check the url settings of file ueditor.config.js ')\r\n            };\r\n            doc.getElementsByTagName(\"head\")[0].appendChild(element);\r\n        }\r\n    }(),\r\n\r\n    /**\r\n     * 判断obj对象是否为空\r\n     * @method isEmptyObject\r\n     * @param { * } obj 需要判断的对象\r\n     * @remind 如果判断的对象是NULL， 将直接返回true， 如果是数组且为空， 返回true， 如果是字符串， 且字符串为空，\r\n     *          返回true， 如果是普通对象， 且该对象没有任何实例属性， 返回true\r\n     * @return { Boolean } 对象是否为空\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * //output: true\r\n     * console.log( UE.utils.isEmptyObject( {} ) );\r\n     *\r\n     * //output: true\r\n     * console.log( UE.utils.isEmptyObject( [] ) );\r\n     *\r\n     * //output: true\r\n     * console.log( UE.utils.isEmptyObject( \"\" ) );\r\n     *\r\n     * //output: false\r\n     * console.log( UE.utils.isEmptyObject( { key: 1 } ) );\r\n     *\r\n     * //output: false\r\n     * console.log( UE.utils.isEmptyObject( [1] ) );\r\n     *\r\n     * //output: false\r\n     * console.log( UE.utils.isEmptyObject( \"1\" ) );\r\n     *\r\n     * ```\r\n     */\r\n    isEmptyObject:function (obj) {\r\n        if (obj == null) return true;\r\n        if (this.isArray(obj) || this.isString(obj)) return obj.length === 0;\r\n        for (var key in obj) if (obj.hasOwnProperty(key)) return false;\r\n        return true;\r\n    },\r\n\r\n    /**\r\n     * 把rgb格式的颜色值转换成16进制格式\r\n     * @method fixColor\r\n     * @param { String } rgb格式的颜色值\r\n     * @param { String }\r\n     * @example\r\n     * rgb(255,255,255)  => \"#ffffff\"\r\n     */\r\n    fixColor:function (name, value) {\r\n        if (/color/i.test(name) && /rgba?/.test(value)) {\r\n            var array = value.split(\",\");\r\n            if (array.length > 3)\r\n                return \"\";\r\n            value = \"#\";\r\n            for (var i = 0, color; color = array[i++];) {\r\n                color = parseInt(color.replace(/[^\\d]/gi, ''), 10).toString(16);\r\n                value += color.length == 1 ? \"0\" + color : color;\r\n            }\r\n            value = value.toUpperCase();\r\n        }\r\n        return  value;\r\n    },\r\n    /**\r\n     * 只针对border,padding,margin做了处理，因为性能问题\r\n     * @public\r\n     * @function\r\n     * @param {String}    val style字符串\r\n     */\r\n    optCss:function (val) {\r\n        var padding, margin, border;\r\n        val = val.replace(/(padding|margin|border)\\-([^:]+):([^;]+);?/gi, function (str, key, name, val) {\r\n            if (val.split(' ').length == 1) {\r\n                switch (key) {\r\n                    case 'padding':\r\n                        !padding && (padding = {});\r\n                        padding[name] = val;\r\n                        return '';\r\n                    case 'margin':\r\n                        !margin && (margin = {});\r\n                        margin[name] = val;\r\n                        return '';\r\n                    case 'border':\r\n                        return val == 'initial' ? '' : str;\r\n                }\r\n            }\r\n            return str;\r\n        });\r\n\r\n        function opt(obj, name) {\r\n            if (!obj) {\r\n                return '';\r\n            }\r\n            var t = obj.top , b = obj.bottom, l = obj.left, r = obj.right, val = '';\r\n            if (!t || !l || !b || !r) {\r\n                for (var p in obj) {\r\n                    val += ';' + name + '-' + p + ':' + obj[p] + ';';\r\n                }\r\n            } else {\r\n                val += ';' + name + ':' +\r\n                    (t == b && b == l && l == r ? t :\r\n                        t == b && l == r ? (t + ' ' + l) :\r\n                            l == r ? (t + ' ' + l + ' ' + b) : (t + ' ' + r + ' ' + b + ' ' + l)) + ';'\r\n            }\r\n            return val;\r\n        }\r\n\r\n        val += opt(padding, 'padding') + opt(margin, 'margin');\r\n        return val.replace(/^[ \\n\\r\\t;]*|[ \\n\\r\\t]*$/, '').replace(/;([ \\n\\r\\t]+)|\\1;/g, ';')\r\n            .replace(/(&((l|g)t|quot|#39))?;{2,}/g, function (a, b) {\r\n                return b ? b + \";;\" : ';'\r\n            });\r\n    },\r\n\r\n    /**\r\n     * 克隆对象\r\n     * @method clone\r\n     * @param { Object } source 源对象\r\n     * @return { Object } source的一个副本\r\n     */\r\n\r\n    /**\r\n     * 深度克隆对象，将source的属性克隆到target对象， 会覆盖target重名的属性。\r\n     * @method clone\r\n     * @param { Object } source 源对象\r\n     * @param { Object } target 目标对象\r\n     * @return { Object } 附加了source对象所有属性的target对象\r\n     */\r\n    clone:function (source, target) {\r\n        var tmp;\r\n        target = target || {};\r\n        for (var i in source) {\r\n            if (source.hasOwnProperty(i)) {\r\n                tmp = source[i];\r\n                if (typeof tmp == 'object') {\r\n                    target[i] = utils.isArray(tmp) ? [] : {};\r\n                    utils.clone(source[i], target[i])\r\n                } else {\r\n                    target[i] = tmp;\r\n                }\r\n            }\r\n        }\r\n        return target;\r\n    },\r\n\r\n    /**\r\n     * 把cm／pt为单位的值转换为px为单位的值\r\n     * @method transUnitToPx\r\n     * @param { String } 待转换的带单位的字符串\r\n     * @return { String } 转换为px为计量单位的值的字符串\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * //output: 500px\r\n     * console.log( UE.utils.transUnitToPx( '20cm' ) );\r\n     *\r\n     * //output: 27px\r\n     * console.log( UE.utils.transUnitToPx( '20pt' ) );\r\n     *\r\n     * ```\r\n     */\r\n    transUnitToPx:function (val) {\r\n        if (!/(pt|cm)/.test(val)) {\r\n            return val\r\n        }\r\n        var unit;\r\n        val.replace(/([\\d.]+)(\\w+)/, function (str, v, u) {\r\n            val = v;\r\n            unit = u;\r\n        });\r\n        switch (unit) {\r\n            case 'cm':\r\n                val = parseFloat(val) * 25;\r\n                break;\r\n            case 'pt':\r\n                val = Math.round(parseFloat(val) * 96 / 72);\r\n        }\r\n        return val + (val ? 'px' : '');\r\n    },\r\n\r\n    /**\r\n     * 在dom树ready之后执行给定的回调函数\r\n     * @method domReady\r\n     * @remind 如果在执行该方法的时候， dom树已经ready， 那么回调函数将立刻执行\r\n     * @param { Function } fn dom树ready之后的回调函数\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * UE.utils.domReady( function () {\r\n     *\r\n     *     console.log('123');\r\n     *\r\n     * } );\r\n     *\r\n     * ```\r\n     */\r\n    domReady:function () {\r\n\r\n        var fnArr = [];\r\n\r\n        function doReady(doc) {\r\n            //确保onready只执行一次\r\n            doc.isReady = true;\r\n            for (var ci; ci = fnArr.pop(); ci()) {\r\n            }\r\n        }\r\n\r\n        return function (onready, win) {\r\n            win = win || window;\r\n            var doc = win.document;\r\n            onready && fnArr.push(onready);\r\n            if (doc.readyState === \"complete\") {\r\n                doReady(doc);\r\n            } else {\r\n                doc.isReady && doReady(doc);\r\n                if (browser.ie && browser.version != 11) {\r\n                    (function () {\r\n                        if (doc.isReady) return;\r\n                        try {\r\n                            doc.documentElement.doScroll(\"left\");\r\n                        } catch (error) {\r\n                            setTimeout(arguments.callee, 0);\r\n                            return;\r\n                        }\r\n                        doReady(doc);\r\n                    })();\r\n                    win.attachEvent('onload', function () {\r\n                        doReady(doc)\r\n                    });\r\n                } else {\r\n                    doc.addEventListener(\"DOMContentLoaded\", function () {\r\n                        doc.removeEventListener(\"DOMContentLoaded\", arguments.callee, false);\r\n                        doReady(doc);\r\n                    }, false);\r\n                    win.addEventListener('load', function () {\r\n                        doReady(doc)\r\n                    }, false);\r\n                }\r\n            }\r\n\r\n        }\r\n    }(),\r\n\r\n    /**\r\n     * 动态添加css样式\r\n     * @method cssRule\r\n     * @param { String } 节点名称\r\n     * @grammar UE.utils.cssRule('添加的样式的节点名称',['样式'，'放到哪个document上'])\r\n     * @grammar UE.utils.cssRule('body','body{background:#ccc}') => null  //给body添加背景颜色\r\n     * @grammar UE.utils.cssRule('body') =>样式的字符串  //取得key值为body的样式的内容,如果没有找到key值先关的样式将返回空，例如刚才那个背景颜色，将返回 body{background:#ccc}\r\n     * @grammar UE.utils.cssRule('body',document) => 返回指定key的样式，并且指定是哪个document\r\n     * @grammar UE.utils.cssRule('body','') =>null //清空给定的key值的背景颜色\r\n     */\r\n    cssRule:browser.ie && browser.version != 11 ? function (key, style, doc) {\r\n        var indexList, index;\r\n        if(style === undefined || style && style.nodeType && style.nodeType == 9){\r\n            //获取样式\r\n            doc = style && style.nodeType && style.nodeType == 9 ? style : (doc || document);\r\n            indexList = doc.indexList || (doc.indexList = {});\r\n            index = indexList[key];\r\n            if(index !==  undefined){\r\n                return doc.styleSheets[index].cssText\r\n            }\r\n            return undefined;\r\n        }\r\n        doc = doc || document;\r\n        indexList = doc.indexList || (doc.indexList = {});\r\n        index = indexList[key];\r\n        //清除样式\r\n        if(style === ''){\r\n            if(index!== undefined){\r\n                doc.styleSheets[index].cssText = '';\r\n                delete indexList[key];\r\n                return true\r\n            }\r\n            return false;\r\n        }\r\n\r\n        //添加样式\r\n        if(index!== undefined){\r\n            sheetStyle =  doc.styleSheets[index];\r\n        }else{\r\n            sheetStyle = doc.createStyleSheet('', index = doc.styleSheets.length);\r\n            indexList[key] = index;\r\n        }\r\n        sheetStyle.cssText = style;\r\n    }: function (key, style, doc) {\r\n        var head, node;\r\n        if(style === undefined || style && style.nodeType && style.nodeType == 9){\r\n            //获取样式\r\n            doc = style && style.nodeType && style.nodeType == 9 ? style : (doc || document);\r\n            node = doc.getElementById(key);\r\n            return node ? node.innerHTML : undefined;\r\n        }\r\n        doc = doc || document;\r\n        node = doc.getElementById(key);\r\n\r\n        //清除样式\r\n        if(style === ''){\r\n            if(node){\r\n                node.parentNode.removeChild(node);\r\n                return true\r\n            }\r\n            return false;\r\n        }\r\n\r\n        //添加样式\r\n        if(node){\r\n            node.innerHTML = style;\r\n        }else{\r\n            node = doc.createElement('style');\r\n            node.id = key;\r\n            node.innerHTML = style;\r\n            doc.getElementsByTagName('head')[0].appendChild(node);\r\n        }\r\n    },\r\n    sort:function(array,compareFn){\r\n        compareFn = compareFn || function(item1, item2){ return item1.localeCompare(item2);};\r\n        for(var i= 0,len = array.length; i<len; i++){\r\n            for(var j = i,length = array.length; j<length; j++){\r\n                if(compareFn(array[i], array[j]) > 0){\r\n                    var t = array[i];\r\n                    array[i] = array[j];\r\n                    array[j] = t;\r\n                }\r\n            }\r\n        }\r\n        return array;\r\n    },\r\n    serializeParam:function (json) {\r\n        var strArr = [];\r\n        for (var i in json) {\r\n            //忽略默认的几个参数\r\n            if(i==\"method\" || i==\"timeout\" || i==\"async\") continue;\r\n            //传递过来的对象和函数不在提交之列\r\n            if (!((typeof json[i]).toLowerCase() == \"function\" || (typeof json[i]).toLowerCase() == \"object\")) {\r\n                strArr.push( encodeURIComponent(i) + \"=\"+encodeURIComponent(json[i]) );\r\n            } else if (utils.isArray(json[i])) {\r\n                //支持传数组内容\r\n                for(var j = 0; j < json[i].length; j++) {\r\n                    strArr.push( encodeURIComponent(i) + \"[]=\"+encodeURIComponent(json[i][j]) );\r\n                }\r\n            }\r\n        }\r\n        return strArr.join(\"&\");\r\n    },\r\n    formatUrl:function (url) {\r\n        var u = url.replace(/&&/g, '&');\r\n        u = u.replace(/\\?&/g, '?');\r\n        u = u.replace(/&$/g, '');\r\n        u = u.replace(/&#/g, '#');\r\n        u = u.replace(/&+/g, '&');\r\n        return u;\r\n    },\r\n    isCrossDomainUrl:function (url) {\r\n        var a = document.createElement('a');\r\n        a.href = url;\r\n        if (browser.ie) {\r\n            a.href = a.href;\r\n        }\r\n        return !(a.protocol == location.protocol && a.hostname == location.hostname &&\r\n        (a.port == location.port || (a.port == '80' && location.port == '') || (a.port == '' && location.port == '80')));\r\n    },\r\n    clearEmptyAttrs : function(obj){\r\n        for(var p in obj){\r\n            if(obj[p] === ''){\r\n                delete obj[p]\r\n            }\r\n        }\r\n        return obj;\r\n    },\r\n    str2json : function(s){\r\n\r\n        if (!utils.isString(s)) return null;\r\n        if (window.JSON) {\r\n            return JSON.parse(s);\r\n        } else {\r\n            return (new Function(\"return \" + utils.trim(s || '')))();\r\n        }\r\n\r\n    },\r\n    json2str : (function(){\r\n\r\n        if (window.JSON) {\r\n\r\n            return JSON.stringify;\r\n\r\n        } else {\r\n\r\n            var escapeMap = {\r\n                \"\\b\": '\\\\b',\r\n                \"\\t\": '\\\\t',\r\n                \"\\n\": '\\\\n',\r\n                \"\\f\": '\\\\f',\r\n                \"\\r\": '\\\\r',\r\n                '\"' : '\\\\\"',\r\n                \"\\\\\": '\\\\\\\\'\r\n            };\r\n\r\n            function encodeString(source) {\r\n                if (/[\"\\\\\\x00-\\x1f]/.test(source)) {\r\n                    source = source.replace(\r\n                        /[\"\\\\\\x00-\\x1f]/g,\r\n                        function (match) {\r\n                            var c = escapeMap[match];\r\n                            if (c) {\r\n                                return c;\r\n                            }\r\n                            c = match.charCodeAt();\r\n                            return \"\\\\u00\"\r\n                            + Math.floor(c / 16).toString(16)\r\n                            + (c % 16).toString(16);\r\n                        });\r\n                }\r\n                return '\"' + source + '\"';\r\n            }\r\n\r\n            function encodeArray(source) {\r\n                var result = [\"[\"],\r\n                    l = source.length,\r\n                    preComma, i, item;\r\n\r\n                for (i = 0; i < l; i++) {\r\n                    item = source[i];\r\n\r\n                    switch (typeof item) {\r\n                        case \"undefined\":\r\n                        case \"function\":\r\n                        case \"unknown\":\r\n                            break;\r\n                        default:\r\n                            if(preComma) {\r\n                                result.push(',');\r\n                            }\r\n                            result.push(utils.json2str(item));\r\n                            preComma = 1;\r\n                    }\r\n                }\r\n                result.push(\"]\");\r\n                return result.join(\"\");\r\n            }\r\n\r\n            function pad(source) {\r\n                return source < 10 ? '0' + source : source;\r\n            }\r\n\r\n            function encodeDate(source){\r\n                return '\"' + source.getFullYear() + \"-\"\r\n                + pad(source.getMonth() + 1) + \"-\"\r\n                + pad(source.getDate()) + \"T\"\r\n                + pad(source.getHours()) + \":\"\r\n                + pad(source.getMinutes()) + \":\"\r\n                + pad(source.getSeconds()) + '\"';\r\n            }\r\n\r\n            return function (value) {\r\n                switch (typeof value) {\r\n                    case 'undefined':\r\n                        return 'undefined';\r\n\r\n                    case 'number':\r\n                        return isFinite(value) ? String(value) : \"null\";\r\n\r\n                    case 'string':\r\n                        return encodeString(value);\r\n\r\n                    case 'boolean':\r\n                        return String(value);\r\n\r\n                    default:\r\n                        if (value === null) {\r\n                            return 'null';\r\n                        } else if (utils.isArray(value)) {\r\n                            return encodeArray(value);\r\n                        } else if (utils.isDate(value)) {\r\n                            return encodeDate(value);\r\n                        } else {\r\n                            var result = ['{'],\r\n                                encode = utils.json2str,\r\n                                preComma,\r\n                                item;\r\n\r\n                            for (var key in value) {\r\n                                if (Object.prototype.hasOwnProperty.call(value, key)) {\r\n                                    item = value[key];\r\n                                    switch (typeof item) {\r\n                                        case 'undefined':\r\n                                        case 'unknown':\r\n                                        case 'function':\r\n                                            break;\r\n                                        default:\r\n                                            if (preComma) {\r\n                                                result.push(',');\r\n                                            }\r\n                                            preComma = 1;\r\n                                            result.push(encode(key) + ':' + encode(item));\r\n                                    }\r\n                                }\r\n                            }\r\n                            result.push('}');\r\n                            return result.join('');\r\n                        }\r\n                }\r\n            };\r\n        }\r\n\r\n    })()\r\n\r\n};\r\n/**\r\n * 判断给定的对象是否是字符串\r\n * @method isString\r\n * @param { * } object 需要判断的对象\r\n * @return { Boolean } 给定的对象是否是字符串\r\n */\r\n\r\n/**\r\n * 判断给定的对象是否是数组\r\n * @method isArray\r\n * @param { * } object 需要判断的对象\r\n * @return { Boolean } 给定的对象是否是数组\r\n */\r\n\r\n/**\r\n * 判断给定的对象是否是一个Function\r\n * @method isFunction\r\n * @param { * } object 需要判断的对象\r\n * @return { Boolean } 给定的对象是否是Function\r\n */\r\n\r\n/**\r\n * 判断给定的对象是否是Number\r\n * @method isNumber\r\n * @param { * } object 需要判断的对象\r\n * @return { Boolean } 给定的对象是否是Number\r\n */\r\n\r\n/**\r\n * 判断给定的对象是否是一个正则表达式\r\n * @method isRegExp\r\n * @param { * } object 需要判断的对象\r\n * @return { Boolean } 给定的对象是否是正则表达式\r\n */\r\n\r\n/**\r\n * 判断给定的对象是否是一个普通对象\r\n * @method isObject\r\n * @param { * } object 需要判断的对象\r\n * @return { Boolean } 给定的对象是否是普通对象\r\n */\r\nutils.each(['String', 'Function', 'Array', 'Number', 'RegExp', 'Object', 'Date'], function (v) {\r\n    UE.utils['is' + v] = function (obj) {\r\n        return Object.prototype.toString.apply(obj) == '[object ' + v + ']';\r\n    }\r\n});\r\n\r\n\r\n// core/EventBase.js\r\n/**\r\n * UE采用的事件基类\r\n * @file\r\n * @module UE\r\n * @class EventBase\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * UEditor公用空间，UEditor所有的功能都挂载在该空间下\r\n * @unfile\r\n * @module UE\r\n */\r\n\r\n/**\r\n * UE采用的事件基类，继承此类的对应类将获取addListener,removeListener,fireEvent方法。\r\n * 在UE中，Editor以及所有ui实例都继承了该类，故可以在对应的ui对象以及editor对象上使用上述方法。\r\n * @unfile\r\n * @module UE\r\n * @class EventBase\r\n */\r\n\r\n/**\r\n * 通过此构造器，子类可以继承EventBase获取事件监听的方法\r\n * @constructor\r\n * @example\r\n * ```javascript\r\n * UE.EventBase.call(editor);\r\n * ```\r\n */\r\nvar EventBase = UE.EventBase = function () {};\r\n\r\nEventBase.prototype = {\r\n\r\n    /**\r\n     * 注册事件监听器\r\n     * @method addListener\r\n     * @param { String } types 监听的事件名称，同时监听多个事件使用空格分隔\r\n     * @param { Function } fn 监听的事件被触发时，会执行该回调函数\r\n     * @waining 事件被触发时，监听的函数假如返回的值恒等于true，回调函数的队列中后面的函数将不执行\r\n     * @example\r\n     * ```javascript\r\n     * editor.addListener('selectionchange',function(){\r\n     *      console.log(\"选区已经变化！\");\r\n     * })\r\n     * editor.addListener('beforegetcontent aftergetcontent',function(type){\r\n     *         if(type == 'beforegetcontent'){\r\n     *             //do something\r\n     *         }else{\r\n     *             //do something\r\n     *         }\r\n     *         console.log(this.getContent) // this是注册的事件的编辑器实例\r\n     * })\r\n     * ```\r\n     * @see UE.EventBase:fireEvent(String)\r\n     */\r\n    addListener:function (types, listener) {\r\n        types = utils.trim(types).split(/\\s+/);\r\n        for (var i = 0, ti; ti = types[i++];) {\r\n            getListener(this, ti, true).push(listener);\r\n        }\r\n    },\r\n\r\n    on : function(types, listener){\r\n      return this.addListener(types,listener);\r\n    },\r\n    off : function(types, listener){\r\n        return this.removeListener(types, listener)\r\n    },\r\n    trigger:function(){\r\n        return this.fireEvent.apply(this,arguments);\r\n    },\r\n    /**\r\n     * 移除事件监听器\r\n     * @method removeListener\r\n     * @param { String } types 移除的事件名称，同时移除多个事件使用空格分隔\r\n     * @param { Function } fn 移除监听事件的函数引用\r\n     * @example\r\n     * ```javascript\r\n     * //changeCallback为方法体\r\n     * editor.removeListener(\"selectionchange\",changeCallback);\r\n     * ```\r\n     */\r\n    removeListener:function (types, listener) {\r\n        types = utils.trim(types).split(/\\s+/);\r\n        for (var i = 0, ti; ti = types[i++];) {\r\n            utils.removeItem(getListener(this, ti) || [], listener);\r\n        }\r\n    },\r\n\r\n    /**\r\n     * 触发事件\r\n     * @method fireEvent\r\n     * @param { String } types 触发的事件名称，同时触发多个事件使用空格分隔\r\n     * @remind 该方法会触发addListener\r\n     * @return { * } 返回触发事件的队列中，最后执行的回调函数的返回值\r\n     * @example\r\n     * ```javascript\r\n     * editor.fireEvent(\"selectionchange\");\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 触发事件\r\n     * @method fireEvent\r\n     * @param { String } types 触发的事件名称，同时触发多个事件使用空格分隔\r\n     * @param { *... } options 可选参数，可以传入一个或多个参数，会传给事件触发的回调函数\r\n     * @return { * } 返回触发事件的队列中，最后执行的回调函数的返回值\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * editor.addListener( \"selectionchange\", function ( type, arg1, arg2 ) {\r\n     *\r\n     *     console.log( arg1 + \" \" + arg2 );\r\n     *\r\n     * } );\r\n     *\r\n     * //触发selectionchange事件， 会执行上面的事件监听器\r\n     * //output: Hello World\r\n     * editor.fireEvent(\"selectionchange\", \"Hello\", \"World\");\r\n     * ```\r\n     */\r\n    fireEvent:function () {\r\n        var types = arguments[0];\r\n        types = utils.trim(types).split(' ');\r\n        for (var i = 0, ti; ti = types[i++];) {\r\n            var listeners = getListener(this, ti),\r\n                r, t, k;\r\n            if (listeners) {\r\n                k = listeners.length;\r\n                while (k--) {\r\n                    if(!listeners[k])continue;\r\n                    t = listeners[k].apply(this, arguments);\r\n                    if(t === true){\r\n                        return t;\r\n                    }\r\n                    if (t !== undefined) {\r\n                        r = t;\r\n                    }\r\n                }\r\n            }\r\n            if (t = this['on' + ti.toLowerCase()]) {\r\n                r = t.apply(this, arguments);\r\n            }\r\n        }\r\n        return r;\r\n    }\r\n};\r\n/**\r\n * 获得对象所拥有监听类型的所有监听器\r\n * @unfile\r\n * @module UE\r\n * @since 1.2.6.1\r\n * @method getListener\r\n * @public\r\n * @param { Object } obj  查询监听器的对象\r\n * @param { String } type 事件类型\r\n * @param { Boolean } force  为true且当前所有type类型的侦听器不存在时，创建一个空监听器数组\r\n * @return { Array } 监听器数组\r\n */\r\nfunction getListener(obj, type, force) {\r\n    var allListeners;\r\n    type = type.toLowerCase();\r\n    return ( ( allListeners = ( obj.__allListeners || force && ( obj.__allListeners = {} ) ) )\r\n        && ( allListeners[type] || force && ( allListeners[type] = [] ) ) );\r\n}\r\n\r\n\r\n\r\n// core/dtd.js\r\n///import editor.js\r\n///import core/dom/dom.js\r\n///import core/utils.js\r\n/**\r\n * dtd html语义化的体现类\r\n * @constructor\r\n * @namespace dtd\r\n */\r\nvar dtd = dom.dtd = (function() {\r\n    function _( s ) {\r\n        for (var k in s) {\r\n            s[k.toUpperCase()] = s[k];\r\n        }\r\n        return s;\r\n    }\r\n    var X = utils.extend2;\r\n    var A = _({isindex:1,fieldset:1}),\r\n        B = _({input:1,button:1,select:1,textarea:1,label:1}),\r\n        C = X( _({a:1}), B ),\r\n        D = X( {iframe:1}, C ),\r\n        E = _({hr:1,ul:1,menu:1,div:1,blockquote:1,noscript:1,table:1,center:1,address:1,dir:1,pre:1,h5:1,dl:1,h4:1,noframes:1,h6:1,ol:1,h1:1,h3:1,h2:1}),\r\n        F = _({ins:1,del:1,script:1,style:1}),\r\n        G = X( _({b:1,acronym:1,bdo:1,'var':1,'#':1,abbr:1,code:1,br:1,i:1,cite:1,kbd:1,u:1,strike:1,s:1,tt:1,strong:1,q:1,samp:1,em:1,dfn:1,span:1}), F ),\r\n        H = X( _({sub:1,img:1,embed:1,object:1,sup:1,basefont:1,map:1,applet:1,font:1,big:1,small:1}), G ),\r\n        I = X( _({p:1}), H ),\r\n        J = X( _({iframe:1}), H, B ),\r\n        K = _({img:1,embed:1,noscript:1,br:1,kbd:1,center:1,button:1,basefont:1,h5:1,h4:1,samp:1,h6:1,ol:1,h1:1,h3:1,h2:1,form:1,font:1,'#':1,select:1,menu:1,ins:1,abbr:1,label:1,code:1,table:1,script:1,cite:1,input:1,iframe:1,strong:1,textarea:1,noframes:1,big:1,small:1,span:1,hr:1,sub:1,bdo:1,'var':1,div:1,object:1,sup:1,strike:1,dir:1,map:1,dl:1,applet:1,del:1,isindex:1,fieldset:1,ul:1,b:1,acronym:1,a:1,blockquote:1,i:1,u:1,s:1,tt:1,address:1,q:1,pre:1,p:1,em:1,dfn:1}),\r\n\r\n        L = X( _({a:0}), J ),//a不能被切开，所以把他\r\n        M = _({tr:1}),\r\n        N = _({'#':1}),\r\n        O = X( _({param:1}), K ),\r\n        P = X( _({form:1}), A, D, E, I ),\r\n        Q = _({li:1,ol:1,ul:1}),\r\n        R = _({style:1,script:1}),\r\n        S = _({base:1,link:1,meta:1,title:1}),\r\n        T = X( S, R ),\r\n        U = _({head:1,body:1}),\r\n        V = _({html:1});\r\n\r\n    var block = _({address:1,blockquote:1,center:1,dir:1,div:1,dl:1,fieldset:1,form:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,hr:1,isindex:1,menu:1,noframes:1,ol:1,p:1,pre:1,table:1,ul:1}),\r\n\r\n        empty =  _({area:1,base:1,basefont:1,br:1,col:1,command:1,dialog:1,embed:1,hr:1,img:1,input:1,isindex:1,keygen:1,link:1,meta:1,param:1,source:1,track:1,wbr:1});\r\n\r\n    return  _({\r\n\r\n        // $ 表示自定的属性\r\n\r\n        // body外的元素列表.\r\n        $nonBodyContent: X( V, U, S ),\r\n\r\n        //块结构元素列表\r\n        $block : block,\r\n\r\n        //内联元素列表\r\n        $inline : L,\r\n\r\n        $inlineWithA : X(_({a:1}),L),\r\n\r\n        $body : X( _({script:1,style:1}), block ),\r\n\r\n        $cdata : _({script:1,style:1}),\r\n\r\n        //自闭和元素\r\n        $empty : empty,\r\n\r\n        //不是自闭合，但不能让range选中里边\r\n        $nonChild : _({iframe:1,textarea:1}),\r\n        //列表元素列表\r\n        $listItem : _({dd:1,dt:1,li:1}),\r\n\r\n        //列表根元素列表\r\n        $list: _({ul:1,ol:1,dl:1}),\r\n\r\n        //不能认为是空的元素\r\n        $isNotEmpty : _({table:1,ul:1,ol:1,dl:1,iframe:1,area:1,base:1,col:1,hr:1,img:1,embed:1,input:1,link:1,meta:1,param:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1}),\r\n\r\n        //如果没有子节点就可以删除的元素列表，像span,a\r\n        $removeEmpty : _({a:1,abbr:1,acronym:1,address:1,b:1,bdo:1,big:1,cite:1,code:1,del:1,dfn:1,em:1,font:1,i:1,ins:1,label:1,kbd:1,q:1,s:1,samp:1,small:1,span:1,strike:1,strong:1,sub:1,sup:1,tt:1,u:1,'var':1}),\r\n\r\n        $removeEmptyBlock : _({'p':1,'div':1}),\r\n\r\n        //在table元素里的元素列表\r\n        $tableContent : _({caption:1,col:1,colgroup:1,tbody:1,td:1,tfoot:1,th:1,thead:1,tr:1,table:1}),\r\n        //不转换的标签\r\n        $notTransContent : _({pre:1,script:1,style:1,textarea:1}),\r\n        html: U,\r\n        head: T,\r\n        style: N,\r\n        script: N,\r\n        body: P,\r\n        base: {},\r\n        link: {},\r\n        meta: {},\r\n        title: N,\r\n        col : {},\r\n        tr : _({td:1,th:1}),\r\n        img : {},\r\n        embed: {},\r\n        colgroup : _({thead:1,col:1,tbody:1,tr:1,tfoot:1}),\r\n        noscript : P,\r\n        td : P,\r\n        br : {},\r\n        th : P,\r\n        center : P,\r\n        kbd : L,\r\n        button : X( I, E ),\r\n        basefont : {},\r\n        h5 : L,\r\n        h4 : L,\r\n        samp : L,\r\n        h6 : L,\r\n        ol : Q,\r\n        h1 : L,\r\n        h3 : L,\r\n        option : N,\r\n        h2 : L,\r\n        form : X( A, D, E, I ),\r\n        select : _({optgroup:1,option:1}),\r\n        font : L,\r\n        ins : L,\r\n        menu : Q,\r\n        abbr : L,\r\n        label : L,\r\n        table : _({thead:1,col:1,tbody:1,tr:1,colgroup:1,caption:1,tfoot:1}),\r\n        code : L,\r\n        tfoot : M,\r\n        cite : L,\r\n        li : P,\r\n        input : {},\r\n        iframe : P,\r\n        strong : L,\r\n        textarea : N,\r\n        noframes : P,\r\n        big : L,\r\n        small : L,\r\n        //trace:\r\n        span :_({'#':1,br:1,b:1,strong:1,u:1,i:1,em:1,sub:1,sup:1,strike:1,span:1}),\r\n        hr : L,\r\n        dt : L,\r\n        sub : L,\r\n        optgroup : _({option:1}),\r\n        param : {},\r\n        bdo : L,\r\n        'var' : L,\r\n        div : P,\r\n        object : O,\r\n        sup : L,\r\n        dd : P,\r\n        strike : L,\r\n        area : {},\r\n        dir : Q,\r\n        map : X( _({area:1,form:1,p:1}), A, F, E ),\r\n        applet : O,\r\n        dl : _({dt:1,dd:1}),\r\n        del : L,\r\n        isindex : {},\r\n        fieldset : X( _({legend:1}), K ),\r\n        thead : M,\r\n        ul : Q,\r\n        acronym : L,\r\n        b : L,\r\n        a : X( _({a:1}), J ),\r\n        blockquote :X(_({td:1,tr:1,tbody:1,li:1}),P),\r\n        caption : L,\r\n        i : L,\r\n        u : L,\r\n        tbody : M,\r\n        s : L,\r\n        address : X( D, I ),\r\n        tt : L,\r\n        legend : L,\r\n        q : L,\r\n        pre : X( G, C ),\r\n        p : X(_({'a':1}),L),\r\n        em :L,\r\n        dfn : L\r\n    });\r\n})();\r\n\r\n\r\n// core/domUtils.js\r\n/**\r\n * Dom操作工具包\r\n * @file\r\n * @module UE.dom.domUtils\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * Dom操作工具包\r\n * @unfile\r\n * @module UE.dom.domUtils\r\n */\r\nfunction getDomNode(node, start, ltr, startFromChild, fn, guard) {\r\n    var tmpNode = startFromChild && node[start],\r\n        parent;\r\n    !tmpNode && (tmpNode = node[ltr]);\r\n    while (!tmpNode && (parent = (parent || node).parentNode)) {\r\n        if (parent.tagName == 'BODY' || guard && !guard(parent)) {\r\n            return null;\r\n        }\r\n        tmpNode = parent[ltr];\r\n    }\r\n    if (tmpNode && fn && !fn(tmpNode)) {\r\n        return  getDomNode(tmpNode, start, ltr, false, fn);\r\n    }\r\n    return tmpNode;\r\n}\r\nvar attrFix = ie && browser.version < 9 ? {\r\n        tabindex:\"tabIndex\",\r\n        readonly:\"readOnly\",\r\n        \"for\":\"htmlFor\",\r\n        \"class\":\"className\",\r\n        maxlength:\"maxLength\",\r\n        cellspacing:\"cellSpacing\",\r\n        cellpadding:\"cellPadding\",\r\n        rowspan:\"rowSpan\",\r\n        colspan:\"colSpan\",\r\n        usemap:\"useMap\",\r\n        frameborder:\"frameBorder\"\r\n    } : {\r\n        tabindex:\"tabIndex\",\r\n        readonly:\"readOnly\"\r\n    },\r\n    styleBlock = utils.listToMap([\r\n        '-webkit-box', '-moz-box', 'block' ,\r\n        'list-item' , 'table' , 'table-row-group' ,\r\n        'table-header-group', 'table-footer-group' ,\r\n        'table-row' , 'table-column-group' , 'table-column' ,\r\n        'table-cell' , 'table-caption'\r\n    ]);\r\nvar domUtils = dom.domUtils = {\r\n    //节点常量\r\n    NODE_ELEMENT:1,\r\n    NODE_DOCUMENT:9,\r\n    NODE_TEXT:3,\r\n    NODE_COMMENT:8,\r\n    NODE_DOCUMENT_FRAGMENT:11,\r\n\r\n    //位置关系\r\n    POSITION_IDENTICAL:0,\r\n    POSITION_DISCONNECTED:1,\r\n    POSITION_FOLLOWING:2,\r\n    POSITION_PRECEDING:4,\r\n    POSITION_IS_CONTAINED:8,\r\n    POSITION_CONTAINS:16,\r\n    //ie6使用其他的会有一段空白出现\r\n    fillChar:ie && browser.version == '6' ? '\\ufeff' : '\\u200B',\r\n    //-------------------------Node部分--------------------------------\r\n    keys:{\r\n        /*Backspace*/ 8:1, /*Delete*/ 46:1,\r\n        /*Shift*/ 16:1, /*Ctrl*/ 17:1, /*Alt*/ 18:1,\r\n        37:1, 38:1, 39:1, 40:1,\r\n        13:1 /*enter*/\r\n    },\r\n    /**\r\n     * 获取节点A相对于节点B的位置关系\r\n     * @method getPosition\r\n     * @param { Node } nodeA 需要查询位置关系的节点A\r\n     * @param { Node } nodeB 需要查询位置关系的节点B\r\n     * @return { Number } 节点A与节点B的关系\r\n     * @example\r\n     * ```javascript\r\n     * //output: 20\r\n     * var position = UE.dom.domUtils.getPosition( document.documentElement, document.body );\r\n     *\r\n     * switch ( position ) {\r\n     *\r\n     *      //0\r\n     *      case UE.dom.domUtils.POSITION_IDENTICAL:\r\n     *          console.log('元素相同');\r\n     *          break;\r\n     *      //1\r\n     *      case UE.dom.domUtils.POSITION_DISCONNECTED:\r\n     *          console.log('两个节点在不同的文档中');\r\n     *          break;\r\n     *      //2\r\n     *      case UE.dom.domUtils.POSITION_FOLLOWING:\r\n     *          console.log('节点A在节点B之后');\r\n     *          break;\r\n     *      //4\r\n     *      case UE.dom.domUtils.POSITION_PRECEDING;\r\n     *          console.log('节点A在节点B之前');\r\n     *          break;\r\n     *      //8\r\n     *      case UE.dom.domUtils.POSITION_IS_CONTAINED:\r\n     *          console.log('节点A被节点B包含');\r\n     *          break;\r\n     *      case 10:\r\n     *          console.log('节点A被节点B包含且节点A在节点B之后');\r\n     *          break;\r\n     *      //16\r\n     *      case UE.dom.domUtils.POSITION_CONTAINS:\r\n     *          console.log('节点A包含节点B');\r\n     *          break;\r\n     *      case 20:\r\n     *          console.log('节点A包含节点B且节点A在节点B之前');\r\n     *          break;\r\n     *\r\n     * }\r\n     * ```\r\n     */\r\n    getPosition:function (nodeA, nodeB) {\r\n        // 如果两个节点是同一个节点\r\n        if (nodeA === nodeB) {\r\n            // domUtils.POSITION_IDENTICAL\r\n            return 0;\r\n        }\r\n        var node,\r\n            parentsA = [nodeA],\r\n            parentsB = [nodeB];\r\n        node = nodeA;\r\n        while (node = node.parentNode) {\r\n            // 如果nodeB是nodeA的祖先节点\r\n            if (node === nodeB) {\r\n                // domUtils.POSITION_IS_CONTAINED + domUtils.POSITION_FOLLOWING\r\n                return 10;\r\n            }\r\n            parentsA.push(node);\r\n        }\r\n        node = nodeB;\r\n        while (node = node.parentNode) {\r\n            // 如果nodeA是nodeB的祖先节点\r\n            if (node === nodeA) {\r\n                // domUtils.POSITION_CONTAINS + domUtils.POSITION_PRECEDING\r\n                return 20;\r\n            }\r\n            parentsB.push(node);\r\n        }\r\n        parentsA.reverse();\r\n        parentsB.reverse();\r\n        if (parentsA[0] !== parentsB[0]) {\r\n            // domUtils.POSITION_DISCONNECTED\r\n            return 1;\r\n        }\r\n        var i = -1;\r\n        while (i++, parentsA[i] === parentsB[i]) {\r\n        }\r\n        nodeA = parentsA[i];\r\n        nodeB = parentsB[i];\r\n        while (nodeA = nodeA.nextSibling) {\r\n            if (nodeA === nodeB) {\r\n                // domUtils.POSITION_PRECEDING\r\n                return 4\r\n            }\r\n        }\r\n        // domUtils.POSITION_FOLLOWING\r\n        return  2;\r\n    },\r\n\r\n    /**\r\n     * 检测节点node在父节点中的索引位置\r\n     * @method getNodeIndex\r\n     * @param { Node } node 需要检测的节点对象\r\n     * @return { Number } 该节点在父节点中的位置\r\n     * @see UE.dom.domUtils.getNodeIndex(Node,Boolean)\r\n     */\r\n\r\n    /**\r\n     * 检测节点node在父节点中的索引位置， 根据给定的mergeTextNode参数决定是否要合并多个连续的文本节点为一个节点\r\n     * @method getNodeIndex\r\n     * @param { Node } node 需要检测的节点对象\r\n     * @param { Boolean } mergeTextNode 是否合并多个连续的文本节点为一个节点\r\n     * @return { Number } 该节点在父节点中的位置\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     *      var node = document.createElement(\"div\");\r\n     *\r\n     *      node.appendChild( document.createTextNode( \"hello\" ) );\r\n     *      node.appendChild( document.createTextNode( \"world\" ) );\r\n     *      node.appendChild( node = document.createElement( \"div\" ) );\r\n     *\r\n     *      //output: 2\r\n     *      console.log( UE.dom.domUtils.getNodeIndex( node ) );\r\n     *\r\n     *      //output: 1\r\n     *      console.log( UE.dom.domUtils.getNodeIndex( node, true ) );\r\n     *\r\n     * ```\r\n     */\r\n    getNodeIndex:function (node, ignoreTextNode) {\r\n        var preNode = node,\r\n            i = 0;\r\n        while (preNode = preNode.previousSibling) {\r\n            if (ignoreTextNode && preNode.nodeType == 3) {\r\n                if(preNode.nodeType != preNode.nextSibling.nodeType ){\r\n                    i++;\r\n                }\r\n                continue;\r\n            }\r\n            i++;\r\n        }\r\n        return i;\r\n    },\r\n\r\n    /**\r\n     * 检测节点node是否在给定的document对象上\r\n     * @method inDoc\r\n     * @param { Node } node 需要检测的节点对象\r\n     * @param { DomDocument } doc 需要检测的document对象\r\n     * @return { Boolean } 该节点node是否在给定的document的dom树上\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * var node = document.createElement(\"div\");\r\n     *\r\n     * //output: false\r\n     * console.log( UE.do.domUtils.inDoc( node, document ) );\r\n     *\r\n     * document.body.appendChild( node );\r\n     *\r\n     * //output: true\r\n     * console.log( UE.do.domUtils.inDoc( node, document ) );\r\n     *\r\n     * ```\r\n     */\r\n    inDoc:function (node, doc) {\r\n        return domUtils.getPosition(node, doc) == 10;\r\n    },\r\n    /**\r\n     * 根据给定的过滤规则filterFn， 查找符合该过滤规则的node节点的第一个祖先节点，\r\n     * 查找的起点是给定node节点的父节点。\r\n     * @method findParent\r\n     * @param { Node } node 需要查找的节点\r\n     * @param { Function } filterFn 自定义的过滤方法。\r\n     * @warning 查找的终点是到body节点为止\r\n     * @remind 自定义的过滤方法filterFn接受一个Node对象作为参数， 该对象代表当前执行检测的祖先节点。 如果该\r\n     *          节点满足过滤条件， 则要求返回true， 这时将直接返回该节点作为findParent()的结果， 否则， 请返回false。\r\n     * @return { Node | Null } 如果找到符合过滤条件的节点， 就返回该节点， 否则返回NULL\r\n     * @example\r\n     * ```javascript\r\n     * var filterNode = UE.dom.domUtils.findParent( document.body.firstChild, function ( node ) {\r\n     *\r\n     *     //由于查找的终点是body节点， 所以永远也不会匹配当前过滤器的条件， 即这里永远会返回false\r\n     *     return node.tagName === \"HTML\";\r\n     *\r\n     * } );\r\n     *\r\n     * //output: true\r\n     * console.log( filterNode === null );\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 根据给定的过滤规则filterFn， 查找符合该过滤规则的node节点的第一个祖先节点，\r\n     * 如果includeSelf的值为true，则查找的起点是给定的节点node， 否则， 起点是node的父节点\r\n     * @method findParent\r\n     * @param { Node } node 需要查找的节点\r\n     * @param { Function } filterFn 自定义的过滤方法。\r\n     * @param { Boolean } includeSelf 查找过程是否包含自身\r\n     * @warning 查找的终点是到body节点为止\r\n     * @remind 自定义的过滤方法filterFn接受一个Node对象作为参数， 该对象代表当前执行检测的祖先节点。 如果该\r\n     *          节点满足过滤条件， 则要求返回true， 这时将直接返回该节点作为findParent()的结果， 否则， 请返回false。\r\n     * @remind 如果includeSelf为true， 则过滤器第一次执行时的参数会是节点本身。\r\n     *          反之， 过滤器第一次执行时的参数将是该节点的父节点。\r\n     * @return { Node | Null } 如果找到符合过滤条件的节点， 就返回该节点， 否则返回NULL\r\n     * @example\r\n     * ```html\r\n     * <body>\r\n     *\r\n     *      <div id=\"test\">\r\n     *      </div>\r\n     *\r\n     *      <script type=\"text/javascript\">\r\n     *\r\n     *          //output: DIV, BODY\r\n     *          var filterNode = UE.dom.domUtils.findParent( document.getElementById( \"test\" ), function ( node ) {\r\n     *\r\n     *              console.log( node.tagName );\r\n     *              return false;\r\n     *\r\n     *          }, true );\r\n     *\r\n     *      </script>\r\n     * </body>\r\n     * ```\r\n     */\r\n    findParent:function (node, filterFn, includeSelf) {\r\n        if (node && !domUtils.isBody(node)) {\r\n            node = includeSelf ? node : node.parentNode;\r\n            while (node) {\r\n                if (!filterFn || filterFn(node) || domUtils.isBody(node)) {\r\n                    return filterFn && !filterFn(node) && domUtils.isBody(node) ? null : node;\r\n                }\r\n                node = node.parentNode;\r\n            }\r\n        }\r\n        return null;\r\n    },\r\n    /**\r\n     * 查找node的节点名为tagName的第一个祖先节点， 查找的起点是node节点的父节点。\r\n     * @method findParentByTagName\r\n     * @param { Node } node 需要查找的节点对象\r\n     * @param { Array } tagNames 需要查找的父节点的名称数组\r\n     * @warning 查找的终点是到body节点为止\r\n     * @return { Node | NULL } 如果找到符合条件的节点， 则返回该节点， 否则返回NULL\r\n     * @example\r\n     * ```javascript\r\n     * var node = UE.dom.domUtils.findParentByTagName( document.getElementsByTagName(\"div\")[0], [ \"BODY\" ] );\r\n     * //output: BODY\r\n     * console.log( node.tagName );\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 查找node的节点名为tagName的祖先节点， 如果includeSelf的值为true，则查找的起点是给定的节点node，\r\n     * 否则， 起点是node的父节点。\r\n     * @method findParentByTagName\r\n     * @param { Node } node 需要查找的节点对象\r\n     * @param { Array } tagNames 需要查找的父节点的名称数组\r\n     * @param { Boolean } includeSelf 查找过程是否包含node节点自身\r\n     * @warning 查找的终点是到body节点为止\r\n     * @return { Node | NULL } 如果找到符合条件的节点， 则返回该节点， 否则返回NULL\r\n     * @example\r\n     * ```javascript\r\n     * var queryTarget = document.getElementsByTagName(\"div\")[0];\r\n     * var node = UE.dom.domUtils.findParentByTagName( queryTarget, [ \"DIV\" ], true );\r\n     * //output: true\r\n     * console.log( queryTarget === node );\r\n     * ```\r\n     */\r\n    findParentByTagName:function (node, tagNames, includeSelf, excludeFn) {\r\n        tagNames = utils.listToMap(utils.isArray(tagNames) ? tagNames : [tagNames]);\r\n        return domUtils.findParent(node, function (node) {\r\n            return tagNames[node.tagName] && !(excludeFn && excludeFn(node));\r\n        }, includeSelf);\r\n    },\r\n    /**\r\n     * 查找节点node的祖先节点集合， 查找的起点是给定节点的父节点，结果集中不包含给定的节点。\r\n     * @method findParents\r\n     * @param { Node } node 需要查找的节点对象\r\n     * @return { Array } 给定节点的祖先节点数组\r\n     * @grammar UE.dom.domUtils.findParents(node)  => Array  //返回一个祖先节点数组集合，不包含自身\r\n     * @grammar UE.dom.domUtils.findParents(node,includeSelf)  => Array  //返回一个祖先节点数组集合，includeSelf指定是否包含自身\r\n     * @grammar UE.dom.domUtils.findParents(node,includeSelf,filterFn)  => Array  //返回一个祖先节点数组集合，filterFn指定过滤条件，返回true的node将被选取\r\n     * @grammar UE.dom.domUtils.findParents(node,includeSelf,filterFn,closerFirst)  => Array  //返回一个祖先节点数组集合，closerFirst为true的话，node的直接父亲节点是数组的第0个\r\n     */\r\n\r\n    /**\r\n     * 查找节点node的祖先节点集合， 如果includeSelf的值为true，\r\n     * 则返回的结果集中允许出现当前给定的节点， 否则， 该节点不会出现在其结果集中。\r\n     * @method findParents\r\n     * @param { Node } node 需要查找的节点对象\r\n     * @param { Boolean } includeSelf 查找的结果中是否允许包含当前查找的节点对象\r\n     * @return { Array } 给定节点的祖先节点数组\r\n     */\r\n    findParents:function (node, includeSelf, filterFn, closerFirst) {\r\n        var parents = includeSelf && ( filterFn && filterFn(node) || !filterFn ) ? [node] : [];\r\n        while (node = domUtils.findParent(node, filterFn)) {\r\n            parents.push(node);\r\n        }\r\n        return closerFirst ? parents : parents.reverse();\r\n    },\r\n\r\n    /**\r\n     * 在节点node后面插入新节点newNode\r\n     * @method insertAfter\r\n     * @param { Node } node 目标节点\r\n     * @param { Node } newNode 新插入的节点， 该节点将置于目标节点之后\r\n     * @return { Node } 新插入的节点\r\n     */\r\n    insertAfter:function (node, newNode) {\r\n        return node.nextSibling ? node.parentNode.insertBefore(newNode, node.nextSibling):\r\n            node.parentNode.appendChild(newNode);\r\n    },\r\n\r\n    /**\r\n     * 删除节点node及其下属的所有节点\r\n     * @method remove\r\n     * @param { Node } node 需要删除的节点对象\r\n     * @return { Node } 返回刚删除的节点对象\r\n     * @example\r\n     * ```html\r\n     * <div id=\"test\">\r\n     *     <div id=\"child\">你好</div>\r\n     * </div>\r\n     * <script>\r\n     *     UE.dom.domUtils.remove( document.body, false );\r\n     *     //output: false\r\n     *     console.log( document.getElementById( \"child\" ) !== null );\r\n     * </script>\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 删除节点node，并根据keepChildren的值决定是否保留子节点\r\n     * @method remove\r\n     * @param { Node } node 需要删除的节点对象\r\n     * @param { Boolean } keepChildren 是否需要保留子节点\r\n     * @return { Node } 返回刚删除的节点对象\r\n     * @example\r\n     * ```html\r\n     * <div id=\"test\">\r\n     *     <div id=\"child\">你好</div>\r\n     * </div>\r\n     * <script>\r\n     *     UE.dom.domUtils.remove( document.body, true );\r\n     *     //output: true\r\n     *     console.log( document.getElementById( \"child\" ) !== null );\r\n     * </script>\r\n     * ```\r\n     */\r\n    remove:function (node, keepChildren) {\r\n        var parent = node.parentNode,\r\n            child;\r\n        if (parent) {\r\n            if (keepChildren && node.hasChildNodes()) {\r\n                while (child = node.firstChild) {\r\n                    parent.insertBefore(child, node);\r\n                }\r\n            }\r\n            parent.removeChild(node);\r\n        }\r\n        return node;\r\n    },\r\n\r\n    /**\r\n     * 取得node节点的下一个兄弟节点， 如果该节点其后没有兄弟节点， 则递归查找其父节点之后的第一个兄弟节点，\r\n     * 直到找到满足条件的节点或者递归到BODY节点之后才会结束。\r\n     * @method getNextDomNode\r\n     * @param { Node } node 需要获取其后的兄弟节点的节点对象\r\n     * @return { Node | NULL } 如果找满足条件的节点， 则返回该节点， 否则返回NULL\r\n     * @example\r\n     * ```html\r\n     *     <body>\r\n     *      <div id=\"test\">\r\n     *          <span></span>\r\n     *      </div>\r\n     *      <i>xxx</i>\r\n     * </body>\r\n     * <script>\r\n     *\r\n     *     //output: i节点\r\n     *     console.log( UE.dom.domUtils.getNextDomNode( document.getElementById( \"test\" ) ) );\r\n     *\r\n     * </script>\r\n     * ```\r\n     * @example\r\n     * ```html\r\n     * <body>\r\n     *      <div>\r\n     *          <span></span>\r\n     *          <i id=\"test\">xxx</i>\r\n     *      </div>\r\n     *      <b>xxx</b>\r\n     * </body>\r\n     * <script>\r\n     *\r\n     *     //由于id为test的i节点之后没有兄弟节点， 则查找其父节点（div）后面的兄弟节点\r\n     *     //output: b节点\r\n     *     console.log( UE.dom.domUtils.getNextDomNode( document.getElementById( \"test\" ) ) );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 取得node节点的下一个兄弟节点， 如果startFromChild的值为ture，则先获取其子节点，\r\n     * 如果有子节点则直接返回第一个子节点；如果没有子节点或者startFromChild的值为false，\r\n     * 则执行<a href=\"#UE.dom.domUtils.getNextDomNode(Node)\">getNextDomNode(Node node)</a>的查找过程。\r\n     * @method getNextDomNode\r\n     * @param { Node } node 需要获取其后的兄弟节点的节点对象\r\n     * @param { Boolean } startFromChild 查找过程是否从其子节点开始\r\n     * @return { Node | NULL } 如果找满足条件的节点， 则返回该节点， 否则返回NULL\r\n     * @see UE.dom.domUtils.getNextDomNode(Node)\r\n     */\r\n    getNextDomNode:function (node, startFromChild, filterFn, guard) {\r\n        return getDomNode(node, 'firstChild', 'nextSibling', startFromChild, filterFn, guard);\r\n    },\r\n    getPreDomNode:function (node, startFromChild, filterFn, guard) {\r\n        return getDomNode(node, 'lastChild', 'previousSibling', startFromChild, filterFn, guard);\r\n    },\r\n    /**\r\n     * 检测节点node是否属是UEditor定义的bookmark节点\r\n     * @method isBookmarkNode\r\n     * @private\r\n     * @param { Node } node 需要检测的节点对象\r\n     * @return { Boolean } 是否是bookmark节点\r\n     * @example\r\n     * ```html\r\n     * <span id=\"_baidu_bookmark_1\"></span>\r\n     * <script>\r\n     *      var bookmarkNode = document.getElementById(\"_baidu_bookmark_1\");\r\n     *      //output: true\r\n     *      console.log( UE.dom.domUtils.isBookmarkNode( bookmarkNode ) );\r\n     * </script>\r\n     * ```\r\n     */\r\n    isBookmarkNode:function (node) {\r\n        return node.nodeType == 1 && node.id && /^_baidu_bookmark_/i.test(node.id);\r\n    },\r\n    /**\r\n     * 获取节点node所属的window对象\r\n     * @method  getWindow\r\n     * @param { Node } node 节点对象\r\n     * @return { Window } 当前节点所属的window对象\r\n     * @example\r\n     * ```javascript\r\n     * //output: true\r\n     * console.log( UE.dom.domUtils.getWindow( document.body ) === window );\r\n     * ```\r\n     */\r\n    getWindow:function (node) {\r\n        var doc = node.ownerDocument || node;\r\n        return doc.defaultView || doc.parentWindow;\r\n    },\r\n    /**\r\n     * 获取离nodeA与nodeB最近的公共的祖先节点\r\n     * @method  getCommonAncestor\r\n     * @param { Node } nodeA 第一个节点\r\n     * @param { Node } nodeB 第二个节点\r\n     * @remind 如果给定的两个节点是同一个节点， 将直接返回该节点。\r\n     * @return { Node | NULL } 如果未找到公共节点， 返回NULL， 否则返回最近的公共祖先节点。\r\n     * @example\r\n     * ```javascript\r\n     * var commonAncestor = UE.dom.domUtils.getCommonAncestor( document.body, document.body.firstChild );\r\n     * //output: true\r\n     * console.log( commonAncestor.tagName.toLowerCase() === 'body' );\r\n     * ```\r\n     */\r\n    getCommonAncestor:function (nodeA, nodeB) {\r\n        if (nodeA === nodeB)\r\n            return nodeA;\r\n        var parentsA = [nodeA] , parentsB = [nodeB], parent = nodeA, i = -1;\r\n        while (parent = parent.parentNode) {\r\n            if (parent === nodeB) {\r\n                return parent;\r\n            }\r\n            parentsA.push(parent);\r\n        }\r\n        parent = nodeB;\r\n        while (parent = parent.parentNode) {\r\n            if (parent === nodeA)\r\n                return parent;\r\n            parentsB.push(parent);\r\n        }\r\n        parentsA.reverse();\r\n        parentsB.reverse();\r\n        while (i++, parentsA[i] === parentsB[i]) {\r\n        }\r\n        return i == 0 ? null : parentsA[i - 1];\r\n\r\n    },\r\n    /**\r\n     * 清除node节点左右连续为空的兄弟inline节点\r\n     * @method clearEmptySibling\r\n     * @param { Node } node 执行的节点对象， 如果该节点的左右连续的兄弟节点是空的inline节点，\r\n     * 则这些兄弟节点将被删除\r\n     * @grammar UE.dom.domUtils.clearEmptySibling(node,ignoreNext)  //ignoreNext指定是否忽略右边空节点\r\n     * @grammar UE.dom.domUtils.clearEmptySibling(node,ignoreNext,ignorePre)  //ignorePre指定是否忽略左边空节点\r\n     * @example\r\n     * ```html\r\n     * <body>\r\n     *     <div></div>\r\n     *     <span id=\"test\"></span>\r\n     *     <i></i>\r\n     *     <b></b>\r\n     *     <em>xxx</em>\r\n     *     <span></span>\r\n     * </body>\r\n     * <script>\r\n     *\r\n     *      UE.dom.domUtils.clearEmptySibling( document.getElementById( \"test\" ) );\r\n     *\r\n     *      //output: <div></div><span id=\"test\"></span><em>xxx</em><span></span>\r\n     *      console.log( document.body.innerHTML );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 清除node节点左右连续为空的兄弟inline节点， 如果ignoreNext的值为true，\r\n     * 则忽略对右边兄弟节点的操作。\r\n     * @method clearEmptySibling\r\n     * @param { Node } node 执行的节点对象， 如果该节点的左右连续的兄弟节点是空的inline节点，\r\n     * @param { Boolean } ignoreNext 是否忽略忽略对右边的兄弟节点的操作\r\n     * 则这些兄弟节点将被删除\r\n     * @see UE.dom.domUtils.clearEmptySibling(Node)\r\n     */\r\n\r\n    /**\r\n     * 清除node节点左右连续为空的兄弟inline节点， 如果ignoreNext的值为true，\r\n     * 则忽略对右边兄弟节点的操作， 如果ignorePre的值为true，则忽略对左边兄弟节点的操作。\r\n     * @method clearEmptySibling\r\n     * @param { Node } node 执行的节点对象， 如果该节点的左右连续的兄弟节点是空的inline节点，\r\n     * @param { Boolean } ignoreNext 是否忽略忽略对右边的兄弟节点的操作\r\n     * @param { Boolean } ignorePre 是否忽略忽略对左边的兄弟节点的操作\r\n     * 则这些兄弟节点将被删除\r\n     * @see UE.dom.domUtils.clearEmptySibling(Node)\r\n     */\r\n    clearEmptySibling:function (node, ignoreNext, ignorePre) {\r\n        function clear(next, dir) {\r\n            var tmpNode;\r\n            while (next && !domUtils.isBookmarkNode(next) && (domUtils.isEmptyInlineElement(next)\r\n                //这里不能把空格算进来会吧空格干掉，出现文字间的空格丢掉了\r\n                || !new RegExp('[^\\t\\n\\r' + domUtils.fillChar + ']').test(next.nodeValue) )) {\r\n                tmpNode = next[dir];\r\n                domUtils.remove(next);\r\n                next = tmpNode;\r\n            }\r\n        }\r\n        !ignoreNext && clear(node.nextSibling, 'nextSibling');\r\n        !ignorePre && clear(node.previousSibling, 'previousSibling');\r\n    },\r\n    /**\r\n     * 将一个文本节点textNode拆分成两个文本节点，offset指定拆分位置\r\n     * @method split\r\n     * @param { Node } textNode 需要拆分的文本节点对象\r\n     * @param { int } offset 需要拆分的位置， 位置计算从0开始\r\n     * @return { Node } 拆分后形成的新节点\r\n     * @example\r\n     * ```html\r\n     * <div id=\"test\">abcdef</div>\r\n     * <script>\r\n     *      var newNode = UE.dom.domUtils.split( document.getElementById( \"test\" ).firstChild, 3 );\r\n     *      //output: def\r\n     *      console.log( newNode.nodeValue );\r\n     * </script>\r\n     * ```\r\n     */\r\n    split:function (node, offset) {\r\n        var doc = node.ownerDocument;\r\n        if (browser.ie && offset == node.nodeValue.length) {\r\n            var next = doc.createTextNode('');\r\n            return domUtils.insertAfter(node, next);\r\n        }\r\n        var retval = node.splitText(offset);\r\n        //ie8下splitText不会跟新childNodes,我们手动触发他的更新\r\n        if (browser.ie8) {\r\n            var tmpNode = doc.createTextNode('');\r\n            domUtils.insertAfter(retval, tmpNode);\r\n            domUtils.remove(tmpNode);\r\n        }\r\n        return retval;\r\n    },\r\n\r\n    /**\r\n     * 检测文本节点textNode是否为空节点（包括空格、换行、占位符等字符）\r\n     * @method  isWhitespace\r\n     * @param { Node } node 需要检测的节点对象\r\n     * @return { Boolean } 检测的节点是否为空\r\n     * @example\r\n     * ```html\r\n     * <div id=\"test\">\r\n     *\r\n     * </div>\r\n     * <script>\r\n     *      //output: true\r\n     *      console.log( UE.dom.domUtils.isWhitespace( document.getElementById(\"test\").firstChild ) );\r\n     * </script>\r\n     * ```\r\n     */\r\n    isWhitespace:function (node) {\r\n        return !new RegExp('[^ \\t\\n\\r' + domUtils.fillChar + ']').test(node.nodeValue);\r\n    },\r\n    /**\r\n     * 获取元素element相对于viewport的位置坐标\r\n     * @method getXY\r\n     * @param { Node } element 需要计算位置的节点对象\r\n     * @return { Object } 返回形如{x:left,y:top}的一个key-value映射对象， 其中键x代表水平偏移距离，\r\n     *                          y代表垂直偏移距离。\r\n     *\r\n     * @example\r\n     * ```javascript\r\n     * var location = UE.dom.domUtils.getXY( document.getElementById(\"test\") );\r\n     * //output: test的坐标为: 12, 24\r\n     * console.log( 'test的坐标为： ', location.x, ',', location.y );\r\n     * ```\r\n     */\r\n    getXY:function (element) {\r\n        var x = 0, y = 0;\r\n        while (element.offsetParent) {\r\n            y += element.offsetTop;\r\n            x += element.offsetLeft;\r\n            element = element.offsetParent;\r\n        }\r\n        return { 'x':x, 'y':y};\r\n    },\r\n    /**\r\n     * 为元素element绑定原生DOM事件，type为事件类型，handler为处理函数\r\n     * @method on\r\n     * @param { Node } element 需要绑定事件的节点对象\r\n     * @param { String } type 绑定的事件类型\r\n     * @param { Function } handler 事件处理器\r\n     * @example\r\n     * ```javascript\r\n     * UE.dom.domUtils.on(document.body,\"click\",function(e){\r\n     *     //e为事件对象，this为被点击元素对戏那个\r\n     * });\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 为元素element绑定原生DOM事件，type为事件类型，handler为处理函数\r\n     * @method on\r\n     * @param { Node } element 需要绑定事件的节点对象\r\n     * @param { Array } type 绑定的事件类型数组\r\n     * @param { Function } handler 事件处理器\r\n     * @example\r\n     * ```javascript\r\n     * UE.dom.domUtils.on(document.body,[\"click\",\"mousedown\"],function(evt){\r\n     *     //evt为事件对象，this为被点击元素对象\r\n     * });\r\n     * ```\r\n     */\r\n    on:function (element, type, handler) {\r\n\r\n        var types = utils.isArray(type) ? type : utils.trim(type).split(/\\s+/),\r\n            k = types.length;\r\n        if (k) while (k--) {\r\n            type = types[k];\r\n            if (element.addEventListener) {\r\n                element.addEventListener(type, handler, false);\r\n            } else {\r\n                if (!handler._d) {\r\n                    handler._d = {\r\n                        els : []\r\n                    };\r\n                }\r\n                var key = type + handler.toString(),index = utils.indexOf(handler._d.els,element);\r\n                if (!handler._d[key] || index == -1) {\r\n                    if(index == -1){\r\n                        handler._d.els.push(element);\r\n                    }\r\n                    if(!handler._d[key]){\r\n                        handler._d[key] = function (evt) {\r\n                            return handler.call(evt.srcElement, evt || window.event);\r\n                        };\r\n                    }\r\n\r\n\r\n                    element.attachEvent('on' + type, handler._d[key]);\r\n                }\r\n            }\r\n        }\r\n        element = null;\r\n    },\r\n    /**\r\n     * 解除DOM事件绑定\r\n     * @method un\r\n     * @param { Node } element 需要解除事件绑定的节点对象\r\n     * @param { String } type 需要接触绑定的事件类型\r\n     * @param { Function } handler 对应的事件处理器\r\n     * @example\r\n     * ```javascript\r\n     * UE.dom.domUtils.un(document.body,\"click\",function(evt){\r\n     *     //evt为事件对象，this为被点击元素对象\r\n     * });\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 解除DOM事件绑定\r\n     * @method un\r\n     * @param { Node } element 需要解除事件绑定的节点对象\r\n     * @param { Array } type 需要接触绑定的事件类型数组\r\n     * @param { Function } handler 对应的事件处理器\r\n     * @example\r\n     * ```javascript\r\n     * UE.dom.domUtils.un(document.body, [\"click\",\"mousedown\"],function(evt){\r\n     *     //evt为事件对象，this为被点击元素对象\r\n     * });\r\n     * ```\r\n     */\r\n    un:function (element, type, handler) {\r\n        var types = utils.isArray(type) ? type : utils.trim(type).split(/\\s+/),\r\n            k = types.length;\r\n        if (k) while (k--) {\r\n            type = types[k];\r\n            if (element.removeEventListener) {\r\n                element.removeEventListener(type, handler, false);\r\n            } else {\r\n                var key = type + handler.toString();\r\n                try{\r\n                    element.detachEvent('on' + type, handler._d ? handler._d[key] : handler);\r\n                }catch(e){}\r\n                if (handler._d && handler._d[key]) {\r\n                    var index = utils.indexOf(handler._d.els,element);\r\n                    if(index!=-1){\r\n                        handler._d.els.splice(index,1);\r\n                    }\r\n                    handler._d.els.length == 0 && delete handler._d[key];\r\n                }\r\n            }\r\n        }\r\n    },\r\n\r\n    /**\r\n     * 比较节点nodeA与节点nodeB是否具有相同的标签名、属性名以及属性值\r\n     * @method  isSameElement\r\n     * @param { Node } nodeA 需要比较的节点\r\n     * @param { Node } nodeB 需要比较的节点\r\n     * @return { Boolean } 两个节点是否具有相同的标签名、属性名以及属性值\r\n     * @example\r\n     * ```html\r\n     * <span style=\"font-size:12px\">ssss</span>\r\n     * <span style=\"font-size:12px\">bbbbb</span>\r\n     * <span style=\"font-size:13px\">ssss</span>\r\n     * <span style=\"font-size:14px\">bbbbb</span>\r\n     *\r\n     * <script>\r\n     *\r\n     *     var nodes = document.getElementsByTagName( \"span\" );\r\n     *\r\n     *     //output: true\r\n     *     console.log( UE.dom.domUtils.isSameElement( nodes[0], nodes[1] ) );\r\n     *\r\n     *     //output: false\r\n     *     console.log( UE.dom.domUtils.isSameElement( nodes[2], nodes[3] ) );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n    isSameElement:function (nodeA, nodeB) {\r\n        if (nodeA.tagName != nodeB.tagName) {\r\n            return false;\r\n        }\r\n        var thisAttrs = nodeA.attributes,\r\n            otherAttrs = nodeB.attributes;\r\n        if (!ie && thisAttrs.length != otherAttrs.length) {\r\n            return false;\r\n        }\r\n        var attrA, attrB, al = 0, bl = 0;\r\n        for (var i = 0; attrA = thisAttrs[i++];) {\r\n            if (attrA.nodeName == 'style') {\r\n                if (attrA.specified) {\r\n                    al++;\r\n                }\r\n                if (domUtils.isSameStyle(nodeA, nodeB)) {\r\n                    continue;\r\n                } else {\r\n                    return false;\r\n                }\r\n            }\r\n            if (ie) {\r\n                if (attrA.specified) {\r\n                    al++;\r\n                    attrB = otherAttrs.getNamedItem(attrA.nodeName);\r\n                } else {\r\n                    continue;\r\n                }\r\n            } else {\r\n                attrB = nodeB.attributes[attrA.nodeName];\r\n            }\r\n            if (!attrB.specified || attrA.nodeValue != attrB.nodeValue) {\r\n                return false;\r\n            }\r\n        }\r\n        // 有可能attrB的属性包含了attrA的属性之外还有自己的属性\r\n        if (ie) {\r\n            for (i = 0; attrB = otherAttrs[i++];) {\r\n                if (attrB.specified) {\r\n                    bl++;\r\n                }\r\n            }\r\n            if (al != bl) {\r\n                return false;\r\n            }\r\n        }\r\n        return true;\r\n    },\r\n\r\n    /**\r\n     * 判断节点nodeA与节点nodeB的元素的style属性是否一致\r\n     * @method isSameStyle\r\n     * @param { Node } nodeA 需要比较的节点\r\n     * @param { Node } nodeB 需要比较的节点\r\n     * @return { Boolean } 两个节点是否具有相同的style属性值\r\n     * @example\r\n     * ```html\r\n     * <span style=\"font-size:12px\">ssss</span>\r\n     * <span style=\"font-size:12px\">bbbbb</span>\r\n     * <span style=\"font-size:13px\">ssss</span>\r\n     * <span style=\"font-size:14px\">bbbbb</span>\r\n     *\r\n     * <script>\r\n     *\r\n     *     var nodes = document.getElementsByTagName( \"span\" );\r\n     *\r\n     *     //output: true\r\n     *     console.log( UE.dom.domUtils.isSameStyle( nodes[0], nodes[1] ) );\r\n     *\r\n     *     //output: false\r\n     *     console.log( UE.dom.domUtils.isSameStyle( nodes[2], nodes[3] ) );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n    isSameStyle:function (nodeA, nodeB) {\r\n        var styleA = nodeA.style.cssText.replace(/( ?; ?)/g, ';').replace(/( ?: ?)/g, ':'),\r\n            styleB = nodeB.style.cssText.replace(/( ?; ?)/g, ';').replace(/( ?: ?)/g, ':');\r\n        if (browser.opera) {\r\n            styleA = nodeA.style;\r\n            styleB = nodeB.style;\r\n            if (styleA.length != styleB.length)\r\n                return false;\r\n            for (var p in styleA) {\r\n                if (/^(\\d+|csstext)$/i.test(p)) {\r\n                    continue;\r\n                }\r\n                if (styleA[p] != styleB[p]) {\r\n                    return false;\r\n                }\r\n            }\r\n            return true;\r\n        }\r\n        if (!styleA || !styleB) {\r\n            return styleA == styleB;\r\n        }\r\n        styleA = styleA.split(';');\r\n        styleB = styleB.split(';');\r\n        if (styleA.length != styleB.length) {\r\n            return false;\r\n        }\r\n        for (var i = 0, ci; ci = styleA[i++];) {\r\n            if (utils.indexOf(styleB, ci) == -1) {\r\n                return false;\r\n            }\r\n        }\r\n        return true;\r\n    },\r\n    /**\r\n     * 检查节点node是否为block元素\r\n     * @method isBlockElm\r\n     * @param { Node } node 需要检测的节点对象\r\n     * @return { Boolean } 是否是block元素节点\r\n     * @warning 该方法的判断规则如下： 如果该元素原本是block元素， 则不论该元素当前的css样式是什么都会返回true；\r\n     *          否则，检测该元素的css样式， 如果该元素当前是block元素， 则返回true。 其余情况下都返回false。\r\n     * @example\r\n     * ```html\r\n     * <span id=\"test1\" style=\"display: block\"></span>\r\n     * <span id=\"test2\"></span>\r\n     * <div id=\"test3\" style=\"display: inline\"></div>\r\n     *\r\n     * <script>\r\n     *\r\n     *     //output: true\r\n     *     console.log( UE.dom.domUtils.isBlockElm( document.getElementById(\"test1\") ) );\r\n     *\r\n     *     //output: false\r\n     *     console.log( UE.dom.domUtils.isBlockElm( document.getElementById(\"test2\") ) );\r\n     *\r\n     *     //output: true\r\n     *     console.log( UE.dom.domUtils.isBlockElm( document.getElementById(\"test3\") ) );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n    isBlockElm:function (node) {\r\n        return node.nodeType == 1 && (dtd.$block[node.tagName] || styleBlock[domUtils.getComputedStyle(node, 'display')]) && !dtd.$nonChild[node.tagName];\r\n    },\r\n    /**\r\n     * 检测node节点是否为body节点\r\n     * @method isBody\r\n     * @param { Element } node 需要检测的dom元素\r\n     * @return { Boolean } 给定的元素是否是body元素\r\n     * @example\r\n     * ```javascript\r\n     * //output: true\r\n     * console.log( UE.dom.domUtils.isBody( document.body ) );\r\n     * ```\r\n     */\r\n    isBody:function (node) {\r\n        return  node && node.nodeType == 1 && node.tagName.toLowerCase() == 'body';\r\n    },\r\n    /**\r\n     * 以node节点为分界，将该节点的指定祖先节点parent拆分成两个独立的节点，\r\n     * 拆分形成的两个节点之间是node节点\r\n     * @method breakParent\r\n     * @param { Node } node 作为分界的节点对象\r\n     * @param { Node } parent 该节点必须是node节点的祖先节点， 且是block节点。\r\n     * @return { Node } 给定的node分界节点\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     *      var node = document.createElement(\"span\"),\r\n     *          wrapNode = document.createElement( \"div\" ),\r\n     *          parent = document.createElement(\"p\");\r\n     *\r\n     *      parent.appendChild( node );\r\n     *      wrapNode.appendChild( parent );\r\n     *\r\n     *      //拆分前\r\n     *      //output: <p><span></span></p>\r\n     *      console.log( wrapNode.innerHTML );\r\n     *\r\n     *\r\n     *      UE.dom.domUtils.breakParent( node, parent );\r\n     *      //拆分后\r\n     *      //output: <p></p><span></span><p></p>\r\n     *      console.log( wrapNode.innerHTML );\r\n     *\r\n     * ```\r\n     */\r\n    breakParent:function (node, parent) {\r\n        var tmpNode,\r\n            parentClone = node,\r\n            clone = node,\r\n            leftNodes,\r\n            rightNodes;\r\n        do {\r\n            parentClone = parentClone.parentNode;\r\n            if (leftNodes) {\r\n                tmpNode = parentClone.cloneNode(false);\r\n                tmpNode.appendChild(leftNodes);\r\n                leftNodes = tmpNode;\r\n                tmpNode = parentClone.cloneNode(false);\r\n                tmpNode.appendChild(rightNodes);\r\n                rightNodes = tmpNode;\r\n            } else {\r\n                leftNodes = parentClone.cloneNode(false);\r\n                rightNodes = leftNodes.cloneNode(false);\r\n            }\r\n            while (tmpNode = clone.previousSibling) {\r\n                leftNodes.insertBefore(tmpNode, leftNodes.firstChild);\r\n            }\r\n            while (tmpNode = clone.nextSibling) {\r\n                rightNodes.appendChild(tmpNode);\r\n            }\r\n            clone = parentClone;\r\n        } while (parent !== parentClone);\r\n        tmpNode = parent.parentNode;\r\n        tmpNode.insertBefore(leftNodes, parent);\r\n        tmpNode.insertBefore(rightNodes, parent);\r\n        tmpNode.insertBefore(node, rightNodes);\r\n        domUtils.remove(parent);\r\n        return node;\r\n    },\r\n    /**\r\n     * 检查节点node是否是空inline节点\r\n     * @method  isEmptyInlineElement\r\n     * @param { Node } node 需要检测的节点对象\r\n     * @return { Number }  如果给定的节点是空的inline节点， 则返回1, 否则返回0。\r\n     * @example\r\n     * ```html\r\n     * <b><i></i></b> => 1\r\n     * <b><i></i><u></u></b> => 1\r\n     * <b></b> => 1\r\n     * <b>xx<i></i></b> => 0\r\n     * ```\r\n     */\r\n    isEmptyInlineElement:function (node) {\r\n        if (node.nodeType != 1 || !dtd.$removeEmpty[ node.tagName ]) {\r\n            return 0;\r\n        }\r\n        node = node.firstChild;\r\n        while (node) {\r\n            //如果是创建的bookmark就跳过\r\n            if (domUtils.isBookmarkNode(node)) {\r\n                return 0;\r\n            }\r\n            if (node.nodeType == 1 && !domUtils.isEmptyInlineElement(node) ||\r\n                node.nodeType == 3 && !domUtils.isWhitespace(node)\r\n                ) {\r\n                return 0;\r\n            }\r\n            node = node.nextSibling;\r\n        }\r\n        return 1;\r\n\r\n    },\r\n\r\n    /**\r\n     * 删除node节点下首尾两端的空白文本子节点\r\n     * @method trimWhiteTextNode\r\n     * @param { Element } node 需要执行删除操作的元素对象\r\n     * @example\r\n     * ```javascript\r\n     *      var node = document.createElement(\"div\");\r\n     *\r\n     *      node.appendChild( document.createTextNode( \"\" ) );\r\n     *\r\n     *      node.appendChild( document.createElement(\"div\") );\r\n     *\r\n     *      node.appendChild( document.createTextNode( \"\" ) );\r\n     *\r\n     *      //3\r\n     *      console.log( node.childNodes.length );\r\n     *\r\n     *      UE.dom.domUtils.trimWhiteTextNode( node );\r\n     *\r\n     *      //1\r\n     *      console.log( node.childNodes.length );\r\n     * ```\r\n     */\r\n    trimWhiteTextNode:function (node) {\r\n        function remove(dir) {\r\n            var child;\r\n            while ((child = node[dir]) && child.nodeType == 3 && domUtils.isWhitespace(child)) {\r\n                node.removeChild(child);\r\n            }\r\n        }\r\n        remove('firstChild');\r\n        remove('lastChild');\r\n    },\r\n\r\n    /**\r\n     * 合并node节点下相同的子节点\r\n     * @name mergeChild\r\n     * @desc\r\n     * UE.dom.domUtils.mergeChild(node,tagName) //tagName要合并的子节点的标签\r\n     * @example\r\n     * <p><span style=\"font-size:12px;\">xx<span style=\"font-size:12px;\">aa</span>xx</span></p>\r\n     * ==> UE.dom.domUtils.mergeChild(node,'span')\r\n     * <p><span style=\"font-size:12px;\">xxaaxx</span></p>\r\n     */\r\n    mergeChild:function (node, tagName, attrs) {\r\n        var list = domUtils.getElementsByTagName(node, node.tagName.toLowerCase());\r\n        for (var i = 0, ci; ci = list[i++];) {\r\n            if (!ci.parentNode || domUtils.isBookmarkNode(ci)) {\r\n                continue;\r\n            }\r\n            //span单独处理\r\n            if (ci.tagName.toLowerCase() == 'span') {\r\n                if (node === ci.parentNode) {\r\n                    domUtils.trimWhiteTextNode(node);\r\n                    if (node.childNodes.length == 1) {\r\n                        node.style.cssText = ci.style.cssText + \";\" + node.style.cssText;\r\n                        domUtils.remove(ci, true);\r\n                        continue;\r\n                    }\r\n                }\r\n                ci.style.cssText = node.style.cssText + ';' + ci.style.cssText;\r\n                if (attrs) {\r\n                    var style = attrs.style;\r\n                    if (style) {\r\n                        style = style.split(';');\r\n                        for (var j = 0, s; s = style[j++];) {\r\n                            ci.style[utils.cssStyleToDomStyle(s.split(':')[0])] = s.split(':')[1];\r\n                        }\r\n                    }\r\n                }\r\n                if (domUtils.isSameStyle(ci, node)) {\r\n                    domUtils.remove(ci, true);\r\n                }\r\n                continue;\r\n            }\r\n            if (domUtils.isSameElement(node, ci)) {\r\n                domUtils.remove(ci, true);\r\n            }\r\n        }\r\n    },\r\n\r\n    /**\r\n     * 原生方法getElementsByTagName的封装\r\n     * @method getElementsByTagName\r\n     * @param { Node } node 目标节点对象\r\n     * @param { String } tagName 需要查找的节点的tagName， 多个tagName以空格分割\r\n     * @return { Array } 符合条件的节点集合\r\n     */\r\n    getElementsByTagName:function (node, name,filter) {\r\n        if(filter && utils.isString(filter)){\r\n           var className = filter;\r\n           filter =  function(node){return domUtils.hasClass(node,className)}\r\n        }\r\n        name = utils.trim(name).replace(/[ ]{2,}/g,' ').split(' ');\r\n        var arr = [];\r\n        for(var n = 0,ni;ni=name[n++];){\r\n            var list = node.getElementsByTagName(ni);\r\n            for (var i = 0, ci; ci = list[i++];) {\r\n                if(!filter || filter(ci))\r\n                    arr.push(ci);\r\n            }\r\n        }\r\n\r\n        return arr;\r\n    },\r\n    /**\r\n     * 将节点node提取到父节点上\r\n     * @method mergeToParent\r\n     * @param { Element } node 需要提取的元素对象\r\n     * @example\r\n     * ```html\r\n     * <div id=\"parent\">\r\n     *     <div id=\"sub\">\r\n     *         <span id=\"child\"></span>\r\n     *     </div>\r\n     * </div>\r\n     *\r\n     * <script>\r\n     *\r\n     *     var child = document.getElementById( \"child\" );\r\n     *\r\n     *     //output: sub\r\n     *     console.log( child.parentNode.id );\r\n     *\r\n     *     UE.dom.domUtils.mergeToParent( child );\r\n     *\r\n     *     //output: parent\r\n     *     console.log( child.parentNode.id );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n    mergeToParent:function (node) {\r\n        var parent = node.parentNode;\r\n        while (parent && dtd.$removeEmpty[parent.tagName]) {\r\n            if (parent.tagName == node.tagName || parent.tagName == 'A') {//针对a标签单独处理\r\n                domUtils.trimWhiteTextNode(parent);\r\n                //span需要特殊处理  不处理这样的情况 <span stlye=\"color:#fff\">xxx<span style=\"color:#ccc\">xxx</span>xxx</span>\r\n                if (parent.tagName == 'SPAN' && !domUtils.isSameStyle(parent, node)\r\n                    || (parent.tagName == 'A' && node.tagName == 'SPAN')) {\r\n                    if (parent.childNodes.length > 1 || parent !== node.parentNode) {\r\n                        node.style.cssText = parent.style.cssText + \";\" + node.style.cssText;\r\n                        parent = parent.parentNode;\r\n                        continue;\r\n                    } else {\r\n                        parent.style.cssText += \";\" + node.style.cssText;\r\n                        //trace:952 a标签要保持下划线\r\n                        if (parent.tagName == 'A') {\r\n                            parent.style.textDecoration = 'underline';\r\n                        }\r\n                    }\r\n                }\r\n                if (parent.tagName != 'A') {\r\n                    parent === node.parentNode && domUtils.remove(node, true);\r\n                    break;\r\n                }\r\n            }\r\n            parent = parent.parentNode;\r\n        }\r\n    },\r\n    /**\r\n     * 合并节点node的左右兄弟节点\r\n     * @method mergeSibling\r\n     * @param { Element } node 需要合并的目标节点\r\n     * @example\r\n     * ```html\r\n     * <b>xxxx</b><b id=\"test\">ooo</b><b>xxxx</b>\r\n     *\r\n     * <script>\r\n     *     var demoNode = document.getElementById(\"test\");\r\n     *     UE.dom.domUtils.mergeSibling( demoNode );\r\n     *     //output: xxxxoooxxxx\r\n     *     console.log( demoNode.innerHTML );\r\n     * </script>\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 合并节点node的左右兄弟节点， 可以根据给定的条件选择是否忽略合并左节点。\r\n     * @method mergeSibling\r\n     * @param { Element } node 需要合并的目标节点\r\n     * @param { Boolean } ignorePre 是否忽略合并左节点\r\n     * @example\r\n     * ```html\r\n     * <b>xxxx</b><b id=\"test\">ooo</b><b>xxxx</b>\r\n     *\r\n     * <script>\r\n     *     var demoNode = document.getElementById(\"test\");\r\n     *     UE.dom.domUtils.mergeSibling( demoNode, true );\r\n     *     //output: oooxxxx\r\n     *     console.log( demoNode.innerHTML );\r\n     * </script>\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 合并节点node的左右兄弟节点，可以根据给定的条件选择是否忽略合并左右节点。\r\n     * @method mergeSibling\r\n     * @param { Element } node 需要合并的目标节点\r\n     * @param { Boolean } ignorePre 是否忽略合并左节点\r\n     * @param { Boolean } ignoreNext 是否忽略合并右节点\r\n     * @remind 如果同时忽略左右节点， 则该操作什么也不会做\r\n     * @example\r\n     * ```html\r\n     * <b>xxxx</b><b id=\"test\">ooo</b><b>xxxx</b>\r\n     *\r\n     * <script>\r\n     *     var demoNode = document.getElementById(\"test\");\r\n     *     UE.dom.domUtils.mergeSibling( demoNode, false, true );\r\n     *     //output: xxxxooo\r\n     *     console.log( demoNode.innerHTML );\r\n     * </script>\r\n     * ```\r\n     */\r\n    mergeSibling:function (node, ignorePre, ignoreNext) {\r\n        function merge(rtl, start, node) {\r\n            var next;\r\n            if ((next = node[rtl]) && !domUtils.isBookmarkNode(next) && next.nodeType == 1 && domUtils.isSameElement(node, next)) {\r\n                while (next.firstChild) {\r\n                    if (start == 'firstChild') {\r\n                        node.insertBefore(next.lastChild, node.firstChild);\r\n                    } else {\r\n                        node.appendChild(next.firstChild);\r\n                    }\r\n                }\r\n                domUtils.remove(next);\r\n            }\r\n        }\r\n        !ignorePre && merge('previousSibling', 'firstChild', node);\r\n        !ignoreNext && merge('nextSibling', 'lastChild', node);\r\n    },\r\n\r\n    /**\r\n     * 设置节点node及其子节点不会被选中\r\n     * @method unSelectable\r\n     * @param { Element } node 需要执行操作的dom元素\r\n     * @remind 执行该操作后的节点， 将不能被鼠标选中\r\n     * @example\r\n     * ```javascript\r\n     * UE.dom.domUtils.unSelectable( document.body );\r\n     * ```\r\n     */\r\n    unSelectable:ie && browser.ie9below || browser.opera ? function (node) {\r\n        //for ie9\r\n        node.onselectstart = function () {\r\n            return false;\r\n        };\r\n        node.onclick = node.onkeyup = node.onkeydown = function () {\r\n            return false;\r\n        };\r\n        node.unselectable = 'on';\r\n        node.setAttribute(\"unselectable\", \"on\");\r\n        for (var i = 0, ci; ci = node.all[i++];) {\r\n            switch (ci.tagName.toLowerCase()) {\r\n                case 'iframe' :\r\n                case 'textarea' :\r\n                case 'input' :\r\n                case 'select' :\r\n                    break;\r\n                default :\r\n                    ci.unselectable = 'on';\r\n                    node.setAttribute(\"unselectable\", \"on\");\r\n            }\r\n        }\r\n    } : function (node) {\r\n        node.style.MozUserSelect =\r\n            node.style.webkitUserSelect =\r\n                node.style.msUserSelect =\r\n                    node.style.KhtmlUserSelect = 'none';\r\n    },\r\n    /**\r\n     * 删除节点node上的指定属性名称的属性\r\n     * @method  removeAttributes\r\n     * @param { Node } node 需要删除属性的节点对象\r\n     * @param { String } attrNames 可以是空格隔开的多个属性名称，该操作将会依次删除相应的属性\r\n     * @example\r\n     * ```html\r\n     * <div id=\"wrap\">\r\n     *      <span style=\"font-size:14px;\" id=\"test\" name=\"followMe\">xxxxx</span>\r\n     * </div>\r\n     *\r\n     * <script>\r\n     *\r\n     *     UE.dom.domUtils.removeAttributes( document.getElementById( \"test\" ), \"id name\" );\r\n     *\r\n     *     //output: <span style=\"font-size:14px;\">xxxxx</span>\r\n     *     console.log( document.getElementById(\"wrap\").innerHTML );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 删除节点node上的指定属性名称的属性\r\n     * @method  removeAttributes\r\n     * @param { Node } node 需要删除属性的节点对象\r\n     * @param { Array } attrNames 需要删除的属性名数组\r\n     * @example\r\n     * ```html\r\n     * <div id=\"wrap\">\r\n     *      <span style=\"font-size:14px;\" id=\"test\" name=\"followMe\">xxxxx</span>\r\n     * </div>\r\n     *\r\n     * <script>\r\n     *\r\n     *     UE.dom.domUtils.removeAttributes( document.getElementById( \"test\" ), [\"id\", \"name\"] );\r\n     *\r\n     *     //output: <span style=\"font-size:14px;\">xxxxx</span>\r\n     *     console.log( document.getElementById(\"wrap\").innerHTML );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n    removeAttributes:function (node, attrNames) {\r\n        attrNames = utils.isArray(attrNames) ? attrNames : utils.trim(attrNames).replace(/[ ]{2,}/g,' ').split(' ');\r\n        for (var i = 0, ci; ci = attrNames[i++];) {\r\n            ci = attrFix[ci] || ci;\r\n            switch (ci) {\r\n                case 'className':\r\n                    node[ci] = '';\r\n                    break;\r\n                case 'style':\r\n                    node.style.cssText = '';\r\n                    var val = node.getAttributeNode('style');\r\n                    !browser.ie && val && node.removeAttributeNode(val);\r\n            }\r\n            node.removeAttribute(ci);\r\n        }\r\n    },\r\n    /**\r\n     * 在doc下创建一个标签名为tag，属性为attrs的元素\r\n     * @method createElement\r\n     * @param { DomDocument } doc 新创建的元素属于该document节点创建\r\n     * @param { String } tagName 需要创建的元素的标签名\r\n     * @param { Object } attrs 新创建的元素的属性key-value集合\r\n     * @return { Element } 新创建的元素对象\r\n     * @example\r\n     * ```javascript\r\n     * var ele = UE.dom.domUtils.createElement( document, 'div', {\r\n     *     id: 'test'\r\n     * } );\r\n     *\r\n     * //output: DIV\r\n     * console.log( ele.tagName );\r\n     *\r\n     * //output: test\r\n     * console.log( ele.id );\r\n     *\r\n     * ```\r\n     */\r\n    createElement:function (doc, tag, attrs) {\r\n        return domUtils.setAttributes(doc.createElement(tag), attrs)\r\n    },\r\n    /**\r\n     * 为节点node添加属性attrs，attrs为属性键值对\r\n     * @method setAttributes\r\n     * @param { Element } node 需要设置属性的元素对象\r\n     * @param { Object } attrs 需要设置的属性名-值对\r\n     * @return { Element } 设置属性的元素对象\r\n     * @example\r\n     * ```html\r\n     * <span id=\"test\"></span>\r\n     *\r\n     * <script>\r\n     *\r\n     *     var testNode = UE.dom.domUtils.setAttributes( document.getElementById( \"test\" ), {\r\n     *         id: 'demo'\r\n     *     } );\r\n     *\r\n     *     //output: demo\r\n     *     console.log( testNode.id );\r\n     *\r\n     * </script>\r\n     *\r\n     */\r\n    setAttributes:function (node, attrs) {\r\n        for (var attr in attrs) {\r\n            if(attrs.hasOwnProperty(attr)){\r\n                var value = attrs[attr];\r\n                switch (attr) {\r\n                    case 'class':\r\n                        //ie下要这样赋值，setAttribute不起作用\r\n                        node.className = value;\r\n                        break;\r\n                    case 'style' :\r\n                        node.style.cssText = node.style.cssText + \";\" + value;\r\n                        break;\r\n                    case 'innerHTML':\r\n                        node[attr] = value;\r\n                        break;\r\n                    case 'value':\r\n                        node.value = value;\r\n                        break;\r\n                    default:\r\n                        node.setAttribute(attrFix[attr] || attr, value);\r\n                }\r\n            }\r\n        }\r\n        return node;\r\n    },\r\n\r\n    /**\r\n     * 获取元素element经过计算后的样式值\r\n     * @method getComputedStyle\r\n     * @param { Element } element 需要获取样式的元素对象\r\n     * @param { String } styleName 需要获取的样式名\r\n     * @return { String } 获取到的样式值\r\n     * @example\r\n     * ```html\r\n     * <style type=\"text/css\">\r\n     *      #test {\r\n     *          font-size: 15px;\r\n     *      }\r\n     * </style>\r\n     *\r\n     * <span id=\"test\"></span>\r\n     *\r\n     * <script>\r\n     *     //output: 15px\r\n     *     console.log( UE.dom.domUtils.getComputedStyle( document.getElementById( \"test\" ), 'font-size' ) );\r\n     * </script>\r\n     * ```\r\n     */\r\n    getComputedStyle:function (element, styleName) {\r\n        //一下的属性单独处理\r\n        var pros = 'width height top left';\r\n\r\n        if(pros.indexOf(styleName) > -1){\r\n            return element['offset' + styleName.replace(/^\\w/,function(s){return s.toUpperCase()})] + 'px';\r\n        }\r\n        //忽略文本节点\r\n        if (element.nodeType == 3) {\r\n            element = element.parentNode;\r\n        }\r\n        //ie下font-size若body下定义了font-size，则从currentStyle里会取到这个font-size. 取不到实际值，故此修改.\r\n        if (browser.ie && browser.version < 9 && styleName == 'font-size' && !element.style.fontSize &&\r\n            !dtd.$empty[element.tagName] && !dtd.$nonChild[element.tagName]) {\r\n            var span = element.ownerDocument.createElement('span');\r\n            span.style.cssText = 'padding:0;border:0;font-family:simsun;';\r\n            span.innerHTML = '.';\r\n            element.appendChild(span);\r\n            var result = span.offsetHeight;\r\n            element.removeChild(span);\r\n            span = null;\r\n            return result + 'px';\r\n        }\r\n        try {\r\n            var value = domUtils.getStyle(element, styleName) ||\r\n                (window.getComputedStyle ? domUtils.getWindow(element).getComputedStyle(element, '').getPropertyValue(styleName) :\r\n                    ( element.currentStyle || element.style )[utils.cssStyleToDomStyle(styleName)]);\r\n\r\n        } catch (e) {\r\n            return \"\";\r\n        }\r\n        return utils.transUnitToPx(utils.fixColor(styleName, value));\r\n    },\r\n    /**\r\n     * 删除元素element指定的className\r\n     * @method removeClasses\r\n     * @param { Element } ele 需要删除class的元素节点\r\n     * @param { String } classNames 需要删除的className， 多个className之间以空格分开\r\n     * @example\r\n     * ```html\r\n     * <span id=\"test\" class=\"test1 test2 test3\">xxx</span>\r\n     *\r\n     * <script>\r\n     *\r\n     *     var testNode = document.getElementById( \"test\" );\r\n     *     UE.dom.domUtils.removeClasses( testNode, \"test1 test2\" );\r\n     *\r\n     *     //output: test3\r\n     *     console.log( testNode.className );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 删除元素element指定的className\r\n     * @method removeClasses\r\n     * @param { Element } ele 需要删除class的元素节点\r\n     * @param { Array } classNames 需要删除的className数组\r\n     * @example\r\n     * ```html\r\n     * <span id=\"test\" class=\"test1 test2 test3\">xxx</span>\r\n     *\r\n     * <script>\r\n     *\r\n     *     var testNode = document.getElementById( \"test\" );\r\n     *     UE.dom.domUtils.removeClasses( testNode, [\"test1\", \"test2\"] );\r\n     *\r\n     *     //output: test3\r\n     *     console.log( testNode.className );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n    removeClasses:function (elm, classNames) {\r\n        classNames = utils.isArray(classNames) ? classNames :\r\n            utils.trim(classNames).replace(/[ ]{2,}/g,' ').split(' ');\r\n        for(var i = 0,ci,cls = elm.className;ci=classNames[i++];){\r\n            cls = cls.replace(new RegExp('\\\\b' + ci + '\\\\b'),'')\r\n        }\r\n        cls = utils.trim(cls).replace(/[ ]{2,}/g,' ');\r\n        if(cls){\r\n            elm.className = cls;\r\n        }else{\r\n            domUtils.removeAttributes(elm,['class']);\r\n        }\r\n    },\r\n    /**\r\n     * 给元素element添加className\r\n     * @method addClass\r\n     * @param { Node } ele 需要增加className的元素\r\n     * @param { String } classNames 需要添加的className， 多个className之间以空格分割\r\n     * @remind 相同的类名不会被重复添加\r\n     * @example\r\n     * ```html\r\n     * <span id=\"test\" class=\"cls1 cls2\"></span>\r\n     *\r\n     * <script>\r\n     *     var testNode = document.getElementById(\"test\");\r\n     *\r\n     *     UE.dom.domUtils.addClass( testNode, \"cls2 cls3 cls4\" );\r\n     *\r\n     *     //output: cl1 cls2 cls3 cls4\r\n     *     console.log( testNode.className );\r\n     *\r\n     * <script>\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 给元素element添加className\r\n     * @method addClass\r\n     * @param { Node } ele 需要增加className的元素\r\n     * @param { Array } classNames 需要添加的className的数组\r\n     * @remind 相同的类名不会被重复添加\r\n     * @example\r\n     * ```html\r\n     * <span id=\"test\" class=\"cls1 cls2\"></span>\r\n     *\r\n     * <script>\r\n     *     var testNode = document.getElementById(\"test\");\r\n     *\r\n     *     UE.dom.domUtils.addClass( testNode, [\"cls2\", \"cls3\", \"cls4\"] );\r\n     *\r\n     *     //output: cl1 cls2 cls3 cls4\r\n     *     console.log( testNode.className );\r\n     *\r\n     * <script>\r\n     * ```\r\n     */\r\n    addClass:function (elm, classNames) {\r\n        if(!elm)return;\r\n        classNames = utils.trim(classNames).replace(/[ ]{2,}/g,' ').split(' ');\r\n        for(var i = 0,ci,cls = elm.className;ci=classNames[i++];){\r\n            if(!new RegExp('\\\\b' + ci + '\\\\b').test(cls)){\r\n                cls += ' ' + ci;\r\n            }\r\n        }\r\n        elm.className = utils.trim(cls);\r\n    },\r\n    /**\r\n     * 判断元素element是否包含给定的样式类名className\r\n     * @method hasClass\r\n     * @param { Node } ele 需要检测的元素\r\n     * @param { String } classNames 需要检测的className， 多个className之间用空格分割\r\n     * @return { Boolean } 元素是否包含所有给定的className\r\n     * @example\r\n     * ```html\r\n     * <span id=\"test1\" class=\"cls1 cls2\"></span>\r\n     *\r\n     * <script>\r\n     *     var test1 = document.getElementById(\"test1\");\r\n     *\r\n     *     //output: false\r\n     *     console.log( UE.dom.domUtils.hasClass( test1, \"cls2 cls1 cls3\" ) );\r\n     *\r\n     *     //output: true\r\n     *     console.log( UE.dom.domUtils.hasClass( test1, \"cls2 cls1\" ) );\r\n     * </script>\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 判断元素element是否包含给定的样式类名className\r\n     * @method hasClass\r\n     * @param { Node } ele 需要检测的元素\r\n     * @param { Array } classNames 需要检测的className数组\r\n     * @return { Boolean } 元素是否包含所有给定的className\r\n     * @example\r\n     * ```html\r\n     * <span id=\"test1\" class=\"cls1 cls2\"></span>\r\n     *\r\n     * <script>\r\n     *     var test1 = document.getElementById(\"test1\");\r\n     *\r\n     *     //output: false\r\n     *     console.log( UE.dom.domUtils.hasClass( test1, [ \"cls2\", \"cls1\", \"cls3\" ] ) );\r\n     *\r\n     *     //output: true\r\n     *     console.log( UE.dom.domUtils.hasClass( test1, [ \"cls2\", \"cls1\" ]) );\r\n     * </script>\r\n     * ```\r\n     */\r\n    hasClass:function (element, className) {\r\n        if(utils.isRegExp(className)){\r\n            return className.test(element.className)\r\n        }\r\n        className = utils.trim(className).replace(/[ ]{2,}/g,' ').split(' ');\r\n        for(var i = 0,ci,cls = element.className;ci=className[i++];){\r\n            if(!new RegExp('\\\\b' + ci + '\\\\b','i').test(cls)){\r\n                return false;\r\n            }\r\n        }\r\n        return i - 1 == className.length;\r\n    },\r\n\r\n    /**\r\n     * 阻止事件默认行为\r\n     * @method preventDefault\r\n     * @param { Event } evt 需要阻止默认行为的事件对象\r\n     * @example\r\n     * ```javascript\r\n     * UE.dom.domUtils.preventDefault( evt );\r\n     * ```\r\n     */\r\n    preventDefault:function (evt) {\r\n        evt.preventDefault ? evt.preventDefault() : (evt.returnValue = false);\r\n    },\r\n    /**\r\n     * 删除元素element指定的样式\r\n     * @method removeStyle\r\n     * @param { Element } element 需要删除样式的元素\r\n     * @param { String } styleName 需要删除的样式名\r\n     * @example\r\n     * ```html\r\n     * <span id=\"test\" style=\"color: red; background: blue;\"></span>\r\n     *\r\n     * <script>\r\n     *\r\n     *     var testNode = document.getElementById(\"test\");\r\n     *\r\n     *     UE.dom.domUtils.removeStyle( testNode, 'color' );\r\n     *\r\n     *     //output: background: blue;\r\n     *     console.log( testNode.style.cssText );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n    removeStyle:function (element, name) {\r\n        if(browser.ie ){\r\n            //针对color先单独处理一下\r\n            if(name == 'color'){\r\n                name = '(^|;)' + name;\r\n            }\r\n            element.style.cssText = element.style.cssText.replace(new RegExp(name + '[^:]*:[^;]+;?','ig'),'')\r\n        }else{\r\n            if (element.style.removeProperty) {\r\n                element.style.removeProperty (name);\r\n            }else {\r\n                element.style.removeAttribute (utils.cssStyleToDomStyle(name));\r\n            }\r\n        }\r\n\r\n\r\n        if (!element.style.cssText) {\r\n            domUtils.removeAttributes(element, ['style']);\r\n        }\r\n    },\r\n    /**\r\n     * 获取元素element的style属性的指定值\r\n     * @method getStyle\r\n     * @param { Element } element 需要获取属性值的元素\r\n     * @param { String } styleName 需要获取的style的名称\r\n     * @warning 该方法仅获取元素style属性中所标明的值\r\n     * @return { String } 该元素包含指定的style属性值\r\n     * @example\r\n     * ```html\r\n     * <div id=\"test\" style=\"color: red;\"></div>\r\n     *\r\n     * <script>\r\n     *\r\n     *      var testNode = document.getElementById( \"test\" );\r\n     *\r\n     *      //output: red\r\n     *      console.log( UE.dom.domUtils.getStyle( testNode, \"color\" ) );\r\n     *\r\n     *      //output: \"\"\r\n     *      console.log( UE.dom.domUtils.getStyle( testNode, \"background\" ) );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n    getStyle:function (element, name) {\r\n        var value = element.style[ utils.cssStyleToDomStyle(name) ];\r\n        return utils.fixColor(name, value);\r\n    },\r\n    /**\r\n     * 为元素element设置样式属性值\r\n     * @method setStyle\r\n     * @param { Element } element 需要设置样式的元素\r\n     * @param { String } styleName 样式名\r\n     * @param { String } styleValue 样式值\r\n     * @example\r\n     * ```html\r\n     * <div id=\"test\"></div>\r\n     *\r\n     * <script>\r\n     *\r\n     *      var testNode = document.getElementById( \"test\" );\r\n     *\r\n     *      //output: \"\"\r\n     *      console.log( testNode.style.color );\r\n     *\r\n     *      UE.dom.domUtils.setStyle( testNode, 'color', 'red' );\r\n     *      //output: \"red\"\r\n     *      console.log( testNode.style.color );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n    setStyle:function (element, name, value) {\r\n        element.style[utils.cssStyleToDomStyle(name)] = value;\r\n        if(!utils.trim(element.style.cssText)){\r\n            this.removeAttributes(element,'style')\r\n        }\r\n    },\r\n    /**\r\n     * 为元素element设置多个样式属性值\r\n     * @method setStyles\r\n     * @param { Element } element 需要设置样式的元素\r\n     * @param { Object } styles 样式名值对\r\n     * @example\r\n     * ```html\r\n     * <div id=\"test\"></div>\r\n     *\r\n     * <script>\r\n     *\r\n     *      var testNode = document.getElementById( \"test\" );\r\n     *\r\n     *      //output: \"\"\r\n     *      console.log( testNode.style.color );\r\n     *\r\n     *      UE.dom.domUtils.setStyles( testNode, {\r\n     *          'color': 'red'\r\n     *      } );\r\n     *      //output: \"red\"\r\n     *      console.log( testNode.style.color );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n    setStyles:function (element, styles) {\r\n        for (var name in styles) {\r\n            if (styles.hasOwnProperty(name)) {\r\n                domUtils.setStyle(element, name, styles[name]);\r\n            }\r\n        }\r\n    },\r\n    /**\r\n     * 删除_moz_dirty属性\r\n     * @private\r\n     * @method removeDirtyAttr\r\n     */\r\n    removeDirtyAttr:function (node) {\r\n        for (var i = 0, ci, nodes = node.getElementsByTagName('*'); ci = nodes[i++];) {\r\n            ci.removeAttribute('_moz_dirty');\r\n        }\r\n        node.removeAttribute('_moz_dirty');\r\n    },\r\n    /**\r\n     * 获取子节点的数量\r\n     * @method getChildCount\r\n     * @param { Element } node 需要检测的元素\r\n     * @return { Number } 给定的node元素的子节点数量\r\n     * @example\r\n     * ```html\r\n     * <div id=\"test\">\r\n     *      <span></span>\r\n     * </div>\r\n     *\r\n     * <script>\r\n     *\r\n     *     //output: 3\r\n     *     console.log( UE.dom.domUtils.getChildCount( document.getElementById(\"test\") ) );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 根据给定的过滤规则， 获取符合条件的子节点的数量\r\n     * @method getChildCount\r\n     * @param { Element } node 需要检测的元素\r\n     * @param { Function } fn 过滤器， 要求对符合条件的子节点返回true， 反之则要求返回false\r\n     * @return { Number } 符合过滤条件的node元素的子节点数量\r\n     * @example\r\n     * ```html\r\n     * <div id=\"test\">\r\n     *      <span></span>\r\n     * </div>\r\n     *\r\n     * <script>\r\n     *\r\n     *     //output: 1\r\n     *     console.log( UE.dom.domUtils.getChildCount( document.getElementById(\"test\"), function ( node ) {\r\n     *\r\n     *         return node.nodeType === 1;\r\n     *\r\n     *     } ) );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n    getChildCount:function (node, fn) {\r\n        var count = 0, first = node.firstChild;\r\n        fn = fn || function () {\r\n            return 1;\r\n        };\r\n        while (first) {\r\n            if (fn(first)) {\r\n                count++;\r\n            }\r\n            first = first.nextSibling;\r\n        }\r\n        return count;\r\n    },\r\n\r\n    /**\r\n     * 判断给定节点是否为空节点\r\n     * @method isEmptyNode\r\n     * @param { Node } node 需要检测的节点对象\r\n     * @return { Boolean } 节点是否为空\r\n     * @example\r\n     * ```javascript\r\n     * UE.dom.domUtils.isEmptyNode( document.body );\r\n     * ```\r\n     */\r\n    isEmptyNode:function (node) {\r\n        return !node.firstChild || domUtils.getChildCount(node, function (node) {\r\n            return  !domUtils.isBr(node) && !domUtils.isBookmarkNode(node) && !domUtils.isWhitespace(node)\r\n        }) == 0\r\n    },\r\n    clearSelectedArr:function (nodes) {\r\n        var node;\r\n        while (node = nodes.pop()) {\r\n            domUtils.removeAttributes(node, ['class']);\r\n        }\r\n    },\r\n    /**\r\n     * 将显示区域滚动到指定节点的位置\r\n     * @method scrollToView\r\n     * @param    {Node}   node    节点\r\n     * @param    {window}   win      window对象\r\n     * @param    {Number}    offsetTop    距离上方的偏移量\r\n     */\r\n    scrollToView:function (node, win, offsetTop) {\r\n        var getViewPaneSize = function () {\r\n                var doc = win.document,\r\n                    mode = doc.compatMode == 'CSS1Compat';\r\n                return {\r\n                    width:( mode ? doc.documentElement.clientWidth : doc.body.clientWidth ) || 0,\r\n                    height:( mode ? doc.documentElement.clientHeight : doc.body.clientHeight ) || 0\r\n                };\r\n            },\r\n            getScrollPosition = function (win) {\r\n                if ('pageXOffset' in win) {\r\n                    return {\r\n                        x:win.pageXOffset || 0,\r\n                        y:win.pageYOffset || 0\r\n                    };\r\n                }\r\n                else {\r\n                    var doc = win.document;\r\n                    return {\r\n                        x:doc.documentElement.scrollLeft || doc.body.scrollLeft || 0,\r\n                        y:doc.documentElement.scrollTop || doc.body.scrollTop || 0\r\n                    };\r\n                }\r\n            };\r\n        var winHeight = getViewPaneSize().height, offset = winHeight * -1 + offsetTop;\r\n        offset += (node.offsetHeight || 0);\r\n        var elementPosition = domUtils.getXY(node);\r\n        offset += elementPosition.y;\r\n        var currentScroll = getScrollPosition(win).y;\r\n        // offset += 50;\r\n        if (offset > currentScroll || offset < currentScroll - winHeight) {\r\n            win.scrollTo(0, offset + (offset < 0 ? -20 : 20));\r\n        }\r\n    },\r\n    /**\r\n     * 判断给定节点是否为br\r\n     * @method isBr\r\n     * @param { Node } node 需要判断的节点对象\r\n     * @return { Boolean } 给定的节点是否是br节点\r\n     */\r\n    isBr:function (node) {\r\n        return node.nodeType == 1 && node.tagName == 'BR';\r\n    },\r\n    /**\r\n     * 判断给定的节点是否是一个“填充”节点\r\n     * @private\r\n     * @method isFillChar\r\n     * @param { Node } node 需要判断的节点\r\n     * @param { Boolean } isInStart 是否从节点内容的开始位置匹配\r\n     * @returns { Boolean } 节点是否是填充节点\r\n     */\r\n    isFillChar:function (node,isInStart) {\r\n        if(node.nodeType != 3)\r\n            return false;\r\n        var text = node.nodeValue;\r\n        if(isInStart){\r\n            return new RegExp('^' + domUtils.fillChar).test(text)\r\n        }\r\n        return !text.replace(new RegExp(domUtils.fillChar,'g'), '').length\r\n    },\r\n    isStartInblock:function (range) {\r\n        var tmpRange = range.cloneRange(),\r\n            flag = 0,\r\n            start = tmpRange.startContainer,\r\n            tmp;\r\n        if(start.nodeType == 1 && start.childNodes[tmpRange.startOffset]){\r\n            start = start.childNodes[tmpRange.startOffset];\r\n            var pre = start.previousSibling;\r\n            while(pre && domUtils.isFillChar(pre)){\r\n                start = pre;\r\n                pre = pre.previousSibling;\r\n            }\r\n        }\r\n        if(this.isFillChar(start,true) && tmpRange.startOffset == 1){\r\n            tmpRange.setStartBefore(start);\r\n            start = tmpRange.startContainer;\r\n        }\r\n\r\n        while (start && domUtils.isFillChar(start)) {\r\n            tmp = start;\r\n            start = start.previousSibling\r\n        }\r\n        if (tmp) {\r\n            tmpRange.setStartBefore(tmp);\r\n            start = tmpRange.startContainer;\r\n        }\r\n        if (start.nodeType == 1 && domUtils.isEmptyNode(start) && tmpRange.startOffset == 1) {\r\n            tmpRange.setStart(start, 0).collapse(true);\r\n        }\r\n        while (!tmpRange.startOffset) {\r\n            start = tmpRange.startContainer;\r\n            if (domUtils.isBlockElm(start) || domUtils.isBody(start)) {\r\n                flag = 1;\r\n                break;\r\n            }\r\n            var pre = tmpRange.startContainer.previousSibling,\r\n                tmpNode;\r\n            if (!pre) {\r\n                tmpRange.setStartBefore(tmpRange.startContainer);\r\n            } else {\r\n                while (pre && domUtils.isFillChar(pre)) {\r\n                    tmpNode = pre;\r\n                    pre = pre.previousSibling;\r\n                }\r\n                if (tmpNode) {\r\n                    tmpRange.setStartBefore(tmpNode);\r\n                } else {\r\n                    tmpRange.setStartBefore(tmpRange.startContainer);\r\n                }\r\n            }\r\n        }\r\n        return flag && !domUtils.isBody(tmpRange.startContainer) ? 1 : 0;\r\n    },\r\n\r\n    /**\r\n     * 判断给定的元素是否是一个空元素\r\n     * @method isEmptyBlock\r\n     * @param { Element } node 需要判断的元素\r\n     * @return { Boolean } 是否是空元素\r\n     * @example\r\n     * ```html\r\n     * <div id=\"test\"></div>\r\n     *\r\n     * <script>\r\n     *     //output: true\r\n     *     console.log( UE.dom.domUtils.isEmptyBlock( document.getElementById(\"test\") ) );\r\n     * </script>\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 根据指定的判断规则判断给定的元素是否是一个空元素\r\n     * @method isEmptyBlock\r\n     * @param { Element } node 需要判断的元素\r\n     * @param { RegExp } reg 对内容执行判断的正则表达式对象\r\n     * @return { Boolean } 是否是空元素\r\n     */\r\n    isEmptyBlock:function (node,reg) {\r\n        // HaoChuan9421\r\n        if(!node){\r\n            return;\r\n        }\r\n        if(node.nodeType != 1)\r\n            return 0;\r\n        reg = reg || new RegExp('[ \\xa0\\t\\r\\n' + domUtils.fillChar + ']', 'g');\r\n\r\n        if (node[browser.ie ? 'innerText' : 'textContent'].replace(reg, '').length > 0) {\r\n            return 0;\r\n        }\r\n        for (var n in dtd.$isNotEmpty) {\r\n            if (node.getElementsByTagName(n).length) {\r\n                return 0;\r\n            }\r\n        }\r\n        return 1;\r\n    },\r\n\r\n    /**\r\n     * 移动元素使得该元素的位置移动指定的偏移量的距离\r\n     * @method setViewportOffset\r\n     * @param { Element } element 需要设置偏移量的元素\r\n     * @param { Object } offset 偏移量， 形如{ left: 100, top: 50 }的一个键值对， 表示该元素将在\r\n     *                                  现有的位置上向水平方向偏移offset.left的距离， 在竖直方向上偏移\r\n     *                                  offset.top的距离\r\n     * @example\r\n     * ```html\r\n     * <div id=\"test\" style=\"top: 100px; left: 50px; position: absolute;\"></div>\r\n     *\r\n     * <script>\r\n     *\r\n     *     var testNode = document.getElementById(\"test\");\r\n     *\r\n     *     UE.dom.domUtils.setViewportOffset( testNode, {\r\n     *         left: 200,\r\n     *         top: 50\r\n     *     } );\r\n     *\r\n     *     //output: top: 300px; left: 100px; position: absolute;\r\n     *     console.log( testNode.style.cssText );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n    setViewportOffset:function (element, offset) {\r\n        var left = parseInt(element.style.left) | 0;\r\n        var top = parseInt(element.style.top) | 0;\r\n        var rect = element.getBoundingClientRect();\r\n        var offsetLeft = offset.left - rect.left;\r\n        var offsetTop = offset.top - rect.top;\r\n        if (offsetLeft) {\r\n            element.style.left = left + offsetLeft + 'px';\r\n        }\r\n        if (offsetTop) {\r\n            element.style.top = top + offsetTop + 'px';\r\n        }\r\n    },\r\n\r\n    /**\r\n     * 用“填充字符”填充节点\r\n     * @method fillNode\r\n     * @private\r\n     * @param { DomDocument } doc 填充的节点所在的docment对象\r\n     * @param { Node } node 需要填充的节点对象\r\n     * @example\r\n     * ```html\r\n     * <div id=\"test\"></div>\r\n     *\r\n     * <script>\r\n     *     var testNode = document.getElementById(\"test\");\r\n     *\r\n     *     //output: 0\r\n     *     console.log( testNode.childNodes.length );\r\n     *\r\n     *     UE.dom.domUtils.fillNode( document, testNode );\r\n     *\r\n     *     //output: 1\r\n     *     console.log( testNode.childNodes.length );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n    fillNode:function (doc, node) {\r\n        var tmpNode = browser.ie ? doc.createTextNode(domUtils.fillChar) : doc.createElement('br');\r\n        node.innerHTML = '';\r\n        node.appendChild(tmpNode);\r\n    },\r\n\r\n    /**\r\n     * 把节点src的所有子节点追加到另一个节点tag上去\r\n     * @method moveChild\r\n     * @param { Node } src 源节点， 该节点下的所有子节点将被移除\r\n     * @param { Node } tag 目标节点， 从源节点移除的子节点将被追加到该节点下\r\n     * @example\r\n     * ```html\r\n     * <div id=\"test1\">\r\n     *      <span></span>\r\n     * </div>\r\n     * <div id=\"test2\">\r\n     *     <div></div>\r\n     * </div>\r\n     *\r\n     * <script>\r\n     *\r\n     *     var test1 = document.getElementById(\"test1\"),\r\n     *         test2 = document.getElementById(\"test2\");\r\n     *\r\n     *     UE.dom.domUtils.moveChild( test1, test2 );\r\n     *\r\n     *     //output: \"\"（空字符串）\r\n     *     console.log( test1.innerHTML );\r\n     *\r\n     *     //output: \"<div></div><span></span>\"\r\n     *     console.log( test2.innerHTML );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 把节点src的所有子节点移动到另一个节点tag上去, 可以通过dir参数控制附加的行为是“追加”还是“插入顶部”\r\n     * @method moveChild\r\n     * @param { Node } src 源节点， 该节点下的所有子节点将被移除\r\n     * @param { Node } tag 目标节点， 从源节点移除的子节点将被附加到该节点下\r\n     * @param { Boolean } dir 附加方式， 如果为true， 则附加进去的节点将被放到目标节点的顶部， 反之，则放到末尾\r\n     * @example\r\n     * ```html\r\n     * <div id=\"test1\">\r\n     *      <span></span>\r\n     * </div>\r\n     * <div id=\"test2\">\r\n     *     <div></div>\r\n     * </div>\r\n     *\r\n     * <script>\r\n     *\r\n     *     var test1 = document.getElementById(\"test1\"),\r\n     *         test2 = document.getElementById(\"test2\");\r\n     *\r\n     *     UE.dom.domUtils.moveChild( test1, test2, true );\r\n     *\r\n     *     //output: \"\"（空字符串）\r\n     *     console.log( test1.innerHTML );\r\n     *\r\n     *     //output: \"<span></span><div></div>\"\r\n     *     console.log( test2.innerHTML );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n    moveChild:function (src, tag, dir) {\r\n        while (src.firstChild) {\r\n            if (dir && tag.firstChild) {\r\n                tag.insertBefore(src.lastChild, tag.firstChild);\r\n            } else {\r\n                tag.appendChild(src.firstChild);\r\n            }\r\n        }\r\n    },\r\n\r\n    /**\r\n     * 判断节点的标签上是否不存在任何属性\r\n     * @method hasNoAttributes\r\n     * @private\r\n     * @param { Node } node 需要检测的节点对象\r\n     * @return { Boolean } 节点是否不包含任何属性\r\n     * @example\r\n     * ```html\r\n     * <div id=\"test\"><span>xxxx</span></div>\r\n     *\r\n     * <script>\r\n     *\r\n     *     //output: false\r\n     *     console.log( UE.dom.domUtils.hasNoAttributes( document.getElementById(\"test\") ) );\r\n     *\r\n     *     //output: true\r\n     *     console.log( UE.dom.domUtils.hasNoAttributes( document.getElementById(\"test\").firstChild ) );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n    hasNoAttributes:function (node) {\r\n        return browser.ie ? /^<\\w+\\s*?>/.test(node.outerHTML) : node.attributes.length == 0;\r\n    },\r\n\r\n    /**\r\n     * 检测节点是否是UEditor所使用的辅助节点\r\n     * @method isCustomeNode\r\n     * @private\r\n     * @param { Node } node 需要检测的节点\r\n     * @remind 辅助节点是指编辑器要完成工作临时添加的节点， 在输出的时候将会从编辑器内移除， 不会影响最终的结果。\r\n     * @return { Boolean } 给定的节点是否是一个辅助节点\r\n     */\r\n    isCustomeNode:function (node) {\r\n        return node.nodeType == 1 && node.getAttribute('_ue_custom_node_');\r\n    },\r\n\r\n    /**\r\n     * 检测节点的标签是否是给定的标签\r\n     * @method isTagNode\r\n     * @param { Node } node 需要检测的节点对象\r\n     * @param { String } tagName 标签\r\n     * @return { Boolean } 节点的标签是否是给定的标签\r\n     * @example\r\n     * ```html\r\n     * <div id=\"test\"></div>\r\n     *\r\n     * <script>\r\n     *\r\n     *     //output: true\r\n     *     console.log( UE.dom.domUtils.isTagNode( document.getElementById(\"test\"), \"div\" ) );\r\n     *\r\n     * </script>\r\n     * ```\r\n     */\r\n    isTagNode:function (node, tagNames) {\r\n        return node.nodeType == 1 && new RegExp('\\\\b' + node.tagName + '\\\\b','i').test(tagNames)\r\n    },\r\n\r\n    /**\r\n     * 给定一个节点数组，在通过指定的过滤器过滤后， 获取其中满足过滤条件的第一个节点\r\n     * @method filterNodeList\r\n     * @param { Array } nodeList 需要过滤的节点数组\r\n     * @param { Function } fn 过滤器， 对符合条件的节点， 执行结果返回true， 反之则返回false\r\n     * @return { Node | NULL } 如果找到符合过滤条件的节点， 则返回该节点， 否则返回NULL\r\n     * @example\r\n     * ```javascript\r\n     * var divNodes = document.getElementsByTagName(\"div\");\r\n     * divNodes = [].slice.call( divNodes, 0 );\r\n     *\r\n     * //output: null\r\n     * console.log( UE.dom.domUtils.filterNodeList( divNodes, function ( node ) {\r\n     *     return node.tagName.toLowerCase() !== 'div';\r\n     * } ) );\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 给定一个节点数组nodeList和一组标签名tagNames， 获取其中能够匹配标签名的节点集合中的第一个节点\r\n     * @method filterNodeList\r\n     * @param { Array } nodeList 需要过滤的节点数组\r\n     * @param { String } tagNames 需要匹配的标签名， 多个标签名之间用空格分割\r\n     * @return { Node | NULL } 如果找到标签名匹配的节点， 则返回该节点， 否则返回NULL\r\n     * @example\r\n     * ```javascript\r\n     * var divNodes = document.getElementsByTagName(\"div\");\r\n     * divNodes = [].slice.call( divNodes, 0 );\r\n     *\r\n     * //output: null\r\n     * console.log( UE.dom.domUtils.filterNodeList( divNodes, 'a span' ) );\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 给定一个节点数组，在通过指定的过滤器过滤后， 如果参数forAll为true， 则会返回所有满足过滤\r\n     * 条件的节点集合， 否则， 返回满足条件的节点集合中的第一个节点\r\n     * @method filterNodeList\r\n     * @param { Array } nodeList 需要过滤的节点数组\r\n     * @param { Function } fn 过滤器， 对符合条件的节点， 执行结果返回true， 反之则返回false\r\n     * @param { Boolean } forAll 是否返回整个节点数组, 如果该参数为false， 则返回节点集合中的第一个节点\r\n     * @return { Array | Node | NULL } 如果找到符合过滤条件的节点， 则根据参数forAll的值决定返回满足\r\n     *                                      过滤条件的节点数组或第一个节点， 否则返回NULL\r\n     * @example\r\n     * ```javascript\r\n     * var divNodes = document.getElementsByTagName(\"div\");\r\n     * divNodes = [].slice.call( divNodes, 0 );\r\n     *\r\n     * //output: 3（假定有3个div）\r\n     * console.log( divNodes.length );\r\n     *\r\n     * var nodes = UE.dom.domUtils.filterNodeList( divNodes, function ( node ) {\r\n     *     return node.tagName.toLowerCase() === 'div';\r\n     * }, true );\r\n     *\r\n     * //output: 3\r\n     * console.log( nodes.length );\r\n     *\r\n     * var node = UE.dom.domUtils.filterNodeList( divNodes, function ( node ) {\r\n     *     return node.tagName.toLowerCase() === 'div';\r\n     * }, false );\r\n     *\r\n     * //output: div\r\n     * console.log( node.nodeName );\r\n     * ```\r\n     */\r\n    filterNodeList : function(nodelist,filter,forAll){\r\n        var results = [];\r\n        if(!utils .isFunction(filter)){\r\n            var str = filter;\r\n            filter = function(n){\r\n                return utils.indexOf(utils.isArray(str) ? str:str.split(' '), n.tagName.toLowerCase()) != -1\r\n            };\r\n        }\r\n        utils.each(nodelist,function(n){\r\n            filter(n) && results.push(n)\r\n        });\r\n        return results.length  == 0 ? null : results.length == 1 || !forAll ? results[0] : results\r\n    },\r\n\r\n    /**\r\n     * 查询给定的range选区是否在给定的node节点内，且在该节点的最末尾\r\n     * @method isInNodeEndBoundary\r\n     * @param { UE.dom.Range } rng 需要判断的range对象， 该对象的startContainer不能为NULL\r\n     * @param node 需要检测的节点对象\r\n     * @return { Number } 如果给定的选取range对象是在node内部的最末端， 则返回1, 否则返回0\r\n     */\r\n    isInNodeEndBoundary : function (rng,node){\r\n        var start = rng.startContainer;\r\n        if(start.nodeType == 3 && rng.startOffset != start.nodeValue.length){\r\n            return 0;\r\n        }\r\n        if(start.nodeType == 1 && rng.startOffset != start.childNodes.length){\r\n            return 0;\r\n        }\r\n        while(start !== node){\r\n            if(start.nextSibling){\r\n                return 0\r\n            };\r\n            start = start.parentNode;\r\n        }\r\n        return 1;\r\n    },\r\n    isBoundaryNode : function (node,dir){\r\n        var tmp;\r\n        while(!domUtils.isBody(node)){\r\n            tmp = node;\r\n            node = node.parentNode;\r\n            if(tmp !== node[dir]){\r\n                return false;\r\n            }\r\n        }\r\n        return true;\r\n    },\r\n    fillHtml :  browser.ie11below ? '&nbsp;' : '<br/>'\r\n};\r\nvar fillCharReg = new RegExp(domUtils.fillChar, 'g');\r\n\r\n// core/Range.js\r\n/**\r\n * Range封装\r\n * @file\r\n * @module UE.dom\r\n * @class Range\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * dom操作封装\r\n * @unfile\r\n * @module UE.dom\r\n */\r\n\r\n/**\r\n * Range实现类，本类是UEditor底层核心类，封装不同浏览器之间的Range操作。\r\n * @unfile\r\n * @module UE.dom\r\n * @class Range\r\n */\r\n\r\n\r\n(function () {\r\n    var guid = 0,\r\n        fillChar = domUtils.fillChar,\r\n        fillData;\r\n\r\n    /**\r\n     * 更新range的collapse状态\r\n     * @param  {Range}   range    range对象\r\n     */\r\n    function updateCollapse(range) {\r\n        range.collapsed =\r\n            range.startContainer && range.endContainer &&\r\n                range.startContainer === range.endContainer &&\r\n                range.startOffset == range.endOffset;\r\n    }\r\n\r\n    function selectOneNode(rng){\r\n        return !rng.collapsed && rng.startContainer.nodeType == 1 && rng.startContainer === rng.endContainer && rng.endOffset - rng.startOffset == 1\r\n    }\r\n    function setEndPoint(toStart, node, offset, range) {\r\n        //如果node是自闭合标签要处理\r\n        if (node.nodeType == 1 && (dtd.$empty[node.tagName] || dtd.$nonChild[node.tagName])) {\r\n            offset = domUtils.getNodeIndex(node) + (toStart ? 0 : 1);\r\n            node = node.parentNode;\r\n        }\r\n        if (toStart) {\r\n            range.startContainer = node;\r\n            range.startOffset = offset;\r\n            if (!range.endContainer) {\r\n                range.collapse(true);\r\n            }\r\n        } else {\r\n            range.endContainer = node;\r\n            range.endOffset = offset;\r\n            if (!range.startContainer) {\r\n                range.collapse(false);\r\n            }\r\n        }\r\n        updateCollapse(range);\r\n        return range;\r\n    }\r\n\r\n    function execContentsAction(range, action) {\r\n        //调整边界\r\n        //range.includeBookmark();\r\n        var start = range.startContainer,\r\n            end = range.endContainer,\r\n            startOffset = range.startOffset,\r\n            endOffset = range.endOffset,\r\n            doc = range.document,\r\n            frag = doc.createDocumentFragment(),\r\n            tmpStart, tmpEnd;\r\n        if (start.nodeType == 1) {\r\n            start = start.childNodes[startOffset] || (tmpStart = start.appendChild(doc.createTextNode('')));\r\n        }\r\n        if (end.nodeType == 1) {\r\n            end = end.childNodes[endOffset] || (tmpEnd = end.appendChild(doc.createTextNode('')));\r\n        }\r\n        if (start === end && start.nodeType == 3) {\r\n            frag.appendChild(doc.createTextNode(start.substringData(startOffset, endOffset - startOffset)));\r\n            //is not clone\r\n            if (action) {\r\n                start.deleteData(startOffset, endOffset - startOffset);\r\n                range.collapse(true);\r\n            }\r\n            return frag;\r\n        }\r\n        var current, currentLevel, clone = frag,\r\n            startParents = domUtils.findParents(start, true), endParents = domUtils.findParents(end, true);\r\n        for (var i = 0; startParents[i] == endParents[i];) {\r\n            i++;\r\n        }\r\n        for (var j = i, si; si = startParents[j]; j++) {\r\n            current = si.nextSibling;\r\n            if (si == start) {\r\n                if (!tmpStart) {\r\n                    if (range.startContainer.nodeType == 3) {\r\n                        clone.appendChild(doc.createTextNode(start.nodeValue.slice(startOffset)));\r\n                        //is not clone\r\n                        if (action) {\r\n                            start.deleteData(startOffset, start.nodeValue.length - startOffset);\r\n                        }\r\n                    } else {\r\n                        clone.appendChild(!action ? start.cloneNode(true) : start);\r\n                    }\r\n                }\r\n            } else {\r\n                currentLevel = si.cloneNode(false);\r\n                clone.appendChild(currentLevel);\r\n            }\r\n            while (current) {\r\n                if (current === end || current === endParents[j]) {\r\n                    break;\r\n                }\r\n                si = current.nextSibling;\r\n                clone.appendChild(!action ? current.cloneNode(true) : current);\r\n                current = si;\r\n            }\r\n            clone = currentLevel;\r\n        }\r\n        clone = frag;\r\n        if (!startParents[i]) {\r\n            clone.appendChild(startParents[i - 1].cloneNode(false));\r\n            clone = clone.firstChild;\r\n        }\r\n        for (var j = i, ei; ei = endParents[j]; j++) {\r\n            current = ei.previousSibling;\r\n            if (ei == end) {\r\n                if (!tmpEnd && range.endContainer.nodeType == 3) {\r\n                    clone.appendChild(doc.createTextNode(end.substringData(0, endOffset)));\r\n                    //is not clone\r\n                    if (action) {\r\n                        end.deleteData(0, endOffset);\r\n                    }\r\n                }\r\n            } else {\r\n                currentLevel = ei.cloneNode(false);\r\n                clone.appendChild(currentLevel);\r\n            }\r\n            //如果两端同级，右边第一次已经被开始做了\r\n            if (j != i || !startParents[i]) {\r\n                while (current) {\r\n                    if (current === start) {\r\n                        break;\r\n                    }\r\n                    ei = current.previousSibling;\r\n                    clone.insertBefore(!action ? current.cloneNode(true) : current, clone.firstChild);\r\n                    current = ei;\r\n                }\r\n            }\r\n            clone = currentLevel;\r\n        }\r\n        if (action) {\r\n            range.setStartBefore(!endParents[i] ? endParents[i - 1] : !startParents[i] ? startParents[i - 1] : endParents[i]).collapse(true);\r\n        }\r\n        tmpStart && domUtils.remove(tmpStart);\r\n        tmpEnd && domUtils.remove(tmpEnd);\r\n        return frag;\r\n    }\r\n\r\n    /**\r\n     * 创建一个跟document绑定的空的Range实例\r\n     * @constructor\r\n     * @param { Document } document 新建的选区所属的文档对象\r\n     */\r\n\r\n    /**\r\n     * @property { Node } startContainer 当前Range的开始边界的容器节点, 可以是一个元素节点或者是文本节点\r\n     */\r\n\r\n    /**\r\n     * @property { Node } startOffset 当前Range的开始边界容器节点的偏移量, 如果是元素节点，\r\n     *                              该值就是childNodes中的第几个节点， 如果是文本节点就是文本内容的第几个字符\r\n     */\r\n\r\n    /**\r\n     * @property { Node } endContainer 当前Range的结束边界的容器节点, 可以是一个元素节点或者是文本节点\r\n     */\r\n\r\n    /**\r\n     * @property { Node } endOffset 当前Range的结束边界容器节点的偏移量, 如果是元素节点，\r\n     *                              该值就是childNodes中的第几个节点， 如果是文本节点就是文本内容的第几个字符\r\n     */\r\n\r\n    /**\r\n     * @property { Boolean } collapsed 当前Range是否闭合\r\n     * @default true\r\n     * @remind Range是闭合的时候， startContainer === endContainer && startOffset === endOffset\r\n     */\r\n\r\n    /**\r\n     * @property { Document } document 当前Range所属的Document对象\r\n     * @remind 不同range的的document属性可以是不同的\r\n     */\r\n    var Range = dom.Range = function (document) {\r\n        var me = this;\r\n        me.startContainer =\r\n            me.startOffset =\r\n                me.endContainer =\r\n                    me.endOffset = null;\r\n        me.document = document;\r\n        me.collapsed = true;\r\n    };\r\n\r\n    /**\r\n     * 删除fillData\r\n     * @param doc\r\n     * @param excludeNode\r\n     */\r\n    function removeFillData(doc, excludeNode) {\r\n        try {\r\n            if (fillData && domUtils.inDoc(fillData, doc)) {\r\n                if (!fillData.nodeValue.replace(fillCharReg, '').length) {\r\n                    var tmpNode = fillData.parentNode;\r\n                    domUtils.remove(fillData);\r\n                    while (tmpNode && domUtils.isEmptyInlineElement(tmpNode) &&\r\n                        //safari的contains有bug\r\n                        (browser.safari ? !(domUtils.getPosition(tmpNode,excludeNode) & domUtils.POSITION_CONTAINS) : !tmpNode.contains(excludeNode))\r\n                        ) {\r\n                        fillData = tmpNode.parentNode;\r\n                        domUtils.remove(tmpNode);\r\n                        tmpNode = fillData;\r\n                    }\r\n                } else {\r\n                    fillData.nodeValue = fillData.nodeValue.replace(fillCharReg, '');\r\n                }\r\n            }\r\n        } catch (e) {\r\n        }\r\n    }\r\n\r\n    /**\r\n     * @param node\r\n     * @param dir\r\n     */\r\n    function mergeSibling(node, dir) {\r\n        var tmpNode;\r\n        node = node[dir];\r\n        while (node && domUtils.isFillChar(node)) {\r\n            tmpNode = node[dir];\r\n            domUtils.remove(node);\r\n            node = tmpNode;\r\n        }\r\n    }\r\n\r\n    Range.prototype = {\r\n\r\n        /**\r\n         * 克隆选区的内容到一个DocumentFragment里\r\n         * @method cloneContents\r\n         * @return { DocumentFragment | NULL } 如果选区是闭合的将返回null， 否则， 返回包含所clone内容的DocumentFragment元素\r\n         * @example\r\n         * ```html\r\n         * <body>\r\n         *      <!-- 中括号表示选区 -->\r\n         *      <b>x<i>x[x</i>xx]x</b>\r\n         *\r\n         *      <script>\r\n         *          //range是已选中的选区\r\n         *          var fragment = range.cloneContents(),\r\n         *              node = document.createElement(\"div\");\r\n         *\r\n         *          node.appendChild( fragment );\r\n         *\r\n         *          //output: <i>x</i>xx\r\n         *          console.log( node.innerHTML );\r\n         *\r\n         *      </script>\r\n         * </body>\r\n         * ```\r\n         */\r\n        cloneContents:function () {\r\n            return this.collapsed ? null : execContentsAction(this, 0);\r\n        },\r\n\r\n        /**\r\n         * 删除当前选区范围中的所有内容\r\n         * @method deleteContents\r\n         * @remind 执行完该操作后， 当前Range对象变成了闭合状态\r\n         * @return { UE.dom.Range } 当前操作的Range对象\r\n         * @example\r\n         * ```html\r\n         * <body>\r\n         *      <!-- 中括号表示选区 -->\r\n         *      <b>x<i>x[x</i>xx]x</b>\r\n         *\r\n         *      <script>\r\n         *          //range是已选中的选区\r\n         *          range.deleteContents();\r\n         *\r\n         *          //竖线表示闭合后的选区位置\r\n         *          //output: <b>x<i>x</i>|x</b>\r\n         *          console.log( document.body.innerHTML );\r\n         *\r\n         *          //此时， range的各项属性为\r\n         *          //output: B\r\n         *          console.log( range.startContainer.tagName );\r\n         *          //output: 2\r\n         *          console.log( range.startOffset );\r\n         *          //output: B\r\n         *          console.log( range.endContainer.tagName );\r\n         *          //output: 2\r\n         *          console.log( range.endOffset );\r\n         *          //output: true\r\n         *          console.log( range.collapsed );\r\n         *\r\n         *      </script>\r\n         * </body>\r\n         * ```\r\n         */\r\n        deleteContents:function () {\r\n            var txt;\r\n            if (!this.collapsed) {\r\n                execContentsAction(this, 1);\r\n            }\r\n            if (browser.webkit) {\r\n                txt = this.startContainer;\r\n                if (txt.nodeType == 3 && !txt.nodeValue.length) {\r\n                    this.setStartBefore(txt).collapse(true);\r\n                    domUtils.remove(txt);\r\n                }\r\n            }\r\n            return this;\r\n        },\r\n\r\n        /**\r\n         * 将当前选区的内容提取到一个DocumentFragment里\r\n         * @method extractContents\r\n         * @remind 执行该操作后， 选区将变成闭合状态\r\n         * @warning 执行该操作后， 原来选区所选中的内容将从dom树上剥离出来\r\n         * @return { DocumentFragment } 返回包含所提取内容的DocumentFragment对象\r\n         * @example\r\n         * ```html\r\n         * <body>\r\n         *      <!-- 中括号表示选区 -->\r\n         *      <b>x<i>x[x</i>xx]x</b>\r\n         *\r\n         *      <script>\r\n         *          //range是已选中的选区\r\n         *          var fragment = range.extractContents(),\r\n         *              node = document.createElement( \"div\" );\r\n         *\r\n         *          node.appendChild( fragment );\r\n         *\r\n         *          //竖线表示闭合后的选区位置\r\n         *\r\n         *          //output: <b>x<i>x</i>|x</b>\r\n         *          console.log( document.body.innerHTML );\r\n         *          //output: <i>x</i>xx\r\n         *          console.log( node.innerHTML );\r\n         *\r\n         *          //此时， range的各项属性为\r\n         *          //output: B\r\n         *          console.log( range.startContainer.tagName );\r\n         *          //output: 2\r\n         *          console.log( range.startOffset );\r\n         *          //output: B\r\n         *          console.log( range.endContainer.tagName );\r\n         *          //output: 2\r\n         *          console.log( range.endOffset );\r\n         *          //output: true\r\n         *          console.log( range.collapsed );\r\n         *\r\n         *      </script>\r\n         * </body>\r\n         */\r\n        extractContents:function () {\r\n            return this.collapsed ? null : execContentsAction(this, 2);\r\n        },\r\n\r\n        /**\r\n         * 设置Range的开始容器节点和偏移量\r\n         * @method  setStart\r\n         * @remind 如果给定的节点是元素节点，那么offset指的是其子元素中索引为offset的元素，\r\n         *          如果是文本节点，那么offset指的是其文本内容的第offset个字符\r\n         * @remind 如果提供的容器节点是一个不能包含子元素的节点， 则该选区的开始容器将被设置\r\n         *          为该节点的父节点， 此时， 其距离开始容器的偏移量也变成了该节点在其父节点\r\n         *          中的索引\r\n         * @param { Node } node 将被设为当前选区开始边界容器的节点对象\r\n         * @param { int } offset 选区的开始位置偏移量\r\n         * @return { UE.dom.Range } 当前range对象\r\n         * @example\r\n         * ```html\r\n         * <!-- 选区 -->\r\n         * <b>xxx<i>x<span>xx</span>xx<em>xx</em>xxx</i>[xxx]</b>\r\n         *\r\n         * <script>\r\n         *\r\n         *     //执行操作\r\n         *     range.setStart( document.getElementsByTagName(\"i\")[0], 1 );\r\n         *\r\n         *     //此时， 选区变成了\r\n         *     //<b>xxx<i>x[<span>xx</span>xx<em>xx</em>xxx</i>xxx]</b>\r\n         *\r\n         * </script>\r\n         * ```\r\n         * @example\r\n         * ```html\r\n         * <!-- 选区 -->\r\n         * <b>xxx<img>[xx]x</b>\r\n         *\r\n         * <script>\r\n         *\r\n         *     //执行操作\r\n         *     range.setStart( document.getElementsByTagName(\"img\")[0], 3 );\r\n         *\r\n         *     //此时， 选区变成了\r\n         *     //<b>xxx[<img>xx]x</b>\r\n         *\r\n         * </script>\r\n         * ```\r\n         */\r\n        setStart:function (node, offset) {\r\n            return setEndPoint(true, node, offset, this);\r\n        },\r\n\r\n        /**\r\n         * 设置Range的结束容器和偏移量\r\n         * @method  setEnd\r\n         * @param { Node } node 作为当前选区结束边界容器的节点对象\r\n         * @param { int } offset 结束边界的偏移量\r\n         * @see UE.dom.Range:setStart(Node,int)\r\n         * @return { UE.dom.Range } 当前range对象\r\n         */\r\n        setEnd:function (node, offset) {\r\n            return setEndPoint(false, node, offset, this);\r\n        },\r\n\r\n        /**\r\n         * 将Range开始位置设置到node节点之后\r\n         * @method  setStartAfter\r\n         * @remind 该操作将会把给定节点的父节点作为range的开始容器， 且偏移量是该节点在其父节点中的位置索引+1\r\n         * @param { Node } node 选区的开始边界将紧接着该节点之后\r\n         * @return { UE.dom.Range } 当前range对象\r\n         * @example\r\n         * ```html\r\n         * <!-- 选区示例 -->\r\n         * <b>xx<i>xxx</i><span>xx[x</span>xxx]</b>\r\n         *\r\n         * <script>\r\n         *\r\n         *     //执行操作\r\n         *     range.setStartAfter( document.getElementsByTagName(\"i\")[0] );\r\n         *\r\n         *     //结果选区\r\n         *     //<b>xx<i>xxx</i>[<span>xxx</span>xxx]</b>\r\n         *\r\n         * </script>\r\n         * ```\r\n         */\r\n        setStartAfter:function (node) {\r\n            return this.setStart(node.parentNode, domUtils.getNodeIndex(node) + 1);\r\n        },\r\n\r\n        /**\r\n         * 将Range开始位置设置到node节点之前\r\n         * @method  setStartBefore\r\n         * @remind 该操作将会把给定节点的父节点作为range的开始容器， 且偏移量是该节点在其父节点中的位置索引\r\n         * @param { Node } node 新的选区开始位置在该节点之前\r\n         * @see UE.dom.Range:setStartAfter(Node)\r\n         * @return { UE.dom.Range } 当前range对象\r\n         */\r\n        setStartBefore:function (node) {\r\n            return this.setStart(node.parentNode, domUtils.getNodeIndex(node));\r\n        },\r\n\r\n        /**\r\n         * 将Range结束位置设置到node节点之后\r\n         * @method  setEndAfter\r\n         * @remind 该操作将会把给定节点的父节点作为range的结束容器， 且偏移量是该节点在其父节点中的位置索引+1\r\n         * @param { Node } node 目标节点\r\n         * @see UE.dom.Range:setStartAfter(Node)\r\n         * @return { UE.dom.Range } 当前range对象\r\n         * @example\r\n         * ```html\r\n         * <!-- 选区示例 -->\r\n         * <b>[xx<i>xxx</i><span>xx]x</span>xxx</b>\r\n         *\r\n         * <script>\r\n         *\r\n         *     //执行操作\r\n         *     range.setStartAfter( document.getElementsByTagName(\"span\")[0] );\r\n         *\r\n         *     //结果选区\r\n         *     //<b>[xx<i>xxx</i><span>xxx</span>]xxx</b>\r\n         *\r\n         * </script>\r\n         * ```\r\n         */\r\n        setEndAfter:function (node) {\r\n            return this.setEnd(node.parentNode, domUtils.getNodeIndex(node) + 1);\r\n        },\r\n\r\n        /**\r\n         * 将Range结束位置设置到node节点之前\r\n         * @method  setEndBefore\r\n         * @remind 该操作将会把给定节点的父节点作为range的结束容器， 且偏移量是该节点在其父节点中的位置索引\r\n         * @param { Node } node 目标节点\r\n         * @see UE.dom.Range:setEndAfter(Node)\r\n         * @return { UE.dom.Range } 当前range对象\r\n         */\r\n        setEndBefore:function (node) {\r\n            return this.setEnd(node.parentNode, domUtils.getNodeIndex(node));\r\n        },\r\n\r\n        /**\r\n         * 设置Range的开始位置到node节点内的第一个子节点之前\r\n         * @method  setStartAtFirst\r\n         * @remind 选区的开始容器将变成给定的节点， 且偏移量为0\r\n         * @remind 如果给定的节点是元素节点， 则该节点必须是允许包含子节点的元素。\r\n         * @param { Node } node 目标节点\r\n         * @see UE.dom.Range:setStartBefore(Node)\r\n         * @return { UE.dom.Range } 当前range对象\r\n         * @example\r\n         * ```html\r\n         * <!-- 选区示例 -->\r\n         * <b>xx<i>xxx</i><span>[xx]x</span>xxx</b>\r\n         *\r\n         * <script>\r\n         *\r\n         *     //执行操作\r\n         *     range.setStartAtFirst( document.getElementsByTagName(\"i\")[0] );\r\n         *\r\n         *     //结果选区\r\n         *     //<b>xx<i>[xxx</i><span>xx]x</span>xxx</b>\r\n         *\r\n         * </script>\r\n         * ```\r\n         */\r\n        setStartAtFirst:function (node) {\r\n            return this.setStart(node, 0);\r\n        },\r\n\r\n        /**\r\n         * 设置Range的开始位置到node节点内的最后一个节点之后\r\n         * @method setStartAtLast\r\n         * @remind 选区的开始容器将变成给定的节点， 且偏移量为该节点的子节点数\r\n         * @remind 如果给定的节点是元素节点， 则该节点必须是允许包含子节点的元素。\r\n         * @param { Node } node 目标节点\r\n         * @see UE.dom.Range:setStartAtFirst(Node)\r\n         * @return { UE.dom.Range } 当前range对象\r\n         */\r\n        setStartAtLast:function (node) {\r\n            return this.setStart(node, node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length);\r\n        },\r\n\r\n        /**\r\n         * 设置Range的结束位置到node节点内的第一个节点之前\r\n         * @method  setEndAtFirst\r\n         * @param { Node } node 目标节点\r\n         * @remind 选区的结束容器将变成给定的节点， 且偏移量为0\r\n         * @remind node必须是一个元素节点， 且必须是允许包含子节点的元素。\r\n         * @see UE.dom.Range:setStartAtFirst(Node)\r\n         * @return { UE.dom.Range } 当前range对象\r\n         */\r\n        setEndAtFirst:function (node) {\r\n            return this.setEnd(node, 0);\r\n        },\r\n\r\n        /**\r\n         * 设置Range的结束位置到node节点内的最后一个节点之后\r\n         * @method  setEndAtLast\r\n         * @param { Node } node 目标节点\r\n         * @remind 选区的结束容器将变成给定的节点， 且偏移量为该节点的子节点数量\r\n         * @remind node必须是一个元素节点， 且必须是允许包含子节点的元素。\r\n         * @see UE.dom.Range:setStartAtFirst(Node)\r\n         * @return { UE.dom.Range } 当前range对象\r\n         */\r\n        setEndAtLast:function (node) {\r\n            return this.setEnd(node, node.nodeType == 3 ? node.nodeValue.length : node.childNodes.length);\r\n        },\r\n\r\n        /**\r\n         * 选中给定节点\r\n         * @method  selectNode\r\n         * @remind 此时， 选区的开始容器和结束容器都是该节点的父节点， 其startOffset是该节点在父节点中的位置索引，\r\n         *          而endOffset为startOffset+1\r\n         * @param { Node } node 需要选中的节点\r\n         * @return { UE.dom.Range } 当前range对象，此时的range仅包含当前给定的节点对象\r\n         * @example\r\n         * ```html\r\n         * <!-- 选区示例 -->\r\n         * <b>xx<i>xxx</i><span>[xx]x</span>xxx</b>\r\n         *\r\n         * <script>\r\n         *\r\n         *     //执行操作\r\n         *     range.selectNode( document.getElementsByTagName(\"i\")[0] );\r\n         *\r\n         *     //结果选区\r\n         *     //<b>xx[<i>xxx</i>]<span>xxx</span>xxx</b>\r\n         *\r\n         * </script>\r\n         * ```\r\n         */\r\n        selectNode:function (node) {\r\n            return this.setStartBefore(node).setEndAfter(node);\r\n        },\r\n\r\n        /**\r\n         * 选中给定节点内部的所有节点\r\n         * @method  selectNodeContents\r\n         * @remind 此时， 选区的开始容器和结束容器都是该节点， 其startOffset为0，\r\n         *          而endOffset是该节点的子节点数。\r\n         * @param { Node } node 目标节点， 当前range将包含该节点内的所有节点\r\n         * @return { UE.dom.Range } 当前range对象， 此时range仅包含给定节点的所有子节点\r\n         * @example\r\n         * ```html\r\n         * <!-- 选区示例 -->\r\n         * <b>xx<i>xxx</i><span>[xx]x</span>xxx</b>\r\n         *\r\n         * <script>\r\n         *\r\n         *     //执行操作\r\n         *     range.selectNode( document.getElementsByTagName(\"b\")[0] );\r\n         *\r\n         *     //结果选区\r\n         *     //<b>[xx<i>xxx</i><span>xxx</span>xxx]</b>\r\n         *\r\n         * </script>\r\n         * ```\r\n         */\r\n        selectNodeContents:function (node) {\r\n            return this.setStart(node, 0).setEndAtLast(node);\r\n        },\r\n\r\n        /**\r\n         * clone当前Range对象\r\n         * @method  cloneRange\r\n         * @remind 返回的range是一个全新的range对象， 其内部所有属性与当前被clone的range相同。\r\n         * @return { UE.dom.Range } 当前range对象的一个副本\r\n         */\r\n        cloneRange:function () {\r\n            var me = this;\r\n            return new Range(me.document).setStart(me.startContainer, me.startOffset).setEnd(me.endContainer, me.endOffset);\r\n\r\n        },\r\n\r\n        /**\r\n         * 向当前选区的结束处闭合选区\r\n         * @method  collapse\r\n         * @return { UE.dom.Range } 当前range对象\r\n         * @example\r\n         * ```html\r\n         * <!-- 选区示例 -->\r\n         * <b>xx<i>xxx</i><span>[xx]x</span>xxx</b>\r\n         *\r\n         * <script>\r\n         *\r\n         *     //执行操作\r\n         *     range.collapse();\r\n         *\r\n         *     //结果选区\r\n         *     //“|”表示选区已闭合\r\n         *     //<b>xx<i>xxx</i><span>xx|x</span>xxx</b>\r\n         *\r\n         * </script>\r\n         * ```\r\n         */\r\n\r\n        /**\r\n         * 闭合当前选区，根据给定的toStart参数项决定是向当前选区开始处闭合还是向结束处闭合，\r\n         * 如果toStart的值为true，则向开始位置闭合， 反之，向结束位置闭合。\r\n         * @method  collapse\r\n         * @param { Boolean } toStart 是否向选区开始处闭合\r\n         * @return { UE.dom.Range } 当前range对象，此时range对象处于闭合状态\r\n         * @see UE.dom.Range:collapse()\r\n         * @example\r\n         * ```html\r\n         * <!-- 选区示例 -->\r\n         * <b>xx<i>xxx</i><span>[xx]x</span>xxx</b>\r\n         *\r\n         * <script>\r\n         *\r\n         *     //执行操作\r\n         *     range.collapse( true );\r\n         *\r\n         *     //结果选区\r\n         *     //“|”表示选区已闭合\r\n         *     //<b>xx<i>xxx</i><span>|xxx</span>xxx</b>\r\n         *\r\n         * </script>\r\n         * ```\r\n         */\r\n        collapse:function (toStart) {\r\n            var me = this;\r\n            if (toStart) {\r\n                me.endContainer = me.startContainer;\r\n                me.endOffset = me.startOffset;\r\n            } else {\r\n                me.startContainer = me.endContainer;\r\n                me.startOffset = me.endOffset;\r\n            }\r\n            me.collapsed = true;\r\n            return me;\r\n        },\r\n\r\n        /**\r\n         * 调整range的开始位置和结束位置，使其\"收缩\"到最小的位置\r\n         * @method  shrinkBoundary\r\n         * @return { UE.dom.Range } 当前range对象\r\n         * @example\r\n         * ```html\r\n         * <span>xx<b>xx[</b>xxxxx]</span> => <span>xx<b>xx</b>[xxxxx]</span>\r\n         * ```\r\n         *\r\n         * @example\r\n         * ```html\r\n         * <!-- 选区示例 -->\r\n         * <b>x[xx</b><i>]xxx</i>\r\n         *\r\n         * <script>\r\n         *\r\n         *     //执行收缩\r\n         *     range.shrinkBoundary();\r\n         *\r\n         *     //结果选区\r\n         *     //<b>x[xx]</b><i>xxx</i>\r\n         * </script>\r\n         * ```\r\n         *\r\n         * @example\r\n         * ```html\r\n         * [<b><i>xxxx</i>xxxxxxx</b>] => <b><i>[xxxx</i>xxxxxxx]</b>\r\n         * ```\r\n         */\r\n\r\n        /**\r\n         * 调整range的开始位置和结束位置，使其\"收缩\"到最小的位置，\r\n         * 如果ignoreEnd的值为true，则忽略对结束位置的调整\r\n         * @method  shrinkBoundary\r\n         * @param { Boolean } ignoreEnd 是否忽略对结束位置的调整\r\n         * @return { UE.dom.Range } 当前range对象\r\n         * @see UE.dom.domUtils.Range:shrinkBoundary()\r\n         */\r\n        shrinkBoundary:function (ignoreEnd) {\r\n            var me = this, child,\r\n                collapsed = me.collapsed;\r\n            function check(node){\r\n                return node.nodeType == 1 && !domUtils.isBookmarkNode(node) && !dtd.$empty[node.tagName] && !dtd.$nonChild[node.tagName]\r\n            }\r\n            while (me.startContainer.nodeType == 1 //是element\r\n                && (child = me.startContainer.childNodes[me.startOffset]) //子节点也是element\r\n                && check(child)) {\r\n                me.setStart(child, 0);\r\n            }\r\n            if (collapsed) {\r\n                return me.collapse(true);\r\n            }\r\n            if (!ignoreEnd) {\r\n                while (me.endContainer.nodeType == 1//是element\r\n                    && me.endOffset > 0 //如果是空元素就退出 endOffset=0那么endOffst-1为负值，childNodes[endOffset]报错\r\n                    && (child = me.endContainer.childNodes[me.endOffset - 1]) //子节点也是element\r\n                    && check(child)) {\r\n                    me.setEnd(child, child.childNodes.length);\r\n                }\r\n            }\r\n            return me;\r\n        },\r\n\r\n        /**\r\n         * 获取离当前选区内包含的所有节点最近的公共祖先节点，\r\n         * @method  getCommonAncestor\r\n         * @remind 返回的公共祖先节点一定不是range自身的容器节点， 但有可能是一个文本节点\r\n         * @return { Node } 当前range对象内所有节点的公共祖先节点\r\n         * @example\r\n         * ```html\r\n         * //选区示例\r\n         * <span>xxx<b>x[x<em>xx]x</em>xxx</b>xx</span>\r\n         * <script>\r\n         *\r\n         *     var node = range.getCommonAncestor();\r\n         *\r\n         *     //公共祖先节点是： b节点\r\n         *     //输出： B\r\n         *     console.log(node.tagName);\r\n         *\r\n         * </script>\r\n         * ```\r\n         */\r\n\r\n        /**\r\n         * 获取当前选区所包含的所有节点的公共祖先节点， 可以根据给定的参数 includeSelf 决定获取到\r\n         * 的公共祖先节点是否可以是当前选区的startContainer或endContainer节点， 如果 includeSelf\r\n         * 的取值为true， 则返回的节点可以是自身的容器节点， 否则， 则不能是容器节点\r\n         * @method  getCommonAncestor\r\n         * @param { Boolean } includeSelf 是否允许获取到的公共祖先节点是当前range对象的容器节点\r\n         * @return { Node } 当前range对象内所有节点的公共祖先节点\r\n         * @see UE.dom.Range:getCommonAncestor()\r\n         * @example\r\n         * ```html\r\n         * <body>\r\n         *\r\n         *     <!-- 选区示例 -->\r\n         *     <b>xxx<i>xxxx<span>xx[x</span>xx]x</i>xxxxxxx</b>\r\n         *\r\n         *     <script>\r\n         *\r\n         *         var node = range.getCommonAncestor( false );\r\n         *\r\n         *         //这里的公共祖先节点是B而不是I， 是因为参数限制了获取到的节点不能是容器节点\r\n         *         //output: B\r\n         *         console.log( node.tagName );\r\n         *\r\n         *     </script>\r\n         *\r\n         * </body>\r\n         * ```\r\n         */\r\n\r\n        /**\r\n         * 获取当前选区所包含的所有节点的公共祖先节点， 可以根据给定的参数 includeSelf 决定获取到\r\n         * 的公共祖先节点是否可以是当前选区的startContainer或endContainer节点， 如果 includeSelf\r\n         * 的取值为true， 则返回的节点可以是自身的容器节点， 否则， 则不能是容器节点； 同时可以根据\r\n         * ignoreTextNode 参数的取值决定是否忽略类型为文本节点的祖先节点。\r\n         * @method  getCommonAncestor\r\n         * @param { Boolean } includeSelf 是否允许获取到的公共祖先节点是当前range对象的容器节点\r\n         * @param { Boolean } ignoreTextNode 获取祖先节点的过程中是否忽略类型为文本节点的祖先节点\r\n         * @return { Node } 当前range对象内所有节点的公共祖先节点\r\n         * @see UE.dom.Range:getCommonAncestor()\r\n         * @see UE.dom.Range:getCommonAncestor(Boolean)\r\n         * @example\r\n         * ```html\r\n         * <body>\r\n         *\r\n         *     <!-- 选区示例 -->\r\n         *     <b>xxx<i>xxxx<span>x[x]x</span>xxx</i>xxxxxxx</b>\r\n         *\r\n         *     <script>\r\n         *\r\n         *         var node = range.getCommonAncestor( true, false );\r\n         *\r\n         *         //output: SPAN\r\n         *         console.log( node.tagName );\r\n         *\r\n         *     </script>\r\n         *\r\n         * </body>\r\n         * ```\r\n         */\r\n        getCommonAncestor:function (includeSelf, ignoreTextNode) {\r\n            var me = this,\r\n                start = me.startContainer,\r\n                end = me.endContainer;\r\n            if (start === end) {\r\n                if (includeSelf && selectOneNode(this)) {\r\n                    start = start.childNodes[me.startOffset];\r\n                    if(start.nodeType == 1)\r\n                        return start;\r\n                }\r\n                //只有在上来就相等的情况下才会出现是文本的情况\r\n                return ignoreTextNode && start.nodeType == 3 ? start.parentNode : start;\r\n            }\r\n            return domUtils.getCommonAncestor(start, end);\r\n        },\r\n\r\n        /**\r\n         * 调整当前Range的开始和结束边界容器，如果是容器节点是文本节点,就调整到包含该文本节点的父节点上\r\n         * @method trimBoundary\r\n         * @remind 该操作有可能会引起文本节点被切开\r\n         * @return { UE.dom.Range } 当前range对象\r\n         * @example\r\n         * ```html\r\n         *\r\n         * //选区示例\r\n         * <b>xxx<i>[xxxxx]</i>xxx</b>\r\n         *\r\n         * <script>\r\n         *     //未调整前， 选区的开始容器和结束都是文本节点\r\n         *     //执行调整\r\n         *     range.trimBoundary();\r\n         *\r\n         *     //调整之后， 容器节点变成了i节点\r\n         *     //<b>xxx[<i>xxxxx</i>]xxx</b>\r\n         * </script>\r\n         * ```\r\n         */\r\n\r\n        /**\r\n         * 调整当前Range的开始和结束边界容器，如果是容器节点是文本节点,就调整到包含该文本节点的父节点上，\r\n         * 可以根据 ignoreEnd 参数的值决定是否调整对结束边界的调整\r\n         * @method trimBoundary\r\n         * @param { Boolean } ignoreEnd 是否忽略对结束边界的调整\r\n         * @return { UE.dom.Range } 当前range对象\r\n         * @example\r\n         * ```html\r\n         *\r\n         * //选区示例\r\n         * <b>xxx<i>[xxxxx]</i>xxx</b>\r\n         *\r\n         * <script>\r\n         *     //未调整前， 选区的开始容器和结束都是文本节点\r\n         *     //执行调整\r\n         *     range.trimBoundary( true );\r\n         *\r\n         *     //调整之后， 开始容器节点变成了i节点\r\n         *     //但是， 结束容器没有发生变化\r\n         *     //<b>xxx[<i>xxxxx]</i>xxx</b>\r\n         * </script>\r\n         * ```\r\n         */\r\n        trimBoundary:function (ignoreEnd) {\r\n            this.txtToElmBoundary();\r\n            var start = this.startContainer,\r\n                offset = this.startOffset,\r\n                collapsed = this.collapsed,\r\n                end = this.endContainer;\r\n            if (start.nodeType == 3) {\r\n                if (offset == 0) {\r\n                    this.setStartBefore(start);\r\n                } else {\r\n                    if (offset >= start.nodeValue.length) {\r\n                        this.setStartAfter(start);\r\n                    } else {\r\n                        var textNode = domUtils.split(start, offset);\r\n                        //跟新结束边界\r\n                        if (start === end) {\r\n                            this.setEnd(textNode, this.endOffset - offset);\r\n                        } else if (start.parentNode === end) {\r\n                            this.endOffset += 1;\r\n                        }\r\n                        this.setStartBefore(textNode);\r\n                    }\r\n                }\r\n                if (collapsed) {\r\n                    return this.collapse(true);\r\n                }\r\n            }\r\n            if (!ignoreEnd) {\r\n                offset = this.endOffset;\r\n                end = this.endContainer;\r\n                if (end.nodeType == 3) {\r\n                    if (offset == 0) {\r\n                        this.setEndBefore(end);\r\n                    } else {\r\n                        offset < end.nodeValue.length && domUtils.split(end, offset);\r\n                        this.setEndAfter(end);\r\n                    }\r\n                }\r\n            }\r\n            return this;\r\n        },\r\n\r\n        /**\r\n         * 如果选区在文本的边界上，就扩展选区到文本的父节点上, 如果当前选区是闭合的， 则什么也不做\r\n         * @method txtToElmBoundary\r\n         * @remind 该操作不会修改dom节点\r\n         * @return { UE.dom.Range } 当前range对象\r\n         */\r\n\r\n        /**\r\n         * 如果选区在文本的边界上，就扩展选区到文本的父节点上, 如果当前选区是闭合的， 则根据参数项\r\n         * ignoreCollapsed 的值决定是否执行该调整\r\n         * @method txtToElmBoundary\r\n         * @param { Boolean } ignoreCollapsed 是否忽略选区的闭合状态， 如果该参数取值为true， 则\r\n         *                      不论选区是否闭合， 都会执行该操作， 反之， 则不会对闭合的选区执行该操作\r\n         * @return { UE.dom.Range } 当前range对象\r\n         */\r\n        txtToElmBoundary:function (ignoreCollapsed) {\r\n            function adjust(r, c) {\r\n                var container = r[c + 'Container'],\r\n                    offset = r[c + 'Offset'];\r\n                if (container.nodeType == 3) {\r\n                    if (!offset) {\r\n                        r['set' + c.replace(/(\\w)/, function (a) {\r\n                            return a.toUpperCase();\r\n                        }) + 'Before'](container);\r\n                    } else if (offset >= container.nodeValue.length) {\r\n                        r['set' + c.replace(/(\\w)/, function (a) {\r\n                            return a.toUpperCase();\r\n                        }) + 'After' ](container);\r\n                    }\r\n                }\r\n            }\r\n\r\n            if (ignoreCollapsed || !this.collapsed) {\r\n                adjust(this, 'start');\r\n                adjust(this, 'end');\r\n            }\r\n            return this;\r\n        },\r\n\r\n        /**\r\n         * 在当前选区的开始位置前插入节点，新插入的节点会被该range包含\r\n         * @method  insertNode\r\n         * @param { Node } node 需要插入的节点\r\n         * @remind 插入的节点可以是一个DocumentFragment依次插入多个节点\r\n         * @return { UE.dom.Range } 当前range对象\r\n         */\r\n        insertNode:function (node) {\r\n            var first = node, length = 1;\r\n            if (node.nodeType == 11) {\r\n                first = node.firstChild;\r\n                length = node.childNodes.length;\r\n            }\r\n            this.trimBoundary(true);\r\n            var start = this.startContainer,\r\n                offset = this.startOffset;\r\n            var nextNode = start.childNodes[ offset ];\r\n            if (nextNode) {\r\n                start.insertBefore(node, nextNode);\r\n            } else {\r\n                start.appendChild(node);\r\n            }\r\n            if (first.parentNode === this.endContainer) {\r\n                this.endOffset = this.endOffset + length;\r\n            }\r\n            return this.setStartBefore(first);\r\n        },\r\n\r\n        /**\r\n         * 闭合选区到当前选区的开始位置， 并且定位光标到闭合后的位置\r\n         * @method  setCursor\r\n         * @return { UE.dom.Range } 当前range对象\r\n         * @see UE.dom.Range:collapse()\r\n         */\r\n\r\n        /**\r\n         * 闭合选区，可以根据参数toEnd的值控制选区是向前闭合还是向后闭合， 并且定位光标到闭合后的位置。\r\n         * @method  setCursor\r\n         * @param { Boolean } toEnd 是否向后闭合， 如果为true， 则闭合选区时， 将向结束容器方向闭合，\r\n         *                      反之，则向开始容器方向闭合\r\n         * @return { UE.dom.Range } 当前range对象\r\n         * @see UE.dom.Range:collapse(Boolean)\r\n         */\r\n        setCursor:function (toEnd, noFillData) {\r\n            return this.collapse(!toEnd).select(noFillData);\r\n        },\r\n\r\n        /**\r\n         * 创建当前range的一个书签，记录下当前range的位置，方便当dom树改变时，还能找回原来的选区位置\r\n         * @method createBookmark\r\n         * @param { Boolean } serialize 控制返回的标记位置是对当前位置的引用还是ID，如果该值为true，则\r\n         *                              返回标记位置的ID， 反之则返回标记位置节点的引用\r\n         * @return { Object } 返回一个书签记录键值对， 其包含的key有： start => 开始标记的ID或者引用，\r\n         *                          end => 结束标记的ID或引用， id => 当前标记的类型， 如果为true，则表示\r\n         *                          返回的记录的类型为ID， 反之则为引用\r\n         */\r\n        createBookmark:function (serialize, same) {\r\n            var endNode,\r\n                startNode = this.document.createElement('span');\r\n            startNode.style.cssText = 'display:none;line-height:0px;';\r\n            startNode.appendChild(this.document.createTextNode('\\u200D'));\r\n            startNode.id = '_baidu_bookmark_start_' + (same ? '' : guid++);\r\n\r\n            if (!this.collapsed) {\r\n                endNode = startNode.cloneNode(true);\r\n                endNode.id = '_baidu_bookmark_end_' + (same ? '' : guid++);\r\n            }\r\n            this.insertNode(startNode);\r\n            if (endNode) {\r\n                this.collapse().insertNode(endNode).setEndBefore(endNode);\r\n            }\r\n            this.setStartAfter(startNode);\r\n            return {\r\n                start:serialize ? startNode.id : startNode,\r\n                end:endNode ? serialize ? endNode.id : endNode : null,\r\n                id:serialize\r\n            }\r\n        },\r\n\r\n        /**\r\n         *  调整当前range的边界到书签位置，并删除该书签对象所标记的位置内的节点\r\n         *  @method  moveToBookmark\r\n         *  @param { BookMark } bookmark createBookmark所创建的标签对象\r\n         *  @return { UE.dom.Range } 当前range对象\r\n         *  @see UE.dom.Range:createBookmark(Boolean)\r\n         */\r\n        moveToBookmark:function (bookmark) {\r\n            var start = bookmark.id ? this.document.getElementById(bookmark.start) : bookmark.start,\r\n                end = bookmark.end && bookmark.id ? this.document.getElementById(bookmark.end) : bookmark.end;\r\n            this.setStartBefore(start);\r\n            domUtils.remove(start);\r\n            if (end) {\r\n                this.setEndBefore(end);\r\n                domUtils.remove(end);\r\n            } else {\r\n                this.collapse(true);\r\n            }\r\n            return this;\r\n        },\r\n\r\n        /**\r\n         * 调整range的边界，使其\"放大\"到最近的父节点\r\n         * @method  enlarge\r\n         * @remind 会引起选区的变化\r\n         * @return { UE.dom.Range } 当前range对象\r\n         */\r\n\r\n        /**\r\n         * 调整range的边界，使其\"放大\"到最近的父节点，根据参数 toBlock 的取值， 可以\r\n         * 要求扩大之后的父节点是block节点\r\n         * @method  enlarge\r\n         * @param { Boolean } toBlock 是否要求扩大之后的父节点必须是block节点\r\n         * @return { UE.dom.Range } 当前range对象\r\n         */\r\n        enlarge:function (toBlock, stopFn) {\r\n            var isBody = domUtils.isBody,\r\n                pre, node, tmp = this.document.createTextNode('');\r\n            if (toBlock) {\r\n                node = this.startContainer;\r\n                if (node.nodeType == 1) {\r\n                    if (node.childNodes[this.startOffset]) {\r\n                        pre = node = node.childNodes[this.startOffset]\r\n                    } else {\r\n                        node.appendChild(tmp);\r\n                        pre = node = tmp;\r\n                    }\r\n                } else {\r\n                    pre = node;\r\n                }\r\n                while (1) {\r\n                    if (domUtils.isBlockElm(node)) {\r\n                        node = pre;\r\n                        while ((pre = node.previousSibling) && !domUtils.isBlockElm(pre)) {\r\n                            node = pre;\r\n                        }\r\n                        this.setStartBefore(node);\r\n                        break;\r\n                    }\r\n                    pre = node;\r\n                    node = node.parentNode;\r\n                }\r\n                node = this.endContainer;\r\n                if (node.nodeType == 1) {\r\n                    if (pre = node.childNodes[this.endOffset]) {\r\n                        node.insertBefore(tmp, pre);\r\n                    } else {\r\n                        node.appendChild(tmp);\r\n                    }\r\n                    pre = node = tmp;\r\n                } else {\r\n                    pre = node;\r\n                }\r\n                while (1) {\r\n                    if (domUtils.isBlockElm(node)) {\r\n                        node = pre;\r\n                        while ((pre = node.nextSibling) && !domUtils.isBlockElm(pre)) {\r\n                            node = pre;\r\n                        }\r\n                        this.setEndAfter(node);\r\n                        break;\r\n                    }\r\n                    pre = node;\r\n                    node = node.parentNode;\r\n                }\r\n                if (tmp.parentNode === this.endContainer) {\r\n                    this.endOffset--;\r\n                }\r\n                domUtils.remove(tmp);\r\n            }\r\n\r\n            // 扩展边界到最大\r\n            if (!this.collapsed) {\r\n                while (this.startOffset == 0) {\r\n                    if (stopFn && stopFn(this.startContainer)) {\r\n                        break;\r\n                    }\r\n                    if (isBody(this.startContainer)) {\r\n                        break;\r\n                    }\r\n                    this.setStartBefore(this.startContainer);\r\n                }\r\n                while (this.endOffset == (this.endContainer.nodeType == 1 ? this.endContainer.childNodes.length : this.endContainer.nodeValue.length)) {\r\n                    if (stopFn && stopFn(this.endContainer)) {\r\n                        break;\r\n                    }\r\n                    if (isBody(this.endContainer)) {\r\n                        break;\r\n                    }\r\n                    this.setEndAfter(this.endContainer);\r\n                }\r\n            }\r\n            return this;\r\n        },\r\n        enlargeToBlockElm:function(ignoreEnd){\r\n            while(!domUtils.isBlockElm(this.startContainer)){\r\n                this.setStartBefore(this.startContainer);\r\n            }\r\n            if(!ignoreEnd){\r\n                while(!domUtils.isBlockElm(this.endContainer)){\r\n                    this.setEndAfter(this.endContainer);\r\n                }\r\n            }\r\n            return this;\r\n        },\r\n        /**\r\n         * 调整Range的边界，使其\"缩小\"到最合适的位置\r\n         * @method adjustmentBoundary\r\n         * @return { UE.dom.Range } 当前range对象\r\n         * @see UE.dom.Range:shrinkBoundary()\r\n         */\r\n        adjustmentBoundary:function () {\r\n            if (!this.collapsed) {\r\n                while (!domUtils.isBody(this.startContainer) &&\r\n                    this.startOffset == this.startContainer[this.startContainer.nodeType == 3 ? 'nodeValue' : 'childNodes'].length &&\r\n                    this.startContainer[this.startContainer.nodeType == 3 ? 'nodeValue' : 'childNodes'].length\r\n                    ) {\r\n\r\n                    this.setStartAfter(this.startContainer);\r\n                }\r\n                while (!domUtils.isBody(this.endContainer) && !this.endOffset &&\r\n                    this.endContainer[this.endContainer.nodeType == 3 ? 'nodeValue' : 'childNodes'].length\r\n                    ) {\r\n                    this.setEndBefore(this.endContainer);\r\n                }\r\n            }\r\n            return this;\r\n        },\r\n\r\n        /**\r\n         * 给range选区中的内容添加给定的inline标签\r\n         * @method applyInlineStyle\r\n         * @param { String } tagName 需要添加的标签名\r\n         * @example\r\n         * ```html\r\n         * <p>xxxx[xxxx]x</p>  ==>  range.applyInlineStyle(\"strong\")  ==>  <p>xxxx[<strong>xxxx</strong>]x</p>\r\n         * ```\r\n         */\r\n\r\n        /**\r\n         * 给range选区中的内容添加给定的inline标签， 并且为标签附加上一些初始化属性。\r\n         * @method applyInlineStyle\r\n         * @param { String } tagName 需要添加的标签名\r\n         * @param { Object } attrs 跟随新添加的标签的属性\r\n         * @return { UE.dom.Range } 当前选区\r\n         * @example\r\n         * ```html\r\n         * <p>xxxx[xxxx]x</p>\r\n         *\r\n         * ==>\r\n         *\r\n         * <!-- 执行操作 -->\r\n         * range.applyInlineStyle(\"strong\",{\"style\":\"font-size:12px\"})\r\n         *\r\n         * ==>\r\n         *\r\n         * <p>xxxx[<strong style=\"font-size:12px\">xxxx</strong>]x</p>\r\n         * ```\r\n         */\r\n        applyInlineStyle:function (tagName, attrs, list) {\r\n            if (this.collapsed)return this;\r\n            this.trimBoundary().enlarge(false,\r\n                function (node) {\r\n                    return node.nodeType == 1 && domUtils.isBlockElm(node)\r\n                }).adjustmentBoundary();\r\n            var bookmark = this.createBookmark(),\r\n                end = bookmark.end,\r\n                filterFn = function (node) {\r\n                    return node.nodeType == 1 ? node.tagName.toLowerCase() != 'br' : !domUtils.isWhitespace(node);\r\n                },\r\n                current = domUtils.getNextDomNode(bookmark.start, false, filterFn),\r\n                node,\r\n                pre,\r\n                range = this.cloneRange();\r\n            while (current && (domUtils.getPosition(current, end) & domUtils.POSITION_PRECEDING)) {\r\n                if (current.nodeType == 3 || dtd[tagName][current.tagName]) {\r\n                    range.setStartBefore(current);\r\n                    node = current;\r\n                    while (node && (node.nodeType == 3 || dtd[tagName][node.tagName]) && node !== end) {\r\n                        pre = node;\r\n                        node = domUtils.getNextDomNode(node, node.nodeType == 1, null, function (parent) {\r\n                            return dtd[tagName][parent.tagName];\r\n                        });\r\n                    }\r\n                    var frag = range.setEndAfter(pre).extractContents(), elm;\r\n                    if (list && list.length > 0) {\r\n                        var level, top;\r\n                        top = level = list[0].cloneNode(false);\r\n                        for (var i = 1, ci; ci = list[i++];) {\r\n                            level.appendChild(ci.cloneNode(false));\r\n                            level = level.firstChild;\r\n                        }\r\n                        elm = level;\r\n                    } else {\r\n                        elm = range.document.createElement(tagName);\r\n                    }\r\n                    if (attrs) {\r\n                        domUtils.setAttributes(elm, attrs);\r\n                    }\r\n                    elm.appendChild(frag);\r\n                    range.insertNode(list ? top : elm);\r\n                    //处理下滑线在a上的情况\r\n                    var aNode;\r\n                    if (tagName == 'span' && attrs.style && /text\\-decoration/.test(attrs.style) && (aNode = domUtils.findParentByTagName(elm, 'a', true))) {\r\n                        domUtils.setAttributes(aNode, attrs);\r\n                        domUtils.remove(elm, true);\r\n                        elm = aNode;\r\n                    } else {\r\n                        domUtils.mergeSibling(elm);\r\n                        domUtils.clearEmptySibling(elm);\r\n                    }\r\n                    //去除子节点相同的\r\n                    domUtils.mergeChild(elm, attrs);\r\n                    current = domUtils.getNextDomNode(elm, false, filterFn);\r\n                    domUtils.mergeToParent(elm);\r\n                    if (node === end) {\r\n                        break;\r\n                    }\r\n                } else {\r\n                    current = domUtils.getNextDomNode(current, true, filterFn);\r\n                }\r\n            }\r\n            return this.moveToBookmark(bookmark);\r\n        },\r\n\r\n        /**\r\n         * 移除当前选区内指定的inline标签，但保留其中的内容\r\n         * @method removeInlineStyle\r\n         * @param { String } tagName 需要移除的标签名\r\n         * @return { UE.dom.Range } 当前的range对象\r\n         * @example\r\n         * ```html\r\n         * xx[x<span>xxx<em>yyy</em>zz]z</span>  => range.removeInlineStyle([\"em\"])  => xx[x<span>xxxyyyzz]z</span>\r\n         * ```\r\n         */\r\n\r\n        /**\r\n         * 移除当前选区内指定的一组inline标签，但保留其中的内容\r\n         * @method removeInlineStyle\r\n         * @param { Array } tagNameArr 需要移除的标签名的数组\r\n         * @return { UE.dom.Range } 当前的range对象\r\n         * @see UE.dom.Range:removeInlineStyle(String)\r\n         */\r\n        removeInlineStyle:function (tagNames) {\r\n            if (this.collapsed)return this;\r\n            tagNames = utils.isArray(tagNames) ? tagNames : [tagNames];\r\n            this.shrinkBoundary().adjustmentBoundary();\r\n            var start = this.startContainer, end = this.endContainer;\r\n            while (1) {\r\n                if (start.nodeType == 1) {\r\n                    if (utils.indexOf(tagNames, start.tagName.toLowerCase()) > -1) {\r\n                        break;\r\n                    }\r\n                    if (start.tagName.toLowerCase() == 'body') {\r\n                        start = null;\r\n                        break;\r\n                    }\r\n                }\r\n                start = start.parentNode;\r\n            }\r\n            while (1) {\r\n                if (end.nodeType == 1) {\r\n                    if (utils.indexOf(tagNames, end.tagName.toLowerCase()) > -1) {\r\n                        break;\r\n                    }\r\n                    if (end.tagName.toLowerCase() == 'body') {\r\n                        end = null;\r\n                        break;\r\n                    }\r\n                }\r\n                end = end.parentNode;\r\n            }\r\n            var bookmark = this.createBookmark(),\r\n                frag,\r\n                tmpRange;\r\n            if (start) {\r\n                tmpRange = this.cloneRange().setEndBefore(bookmark.start).setStartBefore(start);\r\n                frag = tmpRange.extractContents();\r\n                tmpRange.insertNode(frag);\r\n                domUtils.clearEmptySibling(start, true);\r\n                start.parentNode.insertBefore(bookmark.start, start);\r\n            }\r\n            if (end) {\r\n                tmpRange = this.cloneRange().setStartAfter(bookmark.end).setEndAfter(end);\r\n                frag = tmpRange.extractContents();\r\n                tmpRange.insertNode(frag);\r\n                domUtils.clearEmptySibling(end, false, true);\r\n                end.parentNode.insertBefore(bookmark.end, end.nextSibling);\r\n            }\r\n            var current = domUtils.getNextDomNode(bookmark.start, false, function (node) {\r\n                return node.nodeType == 1;\r\n            }), next;\r\n            while (current && current !== bookmark.end) {\r\n                next = domUtils.getNextDomNode(current, true, function (node) {\r\n                    return node.nodeType == 1;\r\n                });\r\n                if (utils.indexOf(tagNames, current.tagName.toLowerCase()) > -1) {\r\n                    domUtils.remove(current, true);\r\n                }\r\n                current = next;\r\n            }\r\n            return this.moveToBookmark(bookmark);\r\n        },\r\n\r\n        /**\r\n         * 获取当前选中的自闭合的节点\r\n         * @method  getClosedNode\r\n         * @return { Node | NULL } 如果当前选中的是自闭合节点， 则返回该节点， 否则返回NULL\r\n         */\r\n        getClosedNode:function () {\r\n            var node;\r\n            if (!this.collapsed) {\r\n                var range = this.cloneRange().adjustmentBoundary().shrinkBoundary();\r\n                if (selectOneNode(range)) {\r\n                    var child = range.startContainer.childNodes[range.startOffset];\r\n                    if (child && child.nodeType == 1 && (dtd.$empty[child.tagName] || dtd.$nonChild[child.tagName])) {\r\n                        node = child;\r\n                    }\r\n                }\r\n            }\r\n            return node;\r\n        },\r\n\r\n        /**\r\n         * 在页面上高亮range所表示的选区\r\n         * @method select\r\n         * @return { UE.dom.Range } 返回当前Range对象\r\n         */\r\n            //这里不区分ie9以上，trace:3824\r\n        select:browser.ie ? function (noFillData, textRange) {\r\n            var nativeRange;\r\n            if (!this.collapsed)\r\n                this.shrinkBoundary();\r\n            var node = this.getClosedNode();\r\n            if (node && !textRange) {\r\n                try {\r\n                    nativeRange = this.document.body.createControlRange();\r\n                    nativeRange.addElement(node);\r\n                    nativeRange.select();\r\n                } catch (e) {}\r\n                return this;\r\n            }\r\n            var bookmark = this.createBookmark(),\r\n                start = bookmark.start,\r\n                end;\r\n            nativeRange = this.document.body.createTextRange();\r\n            nativeRange.moveToElementText(start);\r\n            nativeRange.moveStart('character', 1);\r\n            if (!this.collapsed) {\r\n                var nativeRangeEnd = this.document.body.createTextRange();\r\n                end = bookmark.end;\r\n                nativeRangeEnd.moveToElementText(end);\r\n                nativeRange.setEndPoint('EndToEnd', nativeRangeEnd);\r\n            } else {\r\n                if (!noFillData && this.startContainer.nodeType != 3) {\r\n                    //使用<span>|x<span>固定住光标\r\n                    var tmpText = this.document.createTextNode(fillChar),\r\n                        tmp = this.document.createElement('span');\r\n                    tmp.appendChild(this.document.createTextNode(fillChar));\r\n                    start.parentNode.insertBefore(tmp, start);\r\n                    start.parentNode.insertBefore(tmpText, start);\r\n                    //当点b,i,u时，不能清除i上边的b\r\n                    removeFillData(this.document, tmpText);\r\n                    fillData = tmpText;\r\n                    mergeSibling(tmp, 'previousSibling');\r\n                    mergeSibling(start, 'nextSibling');\r\n                    nativeRange.moveStart('character', -1);\r\n                    nativeRange.collapse(true);\r\n                }\r\n            }\r\n            this.moveToBookmark(bookmark);\r\n            tmp && domUtils.remove(tmp);\r\n            //IE在隐藏状态下不支持range操作，catch一下\r\n            try {\r\n                nativeRange.select();\r\n            } catch (e) {\r\n            }\r\n            return this;\r\n        } : function (notInsertFillData) {\r\n            function checkOffset(rng){\r\n\r\n                function check(node,offset,dir){\r\n                    if(node.nodeType == 3 && node.nodeValue.length < offset){\r\n                        rng[dir + 'Offset'] = node.nodeValue.length\r\n                    }\r\n                }\r\n                check(rng.startContainer,rng.startOffset,'start');\r\n                check(rng.endContainer,rng.endOffset,'end');\r\n            }\r\n            var win = domUtils.getWindow(this.document),\r\n                sel = win.getSelection(),\r\n                txtNode;\r\n            //FF下关闭自动长高时滚动条在关闭dialog时会跳\r\n            //ff下如果不body.focus将不能定位闭合光标到编辑器内\r\n            browser.gecko ? this.document.body.focus() : win.focus();\r\n            if (sel) {\r\n                sel.removeAllRanges();\r\n                // trace:870 chrome/safari后边是br对于闭合得range不能定位 所以去掉了判断\r\n                // this.startContainer.nodeType != 3 &&! ((child = this.startContainer.childNodes[this.startOffset]) && child.nodeType == 1 && child.tagName == 'BR'\r\n                if (this.collapsed && !notInsertFillData) {\r\n//                    //opear如果没有节点接着，原生的不能够定位,不能在body的第一级插入空白节点\r\n//                    if (notInsertFillData && browser.opera && !domUtils.isBody(this.startContainer) && this.startContainer.nodeType == 1) {\r\n//                        var tmp = this.document.createTextNode('');\r\n//                        this.insertNode(tmp).setStart(tmp, 0).collapse(true);\r\n//                    }\r\n//\r\n                    //处理光标落在文本节点的情况\r\n                    //处理以下的情况\r\n                    //<b>|xxxx</b>\r\n                    //<b>xxxx</b>|xxxx\r\n                    //xxxx<b>|</b>\r\n                    var start = this.startContainer,child = start;\r\n                    if(start.nodeType == 1){\r\n                        child = start.childNodes[this.startOffset];\r\n\r\n                    }\r\n                    if( !(start.nodeType == 3 && this.startOffset)  &&\r\n                        (child ?\r\n                            (!child.previousSibling || child.previousSibling.nodeType != 3)\r\n                            :\r\n                            (!start.lastChild || start.lastChild.nodeType != 3)\r\n                        )\r\n                    ){\r\n                        txtNode = this.document.createTextNode(fillChar);\r\n                        //跟着前边走\r\n                        this.insertNode(txtNode);\r\n                        removeFillData(this.document, txtNode);\r\n                        mergeSibling(txtNode, 'previousSibling');\r\n                        mergeSibling(txtNode, 'nextSibling');\r\n                        fillData = txtNode;\r\n                        this.setStart(txtNode, browser.webkit ? 1 : 0).collapse(true);\r\n                    }\r\n                }\r\n                var nativeRange = this.document.createRange();\r\n                if(this.collapsed && browser.opera && this.startContainer.nodeType == 1){\r\n                    var child = this.startContainer.childNodes[this.startOffset];\r\n                    if(!child){\r\n                        //往前靠拢\r\n                        child = this.startContainer.lastChild;\r\n                        if( child && domUtils.isBr(child)){\r\n                            this.setStartBefore(child).collapse(true);\r\n                        }\r\n                    }else{\r\n                        //向后靠拢\r\n                        while(child && domUtils.isBlockElm(child)){\r\n                            if(child.nodeType == 1 && child.childNodes[0]){\r\n                                child = child.childNodes[0]\r\n                            }else{\r\n                                break;\r\n                            }\r\n                        }\r\n                        child && this.setStartBefore(child).collapse(true)\r\n                    }\r\n\r\n                }\r\n                //是createAddress最后一位算的不准，现在这里进行微调\r\n                checkOffset(this);\r\n                nativeRange.setStart(this.startContainer, this.startOffset);\r\n                nativeRange.setEnd(this.endContainer, this.endOffset);\r\n                sel.addRange(nativeRange);\r\n            }\r\n            return this;\r\n        },\r\n\r\n        /**\r\n         * 滚动到当前range开始的位置\r\n         * @method scrollToView\r\n         * @param { Window } win 当前range对象所属的window对象\r\n         * @return { UE.dom.Range } 当前Range对象\r\n         */\r\n\r\n        /**\r\n         * 滚动到距离当前range开始位置 offset 的位置处\r\n         * @method scrollToView\r\n         * @param { Window } win 当前range对象所属的window对象\r\n         * @param { Number } offset 距离range开始位置处的偏移量， 如果为正数， 则向下偏移， 反之， 则向上偏移\r\n         * @return { UE.dom.Range } 当前Range对象\r\n         */\r\n        scrollToView:function (win, offset) {\r\n            win = win ? window : domUtils.getWindow(this.document);\r\n            var me = this,\r\n                span = me.document.createElement('span');\r\n            //trace:717\r\n            span.innerHTML = '&nbsp;';\r\n            me.cloneRange().insertNode(span);\r\n            domUtils.scrollToView(span, win, offset);\r\n            domUtils.remove(span);\r\n            return me;\r\n        },\r\n\r\n        /**\r\n         * 判断当前选区内容是否占位符\r\n         * @private\r\n         * @method inFillChar\r\n         * @return { Boolean } 如果是占位符返回true，否则返回false\r\n         */\r\n        inFillChar : function(){\r\n            var start = this.startContainer;\r\n            if(this.collapsed && start.nodeType == 3\r\n                && start.nodeValue.replace(new RegExp('^' + domUtils.fillChar),'').length + 1 == start.nodeValue.length\r\n                ){\r\n                return true;\r\n            }\r\n            return false;\r\n        },\r\n\r\n        /**\r\n         * 保存\r\n         * @method createAddress\r\n         * @private\r\n         * @return { Boolean } 返回开始和结束的位置\r\n         * @example\r\n         * ```html\r\n         * <body>\r\n         *     <p>\r\n         *         aaaa\r\n         *         <em>\r\n         *             <!-- 选区开始 -->\r\n         *             bbbb\r\n         *             <!-- 选区结束 -->\r\n         *         </em>\r\n         *     </p>\r\n         *\r\n         *     <script>\r\n         *         //output: {startAddress:[0,1,0,0],endAddress:[0,1,0,4]}\r\n         *         console.log( range.createAddress() );\r\n         *     </script>\r\n         * </body>\r\n         * ```\r\n         */\r\n        createAddress : function(ignoreEnd,ignoreTxt){\r\n            var addr = {},me = this;\r\n\r\n            function getAddress(isStart){\r\n                var node = isStart ? me.startContainer : me.endContainer;\r\n                var parents = domUtils.findParents(node,true,function(node){return !domUtils.isBody(node)}),\r\n                    addrs = [];\r\n                for(var i = 0,ci;ci = parents[i++];){\r\n                    addrs.push(domUtils.getNodeIndex(ci,ignoreTxt));\r\n                }\r\n                var firstIndex = 0;\r\n\r\n                if(ignoreTxt){\r\n                    if(node.nodeType == 3){\r\n                        var tmpNode = node.previousSibling;\r\n                        while(tmpNode && tmpNode.nodeType == 3){\r\n                            firstIndex += tmpNode.nodeValue.replace(fillCharReg,'').length;\r\n                            tmpNode = tmpNode.previousSibling;\r\n                        }\r\n                        firstIndex +=  (isStart ? me.startOffset : me.endOffset)// - (fillCharReg.test(node.nodeValue) ? 1 : 0 )\r\n                    }else{\r\n                        node =  node.childNodes[ isStart ? me.startOffset : me.endOffset];\r\n                        if(node){\r\n                            firstIndex = domUtils.getNodeIndex(node,ignoreTxt);\r\n                        }else{\r\n                            node = isStart ? me.startContainer : me.endContainer;\r\n                            var first = node.firstChild;\r\n                            while(first){\r\n                                if(domUtils.isFillChar(first)){\r\n                                    first = first.nextSibling;\r\n                                    continue;\r\n                                }\r\n                                firstIndex++;\r\n                                if(first.nodeType == 3){\r\n                                    while( first && first.nodeType == 3){\r\n                                        first = first.nextSibling;\r\n                                    }\r\n                                }else{\r\n                                    first = first.nextSibling;\r\n                                }\r\n                            }\r\n                        }\r\n                    }\r\n\r\n                }else{\r\n                    firstIndex = isStart ? domUtils.isFillChar(node) ? 0 : me.startOffset  : me.endOffset\r\n                }\r\n                if(firstIndex < 0){\r\n                    firstIndex = 0;\r\n                }\r\n                addrs.push(firstIndex);\r\n                return addrs;\r\n            }\r\n            addr.startAddress = getAddress(true);\r\n            if(!ignoreEnd){\r\n                addr.endAddress = me.collapsed ? [].concat(addr.startAddress) : getAddress();\r\n            }\r\n            return addr;\r\n        },\r\n\r\n        /**\r\n         * 保存\r\n         * @method createAddress\r\n         * @private\r\n         * @return { Boolean } 返回开始和结束的位置\r\n         * @example\r\n         * ```html\r\n         * <body>\r\n         *     <p>\r\n         *         aaaa\r\n         *         <em>\r\n         *             <!-- 选区开始 -->\r\n         *             bbbb\r\n         *             <!-- 选区结束 -->\r\n         *         </em>\r\n         *     </p>\r\n         *\r\n         *     <script>\r\n         *         var range = editor.selection.getRange();\r\n         *         range.moveToAddress({startAddress:[0,1,0,0],endAddress:[0,1,0,4]});\r\n         *         range.select();\r\n         *         //output: 'bbbb'\r\n         *         console.log(editor.selection.getText());\r\n         *     </script>\r\n         * </body>\r\n         * ```\r\n         */\r\n        moveToAddress : function(addr,ignoreEnd){\r\n            var me = this;\r\n            function getNode(address,isStart){\r\n                var tmpNode = me.document.body,\r\n                    parentNode,offset;\r\n                for(var i= 0,ci,l=address.length;i<l;i++){\r\n                    ci = address[i];\r\n                    parentNode = tmpNode;\r\n                    tmpNode = tmpNode.childNodes[ci];\r\n                    if(!tmpNode){\r\n                        offset = ci;\r\n                        break;\r\n                    }\r\n                }\r\n                if(isStart){\r\n                    if(tmpNode){\r\n                        me.setStartBefore(tmpNode)\r\n                    }else{\r\n                        me.setStart(parentNode,offset)\r\n                    }\r\n                }else{\r\n                    if(tmpNode){\r\n                        me.setEndBefore(tmpNode)\r\n                    }else{\r\n                        me.setEnd(parentNode,offset)\r\n                    }\r\n                }\r\n            }\r\n            getNode(addr.startAddress,true);\r\n            !ignoreEnd && addr.endAddress &&  getNode(addr.endAddress);\r\n            return me;\r\n        },\r\n\r\n        /**\r\n         * 判断给定的Range对象是否和当前Range对象表示的是同一个选区\r\n         * @method equals\r\n         * @param { UE.dom.Range } 需要判断的Range对象\r\n         * @return { Boolean } 如果给定的Range对象与当前Range对象表示的是同一个选区， 则返回true， 否则返回false\r\n         */\r\n        equals : function(rng){\r\n            for(var p in this){\r\n                if(this.hasOwnProperty(p)){\r\n                    if(this[p] !== rng[p])\r\n                        return false\r\n                }\r\n            }\r\n            return true;\r\n\r\n        },\r\n\r\n        /**\r\n         * 遍历range内的节点。每当遍历一个节点时， 都会执行参数项 doFn 指定的函数， 该函数的接受当前遍历的节点\r\n         * 作为其参数。\r\n         * @method traversal\r\n         * @param { Function }  doFn 对每个遍历的节点要执行的方法， 该方法接受当前遍历的节点作为其参数\r\n         * @return { UE.dom.Range } 当前range对象\r\n         * @example\r\n         * ```html\r\n         *\r\n         * <body>\r\n         *\r\n         *     <!-- 选区开始 -->\r\n         *     <span></span>\r\n         *     <a></a>\r\n         *     <!-- 选区结束 -->\r\n         * </body>\r\n         *\r\n         * <script>\r\n         *\r\n         *     //output: <span></span><a></a>\r\n         *     console.log( range.cloneContents() );\r\n         *\r\n         *     range.traversal( function ( node ) {\r\n         *\r\n         *         if ( node.nodeType === 1 ) {\r\n         *             node.className = \"test\";\r\n         *         }\r\n         *\r\n         *     } );\r\n         *\r\n         *     //output: <span class=\"test\"></span><a class=\"test\"></a>\r\n         *     console.log( range.cloneContents() );\r\n         *\r\n         * </script>\r\n         * ```\r\n         */\r\n\r\n        /**\r\n         * 遍历range内的节点。\r\n         * 每当遍历一个节点时， 都会执行参数项 doFn 指定的函数， 该函数的接受当前遍历的节点\r\n         * 作为其参数。\r\n         * 可以通过参数项 filterFn 来指定一个过滤器， 只有符合该过滤器过滤规则的节点才会触\r\n         * 发doFn函数的执行\r\n         * @method traversal\r\n         * @param { Function } doFn 对每个遍历的节点要执行的方法， 该方法接受当前遍历的节点作为其参数\r\n         * @param { Function } filterFn 过滤器， 该函数接受当前遍历的节点作为参数， 如果该节点满足过滤\r\n         *                      规则， 请返回true， 该节点会触发doFn， 否则， 请返回false， 则该节点不\r\n         *                      会触发doFn。\r\n         * @return { UE.dom.Range } 当前range对象\r\n         * @see UE.dom.Range:traversal(Function)\r\n         * @example\r\n         * ```html\r\n         *\r\n         * <body>\r\n         *\r\n         *     <!-- 选区开始 -->\r\n         *     <span></span>\r\n         *     <a></a>\r\n         *     <!-- 选区结束 -->\r\n         * </body>\r\n         *\r\n         * <script>\r\n         *\r\n         *     //output: <span></span><a></a>\r\n         *     console.log( range.cloneContents() );\r\n         *\r\n         *     range.traversal( function ( node ) {\r\n         *\r\n         *         node.className = \"test\";\r\n         *\r\n         *     }, function ( node ) {\r\n         *          return node.nodeType === 1;\r\n         *     } );\r\n         *\r\n         *     //output: <span class=\"test\"></span><a class=\"test\"></a>\r\n         *     console.log( range.cloneContents() );\r\n         *\r\n         * </script>\r\n         * ```\r\n         */\r\n        traversal:function(doFn,filterFn){\r\n            if (this.collapsed)\r\n                return this;\r\n            var bookmark = this.createBookmark(),\r\n                end = bookmark.end,\r\n                current = domUtils.getNextDomNode(bookmark.start, false, filterFn);\r\n            while (current && current !== end && (domUtils.getPosition(current, end) & domUtils.POSITION_PRECEDING)) {\r\n                var tmpNode = domUtils.getNextDomNode(current,false,filterFn);\r\n                doFn(current);\r\n                current = tmpNode;\r\n            }\r\n            return this.moveToBookmark(bookmark);\r\n        }\r\n    };\r\n})();\r\n\r\n// core/Selection.js\r\n/**\r\n * 选集\r\n * @file\r\n * @module UE.dom\r\n * @class Selection\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 选区集合\r\n * @unfile\r\n * @module UE.dom\r\n * @class Selection\r\n */\r\n(function () {\r\n\r\n    function getBoundaryInformation( range, start ) {\r\n        var getIndex = domUtils.getNodeIndex;\r\n        range = range.duplicate();\r\n        range.collapse( start );\r\n        var parent = range.parentElement();\r\n        //如果节点里没有子节点，直接退出\r\n        if ( !parent.hasChildNodes() ) {\r\n            return  {container:parent, offset:0};\r\n        }\r\n        var siblings = parent.children,\r\n            child,\r\n            testRange = range.duplicate(),\r\n            startIndex = 0, endIndex = siblings.length - 1, index = -1,\r\n            distance;\r\n        while ( startIndex <= endIndex ) {\r\n            index = Math.floor( (startIndex + endIndex) / 2 );\r\n            child = siblings[index];\r\n            testRange.moveToElementText( child );\r\n            var position = testRange.compareEndPoints( 'StartToStart', range );\r\n            if ( position > 0 ) {\r\n                endIndex = index - 1;\r\n            } else if ( position < 0 ) {\r\n                startIndex = index + 1;\r\n            } else {\r\n                //trace:1043\r\n                return  {container:parent, offset:getIndex( child )};\r\n            }\r\n        }\r\n        if ( index == -1 ) {\r\n            testRange.moveToElementText( parent );\r\n            testRange.setEndPoint( 'StartToStart', range );\r\n            distance = testRange.text.replace( /(\\r\\n|\\r)/g, '\\n' ).length;\r\n            siblings = parent.childNodes;\r\n            if ( !distance ) {\r\n                child = siblings[siblings.length - 1];\r\n                return  {container:child, offset:child.nodeValue.length};\r\n            }\r\n\r\n            var i = siblings.length;\r\n            while ( distance > 0 ){\r\n                distance -= siblings[ --i ].nodeValue.length;\r\n            }\r\n            return {container:siblings[i], offset:-distance};\r\n        }\r\n        testRange.collapse( position > 0 );\r\n        testRange.setEndPoint( position > 0 ? 'StartToStart' : 'EndToStart', range );\r\n        distance = testRange.text.replace( /(\\r\\n|\\r)/g, '\\n' ).length;\r\n        if ( !distance ) {\r\n            return  dtd.$empty[child.tagName] || dtd.$nonChild[child.tagName] ?\r\n            {container:parent, offset:getIndex( child ) + (position > 0 ? 0 : 1)} :\r\n            {container:child, offset:position > 0 ? 0 : child.childNodes.length}\r\n        }\r\n        while ( distance > 0 ) {\r\n            try {\r\n                var pre = child;\r\n                child = child[position > 0 ? 'previousSibling' : 'nextSibling'];\r\n                distance -= child.nodeValue.length;\r\n            } catch ( e ) {\r\n                return {container:parent, offset:getIndex( pre )};\r\n            }\r\n        }\r\n        return  {container:child, offset:position > 0 ? -distance : child.nodeValue.length + distance}\r\n    }\r\n\r\n    /**\r\n     * 将ieRange转换为Range对象\r\n     * @param {Range}   ieRange    ieRange对象\r\n     * @param {Range}   range      Range对象\r\n     * @return  {Range}  range       返回转换后的Range对象\r\n     */\r\n    function transformIERangeToRange( ieRange, range ) {\r\n        if ( ieRange.item ) {\r\n            range.selectNode( ieRange.item( 0 ) );\r\n        } else {\r\n            var bi = getBoundaryInformation( ieRange, true );\r\n            range.setStart( bi.container, bi.offset );\r\n            if ( ieRange.compareEndPoints( 'StartToEnd', ieRange ) != 0 ) {\r\n                bi = getBoundaryInformation( ieRange, false );\r\n                range.setEnd( bi.container, bi.offset );\r\n            }\r\n        }\r\n        return range;\r\n    }\r\n\r\n    /**\r\n     * 获得ieRange\r\n     * @param {Selection} sel    Selection对象\r\n     * @return {ieRange}    得到ieRange\r\n     */\r\n    function _getIERange( sel ) {\r\n        var ieRange;\r\n        //ie下有可能报错\r\n        try {\r\n            ieRange = sel.getNative().createRange();\r\n        } catch ( e ) {\r\n            return null;\r\n        }\r\n        var el = ieRange.item ? ieRange.item( 0 ) : ieRange.parentElement();\r\n        if ( ( el.ownerDocument || el ) === sel.document ) {\r\n            return ieRange;\r\n        }\r\n        return null;\r\n    }\r\n\r\n    var Selection = dom.Selection = function ( doc ) {\r\n        var me = this, iframe;\r\n        me.document = doc;\r\n        if ( browser.ie9below ) {\r\n            iframe = domUtils.getWindow( doc ).frameElement;\r\n            domUtils.on( iframe, 'beforedeactivate', function () {\r\n                me._bakIERange = me.getIERange();\r\n            } );\r\n            domUtils.on( iframe, 'activate', function () {\r\n                try {\r\n                    if ( !_getIERange( me ) && me._bakIERange ) {\r\n                        me._bakIERange.select();\r\n                    }\r\n                } catch ( ex ) {\r\n                }\r\n                me._bakIERange = null;\r\n            } );\r\n        }\r\n        iframe = doc = null;\r\n    };\r\n\r\n    Selection.prototype = {\r\n\r\n        rangeInBody : function(rng,txtRange){\r\n            var node = browser.ie9below || txtRange ? rng.item ? rng.item() : rng.parentElement() : rng.startContainer;\r\n\r\n            return node === this.document.body || domUtils.inDoc(node,this.document);\r\n        },\r\n\r\n        /**\r\n         * 获取原生seleciton对象\r\n         * @method getNative\r\n         * @return { Object } 获得selection对象\r\n         * @example\r\n         * ```javascript\r\n         * editor.selection.getNative();\r\n         * ```\r\n         */\r\n        getNative:function () {\r\n            var doc = this.document;\r\n            try {\r\n                return !doc ? null : browser.ie9below ? doc.selection : domUtils.getWindow( doc ).getSelection();\r\n            } catch ( e ) {\r\n                return null;\r\n            }\r\n        },\r\n\r\n        /**\r\n         * 获得ieRange\r\n         * @method getIERange\r\n         * @return { Object } 返回ie原生的Range\r\n         * @example\r\n         * ```javascript\r\n         * editor.selection.getIERange();\r\n         * ```\r\n         */\r\n        getIERange:function () {\r\n            var ieRange = _getIERange( this );\r\n            if ( !ieRange ) {\r\n                if ( this._bakIERange ) {\r\n                    return this._bakIERange;\r\n                }\r\n            }\r\n            return ieRange;\r\n        },\r\n\r\n        /**\r\n         * 缓存当前选区的range和选区的开始节点\r\n         * @method cache\r\n         */\r\n        cache:function () {\r\n            this.clear();\r\n            this._cachedRange = this.getRange();\r\n            this._cachedStartElement = this.getStart();\r\n            this._cachedStartElementPath = this.getStartElementPath();\r\n        },\r\n\r\n        /**\r\n         * 获取选区开始位置的父节点到body\r\n         * @method getStartElementPath\r\n         * @return { Array } 返回父节点集合\r\n         * @example\r\n         * ```javascript\r\n         * editor.selection.getStartElementPath();\r\n         * ```\r\n         */\r\n        getStartElementPath:function () {\r\n            if ( this._cachedStartElementPath ) {\r\n                return this._cachedStartElementPath;\r\n            }\r\n            var start = this.getStart();\r\n            if ( start ) {\r\n                return domUtils.findParents( start, true, null, true )\r\n            }\r\n            return [];\r\n        },\r\n\r\n        /**\r\n         * 清空缓存\r\n         * @method clear\r\n         */\r\n        clear:function () {\r\n            this._cachedStartElementPath = this._cachedRange = this._cachedStartElement = null;\r\n        },\r\n\r\n        /**\r\n         * 编辑器是否得到了选区\r\n         * @method isFocus\r\n         */\r\n        isFocus:function () {\r\n            try {\r\n                if(browser.ie9below){\r\n\r\n                    var nativeRange = _getIERange(this);\r\n                    return !!(nativeRange && this.rangeInBody(nativeRange));\r\n                }else{\r\n                    return !!this.getNative().rangeCount;\r\n                }\r\n            } catch ( e ) {\r\n                return false;\r\n            }\r\n\r\n        },\r\n\r\n        /**\r\n         * 获取选区对应的Range\r\n         * @method getRange\r\n         * @return { Object } 得到Range对象\r\n         * @example\r\n         * ```javascript\r\n         * editor.selection.getRange();\r\n         * ```\r\n         */\r\n        getRange:function () {\r\n            var me = this;\r\n            function optimze( range ) {\r\n                var child = me.document.body.firstChild,\r\n                    collapsed = range.collapsed;\r\n                while ( child && child.firstChild ) {\r\n                    range.setStart( child, 0 );\r\n                    child = child.firstChild;\r\n                }\r\n                if ( !range.startContainer ) {\r\n                    range.setStart( me.document.body, 0 )\r\n                }\r\n                if ( collapsed ) {\r\n                    range.collapse( true );\r\n                }\r\n            }\r\n\r\n            if ( me._cachedRange != null ) {\r\n                return this._cachedRange;\r\n            }\r\n            var range = new baidu.editor.dom.Range( me.document );\r\n\r\n            if ( browser.ie9below ) {\r\n                var nativeRange = me.getIERange();\r\n                if ( nativeRange ) {\r\n                    //备份的_bakIERange可能已经实效了，dom树发生了变化比如从源码模式切回来，所以try一下，实效就放到body开始位置\r\n                    try{\r\n                        transformIERangeToRange( nativeRange, range );\r\n                    }catch(e){\r\n                        optimze( range );\r\n                    }\r\n\r\n                } else {\r\n                    optimze( range );\r\n                }\r\n            } else {\r\n                var sel = me.getNative();\r\n                if ( sel && sel.rangeCount ) {\r\n                    var firstRange = sel.getRangeAt( 0 );\r\n                    var lastRange = sel.getRangeAt( sel.rangeCount - 1 );\r\n                    range.setStart( firstRange.startContainer, firstRange.startOffset ).setEnd( lastRange.endContainer, lastRange.endOffset );\r\n                    if ( range.collapsed && domUtils.isBody( range.startContainer ) && !range.startOffset ) {\r\n                        optimze( range );\r\n                    }\r\n                } else {\r\n                    //trace:1734 有可能已经不在dom树上了，标识的节点\r\n                    if ( this._bakRange && domUtils.inDoc( this._bakRange.startContainer, this.document ) ){\r\n                        return this._bakRange;\r\n                    }\r\n                    optimze( range );\r\n                }\r\n            }\r\n            return this._bakRange = range;\r\n        },\r\n\r\n        /**\r\n         * 获取开始元素，用于状态反射\r\n         * @method getStart\r\n         * @return { Element } 获得开始元素\r\n         * @example\r\n         * ```javascript\r\n         * editor.selection.getStart();\r\n         * ```\r\n         */\r\n        getStart:function () {\r\n            if ( this._cachedStartElement ) {\r\n                return this._cachedStartElement;\r\n            }\r\n            var range = browser.ie9below ? this.getIERange() : this.getRange(),\r\n                tmpRange,\r\n                start, tmp, parent;\r\n            if ( browser.ie9below ) {\r\n                if ( !range ) {\r\n                    //todo 给第一个值可能会有问题\r\n                    return this.document.body.firstChild;\r\n                }\r\n                //control元素\r\n                if ( range.item ){\r\n                    return range.item( 0 );\r\n                }\r\n                tmpRange = range.duplicate();\r\n                //修正ie下<b>x</b>[xx] 闭合后 <b>x|</b>xx\r\n                tmpRange.text.length > 0 && tmpRange.moveStart( 'character', 1 );\r\n                tmpRange.collapse( 1 );\r\n                start = tmpRange.parentElement();\r\n                parent = tmp = range.parentElement();\r\n                while ( tmp = tmp.parentNode ) {\r\n                    if ( tmp == start ) {\r\n                        start = parent;\r\n                        break;\r\n                    }\r\n                }\r\n            } else {\r\n                range.shrinkBoundary();\r\n                start = range.startContainer;\r\n                if ( start.nodeType == 1 && start.hasChildNodes() ){\r\n                    start = start.childNodes[Math.min( start.childNodes.length - 1, range.startOffset )];\r\n                }\r\n                if ( start.nodeType == 3 ){\r\n                    return start.parentNode;\r\n                }\r\n            }\r\n            return start;\r\n        },\r\n\r\n        /**\r\n         * 得到选区中的文本\r\n         * @method getText\r\n         * @return { String } 选区中包含的文本\r\n         * @example\r\n         * ```javascript\r\n         * editor.selection.getText();\r\n         * ```\r\n         */\r\n        getText:function () {\r\n            var nativeSel, nativeRange;\r\n            if ( this.isFocus() && (nativeSel = this.getNative()) ) {\r\n                nativeRange = browser.ie9below ? nativeSel.createRange() : nativeSel.getRangeAt( 0 );\r\n                return browser.ie9below ? nativeRange.text : nativeRange.toString();\r\n            }\r\n            return '';\r\n        },\r\n\r\n        /**\r\n         * 清除选区\r\n         * @method clearRange\r\n         * @example\r\n         * ```javascript\r\n         * editor.selection.clearRange();\r\n         * ```\r\n         */\r\n        clearRange : function(){\r\n            this.getNative()[browser.ie9below ? 'empty' : 'removeAllRanges']();\r\n        }\r\n    };\r\n})();\r\n\r\n// core/Editor.js\r\n/**\r\n * 编辑器主类，包含编辑器提供的大部分公用接口\r\n * @file\r\n * @module UE\r\n * @class Editor\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * UEditor公用空间，UEditor所有的功能都挂载在该空间下\r\n * @unfile\r\n * @module UE\r\n */\r\n\r\n/**\r\n * UEditor的核心类，为用户提供与编辑器交互的接口。\r\n * @unfile\r\n * @module UE\r\n * @class Editor\r\n */\r\n\r\n(function () {\r\n    var uid = 0, _selectionChangeTimer;\r\n\r\n    /**\r\n     * 获取编辑器的html内容，赋值到编辑器所在表单的textarea文本域里面\r\n     * @private\r\n     * @method setValue\r\n     * @param { UE.Editor } editor 编辑器事例\r\n     */\r\n    function setValue(form, editor) {\r\n        var textarea;\r\n        if (editor.textarea) {\r\n            if (utils.isString(editor.textarea)) {\r\n                for (var i = 0, ti, tis = domUtils.getElementsByTagName(form, 'textarea'); ti = tis[i++];) {\r\n                    if (ti.id == 'ueditor_textarea_' + editor.options.textarea) {\r\n                        textarea = ti;\r\n                        break;\r\n                    }\r\n                }\r\n            } else {\r\n                textarea = editor.textarea;\r\n            }\r\n        }\r\n        if (!textarea) {\r\n            form.appendChild(textarea = domUtils.createElement(document, 'textarea', {\r\n                'name': editor.options.textarea,\r\n                'id': 'ueditor_textarea_' + editor.options.textarea,\r\n                'style': \"display:none\"\r\n            }));\r\n            //不要产生多个textarea\r\n            editor.textarea = textarea;\r\n        }\r\n        !textarea.getAttribute('name') && textarea.setAttribute('name', editor.options.textarea );\r\n        textarea.value = editor.hasContents() ?\r\n            (editor.options.allHtmlEnabled ? editor.getAllHtml() : editor.getContent(null, null, true)) :\r\n            ''\r\n    }\r\n    function loadPlugins(me){\r\n        //初始化插件\r\n        for (var pi in UE.plugins) {\r\n            UE.plugins[pi].call(me);\r\n        }\r\n\r\n    }\r\n    function checkCurLang(I18N){\r\n        for(var lang in I18N){\r\n            return lang\r\n        }\r\n    }\r\n\r\n    function langReadied(me){\r\n        me.langIsReady = true;\r\n\r\n        me.fireEvent(\"langReady\");\r\n    }\r\n\r\n    /**\r\n     * 编辑器准备就绪后会触发该事件\r\n     * @module UE\r\n     * @class Editor\r\n     * @event ready\r\n     * @remind render方法执行完成之后,会触发该事件\r\n     * @remind\r\n     * @example\r\n     * ```javascript\r\n     * editor.addListener( 'ready', function( editor ) {\r\n     *     editor.execCommand( 'focus' ); //编辑器家在完成后，让编辑器拿到焦点\r\n     * } );\r\n     * ```\r\n     */\r\n    /**\r\n     * 执行destroy方法,会触发该事件\r\n     * @module UE\r\n     * @class Editor\r\n     * @event destroy\r\n     * @see UE.Editor:destroy()\r\n     */\r\n    /**\r\n     * 执行reset方法,会触发该事件\r\n     * @module UE\r\n     * @class Editor\r\n     * @event reset\r\n     * @see UE.Editor:reset()\r\n     */\r\n    /**\r\n     * 执行focus方法,会触发该事件\r\n     * @module UE\r\n     * @class Editor\r\n     * @event focus\r\n     * @see UE.Editor:focus(Boolean)\r\n     */\r\n    /**\r\n     * 语言加载完成会触发该事件\r\n     * @module UE\r\n     * @class Editor\r\n     * @event langReady\r\n     */\r\n    /**\r\n     * 运行命令之后会触发该命令\r\n     * @module UE\r\n     * @class Editor\r\n     * @event beforeExecCommand\r\n     */\r\n    /**\r\n     * 运行命令之后会触发该命令\r\n     * @module UE\r\n     * @class Editor\r\n     * @event afterExecCommand\r\n     */\r\n    /**\r\n     * 运行命令之前会触发该命令\r\n     * @module UE\r\n     * @class Editor\r\n     * @event firstBeforeExecCommand\r\n     */\r\n    /**\r\n     * 在getContent方法执行之前会触发该事件\r\n     * @module UE\r\n     * @class Editor\r\n     * @event beforeGetContent\r\n     * @see UE.Editor:getContent()\r\n     */\r\n    /**\r\n     * 在getContent方法执行之后会触发该事件\r\n     * @module UE\r\n     * @class Editor\r\n     * @event afterGetContent\r\n     * @see UE.Editor:getContent()\r\n     */\r\n    /**\r\n     * 在getAllHtml方法执行时会触发该事件\r\n     * @module UE\r\n     * @class Editor\r\n     * @event getAllHtml\r\n     * @see UE.Editor:getAllHtml()\r\n     */\r\n    /**\r\n     * 在setContent方法执行之前会触发该事件\r\n     * @module UE\r\n     * @class Editor\r\n     * @event beforeSetContent\r\n     * @see UE.Editor:setContent(String)\r\n     */\r\n    /**\r\n     * 在setContent方法执行之后会触发该事件\r\n     * @module UE\r\n     * @class Editor\r\n     * @event afterSetContent\r\n     * @see UE.Editor:setContent(String)\r\n     */\r\n    /**\r\n     * 每当编辑器内部选区发生改变时，将触发该事件\r\n     * @event selectionchange\r\n     * @warning 该事件的触发非常频繁，不建议在该事件的处理过程中做重量级的处理\r\n     * @example\r\n     * ```javascript\r\n     * editor.addListener( 'selectionchange', function( editor ) {\r\n     *     console.log('选区发生改变');\r\n     * }\r\n     */\r\n    /**\r\n     * 在所有selectionchange的监听函数执行之前，会触发该事件\r\n     * @module UE\r\n     * @class Editor\r\n     * @event beforeSelectionChange\r\n     * @see UE.Editor:selectionchange\r\n     */\r\n    /**\r\n     * 在所有selectionchange的监听函数执行完之后，会触发该事件\r\n     * @module UE\r\n     * @class Editor\r\n     * @event afterSelectionChange\r\n     * @see UE.Editor:selectionchange\r\n     */\r\n    /**\r\n     * 编辑器内容发生改变时会触发该事件\r\n     * @module UE\r\n     * @class Editor\r\n     * @event contentChange\r\n     */\r\n\r\n\r\n    /**\r\n     * 以默认参数构建一个编辑器实例\r\n     * @constructor\r\n     * @remind 通过 改构造方法实例化的编辑器,不带ui层.需要render到一个容器,编辑器实例才能正常渲染到页面\r\n     * @example\r\n     * ```javascript\r\n     * var editor = new UE.Editor();\r\n     * editor.execCommand('blod');\r\n     * ```\r\n     * @see UE.Config\r\n     */\r\n\r\n    /**\r\n     * 以给定的参数集合创建一个编辑器实例，对于未指定的参数，将应用默认参数。\r\n     * @constructor\r\n     * @remind 通过 改构造方法实例化的编辑器,不带ui层.需要render到一个容器,编辑器实例才能正常渲染到页面\r\n     * @param { Object } setting 创建编辑器的参数\r\n     * @example\r\n     * ```javascript\r\n     * var editor = new UE.Editor();\r\n     * editor.execCommand('blod');\r\n     * ```\r\n     * @see UE.Config\r\n     */\r\n    var Editor = UE.Editor = function (options) {\r\n        var me = this;\r\n        me.uid = uid++;\r\n        EventBase.call(me);\r\n        me.commands = {};\r\n        me.options = utils.extend(utils.clone(options || {}), UEDITOR_CONFIG, true);\r\n        me.shortcutkeys = {};\r\n        me.inputRules = [];\r\n        me.outputRules = [];\r\n        //设置默认的常用属性\r\n        me.setOpt(Editor.defaultOptions(me));\r\n\r\n        /* 尝试异步加载后台配置 */\r\n        me.loadServerConfig();\r\n\r\n        if(!utils.isEmptyObject(UE.I18N)){\r\n            //修改默认的语言类型\r\n            me.options.lang = checkCurLang(UE.I18N);\r\n            UE.plugin.load(me);\r\n            langReadied(me);\r\n\r\n        }else{\r\n            utils.loadFile(document, {\r\n                src: me.options.langPath + me.options.lang + \"/\" + me.options.lang + \".js\",\r\n                tag: \"script\",\r\n                type: \"text/javascript\",\r\n                defer: \"defer\"\r\n            }, function () {\r\n                UE.plugin.load(me);\r\n                langReadied(me);\r\n            });\r\n        }\r\n\r\n        UE.instants['ueditorInstant' + me.uid] = me;\r\n    };\r\n    Editor.prototype = {\r\n         registerCommand : function(name,obj){\r\n            this.commands[name] = obj;\r\n         },\r\n        /**\r\n         * 编辑器对外提供的监听ready事件的接口， 通过调用该方法，达到的效果与监听ready事件是一致的\r\n         * @method ready\r\n         * @param { Function } fn 编辑器ready之后所执行的回调, 如果在注册事件之前编辑器已经ready，将会\r\n         * 立即触发该回调。\r\n         * @remind 需要等待编辑器加载完成后才能执行的代码,可以使用该方法传入\r\n         * @example\r\n         * ```javascript\r\n         * editor.ready( function( editor ) {\r\n         *     editor.setContent('初始化完毕');\r\n         * } );\r\n         * ```\r\n         * @see UE.Editor.event:ready\r\n         */\r\n        ready: function (fn) {\r\n            var me = this;\r\n            if (fn) {\r\n                me.isReady ? fn.apply(me) : me.addListener('ready', fn);\r\n            }\r\n        },\r\n\r\n        /**\r\n         * 该方法是提供给插件里面使用，设置配置项默认值\r\n         * @method setOpt\r\n         * @warning 三处设置配置项的优先级: 实例化时传入参数 > setOpt()设置 > config文件里设置\r\n         * @warning 该方法仅供编辑器插件内部和编辑器初始化时调用，其他地方不能调用。\r\n         * @param { String } key 编辑器的可接受的选项名称\r\n         * @param { * } val  该选项可接受的值\r\n         * @example\r\n         * ```javascript\r\n         * editor.setOpt( 'initContent', '欢迎使用编辑器' );\r\n         * ```\r\n         */\r\n\r\n        /**\r\n         * 该方法是提供给插件里面使用，以{key:value}集合的方式设置插件内用到的配置项默认值\r\n         * @method setOpt\r\n         * @warning 三处设置配置项的优先级: 实例化时传入参数 > setOpt()设置 > config文件里设置\r\n         * @warning 该方法仅供编辑器插件内部和编辑器初始化时调用，其他地方不能调用。\r\n         * @param { Object } options 将要设置的选项的键值对对象\r\n         * @example\r\n         * ```javascript\r\n         * editor.setOpt( {\r\n         *     'initContent': '欢迎使用编辑器'\r\n         * } );\r\n         * ```\r\n         */\r\n        setOpt: function (key, val) {\r\n            var obj = {};\r\n            if (utils.isString(key)) {\r\n                obj[key] = val\r\n            } else {\r\n                obj = key;\r\n            }\r\n            utils.extend(this.options, obj, true);\r\n        },\r\n        getOpt:function(key){\r\n            return this.options[key]\r\n        },\r\n        /**\r\n         * 销毁编辑器实例，使用textarea代替\r\n         * @method destroy\r\n         * @example\r\n         * ```javascript\r\n         * editor.destroy();\r\n         * ```\r\n         */\r\n        destroy: function () {\r\n\r\n            var me = this;\r\n            me.fireEvent('destroy');\r\n            var container = me.container.parentNode;\r\n            var textarea = me.textarea;\r\n            if (!textarea) {\r\n                textarea = document.createElement('textarea');\r\n                container.parentNode.insertBefore(textarea, container);\r\n            } else {\r\n                textarea.style.display = ''\r\n            }\r\n\r\n            textarea.style.width = me.iframe.offsetWidth + 'px';\r\n            textarea.style.height = me.iframe.offsetHeight + 'px';\r\n            textarea.value = me.getContent();\r\n            textarea.id = me.key;\r\n            container.innerHTML = '';\r\n            domUtils.remove(container);\r\n            var key = me.key;\r\n            //trace:2004\r\n            for (var p in me) {\r\n                if (me.hasOwnProperty(p)) {\r\n                    delete this[p];\r\n                }\r\n            }\r\n            UE.delEditor(key);\r\n        },\r\n\r\n        /**\r\n         * 渲染编辑器的DOM到指定容器\r\n         * @method render\r\n         * @param { String } containerId 指定一个容器ID\r\n         * @remind 执行该方法,会触发ready事件\r\n         * @warning 必须且只能调用一次\r\n         */\r\n\r\n        /**\r\n         * 渲染编辑器的DOM到指定容器\r\n         * @method render\r\n         * @param { Element } containerDom 直接指定容器对象\r\n         * @remind 执行该方法,会触发ready事件\r\n         * @warning 必须且只能调用一次\r\n         */\r\n        render: function (container) {\r\n            var me = this,\r\n                options = me.options,\r\n                getStyleValue=function(attr){\r\n                    return parseInt(domUtils.getComputedStyle(container,attr));\r\n                };\r\n            if (utils.isString(container)) {\r\n                container = document.getElementById(container);\r\n            }\r\n            if (container) {\r\n                if(options.initialFrameWidth){\r\n                    options.minFrameWidth = options.initialFrameWidth\r\n                }else{\r\n                    options.minFrameWidth = options.initialFrameWidth = container.offsetWidth;\r\n                }\r\n                if(options.initialFrameHeight){\r\n                    options.minFrameHeight = options.initialFrameHeight\r\n                }else{\r\n                    options.initialFrameHeight = options.minFrameHeight = container.offsetHeight;\r\n                }\r\n\r\n                container.style.width = /%$/.test(options.initialFrameWidth) ?  '100%' : options.initialFrameWidth-\r\n                    getStyleValue(\"padding-left\")- getStyleValue(\"padding-right\") +'px';\r\n                container.style.height = /%$/.test(options.initialFrameHeight) ?  '100%' : options.initialFrameHeight -\r\n                    getStyleValue(\"padding-top\")- getStyleValue(\"padding-bottom\") +'px';\r\n\r\n                container.style.zIndex = options.zIndex;\r\n\r\n                var html = ( ie && browser.version < 9  ? '' : '<!DOCTYPE html>') +\r\n                    '<html xmlns=\\'http://www.w3.org/1999/xhtml\\' class=\\'view\\' ><head>' +\r\n                    '<style type=\\'text/css\\'>' +\r\n                    //设置四周的留边\r\n                    '.view{padding:0;word-wrap:break-word;cursor:text;height:90%;}\\n' +\r\n                    //设置默认字体和字号\r\n                    //font-family不能呢随便改，在safari下fillchar会有解析问题\r\n                    'body{margin:8px;font-family:sans-serif;font-size:16px;}' +\r\n                    //设置段落间距\r\n                    'p{margin:5px 0;}</style>' +\r\n                    ( options.iframeCssUrl ? '<link rel=\\'stylesheet\\' type=\\'text/css\\' href=\\'' + utils.unhtml(options.iframeCssUrl) + '\\'/>' : '' ) +\r\n                    (options.initialStyle ? '<style>' + options.initialStyle + '</style>' : '') +\r\n                    '</head><body class=\\'view\\' ></body>' +\r\n                    '<script type=\\'text/javascript\\' ' + (ie ? 'defer=\\'defer\\'' : '' ) +' id=\\'_initialScript\\'>' +\r\n                    'setTimeout(function(){editor = window.parent.UE.instants[\\'ueditorInstant' + me.uid + '\\'];editor._setup(document);},0);' +\r\n                    'var _tmpScript = document.getElementById(\\'_initialScript\\');_tmpScript.parentNode.removeChild(_tmpScript);</script></html>';\r\n                container.appendChild(domUtils.createElement(document, 'iframe', {\r\n                    id: 'ueditor_' + me.uid,\r\n                    width: \"100%\",\r\n                    height: \"100%\",\r\n                    frameborder: \"0\",\r\n                    //先注释掉了，加的原因忘记了，但开启会直接导致全屏模式下内容多时不会出现滚动条\r\n//                    scrolling :'no',\r\n                    src: 'javascript:void(function(){document.open();' + (options.customDomain && document.domain != location.hostname ?  'document.domain=\"' + document.domain + '\";' : '') +\r\n                        'document.write(\"' + html + '\");document.close();}())'\r\n                }));\r\n                container.style.overflow = 'hidden';\r\n                //解决如果是给定的百分比，会导致高度算不对的问题\r\n                setTimeout(function(){\r\n                    if( /%$/.test(options.initialFrameWidth)){\r\n                        options.minFrameWidth = options.initialFrameWidth = container.offsetWidth;\r\n                        //如果这里给定宽度，会导致ie在拖动窗口大小时，编辑区域不随着变化\r\n//                        container.style.width = options.initialFrameWidth + 'px';\r\n                    }\r\n                    if(/%$/.test(options.initialFrameHeight)){\r\n                        options.minFrameHeight = options.initialFrameHeight = container.offsetHeight;\r\n                        container.style.height = options.initialFrameHeight + 'px';\r\n                    }\r\n                })\r\n            }\r\n        },\r\n\r\n        /**\r\n         * 编辑器初始化\r\n         * @method _setup\r\n         * @private\r\n         * @param { Element } doc 编辑器Iframe中的文档对象\r\n         */\r\n        _setup: function (doc) {\r\n\r\n            var me = this,\r\n                options = me.options;\r\n            if (ie) {\r\n                doc.body.disabled = true;\r\n                doc.body.contentEditable = true;\r\n                doc.body.disabled = false;\r\n            } else {\r\n                doc.body.contentEditable = true;\r\n            }\r\n            doc.body.spellcheck = false;\r\n            me.document = doc;\r\n            me.window = doc.defaultView || doc.parentWindow;\r\n            me.iframe = me.window.frameElement;\r\n            me.body = doc.body;\r\n            me.selection = new dom.Selection(doc);\r\n            //gecko初始化就能得到range,无法判断isFocus了\r\n            var geckoSel;\r\n            if (browser.gecko && (geckoSel = this.selection.getNative())) {\r\n                geckoSel.removeAllRanges();\r\n            }\r\n            this._initEvents();\r\n            //为form提交提供一个隐藏的textarea\r\n            for (var form = this.iframe.parentNode; !domUtils.isBody(form); form = form.parentNode) {\r\n                if (form.tagName == 'FORM') {\r\n                    me.form = form;\r\n                    if(me.options.autoSyncData){\r\n                        domUtils.on(me.window,'blur',function(){\r\n                            setValue(form,me);\r\n                        });\r\n                    }else{\r\n                        domUtils.on(form, 'submit', function () {\r\n                            setValue(this, me);\r\n                        });\r\n                    }\r\n                    break;\r\n                }\r\n            }\r\n            if (options.initialContent) {\r\n                if (options.autoClearinitialContent) {\r\n                    var oldExecCommand = me.execCommand;\r\n                    me.execCommand = function () {\r\n                        me.fireEvent('firstBeforeExecCommand');\r\n                        return oldExecCommand.apply(me, arguments);\r\n                    };\r\n                    this._setDefaultContent(options.initialContent);\r\n                } else\r\n                    this.setContent(options.initialContent, false, true);\r\n            }\r\n\r\n            //编辑器不能为空内容\r\n\r\n            if (domUtils.isEmptyNode(me.body)) {\r\n                me.body.innerHTML = '<p>' + (browser.ie ? '' : '<br/>') + '</p>';\r\n            }\r\n            //如果要求focus, 就把光标定位到内容开始\r\n            if (options.focus) {\r\n                setTimeout(function () {\r\n                    me.focus(me.options.focusInEnd);\r\n                    //如果自动清除开着，就不需要做selectionchange;\r\n                    !me.options.autoClearinitialContent && me._selectionChange();\r\n                }, 0);\r\n            }\r\n            if (!me.container) {\r\n                me.container = this.iframe.parentNode;\r\n            }\r\n            if (options.fullscreen && me.ui) {\r\n                me.ui.setFullScreen(true);\r\n            }\r\n\r\n            try {\r\n                me.document.execCommand('2D-position', false, false);\r\n            } catch (e) {\r\n            }\r\n            try {\r\n                me.document.execCommand('enableInlineTableEditing', false, false);\r\n            } catch (e) {\r\n            }\r\n            try {\r\n                me.document.execCommand('enableObjectResizing', false, false);\r\n            } catch (e) {\r\n            }\r\n\r\n            //挂接快捷键\r\n            me._bindshortcutKeys();\r\n            me.isReady = 1;\r\n            me.fireEvent('ready');\r\n            options.onready && options.onready.call(me);\r\n            if (!browser.ie9below) {\r\n                domUtils.on(me.window, ['blur', 'focus'], function (e) {\r\n                    //chrome下会出现alt+tab切换时，导致选区位置不对\r\n                    if (e.type == 'blur') {\r\n                        me._bakRange = me.selection.getRange();\r\n                        try {\r\n                            me._bakNativeRange = me.selection.getNative().getRangeAt(0);\r\n                            me.selection.getNative().removeAllRanges();\r\n                        } catch (e) {\r\n                            me._bakNativeRange = null;\r\n                        }\r\n\r\n                    } else {\r\n                        try {\r\n                            me._bakRange && me._bakRange.select();\r\n                        } catch (e) {\r\n                        }\r\n                    }\r\n                });\r\n            }\r\n            //trace:1518 ff3.6body不够寛，会导致点击空白处无法获得焦点\r\n            if (browser.gecko && browser.version <= 10902) {\r\n                //修复ff3.6初始化进来，不能点击获得焦点\r\n                me.body.contentEditable = false;\r\n                setTimeout(function () {\r\n                    me.body.contentEditable = true;\r\n                }, 100);\r\n                setInterval(function () {\r\n                    me.body.style.height = me.iframe.offsetHeight - 20 + 'px'\r\n                }, 100)\r\n            }\r\n\r\n            !options.isShow && me.setHide();\r\n            options.readonly && me.setDisabled();\r\n        },\r\n\r\n        /**\r\n         * 同步数据到编辑器所在的form\r\n         * 从编辑器的容器节点向上查找form元素，若找到，就同步编辑内容到找到的form里，为提交数据做准备，主要用于是手动提交的情况\r\n         * 后台取得数据的键值，使用你容器上的name属性，如果没有就使用参数里的textarea项\r\n         * @method sync\r\n         * @example\r\n         * ```javascript\r\n         * editor.sync();\r\n         * form.sumbit(); //form变量已经指向了form元素\r\n         * ```\r\n         */\r\n\r\n        /**\r\n         * 根据传入的formId，在页面上查找要同步数据的表单，若找到，就同步编辑内容到找到的form里，为提交数据做准备\r\n         * 后台取得数据的键值，该键值默认使用给定的编辑器容器的name属性，如果没有name属性则使用参数项里给定的“textarea”项\r\n         * @method sync\r\n         * @param { String } formID 指定一个要同步数据的form的id,编辑器的数据会同步到你指定form下\r\n         */\r\n        sync: function (formId) {\r\n            var me = this,\r\n                form = formId ? document.getElementById(formId) :\r\n                    domUtils.findParent(me.iframe.parentNode, function (node) {\r\n                        return node.tagName == 'FORM'\r\n                    }, true);\r\n            form && setValue(form, me);\r\n        },\r\n\r\n        /**\r\n         * 设置编辑器高度\r\n         * @method setHeight\r\n         * @remind 当配置项autoHeightEnabled为真时,该方法无效\r\n         * @param { Number } number 设置的高度值，纯数值，不带单位\r\n         * @example\r\n         * ```javascript\r\n         * editor.setHeight(number);\r\n         * ```\r\n         */\r\n        setHeight: function (height,notSetHeight) {\r\n            if (height !== parseInt(this.iframe.parentNode.style.height)) {\r\n                this.iframe.parentNode.style.height = height + 'px';\r\n            }\r\n            !notSetHeight && (this.options.minFrameHeight = this.options.initialFrameHeight = height);\r\n            this.body.style.height = height + 'px';\r\n            !notSetHeight && this.trigger('setHeight')\r\n        },\r\n\r\n        /**\r\n         * 为编辑器的编辑命令提供快捷键\r\n         * 这个接口是为插件扩展提供的接口,主要是为新添加的插件，如果需要添加快捷键，所提供的接口\r\n         * @method addshortcutkey\r\n         * @param { Object } keyset 命令名和快捷键键值对对象，多个按钮的快捷键用“＋”分隔\r\n         * @example\r\n         * ```javascript\r\n         * editor.addshortcutkey({\r\n         *     \"Bold\" : \"ctrl+66\",//^B\r\n         *     \"Italic\" : \"ctrl+73\", //^I\r\n         * });\r\n         * ```\r\n         */\r\n        /**\r\n         * 这个接口是为插件扩展提供的接口,主要是为新添加的插件，如果需要添加快捷键，所提供的接口\r\n         * @method addshortcutkey\r\n         * @param { String } cmd 触发快捷键时，响应的命令\r\n         * @param { String } keys 快捷键的字符串，多个按钮用“＋”分隔\r\n         * @example\r\n         * ```javascript\r\n         * editor.addshortcutkey(\"Underline\", \"ctrl+85\"); //^U\r\n         * ```\r\n         */\r\n        addshortcutkey: function (cmd, keys) {\r\n            var obj = {};\r\n            if (keys) {\r\n                obj[cmd] = keys\r\n            } else {\r\n                obj = cmd;\r\n            }\r\n            utils.extend(this.shortcutkeys, obj)\r\n        },\r\n\r\n        /**\r\n         * 对编辑器设置keydown事件监听，绑定快捷键和命令，当快捷键组合触发成功，会响应对应的命令\r\n         * @method _bindshortcutKeys\r\n         * @private\r\n         */\r\n        _bindshortcutKeys: function () {\r\n            var me = this, shortcutkeys = this.shortcutkeys;\r\n            me.addListener('keydown', function (type, e) {\r\n                var keyCode = e.keyCode || e.which;\r\n                for (var i in shortcutkeys) {\r\n                    var tmp = shortcutkeys[i].split(',');\r\n                    for (var t = 0, ti; ti = tmp[t++];) {\r\n                        ti = ti.split(':');\r\n                        var key = ti[0], param = ti[1];\r\n                        if (/^(ctrl)(\\+shift)?\\+(\\d+)$/.test(key.toLowerCase()) || /^(\\d+)$/.test(key)) {\r\n                            if (( (RegExp.$1 == 'ctrl' ? (e.ctrlKey || e.metaKey) : 0)\r\n                                && (RegExp.$2 != \"\" ? e[RegExp.$2.slice(1) + \"Key\"] : 1)\r\n                                && keyCode == RegExp.$3\r\n                                ) ||\r\n                                keyCode == RegExp.$1\r\n                                ) {\r\n                                if (me.queryCommandState(i,param) != -1)\r\n                                    me.execCommand(i, param);\r\n                                domUtils.preventDefault(e);\r\n                            }\r\n                        }\r\n                    }\r\n\r\n                }\r\n            });\r\n        },\r\n\r\n        /**\r\n         * 获取编辑器的内容\r\n         * @method getContent\r\n         * @warning 该方法获取到的是经过编辑器内置的过滤规则进行过滤后得到的内容\r\n         * @return { String } 编辑器的内容字符串, 如果编辑器的内容为空，或者是空的标签内容（如:”&lt;p&gt;&lt;br/&gt;&lt;/p&gt;“）， 则返回空字符串\r\n         * @example\r\n         * ```javascript\r\n         * //编辑器html内容:<p>1<strong>2<em>34</em>5</strong>6</p>\r\n         * var content = editor.getContent(); //返回值:<p>1<strong>2<em>34</em>5</strong>6</p>\r\n         * ```\r\n         */\r\n\r\n        /**\r\n         * 获取编辑器的内容。 可以通过参数定义编辑器内置的判空规则\r\n         * @method getContent\r\n         * @param { Function } fn 自定的判空规则， 要求该方法返回一个boolean类型的值，\r\n         *                      代表当前编辑器的内容是否空，\r\n         *                      如果返回true， 则该方法将直接返回空字符串；如果返回false，则编辑器将返回\r\n         *                      经过内置过滤规则处理后的内容。\r\n         * @remind 该方法在处理包含有初始化内容的时候能起到很好的作用。\r\n         * @warning 该方法获取到的是经过编辑器内置的过滤规则进行过滤后得到的内容\r\n         * @return { String } 编辑器的内容字符串\r\n         * @example\r\n         * ```javascript\r\n         * // editor 是一个编辑器的实例\r\n         * var content = editor.getContent( function ( editor ) {\r\n         *      return editor.body.innerHTML === '欢迎使用UEditor'; //返回空字符串\r\n         * } );\r\n         * ```\r\n         */\r\n        getContent: function (cmd, fn,notSetCursor,ignoreBlank,formatter) {\r\n            var me = this;\r\n            if (cmd && utils.isFunction(cmd)) {\r\n                fn = cmd;\r\n                cmd = '';\r\n            }\r\n            if (fn ? !fn() : !this.hasContents()) {\r\n                return '';\r\n            }\r\n            me.fireEvent('beforegetcontent');\r\n            var root = UE.htmlparser(me.body.innerHTML,ignoreBlank);\r\n            me.filterOutputRule(root);\r\n            me.fireEvent('aftergetcontent', cmd,root);\r\n            return  root.toHtml(formatter);\r\n        },\r\n\r\n        /**\r\n         * 取得完整的html代码，可以直接显示成完整的html文档\r\n         * @method getAllHtml\r\n         * @return { String } 编辑器的内容html文档字符串\r\n         * @eaxmple\r\n         * ```javascript\r\n         * editor.getAllHtml(); //返回格式大致是: <html><head>...</head><body>...</body></html>\r\n         * ```\r\n         */\r\n        getAllHtml: function () {\r\n            var me = this,\r\n                headHtml = [],\r\n                html = '';\r\n            me.fireEvent('getAllHtml', headHtml);\r\n            if (browser.ie && browser.version > 8) {\r\n                var headHtmlForIE9 = '';\r\n                utils.each(me.document.styleSheets, function (si) {\r\n                    headHtmlForIE9 += ( si.href ? '<link rel=\"stylesheet\" type=\"text/css\" href=\"' + si.href + '\" />' : '<style>' + si.cssText + '</style>');\r\n                });\r\n                utils.each(me.document.getElementsByTagName('script'), function (si) {\r\n                    headHtmlForIE9 += si.outerHTML;\r\n                });\r\n\r\n            }\r\n            return '<html><head>' + (me.options.charset ? '<meta http-equiv=\"Content-Type\" content=\"text/html; charset=' + me.options.charset + '\"/>' : '')\r\n                + (headHtmlForIE9 || me.document.getElementsByTagName('head')[0].innerHTML) + headHtml.join('\\n') + '</head>'\r\n                + '<body ' + (ie && browser.version < 9 ? 'class=\"view\"' : '') + '>' + me.getContent(null, null, true) + '</body></html>';\r\n        },\r\n\r\n        /**\r\n         * 得到编辑器的纯文本内容，但会保留段落格式\r\n         * @method getPlainTxt\r\n         * @return { String } 编辑器带段落格式的纯文本内容字符串\r\n         * @example\r\n         * ```javascript\r\n         * //编辑器html内容:<p><strong>1</strong></p><p><strong>2</strong></p>\r\n         * console.log(editor.getPlainTxt()); //输出:\"1\\n2\\n\r\n         * ```\r\n         */\r\n        getPlainTxt: function () {\r\n            var reg = new RegExp(domUtils.fillChar, 'g'),\r\n                html = this.body.innerHTML.replace(/[\\n\\r]/g, '');//ie要先去了\\n在处理\r\n            html = html.replace(/<(p|div)[^>]*>(<br\\/?>|&nbsp;)<\\/\\1>/gi, '\\n')\r\n                .replace(/<br\\/?>/gi, '\\n')\r\n                .replace(/<[^>/]+>/g, '')\r\n                .replace(/(\\n)?<\\/([^>]+)>/g, function (a, b, c) {\r\n                    return dtd.$block[c] ? '\\n' : b ? b : '';\r\n                });\r\n            //取出来的空格会有c2a0会变成乱码，处理这种情况\\u00a0\r\n            return html.replace(reg, '').replace(/\\u00a0/g, ' ').replace(/&nbsp;/g, ' ');\r\n        },\r\n\r\n        /**\r\n         * 获取编辑器中的纯文本内容,没有段落格式\r\n         * @method getContentTxt\r\n         * @return { String } 编辑器不带段落格式的纯文本内容字符串\r\n         * @example\r\n         * ```javascript\r\n         * //编辑器html内容:<p><strong>1</strong></p><p><strong>2</strong></p>\r\n         * console.log(editor.getPlainTxt()); //输出:\"12\r\n         * ```\r\n         */\r\n        getContentTxt: function () {\r\n            var reg = new RegExp(domUtils.fillChar, 'g');\r\n            //取出来的空格会有c2a0会变成乱码，处理这种情况\\u00a0\r\n            return this.body[browser.ie ? 'innerText' : 'textContent'].replace(reg, '').replace(/\\u00a0/g, ' ');\r\n        },\r\n\r\n        /**\r\n         * 设置编辑器的内容，可修改编辑器当前的html内容\r\n         * @method setContent\r\n         * @warning 通过该方法插入的内容，是经过编辑器内置的过滤规则进行过滤后得到的内容\r\n         * @warning 该方法会触发selectionchange事件\r\n         * @param { String } html 要插入的html内容\r\n         * @example\r\n         * ```javascript\r\n         * editor.getContent('<p>test</p>');\r\n         * ```\r\n         */\r\n\r\n        /**\r\n         * 设置编辑器的内容，可修改编辑器当前的html内容\r\n         * @method setContent\r\n         * @warning 通过该方法插入的内容，是经过编辑器内置的过滤规则进行过滤后得到的内容\r\n         * @warning 该方法会触发selectionchange事件\r\n         * @param { String } html 要插入的html内容\r\n         * @param { Boolean } isAppendTo 若传入true，不清空原来的内容，在最后插入内容，否则，清空内容再插入\r\n         * @example\r\n         * ```javascript\r\n         * //假设设置前的编辑器内容是 <p>old text</p>\r\n         * editor.setContent('<p>new text</p>', true); //插入的结果是<p>old text</p><p>new text</p>\r\n         * ```\r\n         */\r\n        setContent: function (html, isAppendTo, notFireSelectionchange) {\r\n            var me = this;\r\n\r\n            me.fireEvent('beforesetcontent', html);\r\n            var root = UE.htmlparser(html);\r\n            me.filterInputRule(root);\r\n            html = root.toHtml();\r\n\r\n            me.body.innerHTML = (isAppendTo ? me.body.innerHTML : '') + html;\r\n\r\n\r\n            function isCdataDiv(node){\r\n                return  node.tagName == 'DIV' && node.getAttribute('cdata_tag');\r\n            }\r\n            //给文本或者inline节点套p标签\r\n            if (me.options.enterTag == 'p') {\r\n\r\n                var child = this.body.firstChild, tmpNode;\r\n                if (!child || child.nodeType == 1 &&\r\n                    (dtd.$cdata[child.tagName] || isCdataDiv(child) ||\r\n                        domUtils.isCustomeNode(child)\r\n                        )\r\n                    && child === this.body.lastChild) {\r\n                    this.body.innerHTML = '<p>' + (browser.ie ? '&nbsp;' : '<br/>') + '</p>' + this.body.innerHTML;\r\n\r\n                } else {\r\n                    var p = me.document.createElement('p');\r\n                    while (child) {\r\n                        while (child && (child.nodeType == 3 || child.nodeType == 1 && dtd.p[child.tagName] && !dtd.$cdata[child.tagName])) {\r\n                            tmpNode = child.nextSibling;\r\n                            p.appendChild(child);\r\n                            child = tmpNode;\r\n                        }\r\n                        if (p.firstChild) {\r\n                            if (!child) {\r\n                                me.body.appendChild(p);\r\n                                break;\r\n                            } else {\r\n                                child.parentNode.insertBefore(p, child);\r\n                                p = me.document.createElement('p');\r\n                            }\r\n                        }\r\n                        child = child.nextSibling;\r\n                    }\r\n                }\r\n            }\r\n            me.fireEvent('aftersetcontent');\r\n            me.fireEvent('contentchange');\r\n\r\n            !notFireSelectionchange && me._selectionChange();\r\n            //清除保存的选区\r\n            me._bakRange = me._bakIERange = me._bakNativeRange = null;\r\n            //trace:1742 setContent后gecko能得到焦点问题\r\n            var geckoSel;\r\n            if (browser.gecko && (geckoSel = this.selection.getNative())) {\r\n                geckoSel.removeAllRanges();\r\n            }\r\n            if(me.options.autoSyncData){\r\n                me.form && setValue(me.form,me);\r\n            }\r\n        },\r\n\r\n        /**\r\n         * 让编辑器获得焦点，默认focus到编辑器头部\r\n         * @method focus\r\n         * @example\r\n         * ```javascript\r\n         * editor.focus()\r\n         * ```\r\n         */\r\n\r\n        /**\r\n         * 让编辑器获得焦点，toEnd确定focus位置\r\n         * @method focus\r\n         * @param { Boolean } toEnd 默认focus到编辑器头部，toEnd为true时focus到内容尾部\r\n         * @example\r\n         * ```javascript\r\n         * editor.focus(true)\r\n         * ```\r\n         */\r\n        focus: function (toEnd) {\r\n            try {\r\n                var me = this,\r\n                    rng = me.selection.getRange();\r\n                if (toEnd) {\r\n                    var node = me.body.lastChild;\r\n                    if(node && node.nodeType == 1 && !dtd.$empty[node.tagName]){\r\n                        if(domUtils.isEmptyBlock(node)){\r\n                            rng.setStartAtFirst(node)\r\n                        }else{\r\n                            rng.setStartAtLast(node)\r\n                        }\r\n                        rng.collapse(true);\r\n                    }\r\n                    rng.setCursor(true);\r\n                } else {\r\n                    if(!rng.collapsed && domUtils.isBody(rng.startContainer) && rng.startOffset == 0){\r\n\r\n                        var node = me.body.firstChild;\r\n                        if(node && node.nodeType == 1 && !dtd.$empty[node.tagName]){\r\n                            rng.setStartAtFirst(node).collapse(true);\r\n                        }\r\n                    }\r\n\r\n                    rng.select(true);\r\n\r\n                }\r\n                this.fireEvent('focus selectionchange');\r\n            } catch (e) {\r\n            }\r\n\r\n        },\r\n        isFocus:function(){\r\n            return this.selection.isFocus();\r\n        },\r\n        blur:function(){\r\n            var sel = this.selection.getNative();\r\n            if(sel.empty && browser.ie){\r\n                var nativeRng = document.body.createTextRange();\r\n                nativeRng.moveToElementText(document.body);\r\n                nativeRng.collapse(true);\r\n                nativeRng.select();\r\n                sel.empty()\r\n            }else{\r\n                sel.removeAllRanges()\r\n            }\r\n\r\n            //this.fireEvent('blur selectionchange');\r\n        },\r\n        /**\r\n         * 初始化UE事件及部分事件代理\r\n         * @method _initEvents\r\n         * @private\r\n         */\r\n        _initEvents: function () {\r\n            var me = this,\r\n                doc = me.document,\r\n                win = me.window;\r\n            me._proxyDomEvent = utils.bind(me._proxyDomEvent, me);\r\n            domUtils.on(doc, ['click', 'contextmenu', 'mousedown', 'keydown', 'keyup', 'keypress', 'mouseup', 'mouseover', 'mouseout', 'selectstart'], me._proxyDomEvent);\r\n            domUtils.on(win, ['focus', 'blur'], me._proxyDomEvent);\r\n            domUtils.on(me.body,'drop',function(e){\r\n                //阻止ff下默认的弹出新页面打开图片\r\n                if(browser.gecko && e.stopPropagation) { e.stopPropagation(); }\r\n                me.fireEvent('contentchange')\r\n            });\r\n            domUtils.on(doc, ['mouseup', 'keydown'], function (evt) {\r\n                //特殊键不触发selectionchange\r\n                if (evt.type == 'keydown' && (evt.ctrlKey || evt.metaKey || evt.shiftKey || evt.altKey)) {\r\n                    return;\r\n                }\r\n                if (evt.button == 2)return;\r\n                me._selectionChange(250, evt);\r\n            });\r\n        },\r\n        /**\r\n         * 触发事件代理\r\n         * @method _proxyDomEvent\r\n         * @private\r\n         * @return { * } fireEvent的返回值\r\n         * @see UE.EventBase:fireEvent(String)\r\n         */\r\n        _proxyDomEvent: function (evt) {\r\n            if(this.fireEvent('before' + evt.type.replace(/^on/, '').toLowerCase()) === false){\r\n                return false;\r\n            }\r\n            if(this.fireEvent(evt.type.replace(/^on/, ''), evt) === false){\r\n                return false;\r\n            }\r\n            return this.fireEvent('after' + evt.type.replace(/^on/, '').toLowerCase())\r\n        },\r\n        /**\r\n         * 变化选区\r\n         * @method _selectionChange\r\n         * @private\r\n         */\r\n        _selectionChange: function (delay, evt) {\r\n            var me = this;\r\n            //有光标才做selectionchange 为了解决未focus时点击source不能触发更改工具栏状态的问题（source命令notNeedUndo=1）\r\n//            if ( !me.selection.isFocus() ){\r\n//                return;\r\n//            }\r\n\r\n\r\n            var hackForMouseUp = false;\r\n            var mouseX, mouseY;\r\n            if (browser.ie && browser.version < 9 && evt && evt.type == 'mouseup') {\r\n                var range = this.selection.getRange();\r\n                if (!range.collapsed) {\r\n                    hackForMouseUp = true;\r\n                    mouseX = evt.clientX;\r\n                    mouseY = evt.clientY;\r\n                }\r\n            }\r\n            clearTimeout(_selectionChangeTimer);\r\n            _selectionChangeTimer = setTimeout(function () {\r\n                if (!me.selection || !me.selection.getNative()) {\r\n                    return;\r\n                }\r\n                //修复一个IE下的bug: 鼠标点击一段已选择的文本中间时，可能在mouseup后的一段时间内取到的range是在selection的type为None下的错误值.\r\n                //IE下如果用户是拖拽一段已选择文本，则不会触发mouseup事件，所以这里的特殊处理不会对其有影响\r\n                var ieRange;\r\n                if (hackForMouseUp && me.selection.getNative().type == 'None') {\r\n                    ieRange = me.document.body.createTextRange();\r\n                    try {\r\n                        ieRange.moveToPoint(mouseX, mouseY);\r\n                    } catch (ex) {\r\n                        ieRange = null;\r\n                    }\r\n                }\r\n                var bakGetIERange;\r\n                if (ieRange) {\r\n                    bakGetIERange = me.selection.getIERange;\r\n                    me.selection.getIERange = function () {\r\n                        return ieRange;\r\n                    };\r\n                }\r\n                me.selection.cache();\r\n                if (bakGetIERange) {\r\n                    me.selection.getIERange = bakGetIERange;\r\n                }\r\n                if (me.selection._cachedRange && me.selection._cachedStartElement) {\r\n                    me.fireEvent('beforeselectionchange');\r\n                    // 第二个参数causeByUi为true代表由用户交互造成的selectionchange.\r\n                    me.fireEvent('selectionchange', !!evt);\r\n                    me.fireEvent('afterselectionchange');\r\n                    me.selection.clear();\r\n                }\r\n            }, delay || 50);\r\n        },\r\n\r\n        /**\r\n         * 执行编辑命令\r\n         * @method _callCmdFn\r\n         * @private\r\n         * @param { String } fnName 函数名称\r\n         * @param { * } args 传给命令函数的参数\r\n         * @return { * } 返回命令函数运行的返回值\r\n         */\r\n        _callCmdFn: function (fnName, args) {\r\n            var cmdName = args[0].toLowerCase(),\r\n                cmd, cmdFn;\r\n            cmd = this.commands[cmdName] || UE.commands[cmdName];\r\n            cmdFn = cmd && cmd[fnName];\r\n            //没有querycommandstate或者没有command的都默认返回0\r\n            if ((!cmd || !cmdFn) && fnName == 'queryCommandState') {\r\n                return 0;\r\n            } else if (cmdFn) {\r\n                return cmdFn.apply(this, args);\r\n            }\r\n        },\r\n\r\n        /**\r\n         * 执行编辑命令cmdName，完成富文本编辑效果\r\n         * @method execCommand\r\n         * @param { String } cmdName 需要执行的命令\r\n         * @remind 具体命令的使用请参考<a href=\"#COMMAND.LIST\">命令列表</a>\r\n         * @return { * } 返回命令函数运行的返回值\r\n         * @example\r\n         * ```javascript\r\n         * editor.execCommand(cmdName);\r\n         * ```\r\n         */\r\n        execCommand: function (cmdName) {\r\n            cmdName = cmdName.toLowerCase();\r\n            var me = this,\r\n                result,\r\n                cmd = me.commands[cmdName] || UE.commands[cmdName];\r\n            if (!cmd || !cmd.execCommand) {\r\n                return null;\r\n            }\r\n            if (!cmd.notNeedUndo && !me.__hasEnterExecCommand) {\r\n                me.__hasEnterExecCommand = true;\r\n                if (me.queryCommandState.apply(me,arguments) != -1) {\r\n                    me.fireEvent('saveScene');\r\n                    me.fireEvent.apply(me, ['beforeexeccommand', cmdName].concat(arguments));\r\n                    result = this._callCmdFn('execCommand', arguments);\r\n                    //保存场景时，做了内容对比，再看是否进行contentchange触发，这里多触发了一次，去掉\r\n//                    (!cmd.ignoreContentChange && !me._ignoreContentChange) && me.fireEvent('contentchange');\r\n                    me.fireEvent.apply(me, ['afterexeccommand', cmdName].concat(arguments));\r\n                    me.fireEvent('saveScene');\r\n                }\r\n                me.__hasEnterExecCommand = false;\r\n            } else {\r\n                result = this._callCmdFn('execCommand', arguments);\r\n                (!me.__hasEnterExecCommand && !cmd.ignoreContentChange && !me._ignoreContentChange) && me.fireEvent('contentchange')\r\n            }\r\n            (!me.__hasEnterExecCommand && !cmd.ignoreContentChange && !me._ignoreContentChange) && me._selectionChange();\r\n            return result;\r\n        },\r\n\r\n        /**\r\n         * 根据传入的command命令，查选编辑器当前的选区，返回命令的状态\r\n         * @method  queryCommandState\r\n         * @param { String } cmdName 需要查询的命令名称\r\n         * @remind 具体命令的使用请参考<a href=\"#COMMAND.LIST\">命令列表</a>\r\n         * @return { Number } number 返回放前命令的状态，返回值三种情况：(-1|0|1)\r\n         * @example\r\n         * ```javascript\r\n         * editor.queryCommandState(cmdName)  => (-1|0|1)\r\n         * ```\r\n         * @see COMMAND.LIST\r\n         */\r\n        queryCommandState: function (cmdName) {\r\n            return this._callCmdFn('queryCommandState', arguments);\r\n        },\r\n\r\n        /**\r\n         * 根据传入的command命令，查选编辑器当前的选区，根据命令返回相关的值\r\n         * @method queryCommandValue\r\n         * @param { String } cmdName 需要查询的命令名称\r\n         * @remind 具体命令的使用请参考<a href=\"#COMMAND.LIST\">命令列表</a>\r\n         * @remind 只有部分插件有此方法\r\n         * @return { * } 返回每个命令特定的当前状态值\r\n         * @grammar editor.queryCommandValue(cmdName)  =>  {*}\r\n         * @see COMMAND.LIST\r\n         */\r\n        queryCommandValue: function (cmdName) {\r\n            return this._callCmdFn('queryCommandValue', arguments);\r\n        },\r\n\r\n        /**\r\n         * 检查编辑区域中是否有内容\r\n         * @method  hasContents\r\n         * @remind 默认有文本内容，或者有以下节点都不认为是空\r\n         * table,ul,ol,dl,iframe,area,base,col,hr,img,embed,input,link,meta,param\r\n         * @return { Boolean } 检查有内容返回true，否则返回false\r\n         * @example\r\n         * ```javascript\r\n         * editor.hasContents()\r\n         * ```\r\n         */\r\n\r\n        /**\r\n         * 检查编辑区域中是否有内容，若包含参数tags中的节点类型，直接返回true\r\n         * @method  hasContents\r\n         * @param { Array } tags 传入数组判断时用到的节点类型\r\n         * @return { Boolean } 若文档中包含tags数组里对应的tag，返回true，否则返回false\r\n         * @example\r\n         * ```javascript\r\n         * editor.hasContents(['span']);\r\n         * ```\r\n         */\r\n        hasContents: function (tags) {\r\n            if (tags) {\r\n                for (var i = 0, ci; ci = tags[i++];) {\r\n                    if (this.document.getElementsByTagName(ci).length > 0) {\r\n                        return true;\r\n                    }\r\n                }\r\n            }\r\n            if (!domUtils.isEmptyBlock(this.body)) {\r\n                return true\r\n            }\r\n            //随时添加,定义的特殊标签如果存在，不能认为是空\r\n            tags = ['div'];\r\n            for (i = 0; ci = tags[i++];) {\r\n                var nodes = domUtils.getElementsByTagName(this.document, ci);\r\n                for (var n = 0, cn; cn = nodes[n++];) {\r\n                    if (domUtils.isCustomeNode(cn)) {\r\n                        return true;\r\n                    }\r\n                }\r\n            }\r\n            return false;\r\n        },\r\n\r\n        /**\r\n         * 重置编辑器，可用来做多个tab使用同一个编辑器实例\r\n         * @method  reset\r\n         * @remind 此方法会清空编辑器内容，清空回退列表，会触发reset事件\r\n         * @example\r\n         * ```javascript\r\n         * editor.reset()\r\n         * ```\r\n         */\r\n        reset: function () {\r\n            this.fireEvent('reset');\r\n        },\r\n\r\n        /**\r\n         * 设置当前编辑区域可以编辑\r\n         * @method setEnabled\r\n         * @example\r\n         * ```javascript\r\n         * editor.setEnabled()\r\n         * ```\r\n         */\r\n        setEnabled: function () {\r\n            var me = this, range;\r\n            if (me.body.contentEditable == 'false') {\r\n                me.body.contentEditable = true;\r\n                range = me.selection.getRange();\r\n                //有可能内容丢失了\r\n                try {\r\n                    range.moveToBookmark(me.lastBk);\r\n                    delete me.lastBk\r\n                } catch (e) {\r\n                    range.setStartAtFirst(me.body).collapse(true)\r\n                }\r\n                range.select(true);\r\n                if (me.bkqueryCommandState) {\r\n                    me.queryCommandState = me.bkqueryCommandState;\r\n                    delete me.bkqueryCommandState;\r\n                }\r\n                if (me.bkqueryCommandValue) {\r\n                    me.queryCommandValue = me.bkqueryCommandValue;\r\n                    delete me.bkqueryCommandValue;\r\n                }\r\n                me.fireEvent('selectionchange');\r\n            }\r\n        },\r\n        enable: function () {\r\n            return this.setEnabled();\r\n        },\r\n\r\n        /** 设置当前编辑区域不可编辑\r\n         * @method setDisabled\r\n         */\r\n\r\n        /** 设置当前编辑区域不可编辑,except中的命令除外\r\n         * @method setDisabled\r\n         * @param { String } except 例外命令的字符串\r\n         * @remind 即使设置了disable，此处配置的例外命令仍然可以执行\r\n         * @example\r\n         * ```javascript\r\n         * editor.setDisabled('bold'); //禁用工具栏中除加粗之外的所有功能\r\n         * ```\r\n         */\r\n\r\n        /** 设置当前编辑区域不可编辑,except中的命令除外\r\n         * @method setDisabled\r\n         * @param { Array } except 例外命令的字符串数组，数组中的命令仍然可以执行\r\n         * @remind 即使设置了disable，此处配置的例外命令仍然可以执行\r\n         * @example\r\n         * ```javascript\r\n         * editor.setDisabled(['bold','insertimage']); //禁用工具栏中除加粗和插入图片之外的所有功能\r\n         * ```\r\n         */\r\n        setDisabled: function (except) {\r\n            var me = this;\r\n            except = except ? utils.isArray(except) ? except : [except] : [];\r\n            if (me.body.contentEditable == 'true') {\r\n                if (!me.lastBk) {\r\n                    me.lastBk = me.selection.getRange().createBookmark(true);\r\n                }\r\n                me.body.contentEditable = false;\r\n                me.bkqueryCommandState = me.queryCommandState;\r\n                me.bkqueryCommandValue = me.queryCommandValue;\r\n                me.queryCommandState = function (type) {\r\n                    if (utils.indexOf(except, type) != -1) {\r\n                        return me.bkqueryCommandState.apply(me, arguments);\r\n                    }\r\n                    return -1;\r\n                };\r\n                me.queryCommandValue = function (type) {\r\n                    if (utils.indexOf(except, type) != -1) {\r\n                        return me.bkqueryCommandValue.apply(me, arguments);\r\n                    }\r\n                    return null;\r\n                };\r\n                me.fireEvent('selectionchange');\r\n            }\r\n        },\r\n        disable: function (except) {\r\n            return this.setDisabled(except);\r\n        },\r\n\r\n        /**\r\n         * 设置默认内容\r\n         * @method _setDefaultContent\r\n         * @private\r\n         * @param  { String } cont 要存入的内容\r\n         */\r\n        _setDefaultContent: function () {\r\n            function clear() {\r\n                var me = this;\r\n                if (me.document.getElementById('initContent')) {\r\n                    me.body.innerHTML = '<p>' + (ie ? '' : '<br/>') + '</p>';\r\n                    me.removeListener('firstBeforeExecCommand focus', clear);\r\n                    setTimeout(function () {\r\n                        me.focus();\r\n                        me._selectionChange();\r\n                    }, 0)\r\n                }\r\n            }\r\n\r\n            return function (cont) {\r\n                var me = this;\r\n                me.body.innerHTML = '<p id=\"initContent\">' + cont + '</p>';\r\n\r\n                me.addListener('firstBeforeExecCommand focus', clear);\r\n            }\r\n        }(),\r\n\r\n        /**\r\n         * 显示编辑器\r\n         * @method setShow\r\n         * @example\r\n         * ```javascript\r\n         * editor.setShow()\r\n         * ```\r\n         */\r\n        setShow: function () {\r\n            var me = this, range = me.selection.getRange();\r\n            if (me.container.style.display == 'none') {\r\n                //有可能内容丢失了\r\n                try {\r\n                    range.moveToBookmark(me.lastBk);\r\n                    delete me.lastBk\r\n                } catch (e) {\r\n                    range.setStartAtFirst(me.body).collapse(true)\r\n                }\r\n                //ie下focus实效，所以做了个延迟\r\n                setTimeout(function () {\r\n                    range.select(true);\r\n                }, 100);\r\n                me.container.style.display = '';\r\n            }\r\n\r\n        },\r\n        show: function () {\r\n            return this.setShow();\r\n        },\r\n        /**\r\n         * 隐藏编辑器\r\n         * @method setHide\r\n         * @example\r\n         * ```javascript\r\n         * editor.setHide()\r\n         * ```\r\n         */\r\n        setHide: function () {\r\n            var me = this;\r\n            if (!me.lastBk) {\r\n                me.lastBk = me.selection.getRange().createBookmark(true);\r\n            }\r\n            me.container.style.display = 'none'\r\n        },\r\n        hide: function () {\r\n            return this.setHide();\r\n        },\r\n\r\n        /**\r\n         * 根据指定的路径，获取对应的语言资源\r\n         * @method getLang\r\n         * @param { String } path 路径根据的是lang目录下的语言文件的路径结构\r\n         * @return { Object | String } 根据路径返回语言资源的Json格式对象或者语言字符串\r\n         * @example\r\n         * ```javascript\r\n         * editor.getLang('contextMenu.delete'); //如果当前是中文，那返回是的是'删除'\r\n         * ```\r\n         */\r\n        getLang: function (path) {\r\n            // HaoChuan9421\r\n            if(!this.options){\r\n                return '';\r\n            }\r\n            var lang = UE.I18N[this.options.lang];\r\n            if (!lang) {\r\n                throw Error(\"not import language file\");\r\n            }\r\n            path = (path || \"\").split(\".\");\r\n            for (var i = 0, ci; ci = path[i++];) {\r\n                lang = lang[ci];\r\n                if (!lang)break;\r\n            }\r\n            return lang;\r\n        },\r\n\r\n        /**\r\n         * 计算编辑器html内容字符串的长度\r\n         * @method  getContentLength\r\n         * @return { Number } 返回计算的长度\r\n         * @example\r\n         * ```javascript\r\n         * //编辑器html内容<p><strong>132</strong></p>\r\n         * editor.getContentLength() //返回27\r\n         * ```\r\n         */\r\n        /**\r\n         * 计算编辑器当前纯文本内容的长度\r\n         * @method  getContentLength\r\n         * @param { Boolean } ingoneHtml 传入true时，只按照纯文本来计算\r\n         * @return { Number } 返回计算的长度，内容中有hr/img/iframe标签，长度加1\r\n         * @example\r\n         * ```javascript\r\n         * //编辑器html内容<p><strong>132</strong></p>\r\n         * editor.getContentLength() //返回3\r\n         * ```\r\n         */\r\n        getContentLength: function (ingoneHtml, tagNames) {\r\n            var count = this.getContent(false,false,true).length;\r\n            if (ingoneHtml) {\r\n                tagNames = (tagNames || []).concat([ 'hr', 'img', 'iframe']);\r\n                count = this.getContentTxt().replace(/[\\t\\r\\n]+/g, '').length;\r\n                for (var i = 0, ci; ci = tagNames[i++];) {\r\n                    count += this.document.getElementsByTagName(ci).length;\r\n                }\r\n            }\r\n            return count;\r\n        },\r\n\r\n        /**\r\n         * 注册输入过滤规则\r\n         * @method  addInputRule\r\n         * @param { Function } rule 要添加的过滤规则\r\n         * @example\r\n         * ```javascript\r\n         * editor.addInputRule(function(root){\r\n         *   $.each(root.getNodesByTagName('div'),function(i,node){\r\n         *       node.tagName=\"p\";\r\n         *   });\r\n         * });\r\n         * ```\r\n         */\r\n        addInputRule: function (rule) {\r\n            this.inputRules.push(rule);\r\n        },\r\n\r\n        /**\r\n         * 执行注册的过滤规则\r\n         * @method  filterInputRule\r\n         * @param { UE.uNode } root 要过滤的uNode节点\r\n         * @remind 执行editor.setContent方法和执行'inserthtml'命令后，会运行该过滤函数\r\n         * @example\r\n         * ```javascript\r\n         * editor.filterInputRule(editor.body);\r\n         * ```\r\n         * @see UE.Editor:addInputRule\r\n         */\r\n        filterInputRule: function (root) {\r\n            for (var i = 0, ci; ci = this.inputRules[i++];) {\r\n                ci.call(this, root)\r\n            }\r\n        },\r\n\r\n        /**\r\n         * 注册输出过滤规则\r\n         * @method  addOutputRule\r\n         * @param { Function } rule 要添加的过滤规则\r\n         * @example\r\n         * ```javascript\r\n         * editor.addOutputRule(function(root){\r\n         *   $.each(root.getNodesByTagName('p'),function(i,node){\r\n         *       node.tagName=\"div\";\r\n         *   });\r\n         * });\r\n         * ```\r\n         */\r\n        addOutputRule: function (rule) {\r\n            this.outputRules.push(rule)\r\n        },\r\n\r\n        /**\r\n         * 根据输出过滤规则，过滤编辑器内容\r\n         * @method  filterOutputRule\r\n         * @remind 执行editor.getContent方法的时候，会先运行该过滤函数\r\n         * @param { UE.uNode } root 要过滤的uNode节点\r\n         * @example\r\n         * ```javascript\r\n         * editor.filterOutputRule(editor.body);\r\n         * ```\r\n         * @see UE.Editor:addOutputRule\r\n         */\r\n        filterOutputRule: function (root) {\r\n            for (var i = 0, ci; ci = this.outputRules[i++];) {\r\n                ci.call(this, root)\r\n            }\r\n        },\r\n\r\n        /**\r\n         * 根据action名称获取请求的路径\r\n         * @method  getActionUrl\r\n         * @remind 假如没有设置serverUrl,会根据imageUrl设置默认的controller路径\r\n         * @param { String } action action名称\r\n         * @example\r\n         * ```javascript\r\n         * editor.getActionUrl('config'); //返回 \"/ueditor/php/controller.php?action=config\"\r\n         * editor.getActionUrl('image'); //返回 \"/ueditor/php/controller.php?action=uplaodimage\"\r\n         * editor.getActionUrl('scrawl'); //返回 \"/ueditor/php/controller.php?action=uplaodscrawl\"\r\n         * editor.getActionUrl('imageManager'); //返回 \"/ueditor/php/controller.php?action=listimage\"\r\n         * ```\r\n         */\r\n        getActionUrl: function(action){\r\n            var actionName = this.getOpt(action) || action,\r\n                imageUrl = this.getOpt('imageUrl'),\r\n                serverUrl = this.getOpt('serverUrl');\r\n\r\n            if(!serverUrl && imageUrl) {\r\n                serverUrl = imageUrl.replace(/^(.*[\\/]).+([\\.].+)$/, '$1controller$2');\r\n            }\r\n\r\n            if(serverUrl) {\r\n                serverUrl = serverUrl + (serverUrl.indexOf('?') == -1 ? '?':'&') + 'action=' + (actionName || '');\r\n                return utils.formatUrl(serverUrl);\r\n            } else {\r\n                return '';\r\n            }\r\n        }\r\n    };\r\n    utils.inherits(Editor, EventBase);\r\n})();\r\n\r\n\r\n// core/Editor.defaultoptions.js\r\n//维护编辑器一下默认的不在插件中的配置项\r\nUE.Editor.defaultOptions = function(editor){\r\n\r\n    var _url = editor.options.UEDITOR_HOME_URL;\r\n    return {\r\n        isShow: true,\r\n        initialContent: '',\r\n        initialStyle:'',\r\n        autoClearinitialContent: false,\r\n        iframeCssUrl: _url + 'themes/iframe.css',\r\n        textarea: 'editorValue',\r\n        focus: false,\r\n        focusInEnd: true,\r\n        autoClearEmptyNode: true,\r\n        fullscreen: false,\r\n        readonly: false,\r\n        zIndex: 99999,\r\n        imagePopup: true,\r\n        enterTag: 'p',\r\n        customDomain: false,\r\n        lang: 'zh-cn',\r\n        langPath: _url + 'lang/',\r\n        theme: 'default',\r\n        themePath: _url + 'themes/',\r\n        allHtmlEnabled: false,\r\n        scaleEnabled: false,\r\n        tableNativeEditInFF: false,\r\n        autoSyncData : true,\r\n        fileNameFormat: '{time}{rand:6}'\r\n    }\r\n};\r\n\r\n// core/loadconfig.js\r\n(function(){\r\n\r\n    UE.Editor.prototype.loadServerConfig = function(){\r\n        var me = this;\r\n        setTimeout(function(){\r\n            try{\r\n                me.options.imageUrl && me.setOpt('serverUrl', me.options.imageUrl.replace(/^(.*[\\/]).+([\\.].+)$/, '$1controller$2'));\r\n\r\n                var configUrl = me.getActionUrl('config'),\r\n                    isJsonp = utils.isCrossDomainUrl(configUrl);\r\n\r\n                /* 发出ajax请求 */\r\n                me._serverConfigLoaded = false;\r\n\r\n                configUrl && UE.ajax.request(configUrl,{\r\n                    'method': 'GET',\r\n                    'dataType': isJsonp ? 'jsonp':'',\r\n                    'onsuccess':function(r){\r\n                        try {\r\n                            var config = isJsonp ? r:eval(\"(\"+r.responseText+\")\");\r\n                            utils.extend(me.options, config);\r\n                            me.fireEvent('serverConfigLoaded');\r\n                            me._serverConfigLoaded = true;\r\n                        } catch (e) {\r\n                            showErrorMsg(me.getLang('loadconfigFormatError'));\r\n                        }\r\n                    },\r\n                    'onerror':function(){\r\n                        showErrorMsg(me.getLang('loadconfigHttpError'));\r\n                    }\r\n                });\r\n            } catch(e){\r\n                showErrorMsg(me.getLang('loadconfigError'));\r\n            }\r\n        });\r\n\r\n        function showErrorMsg(msg) {\r\n            console && console.error(msg);\r\n            //me.fireEvent('showMessage', {\r\n            //    'title': msg,\r\n            //    'type': 'error'\r\n            //});\r\n        }\r\n    };\r\n\r\n    UE.Editor.prototype.isServerConfigLoaded = function(){\r\n        var me = this;\r\n        return me._serverConfigLoaded || false;\r\n    };\r\n\r\n    UE.Editor.prototype.afterConfigReady = function(handler){\r\n        if (!handler || !utils.isFunction(handler)) return;\r\n        var me = this;\r\n        var readyHandler = function(){\r\n            handler.apply(me, arguments);\r\n            me.removeListener('serverConfigLoaded', readyHandler);\r\n        };\r\n\r\n        if (me.isServerConfigLoaded()) {\r\n            handler.call(me, 'serverConfigLoaded');\r\n        } else {\r\n            me.addListener('serverConfigLoaded', readyHandler);\r\n        }\r\n    };\r\n\r\n})();\r\n\r\n\r\n// core/ajax.js\r\n/**\r\n * @file\r\n * @module UE.ajax\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 提供对ajax请求的支持\r\n * @module UE.ajax\r\n */\r\nUE.ajax = function() {\r\n\r\n    //创建一个ajaxRequest对象\r\n    var fnStr = 'XMLHttpRequest()';\r\n    try {\r\n        new ActiveXObject(\"Msxml2.XMLHTTP\");\r\n        fnStr = 'ActiveXObject(\\'Msxml2.XMLHTTP\\')';\r\n    } catch (e) {\r\n        try {\r\n            new ActiveXObject(\"Microsoft.XMLHTTP\");\r\n            fnStr = 'ActiveXObject(\\'Microsoft.XMLHTTP\\')'\r\n        } catch (e) {\r\n        }\r\n    }\r\n    var creatAjaxRequest = new Function('return new ' + fnStr);\r\n\r\n\r\n    /**\r\n     * 将json参数转化成适合ajax提交的参数列表\r\n     * @param json\r\n     */\r\n    function json2str(json) {\r\n        var strArr = [];\r\n        for (var i in json) {\r\n            //忽略默认的几个参数\r\n            if(i==\"method\" || i==\"timeout\" || i==\"async\" || i==\"dataType\" || i==\"callback\") continue;\r\n            //忽略控制\r\n            if(json[i] == undefined || json[i] == null) continue;\r\n            //传递过来的对象和函数不在提交之列\r\n            if (!((typeof json[i]).toLowerCase() == \"function\" || (typeof json[i]).toLowerCase() == \"object\")) {\r\n                strArr.push( encodeURIComponent(i) + \"=\"+encodeURIComponent(json[i]) );\r\n            } else if (utils.isArray(json[i])) {\r\n            //支持传数组内容\r\n                for(var j = 0; j < json[i].length; j++) {\r\n                    strArr.push( encodeURIComponent(i) + \"[]=\"+encodeURIComponent(json[i][j]) );\r\n                }\r\n            }\r\n        }\r\n        return strArr.join(\"&\");\r\n    }\r\n\r\n    function doAjax(url, ajaxOptions) {\r\n        var xhr = creatAjaxRequest(),\r\n        //是否超时\r\n            timeIsOut = false,\r\n        //默认参数\r\n            defaultAjaxOptions = {\r\n                method:\"POST\",\r\n                timeout:5000,\r\n                async:true,\r\n                data:{},//需要传递对象的话只能覆盖\r\n                onsuccess:function() {\r\n                },\r\n                onerror:function() {\r\n                }\r\n            };\r\n\r\n        if (typeof url === \"object\") {\r\n            ajaxOptions = url;\r\n            url = ajaxOptions.url;\r\n        }\r\n        if (!xhr || !url) return;\r\n        var ajaxOpts = ajaxOptions ? utils.extend(defaultAjaxOptions,ajaxOptions) : defaultAjaxOptions;\r\n\r\n        var submitStr = json2str(ajaxOpts);  // { name:\"Jim\",city:\"Beijing\" } --> \"name=Jim&city=Beijing\"\r\n        //如果用户直接通过data参数传递json对象过来，则也要将此json对象转化为字符串\r\n        if (!utils.isEmptyObject(ajaxOpts.data)){\r\n            submitStr += (submitStr? \"&\":\"\") + json2str(ajaxOpts.data);\r\n        }\r\n        //超时检测\r\n        var timerID = setTimeout(function() {\r\n            if (xhr.readyState != 4) {\r\n                timeIsOut = true;\r\n                xhr.abort();\r\n                clearTimeout(timerID);\r\n            }\r\n        }, ajaxOpts.timeout);\r\n\r\n        var method = ajaxOpts.method.toUpperCase();\r\n        var str = url + (url.indexOf(\"?\")==-1?\"?\":\"&\") + (method==\"POST\"?\"\":submitStr+ \"&noCache=\" + +new Date);\r\n        xhr.open(method, str, ajaxOpts.async);\r\n        xhr.onreadystatechange = function() {\r\n            if (xhr.readyState == 4) {\r\n                if (!timeIsOut && xhr.status == 200) {\r\n                    ajaxOpts.onsuccess(xhr);\r\n                } else {\r\n                    ajaxOpts.onerror(xhr);\r\n                }\r\n            }\r\n        };\r\n        if (method == \"POST\") {\r\n            xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');\r\n            xhr.send(submitStr);\r\n        } else {\r\n            xhr.send(null);\r\n        }\r\n    }\r\n\r\n    function doJsonp(url, opts) {\r\n\r\n        var successhandler = opts.onsuccess || function(){},\r\n            scr = document.createElement('SCRIPT'),\r\n            options = opts || {},\r\n            charset = options['charset'],\r\n            callbackField = options['jsonp'] || 'callback',\r\n            callbackFnName,\r\n            timeOut = options['timeOut'] || 0,\r\n            timer,\r\n            reg = new RegExp('(\\\\?|&)' + callbackField + '=([^&]*)'),\r\n            matches;\r\n\r\n        if (utils.isFunction(successhandler)) {\r\n            callbackFnName = 'bd__editor__' + Math.floor(Math.random() * 2147483648).toString(36);\r\n            window[callbackFnName] = getCallBack(0);\r\n        } else if(utils.isString(successhandler)){\r\n            callbackFnName = successhandler;\r\n        } else {\r\n            if (matches = reg.exec(url)) {\r\n                callbackFnName = matches[2];\r\n            }\r\n        }\r\n\r\n        url = url.replace(reg, '\\x241' + callbackField + '=' + callbackFnName);\r\n\r\n        if (url.search(reg) < 0) {\r\n            url += (url.indexOf('?') < 0 ? '?' : '&') + callbackField + '=' + callbackFnName;\r\n        }\r\n\r\n        var queryStr = json2str(opts);  // { name:\"Jim\",city:\"Beijing\" } --> \"name=Jim&city=Beijing\"\r\n        //如果用户直接通过data参数传递json对象过来，则也要将此json对象转化为字符串\r\n        if (!utils.isEmptyObject(opts.data)){\r\n            queryStr += (queryStr? \"&\":\"\") + json2str(opts.data);\r\n        }\r\n        if (queryStr) {\r\n            url = url.replace(/\\?/, '?' + queryStr + '&');\r\n        }\r\n\r\n        scr.onerror = getCallBack(1);\r\n        if( timeOut ){\r\n            timer = setTimeout(getCallBack(1), timeOut);\r\n        }\r\n        createScriptTag(scr, url, charset);\r\n\r\n        function createScriptTag(scr, url, charset) {\r\n            scr.setAttribute('type', 'text/javascript');\r\n            scr.setAttribute('defer', 'defer');\r\n            charset && scr.setAttribute('charset', charset);\r\n            scr.setAttribute('src', url);\r\n            document.getElementsByTagName('head')[0].appendChild(scr);\r\n        }\r\n\r\n        function getCallBack(onTimeOut){\r\n            return function(){\r\n                try {\r\n                    if(onTimeOut){\r\n                        options.onerror && options.onerror();\r\n                    }else{\r\n                        try{\r\n                            clearTimeout(timer);\r\n                            successhandler.apply(window, arguments);\r\n                        } catch (e){}\r\n                    }\r\n                } catch (exception) {\r\n                    options.onerror && options.onerror.call(window, exception);\r\n                } finally {\r\n                    options.oncomplete && options.oncomplete.apply(window, arguments);\r\n                    scr.parentNode && scr.parentNode.removeChild(scr);\r\n                    window[callbackFnName] = null;\r\n                    try {\r\n                        delete window[callbackFnName];\r\n                    }catch(e){}\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    return {\r\n        /**\r\n         * 根据给定的参数项，向指定的url发起一个ajax请求。 ajax请求完成后，会根据请求结果调用相应回调： 如果请求\r\n         * 成功， 则调用onsuccess回调， 失败则调用 onerror 回调\r\n         * @method request\r\n         * @param { URLString } url ajax请求的url地址\r\n         * @param { Object } ajaxOptions ajax请求选项的键值对，支持的选项如下：\r\n         * @example\r\n         * ```javascript\r\n         * //向sayhello.php发起一个异步的Ajax GET请求, 请求超时时间为10s， 请求完成后执行相应的回调。\r\n         * UE.ajax.requeset( 'sayhello.php', {\r\n         *\r\n         *     //请求方法。可选值： 'GET', 'POST'，默认值是'POST'\r\n         *     method: 'GET',\r\n         *\r\n         *     //超时时间。 默认为5000， 单位是ms\r\n         *     timeout: 10000,\r\n         *\r\n         *     //是否是异步请求。 true为异步请求， false为同步请求\r\n         *     async: true,\r\n         *\r\n         *     //请求携带的数据。如果请求为GET请求， data会经过stringify后附加到请求url之后。\r\n         *     data: {\r\n         *         name: 'ueditor'\r\n         *     },\r\n         *\r\n         *     //请求成功后的回调， 该回调接受当前的XMLHttpRequest对象作为参数。\r\n         *     onsuccess: function ( xhr ) {\r\n         *         console.log( xhr.responseText );\r\n         *     },\r\n         *\r\n         *     //请求失败或者超时后的回调。\r\n         *     onerror: function ( xhr ) {\r\n         *          alert( 'Ajax请求失败' );\r\n         *     }\r\n         *\r\n         * } );\r\n         * ```\r\n         */\r\n\r\n        /**\r\n         * 根据给定的参数项发起一个ajax请求， 参数项里必须包含一个url地址。 ajax请求完成后，会根据请求结果调用相应回调： 如果请求\r\n         * 成功， 则调用onsuccess回调， 失败则调用 onerror 回调。\r\n         * @method request\r\n         * @warning 如果在参数项里未提供一个key为“url”的地址值，则该请求将直接退出。\r\n         * @param { Object } ajaxOptions ajax请求选项的键值对，支持的选项如下：\r\n         * @example\r\n         * ```javascript\r\n         *\r\n         * //向sayhello.php发起一个异步的Ajax POST请求, 请求超时时间为5s， 请求完成后不执行任何回调。\r\n         * UE.ajax.requeset( 'sayhello.php', {\r\n         *\r\n         *     //请求的地址， 该项是必须的。\r\n         *     url: 'sayhello.php'\r\n         *\r\n         * } );\r\n         * ```\r\n         */\r\n\t\trequest:function(url, opts) {\r\n            if (opts && opts.dataType == 'jsonp') {\r\n                doJsonp(url, opts);\r\n            } else {\r\n                doAjax(url, opts);\r\n            }\r\n\t\t},\r\n        getJSONP:function(url, data, fn) {\r\n            var opts = {\r\n                'data': data,\r\n                'oncomplete': fn\r\n            };\r\n            doJsonp(url, opts);\r\n\t\t}\r\n\t};\r\n\r\n\r\n}();\r\n\r\n\r\n// core/filterword.js\r\n/**\r\n * UE过滤word的静态方法\r\n * @file\r\n */\r\n\r\n/**\r\n * UEditor公用空间，UEditor所有的功能都挂载在该空间下\r\n * @module UE\r\n */\r\n\r\n\r\n/**\r\n * 根据传入html字符串过滤word\r\n * @module UE\r\n * @since 1.2.6.1\r\n * @method filterWord\r\n * @param { String } html html字符串\r\n * @return { String } 已过滤后的结果字符串\r\n * @example\r\n * ```javascript\r\n * UE.filterWord(html);\r\n * ```\r\n */\r\nvar filterWord = UE.filterWord = function () {\r\n\r\n    //是否是word过来的内容\r\n    function isWordDocument( str ) {\r\n        return /(class=\"?Mso|style=\"[^\"]*\\bmso\\-|w:WordDocument|<(v|o):|lang=)/ig.test( str );\r\n    }\r\n    //去掉小数\r\n    function transUnit( v ) {\r\n        v = v.replace( /[\\d.]+\\w+/g, function ( m ) {\r\n            return utils.transUnitToPx(m);\r\n        } );\r\n        return v;\r\n    }\r\n\r\n    function filterPasteWord( str ) {\r\n        return str.replace(/[\\t\\r\\n]+/g,' ')\r\n                .replace( /<!--[\\s\\S]*?-->/ig, \"\" )\r\n                //转换图片\r\n                .replace(/<v:shape [^>]*>[\\s\\S]*?.<\\/v:shape>/gi,function(str){\r\n                    //opera能自己解析出image所这里直接返回空\r\n                    if(browser.opera){\r\n                        return '';\r\n                    }\r\n                    try{\r\n                        //有可能是bitmap占为图，无用，直接过滤掉，主要体现在粘贴excel表格中\r\n                        if(/Bitmap/i.test(str)){\r\n                            return '';\r\n                        }\r\n                        var width = str.match(/width:([ \\d.]*p[tx])/i)[1],\r\n                            height = str.match(/height:([ \\d.]*p[tx])/i)[1],\r\n                            src =  str.match(/src=\\s*\"([^\"]*)\"/i)[1];\r\n                        return '<img width=\"'+ transUnit(width) +'\" height=\"'+transUnit(height) +'\" src=\"' + src + '\" />';\r\n                    } catch(e){\r\n                        return '';\r\n                    }\r\n                })\r\n                //针对wps添加的多余标签处理\r\n                .replace(/<\\/?div[^>]*>/g,'')\r\n                //去掉多余的属性\r\n                .replace( /v:\\w+=([\"']?)[^'\"]+\\1/g, '' )\r\n                .replace( /<(!|script[^>]*>.*?<\\/script(?=[>\\s])|\\/?(\\?xml(:\\w+)?|xml|meta|link|style|\\w+:\\w+)(?=[\\s\\/>]))[^>]*>/gi, \"\" )\r\n                .replace( /<p [^>]*class=\"?MsoHeading\"?[^>]*>(.*?)<\\/p>/gi, \"<p><strong>$1</strong></p>\" )\r\n                //去掉多余的属性\r\n                .replace( /\\s+(class|lang|align)\\s*=\\s*(['\"]?)([\\w-]+)\\2/ig, function(str,name,marks,val){\r\n                    //保留list的标示\r\n                    return name == 'class' && val == 'MsoListParagraph' ? str : ''\r\n                })\r\n                //清除多余的font/span不能匹配&nbsp;有可能是空格\r\n                .replace( /<(font|span)[^>]*>(\\s*)<\\/\\1>/gi, function(a,b,c){\r\n                    return c.replace(/[\\t\\r\\n ]+/g,' ')\r\n                })\r\n                //处理style的问题\r\n                .replace( /(<[a-z][^>]*)\\sstyle=([\"'])([^\\2]*?)\\2/gi, function( str, tag, tmp, style ) {\r\n                    var n = [],\r\n                        s = style.replace( /^\\s+|\\s+$/, '' )\r\n                            .replace(/&#39;/g,'\\'')\r\n                            .replace( /&quot;/gi, \"'\" )\r\n                            .replace(/[\\d.]+(cm|pt)/g,function(str){\r\n                                return utils.transUnitToPx(str)\r\n                            })\r\n                            .split( /;\\s*/g );\r\n\r\n                    for ( var i = 0,v; v = s[i];i++ ) {\r\n\r\n                        var name, value,\r\n                            parts = v.split( \":\" );\r\n\r\n                        if ( parts.length == 2 ) {\r\n                            name = parts[0].toLowerCase();\r\n                            value = parts[1].toLowerCase();\r\n                            if(/^(background)\\w*/.test(name) && value.replace(/(initial|\\s)/g,'').length == 0\r\n                                ||\r\n                                /^(margin)\\w*/.test(name) && /^0\\w+$/.test(value)\r\n                            ){\r\n                                continue;\r\n                            }\r\n\r\n                            switch ( name ) {\r\n                                case \"mso-padding-alt\":\r\n                                case \"mso-padding-top-alt\":\r\n                                case \"mso-padding-right-alt\":\r\n                                case \"mso-padding-bottom-alt\":\r\n                                case \"mso-padding-left-alt\":\r\n                                case \"mso-margin-alt\":\r\n                                case \"mso-margin-top-alt\":\r\n                                case \"mso-margin-right-alt\":\r\n                                case \"mso-margin-bottom-alt\":\r\n                                case \"mso-margin-left-alt\":\r\n                                //ie下会出现挤到一起的情况\r\n                               //case \"mso-table-layout-alt\":\r\n                                case \"mso-height\":\r\n                                case \"mso-width\":\r\n                                case \"mso-vertical-align-alt\":\r\n                                    //trace:1819 ff下会解析出padding在table上\r\n                                    if(!/<table/.test(tag))\r\n                                        n[i] = name.replace( /^mso-|-alt$/g, \"\" ) + \":\" + transUnit( value );\r\n                                    continue;\r\n                                case \"horiz-align\":\r\n                                    n[i] = \"text-align:\" + value;\r\n                                    continue;\r\n\r\n                                case \"vert-align\":\r\n                                    n[i] = \"vertical-align:\" + value;\r\n                                    continue;\r\n\r\n                                case \"font-color\":\r\n                                case \"mso-foreground\":\r\n                                    n[i] = \"color:\" + value;\r\n                                    continue;\r\n\r\n                                case \"mso-background\":\r\n                                case \"mso-highlight\":\r\n                                    n[i] = \"background:\" + value;\r\n                                    continue;\r\n\r\n                                case \"mso-default-height\":\r\n                                    n[i] = \"min-height:\" + transUnit( value );\r\n                                    continue;\r\n\r\n                                case \"mso-default-width\":\r\n                                    n[i] = \"min-width:\" + transUnit( value );\r\n                                    continue;\r\n\r\n                                case \"mso-padding-between-alt\":\r\n                                    n[i] = \"border-collapse:separate;border-spacing:\" + transUnit( value );\r\n                                    continue;\r\n\r\n                                case \"text-line-through\":\r\n                                    if ( (value == \"single\") || (value == \"double\") ) {\r\n                                        n[i] = \"text-decoration:line-through\";\r\n                                    }\r\n                                    continue;\r\n                                case \"mso-zero-height\":\r\n                                    if ( value == \"yes\" ) {\r\n                                        n[i] = \"display:none\";\r\n                                    }\r\n                                    continue;\r\n//                                case 'background':\r\n//                                    break;\r\n                                case 'margin':\r\n                                    if ( !/[1-9]/.test( value ) ) {\r\n                                        continue;\r\n                                    }\r\n\r\n                            }\r\n\r\n                            if ( /^(mso|column|font-emph|lang|layout|line-break|list-image|nav|panose|punct|row|ruby|sep|size|src|tab-|table-border|text-(?:decor|trans)|top-bar|version|vnd|word-break)/.test( name )\r\n                                ||\r\n                                /text\\-indent|padding|margin/.test(name) && /\\-[\\d.]+/.test(value)\r\n                            ) {\r\n                                continue;\r\n                            }\r\n\r\n                            n[i] = name + \":\" + parts[1];\r\n                        }\r\n                    }\r\n                    return tag + (n.length ? ' style=\"' + n.join( ';').replace(/;{2,}/g,';') + '\"' : '');\r\n                })\r\n\r\n\r\n    }\r\n\r\n    return function ( html ) {\r\n        return (isWordDocument( html ) ? filterPasteWord( html ) : html);\r\n    };\r\n}();\r\n\r\n// core/node.js\r\n/**\r\n * 编辑器模拟的节点类\r\n * @file\r\n * @module UE\r\n * @class uNode\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * UEditor公用空间，UEditor所有的功能都挂载在该空间下\r\n * @unfile\r\n * @module UE\r\n */\r\n\r\n(function () {\r\n\r\n    /**\r\n     * 编辑器模拟的节点类\r\n     * @unfile\r\n     * @module UE\r\n     * @class uNode\r\n     */\r\n\r\n    /**\r\n     * 通过一个键值对，创建一个uNode对象\r\n     * @constructor\r\n     * @param { Object } attr 传入要创建的uNode的初始属性\r\n     * @example\r\n     * ```javascript\r\n     * var node = new uNode({\r\n     *     type:'element',\r\n     *     tagName:'span',\r\n     *     attrs:{style:'font-size:14px;'}\r\n     * }\r\n     * ```\r\n     */\r\n    var uNode = UE.uNode = function (obj) {\r\n        this.type = obj.type;\r\n        this.data = obj.data;\r\n        this.tagName = obj.tagName;\r\n        this.parentNode = obj.parentNode;\r\n        this.attrs = obj.attrs || {};\r\n        this.children = obj.children;\r\n    };\r\n\r\n    var notTransAttrs = {\r\n        'href':1,\r\n        'src':1,\r\n        '_src':1,\r\n        '_href':1,\r\n        'cdata_data':1\r\n    };\r\n\r\n    var notTransTagName = {\r\n        style:1,\r\n        script:1\r\n    };\r\n\r\n    var indentChar = '    ',\r\n        breakChar = '\\n';\r\n\r\n    function insertLine(arr, current, begin) {\r\n        arr.push(breakChar);\r\n        return current + (begin ? 1 : -1);\r\n    }\r\n\r\n    function insertIndent(arr, current) {\r\n        //插入缩进\r\n        for (var i = 0; i < current; i++) {\r\n            arr.push(indentChar);\r\n        }\r\n    }\r\n\r\n    //创建uNode的静态方法\r\n    //支持标签和html\r\n    uNode.createElement = function (html) {\r\n        if (/[<>]/.test(html)) {\r\n            return UE.htmlparser(html).children[0]\r\n        } else {\r\n            return new uNode({\r\n                type:'element',\r\n                children:[],\r\n                tagName:html\r\n            })\r\n        }\r\n    };\r\n    uNode.createText = function (data,noTrans) {\r\n        return new UE.uNode({\r\n            type:'text',\r\n            'data':noTrans ? data : utils.unhtml(data || '')\r\n        })\r\n    };\r\n    function nodeToHtml(node, arr, formatter, current) {\r\n        switch (node.type) {\r\n            case 'root':\r\n                for (var i = 0, ci; ci = node.children[i++];) {\r\n                    //插入新行\r\n                    if (formatter && ci.type == 'element' && !dtd.$inlineWithA[ci.tagName] && i > 1) {\r\n                        insertLine(arr, current, true);\r\n                        insertIndent(arr, current)\r\n                    }\r\n                    nodeToHtml(ci, arr, formatter, current)\r\n                }\r\n                break;\r\n            case 'text':\r\n                isText(node, arr);\r\n                break;\r\n            case 'element':\r\n                isElement(node, arr, formatter, current);\r\n                break;\r\n            case 'comment':\r\n                isComment(node, arr, formatter);\r\n        }\r\n        return arr;\r\n    }\r\n\r\n    function isText(node, arr) {\r\n        if(node.parentNode.tagName == 'pre'){\r\n            //源码模式下输入html标签，不能做转换处理，直接输出\r\n            arr.push(node.data)\r\n        }else{\r\n            arr.push(notTransTagName[node.parentNode.tagName] ? utils.html(node.data) : node.data.replace(/[ ]{2}/g,' &nbsp;'))\r\n        }\r\n\r\n    }\r\n\r\n    function isElement(node, arr, formatter, current) {\r\n        var attrhtml = '';\r\n        if (node.attrs) {\r\n            attrhtml = [];\r\n            var attrs = node.attrs;\r\n            for (var a in attrs) {\r\n                //这里就针对\r\n                //<p>'<img src='http://nsclick.baidu.com/u.gif?&asdf=\\\"sdf&asdfasdfs;asdf'></p>\r\n                //这里边的\\\"做转换，要不用innerHTML直接被截断了，属性src\r\n                //有可能做的不够\r\n                attrhtml.push(a + (attrs[a] !== undefined ? '=\"' + (notTransAttrs[a] ? utils.html(attrs[a]).replace(/[\"]/g, function (a) {\r\n                   return '&quot;'\r\n                }) : utils.unhtml(attrs[a])) + '\"' : ''))\r\n            }\r\n            attrhtml = attrhtml.join(' ');\r\n        }\r\n        arr.push('<' + node.tagName +\r\n            (attrhtml ? ' ' + attrhtml  : '') +\r\n            (dtd.$empty[node.tagName] ? '\\/' : '' ) + '>'\r\n        );\r\n        //插入新行\r\n        if (formatter  &&  !dtd.$inlineWithA[node.tagName] && node.tagName != 'pre') {\r\n            if(node.children && node.children.length){\r\n                current = insertLine(arr, current, true);\r\n                insertIndent(arr, current)\r\n            }\r\n\r\n        }\r\n        if (node.children && node.children.length) {\r\n            for (var i = 0, ci; ci = node.children[i++];) {\r\n                if (formatter && ci.type == 'element' &&  !dtd.$inlineWithA[ci.tagName] && i > 1) {\r\n                    insertLine(arr, current);\r\n                    insertIndent(arr, current)\r\n                }\r\n                nodeToHtml(ci, arr, formatter, current)\r\n            }\r\n        }\r\n        if (!dtd.$empty[node.tagName]) {\r\n            if (formatter && !dtd.$inlineWithA[node.tagName]  && node.tagName != 'pre') {\r\n\r\n                if(node.children && node.children.length){\r\n                    current = insertLine(arr, current);\r\n                    insertIndent(arr, current)\r\n                }\r\n            }\r\n            arr.push('<\\/' + node.tagName + '>');\r\n        }\r\n\r\n    }\r\n\r\n    function isComment(node, arr) {\r\n        arr.push('<!--' + node.data + '-->');\r\n    }\r\n\r\n    function getNodeById(root, id) {\r\n        var node;\r\n        if (root.type == 'element' && root.getAttr('id') == id) {\r\n            return root;\r\n        }\r\n        if (root.children && root.children.length) {\r\n            for (var i = 0, ci; ci = root.children[i++];) {\r\n                if (node = getNodeById(ci, id)) {\r\n                    return node;\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    function getNodesByTagName(node, tagName, arr) {\r\n        if (node.type == 'element' && node.tagName == tagName) {\r\n            arr.push(node);\r\n        }\r\n        if (node.children && node.children.length) {\r\n            for (var i = 0, ci; ci = node.children[i++];) {\r\n                getNodesByTagName(ci, tagName, arr)\r\n            }\r\n        }\r\n    }\r\n    function nodeTraversal(root,fn){\r\n        if(root.children && root.children.length){\r\n            for(var i= 0,ci;ci=root.children[i];){\r\n                nodeTraversal(ci,fn);\r\n                //ci被替换的情况，这里就不再走 fn了\r\n                if(ci.parentNode ){\r\n                    if(ci.children && ci.children.length){\r\n                        fn(ci)\r\n                    }\r\n                    if(ci.parentNode) i++\r\n                }\r\n            }\r\n        }else{\r\n            fn(root)\r\n        }\r\n\r\n    }\r\n    uNode.prototype = {\r\n\r\n        /**\r\n         * 当前节点对象，转换成html文本\r\n         * @method toHtml\r\n         * @return { String } 返回转换后的html字符串\r\n         * @example\r\n         * ```javascript\r\n         * node.toHtml();\r\n         * ```\r\n         */\r\n\r\n        /**\r\n         * 当前节点对象，转换成html文本\r\n         * @method toHtml\r\n         * @param { Boolean } formatter 是否格式化返回值\r\n         * @return { String } 返回转换后的html字符串\r\n         * @example\r\n         * ```javascript\r\n         * node.toHtml( true );\r\n         * ```\r\n         */\r\n        toHtml:function (formatter) {\r\n            var arr = [];\r\n            nodeToHtml(this, arr, formatter, 0);\r\n            return arr.join('')\r\n        },\r\n\r\n        /**\r\n         * 获取节点的html内容\r\n         * @method innerHTML\r\n         * @warning 假如节点的type不是'element'，或节点的标签名称不在dtd列表里，直接返回当前节点\r\n         * @return { String } 返回节点的html内容\r\n         * @example\r\n         * ```javascript\r\n         * var htmlstr = node.innerHTML();\r\n         * ```\r\n         */\r\n\r\n        /**\r\n         * 设置节点的html内容\r\n         * @method innerHTML\r\n         * @warning 假如节点的type不是'element'，或节点的标签名称不在dtd列表里，直接返回当前节点\r\n         * @param { String } htmlstr 传入要设置的html内容\r\n         * @return { UE.uNode } 返回节点本身\r\n         * @example\r\n         * ```javascript\r\n         * node.innerHTML('<span>text</span>');\r\n         * ```\r\n         */\r\n        innerHTML:function (htmlstr) {\r\n            if (this.type != 'element' || dtd.$empty[this.tagName]) {\r\n                return this;\r\n            }\r\n            if (utils.isString(htmlstr)) {\r\n                if(this.children){\r\n                    for (var i = 0, ci; ci = this.children[i++];) {\r\n                        ci.parentNode = null;\r\n                    }\r\n                }\r\n                this.children = [];\r\n                var tmpRoot = UE.htmlparser(htmlstr);\r\n                for (var i = 0, ci; ci = tmpRoot.children[i++];) {\r\n                    this.children.push(ci);\r\n                    ci.parentNode = this;\r\n                }\r\n                return this;\r\n            } else {\r\n                var tmpRoot = new UE.uNode({\r\n                    type:'root',\r\n                    children:this.children\r\n                });\r\n                return tmpRoot.toHtml();\r\n            }\r\n        },\r\n\r\n        /**\r\n         * 获取节点的纯文本内容\r\n         * @method innerText\r\n         * @warning 假如节点的type不是'element'，或节点的标签名称不在dtd列表里，直接返回当前节点\r\n         * @return { String } 返回节点的存文本内容\r\n         * @example\r\n         * ```javascript\r\n         * var textStr = node.innerText();\r\n         * ```\r\n         */\r\n\r\n        /**\r\n         * 设置节点的纯文本内容\r\n         * @method innerText\r\n         * @warning 假如节点的type不是'element'，或节点的标签名称不在dtd列表里，直接返回当前节点\r\n         * @param { String } textStr 传入要设置的文本内容\r\n         * @return { UE.uNode } 返回节点本身\r\n         * @example\r\n         * ```javascript\r\n         * node.innerText('<span>text</span>');\r\n         * ```\r\n         */\r\n        innerText:function (textStr,noTrans) {\r\n            if (this.type != 'element' || dtd.$empty[this.tagName]) {\r\n                return this;\r\n            }\r\n            if (textStr) {\r\n                if(this.children){\r\n                    for (var i = 0, ci; ci = this.children[i++];) {\r\n                        ci.parentNode = null;\r\n                    }\r\n                }\r\n                this.children = [];\r\n                this.appendChild(uNode.createText(textStr,noTrans));\r\n                return this;\r\n            } else {\r\n                return this.toHtml().replace(/<[^>]+>/g, '');\r\n            }\r\n        },\r\n\r\n        /**\r\n         * 获取当前对象的data属性\r\n         * @method getData\r\n         * @return { Object } 若节点的type值是elemenet，返回空字符串，否则返回节点的data属性\r\n         * @example\r\n         * ```javascript\r\n         * node.getData();\r\n         * ```\r\n         */\r\n        getData:function () {\r\n            if (this.type == 'element')\r\n                return '';\r\n            return this.data\r\n        },\r\n\r\n        /**\r\n         * 获取当前节点下的第一个子节点\r\n         * @method firstChild\r\n         * @return { UE.uNode } 返回第一个子节点\r\n         * @example\r\n         * ```javascript\r\n         * node.firstChild(); //返回第一个子节点\r\n         * ```\r\n         */\r\n        firstChild:function () {\r\n//            if (this.type != 'element' || dtd.$empty[this.tagName]) {\r\n//                return this;\r\n//            }\r\n            return this.children ? this.children[0] : null;\r\n        },\r\n\r\n        /**\r\n         * 获取当前节点下的最后一个子节点\r\n         * @method lastChild\r\n         * @return { UE.uNode } 返回最后一个子节点\r\n         * @example\r\n         * ```javascript\r\n         * node.lastChild(); //返回最后一个子节点\r\n         * ```\r\n         */\r\n        lastChild:function () {\r\n//            if (this.type != 'element' || dtd.$empty[this.tagName] ) {\r\n//                return this;\r\n//            }\r\n            return this.children ? this.children[this.children.length - 1] : null;\r\n        },\r\n\r\n        /**\r\n         * 获取和当前节点有相同父亲节点的前一个节点\r\n         * @method previousSibling\r\n         * @return { UE.uNode } 返回前一个节点\r\n         * @example\r\n         * ```javascript\r\n         * node.children[2].previousSibling(); //返回子节点node.children[1]\r\n         * ```\r\n         */\r\n        previousSibling : function(){\r\n            var parent = this.parentNode;\r\n            for (var i = 0, ci; ci = parent.children[i]; i++) {\r\n                if (ci === this) {\r\n                   return i == 0 ? null : parent.children[i-1];\r\n                }\r\n            }\r\n\r\n        },\r\n\r\n        /**\r\n         * 获取和当前节点有相同父亲节点的后一个节点\r\n         * @method nextSibling\r\n         * @return { UE.uNode } 返回后一个节点,找不到返回null\r\n         * @example\r\n         * ```javascript\r\n         * node.children[2].nextSibling(); //如果有，返回子节点node.children[3]\r\n         * ```\r\n         */\r\n        nextSibling : function(){\r\n            var parent = this.parentNode;\r\n            for (var i = 0, ci; ci = parent.children[i++];) {\r\n                if (ci === this) {\r\n                    return parent.children[i];\r\n                }\r\n            }\r\n        },\r\n\r\n        /**\r\n         * 用新的节点替换当前节点\r\n         * @method replaceChild\r\n         * @param { UE.uNode } target 要替换成该节点参数\r\n         * @param { UE.uNode } source 要被替换掉的节点\r\n         * @return { UE.uNode } 返回替换之后的节点对象\r\n         * @example\r\n         * ```javascript\r\n         * node.replaceChild(newNode, childNode); //用newNode替换childNode,childNode是node的子节点\r\n         * ```\r\n         */\r\n        replaceChild:function (target, source) {\r\n            if (this.children) {\r\n                if(target.parentNode){\r\n                    target.parentNode.removeChild(target);\r\n                }\r\n                for (var i = 0, ci; ci = this.children[i]; i++) {\r\n                    if (ci === source) {\r\n                        this.children.splice(i, 1, target);\r\n                        source.parentNode = null;\r\n                        target.parentNode = this;\r\n                        return target;\r\n                    }\r\n                }\r\n            }\r\n        },\r\n\r\n        /**\r\n         * 在节点的子节点列表最后位置插入一个节点\r\n         * @method appendChild\r\n         * @param { UE.uNode } node 要插入的节点\r\n         * @return { UE.uNode } 返回刚插入的子节点\r\n         * @example\r\n         * ```javascript\r\n         * node.appendChild( newNode ); //在node内插入子节点newNode\r\n         * ```\r\n         */\r\n        appendChild:function (node) {\r\n            if (this.type == 'root' || (this.type == 'element' && !dtd.$empty[this.tagName])) {\r\n                if (!this.children) {\r\n                    this.children = []\r\n                }\r\n                if(node.parentNode){\r\n                    node.parentNode.removeChild(node);\r\n                }\r\n                for (var i = 0, ci; ci = this.children[i]; i++) {\r\n                    if (ci === node) {\r\n                        this.children.splice(i, 1);\r\n                        break;\r\n                    }\r\n                }\r\n                this.children.push(node);\r\n                node.parentNode = this;\r\n                return node;\r\n            }\r\n\r\n\r\n        },\r\n\r\n        /**\r\n         * 在传入节点的前面插入一个节点\r\n         * @method insertBefore\r\n         * @param { UE.uNode } target 要插入的节点\r\n         * @param { UE.uNode } source 在该参数节点前面插入\r\n         * @return { UE.uNode } 返回刚插入的子节点\r\n         * @example\r\n         * ```javascript\r\n         * node.parentNode.insertBefore(newNode, node); //在node节点后面插入newNode\r\n         * ```\r\n         */\r\n        insertBefore:function (target, source) {\r\n            if (this.children) {\r\n                if(target.parentNode){\r\n                    target.parentNode.removeChild(target);\r\n                }\r\n                for (var i = 0, ci; ci = this.children[i]; i++) {\r\n                    if (ci === source) {\r\n                        this.children.splice(i, 0, target);\r\n                        target.parentNode = this;\r\n                        return target;\r\n                    }\r\n                }\r\n\r\n            }\r\n        },\r\n\r\n        /**\r\n         * 在传入节点的后面插入一个节点\r\n         * @method insertAfter\r\n         * @param { UE.uNode } target 要插入的节点\r\n         * @param { UE.uNode } source 在该参数节点后面插入\r\n         * @return { UE.uNode } 返回刚插入的子节点\r\n         * @example\r\n         * ```javascript\r\n         * node.parentNode.insertAfter(newNode, node); //在node节点后面插入newNode\r\n         * ```\r\n         */\r\n        insertAfter:function (target, source) {\r\n            if (this.children) {\r\n                if(target.parentNode){\r\n                    target.parentNode.removeChild(target);\r\n                }\r\n                for (var i = 0, ci; ci = this.children[i]; i++) {\r\n                    if (ci === source) {\r\n                        this.children.splice(i + 1, 0, target);\r\n                        target.parentNode = this;\r\n                        return target;\r\n                    }\r\n\r\n                }\r\n            }\r\n        },\r\n\r\n        /**\r\n         * 从当前节点的子节点列表中，移除节点\r\n         * @method removeChild\r\n         * @param { UE.uNode } node 要移除的节点引用\r\n         * @param { Boolean } keepChildren 是否保留移除节点的子节点，若传入true，自动把移除节点的子节点插入到移除的位置\r\n         * @return { * } 返回刚移除的子节点\r\n         * @example\r\n         * ```javascript\r\n         * node.removeChild(childNode,true); //在node的子节点列表中移除child节点，并且吧child的子节点插入到移除的位置\r\n         * ```\r\n         */\r\n        removeChild:function (node,keepChildren) {\r\n            if (this.children) {\r\n                for (var i = 0, ci; ci = this.children[i]; i++) {\r\n                    if (ci === node) {\r\n                        this.children.splice(i, 1);\r\n                        ci.parentNode = null;\r\n                        if(keepChildren && ci.children && ci.children.length){\r\n                            for(var j= 0,cj;cj=ci.children[j];j++){\r\n                                this.children.splice(i+j,0,cj);\r\n                                cj.parentNode = this;\r\n\r\n                            }\r\n                        }\r\n                        return ci;\r\n                    }\r\n                }\r\n            }\r\n        },\r\n\r\n        /**\r\n         * 获取当前节点所代表的元素属性，即获取attrs对象下的属性值\r\n         * @method getAttr\r\n         * @param { String } attrName 要获取的属性名称\r\n         * @return { * } 返回attrs对象下的属性值\r\n         * @example\r\n         * ```javascript\r\n         * node.getAttr('title');\r\n         * ```\r\n         */\r\n        getAttr:function (attrName) {\r\n            return this.attrs && this.attrs[attrName.toLowerCase()]\r\n        },\r\n\r\n        /**\r\n         * 设置当前节点所代表的元素属性，即设置attrs对象下的属性值\r\n         * @method setAttr\r\n         * @param { String } attrName 要设置的属性名称\r\n         * @param { * } attrVal 要设置的属性值，类型视设置的属性而定\r\n         * @return { * } 返回attrs对象下的属性值\r\n         * @example\r\n         * ```javascript\r\n         * node.setAttr('title','标题');\r\n         * ```\r\n         */\r\n        setAttr:function (attrName, attrVal) {\r\n            if (!attrName) {\r\n                delete this.attrs;\r\n                return;\r\n            }\r\n            if(!this.attrs){\r\n                this.attrs = {};\r\n            }\r\n            if (utils.isObject(attrName)) {\r\n                for (var a in attrName) {\r\n                    if (!attrName[a]) {\r\n                        delete this.attrs[a]\r\n                    } else {\r\n                        this.attrs[a.toLowerCase()] = attrName[a];\r\n                    }\r\n                }\r\n            } else {\r\n                if (!attrVal) {\r\n                    delete this.attrs[attrName]\r\n                } else {\r\n                    this.attrs[attrName.toLowerCase()] = attrVal;\r\n                }\r\n\r\n            }\r\n        },\r\n\r\n        /**\r\n         * 获取当前节点在父节点下的位置索引\r\n         * @method getIndex\r\n         * @return { Number } 返回索引数值，如果没有父节点，返回-1\r\n         * @example\r\n         * ```javascript\r\n         * node.getIndex();\r\n         * ```\r\n         */\r\n        getIndex:function(){\r\n            var parent = this.parentNode;\r\n            for(var i= 0,ci;ci=parent.children[i];i++){\r\n                if(ci === this){\r\n                    return i;\r\n                }\r\n            }\r\n            return -1;\r\n        },\r\n\r\n        /**\r\n         * 在当前节点下，根据id查找节点\r\n         * @method getNodeById\r\n         * @param { String } id 要查找的id\r\n         * @return { UE.uNode } 返回找到的节点\r\n         * @example\r\n         * ```javascript\r\n         * node.getNodeById('textId');\r\n         * ```\r\n         */\r\n        getNodeById:function (id) {\r\n            var node;\r\n            if (this.children && this.children.length) {\r\n                for (var i = 0, ci; ci = this.children[i++];) {\r\n                    if (node = getNodeById(ci, id)) {\r\n                        return node;\r\n                    }\r\n                }\r\n            }\r\n        },\r\n\r\n        /**\r\n         * 在当前节点下，根据元素名称查找节点列表\r\n         * @method getNodesByTagName\r\n         * @param { String } tagNames 要查找的元素名称\r\n         * @return { Array } 返回找到的节点列表\r\n         * @example\r\n         * ```javascript\r\n         * node.getNodesByTagName('span');\r\n         * ```\r\n         */\r\n        getNodesByTagName:function (tagNames) {\r\n            tagNames = utils.trim(tagNames).replace(/[ ]{2,}/g, ' ').split(' ');\r\n            var arr = [], me = this;\r\n            utils.each(tagNames, function (tagName) {\r\n                if (me.children && me.children.length) {\r\n                    for (var i = 0, ci; ci = me.children[i++];) {\r\n                        getNodesByTagName(ci, tagName, arr)\r\n                    }\r\n                }\r\n            });\r\n            return arr;\r\n        },\r\n\r\n        /**\r\n         * 根据样式名称，获取节点的样式值\r\n         * @method getStyle\r\n         * @param { String } name 要获取的样式名称\r\n         * @return { String } 返回样式值\r\n         * @example\r\n         * ```javascript\r\n         * node.getStyle('font-size');\r\n         * ```\r\n         */\r\n        getStyle:function (name) {\r\n            var cssStyle = this.getAttr('style');\r\n            if (!cssStyle) {\r\n                return ''\r\n            }\r\n            var reg = new RegExp('(^|;)\\\\s*' + name + ':([^;]+)','i');\r\n            var match = cssStyle.match(reg);\r\n            if (match && match[0]) {\r\n                return match[2]\r\n            }\r\n            return '';\r\n        },\r\n\r\n        /**\r\n         * 给节点设置样式\r\n         * @method setStyle\r\n         * @param { String } name 要设置的的样式名称\r\n         * @param { String } val 要设置的的样值\r\n         * @example\r\n         * ```javascript\r\n         * node.setStyle('font-size', '12px');\r\n         * ```\r\n         */\r\n        setStyle:function (name, val) {\r\n            function exec(name, val) {\r\n                var reg = new RegExp('(^|;)\\\\s*' + name + ':([^;]+;?)', 'gi');\r\n                cssStyle = cssStyle.replace(reg, '$1');\r\n                if (val) {\r\n                    cssStyle = name + ':' + utils.unhtml(val) + ';' + cssStyle\r\n                }\r\n\r\n            }\r\n\r\n            var cssStyle = this.getAttr('style');\r\n            if (!cssStyle) {\r\n                cssStyle = '';\r\n            }\r\n            if (utils.isObject(name)) {\r\n                for (var a in name) {\r\n                    exec(a, name[a])\r\n                }\r\n            } else {\r\n                exec(name, val)\r\n            }\r\n            this.setAttr('style', utils.trim(cssStyle))\r\n        },\r\n\r\n        /**\r\n         * 传入一个函数，递归遍历当前节点下的所有节点\r\n         * @method traversal\r\n         * @param { Function } fn 遍历到节点的时，传入节点作为参数，运行此函数\r\n         * @example\r\n         * ```javascript\r\n         * traversal(node, function(){\r\n         *     console.log(node.type);\r\n         * });\r\n         * ```\r\n         */\r\n        traversal:function(fn){\r\n            if(this.children && this.children.length){\r\n                nodeTraversal(this,fn);\r\n            }\r\n            return this;\r\n        }\r\n    }\r\n})();\r\n\r\n\r\n// core/htmlparser.js\r\n/**\r\n * html字符串转换成uNode节点\r\n * @file\r\n * @module UE\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * UEditor公用空间，UEditor所有的功能都挂载在该空间下\r\n * @unfile\r\n * @module UE\r\n */\r\n\r\n/**\r\n * html字符串转换成uNode节点的静态方法\r\n * @method htmlparser\r\n * @param { String } htmlstr 要转换的html代码\r\n * @param { Boolean } ignoreBlank 若设置为true，转换的时候忽略\\n\\r\\t等空白字符\r\n * @return { uNode } 给定的html片段转换形成的uNode对象\r\n * @example\r\n * ```javascript\r\n * var root = UE.htmlparser('<p><b>htmlparser</b></p>', true);\r\n * ```\r\n */\r\n\r\nvar htmlparser = UE.htmlparser = function (htmlstr,ignoreBlank) {\r\n    //todo 原来的方式  [^\"'<>\\/] 有\\/就不能配对上 <TD vAlign=top background=../AAA.JPG> 这样的标签了\r\n    //先去掉了，加上的原因忘了，这里先记录\r\n    var re_tag = /<(?:(?:\\/([^>]+)>)|(?:!--([\\S|\\s]*?)-->)|(?:([^\\s\\/<>]+)\\s*((?:(?:\"[^\"]*\")|(?:'[^']*')|[^\"'<>])*)\\/?>))/g,\r\n        re_attr = /([\\w\\-:.]+)(?:(?:\\s*=\\s*(?:(?:\"([^\"]*)\")|(?:'([^']*)')|([^\\s>]+)))|(?=\\s|$))/g;\r\n\r\n    //ie下取得的html可能会有\\n存在，要去掉，在处理replace(/[\\t\\r\\n]*/g,'');代码高量的\\n不能去除\r\n    var allowEmptyTags = {\r\n        b:1,code:1,i:1,u:1,strike:1,s:1,tt:1,strong:1,q:1,samp:1,em:1,span:1,\r\n        sub:1,img:1,sup:1,font:1,big:1,small:1,iframe:1,a:1,br:1,pre:1\r\n    };\r\n    htmlstr = htmlstr.replace(new RegExp(domUtils.fillChar, 'g'), '');\r\n    if(!ignoreBlank){\r\n        htmlstr = htmlstr.replace(new RegExp('[\\\\r\\\\t\\\\n'+(ignoreBlank?'':' ')+']*<\\/?(\\\\w+)\\\\s*(?:[^>]*)>[\\\\r\\\\t\\\\n'+(ignoreBlank?'':' ')+']*','g'), function(a,b){\r\n            //br暂时单独处理\r\n            if(b && allowEmptyTags[b.toLowerCase()]){\r\n                return a.replace(/(^[\\n\\r]+)|([\\n\\r]+$)/g,'');\r\n            }\r\n            return a.replace(new RegExp('^[\\\\r\\\\n'+(ignoreBlank?'':' ')+']+'),'').replace(new RegExp('[\\\\r\\\\n'+(ignoreBlank?'':' ')+']+$'),'');\r\n        });\r\n    }\r\n\r\n    var notTransAttrs = {\r\n        'href':1,\r\n        'src':1\r\n    };\r\n\r\n    var uNode = UE.uNode,\r\n        needParentNode = {\r\n            'td':'tr',\r\n            'tr':['tbody','thead','tfoot'],\r\n            'tbody':'table',\r\n            'th':'tr',\r\n            'thead':'table',\r\n            'tfoot':'table',\r\n            'caption':'table',\r\n            'li':['ul', 'ol'],\r\n            'dt':'dl',\r\n            'dd':'dl',\r\n            'option':'select'\r\n        },\r\n        needChild = {\r\n            'ol':'li',\r\n            'ul':'li'\r\n        };\r\n\r\n    function text(parent, data) {\r\n\r\n        if(needChild[parent.tagName]){\r\n            var tmpNode = uNode.createElement(needChild[parent.tagName]);\r\n            parent.appendChild(tmpNode);\r\n            tmpNode.appendChild(uNode.createText(data));\r\n            parent = tmpNode;\r\n        }else{\r\n\r\n            parent.appendChild(uNode.createText(data));\r\n        }\r\n    }\r\n\r\n    function element(parent, tagName, htmlattr) {\r\n        var needParentTag;\r\n        if (needParentTag = needParentNode[tagName]) {\r\n            var tmpParent = parent,hasParent;\r\n            while(tmpParent.type != 'root'){\r\n                if(utils.isArray(needParentTag) ? utils.indexOf(needParentTag, tmpParent.tagName) != -1 : needParentTag == tmpParent.tagName){\r\n                    parent = tmpParent;\r\n                    hasParent = true;\r\n                    break;\r\n                }\r\n                tmpParent = tmpParent.parentNode;\r\n            }\r\n            if(!hasParent){\r\n                parent = element(parent, utils.isArray(needParentTag) ? needParentTag[0] : needParentTag)\r\n            }\r\n        }\r\n        //按dtd处理嵌套\r\n//        if(parent.type != 'root' && !dtd[parent.tagName][tagName])\r\n//            parent = parent.parentNode;\r\n        var elm = new uNode({\r\n            parentNode:parent,\r\n            type:'element',\r\n            tagName:tagName.toLowerCase(),\r\n            //是自闭合的处理一下\r\n            children:dtd.$empty[tagName] ? null : []\r\n        });\r\n        //如果属性存在，处理属性\r\n        if (htmlattr) {\r\n            var attrs = {}, match;\r\n            while (match = re_attr.exec(htmlattr)) {\r\n                attrs[match[1].toLowerCase()] = notTransAttrs[match[1].toLowerCase()] ? (match[2] || match[3] || match[4]) : utils.unhtml(match[2] || match[3] || match[4])\r\n            }\r\n            elm.attrs = attrs;\r\n        }\r\n        //trace:3970\r\n//        //如果parent下不能放elm\r\n//        if(dtd.$inline[parent.tagName] && dtd.$block[elm.tagName] && !dtd[parent.tagName][elm.tagName]){\r\n//            parent = parent.parentNode;\r\n//            elm.parentNode = parent;\r\n//        }\r\n        parent.children.push(elm);\r\n        //如果是自闭合节点返回父亲节点\r\n        return  dtd.$empty[tagName] ? parent : elm\r\n    }\r\n\r\n    function comment(parent, data) {\r\n        parent.children.push(new uNode({\r\n            type:'comment',\r\n            data:data,\r\n            parentNode:parent\r\n        }));\r\n    }\r\n\r\n    var match, currentIndex = 0, nextIndex = 0;\r\n    //设置根节点\r\n    var root = new uNode({\r\n        type:'root',\r\n        children:[]\r\n    });\r\n    var currentParent = root;\r\n\r\n    while (match = re_tag.exec(htmlstr)) {\r\n        currentIndex = match.index;\r\n        try{\r\n            if (currentIndex > nextIndex) {\r\n                //text node\r\n                text(currentParent, htmlstr.slice(nextIndex, currentIndex));\r\n            }\r\n            if (match[3]) {\r\n\r\n                if(dtd.$cdata[currentParent.tagName]){\r\n                    text(currentParent, match[0]);\r\n                }else{\r\n                    //start tag\r\n                    currentParent = element(currentParent, match[3].toLowerCase(), match[4]);\r\n                }\r\n\r\n\r\n            } else if (match[1]) {\r\n                if(currentParent.type != 'root'){\r\n                    if(dtd.$cdata[currentParent.tagName] && !dtd.$cdata[match[1]]){\r\n                        text(currentParent, match[0]);\r\n                    }else{\r\n                        var tmpParent = currentParent;\r\n                        while(currentParent.type == 'element' && currentParent.tagName != match[1].toLowerCase()){\r\n                            currentParent = currentParent.parentNode;\r\n                            if(currentParent.type == 'root'){\r\n                                currentParent = tmpParent;\r\n                                throw 'break'\r\n                            }\r\n                        }\r\n                        //end tag\r\n                        currentParent = currentParent.parentNode;\r\n                    }\r\n\r\n                }\r\n\r\n            } else if (match[2]) {\r\n                //comment\r\n                comment(currentParent, match[2])\r\n            }\r\n        }catch(e){}\r\n\r\n        nextIndex = re_tag.lastIndex;\r\n\r\n    }\r\n    //如果结束是文本，就有可能丢掉，所以这里手动判断一下\r\n    //例如 <li>sdfsdfsdf<li>sdfsdfsdfsdf\r\n    if (nextIndex < htmlstr.length) {\r\n        text(currentParent, htmlstr.slice(nextIndex));\r\n    }\r\n    return root;\r\n};\r\n\r\n\r\n// core/filternode.js\r\n/**\r\n * UE过滤节点的静态方法\r\n * @file\r\n */\r\n\r\n/**\r\n * UEditor公用空间，UEditor所有的功能都挂载在该空间下\r\n * @module UE\r\n */\r\n\r\n\r\n/**\r\n * 根据传入节点和过滤规则过滤相应节点\r\n * @module UE\r\n * @since 1.2.6.1\r\n * @method filterNode\r\n * @param { Object } root 指定root节点\r\n * @param { Object } rules 过滤规则json对象\r\n * @example\r\n * ```javascript\r\n * UE.filterNode(root,editor.options.filterRules);\r\n * ```\r\n */\r\nvar filterNode = UE.filterNode = function () {\r\n    function filterNode(node,rules){\r\n        switch (node.type) {\r\n            case 'text':\r\n                break;\r\n            case 'element':\r\n                var val;\r\n                if(val = rules[node.tagName]){\r\n                   if(val === '-'){\r\n                       node.parentNode.removeChild(node)\r\n                   }else if(utils.isFunction(val)){\r\n                       var parentNode = node.parentNode,\r\n                           index = node.getIndex();\r\n                       val(node);\r\n                       if(node.parentNode){\r\n                           if(node.children){\r\n                               for(var i = 0,ci;ci=node.children[i];){\r\n                                   filterNode(ci,rules);\r\n                                   if(ci.parentNode){\r\n                                       i++;\r\n                                   }\r\n                               }\r\n                           }\r\n                       }else{\r\n                           for(var i = index,ci;ci=parentNode.children[i];){\r\n                               filterNode(ci,rules);\r\n                               if(ci.parentNode){\r\n                                   i++;\r\n                               }\r\n                           }\r\n                       }\r\n\r\n\r\n                   }else{\r\n                       var attrs = val['$'];\r\n                       if(attrs && node.attrs){\r\n                           var tmpAttrs = {},tmpVal;\r\n                           for(var a in attrs){\r\n                               tmpVal = node.getAttr(a);\r\n                               //todo 只先对style单独处理\r\n                               if(a == 'style' && utils.isArray(attrs[a])){\r\n                                   var tmpCssStyle = [];\r\n                                   utils.each(attrs[a],function(v){\r\n                                       var tmp;\r\n                                       if(tmp = node.getStyle(v)){\r\n                                           tmpCssStyle.push(v + ':' + tmp);\r\n                                       }\r\n                                   });\r\n                                   tmpVal = tmpCssStyle.join(';')\r\n                               }\r\n                               if(tmpVal){\r\n                                   tmpAttrs[a] = tmpVal;\r\n                               }\r\n\r\n                           }\r\n                           node.attrs = tmpAttrs;\r\n                       }\r\n                       if(node.children){\r\n                           for(var i = 0,ci;ci=node.children[i];){\r\n                               filterNode(ci,rules);\r\n                               if(ci.parentNode){\r\n                                   i++;\r\n                               }\r\n                           }\r\n                       }\r\n                   }\r\n                }else{\r\n                    //如果不在名单里扣出子节点并删除该节点,cdata除外\r\n                    if(dtd.$cdata[node.tagName]){\r\n                        node.parentNode.removeChild(node)\r\n                    }else{\r\n                        var parentNode = node.parentNode,\r\n                            index = node.getIndex();\r\n                        node.parentNode.removeChild(node,true);\r\n                        for(var i = index,ci;ci=parentNode.children[i];){\r\n                            filterNode(ci,rules);\r\n                            if(ci.parentNode){\r\n                                i++;\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n                break;\r\n            case 'comment':\r\n                node.parentNode.removeChild(node)\r\n        }\r\n\r\n    }\r\n    return function(root,rules){\r\n        if(utils.isEmptyObject(rules)){\r\n            return root;\r\n        }\r\n        var val;\r\n        if(val = rules['-']){\r\n            utils.each(val.split(' '),function(k){\r\n                rules[k] = '-'\r\n            })\r\n        }\r\n        for(var i= 0,ci;ci=root.children[i];){\r\n            filterNode(ci,rules);\r\n            if(ci.parentNode){\r\n               i++;\r\n            }\r\n        }\r\n        return root;\r\n    }\r\n}();\r\n\r\n// core/plugin.js\r\n/**\r\n * Created with JetBrains PhpStorm.\r\n * User: campaign\r\n * Date: 10/8/13\r\n * Time: 6:15 PM\r\n * To change this template use File | Settings | File Templates.\r\n */\r\nUE.plugin = function(){\r\n    var _plugins = {};\r\n    return {\r\n        register : function(pluginName,fn,oldOptionName,afterDisabled){\r\n            if(oldOptionName && utils.isFunction(oldOptionName)){\r\n                afterDisabled = oldOptionName;\r\n                oldOptionName = null\r\n            }\r\n            _plugins[pluginName] = {\r\n                optionName : oldOptionName || pluginName,\r\n                execFn : fn,\r\n                //当插件被禁用时执行\r\n                afterDisabled : afterDisabled\r\n            }\r\n        },\r\n        load : function(editor){\r\n            utils.each(_plugins,function(plugin){\r\n                var _export = plugin.execFn.call(editor);\r\n                if(editor.options[plugin.optionName] !== false){\r\n                    if(_export){\r\n                        //后边需要再做扩展\r\n                        utils.each(_export,function(v,k){\r\n                            switch(k.toLowerCase()){\r\n                                case 'shortcutkey':\r\n                                    editor.addshortcutkey(v);\r\n                                    break;\r\n                                case 'bindevents':\r\n                                    utils.each(v,function(fn,eventName){\r\n                                        editor.addListener(eventName,fn);\r\n                                    });\r\n                                    break;\r\n                                case 'bindmultievents':\r\n                                    utils.each(utils.isArray(v) ? v:[v],function(event){\r\n                                        var types = utils.trim(event.type).split(/\\s+/);\r\n                                        utils.each(types,function(eventName){\r\n                                            editor.addListener(eventName, event.handler);\r\n                                        });\r\n                                    });\r\n                                    break;\r\n                                case 'commands':\r\n                                    utils.each(v,function(execFn,execName){\r\n                                        editor.commands[execName] = execFn\r\n                                    });\r\n                                    break;\r\n                                case 'outputrule':\r\n                                    editor.addOutputRule(v);\r\n                                    break;\r\n                                case 'inputrule':\r\n                                    editor.addInputRule(v);\r\n                                    break;\r\n                                case 'defaultoptions':\r\n                                    editor.setOpt(v)\r\n                            }\r\n                        })\r\n                    }\r\n\r\n                }else if(plugin.afterDisabled){\r\n                    plugin.afterDisabled.call(editor)\r\n                }\r\n\r\n            });\r\n            //向下兼容\r\n            utils.each(UE.plugins,function(plugin){\r\n                plugin.call(editor);\r\n            });\r\n        },\r\n        run : function(pluginName,editor){\r\n            var plugin = _plugins[pluginName];\r\n            if(plugin){\r\n                plugin.exeFn.call(editor)\r\n            }\r\n        }\r\n    }\r\n}();\r\n\r\n// core/keymap.js\r\nvar keymap = UE.keymap  = {\r\n    'Backspace' : 8,\r\n    'Tab' : 9,\r\n    'Enter' : 13,\r\n\r\n    'Shift':16,\r\n    'Control':17,\r\n    'Alt':18,\r\n    'CapsLock':20,\r\n\r\n    'Esc':27,\r\n\r\n    'Spacebar':32,\r\n\r\n    'PageUp':33,\r\n    'PageDown':34,\r\n    'End':35,\r\n    'Home':36,\r\n\r\n    'Left':37,\r\n    'Up':38,\r\n    'Right':39,\r\n    'Down':40,\r\n\r\n    'Insert':45,\r\n\r\n    'Del':46,\r\n\r\n    'NumLock':144,\r\n\r\n    'Cmd':91,\r\n\r\n    '=':187,\r\n    '-':189,\r\n\r\n    \"b\":66,\r\n    'i':73,\r\n    //回退\r\n    'z':90,\r\n    'y':89,\r\n    //粘贴\r\n    'v' : 86,\r\n    'x' : 88,\r\n\r\n    's' : 83,\r\n\r\n    'n' : 78\r\n};\r\n\r\n// core/localstorage.js\r\n//存储媒介封装\r\nvar LocalStorage = UE.LocalStorage = (function () {\r\n\r\n    var storage = window.localStorage || getUserData() || null,\r\n        LOCAL_FILE = 'localStorage';\r\n\r\n    return {\r\n\r\n        saveLocalData: function (key, data) {\r\n\r\n            if (storage && data) {\r\n                storage.setItem(key, data);\r\n                return true;\r\n            }\r\n\r\n            return false;\r\n\r\n        },\r\n\r\n        getLocalData: function (key) {\r\n\r\n            if (storage) {\r\n                return storage.getItem(key);\r\n            }\r\n\r\n            return null;\r\n\r\n        },\r\n\r\n        removeItem: function (key) {\r\n\r\n            storage && storage.removeItem(key);\r\n\r\n        }\r\n\r\n    };\r\n\r\n    function getUserData() {\r\n\r\n        var container = document.createElement(\"div\");\r\n        container.style.display = \"none\";\r\n\r\n        if (!container.addBehavior) {\r\n            return null;\r\n        }\r\n\r\n        container.addBehavior(\"#default#userdata\");\r\n\r\n        return {\r\n\r\n            getItem: function (key) {\r\n\r\n                var result = null;\r\n\r\n                try {\r\n                    document.body.appendChild(container);\r\n                    container.load(LOCAL_FILE);\r\n                    result = container.getAttribute(key);\r\n                    document.body.removeChild(container);\r\n                } catch (e) {\r\n                }\r\n\r\n                return result;\r\n\r\n            },\r\n\r\n            setItem: function (key, value) {\r\n\r\n                document.body.appendChild(container);\r\n                container.setAttribute(key, value);\r\n                container.save(LOCAL_FILE);\r\n                document.body.removeChild(container);\r\n\r\n            },\r\n\r\n            //// 暂时没有用到\r\n            //clear: function () {\r\n            //\r\n            //    var expiresTime = new Date();\r\n            //    expiresTime.setFullYear(expiresTime.getFullYear() - 1);\r\n            //    document.body.appendChild(container);\r\n            //    container.expires = expiresTime.toUTCString();\r\n            //    container.save(LOCAL_FILE);\r\n            //    document.body.removeChild(container);\r\n            //\r\n            //},\r\n\r\n            removeItem: function (key) {\r\n\r\n                document.body.appendChild(container);\r\n                container.removeAttribute(key);\r\n                container.save(LOCAL_FILE);\r\n                document.body.removeChild(container);\r\n\r\n            }\r\n\r\n        };\r\n\r\n    }\r\n\r\n})();\r\n\r\n(function () {\r\n\r\n    var ROOTKEY = 'ueditor_preference';\r\n\r\n    UE.Editor.prototype.setPreferences = function(key,value){\r\n        var obj = {};\r\n        if (utils.isString(key)) {\r\n            obj[ key ] = value;\r\n        } else {\r\n            obj = key;\r\n        }\r\n        var data = LocalStorage.getLocalData(ROOTKEY);\r\n        if (data && (data = utils.str2json(data))) {\r\n            utils.extend(data, obj);\r\n        } else {\r\n            data = obj;\r\n        }\r\n        data && LocalStorage.saveLocalData(ROOTKEY, utils.json2str(data));\r\n    };\r\n\r\n    UE.Editor.prototype.getPreferences = function(key){\r\n        var data = LocalStorage.getLocalData(ROOTKEY);\r\n        if (data && (data = utils.str2json(data))) {\r\n            return key ? data[key] : data\r\n        }\r\n        return null;\r\n    };\r\n\r\n    UE.Editor.prototype.removePreferences = function (key) {\r\n        var data = LocalStorage.getLocalData(ROOTKEY);\r\n        if (data && (data = utils.str2json(data))) {\r\n            data[key] = undefined;\r\n            delete data[key]\r\n        }\r\n        data && LocalStorage.saveLocalData(ROOTKEY, utils.json2str(data));\r\n    };\r\n\r\n})();\r\n\r\n\r\n// plugins/defaultfilter.js\r\n///import core\r\n///plugin 编辑器默认的过滤转换机制\r\n\r\nUE.plugins['defaultfilter'] = function () {\r\n    var me = this;\r\n    me.setOpt({\r\n        'allowDivTransToP':true,\r\n        'disabledTableInTable':true\r\n    });\r\n    //默认的过滤处理\r\n    //进入编辑器的内容处理\r\n    me.addInputRule(function (root) {\r\n        var allowDivTransToP = this.options.allowDivTransToP;\r\n        var val;\r\n        function tdParent(node){\r\n            while(node && node.type == 'element'){\r\n                if(node.tagName == 'td'){\r\n                    return true;\r\n                }\r\n                node = node.parentNode;\r\n            }\r\n            return false;\r\n        }\r\n        //进行默认的处理\r\n        root.traversal(function (node) {\r\n            if (node.type == 'element') {\r\n                if (!dtd.$cdata[node.tagName] && me.options.autoClearEmptyNode && dtd.$inline[node.tagName] && !dtd.$empty[node.tagName] && (!node.attrs || utils.isEmptyObject(node.attrs))) {\r\n                    if (!node.firstChild()) node.parentNode.removeChild(node);\r\n                    else if (node.tagName == 'span' && (!node.attrs || utils.isEmptyObject(node.attrs))) {\r\n                        node.parentNode.removeChild(node, true)\r\n                    }\r\n                    return;\r\n                }\r\n                switch (node.tagName) {\r\n                    case 'style':\r\n                    case 'script':\r\n                        node.setAttr({\r\n                            cdata_tag: node.tagName,\r\n                            cdata_data: (node.innerHTML() || ''),\r\n                            '_ue_custom_node_':'true'\r\n                        });\r\n                        node.tagName = 'div';\r\n                        node.innerHTML('');\r\n                        break;\r\n                    case 'a':\r\n                        if (val = node.getAttr('href')) {\r\n                            node.setAttr('_href', val)\r\n                        }\r\n                        break;\r\n                    case 'img':\r\n                        //todo base64暂时去掉，后边做远程图片上传后，干掉这个\r\n                        if (val = node.getAttr('src')) {\r\n                            if (/^data:/.test(val)) {\r\n                                node.parentNode.removeChild(node);\r\n                                break;\r\n                            }\r\n                        }\r\n                        node.setAttr('_src', node.getAttr('src'));\r\n                        break;\r\n                    case 'span':\r\n                        if (browser.webkit && (val = node.getStyle('white-space'))) {\r\n                            if (/nowrap|normal/.test(val)) {\r\n                                node.setStyle('white-space', '');\r\n                                if (me.options.autoClearEmptyNode && utils.isEmptyObject(node.attrs)) {\r\n                                    node.parentNode.removeChild(node, true)\r\n                                }\r\n                            }\r\n                        }\r\n                        val = node.getAttr('id');\r\n                        if(val && /^_baidu_bookmark_/i.test(val)){\r\n                            node.parentNode.removeChild(node)\r\n                        }\r\n                        break;\r\n                    case 'p':\r\n                        if (val = node.getAttr('align')) {\r\n                            node.setAttr('align');\r\n                            node.setStyle('text-align', val)\r\n                        }\r\n                        //trace:3431\r\n//                        var cssStyle = node.getAttr('style');\r\n//                        if (cssStyle) {\r\n//                            cssStyle = cssStyle.replace(/(margin|padding)[^;]+/g, '');\r\n//                            node.setAttr('style', cssStyle)\r\n//\r\n//                        }\r\n                        //p标签不允许嵌套\r\n                        utils.each(node.children,function(n){\r\n                            if(n.type == 'element' && n.tagName == 'p'){\r\n                                var next = n.nextSibling();\r\n                                node.parentNode.insertAfter(n,node);\r\n                                var last = n;\r\n                                while(next){\r\n                                    var tmp = next.nextSibling();\r\n                                    node.parentNode.insertAfter(next,last);\r\n                                    last = next;\r\n                                    next = tmp;\r\n                                }\r\n                                return false;\r\n                            }\r\n                        });\r\n                        if (!node.firstChild()) {\r\n                            node.innerHTML(browser.ie ? '&nbsp;' : '<br/>')\r\n                        }\r\n                        break;\r\n                    case 'div':\r\n                        if(node.getAttr('cdata_tag')){\r\n                            break;\r\n                        }\r\n                        //针对代码这里不处理插入代码的div\r\n                        val = node.getAttr('class');\r\n                        if(val && /^line number\\d+/.test(val)){\r\n                            break;\r\n                        }\r\n                        if(!allowDivTransToP){\r\n                            break;\r\n                        }\r\n                        var tmpNode, p = UE.uNode.createElement('p');\r\n                        while (tmpNode = node.firstChild()) {\r\n                            if (tmpNode.type == 'text' || !UE.dom.dtd.$block[tmpNode.tagName]) {\r\n                                p.appendChild(tmpNode);\r\n                            } else {\r\n                                if (p.firstChild()) {\r\n                                    node.parentNode.insertBefore(p, node);\r\n                                    p = UE.uNode.createElement('p');\r\n                                } else {\r\n                                    node.parentNode.insertBefore(tmpNode, node);\r\n                                }\r\n                            }\r\n                        }\r\n                        if (p.firstChild()) {\r\n                            node.parentNode.insertBefore(p, node);\r\n                        }\r\n                        node.parentNode.removeChild(node);\r\n                        break;\r\n                    case 'dl':\r\n                        node.tagName = 'ul';\r\n                        break;\r\n                    case 'dt':\r\n                    case 'dd':\r\n                        node.tagName = 'li';\r\n                        break;\r\n                    case 'li':\r\n                        var className = node.getAttr('class');\r\n                        if (!className || !/list\\-/.test(className)) {\r\n                            node.setAttr()\r\n                        }\r\n                        var tmpNodes = node.getNodesByTagName('ol ul');\r\n                        UE.utils.each(tmpNodes, function (n) {\r\n                            node.parentNode.insertAfter(n, node);\r\n                        });\r\n                        break;\r\n                    case 'td':\r\n                    case 'th':\r\n                    case 'caption':\r\n                        if(!node.children || !node.children.length){\r\n                            node.appendChild(browser.ie11below ? UE.uNode.createText(' ') : UE.uNode.createElement('br'))\r\n                        }\r\n                        break;\r\n                    case 'table':\r\n                        if(me.options.disabledTableInTable && tdParent(node)){\r\n                            node.parentNode.insertBefore(UE.uNode.createText(node.innerText()),node);\r\n                            node.parentNode.removeChild(node)\r\n                        }\r\n                }\r\n\r\n            }\r\n//            if(node.type == 'comment'){\r\n//                node.parentNode.removeChild(node);\r\n//            }\r\n        })\r\n\r\n    });\r\n\r\n    //从编辑器出去的内容处理\r\n    me.addOutputRule(function (root) {\r\n\r\n        var val;\r\n        root.traversal(function (node) {\r\n            if (node.type == 'element') {\r\n\r\n                if (me.options.autoClearEmptyNode && dtd.$inline[node.tagName] && !dtd.$empty[node.tagName] && (!node.attrs || utils.isEmptyObject(node.attrs))) {\r\n\r\n                    if (!node.firstChild()) node.parentNode.removeChild(node);\r\n                    else if (node.tagName == 'span' && (!node.attrs || utils.isEmptyObject(node.attrs))) {\r\n                        node.parentNode.removeChild(node, true)\r\n                    }\r\n                    return;\r\n                }\r\n                switch (node.tagName) {\r\n                    case 'div':\r\n                        if (val = node.getAttr('cdata_tag')) {\r\n                            node.tagName = val;\r\n                            node.appendChild(UE.uNode.createText(node.getAttr('cdata_data')));\r\n                            node.setAttr({cdata_tag: '', cdata_data: '','_ue_custom_node_':''});\r\n                        }\r\n                        break;\r\n                    case 'a':\r\n                        if (val = node.getAttr('_href')) {\r\n                            node.setAttr({\r\n                                'href': utils.html(val),\r\n                                '_href': ''\r\n                            })\r\n                        }\r\n                        break;\r\n                        break;\r\n                    case 'span':\r\n                        val = node.getAttr('id');\r\n                        if(val && /^_baidu_bookmark_/i.test(val)){\r\n                            node.parentNode.removeChild(node)\r\n                        }\r\n                        break;\r\n                    case 'img':\r\n                        if (val = node.getAttr('_src')) {\r\n                            node.setAttr({\r\n                                'src': node.getAttr('_src'),\r\n                                '_src': ''\r\n                            })\r\n                        }\r\n\r\n\r\n                }\r\n            }\r\n\r\n        })\r\n\r\n\r\n    });\r\n};\r\n\r\n\r\n// plugins/inserthtml.js\r\n/**\r\n * 插入html字符串插件\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 插入html代码\r\n * @command inserthtml\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @param { String } html 插入的html字符串\r\n * @remaind 插入的标签内容是在当前的选区位置上插入，如果当前是闭合状态，那直接插入内容， 如果当前是选中状态，将先清除当前选中内容后，再做插入\r\n * @warning 注意:该命令会对当前选区的位置，对插入的内容进行过滤转换处理。 过滤的规则遵循html语意化的原则。\r\n * @example\r\n * ```javascript\r\n * //xxx[BB]xxx 当前选区为非闭合选区，选中BB这两个文本\r\n * //执行命令，插入<b>CC</b>\r\n * //插入后的效果 xxx<b>CC</b>xxx\r\n * //<p>xx|xxx</p> 当前选区为闭合状态\r\n * //插入<p>CC</p>\r\n * //结果 <p>xx</p><p>CC</p><p>xxx</p>\r\n * //<p>xxxx</p>|</p>xxx</p> 当前选区在两个p标签之间\r\n * //插入 xxxx\r\n * //结果 <p>xxxx</p><p>xxxx</p></p>xxx</p>\r\n * ```\r\n */\r\n\r\nUE.commands['inserthtml'] = {\r\n    execCommand: function (command,html,notNeedFilter){\r\n        var me = this,\r\n            range,\r\n            div;\r\n        if(!html){\r\n            return;\r\n        }\r\n        if(me.fireEvent('beforeinserthtml',html) === true){\r\n            return;\r\n        }\r\n        range = me.selection.getRange();\r\n        div = range.document.createElement( 'div' );\r\n        div.style.display = 'inline';\r\n\r\n        if (!notNeedFilter) {\r\n            var root = UE.htmlparser(html);\r\n            //如果给了过滤规则就先进行过滤\r\n            if(me.options.filterRules){\r\n                UE.filterNode(root,me.options.filterRules);\r\n            }\r\n            //执行默认的处理\r\n            me.filterInputRule(root);\r\n            html = root.toHtml()\r\n        }\r\n        div.innerHTML = utils.trim( html );\r\n\r\n        if ( !range.collapsed ) {\r\n            var tmpNode = range.startContainer;\r\n            if(domUtils.isFillChar(tmpNode)){\r\n                range.setStartBefore(tmpNode)\r\n            }\r\n            tmpNode = range.endContainer;\r\n            if(domUtils.isFillChar(tmpNode)){\r\n                range.setEndAfter(tmpNode)\r\n            }\r\n            range.txtToElmBoundary();\r\n            //结束边界可能放到了br的前边，要把br包含进来\r\n            // x[xxx]<br/>\r\n            if(range.endContainer && range.endContainer.nodeType == 1){\r\n                tmpNode = range.endContainer.childNodes[range.endOffset];\r\n                if(tmpNode && domUtils.isBr(tmpNode)){\r\n                    range.setEndAfter(tmpNode);\r\n                }\r\n            }\r\n            if(range.startOffset == 0){\r\n                tmpNode = range.startContainer;\r\n                if(domUtils.isBoundaryNode(tmpNode,'firstChild') ){\r\n                    tmpNode = range.endContainer;\r\n                    if(range.endOffset == (tmpNode.nodeType == 3 ? tmpNode.nodeValue.length : tmpNode.childNodes.length) && domUtils.isBoundaryNode(tmpNode,'lastChild')){\r\n                        me.body.innerHTML = '<p>'+(browser.ie ? '' : '<br/>')+'</p>';\r\n                        range.setStart(me.body.firstChild,0).collapse(true)\r\n\r\n                    }\r\n                }\r\n            }\r\n            !range.collapsed && range.deleteContents();\r\n            if(range.startContainer.nodeType == 1){\r\n                var child = range.startContainer.childNodes[range.startOffset],pre;\r\n                if(child && domUtils.isBlockElm(child) && (pre = child.previousSibling) && domUtils.isBlockElm(pre)){\r\n                    range.setEnd(pre,pre.childNodes.length).collapse();\r\n                    while(child.firstChild){\r\n                        pre.appendChild(child.firstChild);\r\n                    }\r\n                    domUtils.remove(child);\r\n                }\r\n            }\r\n\r\n        }\r\n\r\n\r\n        var child,parent,pre,tmp,hadBreak = 0, nextNode;\r\n        //如果当前位置选中了fillchar要干掉，要不会产生空行\r\n        if(range.inFillChar()){\r\n            child = range.startContainer;\r\n            if(domUtils.isFillChar(child)){\r\n                range.setStartBefore(child).collapse(true);\r\n                domUtils.remove(child);\r\n            }else if(domUtils.isFillChar(child,true)){\r\n                child.nodeValue = child.nodeValue.replace(fillCharReg,'');\r\n                range.startOffset--;\r\n                range.collapsed && range.collapse(true)\r\n            }\r\n        }\r\n        //列表单独处理\r\n        var li = domUtils.findParentByTagName(range.startContainer,'li',true);\r\n        if(li){\r\n            var next,last;\r\n            while(child = div.firstChild){\r\n                //针对hr单独处理一下先\r\n                while(child && (child.nodeType == 3 || !domUtils.isBlockElm(child) || child.tagName=='HR' )){\r\n                    next = child.nextSibling;\r\n                    range.insertNode( child).collapse();\r\n                    last = child;\r\n                    child = next;\r\n\r\n                }\r\n                if(child){\r\n                    if(/^(ol|ul)$/i.test(child.tagName)){\r\n                        while(child.firstChild){\r\n                            last = child.firstChild;\r\n                            domUtils.insertAfter(li,child.firstChild);\r\n                            li = li.nextSibling;\r\n                        }\r\n                        domUtils.remove(child)\r\n                    }else{\r\n                        var tmpLi;\r\n                        next = child.nextSibling;\r\n                        tmpLi = me.document.createElement('li');\r\n                        domUtils.insertAfter(li,tmpLi);\r\n                        tmpLi.appendChild(child);\r\n                        last = child;\r\n                        child = next;\r\n                        li = tmpLi;\r\n                    }\r\n                }\r\n            }\r\n            li = domUtils.findParentByTagName(range.startContainer,'li',true);\r\n            if(domUtils.isEmptyBlock(li)){\r\n                domUtils.remove(li)\r\n            }\r\n            if(last){\r\n\r\n                range.setStartAfter(last).collapse(true).select(true)\r\n            }\r\n        }else{\r\n            while ( child = div.firstChild ) {\r\n                if(hadBreak){\r\n                    var p = me.document.createElement('p');\r\n                    while(child && (child.nodeType == 3 || !dtd.$block[child.tagName])){\r\n                        nextNode = child.nextSibling;\r\n                        p.appendChild(child);\r\n                        child = nextNode;\r\n                    }\r\n                    if(p.firstChild){\r\n\r\n                        child = p\r\n                    }\r\n                }\r\n                range.insertNode( child );\r\n                nextNode = child.nextSibling;\r\n                if ( !hadBreak && child.nodeType == domUtils.NODE_ELEMENT && domUtils.isBlockElm( child ) ){\r\n\r\n                    parent = domUtils.findParent( child,function ( node ){ return domUtils.isBlockElm( node ); } );\r\n                    if ( parent && parent.tagName.toLowerCase() != 'body' && !(dtd[parent.tagName][child.nodeName] && child.parentNode === parent)){\r\n                        if(!dtd[parent.tagName][child.nodeName]){\r\n                            pre = parent;\r\n                        }else{\r\n                            tmp = child.parentNode;\r\n                            while (tmp !== parent){\r\n                                pre = tmp;\r\n                                tmp = tmp.parentNode;\r\n\r\n                            }\r\n                        }\r\n\r\n\r\n                        domUtils.breakParent( child, pre || tmp );\r\n                        //去掉break后前一个多余的节点  <p>|<[p> ==> <p></p><div></div><p>|</p>\r\n                        var pre = child.previousSibling;\r\n                        domUtils.trimWhiteTextNode(pre);\r\n                        if(!pre.childNodes.length){\r\n                            domUtils.remove(pre);\r\n                        }\r\n                        //trace:2012,在非ie的情况，切开后剩下的节点有可能不能点入光标添加br占位\r\n\r\n                        if(!browser.ie &&\r\n                            (next = child.nextSibling) &&\r\n                            domUtils.isBlockElm(next) &&\r\n                            next.lastChild &&\r\n                            !domUtils.isBr(next.lastChild)){\r\n                            next.appendChild(me.document.createElement('br'));\r\n                        }\r\n                        hadBreak = 1;\r\n                    }\r\n                }\r\n                var next = child.nextSibling;\r\n                if(!div.firstChild && next && domUtils.isBlockElm(next)){\r\n\r\n                    range.setStart(next,0).collapse(true);\r\n                    break;\r\n                }\r\n                range.setEndAfter( child ).collapse();\r\n\r\n            }\r\n\r\n            child = range.startContainer;\r\n\r\n            if(nextNode && domUtils.isBr(nextNode)){\r\n                domUtils.remove(nextNode)\r\n            }\r\n            //用chrome可能有空白展位符\r\n            if(domUtils.isBlockElm(child) && domUtils.isEmptyNode(child)){\r\n                if(nextNode = child.nextSibling){\r\n                    domUtils.remove(child);\r\n                    if(nextNode.nodeType == 1 && dtd.$block[nextNode.tagName]){\r\n\r\n                        range.setStart(nextNode,0).collapse(true).shrinkBoundary()\r\n                    }\r\n                }else{\r\n\r\n                    try{\r\n                        child.innerHTML = browser.ie ? domUtils.fillChar : '<br/>';\r\n                    }catch(e){\r\n                        range.setStartBefore(child);\r\n                        domUtils.remove(child)\r\n                    }\r\n\r\n                }\r\n\r\n            }\r\n            //加上true因为在删除表情等时会删两次，第一次是删的fillData\r\n            try{\r\n                range.select(true);\r\n            }catch(e){}\r\n\r\n        }\r\n\r\n\r\n\r\n        setTimeout(function(){\r\n            range = me.selection.getRange();\r\n            range.scrollToView(me.autoHeightEnabled,me.autoHeightEnabled ? domUtils.getXY(me.iframe).y:0);\r\n            me.fireEvent('afterinserthtml', html);\r\n        },200);\r\n    }\r\n};\r\n\r\n\r\n// plugins/autotypeset.js\r\n/**\r\n * 自动排版\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 对当前编辑器的内容执行自动排版， 排版的行为根据config配置文件里的“autotypeset”选项进行控制。\r\n * @command autotypeset\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'autotypeset' );\r\n * ```\r\n */\r\n\r\nUE.plugins['autotypeset'] = function(){\r\n\r\n    this.setOpt({'autotypeset': {\r\n        mergeEmptyline: true,           //合并空行\r\n        removeClass: true,              //去掉冗余的class\r\n        removeEmptyline: false,         //去掉空行\r\n        textAlign:\"left\",               //段落的排版方式，可以是 left,right,center,justify 去掉这个属性表示不执行排版\r\n        imageBlockLine: 'center',       //图片的浮动方式，独占一行剧中,左右浮动，默认: center,left,right,none 去掉这个属性表示不执行排版\r\n        pasteFilter: false,             //根据规则过滤没事粘贴进来的内容\r\n        clearFontSize: false,           //去掉所有的内嵌字号，使用编辑器默认的字号\r\n        clearFontFamily: false,         //去掉所有的内嵌字体，使用编辑器默认的字体\r\n        removeEmptyNode: false,         // 去掉空节点\r\n        //可以去掉的标签\r\n        removeTagNames: utils.extend({div:1},dtd.$removeEmpty),\r\n        indent: false,                  // 行首缩进\r\n        indentValue : '2em',            //行首缩进的大小\r\n        bdc2sb: false,\r\n        tobdc: false\r\n    }});\r\n\r\n    var me = this,\r\n        opt = me.options.autotypeset,\r\n        remainClass = {\r\n            'selectTdClass':1,\r\n            'pagebreak':1,\r\n            'anchorclass':1\r\n        },\r\n        remainTag = {\r\n            'li':1\r\n        },\r\n        tags = {\r\n            div:1,\r\n            p:1,\r\n            //trace:2183 这些也认为是行\r\n            blockquote:1,center:1,h1:1,h2:1,h3:1,h4:1,h5:1,h6:1,\r\n            span:1\r\n        },\r\n        highlightCont;\r\n    //升级了版本，但配置项目里没有autotypeset\r\n    if(!opt){\r\n        return;\r\n    }\r\n\r\n    readLocalOpts();\r\n\r\n    function isLine(node,notEmpty){\r\n        if(!node || node.nodeType == 3)\r\n            return 0;\r\n        if(domUtils.isBr(node))\r\n            return 1;\r\n        if(node && node.parentNode && tags[node.tagName.toLowerCase()]){\r\n            if(highlightCont && highlightCont.contains(node)\r\n                ||\r\n                node.getAttribute('pagebreak')\r\n            ){\r\n                return 0;\r\n            }\r\n\r\n            return notEmpty ? !domUtils.isEmptyBlock(node) : domUtils.isEmptyBlock(node,new RegExp('[\\\\s'+domUtils.fillChar\r\n                +']','g'));\r\n        }\r\n    }\r\n\r\n    function removeNotAttributeSpan(node){\r\n        if(!node.style.cssText){\r\n            domUtils.removeAttributes(node,['style']);\r\n            if(node.tagName.toLowerCase() == 'span' && domUtils.hasNoAttributes(node)){\r\n                domUtils.remove(node,true);\r\n            }\r\n        }\r\n    }\r\n    function autotype(type,html){\r\n\r\n        var me = this,cont;\r\n        if(html){\r\n            if(!opt.pasteFilter){\r\n                return;\r\n            }\r\n            cont = me.document.createElement('div');\r\n            cont.innerHTML = html.html;\r\n        }else{\r\n            cont = me.document.body;\r\n        }\r\n        var nodes = domUtils.getElementsByTagName(cont,'*');\r\n\r\n        // 行首缩进，段落方向，段间距，段内间距\r\n        for(var i=0,ci;ci=nodes[i++];){\r\n\r\n            if(me.fireEvent('excludeNodeinautotype',ci) === true){\r\n                continue;\r\n            }\r\n             //font-size\r\n            if(opt.clearFontSize && ci.style.fontSize){\r\n                domUtils.removeStyle(ci,'font-size');\r\n\r\n                removeNotAttributeSpan(ci);\r\n\r\n            }\r\n            //font-family\r\n            if(opt.clearFontFamily && ci.style.fontFamily){\r\n                domUtils.removeStyle(ci,'font-family');\r\n                removeNotAttributeSpan(ci);\r\n            }\r\n\r\n            if(isLine(ci)){\r\n                //合并空行\r\n                if(opt.mergeEmptyline ){\r\n                    var next = ci.nextSibling,tmpNode,isBr = domUtils.isBr(ci);\r\n                    while(isLine(next)){\r\n                        tmpNode = next;\r\n                        next = tmpNode.nextSibling;\r\n                        if(isBr && (!next || next && !domUtils.isBr(next))){\r\n                            break;\r\n                        }\r\n                        domUtils.remove(tmpNode);\r\n                    }\r\n\r\n                }\r\n                 //去掉空行，保留占位的空行\r\n                if(opt.removeEmptyline && domUtils.inDoc(ci,cont) && !remainTag[ci.parentNode.tagName.toLowerCase()] ){\r\n                    if(domUtils.isBr(ci)){\r\n                        next = ci.nextSibling;\r\n                        if(next && !domUtils.isBr(next)){\r\n                            continue;\r\n                        }\r\n                    }\r\n                    domUtils.remove(ci);\r\n                    continue;\r\n\r\n                }\r\n\r\n            }\r\n            if(isLine(ci,true) && ci.tagName != 'SPAN'){\r\n                if(opt.indent){\r\n                    ci.style.textIndent = opt.indentValue;\r\n                }\r\n                if(opt.textAlign){\r\n                    ci.style.textAlign = opt.textAlign;\r\n                }\r\n                // if(opt.lineHeight)\r\n                //     ci.style.lineHeight = opt.lineHeight + 'cm';\r\n\r\n            }\r\n\r\n            //去掉class,保留的class不去掉\r\n            if(opt.removeClass && ci.className && !remainClass[ci.className.toLowerCase()]){\r\n\r\n                if(highlightCont && highlightCont.contains(ci)){\r\n                     continue;\r\n                }\r\n                domUtils.removeAttributes(ci,['class']);\r\n            }\r\n\r\n            //表情不处理\r\n            if(opt.imageBlockLine && ci.tagName.toLowerCase() == 'img' && !ci.getAttribute('emotion')){\r\n                if(html){\r\n                    var img = ci;\r\n                    switch (opt.imageBlockLine){\r\n                        case 'left':\r\n                        case 'right':\r\n                        case 'none':\r\n                            var pN = img.parentNode,tmpNode,pre,next;\r\n                            while(dtd.$inline[pN.tagName] || pN.tagName == 'A'){\r\n                                pN = pN.parentNode;\r\n                            }\r\n                            tmpNode = pN;\r\n                            if(tmpNode.tagName == 'P' && domUtils.getStyle(tmpNode,'text-align') == 'center'){\r\n                                if(!domUtils.isBody(tmpNode) && domUtils.getChildCount(tmpNode,function(node){return !domUtils.isBr(node) && !domUtils.isWhitespace(node)}) == 1){\r\n                                    pre = tmpNode.previousSibling;\r\n                                    next = tmpNode.nextSibling;\r\n                                    if(pre && next && pre.nodeType == 1 &&  next.nodeType == 1 && pre.tagName == next.tagName && domUtils.isBlockElm(pre)){\r\n                                        pre.appendChild(tmpNode.firstChild);\r\n                                        while(next.firstChild){\r\n                                            pre.appendChild(next.firstChild);\r\n                                        }\r\n                                        domUtils.remove(tmpNode);\r\n                                        domUtils.remove(next);\r\n                                    }else{\r\n                                        domUtils.setStyle(tmpNode,'text-align','');\r\n                                    }\r\n\r\n\r\n                                }\r\n\r\n\r\n                            }\r\n                            domUtils.setStyle(img,'float', opt.imageBlockLine);\r\n                            break;\r\n                        case 'center':\r\n                            if(me.queryCommandValue('imagefloat') != 'center'){\r\n                                pN = img.parentNode;\r\n                                domUtils.setStyle(img,'float','none');\r\n                                tmpNode = img;\r\n                                while(pN && domUtils.getChildCount(pN,function(node){return !domUtils.isBr(node) && !domUtils.isWhitespace(node)}) == 1\r\n                                    && (dtd.$inline[pN.tagName] || pN.tagName == 'A')){\r\n                                    tmpNode = pN;\r\n                                    pN = pN.parentNode;\r\n                                }\r\n                                var pNode = me.document.createElement('p');\r\n                                domUtils.setAttributes(pNode,{\r\n\r\n                                    style:'text-align:center'\r\n                                });\r\n                                tmpNode.parentNode.insertBefore(pNode,tmpNode);\r\n                                pNode.appendChild(tmpNode);\r\n                                domUtils.setStyle(tmpNode,'float','');\r\n\r\n                            }\r\n\r\n\r\n                    }\r\n                } else {\r\n                    var range = me.selection.getRange();\r\n                    range.selectNode(ci).select();\r\n                    me.execCommand('imagefloat', opt.imageBlockLine);\r\n                }\r\n\r\n            }\r\n\r\n            //去掉冗余的标签\r\n            if(opt.removeEmptyNode){\r\n                if(opt.removeTagNames[ci.tagName.toLowerCase()] && domUtils.hasNoAttributes(ci) && domUtils.isEmptyBlock(ci)){\r\n                    domUtils.remove(ci);\r\n                }\r\n            }\r\n        }\r\n        if(opt.tobdc){\r\n            var root = UE.htmlparser(cont.innerHTML);\r\n            root.traversal(function(node){\r\n                if(node.type == 'text'){\r\n                    node.data = ToDBC(node.data)\r\n                }\r\n            });\r\n            cont.innerHTML = root.toHtml()\r\n        }\r\n        if(opt.bdc2sb){\r\n            var root = UE.htmlparser(cont.innerHTML);\r\n            root.traversal(function(node){\r\n                if(node.type == 'text'){\r\n                    node.data = DBC2SB(node.data)\r\n                }\r\n            });\r\n            cont.innerHTML = root.toHtml()\r\n        }\r\n        if(html){\r\n            html.html = cont.innerHTML;\r\n        }\r\n    }\r\n    if(opt.pasteFilter){\r\n        me.addListener('beforepaste',autotype);\r\n    }\r\n\r\n    function DBC2SB(str) {\r\n        var result = '';\r\n        for (var i = 0; i < str.length; i++) {\r\n            var code = str.charCodeAt(i); //获取当前字符的unicode编码\r\n            if (code >= 65281 && code <= 65373)//在这个unicode编码范围中的是所有的英文字母已经各种字符\r\n            {\r\n                result += String.fromCharCode(str.charCodeAt(i) - 65248); //把全角字符的unicode编码转换为对应半角字符的unicode码\r\n            } else if (code == 12288)//空格\r\n            {\r\n                result += String.fromCharCode(str.charCodeAt(i) - 12288 + 32);\r\n            } else {\r\n                result += str.charAt(i);\r\n            }\r\n        }\r\n        return result;\r\n    }\r\n    function ToDBC(txtstring) {\r\n        txtstring = utils.html(txtstring);\r\n        var tmp = \"\";\r\n        var mark = \"\";/*用于判断,如果是html尖括里的标记,则不进行全角的转换*/\r\n        for (var i = 0; i < txtstring.length; i++) {\r\n            if (txtstring.charCodeAt(i) == 32) {\r\n                tmp = tmp + String.fromCharCode(12288);\r\n            }\r\n            else if (txtstring.charCodeAt(i) < 127) {\r\n                tmp = tmp + String.fromCharCode(txtstring.charCodeAt(i) + 65248);\r\n            }\r\n            else {\r\n                tmp += txtstring.charAt(i);\r\n            }\r\n        }\r\n        return tmp;\r\n    }\r\n\r\n    function readLocalOpts() {\r\n        var cookieOpt = me.getPreferences('autotypeset');\r\n        utils.extend(me.options.autotypeset, cookieOpt);\r\n    }\r\n\r\n    me.commands['autotypeset'] = {\r\n        execCommand:function () {\r\n            me.removeListener('beforepaste',autotype);\r\n            if(opt.pasteFilter){\r\n                me.addListener('beforepaste',autotype);\r\n            }\r\n            autotype.call(me)\r\n        }\r\n\r\n    };\r\n\r\n};\r\n\r\n\r\n\r\n// plugins/autosubmit.js\r\n/**\r\n * 快捷键提交\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 提交表单\r\n * @command autosubmit\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'autosubmit' );\r\n * ```\r\n */\r\n\r\nUE.plugin.register('autosubmit',function(){\r\n    return {\r\n        shortcutkey:{\r\n            \"autosubmit\":\"ctrl+13\" //手动提交\r\n        },\r\n        commands:{\r\n            'autosubmit':{\r\n                execCommand:function () {\r\n                    var me=this,\r\n                        form = domUtils.findParentByTagName(me.iframe,\"form\", false);\r\n                    if (form){\r\n                        if(me.fireEvent(\"beforesubmit\")===false){\r\n                            return;\r\n                        }\r\n                        me.sync();\r\n                        form.submit();\r\n                    }\r\n                }\r\n            }\r\n        }\r\n    }\r\n});\r\n\r\n// plugins/background.js\r\n/**\r\n * 背景插件，为UEditor提供设置背景功能\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\nUE.plugin.register('background', function () {\r\n    var me = this,\r\n        cssRuleId = 'editor_background',\r\n        isSetColored,\r\n        reg = new RegExp('body[\\\\s]*\\\\{(.+)\\\\}', 'i');\r\n\r\n    function stringToObj(str) {\r\n        var obj = {}, styles = str.split(';');\r\n        utils.each(styles, function (v) {\r\n            var index = v.indexOf(':'),\r\n                key = utils.trim(v.substr(0, index)).toLowerCase();\r\n            key && (obj[key] = utils.trim(v.substr(index + 1) || ''));\r\n        });\r\n        return obj;\r\n    }\r\n\r\n    function setBackground(obj) {\r\n        if (obj) {\r\n            var styles = [];\r\n            for (var name in obj) {\r\n                if (obj.hasOwnProperty(name)) {\r\n                    styles.push(name + \":\" + obj[name] + '; ');\r\n                }\r\n            }\r\n            utils.cssRule(cssRuleId, styles.length ? ('body{' + styles.join(\"\") + '}') : '', me.document);\r\n        } else {\r\n            utils.cssRule(cssRuleId, '', me.document)\r\n        }\r\n    }\r\n    //重写editor.hasContent方法\r\n\r\n    var orgFn = me.hasContents;\r\n    me.hasContents = function(){\r\n        if(me.queryCommandValue('background')){\r\n            return true\r\n        }\r\n        return orgFn.apply(me,arguments);\r\n    };\r\n    return {\r\n        bindEvents: {\r\n            'getAllHtml': function (type, headHtml) {\r\n                var body = this.body,\r\n                    su = domUtils.getComputedStyle(body, \"background-image\"),\r\n                    url = \"\";\r\n                if (su.indexOf(me.options.imagePath) > 0) {\r\n                    url = su.substring(su.indexOf(me.options.imagePath), su.length - 1).replace(/\"|\\(|\\)/ig, \"\");\r\n                } else {\r\n                    url = su != \"none\" ? su.replace(/url\\(\"?|\"?\\)/ig, \"\") : \"\";\r\n                }\r\n                var html = '<style type=\"text/css\">body{';\r\n                var bgObj = {\r\n                    \"background-color\": domUtils.getComputedStyle(body, \"background-color\") || \"#ffffff\",\r\n                    'background-image': url ? 'url(' + url + ')' : '',\r\n                    'background-repeat': domUtils.getComputedStyle(body, \"background-repeat\") || \"\",\r\n                    'background-position': browser.ie ? (domUtils.getComputedStyle(body, \"background-position-x\") + \" \" + domUtils.getComputedStyle(body, \"background-position-y\")) : domUtils.getComputedStyle(body, \"background-position\"),\r\n                    'height': domUtils.getComputedStyle(body, \"height\")\r\n                };\r\n                for (var name in bgObj) {\r\n                    if (bgObj.hasOwnProperty(name)) {\r\n                        html += name + \":\" + bgObj[name] + \"; \";\r\n                    }\r\n                }\r\n                html += '}</style> ';\r\n                headHtml.push(html);\r\n            },\r\n            'aftersetcontent': function () {\r\n                if(isSetColored == false) setBackground();\r\n            }\r\n        },\r\n        inputRule: function (root) {\r\n            isSetColored = false;\r\n            utils.each(root.getNodesByTagName('p'), function (p) {\r\n                var styles = p.getAttr('data-background');\r\n                if (styles) {\r\n                    isSetColored = true;\r\n                    setBackground(stringToObj(styles));\r\n                    p.parentNode.removeChild(p);\r\n                }\r\n            })\r\n        },\r\n        outputRule: function (root) {\r\n            var me = this,\r\n                styles = (utils.cssRule(cssRuleId, me.document) || '').replace(/[\\n\\r]+/g, '').match(reg);\r\n            if (styles) {\r\n                root.appendChild(UE.uNode.createElement('<p style=\"display:none;\" data-background=\"' + utils.trim(styles[1].replace(/\"/g, '').replace(/[\\s]+/g, ' ')) + '\"><br/></p>'));\r\n            }\r\n        },\r\n        commands: {\r\n            'background': {\r\n                execCommand: function (cmd, obj) {\r\n                    setBackground(obj);\r\n                },\r\n                queryCommandValue: function () {\r\n                    var me = this,\r\n                        styles = (utils.cssRule(cssRuleId, me.document) || '').replace(/[\\n\\r]+/g, '').match(reg);\r\n                    return styles ? stringToObj(styles[1]) : null;\r\n                },\r\n                notNeedUndo: true\r\n            }\r\n        }\r\n    }\r\n});\r\n\r\n// plugins/image.js\r\n/**\r\n * 图片插入、排版插件\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 图片对齐方式\r\n * @command imagefloat\r\n * @method execCommand\r\n * @remind 值center为独占一行居中\r\n * @param { String } cmd 命令字符串\r\n * @param { String } align 对齐方式，可传left、right、none、center\r\n * @remaind center表示图片独占一行\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'imagefloat', 'center' );\r\n * ```\r\n */\r\n\r\n/**\r\n * 如果选区所在位置是图片区域\r\n * @command imagefloat\r\n * @method queryCommandValue\r\n * @param { String } cmd 命令字符串\r\n * @return { String } 返回图片对齐方式\r\n * @example\r\n * ```javascript\r\n * editor.queryCommandValue( 'imagefloat' );\r\n * ```\r\n */\r\n\r\nUE.commands['imagefloat'] = {\r\n    execCommand:function (cmd, align) {\r\n        var me = this,\r\n            range = me.selection.getRange();\r\n        if (!range.collapsed) {\r\n            var img = range.getClosedNode();\r\n            if (img && img.tagName == 'IMG') {\r\n                switch (align) {\r\n                    case 'left':\r\n                    case 'right':\r\n                    case 'none':\r\n                        var pN = img.parentNode, tmpNode, pre, next;\r\n                        while (dtd.$inline[pN.tagName] || pN.tagName == 'A') {\r\n                            pN = pN.parentNode;\r\n                        }\r\n                        tmpNode = pN;\r\n                        if (tmpNode.tagName == 'P' && domUtils.getStyle(tmpNode, 'text-align') == 'center') {\r\n                            if (!domUtils.isBody(tmpNode) && domUtils.getChildCount(tmpNode, function (node) {\r\n                                return !domUtils.isBr(node) && !domUtils.isWhitespace(node);\r\n                            }) == 1) {\r\n                                pre = tmpNode.previousSibling;\r\n                                next = tmpNode.nextSibling;\r\n                                if (pre && next && pre.nodeType == 1 && next.nodeType == 1 && pre.tagName == next.tagName && domUtils.isBlockElm(pre)) {\r\n                                    pre.appendChild(tmpNode.firstChild);\r\n                                    while (next.firstChild) {\r\n                                        pre.appendChild(next.firstChild);\r\n                                    }\r\n                                    domUtils.remove(tmpNode);\r\n                                    domUtils.remove(next);\r\n                                } else {\r\n                                    domUtils.setStyle(tmpNode, 'text-align', '');\r\n                                }\r\n\r\n\r\n                            }\r\n\r\n                            range.selectNode(img).select();\r\n                        }\r\n                        domUtils.setStyle(img, 'float', align == 'none' ? '' : align);\r\n                        if(align == 'none'){\r\n                            domUtils.removeAttributes(img,'align');\r\n                        }\r\n\r\n                        break;\r\n                    case 'center':\r\n                        if (me.queryCommandValue('imagefloat') != 'center') {\r\n                            pN = img.parentNode;\r\n                            domUtils.setStyle(img, 'float', '');\r\n                            domUtils.removeAttributes(img,'align');\r\n                            tmpNode = img;\r\n                            while (pN && domUtils.getChildCount(pN, function (node) {\r\n                                return !domUtils.isBr(node) && !domUtils.isWhitespace(node);\r\n                            }) == 1\r\n                                && (dtd.$inline[pN.tagName] || pN.tagName == 'A')) {\r\n                                tmpNode = pN;\r\n                                pN = pN.parentNode;\r\n                            }\r\n                            range.setStartBefore(tmpNode).setCursor(false);\r\n                            pN = me.document.createElement('div');\r\n                            pN.appendChild(tmpNode);\r\n                            domUtils.setStyle(tmpNode, 'float', '');\r\n\r\n                            me.execCommand('insertHtml', '<p id=\"_img_parent_tmp\" style=\"text-align:center\">' + pN.innerHTML + '</p>');\r\n\r\n                            tmpNode = me.document.getElementById('_img_parent_tmp');\r\n                            tmpNode.removeAttribute('id');\r\n                            tmpNode = tmpNode.firstChild;\r\n                            range.selectNode(tmpNode).select();\r\n                            //去掉后边多余的元素\r\n                            next = tmpNode.parentNode.nextSibling;\r\n                            if (next && domUtils.isEmptyNode(next)) {\r\n                                domUtils.remove(next);\r\n                            }\r\n\r\n                        }\r\n\r\n                        break;\r\n                }\r\n\r\n            }\r\n        }\r\n    },\r\n    queryCommandValue:function () {\r\n        var range = this.selection.getRange(),\r\n            startNode, floatStyle;\r\n        if (range.collapsed) {\r\n            return 'none';\r\n        }\r\n        startNode = range.getClosedNode();\r\n        if (startNode && startNode.nodeType == 1 && startNode.tagName == 'IMG') {\r\n            floatStyle = domUtils.getComputedStyle(startNode, 'float') || startNode.getAttribute('align');\r\n\r\n            if (floatStyle == 'none') {\r\n                floatStyle = domUtils.getComputedStyle(startNode.parentNode, 'text-align') == 'center' ? 'center' : floatStyle;\r\n            }\r\n            return {\r\n                left:1,\r\n                right:1,\r\n                center:1\r\n            }[floatStyle] ? floatStyle : 'none';\r\n        }\r\n        return 'none';\r\n\r\n\r\n    },\r\n    queryCommandState:function () {\r\n        var range = this.selection.getRange(),\r\n            startNode;\r\n\r\n        if (range.collapsed)  return -1;\r\n\r\n        startNode = range.getClosedNode();\r\n        if (startNode && startNode.nodeType == 1 && startNode.tagName == 'IMG') {\r\n            return 0;\r\n        }\r\n        return -1;\r\n    }\r\n};\r\n\r\n\r\n/**\r\n * 插入图片\r\n * @command insertimage\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @param { Object } opt 属性键值对，这些属性都将被复制到当前插入图片\r\n * @remind 该命令第二个参数可接受一个图片配置项对象的数组，可以插入多张图片，\r\n * 此时数组的每一个元素都是一个Object类型的图片属性集合。\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'insertimage', {\r\n *     src:'a/b/c.jpg',\r\n *     width:'100',\r\n *     height:'100'\r\n * } );\r\n * ```\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'insertimage', [{\r\n *     src:'a/b/c.jpg',\r\n *     width:'100',\r\n *     height:'100'\r\n * },{\r\n *     src:'a/b/d.jpg',\r\n *     width:'100',\r\n *     height:'100'\r\n * }] );\r\n * ```\r\n */\r\n\r\nUE.commands['insertimage'] = {\r\n    execCommand:function (cmd, opt) {\r\n\r\n        opt = utils.isArray(opt) ? opt : [opt];\r\n        if (!opt.length) {\r\n            return;\r\n        }\r\n        var me = this,\r\n            range = me.selection.getRange(),\r\n            img = range.getClosedNode();\r\n\r\n        if(me.fireEvent('beforeinsertimage', opt) === true){\r\n            return;\r\n        }\r\n\r\n        function unhtmlData(imgCi) {\r\n\r\n            utils.each('width,height,border,hspace,vspace'.split(','), function (item) {\r\n\r\n                if (imgCi[item]) {\r\n                    imgCi[item] = parseInt(imgCi[item], 10) || 0;\r\n                }\r\n            });\r\n\r\n            utils.each('src,_src'.split(','), function (item) {\r\n\r\n                if (imgCi[item]) {\r\n                    imgCi[item] = utils.unhtmlForUrl(imgCi[item]);\r\n                }\r\n            });\r\n            utils.each('title,alt'.split(','), function (item) {\r\n\r\n                if (imgCi[item]) {\r\n                    imgCi[item] = utils.unhtml(imgCi[item]);\r\n                }\r\n            });\r\n        }\r\n\r\n        if (img && /img/i.test(img.tagName) && (img.className != \"edui-faked-video\" || img.className.indexOf(\"edui-upload-video\")!=-1) && !img.getAttribute(\"word_img\")) {\r\n            var first = opt.shift();\r\n            var floatStyle = first['floatStyle'];\r\n            delete first['floatStyle'];\r\n////                img.style.border = (first.border||0) +\"px solid #000\";\r\n////                img.style.margin = (first.margin||0) +\"px\";\r\n//                img.style.cssText += ';margin:' + (first.margin||0) +\"px;\" + 'border:' + (first.border||0) +\"px solid #000\";\r\n            domUtils.setAttributes(img, first);\r\n            me.execCommand('imagefloat', floatStyle);\r\n            if (opt.length > 0) {\r\n                range.setStartAfter(img).setCursor(false, true);\r\n                me.execCommand('insertimage', opt);\r\n            }\r\n\r\n        } else {\r\n            var html = [], str = '', ci;\r\n            ci = opt[0];\r\n            if (opt.length == 1) {\r\n                unhtmlData(ci);\r\n\r\n                str = '<img src=\"' + ci.src + '\" ' + (ci._src ? ' _src=\"' + ci._src + '\" ' : '') +\r\n                    (ci.width ? 'width=\"' + ci.width + '\" ' : '') +\r\n                    (ci.height ? ' height=\"' + ci.height + '\" ' : '') +\r\n                    (ci['floatStyle'] == 'left' || ci['floatStyle'] == 'right' ? ' style=\"float:' + ci['floatStyle'] + ';\"' : '') +\r\n                    (ci.title && ci.title != \"\" ? ' title=\"' + ci.title + '\"' : '') +\r\n                    (ci.border && ci.border != \"0\" ? ' border=\"' + ci.border + '\"' : '') +\r\n                    (ci.alt && ci.alt != \"\" ? ' alt=\"' + ci.alt + '\"' : '') +\r\n                    (ci.hspace && ci.hspace != \"0\" ? ' hspace = \"' + ci.hspace + '\"' : '') +\r\n                    (ci.vspace && ci.vspace != \"0\" ? ' vspace = \"' + ci.vspace + '\"' : '') + '/>';\r\n                if (ci['floatStyle'] == 'center') {\r\n                    str = '<p style=\"text-align: center\">' + str + '</p>';\r\n                }\r\n                html.push(str);\r\n\r\n            } else {\r\n                for (var i = 0; ci = opt[i++];) {\r\n                    unhtmlData(ci);\r\n                    str = '<p ' + (ci['floatStyle'] == 'center' ? 'style=\"text-align: center\" ' : '') + '><img src=\"' + ci.src + '\" ' +\r\n                        (ci.width ? 'width=\"' + ci.width + '\" ' : '') + (ci._src ? ' _src=\"' + ci._src + '\" ' : '') +\r\n                        (ci.height ? ' height=\"' + ci.height + '\" ' : '') +\r\n                        ' style=\"' + (ci['floatStyle'] && ci['floatStyle'] != 'center' ? 'float:' + ci['floatStyle'] + ';' : '') +\r\n                        (ci.border || '') + '\" ' +\r\n                        (ci.title ? ' title=\"' + ci.title + '\"' : '') + ' /></p>';\r\n                    html.push(str);\r\n                }\r\n            }\r\n\r\n            me.execCommand('insertHtml', html.join(''));\r\n        }\r\n\r\n        me.fireEvent('afterinsertimage', opt)\r\n    }\r\n};\r\n\r\n\r\n// plugins/justify.js\r\n/**\r\n * 段落格式\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 段落对齐方式\r\n * @command justify\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @param { String } align 对齐方式：left => 居左，right => 居右，center => 居中，justify => 两端对齐\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'justify', 'center' );\r\n * ```\r\n */\r\n/**\r\n * 如果选区所在位置是段落区域，返回当前段落对齐方式\r\n * @command justify\r\n * @method queryCommandValue\r\n * @param { String } cmd 命令字符串\r\n * @return { String } 返回段落对齐方式\r\n * @example\r\n * ```javascript\r\n * editor.queryCommandValue( 'justify' );\r\n * ```\r\n */\r\n\r\nUE.plugins['justify']=function(){\r\n    var me=this,\r\n        block = domUtils.isBlockElm,\r\n        defaultValue = {\r\n            left:1,\r\n            right:1,\r\n            center:1,\r\n            justify:1\r\n        },\r\n        doJustify = function (range, style) {\r\n            var bookmark = range.createBookmark(),\r\n                filterFn = function (node) {\r\n                    return node.nodeType == 1 ? node.tagName.toLowerCase() != 'br' && !domUtils.isBookmarkNode(node) : !domUtils.isWhitespace(node);\r\n                };\r\n\r\n            range.enlarge(true);\r\n            var bookmark2 = range.createBookmark(),\r\n                current = domUtils.getNextDomNode(bookmark2.start, false, filterFn),\r\n                tmpRange = range.cloneRange(),\r\n                tmpNode;\r\n            while (current && !(domUtils.getPosition(current, bookmark2.end) & domUtils.POSITION_FOLLOWING)) {\r\n                if (current.nodeType == 3 || !block(current)) {\r\n                    tmpRange.setStartBefore(current);\r\n                    while (current && current !== bookmark2.end && !block(current)) {\r\n                        tmpNode = current;\r\n                        current = domUtils.getNextDomNode(current, false, null, function (node) {\r\n                            return !block(node);\r\n                        });\r\n                    }\r\n                    tmpRange.setEndAfter(tmpNode);\r\n                    var common = tmpRange.getCommonAncestor();\r\n                    if (!domUtils.isBody(common) && block(common)) {\r\n                        domUtils.setStyles(common, utils.isString(style) ? {'text-align':style} : style);\r\n                        current = common;\r\n                    } else {\r\n                        var p = range.document.createElement('p');\r\n                        domUtils.setStyles(p, utils.isString(style) ? {'text-align':style} : style);\r\n                        var frag = tmpRange.extractContents();\r\n                        p.appendChild(frag);\r\n                        tmpRange.insertNode(p);\r\n                        current = p;\r\n                    }\r\n                    current = domUtils.getNextDomNode(current, false, filterFn);\r\n                } else {\r\n                    current = domUtils.getNextDomNode(current, true, filterFn);\r\n                }\r\n            }\r\n            return range.moveToBookmark(bookmark2).moveToBookmark(bookmark);\r\n        };\r\n\r\n    UE.commands['justify'] = {\r\n        execCommand:function (cmdName, align) {\r\n            var range = this.selection.getRange(),\r\n                txt;\r\n\r\n            //闭合时单独处理\r\n            if (range.collapsed) {\r\n                txt = this.document.createTextNode('p');\r\n                range.insertNode(txt);\r\n            }\r\n            doJustify(range, align);\r\n            if (txt) {\r\n                range.setStartBefore(txt).collapse(true);\r\n                domUtils.remove(txt);\r\n            }\r\n\r\n            range.select();\r\n\r\n\r\n            return true;\r\n        },\r\n        queryCommandValue:function () {\r\n            var startNode = this.selection.getStart(),\r\n                value = domUtils.getComputedStyle(startNode, 'text-align');\r\n            return defaultValue[value] ? value : 'left';\r\n        },\r\n        queryCommandState:function () {\r\n            var start = this.selection.getStart(),\r\n                cell = start && domUtils.findParentByTagName(start, [\"td\", \"th\",\"caption\"], true);\r\n\r\n            return cell? -1:0;\r\n        }\r\n\r\n    };\r\n};\r\n\r\n\r\n// plugins/font.js\r\n/**\r\n * 字体颜色,背景色,字号,字体,下划线,删除线\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 字体颜色\r\n * @command forecolor\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @param { String } value 色值(必须十六进制)\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'forecolor', '#000' );\r\n * ```\r\n */\r\n/**\r\n * 返回选区字体颜色\r\n * @command forecolor\r\n * @method queryCommandValue\r\n * @param { String } cmd 命令字符串\r\n * @return { String } 返回字体颜色\r\n * @example\r\n * ```javascript\r\n * editor.queryCommandValue( 'forecolor' );\r\n * ```\r\n */\r\n\r\n/**\r\n * 字体背景颜色\r\n * @command backcolor\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @param { String } value 色值(必须十六进制)\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'backcolor', '#000' );\r\n * ```\r\n */\r\n/**\r\n * 返回选区字体颜色\r\n * @command backcolor\r\n * @method queryCommandValue\r\n * @param { String } cmd 命令字符串\r\n * @return { String } 返回字体背景颜色\r\n * @example\r\n * ```javascript\r\n * editor.queryCommandValue( 'backcolor' );\r\n * ```\r\n */\r\n\r\n/**\r\n * 字体大小\r\n * @command fontsize\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @param { String } value 字体大小\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'fontsize', '14px' );\r\n * ```\r\n */\r\n/**\r\n * 返回选区字体大小\r\n * @command fontsize\r\n * @method queryCommandValue\r\n * @param { String } cmd 命令字符串\r\n * @return { String } 返回字体大小\r\n * @example\r\n * ```javascript\r\n * editor.queryCommandValue( 'fontsize' );\r\n * ```\r\n */\r\n\r\n/**\r\n * 字体样式\r\n * @command fontfamily\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @param { String } value 字体样式\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'fontfamily', '微软雅黑' );\r\n * ```\r\n */\r\n/**\r\n * 返回选区字体样式\r\n * @command fontfamily\r\n * @method queryCommandValue\r\n * @param { String } cmd 命令字符串\r\n * @return { String } 返回字体样式\r\n * @example\r\n * ```javascript\r\n * editor.queryCommandValue( 'fontfamily' );\r\n * ```\r\n */\r\n\r\n/**\r\n * 字体下划线,与删除线互斥\r\n * @command underline\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'underline' );\r\n * ```\r\n */\r\n\r\n/**\r\n * 字体删除线,与下划线互斥\r\n * @command strikethrough\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'strikethrough' );\r\n * ```\r\n */\r\n\r\n/**\r\n * 字体边框\r\n * @command fontborder\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'fontborder' );\r\n * ```\r\n */\r\n\r\nUE.plugins['font'] = function () {\r\n    var me = this,\r\n        fonts = {\r\n            'forecolor': 'color',\r\n            'backcolor': 'background-color',\r\n            'fontsize': 'font-size',\r\n            'fontfamily': 'font-family',\r\n            'underline': 'text-decoration',\r\n            'strikethrough': 'text-decoration',\r\n            'fontborder': 'border'\r\n        },\r\n        needCmd = {'underline': 1, 'strikethrough': 1, 'fontborder': 1},\r\n        needSetChild = {\r\n            'forecolor': 'color',\r\n            'backcolor': 'background-color',\r\n            'fontsize': 'font-size',\r\n            'fontfamily': 'font-family'\r\n\r\n        };\r\n    me.setOpt({\r\n        'fontfamily': [\r\n            { name: 'songti', val: '宋体,SimSun'},\r\n            { name: 'yahei', val: '微软雅黑,Microsoft YaHei'},\r\n            { name: 'kaiti', val: '楷体,楷体_GB2312, SimKai'},\r\n            { name: 'heiti', val: '黑体, SimHei'},\r\n            { name: 'lishu', val: '隶书, SimLi'},\r\n            { name: 'andaleMono', val: 'andale mono'},\r\n            { name: 'arial', val: 'arial, helvetica,sans-serif'},\r\n            { name: 'arialBlack', val: 'arial black,avant garde'},\r\n            { name: 'comicSansMs', val: 'comic sans ms'},\r\n            { name: 'impact', val: 'impact,chicago'},\r\n            { name: 'timesNewRoman', val: 'times new roman'}\r\n        ],\r\n        'fontsize': [10, 11, 12, 14, 16, 18, 20, 24, 36]\r\n    });\r\n\r\n    function mergeWithParent(node){\r\n        var parent;\r\n        while(parent = node.parentNode){\r\n            if(parent.tagName == 'SPAN' && domUtils.getChildCount(parent,function(child){\r\n                return !domUtils.isBookmarkNode(child) && !domUtils.isBr(child)\r\n            }) == 1) {\r\n                parent.style.cssText += node.style.cssText;\r\n                domUtils.remove(node,true);\r\n                node = parent;\r\n\r\n            }else{\r\n                break;\r\n            }\r\n        }\r\n\r\n    }\r\n    function mergeChild(rng,cmdName,value){\r\n        if(needSetChild[cmdName]){\r\n            rng.adjustmentBoundary();\r\n            if(!rng.collapsed && rng.startContainer.nodeType == 1){\r\n                var start = rng.startContainer.childNodes[rng.startOffset];\r\n                if(start && domUtils.isTagNode(start,'span')){\r\n                    var bk = rng.createBookmark();\r\n                    utils.each(domUtils.getElementsByTagName(start, 'span'), function (span) {\r\n                        if (!span.parentNode || domUtils.isBookmarkNode(span))return;\r\n                        if(cmdName == 'backcolor' && domUtils.getComputedStyle(span,'background-color').toLowerCase() === value){\r\n                            return;\r\n                        }\r\n                        domUtils.removeStyle(span,needSetChild[cmdName]);\r\n                        if(span.style.cssText.replace(/^\\s+$/,'').length == 0){\r\n                            domUtils.remove(span,true)\r\n                        }\r\n                    });\r\n                    rng.moveToBookmark(bk)\r\n                }\r\n            }\r\n        }\r\n\r\n    }\r\n    function mergesibling(rng,cmdName,value) {\r\n        var collapsed = rng.collapsed,\r\n            bk = rng.createBookmark(), common;\r\n        if (collapsed) {\r\n            common = bk.start.parentNode;\r\n            while (dtd.$inline[common.tagName]) {\r\n                common = common.parentNode;\r\n            }\r\n        } else {\r\n            common = domUtils.getCommonAncestor(bk.start, bk.end);\r\n        }\r\n        utils.each(domUtils.getElementsByTagName(common, 'span'), function (span) {\r\n            if (!span.parentNode || domUtils.isBookmarkNode(span))return;\r\n            if (/\\s*border\\s*:\\s*none;?\\s*/i.test(span.style.cssText)) {\r\n                if(/^\\s*border\\s*:\\s*none;?\\s*$/.test(span.style.cssText)){\r\n                    domUtils.remove(span, true);\r\n                }else{\r\n                    domUtils.removeStyle(span,'border');\r\n                }\r\n                return\r\n            }\r\n            if (/border/i.test(span.style.cssText) && span.parentNode.tagName == 'SPAN' && /border/i.test(span.parentNode.style.cssText)) {\r\n                span.style.cssText = span.style.cssText.replace(/border[^:]*:[^;]+;?/gi, '');\r\n            }\r\n            if(!(cmdName=='fontborder' && value=='none')){\r\n                var next = span.nextSibling;\r\n                while (next && next.nodeType == 1 && next.tagName == 'SPAN' ) {\r\n                    if(domUtils.isBookmarkNode(next) && cmdName == 'fontborder') {\r\n                        span.appendChild(next);\r\n                        next = span.nextSibling;\r\n                        continue;\r\n                    }\r\n                    if (next.style.cssText == span.style.cssText) {\r\n                        domUtils.moveChild(next, span);\r\n                        domUtils.remove(next);\r\n                    }\r\n                    if (span.nextSibling === next)\r\n                        break;\r\n                    next = span.nextSibling;\r\n                }\r\n            }\r\n\r\n\r\n            mergeWithParent(span);\r\n            if(browser.ie && browser.version > 8 ){\r\n                //拷贝父亲们的特别的属性,这里只做背景颜色的处理\r\n                var parent = domUtils.findParent(span,function(n){return n.tagName == 'SPAN' && /background-color/.test(n.style.cssText)});\r\n                if(parent && !/background-color/.test(span.style.cssText)){\r\n                    span.style.backgroundColor = parent.style.backgroundColor;\r\n                }\r\n            }\r\n\r\n        });\r\n        rng.moveToBookmark(bk);\r\n        mergeChild(rng,cmdName,value)\r\n    }\r\n\r\n    me.addInputRule(function (root) {\r\n        utils.each(root.getNodesByTagName('u s del font strike'), function (node) {\r\n            if (node.tagName == 'font') {\r\n                var cssStyle = [];\r\n                for (var p in node.attrs) {\r\n                    switch (p) {\r\n                        case 'size':\r\n                            cssStyle.push('font-size:' +\r\n                                ({\r\n                                '1':'10',\r\n                                '2':'12',\r\n                                '3':'16',\r\n                                '4':'18',\r\n                                '5':'24',\r\n                                '6':'32',\r\n                                '7':'48'\r\n                            }[node.attrs[p]] || node.attrs[p]) + 'px');\r\n                            break;\r\n                        case 'color':\r\n                            cssStyle.push('color:' + node.attrs[p]);\r\n                            break;\r\n                        case 'face':\r\n                            cssStyle.push('font-family:' + node.attrs[p]);\r\n                            break;\r\n                        case 'style':\r\n                            cssStyle.push(node.attrs[p]);\r\n                    }\r\n                }\r\n                node.attrs = {\r\n                    'style': cssStyle.join(';')\r\n                };\r\n            } else {\r\n                var val = node.tagName == 'u' ? 'underline' : 'line-through';\r\n                node.attrs = {\r\n                    'style': (node.getAttr('style') || '') + 'text-decoration:' + val + ';'\r\n                }\r\n            }\r\n            node.tagName = 'span';\r\n        });\r\n//        utils.each(root.getNodesByTagName('span'), function (node) {\r\n//            var val;\r\n//            if(val = node.getAttr('class')){\r\n//                if(/fontstrikethrough/.test(val)){\r\n//                    node.setStyle('text-decoration','line-through');\r\n//                    if(node.attrs['class']){\r\n//                        node.attrs['class'] = node.attrs['class'].replace(/fontstrikethrough/,'');\r\n//                    }else{\r\n//                        node.setAttr('class')\r\n//                    }\r\n//                }\r\n//                if(/fontborder/.test(val)){\r\n//                    node.setStyle('border','1px solid #000');\r\n//                    if(node.attrs['class']){\r\n//                        node.attrs['class'] = node.attrs['class'].replace(/fontborder/,'');\r\n//                    }else{\r\n//                        node.setAttr('class')\r\n//                    }\r\n//                }\r\n//            }\r\n//        });\r\n    });\r\n//    me.addOutputRule(function(root){\r\n//        utils.each(root.getNodesByTagName('span'), function (node) {\r\n//            var val;\r\n//            if(val = node.getStyle('text-decoration')){\r\n//                if(/line-through/.test(val)){\r\n//                    if(node.attrs['class']){\r\n//                        node.attrs['class'] += ' fontstrikethrough';\r\n//                    }else{\r\n//                        node.setAttr('class','fontstrikethrough')\r\n//                    }\r\n//                }\r\n//\r\n//                node.setStyle('text-decoration')\r\n//            }\r\n//            if(val = node.getStyle('border')){\r\n//                if(/1px/.test(val) && /solid/.test(val)){\r\n//                    if(node.attrs['class']){\r\n//                        node.attrs['class'] += ' fontborder';\r\n//\r\n//                    }else{\r\n//                        node.setAttr('class','fontborder')\r\n//                    }\r\n//                }\r\n//                node.setStyle('border')\r\n//\r\n//            }\r\n//        });\r\n//    });\r\n    for (var p in fonts) {\r\n        (function (cmd, style) {\r\n            UE.commands[cmd] = {\r\n                execCommand: function (cmdName, value) {\r\n                    value = value || (this.queryCommandState(cmdName) ? 'none' : cmdName == 'underline' ? 'underline' :\r\n                        cmdName == 'fontborder' ? '1px solid #000' :\r\n                            'line-through');\r\n                    var me = this,\r\n                        range = this.selection.getRange(),\r\n                        text;\r\n\r\n                    if (value == 'default') {\r\n\r\n                        if (range.collapsed) {\r\n                            text = me.document.createTextNode('font');\r\n                            range.insertNode(text).select();\r\n\r\n                        }\r\n                        me.execCommand('removeFormat', 'span,a', style);\r\n                        if (text) {\r\n                            range.setStartBefore(text).collapse(true);\r\n                            domUtils.remove(text);\r\n                        }\r\n                        mergesibling(range,cmdName,value);\r\n                        range.select()\r\n                    } else {\r\n                        if (!range.collapsed) {\r\n                            if (needCmd[cmd] && me.queryCommandValue(cmd)) {\r\n                                me.execCommand('removeFormat', 'span,a', style);\r\n                            }\r\n                            range = me.selection.getRange();\r\n\r\n                            range.applyInlineStyle('span', {'style': style + ':' + value});\r\n                            mergesibling(range, cmdName,value);\r\n                            range.select();\r\n                        } else {\r\n\r\n                            var span = domUtils.findParentByTagName(range.startContainer, 'span', true);\r\n                            text = me.document.createTextNode('font');\r\n                            if (span && !span.children.length && !span[browser.ie ? 'innerText' : 'textContent'].replace(fillCharReg, '').length) {\r\n                                //for ie hack when enter\r\n                                range.insertNode(text);\r\n                                if (needCmd[cmd]) {\r\n                                    range.selectNode(text).select();\r\n                                    me.execCommand('removeFormat', 'span,a', style, null);\r\n\r\n                                    span = domUtils.findParentByTagName(text, 'span', true);\r\n                                    range.setStartBefore(text);\r\n\r\n                                }\r\n                                span && (span.style.cssText += ';' + style + ':' + value);\r\n                                range.collapse(true).select();\r\n\r\n\r\n                            } else {\r\n                                range.insertNode(text);\r\n                                range.selectNode(text).select();\r\n                                span = range.document.createElement('span');\r\n\r\n                                if (needCmd[cmd]) {\r\n                                    //a标签内的不处理跳过\r\n                                    if (domUtils.findParentByTagName(text, 'a', true)) {\r\n                                        range.setStartBefore(text).setCursor();\r\n                                        domUtils.remove(text);\r\n                                        return;\r\n                                    }\r\n                                    me.execCommand('removeFormat', 'span,a', style);\r\n                                }\r\n\r\n                                span.style.cssText = style + ':' + value;\r\n\r\n\r\n                                text.parentNode.insertBefore(span, text);\r\n                                //修复，span套span 但样式不继承的问题\r\n                                if (!browser.ie || browser.ie && browser.version == 9) {\r\n                                    var spanParent = span.parentNode;\r\n                                    while (!domUtils.isBlockElm(spanParent)) {\r\n                                        if (spanParent.tagName == 'SPAN') {\r\n                                            //opera合并style不会加入\";\"\r\n                                            span.style.cssText = spanParent.style.cssText + \";\" + span.style.cssText;\r\n                                        }\r\n                                        spanParent = spanParent.parentNode;\r\n                                    }\r\n                                }\r\n\r\n\r\n                                if (opera) {\r\n                                    setTimeout(function () {\r\n                                        range.setStart(span, 0).collapse(true);\r\n                                        mergesibling(range, cmdName,value);\r\n                                        range.select();\r\n                                    });\r\n                                } else {\r\n                                    range.setStart(span, 0).collapse(true);\r\n                                    mergesibling(range,cmdName,value);\r\n                                    range.select();\r\n                                }\r\n\r\n                                //trace:981\r\n                                //domUtils.mergeToParent(span)\r\n                            }\r\n                            domUtils.remove(text);\r\n                        }\r\n\r\n\r\n                    }\r\n                    return true;\r\n                },\r\n                queryCommandValue: function (cmdName) {\r\n                    var startNode = this.selection.getStart();\r\n\r\n                    //trace:946\r\n                    if (cmdName == 'underline' || cmdName == 'strikethrough') {\r\n                        var tmpNode = startNode, value;\r\n                        while (tmpNode && !domUtils.isBlockElm(tmpNode) && !domUtils.isBody(tmpNode)) {\r\n                            if (tmpNode.nodeType == 1) {\r\n                                value = domUtils.getComputedStyle(tmpNode, style);\r\n                                if (value != 'none') {\r\n                                    return value;\r\n                                }\r\n                            }\r\n\r\n                            tmpNode = tmpNode.parentNode;\r\n                        }\r\n                        return 'none';\r\n                    }\r\n                    if (cmdName == 'fontborder') {\r\n                        var tmp = startNode, val;\r\n                        while (tmp && dtd.$inline[tmp.tagName]) {\r\n                            if (val = domUtils.getComputedStyle(tmp, 'border')) {\r\n\r\n                                if (/1px/.test(val) && /solid/.test(val)) {\r\n                                    return val;\r\n                                }\r\n                            }\r\n                            tmp = tmp.parentNode;\r\n                        }\r\n                        return ''\r\n                    }\r\n\r\n                    if( cmdName == 'FontSize' ) {\r\n                        var styleVal = domUtils.getComputedStyle(startNode, style),\r\n                            tmp = /^([\\d\\.]+)(\\w+)$/.exec( styleVal );\r\n\r\n                        if( tmp ) {\r\n\r\n                            return Math.floor( tmp[1] ) + tmp[2];\r\n\r\n                        }\r\n\r\n                        return styleVal;\r\n\r\n                    }\r\n\r\n                    return  domUtils.getComputedStyle(startNode, style);\r\n                },\r\n                queryCommandState: function (cmdName) {\r\n                    if (!needCmd[cmdName])\r\n                        return 0;\r\n                    var val = this.queryCommandValue(cmdName);\r\n                    if (cmdName == 'fontborder') {\r\n                        return /1px/.test(val) && /solid/.test(val)\r\n                    } else {\r\n                        return  cmdName == 'underline' ? /underline/.test(val) : /line\\-through/.test(val);\r\n\r\n                    }\r\n\r\n                }\r\n            };\r\n        })(p, fonts[p]);\r\n    }\r\n};\r\n\r\n// plugins/link.js\r\n/**\r\n * 超链接\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 插入超链接\r\n * @command link\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @param { Object } options   设置自定义属性，例如：url、title、target\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'link', '{\r\n *     url:'ueditor.baidu.com',\r\n *     title:'ueditor',\r\n *     target:'_blank'\r\n * }' );\r\n * ```\r\n */\r\n/**\r\n * 返回当前选中的第一个超链接节点\r\n * @command link\r\n * @method queryCommandValue\r\n * @param { String } cmd 命令字符串\r\n * @return { Element } 超链接节点\r\n * @example\r\n * ```javascript\r\n * editor.queryCommandValue( 'link' );\r\n * ```\r\n */\r\n\r\n/**\r\n * 取消超链接\r\n * @command unlink\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'unlink');\r\n * ```\r\n */\r\n\r\nUE.plugins['link'] = function(){\r\n    function optimize( range ) {\r\n        var start = range.startContainer,end = range.endContainer;\r\n\r\n        if ( start = domUtils.findParentByTagName( start, 'a', true ) ) {\r\n            range.setStartBefore( start );\r\n        }\r\n        if ( end = domUtils.findParentByTagName( end, 'a', true ) ) {\r\n            range.setEndAfter( end );\r\n        }\r\n    }\r\n\r\n\r\n    UE.commands['unlink'] = {\r\n        execCommand : function() {\r\n            var range = this.selection.getRange(),\r\n                bookmark;\r\n            if(range.collapsed && !domUtils.findParentByTagName( range.startContainer, 'a', true )){\r\n                return;\r\n            }\r\n            bookmark = range.createBookmark();\r\n            optimize( range );\r\n            range.removeInlineStyle( 'a' ).moveToBookmark( bookmark ).select();\r\n        },\r\n        queryCommandState : function(){\r\n            return !this.highlight && this.queryCommandValue('link') ?  0 : -1;\r\n        }\r\n\r\n    };\r\n    function doLink(range,opt,me){\r\n        var rngClone = range.cloneRange(),\r\n            link = me.queryCommandValue('link');\r\n        optimize( range = range.adjustmentBoundary() );\r\n        var start = range.startContainer;\r\n        if(start.nodeType == 1 && link){\r\n            start = start.childNodes[range.startOffset];\r\n            if(start && start.nodeType == 1 && start.tagName == 'A' && /^(?:https?|ftp|file)\\s*:\\s*\\/\\//.test(start[browser.ie?'innerText':'textContent'])){\r\n                start[browser.ie ? 'innerText' : 'textContent'] =  utils.html(opt.textValue||opt.href);\r\n\r\n            }\r\n        }\r\n        if( !rngClone.collapsed || link){\r\n            range.removeInlineStyle( 'a' );\r\n            rngClone = range.cloneRange();\r\n        }\r\n\r\n        if ( rngClone.collapsed ) {\r\n            var a = range.document.createElement( 'a'),\r\n                text = '';\r\n            if(opt.textValue){\r\n\r\n                text =   utils.html(opt.textValue);\r\n                delete opt.textValue;\r\n            }else{\r\n                text =   utils.html(opt.href);\r\n\r\n            }\r\n            domUtils.setAttributes( a, opt );\r\n            start =  domUtils.findParentByTagName( rngClone.startContainer, 'a', true );\r\n            if(start && domUtils.isInNodeEndBoundary(rngClone,start)){\r\n                range.setStartAfter(start).collapse(true);\r\n\r\n            }\r\n            a[browser.ie ? 'innerText' : 'textContent'] = text;\r\n            range.insertNode(a).selectNode( a );\r\n        } else {\r\n            range.applyInlineStyle( 'a', opt );\r\n\r\n        }\r\n    }\r\n    UE.commands['link'] = {\r\n        execCommand : function( cmdName, opt ) {\r\n            var range;\r\n            opt._href && (opt._href = utils.unhtml(opt._href,/[<\">]/g));\r\n            opt.href && (opt.href = utils.unhtml(opt.href,/[<\">]/g));\r\n            opt.textValue && (opt.textValue = utils.unhtml(opt.textValue,/[<\">]/g));\r\n            doLink(range=this.selection.getRange(),opt,this);\r\n            //闭合都不加占位符，如果加了会在a后边多个占位符节点，导致a是图片背景组成的列表，出现空白问题\r\n            range.collapse().select(true);\r\n\r\n        },\r\n        queryCommandValue : function() {\r\n            var range = this.selection.getRange(),\r\n                node;\r\n            if ( range.collapsed ) {\r\n//                    node = this.selection.getStart();\r\n                //在ie下getstart()取值偏上了\r\n                node = range.startContainer;\r\n                node = node.nodeType == 1 ? node : node.parentNode;\r\n\r\n                if ( node && (node = domUtils.findParentByTagName( node, 'a', true )) && ! domUtils.isInNodeEndBoundary(range,node)) {\r\n\r\n                    return node;\r\n                }\r\n            } else {\r\n                //trace:1111  如果是<p><a>xx</a></p> startContainer是p就会找不到a\r\n                range.shrinkBoundary();\r\n                var start = range.startContainer.nodeType  == 3 || !range.startContainer.childNodes[range.startOffset] ? range.startContainer : range.startContainer.childNodes[range.startOffset],\r\n                    end =  range.endContainer.nodeType == 3 || range.endOffset == 0 ? range.endContainer : range.endContainer.childNodes[range.endOffset-1],\r\n                    common = range.getCommonAncestor();\r\n                node = domUtils.findParentByTagName( common, 'a', true );\r\n                if ( !node && common.nodeType == 1){\r\n\r\n                    var as = common.getElementsByTagName( 'a' ),\r\n                        ps,pe;\r\n\r\n                    for ( var i = 0,ci; ci = as[i++]; ) {\r\n                        ps = domUtils.getPosition( ci, start ),pe = domUtils.getPosition( ci,end);\r\n                        if ( (ps & domUtils.POSITION_FOLLOWING || ps & domUtils.POSITION_CONTAINS)\r\n                            &&\r\n                            (pe & domUtils.POSITION_PRECEDING || pe & domUtils.POSITION_CONTAINS)\r\n                            ) {\r\n                            node = ci;\r\n                            break;\r\n                        }\r\n                    }\r\n                }\r\n                return node;\r\n            }\r\n\r\n        },\r\n        queryCommandState : function() {\r\n            //判断如果是视频的话连接不可用\r\n            //fix 853\r\n            var img = this.selection.getRange().getClosedNode(),\r\n                flag = img && (img.className == \"edui-faked-video\" || img.className.indexOf(\"edui-upload-video\")!=-1);\r\n            return flag ? -1 : 0;\r\n        }\r\n    };\r\n};\r\n\r\n// plugins/iframe.js\r\n///import core\r\n///import plugins\\inserthtml.js\r\n///commands 插入框架\r\n///commandsName  InsertFrame\r\n///commandsTitle  插入Iframe\r\n///commandsDialog  dialogs\\insertframe\r\n\r\nUE.plugins['insertframe'] = function() {\r\n   var me =this;\r\n    function deleteIframe(){\r\n        me._iframe && delete me._iframe;\r\n    }\r\n\r\n    me.addListener(\"selectionchange\",function(){\r\n        deleteIframe();\r\n    });\r\n\r\n};\r\n\r\n\r\n\r\n// plugins/scrawl.js\r\n///import core\r\n///commands 涂鸦\r\n///commandsName  Scrawl\r\n///commandsTitle  涂鸦\r\n///commandsDialog  dialogs\\scrawl\r\nUE.commands['scrawl'] = {\r\n    queryCommandState : function(){\r\n        return ( browser.ie && browser.version  <= 8 ) ? -1 :0;\r\n    }\r\n};\r\n\r\n\r\n// plugins/removeformat.js\r\n/**\r\n * 清除格式\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 清除文字样式\r\n * @command removeformat\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @param   {String}   tags     以逗号隔开的标签。如：strong\r\n * @param   {String}   style    样式如：color\r\n * @param   {String}   attrs    属性如:width\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'removeformat', 'strong','color','width' );\r\n * ```\r\n */\r\n\r\nUE.plugins['removeformat'] = function(){\r\n    var me = this;\r\n    me.setOpt({\r\n       'removeFormatTags': 'b,big,code,del,dfn,em,font,i,ins,kbd,q,samp,small,span,strike,strong,sub,sup,tt,u,var',\r\n       'removeFormatAttributes':'class,style,lang,width,height,align,hspace,valign'\r\n    });\r\n    me.commands['removeformat'] = {\r\n        execCommand : function( cmdName, tags, style, attrs,notIncludeA ) {\r\n\r\n            var tagReg = new RegExp( '^(?:' + (tags || this.options.removeFormatTags).replace( /,/g, '|' ) + ')$', 'i' ) ,\r\n                removeFormatAttributes = style ? [] : (attrs || this.options.removeFormatAttributes).split( ',' ),\r\n                range = new dom.Range( this.document ),\r\n                bookmark,node,parent,\r\n                filter = function( node ) {\r\n                    return node.nodeType == 1;\r\n                };\r\n\r\n            function isRedundantSpan (node) {\r\n                if (node.nodeType == 3 || node.tagName.toLowerCase() != 'span'){\r\n                    return 0;\r\n                }\r\n                if (browser.ie) {\r\n                    //ie 下判断实效，所以只能简单用style来判断\r\n                    //return node.style.cssText == '' ? 1 : 0;\r\n                    var attrs = node.attributes;\r\n                    if ( attrs.length ) {\r\n                        for ( var i = 0,l = attrs.length; i<l; i++ ) {\r\n                            if ( attrs[i].specified ) {\r\n                                return 0;\r\n                            }\r\n                        }\r\n                        return 1;\r\n                    }\r\n                }\r\n                return !node.attributes.length;\r\n            }\r\n            function doRemove( range ) {\r\n\r\n                var bookmark1 = range.createBookmark();\r\n                if ( range.collapsed ) {\r\n                    range.enlarge( true );\r\n                }\r\n\r\n                //不能把a标签切了\r\n                if(!notIncludeA){\r\n                    var aNode = domUtils.findParentByTagName(range.startContainer,'a',true);\r\n                    if(aNode){\r\n                        range.setStartBefore(aNode);\r\n                    }\r\n\r\n                    aNode = domUtils.findParentByTagName(range.endContainer,'a',true);\r\n                    if(aNode){\r\n                        range.setEndAfter(aNode);\r\n                    }\r\n\r\n                }\r\n\r\n\r\n                bookmark = range.createBookmark();\r\n\r\n                node = bookmark.start;\r\n\r\n                //切开始\r\n                while ( (parent = node.parentNode) && !domUtils.isBlockElm( parent ) ) {\r\n                    domUtils.breakParent( node, parent );\r\n\r\n                    domUtils.clearEmptySibling( node );\r\n                }\r\n                if ( bookmark.end ) {\r\n                    //切结束\r\n                    node = bookmark.end;\r\n                    while ( (parent = node.parentNode) && !domUtils.isBlockElm( parent ) ) {\r\n                        domUtils.breakParent( node, parent );\r\n                        domUtils.clearEmptySibling( node );\r\n                    }\r\n\r\n                    //开始去除样式\r\n                    var current = domUtils.getNextDomNode( bookmark.start, false, filter ),\r\n                        next;\r\n                    while ( current ) {\r\n                        if ( current == bookmark.end ) {\r\n                            break;\r\n                        }\r\n\r\n                        next = domUtils.getNextDomNode( current, true, filter );\r\n\r\n                        if ( !dtd.$empty[current.tagName.toLowerCase()] && !domUtils.isBookmarkNode( current ) ) {\r\n                            if ( tagReg.test( current.tagName ) ) {\r\n                                if ( style ) {\r\n                                    domUtils.removeStyle( current, style );\r\n                                    if ( isRedundantSpan( current ) && style != 'text-decoration'){\r\n                                        domUtils.remove( current, true );\r\n                                    }\r\n                                } else {\r\n                                    domUtils.remove( current, true );\r\n                                }\r\n                            } else {\r\n                                //trace:939  不能把list上的样式去掉\r\n                                if(!dtd.$tableContent[current.tagName] && !dtd.$list[current.tagName]){\r\n                                    domUtils.removeAttributes( current, removeFormatAttributes );\r\n                                    if ( isRedundantSpan( current ) ){\r\n                                        domUtils.remove( current, true );\r\n                                    }\r\n                                }\r\n\r\n                            }\r\n                        }\r\n                        current = next;\r\n                    }\r\n                }\r\n                //trace:1035\r\n                //trace:1096 不能把td上的样式去掉，比如边框\r\n                var pN = bookmark.start.parentNode;\r\n                if(domUtils.isBlockElm(pN) && !dtd.$tableContent[pN.tagName] && !dtd.$list[pN.tagName]){\r\n                    domUtils.removeAttributes(  pN,removeFormatAttributes );\r\n                }\r\n                pN = bookmark.end.parentNode;\r\n                if(bookmark.end && domUtils.isBlockElm(pN) && !dtd.$tableContent[pN.tagName]&& !dtd.$list[pN.tagName]){\r\n                    domUtils.removeAttributes(  pN,removeFormatAttributes );\r\n                }\r\n                range.moveToBookmark( bookmark ).moveToBookmark(bookmark1);\r\n                //清除冗余的代码 <b><bookmark></b>\r\n                var node = range.startContainer,\r\n                    tmp,\r\n                    collapsed = range.collapsed;\r\n                while(node.nodeType == 1 && domUtils.isEmptyNode(node) && dtd.$removeEmpty[node.tagName]){\r\n                    tmp = node.parentNode;\r\n                    range.setStartBefore(node);\r\n                    //trace:937\r\n                    //更新结束边界\r\n                    if(range.startContainer === range.endContainer){\r\n                        range.endOffset--;\r\n                    }\r\n                    domUtils.remove(node);\r\n                    node = tmp;\r\n                }\r\n\r\n                if(!collapsed){\r\n                    node = range.endContainer;\r\n                    while(node.nodeType == 1 && domUtils.isEmptyNode(node) && dtd.$removeEmpty[node.tagName]){\r\n                        tmp = node.parentNode;\r\n                        range.setEndBefore(node);\r\n                        domUtils.remove(node);\r\n\r\n                        node = tmp;\r\n                    }\r\n\r\n\r\n                }\r\n            }\r\n\r\n\r\n\r\n            range = this.selection.getRange();\r\n            doRemove( range );\r\n            range.select();\r\n\r\n        }\r\n\r\n    };\r\n\r\n};\r\n\r\n\r\n// plugins/blockquote.js\r\n/**\r\n * 添加引用\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 添加引用\r\n * @command blockquote\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'blockquote' );\r\n * ```\r\n */\r\n\r\n/**\r\n * 添加引用\r\n * @command blockquote\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @param { Object } attrs 节点属性\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'blockquote',{\r\n *     style: \"color: red;\"\r\n * } );\r\n * ```\r\n */\r\n\r\n\r\nUE.plugins['blockquote'] = function(){\r\n    var me = this;\r\n    function getObj(editor){\r\n        return domUtils.filterNodeList(editor.selection.getStartElementPath(),'blockquote');\r\n    }\r\n    me.commands['blockquote'] = {\r\n        execCommand : function( cmdName, attrs ) {\r\n            var range = this.selection.getRange(),\r\n                obj = getObj(this),\r\n                blockquote = dtd.blockquote,\r\n                bookmark = range.createBookmark();\r\n\r\n            if ( obj ) {\r\n\r\n                    var start = range.startContainer,\r\n                        startBlock = domUtils.isBlockElm(start) ? start : domUtils.findParent(start,function(node){return domUtils.isBlockElm(node)}),\r\n\r\n                        end = range.endContainer,\r\n                        endBlock = domUtils.isBlockElm(end) ? end :  domUtils.findParent(end,function(node){return domUtils.isBlockElm(node)});\r\n\r\n                    //处理一下li\r\n                    startBlock = domUtils.findParentByTagName(startBlock,'li',true) || startBlock;\r\n                    endBlock = domUtils.findParentByTagName(endBlock,'li',true) || endBlock;\r\n\r\n\r\n                    if(startBlock.tagName == 'LI' || startBlock.tagName == 'TD' || startBlock === obj || domUtils.isBody(startBlock)){\r\n                        domUtils.remove(obj,true);\r\n                    }else{\r\n                        domUtils.breakParent(startBlock,obj);\r\n                    }\r\n\r\n                    if(startBlock !== endBlock){\r\n                        obj = domUtils.findParentByTagName(endBlock,'blockquote');\r\n                        if(obj){\r\n                            if(endBlock.tagName == 'LI' || endBlock.tagName == 'TD'|| domUtils.isBody(endBlock)){\r\n                                obj.parentNode && domUtils.remove(obj,true);\r\n                            }else{\r\n                                domUtils.breakParent(endBlock,obj);\r\n                            }\r\n\r\n                        }\r\n                    }\r\n\r\n                    var blockquotes = domUtils.getElementsByTagName(this.document,'blockquote');\r\n                    for(var i=0,bi;bi=blockquotes[i++];){\r\n                        if(!bi.childNodes.length){\r\n                            domUtils.remove(bi);\r\n                        }else if(domUtils.getPosition(bi,startBlock)&domUtils.POSITION_FOLLOWING && domUtils.getPosition(bi,endBlock)&domUtils.POSITION_PRECEDING){\r\n                            domUtils.remove(bi,true);\r\n                        }\r\n                    }\r\n\r\n\r\n\r\n\r\n            } else {\r\n\r\n                var tmpRange = range.cloneRange(),\r\n                    node = tmpRange.startContainer.nodeType == 1 ? tmpRange.startContainer : tmpRange.startContainer.parentNode,\r\n                    preNode = node,\r\n                    doEnd = 1;\r\n\r\n                //调整开始\r\n                while ( 1 ) {\r\n                    if ( domUtils.isBody(node) ) {\r\n                        if ( preNode !== node ) {\r\n                            if ( range.collapsed ) {\r\n                                tmpRange.selectNode( preNode );\r\n                                doEnd = 0;\r\n                            } else {\r\n                                tmpRange.setStartBefore( preNode );\r\n                            }\r\n                        }else{\r\n                            tmpRange.setStart(node,0);\r\n                        }\r\n\r\n                        break;\r\n                    }\r\n                    if ( !blockquote[node.tagName] ) {\r\n                        if ( range.collapsed ) {\r\n                            tmpRange.selectNode( preNode );\r\n                        } else{\r\n                            tmpRange.setStartBefore( preNode);\r\n                        }\r\n                        break;\r\n                    }\r\n\r\n                    preNode = node;\r\n                    node = node.parentNode;\r\n                }\r\n\r\n                //调整结束\r\n                if ( doEnd ) {\r\n                    preNode = node =  node = tmpRange.endContainer.nodeType == 1 ? tmpRange.endContainer : tmpRange.endContainer.parentNode;\r\n                    while ( 1 ) {\r\n\r\n                        if ( domUtils.isBody( node ) ) {\r\n                            if ( preNode !== node ) {\r\n\r\n                                tmpRange.setEndAfter( preNode );\r\n\r\n                            } else {\r\n                                tmpRange.setEnd( node, node.childNodes.length );\r\n                            }\r\n\r\n                            break;\r\n                        }\r\n                        if ( !blockquote[node.tagName] ) {\r\n                            tmpRange.setEndAfter( preNode );\r\n                            break;\r\n                        }\r\n\r\n                        preNode = node;\r\n                        node = node.parentNode;\r\n                    }\r\n\r\n                }\r\n\r\n\r\n                node = range.document.createElement( 'blockquote' );\r\n                domUtils.setAttributes( node, attrs );\r\n                node.appendChild( tmpRange.extractContents() );\r\n                tmpRange.insertNode( node );\r\n                //去除重复的\r\n                var childs = domUtils.getElementsByTagName(node,'blockquote');\r\n                for(var i=0,ci;ci=childs[i++];){\r\n                    if(ci.parentNode){\r\n                        domUtils.remove(ci,true);\r\n                    }\r\n                }\r\n\r\n            }\r\n            range.moveToBookmark( bookmark ).select();\r\n        },\r\n        queryCommandState : function() {\r\n            return getObj(this) ? 1 : 0;\r\n        }\r\n    };\r\n};\r\n\r\n\r\n\r\n// plugins/convertcase.js\r\n/**\r\n * 大小写转换\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 把选区内文本变大写，与“tolowercase”命令互斥\r\n * @command touppercase\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'touppercase' );\r\n * ```\r\n */\r\n\r\n/**\r\n * 把选区内文本变小写，与“touppercase”命令互斥\r\n * @command tolowercase\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'tolowercase' );\r\n * ```\r\n */\r\nUE.commands['touppercase'] =\r\nUE.commands['tolowercase'] = {\r\n    execCommand:function (cmd) {\r\n        var me = this;\r\n        var rng = me.selection.getRange();\r\n        if(rng.collapsed){\r\n            return rng;\r\n        }\r\n        var bk = rng.createBookmark(),\r\n            bkEnd = bk.end,\r\n            filterFn = function( node ) {\r\n                return !domUtils.isBr(node) && !domUtils.isWhitespace( node );\r\n            },\r\n            curNode = domUtils.getNextDomNode( bk.start, false, filterFn );\r\n        while ( curNode && (domUtils.getPosition( curNode, bkEnd ) & domUtils.POSITION_PRECEDING) ) {\r\n\r\n            if ( curNode.nodeType == 3 ) {\r\n                curNode.nodeValue = curNode.nodeValue[cmd == 'touppercase' ? 'toUpperCase' : 'toLowerCase']();\r\n            }\r\n            curNode = domUtils.getNextDomNode( curNode, true, filterFn );\r\n            if(curNode === bkEnd){\r\n                break;\r\n            }\r\n\r\n        }\r\n        rng.moveToBookmark(bk).select();\r\n    }\r\n};\r\n\r\n\r\n\r\n// plugins/indent.js\r\n/**\r\n * 首行缩进\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 缩进\r\n * @command indent\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'indent' );\r\n * ```\r\n */\r\nUE.commands['indent'] = {\r\n    execCommand : function() {\r\n         var me = this,value = me.queryCommandState(\"indent\") ? \"0em\" : (me.options.indentValue || '2em');\r\n         me.execCommand('Paragraph','p',{style:'text-indent:'+ value});\r\n    },\r\n    queryCommandState : function() {\r\n        var pN = domUtils.filterNodeList(this.selection.getStartElementPath(),'p h1 h2 h3 h4 h5 h6');\r\n        return pN && pN.style.textIndent && parseInt(pN.style.textIndent) ?  1 : 0;\r\n    }\r\n\r\n};\r\n\r\n\r\n// plugins/print.js\r\n/**\r\n * 打印\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 打印\r\n * @command print\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'print' );\r\n * ```\r\n */\r\nUE.commands['print'] = {\r\n    execCommand : function(){\r\n        this.window.print();\r\n    },\r\n    notNeedUndo : 1\r\n};\r\n\r\n\r\n\r\n// plugins/preview.js\r\n/**\r\n * 预览\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 预览\r\n * @command preview\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'preview' );\r\n * ```\r\n */\r\nUE.commands['preview'] = {\r\n    execCommand : function(){\r\n        var w = window.open('', '_blank', ''),\r\n            d = w.document;\r\n        d.open();\r\n        d.write('<!DOCTYPE html><html><head><meta charset=\"utf-8\"/><script src=\"'+this.options.UEDITOR_HOME_URL+'ueditor.parse.js\"></script><script>' +\r\n            \"setTimeout(function(){uParse('div',{rootPath: '\"+ this.options.UEDITOR_HOME_URL +\"'})},300)\" +\r\n            '</script></head><body><div>'+this.getContent(null,null,true)+'</div></body></html>');\r\n        d.close();\r\n    },\r\n    notNeedUndo : 1\r\n};\r\n\r\n\r\n// plugins/selectall.js\r\n/**\r\n * 全选\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 选中所有内容\r\n * @command selectall\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'selectall' );\r\n * ```\r\n */\r\nUE.plugins['selectall'] = function(){\r\n    var me = this;\r\n    me.commands['selectall'] = {\r\n        execCommand : function(){\r\n            //去掉了原生的selectAll,因为会出现报错和当内容为空时，不能出现闭合状态的光标\r\n            var me = this,body = me.body,\r\n                range = me.selection.getRange();\r\n            range.selectNodeContents(body);\r\n            if(domUtils.isEmptyBlock(body)){\r\n                //opera不能自动合并到元素的里边，要手动处理一下\r\n                if(browser.opera && body.firstChild && body.firstChild.nodeType == 1){\r\n                    range.setStartAtFirst(body.firstChild);\r\n                }\r\n                range.collapse(true);\r\n            }\r\n            range.select(true);\r\n        },\r\n        notNeedUndo : 1\r\n    };\r\n\r\n\r\n    //快捷键\r\n    me.addshortcutkey({\r\n         \"selectAll\" : \"ctrl+65\"\r\n    });\r\n};\r\n\r\n\r\n// plugins/paragraph.js\r\n/**\r\n * 段落样式\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 段落格式\r\n * @command paragraph\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @param {String}   style               标签值为：'p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6'\r\n * @param {Object}   attrs               标签的属性\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'Paragraph','h1','{\r\n *     class:'test'\r\n * }' );\r\n * ```\r\n */\r\n\r\n/**\r\n * 返回选区内节点标签名\r\n * @command paragraph\r\n * @method queryCommandValue\r\n * @param { String } cmd 命令字符串\r\n * @return { String } 节点标签名\r\n * @example\r\n * ```javascript\r\n * editor.queryCommandValue( 'Paragraph' );\r\n * ```\r\n */\r\n\r\nUE.plugins['paragraph'] = function() {\r\n    var me = this,\r\n        block = domUtils.isBlockElm,\r\n        notExchange = ['TD','LI','PRE'],\r\n\r\n        doParagraph = function(range,style,attrs,sourceCmdName){\r\n            var bookmark = range.createBookmark(),\r\n                filterFn = function( node ) {\r\n                    return   node.nodeType == 1 ? node.tagName.toLowerCase() != 'br' &&  !domUtils.isBookmarkNode(node) : !domUtils.isWhitespace( node );\r\n                },\r\n                para;\r\n\r\n            range.enlarge( true );\r\n            var bookmark2 = range.createBookmark(),\r\n                current = domUtils.getNextDomNode( bookmark2.start, false, filterFn ),\r\n                tmpRange = range.cloneRange(),\r\n                tmpNode;\r\n            while ( current && !(domUtils.getPosition( current, bookmark2.end ) & domUtils.POSITION_FOLLOWING) ) {\r\n                if ( current.nodeType == 3 || !block( current ) ) {\r\n                    tmpRange.setStartBefore( current );\r\n                    while ( current && current !== bookmark2.end && !block( current ) ) {\r\n                        tmpNode = current;\r\n                        current = domUtils.getNextDomNode( current, false, null, function( node ) {\r\n                            return !block( node );\r\n                        } );\r\n                    }\r\n                    tmpRange.setEndAfter( tmpNode );\r\n                    \r\n                    para = range.document.createElement( style );\r\n                    if(attrs){\r\n                        domUtils.setAttributes(para,attrs);\r\n                        if(sourceCmdName && sourceCmdName == 'customstyle' && attrs.style){\r\n                            para.style.cssText = attrs.style;\r\n                        }\r\n                    }\r\n                    para.appendChild( tmpRange.extractContents() );\r\n                    //需要内容占位\r\n                    if(domUtils.isEmptyNode(para)){\r\n                        domUtils.fillChar(range.document,para);\r\n                        \r\n                    }\r\n\r\n                    tmpRange.insertNode( para );\r\n\r\n                    var parent = para.parentNode;\r\n                    //如果para上一级是一个block元素且不是body,td就删除它\r\n                    if ( block( parent ) && !domUtils.isBody( para.parentNode ) && utils.indexOf(notExchange,parent.tagName)==-1) {\r\n                        //存储dir,style\r\n                        if(!(sourceCmdName && sourceCmdName == 'customstyle')){\r\n                            parent.getAttribute('dir') && para.setAttribute('dir',parent.getAttribute('dir'));\r\n                            //trace:1070\r\n                            parent.style.cssText && (para.style.cssText = parent.style.cssText + ';' + para.style.cssText);\r\n                            //trace:1030\r\n                            parent.style.textAlign && !para.style.textAlign && (para.style.textAlign = parent.style.textAlign);\r\n                            parent.style.textIndent && !para.style.textIndent && (para.style.textIndent = parent.style.textIndent);\r\n                            parent.style.padding && !para.style.padding && (para.style.padding = parent.style.padding);\r\n                        }\r\n\r\n                        //trace:1706 选择的就是h1-6要删除\r\n                        if(attrs && /h\\d/i.test(parent.tagName) && !/h\\d/i.test(para.tagName) ){\r\n                            domUtils.setAttributes(parent,attrs);\r\n                            if(sourceCmdName && sourceCmdName == 'customstyle' && attrs.style){\r\n                                parent.style.cssText = attrs.style;\r\n                            }\r\n                            domUtils.remove(para,true);\r\n                            para = parent;\r\n                        }else{\r\n                            domUtils.remove( para.parentNode, true );\r\n                        }\r\n\r\n                    }\r\n                    if(  utils.indexOf(notExchange,parent.tagName)!=-1){\r\n                        current = parent;\r\n                    }else{\r\n                       current = para;\r\n                    }\r\n\r\n\r\n                    current = domUtils.getNextDomNode( current, false, filterFn );\r\n                } else {\r\n                    current = domUtils.getNextDomNode( current, true, filterFn );\r\n                }\r\n            }\r\n            return range.moveToBookmark( bookmark2 ).moveToBookmark( bookmark );\r\n        };\r\n    me.setOpt('paragraph',{'p':'', 'h1':'', 'h2':'', 'h3':'', 'h4':'', 'h5':'', 'h6':''});\r\n    me.commands['paragraph'] = {\r\n        execCommand : function( cmdName, style,attrs,sourceCmdName ) {\r\n            var range = this.selection.getRange();\r\n             //闭合时单独处理\r\n            if(range.collapsed){\r\n                var txt = this.document.createTextNode('p');\r\n                range.insertNode(txt);\r\n                //去掉冗余的fillchar\r\n                if(browser.ie){\r\n                    var node = txt.previousSibling;\r\n                    if(node && domUtils.isWhitespace(node)){\r\n                        domUtils.remove(node);\r\n                    }\r\n                    node = txt.nextSibling;\r\n                    if(node && domUtils.isWhitespace(node)){\r\n                        domUtils.remove(node);\r\n                    }\r\n                }\r\n\r\n            }\r\n            range = doParagraph(range,style,attrs,sourceCmdName);\r\n            if(txt){\r\n                range.setStartBefore(txt).collapse(true);\r\n                pN = txt.parentNode;\r\n\r\n                domUtils.remove(txt);\r\n\r\n                if(domUtils.isBlockElm(pN)&&domUtils.isEmptyNode(pN)){\r\n                    domUtils.fillNode(this.document,pN);\r\n                }\r\n\r\n            }\r\n\r\n            if(browser.gecko && range.collapsed && range.startContainer.nodeType == 1){\r\n                var child = range.startContainer.childNodes[range.startOffset];\r\n                if(child && child.nodeType == 1 && child.tagName.toLowerCase() == style){\r\n                    range.setStart(child,0).collapse(true);\r\n                }\r\n            }\r\n            //trace:1097 原来有true，原因忘了，但去了就不能清除多余的占位符了\r\n            range.select();\r\n\r\n\r\n            return true;\r\n        },\r\n        queryCommandValue : function() {\r\n            var node = domUtils.filterNodeList(this.selection.getStartElementPath(),'p h1 h2 h3 h4 h5 h6');\r\n            return node ? node.tagName.toLowerCase() : '';\r\n        }\r\n    };\r\n};\r\n\r\n\r\n// plugins/directionality.js\r\n/**\r\n * 设置文字输入的方向的插件\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n(function() {\r\n    var block = domUtils.isBlockElm ,\r\n        getObj = function(editor){\r\n//            var startNode = editor.selection.getStart(),\r\n//                parents;\r\n//            if ( startNode ) {\r\n//                //查找所有的是block的父亲节点\r\n//                parents = domUtils.findParents( startNode, true, block, true );\r\n//                for ( var i = 0,ci; ci = parents[i++]; ) {\r\n//                    if ( ci.getAttribute( 'dir' ) ) {\r\n//                        return ci;\r\n//                    }\r\n//                }\r\n//            }\r\n            return domUtils.filterNodeList(editor.selection.getStartElementPath(),function(n){return n && n.nodeType == 1 && n.getAttribute('dir')});\r\n\r\n        },\r\n        doDirectionality = function(range,editor,forward){\r\n            \r\n            var bookmark,\r\n                filterFn = function( node ) {\r\n                    return   node.nodeType == 1 ? !domUtils.isBookmarkNode(node) : !domUtils.isWhitespace(node);\r\n                },\r\n\r\n                obj = getObj( editor );\r\n\r\n            if ( obj && range.collapsed ) {\r\n                obj.setAttribute( 'dir', forward );\r\n                return range;\r\n            }\r\n            bookmark = range.createBookmark();\r\n            range.enlarge( true );\r\n            var bookmark2 = range.createBookmark(),\r\n                current = domUtils.getNextDomNode( bookmark2.start, false, filterFn ),\r\n                tmpRange = range.cloneRange(),\r\n                tmpNode;\r\n            while ( current &&  !(domUtils.getPosition( current, bookmark2.end ) & domUtils.POSITION_FOLLOWING) ) {\r\n                if ( current.nodeType == 3 || !block( current ) ) {\r\n                    tmpRange.setStartBefore( current );\r\n                    while ( current && current !== bookmark2.end && !block( current ) ) {\r\n                        tmpNode = current;\r\n                        current = domUtils.getNextDomNode( current, false, null, function( node ) {\r\n                            return !block( node );\r\n                        } );\r\n                    }\r\n                    tmpRange.setEndAfter( tmpNode );\r\n                    var common = tmpRange.getCommonAncestor();\r\n                    if ( !domUtils.isBody( common ) && block( common ) ) {\r\n                        //遍历到了block节点\r\n                        common.setAttribute( 'dir', forward );\r\n                        current = common;\r\n                    } else {\r\n                        //没有遍历到，添加一个block节点\r\n                        var p = range.document.createElement( 'p' );\r\n                        p.setAttribute( 'dir', forward );\r\n                        var frag = tmpRange.extractContents();\r\n                        p.appendChild( frag );\r\n                        tmpRange.insertNode( p );\r\n                        current = p;\r\n                    }\r\n\r\n                    current = domUtils.getNextDomNode( current, false, filterFn );\r\n                } else {\r\n                    current = domUtils.getNextDomNode( current, true, filterFn );\r\n                }\r\n            }\r\n            return range.moveToBookmark( bookmark2 ).moveToBookmark( bookmark );\r\n        };\r\n\r\n    /**\r\n     * 文字输入方向\r\n     * @command directionality\r\n     * @method execCommand\r\n     * @param { String } cmdName 命令字符串\r\n     * @param { String } forward 传入'ltr'表示从左向右输入，传入'rtl'表示从右向左输入\r\n     * @example\r\n     * ```javascript\r\n     * editor.execCommand( 'directionality', 'ltr');\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 查询当前选区的文字输入方向\r\n     * @command directionality\r\n     * @method queryCommandValue\r\n     * @param { String } cmdName 命令字符串\r\n     * @return { String } 返回'ltr'表示从左向右输入，返回'rtl'表示从右向左输入\r\n     * @example\r\n     * ```javascript\r\n     * editor.queryCommandValue( 'directionality');\r\n     * ```\r\n     */\r\n    UE.commands['directionality'] = {\r\n        execCommand : function( cmdName,forward ) {\r\n            var range = this.selection.getRange();\r\n            //闭合时单独处理\r\n            if(range.collapsed){\r\n                var txt = this.document.createTextNode('d');\r\n                range.insertNode(txt);\r\n            }\r\n            doDirectionality(range,this,forward);\r\n            if(txt){\r\n                range.setStartBefore(txt).collapse(true);\r\n                domUtils.remove(txt);\r\n            }\r\n\r\n            range.select();\r\n            return true;\r\n        },\r\n        queryCommandValue : function() {\r\n            var node = getObj(this);\r\n            return node ? node.getAttribute('dir') : 'ltr';\r\n        }\r\n    };\r\n})();\r\n\r\n\r\n\r\n// plugins/horizontal.js\r\n/**\r\n * 插入分割线插件\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 插入分割线\r\n * @command horizontal\r\n * @method execCommand\r\n * @param { String } cmdName 命令字符串\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'horizontal' );\r\n * ```\r\n */\r\nUE.plugins['horizontal'] = function(){\r\n    var me = this;\r\n    me.commands['horizontal'] = {\r\n        execCommand : function( cmdName ) {\r\n            var me = this;\r\n            if(me.queryCommandState(cmdName)!==-1){\r\n                me.execCommand('insertHtml','<hr>');\r\n                var range = me.selection.getRange(),\r\n                    start = range.startContainer;\r\n                if(start.nodeType == 1 && !start.childNodes[range.startOffset] ){\r\n\r\n                    var tmp;\r\n                    if(tmp = start.childNodes[range.startOffset - 1]){\r\n                        if(tmp.nodeType == 1 && tmp.tagName == 'HR'){\r\n                            if(me.options.enterTag == 'p'){\r\n                                tmp = me.document.createElement('p');\r\n                                range.insertNode(tmp);\r\n                                range.setStart(tmp,0).setCursor();\r\n\r\n                            }else{\r\n                                tmp = me.document.createElement('br');\r\n                                range.insertNode(tmp);\r\n                                range.setStartBefore(tmp).setCursor();\r\n                            }\r\n                        }\r\n                    }\r\n\r\n                }\r\n                return true;\r\n            }\r\n\r\n        },\r\n        //边界在table里不能加分隔线\r\n        queryCommandState : function() {\r\n            return domUtils.filterNodeList(this.selection.getStartElementPath(),'table') ? -1 : 0;\r\n        }\r\n    };\r\n//    me.addListener('delkeyup',function(){\r\n//        var rng = this.selection.getRange();\r\n//        if(browser.ie && browser.version > 8){\r\n//            rng.txtToElmBoundary(true);\r\n//            if(domUtils.isStartInblock(rng)){\r\n//                var tmpNode = rng.startContainer;\r\n//                var pre = tmpNode.previousSibling;\r\n//                if(pre && domUtils.isTagNode(pre,'hr')){\r\n//                    domUtils.remove(pre);\r\n//                    rng.select();\r\n//                    return;\r\n//                }\r\n//            }\r\n//        }\r\n//        if(domUtils.isBody(rng.startContainer)){\r\n//            var hr = rng.startContainer.childNodes[rng.startOffset -1];\r\n//            if(hr && hr.nodeName == 'HR'){\r\n//                var next = hr.nextSibling;\r\n//                if(next){\r\n//                    rng.setStart(next,0)\r\n//                }else if(hr.previousSibling){\r\n//                    rng.setStartAtLast(hr.previousSibling)\r\n//                }else{\r\n//                    var p = this.document.createElement('p');\r\n//                    hr.parentNode.insertBefore(p,hr);\r\n//                    domUtils.fillNode(this.document,p);\r\n//                    rng.setStart(p,0);\r\n//                }\r\n//                domUtils.remove(hr);\r\n//                rng.setCursor(false,true);\r\n//            }\r\n//        }\r\n//    })\r\n    me.addListener('delkeydown',function(name,evt){\r\n        var rng = this.selection.getRange();\r\n        rng.txtToElmBoundary(true);\r\n        if(domUtils.isStartInblock(rng)){\r\n            var tmpNode = rng.startContainer;\r\n            var pre = tmpNode.previousSibling;\r\n            if(pre && domUtils.isTagNode(pre,'hr')){\r\n                domUtils.remove(pre);\r\n                rng.select();\r\n                domUtils.preventDefault(evt);\r\n                return true;\r\n\r\n            }\r\n        }\r\n\r\n    })\r\n};\r\n\r\n\r\n\r\n// plugins/time.js\r\n/**\r\n * 插入时间和日期\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 插入时间，默认格式：12:59:59\r\n * @command time\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'time');\r\n * ```\r\n */\r\n\r\n/**\r\n * 插入日期，默认格式：2013-08-30\r\n * @command date\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'date');\r\n * ```\r\n */\r\nUE.commands['time'] = UE.commands[\"date\"] = {\r\n    execCommand : function(cmd, format){\r\n        var date = new Date;\r\n\r\n        function formatTime(date, format) {\r\n            var hh = ('0' + date.getHours()).slice(-2),\r\n                ii = ('0' + date.getMinutes()).slice(-2),\r\n                ss = ('0' + date.getSeconds()).slice(-2);\r\n            format = format || 'hh:ii:ss';\r\n            return format.replace(/hh/ig, hh).replace(/ii/ig, ii).replace(/ss/ig, ss);\r\n        }\r\n        function formatDate(date, format) {\r\n            var yyyy = ('000' + date.getFullYear()).slice(-4),\r\n                yy = yyyy.slice(-2),\r\n                mm = ('0' + (date.getMonth()+1)).slice(-2),\r\n                dd = ('0' + date.getDate()).slice(-2);\r\n            format = format || 'yyyy-mm-dd';\r\n            return format.replace(/yyyy/ig, yyyy).replace(/yy/ig, yy).replace(/mm/ig, mm).replace(/dd/ig, dd);\r\n        }\r\n\r\n        this.execCommand('insertHtml',cmd == \"time\" ? formatTime(date, format):formatDate(date, format) );\r\n    }\r\n};\r\n\r\n\r\n// plugins/rowspacing.js\r\n/**\r\n * 段前段后间距插件\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 设置段间距\r\n * @command rowspacing\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @param { String } value 段间距的值，以px为单位\r\n * @param { String } dir 间距位置，top或bottom，分别表示段前和段后\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'rowspacing', '10', 'top' );\r\n * ```\r\n */\r\n\r\nUE.plugins['rowspacing'] = function(){\r\n    var me = this;\r\n    me.setOpt({\r\n        'rowspacingtop':['5', '10', '15', '20', '25'],\r\n        'rowspacingbottom':['5', '10', '15', '20', '25']\r\n\r\n    });\r\n    me.commands['rowspacing'] =  {\r\n        execCommand : function( cmdName,value,dir ) {\r\n            this.execCommand('paragraph','p',{style:'margin-'+dir+':'+value + 'px'});\r\n            return true;\r\n        },\r\n        queryCommandValue : function(cmdName,dir) {\r\n            var pN = domUtils.filterNodeList(this.selection.getStartElementPath(),function(node){return domUtils.isBlockElm(node) }),\r\n                value;\r\n            //trace:1026\r\n            if(pN){\r\n                value = domUtils.getComputedStyle(pN,'margin-'+dir).replace(/[^\\d]/g,'');\r\n                return !value ? 0 : value;\r\n            }\r\n            return 0;\r\n\r\n        }\r\n    };\r\n};\r\n\r\n\r\n\r\n\r\n// plugins/lineheight.js\r\n/**\r\n * 设置行内间距\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\nUE.plugins['lineheight'] = function(){\r\n    var me = this;\r\n    me.setOpt({'lineheight':['1', '1.5','1.75','2', '3', '4', '5']});\r\n\r\n    /**\r\n     * 行距\r\n     * @command lineheight\r\n     * @method execCommand\r\n     * @param { String } cmdName 命令字符串\r\n     * @param { String } value 传入的行高值， 该值是当前字体的倍数， 例如： 1.5, 1.75\r\n     * @example\r\n     * ```javascript\r\n     * editor.execCommand( 'lineheight', 1.5);\r\n     * ```\r\n     */\r\n    /**\r\n     * 查询当前选区内容的行高大小\r\n     * @command lineheight\r\n     * @method queryCommandValue\r\n     * @param { String } cmd 命令字符串\r\n     * @return { String } 返回当前行高大小\r\n     * @example\r\n     * ```javascript\r\n     * editor.queryCommandValue( 'lineheight' );\r\n     * ```\r\n     */\r\n\r\n    me.commands['lineheight'] =  {\r\n        execCommand : function( cmdName,value ) {\r\n            this.execCommand('paragraph','p',{style:'line-height:'+ (value == \"1\" ? \"normal\" : value + 'em') });\r\n            return true;\r\n        },\r\n        queryCommandValue : function() {\r\n            var pN = domUtils.filterNodeList(this.selection.getStartElementPath(),function(node){return domUtils.isBlockElm(node)});\r\n            if(pN){\r\n                var value = domUtils.getComputedStyle(pN,'line-height');\r\n                return value == 'normal' ? 1 : value.replace(/[^\\d.]*/ig,\"\");\r\n            }\r\n        }\r\n    };\r\n};\r\n\r\n\r\n\r\n\r\n// plugins/insertcode.js\r\n/**\r\n * 插入代码插件\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\nUE.plugins['insertcode'] = function() {\r\n    var me = this;\r\n    me.ready(function(){\r\n        utils.cssRule('pre','pre{margin:.5em 0;padding:.4em .6em;border-radius:8px;background:#f8f8f8;}',\r\n            me.document)\r\n    });\r\n    me.setOpt('insertcode',{\r\n            'as3':'ActionScript3',\r\n            'bash':'Bash/Shell',\r\n            'cpp':'C/C++',\r\n            'css':'Css',\r\n            'cf':'CodeFunction',\r\n            'c#':'C#',\r\n            'delphi':'Delphi',\r\n            'diff':'Diff',\r\n            'erlang':'Erlang',\r\n            'groovy':'Groovy',\r\n            'html':'Html',\r\n            'java':'Java',\r\n            'jfx':'JavaFx',\r\n            'js':'Javascript',\r\n            'pl':'Perl',\r\n            'php':'Php',\r\n            'plain':'Plain Text',\r\n            'ps':'PowerShell',\r\n            'python':'Python',\r\n            'ruby':'Ruby',\r\n            'scala':'Scala',\r\n            'sql':'Sql',\r\n            'vb':'Vb',\r\n            'xml':'Xml'\r\n    });\r\n\r\n    /**\r\n     * 插入代码\r\n     * @command insertcode\r\n     * @method execCommand\r\n     * @param { String } cmd 命令字符串\r\n     * @param { String } lang 插入代码的语言\r\n     * @example\r\n     * ```javascript\r\n     * editor.execCommand( 'insertcode', 'javascript' );\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 如果选区所在位置是插入插入代码区域，返回代码的语言\r\n     * @command insertcode\r\n     * @method queryCommandValue\r\n     * @param { String } cmd 命令字符串\r\n     * @return { String } 返回代码的语言\r\n     * @example\r\n     * ```javascript\r\n     * editor.queryCommandValue( 'insertcode' );\r\n     * ```\r\n     */\r\n\r\n    me.commands['insertcode'] = {\r\n        execCommand : function(cmd,lang){\r\n            var me = this,\r\n                rng = me.selection.getRange(),\r\n                pre = domUtils.findParentByTagName(rng.startContainer,'pre',true);\r\n            if(pre){\r\n                pre.className = 'brush:'+lang+';toolbar:false;';\r\n            }else{\r\n                var code = '';\r\n                if(rng.collapsed){\r\n                    code = browser.ie && browser.ie11below ? (browser.version <= 8 ? '&nbsp;':''):'<br/>';\r\n                }else{\r\n                    var frag = rng.extractContents();\r\n                    var div = me.document.createElement('div');\r\n                    div.appendChild(frag);\r\n\r\n                    utils.each(UE.filterNode(UE.htmlparser(div.innerHTML.replace(/[\\r\\t]/g,'')),me.options.filterTxtRules).children,function(node){\r\n                        if(browser.ie && browser.ie11below && browser.version > 8){\r\n\r\n                            if(node.type =='element'){\r\n                                if(node.tagName == 'br'){\r\n                                    code += '\\n'\r\n                                }else if(!dtd.$empty[node.tagName]){\r\n                                    utils.each(node.children,function(cn){\r\n                                        if(cn.type =='element'){\r\n                                            if(cn.tagName == 'br'){\r\n                                                code += '\\n'\r\n                                            }else if(!dtd.$empty[node.tagName]){\r\n                                                code += cn.innerText();\r\n                                            }\r\n                                        }else{\r\n                                            code += cn.data\r\n                                        }\r\n                                    })\r\n                                    if(!/\\n$/.test(code)){\r\n                                        code += '\\n';\r\n                                    }\r\n                                }\r\n                            }else{\r\n                                code += node.data + '\\n'\r\n                            }\r\n                            if(!node.nextSibling() && /\\n$/.test(code)){\r\n                                code = code.replace(/\\n$/,'');\r\n                            }\r\n                        }else{\r\n                            if(browser.ie && browser.ie11below){\r\n\r\n                                if(node.type =='element'){\r\n                                    if(node.tagName == 'br'){\r\n                                        code += '<br>'\r\n                                    }else if(!dtd.$empty[node.tagName]){\r\n                                        utils.each(node.children,function(cn){\r\n                                            if(cn.type =='element'){\r\n                                                if(cn.tagName == 'br'){\r\n                                                    code += '<br>'\r\n                                                }else if(!dtd.$empty[node.tagName]){\r\n                                                    code += cn.innerText();\r\n                                                }\r\n                                            }else{\r\n                                                code += cn.data\r\n                                            }\r\n                                        });\r\n                                        if(!/br>$/.test(code)){\r\n                                            code += '<br>';\r\n                                        }\r\n                                    }\r\n                                }else{\r\n                                    code += node.data + '<br>'\r\n                                }\r\n                                if(!node.nextSibling() && /<br>$/.test(code)){\r\n                                    code = code.replace(/<br>$/,'');\r\n                                }\r\n\r\n                            }else{\r\n                                code += (node.type == 'element' ? (dtd.$empty[node.tagName] ?  '' : node.innerText()) : node.data);\r\n                                if(!/br\\/?\\s*>$/.test(code)){\r\n                                    if(!node.nextSibling())\r\n                                        return;\r\n                                    code += '<br>'\r\n                                }\r\n                            }\r\n\r\n                        }\r\n\r\n                    });\r\n                }\r\n                me.execCommand('inserthtml','<pre id=\"coder\"class=\"brush:'+lang+';toolbar:false\">'+code+'</pre>',true);\r\n\r\n                pre = me.document.getElementById('coder');\r\n                domUtils.removeAttributes(pre,'id');\r\n                var tmpNode = pre.previousSibling;\r\n\r\n                if(tmpNode && (tmpNode.nodeType == 3 && tmpNode.nodeValue.length == 1 && browser.ie && browser.version == 6 ||  domUtils.isEmptyBlock(tmpNode))){\r\n\r\n                    domUtils.remove(tmpNode)\r\n                }\r\n                var rng = me.selection.getRange();\r\n                if(domUtils.isEmptyBlock(pre)){\r\n                    rng.setStart(pre,0).setCursor(false,true)\r\n                }else{\r\n                    rng.selectNodeContents(pre).select()\r\n                }\r\n            }\r\n\r\n\r\n\r\n        },\r\n        queryCommandValue : function(){\r\n            var path = this.selection.getStartElementPath();\r\n            var lang = '';\r\n            utils.each(path,function(node){\r\n                if(node.nodeName =='PRE'){\r\n                    var match = node.className.match(/brush:([^;]+)/);\r\n                    lang = match && match[1] ? match[1] : '';\r\n                    return false;\r\n                }\r\n            });\r\n            return lang;\r\n        }\r\n    };\r\n\r\n    me.addInputRule(function(root){\r\n       utils.each(root.getNodesByTagName('pre'),function(pre){\r\n           var brs = pre.getNodesByTagName('br');\r\n           if(brs.length){\r\n               browser.ie && browser.ie11below && browser.version > 8 && utils.each(brs,function(br){\r\n                   var txt = UE.uNode.createText('\\n');\r\n                   br.parentNode.insertBefore(txt,br);\r\n                   br.parentNode.removeChild(br);\r\n               });\r\n               return;\r\n            }\r\n           if(browser.ie && browser.ie11below && browser.version > 8)\r\n                return;\r\n            var code = pre.innerText().split(/\\n/);\r\n            pre.innerHTML('');\r\n            utils.each(code,function(c){\r\n                if(c.length){\r\n                    pre.appendChild(UE.uNode.createText(c));\r\n                }\r\n                pre.appendChild(UE.uNode.createElement('br'))\r\n            })\r\n       })\r\n    });\r\n    me.addOutputRule(function(root){\r\n        utils.each(root.getNodesByTagName('pre'),function(pre){\r\n            var code = '';\r\n            utils.each(pre.children,function(n){\r\n               if(n.type == 'text'){\r\n                   //在ie下文本内容有可能末尾带有\\n要去掉\r\n                   //trace:3396\r\n                   code += n.data.replace(/[ ]/g,'&nbsp;').replace(/\\n$/,'');\r\n               }else{\r\n                   if(n.tagName == 'br'){\r\n                       code  += '\\n'\r\n                   }else{\r\n                       code += (!dtd.$empty[n.tagName] ? '' : n.innerText());\r\n                   }\r\n\r\n               }\r\n\r\n            });\r\n\r\n            pre.innerText(code.replace(/(&nbsp;|\\n)+$/,''))\r\n        })\r\n    });\r\n    //不需要判断highlight的command列表\r\n    me.notNeedCodeQuery ={\r\n        help:1,\r\n        undo:1,\r\n        redo:1,\r\n        source:1,\r\n        print:1,\r\n        searchreplace:1,\r\n        fullscreen:1,\r\n        preview:1,\r\n        insertparagraph:1,\r\n        elementpath:1,\r\n        insertcode:1,\r\n        inserthtml:1,\r\n        selectall:1\r\n    };\r\n    //将queyCommamndState重置\r\n    var orgQuery = me.queryCommandState;\r\n    me.queryCommandState = function(cmd){\r\n        var me = this;\r\n\r\n        if(!me.notNeedCodeQuery[cmd.toLowerCase()] && me.selection && me.queryCommandValue('insertcode')){\r\n            return -1;\r\n        }\r\n        return UE.Editor.prototype.queryCommandState.apply(this,arguments)\r\n    };\r\n    me.addListener('beforeenterkeydown',function(){\r\n        var rng = me.selection.getRange();\r\n        var pre = domUtils.findParentByTagName(rng.startContainer,'pre',true);\r\n        if(pre){\r\n            me.fireEvent('saveScene');\r\n            if(!rng.collapsed){\r\n               rng.deleteContents();\r\n            }\r\n            if(!browser.ie || browser.ie9above){\r\n                var tmpNode = me.document.createElement('br'),pre;\r\n                rng.insertNode(tmpNode).setStartAfter(tmpNode).collapse(true);\r\n                var next = tmpNode.nextSibling;\r\n                if(!next && (!browser.ie || browser.version > 10)){\r\n                    rng.insertNode(tmpNode.cloneNode(false));\r\n                }else{\r\n                    rng.setStartAfter(tmpNode);\r\n                }\r\n                pre = tmpNode.previousSibling;\r\n                var tmp;\r\n                while(pre ){\r\n                    tmp = pre;\r\n                    pre = pre.previousSibling;\r\n                    if(!pre || pre.nodeName == 'BR'){\r\n                        pre = tmp;\r\n                        break;\r\n                    }\r\n                }\r\n                if(pre){\r\n                    var str = '';\r\n                    while(pre && pre.nodeName != 'BR' &&  new RegExp('^[\\\\s'+domUtils.fillChar+']*$').test(pre.nodeValue)){\r\n                        str += pre.nodeValue;\r\n                        pre = pre.nextSibling;\r\n                    }\r\n                    if(pre.nodeName != 'BR'){\r\n                        var match = pre.nodeValue.match(new RegExp('^([\\\\s'+domUtils.fillChar+']+)'));\r\n                        if(match && match[1]){\r\n                            str += match[1]\r\n                        }\r\n\r\n                    }\r\n                    if(str){\r\n                        str = me.document.createTextNode(str);\r\n                        rng.insertNode(str).setStartAfter(str);\r\n                    }\r\n                }\r\n                rng.collapse(true).select(true);\r\n            }else{\r\n                if(browser.version > 8){\r\n\r\n                    var txt = me.document.createTextNode('\\n');\r\n                    var start = rng.startContainer;\r\n                    if(rng.startOffset == 0){\r\n                        var preNode = start.previousSibling;\r\n                        if(preNode){\r\n                            rng.insertNode(txt);\r\n                            var fillchar = me.document.createTextNode(' ');\r\n                            rng.setStartAfter(txt).insertNode(fillchar).setStart(fillchar,0).collapse(true).select(true)\r\n                        }\r\n                    }else{\r\n                        rng.insertNode(txt).setStartAfter(txt);\r\n                        var fillchar = me.document.createTextNode(' ');\r\n                        start = rng.startContainer.childNodes[rng.startOffset];\r\n                        if(start && !/^\\n/.test(start.nodeValue)){\r\n                            rng.setStartBefore(txt)\r\n                        }\r\n                        rng.insertNode(fillchar).setStart(fillchar,0).collapse(true).select(true)\r\n                    }\r\n\r\n                }else{\r\n                    var tmpNode = me.document.createElement('br');\r\n                    rng.insertNode(tmpNode);\r\n                    rng.insertNode(me.document.createTextNode(domUtils.fillChar));\r\n                    rng.setStartAfter(tmpNode);\r\n                    pre = tmpNode.previousSibling;\r\n                    var tmp;\r\n                    while(pre ){\r\n                        tmp = pre;\r\n                        pre = pre.previousSibling;\r\n                        if(!pre || pre.nodeName == 'BR'){\r\n                            pre = tmp;\r\n                            break;\r\n                        }\r\n                    }\r\n                    if(pre){\r\n                        var str = '';\r\n                        while(pre && pre.nodeName != 'BR' &&  new RegExp('^[ '+domUtils.fillChar+']*$').test(pre.nodeValue)){\r\n                            str += pre.nodeValue;\r\n                            pre = pre.nextSibling;\r\n                        }\r\n                        if(pre.nodeName != 'BR'){\r\n                            var match = pre.nodeValue.match(new RegExp('^([ '+domUtils.fillChar+']+)'));\r\n                            if(match && match[1]){\r\n                                str += match[1]\r\n                            }\r\n\r\n                        }\r\n\r\n                        str = me.document.createTextNode(str);\r\n                        rng.insertNode(str).setStartAfter(str);\r\n                    }\r\n                    rng.collapse(true).select();\r\n                }\r\n\r\n\r\n            }\r\n            me.fireEvent('saveScene');\r\n            return true;\r\n        }\r\n\r\n\r\n    });\r\n\r\n    me.addListener('tabkeydown',function(cmd,evt){\r\n        var rng = me.selection.getRange();\r\n        var pre = domUtils.findParentByTagName(rng.startContainer,'pre',true);\r\n        if(pre){\r\n            me.fireEvent('saveScene');\r\n            if(evt.shiftKey){\r\n\r\n            }else{\r\n                if(!rng.collapsed){\r\n                    var bk = rng.createBookmark();\r\n                    var start = bk.start.previousSibling;\r\n\r\n                    while(start){\r\n                        if(pre.firstChild === start && !domUtils.isBr(start)){\r\n                            pre.insertBefore(me.document.createTextNode('    '),start);\r\n\r\n                            break;\r\n                        }\r\n                        if(domUtils.isBr(start)){\r\n                            pre.insertBefore(me.document.createTextNode('    '),start.nextSibling);\r\n\r\n                            break;\r\n                        }\r\n                        start = start.previousSibling;\r\n                    }\r\n                    var end = bk.end;\r\n                    start = bk.start.nextSibling;\r\n                    if(pre.firstChild === bk.start){\r\n                        pre.insertBefore(me.document.createTextNode('    '),start.nextSibling)\r\n\r\n                    }\r\n                    while(start && start !== end){\r\n                        if(domUtils.isBr(start) && start.nextSibling){\r\n                            if(start.nextSibling === end){\r\n                                break;\r\n                            }\r\n                            pre.insertBefore(me.document.createTextNode('    '),start.nextSibling)\r\n                        }\r\n\r\n                        start = start.nextSibling;\r\n                    }\r\n                    rng.moveToBookmark(bk).select();\r\n                }else{\r\n                    var tmpNode = me.document.createTextNode('    ');\r\n                    rng.insertNode(tmpNode).setStartAfter(tmpNode).collapse(true).select(true);\r\n                }\r\n            }\r\n\r\n\r\n            me.fireEvent('saveScene');\r\n            return true;\r\n        }\r\n\r\n\r\n    });\r\n\r\n\r\n    me.addListener('beforeinserthtml',function(evtName,html){\r\n        var me = this,\r\n            rng = me.selection.getRange(),\r\n            pre = domUtils.findParentByTagName(rng.startContainer,'pre',true);\r\n        if(pre){\r\n            if(!rng.collapsed){\r\n                rng.deleteContents()\r\n            }\r\n            var htmlstr = '';\r\n            if(browser.ie && browser.version > 8){\r\n\r\n                utils.each(UE.filterNode(UE.htmlparser(html),me.options.filterTxtRules).children,function(node){\r\n                    if(node.type =='element'){\r\n                        if(node.tagName == 'br'){\r\n                            htmlstr += '\\n'\r\n                        }else if(!dtd.$empty[node.tagName]){\r\n                            utils.each(node.children,function(cn){\r\n                                if(cn.type =='element'){\r\n                                    if(cn.tagName == 'br'){\r\n                                        htmlstr += '\\n'\r\n                                    }else if(!dtd.$empty[node.tagName]){\r\n                                        htmlstr += cn.innerText();\r\n                                    }\r\n                                }else{\r\n                                    htmlstr += cn.data\r\n                                }\r\n                            })\r\n                            if(!/\\n$/.test(htmlstr)){\r\n                                htmlstr += '\\n';\r\n                            }\r\n                        }\r\n                    }else{\r\n                        htmlstr += node.data + '\\n'\r\n                    }\r\n                    if(!node.nextSibling() && /\\n$/.test(htmlstr)){\r\n                        htmlstr = htmlstr.replace(/\\n$/,'');\r\n                    }\r\n                });\r\n                var tmpNode = me.document.createTextNode(utils.html(htmlstr.replace(/&nbsp;/g,' ')));\r\n                rng.insertNode(tmpNode).selectNode(tmpNode).select();\r\n            }else{\r\n                var frag = me.document.createDocumentFragment();\r\n\r\n                utils.each(UE.filterNode(UE.htmlparser(html),me.options.filterTxtRules).children,function(node){\r\n                    if(node.type =='element'){\r\n                        if(node.tagName == 'br'){\r\n                            frag.appendChild(me.document.createElement('br'))\r\n                        }else if(!dtd.$empty[node.tagName]){\r\n                            utils.each(node.children,function(cn){\r\n                                if(cn.type =='element'){\r\n                                    if(cn.tagName == 'br'){\r\n\r\n                                        frag.appendChild(me.document.createElement('br'))\r\n                                    }else if(!dtd.$empty[node.tagName]){\r\n                                        frag.appendChild(me.document.createTextNode(utils.html(cn.innerText().replace(/&nbsp;/g,' '))));\r\n\r\n                                    }\r\n                                }else{\r\n                                    frag.appendChild(me.document.createTextNode(utils.html( cn.data.replace(/&nbsp;/g,' '))));\r\n\r\n                                }\r\n                            })\r\n                            if(frag.lastChild.nodeName != 'BR'){\r\n                                frag.appendChild(me.document.createElement('br'))\r\n                            }\r\n                        }\r\n                    }else{\r\n                        frag.appendChild(me.document.createTextNode(utils.html( node.data.replace(/&nbsp;/g,' '))));\r\n                    }\r\n                    if(!node.nextSibling() && frag.lastChild.nodeName == 'BR'){\r\n                       frag.removeChild(frag.lastChild)\r\n                    }\r\n\r\n\r\n                });\r\n                rng.insertNode(frag).select();\r\n\r\n            }\r\n\r\n            return true;\r\n        }\r\n    });\r\n    //方向键的处理\r\n    me.addListener('keydown',function(cmd,evt){\r\n        var me = this,keyCode = evt.keyCode || evt.which;\r\n        if(keyCode == 40){\r\n            var rng = me.selection.getRange(),pre,start = rng.startContainer;\r\n            if(rng.collapsed && (pre = domUtils.findParentByTagName(rng.startContainer,'pre',true)) && !pre.nextSibling){\r\n                var last = pre.lastChild\r\n                while(last && last.nodeName == 'BR'){\r\n                    last = last.previousSibling;\r\n                }\r\n                if(last === start || rng.startContainer === pre && rng.startOffset == pre.childNodes.length){\r\n                    me.execCommand('insertparagraph');\r\n                    domUtils.preventDefault(evt)\r\n                }\r\n\r\n            }\r\n        }\r\n    });\r\n    //trace:3395\r\n    me.addListener('delkeydown',function(type,evt){\r\n        var rng = this.selection.getRange();\r\n        rng.txtToElmBoundary(true);\r\n        var start = rng.startContainer;\r\n        if(domUtils.isTagNode(start,'pre') && rng.collapsed && domUtils.isStartInblock(rng)){\r\n            var p = me.document.createElement('p');\r\n            domUtils.fillNode(me.document,p);\r\n            start.parentNode.insertBefore(p,start);\r\n            domUtils.remove(start);\r\n            rng.setStart(p,0).setCursor(false,true);\r\n            domUtils.preventDefault(evt);\r\n            return true;\r\n        }\r\n    })\r\n};\r\n\r\n\r\n// plugins/cleardoc.js\r\n/**\r\n * 清空文档插件\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 清空文档\r\n * @command cleardoc\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * //editor 是编辑器实例\r\n * editor.execCommand('cleardoc');\r\n * ```\r\n */\r\n\r\nUE.commands['cleardoc'] = {\r\n    execCommand : function( cmdName) {\r\n        var me = this,\r\n            enterTag = me.options.enterTag,\r\n            range = me.selection.getRange();\r\n        if(enterTag == \"br\"){\r\n            me.body.innerHTML = \"<br/>\";\r\n            range.setStart(me.body,0).setCursor();\r\n        }else{\r\n            me.body.innerHTML = \"<p>\"+(ie ? \"\" : \"<br/>\")+\"</p>\";\r\n            range.setStart(me.body.firstChild,0).setCursor(false,true);\r\n        }\r\n        setTimeout(function(){\r\n            me.fireEvent(\"clearDoc\");\r\n        },0);\r\n\r\n    }\r\n};\r\n\r\n\r\n\r\n// plugins/anchor.js\r\n/**\r\n * 锚点插件，为UEditor提供插入锚点支持\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\nUE.plugin.register('anchor', function (){\r\n\r\n    return {\r\n        bindEvents:{\r\n            'ready':function(){\r\n                utils.cssRule('anchor',\r\n                    '.anchorclass{background: url(\\''\r\n                        + this.options.themePath\r\n                        + this.options.theme +'/images/anchor.gif\\') no-repeat scroll left center transparent;cursor: auto;display: inline-block;height: 16px;width: 15px;}',\r\n                    this.document);\r\n            }\r\n        },\r\n       outputRule: function(root){\r\n           utils.each(root.getNodesByTagName('img'),function(a){\r\n               var val;\r\n               if(val = a.getAttr('anchorname')){\r\n                   a.tagName = 'a';\r\n                   a.setAttr({\r\n                       anchorname : '',\r\n                       name : val,\r\n                       'class' : ''\r\n                   })\r\n               }\r\n           })\r\n       },\r\n       inputRule:function(root){\r\n           utils.each(root.getNodesByTagName('a'),function(a){\r\n               var val;\r\n               if((val = a.getAttr('name')) && !a.getAttr('href')){\r\n                   a.tagName = 'img';\r\n                   a.setAttr({\r\n                       anchorname :a.getAttr('name'),\r\n                       'class' : 'anchorclass'\r\n                   });\r\n                   a.setAttr('name')\r\n\r\n               }\r\n           })\r\n\r\n       },\r\n       commands:{\r\n           /**\r\n            * 插入锚点\r\n            * @command anchor\r\n            * @method execCommand\r\n            * @param { String } cmd 命令字符串\r\n            * @param { String } name 锚点名称字符串\r\n            * @example\r\n            * ```javascript\r\n            * //editor 是编辑器实例\r\n            * editor.execCommand('anchor', 'anchor1');\r\n            * ```\r\n            */\r\n           'anchor':{\r\n               execCommand:function (cmd, name) {\r\n                   var range = this.selection.getRange(),img = range.getClosedNode();\r\n                   if (img && img.getAttribute('anchorname')) {\r\n                       if (name) {\r\n                           img.setAttribute('anchorname', name);\r\n                       } else {\r\n                           range.setStartBefore(img).setCursor();\r\n                           domUtils.remove(img);\r\n                       }\r\n                   } else {\r\n                       if (name) {\r\n                           //只在选区的开始插入\r\n                           var anchor = this.document.createElement('img');\r\n                           range.collapse(true);\r\n                           domUtils.setAttributes(anchor,{\r\n                               'anchorname':name,\r\n                               'class':'anchorclass'\r\n                           });\r\n                           range.insertNode(anchor).setStartAfter(anchor).setCursor(false,true);\r\n                       }\r\n                   }\r\n               }\r\n           }\r\n       }\r\n    }\r\n});\r\n\r\n\r\n// plugins/wordcount.js\r\n///import core\r\n///commands 字数统计\r\n///commandsName  WordCount,wordCount\r\n///commandsTitle  字数统计\r\n/*\r\n * Created by JetBrains WebStorm.\r\n * User: taoqili\r\n * Date: 11-9-7\r\n * Time: 下午8:18\r\n * To change this template use File | Settings | File Templates.\r\n */\r\n\r\nUE.plugins['wordcount'] = function(){\r\n    var me = this;\r\n    me.setOpt('wordCount',true);\r\n    me.addListener('contentchange',function(){\r\n        me.fireEvent('wordcount');\r\n    });\r\n    var timer;\r\n    me.addListener('ready',function(){\r\n        var me = this;\r\n        domUtils.on(me.body,\"keyup\",function(evt){\r\n            var code = evt.keyCode||evt.which,\r\n                //忽略的按键,ctr,alt,shift,方向键\r\n                ignores = {\"16\":1,\"18\":1,\"20\":1,\"37\":1,\"38\":1,\"39\":1,\"40\":1};\r\n            if(code in ignores) return;\r\n            clearTimeout(timer);\r\n            timer = setTimeout(function(){\r\n                me.fireEvent('wordcount');\r\n            },200)\r\n        })\r\n    });\r\n};\r\n\r\n\r\n// plugins/pagebreak.js\r\n/**\r\n * 分页功能插件\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\nUE.plugins['pagebreak'] = function () {\r\n    var me = this,\r\n        notBreakTags = ['td'];\r\n    me.setOpt('pageBreakTag','_ueditor_page_break_tag_');\r\n\r\n    function fillNode(node){\r\n        if(domUtils.isEmptyBlock(node)){\r\n            var firstChild = node.firstChild,tmpNode;\r\n\r\n            while(firstChild && firstChild.nodeType == 1 && domUtils.isEmptyBlock(firstChild)){\r\n                tmpNode = firstChild;\r\n                firstChild = firstChild.firstChild;\r\n            }\r\n            !tmpNode && (tmpNode = node);\r\n            domUtils.fillNode(me.document,tmpNode);\r\n        }\r\n    }\r\n    //分页符样式添加\r\n\r\n    me.ready(function(){\r\n        utils.cssRule('pagebreak','.pagebreak{display:block;clear:both !important;cursor:default !important;width: 100% !important;margin:0;}',me.document);\r\n    });\r\n    function isHr(node){\r\n        return node && node.nodeType == 1 && node.tagName == 'HR' && node.className == 'pagebreak';\r\n    }\r\n    me.addInputRule(function(root){\r\n        root.traversal(function(node){\r\n            if(node.type == 'text' && node.data == me.options.pageBreakTag){\r\n                var hr = UE.uNode.createElement('<hr class=\"pagebreak\" noshade=\"noshade\" size=\"5\" style=\"-webkit-user-select: none;\">');\r\n                node.parentNode.insertBefore(hr,node);\r\n                node.parentNode.removeChild(node)\r\n            }\r\n        })\r\n    });\r\n    me.addOutputRule(function(node){\r\n        utils.each(node.getNodesByTagName('hr'),function(n){\r\n            if(n.getAttr('class') == 'pagebreak'){\r\n                var txt = UE.uNode.createText(me.options.pageBreakTag);\r\n                n.parentNode.insertBefore(txt,n);\r\n                n.parentNode.removeChild(n);\r\n            }\r\n        })\r\n\r\n    });\r\n\r\n    /**\r\n     * 插入分页符\r\n     * @command pagebreak\r\n     * @method execCommand\r\n     * @param { String } cmd 命令字符串\r\n     * @remind 在表格中插入分页符会把表格切分成两部分\r\n     * @remind 获取编辑器内的数据时， 编辑器会把分页符转换成“_ueditor_page_break_tag_”字符串，\r\n     *          以便于提交数据到服务器端后处理分页。\r\n     * @example\r\n     * ```javascript\r\n     * editor.execCommand( 'pagebreak'); //插入一个hr标签，带有样式类名pagebreak\r\n     * ```\r\n     */\r\n\r\n    me.commands['pagebreak'] = {\r\n        execCommand:function () {\r\n            var range = me.selection.getRange(),hr = me.document.createElement('hr');\r\n            domUtils.setAttributes(hr,{\r\n                'class' : 'pagebreak',\r\n                noshade:\"noshade\",\r\n                size:\"5\"\r\n            });\r\n            domUtils.unSelectable(hr);\r\n            //table单独处理\r\n            var node = domUtils.findParentByTagName(range.startContainer, notBreakTags, true),\r\n\r\n                parents = [], pN;\r\n            if (node) {\r\n                switch (node.tagName) {\r\n                    case 'TD':\r\n                        pN = node.parentNode;\r\n                        if (!pN.previousSibling) {\r\n                            var table = domUtils.findParentByTagName(pN, 'table');\r\n//                            var tableWrapDiv = table.parentNode;\r\n//                            if(tableWrapDiv && tableWrapDiv.nodeType == 1\r\n//                                && tableWrapDiv.tagName == 'DIV'\r\n//                                && tableWrapDiv.getAttribute('dropdrag')\r\n//                                ){\r\n//                                domUtils.remove(tableWrapDiv,true);\r\n//                            }\r\n                            table.parentNode.insertBefore(hr, table);\r\n                            parents = domUtils.findParents(hr, true);\r\n\r\n                        } else {\r\n                            pN.parentNode.insertBefore(hr, pN);\r\n                            parents = domUtils.findParents(hr);\r\n\r\n                        }\r\n                        pN = parents[1];\r\n                        if (hr !== pN) {\r\n                            domUtils.breakParent(hr, pN);\r\n\r\n                        }\r\n                        //table要重写绑定一下拖拽\r\n                        me.fireEvent('afteradjusttable',me.document);\r\n                }\r\n\r\n            } else {\r\n\r\n                if (!range.collapsed) {\r\n                    range.deleteContents();\r\n                    var start = range.startContainer;\r\n                    while ( !domUtils.isBody(start) && domUtils.isBlockElm(start) && domUtils.isEmptyNode(start)) {\r\n                        range.setStartBefore(start).collapse(true);\r\n                        domUtils.remove(start);\r\n                        start = range.startContainer;\r\n                    }\r\n\r\n                }\r\n                range.insertNode(hr);\r\n\r\n                var pN = hr.parentNode, nextNode;\r\n                while (!domUtils.isBody(pN)) {\r\n                    domUtils.breakParent(hr, pN);\r\n                    nextNode = hr.nextSibling;\r\n                    if (nextNode && domUtils.isEmptyBlock(nextNode)) {\r\n                        domUtils.remove(nextNode);\r\n                    }\r\n                    pN = hr.parentNode;\r\n                }\r\n                nextNode = hr.nextSibling;\r\n                var pre = hr.previousSibling;\r\n                if(isHr(pre)){\r\n                    domUtils.remove(pre);\r\n                }else{\r\n                    pre && fillNode(pre);\r\n                }\r\n\r\n                if(!nextNode){\r\n                    var p = me.document.createElement('p');\r\n\r\n                    hr.parentNode.appendChild(p);\r\n                    domUtils.fillNode(me.document,p);\r\n                    range.setStart(p,0).collapse(true);\r\n                }else{\r\n                    if(isHr(nextNode)){\r\n                        domUtils.remove(nextNode);\r\n                    }else{\r\n                        fillNode(nextNode);\r\n                    }\r\n                    range.setEndAfter(hr).collapse(false);\r\n                }\r\n\r\n                range.select(true);\r\n\r\n            }\r\n\r\n        }\r\n    };\r\n};\r\n\r\n// plugins/wordimage.js\r\n///import core\r\n///commands 本地图片引导上传\r\n///commandsName  WordImage\r\n///commandsTitle  本地图片引导上传\r\n///commandsDialog  dialogs\\wordimage\r\n\r\nUE.plugin.register('wordimage',function(){\r\n    var me = this,\r\n        images = [];\r\n    return {\r\n        commands : {\r\n            'wordimage':{\r\n                execCommand:function () {\r\n                    var images = domUtils.getElementsByTagName(me.body, \"img\");\r\n                    var urlList = [];\r\n                    for (var i = 0, ci; ci = images[i++];) {\r\n                        var url = ci.getAttribute(\"word_img\");\r\n                        url && urlList.push(url);\r\n                    }\r\n                    return urlList;\r\n                },\r\n                queryCommandState:function () {\r\n                    images = domUtils.getElementsByTagName(me.body, \"img\");\r\n                    for (var i = 0, ci; ci = images[i++];) {\r\n                        if (ci.getAttribute(\"word_img\")) {\r\n                            return 1;\r\n                        }\r\n                    }\r\n                    return -1;\r\n                },\r\n                notNeedUndo:true\r\n            }\r\n        },\r\n        inputRule : function (root) {\r\n            utils.each(root.getNodesByTagName('img'), function (img) {\r\n                var attrs = img.attrs,\r\n                    flag = parseInt(attrs.width) < 128 || parseInt(attrs.height) < 43,\r\n                    opt = me.options,\r\n                    src = opt.UEDITOR_HOME_URL + 'themes/default/images/spacer.gif';\r\n                if (attrs['src'] && /^(?:(file:\\/+))/.test(attrs['src'])) {\r\n                    img.setAttr({\r\n                        width:attrs.width,\r\n                        height:attrs.height,\r\n                        alt:attrs.alt,\r\n                        word_img: attrs.src,\r\n                        src:src,\r\n                        'style':'background:url(' + ( flag ? opt.themePath + opt.theme + '/images/word.gif' : opt.langPath + opt.lang + '/images/localimage.png') + ') no-repeat center center;border:1px solid #ddd'\r\n                    })\r\n                }\r\n            })\r\n        }\r\n    }\r\n});\r\n\r\n// plugins/dragdrop.js\r\nUE.plugins['dragdrop'] = function (){\r\n\r\n    var me = this;\r\n    me.ready(function(){\r\n        domUtils.on(this.body,'dragend',function(){\r\n            var rng = me.selection.getRange();\r\n            var node = rng.getClosedNode()||me.selection.getStart();\r\n\r\n            if(node && node.tagName == 'IMG'){\r\n\r\n                var pre = node.previousSibling,next;\r\n                while(next = node.nextSibling){\r\n                    if(next.nodeType == 1 && next.tagName == 'SPAN' && !next.firstChild){\r\n                        domUtils.remove(next)\r\n                    }else{\r\n                        break;\r\n                    }\r\n                }\r\n\r\n\r\n                if((pre && pre.nodeType == 1 && !domUtils.isEmptyBlock(pre) || !pre) && (!next || next && !domUtils.isEmptyBlock(next))){\r\n                    if(pre && pre.tagName == 'P' && !domUtils.isEmptyBlock(pre)){\r\n                        pre.appendChild(node);\r\n                        domUtils.moveChild(next,pre);\r\n                        domUtils.remove(next);\r\n                    }else  if(next && next.tagName == 'P' && !domUtils.isEmptyBlock(next)){\r\n                        next.insertBefore(node,next.firstChild);\r\n                    }\r\n\r\n                    if(pre && pre.tagName == 'P' && domUtils.isEmptyBlock(pre)){\r\n                        domUtils.remove(pre)\r\n                    }\r\n                    if(next && next.tagName == 'P' && domUtils.isEmptyBlock(next)){\r\n                        domUtils.remove(next)\r\n                    }\r\n                    rng.selectNode(node).select();\r\n                    me.fireEvent('saveScene');\r\n\r\n                }\r\n\r\n            }\r\n\r\n        })\r\n    });\r\n    me.addListener('keyup', function(type, evt) {\r\n        var keyCode = evt.keyCode || evt.which;\r\n        if (keyCode == 13) {\r\n            var rng = me.selection.getRange(),node;\r\n            if(node = domUtils.findParentByTagName(rng.startContainer,'p',true)){\r\n                if(domUtils.getComputedStyle(node,'text-align') == 'center'){\r\n                    domUtils.removeStyle(node,'text-align')\r\n                }\r\n            }\r\n        }\r\n    })\r\n};\r\n\r\n\r\n// plugins/undo.js\r\n/**\r\n * undo redo\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 撤销上一次执行的命令\r\n * @command undo\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'undo' );\r\n * ```\r\n */\r\n\r\n/**\r\n * 重做上一次执行的命令\r\n * @command redo\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'redo' );\r\n * ```\r\n */\r\n\r\nUE.plugins['undo'] = function () {\r\n    var saveSceneTimer;\r\n    var me = this,\r\n        maxUndoCount = me.options.maxUndoCount || 20,\r\n        maxInputCount = me.options.maxInputCount || 20,\r\n        fillchar = new RegExp(domUtils.fillChar + '|<\\/hr>', 'gi');// ie会产生多余的</hr>\r\n    var noNeedFillCharTags = {\r\n        ol:1,ul:1,table:1,tbody:1,tr:1,body:1\r\n    };\r\n    var orgState = me.options.autoClearEmptyNode;\r\n    function compareAddr(indexA, indexB) {\r\n        if (indexA.length != indexB.length)\r\n            return 0;\r\n        for (var i = 0, l = indexA.length; i < l; i++) {\r\n            if (indexA[i] != indexB[i])\r\n                return 0\r\n        }\r\n        return 1;\r\n    }\r\n\r\n    function compareRangeAddress(rngAddrA, rngAddrB) {\r\n        if (rngAddrA.collapsed != rngAddrB.collapsed) {\r\n            return 0;\r\n        }\r\n        if (!compareAddr(rngAddrA.startAddress, rngAddrB.startAddress) || !compareAddr(rngAddrA.endAddress, rngAddrB.endAddress)) {\r\n            return 0;\r\n        }\r\n        return 1;\r\n    }\r\n\r\n    function UndoManager() {\r\n        this.list = [];\r\n        this.index = 0;\r\n        this.hasUndo = false;\r\n        this.hasRedo = false;\r\n        this.undo = function () {\r\n            if (this.hasUndo) {\r\n                if (!this.list[this.index - 1] && this.list.length == 1) {\r\n                    this.reset();\r\n                    return;\r\n                }\r\n                while (this.list[this.index].content == this.list[this.index - 1].content) {\r\n                    this.index--;\r\n                    if (this.index == 0) {\r\n                        return this.restore(0);\r\n                    }\r\n                }\r\n                this.restore(--this.index);\r\n            }\r\n        };\r\n        this.redo = function () {\r\n            if (this.hasRedo) {\r\n                while (this.list[this.index].content == this.list[this.index + 1].content) {\r\n                    this.index++;\r\n                    if (this.index == this.list.length - 1) {\r\n                        return this.restore(this.index);\r\n                    }\r\n                }\r\n                this.restore(++this.index);\r\n            }\r\n        };\r\n\r\n        this.restore = function () {\r\n            var me = this.editor;\r\n            var scene = this.list[this.index];\r\n            var root = UE.htmlparser(scene.content.replace(fillchar, ''));\r\n            me.options.autoClearEmptyNode = false;\r\n            me.filterInputRule(root);\r\n            me.options.autoClearEmptyNode = orgState;\r\n            //trace:873\r\n            //去掉展位符\r\n            me.document.body.innerHTML = root.toHtml();\r\n            me.fireEvent('afterscencerestore');\r\n            //处理undo后空格不展位的问题\r\n            if (browser.ie) {\r\n                utils.each(domUtils.getElementsByTagName(me.document,'td th caption p'),function(node){\r\n                    if(domUtils.isEmptyNode(node)){\r\n                        domUtils.fillNode(me.document, node);\r\n                    }\r\n                })\r\n            }\r\n\r\n            try{\r\n                var rng = new dom.Range(me.document).moveToAddress(scene.address);\r\n                rng.select(noNeedFillCharTags[rng.startContainer.nodeName.toLowerCase()]);\r\n            }catch(e){}\r\n\r\n            this.update();\r\n            this.clearKey();\r\n            //不能把自己reset了\r\n            me.fireEvent('reset', true);\r\n        };\r\n\r\n        this.getScene = function () {\r\n            var me = this.editor;\r\n            var rng = me.selection.getRange(),\r\n                rngAddress = rng.createAddress(false,true);\r\n            me.fireEvent('beforegetscene');\r\n            var root = UE.htmlparser(me.body.innerHTML);\r\n            me.options.autoClearEmptyNode = false;\r\n            me.filterOutputRule(root);\r\n            me.options.autoClearEmptyNode = orgState;\r\n            var cont = root.toHtml();\r\n            //trace:3461\r\n            //这个会引起回退时导致空格丢失的情况\r\n//            browser.ie && (cont = cont.replace(/>&nbsp;</g, '><').replace(/\\s*</g, '<').replace(/>\\s*/g, '>'));\r\n            me.fireEvent('aftergetscene');\r\n\r\n            return {\r\n                address:rngAddress,\r\n                content:cont\r\n            }\r\n        };\r\n        this.save = function (notCompareRange,notSetCursor) {\r\n            clearTimeout(saveSceneTimer);\r\n            var currentScene = this.getScene(notSetCursor),\r\n                lastScene = this.list[this.index];\r\n\r\n            if(lastScene && lastScene.content != currentScene.content){\r\n                me.trigger('contentchange')\r\n            }\r\n            //内容相同位置相同不存\r\n            if (lastScene && lastScene.content == currentScene.content &&\r\n                ( notCompareRange ? 1 : compareRangeAddress(lastScene.address, currentScene.address) )\r\n                ) {\r\n                return;\r\n            }\r\n            this.list = this.list.slice(0, this.index + 1);\r\n            this.list.push(currentScene);\r\n            //如果大于最大数量了，就把最前的剔除\r\n            if (this.list.length > maxUndoCount) {\r\n                this.list.shift();\r\n            }\r\n            this.index = this.list.length - 1;\r\n            this.clearKey();\r\n            //跟新undo/redo状态\r\n            this.update();\r\n\r\n        };\r\n        this.update = function () {\r\n            this.hasRedo = !!this.list[this.index + 1];\r\n            this.hasUndo = !!this.list[this.index - 1];\r\n        };\r\n        this.reset = function () {\r\n            this.list = [];\r\n            this.index = 0;\r\n            this.hasUndo = false;\r\n            this.hasRedo = false;\r\n            this.clearKey();\r\n        };\r\n        this.clearKey = function () {\r\n            keycont = 0;\r\n            lastKeyCode = null;\r\n        };\r\n    }\r\n\r\n    me.undoManger = new UndoManager();\r\n    me.undoManger.editor = me;\r\n    function saveScene() {\r\n        this.undoManger.save();\r\n    }\r\n\r\n    me.addListener('saveScene', function () {\r\n        var args = Array.prototype.splice.call(arguments,1);\r\n        this.undoManger.save.apply(this.undoManger,args);\r\n    });\r\n\r\n//    me.addListener('beforeexeccommand', saveScene);\r\n//    me.addListener('afterexeccommand', saveScene);\r\n\r\n    me.addListener('reset', function (type, exclude) {\r\n        if (!exclude) {\r\n            this.undoManger.reset();\r\n        }\r\n    });\r\n    me.commands['redo'] = me.commands['undo'] = {\r\n        execCommand:function (cmdName) {\r\n            this.undoManger[cmdName]();\r\n        },\r\n        queryCommandState:function (cmdName) {\r\n            return this.undoManger['has' + (cmdName.toLowerCase() == 'undo' ? 'Undo' : 'Redo')] ? 0 : -1;\r\n        },\r\n        notNeedUndo:1\r\n    };\r\n\r\n    var keys = {\r\n            //  /*Backspace*/ 8:1, /*Delete*/ 46:1,\r\n            /*Shift*/ 16:1, /*Ctrl*/ 17:1, /*Alt*/ 18:1,\r\n            37:1, 38:1, 39:1, 40:1\r\n\r\n        },\r\n        keycont = 0,\r\n        lastKeyCode;\r\n    //输入法状态下不计算字符数\r\n    var inputType = false;\r\n    me.addListener('ready', function () {\r\n        domUtils.on(this.body, 'compositionstart', function () {\r\n            inputType = true;\r\n        });\r\n        domUtils.on(this.body, 'compositionend', function () {\r\n            inputType = false;\r\n        })\r\n    });\r\n    //快捷键\r\n    me.addshortcutkey({\r\n        \"Undo\":\"ctrl+90\", //undo\r\n        \"Redo\":\"ctrl+89\" //redo\r\n\r\n    });\r\n    var isCollapsed = true;\r\n    me.addListener('keydown', function (type, evt) {\r\n\r\n        var me = this;\r\n        var keyCode = evt.keyCode || evt.which;\r\n        if (!keys[keyCode] && !evt.ctrlKey && !evt.metaKey && !evt.shiftKey && !evt.altKey) {\r\n            if (inputType)\r\n                return;\r\n\r\n            if(!me.selection.getRange().collapsed){\r\n                me.undoManger.save(false,true);\r\n                isCollapsed = false;\r\n                return;\r\n            }\r\n            if (me.undoManger.list.length == 0) {\r\n                me.undoManger.save(true);\r\n            }\r\n            clearTimeout(saveSceneTimer);\r\n            function save(cont){\r\n                cont.undoManger.save(false,true);\r\n                cont.fireEvent('selectionchange');\r\n            }\r\n            saveSceneTimer = setTimeout(function(){\r\n                if(inputType){\r\n                    var interalTimer = setInterval(function(){\r\n                        if(!inputType){\r\n                            save(me);\r\n                            clearInterval(interalTimer)\r\n                        }\r\n                    },300)\r\n                    return;\r\n                }\r\n                save(me);\r\n            },200);\r\n\r\n            lastKeyCode = keyCode;\r\n            keycont++;\r\n            if (keycont >= maxInputCount ) {\r\n                save(me)\r\n            }\r\n        }\r\n    });\r\n    me.addListener('keyup', function (type, evt) {\r\n        var keyCode = evt.keyCode || evt.which;\r\n        if (!keys[keyCode] && !evt.ctrlKey && !evt.metaKey && !evt.shiftKey && !evt.altKey) {\r\n            if (inputType)\r\n                return;\r\n            if(!isCollapsed){\r\n                this.undoManger.save(false,true);\r\n                isCollapsed = true;\r\n            }\r\n        }\r\n    });\r\n    //扩展实例，添加关闭和开启命令undo\r\n    me.stopCmdUndo = function(){\r\n        me.__hasEnterExecCommand = true;\r\n    };\r\n    me.startCmdUndo = function(){\r\n        me.__hasEnterExecCommand = false;\r\n    }\r\n};\r\n\r\n\r\n// plugins/copy.js\r\nUE.plugin.register('copy', function () {\r\n\r\n    var me = this;\r\n\r\n    function initZeroClipboard() {\r\n\r\n        ZeroClipboard.config({\r\n            debug: false,\r\n            swfPath: me.options.UEDITOR_HOME_URL + 'third-party/zeroclipboard/ZeroClipboard.swf'\r\n        });\r\n\r\n        var client = me.zeroclipboard = new ZeroClipboard();\r\n\r\n        // 复制内容\r\n        client.on('copy', function (e) {\r\n            var client = e.client,\r\n                rng = me.selection.getRange(),\r\n                div = document.createElement('div');\r\n\r\n            div.appendChild(rng.cloneContents());\r\n            client.setText(div.innerText || div.textContent);\r\n            client.setHtml(div.innerHTML);\r\n            rng.select();\r\n        });\r\n        // hover事件传递到target\r\n        client.on('mouseover mouseout', function (e) {\r\n            var target = e.target;\r\n            if (e.type == 'mouseover') {\r\n                domUtils.addClass(target, 'edui-state-hover');\r\n            } else if (e.type == 'mouseout') {\r\n                domUtils.removeClasses(target, 'edui-state-hover');\r\n            }\r\n        });\r\n        // flash加载不成功\r\n        client.on('wrongflash noflash', function () {\r\n            ZeroClipboard.destroy();\r\n        });\r\n    }\r\n\r\n    return {\r\n        bindEvents: {\r\n            'ready': function () {\r\n                if (!browser.ie) {\r\n                    if (window.ZeroClipboard) {\r\n                        initZeroClipboard();\r\n                    } else {\r\n                        utils.loadFile(document, {\r\n                            src: me.options.UEDITOR_HOME_URL + \"third-party/zeroclipboard/ZeroClipboard.js\",\r\n                            tag: \"script\",\r\n                            type: \"text/javascript\",\r\n                            defer: \"defer\"\r\n                        }, function () {\r\n                            initZeroClipboard();\r\n                        });\r\n                    }\r\n                }\r\n            }\r\n        },\r\n        commands: {\r\n            'copy': {\r\n                execCommand: function (cmd) {\r\n                    if (!me.document.execCommand('copy')) {\r\n                        alert(me.getLang('copymsg'));\r\n                    }\r\n                }\r\n            }\r\n        }\r\n    }\r\n});\r\n\r\n\r\n// plugins/paste.js\r\n///import core\r\n///import plugins/inserthtml.js\r\n///import plugins/undo.js\r\n///import plugins/serialize.js\r\n///commands 粘贴\r\n///commandsName  PastePlain\r\n///commandsTitle  纯文本粘贴模式\r\n/**\r\n * @description 粘贴\r\n * @author zhanyi\r\n */\r\nUE.plugins['paste'] = function () {\r\n    function getClipboardData(callback) {\r\n        var doc = this.document;\r\n        if (doc.getElementById('baidu_pastebin')) {\r\n            return;\r\n        }\r\n        var range = this.selection.getRange(),\r\n            bk = range.createBookmark(),\r\n        //创建剪贴的容器div\r\n            pastebin = doc.createElement('div');\r\n        pastebin.id = 'baidu_pastebin';\r\n        // Safari 要求div必须有内容，才能粘贴内容进来\r\n        browser.webkit && pastebin.appendChild(doc.createTextNode(domUtils.fillChar + domUtils.fillChar));\r\n        doc.body.appendChild(pastebin);\r\n        //trace:717 隐藏的span不能得到top\r\n        //bk.start.innerHTML = '&nbsp;';\r\n        bk.start.style.display = '';\r\n        pastebin.style.cssText = \"position:absolute;width:1px;height:1px;overflow:hidden;left:-1000px;white-space:nowrap;top:\" +\r\n            //要在现在光标平行的位置加入，否则会出现跳动的问题\r\n            domUtils.getXY(bk.start).y + 'px';\r\n\r\n        range.selectNodeContents(pastebin).select(true);\r\n\r\n        setTimeout(function () {\r\n            if (browser.webkit) {\r\n                for (var i = 0, pastebins = doc.querySelectorAll('#baidu_pastebin'), pi; pi = pastebins[i++];) {\r\n                    if (domUtils.isEmptyNode(pi)) {\r\n                        domUtils.remove(pi);\r\n                    } else {\r\n                        pastebin = pi;\r\n                        break;\r\n                    }\r\n                }\r\n            }\r\n            try {\r\n                pastebin.parentNode.removeChild(pastebin);\r\n            } catch (e) {\r\n            }\r\n            range.moveToBookmark(bk).select(true);\r\n            callback(pastebin);\r\n        }, 0);\r\n    }\r\n\r\n    var me = this;\r\n\r\n    me.setOpt({\r\n        retainOnlyLabelPasted : false\r\n    });\r\n\r\n    var txtContent, htmlContent, address;\r\n\r\n    function getPureHtml(html){\r\n        return html.replace(/<(\\/?)([\\w\\-]+)([^>]*)>/gi, function (a, b, tagName, attrs) {\r\n            tagName = tagName.toLowerCase();\r\n            if ({img: 1}[tagName]) {\r\n                return a;\r\n            }\r\n            attrs = attrs.replace(/([\\w\\-]*?)\\s*=\\s*((\"([^\"]*)\")|('([^']*)')|([^\\s>]+))/gi, function (str, atr, val) {\r\n                if ({\r\n                    'src': 1,\r\n                    'href': 1,\r\n                    'name': 1\r\n                }[atr.toLowerCase()]) {\r\n                    return atr + '=' + val + ' '\r\n                }\r\n                return ''\r\n            });\r\n            if ({\r\n                'span': 1,\r\n                'div': 1\r\n            }[tagName]) {\r\n                return ''\r\n            } else {\r\n\r\n                return '<' + b + tagName + ' ' + utils.trim(attrs) + '>'\r\n            }\r\n\r\n        });\r\n    }\r\n    function filter(div) {\r\n        var html;\r\n        if (div.firstChild) {\r\n            //去掉cut中添加的边界值\r\n            var nodes = domUtils.getElementsByTagName(div, 'span');\r\n            for (var i = 0, ni; ni = nodes[i++];) {\r\n                if (ni.id == '_baidu_cut_start' || ni.id == '_baidu_cut_end') {\r\n                    domUtils.remove(ni);\r\n                }\r\n            }\r\n\r\n            if (browser.webkit) {\r\n\r\n                var brs = div.querySelectorAll('div br');\r\n                for (var i = 0, bi; bi = brs[i++];) {\r\n                    var pN = bi.parentNode;\r\n                    if (pN.tagName == 'DIV' && pN.childNodes.length == 1) {\r\n                        pN.innerHTML = '<p><br/></p>';\r\n                        domUtils.remove(pN);\r\n                    }\r\n                }\r\n                var divs = div.querySelectorAll('#baidu_pastebin');\r\n                for (var i = 0, di; di = divs[i++];) {\r\n                    var tmpP = me.document.createElement('p');\r\n                    di.parentNode.insertBefore(tmpP, di);\r\n                    while (di.firstChild) {\r\n                        tmpP.appendChild(di.firstChild);\r\n                    }\r\n                    domUtils.remove(di);\r\n                }\r\n\r\n                var metas = div.querySelectorAll('meta');\r\n                for (var i = 0, ci; ci = metas[i++];) {\r\n                    domUtils.remove(ci);\r\n                }\r\n\r\n                var brs = div.querySelectorAll('br');\r\n                for (i = 0; ci = brs[i++];) {\r\n                    if (/^apple-/i.test(ci.className)) {\r\n                        domUtils.remove(ci);\r\n                    }\r\n                }\r\n            }\r\n            if (browser.gecko) {\r\n                var dirtyNodes = div.querySelectorAll('[_moz_dirty]');\r\n                for (i = 0; ci = dirtyNodes[i++];) {\r\n                    ci.removeAttribute('_moz_dirty');\r\n                }\r\n            }\r\n            if (!browser.ie) {\r\n                var spans = div.querySelectorAll('span.Apple-style-span');\r\n                for (var i = 0, ci; ci = spans[i++];) {\r\n                    domUtils.remove(ci, true);\r\n                }\r\n            }\r\n\r\n            //ie下使用innerHTML会产生多余的\\r\\n字符，也会产生&nbsp;这里过滤掉\r\n            html = div.innerHTML;//.replace(/>(?:(\\s|&nbsp;)*?)</g,'><');\r\n\r\n            //过滤word粘贴过来的冗余属性\r\n            html = UE.filterWord(html);\r\n            //取消了忽略空白的第二个参数，粘贴过来的有些是有空白的，会被套上相关的标签\r\n            var root = UE.htmlparser(html);\r\n            //如果给了过滤规则就先进行过滤\r\n            if (me.options.filterRules) {\r\n                UE.filterNode(root, me.options.filterRules);\r\n            }\r\n            //执行默认的处理\r\n            me.filterInputRule(root);\r\n            //针对chrome的处理\r\n            if (browser.webkit) {\r\n                var br = root.lastChild();\r\n                if (br && br.type == 'element' && br.tagName == 'br') {\r\n                    root.removeChild(br)\r\n                }\r\n                utils.each(me.body.querySelectorAll('div'), function (node) {\r\n                    if (domUtils.isEmptyBlock(node)) {\r\n                        domUtils.remove(node,true)\r\n                    }\r\n                })\r\n            }\r\n            html = {'html': root.toHtml()};\r\n            me.fireEvent('beforepaste', html, root);\r\n            //抢了默认的粘贴，那后边的内容就不执行了，比如表格粘贴\r\n            if(!html.html){\r\n                return;\r\n            }\r\n            root = UE.htmlparser(html.html,true);\r\n            //如果开启了纯文本模式\r\n            if (me.queryCommandState('pasteplain') === 1) {\r\n                me.execCommand('insertHtml', UE.filterNode(root, me.options.filterTxtRules).toHtml(), true);\r\n            } else {\r\n                //文本模式\r\n                UE.filterNode(root, me.options.filterTxtRules);\r\n                txtContent = root.toHtml();\r\n                //完全模式\r\n                htmlContent = html.html;\r\n\r\n                address = me.selection.getRange().createAddress(true);\r\n                me.execCommand('insertHtml', me.getOpt('retainOnlyLabelPasted') === true ?  getPureHtml(htmlContent) : htmlContent, true);\r\n            }\r\n            me.fireEvent(\"afterpaste\", html);\r\n        }\r\n    }\r\n\r\n    me.addListener('pasteTransfer', function (cmd, plainType) {\r\n\r\n        if (address && txtContent && htmlContent && txtContent != htmlContent) {\r\n            var range = me.selection.getRange();\r\n            range.moveToAddress(address, true);\r\n\r\n            if (!range.collapsed) {\r\n\r\n                while (!domUtils.isBody(range.startContainer)\r\n                    ) {\r\n                    var start = range.startContainer;\r\n                    if(start.nodeType == 1){\r\n                        start = start.childNodes[range.startOffset];\r\n                        if(!start){\r\n                            range.setStartBefore(range.startContainer);\r\n                            continue;\r\n                        }\r\n                        var pre = start.previousSibling;\r\n\r\n                        if(pre && pre.nodeType == 3 && new RegExp('^[\\n\\r\\t '+domUtils.fillChar+']*$').test(pre.nodeValue)){\r\n                            range.setStartBefore(pre)\r\n                        }\r\n                    }\r\n                    if(range.startOffset == 0){\r\n                        range.setStartBefore(range.startContainer);\r\n                    }else{\r\n                        break;\r\n                    }\r\n\r\n                }\r\n                while (!domUtils.isBody(range.endContainer)\r\n                    ) {\r\n                    var end = range.endContainer;\r\n                    if(end.nodeType == 1){\r\n                        end = end.childNodes[range.endOffset];\r\n                        if(!end){\r\n                            range.setEndAfter(range.endContainer);\r\n                            continue;\r\n                        }\r\n                        var next = end.nextSibling;\r\n                        if(next && next.nodeType == 3 && new RegExp('^[\\n\\r\\t'+domUtils.fillChar+']*$').test(next.nodeValue)){\r\n                            range.setEndAfter(next)\r\n                        }\r\n                    }\r\n                    if(range.endOffset == range.endContainer[range.endContainer.nodeType == 3 ? 'nodeValue' : 'childNodes'].length){\r\n                        range.setEndAfter(range.endContainer);\r\n                    }else{\r\n                        break;\r\n                    }\r\n\r\n                }\r\n\r\n            }\r\n\r\n            range.deleteContents();\r\n            range.select(true);\r\n            me.__hasEnterExecCommand = true;\r\n            var html = htmlContent;\r\n            if (plainType === 2 ) {\r\n                html = getPureHtml(html);\r\n            } else if (plainType) {\r\n                html = txtContent;\r\n            }\r\n            me.execCommand('inserthtml', html, true);\r\n            me.__hasEnterExecCommand = false;\r\n            var rng = me.selection.getRange();\r\n            while (!domUtils.isBody(rng.startContainer) && !rng.startOffset &&\r\n                rng.startContainer[rng.startContainer.nodeType == 3 ? 'nodeValue' : 'childNodes'].length\r\n                ) {\r\n                rng.setStartBefore(rng.startContainer);\r\n            }\r\n            var tmpAddress = rng.createAddress(true);\r\n            address.endAddress = tmpAddress.startAddress;\r\n        }\r\n    });\r\n\r\n    me.addListener('ready', function () {\r\n        domUtils.on(me.body, 'cut', function () {\r\n            var range = me.selection.getRange();\r\n            if (!range.collapsed && me.undoManger) {\r\n                me.undoManger.save();\r\n            }\r\n        });\r\n\r\n        //ie下beforepaste在点击右键时也会触发，所以用监控键盘才处理\r\n        domUtils.on(me.body, browser.ie || browser.opera ? 'keydown' : 'paste', function (e) {\r\n            if ((browser.ie || browser.opera) && ((!e.ctrlKey && !e.metaKey) || e.keyCode != '86')) {\r\n                return;\r\n            }\r\n            getClipboardData.call(me, function (div) {\r\n                filter(div);\r\n            });\r\n        });\r\n\r\n    });\r\n\r\n    me.commands['paste'] = {\r\n        execCommand: function (cmd) {\r\n            if (browser.ie) {\r\n                getClipboardData.call(me, function (div) {\r\n                    filter(div);\r\n                });\r\n                me.document.execCommand('paste');\r\n            } else {\r\n                alert(me.getLang('pastemsg'));\r\n            }\r\n        }\r\n    }\r\n};\r\n\r\n\r\n\r\n// plugins/puretxtpaste.js\r\n/**\r\n * 纯文本粘贴插件\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\nUE.plugins['pasteplain'] = function(){\r\n    var me = this;\r\n    me.setOpt({\r\n        'pasteplain':false,\r\n        'filterTxtRules' : function(){\r\n            function transP(node){\r\n                node.tagName = 'p';\r\n                node.setStyle();\r\n            }\r\n            function removeNode(node){\r\n                node.parentNode.removeChild(node,true)\r\n            }\r\n            return {\r\n                //直接删除及其字节点内容\r\n                '-' : 'script style object iframe embed input select',\r\n                'p': {$:{}},\r\n                'br':{$:{}},\r\n                div: function (node) {\r\n                    var tmpNode, p = UE.uNode.createElement('p');\r\n                    while (tmpNode = node.firstChild()) {\r\n                        if (tmpNode.type == 'text' || !UE.dom.dtd.$block[tmpNode.tagName]) {\r\n                            p.appendChild(tmpNode);\r\n                        } else {\r\n                            if (p.firstChild()) {\r\n                                node.parentNode.insertBefore(p, node);\r\n                                p = UE.uNode.createElement('p');\r\n                            } else {\r\n                                node.parentNode.insertBefore(tmpNode, node);\r\n                            }\r\n                        }\r\n                    }\r\n                    if (p.firstChild()) {\r\n                        node.parentNode.insertBefore(p, node);\r\n                    }\r\n                    node.parentNode.removeChild(node);\r\n                },\r\n                ol: removeNode,\r\n                ul: removeNode,\r\n                dl:removeNode,\r\n                dt:removeNode,\r\n                dd:removeNode,\r\n                'li':removeNode,\r\n                'caption':transP,\r\n                'th':transP,\r\n                'tr':transP,\r\n                'h1':transP,'h2':transP,'h3':transP,'h4':transP,'h5':transP,'h6':transP,\r\n                'td':function(node){\r\n                        //没有内容的td直接删掉\r\n                        var txt = !!node.innerText();\r\n                        if(txt){\r\n                         node.parentNode.insertAfter(UE.uNode.createText(' &nbsp; &nbsp;'),node);\r\n                    }\r\n                    node.parentNode.removeChild(node,node.innerText())\r\n                }\r\n            }\r\n        }()\r\n    });\r\n    //暂时这里支持一下老版本的属性\r\n    var pasteplain = me.options.pasteplain;\r\n\r\n    /**\r\n     * 启用或取消纯文本粘贴模式\r\n     * @command pasteplain\r\n     * @method execCommand\r\n     * @param { String } cmd 命令字符串\r\n     * @example\r\n     * ```javascript\r\n     * editor.queryCommandState( 'pasteplain' );\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 查询当前是否处于纯文本粘贴模式\r\n     * @command pasteplain\r\n     * @method queryCommandState\r\n     * @param { String } cmd 命令字符串\r\n     * @return { int } 如果处于纯文本模式，返回1，否则，返回0\r\n     * @example\r\n     * ```javascript\r\n     * editor.queryCommandState( 'pasteplain' );\r\n     * ```\r\n     */\r\n    me.commands['pasteplain'] = {\r\n        queryCommandState: function (){\r\n            return pasteplain ? 1 : 0;\r\n        },\r\n        execCommand: function (){\r\n            pasteplain = !pasteplain|0;\r\n        },\r\n        notNeedUndo : 1\r\n    };\r\n};\r\n\r\n// plugins/list.js\r\n/**\r\n * 有序列表,无序列表插件\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\nUE.plugins['list'] = function () {\r\n    var me = this,\r\n        notExchange = {\r\n            'TD':1,\r\n            'PRE':1,\r\n            'BLOCKQUOTE':1\r\n        };\r\n    var customStyle = {\r\n        'cn' : 'cn-1-',\r\n        'cn1' : 'cn-2-',\r\n        'cn2' : 'cn-3-',\r\n        'num':  'num-1-',\r\n        'num1' : 'num-2-',\r\n        'num2' : 'num-3-',\r\n        'dash'  : 'dash',\r\n        'dot':'dot'\r\n    };\r\n\r\n    me.setOpt( {\r\n        'autoTransWordToList':false,\r\n        'insertorderedlist':{\r\n            'num':'',\r\n            'num1':'',\r\n            'num2':'',\r\n            'cn':'',\r\n            'cn1':'',\r\n            'cn2':'',\r\n            'decimal':'',\r\n            'lower-alpha':'',\r\n            'lower-roman':'',\r\n            'upper-alpha':'',\r\n            'upper-roman':''\r\n        },\r\n        'insertunorderedlist':{\r\n            'circle':'',\r\n            'disc':'',\r\n            'square':'',\r\n            'dash' : '',\r\n            'dot':''\r\n        },\r\n        listDefaultPaddingLeft : '30',\r\n        listiconpath : 'http://bs.baidu.com/listicon/',\r\n        maxListLevel : -1,//-1不限制\r\n        disablePInList:false\r\n    } );\r\n    function listToArray(list){\r\n        var arr = [];\r\n        for(var p in list){\r\n            arr.push(p)\r\n        }\r\n        return arr;\r\n    }\r\n    var listStyle = {\r\n        'OL':listToArray(me.options.insertorderedlist),\r\n        'UL':listToArray(me.options.insertunorderedlist)\r\n    };\r\n    var liiconpath = me.options.listiconpath;\r\n\r\n    //根据用户配置，调整customStyle\r\n    for(var s in customStyle){\r\n        if(!me.options.insertorderedlist.hasOwnProperty(s) && !me.options.insertunorderedlist.hasOwnProperty(s)){\r\n            delete customStyle[s];\r\n        }\r\n    }\r\n\r\n    me.ready(function () {\r\n        var customCss = [];\r\n        for(var p in customStyle){\r\n            if(p == 'dash' || p == 'dot'){\r\n                customCss.push('li.list-' + customStyle[p] + '{background-image:url(' + liiconpath +customStyle[p]+'.gif)}');\r\n                customCss.push('ul.custom_'+p+'{list-style:none;}ul.custom_'+p+' li{background-position:0 3px;background-repeat:no-repeat}');\r\n            }else{\r\n                for(var i= 0;i<99;i++){\r\n                    customCss.push('li.list-' + customStyle[p] + i + '{background-image:url(' + liiconpath + 'list-'+customStyle[p] + i + '.gif)}')\r\n                }\r\n                customCss.push('ol.custom_'+p+'{list-style:none;}ol.custom_'+p+' li{background-position:0 3px;background-repeat:no-repeat}');\r\n            }\r\n            switch(p){\r\n                case 'cn':\r\n                    customCss.push('li.list-'+p+'-paddingleft-1{padding-left:25px}');\r\n                    customCss.push('li.list-'+p+'-paddingleft-2{padding-left:40px}');\r\n                    customCss.push('li.list-'+p+'-paddingleft-3{padding-left:55px}');\r\n                    break;\r\n                case 'cn1':\r\n                    customCss.push('li.list-'+p+'-paddingleft-1{padding-left:30px}');\r\n                    customCss.push('li.list-'+p+'-paddingleft-2{padding-left:40px}');\r\n                    customCss.push('li.list-'+p+'-paddingleft-3{padding-left:55px}');\r\n                    break;\r\n                case 'cn2':\r\n                    customCss.push('li.list-'+p+'-paddingleft-1{padding-left:40px}');\r\n                    customCss.push('li.list-'+p+'-paddingleft-2{padding-left:55px}');\r\n                    customCss.push('li.list-'+p+'-paddingleft-3{padding-left:68px}');\r\n                    break;\r\n                case 'num':\r\n                case 'num1':\r\n                    customCss.push('li.list-'+p+'-paddingleft-1{padding-left:25px}');\r\n                    break;\r\n                case 'num2':\r\n                    customCss.push('li.list-'+p+'-paddingleft-1{padding-left:35px}');\r\n                    customCss.push('li.list-'+p+'-paddingleft-2{padding-left:40px}');\r\n                    break;\r\n                case 'dash':\r\n                    customCss.push('li.list-'+p+'-paddingleft{padding-left:35px}');\r\n                    break;\r\n                case 'dot':\r\n                    customCss.push('li.list-'+p+'-paddingleft{padding-left:20px}');\r\n            }\r\n        }\r\n        customCss.push('.list-paddingleft-1{padding-left:0}');\r\n        customCss.push('.list-paddingleft-2{padding-left:'+me.options.listDefaultPaddingLeft+'px}');\r\n        customCss.push('.list-paddingleft-3{padding-left:'+me.options.listDefaultPaddingLeft*2+'px}');\r\n        //如果不给宽度会在自定应样式里出现滚动条\r\n        utils.cssRule('list', 'ol,ul{margin:0;pading:0;'+(browser.ie ? '' : 'width:95%')+'}li{clear:both;}'+customCss.join('\\n'), me.document);\r\n    });\r\n    //单独处理剪切的问题\r\n    me.ready(function(){\r\n        domUtils.on(me.body,'cut',function(){\r\n            setTimeout(function(){\r\n                var rng = me.selection.getRange(),li;\r\n                //trace:3416\r\n                if(!rng.collapsed){\r\n                    if(li = domUtils.findParentByTagName(rng.startContainer,'li',true)){\r\n                        if(!li.nextSibling && domUtils.isEmptyBlock(li)){\r\n                            var pn = li.parentNode,node;\r\n                            if(node = pn.previousSibling){\r\n                                domUtils.remove(pn);\r\n                                rng.setStartAtLast(node).collapse(true);\r\n                                rng.select(true);\r\n                            }else if(node = pn.nextSibling){\r\n                                domUtils.remove(pn);\r\n                                rng.setStartAtFirst(node).collapse(true);\r\n                                rng.select(true);\r\n                            }else{\r\n                                var tmpNode = me.document.createElement('p');\r\n                                domUtils.fillNode(me.document,tmpNode);\r\n                                pn.parentNode.insertBefore(tmpNode,pn);\r\n                                domUtils.remove(pn);\r\n                                rng.setStart(tmpNode,0).collapse(true);\r\n                                rng.select(true);\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n\r\n            })\r\n        })\r\n    });\r\n\r\n    function getStyle(node){\r\n        var cls = node.className;\r\n        if(domUtils.hasClass(node,/custom_/)){\r\n            return cls.match(/custom_(\\w+)/)[1]\r\n        }\r\n        return domUtils.getStyle(node, 'list-style-type')\r\n\r\n    }\r\n\r\n    me.addListener('beforepaste',function(type,html){\r\n        var me = this,\r\n            rng = me.selection.getRange(),li;\r\n        var root = UE.htmlparser(html.html,true);\r\n        if(li = domUtils.findParentByTagName(rng.startContainer,'li',true)){\r\n            var list = li.parentNode,tagName = list.tagName == 'OL' ? 'ul':'ol';\r\n            utils.each(root.getNodesByTagName(tagName),function(n){\r\n                n.tagName = list.tagName;\r\n                n.setAttr();\r\n                if(n.parentNode === root){\r\n                    type = getStyle(list) || (list.tagName == 'OL' ? 'decimal' : 'disc')\r\n                }else{\r\n                    var className = n.parentNode.getAttr('class');\r\n                    if(className && /custom_/.test(className)){\r\n                        type = className.match(/custom_(\\w+)/)[1]\r\n                    }else{\r\n                        type = n.parentNode.getStyle('list-style-type');\r\n                    }\r\n                    if(!type){\r\n                        type = list.tagName == 'OL' ? 'decimal' : 'disc';\r\n                    }\r\n                }\r\n                var index = utils.indexOf(listStyle[list.tagName], type);\r\n                if(n.parentNode !== root)\r\n                    index = index + 1 == listStyle[list.tagName].length ? 0 : index + 1;\r\n                var currentStyle = listStyle[list.tagName][index];\r\n                if(customStyle[currentStyle]){\r\n                    n.setAttr('class', 'custom_' + currentStyle)\r\n\r\n                }else{\r\n                    n.setStyle('list-style-type',currentStyle)\r\n                }\r\n            })\r\n\r\n        }\r\n\r\n        html.html = root.toHtml();\r\n    });\r\n    //导出时，去掉p标签\r\n    me.getOpt('disablePInList') === true && me.addOutputRule(function(root){\r\n        utils.each(root.getNodesByTagName('li'),function(li){\r\n            var newChildrens = [],index=0;\r\n            utils.each(li.children,function(n){\r\n                if(n.tagName == 'p'){\r\n                    var tmpNode;\r\n                    while(tmpNode = n.children.pop()) {\r\n                        newChildrens.splice(index,0,tmpNode);\r\n                        tmpNode.parentNode = li;\r\n                        lastNode = tmpNode;\r\n                    }\r\n                    tmpNode = newChildrens[newChildrens.length-1];\r\n                    if(!tmpNode || tmpNode.type != 'element' || tmpNode.tagName != 'br'){\r\n                        var br = UE.uNode.createElement('br');\r\n                        br.parentNode = li;\r\n                        newChildrens.push(br);\r\n                    }\r\n\r\n                    index = newChildrens.length;\r\n                }\r\n            });\r\n            if(newChildrens.length){\r\n                li.children = newChildrens;\r\n            }\r\n        });\r\n    });\r\n    //进入编辑器的li要套p标签\r\n    me.addInputRule(function(root){\r\n        utils.each(root.getNodesByTagName('li'),function(li){\r\n            var tmpP = UE.uNode.createElement('p');\r\n            for(var i= 0,ci;ci=li.children[i];){\r\n                if(ci.type == 'text' || dtd.p[ci.tagName]){\r\n                    tmpP.appendChild(ci);\r\n                }else{\r\n                    if(tmpP.firstChild()){\r\n                        li.insertBefore(tmpP,ci);\r\n                        tmpP = UE.uNode.createElement('p');\r\n                        i = i + 2;\r\n                    }else{\r\n                        i++;\r\n                    }\r\n\r\n                }\r\n            }\r\n            if(tmpP.firstChild() && !tmpP.parentNode || !li.firstChild()){\r\n                li.appendChild(tmpP);\r\n            }\r\n            //trace:3357\r\n            //p不能为空\r\n            if (!tmpP.firstChild()) {\r\n                tmpP.innerHTML(browser.ie ? '&nbsp;' : '<br/>')\r\n            }\r\n            //去掉末尾的空白\r\n            var p = li.firstChild();\r\n            var lastChild = p.lastChild();\r\n            if(lastChild && lastChild.type == 'text' && /^\\s*$/.test(lastChild.data)){\r\n                p.removeChild(lastChild)\r\n            }\r\n        });\r\n        if(me.options.autoTransWordToList){\r\n            var orderlisttype = {\r\n                    'num1':/^\\d+\\)/,\r\n                    'decimal':/^\\d+\\./,\r\n                    'lower-alpha':/^[a-z]+\\)/,\r\n                    'upper-alpha':/^[A-Z]+\\./,\r\n                    'cn':/^[\\u4E00\\u4E8C\\u4E09\\u56DB\\u516d\\u4e94\\u4e03\\u516b\\u4e5d]+[\\u3001]/,\r\n                    'cn2':/^\\([\\u4E00\\u4E8C\\u4E09\\u56DB\\u516d\\u4e94\\u4e03\\u516b\\u4e5d]+\\)/\r\n                },\r\n                unorderlisttype = {\r\n                    'square':'n'\r\n                };\r\n            function checkListType(content,container){\r\n                var span = container.firstChild();\r\n                if(span &&  span.type == 'element' && span.tagName == 'span' && /Wingdings|Symbol/.test(span.getStyle('font-family'))){\r\n                    for(var p in unorderlisttype){\r\n                        if(unorderlisttype[p] == span.data){\r\n                            return p\r\n                        }\r\n                    }\r\n                    return 'disc'\r\n                }\r\n                for(var p in orderlisttype){\r\n                    if(orderlisttype[p].test(content)){\r\n                        return p;\r\n                    }\r\n                }\r\n\r\n            }\r\n            utils.each(root.getNodesByTagName('p'),function(node){\r\n                if(node.getAttr('class') != 'MsoListParagraph'){\r\n                    return\r\n                }\r\n\r\n                //word粘贴过来的会带有margin要去掉,但这样也可能会误命中一些央视\r\n                node.setStyle('margin','');\r\n                node.setStyle('margin-left','');\r\n                node.setAttr('class','');\r\n\r\n                function appendLi(list,p,type){\r\n                    if(list.tagName == 'ol'){\r\n                        if(browser.ie){\r\n                            var first = p.firstChild();\r\n                            if(first.type =='element' && first.tagName == 'span' && orderlisttype[type].test(first.innerText())){\r\n                                p.removeChild(first);\r\n                            }\r\n                        }else{\r\n                            p.innerHTML(p.innerHTML().replace(orderlisttype[type],''));\r\n                        }\r\n                    }else{\r\n                        p.removeChild(p.firstChild())\r\n                    }\r\n\r\n                    var li = UE.uNode.createElement('li');\r\n                    li.appendChild(p);\r\n                    list.appendChild(li);\r\n                }\r\n                var tmp = node,type,cacheNode = node;\r\n\r\n                if(node.parentNode.tagName != 'li' && (type = checkListType(node.innerText(),node))){\r\n\r\n                    var list = UE.uNode.createElement(me.options.insertorderedlist.hasOwnProperty(type) ? 'ol' : 'ul');\r\n                    if(customStyle[type]){\r\n                        list.setAttr('class','custom_'+type)\r\n                    }else{\r\n                        list.setStyle('list-style-type',type)\r\n                    }\r\n                    while(node && node.parentNode.tagName != 'li' && checkListType(node.innerText(),node)){\r\n                        tmp = node.nextSibling();\r\n                        if(!tmp){\r\n                            node.parentNode.insertBefore(list,node)\r\n                        }\r\n                        appendLi(list,node,type);\r\n                        node = tmp;\r\n                    }\r\n                    if(!list.parentNode && node && node.parentNode){\r\n                        node.parentNode.insertBefore(list,node)\r\n                    }\r\n                }\r\n                var span = cacheNode.firstChild();\r\n                if(span && span.type == 'element' && span.tagName == 'span' && /^\\s*(&nbsp;)+\\s*$/.test(span.innerText())){\r\n                    span.parentNode.removeChild(span)\r\n                }\r\n            })\r\n        }\r\n\r\n    });\r\n\r\n    //调整索引标签\r\n    me.addListener('contentchange',function(){\r\n        adjustListStyle(me.document)\r\n    });\r\n\r\n    function adjustListStyle(doc,ignore){\r\n        utils.each(domUtils.getElementsByTagName(doc,'ol ul'),function(node){\r\n\r\n            if(!domUtils.inDoc(node,doc))\r\n                return;\r\n\r\n            var parent = node.parentNode;\r\n            if(parent.tagName == node.tagName){\r\n                var nodeStyleType = getStyle(node) || (node.tagName == 'OL' ? 'decimal' : 'disc'),\r\n                    parentStyleType = getStyle(parent) || (parent.tagName == 'OL' ? 'decimal' : 'disc');\r\n                if(nodeStyleType == parentStyleType){\r\n                    var styleIndex = utils.indexOf(listStyle[node.tagName], nodeStyleType);\r\n                    styleIndex = styleIndex + 1 == listStyle[node.tagName].length ? 0 : styleIndex + 1;\r\n                    setListStyle(node,listStyle[node.tagName][styleIndex])\r\n                }\r\n\r\n            }\r\n            var index = 0,type = 2;\r\n            if( domUtils.hasClass(node,/custom_/)){\r\n                if(!(/[ou]l/i.test(parent.tagName) && domUtils.hasClass(parent,/custom_/))){\r\n                    type = 1;\r\n                }\r\n            }else{\r\n                if(/[ou]l/i.test(parent.tagName) && domUtils.hasClass(parent,/custom_/)){\r\n                    type = 3;\r\n                }\r\n            }\r\n\r\n            var style = domUtils.getStyle(node, 'list-style-type');\r\n            style && (node.style.cssText = 'list-style-type:' + style);\r\n            node.className = utils.trim(node.className.replace(/list-paddingleft-\\w+/,'')) + ' list-paddingleft-' + type;\r\n            utils.each(domUtils.getElementsByTagName(node,'li'),function(li){\r\n                li.style.cssText && (li.style.cssText = '');\r\n                if(!li.firstChild){\r\n                    domUtils.remove(li);\r\n                    return;\r\n                }\r\n                if(li.parentNode !== node){\r\n                    return;\r\n                }\r\n                index++;\r\n                if(domUtils.hasClass(node,/custom_/) ){\r\n                    var paddingLeft = 1,currentStyle = getStyle(node);\r\n                    if(node.tagName == 'OL'){\r\n                        if(currentStyle){\r\n                            switch(currentStyle){\r\n                                case 'cn' :\r\n                                case 'cn1':\r\n                                case 'cn2':\r\n                                    if(index > 10 && (index % 10 == 0 || index > 10 && index < 20)){\r\n                                        paddingLeft = 2\r\n                                    }else if(index > 20){\r\n                                        paddingLeft = 3\r\n                                    }\r\n                                    break;\r\n                                case 'num2' :\r\n                                    if(index > 9){\r\n                                        paddingLeft = 2\r\n                                    }\r\n                            }\r\n                        }\r\n                        li.className = 'list-'+customStyle[currentStyle]+ index + ' ' + 'list-'+currentStyle+'-paddingleft-' + paddingLeft;\r\n                    }else{\r\n                        li.className = 'list-'+customStyle[currentStyle]  + ' ' + 'list-'+currentStyle+'-paddingleft';\r\n                    }\r\n                }else{\r\n                    li.className = li.className.replace(/list-[\\w\\-]+/gi,'');\r\n                }\r\n                var className = li.getAttribute('class');\r\n                if(className !== null && !className.replace(/\\s/g,'')){\r\n                    domUtils.removeAttributes(li,'class')\r\n                }\r\n            });\r\n            !ignore && adjustList(node,node.tagName.toLowerCase(),getStyle(node)||domUtils.getStyle(node, 'list-style-type'),true);\r\n        })\r\n    }\r\n    function adjustList(list, tag, style,ignoreEmpty) {\r\n        var nextList = list.nextSibling;\r\n        if (nextList && nextList.nodeType == 1 && nextList.tagName.toLowerCase() == tag && (getStyle(nextList) || domUtils.getStyle(nextList, 'list-style-type') || (tag == 'ol' ? 'decimal' : 'disc')) == style) {\r\n            domUtils.moveChild(nextList, list);\r\n            if (nextList.childNodes.length == 0) {\r\n                domUtils.remove(nextList);\r\n            }\r\n        }\r\n        if(nextList && domUtils.isFillChar(nextList)){\r\n            domUtils.remove(nextList);\r\n        }\r\n        var preList = list.previousSibling;\r\n        if (preList && preList.nodeType == 1 && preList.tagName.toLowerCase() == tag && (getStyle(preList) || domUtils.getStyle(preList, 'list-style-type') || (tag == 'ol' ? 'decimal' : 'disc')) == style) {\r\n            domUtils.moveChild(list, preList);\r\n        }\r\n        if(preList && domUtils.isFillChar(preList)){\r\n            domUtils.remove(preList);\r\n        }\r\n        !ignoreEmpty && domUtils.isEmptyBlock(list) && domUtils.remove(list);\r\n        if(getStyle(list)){\r\n            adjustListStyle(list.ownerDocument,true)\r\n        }\r\n    }\r\n\r\n    function setListStyle(list,style){\r\n        if(customStyle[style]){\r\n            list.className = 'custom_' + style;\r\n        }\r\n        try{\r\n            domUtils.setStyle(list, 'list-style-type', style);\r\n        }catch(e){}\r\n    }\r\n    function clearEmptySibling(node) {\r\n        var tmpNode = node.previousSibling;\r\n        if (tmpNode && domUtils.isEmptyBlock(tmpNode)) {\r\n            domUtils.remove(tmpNode);\r\n        }\r\n        tmpNode = node.nextSibling;\r\n        if (tmpNode && domUtils.isEmptyBlock(tmpNode)) {\r\n            domUtils.remove(tmpNode);\r\n        }\r\n    }\r\n\r\n    me.addListener('keydown', function (type, evt) {\r\n        function preventAndSave() {\r\n            evt.preventDefault ? evt.preventDefault() : (evt.returnValue = false);\r\n            me.fireEvent('contentchange');\r\n            me.undoManger && me.undoManger.save();\r\n        }\r\n        function findList(node,filterFn){\r\n            while(node && !domUtils.isBody(node)){\r\n                if(filterFn(node)){\r\n                    return null\r\n                }\r\n                if(node.nodeType == 1 && /[ou]l/i.test(node.tagName)){\r\n                    return node;\r\n                }\r\n                node = node.parentNode;\r\n            }\r\n            return null;\r\n        }\r\n        var keyCode = evt.keyCode || evt.which;\r\n        if (keyCode == 13 && !evt.shiftKey) {//回车\r\n            var rng = me.selection.getRange(),\r\n                parent = domUtils.findParent(rng.startContainer,function(node){return domUtils.isBlockElm(node)},true),\r\n                li = domUtils.findParentByTagName(rng.startContainer,'li',true);\r\n            if(parent && parent.tagName != 'PRE' && !li){\r\n                var html = parent.innerHTML.replace(new RegExp(domUtils.fillChar, 'g'),'');\r\n                if(/^\\s*1\\s*\\.[^\\d]/.test(html)){\r\n                    parent.innerHTML = html.replace(/^\\s*1\\s*\\./,'');\r\n                    rng.setStartAtLast(parent).collapse(true).select();\r\n                    me.__hasEnterExecCommand = true;\r\n                    me.execCommand('insertorderedlist');\r\n                    me.__hasEnterExecCommand = false;\r\n                }\r\n            }\r\n            var range = me.selection.getRange(),\r\n                start = findList(range.startContainer,function (node) {\r\n                    return node.tagName == 'TABLE';\r\n                }),\r\n                end = range.collapsed ? start : findList(range.endContainer,function (node) {\r\n                    return node.tagName == 'TABLE';\r\n                });\r\n\r\n            if (start && end && start === end) {\r\n\r\n                if (!range.collapsed) {\r\n                    start = domUtils.findParentByTagName(range.startContainer, 'li', true);\r\n                    end = domUtils.findParentByTagName(range.endContainer, 'li', true);\r\n                    if (start && end && start === end) {\r\n                        range.deleteContents();\r\n                        li = domUtils.findParentByTagName(range.startContainer, 'li', true);\r\n                        if (li && domUtils.isEmptyBlock(li)) {\r\n\r\n                            pre = li.previousSibling;\r\n                            next = li.nextSibling;\r\n                            p = me.document.createElement('p');\r\n\r\n                            domUtils.fillNode(me.document, p);\r\n                            parentList = li.parentNode;\r\n                            if (pre && next) {\r\n                                range.setStart(next, 0).collapse(true).select(true);\r\n                                domUtils.remove(li);\r\n\r\n                            } else {\r\n                                if (!pre && !next || !pre) {\r\n\r\n                                    parentList.parentNode.insertBefore(p, parentList);\r\n\r\n\r\n                                } else {\r\n                                    li.parentNode.parentNode.insertBefore(p, parentList.nextSibling);\r\n                                }\r\n                                domUtils.remove(li);\r\n                                if (!parentList.firstChild) {\r\n                                    domUtils.remove(parentList);\r\n                                }\r\n                                range.setStart(p, 0).setCursor();\r\n\r\n\r\n                            }\r\n                            preventAndSave();\r\n                            return;\r\n\r\n                        }\r\n                    } else {\r\n                        var tmpRange = range.cloneRange(),\r\n                            bk = tmpRange.collapse(false).createBookmark();\r\n\r\n                        range.deleteContents();\r\n                        tmpRange.moveToBookmark(bk);\r\n                        var li = domUtils.findParentByTagName(tmpRange.startContainer, 'li', true);\r\n\r\n                        clearEmptySibling(li);\r\n                        tmpRange.select();\r\n                        preventAndSave();\r\n                        return;\r\n                    }\r\n                }\r\n\r\n\r\n                li = domUtils.findParentByTagName(range.startContainer, 'li', true);\r\n\r\n                if (li) {\r\n                    if (domUtils.isEmptyBlock(li)) {\r\n                        bk = range.createBookmark();\r\n                        var parentList = li.parentNode;\r\n                        if (li !== parentList.lastChild) {\r\n                            domUtils.breakParent(li, parentList);\r\n                            clearEmptySibling(li);\r\n                        } else {\r\n\r\n                            parentList.parentNode.insertBefore(li, parentList.nextSibling);\r\n                            if (domUtils.isEmptyNode(parentList)) {\r\n                                domUtils.remove(parentList);\r\n                            }\r\n                        }\r\n                        //嵌套不处理\r\n                        if (!dtd.$list[li.parentNode.tagName]) {\r\n\r\n                            if (!domUtils.isBlockElm(li.firstChild)) {\r\n                                p = me.document.createElement('p');\r\n                                li.parentNode.insertBefore(p, li);\r\n                                while (li.firstChild) {\r\n                                    p.appendChild(li.firstChild);\r\n                                }\r\n                                domUtils.remove(li);\r\n                            } else {\r\n                                domUtils.remove(li, true);\r\n                            }\r\n                        }\r\n                        range.moveToBookmark(bk).select();\r\n\r\n\r\n                    } else {\r\n                        var first = li.firstChild;\r\n                        if (!first || !domUtils.isBlockElm(first)) {\r\n                            var p = me.document.createElement('p');\r\n\r\n                            !li.firstChild && domUtils.fillNode(me.document, p);\r\n                            while (li.firstChild) {\r\n\r\n                                p.appendChild(li.firstChild);\r\n                            }\r\n                            li.appendChild(p);\r\n                            first = p;\r\n                        }\r\n\r\n                        var span = me.document.createElement('span');\r\n\r\n                        range.insertNode(span);\r\n                        domUtils.breakParent(span, li);\r\n\r\n                        var nextLi = span.nextSibling;\r\n                        first = nextLi.firstChild;\r\n\r\n                        if (!first) {\r\n                            p = me.document.createElement('p');\r\n\r\n                            domUtils.fillNode(me.document, p);\r\n                            nextLi.appendChild(p);\r\n                            first = p;\r\n                        }\r\n                        if (domUtils.isEmptyNode(first)) {\r\n                            first.innerHTML = '';\r\n                            domUtils.fillNode(me.document, first);\r\n                        }\r\n\r\n                        range.setStart(first, 0).collapse(true).shrinkBoundary().select();\r\n                        domUtils.remove(span);\r\n                        var pre = nextLi.previousSibling;\r\n                        if (pre && domUtils.isEmptyBlock(pre)) {\r\n                            pre.innerHTML = '<p></p>';\r\n                            domUtils.fillNode(me.document, pre.firstChild);\r\n                        }\r\n\r\n                    }\r\n//                        }\r\n                    preventAndSave();\r\n                }\r\n\r\n\r\n            }\r\n\r\n\r\n        }\r\n        if (keyCode == 8) {\r\n            //修中ie中li下的问题\r\n            range = me.selection.getRange();\r\n            if (range.collapsed && domUtils.isStartInblock(range)) {\r\n                tmpRange = range.cloneRange().trimBoundary();\r\n                li = domUtils.findParentByTagName(range.startContainer, 'li', true);\r\n                //要在li的最左边，才能处理\r\n                if (li && domUtils.isStartInblock(tmpRange)) {\r\n                    start = domUtils.findParentByTagName(range.startContainer, 'p', true);\r\n                    if (start && start !== li.firstChild) {\r\n                        var parentList = domUtils.findParentByTagName(start,['ol','ul']);\r\n                        domUtils.breakParent(start,parentList);\r\n                        clearEmptySibling(start);\r\n                        me.fireEvent('contentchange');\r\n                        range.setStart(start,0).setCursor(false,true);\r\n                        me.fireEvent('saveScene');\r\n                        domUtils.preventDefault(evt);\r\n                        return;\r\n                    }\r\n\r\n                    if (li && (pre = li.previousSibling)) {\r\n                        if (keyCode == 46 && li.childNodes.length) {\r\n                            return;\r\n                        }\r\n                        //有可能上边的兄弟节点是个2级菜单，要追加到2级菜单的最后的li\r\n                        if (dtd.$list[pre.tagName]) {\r\n                            pre = pre.lastChild;\r\n                        }\r\n                        me.undoManger && me.undoManger.save();\r\n                        first = li.firstChild;\r\n                        if (domUtils.isBlockElm(first)) {\r\n                            if (domUtils.isEmptyNode(first)) {\r\n//                                    range.setEnd(pre, pre.childNodes.length).shrinkBoundary().collapse().select(true);\r\n                                pre.appendChild(first);\r\n                                range.setStart(first, 0).setCursor(false, true);\r\n                                //first不是唯一的节点\r\n                                while (li.firstChild) {\r\n                                    pre.appendChild(li.firstChild);\r\n                                }\r\n                            } else {\r\n\r\n                                span = me.document.createElement('span');\r\n                                range.insertNode(span);\r\n                                //判断pre是否是空的节点,如果是<p><br/></p>类型的空节点，干掉p标签防止它占位\r\n                                if (domUtils.isEmptyBlock(pre)) {\r\n                                    pre.innerHTML = '';\r\n                                }\r\n                                domUtils.moveChild(li, pre);\r\n                                range.setStartBefore(span).collapse(true).select(true);\r\n\r\n                                domUtils.remove(span);\r\n\r\n                            }\r\n                        } else {\r\n                            if (domUtils.isEmptyNode(li)) {\r\n                                var p = me.document.createElement('p');\r\n                                pre.appendChild(p);\r\n                                range.setStart(p, 0).setCursor();\r\n//                                    range.setEnd(pre, pre.childNodes.length).shrinkBoundary().collapse().select(true);\r\n                            } else {\r\n                                range.setEnd(pre, pre.childNodes.length).collapse().select(true);\r\n                                while (li.firstChild) {\r\n                                    pre.appendChild(li.firstChild);\r\n                                }\r\n                            }\r\n                        }\r\n                        domUtils.remove(li);\r\n                        me.fireEvent('contentchange');\r\n                        me.fireEvent('saveScene');\r\n                        domUtils.preventDefault(evt);\r\n                        return;\r\n\r\n                    }\r\n                    //trace:980\r\n\r\n                    if (li && !li.previousSibling) {\r\n                        var parentList = li.parentNode;\r\n                        var bk = range.createBookmark();\r\n                        if(domUtils.isTagNode(parentList.parentNode,'ol ul')){\r\n                            parentList.parentNode.insertBefore(li,parentList);\r\n                            if(domUtils.isEmptyNode(parentList)){\r\n                                domUtils.remove(parentList)\r\n                            }\r\n                        }else{\r\n\r\n                            while(li.firstChild){\r\n                                parentList.parentNode.insertBefore(li.firstChild,parentList);\r\n                            }\r\n\r\n                            domUtils.remove(li);\r\n                            if(domUtils.isEmptyNode(parentList)){\r\n                                domUtils.remove(parentList)\r\n                            }\r\n\r\n                        }\r\n                        range.moveToBookmark(bk).setCursor(false,true);\r\n                        me.fireEvent('contentchange');\r\n                        me.fireEvent('saveScene');\r\n                        domUtils.preventDefault(evt);\r\n                        return;\r\n\r\n                    }\r\n\r\n\r\n                }\r\n\r\n\r\n            }\r\n\r\n        }\r\n    });\r\n\r\n    me.addListener('keyup',function(type, evt){\r\n        var keyCode = evt.keyCode || evt.which;\r\n        if (keyCode == 8) {\r\n            var rng = me.selection.getRange(),list;\r\n            if(list = domUtils.findParentByTagName(rng.startContainer,['ol', 'ul'],true)){\r\n                adjustList(list,list.tagName.toLowerCase(),getStyle(list)||domUtils.getComputedStyle(list,'list-style-type'),true)\r\n            }\r\n        }\r\n    });\r\n    //处理tab键\r\n    me.addListener('tabkeydown',function(){\r\n\r\n        var range = me.selection.getRange();\r\n\r\n        //控制级数\r\n        function checkLevel(li){\r\n            if(me.options.maxListLevel != -1){\r\n                var level = li.parentNode,levelNum = 0;\r\n                while(/[ou]l/i.test(level.tagName)){\r\n                    levelNum++;\r\n                    level = level.parentNode;\r\n                }\r\n                if(levelNum >= me.options.maxListLevel){\r\n                    return true;\r\n                }\r\n            }\r\n        }\r\n        //只以开始为准\r\n        //todo 后续改进\r\n        var li = domUtils.findParentByTagName(range.startContainer, 'li', true);\r\n        if(li){\r\n\r\n            var bk;\r\n            if(range.collapsed){\r\n                if(checkLevel(li))\r\n                    return true;\r\n                var parentLi = li.parentNode,\r\n                    list = me.document.createElement(parentLi.tagName),\r\n                    index = utils.indexOf(listStyle[list.tagName], getStyle(parentLi)||domUtils.getComputedStyle(parentLi, 'list-style-type'));\r\n                index = index + 1 == listStyle[list.tagName].length ? 0 : index + 1;\r\n                var currentStyle = listStyle[list.tagName][index];\r\n                setListStyle(list,currentStyle);\r\n                if(domUtils.isStartInblock(range)){\r\n                    me.fireEvent('saveScene');\r\n                    bk = range.createBookmark();\r\n                    parentLi.insertBefore(list, li);\r\n                    list.appendChild(li);\r\n                    adjustList(list,list.tagName.toLowerCase(),currentStyle);\r\n                    me.fireEvent('contentchange');\r\n                    range.moveToBookmark(bk).select(true);\r\n                    return true;\r\n                }\r\n            }else{\r\n                me.fireEvent('saveScene');\r\n                bk = range.createBookmark();\r\n                for(var i= 0,closeList,parents = domUtils.findParents(li),ci;ci=parents[i++];){\r\n                    if(domUtils.isTagNode(ci,'ol ul')){\r\n                        closeList = ci;\r\n                        break;\r\n                    }\r\n                }\r\n                var current = li;\r\n                if(bk.end){\r\n                    while(current && !(domUtils.getPosition(current, bk.end) & domUtils.POSITION_FOLLOWING)){\r\n                        if(checkLevel(current)){\r\n                            current = domUtils.getNextDomNode(current,false,null,function(node){return node !== closeList});\r\n                            continue;\r\n                        }\r\n                        var parentLi = current.parentNode,\r\n                            list = me.document.createElement(parentLi.tagName),\r\n                            index = utils.indexOf(listStyle[list.tagName], getStyle(parentLi)||domUtils.getComputedStyle(parentLi, 'list-style-type'));\r\n                        var currentIndex = index + 1 == listStyle[list.tagName].length ? 0 : index + 1;\r\n                        var currentStyle = listStyle[list.tagName][currentIndex];\r\n                        setListStyle(list,currentStyle);\r\n                        parentLi.insertBefore(list, current);\r\n                        while(current && !(domUtils.getPosition(current, bk.end) & domUtils.POSITION_FOLLOWING)){\r\n                            li = current.nextSibling;\r\n                            list.appendChild(current);\r\n                            if(!li || domUtils.isTagNode(li,'ol ul')){\r\n                                if(li){\r\n                                    while(li = li.firstChild){\r\n                                        if(li.tagName == 'LI'){\r\n                                            break;\r\n                                        }\r\n                                    }\r\n                                }else{\r\n                                    li = domUtils.getNextDomNode(current,false,null,function(node){return node !== closeList});\r\n                                }\r\n                                break;\r\n                            }\r\n                            current = li;\r\n                        }\r\n                        adjustList(list,list.tagName.toLowerCase(),currentStyle);\r\n                        current = li;\r\n                    }\r\n                }\r\n                me.fireEvent('contentchange');\r\n                range.moveToBookmark(bk).select();\r\n                return true;\r\n            }\r\n        }\r\n\r\n    });\r\n    function getLi(start){\r\n        while(start && !domUtils.isBody(start)){\r\n            if(start.nodeName == 'TABLE'){\r\n                return null;\r\n            }\r\n            if(start.nodeName == 'LI'){\r\n                return start\r\n            }\r\n            start = start.parentNode;\r\n        }\r\n    }\r\n\r\n    /**\r\n     * 有序列表，与“insertunorderedlist”命令互斥\r\n     * @command insertorderedlist\r\n     * @method execCommand\r\n     * @param { String } command 命令字符串\r\n     * @param { String } style 插入的有序列表类型，值为：decimal,lower-alpha,lower-roman,upper-alpha,upper-roman,cn,cn1,cn2,num,num1,num2\r\n     * @example\r\n     * ```javascript\r\n     * editor.execCommand( 'insertorderedlist','decimal');\r\n     * ```\r\n     */\r\n    /**\r\n     * 查询当前选区内容是否有序列表\r\n     * @command insertorderedlist\r\n     * @method queryCommandState\r\n     * @param { String } cmd 命令字符串\r\n     * @return { int } 如果当前选区是有序列表返回1，否则返回0\r\n     * @example\r\n     * ```javascript\r\n     * editor.queryCommandState( 'insertorderedlist' );\r\n     * ```\r\n     */\r\n    /**\r\n     * 查询当前选区内容是否有序列表\r\n     * @command insertorderedlist\r\n     * @method queryCommandValue\r\n     * @param { String } cmd 命令字符串\r\n     * @return { String } 返回当前有序列表的类型，值为null或decimal,lower-alpha,lower-roman,upper-alpha,upper-roman,cn,cn1,cn2,num,num1,num2\r\n     * @example\r\n     * ```javascript\r\n     * editor.queryCommandValue( 'insertorderedlist' );\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 无序列表，与“insertorderedlist”命令互斥\r\n     * @command insertunorderedlist\r\n     * @method execCommand\r\n     * @param { String } command 命令字符串\r\n     * @param { String } style 插入的无序列表类型，值为：circle,disc,square,dash,dot\r\n     * @example\r\n     * ```javascript\r\n     * editor.execCommand( 'insertunorderedlist','circle');\r\n     * ```\r\n     */\r\n    /**\r\n     * 查询当前是否有word文档粘贴进来的图片\r\n     * @command insertunorderedlist\r\n     * @method insertunorderedlist\r\n     * @param { String } command 命令字符串\r\n     * @return { int } 如果当前选区是无序列表返回1，否则返回0\r\n     * @example\r\n     * ```javascript\r\n     * editor.queryCommandState( 'insertunorderedlist' );\r\n     * ```\r\n     */\r\n    /**\r\n     * 查询当前选区内容是否有序列表\r\n     * @command insertunorderedlist\r\n     * @method queryCommandValue\r\n     * @param { String } command 命令字符串\r\n     * @return { String } 返回当前无序列表的类型，值为null或circle,disc,square,dash,dot\r\n     * @example\r\n     * ```javascript\r\n     * editor.queryCommandValue( 'insertunorderedlist' );\r\n     * ```\r\n     */\r\n\r\n    me.commands['insertorderedlist'] =\r\n    me.commands['insertunorderedlist'] = {\r\n            execCommand:function (command, style) {\r\n\r\n                if (!style) {\r\n                    style = command.toLowerCase() == 'insertorderedlist' ? 'decimal' : 'disc';\r\n                }\r\n                var me = this,\r\n                    range = this.selection.getRange(),\r\n                    filterFn = function (node) {\r\n                        return   node.nodeType == 1 ? node.tagName.toLowerCase() != 'br' : !domUtils.isWhitespace(node);\r\n                    },\r\n                    tag = command.toLowerCase() == 'insertorderedlist' ? 'ol' : 'ul',\r\n                    frag = me.document.createDocumentFragment();\r\n                //去掉是因为会出现选到末尾，导致adjustmentBoundary缩到ol/ul的位置\r\n                //range.shrinkBoundary();//.adjustmentBoundary();\r\n                range.adjustmentBoundary().shrinkBoundary();\r\n                var bko = range.createBookmark(true),\r\n                    start = getLi(me.document.getElementById(bko.start)),\r\n                    modifyStart = 0,\r\n                    end =  getLi(me.document.getElementById(bko.end)),\r\n                    modifyEnd = 0,\r\n                    startParent, endParent,\r\n                    list, tmp;\r\n\r\n                if (start || end) {\r\n                    start && (startParent = start.parentNode);\r\n                    if (!bko.end) {\r\n                        end = start;\r\n                    }\r\n                    end && (endParent = end.parentNode);\r\n\r\n                    if (startParent === endParent) {\r\n                        while (start !== end) {\r\n                            tmp = start;\r\n                            start = start.nextSibling;\r\n                            if (!domUtils.isBlockElm(tmp.firstChild)) {\r\n                                var p = me.document.createElement('p');\r\n                                while (tmp.firstChild) {\r\n                                    p.appendChild(tmp.firstChild);\r\n                                }\r\n                                tmp.appendChild(p);\r\n                            }\r\n                            frag.appendChild(tmp);\r\n                        }\r\n                        tmp = me.document.createElement('span');\r\n                        startParent.insertBefore(tmp, end);\r\n                        if (!domUtils.isBlockElm(end.firstChild)) {\r\n                            p = me.document.createElement('p');\r\n                            while (end.firstChild) {\r\n                                p.appendChild(end.firstChild);\r\n                            }\r\n                            end.appendChild(p);\r\n                        }\r\n                        frag.appendChild(end);\r\n                        domUtils.breakParent(tmp, startParent);\r\n                        if (domUtils.isEmptyNode(tmp.previousSibling)) {\r\n                            domUtils.remove(tmp.previousSibling);\r\n                        }\r\n                        if (domUtils.isEmptyNode(tmp.nextSibling)) {\r\n                            domUtils.remove(tmp.nextSibling)\r\n                        }\r\n                        var nodeStyle = getStyle(startParent) || domUtils.getComputedStyle(startParent, 'list-style-type') || (command.toLowerCase() == 'insertorderedlist' ? 'decimal' : 'disc');\r\n                        if (startParent.tagName.toLowerCase() == tag && nodeStyle == style) {\r\n                            for (var i = 0, ci, tmpFrag = me.document.createDocumentFragment(); ci = frag.firstChild;) {\r\n                                if(domUtils.isTagNode(ci,'ol ul')){\r\n//                                  删除时，子列表不处理\r\n//                                  utils.each(domUtils.getElementsByTagName(ci,'li'),function(li){\r\n//                                        while(li.firstChild){\r\n//                                            tmpFrag.appendChild(li.firstChild);\r\n//                                        }\r\n//\r\n//                                    });\r\n                                    tmpFrag.appendChild(ci);\r\n                                }else{\r\n                                    while (ci.firstChild) {\r\n\r\n                                        tmpFrag.appendChild(ci.firstChild);\r\n                                        domUtils.remove(ci);\r\n                                    }\r\n                                }\r\n\r\n                            }\r\n                            tmp.parentNode.insertBefore(tmpFrag, tmp);\r\n                        } else {\r\n                            list = me.document.createElement(tag);\r\n                            setListStyle(list,style);\r\n                            list.appendChild(frag);\r\n                            tmp.parentNode.insertBefore(list, tmp);\r\n                        }\r\n\r\n                        domUtils.remove(tmp);\r\n                        list && adjustList(list, tag, style);\r\n                        range.moveToBookmark(bko).select();\r\n                        return;\r\n                    }\r\n                    //开始\r\n                    if (start) {\r\n                        while (start) {\r\n                            tmp = start.nextSibling;\r\n                            if (domUtils.isTagNode(start, 'ol ul')) {\r\n                                frag.appendChild(start);\r\n                            } else {\r\n                                var tmpfrag = me.document.createDocumentFragment(),\r\n                                    hasBlock = 0;\r\n                                while (start.firstChild) {\r\n                                    if (domUtils.isBlockElm(start.firstChild)) {\r\n                                        hasBlock = 1;\r\n                                    }\r\n                                    tmpfrag.appendChild(start.firstChild);\r\n                                }\r\n                                if (!hasBlock) {\r\n                                    var tmpP = me.document.createElement('p');\r\n                                    tmpP.appendChild(tmpfrag);\r\n                                    frag.appendChild(tmpP);\r\n                                } else {\r\n                                    frag.appendChild(tmpfrag);\r\n                                }\r\n                                domUtils.remove(start);\r\n                            }\r\n\r\n                            start = tmp;\r\n                        }\r\n                        startParent.parentNode.insertBefore(frag, startParent.nextSibling);\r\n                        if (domUtils.isEmptyNode(startParent)) {\r\n                            range.setStartBefore(startParent);\r\n                            domUtils.remove(startParent);\r\n                        } else {\r\n                            range.setStartAfter(startParent);\r\n                        }\r\n                        modifyStart = 1;\r\n                    }\r\n\r\n                    if (end && domUtils.inDoc(endParent, me.document)) {\r\n                        //结束\r\n                        start = endParent.firstChild;\r\n                        while (start && start !== end) {\r\n                            tmp = start.nextSibling;\r\n                            if (domUtils.isTagNode(start, 'ol ul')) {\r\n                                frag.appendChild(start);\r\n                            } else {\r\n                                tmpfrag = me.document.createDocumentFragment();\r\n                                hasBlock = 0;\r\n                                while (start.firstChild) {\r\n                                    if (domUtils.isBlockElm(start.firstChild)) {\r\n                                        hasBlock = 1;\r\n                                    }\r\n                                    tmpfrag.appendChild(start.firstChild);\r\n                                }\r\n                                if (!hasBlock) {\r\n                                    tmpP = me.document.createElement('p');\r\n                                    tmpP.appendChild(tmpfrag);\r\n                                    frag.appendChild(tmpP);\r\n                                } else {\r\n                                    frag.appendChild(tmpfrag);\r\n                                }\r\n                                domUtils.remove(start);\r\n                            }\r\n                            start = tmp;\r\n                        }\r\n                        var tmpDiv = domUtils.createElement(me.document, 'div', {\r\n                            'tmpDiv':1\r\n                        });\r\n                        domUtils.moveChild(end, tmpDiv);\r\n\r\n                        frag.appendChild(tmpDiv);\r\n                        domUtils.remove(end);\r\n                        endParent.parentNode.insertBefore(frag, endParent);\r\n                        range.setEndBefore(endParent);\r\n                        if (domUtils.isEmptyNode(endParent)) {\r\n                            domUtils.remove(endParent);\r\n                        }\r\n\r\n                        modifyEnd = 1;\r\n                    }\r\n\r\n\r\n                }\r\n\r\n                if (!modifyStart) {\r\n                    range.setStartBefore(me.document.getElementById(bko.start));\r\n                }\r\n                if (bko.end && !modifyEnd) {\r\n                    range.setEndAfter(me.document.getElementById(bko.end));\r\n                }\r\n                range.enlarge(true, function (node) {\r\n                    return notExchange[node.tagName];\r\n                });\r\n\r\n                frag = me.document.createDocumentFragment();\r\n\r\n                var bk = range.createBookmark(),\r\n                    current = domUtils.getNextDomNode(bk.start, false, filterFn),\r\n                    tmpRange = range.cloneRange(),\r\n                    tmpNode,\r\n                    block = domUtils.isBlockElm;\r\n\r\n                while (current && current !== bk.end && (domUtils.getPosition(current, bk.end) & domUtils.POSITION_PRECEDING)) {\r\n\r\n                    if (current.nodeType == 3 || dtd.li[current.tagName]) {\r\n                        if (current.nodeType == 1 && dtd.$list[current.tagName]) {\r\n                            while (current.firstChild) {\r\n                                frag.appendChild(current.firstChild);\r\n                            }\r\n                            tmpNode = domUtils.getNextDomNode(current, false, filterFn);\r\n                            domUtils.remove(current);\r\n                            current = tmpNode;\r\n                            continue;\r\n\r\n                        }\r\n                        tmpNode = current;\r\n                        tmpRange.setStartBefore(current);\r\n\r\n                        while (current && current !== bk.end && (!block(current) || domUtils.isBookmarkNode(current) )) {\r\n                            tmpNode = current;\r\n                            current = domUtils.getNextDomNode(current, false, null, function (node) {\r\n                                return !notExchange[node.tagName];\r\n                            });\r\n                        }\r\n\r\n                        if (current && block(current)) {\r\n                            tmp = domUtils.getNextDomNode(tmpNode, false, filterFn);\r\n                            if (tmp && domUtils.isBookmarkNode(tmp)) {\r\n                                current = domUtils.getNextDomNode(tmp, false, filterFn);\r\n                                tmpNode = tmp;\r\n                            }\r\n                        }\r\n                        tmpRange.setEndAfter(tmpNode);\r\n\r\n                        current = domUtils.getNextDomNode(tmpNode, false, filterFn);\r\n\r\n                        var li = range.document.createElement('li');\r\n\r\n                        li.appendChild(tmpRange.extractContents());\r\n                        if(domUtils.isEmptyNode(li)){\r\n                            var tmpNode = range.document.createElement('p');\r\n                            while(li.firstChild){\r\n                                tmpNode.appendChild(li.firstChild)\r\n                            }\r\n                            li.appendChild(tmpNode);\r\n                        }\r\n                        frag.appendChild(li);\r\n                    } else {\r\n                        current = domUtils.getNextDomNode(current, true, filterFn);\r\n                    }\r\n                }\r\n                range.moveToBookmark(bk).collapse(true);\r\n                list = me.document.createElement(tag);\r\n                setListStyle(list,style);\r\n                list.appendChild(frag);\r\n                range.insertNode(list);\r\n                //当前list上下看能否合并\r\n                adjustList(list, tag, style);\r\n                //去掉冗余的tmpDiv\r\n                for (var i = 0, ci, tmpDivs = domUtils.getElementsByTagName(list, 'div'); ci = tmpDivs[i++];) {\r\n                    if (ci.getAttribute('tmpDiv')) {\r\n                        domUtils.remove(ci, true)\r\n                    }\r\n                }\r\n                range.moveToBookmark(bko).select();\r\n\r\n            },\r\n            queryCommandState:function (command) {\r\n                var tag = command.toLowerCase() == 'insertorderedlist' ? 'ol' : 'ul';\r\n                var path = this.selection.getStartElementPath();\r\n                for(var i= 0,ci;ci = path[i++];){\r\n                    if(ci.nodeName == 'TABLE'){\r\n                        return 0\r\n                    }\r\n                    if(tag == ci.nodeName.toLowerCase()){\r\n                        return 1\r\n                    };\r\n                }\r\n                return 0;\r\n\r\n            },\r\n            queryCommandValue:function (command) {\r\n                var tag = command.toLowerCase() == 'insertorderedlist' ? 'ol' : 'ul';\r\n                var path = this.selection.getStartElementPath(),\r\n                    node;\r\n                for(var i= 0,ci;ci = path[i++];){\r\n                    if(ci.nodeName == 'TABLE'){\r\n                        node = null;\r\n                        break;\r\n                    }\r\n                    if(tag == ci.nodeName.toLowerCase()){\r\n                        node = ci;\r\n                        break;\r\n                    };\r\n                }\r\n                return node ? getStyle(node) || domUtils.getComputedStyle(node, 'list-style-type') : null;\r\n            }\r\n        };\r\n};\r\n\r\n\r\n\r\n// plugins/source.js\r\n/**\r\n * 源码编辑插件\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n(function (){\r\n    var sourceEditors = {\r\n        textarea: function (editor, holder){\r\n            var textarea = holder.ownerDocument.createElement('textarea');\r\n            textarea.style.cssText = 'position:absolute;resize:none;width:100%;height:100%;border:0;padding:0;margin:0;overflow-y:auto;';\r\n            // todo: IE下只有onresize属性可用... 很纠结\r\n            if (browser.ie && browser.version < 8) {\r\n                textarea.style.width = holder.offsetWidth + 'px';\r\n                textarea.style.height = holder.offsetHeight + 'px';\r\n                holder.onresize = function (){\r\n                    textarea.style.width = holder.offsetWidth + 'px';\r\n                    textarea.style.height = holder.offsetHeight + 'px';\r\n                };\r\n            }\r\n            holder.appendChild(textarea);\r\n            return {\r\n                setContent: function (content){\r\n                    textarea.value = content;\r\n                },\r\n                getContent: function (){\r\n                    return textarea.value;\r\n                },\r\n                select: function (){\r\n                    var range;\r\n                    if (browser.ie) {\r\n                        range = textarea.createTextRange();\r\n                        range.collapse(true);\r\n                        range.select();\r\n                    } else {\r\n                        //todo: chrome下无法设置焦点\r\n                        textarea.setSelectionRange(0, 0);\r\n                        textarea.focus();\r\n                    }\r\n                },\r\n                dispose: function (){\r\n                    holder.removeChild(textarea);\r\n                    // todo\r\n                    holder.onresize = null;\r\n                    textarea = null;\r\n                    holder = null;\r\n                }\r\n            };\r\n        },\r\n        codemirror: function (editor, holder){\r\n\r\n            var codeEditor = window.CodeMirror(holder, {\r\n                mode: \"text/html\",\r\n                tabMode: \"indent\",\r\n                lineNumbers: true,\r\n                lineWrapping:true\r\n            });\r\n            var dom = codeEditor.getWrapperElement();\r\n            dom.style.cssText = 'position:absolute;left:0;top:0;width:100%;height:100%;font-family:consolas,\"Courier new\",monospace;font-size:13px;';\r\n            codeEditor.getScrollerElement().style.cssText = 'position:absolute;left:0;top:0;width:100%;height:100%;';\r\n            codeEditor.refresh();\r\n            return {\r\n                getCodeMirror:function(){\r\n                    return codeEditor;\r\n                },\r\n                setContent: function (content){\r\n                    codeEditor.setValue(content);\r\n                },\r\n                getContent: function (){\r\n                    return codeEditor.getValue();\r\n                },\r\n                select: function (){\r\n                    codeEditor.focus();\r\n                },\r\n                dispose: function (){\r\n                    holder.removeChild(dom);\r\n                    dom = null;\r\n                    codeEditor = null;\r\n                }\r\n            };\r\n        }\r\n    };\r\n\r\n    UE.plugins['source'] = function (){\r\n        var me = this;\r\n        var opt = this.options;\r\n        var sourceMode = false;\r\n        var sourceEditor;\r\n        var orgSetContent;\r\n        opt.sourceEditor = browser.ie  ? 'textarea' : (opt.sourceEditor || 'codemirror');\r\n\r\n        me.setOpt({\r\n            sourceEditorFirst:false\r\n        });\r\n        function createSourceEditor(holder){\r\n            return sourceEditors[opt.sourceEditor == 'codemirror' && window.CodeMirror ? 'codemirror' : 'textarea'](me, holder);\r\n        }\r\n\r\n        var bakCssText;\r\n        //解决在源码模式下getContent不能得到最新的内容问题\r\n        var oldGetContent,\r\n            bakAddress;\r\n\r\n        /**\r\n         * 切换源码模式和编辑模式\r\n         * @command source\r\n         * @method execCommand\r\n         * @param { String } cmd 命令字符串\r\n         * @example\r\n         * ```javascript\r\n         * editor.execCommand( 'source');\r\n         * ```\r\n         */\r\n\r\n        /**\r\n         * 查询当前编辑区域的状态是源码模式还是可视化模式\r\n         * @command source\r\n         * @method queryCommandState\r\n         * @param { String } cmd 命令字符串\r\n         * @return { int } 如果当前是源码编辑模式，返回1，否则返回0\r\n         * @example\r\n         * ```javascript\r\n         * editor.queryCommandState( 'source' );\r\n         * ```\r\n         */\r\n\r\n        me.commands['source'] = {\r\n            execCommand: function (){\r\n\r\n                sourceMode = !sourceMode;\r\n                if (sourceMode) {\r\n                    bakAddress = me.selection.getRange().createAddress(false,true);\r\n                    me.undoManger && me.undoManger.save(true);\r\n                    if(browser.gecko){\r\n                        me.body.contentEditable = false;\r\n                    }\r\n\r\n                    bakCssText = me.iframe.style.cssText;\r\n                    me.iframe.style.cssText += 'position:absolute;left:-32768px;top:-32768px;';\r\n\r\n\r\n                    me.fireEvent('beforegetcontent');\r\n                    var root = UE.htmlparser(me.body.innerHTML);\r\n                    me.filterOutputRule(root);\r\n                    root.traversal(function (node) {\r\n                        if (node.type == 'element') {\r\n                            switch (node.tagName) {\r\n                                case 'td':\r\n                                case 'th':\r\n                                case 'caption':\r\n                                if(node.children && node.children.length == 1){\r\n                                    if(node.firstChild().tagName == 'br' ){\r\n                                        node.removeChild(node.firstChild())\r\n                                    }\r\n                                };\r\n                                break;\r\n                                case 'pre':\r\n                                    node.innerText(node.innerText().replace(/&nbsp;/g,' '))\r\n\r\n                            }\r\n                        }\r\n                    });\r\n\r\n                    me.fireEvent('aftergetcontent');\r\n\r\n                    var content = root.toHtml(true);\r\n\r\n                    sourceEditor = createSourceEditor(me.iframe.parentNode);\r\n\r\n                    sourceEditor.setContent(content);\r\n\r\n                    orgSetContent = me.setContent;\r\n\r\n                    me.setContent = function(html){\r\n                        //这里暂时不触发事件，防止报错\r\n                        var root = UE.htmlparser(html);\r\n                        me.filterInputRule(root);\r\n                        html = root.toHtml();\r\n                        sourceEditor.setContent(html);\r\n                    };\r\n\r\n                    setTimeout(function (){\r\n                        sourceEditor.select();\r\n                        me.addListener('fullscreenchanged', function(){\r\n                            try{\r\n                                sourceEditor.getCodeMirror().refresh()\r\n                            }catch(e){}\r\n                        });\r\n                    });\r\n\r\n                    //重置getContent，源码模式下取值也能是最新的数据\r\n                    oldGetContent = me.getContent;\r\n                    me.getContent = function (){\r\n                        return sourceEditor.getContent() || '<p>' + (browser.ie ? '' : '<br/>')+'</p>';\r\n                    };\r\n                } else {\r\n                    me.iframe.style.cssText = bakCssText;\r\n                    var cont = sourceEditor.getContent() || '<p>' + (browser.ie ? '' : '<br/>')+'</p>';\r\n                    //处理掉block节点前后的空格,有可能会误命中，暂时不考虑\r\n                    cont = cont.replace(new RegExp('[\\\\r\\\\t\\\\n ]*<\\/?(\\\\w+)\\\\s*(?:[^>]*)>','g'), function(a,b){\r\n                        if(b && !dtd.$inlineWithA[b.toLowerCase()]){\r\n                            return a.replace(/(^[\\n\\r\\t ]*)|([\\n\\r\\t ]*$)/g,'');\r\n                        }\r\n                        return a.replace(/(^[\\n\\r\\t]*)|([\\n\\r\\t]*$)/g,'')\r\n                    });\r\n\r\n                    me.setContent = orgSetContent;\r\n\r\n                    me.setContent(cont);\r\n                    sourceEditor.dispose();\r\n                    sourceEditor = null;\r\n                    //还原getContent方法\r\n                    me.getContent = oldGetContent;\r\n                    var first = me.body.firstChild;\r\n                    //trace:1106 都删除空了，下边会报错，所以补充一个p占位\r\n                    if(!first){\r\n                        me.body.innerHTML = '<p>'+(browser.ie?'':'<br/>')+'</p>';\r\n                        first = me.body.firstChild;\r\n                    }\r\n\r\n\r\n                    //要在ifm为显示时ff才能取到selection,否则报错\r\n                    //这里不能比较位置了\r\n                    me.undoManger && me.undoManger.save(true);\r\n\r\n                    if(browser.gecko){\r\n\r\n                        var input = document.createElement('input');\r\n                        input.style.cssText = 'position:absolute;left:0;top:-32768px';\r\n\r\n                        document.body.appendChild(input);\r\n\r\n                        me.body.contentEditable = false;\r\n                        setTimeout(function(){\r\n                            domUtils.setViewportOffset(input, { left: -32768, top: 0 });\r\n                            input.focus();\r\n                            setTimeout(function(){\r\n                                me.body.contentEditable = true;\r\n                                me.selection.getRange().moveToAddress(bakAddress).select(true);\r\n                                domUtils.remove(input);\r\n                            });\r\n\r\n                        });\r\n                    }else{\r\n                        //ie下有可能报错，比如在代码顶头的情况\r\n                        try{\r\n                            me.selection.getRange().moveToAddress(bakAddress).select(true);\r\n                        }catch(e){}\r\n\r\n                    }\r\n                }\r\n                this.fireEvent('sourcemodechanged', sourceMode);\r\n            },\r\n            queryCommandState: function (){\r\n                return sourceMode|0;\r\n            },\r\n            notNeedUndo : 1\r\n        };\r\n        var oldQueryCommandState = me.queryCommandState;\r\n\r\n        me.queryCommandState = function (cmdName){\r\n            cmdName = cmdName.toLowerCase();\r\n            if (sourceMode) {\r\n                //源码模式下可以开启的命令\r\n                return cmdName in {\r\n                    'source' : 1,\r\n                    'fullscreen' : 1\r\n                } ? 1 : -1\r\n            }\r\n            return oldQueryCommandState.apply(this, arguments);\r\n        };\r\n\r\n        if(opt.sourceEditor == \"codemirror\"){\r\n\r\n            me.addListener(\"ready\",function(){\r\n                utils.loadFile(document,{\r\n                    src : opt.codeMirrorJsUrl || opt.UEDITOR_HOME_URL + \"third-party/codemirror/codemirror.js\",\r\n                    tag : \"script\",\r\n                    type : \"text/javascript\",\r\n                    defer : \"defer\"\r\n                },function(){\r\n                    if(opt.sourceEditorFirst){\r\n                        setTimeout(function(){\r\n                            me.execCommand(\"source\");\r\n                        },0);\r\n                    }\r\n                });\r\n                utils.loadFile(document,{\r\n                    tag : \"link\",\r\n                    rel : \"stylesheet\",\r\n                    type : \"text/css\",\r\n                    href : opt.codeMirrorCssUrl || opt.UEDITOR_HOME_URL + \"third-party/codemirror/codemirror.css\"\r\n                });\r\n\r\n            });\r\n        }\r\n\r\n    };\r\n\r\n})();\r\n\r\n// plugins/enterkey.js\r\n///import core\r\n///import plugins/undo.js\r\n///commands 设置回车标签p或br\r\n///commandsName  EnterKey\r\n///commandsTitle  设置回车标签p或br\r\n/**\r\n * @description 处理回车\r\n * @author zhanyi\r\n */\r\nUE.plugins['enterkey'] = function() {\r\n    var hTag,\r\n        me = this,\r\n        tag = me.options.enterTag;\r\n    me.addListener('keyup', function(type, evt) {\r\n\r\n        var keyCode = evt.keyCode || evt.which;\r\n        if (keyCode == 13) {\r\n            var range = me.selection.getRange(),\r\n                start = range.startContainer,\r\n                doSave;\r\n\r\n            //修正在h1-h6里边回车后不能嵌套p的问题\r\n            if (!browser.ie) {\r\n\r\n                if (/h\\d/i.test(hTag)) {\r\n                    if (browser.gecko) {\r\n                        var h = domUtils.findParentByTagName(start, [ 'h1', 'h2', 'h3', 'h4', 'h5', 'h6','blockquote','caption','table'], true);\r\n                        if (!h) {\r\n                            me.document.execCommand('formatBlock', false, '<p>');\r\n                            doSave = 1;\r\n                        }\r\n                    } else {\r\n                        //chrome remove div\r\n                        if (start.nodeType == 1) {\r\n                            var tmp = me.document.createTextNode(''),div;\r\n                            range.insertNode(tmp);\r\n                            div = domUtils.findParentByTagName(tmp, 'div', true);\r\n                            if (div) {\r\n                                var p = me.document.createElement('p');\r\n                                while (div.firstChild) {\r\n                                    p.appendChild(div.firstChild);\r\n                                }\r\n                                div.parentNode.insertBefore(p, div);\r\n                                domUtils.remove(div);\r\n                                range.setStartBefore(tmp).setCursor();\r\n                                doSave = 1;\r\n                            }\r\n                            domUtils.remove(tmp);\r\n\r\n                        }\r\n                    }\r\n\r\n                    if (me.undoManger && doSave) {\r\n                        me.undoManger.save();\r\n                    }\r\n                }\r\n                //没有站位符，会出现多行的问题\r\n                browser.opera &&  range.select();\r\n            }else{\r\n                me.fireEvent('saveScene',true,true)\r\n            }\r\n        }\r\n    });\r\n\r\n    me.addListener('keydown', function(type, evt) {\r\n        var keyCode = evt.keyCode || evt.which;\r\n        if (keyCode == 13) {//回车\r\n            if(me.fireEvent('beforeenterkeydown')){\r\n                domUtils.preventDefault(evt);\r\n                return;\r\n            }\r\n            me.fireEvent('saveScene',true,true);\r\n            hTag = '';\r\n\r\n\r\n            var range = me.selection.getRange();\r\n\r\n            if (!range.collapsed) {\r\n                //跨td不能删\r\n                var start = range.startContainer,\r\n                    end = range.endContainer,\r\n                    startTd = domUtils.findParentByTagName(start, 'td', true),\r\n                    endTd = domUtils.findParentByTagName(end, 'td', true);\r\n                if (startTd && endTd && startTd !== endTd || !startTd && endTd || startTd && !endTd) {\r\n                    evt.preventDefault ? evt.preventDefault() : ( evt.returnValue = false);\r\n                    return;\r\n                }\r\n            }\r\n            if (tag == 'p') {\r\n\r\n\r\n                if (!browser.ie) {\r\n\r\n                    start = domUtils.findParentByTagName(range.startContainer, ['ol','ul','p', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6','blockquote','caption'], true);\r\n\r\n                    //opera下执行formatblock会在table的场景下有问题，回车在opera原生支持很好，所以暂时在opera去掉调用这个原生的command\r\n                    //trace:2431\r\n                    if (!start && !browser.opera) {\r\n\r\n                        me.document.execCommand('formatBlock', false, '<p>');\r\n\r\n                        if (browser.gecko) {\r\n                            range = me.selection.getRange();\r\n                            start = domUtils.findParentByTagName(range.startContainer, 'p', true);\r\n                            start && domUtils.removeDirtyAttr(start);\r\n                        }\r\n\r\n\r\n                    } else {\r\n                        hTag = start.tagName;\r\n                        start.tagName.toLowerCase() == 'p' && browser.gecko && domUtils.removeDirtyAttr(start);\r\n                    }\r\n\r\n                }\r\n\r\n            } else {\r\n                evt.preventDefault ? evt.preventDefault() : ( evt.returnValue = false);\r\n\r\n                if (!range.collapsed) {\r\n                    range.deleteContents();\r\n                    start = range.startContainer;\r\n                    if (start.nodeType == 1 && (start = start.childNodes[range.startOffset])) {\r\n                        while (start.nodeType == 1) {\r\n                            if (dtd.$empty[start.tagName]) {\r\n                                range.setStartBefore(start).setCursor();\r\n                                if (me.undoManger) {\r\n                                    me.undoManger.save();\r\n                                }\r\n                                return false;\r\n                            }\r\n                            if (!start.firstChild) {\r\n                                var br = range.document.createElement('br');\r\n                                start.appendChild(br);\r\n                                range.setStart(start, 0).setCursor();\r\n                                if (me.undoManger) {\r\n                                    me.undoManger.save();\r\n                                }\r\n                                return false;\r\n                            }\r\n                            start = start.firstChild;\r\n                        }\r\n                        if (start === range.startContainer.childNodes[range.startOffset]) {\r\n                            br = range.document.createElement('br');\r\n                            range.insertNode(br).setCursor();\r\n\r\n                        } else {\r\n                            range.setStart(start, 0).setCursor();\r\n                        }\r\n\r\n\r\n                    } else {\r\n                        br = range.document.createElement('br');\r\n                        range.insertNode(br).setStartAfter(br).setCursor();\r\n                    }\r\n\r\n\r\n                } else {\r\n                    br = range.document.createElement('br');\r\n                    range.insertNode(br);\r\n                    var parent = br.parentNode;\r\n                    if (parent.lastChild === br) {\r\n                        br.parentNode.insertBefore(br.cloneNode(true), br);\r\n                        range.setStartBefore(br);\r\n                    } else {\r\n                        range.setStartAfter(br);\r\n                    }\r\n                    range.setCursor();\r\n\r\n                }\r\n\r\n            }\r\n\r\n        }\r\n    });\r\n};\r\n\r\n\r\n// plugins/keystrokes.js\r\n/* 处理特殊键的兼容性问题 */\r\nUE.plugins['keystrokes'] = function() {\r\n    var me = this;\r\n    var collapsed = true;\r\n    me.addListener('keydown', function(type, evt) {\r\n        var keyCode = evt.keyCode || evt.which,\r\n            rng = me.selection.getRange();\r\n\r\n        //处理全选的情况\r\n        if(!rng.collapsed && !(evt.ctrlKey || evt.shiftKey || evt.altKey || evt.metaKey) && (keyCode >= 65 && keyCode <=90\r\n            || keyCode >= 48 && keyCode <= 57 ||\r\n            keyCode >= 96 && keyCode <= 111 || {\r\n                    13:1,\r\n                    8:1,\r\n                    46:1\r\n                }[keyCode])\r\n            ){\r\n\r\n            var tmpNode = rng.startContainer;\r\n            if(domUtils.isFillChar(tmpNode)){\r\n                rng.setStartBefore(tmpNode)\r\n            }\r\n            tmpNode = rng.endContainer;\r\n            if(domUtils.isFillChar(tmpNode)){\r\n                rng.setEndAfter(tmpNode)\r\n            }\r\n            rng.txtToElmBoundary();\r\n            //结束边界可能放到了br的前边，要把br包含进来\r\n            // x[xxx]<br/>\r\n            if(rng.endContainer && rng.endContainer.nodeType == 1){\r\n                tmpNode = rng.endContainer.childNodes[rng.endOffset];\r\n                if(tmpNode && domUtils.isBr(tmpNode)){\r\n                    rng.setEndAfter(tmpNode);\r\n                }\r\n            }\r\n            if(rng.startOffset == 0){\r\n                tmpNode = rng.startContainer;\r\n                if(domUtils.isBoundaryNode(tmpNode,'firstChild') ){\r\n                    tmpNode = rng.endContainer;\r\n                    if(rng.endOffset == (tmpNode.nodeType == 3 ? tmpNode.nodeValue.length : tmpNode.childNodes.length) && domUtils.isBoundaryNode(tmpNode,'lastChild')){\r\n                        me.fireEvent('saveScene');\r\n                        me.body.innerHTML = '<p>'+(browser.ie ? '' : '<br/>')+'</p>';\r\n                        rng.setStart(me.body.firstChild,0).setCursor(false,true);\r\n                        me._selectionChange();\r\n                        return;\r\n                    }\r\n                }\r\n            }\r\n        }\r\n\r\n        //处理backspace\r\n        if (keyCode == keymap.Backspace) {\r\n            rng = me.selection.getRange();\r\n            collapsed = rng.collapsed;\r\n            if(me.fireEvent('delkeydown',evt)){\r\n                return;\r\n            }\r\n            var start,end;\r\n            //避免按两次删除才能生效的问题\r\n            if(rng.collapsed && rng.inFillChar()){\r\n                start = rng.startContainer;\r\n\r\n                if(domUtils.isFillChar(start)){\r\n                    rng.setStartBefore(start).shrinkBoundary(true).collapse(true);\r\n                    domUtils.remove(start)\r\n                }else{\r\n                    start.nodeValue = start.nodeValue.replace(new RegExp('^' + domUtils.fillChar ),'');\r\n                    rng.startOffset--;\r\n                    rng.collapse(true).select(true)\r\n                }\r\n            }\r\n\r\n            //解决选中control元素不能删除的问题\r\n            if (start = rng.getClosedNode()) {\r\n                me.fireEvent('saveScene');\r\n                rng.setStartBefore(start);\r\n                domUtils.remove(start);\r\n                rng.setCursor();\r\n                me.fireEvent('saveScene');\r\n                domUtils.preventDefault(evt);\r\n                return;\r\n            }\r\n            //阻止在table上的删除\r\n            if (!browser.ie) {\r\n                start = domUtils.findParentByTagName(rng.startContainer, 'table', true);\r\n                end = domUtils.findParentByTagName(rng.endContainer, 'table', true);\r\n                if (start && !end || !start && end || start !== end) {\r\n                    evt.preventDefault();\r\n                    return;\r\n                }\r\n            }\r\n\r\n        }\r\n        //处理tab键的逻辑\r\n        if (keyCode == keymap.Tab) {\r\n            //不处理以下标签\r\n            var excludeTagNameForTabKey = {\r\n                'ol' : 1,\r\n                'ul' : 1,\r\n                'table':1\r\n            };\r\n            //处理组件里的tab按下事件\r\n            if(me.fireEvent('tabkeydown',evt)){\r\n                domUtils.preventDefault(evt);\r\n                return;\r\n            }\r\n            var range = me.selection.getRange();\r\n            me.fireEvent('saveScene');\r\n            for (var i = 0,txt = '',tabSize = me.options.tabSize|| 4,tabNode =  me.options.tabNode || '&nbsp;'; i < tabSize; i++) {\r\n                txt += tabNode;\r\n            }\r\n            var span = me.document.createElement('span');\r\n            span.innerHTML = txt + domUtils.fillChar;\r\n            if (range.collapsed) {\r\n                range.insertNode(span.cloneNode(true).firstChild).setCursor(true);\r\n            } else {\r\n                var filterFn = function(node) {\r\n                    return domUtils.isBlockElm(node) && !excludeTagNameForTabKey[node.tagName.toLowerCase()]\r\n\r\n                };\r\n                //普通的情况\r\n                start = domUtils.findParent(range.startContainer, filterFn,true);\r\n                end = domUtils.findParent(range.endContainer, filterFn,true);\r\n                if (start && end && start === end) {\r\n                    range.deleteContents();\r\n                    range.insertNode(span.cloneNode(true).firstChild).setCursor(true);\r\n                } else {\r\n                    var bookmark = range.createBookmark();\r\n                    range.enlarge(true);\r\n                    var bookmark2 = range.createBookmark(),\r\n                        current = domUtils.getNextDomNode(bookmark2.start, false, filterFn);\r\n                    while (current && !(domUtils.getPosition(current, bookmark2.end) & domUtils.POSITION_FOLLOWING)) {\r\n                        current.insertBefore(span.cloneNode(true).firstChild, current.firstChild);\r\n                        current = domUtils.getNextDomNode(current, false, filterFn);\r\n                    }\r\n                    range.moveToBookmark(bookmark2).moveToBookmark(bookmark).select();\r\n                }\r\n            }\r\n            domUtils.preventDefault(evt)\r\n        }\r\n        //trace:1634\r\n        //ff的del键在容器空的时候，也会删除\r\n        if(browser.gecko && keyCode == 46){\r\n            range = me.selection.getRange();\r\n            if(range.collapsed){\r\n                start = range.startContainer;\r\n                if(domUtils.isEmptyBlock(start)){\r\n                    var parent = start.parentNode;\r\n                    while(domUtils.getChildCount(parent) == 1 && !domUtils.isBody(parent)){\r\n                        start = parent;\r\n                        parent = parent.parentNode;\r\n                    }\r\n                    if(start === parent.lastChild)\r\n                        evt.preventDefault();\r\n                    return;\r\n                }\r\n            }\r\n        }\r\n    });\r\n    me.addListener('keyup', function(type, evt) {\r\n        var keyCode = evt.keyCode || evt.which,\r\n            rng,me = this;\r\n        if(keyCode == keymap.Backspace){\r\n            if(me.fireEvent('delkeyup')){\r\n                return;\r\n            }\r\n            rng = me.selection.getRange();\r\n            if(rng.collapsed){\r\n                var tmpNode,\r\n                    autoClearTagName = ['h1','h2','h3','h4','h5','h6'];\r\n                if(tmpNode = domUtils.findParentByTagName(rng.startContainer,autoClearTagName,true)){\r\n                    if(domUtils.isEmptyBlock(tmpNode)){\r\n                        var pre = tmpNode.previousSibling;\r\n                        if(pre && pre.nodeName != 'TABLE'){\r\n                            domUtils.remove(tmpNode);\r\n                            rng.setStartAtLast(pre).setCursor(false,true);\r\n                            return;\r\n                        }else{\r\n                            var next = tmpNode.nextSibling;\r\n                            if(next && next.nodeName != 'TABLE'){\r\n                                domUtils.remove(tmpNode);\r\n                                rng.setStartAtFirst(next).setCursor(false,true);\r\n                                return;\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n                //处理当删除到body时，要重新给p标签展位\r\n                if(domUtils.isBody(rng.startContainer)){\r\n                    var tmpNode = domUtils.createElement(me.document,'p',{\r\n                        'innerHTML' : browser.ie ? domUtils.fillChar : '<br/>'\r\n                    });\r\n                    rng.insertNode(tmpNode).setStart(tmpNode,0).setCursor(false,true);\r\n                }\r\n            }\r\n\r\n\r\n            //chrome下如果删除了inline标签，浏览器会有记忆，在输入文字还是会套上刚才删除的标签，所以这里再选一次就不会了\r\n            if( !collapsed && (rng.startContainer.nodeType == 3 || rng.startContainer.nodeType == 1 && domUtils.isEmptyBlock(rng.startContainer))){\r\n                if(browser.ie){\r\n                    var span = rng.document.createElement('span');\r\n                    rng.insertNode(span).setStartBefore(span).collapse(true);\r\n                    rng.select();\r\n                    domUtils.remove(span)\r\n                }else{\r\n                    rng.select()\r\n                }\r\n\r\n            }\r\n        }\r\n\r\n\r\n    })\r\n};\r\n\r\n// plugins/fiximgclick.js\r\n///import core\r\n///commands 修复chrome下图片不能点击的问题，出现八个角可改变大小\r\n///commandsName  FixImgClick\r\n///commandsTitle  修复chrome下图片不能点击的问题，出现八个角可改变大小\r\n//修复chrome下图片不能点击的问题，出现八个角可改变大小\r\n\r\nUE.plugins['fiximgclick'] = (function () {\r\n\r\n    var elementUpdated = false;\r\n    function Scale() {\r\n        this.editor = null;\r\n        this.resizer = null;\r\n        this.cover = null;\r\n        this.doc = document;\r\n        this.prePos = {x: 0, y: 0};\r\n        this.startPos = {x: 0, y: 0};\r\n    }\r\n\r\n    (function () {\r\n        var rect = [\r\n            //[left, top, width, height]\r\n            [0, 0, -1, -1],\r\n            [0, 0, 0, -1],\r\n            [0, 0, 1, -1],\r\n            [0, 0, -1, 0],\r\n            [0, 0, 1, 0],\r\n            [0, 0, -1, 1],\r\n            [0, 0, 0, 1],\r\n            [0, 0, 1, 1]\r\n        ];\r\n\r\n        Scale.prototype = {\r\n            init: function (editor) {\r\n                var me = this;\r\n                me.editor = editor;\r\n                me.startPos = this.prePos = {x: 0, y: 0};\r\n                me.dragId = -1;\r\n\r\n                var hands = [],\r\n                    cover = me.cover = document.createElement('div'),\r\n                    resizer = me.resizer = document.createElement('div');\r\n\r\n                cover.id = me.editor.ui.id + '_imagescale_cover';\r\n                cover.style.cssText = 'position:absolute;display:none;z-index:' + (me.editor.options.zIndex) + ';filter:alpha(opacity=0); opacity:0;background:#CCC;';\r\n                domUtils.on(cover, 'mousedown click', function () {\r\n                    me.hide();\r\n                });\r\n\r\n                for (i = 0; i < 8; i++) {\r\n                    hands.push('<span class=\"edui-editor-imagescale-hand' + i + '\"></span>');\r\n                }\r\n                resizer.id = me.editor.ui.id + '_imagescale';\r\n                resizer.className = 'edui-editor-imagescale';\r\n                resizer.innerHTML = hands.join('');\r\n                resizer.style.cssText += ';display:none;border:1px solid #3b77ff;z-index:' + (me.editor.options.zIndex) + ';';\r\n\r\n                me.editor.ui.getDom().appendChild(cover);\r\n                me.editor.ui.getDom().appendChild(resizer);\r\n\r\n                me.initStyle();\r\n                me.initEvents();\r\n            },\r\n            initStyle: function () {\r\n                utils.cssRule('imagescale', '.edui-editor-imagescale{display:none;position:absolute;border:1px solid #38B2CE;cursor:hand;-webkit-box-sizing: content-box;-moz-box-sizing: content-box;box-sizing: content-box;}' +\r\n                    '.edui-editor-imagescale span{position:absolute;width:6px;height:6px;overflow:hidden;font-size:0px;display:block;background-color:#3C9DD0;}'\r\n                    + '.edui-editor-imagescale .edui-editor-imagescale-hand0{cursor:nw-resize;top:0;margin-top:-4px;left:0;margin-left:-4px;}'\r\n                    + '.edui-editor-imagescale .edui-editor-imagescale-hand1{cursor:n-resize;top:0;margin-top:-4px;left:50%;margin-left:-4px;}'\r\n                    + '.edui-editor-imagescale .edui-editor-imagescale-hand2{cursor:ne-resize;top:0;margin-top:-4px;left:100%;margin-left:-3px;}'\r\n                    + '.edui-editor-imagescale .edui-editor-imagescale-hand3{cursor:w-resize;top:50%;margin-top:-4px;left:0;margin-left:-4px;}'\r\n                    + '.edui-editor-imagescale .edui-editor-imagescale-hand4{cursor:e-resize;top:50%;margin-top:-4px;left:100%;margin-left:-3px;}'\r\n                    + '.edui-editor-imagescale .edui-editor-imagescale-hand5{cursor:sw-resize;top:100%;margin-top:-3px;left:0;margin-left:-4px;}'\r\n                    + '.edui-editor-imagescale .edui-editor-imagescale-hand6{cursor:s-resize;top:100%;margin-top:-3px;left:50%;margin-left:-4px;}'\r\n                    + '.edui-editor-imagescale .edui-editor-imagescale-hand7{cursor:se-resize;top:100%;margin-top:-3px;left:100%;margin-left:-3px;}');\r\n            },\r\n            initEvents: function () {\r\n                var me = this;\r\n\r\n                me.startPos.x = me.startPos.y = 0;\r\n                me.isDraging = false;\r\n            },\r\n            _eventHandler: function (e) {\r\n                var me = this;\r\n                switch (e.type) {\r\n                    case 'mousedown':\r\n                        var hand = e.target || e.srcElement, hand;\r\n                        if (hand.className.indexOf('edui-editor-imagescale-hand') != -1 && me.dragId == -1) {\r\n                            me.dragId = hand.className.slice(-1);\r\n                            me.startPos.x = me.prePos.x = e.clientX;\r\n                            me.startPos.y = me.prePos.y = e.clientY;\r\n                            domUtils.on(me.doc,'mousemove', me.proxy(me._eventHandler, me));\r\n                        }\r\n                        break;\r\n                    case 'mousemove':\r\n                        if (me.dragId != -1) {\r\n                            me.updateContainerStyle(me.dragId, {x: e.clientX - me.prePos.x, y: e.clientY - me.prePos.y});\r\n                            me.prePos.x = e.clientX;\r\n                            me.prePos.y = e.clientY;\r\n                            elementUpdated = true;\r\n                            me.updateTargetElement();\r\n\r\n                        }\r\n                        break;\r\n                    case 'mouseup':\r\n                        if (me.dragId != -1) {\r\n                            me.updateContainerStyle(me.dragId, {x: e.clientX - me.prePos.x, y: e.clientY - me.prePos.y});\r\n                            me.updateTargetElement();\r\n                            if (me.target.parentNode) me.attachTo(me.target);\r\n                            me.dragId = -1;\r\n                        }\r\n                        domUtils.un(me.doc,'mousemove', me.proxy(me._eventHandler, me));\r\n                        //修复只是点击挪动点，但没有改变大小，不应该触发contentchange\r\n                        if(elementUpdated){\r\n                            elementUpdated = false;\r\n                            me.editor.fireEvent('contentchange');\r\n                        }\r\n\r\n                        break;\r\n                    default:\r\n                        break;\r\n                }\r\n            },\r\n            updateTargetElement: function () {\r\n                var me = this;\r\n                domUtils.setStyles(me.target, {\r\n                    'width': me.resizer.style.width,\r\n                    'height': me.resizer.style.height\r\n                });\r\n                me.target.width = parseInt(me.resizer.style.width);\r\n                me.target.height = parseInt(me.resizer.style.height);\r\n                me.attachTo(me.target);\r\n            },\r\n            updateContainerStyle: function (dir, offset) {\r\n                var me = this,\r\n                    dom = me.resizer, tmp;\r\n\r\n                if (rect[dir][0] != 0) {\r\n                    tmp = parseInt(dom.style.left) + offset.x;\r\n                    dom.style.left = me._validScaledProp('left', tmp) + 'px';\r\n                }\r\n                if (rect[dir][1] != 0) {\r\n                    tmp = parseInt(dom.style.top) + offset.y;\r\n                    dom.style.top = me._validScaledProp('top', tmp) + 'px';\r\n                }\r\n                if (rect[dir][2] != 0) {\r\n                    tmp = dom.clientWidth + rect[dir][2] * offset.x;\r\n                    dom.style.width = me._validScaledProp('width', tmp) + 'px';\r\n                }\r\n                if (rect[dir][3] != 0) {\r\n                    tmp = dom.clientHeight + rect[dir][3] * offset.y;\r\n                    dom.style.height = me._validScaledProp('height', tmp) + 'px';\r\n                }\r\n            },\r\n            _validScaledProp: function (prop, value) {\r\n                var ele = this.resizer,\r\n                    wrap = document;\r\n\r\n                value = isNaN(value) ? 0 : value;\r\n                switch (prop) {\r\n                    case 'left':\r\n                        return value < 0 ? 0 : (value + ele.clientWidth) > wrap.clientWidth ? wrap.clientWidth - ele.clientWidth : value;\r\n                    case 'top':\r\n                        return value < 0 ? 0 : (value + ele.clientHeight) > wrap.clientHeight ? wrap.clientHeight - ele.clientHeight : value;\r\n                    case 'width':\r\n                        return value <= 0 ? 1 : (value + ele.offsetLeft) > wrap.clientWidth ? wrap.clientWidth - ele.offsetLeft : value;\r\n                    case 'height':\r\n                        return value <= 0 ? 1 : (value + ele.offsetTop) > wrap.clientHeight ? wrap.clientHeight - ele.offsetTop : value;\r\n                }\r\n            },\r\n            hideCover: function () {\r\n                this.cover.style.display = 'none';\r\n            },\r\n            showCover: function () {\r\n                var me = this,\r\n                    editorPos = domUtils.getXY(me.editor.ui.getDom()),\r\n                    iframePos = domUtils.getXY(me.editor.iframe);\r\n\r\n                domUtils.setStyles(me.cover, {\r\n                    'width': me.editor.iframe.offsetWidth + 'px',\r\n                    'height': me.editor.iframe.offsetHeight + 'px',\r\n                    'top': iframePos.y - editorPos.y + 'px',\r\n                    'left': iframePos.x - editorPos.x + 'px',\r\n                    'position': 'absolute',\r\n                    'display': ''\r\n                })\r\n            },\r\n            show: function (targetObj) {\r\n                var me = this;\r\n                me.resizer.style.display = 'block';\r\n                if(targetObj) me.attachTo(targetObj);\r\n\r\n                domUtils.on(this.resizer, 'mousedown', me.proxy(me._eventHandler, me));\r\n                domUtils.on(me.doc, 'mouseup', me.proxy(me._eventHandler, me));\r\n\r\n                me.showCover();\r\n                me.editor.fireEvent('afterscaleshow', me);\r\n                me.editor.fireEvent('saveScene');\r\n            },\r\n            hide: function () {\r\n                var me = this;\r\n                me.hideCover();\r\n                me.resizer.style.display = 'none';\r\n\r\n                domUtils.un(me.resizer, 'mousedown', me.proxy(me._eventHandler, me));\r\n                domUtils.un(me.doc, 'mouseup', me.proxy(me._eventHandler, me));\r\n                me.editor.fireEvent('afterscalehide', me);\r\n            },\r\n            proxy: function( fn, context ) {\r\n                return function(e) {\r\n                    return fn.apply( context || this, arguments);\r\n                };\r\n            },\r\n            attachTo: function (targetObj) {\r\n                var me = this,\r\n                    target = me.target = targetObj,\r\n                    resizer = this.resizer,\r\n                    imgPos = domUtils.getXY(target),\r\n                    iframePos = domUtils.getXY(me.editor.iframe),\r\n                    editorPos = domUtils.getXY(resizer.parentNode);\r\n\r\n                domUtils.setStyles(resizer, {\r\n                    'width': target.width + 'px',\r\n                    'height': target.height + 'px',\r\n                    'left': iframePos.x + imgPos.x - me.editor.document.body.scrollLeft - editorPos.x - parseInt(resizer.style.borderLeftWidth) + 'px',\r\n                    'top': iframePos.y + imgPos.y - me.editor.document.body.scrollTop - editorPos.y - parseInt(resizer.style.borderTopWidth) + 'px'\r\n                });\r\n            }\r\n        }\r\n    })();\r\n\r\n    return function () {\r\n        var me = this,\r\n            imageScale;\r\n\r\n        me.setOpt('imageScaleEnabled', true);\r\n\r\n        if ( !browser.ie && me.options.imageScaleEnabled) {\r\n            me.addListener('click', function (type, e) {\r\n\r\n                var range = me.selection.getRange(),\r\n                    img = range.getClosedNode();\r\n\r\n                if (img && img.tagName == 'IMG' && me.body.contentEditable!=\"false\") {\r\n\r\n                    if (img.className.indexOf(\"edui-faked-music\") != -1 ||\r\n                        img.getAttribute(\"anchorname\") ||\r\n                        domUtils.hasClass(img, 'loadingclass') ||\r\n                        domUtils.hasClass(img, 'loaderrorclass')) { return }\r\n\r\n                    if (!imageScale) {\r\n                        imageScale = new Scale();\r\n                        imageScale.init(me);\r\n                        me.ui.getDom().appendChild(imageScale.resizer);\r\n\r\n                        var _keyDownHandler = function (e) {\r\n                            imageScale.hide();\r\n                            if(imageScale.target) me.selection.getRange().selectNode(imageScale.target).select();\r\n                        }, _mouseDownHandler = function (e) {\r\n                            var ele = e.target || e.srcElement;\r\n                            if (ele && (ele.className===undefined || ele.className.indexOf('edui-editor-imagescale') == -1)) {\r\n                                _keyDownHandler(e);\r\n                            }\r\n                        }, timer;\r\n\r\n                        me.addListener('afterscaleshow', function (e) {\r\n                            me.addListener('beforekeydown', _keyDownHandler);\r\n                            me.addListener('beforemousedown', _mouseDownHandler);\r\n                            domUtils.on(document, 'keydown', _keyDownHandler);\r\n                            domUtils.on(document,'mousedown', _mouseDownHandler);\r\n                            me.selection.getNative().removeAllRanges();\r\n                        });\r\n                        me.addListener('afterscalehide', function (e) {\r\n                            me.removeListener('beforekeydown', _keyDownHandler);\r\n                            me.removeListener('beforemousedown', _mouseDownHandler);\r\n                            domUtils.un(document, 'keydown', _keyDownHandler);\r\n                            domUtils.un(document,'mousedown', _mouseDownHandler);\r\n                            var target = imageScale.target;\r\n                            if (target.parentNode) {\r\n                                me.selection.getRange().selectNode(target).select();\r\n                            }\r\n                        });\r\n                        //TODO 有iframe的情况，mousedown不能往下传。。\r\n                        domUtils.on(imageScale.resizer, 'mousedown', function (e) {\r\n                            me.selection.getNative().removeAllRanges();\r\n                            var ele = e.target || e.srcElement;\r\n                            if (ele && ele.className.indexOf('edui-editor-imagescale-hand') == -1) {\r\n                                timer = setTimeout(function () {\r\n                                    imageScale.hide();\r\n                                    if(imageScale.target) me.selection.getRange().selectNode(ele).select();\r\n                                }, 200);\r\n                            }\r\n                        });\r\n                        domUtils.on(imageScale.resizer, 'mouseup', function (e) {\r\n                            var ele = e.target || e.srcElement;\r\n                            if (ele && ele.className.indexOf('edui-editor-imagescale-hand') == -1) {\r\n                                clearTimeout(timer);\r\n                            }\r\n                        });\r\n                    }\r\n                    imageScale.show(img);\r\n                } else {\r\n                    if (imageScale && imageScale.resizer.style.display != 'none') imageScale.hide();\r\n                }\r\n            });\r\n        }\r\n\r\n        if (browser.webkit) {\r\n            me.addListener('click', function (type, e) {\r\n                if (e.target.tagName == 'IMG' && me.body.contentEditable!=\"false\") {\r\n                    var range = new dom.Range(me.document);\r\n                    range.selectNode(e.target).select();\r\n                }\r\n            });\r\n        }\r\n    }\r\n})();\r\n\r\n// plugins/autolink.js\r\n///import core\r\n///commands 为非ie浏览器自动添加a标签\r\n///commandsName  AutoLink\r\n///commandsTitle  自动增加链接\r\n/**\r\n * @description 为非ie浏览器自动添加a标签\r\n * @author zhanyi\r\n */\r\n\r\nUE.plugin.register('autolink',function(){\r\n    var cont = 0;\r\n\r\n    return !browser.ie ? {\r\n\r\n            bindEvents:{\r\n                'reset' : function(){\r\n                    cont = 0;\r\n                },\r\n                'keydown':function(type, evt) {\r\n                    var me = this;\r\n                    var keyCode = evt.keyCode || evt.which;\r\n\r\n                    if (keyCode == 32 || keyCode == 13) {\r\n\r\n                        var sel = me.selection.getNative(),\r\n                            range = sel.getRangeAt(0).cloneRange(),\r\n                            offset,\r\n                            charCode;\r\n\r\n                        var start = range.startContainer;\r\n                        while (start.nodeType == 1 && range.startOffset > 0) {\r\n                            start = range.startContainer.childNodes[range.startOffset - 1];\r\n                            if (!start){\r\n                                break;\r\n                            }\r\n                            range.setStart(start, start.nodeType == 1 ? start.childNodes.length : start.nodeValue.length);\r\n                            range.collapse(true);\r\n                            start = range.startContainer;\r\n                        }\r\n\r\n                        do{\r\n                            if (range.startOffset == 0) {\r\n                                start = range.startContainer.previousSibling;\r\n\r\n                                while (start && start.nodeType == 1) {\r\n                                    start = start.lastChild;\r\n                                }\r\n                                if (!start || domUtils.isFillChar(start)){\r\n                                    break;\r\n                                }\r\n                                offset = start.nodeValue.length;\r\n                            } else {\r\n                                start = range.startContainer;\r\n                                offset = range.startOffset;\r\n                            }\r\n                            range.setStart(start, offset - 1);\r\n                            charCode = range.toString().charCodeAt(0);\r\n                        } while (charCode != 160 && charCode != 32);\r\n\r\n                        if (range.toString().replace(new RegExp(domUtils.fillChar, 'g'), '').match(/(?:https?:\\/\\/|ssh:\\/\\/|ftp:\\/\\/|file:\\/|www\\.)/i)) {\r\n                            while(range.toString().length){\r\n                                if(/^(?:https?:\\/\\/|ssh:\\/\\/|ftp:\\/\\/|file:\\/|www\\.)/i.test(range.toString())){\r\n                                    break;\r\n                                }\r\n                                try{\r\n                                    range.setStart(range.startContainer,range.startOffset+1);\r\n                                }catch(e){\r\n                                    //trace:2121\r\n                                    var start = range.startContainer;\r\n                                    while(!(next = start.nextSibling)){\r\n                                        if(domUtils.isBody(start)){\r\n                                            return;\r\n                                        }\r\n                                        start = start.parentNode;\r\n\r\n                                    }\r\n                                    range.setStart(next,0);\r\n\r\n                                }\r\n\r\n                            }\r\n                            //range的开始边界已经在a标签里的不再处理\r\n                            if(domUtils.findParentByTagName(range.startContainer,'a',true)){\r\n                                return;\r\n                            }\r\n                            var a = me.document.createElement('a'),text = me.document.createTextNode(' '),href;\r\n\r\n                            me.undoManger && me.undoManger.save();\r\n                            a.appendChild(range.extractContents());\r\n                            a.href = a.innerHTML = a.innerHTML.replace(/<[^>]+>/g,'');\r\n                            href = a.getAttribute(\"href\").replace(new RegExp(domUtils.fillChar,'g'),'');\r\n                            href = /^(?:https?:\\/\\/)/ig.test(href) ? href : \"http://\"+ href;\r\n                            a.setAttribute('_src',utils.html(href));\r\n                            a.href = utils.html(href);\r\n\r\n                            range.insertNode(a);\r\n                            a.parentNode.insertBefore(text, a.nextSibling);\r\n                            range.setStart(text, 0);\r\n                            range.collapse(true);\r\n                            sel.removeAllRanges();\r\n                            sel.addRange(range);\r\n                            me.undoManger && me.undoManger.save();\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n        }:{}\r\n    },function(){\r\n        var keyCodes = {\r\n            37:1, 38:1, 39:1, 40:1,\r\n            13:1,32:1\r\n        };\r\n        function checkIsCludeLink(node){\r\n            if(node.nodeType == 3){\r\n                return null\r\n            }\r\n            if(node.nodeName == 'A'){\r\n                return node;\r\n            }\r\n            var lastChild = node.lastChild;\r\n\r\n            while(lastChild){\r\n                if(lastChild.nodeName == 'A'){\r\n                    return lastChild;\r\n                }\r\n                if(lastChild.nodeType == 3){\r\n                    if(domUtils.isWhitespace(lastChild)){\r\n                        lastChild = lastChild.previousSibling;\r\n                        continue;\r\n                    }\r\n                    return null\r\n                }\r\n                lastChild = lastChild.lastChild;\r\n            }\r\n        }\r\n        browser.ie && this.addListener('keyup',function(cmd,evt){\r\n            var me = this,keyCode = evt.keyCode;\r\n            if(keyCodes[keyCode]){\r\n                var rng = me.selection.getRange();\r\n                var start = rng.startContainer;\r\n\r\n                if(keyCode == 13){\r\n                    while(start && !domUtils.isBody(start) && !domUtils.isBlockElm(start)){\r\n                        start = start.parentNode;\r\n                    }\r\n                    if(start && !domUtils.isBody(start) && start.nodeName == 'P'){\r\n                        var pre = start.previousSibling;\r\n                        if(pre && pre.nodeType == 1){\r\n                            var pre = checkIsCludeLink(pre);\r\n                            if(pre && !pre.getAttribute('_href')){\r\n                                domUtils.remove(pre,true);\r\n                            }\r\n                        }\r\n                    }\r\n                }else if(keyCode == 32 ){\r\n                    if(start.nodeType == 3 && /^\\s$/.test(start.nodeValue)){\r\n                        start = start.previousSibling;\r\n                        if(start && start.nodeName == 'A' && !start.getAttribute('_href')){\r\n                            domUtils.remove(start,true);\r\n                        }\r\n                    }\r\n                }else {\r\n                    start = domUtils.findParentByTagName(start,'a',true);\r\n                    if(start && !start.getAttribute('_href')){\r\n                        var bk = rng.createBookmark();\r\n\r\n                        domUtils.remove(start,true);\r\n                        rng.moveToBookmark(bk).select(true)\r\n                    }\r\n                }\r\n\r\n            }\r\n\r\n\r\n        });\r\n    }\r\n);\r\n\r\n// plugins/autoheight.js\r\n///import core\r\n///commands 当输入内容超过编辑器高度时，编辑器自动增高\r\n///commandsName  AutoHeight,autoHeightEnabled\r\n///commandsTitle  自动增高\r\n/**\r\n * @description 自动伸展\r\n * @author zhanyi\r\n */\r\nUE.plugins['autoheight'] = function () {\r\n    var me = this;\r\n    //提供开关，就算加载也可以关闭\r\n    me.autoHeightEnabled = me.options.autoHeightEnabled !== false;\r\n    if (!me.autoHeightEnabled) {\r\n        return;\r\n    }\r\n\r\n    var bakOverflow,\r\n        lastHeight = 0,\r\n        options = me.options,\r\n        currentHeight,\r\n        timer;\r\n\r\n    function adjustHeight() {\r\n        var me = this;\r\n        clearTimeout(timer);\r\n        if(isFullscreen)return;\r\n        if (!me.queryCommandState || me.queryCommandState && me.queryCommandState('source') != 1) {\r\n            timer = setTimeout(function(){\r\n\r\n                var node = me.body.lastChild;\r\n                while(node && node.nodeType != 1){\r\n                    node = node.previousSibling;\r\n                }\r\n                if(node && node.nodeType == 1){\r\n                    node.style.clear = 'both';\r\n                    currentHeight = Math.max(domUtils.getXY(node).y + node.offsetHeight + 25 ,Math.max(options.minFrameHeight, options.initialFrameHeight)) ;\r\n                    if (currentHeight != lastHeight) {\r\n                        if (currentHeight !== parseInt(me.iframe.parentNode.style.height)) {\r\n                            me.iframe.parentNode.style.height = currentHeight + 'px';\r\n                        }\r\n                        me.body.style.height = currentHeight + 'px';\r\n                        lastHeight = currentHeight;\r\n                    }\r\n                    domUtils.removeStyle(node,'clear');\r\n                }\r\n\r\n\r\n            },50)\r\n        }\r\n    }\r\n    var isFullscreen;\r\n    me.addListener('fullscreenchanged',function(cmd,f){\r\n        isFullscreen = f\r\n    });\r\n    me.addListener('destroy', function () {\r\n        me.removeListener('contentchange afterinserthtml keyup mouseup',adjustHeight)\r\n    });\r\n    me.enableAutoHeight = function () {\r\n        var me = this;\r\n        if (!me.autoHeightEnabled) {\r\n            return;\r\n        }\r\n        var doc = me.document;\r\n        me.autoHeightEnabled = true;\r\n        bakOverflow = doc.body.style.overflowY;\r\n        doc.body.style.overflowY = 'hidden';\r\n        me.addListener('contentchange afterinserthtml keyup mouseup',adjustHeight);\r\n        //ff不给事件算得不对\r\n\r\n        setTimeout(function () {\r\n            adjustHeight.call(me);\r\n        }, browser.gecko ? 100 : 0);\r\n        me.fireEvent('autoheightchanged', me.autoHeightEnabled);\r\n    };\r\n    me.disableAutoHeight = function () {\r\n\r\n        me.body.style.overflowY = bakOverflow || '';\r\n\r\n        me.removeListener('contentchange', adjustHeight);\r\n        me.removeListener('keyup', adjustHeight);\r\n        me.removeListener('mouseup', adjustHeight);\r\n        me.autoHeightEnabled = false;\r\n        me.fireEvent('autoheightchanged', me.autoHeightEnabled);\r\n    };\r\n\r\n    me.on('setHeight',function(){\r\n        me.disableAutoHeight()\r\n    });\r\n    me.addListener('ready', function () {\r\n        me.enableAutoHeight();\r\n        //trace:1764\r\n        var timer;\r\n        domUtils.on(browser.ie ? me.body : me.document, browser.webkit ? 'dragover' : 'drop', function () {\r\n            clearTimeout(timer);\r\n            timer = setTimeout(function () {\r\n                //trace:3681\r\n                adjustHeight.call(me);\r\n            }, 100);\r\n\r\n        });\r\n        //修复内容过多时，回到顶部，顶部内容被工具栏遮挡问题\r\n        var lastScrollY;\r\n        window.onscroll = function(){\r\n            if(lastScrollY === null){\r\n                lastScrollY = this.scrollY\r\n            }else if(this.scrollY == 0 && lastScrollY != 0){\r\n                me.window.scrollTo(0,0);\r\n                lastScrollY = null;\r\n            }\r\n        }\r\n    });\r\n\r\n\r\n};\r\n\r\n\r\n\r\n// plugins/autofloat.js\r\n///import core\r\n///commands 悬浮工具栏\r\n///commandsName  AutoFloat,autoFloatEnabled\r\n///commandsTitle  悬浮工具栏\r\n/**\r\n *  modified by chengchao01\r\n *  注意： 引入此功能后，在IE6下会将body的背景图片覆盖掉！\r\n */\r\nUE.plugins['autofloat'] = function() {\r\n    var me = this,\r\n        lang = me.getLang();\r\n    me.setOpt({\r\n        topOffset:0\r\n    });\r\n    var optsAutoFloatEnabled = me.options.autoFloatEnabled !== false,\r\n        topOffset = me.options.topOffset;\r\n\r\n\r\n    //如果不固定toolbar的位置，则直接退出\r\n    if(!optsAutoFloatEnabled){\r\n        return;\r\n    }\r\n    var uiUtils = UE.ui.uiUtils,\r\n        LteIE6 = browser.ie && browser.version <= 6,\r\n        quirks = browser.quirks;\r\n\r\n    function checkHasUI(){\r\n        if(!UE.ui){\r\n            alert(lang.autofloatMsg);\r\n            return 0;\r\n        }\r\n        return 1;\r\n    }\r\n    function fixIE6FixedPos(){\r\n        var docStyle = document.body.style;\r\n        docStyle.backgroundImage = 'url(\"about:blank\")';\r\n        docStyle.backgroundAttachment = 'fixed';\r\n    }\r\n    var\tbakCssText,\r\n        placeHolder = document.createElement('div'),\r\n        toolbarBox,orgTop,\r\n        getPosition,\r\n        flag =true;   //ie7模式下需要偏移\r\n    function setFloating(){\r\n        var toobarBoxPos = domUtils.getXY(toolbarBox),\r\n            origalFloat = domUtils.getComputedStyle(toolbarBox,'position'),\r\n            origalLeft = domUtils.getComputedStyle(toolbarBox,'left');\r\n        toolbarBox.style.width = toolbarBox.offsetWidth + 'px';\r\n        toolbarBox.style.zIndex = me.options.zIndex * 1 + 1;\r\n        toolbarBox.parentNode.insertBefore(placeHolder, toolbarBox);\r\n        if (LteIE6 || (quirks && browser.ie)) {\r\n            if(toolbarBox.style.position != 'absolute'){\r\n                toolbarBox.style.position = 'absolute';\r\n            }\r\n            toolbarBox.style.top = (document.body.scrollTop||document.documentElement.scrollTop) - orgTop + topOffset  + 'px';\r\n        } else {\r\n            if (browser.ie7Compat && flag) {\r\n                flag = false;\r\n                toolbarBox.style.left =  domUtils.getXY(toolbarBox).x - document.documentElement.getBoundingClientRect().left+2  + 'px';\r\n            }\r\n            if(toolbarBox.style.position != 'fixed'){\r\n                toolbarBox.style.position = 'fixed';\r\n                toolbarBox.style.top = topOffset +\"px\";\r\n                ((origalFloat == 'absolute' || origalFloat == 'relative') && parseFloat(origalLeft)) && (toolbarBox.style.left = toobarBoxPos.x + 'px');\r\n            }\r\n        }\r\n    }\r\n    function unsetFloating(){\r\n        flag = true;\r\n        if(placeHolder.parentNode){\r\n            placeHolder.parentNode.removeChild(placeHolder);\r\n        }\r\n\r\n        toolbarBox.style.cssText = bakCssText;\r\n    }\r\n\r\n    function updateFloating(){\r\n        var rect3 = getPosition(me.container);\r\n        var offset=me.options.toolbarTopOffset||0;\r\n        if (rect3.top < 0 && rect3.bottom - toolbarBox.offsetHeight > offset) {\r\n            setFloating();\r\n        }else{\r\n            unsetFloating();\r\n        }\r\n    }\r\n    var defer_updateFloating = utils.defer(function(){\r\n        updateFloating();\r\n    },browser.ie ? 200 : 100,true);\r\n\r\n    me.addListener('destroy',function(){\r\n        domUtils.un(window, ['scroll','resize'], updateFloating);\r\n        me.removeListener('keydown', defer_updateFloating);\r\n    });\r\n\r\n    me.addListener('ready', function(){\r\n        if(checkHasUI(me)){\r\n            //加载了ui组件，但在new时，没有加载ui，导致编辑器实例上没有ui类，所以这里做判断\r\n            if(!me.ui){\r\n                return;\r\n            }\r\n            getPosition = uiUtils.getClientRect;\r\n            toolbarBox = me.ui.getDom('toolbarbox');\r\n            orgTop = getPosition(toolbarBox).top;\r\n            bakCssText = toolbarBox.style.cssText;\r\n            placeHolder.style.height = toolbarBox.offsetHeight + 'px';\r\n            if(LteIE6){\r\n                fixIE6FixedPos();\r\n            }\r\n            domUtils.on(window, ['scroll','resize'], updateFloating);\r\n            me.addListener('keydown', defer_updateFloating);\r\n\r\n            me.addListener('beforefullscreenchange', function (t, enabled){\r\n                if (enabled) {\r\n                    unsetFloating();\r\n                }\r\n            });\r\n            me.addListener('fullscreenchanged', function (t, enabled){\r\n                if (!enabled) {\r\n                    updateFloating();\r\n                }\r\n            });\r\n            me.addListener('sourcemodechanged', function (t, enabled){\r\n                setTimeout(function (){\r\n                    updateFloating();\r\n                },0);\r\n            });\r\n            me.addListener(\"clearDoc\",function(){\r\n                setTimeout(function(){\r\n                    updateFloating();\r\n                },0);\r\n\r\n            })\r\n        }\r\n    });\r\n};\r\n\r\n\r\n// plugins/video.js\r\n/**\r\n * video插件， 为UEditor提供视频插入支持\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\nUE.plugins['video'] = function (){\r\n    var me =this;\r\n\r\n    /**\r\n     * 创建插入视频字符窜\r\n     * @param url 视频地址\r\n     * @param width 视频宽度\r\n     * @param height 视频高度\r\n     * @param align 视频对齐\r\n     * @param toEmbed 是否以flash代替显示\r\n     * @param addParagraph  是否需要添加P 标签\r\n     */\r\n    function creatInsertStr(url,width,height,id,align,classname,type){\r\n\r\n        url = utils.unhtmlForUrl(url);\r\n        align = utils.unhtml(align);\r\n        classname = utils.unhtml(classname).trim();\r\n\r\n        width = parseInt(width, 10) || 0;\r\n        height = parseInt(height, 10) || 0;\r\n\r\n        var str;\r\n        switch (type){\r\n            case 'image':\r\n                str = '<img ' + (id ? 'id=\"' + id+'\"' : '') + ' width=\"'+ width +'\" height=\"' + height + '\" _url=\"'+url+'\" class=\"' + classname.replace(/\\bvideo-js\\b/, '') + '\"'  +\r\n                    ' src=\"' + me.options.UEDITOR_HOME_URL+'themes/default/images/spacer.gif\" style=\"background:url('+me.options.UEDITOR_HOME_URL+'themes/default/images/videologo.gif) no-repeat center center; border:1px solid gray;'+(align ? 'float:' + align + ';': '')+'\" />'\r\n                break;\r\n            case 'embed':\r\n                str = '<embed type=\"application/x-shockwave-flash\" class=\"' + classname + '\" pluginspage=\"http://www.macromedia.com/go/getflashplayer\"' +\r\n                    ' src=\"' +  utils.html(url) + '\" width=\"' + width  + '\" height=\"' + height  + '\"'  + (align ? ' style=\"float:' + align + '\"': '') +\r\n                    ' wmode=\"transparent\" play=\"true\" loop=\"false\" menu=\"false\" allowscriptaccess=\"never\" allowfullscreen=\"true\" >';\r\n                break;\r\n            case 'video':\r\n                var ext = url.substr(url.lastIndexOf('.') + 1);\r\n                if(ext == 'ogv') ext = 'ogg';\r\n                str = '<video' + (id ? ' id=\"' + id + '\"' : '') + ' class=\"' + classname + ' video-js\" ' + (align ? ' style=\"float:' + align + '\"': '') +\r\n                    ' controls preload=\"none\" width=\"' + width + '\" height=\"' + height + '\" src=\"' + url + '\" data-setup=\"{}\">' +\r\n                    '<source src=\"' + url + '\" type=\"video/' + ext + '\" /></video>';\r\n                break;\r\n        }\r\n        return str;\r\n    }\r\n\r\n    function switchImgAndVideo(root,img2video){\r\n        utils.each(root.getNodesByTagName(img2video ? 'img' : 'embed video'),function(node){\r\n            var className = node.getAttr('class');\r\n            if(className && className.indexOf('edui-faked-video') != -1){\r\n                var html = creatInsertStr( img2video ? node.getAttr('_url') : node.getAttr('src'),node.getAttr('width'),node.getAttr('height'),null,node.getStyle('float') || '',className,img2video ? 'embed':'image');\r\n                node.parentNode.replaceChild(UE.uNode.createElement(html),node);\r\n            }\r\n            if(className && className.indexOf('edui-upload-video') != -1){\r\n                var html = creatInsertStr( img2video ? node.getAttr('_url') : node.getAttr('src'),node.getAttr('width'),node.getAttr('height'),null,node.getStyle('float') || '',className,img2video ? 'video':'image');\r\n                node.parentNode.replaceChild(UE.uNode.createElement(html),node);\r\n            }\r\n        })\r\n    }\r\n\r\n    me.addOutputRule(function(root){\r\n        switchImgAndVideo(root,true)\r\n    });\r\n    me.addInputRule(function(root){\r\n        switchImgAndVideo(root)\r\n    });\r\n\r\n    /**\r\n     * 插入视频\r\n     * @command insertvideo\r\n     * @method execCommand\r\n     * @param { String } cmd 命令字符串\r\n     * @param { Object } videoAttr 键值对对象， 描述一个视频的所有属性\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * var videoAttr = {\r\n     *      //视频地址\r\n     *      url: 'http://www.youku.com/xxx',\r\n     *      //视频宽高值， 单位px\r\n     *      width: 200,\r\n     *      height: 100\r\n     * };\r\n     *\r\n     * //editor 是编辑器实例\r\n     * //向编辑器插入单个视频\r\n     * editor.execCommand( 'insertvideo', videoAttr );\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 插入视频\r\n     * @command insertvideo\r\n     * @method execCommand\r\n     * @param { String } cmd 命令字符串\r\n     * @param { Array } videoArr 需要插入的视频的数组， 其中的每一个元素都是一个键值对对象， 描述了一个视频的所有属性\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * var videoAttr1 = {\r\n     *      //视频地址\r\n     *      url: 'http://www.youku.com/xxx',\r\n     *      //视频宽高值， 单位px\r\n     *      width: 200,\r\n     *      height: 100\r\n     * },\r\n     * videoAttr2 = {\r\n     *      //视频地址\r\n     *      url: 'http://www.youku.com/xxx',\r\n     *      //视频宽高值， 单位px\r\n     *      width: 200,\r\n     *      height: 100\r\n     * }\r\n     *\r\n     * //editor 是编辑器实例\r\n     * //该方法将会向编辑器内插入两个视频\r\n     * editor.execCommand( 'insertvideo', [ videoAttr1, videoAttr2 ] );\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 查询当前光标所在处是否是一个视频\r\n     * @command insertvideo\r\n     * @method queryCommandState\r\n     * @param { String } cmd 需要查询的命令字符串\r\n     * @return { int } 如果当前光标所在处的元素是一个视频对象， 则返回1，否则返回0\r\n     * @example\r\n     * ```javascript\r\n     *\r\n     * //editor 是编辑器实例\r\n     * editor.queryCommandState( 'insertvideo' );\r\n     * ```\r\n     */\r\n    me.commands[\"insertvideo\"] = {\r\n        execCommand: function (cmd, videoObjs, type){\r\n            videoObjs = utils.isArray(videoObjs)?videoObjs:[videoObjs];\r\n            var html = [],id = 'tmpVedio', cl;\r\n            for(var i=0,vi,len = videoObjs.length;i<len;i++){\r\n                vi = videoObjs[i];\r\n                cl = (type == 'upload' ? 'edui-upload-video video-js vjs-default-skin':'edui-faked-video');\r\n                html.push(creatInsertStr( vi.url, vi.width || 420,  vi.height || 280, id + i, null, cl, 'image'));\r\n            }\r\n            me.execCommand(\"inserthtml\",html.join(\"\"),true);\r\n            var rng = this.selection.getRange();\r\n            for(var i= 0,len=videoObjs.length;i<len;i++){\r\n                var img = this.document.getElementById('tmpVedio'+i);\r\n                domUtils.removeAttributes(img,'id');\r\n                rng.selectNode(img).select();\r\n                me.execCommand('imagefloat',videoObjs[i].align)\r\n            }\r\n        },\r\n        queryCommandState : function(){\r\n            var img = me.selection.getRange().getClosedNode(),\r\n                flag = img && (img.className == \"edui-faked-video\" || img.className.indexOf(\"edui-upload-video\")!=-1);\r\n            return flag ? 1 : 0;\r\n        }\r\n    };\r\n};\r\n\r\n\r\n// plugins/table.core.js\r\n/**\r\n * Created with JetBrains WebStorm.\r\n * User: taoqili\r\n * Date: 13-1-18\r\n * Time: 上午11:09\r\n * To change this template use File | Settings | File Templates.\r\n */\r\n/**\r\n * UE表格操作类\r\n * @param table\r\n * @constructor\r\n */\r\n(function () {\r\n    var UETable = UE.UETable = function (table) {\r\n        this.table = table;\r\n        this.indexTable = [];\r\n        this.selectedTds = [];\r\n        this.cellsRange = {};\r\n        this.update(table);\r\n    };\r\n\r\n    //===以下为静态工具方法===\r\n    UETable.removeSelectedClass = function (cells) {\r\n        utils.each(cells, function (cell) {\r\n            domUtils.removeClasses(cell, \"selectTdClass\");\r\n        })\r\n    };\r\n    UETable.addSelectedClass = function (cells) {\r\n        utils.each(cells, function (cell) {\r\n            domUtils.addClass(cell, \"selectTdClass\");\r\n        })\r\n    };\r\n    UETable.isEmptyBlock = function (node) {\r\n        var reg = new RegExp(domUtils.fillChar, 'g');\r\n        if (node[browser.ie ? 'innerText' : 'textContent'].replace(/^\\s*$/, '').replace(reg, '').length > 0) {\r\n            return 0;\r\n        }\r\n        for (var i in dtd.$isNotEmpty) if (dtd.$isNotEmpty.hasOwnProperty(i)) {\r\n            if (node.getElementsByTagName(i).length) {\r\n                return 0;\r\n            }\r\n        }\r\n        return 1;\r\n    };\r\n    UETable.getWidth = function (cell) {\r\n        if (!cell)return 0;\r\n        return parseInt(domUtils.getComputedStyle(cell, \"width\"), 10);\r\n    };\r\n\r\n    /**\r\n     * 获取单元格或者单元格组的“对齐”状态。 如果当前的检测对象是一个单元格组， 只有在满足所有单元格的 水平和竖直 对齐属性都相同的\r\n     * 条件时才会返回其状态值，否则将返回null； 如果当前只检测了一个单元格， 则直接返回当前单元格的对齐状态；\r\n     * @param table cell or table cells , 支持单个单元格dom对象 或者 单元格dom对象数组\r\n     * @return { align: 'left' || 'right' || 'center', valign: 'top' || 'middle' || 'bottom' } 或者 null\r\n     */\r\n    UETable.getTableCellAlignState = function ( cells ) {\r\n\r\n        !utils.isArray( cells ) && ( cells = [cells] );\r\n\r\n        var result = {},\r\n            status = ['align', 'valign'],\r\n            tempStatus = null,\r\n            isSame = true;//状态是否相同\r\n\r\n        utils.each( cells, function( cellNode ){\r\n\r\n            utils.each( status, function( currentState ){\r\n\r\n                tempStatus = cellNode.getAttribute( currentState );\r\n\r\n                if( !result[ currentState ] && tempStatus ) {\r\n                    result[ currentState ] = tempStatus;\r\n                } else if( !result[ currentState ] || ( tempStatus !== result[ currentState ] ) ) {\r\n                    isSame = false;\r\n                    return false;\r\n                }\r\n\r\n            } );\r\n\r\n            return isSame;\r\n\r\n        });\r\n\r\n        return isSame ? result : null;\r\n\r\n    };\r\n\r\n    /**\r\n     * 根据当前选区获取相关的table信息\r\n     * @return {Object}\r\n     */\r\n    UETable.getTableItemsByRange = function (editor) {\r\n        var start = editor.selection.getStart();\r\n\r\n        //ff下会选中bookmark\r\n        if( start && start.id && start.id.indexOf('_baidu_bookmark_start_') === 0 && start.nextSibling) {\r\n            start = start.nextSibling;\r\n        }\r\n\r\n        //在table或者td边缘有可能存在选中tr的情况\r\n        var cell = start && domUtils.findParentByTagName(start, [\"td\", \"th\"], true),\r\n            tr = cell && cell.parentNode,\r\n            caption = start && domUtils.findParentByTagName(start, 'caption', true),\r\n            table = caption ? caption.parentNode : tr && tr.parentNode.parentNode;\r\n\r\n        return {\r\n            cell:cell,\r\n            tr:tr,\r\n            table:table,\r\n            caption:caption\r\n        }\r\n    };\r\n    UETable.getUETableBySelected = function (editor) {\r\n        var table = UETable.getTableItemsByRange(editor).table;\r\n        if (table && table.ueTable && table.ueTable.selectedTds.length) {\r\n            return table.ueTable;\r\n        }\r\n        return null;\r\n    };\r\n\r\n    UETable.getDefaultValue = function (editor, table) {\r\n        var borderMap = {\r\n                thin:'0px',\r\n                medium:'1px',\r\n                thick:'2px'\r\n            },\r\n            tableBorder, tdPadding, tdBorder, tmpValue;\r\n        if (!table) {\r\n            table = editor.document.createElement('table');\r\n            table.insertRow(0).insertCell(0).innerHTML = 'xxx';\r\n            editor.body.appendChild(table);\r\n            var td = table.getElementsByTagName('td')[0];\r\n            tmpValue = domUtils.getComputedStyle(table, 'border-left-width');\r\n            tableBorder = parseInt(borderMap[tmpValue] || tmpValue, 10);\r\n            tmpValue = domUtils.getComputedStyle(td, 'padding-left');\r\n            tdPadding = parseInt(borderMap[tmpValue] || tmpValue, 10);\r\n            tmpValue = domUtils.getComputedStyle(td, 'border-left-width');\r\n            tdBorder = parseInt(borderMap[tmpValue] || tmpValue, 10);\r\n            domUtils.remove(table);\r\n            return {\r\n                tableBorder:tableBorder,\r\n                tdPadding:tdPadding,\r\n                tdBorder:tdBorder\r\n            };\r\n        } else {\r\n            td = table.getElementsByTagName('td')[0];\r\n            tmpValue = domUtils.getComputedStyle(table, 'border-left-width');\r\n            tableBorder = parseInt(borderMap[tmpValue] || tmpValue, 10);\r\n            tmpValue = domUtils.getComputedStyle(td, 'padding-left');\r\n            tdPadding = parseInt(borderMap[tmpValue] || tmpValue, 10);\r\n            tmpValue = domUtils.getComputedStyle(td, 'border-left-width');\r\n            tdBorder = parseInt(borderMap[tmpValue] || tmpValue, 10);\r\n            return {\r\n                tableBorder:tableBorder,\r\n                tdPadding:tdPadding,\r\n                tdBorder:tdBorder\r\n            };\r\n        }\r\n    };\r\n    /**\r\n     * 根据当前点击的td或者table获取索引对象\r\n     * @param tdOrTable\r\n     */\r\n    UETable.getUETable = function (tdOrTable) {\r\n        var tag = tdOrTable.tagName.toLowerCase();\r\n        tdOrTable = (tag == \"td\" || tag == \"th\" || tag == 'caption') ? domUtils.findParentByTagName(tdOrTable, \"table\", true) : tdOrTable;\r\n        if (!tdOrTable.ueTable) {\r\n            tdOrTable.ueTable = new UETable(tdOrTable);\r\n        }\r\n        return tdOrTable.ueTable;\r\n    };\r\n\r\n    UETable.cloneCell = function(cell,ignoreMerge,keepPro){\r\n        if (!cell || utils.isString(cell)) {\r\n            return this.table.ownerDocument.createElement(cell || 'td');\r\n        }\r\n        var flag = domUtils.hasClass(cell, \"selectTdClass\");\r\n        flag && domUtils.removeClasses(cell, \"selectTdClass\");\r\n        var tmpCell = cell.cloneNode(true);\r\n        if (ignoreMerge) {\r\n            tmpCell.rowSpan = tmpCell.colSpan = 1;\r\n        }\r\n        //去掉宽高\r\n        !keepPro && domUtils.removeAttributes(tmpCell,'width height');\r\n        !keepPro && domUtils.removeAttributes(tmpCell,'style');\r\n\r\n        tmpCell.style.borderLeftStyle = \"\";\r\n        tmpCell.style.borderTopStyle = \"\";\r\n        tmpCell.style.borderLeftColor = cell.style.borderRightColor;\r\n        tmpCell.style.borderLeftWidth = cell.style.borderRightWidth;\r\n        tmpCell.style.borderTopColor = cell.style.borderBottomColor;\r\n        tmpCell.style.borderTopWidth = cell.style.borderBottomWidth;\r\n        flag && domUtils.addClass(cell, \"selectTdClass\");\r\n        return tmpCell;\r\n    }\r\n\r\n    UETable.prototype = {\r\n        getMaxRows:function () {\r\n            var rows = this.table.rows, maxLen = 1;\r\n            for (var i = 0, row; row = rows[i]; i++) {\r\n                var currentMax = 1;\r\n                for (var j = 0, cj; cj = row.cells[j++];) {\r\n                    currentMax = Math.max(cj.rowSpan || 1, currentMax);\r\n                }\r\n                maxLen = Math.max(currentMax + i, maxLen);\r\n            }\r\n            return maxLen;\r\n        },\r\n        /**\r\n         * 获取当前表格的最大列数\r\n         */\r\n        getMaxCols:function () {\r\n            var rows = this.table.rows, maxLen = 0, cellRows = {};\r\n            for (var i = 0, row; row = rows[i]; i++) {\r\n                var cellsNum = 0;\r\n                for (var j = 0, cj; cj = row.cells[j++];) {\r\n                    cellsNum += (cj.colSpan || 1);\r\n                    if (cj.rowSpan && cj.rowSpan > 1) {\r\n                        for (var k = 1; k < cj.rowSpan; k++) {\r\n                            if (!cellRows['row_' + (i + k)]) {\r\n                                cellRows['row_' + (i + k)] = (cj.colSpan || 1);\r\n                            } else {\r\n                                cellRows['row_' + (i + k)]++\r\n                            }\r\n                        }\r\n\r\n                    }\r\n                }\r\n                cellsNum += cellRows['row_' + i] || 0;\r\n                maxLen = Math.max(cellsNum, maxLen);\r\n            }\r\n            return maxLen;\r\n        },\r\n        getCellColIndex:function (cell) {\r\n\r\n        },\r\n        /**\r\n         * 获取当前cell旁边的单元格，\r\n         * @param cell\r\n         * @param right\r\n         */\r\n        getHSideCell:function (cell, right) {\r\n            try {\r\n                var cellInfo = this.getCellInfo(cell),\r\n                    previewRowIndex, previewColIndex;\r\n                var len = this.selectedTds.length,\r\n                    range = this.cellsRange;\r\n                //首行或者首列没有前置单元格\r\n                if ((!right && (!len ? !cellInfo.colIndex : !range.beginColIndex)) || (right && (!len ? (cellInfo.colIndex == (this.colsNum - 1)) : (range.endColIndex == this.colsNum - 1)))) return null;\r\n\r\n                previewRowIndex = !len ? cellInfo.rowIndex : range.beginRowIndex;\r\n                previewColIndex = !right ? ( !len ? (cellInfo.colIndex < 1 ? 0 : (cellInfo.colIndex - 1)) : range.beginColIndex - 1)\r\n                    : ( !len ? cellInfo.colIndex + 1 : range.endColIndex + 1);\r\n                return this.getCell(this.indexTable[previewRowIndex][previewColIndex].rowIndex, this.indexTable[previewRowIndex][previewColIndex].cellIndex);\r\n            } catch (e) {\r\n                showError(e);\r\n            }\r\n        },\r\n        getTabNextCell:function (cell, preRowIndex) {\r\n            var cellInfo = this.getCellInfo(cell),\r\n                rowIndex = preRowIndex || cellInfo.rowIndex,\r\n                colIndex = cellInfo.colIndex + 1 + (cellInfo.colSpan - 1),\r\n                nextCell;\r\n            try {\r\n                nextCell = this.getCell(this.indexTable[rowIndex][colIndex].rowIndex, this.indexTable[rowIndex][colIndex].cellIndex);\r\n            } catch (e) {\r\n                try {\r\n                    rowIndex = rowIndex * 1 + 1;\r\n                    colIndex = 0;\r\n                    nextCell = this.getCell(this.indexTable[rowIndex][colIndex].rowIndex, this.indexTable[rowIndex][colIndex].cellIndex);\r\n                } catch (e) {\r\n                }\r\n            }\r\n            return nextCell;\r\n\r\n        },\r\n        /**\r\n         * 获取视觉上的后置单元格\r\n         * @param cell\r\n         * @param bottom\r\n         */\r\n        getVSideCell:function (cell, bottom, ignoreRange) {\r\n            try {\r\n                var cellInfo = this.getCellInfo(cell),\r\n                    nextRowIndex, nextColIndex;\r\n                var len = this.selectedTds.length && !ignoreRange,\r\n                    range = this.cellsRange;\r\n                //末行或者末列没有后置单元格\r\n                if ((!bottom && (cellInfo.rowIndex == 0)) || (bottom && (!len ? (cellInfo.rowIndex + cellInfo.rowSpan > this.rowsNum - 1) : (range.endRowIndex == this.rowsNum - 1)))) return null;\r\n\r\n                nextRowIndex = !bottom ? ( !len ? cellInfo.rowIndex - 1 : range.beginRowIndex - 1)\r\n                    : ( !len ? (cellInfo.rowIndex + cellInfo.rowSpan) : range.endRowIndex + 1);\r\n                nextColIndex = !len ? cellInfo.colIndex : range.beginColIndex;\r\n                return this.getCell(this.indexTable[nextRowIndex][nextColIndex].rowIndex, this.indexTable[nextRowIndex][nextColIndex].cellIndex);\r\n            } catch (e) {\r\n                showError(e);\r\n            }\r\n        },\r\n        /**\r\n         * 获取相同结束位置的单元格，xOrY指代了是获取x轴相同还是y轴相同\r\n         */\r\n        getSameEndPosCells:function (cell, xOrY) {\r\n            try {\r\n                var flag = (xOrY.toLowerCase() === \"x\"),\r\n                    end = domUtils.getXY(cell)[flag ? 'x' : 'y'] + cell[\"offset\" + (flag ? 'Width' : 'Height')],\r\n                    rows = this.table.rows,\r\n                    cells = null, returns = [];\r\n                for (var i = 0; i < this.rowsNum; i++) {\r\n                    cells = rows[i].cells;\r\n                    for (var j = 0, tmpCell; tmpCell = cells[j++];) {\r\n                        var tmpEnd = domUtils.getXY(tmpCell)[flag ? 'x' : 'y'] + tmpCell[\"offset\" + (flag ? 'Width' : 'Height')];\r\n                        //对应行的td已经被上面行rowSpan了\r\n                        if (tmpEnd > end && flag) break;\r\n                        if (cell == tmpCell || end == tmpEnd) {\r\n                            //只获取单一的单元格\r\n                            //todo 仅获取单一单元格在特定情况下会造成returns为空，从而影响后续的拖拽实现，修正这个。需考虑性能\r\n                            if (tmpCell[flag ? \"colSpan\" : \"rowSpan\"] == 1) {\r\n                                returns.push(tmpCell);\r\n                            }\r\n                            if (flag) break;\r\n                        }\r\n                    }\r\n                }\r\n                return returns;\r\n            } catch (e) {\r\n                showError(e);\r\n            }\r\n        },\r\n        setCellContent:function (cell, content) {\r\n            cell.innerHTML = content || (browser.ie ? domUtils.fillChar : \"<br />\");\r\n        },\r\n        cloneCell:UETable.cloneCell,\r\n        /**\r\n         * 获取跟当前单元格的右边竖线为左边的所有未合并单元格\r\n         */\r\n        getSameStartPosXCells:function (cell) {\r\n            try {\r\n                var start = domUtils.getXY(cell).x + cell.offsetWidth,\r\n                    rows = this.table.rows, cells , returns = [];\r\n                for (var i = 0; i < this.rowsNum; i++) {\r\n                    cells = rows[i].cells;\r\n                    for (var j = 0, tmpCell; tmpCell = cells[j++];) {\r\n                        var tmpStart = domUtils.getXY(tmpCell).x;\r\n                        if (tmpStart > start) break;\r\n                        if (tmpStart == start && tmpCell.colSpan == 1) {\r\n                            returns.push(tmpCell);\r\n                            break;\r\n                        }\r\n                    }\r\n                }\r\n                return returns;\r\n            } catch (e) {\r\n                showError(e);\r\n            }\r\n        },\r\n        /**\r\n         * 更新table对应的索引表\r\n         */\r\n        update:function (table) {\r\n            this.table = table || this.table;\r\n            this.selectedTds = [];\r\n            this.cellsRange = {};\r\n            this.indexTable = [];\r\n            var rows = this.table.rows,\r\n                rowsNum = this.getMaxRows(),\r\n                dNum = rowsNum - rows.length,\r\n                colsNum = this.getMaxCols();\r\n            while (dNum--) {\r\n                this.table.insertRow(rows.length);\r\n            }\r\n            this.rowsNum = rowsNum;\r\n            this.colsNum = colsNum;\r\n            for (var i = 0, len = rows.length; i < len; i++) {\r\n                this.indexTable[i] = new Array(colsNum);\r\n            }\r\n            //填充索引表\r\n            for (var rowIndex = 0, row; row = rows[rowIndex]; rowIndex++) {\r\n                for (var cellIndex = 0, cell, cells = row.cells; cell = cells[cellIndex]; cellIndex++) {\r\n                    //修正整行被rowSpan时导致的行数计算错误\r\n                    if (cell.rowSpan > rowsNum) {\r\n                        cell.rowSpan = rowsNum;\r\n                    }\r\n                    var colIndex = cellIndex,\r\n                        rowSpan = cell.rowSpan || 1,\r\n                        colSpan = cell.colSpan || 1;\r\n                    //当已经被上一行rowSpan或者被前一列colSpan了，则跳到下一个单元格进行\r\n                    while (this.indexTable[rowIndex][colIndex]) colIndex++;\r\n                    for (var j = 0; j < rowSpan; j++) {\r\n                        for (var k = 0; k < colSpan; k++) {\r\n                            this.indexTable[rowIndex + j][colIndex + k] = {\r\n                                rowIndex:rowIndex,\r\n                                cellIndex:cellIndex,\r\n                                colIndex:colIndex,\r\n                                rowSpan:rowSpan,\r\n                                colSpan:colSpan\r\n                            }\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n            //修复残缺td\r\n            for (j = 0; j < rowsNum; j++) {\r\n                for (k = 0; k < colsNum; k++) {\r\n                    if (this.indexTable[j][k] === undefined) {\r\n                        row = rows[j];\r\n                        cell = row.cells[row.cells.length - 1];\r\n                        cell = cell ? cell.cloneNode(true) : this.table.ownerDocument.createElement(\"td\");\r\n                        this.setCellContent(cell);\r\n                        if (cell.colSpan !== 1)cell.colSpan = 1;\r\n                        if (cell.rowSpan !== 1)cell.rowSpan = 1;\r\n                        row.appendChild(cell);\r\n                        this.indexTable[j][k] = {\r\n                            rowIndex:j,\r\n                            cellIndex:cell.cellIndex,\r\n                            colIndex:k,\r\n                            rowSpan:1,\r\n                            colSpan:1\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n            //当框选后删除行或者列后撤销，需要重建选区。\r\n            var tds = domUtils.getElementsByTagName(this.table, \"td\"),\r\n                selectTds = [];\r\n            utils.each(tds, function (td) {\r\n                if (domUtils.hasClass(td, \"selectTdClass\")) {\r\n                    selectTds.push(td);\r\n                }\r\n            });\r\n            if (selectTds.length) {\r\n                var start = selectTds[0],\r\n                    end = selectTds[selectTds.length - 1],\r\n                    startInfo = this.getCellInfo(start),\r\n                    endInfo = this.getCellInfo(end);\r\n                this.selectedTds = selectTds;\r\n                this.cellsRange = {\r\n                    beginRowIndex:startInfo.rowIndex,\r\n                    beginColIndex:startInfo.colIndex,\r\n                    endRowIndex:endInfo.rowIndex + endInfo.rowSpan - 1,\r\n                    endColIndex:endInfo.colIndex + endInfo.colSpan - 1\r\n                };\r\n            }\r\n            //给第一行设置firstRow的样式名称,在排序图标的样式上使用到\r\n            if(!domUtils.hasClass(this.table.rows[0], \"firstRow\")) {\r\n                domUtils.addClass(this.table.rows[0], \"firstRow\");\r\n                for(var i = 1; i< this.table.rows.length; i++) {\r\n                    domUtils.removeClasses(this.table.rows[i], \"firstRow\");\r\n                }\r\n            }\r\n        },\r\n        /**\r\n         * 获取单元格的索引信息\r\n         */\r\n        getCellInfo:function (cell) {\r\n            if (!cell) return;\r\n            var cellIndex = cell.cellIndex,\r\n                rowIndex = cell.parentNode.rowIndex,\r\n                rowInfo = this.indexTable[rowIndex],\r\n                numCols = this.colsNum;\r\n            for (var colIndex = cellIndex; colIndex < numCols; colIndex++) {\r\n                var cellInfo = rowInfo[colIndex];\r\n                if (cellInfo.rowIndex === rowIndex && cellInfo.cellIndex === cellIndex) {\r\n                    return cellInfo;\r\n                }\r\n            }\r\n        },\r\n        /**\r\n         * 根据行列号获取单元格\r\n         */\r\n        getCell:function (rowIndex, cellIndex) {\r\n            return rowIndex < this.rowsNum && this.table.rows[rowIndex].cells[cellIndex] || null;\r\n        },\r\n        /**\r\n         * 删除单元格\r\n         */\r\n        deleteCell:function (cell, rowIndex) {\r\n            rowIndex = typeof rowIndex == 'number' ? rowIndex : cell.parentNode.rowIndex;\r\n            var row = this.table.rows[rowIndex];\r\n            row.deleteCell(cell.cellIndex);\r\n        },\r\n        /**\r\n         * 根据始末两个单元格获取被框选的所有单元格范围\r\n         */\r\n        getCellsRange:function (cellA, cellB) {\r\n            function checkRange(beginRowIndex, beginColIndex, endRowIndex, endColIndex) {\r\n                var tmpBeginRowIndex = beginRowIndex,\r\n                    tmpBeginColIndex = beginColIndex,\r\n                    tmpEndRowIndex = endRowIndex,\r\n                    tmpEndColIndex = endColIndex,\r\n                    cellInfo, colIndex, rowIndex;\r\n                // 通过indexTable检查是否存在超出TableRange上边界的情况\r\n                if (beginRowIndex > 0) {\r\n                    for (colIndex = beginColIndex; colIndex < endColIndex; colIndex++) {\r\n                        cellInfo = me.indexTable[beginRowIndex][colIndex];\r\n                        rowIndex = cellInfo.rowIndex;\r\n                        if (rowIndex < beginRowIndex) {\r\n                            tmpBeginRowIndex = Math.min(rowIndex, tmpBeginRowIndex);\r\n                        }\r\n                    }\r\n                }\r\n                // 通过indexTable检查是否存在超出TableRange右边界的情况\r\n                if (endColIndex < me.colsNum) {\r\n                    for (rowIndex = beginRowIndex; rowIndex < endRowIndex; rowIndex++) {\r\n                        cellInfo = me.indexTable[rowIndex][endColIndex];\r\n                        colIndex = cellInfo.colIndex + cellInfo.colSpan - 1;\r\n                        if (colIndex > endColIndex) {\r\n                            tmpEndColIndex = Math.max(colIndex, tmpEndColIndex);\r\n                        }\r\n                    }\r\n                }\r\n                // 检查是否有超出TableRange下边界的情况\r\n                if (endRowIndex < me.rowsNum) {\r\n                    for (colIndex = beginColIndex; colIndex < endColIndex; colIndex++) {\r\n                        cellInfo = me.indexTable[endRowIndex][colIndex];\r\n                        rowIndex = cellInfo.rowIndex + cellInfo.rowSpan - 1;\r\n                        if (rowIndex > endRowIndex) {\r\n                            tmpEndRowIndex = Math.max(rowIndex, tmpEndRowIndex);\r\n                        }\r\n                    }\r\n                }\r\n                // 检查是否有超出TableRange左边界的情况\r\n                if (beginColIndex > 0) {\r\n                    for (rowIndex = beginRowIndex; rowIndex < endRowIndex; rowIndex++) {\r\n                        cellInfo = me.indexTable[rowIndex][beginColIndex];\r\n                        colIndex = cellInfo.colIndex;\r\n                        if (colIndex < beginColIndex) {\r\n                            tmpBeginColIndex = Math.min(cellInfo.colIndex, tmpBeginColIndex);\r\n                        }\r\n                    }\r\n                }\r\n                //递归调用直至所有完成所有框选单元格的扩展\r\n                if (tmpBeginRowIndex != beginRowIndex || tmpBeginColIndex != beginColIndex || tmpEndRowIndex != endRowIndex || tmpEndColIndex != endColIndex) {\r\n                    return checkRange(tmpBeginRowIndex, tmpBeginColIndex, tmpEndRowIndex, tmpEndColIndex);\r\n                } else {\r\n                    // 不需要扩展TableRange的情况\r\n                    return {\r\n                        beginRowIndex:beginRowIndex,\r\n                        beginColIndex:beginColIndex,\r\n                        endRowIndex:endRowIndex,\r\n                        endColIndex:endColIndex\r\n                    };\r\n                }\r\n            }\r\n\r\n            try {\r\n                var me = this,\r\n                    cellAInfo = me.getCellInfo(cellA);\r\n                if (cellA === cellB) {\r\n                    return {\r\n                        beginRowIndex:cellAInfo.rowIndex,\r\n                        beginColIndex:cellAInfo.colIndex,\r\n                        endRowIndex:cellAInfo.rowIndex + cellAInfo.rowSpan - 1,\r\n                        endColIndex:cellAInfo.colIndex + cellAInfo.colSpan - 1\r\n                    };\r\n                }\r\n                var cellBInfo = me.getCellInfo(cellB);\r\n                // 计算TableRange的四个边\r\n                var beginRowIndex = Math.min(cellAInfo.rowIndex, cellBInfo.rowIndex),\r\n                    beginColIndex = Math.min(cellAInfo.colIndex, cellBInfo.colIndex),\r\n                    endRowIndex = Math.max(cellAInfo.rowIndex + cellAInfo.rowSpan - 1, cellBInfo.rowIndex + cellBInfo.rowSpan - 1),\r\n                    endColIndex = Math.max(cellAInfo.colIndex + cellAInfo.colSpan - 1, cellBInfo.colIndex + cellBInfo.colSpan - 1);\r\n\r\n                return checkRange(beginRowIndex, beginColIndex, endRowIndex, endColIndex);\r\n            } catch (e) {\r\n                //throw e;\r\n            }\r\n        },\r\n        /**\r\n         * 依据cellsRange获取对应的单元格集合\r\n         */\r\n        getCells:function (range) {\r\n            //每次获取cells之前必须先清除上次的选择，否则会对后续获取操作造成影响\r\n            this.clearSelected();\r\n            var beginRowIndex = range.beginRowIndex,\r\n                beginColIndex = range.beginColIndex,\r\n                endRowIndex = range.endRowIndex,\r\n                endColIndex = range.endColIndex,\r\n                cellInfo, rowIndex, colIndex, tdHash = {}, returnTds = [];\r\n            for (var i = beginRowIndex; i <= endRowIndex; i++) {\r\n                for (var j = beginColIndex; j <= endColIndex; j++) {\r\n                    cellInfo = this.indexTable[i][j];\r\n                    rowIndex = cellInfo.rowIndex;\r\n                    colIndex = cellInfo.colIndex;\r\n                    // 如果Cells里已经包含了此Cell则跳过\r\n                    var key = rowIndex + '|' + colIndex;\r\n                    if (tdHash[key]) continue;\r\n                    tdHash[key] = 1;\r\n                    if (rowIndex < i || colIndex < j || rowIndex + cellInfo.rowSpan - 1 > endRowIndex || colIndex + cellInfo.colSpan - 1 > endColIndex) {\r\n                        return null;\r\n                    }\r\n                    returnTds.push(this.getCell(rowIndex, cellInfo.cellIndex));\r\n                }\r\n            }\r\n            return returnTds;\r\n        },\r\n        /**\r\n         * 清理已经选中的单元格\r\n         */\r\n        clearSelected:function () {\r\n            UETable.removeSelectedClass(this.selectedTds);\r\n            this.selectedTds = [];\r\n            this.cellsRange = {};\r\n        },\r\n        /**\r\n         * 根据range设置已经选中的单元格\r\n         */\r\n        setSelected:function (range) {\r\n            var cells = this.getCells(range);\r\n            UETable.addSelectedClass(cells);\r\n            this.selectedTds = cells;\r\n            this.cellsRange = range;\r\n        },\r\n        isFullRow:function () {\r\n            var range = this.cellsRange;\r\n            return (range.endColIndex - range.beginColIndex + 1) == this.colsNum;\r\n        },\r\n        isFullCol:function () {\r\n            var range = this.cellsRange,\r\n                table = this.table,\r\n                ths = table.getElementsByTagName(\"th\"),\r\n                rows = range.endRowIndex - range.beginRowIndex + 1;\r\n            return  !ths.length ? rows == this.rowsNum : rows == this.rowsNum || (rows == this.rowsNum - 1);\r\n\r\n        },\r\n        /**\r\n         * 获取视觉上的前置单元格，默认是左边，top传入时\r\n         * @param cell\r\n         * @param top\r\n         */\r\n        getNextCell:function (cell, bottom, ignoreRange) {\r\n            try {\r\n                var cellInfo = this.getCellInfo(cell),\r\n                    nextRowIndex, nextColIndex;\r\n                var len = this.selectedTds.length && !ignoreRange,\r\n                    range = this.cellsRange;\r\n                //末行或者末列没有后置单元格\r\n                if ((!bottom && (cellInfo.rowIndex == 0)) || (bottom && (!len ? (cellInfo.rowIndex + cellInfo.rowSpan > this.rowsNum - 1) : (range.endRowIndex == this.rowsNum - 1)))) return null;\r\n\r\n                nextRowIndex = !bottom ? ( !len ? cellInfo.rowIndex - 1 : range.beginRowIndex - 1)\r\n                    : ( !len ? (cellInfo.rowIndex + cellInfo.rowSpan) : range.endRowIndex + 1);\r\n                nextColIndex = !len ? cellInfo.colIndex : range.beginColIndex;\r\n                return this.getCell(this.indexTable[nextRowIndex][nextColIndex].rowIndex, this.indexTable[nextRowIndex][nextColIndex].cellIndex);\r\n            } catch (e) {\r\n                showError(e);\r\n            }\r\n        },\r\n        getPreviewCell:function (cell, top) {\r\n            try {\r\n                var cellInfo = this.getCellInfo(cell),\r\n                    previewRowIndex, previewColIndex;\r\n                var len = this.selectedTds.length,\r\n                    range = this.cellsRange;\r\n                //首行或者首列没有前置单元格\r\n                if ((!top && (!len ? !cellInfo.colIndex : !range.beginColIndex)) || (top && (!len ? (cellInfo.rowIndex > (this.colsNum - 1)) : (range.endColIndex == this.colsNum - 1)))) return null;\r\n\r\n                previewRowIndex = !top ? ( !len ? cellInfo.rowIndex : range.beginRowIndex )\r\n                    : ( !len ? (cellInfo.rowIndex < 1 ? 0 : (cellInfo.rowIndex - 1)) : range.beginRowIndex);\r\n                previewColIndex = !top ? ( !len ? (cellInfo.colIndex < 1 ? 0 : (cellInfo.colIndex - 1)) : range.beginColIndex - 1)\r\n                    : ( !len ? cellInfo.colIndex : range.endColIndex + 1);\r\n                return this.getCell(this.indexTable[previewRowIndex][previewColIndex].rowIndex, this.indexTable[previewRowIndex][previewColIndex].cellIndex);\r\n            } catch (e) {\r\n                showError(e);\r\n            }\r\n        },\r\n        /**\r\n         * 移动单元格中的内容\r\n         */\r\n        moveContent:function (cellTo, cellFrom) {\r\n            if (UETable.isEmptyBlock(cellFrom)) return;\r\n            if (UETable.isEmptyBlock(cellTo)) {\r\n                cellTo.innerHTML = cellFrom.innerHTML;\r\n                return;\r\n            }\r\n            var child = cellTo.lastChild;\r\n            if (child.nodeType == 3 || !dtd.$block[child.tagName]) {\r\n                cellTo.appendChild(cellTo.ownerDocument.createElement('br'))\r\n            }\r\n            while (child = cellFrom.firstChild) {\r\n                cellTo.appendChild(child);\r\n            }\r\n        },\r\n        /**\r\n         * 向右合并单元格\r\n         */\r\n        mergeRight:function (cell) {\r\n            var cellInfo = this.getCellInfo(cell),\r\n                rightColIndex = cellInfo.colIndex + cellInfo.colSpan,\r\n                rightCellInfo = this.indexTable[cellInfo.rowIndex][rightColIndex],\r\n                rightCell = this.getCell(rightCellInfo.rowIndex, rightCellInfo.cellIndex);\r\n            //合并\r\n            cell.colSpan = cellInfo.colSpan + rightCellInfo.colSpan;\r\n            //被合并的单元格不应存在宽度属性\r\n            cell.removeAttribute(\"width\");\r\n            //移动内容\r\n            this.moveContent(cell, rightCell);\r\n            //删掉被合并的Cell\r\n            this.deleteCell(rightCell, rightCellInfo.rowIndex);\r\n            this.update();\r\n        },\r\n        /**\r\n         * 向下合并单元格\r\n         */\r\n        mergeDown:function (cell) {\r\n            var cellInfo = this.getCellInfo(cell),\r\n                downRowIndex = cellInfo.rowIndex + cellInfo.rowSpan,\r\n                downCellInfo = this.indexTable[downRowIndex][cellInfo.colIndex],\r\n                downCell = this.getCell(downCellInfo.rowIndex, downCellInfo.cellIndex);\r\n            cell.rowSpan = cellInfo.rowSpan + downCellInfo.rowSpan;\r\n            cell.removeAttribute(\"height\");\r\n            this.moveContent(cell, downCell);\r\n            this.deleteCell(downCell, downCellInfo.rowIndex);\r\n            this.update();\r\n        },\r\n        /**\r\n         * 合并整个range中的内容\r\n         */\r\n        mergeRange:function () {\r\n            //由于合并操作可以在任意时刻进行，所以无法通过鼠标位置等信息实时生成range，只能通过缓存实例中的cellsRange对象来访问\r\n            var range = this.cellsRange,\r\n                leftTopCell = this.getCell(range.beginRowIndex, this.indexTable[range.beginRowIndex][range.beginColIndex].cellIndex);\r\n\r\n            if (leftTopCell.tagName == \"TH\" && range.endRowIndex !== range.beginRowIndex) {\r\n                var index = this.indexTable,\r\n                    info = this.getCellInfo(leftTopCell);\r\n                leftTopCell = this.getCell(1, index[1][info.colIndex].cellIndex);\r\n                range = this.getCellsRange(leftTopCell, this.getCell(index[this.rowsNum - 1][info.colIndex].rowIndex, index[this.rowsNum - 1][info.colIndex].cellIndex));\r\n            }\r\n\r\n            // 删除剩余的Cells\r\n            var cells = this.getCells(range);\r\n            for(var i= 0,ci;ci=cells[i++];){\r\n                if (ci !== leftTopCell) {\r\n                    this.moveContent(leftTopCell, ci);\r\n                    this.deleteCell(ci);\r\n                }\r\n            }\r\n            // 修改左上角Cell的rowSpan和colSpan，并调整宽度属性设置\r\n            leftTopCell.rowSpan = range.endRowIndex - range.beginRowIndex + 1;\r\n            leftTopCell.rowSpan > 1 && leftTopCell.removeAttribute(\"height\");\r\n            leftTopCell.colSpan = range.endColIndex - range.beginColIndex + 1;\r\n            leftTopCell.colSpan > 1 && leftTopCell.removeAttribute(\"width\");\r\n            if (leftTopCell.rowSpan == this.rowsNum && leftTopCell.colSpan != 1) {\r\n                leftTopCell.colSpan = 1;\r\n            }\r\n\r\n            if (leftTopCell.colSpan == this.colsNum && leftTopCell.rowSpan != 1) {\r\n                var rowIndex = leftTopCell.parentNode.rowIndex;\r\n                //解决IE下的表格操作问题\r\n                if( this.table.deleteRow ) {\r\n                    for (var i = rowIndex+ 1, curIndex=rowIndex+ 1, len=leftTopCell.rowSpan; i < len; i++) {\r\n                        this.table.deleteRow(curIndex);\r\n                    }\r\n                } else {\r\n                    for (var i = 0, len=leftTopCell.rowSpan - 1; i < len; i++) {\r\n                        var row = this.table.rows[rowIndex + 1];\r\n                        row.parentNode.removeChild(row);\r\n                    }\r\n                }\r\n                leftTopCell.rowSpan = 1;\r\n            }\r\n            this.update();\r\n        },\r\n        /**\r\n         * 插入一行单元格\r\n         */\r\n        insertRow:function (rowIndex, sourceCell) {\r\n            var numCols = this.colsNum,\r\n                table = this.table,\r\n                row = table.insertRow(rowIndex), cell,\r\n                isInsertTitle = typeof sourceCell == 'string' && sourceCell.toUpperCase() == 'TH';\r\n\r\n            function replaceTdToTh(colIndex, cell, tableRow) {\r\n                if (colIndex == 0) {\r\n                    var tr = tableRow.nextSibling || tableRow.previousSibling,\r\n                        th = tr.cells[colIndex];\r\n                    if (th.tagName == 'TH') {\r\n                        th = cell.ownerDocument.createElement(\"th\");\r\n                        th.appendChild(cell.firstChild);\r\n                        tableRow.insertBefore(th, cell);\r\n                        domUtils.remove(cell)\r\n                    }\r\n                }else{\r\n                    if (cell.tagName == 'TH') {\r\n                        var td = cell.ownerDocument.createElement(\"td\");\r\n                        td.appendChild(cell.firstChild);\r\n                        tableRow.insertBefore(td, cell);\r\n                        domUtils.remove(cell)\r\n                    }\r\n                }\r\n            }\r\n\r\n            //首行直接插入,无需考虑部分单元格被rowspan的情况\r\n            if (rowIndex == 0 || rowIndex == this.rowsNum) {\r\n                for (var colIndex = 0; colIndex < numCols; colIndex++) {\r\n                    cell = this.cloneCell(sourceCell, true);\r\n                    this.setCellContent(cell);\r\n                    cell.getAttribute('vAlign') && cell.setAttribute('vAlign', cell.getAttribute('vAlign'));\r\n                    row.appendChild(cell);\r\n                    if(!isInsertTitle) replaceTdToTh(colIndex, cell, row);\r\n                }\r\n            } else {\r\n                var infoRow = this.indexTable[rowIndex],\r\n                    cellIndex = 0;\r\n                for (colIndex = 0; colIndex < numCols; colIndex++) {\r\n                    var cellInfo = infoRow[colIndex];\r\n                    //如果存在某个单元格的rowspan穿过待插入行的位置，则修改该单元格的rowspan即可，无需插入单元格\r\n                    if (cellInfo.rowIndex < rowIndex) {\r\n                        cell = this.getCell(cellInfo.rowIndex, cellInfo.cellIndex);\r\n                        cell.rowSpan = cellInfo.rowSpan + 1;\r\n                    } else {\r\n                        cell = this.cloneCell(sourceCell, true);\r\n                        this.setCellContent(cell);\r\n                        row.appendChild(cell);\r\n                    }\r\n                    if(!isInsertTitle) replaceTdToTh(colIndex, cell, row);\r\n                }\r\n            }\r\n            //框选时插入不触发contentchange，需要手动更新索引。\r\n            this.update();\r\n            return row;\r\n        },\r\n        /**\r\n         * 删除一行单元格\r\n         * @param rowIndex\r\n         */\r\n        deleteRow:function (rowIndex) {\r\n            var row = this.table.rows[rowIndex],\r\n                infoRow = this.indexTable[rowIndex],\r\n                colsNum = this.colsNum,\r\n                count = 0;     //处理计数\r\n            for (var colIndex = 0; colIndex < colsNum;) {\r\n                var cellInfo = infoRow[colIndex],\r\n                    cell = this.getCell(cellInfo.rowIndex, cellInfo.cellIndex);\r\n                if (cell.rowSpan > 1) {\r\n                    if (cellInfo.rowIndex == rowIndex) {\r\n                        var clone = cell.cloneNode(true);\r\n                        clone.rowSpan = cell.rowSpan - 1;\r\n                        clone.innerHTML = \"\";\r\n                        cell.rowSpan = 1;\r\n                        var nextRowIndex = rowIndex + 1,\r\n                            nextRow = this.table.rows[nextRowIndex],\r\n                            insertCellIndex,\r\n                            preMerged = this.getPreviewMergedCellsNum(nextRowIndex, colIndex) - count;\r\n                        if (preMerged < colIndex) {\r\n                            insertCellIndex = colIndex - preMerged - 1;\r\n                            //nextRow.insertCell(insertCellIndex);\r\n                            domUtils.insertAfter(nextRow.cells[insertCellIndex], clone);\r\n                        } else {\r\n                            if (nextRow.cells.length) nextRow.insertBefore(clone, nextRow.cells[0])\r\n                        }\r\n                        count += 1;\r\n                        //cell.parentNode.removeChild(cell);\r\n                    }\r\n                }\r\n                colIndex += cell.colSpan || 1;\r\n            }\r\n            var deleteTds = [], cacheMap = {};\r\n            for (colIndex = 0; colIndex < colsNum; colIndex++) {\r\n                var tmpRowIndex = infoRow[colIndex].rowIndex,\r\n                    tmpCellIndex = infoRow[colIndex].cellIndex,\r\n                    key = tmpRowIndex + \"_\" + tmpCellIndex;\r\n                if (cacheMap[key])continue;\r\n                cacheMap[key] = 1;\r\n                cell = this.getCell(tmpRowIndex, tmpCellIndex);\r\n                deleteTds.push(cell);\r\n            }\r\n            var mergeTds = [];\r\n            utils.each(deleteTds, function (td) {\r\n                if (td.rowSpan == 1) {\r\n                    td.parentNode.removeChild(td);\r\n                } else {\r\n                    mergeTds.push(td);\r\n                }\r\n            });\r\n            utils.each(mergeTds, function (td) {\r\n                td.rowSpan--;\r\n            });\r\n            row.parentNode.removeChild(row);\r\n            //浏览器方法本身存在bug,采用自定义方法删除\r\n            //this.table.deleteRow(rowIndex);\r\n            this.update();\r\n        },\r\n        insertCol:function (colIndex, sourceCell, defaultValue) {\r\n            var rowsNum = this.rowsNum,\r\n                rowIndex = 0,\r\n                tableRow, cell,\r\n                backWidth = parseInt((this.table.offsetWidth - (this.colsNum + 1) * 20 - (this.colsNum + 1)) / (this.colsNum + 1), 10),\r\n                isInsertTitleCol = typeof sourceCell == 'string' && sourceCell.toUpperCase() == 'TH';\r\n\r\n            function replaceTdToTh(rowIndex, cell, tableRow) {\r\n                if (rowIndex == 0) {\r\n                    var th = cell.nextSibling || cell.previousSibling;\r\n                    if (th.tagName == 'TH') {\r\n                        th = cell.ownerDocument.createElement(\"th\");\r\n                        th.appendChild(cell.firstChild);\r\n                        tableRow.insertBefore(th, cell);\r\n                        domUtils.remove(cell)\r\n                    }\r\n                }else{\r\n                    if (cell.tagName == 'TH') {\r\n                        var td = cell.ownerDocument.createElement(\"td\");\r\n                        td.appendChild(cell.firstChild);\r\n                        tableRow.insertBefore(td, cell);\r\n                        domUtils.remove(cell)\r\n                    }\r\n                }\r\n            }\r\n\r\n            var preCell;\r\n            if (colIndex == 0 || colIndex == this.colsNum) {\r\n                for (; rowIndex < rowsNum; rowIndex++) {\r\n                    tableRow = this.table.rows[rowIndex];\r\n                    preCell = tableRow.cells[colIndex == 0 ? colIndex : tableRow.cells.length];\r\n                    cell = this.cloneCell(sourceCell, true); //tableRow.insertCell(colIndex == 0 ? colIndex : tableRow.cells.length);\r\n                    this.setCellContent(cell);\r\n                    cell.setAttribute('vAlign', cell.getAttribute('vAlign'));\r\n                    preCell && cell.setAttribute('width', preCell.getAttribute('width'));\r\n                    if (!colIndex) {\r\n                        tableRow.insertBefore(cell, tableRow.cells[0]);\r\n                    } else {\r\n                        domUtils.insertAfter(tableRow.cells[tableRow.cells.length - 1], cell);\r\n                    }\r\n                    if(!isInsertTitleCol) replaceTdToTh(rowIndex, cell, tableRow)\r\n                }\r\n            } else {\r\n                for (; rowIndex < rowsNum; rowIndex++) {\r\n                    var cellInfo = this.indexTable[rowIndex][colIndex];\r\n                    if (cellInfo.colIndex < colIndex) {\r\n                        cell = this.getCell(cellInfo.rowIndex, cellInfo.cellIndex);\r\n                        cell.colSpan = cellInfo.colSpan + 1;\r\n                    } else {\r\n                        tableRow = this.table.rows[rowIndex];\r\n                        preCell = tableRow.cells[cellInfo.cellIndex];\r\n\r\n                        cell = this.cloneCell(sourceCell, true);//tableRow.insertCell(cellInfo.cellIndex);\r\n                        this.setCellContent(cell);\r\n                        cell.setAttribute('vAlign', cell.getAttribute('vAlign'));\r\n                        preCell && cell.setAttribute('width', preCell.getAttribute('width'));\r\n                        //防止IE下报错\r\n                        preCell ? tableRow.insertBefore(cell, preCell) : tableRow.appendChild(cell);\r\n                    }\r\n                    if(!isInsertTitleCol) replaceTdToTh(rowIndex, cell, tableRow);\r\n                }\r\n            }\r\n            //框选时插入不触发contentchange，需要手动更新索引\r\n            this.update();\r\n            this.updateWidth(backWidth, defaultValue || {tdPadding:10, tdBorder:1});\r\n        },\r\n        updateWidth:function (width, defaultValue) {\r\n            var table = this.table,\r\n                tmpWidth = UETable.getWidth(table) - defaultValue.tdPadding * 2 - defaultValue.tdBorder + width;\r\n            if (tmpWidth < table.ownerDocument.body.offsetWidth) {\r\n                table.setAttribute(\"width\", tmpWidth);\r\n                return;\r\n            }\r\n            var tds = domUtils.getElementsByTagName(this.table, \"td th\");\r\n            utils.each(tds, function (td) {\r\n                td.setAttribute(\"width\", width);\r\n            })\r\n        },\r\n        deleteCol:function (colIndex) {\r\n            var indexTable = this.indexTable,\r\n                tableRows = this.table.rows,\r\n                backTableWidth = this.table.getAttribute(\"width\"),\r\n                backTdWidth = 0,\r\n                rowsNum = this.rowsNum,\r\n                cacheMap = {};\r\n            for (var rowIndex = 0; rowIndex < rowsNum;) {\r\n                var infoRow = indexTable[rowIndex],\r\n                    cellInfo = infoRow[colIndex],\r\n                    key = cellInfo.rowIndex + '_' + cellInfo.colIndex;\r\n                // 跳过已经处理过的Cell\r\n                if (cacheMap[key])continue;\r\n                cacheMap[key] = 1;\r\n                var cell = this.getCell(cellInfo.rowIndex, cellInfo.cellIndex);\r\n                if (!backTdWidth) backTdWidth = cell && parseInt(cell.offsetWidth / cell.colSpan, 10).toFixed(0);\r\n                // 如果Cell的colSpan大于1, 就修改colSpan, 否则就删掉这个Cell\r\n                if (cell.colSpan > 1) {\r\n                    cell.colSpan--;\r\n                } else {\r\n                    tableRows[rowIndex].deleteCell(cellInfo.cellIndex);\r\n                }\r\n                rowIndex += cellInfo.rowSpan || 1;\r\n            }\r\n            this.table.setAttribute(\"width\", backTableWidth - backTdWidth);\r\n            this.update();\r\n        },\r\n        splitToCells:function (cell) {\r\n            var me = this,\r\n                cells = this.splitToRows(cell);\r\n            utils.each(cells, function (cell) {\r\n                me.splitToCols(cell);\r\n            })\r\n        },\r\n        splitToRows:function (cell) {\r\n            var cellInfo = this.getCellInfo(cell),\r\n                rowIndex = cellInfo.rowIndex,\r\n                colIndex = cellInfo.colIndex,\r\n                results = [];\r\n            // 修改Cell的rowSpan\r\n            cell.rowSpan = 1;\r\n            results.push(cell);\r\n            // 补齐单元格\r\n            for (var i = rowIndex, endRow = rowIndex + cellInfo.rowSpan; i < endRow; i++) {\r\n                if (i == rowIndex)continue;\r\n                var tableRow = this.table.rows[i],\r\n                    tmpCell = tableRow.insertCell(colIndex - this.getPreviewMergedCellsNum(i, colIndex));\r\n                tmpCell.colSpan = cellInfo.colSpan;\r\n                this.setCellContent(tmpCell);\r\n                tmpCell.setAttribute('vAlign', cell.getAttribute('vAlign'));\r\n                tmpCell.setAttribute('align', cell.getAttribute('align'));\r\n                if (cell.style.cssText) {\r\n                    tmpCell.style.cssText = cell.style.cssText;\r\n                }\r\n                results.push(tmpCell);\r\n            }\r\n            this.update();\r\n            return results;\r\n        },\r\n        getPreviewMergedCellsNum:function (rowIndex, colIndex) {\r\n            var indexRow = this.indexTable[rowIndex],\r\n                num = 0;\r\n            for (var i = 0; i < colIndex;) {\r\n                var colSpan = indexRow[i].colSpan,\r\n                    tmpRowIndex = indexRow[i].rowIndex;\r\n                num += (colSpan - (tmpRowIndex == rowIndex ? 1 : 0));\r\n                i += colSpan;\r\n            }\r\n            return num;\r\n        },\r\n        splitToCols:function (cell) {\r\n            var backWidth = (cell.offsetWidth / cell.colSpan - 22).toFixed(0),\r\n\r\n                cellInfo = this.getCellInfo(cell),\r\n                rowIndex = cellInfo.rowIndex,\r\n                colIndex = cellInfo.colIndex,\r\n                results = [];\r\n            // 修改Cell的rowSpan\r\n            cell.colSpan = 1;\r\n            cell.setAttribute(\"width\", backWidth);\r\n            results.push(cell);\r\n            // 补齐单元格\r\n            for (var j = colIndex, endCol = colIndex + cellInfo.colSpan; j < endCol; j++) {\r\n                if (j == colIndex)continue;\r\n                var tableRow = this.table.rows[rowIndex],\r\n                    tmpCell = tableRow.insertCell(this.indexTable[rowIndex][j].cellIndex + 1);\r\n                tmpCell.rowSpan = cellInfo.rowSpan;\r\n                this.setCellContent(tmpCell);\r\n                tmpCell.setAttribute('vAlign', cell.getAttribute('vAlign'));\r\n                tmpCell.setAttribute('align', cell.getAttribute('align'));\r\n                tmpCell.setAttribute('width', backWidth);\r\n                if (cell.style.cssText) {\r\n                    tmpCell.style.cssText = cell.style.cssText;\r\n                }\r\n                //处理th的情况\r\n                if (cell.tagName == 'TH') {\r\n                    var th = cell.ownerDocument.createElement('th');\r\n                    th.appendChild(tmpCell.firstChild);\r\n                    th.setAttribute('vAlign', cell.getAttribute('vAlign'));\r\n                    th.rowSpan = tmpCell.rowSpan;\r\n                    tableRow.insertBefore(th, tmpCell);\r\n                    domUtils.remove(tmpCell);\r\n                }\r\n                results.push(tmpCell);\r\n            }\r\n            this.update();\r\n            return results;\r\n        },\r\n        isLastCell:function (cell, rowsNum, colsNum) {\r\n            rowsNum = rowsNum || this.rowsNum;\r\n            colsNum = colsNum || this.colsNum;\r\n            var cellInfo = this.getCellInfo(cell);\r\n            return ((cellInfo.rowIndex + cellInfo.rowSpan) == rowsNum) &&\r\n                ((cellInfo.colIndex + cellInfo.colSpan) == colsNum);\r\n        },\r\n        getLastCell:function (cells) {\r\n            cells = cells || this.table.getElementsByTagName(\"td\");\r\n            var firstInfo = this.getCellInfo(cells[0]);\r\n            var me = this, last = cells[0],\r\n                tr = last.parentNode,\r\n                cellsNum = 0, cols = 0, rows;\r\n            utils.each(cells, function (cell) {\r\n                if (cell.parentNode == tr)cols += cell.colSpan || 1;\r\n                cellsNum += cell.rowSpan * cell.colSpan || 1;\r\n            });\r\n            rows = cellsNum / cols;\r\n            utils.each(cells, function (cell) {\r\n                if (me.isLastCell(cell, rows, cols)) {\r\n                    last = cell;\r\n                    return false;\r\n                }\r\n            });\r\n            return last;\r\n\r\n        },\r\n        selectRow:function (rowIndex) {\r\n            var indexRow = this.indexTable[rowIndex],\r\n                start = this.getCell(indexRow[0].rowIndex, indexRow[0].cellIndex),\r\n                end = this.getCell(indexRow[this.colsNum - 1].rowIndex, indexRow[this.colsNum - 1].cellIndex),\r\n                range = this.getCellsRange(start, end);\r\n            this.setSelected(range);\r\n        },\r\n        selectTable:function () {\r\n            var tds = this.table.getElementsByTagName(\"td\"),\r\n                range = this.getCellsRange(tds[0], tds[tds.length - 1]);\r\n            this.setSelected(range);\r\n        },\r\n        setBackground:function (cells, value) {\r\n            if (typeof value === \"string\") {\r\n                utils.each(cells, function (cell) {\r\n                    cell.style.backgroundColor = value;\r\n                })\r\n            } else if (typeof value === \"object\") {\r\n                value = utils.extend({\r\n                    repeat:true,\r\n                    colorList:[\"#ddd\", \"#fff\"]\r\n                }, value);\r\n                var rowIndex = this.getCellInfo(cells[0]).rowIndex,\r\n                    count = 0,\r\n                    colors = value.colorList,\r\n                    getColor = function (list, index, repeat) {\r\n                        return list[index] ? list[index] : repeat ? list[index % list.length] : \"\";\r\n                    };\r\n                for (var i = 0, cell; cell = cells[i++];) {\r\n                    var cellInfo = this.getCellInfo(cell);\r\n                    cell.style.backgroundColor = getColor(colors, ((rowIndex + count) == cellInfo.rowIndex) ? count : ++count, value.repeat);\r\n                }\r\n            }\r\n        },\r\n        removeBackground:function (cells) {\r\n            utils.each(cells, function (cell) {\r\n                cell.style.backgroundColor = \"\";\r\n            })\r\n        }\r\n\r\n\r\n    };\r\n    function showError(e) {\r\n    }\r\n})();\r\n\r\n// plugins/table.cmds.js\r\n/**\r\n * Created with JetBrains PhpStorm.\r\n * User: taoqili\r\n * Date: 13-2-20\r\n * Time: 下午6:25\r\n * To change this template use File | Settings | File Templates.\r\n */\r\n;\r\n(function () {\r\n    var UT = UE.UETable,\r\n        getTableItemsByRange = function (editor) {\r\n            return UT.getTableItemsByRange(editor);\r\n        },\r\n        getUETableBySelected = function (editor) {\r\n            return UT.getUETableBySelected(editor)\r\n        },\r\n        getDefaultValue = function (editor, table) {\r\n            return UT.getDefaultValue(editor, table);\r\n        },\r\n        getUETable = function (tdOrTable) {\r\n            return UT.getUETable(tdOrTable);\r\n        };\r\n\r\n\r\n    UE.commands['inserttable'] = {\r\n        queryCommandState: function () {\r\n            return getTableItemsByRange(this).table ? -1 : 0;\r\n        },\r\n        execCommand: function (cmd, opt) {\r\n            function createTable(opt, tdWidth) {\r\n                var html = [],\r\n                    rowsNum = opt.numRows,\r\n                    colsNum = opt.numCols;\r\n                for (var r = 0; r < rowsNum; r++) {\r\n                    html.push('<tr' + (r == 0 ? ' class=\"firstRow\"':'') + '>');\r\n                    for (var c = 0; c < colsNum; c++) {\r\n                        html.push('<td width=\"' + tdWidth + '\"  vAlign=\"' + opt.tdvalign + '\" >' + (browser.ie && browser.version < 11 ? domUtils.fillChar : '<br/>') + '</td>')\r\n                    }\r\n                    html.push('</tr>')\r\n                }\r\n                //禁止指定table-width\r\n                return '<table><tbody>' + html.join('') + '</tbody></table>'\r\n            }\r\n\r\n            if (!opt) {\r\n                opt = utils.extend({}, {\r\n                    numCols: this.options.defaultCols,\r\n                    numRows: this.options.defaultRows,\r\n                    tdvalign: this.options.tdvalign\r\n                })\r\n            }\r\n            var me = this;\r\n            var range = this.selection.getRange(),\r\n                start = range.startContainer,\r\n                firstParentBlock = domUtils.findParent(start, function (node) {\r\n                    return domUtils.isBlockElm(node);\r\n                }, true) || me.body;\r\n\r\n            var defaultValue = getDefaultValue(me),\r\n                tableWidth = firstParentBlock.offsetWidth,\r\n                tdWidth = Math.floor(tableWidth / opt.numCols - defaultValue.tdPadding * 2 - defaultValue.tdBorder);\r\n\r\n            //todo其他属性\r\n            !opt.tdvalign && (opt.tdvalign = me.options.tdvalign);\r\n            me.execCommand(\"inserthtml\", createTable(opt, tdWidth));\r\n        }\r\n    };\r\n\r\n    UE.commands['insertparagraphbeforetable'] = {\r\n        queryCommandState: function () {\r\n            return getTableItemsByRange(this).cell ? 0 : -1;\r\n        },\r\n        execCommand: function () {\r\n            var table = getTableItemsByRange(this).table;\r\n            if (table) {\r\n                var p = this.document.createElement(\"p\");\r\n                p.innerHTML = browser.ie ? '&nbsp;' : '<br />';\r\n                table.parentNode.insertBefore(p, table);\r\n                this.selection.getRange().setStart(p, 0).setCursor();\r\n            }\r\n        }\r\n    };\r\n\r\n    UE.commands['deletetable'] = {\r\n        queryCommandState: function () {\r\n            var rng = this.selection.getRange();\r\n            return domUtils.findParentByTagName(rng.startContainer, 'table', true) ? 0 : -1;\r\n        },\r\n        execCommand: function (cmd, table) {\r\n            var rng = this.selection.getRange();\r\n            table = table || domUtils.findParentByTagName(rng.startContainer, 'table', true);\r\n            if (table) {\r\n                var next = table.nextSibling;\r\n                if (!next) {\r\n                    next = domUtils.createElement(this.document, 'p', {\r\n                        'innerHTML': browser.ie ? domUtils.fillChar : '<br/>'\r\n                    });\r\n                    table.parentNode.insertBefore(next, table);\r\n                }\r\n                domUtils.remove(table);\r\n                rng = this.selection.getRange();\r\n                if (next.nodeType == 3) {\r\n                    rng.setStartBefore(next)\r\n                } else {\r\n                    rng.setStart(next, 0)\r\n                }\r\n                rng.setCursor(false, true)\r\n                this.fireEvent(\"tablehasdeleted\")\r\n\r\n            }\r\n\r\n        }\r\n    };\r\n    UE.commands['cellalign'] = {\r\n        queryCommandState: function () {\r\n            return getSelectedArr(this).length ? 0 : -1\r\n        },\r\n        execCommand: function (cmd, align) {\r\n            var selectedTds = getSelectedArr(this);\r\n            if (selectedTds.length) {\r\n                for (var i = 0, ci; ci = selectedTds[i++];) {\r\n                    ci.setAttribute('align', align);\r\n                }\r\n            }\r\n        }\r\n    };\r\n    UE.commands['cellvalign'] = {\r\n        queryCommandState: function () {\r\n            return getSelectedArr(this).length ? 0 : -1;\r\n        },\r\n        execCommand: function (cmd, valign) {\r\n            var selectedTds = getSelectedArr(this);\r\n            if (selectedTds.length) {\r\n                for (var i = 0, ci; ci = selectedTds[i++];) {\r\n                    ci.setAttribute('vAlign', valign);\r\n                }\r\n            }\r\n        }\r\n    };\r\n    UE.commands['insertcaption'] = {\r\n        queryCommandState: function () {\r\n            var table = getTableItemsByRange(this).table;\r\n            if (table) {\r\n                return table.getElementsByTagName('caption').length == 0 ? 1 : -1;\r\n            }\r\n            return -1;\r\n        },\r\n        execCommand: function () {\r\n            var table = getTableItemsByRange(this).table;\r\n            if (table) {\r\n                var caption = this.document.createElement('caption');\r\n                caption.innerHTML = browser.ie ? domUtils.fillChar : '<br/>';\r\n                table.insertBefore(caption, table.firstChild);\r\n                var range = this.selection.getRange();\r\n                range.setStart(caption, 0).setCursor();\r\n            }\r\n\r\n        }\r\n    };\r\n    UE.commands['deletecaption'] = {\r\n        queryCommandState: function () {\r\n            var rng = this.selection.getRange(),\r\n                table = domUtils.findParentByTagName(rng.startContainer, 'table');\r\n            if (table) {\r\n                return table.getElementsByTagName('caption').length == 0 ? -1 : 1;\r\n            }\r\n            return -1;\r\n        },\r\n        execCommand: function () {\r\n            var rng = this.selection.getRange(),\r\n                table = domUtils.findParentByTagName(rng.startContainer, 'table');\r\n            if (table) {\r\n                domUtils.remove(table.getElementsByTagName('caption')[0]);\r\n                var range = this.selection.getRange();\r\n                range.setStart(table.rows[0].cells[0], 0).setCursor();\r\n            }\r\n\r\n        }\r\n    };\r\n    UE.commands['inserttitle'] = {\r\n        queryCommandState: function () {\r\n            var table = getTableItemsByRange(this).table;\r\n            if (table) {\r\n                var firstRow = table.rows[0];\r\n                return firstRow.cells[firstRow.cells.length-1].tagName.toLowerCase() != 'th' ? 0 : -1\r\n            }\r\n            return -1;\r\n        },\r\n        execCommand: function () {\r\n            var table = getTableItemsByRange(this).table;\r\n            if (table) {\r\n                getUETable(table).insertRow(0, 'th');\r\n            }\r\n            var th = table.getElementsByTagName('th')[0];\r\n            this.selection.getRange().setStart(th, 0).setCursor(false, true);\r\n        }\r\n    };\r\n    UE.commands['deletetitle'] = {\r\n        queryCommandState: function () {\r\n            var table = getTableItemsByRange(this).table;\r\n            if (table) {\r\n                var firstRow = table.rows[0];\r\n                return firstRow.cells[firstRow.cells.length-1].tagName.toLowerCase() == 'th' ? 0 : -1\r\n            }\r\n            return -1;\r\n        },\r\n        execCommand: function () {\r\n            var table = getTableItemsByRange(this).table;\r\n            if (table) {\r\n                domUtils.remove(table.rows[0])\r\n            }\r\n            var td = table.getElementsByTagName('td')[0];\r\n            this.selection.getRange().setStart(td, 0).setCursor(false, true);\r\n        }\r\n    };\r\n    UE.commands['inserttitlecol'] = {\r\n        queryCommandState: function () {\r\n            var table = getTableItemsByRange(this).table;\r\n            if (table) {\r\n                var lastRow = table.rows[table.rows.length-1];\r\n                return lastRow.getElementsByTagName('th').length ? -1 : 0;\r\n            }\r\n            return -1;\r\n        },\r\n        execCommand: function (cmd) {\r\n            var table = getTableItemsByRange(this).table;\r\n            if (table) {\r\n                getUETable(table).insertCol(0, 'th');\r\n            }\r\n            resetTdWidth(table, this);\r\n            var th = table.getElementsByTagName('th')[0];\r\n            this.selection.getRange().setStart(th, 0).setCursor(false, true);\r\n        }\r\n    };\r\n    UE.commands['deletetitlecol'] = {\r\n        queryCommandState: function () {\r\n            var table = getTableItemsByRange(this).table;\r\n            if (table) {\r\n                var lastRow = table.rows[table.rows.length-1];\r\n                return lastRow.getElementsByTagName('th').length ? 0 : -1;\r\n            }\r\n            return -1;\r\n        },\r\n        execCommand: function () {\r\n            var table = getTableItemsByRange(this).table;\r\n            if (table) {\r\n                for(var i = 0; i< table.rows.length; i++ ){\r\n                    domUtils.remove(table.rows[i].children[0])\r\n                }\r\n            }\r\n            resetTdWidth(table, this);\r\n            var td = table.getElementsByTagName('td')[0];\r\n            this.selection.getRange().setStart(td, 0).setCursor(false, true);\r\n        }\r\n    };\r\n\r\n    UE.commands[\"mergeright\"] = {\r\n        queryCommandState: function (cmd) {\r\n            var tableItems = getTableItemsByRange(this),\r\n                table = tableItems.table,\r\n                cell = tableItems.cell;\r\n\r\n            if (!table || !cell) return -1;\r\n            var ut = getUETable(table);\r\n            if (ut.selectedTds.length) return -1;\r\n\r\n            var cellInfo = ut.getCellInfo(cell),\r\n                rightColIndex = cellInfo.colIndex + cellInfo.colSpan;\r\n            if (rightColIndex >= ut.colsNum) return -1; // 如果处于最右边则不能向右合并\r\n\r\n            var rightCellInfo = ut.indexTable[cellInfo.rowIndex][rightColIndex],\r\n                rightCell = table.rows[rightCellInfo.rowIndex].cells[rightCellInfo.cellIndex];\r\n            if (!rightCell || cell.tagName != rightCell.tagName) return -1; // TH和TD不能相互合并\r\n\r\n            // 当且仅当两个Cell的开始列号和结束列号一致时能进行合并\r\n            return (rightCellInfo.rowIndex == cellInfo.rowIndex && rightCellInfo.rowSpan == cellInfo.rowSpan) ? 0 : -1;\r\n        },\r\n        execCommand: function (cmd) {\r\n            var rng = this.selection.getRange(),\r\n                bk = rng.createBookmark(true);\r\n            var cell = getTableItemsByRange(this).cell,\r\n                ut = getUETable(cell);\r\n            ut.mergeRight(cell);\r\n            rng.moveToBookmark(bk).select();\r\n        }\r\n    };\r\n    UE.commands[\"mergedown\"] = {\r\n        queryCommandState: function (cmd) {\r\n            var tableItems = getTableItemsByRange(this),\r\n                table = tableItems.table,\r\n                cell = tableItems.cell;\r\n\r\n            if (!table || !cell) return -1;\r\n            var ut = getUETable(table);\r\n            if (ut.selectedTds.length)return -1;\r\n\r\n            var cellInfo = ut.getCellInfo(cell),\r\n                downRowIndex = cellInfo.rowIndex + cellInfo.rowSpan;\r\n            if (downRowIndex >= ut.rowsNum) return -1; // 如果处于最下边则不能向下合并\r\n\r\n            var downCellInfo = ut.indexTable[downRowIndex][cellInfo.colIndex],\r\n                downCell = table.rows[downCellInfo.rowIndex].cells[downCellInfo.cellIndex];\r\n            if (!downCell || cell.tagName != downCell.tagName) return -1; // TH和TD不能相互合并\r\n\r\n            // 当且仅当两个Cell的开始列号和结束列号一致时能进行合并\r\n            return (downCellInfo.colIndex == cellInfo.colIndex && downCellInfo.colSpan == cellInfo.colSpan) ? 0 : -1;\r\n        },\r\n        execCommand: function () {\r\n            var rng = this.selection.getRange(),\r\n                bk = rng.createBookmark(true);\r\n            var cell = getTableItemsByRange(this).cell,\r\n                ut = getUETable(cell);\r\n            ut.mergeDown(cell);\r\n            rng.moveToBookmark(bk).select();\r\n        }\r\n    };\r\n    UE.commands[\"mergecells\"] = {\r\n        queryCommandState: function () {\r\n            return getUETableBySelected(this) ? 0 : -1;\r\n        },\r\n        execCommand: function () {\r\n            var ut = getUETableBySelected(this);\r\n            if (ut && ut.selectedTds.length) {\r\n                var cell = ut.selectedTds[0];\r\n                ut.mergeRange();\r\n                var rng = this.selection.getRange();\r\n                if (domUtils.isEmptyBlock(cell)) {\r\n                    rng.setStart(cell, 0).collapse(true)\r\n                } else {\r\n                    rng.selectNodeContents(cell)\r\n                }\r\n                rng.select();\r\n            }\r\n\r\n\r\n        }\r\n    };\r\n    UE.commands[\"insertrow\"] = {\r\n        queryCommandState: function () {\r\n            var tableItems = getTableItemsByRange(this),\r\n                cell = tableItems.cell;\r\n            return cell && (cell.tagName == \"TD\" || (cell.tagName == 'TH' && tableItems.tr !== tableItems.table.rows[0])) &&\r\n                getUETable(tableItems.table).rowsNum < this.options.maxRowNum ? 0 : -1;\r\n        },\r\n        execCommand: function () {\r\n            var rng = this.selection.getRange(),\r\n                bk = rng.createBookmark(true);\r\n            var tableItems = getTableItemsByRange(this),\r\n                cell = tableItems.cell,\r\n                table = tableItems.table,\r\n                ut = getUETable(table),\r\n                cellInfo = ut.getCellInfo(cell);\r\n            //ut.insertRow(!ut.selectedTds.length ? cellInfo.rowIndex:ut.cellsRange.beginRowIndex,'');\r\n            if (!ut.selectedTds.length) {\r\n                ut.insertRow(cellInfo.rowIndex, cell);\r\n            } else {\r\n                var range = ut.cellsRange;\r\n                for (var i = 0, len = range.endRowIndex - range.beginRowIndex + 1; i < len; i++) {\r\n                    ut.insertRow(range.beginRowIndex, cell);\r\n                }\r\n            }\r\n            rng.moveToBookmark(bk).select();\r\n            if (table.getAttribute(\"interlaced\") === \"enabled\")this.fireEvent(\"interlacetable\", table);\r\n        }\r\n    };\r\n    //后插入行\r\n    UE.commands[\"insertrownext\"] = {\r\n        queryCommandState: function () {\r\n            var tableItems = getTableItemsByRange(this),\r\n                cell = tableItems.cell;\r\n            return cell && (cell.tagName == \"TD\") && getUETable(tableItems.table).rowsNum < this.options.maxRowNum ? 0 : -1;\r\n        },\r\n        execCommand: function () {\r\n            var rng = this.selection.getRange(),\r\n                bk = rng.createBookmark(true);\r\n            var tableItems = getTableItemsByRange(this),\r\n                cell = tableItems.cell,\r\n                table = tableItems.table,\r\n                ut = getUETable(table),\r\n                cellInfo = ut.getCellInfo(cell);\r\n            //ut.insertRow(!ut.selectedTds.length? cellInfo.rowIndex + cellInfo.rowSpan : ut.cellsRange.endRowIndex + 1,'');\r\n            if (!ut.selectedTds.length) {\r\n                ut.insertRow(cellInfo.rowIndex + cellInfo.rowSpan, cell);\r\n            } else {\r\n                var range = ut.cellsRange;\r\n                for (var i = 0, len = range.endRowIndex - range.beginRowIndex + 1; i < len; i++) {\r\n                    ut.insertRow(range.endRowIndex + 1, cell);\r\n                }\r\n            }\r\n            rng.moveToBookmark(bk).select();\r\n            if (table.getAttribute(\"interlaced\") === \"enabled\")this.fireEvent(\"interlacetable\", table);\r\n        }\r\n    };\r\n    UE.commands[\"deleterow\"] = {\r\n        queryCommandState: function () {\r\n            var tableItems = getTableItemsByRange(this);\r\n            return tableItems.cell ? 0 : -1;\r\n        },\r\n        execCommand: function () {\r\n            var cell = getTableItemsByRange(this).cell,\r\n                ut = getUETable(cell),\r\n                cellsRange = ut.cellsRange,\r\n                cellInfo = ut.getCellInfo(cell),\r\n                preCell = ut.getVSideCell(cell),\r\n                nextCell = ut.getVSideCell(cell, true),\r\n                rng = this.selection.getRange();\r\n            if (utils.isEmptyObject(cellsRange)) {\r\n                ut.deleteRow(cellInfo.rowIndex);\r\n            } else {\r\n                for (var i = cellsRange.beginRowIndex; i < cellsRange.endRowIndex + 1; i++) {\r\n                    ut.deleteRow(cellsRange.beginRowIndex);\r\n                }\r\n            }\r\n            var table = ut.table;\r\n            if (!table.getElementsByTagName('td').length) {\r\n                var nextSibling = table.nextSibling;\r\n                domUtils.remove(table);\r\n                if (nextSibling) {\r\n                    rng.setStart(nextSibling, 0).setCursor(false, true);\r\n                }\r\n            } else {\r\n                if (cellInfo.rowSpan == 1 || cellInfo.rowSpan == cellsRange.endRowIndex - cellsRange.beginRowIndex + 1) {\r\n                    if (nextCell || preCell) rng.selectNodeContents(nextCell || preCell).setCursor(false, true);\r\n                } else {\r\n                    var newCell = ut.getCell(cellInfo.rowIndex, ut.indexTable[cellInfo.rowIndex][cellInfo.colIndex].cellIndex);\r\n                    if (newCell) rng.selectNodeContents(newCell).setCursor(false, true);\r\n                }\r\n            }\r\n            if (table.getAttribute(\"interlaced\") === \"enabled\")this.fireEvent(\"interlacetable\", table);\r\n        }\r\n    };\r\n    UE.commands[\"insertcol\"] = {\r\n        queryCommandState: function (cmd) {\r\n            var tableItems = getTableItemsByRange(this),\r\n                cell = tableItems.cell;\r\n            return cell && (cell.tagName == \"TD\" || (cell.tagName == 'TH' && cell !== tableItems.tr.cells[0])) &&\r\n                getUETable(tableItems.table).colsNum < this.options.maxColNum ? 0 : -1;\r\n        },\r\n        execCommand: function (cmd) {\r\n            var rng = this.selection.getRange(),\r\n                bk = rng.createBookmark(true);\r\n            if (this.queryCommandState(cmd) == -1)return;\r\n            var cell = getTableItemsByRange(this).cell,\r\n                ut = getUETable(cell),\r\n                cellInfo = ut.getCellInfo(cell);\r\n\r\n            //ut.insertCol(!ut.selectedTds.length ? cellInfo.colIndex:ut.cellsRange.beginColIndex);\r\n            if (!ut.selectedTds.length) {\r\n                ut.insertCol(cellInfo.colIndex, cell);\r\n            } else {\r\n                var range = ut.cellsRange;\r\n                for (var i = 0, len = range.endColIndex - range.beginColIndex + 1; i < len; i++) {\r\n                    ut.insertCol(range.beginColIndex, cell);\r\n                }\r\n            }\r\n            rng.moveToBookmark(bk).select(true);\r\n        }\r\n    };\r\n    UE.commands[\"insertcolnext\"] = {\r\n        queryCommandState: function () {\r\n            var tableItems = getTableItemsByRange(this),\r\n                cell = tableItems.cell;\r\n            return cell && getUETable(tableItems.table).colsNum < this.options.maxColNum ? 0 : -1;\r\n        },\r\n        execCommand: function () {\r\n            var rng = this.selection.getRange(),\r\n                bk = rng.createBookmark(true);\r\n            var cell = getTableItemsByRange(this).cell,\r\n                ut = getUETable(cell),\r\n                cellInfo = ut.getCellInfo(cell);\r\n            //ut.insertCol(!ut.selectedTds.length ? cellInfo.colIndex + cellInfo.colSpan:ut.cellsRange.endColIndex +1);\r\n            if (!ut.selectedTds.length) {\r\n                ut.insertCol(cellInfo.colIndex + cellInfo.colSpan, cell);\r\n            } else {\r\n                var range = ut.cellsRange;\r\n                for (var i = 0, len = range.endColIndex - range.beginColIndex + 1; i < len; i++) {\r\n                    ut.insertCol(range.endColIndex + 1, cell);\r\n                }\r\n            }\r\n            rng.moveToBookmark(bk).select();\r\n        }\r\n    };\r\n\r\n    UE.commands[\"deletecol\"] = {\r\n        queryCommandState: function () {\r\n            var tableItems = getTableItemsByRange(this);\r\n            return tableItems.cell ? 0 : -1;\r\n        },\r\n        execCommand: function () {\r\n            var cell = getTableItemsByRange(this).cell,\r\n                ut = getUETable(cell),\r\n                range = ut.cellsRange,\r\n                cellInfo = ut.getCellInfo(cell),\r\n                preCell = ut.getHSideCell(cell),\r\n                nextCell = ut.getHSideCell(cell, true);\r\n            if (utils.isEmptyObject(range)) {\r\n                ut.deleteCol(cellInfo.colIndex);\r\n            } else {\r\n                for (var i = range.beginColIndex; i < range.endColIndex + 1; i++) {\r\n                    ut.deleteCol(range.beginColIndex);\r\n                }\r\n            }\r\n            var table = ut.table,\r\n                rng = this.selection.getRange();\r\n\r\n            if (!table.getElementsByTagName('td').length) {\r\n                var nextSibling = table.nextSibling;\r\n                domUtils.remove(table);\r\n                if (nextSibling) {\r\n                    rng.setStart(nextSibling, 0).setCursor(false, true);\r\n                }\r\n            } else {\r\n                if (domUtils.inDoc(cell, this.document)) {\r\n                    rng.setStart(cell, 0).setCursor(false, true);\r\n                } else {\r\n                    if (nextCell && domUtils.inDoc(nextCell, this.document)) {\r\n                        rng.selectNodeContents(nextCell).setCursor(false, true);\r\n                    } else {\r\n                        if (preCell && domUtils.inDoc(preCell, this.document)) {\r\n                            rng.selectNodeContents(preCell).setCursor(true, true);\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n        }\r\n    };\r\n    UE.commands[\"splittocells\"] = {\r\n        queryCommandState: function () {\r\n            var tableItems = getTableItemsByRange(this),\r\n                cell = tableItems.cell;\r\n            if (!cell) return -1;\r\n            var ut = getUETable(tableItems.table);\r\n            if (ut.selectedTds.length > 0) return -1;\r\n            return cell && (cell.colSpan > 1 || cell.rowSpan > 1) ? 0 : -1;\r\n        },\r\n        execCommand: function () {\r\n            var rng = this.selection.getRange(),\r\n                bk = rng.createBookmark(true);\r\n            var cell = getTableItemsByRange(this).cell,\r\n                ut = getUETable(cell);\r\n            ut.splitToCells(cell);\r\n            rng.moveToBookmark(bk).select();\r\n        }\r\n    };\r\n    UE.commands[\"splittorows\"] = {\r\n        queryCommandState: function () {\r\n            var tableItems = getTableItemsByRange(this),\r\n                cell = tableItems.cell;\r\n            if (!cell) return -1;\r\n            var ut = getUETable(tableItems.table);\r\n            if (ut.selectedTds.length > 0) return -1;\r\n            return cell && cell.rowSpan > 1 ? 0 : -1;\r\n        },\r\n        execCommand: function () {\r\n            var rng = this.selection.getRange(),\r\n                bk = rng.createBookmark(true);\r\n            var cell = getTableItemsByRange(this).cell,\r\n                ut = getUETable(cell);\r\n            ut.splitToRows(cell);\r\n            rng.moveToBookmark(bk).select();\r\n        }\r\n    };\r\n    UE.commands[\"splittocols\"] = {\r\n        queryCommandState: function () {\r\n            var tableItems = getTableItemsByRange(this),\r\n                cell = tableItems.cell;\r\n            if (!cell) return -1;\r\n            var ut = getUETable(tableItems.table);\r\n            if (ut.selectedTds.length > 0) return -1;\r\n            return cell && cell.colSpan > 1 ? 0 : -1;\r\n        },\r\n        execCommand: function () {\r\n            var rng = this.selection.getRange(),\r\n                bk = rng.createBookmark(true);\r\n            var cell = getTableItemsByRange(this).cell,\r\n                ut = getUETable(cell);\r\n            ut.splitToCols(cell);\r\n            rng.moveToBookmark(bk).select();\r\n\r\n        }\r\n    };\r\n\r\n    UE.commands[\"adaptbytext\"] =\r\n        UE.commands[\"adaptbywindow\"] = {\r\n            queryCommandState: function () {\r\n                return getTableItemsByRange(this).table ? 0 : -1\r\n            },\r\n            execCommand: function (cmd) {\r\n                var tableItems = getTableItemsByRange(this),\r\n                    table = tableItems.table;\r\n                if (table) {\r\n                    if (cmd == 'adaptbywindow') {\r\n                        resetTdWidth(table, this);\r\n                    } else {\r\n                        var cells = domUtils.getElementsByTagName(table, \"td th\");\r\n                        utils.each(cells, function (cell) {\r\n                            cell.removeAttribute(\"width\");\r\n                        });\r\n                        table.removeAttribute(\"width\");\r\n                    }\r\n                }\r\n            }\r\n        };\r\n\r\n    //平均分配各列\r\n    UE.commands['averagedistributecol'] = {\r\n        queryCommandState: function () {\r\n            var ut = getUETableBySelected(this);\r\n            if (!ut) return -1;\r\n            return ut.isFullRow() || ut.isFullCol() ? 0 : -1;\r\n        },\r\n        execCommand: function (cmd) {\r\n            var me = this,\r\n                ut = getUETableBySelected(me);\r\n\r\n            function getAverageWidth() {\r\n                var tb = ut.table,\r\n                    averageWidth, sumWidth = 0, colsNum = 0,\r\n                    tbAttr = getDefaultValue(me, tb);\r\n\r\n                if (ut.isFullRow()) {\r\n                    sumWidth = tb.offsetWidth;\r\n                    colsNum = ut.colsNum;\r\n                } else {\r\n                    var begin = ut.cellsRange.beginColIndex,\r\n                        end = ut.cellsRange.endColIndex,\r\n                        node;\r\n                    for (var i = begin; i <= end;) {\r\n                        node = ut.selectedTds[i];\r\n                        sumWidth += node.offsetWidth;\r\n                        i += node.colSpan;\r\n                        colsNum += 1;\r\n                    }\r\n                }\r\n                averageWidth = Math.ceil(sumWidth / colsNum) - tbAttr.tdBorder * 2 - tbAttr.tdPadding * 2;\r\n                return averageWidth;\r\n            }\r\n\r\n            function setAverageWidth(averageWidth) {\r\n                utils.each(domUtils.getElementsByTagName(ut.table, \"th\"), function (node) {\r\n                    node.setAttribute(\"width\", \"\");\r\n                });\r\n                var cells = ut.isFullRow() ? domUtils.getElementsByTagName(ut.table, \"td\") : ut.selectedTds;\r\n\r\n                utils.each(cells, function (node) {\r\n                    if (node.colSpan == 1) {\r\n                        node.setAttribute(\"width\", averageWidth);\r\n                    }\r\n                });\r\n            }\r\n\r\n            if (ut && ut.selectedTds.length) {\r\n                setAverageWidth(getAverageWidth());\r\n            }\r\n        }\r\n    };\r\n    //平均分配各行\r\n    UE.commands['averagedistributerow'] = {\r\n        queryCommandState: function () {\r\n            var ut = getUETableBySelected(this);\r\n            if (!ut) return -1;\r\n            if (ut.selectedTds && /th/ig.test(ut.selectedTds[0].tagName)) return -1;\r\n            return ut.isFullRow() || ut.isFullCol() ? 0 : -1;\r\n        },\r\n        execCommand: function (cmd) {\r\n            var me = this,\r\n                ut = getUETableBySelected(me);\r\n\r\n            function getAverageHeight() {\r\n                var averageHeight, rowNum, sumHeight = 0,\r\n                    tb = ut.table,\r\n                    tbAttr = getDefaultValue(me, tb),\r\n                    tdpadding = parseInt(domUtils.getComputedStyle(tb.getElementsByTagName('td')[0], \"padding-top\"));\r\n\r\n                if (ut.isFullCol()) {\r\n                    var captionArr = domUtils.getElementsByTagName(tb, \"caption\"),\r\n                        thArr = domUtils.getElementsByTagName(tb, \"th\"),\r\n                        captionHeight, thHeight;\r\n\r\n                    if (captionArr.length > 0) {\r\n                        captionHeight = captionArr[0].offsetHeight;\r\n                    }\r\n                    if (thArr.length > 0) {\r\n                        thHeight = thArr[0].offsetHeight;\r\n                    }\r\n\r\n                    sumHeight = tb.offsetHeight - (captionHeight || 0) - (thHeight || 0);\r\n                    rowNum = thArr.length == 0 ? ut.rowsNum : (ut.rowsNum - 1);\r\n                } else {\r\n                    var begin = ut.cellsRange.beginRowIndex,\r\n                        end = ut.cellsRange.endRowIndex,\r\n                        count = 0,\r\n                        trs = domUtils.getElementsByTagName(tb, \"tr\");\r\n                    for (var i = begin; i <= end; i++) {\r\n                        sumHeight += trs[i].offsetHeight;\r\n                        count += 1;\r\n                    }\r\n                    rowNum = count;\r\n                }\r\n                //ie8下是混杂模式\r\n                if (browser.ie && browser.version < 9) {\r\n                    averageHeight = Math.ceil(sumHeight / rowNum);\r\n                } else {\r\n                    averageHeight = Math.ceil(sumHeight / rowNum) - tbAttr.tdBorder * 2 - tdpadding * 2;\r\n                }\r\n                return averageHeight;\r\n            }\r\n\r\n            function setAverageHeight(averageHeight) {\r\n                var cells = ut.isFullCol() ? domUtils.getElementsByTagName(ut.table, \"td\") : ut.selectedTds;\r\n                utils.each(cells, function (node) {\r\n                    if (node.rowSpan == 1) {\r\n                        node.setAttribute(\"height\", averageHeight);\r\n                    }\r\n                });\r\n            }\r\n\r\n            if (ut && ut.selectedTds.length) {\r\n                setAverageHeight(getAverageHeight());\r\n            }\r\n        }\r\n    };\r\n\r\n    //单元格对齐方式\r\n    UE.commands['cellalignment'] = {\r\n        queryCommandState: function () {\r\n            return getTableItemsByRange(this).table ? 0 : -1\r\n        },\r\n        execCommand: function (cmd, data) {\r\n            var me = this,\r\n                ut = getUETableBySelected(me);\r\n\r\n            if (!ut) {\r\n                var start = me.selection.getStart(),\r\n                    cell = start && domUtils.findParentByTagName(start, [\"td\", \"th\", \"caption\"], true);\r\n                if (!/caption/ig.test(cell.tagName)) {\r\n                    domUtils.setAttributes(cell, data);\r\n                } else {\r\n                    cell.style.textAlign = data.align;\r\n                    cell.style.verticalAlign = data.vAlign;\r\n                }\r\n                me.selection.getRange().setCursor(true);\r\n            } else {\r\n                utils.each(ut.selectedTds, function (cell) {\r\n                    domUtils.setAttributes(cell, data);\r\n                });\r\n            }\r\n        },\r\n        /**\r\n         * 查询当前点击的单元格的对齐状态， 如果当前已经选择了多个单元格， 则会返回所有单元格经过统一协调过后的状态\r\n         * @see UE.UETable.getTableCellAlignState\r\n         */\r\n        queryCommandValue: function (cmd) {\r\n\r\n            var activeMenuCell = getTableItemsByRange( this).cell;\r\n\r\n            if( !activeMenuCell ) {\r\n                activeMenuCell = getSelectedArr(this)[0];\r\n            }\r\n\r\n            if (!activeMenuCell) {\r\n\r\n                return null;\r\n\r\n            } else {\r\n\r\n                //获取同时选中的其他单元格\r\n                var cells = UE.UETable.getUETable(activeMenuCell).selectedTds;\r\n\r\n                !cells.length && ( cells = activeMenuCell );\r\n\r\n                return UE.UETable.getTableCellAlignState(cells);\r\n\r\n            }\r\n\r\n        }\r\n    };\r\n    //表格对齐方式\r\n    UE.commands['tablealignment'] = {\r\n        queryCommandState: function () {\r\n            if (browser.ie && browser.version < 8) {\r\n                return -1;\r\n            }\r\n            return getTableItemsByRange(this).table ? 0 : -1\r\n        },\r\n        execCommand: function (cmd, value) {\r\n            var me = this,\r\n                start = me.selection.getStart(),\r\n                table = start && domUtils.findParentByTagName(start, [\"table\"], true);\r\n\r\n            if (table) {\r\n                table.setAttribute(\"align\",value);\r\n            }\r\n        }\r\n    };\r\n\r\n    //表格属性\r\n    UE.commands['edittable'] = {\r\n        queryCommandState: function () {\r\n            return getTableItemsByRange(this).table ? 0 : -1\r\n        },\r\n        execCommand: function (cmd, color) {\r\n            var rng = this.selection.getRange(),\r\n                table = domUtils.findParentByTagName(rng.startContainer, 'table');\r\n            if (table) {\r\n                var arr = domUtils.getElementsByTagName(table, \"td\").concat(\r\n                    domUtils.getElementsByTagName(table, \"th\"),\r\n                    domUtils.getElementsByTagName(table, \"caption\")\r\n                );\r\n                utils.each(arr, function (node) {\r\n                    node.style.borderColor = color;\r\n                });\r\n            }\r\n        }\r\n    };\r\n    //单元格属性\r\n    UE.commands['edittd'] = {\r\n        queryCommandState: function () {\r\n            return getTableItemsByRange(this).table ? 0 : -1\r\n        },\r\n        execCommand: function (cmd, bkColor) {\r\n            var me = this,\r\n                ut = getUETableBySelected(me);\r\n\r\n            if (!ut) {\r\n                var start = me.selection.getStart(),\r\n                    cell = start && domUtils.findParentByTagName(start, [\"td\", \"th\", \"caption\"], true);\r\n                if (cell) {\r\n                    cell.style.backgroundColor = bkColor;\r\n                }\r\n            } else {\r\n                utils.each(ut.selectedTds, function (cell) {\r\n                    cell.style.backgroundColor = bkColor;\r\n                });\r\n            }\r\n        }\r\n    };\r\n\r\n    UE.commands[\"settablebackground\"] = {\r\n        queryCommandState: function () {\r\n            return getSelectedArr(this).length > 1 ? 0 : -1;\r\n        },\r\n        execCommand: function (cmd, value) {\r\n            var cells, ut;\r\n            cells = getSelectedArr(this);\r\n            ut = getUETable(cells[0]);\r\n            ut.setBackground(cells, value);\r\n        }\r\n    };\r\n\r\n    UE.commands[\"cleartablebackground\"] = {\r\n        queryCommandState: function () {\r\n            var cells = getSelectedArr(this);\r\n            if (!cells.length)return -1;\r\n            for (var i = 0, cell; cell = cells[i++];) {\r\n                if (cell.style.backgroundColor !== \"\") return 0;\r\n            }\r\n            return -1;\r\n        },\r\n        execCommand: function () {\r\n            var cells = getSelectedArr(this),\r\n                ut = getUETable(cells[0]);\r\n            ut.removeBackground(cells);\r\n        }\r\n    };\r\n\r\n    UE.commands[\"interlacetable\"] = UE.commands[\"uninterlacetable\"] = {\r\n        queryCommandState: function (cmd) {\r\n            var table = getTableItemsByRange(this).table;\r\n            if (!table) return -1;\r\n            var interlaced = table.getAttribute(\"interlaced\");\r\n            if (cmd == \"interlacetable\") {\r\n                //TODO 待定\r\n                //是否需要待定，如果设置，则命令只能单次执行成功，但反射具备toggle效果；否则可以覆盖前次命令，但反射将不存在toggle效果\r\n                return (interlaced === \"enabled\") ? -1 : 0;\r\n            } else {\r\n                return (!interlaced || interlaced === \"disabled\") ? -1 : 0;\r\n            }\r\n        },\r\n        execCommand: function (cmd, classList) {\r\n            var table = getTableItemsByRange(this).table;\r\n            if (cmd == \"interlacetable\") {\r\n                table.setAttribute(\"interlaced\", \"enabled\");\r\n                this.fireEvent(\"interlacetable\", table, classList);\r\n            } else {\r\n                table.setAttribute(\"interlaced\", \"disabled\");\r\n                this.fireEvent(\"uninterlacetable\", table);\r\n            }\r\n        }\r\n    };\r\n    UE.commands[\"setbordervisible\"] = {\r\n        queryCommandState: function (cmd) {\r\n            var table = getTableItemsByRange(this).table;\r\n            if (!table) return -1;\r\n            return 0;\r\n        },\r\n        execCommand: function () {\r\n            var table = getTableItemsByRange(this).table;\r\n            utils.each(domUtils.getElementsByTagName(table,'td'),function(td){\r\n                td.style.borderWidth = '1px';\r\n                td.style.borderStyle = 'solid';\r\n            })\r\n        }\r\n    };\r\n    function resetTdWidth(table, editor) {\r\n        var tds = domUtils.getElementsByTagName(table,'td th');\r\n        utils.each(tds, function (td) {\r\n            td.removeAttribute(\"width\");\r\n        });\r\n        table.setAttribute('width', getTableWidth(editor, true, getDefaultValue(editor, table)));\r\n        var tdsWidths = [];\r\n        setTimeout(function () {\r\n            utils.each(tds, function (td) {\r\n                (td.colSpan == 1) && tdsWidths.push(td.offsetWidth)\r\n            })\r\n            utils.each(tds, function (td,i) {\r\n                (td.colSpan == 1) && td.setAttribute(\"width\", tdsWidths[i] + \"\");\r\n            })\r\n        }, 0);\r\n    }\r\n\r\n    function getTableWidth(editor, needIEHack, defaultValue) {\r\n        var body = editor.body;\r\n        return body.offsetWidth - (needIEHack ? parseInt(domUtils.getComputedStyle(body, 'margin-left'), 10) * 2 : 0) - defaultValue.tableBorder * 2 - (editor.options.offsetWidth || 0);\r\n    }\r\n\r\n    function getSelectedArr(editor) {\r\n        var cell = getTableItemsByRange(editor).cell;\r\n        if (cell) {\r\n            var ut = getUETable(cell);\r\n            return ut.selectedTds.length ? ut.selectedTds : [cell];\r\n        } else {\r\n            return [];\r\n        }\r\n    }\r\n})();\r\n\r\n\r\n// plugins/table.action.js\r\n/**\r\n * Created with JetBrains PhpStorm.\r\n * User: taoqili\r\n * Date: 12-10-12\r\n * Time: 上午10:05\r\n * To change this template use File | Settings | File Templates.\r\n */\r\nUE.plugins['table'] = function () {\r\n    var me = this,\r\n        tabTimer = null,\r\n        //拖动计时器\r\n        tableDragTimer = null,\r\n        //双击计时器\r\n        tableResizeTimer = null,\r\n        //单元格最小宽度\r\n        cellMinWidth = 5,\r\n        isInResizeBuffer = false,\r\n        //单元格边框大小\r\n        cellBorderWidth = 5,\r\n        //鼠标偏移距离\r\n        offsetOfTableCell = 10,\r\n        //记录在有限时间内的点击状态， 共有3个取值， 0, 1, 2。 0代表未初始化， 1代表单击了1次，2代表2次\r\n        singleClickState = 0,\r\n        userActionStatus = null,\r\n        //双击允许的时间范围\r\n        dblclickTime = 360,\r\n        UT = UE.UETable,\r\n        getUETable = function (tdOrTable) {\r\n            return UT.getUETable(tdOrTable);\r\n        },\r\n        getUETableBySelected = function (editor) {\r\n            return UT.getUETableBySelected(editor);\r\n        },\r\n        getDefaultValue = function (editor, table) {\r\n            return UT.getDefaultValue(editor, table);\r\n        },\r\n        removeSelectedClass = function (cells) {\r\n            return UT.removeSelectedClass(cells);\r\n        };\r\n\r\n    function showError(e) {\r\n//        throw e;\r\n    }\r\n    me.ready(function(){\r\n        var me = this;\r\n        var orgGetText = me.selection.getText;\r\n        me.selection.getText = function(){\r\n            var table = getUETableBySelected(me);\r\n            if(table){\r\n                var str = '';\r\n                utils.each(table.selectedTds,function(td){\r\n                    str += td[browser.ie?'innerText':'textContent'];\r\n                })\r\n                return str;\r\n            }else{\r\n                return orgGetText.call(me.selection)\r\n            }\r\n\r\n        }\r\n    })\r\n\r\n    //处理拖动及框选相关方法\r\n    var startTd = null, //鼠标按下时的锚点td\r\n        currentTd = null, //当前鼠标经过时的td\r\n        onDrag = \"\", //指示当前拖动状态，其值可为\"\",\"h\",\"v\" ,分别表示未拖动状态，横向拖动状态，纵向拖动状态，用于鼠标移动过程中的判断\r\n        onBorder = false, //检测鼠标按下时是否处在单元格边缘位置\r\n        dragButton = null,\r\n        dragOver = false,\r\n        dragLine = null, //模拟的拖动线\r\n        dragTd = null;    //发生拖动的目标td\r\n\r\n    var mousedown = false,\r\n    //todo 判断混乱模式\r\n        needIEHack = true;\r\n\r\n    me.setOpt({\r\n        'maxColNum':20,\r\n        'maxRowNum':100,\r\n        'defaultCols':5,\r\n        'defaultRows':5,\r\n        'tdvalign':'top',\r\n        'cursorpath':me.options.UEDITOR_HOME_URL + \"themes/default/images/cursor_\",\r\n        'tableDragable':false,\r\n        'classList':[\"ue-table-interlace-color-single\",\"ue-table-interlace-color-double\"]\r\n    });\r\n    me.getUETable = getUETable;\r\n    var commands = {\r\n        'deletetable':1,\r\n        'inserttable':1,\r\n        'cellvalign':1,\r\n        'insertcaption':1,\r\n        'deletecaption':1,\r\n        'inserttitle':1,\r\n        'deletetitle':1,\r\n        \"mergeright\":1,\r\n        \"mergedown\":1,\r\n        \"mergecells\":1,\r\n        \"insertrow\":1,\r\n        \"insertrownext\":1,\r\n        \"deleterow\":1,\r\n        \"insertcol\":1,\r\n        \"insertcolnext\":1,\r\n        \"deletecol\":1,\r\n        \"splittocells\":1,\r\n        \"splittorows\":1,\r\n        \"splittocols\":1,\r\n        \"adaptbytext\":1,\r\n        \"adaptbywindow\":1,\r\n        \"adaptbycustomer\":1,\r\n        \"insertparagraph\":1,\r\n        \"insertparagraphbeforetable\":1,\r\n        \"averagedistributecol\":1,\r\n        \"averagedistributerow\":1\r\n    };\r\n    me.ready(function () {\r\n        utils.cssRule('table',\r\n            //选中的td上的样式\r\n            '.selectTdClass{background-color:#edf5fa !important}' +\r\n                'table.noBorderTable td,table.noBorderTable th,table.noBorderTable caption{border:1px dashed #ddd !important}' +\r\n                //插入的表格的默认样式\r\n                'table{margin-bottom:10px;border-collapse:collapse;display:table;}' +\r\n                'td,th{padding: 5px 10px;border: 1px solid #DDD;}' +\r\n                'caption{border:1px dashed #DDD;border-bottom:0;padding:3px;text-align:center;}' +\r\n                'th{border-top:1px solid #BBB;background-color:#F7F7F7;}' +\r\n                'table tr.firstRow th{border-top-width:2px;}' +\r\n                '.ue-table-interlace-color-single{ background-color: #fcfcfc; } .ue-table-interlace-color-double{ background-color: #f7faff; }' +\r\n                'td p{margin:0;padding:0;}', me.document);\r\n\r\n        var tableCopyList, isFullCol, isFullRow;\r\n        //注册del/backspace事件\r\n        me.addListener('keydown', function (cmd, evt) {\r\n            var me = this;\r\n            var keyCode = evt.keyCode || evt.which;\r\n\r\n            if (keyCode == 8) {\r\n\r\n                var ut = getUETableBySelected(me);\r\n                if (ut && ut.selectedTds.length) {\r\n\r\n                    if (ut.isFullCol()) {\r\n                        me.execCommand('deletecol')\r\n                    } else if (ut.isFullRow()) {\r\n                        me.execCommand('deleterow')\r\n                    } else {\r\n                        me.fireEvent('delcells');\r\n                    }\r\n                    domUtils.preventDefault(evt);\r\n                }\r\n\r\n                var caption = domUtils.findParentByTagName(me.selection.getStart(), 'caption', true),\r\n                    range = me.selection.getRange();\r\n                if (range.collapsed && caption && isEmptyBlock(caption)) {\r\n                    me.fireEvent('saveScene');\r\n                    var table = caption.parentNode;\r\n                    domUtils.remove(caption);\r\n                    if (table) {\r\n                        range.setStart(table.rows[0].cells[0], 0).setCursor(false, true);\r\n                    }\r\n                    me.fireEvent('saveScene');\r\n                }\r\n\r\n            }\r\n\r\n            if (keyCode == 46) {\r\n\r\n                ut = getUETableBySelected(me);\r\n                if (ut) {\r\n                    me.fireEvent('saveScene');\r\n                    for (var i = 0, ci; ci = ut.selectedTds[i++];) {\r\n                        domUtils.fillNode(me.document, ci)\r\n                    }\r\n                    me.fireEvent('saveScene');\r\n                    domUtils.preventDefault(evt);\r\n\r\n                }\r\n\r\n            }\r\n            if (keyCode == 13) {\r\n\r\n                var rng = me.selection.getRange(),\r\n                    caption = domUtils.findParentByTagName(rng.startContainer, 'caption', true);\r\n                if (caption) {\r\n                    var table = domUtils.findParentByTagName(caption, 'table');\r\n                    if (!rng.collapsed) {\r\n\r\n                        rng.deleteContents();\r\n                        me.fireEvent('saveScene');\r\n                    } else {\r\n                        if (caption) {\r\n                            rng.setStart(table.rows[0].cells[0], 0).setCursor(false, true);\r\n                        }\r\n                    }\r\n                    domUtils.preventDefault(evt);\r\n                    return;\r\n                }\r\n                if (rng.collapsed) {\r\n                    var table = domUtils.findParentByTagName(rng.startContainer, 'table');\r\n                    if (table) {\r\n                        var cell = table.rows[0].cells[0],\r\n                            start = domUtils.findParentByTagName(me.selection.getStart(), ['td', 'th'], true),\r\n                            preNode = table.previousSibling;\r\n                        if (cell === start && (!preNode || preNode.nodeType == 1 && preNode.tagName == 'TABLE' ) && domUtils.isStartInblock(rng)) {\r\n                            var first = domUtils.findParent(me.selection.getStart(), function(n){return domUtils.isBlockElm(n)}, true);\r\n                            if(first && ( /t(h|d)/i.test(first.tagName) || first ===  start.firstChild )){\r\n                                me.execCommand('insertparagraphbeforetable');\r\n                                domUtils.preventDefault(evt);\r\n                            }\r\n\r\n                        }\r\n                    }\r\n                }\r\n            }\r\n\r\n            if ((evt.ctrlKey || evt.metaKey) && evt.keyCode == '67') {\r\n                tableCopyList = null;\r\n                var ut = getUETableBySelected(me);\r\n                if (ut) {\r\n                    var tds = ut.selectedTds;\r\n                    isFullCol = ut.isFullCol();\r\n                    isFullRow = ut.isFullRow();\r\n                    tableCopyList = [\r\n                        [ut.cloneCell(tds[0],null,true)]\r\n                    ];\r\n                    for (var i = 1, ci; ci = tds[i]; i++) {\r\n                        if (ci.parentNode !== tds[i - 1].parentNode) {\r\n                            tableCopyList.push([ut.cloneCell(ci,null,true)]);\r\n                        } else {\r\n                            tableCopyList[tableCopyList.length - 1].push(ut.cloneCell(ci,null,true));\r\n                        }\r\n\r\n                    }\r\n                }\r\n            }\r\n        });\r\n        me.addListener(\"tablehasdeleted\",function(){\r\n            toggleDraggableState(this, false, \"\", null);\r\n            if (dragButton)domUtils.remove(dragButton);\r\n        });\r\n\r\n        me.addListener('beforepaste', function (cmd, html) {\r\n            var me = this;\r\n            var rng = me.selection.getRange();\r\n            if (domUtils.findParentByTagName(rng.startContainer, 'caption', true)) {\r\n                var div = me.document.createElement(\"div\");\r\n                div.innerHTML = html.html;\r\n                //trace:3729\r\n                html.html = div[browser.ie9below ? 'innerText' : 'textContent'];\r\n                return;\r\n            }\r\n            var table = getUETableBySelected(me);\r\n            if (tableCopyList) {\r\n                me.fireEvent('saveScene');\r\n                var rng = me.selection.getRange();\r\n                var td = domUtils.findParentByTagName(rng.startContainer, ['td', 'th'], true), tmpNode, preNode;\r\n                if (td) {\r\n                    var ut = getUETable(td);\r\n                    if (isFullRow) {\r\n                        var rowIndex = ut.getCellInfo(td).rowIndex;\r\n                        if (td.tagName == 'TH') {\r\n                            rowIndex++;\r\n                        }\r\n                        for (var i = 0, ci; ci = tableCopyList[i++];) {\r\n                            var tr = ut.insertRow(rowIndex++, \"td\");\r\n                            for (var j = 0, cj; cj = ci[j]; j++) {\r\n                                var cell = tr.cells[j];\r\n                                if (!cell) {\r\n                                    cell = tr.insertCell(j)\r\n                                }\r\n                                cell.innerHTML = cj.innerHTML;\r\n                                cj.getAttribute('width') && cell.setAttribute('width', cj.getAttribute('width'));\r\n                                cj.getAttribute('vAlign') && cell.setAttribute('vAlign', cj.getAttribute('vAlign'));\r\n                                cj.getAttribute('align') && cell.setAttribute('align', cj.getAttribute('align'));\r\n                                cj.style.cssText && (cell.style.cssText = cj.style.cssText)\r\n                            }\r\n                            for (var j = 0, cj; cj = tr.cells[j]; j++) {\r\n                                if (!ci[j])\r\n                                    break;\r\n                                cj.innerHTML = ci[j].innerHTML;\r\n                                ci[j].getAttribute('width') && cj.setAttribute('width', ci[j].getAttribute('width'));\r\n                                ci[j].getAttribute('vAlign') && cj.setAttribute('vAlign', ci[j].getAttribute('vAlign'));\r\n                                ci[j].getAttribute('align') && cj.setAttribute('align', ci[j].getAttribute('align'));\r\n                                ci[j].style.cssText && (cj.style.cssText = ci[j].style.cssText)\r\n                            }\r\n                        }\r\n                    } else {\r\n                        if (isFullCol) {\r\n                            cellInfo = ut.getCellInfo(td);\r\n                            var maxColNum = 0;\r\n                            for (var j = 0, ci = tableCopyList[0], cj; cj = ci[j++];) {\r\n                                maxColNum += cj.colSpan || 1;\r\n                            }\r\n                            me.__hasEnterExecCommand = true;\r\n                            for (i = 0; i < maxColNum; i++) {\r\n                                me.execCommand('insertcol');\r\n                            }\r\n                            me.__hasEnterExecCommand = false;\r\n                            td = ut.table.rows[0].cells[cellInfo.cellIndex];\r\n                            if (td.tagName == 'TH') {\r\n                                td = ut.table.rows[1].cells[cellInfo.cellIndex];\r\n                            }\r\n                        }\r\n                        for (var i = 0, ci; ci = tableCopyList[i++];) {\r\n                            tmpNode = td;\r\n                            for (var j = 0, cj; cj = ci[j++];) {\r\n                                if (td) {\r\n                                    td.innerHTML = cj.innerHTML;\r\n                                    //todo 定制处理\r\n                                    cj.getAttribute('width') && td.setAttribute('width', cj.getAttribute('width'));\r\n                                    cj.getAttribute('vAlign') && td.setAttribute('vAlign', cj.getAttribute('vAlign'));\r\n                                    cj.getAttribute('align') && td.setAttribute('align', cj.getAttribute('align'));\r\n                                    cj.style.cssText && (td.style.cssText = cj.style.cssText);\r\n                                    preNode = td;\r\n                                    td = td.nextSibling;\r\n                                } else {\r\n                                    var cloneTd = cj.cloneNode(true);\r\n                                    domUtils.removeAttributes(cloneTd, ['class', 'rowSpan', 'colSpan']);\r\n\r\n                                    preNode.parentNode.appendChild(cloneTd)\r\n                                }\r\n                            }\r\n                            td = ut.getNextCell(tmpNode, true, true);\r\n                            if (!tableCopyList[i])\r\n                                break;\r\n                            if (!td) {\r\n                                var cellInfo = ut.getCellInfo(tmpNode);\r\n                                ut.table.insertRow(ut.table.rows.length);\r\n                                ut.update();\r\n                                td = ut.getVSideCell(tmpNode, true);\r\n                            }\r\n                        }\r\n                    }\r\n                    ut.update();\r\n                } else {\r\n                    table = me.document.createElement('table');\r\n                    for (var i = 0, ci; ci = tableCopyList[i++];) {\r\n                        var tr = table.insertRow(table.rows.length);\r\n                        for (var j = 0, cj; cj = ci[j++];) {\r\n                            cloneTd = UT.cloneCell(cj,null,true);\r\n                            domUtils.removeAttributes(cloneTd, ['class']);\r\n                            tr.appendChild(cloneTd)\r\n                        }\r\n                        if (j == 2 && cloneTd.rowSpan > 1) {\r\n                            cloneTd.rowSpan = 1;\r\n                        }\r\n                    }\r\n\r\n                    var defaultValue = getDefaultValue(me),\r\n                        width = me.body.offsetWidth -\r\n                            (needIEHack ? parseInt(domUtils.getComputedStyle(me.body, 'margin-left'), 10) * 2 : 0) - defaultValue.tableBorder * 2 - (me.options.offsetWidth || 0);\r\n                    me.execCommand('insertHTML', '<table  ' +\r\n                        ( isFullCol && isFullRow ? 'width=\"' + width + '\"' : '') +\r\n                        '>' + table.innerHTML.replace(/>\\s*</g, '><').replace(/\\bth\\b/gi, \"td\") + '</table>')\r\n                }\r\n                me.fireEvent('contentchange');\r\n                me.fireEvent('saveScene');\r\n                html.html = '';\r\n                return true;\r\n            } else {\r\n                var div = me.document.createElement(\"div\"), tables;\r\n                div.innerHTML = html.html;\r\n                tables = div.getElementsByTagName(\"table\");\r\n                if (domUtils.findParentByTagName(me.selection.getStart(), 'table')) {\r\n                    utils.each(tables, function (t) {\r\n                        domUtils.remove(t)\r\n                    });\r\n                    if (domUtils.findParentByTagName(me.selection.getStart(), 'caption', true)) {\r\n                        div.innerHTML = div[browser.ie ? 'innerText' : 'textContent'];\r\n                    }\r\n                } else {\r\n                    utils.each(tables, function (table) {\r\n                        removeStyleSize(table, true);\r\n                        domUtils.removeAttributes(table, ['style', 'border']);\r\n                        utils.each(domUtils.getElementsByTagName(table, \"td\"), function (td) {\r\n                            if (isEmptyBlock(td)) {\r\n                                domUtils.fillNode(me.document, td);\r\n                            }\r\n                            removeStyleSize(td, true);\r\n//                            domUtils.removeAttributes(td, ['style'])\r\n                        });\r\n                    });\r\n                }\r\n                html.html = div.innerHTML;\r\n            }\r\n        });\r\n\r\n        me.addListener('afterpaste', function () {\r\n            utils.each(domUtils.getElementsByTagName(me.body, \"table\"), function (table) {\r\n                if (table.offsetWidth > me.body.offsetWidth) {\r\n                    var defaultValue = getDefaultValue(me, table);\r\n                    table.style.width = me.body.offsetWidth - (needIEHack ? parseInt(domUtils.getComputedStyle(me.body, 'margin-left'), 10) * 2 : 0) - defaultValue.tableBorder * 2 - (me.options.offsetWidth || 0) + 'px'\r\n                }\r\n            })\r\n        });\r\n        me.addListener('blur', function () {\r\n            tableCopyList = null;\r\n        });\r\n        var timer;\r\n        me.addListener('keydown', function () {\r\n            clearTimeout(timer);\r\n            timer = setTimeout(function () {\r\n                var rng = me.selection.getRange(),\r\n                    cell = domUtils.findParentByTagName(rng.startContainer, ['th', 'td'], true);\r\n                if (cell) {\r\n                    var table = cell.parentNode.parentNode.parentNode;\r\n                    if (table.offsetWidth > table.getAttribute(\"width\")) {\r\n                        cell.style.wordBreak = \"break-all\";\r\n                    }\r\n                }\r\n\r\n            }, 100);\r\n        });\r\n        me.addListener(\"selectionchange\", function () {\r\n            toggleDraggableState(me, false, \"\", null);\r\n        });\r\n\r\n\r\n        //内容变化时触发索引更新\r\n        //todo 可否考虑标记检测，如果不涉及表格的变化就不进行索引重建和更新\r\n        me.addListener(\"contentchange\", function () {\r\n            var me = this;\r\n            //尽可能排除一些不需要更新的状况\r\n            hideDragLine(me);\r\n            if (getUETableBySelected(me))return;\r\n            var rng = me.selection.getRange();\r\n            var start = rng.startContainer;\r\n            start = domUtils.findParentByTagName(start, ['td', 'th'], true);\r\n            utils.each(domUtils.getElementsByTagName(me.document, 'table'), function (table) {\r\n                if (me.fireEvent(\"excludetable\", table) === true) return;\r\n                table.ueTable = new UT(table);\r\n                //trace:3742\r\n//                utils.each(domUtils.getElementsByTagName(me.document, 'td'), function (td) {\r\n//\r\n//                    if (domUtils.isEmptyBlock(td) && td !== start) {\r\n//                        domUtils.fillNode(me.document, td);\r\n//                        if (browser.ie && browser.version == 6) {\r\n//                            td.innerHTML = '&nbsp;'\r\n//                        }\r\n//                    }\r\n//                });\r\n//                utils.each(domUtils.getElementsByTagName(me.document, 'th'), function (th) {\r\n//                    if (domUtils.isEmptyBlock(th) && th !== start) {\r\n//                        domUtils.fillNode(me.document, th);\r\n//                        if (browser.ie && browser.version == 6) {\r\n//                            th.innerHTML = '&nbsp;'\r\n//                        }\r\n//                    }\r\n//                });\r\n                table.onmouseover = function () {\r\n                    me.fireEvent('tablemouseover', table);\r\n                };\r\n                table.onmousemove = function () {\r\n                    me.fireEvent('tablemousemove', table);\r\n                    me.options.tableDragable && toggleDragButton(true, this, me);\r\n                    utils.defer(function(){\r\n                        me.fireEvent('contentchange',50)\r\n                    },true)\r\n                };\r\n                table.onmouseout = function () {\r\n                    me.fireEvent('tablemouseout', table);\r\n                    toggleDraggableState(me, false, \"\", null);\r\n                    hideDragLine(me);\r\n                };\r\n                table.onclick = function (evt) {\r\n                    evt = me.window.event || evt;\r\n                    var target = getParentTdOrTh(evt.target || evt.srcElement);\r\n                    if (!target)return;\r\n                    var ut = getUETable(target),\r\n                        table = ut.table,\r\n                        cellInfo = ut.getCellInfo(target),\r\n                        cellsRange,\r\n                        rng = me.selection.getRange();\r\n//                    if (\"topLeft\" == inPosition(table, mouseCoords(evt))) {\r\n//                        cellsRange = ut.getCellsRange(ut.table.rows[0].cells[0], ut.getLastCell());\r\n//                        ut.setSelected(cellsRange);\r\n//                        return;\r\n//                    }\r\n//                    if (\"bottomRight\" == inPosition(table, mouseCoords(evt))) {\r\n//\r\n//                        return;\r\n//                    }\r\n                    if (inTableSide(table, target, evt, true)) {\r\n                        var endTdCol = ut.getCell(ut.indexTable[ut.rowsNum - 1][cellInfo.colIndex].rowIndex, ut.indexTable[ut.rowsNum - 1][cellInfo.colIndex].cellIndex);\r\n                        if (evt.shiftKey && ut.selectedTds.length) {\r\n                            if (ut.selectedTds[0] !== endTdCol) {\r\n                                cellsRange = ut.getCellsRange(ut.selectedTds[0], endTdCol);\r\n                                ut.setSelected(cellsRange);\r\n                            } else {\r\n                                rng && rng.selectNodeContents(endTdCol).select();\r\n                            }\r\n                        } else {\r\n                            if (target !== endTdCol) {\r\n                                cellsRange = ut.getCellsRange(target, endTdCol);\r\n                                ut.setSelected(cellsRange);\r\n                            } else {\r\n                                rng && rng.selectNodeContents(endTdCol).select();\r\n                            }\r\n                        }\r\n                        return;\r\n                    }\r\n                    if (inTableSide(table, target, evt)) {\r\n                        var endTdRow = ut.getCell(ut.indexTable[cellInfo.rowIndex][ut.colsNum - 1].rowIndex, ut.indexTable[cellInfo.rowIndex][ut.colsNum - 1].cellIndex);\r\n                        if (evt.shiftKey && ut.selectedTds.length) {\r\n                            if (ut.selectedTds[0] !== endTdRow) {\r\n                                cellsRange = ut.getCellsRange(ut.selectedTds[0], endTdRow);\r\n                                ut.setSelected(cellsRange);\r\n                            } else {\r\n                                rng && rng.selectNodeContents(endTdRow).select();\r\n                            }\r\n                        } else {\r\n                            if (target !== endTdRow) {\r\n                                cellsRange = ut.getCellsRange(target, endTdRow);\r\n                                ut.setSelected(cellsRange);\r\n                            } else {\r\n                                rng && rng.selectNodeContents(endTdRow).select();\r\n                            }\r\n                        }\r\n                    }\r\n                };\r\n            });\r\n\r\n            switchBorderColor(me, true);\r\n        });\r\n\r\n        domUtils.on(me.document, \"mousemove\", mouseMoveEvent);\r\n\r\n        domUtils.on(me.document, \"mouseout\", function (evt) {\r\n            var target = evt.target || evt.srcElement;\r\n            if (target.tagName == \"TABLE\") {\r\n                toggleDraggableState(me, false, \"\", null);\r\n            }\r\n        });\r\n        /**\r\n         * 表格隔行变色\r\n         */\r\n        me.addListener(\"interlacetable\",function(type,table,classList){\r\n            if(!table) return;\r\n            var me = this,\r\n                rows = table.rows,\r\n                len = rows.length,\r\n                getClass = function(list,index,repeat){\r\n                    return list[index] ? list[index] : repeat ? list[index % list.length]: \"\";\r\n                };\r\n            for(var i = 0;i<len;i++){\r\n                rows[i].className = getClass( classList|| me.options.classList,i,true);\r\n            }\r\n        });\r\n        me.addListener(\"uninterlacetable\",function(type,table){\r\n            if(!table) return;\r\n            var me = this,\r\n                rows = table.rows,\r\n                classList = me.options.classList,\r\n                len = rows.length;\r\n            for(var i = 0;i<len;i++){\r\n                domUtils.removeClasses( rows[i], classList );\r\n            }\r\n        });\r\n\r\n        me.addListener(\"mousedown\", mouseDownEvent);\r\n        me.addListener(\"mouseup\", mouseUpEvent);\r\n        //拖动的时候触发mouseup\r\n        domUtils.on( me.body, 'dragstart', function( evt ){\r\n            mouseUpEvent.call( me, 'dragstart', evt );\r\n        });\r\n        me.addOutputRule(function(root){\r\n            utils.each(root.getNodesByTagName('div'),function(n){\r\n                if (n.getAttr('id') == 'ue_tableDragLine') {\r\n                    n.parentNode.removeChild(n);\r\n                }\r\n            });\r\n        });\r\n\r\n        var currentRowIndex = 0;\r\n        me.addListener(\"mousedown\", function () {\r\n            currentRowIndex = 0;\r\n        });\r\n        me.addListener('tabkeydown', function () {\r\n            var range = this.selection.getRange(),\r\n                common = range.getCommonAncestor(true, true),\r\n                table = domUtils.findParentByTagName(common, 'table');\r\n            if (table) {\r\n                if (domUtils.findParentByTagName(common, 'caption', true)) {\r\n                    var cell = domUtils.getElementsByTagName(table, 'th td');\r\n                    if (cell && cell.length) {\r\n                        range.setStart(cell[0], 0).setCursor(false, true)\r\n                    }\r\n                } else {\r\n                    var cell = domUtils.findParentByTagName(common, ['td', 'th'], true),\r\n                        ua = getUETable(cell);\r\n                    currentRowIndex = cell.rowSpan > 1 ? currentRowIndex : ua.getCellInfo(cell).rowIndex;\r\n                    var nextCell = ua.getTabNextCell(cell, currentRowIndex);\r\n                    if (nextCell) {\r\n                        if (isEmptyBlock(nextCell)) {\r\n                            range.setStart(nextCell, 0).setCursor(false, true)\r\n                        } else {\r\n                            range.selectNodeContents(nextCell).select()\r\n                        }\r\n                    } else {\r\n                        me.fireEvent('saveScene');\r\n                        me.__hasEnterExecCommand = true;\r\n                        this.execCommand('insertrownext');\r\n                        me.__hasEnterExecCommand = false;\r\n                        range = this.selection.getRange();\r\n                        range.setStart(table.rows[table.rows.length - 1].cells[0], 0).setCursor();\r\n                        me.fireEvent('saveScene');\r\n                    }\r\n                }\r\n                return true;\r\n            }\r\n\r\n        });\r\n        browser.ie && me.addListener('selectionchange', function () {\r\n            toggleDraggableState(this, false, \"\", null);\r\n        });\r\n        me.addListener(\"keydown\", function (type, evt) {\r\n            var me = this;\r\n            //处理在表格的最后一个输入tab产生新的表格\r\n            var keyCode = evt.keyCode || evt.which;\r\n            if (keyCode == 8 || keyCode == 46) {\r\n                return;\r\n            }\r\n            var notCtrlKey = !evt.ctrlKey && !evt.metaKey && !evt.shiftKey && !evt.altKey;\r\n            notCtrlKey && removeSelectedClass(domUtils.getElementsByTagName(me.body, \"td\"));\r\n            var ut = getUETableBySelected(me);\r\n            if (!ut) return;\r\n            notCtrlKey && ut.clearSelected();\r\n        });\r\n\r\n        me.addListener(\"beforegetcontent\", function () {\r\n            switchBorderColor(this, false);\r\n            browser.ie && utils.each(this.document.getElementsByTagName('caption'), function (ci) {\r\n                if (domUtils.isEmptyNode(ci)) {\r\n                    ci.innerHTML = '&nbsp;'\r\n                }\r\n            });\r\n        });\r\n        me.addListener(\"aftergetcontent\", function () {\r\n            switchBorderColor(this, true);\r\n        });\r\n        me.addListener(\"getAllHtml\", function () {\r\n            removeSelectedClass(me.document.getElementsByTagName(\"td\"));\r\n        });\r\n        //修正全屏状态下插入的表格宽度在非全屏状态下撑开编辑器的情况\r\n        me.addListener(\"fullscreenchanged\", function (type, fullscreen) {\r\n            if (!fullscreen) {\r\n                var ratio = this.body.offsetWidth / document.body.offsetWidth,\r\n                    tables = domUtils.getElementsByTagName(this.body, \"table\");\r\n                utils.each(tables, function (table) {\r\n                    if (table.offsetWidth < me.body.offsetWidth) return false;\r\n                    var tds = domUtils.getElementsByTagName(table, \"td\"),\r\n                        backWidths = [];\r\n                    utils.each(tds, function (td) {\r\n                        backWidths.push(td.offsetWidth);\r\n                    });\r\n                    for (var i = 0, td; td = tds[i]; i++) {\r\n                        td.setAttribute(\"width\", Math.floor(backWidths[i] * ratio));\r\n                    }\r\n                    table.setAttribute(\"width\", Math.floor(getTableWidth(me, needIEHack, getDefaultValue(me))))\r\n                });\r\n            }\r\n        });\r\n\r\n        //重写execCommand命令，用于处理框选时的处理\r\n        var oldExecCommand = me.execCommand;\r\n        me.execCommand = function (cmd, datatat) {\r\n\r\n            var me = this,\r\n                args = arguments;\r\n\r\n            cmd = cmd.toLowerCase();\r\n            var ut = getUETableBySelected(me), tds,\r\n                range = new dom.Range(me.document),\r\n                cmdFun = me.commands[cmd] || UE.commands[cmd],\r\n                result;\r\n            if (!cmdFun) return;\r\n            if (ut && !commands[cmd] && !cmdFun.notNeedUndo && !me.__hasEnterExecCommand) {\r\n                me.__hasEnterExecCommand = true;\r\n                me.fireEvent(\"beforeexeccommand\", cmd);\r\n                tds = ut.selectedTds;\r\n                var lastState = -2, lastValue = -2, value, state;\r\n                for (var i = 0, td; td = tds[i]; i++) {\r\n                    if (isEmptyBlock(td)) {\r\n                        range.setStart(td, 0).setCursor(false, true)\r\n                    } else {\r\n                        range.selectNode(td).select(true);\r\n                    }\r\n                    state = me.queryCommandState(cmd);\r\n                    value = me.queryCommandValue(cmd);\r\n                    if (state != -1) {\r\n                        if (lastState !== state || lastValue !== value) {\r\n                            me._ignoreContentChange = true;\r\n                            result = oldExecCommand.apply(me, arguments);\r\n                            me._ignoreContentChange = false;\r\n\r\n                        }\r\n                        lastState = me.queryCommandState(cmd);\r\n                        lastValue = me.queryCommandValue(cmd);\r\n                        if (domUtils.isEmptyBlock(td)) {\r\n                            domUtils.fillNode(me.document, td)\r\n                        }\r\n                    }\r\n                }\r\n                range.setStart(tds[0], 0).shrinkBoundary(true).setCursor(false, true);\r\n                me.fireEvent('contentchange');\r\n                me.fireEvent(\"afterexeccommand\", cmd);\r\n                me.__hasEnterExecCommand = false;\r\n                me._selectionChange();\r\n            } else {\r\n                result = oldExecCommand.apply(me, arguments);\r\n            }\r\n            return result;\r\n        };\r\n\r\n\r\n    });\r\n    /**\r\n     * 删除obj的宽高style，改成属性宽高\r\n     * @param obj\r\n     * @param replaceToProperty\r\n     */\r\n    function removeStyleSize(obj, replaceToProperty) {\r\n        removeStyle(obj, \"width\", true);\r\n        removeStyle(obj, \"height\", true);\r\n    }\r\n\r\n    function removeStyle(obj, styleName, replaceToProperty) {\r\n        if (obj.style[styleName]) {\r\n            replaceToProperty && obj.setAttribute(styleName, parseInt(obj.style[styleName], 10));\r\n            obj.style[styleName] = \"\";\r\n        }\r\n    }\r\n\r\n    function getParentTdOrTh(ele) {\r\n        if (ele.tagName == \"TD\" || ele.tagName == \"TH\") return ele;\r\n        var td;\r\n        if (td = domUtils.findParentByTagName(ele, \"td\", true) || domUtils.findParentByTagName(ele, \"th\", true)) return td;\r\n        return null;\r\n    }\r\n\r\n    function isEmptyBlock(node) {\r\n        var reg = new RegExp(domUtils.fillChar, 'g');\r\n        if (node[browser.ie ? 'innerText' : 'textContent'].replace(/^\\s*$/, '').replace(reg, '').length > 0) {\r\n            return 0;\r\n        }\r\n        for (var n in dtd.$isNotEmpty) {\r\n            if (node.getElementsByTagName(n).length) {\r\n                return 0;\r\n            }\r\n        }\r\n        return 1;\r\n    }\r\n\r\n\r\n    function mouseCoords(evt) {\r\n        if (evt.pageX || evt.pageY) {\r\n            return { x:evt.pageX, y:evt.pageY };\r\n        }\r\n        return {\r\n            x:evt.clientX + me.document.body.scrollLeft - me.document.body.clientLeft,\r\n            y:evt.clientY + me.document.body.scrollTop - me.document.body.clientTop\r\n        };\r\n    }\r\n\r\n    function mouseMoveEvent(evt) {\r\n\r\n        if( isEditorDisabled() ) {\r\n            return;\r\n        }\r\n\r\n        try {\r\n\r\n            //普通状态下鼠标移动\r\n            var target = getParentTdOrTh(evt.target || evt.srcElement),\r\n                pos;\r\n\r\n            //区分用户的行为是拖动还是双击\r\n            if( isInResizeBuffer  ) {\r\n\r\n                me.body.style.webkitUserSelect = 'none';\r\n\r\n                if( Math.abs( userActionStatus.x - evt.clientX ) > offsetOfTableCell || Math.abs( userActionStatus.y - evt.clientY ) > offsetOfTableCell ) {\r\n                    clearTableDragTimer();\r\n                    isInResizeBuffer = false;\r\n                    singleClickState = 0;\r\n                    //drag action\r\n                    tableBorderDrag(evt);\r\n                }\r\n            }\r\n\r\n            //修改单元格大小时的鼠标移动\r\n            if (onDrag && dragTd) {\r\n                singleClickState = 0;\r\n                me.body.style.webkitUserSelect = 'none';\r\n                me.selection.getNative()[browser.ie9below ? 'empty' : 'removeAllRanges']();\r\n                pos = mouseCoords(evt);\r\n                toggleDraggableState(me, true, onDrag, pos, target);\r\n                if (onDrag == \"h\") {\r\n                    dragLine.style.left = getPermissionX(dragTd, evt) + \"px\";\r\n                } else if (onDrag == \"v\") {\r\n                    dragLine.style.top = getPermissionY(dragTd, evt) + \"px\";\r\n                }\r\n                return;\r\n            }\r\n            //当鼠标处于table上时，修改移动过程中的光标状态\r\n            if (target) {\r\n                //针对使用table作为容器的组件不触发拖拽效果\r\n                if (me.fireEvent('excludetable', target) === true)\r\n                    return;\r\n                pos = mouseCoords(evt);\r\n                var state = getRelation(target, pos),\r\n                    table = domUtils.findParentByTagName(target, \"table\", true);\r\n\r\n                if (inTableSide(table, target, evt, true)) {\r\n                    if (me.fireEvent(\"excludetable\", table) === true) return;\r\n                    me.body.style.cursor = \"url(\" + me.options.cursorpath + \"h.png),pointer\";\r\n                } else if (inTableSide(table, target, evt)) {\r\n                    if (me.fireEvent(\"excludetable\", table) === true) return;\r\n                    me.body.style.cursor = \"url(\" + me.options.cursorpath + \"v.png),pointer\";\r\n                } else {\r\n                    me.body.style.cursor = \"text\";\r\n                    var curCell = target;\r\n                    if (/\\d/.test(state)) {\r\n                        state = state.replace(/\\d/, '');\r\n                        target = getUETable(target).getPreviewCell(target, state == \"v\");\r\n                    }\r\n                    //位于第一行的顶部或者第一列的左边时不可拖动\r\n                    toggleDraggableState(me, target ? !!state : false, target ? state : '', pos, target);\r\n\r\n                }\r\n            } else {\r\n                toggleDragButton(false, table, me);\r\n            }\r\n\r\n        } catch (e) {\r\n            showError(e);\r\n        }\r\n    }\r\n\r\n    var dragButtonTimer;\r\n\r\n    function toggleDragButton(show, table, editor) {\r\n        if (!show) {\r\n            if (dragOver)return;\r\n            dragButtonTimer = setTimeout(function () {\r\n                !dragOver && dragButton && dragButton.parentNode && dragButton.parentNode.removeChild(dragButton);\r\n            }, 2000);\r\n        } else {\r\n            createDragButton(table, editor);\r\n        }\r\n    }\r\n\r\n    function createDragButton(table, editor) {\r\n        var pos = domUtils.getXY(table),\r\n            doc = table.ownerDocument;\r\n        if (dragButton && dragButton.parentNode)return dragButton;\r\n        dragButton = doc.createElement(\"div\");\r\n        dragButton.contentEditable = false;\r\n        dragButton.innerHTML = \"\";\r\n        dragButton.style.cssText = \"width:15px;height:15px;background-image:url(\" + editor.options.UEDITOR_HOME_URL + \"dialogs/table/dragicon.png);position: absolute;cursor:move;top:\" + (pos.y - 15) + \"px;left:\" + (pos.x) + \"px;\";\r\n        domUtils.unSelectable(dragButton);\r\n        dragButton.onmouseover = function (evt) {\r\n            dragOver = true;\r\n        };\r\n        dragButton.onmouseout = function (evt) {\r\n            dragOver = false;\r\n        };\r\n        domUtils.on(dragButton, 'click', function (type, evt) {\r\n            doClick(evt, this);\r\n        });\r\n        domUtils.on(dragButton, 'dblclick', function (type, evt) {\r\n            doDblClick(evt);\r\n        });\r\n        domUtils.on(dragButton, 'dragstart', function (type, evt) {\r\n            domUtils.preventDefault(evt);\r\n        });\r\n        var timer;\r\n\r\n        function doClick(evt, button) {\r\n            // 部分浏览器下需要清理\r\n            clearTimeout(timer);\r\n            timer = setTimeout(function () {\r\n                editor.fireEvent(\"tableClicked\", table, button);\r\n            }, 300);\r\n        }\r\n\r\n        function doDblClick(evt) {\r\n            clearTimeout(timer);\r\n            var ut = getUETable(table),\r\n                start = table.rows[0].cells[0],\r\n                end = ut.getLastCell(),\r\n                range = ut.getCellsRange(start, end);\r\n            editor.selection.getRange().setStart(start, 0).setCursor(false, true);\r\n            ut.setSelected(range);\r\n        }\r\n\r\n        doc.body.appendChild(dragButton);\r\n    }\r\n\r\n\r\n//    function inPosition(table, pos) {\r\n//        var tablePos = domUtils.getXY(table),\r\n//            width = table.offsetWidth,\r\n//            height = table.offsetHeight;\r\n//        if (pos.x - tablePos.x < 5 && pos.y - tablePos.y < 5) {\r\n//            return \"topLeft\";\r\n//        } else if (tablePos.x + width - pos.x < 5 && tablePos.y + height - pos.y < 5) {\r\n//            return \"bottomRight\";\r\n//        }\r\n//    }\r\n\r\n    function inTableSide(table, cell, evt, top) {\r\n        var pos = mouseCoords(evt),\r\n            state = getRelation(cell, pos);\r\n\r\n        if (top) {\r\n            var caption = table.getElementsByTagName(\"caption\")[0],\r\n                capHeight = caption ? caption.offsetHeight : 0;\r\n            return (state == \"v1\") && ((pos.y - domUtils.getXY(table).y - capHeight) < 8);\r\n        } else {\r\n            return (state == \"h1\") && ((pos.x - domUtils.getXY(table).x) < 8);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * 获取拖动时允许的X轴坐标\r\n     * @param dragTd\r\n     * @param evt\r\n     */\r\n    function getPermissionX(dragTd, evt) {\r\n        var ut = getUETable(dragTd);\r\n        if (ut) {\r\n            var preTd = ut.getSameEndPosCells(dragTd, \"x\")[0],\r\n                nextTd = ut.getSameStartPosXCells(dragTd)[0],\r\n                mouseX = mouseCoords(evt).x,\r\n                left = (preTd ? domUtils.getXY(preTd).x : domUtils.getXY(ut.table).x) + 20 ,\r\n                right = nextTd ? domUtils.getXY(nextTd).x + nextTd.offsetWidth - 20 : (me.body.offsetWidth + 5 || parseInt(domUtils.getComputedStyle(me.body, \"width\"), 10));\r\n\r\n            left += cellMinWidth;\r\n            right -= cellMinWidth;\r\n\r\n            return mouseX < left ? left : mouseX > right ? right : mouseX;\r\n        }\r\n    }\r\n\r\n    /**\r\n     * 获取拖动时允许的Y轴坐标\r\n     */\r\n    function getPermissionY(dragTd, evt) {\r\n        try {\r\n            var top = domUtils.getXY(dragTd).y,\r\n                mousePosY = mouseCoords(evt).y;\r\n            return mousePosY < top ? top : mousePosY;\r\n        } catch (e) {\r\n            showError(e);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * 移动状态切换\r\n     */\r\n    function toggleDraggableState(editor, draggable, dir, mousePos, cell) {\r\n        try {\r\n            editor.body.style.cursor = dir == \"h\" ? \"col-resize\" : dir == \"v\" ? \"row-resize\" : \"text\";\r\n            if (browser.ie) {\r\n                if (dir && !mousedown && !getUETableBySelected(editor)) {\r\n                    getDragLine(editor, editor.document);\r\n                    showDragLineAt(dir, cell);\r\n                } else {\r\n                    hideDragLine(editor)\r\n                }\r\n            }\r\n            onBorder = draggable;\r\n        } catch (e) {\r\n            showError(e);\r\n        }\r\n    }\r\n\r\n    /**\r\n     * 获取与UETable相关的resize line\r\n     * @param uetable UETable对象\r\n     */\r\n    function getResizeLineByUETable() {\r\n\r\n        var lineId = '_UETableResizeLine',\r\n            line = this.document.getElementById( lineId );\r\n\r\n        if( !line ) {\r\n            line = this.document.createElement(\"div\");\r\n            line.id = lineId;\r\n            line.contnetEditable = false;\r\n            line.setAttribute(\"unselectable\", \"on\");\r\n\r\n            var styles = {\r\n                width: 2*cellBorderWidth + 1 + 'px',\r\n                position: 'absolute',\r\n                'z-index': 100000,\r\n                cursor: 'col-resize',\r\n                background: 'red',\r\n                display: 'none'\r\n            };\r\n\r\n            //切换状态\r\n            line.onmouseout = function(){\r\n                this.style.display = 'none';\r\n            };\r\n\r\n            utils.extend( line.style, styles );\r\n\r\n            this.document.body.appendChild( line );\r\n\r\n        }\r\n\r\n        return line;\r\n\r\n    }\r\n\r\n    /**\r\n     * 更新resize-line\r\n     */\r\n    function updateResizeLine( cell, uetable ) {\r\n\r\n        var line = getResizeLineByUETable.call( this ),\r\n            table = uetable.table,\r\n            styles = {\r\n                top: domUtils.getXY( table ).y + 'px',\r\n                left: domUtils.getXY( cell).x + cell.offsetWidth - cellBorderWidth + 'px',\r\n                display: 'block',\r\n                height: table.offsetHeight + 'px'\r\n            };\r\n\r\n        utils.extend( line.style, styles );\r\n\r\n    }\r\n\r\n    /**\r\n     * 显示resize-line\r\n     */\r\n    function showResizeLine( cell ) {\r\n\r\n        var uetable = getUETable( cell );\r\n\r\n        updateResizeLine.call( this, cell, uetable );\r\n\r\n    }\r\n\r\n    /**\r\n     * 获取鼠标与当前单元格的相对位置\r\n     * @param ele\r\n     * @param mousePos\r\n     */\r\n    function getRelation(ele, mousePos) {\r\n        var elePos = domUtils.getXY(ele);\r\n\r\n        if( !elePos ) {\r\n            return '';\r\n        }\r\n\r\n        if (elePos.x + ele.offsetWidth - mousePos.x < cellBorderWidth) {\r\n            return \"h\";\r\n        }\r\n        if (mousePos.x - elePos.x < cellBorderWidth) {\r\n            return 'h1'\r\n        }\r\n        if (elePos.y + ele.offsetHeight - mousePos.y < cellBorderWidth) {\r\n            return \"v\";\r\n        }\r\n        if (mousePos.y - elePos.y < cellBorderWidth) {\r\n            return 'v1'\r\n        }\r\n        return '';\r\n    }\r\n\r\n    function mouseDownEvent(type, evt) {\r\n\r\n        if( isEditorDisabled() ) {\r\n            return ;\r\n        }\r\n\r\n        userActionStatus = {\r\n            x: evt.clientX,\r\n            y: evt.clientY\r\n        };\r\n\r\n        //右键菜单单独处理\r\n        if (evt.button == 2) {\r\n            var ut = getUETableBySelected(me),\r\n                flag = false;\r\n\r\n            if (ut) {\r\n                var td = getTargetTd(me, evt);\r\n                utils.each(ut.selectedTds, function (ti) {\r\n                    if (ti === td) {\r\n                        flag = true;\r\n                    }\r\n                });\r\n                if (!flag) {\r\n                    removeSelectedClass(domUtils.getElementsByTagName(me.body, \"th td\"));\r\n                    ut.clearSelected()\r\n                } else {\r\n                    td = ut.selectedTds[0];\r\n                    setTimeout(function () {\r\n                        me.selection.getRange().setStart(td, 0).setCursor(false, true);\r\n                    }, 0);\r\n\r\n                }\r\n            }\r\n        } else {\r\n            tableClickHander( evt );\r\n        }\r\n\r\n    }\r\n\r\n    //清除表格的计时器\r\n    function clearTableTimer() {\r\n        tabTimer && clearTimeout( tabTimer );\r\n        tabTimer = null;\r\n    }\r\n\r\n    //双击收缩\r\n    function tableDbclickHandler(evt) {\r\n        singleClickState = 0;\r\n        evt = evt || me.window.event;\r\n        var target = getParentTdOrTh(evt.target || evt.srcElement);\r\n        if (target) {\r\n            var h;\r\n            if (h = getRelation(target, mouseCoords(evt))) {\r\n\r\n                hideDragLine( me );\r\n\r\n                if (h == 'h1') {\r\n                    h = 'h';\r\n                    if (inTableSide(domUtils.findParentByTagName(target, \"table\"), target, evt)) {\r\n                        me.execCommand('adaptbywindow');\r\n                    } else {\r\n                        target = getUETable(target).getPreviewCell(target);\r\n                        if (target) {\r\n                            var rng = me.selection.getRange();\r\n                            rng.selectNodeContents(target).setCursor(true, true)\r\n                        }\r\n                    }\r\n                }\r\n                if (h == 'h') {\r\n                    var ut = getUETable(target),\r\n                        table = ut.table,\r\n                        cells = getCellsByMoveBorder( target, table, true );\r\n\r\n                    cells = extractArray( cells, 'left' );\r\n\r\n                    ut.width = ut.offsetWidth;\r\n\r\n                    var oldWidth = [],\r\n                        newWidth = [];\r\n\r\n                    utils.each( cells, function( cell ){\r\n\r\n                        oldWidth.push( cell.offsetWidth );\r\n\r\n                    } );\r\n\r\n                    utils.each( cells, function( cell ){\r\n\r\n                        cell.removeAttribute(\"width\");\r\n\r\n                    } );\r\n\r\n                    window.setTimeout( function(){\r\n\r\n                        //是否允许改变\r\n                        var changeable = true;\r\n\r\n                        utils.each( cells, function( cell, index ){\r\n\r\n                            var width = cell.offsetWidth;\r\n\r\n                            if( width > oldWidth[index] ) {\r\n                                changeable = false;\r\n                                return false;\r\n                            }\r\n\r\n                            newWidth.push( width );\r\n\r\n                        } );\r\n\r\n                        var change = changeable ? newWidth : oldWidth;\r\n\r\n                        utils.each( cells, function( cell, index ){\r\n\r\n                            cell.width = change[index] - getTabcellSpace();\r\n\r\n                        } );\r\n\r\n\r\n                    }, 0 );\r\n\r\n//                    minWidth -= cellMinWidth;\r\n//\r\n//                    table.removeAttribute(\"width\");\r\n//                    utils.each(cells, function (cell) {\r\n//                        cell.style.width = \"\";\r\n//                        cell.width -= minWidth;\r\n//                    });\r\n\r\n                }\r\n            }\r\n        }\r\n    }\r\n\r\n    function tableClickHander( evt ) {\r\n\r\n        removeSelectedClass(domUtils.getElementsByTagName(me.body, \"td th\"));\r\n        //trace:3113\r\n        //选中单元格，点击table外部，不会清掉table上挂的ueTable,会引起getUETableBySelected方法返回值\r\n        utils.each(me.document.getElementsByTagName('table'), function (t) {\r\n            t.ueTable = null;\r\n        });\r\n        startTd = getTargetTd(me, evt);\r\n        if( !startTd ) return;\r\n        var table = domUtils.findParentByTagName(startTd, \"table\", true);\r\n        ut = getUETable(table);\r\n        ut && ut.clearSelected();\r\n\r\n        //判断当前鼠标状态\r\n        if (!onBorder) {\r\n            me.document.body.style.webkitUserSelect = '';\r\n            mousedown = true;\r\n            me.addListener('mouseover', mouseOverEvent);\r\n        } else {\r\n            //边框上的动作处理\r\n            borderActionHandler( evt );\r\n        }\r\n\r\n\r\n    }\r\n\r\n    //处理表格边框上的动作, 这里做延时处理，避免两种动作互相影响\r\n    function borderActionHandler( evt ) {\r\n\r\n        if ( browser.ie ) {\r\n            evt = reconstruct(evt );\r\n        }\r\n\r\n        clearTableDragTimer();\r\n\r\n        //是否正在等待resize的缓冲中\r\n        isInResizeBuffer = true;\r\n\r\n        tableDragTimer = setTimeout(function(){\r\n            tableBorderDrag( evt );\r\n        }, dblclickTime);\r\n\r\n    }\r\n\r\n    function extractArray( originArr, key ) {\r\n\r\n        var result = [],\r\n            tmp = null;\r\n\r\n        for( var i = 0, len = originArr.length; i<len; i++ ) {\r\n\r\n            tmp = originArr[ i ][ key ];\r\n\r\n            if( tmp ) {\r\n                result.push( tmp );\r\n            }\r\n\r\n        }\r\n\r\n        return result;\r\n\r\n    }\r\n\r\n    function clearTableDragTimer() {\r\n        tableDragTimer && clearTimeout(tableDragTimer);\r\n        tableDragTimer = null;\r\n    }\r\n\r\n    function reconstruct( obj ) {\r\n\r\n        var attrs = ['pageX', 'pageY', 'clientX', 'clientY', 'srcElement', 'target'],\r\n            newObj = {};\r\n\r\n        if( obj ) {\r\n\r\n            for( var i = 0, key, val; key = attrs[i]; i++ ) {\r\n                val=obj[ key ];\r\n                val && (newObj[ key ] = val);\r\n            }\r\n\r\n        }\r\n\r\n        return newObj;\r\n\r\n    }\r\n\r\n    //边框拖动\r\n    function tableBorderDrag( evt ) {\r\n\r\n        isInResizeBuffer = false;\r\n\r\n        startTd = evt.target || evt.srcElement;\r\n        if( !startTd ) return;\r\n        var state = getRelation(startTd, mouseCoords(evt));\r\n        if (/\\d/.test(state)) {\r\n            state = state.replace(/\\d/, '');\r\n            startTd = getUETable(startTd).getPreviewCell(startTd, state == 'v');\r\n        }\r\n        hideDragLine(me);\r\n        getDragLine(me, me.document);\r\n        me.fireEvent('saveScene');\r\n        showDragLineAt(state, startTd);\r\n        mousedown = true;\r\n        //拖动开始\r\n        onDrag = state;\r\n        dragTd = startTd;\r\n    }\r\n\r\n    function mouseUpEvent(type, evt) {\r\n\r\n        if( isEditorDisabled() ) {\r\n            return ;\r\n        }\r\n\r\n        clearTableDragTimer();\r\n\r\n        isInResizeBuffer = false;\r\n\r\n        if( onBorder ) {\r\n            singleClickState = ++singleClickState % 3;\r\n\r\n            userActionStatus = {\r\n                x: evt.clientX,\r\n                y: evt.clientY\r\n            };\r\n\r\n            tableResizeTimer = setTimeout(function(){\r\n                singleClickState > 0 && singleClickState--;\r\n            }, dblclickTime );\r\n\r\n            if( singleClickState === 2 ) {\r\n\r\n                singleClickState = 0;\r\n                tableDbclickHandler(evt);\r\n                return;\r\n\r\n            }\r\n\r\n        }\r\n\r\n        if (evt.button == 2)return;\r\n        var me = this;\r\n        //清除表格上原生跨选问题\r\n        var range = me.selection.getRange(),\r\n            start = domUtils.findParentByTagName(range.startContainer, 'table', true),\r\n            end = domUtils.findParentByTagName(range.endContainer, 'table', true);\r\n\r\n        if (start || end) {\r\n            if (start === end) {\r\n                start = domUtils.findParentByTagName(range.startContainer, ['td', 'th', 'caption'], true);\r\n                end = domUtils.findParentByTagName(range.endContainer, ['td', 'th', 'caption'], true);\r\n                if (start !== end) {\r\n                    me.selection.clearRange()\r\n                }\r\n            } else {\r\n                me.selection.clearRange()\r\n            }\r\n        }\r\n        mousedown = false;\r\n        me.document.body.style.webkitUserSelect = '';\r\n        //拖拽状态下的mouseUP\r\n        if ( onDrag && dragTd ) {\r\n\r\n            me.selection.getNative()[browser.ie9below ? 'empty' : 'removeAllRanges']();\r\n\r\n            singleClickState = 0;\r\n            dragLine = me.document.getElementById('ue_tableDragLine');\r\n\r\n            // trace 3973\r\n            if (dragLine) {\r\n                var dragTdPos = domUtils.getXY(dragTd),\r\n                    dragLinePos = domUtils.getXY(dragLine);\r\n\r\n                switch (onDrag) {\r\n                    case \"h\":\r\n                        changeColWidth(dragTd, dragLinePos.x - dragTdPos.x);\r\n                        break;\r\n                    case \"v\":\r\n                        changeRowHeight(dragTd, dragLinePos.y - dragTdPos.y - dragTd.offsetHeight);\r\n                        break;\r\n                    default:\r\n                }\r\n                onDrag = \"\";\r\n                dragTd = null;\r\n\r\n                hideDragLine(me);\r\n                me.fireEvent('saveScene');\r\n                return;\r\n            }\r\n        }\r\n        //正常状态下的mouseup\r\n        if (!startTd) {\r\n            var target = domUtils.findParentByTagName(evt.target || evt.srcElement, \"td\", true);\r\n            if (!target) target = domUtils.findParentByTagName(evt.target || evt.srcElement, \"th\", true);\r\n            if (target && (target.tagName == \"TD\" || target.tagName == \"TH\")) {\r\n                if (me.fireEvent(\"excludetable\", target) === true) return;\r\n                range = new dom.Range(me.document);\r\n                range.setStart(target, 0).setCursor(false, true);\r\n            }\r\n        } else {\r\n            var ut = getUETable(startTd),\r\n                cell = ut ? ut.selectedTds[0] : null;\r\n            if (cell) {\r\n                range = new dom.Range(me.document);\r\n                if (domUtils.isEmptyBlock(cell)) {\r\n                    range.setStart(cell, 0).setCursor(false, true);\r\n                } else {\r\n                    range.selectNodeContents(cell).shrinkBoundary().setCursor(false, true);\r\n                }\r\n            } else {\r\n                range = me.selection.getRange().shrinkBoundary();\r\n                if (!range.collapsed) {\r\n                    var start = domUtils.findParentByTagName(range.startContainer, ['td', 'th'], true),\r\n                        end = domUtils.findParentByTagName(range.endContainer, ['td', 'th'], true);\r\n                    //在table里边的不能清除\r\n                    if (start && !end || !start && end || start && end && start !== end) {\r\n                        range.setCursor(false, true);\r\n                    }\r\n                }\r\n            }\r\n            startTd = null;\r\n            me.removeListener('mouseover', mouseOverEvent);\r\n        }\r\n        me._selectionChange(250, evt);\r\n    }\r\n\r\n    function mouseOverEvent(type, evt) {\r\n\r\n        if( isEditorDisabled() ) {\r\n            return;\r\n        }\r\n\r\n        var me = this,\r\n            tar = evt.target || evt.srcElement;\r\n        currentTd = domUtils.findParentByTagName(tar, \"td\", true) || domUtils.findParentByTagName(tar, \"th\", true);\r\n        //需要判断两个TD是否位于同一个表格内\r\n        if (startTd && currentTd &&\r\n            ((startTd.tagName == \"TD\" && currentTd.tagName == \"TD\") || (startTd.tagName == \"TH\" && currentTd.tagName == \"TH\")) &&\r\n            domUtils.findParentByTagName(startTd, 'table') == domUtils.findParentByTagName(currentTd, 'table')) {\r\n            var ut = getUETable(currentTd);\r\n            if (startTd != currentTd) {\r\n                me.document.body.style.webkitUserSelect = 'none';\r\n                me.selection.getNative()[browser.ie9below ? 'empty' : 'removeAllRanges']();\r\n                var range = ut.getCellsRange(startTd, currentTd);\r\n                ut.setSelected(range);\r\n            } else {\r\n                me.document.body.style.webkitUserSelect = '';\r\n                ut.clearSelected();\r\n            }\r\n\r\n        }\r\n        evt.preventDefault ? evt.preventDefault() : (evt.returnValue = false);\r\n    }\r\n\r\n    function setCellHeight(cell, height, backHeight) {\r\n        var lineHight = parseInt(domUtils.getComputedStyle(cell, \"line-height\"), 10),\r\n            tmpHeight = backHeight + height;\r\n        height = tmpHeight < lineHight ? lineHight : tmpHeight;\r\n        if (cell.style.height) cell.style.height = \"\";\r\n        cell.rowSpan == 1 ? cell.setAttribute(\"height\", height) : (cell.removeAttribute && cell.removeAttribute(\"height\"));\r\n    }\r\n\r\n    function getWidth(cell) {\r\n        if (!cell)return 0;\r\n        return parseInt(domUtils.getComputedStyle(cell, \"width\"), 10);\r\n    }\r\n\r\n    function changeColWidth(cell, changeValue) {\r\n\r\n        var ut = getUETable(cell);\r\n        if (ut) {\r\n\r\n            //根据当前移动的边框获取相关的单元格\r\n            var table = ut.table,\r\n                cells = getCellsByMoveBorder( cell, table );\r\n\r\n            table.style.width = \"\";\r\n            table.removeAttribute(\"width\");\r\n\r\n            //修正改变量\r\n            changeValue = correctChangeValue( changeValue, cell, cells );\r\n\r\n            if (cell.nextSibling) {\r\n\r\n                var i=0;\r\n\r\n                utils.each( cells, function( cellGroup ){\r\n\r\n                    cellGroup.left.width = (+cellGroup.left.width)+changeValue;\r\n                    cellGroup.right && ( cellGroup.right.width = (+cellGroup.right.width)-changeValue );\r\n\r\n                } );\r\n\r\n            } else {\r\n\r\n                utils.each( cells, function( cellGroup ){\r\n                    cellGroup.left.width -= -changeValue;\r\n                } );\r\n\r\n            }\r\n        }\r\n\r\n    }\r\n\r\n    function isEditorDisabled() {\r\n        return me.body.contentEditable === \"false\";\r\n    }\r\n\r\n    function changeRowHeight(td, changeValue) {\r\n        if (Math.abs(changeValue) < 10) return;\r\n        var ut = getUETable(td);\r\n        if (ut) {\r\n            var cells = ut.getSameEndPosCells(td, \"y\"),\r\n            //备份需要连带变化的td的原始高度，否则后期无法获取正确的值\r\n                backHeight = cells[0] ? cells[0].offsetHeight : 0;\r\n            for (var i = 0, cell; cell = cells[i++];) {\r\n                setCellHeight(cell, changeValue, backHeight);\r\n            }\r\n        }\r\n\r\n    }\r\n\r\n    /**\r\n     * 获取调整单元格大小的相关单元格\r\n     * @isContainMergeCell 返回的结果中是否包含发生合并后的单元格\r\n     */\r\n    function getCellsByMoveBorder( cell, table, isContainMergeCell ) {\r\n\r\n        if( !table ) {\r\n            table = domUtils.findParentByTagName( cell, 'table' );\r\n        }\r\n\r\n        if( !table ) {\r\n            return null;\r\n        }\r\n\r\n        //获取到该单元格所在行的序列号\r\n        var index = domUtils.getNodeIndex( cell ),\r\n            temp = cell,\r\n            rows = table.rows,\r\n            colIndex = 0;\r\n\r\n        while( temp ) {\r\n            //获取到当前单元格在未发生单元格合并时的序列\r\n            if( temp.nodeType === 1 ) {\r\n                colIndex += (temp.colSpan || 1);\r\n            }\r\n            temp = temp.previousSibling;\r\n        }\r\n\r\n        temp = null;\r\n\r\n        //记录想关的单元格\r\n        var borderCells = [];\r\n\r\n        utils.each(rows, function( tabRow ){\r\n\r\n            var cells = tabRow.cells,\r\n                currIndex = 0;\r\n\r\n            utils.each( cells, function( tabCell ){\r\n\r\n                currIndex += (tabCell.colSpan || 1);\r\n\r\n                if( currIndex === colIndex ) {\r\n\r\n                    borderCells.push({\r\n                        left: tabCell,\r\n                        right: tabCell.nextSibling || null\r\n                    });\r\n\r\n                    return false;\r\n\r\n                } else if( currIndex > colIndex ) {\r\n\r\n                    if( isContainMergeCell ) {\r\n                        borderCells.push({\r\n                            left: tabCell\r\n                        });\r\n                    }\r\n\r\n                    return false;\r\n                }\r\n\r\n\r\n            } );\r\n\r\n        });\r\n\r\n        return borderCells;\r\n\r\n    }\r\n\r\n\r\n    /**\r\n     * 通过给定的单元格集合获取最小的单元格width\r\n     */\r\n    function getMinWidthByTableCells( cells ) {\r\n\r\n        var minWidth = Number.MAX_VALUE;\r\n\r\n        for( var i = 0, curCell; curCell = cells[ i ] ; i++ ) {\r\n\r\n            minWidth = Math.min( minWidth, curCell.width || getTableCellWidth( curCell ) );\r\n\r\n        }\r\n\r\n        return minWidth;\r\n\r\n    }\r\n\r\n    function correctChangeValue( changeValue, relatedCell, cells ) {\r\n\r\n        //为单元格的paading预留空间\r\n        changeValue -= getTabcellSpace();\r\n\r\n        if( changeValue < 0 ) {\r\n            return 0;\r\n        }\r\n\r\n        changeValue -= getTableCellWidth( relatedCell );\r\n\r\n        //确定方向\r\n        var direction = changeValue < 0 ? 'left':'right';\r\n\r\n        changeValue = Math.abs(changeValue);\r\n\r\n        //只关心非最后一个单元格就可以\r\n        utils.each( cells, function( cellGroup ){\r\n\r\n            var curCell = cellGroup[direction];\r\n\r\n            //为单元格保留最小空间\r\n            if( curCell ) {\r\n                changeValue = Math.min( changeValue, getTableCellWidth( curCell )-cellMinWidth );\r\n            }\r\n\r\n\r\n        } );\r\n\r\n\r\n        //修正越界\r\n        changeValue = changeValue < 0 ? 0 : changeValue;\r\n\r\n        return direction === 'left' ? -changeValue : changeValue;\r\n\r\n    }\r\n\r\n    function getTableCellWidth( cell ) {\r\n\r\n        var width = 0,\r\n            //偏移纠正量\r\n            offset = 0,\r\n            width = cell.offsetWidth - getTabcellSpace();\r\n\r\n        //最后一个节点纠正一下\r\n        if( !cell.nextSibling ) {\r\n\r\n            width -= getTableCellOffset( cell );\r\n\r\n        }\r\n\r\n        width = width < 0 ? 0 : width;\r\n\r\n        try {\r\n            cell.width = width;\r\n        } catch(e) {\r\n        }\r\n\r\n        return width;\r\n\r\n    }\r\n\r\n    /**\r\n     * 获取单元格所在表格的最末单元格的偏移量\r\n     */\r\n    function getTableCellOffset( cell ) {\r\n\r\n        tab = domUtils.findParentByTagName( cell, \"table\", false);\r\n\r\n        if( tab.offsetVal === undefined ) {\r\n\r\n            var prev = cell.previousSibling;\r\n\r\n            if( prev ) {\r\n\r\n                //最后一个单元格和前一个单元格的width diff结果 如果恰好为一个border width， 则条件成立\r\n                tab.offsetVal = cell.offsetWidth - prev.offsetWidth === UT.borderWidth ? UT.borderWidth : 0;\r\n\r\n            } else {\r\n                tab.offsetVal = 0;\r\n            }\r\n\r\n        }\r\n\r\n        return tab.offsetVal;\r\n\r\n    }\r\n\r\n    function getTabcellSpace() {\r\n\r\n        if( UT.tabcellSpace === undefined ) {\r\n\r\n            var cell = null,\r\n                tab = me.document.createElement(\"table\"),\r\n                tbody = me.document.createElement(\"tbody\"),\r\n                trow = me.document.createElement(\"tr\"),\r\n                tabcell = me.document.createElement(\"td\"),\r\n                mirror = null;\r\n\r\n            tabcell.style.cssText = 'border: 0;';\r\n            tabcell.width = 1;\r\n\r\n            trow.appendChild( tabcell );\r\n            trow.appendChild( mirror = tabcell.cloneNode( false ) );\r\n\r\n            tbody.appendChild( trow );\r\n\r\n            tab.appendChild( tbody );\r\n\r\n            tab.style.cssText = \"visibility: hidden;\";\r\n\r\n            me.body.appendChild( tab );\r\n\r\n            UT.paddingSpace = tabcell.offsetWidth - 1;\r\n\r\n            var tmpTabWidth = tab.offsetWidth;\r\n\r\n            tabcell.style.cssText = '';\r\n            mirror.style.cssText = '';\r\n\r\n            UT.borderWidth = ( tab.offsetWidth - tmpTabWidth ) / 3;\r\n\r\n            UT.tabcellSpace = UT.paddingSpace + UT.borderWidth;\r\n\r\n            me.body.removeChild( tab );\r\n\r\n        }\r\n\r\n        getTabcellSpace = function(){ return UT.tabcellSpace; };\r\n\r\n        return UT.tabcellSpace;\r\n\r\n    }\r\n\r\n    function getDragLine(editor, doc) {\r\n        if (mousedown)return;\r\n        dragLine = editor.document.createElement(\"div\");\r\n        domUtils.setAttributes(dragLine, {\r\n            id:\"ue_tableDragLine\",\r\n            unselectable:'on',\r\n            contenteditable:false,\r\n            'onresizestart':'return false',\r\n            'ondragstart':'return false',\r\n            'onselectstart':'return false',\r\n            style:\"background-color:blue;position:absolute;padding:0;margin:0;background-image:none;border:0px none;opacity:0;filter:alpha(opacity=0)\"\r\n        });\r\n        editor.body.appendChild(dragLine);\r\n    }\r\n\r\n    function hideDragLine(editor) {\r\n        if (mousedown)return;\r\n        var line;\r\n        while (line = editor.document.getElementById('ue_tableDragLine')) {\r\n            domUtils.remove(line)\r\n        }\r\n    }\r\n\r\n    /**\r\n     * 依据state（v|h）在cell位置显示横线\r\n     * @param state\r\n     * @param cell\r\n     */\r\n    function showDragLineAt(state, cell) {\r\n        if (!cell) return;\r\n        var table = domUtils.findParentByTagName(cell, \"table\"),\r\n            caption = table.getElementsByTagName('caption'),\r\n            width = table.offsetWidth,\r\n            height = table.offsetHeight - (caption.length > 0 ? caption[0].offsetHeight : 0),\r\n            tablePos = domUtils.getXY(table),\r\n            cellPos = domUtils.getXY(cell), css;\r\n        switch (state) {\r\n            case \"h\":\r\n                css = 'height:' + height + 'px;top:' + (tablePos.y + (caption.length > 0 ? caption[0].offsetHeight : 0)) + 'px;left:' + (cellPos.x + cell.offsetWidth);\r\n                dragLine.style.cssText = css + 'px;position: absolute;display:block;background-color:blue;width:1px;border:0; color:blue;opacity:.3;filter:alpha(opacity=30)';\r\n                break;\r\n            case \"v\":\r\n                css = 'width:' + width + 'px;left:' + tablePos.x + 'px;top:' + (cellPos.y + cell.offsetHeight );\r\n                //必须加上border:0和color:blue，否则低版ie不支持背景色显示\r\n                dragLine.style.cssText = css + 'px;overflow:hidden;position: absolute;display:block;background-color:blue;height:1px;border:0;color:blue;opacity:.2;filter:alpha(opacity=20)';\r\n                break;\r\n            default:\r\n        }\r\n    }\r\n\r\n    /**\r\n     * 当表格边框颜色为白色时设置为虚线,true为添加虚线\r\n     * @param editor\r\n     * @param flag\r\n     */\r\n    function switchBorderColor(editor, flag) {\r\n        var tableArr = domUtils.getElementsByTagName(editor.body, \"table\"), color;\r\n        for (var i = 0, node; node = tableArr[i++];) {\r\n            var td = domUtils.getElementsByTagName(node, \"td\");\r\n            if (td[0]) {\r\n                if (flag) {\r\n                    color = (td[0].style.borderColor).replace(/\\s/g, \"\");\r\n                    if (/(#ffffff)|(rgb\\(255,255,255\\))/ig.test(color))\r\n                        domUtils.addClass(node, \"noBorderTable\")\r\n                } else {\r\n                    domUtils.removeClasses(node, \"noBorderTable\")\r\n                }\r\n            }\r\n\r\n        }\r\n    }\r\n\r\n    function getTableWidth(editor, needIEHack, defaultValue) {\r\n        var body = editor.body;\r\n        return body.offsetWidth - (needIEHack ? parseInt(domUtils.getComputedStyle(body, 'margin-left'), 10) * 2 : 0) - defaultValue.tableBorder * 2 - (editor.options.offsetWidth || 0);\r\n    }\r\n\r\n    /**\r\n     * 获取当前拖动的单元格\r\n     */\r\n    function getTargetTd(editor, evt) {\r\n\r\n        var target = domUtils.findParentByTagName(evt.target || evt.srcElement, [\"td\", \"th\"], true),\r\n            dir = null;\r\n\r\n        if( !target ) {\r\n            return null;\r\n        }\r\n\r\n        dir = getRelation( target, mouseCoords( evt ) );\r\n\r\n        //如果有前一个节点， 需要做一个修正， 否则可能会得到一个错误的td\r\n\r\n        if( !target ) {\r\n            return null;\r\n        }\r\n\r\n        if( dir === 'h1' && target.previousSibling ) {\r\n\r\n            var position = domUtils.getXY( target),\r\n                cellWidth = target.offsetWidth;\r\n\r\n            if( Math.abs( position.x + cellWidth - evt.clientX ) > cellWidth / 3 ) {\r\n                target = target.previousSibling;\r\n            }\r\n\r\n        } else if( dir === 'v1' && target.parentNode.previousSibling ) {\r\n\r\n            var position = domUtils.getXY( target),\r\n                cellHeight = target.offsetHeight;\r\n\r\n            if( Math.abs( position.y + cellHeight - evt.clientY ) > cellHeight / 3 ) {\r\n                target = target.parentNode.previousSibling.firstChild;\r\n            }\r\n\r\n        }\r\n\r\n\r\n        //排除了非td内部以及用于代码高亮部分的td\r\n        return target && !(editor.fireEvent(\"excludetable\", target) === true) ? target : null;\r\n    }\r\n\r\n};\r\n\r\n\r\n// plugins/table.sort.js\r\n/**\r\n * Created with JetBrains PhpStorm.\r\n * User: Jinqn\r\n * Date: 13-10-12\r\n * Time: 上午10:20\r\n * To change this template use File | Settings | File Templates.\r\n */\r\n\r\nUE.UETable.prototype.sortTable = function (sortByCellIndex, compareFn) {\r\n    var table = this.table,\r\n        rows = table.rows,\r\n        trArray = [],\r\n        flag = rows[0].cells[0].tagName === \"TH\",\r\n        lastRowIndex = 0;\r\n    if(this.selectedTds.length){\r\n        var range = this.cellsRange,\r\n            len = range.endRowIndex + 1;\r\n        for (var i = range.beginRowIndex; i < len; i++) {\r\n            trArray[i] = rows[i];\r\n        }\r\n        trArray.splice(0,range.beginRowIndex);\r\n        lastRowIndex = (range.endRowIndex +1) === this.rowsNum ? 0 : range.endRowIndex +1;\r\n    }else{\r\n        for (var i = 0,len = rows.length; i < len; i++) {\r\n            trArray[i] = rows[i];\r\n        }\r\n    }\r\n\r\n    var Fn = {\r\n        'reversecurrent': function(td1,td2){\r\n            return 1;\r\n        },\r\n        'orderbyasc': function(td1,td2){\r\n            var value1 = td1.innerText||td1.textContent,\r\n                value2 = td2.innerText||td2.textContent;\r\n            return value1.localeCompare(value2);\r\n        },\r\n        'reversebyasc': function(td1,td2){\r\n            var value1 = td1.innerHTML,\r\n                value2 = td2.innerHTML;\r\n            return value2.localeCompare(value1);\r\n        },\r\n        'orderbynum': function(td1,td2){\r\n            var value1 = td1[browser.ie ? 'innerText':'textContent'].match(/\\d+/),\r\n                value2 = td2[browser.ie ? 'innerText':'textContent'].match(/\\d+/);\r\n            if(value1) value1 = +value1[0];\r\n            if(value2) value2 = +value2[0];\r\n            return (value1||0) - (value2||0);\r\n        },\r\n        'reversebynum': function(td1,td2){\r\n            var value1 = td1[browser.ie ? 'innerText':'textContent'].match(/\\d+/),\r\n                value2 = td2[browser.ie ? 'innerText':'textContent'].match(/\\d+/);\r\n            if(value1) value1 = +value1[0];\r\n            if(value2) value2 = +value2[0];\r\n            return (value2||0) - (value1||0);\r\n        }\r\n    };\r\n\r\n    //对表格设置排序的标记data-sort-type\r\n    table.setAttribute('data-sort-type', compareFn && typeof compareFn === \"string\" && Fn[compareFn] ? compareFn:'');\r\n\r\n    //th不参与排序\r\n    flag && trArray.splice(0, 1);\r\n    trArray = utils.sort(trArray,function (tr1, tr2) {\r\n        var result;\r\n        if (compareFn && typeof compareFn === \"function\") {\r\n            result = compareFn.call(this, tr1.cells[sortByCellIndex], tr2.cells[sortByCellIndex]);\r\n        } else if (compareFn && typeof compareFn === \"number\") {\r\n            result = 1;\r\n        } else if (compareFn && typeof compareFn === \"string\" && Fn[compareFn]) {\r\n            result = Fn[compareFn].call(this, tr1.cells[sortByCellIndex], tr2.cells[sortByCellIndex]);\r\n        } else {\r\n            result = Fn['orderbyasc'].call(this, tr1.cells[sortByCellIndex], tr2.cells[sortByCellIndex]);\r\n        }\r\n        return result;\r\n    });\r\n    var fragment = table.ownerDocument.createDocumentFragment();\r\n    for (var j = 0, len = trArray.length; j < len; j++) {\r\n        fragment.appendChild(trArray[j]);\r\n    }\r\n    var tbody = table.getElementsByTagName(\"tbody\")[0];\r\n    if(!lastRowIndex){\r\n        tbody.appendChild(fragment);\r\n    }else{\r\n        tbody.insertBefore(fragment,rows[lastRowIndex- range.endRowIndex + range.beginRowIndex - 1])\r\n    }\r\n};\r\n\r\nUE.plugins['tablesort'] = function () {\r\n    var me = this,\r\n        UT = UE.UETable,\r\n        getUETable = function (tdOrTable) {\r\n            return UT.getUETable(tdOrTable);\r\n        },\r\n        getTableItemsByRange = function (editor) {\r\n            return UT.getTableItemsByRange(editor);\r\n        };\r\n\r\n\r\n    me.ready(function () {\r\n        //添加表格可排序的样式\r\n        utils.cssRule('tablesort',\r\n            'table.sortEnabled tr.firstRow th,table.sortEnabled tr.firstRow td{padding-right:20px;background-repeat: no-repeat;background-position: center right;' +\r\n                '   background-image:url(' + me.options.themePath + me.options.theme + '/images/sortable.png);}',\r\n            me.document);\r\n\r\n        //做单元格合并操作时,清除可排序标识\r\n        me.addListener(\"afterexeccommand\", function (type, cmd) {\r\n            if( cmd == 'mergeright' || cmd == 'mergedown' || cmd == 'mergecells') {\r\n                this.execCommand('disablesort');\r\n            }\r\n        });\r\n    });\r\n\r\n\r\n\r\n    //表格排序\r\n    UE.commands['sorttable'] = {\r\n        queryCommandState: function () {\r\n            var me = this,\r\n                tableItems = getTableItemsByRange(me);\r\n            if (!tableItems.cell) return -1;\r\n            var table = tableItems.table,\r\n                cells = table.getElementsByTagName(\"td\");\r\n            for (var i = 0, cell; cell = cells[i++];) {\r\n                if (cell.rowSpan != 1 || cell.colSpan != 1) return -1;\r\n            }\r\n            return 0;\r\n        },\r\n        execCommand: function (cmd, fn) {\r\n            var me = this,\r\n                range = me.selection.getRange(),\r\n                bk = range.createBookmark(true),\r\n                tableItems = getTableItemsByRange(me),\r\n                cell = tableItems.cell,\r\n                ut = getUETable(tableItems.table),\r\n                cellInfo = ut.getCellInfo(cell);\r\n            ut.sortTable(cellInfo.cellIndex, fn);\r\n            range.moveToBookmark(bk);\r\n            try{\r\n                range.select();\r\n            }catch(e){}\r\n        }\r\n    };\r\n\r\n    //设置表格可排序,清除表格可排序\r\n    UE.commands[\"enablesort\"] = UE.commands[\"disablesort\"] = {\r\n        queryCommandState: function (cmd) {\r\n            var table = getTableItemsByRange(this).table;\r\n            if(table && cmd=='enablesort') {\r\n                var cells = domUtils.getElementsByTagName(table, 'th td');\r\n                for(var i = 0; i<cells.length; i++) {\r\n                    if(cells[i].getAttribute('colspan')>1 || cells[i].getAttribute('rowspan')>1) return -1;\r\n                }\r\n            }\r\n\r\n            return !table ? -1: cmd=='enablesort' ^ table.getAttribute('data-sort')!='sortEnabled' ? -1:0;\r\n        },\r\n        execCommand: function (cmd) {\r\n            var table = getTableItemsByRange(this).table;\r\n            table.setAttribute(\"data-sort\", cmd == \"enablesort\" ? \"sortEnabled\" : \"sortDisabled\");\r\n            cmd == \"enablesort\" ? domUtils.addClass(table,\"sortEnabled\"):domUtils.removeClasses(table,\"sortEnabled\");\r\n        }\r\n    };\r\n};\r\n\r\n\r\n// plugins/contextmenu.js\r\n///import core\r\n///commands 右键菜单\r\n///commandsName  ContextMenu\r\n///commandsTitle  右键菜单\r\n/**\r\n * 右键菜单\r\n * @function\r\n * @name baidu.editor.plugins.contextmenu\r\n * @author zhanyi\r\n */\r\n\r\nUE.plugins['contextmenu'] = function () {\r\n    var me = this;\r\n    me.setOpt('enableContextMenu',true);\r\n    if(me.getOpt('enableContextMenu') === false){\r\n        return;\r\n    }\r\n    var lang = me.getLang( \"contextMenu\" ),\r\n            menu,\r\n            items = me.options.contextMenu || [\r\n                {label:lang['selectall'], cmdName:'selectall'},\r\n                {\r\n                    label:lang.cleardoc,\r\n                    cmdName:'cleardoc',\r\n                    exec:function () {\r\n                        if ( confirm( lang.confirmclear ) ) {\r\n                            this.execCommand( 'cleardoc' );\r\n                        }\r\n                    }\r\n                },\r\n                '-',\r\n                {\r\n                    label:lang.unlink,\r\n                    cmdName:'unlink'\r\n                },\r\n                '-',\r\n                {\r\n                    group:lang.paragraph,\r\n                    icon:'justifyjustify',\r\n                    subMenu:[\r\n                        {\r\n                            label:lang.justifyleft,\r\n                            cmdName:'justify',\r\n                            value:'left'\r\n                        },\r\n                        {\r\n                            label:lang.justifyright,\r\n                            cmdName:'justify',\r\n                            value:'right'\r\n                        },\r\n                        {\r\n                            label:lang.justifycenter,\r\n                            cmdName:'justify',\r\n                            value:'center'\r\n                        },\r\n                        {\r\n                            label:lang.justifyjustify,\r\n                            cmdName:'justify',\r\n                            value:'justify'\r\n                        }\r\n                    ]\r\n                },\r\n                '-',\r\n                {\r\n                    group:lang.table,\r\n                    icon:'table',\r\n                    subMenu:[\r\n                        {\r\n                            label:lang.inserttable,\r\n                            cmdName:'inserttable'\r\n                        },\r\n                        {\r\n                            label:lang.deletetable,\r\n                            cmdName:'deletetable'\r\n                        },\r\n                        '-',\r\n                        {\r\n                            label:lang.deleterow,\r\n                            cmdName:'deleterow'\r\n                        },\r\n                        {\r\n                            label:lang.deletecol,\r\n                            cmdName:'deletecol'\r\n                        },\r\n                        {\r\n                            label:lang.insertcol,\r\n                            cmdName:'insertcol'\r\n                        },\r\n                        {\r\n                            label:lang.insertcolnext,\r\n                            cmdName:'insertcolnext'\r\n                        },\r\n                        {\r\n                            label:lang.insertrow,\r\n                            cmdName:'insertrow'\r\n                        },\r\n                        {\r\n                            label:lang.insertrownext,\r\n                            cmdName:'insertrownext'\r\n                        },\r\n                        '-',\r\n                        {\r\n                            label:lang.insertcaption,\r\n                            cmdName:'insertcaption'\r\n                        },\r\n                        {\r\n                            label:lang.deletecaption,\r\n                            cmdName:'deletecaption'\r\n                        },\r\n                        {\r\n                            label:lang.inserttitle,\r\n                            cmdName:'inserttitle'\r\n                        },\r\n                        {\r\n                            label:lang.deletetitle,\r\n                            cmdName:'deletetitle'\r\n                        },\r\n                        {\r\n                            label:lang.inserttitlecol,\r\n                            cmdName:'inserttitlecol'\r\n                        },\r\n                        {\r\n                            label:lang.deletetitlecol,\r\n                            cmdName:'deletetitlecol'\r\n                        },\r\n                        '-',\r\n                        {\r\n                            label:lang.mergecells,\r\n                            cmdName:'mergecells'\r\n                        },\r\n                        {\r\n                            label:lang.mergeright,\r\n                            cmdName:'mergeright'\r\n                        },\r\n                        {\r\n                            label:lang.mergedown,\r\n                            cmdName:'mergedown'\r\n                        },\r\n                        '-',\r\n                        {\r\n                            label:lang.splittorows,\r\n                            cmdName:'splittorows'\r\n                        },\r\n                        {\r\n                            label:lang.splittocols,\r\n                            cmdName:'splittocols'\r\n                        },\r\n                        {\r\n                            label:lang.splittocells,\r\n                            cmdName:'splittocells'\r\n                        },\r\n                        '-',\r\n                        {\r\n                            label:lang.averageDiseRow,\r\n                            cmdName:'averagedistributerow'\r\n                        },\r\n                        {\r\n                            label:lang.averageDisCol,\r\n                            cmdName:'averagedistributecol'\r\n                        },\r\n                        '-',\r\n                        {\r\n                            label:lang.edittd,\r\n                            cmdName:'edittd',\r\n                            exec:function () {\r\n                                if ( UE.ui['edittd'] ) {\r\n                                    new UE.ui['edittd']( this );\r\n                                }\r\n                                this.getDialog('edittd').open();\r\n                            }\r\n                        },\r\n                        {\r\n                            label:lang.edittable,\r\n                            cmdName:'edittable',\r\n                            exec:function () {\r\n                                if ( UE.ui['edittable'] ) {\r\n                                    new UE.ui['edittable']( this );\r\n                                }\r\n                                this.getDialog('edittable').open();\r\n                            }\r\n                        },\r\n                        {\r\n                            label:lang.setbordervisible,\r\n                            cmdName:'setbordervisible'\r\n                        }\r\n                    ]\r\n                },\r\n                {\r\n                    group:lang.tablesort,\r\n                    icon:'tablesort',\r\n                    subMenu:[\r\n                        {\r\n                            label:lang.enablesort,\r\n                            cmdName:'enablesort'\r\n                        },\r\n                        {\r\n                            label:lang.disablesort,\r\n                            cmdName:'disablesort'\r\n                        },\r\n                        '-',\r\n                        {\r\n                            label:lang.reversecurrent,\r\n                            cmdName:'sorttable',\r\n                            value:'reversecurrent'\r\n                        },\r\n                        {\r\n                            label:lang.orderbyasc,\r\n                            cmdName:'sorttable',\r\n                            value:'orderbyasc'\r\n                        },\r\n                        {\r\n                            label:lang.reversebyasc,\r\n                            cmdName:'sorttable',\r\n                            value:'reversebyasc'\r\n                        },\r\n                        {\r\n                            label:lang.orderbynum,\r\n                            cmdName:'sorttable',\r\n                            value:'orderbynum'\r\n                        },\r\n                        {\r\n                            label:lang.reversebynum,\r\n                            cmdName:'sorttable',\r\n                            value:'reversebynum'\r\n                        }\r\n                    ]\r\n                },\r\n                {\r\n                    group:lang.borderbk,\r\n                    icon:'borderBack',\r\n                    subMenu:[\r\n                        {\r\n                            label:lang.setcolor,\r\n                            cmdName:\"interlacetable\",\r\n                            exec:function(){\r\n                                this.execCommand(\"interlacetable\");\r\n                            }\r\n                        },\r\n                        {\r\n                            label:lang.unsetcolor,\r\n                            cmdName:\"uninterlacetable\",\r\n                            exec:function(){\r\n                                this.execCommand(\"uninterlacetable\");\r\n                            }\r\n                        },\r\n                        {\r\n                            label:lang.setbackground,\r\n                            cmdName:\"settablebackground\",\r\n                            exec:function(){\r\n                                this.execCommand(\"settablebackground\",{repeat:true,colorList:[\"#bbb\",\"#ccc\"]});\r\n                            }\r\n                        },\r\n                        {\r\n                            label:lang.unsetbackground,\r\n                            cmdName:\"cleartablebackground\",\r\n                            exec:function(){\r\n                                this.execCommand(\"cleartablebackground\");\r\n                            }\r\n                        },\r\n                        {\r\n                            label:lang.redandblue,\r\n                            cmdName:\"settablebackground\",\r\n                            exec:function(){\r\n                                this.execCommand(\"settablebackground\",{repeat:true,colorList:[\"red\",\"blue\"]});\r\n                            }\r\n                        },\r\n                        {\r\n                            label:lang.threecolorgradient,\r\n                            cmdName:\"settablebackground\",\r\n                            exec:function(){\r\n                                this.execCommand(\"settablebackground\",{repeat:true,colorList:[\"#aaa\",\"#bbb\",\"#ccc\"]});\r\n                            }\r\n                        }\r\n                    ]\r\n                },\r\n                {\r\n                    group:lang.aligntd,\r\n                    icon:'aligntd',\r\n                    subMenu:[\r\n                        {\r\n                            cmdName:'cellalignment',\r\n                            value:{align:'left',vAlign:'top'}\r\n                        },\r\n                        {\r\n                            cmdName:'cellalignment',\r\n                            value:{align:'center',vAlign:'top'}\r\n                        },\r\n                        {\r\n                            cmdName:'cellalignment',\r\n                            value:{align:'right',vAlign:'top'}\r\n                        },\r\n                        {\r\n                            cmdName:'cellalignment',\r\n                            value:{align:'left',vAlign:'middle'}\r\n                        },\r\n                        {\r\n                            cmdName:'cellalignment',\r\n                            value:{align:'center',vAlign:'middle'}\r\n                        },\r\n                        {\r\n                            cmdName:'cellalignment',\r\n                            value:{align:'right',vAlign:'middle'}\r\n                        },\r\n                        {\r\n                            cmdName:'cellalignment',\r\n                            value:{align:'left',vAlign:'bottom'}\r\n                        },\r\n                        {\r\n                            cmdName:'cellalignment',\r\n                            value:{align:'center',vAlign:'bottom'}\r\n                        },\r\n                        {\r\n                            cmdName:'cellalignment',\r\n                            value:{align:'right',vAlign:'bottom'}\r\n                        }\r\n                    ]\r\n                },\r\n                {\r\n                    group:lang.aligntable,\r\n                    icon:'aligntable',\r\n                    subMenu:[\r\n                        {\r\n                            cmdName:'tablealignment',\r\n                            className: 'left',\r\n                            label:lang.tableleft,\r\n                            value:\"left\"\r\n                        },\r\n                        {\r\n                            cmdName:'tablealignment',\r\n                            className: 'center',\r\n                            label:lang.tablecenter,\r\n                            value:\"center\"\r\n                        },\r\n                        {\r\n                            cmdName:'tablealignment',\r\n                            className: 'right',\r\n                            label:lang.tableright,\r\n                            value:\"right\"\r\n                        }\r\n                    ]\r\n                },\r\n                '-',\r\n                {\r\n                    label:lang.insertparagraphbefore,\r\n                    cmdName:'insertparagraph',\r\n                    value:true\r\n                },\r\n                {\r\n                    label:lang.insertparagraphafter,\r\n                    cmdName:'insertparagraph'\r\n                },\r\n                {\r\n                    label:lang['copy'],\r\n                    cmdName:'copy'\r\n                },\r\n                {\r\n                    label:lang['paste'],\r\n                    cmdName:'paste'\r\n                }\r\n            ];\r\n    if ( !items.length ) {\r\n        return;\r\n    }\r\n    var uiUtils = UE.ui.uiUtils;\r\n\r\n    me.addListener( 'contextmenu', function ( type, evt ) {\r\n\r\n        var offset = uiUtils.getViewportOffsetByEvent( evt );\r\n        me.fireEvent( 'beforeselectionchange' );\r\n        if ( menu ) {\r\n            menu.destroy();\r\n        }\r\n        for ( var i = 0, ti, contextItems = []; ti = items[i]; i++ ) {\r\n            var last;\r\n            (function ( item ) {\r\n                if ( item == '-' ) {\r\n                    if ( (last = contextItems[contextItems.length - 1 ] ) && last !== '-' ) {\r\n                        contextItems.push( '-' );\r\n                    }\r\n                } else if ( item.hasOwnProperty( \"group\" ) ) {\r\n                    for ( var j = 0, cj, subMenu = []; cj = item.subMenu[j]; j++ ) {\r\n                        (function ( subItem ) {\r\n                            if ( subItem == '-' ) {\r\n                                if ( (last = subMenu[subMenu.length - 1 ] ) && last !== '-' ) {\r\n                                    subMenu.push( '-' );\r\n                                }else{\r\n                                    subMenu.splice(subMenu.length-1);\r\n                                }\r\n                            } else {\r\n                                if ( (me.commands[subItem.cmdName] || UE.commands[subItem.cmdName] || subItem.query) &&\r\n                                        (subItem.query ? subItem.query() : me.queryCommandState( subItem.cmdName )) > -1 ) {\r\n                                    subMenu.push( {\r\n                                        'label':subItem.label || me.getLang( \"contextMenu.\" + subItem.cmdName + (subItem.value || '') )||\"\",\r\n                                        'className':'edui-for-' +subItem.cmdName + ( subItem.className ? ( ' edui-for-' + subItem.cmdName + '-' + subItem.className ) : '' ),\r\n                                        onclick:subItem.exec ? function () {\r\n                                                subItem.exec.call( me );\r\n                                        } : function () {\r\n                                            me.execCommand( subItem.cmdName, subItem.value );\r\n                                        }\r\n                                    } );\r\n                                }\r\n                            }\r\n                        })( cj );\r\n                    }\r\n                    if ( subMenu.length ) {\r\n                        function getLabel(){\r\n                            switch (item.icon){\r\n                                case \"table\":\r\n                                    return me.getLang( \"contextMenu.table\" );\r\n                                case \"justifyjustify\":\r\n                                    return me.getLang( \"contextMenu.paragraph\" );\r\n                                case \"aligntd\":\r\n                                    return me.getLang(\"contextMenu.aligntd\");\r\n                                case \"aligntable\":\r\n                                    return me.getLang(\"contextMenu.aligntable\");\r\n                                case \"tablesort\":\r\n                                    return lang.tablesort;\r\n                                case \"borderBack\":\r\n                                    return lang.borderbk;\r\n                                default :\r\n                                    return '';\r\n                            }\r\n                        }\r\n                        contextItems.push( {\r\n                            //todo 修正成自动获取方式\r\n                            'label':getLabel(),\r\n                            className:'edui-for-' + item.icon,\r\n                            'subMenu':{\r\n                                items:subMenu,\r\n                                editor:me\r\n                            }\r\n                        } );\r\n                    }\r\n\r\n                } else {\r\n                    //有可能commmand没有加载右键不能出来，或者没有command也想能展示出来添加query方法\r\n                    if ( (me.commands[item.cmdName] || UE.commands[item.cmdName] || item.query) &&\r\n                            (item.query ? item.query.call(me) : me.queryCommandState( item.cmdName )) > -1 ) {\r\n\r\n                        contextItems.push( {\r\n                            'label':item.label || me.getLang( \"contextMenu.\" + item.cmdName ),\r\n                            className:'edui-for-' + (item.icon ? item.icon : item.cmdName + (item.value || '')),\r\n                            onclick:item.exec ? function () {\r\n                                item.exec.call( me );\r\n                            } : function () {\r\n                                me.execCommand( item.cmdName, item.value );\r\n                            }\r\n                        } );\r\n                    }\r\n\r\n                }\r\n\r\n            })( ti );\r\n        }\r\n        if ( contextItems[contextItems.length - 1] == '-' ) {\r\n            contextItems.pop();\r\n        }\r\n\r\n        menu = new UE.ui.Menu( {\r\n            items:contextItems,\r\n            className:\"edui-contextmenu\",\r\n            editor:me\r\n        } );\r\n        menu.render();\r\n        menu.showAt( offset );\r\n\r\n        me.fireEvent(\"aftershowcontextmenu\",menu);\r\n\r\n        domUtils.preventDefault( evt );\r\n        if ( browser.ie ) {\r\n            var ieRange;\r\n            try {\r\n                ieRange = me.selection.getNative().createRange();\r\n            } catch ( e ) {\r\n                return;\r\n            }\r\n            if ( ieRange.item ) {\r\n                var range = new dom.Range( me.document );\r\n                range.selectNode( ieRange.item( 0 ) ).select( true, true );\r\n            }\r\n        }\r\n    });\r\n\r\n    // 添加复制的flash按钮\r\n    me.addListener('aftershowcontextmenu', function(type, menu) {\r\n        if (me.zeroclipboard) {\r\n            var items = menu.items;\r\n            for (var key in items) {\r\n                if (items[key].className == 'edui-for-copy') {\r\n                    me.zeroclipboard.clip(items[key].getDom());\r\n                }\r\n            }\r\n        }\r\n    });\r\n\r\n};\r\n\r\n\r\n// plugins/shortcutmenu.js\r\n///import core\r\n///commands       弹出菜单\r\n// commandsName  popupmenu\r\n///commandsTitle  弹出菜单\r\n/**\r\n * 弹出菜单\r\n * @function\r\n * @name baidu.editor.plugins.popupmenu\r\n * @author xuheng\r\n */\r\n\r\nUE.plugins['shortcutmenu'] = function () {\r\n    var me = this,\r\n        menu,\r\n        items = me.options.shortcutMenu || [];\r\n\r\n    if (!items.length) {\r\n        return;\r\n    }\r\n\r\n    me.addListener ('contextmenu mouseup' , function (type , e) {\r\n        var me = this,\r\n            customEvt = {\r\n                type : type ,\r\n                target : e.target || e.srcElement ,\r\n                screenX : e.screenX ,\r\n                screenY : e.screenY ,\r\n                clientX : e.clientX ,\r\n                clientY : e.clientY\r\n            };\r\n\r\n        setTimeout (function () {\r\n            var rng = me.selection.getRange ();\r\n            if (rng.collapsed === false || type == \"contextmenu\") {\r\n\r\n                if (!menu) {\r\n                    menu = new baidu.editor.ui.ShortCutMenu ({\r\n                        editor : me ,\r\n                        items : items ,\r\n                        theme : me.options.theme ,\r\n                        className : 'edui-shortcutmenu'\r\n                    });\r\n\r\n                    menu.render ();\r\n                    me.fireEvent (\"afterrendershortcutmenu\" , menu);\r\n                }\r\n\r\n                menu.show (customEvt , !!UE.plugins['contextmenu']);\r\n            }\r\n        });\r\n\r\n        if (type == 'contextmenu') {\r\n            domUtils.preventDefault (e);\r\n            if (browser.ie9below) {\r\n                var ieRange;\r\n                try {\r\n                    ieRange = me.selection.getNative().createRange();\r\n                } catch (e) {\r\n                    return;\r\n                }\r\n                if (ieRange.item) {\r\n                    var range = new dom.Range (me.document);\r\n                    range.selectNode (ieRange.item (0)).select (true , true);\r\n\r\n                }\r\n            }\r\n        }\r\n    });\r\n\r\n    me.addListener ('keydown' , function (type) {\r\n        if (type == \"keydown\") {\r\n            menu && !menu.isHidden && menu.hide ();\r\n        }\r\n\r\n    });\r\n\r\n};\r\n\r\n\r\n\r\n\r\n// plugins/basestyle.js\r\n/**\r\n * B、I、sub、super命令支持\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\nUE.plugins['basestyle'] = function(){\r\n\r\n    /**\r\n     * 字体加粗\r\n     * @command bold\r\n     * @param { String } cmd 命令字符串\r\n     * @remind 对已加粗的文本内容执行该命令， 将取消加粗\r\n     * @method execCommand\r\n     * @example\r\n     * ```javascript\r\n     * //editor是编辑器实例\r\n     * //对当前选中的文本内容执行加粗操作\r\n     * //第一次执行， 文本内容加粗\r\n     * editor.execCommand( 'bold' );\r\n     *\r\n     * //第二次执行， 文本内容取消加粗\r\n     * editor.execCommand( 'bold' );\r\n     * ```\r\n     */\r\n\r\n\r\n    /**\r\n     * 字体倾斜\r\n     * @command italic\r\n     * @method execCommand\r\n     * @param { String } cmd 命令字符串\r\n     * @remind 对已倾斜的文本内容执行该命令， 将取消倾斜\r\n     * @example\r\n     * ```javascript\r\n     * //editor是编辑器实例\r\n     * //对当前选中的文本内容执行斜体操作\r\n     * //第一次操作， 文本内容将变成斜体\r\n     * editor.execCommand( 'italic' );\r\n     *\r\n     * //再次对同一文本内容执行， 则文本内容将恢复正常\r\n     * editor.execCommand( 'italic' );\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 下标文本，与“superscript”命令互斥\r\n     * @command subscript\r\n     * @method execCommand\r\n     * @remind  把选中的文本内容切换成下标文本， 如果当前选中的文本已经是下标， 则该操作会把文本内容还原成正常文本\r\n     * @param { String } cmd 命令字符串\r\n     * @example\r\n     * ```javascript\r\n     * //editor是编辑器实例\r\n     * //对当前选中的文本内容执行下标操作\r\n     * //第一次操作， 文本内容将变成下标文本\r\n     * editor.execCommand( 'subscript' );\r\n     *\r\n     * //再次对同一文本内容执行， 则文本内容将恢复正常\r\n     * editor.execCommand( 'subscript' );\r\n     * ```\r\n     */\r\n\r\n    /**\r\n     * 上标文本，与“subscript”命令互斥\r\n     * @command superscript\r\n     * @method execCommand\r\n     * @remind 把选中的文本内容切换成上标文本， 如果当前选中的文本已经是上标， 则该操作会把文本内容还原成正常文本\r\n     * @param { String } cmd 命令字符串\r\n     * @example\r\n     * ```javascript\r\n     * //editor是编辑器实例\r\n     * //对当前选中的文本内容执行上标操作\r\n     * //第一次操作， 文本内容将变成上标文本\r\n     * editor.execCommand( 'superscript' );\r\n     *\r\n     * //再次对同一文本内容执行， 则文本内容将恢复正常\r\n     * editor.execCommand( 'superscript' );\r\n     * ```\r\n     */\r\n    var basestyles = {\r\n            'bold':['strong','b'],\r\n            'italic':['em','i'],\r\n            'subscript':['sub'],\r\n            'superscript':['sup']\r\n        },\r\n        getObj = function(editor,tagNames){\r\n            return domUtils.filterNodeList(editor.selection.getStartElementPath(),tagNames);\r\n        },\r\n        me = this;\r\n    //添加快捷键\r\n    me.addshortcutkey({\r\n        \"Bold\" : \"ctrl+66\",//^B\r\n        \"Italic\" : \"ctrl+73\", //^I\r\n        \"Underline\" : \"ctrl+85\"//^U\r\n    });\r\n    me.addInputRule(function(root){\r\n        utils.each(root.getNodesByTagName('b i'),function(node){\r\n            switch (node.tagName){\r\n                case 'b':\r\n                    node.tagName = 'strong';\r\n                    break;\r\n                case 'i':\r\n                    node.tagName = 'em';\r\n            }\r\n        });\r\n    });\r\n    for ( var style in basestyles ) {\r\n        (function( cmd, tagNames ) {\r\n            me.commands[cmd] = {\r\n                execCommand : function( cmdName ) {\r\n                    var range = me.selection.getRange(),obj = getObj(this,tagNames);\r\n                    if ( range.collapsed ) {\r\n                        if ( obj ) {\r\n                            var tmpText =  me.document.createTextNode('');\r\n                            range.insertNode( tmpText ).removeInlineStyle( tagNames );\r\n                            range.setStartBefore(tmpText);\r\n                            domUtils.remove(tmpText);\r\n                        } else {\r\n                            var tmpNode = range.document.createElement( tagNames[0] );\r\n                            if(cmdName == 'superscript' || cmdName == 'subscript'){\r\n                                tmpText = me.document.createTextNode('');\r\n                                range.insertNode(tmpText)\r\n                                    .removeInlineStyle(['sub','sup'])\r\n                                    .setStartBefore(tmpText)\r\n                                    .collapse(true);\r\n                            }\r\n                            range.insertNode( tmpNode ).setStart( tmpNode, 0 );\r\n                        }\r\n                        range.collapse( true );\r\n                    } else {\r\n                        if(cmdName == 'superscript' || cmdName == 'subscript'){\r\n                            if(!obj || obj.tagName.toLowerCase() != cmdName){\r\n                                range.removeInlineStyle(['sub','sup']);\r\n                            }\r\n                        }\r\n                        obj ? range.removeInlineStyle( tagNames ) : range.applyInlineStyle( tagNames[0] );\r\n                    }\r\n                    range.select();\r\n                },\r\n                queryCommandState : function() {\r\n                   return getObj(this,tagNames) ? 1 : 0;\r\n                }\r\n            };\r\n        })( style, basestyles[style] );\r\n    }\r\n};\r\n\r\n\r\n\r\n// plugins/elementpath.js\r\n/**\r\n * 选取路径命令\r\n * @file\r\n */\r\nUE.plugins['elementpath'] = function(){\r\n    var currentLevel,\r\n        tagNames,\r\n        me = this;\r\n    me.setOpt('elementPathEnabled',true);\r\n    if(!me.options.elementPathEnabled){\r\n        return;\r\n    }\r\n    me.commands['elementpath'] = {\r\n        execCommand : function( cmdName, level ) {\r\n            var start = tagNames[level],\r\n                range = me.selection.getRange();\r\n            currentLevel = level*1;\r\n            range.selectNode(start).select();\r\n        },\r\n        queryCommandValue : function() {\r\n            //产生一个副本，不能修改原来的startElementPath;\r\n            var parents = [].concat(this.selection.getStartElementPath()).reverse(),\r\n                names = [];\r\n            tagNames = parents;\r\n            for(var i=0,ci;ci=parents[i];i++){\r\n                if(ci.nodeType == 3) {\r\n                    continue;\r\n                }\r\n                var name = ci.tagName.toLowerCase();\r\n                if(name == 'img' && ci.getAttribute('anchorname')){\r\n                    name = 'anchor';\r\n                }\r\n                names[i] = name;\r\n                if(currentLevel == i){\r\n                   currentLevel = -1;\r\n                    break;\r\n                }\r\n            }\r\n            return names;\r\n        }\r\n    };\r\n};\r\n\r\n\r\n\r\n// plugins/formatmatch.js\r\n/**\r\n * 格式刷，只格式inline的\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 格式刷\r\n * @command formatmatch\r\n * @method execCommand\r\n * @remind 该操作不能复制段落格式\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * //editor是编辑器实例\r\n * //获取格式刷\r\n * editor.execCommand( 'formatmatch' );\r\n * ```\r\n */\r\nUE.plugins['formatmatch'] = function(){\r\n\r\n    var me = this,\r\n        list = [],img,\r\n        flag = 0;\r\n\r\n     me.addListener('reset',function(){\r\n         list = [];\r\n         flag = 0;\r\n     });\r\n\r\n    function addList(type,evt){\r\n        \r\n        if(browser.webkit){\r\n            var target = evt.target.tagName == 'IMG' ? evt.target : null;\r\n        }\r\n\r\n        function addFormat(range){\r\n\r\n            if(text){\r\n                range.selectNode(text);\r\n            }\r\n            return range.applyInlineStyle(list[list.length-1].tagName,null,list);\r\n\r\n        }\r\n\r\n        me.undoManger && me.undoManger.save();\r\n\r\n        var range = me.selection.getRange(),\r\n            imgT = target || range.getClosedNode();\r\n        if(img && imgT && imgT.tagName == 'IMG'){\r\n            //trace:964\r\n\r\n            imgT.style.cssText += ';float:' + (img.style.cssFloat || img.style.styleFloat ||'none') + ';display:' + (img.style.display||'inline');\r\n\r\n            img = null;\r\n        }else{\r\n            if(!img){\r\n                var collapsed = range.collapsed;\r\n                if(collapsed){\r\n                    var text = me.document.createTextNode('match');\r\n                    range.insertNode(text).select();\r\n\r\n\r\n                }\r\n                me.__hasEnterExecCommand = true;\r\n                //不能把block上的属性干掉\r\n                //trace:1553\r\n                var removeFormatAttributes = me.options.removeFormatAttributes;\r\n                me.options.removeFormatAttributes = '';\r\n                me.execCommand('removeformat');\r\n                me.options.removeFormatAttributes = removeFormatAttributes;\r\n                me.__hasEnterExecCommand = false;\r\n                //trace:969\r\n                range = me.selection.getRange();\r\n                if(list.length){\r\n                    addFormat(range);\r\n                }\r\n                if(text){\r\n                    range.setStartBefore(text).collapse(true);\r\n\r\n                }\r\n                range.select();\r\n                text && domUtils.remove(text);\r\n            }\r\n\r\n        }\r\n\r\n\r\n\r\n\r\n        me.undoManger && me.undoManger.save();\r\n        me.removeListener('mouseup',addList);\r\n        flag = 0;\r\n    }\r\n\r\n    me.commands['formatmatch'] = {\r\n        execCommand : function( cmdName ) {\r\n          \r\n            if(flag){\r\n                flag = 0;\r\n                list = [];\r\n                 me.removeListener('mouseup',addList);\r\n                return;\r\n            }\r\n\r\n\r\n              \r\n            var range = me.selection.getRange();\r\n            img = range.getClosedNode();\r\n            if(!img || img.tagName != 'IMG'){\r\n               range.collapse(true).shrinkBoundary();\r\n               var start = range.startContainer;\r\n               list = domUtils.findParents(start,true,function(node){\r\n                   return !domUtils.isBlockElm(node) && node.nodeType == 1;\r\n               });\r\n               //a不能加入格式刷, 并且克隆节点\r\n               for(var i=0,ci;ci=list[i];i++){\r\n                   if(ci.tagName == 'A'){\r\n                       list.splice(i,1);\r\n                       break;\r\n                   }\r\n               }\r\n\r\n            }\r\n\r\n            me.addListener('mouseup',addList);\r\n            flag = 1;\r\n\r\n\r\n        },\r\n        queryCommandState : function() {\r\n            return flag;\r\n        },\r\n        notNeedUndo : 1\r\n    };\r\n};\r\n\r\n\r\n\r\n// plugins/searchreplace.js\r\n///import core\r\n///commands 查找替换\r\n///commandsName  SearchReplace\r\n///commandsTitle  查询替换\r\n///commandsDialog  dialogs\\searchreplace\r\n/**\r\n * @description 查找替换\r\n * @author zhanyi\r\n */\r\n\r\nUE.plugin.register('searchreplace',function(){\r\n    var me = this;\r\n\r\n    var _blockElm = {'table':1,'tbody':1,'tr':1,'ol':1,'ul':1};\r\n\r\n    function findTextInString(textContent,opt,currentIndex){\r\n        var str = opt.searchStr;\r\n        if(opt.dir == -1){\r\n            textContent = textContent.split('').reverse().join('');\r\n            str = str.split('').reverse().join('');\r\n            currentIndex = textContent.length - currentIndex;\r\n\r\n        }\r\n        var reg = new RegExp(str,'g' + (opt.casesensitive ? '' : 'i')),match;\r\n\r\n        while(match = reg.exec(textContent)){\r\n            if(match.index >= currentIndex){\r\n                return opt.dir == -1 ? textContent.length - match.index - opt.searchStr.length : match.index;\r\n            }\r\n        }\r\n        return  -1\r\n    }\r\n    function findTextBlockElm(node,currentIndex,opt){\r\n        var textContent,index,methodName = opt.all || opt.dir == 1 ? 'getNextDomNode' : 'getPreDomNode';\r\n        if(domUtils.isBody(node)){\r\n            node = node.firstChild;\r\n        }\r\n        var first = 1;\r\n        while(node){\r\n            textContent = node.nodeType == 3 ? node.nodeValue : node[browser.ie ? 'innerText' : 'textContent'];\r\n            index = findTextInString(textContent,opt,currentIndex );\r\n            first = 0;\r\n            if(index!=-1){\r\n                return {\r\n                    'node':node,\r\n                    'index':index\r\n                }\r\n            }\r\n            node = domUtils[methodName](node);\r\n            while(node && _blockElm[node.nodeName.toLowerCase()]){\r\n                node = domUtils[methodName](node,true);\r\n            }\r\n            if(node){\r\n                currentIndex = opt.dir == -1 ? (node.nodeType == 3 ? node.nodeValue : node[browser.ie ? 'innerText' : 'textContent']).length : 0;\r\n            }\r\n\r\n        }\r\n    }\r\n    function findNTextInBlockElm(node,index,str){\r\n        var currentIndex = 0,\r\n            currentNode = node.firstChild,\r\n            currentNodeLength = 0,\r\n            result;\r\n        while(currentNode){\r\n            if(currentNode.nodeType == 3){\r\n                currentNodeLength = currentNode.nodeValue.replace(/(^[\\t\\r\\n]+)|([\\t\\r\\n]+$)/,'').length;\r\n                currentIndex += currentNodeLength;\r\n                if(currentIndex >= index){\r\n                    return {\r\n                        'node':currentNode,\r\n                        'index': currentNodeLength - (currentIndex - index)\r\n                    }\r\n                }\r\n            }else if(!dtd.$empty[currentNode.tagName]){\r\n                currentNodeLength = currentNode[browser.ie ? 'innerText' : 'textContent'].replace(/(^[\\t\\r\\n]+)|([\\t\\r\\n]+$)/,'').length\r\n                currentIndex += currentNodeLength;\r\n                if(currentIndex >= index){\r\n                    result = findNTextInBlockElm(currentNode,currentNodeLength - (currentIndex - index),str);\r\n                    if(result){\r\n                        return result;\r\n                    }\r\n                }\r\n            }\r\n            currentNode = domUtils.getNextDomNode(currentNode);\r\n\r\n        }\r\n    }\r\n\r\n    function searchReplace(me,opt){\r\n\r\n        var rng = me.selection.getRange(),\r\n            startBlockNode,\r\n            searchStr = opt.searchStr,\r\n            span = me.document.createElement('span');\r\n        span.innerHTML = '$$ueditor_searchreplace_key$$';\r\n\r\n        rng.shrinkBoundary(true);\r\n\r\n        //判断是不是第一次选中\r\n        if(!rng.collapsed){\r\n            rng.select();\r\n            var rngText = me.selection.getText();\r\n            if(new RegExp('^' + opt.searchStr + '$',(opt.casesensitive ? '' : 'i')).test(rngText)){\r\n                if(opt.replaceStr != undefined){\r\n                    replaceText(rng,opt.replaceStr);\r\n                    rng.select();\r\n                    return true;\r\n                }else{\r\n                    rng.collapse(opt.dir == -1)\r\n                }\r\n\r\n            }\r\n        }\r\n\r\n\r\n        rng.insertNode(span);\r\n        rng.enlargeToBlockElm(true);\r\n        startBlockNode = rng.startContainer;\r\n        var currentIndex = startBlockNode[browser.ie ? 'innerText' : 'textContent'].indexOf('$$ueditor_searchreplace_key$$');\r\n        rng.setStartBefore(span);\r\n        domUtils.remove(span);\r\n        var result = findTextBlockElm(startBlockNode,currentIndex,opt);\r\n        if(result){\r\n            var rngStart = findNTextInBlockElm(result.node,result.index,searchStr);\r\n            var rngEnd = findNTextInBlockElm(result.node,result.index + searchStr.length,searchStr);\r\n            rng.setStart(rngStart.node,rngStart.index).setEnd(rngEnd.node,rngEnd.index);\r\n\r\n            if(opt.replaceStr !== undefined){\r\n                replaceText(rng,opt.replaceStr)\r\n            }\r\n            rng.select();\r\n            return true;\r\n        }else{\r\n            rng.setCursor()\r\n        }\r\n\r\n    }\r\n    function replaceText(rng,str){\r\n\r\n        str = me.document.createTextNode(str);\r\n        rng.deleteContents().insertNode(str);\r\n\r\n    }\r\n    return {\r\n        commands:{\r\n            'searchreplace':{\r\n                execCommand:function(cmdName,opt){\r\n                    utils.extend(opt,{\r\n                        all : false,\r\n                        casesensitive : false,\r\n                        dir : 1\r\n                    },true);\r\n                    var num = 0;\r\n                    if(opt.all){\r\n\r\n                        var rng = me.selection.getRange(),\r\n                            first = me.body.firstChild;\r\n                        if(first && first.nodeType == 1){\r\n                            rng.setStart(first,0);\r\n                            rng.shrinkBoundary(true);\r\n                        }else if(first.nodeType == 3){\r\n                            rng.setStartBefore(first)\r\n                        }\r\n                        rng.collapse(true).select(true);\r\n                        if(opt.replaceStr !== undefined){\r\n                            me.fireEvent('saveScene');\r\n                        }\r\n                        while(searchReplace(this,opt)){\r\n                            num++;\r\n                        }\r\n                        if(num){\r\n                            me.fireEvent('saveScene');\r\n                        }\r\n                    }else{\r\n                        if(opt.replaceStr !== undefined){\r\n                            me.fireEvent('saveScene');\r\n                        }\r\n                        if(searchReplace(this,opt)){\r\n                            num++\r\n                        }\r\n                        if(num){\r\n                            me.fireEvent('saveScene');\r\n                        }\r\n\r\n                    }\r\n\r\n                    return num;\r\n                },\r\n                notNeedUndo:1\r\n            }\r\n        }\r\n    }\r\n});\r\n\r\n// plugins/customstyle.js\r\n/**\r\n * 自定义样式\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n/**\r\n * 根据config配置文件里“customstyle”选项的值对匹配的标签执行样式替换。\r\n * @command customstyle\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * editor.execCommand( 'customstyle' );\r\n * ```\r\n */\r\nUE.plugins['customstyle'] = function() {\r\n    var me = this;\r\n    me.setOpt({ 'customstyle':[\r\n        {tag:'h1',name:'tc', style:'font-size:32px;font-weight:bold;border-bottom:#ccc 2px solid;padding:0 4px 0 0;text-align:center;margin:0 0 20px 0;'},\r\n        {tag:'h1',name:'tl', style:'font-size:32px;font-weight:bold;border-bottom:#ccc 2px solid;padding:0 4px 0 0;text-align:left;margin:0 0 10px 0;'},\r\n        {tag:'span',name:'im', style:'font-size:16px;font-style:italic;font-weight:bold;line-height:18px;'},\r\n        {tag:'span',name:'hi', style:'font-size:16px;font-style:italic;font-weight:bold;color:rgb(51, 153, 204);line-height:18px;'}\r\n    ]});\r\n    me.commands['customstyle'] = {\r\n        execCommand : function(cmdName, obj) {\r\n            var me = this,\r\n                    tagName = obj.tag,\r\n                    node = domUtils.findParent(me.selection.getStart(), function(node) {\r\n                        return node.getAttribute('label');\r\n                    }, true),\r\n                    range,bk,tmpObj = {};\r\n            for (var p in obj) {\r\n               if(obj[p]!==undefined)\r\n                    tmpObj[p] = obj[p];\r\n            }\r\n            delete tmpObj.tag;\r\n            if (node && node.getAttribute('label') == obj.label) {\r\n                range = this.selection.getRange();\r\n                bk = range.createBookmark();\r\n                if (range.collapsed) {\r\n                    //trace:1732 删掉自定义标签，要有p来回填站位\r\n                    if(dtd.$block[node.tagName]){\r\n                        var fillNode = me.document.createElement('p');\r\n                        domUtils.moveChild(node, fillNode);\r\n                        node.parentNode.insertBefore(fillNode, node);\r\n                        domUtils.remove(node);\r\n                    }else{\r\n                        domUtils.remove(node,true);\r\n                    }\r\n\r\n                } else {\r\n\r\n                    var common = domUtils.getCommonAncestor(bk.start, bk.end),\r\n                            nodes = domUtils.getElementsByTagName(common, tagName);\r\n                    if(new RegExp(tagName,'i').test(common.tagName)){\r\n                        nodes.push(common);\r\n                    }\r\n                    for (var i = 0,ni; ni = nodes[i++];) {\r\n                        if (ni.getAttribute('label') == obj.label) {\r\n                            var ps = domUtils.getPosition(ni, bk.start),pe = domUtils.getPosition(ni, bk.end);\r\n                            if ((ps & domUtils.POSITION_FOLLOWING || ps & domUtils.POSITION_CONTAINS)\r\n                                    &&\r\n                                    (pe & domUtils.POSITION_PRECEDING || pe & domUtils.POSITION_CONTAINS)\r\n                                    )\r\n                                if (dtd.$block[tagName]) {\r\n                                    var fillNode = me.document.createElement('p');\r\n                                    domUtils.moveChild(ni, fillNode);\r\n                                    ni.parentNode.insertBefore(fillNode, ni);\r\n                                }\r\n                            domUtils.remove(ni, true);\r\n                        }\r\n                    }\r\n                    node = domUtils.findParent(common, function(node) {\r\n                        return node.getAttribute('label') == obj.label;\r\n                    }, true);\r\n                    if (node) {\r\n\r\n                        domUtils.remove(node, true);\r\n\r\n                    }\r\n\r\n                }\r\n                range.moveToBookmark(bk).select();\r\n            } else {\r\n                if (dtd.$block[tagName]) {\r\n                    this.execCommand('paragraph', tagName, tmpObj,'customstyle');\r\n                    range = me.selection.getRange();\r\n                    if (!range.collapsed) {\r\n                        range.collapse();\r\n                        node = domUtils.findParent(me.selection.getStart(), function(node) {\r\n                            return node.getAttribute('label') == obj.label;\r\n                        }, true);\r\n                        var pNode = me.document.createElement('p');\r\n                        domUtils.insertAfter(node, pNode);\r\n                        domUtils.fillNode(me.document, pNode);\r\n                        range.setStart(pNode, 0).setCursor();\r\n                    }\r\n                } else {\r\n\r\n                    range = me.selection.getRange();\r\n                    if (range.collapsed) {\r\n                        node = me.document.createElement(tagName);\r\n                        domUtils.setAttributes(node, tmpObj);\r\n                        range.insertNode(node).setStart(node, 0).setCursor();\r\n\r\n                        return;\r\n                    }\r\n\r\n                    bk = range.createBookmark();\r\n                    range.applyInlineStyle(tagName, tmpObj).moveToBookmark(bk).select();\r\n                }\r\n            }\r\n\r\n        },\r\n        queryCommandValue : function() {\r\n            var parent = domUtils.filterNodeList(\r\n                this.selection.getStartElementPath(),\r\n                function(node){return node.getAttribute('label')}\r\n            );\r\n            return  parent ? parent.getAttribute('label') : '';\r\n        }\r\n    };\r\n    //当去掉customstyle是，如果是块元素，用p代替\r\n    me.addListener('keyup', function(type, evt) {\r\n        var keyCode = evt.keyCode || evt.which;\r\n\r\n        if (keyCode == 32 || keyCode == 13) {\r\n            var range = me.selection.getRange();\r\n            if (range.collapsed) {\r\n                var node = domUtils.findParent(me.selection.getStart(), function(node) {\r\n                    return node.getAttribute('label');\r\n                }, true);\r\n                if (node && dtd.$block[node.tagName] && domUtils.isEmptyNode(node)) {\r\n                        var p = me.document.createElement('p');\r\n                        domUtils.insertAfter(node, p);\r\n                        domUtils.fillNode(me.document, p);\r\n                        domUtils.remove(node);\r\n                        range.setStart(p, 0).setCursor();\r\n\r\n\r\n                }\r\n            }\r\n        }\r\n    });\r\n};\r\n\r\n// plugins/catchremoteimage.js\r\n///import core\r\n///commands 远程图片抓取\r\n///commandsName  catchRemoteImage,catchremoteimageenable\r\n///commandsTitle  远程图片抓取\r\n/**\r\n * 远程图片抓取,当开启本插件时所有不符合本地域名的图片都将被抓取成为本地服务器上的图片\r\n */\r\nUE.plugins['catchremoteimage'] = function () {\r\n    var me = this,\r\n        ajax = UE.ajax;\r\n\r\n    /* 设置默认值 */\r\n    if (me.options.catchRemoteImageEnable === false) return;\r\n    me.setOpt({\r\n        catchRemoteImageEnable: false\r\n    });\r\n\r\n    me.addListener(\"afterpaste\", function () {\r\n        me.fireEvent(\"catchRemoteImage\");\r\n    });\r\n\r\n    me.addListener(\"catchRemoteImage\", function () {\r\n\r\n        var catcherLocalDomain = me.getOpt('catcherLocalDomain'),\r\n            catcherActionUrl = me.getActionUrl(me.getOpt('catcherActionName')),\r\n            catcherUrlPrefix = me.getOpt('catcherUrlPrefix'),\r\n            catcherFieldName = me.getOpt('catcherFieldName');\r\n\r\n        var remoteImages = [],\r\n            imgs = domUtils.getElementsByTagName(me.document, \"img\"),\r\n            test = function (src, urls) {\r\n                if (src.indexOf(location.host) != -1 || /(^\\.)|(^\\/)/.test(src)) {\r\n                    return true;\r\n                }\r\n                if (urls) {\r\n                    for (var j = 0, url; url = urls[j++];) {\r\n                        if (src.indexOf(url) !== -1) {\r\n                            return true;\r\n                        }\r\n                    }\r\n                }\r\n                return false;\r\n            };\r\n\r\n        for (var i = 0, ci; ci = imgs[i++];) {\r\n            if (ci.getAttribute(\"word_img\")) {\r\n                continue;\r\n            }\r\n            var src = ci.getAttribute(\"_src\") || ci.src || \"\";\r\n            if (/^(https?|ftp):/i.test(src) && !test(src, catcherLocalDomain)) {\r\n                remoteImages.push(src);\r\n            }\r\n        }\r\n\r\n        if (remoteImages.length) {\r\n            catchremoteimage(remoteImages, {\r\n                //成功抓取\r\n                success: function (r) {\r\n                    try {\r\n                        var info = r.state !== undefined ? r:eval(\"(\" + r.responseText + \")\");\r\n                    } catch (e) {\r\n                        return;\r\n                    }\r\n\r\n                    /* 获取源路径和新路径 */\r\n                    var i, j, ci, cj, oldSrc, newSrc, list = info.list;\r\n\r\n                    for (i = 0; ci = imgs[i++];) {\r\n                        oldSrc = ci.getAttribute(\"_src\") || ci.src || \"\";\r\n                        for (j = 0; cj = list[j++];) {\r\n                            if (oldSrc == cj.source && cj.state == \"SUCCESS\") {  //抓取失败时不做替换处理\r\n                                newSrc = catcherUrlPrefix + cj.url;\r\n                                domUtils.setAttributes(ci, {\r\n                                    \"src\": newSrc,\r\n                                    \"_src\": newSrc\r\n                                });\r\n                                break;\r\n                            }\r\n                        }\r\n                    }\r\n                    me.fireEvent('catchremotesuccess')\r\n                },\r\n                //回调失败，本次请求超时\r\n                error: function () {\r\n                    me.fireEvent(\"catchremoteerror\");\r\n                }\r\n            });\r\n        }\r\n\r\n        function catchremoteimage(imgs, callbacks) {\r\n            var params = utils.serializeParam(me.queryCommandValue('serverparam')) || '',\r\n                url = utils.formatUrl(catcherActionUrl + (catcherActionUrl.indexOf('?') == -1 ? '?':'&') + params),\r\n                isJsonp = utils.isCrossDomainUrl(url),\r\n                opt = {\r\n                    'method': 'POST',\r\n                    'dataType': isJsonp ? 'jsonp':'',\r\n                    'timeout': 60000, //单位：毫秒，回调请求超时设置。目标用户如果网速不是很快的话此处建议设置一个较大的数值\r\n                    'onsuccess': callbacks[\"success\"],\r\n                    'onerror': callbacks[\"error\"]\r\n                };\r\n            opt[catcherFieldName] = imgs;\r\n            ajax.request(url, opt);\r\n        }\r\n\r\n    });\r\n};\r\n\r\n// plugins/snapscreen.js\r\n/**\r\n * 截屏插件，为UEditor提供插入支持\r\n * @file\r\n * @since 1.4.2\r\n */\r\nUE.plugin.register('snapscreen', function (){\r\n\r\n    var me = this;\r\n    var snapplugin;\r\n\r\n    function getLocation(url){\r\n        var search,\r\n            a = document.createElement('a'),\r\n            params = utils.serializeParam(me.queryCommandValue('serverparam')) || '';\r\n\r\n        a.href = url;\r\n        if (browser.ie) {\r\n            a.href = a.href;\r\n        }\r\n\r\n\r\n        search = a.search;\r\n        if (params) {\r\n            search = search + (search.indexOf('?') == -1 ? '?':'&')+ params;\r\n            search = search.replace(/[&]+/ig, '&');\r\n        }\r\n        return {\r\n            'port': a.port,\r\n            'hostname': a.hostname,\r\n            'path': a.pathname + search ||  + a.hash\r\n        }\r\n    }\r\n\r\n    return {\r\n        commands:{\r\n            /**\r\n             * 字体背景颜色\r\n             * @command snapscreen\r\n             * @method execCommand\r\n             * @param { String } cmd 命令字符串\r\n             * @example\r\n             * ```javascript\r\n             * editor.execCommand('snapscreen');\r\n             * ```\r\n             */\r\n            'snapscreen':{\r\n                execCommand:function (cmd) {\r\n                    var url, local, res;\r\n                    var lang = me.getLang(\"snapScreen_plugin\");\r\n\r\n                    if(!snapplugin){\r\n                        var container = me.container;\r\n                        var doc = me.container.ownerDocument || me.container.document;\r\n                        snapplugin = doc.createElement(\"object\");\r\n                        try{snapplugin.type = \"application/x-pluginbaidusnap\";}catch(e){\r\n                            return;\r\n                        }\r\n                        snapplugin.style.cssText = \"position:absolute;left:-9999px;width:0;height:0;\";\r\n                        snapplugin.setAttribute(\"width\",\"0\");\r\n                        snapplugin.setAttribute(\"height\",\"0\");\r\n                        container.appendChild(snapplugin);\r\n                    }\r\n\r\n                    function onSuccess(rs){\r\n                        try{\r\n                            rs = eval(\"(\"+ rs +\")\");\r\n                            if(rs.state == 'SUCCESS'){\r\n                                var opt = me.options;\r\n                                me.execCommand('insertimage', {\r\n                                    src: opt.snapscreenUrlPrefix + rs.url,\r\n                                    _src: opt.snapscreenUrlPrefix + rs.url,\r\n                                    alt: rs.title || '',\r\n                                    floatStyle: opt.snapscreenImgAlign\r\n                                });\r\n                            } else {\r\n                                alert(rs.state);\r\n                            }\r\n                        }catch(e){\r\n                            alert(lang.callBackErrorMsg);\r\n                        }\r\n                    }\r\n                    url = me.getActionUrl(me.getOpt('snapscreenActionName'));\r\n                    local = getLocation(url);\r\n                    setTimeout(function () {\r\n                        try{\r\n                            res =snapplugin.saveSnapshot(local.hostname, local.path, local.port);\r\n                        }catch(e){\r\n                            me.ui._dialogs['snapscreenDialog'].open();\r\n                            return;\r\n                        }\r\n\r\n                        onSuccess(res);\r\n                    }, 50);\r\n                },\r\n                queryCommandState: function(){\r\n                    return (navigator.userAgent.indexOf(\"Windows\",0) != -1) ? 0:-1;\r\n                }\r\n            }\r\n        }\r\n    }\r\n});\r\n\r\n\r\n// plugins/insertparagraph.js\r\n/**\r\n * 插入段落\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n\r\n/**\r\n * 插入段落\r\n * @command insertparagraph\r\n * @method execCommand\r\n * @param { String } cmd 命令字符串\r\n * @example\r\n * ```javascript\r\n * //editor是编辑器实例\r\n * editor.execCommand( 'insertparagraph' );\r\n * ```\r\n */\r\n\r\nUE.commands['insertparagraph'] = {\r\n    execCommand : function( cmdName,front) {\r\n        var me = this,\r\n            range = me.selection.getRange(),\r\n            start = range.startContainer,tmpNode;\r\n        while(start ){\r\n            if(domUtils.isBody(start)){\r\n                break;\r\n            }\r\n            tmpNode = start;\r\n            start = start.parentNode;\r\n        }\r\n        if(tmpNode){\r\n            var p = me.document.createElement('p');\r\n            if(front){\r\n                tmpNode.parentNode.insertBefore(p,tmpNode)\r\n            }else{\r\n                tmpNode.parentNode.insertBefore(p,tmpNode.nextSibling)\r\n            }\r\n            domUtils.fillNode(me.document,p);\r\n            range.setStart(p,0).setCursor(false,true);\r\n        }\r\n    }\r\n};\r\n\r\n\r\n\r\n// plugins/webapp.js\r\n/**\r\n * 百度应用\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\n\r\n\r\n/**\r\n * 插入百度应用\r\n * @command webapp\r\n * @method execCommand\r\n * @remind 需要百度APPKey\r\n * @remind 百度应用主页： <a href=\"http://app.baidu.com/\" target=\"_blank\">http://app.baidu.com/</a>\r\n * @param { Object } appOptions 应用所需的参数项， 支持的key有： title=>应用标题， width=>应用容器宽度，\r\n * height=>应用容器高度，logo=>应用logo，url=>应用地址\r\n * @example\r\n * ```javascript\r\n * //editor是编辑器实例\r\n * //在编辑器里插入一个“植物大战僵尸”的APP\r\n * editor.execCommand( 'webapp' , {\r\n *     title: '植物大战僵尸',\r\n *     width: 560,\r\n *     height: 465,\r\n *     logo: '应用展示的图片',\r\n *     url: '百度应用的地址'\r\n * } );\r\n * ```\r\n */\r\n\r\n//UE.plugins['webapp'] = function () {\r\n//    var me = this;\r\n//    function createInsertStr( obj, toIframe, addParagraph ) {\r\n//        return !toIframe ?\r\n//                (addParagraph ? '<p>' : '') + '<img title=\"'+obj.title+'\" width=\"' + obj.width + '\" height=\"' + obj.height + '\"' +\r\n//                        ' src=\"' + me.options.UEDITOR_HOME_URL + 'themes/default/images/spacer.gif\" style=\"background:url(' + obj.logo+') no-repeat center center; border:1px solid gray;\" class=\"edui-faked-webapp\" _url=\"' + obj.url + '\" />' +\r\n//                        (addParagraph ? '</p>' : '')\r\n//                :\r\n//                '<iframe class=\"edui-faked-webapp\" title=\"'+obj.title+'\" width=\"' + obj.width + '\" height=\"' + obj.height + '\"  scrolling=\"no\" frameborder=\"0\" src=\"' + obj.url + '\" logo_url = '+obj.logo+'></iframe>';\r\n//    }\r\n//\r\n//    function switchImgAndIframe( img2frame ) {\r\n//        var tmpdiv,\r\n//                nodes = domUtils.getElementsByTagName( me.document, !img2frame ? \"iframe\" : \"img\" );\r\n//        for ( var i = 0, node; node = nodes[i++]; ) {\r\n//            if ( node.className != \"edui-faked-webapp\" ){\r\n//                continue;\r\n//            }\r\n//            tmpdiv = me.document.createElement( \"div\" );\r\n//            tmpdiv.innerHTML = createInsertStr( img2frame ? {url:node.getAttribute( \"_url\" ), width:node.width, height:node.height,title:node.title,logo:node.style.backgroundImage.replace(\"url(\",\"\").replace(\")\",\"\")} : {url:node.getAttribute( \"src\", 2 ),title:node.title, width:node.width, height:node.height,logo:node.getAttribute(\"logo_url\")}, img2frame ? true : false,false );\r\n//            node.parentNode.replaceChild( tmpdiv.firstChild, node );\r\n//        }\r\n//    }\r\n//\r\n//    me.addListener( \"beforegetcontent\", function () {\r\n//        switchImgAndIframe( true );\r\n//    } );\r\n//    me.addListener( 'aftersetcontent', function () {\r\n//        switchImgAndIframe( false );\r\n//    } );\r\n//    me.addListener( 'aftergetcontent', function ( cmdName ) {\r\n//        if ( cmdName == 'aftergetcontent' && me.queryCommandState( 'source' ) ){\r\n//            return;\r\n//        }\r\n//        switchImgAndIframe( false );\r\n//    } );\r\n//\r\n//    me.commands['webapp'] = {\r\n//        execCommand:function ( cmd, obj ) {\r\n//            me.execCommand( \"inserthtml\", createInsertStr( obj, false,true ) );\r\n//        }\r\n//    };\r\n//};\r\n\r\nUE.plugin.register('webapp', function (){\r\n    var me = this;\r\n    function createInsertStr(obj,toEmbed){\r\n        return  !toEmbed ?\r\n            '<img title=\"'+obj.title+'\" width=\"' + obj.width + '\" height=\"' + obj.height + '\"' +\r\n                ' src=\"' + me.options.UEDITOR_HOME_URL + 'themes/default/images/spacer.gif\" _logo_url=\"'+obj.logo+'\" style=\"background:url(' + obj.logo\r\n                +') no-repeat center center; border:1px solid gray;\" class=\"edui-faked-webapp\" _url=\"' + obj.url + '\" ' +\r\n                (obj.align && !obj.cssfloat? 'align=\"' + obj.align + '\"' : '') +\r\n                (obj.cssfloat ? 'style=\"float:' + obj.cssfloat + '\"' : '') +\r\n                '/>'\r\n            :\r\n            '<iframe class=\"edui-faked-webapp\" title=\"'+obj.title+'\" ' +\r\n                (obj.align && !obj.cssfloat? 'align=\"' + obj.align + '\"' : '') +\r\n                (obj.cssfloat ? 'style=\"float:' + obj.cssfloat + '\"' : '') +\r\n                'width=\"' + obj.width + '\" height=\"' + obj.height + '\"  scrolling=\"no\" frameborder=\"0\" src=\"' + obj.url + '\" logo_url = \"'+obj.logo+'\"></iframe>'\r\n\r\n    }\r\n    return {\r\n        outputRule: function(root){\r\n            utils.each(root.getNodesByTagName('img'),function(node){\r\n                var html;\r\n                if(node.getAttr('class') == 'edui-faked-webapp'){\r\n                    html =  createInsertStr({\r\n                        title:node.getAttr('title'),\r\n                        'width':node.getAttr('width'),\r\n                        'height':node.getAttr('height'),\r\n                        'align':node.getAttr('align'),\r\n                        'cssfloat':node.getStyle('float'),\r\n                        'url':node.getAttr(\"_url\"),\r\n                        'logo':node.getAttr('_logo_url')\r\n                    },true);\r\n                    var embed = UE.uNode.createElement(html);\r\n                    node.parentNode.replaceChild(embed,node);\r\n                }\r\n            })\r\n        },\r\n        inputRule:function(root){\r\n            utils.each(root.getNodesByTagName('iframe'),function(node){\r\n                if(node.getAttr('class') == 'edui-faked-webapp'){\r\n                    var img = UE.uNode.createElement(createInsertStr({\r\n                        title:node.getAttr('title'),\r\n                        'width':node.getAttr('width'),\r\n                        'height':node.getAttr('height'),\r\n                        'align':node.getAttr('align'),\r\n                        'cssfloat':node.getStyle('float'),\r\n                        'url':node.getAttr(\"src\"),\r\n                        'logo':node.getAttr('logo_url')\r\n                    }));\r\n                    node.parentNode.replaceChild(img,node);\r\n                }\r\n            })\r\n\r\n        },\r\n        commands:{\r\n            /**\r\n             * 插入百度应用\r\n             * @command webapp\r\n             * @method execCommand\r\n             * @remind 需要百度APPKey\r\n             * @remind 百度应用主页： <a href=\"http://app.baidu.com/\" target=\"_blank\">http://app.baidu.com/</a>\r\n             * @param { Object } appOptions 应用所需的参数项， 支持的key有： title=>应用标题， width=>应用容器宽度，\r\n             * height=>应用容器高度，logo=>应用logo，url=>应用地址\r\n             * @example\r\n             * ```javascript\r\n             * //editor是编辑器实例\r\n             * //在编辑器里插入一个“植物大战僵尸”的APP\r\n             * editor.execCommand( 'webapp' , {\r\n             *     title: '植物大战僵尸',\r\n             *     width: 560,\r\n             *     height: 465,\r\n             *     logo: '应用展示的图片',\r\n             *     url: '百度应用的地址'\r\n             * } );\r\n             * ```\r\n             */\r\n            'webapp':{\r\n                execCommand:function (cmd, obj) {\r\n\r\n                    var me = this,\r\n                        str = createInsertStr(utils.extend(obj,{\r\n                            align:'none'\r\n                        }), false);\r\n                    me.execCommand(\"inserthtml\",str);\r\n                },\r\n                queryCommandState:function () {\r\n                    var me = this,\r\n                        img = me.selection.getRange().getClosedNode(),\r\n                        flag = img && (img.className == \"edui-faked-webapp\");\r\n                    return flag ? 1 : 0;\r\n                }\r\n            }\r\n        }\r\n    }\r\n});\r\n\r\n// plugins/template.js\r\n///import core\r\n///import plugins\\inserthtml.js\r\n///import plugins\\cleardoc.js\r\n///commands 模板\r\n///commandsName  template\r\n///commandsTitle  模板\r\n///commandsDialog  dialogs\\template\r\nUE.plugins['template'] = function () {\r\n    UE.commands['template'] = {\r\n        execCommand:function (cmd, obj) {\r\n            obj.html && this.execCommand(\"inserthtml\", obj.html);\r\n        }\r\n    };\r\n    this.addListener(\"click\", function (type, evt) {\r\n        var el = evt.target || evt.srcElement,\r\n            range = this.selection.getRange();\r\n        var tnode = domUtils.findParent(el, function (node) {\r\n            if (node.className && domUtils.hasClass(node, \"ue_t\")) {\r\n                return node;\r\n            }\r\n        }, true);\r\n        tnode && range.selectNode(tnode).shrinkBoundary().select();\r\n    });\r\n    this.addListener(\"keydown\", function (type, evt) {\r\n        var range = this.selection.getRange();\r\n        if (!range.collapsed) {\r\n            if (!evt.ctrlKey && !evt.metaKey && !evt.shiftKey && !evt.altKey) {\r\n                var tnode = domUtils.findParent(range.startContainer, function (node) {\r\n                    if (node.className && domUtils.hasClass(node, \"ue_t\")) {\r\n                        return node;\r\n                    }\r\n                }, true);\r\n                if (tnode) {\r\n                    domUtils.removeClasses(tnode, [\"ue_t\"]);\r\n                }\r\n            }\r\n        }\r\n    });\r\n};\r\n\r\n\r\n// plugins/music.js\r\n/**\r\n * 插入音乐命令\r\n * @file\r\n */\r\nUE.plugin.register('music', function (){\r\n    var me = this;\r\n    function creatInsertStr(url,width,height,align,cssfloat,toEmbed){\r\n        return  !toEmbed ?\r\n                '<img ' +\r\n                    (align && !cssfloat? 'align=\"' + align + '\"' : '') +\r\n                    (cssfloat ? 'style=\"float:' + cssfloat + '\"' : '') +\r\n                    ' width=\"'+ width +'\" height=\"' + height + '\" _url=\"'+url+'\" class=\"edui-faked-music\"' +\r\n                    ' src=\"'+me.options.langPath+me.options.lang+'/images/music.png\" />'\r\n            :\r\n            '<embed type=\"application/x-shockwave-flash\" class=\"edui-faked-music\" pluginspage=\"http://www.macromedia.com/go/getflashplayer\"' +\r\n                ' src=\"' + url + '\" width=\"' + width  + '\" height=\"' + height  + '\" '+ (align && !cssfloat? 'align=\"' + align + '\"' : '') +\r\n                (cssfloat ? 'style=\"float:' + cssfloat + '\"' : '') +\r\n                ' wmode=\"transparent\" play=\"true\" loop=\"false\" menu=\"false\" allowscriptaccess=\"never\" allowfullscreen=\"true\" >';\r\n    }\r\n    return {\r\n        outputRule: function(root){\r\n            utils.each(root.getNodesByTagName('img'),function(node){\r\n                var html;\r\n                if(node.getAttr('class') == 'edui-faked-music'){\r\n                    var cssfloat = node.getStyle('float');\r\n                    var align = node.getAttr('align');\r\n                    html =  creatInsertStr(node.getAttr(\"_url\"), node.getAttr('width'), node.getAttr('height'), align, cssfloat, true);\r\n                    var embed = UE.uNode.createElement(html);\r\n                    node.parentNode.replaceChild(embed,node);\r\n                }\r\n            })\r\n        },\r\n        inputRule:function(root){\r\n            utils.each(root.getNodesByTagName('embed'),function(node){\r\n                if(node.getAttr('class') == 'edui-faked-music'){\r\n                    var cssfloat = node.getStyle('float');\r\n                    var align = node.getAttr('align');\r\n                    html =  creatInsertStr(node.getAttr(\"src\"), node.getAttr('width'), node.getAttr('height'), align, cssfloat,false);\r\n                    var img = UE.uNode.createElement(html);\r\n                    node.parentNode.replaceChild(img,node);\r\n                }\r\n            })\r\n\r\n        },\r\n        commands:{\r\n            /**\r\n             * 插入音乐\r\n             * @command music\r\n             * @method execCommand\r\n             * @param { Object } musicOptions 插入音乐的参数项， 支持的key有： url=>音乐地址；\r\n             * width=>音乐容器宽度；height=>音乐容器高度；align=>音乐文件的对齐方式， 可选值有: left, center, right, none\r\n             * @example\r\n             * ```javascript\r\n             * //editor是编辑器实例\r\n             * //在编辑器里插入一个“植物大战僵尸”的APP\r\n             * editor.execCommand( 'music' , {\r\n             *     width: 400,\r\n             *     height: 95,\r\n             *     align: \"center\",\r\n             *     url: \"音乐地址\"\r\n             * } );\r\n             * ```\r\n             */\r\n            'music':{\r\n                execCommand:function (cmd, musicObj) {\r\n                    var me = this,\r\n                        str = creatInsertStr(musicObj.url, musicObj.width || 400, musicObj.height || 95, \"none\", false);\r\n                    me.execCommand(\"inserthtml\",str);\r\n                },\r\n                queryCommandState:function () {\r\n                    var me = this,\r\n                        img = me.selection.getRange().getClosedNode(),\r\n                        flag = img && (img.className == \"edui-faked-music\");\r\n                    return flag ? 1 : 0;\r\n                }\r\n            }\r\n        }\r\n    }\r\n});\r\n\r\n// plugins/autoupload.js\r\n/**\r\n * @description\r\n * 1.拖放文件到编辑区域，自动上传并插入到选区\r\n * 2.插入粘贴板的图片，自动上传并插入到选区\r\n * @author Jinqn\r\n * @date 2013-10-14\r\n */\r\nUE.plugin.register('autoupload', function (){\r\n\r\n    function sendAndInsertFile(file, editor) {\r\n        var me  = editor;\r\n        //模拟数据\r\n        var fieldName, urlPrefix, maxSize, allowFiles, actionUrl,\r\n            loadingHtml, errorHandler, successHandler,\r\n            filetype = /image\\/\\w+/i.test(file.type) ? 'image':'file',\r\n            loadingId = 'loading_' + (+new Date()).toString(36);\r\n\r\n        fieldName = me.getOpt(filetype + 'FieldName');\r\n        urlPrefix = me.getOpt(filetype + 'UrlPrefix');\r\n        maxSize = me.getOpt(filetype + 'MaxSize');\r\n        allowFiles = me.getOpt(filetype + 'AllowFiles');\r\n        actionUrl = me.getActionUrl(me.getOpt(filetype + 'ActionName'));\r\n        errorHandler = function(title) {\r\n            var loader = me.document.getElementById(loadingId);\r\n            loader && domUtils.remove(loader);\r\n            me.fireEvent('showmessage', {\r\n                'id': loadingId,\r\n                'content': title,\r\n                'type': 'error',\r\n                'timeout': 4000\r\n            });\r\n        };\r\n\r\n        if (filetype == 'image') {\r\n            loadingHtml = '<img class=\"loadingclass\" id=\"' + loadingId + '\" src=\"' +\r\n                me.options.themePath + me.options.theme +\r\n                '/images/spacer.gif\" title=\"' + (me.getLang('autoupload.loading') || '') + '\" >';\r\n            successHandler = function(data) {\r\n                var link = urlPrefix + data.url,\r\n                    loader = me.document.getElementById(loadingId);\r\n                if (loader) {\r\n                    loader.setAttribute('src', link);\r\n                    loader.setAttribute('_src', link);\r\n                    loader.setAttribute('title', data.title || '');\r\n                    loader.setAttribute('alt', data.original || '');\r\n                    loader.removeAttribute('id');\r\n                    domUtils.removeClasses(loader, 'loadingclass');\r\n                }\r\n            };\r\n        } else {\r\n            loadingHtml = '<p>' +\r\n                '<img class=\"loadingclass\" id=\"' + loadingId + '\" src=\"' +\r\n                me.options.themePath + me.options.theme +\r\n                '/images/spacer.gif\" title=\"' + (me.getLang('autoupload.loading') || '') + '\" >' +\r\n                '</p>';\r\n            successHandler = function(data) {\r\n                var link = urlPrefix + data.url,\r\n                    loader = me.document.getElementById(loadingId);\r\n\r\n                var rng = me.selection.getRange(),\r\n                    bk = rng.createBookmark();\r\n                rng.selectNode(loader).select();\r\n                me.execCommand('insertfile', {'url': link});\r\n                rng.moveToBookmark(bk).select();\r\n            };\r\n        }\r\n\r\n        /* 插入loading的占位符 */\r\n        me.execCommand('inserthtml', loadingHtml);\r\n\r\n        /* 判断后端配置是否没有加载成功 */\r\n        if (!me.getOpt(filetype + 'ActionName')) {\r\n            errorHandler(me.getLang('autoupload.errorLoadConfig'));\r\n            return;\r\n        }\r\n        /* 判断文件大小是否超出限制 */\r\n        if(file.size > maxSize) {\r\n            errorHandler(me.getLang('autoupload.exceedSizeError'));\r\n            return;\r\n        }\r\n        /* 判断文件格式是否超出允许 */\r\n        var fileext = file.name ? file.name.substr(file.name.lastIndexOf('.')):'';\r\n        if ((fileext && filetype != 'image') || (allowFiles && (allowFiles.join('') + '.').indexOf(fileext.toLowerCase() + '.') == -1)) {\r\n            errorHandler(me.getLang('autoupload.exceedTypeError'));\r\n            return;\r\n        }\r\n\r\n        /* 创建Ajax并提交 */\r\n        var xhr = new XMLHttpRequest(),\r\n            fd = new FormData(),\r\n            params = utils.serializeParam(me.queryCommandValue('serverparam')) || '',\r\n            url = utils.formatUrl(actionUrl + (actionUrl.indexOf('?') == -1 ? '?':'&') + params);\r\n\r\n        fd.append(fieldName, file, file.name || ('blob.' + file.type.substr('image/'.length)));\r\n        fd.append('type', 'ajax');\r\n        xhr.open(\"post\", url, true);\r\n        xhr.setRequestHeader(\"X-Requested-With\", \"XMLHttpRequest\");\r\n        xhr.addEventListener('load', function (e) {\r\n            try{\r\n                var json = (new Function(\"return \" + utils.trim(e.target.response)))();\r\n                if (json.state == 'SUCCESS' && json.url) {\r\n                    successHandler(json);\r\n                } else {\r\n                    errorHandler(json.state);\r\n                }\r\n            }catch(er){\r\n                errorHandler(me.getLang('autoupload.loadError'));\r\n            }\r\n        });\r\n        xhr.send(fd);\r\n    }\r\n\r\n    function getPasteImage(e){\r\n        return e.clipboardData && e.clipboardData.items && e.clipboardData.items.length == 1 && /^image\\//.test(e.clipboardData.items[0].type) ? e.clipboardData.items:null;\r\n    }\r\n    function getDropImage(e){\r\n        return  e.dataTransfer && e.dataTransfer.files ? e.dataTransfer.files:null;\r\n    }\r\n\r\n    return {\r\n        outputRule: function(root){\r\n            utils.each(root.getNodesByTagName('img'),function(n){\r\n                if (/\\b(loaderrorclass)|(bloaderrorclass)\\b/.test(n.getAttr('class'))) {\r\n                    n.parentNode.removeChild(n);\r\n                }\r\n            });\r\n            utils.each(root.getNodesByTagName('p'),function(n){\r\n                if (/\\bloadpara\\b/.test(n.getAttr('class'))) {\r\n                    n.parentNode.removeChild(n);\r\n                }\r\n            });\r\n        },\r\n        bindEvents:{\r\n            //插入粘贴板的图片，拖放插入图片\r\n            'ready':function(e){\r\n                var me = this;\r\n                if(window.FormData && window.FileReader) {\r\n                    domUtils.on(me.body, 'paste drop', function(e){\r\n                        var hasImg = false,\r\n                            items;\r\n                        //获取粘贴板文件列表或者拖放文件列表\r\n                        items = e.type == 'paste' ? getPasteImage(e):getDropImage(e);\r\n                        if(items){\r\n                            var len = items.length,\r\n                                file;\r\n                            while (len--){\r\n                                file = items[len];\r\n                                if(file.getAsFile) file = file.getAsFile();\r\n                                if(file && file.size > 0) {\r\n                                    sendAndInsertFile(file, me);\r\n                                    hasImg = true;\r\n                                }\r\n                            }\r\n                            hasImg && e.preventDefault();\r\n                        }\r\n\r\n                    });\r\n                    //取消拖放图片时出现的文字光标位置提示\r\n                    domUtils.on(me.body, 'dragover', function (e) {\r\n                        if(e.dataTransfer.types[0] == 'Files') {\r\n                            e.preventDefault();\r\n                        }\r\n                    });\r\n\r\n                    //设置loading的样式\r\n                    utils.cssRule('loading',\r\n                        '.loadingclass{display:inline-block;cursor:default;background: url(\\''\r\n                            + this.options.themePath\r\n                            + this.options.theme +'/images/loading.gif\\') no-repeat center center transparent;border:1px solid #cccccc;margin-left:1px;height: 22px;width: 22px;}\\n' +\r\n                            '.loaderrorclass{display:inline-block;cursor:default;background: url(\\''\r\n                            + this.options.themePath\r\n                            + this.options.theme +'/images/loaderror.png\\') no-repeat center center transparent;border:1px solid #cccccc;margin-right:1px;height: 22px;width: 22px;' +\r\n                            '}',\r\n                        this.document);\r\n                }\r\n            }\r\n        }\r\n    }\r\n});\r\n\r\n// plugins/autosave.js\r\nUE.plugin.register('autosave', function (){\r\n\r\n    var me = this,\r\n        //无限循环保护\r\n        lastSaveTime = new Date(),\r\n        //最小保存间隔时间\r\n        MIN_TIME = 20,\r\n        //auto save key\r\n        saveKey = null;\r\n\r\n    function save ( editor ) {\r\n\r\n        var saveData;\r\n\r\n        if ( new Date() - lastSaveTime < MIN_TIME ) {\r\n            return;\r\n        }\r\n\r\n        if ( !editor.hasContents() ) {\r\n            //这里不能调用命令来删除， 会造成事件死循环\r\n            saveKey && me.removePreferences( saveKey );\r\n            return;\r\n        }\r\n\r\n        lastSaveTime = new Date();\r\n\r\n        editor._saveFlag = null;\r\n\r\n        saveData = me.body.innerHTML;\r\n\r\n        if ( editor.fireEvent( \"beforeautosave\", {\r\n            content: saveData\r\n        } ) === false ) {\r\n            return;\r\n        }\r\n\r\n        me.setPreferences( saveKey, saveData );\r\n\r\n        editor.fireEvent( \"afterautosave\", {\r\n            content: saveData\r\n        } );\r\n\r\n    }\r\n\r\n    return {\r\n        defaultOptions: {\r\n            //默认间隔时间\r\n            saveInterval: 500,\r\n            enableAutoSave: true // HaoChuan9421\r\n        },\r\n        bindEvents:{\r\n            'ready':function(){\r\n\r\n                var _suffix = \"-drafts-data\",\r\n                    key = null;\r\n\r\n                if ( me.key ) {\r\n                    key = me.key + _suffix;\r\n                } else {\r\n                    key = ( me.container.parentNode.id || 'ue-common' ) + _suffix;\r\n                }\r\n\r\n                //页面地址+编辑器ID 保持唯一\r\n                saveKey = ( location.protocol + location.host + location.pathname ).replace( /[.:\\/]/g, '_' ) + key;\r\n\r\n            },\r\n\r\n            'contentchange': function () {\r\n                // HaoChuan9421\r\n                if (!me.getOpt('enableAutoSave')) {\r\n                    return;\r\n                }\r\n\r\n                if ( !saveKey ) {\r\n                    return;\r\n                }\r\n\r\n                if ( me._saveFlag ) {\r\n                    window.clearTimeout( me._saveFlag );\r\n                }\r\n\r\n                if ( me.options.saveInterval > 0 ) {\r\n\r\n                    me._saveFlag = window.setTimeout( function () {\r\n\r\n                        save( me );\r\n\r\n                    }, me.options.saveInterval );\r\n\r\n                } else {\r\n\r\n                    save(me);\r\n\r\n                }\r\n\r\n\r\n            }\r\n        },\r\n        commands:{\r\n            'clearlocaldata':{\r\n                execCommand:function (cmd, name) {\r\n                    if ( saveKey && me.getPreferences( saveKey ) ) {\r\n                        me.removePreferences( saveKey )\r\n                    }\r\n                },\r\n                notNeedUndo: true,\r\n                ignoreContentChange:true\r\n            },\r\n\r\n            'getlocaldata':{\r\n                execCommand:function (cmd, name) {\r\n                    return saveKey ? me.getPreferences( saveKey ) || '' : '';\r\n                },\r\n                notNeedUndo: true,\r\n                ignoreContentChange:true\r\n            },\r\n\r\n            'drafts':{\r\n                execCommand:function (cmd, name) {\r\n                    if ( saveKey ) {\r\n                        me.body.innerHTML = me.getPreferences( saveKey ) || '<p>'+domUtils.fillHtml+'</p>';\r\n                        me.focus(true);\r\n                    }\r\n                },\r\n                queryCommandState: function () {\r\n                    return saveKey ? ( me.getPreferences( saveKey ) === null ? -1 : 0 ) : -1;\r\n                },\r\n                notNeedUndo: true,\r\n                ignoreContentChange:true\r\n            }\r\n        }\r\n    }\r\n\r\n});\r\n\r\n// plugins/charts.js\r\nUE.plugin.register('charts', function (){\r\n\r\n    var me = this;\r\n\r\n    return {\r\n        bindEvents: {\r\n            'chartserror': function () {\r\n            }\r\n        },\r\n        commands:{\r\n            'charts': {\r\n                execCommand: function ( cmd, data ) {\r\n\r\n                    var tableNode = domUtils.findParentByTagName(this.selection.getRange().startContainer, 'table', true),\r\n                        flagText = [],\r\n                        config = {};\r\n\r\n                    if ( !tableNode ) {\r\n                        return false;\r\n                    }\r\n\r\n                    if ( !validData( tableNode ) ) {\r\n                        me.fireEvent( \"chartserror\" );\r\n                        return false;\r\n                    }\r\n\r\n                    config.title = data.title || '';\r\n                    config.subTitle = data.subTitle || '';\r\n                    config.xTitle = data.xTitle || '';\r\n                    config.yTitle = data.yTitle || '';\r\n                    config.suffix = data.suffix || '';\r\n                    config.tip = data.tip || '';\r\n                    //数据对齐方式\r\n                    config.dataFormat = data.tableDataFormat || '';\r\n                    //图表类型\r\n                    config.chartType = data.chartType || 0;\r\n\r\n                    for ( var key in config ) {\r\n\r\n                        if ( !config.hasOwnProperty( key ) ) {\r\n                            continue;\r\n                        }\r\n\r\n                        flagText.push( key+\":\"+config[ key ] );\r\n\r\n                    }\r\n\r\n                    tableNode.setAttribute( \"data-chart\", flagText.join( \";\" ) );\r\n                    domUtils.addClass( tableNode, \"edui-charts-table\" );\r\n\r\n\r\n\r\n                },\r\n                queryCommandState: function ( cmd, name ) {\r\n\r\n                    var tableNode = domUtils.findParentByTagName(this.selection.getRange().startContainer, 'table', true);\r\n                    return tableNode && validData( tableNode ) ? 0 : -1;\r\n\r\n                }\r\n            }\r\n        },\r\n        inputRule:function(root){\r\n            utils.each(root.getNodesByTagName('table'),function( tableNode ){\r\n\r\n                if ( tableNode.getAttr(\"data-chart\") !== undefined ) {\r\n                    tableNode.setAttr(\"style\");\r\n                }\r\n\r\n            })\r\n\r\n        },\r\n        outputRule:function(root){\r\n            utils.each(root.getNodesByTagName('table'),function( tableNode ){\r\n\r\n                if ( tableNode.getAttr(\"data-chart\") !== undefined ) {\r\n                    tableNode.setAttr(\"style\", \"display: none;\");\r\n                }\r\n\r\n            })\r\n\r\n        }\r\n    }\r\n\r\n    function validData ( table ) {\r\n\r\n        var firstRows = null,\r\n            cellCount = 0;\r\n\r\n        //行数不够\r\n        if ( table.rows.length < 2 ) {\r\n            return false;\r\n        }\r\n\r\n        //列数不够\r\n        if ( table.rows[0].cells.length < 2 ) {\r\n            return false;\r\n        }\r\n\r\n        //第一行所有cell必须是th\r\n        firstRows = table.rows[ 0 ].cells;\r\n        cellCount = firstRows.length;\r\n\r\n        for ( var i = 0, cell; cell = firstRows[ i ]; i++ ) {\r\n\r\n            if ( cell.tagName.toLowerCase() !== 'th' ) {\r\n                return false;\r\n            }\r\n\r\n        }\r\n\r\n        for ( var i = 1, row; row = table.rows[ i ]; i++ ) {\r\n\r\n            //每行单元格数不匹配， 返回false\r\n            if ( row.cells.length != cellCount ) {\r\n                return false;\r\n            }\r\n\r\n            //第一列不是th也返回false\r\n            if ( row.cells[0].tagName.toLowerCase() !== 'th' ) {\r\n                return false;\r\n            }\r\n\r\n            for ( var j = 1, cell; cell = row.cells[ j ]; j++ ) {\r\n\r\n                var value = utils.trim( ( cell.innerText || cell.textContent || '' ) );\r\n\r\n                value = value.replace( new RegExp( UE.dom.domUtils.fillChar, 'g' ), '' ).replace( /^\\s+|\\s+$/g, '' );\r\n\r\n                //必须是数字\r\n                if ( !/^\\d*\\.?\\d+$/.test( value ) ) {\r\n                    return false;\r\n                }\r\n\r\n            }\r\n\r\n        }\r\n\r\n        return true;\r\n\r\n    }\r\n\r\n});\r\n\r\n// plugins/section.js\r\n/**\r\n * 目录大纲支持插件\r\n * @file\r\n * @since 1.3.0\r\n */\r\nUE.plugin.register('section', function (){\r\n    /* 目录节点对象 */\r\n    function Section(option){\r\n        this.tag = '';\r\n        this.level = -1,\r\n            this.dom = null;\r\n        this.nextSection = null;\r\n        this.previousSection = null;\r\n        this.parentSection = null;\r\n        this.startAddress = [];\r\n        this.endAddress = [];\r\n        this.children = [];\r\n    }\r\n    function getSection(option) {\r\n        var section = new Section();\r\n        return utils.extend(section, option);\r\n    }\r\n    function getNodeFromAddress(startAddress, root) {\r\n        var current = root;\r\n        for(var i = 0;i < startAddress.length; i++) {\r\n            if(!current.childNodes) return null;\r\n            current = current.childNodes[startAddress[i]];\r\n        }\r\n        return current;\r\n    }\r\n\r\n    var me = this;\r\n\r\n    return {\r\n        bindMultiEvents:{\r\n            type: 'aftersetcontent afterscencerestore',\r\n            handler: function(){\r\n                me.fireEvent('updateSections');\r\n            }\r\n        },\r\n        bindEvents:{\r\n            /* 初始化、拖拽、粘贴、执行setcontent之后 */\r\n            'ready': function (){\r\n                me.fireEvent('updateSections');\r\n                domUtils.on(me.body, 'drop paste', function(){\r\n                    me.fireEvent('updateSections');\r\n                });\r\n            },\r\n            /* 执行paragraph命令之后 */\r\n            'afterexeccommand': function (type, cmd) {\r\n                if(cmd == 'paragraph') {\r\n                    me.fireEvent('updateSections');\r\n                }\r\n            },\r\n            /* 部分键盘操作，触发updateSections事件 */\r\n            'keyup': function (type, e) {\r\n                var me = this,\r\n                    range = me.selection.getRange();\r\n                if(range.collapsed != true) {\r\n                    me.fireEvent('updateSections');\r\n                } else {\r\n                    var keyCode = e.keyCode || e.which;\r\n                    if(keyCode == 13 || keyCode == 8 || keyCode == 46) {\r\n                        me.fireEvent('updateSections');\r\n                    }\r\n                }\r\n            }\r\n        },\r\n        commands:{\r\n            'getsections': {\r\n                execCommand: function (cmd, levels) {\r\n                    var levelFn = levels || ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'];\r\n\r\n                    for (var i = 0; i < levelFn.length; i++) {\r\n                        if (typeof levelFn[i] == 'string') {\r\n                            levelFn[i] = function(fn){\r\n                                return function(node){\r\n                                    return node.tagName == fn.toUpperCase()\r\n                                };\r\n                            }(levelFn[i]);\r\n                        } else if (typeof levelFn[i] != 'function') {\r\n                            levelFn[i] = function (node) {\r\n                                return null;\r\n                            }\r\n                        }\r\n                    }\r\n                    function getSectionLevel(node) {\r\n                        for (var i = 0; i < levelFn.length; i++) {\r\n                            if (levelFn[i](node)) return i;\r\n                        }\r\n                        return -1;\r\n                    }\r\n\r\n                    var me = this,\r\n                        Directory = getSection({'level':-1, 'title':'root'}),\r\n                        previous = Directory;\r\n\r\n                    function traversal(node, Directory) {\r\n                        var level,\r\n                            tmpSection = null,\r\n                            parent,\r\n                            child,\r\n                            children = node.childNodes;\r\n                        for (var i = 0, len = children.length; i < len; i++) {\r\n                            child = children[i];\r\n                            level = getSectionLevel(child);\r\n                            if (level >= 0) {\r\n                                var address = me.selection.getRange().selectNode(child).createAddress(true).startAddress,\r\n                                    current = getSection({\r\n                                        'tag': child.tagName,\r\n                                        'title': child.innerText || child.textContent || '',\r\n                                        'level': level,\r\n                                        'dom': child,\r\n                                        'startAddress': utils.clone(address, []),\r\n                                        'endAddress': utils.clone(address, []),\r\n                                        'children': []\r\n                                    });\r\n                                previous.nextSection = current;\r\n                                current.previousSection = previous;\r\n                                parent = previous;\r\n                                while(level <= parent.level){\r\n                                    parent = parent.parentSection;\r\n                                }\r\n                                current.parentSection = parent;\r\n                                parent.children.push(current);\r\n                                tmpSection = previous = current;\r\n                            } else {\r\n                                child.nodeType === 1 && traversal(child, Directory);\r\n                                tmpSection && tmpSection.endAddress[tmpSection.endAddress.length - 1] ++;\r\n                            }\r\n                        }\r\n                    }\r\n                    traversal(me.body, Directory);\r\n                    return Directory;\r\n                },\r\n                notNeedUndo: true\r\n            },\r\n            'movesection': {\r\n                execCommand: function (cmd, sourceSection, targetSection, isAfter) {\r\n\r\n                    var me = this,\r\n                        targetAddress,\r\n                        target;\r\n\r\n                    if(!sourceSection || !targetSection || targetSection.level == -1) return;\r\n\r\n                    targetAddress = isAfter ? targetSection.endAddress:targetSection.startAddress;\r\n                    target = getNodeFromAddress(targetAddress, me.body);\r\n\r\n                    /* 判断目标地址是否被源章节包含 */\r\n                    if(!targetAddress || !target || isContainsAddress(sourceSection.startAddress, sourceSection.endAddress, targetAddress)) return;\r\n\r\n                    var startNode = getNodeFromAddress(sourceSection.startAddress, me.body),\r\n                        endNode = getNodeFromAddress(sourceSection.endAddress, me.body),\r\n                        current,\r\n                        nextNode;\r\n\r\n                    if(isAfter) {\r\n                        current = endNode;\r\n                        while ( current && !(domUtils.getPosition( startNode, current ) & domUtils.POSITION_FOLLOWING) ) {\r\n                            nextNode = current.previousSibling;\r\n                            domUtils.insertAfter(target, current);\r\n                            if(current == startNode) break;\r\n                            current = nextNode;\r\n                        }\r\n                    } else {\r\n                        current = startNode;\r\n                        while ( current && !(domUtils.getPosition( current, endNode ) & domUtils.POSITION_FOLLOWING) ) {\r\n                            nextNode = current.nextSibling;\r\n                            target.parentNode.insertBefore(current, target);\r\n                            if(current == endNode) break;\r\n                            current = nextNode;\r\n                        }\r\n                    }\r\n\r\n                    me.fireEvent('updateSections');\r\n\r\n                    /* 获取地址的包含关系 */\r\n                    function isContainsAddress(startAddress, endAddress, addressTarget){\r\n                        var isAfterStartAddress = false,\r\n                            isBeforeEndAddress = false;\r\n                        for(var i = 0; i< startAddress.length; i++){\r\n                            if(i >= addressTarget.length) break;\r\n                            if(addressTarget[i] > startAddress[i]) {\r\n                                isAfterStartAddress = true;\r\n                                break;\r\n                            } else if(addressTarget[i] < startAddress[i]) {\r\n                                break;\r\n                            }\r\n                        }\r\n                        for(var i = 0; i< endAddress.length; i++){\r\n                            if(i >= addressTarget.length) break;\r\n                            if(addressTarget[i] < startAddress[i]) {\r\n                                isBeforeEndAddress = true;\r\n                                break;\r\n                            } else if(addressTarget[i] > startAddress[i]) {\r\n                                break;\r\n                            }\r\n                        }\r\n                        return isAfterStartAddress && isBeforeEndAddress;\r\n                    }\r\n                }\r\n            },\r\n            'deletesection': {\r\n                execCommand: function (cmd, section, keepChildren) {\r\n                    var me = this;\r\n\r\n                    if(!section) return;\r\n\r\n                    function getNodeFromAddress(startAddress) {\r\n                        var current = me.body;\r\n                        for(var i = 0;i < startAddress.length; i++) {\r\n                            if(!current.childNodes) return null;\r\n                            current = current.childNodes[startAddress[i]];\r\n                        }\r\n                        return current;\r\n                    }\r\n\r\n                    var startNode = getNodeFromAddress(section.startAddress),\r\n                        endNode = getNodeFromAddress(section.endAddress),\r\n                        current = startNode,\r\n                        nextNode;\r\n\r\n                    if(!keepChildren) {\r\n                        while ( current && domUtils.inDoc(endNode, me.document) && !(domUtils.getPosition( current, endNode ) & domUtils.POSITION_FOLLOWING) ) {\r\n                            nextNode = current.nextSibling;\r\n                            domUtils.remove(current);\r\n                            current = nextNode;\r\n                        }\r\n                    } else {\r\n                        domUtils.remove(current);\r\n                    }\r\n\r\n                    me.fireEvent('updateSections');\r\n                }\r\n            },\r\n            'selectsection': {\r\n                execCommand: function (cmd, section) {\r\n                    if(!section && !section.dom) return false;\r\n                    var me = this,\r\n                        range = me.selection.getRange(),\r\n                        address = {\r\n                            'startAddress':utils.clone(section.startAddress, []),\r\n                            'endAddress':utils.clone(section.endAddress, [])\r\n                        };\r\n                    address.endAddress[address.endAddress.length - 1]++;\r\n                    range.moveToAddress(address).select().scrollToView();\r\n                    return true;\r\n                },\r\n                notNeedUndo: true\r\n            },\r\n            'scrolltosection': {\r\n                execCommand: function (cmd, section) {\r\n                    if(!section && !section.dom) return false;\r\n                    var me = this,\r\n                        range = me.selection.getRange(),\r\n                        address = {\r\n                            'startAddress':section.startAddress,\r\n                            'endAddress':section.endAddress\r\n                        };\r\n                    address.endAddress[address.endAddress.length - 1]++;\r\n                    range.moveToAddress(address).scrollToView();\r\n                    return true;\r\n                },\r\n                notNeedUndo: true\r\n            }\r\n        }\r\n    }\r\n});\r\n\r\n// plugins/simpleupload.js\r\n/**\r\n * @description\r\n * 简单上传:点击按钮,直接选择文件上传。\r\n * 原 UEditor 作者使用了 form 表单 + iframe 的方式上传\r\n * 但由于同源策略的限制，父页面无法访问跨域的 iframe 内容\r\n * 导致无法获取接口返回的数据，使得单图上传无法在跨域的情况下使用\r\n * 这里改为普通的XHR上传，兼容到IE10+\r\n * @author HaoChuan9421 <hc199421@gmail.com>\r\n * @date 2018-12-20\r\n */\r\nUE.plugin.register('simpleupload', function() {\r\n  var me = this,\r\n    containerBtn,\r\n    timestrap = (+new Date()).toString(36);\r\n\r\n  function initUploadBtn() {\r\n    var w = containerBtn.offsetWidth || 20,\r\n      h = containerBtn.offsetHeight || 20,\r\n      btnStyle = 'display:block;width:' + w + 'px;height:' + h + 'px;overflow:hidden;border:0;margin:0;padding:0;position:absolute;top:0;left:0;filter:alpha(opacity=0);-moz-opacity:0;-khtml-opacity: 0;opacity: 0;cursor:pointer;';\r\n\r\n    var form = document.createElement('form');\r\n    var input = document.createElement('input');\r\n    form.id = 'edui_form_' + timestrap;\r\n    form.enctype = 'multipart/form-data';\r\n    form.style = btnStyle;\r\n    input.id = 'edui_input_' + timestrap;\r\n    input.type = 'file'\r\n    input.accept = 'image/*';\r\n    input.name = me.options.imageFieldName;\r\n    input.style = btnStyle;\r\n    form.appendChild(input);\r\n    containerBtn.appendChild(form);\r\n\r\n    input.addEventListener('change', function(event) {\r\n      if (!input.value) return;\r\n      var loadingId = 'loading_' + (+new Date()).toString(36);\r\n      var imageActionUrl = me.getActionUrl(me.getOpt('imageActionName'));\r\n      var params = utils.serializeParam(me.queryCommandValue('serverparam')) || '';\r\n      var action = utils.formatUrl(imageActionUrl + (imageActionUrl.indexOf('?') == -1 ? '?' : '&') + params);\r\n      var allowFiles = me.getOpt('imageAllowFiles');\r\n      me.focus();\r\n      me.execCommand('inserthtml', '<img class=\"loadingclass\" id=\"' + loadingId + '\" src=\"' + me.options.themePath + me.options.theme + '/images/spacer.gif\" title=\"' + (me.getLang('simpleupload.loading') || '') + '\" >');\r\n\r\n      function showErrorLoader(title) {\r\n        if (loadingId) {\r\n          var loader = me.document.getElementById(loadingId);\r\n          loader && domUtils.remove(loader);\r\n          me.fireEvent('showmessage', {\r\n            'id': loadingId,\r\n            'content': title,\r\n            'type': 'error',\r\n            'timeout': 4000\r\n          });\r\n        }\r\n      }\r\n      /* 判断后端配置是否没有加载成功 */\r\n      if (!me.getOpt('imageActionName')) {\r\n        showErrorLoader(me.getLang('autoupload.errorLoadConfig'));\r\n        return;\r\n      }\r\n      // 判断文件格式是否错误\r\n      var filename = input.value,\r\n        fileext = filename ? filename.substr(filename.lastIndexOf('.')) : '';\r\n      if (!fileext || (allowFiles && (allowFiles.join('') + '.').indexOf(fileext.toLowerCase() + '.') == -1)) {\r\n        showErrorLoader(me.getLang('simpleupload.exceedTypeError'));\r\n        return;\r\n      }\r\n\r\n      var xhr = new XMLHttpRequest()\r\n      xhr.open('post', action, true)\r\n      if (me.options.headers && Object.prototype.toString.apply(me.options.headers) === \"[object Object]\") {\r\n        for (var key in me.options.headers) {\r\n          xhr.setRequestHeader(key, me.options.headers[key])\r\n        }\r\n      }\r\n      xhr.onload = function() {\r\n        if ((xhr.status >= 200 && xhr.status < 300) || xhr.status == 304) {\r\n          var res = JSON.parse(xhr.responseText)\r\n          var link = me.options.imageUrlPrefix + res.url;\r\n\r\n          if (res.state == 'SUCCESS' && res.url) {\r\n            loader = me.document.getElementById(loadingId);\r\n            loader.setAttribute('src', link);\r\n            loader.setAttribute('_src', link);\r\n            loader.setAttribute('title', res.title || '');\r\n            loader.setAttribute('alt', res.original || '');\r\n            loader.removeAttribute('id');\r\n            domUtils.removeClasses(loader, 'loadingclass');\r\n            me.fireEvent(\"contentchange\");\r\n          } else {\r\n            showErrorLoader(res.state);\r\n          }\r\n        } else {\r\n          showErrorLoader(me.getLang('simpleupload.loadError'));\r\n        }\r\n      };\r\n      xhr.onerror = function() {\r\n        showErrorLoader(me.getLang('simpleupload.loadError'));\r\n      };\r\n      xhr.send(new FormData(form));\r\n      form.reset();\r\n    })\r\n  }\r\n\r\n  return {\r\n    bindEvents: {\r\n      'ready': function() {\r\n        //设置loading的样式\r\n        utils.cssRule('loading',\r\n          '.loadingclass{display:inline-block;cursor:default;background: url(\\'' +\r\n          this.options.themePath +\r\n          this.options.theme + '/images/loading.gif\\') no-repeat center center transparent;border:1px solid #cccccc;margin-right:1px;height: 22px;width: 22px;}\\n' +\r\n          '.loaderrorclass{display:inline-block;cursor:default;background: url(\\'' +\r\n          this.options.themePath +\r\n          this.options.theme + '/images/loaderror.png\\') no-repeat center center transparent;border:1px solid #cccccc;margin-right:1px;height: 22px;width: 22px;' +\r\n          '}',\r\n          this.document);\r\n      },\r\n      /* 初始化简单上传按钮 */\r\n      'simpleuploadbtnready': function(type, container) {\r\n        containerBtn = container;\r\n        me.afterConfigReady(initUploadBtn);\r\n      }\r\n    },\r\n    outputRule: function(root) {\r\n      utils.each(root.getNodesByTagName('img'), function(n) {\r\n        if (/\\b(loaderrorclass)|(bloaderrorclass)\\b/.test(n.getAttr('class'))) {\r\n          n.parentNode.removeChild(n);\r\n        }\r\n      });\r\n    }\r\n  }\r\n});\r\n\r\n// plugins/serverparam.js\r\n/**\r\n * 服务器提交的额外参数列表设置插件\r\n * @file\r\n * @since 1.2.6.1\r\n */\r\nUE.plugin.register('serverparam', function (){\r\n\r\n    var me = this,\r\n        serverParam = {};\r\n\r\n    return {\r\n        commands:{\r\n            /**\r\n             * 修改服务器提交的额外参数列表,清除所有项\r\n             * @command serverparam\r\n             * @method execCommand\r\n             * @param { String } cmd 命令字符串\r\n             * @example\r\n             * ```javascript\r\n             * editor.execCommand('serverparam');\r\n             * editor.queryCommandValue('serverparam'); //返回空\r\n             * ```\r\n             */\r\n            /**\r\n             * 修改服务器提交的额外参数列表,删除指定项\r\n             * @command serverparam\r\n             * @method execCommand\r\n             * @param { String } cmd 命令字符串\r\n             * @param { String } key 要清除的属性\r\n             * @example\r\n             * ```javascript\r\n             * editor.execCommand('serverparam', 'name'); //删除属性name\r\n             * ```\r\n             */\r\n            /**\r\n             * 修改服务器提交的额外参数列表,使用键值添加项\r\n             * @command serverparam\r\n             * @method execCommand\r\n             * @param { String } cmd 命令字符串\r\n             * @param { String } key 要添加的属性\r\n             * @param { String } value 要添加属性的值\r\n             * @example\r\n             * ```javascript\r\n             * editor.execCommand('serverparam', 'name', 'hello');\r\n             * editor.queryCommandValue('serverparam'); //返回对象 {'name': 'hello'}\r\n             * ```\r\n             */\r\n            /**\r\n             * 修改服务器提交的额外参数列表,传入键值对对象添加多项\r\n             * @command serverparam\r\n             * @method execCommand\r\n             * @param { String } cmd 命令字符串\r\n             * @param { Object } key 传入的键值对对象\r\n             * @example\r\n             * ```javascript\r\n             * editor.execCommand('serverparam', {'name': 'hello'});\r\n             * editor.queryCommandValue('serverparam'); //返回对象 {'name': 'hello'}\r\n             * ```\r\n             */\r\n            /**\r\n             * 修改服务器提交的额外参数列表,使用自定义函数添加多项\r\n             * @command serverparam\r\n             * @method execCommand\r\n             * @param { String } cmd 命令字符串\r\n             * @param { Function } key 自定义获取参数的函数\r\n             * @example\r\n             * ```javascript\r\n             * editor.execCommand('serverparam', function(editor){\r\n             *     return {'key': 'value'};\r\n             * });\r\n             * editor.queryCommandValue('serverparam'); //返回对象 {'key': 'value'}\r\n             * ```\r\n             */\r\n\r\n            /**\r\n             * 获取服务器提交的额外参数列表\r\n             * @command serverparam\r\n             * @method queryCommandValue\r\n             * @param { String } cmd 命令字符串\r\n             * @example\r\n             * ```javascript\r\n             * editor.queryCommandValue( 'serverparam' ); //返回对象 {'key': 'value'}\r\n             * ```\r\n             */\r\n            'serverparam':{\r\n                execCommand:function (cmd, key, value) {\r\n                    if (key === undefined || key === null) { //不传参数,清空列表\r\n                        serverParam = {};\r\n                    } else if (utils.isString(key)) { //传入键值\r\n                        if(value === undefined || value === null) {\r\n                            delete serverParam[key];\r\n                        } else {\r\n                            serverParam[key] = value;\r\n                        }\r\n                    } else if (utils.isObject(key)) { //传入对象,覆盖列表项\r\n                        utils.extend(serverParam, key, true);\r\n                    } else if (utils.isFunction(key)){ //传入函数,添加列表项\r\n                        utils.extend(serverParam, key(), true);\r\n                    }\r\n                },\r\n                queryCommandValue: function(){\r\n                    return serverParam || {};\r\n                }\r\n            }\r\n        }\r\n    }\r\n});\r\n\r\n\r\n// plugins/insertfile.js\r\n/**\r\n * 插入附件\r\n */\r\nUE.plugin.register('insertfile', function (){\r\n\r\n    var me = this;\r\n\r\n    function getFileIcon(url){\r\n        var ext = url.substr(url.lastIndexOf('.') + 1).toLowerCase(),\r\n            maps = {\r\n                \"rar\":\"icon_rar.gif\",\r\n                \"zip\":\"icon_rar.gif\",\r\n                \"tar\":\"icon_rar.gif\",\r\n                \"gz\":\"icon_rar.gif\",\r\n                \"bz2\":\"icon_rar.gif\",\r\n                \"doc\":\"icon_doc.gif\",\r\n                \"docx\":\"icon_doc.gif\",\r\n                \"pdf\":\"icon_pdf.gif\",\r\n                \"mp3\":\"icon_mp3.gif\",\r\n                \"xls\":\"icon_xls.gif\",\r\n                \"chm\":\"icon_chm.gif\",\r\n                \"ppt\":\"icon_ppt.gif\",\r\n                \"pptx\":\"icon_ppt.gif\",\r\n                \"avi\":\"icon_mv.gif\",\r\n                \"rmvb\":\"icon_mv.gif\",\r\n                \"wmv\":\"icon_mv.gif\",\r\n                \"flv\":\"icon_mv.gif\",\r\n                \"swf\":\"icon_mv.gif\",\r\n                \"rm\":\"icon_mv.gif\",\r\n                \"exe\":\"icon_exe.gif\",\r\n                \"psd\":\"icon_psd.gif\",\r\n                \"txt\":\"icon_txt.gif\",\r\n                \"jpg\":\"icon_jpg.gif\",\r\n                \"png\":\"icon_jpg.gif\",\r\n                \"jpeg\":\"icon_jpg.gif\",\r\n                \"gif\":\"icon_jpg.gif\",\r\n                \"ico\":\"icon_jpg.gif\",\r\n                \"bmp\":\"icon_jpg.gif\"\r\n            };\r\n        return maps[ext] ? maps[ext]:maps['txt'];\r\n    }\r\n\r\n    return {\r\n        commands:{\r\n            'insertfile': {\r\n                execCommand: function (command, filelist){\r\n                    filelist = utils.isArray(filelist) ? filelist : [filelist];\r\n\r\n                    var i, item, icon, title,\r\n                        html = '',\r\n                        URL = me.getOpt('UEDITOR_HOME_URL'),\r\n                        iconDir = URL + (URL.substr(URL.length - 1) == '/' ? '':'/') + 'dialogs/attachment/fileTypeImages/';\r\n                    for (i = 0; i < filelist.length; i++) {\r\n                        item = filelist[i];\r\n                        icon = iconDir + getFileIcon(item.url);\r\n                        title = item.title || item.url.substr(item.url.lastIndexOf('/') + 1);\r\n                        html += '<p style=\"line-height: 16px;\">' +\r\n                            '<img style=\"vertical-align: middle; margin-right: 2px;\" src=\"'+ icon + '\" _src=\"' + icon + '\" />' +\r\n                            '<a style=\"font-size:12px; color:#0066cc;\" href=\"' + item.url +'\" title=\"' + title + '\">' + title + '</a>' +\r\n                            '</p>';\r\n                    }\r\n                    me.execCommand('insertHtml', html);\r\n                }\r\n            }\r\n        }\r\n    }\r\n});\r\n\r\n\r\n\r\n\r\n// plugins/xssFilter.js\r\n/**\r\n * @file xssFilter.js\r\n * @desc xss过滤器\r\n * @author robbenmu\r\n */\r\n\r\nUE.plugins.xssFilter = function() {\r\n\r\n\tvar config = UEDITOR_CONFIG;\r\n\tvar whitList = config.whitList;\r\n\r\n\tfunction filter(node) {\r\n\r\n\t\tvar tagName = node.tagName;\r\n\t\tvar attrs = node.attrs;\r\n\r\n\t\tif (!whitList.hasOwnProperty(tagName)) {\r\n\t\t\tnode.parentNode.removeChild(node);\r\n\t\t\treturn false;\r\n\t\t}\r\n\r\n\t\tUE.utils.each(attrs, function (val, key) {\r\n\r\n\t\t\tif (whitList[tagName].indexOf(key) === -1) {\r\n\t\t\t\tnode.setAttr(key);\r\n\t\t\t}\r\n\t\t});\r\n\t}\r\n\r\n\t// 添加inserthtml\\paste等操作用的过滤规则\r\n\tif (whitList && config.xssFilterRules) {\r\n\t\tthis.options.filterRules = function () {\r\n\r\n\t\t\tvar result = {};\r\n\r\n\t\t\tUE.utils.each(whitList, function(val, key) {\r\n\t\t\t\tresult[key] = function (node) {\r\n\t\t\t\t\treturn filter(node);\r\n\t\t\t\t};\r\n\t\t\t});\r\n\r\n\t\t\treturn result;\r\n\t\t}();\r\n\t}\r\n\r\n\tvar tagList = [];\r\n\r\n\tUE.utils.each(whitList, function (val, key) {\r\n\t\ttagList.push(key);\r\n\t});\r\n\r\n\t// 添加input过滤规则\r\n\t//\r\n\tif (whitList && config.inputXssFilter) {\r\n\t\tthis.addInputRule(function (root) {\r\n\r\n\t\t\troot.traversal(function(node) {\r\n\t\t\t\tif (node.type !== 'element') {\r\n\t\t\t\t\treturn false;\r\n\t\t\t\t}\r\n\t\t\t\tfilter(node);\r\n\t\t\t});\r\n\t\t});\r\n\t}\r\n\t// 添加output过滤规则\r\n\t//\r\n\tif (whitList && config.outputXssFilter) {\r\n\t\tthis.addOutputRule(function (root) {\r\n\r\n\t\t\troot.traversal(function(node) {\r\n\t\t\t\tif (node.type !== 'element') {\r\n\t\t\t\t\treturn false;\r\n\t\t\t\t}\r\n\t\t\t\tfilter(node);\r\n\t\t\t});\r\n\t\t});\r\n\t}\r\n\r\n};\r\n\r\n\r\n// ui/ui.js\r\nvar baidu = baidu || {};\r\nbaidu.editor = baidu.editor || {};\r\nUE.ui = baidu.editor.ui = {};\r\n\r\n// ui/uiutils.js\r\n(function (){\r\n    var browser = baidu.editor.browser,\r\n        domUtils = baidu.editor.dom.domUtils;\r\n\r\n    var magic = '$EDITORUI';\r\n    var root = window[magic] = {};\r\n    var uidMagic = 'ID' + magic;\r\n    var uidCount = 0;\r\n\r\n    var uiUtils = baidu.editor.ui.uiUtils = {\r\n        uid: function (obj){\r\n            return (obj ? obj[uidMagic] || (obj[uidMagic] = ++ uidCount) : ++ uidCount);\r\n        },\r\n        hook: function ( fn, callback ) {\r\n            var dg;\r\n            if (fn && fn._callbacks) {\r\n                dg = fn;\r\n            } else {\r\n                dg = function (){\r\n                    var q;\r\n                    if (fn) {\r\n                        q = fn.apply(this, arguments);\r\n                    }\r\n                    var callbacks = dg._callbacks;\r\n                    var k = callbacks.length;\r\n                    while (k --) {\r\n                        var r = callbacks[k].apply(this, arguments);\r\n                        if (q === undefined) {\r\n                            q = r;\r\n                        }\r\n                    }\r\n                    return q;\r\n                };\r\n                dg._callbacks = [];\r\n            }\r\n            dg._callbacks.push(callback);\r\n            return dg;\r\n        },\r\n        createElementByHtml: function (html){\r\n            var el = document.createElement('div');\r\n            el.innerHTML = html;\r\n            el = el.firstChild;\r\n            el.parentNode.removeChild(el);\r\n            return el;\r\n        },\r\n        getViewportElement: function (){\r\n            return (browser.ie && browser.quirks) ?\r\n                document.body : document.documentElement;\r\n        },\r\n        getClientRect: function (element){\r\n            var bcr;\r\n            //trace  IE6下在控制编辑器显隐时可能会报错，catch一下\r\n            try{\r\n                bcr = element.getBoundingClientRect();\r\n            }catch(e){\r\n                bcr={left:0,top:0,height:0,width:0}\r\n            }\r\n            var rect = {\r\n                left: Math.round(bcr.left),\r\n                top: Math.round(bcr.top),\r\n                height: Math.round(bcr.bottom - bcr.top),\r\n                width: Math.round(bcr.right - bcr.left)\r\n            };\r\n            var doc;\r\n            while ((doc = element.ownerDocument) !== document &&\r\n                (element = domUtils.getWindow(doc).frameElement)) {\r\n                bcr = element.getBoundingClientRect();\r\n                rect.left += bcr.left;\r\n                rect.top += bcr.top;\r\n            }\r\n            rect.bottom = rect.top + rect.height;\r\n            rect.right = rect.left + rect.width;\r\n            return rect;\r\n        },\r\n        getViewportRect: function (){\r\n            var viewportEl = uiUtils.getViewportElement();\r\n            var width = (window.innerWidth || viewportEl.clientWidth) | 0;\r\n            var height = (window.innerHeight ||viewportEl.clientHeight) | 0;\r\n            return {\r\n                left: 0,\r\n                top: 0,\r\n                height: height,\r\n                width: width,\r\n                bottom: height,\r\n                right: width\r\n            };\r\n        },\r\n        setViewportOffset: function (element, offset){\r\n            var rect;\r\n            var fixedLayer = uiUtils.getFixedLayer();\r\n            if (element.parentNode === fixedLayer) {\r\n                element.style.left = offset.left + 'px';\r\n                element.style.top = offset.top + 'px';\r\n            } else {\r\n                domUtils.setViewportOffset(element, offset);\r\n            }\r\n        },\r\n        getEventOffset: function (evt){\r\n            var el = evt.target || evt.srcElement;\r\n            var rect = uiUtils.getClientRect(el);\r\n            var offset = uiUtils.getViewportOffsetByEvent(evt);\r\n            return {\r\n                left: offset.left - rect.left,\r\n                top: offset.top - rect.top\r\n            };\r\n        },\r\n        getViewportOffsetByEvent: function (evt){\r\n            var el = evt.target || evt.srcElement;\r\n            var frameEl = domUtils.getWindow(el).frameElement;\r\n            var offset = {\r\n                left: evt.clientX,\r\n                top: evt.clientY\r\n            };\r\n            if (frameEl && el.ownerDocument !== document) {\r\n                var rect = uiUtils.getClientRect(frameEl);\r\n                offset.left += rect.left;\r\n                offset.top += rect.top;\r\n            }\r\n            return offset;\r\n        },\r\n        setGlobal: function (id, obj){\r\n            root[id] = obj;\r\n            return magic + '[\"' + id  + '\"]';\r\n        },\r\n        unsetGlobal: function (id){\r\n            delete root[id];\r\n        },\r\n        copyAttributes: function (tgt, src){\r\n            var attributes = src.attributes;\r\n            var k = attributes.length;\r\n            while (k --) {\r\n                var attrNode = attributes[k];\r\n                if ( attrNode.nodeName != 'style' && attrNode.nodeName != 'class' && (!browser.ie || attrNode.specified) ) {\r\n                    tgt.setAttribute(attrNode.nodeName, attrNode.nodeValue);\r\n                }\r\n            }\r\n            if (src.className) {\r\n                domUtils.addClass(tgt,src.className);\r\n            }\r\n            if (src.style.cssText) {\r\n                tgt.style.cssText += ';' + src.style.cssText;\r\n            }\r\n        },\r\n        removeStyle: function (el, styleName){\r\n            if (el.style.removeProperty) {\r\n                el.style.removeProperty(styleName);\r\n            } else if (el.style.removeAttribute) {\r\n                el.style.removeAttribute(styleName);\r\n            } else throw '';\r\n        },\r\n        contains: function (elA, elB){\r\n            return elA && elB && (elA === elB ? false : (\r\n                elA.contains ? elA.contains(elB) :\r\n                    elA.compareDocumentPosition(elB) & 16\r\n                ));\r\n        },\r\n        startDrag: function (evt, callbacks,doc){\r\n            var doc = doc || document;\r\n            var startX = evt.clientX;\r\n            var startY = evt.clientY;\r\n            function handleMouseMove(evt){\r\n                var x = evt.clientX - startX;\r\n                var y = evt.clientY - startY;\r\n                callbacks.ondragmove(x, y,evt);\r\n                if (evt.stopPropagation) {\r\n                    evt.stopPropagation();\r\n                } else {\r\n                    evt.cancelBubble = true;\r\n                }\r\n            }\r\n            if (doc.addEventListener) {\r\n                function handleMouseUp(evt){\r\n                    doc.removeEventListener('mousemove', handleMouseMove, true);\r\n                    doc.removeEventListener('mouseup', handleMouseUp, true);\r\n                    window.removeEventListener('mouseup', handleMouseUp, true);\r\n                    callbacks.ondragstop();\r\n                }\r\n                doc.addEventListener('mousemove', handleMouseMove, true);\r\n                doc.addEventListener('mouseup', handleMouseUp, true);\r\n                window.addEventListener('mouseup', handleMouseUp, true);\r\n\r\n                evt.preventDefault();\r\n            } else {\r\n                var elm = evt.srcElement;\r\n                elm.setCapture();\r\n                function releaseCaptrue(){\r\n                    elm.releaseCapture();\r\n                    elm.detachEvent('onmousemove', handleMouseMove);\r\n                    elm.detachEvent('onmouseup', releaseCaptrue);\r\n                    elm.detachEvent('onlosecaptrue', releaseCaptrue);\r\n                    callbacks.ondragstop();\r\n                }\r\n                elm.attachEvent('onmousemove', handleMouseMove);\r\n                elm.attachEvent('onmouseup', releaseCaptrue);\r\n                elm.attachEvent('onlosecaptrue', releaseCaptrue);\r\n                evt.returnValue = false;\r\n            }\r\n            callbacks.ondragstart();\r\n        },\r\n        getFixedLayer: function (){\r\n            var layer = document.getElementById('edui_fixedlayer');\r\n            if (layer == null) {\r\n                layer = document.createElement('div');\r\n                layer.id = 'edui_fixedlayer';\r\n                document.body.appendChild(layer);\r\n                if (browser.ie && browser.version <= 8) {\r\n                    layer.style.position = 'absolute';\r\n                    bindFixedLayer();\r\n                    setTimeout(updateFixedOffset);\r\n                } else {\r\n                    layer.style.position = 'fixed';\r\n                }\r\n                layer.style.left = '0';\r\n                layer.style.top = '0';\r\n                layer.style.width = '0';\r\n                layer.style.height = '0';\r\n            }\r\n            return layer;\r\n        },\r\n        makeUnselectable: function (element){\r\n            if (browser.opera || (browser.ie && browser.version < 9)) {\r\n                element.unselectable = 'on';\r\n                if (element.hasChildNodes()) {\r\n                    for (var i=0; i<element.childNodes.length; i++) {\r\n                        if (element.childNodes[i].nodeType == 1) {\r\n                            uiUtils.makeUnselectable(element.childNodes[i]);\r\n                        }\r\n                    }\r\n                }\r\n            } else {\r\n                if (element.style.MozUserSelect !== undefined) {\r\n                    element.style.MozUserSelect = 'none';\r\n                } else if (element.style.WebkitUserSelect !== undefined) {\r\n                    element.style.WebkitUserSelect = 'none';\r\n                } else if (element.style.KhtmlUserSelect !== undefined) {\r\n                    element.style.KhtmlUserSelect = 'none';\r\n                }\r\n            }\r\n        }\r\n    };\r\n    function updateFixedOffset(){\r\n        var layer = document.getElementById('edui_fixedlayer');\r\n        uiUtils.setViewportOffset(layer, {\r\n            left: 0,\r\n            top: 0\r\n        });\r\n//        layer.style.display = 'none';\r\n//        layer.style.display = 'block';\r\n\r\n        //#trace: 1354\r\n//        setTimeout(updateFixedOffset);\r\n    }\r\n    function bindFixedLayer(adjOffset){\r\n        domUtils.on(window, 'scroll', updateFixedOffset);\r\n        domUtils.on(window, 'resize', baidu.editor.utils.defer(updateFixedOffset, 0, true));\r\n    }\r\n})();\r\n\r\n\r\n// ui/uibase.js\r\n(function () {\r\n    var utils = baidu.editor.utils,\r\n        uiUtils = baidu.editor.ui.uiUtils,\r\n        EventBase = baidu.editor.EventBase,\r\n        UIBase = baidu.editor.ui.UIBase = function () {\r\n        };\r\n\r\n    UIBase.prototype = {\r\n        className:'',\r\n        uiName:'',\r\n        initOptions:function (options) {\r\n            var me = this;\r\n            for (var k in options) {\r\n                me[k] = options[k];\r\n            }\r\n            this.id = this.id || 'edui' + uiUtils.uid();\r\n        },\r\n        initUIBase:function () {\r\n            this._globalKey = utils.unhtml(uiUtils.setGlobal(this.id, this));\r\n        },\r\n        render:function (holder) {\r\n            var html = this.renderHtml();\r\n            var el = uiUtils.createElementByHtml(html);\r\n\r\n            //by xuheng 给每个node添加class\r\n            var list = domUtils.getElementsByTagName(el, \"*\");\r\n            var theme = \"edui-\" + (this.theme || this.editor.options.theme);\r\n            var layer = document.getElementById('edui_fixedlayer');\r\n            for (var i = 0, node; node = list[i++];) {\r\n                domUtils.addClass(node, theme);\r\n            }\r\n            domUtils.addClass(el, theme);\r\n            if(layer){\r\n                layer.className=\"\";\r\n                domUtils.addClass(layer,theme);\r\n            }\r\n\r\n            var seatEl = this.getDom();\r\n            if (seatEl != null) {\r\n                seatEl.parentNode.replaceChild(el, seatEl);\r\n                uiUtils.copyAttributes(el, seatEl);\r\n            } else {\r\n                if (typeof holder == 'string') {\r\n                    holder = document.getElementById(holder);\r\n                }\r\n                holder = holder || uiUtils.getFixedLayer();\r\n                domUtils.addClass(holder, theme);\r\n                holder.appendChild(el);\r\n            }\r\n            this.postRender();\r\n        },\r\n        getDom:function (name) {\r\n            if (!name) {\r\n                return document.getElementById(this.id);\r\n            } else {\r\n                return document.getElementById(this.id + '_' + name);\r\n            }\r\n        },\r\n        postRender:function () {\r\n            this.fireEvent('postrender');\r\n        },\r\n        getHtmlTpl:function () {\r\n            return '';\r\n        },\r\n        formatHtml:function (tpl) {\r\n            var prefix = 'edui-' + this.uiName;\r\n            return (tpl\r\n                .replace(/##/g, this.id)\r\n                .replace(/%%-/g, this.uiName ? prefix + '-' : '')\r\n                .replace(/%%/g, (this.uiName ? prefix : '') + ' ' + this.className)\r\n                .replace(/\\$\\$/g, this._globalKey));\r\n        },\r\n        renderHtml:function () {\r\n            return this.formatHtml(this.getHtmlTpl());\r\n        },\r\n        dispose:function () {\r\n            var box = this.getDom();\r\n            if (box) baidu.editor.dom.domUtils.remove(box);\r\n            uiUtils.unsetGlobal(this.id);\r\n        }\r\n    };\r\n    utils.inherits(UIBase, EventBase);\r\n})();\r\n\r\n\r\n// ui/separator.js\r\n(function (){\r\n    var utils = baidu.editor.utils,\r\n        UIBase = baidu.editor.ui.UIBase,\r\n        Separator = baidu.editor.ui.Separator = function (options){\r\n            this.initOptions(options);\r\n            this.initSeparator();\r\n        };\r\n    Separator.prototype = {\r\n        uiName: 'separator',\r\n        initSeparator: function (){\r\n            this.initUIBase();\r\n        },\r\n        getHtmlTpl: function (){\r\n            return '<div id=\"##\" class=\"edui-box %%\"></div>';\r\n        }\r\n    };\r\n    utils.inherits(Separator, UIBase);\r\n\r\n})();\r\n\r\n\r\n// ui/mask.js\r\n///import core\r\n///import uicore\r\n(function (){\r\n    var utils = baidu.editor.utils,\r\n        domUtils = baidu.editor.dom.domUtils,\r\n        UIBase = baidu.editor.ui.UIBase,\r\n        uiUtils = baidu.editor.ui.uiUtils;\r\n    \r\n    var Mask = baidu.editor.ui.Mask = function (options){\r\n        this.initOptions(options);\r\n        this.initUIBase();\r\n    };\r\n    Mask.prototype = {\r\n        getHtmlTpl: function (){\r\n            return '<div id=\"##\" class=\"edui-mask %%\" onclick=\"return $$._onClick(event, this);\" onmousedown=\"return $$._onMouseDown(event, this);\"></div>';\r\n        },\r\n        postRender: function (){\r\n            var me = this;\r\n            domUtils.on(window, 'resize', function (){\r\n                setTimeout(function (){\r\n                    if (!me.isHidden()) {\r\n                        me._fill();\r\n                    }\r\n                });\r\n            });\r\n        },\r\n        show: function (zIndex){\r\n            this._fill();\r\n            this.getDom().style.display = '';\r\n            this.getDom().style.zIndex = zIndex;\r\n        },\r\n        hide: function (){\r\n            this.getDom().style.display = 'none';\r\n            this.getDom().style.zIndex = '';\r\n        },\r\n        isHidden: function (){\r\n            return this.getDom().style.display == 'none';\r\n        },\r\n        _onMouseDown: function (){\r\n            return false;\r\n        },\r\n        _onClick: function (e, target){\r\n            this.fireEvent('click', e, target);\r\n        },\r\n        _fill: function (){\r\n            var el = this.getDom();\r\n            var vpRect = uiUtils.getViewportRect();\r\n            el.style.width = vpRect.width + 'px';\r\n            el.style.height = vpRect.height + 'px';\r\n        }\r\n    };\r\n    utils.inherits(Mask, UIBase);\r\n})();\r\n\r\n\r\n// ui/popup.js\r\n///import core\r\n///import uicore\r\n(function () {\r\n    var utils = baidu.editor.utils,\r\n        uiUtils = baidu.editor.ui.uiUtils,\r\n        domUtils = baidu.editor.dom.domUtils,\r\n        UIBase = baidu.editor.ui.UIBase,\r\n        Popup = baidu.editor.ui.Popup = function (options){\r\n            this.initOptions(options);\r\n            this.initPopup();\r\n        };\r\n\r\n    var allPopups = [];\r\n    function closeAllPopup( evt,el ){\r\n        for ( var i = 0; i < allPopups.length; i++ ) {\r\n            var pop = allPopups[i];\r\n            if (!pop.isHidden()) {\r\n                if (pop.queryAutoHide(el) !== false) {\r\n                    if(evt&&/scroll/ig.test(evt.type)&&pop.className==\"edui-wordpastepop\")   return;\r\n                    pop.hide();\r\n                }\r\n            }\r\n        }\r\n\r\n        if(allPopups.length)\r\n            pop.editor.fireEvent(\"afterhidepop\");\r\n    }\r\n\r\n    Popup.postHide = closeAllPopup;\r\n\r\n    var ANCHOR_CLASSES = ['edui-anchor-topleft','edui-anchor-topright',\r\n        'edui-anchor-bottomleft','edui-anchor-bottomright'];\r\n    Popup.prototype = {\r\n        SHADOW_RADIUS: 5,\r\n        content: null,\r\n        _hidden: false,\r\n        autoRender: true,\r\n        canSideLeft: true,\r\n        canSideUp: true,\r\n        initPopup: function (){\r\n            this.initUIBase();\r\n            allPopups.push( this );\r\n        },\r\n        getHtmlTpl: function (){\r\n            return '<div id=\"##\" class=\"edui-popup %%\" onmousedown=\"return false;\">' +\r\n                ' <div id=\"##_body\" class=\"edui-popup-body\">' +\r\n                ' <iframe style=\"position:absolute;z-index:-1;left:0;top:0;background-color: transparent;\" frameborder=\"0\" width=\"100%\" height=\"100%\" src=\"about:blank\"></iframe>' +\r\n                ' <div class=\"edui-shadow\"></div>' +\r\n                ' <div id=\"##_content\" class=\"edui-popup-content\">' +\r\n                this.getContentHtmlTpl() +\r\n                '  </div>' +\r\n                ' </div>' +\r\n                '</div>';\r\n        },\r\n        getContentHtmlTpl: function (){\r\n            if(this.content){\r\n                if (typeof this.content == 'string') {\r\n                    return this.content;\r\n                }\r\n                return this.content.renderHtml();\r\n            }else{\r\n                return ''\r\n            }\r\n\r\n        },\r\n        _UIBase_postRender: UIBase.prototype.postRender,\r\n        postRender: function (){\r\n\r\n\r\n            if (this.content instanceof UIBase) {\r\n                this.content.postRender();\r\n            }\r\n\r\n            //捕获鼠标滚轮\r\n            if( this.captureWheel && !this.captured ) {\r\n\r\n                this.captured = true;\r\n\r\n                var winHeight = ( document.documentElement.clientHeight || document.body.clientHeight )  - 80,\r\n                    _height = this.getDom().offsetHeight,\r\n                    _top = uiUtils.getClientRect( this.combox.getDom() ).top,\r\n                    content = this.getDom('content'),\r\n                    ifr = this.getDom('body').getElementsByTagName('iframe'),\r\n                    me = this;\r\n\r\n                ifr.length && ( ifr = ifr[0] );\r\n\r\n                while( _top + _height > winHeight ) {\r\n                    _height -= 30;\r\n                }\r\n                content.style.height = _height + 'px';\r\n                //同步更改iframe高度\r\n                ifr && ( ifr.style.height = _height + 'px' );\r\n\r\n                //阻止在combox上的鼠标滚轮事件, 防止用户的正常操作被误解\r\n                if( window.XMLHttpRequest ) {\r\n\r\n                    domUtils.on( content, ( 'onmousewheel' in document.body ) ? 'mousewheel' :'DOMMouseScroll' , function(e){\r\n\r\n                        if(e.preventDefault) {\r\n                            e.preventDefault();\r\n                        } else {\r\n                            e.returnValue = false;\r\n                        }\r\n\r\n                        if( e.wheelDelta ) {\r\n\r\n                            content.scrollTop -= ( e.wheelDelta / 120 )*60;\r\n\r\n                        } else {\r\n\r\n                            content.scrollTop -= ( e.detail / -3 )*60;\r\n\r\n                        }\r\n\r\n                    });\r\n\r\n                } else {\r\n\r\n                    //ie6\r\n                    domUtils.on( this.getDom(), 'mousewheel' , function(e){\r\n\r\n                        e.returnValue = false;\r\n\r\n                        me.getDom('content').scrollTop -= ( e.wheelDelta / 120 )*60;\r\n\r\n                    });\r\n\r\n                }\r\n\r\n            }\r\n            this.fireEvent('postRenderAfter');\r\n            this.hide(true);\r\n            this._UIBase_postRender();\r\n        },\r\n        _doAutoRender: function (){\r\n            if (!this.getDom() && this.autoRender) {\r\n                this.render();\r\n            }\r\n        },\r\n        mesureSize: function (){\r\n            var box = this.getDom('content');\r\n            return uiUtils.getClientRect(box);\r\n        },\r\n        fitSize: function (){\r\n            if( this.captureWheel && this.sized ) {\r\n                return this.__size;\r\n            }\r\n            this.sized = true;\r\n            var popBodyEl = this.getDom('body');\r\n            popBodyEl.style.width = '';\r\n            popBodyEl.style.height = '';\r\n            var size = this.mesureSize();\r\n            if( this.captureWheel ) {\r\n                popBodyEl.style.width =  -(-20 -size.width) + 'px';\r\n                var height = parseInt( this.getDom('content').style.height, 10 );\r\n                !window.isNaN( height ) && ( size.height = height );\r\n            } else {\r\n                popBodyEl.style.width =  size.width + 'px';\r\n            }\r\n            popBodyEl.style.height = size.height + 'px';\r\n            this.__size = size;\r\n            this.captureWheel && (this.getDom('content').style.overflow = 'auto');\r\n            return size;\r\n        },\r\n        showAnchor: function ( element, hoz ){\r\n            this.showAnchorRect( uiUtils.getClientRect( element ), hoz );\r\n        },\r\n        showAnchorRect: function ( rect, hoz, adj ){\r\n            this._doAutoRender();\r\n            var vpRect = uiUtils.getViewportRect();\r\n            this.getDom().style.visibility = 'hidden';\r\n            this._show();\r\n            var popSize = this.fitSize();\r\n\r\n            var sideLeft, sideUp, left, top;\r\n            if (hoz) {\r\n                sideLeft = this.canSideLeft && (rect.right + popSize.width > vpRect.right && rect.left > popSize.width);\r\n                sideUp = this.canSideUp && (rect.top + popSize.height > vpRect.bottom && rect.bottom > popSize.height);\r\n                left = (sideLeft ? rect.left - popSize.width : rect.right);\r\n                top = (sideUp ? rect.bottom - popSize.height : rect.top);\r\n            } else {\r\n                sideLeft = this.canSideLeft && (rect.right + popSize.width > vpRect.right && rect.left > popSize.width);\r\n                sideUp = this.canSideUp && (rect.top + popSize.height > vpRect.bottom && rect.bottom > popSize.height);\r\n                left = (sideLeft ? rect.right - popSize.width : rect.left);\r\n                top = (sideUp ? rect.top - popSize.height : rect.bottom);\r\n            }\r\n\r\n            var popEl = this.getDom();\r\n            uiUtils.setViewportOffset(popEl, {\r\n                left: left,\r\n                top: top\r\n            });\r\n            domUtils.removeClasses(popEl, ANCHOR_CLASSES);\r\n            popEl.className += ' ' + ANCHOR_CLASSES[(sideUp ? 1 : 0) * 2 + (sideLeft ? 1 : 0)];\r\n            if(this.editor){\r\n                popEl.style.zIndex = this.editor.container.style.zIndex * 1 + 10;\r\n                baidu.editor.ui.uiUtils.getFixedLayer().style.zIndex = popEl.style.zIndex - 1;\r\n            }\r\n            this.getDom().style.visibility = 'visible';\r\n\r\n        },\r\n        showAt: function (offset) {\r\n            var left = offset.left;\r\n            var top = offset.top;\r\n            var rect = {\r\n                left: left,\r\n                top: top,\r\n                right: left,\r\n                bottom: top,\r\n                height: 0,\r\n                width: 0\r\n            };\r\n            this.showAnchorRect(rect, false, true);\r\n        },\r\n        _show: function (){\r\n            if (this._hidden) {\r\n                var box = this.getDom();\r\n                box.style.display = '';\r\n                this._hidden = false;\r\n//                if (box.setActive) {\r\n//                    box.setActive();\r\n//                }\r\n                this.fireEvent('show');\r\n            }\r\n        },\r\n        isHidden: function (){\r\n            return this._hidden;\r\n        },\r\n        show: function (){\r\n            this._doAutoRender();\r\n            this._show();\r\n        },\r\n        hide: function (notNofity){\r\n            if (!this._hidden && this.getDom()) {\r\n                this.getDom().style.display = 'none';\r\n                this._hidden = true;\r\n                if (!notNofity) {\r\n                    this.fireEvent('hide');\r\n                }\r\n            }\r\n        },\r\n        queryAutoHide: function (el){\r\n            return !el || !uiUtils.contains(this.getDom(), el);\r\n        }\r\n    };\r\n    utils.inherits(Popup, UIBase);\r\n    \r\n    domUtils.on( document, 'mousedown', function ( evt ) {\r\n        var el = evt.target || evt.srcElement;\r\n        closeAllPopup( evt,el );\r\n    } );\r\n    domUtils.on( window, 'scroll', function (evt,el) {\r\n        closeAllPopup( evt,el );\r\n    } );\r\n\r\n})();\r\n\r\n\r\n// ui/colorpicker.js\r\n///import core\r\n///import uicore\r\n(function (){\r\n    var utils = baidu.editor.utils,\r\n        UIBase = baidu.editor.ui.UIBase,\r\n        ColorPicker = baidu.editor.ui.ColorPicker = function (options){\r\n            this.initOptions(options);\r\n            this.noColorText = this.noColorText || this.editor.getLang(\"clearColor\");\r\n            this.initUIBase();\r\n        };\r\n\r\n    ColorPicker.prototype = {\r\n        getHtmlTpl: function (){\r\n            return genColorPicker(this.noColorText,this.editor);\r\n        },\r\n        _onTableClick: function (evt){\r\n            var tgt = evt.target || evt.srcElement;\r\n            var color = tgt.getAttribute('data-color');\r\n            if (color) {\r\n                this.fireEvent('pickcolor', color);\r\n            }\r\n        },\r\n        _onTableOver: function (evt){\r\n            var tgt = evt.target || evt.srcElement;\r\n            var color = tgt.getAttribute('data-color');\r\n            if (color) {\r\n                this.getDom('preview').style.backgroundColor = color;\r\n            }\r\n        },\r\n        _onTableOut: function (){\r\n            this.getDom('preview').style.backgroundColor = '';\r\n        },\r\n        _onPickNoColor: function (){\r\n            this.fireEvent('picknocolor');\r\n        }\r\n    };\r\n    utils.inherits(ColorPicker, UIBase);\r\n\r\n    var COLORS = (\r\n        'ffffff,000000,eeece1,1f497d,4f81bd,c0504d,9bbb59,8064a2,4bacc6,f79646,' +\r\n            'f2f2f2,7f7f7f,ddd9c3,c6d9f0,dbe5f1,f2dcdb,ebf1dd,e5e0ec,dbeef3,fdeada,' +\r\n            'd8d8d8,595959,c4bd97,8db3e2,b8cce4,e5b9b7,d7e3bc,ccc1d9,b7dde8,fbd5b5,' +\r\n            'bfbfbf,3f3f3f,938953,548dd4,95b3d7,d99694,c3d69b,b2a2c7,92cddc,fac08f,' +\r\n            'a5a5a5,262626,494429,17365d,366092,953734,76923c,5f497a,31859b,e36c09,' +\r\n            '7f7f7f,0c0c0c,1d1b10,0f243e,244061,632423,4f6128,3f3151,205867,974806,' +\r\n            'c00000,ff0000,ffc000,ffff00,92d050,00b050,00b0f0,0070c0,002060,7030a0,').split(',');\r\n\r\n    function genColorPicker(noColorText,editor){\r\n        var html = '<div id=\"##\" class=\"edui-colorpicker %%\">' +\r\n            '<div class=\"edui-colorpicker-topbar edui-clearfix\">' +\r\n            '<div unselectable=\"on\" id=\"##_preview\" class=\"edui-colorpicker-preview\"></div>' +\r\n            '<div unselectable=\"on\" class=\"edui-colorpicker-nocolor\" onclick=\"$$._onPickNoColor(event, this);\">'+ noColorText +'</div>' +\r\n            '</div>' +\r\n            '<table  class=\"edui-box\" style=\"border-collapse: collapse;\" onmouseover=\"$$._onTableOver(event, this);\" onmouseout=\"$$._onTableOut(event, this);\" onclick=\"return $$._onTableClick(event, this);\" cellspacing=\"0\" cellpadding=\"0\">' +\r\n            '<tr style=\"border-bottom: 1px solid #ddd;font-size: 13px;line-height: 25px;color:#39C;padding-top: 2px\"><td colspan=\"10\">'+editor.getLang(\"themeColor\")+'</td> </tr>'+\r\n            '<tr class=\"edui-colorpicker-tablefirstrow\" >';\r\n        for (var i=0; i<COLORS.length; i++) {\r\n            if (i && i%10 === 0) {\r\n                html += '</tr>'+(i==60?'<tr style=\"border-bottom: 1px solid #ddd;font-size: 13px;line-height: 25px;color:#39C;\"><td colspan=\"10\">'+editor.getLang(\"standardColor\")+'</td></tr>':'')+'<tr'+(i==60?' class=\"edui-colorpicker-tablefirstrow\"':'')+'>';\r\n            }\r\n            html += i<70 ? '<td style=\"padding: 0 2px;\"><a hidefocus title=\"'+COLORS[i]+'\" onclick=\"return false;\" href=\"javascript:\" unselectable=\"on\" class=\"edui-box edui-colorpicker-colorcell\"' +\r\n                ' data-color=\"#'+ COLORS[i] +'\"'+\r\n                ' style=\"background-color:#'+ COLORS[i] +';border:solid #ccc;'+\r\n                (i<10 || i>=60?'border-width:1px;':\r\n                    i>=10&&i<20?'border-width:1px 1px 0 1px;':\r\n\r\n                        'border-width:0 1px 0 1px;')+\r\n                '\"' +\r\n                '></a></td>':'';\r\n        }\r\n        html += '</tr></table></div>';\r\n        return html;\r\n    }\r\n})();\r\n\r\n\r\n// ui/tablepicker.js\r\n///import core\r\n///import uicore\r\n(function (){\r\n    var utils = baidu.editor.utils,\r\n        uiUtils = baidu.editor.ui.uiUtils,\r\n        UIBase = baidu.editor.ui.UIBase;\r\n    \r\n    var TablePicker = baidu.editor.ui.TablePicker = function (options){\r\n        this.initOptions(options);\r\n        this.initTablePicker();\r\n    };\r\n    TablePicker.prototype = {\r\n        defaultNumRows: 10,\r\n        defaultNumCols: 10,\r\n        maxNumRows: 20,\r\n        maxNumCols: 20,\r\n        numRows: 10,\r\n        numCols: 10,\r\n        lengthOfCellSide: 22,\r\n        initTablePicker: function (){\r\n            this.initUIBase();\r\n        },\r\n        getHtmlTpl: function (){\r\n            var me = this;\r\n            return '<div id=\"##\" class=\"edui-tablepicker %%\">' +\r\n                 '<div class=\"edui-tablepicker-body\">' +\r\n                  '<div class=\"edui-infoarea\">' +\r\n                   '<span id=\"##_label\" class=\"edui-label\"></span>' +\r\n                  '</div>' +\r\n                  '<div class=\"edui-pickarea\"' +\r\n                   ' onmousemove=\"$$._onMouseMove(event, this);\"' +\r\n                   ' onmouseover=\"$$._onMouseOver(event, this);\"' +\r\n                   ' onmouseout=\"$$._onMouseOut(event, this);\"' +\r\n                   ' onclick=\"$$._onClick(event, this);\"' +\r\n                  '>' +\r\n                    '<div id=\"##_overlay\" class=\"edui-overlay\"></div>' +\r\n                  '</div>' +\r\n                 '</div>' +\r\n                '</div>';\r\n        },\r\n        _UIBase_render: UIBase.prototype.render,\r\n        render: function (holder){\r\n            this._UIBase_render(holder);\r\n            this.getDom('label').innerHTML = '0'+this.editor.getLang(\"t_row\")+' x 0'+this.editor.getLang(\"t_col\");\r\n        },\r\n        _track: function (numCols, numRows){\r\n            var style = this.getDom('overlay').style;\r\n            var sideLen = this.lengthOfCellSide;\r\n            style.width = numCols * sideLen + 'px';\r\n            style.height = numRows * sideLen + 'px';\r\n            var label = this.getDom('label');\r\n            label.innerHTML = numCols +this.editor.getLang(\"t_col\")+' x ' + numRows + this.editor.getLang(\"t_row\");\r\n            this.numCols = numCols;\r\n            this.numRows = numRows;\r\n        },\r\n        _onMouseOver: function (evt, el){\r\n            var rel = evt.relatedTarget || evt.fromElement;\r\n            if (!uiUtils.contains(el, rel) && el !== rel) {\r\n                this.getDom('label').innerHTML = '0'+this.editor.getLang(\"t_col\")+' x 0'+this.editor.getLang(\"t_row\");\r\n                this.getDom('overlay').style.visibility = '';\r\n            }\r\n        },\r\n        _onMouseOut: function (evt, el){\r\n            var rel = evt.relatedTarget || evt.toElement;\r\n            if (!uiUtils.contains(el, rel) && el !== rel) {\r\n                this.getDom('label').innerHTML = '0'+this.editor.getLang(\"t_col\")+' x 0'+this.editor.getLang(\"t_row\");\r\n                this.getDom('overlay').style.visibility = 'hidden';\r\n            }\r\n        },\r\n        _onMouseMove: function (evt, el){\r\n            var style = this.getDom('overlay').style;\r\n            var offset = uiUtils.getEventOffset(evt);\r\n            var sideLen = this.lengthOfCellSide;\r\n            var numCols = Math.ceil(offset.left / sideLen);\r\n            var numRows = Math.ceil(offset.top / sideLen);\r\n            this._track(numCols, numRows);\r\n        },\r\n        _onClick: function (){\r\n            this.fireEvent('picktable', this.numCols, this.numRows);\r\n        }\r\n    };\r\n    utils.inherits(TablePicker, UIBase);\r\n})();\r\n\r\n\r\n// ui/stateful.js\r\n(function (){\r\n    var browser = baidu.editor.browser,\r\n        domUtils = baidu.editor.dom.domUtils,\r\n        uiUtils = baidu.editor.ui.uiUtils;\r\n    \r\n    var TPL_STATEFUL = 'onmousedown=\"$$.Stateful_onMouseDown(event, this);\"' +\r\n        ' onmouseup=\"$$.Stateful_onMouseUp(event, this);\"' +\r\n        ( browser.ie ? (\r\n        ' onmouseenter=\"$$.Stateful_onMouseEnter(event, this);\"' +\r\n        ' onmouseleave=\"$$.Stateful_onMouseLeave(event, this);\"' )\r\n        : (\r\n        ' onmouseover=\"$$.Stateful_onMouseOver(event, this);\"' +\r\n        ' onmouseout=\"$$.Stateful_onMouseOut(event, this);\"' ));\r\n    \r\n    baidu.editor.ui.Stateful = {\r\n        alwalysHoverable: false,\r\n        target:null,//目标元素和this指向dom不一样\r\n        Stateful_init: function (){\r\n            this._Stateful_dGetHtmlTpl = this.getHtmlTpl;\r\n            this.getHtmlTpl = this.Stateful_getHtmlTpl;\r\n        },\r\n        Stateful_getHtmlTpl: function (){\r\n            var tpl = this._Stateful_dGetHtmlTpl();\r\n            // 使用function避免$转义\r\n            return tpl.replace(/stateful/g, function (){ return TPL_STATEFUL; });\r\n        },\r\n        Stateful_onMouseEnter: function (evt, el){\r\n            this.target=el;\r\n            if (!this.isDisabled() || this.alwalysHoverable) {\r\n                this.addState('hover');\r\n                this.fireEvent('over');\r\n            }\r\n        },\r\n        Stateful_onMouseLeave: function (evt, el){\r\n            if (!this.isDisabled() || this.alwalysHoverable) {\r\n                this.removeState('hover');\r\n                this.removeState('active');\r\n                this.fireEvent('out');\r\n            }\r\n        },\r\n        Stateful_onMouseOver: function (evt, el){\r\n            var rel = evt.relatedTarget;\r\n            if (!uiUtils.contains(el, rel) && el !== rel) {\r\n                this.Stateful_onMouseEnter(evt, el);\r\n            }\r\n        },\r\n        Stateful_onMouseOut: function (evt, el){\r\n            var rel = evt.relatedTarget;\r\n            if (!uiUtils.contains(el, rel) && el !== rel) {\r\n                this.Stateful_onMouseLeave(evt, el);\r\n            }\r\n        },\r\n        Stateful_onMouseDown: function (evt, el){\r\n            if (!this.isDisabled()) {\r\n                this.addState('active');\r\n            }\r\n        },\r\n        Stateful_onMouseUp: function (evt, el){\r\n            if (!this.isDisabled()) {\r\n                this.removeState('active');\r\n            }\r\n        },\r\n        Stateful_postRender: function (){\r\n            if (this.disabled && !this.hasState('disabled')) {\r\n                this.addState('disabled');\r\n            }\r\n        },\r\n        hasState: function (state){\r\n            return domUtils.hasClass(this.getStateDom(), 'edui-state-' + state);\r\n        },\r\n        addState: function (state){\r\n            if (!this.hasState(state)) {\r\n                this.getStateDom().className += ' edui-state-' + state;\r\n            }\r\n        },\r\n        removeState: function (state){\r\n            if (this.hasState(state)) {\r\n                domUtils.removeClasses(this.getStateDom(), ['edui-state-' + state]);\r\n            }\r\n        },\r\n        getStateDom: function (){\r\n            return this.getDom('state');\r\n        },\r\n        isChecked: function (){\r\n            return this.hasState('checked');\r\n        },\r\n        setChecked: function (checked){\r\n            if (!this.isDisabled() && checked) {\r\n                this.addState('checked');\r\n            } else {\r\n                this.removeState('checked');\r\n            }\r\n        },\r\n        isDisabled: function (){\r\n            return this.hasState('disabled');\r\n        },\r\n        setDisabled: function (disabled){\r\n            if (disabled) {\r\n                this.removeState('hover');\r\n                this.removeState('checked');\r\n                this.removeState('active');\r\n                this.addState('disabled');\r\n            } else {\r\n                this.removeState('disabled');\r\n            }\r\n        }\r\n    };\r\n})();\r\n\r\n\r\n// ui/button.js\r\n///import core\r\n///import uicore\r\n///import ui/stateful.js\r\n(function (){\r\n    var utils = baidu.editor.utils,\r\n        UIBase = baidu.editor.ui.UIBase,\r\n        Stateful = baidu.editor.ui.Stateful,\r\n        Button = baidu.editor.ui.Button = function (options){\r\n            if(options.name){\r\n                var btnName = options.name;\r\n                var cssRules = options.cssRules;\r\n                if(!options.className){\r\n                    options.className =  'edui-for-' + btnName;\r\n                }\r\n                options.cssRules = '.edui-default  .edui-for-'+ btnName +' .edui-icon {'+ cssRules +'}'\r\n            }\r\n            this.initOptions(options);\r\n            this.initButton();\r\n        };\r\n    Button.prototype = {\r\n        uiName: 'button',\r\n        label: '',\r\n        title: '',\r\n        showIcon: true,\r\n        showText: true,\r\n        cssRules:'',\r\n        initButton: function (){\r\n            this.initUIBase();\r\n            this.Stateful_init();\r\n            if(this.cssRules){\r\n                utils.cssRule('edui-customize-'+this.name+'-style',this.cssRules);\r\n            }\r\n        },\r\n        getHtmlTpl: function (){\r\n            return '<div id=\"##\" class=\"edui-box %%\">' +\r\n                '<div id=\"##_state\" stateful>' +\r\n                 '<div class=\"%%-wrap\"><div id=\"##_body\" unselectable=\"on\" ' + (this.title ? 'title=\"' + this.title + '\"' : '') +\r\n                 ' class=\"%%-body\" onmousedown=\"return $$._onMouseDown(event, this);\" onclick=\"return $$._onClick(event, this);\">' +\r\n                  (this.showIcon ? '<div class=\"edui-box edui-icon\"></div>' : '') +\r\n                  (this.showText ? '<div class=\"edui-box edui-label\">' + this.label + '</div>' : '') +\r\n                 '</div>' +\r\n                '</div>' +\r\n                '</div></div>';\r\n        },\r\n        postRender: function (){\r\n            this.Stateful_postRender();\r\n            this.setDisabled(this.disabled)\r\n        },\r\n        _onMouseDown: function (e){\r\n            var target = e.target || e.srcElement,\r\n                tagName = target && target.tagName && target.tagName.toLowerCase();\r\n            if (tagName == 'input' || tagName == 'object' || tagName == 'object') {\r\n                return false;\r\n            }\r\n        },\r\n        _onClick: function (){\r\n            if (!this.isDisabled()) {\r\n                this.fireEvent('click');\r\n            }\r\n        },\r\n        setTitle: function(text){\r\n            var label = this.getDom('label');\r\n            label.innerHTML = text;\r\n        }\r\n    };\r\n    utils.inherits(Button, UIBase);\r\n    utils.extend(Button.prototype, Stateful);\r\n\r\n})();\r\n\r\n\r\n// ui/splitbutton.js\r\n///import core\r\n///import uicore\r\n///import ui/stateful.js\r\n(function (){\r\n    var utils = baidu.editor.utils,\r\n        uiUtils = baidu.editor.ui.uiUtils,\r\n        domUtils = baidu.editor.dom.domUtils,\r\n        UIBase = baidu.editor.ui.UIBase,\r\n        Stateful = baidu.editor.ui.Stateful,\r\n        SplitButton = baidu.editor.ui.SplitButton = function (options){\r\n            this.initOptions(options);\r\n            this.initSplitButton();\r\n        };\r\n    SplitButton.prototype = {\r\n        popup: null,\r\n        uiName: 'splitbutton',\r\n        title: '',\r\n        initSplitButton: function (){\r\n            this.initUIBase();\r\n            this.Stateful_init();\r\n            var me = this;\r\n            if (this.popup != null) {\r\n                var popup = this.popup;\r\n                this.popup = null;\r\n                this.setPopup(popup);\r\n            }\r\n        },\r\n        _UIBase_postRender: UIBase.prototype.postRender,\r\n        postRender: function (){\r\n            this.Stateful_postRender();\r\n            this._UIBase_postRender();\r\n        },\r\n        setPopup: function (popup){\r\n            if (this.popup === popup) return;\r\n            if (this.popup != null) {\r\n                this.popup.dispose();\r\n            }\r\n            popup.addListener('show', utils.bind(this._onPopupShow, this));\r\n            popup.addListener('hide', utils.bind(this._onPopupHide, this));\r\n            popup.addListener('postrender', utils.bind(function (){\r\n                popup.getDom('body').appendChild(\r\n                    uiUtils.createElementByHtml('<div id=\"' +\r\n                        this.popup.id + '_bordereraser\" class=\"edui-bordereraser edui-background\" style=\"width:' +\r\n                        (uiUtils.getClientRect(this.getDom()).width + 20) + 'px\"></div>')\r\n                    );\r\n                popup.getDom().className += ' ' + this.className;\r\n            }, this));\r\n            this.popup = popup;\r\n        },\r\n        _onPopupShow: function (){\r\n            this.addState('opened');\r\n        },\r\n        _onPopupHide: function (){\r\n            this.removeState('opened');\r\n        },\r\n        getHtmlTpl: function (){\r\n            return '<div id=\"##\" class=\"edui-box %%\">' +\r\n                '<div '+ (this.title ? 'title=\"' + this.title + '\"' : '') +' id=\"##_state\" stateful><div class=\"%%-body\">' +\r\n                '<div id=\"##_button_body\" class=\"edui-box edui-button-body\" onclick=\"$$._onButtonClick(event, this);\">' +\r\n                '<div class=\"edui-box edui-icon\"></div>' +\r\n                '</div>' +\r\n                '<div class=\"edui-box edui-splitborder\"></div>' +\r\n                '<div class=\"edui-box edui-arrow\" onclick=\"$$._onArrowClick();\"></div>' +\r\n                '</div></div></div>';\r\n        },\r\n        showPopup: function (){\r\n            // 当popup往上弹出的时候，做特殊处理\r\n            var rect = uiUtils.getClientRect(this.getDom());\r\n            rect.top -= this.popup.SHADOW_RADIUS;\r\n            rect.height += this.popup.SHADOW_RADIUS;\r\n            this.popup.showAnchorRect(rect);\r\n        },\r\n        _onArrowClick: function (event, el){\r\n            if (!this.isDisabled()) {\r\n                this.showPopup();\r\n            }\r\n        },\r\n        _onButtonClick: function (){\r\n            if (!this.isDisabled()) {\r\n                this.fireEvent('buttonclick');\r\n            }\r\n        }\r\n    };\r\n    utils.inherits(SplitButton, UIBase);\r\n    utils.extend(SplitButton.prototype, Stateful, true);\r\n\r\n})();\r\n\r\n\r\n// ui/colorbutton.js\r\n///import core\r\n///import uicore\r\n///import ui/colorpicker.js\r\n///import ui/popup.js\r\n///import ui/splitbutton.js\r\n(function (){\r\n    var utils = baidu.editor.utils,\r\n        uiUtils = baidu.editor.ui.uiUtils,\r\n        ColorPicker = baidu.editor.ui.ColorPicker,\r\n        Popup = baidu.editor.ui.Popup,\r\n        SplitButton = baidu.editor.ui.SplitButton,\r\n        ColorButton = baidu.editor.ui.ColorButton = function (options){\r\n            this.initOptions(options);\r\n            this.initColorButton();\r\n        };\r\n    ColorButton.prototype = {\r\n        initColorButton: function (){\r\n            var me = this;\r\n            this.popup = new Popup({\r\n                content: new ColorPicker({\r\n                    noColorText: me.editor.getLang(\"clearColor\"),\r\n                    editor:me.editor,\r\n                    onpickcolor: function (t, color){\r\n                        me._onPickColor(color);\r\n                    },\r\n                    onpicknocolor: function (t, color){\r\n                        me._onPickNoColor(color);\r\n                    }\r\n                }),\r\n                editor:me.editor\r\n            });\r\n            this.initSplitButton();\r\n        },\r\n        _SplitButton_postRender: SplitButton.prototype.postRender,\r\n        postRender: function (){\r\n            this._SplitButton_postRender();\r\n            this.getDom('button_body').appendChild(\r\n                uiUtils.createElementByHtml('<div id=\"' + this.id + '_colorlump\" class=\"edui-colorlump\"></div>')\r\n            );\r\n            this.getDom().className += ' edui-colorbutton';\r\n        },\r\n        setColor: function (color){\r\n            this.getDom('colorlump').style.backgroundColor = color;\r\n            this.color = color;\r\n        },\r\n        _onPickColor: function (color){\r\n            if (this.fireEvent('pickcolor', color) !== false) {\r\n                this.setColor(color);\r\n                this.popup.hide();\r\n            }\r\n        },\r\n        _onPickNoColor: function (color){\r\n            if (this.fireEvent('picknocolor') !== false) {\r\n                this.popup.hide();\r\n            }\r\n        }\r\n    };\r\n    utils.inherits(ColorButton, SplitButton);\r\n\r\n})();\r\n\r\n\r\n// ui/tablebutton.js\r\n///import core\r\n///import uicore\r\n///import ui/popup.js\r\n///import ui/tablepicker.js\r\n///import ui/splitbutton.js\r\n(function (){\r\n    var utils = baidu.editor.utils,\r\n        Popup = baidu.editor.ui.Popup,\r\n        TablePicker = baidu.editor.ui.TablePicker,\r\n        SplitButton = baidu.editor.ui.SplitButton,\r\n        TableButton = baidu.editor.ui.TableButton = function (options){\r\n            this.initOptions(options);\r\n            this.initTableButton();\r\n        };\r\n    TableButton.prototype = {\r\n        initTableButton: function (){\r\n            var me = this;\r\n            this.popup = new Popup({\r\n                content: new TablePicker({\r\n                    editor:me.editor,\r\n                    onpicktable: function (t, numCols, numRows){\r\n                        me._onPickTable(numCols, numRows);\r\n                    }\r\n                }),\r\n                'editor':me.editor\r\n            });\r\n            this.initSplitButton();\r\n        },\r\n        _onPickTable: function (numCols, numRows){\r\n            if (this.fireEvent('picktable', numCols, numRows) !== false) {\r\n                this.popup.hide();\r\n            }\r\n        }\r\n    };\r\n    utils.inherits(TableButton, SplitButton);\r\n\r\n})();\r\n\r\n\r\n// ui/autotypesetpicker.js\r\n///import core\r\n///import uicore\r\n(function () {\r\n    var utils = baidu.editor.utils,\r\n        UIBase = baidu.editor.ui.UIBase;\r\n\r\n    var AutoTypeSetPicker = baidu.editor.ui.AutoTypeSetPicker = function (options) {\r\n        this.initOptions(options);\r\n        this.initAutoTypeSetPicker();\r\n    };\r\n    AutoTypeSetPicker.prototype = {\r\n        initAutoTypeSetPicker:function () {\r\n            this.initUIBase();\r\n        },\r\n        getHtmlTpl:function () {\r\n            var me = this.editor,\r\n                opt = me.options.autotypeset,\r\n                lang = me.getLang(\"autoTypeSet\");\r\n\r\n            var textAlignInputName = 'textAlignValue' + me.uid,\r\n                imageBlockInputName = 'imageBlockLineValue' + me.uid,\r\n                symbolConverInputName = 'symbolConverValue' + me.uid;\r\n\r\n            return '<div id=\"##\" class=\"edui-autotypesetpicker %%\">' +\r\n                '<div class=\"edui-autotypesetpicker-body\">' +\r\n                '<table >' +\r\n                '<tr><td nowrap><input type=\"checkbox\" name=\"mergeEmptyline\" ' + (opt[\"mergeEmptyline\"] ? \"checked\" : \"\" ) + '>' + lang.mergeLine + '</td><td colspan=\"2\"><input type=\"checkbox\" name=\"removeEmptyline\" ' + (opt[\"removeEmptyline\"] ? \"checked\" : \"\" ) + '>' + lang.delLine + '</td></tr>' +\r\n                '<tr><td nowrap><input type=\"checkbox\" name=\"removeClass\" ' + (opt[\"removeClass\"] ? \"checked\" : \"\" ) + '>' + lang.removeFormat + '</td><td colspan=\"2\"><input type=\"checkbox\" name=\"indent\" ' + (opt[\"indent\"] ? \"checked\" : \"\" ) + '>' + lang.indent + '</td></tr>' +\r\n                '<tr>' +\r\n                '<td nowrap><input type=\"checkbox\" name=\"textAlign\" ' + (opt[\"textAlign\"] ? \"checked\" : \"\" ) + '>' + lang.alignment + '</td>' +\r\n                '<td colspan=\"2\" id=\"' + textAlignInputName + '\">' +\r\n                '<input type=\"radio\" name=\"'+ textAlignInputName +'\" value=\"left\" ' + ((opt[\"textAlign\"] && opt[\"textAlign\"] == \"left\") ? \"checked\" : \"\") + '>' + me.getLang(\"justifyleft\") +\r\n                '<input type=\"radio\" name=\"'+ textAlignInputName +'\" value=\"center\" ' + ((opt[\"textAlign\"] && opt[\"textAlign\"] == \"center\") ? \"checked\" : \"\") + '>' + me.getLang(\"justifycenter\") +\r\n                '<input type=\"radio\" name=\"'+ textAlignInputName +'\" value=\"right\" ' + ((opt[\"textAlign\"] && opt[\"textAlign\"] == \"right\") ? \"checked\" : \"\") + '>' + me.getLang(\"justifyright\") +\r\n                '</td>' +\r\n                '</tr>' +\r\n                '<tr>' +\r\n                '<td nowrap><input type=\"checkbox\" name=\"imageBlockLine\" ' + (opt[\"imageBlockLine\"] ? \"checked\" : \"\" ) + '>' + lang.imageFloat + '</td>' +\r\n                '<td nowrap id=\"'+ imageBlockInputName +'\">' +\r\n                '<input type=\"radio\" name=\"'+ imageBlockInputName +'\" value=\"none\" ' + ((opt[\"imageBlockLine\"] && opt[\"imageBlockLine\"] == \"none\") ? \"checked\" : \"\") + '>' + me.getLang(\"default\") +\r\n                '<input type=\"radio\" name=\"'+ imageBlockInputName +'\" value=\"left\" ' + ((opt[\"imageBlockLine\"] && opt[\"imageBlockLine\"] == \"left\") ? \"checked\" : \"\") + '>' + me.getLang(\"justifyleft\") +\r\n                '<input type=\"radio\" name=\"'+ imageBlockInputName +'\" value=\"center\" ' + ((opt[\"imageBlockLine\"] && opt[\"imageBlockLine\"] == \"center\") ? \"checked\" : \"\") + '>' + me.getLang(\"justifycenter\") +\r\n                '<input type=\"radio\" name=\"'+ imageBlockInputName +'\" value=\"right\" ' + ((opt[\"imageBlockLine\"] && opt[\"imageBlockLine\"] == \"right\") ? \"checked\" : \"\") + '>' + me.getLang(\"justifyright\") +\r\n                '</td>' +\r\n                '</tr>' +\r\n                '<tr><td nowrap><input type=\"checkbox\" name=\"clearFontSize\" ' + (opt[\"clearFontSize\"] ? \"checked\" : \"\" ) + '>' + lang.removeFontsize + '</td><td colspan=\"2\"><input type=\"checkbox\" name=\"clearFontFamily\" ' + (opt[\"clearFontFamily\"] ? \"checked\" : \"\" ) + '>' + lang.removeFontFamily + '</td></tr>' +\r\n                '<tr><td nowrap colspan=\"3\"><input type=\"checkbox\" name=\"removeEmptyNode\" ' + (opt[\"removeEmptyNode\"] ? \"checked\" : \"\" ) + '>' + lang.removeHtml + '</td></tr>' +\r\n                '<tr><td nowrap colspan=\"3\"><input type=\"checkbox\" name=\"pasteFilter\" ' + (opt[\"pasteFilter\"] ? \"checked\" : \"\" ) + '>' + lang.pasteFilter + '</td></tr>' +\r\n                '<tr>' +\r\n                '<td nowrap><input type=\"checkbox\" name=\"symbolConver\" ' + (opt[\"bdc2sb\"] || opt[\"tobdc\"] ? \"checked\" : \"\" ) + '>' + lang.symbol + '</td>' +\r\n                '<td id=\"' + symbolConverInputName + '\">' +\r\n                '<input type=\"radio\" name=\"bdc\" value=\"bdc2sb\" ' + (opt[\"bdc2sb\"] ? \"checked\" : \"\" ) + '>' + lang.bdc2sb +\r\n                '<input type=\"radio\" name=\"bdc\" value=\"tobdc\" ' + (opt[\"tobdc\"] ? \"checked\" : \"\" ) + '>' + lang.tobdc + '' +\r\n                '</td>' +\r\n                '<td nowrap align=\"right\"><button >' + lang.run + '</button></td>' +\r\n                '</tr>' +\r\n                '</table>' +\r\n                '</div>' +\r\n                '</div>';\r\n\r\n\r\n        },\r\n        _UIBase_render:UIBase.prototype.render\r\n    };\r\n    utils.inherits(AutoTypeSetPicker, UIBase);\r\n})();\r\n\r\n\r\n// ui/autotypesetbutton.js\r\n///import core\r\n///import uicore\r\n///import ui/popup.js\r\n///import ui/autotypesetpicker.js\r\n///import ui/splitbutton.js\r\n(function (){\r\n    var utils = baidu.editor.utils,\r\n        Popup = baidu.editor.ui.Popup,\r\n        AutoTypeSetPicker = baidu.editor.ui.AutoTypeSetPicker,\r\n        SplitButton = baidu.editor.ui.SplitButton,\r\n        AutoTypeSetButton = baidu.editor.ui.AutoTypeSetButton = function (options){\r\n            this.initOptions(options);\r\n            this.initAutoTypeSetButton();\r\n        };\r\n    function getPara(me){\r\n\r\n        var opt = {},\r\n            cont = me.getDom(),\r\n            editorId = me.editor.uid,\r\n            inputType = null,\r\n            attrName = null,\r\n            ipts = domUtils.getElementsByTagName(cont,\"input\");\r\n        for(var i=ipts.length-1,ipt;ipt=ipts[i--];){\r\n            inputType = ipt.getAttribute(\"type\");\r\n            if(inputType==\"checkbox\"){\r\n                attrName = ipt.getAttribute(\"name\");\r\n                opt[attrName] && delete opt[attrName];\r\n                if(ipt.checked){\r\n                    var attrValue = document.getElementById( attrName + \"Value\" + editorId );\r\n                    if(attrValue){\r\n                        if(/input/ig.test(attrValue.tagName)){\r\n                            opt[attrName] = attrValue.value;\r\n                        } else {\r\n                            var iptChilds = attrValue.getElementsByTagName(\"input\");\r\n                            for(var j=iptChilds.length-1,iptchild;iptchild=iptChilds[j--];){\r\n                                if(iptchild.checked){\r\n                                    opt[attrName] = iptchild.value;\r\n                                    break;\r\n                                }\r\n                            }\r\n                        }\r\n                    } else {\r\n                        opt[attrName] = true;\r\n                    }\r\n                } else {\r\n                    opt[attrName] = false;\r\n                }\r\n            } else {\r\n                opt[ipt.getAttribute(\"value\")] = ipt.checked;\r\n            }\r\n\r\n        }\r\n\r\n        var selects = domUtils.getElementsByTagName(cont,\"select\");\r\n        for(var i=0,si;si=selects[i++];){\r\n            var attr = si.getAttribute('name');\r\n            opt[attr] = opt[attr] ? si.value : '';\r\n        }\r\n\r\n        utils.extend(me.editor.options.autotypeset,opt);\r\n\r\n        me.editor.setPreferences('autotypeset', opt);\r\n    }\r\n\r\n    AutoTypeSetButton.prototype = {\r\n        initAutoTypeSetButton: function (){\r\n\r\n            var me = this;\r\n            this.popup = new Popup({\r\n                //传入配置参数\r\n                content: new AutoTypeSetPicker({editor:me.editor}),\r\n                'editor':me.editor,\r\n                hide : function(){\r\n                    if (!this._hidden && this.getDom()) {\r\n                        getPara(this);\r\n                        this.getDom().style.display = 'none';\r\n                        this._hidden = true;\r\n                        this.fireEvent('hide');\r\n                    }\r\n                }\r\n            });\r\n            var flag = 0;\r\n            this.popup.addListener('postRenderAfter',function(){\r\n                var popupUI = this;\r\n                if(flag)return;\r\n                var cont = this.getDom(),\r\n                    btn = cont.getElementsByTagName('button')[0];\r\n\r\n                btn.onclick = function(){\r\n                    getPara(popupUI);\r\n                    me.editor.execCommand('autotypeset');\r\n                    popupUI.hide()\r\n                };\r\n\r\n                domUtils.on(cont, 'click', function(e) {\r\n                    var target = e.target || e.srcElement,\r\n                        editorId = me.editor.uid;\r\n                    if (target && target.tagName == 'INPUT') {\r\n\r\n                        // 点击图片浮动的checkbox,去除对应的radio\r\n                        if (target.name == 'imageBlockLine' || target.name == 'textAlign' || target.name == 'symbolConver') {\r\n                            var checked = target.checked,\r\n                                radioTd = document.getElementById( target.name + 'Value' + editorId),\r\n                                radios = radioTd.getElementsByTagName('input'),\r\n                                defalutSelect = {\r\n                                    'imageBlockLine': 'none',\r\n                                    'textAlign': 'left',\r\n                                    'symbolConver': 'tobdc'\r\n                                };\r\n\r\n                            for (var i = 0; i < radios.length; i++) {\r\n                                if (checked) {\r\n                                    if (radios[i].value == defalutSelect[target.name]) {\r\n                                        radios[i].checked = 'checked';\r\n                                    }\r\n                                } else {\r\n                                    radios[i].checked = false;\r\n                                }\r\n                            }\r\n                        }\r\n                        // 点击radio,选中对应的checkbox\r\n                        if (target.name == ('imageBlockLineValue' + editorId) || target.name == ('textAlignValue' + editorId) || target.name == 'bdc') {\r\n                            var checkboxs = target.parentNode.previousSibling.getElementsByTagName('input');\r\n                            checkboxs && (checkboxs[0].checked = true);\r\n                        }\r\n\r\n                        getPara(popupUI);\r\n                    }\r\n                });\r\n\r\n                flag = 1;\r\n            });\r\n            this.initSplitButton();\r\n        }\r\n    };\r\n    utils.inherits(AutoTypeSetButton, SplitButton);\r\n\r\n})();\r\n\r\n\r\n// ui/cellalignpicker.js\r\n///import core\r\n///import uicore\r\n(function () {\r\n    var utils = baidu.editor.utils,\r\n        Popup = baidu.editor.ui.Popup,\r\n        Stateful = baidu.editor.ui.Stateful,\r\n        UIBase = baidu.editor.ui.UIBase;\r\n\r\n    /**\r\n     * 该参数将新增一个参数： selected， 参数类型为一个Object， 形如{ 'align': 'center', 'valign': 'top' }， 表示单元格的初始\r\n     * 对齐状态为： 竖直居上，水平居中; 其中 align的取值为：'center', 'left', 'right'; valign的取值为: 'top', 'middle', 'bottom'\r\n     * @update 2013/4/2 hancong03@baidu.com\r\n     */\r\n    var CellAlignPicker = baidu.editor.ui.CellAlignPicker = function (options) {\r\n        this.initOptions(options);\r\n        this.initSelected();\r\n        this.initCellAlignPicker();\r\n    };\r\n    CellAlignPicker.prototype = {\r\n        //初始化选中状态， 该方法将根据传递进来的参数获取到应该选中的对齐方式图标的索引\r\n        initSelected: function(){\r\n\r\n            var status = {\r\n\r\n                valign: {\r\n                    top: 0,\r\n                    middle: 1,\r\n                    bottom: 2\r\n                },\r\n                align: {\r\n                    left: 0,\r\n                    center: 1,\r\n                    right: 2\r\n                },\r\n                count: 3\r\n\r\n                },\r\n                result = -1;\r\n\r\n            if( this.selected ) {\r\n                this.selectedIndex = status.valign[ this.selected.valign ] * status.count + status.align[ this.selected.align ];\r\n            }\r\n\r\n        },\r\n        initCellAlignPicker:function () {\r\n            this.initUIBase();\r\n            this.Stateful_init();\r\n        },\r\n        getHtmlTpl:function () {\r\n\r\n            var alignType = [ 'left', 'center', 'right' ],\r\n                COUNT = 9,\r\n                tempClassName = null,\r\n                tempIndex = -1,\r\n                tmpl = [];\r\n\r\n\r\n            for( var i= 0; i<COUNT; i++ ) {\r\n\r\n                tempClassName = this.selectedIndex === i ? ' class=\"edui-cellalign-selected\" ' : '';\r\n                tempIndex = i % 3;\r\n\r\n                tempIndex === 0 && tmpl.push('<tr>');\r\n\r\n                tmpl.push( '<td index=\"'+ i +'\" ' + tempClassName + ' stateful><div class=\"edui-icon edui-'+ alignType[ tempIndex ] +'\"></div></td>' );\r\n\r\n                tempIndex === 2 && tmpl.push('</tr>');\r\n\r\n            }\r\n\r\n            return '<div id=\"##\" class=\"edui-cellalignpicker %%\">' +\r\n                '<div class=\"edui-cellalignpicker-body\">' +\r\n                '<table onclick=\"$$._onClick(event);\">' +\r\n                tmpl.join('') +\r\n                '</table>' +\r\n                '</div>' +\r\n                '</div>';\r\n        },\r\n        getStateDom: function (){\r\n            return this.target;\r\n        },\r\n        _onClick: function (evt){\r\n            var target= evt.target || evt.srcElement;\r\n            if(/icon/.test(target.className)){\r\n                this.items[target.parentNode.getAttribute(\"index\")].onclick();\r\n                Popup.postHide(evt);\r\n            }\r\n        },\r\n        _UIBase_render:UIBase.prototype.render\r\n    };\r\n    utils.inherits(CellAlignPicker, UIBase);\r\n    utils.extend(CellAlignPicker.prototype, Stateful,true);\r\n})();\r\n\r\n\r\n\r\n\r\n\r\n// ui/pastepicker.js\r\n///import core\r\n///import uicore\r\n(function () {\r\n    var utils = baidu.editor.utils,\r\n        Stateful = baidu.editor.ui.Stateful,\r\n        uiUtils = baidu.editor.ui.uiUtils,\r\n        UIBase = baidu.editor.ui.UIBase;\r\n\r\n    var PastePicker = baidu.editor.ui.PastePicker = function (options) {\r\n        this.initOptions(options);\r\n        this.initPastePicker();\r\n    };\r\n    PastePicker.prototype = {\r\n        initPastePicker:function () {\r\n            this.initUIBase();\r\n            this.Stateful_init();\r\n        },\r\n        getHtmlTpl:function () {\r\n            return '<div class=\"edui-pasteicon\" onclick=\"$$._onClick(this)\"></div>' +\r\n                '<div class=\"edui-pastecontainer\">' +\r\n                '<div class=\"edui-title\">' + this.editor.getLang(\"pasteOpt\") + '</div>' +\r\n                '<div class=\"edui-button\">' +\r\n                '<div title=\"' + this.editor.getLang(\"pasteSourceFormat\") + '\" onclick=\"$$.format(false)\" stateful>' +\r\n                '<div class=\"edui-richtxticon\"></div></div>' +\r\n                '<div title=\"' + this.editor.getLang(\"tagFormat\") + '\" onclick=\"$$.format(2)\" stateful>' +\r\n                '<div class=\"edui-tagicon\"></div></div>' +\r\n                '<div title=\"' + this.editor.getLang(\"pasteTextFormat\") + '\" onclick=\"$$.format(true)\" stateful>' +\r\n                '<div class=\"edui-plaintxticon\"></div></div>' +\r\n                '</div>' +\r\n                '</div>' +\r\n                '</div>'\r\n        },\r\n        getStateDom:function () {\r\n            return this.target;\r\n        },\r\n        format:function (param) {\r\n            this.editor.ui._isTransfer = true;\r\n            this.editor.fireEvent('pasteTransfer', param);\r\n        },\r\n        _onClick:function (cur) {\r\n            var node = domUtils.getNextDomNode(cur),\r\n                screenHt = uiUtils.getViewportRect().height,\r\n                subPop = uiUtils.getClientRect(node);\r\n\r\n            if ((subPop.top + subPop.height) > screenHt)\r\n                node.style.top = (-subPop.height - cur.offsetHeight) + \"px\";\r\n            else\r\n                node.style.top = \"\";\r\n\r\n            if (/hidden/ig.test(domUtils.getComputedStyle(node, \"visibility\"))) {\r\n                node.style.visibility = \"visible\";\r\n                domUtils.addClass(cur, \"edui-state-opened\");\r\n            } else {\r\n                node.style.visibility = \"hidden\";\r\n                domUtils.removeClasses(cur, \"edui-state-opened\")\r\n            }\r\n        },\r\n        _UIBase_render:UIBase.prototype.render\r\n    };\r\n    utils.inherits(PastePicker, UIBase);\r\n    utils.extend(PastePicker.prototype, Stateful, true);\r\n})();\r\n\r\n\r\n\r\n\r\n\r\n\r\n// ui/toolbar.js\r\n(function (){\r\n    var utils = baidu.editor.utils,\r\n        uiUtils = baidu.editor.ui.uiUtils,\r\n        UIBase = baidu.editor.ui.UIBase,\r\n        Toolbar = baidu.editor.ui.Toolbar = function (options){\r\n            this.initOptions(options);\r\n            this.initToolbar();\r\n        };\r\n    Toolbar.prototype = {\r\n        items: null,\r\n        initToolbar: function (){\r\n            this.items = this.items || [];\r\n            this.initUIBase();\r\n        },\r\n        add: function (item,index){\r\n            if(index === undefined){\r\n                this.items.push(item);\r\n            }else{\r\n                this.items.splice(index,0,item)\r\n            }\r\n\r\n        },\r\n        getHtmlTpl: function (){\r\n            var buff = [];\r\n            for (var i=0; i<this.items.length; i++) {\r\n                buff[i] = this.items[i].renderHtml();\r\n            }\r\n            return '<div id=\"##\" class=\"edui-toolbar %%\" onselectstart=\"return false;\" onmousedown=\"return $$._onMouseDown(event, this);\">' +\r\n                buff.join('') +\r\n                '</div>'\r\n        },\r\n        postRender: function (){\r\n            var box = this.getDom();\r\n            for (var i=0; i<this.items.length; i++) {\r\n                this.items[i].postRender();\r\n            }\r\n            uiUtils.makeUnselectable(box);\r\n        },\r\n        _onMouseDown: function (e){\r\n            var target = e.target || e.srcElement,\r\n                tagName = target && target.tagName && target.tagName.toLowerCase();\r\n            if (tagName == 'input' || tagName == 'object' || tagName == 'object') {\r\n                return false;\r\n            }\r\n        }\r\n    };\r\n    utils.inherits(Toolbar, UIBase);\r\n\r\n})();\r\n\r\n\r\n// ui/menu.js\r\n///import core\r\n///import uicore\r\n///import ui\\popup.js\r\n///import ui\\stateful.js\r\n(function () {\r\n    var utils = baidu.editor.utils,\r\n        domUtils = baidu.editor.dom.domUtils,\r\n        uiUtils = baidu.editor.ui.uiUtils,\r\n        UIBase = baidu.editor.ui.UIBase,\r\n        Popup = baidu.editor.ui.Popup,\r\n        Stateful = baidu.editor.ui.Stateful,\r\n        CellAlignPicker = baidu.editor.ui.CellAlignPicker,\r\n\r\n        Menu = baidu.editor.ui.Menu = function (options) {\r\n            this.initOptions(options);\r\n            this.initMenu();\r\n        };\r\n\r\n    var menuSeparator = {\r\n        renderHtml:function () {\r\n            return '<div class=\"edui-menuitem edui-menuseparator\"><div class=\"edui-menuseparator-inner\"></div></div>';\r\n        },\r\n        postRender:function () {\r\n        },\r\n        queryAutoHide:function () {\r\n            return true;\r\n        }\r\n    };\r\n    Menu.prototype = {\r\n        items:null,\r\n        uiName:'menu',\r\n        initMenu:function () {\r\n            this.items = this.items || [];\r\n            this.initPopup();\r\n            this.initItems();\r\n        },\r\n        initItems:function () {\r\n            for (var i = 0; i < this.items.length; i++) {\r\n                var item = this.items[i];\r\n                if (item == '-') {\r\n                    this.items[i] = this.getSeparator();\r\n                } else if (!(item instanceof MenuItem)) {\r\n                    item.editor = this.editor;\r\n                    item.theme = this.editor.options.theme;\r\n                    this.items[i] = this.createItem(item);\r\n                }\r\n            }\r\n        },\r\n        getSeparator:function () {\r\n            return menuSeparator;\r\n        },\r\n        createItem:function (item) {\r\n            //新增一个参数menu, 该参数存储了menuItem所对应的menu引用\r\n            item.menu = this;\r\n            return new MenuItem(item);\r\n        },\r\n        _Popup_getContentHtmlTpl:Popup.prototype.getContentHtmlTpl,\r\n        getContentHtmlTpl:function () {\r\n            if (this.items.length == 0) {\r\n                return this._Popup_getContentHtmlTpl();\r\n            }\r\n            var buff = [];\r\n            for (var i = 0; i < this.items.length; i++) {\r\n                var item = this.items[i];\r\n                buff[i] = item.renderHtml();\r\n            }\r\n            return ('<div class=\"%%-body\">' + buff.join('') + '</div>');\r\n        },\r\n        _Popup_postRender:Popup.prototype.postRender,\r\n        postRender:function () {\r\n            var me = this;\r\n            for (var i = 0; i < this.items.length; i++) {\r\n                var item = this.items[i];\r\n                item.ownerMenu = this;\r\n                item.postRender();\r\n            }\r\n            domUtils.on(this.getDom(), 'mouseover', function (evt) {\r\n                evt = evt || event;\r\n                var rel = evt.relatedTarget || evt.fromElement;\r\n                var el = me.getDom();\r\n                if (!uiUtils.contains(el, rel) && el !== rel) {\r\n                    me.fireEvent('over');\r\n                }\r\n            });\r\n            this._Popup_postRender();\r\n        },\r\n        queryAutoHide:function (el) {\r\n            if (el) {\r\n                if (uiUtils.contains(this.getDom(), el)) {\r\n                    return false;\r\n                }\r\n                for (var i = 0; i < this.items.length; i++) {\r\n                    var item = this.items[i];\r\n                    if (item.queryAutoHide(el) === false) {\r\n                        return false;\r\n                    }\r\n                }\r\n            }\r\n        },\r\n        clearItems:function () {\r\n            for (var i = 0; i < this.items.length; i++) {\r\n                var item = this.items[i];\r\n                clearTimeout(item._showingTimer);\r\n                clearTimeout(item._closingTimer);\r\n                if (item.subMenu) {\r\n                    item.subMenu.destroy();\r\n                }\r\n            }\r\n            this.items = [];\r\n        },\r\n        destroy:function () {\r\n            if (this.getDom()) {\r\n                domUtils.remove(this.getDom());\r\n            }\r\n            this.clearItems();\r\n        },\r\n        dispose:function () {\r\n            this.destroy();\r\n        }\r\n    };\r\n    utils.inherits(Menu, Popup);\r\n\r\n    /**\r\n     * @update 2013/04/03 hancong03 新增一个参数menu, 该参数存储了menuItem所对应的menu引用\r\n     * @type {Function}\r\n     */\r\n    var MenuItem = baidu.editor.ui.MenuItem = function (options) {\r\n        this.initOptions(options);\r\n        this.initUIBase();\r\n        this.Stateful_init();\r\n        if (this.subMenu && !(this.subMenu instanceof Menu)) {\r\n            if (options.className && options.className.indexOf(\"aligntd\") != -1) {\r\n                var me = this;\r\n\r\n                //获取单元格对齐初始状态\r\n                this.subMenu.selected = this.editor.queryCommandValue( 'cellalignment' );\r\n\r\n                this.subMenu = new Popup({\r\n                    content:new CellAlignPicker(this.subMenu),\r\n                    parentMenu:me,\r\n                    editor:me.editor,\r\n                    destroy:function () {\r\n                        if (this.getDom()) {\r\n                            domUtils.remove(this.getDom());\r\n                        }\r\n                    }\r\n                });\r\n                this.subMenu.addListener(\"postRenderAfter\", function () {\r\n                    domUtils.on(this.getDom(), \"mouseover\", function () {\r\n                        me.addState('opened');\r\n                    });\r\n                });\r\n            } else {\r\n                this.subMenu = new Menu(this.subMenu);\r\n            }\r\n        }\r\n    };\r\n    MenuItem.prototype = {\r\n        label:'',\r\n        subMenu:null,\r\n        ownerMenu:null,\r\n        uiName:'menuitem',\r\n        alwalysHoverable:true,\r\n        getHtmlTpl:function () {\r\n            return '<div id=\"##\" class=\"%%\" stateful onclick=\"$$._onClick(event, this);\">' +\r\n                '<div class=\"%%-body\">' +\r\n                this.renderLabelHtml() +\r\n                '</div>' +\r\n                '</div>';\r\n        },\r\n        postRender:function () {\r\n            var me = this;\r\n            this.addListener('over', function () {\r\n                me.ownerMenu.fireEvent('submenuover', me);\r\n                if (me.subMenu) {\r\n                    me.delayShowSubMenu();\r\n                }\r\n            });\r\n            if (this.subMenu) {\r\n                this.getDom().className += ' edui-hassubmenu';\r\n                this.subMenu.render();\r\n                this.addListener('out', function () {\r\n                    me.delayHideSubMenu();\r\n                });\r\n                this.subMenu.addListener('over', function () {\r\n                    clearTimeout(me._closingTimer);\r\n                    me._closingTimer = null;\r\n                    me.addState('opened');\r\n                });\r\n                this.ownerMenu.addListener('hide', function () {\r\n                    me.hideSubMenu();\r\n                });\r\n                this.ownerMenu.addListener('submenuover', function (t, subMenu) {\r\n                    if (subMenu !== me) {\r\n                        me.delayHideSubMenu();\r\n                    }\r\n                });\r\n                this.subMenu._bakQueryAutoHide = this.subMenu.queryAutoHide;\r\n                this.subMenu.queryAutoHide = function (el) {\r\n                    if (el && uiUtils.contains(me.getDom(), el)) {\r\n                        return false;\r\n                    }\r\n                    return this._bakQueryAutoHide(el);\r\n                };\r\n            }\r\n            this.getDom().style.tabIndex = '-1';\r\n            uiUtils.makeUnselectable(this.getDom());\r\n            this.Stateful_postRender();\r\n        },\r\n        delayShowSubMenu:function () {\r\n            var me = this;\r\n            if (!me.isDisabled()) {\r\n                me.addState('opened');\r\n                clearTimeout(me._showingTimer);\r\n                clearTimeout(me._closingTimer);\r\n                me._closingTimer = null;\r\n                me._showingTimer = setTimeout(function () {\r\n                    me.showSubMenu();\r\n                }, 250);\r\n            }\r\n        },\r\n        delayHideSubMenu:function () {\r\n            var me = this;\r\n            if (!me.isDisabled()) {\r\n                me.removeState('opened');\r\n                clearTimeout(me._showingTimer);\r\n                if (!me._closingTimer) {\r\n                    me._closingTimer = setTimeout(function () {\r\n                        if (!me.hasState('opened')) {\r\n                            me.hideSubMenu();\r\n                        }\r\n                        me._closingTimer = null;\r\n                    }, 400);\r\n                }\r\n            }\r\n        },\r\n        renderLabelHtml:function () {\r\n            return '<div class=\"edui-arrow\"></div>' +\r\n                '<div class=\"edui-box edui-icon\"></div>' +\r\n                '<div class=\"edui-box edui-label %%-label\">' + (this.label || '') + '</div>';\r\n        },\r\n        getStateDom:function () {\r\n            return this.getDom();\r\n        },\r\n        queryAutoHide:function (el) {\r\n            if (this.subMenu && this.hasState('opened')) {\r\n                return this.subMenu.queryAutoHide(el);\r\n            }\r\n        },\r\n        _onClick:function (event, this_) {\r\n            if (this.hasState('disabled')) return;\r\n            if (this.fireEvent('click', event, this_) !== false) {\r\n                if (this.subMenu) {\r\n                    this.showSubMenu();\r\n                } else {\r\n                    Popup.postHide(event);\r\n                }\r\n            }\r\n        },\r\n        showSubMenu:function () {\r\n            var rect = uiUtils.getClientRect(this.getDom());\r\n            rect.right -= 5;\r\n            rect.left += 2;\r\n            rect.width -= 7;\r\n            rect.top -= 4;\r\n            rect.bottom += 4;\r\n            rect.height += 8;\r\n            this.subMenu.showAnchorRect(rect, true, true);\r\n        },\r\n        hideSubMenu:function () {\r\n            this.subMenu.hide();\r\n        }\r\n    };\r\n    utils.inherits(MenuItem, UIBase);\r\n    utils.extend(MenuItem.prototype, Stateful, true);\r\n})();\r\n\r\n\r\n// ui/combox.js\r\n///import core\r\n///import uicore\r\n///import ui/menu.js\r\n///import ui/splitbutton.js\r\n(function (){\r\n    // todo: menu和item提成通用list\r\n    var utils = baidu.editor.utils,\r\n        uiUtils = baidu.editor.ui.uiUtils,\r\n        Menu = baidu.editor.ui.Menu,\r\n        SplitButton = baidu.editor.ui.SplitButton,\r\n        Combox = baidu.editor.ui.Combox = function (options){\r\n            this.initOptions(options);\r\n            this.initCombox();\r\n        };\r\n    Combox.prototype = {\r\n        uiName: 'combox',\r\n        onbuttonclick:function () {\r\n            this.showPopup();\r\n        },\r\n        initCombox: function (){\r\n            var me = this;\r\n            this.items = this.items || [];\r\n            for (var i=0; i<this.items.length; i++) {\r\n                var item = this.items[i];\r\n                item.uiName = 'listitem';\r\n                item.index = i;\r\n                item.onclick = function (){\r\n                    me.selectByIndex(this.index);\r\n                };\r\n            }\r\n            this.popup = new Menu({\r\n                items: this.items,\r\n                uiName: 'list',\r\n                editor:this.editor,\r\n                captureWheel: true,\r\n                combox: this\r\n            });\r\n\r\n            this.initSplitButton();\r\n        },\r\n        _SplitButton_postRender: SplitButton.prototype.postRender,\r\n        postRender: function (){\r\n            this._SplitButton_postRender();\r\n            this.setLabel(this.label || '');\r\n            this.setValue(this.initValue || '');\r\n        },\r\n        showPopup: function (){\r\n            var rect = uiUtils.getClientRect(this.getDom());\r\n            rect.top += 1;\r\n            rect.bottom -= 1;\r\n            rect.height -= 2;\r\n            this.popup.showAnchorRect(rect);\r\n        },\r\n        getValue: function (){\r\n            return this.value;\r\n        },\r\n        setValue: function (value){\r\n            var index = this.indexByValue(value);\r\n            if (index != -1) {\r\n                this.selectedIndex = index;\r\n                this.setLabel(this.items[index].label);\r\n                this.value = this.items[index].value;\r\n            } else {\r\n                this.selectedIndex = -1;\r\n                this.setLabel(this.getLabelForUnknowValue(value));\r\n                this.value = value;\r\n            }\r\n        },\r\n        setLabel: function (label){\r\n            this.getDom('button_body').innerHTML = label;\r\n            this.label = label;\r\n        },\r\n        getLabelForUnknowValue: function (value){\r\n            return value;\r\n        },\r\n        indexByValue: function (value){\r\n            for (var i=0; i<this.items.length; i++) {\r\n                if (value == this.items[i].value) {\r\n                    return i;\r\n                }\r\n            }\r\n            return -1;\r\n        },\r\n        getItem: function (index){\r\n            return this.items[index];\r\n        },\r\n        selectByIndex: function (index){\r\n            if (index < this.items.length && this.fireEvent('select', index) !== false) {\r\n                this.selectedIndex = index;\r\n                this.value = this.items[index].value;\r\n                this.setLabel(this.items[index].label);\r\n            }\r\n        }\r\n    };\r\n    utils.inherits(Combox, SplitButton);\r\n})();\r\n\r\n\r\n// ui/dialog.js\r\n///import core\r\n///import uicore\r\n///import ui/mask.js\r\n///import ui/button.js\r\n(function (){\r\n    var utils = baidu.editor.utils,\r\n        domUtils = baidu.editor.dom.domUtils,\r\n        uiUtils = baidu.editor.ui.uiUtils,\r\n        Mask = baidu.editor.ui.Mask,\r\n        UIBase = baidu.editor.ui.UIBase,\r\n        Button = baidu.editor.ui.Button,\r\n        Dialog = baidu.editor.ui.Dialog = function (options){\r\n            if(options.name){\r\n                var name = options.name;\r\n                var cssRules = options.cssRules;\r\n                if(!options.className){\r\n                    options.className =  'edui-for-' + name;\r\n                }\r\n                if(cssRules){\r\n                    options.cssRules = '.edui-default .edui-for-'+ name +' .edui-dialog-content  {'+ cssRules +'}'\r\n                }\r\n            }\r\n            this.initOptions(utils.extend({\r\n                autoReset: true,\r\n                draggable: true,\r\n                onok: function (){},\r\n                oncancel: function (){},\r\n                onclose: function (t, ok){\r\n                    return ok ? this.onok() : this.oncancel();\r\n                },\r\n                //是否控制dialog中的scroll事件， 默认为不阻止\r\n                holdScroll: false\r\n            },options));\r\n            this.initDialog();\r\n        };\r\n    var modalMask;\r\n    var dragMask;\r\n    var activeDialog;\r\n    Dialog.prototype = {\r\n        draggable: false,\r\n        uiName: 'dialog',\r\n        initDialog: function (){\r\n            var me = this,\r\n                theme=this.editor.options.theme;\r\n            if(this.cssRules){\r\n                utils.cssRule('edui-customize-'+this.name+'-style',this.cssRules);\r\n            }\r\n            this.initUIBase();\r\n            this.modalMask = (modalMask || (modalMask = new Mask({\r\n                className: 'edui-dialog-modalmask',\r\n                theme:theme,\r\n                onclick: function (){\r\n                    activeDialog && activeDialog.close(false);\r\n                }\r\n            })));\r\n            this.dragMask = (dragMask || (dragMask = new Mask({\r\n                className: 'edui-dialog-dragmask',\r\n                theme:theme\r\n            })));\r\n            this.closeButton = new Button({\r\n                className: 'edui-dialog-closebutton',\r\n                title: me.closeDialog,\r\n                theme:theme,\r\n                onclick: function (){\r\n                    me.close(false);\r\n                }\r\n            });\r\n\r\n            this.fullscreen && this.initResizeEvent();\r\n\r\n            if (this.buttons) {\r\n                for (var i=0; i<this.buttons.length; i++) {\r\n                    if (!(this.buttons[i] instanceof Button)) {\r\n                        this.buttons[i] = new Button(utils.extend(this.buttons[i],{\r\n                            editor : this.editor\r\n                        },true));\r\n                    }\r\n                }\r\n            }\r\n        },\r\n        initResizeEvent: function () {\r\n\r\n            var me = this;\r\n\r\n            domUtils.on( window, \"resize\", function () {\r\n\r\n                if ( me._hidden || me._hidden === undefined ) {\r\n                    return;\r\n                }\r\n\r\n                if ( me.__resizeTimer ) {\r\n                    window.clearTimeout( me.__resizeTimer );\r\n                }\r\n\r\n                me.__resizeTimer = window.setTimeout( function () {\r\n\r\n                    me.__resizeTimer = null;\r\n\r\n                    var dialogWrapNode = me.getDom(),\r\n                        contentNode = me.getDom('content'),\r\n                        wrapRect = UE.ui.uiUtils.getClientRect( dialogWrapNode ),\r\n                        contentRect = UE.ui.uiUtils.getClientRect( contentNode ),\r\n                        vpRect = uiUtils.getViewportRect();\r\n\r\n                    contentNode.style.width = ( vpRect.width - wrapRect.width + contentRect.width ) + \"px\";\r\n                    contentNode.style.height = ( vpRect.height - wrapRect.height + contentRect.height ) + \"px\";\r\n\r\n                    dialogWrapNode.style.width = vpRect.width + \"px\";\r\n                    dialogWrapNode.style.height = vpRect.height + \"px\";\r\n\r\n                    me.fireEvent( \"resize\" );\r\n\r\n                }, 100 );\r\n\r\n            } );\r\n\r\n        },\r\n        fitSize: function (){\r\n            var popBodyEl = this.getDom('body');\r\n//            if (!(baidu.editor.browser.ie && baidu.editor.browser.version == 7)) {\r\n//                uiUtils.removeStyle(popBodyEl, 'width');\r\n//                uiUtils.removeStyle(popBodyEl, 'height');\r\n//            }\r\n            var size = this.mesureSize();\r\n            popBodyEl.style.width = size.width + 'px';\r\n            popBodyEl.style.height = size.height + 'px';\r\n            return size;\r\n        },\r\n        safeSetOffset: function (offset){\r\n            var me = this;\r\n            var el = me.getDom();\r\n            var vpRect = uiUtils.getViewportRect();\r\n            var rect = uiUtils.getClientRect(el);\r\n            var left = offset.left;\r\n            if (left + rect.width > vpRect.right) {\r\n                left = vpRect.right - rect.width;\r\n            }\r\n            var top = offset.top;\r\n            if (top + rect.height > vpRect.bottom) {\r\n                top = vpRect.bottom - rect.height;\r\n            }\r\n            el.style.left = Math.max(left, 0) + 'px';\r\n            el.style.top = Math.max(top, 0) + 'px';\r\n        },\r\n        showAtCenter: function (){\r\n\r\n            var vpRect = uiUtils.getViewportRect();\r\n\r\n            if ( !this.fullscreen ) {\r\n                this.getDom().style.display = '';\r\n                var popSize = this.fitSize();\r\n                var titleHeight = this.getDom('titlebar').offsetHeight | 0;\r\n                var left = vpRect.width / 2 - popSize.width / 2;\r\n                var top = vpRect.height / 2 - (popSize.height - titleHeight) / 2 - titleHeight;\r\n                var popEl = this.getDom();\r\n                this.safeSetOffset({\r\n                    left: Math.max(left | 0, 0),\r\n                    top: Math.max(top | 0, 0)\r\n                });\r\n                if (!domUtils.hasClass(popEl, 'edui-state-centered')) {\r\n                    popEl.className += ' edui-state-centered';\r\n                }\r\n            } else {\r\n                var dialogWrapNode = this.getDom(),\r\n                    contentNode = this.getDom('content');\r\n\r\n                dialogWrapNode.style.display = \"block\";\r\n\r\n                var wrapRect = UE.ui.uiUtils.getClientRect( dialogWrapNode ),\r\n                    contentRect = UE.ui.uiUtils.getClientRect( contentNode );\r\n                dialogWrapNode.style.left = \"-100000px\";\r\n\r\n                contentNode.style.width = ( vpRect.width - wrapRect.width + contentRect.width ) + \"px\";\r\n                contentNode.style.height = ( vpRect.height - wrapRect.height + contentRect.height ) + \"px\";\r\n\r\n                dialogWrapNode.style.width = vpRect.width + \"px\";\r\n                dialogWrapNode.style.height = vpRect.height + \"px\";\r\n                dialogWrapNode.style.left = 0;\r\n\r\n                //保存环境的overflow值\r\n                this._originalContext = {\r\n                    html: {\r\n                        overflowX: document.documentElement.style.overflowX,\r\n                        overflowY: document.documentElement.style.overflowY\r\n                    },\r\n                    body: {\r\n                        overflowX: document.body.style.overflowX,\r\n                        overflowY: document.body.style.overflowY\r\n                    }\r\n                };\r\n\r\n                document.documentElement.style.overflowX = 'hidden';\r\n                document.documentElement.style.overflowY = 'hidden';\r\n                document.body.style.overflowX = 'hidden';\r\n                document.body.style.overflowY = 'hidden';\r\n\r\n            }\r\n\r\n            this._show();\r\n        },\r\n        getContentHtml: function (){\r\n            var contentHtml = '';\r\n            if (typeof this.content == 'string') {\r\n                contentHtml = this.content;\r\n            } else if (this.iframeUrl) {\r\n                contentHtml = '<span id=\"'+ this.id +'_contmask\" class=\"dialogcontmask\"></span><iframe id=\"'+ this.id +\r\n                    '_iframe\" class=\"%%-iframe\" height=\"100%\" width=\"100%\" frameborder=\"0\" src=\"'+ this.iframeUrl +'\"></iframe>';\r\n            }\r\n            return contentHtml;\r\n        },\r\n        getHtmlTpl: function (){\r\n            var footHtml = '';\r\n\r\n            if (this.buttons) {\r\n                var buff = [];\r\n                for (var i=0; i<this.buttons.length; i++) {\r\n                    buff[i] = this.buttons[i].renderHtml();\r\n                }\r\n                footHtml = '<div class=\"%%-foot\">' +\r\n                     '<div id=\"##_buttons\" class=\"%%-buttons\">' + buff.join('') + '</div>' +\r\n                    '</div>';\r\n            }\r\n\r\n            return '<div id=\"##\" class=\"%%\"><div '+ ( !this.fullscreen ? 'class=\"%%\"' : 'class=\"%%-wrap edui-dialog-fullscreen-flag\"' ) +'><div id=\"##_body\" class=\"%%-body\">' +\r\n                '<div class=\"%%-shadow\"></div>' +\r\n                '<div id=\"##_titlebar\" class=\"%%-titlebar\">' +\r\n                '<div class=\"%%-draghandle\" onmousedown=\"$$._onTitlebarMouseDown(event, this);\">' +\r\n                 '<span class=\"%%-caption\">' + (this.title || '') + '</span>' +\r\n                '</div>' +\r\n                this.closeButton.renderHtml() +\r\n                '</div>' +\r\n                '<div id=\"##_content\" class=\"%%-content\">'+ ( this.autoReset ? '' : this.getContentHtml()) +'</div>' +\r\n                footHtml +\r\n                '</div></div></div>';\r\n        },\r\n        postRender: function (){\r\n            // todo: 保持居中/记住上次关闭位置选项\r\n            if (!this.modalMask.getDom()) {\r\n                this.modalMask.render();\r\n                this.modalMask.hide();\r\n            }\r\n            if (!this.dragMask.getDom()) {\r\n                this.dragMask.render();\r\n                this.dragMask.hide();\r\n            }\r\n            var me = this;\r\n            this.addListener('show', function (){\r\n                me.modalMask.show(this.getDom().style.zIndex - 2);\r\n            });\r\n            this.addListener('hide', function (){\r\n                me.modalMask.hide();\r\n            });\r\n            if (this.buttons) {\r\n                for (var i=0; i<this.buttons.length; i++) {\r\n                    this.buttons[i].postRender();\r\n                }\r\n            }\r\n            domUtils.on(window, 'resize', function (){\r\n                setTimeout(function (){\r\n                    if (!me.isHidden()) {\r\n                        me.safeSetOffset(uiUtils.getClientRect(me.getDom()));\r\n                    }\r\n                });\r\n            });\r\n\r\n            //hold住scroll事件，防止dialog的滚动影响页面\r\n//            if( this.holdScroll ) {\r\n//\r\n//                if( !me.iframeUrl ) {\r\n//                    domUtils.on( document.getElementById( me.id + \"_iframe\"), !browser.gecko ? \"mousewheel\" : \"DOMMouseScroll\", function(e){\r\n//                        domUtils.preventDefault(e);\r\n//                    } );\r\n//                } else {\r\n//                    me.addListener('dialogafterreset', function(){\r\n//                        window.setTimeout(function(){\r\n//                            var iframeWindow = document.getElementById( me.id + \"_iframe\").contentWindow;\r\n//\r\n//                            if( browser.ie ) {\r\n//\r\n//                                var timer = window.setInterval(function(){\r\n//\r\n//                                    if( iframeWindow.document && iframeWindow.document.body ) {\r\n//                                        window.clearInterval( timer );\r\n//                                        timer = null;\r\n//                                        domUtils.on( iframeWindow.document.body, !browser.gecko ? \"mousewheel\" : \"DOMMouseScroll\", function(e){\r\n//                                            domUtils.preventDefault(e);\r\n//                                        } );\r\n//                                    }\r\n//\r\n//                                }, 100);\r\n//\r\n//                            } else {\r\n//                                domUtils.on( iframeWindow, !browser.gecko ? \"mousewheel\" : \"DOMMouseScroll\", function(e){\r\n//                                    domUtils.preventDefault(e);\r\n//                                } );\r\n//                            }\r\n//\r\n//                        }, 1);\r\n//                    });\r\n//                }\r\n//\r\n//            }\r\n            this._hide();\r\n        },\r\n        mesureSize: function (){\r\n            var body = this.getDom('body');\r\n            var width = uiUtils.getClientRect(this.getDom('content')).width;\r\n            var dialogBodyStyle = body.style;\r\n            dialogBodyStyle.width = width;\r\n            return uiUtils.getClientRect(body);\r\n        },\r\n        _onTitlebarMouseDown: function (evt, el){\r\n            if (this.draggable) {\r\n                var rect;\r\n                var vpRect = uiUtils.getViewportRect();\r\n                var me = this;\r\n                uiUtils.startDrag(evt, {\r\n                    ondragstart: function (){\r\n                        rect = uiUtils.getClientRect(me.getDom());\r\n                        me.getDom('contmask').style.visibility = 'visible';\r\n                        me.dragMask.show(me.getDom().style.zIndex - 1);\r\n                    },\r\n                    ondragmove: function (x, y){\r\n                        var left = rect.left + x;\r\n                        var top = rect.top + y;\r\n                        me.safeSetOffset({\r\n                            left: left,\r\n                            top: top\r\n                        });\r\n                    },\r\n                    ondragstop: function (){\r\n                        me.getDom('contmask').style.visibility = 'hidden';\r\n                        domUtils.removeClasses(me.getDom(), ['edui-state-centered']);\r\n                        me.dragMask.hide();\r\n                    }\r\n                });\r\n            }\r\n        },\r\n        reset: function (){\r\n            this.getDom('content').innerHTML = this.getContentHtml();\r\n            this.fireEvent('dialogafterreset');\r\n        },\r\n        _show: function (){\r\n            if (this._hidden) {\r\n                this.getDom().style.display = '';\r\n\r\n                //要高过编辑器的zindxe\r\n                this.editor.container.style.zIndex && (this.getDom().style.zIndex = this.editor.container.style.zIndex * 1 + 10);\r\n                this._hidden = false;\r\n                this.fireEvent('show');\r\n                baidu.editor.ui.uiUtils.getFixedLayer().style.zIndex = this.getDom().style.zIndex - 4;\r\n            }\r\n        },\r\n        isHidden: function (){\r\n            return this._hidden;\r\n        },\r\n        _hide: function (){\r\n            if (!this._hidden) {\r\n                var wrapNode = this.getDom();\r\n                wrapNode.style.display = 'none';\r\n                wrapNode.style.zIndex = '';\r\n                wrapNode.style.width = '';\r\n                wrapNode.style.height = '';\r\n                this._hidden = true;\r\n                this.fireEvent('hide');\r\n            }\r\n        },\r\n        open: function (){\r\n            if (this.autoReset) {\r\n                //有可能还没有渲染\r\n                try{\r\n                    this.reset();\r\n                }catch(e){\r\n                    this.render();\r\n                    this.open()\r\n                }\r\n            }\r\n            this.showAtCenter();\r\n            if (this.iframeUrl) {\r\n                try {\r\n                    this.getDom('iframe').focus();\r\n                } catch(ex){}\r\n            }\r\n            activeDialog = this;\r\n        },\r\n        _onCloseButtonClick: function (evt, el){\r\n            this.close(false);\r\n        },\r\n        close: function (ok){\r\n            if (this.fireEvent('close', ok) !== false) {\r\n                //还原环境\r\n                if ( this.fullscreen ) {\r\n\r\n                    document.documentElement.style.overflowX = this._originalContext.html.overflowX;\r\n                    document.documentElement.style.overflowY = this._originalContext.html.overflowY;\r\n                    document.body.style.overflowX = this._originalContext.body.overflowX;\r\n                    document.body.style.overflowY = this._originalContext.body.overflowY;\r\n                    delete this._originalContext;\r\n\r\n                }\r\n                this._hide();\r\n\r\n                //销毁content\r\n                var content = this.getDom('content');\r\n                var iframe = this.getDom('iframe');\r\n                if (content && iframe) {\r\n                    var doc = iframe.contentDocument || iframe.contentWindow.document;\r\n                    doc && (doc.body.innerHTML = '');\r\n                    domUtils.remove(content);\r\n                }\r\n            }\r\n        }\r\n    };\r\n    utils.inherits(Dialog, UIBase);\r\n})();\r\n\r\n\r\n// ui/menubutton.js\r\n///import core\r\n///import uicore\r\n///import ui/menu.js\r\n///import ui/splitbutton.js\r\n(function (){\r\n    var utils = baidu.editor.utils,\r\n        Menu = baidu.editor.ui.Menu,\r\n        SplitButton = baidu.editor.ui.SplitButton,\r\n        MenuButton = baidu.editor.ui.MenuButton = function (options){\r\n            this.initOptions(options);\r\n            this.initMenuButton();\r\n        };\r\n    MenuButton.prototype = {\r\n        initMenuButton: function (){\r\n            var me = this;\r\n            this.uiName = \"menubutton\";\r\n            this.popup = new Menu({\r\n                items: me.items,\r\n                className: me.className,\r\n                editor:me.editor\r\n            });\r\n            this.popup.addListener('show', function (){\r\n                var list = this;\r\n                for (var i=0; i<list.items.length; i++) {\r\n                    list.items[i].removeState('checked');\r\n                    if (list.items[i].value == me._value) {\r\n                        list.items[i].addState('checked');\r\n                        this.value = me._value;\r\n                    }\r\n                }\r\n            });\r\n            this.initSplitButton();\r\n        },\r\n        setValue : function(value){\r\n            this._value = value;\r\n        }\r\n        \r\n    };\r\n    utils.inherits(MenuButton, SplitButton);\r\n})();\r\n\r\n// ui/multiMenu.js\r\n///import core\r\n///import uicore\r\n ///commands 表情\r\n(function(){\r\n    var utils = baidu.editor.utils,\r\n        Popup = baidu.editor.ui.Popup,\r\n        SplitButton = baidu.editor.ui.SplitButton,\r\n        MultiMenuPop = baidu.editor.ui.MultiMenuPop = function(options){\r\n            this.initOptions(options);\r\n            this.initMultiMenu();\r\n        };\r\n\r\n    MultiMenuPop.prototype = {\r\n        initMultiMenu: function (){\r\n            var me = this;\r\n            this.popup = new Popup({\r\n                content: '',\r\n                editor : me.editor,\r\n                iframe_rendered: false,\r\n                onshow: function (){\r\n                    if (!this.iframe_rendered) {\r\n                        this.iframe_rendered = true;\r\n                        this.getDom('content').innerHTML = '<iframe id=\"'+me.id+'_iframe\" src=\"'+ me.iframeUrl +'\" frameborder=\"0\"></iframe>';\r\n                        me.editor.container.style.zIndex && (this.getDom().style.zIndex = me.editor.container.style.zIndex * 1 + 1);\r\n                    }\r\n                }\r\n               // canSideUp:false,\r\n               // canSideLeft:false\r\n            });\r\n            this.onbuttonclick = function(){\r\n                this.showPopup();\r\n            };\r\n            this.initSplitButton();\r\n        }\r\n\r\n    };\r\n\r\n    utils.inherits(MultiMenuPop, SplitButton);\r\n})();\r\n\r\n\r\n// ui/shortcutmenu.js\r\n(function () {\r\n    var UI = baidu.editor.ui,\r\n        UIBase = UI.UIBase,\r\n        uiUtils = UI.uiUtils,\r\n        utils = baidu.editor.utils,\r\n        domUtils = baidu.editor.dom.domUtils;\r\n\r\n    var allMenus = [],//存储所有快捷菜单\r\n        timeID,\r\n        isSubMenuShow = false;//是否有子pop显示\r\n\r\n    var ShortCutMenu = UI.ShortCutMenu = function (options) {\r\n        this.initOptions (options);\r\n        this.initShortCutMenu ();\r\n    };\r\n\r\n    ShortCutMenu.postHide = hideAllMenu;\r\n\r\n    ShortCutMenu.prototype = {\r\n        isHidden : true ,\r\n        SPACE : 5 ,\r\n        initShortCutMenu : function () {\r\n            this.items = this.items || [];\r\n            this.initUIBase ();\r\n            this.initItems ();\r\n            this.initEvent ();\r\n            allMenus.push (this);\r\n        } ,\r\n        initEvent : function () {\r\n            var me = this,\r\n                doc = me.editor.document;\r\n\r\n            domUtils.on (doc , \"mousemove\" , function (e) {\r\n                if (me.isHidden === false) {\r\n                    //有pop显示就不隐藏快捷菜单\r\n                    if (me.getSubMenuMark () || me.eventType == \"contextmenu\")   return;\r\n\r\n\r\n                    var flag = true,\r\n                        el = me.getDom (),\r\n                        wt = el.offsetWidth,\r\n                        ht = el.offsetHeight,\r\n                        distanceX = wt / 2 + me.SPACE,//距离中心X标准\r\n                        distanceY = ht / 2,//距离中心Y标准\r\n                        x = Math.abs (e.screenX - me.left),//离中心距离横坐标\r\n                        y = Math.abs (e.screenY - me.top);//离中心距离纵坐标\r\n\r\n                    clearTimeout (timeID);\r\n                    timeID = setTimeout (function () {\r\n                        if (y > 0 && y < distanceY) {\r\n                            me.setOpacity (el , \"1\");\r\n                        } else if (y > distanceY && y < distanceY + 70) {\r\n                            me.setOpacity (el , \"0.5\");\r\n                            flag = false;\r\n                        } else if (y > distanceY + 70 && y < distanceY + 140) {\r\n                            me.hide ();\r\n                        }\r\n\r\n                        if (flag && x > 0 && x < distanceX) {\r\n                            me.setOpacity (el , \"1\")\r\n                        } else if (x > distanceX && x < distanceX + 70) {\r\n                            me.setOpacity (el , \"0.5\")\r\n                        } else if (x > distanceX + 70 && x < distanceX + 140) {\r\n                            me.hide ();\r\n                        }\r\n                    });\r\n                }\r\n            });\r\n\r\n            //ie\\ff下 mouseout不准\r\n            if (browser.chrome) {\r\n                domUtils.on (doc , \"mouseout\" , function (e) {\r\n                    var relatedTgt = e.relatedTarget || e.toElement;\r\n\r\n                    if (relatedTgt == null || relatedTgt.tagName == \"HTML\") {\r\n                        me.hide ();\r\n                    }\r\n                });\r\n            }\r\n\r\n            me.editor.addListener (\"afterhidepop\" , function () {\r\n                if (!me.isHidden) {\r\n                    isSubMenuShow = true;\r\n                }\r\n            });\r\n\r\n        } ,\r\n        initItems : function () {\r\n            if (utils.isArray (this.items)) {\r\n                for (var i = 0, len = this.items.length ; i < len ; i++) {\r\n                    var item = this.items[i].toLowerCase ();\r\n\r\n                    if (UI[item]) {\r\n                        this.items[i] = new UI[item] (this.editor);\r\n                        this.items[i].className += \" edui-shortcutsubmenu \";\r\n                    }\r\n                }\r\n            }\r\n        } ,\r\n        setOpacity : function (el , value) {\r\n            if (browser.ie && browser.version < 9) {\r\n                el.style.filter = \"alpha(opacity = \" + parseFloat (value) * 100 + \");\"\r\n            } else {\r\n                el.style.opacity = value;\r\n            }\r\n        } ,\r\n        getSubMenuMark : function () {\r\n            isSubMenuShow = false;\r\n            var layerEle = uiUtils.getFixedLayer ();\r\n            var list = domUtils.getElementsByTagName (layerEle , \"div\" , function (node) {\r\n                return domUtils.hasClass (node , \"edui-shortcutsubmenu edui-popup\")\r\n            });\r\n\r\n            for (var i = 0, node ; node = list[i++] ;) {\r\n                if (node.style.display != \"none\") {\r\n                    isSubMenuShow = true;\r\n                }\r\n            }\r\n            return isSubMenuShow;\r\n        } ,\r\n        show : function (e , hasContextmenu) {\r\n            var me = this,\r\n                offset = {},\r\n                el = this.getDom (),\r\n                fixedlayer = uiUtils.getFixedLayer ();\r\n\r\n            function setPos (offset) {\r\n                if (offset.left < 0) {\r\n                    offset.left = 0;\r\n                }\r\n                if (offset.top < 0) {\r\n                    offset.top = 0;\r\n                }\r\n                el.style.cssText = \"position:absolute;left:\" + offset.left + \"px;top:\" + offset.top + \"px;\";\r\n            }\r\n\r\n            function setPosByCxtMenu (menu) {\r\n                if (!menu.tagName) {\r\n                    menu = menu.getDom ();\r\n                }\r\n                offset.left = parseInt (menu.style.left);\r\n                offset.top = parseInt (menu.style.top);\r\n                offset.top -= el.offsetHeight + 15;\r\n                setPos (offset);\r\n            }\r\n\r\n\r\n            me.eventType = e.type;\r\n            el.style.cssText = \"display:block;left:-9999px\";\r\n\r\n            if (e.type == \"contextmenu\" && hasContextmenu) {\r\n                var menu = domUtils.getElementsByTagName (fixedlayer , \"div\" , \"edui-contextmenu\")[0];\r\n                if (menu) {\r\n                    setPosByCxtMenu (menu)\r\n                } else {\r\n                    me.editor.addListener (\"aftershowcontextmenu\" , function (type , menu) {\r\n                        setPosByCxtMenu (menu);\r\n                    });\r\n                }\r\n            } else {\r\n                offset = uiUtils.getViewportOffsetByEvent (e);\r\n                offset.top -= el.offsetHeight + me.SPACE;\r\n                offset.left += me.SPACE + 20;\r\n                setPos (offset);\r\n                me.setOpacity (el , 0.2);\r\n            }\r\n\r\n\r\n            me.isHidden = false;\r\n            me.left = e.screenX + el.offsetWidth / 2 - me.SPACE;\r\n            me.top = e.screenY - (el.offsetHeight / 2) - me.SPACE;\r\n\r\n            if (me.editor) {\r\n                el.style.zIndex = me.editor.container.style.zIndex * 1 + 10;\r\n                fixedlayer.style.zIndex = el.style.zIndex - 1;\r\n            }\r\n        } ,\r\n        hide : function () {\r\n            if (this.getDom ()) {\r\n                this.getDom ().style.display = \"none\";\r\n            }\r\n            this.isHidden = true;\r\n        } ,\r\n        postRender : function () {\r\n            if (utils.isArray (this.items)) {\r\n                for (var i = 0, item ; item = this.items[i++] ;) {\r\n                    item.postRender ();\r\n                }\r\n            }\r\n        } ,\r\n        getHtmlTpl : function () {\r\n            var buff;\r\n            if (utils.isArray (this.items)) {\r\n                buff = [];\r\n                for (var i = 0 ; i < this.items.length ; i++) {\r\n                    buff[i] = this.items[i].renderHtml ();\r\n                }\r\n                buff = buff.join (\"\");\r\n            } else {\r\n                buff = this.items;\r\n            }\r\n\r\n            return '<div id=\"##\" class=\"%% edui-toolbar\" data-src=\"shortcutmenu\" onmousedown=\"return false;\" onselectstart=\"return false;\" >' +\r\n                buff +\r\n                '</div>';\r\n        }\r\n    };\r\n\r\n    utils.inherits (ShortCutMenu , UIBase);\r\n\r\n    function hideAllMenu (e) {\r\n        var tgt = e.target || e.srcElement,\r\n            cur = domUtils.findParent (tgt , function (node) {\r\n                return domUtils.hasClass (node , \"edui-shortcutmenu\") || domUtils.hasClass (node , \"edui-popup\");\r\n            } , true);\r\n\r\n        if (!cur) {\r\n            for (var i = 0, menu ; menu = allMenus[i++] ;) {\r\n                menu.hide ()\r\n            }\r\n        }\r\n    }\r\n\r\n    domUtils.on (document , 'mousedown' , function (e) {\r\n        hideAllMenu (e);\r\n    });\r\n\r\n    domUtils.on (window , 'scroll' , function (e) {\r\n        hideAllMenu (e);\r\n    });\r\n\r\n}) ();\r\n\r\n\r\n// ui/breakline.js\r\n(function (){\r\n    var utils = baidu.editor.utils,\r\n        UIBase = baidu.editor.ui.UIBase,\r\n        Breakline = baidu.editor.ui.Breakline = function (options){\r\n            this.initOptions(options);\r\n            this.initSeparator();\r\n        };\r\n    Breakline.prototype = {\r\n        uiName: 'Breakline',\r\n        initSeparator: function (){\r\n            this.initUIBase();\r\n        },\r\n        getHtmlTpl: function (){\r\n            return '<br/>';\r\n        }\r\n    };\r\n    utils.inherits(Breakline, UIBase);\r\n\r\n})();\r\n\r\n\r\n// ui/message.js\r\n///import core\r\n///import uicore\r\n(function () {\r\n    var utils = baidu.editor.utils,\r\n        domUtils = baidu.editor.dom.domUtils,\r\n        UIBase = baidu.editor.ui.UIBase,\r\n        Message = baidu.editor.ui.Message = function (options){\r\n            this.initOptions(options);\r\n            this.initMessage();\r\n        };\r\n\r\n    Message.prototype = {\r\n        initMessage: function (){\r\n            this.initUIBase();\r\n        },\r\n        getHtmlTpl: function (){\r\n            return '<div id=\"##\" class=\"edui-message %%\">' +\r\n            ' <div id=\"##_closer\" class=\"edui-message-closer\">×</div>' +\r\n            ' <div id=\"##_body\" class=\"edui-message-body edui-message-type-info\">' +\r\n            ' <iframe style=\"position:absolute;z-index:-1;left:0;top:0;background-color: transparent;\" frameborder=\"0\" width=\"100%\" height=\"100%\" src=\"about:blank\"></iframe>' +\r\n            ' <div class=\"edui-shadow\"></div>' +\r\n            ' <div id=\"##_content\" class=\"edui-message-content\">' +\r\n            '  </div>' +\r\n            ' </div>' +\r\n            '</div>';\r\n        },\r\n        reset: function(opt){\r\n            var me = this;\r\n            if (!opt.keepshow) {\r\n                clearTimeout(this.timer);\r\n                me.timer = setTimeout(function(){\r\n                    me.hide();\r\n                }, opt.timeout || 4000);\r\n            }\r\n\r\n            opt.content !== undefined && me.setContent(opt.content);\r\n            opt.type !== undefined && me.setType(opt.type);\r\n\r\n            me.show();\r\n        },\r\n        postRender: function(){\r\n            var me = this,\r\n                closer = this.getDom('closer');\r\n            closer && domUtils.on(closer, 'click', function(){\r\n                me.hide();\r\n            });\r\n        },\r\n        setContent: function(content){\r\n            this.getDom('content').innerHTML = content;\r\n        },\r\n        setType: function(type){\r\n            type = type || 'info';\r\n            var body = this.getDom('body');\r\n            body.className = body.className.replace(/edui-message-type-[\\w-]+/, 'edui-message-type-' + type);\r\n        },\r\n        getContent: function(){\r\n            return this.getDom('content').innerHTML;\r\n        },\r\n        getType: function(){\r\n            var arr = this.getDom('body').match(/edui-message-type-([\\w-]+)/);\r\n            return arr ? arr[1]:'';\r\n        },\r\n        show: function (){\r\n            this.getDom().style.display = 'block';\r\n        },\r\n        hide: function (){\r\n            var dom = this.getDom();\r\n            if (dom) {\r\n                dom.style.display = 'none';\r\n                dom.parentNode && dom.parentNode.removeChild(dom);\r\n            }\r\n        }\r\n    };\r\n\r\n    utils.inherits(Message, UIBase);\r\n\r\n})();\r\n\r\n\r\n// adapter/editorui.js\r\n//ui跟编辑器的适配層\r\n//那个按钮弹出是dialog，是下拉筐等都是在这个js中配置\r\n//自己写的ui也要在这里配置，放到baidu.editor.ui下边，当编辑器实例化的时候会根据ueditor.config中的toolbars找到相应的进行实例化\r\n(function () {\r\n    var utils = baidu.editor.utils;\r\n    var editorui = baidu.editor.ui;\r\n    var _Dialog = editorui.Dialog;\r\n    editorui.buttons = {};\r\n\r\n    editorui.Dialog = function (options) {\r\n        var dialog = new _options);\r\n        dialog.addListener('hide', function () {\r\n\r\n            if (dialog.editor) {\r\n                var editor = dialog.editor;\r\n                try {\r\n                    if (browser.gecko) {\r\n                        var y = editor.window.scrollY,\r\n                            x = editor.window.scrollX;\r\n                        editor.body.focus();\r\n                        editor.window.scrollTo(x, y);\r\n                    } else {\r\n                        editor.focus();\r\n                    }\r\n\r\n\r\n                } catch (ex) {\r\n                }\r\n            }\r\n        });\r\n        return dialog;\r\n    };\r\n\r\n    var iframeUrlMap = {\r\n        'anchor':'~/dialogs/anchor/anchor.html',\r\n        'insertimage':'~/dialogs/image/image.html',\r\n        'link':'~/dialogs/link/link.html',\r\n        'spechars':'~/dialogs/spechars/spechars.html',\r\n        'searchreplace':'~/dialogs/searchreplace/searchreplace.html',\r\n        'map':'~/dialogs/map/map.html',\r\n        'gmap':'~/dialogs/gmap/gmap.html',\r\n        'insertvideo':'~/dialogs/video/video.html',\r\n        'help':'~/dialogs/help/help.html',\r\n        'preview':'~/dialogs/preview/preview.html',\r\n        'emotion':'~/dialogs/emotion/emotion.html',\r\n        'wordimage':'~/dialogs/wordimage/wordimage.html',\r\n        'attachment':'~/dialogs/attachment/attachment.html',\r\n        'insertframe':'~/dialogs/insertframe/insertframe.html',\r\n        'edittip':'~/dialogs/table/edittip.html',\r\n        'edittable':'~/dialogs/table/edittable.html',\r\n        'edittd':'~/dialogs/table/edittd.html',\r\n        'webapp':'~/dialogs/webapp/webapp.html',\r\n        'snapscreen':'~/dialogs/snapscreen/snapscreen.html',\r\n        'scrawl':'~/dialogs/scrawl/scrawl.html',\r\n        'music':'~/dialogs/music/music.html',\r\n        'template':'~/dialogs/template/template.html',\r\n        'background':'~/dialogs/background/background.html',\r\n        'charts': '~/dialogs/charts/charts.html'\r\n    };\r\n    //为工具栏添加按钮，以下都是统一的按钮触发命令，所以写在一起\r\n    var btnCmds = ['undo', 'redo', 'formatmatch',\r\n        'bold', 'italic', 'underline', 'fontborder', 'touppercase', 'tolowercase',\r\n        'strikethrough', 'subscript', 'superscript', 'source', 'indent', 'outdent',\r\n        'blockquote', 'pasteplain', 'pagebreak',\r\n        'selectall', 'print','horizontal', 'removeformat', 'time', 'date', 'unlink',\r\n        'insertparagraphbeforetable', 'insertrow', 'insertcol', 'mergeright', 'mergedown', 'deleterow',\r\n        'deletecol', 'splittorows', 'splittocols', 'splittocells', 'mergecells', 'deletetable', 'drafts'];\r\n\r\n    for (var i = 0, ci; ci = btnCmds[i++];) {\r\n        ci = ci.toLowerCase();\r\n        editorui[ci] = function (cmd) {\r\n            return function (editor) {\r\n                var ui = new editorui.Button({\r\n                    className:'edui-for-' + cmd,\r\n                    title:editor.options.labelMap[cmd] || editor.getLang(\"labelMap.\" + cmd) || '',\r\n                    onclick:function () {\r\n                        editor.execCommand(cmd);\r\n                    },\r\n                    theme:editor.options.theme,\r\n                    showText:false\r\n                });\r\n                editorui.buttons[cmd] = ui;\r\n                editor.addListener('selectionchange', function (type, causeByUi, uiReady) {\r\n                    var state = editor.queryCommandState(cmd);\r\n                    if (state == -1) {\r\n                        ui.setDisabled(true);\r\n                        ui.setChecked(false);\r\n                    } else {\r\n                        if (!uiReady) {\r\n                            ui.setDisabled(false);\r\n                            ui.setChecked(state);\r\n                        }\r\n                    }\r\n                });\r\n                return ui;\r\n            };\r\n        }(ci);\r\n    }\r\n\r\n    //清除文档\r\n    editorui.cleardoc = function (editor) {\r\n        var ui = new editorui.Button({\r\n            className:'edui-for-cleardoc',\r\n            title:editor.options.labelMap.cleardoc || editor.getLang(\"labelMap.cleardoc\") || '',\r\n            theme:editor.options.theme,\r\n            onclick:function () {\r\n                if (confirm(editor.getLang(\"confirmClear\"))) {\r\n                    editor.execCommand('cleardoc');\r\n                }\r\n            }\r\n        });\r\n        editorui.buttons[\"cleardoc\"] = ui;\r\n        editor.addListener('selectionchange', function () {\r\n            ui.setDisabled(editor.queryCommandState('cleardoc') == -1);\r\n        });\r\n        return ui;\r\n    };\r\n\r\n    //排版，图片排版，文字方向\r\n    var typeset = {\r\n        'justify':['left', 'right', 'center', 'justify'],\r\n        'imagefloat':['none', 'left', 'center', 'right'],\r\n        'directionality':['ltr', 'rtl']\r\n    };\r\n\r\n    for (var p in typeset) {\r\n\r\n        (function (cmd, val) {\r\n            for (var i = 0, ci; ci = val[i++];) {\r\n                (function (cmd2) {\r\n                    editorui[cmd.replace('float', '') + cmd2] = function (editor) {\r\n                        var ui = new editorui.Button({\r\n                            className:'edui-for-' + cmd.replace('float', '') + cmd2,\r\n                            title:editor.options.labelMap[cmd.replace('float', '') + cmd2] || editor.getLang(\"labelMap.\" + cmd.replace('float', '') + cmd2) || '',\r\n                            theme:editor.options.theme,\r\n                            onclick:function () {\r\n                                editor.execCommand(cmd, cmd2);\r\n                            }\r\n                        });\r\n                        editorui.buttons[cmd] = ui;\r\n                        editor.addListener('selectionchange', function (type, causeByUi, uiReady) {\r\n                            ui.setDisabled(editor.queryCommandState(cmd) == -1);\r\n                            ui.setChecked(editor.queryCommandValue(cmd) == cmd2 && !uiReady);\r\n                        });\r\n                        return ui;\r\n                    };\r\n                })(ci)\r\n            }\r\n        })(p, typeset[p])\r\n    }\r\n\r\n    //字体颜色和背景颜色\r\n    for (var i = 0, ci; ci = ['backcolor', 'forecolor'][i++];) {\r\n        editorui[ci] = function (cmd) {\r\n            return function (editor) {\r\n                var ui = new editorui.ColorButton({\r\n                    className:'edui-for-' + cmd,\r\n                    color:'default',\r\n                    title:editor.options.labelMap[cmd] || editor.getLang(\"labelMap.\" + cmd) || '',\r\n                    editor:editor,\r\n                    onpickcolor:function (t, color) {\r\n                        editor.execCommand(cmd, color);\r\n                    },\r\n                    onpicknocolor:function () {\r\n                        editor.execCommand(cmd, 'default');\r\n                        this.setColor('transparent');\r\n                        this.color = 'default';\r\n                    },\r\n                    onbuttonclick:function () {\r\n                        editor.execCommand(cmd, this.color);\r\n                    }\r\n                });\r\n                editorui.buttons[cmd] = ui;\r\n                editor.addListener('selectionchange', function () {\r\n                    ui.setDisabled(editor.queryCommandState(cmd) == -1);\r\n                });\r\n                return ui;\r\n            };\r\n        }(ci);\r\n    }\r\n\r\n\r\n    var dialogBtns = {\r\n        noOk:['searchreplace', 'help', 'spechars', 'webapp','preview'],\r\n        ok:['attachment', 'anchor', 'link', 'insertimage', 'map', 'gmap', 'insertframe', 'wordimage',\r\n            'insertvideo', 'insertframe', 'edittip', 'edittable', 'edittd', 'scrawl', 'template', 'music', 'background', 'charts']\r\n    };\r\n\r\n    for (var p in dialogBtns) {\r\n        (function (type, vals) {\r\n            for (var i = 0, ci; ci = vals[i++];) {\r\n                //todo opera下存在问题\r\n                if (browser.opera && ci === \"searchreplace\") {\r\n                    continue;\r\n                }\r\n                (function (cmd) {\r\n                    editorui[cmd] = function (editor, iframeUrl, title) {\r\n                        iframeUrl = iframeUrl || (editor.options.iframeUrlMap || {})[cmd] || iframeUrlMap[cmd];\r\n                        title = editor.options.labelMap[cmd] || editor.getLang(\"labelMap.\" + cmd) || '';\r\n\r\n                        var dialog;\r\n                        //没有iframeUrl不创建dialog\r\n                        if (iframeUrl) {\r\n                            dialog = new editorui.Dialog(utils.extend({\r\n                                iframeUrl:editor.ui.mapUrl(iframeUrl),\r\n                                editor:editor,\r\n                                className:'edui-for-' + cmd,\r\n                                title:title,\r\n                                holdScroll: cmd === 'insertimage',\r\n                                fullscreen: /charts|preview/.test(cmd),\r\n                                closeDialog:editor.getLang(\"closeDialog\")\r\n                            }, type == 'ok' ? {\r\n                                buttons:[\r\n                                    {\r\n                                        className:'edui-okbutton',\r\n                                        label:editor.getLang(\"ok\"),\r\n                                        editor:editor,\r\n                                        onclick:function () {\r\n                                            dialog.close(true);\r\n                                        }\r\n                                    },\r\n                                    {\r\n                                        className:'edui-cancelbutton',\r\n                                        label:editor.getLang(\"cancel\"),\r\n                                        editor:editor,\r\n                                        onclick:function () {\r\n                                            dialog.close(false);\r\n                                        }\r\n                                    }\r\n                                ]\r\n                            } : {}));\r\n\r\n                            editor.ui._dialogs[cmd + \"Dialog\"] = dialog;\r\n                        }\r\n\r\n                        var ui = new editorui.Button({\r\n                            className:'edui-for-' + cmd,\r\n                            title:title,\r\n                            onclick:function () {\r\n                                if (dialog) {\r\n                                    switch (cmd) {\r\n                                        case \"wordimage\":\r\n                                            var images = editor.execCommand(\"wordimage\");\r\n                                            if (images && images.length) {\r\n                                                dialog.render();\r\n                                                dialog.open();\r\n                                            }\r\n                                            break;\r\n                                        case \"scrawl\":\r\n                                            if (editor.queryCommandState(\"scrawl\") != -1) {\r\n                                                dialog.render();\r\n                                                dialog.open();\r\n                                            }\r\n\r\n                                            break;\r\n                                        default:\r\n                                            dialog.render();\r\n                                            dialog.open();\r\n                                    }\r\n                                }\r\n                            },\r\n                            theme:editor.options.theme,\r\n                            disabled:(cmd == 'scrawl' && editor.queryCommandState(\"scrawl\") == -1) || ( cmd == 'charts' )\r\n                        });\r\n                        editorui.buttons[cmd] = ui;\r\n                        editor.addListener('selectionchange', function () {\r\n                            //只存在于右键菜单而无工具栏按钮的ui不需要检测状态\r\n                            var unNeedCheckState = {'edittable':1};\r\n                            if (cmd in unNeedCheckState)return;\r\n\r\n                            var state = editor.queryCommandState(cmd);\r\n                            if (ui.getDom()) {\r\n                                ui.setDisabled(state == -1);\r\n                                ui.setChecked(state);\r\n                            }\r\n\r\n                        });\r\n\r\n                        return ui;\r\n                    };\r\n                })(ci.toLowerCase())\r\n            }\r\n        })(p, dialogBtns[p]);\r\n    }\r\n\r\n    editorui.snapscreen = function (editor, iframeUrl, title) {\r\n        title = editor.options.labelMap['snapscreen'] || editor.getLang(\"labelMap.snapscreen\") || '';\r\n        var ui = new editorui.Button({\r\n            className:'edui-for-snapscreen',\r\n            title:title,\r\n            onclick:function () {\r\n                editor.execCommand(\"snapscreen\");\r\n            },\r\n            theme:editor.options.theme\r\n\r\n        });\r\n        editorui.buttons['snapscreen'] = ui;\r\n        iframeUrl = iframeUrl || (editor.options.iframeUrlMap || {})[\"snapscreen\"] || iframeUrlMap[\"snapscreen\"];\r\n        if (iframeUrl) {\r\n            var dialog = new editorui.Dialog({\r\n                iframeUrl:editor.ui.mapUrl(iframeUrl),\r\n                editor:editor,\r\n                className:'edui-for-snapscreen',\r\n                title:title,\r\n                buttons:[\r\n                    {\r\n                        className:'edui-okbutton',\r\n                        label:editor.getLang(\"ok\"),\r\n                        editor:editor,\r\n                        onclick:function () {\r\n                            dialog.close(true);\r\n                        }\r\n                    },\r\n                    {\r\n                        className:'edui-cancelbutton',\r\n                        label:editor.getLang(\"cancel\"),\r\n                        editor:editor,\r\n                        onclick:function () {\r\n                            dialog.close(false);\r\n                        }\r\n                    }\r\n                ]\r\n\r\n            });\r\n            dialog.render();\r\n            editor.ui._dialogs[\"snapscreenDialog\"] = dialog;\r\n        }\r\n        editor.addListener('selectionchange', function () {\r\n            ui.setDisabled(editor.queryCommandState('snapscreen') == -1);\r\n        });\r\n        return ui;\r\n    };\r\n\r\n    editorui.insertcode = function (editor, list, title) {\r\n        list = editor.options['insertcode'] || [];\r\n        title = editor.options.labelMap['insertcode'] || editor.getLang(\"labelMap.insertcode\") || '';\r\n       // if (!list.length) return;\r\n        var items = [];\r\n        utils.each(list,function(key,val){\r\n            items.push({\r\n                label:key,\r\n                value:val,\r\n                theme:editor.options.theme,\r\n                renderLabelHtml:function () {\r\n                    return '<div class=\"edui-label %%-label\" >' + (this.label || '') + '</div>';\r\n                }\r\n            });\r\n        });\r\n\r\n        var ui = new editorui.Combox({\r\n            editor:editor,\r\n            items:items,\r\n            onselect:function (t, index) {\r\n                editor.execCommand('insertcode', this.items[index].value);\r\n            },\r\n            onbuttonclick:function () {\r\n                this.showPopup();\r\n            },\r\n            title:title,\r\n            initValue:title,\r\n            className:'edui-for-insertcode',\r\n            indexByValue:function (value) {\r\n                if (value) {\r\n                    for (var i = 0, ci; ci = this.items[i]; i++) {\r\n                        if (ci.value.indexOf(value) != -1)\r\n                            return i;\r\n                    }\r\n                }\r\n\r\n                return -1;\r\n            }\r\n        });\r\n        editorui.buttons['insertcode'] = ui;\r\n        editor.addListener('selectionchange', function (type, causeByUi, uiReady) {\r\n            if (!uiReady) {\r\n                var state = editor.queryCommandState('insertcode');\r\n                if (state == -1) {\r\n                    ui.setDisabled(true);\r\n                } else {\r\n                    ui.setDisabled(false);\r\n                    var value = editor.queryCommandValue('insertcode');\r\n                    if(!value){\r\n                        ui.setValue(title);\r\n                        return;\r\n                    }\r\n                    //trace:1871 ie下从源码模式切换回来时，字体会带单引号，而且会有逗号\r\n                    value && (value = value.replace(/['\"]/g, '').split(',')[0]);\r\n                    ui.setValue(value);\r\n\r\n                }\r\n            }\r\n\r\n        });\r\n        return ui;\r\n    };\r\n    editorui.fontfamily = function (editor, list, title) {\r\n\r\n        list = editor.options['fontfamily'] || [];\r\n        title = editor.options.labelMap['fontfamily'] || editor.getLang(\"labelMap.fontfamily\") || '';\r\n        if (!list.length) return;\r\n        for (var i = 0, ci, items = []; ci = list[i]; i++) {\r\n            var langLabel = editor.getLang('fontfamily')[ci.name] || \"\";\r\n            (function (key, val) {\r\n                items.push({\r\n                    label:key,\r\n                    value:val,\r\n                    theme:editor.options.theme,\r\n                    renderLabelHtml:function () {\r\n                        return '<div class=\"edui-label %%-label\" style=\"font-family:' +\r\n                            utils.unhtml(this.value) + '\">' + (this.label || '') + '</div>';\r\n                    }\r\n                });\r\n            })(ci.label || langLabel, ci.val)\r\n        }\r\n        var ui = new editorui.Combox({\r\n            editor:editor,\r\n            items:items,\r\n            onselect:function (t, index) {\r\n                editor.execCommand('FontFamily', this.items[index].value);\r\n            },\r\n            onbuttonclick:function () {\r\n                this.showPopup();\r\n            },\r\n            title:title,\r\n            initValue:title,\r\n            className:'edui-for-fontfamily',\r\n            indexByValue:function (value) {\r\n                if (value) {\r\n                    for (var i = 0, ci; ci = this.items[i]; i++) {\r\n                        if (ci.value.indexOf(value) != -1)\r\n                            return i;\r\n                    }\r\n                }\r\n\r\n                return -1;\r\n            }\r\n        });\r\n        editorui.buttons['fontfamily'] = ui;\r\n        editor.addListener('selectionchange', function (type, causeByUi, uiReady) {\r\n            if (!uiReady) {\r\n                var state = editor.queryCommandState('FontFamily');\r\n                if (state == -1) {\r\n                    ui.setDisabled(true);\r\n                } else {\r\n                    ui.setDisabled(false);\r\n                    var value = editor.queryCommandValue('FontFamily');\r\n                    //trace:1871 ie下从源码模式切换回来时，字体会带单引号，而且会有逗号\r\n                    value && (value = value.replace(/['\"]/g, '').split(',')[0]);\r\n                    ui.setValue(value);\r\n\r\n                }\r\n            }\r\n\r\n        });\r\n        return ui;\r\n    };\r\n\r\n    editorui.fontsize = function (editor, list, title) {\r\n        title = editor.options.labelMap['fontsize'] || editor.getLang(\"labelMap.fontsize\") || '';\r\n        list = list || editor.options['fontsize'] || [];\r\n        if (!list.length) return;\r\n        var items = [];\r\n        for (var i = 0; i < list.length; i++) {\r\n            var size = list[i] + 'px';\r\n            items.push({\r\n                label:size,\r\n                value:size,\r\n                theme:editor.options.theme,\r\n                renderLabelHtml:function () {\r\n                    return '<div class=\"edui-label %%-label\" style=\"line-height:1;font-size:' +\r\n                        this.value + '\">' + (this.label || '') + '</div>';\r\n                }\r\n            });\r\n        }\r\n        var ui = new editorui.Combox({\r\n            editor:editor,\r\n            items:items,\r\n            title:title,\r\n            initValue:title,\r\n            onselect:function (t, index) {\r\n                editor.execCommand('FontSize', this.items[index].value);\r\n            },\r\n            onbuttonclick:function () {\r\n                this.showPopup();\r\n            },\r\n            className:'edui-for-fontsize'\r\n        });\r\n        editorui.buttons['fontsize'] = ui;\r\n        editor.addListener('selectionchange', function (type, causeByUi, uiReady) {\r\n            if (!uiReady) {\r\n                var state = editor.queryCommandState('FontSize');\r\n                if (state == -1) {\r\n                    ui.setDisabled(true);\r\n                } else {\r\n                    ui.setDisabled(false);\r\n                    ui.setValue(editor.queryCommandValue('FontSize'));\r\n                }\r\n            }\r\n\r\n        });\r\n        return ui;\r\n    };\r\n\r\n    editorui.paragraph = function (editor, list, title) {\r\n        title = editor.options.labelMap['paragraph'] || editor.getLang(\"labelMap.paragraph\") || '';\r\n        list = editor.options['paragraph'] || [];\r\n        if (utils.isEmptyObject(list)) return;\r\n        var items = [];\r\n        for (var i in list) {\r\n            items.push({\r\n                value:i,\r\n                label:list[i] || editor.getLang(\"paragraph\")[i],\r\n                theme:editor.options.theme,\r\n                renderLabelHtml:function () {\r\n                    return '<div class=\"edui-label %%-label\"><span class=\"edui-for-' + this.value + '\">' + (this.label || '') + '</span></div>';\r\n                }\r\n            })\r\n        }\r\n        var ui = new editorui.Combox({\r\n            editor:editor,\r\n            items:items,\r\n            title:title,\r\n            initValue:title,\r\n            className:'edui-for-paragraph',\r\n            onselect:function (t, index) {\r\n                editor.execCommand('Paragraph', this.items[index].value);\r\n            },\r\n            onbuttonclick:function () {\r\n                this.showPopup();\r\n            }\r\n        });\r\n        editorui.buttons['paragraph'] = ui;\r\n        editor.addListener('selectionchange', function (type, causeByUi, uiReady) {\r\n            if (!uiReady) {\r\n                var state = editor.queryCommandState('Paragraph');\r\n                if (state == -1) {\r\n                    ui.setDisabled(true);\r\n                } else {\r\n                    ui.setDisabled(false);\r\n                    var value = editor.queryCommandValue('Paragraph');\r\n                    var index = ui.indexByValue(value);\r\n                    if (index != -1) {\r\n                        ui.setValue(value);\r\n                    } else {\r\n                        ui.setValue(ui.initValue);\r\n                    }\r\n                }\r\n            }\r\n\r\n        });\r\n        return ui;\r\n    };\r\n\r\n\r\n    //自定义标题\r\n    editorui.customstyle = function (editor) {\r\n        var list = editor.options['customstyle'] || [],\r\n            title = editor.options.labelMap['customstyle'] || editor.getLang(\"labelMap.customstyle\") || '';\r\n        if (!list.length)return;\r\n        var langCs = editor.getLang('customstyle');\r\n        for (var i = 0, items = [], t; t = list[i++];) {\r\n            (function (t) {\r\n                var ck = {};\r\n                ck.label = t.label ? t.label : langCs[t.name];\r\n                ck.style = t.style;\r\n                ck.className = t.className;\r\n                ck.tag = t.tag;\r\n                items.push({\r\n                    label:ck.label,\r\n                    value:ck,\r\n                    theme:editor.options.theme,\r\n                    renderLabelHtml:function () {\r\n                        return '<div class=\"edui-label %%-label\">' + '<' + ck.tag + ' ' + (ck.className ? ' class=\"' + ck.className + '\"' : \"\")\r\n                            + (ck.style ? ' style=\"' + ck.style + '\"' : \"\") + '>' + ck.label + \"<\\/\" + ck.tag + \">\"\r\n                            + '</div>';\r\n                    }\r\n                });\r\n            })(t);\r\n        }\r\n\r\n        var ui = new editorui.Combox({\r\n            editor:editor,\r\n            items:items,\r\n            title:title,\r\n            initValue:title,\r\n            className:'edui-for-customstyle',\r\n            onselect:function (t, index) {\r\n                editor.execCommand('customstyle', this.items[index].value);\r\n            },\r\n            onbuttonclick:function () {\r\n                this.showPopup();\r\n            },\r\n            indexByValue:function (value) {\r\n                for (var i = 0, ti; ti = this.items[i++];) {\r\n                    if (ti.label == value) {\r\n                        return i - 1\r\n                    }\r\n                }\r\n                return -1;\r\n            }\r\n        });\r\n        editorui.buttons['customstyle'] = ui;\r\n        editor.addListener('selectionchange', function (type, causeByUi, uiReady) {\r\n            if (!uiReady) {\r\n                var state = editor.queryCommandState('customstyle');\r\n                if (state == -1) {\r\n                    ui.setDisabled(true);\r\n                } else {\r\n                    ui.setDisabled(false);\r\n                    var value = editor.queryCommandValue('customstyle');\r\n                    var index = ui.indexByValue(value);\r\n                    if (index != -1) {\r\n                        ui.setValue(value);\r\n                    } else {\r\n                        ui.setValue(ui.initValue);\r\n                    }\r\n                }\r\n            }\r\n\r\n        });\r\n        return ui;\r\n    };\r\n    editorui.inserttable = function (editor, iframeUrl, title) {\r\n        title = editor.options.labelMap['inserttable'] || editor.getLang(\"labelMap.inserttable\") || '';\r\n        var ui = new editorui.TableButton({\r\n            editor:editor,\r\n            title:title,\r\n            className:'edui-for-inserttable',\r\n            onpicktable:function (t, numCols, numRows) {\r\n                editor.execCommand('InsertTable', {numRows:numRows, numCols:numCols, border:1});\r\n            },\r\n            onbuttonclick:function () {\r\n                this.showPopup();\r\n            }\r\n        });\r\n        editorui.buttons['inserttable'] = ui;\r\n        editor.addListener('selectionchange', function () {\r\n            ui.setDisabled(editor.queryCommandState('inserttable') == -1);\r\n        });\r\n        return ui;\r\n    };\r\n\r\n    editorui.lineheight = function (editor) {\r\n        var val = editor.options.lineheight || [];\r\n        if (!val.length)return;\r\n        for (var i = 0, ci, items = []; ci = val[i++];) {\r\n            items.push({\r\n                //todo:写死了\r\n                label:ci,\r\n                value:ci,\r\n                theme:editor.options.theme,\r\n                onclick:function () {\r\n                    editor.execCommand(\"lineheight\", this.value);\r\n                }\r\n            })\r\n        }\r\n        var ui = new editorui.MenuButton({\r\n            editor:editor,\r\n            className:'edui-for-lineheight',\r\n            title:editor.options.labelMap['lineheight'] || editor.getLang(\"labelMap.lineheight\") || '',\r\n            items:items,\r\n            onbuttonclick:function () {\r\n                var value = editor.queryCommandValue('LineHeight') || this.value;\r\n                editor.execCommand(\"LineHeight\", value);\r\n            }\r\n        });\r\n        editorui.buttons['lineheight'] = ui;\r\n        editor.addListener('selectionchange', function () {\r\n            var state = editor.queryCommandState('LineHeight');\r\n            if (state == -1) {\r\n                ui.setDisabled(true);\r\n            } else {\r\n                ui.setDisabled(false);\r\n                var value = editor.queryCommandValue('LineHeight');\r\n                value && ui.setValue((value + '').replace(/cm/, ''));\r\n                ui.setChecked(state)\r\n            }\r\n        });\r\n        return ui;\r\n    };\r\n\r\n    var rowspacings = ['top', 'bottom'];\r\n    for (var r = 0, ri; ri = rowspacings[r++];) {\r\n        (function (cmd) {\r\n            editorui['rowspacing' + cmd] = function (editor) {\r\n                var val = editor.options['rowspacing' + cmd] || [];\r\n                if (!val.length) return null;\r\n                for (var i = 0, ci, items = []; ci = val[i++];) {\r\n                    items.push({\r\n                        label:ci,\r\n                        value:ci,\r\n                        theme:editor.options.theme,\r\n                        onclick:function () {\r\n                            editor.execCommand(\"rowspacing\", this.value, cmd);\r\n                        }\r\n                    })\r\n                }\r\n                var ui = new editorui.MenuButton({\r\n                    editor:editor,\r\n                    className:'edui-for-rowspacing' + cmd,\r\n                    title:editor.options.labelMap['rowspacing' + cmd] || editor.getLang(\"labelMap.rowspacing\" + cmd) || '',\r\n                    items:items,\r\n                    onbuttonclick:function () {\r\n                        var value = editor.queryCommandValue('rowspacing', cmd) || this.value;\r\n                        editor.execCommand(\"rowspacing\", value, cmd);\r\n                    }\r\n                });\r\n                editorui.buttons[cmd] = ui;\r\n                editor.addListener('selectionchange', function () {\r\n                    var state = editor.queryCommandState('rowspacing', cmd);\r\n                    if (state == -1) {\r\n                        ui.setDisabled(true);\r\n                    } else {\r\n                        ui.setDisabled(false);\r\n                        var value = editor.queryCommandValue('rowspacing', cmd);\r\n                        value && ui.setValue((value + '').replace(/%/, ''));\r\n                        ui.setChecked(state)\r\n                    }\r\n                });\r\n                return ui;\r\n            }\r\n        })(ri)\r\n    }\r\n    //有序，无序列表\r\n    var lists = ['insertorderedlist', 'insertunorderedlist'];\r\n    for (var l = 0, cl; cl = lists[l++];) {\r\n        (function (cmd) {\r\n            editorui[cmd] = function (editor) {\r\n                var vals = editor.options[cmd],\r\n                    _onMenuClick = function () {\r\n                        editor.execCommand(cmd, this.value);\r\n                    }, items = [];\r\n                for (var i in vals) {\r\n                    items.push({\r\n                        label:vals[i] || editor.getLang()[cmd][i] || \"\",\r\n                        value:i,\r\n                        theme:editor.options.theme,\r\n                        onclick:_onMenuClick\r\n                    })\r\n                }\r\n                var ui = new editorui.MenuButton({\r\n                    editor:editor,\r\n                    className:'edui-for-' + cmd,\r\n                    title:editor.getLang(\"labelMap.\" + cmd) || '',\r\n                    'items':items,\r\n                    onbuttonclick:function () {\r\n                        var value = editor.queryCommandValue(cmd) || this.value;\r\n                        editor.execCommand(cmd, value);\r\n                    }\r\n                });\r\n                editorui.buttons[cmd] = ui;\r\n                editor.addListener('selectionchange', function () {\r\n                    var state = editor.queryCommandState(cmd);\r\n                    if (state == -1) {\r\n                        ui.setDisabled(true);\r\n                    } else {\r\n                        ui.setDisabled(false);\r\n                        var value = editor.queryCommandValue(cmd);\r\n                        ui.setValue(value);\r\n                        ui.setChecked(state)\r\n                    }\r\n                });\r\n                return ui;\r\n            };\r\n        })(cl)\r\n    }\r\n\r\n    editorui.fullscreen = function (editor, title) {\r\n        title = editor.options.labelMap['fullscreen'] || editor.getLang(\"labelMap.fullscreen\") || '';\r\n        var ui = new editorui.Button({\r\n            className:'edui-for-fullscreen',\r\n            title:title,\r\n            theme:editor.options.theme,\r\n            onclick:function () {\r\n                if (editor.ui) {\r\n                    editor.ui.setFullScreen(!editor.ui.isFullScreen());\r\n                }\r\n                this.setChecked(editor.ui.isFullScreen());\r\n            }\r\n        });\r\n        editorui.buttons['fullscreen'] = ui;\r\n        editor.addListener('selectionchange', function () {\r\n            var state = editor.queryCommandState('fullscreen');\r\n            ui.setDisabled(state == -1);\r\n            ui.setChecked(editor.ui.isFullScreen());\r\n        });\r\n        return ui;\r\n    };\r\n\r\n    // 表情\r\n    editorui[\"emotion\"] = function (editor, iframeUrl) {\r\n        var cmd = \"emotion\";\r\n        var ui = new editorui.MultiMenuPop({\r\n            title:editor.options.labelMap[cmd] || editor.getLang(\"labelMap.\" + cmd + \"\") || '',\r\n            editor:editor,\r\n            className:'edui-for-' + cmd,\r\n            iframeUrl:editor.ui.mapUrl(iframeUrl || (editor.options.iframeUrlMap || {})[cmd] || iframeUrlMap[cmd])\r\n        });\r\n        editorui.buttons[cmd] = ui;\r\n\r\n        editor.addListener('selectionchange', function () {\r\n            ui.setDisabled(editor.queryCommandState(cmd) == -1)\r\n        });\r\n        return ui;\r\n    };\r\n\r\n    editorui.autotypeset = function (editor) {\r\n        var ui = new editorui.AutoTypeSetButton({\r\n            editor:editor,\r\n            title:editor.options.labelMap['autotypeset'] || editor.getLang(\"labelMap.autotypeset\") || '',\r\n            className:'edui-for-autotypeset',\r\n            onbuttonclick:function () {\r\n                editor.execCommand('autotypeset')\r\n            }\r\n        });\r\n        editorui.buttons['autotypeset'] = ui;\r\n        editor.addListener('selectionchange', function () {\r\n            ui.setDisabled(editor.queryCommandState('autotypeset') == -1);\r\n        });\r\n        return ui;\r\n    };\r\n\r\n    /* 简单上传插件 */\r\n    editorui[\"simpleupload\"] = function (editor) {\r\n        var name = 'simpleupload',\r\n            ui = new editorui.Button({\r\n                className:'edui-for-' + name,\r\n                title:editor.options.labelMap[name] || editor.getLang(\"labelMap.\" + name) || '',\r\n                onclick:function () {},\r\n                theme:editor.options.theme,\r\n                showText:false\r\n            });\r\n        editorui.buttons[name] = ui;\r\n        editor.addListener('ready', function() {\r\n            var b = ui.getDom('body'),\r\n                iconSpan = b.children[0];\r\n            editor.fireEvent('simpleuploadbtnready', iconSpan);\r\n        });\r\n        editor.addListener('selectionchange', function (type, causeByUi, uiReady) {\r\n            var state = editor.queryCommandState(name);\r\n            if (state == -1) {\r\n                ui.setDisabled(true);\r\n                ui.setChecked(false);\r\n            } else {\r\n                if (!uiReady) {\r\n                    ui.setDisabled(false);\r\n                    ui.setChecked(state);\r\n                }\r\n            }\r\n        });\r\n        return ui;\r\n    };\r\n\r\n})();\r\n\r\n\r\n// adapter/editor.js\r\n///import core\r\n///commands 全屏\r\n///commandsName FullScreen\r\n///commandsTitle  全屏\r\n(function () {\r\n    var utils = baidu.editor.utils,\r\n        uiUtils = baidu.editor.ui.uiUtils,\r\n        UIBase = baidu.editor.ui.UIBase,\r\n        domUtils = baidu.editor.dom.domUtils;\r\n    var nodeStack = [];\r\n\r\n    function EditorUI(options) {\r\n        this.initOptions(options);\r\n        this.initEditorUI();\r\n    }\r\n\r\n    EditorUI.prototype = {\r\n        uiName:'editor',\r\n        initEditorUI:function () {\r\n            this.editor.ui = this;\r\n            this._dialogs = {};\r\n            this.initUIBase();\r\n            this._initToolbars();\r\n            var editor = this.editor,\r\n                me = this;\r\n\r\n            editor.addListener('ready', function () {\r\n                //提供getDialog方法\r\n                editor.getDialog = function (name) {\r\n                    return editor.ui._dialogs[name + \"Dialog\"];\r\n                };\r\n                domUtils.on(editor.window, 'scroll', function (evt) {\r\n                    baidu.editor.ui.Popup.postHide(evt);\r\n                });\r\n                //提供编辑器实时宽高(全屏时宽高不变化)\r\n                editor.ui._actualFrameWidth = editor.options.initialFrameWidth;\r\n\r\n                UE.browser.ie && UE.browser.version === 6 && editor.container.ownerDocument.execCommand(\"BackgroundImageCache\", false, true);\r\n\r\n                //display bottom-bar label based on config\r\n                if (editor.options.elementPathEnabled) {\r\n                    editor.ui.getDom('elementpath').innerHTML = '<div class=\"edui-editor-breadcrumb\">' + editor.getLang(\"elementPathTip\") + ':</div>';\r\n                }\r\n                if (editor.options.wordCount) {\r\n                    function countFn() {\r\n                        setCount(editor,me);\r\n                        domUtils.un(editor.document, \"click\", arguments.callee);\r\n                    }\r\n                    domUtils.on(editor.document, \"click\", countFn);\r\n                    editor.ui.getDom('wordcount').innerHTML = editor.getLang(\"wordCountTip\");\r\n                }\r\n                editor.ui._scale();\r\n                if (editor.options.scaleEnabled) {\r\n                    if (editor.autoHeightEnabled) {\r\n                        editor.disableAutoHeight();\r\n                    }\r\n                    me.enableScale();\r\n                } else {\r\n                    me.disableScale();\r\n                }\r\n                if (!editor.options.elementPathEnabled && !editor.options.wordCount && !editor.options.scaleEnabled) {\r\n                    editor.ui.getDom('elementpath').style.display = \"none\";\r\n                    editor.ui.getDom('wordcount').style.display = \"none\";\r\n                    editor.ui.getDom('scale').style.display = \"none\";\r\n                }\r\n\r\n                if (!editor.selection.isFocus())return;\r\n                editor.fireEvent('selectionchange', false, true);\r\n\r\n\r\n            });\r\n\r\n            editor.addListener('mousedown', function (t, evt) {\r\n                var el = evt.target || evt.srcElement;\r\n                baidu.editor.ui.Popup.postHide(evt, el);\r\n                baidu.editor.ui.ShortCutMenu.postHide(evt);\r\n\r\n            });\r\n            editor.addListener(\"delcells\", function () {\r\n                if (UE.ui['edittip']) {\r\n                    new UE.ui['edittip'](editor);\r\n                }\r\n                editor.getDialog('edittip').open();\r\n            });\r\n\r\n            var pastePop, isPaste = false, timer;\r\n            editor.addListener(\"afterpaste\", function () {\r\n                if(editor.queryCommandState('pasteplain'))\r\n                    return;\r\n                if(baidu.editor.ui.PastePicker){\r\n                    pastePop = new baidu.editor.ui.Popup({\r\n                        content:new baidu.editor.ui.PastePicker({editor:editor}),\r\n                        editor:editor,\r\n                        className:'edui-wordpastepop'\r\n                    });\r\n                    pastePop.render();\r\n                }\r\n                isPaste = true;\r\n            });\r\n\r\n            editor.addListener(\"afterinserthtml\", function () {\r\n                clearTimeout(timer);\r\n                timer = setTimeout(function () {\r\n                    if (pastePop && (isPaste || editor.ui._isTransfer)) {\r\n                        if(pastePop.isHidden()){\r\n                            var span = domUtils.createElement(editor.document, 'span', {\r\n                                    'style':\"line-height:0px;\",\r\n                                    'innerHTML':'\\ufeff'\r\n                                }),\r\n                                range = editor.selection.getRange();\r\n                            range.insertNode(span);\r\n                            var tmp= getDomNode(span, 'firstChild', 'previousSibling');\r\n                            tmp && pastePop.showAnchor(tmp.nodeType == 3 ? tmp.parentNode : tmp);\r\n                            domUtils.remove(span);\r\n                        }else{\r\n                            pastePop.show();\r\n                        }\r\n                        delete editor.ui._isTransfer;\r\n                        isPaste = false;\r\n                    }\r\n                }, 200)\r\n            });\r\n            editor.addListener('contextmenu', function (t, evt) {\r\n                baidu.editor.ui.Popup.postHide(evt);\r\n            });\r\n            editor.addListener('keydown', function (t, evt) {\r\n                if (pastePop)    pastePop.dispose(evt);\r\n                var keyCode = evt.keyCode || evt.which;\r\n                if(evt.altKey&&keyCode==90){\r\n                    UE.ui.buttons['fullscreen'].onclick();\r\n                }\r\n            });\r\n            editor.addListener('wordcount', function (type) {\r\n                setCount(this,me);\r\n            });\r\n            function setCount(editor,ui) {\r\n                editor.setOpt({\r\n                    wordCount:true,\r\n                    maximumWords:10000,\r\n                    wordCountMsg:editor.options.wordCountMsg || editor.getLang(\"wordCountMsg\"),\r\n                    wordOverFlowMsg:editor.options.wordOverFlowMsg || editor.getLang(\"wordOverFlowMsg\")\r\n                });\r\n                var opt = editor.options,\r\n                    max = opt.maximumWords,\r\n                    msg = opt.wordCountMsg ,\r\n                    errMsg = opt.wordOverFlowMsg,\r\n                    countDom = ui.getDom('wordcount');\r\n                if (!opt.wordCount) {\r\n                    return;\r\n                }\r\n                var count = editor.getContentLength(true);\r\n                if (count > max) {\r\n                    countDom.innerHTML = errMsg;\r\n                    editor.fireEvent(\"wordcountoverflow\");\r\n                } else {\r\n                    countDom.innerHTML = msg.replace(\"{#leave}\", max - count).replace(\"{#count}\", count);\r\n                }\r\n            }\r\n\r\n            editor.addListener('selectionchange', function () {\r\n                if (editor.options.elementPathEnabled) {\r\n                    me[(editor.queryCommandState('elementpath') == -1 ? 'dis' : 'en') + 'ableElementPath']()\r\n                }\r\n                if (editor.options.scaleEnabled) {\r\n                    me[(editor.queryCommandState('scale') == -1 ? 'dis' : 'en') + 'ableScale']();\r\n\r\n                }\r\n            });\r\n            var popup = new baidu.editor.ui.Popup({\r\n                editor:editor,\r\n                content:'',\r\n                className:'edui-bubble',\r\n                _onEditButtonClick:function () {\r\n                    this.hide();\r\n                    editor.ui._dialogs.linkDialog.open();\r\n                },\r\n                _onImgEditButtonClick:function (name) {\r\n                    this.hide();\r\n                    editor.ui._dialogs[name] && editor.ui._dialogs[name].open();\r\n\r\n                },\r\n                _onImgSetFloat:function (value) {\r\n                    this.hide();\r\n                    editor.execCommand(\"imagefloat\", value);\r\n\r\n                },\r\n                _setIframeAlign:function (value) {\r\n                    var frame = popup.anchorEl;\r\n                    var newFrame = frame.cloneNode(true);\r\n                    switch (value) {\r\n                        case -2:\r\n                            newFrame.setAttribute(\"align\", \"\");\r\n                            break;\r\n                        case -1:\r\n                            newFrame.setAttribute(\"align\", \"left\");\r\n                            break;\r\n                        case 1:\r\n                            newFrame.setAttribute(\"align\", \"right\");\r\n                            break;\r\n                    }\r\n                    frame.parentNode.insertBefore(newFrame, frame);\r\n                    domUtils.remove(frame);\r\n                    popup.anchorEl = newFrame;\r\n                    popup.showAnchor(popup.anchorEl);\r\n                },\r\n                _updateIframe:function () {\r\n                    var frame = editor._iframe = popup.anchorEl;\r\n                    if(domUtils.hasClass(frame, 'ueditor_baidumap')) {\r\n                        editor.selection.getRange().selectNode(frame).select();\r\n                        editor.ui._dialogs.mapDialog.open();\r\n                        popup.hide();\r\n                    } else {\r\n                        editor.ui._dialogs.insertframeDialog.open();\r\n                        popup.hide();\r\n                    }\r\n                },\r\n                _onRemoveButtonClick:function (cmdName) {\r\n                    editor.execCommand(cmdName);\r\n                    this.hide();\r\n                },\r\n                queryAutoHide:function (el) {\r\n                    if (el && el.ownerDocument == editor.document) {\r\n                        if (el.tagName.toLowerCase() == 'img' || domUtils.findParentByTagName(el, 'a', true)) {\r\n                            return el !== popup.anchorEl;\r\n                        }\r\n                    }\r\n                    return baidu.editor.ui.Popup.prototype.queryAutoHide.call(this, el);\r\n                }\r\n            });\r\n            popup.render();\r\n            if (editor.options.imagePopup) {\r\n                editor.addListener('mouseover', function (t, evt) {\r\n                    evt = evt || window.event;\r\n                    var el = evt.target || evt.srcElement;\r\n                    if (editor.ui._dialogs.insertframeDialog && /iframe/ig.test(el.tagName)) {\r\n                        var html = popup.formatHtml(\r\n                            '<nobr>' + editor.getLang(\"property\") + ': <span onclick=$$._setIframeAlign(-2) class=\"edui-clickable\">' + editor.getLang(\"default\") + '</span>&nbsp;&nbsp;<span onclick=$$._setIframeAlign(-1) class=\"edui-clickable\">' + editor.getLang(\"justifyleft\") + '</span>&nbsp;&nbsp;<span onclick=$$._setIframeAlign(1) class=\"edui-clickable\">' + editor.getLang(\"justifyright\") + '</span>&nbsp;&nbsp;' +\r\n                                ' <span onclick=\"$$._updateIframe( this);\" class=\"edui-clickable\">' + editor.getLang(\"modify\") + '</span></nobr>');\r\n                        if (html) {\r\n                            popup.getDom('content').innerHTML = html;\r\n                            popup.anchorEl = el;\r\n                            popup.showAnchor(popup.anchorEl);\r\n                        } else {\r\n                            popup.hide();\r\n                        }\r\n                    }\r\n                });\r\n                editor.addListener('selectionchange', function (t, causeByUi) {\r\n                    if (!causeByUi) return;\r\n                    var html = '', str = \"\",\r\n                        img = editor.selection.getRange().getClosedNode(),\r\n                        dialogs = editor.ui._dialogs;\r\n                    if (img && img.tagName == 'IMG') {\r\n                        var dialogName = 'insertimageDialog';\r\n                        if (img.className.indexOf(\"edui-faked-video\") != -1 || img.className.indexOf(\"edui-upload-video\") != -1) {\r\n                            dialogName = \"insertvideoDialog\"\r\n                        }\r\n                        if (img.className.indexOf(\"edui-faked-webapp\") != -1) {\r\n                            dialogName = \"webappDialog\"\r\n                        }\r\n                        if (img.src.indexOf(\"http://api.map.baidu.com\") != -1) {\r\n                            dialogName = \"mapDialog\"\r\n                        }\r\n                        if (img.className.indexOf(\"edui-faked-music\") != -1) {\r\n                            dialogName = \"musicDialog\"\r\n                        }\r\n                        if (img.src.indexOf(\"http://maps.google.com/maps/api/staticmap\") != -1) {\r\n                            dialogName = \"gmapDialog\"\r\n                        }\r\n                        if (img.getAttribute(\"anchorname\")) {\r\n                            dialogName = \"anchorDialog\";\r\n                            html = popup.formatHtml(\r\n                                '<nobr>' + editor.getLang(\"property\") + ': <span onclick=$$._onImgEditButtonClick(\"anchorDialog\") class=\"edui-clickable\">' + editor.getLang(\"modify\") + '</span>&nbsp;&nbsp;' +\r\n                                    '<span onclick=$$._onRemoveButtonClick(\\'anchor\\') class=\"edui-clickable\">' + editor.getLang(\"delete\") + '</span></nobr>');\r\n                        }\r\n                        if (img.getAttribute(\"word_img\")) {\r\n                            //todo 放到dialog去做查询\r\n                            editor.word_img = [img.getAttribute(\"word_img\")];\r\n                            dialogName = \"wordimageDialog\"\r\n                        }\r\n                        if(domUtils.hasClass(img, 'loadingclass') || domUtils.hasClass(img, 'loaderrorclass')) {\r\n                            dialogName = \"\";\r\n                        }\r\n                        if (!dialogs[dialogName]) {\r\n                            return;\r\n                        }\r\n                        str = '<nobr>' + editor.getLang(\"property\") + ': '+\r\n                            '<span onclick=$$._onImgSetFloat(\"none\") class=\"edui-clickable\">' + editor.getLang(\"default\") + '</span>&nbsp;&nbsp;' +\r\n                            '<span onclick=$$._onImgSetFloat(\"left\") class=\"edui-clickable\">' + editor.getLang(\"justifyleft\") + '</span>&nbsp;&nbsp;' +\r\n                            '<span onclick=$$._onImgSetFloat(\"right\") class=\"edui-clickable\">' + editor.getLang(\"justifyright\") + '</span>&nbsp;&nbsp;' +\r\n                            '<span onclick=$$._onImgSetFloat(\"center\") class=\"edui-clickable\">' + editor.getLang(\"justifycenter\") + '</span>&nbsp;&nbsp;'+\r\n                            '<span onclick=\"$$._onImgEditButtonClick(\\'' + dialogName + '\\');\" class=\"edui-clickable\">' + editor.getLang(\"modify\") + '</span></nobr>';\r\n\r\n                        !html && (html = popup.formatHtml(str))\r\n\r\n                    }\r\n                    if (editor.ui._dialogs.linkDialog) {\r\n                        var link = editor.queryCommandValue('link');\r\n                        var url;\r\n                        if (link && (url = (link.getAttribute('_href') || link.getAttribute('href', 2)))) {\r\n                            var txt = url;\r\n                            if (url.length > 30) {\r\n                                txt = url.substring(0, 20) + \"...\";\r\n                            }\r\n                            if (html) {\r\n                                html += '<div style=\"height:5px;\"></div>'\r\n                            }\r\n                            html += popup.formatHtml(\r\n                                '<nobr>' + editor.getLang(\"anthorMsg\") + ': <a target=\"_blank\" href=\"' + url + '\" title=\"' + url + '\" >' + txt + '</a>' +\r\n                                    ' <span class=\"edui-clickable\" onclick=\"$$._onEditButtonClick();\">' + editor.getLang(\"modify\") + '</span>' +\r\n                                    ' <span class=\"edui-clickable\" onclick=\"$$._onRemoveButtonClick(\\'unlink\\');\"> ' + editor.getLang(\"clear\") + '</span></nobr>');\r\n                            popup.showAnchor(link);\r\n                        }\r\n                    }\r\n\r\n                    if (html) {\r\n                        popup.getDom('content').innerHTML = html;\r\n                        popup.anchorEl = img || link;\r\n                        popup.showAnchor(popup.anchorEl);\r\n                    } else {\r\n                        popup.hide();\r\n                    }\r\n                });\r\n            }\r\n\r\n        },\r\n        _initToolbars:function () {\r\n            var editor = this.editor;\r\n            var toolbars = this.toolbars || [];\r\n            var toolbarUis = [];\r\n            for (var i = 0; i < toolbars.length; i++) {\r\n                var toolbar = toolbars[i];\r\n                var toolbarUi = new baidu.editor.ui.Toolbar({theme:editor.options.theme});\r\n                for (var j = 0; j < toolbar.length; j++) {\r\n                    var toolbarItem = toolbar[j];\r\n                    var toolbarItemUi = null;\r\n                    if (typeof toolbarItem == 'string') {\r\n                        toolbarItem = toolbarItem.toLowerCase();\r\n                        if (toolbarItem == '|') {\r\n                            toolbarItem = 'Separator';\r\n                        }\r\n                        if(toolbarItem == '||'){\r\n                            toolbarItem = 'Breakline';\r\n                        }\r\n                        if (baidu.editor.ui[toolbarItem]) {\r\n                            toolbarItemUi = new baidu.editor.ui[toolbarItem](editor);\r\n                        }\r\n\r\n                        //fullscreen这里单独处理一下，放到首行去\r\n                        if (toolbarItem == 'fullscreen') {\r\n                            if (toolbarUis && toolbarUis[0]) {\r\n                                toolbarUis[0].items.splice(0, 0, toolbarItemUi);\r\n                            } else {\r\n                                toolbarItemUi && toolbarUi.items.splice(0, 0, toolbarItemUi);\r\n                            }\r\n\r\n                            continue;\r\n\r\n\r\n                        }\r\n                    } else {\r\n                        toolbarItemUi = toolbarItem;\r\n                    }\r\n                    if (toolbarItemUi && toolbarItemUi.id) {\r\n\r\n                        toolbarUi.add(toolbarItemUi);\r\n                    }\r\n                }\r\n                toolbarUis[i] = toolbarUi;\r\n            }\r\n\r\n            //接受外部定制的UI（修复因 utils.each 无法准确的循环出对象的全部元素而导致的自定义 UI 不符合预期的 BUG by HaoChuan9421）\r\n\r\n            // utils.each(UE._customizeUI,function(obj,key){\r\n            //     var itemUI,index;\r\n            //     if(obj.id && obj.id != editor.key){\r\n            //        return false;\r\n            //     }\r\n            //     itemUI = obj.execFn.call(editor,editor,key);\r\n            //     if(itemUI){\r\n            //         index = obj.index;\r\n            //         if(index === undefined){\r\n            //             index = toolbarUi.items.length;\r\n            //         }\r\n            //         toolbarUi.add(itemUI,index)\r\n            //     }\r\n            // });\r\n\r\n            \r\n            for(var key in UE._customizeUI){\r\n                var obj = UE._customizeUI[key]\r\n                var itemUI,index;\r\n                if(!obj.id || obj.id == editor.key){\r\n                    itemUI = obj.execFn.call(editor,editor,key);\r\n                    if(itemUI){\r\n                        index = obj.index;\r\n                        if(index === undefined){\r\n                            index = toolbarUi.items.length;\r\n                        }\r\n                        toolbarUi.add(itemUI,index)\r\n                    }\r\n                }\r\n            }\r\n\r\n            this.toolbars = toolbarUis;\r\n        },\r\n        getHtmlTpl:function () {\r\n            return '<div id=\"##\" class=\"%%\">' +\r\n                '<div id=\"##_toolbarbox\" class=\"%%-toolbarbox\">' +\r\n                (this.toolbars.length ?\r\n                    '<div id=\"##_toolbarboxouter\" class=\"%%-toolbarboxouter\"><div class=\"%%-toolbarboxinner\">' +\r\n                        this.renderToolbarBoxHtml() +\r\n                        '</div></div>' : '') +\r\n                '<div id=\"##_toolbarmsg\" class=\"%%-toolbarmsg\" style=\"display:none;\">' +\r\n                '<div id = \"##_upload_dialog\" class=\"%%-toolbarmsg-upload\" onclick=\"$$.showWordImageDialog();\">' + this.editor.getLang(\"clickToUpload\") + '</div>' +\r\n                '<div class=\"%%-toolbarmsg-close\" onclick=\"$$.hideToolbarMsg();\">x</div>' +\r\n                '<div id=\"##_toolbarmsg_label\" class=\"%%-toolbarmsg-label\"></div>' +\r\n                '<div style=\"height:0;overflow:hidden;clear:both;\"></div>' +\r\n                '</div>' +\r\n                '<div id=\"##_message_holder\" class=\"%%-messageholder\"></div>' +\r\n                '</div>' +\r\n                '<div id=\"##_iframeholder\" class=\"%%-iframeholder\">' +\r\n                '</div>' +\r\n                //modify wdcount by matao\r\n                '<div id=\"##_bottombar\" class=\"%%-bottomContainer\"><table><tr>' +\r\n                '<td id=\"##_elementpath\" class=\"%%-bottombar\"></td>' +\r\n                '<td id=\"##_wordcount\" class=\"%%-wordcount\"></td>' +\r\n                '<td id=\"##_scale\" class=\"%%-scale\"><div class=\"%%-icon\"></div></td>' +\r\n                '</tr></table></div>' +\r\n                '<div id=\"##_scalelayer\"></div>' +\r\n                '</div>';\r\n        },\r\n        showWordImageDialog:function () {\r\n            this._dialogs['wordimageDialog'].open();\r\n        },\r\n        renderToolbarBoxHtml:function () {\r\n            var buff = [];\r\n            for (var i = 0; i < this.toolbars.length; i++) {\r\n                buff.push(this.toolbars[i].renderHtml());\r\n            }\r\n            return buff.join('');\r\n        },\r\n        setFullScreen:function (fullscreen) {\r\n\r\n            var editor = this.editor,\r\n                container = editor.container.parentNode.parentNode;\r\n            if (this._fullscreen != fullscreen) {\r\n                this._fullscreen = fullscreen;\r\n                this.editor.fireEvent('beforefullscreenchange', fullscreen);\r\n                if (baidu.editor.browser.gecko) {\r\n                    var bk = editor.selection.getRange().createBookmark();\r\n                }\r\n                if (fullscreen) {\r\n                    while (container.tagName != \"BODY\") {\r\n                        var position = baidu.editor.dom.domUtils.getComputedStyle(container, \"position\");\r\n                        nodeStack.push(position);\r\n                        container.style.position = \"static\";\r\n                        container = container.parentNode;\r\n                    }\r\n                    this._bakHtmlOverflow = document.documentElement.style.overflow;\r\n                    this._bakBodyOverflow = document.body.style.overflow;\r\n                    this._bakAutoHeight = this.editor.autoHeightEnabled;\r\n                    this._bakScrollTop = Math.max(document.documentElement.scrollTop, document.body.scrollTop);\r\n\r\n                    this._bakEditorContaninerWidth = editor.iframe.parentNode.offsetWidth;\r\n                    if (this._bakAutoHeight) {\r\n                        //当全屏时不能执行自动长高\r\n                        editor.autoHeightEnabled = false;\r\n                        this.editor.disableAutoHeight();\r\n                    }\r\n\r\n                    document.documentElement.style.overflow = 'hidden';\r\n                    //修复，滚动条不收起的问题\r\n\r\n                    window.scrollTo(0,window.scrollY);\r\n                    this._bakCssText = this.getDom().style.cssText;\r\n                    this._bakCssText1 = this.getDom('iframeholder').style.cssText;\r\n                    editor.iframe.parentNode.style.width = '';\r\n                    this._updateFullScreen();\r\n                } else {\r\n                    while (container.tagName != \"BODY\") {\r\n                        container.style.position = nodeStack.shift();\r\n                        container = container.parentNode;\r\n                    }\r\n                    this.getDom().style.cssText = this._bakCssText;\r\n                    this.getDom('iframeholder').style.cssText = this._bakCssText1;\r\n                    if (this._bakAutoHeight) {\r\n                        editor.autoHeightEnabled = true;\r\n                        this.editor.enableAutoHeight();\r\n                    }\r\n\r\n                    document.documentElement.style.overflow = this._bakHtmlOverflow;\r\n                    document.body.style.overflow = this._bakBodyOverflow;\r\n                    editor.iframe.parentNode.style.width = this._bakEditorContaninerWidth + 'px';\r\n                    window.scrollTo(0, this._bakScrollTop);\r\n                }\r\n                if (browser.gecko && editor.body.contentEditable === 'true') {\r\n                    var input = document.createElement('input');\r\n                    document.body.appendChild(input);\r\n                    editor.body.contentEditable = false;\r\n                    setTimeout(function () {\r\n                        input.focus();\r\n                        setTimeout(function () {\r\n                            editor.body.contentEditable = true;\r\n                            editor.fireEvent('fullscreenchanged', fullscreen);\r\n                            editor.selection.getRange().moveToBookmark(bk).select(true);\r\n                            baidu.editor.dom.domUtils.remove(input);\r\n                            fullscreen && window.scroll(0, 0);\r\n                        }, 0)\r\n                    }, 0)\r\n                }\r\n\r\n                if(editor.body.contentEditable === 'true'){\r\n                    this.editor.fireEvent('fullscreenchanged', fullscreen);\r\n                    this.triggerLayout();\r\n                }\r\n\r\n            }\r\n        },\r\n        _updateFullScreen:function () {\r\n            if (this._fullscreen) {\r\n                var vpRect = uiUtils.getViewportRect();\r\n                this.getDom().style.cssText = 'border:0;position:absolute;left:0;top:' + (this.editor.options.topOffset || 0) + 'px;width:' + vpRect.width + 'px;height:' + vpRect.height + 'px;z-index:' + (this.getDom().style.zIndex * 1 + 100);\r\n                uiUtils.setViewportOffset(this.getDom(), { left:0, top:this.editor.options.topOffset || 0 });\r\n                this.editor.setHeight(vpRect.height - this.getDom('toolbarbox').offsetHeight - this.getDom('bottombar').offsetHeight - (this.editor.options.topOffset || 0),true);\r\n                //不手动调一下，会导致全屏失效\r\n                if(browser.gecko){\r\n                    try{\r\n                        window.onresize();\r\n                    }catch(e){\r\n\r\n                    }\r\n\r\n                }\r\n            }\r\n        },\r\n        _updateElementPath:function () {\r\n            var bottom = this.getDom('elementpath'), list;\r\n            if (this.elementPathEnabled && (list = this.editor.queryCommandValue('elementpath'))) {\r\n\r\n                var buff = [];\r\n                for (var i = 0, ci; ci = list[i]; i++) {\r\n                    buff[i] = this.formatHtml('<span unselectable=\"on\" onclick=\"$$.editor.execCommand(&quot;elementpath&quot;, &quot;' + i + '&quot;);\">' + ci + '</span>');\r\n                }\r\n                bottom.innerHTML = '<div class=\"edui-editor-breadcrumb\" onmousedown=\"return false;\">' + this.editor.getLang(\"elementPathTip\") + ': ' + buff.join(' &gt; ') + '</div>';\r\n\r\n            } else {\r\n                bottom.style.display = 'none'\r\n            }\r\n        },\r\n        disableElementPath:function () {\r\n            var bottom = this.getDom('elementpath');\r\n            bottom.innerHTML = '';\r\n            bottom.style.display = 'none';\r\n            this.elementPathEnabled = false;\r\n\r\n        },\r\n        enableElementPath:function () {\r\n            var bottom = this.getDom('elementpath');\r\n            bottom.style.display = '';\r\n            this.elementPathEnabled = true;\r\n            this._updateElementPath();\r\n        },\r\n        _scale:function () {\r\n            var doc = document,\r\n                editor = this.editor,\r\n                editorHolder = editor.container,\r\n                editorDocument = editor.document,\r\n                toolbarBox = this.getDom(\"toolbarbox\"),\r\n                bottombar = this.getDom(\"bottombar\"),\r\n                scale = this.getDom(\"scale\"),\r\n                scalelayer = this.getDom(\"scalelayer\");\r\n\r\n            var isMouseMove = false,\r\n                position = null,\r\n                minEditorHeight = 0,\r\n                minEditorWidth = editor.options.minFrameWidth,\r\n                pageX = 0,\r\n                pageY = 0,\r\n                scaleWidth = 0,\r\n                scaleHeight = 0;\r\n\r\n            function down() {\r\n                position = domUtils.getXY(editorHolder);\r\n\r\n                if (!minEditorHeight) {\r\n                    minEditorHeight = editor.options.minFrameHeight + toolbarBox.offsetHeight + bottombar.offsetHeight;\r\n                }\r\n\r\n                scalelayer.style.cssText = \"position:absolute;left:0;display:;top:0;background-color:#41ABFF;opacity:0.4;filter: Alpha(opacity=40);width:\" + editorHolder.offsetWidth + \"px;height:\"\r\n                    + editorHolder.offsetHeight + \"px;z-index:\" + (editor.options.zIndex + 1);\r\n\r\n                domUtils.on(doc, \"mousemove\", move);\r\n                domUtils.on(editorDocument, \"mouseup\", up);\r\n                domUtils.on(doc, \"mouseup\", up);\r\n            }\r\n\r\n            var me = this;\r\n            //by xuheng 全屏时关掉缩放\r\n            this.editor.addListener('fullscreenchanged', function (e, fullScreen) {\r\n                if (fullScreen) {\r\n                    me.disableScale();\r\n\r\n                } else {\r\n                    if (me.editor.options.scaleEnabled) {\r\n                        me.enableScale();\r\n                        var tmpNode = me.editor.document.createElement('span');\r\n                        me.editor.body.appendChild(tmpNode);\r\n                        me.editor.body.style.height = Math.max(domUtils.getXY(tmpNode).y, me.editor.iframe.offsetHeight - 20) + 'px';\r\n                        domUtils.remove(tmpNode)\r\n                    }\r\n                }\r\n            });\r\n            function move(event) {\r\n                clearSelection();\r\n                var e = event || window.event;\r\n                pageX = e.pageX || (doc.documentElement.scrollLeft + e.clientX);\r\n                pageY = e.pageY || (doc.documentElement.scrollTop + e.clientY);\r\n                scaleWidth = pageX - position.x;\r\n                scaleHeight = pageY - position.y;\r\n\r\n                if (scaleWidth >= minEditorWidth) {\r\n                    isMouseMove = true;\r\n                    scalelayer.style.width = scaleWidth + 'px';\r\n                }\r\n                if (scaleHeight >= minEditorHeight) {\r\n                    isMouseMove = true;\r\n                    scalelayer.style.height = scaleHeight + \"px\";\r\n                }\r\n            }\r\n\r\n            function up() {\r\n                if (isMouseMove) {\r\n                    isMouseMove = false;\r\n                    editor.ui._actualFrameWidth = scalelayer.offsetWidth - 2;\r\n                    editorHolder.style.width = editor.ui._actualFrameWidth + 'px';\r\n\r\n                    editor.setHeight(scalelayer.offsetHeight - bottombar.offsetHeight - toolbarBox.offsetHeight - 2,true);\r\n                }\r\n                if (scalelayer) {\r\n                    scalelayer.style.display = \"none\";\r\n                }\r\n                clearSelection();\r\n                domUtils.un(doc, \"mousemove\", move);\r\n                domUtils.un(editorDocument, \"mouseup\", up);\r\n                domUtils.un(doc, \"mouseup\", up);\r\n            }\r\n\r\n            function clearSelection() {\r\n                if (browser.ie)\r\n                    doc.selection.clear();\r\n                else\r\n                    window.getSelection().removeAllRanges();\r\n            }\r\n\r\n            this.enableScale = function () {\r\n                //trace:2868\r\n                if (editor.queryCommandState(\"source\") == 1)    return;\r\n                scale.style.display = \"\";\r\n                this.scaleEnabled = true;\r\n                domUtils.on(scale, \"mousedown\", down);\r\n            };\r\n            this.disableScale = function () {\r\n                scale.style.display = \"none\";\r\n                this.scaleEnabled = false;\r\n                domUtils.un(scale, \"mousedown\", down);\r\n            };\r\n        },\r\n        isFullScreen:function () {\r\n            return this._fullscreen;\r\n        },\r\n        postRender:function () {\r\n            UIBase.prototype.postRender.call(this);\r\n            for (var i = 0; i < this.toolbars.length; i++) {\r\n                this.toolbars[i].postRender();\r\n            }\r\n            var me = this;\r\n            var timerId,\r\n                domUtils = baidu.editor.dom.domUtils,\r\n                updateFullScreenTime = function () {\r\n                    clearTimeout(timerId);\r\n                    timerId = setTimeout(function () {\r\n                        me._updateFullScreen();\r\n                    });\r\n                };\r\n            domUtils.on(window, 'resize', updateFullScreenTime);\r\n\r\n            me.addListener('destroy', function () {\r\n                domUtils.un(window, 'resize', updateFullScreenTime);\r\n                clearTimeout(timerId);\r\n            })\r\n        },\r\n        showToolbarMsg:function (msg, flag) {\r\n            this.getDom('toolbarmsg_label').innerHTML = msg;\r\n            this.getDom('toolbarmsg').style.display = '';\r\n            //\r\n            if (!flag) {\r\n                var w = this.getDom('upload_dialog');\r\n                w.style.display = 'none';\r\n            }\r\n        },\r\n        hideToolbarMsg:function () {\r\n            this.getDom('toolbarmsg').style.display = 'none';\r\n        },\r\n        mapUrl:function (url) {\r\n            return url ? url.replace('~/', this.editor.options.UEDITOR_HOME_URL || '') : ''\r\n        },\r\n        triggerLayout:function () {\r\n            var dom = this.getDom();\r\n            if (dom.style.zoom == '1') {\r\n                dom.style.zoom = '100%';\r\n            } else {\r\n                dom.style.zoom = '1';\r\n            }\r\n        }\r\n    };\r\n    utils.inherits(EditorUI, baidu.editor.ui.UIBase);\r\n\r\n\r\n    var instances = {};\r\n\r\n\r\n    UE.ui.Editor = function (options) {\r\n        var editor = new UE.Editor(options);\r\n        editor.options.editor = editor;\r\n        utils.loadFile(document, {\r\n            href:editor.options.themePath + editor.options.theme + \"/css/ueditor.css\",\r\n            tag:\"link\",\r\n            type:\"text/css\",\r\n            rel:\"stylesheet\"\r\n        });\r\n\r\n        var oldRender = editor.render;\r\n        editor.render = function (holder) {\r\n            if (holder.constructor === String) {\r\n                editor.key = holder;\r\n                instances[holder] = editor;\r\n            }\r\n            utils.domReady(function () {\r\n                editor.langIsReady ? renderUI() : editor.addListener(\"langReady\", renderUI);\r\n                function renderUI() {\r\n                    editor.setOpt({\r\n                        labelMap:editor.options.labelMap || editor.getLang('labelMap')\r\n                    });\r\n                    new EditorUI(editor.options);\r\n                    if (holder) {\r\n                        if (holder.constructor === String) {\r\n                            holder = document.getElementById(holder);\r\n                        }\r\n                        holder && holder.getAttribute('name') && ( editor.options.textarea = holder.getAttribute('name'));\r\n                        if (holder && /script|textarea/ig.test(holder.tagName)) {\r\n                            var newDiv = document.createElement('div');\r\n                            holder.parentNode.insertBefore(newDiv, holder);\r\n                            var cont = holder.value || holder.innerHTML;\r\n                            editor.options.initialContent = /^[\\t\\r\\n ]*$/.test(cont) ? editor.options.initialContent :\r\n                                cont.replace(/>[\\n\\r\\t]+([ ]{4})+/g, '>')\r\n                                    .replace(/[\\n\\r\\t]+([ ]{4})+</g, '<')\r\n                                    .replace(/>[\\n\\r\\t]+</g, '><');\r\n                            holder.className && (newDiv.className = holder.className);\r\n                            holder.style.cssText && (newDiv.style.cssText = holder.style.cssText);\r\n                            if (/textarea/i.test(holder.tagName)) {\r\n                                editor.textarea = holder;\r\n                                editor.textarea.style.display = 'none';\r\n\r\n\r\n                            } else {\r\n                                holder.parentNode.removeChild(holder);\r\n\r\n\r\n                            }\r\n                            if(holder.id){\r\n                                newDiv.id = holder.id;\r\n                                domUtils.removeAttributes(holder,'id');\r\n                            }\r\n                            holder = newDiv;\r\n                            holder.innerHTML = '';\r\n                        }\r\n\r\n                    }\r\n                    domUtils.addClass(holder, \"edui-\" + editor.options.theme);\r\n                    editor.ui.render(holder);\r\n                    var opt = editor.options;\r\n                    //给实例添加一个编辑器的容器引用\r\n                    editor.container = editor.ui.getDom();\r\n                    var parents = domUtils.findParents(holder,true);\r\n                    var displays = [];\r\n                    for(var i = 0 ,ci;ci=parents[i];i++){\r\n                        displays[i] = ci.style.display;\r\n                        ci.style.display = 'block'\r\n                    }\r\n                    if (opt.initialFrameWidth) {\r\n                        opt.minFrameWidth = opt.initialFrameWidth;\r\n                    } else {\r\n                        opt.minFrameWidth = opt.initialFrameWidth = holder.offsetWidth;\r\n                        var styleWidth = holder.style.width;\r\n                        if(/%$/.test(styleWidth)) {\r\n                            opt.initialFrameWidth = styleWidth;\r\n                        }\r\n                    }\r\n                    if (opt.initialFrameHeight) {\r\n                        opt.minFrameHeight = opt.initialFrameHeight;\r\n                    } else {\r\n                        opt.initialFrameHeight = opt.minFrameHeight = holder.offsetHeight;\r\n                    }\r\n                    for(var i = 0 ,ci;ci=parents[i];i++){\r\n                        ci.style.display =  displays[i]\r\n                    }\r\n                    //编辑器最外容器设置了高度，会导致，编辑器不占位\r\n                    //todo 先去掉，没有找到原因\r\n                    if(holder.style.height){\r\n                        holder.style.height = ''\r\n                    }\r\n                    editor.container.style.width = opt.initialFrameWidth + (/%$/.test(opt.initialFrameWidth) ? '' : 'px');\r\n                    editor.container.style.zIndex = opt.zIndex;\r\n                    oldRender.call(editor, editor.ui.getDom('iframeholder'));\r\n                    editor.fireEvent(\"afteruiready\");\r\n                }\r\n            })\r\n        };\r\n        return editor;\r\n    };\r\n\r\n\r\n    /**\r\n     * @file\r\n     * @name UE\r\n     * @short UE\r\n     * @desc UEditor的顶部命名空间\r\n     */\r\n    /**\r\n     * @name getEditor\r\n     * @since 1.2.4+\r\n     * @grammar UE.getEditor(id,[opt])  =>  Editor实例\r\n     * @desc 提供一个全局的方法得到编辑器实例\r\n     *\r\n     * * ''id''  放置编辑器的容器id, 如果容器下的编辑器已经存在，就直接返回\r\n     * * ''opt'' 编辑器的可选参数\r\n     * @example\r\n     *  UE.getEditor('containerId',{onready:function(){//创建一个编辑器实例\r\n     *      this.setContent('hello')\r\n     *  }});\r\n     *  UE.getEditor('containerId'); //返回刚创建的实例\r\n     *\r\n     */\r\n    UE.getEditor = function (id, opt) {\r\n        var editor = instances[id];\r\n        if (!editor) {\r\n            editor = instances[id] = new UE.ui.Editor(opt);\r\n            editor.render(id);\r\n        }\r\n        return editor;\r\n    };\r\n\r\n\r\n    UE.delEditor = function (id) {\r\n        var editor;\r\n        if (editor = instances[id]) {\r\n            editor.key && editor.destroy();\r\n            delete instances[id]\r\n        }\r\n    };\r\n\r\n    UE.registerUI = function(uiName,fn,index,editorId){\r\n        utils.each(uiName.split(/\\s+/), function (name) {\r\n            UE._customizeUI[name] = {\r\n                id : editorId,\r\n                execFn:fn,\r\n                index:index\r\n            };\r\n        })\r\n\r\n    }\r\n\r\n})();\r\n\r\n// adapter/message.js\r\nUE.registerUI('message', function(editor) {\r\n\r\n    var editorui = baidu.editor.ui;\r\n    var Message = editorui.Message;\r\n    var holder;\r\n    var _messageItems = [];\r\n    var me = editor;\r\n\r\n    me.addListener('ready', function(){\r\n        holder = document.getElementById(me.ui.id + '_message_holder');\r\n        updateHolderPos();\r\n        // HaoChuan9421\r\n        // setTimeout(function(){\r\n        //     updateHolderPos();\r\n        // }, 500);\r\n    });\r\n\r\n    me.addListener('showmessage', function(type, opt){\r\n        opt = utils.isString(opt) ? {\r\n            'content': opt\r\n        } : opt;\r\n        var message = new Message({\r\n                'timeout': opt.timeout,\r\n                'type': opt.type,\r\n                'content': opt.content,\r\n                'keepshow': opt.keepshow,\r\n                'editor': me\r\n            }),\r\n            mid = opt.id || ('msg_' + (+new Date()).toString(36));\r\n        message.render(holder);\r\n        _messageItems[mid] = message;\r\n        message.reset(opt);\r\n        updateHolderPos();\r\n        return mid;\r\n    });\r\n\r\n    me.addListener('updatemessage',function(type, id, opt){\r\n        opt = utils.isString(opt) ? {\r\n            'content': opt\r\n        } : opt;\r\n        var message = _messageItems[id];\r\n        message.render(holder);\r\n        message && message.reset(opt);\r\n    });\r\n\r\n    me.addListener('hidemessage',function(type, id){\r\n        var message = _messageItems[id];\r\n        message && message.hide();\r\n    });\r\n\r\n    function updateHolderPos(){\r\n        var toolbarbox = me.ui.getDom('toolbarbox');\r\n        if (toolbarbox) {\r\n            holder.style.top = toolbarbox.offsetHeight + 3 + 'px';\r\n        }\r\n        holder.style.zIndex = Math.max(me.options.zIndex, me.iframe.style.zIndex) + 1;\r\n    }\r\n\r\n});\r\n\r\n\r\n// adapter/autosave.js\r\nUE.registerUI('autosave', function(editor) {\r\n    var timer = null,uid = null;\r\n    editor.on('afterautosave',function(){\r\n        clearTimeout(timer);\r\n\r\n        timer = setTimeout(function(){\r\n            if(uid){\r\n                editor.trigger('hidemessage',uid);\r\n            }\r\n            uid = editor.trigger('showmessage',{\r\n                content : editor.getLang('autosave.success'),\r\n                timeout : 2000\r\n            });\r\n\r\n        },2000)\r\n    })\r\n\r\n});\r\n\r\n\r\n\r\n})();\r\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/ueditor.config.js",
    "content": "/**\n * ueditor完整配置项\n * 可以在这里配置整个编辑器的特性\n */\n/**************************提示********************************\n * 所有被注释的配置项均为UEditor默认值。\n * 修改默认配置请首先确保已经完全明确该参数的真实用途。\n * 主要有两种修改方案，一种是取消此处注释，然后修改成对应参数；另一种是在实例化编辑器时传入对应参数。\n * 当升级编辑器时，可直接使用旧版配置文件替换新版配置文件,不用担心旧版配置文件中因缺少新功能所需的参数而导致脚本报错。\n **************************提示********************************/\n\n(function () {\n\n    /**\n     * 编辑器资源文件根路径。它所表示的含义是：以编辑器实例化页面为当前路径，指向编辑器资源文件（即dialog等文件夹）的路径。\n     * 鉴于很多同学在使用编辑器的时候出现的种种路径问题，此处强烈建议大家使用\"相对于网站根目录的相对路径\"进行配置。\n     * \"相对于网站根目录的相对路径\"也就是以斜杠开头的形如\"/myProject/ueditor/\"这样的路径。\n     * 如果站点中有多个不在同一层级的页面需要实例化编辑器，且引用了同一UEditor的时候，此处的URL可能不适用于每个页面的编辑器。\n     * 因此，UEditor提供了针对不同页面的编辑器可单独配置的根路径，具体来说，在需要实例化编辑器的页面最顶部写上如下代码即可。当然，需要令此处的URL等于对应的配置。\n     * window.UEDITOR_HOME_URL = \"/xxxx/xxxx/\";\n     */\n    var URL = window.UEDITOR_HOME_URL || getUEBasePath();\n\n    /**\n     * 配置项主体。注意，此处所有涉及到路径的配置别遗漏URL变量。\n     */\n    window.UEDITOR_CONFIG = {\n\n        //为编辑器实例添加一个路径，这个不能被注释\n        UEDITOR_HOME_URL: URL\n\n        // 服务器统一请求接口路径\n        , serverUrl: URL + \"php/controller.php\"\n\n        //工具栏上的所有的功能按钮和下拉框，可以在new编辑器的实例时选择自己需要的重新定义\n        , toolbars: [[\n            'fullscreen', 'source', '|', 'undo', 'redo', '|',\n            'bold', 'italic', 'underline', 'fontborder', 'strikethrough', 'superscript', 'subscript', 'removeformat', 'formatmatch', 'autotypeset', 'blockquote', 'pasteplain', '|', 'forecolor', 'backcolor', 'insertorderedlist', 'insertunorderedlist', 'selectall', 'cleardoc', '|',\n            'rowspacingtop', 'rowspacingbottom', 'lineheight', '|',\n            'customstyle', 'paragraph', 'fontfamily', 'fontsize', '|',\n            'directionalityltr', 'directionalityrtl', 'indent', '|',\n            'justifyleft', 'justifycenter', 'justifyright', 'justifyjustify', '|', 'touppercase', 'tolowercase', '|',\n            'link', 'unlink', 'anchor', '|', 'imagenone', 'imageleft', 'imageright', 'imagecenter', '|',\n            'simpleupload', 'insertimage', 'emotion', 'scrawl', 'insertvideo', 'music', 'attachment', 'map', 'gmap', 'insertframe', 'insertcode', 'webapp', 'pagebreak', 'template', 'background', '|',\n            'horizontal', 'date', 'time', 'spechars', 'snapscreen', 'wordimage', '|',\n            'inserttable', 'deletetable', 'insertparagraphbeforetable', 'insertrow', 'deleterow', 'insertcol', 'deletecol', 'mergecells', 'mergeright', 'mergedown', 'splittocells', 'splittorows', 'splittocols', 'charts', '|',\n            'print', 'preview', 'searchreplace', 'drafts', 'help'\n        ]]\n        //当鼠标放在工具栏上时显示的tooltip提示,留空支持自动多语言配置，否则以配置值为准\n        //,labelMap:{\n        //    'anchor':'', 'undo':''\n        //}\n\n        //语言配置项,默认是zh-cn。有需要的话也可以使用如下这样的方式来自动多语言切换，当然，前提条件是lang文件夹下存在对应的语言文件：\n        //lang值也可以通过自动获取 (navigator.language||navigator.browserLanguage ||navigator.userLanguage).toLowerCase()\n        //,lang:\"zh-cn\"\n        //,langPath:URL +\"lang/\"\n\n        //主题配置项,默认是default。有需要的话也可以使用如下这样的方式来自动多主题切换，当然，前提条件是themes文件夹下存在对应的主题文件：\n        //现有如下皮肤:default\n        //,theme:'default'\n        //,themePath:URL +\"themes/\"\n\n        //,zIndex : 900     //编辑器层级的基数,默认是900\n\n        //针对getAllHtml方法，会在对应的head标签中增加该编码设置。\n        //,charset:\"utf-8\"\n\n        //若实例化编辑器的页面手动修改的domain，此处需要设置为true\n        //,customDomain:false\n\n        //常用配置项目\n        //,isShow : true    //默认显示编辑器\n\n        //,textarea:'editorValue' // 提交表单时，服务器获取编辑器提交内容的所用的参数，多实例时可以给容器name属性，会将name给定的值最为每个实例的键值，不用每次实例化的时候都设置这个值\n\n        //,initialContent:'欢迎使用ueditor!'    //初始化编辑器的内容,也可以通过textarea/script给值，看官网例子\n\n        //,autoClearinitialContent:true //是否自动清除编辑器初始内容，注意：如果focus属性设置为true,这个也为真，那么编辑器一上来就会触发导致初始化的内容看不到了\n\n        //,focus:false //初始化时，是否让编辑器获得焦点true或false\n\n        //如果自定义，最好给p标签如下的行高，要不输入中文时，会有跳动感\n        //,initialStyle:'p{line-height:1em}'//编辑器层级的基数,可以用来改变字体等\n\n        //,iframeCssUrl: URL + '/themes/iframe.css' //给编辑区域的iframe引入一个css文件\n\n        //indentValue\n        //首行缩进距离,默认是2em\n        //,indentValue:'2em'\n\n        //,initialFrameWidth:1000  //初始化编辑器宽度,默认1000\n        //,initialFrameHeight:320  //初始化编辑器高度,默认320\n\n        //,readonly : false //编辑器初始化结束后,编辑区域是否是只读的，默认是false\n\n        //,autoClearEmptyNode : true //getContent时，是否删除空的inlineElement节点（包括嵌套的情况）\n\n        //启用自动保存\n        //,enableAutoSave: true\n        //自动保存间隔时间， 单位ms\n        //,saveInterval: 500\n\n        //,fullscreen : false //是否开启初始化时即全屏，默认关闭\n\n        //,imagePopup:true      //图片操作的浮层开关，默认打开\n\n        //,autoSyncData:true //自动同步编辑器要提交的数据\n        //,emotionLocalization:false //是否开启表情本地化，默认关闭。若要开启请确保emotion文件夹下包含官网提供的images表情文件夹\n\n        //粘贴只保留标签，去除标签所有属性\n        //,retainOnlyLabelPasted: false\n\n        //,pasteplain:false  //是否默认为纯文本粘贴。false为不使用纯文本粘贴，true为使用纯文本粘贴\n        //纯文本粘贴模式下的过滤规则\n        //'filterTxtRules' : function(){\n        //    function transP(node){\n        //        node.tagName = 'p';\n        //        node.setStyle();\n        //    }\n        //    return {\n        //        //直接删除及其字节点内容\n        //        '-' : 'script style object iframe embed input select',\n        //        'p': {$:{}},\n        //        'br':{$:{}},\n        //        'div':{'$':{}},\n        //        'li':{'$':{}},\n        //        'caption':transP,\n        //        'th':transP,\n        //        'tr':transP,\n        //        'h1':transP,'h2':transP,'h3':transP,'h4':transP,'h5':transP,'h6':transP,\n        //        'td':function(node){\n        //            //没有内容的td直接删掉\n        //            var txt = !!node.innerText();\n        //            if(txt){\n        //                node.parentNode.insertAfter(UE.uNode.createText(' &nbsp; &nbsp;'),node);\n        //            }\n        //            node.parentNode.removeChild(node,node.innerText())\n        //        }\n        //    }\n        //}()\n\n        //,allHtmlEnabled:false //提交到后台的数据是否包含整个html字符串\n\n        //insertorderedlist\n        //有序列表的下拉配置,值留空时支持多语言自动识别，若配置值，则以此值为准\n        //,'insertorderedlist':{\n        //      //自定的样式\n        //        'num':'1,2,3...',\n        //        'num1':'1),2),3)...',\n        //        'num2':'(1),(2),(3)...',\n        //        'cn':'一,二,三....',\n        //        'cn1':'一),二),三)....',\n        //        'cn2':'(一),(二),(三)....',\n        //     //系统自带\n        //     'decimal' : '' ,         //'1,2,3...'\n        //     'lower-alpha' : '' ,    // 'a,b,c...'\n        //     'lower-roman' : '' ,    //'i,ii,iii...'\n        //     'upper-alpha' : '' , lang   //'A,B,C'\n        //     'upper-roman' : ''      //'I,II,III...'\n        //}\n\n        //insertunorderedlist\n        //无序列表的下拉配置，值留空时支持多语言自动识别，若配置值，则以此值为准\n        //,insertunorderedlist : { //自定的样式\n        //    'dash' :'— 破折号', //-破折号\n        //    'dot':' 。 小圆圈', //系统自带\n        //    'circle' : '',  // '○ 小圆圈'\n        //    'disc' : '',    // '● 小圆点'\n        //    'square' : ''   //'■ 小方块'\n        //}\n        //,listDefaultPaddingLeft : '30'//默认的左边缩进的基数倍\n        //,listiconpath : 'http://bs.baidu.com/listicon/'//自定义标号的路径\n        //,maxListLevel : 3 //限制可以tab的级数, 设置-1为不限制\n\n        //,autoTransWordToList:false  //禁止word中粘贴进来的列表自动变成列表标签\n\n        //fontfamily\n        //字体设置 label留空支持多语言自动切换，若配置，则以配置值为准\n        //,'fontfamily':[\n        //    { label:'',name:'songti',val:'宋体,SimSun'},\n        //    { label:'',name:'kaiti',val:'楷体,楷体_GB2312, SimKai'},\n        //    { label:'',name:'yahei',val:'微软雅黑,Microsoft YaHei'},\n        //    { label:'',name:'heiti',val:'黑体, SimHei'},\n        //    { label:'',name:'lishu',val:'隶书, SimLi'},\n        //    { label:'',name:'andaleMono',val:'andale mono'},\n        //    { label:'',name:'arial',val:'arial, helvetica,sans-serif'},\n        //    { label:'',name:'arialBlack',val:'arial black,avant garde'},\n        //    { label:'',name:'comicSansMs',val:'comic sans ms'},\n        //    { label:'',name:'impact',val:'impact,chicago'},\n        //    { label:'',name:'timesNewRoman',val:'times new roman'}\n        //]\n\n        //fontsize\n        //字号\n        //,'fontsize':[10, 11, 12, 14, 16, 18, 20, 24, 36]\n\n        //paragraph\n        //段落格式 值留空时支持多语言自动识别，若配置，则以配置值为准\n        //,'paragraph':{'p':'', 'h1':'', 'h2':'', 'h3':'', 'h4':'', 'h5':'', 'h6':''}\n\n        //rowspacingtop\n        //段间距 值和显示的名字相同\n        //,'rowspacingtop':['5', '10', '15', '20', '25']\n\n        //rowspacingBottom\n        //段间距 值和显示的名字相同\n        //,'rowspacingbottom':['5', '10', '15', '20', '25']\n\n        //lineheight\n        //行内间距 值和显示的名字相同\n        //,'lineheight':['1', '1.5','1.75','2', '3', '4', '5']\n\n        //customstyle\n        //自定义样式，不支持国际化，此处配置值即可最后显示值\n        //block的元素是依据设置段落的逻辑设置的，inline的元素依据BIU的逻辑设置\n        //尽量使用一些常用的标签\n        //参数说明\n        //tag 使用的标签名字\n        //label 显示的名字也是用来标识不同类型的标识符，注意这个值每个要不同，\n        //style 添加的样式\n        //每一个对象就是一个自定义的样式\n        //,'customstyle':[\n        //    {tag:'h1', name:'tc', label:'', style:'border-bottom:#ccc 2px solid;padding:0 4px 0 0;text-align:center;margin:0 0 20px 0;'},\n        //    {tag:'h1', name:'tl',label:'', style:'border-bottom:#ccc 2px solid;padding:0 4px 0 0;margin:0 0 10px 0;'},\n        //    {tag:'span',name:'im', label:'', style:'font-style:italic;font-weight:bold'},\n        //    {tag:'span',name:'hi', label:'', style:'font-style:italic;font-weight:bold;color:rgb(51, 153, 204)'}\n        //]\n\n        //打开右键菜单功能\n        //,enableContextMenu: true\n        //右键菜单的内容，可以参考plugins/contextmenu.js里边的默认菜单的例子，label留空支持国际化，否则以此配置为准\n        //,contextMenu:[\n        //    {\n        //        label:'',       //显示的名称\n        //        cmdName:'selectall',//执行的command命令，当点击这个右键菜单时\n        //        //exec可选，有了exec就会在点击时执行这个function，优先级高于cmdName\n        //        exec:function () {\n        //            //this是当前编辑器的实例\n        //            //this.ui._dialogs['inserttableDialog'].open();\n        //        }\n        //    }\n        //]\n\n        //快捷菜单\n        //,shortcutMenu:[\"fontfamily\", \"fontsize\", \"bold\", \"italic\", \"underline\", \"forecolor\", \"backcolor\", \"insertorderedlist\", \"insertunorderedlist\"]\n\n        //elementPathEnabled\n        //是否启用元素路径，默认是显示\n        //,elementPathEnabled : true\n\n        //wordCount\n        //,wordCount:true          //是否开启字数统计\n        //,maximumWords:10000       //允许的最大字符数\n        //字数统计提示，{#count}代表当前字数，{#leave}代表还可以输入多少字符数,留空支持多语言自动切换，否则按此配置显示\n        //,wordCountMsg:''   //当前已输入 {#count} 个字符，您还可以输入{#leave} 个字符\n        //超出字数限制提示  留空支持多语言自动切换，否则按此配置显示\n        //,wordOverFlowMsg:''    //<span style=\"color:red;\">你输入的字符个数已经超出最大允许值，服务器可能会拒绝保存！</span>\n\n        //tab\n        //点击tab键时移动的距离,tabSize倍数，tabNode什么字符做为单位\n        //,tabSize:4\n        //,tabNode:'&nbsp;'\n\n        //removeFormat\n        //清除格式时可以删除的标签和属性\n        //removeForamtTags标签\n        //,removeFormatTags:'b,big,code,del,dfn,em,font,i,ins,kbd,q,samp,small,span,strike,strong,sub,sup,tt,u,var'\n        //removeFormatAttributes属性\n        //,removeFormatAttributes:'class,style,lang,width,height,align,hspace,valign'\n\n        //undo\n        //可以最多回退的次数,默认20\n        //,maxUndoCount:20\n        //当输入的字符数超过该值时，保存一次现场\n        //,maxInputCount:1\n\n        //autoHeightEnabled\n        // 是否自动长高,默认true\n        //,autoHeightEnabled:true\n\n        //scaleEnabled\n        //是否可以拉伸长高,默认true(当开启时，自动长高失效)\n        //,scaleEnabled:false\n        //,minFrameWidth:800    //编辑器拖动时最小宽度,默认800\n        //,minFrameHeight:220  //编辑器拖动时最小高度,默认220\n\n        //autoFloatEnabled\n        //是否保持toolbar的位置不动,默认true\n        //,autoFloatEnabled:true\n        //浮动时工具栏距离浏览器顶部的高度，用于某些具有固定头部的页面\n        //,topOffset:30\n        //编辑器底部距离工具栏高度(如果参数大于等于编辑器高度，则设置无效)\n        //,toolbarTopOffset:400\n\n        //设置远程图片是否抓取到本地保存\n        //,catchRemoteImageEnable: true //设置是否抓取远程图片\n\n        //pageBreakTag\n        //分页标识符,默认是_ueditor_page_break_tag_\n        //,pageBreakTag:'_ueditor_page_break_tag_'\n\n        //autotypeset\n        //自动排版参数\n        //,autotypeset: {\n        //    mergeEmptyline: true,           //合并空行\n        //    removeClass: true,              //去掉冗余的class\n        //    removeEmptyline: false,         //去掉空行\n        //    textAlign:\"left\",               //段落的排版方式，可以是 left,right,center,justify 去掉这个属性表示不执行排版\n        //    imageBlockLine: 'center',       //图片的浮动方式，独占一行剧中,左右浮动，默认: center,left,right,none 去掉这个属性表示不执行排版\n        //    pasteFilter: false,             //根据规则过滤没事粘贴进来的内容\n        //    clearFontSize: false,           //去掉所有的内嵌字号，使用编辑器默认的字号\n        //    clearFontFamily: false,         //去掉所有的内嵌字体，使用编辑器默认的字体\n        //    removeEmptyNode: false,         // 去掉空节点\n        //    //可以去掉的标签\n        //    removeTagNames: {标签名字:1},\n        //    indent: false,                  // 行首缩进\n        //    indentValue : '2em',            //行首缩进的大小\n        //    bdc2sb: false,\n        //    tobdc: false\n        //}\n\n        //tableDragable\n        //表格是否可以拖拽\n        //,tableDragable: true\n\n\n\n        //sourceEditor\n        //源码的查看方式,codemirror 是代码高亮，textarea是文本框,默认是codemirror\n        //注意默认codemirror只能在ie8+和非ie中使用\n        //,sourceEditor:\"codemirror\"\n        //如果sourceEditor是codemirror，还用配置一下两个参数\n        //codeMirrorJsUrl js加载的路径，默认是 URL + \"third-party/codemirror/codemirror.js\"\n        //,codeMirrorJsUrl:URL + \"third-party/codemirror/codemirror.js\"\n        //codeMirrorCssUrl css加载的路径，默认是 URL + \"third-party/codemirror/codemirror.css\"\n        //,codeMirrorCssUrl:URL + \"third-party/codemirror/codemirror.css\"\n        //编辑器初始化完成后是否进入源码模式，默认为否。\n        //,sourceEditorFirst:false\n\n        //iframeUrlMap\n        //dialog内容的路径 ～会被替换成URL,垓属性一旦打开，将覆盖所有的dialog的默认路径\n        //,iframeUrlMap:{\n        //    'anchor':'~/dialogs/anchor/anchor.html',\n        //}\n\n        //allowLinkProtocol 允许的链接地址，有这些前缀的链接地址不会自动添加http\n        //, allowLinkProtocols: ['http:', 'https:', '#', '/', 'ftp:', 'mailto:', 'tel:', 'git:', 'svn:']\n\n        //webAppKey 百度应用的APIkey，每个站长必须首先去百度官网注册一个key后方能正常使用app功能，注册介绍，http://app.baidu.com/static/cms/getapikey.html\n        //, webAppKey: \"\"\n\n        //默认过滤规则相关配置项目\n        //,disabledTableInTable:true  //禁止表格嵌套\n        //,allowDivTransToP:true      //允许进入编辑器的div标签自动变成p标签\n        //,rgb2Hex:true               //默认产出的数据中的color自动从rgb格式变成16进制格式\n\n\t\t// xss 过滤是否开启,inserthtml等操作\n\t\t,xssFilterRules: true\n\t\t//input xss过滤\n\t\t,inputXssFilter: true\n\t\t//output xss过滤\n\t\t,outputXssFilter: true\n\t\t// xss过滤白名单 名单来源: https://raw.githubusercontent.com/leizongmin/js-xss/master/lib/default.js\n\t\t,whiteList: {\n\t\t\ta:      ['target', 'href', 'title', 'class', 'style'],\n\t\t\tabbr:   ['title', 'class', 'style'],\n\t\t\taddress: ['class', 'style'],\n\t\t\tarea:   ['shape', 'coords', 'href', 'alt'],\n\t\t\tarticle: [],\n\t\t\taside:  [],\n\t\t\taudio:  ['autoplay', 'controls', 'loop', 'preload', 'src', 'class', 'style'],\n\t\t\tb:      ['class', 'style'],\n\t\t\tbdi:    ['dir'],\n\t\t\tbdo:    ['dir'],\n\t\t\tbig:    [],\n\t\t\tblockquote: ['cite', 'class', 'style'],\n\t\t\tbr:     [],\n\t\t\tcaption: ['class', 'style'],\n\t\t\tcenter: [],\n\t\t\tcite:   [],\n\t\t\tcode:   ['class', 'style'],\n\t\t\tcol:    ['align', 'valign', 'span', 'width', 'class', 'style'],\n\t\t\tcolgroup: ['align', 'valign', 'span', 'width', 'class', 'style'],\n\t\t\tdd:     ['class', 'style'],\n\t\t\tdel:    ['datetime'],\n\t\t\tdetails: ['open'],\n\t\t\tdiv:    ['class', 'style'],\n\t\t\tdl:     ['class', 'style'],\n\t\t\tdt:     ['class', 'style'],\n\t\t\tem:     ['class', 'style'],\n\t\t\tfont:   ['color', 'size', 'face'],\n\t\t\tfooter: [],\n\t\t\th1:     ['class', 'style'],\n\t\t\th2:     ['class', 'style'],\n\t\t\th3:     ['class', 'style'],\n\t\t\th4:     ['class', 'style'],\n\t\t\th5:     ['class', 'style'],\n\t\t\th6:     ['class', 'style'],\n\t\t\theader: [],\n\t\t\thr:     [],\n\t\t\ti:      ['class', 'style'],\n\t\t\timg:    ['src', 'alt', 'title', 'width', 'height', 'id', '_src', 'loadingclass', 'class', 'data-latex'],\n\t\t\tins:    ['datetime'],\n\t\t\tli:     ['class', 'style'],\n\t\t\tmark:   [],\n\t\t\tnav:    [],\n\t\t\tol:     ['class', 'style'],\n\t\t\tp:      ['class', 'style'],\n\t\t\tpre:    ['class', 'style'],\n\t\t\ts:      [],\n\t\t\tsection:[],\n\t\t\tsmall:  [],\n\t\t\tspan:   ['class', 'style'],\n\t\t\tsub:    ['class', 'style'],\n\t\t\tsup:    ['class', 'style'],\n\t\t\tstrong: ['class', 'style'],\n\t\t\ttable:  ['width', 'border', 'align', 'valign', 'class', 'style'],\n\t\t\ttbody:  ['align', 'valign', 'class', 'style'],\n\t\t\ttd:     ['width', 'rowspan', 'colspan', 'align', 'valign', 'class', 'style'],\n\t\t\ttfoot:  ['align', 'valign', 'class', 'style'],\n\t\t\tth:     ['width', 'rowspan', 'colspan', 'align', 'valign', 'class', 'style'],\n\t\t\tthead:  ['align', 'valign', 'class', 'style'],\n\t\t\ttr:     ['rowspan', 'align', 'valign', 'class', 'style'],\n\t\t\ttt:     [],\n\t\t\tu:      [],\n\t\t\tul:     ['class', 'style'],\n\t\t\tvideo:  ['autoplay', 'controls', 'loop', 'preload', 'src', 'height', 'width', 'class', 'style']\n\t\t}\n    };\n\n    function getUEBasePath(docUrl, confUrl) {\n\n        return getBasePath(docUrl || self.document.URL || self.location.href, confUrl || getConfigFilePath());\n\n    }\n\n    function getConfigFilePath() {\n\n        var configPath = document.getElementsByTagName('script');\n\n        return configPath[ configPath.length - 1 ].src;\n\n    }\n\n    function getBasePath(docUrl, confUrl) {\n\n        var basePath = confUrl;\n\n\n        if (/^(\\/|\\\\\\\\)/.test(confUrl)) {\n\n            basePath = /^.+?\\w(\\/|\\\\\\\\)/.exec(docUrl)[0] + confUrl.replace(/^(\\/|\\\\\\\\)/, '');\n\n        } else if (!/^[a-z]+:/i.test(confUrl)) {\n\n            docUrl = docUrl.split(\"#\")[0].split(\"?\")[0].replace(/[^\\\\\\/]+$/, '');\n\n            basePath = docUrl + \"\" + confUrl;\n\n        }\n\n        return optimizationPath(basePath);\n\n    }\n\n    function optimizationPath(path) {\n\n        var protocol = /^[a-z]+:\\/\\//.exec(path)[ 0 ],\n            tmp = null,\n            res = [];\n\n        path = path.replace(protocol, \"\").split(\"?\")[0].split(\"#\")[0];\n\n        path = path.replace(/\\\\/g, '/').split(/\\//);\n\n        path[ path.length - 1 ] = \"\";\n\n        while (path.length) {\n\n            if (( tmp = path.shift() ) === \"..\") {\n                res.pop();\n            } else if (tmp !== \".\") {\n                res.push(tmp);\n            }\n\n        }\n\n        return protocol + res.join(\"/\");\n\n    }\n\n    window.UE = {\n        getUEBasePath: getUEBasePath\n    };\n\n})();\n"
  },
  {
    "path": "yshop-drink-vue3/public/UEditor22/ueditor.parse.js",
    "content": "/*!\n * UEditor\n * version: ueditor\n * build: Wed Dec 26 2018 17:25:05 GMT+0800 (CST)\n */\n\n(function(){\n\n(function(){\n    UE = window.UE || {};\n    var isIE = !!window.ActiveXObject;\n    //定义utils工具\n    var utils = {\n            removeLastbs : function(url){\n                return url.replace(/\\/$/,'')\n            },\n            extend : function(t,s){\n                var a = arguments,\n                    notCover = this.isBoolean(a[a.length - 1]) ? a[a.length - 1] : false,\n                    len = this.isBoolean(a[a.length - 1]) ? a.length - 1 : a.length;\n                for (var i = 1; i < len; i++) {\n                    var x = a[i];\n                    for (var k in x) {\n                        if (!notCover || !t.hasOwnProperty(k)) {\n                            t[k] = x[k];\n                        }\n                    }\n                }\n                return t;\n            },\n            isIE : isIE,\n            cssRule : isIE ? function(key,style,doc){\n                var indexList,index;\n                doc = doc || document;\n                if(doc.indexList){\n                    indexList = doc.indexList;\n                }else{\n                    indexList = doc.indexList =  {};\n                }\n                var sheetStyle;\n                if(!indexList[key]){\n                    if(style === undefined){\n                        return ''\n                    }\n                    sheetStyle = doc.createStyleSheet('',index = doc.styleSheets.length);\n                    indexList[key] = index;\n                }else{\n                    sheetStyle = doc.styleSheets[indexList[key]];\n                }\n                if(style === undefined){\n                    return sheetStyle.cssText\n                }\n                sheetStyle.cssText = sheetStyle.cssText + '\\n' + (style || '')\n            } : function(key,style,doc){\n                doc = doc || document;\n                var head = doc.getElementsByTagName('head')[0],node;\n                if(!(node = doc.getElementById(key))){\n                    if(style === undefined){\n                        return ''\n                    }\n                    node = doc.createElement('style');\n                    node.id = key;\n                    head.appendChild(node)\n                }\n                if(style === undefined){\n                    return node.innerHTML\n                }\n                if(style !== ''){\n                    node.innerHTML = node.innerHTML + '\\n' + style;\n                }else{\n                    head.removeChild(node)\n                }\n            },\n            domReady : function (onready) {\n                var doc = window.document;\n                if (doc.readyState === \"complete\") {\n                    onready();\n                }else{\n                    if (isIE) {\n                        (function () {\n                            if (doc.isReady) return;\n                            try {\n                                doc.documentElement.doScroll(\"left\");\n                            } catch (error) {\n                                setTimeout(arguments.callee, 0);\n                                return;\n                            }\n                            onready();\n                        })();\n                        window.attachEvent('onload', function(){\n                            onready()\n                        });\n                    } else {\n                        doc.addEventListener(\"DOMContentLoaded\", function () {\n                            doc.removeEventListener(\"DOMContentLoaded\", arguments.callee, false);\n                            onready();\n                        }, false);\n                        window.addEventListener('load', function(){onready()}, false);\n                    }\n                }\n\n            },\n            each : function(obj, iterator, context) {\n                if (obj == null) return;\n                if (obj.length === +obj.length) {\n                    for (var i = 0, l = obj.length; i < l; i++) {\n                        if(iterator.call(context, obj[i], i, obj) === false)\n                            return false;\n                    }\n                } else {\n                    for (var key in obj) {\n                        if (obj.hasOwnProperty(key)) {\n                            if(iterator.call(context, obj[key], key, obj) === false)\n                                return false;\n                        }\n                    }\n                }\n            },\n            inArray : function(arr,item){\n                var index = -1;\n                this.each(arr,function(v,i){\n                    if(v === item){\n                        index = i;\n                        return false;\n                    }\n                });\n                return index;\n            },\n            pushItem : function(arr,item){\n                if(this.inArray(arr,item)==-1){\n                    arr.push(item)\n                }\n            },\n            trim: function (str) {\n                return str.replace(/(^[ \\t\\n\\r]+)|([ \\t\\n\\r]+$)/g, '');\n            },\n            indexOf: function (array, item, start) {\n                var index = -1;\n                start = this.isNumber(start) ? start : 0;\n                this.each(array, function (v, i) {\n                    if (i >= start && v === item) {\n                        index = i;\n                        return false;\n                    }\n                });\n                return index;\n            },\n            hasClass: function (element, className) {\n                className = className.replace(/(^[ ]+)|([ ]+$)/g, '').replace(/[ ]{2,}/g, ' ').split(' ');\n                for (var i = 0, ci, cls = element.className; ci = className[i++];) {\n                    if (!new RegExp('\\\\b' + ci + '\\\\b', 'i').test(cls)) {\n                        return false;\n                    }\n                }\n                return i - 1 == className.length;\n            },\n            addClass:function (elm, classNames) {\n                if(!elm)return;\n                classNames = this.trim(classNames).replace(/[ ]{2,}/g,' ').split(' ');\n                for(var i = 0,ci,cls = elm.className;ci=classNames[i++];){\n                    if(!new RegExp('\\\\b' + ci + '\\\\b').test(cls)){\n                        cls += ' ' + ci;\n                    }\n                }\n                elm.className = utils.trim(cls);\n            },\n            removeClass:function (elm, classNames) {\n                classNames = this.isArray(classNames) ? classNames :\n                    this.trim(classNames).replace(/[ ]{2,}/g,' ').split(' ');\n                for(var i = 0,ci,cls = elm.className;ci=classNames[i++];){\n                    cls = cls.replace(new RegExp('\\\\b' + ci + '\\\\b'),'')\n                }\n                cls = this.trim(cls).replace(/[ ]{2,}/g,' ');\n                elm.className = cls;\n                !cls && elm.removeAttribute('className');\n            },\n            on: function (element, type, handler) {\n                var types = this.isArray(type) ? type : type.split(/\\s+/),\n                    k = types.length;\n                if (k) while (k--) {\n                    type = types[k];\n                    if (element.addEventListener) {\n                        element.addEventListener(type, handler, false);\n                    } else {\n                        if (!handler._d) {\n                            handler._d = {\n                                els : []\n                            };\n                        }\n                        var key = type + handler.toString(),index = utils.indexOf(handler._d.els,element);\n                        if (!handler._d[key] || index == -1) {\n                            if(index == -1){\n                                handler._d.els.push(element);\n                            }\n                            if(!handler._d[key]){\n                                handler._d[key] = function (evt) {\n                                    return handler.call(evt.srcElement, evt || window.event);\n                                };\n                            }\n\n\n                            element.attachEvent('on' + type, handler._d[key]);\n                        }\n                    }\n                }\n                element = null;\n            },\n            off: function (element, type, handler) {\n                var types = this.isArray(type) ? type : type.split(/\\s+/),\n                    k = types.length;\n                if (k) while (k--) {\n                    type = types[k];\n                    if (element.removeEventListener) {\n                        element.removeEventListener(type, handler, false);\n                    } else {\n                        var key = type + handler.toString();\n                        try{\n                            element.detachEvent('on' + type, handler._d ? handler._d[key] : handler);\n                        }catch(e){}\n                        if (handler._d && handler._d[key]) {\n                            var index = utils.indexOf(handler._d.els,element);\n                            if(index!=-1){\n                                handler._d.els.splice(index,1);\n                            }\n                            handler._d.els.length == 0 && delete handler._d[key];\n                        }\n                    }\n                }\n            },\n            loadFile : function () {\n                var tmpList = [];\n                function getItem(doc,obj){\n                    try{\n                        for(var i= 0,ci;ci=tmpList[i++];){\n                            if(ci.doc === doc && ci.url == (obj.src || obj.href)){\n                                return ci;\n                            }\n                        }\n                    }catch(e){\n                        return null;\n                    }\n\n                }\n                return function (doc, obj, fn) {\n                    var item = getItem(doc,obj);\n                    if (item) {\n                        if(item.ready){\n                            fn && fn();\n                        }else{\n                            item.funs.push(fn)\n                        }\n                        return;\n                    }\n                    tmpList.push({\n                        doc:doc,\n                        url:obj.src||obj.href,\n                        funs:[fn]\n                    });\n                    if (!doc.body) {\n                        var html = [];\n                        for(var p in obj){\n                            if(p == 'tag')continue;\n                            html.push(p + '=\"' + obj[p] + '\"')\n                        }\n                        doc.write('<' + obj.tag + ' ' + html.join(' ') + ' ></'+obj.tag+'>');\n                        return;\n                    }\n                    if (obj.id && doc.getElementById(obj.id)) {\n                        return;\n                    }\n                    var element = doc.createElement(obj.tag);\n                    delete obj.tag;\n                    for (var p in obj) {\n                        element.setAttribute(p, obj[p]);\n                    }\n                    element.onload = element.onreadystatechange = function () {\n                        if (!this.readyState || /loaded|complete/.test(this.readyState)) {\n                            item = getItem(doc,obj);\n                            if (item.funs.length > 0) {\n                                item.ready = 1;\n                                for (var fi; fi = item.funs.pop();) {\n                                    fi();\n                                }\n                            }\n                            element.onload = element.onreadystatechange = null;\n                        }\n                    };\n                    element.onerror = function(){\n                        throw Error('The load '+(obj.href||obj.src)+' fails,check the url')\n                    };\n                    doc.getElementsByTagName(\"head\")[0].appendChild(element);\n                }\n            }()\n    };\n    utils.each(['String', 'Function', 'Array', 'Number', 'RegExp', 'Object','Boolean'], function (v) {\n        utils['is' + v] = function (obj) {\n            return Object.prototype.toString.apply(obj) == '[object ' + v + ']';\n        }\n    });\n    var parselist = {};\n    UE.parse = {\n        register : function(parseName,fn){\n            parselist[parseName] = fn;\n        },\n        load : function(opt){\n            utils.each(parselist,function(v){\n                v.call(opt,utils);\n            })\n        }\n    };\n    uParse = function(selector,opt){\n        utils.domReady(function(){\n            var contents;\n            if(document.querySelectorAll){\n                contents = document.querySelectorAll(selector)\n            }else{\n                if(/^#/.test(selector)){\n                    contents = [document.getElementById(selector.replace(/^#/,''))]\n                }else if(/^\\./.test(selector)){\n                    var contents = [];\n                    utils.each(document.getElementsByTagName('*'),function(node){\n                        if(node.className && new RegExp('\\\\b' + selector.replace(/^\\./,'') + '\\\\b','i').test(node.className)){\n                            contents.push(node)\n                        }\n                    })\n                }else{\n                    contents = document.getElementsByTagName(selector)\n                }\n            }\n            utils.each(contents,function(v){\n                UE.parse.load(utils.extend({root:v,selector:selector},opt))\n            })\n        })\n    }\n})();\n\nUE.parse.register('insertcode',function(utils){\n    var pres = this.root.getElementsByTagName('pre');\n    if(pres.length){\n        if(typeof XRegExp == \"undefined\"){\n            var jsurl,cssurl;\n            if(this.rootPath !== undefined){\n                jsurl = utils.removeLastbs(this.rootPath)  + '/third-party/SyntaxHighlighter/shCore.js';\n                cssurl = utils.removeLastbs(this.rootPath) + '/third-party/SyntaxHighlighter/shCoreDefault.css';\n            }else{\n                jsurl = this.highlightJsUrl;\n                cssurl = this.highlightCssUrl;\n            }\n            utils.loadFile(document,{\n                id : \"syntaxhighlighter_css\",\n                tag : \"link\",\n                rel : \"stylesheet\",\n                type : \"text/css\",\n                href : cssurl\n            });\n            utils.loadFile(document,{\n                id : \"syntaxhighlighter_js\",\n                src : jsurl,\n                tag : \"script\",\n                type : \"text/javascript\",\n                defer : \"defer\"\n            },function(){\n                utils.each(pres,function(pi){\n                    if(pi && /brush/i.test(pi.className)){\n                        SyntaxHighlighter.highlight(pi);\n                    }\n                });\n            });\n        }else{\n            utils.each(pres,function(pi){\n                if(pi && /brush/i.test(pi.className)){\n                    SyntaxHighlighter.highlight(pi);\n                }\n            });\n        }\n    }\n\n});\nUE.parse.register('table', function (utils) {\n    var me = this,\n        root = this.root,\n        tables = root.getElementsByTagName('table');\n    if (tables.length) {\n        var selector = this.selector;\n        //追加默认的表格样式\n        utils.cssRule('table',\n            selector + ' table.noBorderTable td,' +\n                selector + ' table.noBorderTable th,' +\n                selector + ' table.noBorderTable caption{border:1px dashed #ddd !important}' +\n                selector + ' table.sortEnabled tr.firstRow th,' + selector + ' table.sortEnabled tr.firstRow td{padding-right:20px; background-repeat: no-repeat;' +\n                    'background-position: center right; background-image:url(' + this.rootPath + 'themes/default/images/sortable.png);}' +\n                selector + ' table.sortEnabled tr.firstRow th:hover,' + selector + ' table.sortEnabled tr.firstRow td:hover{background-color: #EEE;}' +\n                selector + ' table{margin-bottom:10px;border-collapse:collapse;display:table;}' +\n                selector + ' td,' + selector + ' th{ background:white; padding: 5px 10px;border: 1px solid #DDD;}' +\n                selector + ' caption{border:1px dashed #DDD;border-bottom:0;padding:3px;text-align:center;}' +\n                selector + ' th{border-top:1px solid #BBB;background:#F7F7F7;}' +\n                selector + ' table tr.firstRow th{border-top:2px solid #BBB;background:#F7F7F7;}' +\n                selector + ' tr.ue-table-interlace-color-single td{ background: #fcfcfc; }' +\n                selector + ' tr.ue-table-interlace-color-double td{ background: #f7faff; }' +\n                selector + ' td p{margin:0;padding:0;}',\n            document);\n        //填充空的单元格\n\n        utils.each('td th caption'.split(' '), function (tag) {\n            var cells = root.getElementsByTagName(tag);\n            cells.length && utils.each(cells, function (node) {\n                if (!node.firstChild) {\n                    node.innerHTML = '&nbsp;';\n\n                }\n            })\n        });\n\n        //表格可排序\n        var tables = root.getElementsByTagName('table');\n        utils.each(tables, function (table) {\n            if (/\\bsortEnabled\\b/.test(table.className)) {\n                utils.on(table, 'click', function(e){\n                    var target = e.target || e.srcElement,\n                        cell = findParentByTagName(target, ['td', 'th']);\n                    var table = findParentByTagName(target, 'table'),\n                        colIndex = utils.indexOf(table.rows[0].cells, cell),\n                        sortType = table.getAttribute('data-sort-type');\n                    if(colIndex != -1) {\n                        sortTable(table, colIndex, me.tableSortCompareFn || sortType);\n                        updateTable(table);\n                    }\n                });\n            }\n        });\n\n        //按照标签名查找父节点\n        function findParentByTagName(target, tagNames) {\n            var i, current = target;\n            tagNames = utils.isArray(tagNames) ? tagNames:[tagNames];\n            while(current){\n                for(i = 0;i < tagNames.length; i++) {\n                    if(current.tagName == tagNames[i].toUpperCase()) return current;\n                }\n                current = current.parentNode;\n            }\n            return null;\n        }\n        //表格排序\n        function sortTable(table, sortByCellIndex, compareFn) {\n            var rows = table.rows,\n                trArray = [],\n                flag = rows[0].cells[0].tagName === \"TH\",\n                lastRowIndex = 0;\n\n            for (var i = 0,len = rows.length; i < len; i++) {\n                trArray[i] = rows[i];\n            }\n\n            var Fn = {\n                'reversecurrent': function(td1,td2){\n                    return 1;\n                },\n                'orderbyasc': function(td1,td2){\n                    var value1 = td1.innerText||td1.textContent,\n                        value2 = td2.innerText||td2.textContent;\n                    return value1.localeCompare(value2);\n                },\n                'reversebyasc': function(td1,td2){\n                    var value1 = td1.innerHTML,\n                        value2 = td2.innerHTML;\n                    return value2.localeCompare(value1);\n                },\n                'orderbynum': function(td1,td2){\n                    var value1 = td1[utils.isIE ? 'innerText':'textContent'].match(/\\d+/),\n                        value2 = td2[utils.isIE ? 'innerText':'textContent'].match(/\\d+/);\n                    if(value1) value1 = +value1[0];\n                    if(value2) value2 = +value2[0];\n                    return (value1||0) - (value2||0);\n                },\n                'reversebynum': function(td1,td2){\n                    var value1 = td1[utils.isIE ? 'innerText':'textContent'].match(/\\d+/),\n                        value2 = td2[utils.isIE ? 'innerText':'textContent'].match(/\\d+/);\n                    if(value1) value1 = +value1[0];\n                    if(value2) value2 = +value2[0];\n                    return (value2||0) - (value1||0);\n                }\n            };\n\n            //对表格设置排序的标记data-sort-type\n            table.setAttribute('data-sort-type', compareFn && typeof compareFn === \"string\" && Fn[compareFn] ? compareFn:'');\n\n            //th不参与排序\n            flag && trArray.splice(0, 1);\n            trArray = sort(trArray,function (tr1, tr2) {\n                var result;\n                if (compareFn && typeof compareFn === \"function\") {\n                    result = compareFn.call(this, tr1.cells[sortByCellIndex], tr2.cells[sortByCellIndex]);\n                } else if (compareFn && typeof compareFn === \"number\") {\n                    result = 1;\n                } else if (compareFn && typeof compareFn === \"string\" && Fn[compareFn]) {\n                    result = Fn[compareFn].call(this, tr1.cells[sortByCellIndex], tr2.cells[sortByCellIndex]);\n                } else {\n                    result = Fn['orderbyasc'].call(this, tr1.cells[sortByCellIndex], tr2.cells[sortByCellIndex]);\n                }\n                return result;\n            });\n            var fragment = table.ownerDocument.createDocumentFragment();\n            for (var j = 0, len = trArray.length; j < len; j++) {\n                fragment.appendChild(trArray[j]);\n            }\n            var tbody = table.getElementsByTagName(\"tbody\")[0];\n            if(!lastRowIndex){\n                tbody.appendChild(fragment);\n            }else{\n                tbody.insertBefore(fragment,rows[lastRowIndex- range.endRowIndex + range.beginRowIndex - 1])\n            }\n        }\n        //冒泡排序\n        function sort(array, compareFn){\n            compareFn = compareFn || function(item1, item2){ return item1.localeCompare(item2);};\n            for(var i= 0,len = array.length; i<len; i++){\n                for(var j = i,length = array.length; j<length; j++){\n                    if(compareFn(array[i], array[j]) > 0){\n                        var t = array[i];\n                        array[i] = array[j];\n                        array[j] = t;\n                    }\n                }\n            }\n            return array;\n        }\n        //更新表格\n        function updateTable(table) {\n            //给第一行设置firstRow的样式名称,在排序图标的样式上使用到\n            if(!utils.hasClass(table.rows[0], \"firstRow\")) {\n                for(var i = 1; i< table.rows.length; i++) {\n                    utils.removeClass(table.rows[i], \"firstRow\");\n                }\n                utils.addClass(table.rows[0], \"firstRow\");\n            }\n        }\n    }\n});\nUE.parse.register('charts',function( utils ){\n\n    utils.cssRule('chartsContainerHeight','.edui-chart-container { height:'+(this.chartContainerHeight||300)+'px}');\n    var resourceRoot = this.rootPath,\n        containers = this.root,\n        sources = null;\n\n    //不存在指定的根路径， 则直接退出\n    if ( !resourceRoot ) {\n        return;\n    }\n\n    if ( sources = parseSources() ) {\n\n        loadResources();\n\n    }\n\n\n    function parseSources () {\n\n        if ( !containers ) {\n            return null;\n        }\n\n        return extractChartData( containers );\n\n    }\n\n    /**\n     * 提取数据\n     */\n    function extractChartData ( rootNode ) {\n\n        var data = [],\n            tables = rootNode.getElementsByTagName( \"table\" );\n\n        for ( var i = 0, tableNode; tableNode = tables[ i ]; i++ ) {\n\n            if ( tableNode.getAttribute( \"data-chart\" ) !== null ) {\n\n                data.push( formatData( tableNode ) );\n\n            }\n\n        }\n\n        return data.length ? data : null;\n\n    }\n\n    function formatData ( tableNode ) {\n\n        var meta = tableNode.getAttribute( \"data-chart\" ),\n            metaConfig = {},\n            data = [];\n\n        //提取table数据\n        for ( var i = 0, row; row = tableNode.rows[ i ]; i++ ) {\n\n            var rowData = [];\n\n            for ( var j = 0, cell; cell = row.cells[ j ]; j++ ) {\n\n                var value = ( cell.innerText || cell.textContent || '' );\n                rowData.push( cell.tagName == 'TH' ? value:(value | 0) );\n\n            }\n\n            data.push( rowData );\n\n        }\n\n        //解析元信息\n        meta = meta.split( \";\" );\n        for ( var i = 0, metaData; metaData = meta[ i ]; i++ ) {\n\n            metaData = metaData.split( \":\" );\n            metaConfig[ metaData[ 0 ] ] = metaData[ 1 ];\n\n        }\n\n\n        return {\n            table: tableNode,\n            meta: metaConfig,\n            data: data\n        };\n\n    }\n\n    //加载资源\n    function loadResources () {\n\n        loadJQuery();\n\n    }\n\n    function loadJQuery () {\n\n        //不存在jquery， 则加载jquery\n        if ( !window.jQuery ) {\n\n            utils.loadFile(document,{\n                src : resourceRoot + \"/third-party/jquery-1.10.2.min.js\",\n                tag : \"script\",\n                type : \"text/javascript\",\n                defer : \"defer\"\n            },function(){\n\n                loadHighcharts();\n\n            });\n\n        } else {\n\n            loadHighcharts();\n\n        }\n\n    }\n\n    function loadHighcharts () {\n\n        //不存在Highcharts， 则加载Highcharts\n        if ( !window.Highcharts ) {\n\n            utils.loadFile(document,{\n                src : resourceRoot + \"/third-party/highcharts/highcharts.js\",\n                tag : \"script\",\n                type : \"text/javascript\",\n                defer : \"defer\"\n            },function(){\n\n                loadTypeConfig();\n\n            });\n\n        } else {\n\n            loadTypeConfig();\n\n        }\n\n    }\n\n    //加载图表差异化配置文件\n    function loadTypeConfig () {\n\n        utils.loadFile(document,{\n            src : resourceRoot + \"/dialogs/charts/chart.config.js\",\n            tag : \"script\",\n            type : \"text/javascript\",\n            defer : \"defer\"\n        },function(){\n\n            render();\n\n        });\n\n    }\n\n    //渲染图表\n    function render () {\n\n        var config = null,\n            chartConfig = null,\n            container = null;\n\n        for ( var i = 0, len = sources.length; i < len; i++ ) {\n\n            config = sources[ i ];\n\n            chartConfig = analysisConfig( config );\n\n            container = createContainer( config.table );\n\n            renderChart( container, typeConfig[ config.meta.chartType ], chartConfig );\n\n        }\n\n\n    }\n\n    /**\n     * 渲染图表\n     * @param container 图表容器节点对象\n     * @param typeConfig 图表类型配置\n     * @param config 图表通用配置\n     * */\n    function renderChart ( container, typeConfig, config ) {\n\n\n        $( container ).highcharts( $.extend( {}, typeConfig, {\n\n            credits: {\n                enabled: false\n            },\n            exporting: {\n                enabled: false\n            },\n            title: {\n                text: config.title,\n                x: -20 //center\n            },\n            subtitle: {\n                text: config.subTitle,\n                x: -20\n            },\n            xAxis: {\n                title: {\n                    text: config.xTitle\n                },\n                categories: config.categories\n            },\n            yAxis: {\n                title: {\n                    text: config.yTitle\n                },\n                plotLines: [{\n                    value: 0,\n                    width: 1,\n                    color: '#808080'\n                }]\n            },\n            tooltip: {\n                enabled: true,\n                valueSuffix: config.suffix\n            },\n            legend: {\n                layout: 'vertical',\n                align: 'right',\n                verticalAlign: 'middle',\n                borderWidth: 1\n            },\n            series: config.series\n\n        } ));\n\n    }\n\n    /**\n     * 创建图表的容器\n     * 新创建的容器会替换掉对应的table对象\n     * */\n    function createContainer ( tableNode ) {\n\n        var container = document.createElement( \"div\" );\n        container.className = \"edui-chart-container\";\n\n        tableNode.parentNode.replaceChild( container, tableNode );\n\n        return container;\n\n    }\n\n    //根据config解析出正确的类别和图表数据信息\n    function analysisConfig ( config ) {\n\n        var series = [],\n        //数据类别\n            categories = [],\n            result = [],\n            data = config.data,\n            meta = config.meta;\n\n        //数据对齐方式为相反的方式， 需要反转数据\n        if ( meta.dataFormat != \"1\" ) {\n\n            for ( var i = 0, len = data.length; i < len ; i++ ) {\n\n                for ( var j = 0, jlen = data[ i ].length; j < jlen; j++ ) {\n\n                    if ( !result[ j ] ) {\n                        result[ j ] = [];\n                    }\n\n                    result[ j ][ i ] = data[ i ][ j ];\n\n                }\n\n            }\n\n            data = result;\n\n        }\n\n        result = {};\n\n        //普通图表\n        if ( meta.chartType != typeConfig.length - 1 ) {\n\n            categories = data[ 0 ].slice( 1 );\n\n            for ( var i = 1, curData; curData = data[ i ]; i++ ) {\n                series.push( {\n                    name: curData[ 0 ],\n                    data: curData.slice( 1 )\n                } );\n            }\n\n            result.series = series;\n            result.categories = categories;\n            result.title = meta.title;\n            result.subTitle = meta.subTitle;\n            result.xTitle = meta.xTitle;\n            result.yTitle = meta.yTitle;\n            result.suffix = meta.suffix;\n\n        } else {\n\n            var curData = [];\n\n            for ( var i = 1, len = data[ 0 ].length; i < len; i++ ) {\n\n                curData.push( [ data[ 0 ][ i ], data[ 1 ][ i ] | 0 ] );\n\n            }\n\n            //饼图\n            series[ 0 ] = {\n                type: 'pie',\n                name: meta.tip,\n                data: curData\n            };\n\n            result.series = series;\n            result.title = meta.title;\n            result.suffix = meta.suffix;\n\n        }\n\n        return result;\n\n    }\n\n});\nUE.parse.register('background', function (utils) {\n    var me = this,\n        root = me.root,\n        p = root.getElementsByTagName('p'),\n        styles;\n\n    for (var i = 0,ci; ci = p[i++];) {\n        styles = ci.getAttribute('data-background');\n        if (styles){\n            ci.parentNode.removeChild(ci);\n        }\n    }\n\n    //追加默认的表格样式\n    styles && utils.cssRule('ueditor_background', me.selector + '{' + styles + '}', document);\n});\nUE.parse.register('list',function(utils){\n    var customCss = [],\n        customStyle = {\n            'cn'    :   'cn-1-',\n            'cn1'   :   'cn-2-',\n            'cn2'   :   'cn-3-',\n            'num'   :   'num-1-',\n            'num1'  :   'num-2-',\n            'num2'  :   'num-3-',\n            'dash'  :   'dash',\n            'dot'   :   'dot'\n        };\n\n\n    utils.extend(this,{\n        liiconpath : 'http://bs.baidu.com/listicon/',\n        listDefaultPaddingLeft : '20'\n    });\n\n    var root = this.root,\n        ols = root.getElementsByTagName('ol'),\n        uls = root.getElementsByTagName('ul'),\n        selector = this.selector;\n\n    if(ols.length){\n        applyStyle.call(this,ols);\n    }\n\n    if(uls.length){\n        applyStyle.call(this,uls);\n    }\n\n    if(ols.length || uls.length){\n        customCss.push(selector +' .list-paddingleft-1{padding-left:0}');\n        customCss.push(selector +' .list-paddingleft-2{padding-left:'+ this.listDefaultPaddingLeft+'px}');\n        customCss.push(selector +' .list-paddingleft-3{padding-left:'+ this.listDefaultPaddingLeft*2+'px}');\n\n        utils.cssRule('list', selector +' ol,'+selector +' ul{margin:0;padding:0;}li{clear:both;}'+customCss.join('\\n'), document);\n    }\n    function applyStyle(nodes){\n        var T = this;\n        utils.each(nodes,function(list){\n            if(list.className && /custom_/i.test(list.className)){\n                var listStyle = list.className.match(/custom_(\\w+)/)[1];\n                if(listStyle == 'dash' || listStyle == 'dot'){\n                    utils.pushItem(customCss,selector +' li.list-' + customStyle[listStyle] + '{background-image:url(' + T.liiconpath +customStyle[listStyle]+'.gif)}');\n                    utils.pushItem(customCss,selector +' ul.custom_'+listStyle+'{list-style:none;} '+ selector +' ul.custom_'+listStyle+' li{background-position:0 3px;background-repeat:no-repeat}');\n\n                }else{\n                    var index = 1;\n                    utils.each(list.childNodes,function(li){\n                        if(li.tagName == 'LI'){\n                            utils.pushItem(customCss,selector + ' li.list-' + customStyle[listStyle] + index + '{background-image:url(' + T.liiconpath  + 'list-'+customStyle[listStyle] +index + '.gif)}');\n                            index++;\n                        }\n                    });\n                    utils.pushItem(customCss,selector + ' ol.custom_'+listStyle+'{list-style:none;}'+selector+' ol.custom_'+listStyle+' li{background-position:0 3px;background-repeat:no-repeat}');\n                }\n                switch(listStyle){\n                    case 'cn':\n                        utils.pushItem(customCss,selector + ' li.list-'+listStyle+'-paddingleft-1{padding-left:25px}');\n                        utils.pushItem(customCss,selector + ' li.list-'+listStyle+'-paddingleft-2{padding-left:40px}');\n                        utils.pushItem(customCss,selector + ' li.list-'+listStyle+'-paddingleft-3{padding-left:55px}');\n                        break;\n                    case 'cn1':\n                        utils.pushItem(customCss,selector + ' li.list-'+listStyle+'-paddingleft-1{padding-left:30px}');\n                        utils.pushItem(customCss,selector + ' li.list-'+listStyle+'-paddingleft-2{padding-left:40px}');\n                        utils.pushItem(customCss,selector + ' li.list-'+listStyle+'-paddingleft-3{padding-left:55px}');\n                        break;\n                    case 'cn2':\n                        utils.pushItem(customCss,selector + ' li.list-'+listStyle+'-paddingleft-1{padding-left:40px}');\n                        utils.pushItem(customCss,selector + ' li.list-'+listStyle+'-paddingleft-2{padding-left:55px}');\n                        utils.pushItem(customCss,selector + ' li.list-'+listStyle+'-paddingleft-3{padding-left:68px}');\n                        break;\n                    case 'num':\n                    case 'num1':\n                        utils.pushItem(customCss,selector + ' li.list-'+listStyle+'-paddingleft-1{padding-left:25px}');\n                        break;\n                    case 'num2':\n                        utils.pushItem(customCss,selector + ' li.list-'+listStyle+'-paddingleft-1{padding-left:35px}');\n                        utils.pushItem(customCss,selector + ' li.list-'+listStyle+'-paddingleft-2{padding-left:40px}');\n                        break;\n                    case 'dash':\n                        utils.pushItem(customCss,selector + ' li.list-'+listStyle+'-paddingleft{padding-left:35px}');\n                        break;\n                    case 'dot':\n                        utils.pushItem(customCss,selector + ' li.list-'+listStyle+'-paddingleft{padding-left:20px}');\n                }\n            }\n        });\n    }\n\n\n});\nUE.parse.register('vedio',function(utils){\n    var video = this.root.getElementsByTagName('video'),\n        audio = this.root.getElementsByTagName('audio');\n\n    document.createElement('video');document.createElement('audio');\n    if(video.length || audio.length){\n        var sourcePath = utils.removeLastbs(this.rootPath),\n            jsurl = sourcePath + '/third-party/video-js/video.js',\n            cssurl = sourcePath + '/third-party/video-js/video-js.min.css',\n            swfUrl = sourcePath + '/third-party/video-js/video-js.swf';\n\n        if(window.videojs) {\n            videojs.autoSetup();\n        } else {\n            utils.loadFile(document,{\n                id : \"video_css\",\n                tag : \"link\",\n                rel : \"stylesheet\",\n                type : \"text/css\",\n                href : cssurl\n            });\n            utils.loadFile(document,{\n                id : \"video_js\",\n                src : jsurl,\n                tag : \"script\",\n                type : \"text/javascript\"\n            },function(){\n                videojs.options.flash.swf = swfUrl;\n                videojs.autoSetup();\n            });\n        }\n\n    }\n});\n\n})();\n"
  },
  {
    "path": "yshop-drink-vue3/src/App.vue",
    "content": "<script lang=\"ts\" setup>\nimport { isDark } from '@/utils/is'\nimport { useAppStore } from '@/store/modules/app'\nimport { useDesign } from '@/hooks/web/useDesign'\nimport { CACHE_KEY, useCache } from '@/hooks/web/useCache'\nimport routerSearch from '@/components/RouterSearch/index.vue'\n\ndefineOptions({ name: 'APP' })\n\nconst { getPrefixCls } = useDesign()\nconst prefixCls = getPrefixCls('app')\nconst appStore = useAppStore()\nconst currentSize = computed(() => appStore.getCurrentSize)\nconst greyMode = computed(() => appStore.getGreyMode)\nconst { wsCache } = useCache()\n\n// 根据浏览器当前主题设置系统主题色\nconst setDefaultTheme = () => {\n  let isDarkTheme = wsCache.get(CACHE_KEY.IS_DARK)\n  if (isDarkTheme === null) {\n    isDarkTheme = isDark()\n  }\n  appStore.setIsDark(isDarkTheme)\n}\nsetDefaultTheme()\n</script>\n<template>\n  <ConfigGlobal :size=\"currentSize\">\n    <RouterView :class=\"greyMode ? `${prefixCls}-grey-mode` : ''\" />\n    <routerSearch />\n  </ConfigGlobal>\n</template>\n<style lang=\"scss\">\n$prefix-cls: #{$namespace}-app;\n\n.size {\n  width: 100%;\n  height: 100%;\n}\n\nhtml,\nbody {\n  @extend .size;\n\n  padding: 0 !important;\n  margin: 0;\n  overflow: hidden;\n\n  #app {\n    @extend .size;\n  }\n}\n\n.#{$prefix-cls}-grey-mode {\n  filter: grayscale(100%);\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/express/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface ExpressVO {\n  id: number\n  code: string\n  name: string\n  sort: number\n}\n// 查询快递公司列表\nexport const getExpressList = async () => {\n  return await request.get({ url: `/order/express/list` })\n}\n\n// 查询快递公司列表\nexport const getExpressPage = async (params: ExpressPageReqVO) => {\n  return await request.get({ url: `/order/express/page`, params })\n}\n\n// 查询快递公司详情\nexport const getExpress = async (id: number) => {\n  return await request.get({ url: `/order/express/get?id=` + id })\n}\n\n// 查询快递鸟公司配置详情\nexport const getExpressSet = async () => {\n  return await request.get({ url: `/order/express/set` })\n}\n\nexport const postExpressSet = async (data) => {\n  return await request.post({ url: `/order/express/set`,data })\n}\n\n\n\n// 新增快递公司\nexport const createExpress = async (data: ExpressVO) => {\n  return await request.post({ url: `/order/express/create`, data })\n}\n\n// 修改快递公司\nexport const updateExpress = async (data: ExpressVO) => {\n  return await request.put({ url: `/order/express/update`, data })\n}\n\n// 删除快递公司\nexport const deleteExpress = async (id: number) => {\n  return await request.delete({ url: `/order/express/delete?id=` + id })\n}\n\n// 导出快递公司 Excel\nexport const exportExpress = async (params) => {\n  return await request.download({ url: `/order/express/export-excel`, params })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/infra/apiAccessLog/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface ApiAccessLogVO {\n  id: number\n  traceId: string\n  userId: number\n  userType: number\n  applicationName: string\n  requestMethod: string\n  requestParams: string\n  responseBody: string\n  requestUrl: string\n  userIp: string\n  userAgent: string\n  operateModule: string\n  operateName: string\n  operateType: number\n  beginTime: Date\n  endTime: Date\n  duration: number\n  resultCode: number\n  resultMsg: string\n  createTime: Date\n}\n\n// 查询列表API 访问日志\nexport const getApiAccessLogPage = (params: PageParam) => {\n  return request.get({ url: '/infra/api-access-log/page', params })\n}\n\n// 导出API 访问日志\nexport const exportApiAccessLog = (params) => {\n  return request.download({ url: '/infra/api-access-log/export-excel', params })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/infra/apiErrorLog/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface ApiErrorLogVO {\n  id: number\n  traceId: string\n  userId: number\n  userType: number\n  applicationName: string\n  requestMethod: string\n  requestParams: string\n  requestUrl: string\n  userIp: string\n  userAgent: string\n  exceptionTime: Date\n  exceptionName: string\n  exceptionMessage: string\n  exceptionRootCauseMessage: string\n  exceptionStackTrace: string\n  exceptionClassName: string\n  exceptionFileName: string\n  exceptionMethodName: string\n  exceptionLineNumber: number\n  processUserId: number\n  processStatus: number\n  processTime: Date\n  resultCode: number\n  createTime: Date\n}\n\n// 查询列表API 访问日志\nexport const getApiErrorLogPage = (params: PageParam) => {\n  return request.get({ url: '/infra/api-error-log/page', params })\n}\n\n// 更新 API 错误日志的处理状态\nexport const updateApiErrorLogPage = (id: number, processStatus: number) => {\n  return request.put({\n    url: '/infra/api-error-log/update-status?id=' + id + '&processStatus=' + processStatus\n  })\n}\n\n// 导出API 访问日志\nexport const exportApiErrorLog = (params) => {\n  return request.download({\n    url: '/infra/api-error-log/export-excel',\n    params\n  })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/infra/codegen/index.ts",
    "content": "import request from '@/config/axios'\n\nexport type CodegenTableVO = {\n  id: number\n  tableId: number\n  isParentMenuIdValid: boolean\n  dataSourceConfigId: number\n  scene: number\n  tableName: string\n  tableComment: string\n  remark: string\n  moduleName: string\n  businessName: string\n  className: string\n  classComment: string\n  author: string\n  createTime: Date\n  updateTime: Date\n  templateType: number\n  parentMenuId: number\n}\n\nexport type CodegenColumnVO = {\n  id: number\n  tableId: number\n  columnName: string\n  dataType: string\n  columnComment: string\n  nullable: number\n  primaryKey: number\n  ordinalPosition: number\n  javaType: string\n  javaField: string\n  dictType: string\n  example: string\n  createOperation: number\n  updateOperation: number\n  listOperation: number\n  listOperationCondition: string\n  listOperationResult: number\n  htmlType: string\n}\n\nexport type DatabaseTableVO = {\n  name: string\n  comment: string\n}\n\nexport type CodegenDetailVO = {\n  table: CodegenTableVO\n  columns: CodegenColumnVO[]\n}\n\nexport type CodegenPreviewVO = {\n  filePath: string\n  code: string\n}\n\nexport type CodegenUpdateReqVO = {\n  table: CodegenTableVO | any\n  columns: CodegenColumnVO[]\n}\n\nexport type CodegenCreateListReqVO = {\n  dataSourceConfigId: number\n  tableNames: string[]\n}\n\n// 查询列表代码生成表定义\nexport const getCodegenTableList = (dataSourceConfigId: number) => {\n  return request.get({ url: '/infra/codegen/table/list?dataSourceConfigId=' + dataSourceConfigId })\n}\n\n// 查询列表代码生成表定义\nexport const getCodegenTablePage = (params: PageParam) => {\n  return request.get({ url: '/infra/codegen/table/page', params })\n}\n\n// 查询详情代码生成表定义\nexport const getCodegenTable = (id: number) => {\n  return request.get({ url: '/infra/codegen/detail?tableId=' + id })\n}\n\n// 新增代码生成表定义\nexport const createCodegenTable = (data: CodegenCreateListReqVO) => {\n  return request.post({ url: '/infra/codegen/create', data })\n}\n\n// 修改代码生成表定义\nexport const updateCodegenTable = (data: CodegenUpdateReqVO) => {\n  return request.put({ url: '/infra/codegen/update', data })\n}\n\n// 基于数据库的表结构，同步数据库的表和字段定义\nexport const syncCodegenFromDB = (id: number) => {\n  return request.put({ url: '/infra/codegen/sync-from-db?tableId=' + id })\n}\n\n// 预览生成代码\nexport const previewCodegen = (id: number) => {\n  return request.get({ url: '/infra/codegen/preview?tableId=' + id })\n}\n\n// 下载生成代码\nexport const downloadCodegen = (id: number) => {\n  return request.download({ url: '/infra/codegen/download?tableId=' + id })\n}\n\n// 获得表定义\nexport const getSchemaTableList = (params) => {\n  return request.get({ url: '/infra/codegen/db/table/list', params })\n}\n\n// 基于数据库的表结构，创建代码生成器的表定义\nexport const createCodegenList = (data) => {\n  return request.post({ url: '/infra/codegen/create-list', data })\n}\n\n// 删除代码生成表定义\nexport const deleteCodegenTable = (id: number) => {\n  return request.delete({ url: '/infra/codegen/delete?tableId=' + id })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/infra/config/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface ConfigVO {\n  id: number | undefined\n  category: string\n  name: string\n  key: string\n  value: string\n  type: number\n  visible: boolean\n  remark: string\n  createTime: Date\n}\n\n// 查询参数列表\nexport const getConfigPage = (params: PageParam) => {\n  return request.get({ url: '/infra/config/page', params })\n}\n\n// 查询参数详情\nexport const getConfig = (id: number) => {\n  return request.get({ url: '/infra/config/get?id=' + id })\n}\n\n// 根据参数键名查询参数值\nexport const getConfigKey = (configKey: string) => {\n  return request.get({ url: '/infra/config/get-value-by-key?key=' + configKey })\n}\n\n// 新增参数\nexport const createConfig = (data: ConfigVO) => {\n  return request.post({ url: '/infra/config/create', data })\n}\n\n// 修改参数\nexport const updateConfig = (data: ConfigVO) => {\n  return request.put({ url: '/infra/config/update', data })\n}\n\n// 删除参数\nexport const deleteConfig = (id: number) => {\n  return request.delete({ url: '/infra/config/delete?id=' + id })\n}\n\n// 导出参数\nexport const exportConfig = (params) => {\n  return request.download({ url: '/infra/config/export', params })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/infra/dataSourceConfig/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface DataSourceConfigVO {\n  id: number | undefined\n  name: string\n  url: string\n  username: string\n  password: string\n  createTime?: Date\n}\n\n// 新增数据源配置\nexport const createDataSourceConfig = (data: DataSourceConfigVO) => {\n  return request.post({ url: '/infra/data-source-config/create', data })\n}\n\n// 修改数据源配置\nexport const updateDataSourceConfig = (data: DataSourceConfigVO) => {\n  return request.put({ url: '/infra/data-source-config/update', data })\n}\n\n// 删除数据源配置\nexport const deleteDataSourceConfig = (id: number) => {\n  return request.delete({ url: '/infra/data-source-config/delete?id=' + id })\n}\n\n// 查询数据源配置详情\nexport const getDataSourceConfig = (id: number) => {\n  return request.get({ url: '/infra/data-source-config/get?id=' + id })\n}\n\n// 查询数据源配置列表\nexport const getDataSourceConfigList = () => {\n  return request.get({ url: '/infra/data-source-config/list' })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/infra/demo/demo01/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface Demo01ContactVO {\n  id: number\n  name: string\n  sex: number\n  birthday: Date\n  description: string\n  avatar: string\n}\n\n// 查询示例联系人分页\nexport const getDemo01ContactPage = async (params) => {\n  return await request.get({ url: `/infra/demo01-contact/page`, params })\n}\n\n// 查询示例联系人详情\nexport const getDemo01Contact = async (id: number) => {\n  return await request.get({ url: `/infra/demo01-contact/get?id=` + id })\n}\n\n// 新增示例联系人\nexport const createDemo01Contact = async (data: Demo01ContactVO) => {\n  return await request.post({ url: `/infra/demo01-contact/create`, data })\n}\n\n// 修改示例联系人\nexport const updateDemo01Contact = async (data: Demo01ContactVO) => {\n  return await request.put({ url: `/infra/demo01-contact/update`, data })\n}\n\n// 删除示例联系人\nexport const deleteDemo01Contact = async (id: number) => {\n  return await request.delete({ url: `/infra/demo01-contact/delete?id=` + id })\n}\n\n// 导出示例联系人 Excel\nexport const exportDemo01Contact = async (params) => {\n  return await request.download({ url: `/infra/demo01-contact/export-excel`, params })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/infra/demo/demo02/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface Demo02CategoryVO {\n  id: number\n  name: string\n  parentId: number\n}\n\n// 查询示例分类列表\nexport const getDemo02CategoryList = async () => {\n  return await request.get({ url: `/infra/demo02-category/list` })\n}\n\n// 查询示例分类详情\nexport const getDemo02Category = async (id: number) => {\n  return await request.get({ url: `/infra/demo02-category/get?id=` + id })\n}\n\n// 新增示例分类\nexport const createDemo02Category = async (data: Demo02CategoryVO) => {\n  return await request.post({ url: `/infra/demo02-category/create`, data })\n}\n\n// 修改示例分类\nexport const updateDemo02Category = async (data: Demo02CategoryVO) => {\n  return await request.put({ url: `/infra/demo02-category/update`, data })\n}\n\n// 删除示例分类\nexport const deleteDemo02Category = async (id: number) => {\n  return await request.delete({ url: `/infra/demo02-category/delete?id=` + id })\n}\n\n// 导出示例分类 Excel\nexport const exportDemo02Category = async (params) => {\n  return await request.download({ url: `/infra/demo02-category/export-excel`, params })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/infra/demo/demo03/erp/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface Demo03StudentVO {\n  id: number\n  name: string\n  sex: number\n  birthday: Date\n  description: string\n}\n\n// 查询学生分页\nexport const getDemo03StudentPage = async (params) => {\n  return await request.get({ url: `/infra/demo03-student/page`, params })\n}\n\n// 查询学生详情\nexport const getDemo03Student = async (id: number) => {\n  return await request.get({ url: `/infra/demo03-student/get?id=` + id })\n}\n\n// 新增学生\nexport const createDemo03Student = async (data: Demo03StudentVO) => {\n  return await request.post({ url: `/infra/demo03-student/create`, data })\n}\n\n// 修改学生\nexport const updateDemo03Student = async (data: Demo03StudentVO) => {\n  return await request.put({ url: `/infra/demo03-student/update`, data })\n}\n\n// 删除学生\nexport const deleteDemo03Student = async (id: number) => {\n  return await request.delete({ url: `/infra/demo03-student/delete?id=` + id })\n}\n\n// 导出学生 Excel\nexport const exportDemo03Student = async (params) => {\n  return await request.download({ url: `/infra/demo03-student/export-excel`, params })\n}\n\n// ==================== 子表（学生课程） ====================\n\n// 获得学生课程分页\nexport const getDemo03CoursePage = async (params) => {\n  return await request.get({ url: `/infra/demo03-student/demo03-course/page`, params })\n}\n// 新增学生课程\nexport const createDemo03Course = async (data) => {\n  return await request.post({ url: `/infra/demo03-student/demo03-course/create`, data })\n}\n\n// 修改学生课程\nexport const updateDemo03Course = async (data) => {\n  return await request.put({ url: `/infra/demo03-student/demo03-course/update`, data })\n}\n\n// 删除学生课程\nexport const deleteDemo03Course = async (id: number) => {\n  return await request.delete({ url: `/infra/demo03-student/demo03-course/delete?id=` + id })\n}\n\n// 获得学生课程\nexport const getDemo03Course = async (id: number) => {\n  return await request.get({ url: `/infra/demo03-student/demo03-course/get?id=` + id })\n}\n\n// ==================== 子表（学生班级） ====================\n\n// 获得学生班级分页\nexport const getDemo03GradePage = async (params) => {\n  return await request.get({ url: `/infra/demo03-student/demo03-grade/page`, params })\n}\n// 新增学生班级\nexport const createDemo03Grade = async (data) => {\n  return await request.post({ url: `/infra/demo03-student/demo03-grade/create`, data })\n}\n\n// 修改学生班级\nexport const updateDemo03Grade = async (data) => {\n  return await request.put({ url: `/infra/demo03-student/demo03-grade/update`, data })\n}\n\n// 删除学生班级\nexport const deleteDemo03Grade = async (id: number) => {\n  return await request.delete({ url: `/infra/demo03-student/demo03-grade/delete?id=` + id })\n}\n\n// 获得学生班级\nexport const getDemo03Grade = async (id: number) => {\n  return await request.get({ url: `/infra/demo03-student/demo03-grade/get?id=` + id })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/infra/demo/demo03/inner/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface Demo03StudentVO {\n  id: number\n  name: string\n  sex: number\n  birthday: Date\n  description: string\n}\n\n// 查询学生分页\nexport const getDemo03StudentPage = async (params) => {\n  return await request.get({ url: `/infra/demo03-student/page`, params })\n}\n\n// 查询学生详情\nexport const getDemo03Student = async (id: number) => {\n  return await request.get({ url: `/infra/demo03-student/get?id=` + id })\n}\n\n// 新增学生\nexport const createDemo03Student = async (data: Demo03StudentVO) => {\n  return await request.post({ url: `/infra/demo03-student/create`, data })\n}\n\n// 修改学生\nexport const updateDemo03Student = async (data: Demo03StudentVO) => {\n  return await request.put({ url: `/infra/demo03-student/update`, data })\n}\n\n// 删除学生\nexport const deleteDemo03Student = async (id: number) => {\n  return await request.delete({ url: `/infra/demo03-student/delete?id=` + id })\n}\n\n// 导出学生 Excel\nexport const exportDemo03Student = async (params) => {\n  return await request.download({ url: `/infra/demo03-student/export-excel`, params })\n}\n\n// ==================== 子表（学生课程） ====================\n\n// 获得学生课程列表\nexport const getDemo03CourseListByStudentId = async (studentId) => {\n  return await request.get({\n    url: `/infra/demo03-student/demo03-course/list-by-student-id?studentId=` + studentId\n  })\n}\n\n// ==================== 子表（学生班级） ====================\n\n// 获得学生班级\nexport const getDemo03GradeByStudentId = async (studentId) => {\n  return await request.get({\n    url: `/infra/demo03-student/demo03-grade/get-by-student-id?studentId=` + studentId\n  })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/infra/demo/demo03/normal/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface Demo03StudentVO {\n  id: number\n  name: string\n  sex: number\n  birthday: Date\n  description: string\n}\n\n// 查询学生分页\nexport const getDemo03StudentPage = async (params) => {\n  return await request.get({ url: `/infra/demo03-student/page`, params })\n}\n\n// 查询学生详情\nexport const getDemo03Student = async (id: number) => {\n  return await request.get({ url: `/infra/demo03-student/get?id=` + id })\n}\n\n// 新增学生\nexport const createDemo03Student = async (data: Demo03StudentVO) => {\n  return await request.post({ url: `/infra/demo03-student/create`, data })\n}\n\n// 修改学生\nexport const updateDemo03Student = async (data: Demo03StudentVO) => {\n  return await request.put({ url: `/infra/demo03-student/update`, data })\n}\n\n// 删除学生\nexport const deleteDemo03Student = async (id: number) => {\n  return await request.delete({ url: `/infra/demo03-student/delete?id=` + id })\n}\n\n// 导出学生 Excel\nexport const exportDemo03Student = async (params) => {\n  return await request.download({ url: `/infra/demo03-student/export-excel`, params })\n}\n\n// ==================== 子表（学生课程） ====================\n\n// 获得学生课程列表\nexport const getDemo03CourseListByStudentId = async (studentId) => {\n  return await request.get({\n    url: `/infra/demo03-student/demo03-course/list-by-student-id?studentId=` + studentId\n  })\n}\n\n// ==================== 子表（学生班级） ====================\n\n// 获得学生班级\nexport const getDemo03GradeByStudentId = async (studentId) => {\n  return await request.get({\n    url: `/infra/demo03-student/demo03-grade/get-by-student-id?studentId=` + studentId\n  })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/infra/file/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface FilePageReqVO extends PageParam {\n  path?: string\n  type?: string\n  createTime?: Date[]\n}\n\n// 文件预签名地址 Response VO\nexport interface FilePresignedUrlRespVO {\n  // 文件配置编号\n  configId: number\n  // 文件上传 URL\n  uploadUrl: string\n  // 文件 URL\n  url: string\n}\n\n// 查询文件列表\nexport const getFilePage = (params: FilePageReqVO) => {\n  return request.get({ url: '/infra/file/page', params })\n}\n\n// 删除文件\nexport const deleteFile = (id: number) => {\n  return request.delete({ url: '/infra/file/delete?id=' + id })\n}\n\n// 获取文件预签名地址\nexport const getFilePresignedUrl = (path: string) => {\n  return request.get<FilePresignedUrlRespVO>({\n    url: '/infra/file/presigned-url',\n    params: { path }\n  })\n}\n\n// 创建文件\nexport const createFile = (data: any) => {\n  return request.post({ url: '/infra/file/create', data })\n}\n\n// 上传文件\nexport const updateFile = (data: any) => {\n  return request.upload({ url: '/infra/file/upload', data })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/infra/fileConfig/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface FileClientConfig {\n  basePath: string\n  host?: string\n  port?: number\n  username?: string\n  password?: string\n  mode?: string\n  endpoint?: string\n  bucket?: string\n  accessKey?: string\n  accessSecret?: string\n  domain: string\n}\n\nexport interface FileConfigVO {\n  id: number\n  name: string\n  storage?: number\n  master: boolean\n  visible: boolean\n  config: FileClientConfig\n  remark: string\n  createTime: Date\n}\n\n// 查询文件配置列表\nexport const getFileConfigPage = (params: PageParam) => {\n  return request.get({ url: '/infra/file-config/page', params })\n}\n\n// 查询文件配置详情\nexport const getFileConfig = (id: number) => {\n  return request.get({ url: '/infra/file-config/get?id=' + id })\n}\n\n// 更新文件配置为主配置\nexport const updateFileConfigMaster = (id: number) => {\n  return request.put({ url: '/infra/file-config/update-master?id=' + id })\n}\n\n// 新增文件配置\nexport const createFileConfig = (data: FileConfigVO) => {\n  return request.post({ url: '/infra/file-config/create', data })\n}\n\n// 修改文件配置\nexport const updateFileConfig = (data: FileConfigVO) => {\n  return request.put({ url: '/infra/file-config/update', data })\n}\n\n// 删除文件配置\nexport const deleteFileConfig = (id: number) => {\n  return request.delete({ url: '/infra/file-config/delete?id=' + id })\n}\n\n// 测试文件配置\nexport const testFileConfig = (id: number) => {\n  return request.get({ url: '/infra/file-config/test?id=' + id })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/infra/job/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface JobVO {\n  id: number\n  name: string\n  status: number\n  handlerName: string\n  handlerParam: string\n  cronExpression: string\n  retryCount: number\n  retryInterval: number\n  monitorTimeout: number\n  createTime: Date\n}\n\n// 任务列表\nexport const getJobPage = (params: PageParam) => {\n  return request.get({ url: '/infra/job/page', params })\n}\n\n// 任务详情\nexport const getJob = (id: number) => {\n  return request.get({ url: '/infra/job/get?id=' + id })\n}\n\n// 新增任务\nexport const createJob = (data: JobVO) => {\n  return request.post({ url: '/infra/job/create', data })\n}\n\n// 修改定时任务调度\nexport const updateJob = (data: JobVO) => {\n  return request.put({ url: '/infra/job/update', data })\n}\n\n// 删除定时任务调度\nexport const deleteJob = (id: number) => {\n  return request.delete({ url: '/infra/job/delete?id=' + id })\n}\n\n// 导出定时任务调度\nexport const exportJob = (params) => {\n  return request.download({ url: '/infra/job/export-excel', params })\n}\n\n// 任务状态修改\nexport const updateJobStatus = (id: number, status: number) => {\n  const params = {\n    id,\n    status\n  }\n  return request.put({ url: '/infra/job/update-status', params })\n}\n\n// 定时任务立即执行一次\nexport const runJob = (id: number) => {\n  return request.put({ url: '/infra/job/trigger?id=' + id })\n}\n\n// 获得定时任务的下 n 次执行时间\nexport const getJobNextTimes = (id: number) => {\n  return request.get({ url: '/infra/job/get_next_times?id=' + id })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/infra/jobLog/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface JobLogVO {\n  id: number\n  jobId: number\n  handlerName: string\n  handlerParam: string\n  cronExpression: string\n  executeIndex: string\n  beginTime: Date\n  endTime: Date\n  duration: string\n  status: number\n  createTime: string\n}\n\n// 任务日志列表\nexport const getJobLogPage = (params: PageParam) => {\n  return request.get({ url: '/infra/job-log/page', params })\n}\n\n// 任务日志详情\nexport const getJobLog = (id: number) => {\n  return request.get({ url: '/infra/job-log/get?id=' + id })\n}\n\n// 导出定时任务日志\nexport const exportJobLog = (params) => {\n  return request.download({\n    url: '/infra/job-log/export-excel',\n    params\n  })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/infra/redis/index.ts",
    "content": "import request from '@/config/axios'\n\n/**\n * 获取redis 监控信息\n */\nexport const getCache = () => {\n  return request.get({ url: '/infra/redis/get-monitor-info' })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/infra/redis/types.ts",
    "content": "export interface RedisMonitorInfoVO {\n  info: RedisInfoVO\n  dbSize: number\n  commandStats: RedisCommandStatsVO[]\n}\n\nexport interface RedisInfoVO {\n  io_threaded_reads_processed: string\n  tracking_clients: string\n  uptime_in_seconds: string\n  cluster_connections: string\n  current_cow_size: string\n  maxmemory_human: string\n  aof_last_cow_size: string\n  master_replid2: string\n  mem_replication_backlog: string\n  aof_rewrite_scheduled: string\n  total_net_input_bytes: string\n  rss_overhead_ratio: string\n  hz: string\n  current_cow_size_age: string\n  redis_build_id: string\n  errorstat_BUSYGROUP: string\n  aof_last_bgrewrite_status: string\n  multiplexing_api: string\n  client_recent_max_output_buffer: string\n  allocator_resident: string\n  mem_fragmentation_bytes: string\n  aof_current_size: string\n  repl_backlog_first_byte_offset: string\n  tracking_total_prefixes: string\n  redis_mode: string\n  redis_git_dirty: string\n  aof_delayed_fsync: string\n  allocator_rss_bytes: string\n  repl_backlog_histlen: string\n  io_threads_active: string\n  rss_overhead_bytes: string\n  total_system_memory: string\n  loading: string\n  evicted_keys: string\n  maxclients: string\n  cluster_enabled: string\n  redis_version: string\n  repl_backlog_active: string\n  mem_aof_buffer: string\n  allocator_frag_bytes: string\n  io_threaded_writes_processed: string\n  instantaneous_ops_per_sec: string\n  used_memory_human: string\n  total_error_replies: string\n  role: string\n  maxmemory: string\n  used_memory_lua: string\n  rdb_current_bgsave_time_sec: string\n  used_memory_startup: string\n  used_cpu_sys_main_thread: string\n  lazyfree_pending_objects: string\n  aof_pending_bio_fsync: string\n  used_memory_dataset_perc: string\n  allocator_frag_ratio: string\n  arch_bits: string\n  used_cpu_user_main_thread: string\n  mem_clients_normal: string\n  expired_time_cap_reached_count: string\n  unexpected_error_replies: string\n  mem_fragmentation_ratio: string\n  aof_last_rewrite_time_sec: string\n  master_replid: string\n  aof_rewrite_in_progress: string\n  lru_clock: string\n  maxmemory_policy: string\n  run_id: string\n  latest_fork_usec: string\n  tracking_total_items: string\n  total_commands_processed: string\n  expired_keys: string\n  errorstat_ERR: string\n  used_memory: string\n  module_fork_in_progress: string\n  errorstat_WRONGPASS: string\n  aof_buffer_length: string\n  dump_payload_sanitizations: string\n  mem_clients_slaves: string\n  keyspace_misses: string\n  server_time_usec: string\n  executable: string\n  lazyfreed_objects: string\n  db0: string\n  used_memory_peak_human: string\n  keyspace_hits: string\n  rdb_last_cow_size: string\n  aof_pending_rewrite: string\n  used_memory_overhead: string\n  active_defrag_hits: string\n  tcp_port: string\n  uptime_in_days: string\n  used_memory_peak_perc: string\n  current_save_keys_processed: string\n  blocked_clients: string\n  total_reads_processed: string\n  expire_cycle_cpu_milliseconds: string\n  sync_partial_err: string\n  used_memory_scripts_human: string\n  aof_current_rewrite_time_sec: string\n  aof_enabled: string\n  process_supervised: string\n  master_repl_offset: string\n  used_memory_dataset: string\n  used_cpu_user: string\n  rdb_last_bgsave_status: string\n  tracking_total_keys: string\n  atomicvar_api: string\n  allocator_rss_ratio: string\n  client_recent_max_input_buffer: string\n  clients_in_timeout_table: string\n  aof_last_write_status: string\n  mem_allocator: string\n  used_memory_scripts: string\n  used_memory_peak: string\n  process_id: string\n  master_failover_state: string\n  errorstat_NOAUTH: string\n  used_cpu_sys: string\n  repl_backlog_size: string\n  connected_slaves: string\n  current_save_keys_total: string\n  gcc_version: string\n  total_system_memory_human: string\n  sync_full: string\n  connected_clients: string\n  module_fork_last_cow_size: string\n  total_writes_processed: string\n  allocator_active: string\n  total_net_output_bytes: string\n  pubsub_channels: string\n  current_fork_perc: string\n  active_defrag_key_hits: string\n  rdb_changes_since_last_save: string\n  instantaneous_input_kbps: string\n  used_memory_rss_human: string\n  configured_hz: string\n  expired_stale_perc: string\n  active_defrag_misses: string\n  used_cpu_sys_children: string\n  number_of_cached_scripts: string\n  sync_partial_ok: string\n  used_memory_lua_human: string\n  rdb_last_save_time: string\n  pubsub_patterns: string\n  slave_expires_tracked_keys: string\n  redis_git_sha1: string\n  used_memory_rss: string\n  rdb_last_bgsave_time_sec: string\n  os: string\n  mem_not_counted_for_evict: string\n  active_defrag_running: string\n  rejected_connections: string\n  aof_rewrite_buffer_length: string\n  total_forks: string\n  active_defrag_key_misses: string\n  allocator_allocated: string\n  aof_base_size: string\n  instantaneous_output_kbps: string\n  second_repl_offset: string\n  rdb_bgsave_in_progress: string\n  used_cpu_user_children: string\n  total_connections_received: string\n  migrate_cached_sockets: string\n}\n\nexport interface RedisCommandStatsVO {\n  command: string\n  calls: number\n  usec: number\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/login/index.ts",
    "content": "import request from '@/config/axios'\nimport { getRefreshToken } from '@/utils/auth'\nimport type { UserLoginVO } from './types'\n\nexport interface SmsCodeVO {\n  mobile: string\n  scene: number\n}\n\nexport interface SmsLoginVO {\n  mobile: string\n  code: string\n}\n\n// 登录\nexport const login = (data: UserLoginVO) => {\n  return request.post({ url: '/system/auth/login', data })\n}\n\n// 刷新访问令牌\nexport const refreshToken = () => {\n  return request.post({ url: '/system/auth/refresh-token?refreshToken=' + getRefreshToken() })\n}\n\n// 使用租户名，获得租户编号\nexport const getTenantIdByName = (name: string) => {\n  return request.get({ url: '/system/tenant/get-id-by-name?name=' + name })\n}\n\n// 使用租户域名，获得租户信息\nexport const getTenantByWebsite = (website: string) => {\n  return request.get({ url: '/system/tenant/get-by-website?website=' + website })\n}\n\n// 登出\nexport const loginOut = () => {\n  return request.post({ url: '/system/auth/logout' })\n}\n\n// 获取用户权限信息\nexport const getInfo = () => {\n  return request.get({ url: '/system/auth/get-permission-info' })\n}\n\n//获取登录验证码\nexport const sendSmsCode = (data: SmsCodeVO) => {\n  return request.post({ url: '/system/auth/send-sms-code', data })\n}\n\n// 短信验证码登录\nexport const smsLogin = (data: SmsLoginVO) => {\n  return request.post({ url: '/system/auth/sms-login', data })\n}\n\n// 社交快捷登录，使用 code 授权码\nexport function socialLogin(type: string, code: string, state: string) {\n  return request.post({\n    url: '/system/auth/social-login',\n    data: {\n      type,\n      code,\n      state\n    }\n  })\n}\n\n// 社交授权的跳转\nexport const socialAuthRedirect = (type: number, redirectUri: string) => {\n  return request.get({\n    url: '/system/auth/social-auth-redirect?type=' + type + '&redirectUri=' + redirectUri\n  })\n}\n// 获取验证图片以及 token\nexport const getCode = (data) => {\n  return request.postOriginal({ url: 'system/captcha/get', data })\n}\n\n// 滑动或者点选验证\nexport const reqCheck = (data) => {\n  return request.postOriginal({ url: 'system/captcha/check', data })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/login/oauth2/index.ts",
    "content": "import request from '@/config/axios'\n\n// 获得授权信息\nexport const getAuthorize = (clientId: string) => {\n  return request.get({ url: '/system/oauth2/authorize?clientId=' + clientId })\n}\n\n// 发起授权\nexport const authorize = (\n  responseType: string,\n  clientId: string,\n  redirectUri: string,\n  state: string,\n  autoApprove: boolean,\n  checkedScopes: string[],\n  uncheckedScopes: string[]\n) => {\n  // 构建 scopes\n  const scopes = {}\n  for (const scope of checkedScopes) {\n    scopes[scope] = true\n  }\n  for (const scope of uncheckedScopes) {\n    scopes[scope] = false\n  }\n  // 发起请求\n  return request.post({\n    url: '/system/oauth2/authorize',\n    headers: {\n      'Content-type': 'application/x-www-form-urlencoded'\n    },\n    params: {\n      response_type: responseType,\n      client_id: clientId,\n      redirect_uri: redirectUri,\n      state: state,\n      auto_approve: autoApprove,\n      scope: JSON.stringify(scopes)\n    }\n  })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/login/types.ts",
    "content": "export type UserLoginVO = {\n  username: string\n  password: string\n  captchaVerification: string\n  socialType?: string\n  socialCode?: string\n  socialState?: string\n}\n\nexport type TokenType = {\n  id: number // 编号\n  accessToken: string // 访问令牌\n  refreshToken: string // 刷新令牌\n  userId: number // 用户编号\n  userType: number //用户类型\n  clientId: string //客户端编号\n  expiresTime: number //过期时间\n}\n\nexport type UserVO = {\n  id: number\n  username: string\n  nickname: string\n  deptId: number\n  email: string\n  mobile: string\n  sex: number\n  avatar: string\n  loginIp: string\n  loginDate: string\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/mall/coupon/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface VO {\n  id: number\n  shopId: string\n  shopName: string\n  title: string\n  switch: boolean\n  least: number\n  value: number\n  startTime: Date\n  endtIme: Date\n  weigh: number\n  type: boolean\n  exchangeCode: string\n  receive: number\n  distribute: number\n  score: number\n  instructions: string\n  image: string\n  limit: number\n}\n// 查询优惠券列表\nexport const getCouponList = async () => {\n  return await request.get({ url: `/coupon/list` })\n}\n// 查询优惠券列表\nexport const getCouponPage = async (params: PageReqVO) => {\n  return await request.get({ url: `/coupon/page`, params })\n}\n\n// 查询优惠券详情\nexport const getCoupon = async (id: number) => {\n  return await request.get({ url: `/coupon/get?id=` + id })\n}\n\n// 新增优惠券\nexport const createCoupon = async (data: VO) => {\n  return await request.post({ url: `/coupon/create`, data })\n}\n\n// 修改优惠券\nexport const updateCoupon = async (data: VO) => {\n  return await request.put({ url: `/coupon/update`, data })\n}\n\n// 删除优惠券\nexport const deleteCoupon = async (id: number) => {\n  return await request.delete({ url: `/coupon/delete?id=` + id })\n}\n\n// 导出优惠券 Excel\nexport const exportCoupon = async (params) => {\n  return await request.download({ url: `/coupon/export-excel`, params })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/mall/coupon/user/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface UserVO {\n  id: number\n  shopId: string\n  shopName: string\n  title: string\n  least: number\n  value: number\n  starttime: number\n  endtime: number\n  createtime: number\n  updatetime: number\n  type: boolean\n  score: number\n  instructions: string\n  image: string\n  userId: number\n  status: boolean\n  couponId: number\n  exchangeCode: string\n}\nexport const getUserList = async (id) => {\n  return await request.get({ url: `/coupon/user/list?couponId=`+id })\n}\n// 查询用户领的优惠券列表\nexport const getUserPage = async (params: UserPageReqVO) => {\n  return await request.get({ url: `/coupon/user/page`, params })\n}\n\n// 查询用户领的优惠券详情\nexport const getUser = async (id: number) => {\n  return await request.get({ url: `/coupon/user/get?id=` + id })\n}\n\n// 新增用户领的优惠券\nexport const createUser = async (data: UserVO) => {\n  return await request.post({ url: `/coupon/user/create`, data })\n}\n\n// 修改用户领的优惠券\nexport const updateUser = async (data: UserVO) => {\n  return await request.put({ url: `/coupon/user/update`, data })\n}\n\n// 删除用户领的优惠券\nexport const deleteUser = async (id: number) => {\n  return await request.delete({ url: `/coupon/user/delete?id=` + id })\n}\n\n// 导出用户领的优惠券 Excel\nexport const exportUser = async (params) => {\n  return await request.download({ url: `/coupon/user/export-excel`, params })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/mall/order/storeOrder/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface StoreOrderVO {\n  id: number\n  orderId: string\n  extendOrderId: string\n  uid: number\n  realName: string\n  userPhone: string\n  userAddress: string\n  cartId: string\n  freightPrice: number\n  totalNum: number\n  totalPrice: number\n  totalPostage: number\n  payPrice: number\n  payPostage: number\n  deductionPrice: number\n  couponId: number\n  couponPrice: number\n  paid: byte\n  payTime: Date\n  payType: string\n  status: boolean\n  refundStatus: byte\n  refundReasonWapImg: string\n  refundReasonWapExplain: string\n  refundReasonTime: Date\n  refundReasonWap: string\n  refundReason: string\n  refundPrice: number\n  deliverySn: string\n  deliveryName: string\n  deliveryType: string\n  deliveryId: string\n  gainIntegral: number\n  useIntegral: number\n  payIntegral: number\n  backIntegral: number\n  mark: string\n  unique: string\n  remark: string\n  merId: number\n  combinationId: number\n  pinkId: number\n  cost: number\n  seckillId: number\n  bargainId: number\n  verifyCode: string\n  storeId: number\n  shippingType: boolean\n  isChannel: byte\n  isSystemDel: boolean\n}\n\n// 查询订单列表\nexport const getStoreOrderPage = async (params: StoreOrderPageReqVO) => {\n  return await request.get({ url: `/order/store-order/page`, params })\n}\n\n// 查询订单详情\nexport const getStoreOrder = async (id: number) => {\n  return await request.get({ url: `/order/store-order/get?id=` + id })\n}\n\n// 新增订单\nexport const createStoreOrder = async (data: StoreOrderVO) => {\n  return await request.post({ url: `/order/store-order/create`, data })\n}\n\n// 修改订单\nexport const updateStoreOrder = async (data: StoreOrderVO) => {\n  return await request.put({ url: `/order/store-order/update`, data })\n}\n\n// 删除订单\nexport const deleteStoreOrder = async (id: number) => {\n  return await request.delete({ url: `/order/store-order/delete?id=` + id })\n}\n\nexport const payStoreOrder = async (id: number) => {\n  return await request.get({ url: `/order/store-order/pay?id=` + id })\n}\n\nexport const takeStoreOrder = async (id: number) => {\n  return await request.get({ url: `/order/store-order/take?id=` + id })\n}\n\nexport const rufundStoreOrder = async (data) => {\n  return await request.post({ url: `/order/store-order/refund`,data })\n}\n\n\nexport const getStoreOrderRecordList = async (id: number) => {\n  return await request.get({ url: `/order/store-order/record-list?id=` + id })\n}\n\n\n\n// 导出订单 Excel\nexport const exportStoreOrder = async (params) => {\n  return await request.download({ url: `/order/store-order/export-excel`, params })\n}\n\nexport const getLogistic = async (param1,param2) => {\n  return await request.get({ url: `/order/express/getLogistic?shipperCode=` + param1 + `&logisticCode=` + param2})\n}\n\nexport const getOrderHtml = async (param1,param2) => {\n  return await request.get({ url: `/order/store-order/printOrder?id=` + param1 + `&electId=` + param2})\n}\n\nexport const getShopCount = async () => {\n  return await request.get({ url: `/order/store-order/count`})\n}\n\nexport const orderNoticeUrl = async () => {\n  return await request.get({ url: `/order/store-order/notice`})\n}\n\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/mall/product/brand.ts",
    "content": "import request from '@/config/axios'\n\n/**\n * 商品品牌\n */\nexport interface BrandVO {\n  /**\n   * 品牌编号\n   */\n  id?: number\n  /**\n   * 品牌名称\n   */\n  name: string\n  /**\n   * 品牌图片\n   */\n  picUrl: string\n  /**\n   * 品牌排序\n   */\n  sort?: number\n  /**\n   * 品牌描述\n   */\n  description?: string\n  /**\n   * 开启状态\n   */\n  status: number\n}\n\n// 创建商品品牌\nexport const createBrand = (data: BrandVO) => {\n  return request.post({ url: '/product/brand/create', data })\n}\n\n// 更新商品品牌\nexport const updateBrand = (data: BrandVO) => {\n  return request.put({ url: '/product/brand/update', data })\n}\n\n// 删除商品品牌\nexport const deleteBrand = (id: number) => {\n  return request.delete({ url: `/product/brand/delete?id=${id}` })\n}\n\n// 获得商品品牌\nexport const getBrand = (id: number) => {\n  return request.get({ url: `/product/brand/get?id=${id}` })\n}\n\n// 获得商品品牌列表\nexport const getBrandParam = (params: PageParam) => {\n  return request.get({ url: '/product/brand/page', params })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/mall/product/category.ts",
    "content": "import request from '@/config/axios'\n\n/**\n * 产品分类\n */\nexport interface CategoryVO {\n  /**\n   * 分类编号\n   */\n  id?: number\n  /**\n   * 父分类编号\n   */\n  parentId?: number\n  /**\n   * 分类名称\n   */\n  name: string\n  /**\n   * 分类图片\n   */\n  picUrl: string\n  /**\n   * 分类排序\n   */\n  sort?: number\n  /**\n   * 分类描述\n   */\n  description?: string\n  /**\n   * 开启状态\n   */\n  status: number\n}\n\n// 创建商品分类\nexport const createCategory = (data: CategoryVO) => {\n  return request.post({ url: '/product/category/create', data })\n}\n\n// 更新商品分类\nexport const updateCategory = (data: CategoryVO) => {\n  return request.put({ url: '/product/category/update', data })\n}\n\n// 删除商品分类\nexport const deleteCategory = (id: number) => {\n  return request.delete({ url: `/product/category/delete?id=${id}` })\n}\n\n// 获得商品分类\nexport const getCategory = (id: number) => {\n  return request.get({ url: `/product/category/get?id=${id}` })\n}\n\n// 获得商品分类列表\nexport const getCategoryList = (params: any) => {\n  return request.get({ url: '/product/category/list', params })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/mall/product/product.ts",
    "content": "import request from '@/config/axios'\n\nexport interface StoreProductVO {\n  id: number\n  image: string\n  sliderImage: string\n  storeName: string\n  storeInfo: string\n  keyword: string\n  barCode: string\n  cateId: string\n  price: number\n  vipPrice: number\n  otPrice: number\n  postage: number\n  unitName: string\n  sort: number\n  sales: number\n  stock: number\n  isShow: boolean\n  isHot: boolean\n  isBenefit: boolean\n  isBest: boolean\n  isNew: boolean\n  description: string\n  isPostage: byte\n  merUse: byte\n  giveIntegral: number\n  cost: number\n  isSeckill: byte\n  isBargain: byte\n  isGood: boolean\n  ficti: number\n  browse: number\n  codePath: string\n  isSub: boolean\n  tempId: number\n  specType: boolean\n  isIntegral: byte\n  integral: number\n}\n\n// 查询商品列表\nexport const getStoreProductPage = async (params: StoreProductPageReqVO) => {\n  return await request.get({ url: `/product/store-product/page`, params })\n}\n\n// 查询商品详情\nexport const getStoreProduct = async (id: number) => {\n  return await request.get({ url: `/product/store-product/get?id=` + id })\n}\n\n// 查询商品详情\nexport const getStoreProductInfo = async (id: number) => {\n  return await request.get({ url: `/product/store-product/info/` + id })\n}\n\n// 新增商品\nexport const createStoreProduct = async (data) => {\n  return await request.post({ url: `/product/store-product/create`, data })\n}\n\n// 修改商品\nexport const updateStoreProduct = async (data: StoreProductVO) => {\n  return await request.put({ url: `/product/store-product/update`, data })\n}\n\n// 删除商品\nexport const deleteStoreProduct = async (id: number) => {\n  return await request.delete({ url: `/product/store-product/delete?id=` + id })\n}\n\n// 导出商品 Excel\nexport const exportStoreProduct = async (params) => {\n  return await request.download({ url: `/product/store-product/export-excel`, params })\n}\n// 规格格式化\nexport const isFormatAttr = async (id, data) => {\n  return await request.post({ url: '/product/store-product/isFormatAttr/' + id, data })\n}\n\n// 删除商品\nexport const saleStoreProduct = async (id,isShow) => {\n  return await request.get({ url: `/product/store-product/sale?id=` + id + `&type=` + isShow })\n}\n\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/mall/product/storeProductRelation/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface StoreProductRelationVO {\n  id: number\n  uid: number\n  productId: number\n  type: string\n  category: string\n}\n\n// 查询商品点赞和收藏列表\nexport const getStoreProductRelationPage = async (params: StoreProductRelationPageReqVO) => {\n  return await request.get({ url: `/product/store-product-relation/page`, params })\n}\n\n// 查询商品点赞和收藏详情\nexport const getStoreProductRelation = async (id: number) => {\n  return await request.get({ url: `/product/store-product-relation/get?id=` + id })\n}\n\n// 新增商品点赞和收藏\nexport const createStoreProductRelation = async (data: StoreProductRelationVO) => {\n  return await request.post({ url: `/product/store-product-relation/create`, data })\n}\n\n// 修改商品点赞和收藏\nexport const updateStoreProductRelation = async (data: StoreProductRelationVO) => {\n  return await request.put({ url: `/product/store-product-relation/update`, data })\n}\n\n// 删除商品点赞和收藏\nexport const deleteStoreProductRelation = async (id: number) => {\n  return await request.delete({ url: `/product/store-product-relation/delete?id=` + id })\n}\n\n// 导出商品点赞和收藏 Excel\nexport const exportStoreProductRelation = async (params) => {\n  return await request.download({ url: `/product/store-product-relation/export-excel`, params })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/mall/product/storeProductReply/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface StoreProductReplyVO {\n  id: number\n  uid: number\n  oid: number\n  unique: string\n  productId: number\n  replyType: string\n  productScore: boolean\n  serviceScore: boolean\n  comment: string\n  pics: string\n  merchantReplyContent: string\n  merchantReplyTime: Date\n  isReply: boolean\n}\n\n// 查询评论列表\nexport const getStoreProductReplyPage = async (params: StoreProductReplyPageReqVO) => {\n  return await request.get({ url: `/product/store-product-reply/page`, params })\n}\n\n// 查询评论详情\nexport const getStoreProductReply = async (id: number) => {\n  return await request.get({ url: `/product/store-product-reply/get?id=` + id })\n}\n\n// 新增评论\nexport const createStoreProductReply = async (data: StoreProductReplyVO) => {\n  return await request.post({ url: `/product/store-product-reply/create`, data })\n}\n\n// 修改评论\nexport const updateStoreProductReply = async (data: StoreProductReplyVO) => {\n  return await request.put({ url: `/product/store-product-reply/update`, data })\n}\n\n// 删除评论\nexport const deleteStoreProductReply = async (id: number) => {\n  return await request.delete({ url: `/product/store-product-reply/delete?id=` + id })\n}\n\n// 导出评论 Excel\nexport const exportStoreProductReply = async (params) => {\n  return await request.download({ url: `/product/store-product-reply/export-excel`, params })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/mall/shop/ads/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface AdsVO {\n  id: number\n  image: string\n  switch: boolean\n  weigh: number\n  shopId: string\n}\n\n// 查询广告图管理列表\nexport const getAdsPage = async (params: AdsPageReqVO) => {\n  return await request.get({ url: `/shop/ads/page`, params })\n}\n\n// 查询广告图管理详情\nexport const getAds = async (id: number) => {\n  return await request.get({ url: `/shop/ads/get?id=` + id })\n}\n\n// 新增广告图管理\nexport const createAds = async (data: AdsVO) => {\n  return await request.post({ url: `/shop/ads/create`, data })\n}\n\n// 修改广告图管理\nexport const updateAds = async (data: AdsVO) => {\n  return await request.put({ url: `/shop/ads/update`, data })\n}\n\n// 删除广告图管理\nexport const deleteAds = async (id: number) => {\n  return await request.delete({ url: `/shop/ads/delete?id=` + id })\n}\n\n// 导出广告图管理 Excel\nexport const exportAds = async (params) => {\n  return await request.download({ url: `/shop/ads/export-excel`, params })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/mall/shop/materialGroup/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface MaterialGroupVO {\n  id: number\n  name: string\n}\n\n// 查询素材分组列表\nexport const getMaterialGroupPage = async (params: MaterialGroupPageReqVO) => {\n  return await request.get({ url: `/shop/material-group/page`, params })\n}\n\n// 查询素材分组详情\nexport const getMaterialGroup = async (id: number) => {\n  return await request.get({ url: `/shop/material-group/get?id=` + id })\n}\n\n// 新增素材分组\nexport const createMaterialGroup = async (data: MaterialGroupVO) => {\n  return await request.post({ url: `/shop/material-group/create`, data })\n}\n\n// 修改素材分组\nexport const updateMaterialGroup = async (data: MaterialGroupVO) => {\n  return await request.put({ url: `/shop/material-group/update`, data })\n}\n\n// 删除素材分组\nexport const deleteMaterialGroup = async (id: number) => {\n  return await request.delete({ url: `/shop/material-group/delete?id=` + id })\n}\n\n// 导出素材分组 Excel\nexport const exportMaterialGroup = async (params) => {\n  return await request.download({ url: `/shop/material-group/export-excel`, params })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/mall/shop/recharge/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface RechargeVO {\n  id: number\n  name: string\n  sales: number\n  value: number\n  weigh: number\n  status: boolean\n  sellPrice: number\n}\n\n// 查询充值金额管理列表\nexport const getRechargePage = async (params: RechargePageReqVO) => {\n  return await request.get({ url: `/shop/recharge/page`, params })\n}\n\n// 查询充值金额管理详情\nexport const getRecharge = async (id: number) => {\n  return await request.get({ url: `/shop/recharge/get?id=` + id })\n}\n\n// 新增充值金额管理\nexport const createRecharge = async (data: RechargeVO) => {\n  return await request.post({ url: `/shop/recharge/create`, data })\n}\n\n// 修改充值金额管理\nexport const updateRecharge = async (data: RechargeVO) => {\n  return await request.put({ url: `/shop/recharge/update`, data })\n}\n\n// 删除充值金额管理\nexport const deleteRecharge = async (id: number) => {\n  return await request.delete({ url: `/shop/recharge/delete?id=` + id })\n}\n\n// 导出充值金额管理 Excel\nexport const exportRecharge = async (params) => {\n  return await request.download({ url: `/shop/recharge/export-excel`, params })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/mall/shop/service/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface ServiceVO {\n  id: number\n  name: string\n  image: string\n  type: string\n  content: string\n  pid: number\n  appId: string\n  pages: string\n  phone: string\n  weigh: number\n  status: boolean\n}\n\n// 查询我的服务列表\nexport const getServicePage = async (params: ServicePageReqVO) => {\n  return await request.get({ url: `/shop/service/page`, params })\n}\n\n// 查询我的服务详情\nexport const getService = async (id: number) => {\n  return await request.get({ url: `/shop/service/get?id=` + id })\n}\n\n// 新增我的服务\nexport const createService = async (data: ServiceVO) => {\n  return await request.post({ url: `/shop/service/create`, data })\n}\n\n// 修改我的服务\nexport const updateService = async (data: ServiceVO) => {\n  return await request.put({ url: `/shop/service/update`, data })\n}\n\n// 删除我的服务\nexport const deleteService = async (id: number) => {\n  return await request.delete({ url: `/shop/service/delete?id=` + id })\n}\n\n// 导出我的服务 Excel\nexport const exportService = async (params) => {\n  return await request.download({ url: `/shop/service/export-excel`, params })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/mall/shop/storeProductRule/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface StoreProductRuleVO {\n  id: number\n  ruleName: string\n  ruleValue: string\n}\n\n// 查询商品规则值(规格)列表\nexport const getStoreProductRulePage = async (params: StoreProductRulePageReqVO) => {\n  return await request.get({ url: `/product/store-product-rule/page`, params })\n}\n\n// 查询商品规则值(规格)详情\nexport const getStoreProductRule = async (id: number) => {\n  return await request.get({ url: `/product/store-product-rule/get?id=` + id })\n}\n\n// 新增商品规则值(规格)\nexport const createStoreProductRule = async (data: StoreProductRuleVO,id: number) => {\n  return await request.post({ url: `/product/store-product-rule/save/` + id, data })\n}\n\n// 修改商品规则值(规格)\nexport const updateStoreProductRule = async (data: StoreProductRuleVO) => {\n  return await request.put({ url: `/product/store-product-rule/update`, data })\n}\n\n// 删除商品规则值(规格)\nexport const deleteStoreProductRule = async (id: number) => {\n  return await request.delete({ url: `/product/store-product-rule/delete?id=` + id })\n}\n\n// 导出商品规则值(规格) Excel\nexport const exportStoreProductRule = async (params) => {\n  return await request.download({ url: `/product/store-product-rule/export-excel`, params })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/mall/store/shop/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface ShopVO {\n  id: number\n  name: string\n  mobile: string\n  image: string\n  images: string\n  address: string\n  addressMap: string\n  lng: string\n  lat: string\n  distance: number\n  minPrice: number\n  deliveryPrice: number\n  notice: string\n  status: boolean\n  adminId: string\n  uniprintId: string\n  startTime: Date\n  endTime: Date\n}\n\nexport const getShopList = async () => {\n  return await request.get({ url: `/store/shop/list` })\n}\n\n// 查询门店管理列表\nexport const getShopPage = async (params: ShopPageReqVO) => {\n  return await request.get({ url: `/store/shop/page`, params })\n}\n\n// 查询门店管理详情\nexport const getShop = async (id: number) => {\n  return await request.get({ url: `/store/shop/get?id=` + id })\n}\n\n// 新增门店管理\nexport const createShop = async (data: ShopVO) => {\n  return await request.post({ url: `/store/shop/create`, data })\n}\n\n// 修改门店管理\nexport const updateShop = async (data: ShopVO) => {\n  return await request.put({ url: `/store/shop/update`, data })\n}\n\n// 删除门店管理\nexport const deleteShop = async (id: number) => {\n  return await request.delete({ url: `/store/shop/delete?id=` + id })\n}\n\n// 导出门店管理 Excel\nexport const exportShop = async (params) => {\n  return await request.download({ url: `/store/shop/export-excel`, params })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/member/user/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface UserVO {\n  id: number\n  username: string\n  password: string\n  realName: string\n  birthday: number\n  cardId: string\n  mark: string\n  partnerId: number\n  groupId: number\n  nickname: string\n  avatar: string\n  phone: string\n  addIp: string\n  lastIp: string\n  nowMoney: number\n  brokeragePrice: number\n  integral: number\n  signNum: number\n  status: boolean\n  level: byte\n  spreadUid: number\n  spreadTime: Date\n  userType: string\n  isPromoter: byte\n  payCount: number\n  spreadCount: number\n  addres: string\n  adminid: number\n  loginType: string\n  wxProfile: string\n}\n\n// 查询用户列表\nexport const getUserPage = async (params: UserPageReqVO) => {\n  return await request.get({ url: `/member/user/page`, params })\n}\n\n// 查询用户详情\nexport const getUser = async (id: number) => {\n  return await request.get({ url: `/member/user/get?id=` + id })\n}\n\n// 新增用户\nexport const createUser = async (data: UserVO) => {\n  return await request.post({ url: `/member/user/create`, data })\n}\n\n// 修改用户\nexport const updateUser = async (data: UserVO) => {\n  return await request.put({ url: `/member/user/update`, data })\n}\n\n// 修改余额\nexport const updateMony = async (data: UserVO) => {\n  return await request.put({ url: `/member/user/updateMony`, data })\n}\n\n// 删除用户\nexport const deleteUser = async (id: number) => {\n  return await request.delete({ url: `/member/user/delete?id=` + id })\n}\n\n// 导出用户 Excel\nexport const exportUser = async (params) => {\n  return await request.download({ url: `/member/user/export-excel`, params })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/member/userAddress/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface UserAddressVO {\n  id: number\n  uid: number\n  realName: string\n  phone: string\n  province: string\n  city: string\n  cityId: number\n  district: string\n  detail: string\n  postCode: string\n  longitude: string\n  latitude: string\n  isDefault: byte\n}\n\n// 查询用户地址列表\nexport const getUserAddressPage = async (params: UserAddressPageReqVO) => {\n  return await request.get({ url: `/member/user-address/page`, params })\n}\n\n// 查询用户地址详情\nexport const getUserAddress = async (id: number) => {\n  return await request.get({ url: `/member/user-address/get?id=` + id })\n}\n\n// 新增用户地址\nexport const createUserAddress = async (data: UserAddressVO) => {\n  return await request.post({ url: `/member/user-address/create`, data })\n}\n\n// 修改用户地址\nexport const updateUserAddress = async (data: UserAddressVO) => {\n  return await request.put({ url: `/member/user-address/update`, data })\n}\n\n// 删除用户地址\nexport const deleteUserAddress = async (id: number) => {\n  return await request.delete({ url: `/member/user-address/delete?id=` + id })\n}\n\n// 导出用户地址 Excel\nexport const exportUserAddress = async (params) => {\n  return await request.download({ url: `/member/user-address/export-excel`, params })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/member/userBill/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface UserBillVO {\n  id: number\n  uid: number\n  linkId: string\n  pm: byte\n  title: string\n  category: string\n  type: string\n  number: number\n  balance: number\n  mark: string\n  status: boolean\n}\n\n// 查询用户账单列表\nexport const getUserBillPage = async (params: UserBillPageReqVO) => {\n  return await request.get({ url: `/member/user-bill/page`, params })\n}\n\n// 查询用户账单详情\nexport const getUserBill = async (id: number) => {\n  return await request.get({ url: `/member/user-bill/get?id=` + id })\n}\n\n// 新增用户账单\nexport const createUserBill = async (data: UserBillVO) => {\n  return await request.post({ url: `/member/user-bill/create`, data })\n}\n\n// 修改用户账单\nexport const updateUserBill = async (data: UserBillVO) => {\n  return await request.put({ url: `/member/user-bill/update`, data })\n}\n\n// 删除用户账单\nexport const deleteUserBill = async (id: number) => {\n  return await request.delete({ url: `/member/user-bill/delete?id=` + id })\n}\n\n// 导出用户账单 Excel\nexport const exportUserBill = async (params) => {\n  return await request.download({ url: `/member/user-bill/export-excel`, params })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/message/wechatTemplate/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface WechatTemplateVO {\n  id: number\n  tempkey: string\n  name: string\n  content: string\n  tempid: string\n  status: byte\n  type: string\n}\n\n// 查询微信模板列表\nexport const getWechatTemplatePage = async (params: WechatTemplatePageReqVO) => {\n  return await request.get({ url: `/message/wechat-template/page`, params })\n}\n\n// 查询微信模板详情\nexport const getWechatTemplate = async (id: number) => {\n  return await request.get({ url: `/message/wechat-template/get?id=` + id })\n}\n\n// 新增微信模板\nexport const createWechatTemplate = async (data: WechatTemplateVO) => {\n  return await request.post({ url: `/message/wechat-template/create`, data })\n}\n\n// 修改微信模板\nexport const updateWechatTemplate = async (data: WechatTemplateVO) => {\n  return await request.put({ url: `/message/wechat-template/update`, data })\n}\n\n// 删除微信模板\nexport const deleteWechatTemplate = async (id: number) => {\n  return await request.delete({ url: `/message/wechat-template/delete?id=` + id })\n}\n\n// 导出微信模板 Excel\nexport const exportWechatTemplate = async (params) => {\n  return await request.download({ url: `/message/wechat-template/export-excel`, params })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/mp/account/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface AccountVO {\n  id: number\n  name: string\n}\n\n// 创建公众号账号\nexport const createAccount = async (data) => {\n  return await request.post({ url: '/mp/account/create', data })\n}\n\n// 更新公众号账号\nexport const updateAccount = async (data) => {\n  return request.put({ url: '/mp/account/update', data: data })\n}\n\n// 删除公众号账号\nexport const deleteAccount = async (id) => {\n  return request.delete({ url: '/mp/account/delete?id=' + id, method: 'delete' })\n}\n\n// 获得公众号账号\nexport const getAccount = async (id) => {\n  return request.get({ url: '/mp/account/get?id=' + id })\n}\n\n// 获得公众号账号分页\nexport const getAccountPage = async (query) => {\n  return request.get({ url: '/mp/account/page', params: query })\n}\n\n// 获取公众号账号精简信息列表\nexport const getSimpleAccountList = async () => {\n  return request.get({ url: '/mp/account/list-all-simple' })\n}\n\n// 生成公众号二维码\nexport const generateAccountQrCode = async (id) => {\n  return request.put({ url: '/mp/account/generate-qr-code?id=' + id })\n}\n\n// 清空公众号 API 配额\nexport const clearAccountQuota = async (id) => {\n  return request.put({ url: '/mp/account/clear-quota?id=' + id })\n}\n\nexport const setAccountMain = async (id) => {\n  return request.put({ url: '/mp/account/set-main?id=' + id })\n}\n\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/mp/account2/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface AccountVO {\n  id?: number\n  name: string\n}\n\n// 创建公众号账号\nexport const createAccount = async (data) => {\n  return await request.post({ url: '/ma/account/create', data })\n}\n\n// 更新公众号账号\nexport const updateAccount = async (data) => {\n  return request.put({ url: '/ma/account/update', data: data })\n}\n\n// 删除公众号账号\nexport const deleteAccount = async (id) => {\n  return request.delete({ url: '/ma/account/delete?id=' + id, method: 'delete' })\n}\n\n// 获得公众号账号\nexport const getAccount = async (id) => {\n  return request.get({ url: '/ma/account/get?id=' + id })\n}\n\n// 获得公众号账号分页\nexport const getAccountPage = async (query) => {\n  return request.get({ url: '/ma/account/page', params: query })\n}\n\n\nexport const setAccountMain = async (id) => {\n  return request.put({ url: '/ma/account/set-main?id=' + id })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/mp/autoReply/index.ts",
    "content": "import request from '@/config/axios'\n\n// 创建公众号的自动回复\nexport const createAutoReply = (data) => {\n  return request.post({\n    url: '/mp/auto-reply/create',\n    data: data\n  })\n}\n\n// 更新公众号的自动回复\nexport const updateAutoReply = (data) => {\n  return request.put({\n    url: '/mp/auto-reply/update',\n    data: data\n  })\n}\n\n// 删除公众号的自动回复\nexport const deleteAutoReply = (id) => {\n  return request.delete({\n    url: '/mp/auto-reply/delete?id=' + id\n  })\n}\n\n// 获得公众号的自动回复\nexport const getAutoReply = (id) => {\n  return request.get({\n    url: '/mp/auto-reply/get?id=' + id\n  })\n}\n\n// 获得公众号的自动回复分页\nexport const getAutoReplyPage = (query) => {\n  return request.get({\n    url: '/mp/auto-reply/page',\n    params: query\n  })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/mp/draft/index.ts",
    "content": "import request from '@/config/axios'\n\n// 获得公众号草稿分页\nexport const getDraftPage = (query) => {\n  return request.get({\n    url: '/mp/draft/page',\n    params: query\n  })\n}\n\n// 创建公众号草稿\nexport const createDraft = (accountId, articles) => {\n  return request.post({\n    url: '/mp/draft/create?accountId=' + accountId,\n    data: {\n      articles\n    }\n  })\n}\n\n// 更新公众号草稿\nexport const updateDraft = (accountId, mediaId, articles) => {\n  return request.put({\n    url: '/mp/draft/update?accountId=' + accountId + '&mediaId=' + mediaId,\n    method: 'put',\n    data: articles\n  })\n}\n\n// 删除公众号草稿\nexport const deleteDraft = (accountId, mediaId) => {\n  return request.delete({\n    url: '/mp/draft/delete?accountId=' + accountId + '&mediaId=' + mediaId\n  })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/mp/freePublish/index.ts",
    "content": "import request from '@/config/axios'\n\n// 获得公众号素材分页\nexport const getFreePublishPage = (query) => {\n  return request.get({\n    url: '/mp/free-publish/page',\n    params: query\n  })\n}\n\n// 删除公众号素材\nexport const deleteFreePublish = (accountId, articleId) => {\n  return request.delete({\n    url: '/mp/free-publish/delete?accountId=' + accountId + '&articleId=' + articleId\n  })\n}\n\n// 发布公众号素材\nexport const submitFreePublish = (accountId, mediaId) => {\n  return request.post({\n    url: '/mp/free-publish/submit?accountId=' + accountId + '&mediaId=' + mediaId\n  })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/mp/material/index.ts",
    "content": "import request from '@/config/axios'\n\n// 获得公众号素材分页\nexport const getMaterialPage = (query) => {\n  return request.get({\n    url: '/mp/material/page',\n    params: query\n  })\n}\n\n// 删除公众号永久素材\nexport const deletePermanentMaterial = (id) => {\n  return request.delete({\n    url: '/mp/material/delete-permanent?id=' + id\n  })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/mp/menu/index.ts",
    "content": "import request from '@/config/axios'\n\n// 获得公众号菜单列表\nexport const getMenuList = (accountId) => {\n  return request.get({\n    url: '/mp/menu/list?accountId=' + accountId\n  })\n}\n\n// 保存公众号菜单\nexport const saveMenu = (accountId, menus) => {\n  return request.post({\n    url: '/mp/menu/save',\n    data: {\n      accountId,\n      menus\n    }\n  })\n}\n\n// 删除公众号菜单\nexport const deleteMenu = (accountId) => {\n  return request.delete({\n    url: '/mp/menu/delete?accountId=' + accountId\n  })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/mp/message/index.ts",
    "content": "import request from '@/config/axios'\n\n// 获得公众号消息分页\nexport const getMessagePage = (query: PageParam) => {\n  return request.get({\n    url: '/mp/message/page',\n    params: query\n  })\n}\n\n// 给粉丝发送消息\nexport const sendMessage = (data) => {\n  return request.post({\n    url: '/mp/message/send',\n    data: data\n  })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/mp/statistics/index.ts",
    "content": "import request from '@/config/axios'\n\n// 获取消息发送概况数据\nexport const getUpstreamMessage = (query) => {\n  return request.get({\n    url: '/mp/statistics/upstream-message',\n    params: query\n  })\n}\n\n// 用户增减数据\nexport const getUserSummary = (query) => {\n  return request.get({\n    url: '/mp/statistics/user-summary',\n    params: query\n  })\n}\n\n// 获得用户累计数据\nexport const getUserCumulate = (query) => {\n  return request.get({\n    url: '/mp/statistics/user-cumulate',\n    params: query\n  })\n}\n\n// 获得接口分析数据\nexport const getInterfaceSummary = (query) => {\n  return request.get({\n    url: '/mp/statistics/interface-summary',\n    params: query\n  })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/mp/tag/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface TagVO {\n  id?: number\n  name: string\n  accountId: number\n  createTime: Date\n}\n\n// 创建公众号标签\nexport const createTag = (data: TagVO) => {\n  return request.post({\n    url: '/mp/tag/create',\n    data: data\n  })\n}\n\n// 更新公众号标签\nexport const updateTag = (data: TagVO) => {\n  return request.put({\n    url: '/mp/tag/update',\n    data: data\n  })\n}\n\n// 删除公众号标签\nexport const deleteTag = (id: number) => {\n  return request.delete({\n    url: '/mp/tag/delete?id=' + id\n  })\n}\n\n// 获得公众号标签\nexport const getTag = (id: number) => {\n  return request.get({\n    url: '/mp/tag/get?id=' + id\n  })\n}\n\n// 获得公众号标签分页\nexport const getTagPage = (query: PageParam) => {\n  return request.get({\n    url: '/mp/tag/page',\n    params: query\n  })\n}\n\n// 获取公众号标签精简信息列表\nexport const getSimpleTagList = () => {\n  return request.get({\n    url: '/mp/tag/list-all-simple'\n  })\n}\n\n// 同步公众号标签\nexport const syncTag = (accountId: number) => {\n  return request.post({\n    url: '/mp/tag/sync?accountId=' + accountId\n  })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/mp/user/index.ts",
    "content": "import request from '@/config/axios'\n\n// 更新公众号粉丝\nexport const updateUser = (data) => {\n  return request.put({\n    url: '/mp/user/update',\n    data: data\n  })\n}\n\n// 获得公众号粉丝\nexport const getUser = (id) => {\n  return request.get({\n    url: '/mp/user/get?id=' + id\n  })\n}\n\n// 获得公众号粉丝分页\nexport const getUserPage = (query) => {\n  return request.get({\n    url: '/mp/user/page',\n    params: query\n  })\n}\n\n// 同步公众号粉丝\nexport const syncUser = (accountId) => {\n  return request.post({\n    url: '/mp/user/sync?accountId=' + accountId\n  })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/pay/merchantDetails/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface MerchantDetailsVO {\n  detailsId: string\n  payType: string\n  appid: string\n  mchId: string\n  certStoreType: string\n  keyPrivate: string\n  keyPublic: string\n  keyCert: string\n  keyCertPwd: string\n  notifyUrl: string\n  returnUrl: string\n  signType: string\n  seller: string\n  subAppId: string\n  subMchId: string\n  inputCharset: string\n  isTest: boolean\n}\n\n// 查询支付服务商配置列表\nexport const getMerchantDetailsPage = async (params: MerchantDetailsPageReqVO) => {\n  return await request.get({ url: `/pay/merchant-details/page`, params })\n}\n\n// 查询支付服务商配置详情\nexport const getMerchantDetails = async (id: number) => {\n  return await request.get({ url: `/pay/merchant-details/get?id=` + id })\n}\n\n// 新增支付服务商配置\nexport const createMerchantDetails = async (data: MerchantDetailsVO) => {\n  return await request.post({ url: `/pay/merchant-details/create`, data })\n}\n\n// 修改支付服务商配置\nexport const updateMerchantDetails = async (data: MerchantDetailsVO) => {\n  return await request.put({ url: `/pay/merchant-details/update`, data })\n}\n\n// 删除支付服务商配置\nexport const deleteMerchantDetails = async (id: number) => {\n  return await request.delete({ url: `/pay/merchant-details/delete?id=` + id })\n}\n\n// 导出支付服务商配置 Excel\nexport const exportMerchantDetails = async (params) => {\n  return await request.download({ url: `/pay/merchant-details/export-excel`, params })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/score/order/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface OrderVO {\n  id: number\n  userId: number\n  productId: number\n  number: number\n  score: number\n  totalScore: number\n  ip: string\n  expressNumber: string\n  expressCompany: string\n  customerName: string\n  customerPhone: string\n  customerAddress: string\n  status: boolean\n  havePaid: number\n  haveDelivered: number\n  haveReceived: number\n}\n\n// 查询积分商城订单列表\nexport const getOrderPage = async (params: OrderPageReqVO) => {\n  return await request.get({ url: `/score/order/page`, params })\n}\n\n// 查询积分商城订单详情\nexport const getOrder = async (id: number) => {\n  return await request.get({ url: `/score/order/get?id=` + id })\n}\n\n// 新增积分商城订单\nexport const createOrder = async (data: OrderVO) => {\n  return await request.post({ url: `/score/order/create`, data })\n}\n\n// 修改积分商城订单\nexport const updateOrder = async (data: OrderVO) => {\n  return await request.put({ url: `/score/order/update`, data })\n}\n\n// 删除积分商城订单\nexport const deleteOrder = async (id: number) => {\n  return await request.delete({ url: `/score/order/delete?id=` + id })\n}\n\n// 导出积分商城订单 Excel\nexport const exportOrder = async (params) => {\n  return await request.download({ url: `/score/order/export-excel`, params })\n}\n\nexport const getLogistic = async (param1,param2) => {\n  return await request.get({ url: `/order/express/getLogistic?shipperCode=` + param1 + `&logisticCode=` + param2})\n}\n\n// 收货\nexport const takeStoreOrder = async (id) => {\n  return await request.get({ url: `/score/order/take?id=` + id })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/score/product/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface ProductVO {\n  id: number\n  title: string\n  image: string\n  images: string\n  desc: string\n  score: number\n  weigh: number\n  stock: number\n  sales: number\n  switch: boolean\n}\n\n// 查询积分产品列表\nexport const getProductPage = async (params: ProductPageReqVO) => {\n  return await request.get({ url: `/score/product/page`, params })\n}\n\n// 查询积分产品详情\nexport const getProduct = async (id: number) => {\n  return await request.get({ url: `/score/product/get?id=` + id })\n}\n\n// 新增积分产品\nexport const createProduct = async (data: ProductVO) => {\n  return await request.post({ url: `/score/product/create`, data })\n}\n\n// 修改积分产品\nexport const updateProduct = async (data: ProductVO) => {\n  return await request.put({ url: `/score/product/update`, data })\n}\n\n// 删除积分产品\nexport const deleteProduct = async (id: number) => {\n  return await request.delete({ url: `/score/product/delete?id=` + id })\n}\n\n// 导出积分产品 Excel\nexport const exportProduct = async (params) => {\n  return await request.download({ url: `/score/product/export-excel`, params })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/system/area/index.ts",
    "content": "import request from '@/config/axios'\n\n// 获得地区树\nexport const getAreaTree = async () => {\n  return await request.get({ url: '/system/area/tree' })\n}\n\n// 获得 IP 对应的地区名\nexport const getAreaByIp = async (ip: string) => {\n  return await request.get({ url: '/system/area/get-by-ip?ip=' + ip })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/system/dept/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface DeptVO {\n  id?: number\n  name: string\n  parentId: number\n  status: number\n  sort: number\n  leaderUserId: number\n  phone: string\n  email: string\n  createTime: Date\n}\n\n// 查询部门（精简)列表\nexport const getSimpleDeptList = async (): Promise<DeptVO[]> => {\n  return await request.get({ url: '/system/dept/simple-list' })\n}\n\n// 查询部门列表\nexport const getDeptPage = async (params: PageParam) => {\n  return await request.get({ url: '/system/dept/list', params })\n}\n\n// 查询部门详情\nexport const getDept = async (id: number) => {\n  return await request.get({ url: '/system/dept/get?id=' + id })\n}\n\n// 新增部门\nexport const createDept = async (data: DeptVO) => {\n  return await request.post({ url: '/system/dept/create', data: data })\n}\n\n// 修改部门\nexport const updateDept = async (params: DeptVO) => {\n  return await request.put({ url: '/system/dept/update', data: params })\n}\n\n// 删除部门\nexport const deleteDept = async (id: number) => {\n  return await request.delete({ url: '/system/dept/delete?id=' + id })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/system/dict/dict.data.ts",
    "content": "import request from '@/config/axios'\n\nexport type DictDataVO = {\n  id: number | undefined\n  sort: number | undefined\n  label: string\n  value: string\n  dictType: string\n  status: number\n  colorType: string\n  cssClass: string\n  remark: string\n  createTime: Date\n}\n\n// 查询字典数据（精简)列表\nexport const getSimpleDictDataList = () => {\n  return request.get({ url: '/system/dict-data/simple-list' })\n}\n\n// 查询字典数据列表\nexport const getDictDataPage = (params: PageParam) => {\n  return request.get({ url: '/system/dict-data/page', params })\n}\n\n// 查询字典数据详情\nexport const getDictData = (id: number) => {\n  return request.get({ url: '/system/dict-data/get?id=' + id })\n}\n\n// 新增字典数据\nexport const createDictData = (data: DictDataVO) => {\n  return request.post({ url: '/system/dict-data/create', data })\n}\n\n// 修改字典数据\nexport const updateDictData = (data: DictDataVO) => {\n  return request.put({ url: '/system/dict-data/update', data })\n}\n\n// 删除字典数据\nexport const deleteDictData = (id: number) => {\n  return request.delete({ url: '/system/dict-data/delete?id=' + id })\n}\n\n// 导出字典类型数据\nexport const exportDictData = (params) => {\n  return request.download({ url: '/system/dict-data/export', params })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/system/dict/dict.type.ts",
    "content": "import request from '@/config/axios'\n\nexport type DictTypeVO = {\n  id: number | undefined\n  name: string\n  type: string\n  status: number\n  remark: string\n  createTime: Date\n}\n\n// 查询字典（精简)列表\nexport const getSimpleDictTypeList = () => {\n  return request.get({ url: '/system/dict-type/list-all-simple' })\n}\n\n// 查询字典列表\nexport const getDictTypePage = (params: PageParam) => {\n  return request.get({ url: '/system/dict-type/page', params })\n}\n\n// 查询字典详情\nexport const getDictType = (id: number) => {\n  return request.get({ url: '/system/dict-type/get?id=' + id })\n}\n\n// 新增字典\nexport const createDictType = (data: DictTypeVO) => {\n  return request.post({ url: '/system/dict-type/create', data })\n}\n\n// 修改字典\nexport const updateDictType = (data: DictTypeVO) => {\n  return request.put({ url: '/system/dict-type/update', data })\n}\n\n// 删除字典\nexport const deleteDictType = (id: number) => {\n  return request.delete({ url: '/system/dict-type/delete?id=' + id })\n}\n// 导出字典类型\nexport const exportDictType = (params) => {\n  return request.download({ url: '/system/dict-type/export', params })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/system/loginLog/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface LoginLogVO {\n  id: number\n  logType: number\n  traceId: number\n  userId: number\n  userType: number\n  username: string\n  result: number\n  status: number\n  userIp: string\n  userAgent: string\n  createTime: Date\n}\n\n// 查询登录日志列表\nexport const getLoginLogPage = (params: PageParam) => {\n  return request.get({ url: '/system/login-log/page', params })\n}\n\n// 导出登录日志\nexport const exportLoginLog = (params) => {\n  return request.download({ url: '/system/login-log/export', params })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/system/mail/account/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface MailAccountVO {\n  id: number\n  mail: string\n  username: string\n  password: string\n  host: string\n  port: number\n  sslEnable: boolean\n  starttlsEnable: boolean\n}\n\n// 查询邮箱账号列表\nexport const getMailAccountPage = async (params: PageParam) => {\n  return await request.get({ url: '/system/mail-account/page', params })\n}\n\n// 查询邮箱账号详情\nexport const getMailAccount = async (id: number) => {\n  return await request.get({ url: '/system/mail-account/get?id=' + id })\n}\n\n// 新增邮箱账号\nexport const createMailAccount = async (data: MailAccountVO) => {\n  return await request.post({ url: '/system/mail-account/create', data })\n}\n\n// 修改邮箱账号\nexport const updateMailAccount = async (data: MailAccountVO) => {\n  return await request.put({ url: '/system/mail-account/update', data })\n}\n\n// 删除邮箱账号\nexport const deleteMailAccount = async (id: number) => {\n  return await request.delete({ url: '/system/mail-account/delete?id=' + id })\n}\n\n// 获得邮箱账号精简列表\nexport const getSimpleMailAccountList = async () => {\n  return request.get({ url: '/system/mail-account/simple-list' })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/system/mail/log/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface MailLogVO {\n  id: number\n  userId: number\n  userType: number\n  toMail: string\n  accountId: number\n  fromMail: string\n  templateId: number\n  templateCode: string\n  templateNickname: string\n  templateTitle: string\n  templateContent: string\n  templateParams: string\n  sendStatus: number\n  sendTime: Date\n  sendMessageId: string\n  sendException: string\n}\n\n// 查询邮件日志列表\nexport const getMailLogPage = async (params: PageParam) => {\n  return await request.get({ url: '/system/mail-log/page', params })\n}\n\n// 查询邮件日志详情\nexport const getMailLog = async (id: number) => {\n  return await request.get({ url: '/system/mail-log/get?id=' + id })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/system/mail/template/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface MailTemplateVO {\n  id: number\n  name: string\n  code: string\n  accountId: number\n  nickname: string\n  title: string\n  content: string\n  params: string\n  status: number\n  remark: string\n}\n\nexport interface MailSendReqVO {\n  mail: string\n  templateCode: string\n  templateParams: Map<String, Object>\n}\n\n// 查询邮件模版列表\nexport const getMailTemplatePage = async (params: PageParam) => {\n  return await request.get({ url: '/system/mail-template/page', params })\n}\n\n// 查询邮件模版详情\nexport const getMailTemplate = async (id: number) => {\n  return await request.get({ url: '/system/mail-template/get?id=' + id })\n}\n\n// 新增邮件模版\nexport const createMailTemplate = async (data: MailTemplateVO) => {\n  return await request.post({ url: '/system/mail-template/create', data })\n}\n\n// 修改邮件模版\nexport const updateMailTemplate = async (data: MailTemplateVO) => {\n  return await request.put({ url: '/system/mail-template/update', data })\n}\n\n// 删除邮件模版\nexport const deleteMailTemplate = async (id: number) => {\n  return await request.delete({ url: '/system/mail-template/delete?id=' + id })\n}\n\n// 发送邮件\nexport const sendMail = (data: MailSendReqVO) => {\n  return request.post({ url: '/system/mail-template/send-mail', data })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/system/menu/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface MenuVO {\n  id: number\n  name: string\n  permission: string\n  type: number\n  sort: number\n  parentId: number\n  path: string\n  icon: string\n  component: string\n  componentName?: string\n  status: number\n  visible: boolean\n  keepAlive: boolean\n  alwaysShow?: boolean\n  createTime: Date\n}\n\n// 查询菜单（精简）列表\nexport const getSimpleMenusList = () => {\n  return request.get({ url: '/system/menu/simple-list' })\n}\n\n// 查询菜单列表\nexport const getMenuList = (params) => {\n  return request.get({ url: '/system/menu/list', params })\n}\n\n// 获取菜单详情\nexport const getMenu = (id: number) => {\n  return request.get({ url: '/system/menu/get?id=' + id })\n}\n\n// 新增菜单\nexport const createMenu = (data: MenuVO) => {\n  return request.post({ url: '/system/menu/create', data })\n}\n\n// 修改菜单\nexport const updateMenu = (data: MenuVO) => {\n  return request.put({ url: '/system/menu/update', data })\n}\n\n// 删除菜单\nexport const deleteMenu = (id: number) => {\n  return request.delete({ url: '/system/menu/delete?id=' + id })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/system/notice/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface NoticeVO {\n  id: number | undefined\n  title: string\n  type: number\n  content: string\n  status: number\n  remark: string\n  creator: string\n  createTime: Date\n}\n\n// 查询公告列表\nexport const getNoticePage = (params: PageParam) => {\n  return request.get({ url: '/system/notice/page', params })\n}\n\n// 查询公告详情\nexport const getNotice = (id: number) => {\n  return request.get({ url: '/system/notice/get?id=' + id })\n}\n\n// 新增公告\nexport const createNotice = (data: NoticeVO) => {\n  return request.post({ url: '/system/notice/create', data })\n}\n\n// 修改公告\nexport const updateNotice = (data: NoticeVO) => {\n  return request.put({ url: '/system/notice/update', data })\n}\n\n// 删除公告\nexport const deleteNotice = (id: number) => {\n  return request.delete({ url: '/system/notice/delete?id=' + id })\n}\n\n// 推送公告\nexport const pushNotice = (id: number) => {\n  return request.post({ url: '/system/notice/push?id=' + id })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/system/notify/message/index.ts",
    "content": "import request from '@/config/axios'\nimport qs from 'qs'\n\nexport interface NotifyMessageVO {\n  id: number\n  userId: number\n  userType: number\n  templateId: number\n  templateCode: string\n  templateNickname: string\n  templateContent: string\n  templateType: number\n  templateParams: string\n  readStatus: boolean\n  readTime: Date\n  createTime: Date\n}\n\n// 查询站内信消息列表\nexport const getNotifyMessagePage = async (params: PageParam) => {\n  return await request.get({ url: '/system/notify-message/page', params })\n}\n\n// 获得我的站内信分页\nexport const getMyNotifyMessagePage = async (params: PageParam) => {\n  return await request.get({ url: '/system/notify-message/my-page', params })\n}\n\n// 批量标记已读\nexport const updateNotifyMessageRead = async (ids) => {\n  return await request.put({\n    url: '/system/notify-message/update-read?' + qs.stringify({ ids: ids }, { indices: false })\n  })\n}\n\n// 标记所有站内信为已读\nexport const updateAllNotifyMessageRead = async () => {\n  return await request.put({ url: '/system/notify-message/update-all-read' })\n}\n\n// 获取当前用户的最新站内信列表\nexport const getUnreadNotifyMessageList = async () => {\n  return await request.get({ url: '/system/notify-message/get-unread-list' })\n}\n\n// 获得当前用户的未读站内信数量\nexport const getUnreadNotifyMessageCount = async () => {\n  return await request.get({ url: '/system/notify-message/get-unread-count' })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/system/notify/template/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface NotifyTemplateVO {\n  id?: number\n  name: string\n  nickname: string\n  code: string\n  content: string\n  type?: number\n  params: string\n  status: number\n  remark: string\n}\n\nexport interface NotifySendReqVO {\n  userId: number | null\n  templateCode: string\n  templateParams: Map<String, Object>\n}\n\n// 查询站内信模板列表\nexport const getNotifyTemplatePage = async (params: PageParam) => {\n  return await request.get({ url: '/system/notify-template/page', params })\n}\n\n// 查询站内信模板详情\nexport const getNotifyTemplate = async (id: number) => {\n  return await request.get({ url: '/system/notify-template/get?id=' + id })\n}\n\n// 新增站内信模板\nexport const createNotifyTemplate = async (data: NotifyTemplateVO) => {\n  return await request.post({ url: '/system/notify-template/create', data })\n}\n\n// 修改站内信模板\nexport const updateNotifyTemplate = async (data: NotifyTemplateVO) => {\n  return await request.put({ url: '/system/notify-template/update', data })\n}\n\n// 删除站内信模板\nexport const deleteNotifyTemplate = async (id: number) => {\n  return await request.delete({ url: '/system/notify-template/delete?id=' + id })\n}\n\n// 发送站内信\nexport const sendNotify = (data: NotifySendReqVO) => {\n  return request.post({ url: '/system/notify-template/send-notify', data })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/system/oauth2/client.ts",
    "content": "import request from '@/config/axios'\n\nexport interface OAuth2ClientVO {\n  id: number\n  clientId: string\n  secret: string\n  name: string\n  logo: string\n  description: string\n  status: number\n  accessTokenValiditySeconds: number\n  refreshTokenValiditySeconds: number\n  redirectUris: string[]\n  autoApprove: boolean\n  authorizedGrantTypes: string[]\n  scopes: string[]\n  authorities: string[]\n  resourceIds: string[]\n  additionalInformation: string\n  isAdditionalInformationJson: boolean\n  createTime: Date\n}\n\n// 查询 OAuth2 客户端的列表\nexport const getOAuth2ClientPage = (params: PageParam) => {\n  return request.get({ url: '/system/oauth2-client/page', params })\n}\n\n// 查询 OAuth2 客户端的详情\nexport const getOAuth2Client = (id: number) => {\n  return request.get({ url: '/system/oauth2-client/get?id=' + id })\n}\n\n// 新增 OAuth2 客户端\nexport const createOAuth2Client = (data: OAuth2ClientVO) => {\n  return request.post({ url: '/system/oauth2-client/create', data })\n}\n\n// 修改 OAuth2 客户端\nexport const updateOAuth2Client = (data: OAuth2ClientVO) => {\n  return request.put({ url: '/system/oauth2-client/update', data })\n}\n\n// 删除 OAuth2\nexport const deleteOAuth2Client = (id: number) => {\n  return request.delete({ url: '/system/oauth2-client/delete?id=' + id })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/system/oauth2/token.ts",
    "content": "import request from '@/config/axios'\n\nexport interface OAuth2TokenVO {\n  id: number\n  accessToken: string\n  refreshToken: string\n  userId: number\n  userType: number\n  clientId: string\n  createTime: Date\n  expiresTime: Date\n}\n\n// 查询 token列表\nexport const getAccessTokenPage = (params: PageParam) => {\n  return request.get({ url: '/system/oauth2-token/page', params })\n}\n\n// 删除 token\nexport const deleteAccessToken = (accessToken: string) => {\n  return request.delete({ url: '/system/oauth2-token/delete?accessToken=' + accessToken })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/system/operatelog/index.ts",
    "content": "import request from '@/config/axios'\n\nexport type OperateLogVO = {\n  id: number\n  traceId: string\n  userType: number\n  userId: number\n  userName: string\n  type: string\n  subType: string\n  bizId: number\n  action: string\n  extra: string\n  requestMethod: string\n  requestUrl: string\n  userIp: string\n  userAgent: string\n  creator: string\n  creatorName: string\n  createTime: Date\n}\n\n// 查询操作日志列表\nexport const getOperateLogPage = (params: PageParam) => {\n  return request.get({ url: '/system/operate-log/page', params })\n}\n// 导出操作日志\nexport const exportOperateLog = (params: any) => {\n  return request.download({ url: '/system/operate-log/export', params })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/system/permission/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface PermissionAssignUserRoleReqVO {\n  userId: number\n  roleIds: number[]\n}\n\nexport interface PermissionAssignRoleMenuReqVO {\n  roleId: number\n  menuIds: number[]\n}\n\nexport interface PermissionAssignRoleDataScopeReqVO {\n  roleId: number\n  dataScope: number\n  dataScopeDeptIds: number[]\n}\n\n// 查询角色拥有的菜单权限\nexport const getRoleMenuList = async (roleId: number) => {\n  return await request.get({ url: '/system/permission/list-role-menus?roleId=' + roleId })\n}\n\n// 赋予角色菜单权限\nexport const assignRoleMenu = async (data: PermissionAssignRoleMenuReqVO) => {\n  return await request.post({ url: '/system/permission/assign-role-menu', data })\n}\n\n// 赋予角色数据权限\nexport const assignRoleDataScope = async (data: PermissionAssignRoleDataScopeReqVO) => {\n  return await request.post({ url: '/system/permission/assign-role-data-scope', data })\n}\n\n// 查询用户拥有的角色数组\nexport const getUserRoleList = async (userId: number) => {\n  return await request.get({ url: '/system/permission/list-user-roles?userId=' + userId })\n}\n\n// 赋予用户角色\nexport const assignUserRole = async (data: PermissionAssignUserRoleReqVO) => {\n  return await request.post({ url: '/system/permission/assign-user-role', data })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/system/post/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface PostVO {\n  id?: number\n  name: string\n  code: string\n  sort: number\n  status: number\n  remark: string\n  createTime?: Date\n}\n\n// 查询岗位列表\nexport const getPostPage = async (params: PageParam) => {\n  return await request.get({ url: '/system/post/page', params })\n}\n\n// 获取岗位精简信息列表\nexport const getSimplePostList = async (): Promise<PostVO[]> => {\n  return await request.get({ url: '/system/post/simple-list' })\n}\n\n// 查询岗位详情\nexport const getPost = async (id: number) => {\n  return await request.get({ url: '/system/post/get?id=' + id })\n}\n\n// 新增岗位\nexport const createPost = async (data: PostVO) => {\n  return await request.post({ url: '/system/post/create', data })\n}\n\n// 修改岗位\nexport const updatePost = async (data: PostVO) => {\n  return await request.put({ url: '/system/post/update', data })\n}\n\n// 删除岗位\nexport const deletePost = async (id: number) => {\n  return await request.delete({ url: '/system/post/delete?id=' + id })\n}\n\n// 导出岗位\nexport const exportPost = async (params) => {\n  return await request.download({ url: '/system/post/export', params })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/system/role/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface RoleVO {\n  id: number\n  name: string\n  code: string\n  sort: number\n  status: number\n  type: number\n  dataScope: number\n  dataScopeDeptIds: number[]\n  createTime: Date\n}\n\nexport interface UpdateStatusReqVO {\n  id: number\n  status: number\n}\n\n// 查询角色列表\nexport const getRolePage = async (params: PageParam) => {\n  return await request.get({ url: '/system/role/page', params })\n}\n\n// 查询角色（精简)列表\nexport const getSimpleRoleList = async (): Promise<RoleVO[]> => {\n  return await request.get({ url: '/system/role/simple-list' })\n}\n\n// 查询角色详情\nexport const getRole = async (id: number) => {\n  return await request.get({ url: '/system/role/get?id=' + id })\n}\n\n// 新增角色\nexport const createRole = async (data: RoleVO) => {\n  return await request.post({ url: '/system/role/create', data })\n}\n\n// 修改角色\nexport const updateRole = async (data: RoleVO) => {\n  return await request.put({ url: '/system/role/update', data })\n}\n\n// 修改角色状态\nexport const updateRoleStatus = async (data: UpdateStatusReqVO) => {\n  return await request.put({ url: '/system/role/update-status', data })\n}\n\n// 删除角色\nexport const deleteRole = async (id: number) => {\n  return await request.delete({ url: '/system/role/delete?id=' + id })\n}\n\n// 导出角色\nexport const exportRole = (params) => {\n  return request.download({\n    url: '/system/role/export-excel',\n    params\n  })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/system/sms/smsChannel/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface SmsChannelVO {\n  id: number\n  code: string\n  status: number\n  signature: string\n  remark: string\n  apiKey: string\n  apiSecret: string\n  callbackUrl: string\n  createTime: Date\n}\n\n// 查询短信渠道列表\nexport const getSmsChannelPage = (params: PageParam) => {\n  return request.get({ url: '/system/sms-channel/page', params })\n}\n\n// 获得短信渠道精简列表\nexport function getSimpleSmsChannelList() {\n  return request.get({ url: '/system/sms-channel/simple-list' })\n}\n\n// 查询短信渠道详情\nexport const getSmsChannel = (id: number) => {\n  return request.get({ url: '/system/sms-channel/get?id=' + id })\n}\n\n// 新增短信渠道\nexport const createSmsChannel = (data: SmsChannelVO) => {\n  return request.post({ url: '/system/sms-channel/create', data })\n}\n\n// 修改短信渠道\nexport const updateSmsChannel = (data: SmsChannelVO) => {\n  return request.put({ url: '/system/sms-channel/update', data })\n}\n\n// 删除短信渠道\nexport const deleteSmsChannel = (id: number) => {\n  return request.delete({ url: '/system/sms-channel/delete?id=' + id })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/system/sms/smsLog/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface SmsLogVO {\n  id: number | null\n  channelId: number | null\n  channelCode: string\n  templateId: number | null\n  templateCode: string\n  templateType: number | null\n  templateContent: string\n  templateParams: Map<string, object> | null\n  apiTemplateId: string\n  mobile: string\n  userId: number | null\n  userType: number | null\n  sendStatus: number | null\n  sendTime: Date | null\n  apiSendCode: string\n  apiSendMsg: string\n  apiRequestId: string\n  apiSerialNo: string\n  receiveStatus: number | null\n  receiveTime: Date | null\n  apiReceiveCode: string\n  apiReceiveMsg: string\n  createTime: Date | null\n}\n\n// 查询短信日志列表\nexport const getSmsLogPage = (params: PageParam) => {\n  return request.get({ url: '/system/sms-log/page', params })\n}\n\n// 导出短信日志\nexport const exportSmsLog = (params) => {\n  return request.download({ url: '/system/sms-log/export-excel', params })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/system/sms/smsTemplate/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface SmsTemplateVO {\n  id?: number\n  type?: number\n  status: number\n  code: string\n  name: string\n  content: string\n  remark: string\n  apiTemplateId: string\n  channelId?: number\n  channelCode?: string\n  params?: string[]\n  createTime?: Date\n}\n\nexport interface SendSmsReqVO {\n  mobile: string\n  templateCode: string\n  templateParams: Map<String, Object>\n}\n\n// 查询短信模板列表\nexport const getSmsTemplatePage = (params: PageParam) => {\n  return request.get({ url: '/system/sms-template/page', params })\n}\n\n// 查询短信模板详情\nexport const getSmsTemplate = (id: number) => {\n  return request.get({ url: '/system/sms-template/get?id=' + id })\n}\n\n// 新增短信模板\nexport const createSmsTemplate = (data: SmsTemplateVO) => {\n  return request.post({ url: '/system/sms-template/create', data })\n}\n\n// 修改短信模板\nexport const updateSmsTemplate = (data: SmsTemplateVO) => {\n  return request.put({ url: '/system/sms-template/update', data })\n}\n\n// 删除短信模板\nexport const deleteSmsTemplate = (id: number) => {\n  return request.delete({ url: '/system/sms-template/delete?id=' + id })\n}\n\n// 导出短信模板\nexport const exportSmsTemplate = (params) => {\n  return request.download({\n    url: '/system/sms-template/export-excel',\n    params\n  })\n}\n\n// 发送短信\nexport const sendSms = (data: SendSmsReqVO) => {\n  return request.post({ url: '/system/sms-template/send-sms', data })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/system/social/client/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface SocialClientVO {\n  id: number\n  name: string\n  socialType: number\n  userType: number\n  clientId: string\n  clientSecret: string\n  agentId: string\n  status: number\n}\n\n// 查询社交客户端列表\nexport const getSocialClientPage = async (params) => {\n  return await request.get({ url: `/system/social-client/page`, params })\n}\n\n// 查询社交客户端详情\nexport const getSocialClient = async (id: number) => {\n  return await request.get({ url: `/system/social-client/get?id=` + id })\n}\n\n// 新增社交客户端\nexport const createSocialClient = async (data: SocialClientVO) => {\n  return await request.post({ url: `/system/social-client/create`, data })\n}\n\n// 修改社交客户端\nexport const updateSocialClient = async (data: SocialClientVO) => {\n  return await request.put({ url: `/system/social-client/update`, data })\n}\n\n// 删除社交客户端\nexport const deleteSocialClient = async (id: number) => {\n  return await request.delete({ url: `/system/social-client/delete?id=` + id })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/system/social/user/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface SocialUserVO {\n  id: number\n  type: number\n  openid: string\n  token: string\n  rawTokenInfo: string\n  nickname: string\n  avatar: string\n  rawUserInfo: string\n  code: string\n  state: string\n}\n\n// 查询社交用户列表\nexport const getSocialUserPage = async (params) => {\n  return await request.get({ url: `/system/social-user/page`, params })\n}\n\n// 查询社交用户详情\nexport const getSocialUser = async (id: number) => {\n  return await request.get({ url: `/system/social-user/get?id=` + id })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/system/tenant/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface TenantVO {\n  id: number\n  name: string\n  contactName: string\n  contactMobile: string\n  status: number\n  domain: string\n  packageId: number\n  username: string\n  password: string\n  expireTime: Date\n  accountCount: number\n  createTime: Date\n}\n\nexport interface TenantPageReqVO extends PageParam {\n  name?: string\n  contactName?: string\n  contactMobile?: string\n  status?: number\n  createTime?: Date[]\n}\n\nexport interface TenantExportReqVO {\n  name?: string\n  contactName?: string\n  contactMobile?: string\n  status?: number\n  createTime?: Date[]\n}\n\n// 查询租户列表\nexport const getTenantPage = (params: TenantPageReqVO) => {\n  return request.get({ url: '/system/tenant/page', params })\n}\n\n// 查询租户详情\nexport const getTenant = (id: number) => {\n  return request.get({ url: '/system/tenant/get?id=' + id })\n}\n\n// 新增租户\nexport const createTenant = (data: TenantVO) => {\n  return request.post({ url: '/system/tenant/create', data })\n}\n\n// 修改租户\nexport const updateTenant = (data: TenantVO) => {\n  return request.put({ url: '/system/tenant/update', data })\n}\n\n// 删除租户\nexport const deleteTenant = (id: number) => {\n  return request.delete({ url: '/system/tenant/delete?id=' + id })\n}\n\n// 导出租户\nexport const exportTenant = (params: TenantExportReqVO) => {\n  return request.download({ url: '/system/tenant/export-excel', params })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/system/tenantPackage/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface TenantPackageVO {\n  id: number\n  name: string\n  status: number\n  remark: string\n  creator: string\n  updater: string\n  updateTime: string\n  menuIds: number[]\n  createTime: Date\n}\n\n// 查询租户套餐列表\nexport const getTenantPackagePage = (params: PageParam) => {\n  return request.get({ url: '/system/tenant-package/page', params })\n}\n\n// 获得租户\nexport const getTenantPackage = (id: number) => {\n  return request.get({ url: '/system/tenant-package/get?id=' + id })\n}\n\n// 新增租户套餐\nexport const createTenantPackage = (data: TenantPackageVO) => {\n  return request.post({ url: '/system/tenant-package/create', data })\n}\n\n// 修改租户套餐\nexport const updateTenantPackage = (data: TenantPackageVO) => {\n  return request.put({ url: '/system/tenant-package/update', data })\n}\n\n// 删除租户套餐\nexport const deleteTenantPackage = (id: number) => {\n  return request.delete({ url: '/system/tenant-package/delete?id=' + id })\n}\n// 获取租户套餐精简信息列表\nexport const getTenantPackageList = () => {\n  return request.get({ url: '/system/tenant-package/simple-list' })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/system/user/index.ts",
    "content": "import request from '@/config/axios'\n\nexport interface UserVO {\n  id: number\n  username: string\n  nickname: string\n  deptId: number\n  postIds: string[]\n  email: string\n  mobile: string\n  sex: number\n  avatar: string\n  loginIp: string\n  status: number\n  remark: string\n  loginDate: Date\n  createTime: Date\n}\n\n// 查询用户管理列表\nexport const getUserPage = (params: PageParam) => {\n  return request.get({ url: '/system/user/page', params })\n}\n\n// 查询所有用户列表\nexport const getAllUser = () => {\n  return request.get({ url: '/system/user/all' })\n}\n\n// 查询用户详情\nexport const getUser = (id: number) => {\n  return request.get({ url: '/system/user/get?id=' + id })\n}\n\n// 新增用户\nexport const createUser = (data: UserVO) => {\n  return request.post({ url: '/system/user/create', data })\n}\n\n// 修改用户\nexport const updateUser = (data: UserVO) => {\n  return request.put({ url: '/system/user/update', data })\n}\n\n// 删除用户\nexport const deleteUser = (id: number) => {\n  return request.delete({ url: '/system/user/delete?id=' + id })\n}\n\n// 导出用户\nexport const exportUser = (params) => {\n  return request.download({ url: '/system/user/export', params })\n}\n\n// 下载用户导入模板\nexport const importUserTemplate = () => {\n  return request.download({ url: '/system/user/get-import-template' })\n}\n\n// 用户密码重置\nexport const resetUserPwd = (id: number, password: string) => {\n  const data = {\n    id,\n    password\n  }\n  return request.put({ url: '/system/user/update-password', data: data })\n}\n\n// 用户状态修改\nexport const updateUserStatus = (id: number, status: number) => {\n  const data = {\n    id,\n    status\n  }\n  return request.put({ url: '/system/user/update-status', data: data })\n}\n\n// 获取用户精简信息列表\nexport const getSimpleUserList = (): Promise<UserVO[]> => {\n  return request.get({ url: '/system/user/simple-list' })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/system/user/profile.ts",
    "content": "import request from '@/config/axios'\n\nexport interface ProfileVO {\n  id: number\n  username: string\n  nickname: string\n  dept: {\n    id: number\n    name: string\n  }\n  roles: {\n    id: number\n    name: string\n  }[]\n  posts: {\n    id: number\n    name: string\n  }[]\n  socialUsers: {\n    type: number\n    openid: string\n  }[]\n  email: string\n  mobile: string\n  sex: number\n  avatar: string\n  status: number\n  remark: string\n  loginIp: string\n  loginDate: Date\n  createTime: Date\n}\n\nexport interface UserProfileUpdateReqVO {\n  nickname: string\n  email: string\n  mobile: string\n  sex: number\n}\n\n// 查询用户个人信息\nexport const getUserProfile = () => {\n  return request.get({ url: '/system/user/profile/get' })\n}\n\n// 修改用户个人信息\nexport const updateUserProfile = (data: UserProfileUpdateReqVO) => {\n  return request.put({ url: '/system/user/profile/update', data })\n}\n\n// 用户密码重置\nexport const updateUserPassword = (oldPassword: string, newPassword: string) => {\n  return request.put({\n    url: '/system/user/profile/update-password',\n    data: {\n      oldPassword: oldPassword,\n      newPassword: newPassword\n    }\n  })\n}\n\n// 用户头像上传\nexport const uploadAvatar = (data) => {\n  return request.upload({ url: '/system/user/profile/update-avatar', data: data })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/system/user/socialUser.ts",
    "content": "import request from '@/config/axios'\n\n// 社交绑定，使用 code 授权码\nexport const socialBind = (type, code, state) => {\n  return request.post({\n    url: '/system/social-user/bind',\n    data: {\n      type,\n      code,\n      state\n    }\n  })\n}\n\n// 取消社交绑定\nexport const socialUnbind = (type, openid) => {\n  return request.delete({\n    url: '/system/social-user/unbind',\n    data: {\n      type,\n      openid\n    }\n  })\n}\n\n// 社交授权的跳转\nexport const socialAuthRedirect = (type, redirectUri) => {\n  return request.get({\n    url: '/system/auth/social-auth-redirect?type=' + type + '&redirectUri=' + redirectUri\n  })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/tools/material.js",
    "content": "/*\n * @Author: Gaoxs\n * @Date: 2023-05-21 23:40:06\n * @LastEditors: Gaoxs\n * @Description:\n */\nimport request from '@/config/axios'\n\nexport async function getPage(query) {\n  return await request.get({\n    url: '/shop/material/page',\n\n    params: query\n  })\n}\n\nexport async function addObj(obj) {\n  return await request.post({\n    url: '/shop/material/create',\n\n    data: obj\n  })\n}\n\nexport async function getObj(id) {\n  return await request.ge({\n    url: '/shop/material/' + id\n  })\n}\n\nexport async function delObj(id) {\n  return await request.delete({\n    url: '/shop/material/delete',\n    params: {\n      id\n    }\n  })\n}\n\nexport async function putObj(obj) {\n  return await request.put({\n    url: '/shop/material/update',\n\n    data: obj\n  })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/api/tools/materialgroup.js",
    "content": "/*\n * @Author: Gaoxs\n * @Date: 2023-05-21 23:40:06\n * @LastEditors: Gaoxs\n * @Description:\n */\nimport request from '@/config/axios'\n\nexport async function getPage(query) {\n  return await request.get({\n    url: '/shop/material-group/page',\n\n    params: query\n  })\n}\n\nexport async function getList(query) {\n  return await request.get({\n    url: '/shop/material-group/list',\n\n    params: query\n  })\n}\n\nexport async function addObj(obj) {\n  return await request.post({\n    url: '/shop/material-group/create',\n\n    data: obj\n  })\n}\n\nexport async function getObj(id) {\n  return await request.ge({\n    url: '/shop/material-group/' + id\n  })\n}\n\nexport async function delObj(id) {\n  return await request.delete({\n    url: '/shop/material-group/delete',\n    params: { id }\n  })\n}\n\nexport async function putObj(obj) {\n  return await request.put({\n    url: '/shop/material-group/update',\n\n    data: obj\n  })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/assets/map/json/china.json",
    "content": "{\n  \"type\": \"FeatureCollection\",\n  \"features\": [\n    {\n      \"type\": \"Feature\",\n      \"id\": \"710000\",\n      \"properties\": {\n        \"id\": \"710000\",\n        \"cp\": [121.509062, 24.044332],\n        \"name\": \"台湾\",\n        \"childNum\": 6\n      },\n      \"geometry\": {\n        \"type\": \"MultiPolygon\",\n        \"coordinates\": [\n          [\"@@°Ü¯Û\"],\n          [\n            \"@@ƛĴÕƊÉɼģºðʀ\\\\ƎsÆNŌÔĚänÜƤɊĂǀĆĴĤǊŨxĚĮǂƺòƌâÔ®ĮXŦţƸZûÐƕƑGđ¨ĭMó·ęcëƝɉlÝƯֹÅŃ^Ó·śŃǋƏďíåɛGɉ¿@ăƑ¥ĘWǬÏĶŁâ\"\n          ],\n          [\"@@\\\\p|WoYG¿¥Ij@¢\"],\n          [\"@@¡@V^RqBbAnTXeRz¤L«³I\"],\n          [\"@@ÆEEkWqë @\"],\n          [\"@@fced\"],\n          [\"@@¯ɜÄèaì¯ØǓIġĽ\"],\n          [\"@@çûĖëĄhòř \"]\n        ],\n        \"encodeOffsets\": [\n          [[122886, 24033]],\n          [[123335, 22980]],\n          [[122375, 24193]],\n          [[122518, 24117]],\n          [[124427, 22618]],\n          [[124862, 26043]],\n          [[126259, 26318]],\n          [[127671, 26683]]\n        ]\n      }\n    },\n    {\n      \"type\": \"Feature\",\n      \"id\": \"130000\",\n      \"properties\": {\n        \"id\": \"130000\",\n        \"cp\": [114.502461, 38.045474],\n        \"name\": \"河北\",\n        \"childNum\": 3\n      },\n      \"geometry\": {\n        \"type\": \"MultiPolygon\",\n        \"coordinates\": [\n          [\"@@o~Z]ªrºc_ħ²G¼s`jÎŸnüsÂłNX_M`Ç½ÓnUKĜēs¤­©yrý§uģcJe\"],\n          [\"@@U`Ts¿mÂ\"],\n          [\n            \"@@oºƋÄdeVDJj£J|ÅdzÂFt~KŨ¸IÆv|¢r}èonb}`RÎÄn°ÒdÞ²^®lnÐèĄlðÓ×]ªÆ}LiĂ±Ö`^°Ç¶p®đDcŋ`ZÔ¶êqvFÆN®ĆTH®¦O¾IbÐã´BĐɢŴÆíȦpĐÞXR·nndO¤OÀĈƒ­QgµFo|gȒęSWb©osx|hYhgŃfmÖĩnºTÌSp¢dYĤ¶UĈjlǐpäìë|³kÛfw²Xjz~ÂqbTÑěŨ@|oMzv¢ZrÃVw¬ŧĖ¸f°ÐTªqs{S¯r æÝlNd®²Ğ ǆiGĘJ¼lr}~K¨ŸƐÌWöÆzR¤lêmĞLÎ@¡|q]SvKÑcwpÏÏĿćènĪWlĄkT}J¤~ÈTdpddʾĬBVtEÀ¢ôPĎƗè@~kü\\\\rÊĔÖæW_§¼F´©òDòjYÈrbĞāøŀG{ƀ|¦ðrb|ÀH`pʞkvGpuARhÞÆǶgĘTǼƹS£¨¡ù³ŘÍ]¿ÂyôEP xX¶¹ÜO¡gÚ¡IwÃé¦ÅBÏ|Ç°N«úmH¯âDùyŜŲIÄuĐ¨D¸dɂFOhđ©OiÃ`ww^ÌkÑH«ƇǤŗĺtFu{Z}Ö@U´ʚLg®¯Oı°Ãw ^VbÉsmAê]]w§RRl£ȭµu¯b{ÍDěïÿȧuT£ġěŗƃĝQ¨fVƋƅn­a@³@ďyÃ½IĹÊKŭfċŰóxV@tƯJ]eR¾fe|rHA|h~Ėƍl§ÏlTíb ØoÅbbx³^zÃĶ¶Sj®AyÂhðk`«PËµEFÛ¬Y¨Ļrõqi¼Wi°§Ð±´°^[À|ĠO@ÆxO\\\\ta\\\\tĕtû{ġȧXýĪÓjùÎRb^ÎfK[ÝděYfíÙTyuUSyŌŏů@Oi½éŅ­aVcř§ax¹XŻácWU£ôãºQ¨÷Ñws¥qEHÙ|šYQoŕÇyáĂ£MÃ°oťÊP¡mWO¡v{ôvîēÜISpÌhp¨ jdeŔQÖjX³àĈ[n`Yp@UcM`RKhEbpŞlNut®EtqnsÁgAiúoHqCXhfgu~ÏWP½¢G^}¯ÅīGCÑ^ãziMáļMTÃƘrMc|O_¯Ŏ´|morDkO\\\\mĆJfl@cĢ¬¢aĦtRıÒ¾ùƀ^juųœK­UFyƝīÛ÷ąV×qƥV¿aȉd³BqPBmaËđŻģmÅ®V¹d^KKonYg¯XhqaLdu¥Ípǅ¡KąÅkĝęěhq}HyÃ]¹ǧ£Í÷¿qáµ§g¤o^á¾ZE¤i`ĳ{nOl»WÝĔįhgF[¿¡ßkOüš_ūiǱàUtėGyl}ÓM}jpEC~¡FtoQiHkk{Ãmï\"\n          ]\n        ],\n        \"encodeOffsets\": [[[119712, 40641]], [[121616, 39981]], [[116462, 37237]]]\n      }\n    },\n    {\n      \"type\": \"Feature\",\n      \"id\": \"140000\",\n      \"properties\": {\n        \"id\": \"140000\",\n        \"cp\": [111.849248, 36.857014],\n        \"name\": \"山西\",\n        \"childNum\": 1\n      },\n      \"geometry\": {\n        \"type\": \"Polygon\",\n        \"coordinates\": [\n          \"@@ÞĩÒSra}ÁyWix±Üe´lèßÓǏokćiµVZģ¡coTSË¹ĪmnÕńehZg{gtwªpXaĚThȑp{¶Eh®RćƑP¿£Pmc¸mQÝWďȥoÅîɡųAďä³aÏJ½¥PG­ąSM­EÅruµéYÓŌ_dĒCo­Èµ]¯_²ÕjāK~©ÅØ^ÔkïçămÏk]­±cÝ¯ÑÃmQÍ~_apm~ç¡qu{JÅŧ·Ls}EyÁÆcI{¤IiCfUcƌÃp§]ě«vD@¡SÀµMÅwuYY¡DbÑc¡h×]nkoQdaMç~eDÛtT©±@¥ù@É¡ZcW|WqOJmĩl«ħşvOÓ«IqăV¥D[mI~Ó¢cehiÍ]Ɠ~ĥqX·eƷn±}v[ěďŕ]_œ`¹§ÕōIo©b­s^}Ét±ū«³p£ÿ·Wµ|¡¥ăFÏs×¥ŅxÊdÒ{ºvĴÎêÌɊ²¶ü¨|ÞƸµȲLLúÉƎ¤ϊęĔV`_bªS^|dzY|dz¥pZbÆ£¶ÒK}tĦÔņƠPYznÍvX¶Ěn ĠÔzý¦ª÷ÑĸÙUȌ¸dòÜJð´ìúNM¬XZ´¤ŊǸ_tldI{¦ƀðĠȤ¥NehXnYGR° ƬDj¬¸|CĞKqºfƐiĺ©ª~ĆOQª ¤@ìǦɌ²æBÊTŸʂōĖĴŞȀÆÿȄlŤĒötÎ½î¼ĨXh|ªM¤Ðz\"\n        ],\n        \"encodeOffsets\": [[116874, 41716]]\n      }\n    },\n    {\n      \"type\": \"Feature\",\n      \"id\": \"150000\",\n      \"properties\": {\n        \"id\": \"150000\",\n        \"cp\": [111.670801, 41.818311],\n        \"name\": \"内蒙古\",\n        \"childNum\": 2\n      },\n      \"geometry\": {\n        \"type\": \"MultiPolygon\",\n        \"coordinates\": [\n          [\n            \"@@¯PqFB|S³C|kñHdiÄ¥sŉÅPóÑÑE^ÅPpy_YtShQ·aHwsOnŉÃs©iqjUSiº]ïW«gW¡ARë¥_sgÁnUI«m]jvV¼euhwqAaW_µj»çjioQR¹ēÃßt@r³[ÛlćË^ÍÉáGOUÛOB±XkÅ¹£k|e]olkVÍ¼ÕqtaÏõjgÁ£§U^RLËnX°ÇBz^~wfvypV ¯ƫĉ˭ȫƗŷɿÿĿƑ˃ĝÿÃǃßËőó©ǐȍŒĖM×ÍEyxþp]ÉvïèvƀnÂĴÖ@V~Ĉv¦wĖtējyÄDXÄxGQuv_i¦aBçw˛wD©{tāmQ{EJ§KPśƘƿ¥@sCTÉ}ɃwƇy±gÑ}T[÷kÐç¦«SÒ¥¸ëBX½HáÅµÀğtSÝÂa[ƣ°¯¦Pï¡]£ġÒk®G²èQ°óMq}EóƐÇ\\\\@áügQÍu¥FTÕ¿Jû]|mvāÎYua^WoÀa·­ząÒot×¶CLƗi¯¤mƎHǊ¤îìɾŊìTdåwsRÖgĒųúÍġäÕ}Q¶¿A[¡{d×uQAMxVvMOmăl«ct[wº_ÇÊjbÂ£ĦS_éQZ_lwgOiýe`YYLq§IÁǳ£ÙË[ÕªuƏ³ÍTs·bÁĽäė[b[ŗfãcn¥îC¿÷µ[ŏÀQ­ōĉm¿Á^£mJVmL[{Ï_£F¥Ö{ŹA}×Wu©ÅaųĳƳhB{·TQqÙIķËZđ©Yc|M¡LeVUóK_QWk_ĥ¿ãZ»X\\\\ĴuUèlG®ěłTĠğDŃOrÍdÆÍz]±ŭ©Å]ÅÐ}UË¥©TċïxgckfWgi\\\\ÏĒ¥HkµEë{»ÏetcG±ahUiñiWsɁ·cCÕk]wȑ|ća}wVaĚá G°ùnM¬¯{ÈÐÆA¥ÄêJxÙ¢hP¢ÛºµwWOóFÁz^ÀŗÎú´§¢T¤ǻƺSėǵhÝÅQgvBHouʝl_o¿Ga{ïq{¥|ſĿHĂ÷aĝÇqZñiñC³ª»E`¨åXēÕqÉû[l}ç@čƘóO¿¡FUsAʽīccocÇS}£IS~ălkĩXçmĈŀÐoÐdxÒuL^T{r@¢ÍĝKén£kQyÅõËXŷƏL§~}kq»IHėǅjĝ»ÑÞoå°qTt|r©ÏS¯·eŨĕx«È[eM¿yupN~¹ÏyN£{©għWí»Í¾səšǅ_ÃĀɗ±ąĳĉʍŌŷSÉA±åǥɋ@ë£R©ąP©}ĹªƏj¹erLDĝ·{i«ƫC£µsKCGS|úþXgp{ÁX¿ć{ƱȏñZáĔyoÁhA}ŅĆfdŉ_¹Y°ėǩÑ¡H¯¶oMQqð¡Ë|Ñ`ƭŁX½·óÛxğįÅcQs«tȋǅFù^it«Č¯[hAi©á¥ÇĚ×l|¹y¯YȵƓñǙµïċĻ|Düȭ¶¡oŽäÕG\\\\ÄT¿Òõr¯LguÏYęRƩɷŌO\\\\İÐ¢æ^Ŋ ĲȶȆbÜGĝ¬¿ĚVĎgª^íu½jÿĕęjık@Ľ]ėl¥ËĭûÁėéV©±ćn©­ȇÍq¯½YÃÔŉÉNÑÅÝy¹NqáʅDǡËñ­ƁYÅy̱os§ȋµʽǘǏƬɱàưN¢ƔÊuľýľώȪƺɂļxZĈ}ÌŉŪĺœĭFЛĽ̅ȣͽÒŵìƩÇϋÿȮǡŏçƑůĕ~Ç¼ȳÐUfdIxÿ\\\\G zâɏÙOº·pqy£@qþ@Ǟ˽IBäƣzsÂZÁàĻdñ°ŕzéØűzșCìDȐĴĺf®Àľưø@ɜÖÞKĊŇƄ§͑těï͡VAġÑÑ»d³öǍÝXĉĕÖ{þĉu¸ËʅğU̎éhɹƆ̗̮ȘǊ֥ड़ࡰţાíϲäʮW¬®ҌeרūȠkɬɻ̼ãüfƠSצɩςåȈHϚÎKǳͲOðÏȆƘ¼CϚǚ࢚˼ФÔ¤ƌĞ̪Qʤ´¼mȠJˀƲÀɠmǐnǔĎȆÞǠN~ʢĜ¶ƌĆĘźʆȬ˪ĚĒ¸ĞGȖƴƀj`ĢçĶāàŃºēĢĖćYÀŎüôQÐÂŎŞǆŞêƖoˆDĤÕºÑǘÛˤ³̀gńƘĔÀ^ªƂ`ªt¾äƚêĦĀ¼ÐĔǎ¨Ȕ»͠^ˮÊȦƤøxRrŜH¤¸ÂxDÄ|ø˂˜ƮÐ¬ɚwɲFjĔ²Äw°ǆdÀÉ_ĸdîàŎjÊêTĞªŌŜWÈ|tqĢUB~´°ÎFCU¼pĀēƄN¦¾O¶łKĊOjĚj´ĜYp{¦SĚÍ\\\\T×ªV÷Ší¨ÅDK°ßtŇĔK¨ǵÂcḷ̌ĚǣȄĽFlġUĵŇȣFʉɁMğįʏƶɷØŭOǽ«ƽū¹Ʊő̝Ȩ§ȞʘĖiɜɶʦ}¨֪ࠜ̀ƇǬ¹ǨE˦ĥªÔêFxúQEr´Wrh¤Ɛ \\\\talĈDJÜ|[Pll̚¸ƎGú´P¬W¦^¦H]prRn|or¾wLVnÇIujkmon£cX^Bh`¥V¦U¤¸}xRj[^xN[~ªxQ[`ªHÆÂExx^wN¶Ê|¨ìMrdYpoRzNyÀDs~bcfÌ`L¾n|¾T°c¨È¢ar¤`[|òDŞĔöxElÖdHÀI`Ď\\\\Àì~ÆR¼tf¦^¢ķ¶eÐÚMptgjɡČÅyġLûŇV®ÄÈƀĎ°P|ªVVªj¬ĚÒêp¬E|ŬÂc|ÀtƐK f{ĘFĒƌXƲąo½Ę\\\\¥o}Ûu£ç­kX{uĩ«āíÓUŅßŢqŤ¥lyň[oi{¦LńðFȪȖĒL¿Ìf£K£ʺoqNwğc`uetOj×°KJ±qÆġmĚŗos¬qehqsuH{¸kH¡ÊRǪÇƌbȆ¢´äÜ¢NìÉʖ¦â©Ġu¦öČ^â£ĂhĖMÈÄw\\\\fŦ°W ¢¾luŸDw\\\\̀ʉÌÛMĀ[bÓEn}¶Vcês\"\n          ]\n        ],\n        \"encodeOffsets\": [[[129102, 52189]]]\n      }\n    },\n    {\n      \"type\": \"Feature\",\n      \"id\": \"210000\",\n      \"properties\": {\n        \"id\": \"210000\",\n        \"cp\": [123.429096, 41.796767],\n        \"name\": \"辽宁\",\n        \"childNum\": 16\n      },\n      \"geometry\": {\n        \"type\": \"MultiPolygon\",\n        \"coordinates\": [\n          [\"@@L@@sa\"],\n          [\"@@MnNm\"],\n          [\"@@dc\"],\n          [\"@@eÀC@b\"],\n          [\"@@fXwkbrÄ`qg\"],\n          [\"@@^jtWQ\"],\n          [\"@@~ Y]c\"],\n          [\"@@G`ĔN^_¿ZÃM\"],\n          [\"@@iX¶BY\"],\n          [\"@@YZ\"],\n          [\"@@L_{Epf\"],\n          [\"@@^WqCT\\\\\"],\n          [\"@@\\\\[§t|¤_\"],\n          [\"@@m`n_\"],\n          [\"@@Ïxǌ{q_×^Giip\"],\n          [\n            \"@@@é^BntaÊU]x ¯ÄPĲ­°hʙK³VÕ@Y~|EvĹsÇ¦­L^pÃ²ŸÒG Ël]xxÄ_fT¤Ď¤cPC¨¸TVjbgH²sdÎdHt`B²¬GJję¶[ÐhjeXdlwhðSČ¦ªVÊÏÆZÆŶ®²^ÎyÅÎcPqńĚDMħĜŁH­kçvV[ĳ¼WYÀäĦ`XlR`ôLUVfK¢{NZdĒªYĸÌÚJRr¸SA|ƴgŴĴÆbvªØX~źB|¦ÕE¤Ð`\\\\|KUnnI]¤ÀÂĊnŎR®Ő¿¶\\\\ÀøíDm¦ÎbŨabaĘ\\\\ľãÂ¸atÎSƐ´©v\\\\ÖÚÌǴ¤Â¨JKrZ_ZfjþhPkx`YRIjJcVf~sCN¤ EhæmsHy¨SðÑÌ\\\\\\\\ĐRZk°IS§fqŒßýáĞÙÉÖ[^¯ǤŲê´\\\\¦¬ĆPM¯£»uïpùzExanµyoluqe¦W^£ÊL}ñrkqWňûPUP¡ôJoo·U}£[·¨@XĸDXm­ÛÝºGUCÁª½{íĂ^cjk¶Ã[q¤LÉö³cux«zZf²BWÇ®Yß½ve±ÃCý£W{Ú^q^sÑ·¨ÍOt¹·C¥GDrí@wÕKţÃ«V·i}xËÍ÷i©ĝɝǡ]{c±OW³Ya±_ç©HĕoƫŇqr³Lys[ñ³¯OSďOMisZ±ÅFC¥Pq{Ã[Pg}\\\\¿ghćOk^ģÁFıĉĥM­oEqqZûěŉ³F¦oĵhÕP{¯~TÍlªNßYÐ{Ps{ÃVUeĎwk±ŉVÓ½ŽJãÇÇ»Jm°dhcÀffdF~ĀeĖd`sx² ®EżĀdQÂd^~ăÔH¦\\\\LKpĄVez¤NP ǹÓRÆąJSh­a[¦´ÂghwmBÐ¨źhI|VV|p] Â¼èNä¶ÜBÖ¼L`¼bØæKVpoúNZÞÒKxpw|ÊEMnzEQIZZNBčÚFÜçmĩWĪñtÞĵÇñZ«uD±|Əlĳ¥ãn·±PmÍada CLǑkùó¡³Ï«QaċÏOÃ¥ÕđQȥċƭy³ÃA\"\n          ]\n        ],\n        \"encodeOffsets\": [\n          [[123686, 41445]],\n          [[126019, 40435]],\n          [[124393, 40128]],\n          [[126117, 39963]],\n          [[125322, 40140]],\n          [[126686, 40700]],\n          [[126041, 40374]],\n          [[125584, 40168]],\n          [[125453, 40165]],\n          [[125362, 40214]],\n          [[125280, 40291]],\n          [[125774, 39997]],\n          [[125976, 40496]],\n          [[125822, 39993]],\n          [[125509, 40217]],\n          [[122731, 40949]]\n        ]\n      }\n    },\n    {\n      \"type\": \"Feature\",\n      \"id\": \"220000\",\n      \"properties\": { \"id\": \"220000\", \"cp\": [125.3245, 43.886841], \"name\": \"吉林\", \"childNum\": 1 },\n      \"geometry\": {\n        \"type\": \"Polygon\",\n        \"coordinates\": [\n          \"@@pä³PClFbbÍzwBGĭZÅi»lY­ċ²SgkÇ£^Sqd¯R©é£¯S\\\\cZ¹iűƏCuƍÓXoR}M^o£R}oªU­FuuXHlEÅÏ©¤ÛmTþ¤D²ÄufàÀ­XXÈ±AeyYw¬dvõ´KÊ£\\\\rµÄlidā]|î©¾DÂVH¹Þ®ÜWnCķ W§@\\\\¸~¤Vp¸póIO¢VOŇürXql~òÉK]¤¥Xrfkvzpm¶bwyFoúvð¼¤ N°ąO¥«³[éǡű_°Õ\\\\ÚÊĝþâőàerR¨­JYlďQ[ ÏYëÐ§TGztnß¡gFkMāGÁ¤ia ÉÈ¹`\\\\xs¬dĆkNnuNUuP@vRY¾\\\\¢GªóĄ~RãÖÎĢùđŴÕhQxtcæëSɽŉíëǉ£ƍG£nj°KƘµDsØÑpyĆ¸®¿bXp]vbÍZuĂ{n^IüÀSÖ¦EvRÎûh@â[ƏÈô~FNr¯ôçR±­HÑlĢ^¤¢OðævxsŒ]ÞÁTĠs¶¿âÆGW¾ìA¦·TÑ¬è¥ÏÐJ¨¼ÒÖ¼ƦɄxÊ~StD@Ă¼Ŵ¡jlºWvÐzƦZÐ²CH AxiukdGgetqmcÛ£Ozy¥cE}|¾cZk¿uŐã[oxGikfeäT@SUwpiÚFM©£è^Ú`@v¶eňf heP¶täOlÃUgÞzŸU`l}ÔÆUvØ_Ō¬Öi^ĉi§²ÃB~¡ĈÚEgc|DC_Ȧm²rBx¼MÔ¦ŮdĨÃâYxƘDVÇĺĿg¿cwÅ\\\\¹¥Yĭl¤OvLjM_a W`zļMž·\\\\swqÝSAqŚĳ¯°kRē°wx^ĐkǂÒ\\\\]nrĂ}²ĊŲÒøãh·M{yMzysěnĒġV·°G³¼XÀ¤¹i´o¤ŃÈ`ÌǲÄUĞd\\\\iÖmÈBĤÜɲDEh LG¾ƀÄ¾{WaYÍÈĢĘÔRîĐj}ÇccjoUb½{h§Ǿ{KƖµÎ÷GĀÖŠåưÎs­lyiē«`å§H¥Ae^§GK}iã\\\\c]v©ģZmÃ|[M}ģTɟĵÂÂ`ÀçmFK¥ÚíÁbX³ÌQÒHof{]ept·GŋĜYünĎųVY^ydõkÅZW«WUa~U·SbwGçǑiW^qFuNĝ·EwUtW·Ýďæ©PuqEzwAVXRãQ`­©GMehccďÏd©ÑW_ÏYƅ»é\\\\ɹ~ǙG³mØ©BšuT§Ĥ½¢Ã_Ã½L¡ýqT^rme\\\\PpZZbyuybQefµ]UhĿDCmûvaÙNSkCwncćfv~YÇG\"\n        ],\n        \"encodeOffsets\": [[130196, 42528]]\n      }\n    },\n    {\n      \"type\": \"Feature\",\n      \"id\": \"230000\",\n      \"properties\": {\n        \"id\": \"230000\",\n        \"cp\": [128.642464, 46.756967],\n        \"name\": \"黑龙江\",\n        \"childNum\": 2\n      },\n      \"geometry\": {\n        \"type\": \"MultiPolygon\",\n        \"coordinates\": [\n          [\n            \"@@UµNÿ¥īèçHÍøƕ¶Lǽ|g¨|a¾pVidd~ÈiíďÓQġėÇZÎXb½|ſÃH½KFgɱCģÛÇAnjÕc[VĝǱÃËÇ_ £ń³pj£º¿»WH´¯U¸đĢmtĜyzzNN|g¸÷äűÑ±ĉā~mq^[ǁÑďlw]¯xQĔ¯l°řĴrBÞTxr[tŽ¸ĻN_yX`biNKuP£kZĮ¦[ºxÆÀdhĹŀUÈƗCwáZħÄŭcÓ¥»NAw±qȥnD`{ChdÙFć}¢A±Äj¨]ĊÕjŋ«×`VuÓÅ~_kŷVÝyhVkÄãPsOµfgeŇµf@u_Ù ÙcªNªÙEojVxT@ãSefjlwH\\\\pŏäÀvlY½d{F~¦dyz¤PÜndsrhfHcvlwjF£G±DÏƥYyÏu¹XikĿ¦ÏqƗǀOŜ¨LI|FRĂn sª|C˜zxAè¥bfudTrFWÁ¹Am|ĔĕsķÆF´N}ćUÕ@Áĳſmuçuð^ÊýowFzØÎĕNőǏȎôªÌŒǄàĀÄ˄ĞŀƒʀĀƘŸˮȬƬĊ°Uzouxe]}AyÈW¯ÌmKQ]Īºif¸ÄX|sZt|½ÚUÎ lk^p{f¤lºlÆW A²PVÜPHÊâ]ÎĈÌÜk´\\\\@qàsĔÄQºpRij¼èi`¶bXrBgxfv»uUi^v~J¬mVp´£´VWrnP½ì¢BX¬hðX¹^TjVriªjtŊÄmtPGx¸bgRsT`ZozÆO]ÒFôÒOÆŊvÅpcGêsx´DR{AEOr°x|íb³Wm~DVjºéNNËÜ˛ɶ­GxŷCSt}]ûōSmtuÇÃĕNāg»íT«u}ç½BĵÞʣ¥ëÊ¡MÛ³ãȅ¡ƋaǩÈÉQG¢·lG|tvgrrf«ptęŘnÅĢrI²¯LiØsPf_vĠdxM prʹL¤¤eËÀđKïÙVY§]Ióáĥ]ķK¥j|pŇ\\\\kzţ¦šnņäÔVĂîĪ¬|vW®l¤èØrxm¶ă~lÄƯĄ̈́öȄEÔ¤ØQĄĄ»ƢjȦOǺ¨ìSŖÆƬyQv`cwZSÌ®ü±Ǆ]ŀç¬B¬©ńzƺŷɄeeOĨSfm ĊƀP̎ēz©ĊÄÕÊmgÇsJ¥ƔŊśæÎÑqv¿íUOµªÂnĦÁ_½ä@êí£P}Ġ[@gġ}gɊ×ûÏWXá¢užƻÌsNÍ½ƎÁ§čŐAēeL³àydl¦ĘVçŁpśǆĽĺſÊQíÜçÛġÔsĕ¬Ǹ¯YßċġHµ ¡eå`ļrĉŘóƢFìĎWøxÊkƈdƬv|I|·©NqńRŀ¤éeŊŀàŀU²ŕƀBQ£Ď}L¹Îk@©ĈuǰųǨÚ§ƈnTËÇéƟÊcfčŤ^XmHĊĕË«W·ċëx³ǔķÐċJāwİ_ĸȀ^ôWr­°oú¬ĦŨK~ȰCĐ´Ƕ£fNÎèâw¢XnŮeÂÆĶ¾¾xäLĴĘlļO¤ÒĨA¢Êɚ¨®ØCÔ ŬGƠƦYĜĘÜƬDJg_ͥœ@čŅĻA¶¯@wÎqC½Ĉ»NăëKďÍQÙƫ[«ÃígßÔÇOÝáWñuZ¯ĥŕā¡ÑķJu¤E å¯°WKÉ±_d_}}vyõu¬ï¹ÓU±½@gÏ¿rÃ½DgCdµ°MFYxw¿CG£Rƛ½Õ{]L§{qqą¿BÇƻğëܭǊË|c²}Fµ}ÙRsÓpg±QNqǫŋRwŕnéÑÉK«SeYRŋ@{¤SJ}D Ûǖ֍]gr¡µŷjqWÛham³~S«Þ]\"\n          ]\n        ],\n        \"encodeOffsets\": [[[134456, 44547]]]\n      }\n    },\n    {\n      \"type\": \"Feature\",\n      \"id\": \"320000\",\n      \"properties\": {\n        \"id\": \"320000\",\n        \"cp\": [119.767413, 33.041544],\n        \"name\": \"江苏\",\n        \"childNum\": 1\n      },\n      \"geometry\": {\n        \"type\": \"Polygon\",\n        \"coordinates\": [\n          \"@@cþÅPi`ZRu¥É\\\\]~°Y`µÓ^phÁbnÀşúòaĬºTÖŒbe¦¦{¸ZâćNp©Hr|^mjhSEb\\\\afv`sz^lkljÄtg¤D­¾X¿À|ĐiZȀåB·î}GL¢õcßjayBFµÏC^ĭcÙt¿sğH]j{s©HM¢QnDÀ©DaÜÞ·jgàiDbPufjDk`dPOîhw¡ĥ¥GP²ĐobºrYî¶aHŢ´ ]´rılw³r_{£DB_Ûdåuk|Ũ¯F Cºyr{XFye³Þċ¿ÂkĭB¿MvÛpm`rÚã@Ę¹hågËÖƿxnlč¶Åì½Ot¾dJlVJĂǀŞqvnO^JZż·Q}êÍÅmµÒ]ƍ¦Dq}¬R^èĂ´ŀĻĊIÔtĲyQŐĠMNtR®òLhĚs©»}OÓGZz¶A\\\\jĨFäOĤHYJvÞHNiÜaĎÉnFQlNM¤B´ĄNöɂtpŬdfåqm¿QûùŞÚb¤uŃJŴu»¹ĄlȖħŴw̌ŵ²ǹǠ͛hĭłƕrçü±Yxcitğ®jű¢KOķCoy`å®VTa­_Ā]ŐÝɞï²ʯÊ^]afYǸÃĆēĪȣJđ͍ôƋÄÄÍīçÛɈǥ£­ÛmY`ó£Z«§°Ó³QafusNıǅ_k}¢m[ÝóDµ¡RLčiXyÅNïă¡¸iĔÏNÌŕoēdōîåŤûHcs}~Ûwbù¹£¦ÓCtOPrE^ÒogĉIµÛÅʹK¤½phMü`oæŀ\"\n        ],\n        \"encodeOffsets\": [[121740, 32276]]\n      }\n    },\n    {\n      \"type\": \"Feature\",\n      \"id\": \"330000\",\n      \"properties\": {\n        \"id\": \"330000\",\n        \"cp\": [120.153576, 29.287459],\n        \"name\": \"浙江\",\n        \"childNum\": 45\n      },\n      \"geometry\": {\n        \"type\": \"MultiPolygon\",\n        \"coordinates\": [\n          [\"@@E^dQ]K\"],\n          [\"@@jX^j\"],\n          [\"@@sfbU\"],\n          [\"@@qP\\\\xz[ck\"],\n          [\"@@R¢FX}°[s_\"],\n          [\"@@Cb\\\\}\"],\n          [\"@@e|v\\\\la{u\"],\n          [\"@@v~u}\"],\n          [\"@@QxÂF¯}\"],\n          [\"@@¹nvÞs¯o\"],\n          [\"@@rSkUEj\"],\n          [\"@@bi­ZP\"],\n          [\"@@p[}INf\"],\n          [\"@@À¿\"],\n          [\"@@¹dnb\"],\n          [\"@@rSBnR\"],\n          [\"@@g~h}\"],\n          [\"@@FlEk\"],\n          [\"@@OdPc\"],\n          [\"@@v[u\\\\\"],\n          [\"@@FjâL~wyoo~sµL\\\\\"],\n          [\"@@¬e¹aN\"],\n          [\"@@\\\\nÔ¡q]L³ë\\\\ÿ®QÖ\"],\n          [\"@@ÊA­©[¬\"],\n          [\"@@Kxv­\"],\n          [\"@@@hlIk]\"],\n          [\"@@pW{o||j\"],\n          [\"@@Md|_mC\"],\n          [\"@@¢X£ÏylD¼XtH\"],\n          [\"@@hlÜ[LykAvyfw^E¤\"],\n          [\"@@fp¤MusR\"],\n          [\"@@®_ma~LÁ¬Z\"],\n          [\"@@iMxZ\"],\n          [\"@@ZcYd\"],\n          [\"@@Z~dOSo|A¿qZv\"],\n          [\"@@@`EN¡v\"],\n          [\"@@|TY{\"],\n          [\"@@@n@m\"],\n          [\"@@XWkCT\\\\\"],\n          [\"@@ºwZRkĕWO¢\"],\n          [\"@@X®±GrÆª\\\\ÔáXq{\"],\n          [\"@@ůTG°ĄLHm°UC\"],\n          [\n            \"@@¤aÜx~}dtüGæţŎíĔcŖpMËÐjē¢·ðĄÆMzjWKĎ¢Q¶À_ê_Bıi«pZgf¤Nrq]§ĂN®«H±yƳí¾×ŸīàLłčŴǝĂíÀBŖÕªÁŖHŗŉåqûõi¨hÜ·ñt»¹ýv_[«¸mYL¯QªmĉÅdMgÇjcº«ę¬­K­´B«Âącoċ\\\\xKd¡gěŧ«®á[~ıxu·ÅKsËÉc¢Ù\\\\ĭƛëbf¹­ģSĜkáƉÔ­ĈZB{aMµfzŉfåÂŧįƋǝÊĕġć£g³ne­ą»@­¦S®\\\\ßðChiqªĭiAuA­µ_W¥ƣO\\\\lċĢttC¨£t`PZäuXßBsĻyekOđġĵHuXBµ]×­­\\\\°®¬F¢¾pµ¼kŘó¬Wät¸|@L¨¸µrºù³Ù~§WIZW®±Ð¨ÒÉx`²pĜrOògtÁZ}þÙ]¡FKwsPlU[}¦Rvn`hq¬\\\\nQ´ĘRWb_ rtČFIÖkĦPJ¶ÖÀÖJĈĄTĚòC ²@PúØz©Pî¢£CÈÚĒ±hŖl¬â~nm¨f©iļ«mntuÖZÜÄjL®EÌFª²iÊxØ¨IÈhhst\"\n          ],\n          [\"@@o\\\\VzRZ}y\"],\n          [\"@@@°¡mÛGĕ¨§Ianá[ýƤjfæØLäGr\"]\n        ],\n        \"encodeOffsets\": [\n          [[125592, 31553]],\n          [[125785, 31436]],\n          [[125729, 31431]],\n          [[125513, 31380]],\n          [[125223, 30438]],\n          [[125115, 30114]],\n          [[124815, 29155]],\n          [[124419, 28746]],\n          [[124095, 28635]],\n          [[124005, 28609]],\n          [[125000, 30713]],\n          [[125111, 30698]],\n          [[125078, 30682]],\n          [[125150, 30684]],\n          [[124014, 28103]],\n          [[125008, 31331]],\n          [[125411, 31468]],\n          [[125329, 31479]],\n          [[125626, 30916]],\n          [[125417, 30956]],\n          [[125254, 30976]],\n          [[125199, 30997]],\n          [[125095, 31058]],\n          [[125083, 30915]],\n          [[124885, 31015]],\n          [[125218, 30798]],\n          [[124867, 30838]],\n          [[124755, 30788]],\n          [[124802, 30809]],\n          [[125267, 30657]],\n          [[125218, 30578]],\n          [[125200, 30562]],\n          [[124968, 30474]],\n          [[125167, 30396]],\n          [[124955, 29879]],\n          [[124714, 29781]],\n          [[124762, 29462]],\n          [[124325, 28754]],\n          [[123990, 28459]],\n          [[125366, 31477]],\n          [[125115, 30363]],\n          [[125369, 31139]],\n          [[122495, 31878]],\n          [[125329, 30690]],\n          [[125192, 30787]]\n        ]\n      }\n    },\n    {\n      \"type\": \"Feature\",\n      \"id\": \"340000\",\n      \"properties\": { \"id\": \"340000\", \"cp\": [117.283042, 31.26119], \"name\": \"安徽\", \"childNum\": 3 },\n      \"geometry\": {\n        \"type\": \"MultiPolygon\",\n        \"coordinates\": [\n          [\"@@^iuLX^\"],\n          [\"@@e©Ehl\"],\n          [\n            \"@@°ZÆëĎµmkǀwÌÕæhºgBĝâqÙĊzÖgņtÀÁĂÆáhEz|WzqD¹°Eŧl{ævÜcA`¤C`|´qxĲkq^³³GšµbíZ¹qpa±ď OH¦Ħx¢gPícOl_iCveaOjChß¸iÝbÛªCC¿mRV§¢A|t^iĠGÀtÚsd]ĮÐDE¶zAb àiödK¡~H¸íæAǿYj{ď¿À½W®£ChÃsikkly]_teu[bFaTign{]GqªoĈMYá|·¥f¥őaSÕėNµñĞ«Im_m¿Âa]uĜp Z_§{Cäg¤°r[_YjÆOdý[I[á·¥Q_nùgL¾mvˊBÜÆ¶ĊJhpc¹O]iŠ]¥ jtsggJÇ§w×jÉ©±EFË­KiÛÃÕYvsm¬njĻª§emná}k«ŕgđ²ÙDÇ¤í¡ªOy×Où±@DñSęćăÕIÕ¿IµĥOjNÕËT¡¿tNæŇàåyķrĕq§ÄĩsWÆßF¶X®¿mwRIÞfßoG³¾©uyHį{Ɓħ¯AFnuPÍÔzVdàôº^Ðæd´oG¤{S¬ćxã}ŧ×Kǥĩ«ÕOEÐ·ÖdÖsƘÑ¨[Û^Xr¢¼§xvÄÆµ`K§ tÒ´Cvlo¸fzŨð¾NY´ı~ÉĔēßúLÃÃ_ÈÏ|]ÂÏFlg`ben¾¢pUh~ƴĖ¶_r sĄ~cƈ]|r c~`¼{À{ȒiJjz`îÀT¥Û³]u}fïQl{skloNdjäËzDvčoQďHI¦rbtHĔ~BmlRV_ħTLnñH±DL¼Lªl§Ťa¸ĚlK²\\\\RòvDcÎJbt[¤D@®hh~kt°ǾzÖ@¾ªdbYhüóZ ň¶vHrľ\\\\ÊJuxAT|dmÀO[ÃÔG·ĚąĐlŪÚpSJ¨ĸLvÞcPæķŨ®mÐálwKhïgA¢ųÆ©Þ¤OÈm°K´\"\n          ]\n        ],\n        \"encodeOffsets\": [[[121722, 32278]], [[119475, 30423]], [[119168, 35472]]]\n      }\n    },\n    {\n      \"type\": \"Feature\",\n      \"id\": \"350000\",\n      \"properties\": {\n        \"id\": \"350000\",\n        \"cp\": [118.306239, 26.075302],\n        \"name\": \"福建\",\n        \"childNum\": 18\n      },\n      \"geometry\": {\n        \"type\": \"MultiPolygon\",\n        \"coordinates\": [\n          [\"@@zht´]\"],\n          [\"@@aj^~ĆG©O\"],\n          [\"@@ed¨C}}i\"],\n          [\"@@@vPGsQ\"],\n          [\"@@sBzddW]Q\"],\n          [\"@@S¨Q{\"],\n          [\"@@NVucW\"],\n          [\"@@qptBAq\"],\n          [\"@@¸[mu\"],\n          [\"@@Q\\\\pD]_\"],\n          [\"@@jSwUadpF\"],\n          [\"@@eXª~\"],\n          [\"@@AjvFso\"],\n          [\"@@fT_Çí\\\\v|ba¦jZÆy°\"],\n          [\"@@IjJi\"],\n          [\"@@wJIx«¼AoNe{M­\"],\n          [\"@@K±¡ÓČäeZ\"],\n          [\n            \"@@k¡¹Eh~c®wBkUplÀ¡I~Māe£bN¨gZý¡a±Öcp©PhI¢QqÇGj|¥U g[Ky¬ŏv@OptÉEF\\\\@ åA¬V{XģĐBycpě¼³Ăp·¤¥ohqqÚ¡ŅLs^Ã¡§qlÀhH¨MCe»åÇGD¥zPO£čÙkJA¼ßėuĕeûÒiÁŧSW¥Qûŗ½ùěcÝ§SùĩąSWó«íęACµeRåǃRCÒÇZÍ¢ź±^dlstjD¸ZpuÔâÃH¾oLUêÃÔjjēò´ĄWƛ^Ñ¥Ħ@ÇòmOw¡õyJyD}¢ďÑÈġfZda©º²z£NjD°Ötj¶¬ZSÎ~¾c°¶ÐmxO¸¢Pl´SL|¥AȪĖMņĲg®áIJČĒü` QF¬h|ĂJ@zµ |ê³È ¸UÖŬŬÀEttĸr]ðM¤ĶĲHtÏ AĬkvsq^aÎbvdfÊòSD´Z^xPsĂrvƞŀjJd×ŘÉ ®AÎ¦ĤdxĆqAZRÀMźnĊ»İÐZ YXæJyĊ²·¶q§·K@·{sXãô«lŗ¶»o½E¡­«¢±¨Y®Ø¶^AvWĶGĒĢPlzfļtàAvWYãO_¤sD§ssČġ[kƤPX¦`¶®BBvĪjv©jx[L¥àï[F¼ÍË»ğV`«Ip}ccÅĥZEãoP´B@D¸m±z«Ƴ¿å³BRØ¶Wlâþäą`]Z£Tc ĹGµ¶Hm@_©k¾xĨôȉðX«½đCIbćqK³ÁÄš¬OAwã»aLŉËĥW[ÂGIÂNxĳ¤D¢îĎÎB§°_JGs¥E@¤ućPåcuMuw¢BI¿]zG¹guĮck\\\\_\"\n          ]\n        ],\n        \"encodeOffsets\": [\n          [[123250, 27563]],\n          [[122541, 27268]],\n          [[123020, 27189]],\n          [[122916, 27125]],\n          [[122887, 26845]],\n          [[122808, 26762]],\n          [[122568, 25912]],\n          [[122778, 26197]],\n          [[122515, 26757]],\n          [[122816, 26587]],\n          [[123388, 27005]],\n          [[122450, 26243]],\n          [[122578, 25962]],\n          [[121255, 25103]],\n          [[120987, 24903]],\n          [[122339, 25802]],\n          [[121042, 25093]],\n          [[122439, 26024]]\n        ]\n      }\n    },\n    {\n      \"type\": \"Feature\",\n      \"id\": \"360000\",\n      \"properties\": {\n        \"id\": \"360000\",\n        \"cp\": [115.592151, 27.676493],\n        \"name\": \"江西\",\n        \"childNum\": 1\n      },\n      \"geometry\": {\n        \"type\": \"Polygon\",\n        \"coordinates\": [\n          \"@@ĢĨƐgļ¼ÂMD~ņªe^\\\\^§ý©j×cZØ¨zdÒa¶lÒJìõ`oz÷@¤uŞ¸´ôęöY¼HČƶajlÞƩ¥éZ[|h}^U  ¥pĄžƦO lt¸Æ Q\\\\aÆ|CnÂOjt­ĚĤdÈF`¶@Ðë ¦ōÒ¨SêvHĢûXD®QgÄWiØPÞìºr¤ǆNĠ¢lĄtZoCƞÔºCxrpĠV®Ê{f_Y`_eq®Aot`@oDXfkp¨|s¬\\\\DÄSfè©Hn¬^DhÆyøJhØxĢĀLÊƠPżċĄwȠĚ¦G®ǒĤäTŠÆ~Ħw«|TF¡nc³Ïå¹]ĉđxe{ÎÓvOEm°BƂĨİ|Gvz½ª´HàpeJÝQxnÀW­EµàXÅĪt¨ÃĖrÄwÀFÎ|ňÓMå¼ibµ¯»åDT±m[r«_gmQu~¥V\\\\OkxtL E¢Ú^~ýêPóqoě±_Êw§ÑªåƗā¼mĉŹ¿NQYBąrwģcÍ¥B­ŗÊcØiIƝĿuqtāwO]³YCñTeÉcaubÍ]trluīBÐGsĵıN£ï^ķqss¿FūūVÕ·´Ç{éĈýÿOER_đûIċâJh­ŅıNȩĕB¦K{Tk³¡OP·wnµÏd¯}½TÍ«YiµÕsC¯iM¤­¦¯P|ÿUHvhe¥oFTuõ\\\\OSsMòđƇiaºćXĊĵà·çhƃ÷Ç{ígu^đgm[×zkKN¶Õ»lčÓ{XSÆv©_ÈëJbVkĔVÀ¤P¾ºÈMÖxlò~ªÚàGĂ¢B±ÌKyáV¼Ã~­`gsÙfIƋlę¹e|~udjuTlXµf`¿Jd[\\\\L²\"\n        ],\n        \"encodeOffsets\": [[116689, 26234]]\n      }\n    },\n    {\n      \"type\": \"Feature\",\n      \"id\": \"370000\",\n      \"properties\": {\n        \"id\": \"370000\",\n        \"cp\": [118.000923, 36.275807],\n        \"name\": \"山东\",\n        \"childNum\": 13\n      },\n      \"geometry\": {\n        \"type\": \"MultiPolygon\",\n        \"coordinates\": [\n          [\"@@Xjd]{K\"],\n          [\"@@itbFHy\"],\n          [\"@@HlGk\"],\n          [\"@@TGy\"],\n          [\"@@K¬U\"],\n          [\"@@WdXc\"],\n          [\"@@PtOs\"],\n          [\"@@LnXhc\"],\n          [\"@@ppVu]Or\"],\n          [\"@@cdzAUa\"],\n          [\"@@udRhnCI\"],\n          [\"@@oIpR\"],\n          [\n            \"@@Ľč{fzƤîKÎMĮ]ZF½Y]â£ph¶¨râøÀÎǨ¤^ºÄGz~grĚĜlĞÆLĆǆ¢Îo¦cvKbgr°WhmZp L]LºcUÆ­nżĤÌĒbAnrOA´ȊcÀbƦUØrĆUÜøĬƞEzVL®öØBkŖÝĐĖ¹ŧ̄±ÀbÎÉnb²ĦhņBĖįĦåXćì@L¯´ywƕCéÃµė ƿ¸lµ¾Z|ZWyFY¨Mf~C¿`à_RÇzwƌfQnny´INoƬèôº|sTJULîVjǎ¾ĒØDz²XPn±ŴPè¸ŔLƔÜƺ_TüÃĤBBċÈöA´faM¨{«M`¶d¡ôÖ°mȰBÔjj´PM|c^d¤u¤Û´ä«ƢfPk¶Môl]Lb}su^ke{lCMrDÇ­]NÑFsmoõľHyGă{{çrnÓEƕZGª¹Fj¢ïWuøCǷë¡ąuhÛ¡^KxC`C\\\\bÅxì²ĝÝ¿_NīCȽĿåB¥¢·IŖÕy\\\\¹kxÃ£Č×GDyÃ¤ÁçFQ¡KtŵƋ]CgÏAùSedcÚźuYfyMmhUWpSyGwMPqŀÁ¼zK¶G­Y§Ë@´śÇµƕBm@IogZ¯uTMx}CVKï{éƵP_K«pÛÙqċtkkù]gTğwoɁsMõ³ăAN£MRkmEÊčÛbMjÝGuIZGPģãħE[iµBEuDPÔ~ª¼ęt]ûG§¡QMsğNPŏįzs£Ug{đJĿļā³]ç«Qr~¥CƎÑ^n¶ÆéÎR~Ż¸YI] PumŝrƿIā[xeÇ³L¯v¯s¬ÁY~}ťuŁgƋpÝĄ_ņī¶ÏSR´ÁP~¿Cyċßdwk´SsX|t`Ä ÈðAªìÎT°¦Dda^lĎDĶÚY°`ĪŴǒàŠv\\\\ebZHŖR¬ŢƱùęOÑM­³FÛWp[\"\n          ]\n        ],\n        \"encodeOffsets\": [\n          [[123806, 39303]],\n          [[123821, 39266]],\n          [[123742, 39256]],\n          [[123702, 39203]],\n          [[123649, 39066]],\n          [[123847, 38933]],\n          [[123580, 38839]],\n          [[123894, 37288]],\n          [[123043, 36624]],\n          [[123344, 38676]],\n          [[123522, 38857]],\n          [[123628, 38858]],\n          [[118260, 36742]]\n        ]\n      }\n    },\n    {\n      \"type\": \"Feature\",\n      \"id\": \"410000\",\n      \"properties\": {\n        \"id\": \"410000\",\n        \"cp\": [113.665412, 33.757975],\n        \"name\": \"河南\",\n        \"childNum\": 1\n      },\n      \"geometry\": {\n        \"type\": \"Polygon\",\n        \"coordinates\": [\n          \"@@ýLùµP³swIÓxcŢĞð´E®ÚPtĴXØxÂ¶@«ŕŕQGYfa[şußǩđš_X³ĳÕčC]kbc¥CS¯ëÍB©÷³­Si_}mYTt³xlàcČzÀD}ÂOQ³ÐTĨ¯ƗòËŖ[hłŦv~}ÂZ«¤lPÇ£ªÝŴÅR§ØnhctâknÏ­ľŹUÓÝdKuķI§oTũÙďkęĆH¸Ó\\\\Ä¿PcnS{wBIvÉĽ[GqµuŇôYgûZca©@½Õǽys¯}lgg@­C\\\\£asIdÍuCQñ[L±ęk·ţb¨©kK»KC²òGKmĨS`UQnk}AGēsqaJ¥ĐGRĎpCuÌy ã iMcplk|tRkðev~^´¦ÜSí¿_iyjI|ȑ|¿_»d}q^{Ƈdă}tqµ`Ƴĕg}V¡om½faÇo³TTj¥tĠRyK{ùÓjuµ{t}uËRivGçJFjµÍyqÎàQÂFewixGw½Yŷpµú³XU½ġyłåkÚwZX·l¢Á¢KzOÎÎjc¼htoDHr|­J½}JZ_¯iPq{tę½ĕ¦Zpĵø«kQĹ¤]MÛfaQpě±ǽ¾]u­Fu÷nčÄ¯ADp}AjmcEÇaª³o³ÆÍSƇĈÙDIzËčľ^KLiÞñ[aA²zzÌ÷D|[íÄ³gfÕÞd®|`Ć~oĠƑô³ŊD×°¯CsøÀ«ìUMhTº¨¸ǡîSÔDruÂÇZÖEvPZW~ØÐtĄE¢¦Ðy¸bô´oŬ¬²Ês~]®tªapŎJ¨Öº_Ŕ`Ŗ^Đ\\\\Ĝu~m²Ƹ¸fWĦrƔ}Î^gjdfÔ¡J}\\\\n C¦þWxªJRÔŠu¬ĨĨmFdM{\\\\d\\\\YÊ¢ú@@¦ª²SÜsC}fNècbpRmlØ^gd¢aÒ¢CZZxvÆ¶N¿¢T@uC¬^ĊðÄn|lGlRjsp¢ED}Fio~ÔN~zkĘHVsǲßjŬŢ`Pûàl¢\\\\ÀEhİgÞē X¼Pk|m\"\n        ],\n        \"encodeOffsets\": [[118256, 37017]]\n      }\n    },\n    {\n      \"type\": \"Feature\",\n      \"id\": \"420000\",\n      \"properties\": {\n        \"id\": \"420000\",\n        \"cp\": [113.298572, 30.684355],\n        \"name\": \"湖北\",\n        \"childNum\": 3\n      },\n      \"geometry\": {\n        \"type\": \"MultiPolygon\",\n        \"coordinates\": [\n          [\"@@AB\"],\n          [\"@@lskt\"],\n          [\n            \"@@¾«}{ra®pîÃ\\\\{øCËyyB±b\\\\òÝjKL ]ĎĽÌJyÚCƈćÎT´Å´pb©ÈdFin~BCo°BĎÃømv®E^vǾ½Ĝ²RobÜeN^ĺ£R¬lĶ÷YoĖ¥Ě¾|sOr°jY`~I¾®I{GqpCgyl{£ÍÍyPLÂ¡¡¸kWxYlÙæŁĢz¾V´W¶ùŸo¾ZHxjwfxGNÁ³Xéæl¶EièIH ujÌQ~v|sv¶Ôi|ú¢FhQsğ¦SiŠBgÐE^ÁÐ{čnOÂÈUÎóĔÊēĲ}Z³½Mŧïeyp·uk³DsÑ¨L¶_ÅuÃ¨w»¡WqÜ]\\\\Ò§tƗcÕ¸ÕFÏǝĉăxŻČƟOKÉġÿ×wg÷IÅzCg]m«ªGeçÃTC«[t§{loWeC@ps_Bp­rf_``Z|ei¡oċMqow¹DƝÓDYpûsYkıǃ}s¥ç³[§cY§HK«Qy]¢wwö¸ïx¼ņ¾Xv®ÇÀµRĠÐHM±cÏdƒǍũȅȷ±DSyúĝ£ŤĀàtÖÿï[îb\\\\}pĭÉI±Ñy¿³x¯No|¹HÏÛmjúË~TuęjCöAwě¬Rđl¯ Ñb­ŇTĿ_[IčĄʿnM¦ğ\\\\É[T·k¹©oĕ@A¾wya¥Y\\\\¥Âaz¯ãÁ¡k¥ne£ÛwE©Êō¶˓uoj_U¡cF¹­[WvP©whuÕyBF`RqJUw\\\\i¡{jEPïÿ½fćQÑÀQ{°fLÔ~wXgītêÝ¾ĺHd³fJd]HJ²EoU¥HhwQsƐ»Xmg±çve]DmÍPoCc¾_hhøYrŊU¶eD°Č_N~øĹĚ·`z]Äþp¼äÌQv\\\\rCé¾TnkžŐÚÜa¼ÝƆĢ¶ÛodĔňÐ¢JqPb ¾|J¾fXƐîĨ_Z¯À}úƲN_ĒÄ^ĈaŐyp»CÇÄKñL³ġM²wrIÒŭxjb[n«øæà ^²­h¯ÚŐªÞ¸Y²ĒVø}Ā^İ´LÚm¥ÀJÞ{JVųÞŃx×sxxƈē ģMřÚðòIfĊŒ\\\\Ʈ±ŒdÊ§ĘDvČ_Àæ~Dċ´A®µ¨ØLV¦êHÒ¤\"\n          ]\n        ],\n        \"encodeOffsets\": [[[113712, 34000]], [[115612, 30507]], [[113649, 34054]]]\n      }\n    },\n    {\n      \"type\": \"Feature\",\n      \"id\": \"430000\",\n      \"properties\": { \"id\": \"430000\", \"cp\": [111.782279, 28.09409], \"name\": \"湖南\", \"childNum\": 3 },\n      \"geometry\": {\n        \"type\": \"MultiPolygon\",\n        \"coordinates\": [\n          [\"@@nFTs\"],\n          [\"@@ßÅÆá½ÔXrCOËRïÿĩ­TooQyÓ[ŅBE¬ÎÓXaį§Ã¸G °ITxpúxÚĳ¥ÏĢ¾edÄ©ĸGàGhM¤Â_U}Ċ}¢pczfþg¤ÇòAVM\"],\n          [\n            \"@@©KA·³CQ±Á«³BUƑ¹AtćOwD]JiØSm¯b£ylXHËÑ±H«C^õľAÅ§¤É¥ïyuǙuA¢^{ÌC´­¦ŷJ£^[ª¿ĕ~ƇN skóā¹¿ï]ă~÷O§­@Vm¡Qđ¦¢Ĥ{ºjÔª¥nf´~Õo×ÛąMąıuZmZcÒ ĲĪ²SÊǄŶ¨ƚCÖŎªQØ¼rŭ­«}NÏürÊ¬mjr@ĘrTW ­SsdHzƓ^ÇÂyUi¯DÅYlŹu{hT}mĉ¹¥ěDÿë©ıÓ[Oº£¥ótł¹MÕƪ`PDiÛU¾ÅâìUñBÈ£ýhedy¡oċ`pfmjP~kZaZsÐd°wj§@Ĵ®w~^kÀÅKvNmX\\\\¨aŃqvíó¿F¤¡@ũÑVw}S@j}¾«pĂrªg àÀ²NJ¶¶DôK|^ª°LX¾ŴäPĪ±£EXd^¶ĲÞÜ~u¸ǔMRhsRe`ÄofIÔ\\\\Ø  ićymnú¨cj ¢»GČìƊÿÐ¨XeĈĀ¾Oð Fi ¢|[jVxrIQ_EzAN¦zLU`cªxOTu RLÄ¢dVi`p˔vŎµªÉF~Ød¢ºgİàw¸Áb[¦Zb¦z½xBĖ@ªpºlS¸Ö\\\\Ĕ[N¥ˀmĎăJ\\\\ŀ`ňSÚĖÁĐiOĜ«BxDõĚivSÌ}iùÜnÐºG{p°M´wÀÒzJ²ò¨ oTçüöoÛÿñőĞ¤ùTz²CȆȸǎŪƑÐc°dPÎğË¶[È½u¯½WM¡­ÉB·rínZÒ `¨GA¾\\\\pēXhÃRC­üWGġuTé§ŎÑ©ò³I±³}_EÃħg®ęisÁPDmÅ{b[RÅs·kPŽƥóRoOV~]{g\\\\êYƪ¦kÝbiċƵGZ»Ěõó·³vŝ£ø@pyö_ëIkÑµbcÑ§y×dYØªiþ¨[]f]Ņ©C}ÁN»hĻħƏĩ\"\n          ]\n        ],\n        \"encodeOffsets\": [[[115640, 30489]], [[112543, 27312]], [[116690, 26230]]]\n      }\n    },\n    {\n      \"type\": \"Feature\",\n      \"id\": \"440000\",\n      \"properties\": {\n        \"id\": \"440000\",\n        \"cp\": [113.280637, 23.125178],\n        \"name\": \"广东\",\n        \"childNum\": 24\n      },\n      \"geometry\": {\n        \"type\": \"MultiPolygon\",\n        \"coordinates\": [\n          [\"@@QdAua\"],\n          [\"@@lxDLo\"],\n          [\"@@sbhNLo\"],\n          [\"@@Ă ā\"],\n          [\"@@WltO[[\"],\n          [\"@@Kr]S\"],\n          [\"@@eI]y\"],\n          [\"@@I|Mym\"],\n          [\"@@Û³LS¼Y\"],\n          [\"@@nvºBëui©`¾\"],\n          [\"@@zdÛJw®\"],\n          [\"@@°¯\"],\n          [\"@@a yAª¸ËJIxØ@ĀHAmÃV¡ofuo\"],\n          [\"@@sŗÃÔėAƁZÄ ~°ČPäh\"],\n          [\"@@¶ÝÌvmĞh­ıQ\"],\n          [\"@@HdSjĒ¢D}waru«ZqadYM\"],\n          [\"@@el\\\\LqqU\"],\n          [\"@@~rMo\\\\\"],\n          [\"@@f^C\"],\n          [\"@@øPªoj÷ÍÝħXČx°Q¨ıXNv\"],\n          [\"@@gÇƳo[~tly\"],\n          [\"@@EÆC¿\"],\n          [\"@@OP\"],\n          [\n            \"@@wđógĝ[³¡VÙæÅöMÌ³¹pÁaËýý©D©ÜJŹƕģGą¤{ÙūÇO²«BƱéAÒĥ¡«BhlmtÃPµyU¯ucd·w_bŝcīímGO|KPȏŹãŝIŕŭŕ@Óoo¿ē±ß}ŭĲWÈCőâUâǙIğŉ©IĳE×Á³AówXJþ±ÌÜÓĨ£L]ĈÙƺZǾĆĖMĸĤfÎĵlŨnÈĐtFFĤêk¶^k°f¶g}®Faf`vXŲxl¦ÔÁ²¬Ð¦pqÊÌ²iXØRDÎ}Ä@ZĠsx®AR~®ETtĄZƈfŠŠHâÒÐAµ\\\\S¸^wĖkRzalŜ|E¨ÈNĀňZTpBh£\\\\ĎƀuXĖtKL¶G|»ĺEļĞ~ÜĢÛĊrOÙîvd]n¬VÊĜ°RÖpMƂªFbwEÀ©\\\\¤]ŸI®¥D³|Ë]CöAŤ¦æ´¥¸Lv¼¢ĽBaôF~®²GÌÒEYzk¤°ahlVÕI^CxĈPsBƒºV¸@¾ªR²ĨN]´_eavSivc}p}Đ¼ƌkJÚe th_¸ ºx±ò_xNË²@ă¡ßH©Ùñ}wkNÕ¹ÇO½¿£ĕ]ly_WìIÇª`uTÅxYĒÖ¼kÖµMjJÚwn\\\\hĒv]îh|ÈƄøèg¸Ķß ĉĈWb¹ƀdéĘNTtP[öSvrCZaGubo´ŖÒÇĐ~¡zCIözx¢PnÈñ @ĥÒ¦]ƞV}³ăĔñiiÄÓVépKG½ÄÓávYoC·sitiaÀyŧÎ¡ÈYDÑům}ý|m[węõĉZÅxUO}÷N¹³ĉo_qtăqwµŁYÙǝŕ¹tïÛUÃ¯mRCºĭ|µÕÊK½Rē ó]GªęAx»HO£|ām¡diď×YïYWªŉOeÚtĐ«zđ¹TāúEá²\\\\ķÍ}jYàÙÆſ¿Çdğ·ùTßÇţʄ¡XgWÀǇğ·¿ÃOj YÇ÷Qěi\"\n          ]\n        ],\n        \"encodeOffsets\": [\n          [[117381, 22988]],\n          [[116552, 22934]],\n          [[116790, 22617]],\n          [[116973, 22545]],\n          [[116444, 22536]],\n          [[116931, 22515]],\n          [[116496, 22490]],\n          [[116453, 22449]],\n          [[113301, 21439]],\n          [[118726, 21604]],\n          [[118709, 21486]],\n          [[113210, 20816]],\n          [[115482, 22082]],\n          [[113171, 21585]],\n          [[113199, 21590]],\n          [[115232, 22102]],\n          [[115739, 22373]],\n          [[115134, 22184]],\n          [[113056, 21175]],\n          [[119573, 21271]],\n          [[119957, 24020]],\n          [[115859, 22356]],\n          [[116561, 22649]],\n          [[116285, 22746]]\n        ]\n      }\n    },\n    {\n      \"type\": \"Feature\",\n      \"id\": \"450000\",\n      \"properties\": { \"id\": \"450000\", \"cp\": [108.320004, 22.82402], \"name\": \"广西\", \"childNum\": 2 },\n      \"geometry\": {\n        \"type\": \"MultiPolygon\",\n        \"coordinates\": [\n          [\"@@H TQ§A\"],\n          [\n            \"@@ĨÊªLƊDÎĹĐCǦė¸zÚGn£¾rªŀÜt¬@ÖÚSx~øOŒŶÐÂæȠ\\\\ÈÜObĖw^oÞLf¬°bI lTØBÌF£Ć¹gñĤaYt¿¤VSñK¸¤nM¼JE±½¸ñoÜCƆæĪ^ĚQÖ¦^f´QüÜÊz¯lzUĺš@ìp¶n]sxtx¶@~ÒĂJb©gk{°~c°`Ô¬rV\\\\la¼¤ôá`¯¹LCÆbxEræOv[H­[~|aB£ÖsºdAĐzNÂðsÞÆĤªbab`ho¡³F«èVlo¤ÔRzpp®SĪº¨ÖºNĳd`a¦¤F³ºDÎńĀìCĜº¦Ċ~nS|gźvZkCÆj°zVÈÁƔ]LÊFZgčP­kini«qÇczÍY®¬Ů»qR×ō©DÕ§ƙǃŵTÉĩ±ıdÑnYYĲvNĆĆØÜ Öp}e³¦m©iÓ|¹ħņ|ª¦QF¢Â¬ʖovg¿em^ucà÷gÕuíÙćĝ}FĻ¼Ĺ{µHKsLSđƃrč¤[AgoSŇYMÿ§Ç{FśbkylQxĕ]T·¶[BÑÏGáşşƇeăYSs­FQ}­BwtYğÃ@~CÍQ ×WjË±rÉ¥oÏ ±«ÓÂ¥kwWűmcih³K~µh¯e]lµélEģEďsmÇŧē`ãògK_ÛsUʝćğ¶höO¤Ǜn³c`¡y¦CezYwa[ďĵűMę§]XÎ_íÛ]éÛUćİÕBƣ±dy¹T^dûÅÑŦ·PĻþÙ`K¦¢ÍeĥR¿³£[~äu¼dltW¸oRM¢ď\\\\z}Æzdvň{ÎXF¶°Â_ÒÂÏL©ÖTmu¼ãlīkiqéfA·Êµ\\\\őDc¥ÝFyÔćcűH_hLÜêĺĐ¨c}rn`½Ì@¸¶ªVLhŒ\\\\Ţĺk~Ġið°|gtTĭĸ^xvKVGréAébUuMJVÃO¡qĂXËSģãlýà_juYÛÒBG^éÖ¶§EGÅzěƯ¤EkN[kdåucé¬dnYpAyČ{`]þ¯TbÜÈk¡ĠvàhÂƄ¢Jî¶²\"\n          ]\n        ],\n        \"encodeOffsets\": [[[111707, 21520]], [[107619, 25527]]]\n      }\n    },\n    {\n      \"type\": \"Feature\",\n      \"id\": \"460000\",\n      \"properties\": { \"id\": \"460000\", \"cp\": [109.83119, 19.031971], \"name\": \"海南\", \"childNum\": 1 },\n      \"geometry\": {\n        \"type\": \"Polygon\",\n        \"coordinates\": [\n          \"@@¦Ŝil¢XƦƞòïè§ŞCêɕrŧůÇąĻõ·ĉ³œ̅kÇm@ċȧŧĥĽʉ­ƅſȓÒË¦ŝE}ºƑ[ÍĜȋ gÎfǐÏĤ¨êƺ\\\\Ɔ¸ĠĎvʄȀÐ¾jNðĀÒRZǆzÐŘÎ°H¨Ƣb²_Ġ \"\n        ],\n        \"encodeOffsets\": [[112750, 20508]]\n      }\n    },\n    {\n      \"type\": \"Feature\",\n      \"id\": \"510000\",\n      \"properties\": {\n        \"id\": \"510000\",\n        \"cp\": [104.065735, 30.659462],\n        \"name\": \"四川\",\n        \"childNum\": 2\n      },\n      \"geometry\": {\n        \"type\": \"MultiPolygon\",\n        \"coordinates\": [\n          [\"@@LqKr\"],\n          [\n            \"@@[ĻéV£_ţġñpG réÏ·~ąSfy×Í·ºſƽiÍıƣıĻmHH}siaX@iÇ°ÁÃ×t«­T¤JJJyJÈ`Ohß¦¡uËhIyCjmÿwZGTiSsOB²fNmsPa{M{õE^Hj}gYpaeu¯oáwHjÁ½M¡pMuåmni{fk\\\\oÎqCwEZ¼KĝAy{m÷LwO×SimRI¯rKõBS«sFe]fµ¢óY_ÆPRcue°Cbo×bd£ŌIHgtrnyPt¦foaXďxlBowz_{ÊéWiêEGhÜ¸ºuFĈIxf®Y½ĀǙ]¤EyF²ċw¸¿@g¢§RGv»áW`ÃĵJwi]t¥wO­½a[×]`Ãi­üL¦LabbTÀåc}ÍhÆh®BHî|îºÉk­¤Sy£ia©taį·Ɖ`ō¥UhOĝLk}©Fos´JmµlŁuønÑJWÎªYÀïAetTŅÓGË«bo{ıwodƟ½OġÜÂµxàNÖ¾P²§HKv¾]|BÆåoZ`¡Ø`ÀmºĠ~ÌÐ§nÇ¿¤]wğ@srğu~Io[é±¹ ¿ſđÓ@qg¹zƱřaí°KtÇ¤V»Ã[ĩǭƑ^ÇÓ@áťsZÏÅĭƋěpwDóÖáŻneQËq·GCœýS]x·ýq³OÕ¶Qzßti{řáÍÇWŝŭñzÇWpç¿JXĩè½cFÂLiVjx}\\\\NŇĖ¥GeJA¼ÄHfÈu~¸Æ«dE³ÉMA|bÒćhG¬CMõƤąAvüVéŀ_VÌ³ĐwQj´·ZeÈÁ¨X´Æ¡Qu·»ÕZ³ġqDoy`L¬gdp°şp¦ėìÅĮZ°Iähzĵf²å ĚÑKpIN|Ñz]ń·FU×é»R³MÉ»GM«kiér}Ã`¹ăÞmÈnÁîRǀ³ĜoİzŔwǶVÚ£À]ɜ»ĆlƂ²ĠþTº·àUȞÏʦ¶I«dĽĢdĬ¿»Ĕ×h\\\\c¬ä²GêëĤł¥ÀǿżÃÆMº}BÕĢyFVvwxBèĻĒ©ĈtCĢɽŠȣ¦āæ·HĽîôNÔ~^¤Ɗu^s¼{TA¼ø°¢İªDè¾Ň¶ÝJ®Z´ğ~Sn|ªWÚ©òzPOȸbð¢|øĞŒQìÛÐ@ĞǎRS¤Á§di´ezÝúØã]HqkIþËQÇ¦ÃsÇ¤[E¬ÉŪÍxXƒ·ÖƁİlƞ¹ª¹|XÊwnÆƄmÀêErĒtD®ċæcQE®³^ĭ¥©l}äQtoŖÜqÆkµªÔĻĴ¡@Ċ°B²Èw^^RsºTĀ£ŚæQPJvÄz^Đ¹Æ¯fLà´GC²dt­ĀRt¼¤ĦOðğfÔðDŨŁĞƘïPÈ®âbMüÀXZ ¸£@Å»»QÉ­]dsÖ×_Í_ÌêŮPrĔĐÕGĂeZÜîĘqBhtO ¤tE[h|YÔZśÎs´xº±Uñt|OĩĠºNbgþJy^dÂY Į]Řz¦gC³R`Āz¢Aj¸CL¤RÆ»@­Ŏk\\\\Ç´£YW}z@Z}Ã¶oû¶]´^NÒ}èNªPÍy¹`S°´ATeVamdUĐwʄvĮÕ\\\\uÆŗ¨Yp¹àZÂmWh{á}WØǍÉüwga§áCNęÎ[ĀÕĪgÖÉªXøx¬½Ů¦¦[NÎLÜUÖ´òrÙŠxR^JkĳnDX{U~ET{ļº¦PZcjF²Ė@pg¨B{u¨ŦyhoÚD®¯¢ WòàFÎ¤¨GDäz¦kŮPġqË¥À]eâÚ´ªKxīPÖ|æ[xÃ¤JÞĥsNÖ½I¬nĨY´®ÐƐmDŝuäđđEbee_v¡}ìęǊē}qÉåT¯µRs¡M@}ůaa­¯wvƉåZw\\\\Z{åû^\"\n          ]\n        ],\n        \"encodeOffsets\": [[[108815, 30935]], [[110617, 31811]]]\n      }\n    },\n    {\n      \"type\": \"Feature\",\n      \"id\": \"520000\",\n      \"properties\": {\n        \"id\": \"520000\",\n        \"cp\": [106.713478, 26.578343],\n        \"name\": \"贵州\",\n        \"childNum\": 3\n      },\n      \"geometry\": {\n        \"type\": \"MultiPolygon\",\n        \"coordinates\": [\n          [\"@@G\\\\lY£in\"],\n          [\"@@q|mc¯tÏVSÎ\"],\n          [\n            \"@@hÑ£IsNgßHHªķÃh_¹¡ĝÄ§ń¦uÙùgS¯JH|sÝÅtÁïyMDč»eÕtA¤{b\\\\}G®u\\\\åPFqwÅaDK°ºâ_£ùbµmÁÛĹM[q|hlaªāI}Ñµ@swtwm^oµDéĽŠyVky°ÉûÛR³e¥]RÕěħ[ƅåÛDpJiVÂF²I»mN·£LbÒYbWsÀbpkiTZĄă¶Hq`ĥ_J¯ae«KpÝx]aĕÛPÇȟ[ÁåŵÏő÷Pw}TÙ@Õs«ĿÛq©½m¤ÙH·yǥĘĉBµĨÕnđ]K©œáGçş§ÕßgǗĦTèƤƺ{¶ÉHÎd¾ŚÊ·OÐjXWrãLyzÉAL¾ę¢bĶėy_qMĔąro¼hĊw¶øV¤w²Ĉ]ÊKx|`ź¦ÂÈdrcÈbe¸`I¼čTF´¼Óýȃr¹ÍJ©k_șl³´_pĐ`oÒh¶pa^ÓĔ}D»^Xy`d[KvJPhèhCrĂĚÂ^Êƌ wZL­Ġ£ÁbrzOIlMMĪŐžËr×ÎeŦtw|¢mKjSǘňĂStÎŦEtqFT¾Eì¬¬ôxÌO¢ K³ŀºäYPVgŎ¦ŊmŞ¼VZwVlz¤£Tl®ctĽÚó{G­AÇge~Îd¿æaSba¥KKûj®_Ä^\\\\Ø¾bP®¦x^sxjĶI_Ä Xâ¼Hu¨Qh¡À@Ëô}±GNìĎlT¸`V~R°tbÕĊ`¸úÛtÏFDu[MfqGH·¥yAztMFe|R_GkChZeÚ°tov`xbDnÐ{E}ZèxNEÞREn[Pv@{~rĆAB§EO¿|UZ~ìUf¨J²ĂÝÆsªB`s¶fvö¦Õ~dÔq¨¸º»uù[[§´sb¤¢zþF¢ÆÀhÂW\\\\ıËIÝo±ĭŠ£þÊs}¡R]ěDg´VG¢j±®èºÃmpU[Áëº°rÜbNu¸}º¼`niºÔXĄ¤¼ÔdaµÁ_ÃftQQgR·Ǔv}Ý×ĵ]µWc¤F²OĩųãW½¯K©]{LóµCIµ±Mß¿h©āq¬o½~@i~TUxŪÒ¢@£ÀEîôruńb[§nWuMÆLl¿]x}ĳ­½\"\n          ]\n        ],\n        \"encodeOffsets\": [[[112158, 27383]], [[112105, 27474]], [[112095, 27476]]]\n      }\n    },\n    {\n      \"type\": \"Feature\",\n      \"id\": \"530000\",\n      \"properties\": {\n        \"id\": \"530000\",\n        \"cp\": [101.512251, 24.740609],\n        \"name\": \"云南\",\n        \"childNum\": 1\n      },\n      \"geometry\": {\n        \"type\": \"Polygon\",\n        \"coordinates\": [\n          \"@@[ùx½}ÑRHYīĺûsÍniEoã½Ya²ė{c¬ĝgĂsAØÅwďõzFjw}«Dx¿}Uũlê@HÅ­F¨ÇoJ´Ónũuą¡Ã¢pÒÅØ TF²xa²ËXcÊlHîAßËŁkŻƑŷÉ©hW­æßUËs¡¦}teèÆ¶StÇÇ}Fd£jĈZĆÆ¤Tč\\\\D}O÷£U§~ŃGåŃDĝ¸Tsd¶¶Bª¤u¢ŌĎo~t¾ÍŶÒtD¦ÚiôözØX²ghįh½Û±¯ÿm·zR¦Ɵ`ªŊÃh¢rOÔ´£Ym¼èêf¯ŪĽncÚbw\\\\zlvWªâ ¦gmĿBĹ£¢ƹřbĥkǫßeeZkÙIKueT»sVesbaĕ  ¶®dNĄÄpªy¼³BE®lGŭCǶwêżĔÂepÍÀQƞpC¼ŲÈ­AÎô¶RäQ^Øu¬°_Èôc´¹ò¨PÎ¢hlĎ¦´ĦÆ´sâÇŲPnÊD^¯°Upv}®BPÌªjǬxSöwlfòªvqĸ|`H­viļndĜ­Ćhňem·FyÞqóSį¯³X_ĞçêtryvL¤§z¦c¦¥jnŞklD¤øz½ĜàĂŧMÅ|áƆàÊcðÂFÜáŢ¥\\\\\\\\ºİøÒÐJĴîD¦zK²ǏÎEh~CD­hMn^ÌöÄ©ČZÀaüfɭyœpį´ěFűk]Ôě¢qlÅĆÙa¶~ÄqêljN¬¼HÊNQ´ê¼VØ¸E^ŃÒyM{JLoÒęæe±Ķygã¯JYÆĭĘëo¥Šo¯hcK«z_prC´ĢÖY¼ v¸¢RÅW³Â§fÇ¸Yi³xR´ďUË`êĿUûuĆBƣöNDH«ĈgÑaB{ÊNF´¬c·Åv}eÇÃGB»If¦HňĕM~[iwjUÁKE¾dĪçWIèÀoÈXòyŞŮÈXâÎŚj|àsRyµÖPr´þ ¸^wþTDŔHr¸RÌmfżÕâCôoxĜƌÆĮÐYtâŦÔ@]ÈǮƒ\\\\Ī¼Ä£UsÈ¯LbîƲŚºyhr@ĒÔƀÀ²º\\\\êpJ}ĠvqtĠ@^xÀ£È¨mËÏğ}n¹_¿¢×Y_æpÅA^{½Lu¨GO±Õ½ßM¶wÁĢÛPƢ¼pcĲx|apÌ¬HÐŊSfsðBZ¿©XÏÒKk÷Eû¿SrEFsÕūkóVǥŉiTL¡n{uxţÏhôŝ¬ğōNNJkyPaqÂğ¤K®YxÉƋÁ]āęDqçgOgILu\\\\_gz]W¼~CÔē]bµogpÑ_oď`´³Țkl`IªºÎȄqÔþ»E³ĎSJ»_f·adÇqÇc¥Á_Źw{L^É±ćxU£µ÷xgĉp»ĆqNē`rĘzaĵĚ¡K½ÊBzyäKXqiWPÏÉ¸½řÍcÊG|µƕƣGË÷k°_^ý|_zċBZocmø¯hhcæ\\\\lMFlư£ĜÆyHF¨µêÕ]HAàÓ^it `þßäkĤÎT~Wlÿ¨ÔPzUCNVv [jâôDôď[}z¿msSh¯{jïğl}šĹ[őgK©U·µË@¾m_~q¡f¹ÅË^»f³ø}Q¡ÖË³gÍ±^Ç\\\\ëÃA_¿bWÏ[¶ƛé£F{īZgm@|kHǭƁć¦UĔť×ë}ǝeďºȡȘÏíBÉ£āĘPªĳ¶ŉÿy©nď£G¹¡I±LÉĺÑdĉÜW¥}gÁ{aqÃ¥aıęÏZï`\"\n        ],\n        \"encodeOffsets\": [[104636, 22969]]\n      }\n    },\n    {\n      \"type\": \"Feature\",\n      \"id\": \"540000\",\n      \"properties\": { \"id\": \"540000\", \"cp\": [89.132212, 30.860361], \"name\": \"西藏\", \"childNum\": 1 },\n      \"geometry\": {\n        \"type\": \"Polygon\",\n        \"coordinates\": [\n          \"@@ÂhľxŖxÒVºÅâAĪÝȆµę¯Ňa±r_w~uSÕňqOj]ɄQ£ZUDûoY»©M[L¼qãË{VÍçWVi]ë©Ä÷àyƛhÚU°adcQ~Mx¥cc¡ÙaSyFÖk­uRýq¿ÔµQĽ³aG{¿FµëªéĜÿª@¬·K·àariĕĀ«V»ŶĴūgèLǴŇƶaftèBŚ£^âǐÝ®M¦ÁǞÿ¬LhJ¾óƾÆºcxwf]Y´¦|QLn°adĊ\\\\¨oǀÍŎ´ĩĀd`tÊQŞŕ|¨C^©Ĉ¦¦ÎJĊ{ëĎjª²rÐl`¼Ą[t|¦Stè¾PÜK¸dƄı]s¤î_v¹ÎVòŦj£Əsc¬_Ğ´|Ł¦Av¦w`ăaÝaa­¢e¤ı²©ªSªÈMĄwÉØŔì@T¤Ę\\\\õª@þo´­xA sÂtŎKzó´ÇĊµ¢r^nĊ­Æ¬×üG¢³ {âĊ]G~bÀgVjzlhǶfOfdªB]pjTOtĊn¤}®¦Č¥d¢¼»ddY¼t¢eȤJ¤}Ǿ¡°§¤AÐlc@ĝsªćļđAçwxUuzEÖġ~AN¹ÄÅȀŻ¦¿ģŁéì±Hãd«g[Ø¼ēÀcīľġ¬cJµÐʥVȝ¸ßS¹ý±ğkƁ¼ą^ɛ¤Ûÿb[}¬ōõÃ]ËNm®g@Bg}ÍF±ǐyL¥íCIĳÏ÷Ñį[¹¦[âšEÛïÁÉdƅß{âNÆāŨß¾ě÷yC£k­´ÓH@Â¹TZ¥¢į·ÌAÐ§®Zcv½Z­¹|ÅWZqgW|ieZÅYVÓqdqbc²R@c¥Rã»GeeƃīQ}J[ÒK¬Ə|oėjġĠÑN¡ð¯EBčnwôɍėª²CλŹġǝʅįĭạ̃ūȹ]ΓͧgšsgȽóϧµǛęgſ¶ҍć`ĘąŌJÞä¤rÅň¥ÖÁUětęuůÞiĊÄÀ\\\\Æs¦ÓRb|Â^řÌkÄŷ¶½÷f±iMÝ@ĥ°G¬ÃM¥n£Øąğ¯ß§aëbéüÑOčk£{\\\\eµª×MÉfm«Ƒ{Å×Gŏǩãy³©WÑăû··Qòı}¯ãIéÕÂZ¨īès¶ZÈsæĔTŘvgÌsN@îá¾ó@ÙwU±ÉTå»£TđWxq¹Zobs[×¯cĩvėŧ³BM|¹kªħ¥TzNYnÝßpęrñĠĉRS~½ěVVµõ«M££µBĉ¥áºae~³AuĐh`Ü³ç@BÛïĿa©|z²Ý¼D£àč²ŸIûI āóK¥}rÝ_Á´éMaň¨~ªSĈ½½KÙóĿeƃÆB·¬ën×W|Uº}LJrƳlŒµ`bÔ`QÐÓ@s¬ñIÍ@ûws¡åQÑßÁ`ŋĴ{ĪTÚÅTSÄ³Yo|Ç[Ç¾µMW¢ĭiÕØ¿@MhpÕ]jéò¿OƇĆƇpêĉâlØwěsǩĵ¸cbU¹ř¨WavquSMzeo_^gsÏ·¥Ó@~¯¿RiīB\\\\qTGªÇĜçPoÿfñòą¦óQīÈáPābß{ZŗĸIæÅhnszÁCËìñÏ·ąĚÝUm®ó­L·ăUÈíoù´Êj°ŁŤ_uµ^°ìÇ@tĶĒ¡ÆM³Ģ«İĨÅ®ğRāðggheÆ¢zÊ©Ô\\\\°ÝĎz~ź¤PnMĪÖB£kné§żćĆKĒ°¼L¶èâz¨u¦¥LDĘz¬ýÎmĘd¾ßFzhg²Fy¦ĝ¤ċņbÎ@yĄæm°NĮZRÖíJ²öLĸÒ¨Y®ƌÐVàtt_ÚÂyĠz]ŢhzĎ{ÂĢXc|ÐqfO¢¤ögÌHNPKŖUú´xx[xvĐCûĀìÖT¬¸^}Ìsòd´_KgžLĴÀBon|H@Êx¦BpŰŌ¿fµƌA¾zǈRx¶FkĄźRzŀ~¶[´HnªVƞuĒ­È¨ƎcƽÌm¸ÁÈM¦x͊ëÀxǆBú^´W£dkɾĬpw˂ØɦļĬIŚÊnŔa¸~J°îlɌxĤÊÈðhÌ®gT´øàCÀ^ªerrƘd¢İP|Ė ŸWªĦ^¶´ÂLaT±üWƜǀRÂŶUńĖ[QhlLüAÜ\\\\qRĄ©\"\n        ],\n        \"encodeOffsets\": [[90849, 37210]]\n      }\n    },\n    {\n      \"type\": \"Feature\",\n      \"id\": \"610000\",\n      \"properties\": {\n        \"id\": \"610000\",\n        \"cp\": [108.948024, 34.263161],\n        \"name\": \"陕西\",\n        \"childNum\": 1\n      },\n      \"geometry\": {\n        \"type\": \"Polygon\",\n        \"coordinates\": [\n          \"@@p¢ȮµûGĦ}Ħðǚ¶òƄjɂz°{ºØkÈęâ¦jªBg\\\\ċ°s¬]jú EȌǆ¬stRÆdĠİwÜ¸ôW¾ƮłÒ_{Ìû¼jº¹¢GǪÒ¯ĘZ`ºŊecņą~BÂgzpâēòYǠȰÌTÎ¨ÂW|fcă§uF@N¢XLRMº[ğȣſï|¥Jkc`sŉǷY¹W@µ÷Kãï³ÛIcñ·VȋÚÒķø©þ¥yÓğęmWµÎumZyOŅƟĥÓ~sÑL¤µaÅY¦ocyZ{y c]{Ta©`U_Ěē£ωÊƍKùK¶ȱÝƷ§{û»ÅÁȹÍéuĳ|¹cÑdìUYOuFÕÈYvÁCqÓTǢí§·S¹NgV¬ë÷Át°DØ¯C´ŉƒópģ}ċcEËFéGU¥×K§­¶³BČ}C¿åċ`wġB·¤őcƭ²ő[Å^axwQOÿEËßŚĤNĔwƇÄńwĪ­o[_KÓª³ÙnKÇěÿ]ďă_d©·©Ýŏ°Ù®g]±ßå¬÷m\\\\iaǑkěX{¢|ZKlçhLtŇîŵœè[É@ƉĄEtƇÏ³­ħZ«mJ×¾MtÝĦ£IwÄå\\\\Õ{OwĬ©LÙ³ÙgBƕŀrÌĢŭO¥lãyC§HÍ£ßEñX¡­°ÙCgpťzb`wIvA|§hoĕ@E±iYd¥OĻ¹S|}F@¾oAO²{tfÜ¢FǂÒW²°BĤh^Wx{@¬­F¸¡ķn£P|ªĴ@^ĠĈæbÔc¶lYi^MicĎ°Â[ävï¶gv@ÀĬ·lJ¸sn|¼u~a]ÆÈtŌºJpþ£KKf~¦UbyäIĺãnÔ¿^­ŵMThĠÜ¤ko¼Ŏìąǜh`[tRd²Ĳ_XPrɲlXiL§à¹H°Ȧqº®QCbAŌJ¸ĕÚ³ĺ§ `d¨YjiZvRĺ±öVKkjGȊÄePĞZmļKÀ[`ösìhïÎoĬdtKÞ{¬èÒÒBÔpĲÇĬJŊ¦±J«Y§@·pHµàåVKepWftsAÅqC·¬ko«pHÆuK@oHĆÛķhxenS³àǍrqƶRbzy¸ËÐl¼EºpĤ¼x¼½~Ğà@ÚüdK^mÌSj\"\n        ],\n        \"encodeOffsets\": [[110234, 38774]]\n      }\n    },\n    {\n      \"type\": \"Feature\",\n      \"id\": \"620000\",\n      \"properties\": {\n        \"id\": \"620000\",\n        \"cp\": [103.823557, 36.058039],\n        \"name\": \"甘肃\",\n        \"childNum\": 2\n      },\n      \"geometry\": {\n        \"type\": \"MultiPolygon\",\n        \"coordinates\": [\n          [\"@@VuUv\"],\n          [\n            \"@@ũEĠtt~nkh`Q¦ÅÄÜdwAb×ĠąJ¤DüègĺqBqj°lI¡ĨÒ¤úSHbjÎB°aZ¢KJO[|A£Dx}NĂ¬HUnrk kp¼Y kMJn[aGáÚÏ[½rc}aQxOgsPMnUsncZsKúvAtÞġ£®ĀYKdnFw¢JE°Latf`¼h¬we|Æbj}GA·~W`¢MC¤tL©Ĳ°qdfObÞĬ¹ttu`^ZúE`[@Æsîz®¡CƳƜG²R¢RmfwĸgÜą G@pzJM½mhVy¸uÈÔO±¨{LfæU¶ßGĂq\\\\ª¬²I¥IŉÈīoıÓÑAçÑ|«LÝcspīðÍgtë_õ\\\\ĉñLYnĝgRǡÁiHLlõUĹ²uQjYi§Z_c¨´ĹĖÙ·ŋIaBD­R¹ȥr¯GºßK¨jWkɱOqWĳ\\\\a­Q\\\\sg_ĆǛōëp»£lğÛgSŶN®À]ÓämĹãJaz¥V}Le¤Lýo¹IsŋÅÇ^bz³tmEÁ´a¹cčecÇNĊãÁ\\\\č¯dNj]jZµkÓdaćå]ğĳ@ ©O{¤ĸm¢E·®«|@Xwg]Aģ±¯XǁÑǳªcwQÚŝñsÕ³ÛV_ý¥\\\\ů¥©¾÷w©WÕÊĩhÿÖÁRo¸V¬âDb¨hûxÊ×ǌ~Zâg|XÁnßYoº§ZÅŘv[ĭÖʃuďxcVbnUSfB¯³_TzºÎO©çMÑ~M³]µ^püµÄY~y@X~¤Z³[Èōl@®Å¼£QK·Di¡ByÿQ_´D¥hŗy^ĭÁZ]cIzýah¹MĪğPs{ò²Vw¹t³ŜË[Ñ}X\\\\gsF£sPAgěp×ëfYHāďÖqēŭOÏëdLü\\\\it^c®RÊº¶¢H°mrY£B¹čIoľu¶uI]vģSQ{UŻÅ}QÂ|Ì°ƅ¤ĩŪU ęĄÌZÒ\\\\v²PĔ»ƢNHĂyAmƂwVm`]ÈbH`Ì¢²ILvĜH®¤Dlt_¢JJÄämèÔDëþgºƫaʎÌrêYi~ Îİ¤NpÀA¾Ĕ¼bð÷®üszMzÖĖQdȨýv§Tè|ªHÃ¾a¸|Ð ƒwKĢx¦ivr^ÿ ¸l öæfƟĴ·PJv}n\\\\h¹¶v·À|\\\\ƁĚN´ĜçèÁz]ġ¤²¨QÒŨTIlªťØ}¼˗ƦvÄùØEÂ«FïËIqōTvāÜŏíÛßÛVj³âwGăÂíNOPìyV³ŉĖýZso§HÑiYw[ß\\\\X¦¥c]ÔƩÜ·«jÐqvÁ¦m^ċ±R¦΋ƈťĚgÀ»IïĨʗƮ°ƝĻþÍAƉſ±tÍEÕÞāNUÍ¡\\\\ſčåÒʻĘm ƭÌŹöʥëQ¤µ­ÇcƕªoIýIÉ_mkl³ăƓ¦j¡YzŇi}Msßõīʋ }ÁVm_[n}eı­Uĥ¼ªI{Î§DÓƻėojqYhĹT©oūĶ£]ďxĩǑMĝq`B´ƃ˺Чç~²ņj@¥@đ´ί}ĥtPńÇ¾V¬ufÓÉCtÓ̻¹£G³]ƖƾŎĪŪĘ̖¨ʈĢƂlɘ۪üºňUðǜȢƢż̌ȦǼĤŊɲĖÂ­Kq´ï¦ºĒǲņɾªǀÞĈĂD½ĄĎÌŗĞrôñnN¼â¾ʄľԆ|Ǆ֦ज़ȗǉ̘̭ɺƅêgV̍ʆĠ·ÌĊv|ýĖÕWĊǎÞ´õ¼cÒÒBĢ͢UĜð͒s¨ňƃLĉÕÝ@ɛƯ÷¿Ľ­ĹeȏĳëCȚDŲyê×Ŗyò¯ļcÂßYtÁƤyAã˾J@ǝrý@¤rz¸oP¹ɐÚyáHĀ[JwcVeȴÏ»ÈĖ}ƒŰŐèȭǢόĀƪÈŶë;Ñ̆ȤМľĮEŔĹŊũ~ËUă{ĻƹɁύȩþĽvĽƓÉ@ēĽɲßǐƫʾǗĒpäWÐxnsÀ^ƆwW©¦cÅ¡Ji§vúF¶¨c~c¼īeXǚ\\\\đ¾JwÀďksãAfÕ¦L}waoZD½Ml«]eÒÅaÉ²áo½FõÛ]ĻÒ¡wYR£¢rvÓ®y®LFLzĈôe]gx}|KK}xklL]c¦£fRtív¦PĤoH{tK\"\n          ]\n        ],\n        \"encodeOffsets\": [[[108619, 36299]], [[108589, 36341]]]\n      }\n    },\n    {\n      \"type\": \"Feature\",\n      \"id\": \"630000\",\n      \"properties\": { \"id\": \"630000\", \"cp\": [96.778916, 35.623178], \"name\": \"青海\", \"childNum\": 2 },\n      \"geometry\": {\n        \"type\": \"MultiPolygon\",\n        \"coordinates\": [\n          [\"@@InJm\"],\n          [\n            \"@@CÆ½OŃĦsΰ~Ē³¦@@Ņi±è}ШƄ˹A³r_ĞǒNĪĐw¤^ŬĵªpĺSZgrpiƼĘÔ¨C|ÍJ©Ħ»®VĲ~f\\\\m `UnÂ~ʌĬàöNt~ňjy¢ZiƔ¥Ąk´nl`JÊJþ©pdƖ®È£¶ìRʦźõƮËnʼėæÑƀĎ[¢VÎĂMÖÝÎF²sƊƀÎBļýƞ¯ʘƭðħ¼Jh¿ŦęΌƇ¥²Q]Č¥nuÂÏri¸¬ƪÛ^Ó¦d¥[Wàx\\\\ZjÒ¨GtpþYŊĕ´zUOëPîMĄÁxH´áiÜUàîÜŐĂÛSuŎrJðÌ¬EFÁú×uÃÎkrĒ{V}İ«O_ÌËĬ©ÓŧSRÑ±§Ģ£^ÂyèçěM³Ƃę{[¸¿uºµ[gt£¸OƤĿéYõ·kĀq]juw¥DĩƍõÇPéÄ½G©ã¤GuȧþRcÕĕNyyût­øï»a½ē¿BMoį£Íj}éZËqbʍƬh¹ìÿÓAçãnIÃ¡I`ks£CG­ěUy×Cy@¶ʡÊBnāzGơMē¼±O÷õJËĚăVĪũƆ£¯{ËL½ÌzżVR|ĠTbuvJvµhĻĖHAëáa­OÇðñęNwœľ·LmI±íĠĩPÉ×®ÿscB³±JKßĊ«`ađ»·QAmOVţéÿ¤¹SQt]]Çx±¯A@ĉĳ¢Óļ©l¶ÅÛrŕspãRk~¦ª]Į­´FRåd­ČsCqđéFn¿ÅƃmÉx{W©ºƝºįkÕƂƑ¸wWūÐ©ÈF£\\\\tÈ¥ÄRÈýÌJ lGr^×äùyÞ³fjc¨£ÂZ|ǓMĝÏ@ëÜőRĝ÷¡{aïȷPu°ËXÙ{©TmĠ}Y³­ÞIňµç½©C¡į÷¯B»|St»]vųs»}MÓ ÿʪƟǭA¡fs»PY¼c¡»¦cċ­¥£~msĉPSi^o©AecPeǵkgyUi¿h}aHĉ^|á´¡HØûÅ«ĉ®]m¡qĉ¶³ÈyôōLÁstB®wn±ă¥HSòė£Së@×œÊăxÇN©©T±ª£Ĳ¡fb®Þbb_Ą¥xu¥B{łĝ³«`dƐt¤ťiñÍUuºí`£^tƃĲc·ÛLO½sç¥Ts{ă\\\\_»kÏ±q©čiìĉ|ÍI¥ć¥]ª§D{ŝŖÉR_sÿc³ĪōƿÎ§p[ĉc¯bKmR¥{³Ze^wx¹dƽÅ½ôIg §Mĕ ƹĴ¿ǣÜÍ]Ý]snåA{eƭ`ǻŊĿ\\\\ĳŬűYÂÿ¬jĖqßb¸L«¸©@ěĀ©ê¶ìÀEH|´bRľÓ¶rÀQþvl®ÕETzÜdb hw¤{LRdcb¯ÙVgƜßzÃôì®^jUèXÎ|UäÌ»rK\\\\ªN¼pZCüVY¤ɃRi^rPŇTÖ}|br°qňbĚ°ªiƶGQ¾²x¦PmlŜ[Ĥ¡ΞsĦÔÏâ\\\\ªÚŒU\\\\f¢N²§x|¤§xĔsZPòʛ²SÐqF`ªVÞŜĶƨVZÌL`¢dŐIqr\\\\oäõFÎ·¤»Ŷ×h¹]ClÙ\\\\¦ďÌį¬řtTӺƙgQÇÓHţĒ´ÃbEÄlbʔC|CŮkƮ[ʼ¬ň´KŮÈΰÌĪ¶ƶlðļATUvdTGº̼ÔsÊDÔveOg\"\n          ]\n        ],\n        \"encodeOffsets\": [[[105308, 37219]], [[95370, 40081]]]\n      }\n    },\n    {\n      \"type\": \"Feature\",\n      \"id\": \"640000\",\n      \"properties\": { \"id\": \"640000\", \"cp\": [106.278179, 37.26637], \"name\": \"宁夏\", \"childNum\": 2 },\n      \"geometry\": {\n        \"type\": \"MultiPolygon\",\n        \"coordinates\": [\n          [\n            \"@@KëÀęĞ«Oęȿȕı]ŉ¡åįÕÔ«ǴõƪĚQÐZhv K°öqÀÑS[ÃÖHƖčËnL]ûcÙß@ĝ¾}w»»oģF¹»kÌÏ·{zP§B­¢íyÅt@@á]Yv_ssģ¼ißĻL¾ġsKD£¡N_X¸}B~HaiÅf{«x»ge_bsKF¯¡IxmELcÿZ¤­ĢÝsuBLùtYdmVtNmtOPhRw~bd¾qÐ\\\\âÙH\\\\bImlNZ»loqlVmGā§~QCw¤{A\\\\PKNY¯bFkC¥sks_Ã\\\\ă«¢ħkJi¯rrAhĹûç£CUĕĊ_ÔBixÅÙĄnªÑaM~ħpOu¥sîeQ¥¤^dkKwlL~{L~hw^ófćKyE­K­zuÔ¡qQ¤xZÑ¢^ļöÜ¾Ep±âbÊÑÆ^fk¬NC¾YpxbK~¥eÖäBlt¿Đx½I[ĒǙWf»Ĭ}d§dµùEuj¨IÆ¢¥dXªƅx¿]mtÏwßRĶX¢͎vÆzƂZò®ǢÌʆCrâºMÞzÆMÒÊÓŊZÄ¾r°Î®Ȉmª²ĈUªĚîøºĮ¦ÌĘk^FłĬhĚiĀĖ¾iİbjÕ\"\n          ],\n          [\"@@mfwěwMrŢªv@G\"]\n        ],\n        \"encodeOffsets\": [[[109366, 40242]], [[108600, 36303]]]\n      }\n    },\n    {\n      \"type\": \"Feature\",\n      \"id\": \"650000\",\n      \"properties\": { \"id\": \"650000\", \"cp\": [85.617733, 40.792818], \"name\": \"新疆\", \"childNum\": 1 },\n      \"geometry\": {\n        \"type\": \"Polygon\",\n        \"coordinates\": [\n          \"@@QØĔ²X¨~ǘBºjʐßØvKƔX¨vĊOÃ·¢i@~cĝe_«E}QxgɪëÏÃ@sÅyXoŖ{ô«ŸuXêÎf`C¹ÂÿÐGĮÕĞXŪōŸMźÈƺQèĽôe|¿ƸJR¤ĘEjcUóº¯Ĩ_ŘÁMª÷Ð¥OéÈ¿ÖğǤǷÂFÒzÉx[]­Ĥĝœ¦EP}ûƥé¿İƷTėƫœŕƅƱB»Đ±ēO¦E}`cȺrĦáŖuÒª«ĲπdƺÏØZƴwʄ¤ĖGĐǂZĶèH¶}ÚZצʥĪï|ÇĦMŔ»İĝǈì¥Βba­¯¥ǕǚkĆŵĦɑĺƯxūД̵nơʃĽá½M»òmqóŘĝčË¾ăCćāƿÝɽ©ǱŅ¹đ¥³ðLrÁ®ɱĕģŉǻ̋ȥơŻǛȡVï¹Ň۩ûkɗġƁ§ʇė̕ĩũƽō^ƕUv£ƁQïƵkŏ½ΉÃŭÇ³LŇʻ«ƭ\\\\lŭD{ʓDkaFÃÄa³ŤđÔGRÈƚhSӹŚsİ«ĐË[¥ÚDkº^Øg¼ŵ¸£EÍöůŉT¡c_ËKYƧUśĵÝU_©rETÏʜ±OñtYwē¨{£¨uM³x½şL©Ùá[ÓÐĥ Νtģ¢\\\\śnkOw¥±T»ƷFɯàĩÞáB¹ÆÑUwŕĽw[mG½Èå~Æ÷QyěCFmĭZīŵVÁƿQƛûXS²b½KÏ½ĉS©ŷXĕ{ĕK·¥Ɨcqq©f¿]ßDõU³h­gËÇïģÉɋwk¯í}I·œbmÉřīJɥĻˁ×xoɹīlc¤³Xù]ǅA¿w͉ì¥wÇN·ÂËnƾƍdÇ§đ®ƝvUm©³G\\\\}µĿQyŹlăµEwǇQ½yƋBe¶ŋÀůo¥AÉw@{Gpm¿AĳŽKLh³`ñcËtW±»ÕSëüÿďDu\\\\wwwù³VLŕOMËGh£õP¡erÏd{ġWÁč|yšg^ğyÁzÙs`s|ÉåªÇ}m¢Ń¨`x¥ù^}Ì¥H«YªƅAÐ¹n~ź¯f¤áÀzgÇDIÔ´AňĀÒ¶ûEYospõD[{ù°]uJqU|Soċxţ[õÔĥkŋÞŭZËºóYËüċrw ÞkrťË¿XGÉbřaDü·Ē÷AÃª[ÄäIÂ®BÕĐÞ_¢āĠpÛÄȉĖġDKwbmÄNôfƫVÉviǳHQµâFù­Âœ³¦{YGd¢ĚÜO {Ö¦ÞÍÀP^bƾl[vt×ĈÍEË¨¡Đ~´î¸ùÎhuè`¸HÕŔVºwĠââWò@{ÙNÝ´ə²ȕn{¿¥{l÷eé^eďXj©î\\\\ªÑòÜìc\\\\üqÕ[Č¡xoÂċªbØ­ø|¶ȴZdÆÂońéG\\\\¼C°ÌÆn´nxÊOĨŪƴĸ¢¸òTxÊǪMīĞÖŲÃɎOvʦƢ~FRěò¿ġ~åŊúN¸qĘ[Ĕ¶ÂćnÒPĒÜvúĀÊbÖ{Äî¸~Ŕünp¤ÂH¾ĄYÒ©ÊfºmÔĘcDoĬMŬS¤s²ʘÚžȂVŦ èW°ªB|ĲXŔþÈJĦÆæFĚêYĂªĂ]øªŖNÞüAfɨJ¯ÎrDDĤ`mz\\\\§~D¬{vJÂ«lµĂb¤pŌŰNĄ¨ĊXW|ų ¿¾ɄĦƐMTòP÷fØĶK¢ȝ˔Sô¹òEð­`Ɩ½ǒÂň×äı§ĤƝ§C~¡hlåǺŦŞkâ~}FøàĲaĞfƠ¥Ŕd®U¸źXv¢aƆúŪtŠųƠjdƺƺÅìnrh\\\\ĺ¯äɝĦ]èpĄ¦´LƞĬ´ƤǬ˼Ēɸ¤rºǼ²¨zÌPðŀbþ¹ļD¢¹\\\\ĜÑŚ¶ZƄ³àjĨoâȴLÊȮĐ­ĚăÀêZǚŐ¤qȂ\\\\L¢ŌİfÆs|zºeªÙæ§΢{Ā´ƐÚ¬¨Ĵà²łhʺKÞºÖTiƢ¾ªì°`öøu®Ê¾ãØ\"\n        ],\n        \"encodeOffsets\": [[88824, 50096]]\n      }\n    },\n    {\n      \"type\": \"Feature\",\n      \"id\": \"110000\",\n      \"properties\": {\n        \"id\": \"110000\",\n        \"cp\": [116.405285, 39.904989],\n        \"name\": \"北京\",\n        \"childNum\": 1\n      },\n      \"geometry\": {\n        \"type\": \"Polygon\",\n        \"coordinates\": [\n          \"@@ĽOÁûtŷmiÍt_H»Ĩ±d`¹­{bwYr³S]§§o¹qGtm_SŧoaFLgQN_dV@Zom_ć\\\\ßcÂ±x¯oœRcfe£o§ËgToÛJíĔóu|wP¤XnO¢ÉŦ¯rNÄā¤zâŖÈRpŢZÚ{GrFt¦Òx§ø¹RóäV¤XdżâºWbwŚ¨Ud®bêņ¾jnŎGŃŶnzÚSeîĜZczî¾i]ÍQaúÍÔiþĩȨWĢü|Ėu[qb[swP@ÅğP¿{\\\\¥A¨ÏÑ¨j¯X\\\\¯MKpA³[Hīu}}\"\n        ],\n        \"encodeOffsets\": [[120023, 41045]]\n      }\n    },\n    {\n      \"type\": \"Feature\",\n      \"id\": \"120000\",\n      \"properties\": {\n        \"id\": \"120000\",\n        \"cp\": [117.190182, 39.125596],\n        \"name\": \"天津\",\n        \"childNum\": 1\n      },\n      \"geometry\": {\n        \"type\": \"Polygon\",\n        \"coordinates\": [\n          \"@@ŬgX§Ü«E¶FÌ¬O_ïlÁgz±AXeµÄĵ{¶]gitgIj·¥îakS¨ÐƎk}ĕ{gBqGf{¿aU^fIư³õ{YıëNĿk©ïËZŏR§òoY×Ógcĥs¡bġ«@dekąI[nlPqCnp{ō³°`{PNdƗqSÄĻNNâyj]äÒD ĬH°Æ]~¡HO¾X}ÐxgpgWrDGpù^LrzWxZ^¨´T\\\\|~@IzbĤjeĊªz£®ĔvěLmV¾Ô_ÈNW~zbĬvG²ZmDM~~\"\n        ],\n        \"encodeOffsets\": [[120237, 41215]]\n      }\n    },\n    {\n      \"type\": \"Feature\",\n      \"id\": \"310000\",\n      \"properties\": {\n        \"id\": \"310000\",\n        \"cp\": [121.472644, 31.231706],\n        \"name\": \"上海\",\n        \"childNum\": 6\n      },\n      \"geometry\": {\n        \"type\": \"MultiPolygon\",\n        \"coordinates\": [\n          [\"@@ɧư¬EpƸÁxc\"],\n          [\"@@©ª\"],\n          [\"@@MA\"],\n          [\"@@QpİE§ÉC¾\"],\n          [\"@@bŝÕÕEȣÚƥêImɇǦèÜĠÚÃƌÃ͎ó\"],\n          [\"@@ǜûȬɋŭ×^sYɍDŋŽąñCG²«ªč@h_p¯A{oloY¬j@Ĳ`gQÚhr|ǀ^MĲvtbe´R¯Ô¬¨Yô¤r]ìƬį\"]\n        ],\n        \"encodeOffsets\": [\n          [[124702, 32062]],\n          [[124547, 32200]],\n          [[124808, 31991]],\n          [[124726, 32110]],\n          [[124903, 32376]],\n          [[124438, 32149]]\n        ]\n      }\n    },\n    {\n      \"type\": \"Feature\",\n      \"id\": \"500000\",\n      \"properties\": {\n        \"id\": \"500000\",\n        \"cp\": [107.304962, 29.533155],\n        \"name\": \"重庆\",\n        \"childNum\": 2\n      },\n      \"geometry\": {\n        \"type\": \"MultiPolygon\",\n        \"coordinates\": [\n          [\n            \"@@vjG~nGŘŬĶȂƀƾ¹¸ØÎezĆT¸}êÐqHðqĖä¥^CÆIj²p\\\\_ æüY|[YxƊæu°xb®Űb@~¢NQt°¶Sæ Ê~rǉĔëĚ¢~uf`faĔJåĊnÖ]jƎćÊ@£¾a®£Ű{ŶĕFègLk{Y|¡ĜWƔtƬJÑxq±ĢN´òKLÈÃ¼D|s`ŋć]Ã`đMûƱ½~Y°ħ`ƏíW½eI½{aOIrÏ¡ĕŇapµÜƅġ^ÖÛbÙŽŏml½SêqDu[RãË»ÿw`»y¸_ĺę}÷`M¯ċfCVµqŉ÷Zgg`d½pDOÎCn^uf²ènh¼WtƏxRGg¦pVFI±G^Ic´ecGĹÞ½sëĬhxW}KÓe­XsbkF¦LØgTkïƵNï¶}Gyw\\\\oñ¡nmĈzj@Óc£»Wă¹Ój_m»¹·~MvÛaq»­ê\\\\ÂoVnÓØÍ²«bq¿efE Ĝ^Q~ Évýş¤²ĮpEİ}zcĺL½¿gÅ¡ýE¡ya£³t\\\\¨\\\\vú»¼§·Ñr_oÒý¥u_n»_At©ÞÅ±ā§IVeëY}{VPÀFA¨ąB}q@|Ou\\\\FmQFÝMwå}]|FmÏCawu_p¯sfÙgYDHl`{QEfNysB¦zG¸rHeN\\\\CvEsÐùÜ_·ÖĉsaQ¯}_UxÃđqNH¬Äd^ÝŰR¬ã°wećJE·vÝ·HgéFXjÉê`|ypxkAwWĐpb¥eOsmzwqChóUQl¥F^lafanòsrEvfQdÁUVfÎvÜ^eftET¬ôA\\\\¢sJnQTjPØxøK|nBzĞ»LYFDxÓvr[ehľvN¢o¾NiÂxGpâ¬zbfZo~hGi]öF||NbtOMn eA±tPTLjpYQ|SHYĀxinzDJÌg¢và¥Pg_ÇzIIII£®S¬ØsÎ¼£N\"\n          ],\n          [\"@@ifjN@s\"]\n        ],\n        \"encodeOffsets\": [[[109628, 30765]], [[111725, 31320]]]\n      }\n    },\n    {\n      \"type\": \"Feature\",\n      \"id\": \"810000\",\n      \"properties\": {\n        \"id\": \"810000\",\n        \"cp\": [114.173355, 22.320048],\n        \"name\": \"香港\",\n        \"childNum\": 5\n      },\n      \"geometry\": {\n        \"type\": \"MultiPolygon\",\n        \"coordinates\": [\n          [\"@@AlBk\"],\n          [\"@@mn\"],\n          [\"@@EpFo\"],\n          [\"@@ea¢pl¸Eõ¹hj[]ÔCÎ@lj¡uBX´AI¹[yDU]W`çwZkmcMpÅv}IoJlcafŃK°ä¬XJmÐ đhI®æÔtSHnEÒrÈc\"],\n          [\"@@rMUwAS®e\"]\n        ],\n        \"encodeOffsets\": [\n          [[117111, 23002]],\n          [[117072, 22876]],\n          [[117045, 22887]],\n          [[116975, 23082]],\n          [[116882, 22747]]\n        ]\n      }\n    },\n    {\n      \"type\": \"Feature\",\n      \"id\": \"820000\",\n      \"properties\": { \"id\": \"820000\", \"cp\": [113.54909, 22.198951], \"name\": \"澳门\", \"childNum\": 1 },\n      \"geometry\": {\n        \"type\": \"Polygon\",\n        \"coordinates\": [\"@@kÊd°å§s\"],\n        \"encodeOffsets\": [[116279, 22639]]\n      }\n    }\n  ],\n  \"UTF8Encoding\": true\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/AppLinkInput/AppLinkSelectDialog.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" title=\"选择链接\" width=\"65%\">\n    <div class=\"h-500px flex gap-8px\">\n      <!-- 左侧分组列表 -->\n      <el-scrollbar wrap-class=\"h-full\" ref=\"groupScrollbar\" view-class=\"flex flex-col\">\n        <el-button\n          v-for=\"(group, groupIndex) in APP_LINK_GROUP_LIST\"\n          :key=\"groupIndex\"\n          :class=\"[\n            'm-r-16px m-l-0px! justify-start! w-90px',\n            { active: activeGroup === group.name }\n          ]\"\n          ref=\"groupBtnRefs\"\n          :text=\"activeGroup !== group.name\"\n          :type=\"activeGroup === group.name ? 'primary' : 'default'\"\n          @click=\"handleGroupSelected(group.name)\"\n        >\n          {{ group.name }}\n        </el-button>\n      </el-scrollbar>\n      <!-- 右侧链接列表 -->\n      <el-scrollbar class=\"h-full flex-1\" @scroll=\"handleScroll\" ref=\"linkScrollbar\">\n        <div v-for=\"(group, groupIndex) in APP_LINK_GROUP_LIST\" :key=\"groupIndex\">\n          <!-- 分组标题 -->\n          <div class=\"font-bold\" ref=\"groupTitleRefs\">{{ group.name }}</div>\n          <!-- 链接列表 -->\n          <el-tooltip\n            v-for=\"(appLink, appLinkIndex) in group.links\"\n            :key=\"appLinkIndex\"\n            :content=\"appLink.path\"\n            placement=\"bottom\"\n            :show-after=\"300\"\n          >\n            <el-button\n              class=\"m-b-8px m-r-8px m-l-0px!\"\n              :type=\"isSameLink(appLink.path, activeAppLink.path) ? 'primary' : 'default'\"\n              @click=\"handleAppLinkSelected(appLink)\"\n            >\n              {{ appLink.name }}\n            </el-button>\n          </el-tooltip>\n        </div>\n      </el-scrollbar>\n    </div>\n    <!-- 底部对话框操作按钮 -->\n    <template #footer>\n      <el-button type=\"primary\" @click=\"handleSubmit\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n  <Dialog v-model=\"detailSelectDialog.visible\" title=\"\" width=\"50%\">\n    <el-form class=\"min-h-200px\">\n      <el-form-item\n        label=\"选择分类\"\n        v-if=\"detailSelectDialog.type === APP_LINK_TYPE_ENUM.PRODUCT_CATEGORY_LIST\"\n      >\n        <ProductCategorySelect\n          v-model=\"detailSelectDialog.id\"\n          :parent-id=\"0\"\n          @update:model-value=\"handleProductCategorySelected\"\n        />\n      </el-form-item>\n    </el-form>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport { APP_LINK_GROUP_LIST, APP_LINK_TYPE_ENUM, AppLink } from './data'\nimport { ButtonInstance, ScrollbarInstance } from 'element-plus'\nimport { split } from 'lodash-es'\nimport ProductCategorySelect from '@/views/mall/product/category/components/ProductCategorySelect.vue'\nimport { getUrlNumberValue } from '@/utils'\n\n// APP 链接选择弹框\ndefineOptions({ name: 'AppLinkSelectDialog' })\n// 选中的分组，默认选中第一个\nconst activeGroup = ref(APP_LINK_GROUP_LIST[0].name)\n// 选中的 APP 链接\nconst activeAppLink = ref({} as AppLink)\n\n/** 打开弹窗 */\nconst dialogVisible = ref(false)\nconst open = (link: string) => {\n  activeAppLink.value.path = link\n  dialogVisible.value = true\n\n  // 滚动到当前的链接\n  const group = APP_LINK_GROUP_LIST.find((group) =>\n    group.links.some((linkItem) => {\n      const sameLink = isSameLink(linkItem.path, link)\n      if (sameLink) {\n        activeAppLink.value = { ...linkItem, path: link }\n      }\n      return sameLink\n    })\n  )\n  if (group) {\n    // 使用 nextTick 的原因：可能 Dom 还没生成，导致滚动失败\n    nextTick(() => handleGroupSelected(group.name))\n  }\n}\ndefineExpose({ open })\n\n// 处理 APP 链接选中\nconst handleAppLinkSelected = (appLink: AppLink) => {\n  if (!isSameLink(appLink.path, activeAppLink.value.path)) {\n    activeAppLink.value = appLink\n  }\n  switch (appLink.type) {\n    case APP_LINK_TYPE_ENUM.PRODUCT_CATEGORY_LIST:\n      detailSelectDialog.value.visible = true\n      detailSelectDialog.value.type = appLink.type\n      // 返显\n      detailSelectDialog.value.id =\n        getUrlNumberValue('id', 'http://127.0.0.1' + activeAppLink.value.path) || undefined\n      break\n    default:\n      break\n  }\n}\n\n// 处理绑定值更新\nconst emit = defineEmits<{\n  change: [link: string]\n  appLinkChange: [appLink: AppLink]\n}>()\nconst handleSubmit = () => {\n  dialogVisible.value = false\n  emit('change', activeAppLink.value.path)\n  emit('appLinkChange', activeAppLink.value)\n}\n\n// 分组标题引用列表\nconst groupTitleRefs = ref<HTMLInputElement[]>([])\n/**\n * 处理右侧链接列表滚动\n * @param scrollTop 滚动条的位置\n */\nconst handleScroll = ({ scrollTop }: { scrollTop: number }) => {\n  const titleEl = groupTitleRefs.value.find((titleEl: HTMLInputElement) => {\n    // 获取标题的位置信息\n    const { offsetHeight, offsetTop } = titleEl\n    // 判断标题是否在可视范围内\n    return scrollTop >= offsetTop && scrollTop < offsetTop + offsetHeight\n  })\n  // 只需处理一次\n  if (titleEl && activeGroup.value !== titleEl.textContent) {\n    activeGroup.value = titleEl.textContent || ''\n    // 同步左侧的滚动条位置\n    scrollToGroupBtn(activeGroup.value)\n  }\n}\n\n// 右侧滚动条\nconst linkScrollbar = ref<ScrollbarInstance>()\n// 处理分组选中\nconst handleGroupSelected = (group: string) => {\n  activeGroup.value = group\n  const titleRef = groupTitleRefs.value.find((item: HTMLInputElement) => item.textContent === group)\n  if (titleRef) {\n    // 滚动分组标题\n    linkScrollbar.value?.setScrollTop(titleRef.offsetTop)\n  }\n}\n\n// 分组滚动条\nconst groupScrollbar = ref<ScrollbarInstance>()\n// 分组引用列表\nconst groupBtnRefs = ref<ButtonInstance[]>([])\n// 自动滚动分组按钮，确保分组按钮保持在可视区域内\nconst scrollToGroupBtn = (group: string) => {\n  const groupBtn = groupBtnRefs.value\n    .map((btn: ButtonInstance) => btn['ref'])\n    .find((ref: Node) => ref.textContent === group)\n  if (groupBtn) {\n    groupScrollbar.value?.setScrollTop(groupBtn.offsetTop)\n  }\n}\n\n// 是否为相同的链接（不比较参数，只比较链接）\nconst isSameLink = (link1: string, link2: string) => {\n  return split(link1, '?', 1)[0] === split(link2, '?', 1)[0]\n}\n\n// 详情选择对话框\nconst detailSelectDialog = ref<{\n  visible: boolean\n  id?: number\n  type?: APP_LINK_TYPE_ENUM\n}>({\n  visible: false,\n  id: undefined,\n  type: undefined\n})\n// 处理详情选择\nconst handleProductCategorySelected = (id: number) => {\n  const url = new URL(activeAppLink.value.path, 'http://127.0.0.1')\n  // 修改 id 参数\n  url.searchParams.set('id', `${id}`)\n  // 排除域名\n  activeAppLink.value.path = `${url.pathname}${url.search}`\n  // 关闭对话框\n  detailSelectDialog.value.visible = false\n  // 重置 id\n  detailSelectDialog.value.id = undefined\n}\n</script>\n<style lang=\"scss\" scoped></style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/AppLinkInput/data.ts",
    "content": "// APP 链接分组\nexport interface AppLinkGroup {\n  // 分组名称\n  name: string\n  // 链接列表\n  links: AppLink[]\n}\n// APP 链接\nexport interface AppLink {\n  // 链接名称\n  name: string\n  // 链接地址\n  path: string\n  // 链接的类型\n  type?: APP_LINK_TYPE_ENUM\n}\n\n// APP 链接类型（需要特殊处理，例如商品详情）\nexport const enum APP_LINK_TYPE_ENUM {\n  // 拼团活动\n  ACTIVITY_COMBINATION,\n  // 秒杀活动\n  ACTIVITY_SECKILL,\n  // 文章详情\n  ARTICLE_DETAIL,\n  // 优惠券详情\n  COUPON_DETAIL,\n  // 自定义页面详情\n  DIY_PAGE_DETAIL,\n  // 品类列表\n  PRODUCT_CATEGORY_LIST,\n  // 商品列表\n  PRODUCT_LIST,\n  // 商品详情\n  PRODUCT_DETAIL_NORMAL,\n  // 拼团商品详情\n  PRODUCT_DETAIL_COMBINATION,\n  // 秒杀商品详情\n  PRODUCT_DETAIL_SECKILL\n}\n\n// APP 链接列表（做一下持久化？）\nexport const APP_LINK_GROUP_LIST = [\n  {\n    name: '商城',\n    links: [\n      {\n        name: '首页',\n        path: '/pages/index/index'\n      },\n      {\n        name: '商品分类',\n        path: '/pages/index/category',\n        type: APP_LINK_TYPE_ENUM.PRODUCT_CATEGORY_LIST\n      },\n      {\n        name: '购物车',\n        path: '/pages/index/cart'\n      },\n      {\n        name: '个人中心',\n        path: '/pages/index/user'\n      },\n      {\n        name: '商品搜索',\n        path: '/pages/index/search'\n      },\n      {\n        name: '自定义页面',\n        path: '/pages/index/page',\n        type: APP_LINK_TYPE_ENUM.DIY_PAGE_DETAIL\n      },\n      {\n        name: '客服',\n        path: '/pages/chat/index'\n      },\n      {\n        name: '系统设置',\n        path: '/pages/public/setting'\n      },\n      {\n        name: '常见问题',\n        path: '/pages/public/faq'\n      }\n    ]\n  },\n  {\n    name: '商品',\n    links: [\n      {\n        name: '商品列表',\n        path: '/pages/goods/list',\n        type: APP_LINK_TYPE_ENUM.PRODUCT_LIST\n      },\n      {\n        name: '商品详情',\n        path: '/pages/goods/index',\n        type: APP_LINK_TYPE_ENUM.PRODUCT_DETAIL_NORMAL\n      },\n      {\n        name: '拼团商品详情',\n        path: '/pages/goods/groupon',\n        type: APP_LINK_TYPE_ENUM.PRODUCT_DETAIL_COMBINATION\n      },\n      {\n        name: '秒杀商品详情',\n        path: '/pages/goods/seckill',\n        type: APP_LINK_TYPE_ENUM.PRODUCT_DETAIL_SECKILL\n      }\n    ]\n  },\n  {\n    name: '营销活动',\n    links: [\n      {\n        name: '拼团订单',\n        path: '/pages/activity/groupon/order'\n      },\n      {\n        name: '营销商品',\n        path: '/pages/activity/index'\n      },\n      {\n        name: '拼团活动',\n        path: '/pages/activity/groupon/list',\n        type: APP_LINK_TYPE_ENUM.ACTIVITY_COMBINATION\n      },\n      {\n        name: '秒杀活动',\n        path: '/pages/activity/seckill/list',\n        type: APP_LINK_TYPE_ENUM.ACTIVITY_SECKILL\n      },\n      {\n        name: '签到中心',\n        path: '/pages/app/sign'\n      },\n      {\n        name: '优惠券中心',\n        path: '/pages/coupon/list'\n      },\n      {\n        name: '优惠券详情',\n        path: '/pages/coupon/detail',\n        type: APP_LINK_TYPE_ENUM.COUPON_DETAIL\n      },\n      {\n        name: '文章详情',\n        path: '/pages/public/richtext',\n        type: APP_LINK_TYPE_ENUM.ARTICLE_DETAIL\n      }\n    ]\n  },\n  {\n    name: '分销商城',\n    links: [\n      {\n        name: '分销中心',\n        path: '/pages/commission/index'\n      },\n      {\n        name: '推广商品',\n        path: '/pages/commission/goods'\n      },\n      {\n        name: '分销订单',\n        path: '/pages/commission/order'\n      },\n      {\n        name: '我的团队',\n        path: '/pages/commission/team'\n      }\n    ]\n  },\n  {\n    name: '支付',\n    links: [\n      {\n        name: '充值余额',\n        path: '/pages/pay/recharge'\n      },\n      {\n        name: '充值记录',\n        path: '/pages/pay/recharge-log'\n      }\n    ]\n  },\n  {\n    name: '用户中心',\n    links: [\n      {\n        name: '用户信息',\n        path: '/pages/user/info'\n      },\n      {\n        name: '用户订单',\n        path: '/pages/order/list'\n      },\n      {\n        name: '售后订单',\n        path: '/pages/order/aftersale/list'\n      },\n      {\n        name: '商品收藏',\n        path: '/pages/user/goods-collect'\n      },\n      {\n        name: '浏览记录',\n        path: '/pages/user/goods-log'\n      },\n      {\n        name: '地址管理',\n        path: '/pages/user/address/list'\n      },\n      {\n        name: '用户佣金',\n        path: '/pages/user/wallet/commission'\n      },\n      {\n        name: '用户余额',\n        path: '/pages/user/wallet/money'\n      },\n      {\n        name: '用户积分',\n        path: '/pages/user/wallet/score'\n      }\n    ]\n  }\n] as AppLinkGroup[]\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/AppLinkInput/index.vue",
    "content": "<template>\n  <el-input v-model=\"appLink\" placeholder=\"输入或选择链接\">\n    <template #append>\n      <el-button @click=\"handleOpenDialog\">选择</el-button>\n    </template>\n  </el-input>\n  <AppLinkSelectDialog ref=\"dialogRef\" @change=\"handleLinkSelected\" />\n</template>\n<script lang=\"ts\" setup>\nimport { propTypes } from '@/utils/propTypes'\n\n// APP 链接输入框\ndefineOptions({ name: 'AppLinkInput' })\n// 定义属性\nconst props = defineProps({\n  // 当前选中的链接\n  modelValue: propTypes.string.def('')\n})\n// 当前的链接\nconst appLink = ref('')\n// 选择对话框\nconst dialogRef = ref()\n// 处理打开对话框\nconst handleOpenDialog = () => dialogRef.value?.open(appLink.value)\n// 处理 APP 链接选中\nconst handleLinkSelected = (link: string) => (appLink.value = link)\n\n// getter\nwatch(\n  () => props.modelValue,\n  () => (appLink.value = props.modelValue),\n  { immediate: true }\n)\n\n// setter\nconst emit = defineEmits<{\n  'update:modelValue': [link: string]\n}>()\nwatch(\n  () => appLink.value,\n  () => emit('update:modelValue', appLink.value)\n)\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Backtop/index.ts",
    "content": "import Backtop from './src/Backtop.vue'\n\nexport { Backtop }\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Backtop/src/Backtop.vue",
    "content": "<script lang=\"ts\" setup>\nimport { ElBacktop } from 'element-plus'\nimport { useDesign } from '@/hooks/web/useDesign'\n\ndefineOptions({ name: 'BackTop' })\n\nconst { getPrefixCls, variables } = useDesign()\n\nconst prefixCls = getPrefixCls('backtop')\n</script>\n\n<template>\n  <ElBacktop\n    :class=\"`${prefixCls}-backtop`\"\n    :target=\"`.${variables.namespace}-layout-content-scrollbar .${variables.elNamespace}-scrollbar__wrap`\"\n  />\n</template>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Card/index.ts",
    "content": "import CardTitle from './src/CardTitle.vue'\n\nexport { CardTitle }\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Card/src/CardTitle.vue",
    "content": "<script lang=\"ts\" setup>\ndefineComponent({\n  name: 'CardTitle'\n})\n\ndefineProps({\n  title: {\n    type: String,\n    required: true\n  }\n})\n</script>\n\n<template>\n  <span class=\"card-title\">{{ title }}</span>\n</template>\n\n<style scoped lang=\"scss\">\n.card-title {\n  font-size: 14px;\n  font-weight: 600;\n\n  &::before {\n    position: relative;\n    top: 8px;\n    left: -5px;\n    display: inline-block;\n    width: 3px;\n    height: 14px;\n    //background-color: #105cfb;\n    background: var(--el-color-primary);\n    border-radius: 5px;\n    content: '';\n    transform: translateY(-50%);\n  }\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/ColorInput/index.vue",
    "content": "<template>\n  <el-input v-model=\"color\">\n    <template #prepend>\n      <el-color-picker v-model=\"color\" :predefine=\"PREDEFINE_COLORS\" />\n    </template>\n  </el-input>\n</template>\n\n<script setup lang=\"ts\">\nimport { propTypes } from '@/utils/propTypes'\nimport { PREDEFINE_COLORS } from '@/utils/color'\n\n// 颜色输入框\ndefineOptions({ name: 'ColorInput' })\n\nconst props = defineProps({\n  modelValue: propTypes.string.def('')\n})\nconst emit = defineEmits(['update:modelValue'])\nconst color = computed({\n  get: () => {\n    return props.modelValue\n  },\n  set: (val: string) => {\n    emit('update:modelValue', val)\n  }\n})\n</script>\n\n<style scoped lang=\"scss\">\n:deep(.el-input-group__prepend) {\n  padding: 0;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/ConfigGlobal/index.ts",
    "content": "import ConfigGlobal from './src/ConfigGlobal.vue'\n\nexport { ConfigGlobal }\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/ConfigGlobal/src/ConfigGlobal.vue",
    "content": "<script setup lang=\"ts\">\nimport { provide, computed, watch, onMounted } from 'vue'\nimport { propTypes } from '@/utils/propTypes'\nimport { ComponentSize, ElConfigProvider } from 'element-plus'\nimport { useLocaleStore } from '@/store/modules/locale'\nimport { useWindowSize } from '@vueuse/core'\nimport { useAppStore } from '@/store/modules/app'\nimport { setCssVar } from '@/utils'\nimport { useDesign } from '@/hooks/web/useDesign'\n\nconst { variables } = useDesign()\n\nconst appStore = useAppStore()\n\nconst props = defineProps({\n  size: propTypes.oneOf<ComponentSize>(['default', 'small', 'large']).def('default')\n})\n\nprovide('configGlobal', props)\n\n// 初始化所有主题色\nonMounted(() => {\n  appStore.setCssVarTheme()\n})\n\nconst { width } = useWindowSize()\n\n// 监听窗口变化\nwatch(\n  () => width.value,\n  (width: number) => {\n    if (width < 768) {\n      !appStore.getMobile ? appStore.setMobile(true) : undefined\n      setCssVar('--left-menu-min-width', '0')\n      appStore.setCollapse(true)\n      appStore.getLayout !== 'classic' ? appStore.setLayout('classic') : undefined\n    } else {\n      appStore.getMobile ? appStore.setMobile(false) : undefined\n      setCssVar('--left-menu-min-width', '64px')\n    }\n  },\n  {\n    immediate: true\n  }\n)\n\n// 多语言相关\nconst localeStore = useLocaleStore()\n\nconst currentLocale = computed(() => localeStore.currentLocale)\n</script>\n\n<template>\n  <ElConfigProvider\n    :namespace=\"variables.elNamespace\"\n    :locale=\"currentLocale.elLocale\"\n    :message=\"{ max: 1 }\"\n    :size=\"size\"\n  >\n    <slot></slot>\n  </ElConfigProvider>\n</template>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/ContentDetailWrap/index.ts",
    "content": "import ContentDetailWrap from './src/ContentDetailWrap.vue'\n\nexport { ContentDetailWrap }\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/ContentDetailWrap/src/ContentDetailWrap.vue",
    "content": "<script lang=\"ts\" setup>\nimport { propTypes } from '@/utils/propTypes'\nimport { useDesign } from '@/hooks/web/useDesign'\n\ndefineOptions({ name: 'ContentDetailWrap' })\n\nconst { t } = useI18n()\n\nconst { getPrefixCls } = useDesign()\n\nconst prefixCls = getPrefixCls('content-detail-wrap')\n\ndefineProps({\n  title: propTypes.string.def(''),\n  message: propTypes.string.def('')\n})\nconst emit = defineEmits(['back'])\nconst offset = ref(85)\nconst contentDetailWrap = ref()\nonMounted(() => {\n  offset.value = contentDetailWrap.value.getBoundingClientRect().top\n})\n</script>\n\n<template>\n  <div ref=\"contentDetailWrap\" :class=\"[`${prefixCls}-container`]\">\n    <Sticky :offset=\"offset\">\n      <div\n        :class=\"[\n          `${prefixCls}-header`,\n          'flex b-b-1 h-50px items-center text-center bg-white pr-10px'\n        ]\"\n      >\n        <div :class=\"[`${prefixCls}-header__back`, 'flex pl-10px pr-10px ']\">\n          <ElButton @click=\"emit('back')\">\n            <Icon class=\"mr-5px\" icon=\"ep:arrow-left\" />\n            {{ t('common.back') }}\n          </ElButton>\n        </div>\n        <div :class=\"[`${prefixCls}-header__title`, 'flex flex-1  justify-center']\">\n          <slot name=\"title\">\n            <label class=\"text-16px font-700\">{{ title }}</label>\n          </slot>\n        </div>\n        <div :class=\"[`${prefixCls}-header__right`, 'flex  pl-10px pr-10px']\">\n          <slot name=\"right\"></slot>\n        </div>\n      </div>\n    </Sticky>\n    <div style=\"padding: var(--app-content-padding)\">\n      <ElCard :class=\"[`${prefixCls}-body`, 'mb-20px']\" shadow=\"never\">\n        <div>\n          <slot></slot>\n        </div>\n      </ElCard>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/ContentWrap/index.ts",
    "content": "import ContentWrap from './src/ContentWrap.vue'\n\nexport { ContentWrap }\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/ContentWrap/src/ContentWrap.vue",
    "content": "<script lang=\"ts\" setup>\nimport { propTypes } from '@/utils/propTypes'\nimport { useDesign } from '@/hooks/web/useDesign'\n\ndefineOptions({ name: 'ContentWrap' })\n\nconst { getPrefixCls } = useDesign()\n\nconst prefixCls = getPrefixCls('content-wrap')\n\ndefineProps({\n  title: propTypes.string.def(''),\n  message: propTypes.string.def('')\n})\n</script>\n\n<template>\n  <ElCard :class=\"[prefixCls, 'mb-15px']\" shadow=\"never\">\n    <template v-if=\"title\" #header>\n      <div class=\"flex items-center\">\n        <span class=\"text-16px font-700\">{{ title }}</span>\n        <ElTooltip v-if=\"message\" effect=\"dark\" placement=\"right\">\n          <template #content>\n            <div class=\"max-w-200px\">{{ message }}</div>\n          </template>\n          <Icon :size=\"14\" class=\"ml-5px\" icon=\"ep:question-filled\" />\n        </ElTooltip>\n        <div class=\"flex flex-grow pl-20px\">\n          <slot name=\"header\"></slot>\n        </div>\n      </div>\n    </template>\n    <div>\n      <slot></slot>\n    </div>\n  </ElCard>\n</template>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/CountTo/index.ts",
    "content": "import CountTo from './src/CountTo.vue'\n\nexport { CountTo }\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/CountTo/src/CountTo.vue",
    "content": "<script lang=\"ts\" setup>\nimport { PropType } from 'vue'\nimport { isNumber } from '@/utils/is'\nimport { propTypes } from '@/utils/propTypes'\nimport { useDesign } from '@/hooks/web/useDesign'\n\ndefineOptions({ name: 'CountTo' })\n\nconst { getPrefixCls } = useDesign()\n\nconst prefixCls = getPrefixCls('count-to')\n\nconst props = defineProps({\n  startVal: propTypes.number.def(0), // 开始播放值\n  endVal: propTypes.number.def(2021), // 最终值\n  duration: propTypes.number.def(3000), // 动画时长\n  autoplay: propTypes.bool.def(true), // 是否自动播放动画, 默认播放\n  decimals: propTypes.number.validate((value: number) => value >= 0).def(0), // 显示的小数位数, 默认不显示小数\n  decimal: propTypes.string.def('.'), // 小数分隔符号, 默认为点\n  separator: propTypes.string.def(','), // 数字每三位的分隔符, 默认为逗号\n  prefix: propTypes.string.def(''), // 前缀, 数值前面显示的内容\n  suffix: propTypes.string.def(''), // 后缀, 数值后面显示的内容\n  useEasing: propTypes.bool.def(true), // 是否使用缓动效果, 默认启用\n  easingFn: {\n    type: Function as PropType<(t: number, b: number, c: number, d: number) => number>,\n    default(t: number, b: number, c: number, d: number) {\n      return (c * (-Math.pow(2, (-10 * t) / d) + 1) * 1024) / 1023 + b\n    } // 缓动函数\n  }\n})\n\nconst emit = defineEmits(['mounted', 'callback'])\n\nconst formatNumber = (num: number | string) => {\n  const { decimals, decimal, separator, suffix, prefix } = props\n  num = Number(num).toFixed(decimals)\n  num += ''\n  const x = num.split('.')\n  let x1 = x[0]\n  const x2 = x.length > 1 ? decimal + x[1] : ''\n  const rgx = /(\\d+)(\\d{3})/\n  if (separator && !isNumber(separator)) {\n    while (rgx.test(x1)) {\n      x1 = x1.replace(rgx, '$1' + separator + '$2')\n    }\n  }\n  return prefix + x1 + x2 + suffix\n}\n\nconst state = reactive<{\n  localStartVal: number\n  printVal: number | null\n  displayValue: string\n  paused: boolean\n  localDuration: number | null\n  startTime: number | null\n  timestamp: number | null\n  rAF: any\n  remaining: number | null\n}>({\n  localStartVal: props.startVal,\n  displayValue: formatNumber(props.startVal),\n  printVal: null,\n  paused: false,\n  localDuration: props.duration,\n  startTime: null,\n  timestamp: null,\n  remaining: null,\n  rAF: null\n})\n\nconst displayValue = toRef(state, 'displayValue')\n\nonMounted(() => {\n  if (props.autoplay) {\n    start()\n  }\n  emit('mounted')\n})\n\nconst getCountDown = computed(() => {\n  return props.startVal > props.endVal\n})\n\nwatch([() => props.startVal, () => props.endVal], () => {\n  if (props.autoplay) {\n    start()\n  }\n})\n\nconst start = () => {\n  const { startVal, duration } = props\n  state.localStartVal = startVal\n  state.startTime = null\n  state.localDuration = duration\n  state.paused = false\n  state.rAF = requestAnimationFrame(count)\n}\n\nconst pauseResume = () => {\n  if (state.paused) {\n    resume()\n    state.paused = false\n  } else {\n    pause()\n    state.paused = true\n  }\n}\n\nconst pause = () => {\n  cancelAnimationFrame(state.rAF)\n}\n\nconst resume = () => {\n  state.startTime = null\n  state.localDuration = +(state.remaining as number)\n  state.localStartVal = +(state.printVal as number)\n  requestAnimationFrame(count)\n}\n\nconst reset = () => {\n  state.startTime = null\n  cancelAnimationFrame(state.rAF)\n  state.displayValue = formatNumber(props.startVal)\n}\n\nconst count = (timestamp: number) => {\n  const { useEasing, easingFn, endVal } = props\n  if (!state.startTime) state.startTime = timestamp\n  state.timestamp = timestamp\n  const progress = timestamp - state.startTime\n  state.remaining = (state.localDuration as number) - progress\n  if (useEasing) {\n    if (unref(getCountDown)) {\n      state.printVal =\n        state.localStartVal -\n        easingFn(progress, 0, state.localStartVal - endVal, state.localDuration as number)\n    } else {\n      state.printVal = easingFn(\n        progress,\n        state.localStartVal,\n        endVal - state.localStartVal,\n        state.localDuration as number\n      )\n    }\n  } else {\n    if (unref(getCountDown)) {\n      state.printVal =\n        state.localStartVal -\n        (state.localStartVal - endVal) * (progress / (state.localDuration as number))\n    } else {\n      state.printVal =\n        state.localStartVal +\n        (endVal - state.localStartVal) * (progress / (state.localDuration as number))\n    }\n  }\n  if (unref(getCountDown)) {\n    state.printVal = state.printVal < endVal ? endVal : state.printVal\n  } else {\n    state.printVal = state.printVal > endVal ? endVal : state.printVal\n  }\n  state.displayValue = formatNumber(state.printVal!)\n  if (progress < (state.localDuration as number)) {\n    state.rAF = requestAnimationFrame(count)\n  } else {\n    emit('callback')\n  }\n}\n\ndefineExpose({\n  pauseResume,\n  reset,\n  start,\n  pause\n})\n</script>\n\n<template>\n  <span :class=\"prefixCls\">\n    {{ displayValue }}\n  </span>\n</template>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Crontab/index.ts",
    "content": "import Crontab from './src/Crontab.vue'\nexport { Crontab }\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Crontab/src/Crontab.vue",
    "content": "<script lang=\"ts\" setup>\nimport { ElMessage } from 'element-plus'\nimport { PropType } from 'vue'\n\ndefineOptions({ name: 'Crontab' })\n\ninterface shortcutsType {\n  text: string\n  value: string\n}\n\nconst props = defineProps({\n  modelValue: {\n    type: String,\n    default: '* * * * * ?'\n  },\n  shortcuts: { type: Array as PropType<shortcutsType[]>, default: () => [] }\n})\nconst defaultValue = ref('')\nconst dialogVisible = ref(false)\nconst getYear = () => {\n  let v: number[] = []\n  let y = new Date().getFullYear()\n  for (let i = 0; i < 11; i++) {\n    v.push(y + i)\n  }\n  return v\n}\nconst cronValue = reactive({\n  second: {\n    type: '0',\n    range: {\n      start: 1,\n      end: 2\n    },\n    loop: {\n      start: 0,\n      end: 1\n    },\n    appoint: [] as string[]\n  },\n  minute: {\n    type: '0',\n    range: {\n      start: 1,\n      end: 2\n    },\n    loop: {\n      start: 0,\n      end: 1\n    },\n    appoint: [] as string[]\n  },\n  hour: {\n    type: '0',\n    range: {\n      start: 1,\n      end: 2\n    },\n    loop: {\n      start: 0,\n      end: 1\n    },\n    appoint: [] as string[]\n  },\n  day: {\n    type: '0',\n    range: {\n      start: 1,\n      end: 2\n    },\n    loop: {\n      start: 1,\n      end: 1\n    },\n    appoint: [] as string[]\n  },\n  month: {\n    type: '0',\n    range: {\n      start: 1,\n      end: 2\n    },\n    loop: {\n      start: 1,\n      end: 1\n    },\n    appoint: [] as string[]\n  },\n  week: {\n    type: '5',\n    range: {\n      start: '2',\n      end: '3'\n    },\n    loop: {\n      start: 0,\n      end: '2'\n    },\n    last: '2',\n    appoint: [] as string[]\n  },\n  year: {\n    type: '-1',\n    range: {\n      start: getYear()[0],\n      end: getYear()[1]\n    },\n    loop: {\n      start: getYear()[0],\n      end: 1\n    },\n    appoint: [] as string[]\n  }\n})\nconst data = reactive({\n  second: ['0', '5', '15', '20', '25', '30', '35', '40', '45', '50', '55', '59'],\n  minute: ['0', '5', '15', '20', '25', '30', '35', '40', '45', '50', '55', '59'],\n  hour: [\n    '0',\n    '1',\n    '2',\n    '3',\n    '4',\n    '5',\n    '6',\n    '7',\n    '8',\n    '9',\n    '10',\n    '11',\n    '12',\n    '13',\n    '14',\n    '15',\n    '16',\n    '17',\n    '18',\n    '19',\n    '20',\n    '21',\n    '22',\n    '23'\n  ],\n  day: [\n    '1',\n    '2',\n    '3',\n    '4',\n    '5',\n    '6',\n    '7',\n    '8',\n    '9',\n    '10',\n    '11',\n    '12',\n    '13',\n    '14',\n    '15',\n    '16',\n    '17',\n    '18',\n    '19',\n    '20',\n    '21',\n    '22',\n    '23',\n    '24',\n    '25',\n    '26',\n    '27',\n    '28',\n    '29',\n    '30',\n    '31'\n  ],\n  month: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12'],\n  week: [\n    {\n      value: '1',\n      label: '周日'\n    },\n    {\n      value: '2',\n      label: '周一'\n    },\n    {\n      value: '3',\n      label: '周二'\n    },\n    {\n      value: '4',\n      label: '周三'\n    },\n    {\n      value: '5',\n      label: '周四'\n    },\n    {\n      value: '6',\n      label: '周五'\n    },\n    {\n      value: '7',\n      label: '周六'\n    }\n  ],\n  year: getYear()\n})\n\nconst value_second = computed(() => {\n  let v = cronValue.second\n  if (v.type == '0') {\n    return '*'\n  } else if (v.type == '1') {\n    return v.range.start + '-' + v.range.end\n  } else if (v.type == '2') {\n    return v.loop.start + '/' + v.loop.end\n  } else if (v.type == '3') {\n    return v.appoint.length > 0 ? v.appoint.join(',') : '*'\n  } else {\n    return '*'\n  }\n})\nconst value_minute = computed(() => {\n  let v = cronValue.minute\n  if (v.type == '0') {\n    return '*'\n  } else if (v.type == '1') {\n    return v.range.start + '-' + v.range.end\n  } else if (v.type == '2') {\n    return v.loop.start + '/' + v.loop.end\n  } else if (v.type == '3') {\n    return v.appoint.length > 0 ? v.appoint.join(',') : '*'\n  } else {\n    return '*'\n  }\n})\nconst value_hour = computed(() => {\n  let v = cronValue.hour\n  if (v.type == '0') {\n    return '*'\n  } else if (v.type == '1') {\n    return v.range.start + '-' + v.range.end\n  } else if (v.type == '2') {\n    return v.loop.start + '/' + v.loop.end\n  } else if (v.type == '3') {\n    return v.appoint.length > 0 ? v.appoint.join(',') : '*'\n  } else {\n    return '*'\n  }\n})\nconst value_day = computed(() => {\n  let v = cronValue.day\n  if (v.type == '0') {\n    return '*'\n  } else if (v.type == '1') {\n    return v.range.start + '-' + v.range.end\n  } else if (v.type == '2') {\n    return v.loop.start + '/' + v.loop.end\n  } else if (v.type == '3') {\n    return v.appoint.length > 0 ? v.appoint.join(',') : '*'\n  } else if (v.type == '4') {\n    return 'L'\n  } else if (v.type == '5') {\n    return '?'\n  } else {\n    return '*'\n  }\n})\nconst value_month = computed(() => {\n  let v = cronValue.month\n  if (v.type == '0') {\n    return '*'\n  } else if (v.type == '1') {\n    return v.range.start + '-' + v.range.end\n  } else if (v.type == '2') {\n    return v.loop.start + '/' + v.loop.end\n  } else if (v.type == '3') {\n    return v.appoint.length > 0 ? v.appoint.join(',') : '*'\n  } else {\n    return '*'\n  }\n})\nconst value_week = computed(() => {\n  let v = cronValue.week\n  if (v.type == '0') {\n    return '*'\n  } else if (v.type == '1') {\n    return v.range.start + '-' + v.range.end\n  } else if (v.type == '2') {\n    return v.loop.end + '#' + v.loop.start\n  } else if (v.type == '3') {\n    return v.appoint.length > 0 ? v.appoint.join(',') : '*'\n  } else if (v.type == '4') {\n    return v.last + 'L'\n  } else if (v.type == '5') {\n    return '?'\n  } else {\n    return '*'\n  }\n})\nconst value_year = computed(() => {\n  let v = cronValue.year\n  if (v.type == '-1') {\n    return ''\n  } else if (v.type == '0') {\n    return '*'\n  } else if (v.type == '1') {\n    return v.range.start + '-' + v.range.end\n  } else if (v.type == '2') {\n    return v.loop.start + '/' + v.loop.end\n  } else if (v.type == '3') {\n    return v.appoint.length > 0 ? v.appoint.join(',') : ''\n  } else {\n    return ''\n  }\n})\nwatch(\n  () => cronValue.week.type,\n  (val) => {\n    if (val != '5') {\n      cronValue.day.type = '5'\n    }\n  }\n)\nwatch(\n  () => cronValue.day.type,\n  (val) => {\n    if (val != '5') {\n      cronValue.week.type = '5'\n    }\n  }\n)\nwatch(\n  () => props.modelValue,\n  () => {\n    defaultValue.value = props.modelValue\n  }\n)\nonMounted(() => {\n  defaultValue.value = props.modelValue\n})\nconst emit = defineEmits(['update:modelValue'])\nconst select = ref()\nwatch(\n  () => select.value,\n  () => {\n    if (select.value == 'custom') {\n      open()\n    } else {\n      defaultValue.value = select.value\n      emit('update:modelValue', defaultValue.value)\n    }\n  }\n)\nconst open = () => {\n  set()\n  dialogVisible.value = true\n}\nconst set = () => {\n  defaultValue.value = props.modelValue\n  let arr = (props.modelValue || '* * * * * ?').split(' ')\n  //简单检查\n  if (arr.length < 6) {\n    ElMessage.warning('cron表达式错误，已转换为默认表达式')\n    arr = '* * * * * ?'.split(' ')\n  }\n\n  //秒\n  if (arr[0] == '*') {\n    cronValue.second.type = '0'\n  } else if (arr[0].includes('-')) {\n    cronValue.second.type = '1'\n    cronValue.second.range.start = Number(arr[0].split('-')[0])\n    cronValue.second.range.end = Number(arr[0].split('-')[1])\n  } else if (arr[0].includes('/')) {\n    cronValue.second.type = '2'\n    cronValue.second.loop.start = Number(arr[0].split('/')[0])\n    cronValue.second.loop.end = Number(arr[0].split('/')[1])\n  } else {\n    cronValue.second.type = '3'\n    cronValue.second.appoint = arr[0].split(',')\n  }\n  //分\n  if (arr[1] == '*') {\n    cronValue.minute.type = '0'\n  } else if (arr[1].includes('-')) {\n    cronValue.minute.type = '1'\n    cronValue.minute.range.start = Number(arr[1].split('-')[0])\n    cronValue.minute.range.end = Number(arr[1].split('-')[1])\n  } else if (arr[1].includes('/')) {\n    cronValue.minute.type = '2'\n    cronValue.minute.loop.start = Number(arr[1].split('/')[0])\n    cronValue.minute.loop.end = Number(arr[1].split('/')[1])\n  } else {\n    cronValue.minute.type = '3'\n    cronValue.minute.appoint = arr[1].split(',')\n  }\n  //小时\n  if (arr[2] == '*') {\n    cronValue.hour.type = '0'\n  } else if (arr[2].includes('-')) {\n    cronValue.hour.type = '1'\n    cronValue.hour.range.start = Number(arr[2].split('-')[0])\n    cronValue.hour.range.end = Number(arr[2].split('-')[1])\n  } else if (arr[2].includes('/')) {\n    cronValue.hour.type = '2'\n    cronValue.hour.loop.start = Number(arr[2].split('/')[0])\n    cronValue.hour.loop.end = Number(arr[2].split('/')[1])\n  } else {\n    cronValue.hour.type = '3'\n    cronValue.hour.appoint = arr[2].split(',')\n  }\n  //日\n  if (arr[3] == '*') {\n    cronValue.day.type = '0'\n  } else if (arr[3] == 'L') {\n    cronValue.day.type = '4'\n  } else if (arr[3] == '?') {\n    cronValue.day.type = '5'\n  } else if (arr[3].includes('-')) {\n    cronValue.day.type = '1'\n    cronValue.day.range.start = Number(arr[3].split('-')[0])\n    cronValue.day.range.end = Number(arr[3].split('-')[1])\n  } else if (arr[3].includes('/')) {\n    cronValue.day.type = '2'\n    cronValue.day.loop.start = Number(arr[3].split('/')[0])\n    cronValue.day.loop.end = Number(arr[3].split('/')[1])\n  } else {\n    cronValue.day.type = '3'\n    cronValue.day.appoint = arr[3].split(',')\n  }\n  //月\n  if (arr[4] == '*') {\n    cronValue.month.type = '0'\n  } else if (arr[4].includes('-')) {\n    cronValue.month.type = '1'\n    cronValue.month.range.start = Number(arr[4].split('-')[0])\n    cronValue.month.range.end = Number(arr[4].split('-')[1])\n  } else if (arr[4].includes('/')) {\n    cronValue.month.type = '2'\n    cronValue.month.loop.start = Number(arr[4].split('/')[0])\n    cronValue.month.loop.end = Number(arr[4].split('/')[1])\n  } else {\n    cronValue.month.type = '3'\n    cronValue.month.appoint = arr[4].split(',')\n  }\n  //周\n  if (arr[5] == '*') {\n    cronValue.week.type = '0'\n  } else if (arr[5] == '?') {\n    cronValue.week.type = '5'\n  } else if (arr[5].includes('-')) {\n    cronValue.week.type = '1'\n    cronValue.week.range.start = arr[5].split('-')[0]\n    cronValue.week.range.end = arr[5].split('-')[1]\n  } else if (arr[5].includes('#')) {\n    cronValue.week.type = '2'\n    cronValue.week.loop.start = Number(arr[5].split('#')[1])\n    cronValue.week.loop.end = arr[5].split('#')[0]\n  } else if (arr[5].includes('L')) {\n    cronValue.week.type = '4'\n    cronValue.week.last = arr[5].split('L')[0]\n  } else {\n    cronValue.week.type = '3'\n    cronValue.week.appoint = arr[5].split(',')\n  }\n  //年\n  if (!arr[6]) {\n    cronValue.year.type = '-1'\n  } else if (arr[6] == '*') {\n    cronValue.year.type = '0'\n  } else if (arr[6].includes('-')) {\n    cronValue.year.type = '1'\n    cronValue.year.range.start = Number(arr[6].split('-')[0])\n    cronValue.year.range.end = Number(arr[6].split('-')[1])\n  } else if (arr[6].includes('/')) {\n    cronValue.year.type = '2'\n    cronValue.year.loop.start = Number(arr[6].split('/')[1])\n    cronValue.year.loop.end = Number(arr[6].split('/')[0])\n  } else {\n    cronValue.year.type = '3'\n    cronValue.year.appoint = arr[6].split(',')\n  }\n}\nconst submit = () => {\n  let year = value_year.value ? ' ' + value_year.value : ''\n  defaultValue.value =\n    value_second.value +\n    ' ' +\n    value_minute.value +\n    ' ' +\n    value_hour.value +\n    ' ' +\n    value_day.value +\n    ' ' +\n    value_month.value +\n    ' ' +\n    value_week.value +\n    year\n  emit('update:modelValue', defaultValue.value)\n  dialogVisible.value = false\n}\n\nconst inputChange = () => {\n  emit('update:modelValue', defaultValue.value)\n}\n</script>\n<template>\n  <el-input v-model=\"defaultValue\" class=\"input-with-select\" v-bind=\"$attrs\" @input=\"inputChange\">\n    <template #append>\n      <el-select v-model=\"select\" placeholder=\"生成器\" style=\"width: 115px\">\n        <el-option label=\"每分钟\" value=\"0 * * * * ?\" />\n        <el-option label=\"每小时\" value=\"0 0 * * * ?\" />\n        <el-option label=\"每天零点\" value=\"0 0 0 * * ?\" />\n        <el-option label=\"每月一号零点\" value=\"0 0 0 1 * ?\" />\n        <el-option label=\"每月最后一天零点\" value=\"0 0 0 L * ?\" />\n        <el-option label=\"每周星期日零点\" value=\"0 0 0 ? * 1\" />\n        <el-option\n          v-for=\"(item, index) in shortcuts\"\n          :key=\"index\"\n          :label=\"item.text\"\n          :value=\"item.value\"\n        />\n        <el-option label=\"自定义\" value=\"custom\" />\n      </el-select>\n    </template>\n  </el-input>\n\n  <el-dialog\n    v-model=\"dialogVisible\"\n    :width=\"580\"\n    append-to-body\n    destroy-on-close\n    title=\"cron规则生成器\"\n  >\n    <div class=\"sc-cron\">\n      <el-tabs>\n        <el-tab-pane>\n          <template #label>\n            <div class=\"sc-cron-num\">\n              <h2>秒</h2>\n              <h4>{{ value_second }}</h4>\n            </div>\n          </template>\n          <el-form>\n            <el-form-item label=\"类型\">\n              <el-radio-group v-model=\"cronValue.second.type\">\n                <el-radio-button label=\"0\">任意值</el-radio-button>\n                <el-radio-button label=\"1\">范围</el-radio-button>\n                <el-radio-button label=\"2\">间隔</el-radio-button>\n                <el-radio-button label=\"3\">指定</el-radio-button>\n              </el-radio-group>\n            </el-form-item>\n            <el-form-item v-if=\"cronValue.second.type == '1'\" label=\"范围\">\n              <el-input-number\n                v-model=\"cronValue.second.range.start\"\n                :max=\"59\"\n                :min=\"0\"\n                controls-position=\"right\"\n              />\n              <span style=\"padding: 0 15px\">-</span>\n              <el-input-number\n                v-model=\"cronValue.second.range.end\"\n                :max=\"59\"\n                :min=\"0\"\n                controls-position=\"right\"\n              />\n            </el-form-item>\n            <el-form-item v-if=\"cronValue.second.type == '2'\" label=\"间隔\">\n              <el-input-number\n                v-model=\"cronValue.second.loop.start\"\n                :max=\"59\"\n                :min=\"0\"\n                controls-position=\"right\"\n              />\n              秒开始，每\n              <el-input-number\n                v-model=\"cronValue.second.loop.end\"\n                :max=\"59\"\n                :min=\"0\"\n                controls-position=\"right\"\n              />\n              秒执行一次\n            </el-form-item>\n            <el-form-item v-if=\"cronValue.second.type == '3'\" label=\"指定\">\n              <el-select v-model=\"cronValue.second.appoint\" multiple style=\"width: 100%\">\n                <el-option\n                  v-for=\"(item, index) in data.second\"\n                  :key=\"index\"\n                  :label=\"item\"\n                  :value=\"item\"\n                />\n              </el-select>\n            </el-form-item>\n          </el-form>\n        </el-tab-pane>\n        <el-tab-pane>\n          <template #label>\n            <div class=\"sc-cron-num\">\n              <h2>分钟</h2>\n              <h4>{{ value_minute }}</h4>\n            </div>\n          </template>\n          <el-form>\n            <el-form-item label=\"类型\">\n              <el-radio-group v-model=\"cronValue.minute.type\">\n                <el-radio-button label=\"0\">任意值</el-radio-button>\n                <el-radio-button label=\"1\">范围</el-radio-button>\n                <el-radio-button label=\"2\">间隔</el-radio-button>\n                <el-radio-button label=\"3\">指定</el-radio-button>\n              </el-radio-group>\n            </el-form-item>\n            <el-form-item v-if=\"cronValue.minute.type == '1'\" label=\"范围\">\n              <el-input-number\n                v-model=\"cronValue.minute.range.start\"\n                :max=\"59\"\n                :min=\"0\"\n                controls-position=\"right\"\n              />\n              <span style=\"padding: 0 15px\">-</span>\n              <el-input-number\n                v-model=\"cronValue.minute.range.end\"\n                :max=\"59\"\n                :min=\"0\"\n                controls-position=\"right\"\n              />\n            </el-form-item>\n            <el-form-item v-if=\"cronValue.minute.type == '2'\" label=\"间隔\">\n              <el-input-number\n                v-model=\"cronValue.minute.loop.start\"\n                :max=\"59\"\n                :min=\"0\"\n                controls-position=\"right\"\n              />\n              分钟开始，每\n              <el-input-number\n                v-model=\"cronValue.minute.loop.end\"\n                :max=\"59\"\n                :min=\"0\"\n                controls-position=\"right\"\n              />\n              分钟执行一次\n            </el-form-item>\n            <el-form-item v-if=\"cronValue.minute.type == '3'\" label=\"指定\">\n              <el-select v-model=\"cronValue.minute.appoint\" multiple style=\"width: 100%\">\n                <el-option\n                  v-for=\"(item, index) in data.minute\"\n                  :key=\"index\"\n                  :label=\"item\"\n                  :value=\"item\"\n                />\n              </el-select>\n            </el-form-item>\n          </el-form>\n        </el-tab-pane>\n        <el-tab-pane>\n          <template #label>\n            <div class=\"sc-cron-num\">\n              <h2>小时</h2>\n              <h4>{{ value_hour }}</h4>\n            </div>\n          </template>\n          <el-form>\n            <el-form-item label=\"类型\">\n              <el-radio-group v-model=\"cronValue.hour.type\">\n                <el-radio-button label=\"0\">任意值</el-radio-button>\n                <el-radio-button label=\"1\">范围</el-radio-button>\n                <el-radio-button label=\"2\">间隔</el-radio-button>\n                <el-radio-button label=\"3\">指定</el-radio-button>\n              </el-radio-group>\n            </el-form-item>\n            <el-form-item v-if=\"cronValue.hour.type == '1'\" label=\"范围\">\n              <el-input-number\n                v-model=\"cronValue.hour.range.start\"\n                :max=\"23\"\n                :min=\"0\"\n                controls-position=\"right\"\n              />\n              <span style=\"padding: 0 15px\">-</span>\n              <el-input-number\n                v-model=\"cronValue.hour.range.end\"\n                :max=\"23\"\n                :min=\"0\"\n                controls-position=\"right\"\n              />\n            </el-form-item>\n            <el-form-item v-if=\"cronValue.hour.type == '2'\" label=\"间隔\">\n              <el-input-number\n                v-model=\"cronValue.hour.loop.start\"\n                :max=\"23\"\n                :min=\"0\"\n                controls-position=\"right\"\n              />\n              小时开始，每\n              <el-input-number\n                v-model=\"cronValue.hour.loop.end\"\n                :max=\"23\"\n                :min=\"0\"\n                controls-position=\"right\"\n              />\n              小时执行一次\n            </el-form-item>\n            <el-form-item v-if=\"cronValue.hour.type == '3'\" label=\"指定\">\n              <el-select v-model=\"cronValue.hour.appoint\" multiple style=\"width: 100%\">\n                <el-option\n                  v-for=\"(item, index) in data.hour\"\n                  :key=\"index\"\n                  :label=\"item\"\n                  :value=\"item\"\n                />\n              </el-select>\n            </el-form-item>\n          </el-form>\n        </el-tab-pane>\n        <el-tab-pane>\n          <template #label>\n            <div class=\"sc-cron-num\">\n              <h2>日</h2>\n              <h4>{{ value_day }}</h4>\n            </div>\n          </template>\n          <el-form>\n            <el-form-item label=\"类型\">\n              <el-radio-group v-model=\"cronValue.day.type\">\n                <el-radio-button label=\"0\">任意值</el-radio-button>\n                <el-radio-button label=\"1\">范围</el-radio-button>\n                <el-radio-button label=\"2\">间隔</el-radio-button>\n                <el-radio-button label=\"3\">指定</el-radio-button>\n                <el-radio-button label=\"4\">本月最后一天</el-radio-button>\n                <el-radio-button label=\"5\">不指定</el-radio-button>\n              </el-radio-group>\n            </el-form-item>\n            <el-form-item v-if=\"cronValue.day.type == '1'\" label=\"范围\">\n              <el-input-number\n                v-model=\"cronValue.day.range.start\"\n                :max=\"31\"\n                :min=\"1\"\n                controls-position=\"right\"\n              />\n              <span style=\"padding: 0 15px\">-</span>\n              <el-input-number\n                v-model=\"cronValue.day.range.end\"\n                :max=\"31\"\n                :min=\"1\"\n                controls-position=\"right\"\n              />\n            </el-form-item>\n            <el-form-item v-if=\"cronValue.day.type == '2'\" label=\"间隔\">\n              <el-input-number\n                v-model=\"cronValue.day.loop.start\"\n                :max=\"31\"\n                :min=\"1\"\n                controls-position=\"right\"\n              />\n              号开始，每\n              <el-input-number\n                v-model=\"cronValue.day.loop.end\"\n                :max=\"31\"\n                :min=\"1\"\n                controls-position=\"right\"\n              />\n              天执行一次\n            </el-form-item>\n            <el-form-item v-if=\"cronValue.day.type == '3'\" label=\"指定\">\n              <el-select v-model=\"cronValue.day.appoint\" multiple style=\"width: 100%\">\n                <el-option\n                  v-for=\"(item, index) in data.day\"\n                  :key=\"index\"\n                  :label=\"item\"\n                  :value=\"item\"\n                />\n              </el-select>\n            </el-form-item>\n          </el-form>\n        </el-tab-pane>\n        <el-tab-pane>\n          <template #label>\n            <div class=\"sc-cron-num\">\n              <h2>月</h2>\n              <h4>{{ value_month }}</h4>\n            </div>\n          </template>\n          <el-form>\n            <el-form-item label=\"类型\">\n              <el-radio-group v-model=\"cronValue.month.type\">\n                <el-radio-button label=\"0\">任意值</el-radio-button>\n                <el-radio-button label=\"1\">范围</el-radio-button>\n                <el-radio-button label=\"2\">间隔</el-radio-button>\n                <el-radio-button label=\"3\">指定</el-radio-button>\n              </el-radio-group>\n            </el-form-item>\n            <el-form-item v-if=\"cronValue.month.type == '1'\" label=\"范围\">\n              <el-input-number\n                v-model=\"cronValue.month.range.start\"\n                :max=\"12\"\n                :min=\"1\"\n                controls-position=\"right\"\n              />\n              <span style=\"padding: 0 15px\">-</span>\n              <el-input-number\n                v-model=\"cronValue.month.range.end\"\n                :max=\"12\"\n                :min=\"1\"\n                controls-position=\"right\"\n              />\n            </el-form-item>\n            <el-form-item v-if=\"cronValue.month.type == '2'\" label=\"间隔\">\n              <el-input-number\n                v-model=\"cronValue.month.loop.start\"\n                :max=\"12\"\n                :min=\"1\"\n                controls-position=\"right\"\n              />\n              月开始，每\n              <el-input-number\n                v-model=\"cronValue.month.loop.end\"\n                :max=\"12\"\n                :min=\"1\"\n                controls-position=\"right\"\n              />\n              月执行一次\n            </el-form-item>\n            <el-form-item v-if=\"cronValue.month.type == '3'\" label=\"指定\">\n              <el-select v-model=\"cronValue.month.appoint\" multiple style=\"width: 100%\">\n                <el-option\n                  v-for=\"(item, index) in data.month\"\n                  :key=\"index\"\n                  :label=\"item\"\n                  :value=\"item\"\n                />\n              </el-select>\n            </el-form-item>\n          </el-form>\n        </el-tab-pane>\n        <el-tab-pane>\n          <template #label>\n            <div class=\"sc-cron-num\">\n              <h2>周</h2>\n              <h4>{{ value_week }}</h4>\n            </div>\n          </template>\n          <el-form>\n            <el-form>\n              <el-form-item label=\"类型\">\n                <el-radio-group v-model=\"cronValue.week.type\">\n                  <el-radio-button label=\"0\">任意值</el-radio-button>\n                  <el-radio-button label=\"1\">范围</el-radio-button>\n                  <el-radio-button label=\"2\">间隔</el-radio-button>\n                  <el-radio-button label=\"3\">指定</el-radio-button>\n                  <el-radio-button label=\"4\">本月最后一周</el-radio-button>\n                  <el-radio-button label=\"5\">不指定</el-radio-button>\n                </el-radio-group>\n              </el-form-item>\n              <el-form-item v-if=\"cronValue.week.type == '1'\" label=\"范围\">\n                <el-select v-model=\"cronValue.week.range.start\">\n                  <el-option\n                    v-for=\"(item, index) in data.week\"\n                    :key=\"index\"\n                    :label=\"item.label\"\n                    :value=\"item.value\"\n                  />\n                </el-select>\n                <span style=\"padding: 0 15px\">-</span>\n                <el-select v-model=\"cronValue.week.range.end\">\n                  <el-option\n                    v-for=\"(item, index) in data.week\"\n                    :key=\"index\"\n                    :label=\"item.label\"\n                    :value=\"item.value\"\n                  />\n                </el-select>\n              </el-form-item>\n              <el-form-item v-if=\"cronValue.week.type == '2'\" label=\"间隔\">\n                第\n                <el-input-number\n                  v-model=\"cronValue.week.loop.start\"\n                  :max=\"4\"\n                  :min=\"1\"\n                  controls-position=\"right\"\n                />\n                周的星期\n                <el-select v-model=\"cronValue.week.loop.end\">\n                  <el-option\n                    v-for=\"(item, index) in data.week\"\n                    :key=\"index\"\n                    :label=\"item.label\"\n                    :value=\"item.value\"\n                  />\n                </el-select>\n                执行一次\n              </el-form-item>\n              <el-form-item v-if=\"cronValue.week.type == '3'\" label=\"指定\">\n                <el-select v-model=\"cronValue.week.appoint\" multiple style=\"width: 100%\">\n                  <el-option\n                    v-for=\"(item, index) in data.week\"\n                    :key=\"index\"\n                    :label=\"item.label\"\n                    :value=\"item.value\"\n                  />\n                </el-select>\n              </el-form-item>\n              <el-form-item v-if=\"cronValue.week.type == '4'\" label=\"最后一周\">\n                <el-select v-model=\"cronValue.week.last\">\n                  <el-option\n                    v-for=\"(item, index) in data.week\"\n                    :key=\"index\"\n                    :label=\"item.label\"\n                    :value=\"item.value\"\n                  />\n                </el-select>\n              </el-form-item>\n            </el-form>\n          </el-form>\n        </el-tab-pane>\n        <el-tab-pane>\n          <template #label>\n            <div class=\"sc-cron-num\">\n              <h2>年</h2>\n              <h4>{{ value_year }}</h4>\n            </div>\n          </template>\n          <el-form>\n            <el-form-item label=\"类型\">\n              <el-radio-group v-model=\"cronValue.year.type\">\n                <el-radio-button label=\"-1\">忽略</el-radio-button>\n                <el-radio-button label=\"0\">任意值</el-radio-button>\n                <el-radio-button label=\"1\">范围</el-radio-button>\n                <el-radio-button label=\"2\">间隔</el-radio-button>\n                <el-radio-button label=\"3\">指定</el-radio-button>\n              </el-radio-group>\n            </el-form-item>\n            <el-form-item v-if=\"cronValue.year.type == '1'\" label=\"范围\">\n              <el-input-number v-model=\"cronValue.year.range.start\" controls-position=\"right\" />\n              <span style=\"padding: 0 15px\">-</span>\n              <el-input-number v-model=\"cronValue.year.range.end\" controls-position=\"right\" />\n            </el-form-item>\n            <el-form-item v-if=\"cronValue.year.type == '2'\" label=\"间隔\">\n              <el-input-number v-model=\"cronValue.year.loop.start\" controls-position=\"right\" />\n              年开始，每\n              <el-input-number\n                v-model=\"cronValue.year.loop.end\"\n                :min=\"1\"\n                controls-position=\"right\"\n              />\n              年执行一次\n            </el-form-item>\n            <el-form-item v-if=\"cronValue.year.type == '3'\" label=\"指定\">\n              <el-select v-model=\"cronValue.year.appoint\" multiple style=\"width: 100%\">\n                <el-option\n                  v-for=\"(item, index) in data.year\"\n                  :key=\"index\"\n                  :label=\"item\"\n                  :value=\"item\"\n                />\n              </el-select>\n            </el-form-item>\n          </el-form>\n        </el-tab-pane>\n      </el-tabs>\n    </div>\n\n    <template #footer>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n      <el-button type=\"primary\" @click=\"submit()\">确 认</el-button>\n    </template>\n  </el-dialog>\n</template>\n\n<style scoped>\n.sc-cron:deep(.el-tabs__item) {\n  height: auto;\n  padding: 0 7px;\n  line-height: 1;\n  vertical-align: bottom;\n}\n\n.sc-cron-num {\n  width: 100%;\n  margin-bottom: 15px;\n  text-align: center;\n}\n\n.sc-cron-num h2 {\n  margin-bottom: 15px;\n  font-size: 12px;\n  font-weight: normal;\n}\n\n.sc-cron-num h4 {\n  display: block;\n  width: 100%;\n  height: 32px;\n  padding: 0 15px;\n  font-size: 12px;\n  line-height: 30px;\n  background: var(--el-color-primary-light-9);\n  border-radius: 4px;\n}\n\n.sc-cron:deep(.el-tabs__item.is-active) .sc-cron-num h4 {\n  color: #fff;\n  background: var(--el-color-primary);\n}\n\n[data-theme='dark'] .sc-cron-num h4 {\n  background: var(--el-color-white);\n}\n\n.input-with-select .el-input-group__prepend {\n  background-color: var(--el-fill-color-blank);\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Cropper/index.ts",
    "content": "import CropperImage from './src/Cropper.vue'\nimport CropperAvatar from './src/CropperAvatar.vue'\n\nexport { CropperImage, CropperAvatar }\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Cropper/src/CopperModal.vue",
    "content": "<template>\n  <div>\n    <Dialog\n      v-model=\"dialogVisible\"\n      :canFullscreen=\"false\"\n      :title=\"t('cropper.modalTitle')\"\n      maxHeight=\"380px\"\n      width=\"800px\"\n    >\n      <div :class=\"prefixCls\">\n        <div :class=\"`${prefixCls}-left`\">\n          <div :class=\"`${prefixCls}-cropper`\">\n            <CropperImage\n              v-if=\"src\"\n              :circled=\"circled\"\n              :src=\"src\"\n              height=\"300px\"\n              @cropend=\"handleCropend\"\n              @ready=\"handleReady\"\n            />\n          </div>\n\n          <div :class=\"`${prefixCls}-toolbar`\">\n            <el-upload :beforeUpload=\"handleBeforeUpload\" :fileList=\"[]\" accept=\"image/*\">\n              <el-tooltip :content=\"t('cropper.selectImage')\" placement=\"bottom\">\n                <XButton preIcon=\"ant-design:upload-outlined\" type=\"primary\" />\n              </el-tooltip>\n            </el-upload>\n            <el-space>\n              <el-tooltip :content=\"t('cropper.btn_reset')\" placement=\"bottom\">\n                <XButton\n                  :disabled=\"!src\"\n                  preIcon=\"ant-design:reload-outlined\"\n                  size=\"small\"\n                  type=\"primary\"\n                  @click=\"handlerToolbar('reset')\"\n                />\n              </el-tooltip>\n              <el-tooltip :content=\"t('cropper.btn_rotate_left')\" placement=\"bottom\">\n                <XButton\n                  :disabled=\"!src\"\n                  preIcon=\"ant-design:rotate-left-outlined\"\n                  size=\"small\"\n                  type=\"primary\"\n                  @click=\"handlerToolbar('rotate', -45)\"\n                />\n              </el-tooltip>\n              <el-tooltip :content=\"t('cropper.btn_rotate_right')\" placement=\"bottom\">\n                <XButton\n                  :disabled=\"!src\"\n                  preIcon=\"ant-design:rotate-right-outlined\"\n                  size=\"small\"\n                  type=\"primary\"\n                  @click=\"handlerToolbar('rotate', 45)\"\n                />\n              </el-tooltip>\n              <el-tooltip :content=\"t('cropper.btn_scale_x')\" placement=\"bottom\">\n                <XButton\n                  :disabled=\"!src\"\n                  preIcon=\"vaadin:arrows-long-h\"\n                  size=\"small\"\n                  type=\"primary\"\n                  @click=\"handlerToolbar('scaleX')\"\n                />\n              </el-tooltip>\n              <el-tooltip :content=\"t('cropper.btn_scale_y')\" placement=\"bottom\">\n                <XButton\n                  :disabled=\"!src\"\n                  preIcon=\"vaadin:arrows-long-v\"\n                  size=\"small\"\n                  type=\"primary\"\n                  @click=\"handlerToolbar('scaleY')\"\n                />\n              </el-tooltip>\n              <el-tooltip :content=\"t('cropper.btn_zoom_in')\" placement=\"bottom\">\n                <XButton\n                  :disabled=\"!src\"\n                  preIcon=\"ant-design:zoom-in-outlined\"\n                  size=\"small\"\n                  type=\"primary\"\n                  @click=\"handlerToolbar('zoom', 0.1)\"\n                />\n              </el-tooltip>\n              <el-tooltip :content=\"t('cropper.btn_zoom_out')\" placement=\"bottom\">\n                <XButton\n                  :disabled=\"!src\"\n                  preIcon=\"ant-design:zoom-out-outlined\"\n                  size=\"small\"\n                  type=\"primary\"\n                  @click=\"handlerToolbar('zoom', -0.1)\"\n                />\n              </el-tooltip>\n            </el-space>\n          </div>\n        </div>\n        <div :class=\"`${prefixCls}-right`\">\n          <div :class=\"`${prefixCls}-preview`\">\n            <img v-if=\"previewSource\" :alt=\"t('cropper.preview')\" :src=\"previewSource\" />\n          </div>\n          <template v-if=\"previewSource\">\n            <div :class=\"`${prefixCls}-group`\">\n              <el-avatar :src=\"previewSource\" size=\"large\" />\n              <el-avatar :size=\"48\" :src=\"previewSource\" />\n              <el-avatar :size=\"64\" :src=\"previewSource\" />\n              <el-avatar :size=\"80\" :src=\"previewSource\" />\n            </div>\n          </template>\n        </div>\n      </div>\n      <template #footer>\n        <el-button type=\"primary\" @click=\"handleOk\">{{ t('cropper.okText') }}</el-button>\n      </template>\n    </Dialog>\n  </div>\n</template>\n<script lang=\"ts\" setup>\nimport { useDesign } from '@/hooks/web/useDesign'\nimport { dataURLtoBlob } from '@/utils/filt'\nimport { useI18n } from 'vue-i18n'\nimport type { CropendResult, Cropper } from './types'\nimport { propTypes } from '@/utils/propTypes'\nimport { CropperImage } from '@/components/Cropper'\n\ndefineOptions({ name: 'CopperModal' })\n\nconst props = defineProps({\n  srcValue: propTypes.string.def(''),\n  circled: propTypes.bool.def(true)\n})\nconst emit = defineEmits(['uploadSuccess'])\nconst { t } = useI18n()\nconst { getPrefixCls } = useDesign()\nconst prefixCls = getPrefixCls('cropper-am')\n\nconst src = ref(props.srcValue)\nconst previewSource = ref('')\nconst cropper = ref<Cropper>()\nconst dialogVisible = ref(false)\nlet filename = ''\nlet scaleX = 1\nlet scaleY = 1\n\n// Block upload\nfunction handleBeforeUpload(file: File) {\n  const reader = new FileReader()\n  reader.readAsDataURL(file)\n  src.value = ''\n  previewSource.value = ''\n  reader.onload = function (e) {\n    src.value = (e.target?.result as string) ?? ''\n    filename = file.name\n  }\n  return false\n}\n\nfunction handleCropend({ imgBase64 }: CropendResult) {\n  previewSource.value = imgBase64\n}\n\nfunction handleReady(cropperInstance: Cropper) {\n  cropper.value = cropperInstance\n}\n\nfunction handlerToolbar(event: string, arg?: number) {\n  if (event === 'scaleX') {\n    scaleX = arg = scaleX === -1 ? 1 : -1\n  }\n  if (event === 'scaleY') {\n    scaleY = arg = scaleY === -1 ? 1 : -1\n  }\n  cropper?.value?.[event]?.(arg)\n}\n\nasync function handleOk() {\n  const blob = dataURLtoBlob(previewSource.value)\n  emit('uploadSuccess', { source: previewSource.value, data: blob, filename: filename })\n}\n\nfunction openModal() {\n  dialogVisible.value = true\n}\n\nfunction closeModal() {\n  dialogVisible.value = false\n}\n\ndefineExpose({ openModal, closeModal })\n</script>\n<style lang=\"scss\">\n$prefix-cls: #{$namespace}-cropper-am;\n\n.#{$prefix-cls} {\n  display: flex;\n\n  &-left,\n  &-right {\n    height: 340px;\n  }\n\n  &-left {\n    width: 55%;\n  }\n\n  &-right {\n    width: 45%;\n  }\n\n  &-cropper {\n    height: 300px;\n    background: #eee;\n    background-image: linear-gradient(\n        45deg,\n        rgb(0 0 0 / 25%) 25%,\n        transparent 0,\n        transparent 75%,\n        rgb(0 0 0 / 25%) 0\n      ),\n      linear-gradient(\n        45deg,\n        rgb(0 0 0 / 25%) 25%,\n        transparent 0,\n        transparent 75%,\n        rgb(0 0 0 / 25%) 0\n      );\n    background-position:\n      0 0,\n      12px 12px;\n    background-size: 24px 24px;\n  }\n\n  &-toolbar {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    margin-top: 10px;\n  }\n\n  &-preview {\n    width: 220px;\n    height: 220px;\n    margin: 0 auto;\n    overflow: hidden;\n    border: 1px solid;\n    border-radius: 50%;\n\n    img {\n      width: 100%;\n      height: 100%;\n    }\n  }\n\n  &-group {\n    display: flex;\n    padding-top: 8px;\n    margin-top: 8px;\n    border-top: 1px solid;\n    justify-content: space-around;\n    align-items: center;\n  }\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Cropper/src/Cropper.vue",
    "content": "<template>\n  <div :class=\"getClass\" :style=\"getWrapperStyle\">\n    <img\n      v-show=\"isReady\"\n      ref=\"imgElRef\"\n      :alt=\"alt\"\n      :crossorigin=\"crossorigin\"\n      :src=\"src\"\n      :style=\"getImageStyle\"\n    />\n  </div>\n</template>\n<script lang=\"ts\" setup>\nimport { CSSProperties, PropType } from 'vue'\nimport Cropper from 'cropperjs'\nimport 'cropperjs/dist/cropper.css'\nimport { useDesign } from '@/hooks/web/useDesign'\nimport { propTypes } from '@/utils/propTypes'\nimport { useDebounceFn } from '@vueuse/core'\n\ndefineOptions({ name: 'Cropper' })\n\ntype Options = Cropper.Options\n\nconst defaultOptions: Options = {\n  aspectRatio: 1,\n  zoomable: true,\n  zoomOnTouch: true,\n  zoomOnWheel: true,\n  cropBoxMovable: true,\n  cropBoxResizable: true,\n  toggleDragModeOnDblclick: true,\n  autoCrop: true,\n  background: true,\n  highlight: true,\n  center: true,\n  responsive: true,\n  restore: true,\n  checkCrossOrigin: true,\n  checkOrientation: true,\n  scalable: true,\n  modal: true,\n  guides: true,\n  movable: true,\n  rotatable: true\n}\n\nconst props = defineProps({\n  src: propTypes.string.def(''),\n  alt: propTypes.string.def(''),\n  circled: propTypes.bool.def(false),\n  realTimePreview: propTypes.bool.def(true),\n  height: propTypes.string.def('360px'),\n  crossorigin: {\n    type: String as PropType<'' | 'anonymous' | 'use-credentials' | undefined>,\n    default: undefined\n  },\n  imageStyle: { type: Object as PropType<CSSProperties>, default: () => ({}) },\n  options: { type: Object as PropType<Options>, default: () => ({}) }\n})\n\nconst emit = defineEmits(['cropend', 'ready', 'cropendError'])\nconst attrs = useAttrs()\nconst imgElRef = ref<ElRef<HTMLImageElement>>()\nconst cropper = ref<Nullable<Cropper>>()\nconst isReady = ref(false)\n\nconst { getPrefixCls } = useDesign()\nconst prefixCls = getPrefixCls('cropper-image')\nconst debounceRealTimeCroppered = useDebounceFn(realTimeCroppered, 80)\n\nconst getImageStyle = computed((): CSSProperties => {\n  return {\n    height: props.height,\n    maxWidth: '100%',\n    ...props.imageStyle\n  }\n})\n\nconst getClass = computed(() => {\n  return [\n    prefixCls,\n    attrs.class,\n    {\n      [`${prefixCls}--circled`]: props.circled\n    }\n  ]\n})\nconst getWrapperStyle = computed((): CSSProperties => {\n  return { height: `${props.height}`.replace(/px/, '') + 'px' }\n})\n\nonMounted(init)\n\nonUnmounted(() => {\n  cropper.value?.destroy()\n})\n\nasync function init() {\n  const imgEl = unref(imgElRef)\n  if (!imgEl) {\n    return\n  }\n  cropper.value = new Cropper(imgEl, {\n    ...defaultOptions,\n    ready: () => {\n      isReady.value = true\n      realTimeCroppered()\n      emit('ready', cropper.value)\n    },\n    crop() {\n      debounceRealTimeCroppered()\n    },\n    zoom() {\n      debounceRealTimeCroppered()\n    },\n    cropmove() {\n      debounceRealTimeCroppered()\n    },\n    ...props.options\n  })\n}\n\n// Real-time display preview\nfunction realTimeCroppered() {\n  props.realTimePreview && croppered()\n}\n\n// event: return base64 and width and height information after cropping\nfunction croppered() {\n  if (!cropper.value) {\n    return\n  }\n  let imgInfo = cropper.value.getData()\n  const canvas = props.circled ? getRoundedCanvas() : cropper.value.getCroppedCanvas()\n  canvas.toBlob((blob) => {\n    if (!blob) {\n      return\n    }\n    let fileReader: FileReader = new FileReader()\n    fileReader.readAsDataURL(blob)\n    fileReader.onloadend = (e) => {\n      emit('cropend', {\n        imgBase64: e.target?.result ?? '',\n        imgInfo\n      })\n    }\n    fileReader.onerror = () => {\n      emit('cropendError')\n    }\n  }, 'image/png')\n}\n\n// Get a circular picture canvas\nfunction getRoundedCanvas() {\n  const sourceCanvas = cropper.value!.getCroppedCanvas()\n  const canvas = document.createElement('canvas')\n  const context = canvas.getContext('2d')!\n  const width = sourceCanvas.width\n  const height = sourceCanvas.height\n  canvas.width = width\n  canvas.height = height\n  context.imageSmoothingEnabled = true\n  context.drawImage(sourceCanvas, 0, 0, width, height)\n  context.globalCompositeOperation = 'destination-in'\n  context.beginPath()\n  context.arc(width / 2, height / 2, Math.min(width, height) / 2, 0, 2 * Math.PI, true)\n  context.fill()\n  return canvas\n}\n</script>\n<style lang=\"scss\">\n$prefix-cls: #{$namespace}-cropper-image;\n\n.#{$prefix-cls} {\n  &--circled {\n    .cropper-view-box,\n    .cropper-face {\n      border-radius: 50%;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Cropper/src/CropperAvatar.vue",
    "content": "<template>\n  <div class=\"user-info-head\" @click=\"open()\">\n    <el-avatar v-if=\"sourceValue\" :src=\"sourceValue\" alt=\"avatar\" class=\"img-circle img-lg\" />\n    <el-avatar v-if=\"!sourceValue\" :src=\"avatar\" alt=\"avatar\" class=\"img-circle img-lg\" />\n    <el-button v-if=\"showBtn\" :class=\"`${prefixCls}-upload-btn`\" @click=\"open()\">\n      {{ btnText ? btnText : t('cropper.selectImage') }}\n    </el-button>\n    <CopperModal\n      ref=\"cropperModelRef\"\n      :srcValue=\"sourceValue\"\n      @upload-success=\"handleUploadSuccess\"\n    />\n  </div>\n</template>\n<script lang=\"ts\" setup>\nimport { useDesign } from '@/hooks/web/useDesign'\n\nimport { propTypes } from '@/utils/propTypes'\nimport { useI18n } from 'vue-i18n'\nimport CopperModal from './CopperModal.vue'\nimport avatar from '@/assets/imgs/avatar.gif'\n\ndefineOptions({ name: 'CropperAvatar' })\n\nconst props = defineProps({\n  width: propTypes.string.def('200px'),\n  value: propTypes.string.def(''),\n  showBtn: propTypes.bool.def(true),\n  btnText: propTypes.string.def('')\n})\n\nconst emit = defineEmits(['update:value', 'change'])\nconst sourceValue = ref(props.value)\nconst { getPrefixCls } = useDesign()\nconst prefixCls = getPrefixCls('cropper-avatar')\nconst message = useMessage()\nconst { t } = useI18n()\n\nconst cropperModelRef = ref()\n\nwatchEffect(() => {\n  sourceValue.value = props.value\n})\n\nwatch(\n  () => sourceValue.value,\n  (v: string) => {\n    emit('update:value', v)\n  }\n)\n\nfunction handleUploadSuccess({ source, data, filename }) {\n  sourceValue.value = source\n  emit('change', { source, data, filename })\n  message.success(t('cropper.uploadSuccess'))\n}\n\nfunction open() {\n  cropperModelRef.value.openModal()\n}\n\nfunction close() {\n  cropperModelRef.value.closeModal()\n}\n\ndefineExpose({\n  open,\n  close\n})\n</script>\n<style lang=\"scss\" scoped>\n$prefix-cls: #{$namespace}--cropper-avatar;\n\n.#{$prefix-cls} {\n  display: inline-block;\n  text-align: center;\n\n  &-image-wrapper {\n    overflow: hidden;\n    cursor: pointer;\n    border: 1px solid;\n    border-radius: 50%;\n\n    img {\n      width: 100%;\n    }\n  }\n\n  &-image-mask {\n    position: absolute;\n    width: inherit;\n    height: inherit;\n    cursor: pointer;\n    background: rgb(0 0 0 / 40%);\n    border: inherit;\n    border-radius: inherit;\n    opacity: 0;\n    transition: opacity 0.4s;\n\n    ::v-deep(svg) {\n      margin: auto;\n    }\n  }\n\n  &-image-mask:hover {\n    opacity: 40;\n  }\n\n  &-upload-btn {\n    margin: 10px auto;\n  }\n}\n\n.user-info-head {\n  position: relative;\n  display: inline-block;\n}\n\n.img-circle {\n  border-radius: 50%;\n}\n\n.img-lg {\n  width: 120px;\n  height: 120px;\n}\n\n.user-info-head:hover::after {\n  position: absolute;\n  inset: 0;\n  font-size: 24px;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  font-style: normal;\n  line-height: 110px;\n  color: #eee;\n  cursor: pointer;\n  background: rgb(0 0 0 / 50%);\n  border-radius: 50%;\n  content: '+';\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Cropper/src/types.ts",
    "content": "import type Cropper from 'cropperjs'\n\nexport interface CropendResult {\n  imgBase64: string\n  imgInfo: Cropper.Data\n}\n\nexport type { Cropper }\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Descriptions/index.ts",
    "content": "import Descriptions from './src/Descriptions.vue'\nimport DescriptionsItemLabel from './src/DescriptionsItemLabel.vue'\n\nexport { Descriptions, DescriptionsItemLabel }\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Descriptions/src/Descriptions.vue",
    "content": "<script lang=\"ts\" setup>\nimport { PropType } from 'vue'\nimport dayjs from 'dayjs'\nimport { useDesign } from '@/hooks/web/useDesign'\nimport { propTypes } from '@/utils/propTypes'\nimport { useAppStore } from '@/store/modules/app'\nimport { DescriptionsSchema } from '@/types/descriptions'\n\ndefineOptions({ name: 'Descriptions' })\n\nconst appStore = useAppStore()\n\nconst mobile = computed(() => appStore.getMobile)\n\nconst attrs = useAttrs()\n\nconst slots = useSlots()\n\nconst props = defineProps({\n  title: propTypes.string.def(''),\n  message: propTypes.string.def(''),\n  collapse: propTypes.bool.def(true),\n  columns: propTypes.number.def(1),\n  schema: {\n    type: Array as PropType<DescriptionsSchema[]>,\n    default: () => []\n  },\n  data: {\n    type: Object as PropType<any>,\n    default: () => ({})\n  }\n})\n\nconst { getPrefixCls } = useDesign()\n\nconst prefixCls = getPrefixCls('descriptions')\n\nconst getBindValue = computed(() => {\n  const delArr: string[] = ['title', 'message', 'collapse', 'schema', 'data', 'class']\n  const obj = { ...attrs, ...props }\n  for (const key in obj) {\n    if (delArr.indexOf(key) !== -1) {\n      delete obj[key]\n    }\n  }\n  return obj\n})\n\nconst getBindItemValue = (item: DescriptionsSchema) => {\n  const delArr: string[] = ['field']\n  const obj = { ...item }\n  for (const key in obj) {\n    if (delArr.indexOf(key) !== -1) {\n      delete obj[key]\n    }\n  }\n  return obj\n}\n\n// 折叠\nconst show = ref(true)\n\nconst toggleClick = () => {\n  if (props.collapse) {\n    show.value = !unref(show)\n  }\n}\n</script>\n\n<template>\n  <div\n    :class=\"[\n      prefixCls,\n      'bg-[var(--el-color-white)] dark:bg-[var(--el-bg-color)] dark:border-[var(--el-border-color)] dark:border-1px'\n    ]\"\n  >\n    <div\n      v-if=\"title\"\n      :class=\"[\n        `${prefixCls}-header`,\n        'h-50px flex justify-between items-center b-b-1 border-solid border-[var(--el-border-color)] px-10px cursor-pointer dark:border-[var(--el-border-color)]'\n      ]\"\n      @click=\"toggleClick\"\n    >\n      <div :class=\"[`${prefixCls}-header__title`, 'relative font-18px font-bold ml-10px']\">\n        <div class=\"flex items-center\">\n          {{ title }}\n          <ElTooltip v-if=\"message\" :content=\"message\" placement=\"right\">\n            <Icon class=\"ml-5px\" icon=\"ep:warning\" />\n          </ElTooltip>\n        </div>\n      </div>\n      <Icon v-if=\"collapse\" :icon=\"show ? 'ep:arrow-down' : 'ep:arrow-up'\" />\n    </div>\n\n    <ElCollapseTransition>\n      <div v-show=\"show\" :class=\"[`${prefixCls}-content`, 'p-10px']\">\n        <ElDescriptions\n          :column=\"props.columns\"\n          :direction=\"mobile ? 'vertical' : 'horizontal'\"\n          border\n          v-bind=\"getBindValue\"\n        >\n          <template v-if=\"slots['extra']\" #extra>\n            <slot name=\"extra\"></slot>\n          </template>\n          <ElDescriptionsItem\n            v-for=\"item in schema\"\n            :key=\"item.field\"\n            min-width=\"80\"\n            v-bind=\"getBindItemValue(item)\"\n          >\n            <template #label>\n              <slot\n                :name=\"`${item.field}-label`\"\n                :row=\"{\n                  label: item.label\n                }\"\n                >{{ item.label }}\n              </slot>\n            </template>\n\n            <template #default>\n              <slot v-if=\"item.dateFormat\">\n                {{\n                  data[item.field] !== null ? dayjs(data[item.field]).format(item.dateFormat) : ''\n                }}\n              </slot>\n              <slot v-else-if=\"item.dictType\">\n                <DictTag :type=\"item.dictType\" :value=\"data[item.field] + ''\" />\n              </slot>\n              <slot v-else :name=\"item.field\" :row=\"data\">\n                {{\n                    item.mappedField ? data[item.mappedField] : data[item.field]\n                }}\n              </slot>\n            </template>\n          </ElDescriptionsItem>\n        </ElDescriptions>\n      </div>\n    </ElCollapseTransition>\n  </div>\n</template>\n\n<style lang=\"scss\" scoped>\n$prefix-cls: #{$namespace}-descriptions;\n\n.#{$prefix-cls}-header {\n  &__title {\n    &::after {\n      position: absolute;\n      top: 3px;\n      left: -10px;\n      width: 4px;\n      height: 70%;\n      background: var(--el-color-primary);\n      content: '';\n    }\n  }\n}\n\n.#{$prefix-cls}-content {\n  :deep(.#{$elNamespace}-descriptions__cell) {\n    width: 0;\n  }\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Descriptions/src/DescriptionsItemLabel.vue",
    "content": "<script setup lang=\"ts\">\nconst { label } = defineProps({\n  label: {\n    type: String,\n    required: true\n  },\n  icon: {\n    type: String,\n    required: false\n  }\n})\n</script>\n\n<template>\n  <div class=\"cell-item\">\n    <Icon :icon=\"icon\" v-if=\"icon\" style=\"vertical-align: middle\" :size=\"18\" />\n    {{ label }}\n  </div>\n</template>\n\n<style scoped lang=\"scss\">\n.cell-item {\n  display: inline;\n}\n\n.cell-item::after {\n  content: ':';\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Dialog/index.ts",
    "content": "import Dialog from './src/Dialog.vue'\n\nexport { Dialog }\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Dialog/src/Dialog.vue",
    "content": "<script lang=\"ts\" setup>\nimport { propTypes } from '@/utils/propTypes'\nimport { isNumber } from '@/utils/is'\ndefineOptions({ name: 'Dialog' })\n\nconst slots = useSlots()\n\nconst props = defineProps({\n  modelValue: propTypes.bool.def(false),\n  title: propTypes.string.def('Dialog'),\n  fullscreen: propTypes.bool.def(true),\n  width: propTypes.oneOfType([String, Number]).def('40%'),\n  scroll: propTypes.bool.def(false), // 是否开启滚动条。如果是的话，按照 maxHeight 设置最大高度\n  maxHeight: propTypes.oneOfType([String, Number]).def('400px')\n})\n\nconst getBindValue = computed(() => {\n  const delArr: string[] = ['fullscreen', 'title', 'maxHeight', 'appendToBody']\n  const attrs = useAttrs()\n  const obj = { ...attrs, ...props }\n  for (const key in obj) {\n    if (delArr.indexOf(key) !== -1) {\n      delete obj[key]\n    }\n  }\n  return obj\n})\n\nconst isFullscreen = ref(false)\n\nconst toggleFull = () => {\n  isFullscreen.value = !unref(isFullscreen)\n}\n\nconst dialogHeight = ref(isNumber(props.maxHeight) ? `${props.maxHeight}px` : props.maxHeight)\n\nwatch(\n  () => isFullscreen.value,\n  async (val: boolean) => {\n    await nextTick()\n    if (val) {\n      const windowHeight = document.documentElement.offsetHeight\n      dialogHeight.value = `${windowHeight - 55 - 60 - (slots.footer ? 63 : 0)}px`\n    } else {\n      dialogHeight.value = isNumber(props.maxHeight) ? `${props.maxHeight}px` : props.maxHeight\n    }\n  },\n  {\n    immediate: true\n  }\n)\n\nconst dialogStyle = computed(() => {\n  return {\n    height: unref(dialogHeight)\n  }\n})\n</script>\n\n<template>\n  <ElDialog\n    v-bind=\"getBindValue\"\n    :close-on-click-modal=\"true\"\n    :fullscreen=\"isFullscreen\"\n    :width=\"width\"\n    destroy-on-close\n    lock-scroll\n    draggable\n    class=\"com-dialog\"\n    :show-close=\"false\"\n  >\n    <template #header=\"{ close }\">\n      <div class=\"relative h-54px flex items-center justify-between pl-15px pr-15px\">\n        <slot name=\"title\">\n          {{ title }}\n        </slot>\n        <div\n          class=\"absolute right-15px top-[50%] h-54px flex translate-y-[-50%] items-center justify-between\"\n        >\n          <Icon\n            v-if=\"fullscreen\"\n            class=\"is-hover mr-10px cursor-pointer\"\n            :icon=\"isFullscreen ? 'radix-icons:exit-full-screen' : 'radix-icons:enter-full-screen'\"\n            color=\"var(--el-color-info)\"\n            hover-color=\"var(--el-color-primary)\"\n            @click=\"toggleFull\"\n          />\n          <Icon\n            class=\"is-hover cursor-pointer\"\n            icon=\"ep:close\"\n            hover-color=\"var(--el-color-primary)\"\n            color=\"var(--el-color-info)\"\n            @click=\"close\"\n          />\n        </div>\n      </div>\n    </template>\n\n    <ElScrollbar v-if=\"scroll\" :style=\"dialogStyle\">\n      <slot></slot>\n    </ElScrollbar>\n    <slot v-else></slot>\n    <template v-if=\"slots.footer\" #footer>\n      <slot name=\"footer\"></slot>\n    </template>\n  </ElDialog>\n</template>\n\n<style lang=\"scss\">\n.com-dialog {\n  .#{$elNamespace}-overlay-dialog {\n    display: flex;\n    justify-content: center;\n    align-items: center;\n  }\n\n  .#{$elNamespace}-dialog {\n    margin: 0 !important;\n\n    &__header {\n      height: 54px;\n      padding: 0;\n      margin-right: 0 !important;\n      border-bottom: 1px solid var(--el-border-color);\n    }\n\n    &__body {\n      padding: 15px !important;\n    }\n\n    &__footer {\n      border-top: 1px solid var(--el-border-color);\n    }\n\n    &__headerbtn {\n      top: 0;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/DictTag/index.ts",
    "content": "import DictTag from './src/DictTag.vue'\n\nexport { DictTag }\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/DictTag/src/DictTag.vue",
    "content": "<script lang=\"tsx\">\nimport { defineComponent, PropType, ref } from 'vue'\nimport { isHexColor } from '@/utils/color'\nimport { ElTag } from 'element-plus'\nimport { DictDataType, getDictOptions } from '@/utils/dict'\n\nexport default defineComponent({\n  name: 'DictTag',\n  props: {\n    type: {\n      type: String as PropType<string>,\n      required: true\n    },\n    value: {\n      type: [String, Number, Boolean] as PropType<string | number | boolean>,\n      required: true\n    }\n  },\n  setup(props) {\n    const dictData = ref<DictDataType>()\n    const getDictObj = (dictType: string, value: string) => {\n      const dictOptions = getDictOptions(dictType)\n      dictOptions.forEach((dict: DictDataType) => {\n        if (dict.value === value) {\n          if (dict.colorType + '' === 'primary' || dict.colorType + '' === 'default') {\n            dict.colorType = ''\n          }\n          dictData.value = dict\n        }\n      })\n    }\n    const rederDictTag = () => {\n      if (!props.type) {\n        return null\n      }\n      // 解决自定义字典标签值为零时标签不渲染的问题\n      if (props.value === undefined || props.value === null) {\n        return null\n      }\n      getDictObj(props.type, props.value.toString())\n      // 添加标签的文字颜色为白色，解决自定义背景颜色时标签文字看不清的问题\n      return (\n        <ElTag\n          style={dictData.value?.cssClass ? 'color: #fff' : ''}\n          type={dictData.value?.colorType}\n          color={\n            dictData.value?.cssClass && isHexColor(dictData.value?.cssClass)\n              ? dictData.value?.cssClass\n              : ''\n          }\n          disableTransitions={true}\n        >\n          {dictData.value?.label}\n        </ElTag>\n      )\n    }\n    return () => rederDictTag()\n  }\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/DocAlert/index.vue",
    "content": "<template>\n  <el-alert v-if=\"getEnable()\" type=\"success\" show-icon>\n    <template #title>\n      <div @click=\"goToUrl\">{{ '【' + title + '】文档地址：' + url }}</div>\n    </template>\n  </el-alert>\n</template>\n<script setup lang=\"tsx\">\nimport { propTypes } from '@/utils/propTypes'\n\ndefineOptions({ name: 'DocAlert' })\n\nconst props = defineProps({\n  title: propTypes.string,\n  url: propTypes.string\n})\n\n/** 跳转 URL 链接 */\nconst goToUrl = () => {\n  window.open(props.url)\n}\n\n/** 是否开启 */\nconst getEnable = () => {\n  return import.meta.env.VITE_APP_DOCALERT_ENABLE !== 'false'\n}\n</script>\n<style scoped>\n.el-alert--success.is-light {\n  margin-bottom: 10px;\n  cursor: pointer;\n  border: 1px solid green;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Draggable/index.vue",
    "content": "<template>\n  <el-text type=\"info\" size=\"small\"> 拖动左上角的小圆点可对其排序 </el-text>\n  <VueDraggable\n    :list=\"formData\"\n    :force-fallback=\"true\"\n    :animation=\"200\"\n    handle=\".drag-icon\"\n    class=\"m-t-8px\"\n    item-key=\"index\"\n  >\n    <template #item=\"{ element, index }\">\n      <div\n        class=\"mb-4px flex flex-col gap-4px border border-gray-2 border-rounded rounded border-solid p-8px\"\n      >\n        <!-- 操作按钮区 -->\n        <div class=\"m--8px m-b-4px flex flex-row items-center justify-between bg-gray-1 p-8px\">\n          <el-tooltip content=\"拖动排序\">\n            <Icon icon=\"ic:round-drag-indicator\" class=\"drag-icon cursor-move\" />\n          </el-tooltip>\n          <el-tooltip content=\"删除\">\n            <Icon\n              icon=\"ep:delete\"\n              class=\"cursor-pointer text-red-5\"\n              v-if=\"formData.length > 1\"\n              @click=\"handleDelete(index)\"\n            />\n          </el-tooltip>\n        </div>\n        <!-- 内容区 -->\n        <slot :element=\"element\" :index=\"index\"></slot>\n      </div>\n    </template>\n  </VueDraggable>\n  <el-tooltip :disabled=\"limit < 1\" :content=\"`最多添加${limit}个`\">\n    <el-button\n      type=\"primary\"\n      plain\n      class=\"m-t-4px w-full\"\n      :disabled=\"limit > 0 && formData.length >= limit\"\n      @click=\"handleAdd\"\n    >\n      <Icon icon=\"ep:plus\" /><span>添加</span>\n    </el-button>\n  </el-tooltip>\n</template>\n\n<script setup lang=\"ts\">\n// 拖拽组件\nimport VueDraggable from 'vuedraggable'\nimport { usePropertyForm } from '@/components/DiyEditor/util'\nimport { any, array } from 'vue-types'\nimport { propTypes } from '@/utils/propTypes'\nimport { cloneDeep } from 'lodash-es'\n\n// 拖拽组件封装\ndefineOptions({ name: 'Draggable' })\n\n// 定义属性\nconst props = defineProps({\n  // 绑定值\n  modelValue: array<any>().isRequired,\n  // 空的元素：点击添加按钮时，创建元素并添加到列表；默认为空对象\n  emptyItem: any<unknown>().def({}),\n  // 数量限制：默认为0，表示不限制\n  limit: propTypes.number.def(0)\n})\n// 定义事件\nconst emit = defineEmits(['update:modelValue'])\nconst { formData } = usePropertyForm(props.modelValue, emit)\n\n// 处理添加\nconst handleAdd = () => formData.value.push(cloneDeep(props.emptyItem || {}))\n// 处理删除\nconst handleDelete = (index: number) => formData.value.splice(index, 1)\n</script>\n\n<style scoped lang=\"scss\"></style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Echart/index.ts",
    "content": "import Echart from './src/Echart.vue'\n\nexport { Echart }\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Echart/src/Echart.vue",
    "content": "<script lang=\"ts\" setup>\nimport type { EChartsOption } from 'echarts'\nimport echarts from '@/plugins/echarts'\nimport { debounce } from 'lodash-es'\nimport 'echarts-wordcloud'\nimport { propTypes } from '@/utils/propTypes'\nimport { PropType } from 'vue'\nimport { useAppStore } from '@/store/modules/app'\nimport { isString } from '@/utils/is'\nimport { useDesign } from '@/hooks/web/useDesign'\n\ndefineOptions({ name: 'EChart' })\n\nconst { getPrefixCls, variables } = useDesign()\n\nconst prefixCls = getPrefixCls('echart')\n\nconst appStore = useAppStore()\n\nconst props = defineProps({\n  options: {\n    type: Object as PropType<EChartsOption>,\n    required: true\n  },\n  width: propTypes.oneOfType([Number, String]).def(''),\n  height: propTypes.oneOfType([Number, String]).def('500px')\n})\n\nconst isDark = computed(() => appStore.getIsDark)\n\nconst theme = computed(() => {\n  const echartTheme: boolean | string = unref(isDark) ? true : 'auto'\n\n  return echartTheme\n})\n\nconst options = computed(() => {\n  return Object.assign(props.options, {\n    darkMode: unref(theme)\n  })\n})\n\nconst elRef = ref<ElRef>()\n\nlet echartRef: Nullable<echarts.ECharts> = null\n\nconst contentEl = ref<Element>()\n\nconst styles = computed(() => {\n  const width = isString(props.width) ? props.width : `${props.width}px`\n  const height = isString(props.height) ? props.height : `${props.height}px`\n\n  return {\n    width,\n    height\n  }\n})\n\nconst initChart = () => {\n  if (unref(elRef) && props.options) {\n    echartRef = echarts.init(unref(elRef) as HTMLElement)\n    echartRef?.setOption(unref(options))\n  }\n}\n\nwatch(\n  () => options.value,\n  (options) => {\n    if (echartRef) {\n      echartRef?.setOption(options)\n    }\n  },\n  {\n    deep: true\n  }\n)\n\nconst resizeHandler = debounce(() => {\n  if (echartRef) {\n    echartRef.resize()\n  }\n}, 100)\n\nconst contentResizeHandler = async (e: TransitionEvent) => {\n  if (e.propertyName === 'width') {\n    resizeHandler()\n  }\n}\n\nonMounted(() => {\n  initChart()\n\n  window.addEventListener('resize', resizeHandler)\n\n  contentEl.value = document.getElementsByClassName(`${variables.namespace}-layout-content`)[0]\n  unref(contentEl) &&\n    (unref(contentEl) as Element).addEventListener('transitionend', contentResizeHandler)\n})\n\nonBeforeUnmount(() => {\n  window.removeEventListener('resize', resizeHandler)\n  unref(contentEl) &&\n    (unref(contentEl) as Element).removeEventListener('transitionend', contentResizeHandler)\n})\n\nonActivated(() => {\n  if (echartRef) {\n    echartRef.resize()\n  }\n})\n</script>\n\n<template>\n  <div ref=\"elRef\" :class=\"[$attrs.class, prefixCls]\" :style=\"styles\"></div>\n</template>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Editor/index.ts",
    "content": "import Editor from './src/Editor.vue'\nimport { IDomEditor } from '@wangeditor/editor'\n\nexport interface EditorExpose {\n  getEditorRef: () => Promise<IDomEditor>\n}\n\nexport { Editor }\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Editor/src/Editor.vue",
    "content": "<script lang=\"ts\" setup>\nimport { PropType } from 'vue'\nimport { Editor, Toolbar } from '@wangeditor/editor-for-vue'\nimport { i18nChangeLanguage, IDomEditor, IEditorConfig } from '@wangeditor/editor'\nimport { propTypes } from '@/utils/propTypes'\nimport { isNumber } from '@/utils/is'\nimport { ElMessage } from 'element-plus'\nimport { useLocaleStore } from '@/store/modules/locale'\nimport { getAccessToken, getTenantId } from '@/utils/auth'\n\ndefineOptions({ name: 'Editor' })\n\ntype InsertFnType = (url: string, alt: string, href: string) => void\n\nconst localeStore = useLocaleStore()\n\nconst currentLocale = computed(() => localeStore.getCurrentLocale)\n\ni18nChangeLanguage(unref(currentLocale).lang)\n\nconst props = defineProps({\n  editorId: propTypes.string.def('wangeEditor-1'),\n  height: propTypes.oneOfType([Number, String]).def('500px'),\n  editorConfig: {\n    type: Object as PropType<Partial<IEditorConfig>>,\n    default: () => undefined\n  },\n  readonly: propTypes.bool.def(false),\n  modelValue: propTypes.string.def('')\n})\n\nconst emit = defineEmits(['change', 'update:modelValue'])\n\n// 编辑器实例，必须用 shallowRef\nconst editorRef = shallowRef<IDomEditor>()\n\nconst valueHtml = ref('')\n\nwatch(\n  () => props.modelValue,\n  (val: string) => {\n    if (val === unref(valueHtml)) return\n    valueHtml.value = val\n  },\n  {\n    immediate: true\n  }\n)\n\n// 监听\nwatch(\n  () => valueHtml.value,\n  (val: string) => {\n    emit('update:modelValue', val)\n  }\n)\n\nconst handleCreated = (editor: IDomEditor) => {\n  editorRef.value = editor\n}\n\n// 编辑器配置\nconst editorConfig = computed((): IEditorConfig => {\n  return Object.assign(\n    {\n      placeholder: '请输入内容...',\n      readOnly: props.readonly,\n      customAlert: (s: string, t: string) => {\n        switch (t) {\n          case 'success':\n            ElMessage.success(s)\n            break\n          case 'info':\n            ElMessage.info(s)\n            break\n          case 'warning':\n            ElMessage.warning(s)\n            break\n          case 'error':\n            ElMessage.error(s)\n            break\n          default:\n            ElMessage.info(s)\n            break\n        }\n      },\n      autoFocus: false,\n      scroll: true,\n      MENU_CONF: {\n        ['uploadImage']: {\n          server: import.meta.env.VITE_UPLOAD_URL,\n          // 单个文件的最大体积限制，默认为 2M\n          maxFileSize: 5 * 1024 * 1024,\n          // 最多可上传几个文件，默认为 100\n          maxNumberOfFiles: 10,\n          // 选择文件时的类型限制，默认为 ['image/*'] 。如不想限制，则设置为 []\n          allowedFileTypes: ['image/*'],\n\n          // 自定义上传参数，例如传递验证的 token 等。参数会被添加到 formData 中，一起上传到服务端。\n          meta: { updateSupport: 0 },\n          // 将 meta 拼接到 url 参数中，默认 false\n          metaWithUrl: true,\n\n          // 自定义增加 http  header\n          headers: {\n            Accept: '*',\n            Authorization: 'Bearer ' + getAccessToken(),\n            'tenant-id': getTenantId()\n          },\n\n          // 跨域是否传递 cookie ，默认为 false\n          withCredentials: true,\n\n          // 超时时间，默认为 10 秒\n          timeout: 5 * 1000, // 5 秒\n\n          // form-data fieldName，后端接口参数名称，默认值wangeditor-uploaded-image\n          fieldName: 'file',\n\n          // 上传之前触发\n          onBeforeUpload(file: File) {\n            console.log(file)\n            return file\n          },\n          // 上传进度的回调函数\n          onProgress(progress: number) {\n            // progress 是 0-100 的数字\n            console.log('progress', progress)\n          },\n          onSuccess(file: File, res: any) {\n            console.log('onSuccess', file, res)\n          },\n          onFailed(file: File, res: any) {\n            alert(res.message)\n            console.log('onFailed', file, res)\n          },\n          onError(file: File, err: any, res: any) {\n            alert(err.message)\n            console.error('onError', file, err, res)\n          },\n          // 自定义插入图片\n          customInsert(res: any, insertFn: InsertFnType) {\n            insertFn(res.data, 'image', res.data)\n          }\n        }\n      },\n      uploadImgShowBase64: true\n    },\n    props.editorConfig || {}\n  )\n})\n\nconst editorStyle = computed(() => {\n  return {\n    height: isNumber(props.height) ? `${props.height}px` : props.height\n  }\n})\n\n// 回调函数\nconst handleChange = (editor: IDomEditor) => {\n  emit('change', editor)\n}\n\n// 组件销毁时，及时销毁编辑器\nonBeforeUnmount(() => {\n  const editor = unref(editorRef.value)\n\n  // 销毁，并移除 editor\n  editor?.destroy()\n})\n\nconst getEditorRef = async (): Promise<IDomEditor> => {\n  await nextTick()\n  return unref(editorRef.value) as IDomEditor\n}\n\ndefineExpose({\n  getEditorRef\n})\n</script>\n\n<template>\n  <div class=\"border-1 border-solid border-[var(--tags-view-border-color)] z-10\">\n    <!-- 工具栏 -->\n    <Toolbar\n      :editor=\"editorRef\"\n      :editorId=\"editorId\"\n      class=\"border-0 b-b-1 border-solid border-[var(--tags-view-border-color)]\"\n    />\n    <!-- 编辑器 -->\n    <Editor\n      v-model=\"valueHtml\"\n      :defaultConfig=\"editorConfig\"\n      :editorId=\"editorId\"\n      :style=\"editorStyle\"\n      @on-change=\"handleChange\"\n      @on-created=\"handleCreated\"\n    />\n  </div>\n</template>\n\n<style src=\"@wangeditor/editor/dist/css/style.css\"></style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Error/index.ts",
    "content": "import Error from './src/Error.vue'\n\nexport { Error }\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Error/src/Error.vue",
    "content": "<script lang=\"ts\" setup>\nimport pageError from '@/assets/svgs/404.svg'\nimport networkError from '@/assets/svgs/500.svg'\nimport noPermission from '@/assets/svgs/403.svg'\nimport { propTypes } from '@/utils/propTypes'\n\ndefineOptions({ name: 'Error' })\n\ninterface ErrorMap {\n  url: string\n  message: string\n  buttonText: string\n}\n\nconst { t } = useI18n()\n\nconst errorMap: {\n  [key: string]: ErrorMap\n} = {\n  '404': {\n    url: pageError,\n    message: t('error.pageError'),\n    buttonText: t('error.returnToHome')\n  },\n  '500': {\n    url: networkError,\n    message: t('error.networkError'),\n    buttonText: t('error.returnToHome')\n  },\n  '403': {\n    url: noPermission,\n    message: t('error.noPermission'),\n    buttonText: t('error.returnToHome')\n  }\n}\n\nconst props = defineProps({\n  type: propTypes.string.validate((v: string) => ['404', '500', '403'].includes(v)).def('404')\n})\n\nconst emit = defineEmits(['errorClick'])\n\nconst btnClick = () => {\n  emit('errorClick', props.type)\n}\n</script>\n\n<template>\n  <div class=\"flex justify-center\">\n    <div class=\"text-center\">\n      <img :src=\"errorMap[type].url\" alt=\"\" width=\"350\" />\n      <div class=\"text-14px text-[var(--el-color-info)]\">{{ errorMap[type].message }}</div>\n      <div class=\"mt-20px\">\n        <ElButton type=\"primary\" @click=\"btnClick\">{{ errorMap[type].buttonText }}</ElButton>\n      </div>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Form/index.ts",
    "content": "import Form from './src/Form.vue'\nimport { ElForm } from 'element-plus'\nimport { FormSchema, FormSetPropsType } from '@/types/form'\n\nexport interface FormExpose {\n  setValues: (data: Recordable) => void\n  setProps: (props: Recordable) => void\n  delSchema: (field: string) => void\n  addSchema: (formSchema: FormSchema, index?: number) => void\n  setSchema: (schemaProps: FormSetPropsType[]) => void\n  formModel: Recordable\n  getElFormRef: () => ComponentRef<typeof ElForm>\n}\n\nexport { Form }\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Form/src/Form.vue",
    "content": "<script lang=\"tsx\">\nimport { computed, defineComponent, onMounted, PropType, ref, unref, watch } from 'vue'\nimport { ElCol, ElForm, ElFormItem, ElRow, ElTooltip } from 'element-plus'\nimport { componentMap } from './componentMap'\nimport { propTypes } from '@/utils/propTypes'\nimport { getSlot } from '@/utils/tsxHelper'\nimport {\n  initModel,\n  setComponentProps,\n  setFormItemSlots,\n  setGridProp,\n  setItemComponentSlots,\n  setTextPlaceholder\n} from './helper'\nimport { useRenderSelect } from './components/useRenderSelect'\nimport { useRenderRadio } from './components/useRenderRadio'\nimport { useRenderCheckbox } from './components/useRenderCheckbox'\nimport { useDesign } from '@/hooks/web/useDesign'\nimport { findIndex } from '@/utils'\nimport { set } from 'lodash-es'\nimport { FormProps } from './types'\nimport { Icon } from '@/components/Icon'\nimport { FormSchema, FormSetPropsType } from '@/types/form'\n\nconst { getPrefixCls } = useDesign()\n\nconst prefixCls = getPrefixCls('form')\n\nexport default defineComponent({\n  // eslint-disable-next-line vue/no-reserved-component-names\n  name: 'Form',\n  props: {\n    // 生成Form的布局结构数组\n    schema: {\n      type: Array as PropType<FormSchema[]>,\n      default: () => []\n    },\n    // 是否需要栅格布局\n    // update by 芋艿：将 true 改成 false，因为项目更常用这种方式\n    isCol: propTypes.bool.def(false),\n    // 表单数据对象\n    model: {\n      type: Object as PropType<Recordable>,\n      default: () => ({})\n    },\n    // 是否自动设置placeholder\n    autoSetPlaceholder: propTypes.bool.def(true),\n    // 是否自定义内容\n    isCustom: propTypes.bool.def(false),\n    // 表单label宽度\n    labelWidth: propTypes.oneOfType([String, Number]).def('auto'),\n    // 是否 loading 数据中 add by 芋艿\n    vLoading: propTypes.bool.def(false)\n  },\n  emits: ['register'],\n  setup(props, { slots, expose, emit }) {\n    // element form 实例\n    const elFormRef = ref<ComponentRef<typeof ElForm>>()\n\n    // useForm传入的props\n    const outsideProps = ref<FormProps>({})\n\n    const mergeProps = ref<FormProps>({})\n\n    const getProps = computed(() => {\n      const propsObj = { ...props }\n      Object.assign(propsObj, unref(mergeProps))\n      return propsObj\n    })\n\n    // 表单数据\n    const formModel = ref<Recordable>({})\n\n    onMounted(() => {\n      emit('register', unref(elFormRef)?.$parent, unref(elFormRef))\n    })\n\n    // 对表单赋值\n    const setValues = (data: Recordable = {}) => {\n      formModel.value = Object.assign(unref(formModel), data)\n    }\n\n    const setProps = (props: FormProps = {}) => {\n      mergeProps.value = Object.assign(unref(mergeProps), props)\n      outsideProps.value = props\n    }\n\n    const delSchema = (field: string) => {\n      const { schema } = unref(getProps)\n\n      const index = findIndex(schema, (v: FormSchema) => v.field === field)\n      if (index > -1) {\n        schema.splice(index, 1)\n      }\n    }\n\n    const addSchema = (formSchema: FormSchema, index?: number) => {\n      const { schema } = unref(getProps)\n      if (index !== void 0) {\n        schema.splice(index, 0, formSchema)\n        return\n      }\n      schema.push(formSchema)\n    }\n\n    const setSchema = (schemaProps: FormSetPropsType[]) => {\n      const { schema } = unref(getProps)\n      for (const v of schema) {\n        for (const item of schemaProps) {\n          if (v.field === item.field) {\n            set(v, item.path, item.value)\n          }\n        }\n      }\n    }\n\n    const getElFormRef = (): ComponentRef<typeof ElForm> => {\n      return unref(elFormRef) as ComponentRef<typeof ElForm>\n    }\n\n    expose({\n      setValues,\n      formModel,\n      setProps,\n      delSchema,\n      addSchema,\n      setSchema,\n      getElFormRef\n    })\n\n    // 监听表单结构化数组，重新生成formModel\n    watch(\n      () => unref(getProps).schema,\n      (schema = []) => {\n        formModel.value = initModel(schema, unref(formModel))\n      },\n      {\n        immediate: true,\n        deep: true\n      }\n    )\n\n    // 渲染包裹标签，是否使用栅格布局\n    const renderWrap = () => {\n      const { isCol } = unref(getProps)\n      const content = isCol ? (\n        <ElRow gutter={20}>{renderFormItemWrap()}</ElRow>\n      ) : (\n        renderFormItemWrap()\n      )\n      return content\n    }\n\n    // 是否要渲染el-col\n    const renderFormItemWrap = () => {\n      // hidden属性表示隐藏，不做渲染\n      const { schema = [], isCol } = unref(getProps)\n\n      return schema\n        .filter((v) => !v.hidden)\n        .map((item) => {\n          // 如果是 Divider 组件，需要自己占用一行\n          const isDivider = item.component === 'Divider'\n          const Com = componentMap['Divider'] as ReturnType<typeof defineComponent>\n          return isDivider ? (\n            <Com {...{ contentPosition: 'left', ...item.componentProps }}>{item?.label}</Com>\n          ) : isCol ? (\n            // 如果需要栅格，需要包裹 ElCol\n            <ElCol {...setGridProp(item.colProps)}>{renderFormItem(item)}</ElCol>\n          ) : (\n            renderFormItem(item)\n          )\n        })\n    }\n\n    // 渲染formItem\n    const renderFormItem = (item: FormSchema) => {\n      // 单独给只有options属性的组件做判断\n      const notRenderOptions = ['SelectV2', 'Cascader', 'Transfer']\n      const slotsMap: Recordable = {\n        ...setItemComponentSlots(slots, item?.componentProps?.slots, item.field)\n      }\n      if (\n        item?.component !== 'SelectV2' &&\n        item?.component !== 'Cascader' &&\n        item?.componentProps?.options\n      ) {\n        slotsMap.default = () => renderOptions(item)\n      }\n\n      const formItemSlots: Recordable = setFormItemSlots(slots, item.field)\n      // 如果有 labelMessage，自动使用插槽渲染\n      if (item?.labelMessage) {\n        formItemSlots.label = () => {\n          return (\n            <>\n              <span>{item.label}</span>\n              <ElTooltip placement=\"right\" raw-content>\n                {{\n                  content: () => <span v-dompurify-html={item.labelMessage}></span>,\n                  default: () => (\n                    <Icon\n                      icon=\"ep:warning\"\n                      size={16}\n                      color=\"var(--el-color-primary)\"\n                      class=\"relative top-1px ml-2px\"\n                    ></Icon>\n                  )\n                }}\n              </ElTooltip>\n            </>\n          )\n        }\n      }\n      return (\n        <ElFormItem {...(item.formItemProps || {})} prop={item.field} label={item.label || ''}>\n          {{\n            ...formItemSlots,\n            default: () => {\n              const Com = componentMap[item.component as string] as ReturnType<\n                typeof defineComponent\n              >\n\n              const { autoSetPlaceholder } = unref(getProps)\n\n              return slots[item.field] ? (\n                getSlot(slots, item.field, formModel.value)\n              ) : (\n                <Com\n                  vModel={formModel.value[item.field]}\n                  {...(autoSetPlaceholder && setTextPlaceholder(item))}\n                  {...setComponentProps(item)}\n                  style={item.componentProps?.style}\n                  {...(notRenderOptions.includes(item?.component as string) &&\n                  item?.componentProps?.options\n                    ? { options: item?.componentProps?.options || [] }\n                    : {})}\n                >\n                  {{ ...slotsMap }}\n                </Com>\n              )\n            }\n          }}\n        </ElFormItem>\n      )\n    }\n\n    // 渲染options\n    const renderOptions = (item: FormSchema) => {\n      switch (item.component) {\n        case 'Select':\n        case 'SelectV2':\n          const { renderSelectOptions } = useRenderSelect(slots)\n          return renderSelectOptions(item)\n        case 'Radio':\n        case 'RadioButton':\n          const { renderRadioOptions } = useRenderRadio()\n          return renderRadioOptions(item)\n        case 'Checkbox':\n        case 'CheckboxButton':\n          const { renderCheckboxOptions } = useRenderCheckbox()\n          return renderCheckboxOptions(item)\n        default:\n          break\n      }\n    }\n\n    // 过滤传入Form组件的属性\n    const getFormBindValue = () => {\n      // 避免在标签上出现多余的属性\n      const delKeys = ['schema', 'isCol', 'autoSetPlaceholder', 'isCustom', 'model']\n      const props = { ...unref(getProps) }\n      for (const key in props) {\n        if (delKeys.indexOf(key) !== -1) {\n          delete props[key]\n        }\n      }\n      return props\n    }\n\n    return () => (\n      <ElForm\n        ref={elFormRef}\n        {...getFormBindValue()}\n        model={props.isCustom ? props.model : formModel}\n        class={prefixCls}\n        v-loading={props.vLoading}\n      >\n        {{\n          // 如果需要自定义，就什么都不渲染，而是提供默认插槽\n          default: () => {\n            const { isCustom } = unref(getProps)\n            return isCustom ? getSlot(slots, 'default') : renderWrap()\n          }\n        }}\n      </ElForm>\n    )\n  }\n})\n</script>\n\n<style lang=\"scss\" scoped>\n.#{$elNamespace}-form.#{$namespace}-form .#{$elNamespace}-row {\n  margin-right: 0 !important;\n  margin-left: 0 !important;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Form/src/componentMap.ts",
    "content": "import type { Component } from 'vue'\nimport {\n  ElCascader,\n  ElCheckboxGroup,\n  ElColorPicker,\n  ElDatePicker,\n  ElInput,\n  ElInputNumber,\n  ElRadioGroup,\n  ElRate,\n  ElSelect,\n  ElSelectV2,\n  ElTreeSelect,\n  ElSlider,\n  ElSwitch,\n  ElTimePicker,\n  ElTimeSelect,\n  ElTransfer,\n  ElAutocomplete,\n  ElDivider\n} from 'element-plus'\nimport { InputPassword } from '@/components/InputPassword'\nimport { Editor } from '@/components/Editor'\nimport { UploadImg, UploadImgs, UploadFile } from '@/components/UploadFile'\nimport { ComponentName } from '@/types/components'\n\nconst componentMap: Recordable<Component, ComponentName> = {\n  Radio: ElRadioGroup,\n  Checkbox: ElCheckboxGroup,\n  CheckboxButton: ElCheckboxGroup,\n  Input: ElInput,\n  Autocomplete: ElAutocomplete,\n  InputNumber: ElInputNumber,\n  Select: ElSelect,\n  Cascader: ElCascader,\n  Switch: ElSwitch,\n  Slider: ElSlider,\n  TimePicker: ElTimePicker,\n  DatePicker: ElDatePicker,\n  Rate: ElRate,\n  ColorPicker: ElColorPicker,\n  Transfer: ElTransfer,\n  Divider: ElDivider,\n  TimeSelect: ElTimeSelect,\n  SelectV2: ElSelectV2,\n  TreeSelect: ElTreeSelect,\n  RadioButton: ElRadioGroup,\n  InputPassword: InputPassword,\n  Editor: Editor,\n  UploadImg: UploadImg,\n  UploadImgs: UploadImgs,\n  UploadFile: UploadFile\n}\n\nexport { componentMap }\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Form/src/components/useRenderCheckbox.tsx",
    "content": "import { FormSchema } from '@/types/form'\nimport { ElCheckbox, ElCheckboxButton } from 'element-plus'\nimport { defineComponent } from 'vue'\n\nexport const useRenderCheckbox = () => {\n  const renderCheckboxOptions = (item: FormSchema) => {\n    // 如果有别名，就取别名\n    const labelAlias = item?.componentProps?.optionsAlias?.labelField\n    const valueAlias = item?.componentProps?.optionsAlias?.valueField\n    const Com = (item.component === 'Checkbox' ? ElCheckbox : ElCheckboxButton) as ReturnType<\n      typeof defineComponent\n    >\n    return item?.componentProps?.options?.map((option) => {\n      const { ...other } = option\n      return (\n        <Com {...other} label={option[valueAlias || 'value']}>\n          {option[labelAlias || 'label']}\n        </Com>\n      )\n    })\n  }\n\n  return {\n    renderCheckboxOptions\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Form/src/components/useRenderRadio.tsx",
    "content": "import { FormSchema } from '@/types/form'\nimport { ElRadio, ElRadioButton } from 'element-plus'\nimport { defineComponent } from 'vue'\n\nexport const useRenderRadio = () => {\n  const renderRadioOptions = (item: FormSchema) => {\n    // 如果有别名，就取别名\n    const labelAlias = item?.componentProps?.optionsAlias?.labelField\n    const valueAlias = item?.componentProps?.optionsAlias?.valueField\n    const Com = (item.component === 'Radio' ? ElRadio : ElRadioButton) as ReturnType<\n      typeof defineComponent\n    >\n    return item?.componentProps?.options?.map((option) => {\n      const { ...other } = option\n      return (\n        <Com {...other} label={option[valueAlias || 'value']}>\n          {option[labelAlias || 'label']}\n        </Com>\n      )\n    })\n  }\n\n  return {\n    renderRadioOptions\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Form/src/components/useRenderSelect.tsx",
    "content": "import { FormSchema } from '@/types/form'\nimport { ComponentOptions } from '@/types/components'\nimport { ElOption, ElOptionGroup } from 'element-plus'\nimport { getSlot } from '@/utils/tsxHelper'\nimport { Slots } from 'vue'\n\nexport const useRenderSelect = (slots: Slots) => {\n  // 渲染 select options\n  const renderSelectOptions = (item: FormSchema) => {\n    // 如果有别名，就取别名\n    const labelAlias = item?.componentProps?.optionsAlias?.labelField\n    return item?.componentProps?.options?.map((option) => {\n      if (option?.options?.length) {\n        return (\n          <ElOptionGroup label={option[labelAlias || 'label']}>\n            {() => {\n              return option?.options?.map((v) => {\n                return renderSelectOptionItem(item, v)\n              })\n            }}\n          </ElOptionGroup>\n        )\n      } else {\n        return renderSelectOptionItem(item, option)\n      }\n    })\n  }\n\n  // 渲染 select option item\n  const renderSelectOptionItem = (item: FormSchema, option: ComponentOptions) => {\n    // 如果有别名，就取别名\n    const labelAlias = item?.componentProps?.optionsAlias?.labelField\n    const valueAlias = item?.componentProps?.optionsAlias?.valueField\n\n    const { label, value, ...other } = option\n\n    return (\n      <ElOption\n        {...other}\n        label={labelAlias ? option[labelAlias] : label}\n        value={valueAlias ? option[valueAlias] : value}\n      >\n        {{\n          default: () =>\n            // option 插槽名规则，{field}-option\n            item?.componentProps?.optionsSlot\n              ? getSlot(slots, `${item.field}-option`, { item: option })\n              : undefined\n        }}\n      </ElOption>\n    )\n  }\n\n  return {\n    renderSelectOptions\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Form/src/helper.ts",
    "content": "import type { Slots } from 'vue'\nimport { getSlot } from '@/utils/tsxHelper'\nimport { PlaceholderModel } from './types'\nimport { FormSchema } from '@/types/form'\nimport { ColProps } from '@/types/components'\n\n/**\n *\n * @param schema 对应组件数据\n * @returns 返回提示信息对象\n * @description 用于自动设置placeholder\n */\nexport const setTextPlaceholder = (schema: FormSchema): PlaceholderModel => {\n  const { t } = useI18n()\n  const textMap = ['Input', 'Autocomplete', 'InputNumber', 'InputPassword']\n  const selectMap = ['Select', 'SelectV2', 'TimePicker', 'DatePicker', 'TimeSelect', 'TimeSelect']\n  if (textMap.includes(schema?.component as string)) {\n    return {\n      placeholder: t('common.inputText') + schema.label\n    }\n  }\n  if (selectMap.includes(schema?.component as string)) {\n    // 一些范围选择器\n    const twoTextMap = ['datetimerange', 'daterange', 'monthrange', 'datetimerange', 'daterange']\n    if (\n      twoTextMap.includes(\n        (schema?.componentProps?.type || schema?.componentProps?.isRange) as string\n      )\n    ) {\n      return {\n        startPlaceholder: t('common.startTimeText'),\n        endPlaceholder: t('common.endTimeText'),\n        rangeSeparator: '-'\n      }\n    } else {\n      return {\n        placeholder: t('common.selectText') + schema.label\n      }\n    }\n  }\n  return {}\n}\n\n/**\n *\n * @param col 内置栅格\n * @returns 返回栅格属性\n * @description 合并传入进来的栅格属性\n */\nexport const setGridProp = (col: ColProps = {}): ColProps => {\n  const colProps: ColProps = {\n    // 如果有span，代表用户优先级更高，所以不需要默认栅格\n    ...(col.span\n      ? {}\n      : {\n          xs: 24,\n          sm: 12,\n          md: 12,\n          lg: 12,\n          xl: 12\n        }),\n    ...col\n  }\n  return colProps\n}\n\n/**\n *\n * @param item 传入的组件属性\n * @returns 默认添加 clearable 属性\n */\nexport const setComponentProps = (item: FormSchema): Recordable => {\n  const notNeedClearable = ['ColorPicker']\n  const componentProps: Recordable = notNeedClearable.includes(item.component as string)\n    ? { ...item.componentProps }\n    : {\n        clearable: true,\n        ...item.componentProps\n      }\n  // 需要删除额外的属性\n  delete componentProps?.slots\n  return componentProps\n}\n\n/**\n *\n * @param slots 插槽\n * @param slotsProps 插槽属性\n * @param field 字段名\n */\nexport const setItemComponentSlots = (\n  slots: Slots,\n  slotsProps: Recordable = {},\n  field: string\n): Recordable => {\n  const slotObj: Recordable = {}\n  for (const key in slotsProps) {\n    if (slotsProps[key]) {\n      // 由于组件有可能重复，需要有一个唯一的前缀\n      slotObj[key] = (data: Recordable) => {\n        return getSlot(slots, `${field}-${key}`, data)\n      }\n    }\n  }\n  return slotObj\n}\n\n/**\n *\n * @param schema Form表单结构化数组\n * @param formModel FormModel\n * @returns FormModel\n * @description 生成对应的formModel\n */\nexport const initModel = (schema: FormSchema[], formModel: Recordable) => {\n  const model: Recordable = { ...formModel }\n  schema.map((v) => {\n    // 如果是hidden，就删除对应的值\n    if (v.hidden) {\n      delete model[v.field]\n    } else if (v.component && v.component !== 'Divider') {\n      const hasField = Reflect.has(model, v.field)\n      // 如果先前已经有值存在，则不进行重新赋值，而是采用现有的值\n      model[v.field] = hasField ? model[v.field] : v.value !== void 0 ? v.value : ''\n    }\n  })\n  return model\n}\n\n/**\n * @param slots 插槽\n * @param field 字段名\n * @returns 返回FormIiem插槽\n */\nexport const setFormItemSlots = (slots: Slots, field: string): Recordable => {\n  const slotObj: Recordable = {}\n  if (slots[`${field}-error`]) {\n    slotObj['error'] = (data: Recordable) => {\n      return getSlot(slots, `${field}-error`, data)\n    }\n  }\n  if (slots[`${field}-label`]) {\n    slotObj['label'] = (data: Recordable) => {\n      return getSlot(slots, `${field}-label`, data)\n    }\n  }\n  return slotObj\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Form/src/types.ts",
    "content": "import { FormSchema } from '@/types/form'\n\nexport interface PlaceholderModel {\n  placeholder?: string\n  startPlaceholder?: string\n  endPlaceholder?: string\n  rangeSeparator?: string\n}\n\nexport type FormProps = {\n  schema?: FormSchema[]\n  isCol?: boolean\n  model?: Recordable\n  autoSetPlaceholder?: boolean\n  isCustom?: boolean\n  labelWidth?: string | number\n} & Recordable\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/FormCreate/index.ts",
    "content": "import { useFormCreateDesigner } from './src/useFormCreateDesigner'\nimport { useApiSelect } from './src/components/useApiSelect'\n\nexport { useFormCreateDesigner, useApiSelect }\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/FormCreate/src/components/DictSelect.vue",
    "content": "<!-- 数据字典 Select 选择器 -->\n<template>\n  <el-select v-if=\"selectType === 'select'\" class=\"w-1/1\" v-bind=\"attrs\">\n    <el-option\n      v-for=\"(dict, index) in getDictOptions\"\n      :key=\"index\"\n      :label=\"dict.label\"\n      :value=\"dict.value\"\n    />\n  </el-select>\n  <el-radio-group v-if=\"selectType === 'radio'\" class=\"w-1/1\" v-bind=\"attrs\">\n    <el-radio v-for=\"(dict, index) in getDictOptions\" :key=\"index\" :value=\"dict.value\">\n      {{ dict.label }}\n    </el-radio>\n  </el-radio-group>\n  <el-checkbox-group v-if=\"selectType === 'checkbox'\" class=\"w-1/1\" v-bind=\"attrs\">\n    <el-checkbox\n      v-for=\"(dict, index) in getDictOptions\"\n      :key=\"index\"\n      :label=\"dict.label\"\n      :value=\"dict.value\"\n    />\n  </el-checkbox-group>\n</template>\n\n<script lang=\"ts\" setup>\nimport { getBoolDictOptions, getIntDictOptions, getStrDictOptions } from '@/utils/dict'\n\ndefineOptions({ name: 'DictSelect' })\n\nconst attrs = useAttrs()\n\n// 接受父组件参数\ninterface Props {\n  dictType: string // 字典类型\n  valueType?: 'str' | 'int' | 'bool' // 字典值类型\n  selectType?: 'select' | 'radio' | 'checkbox' // 选择器类型，下拉框 select、多选框 checkbox、单选框 radio\n  formCreateInject?: any\n}\n\nconst props = withDefaults(defineProps<Props>(), {\n  valueType: 'str',\n  selectType: 'select'\n})\n\n// 获得字典配置\nconst getDictOptions = computed(() => {\n  switch (props.valueType) {\n    case 'str':\n      return getStrDictOptions(props.dictType)\n    case 'int':\n      return getIntDictOptions(props.dictType)\n    case 'bool':\n      return getBoolDictOptions(props.dictType)\n    default:\n      return []\n  }\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/FormCreate/src/components/useApiSelect.tsx",
    "content": "import request from '@/config/axios'\nimport { isEmpty } from '@/utils/is'\nimport { ApiSelectProps } from '@/components/FormCreate/src/type'\nimport { jsonParse } from '@/utils'\n\nexport const useApiSelect = (option: ApiSelectProps) => {\n  return defineComponent({\n    name: option.name,\n    props: {\n      // 选项标签\n      labelField: {\n        type: String,\n        default: () => option.labelField ?? 'label'\n      },\n      // 选项的值\n      valueField: {\n        type: String,\n        default: () => option.valueField ?? 'value'\n      },\n      // api 接口\n      url: {\n        type: String,\n        default: () => option.url ?? ''\n      },\n      // 请求类型\n      method: {\n        type: String,\n        default: 'GET'\n      },\n      // 请求参数\n      data: {\n        type: String,\n        default: ''\n      },\n      // 选择器类型，下拉框 select、多选框 checkbox、单选框 radio\n      selectType: {\n        type: String,\n        default: 'select'\n      },\n      // 是否多选\n      multiple: {\n        type: Boolean,\n        default: false\n      }\n    },\n    setup(props) {\n      const attrs = useAttrs()\n      const options = ref<any[]>([]) // 下拉数据\n      const getOptions = async () => {\n        options.value = []\n        // 接口选择器\n        if (isEmpty(props.url)) {\n          return\n        }\n        let data = []\n        switch (props.method) {\n          case 'GET':\n            data = await request.get({ url: props.url })\n            break\n          case 'POST':\n            data = await request.post({ url: props.url, data: jsonParse(props.data) })\n            break\n        }\n\n        if (Array.isArray(data)) {\n          options.value = data.map((item: any) => ({\n            label: item[props.labelField],\n            value: item[props.valueField]\n          }))\n          return\n        }\n        console.error(`接口[${props.url}] 返回结果不是一个数组`)\n      }\n\n      onMounted(async () => {\n        await getOptions()\n      })\n\n      const buildSelect = () => {\n        if (props.multiple) {\n          // fix：多写此步是为了解决 multiple 属性问题\n          return (\n            <el-select class=\"w-1/1\" {...attrs} multiple>\n              {options.value.map((item, index) => (\n                <el-option key={index} label={item.label} value={item.value} />\n              ))}\n            </el-select>\n          )\n        }\n        return (\n          <el-select class=\"w-1/1\" {...attrs}>\n            {options.value.map((item, index) => (\n              <el-option key={index} label={item.label} value={item.value} />\n            ))}\n          </el-select>\n        )\n      }\n      const buildCheckbox = () => {\n        if (isEmpty(options.value)) {\n          options.value = [\n            { label: '选项1', value: '选项1' },\n            { label: '选项2', value: '选项2' }\n          ]\n        }\n        return (\n          <el-checkbox-group class=\"w-1/1\" {...attrs}>\n            {options.value.map((item, index) => (\n              <el-checkbox key={index} label={item.label} value={item.value} />\n            ))}\n          </el-checkbox-group>\n        )\n      }\n      const buildRadio = () => {\n        if (isEmpty(options.value)) {\n          options.value = [\n            { label: '选项1', value: '选项1' },\n            { label: '选项2', value: '选项2' }\n          ]\n        }\n        return (\n          <el-radio-group class=\"w-1/1\" {...attrs}>\n            {options.value.map((item, index) => (\n              <el-radio key={index} value={item.value}>\n                {item.label}\n              </el-radio>\n            ))}\n          </el-radio-group>\n        )\n      }\n      return () => (\n        <>\n          {props.selectType === 'select'\n            ? buildSelect()\n            : props.selectType === 'radio'\n              ? buildRadio()\n              : props.selectType === 'checkbox'\n                ? buildCheckbox()\n                : buildSelect()}\n        </>\n      )\n    }\n  })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/FormCreate/src/config/index.ts",
    "content": "import { useUploadFileRule } from './useUploadFileRule'\nimport { useUploadImgRule } from './useUploadImgRule'\nimport { useUploadImgsRule } from './useUploadImgsRule'\nimport { useDictSelectRule } from './useDictSelectRule'\nimport { useEditorRule } from './useEditorRule'\nimport { useSelectRule } from './useSelectRule'\n\nexport {\n  useUploadFileRule,\n  useUploadImgRule,\n  useUploadImgsRule,\n  useDictSelectRule,\n  useEditorRule,\n  useSelectRule\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/FormCreate/src/config/selectRule.ts",
    "content": "const selectRule = [\n  {\n    type: 'select',\n    field: 'selectType',\n    title: '选择器类型',\n    value: 'select',\n    options: [\n      { label: '下拉框', value: 'select' },\n      { label: '单选框', value: 'radio' },\n      { label: '多选框', value: 'checkbox' }\n    ],\n    // 参考 https://www.form-create.com/v3/guide/control 组件联动，单选框和多选框不需要多选属性\n    control: [\n      {\n        value: 'select',\n        condition: '=',\n        method: 'hidden',\n        rule: ['multiple']\n      }\n    ]\n  },\n  { type: 'switch', field: 'multiple', title: '是否多选' },\n  {\n    type: 'switch',\n    field: 'disabled',\n    title: '是否禁用'\n  },\n  { type: 'switch', field: 'clearable', title: '是否可以清空选项' },\n  {\n    type: 'switch',\n    field: 'collapseTags',\n    title: '多选时是否将选中值按文字的形式展示'\n  },\n  {\n    type: 'inputNumber',\n    field: 'multipleLimit',\n    title: '多选时用户最多可以选择的项目数，为 0 则不限制',\n    props: { min: 0 }\n  },\n  {\n    type: 'input',\n    field: 'autocomplete',\n    title: 'autocomplete 属性'\n  },\n  { type: 'input', field: 'placeholder', title: '占位符' },\n  {\n    type: 'switch',\n    field: 'filterable',\n    title: '是否可搜索'\n  },\n  { type: 'switch', field: 'allowCreate', title: '是否允许用户创建新条目' },\n  {\n    type: 'input',\n    field: 'noMatchText',\n    title: '搜索条件无匹配时显示的文字'\n  },\n  {\n    type: 'switch',\n    field: 'remote',\n    title: '其中的选项是否从服务器远程加载'\n  },\n  {\n    type: 'Struct',\n    field: 'remoteMethod',\n    title: '自定义远程搜索方法'\n  },\n  { type: 'input', field: 'noDataText', title: '选项为空时显示的文字' },\n  {\n    type: 'switch',\n    field: 'reserveKeyword',\n    title: '多选且可搜索时，是否在选中一个选项后保留当前的搜索关键词'\n  },\n  {\n    type: 'switch',\n    field: 'defaultFirstOption',\n    title: '在输入框按下回车，选择第一个匹配项'\n  },\n  {\n    type: 'switch',\n    field: 'popperAppendToBody',\n    title: '是否将弹出框插入至 body 元素',\n    value: true\n  },\n  {\n    type: 'switch',\n    field: 'automaticDropdown',\n    title: '对于不可搜索的 Select，是否在输入框获得焦点后自动弹出选项菜单'\n  }\n]\n\nconst apiSelectRule = [\n  {\n    type: 'input',\n    field: 'url',\n    title: 'url 地址',\n    props: {\n      placeholder: '/system/user/simple-list'\n    }\n  },\n  {\n    type: 'select',\n    field: 'method',\n    title: '请求类型',\n    value: 'GET',\n    options: [\n      { label: 'GET', value: 'GET' },\n      { label: 'POST', value: 'POST' }\n    ],\n    control: [\n      {\n        value: 'GET',\n        condition: '!=',\n        method: 'hidden',\n        rule: [\n          {\n            type: 'input',\n            field: 'data',\n            title: '请求参数 JSON 格式',\n            props: {\n              autosize: true,\n              type: 'textarea',\n              placeholder: '{\"type\": 1}'\n            }\n          }\n        ]\n      }\n    ]\n  },\n  {\n    type: 'input',\n    field: 'labelField',\n    title: 'label 属性',\n    props: {\n      placeholder: 'nickname'\n    }\n  },\n  {\n    type: 'input',\n    field: 'valueField',\n    title: 'value 属性',\n    props: {\n      placeholder: 'id'\n    }\n  }\n]\n\nexport { selectRule, apiSelectRule }\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/FormCreate/src/config/useDictSelectRule.ts",
    "content": "import { generateUUID } from '@/utils'\nimport * as DictDataApi from '@/api/system/dict/dict.type'\nimport { localeProps, makeRequiredRule } from '@/components/FormCreate/src/utils'\nimport { selectRule } from '@/components/FormCreate/src/config/selectRule'\n\n/**\n * 字典选择器规则，如果规则使用到动态数据则需要单独配置不能使用 useSelectRule\n */\nexport const useDictSelectRule = () => {\n  const label = '字典选择器'\n  const name = 'DictSelect'\n  const dictOptions = ref<{ label: string; value: string }[]>([]) // 字典类型下拉数据\n  onMounted(async () => {\n    const data = await DictDataApi.getSimpleDictTypeList()\n    if (!data || data.length === 0) {\n      return\n    }\n    dictOptions.value =\n      data?.map((item: DictDataApi.DictTypeVO) => ({\n        label: item.name,\n        value: item.type\n      })) ?? []\n  })\n  return {\n    icon: 'icon-doc-text',\n    label,\n    name,\n    rule() {\n      return {\n        type: name,\n        field: generateUUID(),\n        title: label,\n        info: '',\n        $required: false\n      }\n    },\n    props(_, { t }) {\n      return localeProps(t, name + '.props', [\n        makeRequiredRule(),\n        {\n          type: 'select',\n          field: 'dictType',\n          title: '字典类型',\n          value: '',\n          options: dictOptions.value\n        },\n        {\n          type: 'select',\n          field: 'dictValueType',\n          title: '字典值类型',\n          value: 'str',\n          options: [\n            { label: '数字', value: 'int' },\n            { label: '字符串', value: 'str' },\n            { label: '布尔值', value: 'bool' }\n          ]\n        },\n        ...selectRule\n      ])\n    }\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/FormCreate/src/config/useEditorRule.ts",
    "content": "import { generateUUID } from '@/utils'\nimport { localeProps, makeRequiredRule } from '@/components/FormCreate/src/utils'\n\nexport const useEditorRule = () => {\n  const label = '富文本'\n  const name = 'Editor'\n  return {\n    icon: 'icon-editor',\n    label,\n    name,\n    rule() {\n      return {\n        type: name,\n        field: generateUUID(),\n        title: label,\n        info: '',\n        $required: false\n      }\n    },\n    props(_, { t }) {\n      return localeProps(t, name + '.props', [\n        makeRequiredRule(),\n        {\n          type: 'input',\n          field: 'height',\n          title: '高度'\n        },\n        { type: 'switch', field: 'readonly', title: '是否只读' }\n      ])\n    }\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/FormCreate/src/config/useSelectRule.ts",
    "content": "import { generateUUID } from '@/utils'\nimport { localeProps, makeRequiredRule } from '@/components/FormCreate/src/utils'\nimport { selectRule } from '@/components/FormCreate/src/config/selectRule'\nimport { SelectRuleOption } from '@/components/FormCreate/src/type'\n\n/**\n * 通用选择器规则 hook\n *\n * @param option 规则配置\n */\nexport const useSelectRule = (option: SelectRuleOption) => {\n  const label = option.label\n  const name = option.name\n  return {\n    icon: option.icon,\n    label,\n    name,\n    rule() {\n      return {\n        type: name,\n        field: generateUUID(),\n        title: label,\n        info: '',\n        $required: false\n      }\n    },\n    props(_, { t }) {\n      if (!option.props) {\n        option.props = []\n      }\n      return localeProps(t, name + '.props', [makeRequiredRule(), ...option.props, ...selectRule])\n    }\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/FormCreate/src/config/useUploadFileRule.ts",
    "content": "import { generateUUID } from '@/utils'\nimport { localeProps, makeRequiredRule } from '@/components/FormCreate/src/utils'\n\nexport const useUploadFileRule = () => {\n  const label = '文件上传'\n  const name = 'UploadFile'\n  return {\n    icon: 'icon-upload',\n    label,\n    name,\n    rule() {\n      return {\n        type: name,\n        field: generateUUID(),\n        title: label,\n        info: '',\n        $required: false\n      }\n    },\n    props(_, { t }) {\n      return localeProps(t, name + '.props', [\n        makeRequiredRule(),\n        {\n          type: 'select',\n          field: 'fileType',\n          title: '文件类型',\n          value: ['doc', 'xls', 'ppt', 'txt', 'pdf'],\n          options: [\n            { label: 'doc', value: 'doc' },\n            { label: 'xls', value: 'xls' },\n            { label: 'ppt', value: 'ppt' },\n            { label: 'txt', value: 'txt' },\n            { label: 'pdf', value: 'pdf' }\n          ],\n          props: {\n            multiple: true\n          }\n        },\n        {\n          type: 'switch',\n          field: 'autoUpload',\n          title: '是否在选取文件后立即进行上传',\n          value: true\n        },\n        {\n          type: 'switch',\n          field: 'drag',\n          title: '拖拽上传',\n          value: false\n        },\n        {\n          type: 'switch',\n          field: 'isShowTip',\n          title: '是否显示提示',\n          value: true\n        },\n        {\n          type: 'inputNumber',\n          field: 'fileSize',\n          title: '大小限制(MB)',\n          value: 5,\n          props: { min: 0 }\n        },\n        {\n          type: 'inputNumber',\n          field: 'limit',\n          title: '数量限制',\n          value: 5,\n          props: { min: 0 }\n        },\n        {\n          type: 'switch',\n          field: 'disabled',\n          title: '是否禁用',\n          value: false\n        }\n      ])\n    }\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/FormCreate/src/config/useUploadImgRule.ts",
    "content": "import { generateUUID } from '@/utils'\nimport { localeProps, makeRequiredRule } from '@/components/FormCreate/src/utils'\n\nexport const useUploadImgRule = () => {\n  const label = '单图上传'\n  const name = 'UploadImg'\n  return {\n    icon: 'icon-upload',\n    label,\n    name,\n    rule() {\n      return {\n        type: name,\n        field: generateUUID(),\n        title: label,\n        info: '',\n        $required: false\n      }\n    },\n    props(_, { t }) {\n      return localeProps(t, name + '.props', [\n        makeRequiredRule(),\n        {\n          type: 'switch',\n          field: 'drag',\n          title: '拖拽上传',\n          value: false\n        },\n        {\n          type: 'select',\n          field: 'fileType',\n          title: '图片类型限制',\n          value: ['image/jpeg', 'image/png', 'image/gif'],\n          options: [\n            { label: 'image/apng', value: 'image/apng' },\n            { label: 'image/bmp', value: 'image/bmp' },\n            { label: 'image/gif', value: 'image/gif' },\n            { label: 'image/jpeg', value: 'image/jpeg' },\n            { label: 'image/pjpeg', value: 'image/pjpeg' },\n            { label: 'image/svg+xml', value: 'image/svg+xml' },\n            { label: 'image/tiff', value: 'image/tiff' },\n            { label: 'image/webp', value: 'image/webp' },\n            { label: 'image/x-icon', value: 'image/x-icon' }\n          ],\n          props: {\n            multiple: true\n          }\n        },\n        {\n          type: 'inputNumber',\n          field: 'fileSize',\n          title: '大小限制(MB)',\n          value: 5,\n          props: { min: 0 }\n        },\n        {\n          type: 'input',\n          field: 'height',\n          title: '组件高度',\n          value: '150px'\n        },\n        {\n          type: 'input',\n          field: 'width',\n          title: '组件宽度',\n          value: '150px'\n        },\n        {\n          type: 'input',\n          field: 'borderradius',\n          title: '组件边框圆角',\n          value: '8px'\n        },\n        {\n          type: 'switch',\n          field: 'disabled',\n          title: '是否显示删除按钮',\n          value: true\n        },\n        {\n          type: 'switch',\n          field: 'showBtnText',\n          title: '是否显示按钮文字',\n          value: true\n        }\n      ])\n    }\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/FormCreate/src/config/useUploadImgsRule.ts",
    "content": "import { generateUUID } from '@/utils'\nimport { localeProps, makeRequiredRule } from '@/components/FormCreate/src/utils'\n\nexport const useUploadImgsRule = () => {\n  const label = '多图上传'\n  const name = 'UploadImgs'\n  return {\n    icon: 'icon-upload',\n    label,\n    name,\n    rule() {\n      return {\n        type: name,\n        field: generateUUID(),\n        title: label,\n        info: '',\n        $required: false\n      }\n    },\n    props(_, { t }) {\n      return localeProps(t, name + '.props', [\n        makeRequiredRule(),\n        {\n          type: 'switch',\n          field: 'drag',\n          title: '拖拽上传',\n          value: false\n        },\n        {\n          type: 'select',\n          field: 'fileType',\n          title: '图片类型限制',\n          value: ['image/jpeg', 'image/png', 'image/gif'],\n          options: [\n            { label: 'image/apng', value: 'image/apng' },\n            { label: 'image/bmp', value: 'image/bmp' },\n            { label: 'image/gif', value: 'image/gif' },\n            { label: 'image/jpeg', value: 'image/jpeg' },\n            { label: 'image/pjpeg', value: 'image/pjpeg' },\n            { label: 'image/svg+xml', value: 'image/svg+xml' },\n            { label: 'image/tiff', value: 'image/tiff' },\n            { label: 'image/webp', value: 'image/webp' },\n            { label: 'image/x-icon', value: 'image/x-icon' }\n          ],\n          props: {\n            multiple: true\n          }\n        },\n        {\n          type: 'inputNumber',\n          field: 'fileSize',\n          title: '大小限制(MB)',\n          value: 5,\n          props: { min: 0 }\n        },\n        {\n          type: 'inputNumber',\n          field: 'limit',\n          title: '数量限制',\n          value: 5,\n          props: { min: 0 }\n        },\n        {\n          type: 'input',\n          field: 'height',\n          title: '组件高度',\n          value: '150px'\n        },\n        {\n          type: 'input',\n          field: 'width',\n          title: '组件宽度',\n          value: '150px'\n        },\n        {\n          type: 'input',\n          field: 'borderradius',\n          title: '组件边框圆角',\n          value: '8px'\n        }\n      ])\n    }\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/FormCreate/src/type/index.ts",
    "content": "import { Rule } from '@form-create/element-ui' //左侧拖拽按钮\n\n// 左侧拖拽按钮\nexport interface MenuItem {\n  label: string\n  name: string\n  icon: string\n}\n\n// 左侧拖拽按钮分类\nexport interface Menu {\n  title: string\n  name: string\n  list: MenuItem[]\n}\n\nexport interface MenuList extends Array<Menu> {}\n\n// 拖拽组件的规则\nexport interface DragRule {\n  icon: string\n  name: string\n  label: string\n  children?: string\n  inside?: true\n  drag?: true | String\n  dragBtn?: false\n  mask?: false\n\n  rule(): Rule\n\n  props(v: any, v1: any): Rule[]\n}\n\n// 通用下拉组件 Props 类型\nexport interface ApiSelectProps {\n  name: string // 组件名称\n  labelField?: string // 选项标签\n  valueField?: string // 选项的值\n  url?: string // url 接口\n  isDict?: boolean // 是否字典选择器\n}\n\n// 选择组件规则配置类型\nexport interface SelectRuleOption {\n  label: string // label 名称\n  name: string // 组件名称\n  icon: string // 组件图标\n  props?: any[] // 组件规则\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/FormCreate/src/useFormCreateDesigner.ts",
    "content": "import {\n  useDictSelectRule,\n  useEditorRule,\n  useSelectRule,\n  useUploadFileRule,\n  useUploadImgRule,\n  useUploadImgsRule\n} from './config'\nimport { Ref } from 'vue'\nimport { Menu } from '@/components/FormCreate/src/type'\nimport { apiSelectRule } from '@/components/FormCreate/src/config/selectRule'\n\n/**\n * 表单设计器增强 hook\n * 新增\n * - 文件上传\n * - 单图上传\n * - 多图上传\n * - 字典选择器\n * - 用户选择器\n * - 部门选择器\n * - 富文本\n */\nexport const useFormCreateDesigner = async (designer: Ref) => {\n  const editorRule = useEditorRule()\n  const uploadFileRule = useUploadFileRule()\n  const uploadImgRule = useUploadImgRule()\n  const uploadImgsRule = useUploadImgsRule()\n\n  /**\n   * 构建表单组件\n   */\n  const buildFormComponents = () => {\n    // 移除自带的上传组件规则，使用 uploadFileRule、uploadImgRule、uploadImgsRule 替代\n    designer.value?.removeMenuItem('upload')\n    // 移除自带的富文本组件规则，使用 editorRule 替代\n    designer.value?.removeMenuItem('fc-editor')\n    const components = [editorRule, uploadFileRule, uploadImgRule, uploadImgsRule]\n    components.forEach((component) => {\n      // 插入组件规则\n      designer.value?.addComponent(component)\n      // 插入拖拽按钮到 `main` 分类下\n      designer.value?.appendMenuItem('main', {\n        icon: component.icon,\n        name: component.name,\n        label: component.label\n      })\n    })\n  }\n\n  const userSelectRule = useSelectRule({\n    name: 'UserSelect',\n    label: '用户选择器',\n    icon: 'icon-user-o'\n  })\n  const deptSelectRule = useSelectRule({\n    name: 'DeptSelect',\n    label: '部门选择器',\n    icon: 'icon-address-card-o'\n  })\n  const dictSelectRule = useDictSelectRule()\n  const apiSelectRule0 = useSelectRule({\n    name: 'ApiSelect',\n    label: '接口选择器',\n    icon: 'icon-server',\n    props: [...apiSelectRule]\n  })\n\n  /**\n   * 构建系统字段菜单\n   */\n  const buildSystemMenu = () => {\n    // 移除自带的下拉选择器组件，使用 currencySelectRule 替代\n    designer.value?.removeMenuItem('select')\n    designer.value?.removeMenuItem('radio')\n    designer.value?.removeMenuItem('checkbox')\n    const components = [userSelectRule, deptSelectRule, dictSelectRule, apiSelectRule0]\n    const menu: Menu = {\n      name: 'system',\n      title: '系统字段',\n      list: components.map((component) => {\n        // 插入组件规则\n        designer.value?.addComponent(component)\n        // 插入拖拽按钮到 `system` 分类下\n        return {\n          icon: component.icon,\n          name: component.name,\n          label: component.label\n        }\n      })\n    }\n    designer.value?.addMenu(menu)\n  }\n\n  onMounted(async () => {\n    await nextTick()\n    buildFormComponents()\n    buildSystemMenu()\n  })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/FormCreate/src/utils/index.ts",
    "content": "// TODO puhui999: 借鉴一下 form-create-designer utils 方法 🤣 (导入不了只能先 copy 过来用下)\nexport function makeRequiredRule() {\n  return {\n    type: 'Required',\n    field: 'formCreate$required',\n    title: '是否必填'\n  }\n}\n\nexport const localeProps = (t, prefix, rules) => {\n  return rules.map((rule) => {\n    if (rule.field === 'formCreate$required') {\n      rule.title = t('props.required') || rule.title\n    } else if (rule.field && rule.field !== '_optionType') {\n      rule.title = t('components.' + prefix + '.' + rule.field) || rule.title\n    }\n    return rule\n  })\n}\n\nexport function upper(str) {\n  return str.replace(str[0], str[0].toLocaleUpperCase())\n}\n\nexport function makeOptionsRule(t, to, userOptions) {\n  console.log(userOptions[0])\n  const options = [\n    { label: t('props.optionsType.struct'), value: 0 },\n    { label: t('props.optionsType.json'), value: 1 },\n    { label: '用户数据', value: 2 }\n  ]\n\n  const control = [\n    {\n      value: 0,\n      rule: [\n        {\n          type: 'TableOptions',\n          field: 'formCreate' + upper(to).replace('.', '>'),\n          props: { defaultValue: [] }\n        }\n      ]\n    },\n    {\n      value: 1,\n      rule: [\n        {\n          type: 'Struct',\n          field: 'formCreate' + upper(to).replace('.', '>'),\n          props: { defaultValue: [] }\n        }\n      ]\n    },\n    {\n      value: 2,\n      rule: [\n        {\n          type: 'TableOptions',\n          field: 'formCreate' + upper(to).replace('.', '>'),\n          props: { modelValue: [] }\n        }\n      ]\n    }\n  ]\n  options.splice(0, 0)\n  control.push()\n\n  return {\n    type: 'radio',\n    title: t('props.options'),\n    field: '_optionType',\n    value: 0,\n    options,\n    props: {\n      type: 'button'\n    },\n    control\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Highlight/index.ts",
    "content": "import Highlight from './src/Highlight.vue'\n\nexport { Highlight }\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Highlight/src/Highlight.vue",
    "content": "<script lang=\"tsx\">\nimport { defineComponent, PropType, computed, h, unref } from 'vue'\nimport { propTypes } from '@/utils/propTypes'\n\nexport default defineComponent({\n  name: 'Highlight',\n  props: {\n    tag: propTypes.string.def('span'),\n    keys: {\n      type: Array as PropType<string[]>,\n      default: () => []\n    },\n    color: propTypes.string.def('var(--el-color-primary)')\n  },\n  emits: ['click'],\n  setup(props, { emit, slots }) {\n    const keyNodes = computed(() => {\n      return props.keys.map((key) => {\n        return h(\n          'span',\n          {\n            onClick: () => {\n              emit('click', key)\n            },\n            style: {\n              color: props.color,\n              cursor: 'pointer'\n            }\n          },\n          key\n        )\n      })\n    })\n\n    const parseText = (text: string) => {\n      props.keys.forEach((key, index) => {\n        const regexp = new RegExp(key, 'g')\n        text = text.replace(regexp, `{{${index}}}`)\n      })\n      return text.split(/{{|}}/)\n    }\n\n    const renderText = () => {\n      if (!slots?.default) return null\n      const node = slots?.default()[0].children\n\n      if (!node) {\n        return slots?.default()[0]\n      }\n\n      const textArray = parseText(node as string)\n      const regexp = /^[0-9]*$/\n      const nodes = textArray.map((t) => {\n        if (regexp.test(t)) {\n          return unref(keyNodes)[t] || t\n        }\n        return t\n      })\n      return h(props.tag, nodes)\n    }\n\n    return () => renderText()\n  }\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/IFrame/index.ts",
    "content": "import IFrame from './src/IFrame.vue'\n\nexport { IFrame }\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/IFrame/src/IFrame.vue",
    "content": "<script lang=\"ts\" setup>\nimport { propTypes } from '@/utils/propTypes'\n\ndefineOptions({ name: 'IFrame' })\n\nconst props = defineProps({\n  src: propTypes.string.def('')\n})\nconst loading = ref(true)\nconst height = ref('')\nconst frameRef = ref<HTMLElement | null>(null)\nconst init = () => {\n  height.value = document.documentElement.clientHeight - 94.5 + 'px'\n  loading.value = false\n}\nonMounted(() => {\n  setTimeout(() => {\n    init()\n  }, 300)\n})\n</script>\n<template>\n  <div v-loading=\"loading\" :style=\"'height:' + height\">\n    <iframe\n      ref=\"frameRef\"\n      :src=\"props.src\"\n      frameborder=\"no\"\n      scrolling=\"auto\"\n      style=\"width: 100%; height: 100%\"\n    ></iframe>\n  </div>\n</template>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Icon/index.ts",
    "content": "import Icon from './src/Icon.vue'\nimport IconSelect from './src/IconSelect.vue'\n\nexport { Icon, IconSelect }\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Icon/src/Icon.vue",
    "content": "<script lang=\"ts\" setup>\nimport { propTypes } from '@/utils/propTypes'\nimport Iconify from '@purge-icons/generated'\nimport { useDesign } from '@/hooks/web/useDesign'\n\ndefineOptions({ name: 'Icon' })\n\nconst { getPrefixCls } = useDesign()\n\nconst prefixCls = getPrefixCls('icon')\n\nconst props = defineProps({\n  // icon name\n  icon: propTypes.string,\n  // icon color\n  color: propTypes.string,\n  // icon size\n  size: propTypes.number.def(16),\n  // icon svg class\n  svgClass: propTypes.string.def('')\n})\n\nconst elRef = ref<ElRef>(null)\n\nconst isLocal = computed(() => props.icon.startsWith('svg-icon:'))\n\nconst symbolId = computed(() => {\n  return unref(isLocal) ? `#icon-${props.icon.split('svg-icon:')[1]}` : props.icon\n})\n\nconst getIconifyStyle = computed(() => {\n  const { color, size } = props\n  return {\n    fontSize: `${size}px`,\n    height: '1em',\n    color\n  }\n})\n\nconst getSvgClass = computed(() => {\n  const { svgClass } = props\n  return `iconify ${svgClass}`\n})\n\nconst updateIcon = async (icon: string) => {\n  if (unref(isLocal)) return\n\n  const el = unref(elRef)\n  if (!el) return\n\n  await nextTick()\n\n  if (!icon) return\n\n  const svg = Iconify.renderSVG(icon, {})\n  if (svg) {\n    el.textContent = ''\n    el.appendChild(svg)\n  } else {\n    const span = document.createElement('span')\n    span.className = 'iconify'\n    span.dataset.icon = icon\n    el.textContent = ''\n    el.appendChild(span)\n  }\n}\n\nwatch(\n  () => props.icon,\n  (icon: string) => {\n    updateIcon(icon)\n  }\n)\n</script>\n\n<template>\n  <ElIcon :class=\"prefixCls\" :color=\"color\" :size=\"size\">\n    <svg v-if=\"isLocal\" :class=\"getSvgClass\" aria-hidden=\"true\">\n      <use :xlink:href=\"symbolId\" />\n    </svg>\n\n    <span v-else ref=\"elRef\" :class=\"$attrs.class\" :style=\"getIconifyStyle\">\n      <span :class=\"getSvgClass\" :data-icon=\"symbolId\"></span>\n    </span>\n  </ElIcon>\n</template>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Icon/src/IconSelect.vue",
    "content": "<script lang=\"ts\" setup>\nimport { CSSProperties } from 'vue'\nimport { cloneDeep } from 'lodash-es'\nimport { IconJson } from '@/components/Icon/src/data'\n\ndefineOptions({ name: 'IconSelect' })\n\ntype ParameterCSSProperties = (item?: string) => CSSProperties | undefined\n\nconst props = defineProps({\n  modelValue: {\n    require: false,\n    type: String\n  }\n})\nconst emit = defineEmits<{ (e: 'update:modelValue', v: string) }>()\n\nconst visible = ref(false)\nconst inputValue = toRef(props, 'modelValue')\nconst iconList = ref(IconJson)\nconst icon = ref('add-location')\nconst currentActiveType = ref('ep:')\n// 深拷贝图标数据，前端做搜索\nconst copyIconList = cloneDeep(iconList.value)\n\nconst pageSize = ref(96)\nconst currentPage = ref(1)\n\n// 搜索条件\nconst filterValue = ref('')\n\nconst tabsList = [\n  {\n    label: 'Element Plus',\n    name: 'ep:'\n  },\n  {\n    label: 'Font Awesome 4',\n    name: 'fa:'\n  },\n  {\n    label: 'Font Awesome 5 Solid',\n    name: 'fa-solid:'\n  }\n]\n\nconst pageList = computed(() => {\n  if (currentPage.value === 1) {\n    return copyIconList[currentActiveType.value]\n      ?.filter((v) => v.includes(filterValue.value))\n      .slice(currentPage.value - 1, pageSize.value)\n  } else {\n    return copyIconList[currentActiveType.value]\n      ?.filter((v) => v.includes(filterValue.value))\n      .slice(\n        pageSize.value * (currentPage.value - 1),\n        pageSize.value * (currentPage.value - 1) + pageSize.value\n      )\n  }\n})\nconst iconCount = computed(() => {\n  return copyIconList[currentActiveType.value] == undefined\n    ? 0\n    : copyIconList[currentActiveType.value].length\n})\n\nconst iconItemStyle = computed((): ParameterCSSProperties => {\n  return (item) => {\n    if (inputValue.value === currentActiveType.value + item) {\n      return {\n        borderColor: 'var(--el-color-primary)',\n        color: 'var(--el-color-primary)'\n      }\n    }\n  }\n})\n\nfunction handleClick({ props }) {\n  currentPage.value = 1\n  currentActiveType.value = props.name\n  emit('update:modelValue', currentActiveType.value + iconList.value[currentActiveType.value][0])\n  icon.value = iconList.value[currentActiveType.value][0]\n}\n\nfunction onChangeIcon(item) {\n  icon.value = item\n  emit('update:modelValue', currentActiveType.value + item)\n  visible.value = false\n}\n\nfunction onCurrentChange(page) {\n  currentPage.value = page\n}\n\nwatch(\n  () => {\n    return props.modelValue\n  },\n  () => {\n    if (props.modelValue && props.modelValue.indexOf(':') >= 0) {\n      currentActiveType.value = props.modelValue.substring(0, props.modelValue.indexOf(':') + 1)\n      icon.value = props.modelValue.substring(props.modelValue.indexOf(':') + 1)\n    }\n  }\n)\nwatch(\n  () => {\n    return filterValue.value\n  },\n  () => {\n    currentPage.value = 1\n  }\n)\n</script>\n\n<template>\n  <div class=\"selector\">\n    <ElInput v-model=\"inputValue\" @click=\"visible = !visible\">\n      <template #append>\n        <ElPopover\n          :popper-options=\"{\n            placement: 'auto'\n          }\"\n          :visible=\"visible\"\n          :width=\"350\"\n          popper-class=\"pure-popper\"\n          trigger=\"click\"\n        >\n          <template #reference>\n            <div\n              class=\"h-32px w-40px flex cursor-pointer items-center justify-center\"\n              @click=\"visible = !visible\"\n            >\n              <Icon :icon=\"currentActiveType + icon\" />\n            </div>\n          </template>\n\n          <ElInput v-model=\"filterValue\" class=\"p-2\" clearable placeholder=\"搜索图标\" />\n          <ElDivider border-style=\"dashed\" />\n\n          <ElTabs v-model=\"currentActiveType\" @tab-click=\"handleClick\">\n            <ElTabPane\n              v-for=\"(pane, index) in tabsList\"\n              :key=\"index\"\n              :label=\"pane.label\"\n              :name=\"pane.name\"\n            >\n              <ElDivider border-style=\"dashed\" class=\"tab-divider\" />\n              <ElScrollbar height=\"220px\">\n                <ul class=\"ml-2 flex flex-wrap px-2\">\n                  <li\n                    v-for=\"(item, key) in pageList\"\n                    :key=\"key\"\n                    :style=\"iconItemStyle(item)\"\n                    :title=\"item\"\n                    class=\"icon-item mr-2 mt-1 w-1/10 flex cursor-pointer items-center justify-center border border-solid p-2\"\n                    @click=\"onChangeIcon(item)\"\n                  >\n                    <Icon :icon=\"currentActiveType + item\" />\n                  </li>\n                </ul>\n              </ElScrollbar>\n            </ElTabPane>\n          </ElTabs>\n          <ElDivider border-style=\"dashed\" />\n\n          <ElPagination\n            :current-page=\"currentPage\"\n            :page-size=\"pageSize\"\n            :total=\"iconCount\"\n            background\n            class=\"h-10 flex items-center justify-center\"\n            layout=\"prev, pager, next\"\n            small\n            @current-change=\"onCurrentChange\"\n          />\n        </ElPopover>\n      </template>\n    </ElInput>\n  </div>\n</template>\n\n<style lang=\"scss\" scoped>\n.el-divider--horizontal {\n  margin: 1px auto !important;\n}\n\n.tab-divider.el-divider--horizontal {\n  margin: 0 !important;\n}\n\n.icon-item {\n  &:hover {\n    color: var(--el-color-primary);\n    border-color: var(--el-color-primary);\n    transform: scaleX(1.05);\n    transition: all 0.4s;\n  }\n}\n\n:deep(.el-tabs__nav-next) {\n  font-size: 15px;\n  line-height: 32px;\n  box-shadow: -5px 0 5px -6px #ccc;\n}\n\n:deep(.el-tabs__nav-prev) {\n  font-size: 15px;\n  line-height: 32px;\n  box-shadow: 5px 0 5px -6px #ccc;\n}\n\n:deep(.el-input-group__append) {\n  padding: 0;\n}\n\n:deep(.el-tabs__item) {\n  height: 30px;\n  font-size: 12px;\n  font-weight: normal;\n  line-height: 30px;\n}\n\n:deep(.el-tabs__header),\n:deep(.el-tabs__nav-wrap) {\n  position: static;\n  margin: 0;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Icon/src/data.ts",
    "content": "export const IconJson = {\n  'ep:': [\n    'add-location',\n    'aim',\n    'alarm-clock',\n    'apple',\n    'arrow-down',\n    'arrow-down-bold',\n    'arrow-left',\n    'arrow-left-bold',\n    'arrow-right',\n    'arrow-right-bold',\n    'arrow-up',\n    'arrow-up-bold',\n    'avatar',\n    'back',\n    'baseball',\n    'basketball',\n    'bell',\n    'bell-filled',\n    'bicycle',\n    'bottom',\n    'bottom-left',\n    'bottom-right',\n    'bowl',\n    'box',\n    'briefcase',\n    'brush',\n    'brush-filled',\n    'burger',\n    'calendar',\n    'camera',\n    'camera-filled',\n    'caret-bottom',\n    'caret-left',\n    'caret-right',\n    'caret-top',\n    'cellphone',\n    'chat-dot-round',\n    'chat-dot-square',\n    'chat-line-round',\n    'chat-line-square',\n    'chat-round',\n    'chat-square',\n    'check',\n    'checked',\n    'cherry',\n    'chicken',\n    'circle-check',\n    'circle-check-filled',\n    'circle-close',\n    'circle-close-filled',\n    'circle-plus',\n    'circle-plus-filled',\n    'clock',\n    'close',\n    'close-bold',\n    'cloudy',\n    'coffee',\n    'coffee-cup',\n    'coin',\n    'cold-drink',\n    'collection',\n    'collection-tag',\n    'comment',\n    'compass',\n    'connection',\n    'coordinate',\n    'copy-document',\n    'cpu',\n    'credit-card',\n    'crop',\n    'd-arrow-left',\n    'd-arrow-right',\n    'd-caret',\n    'data-analysis',\n    'data-board',\n    'data-line',\n    'delete',\n    'delete-filled',\n    'delete-location',\n    'dessert',\n    'discount',\n    'dish',\n    'dish-dot',\n    'document',\n    'document-add',\n    'document-checked',\n    'document-copy',\n    'document-delete',\n    'document-remove',\n    'download',\n    'drizzling',\n    'edit',\n    'edit-pen',\n    'eleme',\n    'eleme-filled',\n    'expand',\n    'failed',\n    'female',\n    'files',\n    'film',\n    'filter',\n    'finished',\n    'first-aid-kit',\n    'flag',\n    'fold',\n    'folder',\n    'folder-add',\n    'folder-checked',\n    'folder-delete',\n    'folder-opened',\n    'folder-remove',\n    'food',\n    'football',\n    'fork-spoon',\n    'fries',\n    'full-screen',\n    'goblet',\n    'goblet-full',\n    'goblet-square',\n    'goblet-square-full',\n    'goods',\n    'goods-filled',\n    'grape',\n    'grid',\n    'guide',\n    'headset',\n    'help',\n    'help-filled',\n    'histogram',\n    'home-filled',\n    'hot-water',\n    'house',\n    'ice-cream',\n    'ice-cream-round',\n    'ice-cream-square',\n    'ice-drink',\n    'ice-tea',\n    'info-filled',\n    'iphone',\n    'key',\n    'knife-fork',\n    'lightning',\n    'link',\n    'list',\n    'loading',\n    'location',\n    'location-filled',\n    'location-information',\n    'lock',\n    'lollipop',\n    'magic-stick',\n    'magnet',\n    'male',\n    'management',\n    'map-location',\n    'medal',\n    'menu',\n    'message',\n    'message-box',\n    'mic',\n    'microphone',\n    'milk-tea',\n    'minus',\n    'money',\n    'monitor',\n    'moon',\n    'moon-night',\n    'more',\n    'more-filled',\n    'mostly-cloudy',\n    'mouse',\n    'mug',\n    'mute',\n    'mute-notification',\n    'no-smoking',\n    'notebook',\n    'notification',\n    'odometer',\n    'office-building',\n    'open',\n    'operation',\n    'opportunity',\n    'orange',\n    'paperclip',\n    'partly-cloudy',\n    'pear',\n    'phone',\n    'phone-filled',\n    'picture',\n    'picture-filled',\n    'picture-rounded',\n    'pie-chart',\n    'place',\n    'platform',\n    'plus',\n    'pointer',\n    'position',\n    'postcard',\n    'pouring',\n    'present',\n    'price-tag',\n    'printer',\n    'promotion',\n    'question-filled',\n    'rank',\n    'reading',\n    'reading-lamp',\n    'refresh',\n    'refresh-left',\n    'refresh-right',\n    'refrigerator',\n    'remove',\n    'remove-filled',\n    'right',\n    'scale-to-original',\n    'school',\n    'scissor',\n    'search',\n    'select',\n    'sell',\n    'semi-select',\n    'service',\n    'set-up',\n    'setting',\n    'share',\n    'ship',\n    'shop',\n    'shopping-bag',\n    'shopping-cart',\n    'shopping-cart-full',\n    'smoking',\n    'soccer',\n    'sold-out',\n    'sort',\n    'sort-down',\n    'sort-up',\n    'stamp',\n    'star',\n    'star-filled',\n    'stopwatch',\n    'success-filled',\n    'sugar',\n    'suitcase',\n    'sunny',\n    'sunrise',\n    'sunset',\n    'switch',\n    'switch-button',\n    'takeaway-box',\n    'ticket',\n    'tickets',\n    'timer',\n    'toilet-paper',\n    'tools',\n    'top',\n    'top-left',\n    'top-right',\n    'trend-charts',\n    'trophy',\n    'turn-off',\n    'umbrella',\n    'unlock',\n    'upload',\n    'upload-filled',\n    'user',\n    'user-filled',\n    'van',\n    'video-camera',\n    'video-camera-filled',\n    'video-pause',\n    'video-play',\n    'view',\n    'wallet',\n    'wallet-filled',\n    'warning',\n    'warning-filled',\n    'watch',\n    'watermelon',\n    'wind-power',\n    'zoom-in',\n    'zoom-out'\n  ],\n  'fa:': [\n    '500px',\n    'address-book',\n    'address-book-o',\n    'address-card',\n    'address-card-o',\n    'adjust',\n    'adn',\n    'align-center',\n    'align-justify',\n    'align-left',\n    'amazon',\n    'ambulance',\n    'american-sign-language-interpreting',\n    'anchor',\n    'android',\n    'angellist',\n    'angle-double-left',\n    'angle-double-up',\n    'angle-down',\n    'angle-left',\n    'angle-up',\n    'apple',\n    'archive',\n    'area-chart',\n    'arrow-circle-left',\n    'arrow-circle-o-left',\n    'arrow-circle-o-up',\n    'arrow-circle-up',\n    'arrow-left',\n    'arrow-up',\n    'arrows',\n    'arrows-alt',\n    'arrows-h',\n    'arrows-v',\n    'assistive-listening-systems',\n    'asterisk',\n    'at',\n    'audio-description',\n    'automobile',\n    'backward',\n    'balance-scale',\n    'ban',\n    'bandcamp',\n    'bank',\n    'bar-chart',\n    'barcode',\n    'bars',\n    'bath',\n    'battery',\n    'battery-0',\n    'battery-1',\n    'battery-2',\n    'battery-3',\n    'bed',\n    'beer',\n    'behance',\n    'behance-square',\n    'bell',\n    'bell-o',\n    'bell-slash',\n    'bell-slash-o',\n    'bicycle',\n    'binoculars',\n    'birthday-cake',\n    'bitbucket',\n    'bitbucket-square',\n    'bitcoin',\n    'black-tie',\n    'blind',\n    'bluetooth',\n    'bluetooth-b',\n    'bold',\n    'bolt',\n    'bomb',\n    'book',\n    'bookmark',\n    'bookmark-o',\n    'braille',\n    'briefcase',\n    'bug',\n    'building',\n    'building-o',\n    'bullhorn',\n    'bullseye',\n    'bus',\n    'buysellads',\n    'cab',\n    'calculator',\n    'calendar',\n    'calendar-check-o',\n    'calendar-minus-o',\n    'calendar-o',\n    'calendar-plus-o',\n    'calendar-times-o',\n    'camera',\n    'camera-retro',\n    'caret-down',\n    'caret-left',\n    'caret-square-o-left',\n    'caret-square-o-up',\n    'caret-up',\n    'cart-arrow-down',\n    'cart-plus',\n    'cc',\n    'cc-amex',\n    'cc-diners-club',\n    'cc-discover',\n    'cc-jcb',\n    'cc-mastercard',\n    'cc-paypal',\n    'cc-stripe',\n    'cc-visa',\n    'certificate',\n    'chain',\n    'chain-broken',\n    'check',\n    'check-circle',\n    'check-circle-o',\n    'check-square',\n    'check-square-o',\n    'chevron-circle-left',\n    'chevron-circle-up',\n    'chevron-down',\n    'chevron-left',\n    'chevron-up',\n    'child',\n    'chrome',\n    'circle',\n    'circle-o',\n    'circle-o-notch',\n    'circle-thin',\n    'clipboard',\n    'clock-o',\n    'clone',\n    'close',\n    'cloud',\n    'cloud-download',\n    'cloud-upload',\n    'cny',\n    'code',\n    'code-fork',\n    'codepen',\n    'codiepie',\n    'coffee',\n    'cog',\n    'cogs',\n    'columns',\n    'comment',\n    'comment-o',\n    'commenting',\n    'commenting-o',\n    'comments',\n    'comments-o',\n    'compass',\n    'compress',\n    'connectdevelop',\n    'contao',\n    'copy',\n    'copyright',\n    'creative-commons',\n    'credit-card',\n    'credit-card-alt',\n    'crop',\n    'crosshairs',\n    'css3',\n    'cube',\n    'cubes',\n    'cut',\n    'cutlery',\n    'dashboard',\n    'dashcube',\n    'database',\n    'deaf',\n    'dedent',\n    'delicious',\n    'desktop',\n    'deviantart',\n    'diamond',\n    'digg',\n    'dollar',\n    'dot-circle-o',\n    'download',\n    'dribbble',\n    'drivers-license',\n    'drivers-license-o',\n    'dropbox',\n    'drupal',\n    'edge',\n    'edit',\n    'eercast',\n    'eject',\n    'ellipsis-h',\n    'ellipsis-v',\n    'empire',\n    'envelope',\n    'envelope-o',\n    'envelope-open',\n    'envelope-open-o',\n    'envelope-square',\n    'envira',\n    'eraser',\n    'etsy',\n    'eur',\n    'exchange',\n    'exclamation',\n    'exclamation-circle',\n    'exclamation-triangle',\n    'expand',\n    'expeditedssl',\n    'external-link',\n    'external-link-square',\n    'eye',\n    'eye-slash',\n    'eyedropper',\n    'fa',\n    'facebook',\n    'facebook-official',\n    'facebook-square',\n    'fast-backward',\n    'fax',\n    'feed',\n    'female',\n    'fighter-jet',\n    'file',\n    'file-archive-o',\n    'file-audio-o',\n    'file-code-o',\n    'file-excel-o',\n    'file-image-o',\n    'file-movie-o',\n    'file-o',\n    'file-pdf-o',\n    'file-powerpoint-o',\n    'file-text',\n    'file-text-o',\n    'file-word-o',\n    'film',\n    'filter',\n    'fire',\n    'fire-extinguisher',\n    'firefox',\n    'first-order',\n    'flag',\n    'flag-checkered',\n    'flag-o',\n    'flask',\n    'flickr',\n    'floppy-o',\n    'folder',\n    'folder-o',\n    'folder-open',\n    'folder-open-o',\n    'font',\n    'fonticons',\n    'fort-awesome',\n    'forumbee',\n    'foursquare',\n    'free-code-camp',\n    'frown-o',\n    'futbol-o',\n    'gamepad',\n    'gavel',\n    'gbp',\n    'genderless',\n    'get-pocket',\n    'gg',\n    'gg-circle',\n    'gift',\n    'git',\n    'git-square',\n    'github',\n    'github-alt',\n    'github-square',\n    'gitlab',\n    'gittip',\n    'glass',\n    'glide',\n    'glide-g',\n    'globe',\n    'google',\n    'google-plus',\n    'google-plus-circle',\n    'google-plus-square',\n    'google-wallet',\n    'graduation-cap',\n    'grav',\n    'group',\n    'h-square',\n    'hacker-news',\n    'hand-grab-o',\n    'hand-lizard-o',\n    'hand-o-left',\n    'hand-o-up',\n    'hand-paper-o',\n    'hand-peace-o',\n    'hand-pointer-o',\n    'hand-scissors-o',\n    'hand-spock-o',\n    'handshake-o',\n    'hashtag',\n    'hdd-o',\n    'header',\n    'headphones',\n    'heart',\n    'heart-o',\n    'heartbeat',\n    'history',\n    'home',\n    'hospital-o',\n    'hourglass',\n    'hourglass-1',\n    'hourglass-2',\n    'hourglass-3',\n    'hourglass-o',\n    'houzz',\n    'html5',\n    'i-cursor',\n    'id-badge',\n    'ils',\n    'image',\n    'imdb',\n    'inbox',\n    'indent',\n    'industry',\n    'info',\n    'info-circle',\n    'inr',\n    'instagram',\n    'internet-explorer',\n    'intersex',\n    'ioxhost',\n    'italic',\n    'joomla',\n    'jsfiddle',\n    'key',\n    'keyboard-o',\n    'krw',\n    'language',\n    'laptop',\n    'lastfm',\n    'lastfm-square',\n    'leaf',\n    'leanpub',\n    'lemon-o',\n    'level-up',\n    'life-bouy',\n    'lightbulb-o',\n    'line-chart',\n    'linkedin',\n    'linkedin-square',\n    'linode',\n    'linux',\n    'list',\n    'list-alt',\n    'list-ol',\n    'list-ul',\n    'location-arrow',\n    'lock',\n    'long-arrow-left',\n    'long-arrow-up',\n    'low-vision',\n    'magic',\n    'magnet',\n    'mail-forward',\n    'mail-reply',\n    'mail-reply-all',\n    'male',\n    'map',\n    'map-marker',\n    'map-o',\n    'map-pin',\n    'map-signs',\n    'mars',\n    'mars-double',\n    'mars-stroke',\n    'mars-stroke-h',\n    'mars-stroke-v',\n    'maxcdn',\n    'meanpath',\n    'medium',\n    'medkit',\n    'meetup',\n    'meh-o',\n    'mercury',\n    'microchip',\n    'microphone',\n    'microphone-slash',\n    'minus',\n    'minus-circle',\n    'minus-square',\n    'minus-square-o',\n    'mixcloud',\n    'mobile',\n    'modx',\n    'money',\n    'moon-o',\n    'motorcycle',\n    'mouse-pointer',\n    'music',\n    'neuter',\n    'newspaper-o',\n    'object-group',\n    'object-ungroup',\n    'odnoklassniki',\n    'odnoklassniki-square',\n    'opencart',\n    'openid',\n    'opera',\n    'optin-monster',\n    'pagelines',\n    'paint-brush',\n    'paper-plane',\n    'paper-plane-o',\n    'paperclip',\n    'paragraph',\n    'pause',\n    'pause-circle',\n    'pause-circle-o',\n    'paw',\n    'paypal',\n    'pencil',\n    'pencil-square',\n    'percent',\n    'phone',\n    'phone-square',\n    'pie-chart',\n    'pied-piper',\n    'pied-piper-alt',\n    'pied-piper-pp',\n    'pinterest',\n    'pinterest-p',\n    'pinterest-square',\n    'plane',\n    'play',\n    'play-circle',\n    'play-circle-o',\n    'plug',\n    'plus',\n    'plus-circle',\n    'plus-square',\n    'plus-square-o',\n    'podcast',\n    'power-off',\n    'print',\n    'product-hunt',\n    'puzzle-piece',\n    'qq',\n    'qrcode',\n    'question',\n    'question-circle',\n    'question-circle-o',\n    'quora',\n    'quote-left',\n    'quote-right',\n    'ra',\n    'random',\n    'ravelry',\n    'recycle',\n    'reddit',\n    'reddit-alien',\n    'reddit-square',\n    'refresh',\n    'registered',\n    'renren',\n    'repeat',\n    'retweet',\n    'road',\n    'rocket',\n    'rotate-left',\n    'rouble',\n    'rss-square',\n    'safari',\n    'scribd',\n    'search',\n    'search-minus',\n    'search-plus',\n    'sellsy',\n    'server',\n    'share-alt',\n    'share-alt-square',\n    'share-square',\n    'share-square-o',\n    'shield',\n    'ship',\n    'shirtsinbulk',\n    'shopping-bag',\n    'shopping-basket',\n    'shopping-cart',\n    'shower',\n    'sign-in',\n    'sign-language',\n    'sign-out',\n    'signal',\n    'simplybuilt',\n    'sitemap',\n    'skyatlas',\n    'skype',\n    'slack',\n    'sliders',\n    'slideshare',\n    'smile-o',\n    'snapchat',\n    'snapchat-ghost',\n    'snapchat-square',\n    'snowflake-o',\n    'sort',\n    'sort-alpha-asc',\n    'sort-alpha-desc',\n    'sort-amount-asc',\n    'sort-amount-desc',\n    'sort-asc',\n    'sort-numeric-asc',\n    'sort-numeric-desc',\n    'soundcloud',\n    'space-shuttle',\n    'spinner',\n    'spoon',\n    'spotify',\n    'square',\n    'square-o',\n    'stack-exchange',\n    'stack-overflow',\n    'star',\n    'star-half',\n    'star-half-empty',\n    'star-o',\n    'steam',\n    'steam-square',\n    'step-backward',\n    'stethoscope',\n    'sticky-note',\n    'sticky-note-o',\n    'stop',\n    'stop-circle',\n    'stop-circle-o',\n    'street-view',\n    'strikethrough',\n    'stumbleupon',\n    'stumbleupon-circle',\n    'subscript',\n    'subway',\n    'suitcase',\n    'sun-o',\n    'superpowers',\n    'superscript',\n    'table',\n    'tablet',\n    'tag',\n    'tags',\n    'tasks',\n    'telegram',\n    'television',\n    'tencent-weibo',\n    'terminal',\n    'text-height',\n    'text-width',\n    'th',\n    'th-large',\n    'th-list',\n    'themeisle',\n    'thermometer',\n    'thermometer-0',\n    'thermometer-1',\n    'thermometer-2',\n    'thermometer-3',\n    'thumb-tack',\n    'thumbs-down',\n    'thumbs-o-up',\n    'thumbs-up',\n    'ticket',\n    'times-circle',\n    'times-circle-o',\n    'times-rectangle',\n    'times-rectangle-o',\n    'tint',\n    'toggle-off',\n    'toggle-on',\n    'trademark',\n    'train',\n    'transgender-alt',\n    'trash',\n    'trash-o',\n    'tree',\n    'trello',\n    'tripadvisor',\n    'trophy',\n    'truck',\n    'try',\n    'tty',\n    'tumblr',\n    'tumblr-square',\n    'twitch',\n    'twitter',\n    'twitter-square',\n    'umbrella',\n    'underline',\n    'universal-access',\n    'unlock',\n    'unlock-alt',\n    'upload',\n    'usb',\n    'user',\n    'user-circle',\n    'user-circle-o',\n    'user-md',\n    'user-o',\n    'user-plus',\n    'user-secret',\n    'user-times',\n    'venus',\n    'venus-double',\n    'venus-mars',\n    'viacoin',\n    'viadeo',\n    'viadeo-square',\n    'video-camera',\n    'vimeo',\n    'vimeo-square',\n    'vine',\n    'vk',\n    'volume-control-phone',\n    'volume-down',\n    'volume-off',\n    'volume-up',\n    'wechat',\n    'weibo',\n    'whatsapp',\n    'wheelchair',\n    'wheelchair-alt',\n    'wifi',\n    'wikipedia-w',\n    'window-maximize',\n    'window-minimize',\n    'window-restore',\n    'windows',\n    'wordpress',\n    'wpbeginner',\n    'wpexplorer',\n    'wpforms',\n    'wrench',\n    'xing',\n    'xing-square',\n    'y-combinator',\n    'yahoo',\n    'yelp',\n    'yoast',\n    'youtube',\n    'youtube-play',\n    'youtube-square'\n  ],\n  'fa-solid:': [\n    'abacus',\n    'ad',\n    'address-book',\n    'address-card',\n    'adjust',\n    'air-freshener',\n    'align-center',\n    'align-justify',\n    'align-left',\n    'align-right',\n    'allergies',\n    'ambulance',\n    'american-sign-language-interpreting',\n    'anchor',\n    'angle-double-down',\n    'angle-double-left',\n    'angle-double-right',\n    'angle-double-up',\n    'angle-down',\n    'angle-left',\n    'angle-right',\n    'angle-up',\n    'angry',\n    'ankh',\n    'apple-alt',\n    'archive',\n    'archway',\n    'arrow-alt-circle-down',\n    'arrow-alt-circle-left',\n    'arrow-alt-circle-right',\n    'arrow-alt-circle-up',\n    'arrow-circle-down',\n    'arrow-circle-left',\n    'arrow-circle-right',\n    'arrow-circle-up',\n    'arrow-down',\n    'arrow-left',\n    'arrow-right',\n    'arrow-up',\n    'arrows-alt',\n    'arrows-alt-h',\n    'arrows-alt-v',\n    'assistive-listening-systems',\n    'asterisk',\n    'at',\n    'atlas',\n    'atom',\n    'audio-description',\n    'award',\n    'baby',\n    'baby-carriage',\n    'backspace',\n    'backward',\n    'bacon',\n    'bacteria',\n    'bacterium',\n    'bahai',\n    'balance-scale',\n    'balance-scale-left',\n    'balance-scale-right',\n    'ban',\n    'band-aid',\n    'barcode',\n    'bars',\n    'baseball-ball',\n    'basketball-ball',\n    'bath',\n    'battery-empty',\n    'battery-full',\n    'battery-half',\n    'battery-quarter',\n    'battery-three-quarters',\n    'bed',\n    'beer',\n    'bell',\n    'bell-slash',\n    'bezier-curve',\n    'bible',\n    'bicycle',\n    'biking',\n    'binoculars',\n    'biohazard',\n    'birthday-cake',\n    'blender',\n    'blender-phone',\n    'blind',\n    'blog',\n    'bold',\n    'bolt',\n    'bomb',\n    'bone',\n    'bong',\n    'book',\n    'book-dead',\n    'book-medical',\n    'book-open',\n    'book-reader',\n    'bookmark',\n    'border-all',\n    'border-none',\n    'border-style',\n    'bowling-ball',\n    'box',\n    'box-open',\n    'box-tissue',\n    'boxes',\n    'braille',\n    'brain',\n    'bread-slice',\n    'briefcase',\n    'briefcase-medical',\n    'broadcast-tower',\n    'broom',\n    'brush',\n    'bug',\n    'building',\n    'bullhorn',\n    'bullseye',\n    'burn',\n    'bus',\n    'bus-alt',\n    'business-time',\n    'calculator',\n    'calculator-alt',\n    'calendar',\n    'calendar-alt',\n    'calendar-check',\n    'calendar-day',\n    'calendar-minus',\n    'calendar-plus',\n    'calendar-times',\n    'calendar-week',\n    'camera',\n    'camera-retro',\n    'campground',\n    'candy-cane',\n    'cannabis',\n    'capsules',\n    'car',\n    'car-alt',\n    'car-battery',\n    'car-crash',\n    'car-side',\n    'caravan',\n    'caret-down',\n    'caret-left',\n    'caret-right',\n    'caret-square-down',\n    'caret-square-left',\n    'caret-square-right',\n    'caret-square-up',\n    'caret-up',\n    'carrot',\n    'cart-arrow-down',\n    'cart-plus',\n    'cash-register',\n    'cat',\n    'certificate',\n    'chair',\n    'chalkboard',\n    'chalkboard-teacher',\n    'charging-station',\n    'chart-area',\n    'chart-bar',\n    'chart-line',\n    'chart-pie',\n    'check',\n    'check-circle',\n    'check-double',\n    'check-square',\n    'cheese',\n    'chess',\n    'chess-bishop',\n    'chess-board',\n    'chess-king',\n    'chess-knight',\n    'chess-pawn',\n    'chess-queen',\n    'chess-rook',\n    'chevron-circle-down',\n    'chevron-circle-left',\n    'chevron-circle-right',\n    'chevron-circle-up',\n    'chevron-down',\n    'chevron-left',\n    'chevron-right',\n    'chevron-up',\n    'child',\n    'church',\n    'circle',\n    'circle-notch',\n    'city',\n    'clinic-medical',\n    'clipboard',\n    'clipboard-check',\n    'clipboard-list',\n    'clock',\n    'clone',\n    'closed-captioning',\n    'cloud',\n    'cloud-download-alt',\n    'cloud-meatball',\n    'cloud-moon',\n    'cloud-moon-rain',\n    'cloud-rain',\n    'cloud-showers-heavy',\n    'cloud-sun',\n    'cloud-sun-rain',\n    'cloud-upload-alt',\n    'cocktail',\n    'code',\n    'code-branch',\n    'coffee',\n    'cog',\n    'cogs',\n    'coins',\n    'columns',\n    'comment',\n    'comment-alt',\n    'comment-dollar',\n    'comment-dots',\n    'comment-medical',\n    'comment-slash',\n    'comments',\n    'comments-dollar',\n    'compact-disc',\n    'compass',\n    'compress',\n    'compress-alt',\n    'compress-arrows-alt',\n    'concierge-bell',\n    'cookie',\n    'cookie-bite',\n    'copy',\n    'copyright',\n    'couch',\n    'credit-card',\n    'crop',\n    'crop-alt',\n    'cross',\n    'crosshairs',\n    'crow',\n    'crown',\n    'crutch',\n    'cube',\n    'cubes',\n    'cut',\n    'database',\n    'deaf',\n    'democrat',\n    'desktop',\n    'dharmachakra',\n    'diagnoses',\n    'dice',\n    'dice-d20',\n    'dice-d6',\n    'dice-five',\n    'dice-four',\n    'dice-one',\n    'dice-six',\n    'dice-three',\n    'dice-two',\n    'digital-tachograph',\n    'directions',\n    'disease',\n    'divide',\n    'dizzy',\n    'dna',\n    'dog',\n    'dollar-sign',\n    'dolly',\n    'dolly-flatbed',\n    'donate',\n    'door-closed',\n    'door-open',\n    'dot-circle',\n    'dove',\n    'download',\n    'drafting-compass',\n    'dragon',\n    'draw-polygon',\n    'drum',\n    'drum-steelpan',\n    'drumstick-bite',\n    'dumbbell',\n    'dumpster',\n    'dumpster-fire',\n    'dungeon',\n    'edit',\n    'egg',\n    'eject',\n    'ellipsis-h',\n    'ellipsis-v',\n    'empty-set',\n    'envelope',\n    'envelope-open',\n    'envelope-open-text',\n    'envelope-square',\n    'equals',\n    'eraser',\n    'ethernet',\n    'euro-sign',\n    'exchange-alt',\n    'exclamation',\n    'exclamation-circle',\n    'exclamation-triangle',\n    'expand',\n    'expand-alt',\n    'expand-arrows-alt',\n    'external-link-alt',\n    'external-link-square-alt',\n    'eye',\n    'eye-dropper',\n    'eye-slash',\n    'fan',\n    'fast-backward',\n    'fast-forward',\n    'faucet',\n    'fax',\n    'feather',\n    'feather-alt',\n    'female',\n    'fighter-jet',\n    'file',\n    'file-alt',\n    'file-archive',\n    'file-audio',\n    'file-code',\n    'file-contract',\n    'file-csv',\n    'file-download',\n    'file-excel',\n    'file-export',\n    'file-image',\n    'file-import',\n    'file-invoice',\n    'file-invoice-dollar',\n    'file-medical',\n    'file-medical-alt',\n    'file-pdf',\n    'file-powerpoint',\n    'file-prescription',\n    'file-signature',\n    'file-upload',\n    'file-video',\n    'file-word',\n    'fill',\n    'fill-drip',\n    'film',\n    'filter',\n    'fingerprint',\n    'fire',\n    'fire-alt',\n    'fire-extinguisher',\n    'first-aid',\n    'fish',\n    'fist-raised',\n    'flag',\n    'flag-checkered',\n    'flag-usa',\n    'flask',\n    'flushed',\n    'folder',\n    'folder-minus',\n    'folder-open',\n    'folder-plus',\n    'font',\n    'football-ball',\n    'forward',\n    'frog',\n    'frown',\n    'frown-open',\n    'function',\n    'funnel-dollar',\n    'futbol',\n    'gamepad',\n    'gas-pump',\n    'gavel',\n    'gem',\n    'genderless',\n    'ghost',\n    'gift',\n    'gifts',\n    'glass-cheers',\n    'glass-martini',\n    'glass-martini-alt',\n    'glass-whiskey',\n    'glasses',\n    'globe',\n    'globe-africa',\n    'globe-americas',\n    'globe-asia',\n    'globe-europe',\n    'golf-ball',\n    'gopuram',\n    'graduation-cap',\n    'greater-than',\n    'greater-than-equal',\n    'grimace',\n    'grin',\n    'grin-alt',\n    'grin-beam',\n    'grin-beam-sweat',\n    'grin-hearts',\n    'grin-squint',\n    'grin-squint-tears',\n    'grin-stars',\n    'grin-tears',\n    'grin-tongue',\n    'grin-tongue-squint',\n    'grin-tongue-wink',\n    'grin-wink',\n    'grip-horizontal',\n    'grip-lines',\n    'grip-lines-vertical',\n    'grip-vertical',\n    'guitar',\n    'h-square',\n    'hamburger',\n    'hammer',\n    'hamsa',\n    'hand-holding',\n    'hand-holding-heart',\n    'hand-holding-medical',\n    'hand-holding-usd',\n    'hand-holding-water',\n    'hand-lizard',\n    'hand-middle-finger',\n    'hand-paper',\n    'hand-peace',\n    'hand-point-down',\n    'hand-point-left',\n    'hand-point-right',\n    'hand-point-up',\n    'hand-pointer',\n    'hand-rock',\n    'hand-scissors',\n    'hand-sparkles',\n    'hand-spock',\n    'hands',\n    'hands-helping',\n    'hands-wash',\n    'handshake',\n    'handshake-alt-slash',\n    'handshake-slash',\n    'hanukiah',\n    'hard-hat',\n    'hashtag',\n    'hat-cowboy',\n    'hat-cowboy-side',\n    'hat-wizard',\n    'hdd',\n    'head-side-cough',\n    'head-side-cough-slash',\n    'head-side-mask',\n    'head-side-virus',\n    'heading',\n    'headphones',\n    'headphones-alt',\n    'headset',\n    'heart',\n    'heart-broken',\n    'heartbeat',\n    'helicopter',\n    'highlighter',\n    'hiking',\n    'hippo',\n    'history',\n    'hockey-puck',\n    'holly-berry',\n    'home',\n    'horse',\n    'horse-head',\n    'hospital',\n    'hospital-alt',\n    'hospital-symbol',\n    'hospital-user',\n    'hot-tub',\n    'hotdog',\n    'hotel',\n    'hourglass',\n    'hourglass-end',\n    'hourglass-half',\n    'hourglass-start',\n    'house-damage',\n    'house-user',\n    'hryvnia',\n    'i-cursor',\n    'ice-cream',\n    'icicles',\n    'icons',\n    'id-badge',\n    'id-card',\n    'id-card-alt',\n    'igloo',\n    'image',\n    'images',\n    'inbox',\n    'indent',\n    'industry',\n    'infinity',\n    'info',\n    'info-circle',\n    'integral',\n    'intersection',\n    'italic',\n    'jedi',\n    'joint',\n    'journal-whills',\n    'kaaba',\n    'key',\n    'keyboard',\n    'khanda',\n    'kiss',\n    'kiss-beam',\n    'kiss-wink-heart',\n    'kiwi-bird',\n    'lambda',\n    'landmark',\n    'language',\n    'laptop',\n    'laptop-code',\n    'laptop-house',\n    'laptop-medical',\n    'laugh',\n    'laugh-beam',\n    'laugh-squint',\n    'laugh-wink',\n    'layer-group',\n    'leaf',\n    'lemon',\n    'less-than',\n    'less-than-equal',\n    'level-down-alt',\n    'level-up-alt',\n    'life-ring',\n    'lightbulb',\n    'link',\n    'lira-sign',\n    'list',\n    'list-alt',\n    'list-ol',\n    'list-ul',\n    'location-arrow',\n    'lock',\n    'lock-open',\n    'long-arrow-alt-down',\n    'long-arrow-alt-left',\n    'long-arrow-alt-right',\n    'long-arrow-alt-up',\n    'low-vision',\n    'luggage-cart',\n    'lungs',\n    'lungs-virus',\n    'magic',\n    'magnet',\n    'mail-bulk',\n    'male',\n    'map',\n    'map-marked',\n    'map-marked-alt',\n    'map-marker',\n    'map-marker-alt',\n    'map-pin',\n    'map-signs',\n    'marker',\n    'mars',\n    'mars-double',\n    'mars-stroke',\n    'mars-stroke-h',\n    'mars-stroke-v',\n    'mask',\n    'medal',\n    'medkit',\n    'meh',\n    'meh-blank',\n    'meh-rolling-eyes',\n    'memory',\n    'menorah',\n    'mercury',\n    'meteor',\n    'microchip',\n    'microphone',\n    'microphone-alt',\n    'microphone-alt-slash',\n    'microphone-slash',\n    'microscope',\n    'minus',\n    'minus-circle',\n    'minus-square',\n    'mitten',\n    'mobile',\n    'mobile-alt',\n    'money-bill',\n    'money-bill-alt',\n    'money-bill-wave',\n    'money-bill-wave-alt',\n    'money-check',\n    'money-check-alt',\n    'monument',\n    'moon',\n    'mortar-pestle',\n    'mosque',\n    'motorcycle',\n    'mountain',\n    'mouse',\n    'mouse-pointer',\n    'mug-hot',\n    'music',\n    'network-wired',\n    'neuter',\n    'newspaper',\n    'not-equal',\n    'notes-medical',\n    'object-group',\n    'object-ungroup',\n    'oil-can',\n    'om',\n    'omega',\n    'otter',\n    'outdent',\n    'pager',\n    'paint-brush',\n    'paint-roller',\n    'palette',\n    'pallet',\n    'paper-plane',\n    'paperclip',\n    'parachute-box',\n    'paragraph',\n    'parking',\n    'passport',\n    'pastafarianism',\n    'paste',\n    'pause',\n    'pause-circle',\n    'paw',\n    'peace',\n    'pen',\n    'pen-alt',\n    'pen-fancy',\n    'pen-nib',\n    'pen-square',\n    'pencil-alt',\n    'pencil-ruler',\n    'people-arrows',\n    'people-carry',\n    'pepper-hot',\n    'percent',\n    'percentage',\n    'person-booth',\n    'phone',\n    'phone-alt',\n    'phone-slash',\n    'phone-square',\n    'phone-square-alt',\n    'phone-volume',\n    'photo-video',\n    'pi',\n    'piggy-bank',\n    'pills',\n    'pizza-slice',\n    'place-of-worship',\n    'plane',\n    'plane-arrival',\n    'plane-departure',\n    'plane-slash',\n    'play',\n    'play-circle',\n    'plug',\n    'plus',\n    'plus-circle',\n    'plus-square',\n    'podcast',\n    'poll',\n    'poll-h',\n    'poo',\n    'poo-storm',\n    'poop',\n    'portrait',\n    'pound-sign',\n    'power-off',\n    'pray',\n    'praying-hands',\n    'prescription',\n    'prescription-bottle',\n    'prescription-bottle-alt',\n    'print',\n    'procedures',\n    'project-diagram',\n    'pump-medical',\n    'pump-soap',\n    'puzzle-piece',\n    'qrcode',\n    'question',\n    'question-circle',\n    'quidditch',\n    'quote-left',\n    'quote-right',\n    'quran',\n    'radiation',\n    'radiation-alt',\n    'rainbow',\n    'random',\n    'receipt',\n    'record-vinyl',\n    'recycle',\n    'redo',\n    'redo-alt',\n    'registered',\n    'remove-format',\n    'reply',\n    'reply-all',\n    'republican',\n    'restroom',\n    'retweet',\n    'ribbon',\n    'ring',\n    'road',\n    'robot',\n    'rocket',\n    'route',\n    'rss',\n    'rss-square',\n    'ruble-sign',\n    'ruler',\n    'ruler-combined',\n    'ruler-horizontal',\n    'ruler-vertical',\n    'running',\n    'rupee-sign',\n    'sad-cry',\n    'sad-tear',\n    'satellite',\n    'satellite-dish',\n    'save',\n    'school',\n    'screwdriver',\n    'scroll',\n    'sd-card',\n    'search',\n    'search-dollar',\n    'search-location',\n    'search-minus',\n    'search-plus',\n    'seedling',\n    'server',\n    'shapes',\n    'share',\n    'share-alt',\n    'share-alt-square',\n    'share-square',\n    'shekel-sign',\n    'shield-alt',\n    'shield-virus',\n    'ship',\n    'shipping-fast',\n    'shoe-prints',\n    'shopping-bag',\n    'shopping-basket',\n    'shopping-cart',\n    'shower',\n    'shuttle-van',\n    'sigma',\n    'sign',\n    'sign-in-alt',\n    'sign-language',\n    'sign-out-alt',\n    'signal',\n    'signal-alt',\n    'signal-alt-slash',\n    'signal-slash',\n    'signature',\n    'sim-card',\n    'sink',\n    'sitemap',\n    'skating',\n    'skiing',\n    'skiing-nordic',\n    'skull',\n    'skull-crossbones',\n    'slash',\n    'sleigh',\n    'sliders-h',\n    'smile',\n    'smile-beam',\n    'smile-wink',\n    'smog',\n    'smoking',\n    'smoking-ban',\n    'sms',\n    'snowboarding',\n    'snowflake',\n    'snowman',\n    'snowplow',\n    'soap',\n    'socks',\n    'solar-panel',\n    'sort',\n    'sort-alpha-down',\n    'sort-alpha-down-alt',\n    'sort-alpha-up',\n    'sort-alpha-up-alt',\n    'sort-amount-down',\n    'sort-amount-down-alt',\n    'sort-amount-up',\n    'sort-amount-up-alt',\n    'sort-down',\n    'sort-numeric-down',\n    'sort-numeric-down-alt',\n    'sort-numeric-up',\n    'sort-numeric-up-alt',\n    'sort-up',\n    'spa',\n    'space-shuttle',\n    'spell-check',\n    'spider',\n    'spinner',\n    'splotch',\n    'spray-can',\n    'square',\n    'square-full',\n    'square-root',\n    'square-root-alt',\n    'stamp',\n    'star',\n    'star-and-crescent',\n    'star-half',\n    'star-half-alt',\n    'star-of-david',\n    'star-of-life',\n    'step-backward',\n    'step-forward',\n    'stethoscope',\n    'sticky-note',\n    'stop',\n    'stop-circle',\n    'stopwatch',\n    'stopwatch-20',\n    'store',\n    'store-alt',\n    'store-alt-slash',\n    'store-slash',\n    'stream',\n    'street-view',\n    'strikethrough',\n    'stroopwafel',\n    'subscript',\n    'subway',\n    'suitcase',\n    'suitcase-rolling',\n    'sun',\n    'superscript',\n    'surprise',\n    'swatchbook',\n    'swimmer',\n    'swimming-pool',\n    'synagogue',\n    'sync',\n    'sync-alt',\n    'syringe',\n    'table',\n    'table-tennis',\n    'tablet',\n    'tablet-alt',\n    'tablets',\n    'tachometer-alt',\n    'tag',\n    'tags',\n    'tally',\n    'tape',\n    'tasks',\n    'taxi',\n    'teeth',\n    'teeth-open',\n    'temperature-high',\n    'temperature-low',\n    'tenge',\n    'terminal',\n    'text-height',\n    'text-width',\n    'th',\n    'th-large',\n    'th-list',\n    'theater-masks',\n    'thermometer',\n    'thermometer-empty',\n    'thermometer-full',\n    'thermometer-half',\n    'thermometer-quarter',\n    'thermometer-three-quarters',\n    'theta',\n    'thumbs-down',\n    'thumbs-up',\n    'thumbtack',\n    'ticket-alt',\n    'tilde',\n    'times',\n    'times-circle',\n    'tint',\n    'tint-slash',\n    'tired',\n    'toggle-off',\n    'toggle-on',\n    'toilet',\n    'toilet-paper',\n    'toilet-paper-slash',\n    'toolbox',\n    'tools',\n    'tooth',\n    'torah',\n    'torii-gate',\n    'tractor',\n    'trademark',\n    'traffic-light',\n    'trailer',\n    'train',\n    'tram',\n    'transgender',\n    'transgender-alt',\n    'trash',\n    'trash-alt',\n    'trash-restore',\n    'trash-restore-alt',\n    'tree',\n    'trophy',\n    'truck',\n    'truck-loading',\n    'truck-monster',\n    'truck-moving',\n    'truck-pickup',\n    'tshirt',\n    'tty',\n    'tv',\n    'umbrella',\n    'umbrella-beach',\n    'underline',\n    'undo',\n    'undo-alt',\n    'union',\n    'universal-access',\n    'university',\n    'unlink',\n    'unlock',\n    'unlock-alt',\n    'upload',\n    'user',\n    'user-alt',\n    'user-alt-slash',\n    'user-astronaut',\n    'user-check',\n    'user-circle',\n    'user-clock',\n    'user-cog',\n    'user-edit',\n    'user-friends',\n    'user-graduate',\n    'user-injured',\n    'user-lock',\n    'user-md',\n    'user-minus',\n    'user-ninja',\n    'user-nurse',\n    'user-plus',\n    'user-secret',\n    'user-shield',\n    'user-slash',\n    'user-tag',\n    'user-tie',\n    'user-times',\n    'users',\n    'users-cog',\n    'users-slash',\n    'utensil-spoon',\n    'utensils',\n    'value-absolute',\n    'vector-square',\n    'venus',\n    'venus-double',\n    'venus-mars',\n    'vest',\n    'vest-patches',\n    'vial',\n    'vials',\n    'video',\n    'video-slash',\n    'vihara',\n    'virus',\n    'virus-slash',\n    'viruses',\n    'voicemail',\n    'volleyball-ball',\n    'volume',\n    'volume-down',\n    'volume-mute',\n    'volume-off',\n    'volume-slash',\n    'volume-up',\n    'vote-yea',\n    'vr-cardboard',\n    'walking',\n    'wallet',\n    'warehouse',\n    'water',\n    'wave-square',\n    'weight',\n    'weight-hanging',\n    'wheelchair',\n    'wifi',\n    'wifi-slash',\n    'wind',\n    'window-close',\n    'window-maximize',\n    'window-minimize',\n    'window-restore',\n    'wine-bottle',\n    'wine-glass',\n    'wine-glass-alt',\n    'won-sign',\n    'wrench',\n    'x-ray',\n    'yen-sign',\n    'yin-yang'\n  ]\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/ImageViewer/index.ts",
    "content": "import ImageViewer from './src/ImageViewer.vue'\nimport { isClient } from '@/utils/is'\nimport { createVNode, render, VNode } from 'vue'\nimport { ImageViewerProps } from './src/types'\n\nlet instance: Nullable<VNode> = null\n\nexport function createImageViewer(options: ImageViewerProps) {\n  if (!isClient) return\n  const {\n    urlList,\n    initialIndex = 0,\n    infinite = true,\n    hideOnClickModal = false,\n    teleported = false,\n    zIndex = 2000,\n    show = true\n  } = options\n\n  const propsData: Partial<ImageViewerProps> = {}\n  const container = document.createElement('div')\n  propsData.urlList = urlList\n  propsData.initialIndex = initialIndex\n  propsData.infinite = infinite\n  propsData.hideOnClickModal = hideOnClickModal\n  propsData.teleported = teleported\n  propsData.zIndex = zIndex\n  propsData.show = show\n\n  document.body.appendChild(container)\n  instance = createVNode(ImageViewer, propsData)\n  render(instance, container)\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/ImageViewer/src/ImageViewer.vue",
    "content": "<script lang=\"ts\" setup>\nimport { PropType } from 'vue'\nimport { propTypes } from '@/utils/propTypes'\n\ndefineOptions({ name: 'ImageViewer' })\n\nconst props = defineProps({\n  urlList: {\n    type: Array as PropType<string[]>,\n    default: (): string[] => []\n  },\n  zIndex: propTypes.number.def(200),\n  initialIndex: propTypes.number.def(0),\n  infinite: propTypes.bool.def(true),\n  hideOnClickModal: propTypes.bool.def(false),\n  teleported: propTypes.bool.def(false),\n  show: propTypes.bool.def(false)\n})\n\nconst getBindValue = computed(() => {\n  const propsData: Recordable = { ...props }\n  delete propsData.show\n  return propsData\n})\n\nconst show = ref(props.show)\n\nconst close = () => {\n  show.value = false\n}\n</script>\n\n<template>\n  <ElImageViewer v-if=\"show\" v-bind=\"getBindValue\" @close=\"close\" />\n</template>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/ImageViewer/src/types.ts",
    "content": "export interface ImageViewerProps {\n  urlList?: string[]\n  zIndex?: number\n  initialIndex?: number\n  infinite?: boolean\n  hideOnClickModal?: boolean\n  teleported?: boolean\n  show?: boolean\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Infotip/index.ts",
    "content": "import Infotip from './src/Infotip.vue'\n\nexport { Infotip }\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Infotip/src/Infotip.vue",
    "content": "<script lang=\"ts\" setup>\nimport { PropType } from 'vue'\nimport { useDesign } from '@/hooks/web/useDesign'\nimport { propTypes } from '@/utils/propTypes'\nimport { TipSchema } from '@/types/infoTip'\n\ndefineOptions({ name: 'InfoTip' })\n\nconst { getPrefixCls } = useDesign()\n\nconst prefixCls = getPrefixCls('infotip')\n\ndefineProps({\n  title: propTypes.string.def(''),\n  schema: {\n    type: Array as PropType<Array<string | TipSchema>>,\n    required: true,\n    default: () => []\n  },\n  showIndex: propTypes.bool.def(true),\n  highlightColor: propTypes.string.def('var(--el-color-primary)')\n})\n\nconst emit = defineEmits(['click'])\n\nconst keyClick = (key: string) => {\n  emit('click', key)\n}\n</script>\n\n<template>\n  <div\n    :class=\"[\n      prefixCls,\n      'p-20px mb-20px border-1px border-solid border-[var(--el-color-primary)] bg-[var(--el-color-primary-light-9)]'\n    ]\"\n  >\n    <div v-if=\"title\" :class=\"[`${prefixCls}__header`, 'flex items-center']\">\n      <Icon :size=\"22\" color=\"var(--el-color-primary)\" icon=\"ep:warning-filled\" />\n      <span :class=\"[`${prefixCls}__title`, 'pl-5px text-16px font-bold']\">{{ title }}</span>\n    </div>\n    <div :class=\"`${prefixCls}__content`\">\n      <p v-for=\"(item, $index) in schema\" :key=\"$index\" class=\"mt-15px text-14px\">\n        <Highlight\n          :color=\"highlightColor\"\n          :keys=\"typeof item === 'string' ? [] : item.keys\"\n          @click=\"keyClick\"\n        >\n          {{ showIndex ? `${$index + 1}、` : '' }}{{ typeof item === 'string' ? item : item.label }}\n        </Highlight>\n      </p>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/InputPassword/index.ts",
    "content": "import InputPassword from './src/InputPassword.vue'\n\nexport { InputPassword }\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/InputPassword/src/InputPassword.vue",
    "content": "<script lang=\"ts\" setup>\nimport { propTypes } from '@/utils/propTypes'\nimport { useConfigGlobal } from '@/hooks/web/useConfigGlobal'\nimport type { ZxcvbnResult } from '@zxcvbn-ts/core'\nimport { zxcvbn } from '@zxcvbn-ts/core'\nimport { useDesign } from '@/hooks/web/useDesign'\n\ndefineOptions({ name: 'InputPassword' })\n\nconst { getPrefixCls } = useDesign()\n\nconst prefixCls = getPrefixCls('input-password')\n\nconst props = defineProps({\n  // 是否显示密码强度\n  strength: propTypes.bool.def(false),\n  modelValue: propTypes.string.def('')\n})\n\nwatch(\n  () => props.modelValue,\n  (val: string) => {\n    if (val === unref(valueRef)) return\n    valueRef.value = val\n  }\n)\n\nconst { configGlobal } = useConfigGlobal()\n\nconst emit = defineEmits(['update:modelValue'])\n\n// 设置input的type属性\nconst textType = ref<'password' | 'text'>('password')\n\nconst changeTextType = () => {\n  textType.value = unref(textType) === 'text' ? 'password' : 'text'\n}\n\n// 输入框的值\nconst valueRef = ref(props.modelValue)\n\n// 监听\nwatch(\n  () => valueRef.value,\n  (val: string) => {\n    emit('update:modelValue', val)\n  }\n)\n\n// 获取密码强度\nconst getPasswordStrength = computed(() => {\n  const value = unref(valueRef)\n  const zxcvbnRef = zxcvbn(unref(valueRef)) as ZxcvbnResult\n  return value ? zxcvbnRef.score : -1\n})\n\nconst getIconName = computed(() => (unref(textType) === 'password' ? 'ep:hide' : 'ep:view'))\n</script>\n\n<template>\n  <div :class=\"[prefixCls, `${prefixCls}--${configGlobal?.size}`]\">\n    <ElInput v-model=\"valueRef\" :type=\"textType\" v-bind=\"$attrs\">\n      <template #suffix>\n        <Icon :icon=\"getIconName\" class=\"el-input__icon cursor-pointer\" @click=\"changeTextType\" />\n      </template>\n    </ElInput>\n    <div\n      v-if=\"strength\"\n      :class=\"`${prefixCls}__bar`\"\n      class=\"relative mb-6px ml-auto mr-auto mt-10px h-6px\"\n    >\n      <div :class=\"`${prefixCls}__bar--fill`\" :data-score=\"getPasswordStrength\"></div>\n    </div>\n  </div>\n</template>\n\n<style lang=\"scss\" scoped>\n$prefix-cls: #{$namespace}-input-password;\n\n.#{$prefix-cls} {\n  :deep(.#{$elNamespace}-input__clear) {\n    margin-left: 5px;\n  }\n\n  &__bar {\n    background-color: var(--el-text-color-disabled);\n    border-radius: var(--el-border-radius-base);\n\n    &::before,\n    &::after {\n      position: absolute;\n      z-index: 10;\n      display: block;\n      width: 20%;\n      height: inherit;\n      background-color: transparent;\n      border-color: var(--el-color-white);\n      border-style: solid;\n      border-width: 0 5px;\n      content: '';\n    }\n\n    &::before {\n      left: 20%;\n    }\n\n    &::after {\n      right: 20%;\n    }\n\n    &--fill {\n      position: absolute;\n      width: 0;\n      height: inherit;\n      background-color: transparent;\n      border-radius: inherit;\n      transition:\n        width 0.5s ease-in-out,\n        background 0.25s;\n\n      &[data-score='0'] {\n        width: 20%;\n        background-color: var(--el-color-danger);\n      }\n\n      &[data-score='1'] {\n        width: 40%;\n        background-color: var(--el-color-danger);\n      }\n\n      &[data-score='2'] {\n        width: 60%;\n        background-color: var(--el-color-warning);\n      }\n\n      &[data-score='3'] {\n        width: 80%;\n        background-color: var(--el-color-success);\n      }\n\n      &[data-score='4'] {\n        width: 100%;\n        background-color: var(--el-color-success);\n      }\n    }\n  }\n\n  &--mini > &__bar {\n    border-radius: var(--el-border-radius-small);\n  }\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/InputWithColor/index.vue",
    "content": "<template>\n  <el-input v-model=\"valueRef\" v-bind=\"$attrs\">\n    <template #append>\n      <el-color-picker v-model=\"colorRef\" :predefine=\"PREDEFINE_COLORS\" />\n    </template>\n  </el-input>\n</template>\n\n<script lang=\"ts\" setup>\nimport { propTypes } from '@/utils/propTypes'\nimport { PREDEFINE_COLORS } from '@/utils/color'\n\n/**\n * 带颜色选择器输入框\n */\ndefineOptions({ name: 'InputWithColor' })\n\nconst props = defineProps({\n  modelValue: propTypes.string.def('').isRequired,\n  color: propTypes.string.def('').isRequired\n})\n\nwatch(\n  () => props.modelValue,\n  (val: string) => {\n    if (val === unref(valueRef)) return\n    valueRef.value = val\n  }\n)\n\nconst emit = defineEmits(['update:modelValue', 'update:color'])\n\n// 输入框的值\nconst valueRef = ref(props.modelValue)\nwatch(\n  () => valueRef.value,\n  (val: string) => {\n    emit('update:modelValue', val)\n  }\n)\n// 颜色\nconst colorRef = ref(props.color)\nwatch(\n  () => colorRef.value,\n  (val: string) => {\n    emit('update:color', val)\n  }\n)\n</script>\n<style scoped lang=\"scss\">\n:deep(.el-input-group__append) {\n  padding: 0;\n  .el-color-picker__trigger {\n    padding: 0;\n    border-left: none;\n    border-radius: 0 var(--el-input-border-radius) var(--el-input-border-radius) 0;\n  }\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/MagicCubeEditor/index.vue",
    "content": "<template>\n  <div class=\"relative\">\n    <table class=\"cube-table\">\n      <!-- 底层：魔方矩阵 -->\n      <tbody>\n        <tr v-for=\"(rowCubes, row) in cubes\" :key=\"row\">\n          <td\n            v-for=\"(cube, col) in rowCubes\"\n            :key=\"col\"\n            :class=\"['cube', { active: cube.active }]\"\n            :style=\"{\n              width: `${cubeSize}px`,\n              height: `${cubeSize}px`\n            }\"\n            @click=\"handleCubeClick(row, col)\"\n            @mouseenter=\"handleCellHover(row, col)\"\n          >\n            <Icon icon=\"ep-plus\" />\n          </td>\n        </tr>\n      </tbody>\n      <!-- 顶层：热区 -->\n      <div\n        v-for=\"(hotArea, index) in hotAreas\"\n        :key=\"index\"\n        class=\"hot-area\"\n        :style=\"{\n          top: `${cubeSize * hotArea.top}px`,\n          left: `${cubeSize * hotArea.left}px`,\n          height: `${cubeSize * hotArea.height}px`,\n          width: `${cubeSize * hotArea.width}px`\n        }\"\n        @click=\"handleHotAreaSelected(hotArea, index)\"\n        @mouseover=\"exitHotAreaSelectMode\"\n      >\n        <!-- 右上角热区删除按钮 -->\n        <div\n          v-if=\"selectedHotAreaIndex === index\"\n          class=\"btn-delete\"\n          @click=\"handleDeleteHotArea(index)\"\n        >\n          <Icon icon=\"ep:circle-close-filled\" />\n        </div>\n        {{ `${hotArea.width}×${hotArea.height}` }}\n      </div>\n    </table>\n  </div>\n</template>\n<script lang=\"ts\" setup>\nimport { propTypes } from '@/utils/propTypes'\nimport * as vueTypes from 'vue-types'\nimport { Point, Rect, isContains, isOverlap, createRect } from './util'\n\n// 魔方编辑器\n// 有两部分组成：\n// 1. 魔方矩阵：位于底层，由方块组件的二维表格，用于创建热区\n//    操作方法：\n//    1.1 点击其中一个方块就会进入热区选择模式\n//    1.2 再次点击另外一个方块时，结束热区选择模式\n//    1.3 在两个方块中间的区域创建热区\n//    如果两次点击的都是同一方块，就只创建一个格子的热区\n// 2. 热区：位于顶层，采用绝对定位，覆盖在魔方矩阵上面。\ndefineOptions({ name: 'MagicCubeEditor' })\n\n/**\n * 方块\n * @property active 是否激活\n */\ntype Cube = Point & { active: boolean }\n\n// 定义属性\nconst props = defineProps({\n  // 热区列表\n  modelValue: vueTypes.array<any>().isRequired,\n  // 行数，默认 4 行\n  rows: propTypes.number.def(4),\n  // 列数，默认 4 列\n  cols: propTypes.number.def(4),\n  // 方块大小，单位px，默认75px\n  cubeSize: propTypes.number.def(75)\n})\n\n// 魔方矩阵：所有的方块\nconst cubes = ref<Cube[][]>([])\n// 监听行数、列数变化\nwatch(\n  () => [props.rows, props.cols],\n  () => {\n    // 清空魔方\n    cubes.value = []\n    if (!props.rows || !props.cols) return\n\n    // 初始化魔方\n    for (let row = 0; row < props.rows; row++) {\n      cubes.value[row] = []\n      for (let col = 0; col < props.cols; col++) {\n        cubes.value[row].push({ x: col, y: row, active: false })\n      }\n    }\n  },\n  { immediate: true }\n)\n\n// 热区列表\nconst hotAreas = ref<Rect[]>([])\n// 初始化热区\nwatch(\n  () => props.modelValue,\n  () => (hotAreas.value = props.modelValue || []),\n  { immediate: true }\n)\n\n// 热区起始方块\nconst hotAreaBeginCube = ref<Cube>()\n// 是否开启了热区选择模式\nconst isHotAreaSelectMode = () => !!hotAreaBeginCube.value\n/**\n * 处理鼠标点击方块\n *\n * @param currentRow 当前行号\n * @param currentCol 当前列号\n */\nconst handleCubeClick = (currentRow: number, currentCol: number) => {\n  const currentCube = cubes.value[currentRow][currentCol]\n  // 情况1：进入热区选择模式\n  if (!isHotAreaSelectMode()) {\n    hotAreaBeginCube.value = currentCube\n    hotAreaBeginCube.value.active = true\n    return\n  }\n\n  // 情况2：结束热区选择模式\n  hotAreas.value.push(createRect(hotAreaBeginCube.value!, currentCube))\n  // 结束热区选择模式\n  exitHotAreaSelectMode()\n  // 创建后就选中热区\n  let hotAreaIndex = hotAreas.value.length - 1\n  handleHotAreaSelected(hotAreas.value[hotAreaIndex], hotAreaIndex)\n  // 发送热区变动通知\n  emitUpdateModelValue()\n}\n/**\n * 处理鼠标经过方块\n *\n * @param currentRow 当前行号\n * @param currentCol 当前列号\n */\nconst handleCellHover = (currentRow: number, currentCol: number) => {\n  // 当前没有进入热区选择模式\n  if (!isHotAreaSelectMode()) return\n\n  // 当前已选的区域\n  const currentSelectedArea = createRect(\n    hotAreaBeginCube.value!,\n    cubes.value[currentRow][currentCol]\n  )\n  // 热区不允许重叠\n  for (const hotArea of hotAreas.value) {\n    // 检查是否重叠\n    if (isOverlap(hotArea, currentSelectedArea)) {\n      // 结束热区选择模式\n      exitHotAreaSelectMode()\n\n      return\n    }\n  }\n\n  // 激活选中区域内部的方块\n  eachCube((_, __, cube) => {\n    cube.active = isContains(currentSelectedArea, cube)\n  })\n}\n/**\n * 处理热区删除\n *\n * @param index 热区索引\n */\nconst handleDeleteHotArea = (index: number) => {\n  hotAreas.value.splice(index, 1)\n  // 结束热区选择模式\n  exitHotAreaSelectMode()\n  // 发送热区变动通知\n  emitUpdateModelValue()\n}\n\n// 发送模型更新\nconst emit = defineEmits(['update:modelValue', 'hotAreaSelected'])\n// 发送热区变动通知\nconst emitUpdateModelValue = () => emit('update:modelValue', hotAreas)\n\n// 热区选中\nconst selectedHotAreaIndex = ref(0)\nconst handleHotAreaSelected = (hotArea: Rect, index: number) => {\n  selectedHotAreaIndex.value = index\n  emit('hotAreaSelected', hotArea, index)\n}\n\n/**\n * 结束热区选择模式\n */\nfunction exitHotAreaSelectMode() {\n  // 移除方块激活标记\n  eachCube((_, __, cube) => {\n    if (cube.active) {\n      cube.active = false\n    }\n  })\n\n  // 清除起点\n  hotAreaBeginCube.value = undefined\n}\n\n/**\n * 迭代魔方矩阵\n * @param callback 回调\n */\nconst eachCube = (callback: (x: number, y: number, cube: Cube) => void) => {\n  for (let x = 0; x < cubes.value.length; x++) {\n    for (let y = 0; y < cubes.value[x].length; y++) {\n      callback(x, y, cubes.value[x][y])\n    }\n  }\n}\n</script>\n<style lang=\"scss\" scoped>\n.cube-table {\n  position: relative;\n  border-spacing: 0;\n  border-collapse: collapse;\n\n  .cube {\n    border: 1px solid var(--el-border-color);\n    text-align: center;\n    color: var(--el-text-color-secondary);\n    cursor: pointer;\n    box-sizing: border-box;\n    &.active {\n      background: var(--el-color-primary-light-9);\n    }\n  }\n\n  .hot-area {\n    position: absolute;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    border: 1px solid var(--el-color-primary);\n    background: var(--el-color-primary-light-8);\n    color: var(--el-color-primary);\n    box-sizing: border-box;\n    border-spacing: 0;\n    border-collapse: collapse;\n    cursor: pointer;\n\n    .btn-delete {\n      z-index: 1;\n      position: absolute;\n      top: -8px;\n      right: -8px;\n      height: 16px;\n      width: 16px;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      border-radius: 50%;\n      background-color: #fff;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/MagicCubeEditor/util.ts",
    "content": "// 坐标点\nexport interface Point {\n  x: number\n  y: number\n}\n\n// 矩形\nexport interface Rect {\n  // 左上角 X 轴坐标\n  left: number\n  // 左上角 Y 轴坐标\n  top: number\n  // 右下角 X 轴坐标\n  right: number\n  // 右下角 Y 轴坐标\n  bottom: number\n  // 矩形宽度\n  width: number\n  // 矩形高度\n  height: number\n}\n\n/**\n * 判断两个矩形是否重叠\n * @param a 矩形 A\n * @param b 矩形 B\n */\nexport const isOverlap = (a: Rect, b: Rect): boolean => {\n  return (\n    a.left < b.left + b.width &&\n    a.left + a.width > b.left &&\n    a.top < b.top + b.height &&\n    a.height + a.top > b.top\n  )\n}\n/**\n * 检查坐标点是否在矩形内\n * @param hotArea 矩形\n * @param point 坐标\n */\nexport const isContains = (hotArea: Rect, point: Point): boolean => {\n  return (\n    point.x >= hotArea.left &&\n    point.x < hotArea.right &&\n    point.y >= hotArea.top &&\n    point.y < hotArea.bottom\n  )\n}\n\n/**\n * 在两个坐标点中间，创建一个矩形\n *\n * 存在以下情况：\n * 1. 两个坐标点是同一个位置，只占一个位置的正方形，宽高都为 1\n * 2. X 轴坐标相同，只占一行的矩形，高度为 1\n * 3. Y 轴坐标相同，只占一列的矩形，宽度为 1\n * 4. 多行多列的矩形\n *\n * @param a 坐标点一\n * @param b 坐标点二\n */\nexport const createRect = (a: Point, b: Point): Rect => {\n  // 计算矩形的范围\n  const [left, left2] = [a.x, b.x].sort()\n  const [top, top2] = [a.y, b.y].sort()\n  const right = left2 + 1\n  const bottom = top2 + 1\n  const height = bottom - top\n  const width = right - left\n\n  return { left, right, top, bottom, height, width }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Materials/index.ts",
    "content": "/*\n * @Author: Gaoxs\n * @Date: 2023-05-21 23:36:03\n * @LastEditors: Gaoxs\n * @Description:\n */\nimport Materials from './src/Materials.vue'\nimport EditorMaterials from './src/EditorMaterials.vue'\n\nexport { Materials, EditorMaterials }\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Materials/src/Materials.vue",
    "content": "<template>\n  <div v-if=\"type == 'image'\">\n\n    <ul\n      v-for=\"(item, index) in value\"\n      :key=\"index\"\n      class=\"el-upload-list el-upload-list--picture-card\"\n    >\n      <li\n        tabindex=\"0\"\n        class=\"el-upload-list__item is-ready\"\n        :style=\"'width: ' + width + 'px;height: ' + height + 'px'\"\n      >\n        <div>\n          <img :src=\"item\" alt=\"\" class=\"el-upload-list__item-thumbnail\" />\n          <span class=\"el-upload-list__item-actions\">\n            <span\n              v-if=\"index != 0\"\n              class=\"el-upload-list__item-preview\"\n              @click=\"moveMaterial(index, 'up')\"\n            >\n            <Icon icon=\"ep:back\" />\n            </span>\n            <span class=\"el-upload-list__item-preview\" @click=\"zoomMaterial(index)\">\n              <i class=\"el-icon-view\"></i>\n              <Icon icon=\"ep:view\" />\n            </span>\n            <span class=\"el-upload-list__item-delete\" @click=\"deleteMaterial(index)\">\n              <Icon icon=\"ep:delete\" />\n            </span>\n            <span\n              v-if=\"index != value.length - 1\"\n              class=\"el-upload-list__item-preview\"\n              @click=\"moveMaterial(index, 'down')\"\n            >\n              <Icon icon=\"ep:right\" />\n            </span>\n          </span>\n        </div>\n      </li>\n    </ul>\n\n    <div\n      v-if=\"num > value.length\"\n      tabindex=\"0\"\n      class=\"el-upload el-upload--picture-card\"\n      :style=\"'width: ' + width + 'px;height: ' + height + 'px;' + 'line-height:' + height + 'px;'\"\n      @click=\"toSeleteMaterial\">\n      <Icon icon=\"ep:plus\" />\n    </div>\n\n    <el-dialog title=\"图片素材库\" append-to-body v-model=\"listDialogVisible\" width=\"70%\">\n      <el-container>\n        <el-aside width=\"100px\">\n          <div style=\"margin-bottom: 10px\">\n            <el-button class=\"el-icon-plus\" size=\"small\" @click=\"handleMaterialgroupAdd()\">\n              添加分组\n            </el-button>\n          </div>\n          <el-tabs\n            v-model=\"materialgroupObjId\"\n            v-loading=\"materialgroupLoading\"\n            tab-position=\"left\"\n            @tab-click=\"tabClick\"\n          >\n            <el-tab-pane v-for=\"item in materialgroupList\" :key=\"item.id\" :name=\"item.id\">\n              <template #label>\n                <span> {{ item.name }}</span>\n              </template>\n            </el-tab-pane>\n          </el-tabs>\n        </el-aside>\n        <el-main>\n          <el-card>\n            <template #header>\n              <div>\n                <el-row>\n                  <el-col :span=\"12\">\n                    <span>{{ materialgroupObj.name }}</span>\n                    <span v-if=\"materialgroupObj.id != '-1'\">\n                      <el-button\n                        size=\"small\"\n                        type=\"text\"\n                        class=\"el-icon-edit\"\n                        style=\"margin-left: 10px\"\n                        @click=\"handleMaterialgroupEdit(materialgroupObj)\"\n                        >重命名</el-button\n                      >\n                      <el-button\n                        size=\"small\"\n                        type=\"text\"\n                        class=\"el-icon-delete\"\n                        style=\"margin-left: 10px; color: red\"\n                        @click=\"materialgroupDelete(materialgroupObj)\"\n                        >删除</el-button\n                      >\n                    </span>\n                  </el-col>\n                  <el-col :span=\"12\" style=\"text-align: right\">\n                    <el-upload\n                      :action=\"uploadApi\"\n                      :headers=\"headers\"\n                      :file-list=\"[]\"\n                      :on-progress=\"handleProgress\"\n                      :before-upload=\"beforeUpload\"\n                      :on-success=\"handleSuccess\"\n                      :data=\"{ type: 1 }\"\n                      multiple\n                    >\n                      <el-button size=\"small\" type=\"primary\">批量上传</el-button>\n                    </el-upload>\n                  </el-col>\n                </el-row>\n              </div>\n            </template>\n            <div v-loading=\"tableLoading\">\n              <el-alert\n                v-if=\"tableData.length <= 0\"\n                title=\"暂无数据\"\n                type=\"info\"\n                :closable=\"false\"\n                center\n                show-icon\n              />\n              <el-checkbox-group v-model=\"urls\" :max=\"num - value.length\">\n                <el-row :gutter=\"5\">\n                  <el-col v-for=\"(item, index) in tableData\" :key=\"index\" :span=\"4\">\n                    <el-card :body-style=\"{ padding: '5px' }\">\n                      <el-image\n                        style=\"width: 100%; height: 100px\"\n                        :src=\"item.url\"\n                        fit=\"contain\"\n                        :preview-src-list=\"[item.url]\"\n                        :z-index=\"9999\"\n                      />\n                      <div>\n                        <el-checkbox class=\"material-name\" :label=\"item.url\"> 选择 </el-checkbox>\n                        <el-row>\n                          <el-col :span=\"24\" class=\"col-do\">\n                            <el-button type=\"text\" size=\"medium\" @click=\"materialDel(item)\"\n                              >删除</el-button\n                            >\n                          </el-col>\n                        </el-row>\n                      </div>\n                    </el-card>\n                  </el-col>\n                </el-row>\n              </el-checkbox-group>\n\n              <el-pagination\n                v-model:current-page=\"page.currentPage\"\n                :page-sizes=\"[12, 24]\"\n                :page-size=\"page.pageSize\"\n                layout=\"total, sizes, prev, pager, next, jumper\"\n                :total=\"page.total\"\n                class=\"pagination\"\n                @size-change=\"sizeChange\"\n                @current-change=\"pageChange\"\n              />\n            </div>\n          </el-card>\n        </el-main>\n      </el-container>\n      <template #footer>\n        <span class=\"dialog-footer\">\n          <el-button @click=\"listDialogVisible = false\">取 消</el-button>\n          <el-button type=\"primary\" @click=\"sureUrls\">确 定</el-button>\n        </span>\n      </template>\n    </el-dialog>\n  </div>\n</template>\n\n<script setup name=\"Materials\">\nimport { ElMessageBox } from 'element-plus'\nimport { ref } from 'vue'\nimport {\n  getList as materialgroupPage,\n  addObj as materialgroupAdd,\n  delObj as materialgroupDel,\n  putObj as materialgroupEdit\n} from '@/api/tools/materialgroup'\nimport { getPage, addObj, delObj } from '@/api/tools/material'\nimport { getAccessToken } from '@/utils/auth'\n\nconst props = defineProps({\n  modelValue: {\n    type: Array,\n    default() {\n      return []\n    }\n  },\n  // 素材类型\n  type: {\n    type: String\n  },\n  // 素材限制数量，默认5个\n  num: {\n    type: Number,\n    default() {\n      return 5\n    }\n  },\n  // 宽度\n  width: {\n    type: Number,\n    default() {\n      return 150\n    }\n  },\n  // 宽度\n  height: {\n    type: Number,\n    default() {\n      return 150\n    }\n  }\n})\n\nconst headers = ref({\n  // Authorization: getToken()\n  Authorization: 'Bearer ' + getAccessToken(),\n})\n//const value = ref(props.value)\nconst dialogVisible = ref(false)\nconst url = ref('')\nconst listDialogVisible = ref(false)\nconst listDialogVisible2 = ref(false)\nconst materialgroupList = ref([])\nconst materialgroupObjId = ref('')\nconst materialgroupObj = ref({})\nconst materialgroupLoading = ref(false)\nconst tableData = ref([])\nconst resultNumber = ref(0)\nconst page = ref({\n  total: 0, // 总页数\n  currentPage: 1, // 当前页数\n  pageSize: 12, // 每页显示多少条\n  ascs: [], // 升序字段\n  descs: 'create_time' // 降序字段\n})\nconst tableLoading = ref(false)\nconst groupId = ref(null)\nconst urls = ref([])\n\nconst value = computed({\n  get() {\n    //  console.log('length:',props.modelValue.length)\n    if(!props.modelValue || props.modelValue.length == 0){\n        return []\n    }\n    if (Array.isArray(props.modelValue)) {\n      return props.modelValue\n    }\n    return [props.modelValue]\n    \n  }\n})\n\n// const store = useStore()\n\nconst uploadApi = import.meta.env.VITE_UPLOAD_URL\n\nfunction moveMaterial(index, type) {\n  if (type == 'up') {\n    const tempOption = value.value[index - 1]\n    value.value[index - 1] = value.value[index]\n    value.value[index] = tempOption\n  }\n  if (type == 'down') {\n    const tempOption = value.value[index + 1]\n    value.value[index + 1] = value.value[index]\n    value.value[index] = tempOption\n  }\n}\nfunction zoomMaterial(index) {\n  dialogVisible.value = true\n  url.value = value.value[index]\n}\nfunction deleteMaterial(index) {\n  // 修改为新的提示\n  ElMessageBox.confirm('是否确认删除？', '提示', {\n    confirmButtonText: '确定',\n    cancelButtonText: '取消',\n    type: 'warning'\n  }).then(function () {\n    value.value.splice(index, 1)\n    urls.value = []\n    // if (urls.value.length > 1 || props.num > 1) {\n    //   emit('update:modelValue', urls.value)\n    // } else {\n    //   emit('update:modelValue', urls.value[0])\n    // }\n    emit('update:modelValue', value.value)\n\n  })\n}\nfunction toSeleteMaterial() {\n  listDialogVisible.value = true\n  listDialogVisible2.value = true\n  if (tableData.value.length <= 0) {\n    getMaterialgroupPage()\n  }\n}\nfunction getMaterialgroupPage() {\n  materialgroupLoading.value = true\n  materialgroupPage({\n    total: 0, // 总页数\n    pageNo: 1, // 当前页数\n    pageSize: 100, // 每页显示多少条\n    ascs: [], // 升序字段\n    sort: 'create_time,desc' // 降序字段\n  }).then((response) => {\n    materialgroupLoading.value = false\n    materialgroupList.value = response\n    materialgroupList.value.unshift({\n      id: '-1',\n      name: '全部分组'\n    })\n\n    tabClick({\n      index: 0\n    })\n  })\n}\nfunction materialgroupDelete(materialgroupObj) {\n  ElMessageBox.confirm('是否确认删除该分组？', '提示', {\n    confirmButtonText: '确定',\n    cancelButtonText: '取消',\n    type: 'warning'\n  }).then(function () {\n    materialgroupDel(materialgroupObj.id).then(function () {\n      getMaterialgroupPage()\n    })\n  })\n}\nfunction handleMaterialgroupEdit(materialgroupObj) {\n  ElMessageBox.prompt('请输入分组名', '提示', {\n    confirmButtonText: '确定',\n    cancelButtonText: '取消',\n    inputValue: materialgroupObj.name\n  })\n    .then(({ value }) => {\n      materialgroupEdit({\n        id: materialgroupObj.id,\n        name: value\n      }).then(function () {\n        materialgroupObj.name = value\n        materialgroupList.value[materialgroupObj.index] = materialgroupObj\n      })\n    })\n    .catch(() => {})\n}\nfunction handleMaterialgroupAdd() {\n  ElMessageBox.prompt('请输入分组名', '提示', {\n    confirmButtonText: '确定',\n    cancelButtonText: '取消'\n  })\n    .then(({ value }) => {\n      materialgroupAdd({\n        name: value\n      }).then(function () {\n        getMaterialgroupPage()\n      })\n    })\n    .catch(() => {})\n}\nfunction tabClick(tab) {\n  urls.value = []\n  const index = Number(tab.index)\n  materialgroupObj.value = materialgroupList.value[index]\n\n  console.log('🚀 ~ file: Materials.vue:351 ~ tabClick ~ materialgroupObj:', materialgroupObj)\n\n  materialgroupObj.value.index = index\n  materialgroupObjId.value = materialgroupObj.value.id\n\n  page.value.currentPage = 1\n  page.value.total = 0\n  if (materialgroupObj.value.id != '-1') {\n    groupId.value = materialgroupObj.value.id\n  } else {\n    groupId.value = null\n  }\n  getMaterialPage(page.value)\n}\nfunction getMaterialPage(initPage) {\n  tableLoading.value = true\n  getPage(\n    Object.assign(\n      {\n        pageNo: initPage.currentPage,\n        pageSize: initPage.pageSize,\n        descs: initPage.descs,\n        ascs: initPage.ascs,\n        sort: 'create_time,desc'\n      },\n      {\n        groupId: groupId.value\n      }\n    )\n  )\n    .then((response) => {\n      console.log('🚀 ~ file: Materials.vue:382 ~ .then ~ response:', response.list)\n      console.log('🚀 ~ file: Materials.vue:382 ~ .then ~ response:', response.total)\n      page.value.total = response.total\n      page.value.currentPage = initPage.currentPage\n      page.value.pageSize = initPage.pageSize\n      tableData.value = response.list\n      console.log('🚀 ~ file: Materials.vue:387 ~ .then ~ tableData.value:', tableData.value)\n      tableLoading.value = false\n    })\n    .catch((error) => {\n      console.log('🚀 ~ file: Materials.vue:391 ~ getMaterialPage ~ error:', error)\n      tableLoading.value = false\n    })\n}\nfunction sizeChange(val) {\n  console.log(val)\n  page.value.currentPage = 1\n  page.value.pageSize = val\n  getMaterialPage(this.page)\n}\nfunction pageChange(val) {\n  console.log(val)\n  page.value.currentPage = val\n  // this.page.pageSize = val\n  getMaterialPage(page.value)\n}\nfunction materialDel(item) {\n  ElMessageBox.confirm('是否确认删除该素材？', '提示', {\n    confirmButtonText: '确定',\n    cancelButtonText: '取消',\n    type: 'warning'\n  }).then(function () {\n    delObj(item.id).finally(function () {\n      console.log('getMaterialPage')\n      getMaterialPage(page.value)\n    })\n  })\n}\nfunction handleProgress(event) {\n  console.log(event)\n}\nfunction handleSuccess(response, file, fileList) {\n  addObj({\n    type: '1',\n    groupId: groupId.value != '-1' ? groupId.value : null,\n    name: file.name,\n    url: response.data\n  }).then(() => {\n    resultNumber.value++\n    //console.log('res:', resultNumber.value)\n    //console.log('fileList:',fileList.length)\n    if (fileList.length === resultNumber.value) {\n      getMaterialPage(page.value)\n      //resultNumber.value = 0\n    }\n  })\n}\nfunction beforeUpload(file) {\n  const isPic =\n    file.type === 'image/jpeg' ||\n    file.type === 'image/png' ||\n    file.type === 'image/gif' ||\n    file.type === 'image/jpg'\n  const isLt2M = file.size / 1024 / 1024 < 2\n  if (!isPic) {\n    this.$message.error('上传图片只能是 JPG、JPEG、PNG、GIF 格式!')\n    return false\n  }\n  if (!isLt2M) {\n    this.$message.error('上传头像图片大小不能超过 2MB!')\n  }\n  return isPic && isLt2M\n}\n\nconst emit = defineEmits(['update:modelValue'])\nfunction sureUrls() {\n  console.log('value.value.length:',value.value.length)\n  urls.value.forEach((item) => {\n    value.value[value.value.length] = item\n  })\n  console.log('urls.value:',value.value)\n  listDialogVisible.value = false\n  if (urls.value.length > 1 || props.num > 1) {\n    emit('update:modelValue', value.value)\n  } else {\n    emit('update:modelValue', value.value[0])\n  }\n  \n  \n}\n</script>\n\n<style lang=\"scss\" scoped>\n.el-icon-circle-close {\n  color: red;\n}\n\n.material-name {\n  padding: 8px 0px;\n}\n\n.col-do {\n  text-align: center;\n}\n\n.button-do {\n  padding: unset !important;\n  font-size: 12px;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Materials/src/editorMaterials.vue",
    "content": "<template>\n  <div>\n    <div>\n      <el-container>\n        <el-aside width=\"100px\">\n          <div style=\"margin-bottom: 10px\">\n            <el-button class=\"el-icon-plus\" size=\"small\" @click=\"handleMaterialgroupAdd()\">\n              添加分组\n            </el-button>\n          </div>\n          <el-tabs\n            v-model=\"materialgroupObjId\"\n            v-loading=\"materialgroupLoading\"\n            tab-position=\"left\"\n            @tab-click=\"tabClick\"\n          >\n            <el-tab-pane v-for=\"item in materialgroupList\" :key=\"item.id\" :name=\"item.id\">\n              <template #label>\n                <span> {{ item.name }}</span>\n              </template>\n            </el-tab-pane>\n          </el-tabs>\n        </el-aside>\n        <el-main>\n          <el-card>\n            <template #header>\n              <div>\n                <el-row>\n                  <el-col :span=\"12\">\n                    <span>{{ materialgroupObj.name }}</span>\n                    <span v-if=\"materialgroupObj.id != '-1'\">\n                      <el-button\n                        size=\"small\"\n                        type=\"text\"\n                        class=\"el-icon-edit\"\n                        style=\"margin-left: 10px\"\n                        @click=\"handleMaterialgroupEdit(materialgroupObj)\"\n                        >重命名</el-button\n                      >\n                      <el-button\n                        size=\"small\"\n                        type=\"text\"\n                        class=\"el-icon-delete\"\n                        style=\"margin-left: 10px; color: red\"\n                        @click=\"materialgroupDelete(materialgroupObj)\"\n                        >删除</el-button\n                      >\n                    </span>\n                  </el-col>\n                  <el-col :span=\"12\" style=\"text-align: right\">\n                    <el-upload\n                      :action=\"uploadApi\"\n                      :headers=\"headers\"\n                      :file-list=\"[]\"\n                      :on-progress=\"handleProgress\"\n                      :before-upload=\"beforeUpload\"\n                      :on-success=\"handleSuccess\"\n                      :data=\"{ type: 1 }\"\n                      multiple\n                    >\n                      <el-button size=\"small\" type=\"primary\">批量上传</el-button>\n                    </el-upload>\n                  </el-col>\n                </el-row>\n              </div>\n            </template>\n            <div v-loading=\"tableLoading\">\n              <el-alert\n                v-if=\"tableData.length <= 0\"\n                title=\"暂无数据\"\n                type=\"info\"\n                :closable=\"false\"\n                center\n                show-icon\n              />\n              <el-checkbox-group v-model=\"urls\" :max=\"num - value.length\">\n                <el-row :gutter=\"5\">\n                  <el-col v-for=\"(item, index) in tableData\" :key=\"index\" :span=\"4\">\n                    <el-card :body-style=\"{ padding: '5px' }\">\n                      <el-image\n                        style=\"width: 100%; height: 100px\"\n                        :src=\"item.url\"\n                        fit=\"contain\"\n                        :preview-src-list=\"[item.url]\"\n                        :z-index=\"9999\"\n                      />\n                      <div>\n                        <el-checkbox class=\"material-name\" :label=\"item.url\"> 选择 </el-checkbox>\n                        <el-row>\n                          <el-col :span=\"24\" class=\"col-do\">\n                            <el-button type=\"text\" size=\"medium\" @click=\"materialDel(item)\"\n                              >删除</el-button\n                            >\n                          </el-col>\n                        </el-row>\n                      </div>\n                    </el-card>\n                  </el-col>\n                </el-row>\n              </el-checkbox-group>\n\n              <el-pagination\n                v-model:current-page=\"page.currentPage\"\n                :page-sizes=\"[12, 24]\"\n                :page-size=\"page.pageSize\"\n                layout=\"total, sizes, prev, pager, next, jumper\"\n                :total=\"page.total\"\n                class=\"pagination\"\n                @size-change=\"sizeChange\"\n                @current-change=\"pageChange\"\n              />\n            </div>\n          </el-card>\n        </el-main>\n      </el-container>\n      <!-- <template #footer> -->\n        <span class=\"dialog-footer\" style=\"color: red;\">\n          <!-- <el-button @click=\"listDialogVisible = false\">取 消</el-button> -->\n          <el-button type=\"primary\" @click=\"sureUrls\">确 定</el-button>\n        </span>\n      <!-- </template> -->\n      </div>\n  </div>\n</template>\n\n<script setup name=\"EditorMaterials\">\nimport { ElMessageBox } from 'element-plus'\nimport { ref } from 'vue'\nimport {\n  getList as materialgroupPage,\n  addObj as materialgroupAdd,\n  delObj as materialgroupDel,\n  putObj as materialgroupEdit\n} from '@/api/tools/materialgroup'\nimport { getPage, addObj, delObj } from '@/api/tools/material'\nimport { getAccessToken } from '@/utils/auth'\nimport '../../../../public/UEditor/dialogs/internal'\n\nconst props = defineProps({\n  value: {\n    type: Array,\n    default() {\n      return []\n    }\n  },\n  // 素材类型\n  type: {\n    type: String\n  },\n  // 素材限制数量，默认5个\n  num: {\n    type: Number,\n    default() {\n      return 5\n    }\n  },\n  // 宽度\n  width: {\n    type: Number,\n    default() {\n      return 150\n    }\n  },\n  // 宽度\n  height: {\n    type: Number,\n    default() {\n      return 150\n    }\n  }\n})\n\nconst headers = ref({\n  // Authorization: getToken()\n  Authorization: 'Bearer ' + getAccessToken(),\n})\nconst value = ref(props.value)\n// const dialogVisible = ref(false)\n// const url = ref('')\nconst listDialogVisible = ref(false)\n// const listDialogVisible2 = ref(false)\nconst materialgroupList = ref([])\nconst materialgroupObjId = ref('')\nconst materialgroupObj = ref({})\nconst materialgroupLoading = ref(false)\nconst tableData = ref([])\nconst resultNumber = ref(0)\nconst page = ref({\n  total: 0, // 总页数\n  currentPage: 1, // 当前页数\n  pageSize: 12, // 每页显示多少条\n  ascs: [], // 升序字段\n  descs: 'create_time' // 降序字段\n})\nconst tableLoading = ref(false)\nconst groupId = ref(null)\nconst urls = ref([])\n\n// const store = useStore()\n\nconst uploadApi = import.meta.env.VITE_UPLOAD_URL\n\nonMounted(() => {\n  getMaterialgroupPage()\n})\n\n\nfunction getMaterialgroupPage() {\n  materialgroupLoading.value = true\n  materialgroupPage({\n    total: 0, // 总页数\n    page: 1, // 当前页数\n    size: 100, // 每页显示多少条\n    ascs: [], // 升序字段\n    sort: 'create_time,desc' // 降序字段\n  }).then((response) => {\n    materialgroupLoading.value = false\n    materialgroupList.value = response\n    materialgroupList.value.unshift({\n      id: '-1',\n      name: '全部分组'\n    })\n\n    tabClick({\n      index: 0\n    })\n  })\n}\nfunction materialgroupDelete(materialgroupObj) {\n  ElMessageBox.confirm('是否确认删除该分组？', '提示', {\n    confirmButtonText: '确定',\n    cancelButtonText: '取消',\n    type: 'warning'\n  }).then(function () {\n    materialgroupDel(materialgroupObj.id).then(function () {\n      getMaterialgroupPage()\n    })\n  })\n}\nfunction handleMaterialgroupEdit(materialgroupObj) {\n  ElMessageBox.prompt('请输入分组名', '提示', {\n    confirmButtonText: '确定',\n    cancelButtonText: '取消',\n    inputValue: materialgroupObj.name\n  })\n    .then(({ value }) => {\n      materialgroupEdit({\n        id: materialgroupObj.id,\n        name: value\n      }).then(function () {\n        materialgroupObj.name = value\n        materialgroupList.value[materialgroupObj.index] = materialgroupObj\n      })\n    })\n    .catch(() => {})\n}\nfunction handleMaterialgroupAdd() {\n  ElMessageBox.prompt('请输入分组名', '提示', {\n    confirmButtonText: '确定',\n    cancelButtonText: '取消'\n  })\n    .then(({ value }) => {\n      materialgroupAdd({\n        name: value\n      }).then(function () {\n        getMaterialgroupPage()\n      })\n    })\n    .catch(() => {})\n}\nfunction tabClick(tab) {\n  urls.value = []\n  const index = Number(tab.index)\n  materialgroupObj.value = materialgroupList.value[index]\n\n  console.log('🚀 ~ file: Materials.vue:351 ~ tabClick ~ materialgroupObj:', materialgroupObj)\n\n  materialgroupObj.value.index = index\n  materialgroupObjId.value = materialgroupObj.value.id\n\n  page.value.currentPage = 1\n  page.value.total = 0\n  if (materialgroupObj.value.id != '-1') {\n    groupId.value = materialgroupObj.value.id\n  } else {\n    groupId.value = null\n  }\n  getMaterialPage(page.value)\n}\nfunction getMaterialPage(initPage) {\n  tableLoading.value = true\n  getPage(\n    Object.assign(\n      {\n        page: initPage.currentPage - 1,\n        size: initPage.pageSize,\n        descs: initPage.descs,\n        ascs: initPage.ascs,\n        sort: 'create_time,desc'\n      },\n      {\n        groupId: groupId.value\n      }\n    )\n  )\n    .then((response) => {\n      console.log('🚀 ~ file: Materials.vue:382 ~ .then ~ response:', response.list)\n      console.log('🚀 ~ file: Materials.vue:382 ~ .then ~ response:', response.total)\n      page.value.total = response.total\n      page.value.currentPage = initPage.currentPage\n      page.value.pageSize = initPage.pageSize\n      tableData.value = response.list\n      console.log('🚀 ~ file: Materials.vue:387 ~ .then ~ tableData.value:', tableData.value)\n      tableLoading.value = false\n    })\n    .catch((error) => {\n      console.log('🚀 ~ file: Materials.vue:391 ~ getMaterialPage ~ error:', error)\n      tableLoading.value = false\n    })\n}\nfunction sizeChange(val) {\n  console.log(val)\n  page.value.currentPage = 1\n  page.value.pageSize = val\n  getMaterialPage(this.page)\n}\nfunction pageChange(val) {\n  console.log(val)\n  page.value.currentPage = val\n  // this.page.pageSize = val\n  getMaterialPage(page.value)\n}\n\nfunction materialDel(item) {\n  ElMessageBox.confirm('是否确认删除该素材？', '提示', {\n    confirmButtonText: '确定',\n    cancelButtonText: '取消',\n    type: 'warning'\n  }).then(function () {\n    delObj(item.id).finally(function () {\n      console.log('getMaterialPage')\n      getMaterialPage(page.value)\n    })\n  })\n}\n\nfunction handleProgress(event) {\n  console.log(event)\n}\nfunction handleSuccess(response, file, fileList) {\n  addObj({\n    type: '1',\n    groupId: groupId.value != '-1' ? groupId.value : null,\n    name: file.name,\n    url: response.data\n  }).then(() => {\n    resultNumber.value++\n    //console.log('res:', resultNumber.value)\n    //console.log('fileList:',fileList.length)\n    if (fileList.length === resultNumber.value) {\n      getMaterialPage(page.value)\n      //resultNumber.value = 0\n    }\n  })\n}\nfunction beforeUpload(file) {\n  const isPic =\n    file.type === 'image/jpeg' ||\n    file.type === 'image/png' ||\n    file.type === 'image/gif' ||\n    file.type === 'image/jpg'\n  const isLt2M = file.size / 1024 / 1024 < 2\n  if (!isPic) {\n    this.$message.error('上传图片只能是 JPG、JPEG、PNG、GIF 格式!')\n    return false\n  }\n  if (!isLt2M) {\n    this.$message.error('上传头像图片大小不能超过 2MB!')\n  }\n  return isPic && isLt2M\n}\nfunction sureUrls() {\n  listDialogVisible.value = false\n\n  let str = ''\n  urls.value.forEach(item => {\n    str += '<img src=\"' + item + '\">'\n  })\n  nowEditor.dialog.close(true)\n  nowEditor.editor.setContent(str, true)\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.el-icon-circle-close {\n  color: red;\n}\n\n.material-name {\n  padding: 8px 0px;\n}\n\n.col-do {\n  text-align: center;\n}\n\n.button-do {\n  padding: unset !important;\n  font-size: 12px;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/OperateLogV2/index.ts",
    "content": "import OperateLogV2 from './src/OperateLogV2.vue'\n\nexport { OperateLogV2 }\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/OperateLogV2/src/OperateLogV2.vue",
    "content": "<!-- 某个记录的操作日志列表，目前主要用于 CRM 客户、商机等详情界面 -->\n<template>\n  <div class=\"pt-20px\">\n    <el-timeline>\n      <el-timeline-item\n        v-for=\"(log, index) in logList\"\n        :key=\"index\"\n        :timestamp=\"formatDate(log.createTime)\"\n        placement=\"top\"\n      >\n        <div class=\"el-timeline-right-content\">\n          <el-tag class=\"mr-10px\" type=\"success\">{{ log.userName }}</el-tag>\n          {{ log.action }}\n        </div>\n        <template #dot>\n          <span :style=\"{ backgroundColor: getUserTypeColor(log.userType) }\" class=\"dot-node-style\">\n            {{ getDictLabel(DICT_TYPE.USER_TYPE, log.userType)[0] }}\n          </span>\n        </template>\n      </el-timeline-item>\n    </el-timeline>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { OperateLogVO } from '@/api/system/operatelog'\nimport { formatDate } from '@/utils/formatTime'\nimport { DICT_TYPE, getDictLabel, getDictObj } from '@/utils/dict'\nimport { ElTag } from 'element-plus'\n\ndefineOptions({ name: 'OperateLogV2' })\n\ninterface Props {\n  logList: OperateLogVO[] // 操作日志列表\n}\n\nwithDefaults(defineProps<Props>(), {\n  logList: () => []\n})\n\n/** 获得 userType 颜色 */\nconst getUserTypeColor = (type: number) => {\n  const dict = getDictObj(DICT_TYPE.USER_TYPE, type)\n  switch (dict?.colorType) {\n    case 'success':\n      return '#67C23A'\n    case 'info':\n      return '#909399'\n    case 'warning':\n      return '#E6A23C'\n    case 'danger':\n      return '#F56C6C'\n  }\n  return '#409EFF'\n}\n</script>\n\n<style lang=\"scss\" scoped>\n// 时间线样式调整\n:deep(.el-timeline) {\n  margin: 10px 0 0 110px;\n\n  .el-timeline-item__wrapper {\n    position: relative;\n    top: -20px;\n\n    .el-timeline-item__timestamp {\n      position: absolute !important;\n      top: 10px;\n      left: -150px;\n    }\n  }\n\n  .el-timeline-right-content {\n    display: flex;\n    align-items: center;\n    min-height: 30px;\n    padding: 10px;\n    background-color: #fff;\n\n    &::before {\n      position: absolute;\n      top: 10px;\n      left: 13px; /* 将伪元素水平居中 */\n      border-color: transparent #fff transparent transparent; /* 尖角颜色，左侧朝向 */\n      border-style: solid;\n      border-width: 8px; /* 调整尖角大小 */\n      content: ''; /* 必须设置 content 属性 */\n    }\n  }\n}\n\n.dot-node-style {\n  position: absolute;\n  left: -5px;\n  display: flex;\n  width: 20px;\n  height: 20px;\n  font-size: 10px;\n  color: #fff;\n  border-radius: 50%;\n  justify-content: center;\n  align-items: center;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Pagination/index.vue",
    "content": "，并使用 ts 重写 -->\n<template>\n  <el-pagination\n    v-show=\"total > 0\"\n    v-model:current-page=\"currentPage\"\n    v-model:page-size=\"pageSize\"\n    :background=\"true\"\n    :page-sizes=\"[10, 20, 30, 50, 100]\"\n    :pager-count=\"pagerCount\"\n    :total=\"total\"\n    :small=\"isSmall\"\n    class=\"float-right mb-15px mt-15px\"\n    layout=\"total, sizes, prev, pager, next, jumper\"\n    @size-change=\"handleSizeChange\"\n    @current-change=\"handleCurrentChange\"\n  />\n</template>\n<script lang=\"ts\" setup>\nimport { computed, watchEffect } from 'vue'\nimport { useAppStore } from '@/store/modules/app'\n\ndefineOptions({ name: 'Pagination' })\n\n// 此处解决了当全局size为small的时候分页组件样式太大的问题\nconst appStore = useAppStore()\nconst layoutCurrentSize = computed(() => appStore.currentSize)\nconst isSmall = ref<boolean>(layoutCurrentSize.value === 'small')\nwatchEffect(() => {\n  isSmall.value = layoutCurrentSize.value === 'small'\n})\n\nconst props = defineProps({\n  // 总条目数\n  total: {\n    required: true,\n    type: Number\n  },\n  // 当前页数：pageNo\n  page: {\n    type: Number,\n    default: 1\n  },\n  // 每页显示条目个数：pageSize\n  limit: {\n    type: Number,\n    default: 20\n  },\n  // 设置最大页码按钮数。 页码按钮的数量，当总页数超过该值时会折叠\n  // 移动端页码按钮的数量端默认值 5\n  pagerCount: {\n    type: Number,\n    default: document.body.clientWidth < 992 ? 5 : 7\n  }\n})\n\nconst emit = defineEmits(['update:page', 'update:limit', 'pagination'])\nconst currentPage = computed({\n  get() {\n    return props.page\n  },\n  set(val) {\n    // 触发 update:page 事件，更新 limit 属性，从而更新 pageNo\n    emit('update:page', val)\n  }\n})\nconst pageSize = computed({\n  get() {\n    return props.limit\n  },\n  set(val) {\n    // 触发 update:limit 事件，更新 limit 属性，从而更新 pageSize\n    emit('update:limit', val)\n  }\n})\nconst handleSizeChange = (val) => {\n  // 如果修改后超过最大页面，强制跳转到第 1 页\n  if (currentPage.value * val > props.total) {\n    currentPage.value = 1\n  }\n  // 触发 pagination 事件，重新加载列表\n  emit('pagination', { page: currentPage.value, limit: val })\n}\nconst handleCurrentChange = (val) => {\n  // 触发 pagination 事件，重新加载列表\n  emit('pagination', { page: val, limit: pageSize.value })\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Qrcode/index.ts",
    "content": "import Qrcode from './src/Qrcode.vue'\n\nexport { Qrcode }\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Qrcode/src/Qrcode.vue",
    "content": "<script lang=\"ts\" setup>\nimport { computed, nextTick, PropType, ref, unref, watch } from 'vue'\nimport QRCode, { QRCodeRenderersOptions } from 'qrcode'\nimport { cloneDeep } from 'lodash-es'\nimport { propTypes } from '@/utils/propTypes'\nimport { useDesign } from '@/hooks/web/useDesign'\nimport { isString } from '@/utils/is'\nimport { QrcodeLogo } from '@/types/qrcode'\n\ndefineOptions({ name: 'Qrcode' })\n\nconst props = defineProps({\n  // img 或者 canvas,img不支持logo嵌套\n  tag: propTypes.string.validate((v: string) => ['canvas', 'img'].includes(v)).def('canvas'),\n  // 二维码内容\n  text: {\n    type: [String, Array] as PropType<string | Recordable[]>,\n    default: null\n  },\n  // qrcode.js配置项\n  options: {\n    type: Object as PropType<QRCodeRenderersOptions>,\n    default: () => ({})\n  },\n  // 宽度\n  width: propTypes.number.def(200),\n  // logo\n  logo: {\n    type: [String, Object] as PropType<Partial<QrcodeLogo> | string>,\n    default: ''\n  },\n  // 是否过期\n  disabled: propTypes.bool.def(false),\n  // 过期提示内容\n  disabledText: propTypes.string.def('')\n})\n\nconst emit = defineEmits(['done', 'click', 'disabled-click'])\n\nconst { getPrefixCls } = useDesign()\n\nconst prefixCls = getPrefixCls('qrcode')\n\nconst { toCanvas, toDataURL } = QRCode\n\nconst loading = ref(true)\n\nconst wrapRef = ref<Nullable<HTMLCanvasElement | HTMLImageElement>>(null)\n\nconst renderText = computed(() => String(props.text))\n\nconst wrapStyle = computed(() => {\n  return {\n    width: props.width + 'px',\n    height: props.width + 'px'\n  }\n})\n\nconst initQrcode = async () => {\n  await nextTick()\n  const options = cloneDeep(props.options || {})\n  if (props.tag === 'canvas') {\n    // 容错率，默认对内容少的二维码采用高容错率，内容多的二维码采用低容错率\n    options.errorCorrectionLevel =\n      options.errorCorrectionLevel || getErrorCorrectionLevel(unref(renderText))\n    const _width: number = await getOriginWidth(unref(renderText), options)\n    options.scale = props.width === 0 ? undefined : (props.width / _width) * 4\n    const canvasRef: HTMLCanvasElement | any = await toCanvas(\n      unref(wrapRef) as HTMLCanvasElement,\n      unref(renderText),\n      options\n    )\n    if (props.logo) {\n      const url = await createLogoCode(canvasRef)\n      emit('done', url)\n      loading.value = false\n    } else {\n      emit('done', canvasRef.toDataURL())\n      loading.value = false\n    }\n  } else {\n    const url = await toDataURL(renderText.value, {\n      errorCorrectionLevel: 'H',\n      width: props.width,\n      ...options\n    })\n    ;(unref(wrapRef) as HTMLImageElement).src = url\n    emit('done', url)\n    loading.value = false\n  }\n}\n\nwatch(\n  () => renderText.value,\n  (val) => {\n    if (!val) return\n    initQrcode()\n  },\n  {\n    deep: true,\n    immediate: true\n  }\n)\n\nconst createLogoCode = (canvasRef: HTMLCanvasElement) => {\n  const canvasWidth = canvasRef.width\n  const logoOptions: QrcodeLogo = Object.assign(\n    {\n      logoSize: 0.15,\n      bgColor: '#ffffff',\n      borderSize: 0.05,\n      crossOrigin: 'anonymous',\n      borderRadius: 8,\n      logoRadius: 0\n    },\n    isString(props.logo) ? {} : props.logo\n  )\n  const {\n    logoSize = 0.15,\n    bgColor = '#ffffff',\n    borderSize = 0.05,\n    crossOrigin = 'anonymous',\n    borderRadius = 8,\n    logoRadius = 0\n  } = logoOptions\n  const logoSrc = isString(props.logo) ? props.logo : props.logo.src\n  const logoWidth = canvasWidth * logoSize\n  const logoXY = (canvasWidth * (1 - logoSize)) / 2\n  const logoBgWidth = canvasWidth * (logoSize + borderSize)\n  const logoBgXY = (canvasWidth * (1 - logoSize - borderSize)) / 2\n\n  const ctx = canvasRef.getContext('2d')\n  if (!ctx) return\n\n  // logo 底色\n  canvasRoundRect(ctx)(logoBgXY, logoBgXY, logoBgWidth, logoBgWidth, borderRadius)\n  ctx.fillStyle = bgColor\n  ctx.fill()\n\n  // logo\n  const image = new Image()\n  if (crossOrigin || logoRadius) {\n    image.setAttribute('crossOrigin', crossOrigin)\n  }\n  ;(image as any).src = logoSrc\n\n  // 使用image绘制可以避免某些跨域情况\n  const drawLogoWithImage = (image: HTMLImageElement) => {\n    ctx.drawImage(image, logoXY, logoXY, logoWidth, logoWidth)\n  }\n\n  // 使用canvas绘制以获得更多的功能\n  const drawLogoWithCanvas = (image: HTMLImageElement) => {\n    const canvasImage = document.createElement('canvas')\n    canvasImage.width = logoXY + logoWidth\n    canvasImage.height = logoXY + logoWidth\n    const imageCanvas = canvasImage.getContext('2d')\n    if (!imageCanvas || !ctx) return\n    imageCanvas.drawImage(image, logoXY, logoXY, logoWidth, logoWidth)\n\n    canvasRoundRect(ctx)(logoXY, logoXY, logoWidth, logoWidth, logoRadius)\n    if (!ctx) return\n    const fillStyle = ctx.createPattern(canvasImage, 'no-repeat')\n    if (fillStyle) {\n      ctx.fillStyle = fillStyle\n      ctx.fill()\n    }\n  }\n\n  // 将 logo绘制到 canvas上\n  return new Promise((resolve: any) => {\n    image.onload = () => {\n      logoRadius ? drawLogoWithCanvas(image) : drawLogoWithImage(image)\n      resolve(canvasRef.toDataURL())\n    }\n  })\n}\n\n// 得到原QrCode的大小，以便缩放得到正确的QrCode大小\nconst getOriginWidth = async (content: string, options: QRCodeRenderersOptions) => {\n  const _canvas = document.createElement('canvas')\n  await toCanvas(_canvas, content, options)\n  return _canvas.width\n}\n\n// 对于内容少的QrCode，增大容错率\nconst getErrorCorrectionLevel = (content: string) => {\n  if (content.length > 36) {\n    return 'M'\n  } else if (content.length > 16) {\n    return 'Q'\n  } else {\n    return 'H'\n  }\n}\n\n// copy来的方法，用于绘制圆角\nconst canvasRoundRect = (ctx: CanvasRenderingContext2D) => {\n  return (x: number, y: number, w: number, h: number, r: number) => {\n    const minSize = Math.min(w, h)\n    if (r > minSize / 2) {\n      r = minSize / 2\n    }\n    ctx.beginPath()\n    ctx.moveTo(x + r, y)\n    ctx.arcTo(x + w, y, x + w, y + h, r)\n    ctx.arcTo(x + w, y + h, x, y + h, r)\n    ctx.arcTo(x, y + h, x, y, r)\n    ctx.arcTo(x, y, x + w, y, r)\n    ctx.closePath()\n    return ctx\n  }\n}\n\nconst clickCode = () => {\n  emit('click')\n}\n\nconst disabledClick = () => {\n  emit('disabled-click')\n}\n</script>\n\n<template>\n  <div v-loading=\"loading\" :class=\"[prefixCls, 'relative inline-block']\" :style=\"wrapStyle\">\n    <component :is=\"tag\" ref=\"wrapRef\" @click=\"clickCode\" />\n    <div\n      v-if=\"disabled\"\n      :class=\"`${prefixCls}--disabled`\"\n      class=\"absolute left-0 top-0 h-full w-full flex items-center justify-center\"\n      @click=\"disabledClick\"\n    >\n      <div class=\"absolute left-[50%] top-[50%] font-bold\">\n        <Icon :size=\"30\" color=\"var(--el-color-primary)\" icon=\"ep:refresh-right\" />\n        <div>{{ disabledText }}</div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<style lang=\"scss\" scoped>\n$prefix-cls: #{$namespace}-qrcode;\n\n.#{$prefix-cls} {\n  &--disabled {\n    background: rgb(255 255 255 / 95%);\n\n    & > div {\n      transform: translate(-50%, -50%);\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/RouterSearch/index.vue",
    "content": "<template>\n  <ElDialog v-if=\"isModal\" v-model=\"showSearch\" :show-close=\"false\" title=\"菜单搜索\">\n    <el-select\n      filterable\n      :reserve-keyword=\"false\"\n      remote\n      placeholder=\"请输入菜单内容\"\n      :remote-method=\"remoteMethod\"\n      style=\"width: 100%\"\n      @change=\"handleChange\"\n    >\n      <el-option\n        v-for=\"item in options\"\n        :key=\"item.value\"\n        :label=\"item.label\"\n        :value=\"item.value\"\n      />\n    </el-select>\n  </ElDialog>\n  <div v-else class=\"custom-hover\" @click.stop=\"showTopSearch = !showTopSearch\">\n    <Icon icon=\"ep:search\" />\n    <el-select\n      filterable\n      :reserve-keyword=\"false\"\n      remote\n      placeholder=\"请输入菜单内容\"\n      :remote-method=\"remoteMethod\"\n      class=\"overflow-hidden transition-all-600\"\n      :class=\"showTopSearch ? '!w-220px ml2' : '!w-0'\"\n      @change=\"handleChange\"\n    >\n      <el-option\n        v-for=\"item in options\"\n        :key=\"item.value\"\n        :label=\"item.label\"\n        :value=\"item.value\"\n      />\n    </el-select>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\ndefineProps({\n  isModal: {\n    type: Boolean,\n    default: true\n  }\n})\n\nconst router = useRouter() // 路由对象\nconst showSearch = ref(false) // 是否显示弹框\nconst showTopSearch = ref(false) // 是否显示顶部搜索框\nconst value: Ref = ref('') // 用户输入的值\n\nconst routers = router.getRoutes() // 路由对象\nconst options = computed(() => {\n  // 提示选项\n  if (!value.value) {\n    return []\n  }\n  const list = routers.filter((item: any) => {\n    if (item.meta.title?.indexOf(value.value) > -1 || item.path.indexOf(value.value) > -1) {\n      return true\n    }\n  })\n  return list.map((item) => {\n    return {\n      label: `${item.meta.title}${item.path}`,\n      value: item.path\n    }\n  })\n})\n\nfunction remoteMethod(data) {\n  // 这里可以执行相应的操作（例如打开搜索框等）\n  value.value = data\n}\n\nfunction handleChange(path) {\n  router.push({ path })\n  hiddenTopSearch()\n}\n\nfunction hiddenTopSearch() {\n  showTopSearch.value = false\n}\n\nonMounted(() => {\n  window.addEventListener('keydown', listenKey)\n  window.addEventListener('click', hiddenTopSearch)\n})\n\nonUnmounted(() => {\n  window.removeEventListener('keydown', listenKey)\n  window.removeEventListener('click', hiddenTopSearch)\n})\n\n// 监听 ctrl + k\nfunction listenKey(event) {\n  if ((event.ctrlKey || event.metaKey) && event.key === 'k') {\n    showSearch.value = !showSearch.value\n    // 这里可以执行相应的操作（例如打开搜索框等）\n  }\n}\n\ndefineExpose({\n  openSearch: () => {\n    showSearch.value = true\n  }\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Search/index.ts",
    "content": "import Search from './src/Search.vue'\n\nexport { Search }\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Search/src/Search.vue",
    "content": "<script lang=\"ts\" setup>\nimport { PropType } from 'vue'\nimport { propTypes } from '@/utils/propTypes'\n\nimport { useForm } from '@/hooks/web/useForm'\nimport { findIndex } from '@/utils'\nimport { cloneDeep } from 'lodash-es'\nimport { FormSchema } from '@/types/form'\n\ndefineOptions({ name: 'Search' })\n\nconst { t } = useI18n()\n\nconst props = defineProps({\n  // 生成Form的布局结构数组\n  schema: {\n    type: Array as PropType<FormSchema[]>,\n    default: () => []\n  },\n  // 是否需要栅格布局\n  isCol: propTypes.bool.def(false),\n  // 表单label宽度\n  labelWidth: propTypes.oneOfType([String, Number]).def('auto'),\n  // 操作按钮风格位置\n  layout: propTypes.string.validate((v: string) => ['inline', 'bottom'].includes(v)).def('inline'),\n  // 底部按钮的对齐方式\n  buttomPosition: propTypes.string\n    .validate((v: string) => ['left', 'center', 'right'].includes(v))\n    .def('center'),\n  showSearch: propTypes.bool.def(true),\n  showReset: propTypes.bool.def(true),\n  // 是否显示伸缩\n  expand: propTypes.bool.def(false),\n  // 伸缩的界限字段\n  expandField: propTypes.string.def(''),\n  inline: propTypes.bool.def(true),\n  model: {\n    type: Object as PropType<Recordable>,\n    default: () => ({})\n  }\n})\n\nconst emit = defineEmits(['search', 'reset'])\n\nconst visible = ref(true)\n\nconst newSchema = computed(() => {\n  let schema: FormSchema[] = cloneDeep(props.schema)\n  if (props.expand && props.expandField && !unref(visible)) {\n    const index = findIndex(schema, (v: FormSchema) => v.field === props.expandField)\n    if (index > -1) {\n      const length = schema.length\n      schema.splice(index + 1, length)\n    }\n  }\n  if (props.layout === 'inline') {\n    schema = schema.concat([\n      {\n        field: 'action',\n        formItemProps: {\n          labelWidth: '0px'\n        }\n      }\n    ])\n  }\n  return schema\n})\n\nconst { register, elFormRef, methods } = useForm({\n  model: props.model || {}\n})\n\nconst search = async () => {\n  await unref(elFormRef)?.validate(async (isValid) => {\n    if (isValid) {\n      const { getFormData } = methods\n      const model = await getFormData()\n      emit('search', model)\n    }\n  })\n}\n\nconst reset = async () => {\n  unref(elFormRef)?.resetFields()\n  const { getFormData } = methods\n  const model = await getFormData()\n  emit('reset', model)\n}\n\nconst bottonButtonStyle = computed(() => {\n  return {\n    textAlign: props.buttomPosition as unknown as 'left' | 'center' | 'right'\n  }\n})\n\nconst setVisible = () => {\n  unref(elFormRef)?.resetFields()\n  visible.value = !unref(visible)\n}\n</script>\n\n<template>\n  <!-- update by 芋艿：class=\"-mb-15px\" 用于降低和 ContentWrap 组件的底部距离，避免空隙过大 -->\n  <Form\n    :inline=\"inline\"\n    :is-col=\"isCol\"\n    :is-custom=\"false\"\n    :label-width=\"labelWidth\"\n    :schema=\"newSchema\"\n    class=\"-mb-15px\"\n    hide-required-asterisk\n    @register=\"register\"\n  >\n    <template #action>\n      <div v-if=\"layout === 'inline'\">\n        <ElButton v-if=\"showSearch\" @click=\"search\">\n          <Icon class=\"mr-5px\" icon=\"ep:search\" />\n          {{ t('common.query') }}\n        </ElButton>\n        <ElButton v-if=\"showReset\" @click=\"reset\">\n          <Icon class=\"mr-5px\" icon=\"ep:refresh\" />\n          {{ t('common.reset') }}\n        </ElButton>\n        <ElButton v-if=\"expand\" text @click=\"setVisible\">\n          {{ t(visible ? 'common.shrink' : 'common.expand') }}\n          <Icon :icon=\"visible ? 'ep:arrow-up' : 'ep:arrow-down'\" />\n        </ElButton>\n        <slot name=\"actionMore\"></slot>\n      </div>\n    </template>\n    <template v-for=\"name in Object.keys($slots)\" :key=\"name\" #[name]>\n      <slot :name=\"name\"></slot>\n    </template>\n  </Form>\n\n  <template v-if=\"layout === 'bottom'\">\n    <div :style=\"bottonButtonStyle\">\n      <ElButton v-if=\"showSearch\" type=\"primary\" @click=\"search\">\n        <Icon class=\"mr-5px\" icon=\"ep:search\" />\n        {{ t('common.query') }}\n      </ElButton>\n      <ElButton v-if=\"showReset\" @click=\"reset\">\n        <Icon class=\"mr-5px\" icon=\"ep:refresh-right\" />\n        {{ t('common.reset') }}\n      </ElButton>\n      <ElButton v-if=\"expand\" text @click=\"setVisible\">\n        {{ t(visible ? 'common.shrink' : 'common.expand') }}\n        <Icon :icon=\"visible ? 'ep:arrow-up' : 'ep:arrow-down'\" />\n      </ElButton>\n      <slot name=\"actionMore\"></slot>\n    </div>\n  </template>\n</template>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/ShortcutDateRangePicker/index.vue",
    "content": "<template>\n  <div class=\"flex flex-row items-center gap-2\">\n    <el-radio-group v-model=\"shortcutDays\" @change=\"handleShortcutDaysChange\">\n      <el-radio-button :label=\"1\">昨天</el-radio-button>\n      <el-radio-button :label=\"7\">最近7天</el-radio-button>\n      <el-radio-button :label=\"30\">最近30天</el-radio-button>\n    </el-radio-group>\n    <el-date-picker\n      v-model=\"times\"\n      value-format=\"YYYY-MM-DD HH:mm:ss\"\n      type=\"daterange\"\n      start-placeholder=\"开始日期\"\n      end-placeholder=\"结束日期\"\n      :default-time=\"[new Date('1 00:00:00'), new Date('1 23:59:59')]\"\n      :shortcuts=\"shortcuts\"\n      class=\"!w-240px\"\n      @change=\"emitDateRangePicker\"\n    />\n    <slot></slot>\n  </div>\n</template>\n<script lang=\"ts\" setup>\nimport dayjs from 'dayjs'\nimport * as DateUtil from '@/utils/formatTime'\n\n/** 快捷日期范围选择组件 */\ndefineOptions({ name: 'ShortcutDateRangePicker' })\n\nconst shortcutDays = ref(7) // 日期快捷天数（单选按钮组）, 默认7天\nconst times = ref<[string, string]>(['', '']) // 时间范围参数\ndefineExpose({ times }) // 暴露时间范围参数\n/** 日期快捷选择 */\nconst shortcuts = [\n  {\n    text: '昨天',\n    value: () => DateUtil.getDayRange(new Date(), -1)\n  },\n  {\n    text: '最近7天',\n    value: () => DateUtil.getLast7Days()\n  },\n  {\n    text: '本月',\n    value: () => [dayjs().startOf('M'), dayjs().subtract(1, 'd')]\n  },\n  {\n    text: '最近30天',\n    value: () => DateUtil.getLast30Days()\n  },\n  {\n    text: '最近1年',\n    value: () => DateUtil.getLast1Year()\n  }\n]\n\n/** 设置时间范围 */\nfunction setTimes() {\n  const beginDate = dayjs().subtract(shortcutDays.value, 'd')\n  const yesterday = dayjs().subtract(1, 'd')\n  times.value = DateUtil.getDateRange(beginDate, yesterday)\n}\n\n/** 快捷日期单选按钮选中 */\nconst handleShortcutDaysChange = async () => {\n  // 设置时间范围\n  setTimes()\n  // 发送时间范围选中事件\n  await emitDateRangePicker()\n}\n\n/** 触发事件：时间范围选中 */\nconst emits = defineEmits<{\n  (e: 'change', times: [dayjs.ConfigType, dayjs.ConfigType]): void\n}>()\n/** 触发时间范围选中事件 */\nconst emitDateRangePicker = async () => {\n  emits('change', times.value)\n}\n\n/** 初始化 **/\nonMounted(() => {\n  handleShortcutDaysChange()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/SimpleProcessDesigner/src/addNode.vue",
    "content": "/* stylelint-disable order/properties-order */\n<template>\n  <div class=\"add-node-btn-box\">\n    <div class=\"add-node-btn\">\n      <el-popover placement=\"right-start\" v-model=\"visible\" width=\"auto\">\n        <div class=\"add-node-popover-body\">\n          <a class=\"add-node-popover-item approver\" @click=\"addType(1)\">\n            <div class=\"item-wrapper\">\n              <span class=\"iconfont\"></span>\n            </div>\n            <p>审批人</p>\n          </a>\n          <a class=\"add-node-popover-item notifier\" @click=\"addType(2)\">\n            <div class=\"item-wrapper\">\n              <span class=\"iconfont\"></span>\n            </div>\n            <p>抄送人</p>\n          </a>\n          <a class=\"add-node-popover-item condition\" @click=\"addType(4)\">\n            <div class=\"item-wrapper\">\n              <span class=\"iconfont\"></span>\n            </div>\n            <p>条件分支</p>\n          </a>\n        </div>\n        <template #reference>\n          <button class=\"btn\" type=\"button\">\n            <span class=\"iconfont\"></span>\n          </button>\n        </template>\n      </el-popover>\n    </div>\n  </div>\n</template>\n<script setup>\nimport { ref } from 'vue'\nlet props = defineProps({\n  childNodeP: {\n    type: Object,\n    default: () => ({})\n  }\n})\nlet emits = defineEmits(['update:childNodeP'])\nlet visible = ref(false)\nconst addType = (type) => {\n  visible.value = false\n  if (type != 4) {\n    var data\n    if (type == 1) {\n      data = {\n        nodeName: '审核人',\n        error: true,\n        type: 1,\n        settype: 1,\n        selectMode: 0,\n        selectRange: 0,\n        directorLevel: 1,\n        examineMode: 1,\n        noHanderAction: 1,\n        examineEndDirectorLevel: 0,\n        childNode: props.childNodeP,\n        nodeUserList: []\n      }\n    } else if (type == 2) {\n      data = {\n        nodeName: '抄送人',\n        type: 2,\n        ccSelfSelectFlag: 1,\n        childNode: props.childNodeP,\n        nodeUserList: []\n      }\n    }\n    emits('update:childNodeP', data)\n  } else {\n    emits('update:childNodeP', {\n      nodeName: '路由',\n      type: 4,\n      childNode: null,\n      conditionNodes: [\n        {\n          nodeName: '条件1',\n          error: true,\n          type: 3,\n          priorityLevel: 1,\n          conditionList: [],\n          nodeUserList: [],\n          childNode: props.childNodeP\n        },\n        {\n          nodeName: '条件2',\n          type: 3,\n          priorityLevel: 2,\n          conditionList: [],\n          nodeUserList: [],\n          childNode: null\n        }\n      ]\n    })\n  }\n}\n</script>\n<style scoped lang=\"scss\">\n.add-node-btn-box {\n  width: 240px;\n  display: inline-flex;\n  -ms-flex-negative: 0;\n  flex-shrink: 0;\n  -webkit-box-flex: 1;\n  -ms-flex-positive: 1;\n  position: relative;\n\n  &:before {\n    content: '';\n    position: absolute;\n    top: 0;\n    left: 0;\n    right: 0;\n    bottom: 0;\n    z-index: -1;\n    margin: auto;\n    width: 2px;\n    height: 100%;\n    background-color: #cacaca;\n  }\n\n  .add-node-btn {\n    user-select: none;\n    width: 240px;\n    padding: 20px 0 32px;\n    display: flex;\n    -webkit-box-pack: center;\n    justify-content: center;\n    flex-shrink: 0;\n    -webkit-box-flex: 1;\n    flex-grow: 1;\n\n    .btn {\n      outline: none;\n      box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1);\n      width: 30px;\n      height: 30px;\n      background: #3296fa;\n      border-radius: 50%;\n      position: relative;\n      border: none;\n      line-height: 30px;\n      -webkit-transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);\n      transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);\n\n      .iconfont {\n        color: #fff;\n        font-size: 16px;\n      }\n\n      &:hover {\n        transform: scale(1.3);\n        box-shadow: 0 13px 27px 0 rgba(0, 0, 0, 0.1);\n      }\n\n      &:active {\n        transform: none;\n        background: #1e83e9;\n        box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.1);\n      }\n    }\n  }\n}\n\n.add-node-popover-body {\n  display: flex;\n\n  .add-node-popover-item {\n    margin-right: 10px;\n    cursor: pointer;\n    text-align: center;\n    flex: 1;\n    color: #191f25 !important;\n\n    .item-wrapper {\n      user-select: none;\n      display: inline-block;\n      width: 80px;\n      height: 80px;\n      margin-bottom: 5px;\n      background: #fff;\n      border: 1px solid #e2e2e2;\n      border-radius: 50%;\n      transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);\n\n      .iconfont {\n        font-size: 35px;\n        line-height: 80px;\n      }\n    }\n\n    &.approver {\n      .item-wrapper {\n        color: #ff943e;\n      }\n    }\n\n    &.notifier {\n      .item-wrapper {\n        color: #3296fa;\n      }\n    }\n\n    &.condition {\n      .item-wrapper {\n        color: #15bc83;\n      }\n    }\n\n    &:hover {\n      .item-wrapper {\n        background: #3296fa;\n        box-shadow: 0 10px 20px 0 rgba(50, 150, 250, 0.4);\n      }\n\n      .iconfont {\n        color: #fff;\n      }\n    }\n\n    &:active {\n      .item-wrapper {\n        box-shadow: none;\n        background: #eaeaea;\n      }\n\n      .iconfont {\n        color: inherit;\n      }\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/SimpleProcessDesigner/src/nodeWrap.vue",
    "content": "<!-- eslint-disable vue/no-mutating-props -->\n<!--\n * @Date: 2022-09-21 14:41:53\n * @LastEditors: StavinLi 495727881@qq.com\n * @LastEditTime: 2023-05-24 15:20:24\n * @FilePath: /Workflow-Vue3/src/components/nodeWrap.vue\n-->\n<template>\n     <div class=\"node-wrap\" v-if=\"nodeConfig.type < 3\">\n      <div class=\"node-wrap-box\" :class=\"(nodeConfig.type == 0 ? 'start-node ' : '') +(isTried && nodeConfig.error ? 'active error' : '')\">\n          <div class=\"title\" :style=\"`background: rgb(${bgColors[nodeConfig.type]});`\">\n            <span v-if=\"nodeConfig.type == 0\">{{ nodeConfig.nodeName }}</span>\n            <template v-else>\n              <span class=\"iconfont\">{{nodeConfig.type == 1?'':''}}</span>\n              <input\n                v-if=\"isInput\"\n                type=\"text\"\n                class=\"ant-input editable-title-input\"\n                @blur=\"blurEvent()\"\n                @focus=\"$event.currentTarget.select()\"\n                v-focus\n                v-model=\"nodeConfig.nodeName\"\n                :placeholder=\"defaultText\"\n              />\n              <span v-else class=\"editable-title\" @click=\"clickEvent()\">{{ nodeConfig.nodeName }}</span>\n              <i class=\"anticon anticon-close close\" @click=\"delNode\"></i>\n            </template>\n          </div>\n          <div class=\"content\" @click=\"setPerson\">\n            <div class=\"text\">\n                <span class=\"placeholder\" v-if=\"!showText\">请选择{{defaultText}}</span>\n                {{showText}}\n            </div>\n            <i class=\"anticon anticon-right arrow\"></i>\n          </div>\n          <div class=\"error_tip\" v-if=\"isTried && nodeConfig.error\">\n            <i class=\"anticon anticon-exclamation-circle\"></i>\n          </div>\n      </div>\n      <addNode v-model:childNodeP=\"nodeConfig.childNode\" />\n    </div>\n    <div class=\"branch-wrap\" v-if=\"nodeConfig.type == 4\">\n    <div class=\"branch-box-wrap\">\n      <div class=\"branch-box\">\n        <button class=\"add-branch\" @click=\"addTerm\">添加条件</button>\n        <div class=\"col-box\" v-for=\"(item, index) in nodeConfig.conditionNodes\" :key=\"index\">\n          <div class=\"condition-node\">\n            <div class=\"condition-node-box\">\n              <div class=\"auto-judge\" :class=\"isTried && item.error ? 'error active' : ''\">\n                <div class=\"sort-left\" v-if=\"index != 0\" @click=\"arrTransfer(index, -1)\">&lt;</div>\n                <div class=\"title-wrapper\">\n                  <input\n                    v-if=\"isInputList[index]\"\n                    type=\"text\"\n                    class=\"ant-input editable-title-input\"\n                    @blur=\"blurEvent(index)\"\n                    @focus=\"$event.currentTarget.select()\"\n                    v-model=\"item.nodeName\"\n                  />\n                  <span v-else class=\"editable-title\" @click=\"clickEvent(index)\">{{ item.nodeName }}</span>\n                  <span class=\"priority-title\" @click=\"setPerson(item.priorityLevel)\">优先级{{ item.priorityLevel }}</span>\n                  <i class=\"anticon anticon-close close\" @click=\"delTerm(index)\"></i>\n                </div>\n                <div class=\"sort-right\" v-if=\"index != nodeConfig.conditionNodes.length - 1\" @click=\"arrTransfer(index)\">&gt;</div>\n                <div class=\"content\" @click=\"setPerson(item.priorityLevel)\">{{ conditionStr(nodeConfig, index) }}</div>\n                <div class=\"error_tip\" v-if=\"isTried && item.error\">\n                    <i class=\"anticon anticon-exclamation-circle\"></i>\n                </div>\n              </div>\n              <addNode v-model:childNodeP=\"item.childNode\" />\n            </div>\n          </div>\n          <nodeWrap v-if=\"item.childNode\" v-model:nodeConfig=\"item.childNode\" />\n          <template v-if=\"index == 0\">\n            <div class=\"top-left-cover-line\"></div>\n            <div class=\"bottom-left-cover-line\"></div>\n          </template>\n          <template v-if=\"index == nodeConfig.conditionNodes.length - 1\">\n            <div class=\"top-right-cover-line\"></div>\n            <div class=\"bottom-right-cover-line\"></div>\n          </template>\n        </div>\n      </div>\n      <addNode v-model:childNodeP=\"nodeConfig.childNode\" />\n    </div>\n  </div>\n    <nodeWrap v-if=\"nodeConfig.childNode\" v-model:nodeConfig=\"nodeConfig.childNode\" />\n</template>\n<script  setup>\nimport addNode from './addNode.vue'\nimport { onMounted, ref, watch, getCurrentInstance, computed } from 'vue'\nimport {\n  arrToStr,\n  conditionStr,\n  setApproverStr,\n  copyerStr,\n  bgColors,\n  placeholderList\n} from './util'\nimport { useWorkFlowStoreWithOut } from '@/store/modules/simpleWorkflow'\nlet _uid = getCurrentInstance().uid\n\nlet props = defineProps({\n  nodeConfig: {\n    type: Object,\n    default: () => ({})\n  },\n  flowPermission: {\n    type: Object,\n    // eslint-disable-next-line vue/require-valid-default-prop\n    default: () => []\n  }\n})\n\nlet defaultText = computed(() => {\n  return placeholderList[props.nodeConfig.type]\n})\nlet showText = computed(() => {\n  if (props.nodeConfig.type == 0) return arrToStr(props.flowPermission) || '所有人'\n  if (props.nodeConfig.type == 1) return setApproverStr(props.nodeConfig)\n  return copyerStr(props.nodeConfig)\n})\n\nlet isInputList = ref([])\nlet isInput = ref(false)\nconst resetConditionNodesErr = () => {\n  for (var i = 0; i < props.nodeConfig.conditionNodes.length; i++) {\n    // eslint-disable-next-line vue/no-mutating-props\n    props.nodeConfig.conditionNodes[i].error =\n      conditionStr(props.nodeConfig, i) == '请设置条件' &&\n      i != props.nodeConfig.conditionNodes.length - 1\n  }\n}\nonMounted(() => {\n  if (props.nodeConfig.type == 1) {\n    // eslint-disable-next-line vue/no-mutating-props\n    props.nodeConfig.error = !setApproverStr(props.nodeConfig)\n  } else if (props.nodeConfig.type == 2) {\n    // eslint-disable-next-line vue/no-mutating-props\n    props.nodeConfig.error = !copyerStr(props.nodeConfig)\n  } else if (props.nodeConfig.type == 4) {\n    resetConditionNodesErr()\n  }\n})\nlet emits = defineEmits(['update:flowPermission', 'update:nodeConfig'])\nlet store = useWorkFlowStoreWithOut()\nlet {\n  setPromoter,\n  setApprover,\n  setCopyer,\n  setCondition,\n  setFlowPermission,\n  setApproverConfig,\n  setCopyerConfig,\n  setConditionsConfig\n} = store\nlet isTried = computed(() => store.isTried)\nlet flowPermission1 = computed(() => store.flowPermission1)\nlet approverConfig1 = computed(() => store.approverConfig1)\nlet copyerConfig1 = computed(() => store.copyerConfig1)\nlet conditionsConfig1 = computed(() => store.conditionsConfig1)\nwatch(flowPermission1, (flow) => {\n  if (flow.flag && flow.id === _uid) {\n    emits('update:flowPermission', flow.value)\n  }\n})\nwatch(approverConfig1, (approver) => {\n  if (approver.flag && approver.id === _uid) {\n    emits('update:nodeConfig', approver.value)\n  }\n})\nwatch(copyerConfig1, (copyer) => {\n  if (copyer.flag && copyer.id === _uid) {\n    emits('update:nodeConfig', copyer.value)\n  }\n})\nwatch(conditionsConfig1, (condition) => {\n  if (condition.flag && condition.id === _uid) {\n    emits('update:nodeConfig', condition.value)\n  }\n})\n\nconst clickEvent = (index) => {\n  if (index || index === 0) {\n    isInputList.value[index] = true\n  } else {\n    isInput.value = true\n  }\n}\nconst blurEvent = (index) => {\n  if (index || index === 0) {\n    isInputList.value[index] = false\n    // eslint-disable-next-line vue/no-mutating-props\n    props.nodeConfig.conditionNodes[index].nodeName =\n      props.nodeConfig.conditionNodes[index].nodeName || '条件'\n  } else {\n    isInput.value = false\n    // eslint-disable-next-line vue/no-mutating-props\n    props.nodeConfig.nodeName = props.nodeConfig.nodeName || defaultText\n  }\n}\nconst delNode = () => {\n  emits('update:nodeConfig', props.nodeConfig.childNode)\n}\nconst addTerm = () => {\n  let len = props.nodeConfig.conditionNodes.length + 1\n  // eslint-disable-next-line vue/no-mutating-props\n  props.nodeConfig.conditionNodes.push({\n    nodeName: '条件' + len,\n    type: 3,\n    priorityLevel: len,\n    conditionList: [],\n    nodeUserList: [],\n    childNode: null\n  })\n  resetConditionNodesErr()\n  emits('update:nodeConfig', props.nodeConfig)\n}\nconst delTerm = (index) => {\n  // eslint-disable-next-line vue/no-mutating-props\n  props.nodeConfig.conditionNodes.splice(index, 1)\n  props.nodeConfig.conditionNodes.map((item, index) => {\n    item.priorityLevel = index + 1\n    item.nodeName = `条件${index + 1}`\n  })\n  resetConditionNodesErr()\n  emits('update:nodeConfig', props.nodeConfig)\n  if (props.nodeConfig.conditionNodes.length == 1) {\n    if (props.nodeConfig.childNode) {\n      if (props.nodeConfig.conditionNodes[0].childNode) {\n        reData(props.nodeConfig.conditionNodes[0].childNode, props.nodeConfig.childNode)\n      } else {\n        // eslint-disable-next-line vue/no-mutating-props\n        props.nodeConfig.conditionNodes[0].childNode = props.nodeConfig.childNode\n      }\n    }\n    emits('update:nodeConfig', props.nodeConfig.conditionNodes[0].childNode)\n  }\n}\nconst reData = (data, addData) => {\n  if (!data.childNode) {\n    data.childNode = addData\n  } else {\n    reData(data.childNode, addData)\n  }\n}\nconst setPerson = (priorityLevel) => {\n  var { type } = props.nodeConfig\n  if (type == 0) {\n    setPromoter(true)\n    setFlowPermission({\n      value: props.flowPermission,\n      flag: false,\n      id: _uid\n    })\n  } else if (type == 1) {\n    setApprover(true)\n    setApproverConfig({\n      value: {\n        ...JSON.parse(JSON.stringify(props.nodeConfig)),\n        ...{ settype: props.nodeConfig.settype ? props.nodeConfig.settype : 1 }\n      },\n      flag: false,\n      id: _uid\n    })\n  } else if (type == 2) {\n    setCopyer(true)\n    setCopyerConfig({\n      value: JSON.parse(JSON.stringify(props.nodeConfig)),\n      flag: false,\n      id: _uid\n    })\n  } else {\n    setCondition(true)\n    setConditionsConfig({\n      value: JSON.parse(JSON.stringify(props.nodeConfig)),\n      priorityLevel,\n      flag: false,\n      id: _uid\n    })\n  }\n}\nconst arrTransfer = (index, type = 1) => {\n  //向左-1,向右1\n  // eslint-disable-next-line vue/no-mutating-props\n  props.nodeConfig.conditionNodes[index] = props.nodeConfig.conditionNodes.splice(\n    index + type,\n    1,\n    props.nodeConfig.conditionNodes[index]\n  )[0]\n  props.nodeConfig.conditionNodes.map((item, index) => {\n    item.priorityLevel = index + 1\n  })\n  resetConditionNodesErr()\n  emits('update:nodeConfig', props.nodeConfig)\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/SimpleProcessDesigner/src/util.ts",
    "content": "/**\n * todo\n */\nexport const arrToStr = (arr?: [{ name: string }]) => {\n  if (arr) {\n    return arr\n      .map((item) => {\n        return item.name\n      })\n      .toString()\n  }\n}\n\nexport const setApproverStr = (nodeConfig: any) => {\n  if (nodeConfig.settype == 1) {\n    if (nodeConfig.nodeUserList.length == 1) {\n      return nodeConfig.nodeUserList[0].name\n    } else if (nodeConfig.nodeUserList.length > 1) {\n      if (nodeConfig.examineMode == 1) {\n        return arrToStr(nodeConfig.nodeUserList)\n      } else if (nodeConfig.examineMode == 2) {\n        return nodeConfig.nodeUserList.length + '人会签'\n      }\n    }\n  } else if (nodeConfig.settype == 2) {\n    const level =\n      nodeConfig.directorLevel == 1 ? '直接主管' : '第' + nodeConfig.directorLevel + '级主管'\n    if (nodeConfig.examineMode == 1) {\n      return level\n    } else if (nodeConfig.examineMode == 2) {\n      return level + '会签'\n    }\n  } else if (nodeConfig.settype == 4) {\n    if (nodeConfig.selectRange == 1) {\n      return '发起人自选'\n    } else {\n      if (nodeConfig.nodeUserList.length > 0) {\n        if (nodeConfig.selectRange == 2) {\n          return '发起人自选'\n        } else {\n          return '发起人从' + nodeConfig.nodeUserList[0].name + '中自选'\n        }\n      } else {\n        return ''\n      }\n    }\n  } else if (nodeConfig.settype == 5) {\n    return '发起人自己'\n  } else if (nodeConfig.settype == 7) {\n    return '从直接主管到通讯录中级别最高的第' + nodeConfig.examineEndDirectorLevel + '个层级主管'\n  }\n}\n\nexport const copyerStr = (nodeConfig: any) => {\n  if (nodeConfig.nodeUserList.length != 0) {\n    return arrToStr(nodeConfig.nodeUserList)\n  } else {\n    if (nodeConfig.ccSelfSelectFlag == 1) {\n      return '发起人自选'\n    }\n  }\n}\nexport const conditionStr = (nodeConfig, index) => {\n  const { conditionList, nodeUserList } = nodeConfig.conditionNodes[index]\n  if (conditionList.length == 0) {\n    return index == nodeConfig.conditionNodes.length - 1 &&\n      nodeConfig.conditionNodes[0].conditionList.length != 0\n      ? '其他条件进入此流程'\n      : '请设置条件'\n  } else {\n    let str = ''\n    for (let i = 0; i < conditionList.length; i++) {\n      const {\n        columnId,\n        columnType,\n        showType,\n        showName,\n        optType,\n        zdy1,\n        opt1,\n        zdy2,\n        opt2,\n        fixedDownBoxValue\n      } = conditionList[i]\n      if (columnId == 0) {\n        if (nodeUserList.length != 0) {\n          str += '发起人属于：'\n          str +=\n            nodeUserList\n              .map((item) => {\n                return item.name\n              })\n              .join('或') + ' 并且 '\n        }\n      }\n      if (columnType == 'String' && showType == '3') {\n        if (zdy1) {\n          str += showName + '属于：' + dealStr(zdy1, JSON.parse(fixedDownBoxValue)) + ' 并且 '\n        }\n      }\n      if (columnType == 'Double') {\n        if (optType != 6 && zdy1) {\n          const optTypeStr = ['', '<', '>', '≤', '=', '≥'][optType]\n          str += `${showName} ${optTypeStr} ${zdy1} 并且 `\n        } else if (optType == 6 && zdy1 && zdy2) {\n          str += `${zdy1} ${opt1} ${showName} ${opt2} ${zdy2} 并且 `\n        }\n      }\n    }\n    return str ? str.substring(0, str.length - 4) : '请设置条件'\n  }\n}\n\nexport const dealStr = (str: string, obj) => {\n  const arr = []\n  const list = str.split(',')\n  for (const elem in obj) {\n    list.map((item) => {\n      if (item == elem) {\n        arr.push(obj[elem].value)\n      }\n    })\n  }\n  return arr.join('或')\n}\n\nexport const removeEle = (arr, elem, key = 'id') => {\n  let includesIndex\n  arr.map((item, index) => {\n    if (item[key] == elem[key]) {\n      includesIndex = index\n    }\n  })\n  arr.splice(includesIndex, 1)\n}\n\nexport const bgColors = ['87, 106, 149', '255, 148, 62', '50, 150, 250']\nexport const placeholderList = ['发起人', '审核人', '抄送人']\nexport const setTypes = [\n  { value: 1, label: '指定成员' },\n  { value: 2, label: '主管' },\n  { value: 4, label: '发起人自选' },\n  { value: 5, label: '发起人自己' },\n  { value: 7, label: '连续多级主管' }\n]\n\nexport const selectModes = [\n  { value: 1, label: '选一个人' },\n  { value: 2, label: '选多个人' }\n]\n\nexport const selectRanges = [\n  { value: 1, label: '全公司' },\n  { value: 2, label: '指定成员' },\n  { value: 3, label: '指定角色' }\n]\n\nexport const optTypes = [\n  { value: '1', label: '小于' },\n  { value: '2', label: '大于' },\n  { value: '3', label: '小于等于' },\n  { value: '4', label: '等于' },\n  { value: '5', label: '大于等于' },\n  { value: '6', label: '介于两个数之间' }\n]\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/SimpleProcessDesigner/theme/workflow.css",
    "content": "\n.clearfix {\n    zoom: 1\n}\n\n.clearfix:after,\n.clearfix:before {\n    content: \"\";\n    display: table\n}\n\n.clearfix:after {\n    clear: both\n}\n\n@font-face {\n    font-family: anticon;\n    font-display: fallback;\n    src: url(\"https://at.alicdn.com/t/font_148784_v4ggb6wrjmkotj4i.eot\");\n    src: url(\"https://at.alicdn.com/t/font_148784_v4ggb6wrjmkotj4i.woff\") format(\"woff\"), url(\"https://at.alicdn.com/t/font_148784_v4ggb6wrjmkotj4i.ttf\") format(\"truetype\"), url(\"https://at.alicdn.com/t/font_148784_v4ggb6wrjmkotj4i.svg#iconfont\") format(\"svg\")\n}\n\n.anticon {\n    display: inline-block;\n    font-style: normal;\n    vertical-align: baseline;\n    text-align: center;\n    text-transform: none;\n    line-height: 1;\n    text-rendering: optimizeLegibility;\n    -webkit-font-smoothing: antialiased;\n    -moz-osx-font-smoothing: grayscale\n}\n\n.anticon:before {\n    display: block;\n    font-family: anticon!important\n}\n.anticon-close:before {\n  content: \"\\E633\"\n}\n.anticon-right:before {\n    content: \"\\E61F\"\n}\n.anticon-exclamation-circle{\n    color: rgb(242, 86, 67)\n}\n.anticon-exclamation-circle:before {\n    content: \"\\E62C\"\n}\n\n.anticon-left:before {\n    content: \"\\E620\"\n}\n\n.anticon-close-circle:before {\n    content: \"\\E62E\"\n}\n  \n.ant-btn {\n    line-height: 1.5;\n    display: inline-block;\n    font-weight: 400;\n    text-align: center;\n    touch-action: manipulation;\n    cursor: pointer;\n    background-image: none;\n    border: 1px solid transparent;\n    white-space: nowrap;\n    padding: 0 15px;\n    font-size: 14px;\n    border-radius: 4px;\n    height: 32px;\n    user-select: none;\n    transition: all .3s cubic-bezier(.645, .045, .355, 1);\n    position: relative;\n    color: rgba(0, 0, 0, .65);\n    background-color: #fff;\n    border-color: #d9d9d9\n}\n\n.ant-btn>.anticon {\n    line-height: 1\n}\n\n.ant-btn,\n.ant-btn:active,\n.ant-btn:focus {\n    outline: 0\n}\n\n.ant-btn>a:only-child {\n    color: currentColor\n}\n\n.ant-btn>a:only-child:after {\n    content: \"\";\n    position: absolute;\n    top: 0;\n    left: 0;\n    bottom: 0;\n    right: 0;\n    background: transparent\n}\n\n.ant-btn:focus,\n.ant-btn:hover {\n    color: #40a9ff;\n    background-color: #fff;\n    border-color: #40a9ff\n}\n\n.ant-btn:focus>a:only-child,\n.ant-btn:hover>a:only-child {\n    color: currentColor\n}\n\n.ant-btn:focus>a:only-child:after,\n.ant-btn:hover>a:only-child:after {\n    content: \"\";\n    position: absolute;\n    top: 0;\n    left: 0;\n    bottom: 0;\n    right: 0;\n    background: transparent\n}\n\n.ant-btn.active,\n.ant-btn:active {\n    color: #096dd9;\n    background-color: #fff;\n    border-color: #096dd9\n}\n\n.ant-btn.active>a:only-child,\n.ant-btn:active>a:only-child {\n    color: currentColor\n}\n\n.ant-btn.active>a:only-child:after,\n.ant-btn:active>a:only-child:after {\n    content: \"\";\n    position: absolute;\n    top: 0;\n    left: 0;\n    bottom: 0;\n    right: 0;\n    background: transparent\n}\n\n.ant-btn.active,\n.ant-btn:active,\n.ant-btn:focus,\n.ant-btn:hover {\n    background: #fff;\n    text-decoration: none\n}\n\n.ant-btn>i,\n.ant-btn>span {\n    pointer-events: none\n}\n\n.ant-btn:before {\n    position: absolute;\n    top: -1px;\n    left: -1px;\n    bottom: -1px;\n    right: -1px;\n    background: #fff;\n    opacity: .35;\n    content: \"\";\n    border-radius: inherit;\n    z-index: 1;\n    transition: opacity .2s;\n    pointer-events: none;\n    display: none\n}\n\n.ant-btn .anticon {\n    transition: margin-left .3s cubic-bezier(.645, .045, .355, 1)\n}\n\n.ant-btn:active>span,\n.ant-btn:focus>span {\n    position: relative\n}\n\n.ant-btn>.anticon+span,\n.ant-btn>span+.anticon {\n    margin-left: 8px\n}\n\n.ant-input {\n    font-family: Chinese Quote, -apple-system, BlinkMacSystemFont, Segoe UI, PingFang SC, Hiragino Sans GB, Microsoft YaHei, Helvetica Neue, Helvetica, Arial, sans-serif;\n    font-variant: tabular-nums;\n    box-sizing: border-box;\n    margin: 0;\n    padding: 0;\n    list-style: none;\n    position: relative;\n    display: inline-block;\n    padding: 4px 11px;\n    width: 100%;\n    height: 32px;\n    font-size: 14px;\n    line-height: 1.5;\n    color: rgba(0, 0, 0, .65);\n    background-color: #fff;\n    background-image: none;\n    border: 1px solid #d9d9d9;\n    border-radius: 4px;\n    transition: all .3s\n}\n\n.ant-input::-moz-placeholder {\n    color: #bfbfbf;\n    opacity: 1\n}\n\n.ant-input:-ms-input-placeholder {\n    color: #bfbfbf\n}\n\n.ant-input::-webkit-input-placeholder {\n    color: #bfbfbf\n}\n\n.ant-input:focus,\n.ant-input:hover {\n    border-color: #40a9ff;\n    border-right-width: 1px!important\n}\n\n.ant-input:focus {\n    outline: 0;\n    box-shadow: 0 0 0 2px rgba(24, 144, 255, .2)\n}\n\ntextarea.ant-input {\n    max-width: 100%;\n    height: auto;\n    vertical-align: bottom;\n    transition: all .3s, height 0s;\n    min-height: 32px\n}\n\na,\nabbr,\nacronym,\naddress,\napplet,\narticle,\naside,\naudio,\nb,\nbig,\nblockquote,\nbody,\ncanvas,\ncaption,\ncenter,\ncite,\ncode,\ndd,\ndel,\ndetails,\ndfn,\ndiv,\ndl,\ndt,\nem,\nfieldset,\nfigcaption,\nfigure,\nfooter,\nform,\nh1,\nh2,\nh3,\nh4,\nh5,\nh6,\nheader,\nhgroup,\nhtml,\ni,\niframe,\nimg,\nins,\nkbd,\nlabel,\nlegend,\nli,\nmark,\nmenu,\nnav,\nobject,\nol,\np,\npre,\nq,\ns,\nsamp,\nsection,\nsmall,\nspan,\nstrike,\nstrong,\nsub,\nsummary,\nsup,\ntable,\ntbody,\ntd,\ntfoot,\nth,\nthead,\ntime,\ntr,\ntt,\nu,\nul,\nvar,\nvideo {\n    margin: 0;\n    padding: 0;\n    border: 0;\n    outline: 0;\n    font-size: 100%;\n    font: inherit;\n    vertical-align: baseline\n}\n\n*,\n:after,\n:before {\n    -webkit-box-sizing: border-box;\n    -moz-box-sizing: border-box;\n    box-sizing: border-box\n}\n\nhtml {\n    font-family: sans-serif;\n    -ms-text-size-adjust: 100%;\n    -webkit-text-size-adjust: 100%\n}\n\nbody,\nhtml {\n    font-size: 14px\n}\n\nbody {\n    font-family: Microsoft Yahei, Lucida Grande, Lucida Sans Unicode, Helvetica, Arial, Verdana, sans-serif;\n    line-height: 1.6;\n    background-color: #fff;\n    position: static!important;\n    -webkit-tap-highlight-color: rgba(0, 0, 0, 0)\n}\n\nol,\nul {\n    list-style-type: none\n}\n\nb,\nstrong {\n    font-weight: 700\n}\n\nimg {\n    border: 0\n}\n\nbutton,\ninput,\nselect,\ntextarea {\n    font-family: inherit;\n    font-size: 100%;\n    margin: 0\n}\n\ntextarea {\n    overflow: auto;\n    vertical-align: top;\n    -webkit-appearance: none\n}\n\nbutton,\ninput {\n    line-height: normal\n}\n\nbutton,\nselect {\n    text-transform: none\n}\n\nbutton,\nhtml input[type=button],\ninput[type=reset],\ninput[type=submit] {\n    -webkit-appearance: button;\n    cursor: pointer\n}\n\ninput[type=search] {\n    -webkit-appearance: textfield;\n    -moz-box-sizing: content-box;\n    -webkit-box-sizing: content-box;\n    box-sizing: content-box\n}\n\ninput[type=search]::-webkit-search-cancel-button,\ninput[type=search]::-webkit-search-decoration {\n    -webkit-appearance: none\n}\n\nbutton::-moz-focus-inner,\ninput::-moz-focus-inner {\n    border: 0;\n    padding: 0\n}\n\ntable {\n    width: 100%;\n    border-spacing: 0;\n    border-collapse: collapse\n}\n\ntable,\ntd,\nth {\n    border: 0\n}\n\ntd,\nth {\n    padding: 0;\n    vertical-align: top\n}\n\nth {\n    font-weight: 700;\n    text-align: left\n}\n\nthead th {\n    white-space: nowrap\n}\n\na {\n    text-decoration: none;\n    cursor: pointer;\n    color: #3296fa\n}\n\na:active,\na:hover {\n    outline: 0;\n    color: #3296fa\n}\n\nsmall {\n    font-size: 80%\n}\n\nbody,\nhtml {\n    font-size: 12px!important;\n    color: #191f25!important;\n    background: #f6f6f6!important\n}\n\n.wrap {\n    display: -webkit-box;\n    display: -ms-flexbox;\n    display: flex;\n    -webkit-box-orient: vertical;\n    -webkit-box-direction: normal;\n    -ms-flex-direction: column;\n    flex-direction: column;\n    height: 100%\n}\n\n@font-face {\n    font-family: IconFont;\n    src: url(\"//at.alicdn.com/t/font_135284_ph2thxxbzgf.eot\");\n    src: url(\"//at.alicdn.com/t/font_135284_ph2thxxbzgf.eot?#iefix\") format(\"embedded-opentype\"), url(\"//at.alicdn.com/t/font_135284_ph2thxxbzgf.woff\") format(\"woff\"), url(\"//at.alicdn.com/t/font_135284_ph2thxxbzgf.ttf\") format(\"truetype\"), url(\"//at.alicdn.com/t/font_135284_ph2thxxbzgf.svg#IconFont\") format(\"svg\")\n}\n\n.iconfont {\n    font-family: IconFont!important;\n    font-size: 16px;\n    font-style: normal;\n    -webkit-font-smoothing: antialiased;\n    -webkit-text-stroke-width: .2px;\n    -moz-osx-font-smoothing: grayscale\n}\n\n.fd-nav {\n    position: fixed;\n    top: 0;\n    left: 0;\n    right: 0;\n    z-index: 997;\n    width: 100%;\n    height: 60px;\n    font-size: 14px;\n    color: #fff;\n    background: #3296fa;\n    display: flex;\n    align-items: center\n}\n\n.fd-nav>* {\n    flex: 1;\n    width: 100%\n}\n\n.fd-nav .fd-nav-left {\n    display: -webkit-box;\n    display: flex;\n    align-items: center\n}\n\n.fd-nav .fd-nav-center {\n    flex: none;\n    width: 600px;\n    text-align: center\n}\n\n.fd-nav .fd-nav-right {\n    display: flex;\n    align-items: center;\n    justify-content: flex-end;\n    text-align: right\n}\n\n.fd-nav .fd-nav-back {\n    display: inline-block;\n    width: 60px;\n    height: 60px;\n    font-size: 22px;\n    border-right: 1px solid #1583f2;\n    text-align: center;\n    cursor: pointer\n}\n\n.fd-nav .fd-nav-back:hover {\n    background: #5af\n}\n\n.fd-nav .fd-nav-back:active {\n    background: #1583f2\n}\n\n.fd-nav .fd-nav-back .anticon {\n    line-height: 60px\n}\n\n.fd-nav .fd-nav-title {\n    width: 0;\n    flex: 1;\n    overflow: hidden;\n    white-space: nowrap;\n    text-overflow: ellipsis;\n    padding: 0 15px\n}\n\n.fd-nav a {\n    color: #fff;\n    margin-left: 12px\n}\n\n.fd-nav .button-publish {\n    min-width: 80px;\n    margin-left: 4px;\n    margin-right: 15px;\n    color: #3296fa;\n    border-color: #fff\n}\n\n.fd-nav .button-publish.ant-btn:focus,\n.fd-nav .button-publish.ant-btn:hover {\n    color: #3296fa;\n    border-color: #fff;\n    box-shadow: 0 10px 20px 0 rgba(0, 0, 0, .3)\n}\n\n.fd-nav .button-publish.ant-btn:active {\n    color: #3296fa;\n    background: #d6eaff;\n    box-shadow: none\n}\n\n.fd-nav .button-preview {\n    min-width: 80px;\n    margin-left: 16px;\n    margin-right: 4px;\n    color: #fff;\n    border-color: #fff;\n    background: transparent\n}\n\n.fd-nav .button-preview.ant-btn:focus,\n.fd-nav .button-preview.ant-btn:hover {\n    color: #fff;\n    border-color: #fff;\n    background: #59acfc\n}\n\n.fd-nav .button-preview.ant-btn:active {\n    color: #fff;\n    border-color: #fff;\n    background: #2186ef\n}\n\n.fd-nav-content {\n    position: fixed;\n    top: 60px;\n    left: 0;\n    right: 0;\n    bottom: 0;\n    z-index: 1;\n    overflow-x: hidden;\n    overflow-y: auto;\n    padding-bottom: 30px\n}\n\n.error-modal-desc {\n    font-size: 13px;\n    color: rgba(25, 31, 37, .56);\n    line-height: 22px;\n    margin-bottom: 14px\n}\n\n.error-modal-list {\n    height: 200px;\n    overflow-y: auto;\n    margin-right: -25px;\n    padding-right: 25px\n}\n\n.error-modal-item {\n    padding: 10px 20px;\n    line-height: 21px;\n    background: #f6f6f6;\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    margin-bottom: 8px;\n    border-radius: 4px\n}\n\n.error-modal-item-label {\n    flex: none;\n    font-size: 15px;\n    color: rgba(25, 31, 37, .56);\n    padding-right: 10px\n}\n\n.error-modal-item-content {\n    text-align: right;\n    flex: 1;\n    font-size: 13px;\n    color: #191f25\n}\n\n#body.blur {\n    -webkit-filter: blur(3px);\n    filter: blur(3px)\n}\n\n.zoom {\n    display: flex;\n    position: fixed;\n    -webkit-box-align: center;\n    -ms-flex-align: center;\n    align-items: center;\n    -webkit-box-pack: justify;\n    -ms-flex-pack: justify;\n    justify-content: space-between;\n    height: 40px;\n    width: 125px;\n    right: 40px;\n    margin-top: 30px;\n    z-index: 10\n}\n\n.zoom .zoom-in,\n.zoom .zoom-out {\n    width: 30px;\n    height: 30px;\n    background: #fff;\n    color: #c1c1cd;\n    cursor: pointer;\n    background-size: 100%;\n    background-repeat: no-repeat\n}\n\n.zoom .zoom-out {\n    background-image: url(https://gw.alicdn.com/tfs/TB1s0qhBHGYBuNjy0FoXXciBFXa-90-90.png)\n}\n\n.zoom .zoom-out.disabled {\n    opacity: .5\n}\n\n.zoom .zoom-in {\n    background-image: url(https://gw.alicdn.com/tfs/TB1UIgJBTtYBeNjy1XdXXXXyVXa-90-90.png)\n}\n\n.zoom .zoom-in.disabled {\n    opacity: .5\n}\n\n.auto-judge:hover .editable-title,\n.node-wrap-box:hover .editable-title {\n    border-bottom: 1px dashed #fff\n}\n\n.auto-judge:hover .editable-title.editing,\n.node-wrap-box:hover .editable-title.editing {\n    text-decoration: none;\n    border: 1px solid #d9d9d9\n}\n\n.auto-judge:hover .editable-title {\n    border-color: #15bc83\n}\n\n.editable-title {\n    line-height: 15px;\n    overflow: hidden;\n    white-space: nowrap;\n    text-overflow: ellipsis;\n    border-bottom: 1px dashed transparent\n}\n\n.editable-title:before {\n    content: \"\";\n    position: absolute;\n    top: 0;\n    left: 0;\n    bottom: 0;\n    right: 40px\n}\n\n.editable-title:hover {\n    border-bottom: 1px dashed #fff\n}\n\n.editable-title-input {\n    flex: none;\n    height: 18px;\n    padding-left: 4px;\n    text-indent: 0;\n    font-size: 12px;\n    line-height: 18px;\n    z-index: 1\n}\n\n.editable-title-input:hover {\n    text-decoration: none\n}\n\n.ant-btn {\n    position: relative\n}\n\n.node-wrap-box {\n    display: -webkit-inline-box;\n    display: -ms-inline-flexbox;\n    display: inline-flex;\n    -webkit-box-orient: vertical;\n    -webkit-box-direction: normal;\n    -ms-flex-direction: column;\n    flex-direction: column;\n    position: relative;\n    width: 220px;\n    min-height: 72px;\n    -ms-flex-negative: 0;\n    flex-shrink: 0;\n    background: #fff;\n    border-radius: 4px;\n    cursor: pointer\n}\n\n.node-wrap-box:after {\n    pointer-events: none;\n    content: \"\";\n    position: absolute;\n    top: 0;\n    bottom: 0;\n    left: 0;\n    right: 0;\n    z-index: 2;\n    border-radius: 4px;\n    border: 1px solid transparent;\n    transition: all .1s cubic-bezier(.645, .045, .355, 1);\n    box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .1)\n}\n\n.node-wrap-box.active:after,\n.node-wrap-box:active:after,\n.node-wrap-box:hover:after {\n    border: 1px solid #3296fa;\n    box-shadow: 0 0 6px 0 rgba(50, 150, 250, .3)\n}\n\n.node-wrap-box.active .close,\n.node-wrap-box:active .close,\n.node-wrap-box:hover .close {\n    display: block\n}\n\n.node-wrap-box.error:after {\n    border: 1px solid #f25643;\n    box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .1)\n}\n\n.node-wrap-box .title {\n    position: relative;\n    display: flex;\n    align-items: center;\n    padding-left: 16px;\n    padding-right: 30px;\n    width: 100%;\n    height: 24px;\n    line-height: 24px;\n    font-size: 12px;\n    color: #fff;\n    text-align: left;\n    background: #576a95;\n    border-radius: 4px 4px 0 0\n}\n\n.node-wrap-box .title .iconfont {\n    font-size: 12px;\n    margin-right: 5px\n}\n\n.node-wrap-box .placeholder {\n    color: #bfbfbf\n}\n\n.node-wrap-box .close {\n    display: none;\n    position: absolute;\n    right: 10px;\n    top: 50%;\n    transform: translateY(-50%);\n    width: 20px;\n    height: 20px;\n    font-size: 14px;\n    color: #fff;\n    border-radius: 50%;\n    text-align: center;\n    line-height: 20px\n}\n\n.node-wrap-box .content {\n    position: relative;\n    font-size: 14px;\n    padding: 16px;\n    padding-right: 30px\n}\n\n.node-wrap-box .content .text {\n    overflow: hidden;\n    text-overflow: ellipsis;\n    display: -webkit-box;\n    -webkit-line-clamp: 3;\n    -webkit-box-orient: vertical\n}\n\n.node-wrap-box .content .arrow {\n    position: absolute;\n    right: 10px;\n    top: 50%;\n    transform: translateY(-50%);\n    width: 20px;\n    height: 14px;\n    font-size: 14px;\n    color: #979797\n}\n\n.start-node.node-wrap-box .content .text {\n    display: block;\n    white-space: nowrap\n}\n\n.node-wrap-box:before {\n    content: \"\";\n    position: absolute;\n    top: -12px;\n    left: 50%;\n    -webkit-transform: translateX(-50%);\n    transform: translateX(-50%);\n    width: 0;\n    height: 4px;\n    border-style: solid;\n    border-width: 8px 6px 4px;\n    border-color: #cacaca transparent transparent;\n    background: #f5f5f7\n}\n\n.node-wrap-box.start-node:before {\n    content: none\n}\n\n.top-left-cover-line {\n    left: -1px\n}\n\n.top-left-cover-line,\n.top-right-cover-line {\n    position: absolute;\n    height: 8px;\n    width: 50%;\n    background-color: #f5f5f7;\n    top: -4px\n}\n\n.top-right-cover-line {\n    right: -1px\n}\n\n.bottom-left-cover-line {\n    left: -1px\n}\n\n.bottom-left-cover-line,\n.bottom-right-cover-line {\n    position: absolute;\n    height: 8px;\n    width: 50%;\n    background-color: #f5f5f7;\n    bottom: -4px\n}\n\n.bottom-right-cover-line {\n    right: -1px\n}\n\n.dingflow-design {\n    width: 100%;\n    background-color: #f5f5f7;\n    overflow: auto;\n    position: absolute;\n    bottom: 0;\n    left: 0;\n    right: 0;\n    top: 0\n}\n\n.dingflow-design .box-scale {\n    transform: scale(1);\n    display: inline-block;\n    position: relative;\n    width: 100%;\n    padding: 54.5px 0;\n    -webkit-box-align: start;\n    -ms-flex-align: start;\n    align-items: flex-start;\n    -webkit-box-pack: center;\n    -ms-flex-pack: center;\n    justify-content: center;\n    -ms-flex-wrap: wrap;\n    flex-wrap: wrap;\n    min-width: -webkit-min-content;\n    min-width: -moz-min-content;\n    min-width: min-content;\n    background-color: #f5f5f7;\n    transform-origin: 50% 0px 0px;\n}\n\n.dingflow-design .node-wrap {\n    flex-direction: column;\n    -webkit-box-pack: start;\n    -ms-flex-pack: start;\n    justify-content: flex-start;\n    -webkit-box-align: center;\n    -ms-flex-align: center;\n    align-items: center;\n    -ms-flex-wrap: wrap;\n    flex-wrap: wrap;\n    -webkit-box-flex: 1;\n    -ms-flex-positive: 1;\n    padding: 0 50px;\n    position: relative\n}\n\n.dingflow-design .branch-wrap,\n.dingflow-design .node-wrap {\n    display: inline-flex;\n    width: 100%\n}\n\n.dingflow-design .branch-box-wrap {\n    display: flex;\n    -webkit-box-orient: vertical;\n    -webkit-box-direction: normal;\n    -ms-flex-direction: column;\n    flex-direction: column;\n    -ms-flex-wrap: wrap;\n    flex-wrap: wrap;\n    -webkit-box-align: center;\n    -ms-flex-align: center;\n    align-items: center;\n    min-height: 270px;\n    width: 100%;\n    -ms-flex-negative: 0;\n    flex-shrink: 0\n}\n\n.dingflow-design .branch-box {\n    display: flex;\n    overflow: visible;\n    min-height: 180px;\n    height: auto;\n    border-bottom: 2px solid #ccc;\n    border-top: 2px solid #ccc;\n    position: relative;\n    margin-top: 15px\n}\n\n.dingflow-design .branch-box .col-box {\n    background: #f5f5f7\n}\n\n.dingflow-design .branch-box .col-box:before {\n    content: \"\";\n    position: absolute;\n    top: 0;\n    left: 0;\n    right: 0;\n    bottom: 0;\n    z-index: 0;\n    margin: auto;\n    width: 2px;\n    height: 100%;\n    background-color: #cacaca\n}\n\n.dingflow-design .add-branch {\n    border: none;\n    outline: none;\n    user-select: none;\n    justify-content: center;\n    font-size: 12px;\n    padding: 0 10px;\n    height: 30px;\n    line-height: 30px;\n    border-radius: 15px;\n    color: #3296fa;\n    background: #fff;\n    box-shadow: 0 2px 4px 0 rgba(0, 0, 0, .1);\n    position: absolute;\n    top: -16px;\n    left: 50%;\n    transform: translateX(-50%);\n    transform-origin: center center;\n    cursor: pointer;\n    z-index: 1;\n    display: inline-flex;\n    align-items: center;\n    -webkit-transition: all .3s cubic-bezier(.645, .045, .355, 1);\n    transition: all .3s cubic-bezier(.645, .045, .355, 1)\n}\n\n.dingflow-design .add-branch:hover {\n    transform: translateX(-50%) scale(1.1);\n    box-shadow: 0 8px 16px 0 rgba(0, 0, 0, .1)\n}\n\n.dingflow-design .add-branch:active {\n    transform: translateX(-50%);\n    box-shadow: none\n}\n\n.dingflow-design .col-box {\n    display: inline-flex;\n    -webkit-box-orient: vertical;\n    -webkit-box-direction: normal;\n    flex-direction: column;\n    -webkit-box-align: center;\n    align-items: center;\n    position: relative\n}\n\n.dingflow-design .condition-node {\n    min-height: 220px\n}\n\n.dingflow-design .condition-node,\n.dingflow-design .condition-node-box {\n    display: inline-flex;\n    -webkit-box-orient: vertical;\n    -webkit-box-direction: normal;\n    flex-direction: column;\n    -webkit-box-flex: 1\n}\n\n.dingflow-design .condition-node-box {\n    padding-top: 30px;\n    padding-right: 50px;\n    padding-left: 50px;\n    -webkit-box-pack: center;\n    justify-content: center;\n    -webkit-box-align: center;\n    align-items: center;\n    flex-grow: 1;\n    position: relative\n}\n\n.dingflow-design .condition-node-box:before {\n    content: \"\";\n    position: absolute;\n    top: 0;\n    left: 0;\n    right: 0;\n    bottom: 0;\n    margin: auto;\n    width: 2px;\n    height: 100%;\n    background-color: #cacaca\n}\n\n.dingflow-design .auto-judge {\n    position: relative;\n    width: 220px;\n    min-height: 72px;\n    background: #fff;\n    border-radius: 4px;\n    padding: 14px 19px;\n    cursor: pointer\n}\n\n.dingflow-design .auto-judge:after {\n    pointer-events: none;\n    content: \"\";\n    position: absolute;\n    top: 0;\n    bottom: 0;\n    left: 0;\n    right: 0;\n    z-index: 2;\n    border-radius: 4px;\n    border: 1px solid transparent;\n    transition: all .1s cubic-bezier(.645, .045, .355, 1);\n    box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .1)\n}\n\n.dingflow-design .auto-judge.active:after,\n.dingflow-design .auto-judge:active:after,\n.dingflow-design .auto-judge:hover:after {\n    border: 1px solid #3296fa;\n    box-shadow: 0 0 6px 0 rgba(50, 150, 250, .3)\n}\n\n.dingflow-design .auto-judge.active .close,\n.dingflow-design .auto-judge:active .close,\n.dingflow-design .auto-judge:hover .close {\n    display: block\n}\n\n.dingflow-design .auto-judge.error:after {\n    border: 1px solid #f25643;\n    box-shadow: 0 2px 5px 0 rgba(0, 0, 0, .1)\n}\n\n.dingflow-design .auto-judge .title-wrapper {\n    position: relative;\n    font-size: 12px;\n    color: #15bc83;\n    text-align: left;\n    line-height: 16px\n}\n\n.dingflow-design .auto-judge .title-wrapper .editable-title {\n    display: inline-block;\n    max-width: 120px;\n    overflow: hidden;\n    white-space: nowrap;\n    text-overflow: ellipsis\n}\n\n.dingflow-design .auto-judge .title-wrapper .priority-title {\n    display: inline-block;\n    float: right;\n    margin-right: 10px;\n    color: rgba(25, 31, 37, .56)\n}\n\n.dingflow-design .auto-judge .placeholder {\n    color: #bfbfbf\n}\n\n.dingflow-design .auto-judge .close {\n    display: none;\n    position: absolute;\n    right: -10px;\n    top: -10px;\n    width: 20px;\n    height: 20px;\n    font-size: 14px;\n    color: rgba(0, 0, 0, .25);\n    border-radius: 50%;\n    text-align: center;\n    line-height: 20px;\n    z-index: 2\n}\n\n.dingflow-design .auto-judge .content {\n    font-size: 14px;\n    color: #191f25;\n    text-align: left;\n    margin-top: 6px;\n    overflow: hidden;\n    text-overflow: ellipsis;\n    display: -webkit-box;\n    -webkit-line-clamp: 3;\n    -webkit-box-orient: vertical\n}\n\n.dingflow-design .auto-judge .sort-left,\n.dingflow-design .auto-judge .sort-right {\n    position: absolute;\n    top: 0;\n    bottom: 0;\n    display: none;\n    z-index: 1\n}\n\n.dingflow-design .auto-judge .sort-left {\n    left: 0;\n    border-right: 1px solid #f6f6f6\n}\n\n.dingflow-design .auto-judge .sort-right {\n    right: 0;\n    border-left: 1px solid #f6f6f6\n}\n\n.dingflow-design .auto-judge:hover .sort-left,\n.dingflow-design .auto-judge:hover .sort-right {\n    display: flex;\n    align-items: center\n}\n\n.dingflow-design .auto-judge .sort-left:hover,\n.dingflow-design .auto-judge .sort-right:hover {\n    background: #efefef\n}\n\n.dingflow-design .end-node {\n    border-radius: 50%;\n    font-size: 14px;\n    color: rgba(25, 31, 37, .4);\n    text-align: left\n}\n\n.dingflow-design .end-node .end-node-circle {\n    width: 10px;\n    height: 10px;\n    margin: auto;\n    border-radius: 50%;\n    background: #dbdcdc\n}\n\n.dingflow-design .end-node .end-node-text {\n    margin-top: 5px;\n    text-align: center\n}\n\n.approval-setting {\n    border-radius: 2px;\n    margin: 20px 0;\n    position: relative;\n    background: #fff\n}\n\n.ant-btn {\n    position: relative\n}\n\n\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Sticky/index.ts",
    "content": "import Sticky from './src/Sticky.vue'\n\nexport { Sticky }\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Sticky/src/Sticky.vue",
    "content": "<script lang=\"ts\" setup>\nimport { propTypes } from '@/utils/propTypes'\nimport { isClient, useEventListener, useWindowSize } from '@vueuse/core'\nimport type { CSSProperties } from 'vue'\n\ndefineOptions({ name: 'Sticky' })\n\nconst props = defineProps({\n  // 距离顶部或者底部的距离(单位px)\n  offset: propTypes.number.def(0),\n  // 设置元素的堆叠顺序\n  zIndex: propTypes.number.def(999),\n  // 设置指定的class\n  className: propTypes.string.def(''),\n  // 定位方式，默认为(top)，表示距离顶部位置，可以设置为top或者bottom\n  position: {\n    type: String,\n    validator: function (value: string) {\n      return ['top', 'bottom'].indexOf(value) !== -1\n    },\n    default: 'top'\n  }\n})\nconst width = ref('auto' as string)\nconst height = ref('auto' as string)\nconst isSticky = ref(false)\nconst refSticky = shallowRef<HTMLElement>()\nconst scrollContainer = shallowRef<HTMLElement | Window>()\nconst { height: windowHeight } = useWindowSize()\nonMounted(() => {\n  height.value = refSticky.value?.getBoundingClientRect().height + 'px'\n\n  scrollContainer.value = getScrollContainer(refSticky.value!, true)\n  useEventListener(scrollContainer, 'scroll', handleScroll)\n  useEventListener('resize', handleResize)\n  handleScroll()\n})\nonActivated(() => {\n  handleScroll()\n})\n\nconst camelize = (str: string): string => {\n  return str.replace(/-(\\w)/g, (_, c) => (c ? c.toUpperCase() : ''))\n}\n\nconst getStyle = (element: HTMLElement, styleName: keyof CSSProperties): string => {\n  if (!isClient || !element || !styleName) return ''\n\n  let key = camelize(styleName)\n  if (key === 'float') key = 'cssFloat'\n  try {\n    const style = element.style[styleName]\n    if (style) return style\n    const computed = document.defaultView?.getComputedStyle(element, '')\n    return computed ? computed[styleName] : ''\n  } catch {\n    return element.style[styleName]\n  }\n}\nconst isScroll = (el: HTMLElement, isVertical?: boolean): boolean => {\n  if (!isClient) return false\n  const key = (\n    {\n      undefined: 'overflow',\n      true: 'overflow-y',\n      false: 'overflow-x'\n    } as const\n  )[String(isVertical)]!\n  const overflow = getStyle(el, key)\n  return ['scroll', 'auto', 'overlay'].some((s) => overflow.includes(s))\n}\n\nconst getScrollContainer = (\n  el: HTMLElement,\n  isVertical: boolean\n): Window | HTMLElement | undefined => {\n  if (!isClient) return\n  let parent = el\n  while (parent) {\n    if ([window, document, document.documentElement].includes(parent)) return window\n    if (isScroll(parent, isVertical)) return parent\n    parent = parent.parentNode as HTMLElement\n  }\n  return parent\n}\n\nconst handleScroll = () => {\n  width.value = refSticky.value!.getBoundingClientRect().width! + 'px'\n  if (props.position === 'top') {\n    const offsetTop = refSticky.value?.getBoundingClientRect().top\n    if (offsetTop !== undefined && offsetTop < props.offset) {\n      sticky()\n      return\n    }\n    reset()\n  } else {\n    const offsetBottom = refSticky.value?.getBoundingClientRect().bottom\n\n    if (offsetBottom !== undefined && offsetBottom > windowHeight.value - props.offset) {\n      sticky()\n      return\n    }\n    reset()\n  }\n}\nconst handleResize = () => {\n  if (isSticky.value && refSticky.value) {\n    width.value = refSticky.value.getBoundingClientRect().width + 'px'\n  }\n}\nconst sticky = () => {\n  if (isSticky.value) {\n    return\n  }\n  isSticky.value = true\n}\nconst reset = () => {\n  if (!isSticky.value) {\n    return\n  }\n  width.value = 'auto'\n  isSticky.value = false\n}\n</script>\n<template>\n  <div ref=\"refSticky\" :style=\"{ height: height, zIndex: zIndex }\">\n    <div\n      :class=\"className\"\n      :style=\"{\n        top: position === 'top' ? offset + 'px' : '',\n        bottom: position !== 'top' ? offset + 'px' : '',\n        zIndex: zIndex,\n        position: isSticky ? 'fixed' : 'static',\n        width: width,\n        height: height\n      }\"\n    >\n      <slot>\n        <div>sticky</div>\n      </slot>\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/SummaryCard/index.vue",
    "content": "<template>\n  <div class=\"flex flex-row items-center gap-3 rounded bg-[var(--el-bg-color-overlay)] p-4\">\n    <div\n      class=\"h-12 w-12 flex flex-shrink-0 items-center justify-center rounded-1\"\n      :class=\"`${iconColor} ${iconBgColor}`\"\n    >\n      <Icon :icon=\"icon\" class=\"!text-6\" />\n    </div>\n    <div class=\"flex flex-col gap-1\">\n      <div class=\"flex items-center gap-1 text-gray-500\">\n        <span class=\"text-3.5\">{{ title }}</span>\n        <el-tooltip :content=\"tooltip\" placement=\"top-start\" v-if=\"tooltip\">\n          <Icon icon=\"ep:warning\" class=\"item-center flex !text-3\" />\n        </el-tooltip>\n      </div>\n      <div class=\"flex flex-row items-baseline gap-2\">\n        <div class=\"text-7\">\n          <CountTo :prefix=\"prefix\" :end-val=\"value\" :decimals=\"decimals\" />\n        </div>\n        <span\n          v-if=\"percent != undefined\"\n          :class=\"toNumber(percent) > 0 ? 'text-red-500' : 'text-green-500'\"\n        >\n          <span class=\"text-sm\">{{ Math.abs(toNumber(percent)) }}%</span>\n          <Icon\n            :icon=\"toNumber(percent) > 0 ? 'ep:caret-top' : 'ep:caret-bottom'\"\n            class=\"ml-0.5 !text-3\"\n          />\n        </span>\n      </div>\n    </div>\n  </div>\n</template>\n<script lang=\"ts\" setup>\nimport { propTypes } from '@/utils/propTypes'\nimport { toNumber } from 'lodash-es'\n\n/** 统计卡片 */\ndefineOptions({ name: 'SummaryCard' })\n\ndefineProps({\n  title: propTypes.string.def(''),\n  tooltip: propTypes.string.def(''),\n  icon: propTypes.string.def(''),\n  iconColor: propTypes.string.def(''),\n  iconBgColor: propTypes.string.def(''),\n  prefix: propTypes.string.def(''),\n  value: propTypes.number.def(0),\n  decimals: propTypes.number.def(0),\n  percent: propTypes.oneOfType([Number, String]).def(undefined)\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Table/index.ts",
    "content": "import Table from './src/Table.vue'\nimport { ElTable } from 'element-plus'\nimport { TableSetPropsType } from '@/types/table'\nimport TableSelectForm from './src/TableSelectForm.vue'\n\nexport interface TableExpose {\n  setProps: (props: Recordable) => void\n  setColumn: (columnProps: TableSetPropsType[]) => void\n  selections: Recordable[]\n  elTableRef: ComponentRef<typeof ElTable>\n}\n\nexport { Table, TableSelectForm }\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Table/src/Table.vue",
    "content": "<script lang=\"tsx\">\nimport { ElTable, ElTableColumn, ElPagination } from 'element-plus'\nimport { defineComponent, PropType, ref, computed, unref, watch, onMounted } from 'vue'\nimport { propTypes } from '@/utils/propTypes'\nimport { setIndex } from './helper'\nimport { getSlot } from '@/utils/tsxHelper'\nimport type { TableProps } from './types'\nimport { set } from 'lodash-es'\nimport { Pagination, TableColumn, TableSetPropsType, TableSlotDefault } from '@/types/table'\n\nexport default defineComponent({\n  // eslint-disable-next-line vue/no-reserved-component-names\n  name: 'Table',\n  props: {\n    pageSize: propTypes.number.def(10),\n    currentPage: propTypes.number.def(1),\n    // 是否多选\n    selection: propTypes.bool.def(false),\n    // 是否所有的超出隐藏，优先级低于schema中的showOverflowTooltip,\n    showOverflowTooltip: propTypes.bool.def(true),\n    // 表头\n    columns: {\n      type: Array as PropType<TableColumn[]>,\n      default: () => []\n    },\n    // 展开行\n    expand: propTypes.bool.def(false),\n    // 是否展示分页\n    pagination: {\n      type: Object as PropType<Pagination>,\n      default: (): Pagination | undefined => undefined\n    },\n    // 仅对 type=selection 的列有效，类型为 Boolean，为 true 则会在数据更新之后保留之前选中的数据（需指定 row-key）\n    reserveSelection: propTypes.bool.def(false),\n    // 加载状态\n    loading: propTypes.bool.def(false),\n    // 是否叠加索引\n    reserveIndex: propTypes.bool.def(false),\n    // 对齐方式\n    align: propTypes.string\n      .validate((v: string) => ['left', 'center', 'right'].includes(v))\n      .def('center'),\n    // 表头对齐方式\n    headerAlign: propTypes.string\n      .validate((v: string) => ['left', 'center', 'right'].includes(v))\n      .def('center'),\n    data: {\n      type: Array as PropType<Recordable[]>,\n      default: () => []\n    }\n  },\n  emits: ['update:pageSize', 'update:currentPage', 'register'],\n  setup(props, { attrs, slots, emit, expose }) {\n    const elTableRef = ref<ComponentRef<typeof ElTable>>()\n\n    // 注册\n    onMounted(() => {\n      const tableRef = unref(elTableRef)\n      emit('register', tableRef?.$parent, elTableRef)\n    })\n\n    const pageSizeRef = ref(props.pageSize)\n\n    const currentPageRef = ref(props.currentPage)\n\n    // useTable传入的props\n    const outsideProps = ref<TableProps>({})\n\n    const mergeProps = ref<TableProps>({})\n\n    const getProps = computed(() => {\n      const propsObj = { ...props }\n      Object.assign(propsObj, unref(mergeProps))\n      return propsObj\n    })\n\n    const setProps = (props: TableProps = {}) => {\n      mergeProps.value = Object.assign(unref(mergeProps), props)\n      outsideProps.value = props\n    }\n\n    const setColumn = (columnProps: TableSetPropsType[], columnsChildren?: TableColumn[]) => {\n      const { columns } = unref(getProps)\n      for (const v of columnsChildren || columns) {\n        for (const item of columnProps) {\n          if (v.field === item.field) {\n            set(v, item.path, item.value)\n          } else if (v.children?.length) {\n            setColumn(columnProps, v.children)\n          }\n        }\n      }\n    }\n\n    const selections = ref<Recordable[]>([])\n\n    const selectionChange = (selection: Recordable[]) => {\n      selections.value = selection\n    }\n\n    expose({\n      setProps,\n      setColumn,\n      selections\n    })\n\n    const pagination = computed(() => {\n      // update by 芋艿：保持和 Pagination 组件的逻辑一致\n      return Object.assign(\n        {\n          small: false,\n          background: true,\n          pagerCount: document.body.clientWidth < 992 ? 5 : 7,\n          layout: 'total, sizes, prev, pager, next, jumper',\n          pageSizes: [10, 20, 30, 50, 100],\n          disabled: false,\n          hideOnSinglePage: false,\n          total: 10\n        },\n        unref(getProps).pagination\n      )\n    })\n\n    watch(\n      () => unref(getProps).pageSize,\n      (val: number) => {\n        pageSizeRef.value = val\n      }\n    )\n\n    watch(\n      () => unref(getProps).currentPage,\n      (val: number) => {\n        currentPageRef.value = val\n      }\n    )\n\n    watch(\n      () => pageSizeRef.value,\n      (val: number) => {\n        emit('update:pageSize', val)\n      }\n    )\n\n    watch(\n      () => currentPageRef.value,\n      (val: number) => {\n        emit('update:currentPage', val)\n      }\n    )\n\n    const getBindValue = computed(() => {\n      const bindValue: Recordable = { ...attrs, ...props }\n      delete bindValue.columns\n      delete bindValue.data\n      return bindValue\n    })\n\n    const renderTableSelection = () => {\n      const { selection, reserveSelection, align, headerAlign } = unref(getProps)\n      // 渲染多选\n      return selection ? (\n        <ElTableColumn\n          type=\"selection\"\n          reserveSelection={reserveSelection}\n          align={align}\n          headerAlign={headerAlign}\n          width=\"50\"\n        ></ElTableColumn>\n      ) : undefined\n    }\n\n    const renderTableExpand = () => {\n      const { align, headerAlign, expand } = unref(getProps)\n      // 渲染展开行\n      return expand ? (\n        <ElTableColumn type=\"expand\" align={align} headerAlign={headerAlign}>\n          {{\n            // @ts-ignore\n            default: (data: TableSlotDefault) => getSlot(slots, 'expand', data)\n          }}\n        </ElTableColumn>\n      ) : undefined\n    }\n\n    const rnderTreeTableColumn = (columnsChildren: TableColumn[]) => {\n      const { align, headerAlign, showOverflowTooltip } = unref(getProps)\n      return columnsChildren.map((v) => {\n        const props = { ...v }\n        if (props.children) delete props.children\n        return (\n          <ElTableColumn\n            showOverflowTooltip={showOverflowTooltip}\n            align={align}\n            headerAlign={headerAlign}\n            {...props}\n            prop={v.field}\n          >\n            {{\n              default: (data: TableSlotDefault) =>\n                v.children && v.children.length\n                  ? rnderTableColumn(v.children)\n                  : // @ts-ignore\n                    getSlot(slots, v.field, data) ||\n                    v?.formatter?.(data.row, data.column, data.row[v.field], data.$index) ||\n                    data.row[v.field],\n              // @ts-ignore\n              header: getSlot(slots, `${v.field}-header`)\n            }}\n          </ElTableColumn>\n        )\n      })\n    }\n\n    const rnderTableColumn = (columnsChildren?: TableColumn[]) => {\n      const {\n        columns,\n        reserveIndex,\n        pageSize,\n        currentPage,\n        align,\n        headerAlign,\n        showOverflowTooltip\n      } = unref(getProps)\n      return [...[renderTableExpand()], ...[renderTableSelection()]].concat(\n        (columnsChildren || columns).map((v) => {\n          // 自定生成序号\n          if (v.type === 'index') {\n            return (\n              <ElTableColumn\n                type=\"index\"\n                index={\n                  v.index\n                    ? v.index\n                    : (index) => setIndex(reserveIndex, index, pageSize, currentPage)\n                }\n                align={v.align || align}\n                headerAlign={v.headerAlign || headerAlign}\n                label={v.label}\n                width=\"65px\"\n              ></ElTableColumn>\n            )\n          } else {\n            const props = { ...v }\n            if (props.children) delete props.children\n            return (\n              <ElTableColumn\n                showOverflowTooltip={showOverflowTooltip}\n                align={align}\n                headerAlign={headerAlign}\n                {...props}\n                prop={v.field}\n              >\n                {{\n                  default: (data: TableSlotDefault) =>\n                    v.children && v.children.length\n                      ? rnderTreeTableColumn(v.children)\n                      : // @ts-ignore\n                        getSlot(slots, v.field, data) ||\n                        v?.formatter?.(data.row, data.column, data.row[v.field], data.$index) ||\n                        data.row[v.field],\n                  // @ts-ignore\n                  header: () => getSlot(slots, `${v.field}-header`) || v.label\n                }}\n              </ElTableColumn>\n            )\n          }\n        })\n      )\n    }\n\n    return () => (\n      <div v-loading={unref(getProps).loading}>\n        <ElTable\n          // @ts-ignore\n          ref={elTableRef}\n          data={unref(getProps).data}\n          onSelection-change={selectionChange}\n          {...unref(getBindValue)}\n        >\n          {{\n            default: () => rnderTableColumn(),\n            // @ts-ignore\n            append: () => getSlot(slots, 'append')\n          }}\n        </ElTable>\n        {unref(getProps).pagination ? (\n          // update by 芋艿：保持和 Pagination 组件一致\n          <ElPagination\n            v-model:pageSize={pageSizeRef.value}\n            v-model:currentPage={currentPageRef.value}\n            class=\"float-right mb-15px mt-15px\"\n            {...unref(pagination)}\n          ></ElPagination>\n        ) : undefined}\n      </div>\n    )\n  }\n})\n</script>\n<style lang=\"scss\" scoped>\n:deep(.el-button.is-text) {\n  padding: 8px 4px;\n  margin-left: 0;\n}\n\n:deep(.el-button.is-link) {\n  padding: 8px 4px;\n  margin-left: 0;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Table/src/TableSelectForm.vue",
    "content": "<!-- 列表选择通用组件，参考 ProductList 组件使用 -->\n<template>\n  <Dialog v-model=\"dialogVisible\" :appendToBody=\"true\" :scroll=\"true\" :title=\"title\" width=\"60%\">\n    <el-table\n      ref=\"multipleTableRef\"\n      v-loading=\"loading\"\n      :data=\"list\"\n      :show-overflow-tooltip=\"true\"\n      :stripe=\"true\"\n      @selection-change=\"handleSelectionChange\"\n    >\n      <el-table-column type=\"selection\" width=\"55\" />\n      <slot></slot>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      v-model:limit=\"queryParams.pageSize\"\n      v-model:page=\"queryParams.pageNo\"\n      :total=\"total\"\n      @pagination=\"getList\"\n    />\n    <template #footer>\n      <el-button :disabled=\"formLoading\" type=\"primary\" @click=\"submitForm\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n\n<script lang=\"ts\" setup>\nimport { ElTable } from 'element-plus'\n\ndefineOptions({ name: 'TableSelectForm' })\nwithDefaults(\n  defineProps<{\n    modelValue: any[]\n    title: string\n  }>(),\n  { modelValue: () => [], title: '选择' }\n)\nconst list = ref([]) // 列表的数据\nconst total = ref(0) // 列表的总页数\nconst loading = ref(false) // 列表的加载中\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst formLoading = ref(false)\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10\n})\n// 确认选择时的触发事件\nconst emits = defineEmits<{\n  (e: 'update:modelValue', v: number[]): void\n}>()\nconst multipleTableRef = ref<InstanceType<typeof ElTable>>()\nconst multipleSelection = ref<any[]>([])\nconst handleSelectionChange = (val: any[]) => {\n  multipleSelection.value = val\n}\n/** 触发 */\nconst submitForm = () => {\n  formLoading.value = true\n  try {\n    emits('update:modelValue', multipleSelection.value) // 返回选择的原始数据由使用方处理\n  } finally {\n    formLoading.value = false\n    // 关闭弹窗\n    dialogVisible.value = false\n  }\n}\n\nconst getList = async (getListFunc: Function) => {\n  loading.value = true\n  try {\n    const data = await getListFunc(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 打开弹窗 */\nconst open = async (getListFunc: Function) => {\n  dialogVisible.value = true\n  await nextTick()\n  if (multipleSelection.value.length > 0) {\n    multipleTableRef.value!.clearSelection()\n  }\n  await getList(getListFunc)\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Table/src/helper.ts",
    "content": "export const setIndex = (reserveIndex: boolean, index: number, size: number, current: number) => {\n  const newIndex = index + 1\n  if (reserveIndex) {\n    return size * (current - 1) + newIndex\n  } else {\n    return newIndex\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Table/src/types.ts",
    "content": "import { Pagination, TableColumn } from '@/types/table'\n\nexport type TableProps = {\n  pageSize?: number\n  currentPage?: number\n  // 是否多选\n  selection?: boolean\n  // 是否所有的超出隐藏，优先级低于schema中的showOverflowTooltip,\n  showOverflowTooltip?: boolean\n  // 表头\n  columns?: TableColumn[]\n  // 是否展示分页\n  pagination?: Pagination | undefined\n  // 仅对 type=selection 的列有效，类型为 Boolean，为 true 则会在数据更新之后保留之前选中的数据（需指定 row-key）\n  reserveSelection?: boolean\n  // 加载状态\n  loading?: boolean\n  // 是否叠加索引\n  reserveIndex?: boolean\n  // 对齐方式\n  align?: 'left' | 'center' | 'right'\n  // 表头对齐方式\n  headerAlign?: 'left' | 'center' | 'right'\n  data?: Recordable\n  expand?: boolean\n} & Recordable\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Tooltip/index.ts",
    "content": "import Tooltip from './src/Tooltip.vue'\n\nexport { Tooltip }\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Tooltip/src/Tooltip.vue",
    "content": "<script lang=\"ts\" setup>\nimport { propTypes } from '@/utils/propTypes'\n\ndefineOptions({ name: 'Tooltip' })\n\ndefineProps({\n  title: propTypes.string.def(''),\n  message: propTypes.string.def(''),\n  icon: propTypes.string.def('ep:question-filled')\n})\n</script>\n<template>\n  <span>{{ title }}</span>\n  <ElTooltip :content=\"message\" placement=\"top\">\n    <Icon :icon=\"icon\" class=\"relative top-1px ml-1px\" />\n  </ElTooltip>\n</template>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/UploadFile/index.ts",
    "content": "import UploadImg from './src/UploadImg.vue'\nimport UploadImgs from './src/UploadImgs.vue'\nimport UploadFile from './src/UploadFile.vue'\n\nexport { UploadImg, UploadImgs, UploadFile }\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/UploadFile/src/UploadFile.vue",
    "content": "<template>\n  <div class=\"upload-file\">\n    <el-upload\n      ref=\"uploadRef\"\n      v-model:file-list=\"fileList\"\n      :action=\"uploadUrl\"\n      :auto-upload=\"autoUpload\"\n      :before-upload=\"beforeUpload\"\n      :disabled=\"disabled\"\n      :drag=\"drag\"\n      :http-request=\"httpRequest\"\n      :limit=\"props.limit\"\n      :multiple=\"props.limit > 1\"\n      :on-error=\"excelUploadError\"\n      :on-exceed=\"handleExceed\"\n      :on-preview=\"handlePreview\"\n      :on-remove=\"handleRemove\"\n      :on-success=\"handleFileSuccess\"\n      :show-file-list=\"true\"\n      class=\"upload-file-uploader\"\n      name=\"file\"\n    >\n      <el-button v-if=\"!disabled\" type=\"primary\">\n        <Icon icon=\"ep:upload-filled\" />\n        选取文件\n      </el-button>\n      <template v-if=\"isShowTip && !disabled\" #tip>\n        <div style=\"font-size: 8px\">\n          大小不超过 <b style=\"color: #f56c6c\">{{ fileSize }}MB</b>\n        </div>\n        <div style=\"font-size: 8px\">\n          格式为 <b style=\"color: #f56c6c\">{{ fileType.join('/') }}</b> 的文件\n        </div>\n      </template>\n      <template #file=\"row\">\n        <div class=\"flex items-center\">\n          <span>{{ row.file.name }}</span>\n          <div class=\"ml-10px\">\n            <el-link\n              :href=\"row.file.url\"\n              :underline=\"false\"\n              download\n              target=\"_blank\"\n              type=\"primary\"\n            >\n              下载\n            </el-link>\n          </div>\n          <div class=\"ml-10px\">\n            <el-button link type=\"danger\" @click=\"handleRemove(row.file)\"> 删除</el-button>\n          </div>\n        </div>\n      </template>\n    </el-upload>\n  </div>\n</template>\n<script lang=\"ts\" setup>\nimport { propTypes } from '@/utils/propTypes'\nimport type { UploadInstance, UploadProps, UploadRawFile, UploadUserFile } from 'element-plus'\nimport { isString } from '@/utils/is'\nimport { useUpload } from '@/components/UploadFile/src/useUpload'\nimport { UploadFile } from 'element-plus/es/components/upload/src/upload'\n\ndefineOptions({ name: 'UploadFile' })\n\nconst message = useMessage() // 消息弹窗\nconst emit = defineEmits(['update:modelValue'])\n\nconst props = defineProps({\n  modelValue: propTypes.oneOfType<string | string[]>([String, Array<String>]).isRequired,\n  fileType: propTypes.array.def(['doc', 'xls', 'ppt', 'txt', 'pdf']), // 文件类型, 例如['png', 'jpg', 'jpeg']\n  fileSize: propTypes.number.def(5), // 大小限制(MB)\n  limit: propTypes.number.def(5), // 数量限制\n  autoUpload: propTypes.bool.def(true), // 自动上传\n  drag: propTypes.bool.def(false), // 拖拽上传\n  isShowTip: propTypes.bool.def(true), // 是否显示提示\n  disabled: propTypes.bool.def(false) // 是否禁用上传组件 ==> 非必传（默认为 false）\n})\n\n// ========== 上传相关 ==========\nconst uploadRef = ref<UploadInstance>()\nconst uploadList = ref<UploadUserFile[]>([])\nconst fileList = ref<UploadUserFile[]>([])\nconst uploadNumber = ref<number>(0)\n\nconst { uploadUrl, httpRequest } = useUpload()\n\n// 文件上传之前判断\nconst beforeUpload: UploadProps['beforeUpload'] = (file: UploadRawFile) => {\n  if (fileList.value.length >= props.limit) {\n    message.error(`上传文件数量不能超过${props.limit}个!`)\n    return false\n  }\n  let fileExtension = ''\n  if (file.name.lastIndexOf('.') > -1) {\n    fileExtension = file.name.slice(file.name.lastIndexOf('.') + 1)\n  }\n  const isImg = props.fileType.some((type: string) => {\n    if (file.type.indexOf(type) > -1) return true\n    return !!(fileExtension && fileExtension.indexOf(type) > -1)\n  })\n  const isLimit = file.size < props.fileSize * 1024 * 1024\n  if (!isImg) {\n    message.error(`文件格式不正确, 请上传${props.fileType.join('/')}格式!`)\n    return false\n  }\n  if (!isLimit) {\n    message.error(`上传文件大小不能超过${props.fileSize}MB!`)\n    return false\n  }\n  message.success('正在上传文件，请稍候...')\n  uploadNumber.value++\n}\n// 处理上传的文件发生变化\n// const handleFileChange = (uploadFile: UploadFile): void => {\n//   uploadRef.value.data.path = uploadFile.name\n// }\n// 文件上传成功\nconst handleFileSuccess: UploadProps['onSuccess'] = (res: any): void => {\n  message.success('上传成功')\n  // 删除自身\n  const index = fileList.value.findIndex((item) => item.response?.data === res.data)\n  fileList.value.splice(index, 1)\n  uploadList.value.push({ name: res.data, url: res.data })\n  if (uploadList.value.length == uploadNumber.value) {\n    fileList.value.push(...uploadList.value)\n    uploadList.value = []\n    uploadNumber.value = 0\n    emitUpdateModelValue()\n  }\n}\n// 文件数超出提示\nconst handleExceed: UploadProps['onExceed'] = (): void => {\n  message.error(`上传文件数量不能超过${props.limit}个!`)\n}\n// 上传错误提示\nconst excelUploadError: UploadProps['onError'] = (): void => {\n  message.error('导入数据失败，请您重新上传！')\n}\n// 删除上传文件\nconst handleRemove = (file: UploadFile) => {\n  const index = fileList.value.map((f) => f.name).indexOf(file.name)\n  if (index > -1) {\n    fileList.value.splice(index, 1)\n    emitUpdateModelValue()\n  }\n}\nconst handlePreview: UploadProps['onPreview'] = (uploadFile) => {\n  console.log(uploadFile)\n}\n\n// 监听模型绑定值变动\nwatch(\n  () => props.modelValue,\n  (val: string | string[]) => {\n    if (!val) {\n      fileList.value = [] // fix：处理掉缓存，表单重置后上传组件的内容并没有重置\n      return\n    }\n\n    fileList.value = [] // 保障数据为空\n    // 情况1：字符串\n    if (isString(val)) {\n      fileList.value.push(\n        ...val.split(',').map((url) => ({ name: url.substring(url.lastIndexOf('/') + 1), url }))\n      )\n      return\n    }\n    // 情况2：数组\n    fileList.value.push(\n      ...(val as string[]).map((url) => ({ name: url.substring(url.lastIndexOf('/') + 1), url }))\n    )\n  },\n  { immediate: true, deep: true }\n)\n// 发送文件链接列表更新\nconst emitUpdateModelValue = () => {\n  // 情况1：数组结果\n  let result: string | string[] = fileList.value.map((file) => file.url!)\n  // 情况2：逗号分隔的字符串\n  if (props.limit === 1 || isString(props.modelValue)) {\n    result = result.join(',')\n  }\n  emit('update:modelValue', result)\n}\n</script>\n<style lang=\"scss\" scoped>\n.upload-file-uploader {\n  margin-bottom: 5px;\n}\n\n:deep(.upload-file-list .el-upload-list__item) {\n  position: relative;\n  margin-bottom: 10px;\n  line-height: 2;\n  border: 1px solid #e4e7ed;\n}\n\n:deep(.el-upload-list__item-file-name) {\n  max-width: 250px;\n}\n\n:deep(.upload-file-list .ele-upload-list__item-content) {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  color: inherit;\n}\n\n:deep(.ele-upload-list__item-content-action .el-link) {\n  margin-right: 10px;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/UploadFile/src/UploadImg.vue",
    "content": "<template>\n  <div class=\"upload-box\">\n    <el-upload\n      :id=\"uuid\"\n      :accept=\"fileType.join(',')\"\n      :action=\"uploadUrl\"\n      :before-upload=\"beforeUpload\"\n      :class=\"['upload', drag ? 'no-border' : '']\"\n      :disabled=\"disabled\"\n      :drag=\"drag\"\n      :http-request=\"httpRequest\"\n      :multiple=\"false\"\n      :on-error=\"uploadError\"\n      :on-success=\"uploadSuccess\"\n      :show-file-list=\"false\"\n    >\n      <template v-if=\"modelValue\">\n        <img :src=\"modelValue\" class=\"upload-image\" />\n        <div class=\"upload-handle\" @click.stop>\n          <div v-if=\"!disabled\" class=\"handle-icon\" @click=\"editImg\">\n            <Icon icon=\"ep:edit\" />\n            <span v-if=\"showBtnText\">{{ t('action.edit') }}</span>\n          </div>\n          <div class=\"handle-icon\" @click=\"imagePreview(modelValue)\">\n            <Icon icon=\"ep:zoom-in\" />\n            <span v-if=\"showBtnText\">{{ t('action.detail') }}</span>\n          </div>\n          <div v-if=\"showDelete && !disabled\" class=\"handle-icon\" @click=\"deleteImg\">\n            <Icon icon=\"ep:delete\" />\n            <span v-if=\"showBtnText\">{{ t('action.del') }}</span>\n          </div>\n        </div>\n      </template>\n      <template v-else>\n        <div class=\"upload-empty\">\n          <slot name=\"empty\">\n            <Icon icon=\"ep:plus\" />\n            <!-- <span>请上传图片</span> -->\n          </slot>\n        </div>\n      </template>\n    </el-upload>\n    <div class=\"el-upload__tip\">\n      <slot name=\"tip\"></slot>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport type { UploadProps } from 'element-plus'\n\nimport { generateUUID } from '@/utils'\nimport { propTypes } from '@/utils/propTypes'\nimport { createImageViewer } from '@/components/ImageViewer'\nimport { useUpload } from '@/components/UploadFile/src/useUpload'\n\ndefineOptions({ name: 'UploadImg' })\n\ntype FileTypes =\n  | 'image/apng'\n  | 'image/bmp'\n  | 'image/gif'\n  | 'image/jpeg'\n  | 'image/pjpeg'\n  | 'image/png'\n  | 'image/svg+xml'\n  | 'image/tiff'\n  | 'image/webp'\n  | 'image/x-icon'\n\n// 接受父组件参数\nconst props = defineProps({\n  modelValue: propTypes.string.def(''),\n  drag: propTypes.bool.def(true), // 是否支持拖拽上传 ==> 非必传（默认为 true）\n  disabled: propTypes.bool.def(false), // 是否禁用上传组件 ==> 非必传（默认为 false）\n  fileSize: propTypes.number.def(5), // 图片大小限制 ==> 非必传（默认为 5M）\n  fileType: propTypes.array.def(['image/jpeg', 'image/png', 'image/gif']), // 图片类型限制 ==> 非必传（默认为 [\"image/jpeg\", \"image/png\", \"image/gif\"]）\n  height: propTypes.string.def('150px'), // 组件高度 ==> 非必传（默认为 150px）\n  width: propTypes.string.def('150px'), // 组件宽度 ==> 非必传（默认为 150px）\n  borderradius: propTypes.string.def('8px'), // 组件边框圆角 ==> 非必传（默认为 8px）\n  showDelete: propTypes.bool.def(true), // 是否显示删除按钮\n  showBtnText: propTypes.bool.def(true) // 是否显示按钮文字\n})\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n// 生成组件唯一id\nconst uuid = ref('id-' + generateUUID())\n// 查看图片\nconst imagePreview = (imgUrl: string) => {\n  createImageViewer({\n    zIndex: 9999999,\n    urlList: [imgUrl]\n  })\n}\n\nconst emit = defineEmits(['update:modelValue'])\n\nconst deleteImg = () => {\n  emit('update:modelValue', '')\n}\n\nconst { uploadUrl, httpRequest } = useUpload()\n\nconst editImg = () => {\n  const dom = document.querySelector(`#${uuid.value} .el-upload__input`)\n  dom && dom.dispatchEvent(new MouseEvent('click'))\n}\n\nconst beforeUpload: UploadProps['beforeUpload'] = (rawFile) => {\n  const imgSize = rawFile.size / 1024 / 1024 < props.fileSize\n  const imgType = props.fileType\n  if (!imgType.includes(rawFile.type as FileTypes))\n    message.notifyWarning('上传图片不符合所需的格式！')\n  if (!imgSize) message.notifyWarning(`上传图片大小不能超过 ${props.fileSize}M！`)\n  return imgType.includes(rawFile.type as FileTypes) && imgSize\n}\n\n// 图片上传成功提示\nconst uploadSuccess: UploadProps['onSuccess'] = (res: any): void => {\n  message.success('上传成功')\n  emit('update:modelValue', res.data)\n}\n\n// 图片上传错误提示\nconst uploadError = () => {\n  message.notifyError('图片上传失败，请您重新上传！')\n}\n</script>\n<style lang=\"scss\" scoped>\n.is-error {\n  .upload {\n    :deep(.el-upload),\n    :deep(.el-upload-dragger) {\n      border: 1px dashed var(--el-color-danger) !important;\n\n      &:hover {\n        border-color: var(--el-color-primary) !important;\n      }\n    }\n  }\n}\n\n:deep(.disabled) {\n  .el-upload,\n  .el-upload-dragger {\n    cursor: not-allowed !important;\n    background: var(--el-disabled-bg-color);\n    border: 1px dashed var(--el-border-color-darker) !important;\n\n    &:hover {\n      border: 1px dashed var(--el-border-color-darker) !important;\n    }\n  }\n}\n\n.upload-box {\n  .no-border {\n    :deep(.el-upload) {\n      border: none !important;\n    }\n  }\n\n  :deep(.upload) {\n    .el-upload {\n      position: relative;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      width: v-bind(width);\n      height: v-bind(height);\n      overflow: hidden;\n      border: 1px dashed var(--el-border-color-darker);\n      border-radius: v-bind(borderradius);\n      transition: var(--el-transition-duration-fast);\n\n      &:hover {\n        border-color: var(--el-color-primary);\n\n        .upload-handle {\n          opacity: 1;\n        }\n      }\n\n      .el-upload-dragger {\n        display: flex;\n        align-items: center;\n        justify-content: center;\n        width: 100%;\n        height: 100%;\n        padding: 0;\n        overflow: hidden;\n        background-color: transparent;\n        border: 1px dashed var(--el-border-color-darker);\n        border-radius: v-bind(borderradius);\n\n        &:hover {\n          border: 1px dashed var(--el-color-primary);\n        }\n      }\n\n      .el-upload-dragger.is-dragover {\n        background-color: var(--el-color-primary-light-9);\n        border: 2px dashed var(--el-color-primary) !important;\n      }\n\n      .upload-image {\n        width: 100%;\n        height: 100%;\n        object-fit: contain;\n      }\n\n      .upload-empty {\n        position: relative;\n        display: flex;\n        flex-direction: column;\n        align-items: center;\n        justify-content: center;\n        font-size: 12px;\n        line-height: 30px;\n        color: var(--el-color-info);\n\n        .el-icon {\n          font-size: 28px;\n          color: var(--el-text-color-secondary);\n        }\n      }\n\n      .upload-handle {\n        position: absolute;\n        top: 0;\n        right: 0;\n        display: flex;\n        width: 100%;\n        height: 100%;\n        cursor: pointer;\n        background: rgb(0 0 0 / 60%);\n        opacity: 0;\n        box-sizing: border-box;\n        transition: var(--el-transition-duration-fast);\n        align-items: center;\n        justify-content: center;\n\n        .handle-icon {\n          display: flex;\n          flex-direction: column;\n          align-items: center;\n          justify-content: center;\n          padding: 0 6%;\n          color: aliceblue;\n\n          .el-icon {\n            margin-bottom: 40%;\n            font-size: 130%;\n            line-height: 130%;\n          }\n\n          span {\n            font-size: 85%;\n            line-height: 85%;\n          }\n        }\n      }\n    }\n  }\n\n  .el-upload__tip {\n    line-height: 18px;\n    text-align: center;\n  }\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/UploadFile/src/UploadImgs.vue",
    "content": "<template>\n  <div class=\"upload-box\">\n    <el-upload\n      v-model:file-list=\"fileList\"\n      :accept=\"fileType.join(',')\"\n      :action=\"uploadUrl\"\n      :before-upload=\"beforeUpload\"\n      :class=\"['upload', drag ? 'no-border' : '']\"\n      :disabled=\"disabled\"\n      :drag=\"drag\"\n      :http-request=\"httpRequest\"\n      :limit=\"limit\"\n      :multiple=\"true\"\n      :on-error=\"uploadError\"\n      :on-exceed=\"handleExceed\"\n      :on-success=\"uploadSuccess\"\n      list-type=\"picture-card\"\n    >\n      <div class=\"upload-empty\">\n        <slot name=\"empty\">\n          <Icon icon=\"ep:plus\" />\n          <!-- <span>请上传图片</span> -->\n        </slot>\n      </div>\n      <template #file=\"{ file }\">\n        <img :src=\"file.url\" class=\"upload-image\" />\n        <div class=\"upload-handle\" @click.stop>\n          <div class=\"handle-icon\" @click=\"handlePictureCardPreview(file)\">\n            <Icon icon=\"ep:zoom-in\" />\n            <span>查看</span>\n          </div>\n          <div v-if=\"!disabled\" class=\"handle-icon\" @click=\"handleRemove(file)\">\n            <Icon icon=\"ep:delete\" />\n            <span>删除</span>\n          </div>\n        </div>\n      </template>\n    </el-upload>\n    <div class=\"el-upload__tip\">\n      <slot name=\"tip\"></slot>\n    </div>\n    <el-image-viewer\n      v-if=\"imgViewVisible\"\n      :url-list=\"[viewImageUrl]\"\n      @close=\"imgViewVisible = false\"\n    />\n  </div>\n</template>\n<script lang=\"ts\" setup>\nimport type { UploadFile, UploadProps, UploadUserFile } from 'element-plus'\nimport { ElNotification } from 'element-plus'\n\nimport { propTypes } from '@/utils/propTypes'\nimport { useUpload } from '@/components/UploadFile/src/useUpload'\n\ndefineOptions({ name: 'UploadImgs' })\n\nconst message = useMessage() // 消息弹窗\n\ntype FileTypes =\n  | 'image/apng'\n  | 'image/bmp'\n  | 'image/gif'\n  | 'image/jpeg'\n  | 'image/pjpeg'\n  | 'image/png'\n  | 'image/svg+xml'\n  | 'image/tiff'\n  | 'image/webp'\n  | 'image/x-icon'\n\nconst props = defineProps({\n  modelValue: propTypes.oneOfType<string | string[]>([String, Array<String>]).isRequired,\n  drag: propTypes.bool.def(true), // 是否支持拖拽上传 ==> 非必传（默认为 true）\n  disabled: propTypes.bool.def(false), // 是否禁用上传组件 ==> 非必传（默认为 false）\n  limit: propTypes.number.def(5), // 最大图片上传数 ==> 非必传（默认为 5张）\n  fileSize: propTypes.number.def(5), // 图片大小限制 ==> 非必传（默认为 5M）\n  fileType: propTypes.array.def(['image/jpeg', 'image/png', 'image/gif']), // 图片类型限制 ==> 非必传（默认为 [\"image/jpeg\", \"image/png\", \"image/gif\"]）\n  height: propTypes.string.def('150px'), // 组件高度 ==> 非必传（默认为 150px）\n  width: propTypes.string.def('150px'), // 组件宽度 ==> 非必传（默认为 150px）\n  borderradius: propTypes.string.def('8px') // 组件边框圆角 ==> 非必传（默认为 8px）\n})\n\nconst { uploadUrl, httpRequest } = useUpload()\n\nconst fileList = ref<UploadUserFile[]>([])\nconst uploadNumber = ref<number>(0)\nconst uploadList = ref<UploadUserFile[]>([])\n/**\n * @description 文件上传之前判断\n * @param rawFile 上传的文件\n * */\nconst beforeUpload: UploadProps['beforeUpload'] = (rawFile) => {\n  const imgSize = rawFile.size / 1024 / 1024 < props.fileSize\n  const imgType = props.fileType\n  if (!imgType.includes(rawFile.type as FileTypes))\n    ElNotification({\n      title: '温馨提示',\n      message: '上传图片不符合所需的格式！',\n      type: 'warning'\n    })\n  if (!imgSize)\n    ElNotification({\n      title: '温馨提示',\n      message: `上传图片大小不能超过 ${props.fileSize}M！`,\n      type: 'warning'\n    })\n  uploadNumber.value++\n  return imgType.includes(rawFile.type as FileTypes) && imgSize\n}\n\n// 图片上传成功\ninterface UploadEmits {\n  (e: 'update:modelValue', value: string[]): void\n}\n\nconst emit = defineEmits<UploadEmits>()\nconst uploadSuccess: UploadProps['onSuccess'] = (res: any): void => {\n  message.success('上传成功')\n  // 删除自身\n  const index = fileList.value.findIndex((item) => item.response?.data === res.data)\n  fileList.value.splice(index, 1)\n  uploadList.value.push({ name: res.data, url: res.data })\n  if (uploadList.value.length == uploadNumber.value) {\n    fileList.value.push(...uploadList.value)\n    uploadList.value = []\n    uploadNumber.value = 0\n    emitUpdateModelValue()\n  }\n}\n\n// 监听模型绑定值变动\nwatch(\n  () => props.modelValue,\n  (val: string | string[]) => {\n    if (!val) {\n      fileList.value = [] // fix：处理掉缓存，表单重置后上传组件的内容并没有重置\n      return\n    }\n\n    fileList.value = [] // 保障数据为空\n    fileList.value.push(\n      ...(val as string[]).map((url) => ({ name: url.substring(url.lastIndexOf('/') + 1), url }))\n    )\n  },\n  { immediate: true, deep: true }\n)\n// 发送图片链接列表更新\nconst emitUpdateModelValue = () => {\n  let result: string[] = fileList.value.map((file) => file.url!)\n  emit('update:modelValue', result)\n}\n// 删除图片\nconst handleRemove = (uploadFile: UploadFile) => {\n  fileList.value = fileList.value.filter(\n    (item) => item.url !== uploadFile.url || item.name !== uploadFile.name\n  )\n  emit(\n    'update:modelValue',\n    fileList.value.map((file) => file.url!)\n  )\n}\n\n// 图片上传错误提示\nconst uploadError = () => {\n  ElNotification({\n    title: '温馨提示',\n    message: '图片上传失败，请您重新上传！',\n    type: 'error'\n  })\n}\n\n// 文件数超出提示\nconst handleExceed = () => {\n  ElNotification({\n    title: '温馨提示',\n    message: `当前最多只能上传 ${props.limit} 张图片，请移除后上传！`,\n    type: 'warning'\n  })\n}\n\n// 图片预览\nconst viewImageUrl = ref('')\nconst imgViewVisible = ref(false)\nconst handlePictureCardPreview: UploadProps['onPreview'] = (uploadFile) => {\n  viewImageUrl.value = uploadFile.url!\n  imgViewVisible.value = true\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.is-error {\n  .upload {\n    :deep(.el-upload--picture-card),\n    :deep(.el-upload-dragger) {\n      border: 1px dashed var(--el-color-danger) !important;\n\n      &:hover {\n        border-color: var(--el-color-primary) !important;\n      }\n    }\n  }\n}\n\n:deep(.disabled) {\n  .el-upload--picture-card,\n  .el-upload-dragger {\n    cursor: not-allowed;\n    background: var(--el-disabled-bg-color) !important;\n    border: 1px dashed var(--el-border-color-darker);\n\n    &:hover {\n      border-color: var(--el-border-color-darker) !important;\n    }\n  }\n}\n\n.upload-box {\n  .no-border {\n    :deep(.el-upload--picture-card) {\n      border: none !important;\n    }\n  }\n\n  :deep(.upload) {\n    .el-upload-dragger {\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      width: 100%;\n      height: 100%;\n      padding: 0;\n      overflow: hidden;\n      border: 1px dashed var(--el-border-color-darker);\n      border-radius: v-bind(borderradius);\n\n      &:hover {\n        border: 1px dashed var(--el-color-primary);\n      }\n    }\n\n    .el-upload-dragger.is-dragover {\n      background-color: var(--el-color-primary-light-9);\n      border: 2px dashed var(--el-color-primary) !important;\n    }\n\n    .el-upload-list__item,\n    .el-upload--picture-card {\n      width: v-bind(width);\n      height: v-bind(height);\n      background-color: transparent;\n      border-radius: v-bind(borderradius);\n    }\n\n    .upload-image {\n      width: 100%;\n      height: 100%;\n      object-fit: contain;\n    }\n\n    .upload-handle {\n      position: absolute;\n      top: 0;\n      right: 0;\n      display: flex;\n      width: 100%;\n      height: 100%;\n      cursor: pointer;\n      background: rgb(0 0 0 / 60%);\n      opacity: 0;\n      box-sizing: border-box;\n      transition: var(--el-transition-duration-fast);\n      align-items: center;\n      justify-content: center;\n\n      .handle-icon {\n        display: flex;\n        flex-direction: column;\n        align-items: center;\n        justify-content: center;\n        padding: 0 6%;\n        color: aliceblue;\n\n        .el-icon {\n          margin-bottom: 15%;\n          font-size: 140%;\n        }\n\n        span {\n          font-size: 100%;\n        }\n      }\n    }\n\n    .el-upload-list__item {\n      &:hover {\n        .upload-handle {\n          opacity: 1;\n        }\n      }\n    }\n\n    .upload-empty {\n      display: flex;\n      flex-direction: column;\n      align-items: center;\n      font-size: 12px;\n      line-height: 30px;\n      color: var(--el-color-info);\n\n      .el-icon {\n        font-size: 28px;\n        color: var(--el-text-color-secondary);\n      }\n    }\n  }\n\n  .el-upload__tip {\n    line-height: 15px;\n    text-align: center;\n  }\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/UploadFile/src/useUpload.ts",
    "content": "import * as FileApi from '@/api/infra/file'\nimport CryptoJS from 'crypto-js'\nimport { UploadRawFile, UploadRequestOptions } from 'element-plus/es/components/upload/src/upload'\nimport axios from 'axios'\n\nexport const useUpload = () => {\n  // 后端上传地址\n  const uploadUrl = import.meta.env.VITE_UPLOAD_URL\n  // 是否使用前端直连上传\n  const isClientUpload = UPLOAD_TYPE.CLIENT === import.meta.env.VITE_UPLOAD_TYPE\n  // 重写ElUpload上传方法\n  const httpRequest = async (options: UploadRequestOptions) => {\n    // 模式一：前端上传\n    if (isClientUpload) {\n      // 1.1 生成文件名称\n      const fileName = await generateFileName(options.file)\n      // 1.2 获取文件预签名地址\n      const presignedInfo = await FileApi.getFilePresignedUrl(fileName)\n      // 1.3 上传文件（不能使用 ElUpload 的 ajaxUpload 方法的原因：其使用的是 FormData 上传，Minio 不支持）\n      return axios.put(presignedInfo.uploadUrl, options.file, {\n        headers: {\n          'Content-Type': options.file.type,\n        }\n      }).then(() => {\n        // 1.4. 记录文件信息到后端（异步）\n        createFile(presignedInfo, fileName, options.file)\n        // 通知成功，数据格式保持与后端上传的返回结果一致\n        return { data: presignedInfo.url }\n      })\n    } else {\n      // 模式二：后端上传\n      // 重写 el-upload httpRequest 文件上传成功会走成功的钩子，失败走失败的钩子\n      return new Promise((resolve, reject) => {\n        FileApi.updateFile({ file: options.file })\n          .then((res) => {\n            if (res.code === 0) {\n              resolve(res)\n            } else {\n              reject(res)\n            }\n          })\n          .catch((res) => {\n            reject(res)\n          })\n      })\n    }\n  }\n\n  return {\n    uploadUrl,\n    httpRequest\n  }\n}\n\n/**\n * 创建文件信息\n * @param vo 文件预签名信息\n * @param name 文件名称\n * @param file 文件\n */\nfunction createFile(vo: FileApi.FilePresignedUrlRespVO, name: string, file: UploadRawFile) {\n  const fileVo = {\n    configId: vo.configId,\n    url: vo.url,\n    path: name,\n    name: file.name,\n    type: file.type,\n    size: file.size\n  }\n  FileApi.createFile(fileVo)\n  return fileVo\n}\n\n/**\n * 生成文件名称（使用算法SHA256）\n * @param file 要上传的文件\n */\nasync function generateFileName(file: UploadRawFile) {\n  // 读取文件内容\n  const data = await file.arrayBuffer()\n  const wordArray = CryptoJS.lib.WordArray.create(data)\n  // 计算SHA256\n  const sha256 = CryptoJS.SHA256(wordArray).toString()\n  // 拼接后缀\n  const ext = file.name.substring(file.name.lastIndexOf('.'))\n  return `${sha256}${ext}`\n}\n\n/**\n * 上传类型\n */\nenum UPLOAD_TYPE {\n  // 客户端直接上传（只支持S3服务）\n  CLIENT = 'client',\n  // 客户端发送到后端上传\n  SERVER = 'server'\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Verifition/index.ts",
    "content": "import Verify from './src/Verify.vue'\n\nexport { Verify }\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Verifition/src/Verify/VerifyPoints.vue",
    "content": "<template>\n  <div style=\"position: relative\">\n    <div class=\"verify-img-out\">\n      <div\n        :style=\"{\n          width: setSize.imgWidth,\n          height: setSize.imgHeight,\n          'background-size': setSize.imgWidth + ' ' + setSize.imgHeight,\n          'margin-bottom': vSpace + 'px'\n        }\"\n        class=\"verify-img-panel\"\n      >\n        <div v-show=\"showRefresh\" class=\"verify-refresh\" style=\"z-index: 3\" @click=\"refresh\">\n          <i class=\"iconfont icon-refresh\"></i>\n        </div>\n        <img\n          ref=\"canvas\"\n          :src=\"'data:image/png;base64,' + pointBackImgBase\"\n          alt=\"\"\n          style=\"display: block; width: 100%; height: 100%\"\n          @click=\"bindingClick ? canvasClick($event) : undefined\"\n        />\n\n        <div\n          v-for=\"(tempPoint, index) in tempPoints\"\n          :key=\"index\"\n          :style=\"{\n            'background-color': '#1abd6c',\n            color: '#fff',\n            'z-index': 9999,\n            width: '20px',\n            height: '20px',\n            'text-align': 'center',\n            'line-height': '20px',\n            'border-radius': '50%',\n            position: 'absolute',\n            top: parseInt(tempPoint.y - 10) + 'px',\n            left: parseInt(tempPoint.x - 10) + 'px'\n          }\"\n          class=\"point-area\"\n        >\n          {{ index + 1 }}\n        </div>\n      </div>\n    </div>\n    <!-- 'height': this.barSize.height, -->\n    <div\n      :style=\"{\n        width: setSize.imgWidth,\n        color: barAreaColor,\n        'border-color': barAreaBorderColor,\n        'line-height': barSize.height\n      }\"\n      class=\"verify-bar-area\"\n    >\n      <span class=\"verify-msg\">{{ text }}</span>\n    </div>\n  </div>\n</template>\n<script setup type=\"text/babel\">\n/**\n * VerifyPoints\n * @description 点选\n * */\nimport { resetSize } from './../utils/util'\nimport { aesEncrypt } from './../utils/ase'\nimport { getCode, reqCheck } from '@/api/login'\nimport { getCurrentInstance, nextTick, onMounted, reactive, ref, toRefs } from 'vue'\n\nconst props = defineProps({\n  //弹出式pop，固定fixed\n  mode: {\n    type: String,\n    default: 'fixed'\n  },\n  captchaType: {\n    type: String\n  },\n  //间隔\n  vSpace: {\n    type: Number,\n    default: 5\n  },\n  imgSize: {\n    type: Object,\n    default() {\n      return {\n        width: '310px',\n        height: '155px'\n      }\n    }\n  },\n  barSize: {\n    type: Object,\n    default() {\n      return {\n        width: '310px',\n        height: '40px'\n      }\n    }\n  }\n})\n\nconst { t } = useI18n()\nconst { mode, captchaType } = toRefs(props)\nconst { proxy } = getCurrentInstance()\nlet secretKey = ref(''), //后端返回的ase加密秘钥\n  checkNum = ref(3), //默认需要点击的字数\n  fontPos = reactive([]), //选中的坐标信息\n  checkPosArr = reactive([]), //用户点击的坐标\n  num = ref(1), //点击的记数\n  pointBackImgBase = ref(''), //后端获取到的背景图片\n  poinTextList = reactive([]), //后端返回的点击字体顺序\n  backToken = ref(''), //后端返回的token值\n  setSize = reactive({\n    imgHeight: 0,\n    imgWidth: 0,\n    barHeight: 0,\n    barWidth: 0\n  }),\n  tempPoints = reactive([]),\n  text = ref(''),\n  barAreaColor = ref(undefined),\n  barAreaBorderColor = ref(undefined),\n  showRefresh = ref(true),\n  bindingClick = ref(true)\n\nconst init = () => {\n  //加载页面\n  fontPos.splice(0, fontPos.length)\n  checkPosArr.splice(0, checkPosArr.length)\n  num.value = 1\n  getPictrue()\n  nextTick(() => {\n    let { imgHeight, imgWidth, barHeight, barWidth } = resetSize(proxy)\n    setSize.imgHeight = imgHeight\n    setSize.imgWidth = imgWidth\n    setSize.barHeight = barHeight\n    setSize.barWidth = barWidth\n    proxy.$parent.$emit('ready', proxy)\n  })\n}\nonMounted(() => {\n  // 禁止拖拽\n  init()\n  proxy.$el.onselectstart = function () {\n    return false\n  }\n})\nconst canvas = ref(null)\nconst canvasClick = (e) => {\n  checkPosArr.push(getMousePos(canvas, e))\n  if (num.value == checkNum.value) {\n    num.value = createPoint(getMousePos(canvas, e))\n    //按比例转换坐标值\n    let arr = pointTransfrom(checkPosArr, setSize)\n    checkPosArr.length = 0\n    checkPosArr.push(...arr)\n    //等创建坐标执行完\n    setTimeout(() => {\n      // var flag = this.comparePos(this.fontPos, this.checkPosArr);\n      //发送后端请求\n      var captchaVerification = secretKey.value\n        ? aesEncrypt(backToken.value + '---' + JSON.stringify(checkPosArr), secretKey.value)\n        : backToken.value + '---' + JSON.stringify(checkPosArr)\n      let data = {\n        captchaType: captchaType.value,\n        pointJson: secretKey.value\n          ? aesEncrypt(JSON.stringify(checkPosArr), secretKey.value)\n          : JSON.stringify(checkPosArr),\n        token: backToken.value\n      }\n      reqCheck(data).then((res) => {\n        if (res.repCode == '0000') {\n          barAreaColor.value = '#4cae4c'\n          barAreaBorderColor.value = '#5cb85c'\n          text.value = t('captcha.success')\n          bindingClick.value = false\n          if (mode.value == 'pop') {\n            setTimeout(() => {\n              proxy.$parent.clickShow = false\n              refresh()\n            }, 1500)\n          }\n          proxy.$parent.$emit('success', { captchaVerification })\n        } else {\n          proxy.$parent.$emit('error', proxy)\n          barAreaColor.value = '#d9534f'\n          barAreaBorderColor.value = '#d9534f'\n          text.value = t('captcha.fail')\n          setTimeout(() => {\n            refresh()\n          }, 700)\n        }\n      })\n    }, 400)\n  }\n  if (num.value < checkNum.value) {\n    num.value = createPoint(getMousePos(canvas, e))\n  }\n}\n//获取坐标\nconst getMousePos = function (obj, e) {\n  var x = e.offsetX\n  var y = e.offsetY\n  return { x, y }\n}\n//创建坐标点\nconst createPoint = function (pos) {\n  tempPoints.push(Object.assign({}, pos))\n  return num.value + 1\n}\nconst refresh = async function () {\n  tempPoints.splice(0, tempPoints.length)\n  barAreaColor.value = '#000'\n  barAreaBorderColor.value = '#ddd'\n  bindingClick.value = true\n  fontPos.splice(0, fontPos.length)\n  checkPosArr.splice(0, checkPosArr.length)\n  num.value = 1\n  await getPictrue()\n  showRefresh.value = true\n}\n\n// 请求背景图片和验证图片\nconst getPictrue = async () => {\n  let data = {\n    captchaType: captchaType.value\n  }\n  const res = await getCode(data)\n  if (res.repCode == '0000') {\n    pointBackImgBase.value = res.repData.originalImageBase64\n    backToken.value = res.repData.token\n    secretKey.value = res.repData.secretKey\n    poinTextList.value = res.repData.wordList\n    text.value = t('captcha.point') + '【' + poinTextList.value.join(',') + '】'\n  } else {\n    text.value = res.repMsg\n  }\n}\n//坐标转换函数\nconst pointTransfrom = function (pointArr, imgSize) {\n  var newPointArr = pointArr.map((p) => {\n    let x = Math.round((310 * p.x) / parseInt(imgSize.imgWidth))\n    let y = Math.round((155 * p.y) / parseInt(imgSize.imgHeight))\n    return { x, y }\n  })\n  return newPointArr\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Verifition/src/Verify/VerifySlide.vue",
    "content": "<template>\n  <div style=\"position: relative\">\n    <div\n      v-if=\"type === '2'\"\n      :style=\"{ height: parseInt(setSize.imgHeight) + vSpace + 'px' }\"\n      class=\"verify-img-out\"\n    >\n      <div :style=\"{ width: setSize.imgWidth, height: setSize.imgHeight }\" class=\"verify-img-panel\">\n        <img\n          :src=\"'data:image/png;base64,' + backImgBase\"\n          alt=\"\"\n          style=\"display: block; width: 100%; height: 100%\"\n        />\n        <div v-show=\"showRefresh\" class=\"verify-refresh\" @click=\"refresh\">\n          <i class=\"iconfont icon-refresh\"></i>\n        </div>\n        <transition name=\"tips\">\n          <span v-if=\"tipWords\" :class=\"passFlag ? 'suc-bg' : 'err-bg'\" class=\"verify-tips\">\n            {{ tipWords }}\n          </span>\n        </transition>\n      </div>\n    </div>\n    <!-- 公共部分 -->\n    <div\n      :style=\"{ width: setSize.imgWidth, height: barSize.height, 'line-height': barSize.height }\"\n      class=\"verify-bar-area\"\n    >\n      <span class=\"verify-msg\" v-text=\"text\"></span>\n      <div\n        :style=\"{\n          width: leftBarWidth !== undefined ? leftBarWidth : barSize.height,\n          height: barSize.height,\n          'border-color': leftBarBorderColor,\n          transaction: transitionWidth\n        }\"\n        class=\"verify-left-bar\"\n      >\n        <span class=\"verify-msg\" v-text=\"finishText\"></span>\n        <div\n          :style=\"{\n            width: barSize.height,\n            height: barSize.height,\n            'background-color': moveBlockBackgroundColor,\n            left: moveBlockLeft,\n            transition: transitionLeft\n          }\"\n          class=\"verify-move-block\"\n          @mousedown=\"start\"\n          @touchstart=\"start\"\n        >\n          <i :class=\"['verify-icon iconfont', iconClass]\" :style=\"{ color: iconColor }\"></i>\n          <div\n            v-if=\"type === '2'\"\n            :style=\"{\n              width: Math.floor((parseInt(setSize.imgWidth) * 47) / 310) + 'px',\n              height: setSize.imgHeight,\n              top: '-' + (parseInt(setSize.imgHeight) + vSpace) + 'px',\n              'background-size': setSize.imgWidth + ' ' + setSize.imgHeight\n            }\"\n            class=\"verify-sub-block\"\n          >\n            <img\n              :src=\"'data:image/png;base64,' + blockBackImgBase\"\n              alt=\"\"\n              style=\"display: block; width: 100%; height: 100%; -webkit-user-drag: none\"\n            />\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</template>\n<script setup type=\"text/babel\">\n/**\n * VerifySlide\n * @description 滑块\n * */\nimport { aesEncrypt } from './../utils/ase'\nimport { resetSize } from './../utils/util'\nimport { getCode, reqCheck } from '@/api/login'\n\nconst props = defineProps({\n  captchaType: {\n    type: String\n  },\n  type: {\n    type: String,\n    default: '1'\n  },\n  //弹出式pop，固定fixed\n  mode: {\n    type: String,\n    default: 'fixed'\n  },\n  vSpace: {\n    type: Number,\n    default: 5\n  },\n  explain: {\n    type: String,\n    default: ''\n  },\n  imgSize: {\n    type: Object,\n    default() {\n      return {\n        width: '310px',\n        height: '155px'\n      }\n    }\n  },\n  blockSize: {\n    type: Object,\n    default() {\n      return {\n        width: '50px',\n        height: '50px'\n      }\n    }\n  },\n  barSize: {\n    type: Object,\n    default() {\n      return {\n        width: '310px',\n        height: '30px'\n      }\n    }\n  }\n})\n\nconst { t } = useI18n()\nconst { mode, captchaType, type, blockSize, explain } = toRefs(props)\nconst { proxy } = getCurrentInstance()\nlet secretKey = ref(''), //后端返回的ase加密秘钥\n  passFlag = ref(''), //是否通过的标识\n  backImgBase = ref(''), //验证码背景图片\n  blockBackImgBase = ref(''), //验证滑块的背景图片\n  backToken = ref(''), //后端返回的唯一token值\n  startMoveTime = ref(''), //移动开始的时间\n  endMovetime = ref(''), //移动结束的时间\n  tipWords = ref(''),\n  text = ref(''),\n  finishText = ref(''),\n  setSize = reactive({\n    imgHeight: 0,\n    imgWidth: 0,\n    barHeight: 0,\n    barWidth: 0\n  }),\n  moveBlockLeft = ref(undefined),\n  leftBarWidth = ref(undefined),\n  // 移动中样式\n  moveBlockBackgroundColor = ref(undefined),\n  leftBarBorderColor = ref('#ddd'),\n  iconColor = ref(undefined),\n  iconClass = ref('icon-right'),\n  status = ref(false), //鼠标状态\n  isEnd = ref(false), //是够验证完成\n  showRefresh = ref(true),\n  transitionLeft = ref(''),\n  transitionWidth = ref(''),\n  startLeft = ref(0)\n\nconst barArea = computed(() => {\n  return proxy.$el.querySelector('.verify-bar-area')\n})\nconst init = () => {\n  if (explain.value === '') {\n    text.value = t('captcha.slide')\n  } else {\n    text.value = explain.value\n  }\n  getPictrue()\n  nextTick(() => {\n    let { imgHeight, imgWidth, barHeight, barWidth } = resetSize(proxy)\n    setSize.imgHeight = imgHeight\n    setSize.imgWidth = imgWidth\n    setSize.barHeight = barHeight\n    setSize.barWidth = barWidth\n    proxy.$parent.$emit('ready', proxy)\n  })\n\n  window.removeEventListener('touchmove', function (e) {\n    move(e)\n  })\n  window.removeEventListener('mousemove', function (e) {\n    move(e)\n  })\n\n  //鼠标松开\n  window.removeEventListener('touchend', function () {\n    end()\n  })\n  window.removeEventListener('mouseup', function () {\n    end()\n  })\n\n  window.addEventListener('touchmove', function (e) {\n    move(e)\n  })\n  window.addEventListener('mousemove', function (e) {\n    move(e)\n  })\n\n  //鼠标松开\n  window.addEventListener('touchend', function () {\n    end()\n  })\n  window.addEventListener('mouseup', function () {\n    end()\n  })\n}\nwatch(type, () => {\n  init()\n})\nonMounted(() => {\n  // 禁止拖拽\n  init()\n  proxy.$el.onselectstart = function () {\n    return false\n  }\n})\n//鼠标按下\nconst start = (e) => {\n  e = e || window.event\n  if (!e.touches) {\n    //兼容PC端\n    var x = e.clientX\n  } else {\n    //兼容移动端\n    var x = e.touches[0].pageX\n  }\n  startLeft.value = Math.floor(x - barArea.value.getBoundingClientRect().left)\n  startMoveTime.value = +new Date() //开始滑动的时间\n  if (isEnd.value == false) {\n    text.value = ''\n    moveBlockBackgroundColor.value = '#337ab7'\n    leftBarBorderColor.value = '#337AB7'\n    iconColor.value = '#fff'\n    e.stopPropagation()\n    status.value = true\n  }\n}\n//鼠标移动\nconst move = (e) => {\n  e = e || window.event\n  if (status.value && isEnd.value == false) {\n    if (!e.touches) {\n      //兼容PC端\n      var x = e.clientX\n    } else {\n      //兼容移动端\n      var x = e.touches[0].pageX\n    }\n    var bar_area_left = barArea.value.getBoundingClientRect().left\n    var move_block_left = x - bar_area_left //小方块相对于父元素的left值\n    if (\n      move_block_left >=\n      barArea.value.offsetWidth - parseInt(parseInt(blockSize.value.width) / 2) - 2\n    ) {\n      move_block_left =\n        barArea.value.offsetWidth - parseInt(parseInt(blockSize.value.width) / 2) - 2\n    }\n    if (move_block_left <= 0) {\n      move_block_left = parseInt(parseInt(blockSize.value.width) / 2)\n    }\n    //拖动后小方块的left值\n    moveBlockLeft.value = move_block_left - startLeft.value + 'px'\n    leftBarWidth.value = move_block_left - startLeft.value + 'px'\n  }\n}\n\n//鼠标松开\nconst end = () => {\n  endMovetime.value = +new Date()\n  //判断是否重合\n  if (status.value && isEnd.value == false) {\n    var moveLeftDistance = parseInt((moveBlockLeft.value || '0').replace('px', ''))\n    moveLeftDistance = (moveLeftDistance * 310) / parseInt(setSize.imgWidth)\n    let data = {\n      captchaType: captchaType.value,\n      pointJson: secretKey.value\n        ? aesEncrypt(JSON.stringify({ x: moveLeftDistance, y: 5.0 }), secretKey.value)\n        : JSON.stringify({ x: moveLeftDistance, y: 5.0 }),\n      token: backToken.value\n    }\n    reqCheck(data).then((res) => {\n      if (res.repCode == '0000') {\n        moveBlockBackgroundColor.value = '#5cb85c'\n        leftBarBorderColor.value = '#5cb85c'\n        iconColor.value = '#fff'\n        iconClass.value = 'icon-check'\n        showRefresh.value = false\n        isEnd.value = true\n        if (mode.value == 'pop') {\n          setTimeout(() => {\n            proxy.$parent.clickShow = false\n            refresh()\n          }, 1500)\n        }\n        passFlag.value = true\n        tipWords.value = `${((endMovetime.value - startMoveTime.value) / 1000).toFixed(2)}s\n            ${t('captcha.success')}`\n        var captchaVerification = secretKey.value\n          ? aesEncrypt(\n              backToken.value + '---' + JSON.stringify({ x: moveLeftDistance, y: 5.0 }),\n              secretKey.value\n            )\n          : backToken.value + '---' + JSON.stringify({ x: moveLeftDistance, y: 5.0 })\n        setTimeout(() => {\n          tipWords.value = ''\n          proxy.$parent.closeBox()\n          proxy.$parent.$emit('success', { captchaVerification })\n        }, 1000)\n      } else {\n        moveBlockBackgroundColor.value = '#d9534f'\n        leftBarBorderColor.value = '#d9534f'\n        iconColor.value = '#fff'\n        iconClass.value = 'icon-close'\n        passFlag.value = false\n        setTimeout(function () {\n          refresh()\n        }, 1000)\n        proxy.$parent.$emit('error', proxy)\n        tipWords.value = t('captcha.fail')\n        setTimeout(() => {\n          tipWords.value = ''\n        }, 1000)\n      }\n    })\n    status.value = false\n  }\n}\n\nconst refresh = async () => {\n  showRefresh.value = true\n  finishText.value = ''\n\n  transitionLeft.value = 'left .3s'\n  moveBlockLeft.value = 0\n\n  leftBarWidth.value = undefined\n  transitionWidth.value = 'width .3s'\n\n  leftBarBorderColor.value = '#ddd'\n  moveBlockBackgroundColor.value = '#fff'\n  iconColor.value = '#000'\n  iconClass.value = 'icon-right'\n  isEnd.value = false\n\n  await getPictrue()\n  setTimeout(() => {\n    transitionWidth.value = ''\n    transitionLeft.value = ''\n    text.value = explain.value\n  }, 300)\n}\n\n// 请求背景图片和验证图片\nconst getPictrue = async () => {\n  let data = {\n    captchaType: captchaType.value\n  }\n  const res = await getCode(data)\n  if (res.repCode == '0000') {\n    backImgBase.value = res.repData.originalImageBase64\n    blockBackImgBase.value = res.repData.jigsawImageBase64\n    backToken.value = res.repData.token\n    secretKey.value = res.repData.secretKey\n  } else {\n    tipWords.value = res.repMsg\n  }\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Verifition/src/Verify/index.ts",
    "content": "import VerifySlide from './VerifySlide.vue'\nimport VerifyPoints from './VerifyPoints.vue'\n\nexport { VerifySlide, VerifyPoints }\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Verifition/src/Verify.vue",
    "content": "<template>\n  <div v-show=\"showBox\" :class=\"mode == 'pop' ? 'mask' : ''\">\n    <div\n      :class=\"mode == 'pop' ? 'verifybox' : ''\"\n      :style=\"{ 'max-width': parseInt(imgSize.width) + 20 + 'px' }\"\n    >\n      <div v-if=\"mode == 'pop'\" class=\"verifybox-top\">\n        {{ t('captcha.verification') }}\n        <span class=\"verifybox-close\" @click=\"closeBox\">\n          <i class=\"iconfont icon-close\"></i>\n        </span>\n      </div>\n      <div :style=\"{ padding: mode == 'pop' ? '10px' : '0' }\" class=\"verifybox-bottom\">\n        <!-- 验证码容器 -->\n        <component\n          :is=\"componentType\"\n          v-if=\"componentType\"\n          ref=\"instance\"\n          :arith=\"arith\"\n          :barSize=\"barSize\"\n          :blockSize=\"blockSize\"\n          :captchaType=\"captchaType\"\n          :explain=\"explain\"\n          :figure=\"figure\"\n          :imgSize=\"imgSize\"\n          :mode=\"mode\"\n          :type=\"verifyType\"\n          :vSpace=\"vSpace\"\n        />\n      </div>\n    </div>\n  </div>\n</template>\n<script type=\"text/babel\">\n/**\n * Verify 验证码组件\n * @description 分发验证码使用\n * */\nimport { VerifyPoints, VerifySlide } from './Verify'\nimport { computed, ref, toRefs, watchEffect } from 'vue'\n\nexport default {\n  name: 'Vue3Verify',\n  components: {\n    VerifySlide,\n    VerifyPoints\n  },\n  props: {\n    captchaType: {\n      type: String,\n      required: true\n    },\n    figure: {\n      type: Number\n    },\n    arith: {\n      type: Number\n    },\n    mode: {\n      type: String,\n      default: 'pop'\n    },\n    vSpace: {\n      type: Number\n    },\n    explain: {\n      type: String\n    },\n    imgSize: {\n      type: Object,\n      default() {\n        return {\n          width: '310px',\n          height: '155px'\n        }\n      }\n    },\n    blockSize: {\n      type: Object\n    },\n    barSize: {\n      type: Object\n    }\n  },\n  setup(props) {\n    const { t } = useI18n()\n    const { captchaType, mode } = toRefs(props)\n    const clickShow = ref(false)\n    const verifyType = ref(undefined)\n    const componentType = ref(undefined)\n\n    const instance = ref({})\n\n    const showBox = computed(() => {\n      if (mode.value == 'pop') {\n        return clickShow.value\n      } else {\n        return true\n      }\n    })\n    /**\n     * refresh\n     * @description 刷新\n     * */\n    const refresh = () => {\n      if (instance.value.refresh) {\n        instance.value.refresh()\n      }\n    }\n    const closeBox = () => {\n      clickShow.value = false\n      refresh()\n    }\n    const show = () => {\n      if (mode.value == 'pop') {\n        clickShow.value = true\n      }\n    }\n    watchEffect(() => {\n      switch (captchaType.value) {\n        case 'blockPuzzle':\n          verifyType.value = '2'\n          componentType.value = 'VerifySlide'\n          break\n        case 'clickWord':\n          verifyType.value = ''\n          componentType.value = 'VerifyPoints'\n          break\n      }\n    })\n\n    return {\n      t,\n      clickShow,\n      verifyType,\n      componentType,\n      instance,\n      showBox,\n      closeBox,\n      show\n    }\n  }\n}\n</script>\n<style>\n.verifybox {\n  position: relative;\n  top: 50%;\n  left: 50%;\n  background-color: #fff;\n  border: 1px solid #e4e7eb;\n  border-radius: 5px;\n  transform: translate(-50%, -50%);\n  box-shadow: 0 0 10px rgb(0 0 0 / 30%);\n  box-sizing: border-box;\n}\n\n.verifybox-top {\n  height: 40px;\n  padding: 0 15px;\n  font-size: 16px;\n  line-height: 40px;\n  color: #45494c;\n  text-align: left;\n  border-bottom: 1px solid #e4e7eb;\n  box-sizing: border-box;\n}\n\n.verifybox-bottom {\n  padding: 10px;\n  box-sizing: border-box;\n}\n\n.verifybox-close {\n  position: absolute;\n  top: 13px;\n  right: 9px;\n  width: 24px;\n  height: 24px;\n  text-align: center;\n  cursor: pointer;\n}\n\n.mask {\n  position: fixed;\n  top: 0;\n  left: 0;\n  z-index: 1001;\n  width: 100%;\n  height: 100vh;\n  background: rgb(0 0 0 / 30%);\n\n  /* display: none; */\n  transition: all 0.5s;\n}\n\n.verify-tips {\n  position: absolute;\n  bottom: 0;\n  left: 0;\n  width: 100%;\n  height: 30px;\n  line-height: 30px;\n  color: #fff;\n  text-indent: 10px;\n}\n\n.suc-bg {\n  background-color: rgb(92 184 92 / 50%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startcolorstr=#7f5CB85C, endcolorstr=#7f5CB85C);\n}\n\n.err-bg {\n  background-color: rgb(217 83 79 / 50%);\n  filter: progid:DXImageTransform.Microsoft.gradient(startcolorstr=#7fD9534F, endcolorstr=#7fD9534F);\n}\n\n.tips-enter,\n.tips-leave-to {\n  bottom: -30px;\n}\n\n.tips-enter-active,\n.tips-leave-active {\n  transition: bottom 0.5s;\n}\n\n/* ---------------------------- */\n\n/* 常规验证码 */\n.verify-code {\n  margin-bottom: 5px;\n  font-size: 20px;\n  text-align: center;\n  cursor: pointer;\n  border: 1px solid #ddd;\n}\n\n.cerify-code-panel {\n  height: 100%;\n  overflow: hidden;\n}\n\n.verify-code-area {\n  float: left;\n}\n\n.verify-input-area {\n  float: left;\n  width: 60%;\n  padding-right: 10px;\n}\n\n.verify-change-area {\n  float: left;\n  line-height: 30px;\n}\n\n.varify-input-code {\n  display: inline-block;\n  width: 100%;\n  height: 25px;\n}\n\n.verify-change-code {\n  color: #337ab7;\n  cursor: pointer;\n}\n\n.verify-btn {\n  width: 200px;\n  height: 30px;\n  margin-top: 10px;\n  color: #fff;\n  background-color: #337ab7;\n  border: none;\n  border-radius: 8px;\n}\n\n/* 滑动验证码 */\n.verify-bar-area {\n  position: relative;\n  text-align: center;\n  background: #fff;\n  border: 1px solid #ddd;\n  border-radius: 8px;\n  box-sizing: content-box;\n}\n\n.verify-bar-area .verify-move-block {\n  position: absolute;\n  top: 0;\n  left: 0;\n  cursor: pointer;\n  background: #fff;\n  border-radius: 8px;\n  box-shadow: 0 0 2px #888;\n  box-sizing: content-box;\n}\n\n.verify-bar-area .verify-move-block:hover {\n  color: #fff;\n  background-color: #337ab7;\n}\n\n.verify-bar-area .verify-left-bar {\n  position: absolute;\n  top: -1px;\n  left: -1px;\n  cursor: pointer;\n  background: #f0fff0;\n  border: 1px solid #ddd;\n  border-radius: 8px;\n  box-sizing: content-box;\n}\n\n.verify-img-panel {\n  position: relative;\n  margin: 0;\n  border-top: 1px solid #ddd;\n  border-bottom: 1px solid #ddd;\n  border-radius: 3px;\n  box-sizing: content-box;\n}\n\n.verify-img-panel .verify-refresh {\n  position: absolute;\n  top: 0;\n  right: 0;\n  z-index: 2;\n  width: 25px;\n  height: 25px;\n  padding: 5px;\n  text-align: center;\n  cursor: pointer;\n}\n\n.verify-img-panel .icon-refresh {\n  font-size: 20px;\n  color: #fff;\n}\n\n.verify-img-panel .verify-gap {\n  position: relative;\n  z-index: 2;\n  background-color: #fff;\n  border: 1px solid #fff;\n}\n\n.verify-bar-area .verify-move-block .verify-sub-block {\n  position: absolute;\n  z-index: 3;\n  text-align: center;\n\n  /* border: 1px solid #fff; */\n}\n\n.verify-bar-area .verify-move-block .verify-icon {\n  font-size: 18px;\n}\n\n.verify-bar-area .verify-msg {\n  z-index: 3;\n}\n\n/* 字体图标的css */\n\n/* @font-face {font-family: \"iconfont\"; */\n\n/* src: url('../fonts/iconfont.eot?t=1508229193188'); !* IE9*! */\n\n/* src: url('../fonts/iconfont.eot?t=1508229193188#iefix') format('embedded-opentype'), !* IE6-IE8 *! */\n\n/* url('data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAAaAAAsAAAAACUwAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAABHU1VCAAABCAAAADMAAABCsP6z7U9TLzIAAAE8AAAARAAAAFZW7kiSY21hcAAAAYAAAAB3AAABuM+qBlRnbHlmAAAB+AAAAnQAAALYnrUwT2hlYWQAAARsAAAALwAAADYPNwajaGhlYQAABJwAAAAcAAAAJAfeA4dobXR4AAAEuAAAABMAAAAYF+kAAGxvY2EAAATMAAAADgAAAA4CvAGsbWF4cAAABNwAAAAfAAAAIAEVAF1uYW1lAAAE/AAAAUUAAAJtPlT+fXBvc3QAAAZEAAAAPAAAAE3oPPXPeJxjYGRgYOBikGPQYWB0cfMJYeBgYGGAAJAMY05meiJQDMoDyrGAaQ4gZoOIAgCKIwNPAHicY2Bk/sM4gYGVgYOpk+kMAwNDP4RmfM1gxMjBwMDEwMrMgBUEpLmmMDgwVDxbwtzwv4EhhrmBoQEozAiSAwAw1A0UeJzFkcENgCAMRX8RjCGO4gTe9eQcnhzAfXC2rqG/hYsT8MmD9gdS0gJIAAaykAjIBYHppCvuD8juR6zMJ67A89Zdn/f1aNPikUn8RvYo8G20CjKim6Rf6b9m34+WWd/vBr+oW8V6q3vF5qKlYrPRp4L0Ad5nGL8AeJxFUc9rE0EYnTezu8lMsrvtbrqb3TRt0rS7bdOmdI0JbWmCtiItIv5oi14qevCk9SQVLFiQgqAF8Q9QLKIHLx48FkHo3ZNnFUXwD5C2B6dO6sFhmI83w7z3fe8RnZCjb2yX5YlLhskkmScXCIFRxYBFiyjH9Rqtoqes9/g5i8WVuJyqDNTYLPwBI+cljXrkGynDhoU+nCgnjbhGY5yst+gMEq8IBIXwsjPU67CnEPm4b0su0h309Fd67da4XBhr55KSm17POk7gOE/Shq6nKdVsC7d9j+tcGPKVboc9u/0jtB/ZIA7PXTVLBef6o/paccjnwOYm3ELJetPuDrvV3gg91wlSXWY6H5qVwRzWf2TybrYYfSdqoXOwh/Qa8RWIjBTiSI3h614/vKSNRhONOrsnQi6Xf4nQFQDTmJE1NKbhI6crHEJO/+S5QPxhYJRRyvBFBP+5T9EPpEAIVzzRQIrjmJ6jY1WTo+NXTMchuBsKuS8PRZATSMl9oTA4uNLkeIA0V1UeqOoGQh7IAxGo+7T83fn3T+voqCNPPAUazUYUI7LgKSV1Jk2oUeghYGhZ+cKOe2FjVu5ZKEY2VkE13AK1+jI4r1KLbPlZfrKiPhOXKPRj7q9sj9XJ7LFHNmrKJS3VCdhXGSdKrtmoQaWeMjQVt0KD6sGPOx0oH2fgtzoNROxtNq8F3tzYM/n+TjKSX5qf2jx941276TIr9FjXxKr8eX/6bK4yuopwo9py1sw8F9kdw4AmurRpLUM3tYx5ZnKpfHPi8dzz19vJ6MjyxYUrpqeb1uLs3eGV6vr21pSqpeWkqonAN9oUyIiXpv8XvlN5e3icY2BkYGAA4n0vN4fG89t8ZeBmYQCBa9wPPRH0/wcsDMwmQC4HAxNIFABAfAqaAHicY2BkYGBu+N/AEMPCAAJAkpEBFbABAEcMAm94nGNhYGBgfsnAwMKAigESnwEBAAAAAAAAdgCkANoBCAFsAAB4nGNgZGBgYGMIZGBlAAEmIOYCQgaG/2A+AwARSAFzAHicZY9NTsMwEIVf+gekEqqoYIfkBWIBKP0Rq25YVGr3XXTfpk6bKokjx63UA3AejsAJOALcgDvwSCebNpbH37x5Y08A3OAHHo7fLfeRPVwyO3INF7gXrlN/EG6QX4SbaONVuEX9TdjHM6bCbXRheYPXuGL2hHdhDx18CNdwjU/hOvUv4Qb5W7iJO/wKt9Dx6sI+5l5XuI1HL/bHVi+cXqnlQcWhySKTOb+CmV7vkoWt0uqca1vEJlODoF9JU51pW91T7NdD5yIVWZOqCas6SYzKrdnq0AUb5/JRrxeJHoQm5Vhj/rbGAo5xBYUlDowxQhhkiMro6DtVZvSvsUPCXntWPc3ndFsU1P9zhQEC9M9cU7qy0nk6T4E9XxtSdXQrbsuelDSRXs1JErJCXta2VELqATZlV44RelzRiT8oZ0j/AAlabsgAAAB4nGNgYoAALgbsgI2RiZGZkYWRlZGNkZ2BsYI1OSM1OZs1OSe/OJW1KDM9o4S9KDWtKLU4g4EBAJ79CeQ=') format('woff'), */\n\n/* url('../fonts/iconfont.ttf?t=1508229193188') format('truetype'), !* chrome, firefox, opera, Safari, Android, iOS 4.2+*! */\n\n/* url('../fonts/iconfont.svg?t=1508229193188#iconfont') format('svg'); !* iOS 4.1- *! */\n\n/* } */\n\n.iconfont {\n  font-family: iconfont !important;\n  font-size: 16px;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  font-style: normal;\n}\n\n.icon-check::before {\n  position: absolute;\n  z-index: 9999;\n  display: block;\n  width: 16px;\n  height: 16px;\n  margin: auto;\n  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADIEAYAAAD9yHLdAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAAAAAAAA+UO7fwAAAAlwSFlzAAAASAAAAEgARslrPgAAIlFJREFUeNrt3X1cVNW6B/BnbcS3xJd7fLmSeo+op/Qmyp4BFcQEwpd8Nyc9iZppgUfE49u1tCwlNcMySCM1S81jCoaioiJvKoYgswfUo5wSJ69SZFKCKSAws+4f2/GetFFRYG3g9/2Hz2xj+O2J4Zm19trrIQIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAKgjmOgAAADwOBhz83TzdPNs397qanW1ujJ2s8fNHjd7FBTkhuSG5IbculVdP1kSfeoAAPBwdFzHdXzgQN0S3RLdkpgY2SJbZMvNm9It6ZZ064cfGmQ2yGyQmZfX3KO5R3OPwkJdsi5Zl5yYKIfL4XL4mDHqs7AqGzhgBAIAoFFdI7pGdI1o1KjFlhZbWmxZv149OmXK4z3r4cPEiROfOFExKSbFVFDwqM+EEQgAgMY8y5/lz/LGjZu3bt66eev9+9Wjj1s4bAYNIkaMWHKyx3mP8x7nmzd/1GdyEP1CAQCASifrZJ3s6FjmWuZa5rprF3uLvcXeGjq0en5au3a8nJfz8k6d8lPyU/JTYmIq+wwYgQAAaIIk0WgaTaO/+IJm0SyaNWJEtf/IPMqjvJde0g/QD9APcHOrdGIhrxMAANzGmJwr58q569ZRLMVS7MSJNfajFVJIYYy/wF/gL7z0UmW/vUGNvk4AAHCHTqfT6XQrVtB4Gk/jg4KEBfmBfqAf+vSp7LdhBAIAUMPUwvH66+oj21eBSqmUStu3r+y3oYAAANQQtXDMmKE+WrlSdB4bvpwv58t/+62y34cCAgBQzeSt8lZ568SJFEiBFLh2reg8d2MD2UA28PTpyn4fCggAQDXRh+pD9aEjR1IABVDA5s20ntbTeklzf3eZF/NiXvv2Vfb7NHciAAC1nRwsB8vBvr5Wf6u/1X/nTubO3Jl7A+0tWvImb/LOyemc3zm/c/6ePZX9dmxlAgBQRfTd9N303Tw8rFusW6xbEhPZLDaLzXJyEp3rHjNoBs24dYt/wj/hn3h5mUwmk8mkKJV9GoxAAAAekz5AH6APeOYZ6znrOeu5Awc0WzgCKZACrVZ2hB1hR15++VELhw1GIAAAj0hdVdWli/ooNVX9WvnlsNUflHSk45wbuZEbg4LUwrFhw+M+LUYgAACV1CuoV1CvoCef5Kv4Kr4qIUE9qsHCcRsv4AW8YOHCqiocNtq7qAMAoFHqZoetW9MgGkSDDh+mhbSQFnbuLDrX/YWGmmJMMaaYsLCqfmZMYQEAPIBt23PLp5ZPLZ8mJ9MROkJHdDrRueyKpViKXbdO6aB0UDoEB1fXj8EUFgCAHX0v973c93KTJpbvLd9bvt+3T+uFg0/mk/nkL79UC0dISHX/PIxAAADuYuvLwQ/xQ/zQnj1sKBvKhj7/vOhc9vA4HsfjYmOd2jm1c2o3btxRdpQdZRUV1f1zMQIBALjNYDAYDAYHB9pEm2jTl19qvXBQGIVRWFKSWjgmTKipwmGDi+gAAERExJhZZ9aZdZGRNJ2m0/Tx40UnssuHfMgnPb2koKSgpGD0aIUpTGGlpTUdAwUEAOo9XbguXBf+/vu0lbbS1ldfFZ3HrgE0gAacPu0423G24+xhw5SOSkel440bouKggABAvaXjOq7j77xDetKTfv580Xns8iIv8srNlfKkPClv8OD0jukd0zv++qvoWLiIDgD1jrpnVXAwb86b8+Yffyw6jz18NV/NV+flWQZaBloGenufYqfYKXbxouhcNriIDgD1hi5Zl6xLnjyZL+AL+ILwcNF57OpLfanv1atsPpvP5vv7a61w2GAEAgB1nrpn1ejRPJNn8szoaM1ur05EREVF6ldfX0VRFEUxmUQnskejLyAAwOPT79fv1+9/7jn+E/+J/7Rjh7YLR3ExceLEhw9XTIpJMWm3cNho9IUEAHh08hB5iDykb1/+M/+Z/7x7N0VSJEU2aiQ61z30pCd9WZl1inWKdcoLL2R5ZnlmeR4/LjrWw8I1EACoM+S2clu5rasr+yv7K/vrgQO0jtbRumbNROe6G4/kkTzSYqFMyqTMgAC1cBw6JDpXZaGAAECt1zukd0jvkG7daBftol2HD1MERVBEq1aic93jdl8O9gv7hf0SGKhOVUVHi471qFBAAKDW0hfri/XFHTs6cAfuwBMS2Bw2h81p1050LruepWfp2fnzlaHKUGXopk2i4zwuFBAAqHVcw1zDXMPatrWSlayUkEBplEZp//VfonPZw86ys+zsm28qE5WJysQPPxSdp6qggABAraHuktuiRYOgBkENgg4dYt7Mm3k/9ZToXHZNpIk0MTzcWGosNZYuXy46TlXDfSAAoHnqfRxNm6qP4uPVr/37i85l11gaS2M3b1YWK4uVxa+8oh7kXHSsqoYRCABoVo+oHlE9oho2pME0mAbHxKhHNVw4IimSImNiXLJdsl2yp09XD9a9wmGDAgIAmmPry9G4f+P+jfv/4x8UT/EUP3iw6Fz3d/hwUXpRelH6Sy9FR0dHR0dbLKITVTfcSAgAGsPYhT4X+lzos2EDG8FGsBHjxolOZA9fxBfxRWlpFeYKc4V57NjckNyQ3JBbt0Tnqim4BgIAmiEvkhfJiz78kMWzeBY/Z47oPPbwpXwpX5qdbRlmGWYZ5uOjbnZYWCg6V03DFBYACKdbq1urW7tiheYLRypP5anffluRU5FTkTN4cH0tHDYYgQCAMOqeVX//O7vKrrKra9aIzmMPP86P8+NmM/fjftzP2zsrLSstK+3HH0XnEg0jEACocXJXuavcdepU1ol1Yp00fGNdP+pH/X78UUqSkqQkf38Ujt9DAQGAGqMP0YfoQ154gbbTdtq+cSMppJDCtDcTwokTLyiwvGh50fKiv79xuHG4cbjZLDqW1mjvfxwA1DluZjezm3nECMkgGSTD11+rRx0dRee6G8/gGTzj+nU+gA/gA/z81BGH0Sg6l1ZhBAIA1Ua9g9zHh/3MfmY/R0WpRzVYOE7yk/xkSYmUI+VIOSNHonA8HIxAAKDK6bvpu+m7eXhYt1i3WLckJrJZbBab5eQkOtcfKy9Xv44Zo7aQjYsTnai2cBAdAADqDn2APkAf8Mwz1gRrgjUhIYG9wF5gL7RsKTrXPQIpkAKtVlbMilnxpElKvBKvxO/eLTpWbYMRCAA8NnWqqksXddXSsWN0gk7QCWdn0bnuDao2dOJGbuTGoCCTyWQymTZsEB2rtsI1EAB4ZL2CegX1CnrySb6Kr+KrEhI0Wzhu4wW8gBcsXIjCUTWwFxYAVJral6N1axpEg2jQ4cO0kBbSws6dRee6v9BQU4wpxhQTFiY6SV2BKSwAeGge5z3Oe5xv3tzyreVby7dJSfQ2vU1v6/Wic9kVS7EUu26d0kHpoHQIDhYdp67BFBYAPFDfy30v973cpElFVkVWRdbevZovHJtpM23etk0tHCEhouPUVRiBAIBd6lSVoyMxYsRsq5SGDROdyx4ex+N4XGysUzundk7txo07yo6yo6yiQnSuugojEACwQ5L4dD6dT9+6VX2s3cJBYRRGYUlJauGYMAGFo2bUWAHps73P9j7b27Xr2bNnz549W7USfeIAYA9jslk2y+YNG9gmtoltmjBBdCJ7bA2dypVypVwZNUotHKWlonPVF1U+hfX7PW8CA9UtAnx9mQfzYB5Nmtz5Dz3IgzwKC+k1eo1ei4+naTSNpq1Zo5gUk2LKyBD9wgDUR/I5+Zx87oMP2CQ2iU2aO1d0HnvQ0EkbHruA9OK9eC/esmWD1AapDVK/+orm0ByaM2TIIz9hNEVT9IYNRfuL9hftDwmpby0iAUSQT8on5ZNLlrAZbAabsXSp6Dz28JV8JV/53XcVpypOVZzy9j694PSC0wt+/ll0rvrqkQuI15+8/uT1Jyen0smlk0snHz9Ox+gYHXN1rdp4KSnlE8onlE8YMUL9Rbl5U/QLBlCXqBfJQ0LUi+Th4aLz3N+lS+o2697e6kzFpUuiE9V3j3wNpHR26ezS2ZGR1VM4bHx8HHs59nLsdeBAj6geUT2imjUT9UIB1CVylBwlR738MulJT/qPPhKdxx6+hq/ha65ckWKlWCnW3x+FQ1sqPQJxN7gb3A29e1tbWVtZW5lMNdUQhifxJJ70zTdNujTp0qTL0KHf/PLNL9/88ttvYl42gNrJ7Te339x+GzuW5bAclhMVpU5ZOWhvU9UQCqGQa9es063TrdN9fLLKs8qzyk+dEh0Lfq/SIxBrf2t/a/+JE2u6kxjzY37Mz8ur9OXSl0tfTklRb2z6j/+o2ZcLoHZyi3aLdov285N2Sjulndu3a7ZwEBFRcTFP4Ak8YdQoFA5tq/wU1l/oL/QXLy9hiY/QETqi05U1L2te1vzgQdtFfGF5ADRMX6wv1hd7eqo9vWNjKZIiKbJRI9G57jGDZtCMW7fYUraULR01yrTNtM20LTVVdCy4v0qPINSLbrm56kW3Ll1EnwAtpaW01Ggse6PsjbI3Bg06c+bMmTNnrl0THQtApDtTza2tra2tU1LoJJ2kk9r7oMUzeSbPrKhg7syduRsMakOnPXtE54KHU+kRCF/Gl/FlGrr2cHtPHseVjisdVyYn39klFKAe6h3SO6R3SLduln9Y/mH5x8GDWi0ctr4cLJ7Fs/igIBSO2qnyU1i9qTf1zskRHfxu7G32Nnu7d2+1oCQmopBAfaL+vnfqJIVJYVJYUhLrx/qxfv/5n6Jz2cNSWApLCQlRhipDlaGbNonOA4+m8gWkM3WmzrGxooPbtYyW0bJevdQptuRk1zDXMNewtm1FxwKoDrYtgugNeoPeSExknsyTeXbsKDqXPewsO8vOvvmm8bzxvPH82rWi88DjqXQB6TK6y+guo3ftosW0mBafOyf6BO6vZ0/Hrxy/cvzq6FE3TzdPN0/tdkoDqAx1xNGiRfmI8hHlIw4epPfoPXqvWzfRueyaSBNpYni4sdRYaixdvlx0HKgaj7wMV5ZlWZZ1OsYYY+zYMfVo06aiT8genspTeeq331rmWuZa5vr5nfr01KenPv3hB9G5ACpD7T1ue5/Fx6tf+/cXncuusTSWxm7erCxWFiuLX3lFPci56FhQNR75TnS1p7Ci8Ml8Mp8cEKAeLS8XfUL2MG/mzbyfesphrMNYh7HJybZezqJzATyMrhFdI7pGNGrE5/F5fJ5tClm7hYNP49P4tB071MIxbdrtoygcdUyV3Qioy9Pl6fKef57n8Tye9/XXbCabyWY2biz6BO1aQAtowcWLFeMrxleMt+3mefGi6FgA/85gMBgMBgcH8wXzBfOFr75Sr+0ZDKJz3d/hw0VTiqYUTRk5Epuh1m1Vfie5foN+g37D0KFWV6ur1TUmRvOFxJM8yfN//9fhosNFh4s+Pif3ndx3ct/334uOBfD/fTk2bmQGZmAG2yd57bH15agwV5grzIMGYfPT+qHatiKRF8mL5EWDB1MohVLo7t339APRJNsmbb6+6rr0CxdEJ4L6SX3/fPihep/EnDmi89iDvhz1W7V1JDStMK0wrYiPV+8wHT1abSxVUiL6hO+vUyeextN4WkqKW5pbmlta166iE0H9oivVlepKly/XfOG4vSilIqcipyJn8GAUjvqp2lvaqtsvHz6sbss8ZAjNpJk088YN0Sduj20dPbvFbrFbKSm2O3tF54K6TU6UE+XE2bPJi7zIa9Ei0Xns4cf5cX7cbObP8ef4c76+aOhUv9XYbro2coAcIAd4e9Pf6G/0t7g4NovNYrOcnES/EPbwE/wEP/HTT9Z0a7o13c8ve0D2gOwBWr//BWoLW18OlsgSWeLnn9f0LtcPrR/1o34//siGsCFsiLe3cbhxuHG42Sw6FohV7SOQu9l22WTBLJgFP/88/5h/zD/W0N5ad7FtCSGRRBIlJ7uvdV/rvva//1t0LqjdbH056M/0Z/rzZ59ptnBw4sQLCqSnpaelpwcNQuGAf1fjBcRGndo6flzqLfWWeg8ZwjN4Bs+4fl30C2IPm8PmsDnt2llbWFtYW9g2bezZU3QuqF3U35tBg7Tel8P2frQ2tja2Nh46NDM4Mzgz+OxZ0blAW4QVEBtjU2NTY9O0NPIgD/Lw9eXhPJyH//qr6Fx2fUQf0Udt26pD+qQkua3cVm5bXS19oa6w9eVQf89jYrTal8O22IU5MAfmMGpUVlpWWlaa0Sg6F2iT5obM6lYNsqwWkoQENpvNZrM13HnQ1npzvXW9df2gQXjDwb+rLX05VLadJMaMUZexx8WJTgTaJnwEcjf1F9dkkhZJi6RFzz3H03k6T//lF9G57IqgCIpo1UrqJfWSeiUkuHd27+ze2d1ddCwQSx+qD9WHPvWUdaR1pHVkfLxmC0cgBVKg1cq6s+6s++TJKBxQGZobgdztzie4C9YL1gsJCepWDhru8+FBHuRRWEgZlEEZQ4ao13oyMkTHgpqh36/fr9/v4sIP8UP8UGoqnaATdEKDu0DfbujEjdzIjUFB6t52GzaIjgW1i+YLiI26aqV7d9aINWKNkpO13jBHVVTE2/A2vM2QIaZDpkOmQ+npohNB9bC1C2BJLIklpaay/qw/6+/iIjqXPczMzMy8cKHxmvGa8dr774vOA7WT5qaw7MlyynLKcsrJUQuHj496ND9fdK77a9GCXWVX2dVDh9wC3QLdAvv1E50Iqpat86U0X5ovzU9I0HrhUIWGonBAVag1BcRGnaP917/UR76+thucROe6vxYtJCYxiSUk6LiO6/jAgaITwePxOO9x3uN88+ZqB8yDB2k5LaflPXqIzmVXLMVS7Lp16vtnyRLRcaBuqDVTWPbYLlZyF+7CXZKS6EP6kD7UcJ8Pd3In95s3eQPegDcYOdK01rTWtDY5WXQseDh9L/e93PdykyZlT5Q9UfbEgQPMn/kzfw1/INhMm2nztm1KT6Wn0nPKFPWg1So6FtQNtb6A2Nj2rJLGSGOkMcnJbD6bz+Z36CA61/0VF1tft75ufX3kyCxDliHLkJQkOhH8MXWqytFRXcSxe7d6dNgw0bns4XE8jsfFxjq1c2rn1G7cuKPsKDvKKipE54K6pdZNYdmTHZEdkR1x/rxloGWgZaC3N1/FV/FVWu/r0bSp9J70nvTe3r26Ql2hrtDfX3Qi+COSxKfz6Xz61q3qY+0WDgqjMApLSlILx4QJKBxQnepMAbGxdRbk2TybZ/v42HYPFZ3r/po2pV20i3bt2yevkFfIK4YPF50IiIgY05l1Zp05MpJtYpvYpgkTRCeyy4d8yCc9vaSgpKCkYPRotXCUloqOBXVbnZnCskedeujUSX2UnKxOQXTpIjqXXXrSk76sjHVgHVgHg8H4lvEt41t794qOVd/I8+R58rxVq9gRdoQd+Z//EZ3n/s6ccdzjuMdxz8CB6R3TO6Z31PBWQFCn1LkRyN3UG/kuXWLH2XF23MdH7beQmys6l11GMpKxYUO1t3x0tO5fun/p/jVqlOhY9YW6lc5bb2m+cNz+PZZcJBfJZdAgFA4Qoc4XEBt108bLl6V8KV/K9/amxbSYFmu4r8ftQkJraA2tiYqSw+VwOXzMGNGx6ir5oHxQPvi3v6mPli0Tnccevpqv5qvz8irCK8Irwv39M6MzozOjf/pJdC6on+pNAbGxveEalDYobVDq68vf5e/ydzW8TfXtQsK2sq1s686dd/pIQJVQd1MOCGCX2WV2+eOPReexqy/1pb5Xr6qrC/39bdf6RMeC+q3eFRCbjJcyXsp46coVx2uO1xyv+fnxo/woP/rPf4rOdX+OjiyH5bCcqCh5q7xV3jpxouhEtdWdqcGf6Cf66YsvaD2tp/WSRt8PRUWUTumUPmTI72+kBRBLo2+YmmMrJBWRFZEVkX5+6tEzZ0TnsudOA6Kn6Wl6essW2ydo0blqC7dot2i3aD8/XsgLeeGOHcyduTP3Bg1E5/pjxcW8O+/Ou48YYdulWnQigH9X51dhVVbvY72P9T7Wpo3DbofdDrsTE+kYHaNj2m0YxSN5JI+0WNgNdoPdeOUVxVfxVXxt9yuAjboar08fCqZgCk5MpHW0jtY1ayY61z1ur8KzTrFOsU4ZNSrLM8szy/PQIdGxAP5IvR+B3C17QPaA7AFXr5YlliWWJQ4cSEtpKS3VboMo24iEN+PNeLPPP5ej5Cg56uWXRefSClvrYR7BI3jEgQNaLRy2DwKUSZmUGRCAwgG1AUYgD9CL9+K9eMuWDtcdrjtcj49nvsyX+Xp4iM5l1+0+D6SQQsrMmerUR2Sk6Fg1zS3NLc0trWtXpmd6pk9N1ez2/7b/X2NoDI159VVlqDJUGbppk+hYAA8DI5AHUFe7FBZamluaW5oPHkycOHENN4hSSCGFMfUP07p18gB5gDxg5kzRsWqKuktuhw7SJemSdCkhQbOFw+ZZepaenT8fhQNqI4xAKkmdEmnRgnzJl3wPHaIUSqGUvn1F57If+PYnXH/yJ//ZsxWDYlAMGl6u+ojuXLuKcYhxiDl6lFIplVK7dxedyx52lp1lZ99801hqLDWWLl8uOg/Ao3AQHaC2yc/Pz8/Pv3WrzZg2Y9qM2bFDWiOtkdZ4erIv2Zfsyz//WXS+ewNTPuUzRiVUQiVDhjhzZ+7Mr11Tz0PDI6mHZCvoUrwUL8UnJNAlukSXtLvoQRURoVxWLiuXFy0SnQTgcaCAPKIrCVcSriSUl7dp3aZ1m9a7djn80+GfDv+0dRzs3Fl0vnvYCome9KQfMqR9m/Zt2rcpKsrPzc/Nz619rXbVLUeaNqXn6Dl67sAB+p6+p+81PBIcS2Np7ObNyjZlm7JtxgzRcQCqAq6BPKbTC04vOL3g5k310fDh6lSRhhtE3b5GorbaXbNGDpAD5IDa80m4R1SPqB5RDRvy2Xw2n71rFyVREiV5e4vOZVckRVJkTIxLtku2S/b06epBzkXHAqgKGIFUEXVKqLzcucS5xLlk1y4+j8/j8/r0YSfYCXZCuz2yWQErYAV+fs6hzqHOoRZL/t78vfl7jx0TnetuBoPBYDA4ONzYd2PfjX3bt7MMlsEytL7J5OHDRa2LWhe1Hjfu+AfHPzj+QXm56EQAVQkX0avJndanTcqalDWJjWWD2WA2WPsNo9T7Ed5+2+Rh8jB5aGVTQcZks2yWzRs3MgMzMMO0aaIT2cMX8UV8UVpahbnCXGEeNOj3I1SAugUFpJp1jega0TWiUaMW+hb6FvroaJpFs2jWiBGicz0I/4J/wb9YtcrkanI1ub7+uqgc8jn5nHzugw/YJDaJTZo7V/TrYg9fypfypdnZlmGWYZZhPj625d+icwFUJ1wDqWa5IbkhuSG3bpXkleSV5I0bx2fymXym9htEsalsKpu6cKF8Wj4tn37vvZr++bJJNsmm0FDNF46VfCVf+d13FTkVORU5gwejcEB9ghFIDbNdBG6yqsmqJqt27lSPjh4tOtcDJVESJYWFKS2VlkrL6mu0pC7LDQlRO0eGh4s+7fu7dEm9sdTb29a4THQigJqEEUgNO/fiuRfPvVhWpv7hefFF2yod0bkeyI/8yG/BAvUP/OrVVf306rLcKVPUZcYffST6dO3qR/2o348/sqVsKVvq44PCAfUZVmEJoq7aslr7F/Yv7F/49dfXrl27du1a167qv/bsKTqfXYwYMU/P9lPbT20/tUWL/NT81PzUw4cf9enuNMjqQ32oz7ZtbCPbyDZqsC8HJ068oEDyl/wlfz8/Y4AxwBjw3XeiYwGIpL03aj0THR0dHR1tsbi4uLi4uEyeTJtpM23etk10rgdh8Syexc+ZI+fKuXLuJ5/cPvrQU6K6Ql2hrtDfX9op7ZR2bt9+p8+JxvAMnsEzrl+3NrY2tjYeOjQzODM4M1jDHSwBahCugWiM7X6HC/0v9L/Q/4sv1Fa2kyaJzvVA0RRN0Rs2KC6Ki+Jiu9Paar37P9MX64v1xZ6efC6fy+cePqxuX/7EE6Lj342f5Cf5yZISJjGJSc8/rzCFKezIEdG5ALQEBUSjbIXEbDabzWbbLq1TpojO9UCcOPHPPlOvDQQGqgetVneDu8Hd0Lu3tbW1tbV1SgqdpJN0smVL0XH/mO2GvzFj1O3w4+JEJwLQIs1NGYDq3Llz586d41y9VrJ3r3OKc4pzSqdOFEMxFOPmJjqfXYwYMVluP6/9vPbzOnZ0/sX5F+dfvvvOusS6xLokMZF9zj5nn7duLTrmPQIpkAKtVlbMilnxpElKvBKvxO/eLToWgJZhBFKrSJK6Cmr9evUPtW1vJQ273aKVjGQkY8OGouPc4/Z293wYH8aHBQaaRplGmUZt3Cg6FkBtgAJSKzEmvyO/I78TEcH2sX1sX3Cw6ES1FTMzMzMvXGi8ZrxmvPb++6LzANQmmMKqpfKP5B/JP3LokLOzs7Ozc6tW6tE+fUTnql1CQxWzYlbM774rOglAbYRlvLUa5+pF3r//nQ7SQTqo4RvwtGI8jafxn3yivm5LloiOA1CbYQqrjtGV6kp1pcuXkxd5kVft6fNR7W7fX6P0VHoqPW2r2e5dZgwADw8jkDpGaaw0VhovXsw38o18I6ZmeByP43Gxsc2eafZMs2emTlWPonAAVAUUkDrKJJtkk/zWW/QqvUqvaqWvRw0KozAKS0pyaufUzqndhAlH2VF2lFVUiI4FUJeggNRxSpASpAS9/ba6jHbpUtF5qh0nTjwjo6SgpKCkYPRotXCUloqOBVAXoYDUE+pWHO+8QyEUQiHiGkRVrzNnHGMdYx1jn39e3fX4xg3RiQDqMizjrWfy9+Tvyd/zzTdPlj5Z+mRpSQm1olbUSvutdu3yIi/yys2VHCVHydHX9+T0k9NPTr96VXQsgPoAq7DqOfmYfEw+Nn8+m8PmsDlhYaLzPCy+mq/mq/PyLAMtAy0Dvb3VToAXL4rOBVCfYAqrnjMNMA0wDVi9mubSXJo7b57oPA/Ul/pS36tX2Xw2n83390fhABAHIxD4HV2sLlYXGxREcRRHcZ98QgoppDx8n4/qVVSkfvX1VW8ENJlEJwKoz3ANBH4nf0f+jvwdRmN73p635/n5LIgFsaBhw8QWkuJi3p13592HDTPFm+JN8RkZol8nAMAIBB5AjpVj5dhXX2VX2BV25dNPaT2tp/U10HL29i6+TMd0TDd6tPE142vG1w4eFP16AMD/QwGBh6I7qDuoOzhtGl2my3R5w4bqKiQ8kkfySItFHfn89a9qY6roaNHnDwD3QgGBSpG7yl3lrlOn0nbaTts3bqyqXua2wiEtk5ZJy6ZONe437jfu//JL0ecLAPbhGghUSv6v+b/m/5qd3b5N+zbt22RksLFsLBvbvz+lURqlVb5FLU/lqTz122+l36TfpN8MBuMc4xzjnL17RZ8nADwYlvHCIzGtMK0wrYiPbza+2fhm47t3V48uWcJX8pV85Xff2fu+3//7kiXXP7v+2fXPevUy9jT2NPY8elT0eQHAw8MUFlQL1zDXMNewJ55o2L1h94bd27UryynLKcu5cuX0gtMLTi+4eVN0PgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAO/4PSBxbMqgmA24AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTctMTItMTVUMTU6NTc6MjcrMDg6MDCiEb4vAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE3LTEyLTE1VDE1OjU3OjI3KzA4OjAw00wGkwAAAE10RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hZG1pbi9pY29uLWZvbnQvdG1wL2ljb25fY2sxYnphMHpqOWpqZGN4ci9jaGVjay5zdmfbTpDYAAAAAElFTkSuQmCC');\n  background-size: contain;\n  content: ' ';\n  inset: 0;\n}\n\n.icon-close::before {\n  position: absolute;\n  z-index: 9999;\n  display: block;\n  width: 16px;\n  height: 16px;\n  margin: auto;\n  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADIEAYAAAD9yHLdAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAAAAAAAA+UO7fwAAAAlwSFlzAAAASAAAAEgARslrPgAADwRJREFUeNrt3V1sU+cZwPHndTAjwZ0mbZPKR/hKm0GqtiJJGZ9CIvMCawJoUksvOpC2XjSi4kMECaa2SO0qFEEhgFCQSqWOVWqJEGJJuyYYWCG9QCIOhQvYlgGCIFmatrVSUhzixO8ujNM1gSZOfPye857/7wYlfPg5xj5/n/fExyIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABATizsWti1sCs/v6y0rLSsdMaMZ/Y8s+eZPZMnm54LQO6kn/fp/UB6v2B6LrdRpgcwZf7e+Xvn7505MxAIBAKBrVt1ja7RNdXVaqlaqpbOmTP0z+u9eq/ee/euFEqhFH7ySeCjwEeBj+rr299of6P9jb//3fT2AMhcWVlZWVnZ3Ln6uD6uj2/eLF3SJV1VVapW1ara6dOH/nn9hf5Cf3HzpupW3aq7qSl5LHkseay+/nLt5drLtbdvm96eXPNZQJQqn1Q+qXzS73+vN+gNesObb0q7tEv7xImZ/kv6kr6kL/X3q0PqkDpUXx/aFNoU2rRz53l1Xp1X/f2mtxTAcMv1cr1cT5jQfb37evf1ujrpkR7p2bxZ1agaVZOXl/E/WCM1UnP/vv5cf64/f+utjg87Puz4cPfu1G9qbXp7neaTgChVeqD0QOmBP/5RHVPH1LHf/CbrN1EplVLZ2iqt0iqtv/51NBqNRqP37pnecgDpI42CgtTz9OTJ1PO0sjLbt6PX6/V6/Z/+1LG5Y3PH5g0bHnzX2pBkXlyPKTtadrTs6Ouvq/fV++r9LVscu6EbckNuPPGEhCUs4UWLpsanxqfGT5yIxWKxWCyRMH0/AH40GI6whCXc3Cyn5bScDoeduj11RV1RV559dkrFlIopFX19sauxq7GrbW2m7wenBEwP4JT0OY7UV6+/nrMbjkhEIitWSIVUSEVLS0ljSWNJYyhk+v4A/GQwHHtkj+xpahp8XuaImqwmq8m7di2oXlC9oHr2bNP3h1OsDUhgfWB9YP2WLdIgDdLwgx/kfICzclbOLluW35Hfkd/x5z8PPqABOGbYEcd22S7bKypyPsiDc6v9df11/XWvvWb6fnGKtQHRj+nH9GOrV5ueY/CVz4MHNCEBsm9YOHJ8xPEo6og6oo64YD/k1PaZHiDbvruD/uYb0/MMUyEVUtHWFi+Pl8fLf/Wray9ee/Haiz09pscCvGjYUpWpI44RBE8FTwVPFRRcLLxYeLEwHjc9T7ZYdwSi2lSbavvxj03P8UgsbQHj5pqlqlFK9iZ7k70u3i+NkXUB6Tvcd7jv8H//a3qOEXGyHciY6ZPjYzXw0sBLAy95YL+UIeuWsNJK75feL71/545arBarxYWFpucZUVjCEj53LvWEqK7mfSTAt9x6jmNEi2WxLL59O3ooeih6aNYs0+Nkm3VHIIO6pEu6Pv3U9Bijxsl2YBjPhiOtUAql0EP7oQxZG5C8SXmT8ibt35++5IjpeUaNpS3As0tVabpBN+iGgQE5Lsfl+KFDpudxirUBuTT90vRL0//xj/S1qkzPkzFOtsOHvHZy/FFUsSpWxfv2pZai//Y30/M4xfpLmRR/VvxZ8Wd//Wvf7b7bfbd//vPBS454xU25KTdnz+YSKbCZ55eq0h5cE2/OB3M+mPPBb3977dq1a9eu2XstLGtPog+Vvp5/X1tfW19bU5N6V72r3v3FL0zPlTHeRwKLeOV9HCPaLbtl94UL8a/jX8e/fv55vzwvfROQNEICmEc47OC7gKQREiD3CIddfBuQNEICOI9w2Mn3AUkjJED2EQ67EZAhCAkwfoTDHwjIIxASIHOEw18IyAgICTAywuFPBGSUCAkwHOHwNwKSIUICEA6kEJAxIiTwI8KB/0dAxomQwA8IBx6GgGQJIYGNCAe+DwHJMkICGxAOjAYBcQghgRcRDmSCgDiMkMALCAfGgoDkCCGBGxEOjAcByTFCAjcgHMgGAmIIIYEJhAPZREAMIyTIBcIBJxAQlyAkcALhgJMIiMsQEmQD4UAuEBCXIiQYC8KBXCIgLkdIMBqEAyYQEI8gJHgYwgGTCIjHEBKIEA64AwHxKELiT4QDbkJAPI6Q+APhgBsREEsQEjsRDrgZAbEMIbED4YAXEBBLERJvIhzwEgJiOULiDYQDXkRAfIKQuBPhgJcREJ8hJO5AOGADAuJThMQMwgGbEBCfIyS5QThgIwICESEkTiEcsBkBwXcQkuwgHPADAoKHIiRjQzjgJwQE34uQjA7hgB8REIwKIXk4wgE/IyDICCFJIRwAAcEY+TUkhAP4FgHBuPglJIQDGI6AICtsDUl+XX5dfl0ySTiA4QgIsmrwlXpYwhJubpaIRCSyYoXpuTIWlrCEz50b/Nrr2xGRiESqq6PRaDQavXfP9FiwAwGBI6w5IvEqjjiQAwQEjiIkOUY4kEMEBDlBSBxGOGAAAUFOEZIsIxwwiIDACEIyToQDLkBAYBQhyRDhgIsQELgCIRkB4YALERC4CiEZgnDAxQgIXMn3ISEc8AACAlfzXUgIBzyEgMATrA8J4YAHERB4inUhIRzwsIDpAYBMJNYm1ibWKqUeV4+rx5X3XwCdkTNyxoLtgC/xwIUnWPN5HI/i8Ge2A04gIHA168MxFCGBhxAQuJLvwjEUIYEHEBC4iu/DMRQhgYsRELgC4RgBIYELERAYRTgyREjgIgQERhCOcSIkcAECgpwiHFlGSGAQAUFOEA6HERIYQEDgKMKRY4QEOURA4AjCYRghQQ7kmR4AdhkMR1jCEm5uliNyRI54MBxhCUv43DkpkiIpunVLbspNuTl7tumxRu2W3JJbM2cGC4IFwYKFC6fGp8anxk+ciMVisVgskTA9HuzAxRSRFcOOOCISkciKFabnylj66ril8dJ46Zo1wY3BjcGNVVV6m96mt505Y3q8jKX/HyqkQipaWkoaSxpLGkMh02PBDixhYVysWaoa4bLq1lxGnqUtZBEBwZj4JRxDERLgWwQEGfFrOIYiJAABwSgRjocjJPAzAoLvRThGh5DAjwgIHopwjA0hgZ8QEHwH4cgOQgI/ICAQEcLhFEICmxEQnyMcuUFIYCMC4lOEwwxCApsQEJ8hHO5ASGADAuIThMOdCAm8jIBYjnB4AyGBFxEQSxEObyIk8BICYhnCYQdCAi8gIJYgHHYiJHAzAuJxhMMfCAnciIB4FOHwJ0ICNyEgHkM4IEJI4A4ExCMIBx6GkMAkAuJyhAOjQUhgAgFxKcKBsSAkyCUC4jKEA9lASJALBMQlCAecQEjgJAJiGOFALhASOIGAGEI4YAIhQTYRkBwjHHADQoJsICA5QjjgRoQE4xEwPYDtbAtH4kriSuIKT1BbXCy8WHixMB6fuGzisonLVq/W2/Q2ve3MGdNzZeysnJWzy5blt+e357f/5S8ljSWNJY2hkOmxbMcRiENsDcfV7Ve3X93+zTemx4IzOCJBJghIlhEO2ICQYDQISJYQDtiIkOD7EJBxIhzwA0KChyEgY0Q44EeEBP+PgGSIcACEBCkEZJQIBzAcIfE3AjICwgGMjJD4EwF5BMIBZI6Q+AsBGYJwAONHSPyBgDxAOIDsIyR2831ACAfgPEJiJ98GhHAAuUdI7OK7gBAOwDxCYgffBIRwAO5DSLzN+oAs18v1cj1hQk95T3lP+aefpr77y1+anitje2SP7Dl7NhW+1auj0Wg0Gr13z/RYQDYMvsALS1jCzc0SkYhEVqwwPVfGKqVSKltbQ++E3gm9U1V1Xp1X51V/v+mxnGL9B0p1X+++3n29ri71FeEA3GjwcR2RiESqq1MhOXfO9FwZa5VWaa2s7DnYc7Dn4O7dpsdxmrUBKX+7/O3yt3/2M5krc2Xupk2m58lYeqkqmogmomvWEA74QfpxHtwY3BjcWFXl1U9I1Iv0Ir1o69b53fO753fPm2d6HqdYG5BkXjIvmbd1q3pOPaeemzDB9Dyjlj7i2Ck7ZeeqVZzjgB+lP2o3dU5kzRqvHZGoGlWjavLyAg2BhkDDa6+Znscp1gZEzVQz1cyqKtNzjBpLVcAwnl/aOi7H5biH9kMZsi4gCzoXdC7o/OEPZZ/sk33TppmeZ0QsVQEj8vbS1owZJY0ljSWNoZDpSbLNuoAMrBtYN7DuRz8yPceIWKoCMubVpa3Q/ND80HwP7JcyZF1ARIkS9e9/mx7jkTjiAMbNa0ckgUmBSYFJ//mP6Tmyzdr3gZTGS+Ol8Rs31FK1VC2dM8f0POkjjuCTwSeDT1ZXp19JmR4LsIFr30eyQ3bIjs7O6AvRF6IvFBebHifb7DsCeUA1qAbV0Nxseg7CATjPrSfb9VP6Kf2UC/ZDDrE2IMlkMplM7t8vNVIjNffv53yAIUtVhANwnluWtvRhfVgf7u1VL6uX1csHDpi+X5xibUAu116uvVx7+3bqqz/8IWc3nD7imBecF5y3ciUnx4HcM36yPSlJSb71VrQj2hHtuHPH9P3hlDzTAzgt1hRrijW1tU3ZMWXHlB1z5qgr6oq68uyzWb+h/bJf9re0BIuCRcGitWs54gDMi8VisVgskZganxqfGj9xInWtqvJyuSE35MYTT2T79vRJfVKfPHas4+mOpzuerq01vf1Osz4gabGWWEus5dSpaV9N+2raV4mE7JJdsmvJEmmXdmnP/J3q+pK+pC/190undErn3r1FkaJIUeR3vzv9yulXTr/S12d6ewF8Kx2S4gvFF4ovfPxxX29fb19vQYE+qo/qowsWqPfUe+q9QMYrMumlKlklq2TVm29+Nxxam95up1n7U1gjKSstKy0rnTFDr9Qr9cotW1SLalEtq1enfgy4qOjhf+vOHVkn62TdJ58M3B24O3C3vv7Lg18e/PJgZ6fp7QGQufQ18/QpfUqf2rw59d3nn0/9OmPGsL+wRJbIkn/+U7+qX9WvNjUFZgVmBWbV17cXtBe0F3R1md6eXPNtQB4l/fkEiTWJNYk1P/1p+n0lvF8D8I/BHwvWokX/5CehaCgaiv7rX6nLs/f2mp4PAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtvsf2vlfs7i0WI4AAAAldEVYdGRhdGU6Y3JlYXRlADIwMTctMTItMTVUMTU6NTc6MjcrMDg6MDCiEb4vAAAAJXRFWHRkYXRlOm1vZGlmeQAyMDE3LTEyLTE1VDE1OjU3OjI3KzA4OjAw00wGkwAAAE10RVh0c3ZnOmJhc2UtdXJpAGZpbGU6Ly8vaG9tZS9hZG1pbi9pY29uLWZvbnQvdG1wL2ljb25fY2sxYnphMHpqOWpqZGN4ci9jbG9zZS5zdmdHkn2WAAAAAElFTkSuQmCC');\n  background-size: contain;\n  content: ' ';\n  inset: 0;\n}\n\n.icon-right::before {\n  position: absolute;\n  z-index: 9999;\n  display: block;\n  width: 16px;\n  height: 16px;\n  margin: auto;\n  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADIEAYAAAD9yHLdAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAAAAAAAA+UO7fwAAAAlwSFlzAAAASAAAAEgARslrPgAAJ4pJREFUeNrt3XtcVXW6P/Dn2VwCBxUzNbnkkXRSGzXW2huQRLyMIqKRJF7Q1CkrDS+VGp3Gy9g5YzI6qVsNfTmlqGmipQiIiJqAcnOvhaKRHidshoatpKaBogL7OX+s6Mz8flO5CfzutXne/+zXWhR8QOXZ3+93Pd8vAHuAEKW10lpp7dix0mXpsnR5/34pX8qX8r/7TpZlWZaJGl//9f6+fY3/X+PnEf2dMMYY/yJqYcbbxtvG2/7+lEM5lLN7NyyCRbBowICmfj56m96mt/PzDZGGSEPkxImWNpY2ljYVFaK/T8ZY6+MiOoCzMn1t+tr09a9/TQfpIB0sLITlsByW9+r1Sz8v5mEe5vn7Q3toD+0nT/Y77Xfa73ROTuWNyhuVNyorRX/fjLHWg0cgzUybcmrThvIoj/JUFcMwDMOeeKLFvmA8xEN8TQ2sh/Ww/rnnFFVRFfXwYdE/B8aY8zOIDuBsqDf1pt6vvdbihaPRBtgAG7y8wAQmMKWlyflyvpw/aZLonwNjzPlxAWlWiOiN3ugdH//Av7QFLGBxd4dzcA7O7dgh75H3yHvmzBH9E2GMOS+ewmomplhTrCn2qads5bZyW3lJieg8jWgADaABf/yjul5dr65fvPj7uyQ6F2NM/3gE0kxsb9vetr3do4foHP8vLMACLPj977W1mS1bwimcwsnVVXQuxpj+cQFpLt/Ct/BtmzaiY/y0adNqltYsrVmakqIVEg8P0YkYY/rFj/E2E5+zPmd9znbpAggI+PzzovP8qItwES727n23w90OdzuEhfl86fOlz5f79lmtVqvVeveu6HiMMf3gEUgzqVfqlXqluFi7qqsTnefnYCImYmJ4OOVSLuWeONF/Zv+Z/Wf6+orOxRjTD15Eb2ZSlVQlVWVkYCRGYuSoUaLz3C86QSfoRHk5lVAJlURElISWhJaE/vWvonMxxhwXj0CaGT1Lz9KzS5eCDDLI+nnaCQfiQBwYEID1WI/1J05oi+6SJDoXY8xx8RpIM7tccbnickVlZdekrkldk4gwAzMwY8gQ0bnuF2ZhFmZ5eWkd7pMn+1T4VPhUKIq2RvLll6LzMcYcB09htShE6YJ0Qbqwdi3GYRzG6bCxbxbMgll372ojqilTlEAlUAncu1d0LMaYeDyF1aKI1CfUJ9Qn5s6FuTAX5r71lt6mtiAJkiDpoYeojuqo7uOP5VQ5VU6dOVN0LMaYeDwCecCkFClFSpk+HcbBOBi3eTOa0IQm/TX20RbaQlsSE9V+aj+131tvic7DGHvwuIAIIp+Xz8vno6OpJ/Wknrt2YRAGYZCnp+hcdpsAE2DC++8rbypvKm82TtHZbKJjMcZaHhcQwYxnjWeNZ8PDaTpNp+mpqdrd9u1F52qa/fu9LF4WL8ukSTmYgzl4547oRIyxlsNrIIJZ+lr6Wvrm5GBv7I29Bw6EN+ANeOMf/xCdq2mefbbGWGOsMR48GHQx6GLQxXbtRCdijLUcfozXQVSWVpZWllZV+df51/nX7dtH8RRP8aNGwQk4ASc6dhSdzz7du9NVukpXR4zoFNMpplPM/v1Xsq9kX8m+dUt0MsZY8+ERiIMpTitOK067dMm1zrXOtS4sTLurqqJz2e04HIfjsuw623W26+yCgsD8wPzAfMfbrZgx1nRcQBxUUVxRXFHclSu1CbUJtQnh4dpd/R1V+0OHuxGNaMzLazw3RXQuxtgvx4voOtEnpU9KnxR3d88yzzLPsu3bIQ3SIG38eNG57BYEQRB04wZVUzVVP/OMukPdoe7IyxMdizFmPx6B6ETZ+LLxZePv3Qv4PODzgM/j4mg37abdGzeKzmW3YiiGYm9vHIyDcXB2tlwil8gl48aJjsUYsx+PQHROTpaT5eSEBDCDGcwrVojOYy9KoiRKamgAK1jBOmuWGq1Gq9GbN4vOxRj7eVxAnISUKWVKma++ihVYgRXr1sEm2ASbDPoZYTZu8bIJNsGmd95RUEEF//AH0bEYYz9OP79g2E9SI9VINfL996mWaql23DjaQBtog44a+RRQQEEEIxjBuHSptgml2ax9UEeFkLFWhEcgTko7z2PIECqiIiravx+DMRiD9drYt3MnEBDQ9OmKqqiK6vgnPjLWGvA7OyelKIqiKJ99pj31NHQovAavwWtVVaJzNU1cHKyCVbAqM/Ppjk93fLpj27aiEzHGeATSahjTjenG9IAAOkyH6XBWFpyEk3BSf419tISW0JJTp2wdbB1sHaKiTg86Pej0oG++EZ2LsdaIC0grozXyPfpow7SGaQ3TMjNxKS7FpTps7CMgoPPntYuICG1q6+9/Fx2LsdaEC0gr1Z/6U3/y9nZNcE1wTThwAI7CUTjauHWK3litVEEVVDFypFqlVqlVpaWiEzHWGvAaSCt1Bs/gGbxx46bfTb+bfsOHUxqlUZpej6rt2hVX4kpcefy4sYOxg7HD00+LTsRYa8AjEAYAALGxsbGxsS4u5XK5XC4nJcEe2AN7XnpJdK6muX1bex0/XnuYICNDdCLGnBEXEPZvIMokk0xLlzb2ZYhOZK/GDne8htfw2iuvKJFKpBL5wQeiczHmTPg8EPZvWZdZl1mXHT/uY/Yx+5ivX4cn4Ul4MiLih4Y/B4cZmIEZBgPchJtwc8wY33Lfct/y2trKO5V3Ku+cPCk6H2POwOF/ETDHoDUmxsVpV1u3aq9ubqJzNY3ZrE1tvf66ds1nuDPWFFxAmF0C9wTuCdwzbBj6oi/67tuHc3AOztFfYx9Npak0dft2TMZkTH7xRe5wZ8x+XEBYk5i6m7qbuptMtlG2UbZRGRlQCIVQ2KmT6Fx2i4RIiExPh0zIhMwJE7SRSeMiPGPsp3ABYb+INrXVq5d2lZWlvT72mOhcdiMgoKIi7WL0aG1EcvWq6FiMOTLuA2G/iPaOvbEjPCQEBsEgGKTDRj4EBAwOhkWwCBbl5BhvG28bb/v7i47FmCPjEQhrVn379u3bt2+HDm55bnlueWlpOAyH4TAdNvaFQiiE/u1v2Bk7Y+eICMtiy2LL4gsXRMdizJHwY7ysWVVVVVVVVd2545Ptk+2T/fHH2t3GvbZ+/WvR+e5bBVRAhbc3zaJZNCsu7lG3R90edcvLu6xcVi4rX38tOh5jjoCnsFiLaFyMDggICAgIiI6mPbSH9uivkQ/n4Tyc9/DDBjSgAbOzA/MD8wPzR44UnYsxR8BTWOwBQpTmS/Ol+StW4HE8jsfffFN0IrsZwQjGe/dgGkyDadOnK6FKqBK6a5foWIyJwFNY7IGyFlgLrAVHjnTd3nV71+03buDj+Dg+PmKEXjrcoRIqodLFBaqgCqpiYnzAB3ygpsZqtVqt1oIC0fEYe5C4gDAhrNus26zbiop8yZd86dIlqIEaqBk9Wvuoi+P/vbSCFayNBW/EiK5ZXbO6Znl6WpOsSdako0dFx2PsQXD8d3ysVZCWS8ul5aNH4yf4CX6ye7d2t00b0bmaJjnZy+Jl8bLMmJGDOZiD9fWiEzHWEriAMIciS7IkS8HB2lV6utaf8cgjonPZi+IpnuIPHHAf7j7cffjEiYX+hf6F/rW1onMx1pz4KSzmULQO8KKihjUNaxrWhIdTPuVTfkWF6Fz2wg24ATc888y9gnsF9woyM7XC2L696FyMNScuIMwhnR50etDpQWVltI7W0bqwMMqjPMrTXyMfJmIiJoaHUy7lUu6JE/1n9p/Zf6avr+hcjDUHnsJiuhBSEVIRUvHww3Xn6s7VnUtP17YcGTBAdC57USIlUuKlS7YDtgO2AxERp82nzafNFy+KzsVYU/AIhOmCtoZw/bpWQIYPh9WwGlYfOiQ6l70wARMwoXt3wzjDOMO4vDxtM0pJEp2LsabgAsJ0pXRh6cLShbdu1V6uvVx7OTqaUimVUvXXyIev4+v4epcuEA/xEJ+To62RjBghOhdj9uApLOYEELVfwCtXak9tzZ8vOpHdvu9wJ5lkkp9/Xn1ZfVl9OSVFdCzGforjN2wxdh+0TvDDh31W+KzwWXHnDtRDPdQPG/avDX8OrLHDfQbMgBkxMT6jfUb7jK6qsn5s/dj6scUiOh5j/47j/8NirAm0tYVp0+gUnaJTf/kLmtCEJldX0bnsRVtoC21JTFT7qf3Ufm+9JToPY/+MCwhzavJ5+bx8PjqaelJP6rlrFwZhEAZ5eorOZbcJMAEmvP++8qbypvLmnDnaTZtNdCzWunEBYa2C8azxrPFseDhNp+k0PTVVu6vDxr4oiIKoffu8lnkt81oWF6dtlXLnjuhYrHXip7BYq2Dpa+lr6ZuTg72xN/YeOBDegDfgjX/8Q3Quu2VABmSMHVtjrDHWGA8eDLoYdDHoYrt2omOx1okX0VmrUllaWVpZWlXlX+df51+3b5+2Z9WoUXACTsCJjh1F57NP9+50la7S1REjOsV0iukUs3//lewr2Veyb90SnYy1DjwCYa1ScVpxWnHapUuuda51rnVhYdpdVRWdy27H4Tgcl2XX2a6zXWcXFGgnJvboIToWax24gLBWrSiuKK4o7sqV2oTahNqE8HDt7uHDonPZCwfiQBwYEIBGNKIxL88Ua4o1xTaeRc9Yy+BFdMb+SZ+UPil9UtzdPcs8yzzLtm+HNEiDtPHjReeyWxAEQdCNG1RN1VT9zDPqDnWHuiMvT3Qs5lx4BMLYPykbXza+bPy9ewGfB3we8HlcHO2m3bR740bRuexWDMVQ7O2Ng3EwDs7OlkvkErlk3DjRsZhz4REIY/dBTpaT5eSEBDCDGcwrVojOYy9KoiRKamjQOvNnzVKj1Wg1evNm0bmYvnEBYcwOUqaUKWW++ipWYAVWrFsHm2ATbDLoZyQvgwwykZb7nXcUVFDBP/xBdCymT/r5i8+YA1Aj1Ug18v33qZZqqXbcONpAG2iDjhr5FFBAQdQ2b1y6VLogXZAumM3aB3VUCJlD4BEIY7+AtufWkCFUREVUtH8/BmMwBuu1sW/nTiAgoOnTtaOF6+pEJ2KOjd9xMPYLKIqiKMpnn2lPPQ0dCq/Ba/BaVZXoXE0TFwerYBWsysx8uuPTHZ/u2Lat6ETMsfEIhLFmZEw3phvTAwLoMB2mw1lZcBJOwkn9NfbRElpCS06dsnWwdbB1iIrSzqj/5hvRuZhj4QLCWAvQGvkefbRhWsO0hmmZmbgUl+JSHTb2ERDQ+fPaRUSENrX197+LjsUcAxcQxlpQf+pP/cnb2zXBNcE14cABOApH4Wjj1il6Y7VSBVVQxciRapVapVaVlopOxMTiNRDGWtAZPINn8MaNm343/W76DR9OaZRGaXv3is7VNF274kpciSuPHzd2MHYwdnj6adGJmFg8AmHsAYqNjY2NjXVxKZfL5XI5KQn2wB7Y89JLonM1ze3b2uv48drDBBkZohOxB4sLCGPCIMokk0xLlzb2ZYhOZK/GDne8htfw2iuvKJFKpBL5wQeic7EHg88DYUwg6zLrMuuy48d9zD5mH/P16/AkPAlPRkT80PDn4DADMzDDYICbcBNujhnjW+5b7lteW1t5p/JO5Z2TJ0XnYy3L4f+CMtaaaI2JcXHa1dat2qubm+hcTWM2a1Nbr7+uXfMZ7s6GCwhjDihwT+CewD3DhqEv+qLvvn04B+fgHP019tFUmkpTt2/HZEzG5Bdf5A5358IFhDEHZupu6m7qbjLZRtlG2UZlZEAhFEJhp06ic9ktEiIhMj0dMiETMidM0EYmjYvwTK+4gDCmA9rUVq9e2lVWlvb62GOic9mNgICKigwHDAcMB6KiTvmd8jvld+2a6FisabgPhDEd0N6xN3aEh4TAIBgEg3TYyIeAgMHBtmJbsa04NzfoYtDFoIt+fqJjsabhEQhjOtS3b9++fft26OCW55bnlpeWhsNwGA7TYWNfKIRC6N/+hp2xM3aOiLAstiy2LL5wQXQsdn/4MV7GdKiqqqqqqurOHZ9sn2yf7I8/1u427rX161+LznffKqACKry9aRbNollxcY+6Per2qFte3mXlsnJZ+fpr0fHYT+MpLMZ0rHExOiAgICAgIDqa9tAe2qO/Rj6ch/Nw3sMPG9CABszODswPzA/MHzlSdC7203gKizGngyjNl+ZL81eswON4HI+/+aboRHYzghGM9+7hLbyFt6ZNs+yw7LDsaBxpMUfBU1iMOSFrgbXAWnDkSNftXbd33X7jBj6Oj+PjI0bopcMdKqESKl1coBt0g27PPecDPuADNTVWq9VqtRYUiI7HNFxAGHNi1m3WbdZtRUW+5Eu+dOkS1EAN1IwerX3UxfH//VvBCtbGgjdiRNesrlldszw9rUnWJGvS0aOi47V2jv9OhDHWbKTl0nJp+ejR+Al+gp/s3q3dbdNGdC67xUAMxGzd6vW219teb7/0Ug7mYA7W14uO1dpwAWGsFZIlWZKl4GDtKj1d68945BHRuexFGZRBGamp7nXude51kyYV+hf6F/rX1orO1VrwU1iMtULanlRFRQ1rGtY0rAkPp3zKp/yKCtG57IVRGIVR0dH3Cu4V3CvIzNQKY/v2onO1FlxAGGvFTg86Pej0oLIyWkfraF1YGOVRHuXpr5EPEzERE8PDKZdyKffEif4z+8/sP9PXV3QuZ8dTWIyxHzyV+1TuU7mdOhm+NXxr+DYjA9/Bd/Adk0l0LnvRCTpBJ8rLaRgNo2FhYSX5Jfkl+ZWVonM5Gx6BMMZ+oI1Ivvnmzt07d+/cHTpUu3v4sOhc9sKBOBAHBgQYFhsWGxbv3dsnpU9KnxR3d9G5nA2PQBhjP6rxF69HqEeoR+jWrRiN0Rg9aZLoXE3z6qta535SkugkzoILCGPsPhkM0gXpgnRhzRqMwziMmzNHdKL7thAWwsKvvlImKhOVid27i47jLLiAMMbsJifLyXJyQgKchJNw8t139dLhjs/is/hsr16862/z4DUQxpjdlGnKNGVaYiJFURRFvfIKJVESJTU0iM71s76Bb+Cb3/xGdAxnwQWEMdZkarQarUZv3ky9qTf1Hj8eXoFX4BWbTXSuH0PP0rP07K9+JTqHs+ACwhhrstjY2NjYWBcX3ISbcFNUFGyCTbDJ4Li/VxbCQljIW540F8f9g2aMOSztjPY2bb7c8OWGLzccOIC7cBfueuEF0bl+ViqkQuqNG6JjOAtX0QEYY/rReJQuHaWjdFRHR+nKIINMVLerblfdLotFdBxnwQWEMfazgi4GXQy66OfXcLbhbMPZrCwYBsNgWJ8+onPdL/oT/Yn+lJ9f6l3qXepdVSU6j7PgAsIY+1HaVFWvXg0TGyY2TMzK0u4+9pjoXPYypBhSDCl//KPoHM6G10AYY/8fU3dTd1N3kwlCIARCcnO1u/orHPQcPUfPbdpkednysuXlzEzReZwNj0AYYz+Q3pbelt6OiLBdt123Xf/kEyiEQijU32OvFE/xFH/gwHc139V8VzNvnug8zsrhO0cZYy1Pm6qKi9Outm7VXt3cROeyF31Kn9Kn27bhWByLY2fM0M49qasTnctZ8RQWY62Ysaexp7Hn7NlaA+D27dpd/RUOjdmsdlO7qd2mT+fC8WC4iA7AGHvwftjL6jSchtPvvaeXvaz+7xvQHssld3In94QE9Zh6TD22eLHoWK0Nj0AYawUaO8blcrlcLt+0CcxgBvOKFaJz2YtO0Sk6VV+P5/E8np8xQ/1U/VT9dOVK0blaK/2842CM2a2HuYe5h/mhh9pvbb+1/dbt2wEBAWNjRedqmtu3tU7y2FjFT/FT/A4eFJ2oteMRCGNOSDsIysurXVy7uHZxaWm6LRxzYS7M/fZbLMdyLB8xgguHY+ERCGNOJHhn8M7gnV261I2pG1M3JjMTB+NgHBwYKDqX3QbAABhQWQn5kA/5I0dqi+Jnz4qOxf4Vj0AYcwJBY4LGBI3p3r3erd6t3i0vT7eFIwzCIOyLL7TCMWAAFw7HxiMQxnTMOMU4xTjlN78hb/Im76wsKIACKPDxEZ3LXrSEltCSU6dwGS7DZaNGaYXj6lXRudhP4050xnRIJplkGjyYjGQk4/792t327UXnshfNp/k0/8gRzxc8X/B8ISbm5LWT105eq64WnYvdH57CYkxH5PPyefl8dDQVUREVNe7tpL/CAdEQDdEffYSrcBWuGjWKC4c+8RQWYzogpUgpUsr06TAOxsG4zZvRhCY0uep0BsFsVhRFUZTXX9euHfcIXPbTuIAw5sB+6BjXaeNfY8e4dtTtO+8oqKCCf/iD6Fiseej0HQxjzgxRKpPKpLJVq+B5eB6ef+MN0YnsRUmUREkNDWAFK1hnzVJRRRU3bxadizUvHoEw5gC0xj93d4+rHlc9riYn4wf4AX4wcaLoXHabBbNg1t27WIqlWDp5ssVsMVvMn3wiOhZrGVxAGBOo38p+K/ut/NWv3ILdgt2C9+6F1+F1eH3kSNG57BYEQRB04wZVUzVVP/OMukPdoe7IyxMdi7UsLiCMCRBSEVIRUvHww3Xn6s7VnUtPh0WwCBYNGCA6V9NYrbZSW6mtNDKypK6krqTuzBnRidiDwY/xMvYABa4KXBW4qlu3ex3vdbzXMT9fr4WDTtAJOlFerl2FhXHhaJ14EZ2xB+Cp3Kdyn8rt0weDMRiDDx3CUAzFUH9/0bnstgyWwTKLpX59/fr69VFRpUqpUqpUVYmOxcTgKSzGWpAsyZIsBQdrV+np2q64jzwiOpfdhsNwGH7smMuLLi+6vDh2bHHP4p7FPb/7TnQsJhZPYTHWAqTl0nJp+ejRWsE4dky3hSMKoiBq3z6vd73e9Xo3KooLB/tnPAJhrBlJnaXOUucpU9Af/dH/ww+1uzo8YzwVUiF1wwbt/I25c7Wb3DHO/hWfic5YM5COSEekI/PmYSAGYuDGjdoZ4/rbaoS20Bbakpio9lR7qj0bGxiJROdijkl3f8EZcxyIUqlUKpW++y7+Dn+Hv0tIEJ3IXo0d42hFK1pnz1b7qf3Ufhs3is7F9IGnsBizQ2xsbGxsrItL+ZflX5Z/uXGjtrYxY4boXHb7vmOcbGQj29Sp6svqy+rLKSmiYzF94QLC2H3oYe5h7mF+6KH2Ie1D2ofs3Kn9Ao6JEZ3LbvEQD/E1NRADMRATE6N4K96Kd3a26FhMn7iAMPYT+lN/6k/e3q5GV6OrMS1NuztwoOhc9qLVtJpWX7liWGRYZFgUGWnJteRacktKROdi+sZrIIz9G7Isy7LctSscgANwoPHgpv79ReeyFyVSIiVeumTba9tr2xsRoeaquWruxYuiczHnwCMQxv6JVjgefxwICCgrS1vjePxx0bnsRTmUQznnzjUsaFjQsGDkyDMbz2w8s/Ef/xCdizkXbiRkDAACQwNDA0ONRgiBEAgpKNBt4UigBErIycFBOAgHDRzIhYO1JB6BsFZNmi3NlmYPHQprYA2s2bdP26uqXTvRuexFGZRBGamp7nXude51kyYV+hf6F/rX1orOxZwbr4GwVklaK62V1o4dC8EQDME7d2qFw8NDdC67xUAMxGzd2rZL2y5tu7z0Ug7mYA7W14uOxVoHHoGwVkUaJA2SBsXH4xScglPMZu2sboPupnJ/6Bjvp/ZT+731lug8rHXiAsJaBTlZTpaTExLADGYwr1ghOo/93wDIIBNBOIRD+IIFymRlsjL5vfdEx2Ktm+7eeTF2Pxo7xqW/Sn+V/pqUpNvCYQQjGO/dw9t4G2/HxXHhYI6ERyDMqfzQMX69/fX217dtgzRIg7Tx40XnspsJTGC6dcs21TbVNnXcuJLQktCS0EOHRMdi7J/xCIQ5hT4pfVL6pHh5tYtrF9cuLi1Nr4WD1tJaWnv9uo1sZKPhw7lwMEfGIxCma8E7g3cG7+zSpf7P9X+u//PBg9pdSRKdy26hEAqhf/sbdsbO2DkiwrLYstiy+MIF0bEY+yn8GC/TpaAxQWOCxnTvXu9W71bvlpWl3e3ZU3Quu/0efg+/Lytz6evS16VvRIR24t/XX4uOxdj94ALCdMU4xTjFOOU3v2mIbIhsiDx0CFbACljh6ys6l90ICKioyBBkCDIERUUV+xX7FftduyY6FmP24CkspgvGs8azxrPh4TSdptP01FTtbvv2onPZbR2sg3VpaW7+bv5u/hMmcMc40zNeRGcOzfhfxv8y/tczz9j62PrY+jTuiqu/wkGf0qf06bZtMBtmw+znnuPCwZwBj0CYQ9J2xZ02jU7RKTr1l7+gCU1o0t8Z4xqzWVEURVFee0275jPGmXPgEQhzKD90jMsgg7xli+4Kx/cd49SNulG3N9/UCse8edoHuXAw58IjEOYAEOW18lp57Z/+BNtgG2xbsEB0IntpI6X6esNgw2DD4Fde0U78+/BD0bkYa0n6eWfHnIrW+Ofu7hHqEeoRunUrREM0RE+aJDpX09y+jZVYiZWxsVrhaOxHYcy58RQWe6D6rey3st/KX/3K447HHY87+/djNEajHgvHXJgLc7/9FsuxHMtHjFD8FD/FjwsHa11cRAdgrUNIRUhFSMXDD9Ntuk23MzNxFa7CVUOHis5ltwEwAAZUVsJe2At7f/tb5ZJySblksYiOxZgIvAbCWpR2VKyPj+Gu4a7hbuOeTn37is5ltzAIg7AvvoBcyIXckSMVVVEV9e9/Fx2LMZF4Cou1iMDqwOrA6t698TP8DD8rLNTu6q9w0BJaQktOndIKx6BBXDgY+z88AmHNytjT2NPYMyiI2lJbapuRAQgI+MgjonPZbSWshJVHj3rEesR6xI4de/LayWsnr1VXi47FmCPhEQhrFsZ0Y7ox/be/tSXbkm3JR47otnBEQzREf/QRLIAFsCAykgsHYz+ORyDsF5E6S52lzlOmoD/6o39j34Obm+hc9qKdtJN2rlunPqE+oT7R2DFus4nOxZgj4xEIaxJZkiVZmjsX/xv/G/87OVm7q6PC0XjGuAUsYFm2TCscc+dqH+TCwdj94BEIswOiTDLJtHSpdlb30qWiE9mLkiiJkhoawApWsM6apUar0Wr05s2iczGmR1xA2E+KjY2NjY11cSmXy+VyOSkJ9sAe2PPSS6Jz2W0WzIJZd+9iKZZi6eTJFrPFbDF/8onoWIzpGRcQ9m/1MPcw9zA/9FA7j3Ye7Tw++gg34Sbc9NxzonPZLQiCIOjGDaqmaqp+5hl1h7pD3ZGXJzoWY86A10DYv+hP/ak/eXu3/7r91+2/zs7Wa+GgAiqggsuXDVcNVw1XhwzhwsFY8+OtTBgAAJhiTbGm2EcfhTbQBtpkZ+OH+CF+GBwsOpe96ASdoBPl5aSSSurQocp8Zb4yv6xMdC7GnBEXkFZO698ICKAqqqKqY8dwG27DbX36iM5lt8EwGAYrSn1ZfVl92dChZyaemXhmYkWF6FiMOTPezr2VkiRJkiRZpm/pW/r24EE4CSfhZOfOonM1zWefucx0meky89lnlZ5KT6Xnd9+JTsRYa8BrIK2MdlTskCFQDMVQfOwYrIE1sEaHhSMKoiBq3z4vi5fFyzJqVHHP4p7FXDgYe6D4KaxWQlorrZXWjh0LwRAMwTt3YjzGY7yHh+hcdkuFVEjdsEE7f4Mb/xgTiUcgTk7KlDKlzFdfRU/0RM+9e/VaOGgLbaEtiYla4Zg9W7vLhYMxkXgNxEnJyXKynJyQAItgESxasUJ0Hns1doyjFa1onT1b7af2U/tt3Cg6F2Ps//BTWE6isWPc44DHAY8D77+PC3EhLnzrLdG57PZ9x7i21ciUKepkdbI6uXGvLcaYI+E1EJ3rk9InpU+Ku7tnmWeZZ9n27ZAGaZA2frzoXHaLh3iIr6mBGIiBmJgYxVvxVryzs0XHYoz9OC4gOqUVDi8vz0TPRM/Exj2dRowQnctetJpW0+orVwyLDIsMiyIjLbmWXEtuSYnoXIyxn8drIDoTvDN4Z/DOLl3qE+sT6xMPHtTuSpLoXPaiREqkxEuXbHtte217IyLUXDVXzb14UXQuxtj946ewdELbo+o//qPukbpH6h7JzdXu6rBw5FAO5Zw717C3YW/D3rCw0+bT5tNmLhyM6RFPYTk403rTetP6J5+0dbB1sHXIyoL34D14z9dXdC57UQIlUEJODq7AFbgiOlpRFVVRb94UnYsx1nRcQByUNFIaKY0MCdEWxdPTMQRDMKRjR9G57EUZlEEZqanude517nWTJhX6F/oX+tfWis7FGPvleA3EwQSWB5YHlo8ZA92gG3TbvRuDMAiDPD1F57JbDMRAzNatbbu07dK2y0sv5WAO5mB9vehYjLHmwyMQByEfk4/Jx6ZOpcE0mAZ/8AGa0IQmV90V+MaOca3xT4d9KIyx+8YFRDDpiHREOjJvHqZgCqasXg0KKKCgfv5cZJBBJoJwCIfwBQuUycpkZfJ774mOxRhrebp7h+scEOUb8g35RmIiDINhMGzhQtGJ7GYEIxjv3cNbeAtvTZtmmWyZbJn88ceiYzHGHhwuIA9IOIVTOLm6Vv+5+s/Vf960SSscL7wgOpfdTGAC061btqm2qbap48aVhJaEloQeOiQ6FmPsweM+kBamnb/Rpk31N9XfVH+Tmoq7cBfu0l/hoLW0ltZev24jG9lo+HAuHIwx/cy168zTHZ/u+HTHtm3v/O7O7+787vBh+Aw+g89CQkTnsttCWAgLv/rKMNAw0DAwIuKU3ym/U37/8z+iYzHGxOMC0iIQ5Xw5X85PTYU5MAfmjBkjOlHTnD1re8j2kO2hkSNL8kvyS/IrK0UnYow5Di4gzcw4xTjFOGXiRPqCvqAvdu0SncduBARUVGQ4YDhgOBAVpY04rl0THYsx5nh4DaSZUSfqRJ3+8z9F57DbOlgH69LS3FLdUt1ShwzhwsEY+zlcQJqJMd2YbkwPCIBcyIXcfv1E57lvH8FH8NGWLV4DvAZ4DYiJ4a1GGGP3ix/jbSbUg3pQj759Reewj9ms9FJ6Kb1ee+3774JEJ2KM6QePQJoJlVIplXboIDrHj/q+Y1w7Y/yNNxRFURRl3rzv03PhYIzZjQtIMyEjGcnoeGsGdIpO0an6ejyP5/H8jBmWSkulpXL1atG5GGP65yI6gLN4rPyx8sfK6+qomqqpuvGdvUDfd4wbrAarwRoTY1lvWW9Zv2eP6FiMMefBI5BmUpxWnFacdukSLIElsOTMGVE5qJAKqfDaNfqKvqKvfvtby8uWly0vZ2aK/vkwxpwPF5BmRlfoCl1ZvlzMV7dawRd8wXfoUPWQekg9VFgo+ufBGHNe3EjYIhCly9Jl6fK+fRiFURgVHd1iXyoMwiDsiy9wOS7H5RERljaWNpY2FRWifwKMMefHI5AWQeT5pOeTnk8+/zy8C+/Cu7m5zf4lvv+8hgWGBYYFYWFcOBhjDxovoreQitqK2orae/d8Pvf53Ofzjz4CBAR0c6NiKqZiWcbNuBk3u7nd7+fT/r/aWqzHeqxftQpWwkpY+cILloWWhZaFNTWiv1/GWOvDU1gPWGBoYGhgqI+Py1cuX7l8NWEC7aW9tHfIELpO1+m6v3/jf4cP48P4cEUFvUPv0DvHjtF39B19l5LCmxoyxhzF/wKeYeMy/zPC/wAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxNy0xMi0xNVQxNTo1NzoyNyswODowMKIRvi8AAAAldEVYdGRhdGU6bW9kaWZ5ADIwMTctMTItMTVUMTU6NTc6MjcrMDg6MDDTTAaTAAAATXRFWHRzdmc6YmFzZS11cmkAZmlsZTovLy9ob21lL2FkbWluL2ljb24tZm9udC90bXAvaWNvbl9jazFiemEwemo5ampkY3hyL3JpZ2h0LnN2Z7O3J80AAAAASUVORK5CYII=');\n  background-size: contain;\n  content: ' ';\n  inset: 0;\n}\n\n.icon-refresh::before {\n  position: absolute;\n  z-index: 9999;\n  display: block;\n  width: 16px;\n  height: 16px;\n  margin: auto;\n  background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADIEAYAAAD9yHLdAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dEAAAAAAAA+UO7fwAAAAlwSFlzAAAASAAAAEgARslrPgAAMQpJREFUeNrt3XlcVHX3B/Bz7rCISi6IC+ijkpZpIswMyBLgluVuKm4pqWmEuG/hUpr5uFYoiuaSFrklZvroo+jPFRURZgYVxZ1K3HIXUBSGe35/XC9PWpYL8J2B8/6H1wwGn3sb5sz93u/3fAEYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOM/QUUHYCx59F0ddPVTVdXq5YXkxeTF1O3Ll7H63jdzY3eoDfojTp1UIta1FatCm/D2/C2kxPchttwu0oVyIRMyKxShVpSS2pZuTIkQzIklyuHv+Av+IudHURBFERJkvJbKlQo+IWhEAqhsgz2YA/2d+8WPP/oMXWkjtTx4UMMwAAMuH4d2kE7aHf9OoVQCIX8/jvuxJ2489o1WkJLaMmlS+AHfuB37hwmYAImnDtnNBlNRlNGhvJDiUSfX/ZygiiIgqhMmayJWROzJgYF4Xbcjtv9/akX9aJerq7QE3pCTwcHiIEYiMnMxNpYG2ufOYNTcApOOXDAcNZw1nA2KUn0cTwrLiBMKO+z3me9z9asKa+V18prtVr5tHxaPv3mmzgaR+Nod3cYCANhYMOGyr9+9VXla9myonMXFoqmaIp+8ADDMRzDz56FTtAJOh07RgmUQAkGA17Da3jNYMjrldcrr1dKyrGxx8YeG3vvnujc7I8QdbG6WF3skCFUjapRtYkTcSSOxJHVqr3Yz0tNVb6OH280Go1G43//K/oIn3rkogOwkgzR09bT1tPW3V3jrHHWOLdoIRtkg2zw84PTcBpO+/jgGByDY2rWFJ3U0tEiWkSL8vNxDa7BNSdOkAM5kMOuXTgYB+PgnTvz1uStyVuzbx8XmOKh0+q0Oq2tLW2hLbRl9WrsgB2wQ7duhf17aAWtoBWzZpncTe4m94gI0cf9JC4g7KU0oSbUhCpW1FTTVNNUa98eTGACU9u2uAf34J6WLWEuzIW5VauKzlni6UEP+txcZYju0CGoDtWh+pYt+QH5AfkB69cfxaN4FH/9VXTMkkJ3UXdRd3HBAuWKMTy8yH8hAQENH64MeUZFiT5+FRcQ9kwaN27cuHHjSpVsbW1tbW2DgxEREbt2Vb7bvLny1dZWdE721+gz+ow+S06W+kn9pH7r1+fdyruVd2vdOi4sz0f7rvZd7bs+Pvgv/Bf+KyEBjGAEIxb5+yjNp/k0PytLE6mJ1ES+9lpybHJscuzVq6LPBxcQ9hjlJqCNTbY+W5+tb98eFsEiWNS3LxyDY3CsXTvlsb296JzsJT2aHEBdqAt12bULMzADM5YsgQ/hQ/hw0yblk25enuiYlka7XLtcu3zTJozGaIzu2LG4fz85kzM5jxxpijPFmeLmzhV9PriAlHKefp5+nn4uLtgQG2LDQYOwDJbBMh99BIfgEBxycRGdjxUvOkSH6NDVq7gEl+CSFSvMx83Hzcejo49+c/Sbo99cuiQ6nyjKPY8qVchABjJcuYJe6IVeNjbFHqQNtIE2W7YYpxmnGad16CD6vEiiA7DipfwhNG6sS9Wl6lJ/+EF6KD2UHv76K6ZgCqZMmcKFo3RDX/RF3+rVYQWsgBXjx9uQDdlQero2XZuuTV+2zOui10Wvi6+9JjpncaMP6AP6ICBAWOFQc0RQBEXUqSP6fKj4CqSEKxizvY7X8fqkSaADHejati2usVtWwqhDX+2pPbXfsIFqU22qPW1aSl5KXkre0aOi4xUV3QPdA92Df/8b/MEf/CdMEJvmwgVlem/t2qLPC1+BlDAe8R7xHvENGypXGuvWFdzsAwCAdu24cLCXshgWw2JJUqetSv2l/lL/lBT19abfot+i3+LmJjpmYaMbdINu1K0rOgf4gi/4irsCehIXECvX5OMmHzf52NVVO087TzsvJkZzSnNKcyo1FRAQMDiYCwYrUurr69HrTa4iV5GrnDihu6O7o7sze7Y6e090zJeFC3ABLnjRhYGFiICALGe2IxcQK+OT4ZPhk+HgoNPpdDrdp5/agA3YwOnTGIMxGNO3r/oJUXROVjopK+rLlIGW0BJajh1rF2gXaBd4+rRut263bndIyKN/ZX0faHbADthRrpzoGCCDDDIXEPacPL/0/NLzy8DAXKdcp1ynlBTl2alT1Z5OovMx9pcSIRESnZ1hLIyFsd9/rxunG6cbt2+fOtQqOt4zQ0BAC3jj9gIvEHgT/0lcQCyUcqVRubJypfHdd9IZ6Yx0Zu9epWnf66+LzsfYC9kFu2BXQIDmjOaM5ozJpCMd6WjKFLU1iOh4Fo+vQNjfUWdN5Z7OPZ172mBQnv3gA76XwUoUdUGqHvSgnzwZpsJUmJqQoP9C/4X+C/6A9DTkTu7kzlcg7JHg4ODg4GCNRv0kpvwhHTiAn+An+IkFzPpgrDhMhskwWa+nS3SJLhmNWq1Wq9V+9JHoWJYGwzAMwzQa5ZH4e53CA5RW+vv6+/r7tWqlD0oflD5o9271k9jjLxDGShl1nxZERFy8WHtVe1V7deNGtWmn6HiWol5Uvah6UeKHsizmUqi00J3SndKd6tRJNskm2bR8OY7H8Ti+cmXRuUobSqIkSsrJUVYW37sHw2E4DH+Gwn0QDsLBihV5SLF4YDtsh+06dbLxt/G38U9OVu6VdOmi9OpS980oerSX9tJeRGyGzbCZ6LMCUPZh2YdlH6pDWQ8fisrBfwDFRNtH20fbZ8IELItlsey0afwG9ILCIAzCHj6kntSTep49C6thNaw+fRpDMARDTp/GbtgNu506BTNgBsy4cEFpQXHrltnb7G32vnXLYaLDRIeJt24l1kqslVgrJ+d5f/3jzSadneVj8jH5WNWqmmhNtCbaxYXqUT2q5+xMy2k5La9ZU9mBsHFj/Ba/xW8bNVKuNF9/HQxgAIOdnejTaXW8wAu87t3DbMzG7IEDDSsNKw0r164t6l+rzdJmabNMJqWAeHqKPg1mg9lgNlSqpHRTvnNHVA5+Aysij88qWbhQmQY4cKDoXJaODtABOpCeDtfgGlw7cADfw/fwvf37lfN34ICbm5ubm9vZs7GxsbGxsfn5ovM+L/V1kT83f27+3Pr1bZbYLLFZ4u5OJ+kknQwMpP20n/a3aMGz7Z4RAQF99ZVyRTJunPKkLBf2r9Fu0W7RbklJwck4GSd7eIg+bOW4nZ2V475xQ1QMHsIqZMoWra+8kt8zv2d+z9hY5dnWrUXnshjhEA7h2dlwAS7Aha1boTW0htYbN5pjzDHmmPj4ow5HHY46PL3rq9IDSPRBvLiCNumBEAiBaWnKs+rXtWuhLJSFsn/oknwOz+G5Fi0wHuMxvkUL6A29oXeHDkpBrVJF9PEIh4CAo0frknRJuqS6dW1r2NawrdGnz4teYVoLZYtjSVI2cBOXgwtIIVH/4M3VzdXN1bdsUXo7iL/UFev+faX99O7d0AJaQIvY2JwbOTdybmzYkDYlbUralOxsmAJTYIronJYnJSElISXh8mXl0cqV0AAaQIOVK9VZe+nn08+nn/f1LWhZQ0BAvXuX2sISBmEQ1qVLHuVRHu3Z4z7HfY77nI4dlS1+r1172R+P+ZiP+Tzk/CQuIC9JWejXoIHyyXrnTmgGzaCZq6voXMVN3fEOHdERHRcsKN+8fPPyzdet24f7cB8+eADTYBpME53S+j0+dHfggPpVmZUzblyF7yt8X+H7Nm0gEiIhMjQUVsJKWPnOO6XmnhsCAjZtalvHto5tnQMHlL/PNm2UK9fz50XHKyz2SfZJ9knip/GW/BdUEfFM8EzwTKhXT1otrZZW79tXavbReLT3tjLdctMmnIpTceqSJYb2hvaG9jt3io7HHlfwOh0qDZWGDh2q3IT+8MPS0gKHIimSIn//HbpBN+jWurXpmuma6dqxY8/7c3QjdSN1I48ehXiIh3h3d9HHJblJbpJbjRqit7blAvKcvDt4d/DuULeueb15vXn9vn3oh37oV6uW6FxFpWC6axZmYdaCBTZbbbbabP3qq8O9D/c+3Pv330XnY89H2RDKySn/Qv6F/AsffYRDcAgOGTWqpA99USIlUuLNm+iDPujTurVyRWJ65rsHllZAZHvZXrZ3dX18qLP48RDWM1IX/pkTzAnmhF27SmrhoGRKpmSzGebDfJi/Zk2+lC/lS599drTi0YpHK/76q+h87OUk10yumVzz5k3l0YwZDdc1XNdw3fz5DjkOOQ454eFUn+pT/YkTcSgOxaGOjqLzFhalcDg5KY9271b+ntu2NZQ1lDWUVffL+RvxEA/xljMEKLvL7rI7IiRAAvxz+iIjfAzN0qn7bdBb9Ba9tWdPiWsxogMd6IigA3SADuvWaS5rLmsuN2pkGm4abhoeEqLMM+fCUVKldU/rntY9O9v4gfED4wezZtEYGkNjGjSAYAiG4KVLCz5QlCgVKtBaWktrt29X7pE0b/6P/0kgBEIgkejkKvvR9qPtR4svaMIDWCp1Ixw7WztbO9uEBOUSv0ED0bkKjT/4g/+5c8rK6o8+Ui7p9+wRHYtZFrXtunRdui5dX7oUp+N0nO7nJzpXYVGHaKVvpW+lb7t2NXxk+Mjw0bZtT/47pdCo904aNxadW5l1V7u2Mi38wgVRMfgK5AnqSmPb8bbjbcevW1dSCof6SZKaUTNqNnu27VjbsbZj3d25cLC/cyTwSOCRwLQ0U1dTV1PXgAByJmdyHjlS+e79+6LzvSz0Rm/0dnAgIxnJuHGjsrPne++p31c6SAQEwAgYASMsYEfCR+Tecm+5N1+BWBztae1p7emoKOyNvbH30KGi8xQOkwnLYTksN3CgId4Qb4hXN6Ri7MUon8hffVV5tHSp8vUZhoIsXMGQ3VbYCluPH7eYledPUFqZ1K0reoiZC8gjavtotQuo6DwvTL2nYQADGL7+uryxvLG8MSJCWY9R0saymWVA1LvoXfQuI0bIF+WL8sXZs5UmlZazb0VJo3HRuGhc3NySNidtTtr8yy+icpT6ISx9qj5VnxoUpBSOBQtE53lRdJgO0+HMTPkr+Sv5q27dlLHRMWO4cLCiR2S4bLhsuBwZSV/T1/R1y5Z0iA7RIXHrE1jxKLUFRNlfoE4dpVvr+vXKs+L767+Y1FTNVc1VzVUvrxTHFMcUxw0bRCdipVPKmJQxKWPi45V7bTodTaAJNOEZpsmy55IXlBeUF1T4TSOfV6krIGovIRu9jd5G/8MPVruAahksg2U//qg88PFR5vefOSM6FmMA/+vl9SD/Qf6D/ObN6Uf6kX785hvRuUoKzWDNYM1g8QWk1I1Rnrc/b3/e/pNPlGaHb70lOs9z2wSbYFN0tLGmsaax5rBhypPiX0iM/RVlnUlurvIoLEz3ve573fe//gpREAVRM2eKzmet8lvlt8pvJX47g1JzBaIP1AfqAz09ldlIkyeLzvO8aAWtoBWzZimFY8gQ5VkuHMy6FCxYnEbTaFp4OIRCKITy6/h52bjauNq4ij9vJb6AKF1K7e3pHt2je99/by07wdEiWkSL8vPhM/gMPgsLM7mb3E3uERGiczFWGExtTG1MbRYuhMWwGBb37as8m5cnOpe1eOj90PuhNxeQIlehZ4WeFXqql8oWsIL0n6ifyE7BKTj1wQfGTsZOxk48dsxKJmUh6+rVFEIhFNKjR8EHJ/a3bNfYrrFdwwWkyHh+6fml55eBgbARNsJG9V6B5aOVtJJWDhtmCjGFmEJWrRKdh7HioPRe+/lnyIRMyBw9WnQeS2e7yXaT7SYuIIVOnWUl1ZfqS/WjopRLZPEbr/wT8iRP8pwyxRRvijfFR0eLzsOYCKZWplamVvPm0WbaTJvV6fXsSXmd8jrldRJ/pWbxb6zPKz09PT09/aOPYCpMhalNmojO848ezaoyLTMtMy37/HPRcRizBPI5+Zx8bvBg8AEf8Ll+XXQeS1PmtzK/lfmNr0AKjU+GT4ZPRuXKysYxX3whOs8/WgSLYNGGDY9Px2WMAahNHK9fV3b6DA8XncfSZEVkRWRFcAEpNHmYh3k4derjG8dYHppBM2jGmTOaSppKmkr9+yvPin8hMGaJlJY8sbE8pPW4SmMqjak0hoewXpq+j76Pvs+bb5ILuZBLaKjoPE8VDuEQnp0tl5HLyGXeey+pflL9pPqZmaJjMWYNzKvMq8yrwsOVfTBu3BCdR7RsXbYuWye+gFj9SnQ6SSfp5FdfWXr3TzKTmcwffqjuryA6D2N/5BXsFewVXL268qh6dfm8fF4+b2+PU3EqTnV0pMk0mSaXL6+8gf9Fz7gFsAAW2NjQEBpCQ/6wFe7H8DF8fOcOfoPf4DfPsKMfAgLev4+f4+f4+cOHT36belJP6rluHfwIP8KPgweLPm/F7lG3beMS4xLjEvHrZqy2nbtOq9PqtE2bKi+4xETReZ6G3qF36J3ISNN003TT9FGjROdhpZPSPLRiRRudjc5GFxKi/N107qxcGXt5QTREQ3T58qJzsn+gBz3oc3ONi42LjYvt7UXHsdohLNpKW2nr+PGiczzVRJgIE9PSMqtnVs+sbsE5WYmm3abdpt02eLDmoOag5uD580rhmDdP+W7z5lw4rExTaApN1d5i4lnskM/TFNzz+Iw+o886dhSd508erSSXt8vb5e0DB55bfG7xucV/vhRnrCjpZutm62ZHR8MkmASTSuFQT0mlAQ1oxA9dqazuCoReo9fotYgIMIIRjOL3BP6TztAZOkdFpSxOWZyy+NAh0XFY6aIM7Q4bVmrvEZRwVJfqUl3LuQKxmgKi36Lfot/i5kaTaBJN6tFDdJ4/GQtjYeyvv+bszdmbs/fTT0XHYaWLUjgqVFCGOHhBaollYUNYVlNA5GPyMfnYuHEWO9tqNsyG2aGhyv4H2dmi47DShcpTeSrfpw8kQRIkVawoOg8rIjLIIPMQ1jPzPut91vvsK6/gT/gT/qS2fbY0O3YoC5527BCdhJVO2AybYbOWLUXnYEWMgIC4gDwzcw9zD3OPnj2VR2XLis5T4LGNcHiWFRNMBzrQubmJjsGKFgZgAAbwENYzwxbYAluoLT8syFW4ClfXrFH2MzCZRMdhpRu1ptbU+g8L+FgJxlcg/8gj3iPeI75hQ9gDe2CPj4/oPAXCIAzCHj7UJGuSNcl8s5xZBpyEk3DS7duic7Ai1hyaQ3O+AvlHmhRNiibFAq88FsEiWLR8edLmpM1Jm3/5RXQcxgAAoA/0gT7nzomOwYrYHtgDe65eFR1DZXEFRJmOaGurbLBkQTfNH93zkDZJm6RNc+eKjsPYH+FwHI7DeRJHiXcQDsJBy+mlZ3EFRF4vr5fXv/sujsSROLJaNdF5CiyGxbB427bkmsk1k2ueOSM6DmN/ZH/C/oT9idhYZT+cmzdF52FFQ+or9ZX6xsaKzlGQR3SAPwXqJnWTullgi5JdsAt2qT2EGLMsB28evHnwZlYWtISW0HLyZNF5WFHYuDE5Njk2OfbIEdFJVBZWQBBhNIyG0W3aiE6iomk0jaadOGGsaKxorLhzp+g8jP0dU7wp3hQfHU0hFEIhP/wgOg97Sf7gD/7nzklukpvkFhYmOs6TLKaA6AP1gfpADw/4Gr6Gr11dRedRKbNboqOVR8+wnwFjFsA03DTcNLxfP+XRzJm0iBbRIvEbELFn1BJaQsv9+8255lxzbrNmypWH5dw8V1lMAVFaMLRtKzrG4/LylJWfljPmyNizk2VlndL48VKUFCVFeXjQJtpEm9asocN0mA7zjpjCPdogSpmeq+5r9P77xtnG2cbZzZod/eboN0e/uXRJdMynsZhuttqftD9pfzp4EKfjdJzu5yc6D0RCJETGxRkDjYHGQMsZUmOsMDRc13Bdw3V2duViy8WWi23Y0DzLPMs8q1YtTT9NP00/Z2c5W86Ws//ccw5H42gcXb48mMEM5r/YmTAKoiCqXDlaQStohZ3d8+bCnbgTd5Ypo3S1dnB40eOjZbSMlt27Bz2hJ/QshHUTs2E2zM7MxLfxbXz7+a/kcASOwBFEspPsJDtdvy6Nk8ZJ465exbbYFtsmJSmTc6xv8oPwAuKT4ZPhk1G5cu6V3Cu5V65dwzAMwzCNRnQumANzYM4HHxhbGFsYW8TEiI7DGGOWRnhXW/N483jz+Nat8SSexJPiCwdFUzRFP3hg42TjZOO0caPoPIwxZqmE3wMhLWlJazmtSjAcwzE8Li6pflL9pPo8RswYY08jvIDAG/AGvOHtLTqGSpm2+3//JzoHY4xZOmEFJIiCKIhsbJQuoh4eok+Eit6it+itPXtE52CMMUsn7B7I/e73u9/v/uabmI7pmP7isy0KzQgYASOuXUtxTHFMcTx1SnQcxhizdMKuQPL75PfJ7+PlJfoEqCiLsihr9+5Hj3jBIGOM/QNx90BOwAk4odOJPgEFJ2KptFRaunev6ByMMWYthBUQvIk38aZeL/oEqEgiiaTkZNE5GGPMWggrIDSLZtGs+vVFn4DH9zbnex+MMfasir2AqCvPsSk2xaavvCL6BMBxOA7H09OVnkH374uOwxhj1qLYC4j5ffP75vdr1xZ94AUOwkE4ePy46BiMMWZtir2AyF3lrnLXOnVEH7iKfMmXfE+cEJ2DMcasTfHfA2kEjaDRv/4l+sALTsCv0q/Sr6dPi87BGGPWptgLCLqjO7pb0BXISlpJKy1voxbGGLN0xX8F0gbaQBvLKSDSIGmQNOj6ddE5GGPM2hR/ASEgoBo1RB+4StnA6sYN0TkYY8zaFHsBoZk0k2ZWqiT6wFXZKdkp2SnXronOwRhj1qb4r0DKQBkoU6GC6ANX3L2b1j2te1r3QtjykjHGSpniLyB2YAd2llJAeOEgY4y9qOKfhbUcl+Nye3vRBw6+4Au+3HWXMcZeVLEVkODg4ODgYI0GjGAEI6LoA+cCwhhjL6fYCkhKQEpASoCNsA2sGGOMFa5iKyB21e2q21XnT/yMMVZSFFsBUWY75eWBDnSgs4BCcggOwSELGEpjjDErVcw30YnAG7zBW/y0WepDfahP5cqiczDGmLUq/mm8RjCCMSdH9IFjOIZjeJky/k7+Tv5Ojo6i8zDGmLUp/gISBEEQdOeO6ANXPajzoM6DOlWris7BGGPWpvgLyByYA3Nu3RJ94CpyJmdydnYWnYMxxqxN8ffC2k7bafvNm6IPvOAE+Ev+kj9fgTDG2PMq/pXoC3EhLrSc5oWyXtbL+po1RedgjDFrU/xDWJWhMlS+cEH0gauwMTbGxg0bis7BGGPWpvgLyApYASsyMkQfuIrKUlkq26iR6ByMMWZtir+AAACABV2BfIqf4qdcQBhj7HkVewGR58vz5fmnT4s+8AKJkAiJzs4e8R7xHvE8G4sxxp5VsReQepH1IutF/vILRVM0RT94IPoEFJyIddI6aV3jxqJzMMaYtdAU9y9MS0tLS0sjcnF0cXRx7N4dfoPf4Ldq1USfCGm7tF3afvbsZfNl82Xz/v2i8zDGmKUTdA8EAKpAFahiMok+ASoaQSNoRIsWonMwxpi1EFZA6Cf6iX46dEj0CSjI05k6U2c/P58MnwyfDAcH0XkYY8zSCdvgCQEBwXIKiNpcMdc31zfX19dXeXb3btG5GGPMUgm7AjGajCaj6cQJ5dHdu6JPRIEBMAAGNG8uOgZjjFk6cfdAAABAlpWvhw+LPhEFMiADMt55R3QMxhizdIILCAAYwAAGCxrKmopTcaqXl8cwj2Eew+rXF52HMcYslfgCchfuwt2DB0XHeJLGXeOuce/RQ3QOxhizVMILyN2YuzF3Y+Lj6TAdpsOZmaLzFFgIC2Hh+++LjsEYY5aq2BcSPunWtlvbbm3Lz3eRXCQXydMTzsAZOGMBvakQELBKlZpv1Xyr5lubNl1Ou5x2Oe3qVdGxGGPMUgi/AinQGlpD640bRcd4krxUXiov7d1bdA7GGLM0llNAhsAQGLJ1K+hBD/rcXNFxVHScjtPxDz90n+M+x31OuXKi8zDGmKUQPoSlunLlypUrVx4+dIl0iXSJ9PeH7bAdtterJzoXxmEcxjk4SD2lnlLPS5eurLqy6sqq5GTRuRhjRcfrotdFr4uvvVa9SvUq1av4+ro2c23m2qxBg2oPqz2s9tDRMcAnwCfA5/ff1d5+ovOKImwl+tPgcByOwzduJIkkkt59V3SeglzZmI3ZI0Yoj775RvmqrmNhjFmj4ODg4OBgjSb9fPr59PP9+9NMmkkzx46VO8md5E6vvaZ0zAAgICAAkEACCQDS09PT09Pv3tVO107XTl+7Vr4qX5WvfvXVkagjUUeizp4VfVzFBUUHeJIyVFS1qu0523O25zIylHUidnaic6kohEIopEsX03DTcNPwn38WnYcx9vx0Wp1Wp61ShSIogiLWr8dZOAtnBQW93E/Ny4NBMAgGzZgBS2AJLJk2Tem4kZcn+niLisUVEJUuRZeiS1m7FgbCQBhoOesxaBftol0HD5oqmiqaKr71lug8jLFnpwxNOTnJHeWOcscDB5TZlg0aFPovagNtoM2WLXer3q16t2q3bueGnRt2btjDh6KPv7BZzk30J3mAB3ioQ0WWA1tiS2zp76+7qLuou9i2reg8jLF/pg5VyWlympy2YUORFQ7VNtgG29q3f6XtK21faTt3rujjLyoWW0CMaEQj7tsHARAAASdPis7zJEqlVEqdPVt9YYrOwxh7uvT26e3T248ZA+NhPIwPDCyu34uzcTbODg319PP08/TT60Wfh8Jm8W98NSrWqFijoq0t3sf7eN+Cbqrvxt24u2rVW7du3bp169IlZRaZ0Sg6F2Psf7wWeC3wWtCokTIpZ80a5Z6qTfFNHroCV+AKIprRjGZJUt4nNm8WfV4Ki8VegajyLuVdyrv0/feUREmUlJMjOs+fzIW5MHfqVH8nfyd/J0dH0XEYYwBBFERBZGMj15HryHW++w4WwSJYZG8vNlXJu2dq8QUkNTU1NTX19m2IhEiIXLNGdJ4n4UgciSOrVXtw6cGlB5ciIkTnYYwBZK/OXp29etgwmAyTYbL4oSNKpmRKrl1bdI7CZvEFRCVfkC/IF2bOVP5HmM2i8/yJP/iD/9ix+kB9oD7Q01N0HMZKoybUhJpQnTqwH/bD/qlTRecpkAzJkIwWO+v1RVlNAVEX6OAMnIEzVq4Uneev2dqSjnSk++67husarmu4znLWrzBW8iHa7LfZb7N/0SLlDdtyWg/halyNqy9eFJ2jsFlNASmwATbAhmnTlAcWuEAnHuIh3t29TL0y9crU+/RT0XEYKw309fX19fXDw2EkjISRljPZpkAf6AN9jh0THaOwWV0BMRqNRqPx/HnqRb2o1w8/iM7zVB7gAR4REV51vep61fXyEh2HsZJI30ffR9/nzTflU/Ip+dTs2aLzPA2GYiiG7tghOkdhs7oCorLZZ7PPZp/lXomgF3qhl41N/on8E/knfvjB+6z3We+zr7wiOhdjJYHaHZvSKI3SYmPRG73R28FBdK4/CYMwCHv40DzPPM88b8MG0XEKm9UWkKTNSZuTNv/yC8RCLMSuWCE6z9NgAAZgwOuvmx3NjmbHmJhHz5a4m2mMFSebXja9bHotXVrkK8pfEt2je3Rv3bojgUcCjwRevy46T2Gz2gKiyvsp76e8nz79FIbBMBh2+7boPE+D7bAdtuvUSZukTdIm8b0Rxl6EvpK+kr7SuHHYCTthp169ROd5GlpEi2hRfj4NoAE0YMYM0XmKSon5JKzT6XQ6XViY8mjhQtF5nioUQiFUlukG3aAbnTqZJpgmmCZs2SI6FmOWTDtBO0E74Z13oDN0hs7//S+GYRiGWXALIQICWrZM6cY7aJDoOEWlxBQQhSRpN2k3aTclJuJUnIpTLf3m9d27+Aa+gW+89ZZhpWGlYeXx46ITMWZJ1FYksqPsKDvu3w9REAVRlSqJzvU0lEiJlHjzJjbFpti0QQOlgNy4ITpXUbH6IazHyTJ8Dp/D52Fh6iWk6ER/r0IFeofeoXd27dJ/of9C/8Xrr4tOxJglaPJxk4+bfOzqKq+QV8grtm619MJRYCtsha3jx5f0wqGy3EvAF3TlkRquNVxruDo74xk8g2e8vUXneqpESITEcuWoP/Wn/u3aVS1btWzVsuvX/2743fC7IStLdDzGipNPhk+GT0blyuAADuCwZ4+yolz81tb/hCbQBJqQkGB6z/Se6b2hQx89W+K3ui1xBUTlkumS6ZKZkAB+4Ad+ISFwES7CRcttdog7cSfurFRJ6i/1l/q/+67LWZezLmfXrVPK4f37ovMxVpSUHQIrVJCvydfka9u2QQzEQIzltwRSm7xiCIZgSLt2yt9ryZtt9TQlbAjrf5RLyLt35SA5SA4KCVFvXovO9Y/+Df+GfzdsqExP3L7dI94j3iPe2Vl0LMaKglo4oAW0gBZxcbAH9sAeHx/RuZ7ZQTgIBz/7TFngfOqU6DjFrcQWEFVKcEpwSvCuXeRDPuQzZ47oPM9Hq5UeSA+kBwcOeHfw7uDdoW5d0YkYKwwFhQMAALZvt7bCoW5t/er8V+e/Oj8yUnQeUUrYLKynU/cHyI7LjsuOi4+HSTAJJvn6is71rOgQHaJDV69KraRWUqu2bQ3xhnhDfEqK6FyMPQ9lun2NGsojdfq6Vis61zPzBm/wvnPHvNC80LzQ0/MoHsWj+OuvomOJUuKvQFT7cB/uQ7MZ8zEf8/v0ocN0mA5nZorO9azQF33Rt3p16kf9qF98vO6O7o7uzttvi87F2LPwzPLM8sx64w3lnuShQ8qzVlQ4HsEszMKssLDSXjhUJfYm+tNcXn159eXVt2/XqFejXo16GRl4GA/j4S5dROd6ZsmQDMl2dpAGaZDWo0eNcjXK1Sh3+/aV3678duW35GTR8Rj7Ix3pSEfNmuFaXItrd+yA9bAe1levLjrXi1m0yLjduN24fdYs0UksRakZwnoa5ZJaXbmurmS3VqtX53yS80nOJ6Ghad3Tuqd1z84WnYiVTrpVulW6VaNGKV2zZ81Sm4uKzvW81Om5D/If5D/Ib95c+bvKzRWdy1KUmiGspylvKG8obxg2DN6Bd+Cd7dtF53k5vXs72DjYONgcPlwwZMBYMVA2UCtfXpeiS9GlrF0LX8PX8PVXX1lr4QBf8AXfy5dxOk7H6d26ceH4a6W+gKj3RjT9Nf01/bt3p320j/ZZcUuRR9OApVgpVopNStJqtVqt9qOPlG9yF2BWuLTvat/Vvuvj44AO6IAmEwyEgTCwRw/RuV6Uuq4DEiABErp0UabnXrkiOpel4jeUJ6gtFGwCbAJsAg4fVj5JubqKzlU4DhzAztgZOw8caPjU8Knh09OnRSdi1qVgNmNMdkx2zOjRSouRL75QvmtrKzrfC3u0TkzuJfeSewUHpzimOKY4lrz9OwobF5CnUHcSlCvLleXKe/cqz5YtKzrXyyr4hPVoAZQ6jz02NjY2NtbSe4cxUTxDPUM9Q319sQN2wA4LF+JknIyTPTxE5yoseAWv4JVRowyXDZcNl0vvuo7nxQXkH+hO6U7pTnXqBO/D+/B+bKzyrBV/0noC7aW9tDclheIojuLGjlUXXorOxcTyuuh10euik1N+bn5ufu6sWTgTZ+LMAQPACEYwlqCh0P7QH/rPmGEcYhxiHDJhgug41qbkvBCKmH6Yfph+WNeudJAO0sE1a5RnS04hedyOHVgOy2G5iAhesFg6KLMRy5ZVNmYbOpRepVfp1XHjcDgOx+GVK4vOV9ioA3WgDgsWmKaYppimqM0P2fPiAvKclNlNXbpIzaRmUrO1a5VnS2AhUXuHLYbFsHjtWnm+PF+eP3lyil+KX4rfuXOi47GXUy+qXlS9KHv7ivMrzq84f9Ag+YR8Qj4xcaK6YFV0vqJCsRRLsd9+a3IzuZnc1I2eSn7X3KJS6mdhPa/Hb6699x6EQRiEPXwoOlehWwyLYbH06PXRu7d0XDouHT99Wrtau1q7+v/+zzPdM90zvUMH5fslaEijhFJ7T2l3andqdw4fXsGpglMFp/Pn6RV6hV6ZP7+kFw6IhViIXbJEKRzqrEQuHC+L//BfknLp37kz6EEP+h9/BAMYwGBnJzpXcaHP6XP6/MgRuA/34f68eZlXM69mXl2z5tywc8PODSuBhdVKKAWjaVNl5feAARAMwRDcp4/yXeufDPLMtsE22DZ3rrGqsaqx6qhRypNcOAoLF5BCohSSdu0gHMIhfO1aiIZoiC5fXnSuYjcMhsGw27fpOl2n6z//jANxIA5cu9ZtkNsgt0G7d/Nsr8Klv6+/r79fq5ZskA2yoUcPvIE38Eb//gXbApQ2j4ZeqQE1oAaffGIKNAWaAr/8UnSskooLSCHzCvYK9gr28MgfnD84f/DmzTgGx+CYmjVF5xKNIimSIn//Hd3QDd3WrwdXcAXX9etzYnNic2ITEnil79/TVtVW1VZ1d1dWRnfsCCfhJJzs3BmyIAuytNoSNzvqhdy/L++V98p7+/bldRzFo5S/4IqOp5+nn6efi4s0QZogTdi0CSbDZJis14vOZZnu36fRNJpGJyRIA6QB0oC9e+EW3IJbe/aUcyjnUM4hKUntGCA6aWFTF+Zl2mXaZdo1aiStllZLqwMDyZ/8yT8wEHfhLtwVGAhzYS7MrVpVdF5Lo25zoHld87rm9Y4dk39J/iX5F24qWly4gBQxdXokhVIohcbE4GJcjIu7dhWdy2p4gRd43btHs2gWzTpxQlnwdeKE0uTu5EnpXeld6d3jx/MG5Q3KG3TypNJm+7fflP+4+Me63ee4z3GfU66c3VG7o3ZH69bNn5o/NX9q3bo4GAfj4FdfhVbQClo1boxrcA2u8fBQJmE0agSLYBEssrcXfbqtS2oqEBBQ+/bKDqQXLohOVNpwASlWiLoFugW6Bf/+NxyDY3AsIoKHHgoXJVMyJZvNYAYzmG/cgFzIhdz/fcUojMKoa9cgBEIg5M6dZ/65QECg0WAwBmOwkxO0hJbQ0slJ+blVqkAe5EFelSqQCImQyFsQF5l20A7a/fyzpq+mr6Zvv35J9ZPqJ9W3nn19Shp+4xKkYEOoltASWn7/vfKsulMbYwwAgKIpmqIfPIBsyIbsiAhTK1MrU6t580TnYgouIIJ5xHvEe8Q7O2t2aHZodixfrkw7bN9edC7GRKJpNI2mnTiBE3EiTuzVSxmiSk0VnYs9jguIRUFU2q8PGoSIiKg2dStF8/ZZ6aQDHeiIIAIiIGLpUltbW1tb2xEjEmsl1kqslZMjOh77a1xALJQ6bRPSIR3SV63CIAzCoDffFJ2LsULlB37g99tvShv1jz9WWuXExYmOxZ4NtzKxUKZrpmuma8eOYSAGYqBWq8xCGjGCDtNhOsw3DZk1y8tTvkZF5QTkBOQEvPkmFw7rxFcgVkZdX4I9sAf2mDkTT+AJPNGnD8/mYpZvz578yPzI/MghQ44EHgk8EpiWJjoRezn8hmPl9Kn6VH1qUBD1o37Ub/585dnGjUXnYqXcoz3FoQt0gS7jxxtbGFsYW8TEiI7FChcPYVk5Q2NDY0PjffuUhQo6nTrUBT7gAz7Xr4vOx0oHdUU4jIJRMGr0aDgEh+BQ/fpcOEo2vgIpodQV0TaeNp42ngMHKiu4J0zglhisUIyAETDi2jWQQQb566+VvdHnzzcajUaj8f590fFY8eACUkp4n/U+6332lVfMn5g/MX8SGoou6IIuI0YonxRdXETnYxZuFIyCUZcugR3Ygd2sWeW7le9WvtvSpUqPsgcPRMdjYvAQVimhtnwwbTBtMG2YMycnMCcwJ7BuXWXr2g8/LNjXgzEAUDok7N+PNbAG1ggJuXvz7s27N1991RhsDDYGz5/PhYMB8BUIe4JnqGeoZ6ivLzbEhtgwLAyaQlNoGhyM4RiO4WXKiM7HChfNo3k079YtfA1fw9diYmQH2UF2WLJEaYd+8qTofMyycQFhf6sJNaEmVLGiTZxNnE1c166URVmU1bcv3sE7eCcg4PGtb5lly8tT2ubv26c0m/zuO8e+jn0d+/70E19RsBfBBYS9EHUnPPov/Zf+27mzsg6lc2eaTtNpemAgeqEXetnYiM5ZOt29C8tgGSyLi4McyIGcTZtyQ3NDc0Pj4lJTU1NTU2/fFp2QlQxcQFih8snwyfDJqFw51y3XLdft7bexMTbGxq1awTgYB+NatYI5MAfm1KkjOqfVerRlK1SBKlDl1Ck6Rsfo2O7dOAJH4Ij//CdnR86OnB379vEOj6w4cAFhxUq/Rb9Fv8XNTR4gD5AH+PmhCU1o8vGBTtAJOvn6Kv9KXQhpays6b7FT95RHQsLERGgADaBBYiJshI2w8fBhjMM4jEtMVLrT3r0rOi4r3biAMIui0+q0Oq2tLV2ki3TxjTfgS/gSvmzcGDMxEzMbNYIgCIKgWrXgB/gBfqhdW5k95uqKq3AVrnJ1tZid/fSgB31urrID4W+/QTWoBtXOnwc3cAO38+dhH+yDfenpShfa8+el8lJ5qfzJk8k1k2sm1zx7Vvkhxb+jImPPgwsIK1G8gr2CvYKrVzdfMl8yX6pZU1ouLZeWu7pCb+gNve3sKIIiKKJcOZgJM2GmnZ2UJWVJWXZ2NIkm0aRy5ZQFcYjkS77kW768ci8nK0uZrXT7Ni7ABbggKwuGwlAYmpmpdJHNytL8R/MfzX+ysiAO4iDuxo26H9T9oO4Hly/HxsbGxsbm54s+L4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYY4wxxhhjjDHGGGOMMcYYKzb/D4DEm9oGCaFQAAAAJXRFWHRkYXRlOmNyZWF0ZQAyMDE3LTEyLTE1VDE1OjU3OjI3KzA4OjAwohG+LwAAACV0RVh0ZGF0ZTptb2RpZnkAMjAxNy0xMi0xNVQxNTo1NzoyNyswODowMNNMBpMAAABPdEVYdHN2ZzpiYXNlLXVyaQBmaWxlOi8vL2hvbWUvYWRtaW4vaWNvbi1mb250L3RtcC9pY29uX2NrMWJ6YTB6ajlqamRjeHIvcmVmcmVzaC5zdmejF0ikAAAAAElFTkSuQmCC');\n  background-size: contain;\n  content: ' ';\n  inset: 0;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Verifition/src/utils/ase.ts",
    "content": "import CryptoJS from 'crypto-js'\n/**\n * @word 要加密的内容\n * @keyWord String  服务器随机返回的关键字\n *  */\nexport function aesEncrypt(word, keyWord = 'XwKsGlMcdPMEhR1B') {\n  const key = CryptoJS.enc.Utf8.parse(keyWord)\n  const srcs = CryptoJS.enc.Utf8.parse(word)\n  const encrypted = CryptoJS.AES.encrypt(srcs, key, {\n    mode: CryptoJS.mode.ECB,\n    padding: CryptoJS.pad.Pkcs7\n  })\n  return encrypted.toString()\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/Verifition/src/utils/util.ts",
    "content": "export function resetSize(vm) {\n  let img_width, img_height, bar_width, bar_height //图片的宽度、高度，移动条的宽度、高度\n  const EmployeeWindow = window as any\n  const parentWidth = vm.$el.parentNode.offsetWidth || EmployeeWindow.offsetWidth\n  const parentHeight = vm.$el.parentNode.offsetHeight || EmployeeWindow.offsetHeight\n  if (vm.imgSize.width.indexOf('%') != -1) {\n    img_width = (parseInt(vm.imgSize.width) / 100) * parentWidth + 'px'\n  } else {\n    img_width = vm.imgSize.width\n  }\n\n  if (vm.imgSize.height.indexOf('%') != -1) {\n    img_height = (parseInt(vm.imgSize.height) / 100) * parentHeight + 'px'\n  } else {\n    img_height = vm.imgSize.height\n  }\n\n  if (vm.barSize.width.indexOf('%') != -1) {\n    bar_width = (parseInt(vm.barSize.width) / 100) * parentWidth + 'px'\n  } else {\n    bar_width = vm.barSize.width\n  }\n\n  if (vm.barSize.height.indexOf('%') != -1) {\n    bar_height = (parseInt(vm.barSize.height) / 100) * parentHeight + 'px'\n  } else {\n    bar_height = vm.barSize.height\n  }\n\n  return { imgWidth: img_width, imgHeight: img_height, barWidth: bar_width, barHeight: bar_height }\n}\n\nexport const _code_chars = [\n  1,\n  2,\n  3,\n  4,\n  5,\n  6,\n  7,\n  8,\n  9,\n  'a',\n  'b',\n  'c',\n  'd',\n  'e',\n  'f',\n  'g',\n  'h',\n  'i',\n  'j',\n  'k',\n  'l',\n  'm',\n  'n',\n  'o',\n  'p',\n  'q',\n  'r',\n  's',\n  't',\n  'u',\n  'v',\n  'w',\n  'x',\n  'y',\n  'z',\n  'A',\n  'B',\n  'C',\n  'D',\n  'E',\n  'F',\n  'G',\n  'H',\n  'I',\n  'J',\n  'K',\n  'L',\n  'M',\n  'N',\n  'O',\n  'P',\n  'Q',\n  'R',\n  'S',\n  'T',\n  'U',\n  'V',\n  'W',\n  'X',\n  'Y',\n  'Z'\n]\nexport const _code_color1 = ['#fffff0', '#f0ffff', '#f0fff0', '#fff0f0']\nexport const _code_color2 = ['#FF0033', '#006699', '#993366', '#FF9900', '#66CC66', '#FF33CC']\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/VerticalButtonGroup/index.vue",
    "content": "<template>\n  <el-button-group v-bind=\"$attrs\">\n    <slot></slot>\n  </el-button-group>\n</template>\n\n<script setup lang=\"ts\">\n/**\n * 垂直按钮组\n * Element官方的按钮组只支持水平显示，通过重写样式实现垂直布局\n */\ndefineOptions({ name: 'VerticalButtonGroup' })\n</script>\n\n<style scoped lang=\"scss\">\n.el-button-group {\n  display: inline-flex;\n  flex-direction: column;\n}\n\n.el-button-group > :deep(.el-button:first-child) {\n  border-bottom-color: var(--el-button-divide-border-color);\n  border-top-right-radius: var(--el-border-radius-base);\n  border-bottom-right-radius: 0;\n  border-bottom-left-radius: 0;\n}\n\n.el-button-group > :deep(.el-button:last-child) {\n  border-top-color: var(--el-button-divide-border-color);\n  border-top-right-radius: 0;\n  border-bottom-left-radius: var(--el-border-radius-base);\n  border-top-left-radius: 0;\n}\n\n.el-button-group :deep(.el-button--primary:not(:first-child, :last-child)) {\n  border-top-color: var(--el-button-divide-border-color);\n  border-bottom-color: var(--el-button-divide-border-color);\n}\n\n.el-button-group > :deep(.el-button:not(:last-child)) {\n  margin-right: 0;\n  margin-bottom: -1px;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/XButton/index.ts",
    "content": "import XButton from './src/XButton.vue'\nimport XTextButton from './src/XTextButton.vue'\n\nexport { XButton, XTextButton }\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/XButton/src/XButton.vue",
    "content": "<script lang=\"ts\" setup>\nimport { PropType } from 'vue'\nimport { propTypes } from '@/utils/propTypes'\n\ndefineOptions({ name: 'XButton' })\n\nconst props = defineProps({\n  modelValue: propTypes.bool.def(false),\n  loading: propTypes.bool.def(false),\n  preIcon: propTypes.string.def(''),\n  postIcon: propTypes.string.def(''),\n  title: propTypes.string.def(''),\n  type: propTypes.oneOf(['', 'primary', 'success', 'warning', 'danger', 'info']).def(''),\n  link: propTypes.bool.def(false),\n  circle: propTypes.bool.def(false),\n  round: propTypes.bool.def(false),\n  plain: propTypes.bool.def(false),\n  onClick: { type: Function as PropType<(...args) => any>, default: null }\n})\nconst getBindValue = computed(() => {\n  const delArr: string[] = ['title', 'preIcon', 'postIcon', 'onClick']\n  const attrs = useAttrs()\n  const obj = { ...attrs, ...props }\n  for (const key in obj) {\n    if (delArr.indexOf(key) !== -1) {\n      delete obj[key]\n    }\n  }\n  return obj\n})\n</script>\n\n<template>\n  <el-button v-bind=\"getBindValue\" @click=\"onClick\">\n    <Icon v-if=\"preIcon\" :icon=\"preIcon\" class=\"mr-1px\" />\n    {{ title ? title : '' }}\n    <Icon v-if=\"postIcon\" :icon=\"postIcon\" class=\"mr-1px\" />\n  </el-button>\n</template>\n<style lang=\"scss\" scoped>\n:deep(.el-button.is-text) {\n  padding: 8px 4px;\n  margin-left: 0;\n}\n\n:deep(.el-button.is-link) {\n  padding: 8px 4px;\n  margin-left: 0;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/XButton/src/XTextButton.vue",
    "content": "<script lang=\"ts\" setup>\nimport { propTypes } from '@/utils/propTypes'\nimport { PropType } from 'vue'\n\ndefineOptions({ name: 'XTextButton' })\n\nconst props = defineProps({\n  modelValue: propTypes.bool.def(false),\n  loading: propTypes.bool.def(false),\n  preIcon: propTypes.string.def(''),\n  postIcon: propTypes.string.def(''),\n  title: propTypes.string.def(''),\n  type: propTypes.oneOf(['', 'primary', 'success', 'warning', 'danger', 'info']).def('primary'),\n  circle: propTypes.bool.def(false),\n  round: propTypes.bool.def(false),\n  plain: propTypes.bool.def(false),\n  onClick: { type: Function as PropType<(...args) => any>, default: null }\n})\nconst getBindValue = computed(() => {\n  const delArr: string[] = ['title', 'preIcon', 'postIcon', 'onClick']\n  const attrs = useAttrs()\n  const obj = { ...attrs, ...props }\n  for (const key in obj) {\n    if (delArr.indexOf(key) !== -1) {\n      delete obj[key]\n    }\n  }\n  return obj\n})\n</script>\n\n<template>\n  <el-button link v-bind=\"getBindValue\" @click=\"onClick\">\n    <Icon v-if=\"preIcon\" :icon=\"preIcon\" class=\"mr-1px\" />\n    {{ title ? title : '' }}\n    <Icon v-if=\"postIcon\" :icon=\"postIcon\" class=\"mr-1px\" />\n  </el-button>\n</template>\n<style lang=\"scss\" scoped>\n:deep(.el-button.is-text) {\n  padding: 8px 4px;\n  margin-left: 0;\n}\n\n:deep(.el-button.is-link) {\n  padding: 8px 4px;\n  margin-left: 0;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/components/index.ts",
    "content": "import type { App } from 'vue'\nimport { Icon } from './Icon'\n\nexport const setupGlobCom = (app: App<Element>): void => {\n  app.component('Icon', Icon)\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/config/axios/config.ts",
    "content": "const config: {\n  base_url: string\n  result_code: number | string\n  default_headers: AxiosHeaders\n  request_timeout: number\n} = {\n  /**\n   * api请求基础路径\n   */\n  base_url: import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL,\n  /**\n   * 接口成功返回状态码\n   */\n  result_code: 200,\n\n  /**\n   * 接口请求超时时间\n   */\n  request_timeout: 30000,\n\n  /**\n   * 默认接口请求类型\n   * 可选值：application/x-www-form-urlencoded multipart/form-data\n   */\n  default_headers: 'application/json'\n}\n\nexport { config }\n"
  },
  {
    "path": "yshop-drink-vue3/src/config/axios/errorCode.ts",
    "content": "export default {\n  '401': '认证失败，无法访问系统资源',\n  '403': '当前操作没有权限',\n  '404': '访问资源不存在',\n  default: '系统未知错误，请反馈给管理员'\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/config/axios/index.ts",
    "content": "import { service } from './service'\n\nimport { config } from './config'\n\nconst { default_headers } = config\n\nconst request = (option: any) => {\n  const { url, method, params, data, headersType, responseType, ...config } = option\n  return service({\n    url: url,\n    method,\n    params,\n    data,\n    ...config,\n    responseType: responseType,\n    headers: {\n      'Content-Type': headersType || default_headers\n    }\n  })\n}\nexport default {\n  get: async <T = any>(option: any) => {\n    const res = await request({ method: 'GET', ...option })\n    return res.data as unknown as T\n  },\n  post: async <T = any>(option: any) => {\n    const res = await request({ method: 'POST', ...option })\n    return res.data as unknown as T\n  },\n  postOriginal: async (option: any) => {\n    const res = await request({ method: 'POST', ...option })\n    return res\n  },\n  delete: async <T = any>(option: any) => {\n    const res = await request({ method: 'DELETE', ...option })\n    return res.data as unknown as T\n  },\n  put: async <T = any>(option: any) => {\n    const res = await request({ method: 'PUT', ...option })\n    return res.data as unknown as T\n  },\n  download: async <T = any>(option: any) => {\n    const res = await request({ method: 'GET', responseType: 'blob', ...option })\n    return res as unknown as Promise<T>\n  },\n  upload: async <T = any>(option: any) => {\n    option.headersType = 'multipart/form-data'\n    const res = await request({ method: 'POST', ...option })\n    return res as unknown as Promise<T>\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/config/axios/service.ts",
    "content": "import axios, {\n  AxiosError,\n  AxiosInstance,\n  AxiosRequestHeaders,\n  AxiosResponse,\n  InternalAxiosRequestConfig\n} from 'axios'\n\nimport { ElMessage, ElMessageBox, ElNotification } from 'element-plus'\nimport qs from 'qs'\nimport { config } from '@/config/axios/config'\nimport { getAccessToken, getRefreshToken, getTenantId, removeToken, setToken } from '@/utils/auth'\nimport errorCode from './errorCode'\n\nimport { resetRouter } from '@/router'\nimport { deleteUserCache } from '@/hooks/web/useCache'\n\nconst tenantEnable = import.meta.env.VITE_APP_TENANT_ENABLE\nconst { result_code, base_url, request_timeout } = config\n\n// 需要忽略的提示。忽略后，自动 Promise.reject('error')\nconst ignoreMsgs = [\n  '无效的刷新令牌', // 刷新令牌被删除时，不用提示\n  '刷新令牌已过期' // 使用刷新令牌，刷新获取新的访问令牌时，结果因为过期失败，此时需要忽略。否则，会导致继续 401，无法跳转到登出界面\n]\n// 是否显示重新登录\nexport const isRelogin = { show: false }\n// Axios 无感知刷新令牌，参考 https://www.dashingdog.cn/article/11 与 https://segmentfault.com/a/1190000020210980 实现\n// 请求队列\nlet requestList: any[] = []\n// 是否正在刷新中\nlet isRefreshToken = false\n// 请求白名单，无须token的接口\nconst whiteList: string[] = ['/login', '/refresh-token']\n\n// 创建axios实例\nconst service: AxiosInstance = axios.create({\n  baseURL: base_url, // api 的 base_url\n  timeout: request_timeout, // 请求超时时间\n  withCredentials: false // 禁用 Cookie 等信息\n})\n\n// request拦截器\nservice.interceptors.request.use(\n  (config: InternalAxiosRequestConfig) => {\n    // 是否需要设置 token\n    let isToken = (config!.headers || {}).isToken === false\n    whiteList.some((v) => {\n      if (config.url) {\n        config.url.indexOf(v) > -1\n        return (isToken = false)\n      }\n    })\n    if (getAccessToken() && !isToken) {\n      ;(config as Recordable).headers.Authorization = 'Bearer ' + getAccessToken() // 让每个请求携带自定义token\n    }\n    // 设置租户\n    if (tenantEnable && tenantEnable === 'true') {\n      const tenantId = getTenantId()\n      if (tenantId) (config as Recordable).headers['tenant-id'] = tenantId\n    }\n    const params = config.params || {}\n    const data = config.data || false\n    if (\n      config.method?.toUpperCase() === 'POST' &&\n      (config.headers as AxiosRequestHeaders)['Content-Type'] ===\n        'application/x-www-form-urlencoded'\n    ) {\n      config.data = qs.stringify(data)\n    }\n    // get参数编码\n    if (config.method?.toUpperCase() === 'GET' && params) {\n      config.params = {}\n      const paramsStr = qs.stringify(params, { allowDots: true })\n      if (paramsStr) {\n        config.url = config.url + '?' + paramsStr\n      }\n    }\n    return config\n  },\n  (error: AxiosError) => {\n    // Do something with request error\n    console.log(error) // for debug\n    Promise.reject(error)\n  }\n)\n\n// response 拦截器\nservice.interceptors.response.use(\n  async (response: AxiosResponse<any>) => {\n    let { data } = response\n    const config = response.config\n    if (!data) {\n      // 返回“[HTTP]请求没有返回值”;\n      throw new Error()\n    }\n    const { t } = useI18n()\n    // 未设置状态码则默认成功状态\n    // 二进制数据则直接返回，例如说 Excel 导出\n    if (\n      response.request.responseType === 'blob' ||\n      response.request.responseType === 'arraybuffer'\n    ) {\n      // 注意：如果导出的响应为 json，说明可能失败了，不直接返回进行下载\n      if (response.data.type !== 'application/json') {\n        return response.data\n      }\n      data = await new Response(response.data).json()\n    }\n    const code = data.code || result_code\n    // 获取错误信息\n    const msg = data.msg || errorCode[code] || errorCode['default']\n    if (ignoreMsgs.indexOf(msg) !== -1) {\n      // 如果是忽略的错误码，直接返回 msg 异常\n      return Promise.reject(msg)\n    } else if (code === 401) {\n      // 如果未认证，并且未进行刷新令牌，说明可能是访问令牌过期了\n      if (!isRefreshToken) {\n        isRefreshToken = true\n        // 1. 如果获取不到刷新令牌，则只能执行登出操作\n        if (!getRefreshToken()) {\n          return handleAuthorized()\n        }\n        // 2. 进行刷新访问令牌\n        try {\n          const refreshTokenRes = await refreshToken()\n          // 2.1 刷新成功，则回放队列的请求 + 当前请求\n          setToken((await refreshTokenRes).data.data)\n          config.headers!.Authorization = 'Bearer ' + getAccessToken()\n          requestList.forEach((cb: any) => {\n            cb()\n          })\n          requestList = []\n          return service(config)\n        } catch (e) {\n          // 为什么需要 catch 异常呢？刷新失败时，请求因为 Promise.reject 触发异常。\n          // 2.2 刷新失败，只回放队列的请求\n          requestList.forEach((cb: any) => {\n            cb()\n          })\n          // 提示是否要登出。即不回放当前请求！不然会形成递归\n          return handleAuthorized()\n        } finally {\n          requestList = []\n          isRefreshToken = false\n        }\n      } else {\n        // 添加到队列，等待刷新获取到新的令牌\n        return new Promise((resolve) => {\n          requestList.push(() => {\n            config.headers!.Authorization = 'Bearer ' + getAccessToken() // 让每个请求携带自定义token 请根据实际情况自行修改\n            resolve(service(config))\n          })\n        })\n      }\n    } else if (code === 500) {\n      ElMessage.error(t('sys.api.errMsg500'))\n      return Promise.reject(new Error(msg))\n    } else if (code === 901) {\n      ElMessage.error({\n        offset: 300,\n        dangerouslyUseHTMLString: true,\n        message:\n          '<div>' +\n          t('sys.api.errMsg901') +\n          '</div>' +\n          '<div> &nbsp; </div>' +\n          '<div> https://www.yixiang.co/</div>' +\n          '<div> &nbsp; </div>' +\n          '<div></div>'\n      })\n      return Promise.reject(new Error(msg))\n    } else if (code !== 200) {\n      if (msg === '无效的刷新令牌') {\n        // hard coding：忽略这个提示，直接登出\n        console.log(msg)\n      } else {\n        ElNotification.error({ title: msg })\n      }\n      return Promise.reject('error')\n    } else {\n      return data\n    }\n  },\n  (error: AxiosError) => {\n    console.log('err' + error) // for debug\n    let { message } = error\n    const { t } = useI18n()\n    if (message === 'Network Error') {\n      message = t('sys.api.errorMessage')\n    } else if (message.includes('timeout')) {\n      message = t('sys.api.apiTimeoutMessage')\n    } else if (message.includes('Request failed with status code')) {\n      message = t('sys.api.apiRequestFailed') + message.substr(message.length - 3)\n    }\n    ElMessage.error(message)\n    return Promise.reject(error)\n  }\n)\n\nconst refreshToken = async () => {\n  axios.defaults.headers.common['tenant-id'] = getTenantId()\n  return await axios.post(base_url + '/system/auth/refresh-token?refreshToken=' + getRefreshToken())\n}\nconst handleAuthorized = () => {\n  const { t } = useI18n()\n  if (!isRelogin.show) {\n    // 如果已经到重新登录页面则不进行弹窗提示\n    if (window.location.href.includes('login?redirect=')) {\n      return\n    }\n    isRelogin.show = true\n    ElMessageBox.confirm(t('sys.api.timeoutMessage'), t('common.confirmTitle'), {\n      showCancelButton: false,\n      closeOnClickModal: false,\n      showClose: false,\n      confirmButtonText: t('login.relogin'),\n      type: 'warning'\n    }).then(() => {\n      resetRouter() // 重置静态路由表\n      deleteUserCache() // 删除用户缓存\n      removeToken()\n      isRelogin.show = false\n      // 干掉token后再走一次路由让它过router.beforeEach的校验\n      window.location.href = window.location.href\n    })\n  }\n  return Promise.reject(t('sys.api.timeoutMessage'))\n}\nexport { service }\n"
  },
  {
    "path": "yshop-drink-vue3/src/directives/index.ts",
    "content": "import type { App } from 'vue'\nimport { hasRole } from './permission/hasRole'\nimport { hasPermi } from './permission/hasPermi'\n\n/**\n * 导出指令：v-xxx\n * @methods hasRole 用户权限，用法: v-hasRole\n * @methods hasPermi 按钮权限，用法: v-hasPermi\n */\nexport const setupAuth = (app: App<Element>) => {\n  hasRole(app)\n  hasPermi(app)\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/directives/permission/hasPermi.ts",
    "content": "import type { App } from 'vue'\nimport { CACHE_KEY, useCache } from '@/hooks/web/useCache'\n\nconst { t } = useI18n() // 国际化\n\nexport function hasPermi(app: App<Element>) {\n  app.directive('hasPermi', (el, binding) => {\n    const { wsCache } = useCache()\n    const { value } = binding\n    const all_permission = '*:*:*'\n    const permissions = wsCache.get(CACHE_KEY.USER).permissions\n\n    if (value && value instanceof Array && value.length > 0) {\n      const permissionFlag = value\n\n      const hasPermissions = permissions.some((permission: string) => {\n        return all_permission === permission || permissionFlag.includes(permission)\n      })\n\n      if (!hasPermissions) {\n        el.parentNode && el.parentNode.removeChild(el)\n      }\n    } else {\n      throw new Error(t('permission.hasPermission'))\n    }\n  })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/directives/permission/hasRole.ts",
    "content": "import type { App } from 'vue'\nimport { CACHE_KEY, useCache } from '@/hooks/web/useCache'\n\nconst { t } = useI18n() // 国际化\n\nexport function hasRole(app: App<Element>) {\n  app.directive('hasRole', (el, binding) => {\n    const { wsCache } = useCache()\n    const { value } = binding\n    const super_admin = 'admin'\n    const roles = wsCache.get(CACHE_KEY.USER).roles\n\n    if (value && value instanceof Array && value.length > 0) {\n      const roleFlag = value\n\n      const hasRole = roles.some((role: string) => {\n        return super_admin === role || roleFlag.includes(role)\n      })\n\n      if (!hasRole) {\n        el.parentNode && el.parentNode.removeChild(el)\n      }\n    } else {\n      throw new Error(t('permission.hasRole'))\n    }\n  })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/hooks/event/useScrollTo.ts",
    "content": "export interface ScrollToParams {\n  el: HTMLElement\n  to: number\n  position: string\n  duration?: number\n  callback?: () => void\n}\n\nconst easeInOutQuad = (t: number, b: number, c: number, d: number) => {\n  t /= d / 2\n  if (t < 1) {\n    return (c / 2) * t * t + b\n  }\n  t--\n  return (-c / 2) * (t * (t - 2) - 1) + b\n}\nconst move = (el: HTMLElement, position: string, amount: number) => {\n  el[position] = amount\n}\n\nexport function useScrollTo({\n  el,\n  position = 'scrollLeft',\n  to,\n  duration = 500,\n  callback\n}: ScrollToParams) {\n  const isActiveRef = ref(false)\n  const start = el[position]\n  const change = to - start\n  const increment = 20\n  let currentTime = 0\n\n  function animateScroll() {\n    if (!unref(isActiveRef)) {\n      return\n    }\n    currentTime += increment\n    const val = easeInOutQuad(currentTime, start, change, duration)\n    move(el, position, val)\n    if (currentTime < duration && unref(isActiveRef)) {\n      requestAnimationFrame(animateScroll)\n    } else {\n      if (callback) {\n        callback()\n      }\n    }\n  }\n\n  function run() {\n    isActiveRef.value = true\n    animateScroll()\n  }\n\n  function stop() {\n    isActiveRef.value = false\n  }\n\n  return { start: run, stop }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/hooks/web/useCache.ts",
    "content": "/**\n * 配置浏览器本地存储的方式，可直接存储对象数组。\n */\n\nimport WebStorageCache from 'web-storage-cache'\n\ntype CacheType = 'localStorage' | 'sessionStorage'\n\nexport const CACHE_KEY = {\n  // 用户相关\n  ROLE_ROUTERS: 'roleRouters',\n  USER: 'user',\n  // 系统设置\n  IS_DARK: 'isDark',\n  LANG: 'lang',\n  THEME: 'theme',\n  LAYOUT: 'layout',\n  DICT_CACHE: 'dictCache',\n  // 登录表单\n  LoginForm: 'loginForm',\n  TenantId: 'tenantId'\n}\n\nexport const useCache = (type: CacheType = 'localStorage') => {\n  const wsCache: WebStorageCache = new WebStorageCache({\n    storage: type\n  })\n\n  return {\n    wsCache\n  }\n}\n\nexport const deleteUserCache = () => {\n  const { wsCache } = useCache()\n  wsCache.delete(CACHE_KEY.USER)\n  wsCache.delete(CACHE_KEY.ROLE_ROUTERS)\n  // 注意，不要清理 LoginForm 登录表单\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/hooks/web/useConfigGlobal.ts",
    "content": "import { ConfigGlobalTypes } from '@/types/configGlobal'\n\nexport const useConfigGlobal = () => {\n  const configGlobal = inject('configGlobal', {}) as ConfigGlobalTypes\n\n  return {\n    configGlobal\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/hooks/web/useCrudSchemas.ts",
    "content": "import { reactive } from 'vue'\nimport { AxiosPromise } from 'axios'\nimport { findIndex } from '@/utils'\nimport { eachTree, filter, treeMap } from '@/utils/tree'\nimport { getBoolDictOptions, getDictOptions, getIntDictOptions } from '@/utils/dict'\n\nimport { FormSchema } from '@/types/form'\nimport { TableColumn } from '@/types/table'\nimport { DescriptionsSchema } from '@/types/descriptions'\nimport { ComponentOptions, ComponentProps } from '@/types/components'\nimport { DictTag } from '@/components/DictTag'\nimport { cloneDeep, merge } from 'lodash-es'\n\nexport type CrudSchema = Omit<TableColumn, 'children'> & {\n  isSearch?: boolean // 是否在查询显示\n  search?: CrudSearchParams // 查询的详细配置\n  isTable?: boolean // 是否在列表显示\n  table?: CrudTableParams // 列表的详细配置\n  isForm?: boolean // 是否在表单显示\n  form?: CrudFormParams // 表单的详细配置\n  isDetail?: boolean // 是否在详情显示\n  detail?: CrudDescriptionsParams // 详情的详细配置\n  children?: CrudSchema[]\n  dictType?: string // 字典类型\n  dictClass?: 'string' | 'number' | 'boolean' // 字典数据类型 string | number | boolean\n}\n\ntype CrudSearchParams = {\n  // 是否显示在查询项\n  show?: boolean\n  // 接口\n  api?: () => Promise<any>\n  // 搜索字段\n  field?: string\n} & Omit<FormSchema, 'field'>\n\ntype CrudTableParams = {\n  // 是否显示表头\n  show?: boolean\n  // 列宽配置\n  width?: number | string\n  // 列是否固定在左侧或者右侧\n  fixed?: 'left' | 'right'\n} & Omit<FormSchema, 'field'>\ntype CrudFormParams = {\n  // 是否显示表单项\n  show?: boolean\n  // 接口\n  api?: () => Promise<any>\n} & Omit<FormSchema, 'field'>\n\ntype CrudDescriptionsParams = {\n  // 是否显示表单项\n  show?: boolean\n} & Omit<DescriptionsSchema, 'field'>\n\ninterface AllSchemas {\n  searchSchema: FormSchema[]\n  tableColumns: TableColumn[]\n  formSchema: FormSchema[]\n  detailSchema: DescriptionsSchema[]\n}\n\nconst { t } = useI18n()\n\n// 过滤所有结构\nexport const useCrudSchemas = (\n  crudSchema: CrudSchema[]\n): {\n  allSchemas: AllSchemas\n} => {\n  // 所有结构数据\n  const allSchemas = reactive<AllSchemas>({\n    searchSchema: [],\n    tableColumns: [],\n    formSchema: [],\n    detailSchema: []\n  })\n\n  const searchSchema = filterSearchSchema(crudSchema, allSchemas)\n  allSchemas.searchSchema = searchSchema || []\n\n  const tableColumns = filterTableSchema(crudSchema)\n  allSchemas.tableColumns = tableColumns || []\n\n  const formSchema = filterFormSchema(crudSchema, allSchemas)\n  allSchemas.formSchema = formSchema\n\n  const detailSchema = filterDescriptionsSchema(crudSchema)\n  allSchemas.detailSchema = detailSchema\n\n  return {\n    allSchemas\n  }\n}\n\n// 过滤 Search 结构\nconst filterSearchSchema = (crudSchema: CrudSchema[], allSchemas: AllSchemas): FormSchema[] => {\n  const searchSchema: FormSchema[] = []\n\n  // 获取字典列表队列\n  const searchRequestTask: Array<() => Promise<void>> = []\n  eachTree(crudSchema, (schemaItem: CrudSchema) => {\n    // 判断是否显示\n    if (schemaItem?.isSearch || schemaItem.search?.show) {\n      let component = schemaItem?.search?.component || 'Input'\n      const options: ComponentOptions[] = []\n      let comonentProps: ComponentProps = {}\n      if (schemaItem.dictType) {\n        const allOptions: ComponentOptions = { label: '全部', value: '' }\n        options.push(allOptions)\n        getDictOptions(schemaItem.dictType).forEach((dict) => {\n          options.push(dict)\n        })\n        comonentProps = {\n          options: options\n        }\n        if (!schemaItem.search?.component) component = 'Select'\n      }\n\n      // updated by AKing: 解决了当使用默认的dict选项时，form中事件不能触发的问题\n      const searchSchemaItem = merge(\n        {\n          // 默认为 input\n          component,\n          ...schemaItem.search,\n          field: schemaItem.field,\n          label: schemaItem.search?.label || schemaItem.label\n        },\n        { componentProps: comonentProps }\n      )\n      if (searchSchemaItem.api) {\n        searchRequestTask.push(async () => {\n          const res = await (searchSchemaItem.api as () => AxiosPromise)()\n          if (res) {\n            const index = findIndex(allSchemas.searchSchema, (v: FormSchema) => {\n              return v.field === searchSchemaItem.field\n            })\n            if (index !== -1) {\n              allSchemas.searchSchema[index]!.componentProps!.options = filterOptions(\n                res,\n                searchSchemaItem.componentProps.optionsAlias?.labelField\n              )\n            }\n          }\n        })\n      }\n      // 删除不必要的字段\n      delete searchSchemaItem.show\n\n      searchSchema.push(searchSchemaItem)\n    }\n  })\n  for (const task of searchRequestTask) {\n    task()\n  }\n  return searchSchema\n}\n\n// 过滤 table 结构\nconst filterTableSchema = (crudSchema: CrudSchema[]): TableColumn[] => {\n  const tableColumns = treeMap<CrudSchema>(crudSchema, {\n    conversion: (schema: CrudSchema) => {\n      if (schema?.isTable !== false && schema?.table?.show !== false) {\n        // add by 芋艿：增加对 dict 字典数据的支持\n        if (!schema.formatter && schema.dictType) {\n          schema.formatter = (_: Recordable, __: TableColumn, cellValue: any) => {\n            return h(DictTag, {\n              type: schema.dictType!, // ! 表示一定不为空\n              value: cellValue\n            })\n          }\n        }\n        return {\n          ...schema.table,\n          ...schema\n        }\n      }\n    }\n  })\n\n  // 第一次过滤会有 undefined 所以需要二次过滤\n  return filter<TableColumn>(tableColumns as TableColumn[], (data) => {\n    if (data.children === void 0) {\n      delete data.children\n    }\n    return !!data.field\n  })\n}\n\n// 过滤 form 结构\nconst filterFormSchema = (crudSchema: CrudSchema[], allSchemas: AllSchemas): FormSchema[] => {\n  const formSchema: FormSchema[] = []\n\n  // 获取字典列表队列\n  const formRequestTask: Array<() => Promise<void>> = []\n\n  eachTree(crudSchema, (schemaItem: CrudSchema) => {\n    // 判断是否显示\n    if (schemaItem?.isForm !== false && schemaItem?.form?.show !== false) {\n      let component = schemaItem?.form?.component || 'Input'\n      let defaultValue: any = ''\n      if (schemaItem.form?.value) {\n        defaultValue = schemaItem.form?.value\n      } else {\n        if (component === 'InputNumber') {\n          defaultValue = 0\n        }\n      }\n      let comonentProps: ComponentProps = {}\n      if (schemaItem.dictType) {\n        const options: ComponentOptions[] = []\n        if (schemaItem.dictClass && schemaItem.dictClass === 'number') {\n          getIntDictOptions(schemaItem.dictType).forEach((dict) => {\n            options.push(dict)\n          })\n        } else if (schemaItem.dictClass && schemaItem.dictClass === 'boolean') {\n          getBoolDictOptions(schemaItem.dictType).forEach((dict) => {\n            options.push(dict)\n          })\n        } else {\n          getDictOptions(schemaItem.dictType).forEach((dict) => {\n            options.push(dict)\n          })\n        }\n        comonentProps = {\n          options: options\n        }\n        if (!(schemaItem.form && schemaItem.form.component)) component = 'Select'\n      }\n\n      // updated by AKing: 解决了当使用默认的dict选项时，form中事件不能触发的问题\n      const formSchemaItem = merge(\n        {\n          // 默认为 input\n          component,\n          value: defaultValue,\n          ...schemaItem.form,\n          field: schemaItem.field,\n          label: schemaItem.form?.label || schemaItem.label\n        },\n        { componentProps: comonentProps }\n      )\n\n      if (formSchemaItem.api) {\n        formRequestTask.push(async () => {\n          const res = await (formSchemaItem.api as () => AxiosPromise)()\n          if (res) {\n            const index = findIndex(allSchemas.formSchema, (v: FormSchema) => {\n              return v.field === formSchemaItem.field\n            })\n            if (index !== -1) {\n              allSchemas.formSchema[index]!.componentProps!.options = filterOptions(\n                res,\n                formSchemaItem.componentProps.optionsAlias?.labelField\n              )\n            }\n          }\n        })\n      }\n\n      // 删除不必要的字段\n      delete formSchemaItem.show\n\n      formSchema.push(formSchemaItem)\n    }\n  })\n\n  for (const task of formRequestTask) {\n    task()\n  }\n  return formSchema\n}\n\n// 过滤 descriptions 结构\nconst filterDescriptionsSchema = (crudSchema: CrudSchema[]): DescriptionsSchema[] => {\n  const descriptionsSchema: FormSchema[] = []\n\n  eachTree(crudSchema, (schemaItem: CrudSchema) => {\n    // 判断是否显示\n    if (schemaItem?.isDetail !== false && schemaItem.detail?.show !== false) {\n      const descriptionsSchemaItem = {\n        ...schemaItem.detail,\n        field: schemaItem.field,\n        label: schemaItem.detail?.label || schemaItem.label\n      }\n      if (schemaItem.dictType) {\n        descriptionsSchemaItem.dictType = schemaItem.dictType\n      }\n      if (schemaItem.detail?.dateFormat || schemaItem.formatter == 'formatDate') {\n        // 优先使用 detail 下的配置，如果没有默认为 YYYY-MM-DD HH:mm:ss\n        descriptionsSchemaItem.dateFormat = schemaItem?.detail?.dateFormat\n          ? schemaItem?.detail?.dateFormat\n          : 'YYYY-MM-DD HH:mm:ss'\n      }\n\n      // 删除不必要的字段\n      delete descriptionsSchemaItem.show\n\n      descriptionsSchema.push(descriptionsSchemaItem)\n    }\n  })\n\n  return descriptionsSchema\n}\n\n// 给options添加国际化\nconst filterOptions = (options: Recordable, labelField?: string) => {\n  return options?.map((v: Recordable) => {\n    if (labelField) {\n      v['labelField'] = t(v.labelField)\n    } else {\n      v['label'] = t(v.label)\n    }\n    return v\n  })\n}\n\n// 将 tableColumns 指定 fields 放到最前面\nexport const sortTableColumns = (tableColumns: TableColumn[], field: string) => {\n  const fieldIndex = tableColumns.findIndex((item) => item.field === field)\n  const fieldColumn = cloneDeep(tableColumns[fieldIndex])\n  tableColumns.splice(fieldIndex, 1)\n  // 添加到开头\n  tableColumns.unshift(fieldColumn)\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/hooks/web/useDesign.ts",
    "content": "import variables from '@/styles/global.module.scss'\n\nexport const useDesign = () => {\n  const scssVariables = variables\n\n  /**\n   * @param scope 类名\n   * @returns 返回空间名-类名\n   */\n  const getPrefixCls = (scope: string) => {\n    return `${scssVariables.namespace}-${scope}`\n  }\n\n  return {\n    variables: scssVariables,\n    getPrefixCls\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/hooks/web/useEmitt.ts",
    "content": "import mitt from 'mitt'\n\ninterface Option {\n  name: string // 事件名称\n  callback: Fn // 回调\n}\n\nconst emitter = mitt()\n\nexport const useEmitt = (option?: Option) => {\n  if (option) {\n    emitter.on(option.name, option.callback)\n\n    onBeforeUnmount(() => {\n      emitter.off(option.name)\n    })\n  }\n\n  return {\n    emitter\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/hooks/web/useForm.ts",
    "content": "import type { Form, FormExpose } from '@/components/Form'\nimport type { ElForm } from 'element-plus'\nimport type { FormProps } from '@/components/Form/src/types'\nimport { FormSchema, FormSetPropsType } from '@/types/form'\n\nexport const useForm = (props?: FormProps) => {\n  // From实例\n  const formRef = ref<typeof Form & FormExpose>()\n\n  // ElForm实例\n  const elFormRef = ref<ComponentRef<typeof ElForm>>()\n\n  /**\n   * @param ref Form实例\n   * @param elRef ElForm实例\n   */\n  const register = (ref: typeof Form & FormExpose, elRef: ComponentRef<typeof ElForm>) => {\n    formRef.value = ref\n    elFormRef.value = elRef\n  }\n\n  const getForm = async () => {\n    await nextTick()\n    const form = unref(formRef)\n    if (!form) {\n      console.error('The form is not registered. Please use the register method to register')\n    }\n    return form\n  }\n\n  // 一些内置的方法\n  const methods: {\n    setProps: (props: Recordable) => void\n    setValues: (data: Recordable) => void\n    getFormData: <T = Recordable | undefined>() => Promise<T>\n    setSchema: (schemaProps: FormSetPropsType[]) => void\n    addSchema: (formSchema: FormSchema, index?: number) => void\n    delSchema: (field: string) => void\n  } = {\n    setProps: async (props: FormProps = {}) => {\n      const form = await getForm()\n      form?.setProps(props)\n      if (props.model) {\n        form?.setValues(props.model)\n      }\n    },\n\n    setValues: async (data: Recordable) => {\n      const form = await getForm()\n      form?.setValues(data)\n    },\n\n    /**\n     * @param schemaProps 需要设置的schemaProps\n     */\n    setSchema: async (schemaProps: FormSetPropsType[]) => {\n      const form = await getForm()\n      form?.setSchema(schemaProps)\n    },\n\n    /**\n     * @param formSchema 需要新增数据\n     * @param index 在哪里新增\n     */\n    addSchema: async (formSchema: FormSchema, index?: number) => {\n      const form = await getForm()\n      form?.addSchema(formSchema, index)\n    },\n\n    /**\n     * @param field 删除哪个数据\n     */\n    delSchema: async (field: string) => {\n      const form = await getForm()\n      form?.delSchema(field)\n    },\n\n    /**\n     * @returns form data\n     */\n    getFormData: async <T = Recordable>(): Promise<T> => {\n      const form = await getForm()\n      return form?.formModel as T\n    }\n  }\n\n  props && methods.setProps(props)\n\n  return {\n    register,\n    elFormRef,\n    methods\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/hooks/web/useGuide.ts",
    "content": "import { Config, driver } from 'driver.js'\nimport 'driver.js/dist/driver.css'\nimport { useDesign } from '@/hooks/web/useDesign'\nimport { useI18n } from '@/hooks/web/useI18n'\n\nconst { t } = useI18n()\n\nconst { variables } = useDesign()\n\nexport const useGuide = (options?: Config) => {\n  const driverObj = driver(\n    options || {\n      showProgress: true,\n      nextBtnText: t('common.nextLabel'),\n      prevBtnText: t('common.prevLabel'),\n      doneBtnText: t('common.doneLabel'),\n      steps: [\n        {\n          element: `#${variables.namespace}-menu`,\n          popover: {\n            title: t('common.menu'),\n            description: t('common.menuDes'),\n            side: 'right'\n          }\n        },\n        {\n          element: `#${variables.namespace}-tool-header`,\n          popover: {\n            title: t('common.tool'),\n            description: t('common.toolDes'),\n            side: 'left'\n          }\n        },\n        {\n          element: `#${variables.namespace}-tags-view`,\n          popover: {\n            title: t('common.tagsView'),\n            description: t('common.tagsViewDes'),\n            side: 'bottom'\n          }\n        }\n      ]\n    }\n  )\n\n  return {\n    ...driverObj\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/hooks/web/useI18n.ts",
    "content": "import { i18n } from '@/plugins/vueI18n'\n\ntype I18nGlobalTranslation = {\n  (key: string): string\n  (key: string, locale: string): string\n  (key: string, locale: string, list: unknown[]): string\n  (key: string, locale: string, named: Record<string, unknown>): string\n  (key: string, list: unknown[]): string\n  (key: string, named: Record<string, unknown>): string\n}\n\ntype I18nTranslationRestParameters = [string, any]\n\nconst getKey = (namespace: string | undefined, key: string) => {\n  if (!namespace) {\n    return key\n  }\n  if (key.startsWith(namespace)) {\n    return key\n  }\n  return `${namespace}.${key}`\n}\n\nexport const useI18n = (\n  namespace?: string\n): {\n  t: I18nGlobalTranslation\n} => {\n  const normalFn = {\n    t: (key: string) => {\n      return getKey(namespace, key)\n    }\n  }\n\n  if (!i18n) {\n    return normalFn\n  }\n\n  const { t, ...methods } = i18n.global\n\n  const tFn: I18nGlobalTranslation = (key: string, ...arg: any[]) => {\n    if (!key) return ''\n    if (!key.includes('.') && !namespace) return key\n    //@ts-ignore\n    return t(getKey(namespace, key), ...(arg as I18nTranslationRestParameters))\n  }\n  return {\n    ...methods,\n    t: tFn\n  }\n}\n\nexport const t = (key: string) => key\n"
  },
  {
    "path": "yshop-drink-vue3/src/hooks/web/useIcon.ts",
    "content": "import { h } from 'vue'\nimport type { VNode } from 'vue'\nimport { Icon } from '@/components/Icon'\nimport { IconTypes } from '@/types/icon'\n\nexport const useIcon = (props: IconTypes): VNode => {\n  return h(Icon, props)\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/hooks/web/useLocale.ts",
    "content": "import { i18n } from '@/plugins/vueI18n'\nimport { useLocaleStoreWithOut } from '@/store/modules/locale'\nimport { setHtmlPageLang } from '@/plugins/vueI18n/helper'\n\nconst setI18nLanguage = (locale: LocaleType) => {\n  const localeStore = useLocaleStoreWithOut()\n\n  if (i18n.mode === 'legacy') {\n    i18n.global.locale = locale\n  } else {\n    ;(i18n.global.locale as any).value = locale\n  }\n  localeStore.setCurrentLocale({\n    lang: locale\n  })\n  setHtmlPageLang(locale)\n}\n\nexport const useLocale = () => {\n  // Switching the language will change the locale of useI18n\n  // And submit to configuration modification\n  const changeLocale = async (locale: LocaleType) => {\n    const globalI18n = i18n.global\n\n    const langModule = await import(`../../locales/${locale}.ts`)\n\n    globalI18n.setLocaleMessage(locale, langModule.default)\n\n    setI18nLanguage(locale)\n  }\n\n  return {\n    changeLocale\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/hooks/web/useMessage.ts",
    "content": "import { ElMessage, ElMessageBox, ElNotification } from 'element-plus'\nimport { useI18n } from './useI18n'\nexport const useMessage = () => {\n  const { t } = useI18n()\n  return {\n    // 消息提示\n    info(content: string) {\n      ElMessage.info(content)\n    },\n    // 错误消息\n    error(content: string) {\n      ElMessage.error(content)\n    },\n    // 成功消息\n    success(content: string) {\n      ElMessage.success(content)\n    },\n    // 警告消息\n    warning(content: string) {\n      ElMessage.warning(content)\n    },\n    // 弹出提示\n    alert(content: string) {\n      ElMessageBox.alert(content, t('common.confirmTitle'))\n    },\n    // 错误提示\n    alertError(content: string) {\n      ElMessageBox.alert(content, t('common.confirmTitle'), { type: 'error' })\n    },\n    // 成功提示\n    alertSuccess(content: string) {\n      ElMessageBox.alert(content, t('common.confirmTitle'), { type: 'success' })\n    },\n    // 警告提示\n    alertWarning(content: string) {\n      ElMessageBox.alert(content, t('common.confirmTitle'), { type: 'warning' })\n    },\n    // 通知提示\n    notify(content: string) {\n      ElNotification.info(content)\n    },\n    // 错误通知\n    notifyError(content: string) {\n      ElNotification.error(content)\n    },\n    // 成功通知\n    notifySuccess(content: string) {\n      ElNotification.success(content)\n    },\n    // 警告通知\n    notifyWarning(content: string) {\n      ElNotification.warning(content)\n    },\n    // 确认窗体\n    confirm(content: string, tip?: string) {\n      return ElMessageBox.confirm(content, tip ? tip : t('common.confirmTitle'), {\n        confirmButtonText: t('common.ok'),\n        cancelButtonText: t('common.cancel'),\n        type: 'warning'\n      })\n    },\n    // 删除窗体\n    delConfirm(content?: string, tip?: string) {\n      return ElMessageBox.confirm(\n        content ? content : t('common.delMessage'),\n        tip ? tip : t('common.confirmTitle'),\n        {\n          confirmButtonText: t('common.ok'),\n          cancelButtonText: t('common.cancel'),\n          type: 'warning'\n        }\n      )\n    },\n    // 导出窗体\n    exportConfirm(content?: string, tip?: string) {\n      return ElMessageBox.confirm(\n        content ? content : t('common.exportMessage'),\n        tip ? tip : t('common.confirmTitle'),\n        {\n          confirmButtonText: t('common.ok'),\n          cancelButtonText: t('common.cancel'),\n          type: 'warning'\n        }\n      )\n    },\n    // 提交内容\n    prompt(content: string, tip: string) {\n      return ElMessageBox.prompt(content, tip, {\n        confirmButtonText: t('common.ok'),\n        cancelButtonText: t('common.cancel'),\n        type: 'warning'\n      })\n    }\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/hooks/web/useNProgress.ts",
    "content": "import { useCssVar } from '@vueuse/core'\nimport type { NProgressOptions } from 'nprogress'\nimport NProgress from 'nprogress'\nimport 'nprogress/nprogress.css'\n\nconst primaryColor = useCssVar('--el-color-primary', document.documentElement)\n\nexport const useNProgress = () => {\n  NProgress.configure({ showSpinner: false } as NProgressOptions)\n\n  const initColor = async () => {\n    await nextTick()\n    const bar = document.getElementById('nprogress')?.getElementsByClassName('bar')[0] as ElRef\n    if (bar) {\n      bar.style.background = unref(primaryColor.value)\n    }\n  }\n\n  initColor()\n\n  const start = () => {\n    NProgress.start()\n  }\n\n  const done = () => {\n    NProgress.done()\n  }\n\n  return {\n    start,\n    done\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/hooks/web/useNetwork.ts",
    "content": "import { ref, onBeforeUnmount } from 'vue'\n\nconst useNetwork = () => {\n  const online = ref(true)\n\n  const updateNetwork = () => {\n    online.value = navigator.onLine\n  }\n\n  window.addEventListener('online', updateNetwork)\n  window.addEventListener('offline', updateNetwork)\n\n  onBeforeUnmount(() => {\n    window.removeEventListener('online', updateNetwork)\n    window.removeEventListener('offline', updateNetwork)\n  })\n\n  return { online }\n}\n\nexport { useNetwork }\n"
  },
  {
    "path": "yshop-drink-vue3/src/hooks/web/useNow.ts",
    "content": "import { dateUtil } from '@/utils/dateUtil'\nimport { reactive, toRefs } from 'vue'\nimport { tryOnMounted, tryOnUnmounted } from '@vueuse/core'\n\nexport const useNow = (immediate = true) => {\n  let timer: IntervalHandle\n\n  const state = reactive({\n    year: 0,\n    month: 0,\n    week: '',\n    day: 0,\n    hour: '',\n    minute: '',\n    second: 0,\n    meridiem: ''\n  })\n\n  const update = () => {\n    const now = dateUtil()\n\n    const h = now.format('HH')\n    const m = now.format('mm')\n    const s = now.get('s')\n\n    state.year = now.get('y')\n    state.month = now.get('M') + 1\n    state.week = '星期' + ['日', '一', '二', '三', '四', '五', '六'][now.day()]\n    state.day = now.get('date')\n    state.hour = h\n    state.minute = m\n    state.second = s\n\n    state.meridiem = now.format('A')\n  }\n\n  function start() {\n    update()\n    clearInterval(timer)\n    timer = setInterval(() => update(), 1000)\n  }\n\n  function stop() {\n    clearInterval(timer)\n  }\n\n  tryOnMounted(() => {\n    immediate && start()\n  })\n\n  tryOnUnmounted(() => {\n    stop()\n  })\n\n  return {\n    ...toRefs(state),\n    start,\n    stop\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/hooks/web/usePageLoading.ts",
    "content": "import { useAppStoreWithOut } from '@/store/modules/app'\n\nconst appStore = useAppStoreWithOut()\n\nexport const usePageLoading = () => {\n  const loadStart = () => {\n    appStore.setPageLoading(true)\n  }\n\n  const loadDone = () => {\n    appStore.setPageLoading(false)\n  }\n\n  return {\n    loadStart,\n    loadDone\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/hooks/web/useTable.ts",
    "content": "import download from '@/utils/download'\nimport { Table, TableExpose } from '@/components/Table'\nimport { ElMessage, ElMessageBox, ElTable } from 'element-plus'\nimport { computed, nextTick, reactive, ref, unref, watch } from 'vue'\nimport type { TableProps } from '@/components/Table/src/types'\n\nimport { TableSetPropsType } from '@/types/table'\n\nconst { t } = useI18n()\ninterface ResponseType<T = any> {\n  list: T[]\n  total?: number\n}\n\ninterface UseTableConfig<T = any> {\n  getListApi: (option: any) => Promise<T>\n  delListApi?: (option: any) => Promise<T>\n  exportListApi?: (option: any) => Promise<T>\n  // 返回数据格式配置\n  response?: ResponseType\n  // 默认传递的参数\n  defaultParams?: Recordable\n  props?: TableProps\n}\n\ninterface TableObject<T = any> {\n  pageSize: number\n  currentPage: number\n  total: number\n  tableList: T[]\n  params: any\n  loading: boolean\n  exportLoading: boolean\n  currentRow: Nullable<T>\n}\n\nexport const useTable = <T = any>(config?: UseTableConfig<T>) => {\n  const tableObject = reactive<TableObject<T>>({\n    // 页数\n    pageSize: 10,\n    // 当前页\n    currentPage: 1,\n    // 总条数\n    total: 10,\n    // 表格数据\n    tableList: [],\n    // AxiosConfig 配置\n    params: {\n      ...(config?.defaultParams || {})\n    },\n    // 加载中\n    loading: true,\n    // 导出加载中\n    exportLoading: false,\n    // 当前行的数据\n    currentRow: null\n  })\n\n  const paramsObj = computed(() => {\n    return {\n      ...tableObject.params,\n      pageSize: tableObject.pageSize,\n      pageNo: tableObject.currentPage\n    }\n  })\n\n  watch(\n    () => tableObject.currentPage,\n    () => {\n      methods.getList()\n    }\n  )\n\n  watch(\n    () => tableObject.pageSize,\n    () => {\n      // 当前页不为1时，修改页数后会导致多次调用getList方法\n      if (tableObject.currentPage === 1) {\n        methods.getList()\n      } else {\n        tableObject.currentPage = 1\n        methods.getList()\n      }\n    }\n  )\n\n  // Table实例\n  const tableRef = ref<typeof Table & TableExpose>()\n\n  // ElTable实例\n  const elTableRef = ref<ComponentRef<typeof ElTable>>()\n\n  const register = (ref: typeof Table & TableExpose, elRef: ComponentRef<typeof ElTable>) => {\n    tableRef.value = ref\n    elTableRef.value = elRef\n  }\n\n  const getTable = async () => {\n    await nextTick()\n    const table = unref(tableRef)\n    if (!table) {\n      console.error('The table is not registered. Please use the register method to register')\n    }\n    return table\n  }\n\n  const delData = async (ids: string | number | string[] | number[]) => {\n    let idsLength = 1\n    if (ids instanceof Array) {\n      idsLength = ids.length\n      await Promise.all(\n        ids.map(async (id: string | number) => {\n          await (config?.delListApi && config?.delListApi(id))\n        })\n      )\n    } else {\n      await (config?.delListApi && config?.delListApi(ids))\n    }\n    ElMessage.success(t('common.delSuccess'))\n\n    // 计算出临界点\n    tableObject.currentPage =\n      tableObject.total % tableObject.pageSize === idsLength || tableObject.pageSize === 1\n        ? tableObject.currentPage > 1\n          ? tableObject.currentPage - 1\n          : tableObject.currentPage\n        : tableObject.currentPage\n    await methods.getList()\n  }\n\n  const methods = {\n    getList: async () => {\n      tableObject.loading = true\n      const res = await config?.getListApi(unref(paramsObj)).finally(() => {\n        tableObject.loading = false\n      })\n      if (res) {\n        tableObject.tableList = (res as unknown as ResponseType).list\n        tableObject.total = (res as unknown as ResponseType).total ?? 0\n      }\n    },\n    setProps: async (props: TableProps = {}) => {\n      const table = await getTable()\n      table?.setProps(props)\n    },\n    setColumn: async (columnProps: TableSetPropsType[]) => {\n      const table = await getTable()\n      table?.setColumn(columnProps)\n    },\n    getSelections: async () => {\n      const table = await getTable()\n      return (table?.selections || []) as T[]\n    },\n    // 与Search组件结合\n    setSearchParams: (data: Recordable) => {\n      tableObject.params = Object.assign(tableObject.params, {\n        pageSize: tableObject.pageSize,\n        pageNo: 1,\n        ...data\n      })\n      // 页码不等于1时更新页码重新获取数据，页码等于1时重新获取数据\n      if (tableObject.currentPage !== 1) {\n        tableObject.currentPage = 1\n      } else {\n        methods.getList()\n      }\n    },\n    // 删除数据\n    delList: async (\n      ids: string | number | string[] | number[],\n      multiple: boolean,\n      message = true\n    ) => {\n      const tableRef = await getTable()\n      if (multiple) {\n        if (!tableRef?.selections.length) {\n          ElMessage.warning(t('common.delNoData'))\n          return\n        }\n      }\n      if (message) {\n        ElMessageBox.confirm(t('common.delMessage'), t('common.confirmTitle'), {\n          confirmButtonText: t('common.ok'),\n          cancelButtonText: t('common.cancel'),\n          type: 'warning'\n        }).then(async () => {\n          await delData(ids)\n        })\n      } else {\n        await delData(ids)\n      }\n    },\n    // 导出列表\n    exportList: async (fileName: string) => {\n      tableObject.exportLoading = true\n      ElMessageBox.confirm(t('common.exportMessage'), t('common.confirmTitle'), {\n        confirmButtonText: t('common.ok'),\n        cancelButtonText: t('common.cancel'),\n        type: 'warning'\n      })\n        .then(async () => {\n          const res = await config?.exportListApi?.(unref(paramsObj) as unknown as T)\n          if (res) {\n            download.excel(res as unknown as Blob, fileName)\n          }\n        })\n        .finally(() => {\n          tableObject.exportLoading = false\n        })\n    }\n  }\n\n  config?.props && methods.setProps(config.props)\n\n  return {\n    register,\n    elTableRef,\n    tableObject,\n    methods,\n    // add by 芋艿：返回 tableMethods 属性，和 tableObject 更统一\n    tableMethods: methods\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/hooks/web/useTagsView.ts",
    "content": "import { useTagsViewStoreWithOut } from '@/store/modules/tagsView'\nimport { RouteLocationNormalizedLoaded, useRouter } from 'vue-router'\nimport { computed, nextTick, unref } from 'vue'\n\nexport const useTagsView = () => {\n  const tagsViewStore = useTagsViewStoreWithOut()\n\n  const { replace, currentRoute } = useRouter()\n\n  const selectedTag = computed(() => tagsViewStore.getSelectedTag)\n\n  const closeAll = (callback?: Fn) => {\n    tagsViewStore.delAllViews()\n    callback?.()\n  }\n\n  const closeLeft = (callback?: Fn) => {\n    tagsViewStore.delLeftViews(unref(selectedTag) as RouteLocationNormalizedLoaded)\n    callback?.()\n  }\n\n  const closeRight = (callback?: Fn) => {\n    tagsViewStore.delRightViews(unref(selectedTag) as RouteLocationNormalizedLoaded)\n    callback?.()\n  }\n\n  const closeOther = (callback?: Fn) => {\n    tagsViewStore.delOthersViews(unref(selectedTag) as RouteLocationNormalizedLoaded)\n    callback?.()\n  }\n\n  const closeCurrent = (view?: RouteLocationNormalizedLoaded, callback?: Fn) => {\n    if (view?.meta?.affix) return\n    tagsViewStore.delView(view || unref(currentRoute))\n\n    callback?.()\n  }\n\n  const refreshPage = async (view?: RouteLocationNormalizedLoaded, callback?: Fn) => {\n    tagsViewStore.delCachedView()\n    const { path, query } = view || unref(currentRoute)\n    await nextTick()\n    replace({\n      path: '/redirect' + path,\n      query: query\n    })\n    callback?.()\n  }\n\n  const setTitle = (title: string, path?: string) => {\n    tagsViewStore.setTitle(title, path)\n  }\n\n  return {\n    closeAll,\n    closeLeft,\n    closeRight,\n    closeOther,\n    closeCurrent,\n    refreshPage,\n    setTitle\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/hooks/web/useTimeAgo.ts",
    "content": "import { useTimeAgo as useTimeAgoCore, UseTimeAgoMessages } from '@vueuse/core'\nimport { useLocaleStoreWithOut } from '@/store/modules/locale'\n\nconst TIME_AGO_MESSAGE_MAP: {\n  'zh-CN': UseTimeAgoMessages\n  en: UseTimeAgoMessages\n} = {\n  // @ts-ignore\n  'zh-CN': {\n    justNow: '刚刚',\n    past: (n) => (n.match(/\\d/) ? `${n}前` : n),\n    future: (n) => (n.match(/\\d/) ? `${n}后` : n),\n    month: (n, past) => (n === 1 ? (past ? '上个月' : '下个月') : `${n} 个月`),\n    year: (n, past) => (n === 1 ? (past ? '去年' : '明年') : `${n} 年`),\n    day: (n, past) => (n === 1 ? (past ? '昨天' : '明天') : `${n} 天`),\n    week: (n, past) => (n === 1 ? (past ? '上周' : '下周') : `${n} 周`),\n    hour: (n) => `${n} 小时`,\n    minute: (n) => `${n} 分钟`,\n    second: (n) => `${n} 秒`\n  },\n  // @ts-ignore\n  en: {\n    justNow: 'just now',\n    past: (n) => (n.match(/\\d/) ? `${n} ago` : n),\n    future: (n) => (n.match(/\\d/) ? `in ${n}` : n),\n    month: (n, past) =>\n      n === 1 ? (past ? 'last month' : 'next month') : `${n} month${n > 1 ? 's' : ''}`,\n    year: (n, past) =>\n      n === 1 ? (past ? 'last year' : 'next year') : `${n} year${n > 1 ? 's' : ''}`,\n    day: (n, past) => (n === 1 ? (past ? 'yesterday' : 'tomorrow') : `${n} day${n > 1 ? 's' : ''}`),\n    week: (n, past) =>\n      n === 1 ? (past ? 'last week' : 'next week') : `${n} week${n > 1 ? 's' : ''}`,\n    hour: (n) => `${n} hour${n > 1 ? 's' : ''}`,\n    minute: (n) => `${n} minute${n > 1 ? 's' : ''}`,\n    second: (n) => `${n} second${n > 1 ? 's' : ''}`\n  }\n}\n\nexport const useTimeAgo = (time: Date | number | string) => {\n  const localeStore = useLocaleStoreWithOut()\n\n  const currentLocale = computed(() => localeStore.getCurrentLocale)\n\n  const timeAgo = useTimeAgoCore(time, {\n    messages: TIME_AGO_MESSAGE_MAP[unref(currentLocale).lang]\n  })\n\n  return timeAgo\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/hooks/web/useTitle.ts",
    "content": "import { watch, ref } from 'vue'\nimport { isString } from '@/utils/is'\nimport { useAppStoreWithOut } from '@/store/modules/app'\n\nconst appStore = useAppStoreWithOut()\n\nexport const useTitle = (newTitle?: string) => {\n  const { t } = useI18n()\n  const title = ref(\n    newTitle ? `${appStore.getTitle} - ${t(newTitle as string)}` : appStore.getTitle\n  )\n\n  watch(\n    title,\n    (n, o) => {\n      if (isString(n) && n !== o && document) {\n        document.title = n\n      }\n    },\n    { immediate: true }\n  )\n\n  return title\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/hooks/web/useValidator.ts",
    "content": "import { useI18n } from '@/hooks/web/useI18n'\nimport { FormItemRule } from 'element-plus'\n\nconst { t } = useI18n()\n\ninterface LengthRange {\n  min: number\n  max: number\n  message?: string\n}\n\nexport const useValidator = () => {\n  const required = (message?: string): FormItemRule => {\n    return {\n      required: true,\n      message: message || t('common.required')\n    }\n  }\n\n  const lengthRange = (options: LengthRange): FormItemRule => {\n    const { min, max, message } = options\n\n    return {\n      min,\n      max,\n      message: message || t('common.lengthRange', { min, max })\n    }\n  }\n\n  const notSpace = (message?: string): FormItemRule => {\n    return {\n      validator: (_, val, callback) => {\n        if (val?.indexOf(' ') !== -1) {\n          callback(new Error(message || t('common.notSpace')))\n        } else {\n          callback()\n        }\n      }\n    }\n  }\n\n  const notSpecialCharacters = (message?: string): FormItemRule => {\n    return {\n      validator: (_, val, callback) => {\n        if (/[`~!@#$%^&*()_+<>?:\"{},.\\/;'[\\]]/gi.test(val)) {\n          callback(new Error(message || t('common.notSpecialCharacters')))\n        } else {\n          callback()\n        }\n      }\n    }\n  }\n\n  return {\n    required,\n    lengthRange,\n    notSpace,\n    notSpecialCharacters\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/hooks/web/useWatermark.ts",
    "content": "const domSymbol = Symbol('watermark-dom')\n\nexport function useWatermark(appendEl: HTMLElement | null = document.body) {\n  let func: Fn = () => {}\n  const id = domSymbol.toString()\n  const clear = () => {\n    const domId = document.getElementById(id)\n    if (domId) {\n      const el = appendEl\n      el && el.removeChild(domId)\n    }\n    window.removeEventListener('resize', func)\n  }\n  const createWatermark = (str: string) => {\n    clear()\n\n    const can = document.createElement('canvas')\n    can.width = 300\n    can.height = 240\n\n    const cans = can.getContext('2d')\n    if (cans) {\n      cans.rotate((-20 * Math.PI) / 120)\n      cans.font = '15px Vedana'\n      cans.fillStyle = 'rgba(0, 0, 0, 0.15)'\n      cans.textAlign = 'left'\n      cans.textBaseline = 'middle'\n      cans.fillText(str, can.width / 20, can.height)\n    }\n\n    const div = document.createElement('div')\n    div.id = id\n    div.style.pointerEvents = 'none'\n    div.style.top = '0px'\n    div.style.left = '0px'\n    div.style.position = 'absolute'\n    div.style.zIndex = '100000000'\n    div.style.width = document.documentElement.clientWidth + 'px'\n    div.style.height = document.documentElement.clientHeight + 'px'\n    div.style.background = 'url(' + can.toDataURL('image/png') + ') left top repeat'\n    const el = appendEl\n    el && el.appendChild(div)\n    return id\n  }\n\n  function setWatermark(str: string) {\n    createWatermark(str)\n    func = () => {\n      createWatermark(str)\n    }\n    window.addEventListener('resize', func)\n  }\n\n  return { setWatermark, clear }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/Layout.vue",
    "content": "<script lang=\"tsx\">\nimport { computed, defineComponent, unref } from 'vue'\nimport { useAppStore } from '@/store/modules/app'\nimport { Backtop } from '@/components/Backtop'\nimport { Setting } from '@/layout/components/Setting'\nimport { useRenderLayout } from './components/useRenderLayout'\nimport { useDesign } from '@/hooks/web/useDesign'\n\nconst { getPrefixCls } = useDesign()\n\nconst prefixCls = getPrefixCls('layout')\n\nconst appStore = useAppStore()\n\n// 是否是移动端\nconst mobile = computed(() => appStore.getMobile)\n\n// 菜单折叠\nconst collapse = computed(() => appStore.getCollapse)\n\nconst layout = computed(() => appStore.getLayout)\n\nconst handleClickOutside = () => {\n  appStore.setCollapse(true)\n}\n\nconst renderLayout = () => {\n  switch (unref(layout)) {\n    case 'classic':\n      const { renderClassic } = useRenderLayout()\n      return renderClassic()\n    case 'topLeft':\n      const { renderTopLeft } = useRenderLayout()\n      return renderTopLeft()\n    case 'top':\n      const { renderTop } = useRenderLayout()\n      return renderTop()\n    case 'cutMenu':\n      const { renderCutMenu } = useRenderLayout()\n      return renderCutMenu()\n    default:\n      break\n  }\n}\n\nexport default defineComponent({\n  name: 'Layout',\n  setup() {\n    return () => (\n      <section class={[prefixCls, `${prefixCls}__${layout.value}`, 'w-[100%] h-[100%] relative']}>\n        {mobile.value && !collapse.value ? (\n          <div\n            class=\"absolute left-0 top-0 z-99 h-full w-full bg-[var(--el-color-black)] opacity-30\"\n            onClick={handleClickOutside}\n          ></div>\n        ) : undefined}\n\n        {renderLayout()}\n\n        <Backtop></Backtop>\n\n        <Setting></Setting>\n      </section>\n    )\n  }\n})\n</script>\n\n<style lang=\"scss\" scoped>\n$prefix-cls: #{$namespace}-layout;\n\n.#{$prefix-cls} {\n  background-color: var(--app-content-bg-color);\n  :deep(.#{$elNamespace}-scrollbar__view) {\n    height: 100% !important;\n  }\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/AppView.vue",
    "content": "<script lang=\"ts\" setup>\nimport { useTagsViewStore } from '@/store/modules/tagsView'\nimport { useAppStore } from '@/store/modules/app'\nimport { Footer } from '@/layout/components/Footer'\n\ndefineOptions({ name: 'AppView' })\n\nconst appStore = useAppStore()\n\nconst layout = computed(() => appStore.getLayout)\n\nconst fixedHeader = computed(() => appStore.getFixedHeader)\n\nconst footer = computed(() => appStore.getFooter)\n\nconst tagsViewStore = useTagsViewStore()\n\nconst getCaches = computed((): string[] => {\n  return tagsViewStore.getCachedViews\n})\n\nconst tagsView = computed(() => appStore.getTagsView)\n\n//region 无感刷新\nconst routerAlive = ref(true)\n// 无感刷新，防止出现页面闪烁白屏\nconst reload = () => {\n  routerAlive.value = false\n  nextTick(() => (routerAlive.value = true))\n}\n// 为组件后代提供刷新方法\nprovide('reload', reload)\n//endregion\n</script>\n\n<template>\n  <section\n    :class=\"[\n      'p-[var(--app-content-padding)] w-[calc(100%-var(--app-content-padding)-var(--app-content-padding))] bg-[var(--app-content-bg-color)] dark:bg-[var(--el-bg-color)]',\n      {\n        '!min-h-[calc(100%-var(--app-content-padding)-var(--app-content-padding)-var(--app-footer-height))]':\n          (fixedHeader &&\n            (layout === 'classic' || layout === 'topLeft' || layout === 'top') &&\n            footer) ||\n          (!tagsView && layout === 'top' && footer),\n        '!min-h-[calc(100%-var(--app-content-padding)-var(--app-content-padding)-var(--app-footer-height)-var(--tags-view-height))]':\n          tagsView && layout === 'top' && footer,\n\n        '!min-h-[calc(100%-var(--tags-view-height)-var(--app-content-padding)-var(--app-content-padding)-var(--top-tool-height)-var(--app-footer-height))]':\n          !fixedHeader && layout === 'classic' && footer,\n\n        '!min-h-[calc(100%-var(--tags-view-height)-var(--app-content-padding)-var(--app-content-padding)-var(--app-footer-height))]':\n          !fixedHeader && layout === 'topLeft' && footer,\n\n        '!min-h-[calc(100%-var(--top-tool-height)-var(--app-content-padding)-var(--app-content-padding))]':\n          fixedHeader && layout === 'cutMenu' && footer,\n\n        '!min-h-[calc(100%-var(--top-tool-height)-var(--app-content-padding)-var(--app-content-padding)-var(--tags-view-height))]':\n          !fixedHeader && layout === 'cutMenu' && footer\n      }\n    ]\"\n  >\n    <router-view v-if=\"routerAlive\">\n      <template #default=\"{ Component, route }\">\n        <keep-alive :include=\"getCaches\">\n          <component :is=\"Component\" :key=\"route.fullPath\" />\n        </keep-alive>\n      </template>\n    </router-view>\n  </section>\n  <Footer v-if=\"footer\" />\n</template>\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/Breadcrumb/index.ts",
    "content": "import Breadcrumb from './src/Breadcrumb.vue'\n\nexport { Breadcrumb }\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/Breadcrumb/src/Breadcrumb.vue",
    "content": "<script lang=\"tsx\">\nimport { ElBreadcrumb, ElBreadcrumbItem } from 'element-plus'\nimport { ref, watch, computed, unref, defineComponent, TransitionGroup } from 'vue'\nimport { useRouter } from 'vue-router'\nimport { usePermissionStore } from '@/store/modules/permission'\nimport { filterBreadcrumb } from './helper'\nimport { filter, treeToList } from '@/utils/tree'\nimport type { RouteLocationNormalizedLoaded, RouteMeta } from 'vue-router'\n\nimport { Icon } from '@/components/Icon'\nimport { useAppStore } from '@/store/modules/app'\nimport { useDesign } from '@/hooks/web/useDesign'\n\nconst { getPrefixCls } = useDesign()\n\nconst prefixCls = getPrefixCls('breadcrumb')\n\nconst appStore = useAppStore()\n\n// 面包屑图标\nconst breadcrumbIcon = computed(() => appStore.getBreadcrumbIcon)\n\nexport default defineComponent({\n  name: 'Breadcrumb',\n  setup() {\n    const { currentRoute } = useRouter()\n\n    const { t } = useI18n()\n\n    const levelList = ref<AppRouteRecordRaw[]>([])\n\n    const permissionStore = usePermissionStore()\n\n    const menuRouters = computed(() => {\n      const routers = permissionStore.getRouters\n      return filterBreadcrumb(routers)\n    })\n\n    const getBreadcrumb = () => {\n      const currentPath = currentRoute.value.matched.slice(-1)[0].path\n\n      levelList.value = filter<AppRouteRecordRaw>(unref(menuRouters), (node: AppRouteRecordRaw) => {\n        return node.path === currentPath\n      })\n    }\n\n    const renderBreadcrumb = () => {\n      const breadcrumbList = treeToList<AppRouteRecordRaw[]>(unref(levelList))\n      return breadcrumbList.map((v) => {\n        const disabled = !v.redirect || v.redirect === 'noredirect'\n        const meta = v.meta as RouteMeta\n        return (\n          <ElBreadcrumbItem to={{ path: disabled ? '' : v.path }} key={v.name}>\n            {meta?.icon && breadcrumbIcon.value ? (\n              <div class=\"flex items-center\">\n                <Icon icon={meta.icon} class=\"mr-[2px]\" svgClass=\"inline-block\"></Icon>\n                {t(v?.meta?.title)}\n              </div>\n            ) : (\n              t(v?.meta?.title)\n            )}\n          </ElBreadcrumbItem>\n        )\n      })\n    }\n\n    watch(\n      () => currentRoute.value,\n      (route: RouteLocationNormalizedLoaded) => {\n        if (route.path.startsWith('/redirect/')) {\n          return\n        }\n        getBreadcrumb()\n      },\n      {\n        immediate: true\n      }\n    )\n\n    return () => (\n      <ElBreadcrumb separator=\"/\" class={`${prefixCls} flex items-center h-full ml-[10px]`}>\n        <TransitionGroup appear enter-active-class=\"animate__animated animate__fadeInRight\">\n          {renderBreadcrumb()}\n        </TransitionGroup>\n      </ElBreadcrumb>\n    )\n  }\n})\n</script>\n\n<style lang=\"scss\" scoped>\n$prefix-cls: #{$elNamespace}-breadcrumb;\n\n.#{$prefix-cls} {\n  :deep(&__item) {\n    display: flex;\n    .#{$prefix-cls}__inner {\n      display: flex;\n      align-items: center;\n      color: var(--top-header-text-color);\n\n      &:hover {\n        color: var(--el-color-primary);\n      }\n    }\n  }\n\n  :deep(&__item):not(:last-child) {\n    .#{$prefix-cls}__inner {\n      color: var(--top-header-text-color);\n\n      &:hover {\n        color: var(--el-color-primary);\n      }\n    }\n  }\n\n  :deep(&__item):last-child {\n    .#{$prefix-cls}__inner {\n      display: flex;\n      align-items: center;\n      color: var(--el-text-color-placeholder);\n\n      &:hover {\n        color: var(--el-text-color-placeholder);\n      }\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/Breadcrumb/src/helper.ts",
    "content": "import { pathResolve } from '@/utils/routerHelper'\nimport type { RouteMeta } from 'vue-router'\n\nexport const filterBreadcrumb = (\n  routes: AppRouteRecordRaw[],\n  parentPath = ''\n): AppRouteRecordRaw[] => {\n  const res: AppRouteRecordRaw[] = []\n\n  for (const route of routes) {\n    const meta = route?.meta as RouteMeta\n    if (meta.hidden && !meta.canTo) {\n      continue\n    }\n\n    const data: AppRouteRecordRaw =\n      !meta.alwaysShow && route.children?.length === 1\n        ? { ...route.children[0], path: pathResolve(route.path, route.children[0].path) }\n        : { ...route }\n\n    data.path = pathResolve(parentPath, data.path)\n\n    if (data.children) {\n      data.children = filterBreadcrumb(data.children, data.path)\n    }\n    if (data) {\n      res.push(data)\n    }\n  }\n  return res\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/Collapse/index.ts",
    "content": "import Collapse from './src/Collapse.vue'\n\nexport { Collapse }\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/Collapse/src/Collapse.vue",
    "content": "<script lang=\"ts\" setup>\nimport { useAppStore } from '@/store/modules/app'\nimport { propTypes } from '@/utils/propTypes'\nimport { useDesign } from '@/hooks/web/useDesign'\n\ndefineOptions({ name: 'Collapse' })\n\nconst { getPrefixCls } = useDesign()\n\nconst prefixCls = getPrefixCls('collapse')\n\ndefineProps({\n  color: propTypes.string.def('')\n})\n\nconst appStore = useAppStore()\n\nconst collapse = computed(() => appStore.getCollapse)\n\nconst toggleCollapse = () => {\n  const collapsed = unref(collapse)\n  appStore.setCollapse(!collapsed)\n}\n</script>\n\n<template>\n  <div :class=\"prefixCls\" @click=\"toggleCollapse\">\n    <Icon\n      :color=\"color\"\n      :icon=\"collapse ? 'ep:expand' : 'ep:fold'\"\n      :size=\"18\"\n      class=\"cursor-pointer\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/ContextMenu/index.ts",
    "content": "import ContextMenu from './src/ContextMenu.vue'\nimport { ElDropdown } from 'element-plus'\nimport type { RouteLocationNormalizedLoaded } from 'vue-router'\n\nexport interface ContextMenuExpose {\n  elDropdownMenuRef: ComponentRef<typeof ElDropdown>\n  tagItem: RouteLocationNormalizedLoaded\n}\n\nexport { ContextMenu }\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/ContextMenu/src/ContextMenu.vue",
    "content": "<script lang=\"ts\" setup>\nimport { PropType } from 'vue'\n\nimport { useDesign } from '@/hooks/web/useDesign'\nimport type { RouteLocationNormalizedLoaded } from 'vue-router'\nimport { contextMenuSchema } from '@/types/contextMenu'\nimport type { ElDropdown } from 'element-plus'\n\ndefineOptions({ name: 'ContextMenu' })\n\nconst { getPrefixCls } = useDesign()\n\nconst prefixCls = getPrefixCls('context-menu')\n\nconst { t } = useI18n()\n\nconst emit = defineEmits(['visibleChange'])\n\nconst props = defineProps({\n  schema: {\n    type: Array as PropType<contextMenuSchema[]>,\n    default: () => []\n  },\n  trigger: {\n    type: String as PropType<'click' | 'hover' | 'focus' | 'contextmenu'>,\n    default: 'contextmenu'\n  },\n  tagItem: {\n    type: Object as PropType<RouteLocationNormalizedLoaded>,\n    default: () => ({})\n  }\n})\n\nconst command = (item: contextMenuSchema) => {\n  item.command && item.command(item)\n}\n\nconst visibleChange = (visible: boolean) => {\n  emit('visibleChange', visible, props.tagItem)\n}\n\nconst elDropdownMenuRef = ref<ComponentRef<typeof ElDropdown>>()\n\ndefineExpose({\n  elDropdownMenuRef,\n  tagItem: props.tagItem\n})\n</script>\n\n<template>\n  <ElDropdown\n    ref=\"elDropdownMenuRef\"\n    :class=\"prefixCls\"\n    :trigger=\"trigger\"\n    placement=\"bottom-start\"\n    popper-class=\"v-context-menu-popper\"\n    @command=\"command\"\n    @visible-change=\"visibleChange\"\n  >\n    <slot></slot>\n    <template #dropdown>\n      <ElDropdownMenu>\n        <ElDropdownItem\n          v-for=\"(item, index) in schema\"\n          :key=\"`dropdown${index}`\"\n          :command=\"item\"\n          :disabled=\"item.disabled\"\n          :divided=\"item.divided\"\n        >\n          <Icon :icon=\"item.icon\" />\n          {{ t(item.label) }}\n        </ElDropdownItem>\n      </ElDropdownMenu>\n    </template>\n  </ElDropdown>\n</template>\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/Footer/index.ts",
    "content": "import Footer from './src/Footer.vue'\n\nexport { Footer }\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/Footer/src/Footer.vue",
    "content": "<script lang=\"ts\" setup>\nimport { useAppStore } from '@/store/modules/app'\nimport { useDesign } from '@/hooks/web/useDesign'\n\n// eslint-disable-next-line vue/no-reserved-component-names\ndefineOptions({ name: 'Footer' })\n\nconst { getPrefixCls } = useDesign()\n\nconst prefixCls = getPrefixCls('footer')\n\nconst appStore = useAppStore()\n\nconst title = computed(() => appStore.getTitle)\n</script>\n\n<template>\n  <div\n    :class=\"prefixCls\"\n    class=\"h-[var(--app-footer-height)] bg-[var(--app-content-bg-color)] text-center leading-[var(--app-footer-height)] text-[var(--el-text-color-placeholder)] dark:bg-[var(--el-bg-color)]\"\n  >\n    <span class=\"text-14px\">Copyright ©2022-{{ title }}</span>\n  </div>\n</template>\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/LocaleDropdown/index.ts",
    "content": "import LocaleDropdown from './src/LocaleDropdown.vue'\n\nexport { LocaleDropdown }\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/LocaleDropdown/src/LocaleDropdown.vue",
    "content": "<script lang=\"ts\" setup>\nimport { useLocaleStore } from '@/store/modules/locale'\nimport { useLocale } from '@/hooks/web/useLocale'\nimport { propTypes } from '@/utils/propTypes'\nimport { useDesign } from '@/hooks/web/useDesign'\n\ndefineOptions({ name: 'LocaleDropdown' })\n\nconst { getPrefixCls } = useDesign()\n\nconst prefixCls = getPrefixCls('locale-dropdown')\n\ndefineProps({\n  color: propTypes.string.def('')\n})\n\nconst localeStore = useLocaleStore()\n\nconst langMap = computed(() => localeStore.getLocaleMap)\n\nconst currentLang = computed(() => localeStore.getCurrentLocale)\n\nconst setLang = (lang: LocaleType) => {\n  if (lang === unref(currentLang).lang) return\n  // 需要重新加载页面让整个语言多初始化\n  window.location.reload()\n  localeStore.setCurrentLocale({\n    lang\n  })\n  const { changeLocale } = useLocale()\n  changeLocale(lang)\n}\n</script>\n\n<template>\n  <ElDropdown :class=\"prefixCls\" trigger=\"click\" @command=\"setLang\">\n    <Icon\n      :class=\"$attrs.class\"\n      :color=\"color\"\n      :size=\"18\"\n      class=\"cursor-pointer !p-0\"\n      icon=\"ion:language-sharp\"\n    />\n    <template #dropdown>\n      <ElDropdownMenu>\n        <ElDropdownItem v-for=\"item in langMap\" :key=\"item.lang\" :command=\"item.lang\">\n          {{ item.name }}\n        </ElDropdownItem>\n      </ElDropdownMenu>\n    </template>\n  </ElDropdown>\n</template>\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/Logo/index.ts",
    "content": "import Logo from './src/Logo.vue'\n\nexport { Logo }\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/Logo/src/Logo.vue",
    "content": "<script lang=\"ts\" setup>\nimport { computed, onMounted, ref, unref, watch } from 'vue'\nimport { useAppStore } from '@/store/modules/app'\nimport { useDesign } from '@/hooks/web/useDesign'\n\ndefineOptions({ name: 'Logo' })\n\nconst { getPrefixCls } = useDesign()\n\nconst prefixCls = getPrefixCls('logo')\n\nconst appStore = useAppStore()\n\nconst show = ref(true)\n\nconst title = computed(() => appStore.getTitle)\n\nconst layout = computed(() => appStore.getLayout)\n\nconst collapse = computed(() => appStore.getCollapse)\n\nonMounted(() => {\n  if (unref(collapse)) show.value = false\n})\n\nwatch(\n  () => collapse.value,\n  (collapse: boolean) => {\n    if (unref(layout) === 'topLeft' || unref(layout) === 'cutMenu') {\n      show.value = true\n      return\n    }\n    if (!collapse) {\n      setTimeout(() => {\n        show.value = !collapse\n      }, 400)\n    } else {\n      show.value = !collapse\n    }\n  }\n)\n\nwatch(\n  () => layout.value,\n  (layout) => {\n    if (layout === 'top' || layout === 'cutMenu') {\n      show.value = true\n    } else {\n      if (unref(collapse)) {\n        show.value = false\n      } else {\n        show.value = true\n      }\n    }\n  }\n)\n</script>\n\n<template>\n  <div>\n    <router-link\n      :class=\"[\n        prefixCls,\n        layout !== 'classic' ? `${prefixCls}__Top` : '',\n        'flex !h-[var(--logo-height)] items-center cursor-pointer pl-8px relative decoration-none overflow-hidden'\n      ]\"\n      to=\"/\"\n    >\n      <img\n        class=\"h-[calc(var(--logo-height)-10px)] w-[calc(var(--logo-height)-10px)]\"\n        src=\"@/assets/imgs/logo.png\"\n      />\n      <div\n        v-if=\"show\"\n        :class=\"[\n          'ml-10px text-16px font-700',\n          {\n            'text-[var(--logo-title-text-color)]': layout === 'classic',\n            'text-[var(--top-header-text-color)]':\n              layout === 'topLeft' || layout === 'top' || layout === 'cutMenu'\n          }\n        ]\"\n      >\n        {{ title }}\n      </div>\n    </router-link>\n  </div>\n</template>\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/Menu/index.ts",
    "content": "import Menu from './src/Menu.vue'\n\nexport { Menu }\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/Menu/src/Menu.vue",
    "content": "<script lang=\"tsx\">\nimport { PropType } from 'vue'\nimport { ElMenu, ElScrollbar } from 'element-plus'\nimport { useAppStore } from '@/store/modules/app'\nimport { usePermissionStore } from '@/store/modules/permission'\nimport { useRenderMenuItem } from './components/useRenderMenuItem'\nimport { isUrl } from '@/utils/is'\nimport { useDesign } from '@/hooks/web/useDesign'\nimport { LayoutType } from '@/types/layout'\n\nconst { getPrefixCls } = useDesign()\n\nconst prefixCls = getPrefixCls('menu')\n\nexport default defineComponent({\n  // eslint-disable-next-line vue/no-reserved-component-names\n  name: 'Menu',\n  props: {\n    menuSelect: {\n      type: Function as PropType<(index: string) => void>,\n      default: undefined\n    }\n  },\n  setup(props) {\n    const appStore = useAppStore()\n\n    const layout = computed(() => appStore.getLayout)\n\n    const { push, currentRoute } = useRouter()\n\n    const permissionStore = usePermissionStore()\n\n    const menuMode = computed((): 'vertical' | 'horizontal' => {\n      // 竖\n      const vertical: LayoutType[] = ['classic', 'topLeft', 'cutMenu']\n\n      if (vertical.includes(unref(layout))) {\n        return 'vertical'\n      } else {\n        return 'horizontal'\n      }\n    })\n\n    const routers = computed(() =>\n      unref(layout) === 'cutMenu' ? permissionStore.getMenuTabRouters : permissionStore.getRouters\n    )\n\n    const collapse = computed(() => appStore.getCollapse)\n\n    const uniqueOpened = computed(() => appStore.getUniqueOpened)\n\n    const activeMenu = computed(() => {\n      const { meta, path } = unref(currentRoute)\n      // if set path, the sidebar will highlight the path you set\n      if (meta.activeMenu) {\n        return meta.activeMenu as string\n      }\n      return path\n    })\n\n    const menuSelect = (index: string) => {\n      if (props.menuSelect) {\n        props.menuSelect(index)\n      }\n      // 自定义事件\n      if (isUrl(index)) {\n        window.open(index)\n      } else {\n        push(index)\n      }\n    }\n\n    const renderMenuWrap = () => {\n      if (unref(layout) === 'top') {\n        return renderMenu()\n      } else {\n        return <ElScrollbar>{renderMenu()}</ElScrollbar>\n      }\n    }\n\n    const renderMenu = () => {\n      return (\n        <ElMenu\n          defaultActive={unref(activeMenu)}\n          mode={unref(menuMode)}\n          collapse={\n            unref(layout) === 'top' || unref(layout) === 'cutMenu' ? false : unref(collapse)\n          }\n          uniqueOpened={unref(layout) === 'top' ? false : unref(uniqueOpened)}\n          backgroundColor=\"var(--left-menu-bg-color)\"\n          textColor=\"var(--left-menu-text-color)\"\n          activeTextColor=\"var(--left-menu-text-active-color)\"\n          onSelect={menuSelect}\n        >\n          {{\n            default: () => {\n              const { renderMenuItem } = useRenderMenuItem(unref(menuMode))\n              return renderMenuItem(unref(routers))\n            }\n          }}\n        </ElMenu>\n      )\n    }\n\n    return () => (\n      <div\n        id={prefixCls}\n        class={[\n          `${prefixCls} ${prefixCls}__${unref(menuMode)}`,\n          'h-[100%] overflow-hidden flex-col bg-[var(--left-menu-bg-color)]',\n          {\n            'w-[var(--left-menu-min-width)]': unref(collapse) && unref(layout) !== 'cutMenu',\n            'w-[var(--left-menu-max-width)]': !unref(collapse) && unref(layout) !== 'cutMenu'\n          }\n        ]}\n      >\n        {renderMenuWrap()}\n      </div>\n    )\n  }\n})\n</script>\n\n<style lang=\"scss\" scoped>\n$prefix-cls: #{$namespace}-menu;\n\n.#{$prefix-cls} {\n  position: relative;\n  transition: width var(--transition-time-02);\n\n  :deep(.#{$elNamespace}-menu) {\n    width: 100% !important;\n    border-right: none;\n\n    // 设置选中时子标题的颜色\n    .is-active {\n      & > .#{$elNamespace}-sub-menu__title {\n        color: var(--left-menu-text-active-color) !important;\n      }\n    }\n\n    // 设置子菜单悬停的高亮和背景色\n    .#{$elNamespace}-sub-menu__title,\n    .#{$elNamespace}-menu-item {\n      &:hover {\n        color: var(--left-menu-text-active-color) !important;\n        background-color: var(--left-menu-bg-color) !important;\n      }\n    }\n\n    // 设置选中时的高亮背景和高亮颜色\n    .#{$elNamespace}-menu-item.is-active {\n      color: var(--left-menu-text-active-color) !important;\n      background-color: var(--left-menu-bg-active-color) !important;\n\n      &:hover {\n        background-color: var(--left-menu-bg-active-color) !important;\n      }\n    }\n\n    .#{$elNamespace}-menu-item.is-active {\n      position: relative;\n    }\n\n    // 设置子菜单的背景颜色\n    .#{$elNamespace}-menu {\n      .#{$elNamespace}-sub-menu__title,\n      .#{$elNamespace}-menu-item:not(.is-active) {\n        background-color: var(--left-menu-bg-light-color) !important;\n      }\n    }\n  }\n\n  // 折叠时的最小宽度\n  :deep(.#{$elNamespace}-menu--collapse) {\n    width: var(--left-menu-min-width);\n\n    & > .is-active,\n    & > .is-active > .#{$elNamespace}-sub-menu__title {\n      position: relative;\n      background-color: var(--left-menu-collapse-bg-active-color) !important;\n    }\n  }\n\n  // 折叠动画的时候，就需要把文字给隐藏掉\n  :deep(.horizontal-collapse-transition) {\n    // transition: 0s width ease-in-out, 0s padding-left ease-in-out, 0s padding-right ease-in-out !important;\n    .#{$prefix-cls}__title {\n      display: none;\n    }\n  }\n\n  // 水平菜单\n  &__horizontal {\n    height: calc(var(--top-tool-height)) !important;\n\n    :deep(.#{$elNamespace}-menu--horizontal) {\n      height: calc(var(--top-tool-height));\n      border-bottom: none;\n      // 重新设置底部高亮颜色\n      & > .#{$elNamespace}-sub-menu.is-active {\n        .#{$elNamespace}-sub-menu__title {\n          border-bottom-color: var(--el-color-primary) !important;\n        }\n      }\n\n      .#{$elNamespace}-menu-item.is-active {\n        position: relative;\n\n        &::after {\n          display: none !important;\n        }\n      }\n\n      .#{$prefix-cls}__title {\n        /* stylelint-disable-next-line */\n        max-height: calc(var(--top-tool-height) - 2px) !important;\n        /* stylelint-disable-next-line */\n        line-height: calc(var(--top-tool-height) - 2px);\n      }\n    }\n  }\n}\n</style>\n\n<style lang=\"scss\">\n$prefix-cls: #{$namespace}-menu-popper;\n\n.#{$prefix-cls}--vertical,\n.#{$prefix-cls}--horizontal {\n  // 设置选中时子标题的颜色\n  .is-active {\n    & > .el-sub-menu__title {\n      color: var(--left-menu-text-active-color) !important;\n    }\n  }\n\n  // 设置子菜单悬停的高亮和背景色\n  .el-sub-menu__title,\n  .el-menu-item {\n    &:hover {\n      color: var(--left-menu-text-active-color) !important;\n      background-color: var(--left-menu-bg-color) !important;\n    }\n  }\n\n  // 设置选中时的高亮背景\n  .el-menu-item.is-active {\n    position: relative;\n    background-color: var(--left-menu-bg-active-color) !important;\n\n    &:hover {\n      background-color: var(--left-menu-bg-active-color) !important;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/Menu/src/components/useRenderMenuItem.tsx",
    "content": "import { ElSubMenu, ElMenuItem } from 'element-plus'\nimport { hasOneShowingChild } from '../helper'\nimport { isUrl } from '@/utils/is'\nimport { useRenderMenuTitle } from './useRenderMenuTitle'\nimport { pathResolve } from '@/utils/routerHelper'\n\nconst { renderMenuTitle } = useRenderMenuTitle()\n\nexport const useRenderMenuItem = () =>\n  // allRouters: AppRouteRecordRaw[] = [],\n  {\n    const renderMenuItem = (routers: AppRouteRecordRaw[], parentPath = '/') => {\n      return routers\n        .filter((v) => !v.meta?.hidden)\n        .map((v) => {\n          const meta = v.meta ?? {}\n          const { oneShowingChild, onlyOneChild } = hasOneShowingChild(v.children, v)\n          const fullPath = isUrl(v.path) ? v.path : pathResolve(parentPath, v.path) // getAllParentPath<AppRouteRecordRaw>(allRouters, v.path).join('/')\n\n          if (\n            oneShowingChild &&\n            (!onlyOneChild?.children || onlyOneChild?.noShowingChildren) &&\n            !meta?.alwaysShow\n          ) {\n            return (\n              <ElMenuItem\n                index={onlyOneChild ? pathResolve(fullPath, onlyOneChild.path) : fullPath}\n              >\n                {{\n                  default: () => renderMenuTitle(onlyOneChild ? onlyOneChild?.meta : meta)\n                }}\n              </ElMenuItem>\n            )\n          } else {\n            return (\n              <ElSubMenu index={fullPath}>\n                {{\n                  title: () => renderMenuTitle(meta),\n                  default: () => renderMenuItem(v.children!, fullPath)\n                }}\n              </ElSubMenu>\n            )\n          }\n        })\n    }\n\n    return {\n      renderMenuItem\n    }\n  }\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/Menu/src/components/useRenderMenuTitle.tsx",
    "content": "import type { RouteMeta } from 'vue-router'\nimport { Icon } from '@/components/Icon'\nimport { useI18n } from '@/hooks/web/useI18n'\n\nexport const useRenderMenuTitle = () => {\n  const renderMenuTitle = (meta: RouteMeta) => {\n    const { t } = useI18n()\n    const { title = 'Please set title', icon } = meta\n\n    return icon ? (\n      <>\n        <Icon icon={meta.icon}></Icon>\n        <span class=\"v-menu__title overflow-hidden overflow-ellipsis whitespace-nowrap\">\n          {t(title as string)}\n        </span>\n      </>\n    ) : (\n      <span class=\"v-menu__title overflow-hidden overflow-ellipsis whitespace-nowrap\">\n        {t(title as string)}\n      </span>\n    )\n  }\n\n  return {\n    renderMenuTitle\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/Menu/src/helper.ts",
    "content": "import type { RouteMeta } from 'vue-router'\nimport { findPath } from '@/utils/tree'\n\ntype OnlyOneChildType = AppRouteRecordRaw & { noShowingChildren?: boolean }\n\ninterface HasOneShowingChild {\n  oneShowingChild?: boolean\n  onlyOneChild?: OnlyOneChildType\n}\n\nexport const getAllParentPath = <T = Recordable>(treeData: T[], path: string) => {\n  const menuList = findPath(treeData, (n) => n.path === path) as AppRouteRecordRaw[]\n  return (menuList || []).map((item) => item.path)\n}\n\nexport const hasOneShowingChild = (\n  children: AppRouteRecordRaw[] = [],\n  parent: AppRouteRecordRaw\n): HasOneShowingChild => {\n  const onlyOneChild = ref<OnlyOneChildType>()\n\n  const showingChildren = children.filter((v) => {\n    const meta = (v.meta ?? {}) as RouteMeta\n    if (meta.hidden) {\n      return false\n    } else {\n      // Temp set(will be used if only has one showing child)\n      onlyOneChild.value = v\n      return true\n    }\n  })\n\n  // When there is only one child router, the child router is displayed by default\n  if (showingChildren.length === 1) {\n    return {\n      oneShowingChild: true,\n      onlyOneChild: unref(onlyOneChild)\n    }\n  }\n\n  // Show parent if there are no child router to display\n  if (!showingChildren.length) {\n    onlyOneChild.value = { ...parent, path: '', noShowingChildren: true }\n    return {\n      oneShowingChild: true,\n      onlyOneChild: unref(onlyOneChild)\n    }\n  }\n\n  return {\n    oneShowingChild: false,\n    onlyOneChild: unref(onlyOneChild)\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/Message/index.ts",
    "content": "import Message from './src/Message.vue'\n\nexport { Message }\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/Message/src/Message.vue",
    "content": "<script lang=\"ts\" setup>\nimport { formatDate } from '@/utils/formatTime'\nimport * as NotifyMessageApi from '@/api/system/notify/message'\n\ndefineOptions({ name: 'Message' })\n\nconst { push } = useRouter()\nconst activeName = ref('notice')\nconst unreadCount = ref(0) // 未读消息数量\nconst list = ref<any[]>([]) // 消息列表\n\n// 获得消息列表\nconst getList = async () => {\n  list.value = await NotifyMessageApi.getUnreadNotifyMessageList()\n  // 强制设置 unreadCount 为 0，避免小红点因为轮询太慢，不消除\n  unreadCount.value = 0\n}\n\n// 获得未读消息数\nconst getUnreadCount = async () => {\n  NotifyMessageApi.getUnreadNotifyMessageCount().then((data) => {\n    unreadCount.value = data\n  })\n}\n\n// 跳转我的站内信\nconst goMyList = () => {\n  push({\n    name: 'MyNotifyMessage'\n  })\n}\n\n// ========== 初始化 =========\nonMounted(() => {\n  // 首次加载小红点\n  // getUnreadCount()\n  // // 轮询刷新小红点\n  // setInterval(\n  //   () => {\n  //     getUnreadCount()\n  //   },\n  //   1000 * 60 * 2\n  // )\n})\n</script>\n<template>\n  <div class=\"message\">\n    <ElPopover :width=\"400\" placement=\"bottom\" trigger=\"click\">\n      <template #reference>\n        <ElBadge :is-dot=\"unreadCount > 0\" class=\"item\">\n          <Icon :size=\"18\" class=\"cursor-pointer\" icon=\"ep:bell\" @click=\"getList\" />\n        </ElBadge>\n      </template>\n      <ElTabs v-model=\"activeName\">\n        <ElTabPane label=\"我的站内信\" name=\"notice\">\n          <el-scrollbar class=\"message-list\">\n            <template v-for=\"item in list\" :key=\"item.id\">\n              <div class=\"message-item\">\n                <img alt=\"\" class=\"message-icon\" src=\"@/assets/imgs/avatar.gif\" />\n                <div class=\"message-content\">\n                  <span class=\"message-title\">\n                    {{ item.templateNickname }}：{{ item.templateContent }}\n                  </span>\n                  <span class=\"message-date\">\n                    {{ formatDate(item.createTime) }}\n                  </span>\n                </div>\n              </div>\n            </template>\n          </el-scrollbar>\n        </ElTabPane>\n      </ElTabs>\n      <!-- 更多 -->\n      <div style=\"margin-top: 10px; text-align: right\">\n        <XButton preIcon=\"ep:view\" title=\"查看全部\" type=\"primary\" @click=\"goMyList\" />\n      </div>\n    </ElPopover>\n  </div>\n</template>\n<style lang=\"scss\" scoped>\n.message-empty {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  height: 260px;\n  line-height: 45px;\n}\n\n.message-list {\n  display: flex;\n  height: 400px;\n  flex-direction: column;\n\n  .message-item {\n    display: flex;\n    align-items: center;\n    padding: 20px 0;\n    border-bottom: 1px solid var(--el-border-color-light);\n\n    &:last-child {\n      border: none;\n    }\n\n    .message-icon {\n      width: 40px;\n      height: 40px;\n      margin: 0 20px 0 5px;\n    }\n\n    .message-content {\n      display: flex;\n      flex-direction: column;\n\n      .message-title {\n        margin-bottom: 5px;\n      }\n\n      .message-date {\n        font-size: 12px;\n        color: var(--el-text-color-secondary);\n      }\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/Screenfull/index.ts",
    "content": "import Screenfull from './src/Screenfull.vue'\n\nexport { Screenfull }\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/Screenfull/src/Screenfull.vue",
    "content": "<script lang=\"ts\" setup>\nimport { Icon } from '@/components/Icon'\nimport { useFullscreen } from '@vueuse/core'\nimport { propTypes } from '@/utils/propTypes'\nimport { useDesign } from '@/hooks/web/useDesign'\n\ndefineOptions({ name: 'ScreenFull' })\n\nconst { getPrefixCls } = useDesign()\n\nconst prefixCls = getPrefixCls('screenfull')\n\ndefineProps({\n  color: propTypes.string.def('')\n})\n\nconst { toggle, isFullscreen } = useFullscreen()\n\nconst toggleFullscreen = () => {\n  toggle()\n}\n</script>\n\n<template>\n  <div :class=\"prefixCls\" @click=\"toggleFullscreen\">\n    <Icon\n      :color=\"color\"\n      :icon=\"isFullscreen ? 'zmdi:fullscreen-exit' : 'zmdi:fullscreen'\"\n      :size=\"18\"\n    />\n  </div>\n</template>\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/Setting/index.ts",
    "content": "import Setting from './src/Setting.vue'\n\nexport { Setting }\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/Setting/src/Setting.vue",
    "content": "<script lang=\"ts\" setup>\nimport { ElMessage } from 'element-plus'\nimport { useClipboard, useCssVar } from '@vueuse/core'\n\nimport { CACHE_KEY, useCache } from '@/hooks/web/useCache'\nimport { useDesign } from '@/hooks/web/useDesign'\n\nimport { setCssVar, trim } from '@/utils'\nimport { colorIsDark, hexToRGB, lighten } from '@/utils/color'\nimport { useAppStore } from '@/store/modules/app'\nimport { ThemeSwitch } from '@/layout/components/ThemeSwitch'\nimport ColorRadioPicker from './components/ColorRadioPicker.vue'\nimport InterfaceDisplay from './components/InterfaceDisplay.vue'\nimport LayoutRadioPicker from './components/LayoutRadioPicker.vue'\n\ndefineOptions({ name: 'Setting' })\n\nconst { t } = useI18n()\nconst appStore = useAppStore()\n\nconst { getPrefixCls } = useDesign()\nconst prefixCls = getPrefixCls('setting')\nconst layout = computed(() => appStore.getLayout)\nconst drawer = ref(false)\n\n// 主题色相关\nconst systemTheme = ref(appStore.getTheme.elColorPrimary)\n\nconst setSystemTheme = (color: string) => {\n  setCssVar('--el-color-primary', color)\n  appStore.setTheme({ elColorPrimary: color })\n  const leftMenuBgColor = useCssVar('--left-menu-bg-color', document.documentElement)\n  setMenuTheme(trim(unref(leftMenuBgColor)))\n}\n\n// 头部主题相关\nconst headerTheme = ref(appStore.getTheme.topHeaderBgColor || '')\n\nconst setHeaderTheme = (color: string) => {\n  const isDarkColor = colorIsDark(color)\n  const textColor = isDarkColor ? '#fff' : 'inherit'\n  const textHoverColor = isDarkColor ? lighten(color!, 6) : '#f6f6f6'\n  const topToolBorderColor = isDarkColor ? color : '#eee'\n  setCssVar('--top-header-bg-color', color)\n  setCssVar('--top-header-text-color', textColor)\n  setCssVar('--top-header-hover-color', textHoverColor)\n  appStore.setTheme({\n    topHeaderBgColor: color,\n    topHeaderTextColor: textColor,\n    topHeaderHoverColor: textHoverColor,\n    topToolBorderColor\n  })\n  if (unref(layout) === 'top') {\n    setMenuTheme(color)\n  }\n}\n\n// 菜单主题相关\nconst menuTheme = ref(appStore.getTheme.leftMenuBgColor || '')\n\nconst setMenuTheme = (color: string) => {\n  const primaryColor = useCssVar('--el-color-primary', document.documentElement)\n  const isDarkColor = colorIsDark(color)\n  const theme: Recordable = {\n    // 左侧菜单边框颜色\n    leftMenuBorderColor: isDarkColor ? 'inherit' : '#eee',\n    // 左侧菜单背景颜色\n    leftMenuBgColor: color,\n    // 左侧菜单浅色背景颜色\n    leftMenuBgLightColor: isDarkColor ? lighten(color!, 6) : color,\n    // 左侧菜单选中背景颜色\n    leftMenuBgActiveColor: isDarkColor\n      ? 'var(--el-color-primary)'\n      : hexToRGB(unref(primaryColor), 0.1),\n    // 左侧菜单收起选中背景颜色\n    leftMenuCollapseBgActiveColor: isDarkColor\n      ? 'var(--el-color-primary)'\n      : hexToRGB(unref(primaryColor), 0.1),\n    // 左侧菜单字体颜色\n    leftMenuTextColor: isDarkColor ? '#bfcbd9' : '#333',\n    // 左侧菜单选中字体颜色\n    leftMenuTextActiveColor: isDarkColor ? '#fff' : 'var(--el-color-primary)',\n    // logo字体颜色\n    logoTitleTextColor: isDarkColor ? '#fff' : 'inherit',\n    // logo边框颜色\n    logoBorderColor: isDarkColor ? color : '#eee'\n  }\n  appStore.setTheme(theme)\n  appStore.setCssVarTheme()\n}\nif (layout.value === 'top' && !appStore.getIsDark) {\n  headerTheme.value = '#fff'\n  setHeaderTheme('#fff')\n}\n\n// 监听layout变化，重置一些主题色\nwatch(\n  () => layout.value,\n  (n) => {\n    if (n === 'top' && !appStore.getIsDark) {\n      headerTheme.value = '#fff'\n      setHeaderTheme('#fff')\n    } else {\n      setMenuTheme(unref(menuTheme))\n    }\n  }\n)\n\n// 拷贝\nconst copyConfig = async () => {\n  const { copy, copied, isSupported } = useClipboard({\n    source: `\n      // 面包屑\n      breadcrumb: ${appStore.getBreadcrumb},\n      // 面包屑图标\n      breadcrumbIcon: ${appStore.getBreadcrumbIcon},\n      // 折叠图标\n      hamburger: ${appStore.getHamburger},\n      // 全屏图标\n      screenfull: ${appStore.getScreenfull},\n      // 尺寸图标\n      size: ${appStore.getSize},\n      // 多语言图标\n      locale: ${appStore.getLocale},\n      // 消息图标\n      message: ${appStore.getMessage},\n      // 标签页\n      tagsView: ${appStore.getTagsView},\n      // 标签页图标\n      getTagsViewIcon: ${appStore.getTagsViewIcon},\n      // logo\n      logo: ${appStore.getLogo},\n      // 菜单手风琴\n      uniqueOpened: ${appStore.getUniqueOpened},\n      // 固定header\n      fixedHeader: ${appStore.getFixedHeader},\n      // 页脚\n      footer: ${appStore.getFooter},\n      // 灰色模式\n      greyMode: ${appStore.getGreyMode},\n      // layout布局\n      layout: '${appStore.getLayout}',\n      // 暗黑模式\n      isDark: ${appStore.getIsDark},\n      // 组件尺寸\n      currentSize: '${appStore.getCurrentSize}',\n      // 主题相关\n      theme: {\n        // 主题色\n        elColorPrimary: '${appStore.getTheme.elColorPrimary}',\n        // 左侧菜单边框颜色\n        leftMenuBorderColor: '${appStore.getTheme.leftMenuBorderColor}',\n        // 左侧菜单背景颜色\n        leftMenuBgColor: '${appStore.getTheme.leftMenuBgColor}',\n        // 左侧菜单浅色背景颜色\n        leftMenuBgLightColor: '${appStore.getTheme.leftMenuBgLightColor}',\n        // 左侧菜单选中背景颜色\n        leftMenuBgActiveColor: '${appStore.getTheme.leftMenuBgActiveColor}',\n        // 左侧菜单收起选中背景颜色\n        leftMenuCollapseBgActiveColor: '${appStore.getTheme.leftMenuCollapseBgActiveColor}',\n        // 左侧菜单字体颜色\n        leftMenuTextColor: '${appStore.getTheme.leftMenuTextColor}',\n        // 左侧菜单选中字体颜色\n        leftMenuTextActiveColor: '${appStore.getTheme.leftMenuTextActiveColor}',\n        // logo字体颜色\n        logoTitleTextColor: '${appStore.getTheme.logoTitleTextColor}',\n        // logo边框颜色\n        logoBorderColor: '${appStore.getTheme.logoBorderColor}',\n        // 头部背景颜色\n        topHeaderBgColor: '${appStore.getTheme.topHeaderBgColor}',\n        // 头部字体颜色\n        topHeaderTextColor: '${appStore.getTheme.topHeaderTextColor}',\n        // 头部悬停颜色\n        topHeaderHoverColor: '${appStore.getTheme.topHeaderHoverColor}',\n        // 头部边框颜色\n        topToolBorderColor: '${appStore.getTheme.topToolBorderColor}'\n      }\n    `\n  })\n  if (!isSupported) {\n    ElMessage.error(t('setting.copyFailed'))\n  } else {\n    await copy()\n    if (unref(copied)) {\n      ElMessage.success(t('setting.copySuccess'))\n    }\n  }\n}\n\n// 清空缓存\nconst clear = () => {\n  const { wsCache } = useCache()\n  wsCache.delete(CACHE_KEY.LAYOUT)\n  wsCache.delete(CACHE_KEY.THEME)\n  wsCache.delete(CACHE_KEY.IS_DARK)\n  window.location.reload()\n}\n</script>\n\n<template>\n  <div\n    :class=\"prefixCls\"\n    class=\"fixed right-0 top-[45%] h-40px w-40px cursor-pointer bg-[var(--el-color-primary)] text-center leading-40px\"\n    @click=\"drawer = true\"\n  >\n    <Icon color=\"#fff\" icon=\"ep:setting\" />\n  </div>\n\n  <ElDrawer v-model=\"drawer\" :z-index=\"4000\" direction=\"rtl\" size=\"350px\">\n    <template #header>\n      <span class=\"text-16px font-700\">{{ t('setting.projectSetting') }}</span>\n    </template>\n\n    <div class=\"text-center\">\n      <!-- 主题 -->\n      <ElDivider>{{ t('setting.theme') }}</ElDivider>\n      <ThemeSwitch />\n\n      <!-- 布局 -->\n      <ElDivider>{{ t('setting.layout') }}</ElDivider>\n      <LayoutRadioPicker />\n\n      <!-- 系统主题 -->\n      <ElDivider>{{ t('setting.systemTheme') }}</ElDivider>\n      <ColorRadioPicker\n        v-model=\"systemTheme\"\n        :schema=\"[\n          '#409eff',\n          '#009688',\n          '#536dfe',\n          '#ff5c93',\n          '#ee4f12',\n          '#0096c7',\n          '#9c27b0',\n          '#ff9800'\n        ]\"\n        @change=\"setSystemTheme\"\n      />\n\n      <!-- 头部主题 -->\n      <ElDivider>{{ t('setting.headerTheme') }}</ElDivider>\n      <ColorRadioPicker\n        v-model=\"headerTheme\"\n        :schema=\"[\n          '#fff',\n          '#151515',\n          '#5172dc',\n          '#e74c3c',\n          '#24292e',\n          '#394664',\n          '#009688',\n          '#383f45'\n        ]\"\n        @change=\"setHeaderTheme\"\n      />\n\n      <!-- 菜单主题 -->\n      <template v-if=\"layout !== 'top'\">\n        <ElDivider>{{ t('setting.menuTheme') }}</ElDivider>\n        <ColorRadioPicker\n          v-model=\"menuTheme\"\n          :schema=\"[\n            '#fff',\n            '#001529',\n            '#212121',\n            '#273352',\n            '#191b24',\n            '#383f45',\n            '#001628',\n            '#344058'\n          ]\"\n          @change=\"setMenuTheme\"\n        />\n      </template>\n    </div>\n\n    <!-- 界面显示 -->\n    <ElDivider>{{ t('setting.interfaceDisplay') }}</ElDivider>\n    <InterfaceDisplay />\n\n    <ElDivider />\n    <div>\n      <ElButton class=\"w-full\" type=\"primary\" @click=\"copyConfig\">{{ t('setting.copy') }}</ElButton>\n    </div>\n    <div class=\"mt-5px\">\n      <ElButton class=\"w-full\" type=\"danger\" @click=\"clear\">\n        {{ t('setting.clearAndReset') }}\n      </ElButton>\n    </div>\n  </ElDrawer>\n</template>\n\n<style lang=\"scss\" scoped>\n$prefix-cls: #{$namespace}-setting;\n\n.#{$prefix-cls} {\n  border-radius: 6px 0 0 6px;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/Setting/src/components/ColorRadioPicker.vue",
    "content": "<script lang=\"ts\" setup>\nimport { PropType } from 'vue'\nimport { propTypes } from '@/utils/propTypes'\nimport { useDesign } from '@/hooks/web/useDesign'\n\ndefineOptions({ name: 'ColorRadioPicker' })\n\nconst { getPrefixCls } = useDesign()\n\nconst prefixCls = getPrefixCls('color-radio-picker')\n\nconst props = defineProps({\n  schema: {\n    type: Array as PropType<string[]>,\n    default: () => []\n  },\n  modelValue: propTypes.string.def('')\n})\n\nconst emit = defineEmits(['update:modelValue', 'change'])\n\nconst colorVal = ref(props.modelValue)\n\nwatch(\n  () => props.modelValue,\n  (val: string) => {\n    if (val === unref(colorVal)) return\n    colorVal.value = val\n  }\n)\n\n// 监听\nwatch(\n  () => colorVal.value,\n  (val: string) => {\n    emit('update:modelValue', val)\n    emit('change', val)\n  }\n)\n</script>\n\n<template>\n  <div :class=\"prefixCls\" class=\"flex flex-wrap space-x-14px\">\n    <span\n      v-for=\"(item, i) in schema\"\n      :key=\"`radio-${i}`\"\n      :class=\"{ 'is-active': colorVal === item }\"\n      :style=\"{\n        background: item\n      }\"\n      class=\"mb-5px h-20px w-20px cursor-pointer border-2px border-gray-300 rounded-2px border-solid text-center leading-20px\"\n      @click=\"colorVal = item\"\n    >\n      <Icon v-if=\"colorVal === item\" :size=\"16\" color=\"#fff\" icon=\"ep:check\" />\n    </span>\n  </div>\n</template>\n\n<style lang=\"scss\" scoped>\n$prefix-cls: #{$namespace}-color-radio-picker;\n\n.#{$prefix-cls} {\n  .is-active {\n    border-color: var(--el-color-primary);\n  }\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/Setting/src/components/InterfaceDisplay.vue",
    "content": "<script lang=\"ts\" setup>\nimport { setCssVar } from '@/utils'\n\nimport { useDesign } from '@/hooks/web/useDesign'\nimport { useWatermark } from '@/hooks/web/useWatermark'\nimport { useAppStore } from '@/store/modules/app'\n\ndefineOptions({ name: 'InterfaceDisplay' })\n\nconst { t } = useI18n()\nconst { getPrefixCls } = useDesign()\nconst { setWatermark } = useWatermark()\nconst prefixCls = getPrefixCls('interface-display')\nconst appStore = useAppStore()\n\nconst water = ref()\n\n// 面包屑\nconst breadcrumb = ref(appStore.getBreadcrumb)\n\nconst breadcrumbChange = (show: boolean) => {\n  appStore.setBreadcrumb(show)\n}\n\n// 面包屑图标\nconst breadcrumbIcon = ref(appStore.getBreadcrumbIcon)\n\nconst breadcrumbIconChange = (show: boolean) => {\n  appStore.setBreadcrumbIcon(show)\n}\n\n// 折叠图标\nconst hamburger = ref(appStore.getHamburger)\n\nconst hamburgerChange = (show: boolean) => {\n  appStore.setHamburger(show)\n}\n\n// 全屏图标\nconst screenfull = ref(appStore.getScreenfull)\n\nconst screenfullChange = (show: boolean) => {\n  appStore.setScreenfull(show)\n}\n\n// 尺寸图标\nconst size = ref(appStore.getSize)\n\nconst sizeChange = (show: boolean) => {\n  appStore.setSize(show)\n}\n\n// 多语言图标\nconst locale = ref(appStore.getLocale)\n\nconst localeChange = (show: boolean) => {\n  appStore.setLocale(show)\n}\n\n// 消息图标\nconst message = ref(appStore.getMessage)\n\nconst messageChange = (show: boolean) => {\n  appStore.setMessage(show)\n}\n\n// 标签页\nconst tagsView = ref(appStore.getTagsView)\n\nconst tagsViewChange = (show: boolean) => {\n  // 切换标签栏显示时，同步切换标签栏的高度\n  setCssVar('--tags-view-height', show ? '35px' : '0px')\n  appStore.setTagsView(show)\n}\n\n// 标签页图标\nconst tagsViewIcon = ref(appStore.getTagsViewIcon)\n\nconst tagsViewIconChange = (show: boolean) => {\n  appStore.setTagsViewIcon(show)\n}\n\n// logo\nconst logo = ref(appStore.getLogo)\n\nconst logoChange = (show: boolean) => {\n  appStore.setLogo(show)\n}\n\n// 菜单手风琴\nconst uniqueOpened = ref(appStore.getUniqueOpened)\n\nconst uniqueOpenedChange = (uniqueOpened: boolean) => {\n  appStore.setUniqueOpened(uniqueOpened)\n}\n\n// 固定头部\nconst fixedHeader = ref(appStore.getFixedHeader)\n\nconst fixedHeaderChange = (show: boolean) => {\n  appStore.setFixedHeader(show)\n}\n\n// 页脚\nconst footer = ref(appStore.getFooter)\n\nconst footerChange = (show: boolean) => {\n  appStore.setFooter(show)\n}\n\n// 灰色模式\nconst greyMode = ref(appStore.getGreyMode)\n\nconst greyModeChange = (show: boolean) => {\n  appStore.setGreyMode(show)\n}\n\n// 固定菜单\nconst fixedMenu = ref(appStore.getFixedMenu)\n\nconst fixedMenuChange = (show: boolean) => {\n  appStore.setFixedMenu(show)\n}\n\n// 设置水印\nconst setWater = () => {\n  setWatermark(water.value)\n}\n\nconst layout = computed(() => appStore.getLayout)\n\nwatch(\n  () => layout.value,\n  (n) => {\n    if (n === 'top') {\n      appStore.setCollapse(false)\n    }\n  }\n)\n</script>\n\n<template>\n  <div :class=\"prefixCls\">\n    <div class=\"flex items-center justify-between\">\n      <span class=\"text-14px\">{{ t('setting.breadcrumb') }}</span>\n      <ElSwitch v-model=\"breadcrumb\" @change=\"breadcrumbChange\" />\n    </div>\n\n    <div class=\"flex items-center justify-between\">\n      <span class=\"text-14px\">{{ t('setting.breadcrumbIcon') }}</span>\n      <ElSwitch v-model=\"breadcrumbIcon\" @change=\"breadcrumbIconChange\" />\n    </div>\n\n    <div class=\"flex items-center justify-between\">\n      <span class=\"text-14px\">{{ t('setting.hamburgerIcon') }}</span>\n      <ElSwitch v-model=\"hamburger\" @change=\"hamburgerChange\" />\n    </div>\n\n    <div class=\"flex items-center justify-between\">\n      <span class=\"text-14px\">{{ t('setting.screenfullIcon') }}</span>\n      <ElSwitch v-model=\"screenfull\" @change=\"screenfullChange\" />\n    </div>\n\n    <div class=\"flex items-center justify-between\">\n      <span class=\"text-14px\">{{ t('setting.sizeIcon') }}</span>\n      <ElSwitch v-model=\"size\" @change=\"sizeChange\" />\n    </div>\n\n    <div class=\"flex items-center justify-between\">\n      <span class=\"text-14px\">{{ t('setting.localeIcon') }}</span>\n      <ElSwitch v-model=\"locale\" @change=\"localeChange\" />\n    </div>\n\n    <div class=\"flex items-center justify-between\">\n      <span class=\"text-14px\">{{ t('setting.messageIcon') }}</span>\n      <ElSwitch v-model=\"message\" @change=\"messageChange\" />\n    </div>\n\n    <div class=\"flex items-center justify-between\">\n      <span class=\"text-14px\">{{ t('setting.tagsView') }}</span>\n      <ElSwitch v-model=\"tagsView\" @change=\"tagsViewChange\" />\n    </div>\n\n    <div class=\"flex items-center justify-between\">\n      <span class=\"text-14px\">{{ t('setting.tagsViewIcon') }}</span>\n      <ElSwitch v-model=\"tagsViewIcon\" @change=\"tagsViewIconChange\" />\n    </div>\n\n    <div class=\"flex items-center justify-between\">\n      <span class=\"text-14px\">{{ t('setting.logo') }}</span>\n      <ElSwitch v-model=\"logo\" @change=\"logoChange\" />\n    </div>\n\n    <div class=\"flex items-center justify-between\">\n      <span class=\"text-14px\">{{ t('setting.uniqueOpened') }}</span>\n      <ElSwitch v-model=\"uniqueOpened\" @change=\"uniqueOpenedChange\" />\n    </div>\n\n    <div class=\"flex items-center justify-between\">\n      <span class=\"text-14px\">{{ t('setting.fixedHeader') }}</span>\n      <ElSwitch v-model=\"fixedHeader\" @change=\"fixedHeaderChange\" />\n    </div>\n\n    <div class=\"flex items-center justify-between\">\n      <span class=\"text-14px\">{{ t('setting.footer') }}</span>\n      <ElSwitch v-model=\"footer\" @change=\"footerChange\" />\n    </div>\n\n    <div class=\"flex items-center justify-between\">\n      <span class=\"text-14px\">{{ t('setting.greyMode') }}</span>\n      <ElSwitch v-model=\"greyMode\" @change=\"greyModeChange\" />\n    </div>\n\n    <div class=\"flex items-center justify-between\">\n      <span class=\"text-14px\">{{ t('setting.fixedMenu') }}</span>\n      <ElSwitch v-model=\"fixedMenu\" @change=\"fixedMenuChange\" />\n    </div>\n\n    <div class=\"flex items-center justify-between\">\n      <span class=\"text-14px\">{{ t('watermark.watermark') }}</span>\n      <ElInput v-model=\"water\" class=\"right-1 w-20\" @change=\"setWater()\" />\n    </div>\n  </div>\n</template>\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/Setting/src/components/LayoutRadioPicker.vue",
    "content": "<script lang=\"ts\" setup>\nimport { useAppStore } from '@/store/modules/app'\nimport { useDesign } from '@/hooks/web/useDesign'\n\ndefineOptions({ name: 'LayoutRadioPicker' })\n\nconst { getPrefixCls } = useDesign()\n\nconst prefixCls = getPrefixCls('layout-radio-picker')\n\nconst appStore = useAppStore()\n\nconst layout = computed(() => appStore.getLayout)\n</script>\n\n<template>\n  <div :class=\"prefixCls\" class=\"flex flex-wrap space-x-14px\">\n    <div\n      :class=\"[\n        `${prefixCls}__classic`,\n        'relative w-56px h-48px cursor-pointer bg-gray-300',\n        {\n          'is-acitve': layout === 'classic'\n        }\n      ]\"\n      @click=\"appStore.setLayout('classic')\"\n    ></div>\n    <div\n      :class=\"[\n        `${prefixCls}__top-left`,\n        'relative w-56px h-48px cursor-pointer bg-gray-300',\n        {\n          'is-acitve': layout === 'topLeft'\n        }\n      ]\"\n      @click=\"appStore.setLayout('topLeft')\"\n    ></div>\n    <div\n      :class=\"[\n        `${prefixCls}__top`,\n        'relative w-56px h-48px cursor-pointer bg-gray-300',\n        {\n          'is-acitve': layout === 'top'\n        }\n      ]\"\n      @click=\"appStore.setLayout('top')\"\n    ></div>\n    <div\n      :class=\"[\n        `${prefixCls}__cut-menu`,\n        'relative w-56px h-48px cursor-pointer bg-gray-300',\n        {\n          'is-acitve': layout === 'cutMenu'\n        }\n      ]\"\n      @click=\"appStore.setLayout('cutMenu')\"\n    >\n      <div class=\"absolute left-[10%] top-0 h-full w-[33%] bg-gray-200\"></div>\n    </div>\n  </div>\n</template>\n\n<style lang=\"scss\" scoped>\n$prefix-cls: #{$namespace}-layout-radio-picker;\n\n.#{$prefix-cls} {\n  &__classic {\n    border: 2px solid #e5e7eb;\n    border-radius: 4px;\n\n    &::before {\n      position: absolute;\n      top: 0;\n      left: 0;\n      z-index: 1;\n      width: 33%;\n      height: 100%;\n      background-color: #273352;\n      border-radius: 4px 0 0 4px;\n      content: '';\n    }\n\n    &::after {\n      position: absolute;\n      top: 0;\n      left: 0;\n      width: 100%;\n      height: 25%;\n      background-color: #fff;\n      border-radius: 4px 4px 0;\n      content: '';\n    }\n  }\n\n  &__top-left {\n    border: 2px solid #e5e7eb;\n    border-radius: 4px;\n\n    &::before {\n      position: absolute;\n      top: 0;\n      left: 0;\n      z-index: 1;\n      width: 100%;\n      height: 33%;\n      background-color: #273352;\n      border-radius: 4px 4px 0 0;\n      content: '';\n    }\n\n    &::after {\n      position: absolute;\n      top: 0;\n      left: 0;\n      width: 33%;\n      height: 100%;\n      background-color: #fff;\n      border-radius: 4px 0 0 4px;\n      content: '';\n    }\n  }\n\n  &__top {\n    border: 2px solid #e5e7eb;\n    border-radius: 4px;\n\n    &::before {\n      position: absolute;\n      top: 0;\n      left: 0;\n      z-index: 1;\n      width: 100%;\n      height: 33%;\n      background-color: #273352;\n      border-radius: 4px 4px 0 0;\n      content: '';\n    }\n  }\n\n  &__cut-menu {\n    border: 2px solid #e5e7eb;\n    border-radius: 4px;\n\n    &::before {\n      position: absolute;\n      top: 0;\n      left: 0;\n      z-index: 1;\n      width: 100%;\n      height: 33%;\n      background-color: #273352;\n      border-radius: 4px 4px 0 0;\n      content: '';\n    }\n\n    &::after {\n      position: absolute;\n      top: 0;\n      left: 0;\n      width: 10%;\n      height: 100%;\n      background-color: #fff;\n      border-radius: 4px 0 0 4px;\n      content: '';\n    }\n  }\n\n  .is-acitve {\n    border-color: var(--el-color-primary);\n  }\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/SizeDropdown/index.ts",
    "content": "import SizeDropdown from './src/SizeDropdown.vue'\n\nexport { SizeDropdown }\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/SizeDropdown/src/SizeDropdown.vue",
    "content": "<script lang=\"ts\" setup>\nimport { useAppStore } from '@/store/modules/app'\n\nimport { propTypes } from '@/utils/propTypes'\nimport { useDesign } from '@/hooks/web/useDesign'\nimport { ElementPlusSize } from '@/types/elementPlus'\n\ndefineOptions({ name: 'SizeDropdown' })\n\nconst { getPrefixCls } = useDesign()\n\nconst prefixCls = getPrefixCls('size-dropdown')\n\ndefineProps({\n  color: propTypes.string.def('')\n})\n\nconst { t } = useI18n()\n\nconst appStore = useAppStore()\n\nconst sizeMap = computed(() => appStore.sizeMap)\n\nconst setCurrentSize = (size: ElementPlusSize) => {\n  appStore.setCurrentSize(size)\n}\n</script>\n\n<template>\n  <ElDropdown :class=\"prefixCls\" trigger=\"click\" @command=\"setCurrentSize\">\n    <Icon :color=\"color\" :size=\"18\" class=\"cursor-pointer\" icon=\"mdi:format-size\" />\n    <template #dropdown>\n      <ElDropdownMenu>\n        <ElDropdownItem v-for=\"item in sizeMap\" :key=\"item\" :command=\"item\">\n          {{ t(`size.${item}`) }}\n        </ElDropdownItem>\n      </ElDropdownMenu>\n    </template>\n  </ElDropdown>\n</template>\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/TabMenu/index.ts",
    "content": "import TabMenu from './src/TabMenu.vue'\n\nexport { TabMenu }\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/TabMenu/src/TabMenu.vue",
    "content": "<script lang=\"tsx\">\nimport { usePermissionStore } from '@/store/modules/permission'\nimport { useAppStore } from '@/store/modules/app'\n\nimport { ElScrollbar } from 'element-plus'\nimport { Icon } from '@/components/Icon'\nimport { Menu } from '@/layout/components/Menu'\nimport { pathResolve } from '@/utils/routerHelper'\nimport { cloneDeep } from 'lodash-es'\nimport { filterMenusPath, initTabMap, tabPathMap } from './helper'\nimport { useDesign } from '@/hooks/web/useDesign'\nimport { isUrl } from '@/utils/is'\n\nconst { getPrefixCls, variables } = useDesign()\n\nconst prefixCls = getPrefixCls('tab-menu')\n\nexport default defineComponent({\n  name: 'TabMenu',\n  setup() {\n    const { push, currentRoute } = useRouter()\n\n    const { t } = useI18n()\n\n    const appStore = useAppStore()\n\n    const collapse = computed(() => appStore.getCollapse)\n\n    const fixedMenu = computed(() => appStore.getFixedMenu)\n\n    const permissionStore = usePermissionStore()\n\n    const routers = computed(() => permissionStore.getRouters)\n\n    const tabRouters = computed(() => unref(routers).filter((v) => !v?.meta?.hidden))\n\n    const setCollapse = () => {\n      appStore.setCollapse(!unref(collapse))\n    }\n\n    onMounted(() => {\n      if (unref(fixedMenu)) {\n        const path = `/${unref(currentRoute).path.split('/')[1]}`\n        const children = unref(tabRouters).find(\n          (v) =>\n            (v.meta?.alwaysShow || (v?.children?.length && v?.children?.length > 1)) &&\n            v.path === path\n        )?.children\n\n        tabActive.value = path\n        if (children) {\n          permissionStore.setMenuTabRouters(\n            cloneDeep(children).map((v) => {\n              v.path = pathResolve(unref(tabActive), v.path)\n              return v\n            })\n          )\n        }\n      }\n    })\n\n    watch(\n      () => routers.value,\n      (routers: AppRouteRecordRaw[]) => {\n        initTabMap(routers)\n        filterMenusPath(routers, routers)\n      },\n      {\n        immediate: true,\n        deep: true\n      }\n    )\n\n    const showTitle = ref(true)\n\n    watch(\n      () => collapse.value,\n      (collapse: boolean) => {\n        if (!collapse) {\n          setTimeout(() => {\n            showTitle.value = !collapse\n          }, 200)\n        } else {\n          showTitle.value = !collapse\n        }\n      }\n    )\n\n    // 是否显示菜单\n    const showMenu = ref(unref(fixedMenu) ? true : false)\n\n    // tab高亮\n    const tabActive = ref('')\n\n    // tab点击事件\n    const tabClick = (item: AppRouteRecordRaw) => {\n      if (isUrl(item.path)) {\n        window.open(item.path)\n        return\n      }\n      const newPath = item.children ? item.path : item.path.split('/')[0]\n      const oldPath = unref(tabActive)\n      tabActive.value = item.children ? item.path : item.path.split('/')[0]\n      if (item.children) {\n        if (newPath === oldPath || !unref(showMenu)) {\n          showMenu.value = unref(fixedMenu) ? true : !unref(showMenu)\n        }\n        if (unref(showMenu)) {\n          permissionStore.setMenuTabRouters(\n            cloneDeep(item.children).map((v) => {\n              v.path = pathResolve(unref(tabActive), v.path)\n              return v\n            })\n          )\n        }\n      } else {\n        push(item.path)\n        permissionStore.setMenuTabRouters([])\n        showMenu.value = false\n      }\n    }\n\n    // 设置高亮\n    const isActive = (currentPath: string) => {\n      const { path } = unref(currentRoute)\n      if (tabPathMap[currentPath].includes(path)) {\n        return true\n      }\n      return false\n    }\n\n    const mouseleave = () => {\n      if (!unref(showMenu) || unref(fixedMenu)) return\n      showMenu.value = false\n    }\n\n    return () => (\n      <div\n        id={`${variables.namespace}-menu`}\n        class={[\n          prefixCls,\n          'relative bg-[var(--left-menu-bg-color)] top-1px layout-border__right',\n          {\n            'w-[var(--tab-menu-max-width)]': !unref(collapse),\n            'w-[var(--tab-menu-min-width)]': unref(collapse)\n          }\n        ]}\n        onMouseleave={mouseleave}\n      >\n        <ElScrollbar class=\"!h-[calc(100%-var(--tab-menu-collapse-height)-1px)]\">\n          <div>\n            {() => {\n              return unref(tabRouters).map((v) => {\n                const item = (\n                  v.meta?.alwaysShow || (v?.children?.length && v?.children?.length > 1)\n                    ? v\n                    : {\n                        ...(v?.children && v?.children[0]),\n                        path: pathResolve(v.path, (v?.children && v?.children[0])?.path as string)\n                      }\n                ) as AppRouteRecordRaw\n                return (\n                  <div\n                    class={[\n                      `${prefixCls}__item`,\n                      'text-center text-12px relative py-12px cursor-pointer',\n                      {\n                        'is-active': isActive(v.path)\n                      }\n                    ]}\n                    onClick={() => {\n                      tabClick(item)\n                    }}\n                  >\n                    <div>\n                      <Icon icon={item?.meta?.icon}></Icon>\n                    </div>\n                    {!unref(showTitle) ? undefined : (\n                      <p class=\"mt-5px break-words px-2px\">{t(item.meta?.title)}</p>\n                    )}\n                  </div>\n                )\n              })\n            }}\n          </div>\n        </ElScrollbar>\n        <div\n          class={[\n            `${prefixCls}--collapse`,\n            'text-center h-[var(--tab-menu-collapse-height)] leading-[var(--tab-menu-collapse-height)] cursor-pointer'\n          ]}\n          onClick={setCollapse}\n        >\n          <Icon icon={unref(collapse) ? 'ep:d-arrow-right' : 'ep:d-arrow-left'}></Icon>\n        </div>\n        <Menu\n          class={[\n            '!absolute top-0 z-11',\n            {\n              '!left-[var(--tab-menu-min-width)]': unref(collapse),\n              '!left-[var(--tab-menu-max-width)]': !unref(collapse),\n              '!w-[calc(var(--left-menu-max-width)+1px)]': unref(showMenu) || unref(fixedMenu),\n              '!w-0': !unref(showMenu) && !unref(fixedMenu)\n            }\n          ]}\n          style=\"transition: width var(--transition-time-02), left var(--transition-time-02);\"\n        ></Menu>\n      </div>\n    )\n  }\n})\n</script>\n\n<style lang=\"scss\" scoped>\n$prefix-cls: #{$namespace}-tab-menu;\n\n.#{$prefix-cls} {\n  transition: all var(--transition-time-02);\n\n  &__item {\n    color: var(--left-menu-text-color);\n    transition: all var(--transition-time-02);\n\n    &:hover {\n      color: var(--left-menu-text-active-color);\n      // background-color: var(--left-menu-bg-active-color);\n    }\n  }\n\n  &--collapse {\n    color: var(--left-menu-text-color);\n    background-color: var(--left-menu-bg-light-color);\n  }\n\n  .is-active {\n    color: var(--left-menu-text-active-color);\n    background-color: var(--left-menu-bg-active-color);\n  }\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/TabMenu/src/helper.ts",
    "content": "import { getAllParentPath } from '@/layout/components/Menu/src/helper'\nimport type { RouteMeta } from 'vue-router'\nimport { isUrl } from '@/utils/is'\nimport { cloneDeep } from 'lodash-es'\n\nexport type TabMapTypes = {\n  [key: string]: string[]\n}\n\nexport const tabPathMap = reactive<TabMapTypes>({})\n\nexport const initTabMap = (routes: AppRouteRecordRaw[]) => {\n  for (const v of routes) {\n    const meta = (v.meta ?? {}) as RouteMeta\n    if (!meta?.hidden) {\n      tabPathMap[v.path] = []\n    }\n  }\n}\n\nexport const filterMenusPath = (\n  routes: AppRouteRecordRaw[],\n  allRoutes: AppRouteRecordRaw[]\n): AppRouteRecordRaw[] => {\n  const res: AppRouteRecordRaw[] = []\n  for (const v of routes) {\n    let data: Nullable<AppRouteRecordRaw> = null\n    const meta = (v.meta ?? {}) as RouteMeta\n    if (!meta.hidden || meta.canTo) {\n      const allParentPath = getAllParentPath<AppRouteRecordRaw>(allRoutes, v.path)\n\n      const fullPath = isUrl(v.path) ? v.path : allParentPath.join('/')\n\n      data = cloneDeep(v)\n      data.path = fullPath\n      if (v.children && data) {\n        data.children = filterMenusPath(v.children, allRoutes)\n      }\n\n      if (data) {\n        res.push(data)\n      }\n\n      if (allParentPath.length && Reflect.has(tabPathMap, allParentPath[0])) {\n        tabPathMap[allParentPath[0]].push(fullPath)\n      }\n    }\n  }\n\n  return res\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/TagsView/index.ts",
    "content": "import TagsView from './src/TagsView.vue'\n\nexport { TagsView }\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/TagsView/src/TagsView.vue",
    "content": "<script lang=\"ts\" setup>\nimport { onMounted, watch, computed, unref, ref, nextTick } from 'vue'\nimport { useRouter } from 'vue-router'\nimport type { RouteLocationNormalizedLoaded, RouterLinkProps } from 'vue-router'\nimport { usePermissionStore } from '@/store/modules/permission'\nimport { useTagsViewStore } from '@/store/modules/tagsView'\nimport { useAppStore } from '@/store/modules/app'\nimport { useI18n } from '@/hooks/web/useI18n'\nimport { filterAffixTags } from './helper'\nimport { ContextMenu, ContextMenuExpose } from '@/layout/components/ContextMenu'\nimport { useDesign } from '@/hooks/web/useDesign'\nimport { useTemplateRefsList } from '@vueuse/core'\nimport { ElScrollbar } from 'element-plus'\nimport { useScrollTo } from '@/hooks/event/useScrollTo'\n\nconst { getPrefixCls } = useDesign()\n\nconst prefixCls = getPrefixCls('tags-view')\n\nconst { t } = useI18n()\n\nconst { currentRoute, push, replace } = useRouter()\n\nconst permissionStore = usePermissionStore()\n\nconst routers = computed(() => permissionStore.getRouters)\n\nconst tagsViewStore = useTagsViewStore()\n\nconst visitedViews = computed(() => tagsViewStore.getVisitedViews)\n\nconst affixTagArr = ref<RouteLocationNormalizedLoaded[]>([])\n\nconst appStore = useAppStore()\n\nconst tagsViewIcon = computed(() => appStore.getTagsViewIcon)\n\nconst isDark = computed(() => appStore.getIsDark)\n\n// 初始化tag\nconst initTags = () => {\n  affixTagArr.value = filterAffixTags(unref(routers))\n  for (const tag of unref(affixTagArr)) {\n    // Must have tag name\n    if (tag.name) {\n      tagsViewStore.addVisitedView(tag)\n    }\n  }\n}\n\nconst selectedTag = ref<RouteLocationNormalizedLoaded>()\n\n// 新增tag\nconst addTags = () => {\n  const { name } = unref(currentRoute)\n  if (name) {\n    selectedTag.value = unref(currentRoute)\n    tagsViewStore.addView(unref(currentRoute))\n  }\n  return false\n}\n\n// 关闭选中的tag\nconst closeSelectedTag = (view: RouteLocationNormalizedLoaded) => {\n  if (view?.meta?.affix) return\n  tagsViewStore.delView(view)\n  if (isActive(view)) {\n    toLastView()\n  }\n}\n\n// 关闭全部\nconst closeAllTags = () => {\n  tagsViewStore.delAllViews()\n  toLastView()\n}\n\n// 关闭其它\nconst closeOthersTags = () => {\n  tagsViewStore.delOthersViews(unref(selectedTag) as RouteLocationNormalizedLoaded)\n}\n\n// 重新加载\nconst refreshSelectedTag = async (view?: RouteLocationNormalizedLoaded) => {\n  if (!view) return\n  tagsViewStore.delCachedView()\n  const { path, query } = view\n  await nextTick()\n  replace({\n    path: '/redirect' + path,\n    query: query\n  })\n}\n\n// 关闭左侧\nconst closeLeftTags = () => {\n  tagsViewStore.delLeftViews(unref(selectedTag) as RouteLocationNormalizedLoaded)\n}\n\n// 关闭右侧\nconst closeRightTags = () => {\n  tagsViewStore.delRightViews(unref(selectedTag) as RouteLocationNormalizedLoaded)\n}\n\n// 跳转到最后一个\nconst toLastView = () => {\n  const visitedViews = tagsViewStore.getVisitedViews\n  const latestView = visitedViews.slice(-1)[0]\n  if (latestView) {\n    push(latestView)\n  } else {\n    if (\n      unref(currentRoute).path === permissionStore.getAddRouters[0].path ||\n      unref(currentRoute).path === permissionStore.getAddRouters[0].redirect\n    ) {\n      addTags()\n      return\n    }\n    // TODO: You can set another route\n    push('/')\n  }\n}\n\n// 滚动到选中的tag\nconst moveToCurrentTag = async () => {\n  await nextTick()\n  for (const v of unref(visitedViews)) {\n    if (v.fullPath === unref(currentRoute).path) {\n      moveToTarget(v)\n      if (v.fullPath !== unref(currentRoute).fullPath) {\n        tagsViewStore.updateVisitedView(unref(currentRoute))\n      }\n\n      break\n    }\n  }\n}\n\nconst tagLinksRefs = useTemplateRefsList<RouterLinkProps>()\n\nconst moveToTarget = (currentTag: RouteLocationNormalizedLoaded) => {\n  const wrap$ = unref(scrollbarRef)?.wrapRef\n  let firstTag: Nullable<RouterLinkProps> = null\n  let lastTag: Nullable<RouterLinkProps> = null\n\n  const tagList = unref(tagLinksRefs)\n  // find first tag and last tag\n  if (tagList.length > 0) {\n    firstTag = tagList[0]\n    lastTag = tagList[tagList.length - 1]\n  }\n  if ((firstTag?.to as RouteLocationNormalizedLoaded).fullPath === currentTag.fullPath) {\n    // 直接滚动到0的位置\n    const { start } = useScrollTo({\n      el: wrap$!,\n      position: 'scrollLeft',\n      to: 0,\n      duration: 500\n    })\n    start()\n  } else if ((lastTag?.to as RouteLocationNormalizedLoaded).fullPath === currentTag.fullPath) {\n    // 滚动到最后的位置\n    const { start } = useScrollTo({\n      el: wrap$!,\n      position: 'scrollLeft',\n      to: wrap$!.scrollWidth - wrap$!.offsetWidth,\n      duration: 500\n    })\n    start()\n  } else {\n    // find preTag and nextTag\n    const currentIndex: number = tagList.findIndex(\n      (item) => (item?.to as RouteLocationNormalizedLoaded).fullPath === currentTag.fullPath\n    )\n    const tgsRefs = document.getElementsByClassName(`${prefixCls}__item`)\n\n    const prevTag = tgsRefs[currentIndex - 1] as HTMLElement\n    const nextTag = tgsRefs[currentIndex + 1] as HTMLElement\n\n    // the tag's offsetLeft after of nextTag\n    const afterNextTagOffsetLeft = nextTag.offsetLeft + nextTag.offsetWidth + 4\n\n    // the tag's offsetLeft before of prevTag\n    const beforePrevTagOffsetLeft = prevTag.offsetLeft - 4\n\n    if (afterNextTagOffsetLeft > unref(scrollLeftNumber) + wrap$!.offsetWidth) {\n      const { start } = useScrollTo({\n        el: wrap$!,\n        position: 'scrollLeft',\n        to: afterNextTagOffsetLeft - wrap$!.offsetWidth,\n        duration: 500\n      })\n      start()\n    } else if (beforePrevTagOffsetLeft < unref(scrollLeftNumber)) {\n      const { start } = useScrollTo({\n        el: wrap$!,\n        position: 'scrollLeft',\n        to: beforePrevTagOffsetLeft,\n        duration: 500\n      })\n      start()\n    }\n  }\n}\n\n// 是否是当前tag\nconst isActive = (route: RouteLocationNormalizedLoaded): boolean => {\n  return route.path === unref(currentRoute).path\n}\n\n// 所有右键菜单组件的元素\nconst itemRefs = useTemplateRefsList<ComponentRef<typeof ContextMenu & ContextMenuExpose>>()\n\n// 右键菜单装填改变的时候\nconst visibleChange = (visible: boolean, tagItem: RouteLocationNormalizedLoaded) => {\n  if (visible) {\n    for (const v of unref(itemRefs)) {\n      const elDropdownMenuRef = v.elDropdownMenuRef\n      if (tagItem.fullPath !== v.tagItem.fullPath) {\n        elDropdownMenuRef?.handleClose()\n      }\n    }\n  }\n}\n\n// elscroll 实例\nconst scrollbarRef = ref<ComponentRef<typeof ElScrollbar>>()\n\n// 保存滚动位置\nconst scrollLeftNumber = ref(0)\n\nconst scroll = ({ scrollLeft }) => {\n  scrollLeftNumber.value = scrollLeft as number\n}\n\n// 移动到某个位置\nconst move = (to: number) => {\n  const wrap$ = unref(scrollbarRef)?.wrapRef\n  const { start } = useScrollTo({\n    el: wrap$!,\n    position: 'scrollLeft',\n    to: unref(scrollLeftNumber) + to,\n    duration: 500\n  })\n  start()\n}\n\nonMounted(() => {\n  initTags()\n  addTags()\n})\n\nwatch(\n  () => currentRoute.value,\n  () => {\n    addTags()\n    moveToCurrentTag()\n  }\n)\n</script>\n\n<template>\n  <div\n    :id=\"prefixCls\"\n    :class=\"prefixCls\"\n    class=\"relative w-full flex bg-[#fff] dark:bg-[var(--el-bg-color)]\"\n  >\n    <span\n      :class=\"`${prefixCls}__tool ${prefixCls}__tool--first`\"\n      class=\"h-[var(--tags-view-height)] w-[var(--tags-view-height)] flex cursor-pointer items-center justify-center\"\n      @click=\"move(-200)\"\n    >\n      <Icon\n        icon=\"ep:d-arrow-left\"\n        color=\"var(--el-text-color-placeholder)\"\n        :hover-color=\"isDark ? '#fff' : 'var(--el-color-black)'\"\n      />\n    </span>\n    <div class=\"flex-1 overflow-hidden\">\n      <ElScrollbar ref=\"scrollbarRef\" class=\"h-full\" @scroll=\"scroll\">\n        <div class=\"h-full flex\">\n          <ContextMenu\n            :ref=\"itemRefs.set\"\n            :schema=\"[\n              {\n                icon: 'ep:refresh',\n                label: t('common.reload'),\n                disabled: selectedTag?.fullPath !== item.fullPath,\n                command: () => {\n                  refreshSelectedTag(item)\n                }\n              },\n              {\n                icon: 'ep:close',\n                label: t('common.closeTab'),\n                disabled: !!visitedViews?.length && selectedTag?.meta.affix,\n                command: () => {\n                  closeSelectedTag(item)\n                }\n              },\n              {\n                divided: true,\n                icon: 'ep:d-arrow-left',\n                label: t('common.closeTheLeftTab'),\n                disabled:\n                  !!visitedViews?.length &&\n                  (item.fullPath === visitedViews[0].fullPath ||\n                    selectedTag?.fullPath !== item.fullPath),\n                command: () => {\n                  closeLeftTags()\n                }\n              },\n              {\n                icon: 'ep:d-arrow-right',\n                label: t('common.closeTheRightTab'),\n                disabled:\n                  !!visitedViews?.length &&\n                  (item.fullPath === visitedViews[visitedViews.length - 1].fullPath ||\n                    selectedTag?.fullPath !== item.fullPath),\n                command: () => {\n                  closeRightTags()\n                }\n              },\n              {\n                divided: true,\n                icon: 'ep:discount',\n                label: t('common.closeOther'),\n                disabled: selectedTag?.fullPath !== item.fullPath,\n                command: () => {\n                  closeOthersTags()\n                }\n              },\n              {\n                icon: 'ep:minus',\n                label: t('common.closeAll'),\n                command: () => {\n                  closeAllTags()\n                }\n              }\n            ]\"\n            v-for=\"item in visitedViews\"\n            :key=\"item.fullPath\"\n            :tag-item=\"item\"\n            :class=\"[\n              `${prefixCls}__item`,\n              item?.meta?.affix ? `${prefixCls}__item--affix` : '',\n              {\n                'is-active': isActive(item)\n              }\n            ]\"\n            @visible-change=\"visibleChange\"\n          >\n            <div>\n              <router-link :ref=\"tagLinksRefs.set\" :to=\"{ ...item }\" custom v-slot=\"{ navigate }\">\n                <div\n                  @click=\"navigate\"\n                  class=\"h-full flex items-center justify-center whitespace-nowrap pl-15px\"\n                >\n                  <Icon\n                    v-if=\"\n                      item?.matched &&\n                      item?.matched[1] &&\n                      item?.matched[1]?.meta?.icon &&\n                      tagsViewIcon\n                    \"\n                    :icon=\"item?.matched[1]?.meta?.icon\"\n                    :size=\"12\"\n                    class=\"mr-5px\"\n                  />\n                  {{ t(item?.meta?.title as string) }}\n                  <Icon\n                    :class=\"`${prefixCls}__item--close`\"\n                    color=\"#333\"\n                    icon=\"ep:close\"\n                    :size=\"12\"\n                    @click.prevent.stop=\"closeSelectedTag(item)\"\n                  />\n                </div>\n              </router-link>\n            </div>\n          </ContextMenu>\n        </div>\n      </ElScrollbar>\n    </div>\n    <span\n      :class=\"`${prefixCls}__tool`\"\n      class=\"h-[var(--tags-view-height)] w-[var(--tags-view-height)] flex cursor-pointer items-center justify-center\"\n      @click=\"move(200)\"\n    >\n      <Icon\n        icon=\"ep:d-arrow-right\"\n        color=\"var(--el-text-color-placeholder)\"\n        :hover-color=\"isDark ? '#fff' : 'var(--el-color-black)'\"\n      />\n    </span>\n    <span\n      :class=\"`${prefixCls}__tool`\"\n      class=\"h-[var(--tags-view-height)] w-[var(--tags-view-height)] flex cursor-pointer items-center justify-center\"\n      @click=\"refreshSelectedTag(selectedTag)\"\n    >\n      <Icon\n        icon=\"ep:refresh-right\"\n        color=\"var(--el-text-color-placeholder)\"\n        :hover-color=\"isDark ? '#fff' : 'var(--el-color-black)'\"\n      />\n    </span>\n    <ContextMenu\n      trigger=\"click\"\n      :schema=\"[\n        {\n          icon: 'ep:refresh',\n          label: t('common.reload'),\n          command: () => {\n            refreshSelectedTag(selectedTag)\n          }\n        },\n        {\n          icon: 'ep:close',\n          label: t('common.closeTab'),\n          disabled: !!visitedViews?.length && selectedTag?.meta.affix,\n          command: () => {\n            closeSelectedTag(selectedTag!)\n          }\n        },\n        {\n          divided: true,\n          icon: 'ep:d-arrow-left',\n          label: t('common.closeTheLeftTab'),\n          disabled: !!visitedViews?.length && selectedTag?.fullPath === visitedViews[0].fullPath,\n          command: () => {\n            closeLeftTags()\n          }\n        },\n        {\n          icon: 'ep:d-arrow-right',\n          label: t('common.closeTheRightTab'),\n          disabled:\n            !!visitedViews?.length &&\n            selectedTag?.fullPath === visitedViews[visitedViews.length - 1].fullPath,\n          command: () => {\n            closeRightTags()\n          }\n        },\n        {\n          divided: true,\n          icon: 'ep:discount',\n          label: t('common.closeOther'),\n          command: () => {\n            closeOthersTags()\n          }\n        },\n        {\n          icon: 'ep:minus',\n          label: t('common.closeAll'),\n          command: () => {\n            closeAllTags()\n          }\n        }\n      ]\"\n    >\n      <span\n        :class=\"`${prefixCls}__tool`\"\n        class=\"block h-[var(--tags-view-height)] w-[var(--tags-view-height)] flex cursor-pointer items-center justify-center\"\n      >\n        <Icon\n          icon=\"ep:menu\"\n          color=\"var(--el-text-color-placeholder)\"\n          :hover-color=\"isDark ? '#fff' : 'var(--el-color-black)'\"\n        />\n      </span>\n    </ContextMenu>\n  </div>\n</template>\n\n<style lang=\"scss\" scoped>\n$prefix-cls: #{$namespace}-tags-view;\n\n.#{$prefix-cls} {\n  :deep(.#{$elNamespace}-scrollbar__view) {\n    height: 100%;\n  }\n\n  &__tool {\n    position: relative;\n\n    &::before {\n      position: absolute;\n      top: 1px;\n      left: 0;\n      width: 100%;\n      height: calc(100% - 1px);\n      border-left: 1px solid var(--el-border-color);\n      content: '';\n    }\n\n    &--first {\n      &::before {\n        position: absolute;\n        top: 1px;\n        left: 0;\n        width: 100%;\n        height: calc(100% - 1px);\n        border-right: 1px solid var(--el-border-color);\n        border-left: none;\n        content: '';\n      }\n    }\n  }\n\n  &__item {\n    position: relative;\n    top: 2px;\n    height: calc(100% - 6px);\n    padding-right: 25px;\n    margin-left: 4px;\n    font-size: 12px;\n    cursor: pointer;\n    border: 1px solid #d9d9d9;\n    border-radius: 2px;\n\n    &--close {\n      position: absolute;\n      top: 50%;\n      right: 5px;\n      display: none;\n      transform: translate(0, -50%);\n    }\n    &:not(.#{$prefix-cls}__item--affix):hover {\n      .#{$prefix-cls}__item--close {\n        display: block;\n      }\n    }\n  }\n\n  &__item:not(.is-active) {\n    &:hover {\n      color: var(--el-color-primary);\n    }\n  }\n\n  &__item.is-active {\n    color: var(--el-color-white);\n    background-color: var(--el-color-primary);\n    border: 1px solid var(--el-color-primary);\n    .#{$prefix-cls}__item--close {\n      :deep(span) {\n        color: var(--el-color-white) !important;\n      }\n    }\n  }\n}\n\n.dark {\n  .#{$prefix-cls} {\n    &__tool {\n      &--first {\n        &::after {\n          display: none;\n        }\n      }\n    }\n\n    &__item {\n      border: 1px solid var(--el-border-color);\n    }\n\n    &__item:not(.is-active) {\n      &:hover {\n        color: var(--el-color-primary);\n      }\n    }\n\n    &__item.is-active {\n      color: var(--el-color-white);\n      background-color: var(--el-color-primary);\n      border: 1px solid var(--el-color-primary);\n      .#{$prefix-cls}__item--close {\n        :deep(span) {\n          color: var(--el-color-white) !important;\n        }\n      }\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/TagsView/src/helper.ts",
    "content": "import type { RouteMeta, RouteLocationNormalizedLoaded } from 'vue-router'\nimport { pathResolve } from '@/utils/routerHelper'\n\nexport const filterAffixTags = (routes: AppRouteRecordRaw[], parentPath = '') => {\n  let tags: RouteLocationNormalizedLoaded[] = []\n  routes.forEach((route) => {\n    const meta = route.meta as RouteMeta\n    const tagPath = pathResolve(parentPath, route.path)\n    if (meta?.affix) {\n      tags.push({ ...route, path: tagPath, fullPath: tagPath } as RouteLocationNormalizedLoaded)\n    }\n    if (route.children) {\n      const tempTags: RouteLocationNormalizedLoaded[] = filterAffixTags(route.children, tagPath)\n      if (tempTags.length >= 1) {\n        tags = [...tags, ...tempTags]\n      }\n    }\n  })\n\n  return tags\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/ThemeSwitch/index.ts",
    "content": "import ThemeSwitch from './src/ThemeSwitch.vue'\n\nexport { ThemeSwitch }\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/ThemeSwitch/src/ThemeSwitch.vue",
    "content": "<script lang=\"ts\" setup>\nimport { useAppStore } from '@/store/modules/app'\nimport { useIcon } from '@/hooks/web/useIcon'\nimport { useDesign } from '@/hooks/web/useDesign'\n\ndefineOptions({ name: 'ThemeSwitch' })\n\nconst { getPrefixCls } = useDesign()\n\nconst prefixCls = getPrefixCls('theme-switch')\n\nconst Sun = useIcon({ icon: 'emojione-monotone:sun', color: '#fde047' })\n\nconst CrescentMoon = useIcon({ icon: 'emojione-monotone:crescent-moon', color: '#fde047' })\n\nconst appStore = useAppStore()\n\n// 初始化获取是否是暗黑主题\nconst isDark = ref(appStore.getIsDark)\n\n// 设置switch的背景颜色\nconst blackColor = 'var(--el-color-black)'\n\nconst themeChange = (val: boolean) => {\n  appStore.setIsDark(val)\n}\n</script>\n\n<template>\n  <ElSwitch\n    v-model=\"isDark\"\n    :active-color=\"blackColor\"\n    :active-icon=\"Sun\"\n    :border-color=\"blackColor\"\n    :class=\"prefixCls\"\n    :inactive-color=\"blackColor\"\n    :inactive-icon=\"CrescentMoon\"\n    inline-prompt\n    @change=\"themeChange\"\n  />\n</template>\n<style lang=\"scss\" scoped>\n:deep(.el-switch__core .el-switch__inner .is-icon) {\n  overflow: visible;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/ToolHeader.vue",
    "content": "<script lang=\"tsx\">\nimport { defineComponent, computed } from 'vue'\nimport { Message } from '@/layout/components//Message'\nimport { Collapse } from '@/layout/components/Collapse'\nimport { UserInfo } from '@/layout/components/UserInfo'\nimport { Screenfull } from '@/layout/components/Screenfull'\nimport { Breadcrumb } from '@/layout/components/Breadcrumb'\nimport { SizeDropdown } from '@/layout/components/SizeDropdown'\nimport { LocaleDropdown } from '@/layout/components/LocaleDropdown'\nimport RouterSearch from '@/components/RouterSearch/index.vue'\nimport { useAppStore } from '@/store/modules/app'\nimport { useDesign } from '@/hooks/web/useDesign'\n\nconst { getPrefixCls, variables } = useDesign()\n\nconst prefixCls = getPrefixCls('tool-header')\n\nconst appStore = useAppStore()\n\n// 面包屑\nconst breadcrumb = computed(() => appStore.getBreadcrumb)\n\n// 折叠图标\nconst hamburger = computed(() => appStore.getHamburger)\n\n// 全屏图标\nconst screenfull = computed(() => appStore.getScreenfull)\n\n// 搜索图片\nconst search = computed(() => appStore.search)\n\n// 尺寸图标\nconst size = computed(() => appStore.getSize)\n\n// 布局\nconst layout = computed(() => appStore.getLayout)\n\n// 多语言图标\nconst locale = computed(() => appStore.getLocale)\n\n// 消息图标\nconst message = computed(() => appStore.getMessage)\n\nexport default defineComponent({\n  name: 'ToolHeader',\n  setup() {\n    return () => (\n      <div\n        id={`${variables.namespace}-tool-header`}\n        class={[\n          prefixCls,\n          'h-[var(--top-tool-height)] relative px-[var(--top-tool-p-x)] flex items-center justify-between',\n          'dark:bg-[var(--el-bg-color)]'\n        ]}\n      >\n        {layout.value !== 'top' ? (\n          <div class=\"h-full flex items-center\">\n            {hamburger.value && layout.value !== 'cutMenu' ? (\n              <Collapse class=\"custom-hover\" color=\"var(--top-header-text-color)\"></Collapse>\n            ) : undefined}\n            {breadcrumb.value ? <Breadcrumb class=\"lt-md:hidden\"></Breadcrumb> : undefined}\n          </div>\n        ) : undefined}\n        <div class=\"h-full flex items-center\">\n          {screenfull.value ? (\n            <Screenfull class=\"custom-hover\" color=\"var(--top-header-text-color)\"></Screenfull>\n          ) : undefined}\n          {search.value ? <RouterSearch isModal={false} /> : undefined}\n          {size.value ? (\n            <SizeDropdown class=\"custom-hover\" color=\"var(--top-header-text-color)\"></SizeDropdown>\n          ) : undefined}\n          {locale.value ? (\n            <LocaleDropdown\n              class=\"custom-hover\"\n              color=\"var(--top-header-text-color)\"\n            ></LocaleDropdown>\n          ) : undefined}\n          {message.value ? (\n            <Message class=\"custom-hover\" color=\"var(--top-header-text-color)\"></Message>\n          ) : undefined}\n          <UserInfo></UserInfo>\n        </div>\n      </div>\n    )\n  }\n})\n</script>\n\n<style lang=\"scss\" scoped>\n$prefix-cls: #{$namespace}-tool-header;\n\n.#{$prefix-cls} {\n  transition: left var(--transition-time-02);\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/UserInfo/index.ts",
    "content": "import UserInfo from './src/UserInfo.vue'\n\nexport { UserInfo }\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/UserInfo/src/UserInfo.vue",
    "content": "<script lang=\"ts\" setup>\nimport { ElMessageBox } from 'element-plus'\n\nimport avatarImg from '@/assets/imgs/avatar.gif'\nimport { useDesign } from '@/hooks/web/useDesign'\nimport { useTagsViewStore } from '@/store/modules/tagsView'\nimport { useUserStore } from '@/store/modules/user'\nimport LockDialog from './components/LockDialog.vue'\nimport LockPage from './components/LockPage.vue'\nimport { useLockStore } from '@/store/modules/lock'\n\ndefineOptions({ name: 'UserInfo' })\n\nconst { t } = useI18n()\n\nconst { push, replace } = useRouter()\n\nconst userStore = useUserStore()\n\nconst tagsViewStore = useTagsViewStore()\n\nconst { getPrefixCls } = useDesign()\n\nconst prefixCls = getPrefixCls('user-info')\n\nconst avatar = computed(() => userStore.user.avatar ?? avatarImg)\nconst userName = computed(() => userStore.user.nickname ?? 'Admin')\n\n// 锁定屏幕\nconst lockStore = useLockStore()\nconst getIsLock = computed(() => lockStore.getLockInfo?.isLock ?? false)\nconst dialogVisible = ref<boolean>(false)\nconst lockScreen = () => {\n  dialogVisible.value = true\n}\n\nconst loginOut = async () => {\n  try {\n    await ElMessageBox.confirm(t('common.loginOutMessage'), t('common.reminder'), {\n      confirmButtonText: t('common.ok'),\n      cancelButtonText: t('common.cancel'),\n      type: 'warning'\n    })\n    await userStore.loginOut()\n    tagsViewStore.delAllViews()\n    replace('/login?redirect=/index')\n  } catch {}\n}\nconst toProfile = async () => {\n  push('/user/profile')\n}\nconst toDocument = () => {\n  window.open('https://www.yixiang.co/')\n}\n</script>\n\n<template>\n  <ElDropdown class=\"custom-hover\" :class=\"prefixCls\" trigger=\"click\">\n    <div class=\"flex items-center\">\n      <ElAvatar :src=\"avatar\" alt=\"\" class=\"w-[calc(var(--logo-height)-25px)] rounded-[50%]\" />\n      <span class=\"pl-[5px] text-14px text-[var(--top-header-text-color)] <lg:hidden\">\n        {{ userName }}\n      </span>\n    </div>\n    <template #dropdown>\n      <ElDropdownMenu>\n        <ElDropdownItem>\n          <Icon icon=\"ep:tools\" />\n          <div @click=\"toProfile\">{{ t('common.profile') }}</div>\n        </ElDropdownItem>\n        <ElDropdownItem>\n          <Icon icon=\"ep:menu\" />\n          <div @click=\"toDocument\">{{ t('common.document') }}</div>\n        </ElDropdownItem>\n        <ElDropdownItem divided>\n          <Icon icon=\"ep:lock\" />\n          <div @click=\"lockScreen\">{{ t('lock.lockScreen') }}</div>\n        </ElDropdownItem>\n        <ElDropdownItem divided @click=\"loginOut\">\n          <Icon icon=\"ep:switch-button\" />\n          <div>{{ t('common.loginOut') }}</div>\n        </ElDropdownItem>\n      </ElDropdownMenu>\n    </template>\n  </ElDropdown>\n\n  <LockDialog v-if=\"dialogVisible\" v-model=\"dialogVisible\" />\n\n  <teleport to=\"body\">\n    <transition name=\"fade-bottom\" mode=\"out-in\">\n      <LockPage v-if=\"getIsLock\" />\n    </transition>\n  </teleport>\n</template>\n\n<style scoped lang=\"scss\">\n.fade-bottom-enter-active,\n.fade-bottom-leave-active {\n  transition:\n    opacity 0.25s,\n    transform 0.3s;\n}\n\n.fade-bottom-enter-from {\n  opacity: 0;\n  transform: translateY(-10%);\n}\n\n.fade-bottom-leave-to {\n  opacity: 0;\n  transform: translateY(10%);\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/UserInfo/src/components/LockDialog.vue",
    "content": "<script setup lang=\"ts\">\nimport { useValidator } from '@/hooks/web/useValidator'\nimport { useDesign } from '@/hooks/web/useDesign'\nimport { useLockStore } from '@/store/modules/lock'\nimport avatarImg from '@/assets/imgs/avatar.gif'\nimport { useUserStore } from '@/store/modules/user'\n\nconst { getPrefixCls } = useDesign()\nconst prefixCls = getPrefixCls('lock-dialog')\n\nconst { required } = useValidator()\n\nconst { t } = useI18n()\n\nconst lockStore = useLockStore()\n\nconst props = defineProps({\n  modelValue: {\n    type: Boolean\n  }\n})\n\nconst userStore = useUserStore()\nconst avatar = computed(() => userStore.user.avatar ?? avatarImg)\nconst userName = computed(() => userStore.user.nickname ?? 'Admin')\n\nconst emit = defineEmits(['update:modelValue'])\n\nconst dialogVisible = computed({\n  get: () => props.modelValue,\n  set: (val) => {\n    console.log('set: ', val)\n    emit('update:modelValue', val)\n  }\n})\n\nconst dialogTitle = ref(t('lock.lockScreen'))\n\nconst formData = ref({\n  password: undefined\n})\nconst formRules = reactive({\n  password: [required()]\n})\n\nconst formRef = ref() // 表单 Ref\nconst handleLock = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  dialogVisible.value = false\n  lockStore.setLockInfo({\n    ...formData.value,\n    isLock: true\n  })\n}\n</script>\n\n<template>\n  <Dialog\n    v-model=\"dialogVisible\"\n    width=\"500px\"\n    max-height=\"170px\"\n    :class=\"prefixCls\"\n    :title=\"dialogTitle\"\n  >\n    <div class=\"flex flex-col items-center\">\n      <img :src=\"avatar\" alt=\"\" class=\"w-70px h-70px rounded-[50%]\" />\n      <span class=\"text-14px my-10px text-[var(--top-header-text-color)]\">\n        {{ userName }}\n      </span>\n    </div>\n    <el-form ref=\"formRef\" :model=\"formData\" :rules=\"formRules\" label-width=\"80px\">\n      <el-form-item :label=\"t('lock.lockPassword')\" prop=\"password\">\n        <el-input\n          type=\"password\"\n          v-model=\"formData.password\"\n          :placeholder=\"'请输入' + t('lock.lockPassword')\"\n          clearable\n          show-password\n        />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <ElButton type=\"primary\" @click=\"handleLock\">{{ t('lock.lock') }}</ElButton>\n    </template>\n  </Dialog>\n</template>\n\n<style lang=\"scss\" scoped>\n:global(.v-lock-dialog) {\n  @media (max-width: 767px) {\n    max-width: calc(100vw - 16px);\n  }\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/UserInfo/src/components/LockPage.vue",
    "content": "<script lang=\"ts\" setup>\nimport { resetRouter } from '@/router'\nimport { deleteUserCache } from '@/hooks/web/useCache'\nimport { useLockStore } from '@/store/modules/lock'\nimport { useNow } from '@/hooks/web/useNow'\nimport { useDesign } from '@/hooks/web/useDesign'\nimport { useTagsViewStore } from '@/store/modules/tagsView'\nimport { useUserStore } from '@/store/modules/user'\nimport avatarImg from '@/assets/imgs/avatar.gif'\n\nconst tagsViewStore = useTagsViewStore()\n\nconst { replace } = useRouter()\n\nconst userStore = useUserStore()\n\nconst password = ref('')\nconst loading = ref(false)\nconst errMsg = ref(false)\nconst showDate = ref(true)\n\nconst { getPrefixCls } = useDesign()\nconst prefixCls = getPrefixCls('lock-page')\n\nconst avatar = computed(() => userStore.user.avatar ?? avatarImg)\nconst userName = computed(() => userStore.user.nickname ?? 'Admin')\n\nconst lockStore = useLockStore()\n\nconst { hour, month, minute, meridiem, year, day, week } = useNow(true)\n\nconst { t } = useI18n()\n\n// 解锁\nasync function unLock() {\n  if (!password.value) {\n    return\n  }\n  let pwd = password.value\n  try {\n    loading.value = true\n    const res = await lockStore.unLock(pwd)\n    errMsg.value = !res\n  } finally {\n    loading.value = false\n  }\n}\n\n// 返回登录\nasync function goLogin() {\n  await userStore.loginOut().catch(() => {})\n  // 登出后清理\n  deleteUserCache() // 清空用户缓存\n  tagsViewStore.delAllViews()\n  resetRouter() // 重置静态路由表\n  lockStore.resetLockInfo()\n  replace('/login')\n}\n\nfunction handleShowForm(show = false) {\n  showDate.value = show\n}\n</script>\n\n<template>\n  <div\n    :class=\"prefixCls\"\n    class=\"fixed inset-0 flex h-screen w-screen bg-black items-center justify-center\"\n  >\n    <div\n      :class=\"`${prefixCls}__unlock`\"\n      class=\"absolute top-0 left-1/2 flex pt-5 h-16 items-center justify-center sm:text-md xl:text-xl text-white flex-col cursor-pointer transform translate-x-1/2\"\n      @click=\"handleShowForm(false)\"\n      v-show=\"showDate\"\n    >\n      <Icon icon=\"ep:lock\" />\n      <span>{{ t('lock.unlock') }}</span>\n    </div>\n\n    <div class=\"flex w-screen h-screen justify-center items-center\">\n      <div :class=\"`${prefixCls}__hour`\" class=\"relative mr-5 md:mr-20 w-2/5 h-2/5 md:h-4/5\">\n        <span>{{ hour }}</span>\n        <span class=\"meridiem absolute left-5 top-5 text-md xl:text-xl\" v-show=\"showDate\">\n          {{ meridiem }}\n        </span>\n      </div>\n      <div :class=\"`${prefixCls}__minute w-2/5 h-2/5 md:h-4/5 `\">\n        <span> {{ minute }}</span>\n      </div>\n    </div>\n    <transition name=\"fade-slide\">\n      <div :class=\"`${prefixCls}-entry`\" v-show=\"!showDate\">\n        <div :class=\"`${prefixCls}-entry-content`\">\n          <div class=\"flex flex-col items-center\">\n            <img :src=\"avatar\" alt=\"\" class=\"w-70px h-70px rounded-[50%]\" />\n            <span class=\"text-14px my-10px text-[var(--logo-title-text-color)]\">\n              {{ userName }}\n            </span>\n          </div>\n          <ElInput\n            type=\"password\"\n            :placeholder=\"t('lock.placeholder')\"\n            class=\"enter-x\"\n            v-model=\"password\"\n          />\n          <span :class=\"`text-14px ${prefixCls}-entry__err-msg enter-x`\" v-if=\"errMsg\">\n            {{ t('lock.message') }}\n          </span>\n          <div :class=\"`${prefixCls}-entry__footer enter-x`\">\n            <ElButton\n              type=\"primary\"\n              size=\"small\"\n              class=\"mt-2 mr-2 enter-x\"\n              link\n              :disabled=\"loading\"\n              @click=\"handleShowForm(true)\"\n            >\n              {{ t('common.back') }}\n            </ElButton>\n            <ElButton\n              type=\"primary\"\n              size=\"small\"\n              class=\"mt-2 mr-2 enter-x\"\n              link\n              :disabled=\"loading\"\n              @click=\"goLogin\"\n            >\n              {{ t('lock.backToLogin') }}\n            </ElButton>\n            <ElButton\n              type=\"primary\"\n              class=\"mt-2\"\n              size=\"small\"\n              link\n              @click=\"unLock()\"\n              :disabled=\"loading\"\n            >\n              {{ t('lock.entrySystem') }}\n            </ElButton>\n          </div>\n        </div>\n      </div>\n    </transition>\n\n    <div class=\"absolute bottom-5 w-full text-gray-300 xl:text-xl 2xl:text-3xl text-center enter-y\">\n      <div class=\"text-5xl mb-4 enter-x\" v-show=\"!showDate\">\n        {{ hour }}:{{ minute }} <span class=\"text-3xl\">{{ meridiem }}</span>\n      </div>\n      <div class=\"text-2xl\">{{ year }}/{{ month }}/{{ day }} {{ week }}</div>\n    </div>\n  </div>\n</template>\n\n<style lang=\"scss\" scoped>\n$prefix-cls: '#{$namespace}-lock-page';\n\n// Small screen / tablet\n$screen-sm: 576px;\n\n// Medium screen / desktop\n$screen-md: 768px;\n\n// Large screen / wide desktop\n$screen-lg: 992px;\n\n// Extra large screen / full hd\n$screen-xl: 1200px;\n\n// Extra extra large screen / large desktop\n$screen-2xl: 1600px;\n\n$error-color: #ed6f6f;\n\n.#{$prefix-cls} {\n  z-index: 3000;\n\n  &__unlock {\n    transform: translate(-50%, 0);\n  }\n\n  &__hour,\n  &__minute {\n    display: flex;\n    font-weight: 700;\n    color: #bababa;\n    background-color: #141313;\n    border-radius: 30px;\n    justify-content: center;\n    align-items: center;\n\n    @media screen and (max-width: $screen-md) {\n      span:not(.meridiem) {\n        font-size: 160px;\n      }\n    }\n\n    @media screen and (min-width: $screen-md) {\n      span:not(.meridiem) {\n        font-size: 160px;\n      }\n    }\n\n    @media screen and (max-width: $screen-sm) {\n      span:not(.meridiem) {\n        font-size: 90px;\n      }\n    }\n    @media screen and (min-width: $screen-lg) {\n      span:not(.meridiem) {\n        font-size: 220px;\n      }\n    }\n\n    @media screen and (min-width: $screen-xl) {\n      span:not(.meridiem) {\n        font-size: 260px;\n      }\n    }\n    @media screen and (min-width: $screen-2xl) {\n      span:not(.meridiem) {\n        font-size: 320px;\n      }\n    }\n  }\n\n  &-entry {\n    position: absolute;\n    top: 0;\n    left: 0;\n    display: flex;\n    width: 100%;\n    height: 100%;\n    background-color: rgba(0, 0, 0, 0.5);\n    backdrop-filter: blur(8px);\n    justify-content: center;\n    align-items: center;\n\n    &-content {\n      width: 260px;\n    }\n\n    &__header {\n      text-align: center;\n\n      &-img {\n        width: 70px;\n        margin: 0 auto;\n        border-radius: 50%;\n      }\n\n      &-name {\n        margin-top: 5px;\n        font-weight: 500;\n        color: #bababa;\n      }\n    }\n\n    &__err-msg {\n      display: inline-block;\n      margin-top: 10px;\n      color: $error-color;\n    }\n\n    &__footer {\n      display: flex;\n      justify-content: space-between;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/layout/components/useRenderLayout.tsx",
    "content": "import { computed } from 'vue'\nimport { useAppStore } from '@/store/modules/app'\nimport { Menu } from '@/layout/components/Menu'\nimport { TabMenu } from '@/layout/components/TabMenu'\nimport { TagsView } from '@/layout/components/TagsView'\nimport { Logo } from '@/layout/components/Logo'\nimport AppView from './AppView.vue'\nimport ToolHeader from './ToolHeader.vue'\nimport { ElScrollbar } from 'element-plus'\nimport { useDesign } from '@/hooks/web/useDesign'\n\nconst { getPrefixCls } = useDesign()\n\nconst prefixCls = getPrefixCls('layout')\n\nconst appStore = useAppStore()\n\nconst pageLoading = computed(() => appStore.getPageLoading)\n\n// 标签页\nconst tagsView = computed(() => appStore.getTagsView)\n\n// 菜单折叠\nconst collapse = computed(() => appStore.getCollapse)\n\n// logo\nconst logo = computed(() => appStore.logo)\n\n// 固定头部\nconst fixedHeader = computed(() => appStore.getFixedHeader)\n\n// 是否是移动端\nconst mobile = computed(() => appStore.getMobile)\n\n// 固定菜单\nconst fixedMenu = computed(() => appStore.getFixedMenu)\n\nexport const useRenderLayout = () => {\n  const renderClassic = () => {\n    return (\n      <>\n        <div\n          class={[\n            'absolute top-0 left-0 h-full layout-border__right',\n            { '!fixed z-3000': mobile.value }\n          ]}\n        >\n          {logo.value ? (\n            <Logo\n              class={[\n                'bg-[var(--left-menu-bg-color)] relative',\n                {\n                  '!pl-0': mobile.value && collapse.value,\n                  'w-[var(--left-menu-min-width)]': appStore.getCollapse,\n                  'w-[var(--left-menu-max-width)]': !appStore.getCollapse\n                }\n              ]}\n              style=\"transition: all var(--transition-time-02);\"\n            ></Logo>\n          ) : undefined}\n          <Menu class={[{ '!h-[calc(100%-var(--logo-height))]': logo.value }]}></Menu>\n        </div>\n        <div\n          class={[\n            `${prefixCls}-content`,\n            'absolute top-0 h-[100%]',\n            {\n              'w-[calc(100%-var(--left-menu-min-width))] left-[var(--left-menu-min-width)]':\n                collapse.value && !mobile.value && !mobile.value,\n              'w-[calc(100%-var(--left-menu-max-width))] left-[var(--left-menu-max-width)]':\n                !collapse.value && !mobile.value && !mobile.value,\n              'fixed !w-full !left-0': mobile.value\n            }\n          ]}\n          style=\"transition: all var(--transition-time-02);\"\n        >\n          <ElScrollbar\n            v-loading={pageLoading.value}\n            class={[\n              `${prefixCls}-content-scrollbar`,\n              {\n                '!h-[calc(100%-var(--top-tool-height)-var(--tags-view-height))] mt-[calc(var(--top-tool-height)+var(--tags-view-height))]':\n                  fixedHeader.value\n              }\n            ]}\n          >\n            <div\n              class={[\n                {\n                  'fixed top-0 left-0 z-10': fixedHeader.value,\n                  'w-[calc(100%-var(--left-menu-min-width))] !left-[var(--left-menu-min-width)]':\n                    collapse.value && fixedHeader.value && !mobile.value,\n                  'w-[calc(100%-var(--left-menu-max-width))] !left-[var(--left-menu-max-width)]':\n                    !collapse.value && fixedHeader.value && !mobile.value,\n                  '!w-full !left-0': mobile.value\n                }\n              ]}\n              style=\"transition: all var(--transition-time-02);\"\n            >\n              <ToolHeader\n                class={[\n                  'bg-[var(--top-header-bg-color)]',\n                  {\n                    'layout-border__bottom': !tagsView.value\n                  }\n                ]}\n              ></ToolHeader>\n\n              {tagsView.value ? (\n                <TagsView class=\"layout-border__top layout-border__bottom\"></TagsView>\n              ) : undefined}\n            </div>\n\n            <AppView></AppView>\n          </ElScrollbar>\n        </div>\n      </>\n    )\n  }\n\n  const renderTopLeft = () => {\n    return (\n      <>\n        <div class=\"relative flex items-center bg-[var(--top-header-bg-color)] layout-border__bottom dark:bg-[var(--el-bg-color)]\">\n          {logo.value ? <Logo class=\"custom-hover\"></Logo> : undefined}\n\n          <ToolHeader class=\"flex-1\"></ToolHeader>\n        </div>\n        <div class=\"absolute left-0 top-[var(--logo-height)+1px] h-[calc(100%-1px-var(--logo-height))] w-full flex\">\n          <Menu class=\"relative layout-border__right !h-full\"></Menu>\n          <div\n            class={[\n              `${prefixCls}-content`,\n              'h-[100%]',\n              {\n                'w-[calc(100%-var(--left-menu-min-width))] left-[var(--left-menu-min-width)]':\n                  collapse.value,\n                'w-[calc(100%-var(--left-menu-max-width))] left-[var(--left-menu-max-width)]':\n                  !collapse.value\n              }\n            ]}\n            style=\"transition: all var(--transition-time-02);\"\n          >\n            <ElScrollbar\n              v-loading={pageLoading.value}\n              class={[\n                `${prefixCls}-content-scrollbar`,\n                {\n                  '!h-[calc(100%-var(--tags-view-height))] mt-[calc(var(--tags-view-height))]':\n                    fixedHeader.value && tagsView.value\n                }\n              ]}\n            >\n              {tagsView.value ? (\n                <TagsView\n                  class={[\n                    'layout-border__bottom absolute',\n                    {\n                      '!fixed top-0 left-0 z-10': fixedHeader.value,\n                      'w-[calc(100%-var(--left-menu-min-width))] !left-[var(--left-menu-min-width)] mt-[calc(var(--logo-height)+1px)]':\n                        collapse.value && fixedHeader.value,\n                      'w-[calc(100%-var(--left-menu-max-width))] !left-[var(--left-menu-max-width)] mt-[calc(var(--logo-height)+1px)]':\n                        !collapse.value && fixedHeader.value\n                    }\n                  ]}\n                  style=\"transition: width var(--transition-time-02), left var(--transition-time-02);\"\n                ></TagsView>\n              ) : undefined}\n\n              <AppView></AppView>\n            </ElScrollbar>\n          </div>\n        </div>\n      </>\n    )\n  }\n\n  const renderTop = () => {\n    return (\n      <>\n        <div\n          class={[\n            'flex items-center justify-between bg-[var(--top-header-bg-color)] relative',\n            {\n              'layout-border__bottom': !tagsView.value\n            }\n          ]}\n        >\n          {logo.value ? <Logo class=\"custom-hover\"></Logo> : undefined}\n          <Menu class=\"h-[var(--top-tool-height)] flex-1 px-10px\"></Menu>\n          <ToolHeader></ToolHeader>\n        </div>\n        <div\n          class={[\n            `${prefixCls}-content`,\n            'w-full',\n            {\n              'h-[calc(100%-var(--app-footer-height))]': !fixedHeader.value,\n              'h-[calc(100%-var(--tags-view-height)-var(--app-footer-height))]': fixedHeader.value\n            }\n          ]}\n        >\n          <ElScrollbar\n            v-loading={pageLoading.value}\n            class={[\n              `${prefixCls}-content-scrollbar`,\n              {\n                'mt-[var(--tags-view-height)] !pb-[calc(var(--tags-view-height)+var(--app-footer-height))]':\n                  fixedHeader.value,\n                'pb-[var(--app-footer-height)]': !fixedHeader.value\n              }\n            ]}\n          >\n            {tagsView.value ? (\n              <TagsView\n                class={[\n                  'layout-border__bottom layout-border__top relative',\n                  {\n                    '!fixed w-full top-[calc(var(--top-tool-height)+1px)] left-0': fixedHeader.value\n                  }\n                ]}\n                style=\"transition: width var(--transition-time-02), left var(--transition-time-02);\"\n              ></TagsView>\n            ) : undefined}\n\n            <AppView></AppView>\n          </ElScrollbar>\n        </div>\n      </>\n    )\n  }\n\n  const renderCutMenu = () => {\n    return (\n      <>\n        <div class=\"relative flex items-center bg-[var(--top-header-bg-color)] layout-border__bottom\">\n          {logo.value ? <Logo class=\"custom-hover !pr-15px\"></Logo> : undefined}\n\n          <ToolHeader class=\"flex-1\"></ToolHeader>\n        </div>\n        <div class=\"absolute left-0 top-[var(--logo-height)] h-[calc(100%-var(--logo-height))] w-[calc(100%-2px)] flex\">\n          <TabMenu></TabMenu>\n          <div\n            class={[\n              `${prefixCls}-content`,\n              'h-[100%]',\n              {\n                'w-[calc(100%-var(--tab-menu-min-width))] left-[var(--tab-menu-min-width)]':\n                  collapse.value && !fixedMenu.value,\n                'w-[calc(100%-var(--tab-menu-max-width))] left-[var(--tab-menu-max-width)]':\n                  !collapse.value && !fixedMenu.value,\n                'w-[calc(100%-var(--tab-menu-min-width)-var(--left-menu-max-width))] ml-[var(--left-menu-max-width)]':\n                  collapse.value && fixedMenu.value,\n                'w-[calc(100%-var(--tab-menu-max-width)-var(--left-menu-max-width))] ml-[var(--left-menu-max-width)]':\n                  !collapse.value && fixedMenu.value\n              }\n            ]}\n            style=\"transition: all var(--transition-time-02);\"\n          >\n            <ElScrollbar\n              v-loading={pageLoading.value}\n              class={[\n                `${prefixCls}-content-scrollbar`,\n                {\n                  '!h-[calc(100%-var(--tags-view-height))] mt-[calc(var(--tags-view-height))]':\n                    fixedHeader.value && tagsView.value\n                }\n              ]}\n            >\n              {tagsView.value ? (\n                <TagsView\n                  class={[\n                    'relative layout-border__bottom layout-border__top',\n                    {\n                      '!fixed top-0 left-0 z-10': fixedHeader.value,\n                      'w-[calc(100%-var(--tab-menu-min-width))] !left-[var(--tab-menu-min-width)] mt-[var(--logo-height)]':\n                        collapse.value && fixedHeader.value,\n                      'w-[calc(100%-var(--tab-menu-max-width))] !left-[var(--tab-menu-max-width)] mt-[var(--logo-height)]':\n                        !collapse.value && fixedHeader.value,\n                      '!fixed top-0 !left-[var(--tab-menu-min-width)+var(--left-menu-max-width)] z-10':\n                        fixedHeader.value && fixedMenu.value,\n                      'w-[calc(100%-var(--tab-menu-min-width)-var(--left-menu-max-width))] !left-[var(--tab-menu-min-width)+var(--left-menu-max-width)] mt-[var(--logo-height)]':\n                        collapse.value && fixedHeader.value && fixedMenu.value,\n                      'w-[calc(100%-var(--tab-menu-max-width)-var(--left-menu-max-width))] !left-[var(--tab-menu-max-width)+var(--left-menu-max-width)] mt-[var(--logo-height)]':\n                        !collapse.value && fixedHeader.value && fixedMenu.value\n                    }\n                  ]}\n                  style=\"transition: width var(--transition-time-02), left var(--transition-time-02);\"\n                ></TagsView>\n              ) : undefined}\n\n              <AppView></AppView>\n            </ElScrollbar>\n          </div>\n        </div>\n      </>\n    )\n  }\n\n  return {\n    renderClassic,\n    renderTopLeft,\n    renderTop,\n    renderCutMenu\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/locales/en.ts",
    "content": "export default {\n  common: {\n    inputText: 'Please input',\n    selectText: 'Please select',\n    startTimeText: 'Start time',\n    endTimeText: 'End time',\n    login: 'Login',\n    required: 'This is required',\n    loginOut: 'Login out',\n    document: 'Document',\n    profile: 'User Center',\n    reminder: 'Reminder',\n    loginOutMessage: 'Exit the system?',\n    back: 'Back',\n    ok: 'OK',\n    save: 'Save',\n    cancel: 'Cancel',\n    close: 'Close',\n    reload: 'Reload current',\n    success: 'Success',\n    closeTab: 'Close current',\n    closeTheLeftTab: 'Close left',\n    closeTheRightTab: 'Close right',\n    closeOther: 'Close other',\n    closeAll: 'Close all',\n    prevLabel: 'Prev',\n    nextLabel: 'Next',\n    skipLabel: 'Jump',\n    doneLabel: 'End',\n    menu: 'Menu',\n    menuDes: 'Menu bar rendered in routed structure',\n    collapse: 'Collapse',\n    collapseDes: 'Expand and zoom the menu bar',\n    tagsView: 'Tags view',\n    tagsViewDes: 'Used to record routing history',\n    tool: 'Tool',\n    toolDes: 'Used to set up custom systems',\n    query: 'Query',\n    reset: 'Reset',\n    shrink: 'Put away',\n    expand: 'Expand',\n    confirmTitle: 'System Hint',\n    exportMessage: 'Whether to confirm export data item?',\n    importMessage: 'Whether to confirm import data item?',\n    createSuccess: 'Create Success',\n    updateSuccess: 'Update Success',\n    delMessage: 'Delete the selected data?',\n    delDataMessage: 'Delete the data?',\n    delNoData: 'Please select the data to delete',\n    delSuccess: 'Deleted successfully',\n    index: 'Index',\n    status: 'Status',\n    createTime: 'Create Time',\n    updateTime: 'Update Time',\n    copy: 'Copy',\n    copySuccess: 'Copy Success',\n    copyError: 'Copy Error'\n  },\n  lock: {\n    lockScreen: 'Lock screen',\n    lock: 'Lock',\n    lockPassword: 'Lock screen password',\n    unlock: 'Click to unlock',\n    backToLogin: 'Back to login',\n    entrySystem: 'Entry the system',\n    placeholder: 'Please enter the lock screen password',\n    message: 'Lock screen password error'\n  },\n  error: {\n    noPermission: `Sorry, you don't have permission to access this page.`,\n    pageError: 'Sorry, the page you visited does not exist.',\n    networkError: 'Sorry, the server reported an error.',\n    returnToHome: 'Return to home'\n  },\n  permission: {\n    hasPermission: `Please set the operation permission label value`,\n    hasRole: `Please set the role permission tag value`\n  },\n  setting: {\n    projectSetting: 'Project setting',\n    theme: 'Theme',\n    layout: 'Layout',\n    systemTheme: 'System theme',\n    menuTheme: 'Menu theme',\n    interfaceDisplay: 'Interface display',\n    breadcrumb: 'Breadcrumb',\n    breadcrumbIcon: 'Breadcrumb icon',\n    collapseMenu: 'Collapse menu',\n    hamburgerIcon: 'Hamburger icon',\n    screenfullIcon: 'Screenfull icon',\n    sizeIcon: 'Size icon',\n    localeIcon: 'Locale icon',\n    messageIcon: 'Message icon',\n    tagsView: 'Tags view',\n    logo: 'Logo',\n    greyMode: 'Grey mode',\n    fixedHeader: 'Fixed header',\n    headerTheme: 'Header theme',\n    cutMenu: 'Cut Menu',\n    copy: 'Copy',\n    clearAndReset: 'Clear cache and reset',\n    copySuccess: 'Copy success',\n    copyFailed: 'Copy failed',\n    footer: 'Footer',\n    uniqueOpened: 'Unique opened',\n    tagsViewIcon: 'Tags view icon',\n    reExperienced: 'Please exit the login experience again',\n    fixedMenu: 'Fixed menu'\n  },\n  size: {\n    default: 'Default',\n    large: 'Large',\n    small: 'Small'\n  },\n  login: {\n    welcome: 'Welcome to the system',\n    message: 'Backstage management system',\n    tenantname: 'TenantName',\n    username: 'Username',\n    password: 'Password',\n    code: 'verification code',\n    login: 'Sign in',\n    relogin: 'Sign in again',\n    otherLogin: 'Sign in with',\n    register: 'Register',\n    checkPassword: 'Confirm password',\n    remember: 'Remember me',\n    hasUser: 'Existing account? Go to login',\n    forgetPassword: 'Forget password?',\n    tenantNamePlaceholder: 'Please Enter Tenant Name',\n    usernamePlaceholder: 'Please Enter Username',\n    passwordPlaceholder: 'Please Enter Password',\n    codePlaceholder: 'Please Enter Verification Code',\n    mobileTitle: 'Mobile sign in',\n    mobileNumber: 'Mobile Number',\n    mobileNumberPlaceholder: 'Plaease Enter Mobile Number',\n    backLogin: 'back',\n    getSmsCode: 'Get SMS Code',\n    btnMobile: 'Mobile sign in',\n    btnQRCode: 'QR code sign in',\n    qrcode: 'Scan the QR code to log in',\n    btnRegister: 'Sign up',\n    SmsSendMsg: 'code has been sent'\n  },\n  captcha: {\n    verification: 'Please complete security verification',\n    slide: 'Swipe right to complete verification',\n    point: 'Please click',\n    success: 'Verification succeeded',\n    fail: 'verification failed'\n  },\n  router: {\n    login: 'Login',\n    home: 'Home',\n    analysis: 'Analysis',\n    workplace: 'Workplace'\n  },\n  analysis: {\n    newUser: 'New user',\n    unreadInformation: 'Unread information',\n    transactionAmount: 'Transaction amount',\n    totalShopping: 'Total Shopping',\n    monthlySales: 'Monthly sales',\n    userAccessSource: 'User access source',\n    january: 'January',\n    february: 'February',\n    march: 'March',\n    april: 'April',\n    may: 'May',\n    june: 'June',\n    july: 'July',\n    august: 'August',\n    september: 'September',\n    october: 'October',\n    november: 'November',\n    december: 'December',\n    estimate: 'Estimate',\n    actual: 'Actual',\n    directAccess: 'Airect access',\n    mailMarketing: 'Mail marketing',\n    allianceAdvertising: 'Alliance advertising',\n    videoAdvertising: 'Video advertising',\n    searchEngines: 'Search engines',\n    weeklyUserActivity: 'Weekly user activity',\n    activeQuantity: 'Active quantity',\n    monday: 'Monday',\n    tuesday: 'Tuesday',\n    wednesday: 'Wednesday',\n    thursday: 'Thursday',\n    friday: 'Friday',\n    saturday: 'Saturday',\n    sunday: 'Sunday'\n  },\n  workplace: {\n    welcome: 'Hello',\n    happyDay: 'Wish you happy every day!',\n    toady: `It's sunny today`,\n    notice: 'Announcement',\n    project: 'Project',\n    access: 'Project access',\n    toDo: 'To do',\n    introduction: 'A serious introduction',\n    shortcutOperation: 'Quick entry',\n    operation: 'Operation',\n    index: 'Index',\n    personal: 'Personal',\n    team: 'Team',\n    quote: 'Quote',\n    contribution: 'Contribution',\n    hot: 'Hot',\n    yield: 'Yield',\n    dynamic: 'Dynamic',\n    push: 'push',\n    follow: 'Follow'\n  },\n  form: {\n    input: 'Input',\n    inputNumber: 'InputNumber',\n    default: 'Default',\n    icon: 'Icon',\n    mixed: 'Mixed',\n    textarea: 'Textarea',\n    slot: 'Slot',\n    position: 'Position',\n    autocomplete: 'Autocomplete',\n    select: 'Select',\n    selectGroup: 'Select Group',\n    selectV2: 'SelectV2',\n    cascader: 'Cascader',\n    switch: 'Switch',\n    rate: 'Rate',\n    colorPicker: 'Color Picker',\n    transfer: 'Transfer',\n    render: 'Render',\n    radio: 'Radio',\n    button: 'Button',\n    checkbox: 'Checkbox',\n    slider: 'Slider',\n    datePicker: 'Date Picker',\n    shortcuts: 'Shortcuts',\n    today: 'Today',\n    yesterday: 'Yesterday',\n    aWeekAgo: 'A week ago',\n    week: 'Week',\n    year: 'Year',\n    month: 'Month',\n    dates: 'Dates',\n    daterange: 'Date Range',\n    monthrange: 'Month Range',\n    dateTimePicker: 'DateTimePicker',\n    dateTimerange: 'Datetime Range',\n    timePicker: 'Time Picker',\n    timeSelect: 'Time Select',\n    inputPassword: 'input Password',\n    passwordStrength: 'Password Strength',\n    operate: 'operate',\n    change: 'Change',\n    restore: 'Restore',\n    disabled: 'Disabled',\n    disablement: 'Disablement',\n    delete: 'Delete',\n    add: 'Add',\n    setValue: 'Set value',\n    resetValue: 'Reset value',\n    set: 'Set',\n    subitem: 'Subitem',\n    formValidation: 'Form validation',\n    verifyReset: 'Verify reset',\n    remark: 'Remark'\n  },\n  watermark: {\n    watermark: 'Watermark'\n  },\n  table: {\n    table: 'Table',\n    index: 'Index',\n    title: 'Title',\n    author: 'Author',\n    createTime: 'Create time',\n    action: 'Action',\n    pagination: 'pagination',\n    reserveIndex: 'Reserve index',\n    restoreIndex: 'Restore index',\n    showSelections: 'Show selections',\n    hiddenSelections: 'Restore selections',\n    showExpandedRows: 'Show expanded rows',\n    hiddenExpandedRows: 'Hidden expanded rows',\n    header: 'Header'\n  },\n  action: {\n    create: 'Create',\n    add: 'Add',\n    del: 'Delete',\n    delete: 'Delete',\n    edit: 'Edit',\n    update: 'Update',\n    preview: 'Preview',\n    more: 'More',\n    sync: 'Sync',\n    save: 'Save',\n    detail: 'Detail',\n    export: 'Export',\n    import: 'Import',\n    generate: 'Generate',\n    logout: 'Login Out',\n    test: 'Test',\n    typeCreate: 'Dict Type Create',\n    typeUpdate: 'Dict Type Eidt',\n    dataCreate: 'Dict Data Create',\n    dataUpdate: 'Dict Data Eidt',\n    fileUpload: 'File Upload'\n  },\n  dialog: {\n    dialog: 'Dialog',\n    open: 'Open',\n    close: 'Close'\n  },\n  sys: {\n    api: {\n      operationFailed: 'Operation failed',\n      errorTip: 'Error Tip',\n      errorMessage: 'The operation failed, the system is abnormal!',\n      timeoutMessage: 'Login timed out, please log in again!',\n      apiTimeoutMessage: 'The interface request timed out, please refresh the page and try again!',\n      apiRequestFailed: 'The interface request failed, please try again later!',\n      networkException: 'network anomaly',\n      networkExceptionMsg:\n        'Please check if your network connection is normal! The network is abnormal',\n\n      errMsg401: 'The user does not have permission (token, user name, password error)!',\n      errMsg403: 'The user is authorized, but access is forbidden!',\n      errMsg404: 'Network request error, the resource was not found!',\n      errMsg405: 'Network request error, request method not allowed!',\n      errMsg408: 'Network request timed out!',\n      errMsg500: 'Server error, please contact the administrator!',\n      errMsg501: 'The network is not implemented!',\n      errMsg502: 'Network Error!',\n      errMsg503: 'The service is unavailable, the server is temporarily overloaded or maintained!',\n      errMsg504: 'Network timeout!',\n      errMsg505: 'The http version does not support the request!',\n      errMsg901: 'Demo mode, no write operations are possible!'\n    },\n    app: {\n      logoutTip: 'Reminder',\n      logoutMessage: 'Confirm to exit the system?',\n      menuLoading: 'Menu loading...'\n    },\n    exception: {\n      backLogin: 'Back Login',\n      backHome: 'Back Home',\n      subTitle403: \"Sorry, you don't have access to this page.\",\n      subTitle404: 'Sorry, the page you visited does not exist.',\n      subTitle500: 'Sorry, the server is reporting an error.',\n      noDataTitle: 'No data on the current page.',\n      networkErrorTitle: 'Network Error',\n      networkErrorSubTitle:\n        'Sorry, Your network connection has been disconnected, please check your network!'\n    },\n    lock: {\n      unlock: 'Click to unlock',\n      alert: 'Lock screen password error',\n      backToLogin: 'Back to login',\n      entry: 'Enter the system',\n      placeholder: 'Please enter the lock screen password or user password'\n    },\n    login: {\n      backSignIn: 'Back sign in',\n      mobileSignInFormTitle: 'Mobile sign in',\n      qrSignInFormTitle: 'Qr code sign in',\n      signInFormTitle: 'Sign in',\n      signUpFormTitle: 'Sign up',\n      forgetFormTitle: 'Reset password',\n\n      signInTitle: 'Backstage management system',\n      signInDesc: 'Enter your personal details and get started!',\n      policy: 'I agree to the xxx Privacy Policy',\n      scanSign: `scanning the code to complete the login`,\n\n      loginButton: 'Sign in',\n      registerButton: 'Sign up',\n      rememberMe: 'Remember me',\n      forgetPassword: 'Forget Password?',\n      otherSignIn: 'Sign in with',\n\n      // notify\n      loginSuccessTitle: 'Login successful',\n      loginSuccessDesc: 'Welcome back',\n\n      // placeholder\n      accountPlaceholder: 'Please input username',\n      passwordPlaceholder: 'Please input password',\n      smsPlaceholder: 'Please input sms code',\n      mobilePlaceholder: 'Please input mobile',\n      policyPlaceholder: 'Register after checking',\n      diffPwd: 'The two passwords are inconsistent',\n\n      userName: 'Username',\n      password: 'Password',\n      confirmPassword: 'Confirm Password',\n      email: 'Email',\n      smsCode: 'SMS code',\n      mobile: 'Mobile'\n    }\n  },\n  profile: {\n    user: {\n      title: 'Personal Information',\n      username: 'User Name',\n      nickname: 'Nick Name',\n      mobile: 'Phone Number',\n      email: 'User Mail',\n      dept: 'Department',\n      posts: 'Position',\n      roles: 'Own Role',\n      sex: 'Sex',\n      man: 'Man',\n      woman: 'Woman',\n      createTime: 'Created Date'\n    },\n    info: {\n      title: 'Basic Information',\n      basicInfo: 'Basic Information',\n      resetPwd: 'Reset Password',\n      userSocial: 'Social Information'\n    },\n    rules: {\n      nickname: 'Please Enter User Nickname',\n      mail: 'Please Input The Email Address',\n      truemail: 'Please Input The Correct Email Address',\n      phone: 'Please Enter The Phone Number',\n      truephone: 'Please Enter The Correct Phone Number'\n    },\n    password: {\n      oldPassword: 'Old PassWord',\n      newPassword: 'New Password',\n      confirmPassword: 'Confirm Password',\n      oldPwdMsg: 'Please Enter Old Password',\n      newPwdMsg: 'Please Enter New Password',\n      cfPwdMsg: 'Please Enter Confirm Password',\n      diffPwd: 'The Passwords Entered Twice No Match'\n    }\n  },\n  cropper: {\n    selectImage: 'Select Image',\n    uploadSuccess: 'Uploaded success!',\n    modalTitle: 'Avatar upload',\n    okText: 'Confirm and upload',\n    btn_reset: 'Reset',\n    btn_rotate_left: 'Counterclockwise rotation',\n    btn_rotate_right: 'Clockwise rotation',\n    btn_scale_x: 'Flip horizontal',\n    btn_scale_y: 'Flip vertical',\n    btn_zoom_in: 'Zoom in',\n    btn_zoom_out: 'Zoom out',\n    preview: 'Preivew'\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/locales/zh-CN.ts",
    "content": "export default {\n  common: {\n    inputText: '请输入',\n    selectText: '请选择',\n    startTimeText: '开始时间',\n    endTimeText: '结束时间',\n    login: '登录',\n    required: '该项为必填项',\n    loginOut: '退出系统',\n    document: '项目文档',\n    profile: '个人中心',\n    reminder: '温馨提示',\n    loginOutMessage: '是否退出本系统？',\n    back: '返回',\n    ok: '确定',\n    save: '保存',\n    cancel: '取消',\n    close: '关闭',\n    reload: '重新加载',\n    success: '成功',\n    closeTab: '关闭标签页',\n    closeTheLeftTab: '关闭左侧标签页',\n    closeTheRightTab: '关闭右侧标签页',\n    closeOther: '关闭其他标签页',\n    closeAll: '关闭全部标签页',\n    prevLabel: '上一步',\n    nextLabel: '下一步',\n    skipLabel: '跳过',\n    doneLabel: '结束',\n    menu: '菜单',\n    menuDes: '以路由的结构渲染的菜单栏',\n    collapse: '展开缩收',\n    collapseDes: '展开和缩放菜单栏',\n    tagsView: '标签页',\n    tagsViewDes: '用于记录路由历史记录',\n    tool: '工具',\n    toolDes: '用于设置定制系统',\n    query: '查询',\n    reset: '重置',\n    shrink: '收起',\n    expand: '展开',\n    confirmTitle: '系统提示',\n    exportMessage: '是否确认导出数据项？',\n    importMessage: '是否确认导入数据项？',\n    createSuccess: '新增成功',\n    updateSuccess: '修改成功',\n    delMessage: '是否删除所选中数据？',\n    delDataMessage: '是否删除数据？',\n    delNoData: '请选择需要删除的数据',\n    delSuccess: '删除成功',\n    index: '序号',\n    status: '状态',\n    createTime: '创建时间',\n    updateTime: '更新时间',\n    copy: '复制',\n    copySuccess: '复制成功',\n    copyError: '复制失败'\n  },\n  lock: {\n    lockScreen: '锁定屏幕',\n    lock: '锁定',\n    lockPassword: '锁屏密码',\n    unlock: '点击解锁',\n    backToLogin: '返回登录',\n    entrySystem: '进入系统',\n    placeholder: '请输入锁屏密码',\n    message: '锁屏密码错误'\n  },\n  error: {\n    noPermission: `抱歉，您无权访问此页面。`,\n    pageError: '抱歉，您访问的页面不存在。',\n    networkError: '抱歉，服务器报告错误。',\n    returnToHome: '返回首页'\n  },\n  permission: {\n    hasPermission: `请设置操作权限标签值`,\n    hasRole: `请设置角色权限标签值`\n  },\n  setting: {\n    projectSetting: '项目配置',\n    theme: '主题',\n    layout: '布局',\n    systemTheme: '系统主题',\n    menuTheme: '菜单主题',\n    interfaceDisplay: '界面显示',\n    breadcrumb: '面包屑',\n    breadcrumbIcon: '面包屑图标',\n    collapseMenu: '折叠菜单',\n    hamburgerIcon: '折叠图标',\n    screenfullIcon: '全屏图标',\n    sizeIcon: '尺寸图标',\n    localeIcon: '多语言图标',\n    messageIcon: '消息图标',\n    tagsView: '标签页',\n    logo: '标志',\n    greyMode: '灰色模式',\n    fixedHeader: '固定头部',\n    headerTheme: '头部主题',\n    cutMenu: '切割菜单',\n    copy: '拷贝',\n    clearAndReset: '清除缓存并且重置',\n    copySuccess: '拷贝成功',\n    copyFailed: '拷贝失败',\n    footer: '页脚',\n    uniqueOpened: '菜单手风琴',\n    tagsViewIcon: '标签页图标',\n    reExperienced: '请重新退出登录体验',\n    fixedMenu: '固定菜单'\n  },\n  size: {\n    default: '默认',\n    large: '大',\n    small: '小'\n  },\n  login: {\n    welcome: '欢迎使用yshop意象点餐系统',\n    message: '欢迎使用yshop意象点餐系统',\n    tenantname: '租户名称',\n    username: '用户名',\n    password: '密码',\n    code: '验证码',\n    login: '登录',\n    relogin: '重新登录',\n    otherLogin: '其他登录方式',\n    register: '注册',\n    checkPassword: '确认密码',\n    remember: '记住我',\n    hasUser: '已有账号？去登录',\n    forgetPassword: '忘记密码?',\n    tenantNamePlaceholder: '请输入租户名称',\n    usernamePlaceholder: '请输入用户名',\n    passwordPlaceholder: '请输入密码',\n    codePlaceholder: '请输入验证码',\n    mobileTitle: '手机登录',\n    mobileNumber: '手机号码',\n    mobileNumberPlaceholder: '请输入手机号码',\n    backLogin: '返回',\n    getSmsCode: '获取验证码',\n    btnMobile: '手机登录',\n    btnQRCode: '二维码登录',\n    qrcode: '扫描二维码登录',\n    btnRegister: '注册',\n    SmsSendMsg: '验证码已发送'\n  },\n  captcha: {\n    verification: '请完成安全验证',\n    slide: '向右滑动完成验证',\n    point: '请依次点击',\n    success: '验证成功',\n    fail: '验证失败'\n  },\n  router: {\n    login: '登录',\n    socialLogin: '社交登录',\n    home: '首页',\n    analysis: '分析页',\n    workplace: '工作台'\n  },\n  analysis: {\n    newUser: '新增用户',\n    unreadInformation: '未读消息',\n    transactionAmount: '成交金额',\n    totalShopping: '购物总量',\n    monthlySales: '每月销售额',\n    userAccessSource: '用户访问来源',\n    january: '一月',\n    february: '二月',\n    march: '三月',\n    april: '四月',\n    may: '五月',\n    june: '六月',\n    july: '七月',\n    august: '八月',\n    september: '九月',\n    october: '十月',\n    november: '十一月',\n    december: '十二月',\n    estimate: '预计',\n    actual: '实际',\n    directAccess: '直接访问',\n    mailMarketing: '邮件营销',\n    allianceAdvertising: '联盟广告',\n    videoAdvertising: '视频广告',\n    searchEngines: '搜索引擎',\n    weeklyUserActivity: '每周用户活跃量',\n    activeQuantity: '活跃量',\n    monday: '周一',\n    tuesday: '周二',\n    wednesday: '周三',\n    thursday: '周四',\n    friday: '周五',\n    saturday: '周六',\n    sunday: '周日'\n  },\n  workplace: {\n    welcome: '你好',\n    happyDay: '祝你开心每一天!',\n    toady: '今日晴',\n    notice: '通知公告',\n    project: '项目数',\n    access: '项目访问',\n    toDo: '待办',\n    introduction: '一个正经的简介',\n    shortcutOperation: '快捷入口',\n    operation: '操作',\n    index: '指数',\n    personal: '个人',\n    team: '团队',\n    quote: '引用',\n    contribution: '贡献',\n    hot: '热度',\n    yield: '产量',\n    dynamic: '动态',\n    push: '推送',\n    follow: '关注'\n  },\n  form: {\n    input: '输入框',\n    inputNumber: '数字输入框',\n    default: '默认',\n    icon: '图标',\n    mixed: '复合型',\n    textarea: '多行文本',\n    slot: '插槽',\n    position: '位置',\n    autocomplete: '自动补全',\n    select: '选择器',\n    selectGroup: '选项分组',\n    selectV2: '虚拟列表选择器',\n    cascader: '级联选择器',\n    switch: '开关',\n    rate: '评分',\n    colorPicker: '颜色选择器',\n    transfer: '穿梭框',\n    render: '渲染器',\n    radio: '单选框',\n    button: '按钮',\n    checkbox: '多选框',\n    slider: '滑块',\n    datePicker: '日期选择器',\n    shortcuts: '快捷选项',\n    today: '今天',\n    yesterday: '昨天',\n    aWeekAgo: '一周前',\n    week: '周',\n    year: '年',\n    month: '月',\n    dates: '日期',\n    daterange: '日期范围',\n    monthrange: '月份范围',\n    dateTimePicker: '日期时间选择器',\n    dateTimerange: '日期时间范围',\n    timePicker: '时间选择器',\n    timeSelect: '时间选择',\n    inputPassword: '密码输入框',\n    passwordStrength: '密码强度',\n    operate: '操作',\n    change: '更改',\n    restore: '还原',\n    disabled: '禁用',\n    disablement: '解除禁用',\n    delete: '删除',\n    add: '添加',\n    setValue: '设置值',\n    resetValue: '重置值',\n    set: '设置',\n    subitem: '子项',\n    formValidation: '表单验证',\n    verifyReset: '验证重置',\n    remark: '备注'\n  },\n  watermark: {\n    watermark: '水印'\n  },\n  table: {\n    table: '表格',\n    index: '序号',\n    title: '标题',\n    author: '作者',\n    createTime: '创建时间',\n    action: '操作',\n    pagination: '分页',\n    reserveIndex: '叠加序号',\n    restoreIndex: '还原序号',\n    showSelections: '显示多选',\n    hiddenSelections: '隐藏多选',\n    showExpandedRows: '显示展开行',\n    hiddenExpandedRows: '隐藏展开行',\n    header: '头部'\n  },\n  action: {\n    setPrint: '配置打印机',\n    check: '审核',\n    buyDetail: '购买记录',\n    order: '订单',\n    qrcode: '二维码',\n    couponRecord: '优惠券领取记录',\n    yue: '积分余额',\n    userDetail: '用户详情',\n    refundOrder: '订单退款',\n    orderRecord: '订单记录',\n    orderDetail: '订单详情',\n    updateOrder: '修改订单',\n    orderSend: '订单发货',\n    remark: '备注',\n    sendInfo: '配送信息',\n    batchCreate: '批量新增',\n    create: '新增',\n    add: '新增',\n    del: '删除',\n    delete: '删除',\n    edit: '编辑',\n    update: '编辑',\n    preview: '预览',\n    more: '更多',\n    sync: '同步',\n    save: '保存',\n    detail: '详情',\n    export: '导出',\n    import: '导入',\n    generate: '生成',\n    logout: '强制退出',\n    test: '测试',\n    typeCreate: '字典类型新增',\n    typeUpdate: '字典类型编辑',\n    dataCreate: '字典数据新增',\n    dataUpdate: '字典数据编辑'\n  },\n  dialog: {\n    dialog: '弹窗',\n    open: '打开',\n    close: '关闭'\n  },\n  sys: {\n    api: {\n      operationFailed: '操作失败',\n      errorTip: '错误提示',\n      errorMessage: '操作失败,系统异常!',\n      timeoutMessage: '登录超时,请重新登录!',\n      apiTimeoutMessage: '接口请求超时,请刷新页面重试!',\n      apiRequestFailed: '请求出错，请稍候重试',\n      networkException: '网络异常',\n      networkExceptionMsg: '网络异常，请检查您的网络连接是否正常!',\n      errMsg401: '用户没有权限（令牌、用户名、密码错误）!',\n      errMsg403: '用户得到授权，但是访问是被禁止的。!',\n      errMsg404: '网络请求错误,未找到该资源!',\n      errMsg405: '网络请求错误,请求方法未允许!',\n      errMsg408: '网络请求超时!',\n      errMsg500: '服务器错误,请联系管理员!',\n      errMsg501: '网络未实现!',\n      errMsg502: '网络错误!',\n      errMsg503: '服务不可用，服务器暂时过载或维护!',\n      errMsg504: '网络超时!',\n      errMsg505: 'http版本不支持该请求!',\n      errMsg901: '演示模式，无法进行写操作!'\n    },\n    app: {\n      logoutTip: '温馨提醒',\n      logoutMessage: '是否确认退出系统?',\n      menuLoading: '菜单加载中...'\n    },\n    exception: {\n      backLogin: '返回登录',\n      backHome: '返回首页',\n      subTitle403: '抱歉，您无权访问此页面。',\n      subTitle404: '抱歉，您访问的页面不存在。',\n      subTitle500: '抱歉，服务器报告错误。',\n      noDataTitle: '当前页无数据',\n      networkErrorTitle: '网络错误',\n      networkErrorSubTitle: '抱歉，您的网络连接已断开，请检查您的网络！'\n    },\n    lock: {\n      unlock: '点击解锁',\n      alert: '锁屏密码错误',\n      backToLogin: '返回登录',\n      entry: '进入系统',\n      placeholder: '请输入锁屏密码或者用户密码'\n    },\n    login: {\n      backSignIn: '返回',\n      signInFormTitle: '登录',\n      ssoFormTitle: '三方授权',\n      mobileSignInFormTitle: '手机登录',\n      qrSignInFormTitle: '二维码登录',\n      signUpFormTitle: '注册',\n      forgetFormTitle: '重置密码',\n      signInTitle: '开箱即用的中后台管理系统',\n      signInDesc: '输入您的个人详细信息开始使用！',\n      policy: '我同意xxx隐私政策',\n      scanSign: `扫码后点击\"确认\"，即可完成登录`,\n      loginButton: '登录',\n      registerButton: '注册',\n      rememberMe: '记住我',\n      forgetPassword: '忘记密码?',\n      otherSignIn: '其他登录方式',\n      // notify\n      loginSuccessTitle: '登录成功',\n      loginSuccessDesc: '欢迎回来',\n      // placeholder\n      accountPlaceholder: '请输入账号',\n      passwordPlaceholder: '请输入密码',\n      smsPlaceholder: '请输入验证码',\n      mobilePlaceholder: '请输入手机号码',\n      policyPlaceholder: '勾选后才能注册',\n      diffPwd: '两次输入密码不一致',\n      userName: '账号',\n      password: '密码',\n      confirmPassword: '确认密码',\n      email: '邮箱',\n      smsCode: '短信验证码',\n      mobile: '手机号码'\n    }\n  },\n  profile: {\n    user: {\n      title: '个人信息',\n      username: '用户名称',\n      nickname: '用户昵称',\n      mobile: '手机号码',\n      email: '用户邮箱',\n      dept: '所属部门',\n      posts: '所属岗位',\n      roles: '所属角色',\n      sex: '性别',\n      man: '男',\n      woman: '女',\n      createTime: '创建日期'\n    },\n    info: {\n      title: '基本信息',\n      basicInfo: '基本资料',\n      resetPwd: '修改密码',\n      userSocial: '社交信息'\n    },\n    rules: {\n      nickname: '请输入用户昵称',\n      mail: '请输入邮箱地址',\n      truemail: '请输入正确的邮箱地址',\n      phone: '请输入正确的手机号码',\n      truephone: '请输入正确的手机号码'\n    },\n    password: {\n      oldPassword: '旧密码',\n      newPassword: '新密码',\n      confirmPassword: '确认密码',\n      oldPwdMsg: '请输入旧密码',\n      newPwdMsg: '请输入新密码',\n      cfPwdMsg: '请输入确认密码',\n      pwdRules: '长度在 6 到 20 个字符',\n      diffPwd: '两次输入密码不一致'\n    }\n  },\n  cropper: {\n    selectImage: '选择图片',\n    uploadSuccess: '上传成功',\n    modalTitle: '头像上传',\n    okText: '确认并上传',\n    btn_reset: '重置',\n    btn_rotate_left: '逆时针旋转',\n    btn_rotate_right: '顺时针旋转',\n    btn_scale_x: '水平翻转',\n    btn_scale_y: '垂直翻转',\n    btn_zoom_in: '放大',\n    btn_zoom_out: '缩小',\n    preview: '预览'\n  },\n  'OAuth 2.0': 'OAuth 2.0' // 避免菜单名是 OAuth 2.0 时，一直 warn 报错\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/main.ts",
    "content": "// 引入unocss css\nimport '@/plugins/unocss'\n\n// 导入全局的svg图标\nimport '@/plugins/svgIcon'\n\n// 初始化多语言\nimport { setupI18n } from '@/plugins/vueI18n'\n\n// 引入状态管理\nimport { setupStore } from '@/store'\n\n// 全局组件\nimport { setupGlobCom } from '@/components'\n\n// 引入 element-plus\nimport { setupElementPlus } from '@/plugins/elementPlus'\n\n// 引入 form-create\nimport { setupFormCreate } from '@/plugins/formCreate'\n\n// 引入全局样式\nimport '@/styles/index.scss'\n\n// 引入动画\nimport '@/plugins/animate.css'\n\n// 路由\nimport router, { setupRouter } from '@/router'\n\n// 权限\nimport { setupAuth } from '@/directives'\n\nimport { createApp } from 'vue'\n\nimport App from './App.vue'\n\nimport './permission'\n\nimport '@/plugins/tongji' // 百度统计\nimport Logger from '@/utils/Logger'\nimport VueUeditorWrap from 'vue-ueditor-wrap'\n\nimport VueDOMPurifyHTML from 'vue-dompurify-html' // 解决v-html 的安全隐患\n\n// 创建实例\nconst setupAll = async () => {\n  const app = createApp(App)\n  app.use(VueUeditorWrap)\n\n  await setupI18n(app)\n\n  setupStore(app)\n\n  setupGlobCom(app)\n\n  setupElementPlus(app)\n\n  setupFormCreate(app)\n\n  setupRouter(app)\n\n  setupAuth(app)\n\n  await router.isReady()\n\n  app.use(VueDOMPurifyHTML)\n\n  app.mount('#app')\n}\n\nsetupAll()\n\nLogger.prettyPrimary(`欢迎使用`, import.meta.env.VITE_APP_TITLE)\n"
  },
  {
    "path": "yshop-drink-vue3/src/permission.ts",
    "content": "import router from './router'\nimport type { RouteRecordRaw } from 'vue-router'\nimport { isRelogin } from '@/config/axios/service'\nimport { getAccessToken } from '@/utils/auth'\nimport { useTitle } from '@/hooks/web/useTitle'\nimport { useNProgress } from '@/hooks/web/useNProgress'\nimport { usePageLoading } from '@/hooks/web/usePageLoading'\nimport { useDictStoreWithOut } from '@/store/modules/dict'\nimport { useUserStoreWithOut } from '@/store/modules/user'\nimport { usePermissionStoreWithOut } from '@/store/modules/permission'\n\nconst { start, done } = useNProgress()\n\nconst { loadStart, loadDone } = usePageLoading()\n\nconst parseURL = (\n  url: string | null | undefined\n): { basePath: string; paramsObject: { [key: string]: string } } => {\n  // 如果输入为 null 或 undefined，返回空字符串和空对象\n  if (url == null) {\n    return { basePath: '', paramsObject: {} }\n  }\n\n  // 找到问号 (?) 的位置，它之前是基础路径，之后是查询参数\n  const questionMarkIndex = url.indexOf('?')\n  let basePath = url\n  const paramsObject: { [key: string]: string } = {}\n\n  // 如果找到了问号，说明有查询参数\n  if (questionMarkIndex !== -1) {\n    // 获取 basePath\n    basePath = url.substring(0, questionMarkIndex)\n\n    // 从 URL 中获取查询字符串部分\n    const queryString = url.substring(questionMarkIndex + 1)\n\n    // 使用 URLSearchParams 遍历参数\n    const searchParams = new URLSearchParams(queryString)\n    searchParams.forEach((value, key) => {\n      // 封装进 paramsObject 对象\n      paramsObject[key] = value\n    })\n  }\n\n  // 返回 basePath 和 paramsObject\n  return { basePath, paramsObject }\n}\n\n// 路由不重定向白名单\nconst whiteList = [\n  '/login',\n  '/social-login',\n  '/auth-redirect',\n  '/bind',\n  '/register',\n  '/oauthLogin/gitee'\n]\n\n// 路由加载前\nrouter.beforeEach(async (to, from, next) => {\n  start()\n  loadStart()\n  if (getAccessToken()) {\n    if (to.path === '/login') {\n      next({ path: '/' })\n    } else {\n      // 获取所有字典\n      const dictStore = useDictStoreWithOut()\n      const userStore = useUserStoreWithOut()\n      const permissionStore = usePermissionStoreWithOut()\n      if (!dictStore.getIsSetDict) {\n        await dictStore.setDictMap()\n      }\n      if (!userStore.getIsSetUser) {\n        isRelogin.show = true\n        await userStore.setUserInfoAction()\n        isRelogin.show = false\n        // 后端过滤菜单\n        await permissionStore.generateRoutes()\n        permissionStore.getAddRouters.forEach((route) => {\n          router.addRoute(route as unknown as RouteRecordRaw) // 动态添加可访问路由表\n        })\n        const redirectPath = from.query.redirect || to.path\n        // 修复跳转时不带参数的问题\n        const redirect = decodeURIComponent(redirectPath as string)\n        const { basePath, paramsObject: query } = parseURL(redirect)\n        const nextData = to.path === redirect ? { ...to, replace: true } : { path: redirect, query }\n        next(nextData)\n      } else {\n        next()\n      }\n    }\n  } else {\n    if (whiteList.indexOf(to.path) !== -1) {\n      next()\n    } else {\n      next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页\n    }\n  }\n})\n\nrouter.afterEach((to) => {\n  useTitle(to?.meta?.title as string)\n  done() // 结束Progress\n  loadDone()\n})\n"
  },
  {
    "path": "yshop-drink-vue3/src/plugins/animate.css/index.ts",
    "content": "import 'animate.css'\n"
  },
  {
    "path": "yshop-drink-vue3/src/plugins/echarts/index.ts",
    "content": "import * as echarts from 'echarts/core'\n\nimport {\n  BarChart,\n  FunnelChart,\n  GaugeChart,\n  LineChart,\n  MapChart,\n  PictorialBarChart,\n  PieChart,\n  RadarChart\n} from 'echarts/charts'\n\nimport {\n  AriaComponent,\n  GridComponent,\n  LegendComponent,\n  ParallelComponent,\n  PolarComponent,\n  TitleComponent,\n  ToolboxComponent,\n  TooltipComponent,\n  VisualMapComponent\n} from 'echarts/components'\n\nimport { CanvasRenderer } from 'echarts/renderers'\n\necharts.use([\n  LegendComponent,\n  TitleComponent,\n  TooltipComponent,\n  ToolboxComponent,\n  GridComponent,\n  PolarComponent,\n  AriaComponent,\n  ParallelComponent,\n  VisualMapComponent,\n  BarChart,\n  LineChart,\n  PieChart,\n  MapChart,\n  CanvasRenderer,\n  PictorialBarChart,\n  RadarChart,\n  GaugeChart,\n  FunnelChart\n])\n\nexport default echarts\n"
  },
  {
    "path": "yshop-drink-vue3/src/plugins/elementPlus/index.ts",
    "content": "import type { App } from 'vue'\n// 需要全局引入一些组件，如ElScrollbar，不然一些下拉项样式有问题\nimport { ElLoading, ElScrollbar, ElButton } from 'element-plus'\n\nconst plugins = [ElLoading]\n\nconst components = [ElScrollbar, ElButton]\n\nexport const setupElementPlus = (app: App<Element>) => {\n  plugins.forEach((plugin) => {\n    app.use(plugin)\n  })\n\n  components.forEach((component) => {\n    app.component(component.name, component)\n  })\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/plugins/formCreate/index.ts",
    "content": "import type { App } from 'vue'\n// 👇使用 form-create 需额外全局引入 element plus 组件\nimport {\n  ElAlert,\n  ElAside,\n  ElContainer,\n  ElDivider,\n  ElHeader,\n  ElMain,\n  ElPopconfirm,\n  ElTable,\n  ElTableColumn,\n  ElTabPane,\n  ElTabs,\n  ElTransfer\n} from 'element-plus'\nimport FcDesigner from '@form-create/designer'\nimport formCreate from '@form-create/element-ui'\nimport install from '@form-create/element-ui/auto-import'\n\n//======================= 自定义组件 =======================\nimport { UploadFile, UploadImg, UploadImgs } from '@/components/UploadFile'\nimport { useApiSelect } from '@/components/FormCreate'\nimport { Editor } from '@/components/Editor'\nimport DictSelect from '@/components/FormCreate/src/components/DictSelect.vue'\n\nconst UserSelect = useApiSelect({\n  name: 'UserSelect',\n  labelField: 'nickname',\n  valueField: 'id',\n  url: '/system/user/simple-list'\n})\nconst DeptSelect = useApiSelect({\n  name: 'DeptSelect',\n  labelField: 'name',\n  valueField: 'id',\n  url: '/system/dept/simple-list'\n})\nconst ApiSelect = useApiSelect({\n  name: 'ApiSelect'\n})\n\nconst components = [\n  ElAside,\n  ElPopconfirm,\n  ElHeader,\n  ElMain,\n  ElContainer,\n  ElDivider,\n  ElTransfer,\n  ElAlert,\n  ElTabs,\n  ElTable,\n  ElTableColumn,\n  ElTabPane,\n  UploadImg,\n  UploadImgs,\n  UploadFile,\n  DictSelect,\n  UserSelect,\n  DeptSelect,\n  ApiSelect,\n  Editor\n]\n\n// 参考 http://www.form-create.com/v3/element-ui/auto-import.html 文档\nexport const setupFormCreate = (app: App<Element>) => {\n  components.forEach((component) => {\n    app.component(component.name, component)\n  })\n  formCreate.use(install)\n  app.use(formCreate)\n  app.use(FcDesigner)\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/plugins/svgIcon/index.ts",
    "content": "import 'virtual:svg-icons-register'\n\nimport '@purge-icons/generated'\n"
  },
  {
    "path": "yshop-drink-vue3/src/plugins/tongji/index.ts",
    "content": "import router from '@/router'\n\n// 用于 router push\nwindow._hmt = window._hmt || []\n// HM_ID\nconst HM_ID = import.meta.env.VITE_APP_BAIDU_CODE\n;(function () {\n  // 有值的时候，才开启\n  if (!HM_ID) {\n    return\n  }\n  const hm = document.createElement('script')\n  hm.src = 'https://hm.baidu.com/hm.js?' + HM_ID\n  const s = document.getElementsByTagName('script')[0]\n  s.parentNode.insertBefore(hm, s)\n})()\n\nrouter.afterEach(function (to) {\n  if (!HM_ID) {\n    return\n  }\n  _hmt.push(['_trackPageview', to.fullPath])\n})\n"
  },
  {
    "path": "yshop-drink-vue3/src/plugins/unocss/index.ts",
    "content": "import 'virtual:uno.css'\n"
  },
  {
    "path": "yshop-drink-vue3/src/plugins/vueI18n/helper.ts",
    "content": "export const setHtmlPageLang = (locale: LocaleType) => {\n  document.querySelector('html')?.setAttribute('lang', locale)\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/plugins/vueI18n/index.ts",
    "content": "import type { App } from 'vue'\nimport { createI18n } from 'vue-i18n'\nimport { useLocaleStoreWithOut } from '@/store/modules/locale'\nimport type { I18n, I18nOptions } from 'vue-i18n'\nimport { setHtmlPageLang } from './helper'\n\nexport let i18n: ReturnType<typeof createI18n>\n\nconst createI18nOptions = async (): Promise<I18nOptions> => {\n  const localeStore = useLocaleStoreWithOut()\n  const locale = localeStore.getCurrentLocale\n  const localeMap = localeStore.getLocaleMap\n  const defaultLocal = await import(`../../locales/${locale.lang}.ts`)\n  const message = defaultLocal.default ?? {}\n\n  setHtmlPageLang(locale.lang)\n\n  localeStore.setCurrentLocale({\n    lang: locale.lang\n    // elLocale: elLocal\n  })\n\n  return {\n    legacy: false,\n    locale: locale.lang,\n    fallbackLocale: locale.lang,\n    messages: {\n      [locale.lang]: message\n    },\n    availableLocales: localeMap.map((v) => v.lang),\n    sync: true,\n    silentTranslationWarn: true,\n    missingWarn: false,\n    silentFallbackWarn: true\n  }\n}\n\nexport const setupI18n = async (app: App<Element>) => {\n  const options = await createI18nOptions()\n  i18n = createI18n(options) as I18n\n  app.use(i18n)\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/router/index.ts",
    "content": "import type { App } from 'vue'\nimport type { RouteRecordRaw } from 'vue-router'\nimport { createRouter, createWebHistory } from 'vue-router'\nimport remainingRouter from './modules/remaining'\n\n// 创建路由实例\nconst router = createRouter({\n  history: createWebHistory(), // createWebHashHistory URL带#，createWebHistory URL不带#\n  strict: true,\n  routes: remainingRouter as RouteRecordRaw[],\n  scrollBehavior: () => ({ left: 0, top: 0 })\n})\n\nexport const resetRouter = (): void => {\n  const resetWhiteNameList = ['Redirect', 'Login', 'NoFind', 'Root']\n  router.getRoutes().forEach((route) => {\n    const { name } = route\n    if (name && !resetWhiteNameList.includes(name as string)) {\n      router.hasRoute(name) && router.removeRoute(name)\n    }\n  })\n}\n\nexport const setupRouter = (app: App<Element>) => {\n  app.use(router)\n}\n\nexport default router\n"
  },
  {
    "path": "yshop-drink-vue3/src/router/modules/remaining.ts",
    "content": "import { Layout } from '@/utils/routerHelper'\n\nconst { t } = useI18n()\n/**\n * redirect: noredirect        当设置 noredirect 的时候该路由在面包屑导航中不可被点击\n * name:'router-name'          设定路由的名字，一定要填写不然使用<keep-alive>时会出现各种问题\n * meta : {\n hidden: true              当设置 true 的时候该路由不会再侧边栏出现 如404，login等页面(默认 false)\n\n alwaysShow: true          当你一个路由下面的 children 声明的路由大于1个时，自动会变成嵌套的模式，\n 只有一个时，会将那个子路由当做根路由显示在侧边栏，\n 若你想不管路由下面的 children 声明的个数都显示你的根路由，\n 你可以设置 alwaysShow: true，这样它就会忽略之前定义的规则，\n 一直显示根路由(默认 false)\n\n title: 'title'            设置该路由在侧边栏和面包屑中展示的名字\n\n icon: 'svg-name'          设置该路由的图标\n\n noCache: true             如果设置为true，则不会被 <keep-alive> 缓存(默认 false)\n\n breadcrumb: false         如果设置为false，则不会在breadcrumb面包屑中显示(默认 true)\n\n affix: true               如果设置为true，则会一直固定在tag项中(默认 false)\n\n noTagsView: true          如果设置为true，则不会出现在tag中(默认 false)\n\n activeMenu: '/dashboard'  显示高亮的路由路径\n\n followAuth: '/dashboard'  跟随哪个路由进行权限过滤\n\n canTo: true               设置为true即使hidden为true，也依然可以进行路由跳转(默认 false)\n }\n **/\nconst remainingRouter: AppRouteRecordRaw[] = [\n  {\n    path: '/redirect',\n    component: Layout,\n    name: 'Redirect',\n    children: [\n      {\n        path: '/redirect/:path(.*)',\n        name: 'Redirect1',\n        component: () => import('@/views/Redirect/Redirect.vue'),\n        meta: {}\n      }\n    ],\n    meta: {\n      hidden: true,\n      noTagsView: true\n    }\n  },\n  {\n    path: '/',\n    component: Layout,\n    redirect: '/index',\n    name: 'Home',\n    meta: {},\n    children: [\n      {\n        path: 'index',\n        component: () => import('@/views/Home/Index.vue'),\n        name: 'Index',\n        meta: {\n          title: t('router.home'),\n          icon: 'ep:home-filled',\n          noCache: false,\n          affix: true\n        }\n      }\n    ]\n  },\n  {\n    path: '/user',\n    component: Layout,\n    name: 'UserInfo',\n    meta: {\n      hidden: true\n    },\n    children: [\n      {\n        path: 'profile',\n        component: () => import('@/views/Profile/Index.vue'),\n        name: 'Profile',\n        meta: {\n          canTo: true,\n          hidden: true,\n          noTagsView: false,\n          icon: 'ep:user',\n          title: t('common.profile')\n        }\n      },\n      {\n        path: 'notify-message',\n        component: () => import('@/views/system/notify/my/index.vue'),\n        name: 'MyNotifyMessage',\n        meta: {\n          canTo: true,\n          hidden: true,\n          noTagsView: false,\n          icon: 'ep:message',\n          title: '我的站内信'\n        }\n      }\n    ]\n  },\n  {\n    path: '/dict',\n    component: Layout,\n    name: 'dict',\n    meta: {\n      hidden: true\n    },\n    children: [\n      {\n        path: 'type/data/:dictType',\n        component: () => import('@/views/system/dict/data/index.vue'),\n        name: 'SystemDictData',\n        meta: {\n          title: '字典数据',\n          noCache: true,\n          hidden: true,\n          canTo: true,\n          icon: '',\n          activeMenu: '/system/dict'\n        }\n      }\n    ]\n  },\n\n  {\n    path: '/codegen',\n    component: Layout,\n    name: 'CodegenEdit',\n    meta: {\n      hidden: true\n    },\n    children: [\n      {\n        path: 'edit',\n        component: () => import('@/views/infra/codegen/EditTable.vue'),\n        name: 'InfraCodegenEditTable',\n        meta: {\n          noCache: true,\n          hidden: true,\n          canTo: true,\n          icon: 'ep:edit',\n          title: '修改生成配置',\n          activeMenu: 'infra/codegen/index'\n        }\n      }\n    ]\n  },\n  {\n    path: '/job',\n    component: Layout,\n    name: 'JobL',\n    meta: {\n      hidden: true\n    },\n    children: [\n      {\n        path: 'job-log',\n        component: () => import('@/views/infra/job/logger/index.vue'),\n        name: 'InfraJobLog',\n        meta: {\n          noCache: true,\n          hidden: true,\n          canTo: true,\n          icon: 'ep:edit',\n          title: '调度日志',\n          activeMenu: 'infra/job/index'\n        }\n      }\n    ]\n  },\n  {\n    path: '/login',\n    component: () => import('@/views/Login/Login.vue'),\n    name: 'Login',\n    meta: {\n      hidden: true,\n      title: t('router.login'),\n      noTagsView: true\n    }\n  },\n  {\n    path: '/sso',\n    component: () => import('@/views/Login/Login.vue'),\n    name: 'SSOLogin',\n    meta: {\n      hidden: true,\n      title: t('router.login'),\n      noTagsView: true\n    }\n  },\n  {\n    path: '/social-login',\n    component: () => import('@/views/Login/SocialLogin.vue'),\n    name: 'SocialLogin',\n    meta: {\n      hidden: true,\n      title: t('router.socialLogin'),\n      noTagsView: true\n    }\n  },\n  {\n    path: '/403',\n    component: () => import('@/views/Error/403.vue'),\n    name: 'NoAccess',\n    meta: {\n      hidden: true,\n      title: '403',\n      noTagsView: true\n    }\n  },\n  {\n    path: '/404',\n    component: () => import('@/views/Error/404.vue'),\n    name: 'NoFound',\n    meta: {\n      hidden: true,\n      title: '404',\n      noTagsView: true\n    }\n  },\n  {\n    path: '/500',\n    component: () => import('@/views/Error/500.vue'),\n    name: 'Error',\n    meta: {\n      hidden: true,\n      title: '500',\n      noTagsView: true\n    }\n  },\n  {\n    path: '/yshop/materia/index',\n    component: () => import('@/components/Materials/src/editorMaterials.vue'),\n    name: 'EditorMaterials',\n    meta: {\n      noCache: true,\n      hidden: true,\n      // canTo: true,\n      title: '上传图片',\n    },\n\n  }\n\n]\n\nexport default remainingRouter\n"
  },
  {
    "path": "yshop-drink-vue3/src/store/index.ts",
    "content": "import type { App } from 'vue'\nimport { createPinia } from 'pinia'\nimport piniaPluginPersistedstate from 'pinia-plugin-persistedstate'\n\nconst store = createPinia()\nstore.use(piniaPluginPersistedstate)\n\nexport const setupStore = (app: App<Element>) => {\n  app.use(store)\n}\n\nexport { store }\n"
  },
  {
    "path": "yshop-drink-vue3/src/store/modules/app.ts",
    "content": "import { defineStore } from 'pinia'\nimport { store } from '../index'\nimport { setCssVar, humpToUnderline } from '@/utils'\nimport { ElMessage } from 'element-plus'\nimport { CACHE_KEY, useCache } from '@/hooks/web/useCache'\nimport { ElementPlusSize } from '@/types/elementPlus'\nimport { LayoutType } from '@/types/layout'\nimport { ThemeTypes } from '@/types/theme'\n\nconst { wsCache } = useCache()\n\ninterface AppState {\n  breadcrumb: boolean\n  breadcrumbIcon: boolean\n  collapse: boolean\n  uniqueOpened: boolean\n  hamburger: boolean\n  screenfull: boolean\n  search: boolean\n  size: boolean\n  locale: boolean\n  message: boolean\n  tagsView: boolean\n  tagsViewIcon: boolean\n  logo: boolean\n  fixedHeader: boolean\n  greyMode: boolean\n  pageLoading: boolean\n  layout: LayoutType\n  title: string\n  userInfo: string\n  isDark: boolean\n  currentSize: ElementPlusSize\n  sizeMap: ElementPlusSize[]\n  mobile: boolean\n  footer: boolean\n  theme: ThemeTypes\n  fixedMenu: boolean\n}\n\nexport const useAppStore = defineStore('app', {\n  state: (): AppState => {\n    return {\n      userInfo: 'userInfo', // 登录信息存储字段-建议每个项目换一个字段，避免与其他项目冲突\n      sizeMap: ['default', 'large', 'small'],\n      mobile: false, // 是否是移动端\n      title: import.meta.env.VITE_APP_TITLE, // 标题\n      pageLoading: false, // 路由跳转loading\n\n      breadcrumb: true, // 面包屑\n      breadcrumbIcon: true, // 面包屑图标\n      collapse: false, // 折叠菜单\n      uniqueOpened: true, // 是否只保持一个子菜单的展开\n      hamburger: true, // 折叠图标\n      screenfull: true, // 全屏图标\n      search: true, // 搜索图标\n      size: true, // 尺寸图标\n      locale: true, // 多语言图标\n      message: true, // 消息图标\n      tagsView: true, // 标签页\n      tagsViewIcon: true, // 是否显示标签图标\n      logo: true, // logo\n      fixedHeader: true, // 固定toolheader\n      footer: true, // 显示页脚\n      greyMode: false, // 是否开始灰色模式，用于特殊悼念日\n      fixedMenu: wsCache.get('fixedMenu') || false, // 是否固定菜单\n\n      layout: wsCache.get(CACHE_KEY.LAYOUT) || 'classic', // layout布局\n      isDark: wsCache.get(CACHE_KEY.IS_DARK) || false, // 是否是暗黑模式\n      currentSize: wsCache.get('default') || 'default', // 组件尺寸\n      theme: wsCache.get(CACHE_KEY.THEME) || {\n        // 主题色\n        elColorPrimary: '#409eff',\n        // 左侧菜单边框颜色\n        leftMenuBorderColor: 'inherit',\n        // 左侧菜单背景颜色\n        leftMenuBgColor: '#001529',\n        // 左侧菜单浅色背景颜色\n        leftMenuBgLightColor: '#0f2438',\n        // 左侧菜单选中背景颜色\n        leftMenuBgActiveColor: 'var(--el-color-primary)',\n        // 左侧菜单收起选中背景颜色\n        leftMenuCollapseBgActiveColor: 'var(--el-color-primary)',\n        // 左侧菜单字体颜色\n        leftMenuTextColor: '#bfcbd9',\n        // 左侧菜单选中字体颜色\n        leftMenuTextActiveColor: '#fff',\n        // logo字体颜色\n        logoTitleTextColor: '#fff',\n        // logo边框颜色\n        logoBorderColor: 'inherit',\n        // 头部背景颜色\n        topHeaderBgColor: '#fff',\n        // 头部字体颜色\n        topHeaderTextColor: 'inherit',\n        // 头部悬停颜色\n        topHeaderHoverColor: '#f6f6f6',\n        // 头部边框颜色\n        topToolBorderColor: '#eee'\n      }\n    }\n  },\n  getters: {\n    getBreadcrumb(): boolean {\n      return this.breadcrumb\n    },\n    getBreadcrumbIcon(): boolean {\n      return this.breadcrumbIcon\n    },\n    getCollapse(): boolean {\n      return this.collapse\n    },\n    getUniqueOpened(): boolean {\n      return this.uniqueOpened\n    },\n    getHamburger(): boolean {\n      return this.hamburger\n    },\n    getScreenfull(): boolean {\n      return this.screenfull\n    },\n    getSize(): boolean {\n      return this.size\n    },\n    getLocale(): boolean {\n      return this.locale\n    },\n    getMessage(): boolean {\n      return this.message\n    },\n    getTagsView(): boolean {\n      return this.tagsView\n    },\n    getTagsViewIcon(): boolean {\n      return this.tagsViewIcon\n    },\n    getLogo(): boolean {\n      return this.logo\n    },\n    getFixedHeader(): boolean {\n      return this.fixedHeader\n    },\n    getGreyMode(): boolean {\n      return this.greyMode\n    },\n    getFixedMenu(): boolean {\n      return this.fixedMenu\n    },\n    getPageLoading(): boolean {\n      return this.pageLoading\n    },\n    getLayout(): LayoutType {\n      return this.layout\n    },\n    getTitle(): string {\n      return this.title\n    },\n    getUserInfo(): string {\n      return this.userInfo\n    },\n    getIsDark(): boolean {\n      return this.isDark\n    },\n    getCurrentSize(): ElementPlusSize {\n      return this.currentSize\n    },\n    getSizeMap(): ElementPlusSize[] {\n      return this.sizeMap\n    },\n    getMobile(): boolean {\n      return this.mobile\n    },\n    getTheme(): ThemeTypes {\n      return this.theme\n    },\n    getFooter(): boolean {\n      return this.footer\n    }\n  },\n  actions: {\n    setBreadcrumb(breadcrumb: boolean) {\n      this.breadcrumb = breadcrumb\n    },\n    setBreadcrumbIcon(breadcrumbIcon: boolean) {\n      this.breadcrumbIcon = breadcrumbIcon\n    },\n    setCollapse(collapse: boolean) {\n      this.collapse = collapse\n    },\n    setUniqueOpened(uniqueOpened: boolean) {\n      this.uniqueOpened = uniqueOpened\n    },\n    setHamburger(hamburger: boolean) {\n      this.hamburger = hamburger\n    },\n    setScreenfull(screenfull: boolean) {\n      this.screenfull = screenfull\n    },\n    setSize(size: boolean) {\n      this.size = size\n    },\n    setLocale(locale: boolean) {\n      this.locale = locale\n    },\n    setMessage(message: boolean) {\n      this.message = message\n    },\n    setTagsView(tagsView: boolean) {\n      this.tagsView = tagsView\n    },\n    setTagsViewIcon(tagsViewIcon: boolean) {\n      this.tagsViewIcon = tagsViewIcon\n    },\n    setLogo(logo: boolean) {\n      this.logo = logo\n    },\n    setFixedHeader(fixedHeader: boolean) {\n      this.fixedHeader = fixedHeader\n    },\n    setGreyMode(greyMode: boolean) {\n      this.greyMode = greyMode\n    },\n    setFixedMenu(fixedMenu: boolean) {\n      wsCache.set('fixedMenu', fixedMenu)\n      this.fixedMenu = fixedMenu\n    },\n    setPageLoading(pageLoading: boolean) {\n      this.pageLoading = pageLoading\n    },\n    setLayout(layout: LayoutType) {\n      if (this.mobile && layout !== 'classic') {\n        ElMessage.warning('移动端模式下不支持切换其他布局')\n        return\n      }\n      this.layout = layout\n      wsCache.set(CACHE_KEY.LAYOUT, this.layout)\n    },\n    setTitle(title: string) {\n      this.title = title\n    },\n    setIsDark(isDark: boolean) {\n      this.isDark = isDark\n      if (this.isDark) {\n        document.documentElement.classList.add('dark')\n        document.documentElement.classList.remove('light')\n      } else {\n        document.documentElement.classList.add('light')\n        document.documentElement.classList.remove('dark')\n      }\n      wsCache.set(CACHE_KEY.IS_DARK, this.isDark)\n    },\n    setCurrentSize(currentSize: ElementPlusSize) {\n      this.currentSize = currentSize\n      wsCache.set('currentSize', this.currentSize)\n    },\n    setMobile(mobile: boolean) {\n      this.mobile = mobile\n    },\n    setTheme(theme: ThemeTypes) {\n      this.theme = Object.assign(this.theme, theme)\n      wsCache.set(CACHE_KEY.THEME, this.theme)\n    },\n    setCssVarTheme() {\n      for (const key in this.theme) {\n        setCssVar(`--${humpToUnderline(key)}`, this.theme[key])\n      }\n    },\n    setFooter(footer: boolean) {\n      this.footer = footer\n    }\n  },\n  persist: false\n})\n\nexport const useAppStoreWithOut = () => {\n  return useAppStore(store)\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/store/modules/dict.ts",
    "content": "import { defineStore } from 'pinia'\nimport { store } from '../index'\n// @ts-ignore\nimport { DictDataVO } from '@/api/system/dict/types'\nimport { CACHE_KEY, useCache } from '@/hooks/web/useCache'\nconst { wsCache } = useCache('sessionStorage')\nimport { getSimpleDictDataList } from '@/api/system/dict/dict.data'\n\nexport interface DictValueType {\n  value: any\n  label: string\n  clorType?: string\n  cssClass?: string\n}\nexport interface DictTypeType {\n  dictType: string\n  dictValue: DictValueType[]\n}\nexport interface DictState {\n  dictMap: Map<string, any>\n  isSetDict: boolean\n}\n\nexport const useDictStore = defineStore('dict', {\n  state: (): DictState => ({\n    dictMap: new Map<string, any>(),\n    isSetDict: false\n  }),\n  getters: {\n    getDictMap(): Recordable {\n      const dictMap = wsCache.get(CACHE_KEY.DICT_CACHE)\n      if (dictMap) {\n        this.dictMap = dictMap\n      }\n      return this.dictMap\n    },\n    getIsSetDict(): boolean {\n      return this.isSetDict\n    }\n  },\n  actions: {\n    async setDictMap() {\n      const dictMap = wsCache.get(CACHE_KEY.DICT_CACHE)\n      if (dictMap) {\n        this.dictMap = dictMap\n        this.isSetDict = true\n      } else {\n        const res = await getSimpleDictDataList()\n        // 设置数据\n        const dictDataMap = new Map<string, any>()\n        res.forEach((dictData: DictDataVO) => {\n          // 获得 dictType 层级\n          const enumValueObj = dictDataMap[dictData.dictType]\n          if (!enumValueObj) {\n            dictDataMap[dictData.dictType] = []\n          }\n          // 处理 dictValue 层级\n          dictDataMap[dictData.dictType].push({\n            value: dictData.value,\n            label: dictData.label,\n            colorType: dictData.colorType,\n            cssClass: dictData.cssClass\n          })\n        })\n        this.dictMap = dictDataMap\n        this.isSetDict = true\n        wsCache.set(CACHE_KEY.DICT_CACHE, dictDataMap, { exp: 60 }) // 60 秒 过期\n      }\n    },\n    getDictByType(type: string) {\n      if (!this.isSetDict) {\n        this.setDictMap()\n      }\n      return this.dictMap[type]\n    },\n    async resetDict() {\n      wsCache.delete(CACHE_KEY.DICT_CACHE)\n      const res = await getSimpleDictDataList()\n      // 设置数据\n      const dictDataMap = new Map<string, any>()\n      res.forEach((dictData: DictDataVO) => {\n        // 获得 dictType 层级\n        const enumValueObj = dictDataMap[dictData.dictType]\n        if (!enumValueObj) {\n          dictDataMap[dictData.dictType] = []\n        }\n        // 处理 dictValue 层级\n        dictDataMap[dictData.dictType].push({\n          value: dictData.value,\n          label: dictData.label,\n          colorType: dictData.colorType,\n          cssClass: dictData.cssClass\n        })\n      })\n      this.dictMap = dictDataMap\n      this.isSetDict = true\n      wsCache.set(CACHE_KEY.DICT_CACHE, dictDataMap, { exp: 60 }) // 60 秒 过期\n    }\n  }\n})\n\nexport const useDictStoreWithOut = () => {\n  return useDictStore(store)\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/store/modules/locale.ts",
    "content": "import { defineStore } from 'pinia'\nimport { store } from '../index'\nimport zhCn from 'element-plus/es/locale/lang/zh-cn'\nimport en from 'element-plus/es/locale/lang/en'\nimport { CACHE_KEY, useCache } from '@/hooks/web/useCache'\nimport { LocaleDropdownType } from '@/types/localeDropdown'\n\nconst { wsCache } = useCache()\n\nconst elLocaleMap = {\n  'zh-CN': zhCn,\n  en: en\n}\ninterface LocaleState {\n  currentLocale: LocaleDropdownType\n  localeMap: LocaleDropdownType[]\n}\n\nexport const useLocaleStore = defineStore('locales', {\n  state: (): LocaleState => {\n    return {\n      currentLocale: {\n        lang: wsCache.get(CACHE_KEY.LANG) || 'zh-CN',\n        elLocale: elLocaleMap[wsCache.get(CACHE_KEY.LANG) || 'zh-CN']\n      },\n      // 多语言\n      localeMap: [\n        {\n          lang: 'zh-CN',\n          name: '简体中文'\n        },\n        {\n          lang: 'en',\n          name: 'English'\n        }\n      ]\n    }\n  },\n  getters: {\n    getCurrentLocale(): LocaleDropdownType {\n      return this.currentLocale\n    },\n    getLocaleMap(): LocaleDropdownType[] {\n      return this.localeMap\n    }\n  },\n  actions: {\n    setCurrentLocale(localeMap: LocaleDropdownType) {\n      // this.locale = Object.assign(this.locale, localeMap)\n      this.currentLocale.lang = localeMap?.lang\n      this.currentLocale.elLocale = elLocaleMap[localeMap?.lang]\n      wsCache.set(CACHE_KEY.LANG, localeMap?.lang)\n    }\n  }\n})\n\nexport const useLocaleStoreWithOut = () => {\n  return useLocaleStore(store)\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/store/modules/lock.ts",
    "content": "import { defineStore } from 'pinia'\nimport { store } from '@/store'\n\ninterface lockInfo {\n  isLock?: boolean\n  password?: string\n}\n\ninterface LockState {\n  lockInfo: lockInfo\n}\n\nexport const useLockStore = defineStore('lock', {\n  state: (): LockState => {\n    return {\n      lockInfo: {\n        // isLock: false, // 是否锁定屏幕\n        // password: '' // 锁屏密码\n      }\n    }\n  },\n  getters: {\n    getLockInfo(): lockInfo {\n      return this.lockInfo\n    }\n  },\n  actions: {\n    setLockInfo(lockInfo: lockInfo) {\n      this.lockInfo = lockInfo\n    },\n    resetLockInfo() {\n      this.lockInfo = {}\n    },\n    unLock(password: string) {\n      if (this.lockInfo?.password === password) {\n        this.resetLockInfo()\n        return true\n      } else {\n        return false\n      }\n    }\n  },\n  persist: true\n})\n\nexport const useLockStoreWithOut = () => {\n  return useLockStore(store)\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/store/modules/permission.ts",
    "content": "import { defineStore } from 'pinia'\nimport { store } from '@/store'\nimport { cloneDeep } from 'lodash-es'\nimport remainingRouter from '@/router/modules/remaining'\nimport { flatMultiLevelRoutes, generateRoute } from '@/utils/routerHelper'\nimport { CACHE_KEY, useCache } from '@/hooks/web/useCache'\n\nconst { wsCache } = useCache()\n\nexport interface PermissionState {\n  routers: AppRouteRecordRaw[]\n  addRouters: AppRouteRecordRaw[]\n  menuTabRouters: AppRouteRecordRaw[]\n}\n\nexport const usePermissionStore = defineStore('permission', {\n  state: (): PermissionState => ({\n    routers: [],\n    addRouters: [],\n    menuTabRouters: []\n  }),\n  getters: {\n    getRouters(): AppRouteRecordRaw[] {\n      return this.routers\n    },\n    getAddRouters(): AppRouteRecordRaw[] {\n      return flatMultiLevelRoutes(cloneDeep(this.addRouters))\n    },\n    getMenuTabRouters(): AppRouteRecordRaw[] {\n      return this.menuTabRouters\n    }\n  },\n  actions: {\n    async generateRoutes(): Promise<unknown> {\n      return new Promise<void>(async (resolve) => {\n        // 获得菜单列表，它在登录的时候，setUserInfoAction 方法中已经进行获取\n        let res: AppCustomRouteRecordRaw[] = []\n        if (wsCache.get(CACHE_KEY.ROLE_ROUTERS)) {\n          res = wsCache.get(CACHE_KEY.ROLE_ROUTERS) as AppCustomRouteRecordRaw[]\n        }\n        const routerMap: AppRouteRecordRaw[] = generateRoute(res)\n        // 动态路由，404一定要放到最后面\n        this.addRouters = routerMap.concat([\n          {\n            path: '/:path(.*)*',\n            redirect: '/404',\n            name: '404Page',\n            meta: {\n              hidden: true,\n              breadcrumb: false\n            }\n          }\n        ])\n        // 渲染菜单的所有路由\n        this.routers = cloneDeep(remainingRouter).concat(routerMap)\n        resolve()\n      })\n    },\n    setMenuTabRouters(routers: AppRouteRecordRaw[]): void {\n      this.menuTabRouters = routers\n    }\n  },\n  persist: false\n})\n\nexport const usePermissionStoreWithOut = () => {\n  return usePermissionStore(store)\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/store/modules/simpleWorkflow.ts",
    "content": "import { store } from '../index'\nimport { defineStore } from 'pinia'\n\nexport const useWorkFlowStore = defineStore('simpleWorkflow', {\n  state: () => ({\n    tableId: '',\n    isTried: false,\n    promoterDrawer: false,\n    flowPermission1: {},\n    approverDrawer: false,\n    approverConfig1: {},\n    copyerDrawer: false,\n    copyerConfig1: {},\n    conditionDrawer: false,\n    conditionsConfig1: {\n      conditionNodes: []\n    }\n  }),\n  actions: {\n    setTableId(payload) {\n      this.tableId = payload\n    },\n    setIsTried(payload) {\n      this.isTried = payload\n    },\n    setPromoter(payload) {\n      this.promoterDrawer = payload\n    },\n    setFlowPermission(payload) {\n      this.flowPermission1 = payload\n    },\n    setApprover(payload) {\n      this.approverDrawer = payload\n    },\n    setApproverConfig(payload) {\n      this.approverConfig1 = payload\n    },\n    setCopyer(payload) {\n      this.copyerDrawer = payload\n    },\n    setCopyerConfig(payload) {\n      this.copyerConfig1 = payload\n    },\n    setCondition(payload) {\n      this.conditionDrawer = payload\n    },\n    setConditionsConfig(payload) {\n      this.conditionsConfig1 = payload\n    }\n  }\n})\n\nexport const useWorkFlowStoreWithOut = () => {\n  return useWorkFlowStore(store)\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/store/modules/tagsView.ts",
    "content": "import router from '@/router'\nimport type { RouteLocationNormalizedLoaded } from 'vue-router'\nimport { getRawRoute } from '@/utils/routerHelper'\nimport { defineStore } from 'pinia'\nimport { store } from '../index'\nimport { findIndex } from '@/utils'\n\nexport interface TagsViewState {\n  visitedViews: RouteLocationNormalizedLoaded[]\n  cachedViews: Set<string>\n}\n\nexport const useTagsViewStore = defineStore('tagsView', {\n  state: (): TagsViewState => ({\n    visitedViews: [],\n    cachedViews: new Set()\n  }),\n  getters: {\n    getVisitedViews(): RouteLocationNormalizedLoaded[] {\n      return this.visitedViews\n    },\n    getCachedViews(): string[] {\n      return Array.from(this.cachedViews)\n    }\n  },\n  actions: {\n    // 新增缓存和tag\n    addView(view: RouteLocationNormalizedLoaded): void {\n      this.addVisitedView(view)\n      this.addCachedView()\n    },\n    // 新增tag\n    addVisitedView(view: RouteLocationNormalizedLoaded) {\n      if (this.visitedViews.some((v) => v.path === view.path)) return\n      if (view.meta?.noTagsView) return\n      this.visitedViews.push(\n        Object.assign({}, view, {\n          title: view.meta?.title || 'no-name'\n        })\n      )\n    },\n    // 新增缓存\n    addCachedView() {\n      const cacheMap: Set<string> = new Set()\n      for (const v of this.visitedViews) {\n        const item = getRawRoute(v)\n        const needCache = !item.meta?.noCache\n        if (!needCache) {\n          continue\n        }\n        const name = item.name as string\n        cacheMap.add(name)\n      }\n      if (Array.from(this.cachedViews).sort().toString() === Array.from(cacheMap).sort().toString())\n        return\n      this.cachedViews = cacheMap\n    },\n    // 删除某个\n    delView(view: RouteLocationNormalizedLoaded) {\n      this.delVisitedView(view)\n      this.delCachedView()\n    },\n    // 删除tag\n    delVisitedView(view: RouteLocationNormalizedLoaded) {\n      for (const [i, v] of this.visitedViews.entries()) {\n        if (v.path === view.path) {\n          this.visitedViews.splice(i, 1)\n          break\n        }\n      }\n    },\n    // 删除缓存\n    delCachedView() {\n      const route = router.currentRoute.value\n      const index = findIndex<string>(this.getCachedViews, (v) => v === route.name)\n      if (index > -1) {\n        this.cachedViews.delete(this.getCachedViews[index])\n      }\n    },\n    // 删除所有缓存和tag\n    delAllViews() {\n      this.delAllVisitedViews()\n      this.delCachedView()\n    },\n    // 删除所有tag\n    delAllVisitedViews() {\n      // const affixTags = this.visitedViews.filter((tag) => tag.meta.affix)\n      this.visitedViews = []\n    },\n    // 删除其他\n    delOthersViews(view: RouteLocationNormalizedLoaded) {\n      this.delOthersVisitedViews(view)\n      this.addCachedView()\n    },\n    // 删除其他tag\n    delOthersVisitedViews(view: RouteLocationNormalizedLoaded) {\n      this.visitedViews = this.visitedViews.filter((v) => {\n        return v?.meta?.affix || v.path === view.path\n      })\n    },\n    // 删除左侧\n    delLeftViews(view: RouteLocationNormalizedLoaded) {\n      const index = findIndex<RouteLocationNormalizedLoaded>(\n        this.visitedViews,\n        (v) => v.path === view.path\n      )\n      if (index > -1) {\n        this.visitedViews = this.visitedViews.filter((v, i) => {\n          return v?.meta?.affix || v.path === view.path || i > index\n        })\n        this.addCachedView()\n      }\n    },\n    // 删除右侧\n    delRightViews(view: RouteLocationNormalizedLoaded) {\n      const index = findIndex<RouteLocationNormalizedLoaded>(\n        this.visitedViews,\n        (v) => v.path === view.path\n      )\n      if (index > -1) {\n        this.visitedViews = this.visitedViews.filter((v, i) => {\n          return v?.meta?.affix || v.path === view.path || i < index\n        })\n        this.addCachedView()\n      }\n    },\n    updateVisitedView(view: RouteLocationNormalizedLoaded) {\n      for (let v of this.visitedViews) {\n        if (v.path === view.path) {\n          v = Object.assign(v, view)\n          break\n        }\n      }\n    }\n  },\n  persist: false\n})\n\nexport const useTagsViewStoreWithOut = () => {\n  return useTagsViewStore(store)\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/store/modules/user.ts",
    "content": "import { store } from '@/store'\nimport { defineStore } from 'pinia'\nimport { getAccessToken, removeToken } from '@/utils/auth'\nimport { CACHE_KEY, useCache, deleteUserCache } from '@/hooks/web/useCache'\nimport { getInfo, loginOut } from '@/api/login'\n\nconst { wsCache } = useCache()\n\ninterface UserVO {\n  id: number\n  avatar: string\n  nickname: string\n  deptId: number\n}\n\ninterface UserInfoVO {\n  // USER 缓存\n  permissions: string[]\n  roles: string[]\n  isSetUser: boolean\n  user: UserVO\n}\n\nexport const useUserStore = defineStore('admin-user', {\n  state: (): UserInfoVO => ({\n    permissions: [],\n    roles: [],\n    isSetUser: false,\n    user: {\n      id: 0,\n      avatar: '',\n      nickname: '',\n      deptId: 0\n    }\n  }),\n  getters: {\n    getPermissions(): string[] {\n      return this.permissions\n    },\n    getRoles(): string[] {\n      return this.roles\n    },\n    getIsSetUser(): boolean {\n      return this.isSetUser\n    },\n    getUser(): UserVO {\n      return this.user\n    }\n  },\n  actions: {\n    async setUserInfoAction() {\n      if (!getAccessToken()) {\n        this.resetState()\n        return null\n      }\n      let userInfo = wsCache.get(CACHE_KEY.USER)\n      if (!userInfo) {\n        userInfo = await getInfo()\n      }\n      this.permissions = userInfo.permissions\n      this.roles = userInfo.roles\n      this.user = userInfo.user\n      this.isSetUser = true\n      wsCache.set(CACHE_KEY.USER, userInfo)\n      wsCache.set(CACHE_KEY.ROLE_ROUTERS, userInfo.menus)\n    },\n    async setUserAvatarAction(avatar: string) {\n      const userInfo = wsCache.get(CACHE_KEY.USER)\n      // NOTE: 是否需要像`setUserInfoAction`一样判断`userInfo != null`\n      this.user.avatar = avatar\n      userInfo.user.avatar = avatar\n      wsCache.set(CACHE_KEY.USER, userInfo)\n    },\n    async setUserNicknameAction(nickname: string) {\n      const userInfo = wsCache.get(CACHE_KEY.USER)\n      // NOTE: 是否需要像`setUserInfoAction`一样判断`userInfo != null`\n      this.user.nickname = nickname\n      userInfo.user.nickname = nickname\n      wsCache.set(CACHE_KEY.USER, userInfo)\n    },\n    async loginOut() {\n      await loginOut()\n      removeToken()\n      deleteUserCache() // 删除用户缓存\n      this.resetState()\n    },\n    resetState() {\n      this.permissions = []\n      this.roles = []\n      this.isSetUser = false\n      this.user = {\n        id: 0,\n        avatar: '',\n        nickname: '',\n        deptId: 0\n      }\n    }\n  }\n})\n\nexport const useUserStoreWithOut = () => {\n  return useUserStore(store)\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/styles/FormCreate/index.scss",
    "content": "// 使用字体图标来源 https://fontello.com/\n\n@font-face {\n  font-family: 'fc-icon';\n  src: url('@/styles/FormCreate/fonts/fontello.woff') format('woff');\n}\n\n.icon-doc-text:before {\n  content: '\\f0f6';\n}\n\n.icon-server:before {\n  content: '\\f233';\n}\n\n.icon-address-card-o:before {\n  content: '\\f2bc';\n}\n\n.icon-user-o:before {\n  content: '\\f2c0';\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/styles/global.module.scss",
    "content": "@import './variables.scss';\n// 导出变量\n:export {\n  namespace: $namespace;\n  elNamespace: $elNamespace;\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/styles/index.scss",
    "content": "@import './var.css';\n@import './FormCreate/index.scss';\n@import 'element-plus/theme-chalk/dark/css-vars.css';\n\n.reset-margin [class*='el-icon'] + span {\n  margin-left: 2px !important;\n}\n\n// 解决抽屉弹出时，body宽度变化的问题\n.el-popup-parent--hidden {\n  width: 100% !important;\n}\n\n// 解决表格内容超过表格总宽度后，横向滚动条前端顶不到表格边缘的问题\n.el-scrollbar__bar {\n  display: flex;\n  justify-content: flex-start;\n}\n\n/* nprogress 适配 element-plus 的主题色 */\n#nprogress {\n  & .bar {\n    background-color: var(--el-color-primary) !important;\n  }\n\n  & .peg {\n    box-shadow:\n      0 0 10px var(--el-color-primary),\n      0 0 5px var(--el-color-primary) !important;\n  }\n\n  & .spinner-icon {\n    border-top-color: var(--el-color-primary);\n    border-left-color: var(--el-color-primary);\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/styles/theme.scss",
    "content": "// .text-color {\n//   color: var(--el-text-color-regular);\n// }\n// .dark .dark\\:text-color {\n//   color: rgba(255, 255, 255, var(--dark-text-color));\n// }\n"
  },
  {
    "path": "yshop-drink-vue3/src/styles/var.css",
    "content": ":root {\n  --login-bg-color: #293146;\n\n  --left-menu-max-width: 200px;\n\n  --left-menu-min-width: 64px;\n\n  --left-menu-bg-color: #001529;\n\n  --left-menu-bg-light-color: #0f2438;\n\n  --left-menu-bg-active-color: var(--el-color-primary);\n\n  --left-menu-text-color: #bfcbd9;\n\n  --left-menu-text-active-color: #fff;\n\n  --left-menu-collapse-bg-active-color: var(--el-color-primary);\n  /* left menu end */\n\n  /* logo start */\n  --logo-height: 50px;\n\n  --logo-title-text-color: #fff;\n  /* logo end */\n\n  /* header start */\n  --top-header-bg-color: '#fff';\n\n  --top-header-text-color: 'inherit';\n\n  --top-header-hover-color: #f6f6f6;\n\n  --top-tool-height: var(--logo-height);\n\n  --top-tool-p-x: 0;\n\n  --tags-view-height: 35px;\n  /* header start */\n\n  /* tab menu start */\n  --tab-menu-max-width: 80px;\n\n  --tab-menu-min-width: 30px;\n\n  --tab-menu-collapse-height: 36px;\n  /* tab menu end */\n\n  --app-content-padding: 20px;\n\n  --app-content-bg-color: #f5f7f9;\n\n  --app-footer-height: 50px;\n\n  --transition-time-02: 0.2s;\n}\n\n.dark {\n  --app-content-bg-color: var(--el-bg-color);\n}\n\nhtml,\nbody {\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/styles/variables.scss",
    "content": "// 命名空间\n$namespace: v;\n// el命名空间\n$elNamespace: el;\n"
  },
  {
    "path": "yshop-drink-vue3/src/types/components.d.ts",
    "content": "export type ComponentName =\n  | 'Radio'\n  | 'RadioButton'\n  | 'Checkbox'\n  | 'CheckboxButton'\n  | 'Input'\n  | 'Autocomplete'\n  | 'InputNumber'\n  | 'Select'\n  | 'Cascader'\n  | 'Switch'\n  | 'Slider'\n  | 'TimePicker'\n  | 'DatePicker'\n  | 'Rate'\n  | 'ColorPicker'\n  | 'Transfer'\n  | 'Divider'\n  | 'TimeSelect'\n  | 'SelectV2'\n  | 'TreeSelect'\n  | 'InputPassword'\n  | 'Editor'\n  | 'UploadImg'\n  | 'UploadImgs'\n  | 'UploadFile'\n\nexport type ColProps = {\n  span?: number\n  xs?: number\n  sm?: number\n  md?: number\n  lg?: number\n  xl?: number\n  tag?: string\n}\n\nexport type ComponentOptions = {\n  label?: string\n  value?: FormValueType\n  disabled?: boolean\n  key?: string | number\n  children?: ComponentOptions[]\n  options?: ComponentOptions[]\n} & Recordable\n\nexport type ComponentOptionsAlias = {\n  labelField?: string\n  valueField?: string\n}\n\nexport type ComponentProps = {\n  optionsAlias?: ComponentOptionsAlias\n  options?: ComponentOptions[]\n  optionsSlot?: boolean\n} & Recordable\n"
  },
  {
    "path": "yshop-drink-vue3/src/types/configGlobal.d.ts",
    "content": "import { ElementPlusSize } from './elementPlus'\nexport interface ConfigGlobalTypes {\n  size?: ElementPlusSize\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/types/contextMenu.d.ts",
    "content": "export type contextMenuSchema = {\n  disabled?: boolean\n  divided?: boolean\n  icon?: string\n  label: string\n  command?: (item: contextMenuSchema) => void\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/types/descriptions.d.ts",
    "content": "export interface DescriptionsSchema {\n  span?: number // 占多少分\n  field: string // 字段名\n  label?: string // label名\n  mappedField?: string // 字段映射\n  width?: string | number\n  minWidth?: string | number\n  align?: 'left' | 'center' | 'right'\n  labelAlign?: 'left' | 'center' | 'right'\n  className?: string\n  labelClassName?: string\n  dateFormat?: string // add by 星语：支持时间的格式化\n  dictType?: string // add by 星语：支持 dict 字典数据\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/types/elementPlus.d.ts",
    "content": "export type ElementPlusSize = 'default' | 'small' | 'large'\n\nexport type ElementPlusInfoType = 'success' | 'info' | 'warning' | 'danger'\n"
  },
  {
    "path": "yshop-drink-vue3/src/types/form.d.ts",
    "content": "import type { CSSProperties } from 'vue'\nimport { ColProps, ComponentProps, ComponentName } from '@/types/components'\nimport type { AxiosPromise } from 'axios'\n\nexport type FormSetPropsType = {\n  field: string\n  path: string\n  value: any\n}\n\nexport type FormValueType = string | number | string[] | number[] | boolean | undefined | null\n\nexport type FormItemProps = {\n  labelWidth?: string | number\n  required?: boolean\n  rules?: Recordable\n  error?: string\n  showMessage?: boolean\n  inlineMessage?: boolean\n  style?: CSSProperties\n}\n\nexport type FormSchema = {\n  // 唯一值\n  field: string\n  // 标题\n  label?: string\n  // 提示\n  labelMessage?: string\n  // col组件属性\n  colProps?: ColProps\n  // 表单组件属性，slots对应的是表单组件的插槽，规则：${field}-xxx，具体可以查看element-plus文档\n  componentProps?: { slots?: Recordable } & ComponentProps\n  // formItem组件属性\n  formItemProps?: FormItemProps\n  // 渲染的组件\n  component?: ComponentName\n  // 初始值\n  value?: FormValueType\n  // 是否隐藏\n  hidden?: boolean\n  // 远程加载下拉项\n  api?: <T = any>() => AxiosPromise<T>\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/types/icon.d.ts",
    "content": "export interface IconTypes {\n  size?: number\n  color?: string\n  icon: string\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/types/infoTip.d.ts",
    "content": "export interface TipSchema {\n  label: string\n  keys?: string[]\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/types/layout.d.ts",
    "content": "export type LayoutType = 'classic' | 'topLeft' | 'top' | 'cutMenu'\n"
  },
  {
    "path": "yshop-drink-vue3/src/types/localeDropdown.d.ts",
    "content": "export interface Language {\n  el: Recordable\n  name: string\n}\n\nexport interface LocaleDropdownType {\n  lang: LocaleType\n  name?: string\n  elLocale?: Language\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/types/qrcode.d.ts",
    "content": "export interface QrcodeLogo {\n  src?: string\n  logoSize?: number\n  bgColor?: string\n  borderSize?: number\n  crossOrigin?: string\n  borderRadius?: number\n  logoRadius?: number\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/types/table.d.ts",
    "content": "export type TableColumn = {\n  field: string\n  label?: string\n  width?: number | string\n  fixed?: 'left' | 'right'\n  children?: TableColumn[]\n} & Recordable\n\nexport type VxeTableColumn = {\n  field: string\n  title?: string\n  children?: TableColumn[]\n} & Recordable\n\nexport type TableSlotDefault = {\n  row: Recordable\n  column: TableColumn\n  $index: number\n} & Recordable\n\nexport interface Pagination {\n  small?: boolean\n  background?: boolean\n  pageSize?: number\n  defaultPageSize?: number\n  total?: number\n  pageCount?: number\n  pagerCount?: number\n  currentPage?: number\n  defaultCurrentPage?: number\n  layout?: string\n  pageSizes?: number[]\n  popperClass?: string\n  prevText?: string\n  nextText?: string\n  disabled?: boolean\n  hideOnSinglePage?: boolean\n}\n\nexport interface TableSetPropsType {\n  field: string\n  path: string\n  value: any\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/types/theme.d.ts",
    "content": "export type ThemeTypes = {\n  elColorPrimary?: string\n  leftMenuBorderColor?: string\n  leftMenuBgColor?: string\n  leftMenuBgLightColor?: string\n  leftMenuBgActiveColor?: string\n  leftMenuCollapseBgActiveColor?: string\n  leftMenuTextColor?: string\n  leftMenuTextActiveColor?: string\n  logoTitleTextColor?: string\n  logoBorderColor?: string\n  topHeaderBgColor?: string\n  topHeaderTextColor?: string\n  topHeaderHoverColor?: string\n  topToolBorderColor?: string\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/utils/Logger.ts",
    "content": "const isArray = function (obj: any): boolean {\n  return Object.prototype.toString.call(obj) === '[object Array]'\n}\n\nconst Logger = () => {}\n\nLogger.typeColor = function (type: string) {\n  let color = ''\n  switch (type) {\n    case 'primary':\n      color = '#2d8cf0'\n      break\n    case 'success':\n      color = '#19be6b'\n      break\n    case 'info':\n      color = '#909399'\n      break\n    case 'warn':\n      color = '#ff9900'\n      break\n    case 'error':\n      color = '#f03f14'\n      break\n    default:\n      color = '#35495E'\n      break\n  }\n  return color\n}\n\nLogger.print = function (type = 'default', text: any, back = false) {\n  if (typeof text === 'object') {\n    // 如果是對象則調用打印對象方式\n    isArray(text) ? console.table(text) : console.dir(text)\n    return\n  }\n  if (back) {\n    // 如果是打印帶背景圖的\n    console.log(\n      `%c ${text} `,\n      `background:${Logger.typeColor(type)}; padding: 2px; border-radius: 4px; color: #fff;`\n    )\n  } else {\n    console.log(\n      `%c ${text} `,\n      `border: 1px solid ${Logger.typeColor(type)};\n        padding: 2px; border-radius: 4px;\n        color: ${Logger.typeColor(type)};`\n    )\n  }\n}\n\nLogger.printBack = function (type = 'primary', text) {\n  this.print(type, text, true)\n}\n\nLogger.pretty = function (type = 'primary', title, text) {\n  if (typeof text === 'object') {\n    console.group('Console Group', title)\n    console.log(\n      `%c ${title}`,\n      `background:${Logger.typeColor(type)};border:1px solid ${Logger.typeColor(type)};\n        padding: 1px; border-radius: 4px; color: #fff;`\n    )\n    isArray(text) ? console.table(text) : console.dir(text)\n    console.groupEnd()\n    return\n  }\n  console.log(\n    `%c ${title} %c ${text} %c`,\n    `background:${Logger.typeColor(type)};border:1px solid ${Logger.typeColor(type)};\n      padding: 1px; border-radius: 4px 0 0 4px; color: #fff;`,\n    `border:1px solid ${Logger.typeColor(type)};\n      padding: 1px; border-radius: 0 4px 4px 0; color: ${Logger.typeColor(type)};`,\n    'background:transparent'\n  )\n}\n\nLogger.prettyPrimary = function (title, ...text) {\n  text.forEach((t) => this.pretty('primary', title, t))\n}\n\nLogger.prettySuccess = function (title, ...text) {\n  text.forEach((t) => this.pretty('success', title, t))\n}\n\nLogger.prettyWarn = function (title, ...text) {\n  text.forEach((t) => this.pretty('warn', title, t))\n}\n\nLogger.prettyError = function (title, ...text) {\n  text.forEach((t) => this.pretty('error', title, t))\n}\n\nLogger.prettyInfo = function (title, ...text) {\n  text.forEach((t) => this.pretty('info', title, t))\n}\n\nexport default Logger\n"
  },
  {
    "path": "yshop-drink-vue3/src/utils/auth.ts",
    "content": "import { useCache, CACHE_KEY } from '@/hooks/web/useCache'\nimport { TokenType } from '@/api/login/types'\nimport { decrypt, encrypt } from '@/utils/jsencrypt'\n\nconst { wsCache } = useCache()\n\nconst AccessTokenKey = 'ACCESS_TOKEN'\nconst RefreshTokenKey = 'REFRESH_TOKEN'\n\n// 获取token\nexport const getAccessToken = () => {\n  // 此处与TokenKey相同，此写法解决初始化时Cookies中不存在TokenKey报错\n  return wsCache.get(AccessTokenKey) ? wsCache.get(AccessTokenKey) : wsCache.get('ACCESS_TOKEN')\n}\n\n// 刷新token\nexport const getRefreshToken = () => {\n  return wsCache.get(RefreshTokenKey)\n}\n\n// 设置token\nexport const setToken = (token: TokenType) => {\n  wsCache.set(RefreshTokenKey, token.refreshToken)\n  wsCache.set(AccessTokenKey, token.accessToken)\n}\n\n// 删除token\nexport const removeToken = () => {\n  wsCache.delete(AccessTokenKey)\n  wsCache.delete(RefreshTokenKey)\n}\n\n/** 格式化token（jwt格式） */\nexport const formatToken = (token: string): string => {\n  return 'Bearer ' + token\n}\n// ========== 账号相关 ==========\n\nexport type LoginFormType = {\n  tenantName: string\n  username: string\n  password: string\n  rememberMe: boolean\n}\n\nexport const getLoginForm = () => {\n  const loginForm: LoginFormType = wsCache.get(CACHE_KEY.LoginForm)\n  if (loginForm) {\n    loginForm.password = decrypt(loginForm.password) as string\n  }\n  return loginForm\n}\n\nexport const setLoginForm = (loginForm: LoginFormType) => {\n  loginForm.password = encrypt(loginForm.password) as string\n  wsCache.set(CACHE_KEY.LoginForm, loginForm, { exp: 30 * 24 * 60 * 60 })\n}\n\nexport const removeLoginForm = () => {\n  wsCache.delete(CACHE_KEY.LoginForm)\n}\n\n// ========== 租户相关 ==========\n\nexport const getTenantId = () => {\n  return wsCache.get(CACHE_KEY.TenantId)\n}\n\nexport const setTenantId = (username: string) => {\n  wsCache.set(CACHE_KEY.TenantId, username)\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/utils/color.ts",
    "content": "/**\n * 判断是否 十六进制颜色值.\n * 输入形式可为 #fff000 #f00\n *\n * @param   String  color   十六进制颜色值\n * @return  Boolean\n */\nexport const isHexColor = (color: string) => {\n  const reg = /^#([0-9a-fA-F]{3}|[0-9a-fA-f]{6})$/\n  return reg.test(color)\n}\n\n/**\n * RGB 颜色值转换为 十六进制颜色值.\n * r, g, 和 b 需要在 [0, 255] 范围内\n *\n * @return  String          类似#ff00ff\n * @param r\n * @param g\n * @param b\n */\nexport const rgbToHex = (r: number, g: number, b: number) => {\n  // tslint:disable-next-line:no-bitwise\n  const hex = ((r << 16) | (g << 8) | b).toString(16)\n  return '#' + new Array(Math.abs(hex.length - 7)).join('0') + hex\n}\n\n/**\n * Transform a HEX color to its RGB representation\n * @param {string} hex The color to transform\n * @returns The RGB representation of the passed color\n */\nexport const hexToRGB = (hex: string, opacity?: number) => {\n  let sHex = hex.toLowerCase()\n  if (isHexColor(hex)) {\n    if (sHex.length === 4) {\n      let sColorNew = '#'\n      for (let i = 1; i < 4; i += 1) {\n        sColorNew += sHex.slice(i, i + 1).concat(sHex.slice(i, i + 1))\n      }\n      sHex = sColorNew\n    }\n    const sColorChange: number[] = []\n    for (let i = 1; i < 7; i += 2) {\n      sColorChange.push(parseInt('0x' + sHex.slice(i, i + 2)))\n    }\n    return opacity\n      ? 'RGBA(' + sColorChange.join(',') + ',' + opacity + ')'\n      : 'RGB(' + sColorChange.join(',') + ')'\n  }\n  return sHex\n}\n\nexport const colorIsDark = (color: string) => {\n  if (!isHexColor(color)) return\n  const [r, g, b] = hexToRGB(color)\n    .replace(/(?:\\(|\\)|rgb|RGB)*/g, '')\n    .split(',')\n    .map((item) => Number(item))\n  return r * 0.299 + g * 0.578 + b * 0.114 < 192\n}\n\n/**\n * Darkens a HEX color given the passed percentage\n * @param {string} color The color to process\n * @param {number} amount The amount to change the color by\n * @returns {string} The HEX representation of the processed color\n */\nexport const darken = (color: string, amount: number) => {\n  color = color.indexOf('#') >= 0 ? color.substring(1, color.length) : color\n  amount = Math.trunc((255 * amount) / 100)\n  return `#${subtractLight(color.substring(0, 2), amount)}${subtractLight(\n    color.substring(2, 4),\n    amount\n  )}${subtractLight(color.substring(4, 6), amount)}`\n}\n\n/**\n * Lightens a 6 char HEX color according to the passed percentage\n * @param {string} color The color to change\n * @param {number} amount The amount to change the color by\n * @returns {string} The processed color represented as HEX\n */\nexport const lighten = (color: string, amount: number) => {\n  color = color.indexOf('#') >= 0 ? color.substring(1, color.length) : color\n  amount = Math.trunc((255 * amount) / 100)\n  return `#${addLight(color.substring(0, 2), amount)}${addLight(\n    color.substring(2, 4),\n    amount\n  )}${addLight(color.substring(4, 6), amount)}`\n}\n\n/* Suma el porcentaje indicado a un color (RR, GG o BB) hexadecimal para aclararlo */\n/**\n * Sums the passed percentage to the R, G or B of a HEX color\n * @param {string} color The color to change\n * @param {number} amount The amount to change the color by\n * @returns {string} The processed part of the color\n */\nconst addLight = (color: string, amount: number) => {\n  const cc = parseInt(color, 16) + amount\n  const c = cc > 255 ? 255 : cc\n  return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}`\n}\n\n/**\n * Calculates luminance of an rgb color\n * @param {number} r red\n * @param {number} g green\n * @param {number} b blue\n */\nconst luminanace = (r: number, g: number, b: number) => {\n  const a = [r, g, b].map((v) => {\n    v /= 255\n    return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4)\n  })\n  return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722\n}\n\n/**\n * Calculates contrast between two rgb colors\n * @param {string} rgb1 rgb color 1\n * @param {string} rgb2 rgb color 2\n */\nconst contrast = (rgb1: string[], rgb2: number[]) => {\n  return (\n    (luminanace(~~rgb1[0], ~~rgb1[1], ~~rgb1[2]) + 0.05) /\n    (luminanace(rgb2[0], rgb2[1], rgb2[2]) + 0.05)\n  )\n}\n\n/**\n * Determines what the best text color is (black or white) based con the contrast with the background\n * @param hexColor - Last selected color by the user\n */\nexport const calculateBestTextColor = (hexColor: string) => {\n  const rgbColor = hexToRGB(hexColor.substring(1))\n  const contrastWithBlack = contrast(rgbColor.split(','), [0, 0, 0])\n\n  return contrastWithBlack >= 12 ? '#000000' : '#FFFFFF'\n}\n\n/**\n * Subtracts the indicated percentage to the R, G or B of a HEX color\n * @param {string} color The color to change\n * @param {number} amount The amount to change the color by\n * @returns {string} The processed part of the color\n */\nconst subtractLight = (color: string, amount: number) => {\n  const cc = parseInt(color, 16) - amount\n  const c = cc < 0 ? 0 : cc\n  return c.toString(16).length > 1 ? c.toString(16) : `0${c.toString(16)}`\n}\n\n// 预设颜色\nexport const PREDEFINE_COLORS = [\n  '#ff4500',\n  '#ff8c00',\n  '#ffd700',\n  '#90ee90',\n  '#00ced1',\n  '#1e90ff',\n  '#c71585',\n  '#409EFF',\n  '#909399',\n  '#C0C4CC',\n  '#b7390b',\n  '#ff7800',\n  '#fad400',\n  '#5b8c5f',\n  '#00babd',\n  '#1f73c3',\n  '#711f57'\n]\n"
  },
  {
    "path": "yshop-drink-vue3/src/utils/constants.ts",
    "content": "/**\n * Created by yshop源码\n *\n * 枚举类\n */\n\n// ========== COMMON 模块 ==========\n// 全局通用状态枚举\nexport const CommonStatusEnum = {\n  ENABLE: 0, // 开启\n  DISABLE: 1 // 禁用\n}\n\n// 全局用户类型枚举\nexport const UserTypeEnum = {\n  MEMBER: 1, // 会员\n  ADMIN: 2 // 管理员\n}\n\n// ========== SYSTEM 模块 ==========\n/**\n * 菜单的类型枚举\n */\nexport const SystemMenuTypeEnum = {\n  DIR: 1, // 目录\n  MENU: 2, // 菜单\n  BUTTON: 3 // 按钮\n}\n\n/**\n * 角色的类型枚举\n */\nexport const SystemRoleTypeEnum = {\n  SYSTEM: 1, // 内置角色\n  CUSTOM: 2 // 自定义角色\n}\n\n/**\n * 数据权限的范围枚举\n */\nexport const SystemDataScopeEnum = {\n  ALL: 1, // 全部数据权限\n  DEPT_CUSTOM: 2, // 指定部门数据权限\n  DEPT_ONLY: 3, // 部门数据权限\n  DEPT_AND_CHILD: 4, // 部门及以下数据权限\n  DEPT_SELF: 5 // 仅本人数据权限\n}\n\n/**\n * 用户的社交平台的类型枚举\n */\nexport const SystemUserSocialTypeEnum = {\n  DINGTALK: {\n    title: '钉钉',\n    type: 20,\n    source: 'dingtalk',\n    img: 'https://s1.ax1x.com/2022/05/22/OzMDRs.png'\n  },\n  WECHAT_ENTERPRISE: {\n    title: '企业微信',\n    type: 30,\n    source: 'wechat_enterprise',\n    img: 'https://s1.ax1x.com/2022/05/22/OzMrzn.png'\n  }\n}\n\n// ========== INFRA 模块 ==========\n/**\n * 代码生成模板类型\n */\nexport const InfraCodegenTemplateTypeEnum = {\n  CRUD: 1, // 基础 CRUD\n  TREE: 2, // 树形 CRUD\n  SUB: 3 // 主子表 CRUD\n}\n\n/**\n * 任务状态的枚举\n */\nexport const InfraJobStatusEnum = {\n  INIT: 0, // 初始化中\n  NORMAL: 1, // 运行中\n  STOP: 2 // 暂停运行\n}\n\n/**\n * API 异常数据的处理状态\n */\nexport const InfraApiErrorLogProcessStatusEnum = {\n  INIT: 0, // 未处理\n  DONE: 1, // 已处理\n  IGNORE: 2 // 已忽略\n}\n\n// ========== PAY 模块 ==========\n/**\n * 支付渠道枚举\n */\nexport const PayChannelEnum = {\n  WX_PUB: {\n    code: 'wx_pub',\n    name: '微信 JSAPI 支付'\n  },\n  WX_LITE: {\n    code: 'wx_lite',\n    name: '微信小程序支付'\n  },\n  WX_APP: {\n    code: 'wx_app',\n    name: '微信 APP 支付'\n  },\n  WX_BAR: {\n    code: 'wx_bar',\n    name: '微信条码支付'\n  },\n  ALIPAY_PC: {\n    code: 'alipay_pc',\n    name: '支付宝 PC 网站支付'\n  },\n  ALIPAY_WAP: {\n    code: 'alipay_wap',\n    name: '支付宝 WAP 网站支付'\n  },\n  ALIPAY_APP: {\n    code: 'alipay_app',\n    name: '支付宝 APP 支付'\n  },\n  ALIPAY_QR: {\n    code: 'alipay_qr',\n    name: '支付宝扫码支付'\n  },\n  ALIPAY_BAR: {\n    code: 'alipay_bar',\n    name: '支付宝条码支付'\n  },\n  WALLET: {\n    code: 'wallet',\n    name: '钱包支付'\n  },\n  MOCK: {\n    code: 'mock',\n    name: '模拟支付'\n  }\n}\n\n/**\n * 支付的展示模式每局\n */\nexport const PayDisplayModeEnum = {\n  URL: {\n    mode: 'url'\n  },\n  IFRAME: {\n    mode: 'iframe'\n  },\n  FORM: {\n    mode: 'form'\n  },\n  QR_CODE: {\n    mode: 'qr_code'\n  },\n  APP: {\n    mode: 'app'\n  }\n}\n\n/**\n * 支付类型枚举\n */\nexport const PayType = {\n  WECHAT: 'WECHAT',\n  ALIPAY: 'ALIPAY',\n  MOCK: 'MOCK'\n}\n\n/**\n * 支付订单状态枚举\n */\nexport const PayOrderStatusEnum = {\n  WAITING: {\n    status: 0,\n    name: '未支付'\n  },\n  SUCCESS: {\n    status: 10,\n    name: '已支付'\n  },\n  CLOSED: {\n    status: 20,\n    name: '未支付'\n  }\n}\n\n// ========== MALL - 商品模块 ==========\n/**\n * 商品 SPU 状态\n */\nexport const ProductSpuStatusEnum = {\n  RECYCLE: {\n    status: -1,\n    name: '回收站'\n  },\n  DISABLE: {\n    status: 0,\n    name: '下架'\n  },\n  ENABLE: {\n    status: 1,\n    name: '上架'\n  }\n}\n\n// ========== MALL - 营销模块 ==========\n/**\n * 优惠劵模板的有限期类型的枚举\n */\nexport const CouponTemplateValidityTypeEnum = {\n  DATE: {\n    type: 1,\n    name: '固定日期可用'\n  },\n  TERM: {\n    type: 2,\n    name: '领取之后可用'\n  }\n}\n\n/**\n * 优惠劵模板的领取方式的枚举\n */\nexport const CouponTemplateTakeTypeEnum = {\n  USER: {\n    type: 1,\n    name: '直接领取'\n  },\n  ADMIN: {\n    type: 2,\n    name: '指定发放'\n  },\n  REGISTER: {\n    type: 3,\n    name: '新人券'\n  }\n}\n\n/**\n * 营销的商品范围枚举\n */\nexport const PromotionProductScopeEnum = {\n  ALL: {\n    scope: 1,\n    name: '通用劵'\n  },\n  SPU: {\n    scope: 2,\n    name: '商品劵'\n  },\n  CATEGORY: {\n    scope: 3,\n    name: '品类劵'\n  }\n}\n\n/**\n * 营销的条件类型枚举\n */\nexport const PromotionConditionTypeEnum = {\n  PRICE: {\n    type: 10,\n    name: '满 N 元'\n  },\n  COUNT: {\n    type: 20,\n    name: '满 N 件'\n  }\n}\n\n/**\n * 优惠类型枚举\n */\nexport const PromotionDiscountTypeEnum = {\n  PRICE: {\n    type: 1,\n    name: '满减'\n  },\n  PERCENT: {\n    type: 2,\n    name: '折扣'\n  }\n}\n\n// ========== MALL - 交易模块 ==========\n/**\n * 分销关系绑定模式枚举\n */\nexport const BrokerageBindModeEnum = {\n  ANYTIME: {\n    mode: 1,\n    name: '首次绑定'\n  },\n  REGISTER: {\n    mode: 2,\n    name: '注册绑定'\n  },\n  OVERRIDE: {\n    mode: 3,\n    name: '覆盖绑定'\n  }\n}\n/**\n * 分佣模式枚举\n */\nexport const BrokerageEnabledConditionEnum = {\n  ALL: {\n    condition: 1,\n    name: '人人分销'\n  },\n  ADMIN: {\n    condition: 2,\n    name: '指定分销'\n  }\n}\n/**\n * 佣金记录业务类型枚举\n */\nexport const BrokerageRecordBizTypeEnum = {\n  ORDER: {\n    type: 1,\n    name: '获得推广佣金'\n  },\n  WITHDRAW: {\n    type: 2,\n    name: '提现申请'\n  }\n}\n/**\n * 佣金提现状态枚举\n */\nexport const BrokerageWithdrawStatusEnum = {\n  AUDITING: {\n    status: 0,\n    name: '审核中'\n  },\n  AUDIT_SUCCESS: {\n    status: 10,\n    name: '审核通过'\n  },\n  AUDIT_FAIL: {\n    status: 20,\n    name: '审核不通过'\n  },\n  WITHDRAW_SUCCESS: {\n    status: 11,\n    name: '提现成功'\n  },\n  WITHDRAW_FAIL: {\n    status: 21,\n    name: '提现失败'\n  }\n}\n/**\n * 佣金提现类型枚举\n */\nexport const BrokerageWithdrawTypeEnum = {\n  WALLET: {\n    type: 1,\n    name: '钱包'\n  },\n  BANK: {\n    type: 2,\n    name: '银行卡'\n  },\n  WECHAT: {\n    type: 3,\n    name: '微信'\n  },\n  ALIPAY: {\n    type: 4,\n    name: '支付宝'\n  }\n}\n\n/**\n * 配送方式枚举\n */\nexport const DeliveryTypeEnum = {\n  EXPRESS: {\n    type: 1,\n    name: '快递发货'\n  },\n  PICK_UP: {\n    type: 2,\n    name: '到店自提'\n  }\n}\n/**\n * 交易订单 - 状态\n */\nexport const TradeOrderStatusEnum = {\n  UNPAID: {\n    status: 0,\n    name: '待支付'\n  },\n  UNDELIVERED: {\n    status: 10,\n    name: '待发货'\n  },\n  DELIVERED: {\n    status: 20,\n    name: '已发货'\n  },\n  COMPLETED: {\n    status: 30,\n    name: '已完成'\n  },\n  CANCELED: {\n    status: 40,\n    name: '已取消'\n  }\n}\n\n// ========== ERP - 企业资源计划 ==========\n\nexport const ErpBizType = {\n  PURCHASE_ORDER: 10,\n  PURCHASE_IN: 11,\n  PURCHASE_RETURN: 12,\n  SALE_ORDER: 20,\n  SALE_OUT: 21,\n  SALE_RETURN: 22\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/utils/dateUtil.ts",
    "content": "/**\n * Independent time operation tool to facilitate subsequent switch to dayjs\n */\n// TODO 芋艿：【锁屏】可能后面删除掉\nimport dayjs from 'dayjs'\n\nconst DATE_TIME_FORMAT = 'YYYY-MM-DD HH:mm:ss'\nconst DATE_FORMAT = 'YYYY-MM-DD'\n\nexport function formatToDateTime(date?: dayjs.ConfigType, format = DATE_TIME_FORMAT): string {\n  return dayjs(date).format(format)\n}\n\nexport function formatToDate(date?: dayjs.ConfigType, format = DATE_FORMAT): string {\n  return dayjs(date).format(format)\n}\n\nexport const dateUtil = dayjs\n"
  },
  {
    "path": "yshop-drink-vue3/src/utils/dict.ts",
    "content": "/**\n * 数据字典工具类\n */\nimport {useDictStoreWithOut} from '@/store/modules/dict'\nimport {ElementPlusInfoType} from '@/types/elementPlus'\n\nconst dictStore = useDictStoreWithOut()\n\n/**\n * 获取 dictType 对应的数据字典数组\n *\n * @param dictType 数据类型\n * @returns {*|Array} 数据字典数组\n */\nexport interface DictDataType {\n  dictType: string\n  label: string\n  value: string | number | boolean\n  colorType: ElementPlusInfoType | ''\n  cssClass: string\n}\n\nexport interface NumberDictDataType extends DictDataType {\n  value: number\n}\n\nexport const getDictOptions = (dictType: string) => {\n  return dictStore.getDictByType(dictType) || []\n}\n\nexport const getIntDictOptions = (dictType: string): NumberDictDataType[] => {\n  // 获得通用的 DictDataType 列表\n  const dictOptions: DictDataType[] = getDictOptions(dictType)\n  // 转换成 number 类型的 NumberDictDataType 类型\n  // why 需要特殊转换：避免 IDEA 在 v-for=\"dict in getIntDictOptions(...)\" 时，el-option 的 key 会告警\n  const dictOption: NumberDictDataType[] = []\n  dictOptions.forEach((dict: DictDataType) => {\n    dictOption.push({\n      ...dict,\n      value: parseInt(dict.value + '')\n    })\n  })\n  return dictOption\n}\n\nexport const getStrDictOptions = (dictType: string) => {\n  const dictOption: DictDataType[] = []\n  const dictOptions: DictDataType[] = getDictOptions(dictType)\n  dictOptions.forEach((dict: DictDataType) => {\n    dictOption.push({\n      ...dict,\n      value: dict.value + ''\n    })\n  })\n  return dictOption\n}\n\nexport const getBoolDictOptions = (dictType: string) => {\n  const dictOption: DictDataType[] = []\n  const dictOptions: DictDataType[] = getDictOptions(dictType)\n  dictOptions.forEach((dict: DictDataType) => {\n    dictOption.push({\n      ...dict,\n      value: dict.value + '' === 'true'\n    })\n  })\n  return dictOption\n}\n\n/**\n * 获取指定字典类型的指定值对应的字典对象\n * @param dictType 字典类型\n * @param value 字典值\n * @return DictDataType 字典对象\n */\nexport const getDictObj = (dictType: string, value: any): DictDataType | undefined => {\n  const dictOptions: DictDataType[] = getDictOptions(dictType)\n  for (const dict of dictOptions) {\n    if (dict.value === value + '') {\n      return dict\n    }\n  }\n}\n\n/**\n * 获得字典数据的文本展示\n *\n * @param dictType 字典类型\n * @param value 字典数据的值\n * @return 字典名称\n */\nexport const getDictLabel = (dictType: string, value: any): string => {\n  const dictOptions: DictDataType[] = getDictOptions(dictType)\n  const dictLabel = ref('')\n  dictOptions.forEach((dict: DictDataType) => {\n    if (dict.value === value + '') {\n      dictLabel.value = dict.label\n    }\n  })\n  return dictLabel.value\n}\n\nexport enum DICT_TYPE {\n  USER_TYPE = 'user_type',\n  COMMON_STATUS = 'common_status',\n  TERMINAL = 'terminal', // 终端\n  DATE_INTERVAL = 'date_interval', // 数据间隔\n\n  // ========== SYSTEM 模块 ==========\n  SYSTEM_USER_SEX = 'system_user_sex',\n  SYSTEM_MENU_TYPE = 'system_menu_type',\n  SYSTEM_ROLE_TYPE = 'system_role_type',\n  SYSTEM_DATA_SCOPE = 'system_data_scope',\n  SYSTEM_NOTICE_TYPE = 'system_notice_type',\n  SYSTEM_LOGIN_TYPE = 'system_login_type',\n  SYSTEM_LOGIN_RESULT = 'system_login_result',\n  SYSTEM_SMS_CHANNEL_CODE = 'system_sms_channel_code',\n  SYSTEM_SMS_TEMPLATE_TYPE = 'system_sms_template_type',\n  SYSTEM_SMS_SEND_STATUS = 'system_sms_send_status',\n  SYSTEM_SMS_RECEIVE_STATUS = 'system_sms_receive_status',\n  SYSTEM_ERROR_CODE_TYPE = 'system_error_code_type',\n  SYSTEM_OAUTH2_GRANT_TYPE = 'system_oauth2_grant_type',\n  SYSTEM_MAIL_SEND_STATUS = 'system_mail_send_status',\n  SYSTEM_NOTIFY_TEMPLATE_TYPE = 'system_notify_template_type',\n  SYSTEM_SOCIAL_TYPE = 'system_social_type',\n\n  // ========== INFRA 模块 ==========\n  INFRA_BOOLEAN_STRING = 'infra_boolean_string',\n  INFRA_JOB_STATUS = 'infra_job_status',\n  INFRA_JOB_LOG_STATUS = 'infra_job_log_status',\n  INFRA_API_ERROR_LOG_PROCESS_STATUS = 'infra_api_error_log_process_status',\n  INFRA_CONFIG_TYPE = 'infra_config_type',\n  INFRA_CODEGEN_TEMPLATE_TYPE = 'infra_codegen_template_type',\n  INFRA_CODEGEN_FRONT_TYPE = 'infra_codegen_front_type',\n  INFRA_CODEGEN_SCENE = 'infra_codegen_scene',\n  INFRA_FILE_STORAGE = 'infra_file_storage',\n  INFRA_OPERATE_TYPE = 'infra_operate_type',\n\n  // ========== BPM 模块 ==========\n  BPM_MODEL_FORM_TYPE = 'bpm_model_form_type',\n  BPM_TASK_CANDIDATE_STRATEGY = 'bpm_task_candidate_strategy',\n  BPM_PROCESS_INSTANCE_STATUS = 'bpm_process_instance_status',\n  BPM_TASK_STATUS = 'bpm_task_status',\n  BPM_OA_LEAVE_TYPE = 'bpm_oa_leave_type',\n  BPM_PROCESS_LISTENER_TYPE = 'bpm_process_listener_type',\n  BPM_PROCESS_LISTENER_VALUE_TYPE = 'bpm_process_listener_value_type',\n\n  // ========== PAY 模块 ==========\n  PAY_CHANNEL_CODE = 'pay_channel_code', // 支付渠道编码类型\n  PAY_ORDER_STATUS = 'pay_order_status', // 商户支付订单状态\n  PAY_REFUND_STATUS = 'pay_refund_status', // 退款订单状态\n  PAY_NOTIFY_STATUS = 'pay_notify_status', // 商户支付回调状态\n  PAY_NOTIFY_TYPE = 'pay_notify_type', // 商户支付回调状态\n  PAY_TRANSFER_STATUS = 'pay_transfer_status', // 转账订单状态\n  PAY_TRANSFER_TYPE = 'pay_transfer_type', // 转账订单状态\n\n  // ========== MP 模块 ==========\n  MP_AUTO_REPLY_REQUEST_MATCH = 'mp_auto_reply_request_match', // 自动回复请求匹配类型\n  MP_MESSAGE_TYPE = 'mp_message_type', // 消息类型\n\n  // ========== Member 会员模块 ==========\n  MEMBER_POINT_BIZ_TYPE = 'member_point_biz_type', // 积分的业务类型\n  MEMBER_EXPERIENCE_BIZ_TYPE = 'member_experience_biz_type', // 会员经验业务类型\n\n  // ========== MALL - 商品模块 ==========\n  PRODUCT_SPU_STATUS = 'product_spu_status', //商品状态\n\n  // ========== MALL - 交易模块 ==========\n  EXPRESS_CHARGE_MODE = 'trade_delivery_express_charge_mode', //快递的计费方式\n  TRADE_AFTER_SALE_STATUS = 'trade_after_sale_status', // 售后 - 状态\n  TRADE_AFTER_SALE_WAY = 'trade_after_sale_way', // 售后 - 方式\n  TRADE_AFTER_SALE_TYPE = 'trade_after_sale_type', // 售后 - 类型\n  TRADE_ORDER_TYPE = 'trade_order_type', // 订单 - 类型\n  TRADE_ORDER_STATUS = 'trade_order_status', // 订单 - 状态\n  TRADE_ORDER_ITEM_AFTER_SALE_STATUS = 'trade_order_item_after_sale_status', // 订单项 - 售后状态\n  TRADE_DELIVERY_TYPE = 'trade_delivery_type', // 配送方式\n  BROKERAGE_ENABLED_CONDITION = 'brokerage_enabled_condition', // 分佣模式\n  BROKERAGE_BIND_MODE = 'brokerage_bind_mode', // 分销关系绑定模式\n  BROKERAGE_BANK_NAME = 'brokerage_bank_name', // 佣金提现银行\n  BROKERAGE_WITHDRAW_TYPE = 'brokerage_withdraw_type', // 佣金提现类型\n  BROKERAGE_RECORD_BIZ_TYPE = 'brokerage_record_biz_type', // 佣金业务类型\n  BROKERAGE_RECORD_STATUS = 'brokerage_record_status', // 佣金状态\n  BROKERAGE_WITHDRAW_STATUS = 'brokerage_withdraw_status', // 佣金提现状态\n\n  // ========== MALL - 营销模块 ==========\n  PROMOTION_DISCOUNT_TYPE = 'promotion_discount_type', // 优惠类型\n  PROMOTION_PRODUCT_SCOPE = 'promotion_product_scope', // 营销的商品范围\n  PROMOTION_COUPON_TEMPLATE_VALIDITY_TYPE = 'promotion_coupon_template_validity_type', // 优惠劵模板的有限期类型\n  PROMOTION_COUPON_STATUS = 'promotion_coupon_status', // 优惠劵的状态\n  PROMOTION_COUPON_TAKE_TYPE = 'promotion_coupon_take_type', // 优惠劵的领取方式\n  PROMOTION_ACTIVITY_STATUS = 'promotion_activity_status', // 优惠活动的状态\n  PROMOTION_CONDITION_TYPE = 'promotion_condition_type', // 营销的条件类型枚举\n  PROMOTION_BARGAIN_RECORD_STATUS = 'promotion_bargain_record_status', // 砍价记录的状态\n  PROMOTION_COMBINATION_RECORD_STATUS = 'promotion_combination_record_status', // 拼团记录的状态\n  PROMOTION_BANNER_POSITION = 'promotion_banner_position', // banner 定位\n\n  // ========== CRM - 客户管理模块 ==========\n  CRM_AUDIT_STATUS = 'crm_audit_status', // CRM 审批状态\n  CRM_BIZ_TYPE = 'crm_biz_type', // CRM 业务类型\n  CRM_BUSINESS_END_STATUS_TYPE = 'crm_business_end_status_type', // CRM 商机结束状态类型\n  CRM_RECEIVABLE_RETURN_TYPE = 'crm_receivable_return_type', // CRM 回款的还款方式\n  CRM_CUSTOMER_INDUSTRY = 'crm_customer_industry', // CRM 客户所属行业\n  CRM_CUSTOMER_LEVEL = 'crm_customer_level', // CRM 客户级别\n  CRM_CUSTOMER_SOURCE = 'crm_customer_source', // CRM 客户来源\n  CRM_PRODUCT_STATUS = 'crm_product_status', // CRM 商品状态\n  CRM_PERMISSION_LEVEL = 'crm_permission_level', // CRM 数据权限的级别\n  CRM_PRODUCT_UNIT = 'crm_product_unit', // CRM 产品单位\n  CRM_FOLLOW_UP_TYPE = 'crm_follow_up_type', // CRM 跟进方式\n\n  // ========== ERP - 企业资源计划模块  ==========\n  ERP_AUDIT_STATUS = 'erp_audit_status', // ERP 审批状态\n  ERP_STOCK_RECORD_BIZ_TYPE = 'erp_stock_record_biz_type' // 库存明细的业务类型\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/utils/domUtils.ts",
    "content": "import { isServer } from './is'\nconst ieVersion = isServer ? 0 : Number((document as any).documentMode)\nconst SPECIAL_CHARS_REGEXP = /([\\:\\-\\_]+(.))/g\nconst MOZ_HACK_REGEXP = /^moz([A-Z])/\n\nexport interface ViewportOffsetResult {\n  left: number\n  top: number\n  right: number\n  bottom: number\n  rightIncludeBody: number\n  bottomIncludeBody: number\n}\n\n/* istanbul ignore next */\nconst trim = function (string: string) {\n  return (string || '').replace(/^[\\s\\uFEFF]+|[\\s\\uFEFF]+$/g, '')\n}\n\n/* istanbul ignore next */\nconst camelCase = function (name: string) {\n  return name\n    .replace(SPECIAL_CHARS_REGEXP, function (_, __, letter, offset) {\n      return offset ? letter.toUpperCase() : letter\n    })\n    .replace(MOZ_HACK_REGEXP, 'Moz$1')\n}\n\n/* istanbul ignore next */\nexport function hasClass(el: Element, cls: string) {\n  if (!el || !cls) return false\n  if (cls.indexOf(' ') !== -1) {\n    throw new Error('className should not contain space.')\n  }\n  if (el.classList) {\n    return el.classList.contains(cls)\n  } else {\n    return (' ' + el.className + ' ').indexOf(' ' + cls + ' ') > -1\n  }\n}\n\n/* istanbul ignore next */\nexport function addClass(el: Element, cls: string) {\n  if (!el) return\n  let curClass = el.className\n  const classes = (cls || '').split(' ')\n\n  for (let i = 0, j = classes.length; i < j; i++) {\n    const clsName = classes[i]\n    if (!clsName) continue\n\n    if (el.classList) {\n      el.classList.add(clsName)\n    } else if (!hasClass(el, clsName)) {\n      curClass += ' ' + clsName\n    }\n  }\n  if (!el.classList) {\n    el.className = curClass\n  }\n}\n\n/* istanbul ignore next */\nexport function removeClass(el: Element, cls: string) {\n  if (!el || !cls) return\n  const classes = cls.split(' ')\n  let curClass = ' ' + el.className + ' '\n\n  for (let i = 0, j = classes.length; i < j; i++) {\n    const clsName = classes[i]\n    if (!clsName) continue\n\n    if (el.classList) {\n      el.classList.remove(clsName)\n    } else if (hasClass(el, clsName)) {\n      curClass = curClass.replace(' ' + clsName + ' ', ' ')\n    }\n  }\n  if (!el.classList) {\n    el.className = trim(curClass)\n  }\n}\n\nexport function getBoundingClientRect(element: Element): DOMRect | number {\n  if (!element || !element.getBoundingClientRect) {\n    return 0\n  }\n  return element.getBoundingClientRect()\n}\n\n/**\n * 获取当前元素的left、top偏移\n *   left：元素最左侧距离文档左侧的距离\n *   top:元素最顶端距离文档顶端的距离\n *   right:元素最右侧距离文档右侧的距离\n *   bottom：元素最底端距离文档底端的距离\n *   rightIncludeBody：元素最左侧距离文档右侧的距离\n *   bottomIncludeBody：元素最底端距离文档最底部的距离\n *\n * @description:\n */\nexport function getViewportOffset(element: Element): ViewportOffsetResult {\n  const doc = document.documentElement\n\n  const docScrollLeft = doc.scrollLeft\n  const docScrollTop = doc.scrollTop\n  const docClientLeft = doc.clientLeft\n  const docClientTop = doc.clientTop\n\n  const pageXOffset = window.pageXOffset\n  const pageYOffset = window.pageYOffset\n\n  const box = getBoundingClientRect(element)\n\n  const { left: retLeft, top: rectTop, width: rectWidth, height: rectHeight } = box as DOMRect\n\n  const scrollLeft = (pageXOffset || docScrollLeft) - (docClientLeft || 0)\n  const scrollTop = (pageYOffset || docScrollTop) - (docClientTop || 0)\n  const offsetLeft = retLeft + pageXOffset\n  const offsetTop = rectTop + pageYOffset\n\n  const left = offsetLeft - scrollLeft\n  const top = offsetTop - scrollTop\n\n  const clientWidth = window.document.documentElement.clientWidth\n  const clientHeight = window.document.documentElement.clientHeight\n  return {\n    left: left,\n    top: top,\n    right: clientWidth - rectWidth - left,\n    bottom: clientHeight - rectHeight - top,\n    rightIncludeBody: clientWidth - left,\n    bottomIncludeBody: clientHeight - top\n  }\n}\n\n/* istanbul ignore next */\nexport const on = function (\n  element: HTMLElement | Document | Window,\n  event: string,\n  handler: EventListenerOrEventListenerObject\n): void {\n  if (element && event && handler) {\n    element.addEventListener(event, handler, false)\n  }\n}\n\n/* istanbul ignore next */\nexport const off = function (\n  element: HTMLElement | Document | Window,\n  event: string,\n  handler: any\n): void {\n  if (element && event && handler) {\n    element.removeEventListener(event, handler, false)\n  }\n}\n\n/* istanbul ignore next */\nexport const once = function (el: HTMLElement, event: string, fn: EventListener): void {\n  const listener = function (this: any, ...args: unknown[]) {\n    if (fn) {\n      // @ts-ignore\n      fn.apply(this, args)\n    }\n    off(el, event, listener)\n  }\n  on(el, event, listener)\n}\n\n/* istanbul ignore next */\nexport const getStyle =\n  ieVersion < 9\n    ? function (element: Element | any, styleName: string) {\n        if (isServer) return\n        if (!element || !styleName) return null\n        styleName = camelCase(styleName)\n        if (styleName === 'float') {\n          styleName = 'styleFloat'\n        }\n        try {\n          switch (styleName) {\n            case 'opacity':\n              try {\n                return element.filters.item('alpha').opacity / 100\n              } catch (e) {\n                return 1.0\n              }\n            default:\n              return element.style[styleName] || element.currentStyle\n                ? element.currentStyle[styleName]\n                : null\n          }\n        } catch (e) {\n          return element.style[styleName]\n        }\n      }\n    : function (element: Element | any, styleName: string) {\n        if (isServer) return\n        if (!element || !styleName) return null\n        styleName = camelCase(styleName)\n        if (styleName === 'float') {\n          styleName = 'cssFloat'\n        }\n        try {\n          const computed = (document as any).defaultView.getComputedStyle(element, '')\n          return element.style[styleName] || computed ? computed[styleName] : null\n        } catch (e) {\n          return element.style[styleName]\n        }\n      }\n\n/* istanbul ignore next */\nexport function setStyle(element: Element | any, styleName: any, value: any) {\n  if (!element || !styleName) return\n\n  if (typeof styleName === 'object') {\n    for (const prop in styleName) {\n      if (Object.prototype.hasOwnProperty.call(styleName, prop)) {\n        setStyle(element, prop, styleName[prop])\n      }\n    }\n  } else {\n    styleName = camelCase(styleName)\n    if (styleName === 'opacity' && ieVersion < 9) {\n      element.style.filter = isNaN(value) ? '' : 'alpha(opacity=' + value * 100 + ')'\n    } else {\n      element.style[styleName] = value\n    }\n  }\n}\n\n/* istanbul ignore next */\nexport const isScroll = (el: Element, vertical: any) => {\n  if (isServer) return\n\n  const determinedDirection = vertical !== null || vertical !== undefined\n  const overflow = determinedDirection\n    ? vertical\n      ? getStyle(el, 'overflow-y')\n      : getStyle(el, 'overflow-x')\n    : getStyle(el, 'overflow')\n\n  return overflow.match(/(scroll|auto)/)\n}\n\n/* istanbul ignore next */\nexport const getScrollContainer = (el: Element, vertical?: any) => {\n  if (isServer) return\n\n  let parent: any = el\n  while (parent) {\n    if ([window, document, document.documentElement].includes(parent)) {\n      return window\n    }\n    if (isScroll(parent, vertical)) {\n      return parent\n    }\n    parent = parent.parentNode\n  }\n\n  return parent\n}\n\n/* istanbul ignore next */\nexport const isInContainer = (el: Element, container: any) => {\n  if (isServer || !el || !container) return false\n\n  const elRect = el.getBoundingClientRect()\n  let containerRect\n\n  if ([window, document, document.documentElement, null, undefined].includes(container)) {\n    containerRect = {\n      top: 0,\n      right: window.innerWidth,\n      bottom: window.innerHeight,\n      left: 0\n    }\n  } else {\n    containerRect = container.getBoundingClientRect()\n  }\n\n  return (\n    elRect.top < containerRect.bottom &&\n    elRect.bottom > containerRect.top &&\n    elRect.right > containerRect.left &&\n    elRect.left < containerRect.right\n  )\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/utils/download.ts",
    "content": "const download0 = (data: Blob, fileName: string, mineType: string) => {\n  // 创建 blob\n  const blob = new Blob([data], { type: mineType })\n  // 创建 href 超链接，点击进行下载\n  window.URL = window.URL || window.webkitURL\n  const href = URL.createObjectURL(blob)\n  const downA = document.createElement('a')\n  downA.href = href\n  downA.download = fileName\n  downA.click()\n  // 销毁超连接\n  window.URL.revokeObjectURL(href)\n}\n\nconst download = {\n  // 下载 Excel 方法\n  excel: (data: Blob, fileName: string) => {\n    download0(data, fileName, 'application/vnd.ms-excel')\n  },\n  // 下载 Word 方法\n  word: (data: Blob, fileName: string) => {\n    download0(data, fileName, 'application/msword')\n  },\n  // 下载 Zip 方法\n  zip: (data: Blob, fileName: string) => {\n    download0(data, fileName, 'application/zip')\n  },\n  // 下载 Html 方法\n  html: (data: Blob, fileName: string) => {\n    download0(data, fileName, 'text/html')\n  },\n  // 下载 Markdown 方法\n  markdown: (data: Blob, fileName: string) => {\n    download0(data, fileName, 'text/markdown')\n  }\n}\n\nexport default download\n"
  },
  {
    "path": "yshop-drink-vue3/src/utils/filt.ts",
    "content": "export const openWindow = (\n  url: string,\n  opt?: {\n    target?: '_self' | '_blank' | string\n    noopener?: boolean\n    noreferrer?: boolean\n  }\n) => {\n  const { target = '__blank', noopener = true, noreferrer = true } = opt || {}\n  const feature: string[] = []\n\n  noopener && feature.push('noopener=yes')\n  noreferrer && feature.push('noreferrer=yes')\n\n  window.open(url, target, feature.join(','))\n}\n\n/**\n * @description: base64 to blob\n */\nexport const dataURLtoBlob = (base64Buf: string): Blob => {\n  const arr = base64Buf.split(',')\n  const typeItem = arr[0]\n  const mime = typeItem.match(/:(.*?);/)![1]\n  const bstr = window.atob(arr[1])\n  let n = bstr.length\n  const u8arr = new Uint8Array(n)\n  while (n--) {\n    u8arr[n] = bstr.charCodeAt(n)\n  }\n  return new Blob([u8arr], { type: mime })\n}\n\n/**\n * img url to base64\n * @param url\n */\nexport const urlToBase64 = (url: string, mineType?: string): Promise<string> => {\n  return new Promise((resolve, reject) => {\n    let canvas = document.createElement('CANVAS') as Nullable<HTMLCanvasElement>\n    const ctx = canvas!.getContext('2d')\n\n    const img = new Image()\n    img.crossOrigin = ''\n    img.onload = function () {\n      if (!canvas || !ctx) {\n        return reject()\n      }\n      canvas.height = img.height\n      canvas.width = img.width\n      ctx.drawImage(img, 0, 0)\n      const dataURL = canvas.toDataURL(mineType || 'image/png')\n      canvas = null\n      resolve(dataURL)\n    }\n    img.src = url\n  })\n}\n\n/**\n * Download online pictures\n * @param url\n * @param filename\n * @param mime\n * @param bom\n */\nexport const downloadByOnlineUrl = (\n  url: string,\n  filename: string,\n  mime?: string,\n  bom?: BlobPart\n) => {\n  urlToBase64(url).then((base64) => {\n    downloadByBase64(base64, filename, mime, bom)\n  })\n}\n\n/**\n * Download pictures based on base64\n * @param buf\n * @param filename\n * @param mime\n * @param bom\n */\nexport const downloadByBase64 = (buf: string, filename: string, mime?: string, bom?: BlobPart) => {\n  const base64Buf = dataURLtoBlob(buf)\n  downloadByData(base64Buf, filename, mime, bom)\n}\n\n/**\n * Download according to the background interface file stream\n * @param {*} data\n * @param {*} filename\n * @param {*} mime\n * @param {*} bom\n */\nexport const downloadByData = (data: BlobPart, filename: string, mime?: string, bom?: BlobPart) => {\n  const blobData = typeof bom !== 'undefined' ? [bom, data] : [data]\n  const blob = new Blob(blobData, { type: mime || 'application/octet-stream' })\n\n  const blobURL = window.URL.createObjectURL(blob)\n  const tempLink = document.createElement('a')\n  tempLink.style.display = 'none'\n  tempLink.href = blobURL\n  tempLink.setAttribute('download', filename)\n  if (typeof tempLink.download === 'undefined') {\n    tempLink.setAttribute('target', '_blank')\n  }\n  document.body.appendChild(tempLink)\n  tempLink.click()\n  document.body.removeChild(tempLink)\n  window.URL.revokeObjectURL(blobURL)\n}\n\n/**\n * Download file according to file address\n * @param {*} sUrl\n */\nexport const downloadByUrl = ({\n  url,\n  target = '_blank',\n  fileName\n}: {\n  url: string\n  target?: '_self' | '_blank'\n  fileName?: string\n}): boolean => {\n  const isChrome = window.navigator.userAgent.toLowerCase().indexOf('chrome') > -1\n  const isSafari = window.navigator.userAgent.toLowerCase().indexOf('safari') > -1\n\n  if (/(iP)/g.test(window.navigator.userAgent)) {\n    console.error('Your browser does not support download!')\n    return false\n  }\n  if (isChrome || isSafari) {\n    const link = document.createElement('a')\n    link.href = url\n    link.target = target\n\n    if (link.download !== undefined) {\n      link.download = fileName || url.substring(url.lastIndexOf('/') + 1, url.length)\n    }\n\n    if (document.createEvent) {\n      const e = document.createEvent('MouseEvents')\n      e.initEvent('click', true, true)\n      link.dispatchEvent(e)\n      return true\n    }\n  }\n  if (url.indexOf('?') === -1) {\n    url += '?download'\n  }\n\n  openWindow(url, { target })\n  return true\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/utils/formCreate.ts",
    "content": "/**\n * 针对 https://github.com/xaboy/form-create-designer 封装的工具类\n */\n\n// 编码表单 Conf\nexport const encodeConf = (designerRef: object) => {\n  // @ts-ignore\n  return JSON.stringify(designerRef.value.getOption())\n}\n\n// 编码表单 Fields\nexport const encodeFields = (designerRef: object) => {\n  // @ts-ignore\n  const rule = designerRef.value.getRule()\n  const fields: string[] = []\n  rule.forEach((item) => {\n    fields.push(JSON.stringify(item))\n  })\n  return fields\n}\n\n// 解码表单 Fields\nexport const decodeFields = (fields: string[]) => {\n  const rule: object[] = []\n  fields.forEach((item) => {\n    rule.push(JSON.parse(item))\n  })\n  return rule\n}\n\n// 设置表单的 Conf 和 Fields，适用 FcDesigner 场景\nexport const setConfAndFields = (designerRef: object, conf: string, fields: string) => {\n  // @ts-ignore\n  designerRef.value.setOption(JSON.parse(conf))\n  // @ts-ignore\n  designerRef.value.setRule(decodeFields(fields))\n}\n\n// 设置表单的 Conf 和 Fields，适用 form-create 场景\nexport const setConfAndFields2 = (\n  detailPreview: object,\n  conf: string,\n  fields: string[],\n  value?: object\n) => {\n  if (isRef(detailPreview)) {\n    detailPreview = detailPreview.value\n  }\n  // @ts-ignore\n  detailPreview.option = JSON.parse(conf)\n  // @ts-ignore\n  detailPreview.rule = decodeFields(fields)\n  if (value) {\n    // @ts-ignore\n    detailPreview.value = value\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/utils/formRules.ts",
    "content": "const { t } = useI18n()\n\n// 必填项\nexport const required = {\n  required: true,\n  message: t('common.required')\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/utils/formatTime.ts",
    "content": "import dayjs from 'dayjs'\nimport type { TableColumnCtx } from 'element-plus'\n\n/**\n * 日期快捷选项适用于 el-date-picker\n */\nexport const defaultShortcuts = [\n  {\n    text: '今天',\n    value: () => {\n      return new Date()\n    }\n  },\n  {\n    text: '昨天',\n    value: () => {\n      const date = new Date()\n      date.setTime(date.getTime() - 3600 * 1000 * 24)\n      return [date, date]\n    }\n  },\n  {\n    text: '最近七天',\n    value: () => {\n      const date = new Date()\n      date.setTime(date.getTime() - 3600 * 1000 * 24 * 7)\n      return [date, new Date()]\n    }\n  },\n  {\n    text: '最近 30 天',\n    value: () => {\n      const date = new Date()\n      date.setTime(date.getTime() - 3600 * 1000 * 24 * 30)\n      return [date, new Date()]\n    }\n  },\n  {\n    text: '本月',\n    value: () => {\n      const date = new Date()\n      date.setDate(1) // 设置为当前月的第一天\n      return [date, new Date()]\n    }\n  },\n  {\n    text: '今年',\n    value: () => {\n      const date = new Date()\n      return [new Date(`${date.getFullYear()}-01-01`), date]\n    }\n  }\n]\n\n/**\n * 时间日期转换\n * @param date 当前时间，new Date() 格式\n * @param format 需要转换的时间格式字符串\n * @description format 字符串随意，如 `YYYY-mm、YYYY-mm-dd`\n * @description format 季度：\"YYYY-mm-dd HH:MM:SS QQQQ\"\n * @description format 星期：\"YYYY-mm-dd HH:MM:SS WWW\"\n * @description format 几周：\"YYYY-mm-dd HH:MM:SS ZZZ\"\n * @description format 季度 + 星期 + 几周：\"YYYY-mm-dd HH:MM:SS WWW QQQQ ZZZ\"\n * @returns 返回拼接后的时间字符串\n */\nexport function formatDate(date: Date, format?: string): string {\n  // 日期不存在，则返回空\n  if (!date) {\n    return ''\n  }\n  // 日期存在，则进行格式化\n  return date ? dayjs(date).format(format ?? 'YYYY-MM-DD HH:mm:ss') : ''\n}\n\n/**\n * 获取当前的日期+时间\n */\nexport function getNowDateTime() {\n  return dayjs()\n}\n\n/**\n * 获取当前日期是第几周\n * @param dateTime 当前传入的日期值\n * @returns 返回第几周数字值\n */\nexport function getWeek(dateTime: Date): number {\n  const temptTime = new Date(dateTime.getTime())\n  // 周几\n  const weekday = temptTime.getDay() || 7\n  // 周1+5天=周六\n  temptTime.setDate(temptTime.getDate() - weekday + 1 + 5)\n  let firstDay = new Date(temptTime.getFullYear(), 0, 1)\n  const dayOfWeek = firstDay.getDay()\n  let spendDay = 1\n  if (dayOfWeek != 0) spendDay = 7 - dayOfWeek + 1\n  firstDay = new Date(temptTime.getFullYear(), 0, 1 + spendDay)\n  const d = Math.ceil((temptTime.valueOf() - firstDay.valueOf()) / 86400000)\n  return Math.ceil(d / 7)\n}\n\n/**\n * 将时间转换为 `几秒前`、`几分钟前`、`几小时前`、`几天前`\n * @param param 当前时间，new Date() 格式或者字符串时间格式\n * @param format 需要转换的时间格式字符串\n * @description param 10秒：  10 * 1000\n * @description param 1分：   60 * 1000\n * @description param 1小时： 60 * 60 * 1000\n * @description param 24小时：60 * 60 * 24 * 1000\n * @description param 3天：   60 * 60* 24 * 1000 * 3\n * @returns 返回拼接后的时间字符串\n */\nexport function formatPast(param: string | Date, format = 'YYYY-mm-dd HH:MM:SS'): string {\n  // 传入格式处理、存储转换值\n  let t: any, s: number\n  // 获取js 时间戳\n  let time: number = new Date().getTime()\n  // 是否是对象\n  typeof param === 'string' || 'object' ? (t = new Date(param).getTime()) : (t = param)\n  // 当前时间戳 - 传入时间戳\n  time = Number.parseInt(`${time - t}`)\n  if (time < 10000) {\n    // 10秒内\n    return '刚刚'\n  } else if (time < 60000 && time >= 10000) {\n    // 超过10秒少于1分钟内\n    s = Math.floor(time / 1000)\n    return `${s}秒前`\n  } else if (time < 3600000 && time >= 60000) {\n    // 超过1分钟少于1小时\n    s = Math.floor(time / 60000)\n    return `${s}分钟前`\n  } else if (time < 86400000 && time >= 3600000) {\n    // 超过1小时少于24小时\n    s = Math.floor(time / 3600000)\n    return `${s}小时前`\n  } else if (time < 259200000 && time >= 86400000) {\n    // 超过1天少于3天内\n    s = Math.floor(time / 86400000)\n    return `${s}天前`\n  } else {\n    // 超过3天\n    const date = typeof param === 'string' || 'object' ? new Date(param) : param\n    return formatDate(date, format)\n  }\n}\n\n/**\n * 时间问候语\n * @param param 当前时间，new Date() 格式\n * @description param 调用 `formatAxis(new Date())` 输出 `上午好`\n * @returns 返回拼接后的时间字符串\n */\nexport function formatAxis(param: Date): string {\n  const hour: number = new Date(param).getHours()\n  if (hour < 6) return '凌晨好'\n  else if (hour < 9) return '早上好'\n  else if (hour < 12) return '上午好'\n  else if (hour < 14) return '中午好'\n  else if (hour < 17) return '下午好'\n  else if (hour < 19) return '傍晚好'\n  else if (hour < 22) return '晚上好'\n  else return '夜里好'\n}\n\n/**\n * 将毫秒，转换成时间字符串。例如说，xx 分钟\n *\n * @param ms 毫秒\n * @returns {string} 字符串\n */\nexport function formatPast2(ms: number): string {\n  const day = Math.floor(ms / (24 * 60 * 60 * 1000))\n  const hour = Math.floor(ms / (60 * 60 * 1000) - day * 24)\n  const minute = Math.floor(ms / (60 * 1000) - day * 24 * 60 - hour * 60)\n  const second = Math.floor(ms / 1000 - day * 24 * 60 * 60 - hour * 60 * 60 - minute * 60)\n  if (day > 0) {\n    return day + ' 天' + hour + ' 小时 ' + minute + ' 分钟'\n  }\n  if (hour > 0) {\n    return hour + ' 小时 ' + minute + ' 分钟'\n  }\n  if (minute > 0) {\n    return minute + ' 分钟'\n  }\n  if (second > 0) {\n    return second + ' 秒'\n  } else {\n    return 0 + ' 秒'\n  }\n}\n\n/**\n * element plus 的时间 Formatter 实现，使用 YYYY-MM-DD HH:mm:ss 格式\n *\n * @param row 行数据\n * @param column 字段\n * @param cellValue 字段值\n */\nexport function dateFormatter(_row: any, _column: TableColumnCtx<any>, cellValue: any): string {\n  return cellValue ? formatDate(cellValue) : ''\n}\n\n/**\n * element plus 的时间 Formatter 实现，使用 HH:mm:ss 格式\n *\n * @param row 行数据\n * @param column 字段\n * @param cellValue 字段值\n */\nexport function dateFormatter2(_row: any, _column: TableColumnCtx<any>, cellValue: any): string {\n  return cellValue ? formatDate(cellValue, 'HH:mm:ss') : ''\n}\n\n/**\n * element plus 的时间 Formatter 实现，使用 YYYY-MM-DD 格式\n *\n * @param row 行数据\n * @param column 字段\n * @param cellValue 字段值\n */\nexport function dateFormatter3(_row: any, _column: TableColumnCtx<any>, cellValue: any): string {\n  return cellValue ? formatDate(cellValue, 'YYYY-MM-DD') : ''\n}\n\n/**\n * 设置起始日期，时间为00:00:00\n * @param param 传入日期\n * @returns 带时间00:00:00的日期\n */\nexport function beginOfDay(param: Date): Date {\n  return new Date(param.getFullYear(), param.getMonth(), param.getDate(), 0, 0, 0)\n}\n\n/**\n * 设置结束日期，时间为23:59:59\n * @param param 传入日期\n * @returns 带时间23:59:59的日期\n */\nexport function endOfDay(param: Date): Date {\n  return new Date(param.getFullYear(), param.getMonth(), param.getDate(), 23, 59, 59)\n}\n\n/**\n * 计算两个日期间隔天数\n * @param param1 日期1\n * @param param2 日期2\n */\nexport function betweenDay(param1: Date, param2: Date): number {\n  param1 = convertDate(param1)\n  param2 = convertDate(param2)\n  // 计算差值\n  return Math.floor((param2.getTime() - param1.getTime()) / (24 * 3600 * 1000))\n}\n\n/**\n * 日期计算\n * @param param1 日期\n * @param param2 添加的时间\n */\nexport function addTime(param1: Date, param2: number): Date {\n  param1 = convertDate(param1)\n  return new Date(param1.getTime() + param2)\n}\n\n/**\n * 日期转换\n * @param param 日期\n */\nexport function convertDate(param: Date | string): Date {\n  if (typeof param === 'string') {\n    return new Date(param)\n  }\n  return param\n}\n\n/**\n * 指定的两个日期, 是否为同一天\n * @param a 日期 A\n * @param b 日期 B\n */\nexport function isSameDay(a: dayjs.ConfigType, b: dayjs.ConfigType): boolean {\n  if (!a || !b) return false\n\n  const aa = dayjs(a)\n  const bb = dayjs(b)\n  return aa.year() == bb.year() && aa.month() == bb.month() && aa.day() == bb.day()\n}\n\n/**\n * 获取一天的开始时间、截止时间\n * @param date 日期\n * @param days 天数\n */\nexport function getDayRange(\n  date: dayjs.ConfigType,\n  days: number\n): [dayjs.ConfigType, dayjs.ConfigType] {\n  const day = dayjs(date).add(days, 'd')\n  return getDateRange(day, day)\n}\n\n/**\n * 获取最近7天的开始时间、截止时间\n */\nexport function getLast7Days(): [dayjs.ConfigType, dayjs.ConfigType] {\n  const lastWeekDay = dayjs().subtract(7, 'd')\n  const yesterday = dayjs().subtract(1, 'd')\n  return getDateRange(lastWeekDay, yesterday)\n}\n\n/**\n * 获取最近30天的开始时间、截止时间\n */\nexport function getLast30Days(): [dayjs.ConfigType, dayjs.ConfigType] {\n  const lastMonthDay = dayjs().subtract(30, 'd')\n  const yesterday = dayjs().subtract(1, 'd')\n  return getDateRange(lastMonthDay, yesterday)\n}\n\n/**\n * 获取最近1年的开始时间、截止时间\n */\nexport function getLast1Year(): [dayjs.ConfigType, dayjs.ConfigType] {\n  const lastYearDay = dayjs().subtract(1, 'y')\n  const yesterday = dayjs().subtract(1, 'd')\n  return getDateRange(lastYearDay, yesterday)\n}\n\n/**\n * 获取指定日期的开始时间、截止时间\n * @param beginDate 开始日期\n * @param endDate 截止日期\n */\nexport function getDateRange(\n  beginDate: dayjs.ConfigType,\n  endDate: dayjs.ConfigType\n): [string, string] {\n  return [\n    dayjs(beginDate).startOf('d').format('YYYY-MM-DD HH:mm:ss'),\n    dayjs(endDate).endOf('d').format('YYYY-MM-DD HH:mm:ss')\n  ]\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/utils/formatter.ts",
    "content": "import { floatToFixed2 } from '@/utils'\n\n// 格式化金额【分转元】\n// @ts-ignore\nexport const fenToYuanFormat = (_, __, cellValue: any, ___) => {\n  return `￥${floatToFixed2(cellValue)}`\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/utils/index.ts",
    "content": "import { toNumber } from 'lodash-es'\n\n/**\n *\n * @param component 需要注册的组件\n * @param alias 组件别名\n * @returns any\n */\nexport const withInstall = <T>(component: T, alias?: string) => {\n  const comp = component as any\n  comp.install = (app: any) => {\n    app.component(comp.name || comp.displayName, component)\n    if (alias) {\n      app.config.globalProperties[alias] = component\n    }\n  }\n  return component as T & Plugin\n}\n\n/**\n * @param str 需要转下划线的驼峰字符串\n * @returns 字符串下划线\n */\nexport const humpToUnderline = (str: string): string => {\n  return str.replace(/([A-Z])/g, '-$1').toLowerCase()\n}\n\n/**\n * @param str 需要转驼峰的下划线字符串\n * @returns 字符串驼峰\n */\nexport const underlineToHump = (str: string): string => {\n  if (!str) return ''\n  return str.replace(/\\-(\\w)/g, (_, letter: string) => {\n    return letter.toUpperCase()\n  })\n}\n\n/**\n * 驼峰转横杠\n */\nexport const humpToDash = (str: string): string => {\n  return str.replace(/([A-Z])/g, '-$1').toLowerCase()\n}\n\nexport const setCssVar = (prop: string, val: any, dom = document.documentElement) => {\n  dom.style.setProperty(prop, val)\n}\n\n/**\n * 查找数组对象的某个下标\n * @param {Array} ary 查找的数组\n * @param {Functon} fn 判断的方法\n */\n// eslint-disable-next-line\nexport const findIndex = <T = Recordable>(ary: Array<T>, fn: Fn): number => {\n  if (ary.findIndex) {\n    return ary.findIndex(fn)\n  }\n  let index = -1\n  ary.some((item: T, i: number, ary: Array<T>) => {\n    const ret: T = fn(item, i, ary)\n    if (ret) {\n      index = i\n      return ret\n    }\n  })\n  return index\n}\n\nexport const trim = (str: string) => {\n  return str.replace(/(^\\s*)|(\\s*$)/g, '')\n}\n\n/**\n * @param {Date | number | string} time 需要转换的时间\n * @param {String} fmt 需要转换的格式 如 yyyy-MM-dd、yyyy-MM-dd HH:mm:ss\n */\nexport function formatTime(time: Date | number | string, fmt: string) {\n  if (!time) return ''\n  else {\n    const date = new Date(time)\n    const o = {\n      'M+': date.getMonth() + 1,\n      'd+': date.getDate(),\n      'H+': date.getHours(),\n      'm+': date.getMinutes(),\n      's+': date.getSeconds(),\n      'q+': Math.floor((date.getMonth() + 3) / 3),\n      S: date.getMilliseconds()\n    }\n    if (/(y+)/.test(fmt)) {\n      fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length))\n    }\n    for (const k in o) {\n      if (new RegExp('(' + k + ')').test(fmt)) {\n        fmt = fmt.replace(\n          RegExp.$1,\n          RegExp.$1.length === 1 ? o[k] : ('00' + o[k]).substr(('' + o[k]).length)\n        )\n      }\n    }\n    return fmt\n  }\n}\n\n/**\n * 生成随机字符串\n */\nexport function toAnyString() {\n  const str: string = 'xxxxx-xxxxx-4xxxx-yxxxx-xxxxx'.replace(/[xy]/g, (c: string) => {\n    const r: number = (Math.random() * 16) | 0\n    const v: number = c === 'x' ? r : (r & 0x3) | 0x8\n    return v.toString()\n  })\n  return str\n}\n\n/**\n * 首字母大写\n */\nexport function firstUpperCase(str: string) {\n  return str.toLowerCase().replace(/( |^)[a-z]/g, (L) => L.toUpperCase())\n}\n\nexport const generateUUID = () => {\n  if (typeof crypto === 'object') {\n    if (typeof crypto.randomUUID === 'function') {\n      return crypto.randomUUID()\n    }\n    if (typeof crypto.getRandomValues === 'function' && typeof Uint8Array === 'function') {\n      const callback = (c: any) => {\n        const num = Number(c)\n        return (num ^ (crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (num / 4)))).toString(\n          16\n        )\n      }\n      return '10000000-1000-4000-8000-100000000000'.replace(/[018]/g, callback)\n    }\n  }\n  let timestamp = new Date().getTime()\n  let performanceNow =\n    (typeof performance !== 'undefined' && performance.now && performance.now() * 1000) || 0\n  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {\n    let random = Math.random() * 16\n    if (timestamp > 0) {\n      random = (timestamp + random) % 16 | 0\n      timestamp = Math.floor(timestamp / 16)\n    } else {\n      random = (performanceNow + random) % 16 | 0\n      performanceNow = Math.floor(performanceNow / 16)\n    }\n    return (c === 'x' ? random : (random & 0x3) | 0x8).toString(16)\n  })\n}\n\n/**\n * element plus 的文件大小 Formatter 实现\n *\n * @param row 行数据\n * @param column 字段\n * @param cellValue 字段值\n */\n// @ts-ignore\nexport const fileSizeFormatter = (row, column, cellValue) => {\n  const fileSize = cellValue\n  const unitArr = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']\n  const srcSize = parseFloat(fileSize)\n  const index = Math.floor(Math.log(srcSize) / Math.log(1024))\n  const size = srcSize / Math.pow(1024, index)\n  const sizeStr = size.toFixed(2) //保留的小数位数\n  return sizeStr + ' ' + unitArr[index]\n}\n\n/**\n * 将值复制到目标对象，且以目标对象属性为准，例：target: {a:1} source:{a:2,b:3} 结果为：{a:2}\n * @param target 目标对象\n * @param source 源对象\n */\nexport const copyValueToTarget = (target: any, source: any) => {\n  const newObj = Object.assign({}, target, source)\n  // 删除多余属性\n  Object.keys(newObj).forEach((key) => {\n    // 如果不是target中的属性则删除\n    if (Object.keys(target).indexOf(key) === -1) {\n      delete newObj[key]\n    }\n  })\n  // 更新目标对象值\n  Object.assign(target, newObj)\n}\n\n/**\n * 获取链接的参数值\n * @param key 参数键名\n * @param urlStr 链接地址，默认为当前浏览器的地址\n */\nexport const getUrlValue = (key: string, urlStr: string = location.href): string => {\n  if (!urlStr || !key) return ''\n  const url = new URL(decodeURIComponent(urlStr))\n  return url.searchParams.get(key) ?? ''\n}\n\n/**\n * 获取链接的参数值（值类型）\n * @param key 参数键名\n * @param urlStr 链接地址，默认为当前浏览器的地址\n */\nexport const getUrlNumberValue = (key: string, urlStr: string = location.href): number => {\n  return toNumber(getUrlValue(key, urlStr))\n}\n\n/**\n * 构建排序字段\n * @param prop 字段名称\n * @param order 顺序\n */\nexport const buildSortingField = ({ prop, order }) => {\n  return { field: prop, order: order === 'ascending' ? 'asc' : 'desc' }\n}\n\n// ========== NumberUtils 数字方法 ==========\n\n/**\n * 数组求和\n *\n * @param values 数字数组\n * @return 求和结果，默认为 0\n */\nexport const getSumValue = (values: number[]): number => {\n  return values.reduce((prev, curr) => {\n    const value = Number(curr)\n    if (!Number.isNaN(value)) {\n      return prev + curr\n    } else {\n      return prev\n    }\n  }, 0)\n}\n\n// ========== 通用金额方法 ==========\n\n/**\n * 将一个整数转换为分数保留两位小数\n * @param num\n */\nexport const formatToFraction = (num: number | string | undefined): string => {\n  if (typeof num === 'undefined') return '0.00'\n  const parsedNumber = typeof num === 'string' ? parseFloat(num) : num\n  return (parsedNumber / 100.0).toFixed(2)\n}\n\n/**\n * 将一个数转换为 1.00 这样\n * 数据呈现的时候使用\n *\n * @param num 整数\n */\n// TODO @芋艿：看看怎么融合掉\nexport const floatToFixed2 = (num: number | string | undefined): string => {\n  let str = '0.00'\n  if (typeof num === 'undefined') {\n    return str\n  }\n  const f = formatToFraction(num)\n  const decimalPart = f.toString().split('.')[1]\n  const len = decimalPart ? decimalPart.length : 0\n  switch (len) {\n    case 0:\n      str = f.toString() + '.00'\n      break\n    case 1:\n      str = f.toString() + '0'\n      break\n    case 2:\n      str = f.toString()\n      break\n  }\n  return str\n}\n\n/**\n * 将一个分数转换为整数\n * @param num\n */\n// TODO @芋艿：看看怎么融合掉\nexport const convertToInteger = (num: number | string | undefined): number => {\n  if (typeof num === 'undefined') return 0\n  const parsedNumber = typeof num === 'string' ? parseFloat(num) : num\n  // TODO 分转元后还有小数则四舍五入\n  return Math.round(parsedNumber * 100)\n}\n\n/**\n * 元转分\n */\nexport const yuanToFen = (amount: string | number): number => {\n  return convertToInteger(amount)\n}\n\n/**\n * 分转元\n */\nexport const fenToYuan = (price: string | number): string => {\n  return formatToFraction(price)\n}\n\n/**\n * 计算环比\n *\n * @param value 当前数值\n * @param reference 对比数值\n */\nexport const calculateRelativeRate = (value?: number, reference?: number) => {\n  // 防止除0\n  if (!reference) return 0\n\n  return ((100 * ((value || 0) - reference)) / reference).toFixed(0)\n}\n\n// ========== ERP 专属方法 ==========\n\nconst ERP_COUNT_DIGIT = 3\nconst ERP_PRICE_DIGIT = 2\n\n/**\n * 【ERP】格式化 Input 数字\n *\n * 例如说：库存数量\n *\n * @param num 数量\n * @package digit 保留的小数位数\n * @return 格式化后的数量\n */\nexport const erpNumberFormatter = (num: number | string | undefined, digit: number) => {\n  if (num == null) {\n    return ''\n  }\n  if (typeof num === 'string') {\n    num = parseFloat(num)\n  }\n  // 如果非 number，则直接返回空串\n  if (isNaN(num)) {\n    return ''\n  }\n  return num.toFixed(digit)\n}\n\n/**\n * 【ERP】格式化数量，保留三位小数\n *\n * 例如说：库存数量\n *\n * @param num 数量\n * @return 格式化后的数量\n */\nexport const erpCountInputFormatter = (num: number | string | undefined) => {\n  return erpNumberFormatter(num, ERP_COUNT_DIGIT)\n}\n\n// noinspection JSCommentMatchesSignature\n/**\n * 【ERP】格式化数量，保留三位小数\n *\n * @param cellValue 数量\n * @return 格式化后的数量\n */\nexport const erpCountTableColumnFormatter = (_, __, cellValue: any, ___) => {\n  return erpNumberFormatter(cellValue, ERP_COUNT_DIGIT)\n}\n\n/**\n * 【ERP】格式化金额，保留二位小数\n *\n * 例如说：库存数量\n *\n * @param num 数量\n * @return 格式化后的数量\n */\nexport const erpPriceInputFormatter = (num: number | string | undefined) => {\n  return erpNumberFormatter(num, ERP_PRICE_DIGIT)\n}\n\n// noinspection JSCommentMatchesSignature\n/**\n * 【ERP】格式化金额，保留二位小数\n *\n * @param cellValue 数量\n * @return 格式化后的数量\n */\nexport const erpPriceTableColumnFormatter = (_, __, cellValue: any, ___) => {\n  return erpNumberFormatter(cellValue, ERP_PRICE_DIGIT)\n}\n\n/**\n * 【ERP】价格计算，四舍五入保留两位小数\n *\n * @param price 价格\n * @param count 数量\n * @return 总价格。如果有任一为空，则返回 undefined\n */\nexport const erpPriceMultiply = (price: number, count: number) => {\n  if (price == null || count == null) {\n    return undefined\n  }\n  return parseFloat((price * count).toFixed(ERP_PRICE_DIGIT))\n}\n\n/**\n * 【ERP】百分比计算，四舍五入保留两位小数\n *\n * 如果 total 为 0，则返回 0\n *\n * @param value 当前值\n * @param total 总值\n */\nexport const erpCalculatePercentage = (value: number, total: number) => {\n  if (total === 0) return 0\n  return ((value / total) * 100).toFixed(2)\n}\n\n/**\n * 适配 echarts map 的地名\n *\n * @param areaName 地区名称\n */\nexport const areaReplace = (areaName: string) => {\n  if (!areaName) {\n    return areaName\n  }\n  return areaName\n    .replace('维吾尔自治区', '')\n    .replace('壮族自治区', '')\n    .replace('回族自治区', '')\n    .replace('自治区', '')\n    .replace('省', '')\n}\n\n/**\n * 解析 JSON 字符串\n *\n * @param str\n */\nexport function jsonParse(str: string) {\n  try {\n    return JSON.parse(str)\n  } catch (e) {\n    console.error(`str[${str}] 不是一个 JSON 字符串`)\n    return ''\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/utils/is.ts",
    "content": "// copy to vben-admin\n\nconst toString = Object.prototype.toString\n\nexport const is = (val: unknown, type: string) => {\n  return toString.call(val) === `[object ${type}]`\n}\n\nexport const isDef = <T = unknown>(val?: T): val is T => {\n  return typeof val !== 'undefined'\n}\n\nexport const isUnDef = <T = unknown>(val?: T): val is T => {\n  return !isDef(val)\n}\n\nexport const isObject = (val: any): val is Record<any, any> => {\n  return val !== null && is(val, 'Object')\n}\n\nexport const isEmpty = <T = unknown>(val: T): val is T => {\n  if (val === null) {\n    return true\n  }\n  if (isArray(val) || isString(val)) {\n    return val.length === 0\n  }\n\n  if (val instanceof Map || val instanceof Set) {\n    return val.size === 0\n  }\n\n  if (isObject(val)) {\n    return Object.keys(val).length === 0\n  }\n\n  return false\n}\n\nexport const isDate = (val: unknown): val is Date => {\n  return is(val, 'Date')\n}\n\nexport const isNull = (val: unknown): val is null => {\n  return val === null\n}\n\nexport const isNullAndUnDef = (val: unknown): val is null | undefined => {\n  return isUnDef(val) && isNull(val)\n}\n\nexport const isNullOrUnDef = (val: unknown): val is null | undefined => {\n  return isUnDef(val) || isNull(val)\n}\n\nexport const isNumber = (val: unknown): val is number => {\n  return is(val, 'Number')\n}\n\nexport const isPromise = <T = any>(val: unknown): val is Promise<T> => {\n  return is(val, 'Promise') && isObject(val) && isFunction(val.then) && isFunction(val.catch)\n}\n\nexport const isString = (val: unknown): val is string => {\n  return is(val, 'String')\n}\n\nexport const isFunction = (val: unknown): val is Function => {\n  return typeof val === 'function'\n}\n\nexport const isBoolean = (val: unknown): val is boolean => {\n  return is(val, 'Boolean')\n}\n\nexport const isRegExp = (val: unknown): val is RegExp => {\n  return is(val, 'RegExp')\n}\n\nexport const isArray = (val: any): val is Array<any> => {\n  return val && Array.isArray(val)\n}\n\nexport const isWindow = (val: any): val is Window => {\n  return typeof window !== 'undefined' && is(val, 'Window')\n}\n\nexport const isElement = (val: unknown): val is Element => {\n  return isObject(val) && !!val.tagName\n}\n\nexport const isMap = (val: unknown): val is Map<any, any> => {\n  return is(val, 'Map')\n}\n\nexport const isServer = typeof window === 'undefined'\n\nexport const isClient = !isServer\n\nexport const isUrl = (path: string): boolean => {\n  const reg =\n    /(((^https?:(?:\\/\\/)?)(?:[-:&=\\+\\$,\\w]+@)?[A-Za-z0-9.-]+(?::\\d+)?|(?:www.|[-:&=\\+\\$,\\w]+@)[A-Za-z0-9.-]+)((?:\\/[\\+~%\\/.\\w-_]*)?\\??(?:[-\\+=&%@.\\w_]*)#?(?:[\\w]*))?)$/\n  return reg.test(path)\n}\n\nexport const isDark = (): boolean => {\n  return window.matchMedia('(prefers-color-scheme: dark)').matches\n}\n\n// 是否是图片链接\nexport const isImgPath = (path: string): boolean => {\n  return /(https?:\\/\\/|data:image\\/).*?\\.(png|jpg|jpeg|gif|svg|webp|ico)/gi.test(path)\n}\n\nexport const isEmptyVal = (val: any): boolean => {\n  return val === '' || val === null || val === undefined\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/utils/jsencrypt.ts",
    "content": "import { JSEncrypt } from 'jsencrypt'\n\n// 密钥对生成 http://web.chacuo.net/netrsakeypair\n\nconst publicKey =\n  'MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdH\\n' +\n  'nzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ=='\n\nconst privateKey =\n  'MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAqhHyZfSsYourNxaY\\n' +\n  '7Nt+PrgrxkiA50efORdI5U5lsW79MmFnusUA355oaSXcLhu5xxB38SMSyP2KvuKN\\n' +\n  'PuH3owIDAQABAkAfoiLyL+Z4lf4Myxk6xUDgLaWGximj20CUf+5BKKnlrK+Ed8gA\\n' +\n  'kM0HqoTt2UZwA5E2MzS4EI2gjfQhz5X28uqxAiEA3wNFxfrCZlSZHb0gn2zDpWow\\n' +\n  'cSxQAgiCstxGUoOqlW8CIQDDOerGKH5OmCJ4Z21v+F25WaHYPxCFMvwxpcw99Ecv\\n' +\n  'DQIgIdhDTIqD2jfYjPTY8Jj3EDGPbH2HHuffvflECt3Ek60CIQCFRlCkHpi7hthh\\n' +\n  'YhovyloRYsM+IS9h/0BzlEAuO0ktMQIgSPT3aFAgJYwKpqRYKlLDVcflZFCKY7u3\\n' +\n  'UP8iWi1Qw0Y='\n\n// 加密\nexport const encrypt = (txt: string) => {\n  const encryptor = new JSEncrypt()\n  encryptor.setPublicKey(publicKey) // 设置公钥\n  return encryptor.encrypt(txt) // 对数据进行加密\n}\n\n// 解密\nexport const decrypt = (txt: string) => {\n  const encryptor = new JSEncrypt()\n  encryptor.setPrivateKey(privateKey) // 设置私钥\n  return encryptor.decrypt(txt) // 对数据进行解密\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/utils/permission.ts",
    "content": "import { CACHE_KEY, useCache } from '@/hooks/web/useCache'\n\nconst { t } = useI18n() // 国际化\n\n/**\n * 字符权限校验\n * @param {Array} value 校验值\n * @returns {Boolean}\n */\nexport function checkPermi(value: string[]) {\n  if (value && value instanceof Array && value.length > 0) {\n    const { wsCache } = useCache()\n    const permissionDatas = value\n    const all_permission = '*:*:*'\n    const permissions = wsCache.get(CACHE_KEY.USER).permissions\n    const hasPermission = permissions.some((permission) => {\n      return all_permission === permission || permissionDatas.includes(permission)\n    })\n    return !!hasPermission\n  } else {\n    console.error(t('permission.hasPermission'))\n    return false\n  }\n}\n\n/**\n * 角色权限校验\n * @param {string[]} value 校验值\n * @returns {Boolean}\n */\nexport function checkRole(value: string[]) {\n  if (value && value instanceof Array && value.length > 0) {\n    const { wsCache } = useCache()\n    const permissionRoles = value\n    const super_admin = 'admin'\n    const roles = wsCache.get(CACHE_KEY.USER).roles\n    const hasRole = roles.some((role) => {\n      return super_admin === role || permissionRoles.includes(role)\n    })\n    return !!hasRole\n  } else {\n    console.error(t('permission.hasRole'))\n    return false\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/utils/propTypes.ts",
    "content": "import { VueTypeValidableDef, VueTypesInterface, createTypes, toValidableType } from 'vue-types'\nimport { CSSProperties } from 'vue'\n\ntype PropTypes = VueTypesInterface & {\n  readonly style: VueTypeValidableDef<CSSProperties>\n}\nconst newPropTypes = createTypes({\n  func: undefined,\n  bool: undefined,\n  string: undefined,\n  number: undefined,\n  object: undefined,\n  integer: undefined\n}) as PropTypes\n\nclass propTypes extends newPropTypes {\n  static get style() {\n    return toValidableType('style', {\n      type: [String, Object]\n    })\n  }\n}\n\nexport { propTypes }\n"
  },
  {
    "path": "yshop-drink-vue3/src/utils/routerHelper.ts",
    "content": "import type { RouteLocationNormalized, Router, RouteRecordNormalized } from 'vue-router'\nimport { createRouter, createWebHashHistory, RouteRecordRaw } from 'vue-router'\nimport { isUrl } from '@/utils/is'\nimport { cloneDeep, omit } from 'lodash-es'\nimport qs from 'qs'\n\nconst modules = import.meta.glob('../views/**/*.{vue,tsx}')\n/**\n * 注册一个异步组件\n * @param componentPath 例:/bpm/oa/leave/detail\n */\nexport const registerComponent = (componentPath: string) => {\n  for (const item in modules) {\n    if (item.includes(componentPath)) {\n      // 使用异步组件的方式来动态加载组件\n      // @ts-ignore\n      return defineAsyncComponent(modules[item])\n    }\n  }\n}\n/* Layout */\nexport const Layout = () => import('@/layout/Layout.vue')\n\nexport const getParentLayout = () => {\n  return () =>\n    new Promise((resolve) => {\n      resolve({\n        name: 'ParentLayout'\n      })\n    })\n}\n\n// 按照路由中meta下的rank等级升序来排序路由\nexport const ascending = (arr: any[]) => {\n  arr.forEach((v) => {\n    if (v?.meta?.rank === null) v.meta.rank = undefined\n    if (v?.meta?.rank === 0) {\n      if (v.name !== 'home' && v.path !== '/') {\n        console.warn('rank only the home page can be 0')\n      }\n    }\n  })\n  return arr.sort((a: { meta: { rank: number } }, b: { meta: { rank: number } }) => {\n    return a?.meta?.rank - b?.meta?.rank\n  })\n}\n\nexport const getRawRoute = (route: RouteLocationNormalized): RouteLocationNormalized => {\n  if (!route) return route\n  const { matched, ...opt } = route\n  return {\n    ...opt,\n    matched: (matched\n      ? matched.map((item) => ({\n          meta: item.meta,\n          name: item.name,\n          path: item.path\n        }))\n      : undefined) as RouteRecordNormalized[]\n  }\n}\n\n// 后端控制路由生成\nexport const generateRoute = (routes: AppCustomRouteRecordRaw[]): AppRouteRecordRaw[] => {\n  const res: AppRouteRecordRaw[] = []\n  const modulesRoutesKeys = Object.keys(modules)\n  for (const route of routes) {\n    // 1. 生成 meta 菜单元数据\n    const meta = {\n      title: route.name,\n      icon: route.icon,\n      hidden: !route.visible,\n      noCache: !route.keepAlive,\n      alwaysShow:\n        route.children &&\n        route.children.length === 1 &&\n        (route.alwaysShow !== undefined ? route.alwaysShow : true)\n    } as any\n    // 特殊逻辑：如果后端配置的 MenuDO.component 包含 ?，则表示需要传递参数\n    // 此时，我们需要解析参数，并且将参数放到 meta.query 中\n    // 这样，后续在 Vue 文件中，可以通过 const { currentRoute } = useRouter() 中，通过 meta.query 获取到参数\n    if (route.component && route.component.indexOf('?') > -1) {\n      const query = route.component.split('?')[1]\n      route.component = route.component.split('?')[0]\n      meta.query = qs.parse(query)\n    }\n\n    // 2. 生成 data（AppRouteRecordRaw）\n    // 路由地址转首字母大写驼峰，作为路由名称，适配keepAlive\n    let data: AppRouteRecordRaw = {\n      path: route.path.indexOf('?') > -1 ? route.path.split('?')[0] : route.path,\n      name:\n        route.componentName && route.componentName.length > 0\n          ? route.componentName\n          : toCamelCase(route.path, true),\n      redirect: route.redirect,\n      meta: meta\n    }\n    //处理顶级非目录路由\n    if (!route.children && route.parentId == 0 && route.component) {\n      data.component = Layout\n      data.meta = {}\n      data.name = toCamelCase(route.path, true) + 'Parent'\n      data.redirect = ''\n      meta.alwaysShow = true\n      const childrenData: AppRouteRecordRaw = {\n        path: '',\n        name:\n          route.componentName && route.componentName.length > 0\n            ? route.componentName\n            : toCamelCase(route.path, true),\n        redirect: route.redirect,\n        meta: meta\n      }\n      const index = route?.component\n        ? modulesRoutesKeys.findIndex((ev) => ev.includes(route.component))\n        : modulesRoutesKeys.findIndex((ev) => ev.includes(route.path))\n      childrenData.component = modules[modulesRoutesKeys[index]]\n      data.children = [childrenData]\n    } else {\n      // 目录\n      if (route.children) {\n        data.component = Layout\n        data.redirect = getRedirect(route.path, route.children)\n        // 外链\n      } else if (isUrl(route.path)) {\n        data = {\n          path: '/external-link',\n          component: Layout,\n          meta: {\n            name: route.name\n          },\n          children: [data]\n        } as AppRouteRecordRaw\n        // 菜单\n      } else {\n        // 对后端传component组件路径和不传做兼容（如果后端传component组件路径，那么path可以随便写，如果不传，component组件路径会根path保持一致）\n        const index = route?.component\n          ? modulesRoutesKeys.findIndex((ev) => ev.includes(route.component))\n          : modulesRoutesKeys.findIndex((ev) => ev.includes(route.path))\n        data.component = modules[modulesRoutesKeys[index]]\n      }\n      if (route.children) {\n        data.children = generateRoute(route.children)\n      }\n    }\n    res.push(data as AppRouteRecordRaw)\n  }\n  return res\n}\nexport const getRedirect = (parentPath: string, children: AppCustomRouteRecordRaw[]) => {\n  if (!children || children.length == 0) {\n    return parentPath\n  }\n  const path = generateRoutePath(parentPath, children[0].path)\n  // 递归子节点\n  if (children[0].children) return getRedirect(path, children[0].children)\n}\nconst generateRoutePath = (parentPath: string, path: string) => {\n  if (parentPath.endsWith('/')) {\n    parentPath = parentPath.slice(0, -1) // 移除默认的 /\n  }\n  if (!path.startsWith('/')) {\n    path = '/' + path\n  }\n  return parentPath + path\n}\nexport const pathResolve = (parentPath: string, path: string) => {\n  if (isUrl(path)) return path\n  const childPath = path.startsWith('/') || !path ? path : `/${path}`\n  return `${parentPath}${childPath}`.replace(/\\/\\//g, '/')\n}\n\n// 路由降级\nexport const flatMultiLevelRoutes = (routes: AppRouteRecordRaw[]) => {\n  const modules: AppRouteRecordRaw[] = cloneDeep(routes)\n  for (let index = 0; index < modules.length; index++) {\n    const route = modules[index]\n    if (!isMultipleRoute(route)) {\n      continue\n    }\n    promoteRouteLevel(route)\n  }\n  return modules\n}\n\n// 层级是否大于2\nconst isMultipleRoute = (route: AppRouteRecordRaw) => {\n  if (!route || !Reflect.has(route, 'children') || !route.children?.length) {\n    return false\n  }\n\n  const children = route.children\n\n  let flag = false\n  for (let index = 0; index < children.length; index++) {\n    const child = children[index]\n    if (child.children?.length) {\n      flag = true\n      break\n    }\n  }\n  return flag\n}\n\n// 生成二级路由\nconst promoteRouteLevel = (route: AppRouteRecordRaw) => {\n  let router: Router | null = createRouter({\n    routes: [route as RouteRecordRaw],\n    history: createWebHashHistory()\n  })\n\n  const routes = router.getRoutes()\n  addToChildren(routes, route.children || [], route)\n  router = null\n\n  route.children = route.children?.map((item) => omit(item, 'children'))\n}\n\n// 添加所有子菜单\nconst addToChildren = (\n  routes: RouteRecordNormalized[],\n  children: AppRouteRecordRaw[],\n  routeModule: AppRouteRecordRaw\n) => {\n  for (let index = 0; index < children.length; index++) {\n    const child = children[index]\n    const route = routes.find((item) => item.name === child.name)\n    if (!route) {\n      continue\n    }\n    routeModule.children = routeModule.children || []\n    if (!routeModule.children.find((item) => item.name === route.name)) {\n      routeModule.children?.push(route as unknown as AppRouteRecordRaw)\n    }\n    if (child.children?.length) {\n      addToChildren(routes, child.children, routeModule)\n    }\n  }\n}\nconst toCamelCase = (str: string, upperCaseFirst: boolean) => {\n  str = (str || '')\n    .replace(/-(.)/g, function (group1: string) {\n      return group1.toUpperCase()\n    })\n    .replaceAll('-', '')\n\n  if (upperCaseFirst && str) {\n    str = str.charAt(0).toUpperCase() + str.slice(1)\n  }\n\n  return str\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/utils/tree.ts",
    "content": "interface TreeHelperConfig {\n  id: string\n  children: string\n  pid: string\n}\n\nconst DEFAULT_CONFIG: TreeHelperConfig = {\n  id: 'id',\n  children: 'children',\n  pid: 'pid'\n}\nexport const defaultProps = {\n  children: 'children',\n  label: 'name',\n  value: 'id',\n  isLeaf: 'leaf',\n  emitPath: false // 用于 cascader 组件：在选中节点改变时，是否返回由该节点所在的各级菜单的值所组成的数组，若设置 false，则只返回该节点的值\n}\n\nconst getConfig = (config: Partial<TreeHelperConfig>) => Object.assign({}, DEFAULT_CONFIG, config)\n\n// tree from list\nexport const listToTree = <T = any>(list: any[], config: Partial<TreeHelperConfig> = {}): T[] => {\n  const conf = getConfig(config) as TreeHelperConfig\n  const nodeMap = new Map()\n  const result: T[] = []\n  const { id, children, pid } = conf\n\n  for (const node of list) {\n    node[children] = node[children] || []\n    nodeMap.set(node[id], node)\n  }\n  for (const node of list) {\n    const parent = nodeMap.get(node[pid])\n    ;(parent ? parent.children : result).push(node)\n  }\n  return result\n}\n\nexport const treeToList = <T = any>(tree: any, config: Partial<TreeHelperConfig> = {}): T => {\n  config = getConfig(config)\n  const { children } = config\n  const result: any = [...tree]\n  for (let i = 0; i < result.length; i++) {\n    if (!result[i][children!]) continue\n    result.splice(i + 1, 0, ...result[i][children!])\n  }\n  return result\n}\n\nexport const findNode = <T = any>(\n  tree: any,\n  func: Fn,\n  config: Partial<TreeHelperConfig> = {}\n): T | null => {\n  config = getConfig(config)\n  const { children } = config\n  const list = [...tree]\n  for (const node of list) {\n    if (func(node)) return node\n    node[children!] && list.push(...node[children!])\n  }\n  return null\n}\n\nexport const findNodeAll = <T = any>(\n  tree: any,\n  func: Fn,\n  config: Partial<TreeHelperConfig> = {}\n): T[] => {\n  config = getConfig(config)\n  const { children } = config\n  const list = [...tree]\n  const result: T[] = []\n  for (const node of list) {\n    func(node) && result.push(node)\n    node[children!] && list.push(...node[children!])\n  }\n  return result\n}\n\nexport const findPath = <T = any>(\n  tree: any,\n  func: Fn,\n  config: Partial<TreeHelperConfig> = {}\n): T | T[] | null => {\n  config = getConfig(config)\n  const path: T[] = []\n  const list = [...tree]\n  const visitedSet = new Set()\n  const { children } = config\n  while (list.length) {\n    const node = list[0]\n    if (visitedSet.has(node)) {\n      path.pop()\n      list.shift()\n    } else {\n      visitedSet.add(node)\n      node[children!] && list.unshift(...node[children!])\n      path.push(node)\n      if (func(node)) {\n        return path\n      }\n    }\n  }\n  return null\n}\n\nexport const findPathAll = (tree: any, func: Fn, config: Partial<TreeHelperConfig> = {}) => {\n  config = getConfig(config)\n  const path: any[] = []\n  const list = [...tree]\n  const result: any[] = []\n  const visitedSet = new Set(),\n    { children } = config\n  while (list.length) {\n    const node = list[0]\n    if (visitedSet.has(node)) {\n      path.pop()\n      list.shift()\n    } else {\n      visitedSet.add(node)\n      node[children!] && list.unshift(...node[children!])\n      path.push(node)\n      func(node) && result.push([...path])\n    }\n  }\n  return result\n}\n\nexport const filter = <T = any>(\n  tree: T[],\n  func: (n: T) => boolean,\n  config: Partial<TreeHelperConfig> = {}\n): T[] => {\n  config = getConfig(config)\n  const children = config.children as string\n\n  function listFilter(list: T[]) {\n    return list\n      .map((node: any) => ({ ...node }))\n      .filter((node) => {\n        node[children] = node[children] && listFilter(node[children])\n        return func(node) || (node[children] && node[children].length)\n      })\n  }\n\n  return listFilter(tree)\n}\n\nexport const forEach = <T = any>(\n  tree: T[],\n  func: (n: T) => any,\n  config: Partial<TreeHelperConfig> = {}\n): void => {\n  config = getConfig(config)\n  const list: any[] = [...tree]\n  const { children } = config\n  for (let i = 0; i < list.length; i++) {\n    // func 返回true就终止遍历，避免大量节点场景下无意义循环，引起浏览器卡顿\n    if (func(list[i])) {\n      return\n    }\n    children && list[i][children] && list.splice(i + 1, 0, ...list[i][children])\n  }\n}\n\n/**\n * @description: Extract tree specified structure\n */\nexport const treeMap = <T = any>(\n  treeData: T[],\n  opt: { children?: string; conversion: Fn }\n): T[] => {\n  return treeData.map((item) => treeMapEach(item, opt))\n}\n\n/**\n * @description: Extract tree specified structure\n */\nexport const treeMapEach = (\n  data: any,\n  { children = 'children', conversion }: { children?: string; conversion: Fn }\n) => {\n  const haveChildren = Array.isArray(data[children]) && data[children].length > 0\n  const conversionData = conversion(data) || {}\n  if (haveChildren) {\n    return {\n      ...conversionData,\n      [children]: data[children].map((i: number) =>\n        treeMapEach(i, {\n          children,\n          conversion\n        })\n      )\n    }\n  } else {\n    return {\n      ...conversionData\n    }\n  }\n}\n\n/**\n * 递归遍历树结构\n * @param treeDatas 树\n * @param callBack 回调\n * @param parentNode 父节点\n */\nexport const eachTree = (treeDatas: any[], callBack: Fn, parentNode = {}) => {\n  treeDatas.forEach((element) => {\n    const newNode = callBack(element, parentNode) || element\n    if (element.children) {\n      eachTree(element.children, callBack, newNode)\n    }\n  })\n}\n\n/**\n * 构造树型结构数据\n * @param {*} data 数据源\n * @param {*} id id字段 默认 'id'\n * @param {*} parentId 父节点字段 默认 'parentId'\n * @param {*} children 孩子节点字段 默认 'children'\n */\nexport const handleTree = (data: any[], id?: string, parentId?: string, children?: string) => {\n  if (!Array.isArray(data)) {\n    console.warn('data must be an array')\n    return []\n  }\n  const config = {\n    id: id || 'id',\n    parentId: parentId || 'parentId',\n    childrenList: children || 'children'\n  }\n\n  const childrenListMap = {}\n  const nodeIds = {}\n  const tree: any[] = []\n\n  for (const d of data) {\n    const parentId = d[config.parentId]\n    if (childrenListMap[parentId] == null) {\n      childrenListMap[parentId] = []\n    }\n    nodeIds[d[config.id]] = d\n    childrenListMap[parentId].push(d)\n  }\n\n  for (const d of data) {\n    const parentId = d[config.parentId]\n    if (nodeIds[parentId] == null) {\n      tree.push(d)\n    }\n  }\n\n  for (const t of tree) {\n    adaptToChildrenList(t)\n  }\n\n  function adaptToChildrenList(o) {\n    if (childrenListMap[o[config.id]] !== null) {\n      o[config.childrenList] = childrenListMap[o[config.id]]\n    }\n    if (o[config.childrenList]) {\n      for (const c of o[config.childrenList]) {\n        adaptToChildrenList(c)\n      }\n    }\n  }\n\n  return tree\n}\n\n/**\n * 构造树型结构数据\n * @param {*} data 数据源\n * @param {*} id id字段 默认 'id'\n * @param {*} parentId 父节点字段 默认 'parentId'\n * @param {*} children 孩子节点字段 默认 'children'\n * @param {*} rootId 根Id 默认 0\n */\n// @ts-ignore\nexport const handleTree2 = (data, id, parentId, children, rootId) => {\n  id = id || 'id'\n  parentId = parentId || 'parentId'\n  // children = children || 'children'\n  rootId =\n    rootId ||\n    Math.min(\n      ...data.map((item) => {\n        return item[parentId]\n      })\n    ) ||\n    0\n  // 对源数据深度克隆\n  const cloneData = JSON.parse(JSON.stringify(data))\n  // 循环所有项\n  const treeData = cloneData.filter((father) => {\n    const branchArr = cloneData.filter((child) => {\n      // 返回每一项的子级数组\n      return father[id] === child[parentId]\n    })\n    branchArr.length > 0 ? (father.children = branchArr) : ''\n    // 返回第一层\n    return father[parentId] === rootId\n  })\n  return treeData !== '' ? treeData : data\n}\n\n/**\n * 校验选中的节点，是否为指定 level\n *\n * @param tree 要操作的树结构数据\n * @param nodeId 需要判断在什么层级的数据\n * @param level 检查的级别, 默认检查到二级\n * @return true 是；false 否\n */\nexport const checkSelectedNode = (tree: any[], nodeId: any, level = 2): boolean => {\n  if (typeof tree === 'undefined' || !Array.isArray(tree) || tree.length === 0) {\n    console.warn('tree must be an array')\n    return false\n  }\n\n  // 校验是否是一级节点\n  if (tree.some((item) => item.id === nodeId)) {\n    return false\n  }\n\n  // 递归计数\n  let count = 1\n\n  // 深层次校验\n  function performAThoroughValidation(arr: any[]): boolean {\n    count += 1\n    for (const item of arr) {\n      if (item.id === nodeId) {\n        return true\n      } else if (typeof item.children !== 'undefined' && item.children.length !== 0) {\n        if (performAThoroughValidation(item.children)) {\n          return true\n        }\n      }\n    }\n    return false\n  }\n\n  for (const item of tree) {\n    count = 1\n    if (performAThoroughValidation(item.children)) {\n      // 找到后对比是否是期望的层级\n      if (count >= level) {\n        return true\n      }\n    }\n  }\n\n  return false\n}\n\n/**\n * 获取节点的完整结构\n * @param tree 树数据\n * @param nodeId 节点 id\n */\nexport const treeToString = (tree: any[], nodeId) => {\n  if (typeof tree === 'undefined' || !Array.isArray(tree) || tree.length === 0) {\n    console.warn('tree must be an array')\n    return ''\n  }\n  // 校验是否是一级节点\n  const node = tree.find((item) => item.id === nodeId)\n  if (typeof node !== 'undefined') {\n    return node.name\n  }\n  let str = ''\n\n  function performAThoroughValidation(arr) {\n    for (const item of arr) {\n      if (item.id === nodeId) {\n        str += ` / ${item.name}`\n        return true\n      } else if (typeof item.children !== 'undefined' && item.children.length !== 0) {\n        str += ` / ${item.name}`\n        if (performAThoroughValidation(item.children)) {\n          return true\n        }\n      }\n    }\n    return false\n  }\n\n  for (const item of tree) {\n    str = `${item.name}`\n    if (performAThoroughValidation(item.children)) {\n      break\n    }\n  }\n  return str\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/utils/tsxHelper.ts",
    "content": "import { Slots } from 'vue'\nimport { isFunction } from '@/utils/is'\n\nexport const getSlot = (slots: Slots, slot = 'default', data?: Recordable) => {\n  // Reflect.has 判断一个对象是否存在某个属性\n  if (!slots || !Reflect.has(slots, slot)) {\n    return null\n  }\n  if (!isFunction(slots[slot])) {\n    console.error(`${slot} is not a function!`)\n    return null\n  }\n  const slotFn = slots[slot]\n  if (!slotFn) return null\n  return slotFn(data)\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/Error/403.vue",
    "content": "<template>\n  <Error type=\"403\" @error-click=\"push('/')\" />\n</template>\n<script lang=\"ts\" setup>\ndefineOptions({ name: 'Error403' })\n\nconst { push } = useRouter()\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/Error/404.vue",
    "content": "<template>\n  <Error @error-click=\"push('/')\" />\n</template>\n<script lang=\"ts\" setup>\ndefineOptions({ name: 'Error404' })\nconst { push } = useRouter()\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/Error/500.vue",
    "content": "<template>\n  <Error type=\"500\" @error-click=\"push('/')\" />\n</template>\n<script lang=\"ts\" setup>\ndefineOptions({ name: 'Error500' })\nconst { push } = useRouter()\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/Home/Index.vue",
    "content": "<template>\n  <div>\n    <div class=\"divBox\">\n      <el-row :gutter=\"24\" class=\"baseInfo\">\n        <el-col :xs=\"12\" :sm=\"12\" :lg=\"6\" class=\"ivu-mb\">\n          <el-card :bordered=\"false\" dis-hover :padding=\"12\">\n            <template #header>\n              <div  class=\"acea-row row-between-wrapper\">\n                <span>会员总数</span>\n                <el-tag type=\"success\">全平台</el-tag>\n              </div>\n            </template>\n            <div class=\"content\" v-if=\"count\">\n              <span class=\"content-number spBlock mb15\"><count-to :start-val=\"0\" :end-val=\"count.userCount\" :duration=\"2600\" class=\"card-panel-num\" /></span>\n              <el-divider />\n              <div class=\"acea-row row-between-wrapper\">\n                <span class=\"content-time\">今日订单数</span>\n                <span>{{ count.todayCount }} 单</span>\n              </div>\n            </div>\n          </el-card>\n        </el-col>\n        <el-col :xs=\"12\" :sm=\"12\" :lg=\"6\" class=\"ivu-mb\">\n          <el-card :bordered=\"false\" dis-hover :padding=\"12\">\n            <template #header>\n              <div class=\"acea-row row-between-wrapper\">\n                <span>订单总数</span>\n                <el-tag type=\"success\">全平台</el-tag>\n              </div>\n            </template>\n            <div class=\"content\" v-if=\"count\">\n              <span class=\"content-number spBlock mb15\"><count-to :start-val=\"0\" :end-val=\"count.orderCount\" :duration=\"3000\" class=\"card-panel-num\" /></span>\n              <el-divider />\n              <div class=\"acea-row row-between-wrapper\">\n                <span class=\"content-time\">昨日订单数</span>\n                <span>{{ count.proCount }} 单</span>\n              </div>\n            </div>\n          </el-card>\n        </el-col>\n        <el-col :xs=\"12\" :sm=\"12\" :lg=\"6\" class=\"ivu-mb\">\n          <el-card :bordered=\"false\" dis-hover :padding=\"12\">\n            <template #header>\n              <div  class=\"acea-row row-between-wrapper\">\n                <span>总金额</span>\n                <el-tag type=\"success\">全平台</el-tag>\n              </div>\n            </template>\n            <div class=\"content\" v-if=\"count\">\n              <span class=\"content-number spBlock mb15\"><count-to :start-val=\"0\" :end-val=\"count.priceCount\" :duration=\"3200\" class=\"card-panel-num\" /></span>\n              <el-divider />\n              <div class=\"acea-row row-between-wrapper\">\n                <span class=\"content-time\">近七天订单数</span>\n                <span>{{ count.lastWeekCount }} 单</span>\n              </div>\n            </div>\n          </el-card>\n        </el-col>\n        <el-col :xs=\"12\" :sm=\"12\" :lg=\"6\" class=\"ivu-mb\">\n          <el-card :bordered=\"false\" dis-hover :padding=\"12\">\n            <template #header>\n              <div class=\"acea-row row-between-wrapper\">\n                <span>商品总数</span>\n                <el-tag type=\"success\">全平台</el-tag>\n              </div>\n            </template>\n            <div class=\"content\" v-if=\"count\">\n              <span class=\"content-number spBlock mb15\"><count-to :start-val=\"0\" :end-val=\"count.goodsCount\" :duration=\"3600\" class=\"card-panel-num\" /></span>\n              <el-divider />\n              <div class=\"acea-row row-between-wrapper\">\n                <span class=\"content-time\">本月订单数</span>\n                <span>{{ count.monthCount }} 单</span>\n              </div>\n            </div>\n          </el-card>\n        </el-col>\n      </el-row>\n    </div>\n    <PanelGroupT />\n    <div class=\"divBox\">\n      <el-card shadow=\"never\" class=\"mt-10px\">\n        <template #header>\n          <div class=\"flex justify-between h-3\">\n            <span>{{ t('workplace.notice') }}</span>\n            <el-link type=\"primary\" :underline=\"false\">{{ t('action.more') }}</el-link>\n          </div>\n        </template>\n        <el-skeleton :loading=\"loading\" animated>\n          <div v-for=\"(item, index) in notice\" :key=\"`dynamics-${index}`\">\n            <div class=\"flex items-center\">\n              <img :src=\"avatar\" alt=\"\" class=\"w-35px h-35px rounded-[50%] mr-20px\" />\n              <div>\n                <div class=\"text-14px\">\n                    {{ item.type == 1 ? '通知' : '公告' }} : {{ item.title }}\n                </div>\n                <div class=\"mt-15px text-12px text-gray-400\">\n                  {{ formatTime(item.createTime, 'yyyy-MM-dd') }}\n                </div>\n              </div>\n            </div>\n            <el-divider />\n          </div>\n        </el-skeleton>\n      </el-card>\n    </div>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport * as StoreOrderApi from '@/api/mall/order/storeOrder'\nimport * as NoticeApi from '@/api/system/notice'\nimport PanelGroupT from './PanelGroupT.vue'\nimport { formatTime } from '@/utils'\nimport { useUserStore } from '@/store/modules/user'\nimport avatarImg from '@/assets/imgs/avatar.gif'\nconst userStore = useUserStore()\nconst avatar = userStore.getUser.avatar ? userStore.getUser.avatar : avatarImg\nconst { t } = useI18n()\nconst count = ref({\n        todayPrice: 0,\n        todayCount: 0,\n        proPrice: 0,\n        proCount: 0,\n        monthPrice: 0,\n        monthCount: 0,\n        lastWeekCount: 0,\n        lastWeekPrice: 0,\n        userCount: 0,\n        orderCount: 0,\n        priceCount: 0,\n        goodsCount: 0\n})\nconst loading = ref(true)\nconst notice = ref([])\n\n\n/** 查询列表 */\nconst getData = async () => {\n  try {\n    const data = await StoreOrderApi.getShopCount()\n\n    count.value = data\n  } finally {\n  }\n}\n\n/** 查询公告列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await NoticeApi.getNoticePage({})\n    notice.value = data.list\n  } finally {\n  }\n}\n\n\n/** 初始化 **/\nonMounted(() => {\n  getData()\n  getList()\n  loading.value = false\n})\n\n\n</script>\n\n<style rel=\"stylesheet/scss\" lang=\"scss\" scoped>\n.acea-row {\n    display: -webkit-box;\n    display: -moz-box;\n    display: -webkit-flex;\n    display: -ms-flexbox;\n    display: flex;\n    -webkit-box-lines: multiple;\n    -moz-box-lines: multiple;\n    -o-box-lines: multiple;\n    -webkit-flex-wrap: wrap;\n    -ms-flex-wrap: wrap;\n    flex-wrap: wrap;\n    /* 辅助类 */\n}\n\n/* 上下两边居中对齐 */\n.acea-row.row-between-wrapper {\n    -webkit-box-align: center;\n    -moz-box-align: center;\n    -o-box-align: center;\n    -ms-flex-align: center;\n    -webkit-align-items: center;\n    align-items: center;\n    -webkit-box-pack: justify;\n    -moz-box-pack: justify;\n    -o-box-pack: justify;\n    -ms-flex-pack: justify;\n    -webkit-justify-content: space-between;\n    justify-content: space-between;\n}\n.panel-group {\n  margin-top: 18px;\n\n  .card-panel-col {\n    margin-bottom: 32px;\n  }\n\n  .card-panel {\n    height: 108px;\n    cursor: pointer;\n    font-size: 12px;\n    position: relative;\n    overflow: hidden;\n    color: #666;\n    background: #fff;\n    box-shadow: 4px 4px 40px rgba(0, 0, 0, 0.05);\n    border-color: rgba(0, 0, 0, 0.05);\n\n    .icon-people {\n      color: #40c9c6;\n    }\n\n    .icon-message {\n      color: #36a3f7;\n    }\n\n    .icon-money {\n      color: #f4516c;\n    }\n\n    .icon-shopping {\n      color: #34bfa3;\n    }\n\n    .card-panel-icon-wrapper {\n      float: left;\n      margin: 14px 0 0 14px;\n      padding: 16px;\n      transition: all 0.38s ease-out;\n      border-radius: 6px;\n    }\n\n    .card-panel-icon {\n      float: left;\n      font-size: 48px;\n    }\n\n    .card-panel-description {\n      float: right;\n      font-weight: bold;\n      margin: 26px;\n      margin-left: 0px;\n\n      .card-panel-text {\n        line-height: 18px;\n        color: rgba(0, 0, 0, 0.45);\n        font-size: 16px;\n        margin-bottom: 12px;\n      }\n\n      .card-panel-num {\n        font-size: 20px;\n      }\n    }\n  }\n}\n\n@media (max-width: 550px) {\n  .card-panel-description {\n    display: none;\n  }\n\n  .card-panel-icon-wrapper {\n    float: none !important;\n    width: 100%;\n    height: 100%;\n    margin: 0 !important;\n\n    .svg-icon {\n      display: block;\n      margin: 14px auto !important;\n      float: none !important;\n    }\n  }\n}\n.baseInfo {\n  ::v-deep .el-card__header {\n    padding: 15px 20px !important;\n  }\n}\n\n.ivu-mb {\n  margin-bottom: 10px;\n}\n.up,\n.el-icon-caret-top {\n  color: #f5222d;\n  font-size: 12px;\n  opacity: 1 !important;\n}\n\n.down,\n.el-icon-caret-bottom {\n  color: #39c15b;\n  font-size: 12px;\n  /*opacity: 100% !important;*/\n}\n\n.content {\n  &-number {\n    font-size: 30px;\n  }\n  &-time {\n    font-size: 14px;\n    /*color: #8C8C8C;*/\n  }\n}\n.spBlock {\n  display: block;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/Home/Index2.vue",
    "content": "<template>\n  <el-row :class=\"prefixCls\" :gutter=\"20\" justify=\"space-between\">\n    <el-col :lg=\"6\" :md=\"12\" :sm=\"12\" :xl=\"6\" :xs=\"24\">\n      <el-card class=\"mb-20px\" shadow=\"hover\">\n        <el-skeleton :loading=\"loading\" :rows=\"2\" animated>\n          <template #default>\n            <div :class=\"`${prefixCls}__item flex justify-between`\">\n              <div>\n                <div\n                  :class=\"`${prefixCls}__item--icon ${prefixCls}__item--peoples p-16px inline-block rounded-6px`\"\n                >\n                  <Icon :size=\"40\" icon=\"svg-icon:peoples\" />\n                </div>\n              </div>\n              <div class=\"flex flex-col justify-between\">\n                <div :class=\"`${prefixCls}__item--text text-16px text-gray-500 text-right`\"\n                  >{{ t('analysis.newUser') }}\n                </div>\n                <CountTo\n                  :duration=\"2600\"\n                  :end-val=\"102400\"\n                  :start-val=\"0\"\n                  class=\"text-right text-20px font-700\"\n                />\n              </div>\n            </div>\n          </template>\n        </el-skeleton>\n      </el-card>\n    </el-col>\n\n    <el-col :lg=\"6\" :md=\"12\" :sm=\"12\" :xl=\"6\" :xs=\"24\">\n      <el-card class=\"mb-20px\" shadow=\"hover\">\n        <el-skeleton :loading=\"loading\" :rows=\"2\" animated>\n          <template #default>\n            <div :class=\"`${prefixCls}__item flex justify-between`\">\n              <div>\n                <div\n                  :class=\"`${prefixCls}__item--icon ${prefixCls}__item--message p-16px inline-block rounded-6px`\"\n                >\n                  <Icon :size=\"40\" icon=\"svg-icon:message\" />\n                </div>\n              </div>\n              <div class=\"flex flex-col justify-between\">\n                <div :class=\"`${prefixCls}__item--text text-16px text-gray-500 text-right`\"\n                  >{{ t('analysis.unreadInformation') }}\n                </div>\n                <CountTo\n                  :duration=\"2600\"\n                  :end-val=\"81212\"\n                  :start-val=\"0\"\n                  class=\"text-right text-20px font-700\"\n                />\n              </div>\n            </div>\n          </template>\n        </el-skeleton>\n      </el-card>\n    </el-col>\n\n    <el-col :lg=\"6\" :md=\"12\" :sm=\"12\" :xl=\"6\" :xs=\"24\">\n      <el-card class=\"mb-20px\" shadow=\"hover\">\n        <el-skeleton :loading=\"loading\" :rows=\"2\" animated>\n          <template #default>\n            <div :class=\"`${prefixCls}__item flex justify-between`\">\n              <div>\n                <div\n                  :class=\"`${prefixCls}__item--icon ${prefixCls}__item--money p-16px inline-block rounded-6px`\"\n                >\n                  <Icon :size=\"40\" icon=\"svg-icon:money\" />\n                </div>\n              </div>\n              <div class=\"flex flex-col justify-between\">\n                <div :class=\"`${prefixCls}__item--text text-16px text-gray-500 text-right`\"\n                  >{{ t('analysis.transactionAmount') }}\n                </div>\n                <CountTo\n                  :duration=\"2600\"\n                  :end-val=\"9280\"\n                  :start-val=\"0\"\n                  class=\"text-right text-20px font-700\"\n                />\n              </div>\n            </div>\n          </template>\n        </el-skeleton>\n      </el-card>\n    </el-col>\n\n    <el-col :lg=\"6\" :md=\"12\" :sm=\"12\" :xl=\"6\" :xs=\"24\">\n      <el-card class=\"mb-20px\" shadow=\"hover\">\n        <el-skeleton :loading=\"loading\" :rows=\"2\" animated>\n          <template #default>\n            <div :class=\"`${prefixCls}__item flex justify-between`\">\n              <div>\n                <div\n                  :class=\"`${prefixCls}__item--icon ${prefixCls}__item--shopping p-16px inline-block rounded-6px`\"\n                >\n                  <Icon :size=\"40\" icon=\"svg-icon:shopping\" />\n                </div>\n              </div>\n              <div class=\"flex flex-col justify-between\">\n                <div :class=\"`${prefixCls}__item--text text-16px text-gray-500 text-right`\"\n                  >{{ t('analysis.totalShopping') }}\n                </div>\n                <CountTo\n                  :duration=\"2600\"\n                  :end-val=\"13600\"\n                  :start-val=\"0\"\n                  class=\"text-right text-20px font-700\"\n                />\n              </div>\n            </div>\n          </template>\n        </el-skeleton>\n      </el-card>\n    </el-col>\n  </el-row>\n  <el-row :gutter=\"20\" justify=\"space-between\">\n    <el-col :lg=\"10\" :md=\"24\" :sm=\"24\" :xl=\"10\" :xs=\"24\">\n      <el-card class=\"mb-20px\" shadow=\"hover\">\n        <el-skeleton :loading=\"loading\" animated>\n          <Echart :height=\"300\" :options=\"pieOptionsData\" />\n        </el-skeleton>\n      </el-card>\n    </el-col>\n    <el-col :lg=\"14\" :md=\"24\" :sm=\"24\" :xl=\"14\" :xs=\"24\">\n      <el-card class=\"mb-20px\" shadow=\"hover\">\n        <el-skeleton :loading=\"loading\" animated>\n          <Echart :height=\"300\" :options=\"barOptionsData\" />\n        </el-skeleton>\n      </el-card>\n    </el-col>\n    <el-col :span=\"24\">\n      <el-card class=\"mb-20px\" shadow=\"hover\">\n        <el-skeleton :loading=\"loading\" :rows=\"4\" animated>\n          <Echart :height=\"350\" :options=\"lineOptionsData\" />\n        </el-skeleton>\n      </el-card>\n    </el-col>\n  </el-row>\n</template>\n<script lang=\"ts\" setup>\nimport { set } from 'lodash-es'\nimport { EChartsOption } from 'echarts'\n\nimport { useDesign } from '@/hooks/web/useDesign'\nimport type { AnalysisTotalTypes } from './types'\nimport { barOptions, lineOptions, pieOptions } from './echarts-data'\n\ndefineOptions({ name: 'Home2' })\n\nconst { t } = useI18n()\nconst loading = ref(true)\nconst { getPrefixCls } = useDesign()\nconst prefixCls = getPrefixCls('panel')\nconst pieOptionsData = reactive<EChartsOption>(pieOptions) as EChartsOption\n\nlet totalState = reactive<AnalysisTotalTypes>({\n  users: 0,\n  messages: 0,\n  moneys: 0,\n  shoppings: 0\n})\n\nconst getCount = async () => {\n  const data = {\n    users: 102400,\n    messages: 81212,\n    moneys: 9280,\n    shoppings: 13600\n  }\n  totalState = Object.assign(totalState, data)\n}\n\n// 用户来源\nconst getUserAccessSource = async () => {\n  const data = [\n    { value: 335, name: 'analysis.directAccess' },\n    { value: 310, name: 'analysis.mailMarketing' },\n    { value: 234, name: 'analysis.allianceAdvertising' },\n    { value: 135, name: 'analysis.videoAdvertising' },\n    { value: 1548, name: 'analysis.searchEngines' }\n  ]\n  set(\n    pieOptionsData,\n    'legend.data',\n    data.map((v) => t(v.name))\n  )\n  set(pieOptionsData, 'series.data', data)\n}\nconst barOptionsData = reactive<EChartsOption>(barOptions) as EChartsOption\n\n// 周活跃量\nconst getWeeklyUserActivity = async () => {\n  const data = [\n    { value: 13253, name: 'analysis.monday' },\n    { value: 34235, name: 'analysis.tuesday' },\n    { value: 26321, name: 'analysis.wednesday' },\n    { value: 12340, name: 'analysis.thursday' },\n    { value: 24643, name: 'analysis.friday' },\n    { value: 1322, name: 'analysis.saturday' },\n    { value: 1324, name: 'analysis.sunday' }\n  ]\n  set(\n    barOptionsData,\n    'xAxis.data',\n    data.map((v) => t(v.name))\n  )\n  set(barOptionsData, 'series', [\n    {\n      name: t('analysis.activeQuantity'),\n      data: data.map((v) => v.value),\n      type: 'bar'\n    }\n  ])\n}\n\nconst lineOptionsData = reactive<EChartsOption>(lineOptions) as EChartsOption\n\n// 每月销售总额\nconst getMonthlySales = async () => {\n  const data = [\n    { estimate: 100, actual: 120, name: 'analysis.january' },\n    { estimate: 120, actual: 82, name: 'analysis.february' },\n    { estimate: 161, actual: 91, name: 'analysis.march' },\n    { estimate: 134, actual: 154, name: 'analysis.april' },\n    { estimate: 105, actual: 162, name: 'analysis.may' },\n    { estimate: 160, actual: 140, name: 'analysis.june' },\n    { estimate: 165, actual: 145, name: 'analysis.july' },\n    { estimate: 114, actual: 250, name: 'analysis.august' },\n    { estimate: 163, actual: 134, name: 'analysis.september' },\n    { estimate: 185, actual: 56, name: 'analysis.october' },\n    { estimate: 118, actual: 99, name: 'analysis.november' },\n    { estimate: 123, actual: 123, name: 'analysis.december' }\n  ]\n  set(\n    lineOptionsData,\n    'xAxis.data',\n    data.map((v) => t(v.name))\n  )\n  set(lineOptionsData, 'series', [\n    {\n      name: t('analysis.estimate'),\n      smooth: true,\n      type: 'line',\n      data: data.map((v) => v.estimate),\n      animationDuration: 2800,\n      animationEasing: 'cubicInOut'\n    },\n    {\n      name: t('analysis.actual'),\n      smooth: true,\n      type: 'line',\n      itemStyle: {},\n      data: data.map((v) => v.actual),\n      animationDuration: 2800,\n      animationEasing: 'quadraticOut'\n    }\n  ])\n}\n\nconst getAllApi = async () => {\n  await Promise.all([getCount(), getUserAccessSource(), getWeeklyUserActivity(), getMonthlySales()])\n  loading.value = false\n}\n\ngetAllApi()\n</script>\n\n<style lang=\"scss\" scoped>\n$prefix-cls: #{$namespace}-panel;\n\n.#{$prefix-cls} {\n  &__item {\n    &--peoples {\n      color: #40c9c6;\n    }\n\n    &--message {\n      color: #36a3f7;\n    }\n\n    &--money {\n      color: #f4516c;\n    }\n\n    &--shopping {\n      color: #34bfa3;\n    }\n\n    &:hover {\n      :deep(.#{$namespace}-icon) {\n        color: #fff !important;\n      }\n\n      .#{$prefix-cls}__item--icon {\n        transition: all 0.38s ease-out;\n      }\n\n      .#{$prefix-cls}__item--peoples {\n        background: #40c9c6;\n      }\n\n      .#{$prefix-cls}__item--message {\n        background: #36a3f7;\n      }\n\n      .#{$prefix-cls}__item--money {\n        background: #f4516c;\n      }\n\n      .#{$prefix-cls}__item--shopping {\n        background: #34bfa3;\n      }\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/Home/PanelGroupT.vue",
    "content": "<template>\n  <div class=\"divBox\" v-show=\"isShow\">\n      <el-row :gutter=\"24\" class=\"dashboard-console-grid\">\n        <el-col v-bind=\"grid\" class=\"ivu-mb\">\n          <el-card :bordered=\"false\">\n            <router-link :to=\"{ path: '/mall/product/store-product' }\">\n               <Icon icon=\"ep:goods\" style=\"color: #69c0ff\" :size=\"35\" />\n              <p>商品管理</p>\n            </router-link>\n          </el-card>\n        </el-col>\n        <el-col v-bind=\"grid\" class=\"ivu-mb\">\n          <el-card :bordered=\"false\">\n            <router-link :to=\"{ path: '/mall/member/user' }\">\n              <Icon icon=\"ep:user\" style=\"color: #95de64\" :size=\"35\" />\n              <p>会员管理</p>\n            </router-link>\n          </el-card>\n        </el-col>\n        <el-col v-bind=\"grid\" class=\"ivu-mb\">\n          <el-card :bordered=\"false\">\n            <router-link :to=\"{ path: '/mall/order/store-order' }\">\n              <Icon icon=\"ep:list\" style=\"color: #ff9c6e\" :size=\"35\" />\n              <p>订单管理</p>\n            </router-link>\n          </el-card>\n        </el-col>\n        <el-col v-bind=\"grid\" class=\"ivu-mb\">\n          <el-card :bordered=\"false\">\n            <router-link :to=\"{ path: '/mall/coupons' }\">\n              <Icon icon=\"ep:ticket\" style=\"color: #b37feb\" :size=\"35\" />\n              <p>优惠券管理</p>\n            </router-link>\n          </el-card>\n        </el-col>\n        <el-col v-bind=\"grid\" class=\"ivu-mb\">\n          <el-card :bordered=\"false\">\n            <router-link :to=\"{ path: '/shop/web-print' }\">\n               <Icon icon=\"ep:postcard\" style=\"color: #ffd666\" :size=\"35\" />\n              <p>打印机管理</p>\n            </router-link>\n          </el-card>\n        </el-col>\n        <el-col v-bind=\"grid\" class=\"ivu-mb\">\n          <el-card :bordered=\"false\">\n            <router-link :to=\"{ path: '/mall/shop' }\" >\n               <Icon icon=\"ep:notebook\" style=\"color: #5cdbd3\" :size=\"35\" />\n              <p>门店管理</p>\n            </router-link>\n          </el-card>\n        </el-col>\n        <el-col v-bind=\"grid\" class=\"ivu-mb\">\n          <el-card :bordered=\"false\">\n            <router-link :to=\"{ path: '/pay/merchant-details' }\">\n              <Icon icon=\"ep:money\" style=\"color: #ff85c0\" :size=\"35\" />\n              <p>支付管理</p>\n            </router-link>\n          </el-card>\n        </el-col>\n        <el-col v-bind=\"grid\" class=\"ivu-mb\">\n          <el-card :bordered=\"false\">\n            <router-link :to=\"{ path: '/message/wechat-template' }\">\n              <Icon icon=\"ep:message\" style=\"color: #ffc069\" :size=\"35\" />\n              <p>消息管理</p>\n            </router-link>\n          </el-card>\n        </el-col>\n      </el-row>\n    </div>\n</template>\n<script setup lang=\"ts\">\nimport { CACHE_KEY, useCache } from '@/hooks/web/useCache'\n\nconst { wsCache } = useCache()\n\n\nconst grid =ref({\n  xl: 3,\n  lg: 3,\n  md: 6,\n  sm: 8,\n  xs: 8,\n})\n\n\n\nconst user = wsCache.get(CACHE_KEY.USER)\n\nconst isShow = ref(true)\nif(user.user.shopId > 0) {\n  isShow.value = false\n}\n\n</script>\n\n<style rel=\"stylesheet/scss\" lang=\"scss\" scoped>\n.panel-group {\n  margin-top: 18px;\n  .card-panel-col{\n    margin-bottom: 32px;\n  }\n  .card-panel {\n    height: 108px;\n    font-size: 12px;\n    position: relative;\n    overflow: hidden;\n    cursor: pointer;\n    color: #666;\n    background: #fff;\n    box-shadow: 4px 4px 40px rgba(0, 0, 0, .05);\n    border-color: rgba(0, 0, 0, .05);\n\n\n\n    .icon-people {\n      color: #40c9c6;\n    }\n    .icon-message {\n      color: #36a3f7;\n    }\n    .icon-money {\n      color: #f4516c;\n    }\n    .icon-shopping {\n      color: #34bfa3\n    }\n    .card-panel-icon-wrapper {\n      float: left;\n      margin: 14px 0 0 14px;\n      padding: 16px;\n      transition: all 0.38s ease-out;\n      border-radius: 6px;\n    }\n    .card-panel-icon {\n      float: left;\n      font-size: 48px;\n    }\n    .card-panel-description {\n      float: right;\n      font-weight: bold;\n      margin: 26px;\n      margin-left: 0px;\n      .card-panel-text {\n        line-height: 18px;\n        color: rgba(0, 0, 0, 0.45);\n        font-size: 16px;\n        margin-bottom: 12px;\n      }\n      .card-panel-num {\n        font-size: 20px;\n      }\n    }\n  }\n}\n\n\n.ivu-mb {\n  margin-bottom: 10px;\n}\n.divBox {\n  // padding: 0 20px !important;\n}\n\n.dashboard-console-grid {\n  text-align: center;\n  .ivu-card-body {\n    padding: 0;\n  }\n  i {\n    font-size: 32px;\n  }\n  a {\n    display: block;\n    color: inherit;\n  }\n  p {\n    margin-top: 8px;\n  }\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/Home/echarts-data.ts",
    "content": "import { EChartsOption } from 'echarts'\n\nconst { t } = useI18n()\n\nexport const lineOptions: EChartsOption = {\n  title: {\n    text: t('analysis.monthlySales'),\n    left: 'center'\n  },\n  xAxis: {\n    data: [\n      t('analysis.january'),\n      t('analysis.february'),\n      t('analysis.march'),\n      t('analysis.april'),\n      t('analysis.may'),\n      t('analysis.june'),\n      t('analysis.july'),\n      t('analysis.august'),\n      t('analysis.september'),\n      t('analysis.october'),\n      t('analysis.november'),\n      t('analysis.december')\n    ],\n    boundaryGap: false,\n    axisTick: {\n      show: false\n    }\n  },\n  grid: {\n    left: 20,\n    right: 20,\n    bottom: 20,\n    top: 80,\n    containLabel: true\n  },\n  tooltip: {\n    trigger: 'axis',\n    axisPointer: {\n      type: 'cross'\n    },\n    padding: [5, 10]\n  },\n  yAxis: {\n    axisTick: {\n      show: false\n    }\n  },\n  legend: {\n    data: [t('analysis.estimate'), t('analysis.actual')],\n    top: 50\n  },\n  series: [\n    {\n      name: t('analysis.estimate'),\n      smooth: true,\n      type: 'line',\n      data: [100, 120, 161, 134, 105, 160, 165, 114, 163, 185, 118, 123],\n      animationDuration: 2800,\n      animationEasing: 'cubicInOut'\n    },\n    {\n      name: t('analysis.actual'),\n      smooth: true,\n      type: 'line',\n      itemStyle: {},\n      data: [120, 82, 91, 154, 162, 140, 145, 250, 134, 56, 99, 123],\n      animationDuration: 2800,\n      animationEasing: 'quadraticOut'\n    }\n  ]\n}\n\nexport const pieOptions: EChartsOption = {\n  title: {\n    text: t('analysis.userAccessSource'),\n    left: 'center'\n  },\n  tooltip: {\n    trigger: 'item',\n    formatter: '{a} <br/>{b} : {c} ({d}%)'\n  },\n  legend: {\n    orient: 'vertical',\n    left: 'left',\n    data: [\n      t('analysis.directAccess'),\n      t('analysis.mailMarketing'),\n      t('analysis.allianceAdvertising'),\n      t('analysis.videoAdvertising'),\n      t('analysis.searchEngines')\n    ]\n  },\n  series: [\n    {\n      name: t('analysis.userAccessSource'),\n      type: 'pie',\n      radius: '55%',\n      center: ['50%', '60%'],\n      data: [\n        { value: 335, name: t('analysis.directAccess') },\n        { value: 310, name: t('analysis.mailMarketing') },\n        { value: 234, name: t('analysis.allianceAdvertising') },\n        { value: 135, name: t('analysis.videoAdvertising') },\n        { value: 1548, name: t('analysis.searchEngines') }\n      ]\n    }\n  ]\n}\n\nexport const barOptions: EChartsOption = {\n  title: {\n    text: t('analysis.weeklyUserActivity'),\n    left: 'center'\n  },\n  tooltip: {\n    trigger: 'axis',\n    axisPointer: {\n      type: 'shadow'\n    }\n  },\n  grid: {\n    left: 50,\n    right: 20,\n    bottom: 20\n  },\n  xAxis: {\n    type: 'category',\n    data: [\n      t('analysis.monday'),\n      t('analysis.tuesday'),\n      t('analysis.wednesday'),\n      t('analysis.thursday'),\n      t('analysis.friday'),\n      t('analysis.saturday'),\n      t('analysis.sunday')\n    ],\n    axisTick: {\n      alignWithLabel: true\n    }\n  },\n  yAxis: {\n    type: 'value'\n  },\n  series: [\n    {\n      name: t('analysis.activeQuantity'),\n      data: [13253, 34235, 26321, 12340, 24643, 1322, 1324],\n      type: 'bar'\n    }\n  ]\n}\n\nexport const radarOption: EChartsOption = {\n  legend: {\n    data: [t('workplace.personal'), t('workplace.team')]\n  },\n  radar: {\n    // shape: 'circle',\n    indicator: [\n      { name: t('workplace.quote'), max: 65 },\n      { name: t('workplace.contribution'), max: 160 },\n      { name: t('workplace.hot'), max: 300 },\n      { name: t('workplace.yield'), max: 130 },\n      { name: t('workplace.follow'), max: 100 }\n    ]\n  },\n  series: [\n    {\n      name: `xxx${t('workplace.index')}`,\n      type: 'radar',\n      data: [\n        {\n          value: [42, 30, 20, 35, 80],\n          name: t('workplace.personal')\n        },\n        {\n          value: [50, 140, 290, 100, 90],\n          name: t('workplace.team')\n        }\n      ]\n    }\n  ]\n}\n\nexport const wordOptions = {\n  series: [\n    {\n      type: 'wordCloud',\n      gridSize: 2,\n      sizeRange: [12, 50],\n      rotationRange: [-90, 90],\n      shape: 'pentagon',\n      width: 600,\n      height: 400,\n      drawOutOfBound: true,\n      textStyle: {\n        color: function () {\n          return (\n            'rgb(' +\n            [\n              Math.round(Math.random() * 160),\n              Math.round(Math.random() * 160),\n              Math.round(Math.random() * 160)\n            ].join(',') +\n            ')'\n          )\n        }\n      },\n      emphasis: {\n        textStyle: {\n          shadowBlur: 10,\n          shadowColor: '#333'\n        }\n      },\n      data: [\n        {\n          name: 'Sam S Club',\n          value: 10000,\n          textStyle: {\n            color: 'black'\n          },\n          emphasis: {\n            textStyle: {\n              color: 'red'\n            }\n          }\n        },\n        {\n          name: 'Macys',\n          value: 6181\n        },\n        {\n          name: 'Amy Schumer',\n          value: 4386\n        },\n        {\n          name: 'Jurassic World',\n          value: 4055\n        },\n        {\n          name: 'Charter Communications',\n          value: 2467\n        },\n        {\n          name: 'Chick Fil A',\n          value: 2244\n        },\n        {\n          name: 'Planet Fitness',\n          value: 1898\n        },\n        {\n          name: 'Pitch Perfect',\n          value: 1484\n        },\n        {\n          name: 'Express',\n          value: 1112\n        },\n        {\n          name: 'Home',\n          value: 965\n        },\n        {\n          name: 'Johnny Depp',\n          value: 847\n        },\n        {\n          name: 'Lena Dunham',\n          value: 582\n        },\n        {\n          name: 'Lewis Hamilton',\n          value: 555\n        },\n        {\n          name: 'KXAN',\n          value: 550\n        },\n        {\n          name: 'Mary Ellen Mark',\n          value: 462\n        },\n        {\n          name: 'Farrah Abraham',\n          value: 366\n        },\n        {\n          name: 'Rita Ora',\n          value: 360\n        },\n        {\n          name: 'Serena Williams',\n          value: 282\n        },\n        {\n          name: 'NCAA baseball tournament',\n          value: 273\n        },\n        {\n          name: 'Point Break',\n          value: 265\n        }\n      ]\n    }\n  ]\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/Home/types.ts",
    "content": "export type WorkplaceTotal = {\n  project: number\n  access: number\n  todo: number\n}\n\nexport type Project = {\n  name: string\n  icon: string\n  message: string\n  personal: string\n  time: Date | number | string\n}\n\nexport type Notice = {\n  title: string\n  type: string\n  keys: string[]\n  date: Date | number | string\n}\n\nexport type Shortcut = {\n  name: string\n  icon: string\n  url: string\n}\n\nexport type RadarData = {\n  personal: number\n  team: number\n  max: number\n  name: string\n}\nexport type AnalysisTotalTypes = {\n  users: number\n  messages: number\n  moneys: number\n  shoppings: number\n}\n\nexport type UserAccessSource = {\n  value: number\n  name: string\n}\n\nexport type WeeklyUserActivity = {\n  value: number\n  name: string\n}\n\nexport type MonthlySales = {\n  name: string\n  estimate: number\n  actual: number\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/Login/Login.vue",
    "content": "<template>\n  <div\n    :class=\"prefixCls\"\n    class=\"h-[100%] relative <xl:bg-v-dark <sm:px-10px <xl:px-10px <md:px-10px\"\n  >\n    <div class=\"relative h-full flex mx-auto\">\n      <div\n        :class=\"`${prefixCls}__left flex-1 bg-gray-500 bg-opacity-20 relative p-30px <xl:hidden`\"\n      >\n        <!-- 左上角的 logo + 系统标题 -->\n        <div class=\"flex items-center relative text-white\">\n          <img alt=\"\" class=\"w-48px h-48px mr-10px\" src=\"@/assets/imgs/logo.png\" />\n          <span class=\"text-20px font-bold\">{{ underlineToHump(appStore.getTitle) }}</span>\n        </div>\n        <!-- 左边的背景图 + 欢迎语 -->\n        <div class=\"flex justify-center items-center h-[calc(100%-60px)]\">\n          <TransitionGroup\n            appear\n            enter-active-class=\"animate__animated animate__bounceInLeft\"\n            tag=\"div\"\n          >\n            <img key=\"1\" alt=\"\" class=\"w-500px\" src=\"@/assets/imgs/login_bg.png\" />\n            <div key=\"2\" class=\"text-3xl text-white\" style=\"text-align: center;\">{{ t('login.welcome') }}</div>\n            <!-- <div key=\"3\" class=\"mt-5 font-normal text-white text-14px\">\n              {{ t('login.message') }}\n            </div> -->\n          </TransitionGroup>\n        </div>\n      </div>\n      <div class=\"flex-1 p-30px <sm:p-10px dark:bg-v-dark relative\">\n        <!-- 右上角的主题、语言选择 -->\n        <div class=\"flex justify-between items-center text-white @2xl:justify-end @xl:justify-end\">\n          <div class=\"flex items-center @2xl:hidden @xl:hidden\">\n            <img alt=\"\" class=\"w-48px h-48px mr-10px\" src=\"@/assets/imgs/logo.png\" />\n            <span class=\"text-20px font-bold\">{{ underlineToHump(appStore.getTitle) }}</span>\n          </div>\n          <div class=\"flex justify-end items-center space-x-10px\">\n            <!-- <ThemeSwitch /> -->\n            <!-- <LocaleDropdown class=\"<xl:text-white dark:text-white\" /> -->\n          </div>\n        </div>\n        <!-- 右边的登录界面 -->\n        <Transition appear enter-active-class=\"animate__animated animate__bounceInRight\">\n      \n          <div\n            class=\"h-full flex items-center m-auto w-[100%] @2xl:max-w-500px @xl:max-w-500px @md:max-w-500px @lg:max-w-500px\"\n          >\n            <!-- 账号登录 -->\n            <LoginForm class=\"p-20px h-auto m-auto <xl:(rounded-3xl light:bg-white)\" />\n            <!-- 手机登录 -->\n            <!-- <MobileForm class=\"p-20px h-auto m-auto <xl:(rounded-3xl light:bg-white)\" />\n             二维码登录 -->\n            <!-- <QrCodeForm class=\"p-20px h-auto m-auto <xl:(rounded-3xl light:bg-white)\" />\n             注册 -->\n            <!-- <RegisterForm class=\"p-20px h-auto m-auto <xl:(rounded-3xl light:bg-white)\" /> -->\n            <!-- 三方登录 -->\n            <!-- <SSOLoginVue class=\"p-20px h-auto m-auto <xl:(rounded-3xl light:bg-white)\" /> --> \n          </div>\n        </Transition>\n      </div>\n    </div>\n  </div>\n</template>\n<script lang=\"ts\" name=\"Login\" setup>\nimport { underlineToHump } from '@/utils'\n\nimport { useDesign } from '@/hooks/web/useDesign'\nimport { useAppStore } from '@/store/modules/app'\n//import { ThemeSwitch } from '@/layout/components/ThemeSwitch'\n//import { LocaleDropdown } from '@/layout/components/LocaleDropdown'\n\nimport { LoginForm } from './components'\n// import { LoginForm, MobileForm, QrCodeForm, RegisterForm, SSOLoginVue } from './components'\n\nconst { t } = useI18n()\nconst appStore = useAppStore()\nconst { getPrefixCls } = useDesign()\nconst prefixCls = getPrefixCls('login')\nappStore.setIsDark(false)\n</script>\n\n<style lang=\"scss\" scoped>\n$prefix-cls: #{$namespace}-login;\n\n.#{$prefix-cls} {\n  &__left {\n    &::before {\n      position: absolute;\n      top: 0;\n      left: 0;\n      z-index: -1;\n      width: 100%;\n      height: 100%;\n      background-image: url('@/assets/svgs/login-bg.svg');\n      background-position: center;\n      background-repeat: no-repeat;\n      content: '';\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/Login/SocialLogin.vue",
    "content": "<template>\n  <div\n    :class=\"prefixCls\"\n    class=\"relative h-[100%] lt-xl:bg-[var(--login-bg-color)] lt-md:px-10px lt-sm:px-10px lt-xl:px-10px\"\n  >\n    <div class=\"relative mx-auto h-full flex\">\n      <div\n        :class=\"`${prefixCls}__left flex-1 bg-gray-500 bg-opacity-20 relative p-30px lt-xl:hidden`\"\n      >\n        <!-- 左上角的 logo + 系统标题 -->\n        <div class=\"relative flex items-center text-white\">\n          <img alt=\"\" class=\"mr-10px h-48px w-48px\" src=\"@/assets/imgs/logo.png\" />\n          <span class=\"text-20px font-bold\">{{ underlineToHump(appStore.getTitle) }}</span>\n        </div>\n        <!-- 左边的背景图 + 欢迎语 -->\n        <div class=\"h-[calc(100%-60px)] flex items-center justify-center\">\n          <TransitionGroup\n            appear\n            enter-active-class=\"animate__animated animate__bounceInLeft\"\n            tag=\"div\"\n          >\n            <img key=\"1\" alt=\"\" class=\"w-350px\" src=\"@/assets/svgs/login-box-bg.svg\" />\n            <div key=\"2\" class=\"text-3xl text-white\">{{ t('login.welcome') }}</div>\n            <div key=\"3\" class=\"mt-5 text-14px font-normal text-white\">\n              {{ t('login.message') }}\n            </div>\n          </TransitionGroup>\n        </div>\n      </div>\n      <div class=\"relative flex-1 p-30px dark:bg-[var(--login-bg-color)] lt-sm:p-10px\">\n        <!-- 右上角的主题、语言选择 -->\n        <div\n          class=\"flex items-center justify-between text-white at-2xl:justify-end at-xl:justify-end\"\n        >\n          <div class=\"flex items-center at-2xl:hidden at-xl:hidden\">\n            <img alt=\"\" class=\"mr-10px h-48px w-48px\" src=\"@/assets/imgs/logo.png\" />\n            <span class=\"text-20px font-bold\">{{ underlineToHump(appStore.getTitle) }}</span>\n          </div>\n          <div class=\"flex items-center justify-end space-x-10px\">\n            <ThemeSwitch />\n            <LocaleDropdown class=\"dark:text-white lt-xl:text-white\" />\n          </div>\n        </div>\n        <!-- 右边的登录界面 -->\n        <Transition appear enter-active-class=\"animate__animated animate__bounceInRight\">\n          <div\n            class=\"m-auto h-full w-[100%] flex items-center at-2xl:max-w-500px at-lg:max-w-500px at-md:max-w-500px at-xl:max-w-500px\"\n          >\n            <!-- 账号登录 -->\n            <el-form\n              v-show=\"getShow\"\n              ref=\"formLogin\"\n              :model=\"loginData.loginForm\"\n              :rules=\"LoginRules\"\n              class=\"login-form\"\n              label-position=\"top\"\n              label-width=\"120px\"\n              size=\"large\"\n            >\n              <el-row style=\"margin-right: -10px; margin-left: -10px\">\n                <el-col :span=\"24\" style=\"padding-right: 10px; padding-left: 10px\">\n                  <el-form-item>\n                    <LoginFormTitle style=\"width: 100%\" />\n                  </el-form-item>\n                </el-col>\n                <el-col :span=\"24\" style=\"padding-right: 10px; padding-left: 10px\">\n                  <el-form-item v-if=\"loginData.tenantEnable\" prop=\"tenantName\">\n                    <el-input\n                      v-model=\"loginData.loginForm.tenantName\"\n                      :placeholder=\"t('login.tenantNamePlaceholder')\"\n                      :prefix-icon=\"iconHouse\"\n                      link\n                      type=\"primary\"\n                    />\n                  </el-form-item>\n                </el-col>\n                <el-col :span=\"24\" style=\"padding-right: 10px; padding-left: 10px\">\n                  <el-form-item prop=\"username\">\n                    <el-input\n                      v-model=\"loginData.loginForm.username\"\n                      :placeholder=\"t('login.usernamePlaceholder')\"\n                      :prefix-icon=\"iconAvatar\"\n                    />\n                  </el-form-item>\n                </el-col>\n                <el-col :span=\"24\" style=\"padding-right: 10px; padding-left: 10px\">\n                  <el-form-item prop=\"password\">\n                    <el-input\n                      v-model=\"loginData.loginForm.password\"\n                      :placeholder=\"t('login.passwordPlaceholder')\"\n                      :prefix-icon=\"iconLock\"\n                      show-password\n                      type=\"password\"\n                      @keyup.enter=\"getCode()\"\n                    />\n                  </el-form-item>\n                </el-col>\n                <el-col\n                  :span=\"24\"\n                  style=\"\n                    padding-right: 10px;\n                    padding-left: 10px;\n                    margin-top: -20px;\n                    margin-bottom: -20px;\n                  \"\n                >\n                  <el-form-item>\n                    <el-row justify=\"space-between\" style=\"width: 100%\">\n                      <el-col :span=\"6\">\n                        <el-checkbox v-model=\"loginData.loginForm.rememberMe\">\n                          {{ t('login.remember') }}\n                        </el-checkbox>\n                      </el-col>\n                      <el-col :offset=\"6\" :span=\"12\">\n                        <el-link style=\"float: right\" type=\"primary\">{{\n                          t('login.forgetPassword')\n                        }}</el-link>\n                      </el-col>\n                    </el-row>\n                  </el-form-item>\n                </el-col>\n                <el-col :span=\"24\" style=\"padding-right: 10px; padding-left: 10px\">\n                  <el-form-item>\n                    <XButton\n                      :loading=\"loginLoading\"\n                      :title=\"t('login.login')\"\n                      class=\"w-[100%]\"\n                      type=\"primary\"\n                      @click=\"getCode()\"\n                    />\n                  </el-form-item>\n                </el-col>\n                <Verify\n                  ref=\"verify\"\n                  :captchaType=\"captchaType\"\n                  :imgSize=\"{ width: '400px', height: '200px' }\"\n                  mode=\"pop\"\n                  @success=\"handleLogin\"\n                />\n              </el-row>\n            </el-form>\n          </div>\n        </Transition>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { underlineToHump } from '@/utils'\n\nimport { ElLoading } from 'element-plus'\n\nimport { useDesign } from '@/hooks/web/useDesign'\nimport { useAppStore } from '@/store/modules/app'\nimport { useIcon } from '@/hooks/web/useIcon'\nimport { usePermissionStore } from '@/store/modules/permission'\n\nimport * as LoginApi from '@/api/login'\nimport * as authUtil from '@/utils/auth'\nimport { ThemeSwitch } from '@/layout/components/ThemeSwitch'\nimport { LocaleDropdown } from '@/layout/components/LocaleDropdown'\nimport { LoginStateEnum, useFormValid, useLoginState } from './components/useLogin'\nimport LoginFormTitle from './components/LoginFormTitle.vue'\nimport router from '@/router'\n\ndefineOptions({ name: 'SocialLogin' })\n\nconst { t } = useI18n()\nconst route = useRoute()\n\nconst appStore = useAppStore()\nconst { getPrefixCls } = useDesign()\nconst prefixCls = getPrefixCls('login')\nconst iconHouse = useIcon({ icon: 'ep:house' })\nconst iconAvatar = useIcon({ icon: 'ep:avatar' })\nconst iconLock = useIcon({ icon: 'ep:lock' })\nconst formLogin = ref<any>()\nconst { validForm } = useFormValid(formLogin)\nconst { getLoginState } = useLoginState()\nconst { push } = useRouter()\nconst permissionStore = usePermissionStore()\nconst loginLoading = ref(false)\nconst verify = ref()\nconst captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字\n\nconst getShow = computed(() => unref(getLoginState) === LoginStateEnum.LOGIN)\n\nconst LoginRules = {\n  tenantName: [required],\n  username: [required],\n  password: [required]\n}\nconst loginData = reactive({\n  isShowPassword: false,\n  captchaEnable: import.meta.env.VITE_APP_CAPTCHA_ENABLE !== 'false',\n  tenantEnable: import.meta.env.VITE_APP_TENANT_ENABLE !== 'false',\n  loginForm: {\n    tenantName: 'yshop源码',\n    username: 'admin',\n    password: 'admin123',\n    captchaVerification: '',\n    rememberMe: false\n  }\n})\n\n// 获取验证码\nconst getCode = async () => {\n  // 情况一，未开启：则直接登录\n  if (!loginData.captchaEnable) {\n    await handleLogin({})\n  } else {\n    // 情况二，已开启：则展示验证码；只有完成验证码的情况，才进行登录\n    // 弹出验证码\n    verify.value.show()\n  }\n}\n//获取租户ID\nconst getTenantId = async () => {\n  if (loginData.tenantEnable) {\n    const res = await LoginApi.getTenantIdByName(loginData.loginForm.tenantName)\n    authUtil.setTenantId(res)\n  }\n}\n// 记住我\nconst getCookie = () => {\n  const loginForm = authUtil.getLoginForm()\n  if (loginForm) {\n    loginData.loginForm = {\n      ...loginData.loginForm,\n      username: loginForm.username ? loginForm.username : loginData.loginForm.username,\n      password: loginForm.password ? loginForm.password : loginData.loginForm.password,\n      rememberMe: loginForm.rememberMe ? true : false,\n      tenantName: loginForm.tenantName ? loginForm.tenantName : loginData.loginForm.tenantName\n    }\n  }\n}\nconst loading = ref() // ElLoading.service 返回的实例\n\n// tricky: 配合LoginForm.vue中redirectUri需要对参数进行encode，需要在回调后进行decode\nfunction getUrlValue(key: string): string {\n  const url = new URL(decodeURIComponent(location.href))\n  return url.searchParams.get(key) ?? ''\n}\n\n// 尝试登录: 当账号已经绑定，socialLogin会直接获得token\nconst tryLogin = async () => {\n  try {\n    const type = getUrlValue('type')\n    const redirect = getUrlValue('redirect')\n    const code = route?.query?.code as string\n    const state = route?.query?.state as string\n\n    const res = await LoginApi.socialLogin(type, code, state)\n    authUtil.setToken(res)\n\n    router.push({ path: redirect || '/' })\n  } catch (err) {}\n}\n\n// 登录\nconst handleLogin = async (params) => {\n  loginLoading.value = true\n  try {\n    await getTenantId()\n    const data = await validForm()\n    if (!data) {\n      return\n    }\n\n    let redirect = getUrlValue('redirect')\n\n    const type = getUrlValue('type')\n    const code = route?.query?.code as string\n    const state = route?.query?.state as string\n\n    const res = await LoginApi.login({\n      // 账号密码登录\n      username: loginData.loginForm.username,\n      password: loginData.loginForm.password,\n      captchaVerification: params.captchaVerification,\n      // 社交登录\n      socialCode: code,\n      socialState: state,\n      socialType: type\n    })\n    if (!res) {\n      return\n    }\n    loading.value = ElLoading.service({\n      lock: true,\n      text: '正在加载系统中...',\n      background: 'rgba(0, 0, 0, 0.7)'\n    })\n    if (loginData.loginForm.rememberMe) {\n      authUtil.setLoginForm(loginData.loginForm)\n    } else {\n      authUtil.removeLoginForm()\n    }\n    authUtil.setToken(res)\n    if (!redirect) {\n      redirect = '/'\n    }\n    // 判断是否为SSO登录\n    if (redirect.indexOf('sso') !== -1) {\n      window.location.href = window.location.href.replace('/login?redirect=', '')\n    } else {\n      push({ path: redirect || permissionStore.addRouters[0].path })\n    }\n  } finally {\n    loginLoading.value = false\n    loading.value.close()\n  }\n}\n\nonMounted(() => {\n  getCookie()\n  tryLogin()\n})\n</script>\n\n<style lang=\"scss\" scoped>\n$prefix-cls: #{$namespace}-login;\n\n.#{$prefix-cls} {\n  overflow: auto;\n\n  &__left {\n    &::before {\n      position: absolute;\n      top: 0;\n      left: 0;\n      z-index: -1;\n      width: 100%;\n      height: 100%;\n      background-image: url('@/assets/svgs/login-bg.svg');\n      background-position: center;\n      background-repeat: no-repeat;\n      content: '';\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/Login/components/LoginForm.vue",
    "content": "<template>\n  <el-form\n    v-show=\"getShow\"\n    ref=\"formLogin\"\n    :model=\"loginData.loginForm\"\n    :rules=\"LoginRules\"\n    class=\"login-form\"\n    label-position=\"top\"\n    label-width=\"120px\"\n    size=\"large\"\n  >\n    <el-row style=\"maring-left: -10px; maring-right: -10px\">\n      <el-col :span=\"24\" style=\"padding-left: 10px; padding-right: 10px\">\n        <el-form-item>\n          <LoginFormTitle style=\"width: 100%\" />\n        </el-form-item>\n      </el-col>\n      <el-col :span=\"24\" style=\"padding-left: 10px; padding-right: 10px\">\n       <el-form-item v-if=\"loginData.tenantEnable === 'true'\" prop=\"tenantName\">\n          <el-input\n            v-model=\"loginData.loginForm.tenantName\"\n            :placeholder=\"t('login.tenantNamePlaceholder')\"\n            :prefix-icon=\"iconHouse\"\n            type=\"primary\"\n            link\n          />\n        </el-form-item>\n      </el-col>\n      <el-col :span=\"24\" style=\"padding-left: 10px; padding-right: 10px\">\n        <el-form-item prop=\"username\">\n          <el-input\n            v-model=\"loginData.loginForm.username\"\n            :placeholder=\"t('login.usernamePlaceholder')\"\n            :prefix-icon=\"iconAvatar\"\n          />\n        </el-form-item>\n      </el-col>\n      <el-col :span=\"24\" style=\"padding-left: 10px; padding-right: 10px\">\n        <el-form-item prop=\"password\">\n          <el-input\n            v-model=\"loginData.loginForm.password\"\n            :placeholder=\"t('login.passwordPlaceholder')\"\n            :prefix-icon=\"iconLock\"\n            show-password\n            type=\"password\"\n            @keyup.enter=\"getCode()\"\n          />\n        </el-form-item>\n      </el-col>\n      <el-col\n        :span=\"24\"\n        style=\"padding-left: 10px; padding-right: 10px; margin-top: -20px; margin-bottom: -20px\"\n      >\n        <el-form-item>\n          <el-row justify=\"space-between\" style=\"width: 100%\">\n            <el-col :span=\"6\">\n              <el-checkbox v-model=\"loginData.loginForm.rememberMe\">\n                {{ t('login.remember') }}\n              </el-checkbox>\n            </el-col>\n          </el-row>\n        </el-form-item>\n      </el-col>\n      <el-col :span=\"24\" style=\"padding-left: 10px; padding-right: 10px\">\n        <el-form-item>\n          <XButton\n            :loading=\"loginLoading\"\n            :title=\"t('login.login')\"\n            class=\"w-[100%]\"\n            type=\"primary\"\n            @click=\"getCode()\"\n          />\n        </el-form-item>\n      </el-col>\n      <Verify\n        ref=\"verify\"\n        :captchaType=\"captchaType\"\n        :imgSize=\"{ width: '400px', height: '200px' }\"\n        mode=\"pop\"\n        @success=\"handleLogin\"\n      />\n   \n    </el-row>\n  </el-form>\n</template>\n<script lang=\"ts\" name=\"LoginForm\" setup>\nimport { ElLoading } from 'element-plus'\nimport LoginFormTitle from './LoginFormTitle.vue'\nimport type { RouteLocationNormalizedLoaded } from 'vue-router'\n\nimport { useIcon } from '@/hooks/web/useIcon'\n\nimport * as authUtil from '@/utils/auth'\nimport { usePermissionStore } from '@/store/modules/permission'\nimport * as LoginApi from '@/api/login'\nimport { LoginStateEnum, useFormValid, useLoginState } from './useLogin'\n\nconst { t } = useI18n()\n//const message = useMessage()\nconst iconAvatar = useIcon({ icon: 'ep:avatar' })\nconst iconLock = useIcon({ icon: 'ep:lock' })\nconst formLogin = ref()\nconst { validForm } = useFormValid(formLogin)\nconst { getLoginState } = useLoginState()\nconst { currentRoute, push } = useRouter()\nconst permissionStore = usePermissionStore()\nconst redirect = ref<string>('')\nconst loginLoading = ref(false)\nconst verify = ref()\nconst captchaType = ref('blockPuzzle') // blockPuzzle 滑块 clickWord 点击文字\n\nconst getShow = computed(() => unref(getLoginState) === LoginStateEnum.LOGIN)\n\nconst LoginRules = {\n  tenantName: [required],\n  username: [required],\n  password: [required]\n}\nconst loginData = reactive({\n  isShowPassword: false,\n  captchaEnable: import.meta.env.VITE_APP_CAPTCHA_ENABLE,\n  tenantEnable: import.meta.env.VITE_APP_TENANT_ENABLE,\n  loginForm: {\n    tenantName: 'yshop',\n    //username: 'yshop002',\n    //password: '123456789',\n    username: 'admin',\n    password: 'admin123',\n    captchaVerification: '',\n    rememberMe: false\n  }\n})\n\n\n\n// 获取验证码\nconst getCode = async () => {\n  // 情况一，未开启：则直接登录\n  console.log('oginData.captchaEnable:',loginData.captchaEnable)\n  if (loginData.captchaEnable === 'false') {\n    await handleLogin({})\n  } else {\n    // 情况二，已开启：则展示验证码；只有完成验证码的情况，才进行登录\n    // 弹出验证码\n    verify.value.show()\n  }\n}\n//获取租户ID\nconst getTenantId = async () => {\n  if (loginData.tenantEnable === 'true') {\n    const res = await LoginApi.getTenantIdByName(loginData.loginForm.tenantName)\n    authUtil.setTenantId(res)\n  }\n}\n// 记住我\nconst getCookie = () => {\n  const loginForm = authUtil.getLoginForm()\n  if (loginForm) {\n    loginData.loginForm = {\n      ...loginData.loginForm,\n      username: loginForm.username ? loginForm.username : loginData.loginForm.username,\n      password: loginForm.password ? loginForm.password : loginData.loginForm.password,\n      rememberMe: loginForm.rememberMe ? true : false,\n      tenantName: loginForm.tenantName ? loginForm.tenantName : loginData.loginForm.tenantName\n    }\n  }\n}\n// 登录\nconst handleLogin = async (params) => {\n  loginLoading.value = true\n  try {\n    await getTenantId()\n    const data = await validForm()\n    if (!data) {\n      return\n    }\n    loginData.loginForm.captchaVerification = params.captchaVerification\n    const res = await LoginApi.login(loginData.loginForm)\n    if (!res) {\n      return\n    }\n    ElLoading.service({\n      lock: true,\n      text: '正在加载系统中...',\n      background: 'rgba(0, 0, 0, 0.7)'\n    })\n    if (loginData.loginForm.rememberMe) {\n      authUtil.setLoginForm(loginData.loginForm)\n    } else {\n      authUtil.removeLoginForm()\n    }\n    authUtil.setToken(res)\n    if (!redirect.value) {\n      redirect.value = '/'\n    }\n    // 判断是否为SSO登录\n    if (redirect.value.indexOf('sso') !== -1) {\n      window.location.href = window.location.href.replace('/login?redirect=', '')\n    } else {\n      push({ path: redirect.value || permissionStore.addRouters[0].path })\n    }\n  } catch {\n    loginLoading.value = false\n  } finally {\n    setTimeout(() => {\n      const loadingInstance = ElLoading.service()\n      loadingInstance.close()\n    }, 400)\n  }\n}\n\n// 社交登录\n// const doSocialLogin = async (type: number) => {\n//   if (type === 0) {\n//     message.error('此方式未配置')\n//   } else {\n//     loginLoading.value = true\n//     if (loginData.tenantEnable === 'true') {\n//       await message.prompt('请输入租户名称', t('common.reminder')).then(async ({ value }) => {\n//         const res = await LoginApi.getTenantIdByName(value)\n//         authUtil.setTenantId(res)\n//       })\n//     }\n//     // 计算 redirectUri\n//     const redirectUri =\n//       location.origin + '/social-login?type=' + type + '&redirect=' + (redirect.value || '/')\n//     // 进行跳转\n//     const res = await LoginApi.socialAuthRedirect(type, encodeURIComponent(redirectUri))\n//     window.location.href = res\n//   }\n// }\nwatch(\n  () => currentRoute.value,\n  (route: RouteLocationNormalizedLoaded) => {\n    redirect.value = route?.query?.redirect as string\n  },\n  {\n    immediate: true\n  }\n)\nonMounted(() => {\n  getCookie()\n})\n</script>\n\n<style lang=\"scss\" scoped>\n:deep(.anticon) {\n  &:hover {\n    color: var(--el-color-primary) !important;\n  }\n}\n\n.login-code {\n  width: 100%;\n  height: 38px;\n  float: right;\n\n  img {\n    cursor: pointer;\n    width: 100%;\n    max-width: 100px;\n    height: auto;\n    vertical-align: middle;\n  }\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/Login/components/LoginFormTitle.vue",
    "content": "<template>\n  <h2 class=\"enter-x mb-3 text-center text-2xl font-bold xl:text-center xl:text-3xl\">\n    {{ getFormTitle }}\n  </h2>\n</template>\n<script lang=\"ts\" setup>\nimport { LoginStateEnum, useLoginState } from './useLogin'\n\ndefineOptions({ name: 'LoginFormTitle' })\n\nconst { t } = useI18n()\n\nconst { getLoginState } = useLoginState()\n\nconst getFormTitle = computed(() => {\n  const titleObj = {\n    [LoginStateEnum.RESET_PASSWORD]: t('sys.login.forgetFormTitle'),\n    [LoginStateEnum.LOGIN]: t('sys.login.signInFormTitle'),\n    [LoginStateEnum.REGISTER]: t('sys.login.signUpFormTitle'),\n    [LoginStateEnum.MOBILE]: t('sys.login.mobileSignInFormTitle'),\n    [LoginStateEnum.QR_CODE]: t('sys.login.qrSignInFormTitle'),\n    [LoginStateEnum.SSO]: t('sys.login.ssoFormTitle')\n  }\n  return titleObj[unref(getLoginState)]\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/Login/components/MobileForm.vue",
    "content": "<template>\n  <el-form\n    v-show=\"getShow\"\n    ref=\"formSmsLogin\"\n    :model=\"loginData.loginForm\"\n    :rules=\"rules\"\n    class=\"login-form\"\n    label-position=\"top\"\n    label-width=\"120px\"\n    size=\"large\"\n  >\n    <el-row style=\"margin-right: -10px; margin-left: -10px\">\n      <!-- 租户名 -->\n      <el-col :span=\"24\" style=\"padding-right: 10px; padding-left: 10px\">\n        <el-form-item>\n          <LoginFormTitle style=\"width: 100%\" />\n        </el-form-item>\n      </el-col>\n      <el-col :span=\"24\" style=\"padding-right: 10px; padding-left: 10px\">\n        <el-form-item v-if=\"loginData.tenantEnable === 'true'\" prop=\"tenantName\">\n          <el-input\n            v-model=\"loginData.loginForm.tenantName\"\n            :placeholder=\"t('login.tenantNamePlaceholder')\"\n            :prefix-icon=\"iconHouse\"\n            type=\"primary\"\n            link\n          />\n        </el-form-item>\n      </el-col>\n      <!-- 手机号 -->\n      <el-col :span=\"24\" style=\"padding-right: 10px; padding-left: 10px\">\n        <el-form-item prop=\"mobileNumber\">\n          <el-input\n            v-model=\"loginData.loginForm.mobileNumber\"\n            :placeholder=\"t('login.mobileNumberPlaceholder')\"\n            :prefix-icon=\"iconCellphone\"\n          />\n        </el-form-item>\n      </el-col>\n      <!-- 验证码 -->\n      <el-col :span=\"24\" style=\"padding-right: 10px; padding-left: 10px\">\n        <el-form-item prop=\"code\">\n          <el-row :gutter=\"5\" justify=\"space-between\" style=\"width: 100%\">\n            <el-col :span=\"24\">\n              <el-input\n                v-model=\"loginData.loginForm.code\"\n                :placeholder=\"t('login.codePlaceholder')\"\n                :prefix-icon=\"iconCircleCheck\"\n              >\n                <!-- <el-button class=\"w-[100%]\"> -->\n                <template #append>\n                  <span\n                    v-if=\"mobileCodeTimer <= 0\"\n                    class=\"getMobileCode\"\n                    style=\"cursor: pointer\"\n                    @click=\"getSmsCode\"\n                  >\n                    {{ t('login.getSmsCode') }}\n                  </span>\n                  <span v-if=\"mobileCodeTimer > 0\" class=\"getMobileCode\" style=\"cursor: pointer\">\n                    {{ mobileCodeTimer }}秒后可重新获取\n                  </span>\n                </template>\n              </el-input>\n              <!-- </el-button> -->\n            </el-col>\n          </el-row>\n        </el-form-item>\n      </el-col>\n      <!-- 登录按钮 / 返回按钮 -->\n      <el-col :span=\"24\" style=\"padding-right: 10px; padding-left: 10px\">\n        <el-form-item>\n          <XButton\n            :loading=\"loginLoading\"\n            :title=\"t('login.login')\"\n            class=\"w-[100%]\"\n            type=\"primary\"\n            @click=\"signIn()\"\n          />\n        </el-form-item>\n      </el-col>\n      <el-col :span=\"24\" style=\"padding-right: 10px; padding-left: 10px\">\n        <el-form-item>\n          <XButton\n            :loading=\"loginLoading\"\n            :title=\"t('login.backLogin')\"\n            class=\"w-[100%]\"\n            @click=\"handleBackLogin()\"\n          />\n        </el-form-item>\n      </el-col>\n    </el-row>\n  </el-form>\n</template>\n<script lang=\"ts\" setup>\nimport type { RouteLocationNormalizedLoaded } from 'vue-router'\n\nimport { useIcon } from '@/hooks/web/useIcon'\n\nimport { setTenantId, setToken } from '@/utils/auth'\nimport { usePermissionStore } from '@/store/modules/permission'\nimport { getTenantIdByName, sendSmsCode, smsLogin } from '@/api/login'\nimport LoginFormTitle from './LoginFormTitle.vue'\nimport { LoginStateEnum, useFormValid, useLoginState } from './useLogin'\nimport { ElLoading } from 'element-plus'\n\ndefineOptions({ name: 'MobileForm' })\n\nconst { t } = useI18n()\nconst message = useMessage()\nconst permissionStore = usePermissionStore()\nconst { currentRoute, push } = useRouter()\nconst formSmsLogin = ref()\nconst loginLoading = ref(false)\nconst iconHouse = useIcon({ icon: 'ep:house' })\nconst iconCellphone = useIcon({ icon: 'ep:cellphone' })\nconst iconCircleCheck = useIcon({ icon: 'ep:circle-check' })\nconst { validForm } = useFormValid(formSmsLogin)\nconst { handleBackLogin, getLoginState } = useLoginState()\nconst getShow = computed(() => unref(getLoginState) === LoginStateEnum.MOBILE)\n\nconst rules = {\n  tenantName: [required],\n  mobileNumber: [required],\n  code: [required]\n}\nconst loginData = reactive({\n  codeImg: '',\n  tenantEnable: import.meta.env.VITE_APP_TENANT_ENABLE,\n  token: '',\n  loading: {\n    signIn: false\n  },\n  loginForm: {\n    uuid: '',\n    tenantName: 'yshop源码',\n    mobileNumber: '',\n    code: ''\n  }\n})\nconst smsVO = reactive({\n  smsCode: {\n    mobile: '',\n    scene: 21\n  },\n  loginSms: {\n    mobile: '',\n    code: ''\n  }\n})\nconst mobileCodeTimer = ref(0)\nconst redirect = ref<string>('')\nconst getSmsCode = async () => {\n  await getTenantId()\n  smsVO.smsCode.mobile = loginData.loginForm.mobileNumber\n  await sendSmsCode(smsVO.smsCode).then(async () => {\n    message.success(t('login.SmsSendMsg'))\n    // 设置倒计时\n    mobileCodeTimer.value = 60\n    let msgTimer = setInterval(() => {\n      mobileCodeTimer.value = mobileCodeTimer.value - 1\n      if (mobileCodeTimer.value <= 0) {\n        clearInterval(msgTimer)\n      }\n    }, 1000)\n  })\n}\nwatch(\n  () => currentRoute.value,\n  (route: RouteLocationNormalizedLoaded) => {\n    redirect.value = route?.query?.redirect as string\n  },\n  {\n    immediate: true\n  }\n)\n// 获取租户 ID\nconst getTenantId = async () => {\n  if (loginData.tenantEnable === 'true') {\n    const res = await getTenantIdByName(loginData.loginForm.tenantName)\n    setTenantId(res)\n  }\n}\n// 登录\nconst signIn = async () => {\n  await getTenantId()\n  const data = await validForm()\n  if (!data) return\n  ElLoading.service({\n    lock: true,\n    text: '正在加载系统中...',\n    background: 'rgba(0, 0, 0, 0.7)'\n  })\n  loginLoading.value = true\n  smsVO.loginSms.mobile = loginData.loginForm.mobileNumber\n  smsVO.loginSms.code = loginData.loginForm.code\n  await smsLogin(smsVO.loginSms)\n    .then(async (res) => {\n      setToken(res)\n      if (!redirect.value) {\n        redirect.value = '/'\n      }\n      push({ path: redirect.value || permissionStore.addRouters[0].path })\n    })\n    .catch(() => {})\n    .finally(() => {\n      loginLoading.value = false\n      setTimeout(() => {\n        const loadingInstance = ElLoading.service()\n        loadingInstance.close()\n      }, 400)\n    })\n}\n</script>\n\n<style lang=\"scss\" scoped>\n:deep(.anticon) {\n  &:hover {\n    color: var(--el-color-primary) !important;\n  }\n}\n\n.smsbtn {\n  margin-top: 33px;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/Login/components/QrCodeForm.vue",
    "content": "<template>\n  <el-row v-show=\"getShow\" style=\"margin-right: -10px; margin-left: -10px\">\n    <el-col :span=\"24\" style=\"padding-right: 10px; padding-left: 10px\">\n      <LoginFormTitle style=\"width: 100%\" />\n    </el-col>\n    <el-col :span=\"24\" style=\"padding-right: 10px; padding-left: 10px\">\n      <el-card class=\"mb-10px text-center\" shadow=\"hover\">\n        <Qrcode :logo=\"logoImg\" />\n      </el-card>\n    </el-col>\n    <el-divider class=\"enter-x\">{{ t('login.qrcode') }}</el-divider>\n    <el-col :span=\"24\" style=\"padding-right: 10px; padding-left: 10px\">\n      <div class=\"mt-15px w-[100%]\">\n        <XButton :title=\"t('login.backLogin')\" class=\"w-[100%]\" @click=\"handleBackLogin()\" />\n      </div>\n    </el-col>\n  </el-row>\n</template>\n<script lang=\"ts\" setup>\nimport logoImg from '@/assets/imgs/logo.png'\n\nimport LoginFormTitle from './LoginFormTitle.vue'\nimport { LoginStateEnum, useLoginState } from './useLogin'\n\ndefineOptions({ name: 'QrCodeForm' })\n\nconst { t } = useI18n()\nconst { handleBackLogin, getLoginState } = useLoginState()\nconst getShow = computed(() => unref(getLoginState) === LoginStateEnum.QR_CODE)\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/Login/components/RegisterForm.vue",
    "content": "<template>\n  <Form\n    v-show=\"getShow\"\n    :rules=\"rules\"\n    :schema=\"schema\"\n    class=\"dark:(border-1 border-[var(--el-border-color)] border-solid)\"\n    hide-required-asterisk\n    label-position=\"top\"\n    size=\"large\"\n    @register=\"register\"\n  >\n    <template #title>\n      <LoginFormTitle style=\"width: 100%\" />\n    </template>\n\n    <template #code=\"form\">\n      <div class=\"w-[100%] flex\">\n        <el-input v-model=\"form['code']\" :placeholder=\"t('login.codePlaceholder')\" />\n      </div>\n    </template>\n\n    <template #register>\n      <div class=\"w-[100%]\">\n        <XButton\n          :loading=\"loading\"\n          :title=\"t('login.register')\"\n          class=\"w-[100%]\"\n          type=\"primary\"\n          @click=\"loginRegister()\"\n        />\n      </div>\n      <div class=\"mt-15px w-[100%]\">\n        <XButton :title=\"t('login.hasUser')\" class=\"w-[100%]\" @click=\"handleBackLogin()\" />\n      </div>\n    </template>\n  </Form>\n</template>\n<script lang=\"ts\" setup>\nimport type { FormRules } from 'element-plus'\n\nimport { useForm } from '@/hooks/web/useForm'\nimport { useValidator } from '@/hooks/web/useValidator'\nimport LoginFormTitle from './LoginFormTitle.vue'\nimport { LoginStateEnum, useLoginState } from './useLogin'\nimport { FormSchema } from '@/types/form'\n\ndefineOptions({ name: 'RegisterForm' })\n\nconst { t } = useI18n()\nconst { required } = useValidator()\nconst { register, elFormRef } = useForm()\nconst { handleBackLogin, getLoginState } = useLoginState()\nconst getShow = computed(() => unref(getLoginState) === LoginStateEnum.REGISTER)\n\nconst schema = reactive<FormSchema[]>([\n  {\n    field: 'title',\n    colProps: {\n      span: 24\n    }\n  },\n  {\n    field: 'username',\n    label: t('login.username'),\n    value: '',\n    component: 'Input',\n    colProps: {\n      span: 24\n    },\n    componentProps: {\n      placeholder: t('login.usernamePlaceholder')\n    }\n  },\n  {\n    field: 'password',\n    label: t('login.password'),\n    value: '',\n    component: 'InputPassword',\n    colProps: {\n      span: 24\n    },\n    componentProps: {\n      style: {\n        width: '100%'\n      },\n      strength: true,\n      placeholder: t('login.passwordPlaceholder')\n    }\n  },\n  {\n    field: 'check_password',\n    label: t('login.checkPassword'),\n    value: '',\n    component: 'InputPassword',\n    colProps: {\n      span: 24\n    },\n    componentProps: {\n      style: {\n        width: '100%'\n      },\n      strength: true,\n      placeholder: t('login.passwordPlaceholder')\n    }\n  },\n  {\n    field: 'code',\n    label: t('login.code'),\n    colProps: {\n      span: 24\n    }\n  },\n  {\n    field: 'register',\n    colProps: {\n      span: 24\n    }\n  }\n])\n\nconst rules: FormRules = {\n  username: [required()],\n  password: [required()],\n  check_password: [required()],\n  code: [required()]\n}\n\nconst loading = ref(false)\n\nconst loginRegister = async () => {\n  const formRef = unref(elFormRef)\n  formRef?.validate(async (valid) => {\n    if (valid) {\n      try {\n        loading.value = true\n      } finally {\n        loading.value = false\n      }\n    }\n  })\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/Login/components/SSOLogin.vue",
    "content": "<template>\n  <div v-show=\"ssoVisible\" class=\"form-cont\">\n    <!-- 应用名 -->\n    <LoginFormTitle style=\"width: 100%\" />\n    <el-tabs class=\"form\" style=\"float: none\" value=\"uname\">\n      <el-tab-pane :label=\"client.name\" name=\"uname\" />\n    </el-tabs>\n    <div>\n      <el-form :model=\"formData\" class=\"login-form\">\n        <!-- 授权范围的选择 -->\n        此第三方应用请求获得以下权限：\n        <el-form-item prop=\"scopes\">\n          <el-checkbox-group v-model=\"formData.scopes\">\n            <el-checkbox\n              v-for=\"scope in queryParams.scopes\"\n              :key=\"scope\"\n              :label=\"scope\"\n              style=\"display: block; margin-bottom: -10px\"\n            >\n              {{ formatScope(scope) }}\n            </el-checkbox>\n          </el-checkbox-group>\n        </el-form-item>\n        <!-- 下方的登录按钮 -->\n        <el-form-item class=\"w-1/1\">\n          <el-button\n            :loading=\"formLoading\"\n            class=\"w-6/10\"\n            type=\"primary\"\n            @click.prevent=\"handleAuthorize(true)\"\n          >\n            <span v-if=\"!formLoading\">同意授权</span>\n            <span v-else>授 权 中...</span>\n          </el-button>\n          <el-button class=\"w-3/10\" @click.prevent=\"handleAuthorize(false)\">拒绝</el-button>\n        </el-form-item>\n      </el-form>\n    </div>\n  </div>\n</template>\n<script lang=\"ts\" setup>\nimport LoginFormTitle from './LoginFormTitle.vue'\nimport * as OAuth2Api from '@/api/login/oauth2'\nimport { LoginStateEnum, useLoginState } from './useLogin'\nimport type { RouteLocationNormalizedLoaded } from 'vue-router'\n\ndefineOptions({ name: 'SSOLogin' })\n\nconst route = useRoute() // 路由\nconst { currentRoute } = useRouter() // 路由\nconst { getLoginState, setLoginState } = useLoginState()\n\nconst client = ref({\n  // 客户端信息\n  name: '',\n  logo: ''\n})\ninterface queryType {\n  responseType: string\n  clientId: string\n  redirectUri: string\n  state: string\n  scopes: string[]\n}\nconst queryParams = reactive<queryType>({\n  // URL 上的 client_id、scope 等参数\n  responseType: '',\n  clientId: '',\n  redirectUri: '',\n  state: '',\n  scopes: [] // 优先从 query 参数获取；如果未传递，从后端获取\n})\nconst ssoVisible = computed(() => unref(getLoginState) === LoginStateEnum.SSO) // 是否展示 SSO 登录的表单\ninterface formType {\n  scopes: string[]\n}\nconst formData = reactive<formType>({\n  scopes: [] // 已选中的 scope 数组\n})\nconst formLoading = ref(false) // 表单是否提交中\n\n/** 初始化授权信息 */\nconst init = async () => {\n  // 防止在没有登录的情况下循环弹窗\n  if (typeof route.query.client_id === 'undefined') return\n  // 解析参数\n  // 例如说【自动授权不通过】：client_id=default&redirect_uri=https%3A%2F%2Fwww.yixiang.co&response_type=code&scope=user.read%20user.write\n  // 例如说【自动授权通过】：client_id=default&redirect_uri=https%3A%2F%2Fwww.yixiang.co&response_type=code&scope=user.read\n  queryParams.responseType = route.query.response_type as string\n  queryParams.clientId = route.query.client_id as string\n  queryParams.redirectUri = route.query.redirect_uri as string\n  queryParams.state = route.query.state as string\n  if (route.query.scope) {\n    queryParams.scopes = (route.query.scope as string).split(' ')\n  }\n\n  // 如果有 scope 参数，先执行一次自动授权，看看是否之前都授权过了。\n  if (queryParams.scopes.length > 0) {\n    const data = await doAuthorize(true, queryParams.scopes, [])\n    if (data) {\n      location.href = data\n      return\n    }\n  }\n\n  // 获取授权页的基本信息\n  const data = await OAuth2Api.getAuthorize(queryParams.clientId)\n  client.value = data.client\n  // 解析 scope\n  let scopes\n  // 1.1 如果 params.scope 非空，则过滤下返回的 scopes\n  if (queryParams.scopes.length > 0) {\n    scopes = []\n    for (const scope of data.scopes) {\n      if (queryParams.scopes.indexOf(scope.key) >= 0) {\n        scopes.push(scope)\n      }\n    }\n    // 1.2 如果 params.scope 为空，则使用返回的 scopes 设置它\n  } else {\n    scopes = data.scopes\n    for (const scope of scopes) {\n      queryParams.scopes.push(scope.key)\n    }\n  }\n  // 生成已选中的 checkedScopes\n  for (const scope of scopes) {\n    if (scope.value) {\n      formData.scopes.push(scope.key)\n    }\n  }\n}\n\n/** 处理授权的提交 */\nconst handleAuthorize = async (approved) => {\n  // 计算 checkedScopes + uncheckedScopes\n  let checkedScopes\n  let uncheckedScopes\n  if (approved) {\n    // 同意授权，按照用户的选择\n    checkedScopes = formData.scopes\n    uncheckedScopes = queryParams.scopes.filter((item) => checkedScopes.indexOf(item) === -1)\n  } else {\n    // 拒绝，则都是取消\n    checkedScopes = []\n    uncheckedScopes = queryParams.scopes\n  }\n  // 提交授权的请求\n  formLoading.value = true\n  try {\n    const data = await doAuthorize(false, checkedScopes, uncheckedScopes)\n    if (!data) {\n      return\n    }\n    location.href = data\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 调用授权 API 接口 */\nconst doAuthorize = (autoApprove, checkedScopes, uncheckedScopes) => {\n  return OAuth2Api.authorize(\n    queryParams.responseType,\n    queryParams.clientId,\n    queryParams.redirectUri,\n    queryParams.state,\n    autoApprove,\n    checkedScopes,\n    uncheckedScopes\n  )\n}\n\n/** 格式化 scope 文本 */\nconst formatScope = (scope) => {\n  // 格式化 scope 授权范围，方便用户理解。\n  // 这里仅仅是一个 demo，可以考虑录入到字典数据中，例如说字典类型 \"system_oauth2_scope\"，它的每个 scope 都是一条字典数据。\n  switch (scope) {\n    case 'user.read':\n      return '访问你的个人信息'\n    case 'user.write':\n      return '修改你的个人信息'\n    default:\n      return scope\n  }\n}\n\n/** 监听当前路由为 SSOLogin 时，进行数据的初始化 */\nwatch(\n  () => currentRoute.value,\n  (route: RouteLocationNormalizedLoaded) => {\n    if (route.name === 'SSOLogin') {\n      setLoginState(LoginStateEnum.SSO)\n      init()\n    }\n  },\n  { immediate: true }\n)\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/Login/components/index.ts",
    "content": "import LoginForm from './LoginForm.vue'\nimport MobileForm from './MobileForm.vue'\nimport LoginFormTitle from './LoginFormTitle.vue'\nimport RegisterForm from './RegisterForm.vue'\nimport QrCodeForm from './QrCodeForm.vue'\nimport SSOLoginVue from './SSOLogin.vue'\n\nexport { LoginForm, MobileForm, LoginFormTitle, RegisterForm, QrCodeForm, SSOLoginVue }\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/Login/components/useLogin.ts",
    "content": "import { Ref } from 'vue'\n\nexport enum LoginStateEnum {\n  LOGIN,\n  REGISTER,\n  RESET_PASSWORD,\n  MOBILE,\n  QR_CODE,\n  SSO\n}\n\nconst currentState = ref(LoginStateEnum.LOGIN)\n\nexport function useLoginState() {\n  function setLoginState(state: LoginStateEnum) {\n    currentState.value = state\n  }\n  const getLoginState = computed(() => currentState.value)\n\n  function handleBackLogin() {\n    setLoginState(LoginStateEnum.LOGIN)\n  }\n\n  return {\n    setLoginState,\n    getLoginState,\n    handleBackLogin\n  }\n}\n\nexport function useFormValid<T extends Object = any>(formRef: Ref<any>) {\n  async function validForm() {\n    const form = unref(formRef)\n    if (!form) return\n    const data = await form.validate()\n    return data as T\n  }\n\n  return {\n    validForm\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/Profile/Index.vue",
    "content": "<template>\n  <div class=\"flex\">\n    <el-card class=\"user w-1/3\" shadow=\"hover\">\n      <template #header>\n        <div class=\"card-header\">\n          <span>{{ t('profile.user.title') }}</span>\n        </div>\n      </template>\n      <ProfileUser />\n    </el-card>\n    <el-card class=\"user ml-3 w-2/3\" shadow=\"hover\">\n      <template #header>\n        <div class=\"card-header\">\n          <span>{{ t('profile.info.title') }}</span>\n        </div>\n      </template>\n      <div>\n        <el-tabs v-model=\"activeName\" class=\"profile-tabs\" style=\"height: 400px\" tab-position=\"top\">\n          <el-tab-pane :label=\"t('profile.info.basicInfo')\" name=\"basicInfo\">\n            <BasicInfo />\n          </el-tab-pane>\n          <el-tab-pane :label=\"t('profile.info.resetPwd')\" name=\"resetPwd\">\n            <ResetPwd />\n          </el-tab-pane>\n          <el-tab-pane :label=\"t('profile.info.userSocial')\" name=\"userSocial\">\n            <UserSocial v-model:activeName=\"activeName\" />\n          </el-tab-pane>\n        </el-tabs>\n      </div>\n    </el-card>\n  </div>\n</template>\n<script lang=\"ts\" setup>\nimport { BasicInfo, ProfileUser, ResetPwd, UserSocial } from './components'\n\nconst { t } = useI18n()\ndefineOptions({ name: 'Profile' })\nconst activeName = ref('basicInfo')\n</script>\n<style scoped>\n.user {\n  max-height: 960px;\n  padding: 15px 20px 20px;\n}\n\n.card-header {\n  display: flex;\n  justify-content: center;\n  align-items: center;\n}\n\n:deep(.el-card .el-card__header, .el-card .el-card__body) {\n  padding: 15px !important;\n}\n\n.profile-tabs > .el-tabs__content {\n  padding: 32px;\n  font-weight: 600;\n  color: #6b778c;\n}\n\n.el-tabs--left .el-tabs__content {\n  height: 100%;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/Profile/components/BasicInfo.vue",
    "content": "<template>\n  <Form ref=\"formRef\" :labelWidth=\"200\" :rules=\"rules\" :schema=\"schema\">\n    <template #sex=\"form\">\n      <el-radio-group v-model=\"form['sex']\">\n        <el-radio :label=\"1\">{{ t('profile.user.man') }}</el-radio>\n        <el-radio :label=\"2\">{{ t('profile.user.woman') }}</el-radio>\n      </el-radio-group>\n    </template>\n  </Form>\n  <div style=\"text-align: center\">\n    <XButton :title=\"t('common.save')\" type=\"primary\" @click=\"submit()\" />\n    <XButton :title=\"t('common.reset')\" type=\"danger\" @click=\"init()\" />\n  </div>\n</template>\n<script lang=\"ts\" setup>\nimport type { FormRules } from 'element-plus'\nimport { FormSchema } from '@/types/form'\nimport type { FormExpose } from '@/components/Form'\nimport {\n  getUserProfile,\n  updateUserProfile,\n  UserProfileUpdateReqVO\n} from '@/api/system/user/profile'\nimport { useUserStore } from '@/store/modules/user'\n\ndefineOptions({ name: 'BasicInfo' })\n\nconst { t } = useI18n()\nconst message = useMessage() // 消息弹窗\nconst userStore = useUserStore() \n// 表单校验\nconst rules = reactive<FormRules>({\n  nickname: [{ required: true, message: t('profile.rules.nickname'), trigger: 'blur' }],\n  email: [\n    { required: true, message: t('profile.rules.mail'), trigger: 'blur' },\n    {\n      type: 'email',\n      message: t('profile.rules.truemail'),\n      trigger: ['blur', 'change']\n    }\n  ],\n  mobile: [\n    { required: true, message: t('profile.rules.phone'), trigger: 'blur' },\n    {\n      pattern: /^1[3|4|5|6|7|8|9][0-9]\\d{8}$/,\n      message: t('profile.rules.truephone'),\n      trigger: 'blur'\n    }\n  ]\n})\nconst schema = reactive<FormSchema[]>([\n  {\n    field: 'nickname',\n    label: t('profile.user.nickname'),\n    component: 'Input'\n  },\n  {\n    field: 'mobile',\n    label: t('profile.user.mobile'),\n    component: 'Input'\n  },\n  {\n    field: 'email',\n    label: t('profile.user.email'),\n    component: 'Input'\n  },\n  {\n    field: 'sex',\n    label: t('profile.user.sex'),\n    component: 'InputNumber',\n    value: 0\n  }\n])\nconst formRef = ref<FormExpose>() // 表单 Ref\nconst submit = () => {\n  const elForm = unref(formRef)?.getElFormRef()\n  if (!elForm) return\n  elForm.validate(async (valid) => {\n    if (valid) {\n      const data = unref(formRef)?.formModel as UserProfileUpdateReqVO\n      await updateUserProfile(data)\n      message.success(t('common.updateSuccess'))\n      const profile = await init()\n      userStore.setUserNicknameAction(profile.nickname)\n    }\n  })\n}\nconst init = async () => {\n  const res = await getUserProfile()\n  unref(formRef)?.setValues(res)\n  return res\n}\nonMounted(async () => {\n  await init()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/Profile/components/ProfileUser.vue",
    "content": "<template>\n  <div>\n    <div class=\"text-center\">\n      <UserAvatar :img=\"userInfo?.avatar\" />\n    </div>\n    <ul class=\"list-group list-group-striped\">\n      <li class=\"list-group-item\">\n        <Icon class=\"mr-5px\" icon=\"ep:user\" />\n        {{ t('profile.user.username') }}\n        <div class=\"pull-right\">{{ userInfo?.username }}</div>\n      </li>\n      <li class=\"list-group-item\">\n        <Icon class=\"mr-5px\" icon=\"ep:phone\" />\n        {{ t('profile.user.mobile') }}\n        <div class=\"pull-right\">{{ userInfo?.mobile }}</div>\n      </li>\n      <li class=\"list-group-item\">\n        <Icon class=\"mr-5px\" icon=\"fontisto:email\" />\n        {{ t('profile.user.email') }}\n        <div class=\"pull-right\">{{ userInfo?.email }}</div>\n      </li>\n      <li class=\"list-group-item\">\n        <Icon class=\"mr-5px\" icon=\"carbon:tree-view-alt\" />\n        {{ t('profile.user.dept') }}\n        <div v-if=\"userInfo?.dept\" class=\"pull-right\">{{ userInfo?.dept.name }}</div>\n      </li>\n      <li class=\"list-group-item\">\n        <Icon class=\"mr-5px\" icon=\"ep:suitcase\" />\n        {{ t('profile.user.posts') }}\n        <div v-if=\"userInfo?.posts\" class=\"pull-right\">\n          {{ userInfo?.posts.map((post) => post.name).join(',') }}\n        </div>\n      </li>\n      <li class=\"list-group-item\">\n        <Icon class=\"mr-5px\" icon=\"icon-park-outline:peoples\" />\n        {{ t('profile.user.roles') }}\n        <div v-if=\"userInfo?.roles\" class=\"pull-right\">\n          {{ userInfo?.roles.map((role) => role.name).join(',') }}\n        </div>\n      </li>\n      <li class=\"list-group-item\">\n        <Icon class=\"mr-5px\" icon=\"ep:calendar\" />\n        {{ t('profile.user.createTime') }}\n        <div class=\"pull-right\">{{ formatDate(userInfo.createTime) }}</div>\n      </li>\n    </ul>\n  </div>\n</template>\n<script lang=\"ts\" setup>\nimport { formatDate } from '@/utils/formatTime'\nimport UserAvatar from './UserAvatar.vue'\n\nimport { getUserProfile, ProfileVO } from '@/api/system/user/profile'\n\ndefineOptions({ name: 'ProfileUser' })\n\nconst { t } = useI18n()\nconst userInfo = ref({} as ProfileVO)\nconst getUserInfo = async () => {\n  const users = await getUserProfile()\n  userInfo.value = users\n}\nonMounted(async () => {\n  await getUserInfo()\n})\n</script>\n\n<style scoped>\n.text-center {\n  position: relative;\n  height: 120px;\n  text-align: center;\n}\n\n.list-group-striped > .list-group-item {\n  padding-right: 0;\n  padding-left: 0;\n  border-right: 0;\n  border-left: 0;\n  border-radius: 0;\n}\n\n.list-group {\n  padding-left: 0;\n  list-style: none;\n}\n\n.list-group-item {\n  padding: 11px 0;\n  margin-bottom: -1px;\n  font-size: 13px;\n  border-top: 1px solid #e7eaec;\n  border-bottom: 1px solid #e7eaec;\n}\n\n.pull-right {\n  float: right !important;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/Profile/components/ResetPwd.vue",
    "content": "<template>\n  <el-form ref=\"formRef\" :model=\"password\" :rules=\"rules\" :label-width=\"200\">\n    <el-form-item :label=\"t('profile.password.oldPassword')\" prop=\"oldPassword\">\n      <InputPassword v-model=\"password.oldPassword\" />\n    </el-form-item>\n    <el-form-item :label=\"t('profile.password.newPassword')\" prop=\"newPassword\">\n      <InputPassword v-model=\"password.newPassword\" strength />\n    </el-form-item>\n    <el-form-item :label=\"t('profile.password.confirmPassword')\" prop=\"confirmPassword\">\n      <InputPassword v-model=\"password.confirmPassword\" strength />\n    </el-form-item>\n    <el-form-item>\n      <XButton :title=\"t('common.save')\" type=\"primary\" @click=\"submit(formRef)\" />\n      <XButton :title=\"t('common.reset')\" type=\"danger\" @click=\"reset(formRef)\" />\n    </el-form-item>\n  </el-form>\n</template>\n<script lang=\"ts\" setup>\nimport type { FormInstance, FormRules } from 'element-plus'\n\nimport { InputPassword } from '@/components/InputPassword'\nimport { updateUserPassword } from '@/api/system/user/profile'\n\ndefineOptions({ name: 'ResetPwd' })\n\nconst { t } = useI18n()\nconst message = useMessage()\nconst formRef = ref<FormInstance>()\nconst password = reactive({\n  oldPassword: '',\n  newPassword: '',\n  confirmPassword: ''\n})\n\n// 表单校验\nconst equalToPassword = (_rule, value, callback) => {\n  if (password.newPassword !== value) {\n    callback(new Error(t('profile.password.diffPwd')))\n  } else {\n    callback()\n  }\n}\n\nconst rules = reactive<FormRules>({\n  oldPassword: [\n    { required: true, message: t('profile.password.oldPwdMsg'), trigger: 'blur' },\n    { min: 6, max: 20, message: t('profile.password.pwdRules'), trigger: 'blur' }\n  ],\n  newPassword: [\n    { required: true, message: t('profile.password.newPwdMsg'), trigger: 'blur' },\n    { min: 6, max: 20, message: t('profile.password.pwdRules'), trigger: 'blur' }\n  ],\n  confirmPassword: [\n    { required: true, message: t('profile.password.cfPwdMsg'), trigger: 'blur' },\n    { required: true, validator: equalToPassword, trigger: 'blur' }\n  ]\n})\n\nconst submit = (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  formEl.validate(async (valid) => {\n    if (valid) {\n      await updateUserPassword(password.oldPassword, password.newPassword)\n      message.success(t('common.updateSuccess'))\n    }\n  })\n}\n\nconst reset = (formEl: FormInstance | undefined) => {\n  if (!formEl) return\n  formEl.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/Profile/components/UserAvatar.vue",
    "content": "<template>\n  <div class=\"change-avatar\">\n    <CropperAvatar\n      ref=\"cropperRef\"\n      :btnProps=\"{ preIcon: 'ant-design:cloud-upload-outlined' }\"\n      :showBtn=\"false\"\n      :value=\"img\"\n      width=\"120px\"\n      @change=\"handelUpload\"\n    />\n  </div>\n</template>\n<script lang=\"ts\" setup>\nimport { propTypes } from '@/utils/propTypes'\nimport { uploadAvatar } from '@/api/system/user/profile'\nimport { CropperAvatar } from '@/components/Cropper'\nimport { useUserStore } from '@/store/modules/user'\n\n\ndefineOptions({ name: 'UserAvatar' })\n\ndefineProps({\n  img: propTypes.string.def('')\n})\n\nconst userStore = useUserStore()\n\n\nconst cropperRef = ref()\nconst handelUpload = async ({ data }) => {\n  const res = await uploadAvatar({ avatarFile: data })\n  cropperRef.value.close()\n  userStore.setUserAvatarAction(res.data)\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.change-avatar {\n  img {\n    display: block;\n    margin-bottom: 15px;\n    border-radius: 50%;\n  }\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/Profile/components/UserSocial.vue",
    "content": "<template>\n  <el-table :data=\"socialUsers\" :show-header=\"false\">\n    <el-table-column fixed=\"left\" title=\"序号\" type=\"seq\" width=\"60\" />\n    <el-table-column align=\"left\" label=\"社交平台\" width=\"120\">\n      <template #default=\"{ row }\">\n        <img :src=\"row.img\" alt=\"\" class=\"h-5 align-middle\" />\n        <p class=\"mr-5\">{{ row.title }}</p>\n      </template>\n    </el-table-column>\n    <el-table-column align=\"center\" label=\"操作\">\n      <template #default=\"{ row }\">\n        <template v-if=\"row.openid\">\n          已绑定\n          <XTextButton class=\"mr-5\" title=\"(解绑)\" type=\"primary\" @click=\"unbind(row)\" />\n        </template>\n        <template v-else>\n          未绑定\n          <XTextButton class=\"mr-5\" title=\"(绑定)\" type=\"primary\" @click=\"bind(row)\" />\n        </template>\n      </template>\n    </el-table-column>\n  </el-table>\n</template>\n<script lang=\"ts\" setup>\nimport { SystemUserSocialTypeEnum } from '@/utils/constants'\nimport { getUserProfile, ProfileVO } from '@/api/system/user/profile'\nimport { socialAuthRedirect, socialBind, socialUnbind } from '@/api/system/user/socialUser'\n\ndefineOptions({ name: 'UserSocial' })\ndefineProps<{\n  activeName: string\n}>()\nconst message = useMessage()\nconst socialUsers = ref<any[]>([])\nconst userInfo = ref<ProfileVO>()\n\nconst initSocial = async () => {\n  socialUsers.value = [] // 重置避免无限增长\n  const res = await getUserProfile()\n  userInfo.value = res\n  for (const i in SystemUserSocialTypeEnum) {\n    const socialUser = { ...SystemUserSocialTypeEnum[i] }\n    socialUsers.value.push(socialUser)\n    if (userInfo.value?.socialUsers) {\n      for (const j in userInfo.value.socialUsers) {\n        if (socialUser.type === userInfo.value.socialUsers[j].type) {\n          socialUser.openid = userInfo.value.socialUsers[j].openid\n          break\n        }\n      }\n    }\n  }\n}\nconst route = useRoute()\nconst emit = defineEmits<{\n  (e: 'update:activeName', v: string): void\n}>()\nconst bindSocial = () => {\n  // 社交绑定\n  const type = getUrlValue('type')\n  const code = route.query.code\n  const state = route.query.state\n  if (!code) {\n    return\n  }\n  socialBind(type, code, state).then(() => {\n    message.success('绑定成功')\n    emit('update:activeName', 'userSocial')\n  })\n}\n\n// 双层 encode 需要在回调后进行 decode\nfunction getUrlValue(key: string): string {\n  const url = new URL(decodeURIComponent(location.href))\n  return url.searchParams.get(key) ?? ''\n}\n\nconst bind = (row) => {\n  // 双层 encode 解决钉钉回调 type 参数丢失的问题\n  const redirectUri = location.origin + '/user/profile?' + encodeURIComponent(`type=${row.type}`)\n  // 进行跳转\n  socialAuthRedirect(row.type, encodeURIComponent(redirectUri)).then((res) => {\n    window.location.href = res\n  })\n}\nconst unbind = async (row) => {\n  const res = await socialUnbind(row.type, row.openid)\n  if (res) {\n    row.openid = undefined\n  }\n  message.success('解绑成功')\n}\n\nonMounted(async () => {\n  await initSocial()\n})\n\nwatch(\n  () => route,\n  () => {\n    bindSocial()\n  },\n  {\n    immediate: true\n  }\n)\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/Profile/components/index.ts",
    "content": "import BasicInfo from './BasicInfo.vue'\nimport ProfileUser from './ProfileUser.vue'\nimport ResetPwd from './ResetPwd.vue'\nimport UserAvatarVue from './UserAvatar.vue'\nimport UserSocial from './UserSocial.vue'\n\nexport { BasicInfo, ProfileUser, ResetPwd, UserAvatarVue, UserSocial }\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/Redirect/Redirect.vue",
    "content": "<template>\n  <div></div>\n</template>\n<script lang=\"ts\" setup>\ndefineOptions({ name: 'Redirect' })\n\nconst { currentRoute, replace } = useRouter()\nconst { params, query } = unref(currentRoute)\nconst { path, _redirect_type = 'path' } = params\n\nReflect.deleteProperty(params, '_redirect_type')\nReflect.deleteProperty(params, 'path')\n\nconst _path = Array.isArray(path) ? path.join('/') : path\n\nif (_redirect_type === 'name') {\n  replace({\n    name: _path,\n    query,\n    params\n  })\n} else {\n  replace({\n    path: _path.startsWith('/') ? _path : '/' + _path,\n    query\n  })\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/express/ExpressForm.vue",
    "content": "<template>\n  <Dialog :title=\"dialogTitle\" v-model=\"dialogVisible\">\n    <el-form\n      ref=\"formRef\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"120px\"\n      v-loading=\"formLoading\"\n    >\n      <el-form-item label=\"快递公司编号\" prop=\"code\">\n        <el-input v-model=\"formData.code\" placeholder=\"请输入快递公司编号\" />\n      </el-form-item>\n      <el-form-item label=\"快递公司全称\" prop=\"name\">\n        <el-input v-model=\"formData.name\" placeholder=\"请输入快递公司全称\" />\n      </el-form-item>\n      <el-form-item label=\"排序\" prop=\"sort\">\n        <el-input v-model=\"formData.sort\" placeholder=\"请输入排序\" />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button @click=\"submitForm\" type=\"primary\" :disabled=\"formLoading\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script setup lang=\"ts\">\nimport * as ExpressApi from '@/api/express'\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  code: undefined,\n  name: undefined,\n  sort: undefined\n})\nconst formRules = reactive({\n  code: [{ required: true, message: '快递公司简称不能为空', trigger: 'blur' }],\n  name: [{ required: true, message: '快递公司全称不能为空', trigger: 'blur' }],\n  sort: [{ required: true, message: '排序不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await ExpressApi.getExpress(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as unknown as ExpressApi.ExpressVO\n    if (formType.value === 'create') {\n      await ExpressApi.createExpress(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await ExpressApi.updateExpress(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    code: undefined,\n    name: undefined,\n    sort: undefined\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/express/ExpressSet.vue",
    "content": "<template>\n   <div>\n    <el-form\n      ref=\"formRef\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"120px\"\n      v-loading=\"formLoading\"\n    >\n      <el-form-item label=\"appId\" prop=\"eBusinessID\">\n        <el-input v-model=\"formData.ebusinessID\" placeholder=\"请输入快递鸟appId\" />\n      </el-form-item>\n      <el-form-item label=\"appKey\" prop=\"apiKey\">\n        <el-input v-model=\"formData.apiKey\" type=\"password\" placeholder=\"请输入快递鸟appKey\" />\n      </el-form-item>\n      <el-form-item label=\"是否收费套餐\" prop=\"isFree\">\n        <el-radio-group v-model=\"formData.isFree\">\n          <el-radio :label=\"false\">是</el-radio>\n          <el-radio :label=\"true\">否</el-radio>\n        </el-radio-group>\n      </el-form-item>\n      <el-form-item label=\" \" prop=\"\">\n        <el-button @click=\"submitForm\" type=\"primary\">确 定</el-button>\n        <el-button @click=\"dialogVisible = false\">取 消</el-button>\n      </el-form-item>\n      <el-form-item label=\" \" prop=\"\">\n        <span><span style=\"color: red;\">yshop-pro系统使用的第三方快递鸟api实现了快递查询，注册地址请点击，</span>\n          <a href=\"http://www.kdniao.com/reg?from=cbb-yx \" style=\"color:blue\">我注册！</a></span>\n      </el-form-item>\n    </el-form>\n    </div>\n</template>\n<script setup lang=\"ts\">\nimport * as ExpressApi from '@/api/express'\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(true) // 弹窗的是否展示\n// const dialogTitle = ref('快递鸟配置') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\n// const formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  ebusinessID: \"\",\n  apiKey: \"\",\n  isFree: true\n\n})\nconst formRules = reactive({\n  appId: [{ required: true, message: '快递鸟appId不能为空', trigger: 'blur' }],\n  appKey: [{ required: true, message: '快递鸟appKey不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\n\n/** 提交表单 */\n//const emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value\n    await ExpressApi.postExpressSet(data)\n    message.success(t('common.createSuccess'))\n    // 发送操作成功的事件\n   // emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 初始化 **/\nonMounted(() => {\n   getList()\n})\n\nconst getList = async () => {\n  const data = await ExpressApi.getExpressSet()\n  if (data) {\n     formData.value = data\n  }\n \n}\n// /** 重置表单 */\n// const resetForm = () => {\n//   formData.value = {\n//   appId: undefined,\n//   appKey: undefined\n//   }\n//   formRef.value?.resetFields()\n// }\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/express/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <!-- 搜索工作栏 -->\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"公司编码\" prop=\"code\">\n        <el-input\n          v-model=\"queryParams.code\"\n          placeholder=\"请输入快递公司编码\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"公司全称\" prop=\"name\">\n        <el-input\n          v-model=\"queryParams.name\"\n          placeholder=\"请输入快递公司全称\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n        <el-button\n          type=\"primary\"\n          plain\n          @click=\"openForm('create')\"\n          v-hasPermi=\"['order:express:create']\"\n        >\n          <Icon icon=\"ep:plus\" class=\"mr-5px\" /> 新增\n        </el-button>\n        <el-button\n          type=\"success\"\n          plain\n          @click=\"handleExport\"\n          :loading=\"exportLoading\"\n          v-hasPermi=\"['order:express:export']\"\n        >\n          <Icon icon=\"ep:download\" class=\"mr-5px\" /> 导出\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column label=\"id\" align=\"center\" prop=\"id\" />\n      <el-table-column label=\"快递公司编码\" align=\"center\" prop=\"code\" />\n      <el-table-column label=\"快递公司全称\" align=\"center\" prop=\"name\" />\n      <el-table-column label=\"排序\" align=\"center\" prop=\"sort\" />\n      <el-table-column\n        label=\"添加时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        :formatter=\"dateFormatter\"\n      />\n      <el-table-column label=\"操作\" align=\"center\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n            v-hasPermi=\"['order:express:update']\"\n          >\n            编辑\n          </el-button>\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n            v-hasPermi=\"['order:express:delete']\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <ExpressForm ref=\"formRef\" @success=\"getList\" />\n</template>\n\n<script setup lang=\"ts\" name=\"Express\">\nimport { dateFormatter } from '@/utils/formatTime'\nimport download from '@/utils/download'\nimport * as ExpressApi from '@/api/express'\nimport ExpressForm from './ExpressForm.vue'\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  code: null,\n  name: null\n})\nconst queryFormRef = ref() // 搜索的表单\nconst exportLoading = ref(false) // 导出的加载中\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await ExpressApi.getExpressPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await ExpressApi.deleteExpress(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 导出按钮操作 */\nconst handleExport = async () => {\n  try {\n    // 导出的二次确认\n    await message.exportConfirm()\n    // 发起导出\n    exportLoading.value = true\n    const data = await ExpressApi.exportExpress(queryParams)\n    download.excel(data, '快递公司.xls')\n  } catch {\n  } finally {\n    exportLoading.value = false\n  }\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/apiAccessLog/ApiAccessLogDetail.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" :max-height=\"500\" :scroll=\"true\" title=\"详情\" width=\"800\">\n    <el-descriptions :column=\"1\" border>\n      <el-descriptions-item label=\"日志主键\" min-width=\"120\">\n        {{ detailData.id }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"链路追踪\">\n        {{ detailData.traceId }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"应用名\">\n        {{ detailData.applicationName }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"用户信息\">\n        {{ detailData.userId }}\n        <dict-tag :type=\"DICT_TYPE.USER_TYPE\" :value=\"detailData.userType\" />\n      </el-descriptions-item>\n      <el-descriptions-item label=\"用户 IP\">\n        {{ detailData.userIp }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"用户 UA\">\n        {{ detailData.userAgent }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"请求信息\">\n        {{ detailData.requestMethod }} {{ detailData.requestUrl }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"请求参数\">\n        {{ detailData.requestParams }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"请求结果\">\n        {{ detailData.responseBody }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"请求时间\">\n        {{ formatDate(detailData.beginTime) }} ~ {{ formatDate(detailData.endTime) }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"请求耗时\">{{ detailData.duration }} ms</el-descriptions-item>\n      <el-descriptions-item label=\"操作结果\">\n        <div v-if=\"detailData.resultCode === 0\">正常</div>\n        <div v-else-if=\"detailData.resultCode > 0\">\n          失败 | {{ detailData.resultCode }} | {{ detailData.resultMsg }}\n        </div>\n      </el-descriptions-item>\n      <el-descriptions-item label=\"操作模块\">\n        {{ detailData.operateModule }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"操作名\">\n        {{ detailData.operateName }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"操作名\">\n        <dict-tag :type=\"DICT_TYPE.INFRA_OPERATE_TYPE\" :value=\"detailData.operateType\" />\n      </el-descriptions-item>\n    </el-descriptions>\n  </Dialog>\n</template>\n\n<script lang=\"ts\" setup>\nimport { DICT_TYPE } from '@/utils/dict'\nimport { formatDate } from '@/utils/formatTime'\nimport * as ApiAccessLog from '@/api/infra/apiAccessLog'\n\ndefineOptions({ name: 'ApiAccessLogDetail' })\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst detailLoading = ref(false) // 表单地加载中\nconst detailData = ref({} as ApiAccessLog.ApiAccessLogVO) // 详情数据\n\n/** 打开弹窗 */\nconst open = async (data: ApiAccessLog.ApiAccessLogVO) => {\n  dialogVisible.value = true\n  // 设置数据\n  detailLoading.value = true\n  try {\n    detailData.value = data\n  } finally {\n    detailLoading.value = false\n  }\n}\n\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/apiAccessLog/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <!-- 搜索工作栏 -->\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"用户编号\" prop=\"userId\">\n        <el-input\n          v-model=\"queryParams.userId\"\n          placeholder=\"请输入用户编号\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"用户类型\" prop=\"userType\">\n        <el-select\n          v-model=\"queryParams.userType\"\n          placeholder=\"请选择用户类型\"\n          clearable\n          class=\"!w-240px\"\n        >\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.USER_TYPE)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"应用名\" prop=\"applicationName\">\n        <el-input\n          v-model=\"queryParams.applicationName\"\n          placeholder=\"请输入应用名\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"请求时间\" prop=\"beginTime\">\n        <el-date-picker\n          v-model=\"queryParams.beginTime\"\n          value-format=\"YYYY-MM-DD HH:mm:ss\"\n          type=\"daterange\"\n          start-placeholder=\"开始日期\"\n          end-placeholder=\"结束日期\"\n          :default-time=\"[new Date('1 00:00:00'), new Date('1 23:59:59')]\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"执行时长\" prop=\"duration\">\n        <el-input\n          v-model=\"queryParams.duration\"\n          placeholder=\"请输入执行时长\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"结果码\" prop=\"resultCode\">\n        <el-input\n          v-model=\"queryParams.resultCode\"\n          placeholder=\"请输入结果码\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n        <el-button\n          type=\"success\"\n          plain\n          @click=\"handleExport\"\n          :loading=\"exportLoading\"\n          v-hasPermi=\"['infra:api-access-log:export']\"\n        >\n          <Icon icon=\"ep:download\" class=\"mr-5px\" /> 导出\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column label=\"日志编号\" align=\"center\" prop=\"id\" width=\"100\" fix=\"right\" />\n      <el-table-column label=\"用户编号\" align=\"center\" prop=\"userId\" />\n      <el-table-column label=\"用户类型\" align=\"center\" prop=\"userType\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.USER_TYPE\" :value=\"scope.row.userType\" />\n        </template>\n      </el-table-column>\n      <el-table-column label=\"应用名\" align=\"center\" prop=\"applicationName\" width=\"150\" />\n      <el-table-column label=\"请求方法\" align=\"center\" prop=\"requestMethod\" width=\"80\" />\n      <el-table-column label=\"请求地址\" align=\"center\" prop=\"requestUrl\" width=\"500\" />\n      <el-table-column label=\"请求时间\" align=\"center\" prop=\"beginTime\" width=\"180\">\n        <template #default=\"scope\">\n          <span>{{ formatDate(scope.row.beginTime) }}</span>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"执行时长\" align=\"center\" prop=\"duration\" width=\"180\">\n        <template #default=\"scope\"> {{ scope.row.duration }} ms </template>\n      </el-table-column>\n      <el-table-column label=\"操作结果\" align=\"center\" prop=\"status\">\n        <template #default=\"scope\">\n          {{ scope.row.resultCode === 0 ? '成功' : '失败(' + scope.row.resultMsg + ')' }}\n        </template>\n      </el-table-column>\n      <el-table-column label=\"操作模块\" align=\"center\" prop=\"operateModule\" width=\"180\" />\n      <el-table-column label=\"操作名\" align=\"center\" prop=\"operateName\" width=\"180\" />\n      <el-table-column label=\"操作类型\" align=\"center\" prop=\"operateType\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.INFRA_OPERATE_TYPE\" :value=\"scope.row.operateType\" />\n        </template>\n      </el-table-column>\n      <el-table-column label=\"操作\" align=\"center\" fixed=\"right\" width=\"60\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openDetail(scope.row)\"\n            v-hasPermi=\"['infra:api-access-log:query']\"\n          >\n            详细\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页组件 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：详情 -->\n  <ApiAccessLogDetail ref=\"detailRef\" />\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getIntDictOptions } from '@/utils/dict'\nimport download from '@/utils/download'\nimport { formatDate } from '@/utils/formatTime'\nimport * as ApiAccessLogApi from '@/api/infra/apiAccessLog'\nimport ApiAccessLogDetail from './ApiAccessLogDetail.vue'\n\ndefineOptions({ name: 'InfraApiAccessLog' })\n\nconst message = useMessage() // 消息弹窗\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  userId: null,\n  userType: null,\n  applicationName: null,\n  requestUrl: null,\n  duration: null,\n  resultCode: null,\n  beginTime: []\n})\nconst queryFormRef = ref() // 搜索的表单\nconst exportLoading = ref(false) // 导出的加载中\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await ApiAccessLogApi.getApiAccessLogPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 详情操作 */\nconst detailRef = ref()\nconst openDetail = (data: ApiAccessLogApi.ApiAccessLogVO) => {\n  detailRef.value.open(data)\n}\n\n/** 导出按钮操作 */\nconst handleExport = async () => {\n  try {\n    // 导出的二次确认\n    await message.exportConfirm()\n    // 发起导出\n    exportLoading.value = true\n    const data = await ApiAccessLogApi.exportApiAccessLog(queryParams)\n    download.excel(data, 'API 访问日志.xls')\n  } catch {\n  } finally {\n    exportLoading.value = false\n  }\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/apiErrorLog/ApiErrorLogDetail.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" :max-height=\"500\" :scroll=\"true\" title=\"详情\" width=\"800\">\n    <el-descriptions :column=\"1\" border>\n      <el-descriptions-item label=\"日志主键\" min-width=\"120\">\n        {{ detailData.id }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"链路追踪\">\n        {{ detailData.traceId }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"应用名\">\n        {{ detailData.applicationName }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"用户编号\">\n        {{ detailData.userId }}\n        <dict-tag :type=\"DICT_TYPE.USER_TYPE\" :value=\"detailData.userType\" />\n      </el-descriptions-item>\n      <el-descriptions-item label=\"用户 IP\">\n        {{ detailData.userIp }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"用户 UA\">\n        {{ detailData.userAgent }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"请求信息\">\n        {{ detailData.requestMethod }} {{ detailData.requestUrl }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"请求参数\">\n        {{ detailData.requestParams }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"异常时间\">\n        {{ formatDate(detailData.exceptionTime) }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"异常名\">\n        {{ detailData.exceptionName }}\n      </el-descriptions-item>\n      <el-descriptions-item v-if=\"detailData.exceptionStackTrace\" label=\"异常堆栈\">\n        <el-input\n          v-model=\"detailData.exceptionStackTrace\"\n          :autosize=\"{ maxRows: 20 }\"\n          :readonly=\"true\"\n          type=\"textarea\"\n        />\n      </el-descriptions-item>\n      <el-descriptions-item label=\"处理状态\">\n        <dict-tag\n          :type=\"DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS\"\n          :value=\"detailData.processStatus\"\n        />\n      </el-descriptions-item>\n      <el-descriptions-item v-if=\"detailData.processUserId\" label=\"处理人\">\n        {{ detailData.processUserId }}\n      </el-descriptions-item>\n      <el-descriptions-item v-if=\"detailData.processTime\" label=\"处理时间\">\n        {{ formatDate(detailData.processTime) }}\n      </el-descriptions-item>\n    </el-descriptions>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE } from '@/utils/dict'\nimport { formatDate } from '@/utils/formatTime'\nimport * as ApiErrorLog from '@/api/infra/apiErrorLog'\n\ndefineOptions({ name: 'ApiErrorLogDetail' })\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst detailLoading = ref(false) // 表单的加载中\nconst detailData = ref({} as ApiErrorLog.ApiErrorLogVO) // 详情数据\n\n/** 打开弹窗 */\nconst open = async (data: ApiErrorLog.ApiErrorLogVO) => {\n  dialogVisible.value = true\n  // 设置数据\n  detailLoading.value = true\n  try {\n    detailData.value = data\n  } finally {\n    detailLoading.value = false\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/apiErrorLog/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <!-- 搜索工作栏 -->\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"用户编号\" prop=\"userId\">\n        <el-input\n          v-model=\"queryParams.userId\"\n          placeholder=\"请输入用户编号\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"用户类型\" prop=\"userType\">\n        <el-select\n          v-model=\"queryParams.userType\"\n          placeholder=\"请选择用户类型\"\n          clearable\n          class=\"!w-240px\"\n        >\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.USER_TYPE)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"应用名\" prop=\"applicationName\">\n        <el-input\n          v-model=\"queryParams.applicationName\"\n          placeholder=\"请输入应用名\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"异常时间\" prop=\"exceptionTime\">\n        <el-date-picker\n          v-model=\"queryParams.exceptionTime\"\n          value-format=\"YYYY-MM-DD HH:mm:ss\"\n          type=\"daterange\"\n          start-placeholder=\"开始日期\"\n          end-placeholder=\"结束日期\"\n          :default-time=\"[new Date('1 00:00:00'), new Date('1 23:59:59')]\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"处理状态\" prop=\"processStatus\">\n        <el-select\n          v-model=\"queryParams.processStatus\"\n          placeholder=\"请选择处理状态\"\n          clearable\n          class=\"!w-240px\"\n        >\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n        <el-button\n          type=\"success\"\n          plain\n          @click=\"handleExport\"\n          :loading=\"exportLoading\"\n          v-hasPermi=\"['infra:api-error-log:export']\"\n        >\n          <Icon icon=\"ep:download\" class=\"mr-5px\" /> 导出\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column label=\"日志编号\" align=\"center\" prop=\"id\" />\n      <el-table-column label=\"用户编号\" align=\"center\" prop=\"userId\" />\n      <el-table-column label=\"用户类型\" align=\"center\" prop=\"userType\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.USER_TYPE\" :value=\"scope.row.userType\" />\n        </template>\n      </el-table-column>\n      <el-table-column label=\"应用名\" align=\"center\" prop=\"applicationName\" width=\"200\" />\n      <el-table-column label=\"请求方法\" align=\"center\" prop=\"requestMethod\" width=\"80\" />\n      <el-table-column label=\"请求地址\" align=\"center\" prop=\"requestUrl\" width=\"180\" />\n      <el-table-column\n        label=\"异常发生时间\"\n        align=\"center\"\n        prop=\"exceptionTime\"\n        width=\"180\"\n        :formatter=\"dateFormatter\"\n      />\n      <el-table-column label=\"异常名\" align=\"center\" prop=\"exceptionName\" width=\"180\" />\n      <el-table-column label=\"处理状态\" align=\"center\" prop=\"processStatus\">\n        <template #default=\"scope\">\n          <dict-tag\n            :type=\"DICT_TYPE.INFRA_API_ERROR_LOG_PROCESS_STATUS\"\n            :value=\"scope.row.processStatus\"\n          />\n        </template>\n      </el-table-column>\n      <el-table-column label=\"操作\" align=\"center\" width=\"200\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openDetail(scope.row)\"\n            v-hasPermi=\"['infra:api-error-log:query']\"\n          >\n            详细\n          </el-button>\n          <el-button\n            link\n            type=\"primary\"\n            v-if=\"scope.row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT\"\n            @click=\"handleProcess(scope.row.id, InfraApiErrorLogProcessStatusEnum.DONE)\"\n            v-hasPermi=\"['infra:api-error-log:update-status']\"\n          >\n            已处理\n          </el-button>\n          <el-button\n            link\n            type=\"primary\"\n            v-if=\"scope.row.processStatus === InfraApiErrorLogProcessStatusEnum.INIT\"\n            @click=\"handleProcess(scope.row.id, InfraApiErrorLogProcessStatusEnum.IGNORE)\"\n            v-hasPermi=\"['infra:api-error-log:update-status']\"\n          >\n            已忽略\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页组件 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：详情 -->\n  <ApiErrorLogDetail ref=\"detailRef\" />\n</template>\n\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getIntDictOptions } from '@/utils/dict'\nimport { dateFormatter } from '@/utils/formatTime'\nimport download from '@/utils/download'\nimport * as ApiErrorLogApi from '@/api/infra/apiErrorLog'\nimport ApiErrorLogDetail from './ApiErrorLogDetail.vue'\nimport { InfraApiErrorLogProcessStatusEnum } from '@/utils/constants'\n\ndefineOptions({ name: 'InfraApiErrorLog' })\n\nconst message = useMessage() // 消息弹窗\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  userId: null,\n  userType: null,\n  applicationName: null,\n  requestUrl: null,\n  processStatus: null,\n  exceptionTime: []\n})\nconst queryFormRef = ref() // 搜索的表单\nconst exportLoading = ref(false) // 导出的加载中\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await ApiErrorLogApi.getApiErrorLogPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 详情操作 */\nconst detailRef = ref()\nconst openDetail = (data: ApiErrorLogApi.ApiErrorLogVO) => {\n  detailRef.value.open(data)\n}\n\n/** 处理已处理 / 已忽略的操作 **/\nconst handleProcess = async (id: number, processStatus: number) => {\n  try {\n    // 操作的二次确认\n    const type = processStatus === InfraApiErrorLogProcessStatusEnum.DONE ? '已处理' : '已忽略'\n    await message.confirm('确认标记为' + type + '?')\n    // 执行操作\n    await ApiErrorLogApi.updateApiErrorLogPage(id, processStatus)\n    await message.success(type)\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 导出按钮操作 */\nconst handleExport = async () => {\n  try {\n    // 导出的二次确认\n    await message.exportConfirm()\n    // 发起导出\n    exportLoading.value = true\n    const data = await ApiErrorLogApi.exportApiErrorLog(queryParams)\n    download.excel(data, '异常日志.xls')\n  } catch {\n  } finally {\n    exportLoading.value = false\n  }\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/build/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <el-row>\n      <el-col>\n        <div class=\"float-right mb-2\">\n          <el-button size=\"small\" type=\"primary\" @click=\"showJson\">生成 JSON</el-button>\n          <el-button size=\"small\" type=\"success\" @click=\"showOption\">生成 Options</el-button>\n          <el-button size=\"small\" type=\"danger\" @click=\"showTemplate\">生成组件</el-button>\n        </div>\n      </el-col>\n    </el-row>\n    <!-- 表单设计器 -->\n    <FcDesigner ref=\"designer\" height=\"780px\" />\n  </ContentWrap>\n\n  <!-- 弹窗：表单预览 -->\n  <Dialog v-model=\"dialogVisible\" :title=\"dialogTitle\" max-height=\"600\">\n    <div v-if=\"dialogVisible\" ref=\"editor\">\n      <el-button style=\"float: right\" @click=\"copy(formData)\">\n        {{ t('common.copy') }}\n      </el-button>\n      <el-scrollbar height=\"580\">\n        <div>\n          <pre><code v-dompurify-html=\"highlightedCode(formData)\" class=\"hljs\"></code></pre>\n        </div>\n      </el-scrollbar>\n    </div>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport { useFormCreateDesigner } from '@/components/FormCreate'\nimport { useClipboard } from '@vueuse/core'\nimport { isString } from '@/utils/is'\n\nimport hljs from 'highlight.js' // 导入代码高亮文件\nimport 'highlight.js/styles/github.css' // 导入代码高亮样式\nimport xml from 'highlight.js/lib/languages/java'\nimport json from 'highlight.js/lib/languages/json'\nimport formCreate from '@form-create/element-ui'\n\ndefineOptions({ name: 'InfraBuild' })\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息\n\nconst designer = ref() // 表单设计器\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formType = ref(-1) // 表单的类型：0 - 生成 JSON；1 - 生成 Options；2 - 生成组件\nconst formData = ref('') // 表单数据\nuseFormCreateDesigner(designer) // 表单设计器增强\n\n/** 打开弹窗 */\nconst openModel = (title: string) => {\n  dialogVisible.value = true\n  dialogTitle.value = title\n}\n\n/** 生成 JSON */\nconst showJson = () => {\n  openModel('生成 JSON')\n  formType.value = 0\n  formData.value = designer.value.getRule()\n}\n\n/** 生成 Options */\nconst showOption = () => {\n  openModel('生成 Options')\n  formType.value = 1\n  formData.value = designer.value.getOption()\n}\n\n/** 生成组件 */\nconst showTemplate = () => {\n  openModel('生成组件')\n  formType.value = 2\n  formData.value = makeTemplate()\n}\n\nconst makeTemplate = () => {\n  const rule = designer.value.getRule()\n  const opt = designer.value.getOption()\n  return `<template>\n    <form-create\n      v-model:api=\"fApi\"\n      :rule=\"rule\"\n      :option=\"option\"\n      @submit=\"onSubmit\"\n    ></form-create>\n  </template>\n  <script setup lang=ts>\n    const faps = ref(null)\n    const rule = ref('')\n    const option = ref('')\n    const init = () => {\n      rule.value = formCreate.parseJson('${formCreate.toJson(rule).replaceAll('\\\\', '\\\\\\\\')}')\n      option.value = formCreate.parseJson('${JSON.stringify(opt)}')\n    }\n    const onSubmit = (formData) => {\n      //todo 提交表单\n    }\n    init()\n  <\\/script>`\n}\n\n/** 复制 **/\nconst copy = async (text: string) => {\n  const { copy, copied, isSupported } = useClipboard({ source: text })\n  if (!isSupported) {\n    message.error(t('common.copyError'))\n  } else {\n    await copy()\n    if (unref(copied)) {\n      message.success(t('common.copySuccess'))\n    }\n  }\n}\n\n/**\n * 代码高亮\n */\nconst highlightedCode = (code) => {\n  // 处理语言和代码\n  let language = 'json'\n  if (formType.value === 2) {\n    language = 'xml'\n  }\n  if (!isString(code)) {\n    code = JSON.stringify(code)\n  }\n  // 高亮\n  const result = hljs.highlight(language, code, true)\n  return result.value || '&nbsp;'\n}\n\n/** 初始化 **/\nonMounted(async () => {\n  // 注册代码高亮的各种语言\n  hljs.registerLanguage('xml', xml)\n  hljs.registerLanguage('json', json)\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/codegen/EditTable.vue",
    "content": "<template>\n  <ContentWrap v-loading=\"formLoading\">\n    <el-tabs v-model=\"activeName\">\n      <el-tab-pane label=\"基本信息\" name=\"basicInfo\">\n        <basic-info-form ref=\"basicInfoRef\" :table=\"formData.table\" />\n      </el-tab-pane>\n      <el-tab-pane label=\"字段信息\" name=\"colum\">\n        <colum-info-form ref=\"columInfoRef\" :columns=\"formData.columns\" />\n      </el-tab-pane>\n      <el-tab-pane label=\"生成信息\" name=\"generateInfo\">\n        <generate-info-form\n          ref=\"generateInfoRef\"\n          :table=\"formData.table\"\n          :columns=\"formData.columns\"\n        />\n      </el-tab-pane>\n    </el-tabs>\n    <el-form>\n      <el-form-item style=\"float: right\">\n        <el-button :loading=\"formLoading\" type=\"primary\" @click=\"submitForm\">保存</el-button>\n        <el-button @click=\"close\">返回</el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n</template>\n<script lang=\"ts\" setup>\nimport { useTagsViewStore } from '@/store/modules/tagsView'\nimport { BasicInfoForm, ColumInfoForm, GenerateInfoForm } from './components'\nimport * as CodegenApi from '@/api/infra/codegen'\n\ndefineOptions({ name: 'InfraCodegenEditTable' })\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\nconst { push, currentRoute } = useRouter() // 路由\nconst { query } = useRoute() // 查询参数\nconst { delView } = useTagsViewStore() // 视图操作\n\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst activeName = ref('colum') // Tag 激活的窗口\nconst basicInfoRef = ref<ComponentRef<typeof BasicInfoForm>>()\nconst columInfoRef = ref<ComponentRef<typeof ColumInfoForm>>()\nconst generateInfoRef = ref<ComponentRef<typeof GenerateInfoForm>>()\nconst formData = ref<CodegenApi.CodegenUpdateReqVO>({\n  table: {},\n  columns: []\n})\n\n/** 获得详情 */\nconst getDetail = async () => {\n  const id = query.id as unknown as number\n  if (!id) {\n    return\n  }\n  formLoading.value = true\n  try {\n    formData.value = await CodegenApi.getCodegenTable(id)\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 提交按钮 */\nconst submitForm = async () => {\n  // 参数校验\n  if (!unref(formData)) return\n  await unref(basicInfoRef)?.validate()\n  await unref(generateInfoRef)?.validate()\n  try {\n    // 提交请求\n    await CodegenApi.updateCodegenTable(formData.value)\n    message.success(t('common.updateSuccess'))\n    close()\n  } catch {}\n}\n\n/** 关闭按钮 */\nconst close = () => {\n  delView(unref(currentRoute))\n  push('/infra/codegen')\n}\n\n/** 初始化 */\nonMounted(() => {\n  getDetail()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/codegen/ImportTable.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" title=\"导入表\" width=\"800px\">\n    <!-- 搜索栏 -->\n    <el-form ref=\"queryFormRef\" :inline=\"true\" :model=\"queryParams\" label-width=\"68px\">\n      <el-form-item label=\"数据源\" prop=\"dataSourceConfigId\">\n        <el-select\n          v-model=\"queryParams.dataSourceConfigId\"\n          class=\"!w-240px\"\n          placeholder=\"请选择数据源\"\n        >\n          <el-option\n            v-for=\"config in dataSourceConfigList\"\n            :key=\"config.id\"\n            :label=\"config.name\"\n            :value=\"config.id\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"表名称\" prop=\"name\">\n        <el-input\n          v-model=\"queryParams.name\"\n          class=\"!w-240px\"\n          clearable\n          placeholder=\"请输入表名称\"\n          @keyup.enter=\"getList\"\n        />\n      </el-form-item>\n      <el-form-item label=\"表描述\" prop=\"comment\">\n        <el-input\n          v-model=\"queryParams.comment\"\n          class=\"!w-240px\"\n          clearable\n          placeholder=\"请输入表描述\"\n          @keyup.enter=\"getList\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"getList\">\n          <Icon class=\"mr-5px\" icon=\"ep:search\" />\n          搜索\n        </el-button>\n        <el-button @click=\"resetQuery\">\n          <Icon class=\"mr-5px\" icon=\"ep:refresh\" />\n          重置\n        </el-button>\n      </el-form-item>\n    </el-form>\n    <!-- 列表 -->\n    <el-row>\n      <el-table\n        ref=\"tableRef\"\n        v-loading=\"dbTableLoading\"\n        :data=\"dbTableList\"\n        height=\"260px\"\n        @row-click=\"handleRowClick\"\n        @selection-change=\"handleSelectionChange\"\n      >\n        <el-table-column type=\"selection\" width=\"55\" />\n        <el-table-column :show-overflow-tooltip=\"true\" label=\"表名称\" prop=\"name\" />\n        <el-table-column :show-overflow-tooltip=\"true\" label=\"表描述\" prop=\"comment\" />\n      </el-table>\n    </el-row>\n    <!-- 操作 -->\n    <template #footer>\n      <el-button :disabled=\"tableList.length === 0\" type=\"primary\" @click=\"handleImportTable\">\n        导入\n      </el-button>\n      <el-button @click=\"close\">关闭</el-button>\n    </template>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport * as CodegenApi from '@/api/infra/codegen'\nimport * as DataSourceConfigApi from '@/api/infra/dataSourceConfig'\nimport { ElTable } from 'element-plus'\n\ndefineOptions({ name: 'InfraCodegenImportTable' })\n\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dbTableLoading = ref(true) // 数据源的加载中\nconst dbTableList = ref<CodegenApi.DatabaseTableVO[]>([]) // 表的列表\nconst queryParams = reactive({\n  name: undefined,\n  comment: undefined,\n  dataSourceConfigId: 0\n})\nconst queryFormRef = ref() // 搜索的表单\nconst dataSourceConfigList = ref<DataSourceConfigApi.DataSourceConfigVO[]>([]) // 数据源列表\n\n/** 查询表数据 */\nconst getList = async () => {\n  dbTableLoading.value = true\n  try {\n    dbTableList.value = await CodegenApi.getSchemaTableList(queryParams)\n  } finally {\n    dbTableLoading.value = false\n  }\n}\n\n/** 重置操作 */\nconst resetQuery = async () => {\n  queryParams.name = undefined\n  queryParams.comment = undefined\n  queryParams.dataSourceConfigId = dataSourceConfigList.value[0].id as number\n  await getList()\n}\n\n/** 打开弹窗 */\nconst open = async () => {\n  // 加载数据源的列表\n  dataSourceConfigList.value = await DataSourceConfigApi.getDataSourceConfigList()\n  queryParams.dataSourceConfigId = dataSourceConfigList.value[0].id as number\n  dialogVisible.value = true\n  // 加载表的列表\n  await getList()\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 关闭弹窗 */\nconst close = () => {\n  dialogVisible.value = false\n  tableList.value = []\n}\n\nconst tableRef = ref<typeof ElTable>() // 表格的 Ref\nconst tableList = ref<string[]>([]) // 选中的表名\n\n/** 处理某一行的点击 */\nconst handleRowClick = (row) => {\n  unref(tableRef)?.toggleRowSelection(row)\n}\n\n/** 多选框选中数据 */\nconst handleSelectionChange = (selection) => {\n  tableList.value = selection.map((item) => item.name)\n}\n\n/** 导入按钮操作 */\nconst handleImportTable = async () => {\n  await CodegenApi.createCodegenList({\n    dataSourceConfigId: queryParams.dataSourceConfigId,\n    tableNames: tableList.value\n  })\n  message.success('导入成功')\n  emit('success')\n  close()\n}\nconst emit = defineEmits(['success'])\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/codegen/PreviewCode.vue",
    "content": "<template>\n  <Dialog\n    v-model=\"dialogVisible\"\n    align-center\n    class=\"app-infra-codegen-preview-container\"\n    title=\"代码预览\"\n    width=\"80%\"\n  >\n    <div class=\"flex\">\n      <!-- 代码目录树 -->\n      <el-card\n        v-loading=\"loading\"\n        :gutter=\"12\"\n        class=\"w-1/3\"\n        element-loading-text=\"生成文件目录中...\"\n        shadow=\"hover\"\n      >\n        <el-scrollbar height=\"calc(100vh - 88px - 40px)\">\n          <el-tree\n            ref=\"treeRef\"\n            :data=\"preview.fileTree\"\n            :expand-on-click-node=\"false\"\n            default-expand-all\n            highlight-current\n            node-key=\"id\"\n            @node-click=\"handleNodeClick\"\n          />\n        </el-scrollbar>\n      </el-card>\n      <!-- 代码 -->\n      <el-card\n        v-loading=\"loading\"\n        :gutter=\"12\"\n        class=\"ml-3 w-2/3\"\n        element-loading-text=\"加载代码中...\"\n        shadow=\"hover\"\n      >\n        <el-tabs v-model=\"preview.activeName\">\n          <el-tab-pane\n            v-for=\"item in previewCodegen\"\n            :key=\"item.filePath\"\n            :label=\"item.filePath.substring(item.filePath.lastIndexOf('/') + 1)\"\n            :name=\"item.filePath\"\n          >\n            <el-button class=\"float-right\" text type=\"primary\" @click=\"copy(item.code)\">\n              {{ t('common.copy') }}\n            </el-button>\n            <el-scrollbar height=\"600px\">\n              <pre><code v-dompurify-html=\"highlightedCode(item)\" class=\"hljs\"></code></pre>\n            </el-scrollbar>\n          </el-tab-pane>\n        </el-tabs>\n      </el-card>\n    </div>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport { useClipboard } from '@vueuse/core'\nimport { handleTree2 } from '@/utils/tree'\nimport * as CodegenApi from '@/api/infra/codegen'\n\nimport hljs from 'highlight.js' // 导入代码高亮文件\nimport 'highlight.js/styles/github.css' // 导入代码高亮样式\nimport java from 'highlight.js/lib/languages/java'\nimport xml from 'highlight.js/lib/languages/java'\nimport javascript from 'highlight.js/lib/languages/javascript'\nimport sql from 'highlight.js/lib/languages/sql'\nimport typescript from 'highlight.js/lib/languages/typescript'\n\ndefineOptions({ name: 'InfraCodegenPreviewCode' })\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst loading = ref(false) // 加载中的状态\nconst preview = reactive({\n  fileTree: [], // 文件树\n  activeName: '' // 激活的文件名\n})\nconst previewCodegen = ref<CodegenApi.CodegenPreviewVO[]>()\n\n/** 点击文件 */\nconst handleNodeClick = async (data, node) => {\n  if (node && !node.isLeaf) {\n    return false\n  }\n  preview.activeName = data.id\n}\n\n/** 生成 files 目录 **/\ninterface filesType {\n  id: string\n  label: string\n  parentId: string\n}\n\n/** 打开弹窗 */\nconst open = async (id: number) => {\n  dialogVisible.value = true\n  try {\n    loading.value = true\n    // 生成代码\n    const data = await CodegenApi.previewCodegen(id)\n    previewCodegen.value = data\n    // 处理文件\n    let file = handleFiles(data)\n    preview.fileTree = handleTree2(file, 'id', 'parentId', 'children', '/')\n    // 点击首个文件\n    preview.activeName = data[0].filePath\n  } finally {\n    loading.value = false\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 处理文件 */\nconst handleFiles = (datas: CodegenApi.CodegenPreviewVO[]) => {\n  let exists = {} // key：file 的 id；value：true\n  let files: filesType[] = []\n  // 遍历每个元素\n  for (const data of datas) {\n    let paths = data.filePath.split('/')\n    let fullPath = '' // 从头开始的路径，用于生成 id\n    // 特殊处理 java 文件\n    if (paths[paths.length - 1].indexOf('.java') >= 0) {\n      let newPaths: string[] = []\n      for (let i = 0; i < paths.length; i++) {\n        let path = paths[i]\n        if (path !== 'java') {\n          newPaths.push(path)\n          continue\n        }\n        newPaths.push(path)\n        // 特殊处理中间的 package，进行合并\n        let tmp = ''\n        while (i < paths.length) {\n          path = paths[i + 1]\n          if (\n            path === 'controller' ||\n            path === 'convert' ||\n            path === 'dal' ||\n            path === 'enums' ||\n            path === 'service' ||\n            path === 'vo' || // 下面三个，主要是兜底。可能考虑到有人改了包结构\n            path === 'mysql' ||\n            path === 'dataobject'\n          ) {\n            break\n          }\n          tmp = tmp ? tmp + '.' + path : path\n          i++\n        }\n        if (tmp) {\n          newPaths.push(tmp)\n        }\n      }\n      paths = newPaths\n    }\n    // 遍历每个 path， 拼接成树\n    for (let i = 0; i < paths.length; i++) {\n      // 已经添加到 files 中，则跳过\n      let oldFullPath = fullPath\n      // 下面的 replaceAll 的原因，是因为上面包处理了，导致和 tabs 不匹配，所以 replaceAll 下\n      fullPath = fullPath.length === 0 ? paths[i] : fullPath.replaceAll('.', '/') + '/' + paths[i]\n      if (exists[fullPath]) {\n        continue\n      }\n      // 添加到 files 中\n      exists[fullPath] = true\n      files.push({\n        id: fullPath,\n        label: paths[i],\n        parentId: oldFullPath || '/' // \"/\" 为根节点\n      })\n    }\n  }\n  return files\n}\n\n/** 复制 **/\nconst copy = async (text: string) => {\n  const { copy, copied, isSupported } = useClipboard({ source: text })\n  if (!isSupported) {\n    message.error(t('common.copyError'))\n    return\n  }\n  await copy()\n  if (unref(copied)) {\n    message.success(t('common.copySuccess'))\n  }\n}\n\n/**\n * 代码高亮\n */\nconst highlightedCode = (item) => {\n  const language = item.filePath.substring(item.filePath.lastIndexOf('.') + 1)\n  const result = hljs.highlight(language, item.code || '', true)\n  return result.value || '&nbsp;'\n}\n\n/** 初始化 **/\nonMounted(async () => {\n  // 注册代码高亮的各种语言\n  hljs.registerLanguage('java', java)\n  hljs.registerLanguage('xml', xml)\n  hljs.registerLanguage('html', xml)\n  hljs.registerLanguage('vue', xml)\n  hljs.registerLanguage('javascript', javascript)\n  hljs.registerLanguage('sql', sql)\n  hljs.registerLanguage('typescript', typescript)\n})\n</script>\n<style lang=\"scss\">\n.app-infra-codegen-preview-container {\n  .el-scrollbar .el-scrollbar__wrap .el-scrollbar__view {\n    display: inline-block;\n    white-space: nowrap;\n  }\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/codegen/components/BasicInfoForm.vue",
    "content": "<template>\n  <el-form ref=\"formRef\" :model=\"formData\" :rules=\"rules\" label-width=\"120px\">\n    <el-row>\n      <el-col :span=\"12\">\n        <el-form-item label=\"表名称\" prop=\"tableName\">\n          <el-input v-model=\"formData.tableName\" placeholder=\"请输入仓库名称\" />\n        </el-form-item>\n      </el-col>\n      <el-col :span=\"12\">\n        <el-form-item label=\"表描述\" prop=\"tableComment\">\n          <el-input v-model=\"formData.tableComment\" placeholder=\"请输入\" />\n        </el-form-item>\n      </el-col>\n      <el-col :span=\"12\">\n        <el-form-item prop=\"className\">\n          <template #label>\n            <span>\n              实体类名称\n              <el-tooltip\n                content=\"默认去除表名的前缀。如果存在重复，则需要手动添加前缀，避免 MyBatis 报 Alias 重复的问题。\"\n                placement=\"top\"\n              >\n                <Icon class=\"\" icon=\"ep:question-filled\" />\n              </el-tooltip>\n            </span>\n          </template>\n          <el-input v-model=\"formData.className\" placeholder=\"请输入\" />\n        </el-form-item>\n      </el-col>\n      <el-col :span=\"12\">\n        <el-form-item label=\"作者\" prop=\"author\">\n          <el-input v-model=\"formData.author\" placeholder=\"请输入\" />\n        </el-form-item>\n      </el-col>\n      <el-col :span=\"24\">\n        <el-form-item label=\"备注\" prop=\"remark\">\n          <el-input v-model=\"formData.remark\" :rows=\"3\" type=\"textarea\" />\n        </el-form-item>\n      </el-col>\n    </el-row>\n  </el-form>\n</template>\n<script lang=\"ts\" setup>\nimport * as CodegenApi from '@/api/infra/codegen'\nimport { PropType } from 'vue'\n\ndefineOptions({ name: 'InfraCodegenBasicInfoForm' })\n\nconst props = defineProps({\n  table: {\n    type: Object as PropType<Nullable<CodegenApi.CodegenTableVO>>,\n    default: () => null\n  }\n})\n\nconst formRef = ref()\nconst formData = ref({\n  tableName: '',\n  tableComment: '',\n  className: '',\n  author: '',\n  remark: ''\n})\nconst rules = reactive({\n  tableName: [required],\n  tableComment: [required],\n  className: [required],\n  author: [required]\n})\n\n/** 监听 table 属性，复制给 formData 属性 */\nwatch(\n  () => props.table,\n  (table) => {\n    if (!table) return\n    formData.value = table\n  },\n  {\n    deep: true,\n    immediate: true\n  }\n)\n\ndefineExpose({\n  validate: async () => unref(formRef)?.validate()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/codegen/components/ColumInfoForm.vue",
    "content": "<template>\n  <el-table ref=\"dragTable\" :data=\"formData\" :max-height=\"tableHeight\" row-key=\"columnId\">\n    <el-table-column\n      :show-overflow-tooltip=\"true\"\n      label=\"字段列名\"\n      min-width=\"10%\"\n      prop=\"columnName\"\n    />\n    <el-table-column label=\"字段描述\" min-width=\"10%\">\n      <template #default=\"scope\">\n        <el-input v-model=\"scope.row.columnComment\" />\n      </template>\n    </el-table-column>\n    <el-table-column\n      :show-overflow-tooltip=\"true\"\n      label=\"物理类型\"\n      min-width=\"10%\"\n      prop=\"dataType\"\n    />\n    <el-table-column label=\"Java类型\" min-width=\"11%\">\n      <template #default=\"scope\">\n        <el-select v-model=\"scope.row.javaType\">\n          <el-option label=\"Long\" value=\"Long\" />\n          <el-option label=\"String\" value=\"String\" />\n          <el-option label=\"Integer\" value=\"Integer\" />\n          <el-option label=\"Double\" value=\"Double\" />\n          <el-option label=\"BigDecimal\" value=\"BigDecimal\" />\n          <el-option label=\"LocalDateTime\" value=\"LocalDateTime\" />\n          <el-option label=\"Boolean\" value=\"Boolean\" />\n        </el-select>\n      </template>\n    </el-table-column>\n    <el-table-column label=\"java属性\" min-width=\"10%\">\n      <template #default=\"scope\">\n        <el-input v-model=\"scope.row.javaField\" />\n      </template>\n    </el-table-column>\n    <el-table-column label=\"插入\" min-width=\"4%\">\n      <template #default=\"scope\">\n        <el-checkbox v-model=\"scope.row.createOperation\" false-label=\"false\" true-label=\"true\" />\n      </template>\n    </el-table-column>\n    <el-table-column label=\"编辑\" min-width=\"4%\">\n      <template #default=\"scope\">\n        <el-checkbox v-model=\"scope.row.updateOperation\" false-label=\"false\" true-label=\"true\" />\n      </template>\n    </el-table-column>\n    <el-table-column label=\"列表\" min-width=\"4%\">\n      <template #default=\"scope\">\n        <el-checkbox\n          v-model=\"scope.row.listOperationResult\"\n          false-label=\"false\"\n          true-label=\"true\"\n        />\n      </template>\n    </el-table-column>\n    <el-table-column label=\"查询\" min-width=\"4%\">\n      <template #default=\"scope\">\n        <el-checkbox v-model=\"scope.row.listOperation\" false-label=\"false\" true-label=\"true\" />\n      </template>\n    </el-table-column>\n    <el-table-column label=\"查询方式\" min-width=\"10%\">\n      <template #default=\"scope\">\n        <el-select v-model=\"scope.row.listOperationCondition\">\n          <el-option label=\"=\" value=\"=\" />\n          <el-option label=\"!=\" value=\"!=\" />\n          <el-option label=\">\" value=\">\" />\n          <el-option label=\">=\" value=\">=\" />\n          <el-option label=\"<\" value=\"<>\" />\n          <el-option label=\"<=\" value=\"<=\" />\n          <el-option label=\"LIKE\" value=\"LIKE\" />\n          <el-option label=\"BETWEEN\" value=\"BETWEEN\" />\n        </el-select>\n      </template>\n    </el-table-column>\n    <el-table-column label=\"允许空\" min-width=\"5%\">\n      <template #default=\"scope\">\n        <el-checkbox v-model=\"scope.row.nullable\" false-label=\"false\" true-label=\"true\" />\n      </template>\n    </el-table-column>\n    <el-table-column label=\"显示类型\" min-width=\"12%\">\n      <template #default=\"scope\">\n        <el-select v-model=\"scope.row.htmlType\">\n          <el-option label=\"文本框\" value=\"input\" />\n          <el-option label=\"文本域\" value=\"textarea\" />\n          <el-option label=\"下拉框\" value=\"select\" />\n          <el-option label=\"单选框\" value=\"radio\" />\n          <el-option label=\"复选框\" value=\"checkbox\" />\n          <el-option label=\"日期控件\" value=\"datetime\" />\n          <el-option label=\"图片上传\" value=\"imageUpload\" />\n          <el-option label=\"文件上传\" value=\"fileUpload\" />\n          <el-option label=\"富文本控件\" value=\"editor\" />\n        </el-select>\n      </template>\n    </el-table-column>\n    <el-table-column label=\"字典类型\" min-width=\"12%\">\n      <template #default=\"scope\">\n        <el-select v-model=\"scope.row.dictType\" clearable filterable placeholder=\"请选择\">\n          <el-option\n            v-for=\"dict in dictOptions\"\n            :key=\"dict.id\"\n            :label=\"dict.name\"\n            :value=\"dict.type\"\n          />\n        </el-select>\n      </template>\n    </el-table-column>\n    <el-table-column label=\"示例\" min-width=\"10%\">\n      <template #default=\"scope\">\n        <el-input v-model=\"scope.row.example\" />\n      </template>\n    </el-table-column>\n  </el-table>\n</template>\n<script lang=\"ts\" setup>\nimport { PropType } from 'vue'\nimport * as CodegenApi from '@/api/infra/codegen'\nimport * as DictDataApi from '@/api/system/dict/dict.type'\n\ndefineOptions({ name: 'InfraCodegenColumInfoForm' })\n\nconst props = defineProps({\n  columns: {\n    type: Array as unknown as PropType<CodegenApi.CodegenColumnVO[]>,\n    default: () => null\n  }\n})\n\nconst formData = ref<CodegenApi.CodegenColumnVO[]>([])\nconst tableHeight = document.documentElement.scrollHeight - 350 + 'px'\n\n/** 查询字典下拉列表 */\nconst dictOptions = ref<DictDataApi.DictTypeVO[]>()\nconst getDictOptions = async () => {\n  dictOptions.value = await DictDataApi.getSimpleDictTypeList()\n}\n\nwatch(\n  () => props.columns,\n  (columns) => {\n    if (!columns) return\n    formData.value = columns\n  },\n  {\n    deep: true,\n    immediate: true\n  }\n)\n\nonMounted(async () => {\n  await getDictOptions()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/codegen/components/GenerateInfoForm.vue",
    "content": "<template>\n  <el-form ref=\"formRef\" :model=\"formData\" :rules=\"rules\" label-width=\"150px\">\n    <el-row>\n      <el-col :span=\"12\">\n        <el-form-item label=\"生成模板\" prop=\"templateType\">\n          <el-select v-model=\"formData.templateType\">\n            <el-option\n              v-for=\"dict in getIntDictOptions(DICT_TYPE.INFRA_CODEGEN_TEMPLATE_TYPE)\"\n              :key=\"dict.value\"\n              :label=\"dict.label\"\n              :value=\"dict.value\"\n            />\n          </el-select>\n        </el-form-item>\n      </el-col>\n      <el-col :span=\"12\">\n        <el-form-item label=\"前端类型\" prop=\"frontType\">\n          <el-select v-model=\"formData.frontType\">\n            <el-option\n              v-for=\"dict in getIntDictOptions(DICT_TYPE.INFRA_CODEGEN_FRONT_TYPE)\"\n              :key=\"dict.value\"\n              :label=\"dict.label\"\n              :value=\"dict.value\"\n            />\n          </el-select>\n        </el-form-item>\n      </el-col>\n\n      <el-col :span=\"12\">\n        <el-form-item label=\"生成场景\" prop=\"scene\">\n          <el-select v-model=\"formData.scene\">\n            <el-option\n              v-for=\"dict in getIntDictOptions(DICT_TYPE.INFRA_CODEGEN_SCENE)\"\n              :key=\"dict.value\"\n              :label=\"dict.label\"\n              :value=\"dict.value\"\n            />\n          </el-select>\n        </el-form-item>\n      </el-col>\n      <el-col :span=\"12\">\n        <el-form-item>\n          <template #label>\n            <span>\n              上级菜单\n              <el-tooltip content=\"分配到指定菜单下，例如 系统管理\" placement=\"top\">\n                <Icon icon=\"ep:question-filled\" />\n              </el-tooltip>\n            </span>\n          </template>\n          <el-tree-select\n            v-model=\"formData.parentMenuId\"\n            :data=\"menus\"\n            :props=\"menuTreeProps\"\n            check-strictly\n            node-key=\"id\"\n            placeholder=\"请选择系统菜单\"\n          />\n        </el-form-item>\n      </el-col>\n\n\n      <el-col :span=\"12\">\n        <el-form-item prop=\"moduleName\">\n          <template #label>\n            <span>\n              模块名\n              <el-tooltip\n                content=\"模块名，即一级目录，例如 system、infra、tool 等等\"\n                placement=\"top\"\n              >\n                <Icon icon=\"ep:question-filled\" />\n              </el-tooltip>\n            </span>\n          </template>\n          <el-input v-model=\"formData.moduleName\" />\n        </el-form-item>\n      </el-col>\n\n      <el-col :span=\"12\">\n        <el-form-item prop=\"businessName\">\n          <template #label>\n            <span>\n              业务名\n              <el-tooltip\n                content=\"业务名，即二级目录，例如 user、permission、dict 等等\"\n                placement=\"top\"\n              >\n                <Icon icon=\"ep:question-filled\" />\n              </el-tooltip>\n            </span>\n          </template>\n          <el-input v-model=\"formData.businessName\" />\n        </el-form-item>\n      </el-col>\n\n      <!--      <el-col :span=\"12\">-->\n      <!--        <el-form-item prop=\"businessPackage\">-->\n      <!--          <span slot=\"label\">-->\n      <!--            业务包-->\n      <!--            <el-tooltip content=\"业务包，自定义二级目录。例如说，我们希望将 dictType 和 dictData 归类成 dict 业务\" placement=\"top\">-->\n      <!--              <i class=\"el-icon-question\"></i>-->\n      <!--            </el-tooltip>-->\n      <!--          </span>-->\n      <!--          <el-input v-model=\"formData.businessPackage\" />-->\n      <!--        </el-form-item>-->\n      <!--      </el-col>-->\n\n      <el-col :span=\"12\">\n        <el-form-item prop=\"className\">\n          <template #label>\n            <span>\n              类名称\n              <el-tooltip\n                content=\"类名称（首字母大写），例如SysUser、SysMenu、SysDictData 等等\"\n                placement=\"top\"\n              >\n                <Icon icon=\"ep:question-filled\" />\n              </el-tooltip>\n            </span>\n          </template>\n          <el-input v-model=\"formData.className\" />\n        </el-form-item>\n      </el-col>\n\n      <el-col :span=\"12\">\n        <el-form-item prop=\"classComment\">\n          <template #label>\n            <span>\n              类描述\n              <el-tooltip content=\"用作类描述，例如 用户\" placement=\"top\">\n                <Icon icon=\"ep:question-filled\" />\n              </el-tooltip>\n            </span>\n          </template>\n          <el-input v-model=\"formData.classComment\" />\n        </el-form-item>\n      </el-col>\n\n      <el-col v-if=\"formData.genType === '1'\" :span=\"24\">\n        <el-form-item prop=\"genPath\">\n          <template #label>\n            <span>\n              自定义路径\n              <el-tooltip\n                content=\"填写磁盘绝对路径，若不填写，则生成到当前Web项目下\"\n                placement=\"top\"\n              >\n                <Icon icon=\"ep:question-filled\" />\n              </el-tooltip>\n            </span>\n          </template>\n          <el-input v-model=\"formData.genPath\">\n            <template #append>\n              <el-dropdown>\n                <el-button type=\"primary\">\n                  最近路径快速选择\n                  <i class=\"el-icon-arrow-down el-icon--right\"></i>\n                </el-button>\n                <template #dropdown>\n                  <el-dropdown-menu>\n                    <el-dropdown-item @click=\"formData.genPath = '/'\">\n                      恢复默认的生成基础路径\n                    </el-dropdown-item>\n                  </el-dropdown-menu>\n                </template>\n              </el-dropdown>\n            </template>\n          </el-input>\n        </el-form-item>\n      </el-col>\n    </el-row>\n\n    <!-- 树表信息 -->\n    <el-row v-if=\"formData.templateType == 2\">\n      <el-col :span=\"24\">\n        <h4 class=\"form-header\">树表信息</h4>\n      </el-col>\n      <el-col :span=\"12\">\n        <el-form-item prop=\"treeParentColumnId\">\n          <template #label>\n            <span>\n              父编号字段\n              <el-tooltip content=\"树显示的父编码字段名， 如：parent_Id\" placement=\"top\">\n                <Icon icon=\"ep:question-filled\" />\n              </el-tooltip>\n            </span>\n          </template>\n          <el-select v-model=\"formData.treeParentColumnId\" placeholder=\"请选择\">\n            <el-option\n              v-for=\"(column, index) in props.columns\"\n              :key=\"index\"\n              :label=\"column.columnName + '：' + column.columnComment\"\n              :value=\"column.id\"\n            />\n          </el-select>\n        </el-form-item>\n      </el-col>\n      <el-col :span=\"12\">\n        <el-form-item prop=\"treeNameColumnId\">\n          <template #label>\n            <span>\n              树名称字段\n              <el-tooltip content=\"树节点的显示名称字段名， 如：dept_name\" placement=\"top\">\n                <Icon icon=\"ep:question-filled\" />\n              </el-tooltip>\n            </span>\n          </template>\n          <el-select v-model=\"formData.treeNameColumnId\" placeholder=\"请选择\">\n            <el-option\n              v-for=\"(column, index) in props.columns\"\n              :key=\"index\"\n              :label=\"column.columnName + '：' + column.columnComment\"\n              :value=\"column.id\"\n            />\n          </el-select>\n        </el-form-item>\n      </el-col>\n    </el-row>\n\n    <!-- 主表信息 -->\n    <el-row v-if=\"formData.templateType == 15\">\n      <el-col :span=\"24\">\n        <h4 class=\"form-header\">主表信息</h4>\n      </el-col>\n      <el-col :span=\"12\">\n        <el-form-item prop=\"masterTableId\">\n          <template #label>\n            <span>\n              关联的主表\n              <el-tooltip content=\"关联主表（父表）的表名， 如：system_user\" placement=\"top\">\n                <Icon icon=\"ep:question-filled\" />\n              </el-tooltip>\n            </span>\n          </template>\n          <el-select v-model=\"formData.masterTableId\" placeholder=\"请选择\">\n            <el-option\n              v-for=\"(table0, index) in tables\"\n              :key=\"index\"\n              :label=\"table0.tableName + '：' + table0.tableComment\"\n              :value=\"table0.id\"\n            />\n          </el-select>\n        </el-form-item>\n      </el-col>\n      <el-col :span=\"12\">\n        <el-form-item prop=\"subJoinColumnId\">\n          <template #label>\n            <span>\n              子表关联的字段\n              <el-tooltip content=\"子表关联的字段， 如：user_id\" placement=\"top\">\n                <Icon icon=\"ep:question-filled\" />\n              </el-tooltip>\n            </span>\n          </template>\n          <el-select v-model=\"formData.subJoinColumnId\" placeholder=\"请选择\">\n            <el-option\n              v-for=\"(column, index) in props.columns\"\n              :key=\"index\"\n              :label=\"column.columnName + '：' + column.columnComment\"\n              :value=\"column.id\"\n            />\n          </el-select>\n        </el-form-item>\n      </el-col>\n      <el-col :span=\"12\">\n        <el-form-item prop=\"subJoinMany\">\n          <template #label>\n            <span>\n              关联关系\n              <el-tooltip content=\"主表与子表的关联关系\" placement=\"top\">\n                <Icon icon=\"ep:question-filled\" />\n              </el-tooltip>\n            </span>\n          </template>\n          <el-radio-group v-model=\"formData.subJoinMany\" placeholder=\"请选择\">\n            <el-radio :label=\"true\">一对多</el-radio>\n            <el-radio :label=\"false\">一对一</el-radio>\n          </el-radio-group>\n        </el-form-item>\n      </el-col>\n    </el-row>\n  </el-form>\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getIntDictOptions } from '@/utils/dict'\nimport { handleTree } from '@/utils/tree'\nimport * as CodegenApi from '@/api/infra/codegen'\nimport * as MenuApi from '@/api/system/menu'\nimport { PropType } from 'vue'\n\ndefineOptions({ name: 'InfraCodegenGenerateInfoForm' })\n\nconst message = useMessage() // 消息弹窗\nconst props = defineProps({\n  table: {\n    type: Object as PropType<Nullable<CodegenApi.CodegenTableVO>>,\n    default: () => null\n  },\n  columns: {\n    type: Array as unknown as PropType<CodegenApi.CodegenColumnVO[]>,\n    default: () => null\n  }\n})\n\nconst formRef = ref()\nconst formData = ref({\n  templateType: null,\n  frontType: null,\n  scene: null,\n  moduleName: '',\n  businessName: '',\n  className: '',\n  classComment: '',\n  parentMenuId: null,\n  genPath: '',\n  genType: '',\n  masterTableId: undefined,\n  subJoinColumnId: undefined,\n  subJoinMany: undefined,\n  treeParentColumnId: undefined,\n  treeNameColumnId: undefined\n})\n\nconst rules = reactive({\n  templateType: [required],\n  frontType: [required],\n  scene: [required],\n  moduleName: [required],\n  businessName: [required],\n  businessPackage: [required],\n  className: [required],\n  classComment: [required],\n  masterTableId: [required],\n  subJoinColumnId: [required],\n  subJoinMany: [required],\n  treeParentColumnId: [required],\n  treeNameColumnId: [required]\n})\n\nconst tables = ref([]) // 表定义列表\nconst menus = ref<any[]>([])\nconst menuTreeProps = {\n  label: 'name'\n}\n\nwatch(\n  () => props.table,\n  async (table) => {\n    if (!table) return\n    formData.value = table as any\n    // 加载表列表\n    if (table.dataSourceConfigId >= 0) {\n      tables.value = await CodegenApi.getCodegenTableList(formData.value.dataSourceConfigId)\n    }\n  },\n  {\n    deep: true,\n    immediate: true\n  }\n)\n\nonMounted(async () => {\n  try {\n    // 加载菜单\n    const resp = await MenuApi.getSimpleMenusList()\n    menus.value = handleTree(resp)\n  } catch {}\n})\n\ndefineExpose({\n  validate: async () => unref(formRef)?.validate()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/codegen/components/index.ts",
    "content": "import BasicInfoForm from './BasicInfoForm.vue'\nimport ColumInfoForm from './ColumInfoForm.vue'\nimport GenerateInfoForm from './GenerateInfoForm.vue'\nexport { BasicInfoForm, ColumInfoForm, GenerateInfoForm }\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/codegen/index.vue",
    "content": "<template>\n  <!-- 搜索 -->\n  <ContentWrap>\n    <el-form\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      :model=\"queryParams\"\n      class=\"-mb-15px\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"表名称\" prop=\"tableName\">\n        <el-input\n          v-model=\"queryParams.tableName\"\n          class=\"!w-240px\"\n          clearable\n          placeholder=\"请输入表名称\"\n          @keyup.enter=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"表描述\" prop=\"tableComment\">\n        <el-input\n          v-model=\"queryParams.tableComment\"\n          class=\"!w-240px\"\n          clearable\n          placeholder=\"请输入表描述\"\n          @keyup.enter=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"创建时间\" prop=\"createTime\">\n        <el-date-picker\n          v-model=\"queryParams.createTime\"\n          :default-time=\"[new Date('1 00:00:00'), new Date('1 23:59:59')]\"\n          class=\"!w-240px\"\n          end-placeholder=\"结束日期\"\n          start-placeholder=\"开始日期\"\n          type=\"daterange\"\n          value-format=\"YYYY-MM-DD HH:mm:ss\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\">\n          <Icon class=\"mr-5px\" icon=\"ep:search\" />\n          搜索\n        </el-button>\n        <el-button @click=\"resetQuery\">\n          <Icon class=\"mr-5px\" icon=\"ep:refresh\" />\n          重置\n        </el-button>\n        <el-button v-hasPermi=\"['infra:codegen:create']\" type=\"primary\" @click=\"openImportTable()\">\n          <Icon class=\"mr-5px\" icon=\"ep:zoom-in\" />\n          导入\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column align=\"center\" label=\"数据源\">\n        <template #default=\"scope\">\n          {{\n            dataSourceConfigList.find((config) => config.id === scope.row.dataSourceConfigId)?.name\n          }}\n        </template>\n      </el-table-column>\n      <el-table-column align=\"center\" label=\"表名称\" prop=\"tableName\" width=\"200\" />\n      <el-table-column\n        :show-overflow-tooltip=\"true\"\n        align=\"center\"\n        label=\"表描述\"\n        prop=\"tableComment\"\n        width=\"200\"\n      />\n      <el-table-column align=\"center\" label=\"实体\" prop=\"className\" width=\"200\" />\n      <el-table-column\n        :formatter=\"dateFormatter\"\n        align=\"center\"\n        label=\"创建时间\"\n        prop=\"createTime\"\n        width=\"180\"\n      />\n      <el-table-column\n        :formatter=\"dateFormatter\"\n        align=\"center\"\n        label=\"更新时间\"\n        prop=\"createTime\"\n        width=\"180\"\n      />\n      <el-table-column align=\"center\" fixed=\"right\" label=\"操作\" width=\"300px\">\n        <template #default=\"scope\">\n          <el-button\n            v-hasPermi=\"['infra:codegen:preview']\"\n            link\n            type=\"primary\"\n            @click=\"handlePreview(scope.row)\"\n          >\n            预览\n          </el-button>\n          <el-button\n            v-hasPermi=\"['infra:codegen:update']\"\n            link\n            type=\"primary\"\n            @click=\"handleUpdate(scope.row.id)\"\n          >\n            编辑\n          </el-button>\n          <el-button\n            v-hasPermi=\"['infra:codegen:delete']\"\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n          >\n            删除\n          </el-button>\n          <el-button\n            v-hasPermi=\"['infra:codegen:update']\"\n            link\n            type=\"primary\"\n            @click=\"handleSyncDB(scope.row)\"\n          >\n            同步\n          </el-button>\n          <el-button\n            v-hasPermi=\"['infra:codegen:download']\"\n            link\n            type=\"primary\"\n            @click=\"handleGenTable(scope.row)\"\n          >\n            生成代码\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      v-model:limit=\"queryParams.pageSize\"\n      v-model:page=\"queryParams.pageNo\"\n      :total=\"total\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 弹窗：导入表 -->\n  <ImportTable ref=\"importRef\" @success=\"getList\" />\n  <!-- 弹窗：预览代码 -->\n  <PreviewCode ref=\"previewRef\" />\n</template>\n<script lang=\"ts\" setup>\nimport { dateFormatter } from '@/utils/formatTime'\nimport download from '@/utils/download'\nimport * as CodegenApi from '@/api/infra/codegen'\nimport * as DataSourceConfigApi from '@/api/infra/dataSourceConfig'\nimport ImportTable from './ImportTable.vue'\nimport PreviewCode from './PreviewCode.vue'\n\ndefineOptions({ name: 'InfraCodegen' })\n\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\nconst { push } = useRouter() // 路由跳转\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  tableName: undefined,\n  tableComment: undefined,\n  createTime: []\n})\nconst queryFormRef = ref() // 搜索的表单\nconst dataSourceConfigList = ref<DataSourceConfigApi.DataSourceConfigVO[]>([]) // 数据源列表\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await CodegenApi.getCodegenTablePage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 导入操作 */\nconst importRef = ref()\nconst openImportTable = () => {\n  importRef.value.open()\n}\n\n/** 编辑操作 */\nconst handleUpdate = (id: number) => {\n  push('/codegen/edit?id=' + id)\n}\n\n/** 预览操作 */\nconst previewRef = ref()\nconst handlePreview = (row: CodegenApi.CodegenTableVO) => {\n  previewRef.value.open(row.id)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await CodegenApi.deleteCodegenTable(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 同步操作  */\nconst handleSyncDB = async (row: CodegenApi.CodegenTableVO) => {\n  // 基于 DB 同步\n  const tableName = row.tableName\n  try {\n    await message.confirm('确认要强制同步' + tableName + '表结构吗?', t('common.reminder'))\n    await CodegenApi.syncCodegenFromDB(row.id)\n    message.success('同步成功')\n  } catch {}\n}\n\n/** 生成代码操作 */\nconst handleGenTable = async (row: CodegenApi.CodegenTableVO) => {\n  const res = await CodegenApi.downloadCodegen(row.id)\n  download.zip(res, 'codegen-' + row.className + '.zip')\n}\n\n/** 初始化 **/\nonMounted(async () => {\n  await getList()\n  // 加载数据源列表\n  dataSourceConfigList.value = await DataSourceConfigApi.getDataSourceConfigList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/config/ConfigForm.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" :title=\"dialogTitle\">\n    <el-form\n      ref=\"formRef\"\n      v-loading=\"formLoading\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"80px\"\n    >\n      <el-form-item label=\"参数分类\" prop=\"category\">\n        <el-input v-model=\"formData.category\" placeholder=\"请输入参数分类\" />\n      </el-form-item>\n      <el-form-item label=\"参数名称\" prop=\"name\">\n        <el-input v-model=\"formData.name\" placeholder=\"请输入参数名称\" />\n      </el-form-item>\n      <el-form-item label=\"参数键名\" prop=\"key\">\n        <el-input v-model=\"formData.key\" placeholder=\"请输入参数键名\" />\n      </el-form-item>\n      <el-form-item label=\"参数键值\" prop=\"value\">\n        <el-input v-model=\"formData.value\" placeholder=\"请输入参数键值\" />\n      </el-form-item>\n      <el-form-item label=\"是否可见\" prop=\"visible\">\n        <el-radio-group v-model=\"formData.visible\">\n          <el-radio\n            v-for=\"dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)\"\n            :key=\"dict.value as string\"\n            :label=\"dict.value\"\n          >\n            {{ dict.label }}\n          </el-radio>\n        </el-radio-group>\n      </el-form-item>\n      <el-form-item label=\"备注\" prop=\"remark\">\n        <el-input v-model=\"formData.remark\" placeholder=\"请输入内容\" type=\"textarea\" />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button :disabled=\"formLoading\" type=\"primary\" @click=\"submitForm\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getBoolDictOptions } from '@/utils/dict'\nimport * as ConfigApi from '@/api/infra/config'\n\ndefineOptions({ name: 'InfraConfigForm' })\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  category: '',\n  name: '',\n  key: '',\n  value: '',\n  visible: true,\n  remark: ''\n})\nconst formRules = reactive({\n  category: [{ required: true, message: '参数分类不能为空', trigger: 'blur' }],\n  name: [{ required: true, message: '参数名称不能为空', trigger: 'blur' }],\n  key: [{ required: true, message: '参数键名不能为空', trigger: 'blur' }],\n  value: [{ required: true, message: '参数键值不能为空', trigger: 'blur' }],\n  visible: [{ required: true, message: '是否可见不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await ConfigApi.getConfig(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as ConfigApi.ConfigVO\n    if (formType.value === 'create') {\n      await ConfigApi.createConfig(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await ConfigApi.updateConfig(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    category: '',\n    name: '',\n    key: '',\n    value: '',\n    visible: true,\n    remark: ''\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/config/index.vue",
    "content": "<template>\n  <!-- 搜索 -->\n  <ContentWrap>\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"参数名称\" prop=\"name\">\n        <el-input\n          v-model=\"queryParams.name\"\n          placeholder=\"请输入参数名称\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"参数键名\" prop=\"key\">\n        <el-input\n          v-model=\"queryParams.key\"\n          placeholder=\"请输入参数键名\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"系统内置\" prop=\"type\">\n        <el-select\n          v-model=\"queryParams.type\"\n          placeholder=\"请选择系统内置\"\n          clearable\n          class=\"!w-240px\"\n        >\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.INFRA_CONFIG_TYPE)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"创建时间\" prop=\"createTime\">\n        <el-date-picker\n          v-model=\"queryParams.createTime\"\n          value-format=\"YYYY-MM-DD HH:mm:ss\"\n          type=\"daterange\"\n          start-placeholder=\"开始日期\"\n          end-placeholder=\"结束日期\"\n          :default-time=\"[new Date('1 00:00:00'), new Date('1 23:59:59')]\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n        <el-button\n          type=\"primary\"\n          plain\n          @click=\"openForm('create')\"\n          v-hasPermi=\"['infra:config:create']\"\n        >\n          <Icon icon=\"ep:plus\" class=\"mr-5px\" /> 新增\n        </el-button>\n        <el-button\n          type=\"success\"\n          plain\n          @click=\"handleExport\"\n          :loading=\"exportLoading\"\n          v-hasPermi=\"['infra:config:export']\"\n        >\n          <Icon icon=\"ep:download\" class=\"mr-5px\" /> 导出\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column label=\"参数主键\" align=\"center\" prop=\"id\" />\n      <el-table-column label=\"参数分类\" align=\"center\" prop=\"category\" />\n      <el-table-column label=\"参数名称\" align=\"center\" prop=\"name\" :show-overflow-tooltip=\"true\" />\n      <el-table-column label=\"参数键名\" align=\"center\" prop=\"key\" :show-overflow-tooltip=\"true\" />\n      <el-table-column label=\"参数键值\" align=\"center\" prop=\"value\" />\n      <el-table-column label=\"是否可见\" align=\"center\" prop=\"visible\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.INFRA_BOOLEAN_STRING\" :value=\"scope.row.visible\" />\n        </template>\n      </el-table-column>\n      <el-table-column label=\"系统内置\" align=\"center\" prop=\"type\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.INFRA_CONFIG_TYPE\" :value=\"scope.row.type\" />\n        </template>\n      </el-table-column>\n      <el-table-column label=\"备注\" align=\"center\" prop=\"remark\" :show-overflow-tooltip=\"true\" />\n      <el-table-column\n        label=\"创建时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        width=\"180\"\n        :formatter=\"dateFormatter\"\n      />\n      <el-table-column label=\"操作\" align=\"center\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n            v-hasPermi=\"['infra:config:update']\"\n          >\n            编辑\n          </el-button>\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n            v-hasPermi=\"['infra:config:delete']\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <ConfigForm ref=\"formRef\" @success=\"getList\" />\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getIntDictOptions } from '@/utils/dict'\nimport { dateFormatter } from '@/utils/formatTime'\nimport download from '@/utils/download'\nimport * as ConfigApi from '@/api/infra/config'\nimport ConfigForm from './ConfigForm.vue'\n\ndefineOptions({ name: 'InfraConfig' })\n\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  name: undefined,\n  key: undefined,\n  type: undefined,\n  createTime: []\n})\nconst queryFormRef = ref() // 搜索的表单\nconst exportLoading = ref(false) // 导出的加载中\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await ConfigApi.getConfigPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await ConfigApi.deleteConfig(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 导出按钮操作 */\nconst handleExport = async () => {\n  try {\n    // 导出的二次确认\n    await message.exportConfirm()\n    // 发起导出\n    exportLoading.value = true\n    const data = await ConfigApi.exportConfig(queryParams)\n    download.excel(data, '参数配置.xls')\n  } catch {\n  } finally {\n    exportLoading.value = false\n  }\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/dataSourceConfig/DataSourceConfigForm.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" :title=\"dialogTitle\">\n    <el-form\n      ref=\"formRef\"\n      v-loading=\"formLoading\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"100px\"\n    >\n      <el-form-item label=\"数据源名称\" prop=\"name\">\n        <el-input v-model=\"formData.name\" placeholder=\"请输入参数名称\" />\n      </el-form-item>\n      <el-form-item label=\"数据源连接\" prop=\"url\">\n        <el-input v-model=\"formData.url\" placeholder=\"请输入数据源连接\" />\n      </el-form-item>\n      <el-form-item label=\"用户名\" prop=\"username\">\n        <el-input v-model=\"formData.username\" placeholder=\"请输入用户名\" />\n      </el-form-item>\n      <el-form-item label=\"密码\" prop=\"password\">\n        <el-input v-model=\"formData.password\" placeholder=\"请输入密码\" />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button :disabled=\"formLoading\" type=\"primary\" @click=\"submitForm\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport * as DataSourceConfigApi from '@/api/infra/dataSourceConfig'\n\ndefineOptions({ name: 'InfraDataSourceConfigForm' })\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref<DataSourceConfigApi.DataSourceConfigVO>({\n  id: undefined,\n  name: '',\n  url: '',\n  username: '',\n  password: ''\n})\nconst formRules = reactive({\n  name: [{ required: true, message: '数据源名称不能为空', trigger: 'blur' }],\n  url: [{ required: true, message: '数据源连接不能为空', trigger: 'blur' }],\n  username: [{ required: true, message: '用户名不能为空', trigger: 'blur' }],\n  password: [{ required: true, message: '密码不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await DataSourceConfigApi.getDataSourceConfig(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as DataSourceConfigApi.DataSourceConfigVO\n    if (formType.value === 'create') {\n      await DataSourceConfigApi.createDataSourceConfig(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await DataSourceConfigApi.updateDataSourceConfig(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    name: '',\n    url: '',\n    username: '',\n    password: ''\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/dataSourceConfig/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <!-- 搜索工作栏 -->\n    <el-form class=\"-mb-15px\" :inline=\"true\">\n      <el-form-item>\n        <el-button\n          type=\"primary\"\n          plain\n          @click=\"openForm('create')\"\n          v-hasPermi=\"['infra:data-source-config:create']\"\n        >\n          <Icon icon=\"ep:plus\" class=\"mr-5px\" /> 新增\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column label=\"主键编号\" align=\"center\" prop=\"id\" />\n      <el-table-column label=\"数据源名称\" align=\"center\" prop=\"name\" />\n      <el-table-column label=\"数据源连接\" align=\"center\" prop=\"url\" :show-overflow-tooltip=\"true\" />\n      <el-table-column label=\"用户名\" align=\"center\" prop=\"username\" />\n      <el-table-column\n        label=\"创建时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        width=\"180\"\n        :formatter=\"dateFormatter\"\n      />\n      <el-table-column label=\"操作\" align=\"center\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n            v-hasPermi=\"['infra:data-source-config:update']\"\n            :disabled=\"scope.row.id === 0\"\n          >\n            编辑\n          </el-button>\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n            v-hasPermi=\"['infra:data-source-config:delete']\"\n            :disabled=\"scope.row.id === 0\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <DataSourceConfigForm ref=\"formRef\" @success=\"getList\" />\n</template>\n<script lang=\"ts\" setup>\nimport { dateFormatter } from '@/utils/formatTime'\nimport * as DataSourceConfigApi from '@/api/infra/dataSourceConfig'\nimport DataSourceConfigForm from './DataSourceConfigForm.vue'\n\ndefineOptions({ name: 'InfraDataSourceConfig' })\n\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst list = ref([]) // 列表的数据\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    list.value = await DataSourceConfigApi.getDataSourceConfigList()\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await DataSourceConfigApi.deleteDataSourceConfig(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/demo/demo01/Demo01ContactForm.vue",
    "content": "<template>\n  <Dialog :title=\"dialogTitle\" v-model=\"dialogVisible\">\n    <el-form\n      ref=\"formRef\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"100px\"\n      v-loading=\"formLoading\"\n    >\n      <el-form-item label=\"名字\" prop=\"name\">\n        <el-input v-model=\"formData.name\" placeholder=\"请输入名字\" />\n      </el-form-item>\n      <el-form-item label=\"性别\" prop=\"sex\">\n        <el-radio-group v-model=\"formData.sex\">\n          <el-radio\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)\"\n            :key=\"dict.value\"\n            :label=\"dict.value\"\n          >\n            {{ dict.label }}\n          </el-radio>\n        </el-radio-group>\n      </el-form-item>\n      <el-form-item label=\"出生年\" prop=\"birthday\">\n        <el-date-picker\n          v-model=\"formData.birthday\"\n          type=\"date\"\n          value-format=\"x\"\n          placeholder=\"选择出生年\"\n        />\n      </el-form-item>\n      <el-form-item label=\"简介\" prop=\"description\">\n        <Editor v-model=\"formData.description\" height=\"150px\" />\n      </el-form-item>\n      <el-form-item label=\"头像\" prop=\"avatar\">\n        <UploadImg v-model=\"formData.avatar\" />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button @click=\"submitForm\" type=\"primary\" :disabled=\"formLoading\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script setup lang=\"ts\">\nimport { getIntDictOptions, DICT_TYPE } from '@/utils/dict'\nimport * as Demo01ContactApi from '@/api/infra/demo/demo01'\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  name: undefined,\n  sex: undefined,\n  birthday: undefined,\n  description: undefined,\n  avatar: undefined\n})\nconst formRules = reactive({\n  name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],\n  sex: [{ required: true, message: '性别不能为空', trigger: 'blur' }],\n  birthday: [{ required: true, message: '出生年不能为空', trigger: 'blur' }],\n  description: [{ required: true, message: '简介不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await Demo01ContactApi.getDemo01Contact(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  await formRef.value.validate()\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as unknown as Demo01ContactApi.Demo01ContactVO\n    if (formType.value === 'create') {\n      await Demo01ContactApi.createDemo01Contact(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await Demo01ContactApi.updateDemo01Contact(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    name: undefined,\n    sex: undefined,\n    birthday: undefined,\n    description: undefined,\n    avatar: undefined\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/demo/demo01/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <!-- 搜索工作栏 -->\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"名字\" prop=\"name\">\n        <el-input\n          v-model=\"queryParams.name\"\n          placeholder=\"请输入名字\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"性别\" prop=\"sex\">\n        <el-select v-model=\"queryParams.sex\" placeholder=\"请选择性别\" clearable class=\"!w-240px\">\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"创建时间\" prop=\"createTime\">\n        <el-date-picker\n          v-model=\"queryParams.createTime\"\n          value-format=\"YYYY-MM-DD HH:mm:ss\"\n          type=\"daterange\"\n          start-placeholder=\"开始日期\"\n          end-placeholder=\"结束日期\"\n          :default-time=\"[new Date('1 00:00:00'), new Date('1 23:59:59')]\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n        <el-button\n          type=\"primary\"\n          plain\n          @click=\"openForm('create')\"\n          v-hasPermi=\"['infra:demo01-contact:create']\"\n        >\n          <Icon icon=\"ep:plus\" class=\"mr-5px\" /> 新增\n        </el-button>\n        <el-button\n          type=\"success\"\n          plain\n          @click=\"handleExport\"\n          :loading=\"exportLoading\"\n          v-hasPermi=\"['infra:demo01-contact:export']\"\n        >\n          <Icon icon=\"ep:download\" class=\"mr-5px\" /> 导出\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\" :stripe=\"true\" :show-overflow-tooltip=\"true\">\n      <el-table-column label=\"编号\" align=\"center\" prop=\"id\" />\n      <el-table-column label=\"名字\" align=\"center\" prop=\"name\" />\n      <el-table-column label=\"性别\" align=\"center\" prop=\"sex\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.SYSTEM_USER_SEX\" :value=\"scope.row.sex\" />\n        </template>\n      </el-table-column>\n      <el-table-column\n        label=\"出生年\"\n        align=\"center\"\n        prop=\"birthday\"\n        :formatter=\"dateFormatter\"\n        width=\"180px\"\n      />\n      <el-table-column label=\"简介\" align=\"center\" prop=\"description\" />\n      <el-table-column label=\"头像\" align=\"center\" prop=\"avatar\" />\n      <el-table-column\n        label=\"创建时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        :formatter=\"dateFormatter\"\n        width=\"180px\"\n      />\n      <el-table-column label=\"操作\" align=\"center\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n            v-hasPermi=\"['infra:demo01-contact:update']\"\n          >\n            编辑\n          </el-button>\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n            v-hasPermi=\"['infra:demo01-contact:delete']\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <Demo01ContactForm ref=\"formRef\" @success=\"getList\" />\n</template>\n\n<script setup lang=\"ts\">\nimport { getIntDictOptions, DICT_TYPE } from '@/utils/dict'\nimport { dateFormatter } from '@/utils/formatTime'\nimport download from '@/utils/download'\nimport * as Demo01ContactApi from '@/api/infra/demo/demo01'\nimport Demo01ContactForm from './Demo01ContactForm.vue'\n\ndefineOptions({ name: 'Demo01Contact' })\n\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst list = ref([]) // 列表的数据\nconst total = ref(0) // 列表的总页数\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  name: null,\n  sex: null,\n  createTime: []\n})\nconst queryFormRef = ref() // 搜索的表单\nconst exportLoading = ref(false) // 导出的加载中\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await Demo01ContactApi.getDemo01ContactPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await Demo01ContactApi.deleteDemo01Contact(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 导出按钮操作 */\nconst handleExport = async () => {\n  try {\n    // 导出的二次确认\n    await message.exportConfirm()\n    // 发起导出\n    exportLoading.value = true\n    const data = await Demo01ContactApi.exportDemo01Contact(queryParams)\n    download.excel(data, '示例联系人.xls')\n  } catch {\n  } finally {\n    exportLoading.value = false\n  }\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/demo/demo02/Demo02CategoryForm.vue",
    "content": "<template>\n  <Dialog :title=\"dialogTitle\" v-model=\"dialogVisible\">\n    <el-form\n      ref=\"formRef\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"100px\"\n      v-loading=\"formLoading\"\n    >\n      <el-form-item label=\"名字\" prop=\"name\">\n        <el-input v-model=\"formData.name\" placeholder=\"请输入名字\" />\n      </el-form-item>\n      <el-form-item label=\"父级编号\" prop=\"parentId\">\n        <el-tree-select\n          v-model=\"formData.parentId\"\n          :data=\"demo02CategoryTree\"\n          :props=\"defaultProps\"\n          check-strictly\n          default-expand-all\n          placeholder=\"请选择父级编号\"\n        />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button @click=\"submitForm\" type=\"primary\" :disabled=\"formLoading\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script setup lang=\"ts\">\nimport * as Demo02CategoryApi from '@/api/infra/demo/demo02'\nimport { defaultProps, handleTree } from '@/utils/tree'\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  name: undefined,\n  parentId: undefined\n})\nconst formRules = reactive({\n  name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],\n  parentId: [{ required: true, message: '父级编号不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\nconst demo02CategoryTree = ref() // 树形结构\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await Demo02CategoryApi.getDemo02Category(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n  await getDemo02CategoryTree()\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  await formRef.value.validate()\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as unknown as Demo02CategoryApi.Demo02CategoryVO\n    if (formType.value === 'create') {\n      await Demo02CategoryApi.createDemo02Category(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await Demo02CategoryApi.updateDemo02Category(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    name: undefined,\n    parentId: undefined\n  }\n  formRef.value?.resetFields()\n}\n\n/** 获得示例分类树 */\nconst getDemo02CategoryTree = async () => {\n  demo02CategoryTree.value = []\n  const data = await Demo02CategoryApi.getDemo02CategoryList()\n  const root: Tree = { id: 0, name: '顶级示例分类', children: [] }\n  root.children = handleTree(data, 'id', 'parentId')\n  demo02CategoryTree.value.push(root)\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/demo/demo02/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <!-- 搜索工作栏 -->\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"名字\" prop=\"name\">\n        <el-input\n          v-model=\"queryParams.name\"\n          placeholder=\"请输入名字\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"创建时间\" prop=\"createTime\">\n        <el-date-picker\n          v-model=\"queryParams.createTime\"\n          value-format=\"YYYY-MM-DD HH:mm:ss\"\n          type=\"daterange\"\n          start-placeholder=\"开始日期\"\n          end-placeholder=\"结束日期\"\n          :default-time=\"[new Date('1 00:00:00'), new Date('1 23:59:59')]\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n        <el-button\n          type=\"primary\"\n          plain\n          @click=\"openForm('create')\"\n          v-hasPermi=\"['infra:demo02-category:create']\"\n        >\n          <Icon icon=\"ep:plus\" class=\"mr-5px\" /> 新增\n        </el-button>\n        <el-button\n          type=\"success\"\n          plain\n          @click=\"handleExport\"\n          :loading=\"exportLoading\"\n          v-hasPermi=\"['infra:demo02-category:export']\"\n        >\n          <Icon icon=\"ep:download\" class=\"mr-5px\" /> 导出\n        </el-button>\n        <el-button type=\"danger\" plain @click=\"toggleExpandAll\">\n          <Icon icon=\"ep:sort\" class=\"mr-5px\" /> 展开/折叠\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table\n      v-loading=\"loading\"\n      :data=\"list\"\n      :stripe=\"true\"\n      :show-overflow-tooltip=\"true\"\n      row-key=\"id\"\n      :default-expand-all=\"isExpandAll\"\n      v-if=\"refreshTable\"\n    >\n      <el-table-column label=\"编号\" align=\"center\" prop=\"id\" />\n      <el-table-column label=\"名字\" align=\"center\" prop=\"name\" />\n      <el-table-column\n        label=\"创建时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        :formatter=\"dateFormatter\"\n        width=\"180px\"\n      />\n      <el-table-column label=\"操作\" align=\"center\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n            v-hasPermi=\"['infra:demo02-category:update']\"\n          >\n            编辑\n          </el-button>\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n            v-hasPermi=\"['infra:demo02-category:delete']\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <Demo02CategoryForm ref=\"formRef\" @success=\"getList\" />\n</template>\n\n<script setup lang=\"ts\">\nimport { dateFormatter } from '@/utils/formatTime'\nimport { handleTree } from '@/utils/tree'\nimport download from '@/utils/download'\nimport * as Demo02CategoryApi from '@/api/infra/demo/demo02'\nimport Demo02CategoryForm from './Demo02CategoryForm.vue'\n\ndefineOptions({ name: 'Demo02Category' })\n\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  name: null,\n  parentId: null,\n  createTime: []\n})\nconst queryFormRef = ref() // 搜索的表单\nconst exportLoading = ref(false) // 导出的加载中\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await Demo02CategoryApi.getDemo02CategoryList(queryParams)\n    list.value = handleTree(data, 'id', 'parentId')\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await Demo02CategoryApi.deleteDemo02Category(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 导出按钮操作 */\nconst handleExport = async () => {\n  try {\n    // 导出的二次确认\n    await message.exportConfirm()\n    // 发起导出\n    exportLoading.value = true\n    const data = await Demo02CategoryApi.exportDemo02Category(queryParams)\n    download.excel(data, '示例分类.xls')\n  } catch {\n  } finally {\n    exportLoading.value = false\n  }\n}\n\n/** 展开/折叠操作 */\nconst isExpandAll = ref(true) // 是否展开，默认全部展开\nconst refreshTable = ref(true) // 重新渲染表格状态\nconst toggleExpandAll = async () => {\n  refreshTable.value = false\n  isExpandAll.value = !isExpandAll.value\n  await nextTick()\n  refreshTable.value = true\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/demo/demo03/erp/Demo03StudentForm.vue",
    "content": "<template>\n  <Dialog :title=\"dialogTitle\" v-model=\"dialogVisible\">\n    <el-form\n      ref=\"formRef\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"100px\"\n      v-loading=\"formLoading\"\n    >\n      <el-form-item label=\"名字\" prop=\"name\">\n        <el-input v-model=\"formData.name\" placeholder=\"请输入名字\" />\n      </el-form-item>\n      <el-form-item label=\"性别\" prop=\"sex\">\n        <el-radio-group v-model=\"formData.sex\">\n          <el-radio\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)\"\n            :key=\"dict.value\"\n            :label=\"dict.value\"\n          >\n            {{ dict.label }}\n          </el-radio>\n        </el-radio-group>\n      </el-form-item>\n      <el-form-item label=\"出生日期\" prop=\"birthday\">\n        <el-date-picker\n          v-model=\"formData.birthday\"\n          type=\"date\"\n          value-format=\"x\"\n          placeholder=\"选择出生日期\"\n        />\n      </el-form-item>\n      <el-form-item label=\"简介\" prop=\"description\">\n        <Editor v-model=\"formData.description\" height=\"150px\" />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button @click=\"submitForm\" type=\"primary\" :disabled=\"formLoading\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script setup lang=\"ts\">\nimport { getIntDictOptions, DICT_TYPE } from '@/utils/dict'\nimport * as Demo03StudentApi from '@/api/infra/demo/demo03/erp'\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  name: undefined,\n  sex: undefined,\n  birthday: undefined,\n  description: undefined\n})\nconst formRules = reactive({\n  name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],\n  sex: [{ required: true, message: '性别不能为空', trigger: 'blur' }],\n  birthday: [{ required: true, message: '出生日期不能为空', trigger: 'blur' }],\n  description: [{ required: true, message: '简介不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await Demo03StudentApi.getDemo03Student(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  await formRef.value.validate()\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as unknown as Demo03StudentApi.Demo03StudentVO\n    if (formType.value === 'create') {\n      await Demo03StudentApi.createDemo03Student(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await Demo03StudentApi.updateDemo03Student(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    name: undefined,\n    sex: undefined,\n    birthday: undefined,\n    description: undefined\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/demo/demo03/erp/components/Demo03CourseForm.vue",
    "content": "<template>\n  <Dialog :title=\"dialogTitle\" v-model=\"dialogVisible\">\n    <el-form\n      ref=\"formRef\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"100px\"\n      v-loading=\"formLoading\"\n    >\n      <el-form-item label=\"名字\" prop=\"name\">\n        <el-input v-model=\"formData.name\" placeholder=\"请输入名字\" />\n      </el-form-item>\n      <el-form-item label=\"分数\" prop=\"score\">\n        <el-input v-model=\"formData.score\" placeholder=\"请输入分数\" />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button @click=\"submitForm\" type=\"primary\" :disabled=\"formLoading\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script setup lang=\"ts\">\nimport * as Demo03StudentApi from '@/api/infra/demo/demo03/erp'\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  studentId: undefined,\n  name: undefined,\n  score: undefined\n})\nconst formRules = reactive({\n  studentId: [{ required: true, message: '学生编号不能为空', trigger: 'blur' }],\n  name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],\n  score: [{ required: true, message: '分数不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number, studentId: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  formData.value.studentId = studentId\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await Demo03StudentApi.getDemo03Course(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  await formRef.value.validate()\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value\n    if (formType.value === 'create') {\n      await Demo03StudentApi.createDemo03Course(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await Demo03StudentApi.updateDemo03Course(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    studentId: undefined,\n    name: undefined,\n    score: undefined\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/demo/demo03/erp/components/Demo03CourseList.vue",
    "content": "<template>\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-button\n      v-hasPermi=\"['infra:demo03-student:create']\"\n      plain\n      type=\"primary\"\n      @click=\"openForm('create')\"\n    >\n      <Icon class=\"mr-5px\" icon=\"ep:plus\" />\n      新增\n    </el-button>\n    <el-table v-loading=\"loading\" :data=\"list\" :show-overflow-tooltip=\"true\" :stripe=\"true\">\n      <el-table-column align=\"center\" label=\"编号\" prop=\"id\" />\n      <el-table-column align=\"center\" label=\"名字\" prop=\"name\" />\n      <el-table-column align=\"center\" label=\"分数\" prop=\"score\" />\n      <el-table-column\n        :formatter=\"dateFormatter\"\n        align=\"center\"\n        label=\"创建时间\"\n        prop=\"createTime\"\n        width=\"180px\"\n      />\n      <el-table-column align=\"center\" label=\"操作\">\n        <template #default=\"scope\">\n          <el-button\n            v-hasPermi=\"['infra:demo03-student:update']\"\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n          >\n            编辑\n          </el-button>\n          <el-button\n            v-hasPermi=\"['infra:demo03-student:delete']\"\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      v-model:limit=\"queryParams.pageSize\"\n      v-model:page=\"queryParams.pageNo\"\n      :total=\"total\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n  <!-- 表单弹窗：添加/修改 -->\n  <Demo03CourseForm ref=\"formRef\" @success=\"getList\" />\n</template>\n\n<script lang=\"ts\" setup>\nimport { dateFormatter } from '@/utils/formatTime'\nimport * as Demo03StudentApi from '@/api/infra/demo/demo03/erp'\nimport Demo03CourseForm from './Demo03CourseForm.vue'\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst props = defineProps<{\n  studentId?: number // 学生编号（主表的关联字段）\n}>()\nconst loading = ref(false) // 列表的加载中\nconst list = ref([]) // 列表的数据\nconst total = ref(0) // 列表的总页数\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  studentId: undefined as unknown\n})\n\n/** 监听主表的关联字段的变化，加载对应的子表数据 */\nwatch(\n  () => props.studentId,\n  (val: number) => {\n    if (!val) {\n      return\n    }\n    queryParams.studentId = val\n    handleQuery()\n  },\n  { immediate: true, deep: true }\n)\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await Demo03StudentApi.getDemo03CoursePage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  if (!props.studentId) {\n    message.error('请选择一个学生')\n    return\n  }\n  formRef.value.open(type, id, props.studentId)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await Demo03StudentApi.deleteDemo03Course(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/demo/demo03/erp/components/Demo03GradeForm.vue",
    "content": "<template>\n  <Dialog :title=\"dialogTitle\" v-model=\"dialogVisible\">\n    <el-form\n      ref=\"formRef\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"100px\"\n      v-loading=\"formLoading\"\n    >\n      <el-form-item label=\"名字\" prop=\"name\">\n        <el-input v-model=\"formData.name\" placeholder=\"请输入名字\" />\n      </el-form-item>\n      <el-form-item label=\"班主任\" prop=\"teacher\">\n        <el-input v-model=\"formData.teacher\" placeholder=\"请输入班主任\" />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button @click=\"submitForm\" type=\"primary\" :disabled=\"formLoading\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script setup lang=\"ts\">\nimport * as Demo03StudentApi from '@/api/infra/demo/demo03/erp'\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  studentId: undefined,\n  name: undefined,\n  teacher: undefined\n})\nconst formRules = reactive({\n  studentId: [{ required: true, message: '学生编号不能为空', trigger: 'blur' }],\n  name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],\n  teacher: [{ required: true, message: '班主任不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number, studentId: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  formData.value.studentId = studentId\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await Demo03StudentApi.getDemo03Grade(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  await formRef.value.validate()\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value\n    if (formType.value === 'create') {\n      await Demo03StudentApi.createDemo03Grade(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await Demo03StudentApi.updateDemo03Grade(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    studentId: undefined,\n    name: undefined,\n    teacher: undefined\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/demo/demo03/erp/components/Demo03GradeList.vue",
    "content": "<template>\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-button\n      v-hasPermi=\"['infra:demo03-student:create']\"\n      plain\n      type=\"primary\"\n      @click=\"openForm('create')\"\n    >\n      <Icon class=\"mr-5px\" icon=\"ep:plus\" />\n      新增\n    </el-button>\n    <el-table v-loading=\"loading\" :data=\"list\" :show-overflow-tooltip=\"true\" :stripe=\"true\">\n      <el-table-column align=\"center\" label=\"编号\" prop=\"id\" />\n      <el-table-column align=\"center\" label=\"名字\" prop=\"name\" />\n      <el-table-column align=\"center\" label=\"班主任\" prop=\"teacher\" />\n      <el-table-column\n        :formatter=\"dateFormatter\"\n        align=\"center\"\n        label=\"创建时间\"\n        prop=\"createTime\"\n        width=\"180px\"\n      />\n      <el-table-column align=\"center\" label=\"操作\">\n        <template #default=\"scope\">\n          <el-button\n            v-hasPermi=\"['infra:demo03-student:update']\"\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n          >\n            编辑\n          </el-button>\n          <el-button\n            v-hasPermi=\"['infra:demo03-student:delete']\"\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      v-model:limit=\"queryParams.pageSize\"\n      v-model:page=\"queryParams.pageNo\"\n      :total=\"total\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n  <!-- 表单弹窗：添加/修改 -->\n  <Demo03GradeForm ref=\"formRef\" @success=\"getList\" />\n</template>\n\n<script lang=\"ts\" setup>\nimport { dateFormatter } from '@/utils/formatTime'\nimport * as Demo03StudentApi from '@/api/infra/demo/demo03/erp'\nimport Demo03GradeForm from './Demo03GradeForm.vue'\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst props = defineProps<{\n  studentId?: number // 学生编号（主表的关联字段）\n}>()\nconst loading = ref(false) // 列表的加载中\nconst list = ref([]) // 列表的数据\nconst total = ref(0) // 列表的总页数\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  studentId: undefined as unknown\n})\n\n/** 监听主表的关联字段的变化，加载对应的子表数据 */\nwatch(\n  () => props.studentId,\n  (val: number) => {\n    if (!val) {\n      return\n    }\n    queryParams.studentId = val\n    handleQuery()\n  },\n  { immediate: true, deep: true }\n)\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await Demo03StudentApi.getDemo03GradePage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  if (!props.studentId) {\n    message.error('请选择一个学生')\n    return\n  }\n  formRef.value.open(type, id, props.studentId)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await Demo03StudentApi.deleteDemo03Grade(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/demo/demo03/erp/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <!-- 搜索工作栏 -->\n    <el-form\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      :model=\"queryParams\"\n      class=\"-mb-15px\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"名字\" prop=\"name\">\n        <el-input\n          v-model=\"queryParams.name\"\n          class=\"!w-240px\"\n          clearable\n          placeholder=\"请输入名字\"\n          @keyup.enter=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"性别\" prop=\"sex\">\n        <el-select v-model=\"queryParams.sex\" class=\"!w-240px\" clearable placeholder=\"请选择性别\">\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"创建时间\" prop=\"createTime\">\n        <el-date-picker\n          v-model=\"queryParams.createTime\"\n          :default-time=\"[new Date('1 00:00:00'), new Date('1 23:59:59')]\"\n          class=\"!w-240px\"\n          end-placeholder=\"结束日期\"\n          start-placeholder=\"开始日期\"\n          type=\"daterange\"\n          value-format=\"YYYY-MM-DD HH:mm:ss\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\">\n          <Icon class=\"mr-5px\" icon=\"ep:search\" />\n          搜索\n        </el-button>\n        <el-button @click=\"resetQuery\">\n          <Icon class=\"mr-5px\" icon=\"ep:refresh\" />\n          重置\n        </el-button>\n        <el-button\n          v-hasPermi=\"['infra:demo03-student:create']\"\n          plain\n          type=\"primary\"\n          @click=\"openForm('create')\"\n        >\n          <Icon class=\"mr-5px\" icon=\"ep:plus\" />\n          新增\n        </el-button>\n        <el-button\n          v-hasPermi=\"['infra:demo03-student:export']\"\n          :loading=\"exportLoading\"\n          plain\n          type=\"success\"\n          @click=\"handleExport\"\n        >\n          <Icon class=\"mr-5px\" icon=\"ep:download\" />\n          导出\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table\n      v-loading=\"loading\"\n      :data=\"list\"\n      :show-overflow-tooltip=\"true\"\n      :stripe=\"true\"\n      highlight-current-row\n      @current-change=\"handleCurrentChange\"\n    >\n      <el-table-column align=\"center\" label=\"编号\" prop=\"id\" />\n      <el-table-column align=\"center\" label=\"名字\" prop=\"name\" />\n      <el-table-column align=\"center\" label=\"性别\" prop=\"sex\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.SYSTEM_USER_SEX\" :value=\"scope.row.sex\" />\n        </template>\n      </el-table-column>\n      <el-table-column\n        :formatter=\"dateFormatter\"\n        align=\"center\"\n        label=\"出生日期\"\n        prop=\"birthday\"\n        width=\"180px\"\n      />\n      <el-table-column align=\"center\" label=\"简介\" prop=\"description\" />\n      <el-table-column\n        :formatter=\"dateFormatter\"\n        align=\"center\"\n        label=\"创建时间\"\n        prop=\"createTime\"\n        width=\"180px\"\n      />\n      <el-table-column align=\"center\" label=\"操作\">\n        <template #default=\"scope\">\n          <el-button\n            v-hasPermi=\"['infra:demo03-student:update']\"\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n          >\n            编辑\n          </el-button>\n          <el-button\n            v-hasPermi=\"['infra:demo03-student:delete']\"\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      v-model:limit=\"queryParams.pageSize\"\n      v-model:page=\"queryParams.pageNo\"\n      :total=\"total\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <Demo03StudentForm ref=\"formRef\" @success=\"getList\" />\n  <!-- 子表的列表 -->\n  <ContentWrap>\n    <el-tabs model-value=\"demo03Course\">\n      <el-tab-pane label=\"学生课程\" name=\"demo03Course\">\n        <Demo03CourseList :student-id=\"currentRow?.id\" />\n      </el-tab-pane>\n      <el-tab-pane label=\"学生班级\" name=\"demo03Grade\">\n        <Demo03GradeList :student-id=\"currentRow?.id\" />\n      </el-tab-pane>\n    </el-tabs>\n  </ContentWrap>\n</template>\n\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getIntDictOptions } from '@/utils/dict'\nimport { dateFormatter } from '@/utils/formatTime'\nimport download from '@/utils/download'\nimport * as Demo03StudentApi from '@/api/infra/demo/demo03/erp'\nimport Demo03StudentForm from './Demo03StudentForm.vue'\nimport Demo03CourseList from './components/Demo03CourseList.vue'\nimport Demo03GradeList from './components/Demo03GradeList.vue'\n\ndefineOptions({ name: 'Demo03Student' })\n\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst list = ref([]) // 列表的数据\nconst total = ref(0) // 列表的总页数\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  name: null,\n  sex: null,\n  description: null,\n  createTime: []\n})\nconst queryFormRef = ref() // 搜索的表单\nconst exportLoading = ref(false) // 导出的加载中\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await Demo03StudentApi.getDemo03StudentPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await Demo03StudentApi.deleteDemo03Student(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 导出按钮操作 */\nconst handleExport = async () => {\n  try {\n    // 导出的二次确认\n    await message.exportConfirm()\n    // 发起导出\n    exportLoading.value = true\n    const data = await Demo03StudentApi.exportDemo03Student(queryParams)\n    download.excel(data, '学生.xls')\n  } catch {\n  } finally {\n    exportLoading.value = false\n  }\n}\n\n/** 选中行操作 */\nconst currentRow = ref({}) // 选中行\nconst handleCurrentChange = (row) => {\n  currentRow.value = row\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/demo/demo03/inner/Demo03StudentForm.vue",
    "content": "<template>\n  <Dialog :title=\"dialogTitle\" v-model=\"dialogVisible\">\n    <el-form\n      ref=\"formRef\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"100px\"\n      v-loading=\"formLoading\"\n    >\n      <el-form-item label=\"名字\" prop=\"name\">\n        <el-input v-model=\"formData.name\" placeholder=\"请输入名字\" />\n      </el-form-item>\n      <el-form-item label=\"性别\" prop=\"sex\">\n        <el-radio-group v-model=\"formData.sex\">\n          <el-radio\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)\"\n            :key=\"dict.value\"\n            :label=\"dict.value\"\n          >\n            {{ dict.label }}\n          </el-radio>\n        </el-radio-group>\n      </el-form-item>\n      <el-form-item label=\"出生日期\" prop=\"birthday\">\n        <el-date-picker\n          v-model=\"formData.birthday\"\n          type=\"date\"\n          value-format=\"x\"\n          placeholder=\"选择出生日期\"\n        />\n      </el-form-item>\n      <el-form-item label=\"简介\" prop=\"description\">\n        <Editor v-model=\"formData.description\" height=\"150px\" />\n      </el-form-item>\n    </el-form>\n    <!-- 子表的表单 -->\n    <el-tabs v-model=\"subTabsName\">\n      <el-tab-pane label=\"学生课程\" name=\"demo03Course\">\n        <Demo03CourseForm ref=\"demo03CourseFormRef\" :student-id=\"formData.id\" />\n      </el-tab-pane>\n      <el-tab-pane label=\"学生班级\" name=\"demo03Grade\">\n        <Demo03GradeForm ref=\"demo03GradeFormRef\" :student-id=\"formData.id\" />\n      </el-tab-pane>\n    </el-tabs>\n    <template #footer>\n      <el-button @click=\"submitForm\" type=\"primary\" :disabled=\"formLoading\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script setup lang=\"ts\">\nimport { getIntDictOptions, DICT_TYPE } from '@/utils/dict'\nimport * as Demo03StudentApi from '@/api/infra/demo/demo03/inner'\nimport Demo03CourseForm from './components/Demo03CourseForm.vue'\nimport Demo03GradeForm from './components/Demo03GradeForm.vue'\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  name: undefined,\n  sex: undefined,\n  birthday: undefined,\n  description: undefined\n})\nconst formRules = reactive({\n  name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],\n  sex: [{ required: true, message: '性别不能为空', trigger: 'blur' }],\n  birthday: [{ required: true, message: '出生日期不能为空', trigger: 'blur' }],\n  description: [{ required: true, message: '简介不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\n/** 子表的表单 */\nconst subTabsName = ref('demo03Course')\nconst demo03CourseFormRef = ref()\nconst demo03GradeFormRef = ref()\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await Demo03StudentApi.getDemo03Student(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  await formRef.value.validate()\n  // 校验子表单\n  try {\n    await demo03CourseFormRef.value.validate()\n  } catch (e) {\n    subTabsName.value = 'demo03Course'\n    return\n  }\n  try {\n    await demo03GradeFormRef.value.validate()\n  } catch (e) {\n    subTabsName.value = 'demo03Grade'\n    return\n  }\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as unknown as Demo03StudentApi.Demo03StudentVO\n    // 拼接子表的数据\n    data.demo03Courses = demo03CourseFormRef.value.getData()\n    data.demo03Grade = demo03GradeFormRef.value.getData()\n    if (formType.value === 'create') {\n      await Demo03StudentApi.createDemo03Student(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await Demo03StudentApi.updateDemo03Student(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    name: undefined,\n    sex: undefined,\n    birthday: undefined,\n    description: undefined\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/demo/demo03/inner/components/Demo03CourseForm.vue",
    "content": "<template>\n  <el-form\n    ref=\"formRef\"\n    :model=\"formData\"\n    :rules=\"formRules\"\n    v-loading=\"formLoading\"\n    label-width=\"0px\"\n    :inline-message=\"true\"\n  >\n    <el-table :data=\"formData\" class=\"-mt-10px\">\n      <el-table-column label=\"序号\" type=\"index\" width=\"100\" />\n      <el-table-column label=\"名字\" min-width=\"150\">\n        <template #default=\"{ row, $index }\">\n          <el-form-item :prop=\"`${$index}.name`\" :rules=\"formRules.name\" class=\"mb-0px!\">\n            <el-input v-model=\"row.name\" placeholder=\"请输入名字\" />\n          </el-form-item>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"分数\" min-width=\"150\">\n        <template #default=\"{ row, $index }\">\n          <el-form-item :prop=\"`${$index}.score`\" :rules=\"formRules.score\" class=\"mb-0px!\">\n            <el-input v-model=\"row.score\" placeholder=\"请输入分数\" />\n          </el-form-item>\n        </template>\n      </el-table-column>\n      <el-table-column align=\"center\" fixed=\"right\" label=\"操作\" width=\"60\">\n        <template #default=\"{ $index }\">\n          <el-button @click=\"handleDelete($index)\" link>—</el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n  </el-form>\n  <el-row justify=\"center\" class=\"mt-3\">\n    <el-button @click=\"handleAdd\" round>+ 添加学生课程</el-button>\n  </el-row>\n</template>\n<script setup lang=\"ts\">\nimport * as Demo03StudentApi from '@/api/infra/demo/demo03/inner'\n\nconst props = defineProps<{\n  studentId: undefined // 学生编号（主表的关联字段）\n}>()\nconst formLoading = ref(false) // 表单的加载中\nconst formData = ref([])\nconst formRules = reactive({\n  studentId: [{ required: true, message: '学生编号不能为空', trigger: 'blur' }],\n  name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],\n  score: [{ required: true, message: '分数不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\n/** 监听主表的关联字段的变化，加载对应的子表数据 */\nwatch(\n  () => props.studentId,\n  async (val) => {\n    // 1. 重置表单\n    formData.value = []\n    // 2. val 非空，则加载数据\n    if (!val) {\n      return\n    }\n    try {\n      formLoading.value = true\n      formData.value = await Demo03StudentApi.getDemo03CourseListByStudentId(val)\n    } finally {\n      formLoading.value = false\n    }\n  },\n  { immediate: true }\n)\n\n/** 新增按钮操作 */\nconst handleAdd = () => {\n  const row = {\n    id: undefined,\n    studentId: undefined,\n    name: undefined,\n    score: undefined\n  }\n  row.studentId = props.studentId\n  formData.value.push(row)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = (index) => {\n  formData.value.splice(index, 1)\n}\n\n/** 表单校验 */\nconst validate = () => {\n  return formRef.value.validate()\n}\n\n/** 表单值 */\nconst getData = () => {\n  return formData.value\n}\n\ndefineExpose({ validate, getData })\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/demo/demo03/inner/components/Demo03CourseList.vue",
    "content": "<template>\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\" :stripe=\"true\" :show-overflow-tooltip=\"true\">\n      <el-table-column label=\"编号\" align=\"center\" prop=\"id\" />\n      <el-table-column label=\"名字\" align=\"center\" prop=\"name\" />\n      <el-table-column label=\"分数\" align=\"center\" prop=\"score\" />\n      <el-table-column\n        label=\"创建时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        :formatter=\"dateFormatter\"\n        width=\"180px\"\n      />\n    </el-table>\n  </ContentWrap>\n</template>\n<script setup lang=\"ts\">\nimport { dateFormatter } from '@/utils/formatTime'\nimport * as Demo03StudentApi from '@/api/infra/demo/demo03/inner'\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst props = defineProps<{\n  studentId: undefined // 学生编号（主表的关联字段）\n}>()\nconst loading = ref(false) // 列表的加载中\nconst list = ref([]) // 列表的数据\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    list.value = await Demo03StudentApi.getDemo03CourseListByStudentId(props.studentId)\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/demo/demo03/inner/components/Demo03GradeForm.vue",
    "content": "<template>\n  <el-form\n    ref=\"formRef\"\n    :model=\"formData\"\n    :rules=\"formRules\"\n    label-width=\"100px\"\n    v-loading=\"formLoading\"\n  >\n    <el-form-item label=\"名字\" prop=\"name\">\n      <el-input v-model=\"formData.name\" placeholder=\"请输入名字\" />\n    </el-form-item>\n    <el-form-item label=\"班主任\" prop=\"teacher\">\n      <el-input v-model=\"formData.teacher\" placeholder=\"请输入班主任\" />\n    </el-form-item>\n  </el-form>\n</template>\n<script setup lang=\"ts\">\nimport * as Demo03StudentApi from '@/api/infra/demo/demo03/inner'\n\nconst props = defineProps<{\n  studentId: undefined // 学生编号（主表的关联字段）\n}>()\nconst formLoading = ref(false) // 表单的加载中\nconst formData = ref([])\nconst formRules = reactive({\n  studentId: [{ required: true, message: '学生编号不能为空', trigger: 'blur' }],\n  name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],\n  teacher: [{ required: true, message: '班主任不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\n/** 监听主表的关联字段的变化，加载对应的子表数据 */\nwatch(\n  () => props.studentId,\n  async (val) => {\n    // 1. 重置表单\n    formData.value = {\n      id: undefined,\n      studentId: undefined,\n      name: undefined,\n      teacher: undefined\n    }\n    // 2. val 非空，则加载数据\n    if (!val) {\n      return\n    }\n    try {\n      formLoading.value = true\n      const data = await Demo03StudentApi.getDemo03GradeByStudentId(val)\n      if (!data) {\n        return\n      }\n      formData.value = data\n    } finally {\n      formLoading.value = false\n    }\n  },\n  { immediate: true }\n)\n\n/** 表单校验 */\nconst validate = () => {\n  return formRef.value.validate()\n}\n\n/** 表单值 */\nconst getData = () => {\n  return formData.value\n}\n\ndefineExpose({ validate, getData })\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/demo/demo03/inner/components/Demo03GradeList.vue",
    "content": "<template>\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\" :stripe=\"true\" :show-overflow-tooltip=\"true\">\n      <el-table-column label=\"编号\" align=\"center\" prop=\"id\" />\n      <el-table-column label=\"名字\" align=\"center\" prop=\"name\" />\n      <el-table-column label=\"班主任\" align=\"center\" prop=\"teacher\" />\n      <el-table-column\n        label=\"创建时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        :formatter=\"dateFormatter\"\n        width=\"180px\"\n      />\n    </el-table>\n  </ContentWrap>\n</template>\n<script setup lang=\"ts\">\nimport { dateFormatter } from '@/utils/formatTime'\nimport * as Demo03StudentApi from '@/api/infra/demo/demo03/inner'\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst props = defineProps<{\n  studentId: undefined // 学生编号（主表的关联字段）\n}>()\nconst loading = ref(false) // 列表的加载中\nconst list = ref([]) // 列表的数据\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await Demo03StudentApi.getDemo03GradeByStudentId(props.studentId)\n    if (!data) {\n      return\n    }\n    list.value.push(data)\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/demo/demo03/inner/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <!-- 搜索工作栏 -->\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"名字\" prop=\"name\">\n        <el-input\n          v-model=\"queryParams.name\"\n          placeholder=\"请输入名字\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"性别\" prop=\"sex\">\n        <el-select v-model=\"queryParams.sex\" placeholder=\"请选择性别\" clearable class=\"!w-240px\">\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"创建时间\" prop=\"createTime\">\n        <el-date-picker\n          v-model=\"queryParams.createTime\"\n          value-format=\"YYYY-MM-DD HH:mm:ss\"\n          type=\"daterange\"\n          start-placeholder=\"开始日期\"\n          end-placeholder=\"结束日期\"\n          :default-time=\"[new Date('1 00:00:00'), new Date('1 23:59:59')]\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n        <el-button\n          type=\"primary\"\n          plain\n          @click=\"openForm('create')\"\n          v-hasPermi=\"['infra:demo03-student:create']\"\n        >\n          <Icon icon=\"ep:plus\" class=\"mr-5px\" /> 新增\n        </el-button>\n        <el-button\n          type=\"success\"\n          plain\n          @click=\"handleExport\"\n          :loading=\"exportLoading\"\n          v-hasPermi=\"['infra:demo03-student:export']\"\n        >\n          <Icon icon=\"ep:download\" class=\"mr-5px\" /> 导出\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\" :stripe=\"true\" :show-overflow-tooltip=\"true\">\n      <!-- 子表的列表 -->\n      <el-table-column type=\"expand\">\n        <template #default=\"scope\">\n          <el-tabs model-value=\"demo03Course\">\n            <el-tab-pane label=\"学生课程\" name=\"demo03Course\">\n              <Demo03CourseList :student-id=\"scope.row.id\" />\n            </el-tab-pane>\n            <el-tab-pane label=\"学生班级\" name=\"demo03Grade\">\n              <Demo03GradeList :student-id=\"scope.row.id\" />\n            </el-tab-pane>\n          </el-tabs>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"编号\" align=\"center\" prop=\"id\" />\n      <el-table-column label=\"名字\" align=\"center\" prop=\"name\" />\n      <el-table-column label=\"性别\" align=\"center\" prop=\"sex\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.SYSTEM_USER_SEX\" :value=\"scope.row.sex\" />\n        </template>\n      </el-table-column>\n      <el-table-column\n        label=\"出生日期\"\n        align=\"center\"\n        prop=\"birthday\"\n        :formatter=\"dateFormatter\"\n        width=\"180px\"\n      />\n      <el-table-column label=\"简介\" align=\"center\" prop=\"description\" />\n      <el-table-column\n        label=\"创建时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        :formatter=\"dateFormatter\"\n        width=\"180px\"\n      />\n      <el-table-column label=\"操作\" align=\"center\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n            v-hasPermi=\"['infra:demo03-student:update']\"\n          >\n            编辑\n          </el-button>\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n            v-hasPermi=\"['infra:demo03-student:delete']\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <Demo03StudentForm ref=\"formRef\" @success=\"getList\" />\n</template>\n\n<script setup lang=\"ts\">\nimport { getIntDictOptions, DICT_TYPE } from '@/utils/dict'\nimport { dateFormatter } from '@/utils/formatTime'\nimport download from '@/utils/download'\nimport * as Demo03StudentApi from '@/api/infra/demo/demo03/inner'\nimport Demo03StudentForm from './Demo03StudentForm.vue'\nimport Demo03CourseList from './components/Demo03CourseList.vue'\nimport Demo03GradeList from './components/Demo03GradeList.vue'\n\ndefineOptions({ name: 'Demo03Student' })\n\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst list = ref([]) // 列表的数据\nconst total = ref(0) // 列表的总页数\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  name: null,\n  sex: null,\n  description: null,\n  createTime: []\n})\nconst queryFormRef = ref() // 搜索的表单\nconst exportLoading = ref(false) // 导出的加载中\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await Demo03StudentApi.getDemo03StudentPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await Demo03StudentApi.deleteDemo03Student(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 导出按钮操作 */\nconst handleExport = async () => {\n  try {\n    // 导出的二次确认\n    await message.exportConfirm()\n    // 发起导出\n    exportLoading.value = true\n    const data = await Demo03StudentApi.exportDemo03Student(queryParams)\n    download.excel(data, '学生.xls')\n  } catch {\n  } finally {\n    exportLoading.value = false\n  }\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/demo/demo03/normal/Demo03StudentForm.vue",
    "content": "<template>\n  <Dialog :title=\"dialogTitle\" v-model=\"dialogVisible\">\n    <el-form\n      ref=\"formRef\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"100px\"\n      v-loading=\"formLoading\"\n    >\n      <el-form-item label=\"名字\" prop=\"name\">\n        <el-input v-model=\"formData.name\" placeholder=\"请输入名字\" />\n      </el-form-item>\n      <el-form-item label=\"性别\" prop=\"sex\">\n        <el-radio-group v-model=\"formData.sex\">\n          <el-radio\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)\"\n            :key=\"dict.value\"\n            :label=\"dict.value\"\n          >\n            {{ dict.label }}\n          </el-radio>\n        </el-radio-group>\n      </el-form-item>\n      <el-form-item label=\"出生日期\" prop=\"birthday\">\n        <el-date-picker\n          v-model=\"formData.birthday\"\n          type=\"date\"\n          value-format=\"x\"\n          placeholder=\"选择出生日期\"\n        />\n      </el-form-item>\n      <el-form-item label=\"简介\" prop=\"description\">\n        <Editor v-model=\"formData.description\" height=\"150px\" />\n      </el-form-item>\n    </el-form>\n    <!-- 子表的表单 -->\n    <el-tabs v-model=\"subTabsName\">\n      <el-tab-pane label=\"学生课程\" name=\"demo03Course\">\n        <Demo03CourseForm ref=\"demo03CourseFormRef\" :student-id=\"formData.id\" />\n      </el-tab-pane>\n      <el-tab-pane label=\"学生班级\" name=\"demo03Grade\">\n        <Demo03GradeForm ref=\"demo03GradeFormRef\" :student-id=\"formData.id\" />\n      </el-tab-pane>\n    </el-tabs>\n    <template #footer>\n      <el-button @click=\"submitForm\" type=\"primary\" :disabled=\"formLoading\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script setup lang=\"ts\">\nimport { getIntDictOptions, DICT_TYPE } from '@/utils/dict'\nimport * as Demo03StudentApi from '@/api/infra/demo/demo03/normal'\nimport Demo03CourseForm from './components/Demo03CourseForm.vue'\nimport Demo03GradeForm from './components/Demo03GradeForm.vue'\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  name: undefined,\n  sex: undefined,\n  birthday: undefined,\n  description: undefined\n})\nconst formRules = reactive({\n  name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],\n  sex: [{ required: true, message: '性别不能为空', trigger: 'blur' }],\n  birthday: [{ required: true, message: '出生日期不能为空', trigger: 'blur' }],\n  description: [{ required: true, message: '简介不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\n/** 子表的表单 */\nconst subTabsName = ref('demo03Course')\nconst demo03CourseFormRef = ref()\nconst demo03GradeFormRef = ref()\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await Demo03StudentApi.getDemo03Student(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  await formRef.value.validate()\n  // 校验子表单\n  try {\n    await demo03CourseFormRef.value.validate()\n  } catch (e) {\n    subTabsName.value = 'demo03Course'\n    return\n  }\n  try {\n    await demo03GradeFormRef.value.validate()\n  } catch (e) {\n    subTabsName.value = 'demo03Grade'\n    return\n  }\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as unknown as Demo03StudentApi.Demo03StudentVO\n    // 拼接子表的数据\n    data.demo03Courses = demo03CourseFormRef.value.getData()\n    data.demo03Grade = demo03GradeFormRef.value.getData()\n    if (formType.value === 'create') {\n      await Demo03StudentApi.createDemo03Student(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await Demo03StudentApi.updateDemo03Student(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    name: undefined,\n    sex: undefined,\n    birthday: undefined,\n    description: undefined\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/demo/demo03/normal/components/Demo03CourseForm.vue",
    "content": "<template>\n  <el-form\n    ref=\"formRef\"\n    :model=\"formData\"\n    :rules=\"formRules\"\n    v-loading=\"formLoading\"\n    label-width=\"0px\"\n    :inline-message=\"true\"\n  >\n    <el-table :data=\"formData\" class=\"-mt-10px\">\n      <el-table-column label=\"序号\" type=\"index\" width=\"100\" />\n      <el-table-column label=\"名字\" min-width=\"150\">\n        <template #default=\"{ row, $index }\">\n          <el-form-item :prop=\"`${$index}.name`\" :rules=\"formRules.name\" class=\"mb-0px!\">\n            <el-input v-model=\"row.name\" placeholder=\"请输入名字\" />\n          </el-form-item>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"分数\" min-width=\"150\">\n        <template #default=\"{ row, $index }\">\n          <el-form-item :prop=\"`${$index}.score`\" :rules=\"formRules.score\" class=\"mb-0px!\">\n            <el-input v-model=\"row.score\" placeholder=\"请输入分数\" />\n          </el-form-item>\n        </template>\n      </el-table-column>\n      <el-table-column align=\"center\" fixed=\"right\" label=\"操作\" width=\"60\">\n        <template #default=\"{ $index }\">\n          <el-button @click=\"handleDelete($index)\" link>—</el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n  </el-form>\n  <el-row justify=\"center\" class=\"mt-3\">\n    <el-button @click=\"handleAdd\" round>+ 添加学生课程</el-button>\n  </el-row>\n</template>\n<script setup lang=\"ts\">\nimport * as Demo03StudentApi from '@/api/infra/demo/demo03/normal'\n\nconst props = defineProps<{\n  studentId: undefined // 学生编号（主表的关联字段）\n}>()\nconst formLoading = ref(false) // 表单的加载中\nconst formData = ref([])\nconst formRules = reactive({\n  studentId: [{ required: true, message: '学生编号不能为空', trigger: 'blur' }],\n  name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],\n  score: [{ required: true, message: '分数不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\n/** 监听主表的关联字段的变化，加载对应的子表数据 */\nwatch(\n  () => props.studentId,\n  async (val) => {\n    // 1. 重置表单\n    formData.value = []\n    // 2. val 非空，则加载数据\n    if (!val) {\n      return\n    }\n    try {\n      formLoading.value = true\n      formData.value = await Demo03StudentApi.getDemo03CourseListByStudentId(val)\n    } finally {\n      formLoading.value = false\n    }\n  },\n  { immediate: true }\n)\n\n/** 新增按钮操作 */\nconst handleAdd = () => {\n  const row = {\n    id: undefined,\n    studentId: undefined,\n    name: undefined,\n    score: undefined\n  }\n  row.studentId = props.studentId\n  formData.value.push(row)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = (index) => {\n  formData.value.splice(index, 1)\n}\n\n/** 表单校验 */\nconst validate = () => {\n  return formRef.value.validate()\n}\n\n/** 表单值 */\nconst getData = () => {\n  return formData.value\n}\n\ndefineExpose({ validate, getData })\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/demo/demo03/normal/components/Demo03GradeForm.vue",
    "content": "<template>\n  <el-form\n    ref=\"formRef\"\n    :model=\"formData\"\n    :rules=\"formRules\"\n    label-width=\"100px\"\n    v-loading=\"formLoading\"\n  >\n    <el-form-item label=\"名字\" prop=\"name\">\n      <el-input v-model=\"formData.name\" placeholder=\"请输入名字\" />\n    </el-form-item>\n    <el-form-item label=\"班主任\" prop=\"teacher\">\n      <el-input v-model=\"formData.teacher\" placeholder=\"请输入班主任\" />\n    </el-form-item>\n  </el-form>\n</template>\n<script setup lang=\"ts\">\nimport * as Demo03StudentApi from '@/api/infra/demo/demo03/normal'\n\nconst props = defineProps<{\n  studentId: undefined // 学生编号（主表的关联字段）\n}>()\nconst formLoading = ref(false) // 表单的加载中\nconst formData = ref([])\nconst formRules = reactive({\n  studentId: [{ required: true, message: '学生编号不能为空', trigger: 'blur' }],\n  name: [{ required: true, message: '名字不能为空', trigger: 'blur' }],\n  teacher: [{ required: true, message: '班主任不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\n/** 监听主表的关联字段的变化，加载对应的子表数据 */\nwatch(\n  () => props.studentId,\n  async (val) => {\n    // 1. 重置表单\n    formData.value = {\n      id: undefined,\n      studentId: undefined,\n      name: undefined,\n      teacher: undefined\n    }\n    // 2. val 非空，则加载数据\n    if (!val) {\n      return\n    }\n    try {\n      formLoading.value = true\n      const data = await Demo03StudentApi.getDemo03GradeByStudentId(val)\n      if (!data) {\n        return\n      }\n      formData.value = data\n    } finally {\n      formLoading.value = false\n    }\n  },\n  { immediate: true }\n)\n\n/** 表单校验 */\nconst validate = () => {\n  return formRef.value.validate()\n}\n\n/** 表单值 */\nconst getData = () => {\n  return formData.value\n}\n\ndefineExpose({ validate, getData })\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/demo/demo03/normal/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <!-- 搜索工作栏 -->\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"名字\" prop=\"name\">\n        <el-input\n          v-model=\"queryParams.name\"\n          placeholder=\"请输入名字\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"性别\" prop=\"sex\">\n        <el-select v-model=\"queryParams.sex\" placeholder=\"请选择性别\" clearable class=\"!w-240px\">\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"创建时间\" prop=\"createTime\">\n        <el-date-picker\n          v-model=\"queryParams.createTime\"\n          value-format=\"YYYY-MM-DD HH:mm:ss\"\n          type=\"daterange\"\n          start-placeholder=\"开始日期\"\n          end-placeholder=\"结束日期\"\n          :default-time=\"[new Date('1 00:00:00'), new Date('1 23:59:59')]\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n        <el-button\n          type=\"primary\"\n          plain\n          @click=\"openForm('create')\"\n          v-hasPermi=\"['infra:demo03-student:create']\"\n        >\n          <Icon icon=\"ep:plus\" class=\"mr-5px\" /> 新增\n        </el-button>\n        <el-button\n          type=\"success\"\n          plain\n          @click=\"handleExport\"\n          :loading=\"exportLoading\"\n          v-hasPermi=\"['infra:demo03-student:export']\"\n        >\n          <Icon icon=\"ep:download\" class=\"mr-5px\" /> 导出\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\" :stripe=\"true\" :show-overflow-tooltip=\"true\">\n      <el-table-column label=\"编号\" align=\"center\" prop=\"id\" />\n      <el-table-column label=\"名字\" align=\"center\" prop=\"name\" />\n      <el-table-column label=\"性别\" align=\"center\" prop=\"sex\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.SYSTEM_USER_SEX\" :value=\"scope.row.sex\" />\n        </template>\n      </el-table-column>\n      <el-table-column\n        label=\"出生日期\"\n        align=\"center\"\n        prop=\"birthday\"\n        :formatter=\"dateFormatter\"\n        width=\"180px\"\n      />\n      <el-table-column label=\"简介\" align=\"center\" prop=\"description\" />\n      <el-table-column\n        label=\"创建时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        :formatter=\"dateFormatter\"\n        width=\"180px\"\n      />\n      <el-table-column label=\"操作\" align=\"center\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n            v-hasPermi=\"['infra:demo03-student:update']\"\n          >\n            编辑\n          </el-button>\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n            v-hasPermi=\"['infra:demo03-student:delete']\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <Demo03StudentForm ref=\"formRef\" @success=\"getList\" />\n</template>\n\n<script setup lang=\"ts\">\nimport { getIntDictOptions, DICT_TYPE } from '@/utils/dict'\nimport { dateFormatter } from '@/utils/formatTime'\nimport download from '@/utils/download'\nimport * as Demo03StudentApi from '@/api/infra/demo/demo03/normal'\nimport Demo03StudentForm from './Demo03StudentForm.vue'\n\ndefineOptions({ name: 'Demo03Student' })\n\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst list = ref([]) // 列表的数据\nconst total = ref(0) // 列表的总页数\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  name: null,\n  sex: null,\n  description: null,\n  createTime: []\n})\nconst queryFormRef = ref() // 搜索的表单\nconst exportLoading = ref(false) // 导出的加载中\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await Demo03StudentApi.getDemo03StudentPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await Demo03StudentApi.deleteDemo03Student(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 导出按钮操作 */\nconst handleExport = async () => {\n  try {\n    // 导出的二次确认\n    await message.exportConfirm()\n    // 发起导出\n    exportLoading.value = true\n    const data = await Demo03StudentApi.exportDemo03Student(queryParams)\n    download.excel(data, '学生.xls')\n  } catch {\n  } finally {\n    exportLoading.value = false\n  }\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/druid/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <IFrame v-if=\"!loading\" :src=\"url\" />\n  </ContentWrap>\n</template>\n<script lang=\"ts\" setup>\nimport * as ConfigApi from '@/api/infra/config'\n\ndefineOptions({ name: 'InfraDruid' })\n\nconst loading = ref(true) // 是否加载中\nconst url = ref(import.meta.env.VITE_BASE_URL + '/druid/index.html')\n\n/** 初始化 */\nonMounted(async () => {\n  try {\n    const data = await ConfigApi.getConfigKey('url.druid')\n    if (data && data.length > 0) {\n      url.value = data\n    }\n  } finally {\n    loading.value = false\n  }\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/file/FileForm.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" title=\"上传文件\">\n    <el-upload\n      ref=\"uploadRef\"\n      v-model:file-list=\"fileList\"\n      :action=\"uploadUrl\"\n      :auto-upload=\"false\"\n      :data=\"data\"\n      :disabled=\"formLoading\"\n      :limit=\"1\"\n      :on-change=\"handleFileChange\"\n      :on-error=\"submitFormError\"\n      :on-exceed=\"handleExceed\"\n      :on-success=\"submitFormSuccess\"\n      :http-request=\"httpRequest\"\n      accept=\".jpg, .png, .gif\"\n      drag\n    >\n      <i class=\"el-icon-upload\"></i>\n      <div class=\"el-upload__text\"> 将文件拖到此处，或 <em>点击上传</em></div>\n      <template #tip>\n        <div class=\"el-upload__tip\" style=\"color: red\">\n          提示：仅允许导入 jpg、png、gif 格式文件！\n        </div>\n      </template>\n    </el-upload>\n    <template #footer>\n      <el-button :disabled=\"formLoading\" type=\"primary\" @click=\"submitFileForm\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport { useUpload } from '@/components/UploadFile/src/useUpload'\n\ndefineOptions({ name: 'InfraFileForm' })\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst formLoading = ref(false) // 表单的加载中\nconst fileList = ref([]) // 文件列表\nconst data = ref({ path: '' })\nconst uploadRef = ref()\n\nconst { uploadUrl, httpRequest } = useUpload()\n\n/** 打开弹窗 */\nconst open = async () => {\n  dialogVisible.value = true\n  resetForm()\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 处理上传的文件发生变化 */\nconst handleFileChange = (file) => {\n  data.value.path = file.name\n}\n\n/** 提交表单 */\nconst submitFileForm = () => {\n  if (fileList.value.length == 0) {\n    message.error('请上传文件')\n    return\n  }\n  unref(uploadRef)?.submit()\n}\n\n/** 文件上传成功处理 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitFormSuccess = () => {\n  // 清理\n  dialogVisible.value = false\n  formLoading.value = false\n  unref(uploadRef)?.clearFiles()\n  // 提示成功，并刷新\n  message.success(t('common.createSuccess'))\n  emit('success')\n}\n\n/** 上传错误提示 */\nconst submitFormError = (): void => {\n  message.error('上传失败，请您重新上传！')\n  formLoading.value = false\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  // 重置上传状态和文件\n  formLoading.value = false\n  uploadRef.value?.clearFiles()\n}\n\n/** 文件数超出提示 */\nconst handleExceed = (): void => {\n  message.error('最多只能上传一个文件！')\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/file/index.vue",
    "content": "<template>\n  <!-- 搜索 -->\n  <ContentWrap>\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"文件路径\" prop=\"path\">\n        <el-input\n          v-model=\"queryParams.path\"\n          placeholder=\"请输入文件路径\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"文件类型\" prop=\"type\" width=\"80\">\n        <el-input\n          v-model=\"queryParams.type\"\n          placeholder=\"请输入文件类型\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"创建时间\" prop=\"createTime\">\n        <el-date-picker\n          v-model=\"queryParams.createTime\"\n          value-format=\"YYYY-MM-DD HH:mm:ss\"\n          type=\"daterange\"\n          start-placeholder=\"开始日期\"\n          end-placeholder=\"结束日期\"\n          :default-time=\"[new Date('1 00:00:00'), new Date('1 23:59:59')]\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n        <el-button type=\"primary\" plain @click=\"openForm\">\n          <Icon icon=\"ep:upload\" class=\"mr-5px\" /> 上传文件\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column label=\"文件名\" align=\"center\" prop=\"name\" :show-overflow-tooltip=\"true\" />\n      <el-table-column label=\"文件路径\" align=\"center\" prop=\"path\" :show-overflow-tooltip=\"true\" />\n      <el-table-column label=\"URL\" align=\"center\" prop=\"url\" :show-overflow-tooltip=\"true\" />\n      <el-table-column\n        label=\"文件大小\"\n        align=\"center\"\n        prop=\"size\"\n        width=\"120\"\n        :formatter=\"fileSizeFormatter\"\n      />\n      <el-table-column label=\"文件类型\" align=\"center\" prop=\"type\" width=\"180px\" />\n      <el-table-column label=\"文件内容\" align=\"center\" prop=\"url\" width=\"110px\">\n        <template #default=\"{ row }\">\n          <el-image\n            v-if=\"row.type.includes('image')\"\n            class=\"h-80px w-80px\"\n            lazy\n            :src=\"row.url\"\n            :preview-src-list=\"[row.url]\"\n            preview-teleported\n            fit=\"cover\"\n          />\n          <el-link\n            v-else-if=\"row.type.includes('pdf')\"\n            type=\"primary\"\n            :href=\"row.url\"\n            :underline=\"false\"\n            target=\"_blank\"\n            >预览</el-link\n          >\n          <el-link v-else type=\"primary\" download :href=\"row.url\" :underline=\"false\" target=\"_blank\"\n            >下载</el-link\n          >\n        </template>\n      </el-table-column>\n      <el-table-column\n        label=\"上传时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        width=\"180\"\n        :formatter=\"dateFormatter\"\n      />\n      <el-table-column label=\"操作\" align=\"center\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n            v-hasPermi=\"['infra:file:delete']\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <FileForm ref=\"formRef\" @success=\"getList\" />\n</template>\n<script lang=\"ts\" setup>\nimport { fileSizeFormatter } from '@/utils'\nimport { dateFormatter } from '@/utils/formatTime'\nimport * as FileApi from '@/api/infra/file'\nimport FileForm from './FileForm.vue'\n\ndefineOptions({ name: 'InfraFile' })\n\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  name: undefined,\n  type: undefined,\n  path: undefined,\n  createTime: []\n})\nconst queryFormRef = ref() // 搜索的表单\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await FileApi.getFilePage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = () => {\n  formRef.value.open()\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await FileApi.deleteFile(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/fileConfig/FileConfigForm.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" :title=\"dialogTitle\">\n    <el-form\n      ref=\"formRef\"\n      v-loading=\"formLoading\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"120px\"\n    >\n      <el-form-item label=\"配置名\" prop=\"name\">\n        <el-input v-model=\"formData.name\" placeholder=\"请输入配置名\" />\n      </el-form-item>\n      <el-form-item label=\"备注\" prop=\"remark\">\n        <el-input v-model=\"formData.remark\" placeholder=\"请输入备注\" />\n      </el-form-item>\n      <el-form-item label=\"存储器\" prop=\"storage\">\n        <el-select\n          v-model=\"formData.storage\"\n          :disabled=\"formData.id !== undefined\"\n          placeholder=\"请选择存储器\"\n        >\n          <el-option\n            v-for=\"dict in getDictOptions(DICT_TYPE.INFRA_FILE_STORAGE)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"parseInt(dict.value)\"\n          />\n        </el-select>\n      </el-form-item>\n      <!-- DB -->\n      <!-- Local / FTP / SFTP -->\n      <el-form-item\n        v-if=\"formData.storage >= 10 && formData.storage <= 12\"\n        label=\"基础路径\"\n        prop=\"config.basePath\"\n      >\n        <el-input v-model=\"formData.config.basePath\" placeholder=\"请输入基础路径\" />\n      </el-form-item>\n      <el-form-item\n        v-if=\"formData.storage >= 11 && formData.storage <= 12\"\n        label=\"主机地址\"\n        prop=\"config.host\"\n      >\n        <el-input v-model=\"formData.config.host\" placeholder=\"请输入主机地址\" />\n      </el-form-item>\n      <el-form-item\n        v-if=\"formData.storage >= 11 && formData.storage <= 12\"\n        label=\"主机端口\"\n        prop=\"config.port\"\n      >\n        <el-input-number v-model=\"formData.config.port\" :min=\"0\" placeholder=\"请输入主机端口\" />\n      </el-form-item>\n      <el-form-item\n        v-if=\"formData.storage >= 11 && formData.storage <= 12\"\n        label=\"用户名\"\n        prop=\"config.username\"\n      >\n        <el-input v-model=\"formData.config.username\" placeholder=\"请输入密码\" />\n      </el-form-item>\n      <el-form-item\n        v-if=\"formData.storage >= 11 && formData.storage <= 12\"\n        label=\"密码\"\n        prop=\"config.password\"\n      >\n        <el-input v-model=\"formData.config.password\" placeholder=\"请输入密码\" />\n      </el-form-item>\n      <el-form-item v-if=\"formData.storage === 11\" label=\"连接模式\" prop=\"config.mode\">\n        <el-radio-group v-model=\"formData.config.mode\">\n          <el-radio key=\"Active\" label=\"Active\">主动模式</el-radio>\n          <el-radio key=\"Passive\" label=\"Passive\">被动模式</el-radio>\n        </el-radio-group>\n      </el-form-item>\n      <!-- S3 -->\n      <el-form-item v-if=\"formData.storage === 20\" label=\"节点地址\" prop=\"config.endpoint\">\n        <el-input v-model=\"formData.config.endpoint\" placeholder=\"请输入节点地址\" />\n      </el-form-item>\n      <el-form-item v-if=\"formData.storage === 20\" label=\"存储 bucket\" prop=\"config.bucket\">\n        <el-input v-model=\"formData.config.bucket\" placeholder=\"请输入 bucket\" />\n      </el-form-item>\n      <el-form-item v-if=\"formData.storage === 20\" label=\"accessKey\" prop=\"config.accessKey\">\n        <el-input v-model=\"formData.config.accessKey\" placeholder=\"请输入 accessKey\" />\n      </el-form-item>\n      <el-form-item v-if=\"formData.storage === 20\" label=\"accessSecret\" prop=\"config.accessSecret\">\n        <el-input v-model=\"formData.config.accessSecret\" placeholder=\"请输入 accessSecret\" />\n      </el-form-item>\n      <!-- 通用 -->\n      <el-form-item v-if=\"formData.storage === 20\" label=\"自定义域名\">\n        <!-- 无需参数校验，所以去掉 prop -->\n        <el-input v-model=\"formData.config.domain\" placeholder=\"请输入自定义域名\" />\n      </el-form-item>\n      <el-form-item v-else-if=\"formData.storage\" label=\"自定义域名\" prop=\"config.domain\">\n        <el-input v-model=\"formData.config.domain\" placeholder=\"请输入自定义域名\" />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button :disabled=\"formLoading\" type=\"primary\" @click=\"submitForm\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getDictOptions } from '@/utils/dict'\nimport * as FileConfigApi from '@/api/infra/fileConfig'\nimport { FormRules } from 'element-plus'\n\ndefineOptions({ name: 'InfraFileConfigForm' })\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  name: '',\n  storage: 0,\n  remark: '',\n  config: {} as FileConfigApi.FileClientConfig\n})\nconst formRules = reactive<FormRules>({\n  name: [{ required: true, message: '配置名不能为空', trigger: 'blur' }],\n  storage: [{ required: true, message: '存储器不能为空', trigger: 'change' }],\n  config: {\n    basePath: [{ required: true, message: '基础路径不能为空', trigger: 'blur' }],\n    host: [{ required: true, message: '主机地址不能为空', trigger: 'blur' }],\n    port: [{ required: true, message: '主机端口不能为空', trigger: 'blur' }],\n    username: [{ required: true, message: '用户名不能为空', trigger: 'blur' }],\n    password: [{ required: true, message: '密码不能为空', trigger: 'blur' }],\n    mode: [{ required: true, message: '连接模式不能为空', trigger: 'change' }],\n    endpoint: [{ required: true, message: '节点地址不能为空', trigger: 'blur' }],\n    bucket: [{ required: true, message: '存储 bucket 不能为空', trigger: 'blur' }],\n    accessKey: [{ required: true, message: 'accessKey 不能为空', trigger: 'blur' }],\n    accessSecret: [{ required: true, message: 'accessSecret 不能为空', trigger: 'blur' }],\n    domain: [{ required: true, message: '自定义域名不能为空', trigger: 'blur' }]\n  } as FormRules\n})\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await FileConfigApi.getFileConfig(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as unknown as FileConfigApi.FileConfigVO\n    if (formType.value === 'create') {\n      await FileConfigApi.createFileConfig(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await FileConfigApi.updateFileConfig(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    name: '',\n    storage: undefined!,\n    remark: '',\n    config: {} as FileConfigApi.FileClientConfig\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/fileConfig/index.vue",
    "content": "<template>\n  <!-- 搜索 -->\n  <ContentWrap>\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"配置名\" prop=\"name\">\n        <el-input\n          v-model=\"queryParams.name\"\n          placeholder=\"请输入配置名\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"存储器\" prop=\"storage\">\n        <el-select\n          v-model=\"queryParams.storage\"\n          placeholder=\"请选择存储器\"\n          clearable\n          class=\"!w-240px\"\n        >\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.INFRA_FILE_STORAGE)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"创建时间\" prop=\"createTime\">\n        <el-date-picker\n          v-model=\"queryParams.createTime\"\n          value-format=\"YYYY-MM-DD HH:mm:ss\"\n          type=\"daterange\"\n          start-placeholder=\"开始日期\"\n          end-placeholder=\"结束日期\"\n          :default-time=\"[new Date('1 00:00:00'), new Date('1 23:59:59')]\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n        <el-button\n          type=\"primary\"\n          plain\n          @click=\"openForm('create')\"\n          v-hasPermi=\"['infra:file-config:create']\"\n        >\n          <Icon icon=\"ep:plus\" class=\"mr-5px\" /> 新增\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column label=\"编号\" align=\"center\" prop=\"id\" />\n      <el-table-column label=\"配置名\" align=\"center\" prop=\"name\" />\n      <el-table-column label=\"存储器\" align=\"center\" prop=\"storage\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.INFRA_FILE_STORAGE\" :value=\"scope.row.storage\" />\n        </template>\n      </el-table-column>\n      <el-table-column label=\"备注\" align=\"center\" prop=\"remark\" />\n      <el-table-column label=\"主配置\" align=\"center\" prop=\"primary\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.INFRA_BOOLEAN_STRING\" :value=\"scope.row.master\" />\n        </template>\n      </el-table-column>\n      <el-table-column\n        label=\"创建时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        width=\"180\"\n        :formatter=\"dateFormatter\"\n      />\n      <el-table-column label=\"操作\" align=\"center\" width=\"240px\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n            v-hasPermi=\"['infra:file-config:update']\"\n          >\n            编辑\n          </el-button>\n          <el-button\n            link\n            type=\"primary\"\n            :disabled=\"scope.row.master\"\n            @click=\"handleMaster(scope.row.id)\"\n            v-hasPermi=\"['infra:file-config:update']\"\n          >\n            主配置\n          </el-button>\n          <el-button link type=\"primary\" @click=\"handleTest(scope.row.id)\"> 测试 </el-button>\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n            v-hasPermi=\"['infra:file-config:delete']\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <FileConfigForm ref=\"formRef\" @success=\"getList\" />\n</template>\n<script lang=\"ts\" setup>\nimport * as FileConfigApi from '@/api/infra/fileConfig'\nimport FileConfigForm from './FileConfigForm.vue'\nimport { DICT_TYPE, getIntDictOptions } from '@/utils/dict'\nimport { dateFormatter } from '@/utils/formatTime'\n\ndefineOptions({ name: 'InfraFileConfig' })\n\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  name: undefined,\n  storage: undefined,\n  createTime: []\n})\nconst queryFormRef = ref() // 搜索的表单\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await FileConfigApi.getFileConfigPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await FileConfigApi.deleteFileConfig(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 主配置按钮操作 */\nconst handleMaster = async (id) => {\n  try {\n    await message.confirm('是否确认修改配置编号为\"' + id + '\"的数据项为主配置?')\n    await FileConfigApi.updateFileConfigMaster(id)\n    message.success(t('common.updateSuccess'))\n    await getList()\n  } catch {}\n}\n\n/** 测试按钮操作 */\nconst handleTest = async (id) => {\n  try {\n    const response = await FileConfigApi.testFileConfig(id)\n    message.alert('测试通过，上传文件成功！访问地址：' + response)\n  } catch {}\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/job/JobDetail.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" title=\"任务详细\" width=\"700px\">\n    <el-descriptions :column=\"1\" border>\n      <el-descriptions-item label=\"任务编号\" min-width=\"60\">\n        {{ detailData.id }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"任务名称\">\n        {{ detailData.name }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"任务名称\">\n        <dict-tag :type=\"DICT_TYPE.INFRA_JOB_STATUS\" :value=\"detailData.status\" />\n      </el-descriptions-item>\n      <el-descriptions-item label=\"处理器的名字\">\n        {{ detailData.handlerName }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"处理器的参数\">\n        {{ detailData.handlerParam }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"Cron 表达式\">\n        {{ detailData.cronExpression }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"重试次数\">\n        {{ detailData.retryCount }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"重试间隔\">\n        {{ detailData.retryInterval + ' 毫秒' }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"监控超时时间\">\n        {{ detailData.monitorTimeout > 0 ? detailData.monitorTimeout + ' 毫秒' : '未开启' }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"后续执行时间\">\n        <el-timeline>\n          <el-timeline-item\n            v-for=\"(nextTime, index) in nextTimes\"\n            :key=\"index\"\n            :timestamp=\"formatDate(nextTime)\"\n          >\n            第 {{ index + 1 }} 次\n          </el-timeline-item>\n        </el-timeline>\n      </el-descriptions-item>\n    </el-descriptions>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE } from '@/utils/dict'\nimport { formatDate } from '@/utils/formatTime'\nimport * as JobApi from '@/api/infra/job'\n\ndefineOptions({ name: 'InfraJobDetail' })\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst detailLoading = ref(false) // 表单的加载中\nconst detailData = ref({} as JobApi.JobVO) // 详情数据\nconst nextTimes = ref([]) // 下一轮执行时间的数组\n\n/** 打开弹窗 */\nconst open = async (id: number) => {\n  dialogVisible.value = true\n  // 查看，设置数据\n  if (id) {\n    detailLoading.value = true\n    try {\n      detailData.value = await JobApi.getJob(id)\n      // 获取下一次执行时间\n      nextTimes.value = await JobApi.getJobNextTimes(id)\n    } finally {\n      detailLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/job/JobForm.vue",
    "content": "<template>\n  <Dialog :title=\"dialogTitle\" v-model=\"dialogVisible\">\n    <el-form\n      ref=\"formRef\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"120px\"\n      v-loading=\"formLoading\"\n    >\n      <el-form-item label=\"任务名称\" prop=\"name\">\n        <el-input v-model=\"formData.name\" placeholder=\"请输入任务名称\" />\n      </el-form-item>\n      <el-form-item label=\"处理器的名字\" prop=\"handlerName\">\n        <el-input\n          :readonly=\"formData.id !== undefined\"\n          v-model=\"formData.handlerName\"\n          placeholder=\"请输入处理器的名字\"\n        />\n      </el-form-item>\n      <el-form-item label=\"处理器的参数\" prop=\"handlerParam\">\n        <el-input v-model=\"formData.handlerParam\" placeholder=\"请输入处理器的参数\" />\n      </el-form-item>\n      <el-form-item label=\"CRON 表达式\" prop=\"cronExpression\">\n        <crontab v-model=\"formData.cronExpression\" />\n      </el-form-item>\n      <el-form-item label=\"重试次数\" prop=\"retryCount\">\n        <el-input\n          v-model=\"formData.retryCount\"\n          placeholder=\"请输入重试次数。设置为 0 时，不进行重试\"\n        />\n      </el-form-item>\n      <el-form-item label=\"重试间隔\" prop=\"retryInterval\">\n        <el-input\n          v-model=\"formData.retryInterval\"\n          placeholder=\"请输入重试间隔，单位：毫秒。设置为 0 时，无需间隔\"\n        />\n      </el-form-item>\n      <el-form-item label=\"监控超时时间\" prop=\"monitorTimeout\">\n        <el-input v-model=\"formData.monitorTimeout\" placeholder=\"请输入监控超时时间，单位：毫秒\" />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button type=\"primary\" @click=\"submitForm\" :loading=\"formLoading\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport * as JobApi from '@/api/infra/job'\n\ndefineOptions({ name: 'JobForm' })\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  name: '',\n  handlerName: '',\n  handlerParam: '',\n  cronExpression: '',\n  retryCount: undefined,\n  retryInterval: undefined,\n  monitorTimeout: undefined\n})\nconst formRules = reactive({\n  name: [{ required: true, message: '任务名称不能为空', trigger: 'blur' }],\n  handlerName: [{ required: true, message: '处理器的名字不能为空', trigger: 'blur' }],\n  cronExpression: [{ required: true, message: 'CRON 表达式不能为空', trigger: 'blur' }],\n  retryCount: [{ required: true, message: '重试次数不能为空', trigger: 'blur' }],\n  retryInterval: [{ required: true, message: '重试间隔不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await JobApi.getJob(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交按钮 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as unknown as JobApi.JobVO\n    if (formType.value === 'create') {\n      await JobApi.createJob(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await JobApi.updateJob(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    name: '',\n    handlerName: '',\n    handlerParam: '',\n    cronExpression: '',\n    retryCount: undefined,\n    retryInterval: undefined,\n    monitorTimeout: undefined\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/job/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <!-- 搜索工作栏 -->\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"100px\"\n    >\n      <el-form-item label=\"任务名称\" prop=\"name\">\n        <el-input\n          v-model=\"queryParams.name\"\n          placeholder=\"请输入任务名称\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"任务状态\" prop=\"status\">\n        <el-select\n          v-model=\"queryParams.status\"\n          placeholder=\"请选择任务状态\"\n          clearable\n          class=\"!w-240px\"\n        >\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.INFRA_JOB_STATUS)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"处理器的名字\" prop=\"handlerName\">\n        <el-input\n          v-model=\"queryParams.handlerName\"\n          placeholder=\"请输入处理器的名字\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n        <el-button\n          type=\"primary\"\n          plain\n          @click=\"openForm('create')\"\n          v-hasPermi=\"['infra:job:create']\"\n        >\n          <Icon icon=\"ep:plus\" class=\"mr-5px\" /> 新增\n        </el-button>\n        <el-button\n          type=\"success\"\n          plain\n          @click=\"handleExport\"\n          :loading=\"exportLoading\"\n          v-hasPermi=\"['infra:job:export']\"\n        >\n          <Icon icon=\"ep:download\" class=\"mr-5px\" /> 导出\n        </el-button>\n        <el-button type=\"info\" plain @click=\"handleJobLog()\" v-hasPermi=\"['infra:job:query']\">\n          <Icon icon=\"ep:zoom-in\" class=\"mr-5px\" /> 执行日志\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column label=\"任务编号\" align=\"center\" prop=\"id\" />\n      <el-table-column label=\"任务名称\" align=\"center\" prop=\"name\" />\n      <el-table-column label=\"任务状态\" align=\"center\" prop=\"status\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.INFRA_JOB_STATUS\" :value=\"scope.row.status\" />\n        </template>\n      </el-table-column>\n      <el-table-column label=\"处理器的名字\" align=\"center\" prop=\"handlerName\" />\n      <el-table-column label=\"处理器的参数\" align=\"center\" prop=\"handlerParam\" />\n      <el-table-column label=\"CRON 表达式\" align=\"center\" prop=\"cronExpression\" />\n      <el-table-column label=\"操作\" align=\"center\" width=\"200\">\n        <template #default=\"scope\">\n          <el-button\n            type=\"primary\"\n            link\n            @click=\"openForm('update', scope.row.id)\"\n            v-hasPermi=\"['infra:job:update']\"\n          >\n            修改\n          </el-button>\n          <el-button\n            type=\"primary\"\n            link\n            @click=\"handleChangeStatus(scope.row)\"\n            v-hasPermi=\"['infra:job:update']\"\n          >\n            {{ scope.row.status === InfraJobStatusEnum.STOP ? '开启' : '暂停' }}\n          </el-button>\n          <el-button\n            type=\"danger\"\n            link\n            @click=\"handleDelete(scope.row.id)\"\n            v-hasPermi=\"['infra:job:delete']\"\n          >\n            删除\n          </el-button>\n          <el-dropdown\n            @command=\"(command) => handleCommand(command, scope.row)\"\n            v-hasPermi=\"['infra:job:trigger', 'infra:job:query']\"\n          >\n            <el-button type=\"primary\" link><Icon icon=\"ep:d-arrow-right\" /> 更多</el-button>\n            <template #dropdown>\n              <el-dropdown-menu>\n                <el-dropdown-item command=\"handleRun\" v-if=\"checkPermi(['infra:job:trigger'])\">\n                  执行一次\n                </el-dropdown-item>\n                <el-dropdown-item command=\"openDetail\" v-if=\"checkPermi(['infra:job:query'])\">\n                  任务详细\n                </el-dropdown-item>\n                <el-dropdown-item command=\"handleJobLog\" v-if=\"checkPermi(['infra:job:query'])\">\n                  调度日志\n                </el-dropdown-item>\n              </el-dropdown-menu>\n            </template>\n          </el-dropdown>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页组件 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <JobForm ref=\"formRef\" @success=\"getList\" />\n  <!-- 表单弹窗：查看 -->\n  <JobDetail ref=\"detailRef\" />\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getIntDictOptions } from '@/utils/dict'\nimport { checkPermi } from '@/utils/permission'\nimport JobForm from './JobForm.vue'\nimport JobDetail from './JobDetail.vue'\nimport download from '@/utils/download'\nimport * as JobApi from '@/api/infra/job'\nimport { InfraJobStatusEnum } from '@/utils/constants'\n\ndefineOptions({ name: 'InfraJob' })\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\nconst { push } = useRouter() // 路由\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  name: undefined,\n  status: undefined,\n  handlerName: undefined\n})\nconst queryFormRef = ref() // 搜索的表单\nconst exportLoading = ref(false) // 导出的加载中\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await JobApi.getJobPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 导出按钮操作 */\nconst handleExport = async () => {\n  try {\n    // 导出的二次确认\n    await message.exportConfirm()\n    // 发起导出\n    exportLoading.value = true\n    const data = await JobApi.exportJob(queryParams)\n    download.excel(data, '定时任务.xls')\n  } catch {\n  } finally {\n    exportLoading.value = false\n  }\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 修改状态操作 */\nconst handleChangeStatus = async (row: JobApi.JobVO) => {\n  try {\n    // 修改状态的二次确认\n    const text = row.status === InfraJobStatusEnum.STOP ? '开启' : '关闭'\n    await message.confirm(\n      '确认要' + text + '定时任务编号为\"' + row.id + '\"的数据项?',\n      t('common.reminder')\n    )\n    const status =\n      row.status === InfraJobStatusEnum.STOP ? InfraJobStatusEnum.NORMAL : InfraJobStatusEnum.STOP\n    await JobApi.updateJobStatus(row.id, status)\n    message.success(text + '成功')\n    // 刷新列表\n    await getList()\n  } catch {\n    // 取消后，进行恢复按钮\n    row.status =\n      row.status === InfraJobStatusEnum.NORMAL ? InfraJobStatusEnum.STOP : InfraJobStatusEnum.NORMAL\n  }\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await JobApi.deleteJob(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** '更多'操作按钮 */\nconst handleCommand = (command, row) => {\n  switch (command) {\n    case 'handleRun':\n      handleRun(row)\n      break\n    case 'openDetail':\n      openDetail(row.id)\n      break\n    case 'handleJobLog':\n      handleJobLog(row?.id)\n      break\n    default:\n      break\n  }\n}\n\n/** 执行一次 */\nconst handleRun = async (row: JobApi.JobVO) => {\n  try {\n    // 二次确认\n    await message.confirm('确认要立即执行一次' + row.name + '?', t('common.reminder'))\n    // 提交执行\n    await JobApi.runJob(row.id)\n    message.success('执行成功')\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 查看操作 */\nconst detailRef = ref()\nconst openDetail = (id: number) => {\n  detailRef.value.open(id)\n}\n\n/** 跳转执行日志 */\nconst handleJobLog = (id?: number) => {\n  if (id && id > 0) {\n    push('/job/job-log?id=' + id)\n  } else {\n    push('/job/job-log')\n  }\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/job/logger/JobLogDetail.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" title=\"任务详细\" width=\"700px\">\n    <el-descriptions :column=\"1\" border>\n      <el-descriptions-item label=\"日志编号\" min-width=\"60\">\n        {{ detailData.id }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"任务编号\">\n        {{ detailData.jobId }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"处理器的名字\">\n        {{ detailData.handlerName }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"处理器的参数\">\n        {{ detailData.handlerParam }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"第几次执行\">\n        {{ detailData.executeIndex }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"执行时间\">\n        {{ formatDate(detailData.beginTime) + ' ~ ' + formatDate(detailData.endTime) }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"执行时长\">\n        {{ detailData.duration + ' 毫秒' }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"任务状态\">\n        <dict-tag :type=\"DICT_TYPE.INFRA_JOB_LOG_STATUS\" :value=\"detailData.status\" />\n      </el-descriptions-item>\n      <el-descriptions-item label=\"执行结果\">\n        {{ detailData.duration + ' result' }}\n      </el-descriptions-item>\n    </el-descriptions>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE } from '@/utils/dict'\nimport { formatDate } from '@/utils/formatTime'\nimport * as JobLogApi from '@/api/infra/jobLog'\n\ndefineOptions({ name: 'JobLogDetail' })\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst detailLoading = ref(false) // 表单的加载中\nconst detailData = ref({} as JobLogApi.JobLogVO) // 详情数据\n\n/** 打开弹窗 */\nconst open = async (id: number) => {\n  dialogVisible.value = true\n  // 查看，设置数据\n  if (id) {\n    detailLoading.value = true\n    try {\n      detailData.value = await JobLogApi.getJobLog(id)\n    } finally {\n      detailLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/job/logger/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <!-- 搜索工作栏 -->\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"120px\"\n    >\n      <el-form-item label=\"处理器的名字\" prop=\"handlerName\">\n        <el-input\n          v-model=\"queryParams.handlerName\"\n          placeholder=\"请输入处理器的名字\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"开始执行时间\" prop=\"beginTime\">\n        <el-date-picker\n          v-model=\"queryParams.beginTime\"\n          type=\"date\"\n          value-format=\"YYYY-MM-DD HH:mm:ss\"\n          placeholder=\"选择开始执行时间\"\n          clearable\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"结束执行时间\" prop=\"endTime\">\n        <el-date-picker\n          v-model=\"queryParams.endTime\"\n          type=\"date\"\n          value-format=\"YYYY-MM-DD HH:mm:ss\"\n          placeholder=\"选择结束执行时间\"\n          clearable\n          :default-time=\"new Date('1 23:59:59')\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"任务状态\" prop=\"status\">\n        <el-select\n          v-model=\"queryParams.status\"\n          placeholder=\"请选择任务状态\"\n          clearable\n          class=\"!w-240px\"\n        >\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.INFRA_JOB_LOG_STATUS)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n        <el-button\n          type=\"success\"\n          plain\n          @click=\"handleExport\"\n          :loading=\"exportLoading\"\n          v-hasPermi=\"['infra:job:export']\"\n        >\n          <Icon icon=\"ep:download\" class=\"mr-5px\" /> 导出\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column label=\"日志编号\" align=\"center\" prop=\"id\" />\n      <el-table-column label=\"任务编号\" align=\"center\" prop=\"jobId\" />\n      <el-table-column label=\"处理器的名字\" align=\"center\" prop=\"handlerName\" />\n      <el-table-column label=\"处理器的参数\" align=\"center\" prop=\"handlerParam\" />\n      <el-table-column label=\"第几次执行\" align=\"center\" prop=\"executeIndex\" />\n      <el-table-column label=\"执行时间\" align=\"center\" width=\"170s\">\n        <template #default=\"scope\">\n          <span>{{ formatDate(scope.row.beginTime) + ' ~ ' + formatDate(scope.row.endTime) }}</span>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"执行时长\" align=\"center\" prop=\"duration\">\n        <template #default=\"scope\">\n          <span>{{ scope.row.duration + ' 毫秒' }}</span>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"任务状态\" align=\"center\" prop=\"status\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.INFRA_JOB_LOG_STATUS\" :value=\"scope.row.status\" />\n        </template>\n      </el-table-column>\n      <el-table-column label=\"操作\" align=\"center\">\n        <template #default=\"scope\">\n          <el-button\n            type=\"primary\"\n            link\n            @click=\"openDetail(scope.row.id)\"\n            v-hasPermi=\"['infra:job:query']\"\n          >\n            详细\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页组件 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：查看 -->\n  <JobLogDetail ref=\"detailRef\" />\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getIntDictOptions } from '@/utils/dict'\nimport { formatDate } from '@/utils/formatTime'\nimport download from '@/utils/download'\nimport JobLogDetail from './JobLogDetail.vue'\nimport * as JobLogApi from '@/api/infra/jobLog'\n\ndefineOptions({ name: 'InfraJobLog' })\n\nconst message = useMessage() // 消息弹窗\nconst { query } = useRoute() // 查询参数\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  jobId: query.id,\n  handlerName: undefined,\n  beginTime: undefined,\n  endTime: undefined,\n  status: undefined\n})\nconst queryFormRef = ref() // 搜索的表单\nconst exportLoading = ref(false) // 导出的加载中\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await JobLogApi.getJobLogPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 查看操作 */\nconst detailRef = ref()\nconst openDetail = (rowId?: number) => {\n  detailRef.value.open(rowId)\n}\n\n/** 导出按钮操作 */\nconst handleExport = async () => {\n  try {\n    // 导出的二次确认\n    await message.exportConfirm()\n    // 发起导出\n    exportLoading.value = true\n    const data = await JobLogApi.exportJobLog(queryParams)\n    download.excel(data, '定时任务执行日志.xls')\n  } catch {\n  } finally {\n    exportLoading.value = false\n  }\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/redis/index.vue",
    "content": "<template>\n  <el-scrollbar height=\"calc(100vh - 88px - 40px - 50px)\">\n    <el-row>\n      <!-- 基本信息 -->\n      <el-col :span=\"24\" class=\"card-box\" shadow=\"hover\">\n        <el-card>\n          <el-descriptions title=\"基本信息\" :column=\"6\" border>\n            <el-descriptions-item label=\"Redis版本 :\">\n              {{ cache?.info?.redis_version }}\n            </el-descriptions-item>\n            <el-descriptions-item label=\"运行模式 :\">\n              {{ cache?.info?.redis_mode == 'standalone' ? '单机' : '集群' }}\n            </el-descriptions-item>\n            <el-descriptions-item label=\"端口 :\">\n              {{ cache?.info?.tcp_port }}\n            </el-descriptions-item>\n            <el-descriptions-item label=\"客户端数 :\">\n              {{ cache?.info?.connected_clients }}\n            </el-descriptions-item>\n            <el-descriptions-item label=\"运行时间(天) :\">\n              {{ cache?.info?.uptime_in_days }}\n            </el-descriptions-item>\n            <el-descriptions-item label=\"使用内存 :\">\n              {{ cache?.info?.used_memory_human }}\n            </el-descriptions-item>\n            <el-descriptions-item label=\"使用CPU :\">\n              {{ cache?.info ? parseFloat(cache?.info?.used_cpu_user_children).toFixed(2) : '' }}\n            </el-descriptions-item>\n            <el-descriptions-item label=\"内存配置 :\">\n              {{ cache?.info?.maxmemory_human }}\n            </el-descriptions-item>\n            <el-descriptions-item label=\"AOF是否开启 :\">\n              {{ cache?.info?.aof_enabled == '0' ? '否' : '是' }}\n            </el-descriptions-item>\n            <el-descriptions-item label=\"RDB是否成功 :\">\n              {{ cache?.info?.rdb_last_bgsave_status }}\n            </el-descriptions-item>\n            <el-descriptions-item label=\"Key数量 :\">\n              {{ cache?.dbSize }}\n            </el-descriptions-item>\n            <el-descriptions-item label=\"网络入口/出口 :\">\n              {{ cache?.info?.instantaneous_input_kbps }}kps/\n              {{ cache?.info?.instantaneous_output_kbps }}kps\n            </el-descriptions-item>\n          </el-descriptions>\n        </el-card>\n      </el-col>\n      <!-- 命令统计 -->\n      <el-col :span=\"12\" class=\"mt-3\">\n        <el-card :gutter=\"12\" shadow=\"hover\">\n          <Echart :options=\"commandStatsRefChika\" :height=\"420\" />\n        </el-card>\n      </el-col>\n      <!-- 内存使用量统计 -->\n      <el-col :span=\"12\" class=\"mt-3\">\n        <el-card class=\"ml-3\" :gutter=\"12\" shadow=\"hover\">\n          <Echart :options=\"usedmemoryEchartChika\" :height=\"420\" />\n        </el-card>\n      </el-col>\n    </el-row>\n  </el-scrollbar>\n</template>\n<script lang=\"ts\" setup>\nimport * as RedisApi from '@/api/infra/redis'\nimport { RedisMonitorInfoVO } from '@/api/infra/redis/types'\nconst cache = ref<RedisMonitorInfoVO>()\n\n// 基本信息\nconst readRedisInfo = async () => {\n  const data = await RedisApi.getCache()\n  cache.value = data\n}\n\n// 内存使用情况\nconst usedmemoryEchartChika = reactive<any>({\n  title: {\n    // 仪表盘标题。\n    text: '内存使用情况',\n    left: 'center',\n    show: true, // 是否显示标题,默认 true。\n    offsetCenter: [0, '20%'], //相对于仪表盘中心的偏移位置，数组第一项是水平方向的偏移，第二项是垂直方向的偏移。可以是绝对的数值，也可以是相对于仪表盘半径的百分比。\n    color: 'yellow', // 文字的颜色,默认 #333。\n    fontSize: 20 // 文字的字体大小,默认 15。\n  },\n  toolbox: {\n    show: false,\n    feature: {\n      restore: { show: true },\n      saveAsImage: { show: true }\n    }\n  },\n  series: [\n    {\n      name: '峰值',\n      type: 'gauge',\n      min: 0,\n      max: 50,\n      splitNumber: 10,\n      //这是指针的颜色\n      color: '#F5C74E',\n      radius: '85%',\n      center: ['50%', '50%'],\n      startAngle: 225,\n      endAngle: -45,\n      axisLine: {\n        // 坐标轴线\n        lineStyle: {\n          // 属性lineStyle控制线条样式\n          color: [\n            [0.2, '#7FFF00'],\n            [0.8, '#00FFFF'],\n            [1, '#FF0000']\n          ],\n          //width: 6 外框的大小（环的宽度）\n          width: 10\n        }\n      },\n      axisTick: {\n        // 坐标轴小标记\n        //里面的线长是5（短线）\n        length: 5, // 属性length控制线长\n        lineStyle: {\n          // 属性lineStyle控制线条样式\n          color: '#76D9D7'\n        }\n      },\n      splitLine: {\n        // 分隔线\n        length: 20, // 属性length控制线长\n        lineStyle: {\n          // 属性lineStyle（详见lineStyle）控制线条样式\n          color: '#76D9D7'\n        }\n      },\n      axisLabel: {\n        color: '#76D9D7',\n        distance: 15,\n        fontSize: 15\n      },\n      pointer: {\n        // 指针的大小\n        width: 7,\n        show: true\n      },\n      detail: {\n        textStyle: {\n          fontWeight: 'normal',\n          // 里面文字下的数值大小（50）\n          fontSize: 15,\n          color: '#FFFFFF'\n        },\n        valueAnimation: true\n      },\n      progress: {\n        show: true\n      }\n    }\n  ]\n})\n\n// 指令使用情况\nconst commandStatsRefChika = reactive({\n  title: {\n    text: '命令统计',\n    left: 'center'\n  },\n  tooltip: {\n    trigger: 'item',\n    formatter: '{a} <br/>{b} : {c} ({d}%)'\n  },\n  legend: {\n    type: 'scroll',\n    orient: 'vertical',\n    right: 30,\n    top: 10,\n    bottom: 20,\n    data: [] as any[],\n    textStyle: {\n      color: '#a1a1a1'\n    }\n  },\n  series: [\n    {\n      name: '命令',\n      type: 'pie',\n      radius: [20, 120],\n      center: ['40%', '60%'],\n      data: [] as any[],\n      roseType: 'radius',\n      label: {\n        show: true\n      },\n      emphasis: {\n        label: {\n          show: true\n        },\n        itemStyle: {\n          shadowBlur: 10,\n          shadowOffsetX: 0,\n          shadowColor: 'rgba(0, 0, 0, 0.5)'\n        }\n      }\n    }\n  ]\n})\n\n/** 加载数据 */\nconst getSummary = () => {\n  // 初始化命令图表\n  initCommandStatsChart()\n  usedMemoryInstance()\n}\n\n/** 命令使用情况 */\nconst initCommandStatsChart = async () => {\n  usedmemoryEchartChika.series[0].data = []\n  // 发起请求\n  try {\n    const data = await RedisApi.getCache()\n    cache.value = data\n    // 处理数据\n    const commandStats = [] as any[]\n    const nameList = [] as string[]\n    data.commandStats.forEach((row) => {\n      commandStats.push({\n        name: row.command,\n        value: row.calls\n      })\n      nameList.push(row.command)\n    })\n    commandStatsRefChika.legend.data = nameList\n    commandStatsRefChika.series[0].data = commandStats\n  } catch {}\n}\nconst usedMemoryInstance = async () => {\n  try {\n    const data = await RedisApi.getCache()\n    cache.value = data\n    // 仪表盘详情，用于显示数据。\n    usedmemoryEchartChika.series[0].detail = {\n      show: true, // 是否显示详情,默认 true。\n      offsetCenter: [0, '50%'], // 相对于仪表盘中心的偏移位置，数组第一项是水平方向的偏移，第二项是垂直方向的偏移。可以是绝对的数值，也可以是相对于仪表盘半径的百分比。\n      color: 'auto', // 文字的颜色,默认 auto。\n      fontSize: 30, // 文字的字体大小,默认 15。\n      formatter: cache.value!.info.used_memory_human // 格式化函数或者字符串\n    }\n\n    usedmemoryEchartChika.series[0].data[0] = {\n      value: cache.value!.info.used_memory_human,\n      name: '内存消耗'\n    }\n    console.log(cache.value!.info)\n    usedmemoryEchartChika.tooltip = {\n      formatter: '{b} <br/>{a} : ' + cache.value!.info.used_memory_human\n    }\n  } catch {}\n}\n\n/** 初始化 **/\nonMounted(() => {\n  // 读取 redis 信息\n  readRedisInfo()\n  // 加载数据\n  getSummary()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/server/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <IFrame v-if=\"!loading\" v-loading=\"loading\" :src=\"src\" />\n  </ContentWrap>\n</template>\n<script lang=\"ts\" setup>\nimport * as ConfigApi from '@/api/infra/config'\n\ndefineOptions({ name: 'InfraAdminServer' })\n\nconst loading = ref(true) // 是否加载中\nconst src = ref(import.meta.env.VITE_BASE_URL + '/admin/applications')\n\n/** 初始化 */\nonMounted(async () => {\n  try {\n    const data = await ConfigApi.getConfigKey('url.spring-boot-admin')\n    if (data && data.length > 0) {\n      src.value = data\n    }\n  } finally {\n    loading.value = false\n  }\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/skywalking/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <IFrame v-if=\"!loading\" v-loading=\"loading\" :src=\"src\" />\n  </ContentWrap>\n</template>\n<script lang=\"ts\" setup>\nimport * as ConfigApi from '@/api/infra/config'\n\ndefineOptions({ name: 'InfraSkyWalking' })\n\nconst loading = ref(true) // 是否加载中\nconst src = ref('http://skywalking.shop.yixiang.co')\n\n/** 初始化 */\nonMounted(async () => {\n  try {\n    const data = await ConfigApi.getConfigKey('url.skywalking')\n    if (data && data.length > 0) {\n      src.value = data\n    }\n  } finally {\n    loading.value = false\n  }\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/swagger/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <IFrame :src=\"src\" />\n  </ContentWrap>\n</template>\n<script lang=\"ts\" setup>\nimport * as ConfigApi from '@/api/infra/config'\n\ndefineOptions({ name: 'InfraSwagger' })\n\nconst loading = ref(true) // 是否加载中\nconst src = ref(import.meta.env.VITE_BASE_URL + '/doc.html') // Knife4j UI\n// const src = ref(import.meta.env.VITE_BASE_URL + '/swagger-ui') // Swagger UI\n\n/** 初始化 */\nonMounted(async () => {\n  try {\n    const data = await ConfigApi.getConfigKey('url.swagger')\n    if (data && data.length > 0) {\n      src.value = data\n    }\n  } finally {\n    loading.value = false\n  }\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/infra/webSocket/index.vue",
    "content": "<template>\n  <div class=\"flex\">\n    <!-- 左侧：建立连接、发送消息 -->\n    <el-card :gutter=\"12\" class=\"w-1/2\" shadow=\"always\">\n      <template #header>\n        <div class=\"card-header\">\n          <span>连接</span>\n        </div>\n      </template>\n      <div class=\"flex items-center\">\n        <span class=\"mr-4 text-lg font-medium\"> 连接状态: </span>\n        <el-tag :color=\"getTagColor\">{{ status }}</el-tag>\n      </div>\n      <hr class=\"my-4\" />\n      <div class=\"flex\">\n        <el-input v-model=\"server\" disabled>\n          <template #prepend>服务地址</template>\n        </el-input>\n        <el-button :type=\"getIsOpen ? 'danger' : 'primary'\" @click=\"toggleConnectStatus\">\n          {{ getIsOpen ? '关闭连接' : '开启连接' }}\n        </el-button>\n      </div>\n      <p class=\"mt-4 text-lg font-medium\">消息输入框</p>\n      <hr class=\"my-4\" />\n      <el-input\n        v-model=\"sendText\"\n        :autosize=\"{ minRows: 2, maxRows: 4 }\"\n        :disabled=\"!getIsOpen\"\n        clearable\n        type=\"textarea\"\n        placeholder=\"请输入你要发送的消息\"\n      />\n      <el-select v-model=\"sendUserId\" class=\"mt-4\" placeholder=\"请选择发送人\">\n        <el-option key=\"\" label=\"所有人\" value=\"\" />\n        <el-option\n          v-for=\"user in userList\"\n          :key=\"user.id\"\n          :label=\"user.nickname\"\n          :value=\"user.id\"\n        />\n      </el-select>\n      <el-button :disabled=\"!getIsOpen\" block class=\"ml-2 mt-4\" type=\"primary\" @click=\"handlerSend\">\n        发送\n      </el-button>\n    </el-card>\n    <!-- 右侧：消息记录 -->\n    <el-card :gutter=\"12\" class=\"w-1/2\" shadow=\"always\">\n      <template #header>\n        <div class=\"card-header\">\n          <span>消息记录</span>\n        </div>\n      </template>\n      <div class=\"max-h-80 overflow-auto\">\n        <ul>\n          <li v-for=\"msg in messageReverseList\" :key=\"msg.time\" class=\"mt-2\">\n            <div class=\"flex items-center\">\n              <span class=\"text-primary mr-2 font-medium\">收到消息:</span>\n              <span>{{ formatDate(msg.time) }}</span>\n            </div>\n            <div>\n              {{ msg.text }}\n            </div>\n          </li>\n        </ul>\n      </div>\n    </el-card>\n  </div>\n</template>\n<script lang=\"ts\" setup>\nimport { formatDate } from '@/utils/formatTime'\nimport { useWebSocket } from '@vueuse/core'\nimport { getAccessToken } from '@/utils/auth'\nimport * as UserApi from '@/api/system/user'\n\ndefineOptions({ name: 'InfraWebSocket' })\n\nconst message = useMessage() // 消息弹窗\n\nconst server = ref(\n  (import.meta.env.VITE_BASE_URL + '/infra/ws').replace('http', 'ws') + '?token=' + getAccessToken()\n) // WebSocket 服务地址\nconst getIsOpen = computed(() => status.value === 'OPEN') // WebSocket 连接是否打开\nconst getTagColor = computed(() => (getIsOpen.value ? 'success' : 'red')) // WebSocket 连接的展示颜色\n\n/** 发起 WebSocket 连接 */\nconst { status, data, send, close, open } = useWebSocket(server.value, {\n  autoReconnect: false,\n  heartbeat: true\n})\n\n/** 监听接收到的数据 */\nconst messageList = ref([] as { time: number; text: string }[]) // 消息列表\nconst messageReverseList = computed(() => messageList.value.slice().reverse())\nwatchEffect(() => {\n  if (!data.value) {\n    return\n  }\n  try {\n    // 1. 收到心跳\n    if (data.value === 'pong') {\n      // state.recordList.push({\n      //   text: '【心跳】',\n      //   time: new Date().getTime()\n      // })\n      return\n    }\n\n    // 2.1 解析 type 消息类型\n    const jsonMessage = JSON.parse(data.value)\n    const type = jsonMessage.type\n    const content = JSON.parse(jsonMessage.content)\n    if (!type) {\n      message.error('未知的消息类型：' + data.value)\n      return\n    }\n    // 2.2 消息类型：demo-message-receive\n    if (type === 'demo-message-receive') {\n      const single = content.single\n      if (single) {\n        messageList.value.push({\n          text: `【单发】用户编号(${content.fromUserId})：${content.text}`,\n          time: new Date().getTime()\n        })\n      } else {\n        messageList.value.push({\n          text: `【群发】用户编号(${content.fromUserId})：${content.text}`,\n          time: new Date().getTime()\n        })\n      }\n      return\n    }\n    // 2.3 消息类型：notice-push\n    if (type === 'notice-push') {\n      messageList.value.push({\n        text: `【系统通知】：${content.title}`,\n        time: new Date().getTime()\n      })\n      return\n    }\n    message.error('未处理消息：' + data.value)\n  } catch (error) {\n    message.error('处理消息发生异常：' + data.value)\n    console.error(error)\n  }\n})\n\n/** 发送消息 */\nconst sendText = ref('') // 发送内容\nconst sendUserId = ref('') // 发送人\nconst handlerSend = () => {\n  // 1.1 先 JSON 化 message 消息内容\n  const messageContent = JSON.stringify({\n    text: sendText.value,\n    toUserId: sendUserId.value\n  })\n  // 1.2 再 JSON 化整个消息\n  const jsonMessage = JSON.stringify({\n    type: 'demo-message-send',\n    content: messageContent\n  })\n  // 2. 最后发送消息\n  send(jsonMessage)\n  sendText.value = ''\n}\n\n/** 切换 websocket 连接状态 */\nconst toggleConnectStatus = () => {\n  if (getIsOpen.value) {\n    close()\n  } else {\n    open()\n  }\n}\n\n/** 初始化 **/\nconst userList = ref<any[]>([]) // 用户列表\nonMounted(async () => {\n  // 获取用户列表\n  userList.value = await UserApi.getSimpleUserList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mall/coupon/Form.vue",
    "content": "<template>\n  <Dialog :title=\"dialogTitle\" v-model=\"dialogVisible\">\n    <el-form\n      ref=\"formRef\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"120px\"\n      v-loading=\"formLoading\"\n    >\n      <el-form-item label=\"展示店铺\" prop=\"shopId\">\n        <el-select\n          v-model=\"formData.shopId\"\n          placeholder=\"选择店铺\"\n        >\n          <el-option\n            v-for=\"item in shopList\"\n            :key=\"item.id\"\n            :label=\"item.name\"\n            :value=\"item.id\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"可用类型\" prop=\"type\">\n        <el-radio-group v-model=\"formData.type\">\n          <el-radio :label=\"0\">通用</el-radio>\n          <el-radio :label=\"1\">自取</el-radio>\n          <el-radio :label=\"2\">外卖</el-radio>\n        </el-radio-group>\n      </el-form-item>\n      <el-form-item label=\"兑换码\" prop=\"exchangeCode\">\n        <el-input v-model=\"formData.exchangeCode\" placeholder=\"请输入兑换码\" />\n      </el-form-item>\n      <el-form-item label=\"图片\" prop=\"image\">\n          <Materials v-model=\"formData.image\" num=\"1\" type=\"image\" />\n      </el-form-item>\n      <el-form-item label=\"优惠券名称\" prop=\"title\">\n        <el-input v-model=\"formData.title\" placeholder=\"请输入优惠券名称\" />\n      </el-form-item>\n      <el-form-item label=\"消费多少可用\" prop=\"least\">\n        <el-input v-model=\"formData.least\" placeholder=\"请输入消费多少可用\" />\n      </el-form-item>\n      <el-form-item label=\"优惠券金额\" prop=\"value\">\n        <el-input v-model=\"formData.value\" placeholder=\"请输入优惠券金额\" />\n      </el-form-item>\n      <el-form-item label=\"开始时间\" prop=\"startTime\">\n        <el-date-picker\n          v-model=\"formData.startTime\"\n          type=\"date\"\n          value-format=\"x\"\n          placeholder=\"选择开始时间\"\n        />\n      </el-form-item>\n      <el-form-item label=\"结束时间\" prop=\"endTime\">\n        <el-date-picker\n          v-model=\"formData.endTime\"\n          type=\"date\"\n          value-format=\"x\"\n          placeholder=\"选择结束时间\"\n        />\n      </el-form-item>\n      <el-form-item label=\"发行数量\" prop=\"distribute\">\n        <el-input v-model=\"formData.distribute\" placeholder=\"请输入发行数量\" />\n      </el-form-item>\n      <el-form-item label=\"所需积分\" prop=\"score\">\n        <el-input v-model=\"formData.score\" placeholder=\"请输入所需积分\" />\n      </el-form-item>\n      <el-form-item label=\"限领数量\" prop=\"limit\">\n        <el-input v-model=\"formData.limit\" placeholder=\"请输入限领数量\" />\n      </el-form-item>\n      <el-form-item label=\"使用说明\" prop=\"instructions\">\n        <el-input type=\"textarea\" rows=\"5\"  v-model=\"formData.instructions\" placeholder=\"请输入使用说明\" />\n      </el-form-item>\n      <el-form-item label=\"是否上架\" prop=\"isSwitch\">\n        <el-radio-group v-model=\"formData.isSwitch\">\n          <el-radio :label=\"1\">是</el-radio>\n          <el-radio :label=\"0\">否</el-radio>\n        </el-radio-group>\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button @click=\"submitForm\" type=\"primary\" :disabled=\"formLoading\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script setup lang=\"ts\">\nimport * as Api from '@/api/mall/coupon/'\nimport * as ShopApi from '@/api/mall/store/shop'\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  shopId: undefined,\n  shopName: undefined,\n  title: undefined,\n  isSwitch: undefined,\n  least: undefined,\n  value: undefined,\n  startTime: undefined,\n  endTime: undefined,\n  weigh: undefined,\n  type: undefined,\n  exchangeCode: undefined,\n  receive: undefined,\n  distribute: undefined,\n  score: undefined,\n  instructions: undefined,\n  image: undefined,\n  limit: undefined\n})\nconst shopList = ref([])\nconst formRules = reactive({\n  shopId: [{ required: true, message: '店铺id,0表示通用不能为空', trigger: 'blur' }],\n  title: [{ required: true, message: '优惠券名称不能为空', trigger: 'blur' }],\n  least: [{ required: true, message: '消费多少可用不能为空', trigger: 'blur' }],\n  value: [{ required: true, message: '优惠券金额不能为空', trigger: 'blur' }],\n  startTime: [{ required: true, message: '开始时间不能为空', trigger: 'blur' }],\n  endTime: [{ required: true, message: '结束时间不能为空', trigger: 'blur' }],\n  type: [{ required: true, message: '可用类型不能为空', trigger: 'blur' }],\n  distribute: [{ required: true, message: '发行数量不能为空', trigger: 'blur' }],\n  limit: [{ required: true, message: '限领数量不能为空', trigger: 'blur' }],\n  instructions: [{ required: true, message: '使用说明不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  getList()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await Api.getCoupon(id)\n      formData.value.shopId = Number(formData.value.shopId)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as unknown as Api.VO\n    if (formType.value === 'create') {\n      await Api.createCoupon(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await Api.updateCoupon(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\nconst getList = async () => {\n  try {\n    const data = await ShopApi.getShopList()\n    shopList.value = data\n\n  } finally {\n    \n  }\n}\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    shopId: undefined,\n    shopName: undefined,\n    title: undefined,\n    isSwitch: 1,\n    least: undefined,\n    value: undefined,\n    startTime: undefined,\n    endTime: undefined,\n    weigh: undefined,\n    type: 0,\n    exchangeCode: undefined,\n    receive: undefined,\n    distribute: undefined,\n    score: undefined,\n    instructions: undefined,\n    image: undefined,\n    limit: undefined\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mall/coupon/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <!-- 搜索工作栏 -->\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"100px\"\n    >\n      <el-form-item label=\"店铺名称\" prop=\"shopName\">\n        <el-input\n          v-model=\"queryParams.shopName\"\n          placeholder=\"请输入店铺名称\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"优惠券名称\" prop=\"title\">\n        <el-input\n          v-model=\"queryParams.title\"\n          placeholder=\"请输入优惠券名称\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n        <el-button type=\"primary\" @click=\"openForm('create')\" v-hasPermi=\"['coupon::create']\">\n          <Icon icon=\"ep:plus\" class=\"mr-5px\" /> 新增\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column label=\"id\" align=\"center\" prop=\"id\" />\n      <el-table-column label=\"可用店铺\" align=\"center\" prop=\"shopName\"  width=\"150\" />\n      <el-table-column label=\"优惠券名称\" align=\"center\" prop=\"title\"  width=\"150\" />\n      <el-table-column label=\"是否上架\" align=\"center\" prop=\"isSwitch\">\n        <template #default=\"scope\">\n          <span v-if=\"scope.row.isSwitch == 1\">上架</span>\n          <span v-else>下架</span>\n         </template>\n      </el-table-column>\n      <el-table-column label=\"消费多少可用\" align=\"center\" prop=\"least\"  width=\"150\" />\n      <el-table-column label=\"优惠券金额\" align=\"center\" prop=\"value\"  width=\"150\"/>\n      <el-table-column label=\"可用类型\" align=\"center\" prop=\"type\">\n        <template #default=\"scope\">\n          <span v-if=\"scope.row.type == 1\">自取</span>\n          <span v-else-if=\"scope.row.type == 2\">外卖</span>\n          <span v-else>通用</span>\n         </template>\n      </el-table-column>\n      <el-table-column label=\"兑换码\" align=\"center\" prop=\"exchangeCode\" />\n      <el-table-column label=\"已领\" align=\"center\" prop=\"receive\" />\n      <el-table-column label=\"发行数量\" align=\"center\" prop=\"distribute\" />\n      <el-table-column label=\"限领数量\" align=\"center\" prop=\"limit\" />\n      <el-table-column\n        label=\"开始时间\"\n        align=\"center\"\n        prop=\"startTime\"\n        :formatter=\"dateFormatter\"\n        width=\"170\"\n      />\n      <el-table-column\n        label=\"结束时间\"\n        align=\"center\"\n        prop=\"endTime\"\n        :formatter=\"dateFormatter\"\n        width=\"170\"\n      />\n      <el-table-column\n        label=\"创建时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        :formatter=\"dateFormatter\"\n        width=\"170\"\n      />\n\n      <el-table-column label=\"操作\" align=\"center\" fixed=\"right\" width=\"150\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n            v-hasPermi=\"['coupon::update']\"\n          >\n            编辑\n          </el-button>\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n            v-hasPermi=\"['coupon::delete']\"\n          >\n            删除\n          </el-button>\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openForm('couponRecord', scope.row.id)\"\n            v-hasPermi=\"['coupon::delete']\"\n          >\n            领取记录\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <Form ref=\"formRef\" @success=\"getList\" />\n  <OrderRecord ref=\"formRef5\" />\n</template>\n\n<script setup lang=\"ts\" name=\"Coupon\">\nimport { dateFormatter } from '@/utils/formatTime'\nimport download from '@/utils/download'\nimport * as Api from '@/api/mall/coupon/'\nimport Form from './Form.vue'\nimport OrderRecord from './user/OrderRecord.vue'\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  shopId: null,\n  shopName: null,\n  title: null,\n  switch: null,\n  least: null,\n  value: null,\n  startTime: [],\n  endtIme: [],\n  createTime: [],\n  weigh: null,\n  type: null,\n  exchangeCode: null,\n  receive: null,\n  distribute: null,\n  score: null,\n  instructions: null,\n  image: null,\n  limit: null\n})\nconst queryFormRef = ref() // 搜索的表单\nconst exportLoading = ref(false) // 导出的加载中\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await Api.getCouponPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst formRef5 = ref()\nconst openForm = (type: string, id?: number) => {\n  if (type == 'couponRecord') {\n    formRef5.value.open(type, id)\n  }else{\n    formRef.value.open(type, id)\n  }\n  \n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await Api.deleteCoupon(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 导出按钮操作 */\nconst handleExport = async () => {\n  try {\n    // 导出的二次确认\n    await message.exportConfirm()\n    // 发起导出\n    exportLoading.value = true\n    const data = await Api.exportCoupon(queryParams)\n    download.excel(data, '优惠券.xls')\n  } catch {\n  } finally {\n    exportLoading.value = false\n  }\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mall/coupon/user/OrderRecord.vue",
    "content": "<template>\n  <el-drawer v-model=\"drawer\" :title=\"dialogTitle\" size=\"50%\">\n    <el-table :data=\"tableData\" style=\"width: 100%\">\n      <el-table-column label=\"id\" align=\"center\" prop=\"id\" />\n      <el-table-column label=\"店铺名称\" align=\"center\" prop=\"shopName\" width=\"150\" />\n      <el-table-column label=\"优惠券名称\" align=\"center\" prop=\"title\" width=\"150\" />\n      <el-table-column label=\"消费多少可用\" align=\"center\" prop=\"least\" width=\"150\" />\n      <el-table-column label=\"优惠券金额\" align=\"center\" prop=\"value\" width=\"150\" />\n      <el-table-column label=\"可用类型\" align=\"center\" prop=\"type\">\n        <template #default=\"scope\">\n          <span v-if=\"scope.row.type == 1\">自取</span>\n          <span v-else-if=\"scope.row.type == 2\">外卖</span>\n          <span v-else>通用</span>\n         </template>\n      </el-table-column>\n      <el-table-column label=\"用户昵称\" align=\"center\" prop=\"userId\" />\n      <el-table-column label=\"是否使用\" align=\"center\" prop=\"status\">\n        <template #default=\"scope\">\n          <span v-if=\"scope.row.type == 1\">已使用</span>\n          <span v-if=\"scope.row.type == 0\">未使用</span>\n         </template>\n      </el-table-column>\n      <el-table-column\n        label=\"领取时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        :formatter=\"dateFormatter\"\n        width=\"170\"\n      />\n      <el-table-column label=\"操作\" align=\"center\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n            v-hasPermi=\"['coupon:user:delete']\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n  </el-drawer>\n\n</template>\n<script setup lang=\"ts\">\nimport * as UserApi from '@/api/mall/coupon/user'\nimport { dateFormatter } from '@/utils/formatTime'\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\nconst dialogTitle = ref('') // 弹窗的标题\nconst drawer = ref(false)\nconst tableData = ref([])\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  drawer.value = true\n  dialogTitle.value = t('action.' + type)\n  console.log(id)\n  getList(id)\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await UserApi.deleteUser(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n   getList(id)\n  } catch {}\n}\nconst getList = async(id) => {\n  tableData.value = await UserApi.getUserList(id)\n}\n</script>\n<style scoped>\n</style>"
  },
  {
    "path": "yshop-drink-vue3/src/views/mall/member/user/UserDetail.vue",
    "content": "<template>\n <el-drawer v-model=\"drawer\" :title=\"dialogTitle\" size=\"50%\">\n    <div>\n      <el-tabs v-model=\"activeName\" class=\"demo-tabs\" @tab-click=\"handleClick\">\n        <el-tab-pane label=\"用户信息\" name=\"first\">\n           <el-descriptions title=\"基本信息\" :column=\"2\">\n            <el-descriptions-item label=\"用户头像\"><el-image style=\"width: 50px; height: 50px\" :src=\"DetailData.avatar\" /></el-descriptions-item>\n            <el-descriptions-item label=\"用户昵称\">{{ DetailData.nickname }}</el-descriptions-item>\n            <el-descriptions-item label=\"余额\">{{ DetailData.nowMoney }}</el-descriptions-item>\n            <el-descriptions-item label=\"生日\">{{ DetailData.birthday }}</el-descriptions-item>\n            <el-descriptions-item label=\"手机号\">{{ DetailData.mobile }}</el-descriptions-item>\n            <el-descriptions-item label=\"身份证号码\">{{ DetailData.cardId }}</el-descriptions-item>\n          </el-descriptions>\n          <el-descriptions title=\"用户概况\" :column=\"2\">\n            <el-descriptions-item label=\"绑定的会员卡\">{{ DetailData.cardName ? DetailData.cardName : '无' }}</el-descriptions-item>\n            <el-descriptions-item label=\"积分\">{{ DetailData.integral }}</el-descriptions-item>\n            <el-descriptions-item label=\"佣金金额\">{{ DetailData.brokeragePrice }}</el-descriptions-item>\n            <el-descriptions-item label=\"连续签到天数\">{{ DetailData.signNum }}</el-descriptions-item>\n            <el-descriptions-item label=\"登录ip\">{{ DetailData.loginIp }}</el-descriptions-item>\n            <el-descriptions-item label=\"等级\">{{ DetailData.level }}</el-descriptions-item>\n            <el-descriptions-item label=\"推广id\">{{ DetailData.spreadUid }}</el-descriptions-item>\n            <el-descriptions-item label=\"购买次数\">{{ DetailData.payCount }}</el-descriptions-item>\n            <el-descriptions-item label=\"下级人数\">{{ DetailData.spreadCount }}</el-descriptions-item>\n            <el-descriptions-item label=\"登陆类型\">\n              <span v-if=\"DetailData.loginType == 'routine'\">小程序</span>\n              <span v-else-if=\"DetailData.loginType == 'wechat'\">公众号</span>\n              <span v-else-if=\"DetailData.loginType == 'h5'\">H5</span>\n              <span v-else>未知</span>\n            </el-descriptions-item>\n            <el-descriptions-item label=\"注册时间\">{{ formatDate(DetailData.createTime) }}</el-descriptions-item>\n          </el-descriptions>\n        </el-tab-pane>\n        <el-tab-pane label=\"消费记录\" name=\"second\">\n             <el-table  :data=\"list\">\n              <el-table-column label=\"支出/获得\" align=\"center\" prop=\"pm\">\n                  <template #default=\"scope\">\n                    <span v-if=\"scope.row.pm == 0\">支出</span>\n                    <span v-else-if=\"scope.row.category == 1\">获得</span>\n                    <span v-else>未知</span>\n                  </template>\n              </el-table-column>\n              <el-table-column label=\"账单标题\" align=\"center\" prop=\"title\" />\n              <el-table-column label=\"明细种类\" align=\"center\" prop=\"category\">\n                 <template #default=\"scope\">\n                    <span v-if=\"scope.row.category == 'now_money'\">余额</span>\n                    <span v-else-if=\"scope.row.category == 'integral'\">积分</span>\n                    <span v-else>未知</span>\n                  </template>\n              </el-table-column>\n              <el-table-column label=\"明细类型\" align=\"center\" prop=\"type\">\n                <template #default=\"scope\">\n                    <span v-if=\"scope.row.type == 'recharge'\">充值</span>\n                    <span v-else-if=\"scope.row.type == 'brokerage'\">返佣</span>\n                    <span v-else-if=\"scope.row.type == 'pay_product'\">消费</span>\n                    <span v-else-if=\"scope.row.type == 'extract'\">提现</span>\n                    <span v-else-if=\"scope.row.type == 'pay_product_refund'\">退款</span>\n                    <span v-else-if=\"scope.row.type == 'system_add'\">系统添加</span>\n                    <span v-else-if=\"scope.row.type == 'system_sub'\">系统减少</span>\n                    <span v-else-if=\"scope.row.type == 'deduction'\">减去</span>\n                    <span v-else-if=\"scope.row.type == 'gain'\">奖励</span>\n                    <span v-else-if=\"scope.row.type == 'sign'\">签到</span>\n                    <span v-else-if=\"scope.row.type == 'vip_card'\">会员卡</span>\n                    <span v-else>未知</span>\n                  </template>\n              </el-table-column>\n              <el-table-column label=\"明细数字(元)\" align=\"center\" prop=\"number\">\n                    <template #default=\"scope\">\n                      <span v-if=\"scope.row.pm == 1\">+</span>\n                      <span v-else>-</span>\n                      <span>{{ scope.row.number }}</span>\n                    </template>\n              </el-table-column>\n              <el-table-column label=\"剩余(元)\" align=\"center\" prop=\"balance\" />\n              <el-table-column label=\"备注\" align=\"center\" prop=\"mark\" width=\"200\" />\n              <el-table-column\n                label=\"添加时间\"\n                align=\"center\"\n                prop=\"createTime\"\n                :formatter=\"dateFormatter\"\n                width=\"100\"\n              />\n            </el-table>\n            <!-- 分页 -->\n            <Pagination\n              :total=\"total\"\n              v-model:page=\"queryParams.pageNo\"\n              v-model:limit=\"queryParams.pageSize\"\n              @pagination=\"getList\"\n            />\n        </el-tab-pane>\n      </el-tabs>\n     \n    </div>\n  </el-drawer>\n</template>\n<script setup lang=\"ts\">\nimport * as UserApi from '@/api/member/user'\nimport { formatDate } from '@/utils/formatTime'\nimport type { TabsPaneContext } from 'element-plus'\nimport * as UserBillApi from '@/api/member/userBill'\nimport { dateFormatter } from '@/utils/formatTime'\n\nconst { t } = useI18n() // 国际化\n// const message = useMessage() // 消息弹窗\nconst dialogTitle = ref('') // 弹窗的标题\nconst drawer = ref(false)\nconst DetailData = ref({})\nconst activeName = ref('first')\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  uid: null,\n  linkId: null,\n  pm: null,\n  title: null,\n  category: null,\n  type: null,\n  number: null,\n  balance: null,\n  mark: null,\n  createTime: [],\n  status: null\n})\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  drawer.value = true\n  dialogTitle.value = t('action.' + type)\n  DetailData.value = await UserApi.getUser(id)\n  queryParams.uid = id\n  getList()\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\nconst handleClick = (tab: TabsPaneContext, event: Event) => {\n}\n/** 查询列表 */\nconst getList = async () => {\n  try {\n    const data = await UserBillApi.getUserBillPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n</script>\n<style scoped>\n</style>"
  },
  {
    "path": "yshop-drink-vue3/src/views/mall/member/user/UserForm.vue",
    "content": "<template>\n  <Dialog :title=\"dialogTitle\" v-model=\"dialogVisible\">\n    <el-form\n      ref=\"formRef\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"100px\"\n      v-loading=\"formLoading\"\n    >\n      <el-form-item label=\"用户账户\" prop=\"username\">\n        <el-input v-model=\"formData.username\" placeholder=\"请输入用户账户\" />\n      </el-form-item>\n      <el-form-item label=\"用户昵称\" prop=\"nickname\">\n        <el-input v-model=\"formData.nickname\" placeholder=\"请输入用户昵称\" />\n      </el-form-item>\n      <el-form-item label=\"手机号码\" prop=\"mobile\">\n        <el-input v-model=\"formData.mobile\" placeholder=\"请输入手机号码\" />\n      </el-form-item>\n      <el-form-item label=\"真实姓名\" prop=\"realName\">\n        <el-input v-model=\"formData.realName\" placeholder=\"请输入真实姓名\" />\n      </el-form-item>\n      <el-form-item label=\"生日\" prop=\"birthday\">\n        <el-input v-model=\"formData.birthday\" placeholder=\"请输入生日\" />\n      </el-form-item>\n      <el-form-item label=\"用户备注\" prop=\"mark\">\n        <el-input v-model=\"formData.mark\" placeholder=\"请输入用户备注\" />\n      </el-form-item>\n      <el-form-item label=\"用户余额\" prop=\"nowMoney\">\n        <el-input v-model=\"formData.nowMoney\" placeholder=\"请输入用户余额\" />\n      </el-form-item>\n      <el-form-item label=\"佣金金额\" prop=\"brokeragePrice\">\n        <el-input v-model=\"formData.brokeragePrice\" placeholder=\"请输入佣金金额\" />\n      </el-form-item>\n      <el-form-item label=\"用户积分\" prop=\"integral\">\n        <el-input v-model=\"formData.integral\" placeholder=\"请输入用户剩余积分\" />\n      </el-form-item>\n      <!-- <el-form-item label=\"等级\" prop=\"level\">\n        <el-input v-model=\"formData.level\" placeholder=\"请输入等级\" />\n      </el-form-item> -->\n      <!-- <el-form-item label=\"是否为推广员\" prop=\"isPromoter\">\n        <el-input v-model=\"formData.isPromoter\" placeholder=\"请输入是否为推广员\" />\n      </el-form-item> -->\n      <el-form-item label=\"详细地址\" prop=\"addres\">\n        <el-input v-model=\"formData.addres\" placeholder=\"请输入详细地址\" />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button @click=\"submitForm\" type=\"primary\" :disabled=\"formLoading\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script setup lang=\"ts\">\nimport * as UserApi from '@/api/member/user'\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  username: undefined,\n  password: undefined,\n  realName: undefined,\n  birthday: undefined,\n  cardId: undefined,\n  mark: undefined,\n  partnerId: undefined,\n  groupId: undefined,\n  nickname: undefined,\n  avatar: undefined,\n  mobile: undefined,\n  addIp: undefined,\n  lastIp: undefined,\n  nowMoney: undefined,\n  brokeragePrice: undefined,\n  integral: undefined,\n  signNum: undefined,\n  status: undefined,\n  level: undefined,\n  spreadUid: undefined,\n  spreadTime: undefined,\n  userType: undefined,\n  isPromoter: undefined,\n  payCount: undefined,\n  spreadCount: undefined,\n  addres: undefined,\n  adminid: undefined,\n  loginType: undefined,\n  wxProfile: undefined\n})\nconst formRules = reactive({\n  username: [{ required: true, message: '用户账户不能为空', trigger: 'blur' }],\n  nickname: [{ required: true, message: '用户昵称不能为空', trigger: 'blur' }],\n  mobile: [{ required: true, message: '佣手机号码不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await UserApi.getUser(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as unknown as UserApi.UserVO\n    if (formType.value === 'create') {\n      await UserApi.createUser(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await UserApi.updateUser(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    username: undefined,\n    password: undefined,\n    realName: undefined,\n    birthday: undefined,\n    cardId: undefined,\n    mark: undefined,\n    partnerId: undefined,\n    groupId: undefined,\n    nickname: undefined,\n    avatar: undefined,\n    mobile: undefined,\n    addIp: undefined,\n    lastIp: undefined,\n    nowMoney: undefined,\n    brokeragePrice: undefined,\n    integral: undefined,\n    signNum: undefined,\n    status: undefined,\n    level: undefined,\n    spreadUid: undefined,\n    spreadTime: undefined,\n    userType: undefined,\n    isPromoter: undefined,\n    payCount: undefined,\n    spreadCount: undefined,\n    addres: undefined,\n    adminid: undefined,\n    loginType: undefined,\n    wxProfile: undefined\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mall/member/user/index.vue",
    "content": "<template>\n  <ContentWrap>\n     <el-tabs v-model=\"activeName\"  @tab-click=\"handleClick\">\n      <el-tab-pane label=\"全部\" name=\"\"/>\n      <el-tab-pane label=\"微信小程序\" name=\"routine\"/>\n      <el-tab-pane label=\"公众号\" name=\"wechat\"/>\n      <el-tab-pane label=\"H5\" name=\"h5\"/>\n    </el-tabs>\n    <!-- 搜索工作栏 -->\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"用户昵称\" prop=\"nickname\">\n        <el-input\n          v-model=\"queryParams.nickname\"\n          placeholder=\"请输入用户昵称\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"手机号码\" prop=\"phone\">\n        <el-input\n          v-model=\"queryParams.phone\"\n          placeholder=\"请输入手机号码\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"添加时间\" prop=\"createTime\">\n        <el-date-picker\n          v-model=\"queryParams.createTime\"\n          value-format=\"YYYY-MM-DD HH:mm:ss\"\n          type=\"daterange\"\n          start-placeholder=\"开始日期\"\n          end-placeholder=\"结束日期\"\n          :default-time=\"[new Date('1 00:00:00'), new Date('1 23:59:59')]\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n        <el-button type=\"primary\" @click=\"openForm('create')\" v-hasPermi=\"['member:user:create']\">\n          <Icon icon=\"ep:plus\" class=\"mr-5px\" /> 新增\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column label=\"id\" align=\"center\" prop=\"id\" />\n      <el-table-column label=\"用户昵称\" align=\"center\" prop=\"nickname\" />\n      <el-table-column label=\"用户头像\" align=\"center\" prop=\"avatar\">\n        <template #default=\"scope\">\n        <el-image style=\"width: 50px; height: 50px\" :src=\"scope.row.avatar\" />\n        </template>\n      </el-table-column>\n      <el-table-column label=\"手机号码\" align=\"center\" prop=\"mobile\" />\n      <el-table-column\n        label=\"添加时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        :formatter=\"dateFormatter\"\n      />\n      <el-table-column label=\"用户余额\" align=\"center\" prop=\"nowMoney\" />\n      <el-table-column label=\"用户登陆类型\" align=\"center\" prop=\"loginType\">\n        <template #default=\"scope\">\n          <span v-if=\"scope.row.loginType == 'routine'\">小程序</span>\n          <span v-else-if=\"scope.row.loginType == 'wechat'\">公众号</span>\n          <span v-else-if=\"scope.row.loginType == 'h5'\">H5</span>\n          <span v-else>未知</span>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"操作\" align=\"center\">\n        <template #default=\"scope\">\n          <div class=\"flex justify-center items-center\">\n            <el-button\n              link\n              type=\"primary\"\n              @click=\"openForm('userDetail', scope.row.id)\"\n              v-hasPermi=\"['member:user:update']\"\n            >\n              详情\n            </el-button>\n            <el-dropdown>\n              <el-button type=\"primary\" link><Icon icon=\"ep:d-arrow-right\" /> 更多</el-button>\n              <template #dropdown>\n                <el-dropdown-menu>\n                  <el-dropdown-item @click=\"openForm('update', scope.row.id)\">编辑</el-dropdown-item>\n                  <el-dropdown-item @click=\"openForm('yue', scope.row.id)\">积分余额</el-dropdown-item>\n                </el-dropdown-menu>\n              </template>\n            </el-dropdown>\n          </div>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <UserForm ref=\"formRef\" @success=\"getList\" />\n  <UserDetail ref=\"formRef1\" />\n  <Yue ref=\"formRef2\" @success=\"getList\" />\n</template>\n\n<script setup lang=\"ts\" name=\"User\">\nimport { dateFormatter } from '@/utils/formatTime'\nimport download from '@/utils/download'\nimport * as UserApi from '@/api/member/user'\nimport UserForm from './UserForm.vue'\nimport UserDetail from './UserDetail.vue'\nimport Yue from './yue.vue'\nconst message = useMessage() // 消息弹窗\n// const { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  username: null,\n  realName: null,\n  nickname: null,\n  phone: null,\n  createTime: []\n})\nconst queryFormRef = ref() // 搜索的表单\nconst exportLoading = ref(false) // 导出的加载中\nconst activeName = ref('')\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await UserApi.getUserPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\nconst handleClick = (tab: TabsPaneContext, event: Event) => {\n  console.log(tab, event)\n  queryParams.pageNo = 1\n  queryParams.loginType = tab.paneName\n  getList()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst formRef1 = ref()\nconst formRef2 = ref()\nconst openForm = (type: string, id?: number) => {\n  if (type == 'update' || type == 'create') {\n    formRef.value.open(type, id)\n  } else if (type == 'userDetail') {\n    formRef1.value.open(type, id)\n  } else if (type == 'yue') {\n    formRef2.value.open(type, id)\n  }\n  \n}\n\n\n\n/** 导出按钮操作 */\nconst handleExport = async () => {\n  try {\n    // 导出的二次确认\n    await message.exportConfirm()\n    // 发起导出\n    exportLoading.value = true\n    const data = await UserApi.exportUser(queryParams)\n    download.excel(data, '用户.xls')\n  } catch {\n  } finally {\n    exportLoading.value = false\n  }\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mall/member/user/yue.vue",
    "content": "<template>\n  <Dialog :title=\"dialogTitle\" v-model=\"dialogVisible\">\n    <el-form\n      ref=\"formRef\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"100px\"\n      v-loading=\"formLoading\"\n    >\n      <el-form-item label=\"修改余额\">\n          <el-radio-group v-model=\"formData.ptype\">\n            <el-radio :label=\"1\">增加</el-radio>\n            <el-radio :label=\"2\">减少</el-radio>\n          </el-radio-group>\n      </el-form-item>\n      <el-form-item label=\"余额\">\n        <el-input v-model=\"formData.money\" style=\"width: 370px;\" placeholder=\"请输入余额\" />\n      </el-form-item>\n      <el-form-item label=\"修改积分\">\n        <el-radio-group v-model=\"formData.itype\">\n            <el-radio :label=\"1\">增加</el-radio>\n            <el-radio :label=\"2\">减少</el-radio>\n         </el-radio-group>\n      </el-form-item>\n      <el-form-item label=\"积分\">\n        <el-input v-model=\"formData.integral\" style=\"width: 370px;\"  placeholder=\"请输入积分\" />\n      </el-form-item>\n    </el-form>\n      \n    <template #footer>\n      <el-button @click=\"submitForm\" type=\"primary\" :disabled=\"formLoading\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script setup lang=\"ts\">\nimport * as UserApi from '@/api/member/user'\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  ptype: 1,\n  money: undefined,\n  itype: 1,\n  integral: undefined\n})\nconst formRules = reactive({\n  money: [{ required: true, message: '金额不能为空', trigger: 'blur' }],\n  integral: [{ required: true, message: '积分不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n   formType.value = type\n  resetForm()\n  // formLoading.value = true\n  formData.value.id = id\n  console.log(type,id)\n  \n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as unknown as UserApi.UserVO\n    await UserApi.updateMony(data)\n    message.success(t('common.updateSuccess'))\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    ptype: 1,\n    money: undefined,\n    itype: 1,\n    integral: undefined\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mall/member/userAddress/UserAddressForm.vue",
    "content": "<template>\n  <Dialog :title=\"dialogTitle\" v-model=\"dialogVisible\">\n    <el-form\n      ref=\"formRef\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"150px\"\n      v-loading=\"formLoading\"\n    >\n      <el-form-item label=\"收货人姓名\" prop=\"realName\">\n        <el-input v-model=\"formData.realName\" placeholder=\"请输入收货人姓名\" />\n      </el-form-item>\n      <el-form-item label=\"收货人电话\" prop=\"phone\">\n        <el-input v-model=\"formData.phone\" placeholder=\"请输入收货人电话\" />\n      </el-form-item>\n      <el-form-item label=\"收货人地址\" prop=\"address\">\n        <el-input v-model=\"formData.address\" placeholder=\"请输入收货人所在省\" />\n      </el-form-item>\n      <el-form-item label=\"收货人详细地址\" prop=\"detail\">\n        <el-input v-model=\"formData.detail\" placeholder=\"请输入收货人详细地址\" />\n      </el-form-item>\n      <el-form-item label=\"是否默认\" prop=\"isDefault\">\n         <el-radio-group v-model=\"formData.isDefault\">\n            <el-radio :label=\"1\">是</el-radio>\n            <el-radio :label=\"0\">否</el-radio>\n          </el-radio-group>\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button @click=\"submitForm\" type=\"primary\" :disabled=\"formLoading\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script setup lang=\"ts\">\nimport * as UserAddressApi from '@/api/member/userAddress'\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  uid: undefined,\n  realName: undefined,\n  phone: undefined,\n  province: undefined,\n  city: undefined,\n  cityId: undefined,\n  district: undefined,\n  detail: undefined,\n  postCode: undefined,\n  longitude: undefined,\n  latitude: undefined,\n  isDefault: undefined,\n  address: ''\n})\nconst formRules = reactive({\n  realName: [{ required: true, message: '收货人姓名不能为空', trigger: 'blur' }],\n  phone: [{ required: true, message: '收货人电话不能为空', trigger: 'blur' }],\n  address: [{ required: true, message: '收货人地址不能为空', trigger: 'blur' }],\n  detail: [{ required: true, message: '收货人详细地址不能为空', trigger: 'blur' }],\n  isDefault: [{ required: true, message: '是否默认不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await UserAddressApi.getUserAddress(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as unknown as UserAddressApi.UserAddressVO\n    if (formType.value === 'create') {\n      await UserAddressApi.createUserAddress(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await UserAddressApi.updateUserAddress(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    uid: undefined,\n    realName: undefined,\n    phone: undefined,\n    province: undefined,\n    city: undefined,\n    cityId: undefined,\n    district: undefined,\n    detail: undefined,\n    postCode: undefined,\n    longitude: undefined,\n    latitude: undefined,\n    isDefault: undefined,\n    address: ''\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mall/member/userAddress/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <!-- 搜索工作栏 -->\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"姓名\" prop=\"realName\">\n        <el-input\n          v-model=\"queryParams.realName\"\n          placeholder=\"请输入收货人姓名\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"电话\" prop=\"phone\">\n        <el-input\n          v-model=\"queryParams.phone\"\n          placeholder=\"请输入收货人电话\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column label=\"id\" align=\"center\" prop=\"id\" />\n      <el-table-column label=\"收货人姓名\" align=\"center\" prop=\"realName\" />\n      <el-table-column label=\"收货人电话\" align=\"center\" prop=\"phone\" />\n      <el-table-column label=\"收货人地址\" align=\"center\" prop=\"address\" />\n      <el-table-column label=\"收货人详细地址\" align=\"center\" prop=\"detail\" />\n      <el-table-column label=\"是否默认\" align=\"center\" prop=\"isDefault\">\n        <template #default=\"scope\">\n         <span v-if=\"scope.row.isDefault == 1\">是</span>\n         <span v-else>否</span>\n        </template>\n      </el-table-column>\n      <el-table-column\n        label=\"添加时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        :formatter=\"dateFormatter\"\n        width=\"180\"\n      />\n      <el-table-column label=\"操作\" align=\"center\" fixed=\"right\" width=\"150\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n            v-hasPermi=\"['member:user-address:update']\"\n          >\n            编辑\n          </el-button>\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n            v-hasPermi=\"['member:user-address:delete']\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <UserAddressForm ref=\"formRef\" @success=\"getList\" />\n</template>\n\n<script setup lang=\"ts\" name=\"UserAddress\">\nimport { dateFormatter } from '@/utils/formatTime'\nimport * as UserAddressApi from '@/api/member/userAddress'\nimport UserAddressForm from './UserAddressForm.vue'\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  uid: null,\n  realName: null,\n  phone: null,\n  province: null,\n  city: null,\n  cityId: null,\n  district: null,\n  detail: null,\n  postCode: null,\n  longitude: null,\n  latitude: null,\n  isDefault: null,\n  createTime: []\n})\nconst queryFormRef = ref() // 搜索的表单\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await UserAddressApi.getUserAddressPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await UserAddressApi.deleteUserAddress(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mall/order/storeOrder/OrderDetail.vue",
    "content": "<template>\n <el-drawer v-model=\"drawer\" :title=\"dialogTitle\" size=\"40%\">\n    <div>\n      <el-descriptions title=\"收货信息1\" :column=\"2\" v-if=\"DetailData.orderType == 'takeout'\">\n        <el-descriptions-item label=\"用户昵称\">{{ nickname }}</el-descriptions-item>\n        <el-descriptions-item label=\"收货人\">{{ DetailData.realName }}</el-descriptions-item>\n        <el-descriptions-item label=\"联系电话\">{{ DetailData.userPhone }}</el-descriptions-item>\n        <el-descriptions-item label=\"收货地址\">{{ DetailData.userAddress }}</el-descriptions-item>\n      </el-descriptions>\n      <el-descriptions title=\"商品明细\" :column=\"1\">\n          <el-descriptions-item label=\"菜品\">\n            <div v-for=\"(num,k) in addProductMark + 1\" :key=\"k\">\n              <div v-if=\"num > 1\" style=\"text-align:center;font-weight:bold;font-size:22px\">\n                第{{ num - 1 }}次加菜\n              </div>\n              <div v-else style=\"text-align:center;font-weight:bold;font-size:22px\">\n                首次点菜\n              </div>\n              <table width=\"100%\">\n                <tr style=\"font-weight:bold;height:50px\">\n                  <td>图片</td>\n                  <td>名称</td>\n                  <td>价格</td>\n                  <td>数量</td>\n                  <td>状态</td>\n                </tr>\n                <tr  v-for=\"(val, i ) in product\" :key=\"i\" >\n                    <td><el-image style=\"width: 40px; height: 40px\" :src=\"val.image\" :fit=\"fit\" /></td>\n                    <td>{{ val.title}}</td>\n                    <td>{{ '￥'+ val.price}}</td>\n                    <td>{{ ' x '+ val.number}}</td>\n                    <td>\n                      <el-tag  type=\"success\" v-if=\"val.isOrder==1\">已出单</el-tag>\n                      <el-tag  type=\"danger\" v-else>未出单</el-tag>\n                    </td>\n                </tr>\n              </table>\n            </div>\n          </el-descriptions-item>\n      </el-descriptions>\n      <el-descriptions title=\"订单信息\" :column=\"2\">\n        <template #title>\n          订单信息\n          <el-tag  type=\"danger\" v-if=\"DetailData.orderType=='desk'\">堂食</el-tag>\n          <el-tag  type=\"danger\" v-if=\"DetailData.orderType=='takeout'\">外卖</el-tag>\n          <el-tag  type=\"danger\" v-if=\"DetailData.orderType=='takein'\">自取</el-tag>\n        </template>\n        <el-descriptions-item label=\"门店\">{{ DetailData.shopName }}</el-descriptions-item>\n        <el-descriptions-item label=\"取餐号\">{{ DetailData.numberId }}</el-descriptions-item>\n        <el-descriptions-item label=\"桌位号\">{{ DetailData.deskNumber ? DetailData.deskNumber : '无' }}</el-descriptions-item>\n        <el-descriptions-item label=\"就餐人数\">{{ DetailData.deskPeople ? DetailData.deskPeople : '无' }}</el-descriptions-item>\n        <el-descriptions-item label=\"订单号\">{{ DetailData.orderId }}</el-descriptions-item>\n        <el-descriptions-item label=\"订单状态\">{{ DetailData.statusStr }}</el-descriptions-item>\n        <el-descriptions-item label=\"商品总数\">{{ DetailData.totalNum }}</el-descriptions-item>\n        <el-descriptions-item label=\"商品总价\">{{ DetailData.totalPrice }}</el-descriptions-item>\n        <el-descriptions-item label=\"支付邮费\">{{ DetailData.payPostage }}</el-descriptions-item>\n        <el-descriptions-item label=\"优惠券金额\">{{ DetailData.couponPrice }}</el-descriptions-item>\n        <el-descriptions-item label=\"积分抵扣\">{{ DetailData.useIntegral }}</el-descriptions-item>\n        <el-descriptions-item label=\"实际支付\">{{ DetailData.payPrice }}</el-descriptions-item>\n        <el-descriptions-item label=\"赠送积分\">{{ DetailData.gainIntegral }}</el-descriptions-item>\n        <el-descriptions-item label=\"创建时间\">{{ formatDate(DetailData.createTime)}}</el-descriptions-item>\n        <el-descriptions-item label=\"支付时间\">{{ formatDate(DetailData.payTime) }}</el-descriptions-item>\n        <el-descriptions-item label=\"支付方式\">\n          <span v-if=\"DetailData.paid == 1\">\n           <span v-if=\"DetailData.payType=='yue'\">余额支付</span>\n           <span v-if=\"DetailData.payType=='weixin'\">微信支付</span>\n           <span v-if=\"DetailData.payType=='alipay'\">支付宝支付</span>\n           <span v-if=\"DetailData.payType=='cash'\">现金支付</span>\n          </span>\n          <span v-else>--</span>\n        </el-descriptions-item>\n        <el-descriptions-item label=\"订单备注\">{{ DetailData.mark }}</el-descriptions-item>\n      </el-descriptions>\n      <el-timeline>\n        <el-timeline-item\n          v-for=\"(activity, index) in logisticResult\"\n          :key=\"index\"\n          :timestamp=\"activity.acceptTime\"\n        >\n          {{ activity.acceptStation }}\n        </el-timeline-item>\n      </el-timeline>\n    </div>\n  </el-drawer>\n</template>\n<script setup lang=\"ts\">\nimport * as StoreOrderApi from '@/api/mall/order/storeOrder'\nimport { formatDate } from '@/utils/formatTime'\n\nconst { t } = useI18n() // 国际化\nconst dialogTitle = ref('') // 弹窗的标题\nconst drawer = ref(false)\nconst DetailData = ref({})\nconst nickname = ref('')\nconst logisticResult = ref({})\nconst product = ref([])\nconst addProductMark = ref(0)\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  drawer.value = true\n  dialogTitle.value = t('action.' + type)\n  DetailData.value = await StoreOrderApi.getStoreOrder(id)\n  nickname.value = DetailData.value.userRespVO.nickname\n  product.value = DetailData.value.storeOrderCartInfoDOList\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n</script>\n<style scoped>\n</style>"
  },
  {
    "path": "yshop-drink-vue3/src/views/mall/order/storeOrder/OrderRecord.vue",
    "content": "<template>\n  <el-drawer v-model=\"drawer\" :title=\"dialogTitle\" size=\"40%\">\n    <el-table :data=\"tableData\" style=\"width: 100%\">\n      <el-table-column prop=\"oid\" label=\"订单id\" width=\"180\" />\n      <el-table-column prop=\"changeMessage\" label=\"操作记录\" width=\"180\" />\n      <el-table-column prop=\"changeTime\" :formatter=\"dateFormatter\" label=\"操作时间\" />\n    </el-table>\n  </el-drawer>\n\n</template>\n<script setup lang=\"ts\">\nimport * as StoreOrderApi from '@/api/mall/order/storeOrder'\nimport { dateFormatter } from '@/utils/formatTime'\n\nconst { t } = useI18n() // 国际化\nconst dialogTitle = ref('') // 弹窗的标题\nconst drawer = ref(false)\nconst tableData = ref([])\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  drawer.value = true\n  dialogTitle.value = t('action.' + type)\n  console.log(id)\n  tableData.value = await StoreOrderApi.getStoreOrderRecordList(id)\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n</script>\n<style scoped>\n</style>"
  },
  {
    "path": "yshop-drink-vue3/src/views/mall/order/storeOrder/OrderSend.vue",
    "content": "<template>\n  <Dialog :title=\"dialogTitle\" v-model=\"dialogVisible\">\n    <el-form\n      ref=\"formRef\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"120px\"\n      v-loading=\"formLoading\"\n    >\n      <el-form-item label=\"订单号\" prop=\"orderType\">\n        <el-input v-model=\"formData.orderId\" disabled class=\"input-width\" />\n      </el-form-item>\n      <el-form-item label=\"桌号\" prop=\"orderType\" v-if=\"formData.orderType == 'desk'\">\n        <el-input v-model=\"formData.deskNumber\" disabled class=\"input-width\" />\n      </el-form-item>\n      <el-form-item label=\"取餐号\" prop=\"orderType\" v-if=\"formData.orderType == 'takein'\">\n        <el-input v-model=\"formData.numberId\" disabled class=\"input-width\" />\n      </el-form-item>\n      <el-form-item label=\"地址\" prop=\"orderType\" v-if=\"formData.orderType == 'takeout'\">\n        <span>{{ formData.realName }}{{ formData.userPhone }}{{ formData.userAddress }}</span>\n      </el-form-item>  \n    </el-form>\n    <template #footer>\n      <el-button @click=\"submitForm\" type=\"primary\"   :disabled=\"formLoading\">打 印</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script setup lang=\"ts\">\nimport * as StoreOrderApi from '@/api/mall/order/storeOrder'\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n    id: undefined,\n    updateType: \"\",\n    orderId: undefined,\n    orderType: undefined,\n    extendOrderId: undefined,\n    uid: undefined,\n    realName: undefined,\n    userPhone: undefined,\n    userAddress: undefined,\n    cartId: undefined,\n    freightPrice: undefined,\n    totalNum: undefined,\n    totalPrice: undefined,\n    totalPostage: undefined,\n    payPrice: undefined,\n    payPostage: undefined,\n    deductionPrice: undefined,\n    couponId: undefined,\n    couponPrice: undefined,\n    paid: undefined,\n    payTime: undefined,\n    payType: undefined,\n    status: undefined,\n    refundStatus: undefined,\n    refundReasonWapImg: undefined,\n    refundReasonWapExplain: undefined,\n    refundReasonTime: undefined,\n    refundReasonWap: undefined,\n    refundReason: undefined,\n    refundPrice: undefined,\n    deliverySn: undefined,\n    deliveryName: undefined,\n    deliveryType: 'normal',\n    deliveryId: undefined,\n    gainIntegral: undefined,\n    useIntegral: undefined,\n    payIntegral: undefined,\n    backIntegral: undefined,\n    mark: undefined,\n    unique: undefined,\n    remark: undefined,\n    merId: undefined,\n    combinationId: undefined,\n    pinkId: undefined,\n    cost: undefined,\n    seckillId: undefined,\n    bargainId: undefined,\n    verifyCode: undefined,\n    storeId: undefined,\n    shippingType: undefined,\n    isChannel: undefined,\n    isSystemDel: undefined\n})\nconst formRules = reactive({\n})\nconst formRef = ref() // 表单 Ref\n\n\n \n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = '小票打印'\n  formType.value = type\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await StoreOrderApi.getStoreOrder(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as unknown as StoreOrderApi.StoreOrderVO\n    data.updateType = 'orderSend'\n    await StoreOrderApi.updateStoreOrder(data)\n    message.success(t('common.updateSuccess'))\n  \n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    updateType: \"\",\n    orderId: undefined,\n    orderType: undefined,\n    extendOrderId: undefined,\n    uid: undefined,\n    realName: undefined,\n    userPhone: undefined,\n    userAddress: undefined,\n    cartId: undefined,\n    freightPrice: undefined,\n    totalNum: undefined,\n    totalPrice: undefined,\n    totalPostage: undefined,\n    payPrice: undefined,\n    payPostage: undefined,\n    deductionPrice: undefined,\n    couponId: undefined,\n    couponPrice: undefined,\n    paid: undefined,\n    payTime: undefined,\n    payType: undefined,\n    status: undefined,\n    refundStatus: undefined,\n    refundReasonWapImg: undefined,\n    refundReasonWapExplain: undefined,\n    refundReasonTime: undefined,\n    refundReasonWap: undefined,\n    refundReason: undefined,\n    refundPrice: undefined,\n    deliverySn: undefined,\n    deliveryName: undefined,\n    deliveryType: 'normal',\n    deliveryId: undefined,\n    gainIntegral: undefined,\n    useIntegral: undefined,\n    payIntegral: undefined,\n    backIntegral: undefined,\n    mark: undefined,\n    unique: undefined,\n    remark: undefined,\n    merId: undefined,\n    combinationId: undefined,\n    pinkId: undefined,\n    cost: undefined,\n    seckillId: undefined,\n    bargainId: undefined,\n    verifyCode: undefined,\n    storeId: undefined,\n    shippingType: undefined,\n    isChannel: undefined,\n    isSystemDel: undefined\n  }\n  formRef.value?.resetFields()\n}\n</script>\n<style scoped>\n.input-width {\n    width: 50%;\n  }\n</style>"
  },
  {
    "path": "yshop-drink-vue3/src/views/mall/order/storeOrder/OrderSendInfo.vue",
    "content": "<template>\n  <Dialog :title=\"dialogTitle\" v-model=\"dialogVisible\">\n    <el-form\n      ref=\"formRef\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"120px\"\n      v-loading=\"formLoading\"\n    >\n      <el-form-item label=\"订单号\" prop=\"orderId\">\n        <el-input v-model=\"formData.orderId\" disabled class=\"input-width\" />\n      </el-form-item>\n      <el-form-item label=\"快递公司\" prop=\"deliverySn\">\n          <el-select v-model=\"formData.deliverySn\" placeholder=\"选择快递公司\" @change=\"selectExpress\" >\n            <el-option label=\"选择快递公司\" value=\"\" />\n            <el-option\n              v-for=\"item in express\"\n              :key=\"item.code\"\n              :label=\"item.name\"\n              :value=\"item.code\"\n            />\n          </el-select>\n      </el-form-item>\n      <el-form-item label=\"快递单号\" prop=\"deliveryId\">\n        <el-input v-model=\"formData.deliveryId\" placeholder=\"请输入快递单号\" class=\"input-width\" />\n      </el-form-item>\n     \n    </el-form>\n    <template #footer>\n      <el-button @click=\"submitForm\" type=\"primary\" :disabled=\"formLoading\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script setup lang=\"ts\">\nimport * as StoreOrderApi from '@/api/mall/order/storeOrder'\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n    id: undefined,\n    updateType: \"\",\n    orderId: undefined,\n    orderType: 'send',\n    extendOrderId: undefined,\n    uid: undefined,\n    realName: undefined,\n    userPhone: undefined,\n    userAddress: undefined,\n    cartId: undefined,\n    freightPrice: undefined,\n    totalNum: undefined,\n    totalPrice: undefined,\n    totalPostage: undefined,\n    payPrice: undefined,\n    payPostage: undefined,\n    deductionPrice: undefined,\n    couponId: undefined,\n    couponPrice: undefined,\n    paid: undefined,\n    payTime: undefined,\n    payType: undefined,\n    status: undefined,\n    refundStatus: undefined,\n    refundReasonWapImg: undefined,\n    refundReasonWapExplain: undefined,\n    refundReasonTime: undefined,\n    refundReasonWap: undefined,\n    refundReason: undefined,\n    refundPrice: undefined,\n    deliverySn: undefined,\n    deliveryName: undefined,\n    deliveryType: 'normal',\n    deliveryId: undefined,\n    gainIntegral: undefined,\n    useIntegral: undefined,\n    payIntegral: undefined,\n    backIntegral: undefined,\n    mark: undefined,\n    unique: undefined,\n    remark: undefined,\n    merId: undefined,\n    combinationId: undefined,\n    pinkId: undefined,\n    cost: undefined,\n    seckillId: undefined,\n    bargainId: undefined,\n    verifyCode: undefined,\n    storeId: undefined,\n    shippingType: undefined,\n    isChannel: undefined,\n    isSystemDel: undefined\n})\nconst formRules = reactive({\n  deliveryName: [{ required: true, message: '快递公司不能为空', trigger: 'blur' }],\n  deliveryId: [{ required: true, message: '快递单号不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\nconst express = ref([])\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await StoreOrderApi.getStoreOrder(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as unknown as StoreOrderApi.StoreOrderVO\n    data.updateType = formType.value\n    await StoreOrderApi.updateStoreOrder(data)\n    message.success(t('common.updateSuccess'))\n  \n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\nconst selectExpress = (val) => {\n  let obj = {};\n  obj = express.value.find((item)=>{ // 这里的userList就是上面遍历的数据源\n      return item.code === val; // 筛选出匹配数据\n  })\n  formData.value.deliveryName = obj.name\n}\n\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    updateType: \"\",\n    orderId: undefined,\n    orderType: 'send',\n    extendOrderId: undefined,\n    uid: undefined,\n    realName: undefined,\n    userPhone: undefined,\n    userAddress: undefined,\n    cartId: undefined,\n    freightPrice: undefined,\n    totalNum: undefined,\n    totalPrice: undefined,\n    totalPostage: undefined,\n    payPrice: undefined,\n    payPostage: undefined,\n    deductionPrice: undefined,\n    couponId: undefined,\n    couponPrice: undefined,\n    paid: undefined,\n    payTime: undefined,\n    payType: undefined,\n    status: undefined,\n    refundStatus: undefined,\n    refundReasonWapImg: undefined,\n    refundReasonWapExplain: undefined,\n    refundReasonTime: undefined,\n    refundReasonWap: undefined,\n    refundReason: undefined,\n    refundPrice: undefined,\n    deliverySn: undefined,\n    deliveryName: undefined,\n    deliveryType: 'normal',\n    deliveryId: undefined,\n    gainIntegral: undefined,\n    useIntegral: undefined,\n    payIntegral: undefined,\n    backIntegral: undefined,\n    mark: undefined,\n    unique: undefined,\n    remark: undefined,\n    merId: undefined,\n    combinationId: undefined,\n    pinkId: undefined,\n    cost: undefined,\n    seckillId: undefined,\n    bargainId: undefined,\n    verifyCode: undefined,\n    storeId: undefined,\n    shippingType: undefined,\n    isChannel: undefined,\n    isSystemDel: undefined\n  }\n  formRef.value?.resetFields()\n}\n</script>\n<style scoped>\n.input-width {\n    width: 50%;\n  }\n</style>"
  },
  {
    "path": "yshop-drink-vue3/src/views/mall/order/storeOrder/StoreOrderForm.vue",
    "content": "<template>\n  <Dialog :title=\"dialogTitle\" v-model=\"dialogVisible\">\n    <el-form\n      ref=\"formRef\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"120px\"\n      v-loading=\"formLoading\"\n    >\n      <el-form-item label=\"订单号\" prop=\"orderId\">\n        <el-input disabled v-model=\"formData.orderId\" placeholder=\"请输入订单号\" />\n      </el-form-item>\n      <el-form-item label=\"订单总价\" prop=\"totalPrice\">\n        <el-input disabled v-model=\"formData.totalPrice\" placeholder=\"请输入订单总价\" />\n      </el-form-item>\n      <el-form-item label=\"实际支付金额\" prop=\"payPrice\">\n        <el-input v-model=\"formData.payPrice\" placeholder=\"请输入实际支付金额\" />\n      </el-form-item>\n      <el-form-item label=\"赠送积分\" prop=\"gainIntegral\">\n        <el-input v-model=\"formData.gainIntegral\" placeholder=\"请输入赠送积分\" />\n      </el-form-item>\n     \n    </el-form>\n    <template #footer>\n      <el-button @click=\"submitForm\" type=\"primary\" :disabled=\"formLoading\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script setup lang=\"ts\">\nimport * as StoreOrderApi from '@/api/mall/order/storeOrder'\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  orderId: undefined,\n  extendOrderId: undefined,\n  uid: undefined,\n  realName: undefined,\n  userPhone: undefined,\n  userAddress: undefined,\n  cartId: undefined,\n  freightPrice: undefined,\n  totalNum: undefined,\n  totalPrice: undefined,\n  totalPostage: undefined,\n  payPrice: undefined,\n  payPostage: undefined,\n  deductionPrice: undefined,\n  couponId: undefined,\n  couponPrice: undefined,\n  paid: undefined,\n  payTime: undefined,\n  payType: undefined,\n  status: undefined,\n  refundStatus: undefined,\n  refundReasonWapImg: undefined,\n  refundReasonWapExplain: undefined,\n  refundReasonTime: undefined,\n  refundReasonWap: undefined,\n  refundReason: undefined,\n  refundPrice: undefined,\n  deliverySn: undefined,\n  deliveryName: undefined,\n  deliveryType: undefined,\n  deliveryId: undefined,\n  gainIntegral: undefined,\n  useIntegral: undefined,\n  payIntegral: undefined,\n  backIntegral: undefined,\n  mark: undefined,\n  unique: undefined,\n  remark: undefined,\n  merId: undefined,\n  combinationId: undefined,\n  pinkId: undefined,\n  cost: undefined,\n  seckillId: undefined,\n  bargainId: undefined,\n  verifyCode: undefined,\n  storeId: undefined,\n  shippingType: undefined,\n  isChannel: undefined,\n  isSystemDel: undefined\n})\nconst formRules = reactive({\n  totalPrice: [{ required: true, message: '订单总价不能为空', trigger: 'blur' }],\n  payPrice: [{ required: true, message: '实际支付金额不能为空', trigger: 'blur' }],\n  gainIntegral: [{ required: true, message: '消费赚取积分不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await StoreOrderApi.getStoreOrder(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as unknown as StoreOrderApi.StoreOrderVO\n    await StoreOrderApi.updateStoreOrder(data)\n    message.success(t('common.updateSuccess'))\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    orderId: undefined,\n    extendOrderId: undefined,\n    uid: undefined,\n    realName: undefined,\n    userPhone: undefined,\n    userAddress: undefined,\n    cartId: undefined,\n    freightPrice: undefined,\n    totalNum: undefined,\n    totalPrice: undefined,\n    totalPostage: undefined,\n    payPrice: undefined,\n    payPostage: undefined,\n    deductionPrice: undefined,\n    couponId: undefined,\n    couponPrice: undefined,\n    paid: undefined,\n    payTime: undefined,\n    payType: undefined,\n    status: undefined,\n    refundStatus: undefined,\n    refundReasonWapImg: undefined,\n    refundReasonWapExplain: undefined,\n    refundReasonTime: undefined,\n    refundReasonWap: undefined,\n    refundReason: undefined,\n    refundPrice: undefined,\n    deliverySn: undefined,\n    deliveryName: undefined,\n    deliveryType: undefined,\n    deliveryId: undefined,\n    gainIntegral: undefined,\n    useIntegral: undefined,\n    payIntegral: undefined,\n    backIntegral: undefined,\n    mark: undefined,\n    unique: undefined,\n    remark: undefined,\n    merId: undefined,\n    combinationId: undefined,\n    pinkId: undefined,\n    cost: undefined,\n    seckillId: undefined,\n    bargainId: undefined,\n    verifyCode: undefined,\n    storeId: undefined,\n    shippingType: undefined,\n    isChannel: undefined,\n    isSystemDel: undefined\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mall/order/storeOrder/StoreOrderRefund.vue",
    "content": "<template>\n  <Dialog :title=\"dialogTitle\" v-model=\"dialogVisible\">\n    <el-form\n      ref=\"formRef\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"120px\"\n      v-loading=\"formLoading\"\n    >\n      <el-form-item label=\"订单号\" prop=\"orderId\">\n        <el-input disabled v-model=\"formData.orderId\" placeholder=\"请输入订单号\" />\n      </el-form-item>\n      <el-form-item label=\"订单总价\" prop=\"totalPrice\">\n        <el-input disabled v-model=\"formData.totalPrice\" placeholder=\"请输入订单总价\" />\n      </el-form-item>\n      <el-form-item label=\"退款金额\" prop=\"payPrice\">\n        <el-input v-model=\"formData.payPrice\" placeholder=\"退款金额\" />\n      </el-form-item>\n     \n    </el-form>\n    <template #footer>\n      <el-button @click=\"submitForm\" type=\"primary\" :disabled=\"formLoading\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script setup lang=\"ts\">\nimport * as StoreOrderApi from '@/api/mall/order/storeOrder'\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  orderId: undefined,\n  extendOrderId: undefined,\n  uid: undefined,\n  realName: undefined,\n  userPhone: undefined,\n  userAddress: undefined,\n  cartId: undefined,\n  freightPrice: undefined,\n  totalNum: undefined,\n  totalPrice: undefined,\n  totalPostage: undefined,\n  payPrice: undefined,\n  payPostage: undefined,\n  deductionPrice: undefined,\n  couponId: undefined,\n  couponPrice: undefined,\n  paid: undefined,\n  payTime: undefined,\n  payType: undefined,\n  status: undefined,\n  refundStatus: undefined,\n  refundReasonWapImg: undefined,\n  refundReasonWapExplain: undefined,\n  refundReasonTime: undefined,\n  refundReasonWap: undefined,\n  refundReason: undefined,\n  refundPrice: undefined,\n  deliverySn: undefined,\n  deliveryName: undefined,\n  deliveryType: undefined,\n  deliveryId: undefined,\n  gainIntegral: undefined,\n  useIntegral: undefined,\n  payIntegral: undefined,\n  backIntegral: undefined,\n  mark: undefined,\n  unique: undefined,\n  remark: undefined,\n  merId: undefined,\n  combinationId: undefined,\n  pinkId: undefined,\n  cost: undefined,\n  seckillId: undefined,\n  bargainId: undefined,\n  verifyCode: undefined,\n  storeId: undefined,\n  shippingType: undefined,\n  isChannel: undefined,\n  isSystemDel: undefined\n})\nconst formRules = reactive({\n  totalPrice: [{ required: true, message: '订单总价不能为空', trigger: 'blur' }],\n  payPrice: [{ required: true, message: '退款金额金额不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await StoreOrderApi.getStoreOrder(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as unknown as StoreOrderApi.StoreOrderVO\n    await StoreOrderApi.rufundStoreOrder(data)\n    message.success(t('common.updateSuccess'))\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    orderId: undefined,\n    extendOrderId: undefined,\n    uid: undefined,\n    realName: undefined,\n    userPhone: undefined,\n    userAddress: undefined,\n    cartId: undefined,\n    freightPrice: undefined,\n    totalNum: undefined,\n    totalPrice: undefined,\n    totalPostage: undefined,\n    payPrice: undefined,\n    payPostage: undefined,\n    deductionPrice: undefined,\n    couponId: undefined,\n    couponPrice: undefined,\n    paid: undefined,\n    payTime: undefined,\n    payType: undefined,\n    status: undefined,\n    refundStatus: undefined,\n    refundReasonWapImg: undefined,\n    refundReasonWapExplain: undefined,\n    refundReasonTime: undefined,\n    refundReasonWap: undefined,\n    refundReason: undefined,\n    refundPrice: undefined,\n    deliverySn: undefined,\n    deliveryName: undefined,\n    deliveryType: undefined,\n    deliveryId: undefined,\n    gainIntegral: undefined,\n    useIntegral: undefined,\n    payIntegral: undefined,\n    backIntegral: undefined,\n    mark: undefined,\n    unique: undefined,\n    remark: undefined,\n    merId: undefined,\n    combinationId: undefined,\n    pinkId: undefined,\n    cost: undefined,\n    seckillId: undefined,\n    bargainId: undefined,\n    verifyCode: undefined,\n    storeId: undefined,\n    shippingType: undefined,\n    isChannel: undefined,\n    isSystemDel: undefined\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mall/order/storeOrder/StoreOrderRemark.vue",
    "content": "<template>\n  <Dialog :title=\"dialogTitle\" v-model=\"dialogVisible\">\n    <el-form\n      ref=\"formRef\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"120px\"\n      v-loading=\"formLoading\"\n    >\n      <el-form-item label=\"备注\" prop=\"remark\">\n        <el-input type=\"textarea\" rows=\"5\"  v-model=\"formData.remark\" placeholder=\"请输入备注\" />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button @click=\"submitForm\" type=\"primary\" :disabled=\"formLoading\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script setup lang=\"ts\">\nimport * as StoreOrderApi from '@/api/mall/order/storeOrder'\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  orderId: undefined,\n  extendOrderId: undefined,\n  uid: undefined,\n  realName: undefined,\n  userPhone: undefined,\n  userAddress: undefined,\n  cartId: undefined,\n  freightPrice: undefined,\n  totalNum: undefined,\n  totalPrice: undefined,\n  totalPostage: undefined,\n  payPrice: undefined,\n  payPostage: undefined,\n  deductionPrice: undefined,\n  couponId: undefined,\n  couponPrice: undefined,\n  paid: undefined,\n  payTime: undefined,\n  payType: undefined,\n  status: undefined,\n  refundStatus: undefined,\n  refundReasonWapImg: undefined,\n  refundReasonWapExplain: undefined,\n  refundReasonTime: undefined,\n  refundReasonWap: undefined,\n  refundReason: undefined,\n  refundPrice: undefined,\n  deliverySn: undefined,\n  deliveryName: undefined,\n  deliveryType: undefined,\n  deliveryId: undefined,\n  gainIntegral: undefined,\n  useIntegral: undefined,\n  payIntegral: undefined,\n  backIntegral: undefined,\n  mark: undefined,\n  unique: undefined,\n  remark: undefined,\n  merId: undefined,\n  combinationId: undefined,\n  pinkId: undefined,\n  cost: undefined,\n  seckillId: undefined,\n  bargainId: undefined,\n  verifyCode: undefined,\n  storeId: undefined,\n  shippingType: undefined,\n  isChannel: undefined,\n  isSystemDel: undefined\n})\nconst formRules = reactive({\n  totalPrice: [{ required: true, message: '订单总价不能为空', trigger: 'blur' }],\n  payPrice: [{ required: true, message: '实际支付金额不能为空', trigger: 'blur' }],\n  gainIntegral: [{ required: true, message: '消费赚取积分不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await StoreOrderApi.getStoreOrder(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as unknown as StoreOrderApi.StoreOrderVO\n    await StoreOrderApi.updateStoreOrder(data)\n    message.success(t('common.updateSuccess'))\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    orderId: undefined,\n    extendOrderId: undefined,\n    uid: undefined,\n    realName: undefined,\n    userPhone: undefined,\n    userAddress: undefined,\n    cartId: undefined,\n    freightPrice: undefined,\n    totalNum: undefined,\n    totalPrice: undefined,\n    totalPostage: undefined,\n    payPrice: undefined,\n    payPostage: undefined,\n    deductionPrice: undefined,\n    couponId: undefined,\n    couponPrice: undefined,\n    paid: undefined,\n    payTime: undefined,\n    payType: undefined,\n    status: undefined,\n    refundStatus: undefined,\n    refundReasonWapImg: undefined,\n    refundReasonWapExplain: undefined,\n    refundReasonTime: undefined,\n    refundReasonWap: undefined,\n    refundReason: undefined,\n    refundPrice: undefined,\n    deliverySn: undefined,\n    deliveryName: undefined,\n    deliveryType: undefined,\n    deliveryId: undefined,\n    gainIntegral: undefined,\n    useIntegral: undefined,\n    payIntegral: undefined,\n    backIntegral: undefined,\n    mark: undefined,\n    unique: undefined,\n    remark: undefined,\n    merId: undefined,\n    combinationId: undefined,\n    pinkId: undefined,\n    cost: undefined,\n    seckillId: undefined,\n    bargainId: undefined,\n    verifyCode: undefined,\n    storeId: undefined,\n    shippingType: undefined,\n    isChannel: undefined,\n    isSystemDel: undefined\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mall/order/storeOrder/index.vue",
    "content": "<template>\n   <ContentWrap>\n    <el-tabs v-model=\"activeName\"  @tab-click=\"handleClick\">\n      <el-tab-pane label=\"全部订单\" name=\"\"/>\n      <el-tab-pane label=\"外卖订单\" name=\"takeout\"/>\n      <el-tab-pane label=\"自取订单\" name=\"takein\"/>\n    </el-tabs>\n    <el-form-item label=\"订单状态：\" >\n      <el-radio-group v-model=\"orderStatus\" size=\"large\"  fill=\"#DC143C\" @change=\"queryOrderStatus\">\n        <el-radio-button label=\"\">全部</el-radio-button>\n        <el-radio-button label=\"0\">未支付</el-radio-button>\n        <el-radio-button label=\"1\">制作中</el-radio-button>\n        <el-radio-button label=\"2\">待收货</el-radio-button>\n        <el-radio-button label=\"4\">已收货/已取餐</el-radio-button>\n        <!-- <el-radio-button label=\"4\">交易完成</el-radio-button> -->\n        <el-radio-button label=\"5\">退款单</el-radio-button>\n        <el-radio-button label=\"6\">已删除</el-radio-button>\n      </el-radio-group>\n    </el-form-item>\n     <el-form-item label=\"支付方式：\" >\n      <el-radio-group v-model=\"payStatus\" size=\"large\"  fill=\"#FF1493\" @change=\"queryPayStatus\">\n        <el-radio-button label=\"\">全部</el-radio-button>\n        <el-radio-button label=\"weixin\">微信支付</el-radio-button>\n        <el-radio-button label=\"alipay\">支付宝支付</el-radio-button>\n        <el-radio-button label=\"yue\">余额支付</el-radio-button>\n      </el-radio-group>\n    </el-form-item>\n  </ContentWrap>\n  <ContentWrap>\n    <!-- 搜索工作栏 -->\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"订单号\" prop=\"orderId\">\n        <el-input\n          v-model=\"queryParams.orderId\"\n          placeholder=\"请输入订单号\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"用户姓名\" prop=\"realName\">\n        <el-input\n          v-model=\"queryParams.realName\"\n          placeholder=\"请输入用户姓名\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"用户电话\" prop=\"userPhone\">\n        <el-input\n          v-model=\"queryParams.userPhone\"\n          placeholder=\"请输入用户电话\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n\n      <el-form-item label=\"创建时间\" prop=\"createTime\">\n        <el-date-picker\n          v-model=\"queryParams.createTime\"\n          value-format=\"YYYY-MM-DD HH:mm:ss\"\n          type=\"daterange\"\n          start-placeholder=\"开始日期\"\n          end-placeholder=\"结束日期\"\n          :default-time=\"[new Date('1 00:00:00'), new Date('1 23:59:59')]\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n     \n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n      </el-form-item>\n    </el-form>\n    \n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\" style=\"width: 100%\">\n      <el-table-column label=\"ID\" align=\"center\" prop=\"id\" />\n      <el-table-column label=\"门店\" align=\"center\" prop=\"shopName\" width=\"100\" />\n      <el-table-column label=\"取餐号\" align=\"center\" prop=\"numberId\" />\n      <el-table-column label=\"桌号\" align=\"center\" prop=\"deskNumber\" />\n      <el-table-column label=\"订单号\" align=\"center\" prop=\"orderId\" width=\"240\">\n        <template #default=\"scope\">\n          <span>\n            <el-tag class=\"ml-2\" type=\"danger\" v-if=\"scope.row.orderType=='desk'\">堂食</el-tag>\n            <el-tag class=\"ml-2\" type=\"danger\" v-if=\"scope.row.orderType=='takeout'\">外卖</el-tag>\n            <el-tag class=\"ml-2\" type=\"danger\" v-if=\"scope.row.orderType=='takein'\">自取</el-tag>\n           {{ scope.row.orderId }}</span>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"用户id｜昵称\" align=\"center\"  width=\"120\" >\n        <template #default=\"scope\">\n          <span>{{ scope.row.uid }}|{{ scope.row.userRespVO ? scope.row.userRespVO.nickname : '无' }}</span>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"用户姓名|电话\" align=\"center\" prop=\"realName\" width=\"150\">\n        <template #default=\"scope\">\n          <span v-if=\"scope.row.orderType == 'takeout'\">{{ scope.row.realName }}|{{ scope.row.userPhone }}</span>\n          <span v-else>无</span>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"商品信息\" align=\"center\" prop=\"userAddress\" width=\"350\">\n        <template #default=\"scope\">\n          <div class=\"tabBox\" v-for=\"(val, i ) in scope.row.storeOrderCartInfoDOList\" :key=\"i\">\n              <div class=\"tabBox_img\">\n                  <img :src=\"val.image\" />\n              </div>\n              <span class=\"tabBox_tit\">{{ val.title + ' - ' }}{{val.spec}}</span>\n              <span class=\"tabBox_pice\">{{ '￥'+ val.price + ' x '+ val.number}}</span>\n          </div>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"实际支付\" align=\"center\">\n        <template #default=\"scope\">\n         <span v-if=\"scope.row.paid == 1\">{{ scope.row.payPrice }}</span>\n         <span v-else>未支付</span>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"支付方式\" align=\"center\">\n        <template #default=\"scope\">\n          <span v-if=\"scope.row.paid == 1\">\n           <span v-if=\"scope.row.payType=='yue'\">余额支付</span>\n           <span v-if=\"scope.row.payType=='weixin'\">微信支付</span>\n           <span v-if=\"scope.row.payType=='alipay'\">支付宝支付</span>\n          </span>\n          <span v-else></span>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"购买类型\" align=\"center\">\n        <template #default=\"scope\">\n           <span v-if=\"scope.row.orderType=='takeout'\">外卖</span>\n           <span v-if=\"scope.row.orderType=='takein'\">自取</span>\n        </template>\n      </el-table-column>\n      <el-table-column\n        label=\"预约取餐时间\"\n        align=\"center\"\n        prop=\"getTime\"\n        :formatter=\"dateFormatter\"\n        width=\"120\"\n      />\n      <el-table-column\n        label=\"支付时间\"\n        align=\"center\"\n        prop=\"payTime\"\n        :formatter=\"dateFormatter\"\n        width=\"120\"\n      />\n      <el-table-column label=\"订单状态\" align=\"center\" prop=\"statusStr\" />\n      <el-table-column\n        label=\"添加时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        :formatter=\"dateFormatter\"\n        width=\"120\"\n      />\n      <el-table-column label=\"操作\" align=\"center\" fixed=\"right\" width=\"150\">\n        <template #default=\"scope\">\n          <div class=\"flex justify-center items-center\">\n          <el-button\n            v-if = \"scope.row.statusStr == '未支付'\"\n            link\n            type=\"primary\"\n            @click=\"openForm('updateOrder', scope.row.id)\"\n            v-hasPermi=\"['order:store-order:update']\"\n          >\n            编辑\n          </el-button>\n          <el-button\n            v-if = \"scope.row.statusStr == '未发货'\"\n            link\n            type=\"primary\"\n            @click=\"openForm('orderSend', scope.row.id)\"\n            v-hasPermi=\"['order:store-order:update']\"\n          >\n            出单\n          </el-button>\n          <el-button\n            v-if = \"scope.row.statusStr == '退款中'\"\n            link\n            type=\"primary\"\n            @click=\"openForm('refundOrder', scope.row.id)\"\n            v-hasPermi=\"['order:store-order:update']\"\n          >\n            确认退款\n          </el-button>\n          <el-dropdown>\n            <el-button type=\"primary\" link><Icon icon=\"ep:d-arrow-right\" /> 更多</el-button>\n            <template #dropdown>\n              <el-dropdown-menu>\n                <el-dropdown-item v-if = \"scope.row.statusStr == '未支付'\" @click=\"handlePay(scope.row.id)\">确认付款</el-dropdown-item>\n                <el-dropdown-item @click=\"openForm('orderDetail', scope.row.id)\">订单详情</el-dropdown-item>\n                <el-dropdown-item @click=\"openForm('orderRecord', scope.row.id)\">订单记录</el-dropdown-item>\n                <el-dropdown-item @click=\"handleDelete(scope.row.id)\">删除订单</el-dropdown-item>\n                <el-dropdown-item v-if = \"scope.row.statusStr != '未支付'\" @click=\"openForm('remark', scope.row.id)\">订单备注</el-dropdown-item>\n                <el-dropdown-item v-if = \"scope.row.statusStr == '待收货'\" @click=\"handleTake(scope.row.id)\">后台收货</el-dropdown-item>\n              </el-dropdown-menu>\n            </template>\n          </el-dropdown>\n          </div>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <StoreOrderForm ref=\"formRef\" @success=\"getList\" />\n  <OrderSend ref=\"formRef1\" @success=\"getList\" />\n  <OrderSendInfo ref=\"formRef2\" @success=\"getList\" />\n  <StoreOrderRemark ref=\"formRef3\" @success=\"getList\" />\n  <OrderDetail ref=\"formRef4\" />\n  <OrderRecord ref=\"formRef5\" />\n  <StoreOrderRefund ref=\"formRef6\" @success=\"getList\" />\n</template>\n\n<script setup lang=\"ts\" name=\"StoreOrder\">\nimport { dateFormatter } from '@/utils/formatTime'\nimport download from '@/utils/download'\nimport * as StoreOrderApi from '@/api/mall/order/storeOrder'\nimport StoreOrderForm from './StoreOrderForm.vue'\nimport OrderSend from './OrderSend.vue'\nimport OrderSendInfo from './OrderSendInfo.vue'\nimport StoreOrderRemark from './StoreOrderRemark.vue'\nimport StoreOrderRefund from './StoreOrderRefund.vue'\nimport OrderDetail from './OrderDetail.vue'\nimport OrderRecord from './OrderRecord.vue'\nimport type { TabsPaneContext } from 'element-plus'\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  orderId: \"\",\n  realName: \"\",\n  userPhone: \"\",\n  createTime: [],\n  orderStatus: \"\",\n  payStatus: \"\",\n  orderType: \"\"\n\n})\nconst queryFormRef = ref() // 搜索的表单\nconst exportLoading = ref(false) // 导出的加载中\n\nconst activeName = ref('')\nconst orderStatus = ref('')\nconst payStatus = ref('')\n\nconst handleClick = (tab: TabsPaneContext, event: Event) => {\n  console.log(tab.paneName, event)\n  queryParams.orderType = tab.paneName \n  getList()\n}\n\nconst queryOrderStatus = (value) => {\n  queryParams.orderStatus = value\n  getList()\n}\nconst queryPayStatus = (value) => {\n  queryParams.payStatus = value\n  getList()\n}\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await StoreOrderApi.getStoreOrderPage(queryParams)\n    list.value = data.list\n    //console.log(\"aa:\",list.value)\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst formRef1 = ref()\nconst formRef2 = ref()\nconst formRef3 = ref()\nconst formRef4 = ref()\nconst formRef5 = ref()\nconst formRef6 = ref()\nconst openForm = (type: string, id?: number) => {\n  if (type == 'updateOrder') {\n    formRef.value.open(type, id)\n  } else if (type == 'orderSend') {\n    formRef1.value.open(type, id)\n  }else if (type == 'sendInfo') {\n    formRef2.value.open(type, id)\n  }else if (type == 'remark') {\n    formRef3.value.open(type, id)\n  }else if (type == 'orderDetail') {\n    formRef4.value.open(type, id)\n  }else if (type == 'orderRecord') {\n    formRef5.value.open(type, id)\n  }else if (type == 'refundOrder') {\n    formRef6.value.open(type, id)\n  }\n\n  \n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await StoreOrderApi.deleteStoreOrder(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 确认付款按钮操作 */\nconst handlePay = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.confirm('修改为支付状态')\n    // 发起删除\n    await StoreOrderApi.payStoreOrder(id)\n    message.success(t('common.updateSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 确认收货按钮操作 */\nconst handleTake = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.confirm('修改收货状态')\n    // 发起删除\n    await StoreOrderApi.takeStoreOrder(id)\n    message.success(t('common.updateSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 导出按钮操作 */\nconst handleExport = async () => {\n  try {\n    // 导出的二次确认\n    await message.exportConfirm()\n    // 发起导出\n    exportLoading.value = true\n    const data = await StoreOrderApi.exportStoreOrder(queryParams)\n    download.excel(data, '订单.xls')\n  } catch {\n  } finally {\n    exportLoading.value = false\n  }\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n\n<style scoped  >\n img {\n        height: 36px;\n        display: block;\n    }\n.tabBox{\n    width: 100%;\n    height: 100%;\n    display: flex;\n    align-items: center\n    }\n.tabBox_img{\n    width: 36px;\n    height: 36px;\n  }\n.tabBox_img img{\n  width: 100%;\n  height: 100%;\n}\n.tabBox_tit{\n    width :60%;\n    font-size: 12px !important;\n    margin: 0 2px 0 10px;\n    letter-spacing: 1px;\n    padding: 5px 0;\n    box-sizing: border-box;\n    text-align: left;\n  }\n  .tabBox_pice{\n    width :30%;\n    font-size: 12px !important;\n    margin: 0 2px 0 10px;\n    letter-spacing: 1px;\n    padding: 5px 0;\n    box-sizing: border-box;\n    text-align: left;\n  }\n\n</style>"
  },
  {
    "path": "yshop-drink-vue3/src/views/mall/order/storeOrder/work.vue",
    "content": "<template>\n  <ContentWrap>\n   <el-tabs v-model=\"activeName\"  @tab-click=\"handleClick\">\n     <el-tab-pane label=\"全部订单\" name=\"first\"/>\n     <el-tab-pane label=\"普通订单\" name=\"second\"/>\n   </el-tabs>\n </ContentWrap>\n <ContentWrap>\n   <!-- 搜索工作栏 -->\n   <el-form\n     class=\"-mb-15px\"\n     :model=\"queryParams\"\n     ref=\"queryFormRef\"\n     :inline=\"true\"\n     label-width=\"68px\"\n   >\n     <el-form-item label=\"订单号\" prop=\"orderId\">\n       <el-input\n         v-model=\"queryParams.orderId\"\n         placeholder=\"请输入订单号\"\n         clearable\n         @keyup.enter=\"handleQuery\"\n         class=\"!w-240px\"\n       />\n     </el-form-item>\n     <el-form-item label=\"取餐号\" prop=\"numberId\">\n       <el-input\n         v-model=\"queryParams.numberId\"\n         placeholder=\"请输入取餐号\"\n         clearable\n         @keyup.enter=\"handleQuery\"\n         class=\"!w-240px\"\n       />\n     </el-form-item>   \n     <el-form-item>\n       <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n       <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n       <el-button\n         type=\"success\"\n         plain\n         @click=\"handleExport\"\n         :loading=\"exportLoading\"\n         v-hasPermi=\"['order:store-order:export']\"\n       >\n         <Icon icon=\"ep:download\" class=\"mr-5px\" /> 导出\n       </el-button>\n     </el-form-item>\n   </el-form>\n   \n </ContentWrap>\n\n <!-- 列表 -->\n <ContentWrap>\n\n     <el-row :gutter=\"24\">\n       <el-col :span=\"6\" v-for=\"(order,k) in list\" :key=\"k\">\n         <div >\n           <el-card class=\"box-card\">\n             <template #header>\n               <div class=\"card-header\">\n                 <span>{{ order.shopName }}</span>\n               </div>\n             </template>\n             <div>取餐号:{{ order.numberId }}</div>\n             <div>下单时间:{{ formatDate(order.createTime) }}</div>\n             <div>取餐时间:{{ formatDate(order.getTime) }}</div>\n             <div>类型:{{ order.orderType == 'takeout' ? '外卖' : '自取' }}</div>\n             <div>  \n               <el-button\n                 type=\"primary\"\n                 @click=\"openForm('orderDetail', order.id)\"\n                 v-hasPermi=\"['order:store-order:update']\"\n               >详情</el-button>\n               <el-button\n                 type=\"primary\"\n                 @click=\"openForm('orderSend', order.id)\"\n                 v-hasPermi=\"['order:store-order:update']\"\n               >出单</el-button>\n               <el-button\n                 type=\"danger\"\n                 @click=\"handleDelete(order.id)\"\n                 v-hasPermi=\"['order:store-order:update']\"\n               >删除</el-button>\n             </div>\n           </el-card>\n        </div>\n        </el-col>\n        </el-row>\n </ContentWrap>\n\n <!-- 表单弹窗：添加/修改 -->\n <StoreOrderForm ref=\"formRef\" @success=\"getList\" />\n <OrderSend ref=\"formRef1\" @success=\"getList\" />\n <OrderSendInfo ref=\"formRef2\" @success=\"getList\" />\n <StoreOrderRemark ref=\"formRef3\" @success=\"getList\" />\n <OrderDetail ref=\"formRef4\" />\n <OrderRecord ref=\"formRef5\" />\n</template>\n\n<script setup lang=\"ts\" name=\"StoreOrder\">\n// import { dateFormatter } from '@/utils/formatTime'\nimport download from '@/utils/download'\nimport * as StoreOrderApi from '@/api/mall/order/storeOrder'\nimport StoreOrderForm from './StoreOrderForm.vue'\nimport OrderSend from './OrderSend.vue'\nimport OrderSendInfo from './OrderSendInfo.vue'\nimport StoreOrderRemark from './StoreOrderRemark.vue'\nimport OrderDetail from './OrderDetail.vue'\nimport OrderRecord from './OrderRecord.vue'\nimport type { TabsPaneContext } from 'element-plus'\nimport { formatDate } from '@/utils/formatTime'\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n pageNo: 1,\n pageSize: 10,\n orderId: \"\",\n realName: \"\",\n userPhone: \"\",\n createTime: [],\n orderStatus: 1,\n payStatus: \"\",\n numberId: undefined\n\n})\nconst queryFormRef = ref() // 搜索的表单\nconst exportLoading = ref(false) // 导出的加载中\n\nconst activeName = ref('first')\n\n\n\nconst handleClick = (tab: TabsPaneContext, event: Event) => {\n console.log(tab, event)\n}\n\n\n/** 查询列表 */\nconst getList = async () => {\n loading.value = true\n try {\n   const data = await StoreOrderApi.getStoreOrderPage(queryParams)\n   list.value = data.list\n   //console.log(\"aa:\",list.value)\n   total.value = data.total\n } finally {\n   loading.value = false\n }\n}\n\n\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n queryParams.pageNo = 1\n getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n queryFormRef.value.resetFields()\n handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst formRef1 = ref()\nconst formRef2 = ref()\nconst formRef3 = ref()\nconst formRef4 = ref()\nconst formRef5 = ref()\nconst openForm = (type: string, id?: number) => {\n if (type == 'updateOrder') {\n   formRef.value.open(type, id)\n } else if (type == 'orderSend') {\n   formRef1.value.open(type, id)\n }else if (type == 'sendInfo') {\n   formRef2.value.open(type, id)\n }else if (type == 'remark') {\n   formRef3.value.open(type, id)\n }else if (type == 'orderDetail') {\n   formRef4.value.open(type, id)\n }else if (type == 'orderRecord') {\n   formRef5.value.open(type, id)\n }\n\n \n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n try {\n   // 删除的二次确认\n   await message.delConfirm()\n   // 发起删除\n   await StoreOrderApi.deleteStoreOrder(id)\n   // 刷新列表\n   getList()\n } catch {}\n}\n\n\n/** 导出按钮操作 */\nconst handleExport = async () => {\n try {\n   // 导出的二次确认\n   await message.exportConfirm()\n   // 发起导出\n   exportLoading.value = true\n   const data = await StoreOrderApi.exportStoreOrder(queryParams)\n   download.excel(data, '订单.xls')\n } catch {\n } finally {\n   exportLoading.value = false\n }\n}\n\n/** 初始化 **/\nonMounted(() => {\n getList()\n})\n</script>\n\n<style scoped  >\nimg {\n       height: 36px;\n       display: block;\n   }\n.tabBox{\n   width: 100%;\n   height: 100%;\n   display: flex;\n   align-items: center\n   }\n.tabBox_img{\n   width: 36px;\n   height: 36px;\n }\n.tabBox_img img{\n width: 100%;\n height: 100%;\n}\n.tabBox_tit{\n   width :60%;\n   font-size: 12px !important;\n   margin: 0 2px 0 10px;\n   letter-spacing: 1px;\n   padding: 5px 0;\n   box-sizing: border-box;\n   text-align: left;\n }\n .tabBox_pice{\n   width :30%;\n   font-size: 12px !important;\n   margin: 0 2px 0 10px;\n   letter-spacing: 1px;\n   padding: 5px 0;\n   box-sizing: border-box;\n   text-align: left;\n }\n\n</style>"
  },
  {
    "path": "yshop-drink-vue3/src/views/mall/product/category/CategoryForm.vue",
    "content": "<template>\n  <Dialog :title=\"dialogTitle\" v-model=\"dialogVisible\">\n    <el-form\n      ref=\"formRef\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"80px\"\n      v-loading=\"formLoading\"\n    >\n      <el-form-item label=\"展示店铺\" prop=\"shopId\">\n        <el-select\n          v-model=\"formData.shopId\"\n          placeholder=\"选择店铺\"\n        >\n          <el-option\n            v-for=\"item in shopList\"\n            :key=\"item.id\"\n            :label=\"item.name\"\n            :value=\"item.id\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"分类名称\" prop=\"name\">\n        <el-input v-model=\"formData.name\" placeholder=\"请输入分类名称\" />\n      </el-form-item>\n      <el-form-item label=\"分类图片\" prop=\"picUrl\">\n        <Materials v-model=\"formData.picUrl\" num=\"1\" type=\"image\" />\n      </el-form-item>\n      <el-form-item label=\"分类排序\" prop=\"sort\">\n        <el-input-number v-model=\"formData.sort\" controls-position=\"right\" :min=\"0\" />\n      </el-form-item>\n      <el-form-item label=\"开启状态\" prop=\"status\">\n        <el-radio-group v-model=\"formData.status\">\n          <el-radio\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)\"\n            :key=\"dict.value\"\n            :label=\"dict.value\"\n          >\n            {{ dict.label }}\n          </el-radio>\n        </el-radio-group>\n      </el-form-item>\n      <el-form-item label=\"分类描述\">\n        <el-input v-model=\"formData.description\" type=\"textarea\" placeholder=\"请输入分类描述\" />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button @click=\"submitForm\" type=\"primary\" :disabled=\"formLoading\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script setup lang=\"ts\" name=\"ProductCategory\">\nimport { DICT_TYPE, getIntDictOptions } from '@/utils/dict'\nimport { CommonStatusEnum } from '@/utils/constants'\n// import { handleTree } from '@/utils/tree'\nimport * as ProductCategoryApi from '@/api/mall/product/category'\nimport * as ShopApi from '@/api/mall/store/shop'\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  shopId: null,\n  name: '',\n  picUrl: '',\n  status: CommonStatusEnum.ENABLE,\n  description: ''\n})\nconst formRules = reactive({\n  shopId: [{ required: true, message: '请选择店铺', trigger: 'blur' }],\n  //parentId: [{ required: true, message: '请选择上级分类', trigger: 'blur' }],\n  name: [{ required: true, message: '分类名称不能为空', trigger: 'blur' }],\n  //picUrl: [{ required: true, message: '分类图片不能为空', trigger: 'blur' }],\n  sort: [{ required: true, message: '分类排序不能为空', trigger: 'blur' }],\n  status: [{ required: true, message: '开启状态不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n// const categoryTree = ref<any[]>([]) // 分类树\nconst shopList = ref([])\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  getList()\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await ProductCategoryApi.getCategory(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n  // 获得分类树\n  await getTree()\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\nconst getList = async () => {\n  try {\n    const data = await ShopApi.getShopList()\n    shopList.value = data\n\n  } finally {\n    \n  }\n}\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as ProductCategoryApi.CategoryVO\n    if (formType.value === 'create') {\n      await ProductCategoryApi.createCategory(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await ProductCategoryApi.updateCategory(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    name: '',\n    picUrl: '',\n    status: CommonStatusEnum.ENABLE,\n    description: ''\n  }\n  formRef.value?.resetFields()\n}\n\n\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mall/product/category/index.vue",
    "content": "<template>\n  <!-- 搜索工作栏 -->\n  <ContentWrap>\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"分类名称\" prop=\"name\">\n        <el-input\n          v-model=\"queryParams.name\"\n          placeholder=\"请输入分类名称\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"店铺名称\" prop=\"shopName\">\n        <el-input\n          v-model=\"queryParams.shopName\"\n          placeholder=\"请输入分类名称\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n        <el-button\n          type=\"primary\"\n          plain\n          @click=\"openForm('create')\"\n          v-hasPermi=\"['product:category:create']\"\n        >\n          <Icon icon=\"ep:plus\" class=\"mr-5px\" /> 新增\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\" row-key=\"id\" default-expand-all>\n      <el-table-column label=\"分类名称\" prop=\"name\" sortable />\n      <el-table-column label=\"所属门店\" prop=\"shopName\" sortable />\n      <el-table-column label=\"分类排序\" align=\"center\" prop=\"sort\" />\n      <el-table-column label=\"开启状态\" align=\"center\" prop=\"status\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.COMMON_STATUS\" :value=\"scope.row.status\" />\n        </template>\n      </el-table-column>\n      <el-table-column\n        label=\"创建时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        width=\"180\"\n        :formatter=\"dateFormatter\"\n      />\n      <el-table-column label=\"操作\" align=\"center\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n            v-hasPermi=\"['product:category:update']\"\n          >\n            编辑\n          </el-button>\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n            v-hasPermi=\"['product:category:delete']\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <CategoryForm ref=\"formRef\" @success=\"getList\" />\n</template>\n<script setup lang=\"ts\" name=\"ProductCategory\">\nimport { DICT_TYPE } from '@/utils/dict'\nimport { handleTree } from '@/utils/tree'\nimport { dateFormatter } from '@/utils/formatTime'\nimport * as ProductCategoryApi from '@/api/mall/product/category'\nimport CategoryForm from './CategoryForm.vue'\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst list = ref<any[]>([]) // 列表的数据\nconst queryParams = reactive({\n  name: undefined,\n  shopName:null\n})\nconst queryFormRef = ref() // 搜索的表单\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await ProductCategoryApi.getCategoryList(queryParams)\n    list.value = handleTree(data, 'id', 'parentId')\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await ProductCategoryApi.deleteCategory(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mall/product/storeProduct/CateTree.vue",
    "content": "<template>\n  <div class=\"head-container\">\n    <el-input v-model=\"deptName\" class=\"mb-20px\" clearable placeholder=\"请输入分类名称\">\n      <template #prefix>\n        <Icon icon=\"ep:search\" />\n      </template>\n    </el-input>\n  </div>\n  <div class=\"head-container\">\n    <el-tree\n      ref=\"treeRef\"\n      :data=\"deptList\"\n      :expand-on-click-node=\"false\"\n      :filter-node-method=\"filterNode\"\n      :props=\"defaultProps\"\n      default-expand-all\n      highlight-current\n      node-key=\"id\"\n      @node-click=\"handleNodeClick\"\n    />\n  </div>\n</template>\n\n<script lang=\"ts\" name=\"StoreProductCateTree\" setup>\nimport { ElTree } from 'element-plus'\nimport * as ProductCategoryApi from '@/api/mall/product/category'\nimport { defaultProps, handleTree } from '@/utils/tree'\n\nconst deptName = ref('')\nconst deptList = ref<Tree[]>([]) // 树形结构\nconst treeRef = ref<InstanceType<typeof ElTree>>()\n\n/** 获得分类树 */\nconst getTree = async () => {\n  const res = await ProductCategoryApi.getCategoryList('')\n  deptList.value = []\n  deptList.value.push(...handleTree(res))\n}\n\n/** 基于名字过滤 */\nconst filterNode = (name: string, data: Tree) => {\n  if (!name) return true\n  return data.name.includes(name)\n}\n\n/** 处理分类被点击 */\nconst handleNodeClick = async (row: { [key: string]: any }) => {\n  emits('node-click', row)\n}\nconst emits = defineEmits(['node-click'])\n\n/** 初始化 */\nonMounted(async () => {\n  await getTree()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mall/product/storeProduct/StoreProductForm.vue",
    "content": "<template>\n  <Dialog :title=\"dialogTitle\" append-to-body=\"true\" v-model=\"dialogVisible\" width=\"70%\">\n    <el-form\n      ref=\"formRef\"\n      :model=\"formValidate\"\n      :rules=\"formRules\"\n      label-width=\"100px\"\n      v-loading=\"formLoading\"\n    >\n    <el-tabs v-model=\"activeName\" type=\"border-card\" @tab-click=\"handleClick\">\n      <el-tab-pane label=\"基本信息\" name=\"one\">\n        <el-form-item label=\"展示店铺\" prop=\"shopId\">\n          <el-select\n            v-model=\"formValidate.shopId\"\n            placeholder=\"选择店铺\"\n            @change=\"selectShop\"\n          >\n            <el-option\n              v-for=\"item in shopList\"\n              :key=\"item.id\"\n              :label=\"item.name\"\n              :value=\"item.id\"\n            />\n          </el-select>\n        </el-form-item>\n        <el-form-item label=\"商品名称\" prop=\"store_name\">\n          <el-input v-model=\"formValidate.store_name\" class=\"input-width\" placeholder=\"请输入商品名称\" />\n        </el-form-item>\n         <el-form-item label=\"商品分类\" prop=\"cate_id\">\n          <el-select\n            v-model=\"formValidate.cate_id\"\n            placeholder=\"选择分类\"\n            @change=\"selectShop\"\n          >\n            <el-option\n              v-for=\"item in categoryTree\"\n              :key=\"item.id\"\n              :label=\"item.name\"\n              :value=\"item.id\"\n            />\n          </el-select>\n        </el-form-item>\n         <el-form-item label=\"关键字\" prop=\"keyword\">\n          <el-input v-model=\"formValidate.keyword\" class=\"input-width\" placeholder=\"请输入关键字\" />\n        </el-form-item>\n         <el-form-item label=\"单位名\" prop=\"unit_name\">\n          <el-input v-model=\"formValidate.unit_name\" class=\"input-width\" placeholder=\"请输入单位名\" />\n        </el-form-item>\n        <el-form-item label=\"商品价格\" prop=\"price\">\n          <el-input v-model=\"formValidate.price\" class=\"input-width\" placeholder=\"请输入商品价格\" />\n        </el-form-item>\n        <el-form-item label=\"市场价\" prop=\"otPrice\">\n          <el-input v-model=\"formValidate.otPrice\" class=\"input-width\" placeholder=\"请输入市场价\" />\n        </el-form-item>\n         <el-form-item label=\"库存\" prop=\"stock\">\n          <el-input v-model=\"formValidate.stock\" class=\"input-width\" placeholder=\"请输入库存\" />\n        </el-form-item>\n        <el-form-item label=\"封面图\" prop=\"image\">\n          <Materials v-model=\"formValidate.image\" num=\"1\" type=\"image\" />\n        </el-form-item>\n       <el-form-item label=\"轮播图\" prop=\"slider_image\">\n        <Materials v-model=\"formValidate.slider_image\" num=\"5\" type=\"image\" />\n        </el-form-item>\n        <el-form-item label=\"商品状态\" prop=\"is_show\">\n          <el-radio-group v-model=\"formValidate.is_show\">\n            <el-radio :label=\"1\">上架</el-radio>\n            <el-radio :label=\"0\">下架</el-radio>\n          </el-radio-group>\n        </el-form-item>\n        <el-form-item label=\"商品简介\" prop=\"store_info\">\n          <el-input type=\"textarea\" rows=\"5\"  v-model=\"formValidate.store_info\" placeholder=\"请输入商品简介\" />\n        </el-form-item>\n      </el-tab-pane>\n      <el-tab-pane label=\"商品规格\" name=\"two\">\n        <el-form-item label=\"商品规格\" prop=\"spec_type\">\n          <el-radio-group v-model=\"formValidate.spec_type\" @change=\"changeSpec\">\n            <el-radio :label=\"0\">单规格</el-radio>\n            <el-radio :label=\"1\">多规格</el-radio>\n          </el-radio-group>\n        </el-form-item>\n           <!-- 多规格添加-->\n          <el-col :span=\"24\" v-if=\"formValidate.spec_type === 1\" class=\"noForm\">\n            <el-col :span=\"24\">\n              <el-form-item label=\"选择规格：\" prop=\"\">\n                <div  class=\"acea-row row-middle\">\n                  <el-select v-model=\"formValidate.selectRule\" >\n                    <el-option v-for=\"(item, index) in ruleList\" :value=\"item.ruleName\" :key=\"index\">{{ item.ruleName }}</el-option>\n                  </el-select>\n                  <el-button type=\"primary\" class=\"mr20\" @click=\"confirm\">确认</el-button>\n                </div>\n              </el-form-item>\n            </el-col>\n            <el-col :span=\"24\">\n              <el-form-item v-if=\"attrs.length!==0\">\n                <div  v-for=\"(item, index) in attrs\" :key=\"index\" style=\"width:100%;\">\n                  <div class=\"acea-row row-middle\"><span class=\"mr5\">{{item.value}}</span>\n                    <Icon icon=\"ep:circle-close\" @click=\"handleRemoveRole(index)\" />\n                  </div>\n                  <div class=\"rulesBox\">\n                    <el-tag type=\"dot\" closable color=\"primary\" v-for=\"(j, indexn) in item.detail\" :key=\"indexn\" :name=\"j\" class=\"mr20\" @close=\"handleRemove2(item.detail,indexn)\">{{j}}</el-tag>\n                    <el-input placeholder=\"请输入属性名称\" v-model=\"item.detail.attrsVal\"\n                              style=\"width: 150px\">\n                      <template #append>\n                        <el-button  type=\"primary\" @click=\"createAttr(item.detail.attrsVal,index)\">添加</el-button>\n                      </template>\n                    </el-input>\n                  </div>\n                </div>\n              </el-form-item>\n            </el-col>\n            <el-col :span=\"24\" v-if=\"createBnt\">\n              <el-form-item>\n                <el-button type=\"primary\" icon=\"md-add\" @click=\"addBtn\" class=\"mr15\">添加新规格</el-button>\n                <el-button type=\"success\" @click=\"generate\">立即生成</el-button>\n              </el-form-item>\n            </el-col>\n            <el-col :span=\"24\" v-if=\"showIput\">\n              <el-col  :xl=\"6\" :lg=\"9\" :md=\"10\" :sm=\"24\" :xs=\"24\" >\n                <el-form-item label=\"规格：\">\n                  <el-input  placeholder=\"请输入规格\" v-model=\"formDynamic.attrsName\"  />\n                </el-form-item>\n              </el-col>\n              <el-col  :xl=\"6\" :lg=\"9\" :md=\"10\" :sm=\"24\" :xs=\"24\">\n                <el-form-item label=\"规格值：\">\n                  <el-input v-model=\"formDynamic.attrsVal\" placeholder=\"请输入规格值\"  />\n                </el-form-item>\n              </el-col>\n              <el-col :xl=\"6\" :lg=\"5\" :md=\"10\" :sm=\"24\" :xs=\"24\" >\n\n                  <el-button type=\"primary\" @click=\"createAttrName\">确定</el-button>\n                  <el-button type=\"danger\" @click=\"offAttrName\" >取消</el-button>\n\n              </el-col>\n            </el-col>\n            <!-- 多规格设置-->\n            <el-col :xl=\"24\" :lg=\"24\" :md=\"24\" :sm=\"24\" :xs=\"24\" v-if=\"manyFormValidate.length\">\n              <!-- 多规格表格-->\n              <el-col :span=\"24\">\n                <el-form-item label=\"商品属性：\" class=\"labeltop\">\n                  <el-table :data=\"manyFormValidate\" size=\"small\" style=\"width: 90%;\">\n                    <el-table-column type=\"myindex\" v-for=\"(item,index) in formValidate.header\" :key=\"index\" :label=\"item.title\" :property=\"item.slot\" align=\"center\">\n                      <template #default=\"scope\">\n                        <div v-if=\"item.slot == 'pic'\" align=\"center\">\n                           <Materials v-model=\"scope.row[item.slot]\" num=\"1\" type=\"image\" :width=\"60\" :height=\"60\" />\n                          <!-- <single-pic v-model=\"scope.row[scope.column.property]\" type=\"image\" :num=\"1\" :width=\"60\" :height=\"60\" /> -->\n                        </div>\n                        <div v-else-if=\"item.slot.indexOf('value') != -1\" align=\"center\">\n                         {{ scope.row[item.slot] }}\n                        </div>\n                        <div v-else-if=\"item.slot == 'action'\" align=\"center\" >\n                          <a align=\"center\">无</a>\n                        </div>\n                        <div v-else align=\"center\">\n                          <el-input  v-model=\"scope.row[item.slot]\" align=\"center\" />\n                        </div>\n                      </template>\n                    </el-table-column>\n\n                  </el-table>\n                </el-form-item>\n              </el-col>\n            </el-col>\n          </el-col>\n          <!-- 单规格表格-->\n          <el-col :xl=\"23\" :lg=\"24\" :md=\"24\" :sm=\"24\" :xs=\"24\" v-if=\"formValidate.spec_type === 0\">\n            <el-form-item >\n              <el-table :data=\"oneFormValidate\"  size=\"small\" style=\"width: 90%;\">\n                <el-table-column prop=\"pic\" label=\"图片\" align=\"center\">\n                  <template #default=\"scope\">\n                    <Materials v-model=\"scope.row.pic\" num=\"1\" type=\"image\" :width=\"60\" :height=\"60\" />\n                  </template>\n                </el-table-column>\n                <el-table-column prop=\"price\" label=\"售价\" align=\"center\">\n                  <template #default=\"scope\">\n                    <el-input type=\"text\" v-model=\"scope.row.price\"/>\n                  </template>\n                </el-table-column>\n                <el-table-column prop=\"cost\" label=\"成本价\" align=\"center\">\n                  <template #default=\"scope\">\n                    <el-input type=\"text\" v-model=\"scope.row.cost\"/>\n                  </template>\n                </el-table-column>\n                <el-table-column prop=\"ot_price\" label=\"原价\" align=\"center\">\n                  <template #default=\"scope\">\n                    <el-input type=\"text\" v-model=\"scope.row.ot_price\"/>\n                  </template>\n                </el-table-column>\n                <el-table-column prop=\"stock\" label=\"库存\" align=\"center\">\n                  <template #default=\"scope\">\n                    <el-input type=\"text\" v-model=\"scope.row.stock\" maxlength=\"7\"/>\n                  </template>\n                </el-table-column>\n                <el-table-column prop=\"bar_code\" label=\"商品编号\" align=\"center\">\n                  <template #default=\"scope\">\n                    <el-input type=\"text\" v-model=\"scope.row.bar_code\"/>\n                  </template>\n                </el-table-column>\n                <el-table-column prop=\"weight\" label=\"重量（KG）\" align=\"center\">\n                  <template #default=\"scope\">\n                    <el-input type=\"text\" v-model=\"scope.row.weight\"/>\n                  </template>\n                </el-table-column>\n                <el-table-column prop=\"volume\" label=\"体积(m³)\" align=\"center\">\n                  <template #default=\"scope\">\n                    <el-input type=\"text\" v-model=\"scope.row.volume\"/>\n                  </template>\n                </el-table-column>\n                <!-- <el-table-column prop=\"volume\" label=\"所需兑换积分\" align=\"center\">\n                  <template #default=\"scope\">\n                    <el-input type=\"text\" v-model=\"scope.row.integral\"/>\n                  </template>\n                </el-table-column> -->\n              </el-table>\n            </el-form-item>\n          </el-col>\n      </el-tab-pane>\n      <el-tab-pane label=\"商品详情\" name=\"three\"> \n        <el-form-item label=\"产品描述\">\n           <vue-ueditor-wrap v-model=\"formValidate.description\" :config=\"myConfig\" @before-init=\"addCustomDialog\"    style=\"width: 90%;\" />\n        </el-form-item>\n      </el-tab-pane>\n      <el-tab-pane label=\"营销设置\" name=\"four\">\n        <el-form-item label=\"获得积分\" prop=\"give_integral\">\n          <el-input v-model=\"formValidate.give_integral\" class=\"input-width\" placeholder=\"请输入获得积分\" />\n        </el-form-item>\n      </el-tab-pane>\n    </el-tabs>\n    </el-form>\n    <template #footer>\n      <el-button v-if=\"activeName !== 'one'\" @click=\"upTab\">上一步</el-button>\n      <el-button type=\"primary\"  v-if=\"activeName !== 'four'\" @click=\"downTab\">下一步</el-button>\n      <el-button v-if=\"activeName == 'four'\" @click=\"submitForm\" type=\"primary\" :disabled=\"formLoading\">保 存</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script setup lang=\"ts\">\nimport * as StoreProductApi from '@/api/mall/product/product'\nimport * as ProductCategoryApi from '@/api/mall/product/category'\nimport type { TabsPaneContext } from 'element-plus'\nimport * as ShopApi from '@/api/mall/store/shop'\n\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  image: undefined,\n  sliderImage: undefined,\n  storeName: undefined,\n  storeInfo: undefined,\n  keyword: undefined,\n  barCode: undefined,\n  cateId: undefined,\n  price: undefined,\n  vipPrice: undefined,\n  otPrice: undefined,\n  postage: undefined,\n  unitName: undefined,\n  sort: undefined,\n  sales: undefined,\n  stock: undefined,\n  isShow: 1,\n  isHot: undefined,\n  isBenefit: undefined,\n  isBest: undefined,\n  isNew: 0,\n  description: undefined,\n  isPostage: undefined,\n  merUse: undefined,\n  giveIntegral: undefined,\n  cost: undefined,\n  isSeckill: undefined,\n  isBargain: undefined,\n  isGood: undefined,\n  ficti: undefined,\n  browse: undefined,\n  codePath: undefined,\n  isSub: undefined,\n  tempId: undefined,\n  specType: 0,\n  isIntegral: undefined,\n  integral: undefined\n})\nconst formValidate = ref({\n    shopId: null,\n    imageArr:[],\n    sliderImageArr: [],\n    store_name: '',\n    cate_id: 0,\n    keyword: '',\n    unit_name: '',\n    store_info: '',\n    image: '',\n    slider_image: [],\n    description: '',\n    ficti: 0,\n    give_integral: 0,\n    sort: 0,\n    is_show: 1,\n    price: 0,\n    otPrice: 0,\n    stock: 0,\n    is_new: 0,\n    postage: 0,\n    is_postage: 0,\n    is_sub: 0,\n    is_integral: 0,\n    id: 0,\n    spec_type: 0,\n    temp_id: '',\n    attrs: [],\n    items: [\n      {\n        pic: '',\n        price: 0,\n        cost: 0,\n        ot_price: 0,\n        stock: 0,\n        bar_code: '',\n        integral:0\n      }\n    ],\n    header: [],\n    selectRule: ''\n})\n\nconst manyFormValidate = ref([])\nconst oneFormValidate = ref([\n  {\n    imageArr: [],\n    pic: '',\n    price: 2,\n    cost: 0,\n    ot_price: 0,\n    stock: 0,\n    seckill_stock: 0,\n    seckill_price: 0,\n    pink_stock: 0,\n    pink_price: 0,\n    bar_code: '',\n    weight: 0,\n    volume: 0,\n    brokerage: 0,\n    brokerage_two: 0,\n    integral: 0\n  }\n])\n\nconst formRules = reactive({\n  shopId: [{ required: true, message: '请选择店铺', trigger: 'blur' }],\n  image: [{ required: true, message: '商品图片不能为空', trigger: 'blur' }],\n  slider_image: [{ required: true, message: '轮播图不能为空', trigger: 'blur' }],\n  store_name: [{ required: true, message: '商品名称不能为空', trigger: 'blur' }],\n  cate_id: [{ required: true, message: '分类id不能为空', trigger: 'blur' }],\n  price: [{ required: true, message: '商品价格不能为空', trigger: 'blur' }]\n})\nconst ruleList = ref([])\nconst attrs = ref([])\n// 规格数据\nconst formDynamic = reactive({\n  attrsName: '',\n  attrsVal: ''\n})\nconst createBnt = ref(false)\nconst showIput = ref(false)\nconst postageSet = ref(false) //false -固定  true 运费模板\nconst templateList = ref([])\n// 批量设置表格data\nconst oneFormBatch = ref([\n  {\n    pic: '',\n    price: 0,\n    cost: 0,\n    ot_price: 0,\n    stock: 0,\n    bar_code: '',\n    seckill_stock: 0,\n    seckill_price: 0,\n    pink_stock: 0,\n    pink_price: 0,\n    weight: 0,\n    volume: 0,\n    integral:0\n  }\n])\n\n\nconst formRef = ref() // 表单 Ref\nconst categoryTree = ref<any[]>([]) // 分类树\n\nconst myConfig = reactive( {\n      autoHeightEnabled: false, // 编辑器不自动被内容撑高\n      initialFrameHeight: 500, // 初始容器高度\n      initialFrameWidth: '100%', // 初始容器宽度\n      UEDITOR_HOME_URL: '/UEditor/',\n      serverUrl: '',\n      zIndex: 9999,\n      toolbars: [\n        [\n          'undo',\n          'redo',\n          '|',\n          'bold',\n          'italic',\n          'underline',\n          'fontborder',\n          'strikethrough',\n          'superscript',\n          'subscript',\n          'removeformat',\n          'formatmatch',\n          'autotypeset',\n          'blockquote',\n          'pasteplain',\n          '|',\n          'forecolor',\n          'backcolor',\n          'insertorderedlist',\n          'insertunorderedlist',\n          'selectall',\n          'cleardoc',\n          '|',\n          'rowspacingtop',\n          'rowspacingbottom',\n          'lineheight',\n          '|',\n          'customstyle',\n          'paragraph',\n          'fontfamily',\n          'fontsize',\n          '|',\n          'directionalityltr',\n          'directionalityrtl',\n          'indent',\n          '|',\n          'justifyleft',\n          'justifycenter',\n          'justifyright',\n          'justifyjustify',\n          '|',\n          'touppercase',\n          'tolowercase',\n          '|',\n          'imagenone',\n          'imageleft',\n          'imageright',\n          'imagecenter',\n        ],\n      ],\n})\nconst activeName = ref('one')\nconst shopList = ref([])\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  activeName.value = 'one'\n  getList()\n  \n  resetForm()\n\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = false\n  } else {\n    id = 0\n  }\n  getInfo(id)\n\n \n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n\nconst selectShop = (val) => {\n  getTree(val)\n}\n\n\nconst getList = async () => {\n  try {\n    const data = await ShopApi.getShopList()\n    shopList.value = data\n\n  } finally {\n    \n  }\n}\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return \n  formRef.value.validate((valid, fields) => {\n  if (valid) {\n    console.log(fields)\n  } else {\n    return message.warning('请添加基本信息')\n  }\n  })\n\n\n  // 提交请求\n  formLoading.value = true\n  try {\n     if(formValidate.value.spec_type ===0 ){\n        formValidate.value.attrs = oneFormValidate.value;\n        formValidate.value.header = [];\n        formValidate.value.items = [];\n      }else{\n        formValidate.value.items = attrs.value;\n        formValidate.value.attrs = manyFormValidate.value;\n      }\n      if(formValidate.value.spec_type === 1 && manyFormValidate.value.length===0){\n        message.warning('请点击生成规格！');\n      }\n      await StoreProductApi.createStoreProduct(formValidate.value)\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n\nconst handleClick = (tab: TabsPaneContext, event: Event) => {\n  console.log(tab.paneName, event)\n}\nconst upTab = () => {\n  if (activeName.value == 'two') {\n    activeName.value = 'one'\n    return\n  }\n  if (activeName.value == 'three') {\n    activeName.value = 'two'\n    return\n  }\n  if (activeName.value == 'four') {\n    activeName.value = 'three'\n    return\n  }\n\n\n}\nconst downTab = () => {\nif (activeName.value == 'one') {\n    activeName.value = 'two'\n    return\n  }\n  if (activeName.value == 'two') {\n    activeName.value = 'three'\n    return\n  }\n  if (activeName.value == 'three') {\n    activeName.value = 'four'\n    return\n  }\n\n}\n\n  \n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    image: undefined,\n    sliderImage: undefined,\n    storeName: undefined,\n    storeInfo: undefined,\n    keyword: undefined,\n    barCode: undefined,\n    cateId: undefined,\n    price: undefined,\n    vipPrice: undefined,\n    otPrice: undefined,\n    postage: undefined,\n    unitName: undefined,\n    sort: undefined,\n    sales: undefined,\n    stock: undefined,\n    isShow: undefined,\n    isHot: undefined,\n    isBenefit: undefined,\n    isBest: undefined,\n    isNew: undefined,\n    description: undefined,\n    isPostage: undefined,\n    merUse: undefined,\n    giveIntegral: undefined,\n    cost: undefined,\n    isSeckill: undefined,\n    isBargain: undefined,\n    isGood: undefined,\n    ficti: undefined,\n    browse: undefined,\n    codePath: undefined,\n    isSub: undefined,\n    tempId: undefined,\n    specType: undefined,\n    isIntegral: undefined,\n    integral: undefined\n  }\n  formValidate.value = {\n    imageArr: [],\n    sliderImageArr: [],\n    store_name: '',\n    cate_id: '',\n    keyword: '',\n    unit_name: '',\n    store_info: '',\n    image: '',\n    slider_image: [],\n    description: '',\n    ficti: 0,\n    give_integral: 0,\n    sort: 0,\n    is_show: 1,\n    price: 0,\n    otPrice: 0,\n    stock: 0,\n    is_new: 0,\n    postage: 0,\n    is_postage: 0,\n    is_sub: 0,\n    is_integral: 0,\n    id: 0,\n    spec_type: 0,\n    temp_id: '',\n    attrs: [],\n    items: [\n      {\n        pic: '',\n        price: 0,\n        cost: 0,\n        ot_price: 0,\n        stock: 0,\n        bar_code: '',\n        integral: 0\n      }\n    ],\n    header: [],\n    selectRule: ''\n  }\n  attrs.value = []\n\n  oneFormValidate.value = [\n  {\n    imageArr: [],\n    pic: '',\n    price: 2,\n    cost: 0,\n    ot_price: 0,\n    stock: 0,\n    seckill_stock: 0,\n    seckill_price: 0,\n    pink_stock: 0,\n    pink_price: 0,\n    bar_code: '',\n    weight: 0,\n    volume: 0,\n    brokerage: 0,\n    brokerage_two: 0,\n    integral: 0\n  }\n]\n\n  formRef.value?.resetFields()\n}\n\n // 详情\nconst  getInfo  = (id) => {\n     // let that = this;\n      StoreProductApi.getStoreProductInfo(id).then(async res => {\n      let data = res.productInfo;\n        console.log('data:', data)\n      postageSet.value = false\n      if(data){\n       // let cate_id = parseInt(data.cate_id) || 0;\n        if(data.temp_id > 0) postageSet.value = true\n        attrs.value = data.items || [];\n        formValidate.value = data;\n        formValidate.value.cate_id = Number(data.cate_id);\n        if(formValidate.value.shopId){\n           console.log('shopId:',formValidate.value.shopId)\n            await getTree(formValidate.value.shopId)\n          }\n        oneFormValidate.value = [data.attr];\n        formValidate.value.header = [];\n        generate();\n        manyFormValidate.value = data.attrs;\n        console.log('data2:',formValidate.value.spec_type)\n        if(data.spec_type === 0){\n          manyFormValidate.value = [];\n        }else {\n          createBnt.value = true;\n          oneFormValidate.value = [\n            {\n              imageArr: [],\n              pic: '',\n              price: 0,\n              cost: 0,\n              ot_price: 0,\n              stock: 0,\n              seckill_stock: 0,\n              seckill_price: 0,\n              pink_stock: 0,\n              pink_price: 0,\n              bar_code: '',\n              weight:0,\n              volume:0,\n              brokerage:0,\n              brokerage_two:0,\n              integral:0\n            }\n          ]\n\n           console.log('spec_type2:',formValidate.value.spec_type)\n        }\n      }\n\n      ruleList.value = res.ruleList;\n      templateList.value = res.tempList;\n\n    }).catch(res => {\n      console.log('err:'+res)\n    })\n}\n\nconst route = useRoute()\n // 立即生成\nconst generate = () => {\n  StoreProductApi.isFormatAttr(formValidate.value.id, { attrs: attrs.value }).then(res => {\n    manyFormValidate.value = res.value;\n    let headerdel = {\n      title: '操作',\n      slot: 'action',\n      fixed: 'right',\n      width: 220\n    };\n    res.header.push(headerdel);\n    formValidate.value.header = res.header;\n    //this.formValidate.attrs = res.attr;\n    let header = res.header;\n    header.pop();\n    if (!route.params.id && formValidate.value.spec_type === 1) {\n      manyFormValidate.value.map((item) => {\n        item.pic = formValidate.value.image\n      });\n      oneFormBatch.value[0].pic = formValidate.value.image;\n    }\n  }).catch(res => {\n   console.log('err:'+res)\n  })\n}\n\n/** 获得分类树 */\nconst getTree = async (val) => {\n  const data =  await ProductCategoryApi.getCategoryList({shopId: val})\n // const tree = handleTree3(data, 'id', 'parentId')\n // console.log('tree:',tree)\n  //const menu = { id: 0, name: '顶级分类', children: tree }\n  categoryTree.value = data\n}\n\nconst addCustomDialog  = () => {\n      window.UE.registerUI('yshop', function (editor, uiName) {\n        let dialog = new window.UE.ui.Dialog({\n          iframeUrl: '/yshop/materia/index',\n          editor: editor,\n          name: uiName,\n          title: '上传图片',\n          cssRules: 'width:1200px;height:650px;padding:20px;'\n        });\n        this.dialog = dialog;\n\n        var btn = new window.UE.ui.Button({\n          name: 'dialog-button',\n          title: '上传图片',\n          cssRules: `background-image: url(../../../assets/imgs/icons.png);background-position: -726px -77px;`,\n          onclick: function () {\n            dialog.render();\n            dialog.open();\n          }\n        });\n\n        return btn;\n      }, 37);\n}\n\n// 改变规格\nconst changeSpec = () => {\n}\n\nconst confirm = () => {\n  createBnt.value = true;\n  if (formValidate.value.selectRule.trim().length <= 0) {\n    message.error('请选择属性')\n  }\n  ruleList.value.forEach(function (item, index) {\n     console.log(index)\n    if (item.ruleName === formValidate.value.selectRule) {\n      attrs.value = item.ruleValue;\n    }\n  })\n}\n\n// 删除规格\nconst handleRemoveRole = (index) => {\n  attrs.value.splice(index, 1);\n  manyFormValidate.value.splice(index, 1);\n}\n\n// 删除属性\nconst handleRemove2 = (item, index) => {\n  item.splice(index, 1);\n}\n\n// 添加规则名称\nconst createAttrName = () =>{\n  if (formDynamic.attrsName && formDynamic.attrsVal) {\n    let data = {\n      value: formDynamic.attrsName,\n      detail: [\n        formDynamic.attrsVal\n      ]\n    };\n    attrs.value.push(data);\n    var hash = {};\n    attrs.value = attrs.value.reduce(function (item, next) {\n      hash[next.value] ? '' : hash[next.value] = true && item.push(next);\n      return item\n    }, [])\n    clearAttr();\n    showIput.value = false;\n    createBnt.value = true;\n  } else {\n     message.warning('请添加完整的规格！')\n  }\n}\n// 添加属性\nconst createAttr = (num, idx) => {\n  if (num) {\n    attrs.value[idx].detail.push(num);\n    var hash = {};\n    attrs.value[idx].detail = attrs.value[idx].detail.reduce(function (item, next) {\n      hash[next] ? '' : hash[next] = true && item.push(next);\n      return item\n    }, [])\n  } else {\n    message.warning('请添加属性！')\n  }\n}\n// 取消\nconst offAttrName = () => {\n  showIput.value = false\n  createBnt.value = true\n}\n// 删除表格中的属性\nconst delAttrTable  = (index) => {\n  manyFormValidate.value.splice(index, 1)\n}\n// 添加按钮\nconst addBtn =  () => {\n  clearAttr()\n  createBnt.value = false\n  showIput.value = true\n}\n\nconst clearAttr = () => {\n  formDynamic.attrsName = ''\n  formDynamic.attrsVal = ''\n}\n\n\n</script>\n\n<style scoped>\n  .input-width {\n    width: 40%;\n  }\n\n  .mb15 {\n    margin-bottom: 15px !important;\n}\n.mb5 {\n    margin-bottom: 5px !important;\n}\n.mr20 {\n    margin-right: 20px !important;\n}\n\n.mr5 {\n    margin-right: 5px !important;\n}\n.mr15 {\n    margin-right: 15px !important;\n}\n.ml95 {\n    margin-left: 95px !important;\n}\n.mt10{\n    margin-top: 10px;\n}\n\n.acea-row {\n    display: -webkit-box;\n    display: -moz-box;\n    display: -webkit-flex;\n    display: -ms-flexbox;\n    display: flex;\n    -webkit-box-lines: multiple;\n    -moz-box-lines: multiple;\n    -o-box-lines: multiple;\n    -webkit-flex-wrap: wrap;\n    -ms-flex-wrap: wrap;\n    flex-wrap: wrap;\n    /* 辅助类 */\n}\n.acea-row.row-middle {\n    -webkit-box-align: center;\n    -moz-box-align: center;\n    -o-box-align: center;\n    -ms-flex-align: center;\n    -webkit-align-items: center;\n    align-items: center;\n}\n\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mall/product/storeProduct/index.vue",
    "content": "<template>\n  <el-row :gutter=\"20\">\n  <!-- 左侧部门树 -->\n  <el-col :span=\"4\" :xs=\"24\">\n    <ContentWrap class=\"h-1/1\">\n      <CateTree @node-click=\"handleDeptNodeClick\" />\n    </ContentWrap>\n  </el-col>\n  <el-col :span=\"20\" :xs=\"24\">\n    <ContentWrap>\n      <!-- 搜索工作栏 -->\n      <el-form\n        class=\"-mb-15px\"\n        :model=\"queryParams\"\n        ref=\"queryFormRef\"\n        :inline=\"true\"\n        label-width=\"68px\"\n      >\n        <el-form-item label=\"商品名称\" prop=\"storeName\">\n          <el-input\n            v-model=\"queryParams.storeName\"\n            placeholder=\"请输入商品名称\"\n            clearable\n            @keyup.enter=\"handleQuery\"\n            class=\"!w-240px\"\n          />\n        </el-form-item>\n        <el-form-item label=\"店铺名称\" prop=\"shopName\">\n        <el-input\n          v-model=\"queryParams.shopName\"\n          placeholder=\"请输入分类名称\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n        <el-form-item>\n          <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n          <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n          <!-- <router-link :to=\"{ path: '/product/store-product/product-crreate' }\"> -->\n          <el-button\n            type=\"primary\"\n            plain\n            @click=\"openForm('create')\"\n            v-hasPermi=\"['shop:store-product:create']\"\n          >\n            <Icon icon=\"ep:plus\" class=\"mr-5px\" /> 新增\n          </el-button>\n        </el-form-item>\n      </el-form>\n    </ContentWrap>\n\n    <!-- 列表 -->\n    <ContentWrap>\n      <el-tabs v-model=\"activeName\" type=\"card\" @tab-click=\"handleClick\">\n        <el-tab-pane v-for=\"item in tableTabs\" :label=\"item.title\" :key=\"item.name\" :name=\"item.name\">\n          <el-table v-loading=\"loading\" :data=\"list\">\n            <el-table-column label=\"id\" align=\"center\" width=\"50\" prop=\"id\" />\n            <el-table-column label=\"商品图片\" align=\"center\" prop=\"image\" >\n              <template #default=\"scope\">\n                <el-image\n                  style=\"width: 100px; height: 100px\"\n                  :src=\"scope.row.image\"\n                  :zoom-rate=\"1.2\"\n                  :preview-src-list=\"[scope.row.image]\"\n                  :initial-index=\"0\"\n                  :z-index=\"900\"\n                  :hide-on-click-modal=\"true\"\n                  :preview-teleported=\"true\"\n                  fit=\"cover\"\n                />\n              </template>\n            </el-table-column>\n            <el-table-column label=\"所属门店\" align=\"center\" prop=\"shopName\" />\n            <el-table-column label=\"商品名称\" align=\"center\" prop=\"storeName\" />\n            <el-table-column label=\"商品价格\" align=\"center\" prop=\"price\" />\n            <el-table-column label=\"销量\" align=\"center\"  width=\"80\" prop=\"sales\" />\n            <el-table-column label=\"库存\" align=\"center\"  width=\"80\" prop=\"stock\" />\n            <el-table-column label=\"状态\" align=\"center\" prop=\"isShow\" >\n              <template #default=\"scope\">\n                <div @click=\"onSale(scope.row.id,scope.row.isShow)\">\n                  <el-tag v-if=\"scope.row.isShow === 1\" style=\"cursor: pointer\" type=\"success\">已上架</el-tag>\n                  <el-tag v-else style=\"cursor: pointer\" type=\"danger\">已下架</el-tag>\n                </div>\n              </template>\n            </el-table-column>\n            <el-table-column\n              label=\"添加时间\"\n              align=\"center\"\n              prop=\"createTime\"\n              :formatter=\"dateFormatter\"\n            />\n            <el-table-column label=\"操作\" align=\"center\">\n              <template #default=\"scope\">\n                <el-button\n                  link\n                  type=\"primary\"\n                  @click=\"openForm('update', scope.row.id)\"\n                  v-hasPermi=\"['shop:store-product:update']\"\n                >\n                  编辑\n                </el-button>\n                <el-button\n                  link\n                  type=\"danger\"\n                  @click=\"handleDelete(scope.row.id)\"\n                  v-hasPermi=\"['shop:store-product:delete']\"\n                >\n                  删除\n                </el-button>\n              </template>\n            </el-table-column>\n          </el-table>\n        </el-tab-pane>\n      </el-tabs>\n      <!-- 分页 -->\n      <Pagination\n        :total=\"total\"\n        v-model:page=\"queryParams.pageNo\"\n        v-model:limit=\"queryParams.pageSize\"\n        @pagination=\"getList\"\n      />\n    </ContentWrap>\n   </el-col>\n  </el-row>\n  <!-- 表单弹窗：添加/修改 -->\n  <StoreProductForm ref=\"formRef\" @success=\"getList\" />\n</template>\n\n<script setup lang=\"ts\" name=\"StoreProduct\">\nimport { dateFormatter } from '@/utils/formatTime'\nimport download from '@/utils/download'\nimport * as StoreProductApi from '@/api/mall/product/product'\nimport StoreProductForm from './StoreProductForm.vue'\nimport CateTree from './CateTree.vue'\nimport type { TabsPaneContext } from 'element-plus'\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  storeName: null,\n  isPostage: null,\n  isShow: 1,\n  stock: 1,\n  cateId: null,\n  shopName: null\n})\nconst queryFormRef = ref() // 搜索的表单\nconst exportLoading = ref(false) // 导出的加载中\n\nconst activeName = ref('1')\nconst tableTabs = ref([\n    {\n    title: '出售中产品',\n    name: '1',\n  },\n   {\n    title: '待上架产品',\n    name: '2',\n  },\n   {\n    title: '已售罄产品',\n    name: '3',\n  },\n  \n])\n\n\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await StoreProductApi.getStoreProductPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 分类点击 */\nconst handleDeptNodeClick = async (row) => {\n  queryParams.cateId = row.id\n  await getList()\n}\n\nconst handleClick = (tab: TabsPaneContext, event: Event) => {\n  console.log(tab.paneName, event)\n  if (tab.paneName == '1') {\n    queryParams.isShow = 1\n    queryParams.stock = 1\n    getList()\n  }\n  \n  if (tab.paneName == '2') {\n    queryParams.isShow = 0\n    queryParams.stock = 1\n    getList()\n  }\n\n  if (tab.paneName == '3') {\n    queryParams.isShow = 1\n    queryParams.stock = 0\n    getList()\n  }\n\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n/** 上下架按钮操作 */\nconst onSale = async (id,isShow) => {\n  try {\n    // 删除的二次确认\n    await message.confirm('确定要上下架？')\n    // 发起删除\n    await StoreProductApi.saleStoreProduct(id,isShow)\n    message.success('操作成功')\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await StoreProductApi.deleteStoreProduct(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 导出按钮操作 */\nconst handleExport = async () => {\n  try {\n    // 导出的二次确认\n    await message.exportConfirm()\n    // 发起导出\n    exportLoading.value = true\n    const data = await StoreProductApi.exportStoreProduct(queryParams)\n    download.excel(data, '商品.xls')\n  } catch {\n  } finally {\n    exportLoading.value = false\n  }\n}\n\n/** 初始化 **/\nonMounted(() => {\n\n  \n  getList()\n})\n\n\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mall/product/storeProductRelation/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <!-- 搜索工作栏 -->\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"用户昵称\" prop=\"nickname\">\n        <el-input\n          v-model=\"queryParams.nickname\"\n          placeholder=\"请输入用户昵称\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column label=\"id\" align=\"center\" prop=\"id\" />\n      <el-table-column label=\"用户昵称\" align=\"center\" prop=\"nickname\" />\n      <el-table-column label=\"商品名称\" align=\"center\" prop=\"storeName\" />\n      <el-table-column\n        label=\"添加时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        :formatter=\"dateFormatter\"\n      />\n      <el-table-column label=\"操作\" align=\"center\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n            v-hasPermi=\"['product:store-product-relation:delete']\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n</template>\n\n<script setup lang=\"ts\" name=\"StoreProductRelation\">\nimport { dateFormatter } from '@/utils/formatTime'\nimport * as StoreProductRelationApi from '@/api/mall/product/storeProductRelation'\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  nickname: \"\"\n})\nconst queryFormRef = ref() // 搜索的表单\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await StoreProductRelationApi.getStoreProductRelationPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await StoreProductRelationApi.deleteStoreProductRelation(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mall/product/storeProductReply/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <!-- 搜索工作栏 -->\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"用户昵称\" prop=\"nickname\">\n        <el-input\n          v-model=\"queryParams.nickname\"\n          placeholder=\"请输入昵称\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column label=\"评论ID\" align=\"center\" prop=\"id\" />\n      <el-table-column label=\"用户昵称\" align=\"center\" prop=\"nickname\" />\n      <el-table-column label=\"用户头像\" align=\"center\" prop=\"avatar\" />\n      <el-table-column label=\"商品分数\" align=\"center\" prop=\"productScore\" />\n      <el-table-column label=\"服务分数\" align=\"center\" prop=\"serviceScore\" />\n      <el-table-column label=\"评论内容\" align=\"center\" prop=\"comment\" />\n      <el-table-column\n        label=\"评论时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        :formatter=\"dateFormatter\"\n      />\n      <el-table-column label=\"管理员回复内容\" align=\"center\" prop=\"merchantReplyContent\" />\n      <el-table-column\n        label=\"管理员回复时间\"\n        align=\"center\"\n        prop=\"merchantReplyTime\"\n        :formatter=\"dateFormatter\"\n      />\n      <el-table-column label=\"操作\" align=\"center\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n            v-hasPermi=\"['product:store-product-reply:update']\"\n          >\n            回复\n          </el-button>\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n            v-hasPermi=\"['product:store-product-reply:delete']\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n</template>\n\n<script setup lang=\"ts\" name=\"StoreProductReply\">\nimport { dateFormatter } from '@/utils/formatTime'\nimport * as StoreProductReplyApi from '@/api/mall/product/storeProductReply'\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  nickname: \"\"\n})\nconst queryFormRef = ref() // 搜索的表\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await StoreProductReplyApi.getStoreProductReplyPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await StoreProductReplyApi.deleteStoreProductReply(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mall/shop/ads/AdsForm.vue",
    "content": "<template>\n  <Dialog :title=\"dialogTitle\" v-model=\"dialogVisible\">\n    <el-form\n      ref=\"formRef\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"100px\"\n      v-loading=\"formLoading\"\n    >\n  \n      <el-form-item label=\"展示店铺\" prop=\"shopId\">\n        <el-select\n          v-model=\"formData.shopId\"\n          placeholder=\"选择店铺\"\n        >\n         <el-option\n            :key=\"0\"\n            label=\"全部\"\n            :value=\"0\"\n          />\n          <el-option\n            v-for=\"item in shopList\"\n            :key=\"item.id\"\n            :label=\"item.name\"\n            :value=\"item.id\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"图片\" prop=\"image\">\n          <Materials v-model=\"formData.image\" num=\"1\" type=\"image\" />\n      </el-form-item>\n      <el-form-item label=\"权重\" prop=\"weigh\">\n        <el-input v-model=\"formData.weigh\" placeholder=\"请输入权重\" />\n      </el-form-item>\n      <el-form-item label=\"是否可用\" prop=\"isSwitch\">\n        <el-radio-group v-model=\"formData.isSwitch\">\n          <el-radio :label=\"1\">是</el-radio>\n          <el-radio :label=\"0\">否</el-radio>\n        </el-radio-group>\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button @click=\"submitForm\" type=\"primary\" :disabled=\"formLoading\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script setup lang=\"ts\">\nimport * as AdsApi from '@/api/mall/shop/ads'\nimport * as ShopApi from '@/api/mall/store/shop'\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  image: undefined,\n  isSwitch: undefined,\n  weigh: undefined,\n  shopId: 0\n})\nconst formRules = reactive({\n  image: [{ required: true, message: '图片不能为空', trigger: 'blur' }],\n  weigh: [{ required: true, message: '权重不能为空', trigger: 'blur' }],\n  shopId: [{ required: true, message: '请选择店铺', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\nconst shopList = ref([])\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  getList()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await AdsApi.getAds(id)\n      formData.value.shopId = Number(formData.value.shopId)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as unknown as AdsApi.AdsVO\n    if (formType.value === 'create') {\n      await AdsApi.createAds(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await AdsApi.updateAds(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\nconst getList = async () => {\n  try {\n    const data = await ShopApi.getShopList()\n    shopList.value = data\n\n  } finally {\n    \n  }\n}\n\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    image: undefined,\n    isSwitch: 1,\n    weigh: undefined,\n    shopId: 0\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mall/shop/ads/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <!-- 搜索工作栏 -->\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"店铺名称\" prop=\"shopId\">\n        <el-input\n          v-model=\"queryParams.shopName\"\n          placeholder=\"请输入店铺名称\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n        <el-button type=\"primary\" @click=\"openForm('create')\" v-hasPermi=\"['shop:ads:create']\">\n          <Icon icon=\"ep:plus\" class=\"mr-5px\" /> 新增\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column label=\"id\" align=\"center\" prop=\"id\" />\n      <el-table-column label=\"图片\" align=\"center\" prop=\"image\">\n        <template #default=\"scope\">\n          <el-image style=\"width: 100px; height: 100px\" :src=\"scope.row.image\"  />\n        </template>\n      </el-table-column>\n      <el-table-column label=\"是否可用\" align=\"center\" prop=\"isSwitch\">\n        <template #default=\"scope\">\n         <span v-if=\"scope.row.isSwitch == 1\">是</span>\n         <span v-else>否</span>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"权重\" align=\"center\" prop=\"weigh\" />\n      <el-table-column label=\"展示店铺\" align=\"center\" prop=\"shopName\" />\n      <el-table-column\n        label=\"添加时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        :formatter=\"dateFormatter\"\n      />\n      <el-table-column label=\"操作\" align=\"center\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n            v-hasPermi=\"['shop:ads:update']\"\n          >\n            编辑\n          </el-button>\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n            v-hasPermi=\"['shop:ads:delete']\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <AdsForm ref=\"formRef\" @success=\"getList\" />\n</template>\n\n<script setup lang=\"ts\" name=\"ShopAds\">\nimport { dateFormatter } from '@/utils/formatTime'\nimport download from '@/utils/download'\nimport * as AdsApi from '@/api/mall/shop/ads'\nimport AdsForm from './AdsForm.vue'\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  shopName: null\n})\nconst queryFormRef = ref() // 搜索的表单\nconst exportLoading = ref(false) // 导出的加载中\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await AdsApi.getAdsPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await AdsApi.deleteAds(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 导出按钮操作 */\nconst handleExport = async () => {\n  try {\n    // 导出的二次确认\n    await message.exportConfirm()\n    // 发起导出\n    exportLoading.value = true\n    const data = await AdsApi.exportAds(queryParams)\n    download.excel(data, '广告图管理.xls')\n  } catch {\n  } finally {\n    exportLoading.value = false\n  }\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mall/shop/recharge/RechargeForm.vue",
    "content": "<template>\n  <Dialog :title=\"dialogTitle\" v-model=\"dialogVisible\">\n    <el-form\n      ref=\"formRef\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"100px\"\n      v-loading=\"formLoading\"\n    >\n      <el-form-item label=\"标题\" prop=\"name\">\n        <el-input v-model=\"formData.name\" placeholder=\"请输入标题\" />\n      </el-form-item>\n      <el-form-item label=\"价值\" prop=\"value\">\n        <el-input v-model=\"formData.value\" placeholder=\"请输入价值\" />\n      </el-form-item>\n      <el-form-item label=\"销售价\" prop=\"sellPrice\">\n        <el-input v-model=\"formData.sellPrice\" placeholder=\"请输入销售价\" />\n      </el-form-item>\n      <el-form-item label=\"权重\" prop=\"weigh\">\n        <el-input v-model=\"formData.weigh\" placeholder=\"请输入权重\" />\n      </el-form-item>\n      <el-form-item label=\"状态\" prop=\"status\">\n        <el-radio-group v-model=\"formData.status\">\n          <el-radio :label=\"1\">显示</el-radio>\n          <el-radio :label=\"0\">隐藏</el-radio>\n        </el-radio-group>\n      </el-form-item>\n    \n    </el-form>\n    <template #footer>\n      <el-button @click=\"submitForm\" type=\"primary\" :disabled=\"formLoading\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script setup lang=\"ts\">\nimport * as RechargeApi from '@/api/mall/shop/recharge'\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  name: undefined,\n  sales: undefined,\n  value: undefined,\n  weigh: undefined,\n  status: undefined,\n  sellPrice: undefined\n})\nconst formRules = reactive({\n  name: [{ required: true, message: '标题不能为空', trigger: 'blur' }],\n  value: [{ required: true, message: '价值不能为空', trigger: 'blur' }],\n  sellPrice: [{ required: true, message: '销售价不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await RechargeApi.getRecharge(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as unknown as RechargeApi.RechargeVO\n    if (formType.value === 'create') {\n      await RechargeApi.createRecharge(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await RechargeApi.updateRecharge(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    name: undefined,\n    sales: undefined,\n    value: undefined,\n    weigh: undefined,\n    status: 1,\n    sellPrice: undefined\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mall/shop/recharge/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <!-- 搜索工作栏 -->\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"标题\" prop=\"name\">\n        <el-input\n          v-model=\"queryParams.name\"\n          placeholder=\"请输入标题\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n        <el-button\n          type=\"primary\"\n          plain\n          @click=\"openForm('create')\"\n          v-hasPermi=\"['shop:recharge:create']\"\n        >\n          <Icon icon=\"ep:plus\" class=\"mr-5px\" /> 新增\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column label=\"id\" align=\"center\" prop=\"id\" />\n      <el-table-column label=\"标题\" align=\"center\" prop=\"name\" />\n      <el-table-column label=\"销量\" align=\"center\" prop=\"sales\" />\n      <el-table-column label=\"价值\" align=\"center\" prop=\"value\" />\n      <el-table-column label=\"权重\" align=\"center\" prop=\"weigh\" />\n      <el-table-column label=\"状态\" align=\"center\" prop=\"status\">\n        <template #default=\"scope\">\n         <span v-if=\"scope.row.status == 1\">显示</span>\n         <span v-else>隐藏</span>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"销售价\" align=\"center\" prop=\"sellPrice\" />\n      <el-table-column\n        label=\"添加时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        :formatter=\"dateFormatter\"\n      />\n      <el-table-column label=\"操作\" align=\"center\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n            v-hasPermi=\"['shop:recharge:update']\"\n          >\n            编辑\n          </el-button>\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n            v-hasPermi=\"['shop:recharge:delete']\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <RechargeForm ref=\"formRef\" @success=\"getList\" />\n</template>\n\n<script setup lang=\"ts\" name=\"Recharge\">\nimport { dateFormatter } from '@/utils/formatTime'\nimport download from '@/utils/download'\nimport * as RechargeApi from '@/api/mall/shop/recharge'\nimport RechargeForm from './RechargeForm.vue'\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  name: null,\n  sales: null,\n  value: null,\n  weigh: null,\n  status: null,\n  sellPrice: null,\n  createTime: []\n})\nconst queryFormRef = ref() // 搜索的表单\nconst exportLoading = ref(false) // 导出的加载中\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await RechargeApi.getRechargePage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await RechargeApi.deleteRecharge(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 导出按钮操作 */\nconst handleExport = async () => {\n  try {\n    // 导出的二次确认\n    await message.exportConfirm()\n    // 发起导出\n    exportLoading.value = true\n    const data = await RechargeApi.exportRecharge(queryParams)\n    download.excel(data, '充值金额管理.xls')\n  } catch {\n  } finally {\n    exportLoading.value = false\n  }\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mall/shop/service/ServiceForm.vue",
    "content": "<template>\n  <Dialog :title=\"dialogTitle\" v-model=\"dialogVisible\">\n    <el-form\n      ref=\"formRef\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"100px\"\n      v-loading=\"formLoading\"\n    >\n      <el-form-item label=\"类型\" prop=\"type\">\n        <el-select v-model=\"formData.type\" placeholder=\"请选择类型\">\n          <el-option label=\"选择类型\" value=\"\" />\n          <el-option label=\"页面\" value=\"pages\" />\n          <el-option label=\"跳转小程\" value=\"miniprogram\" />\n          <el-option label=\"内容\" value=\"content\" />\n          <el-option label=\"电话\" value=\"call\" />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"标题\" prop=\"name\">\n        <el-input v-model=\"formData.name\" placeholder=\"请输入标题\" />\n      </el-form-item>\n      <el-form-item label=\"图标\" prop=\"image\">\n          <Materials v-model=\"formData.image\" num=\"1\" type=\"image\" />\n      </el-form-item>\n      <el-form-item label=\"小程序app_id\" prop=\"appId\">\n        <el-input v-model=\"formData.appId\" placeholder=\"请输入小程序app_id\" />\n      </el-form-item>\n      <el-form-item label=\"页面路径\" prop=\"pages\">\n        <el-input v-model=\"formData.pages\" placeholder=\"请输入页面路径\" />\n      </el-form-item>\n      <el-form-item label=\"内容\">\n        <Editor v-model=\"formData.content\" height=\"150px\" />\n      </el-form-item>\n      <el-form-item label=\"电话\" prop=\"phone\">\n        <el-input v-model=\"formData.phone\" placeholder=\"请输入电话\" />\n      </el-form-item>\n      <el-form-item label=\"权重\" prop=\"weigh\">\n        <el-input v-model=\"formData.weigh\" placeholder=\"请输入权重\" />\n      </el-form-item>\n      <el-form-item label=\"状态\" prop=\"status\">\n        <el-radio-group v-model=\"formData.status\">\n          <el-radio :label=\"1\">上架</el-radio>\n          <el-radio :label=\"0\">下架</el-radio>\n        </el-radio-group>\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button @click=\"submitForm\" type=\"primary\" :disabled=\"formLoading\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script setup lang=\"ts\">\nimport * as ServiceApi from '@/api/mall/shop/service'\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  name: undefined,\n  image: undefined,\n  type: undefined,\n  content: '',\n  pid: undefined,\n  appId: undefined,\n  pages: undefined,\n  phone: undefined,\n  weigh: undefined,\n  status: 1\n})\nconst formRules = reactive({\n  type: [{ required: true, message: '类型不能为空', trigger: 'blur' }],\n  name: [{ required: true, message: '标题不能为空', trigger: 'blur' }],\n  image: [{ required: true, message: '图标不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await ServiceApi.getService(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as unknown as ServiceApi.ServiceVO\n    if (formType.value === 'create') {\n      await ServiceApi.createService(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await ServiceApi.updateService(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    name: undefined,\n    image: undefined,\n    type: undefined,\n    content: '',\n    pid: undefined,\n    appId: undefined,\n    pages: undefined,\n    phone: undefined,\n    weigh: undefined,\n    status: 1\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mall/shop/service/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <!-- 搜索工作栏 -->\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"标题\" prop=\"name\">\n        <el-input\n          v-model=\"queryParams.name\"\n          placeholder=\"请输入标题\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n        <el-button type=\"primary\" @click=\"openForm('create')\" v-hasPermi=\"['shop:service:create']\">\n          <Icon icon=\"ep:plus\" class=\"mr-5px\" /> 新增\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column label=\"id\" align=\"center\" prop=\"id\" />\n      <el-table-column label=\"标题\" align=\"center\" prop=\"name\" />\n      <el-table-column label=\"图标\" align=\"center\" prop=\"image\">\n        <template #default=\"scope\">\n          <el-image style=\"width: 100px; height: 100px\" :src=\"scope.row.image\"  />\n        </template>\n      </el-table-column>\n      <el-table-column label=\"类型\" align=\"center\" prop=\"type\">\n        <template #default=\"scope\">\n         <span v-if=\"scope.row.type == 'pages'\">页面</span>\n         <span v-if=\"scope.row.type == 'miniprogram'\">跳转小程序</span>\n         <span v-if=\"scope.row.type == 'call'\">电话</span>\n         <span v-if=\"scope.row.type == 'content'\">内容</span>\n         <span v-else>无</span>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"页面路径\" align=\"center\" prop=\"pages\" />\n      <el-table-column label=\"电话\" align=\"center\" prop=\"phone\" />\n      <el-table-column label=\"权重\" align=\"center\" prop=\"weigh\" />\n      <el-table-column label=\"状态\" align=\"center\" prop=\"status\">\n        <template #default=\"scope\">\n         <span v-if=\"scope.row.status == 1\">上架</span>\n         <span v-else>下架</span>\n        </template>\n      </el-table-column>\n      <el-table-column\n        label=\"添加时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        :formatter=\"dateFormatter\"\n        width=\"170\"\n      />\n      <el-table-column label=\"操作\" align=\"center\" fixed=\"right\" width=\"150\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n            v-hasPermi=\"['shop:service:update']\"\n          >\n            编辑\n          </el-button>\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n            v-hasPermi=\"['shop:service:delete']\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <ServiceForm ref=\"formRef\" @success=\"getList\" />\n</template>\n\n<script setup lang=\"ts\" name=\"Service\">\nimport { dateFormatter } from '@/utils/formatTime'\nimport download from '@/utils/download'\nimport * as ServiceApi from '@/api/mall/shop/service'\nimport ServiceForm from './ServiceForm.vue'\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  name: null,\n  image: null,\n  type: null,\n  content: null,\n  pid: null,\n  appId: null,\n  pages: null,\n  phone: null,\n  weigh: null,\n  status: null,\n  createTime: []\n})\nconst queryFormRef = ref() // 搜索的表单\nconst exportLoading = ref(false) // 导出的加载中\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await ServiceApi.getServicePage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await ServiceApi.deleteService(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 导出按钮操作 */\nconst handleExport = async () => {\n  try {\n    // 导出的二次确认\n    await message.exportConfirm()\n    // 发起导出\n    exportLoading.value = true\n    const data = await ServiceApi.exportService(queryParams)\n    download.excel(data, '我的服务.xls')\n  } catch {\n  } finally {\n    exportLoading.value = false\n  }\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mall/shop/storeProductRule/StoreProductRuleForm.vue",
    "content": "<template>\n  <Dialog :title=\"dialogTitle\" v-model=\"dialogVisible\" width=\"1000px\">\n    <el-form\n      ref=\"formRef\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"92px\"\n      v-loading=\"formLoading\"\n      :inline=\"true\"\n    >\n        <el-row :gutter=\"20\">\n          <el-col class=\"mb15\">\n            <el-form-item label=\"规格名称\" prop=\"ruleName\">\n              <el-input v-model=\"formData.ruleName\" placeholder=\"请输入规格名称\" />\n            </el-form-item>\n          </el-col>\n        </el-row>\n        <el-row :gutter=\"20\" v-for=\"(item,index) in formData.ruleValue\" :key=\"index\">\n          <el-form-item label=\" \">\n            <div class=\"acea-row row-middle\" style=\"display: block;width: 100%;\">\n              <span class=\"mr5\">{{ item.value }}</span>\n              <Icon icon=\"ep:circle-close\" @click=\"handleRemove(index)\" />\n            </div>\n            <div>\n              <el-tag class=\"mb5\" style=\"margin: 2px 4px 2px 0;\" closable v-for=\"(j,indexn) in item.detail\" :name=\"j\"\n               :key=\"indexn\" @close=\"handleRemove2(item.detail,indexn)\">\n                {{ j }}\n              </el-tag>\n               <el-input placeholder=\"请输入属性名称\" v-model=\"item.detail.attrsVal\"\n                        style=\"width: 170px\">\n                <template #append><el-button type=\"primary\" @click=\"createAttr(item.detail.attrsVal,index)\">添加</el-button></template>\n              </el-input>\n            </div>\n          </el-form-item>\n        </el-row>\n        <el-row :gutter=\"20\" v-if=\"isBtn\" >\n           <el-col :span=\"9\" class=\"mr15\">\n            <el-form-item label=\"规格：\">\n              <el-input placeholder=\"请输入规格\" v-model=\"attrsName\"/>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"9\" class=\"mr20\">\n            <el-form-item label=\"规格值：\">\n              <el-input v-model=\"attrsVal\" placeholder=\"请输入规格值\"/>\n            </el-form-item>\n          </el-col>\n          <el-col :span=\"2\">\n            <el-button type=\"primary\" @click=\"createAttrName\">确定</el-button>\n          </el-col>\n          <el-col :span=\"2\">\n            <el-button @click=\"offAttrName\">取消</el-button>\n          </el-col>\n        </el-row>\n    <el-button type=\"primary\"  @click=\"addBtn\" v-if=\"!isBtn\" class=\"ml95 mt10\">添加新规格</el-button>\n    </el-form>\n    <template #footer>\n      <el-button @click=\"submitForm\" type=\"primary\" :disabled=\"formLoading\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script setup lang=\"ts\">\nimport * as StoreProductRuleApi from '@/api/mall/shop/storeProductRule'\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst isBtn = ref(false)\nconst attrsName = ref('')\nconst attrsVal = ref('')\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: 0,\n  ruleName: '',\n  ruleValue: []\n})\nconst formRules = reactive({\n  ruleName: [{ required: true, message: '规格名称不能为空', trigger: 'blur' }],\n  ruleValue: [{ required: true, message: '规格值不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await StoreProductRuleApi.getStoreProductRule(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as unknown as StoreProductRuleApi.StoreProductRuleVO\n    if (formType.value === 'create') {\n      await StoreProductRuleApi.createStoreProductRule(data,0)\n      message.success(t('common.createSuccess'))\n    } else {\n      await StoreProductRuleApi.createStoreProductRule(data,data.id)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    ruleName: undefined,\n    ruleValue: []\n  }\n  formRef.value?.resetFields()\n}\nvar arr = []\nconst handleRemove = (index) => {\n  formData.value.ruleValue.splice(index, 1);\n  arr.splice(index, 1);\n}\nconst handleRemove2 = (item, index) => {\n  item.splice(index, 1);\n}\nconst createAttr = (num, idx) => {\n    if (num) {\n        formData.value.ruleValue[idx].detail.push(num);\n        var hash = {};\n        formData.value.ruleValue[idx].detail = formData.value.ruleValue[idx].detail.reduce(function (item, next) {\n          hash[next] ? '' : hash[next] = true && item.push(next);\n          return item\n        }, [])\n    } else {\n        message.success(t('shop.productRuleAttrAdd'))\n      }\n}\nconst addBtn = () => {\n  isBtn.value = true\n}\n\nconst createAttrName = () => {\n  if (attrsName.value && attrsVal.value) {\n    let data = {\n      value: attrsName.value,\n      detail: [attrsVal.value]\n    }\n    \n    //arr.push(data)\n    formData.value.ruleValue.push(data)\n    var hash = {}\n    formData.value.ruleValue = formData.value.ruleValue.reduce(function (item, next) {\n            /* eslint-disable */\n            hash[next.value] ? '' : hash[next.value] = true && item.push(next);\n            return item\n    }, [])\n\n     attrsName.value = ''\n     attrsVal.value = ''\n     isBtn.value = false\n          \n  } else {\n    message.success(t('shop.productRuleAdd'))\n  }\n  \n}\nconst offAttrName = () => {\n  isBtn.value = false\n}\n\n</script>\n\n<style scoped>\n.mb15 {\n    margin-bottom: 15px !important;\n}\n.mb5 {\n    margin-bottom: 5px !important;\n}\n.mr20 {\n    margin-right: 20px !important;\n}\n\n.mr5 {\n    margin-right: 5px !important;\n}\n.mr15 {\n    margin-right: 15px !important;\n}\n.ml95 {\n    margin-left: 95px !important;\n}\n.mt10{\n    margin-top: 10px;\n}\n\n.acea-row {\n    display: -webkit-box;\n    display: -moz-box;\n    display: -webkit-flex;\n    display: -ms-flexbox;\n    display: flex;\n    -webkit-box-lines: multiple;\n    -moz-box-lines: multiple;\n    -o-box-lines: multiple;\n    -webkit-flex-wrap: wrap;\n    -ms-flex-wrap: wrap;\n    flex-wrap: wrap;\n    /* 辅助类 */\n}\n.acea-row.row-middle {\n    -webkit-box-align: center;\n    -moz-box-align: center;\n    -o-box-align: center;\n    -ms-flex-align: center;\n    -webkit-align-items: center;\n    align-items: center;\n}\n\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mall/shop/storeProductRule/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <!-- 搜索工作栏 -->\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"规格名称\" prop=\"ruleName\">\n        <el-input\n          v-model=\"queryParams.ruleName\"\n          placeholder=\"请输入规格名称\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n        <el-button\n          type=\"primary\"\n          plain\n          @click=\"openForm('create')\"\n          v-hasPermi=\"['shop:store-product-rule:create']\"\n        >\n          <Icon icon=\"ep:plus\" class=\"mr-5px\" /> 新增\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column label=\"id\" align=\"center\" prop=\"id\" />\n      <el-table-column label=\"规格名称\" align=\"center\" prop=\"ruleName\" />\n      <el-table-column label=\"规格值\" align=\"center\" prop=\"ruleValue\" >\n        <template #default=\"scope\">\n            <div v-for=\"(item,index) in scope.row.ruleValue\" :key=\"index\">\n              {{ item.value }} : {{ item.detail.join(',')}}\n            </div>\n        </template>\n      </el-table-column>\n      <el-table-column\n        label=\"创建时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        :formatter=\"dateFormatter\"\n      />\n      <el-table-column label=\"操作\" align=\"center\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n            v-hasPermi=\"['shop:store-product-rule:update']\"\n          >\n            编辑\n          </el-button>\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n            v-hasPermi=\"['shop:store-product-rule:delete']\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <StoreProductRuleForm ref=\"formRef\" @success=\"getList\" />\n</template>\n\n<script setup lang=\"ts\" name=\"StoreProductRule\">\nimport { dateFormatter } from '@/utils/formatTime'\nimport download from '@/utils/download'\nimport * as StoreProductRuleApi from '@/api/mall/shop/storeProductRule'\nimport StoreProductRuleForm from './StoreProductRuleForm.vue'\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  ruleName: null\n})\nconst queryFormRef = ref() // 搜索的表单\nconst exportLoading = ref(false) // 导出的加载中\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await StoreProductRuleApi.getStoreProductRulePage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await StoreProductRuleApi.deleteStoreProductRule(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 导出按钮操作 */\nconst handleExport = async () => {\n  try {\n    // 导出的二次确认\n    await message.exportConfirm()\n    // 发起导出\n    exportLoading.value = true\n    const data = await StoreProductRuleApi.exportStoreProductRule(queryParams)\n    download.excel(data, '商品规则值(规格).xls')\n  } catch {\n  } finally {\n    exportLoading.value = false\n  }\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mall/store/shop/ShopForm.vue",
    "content": "<template>\n  <Dialog :title=\"dialogTitle\" v-model=\"dialogVisible\" width=\"50%\">\n    <el-form\n      ref=\"formRef\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"150px\"\n      v-loading=\"formLoading\"\n    >\n      <el-form-item label=\"店铺名称\" prop=\"name\">\n        <el-input v-model=\"formData.name\" placeholder=\"请输入店铺名称\" />\n      </el-form-item>\n      <el-form-item label=\"店铺电话\" prop=\"mobile\">\n        <el-input v-model=\"formData.mobile\" placeholder=\"请输入店铺电话\" />\n      </el-form-item>\n      <el-form-item label=\"门店头像\" prop=\"image\">\n          <Materials v-model=\"formData.image\" num=\"1\" type=\"image\" />\n      </el-form-item>\n      <el-form-item label=\"门店组图\" prop=\"images\">\n        <Materials v-model=\"formData.images\" num=\"5\" type=\"image\" />\n      </el-form-item>\n      <el-form-item label=\"营业开始时间\" prop=\"startTime\">\n        <el-time-picker v-model=\"formData.startTime\" placeholder=\"选择营业开始时间\" />\n      </el-form-item>\n      <el-form-item label=\"营业结束时间\" prop=\"endTime\">\n        <el-time-picker v-model=\"formData.endTime\" placeholder=\"选择营业结束时间\" />\n      </el-form-item>\n      <el-form-item label=\"选择地图定位\" prop=\"addressMap\">\n        <el-button @click=\"openForm\"  type=\"primary\">点击选择地址获取经纬度</el-button>\n      </el-form-item>\n      <el-form-item label=\"地图定位地址\" prop=\"addressMap\">\n        <el-input v-model=\"formData.addressMap\" placeholder=\"请输入地图定位地址\" />\n      </el-form-item>\n      <el-form-item label=\"经度\" prop=\"lng\">\n        <el-input v-model=\"formData.lng\" placeholder=\"请输入经度\" />\n      </el-form-item>\n      <el-form-item label=\"纬度\" prop=\"lat\">\n        <el-input v-model=\"formData.lat\" placeholder=\"请输入纬度\" />\n      </el-form-item>\n      <el-form-item label=\"详细地址\" prop=\"address\">\n        <el-input v-model=\"formData.address\" placeholder=\"请输入详细地址\" />\n      </el-form-item>\n      <el-form-item label=\"外卖配送距离/千米\" prop=\"distance\">\n        <el-input v-model=\"formData.distance\" placeholder=\"请输入外卖配送距离,单位为千米。0表示不送外卖\" />\n      </el-form-item>\n      <el-form-item label=\"起送价钱\" prop=\"minPrice\">\n        <el-input v-model=\"formData.minPrice\" placeholder=\"请输入起送价钱\" />\n      </el-form-item>\n      <el-form-item label=\"配送价格\" prop=\"deliveryPrice\">\n        <el-input v-model=\"formData.deliveryPrice\" placeholder=\"请输入配送价格\" />\n      </el-form-item>\n      <el-form-item label=\"公告\" prop=\"notice\">\n        <el-input type=\"textarea\" rows=\"5\"  v-model=\"formData.notice\" placeholder=\"请输入公告\" />\n      </el-form-item>\n      <el-form-item label=\"绑定管理员\" prop=\"adminId\" v-if ='isShow'>\n        <el-select\n          v-model=\"formData.adminId\"\n          multiple\n          placeholder=\"选择用户\"\n        >\n          <el-option\n            v-for=\"item in adminUsers\"\n            :key=\"item.id\"\n            :label=\"item.nickname\"\n            :value=\"item.id\"\n          />\n        </el-select>\n        <div style=\"color: red;\">绑定管理员用于分店管理</div>\n      </el-form-item>\n      <el-form-item label=\"是否营业\" prop=\"status\">\n        <el-radio-group v-model=\"formData.status\">\n          <el-radio :label=\"1\">是</el-radio>\n          <el-radio :label=\"0\">否</el-radio>\n        </el-radio-group>\n      </el-form-item>\n\n     \n    </el-form>\n    <template #footer>\n      <el-button @click=\"submitForm\" type=\"primary\" :disabled=\"formLoading\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n  <MyMap ref=\"formRef1\" @mapConfirm=\"mapConfirm\" />\n</template>\n<script setup lang=\"ts\">\nimport * as ShopApi from '@/api/mall/store/shop'\nimport * as UserApi from '@/api/system/user'\nimport MyMap from './map.vue'\nimport { CACHE_KEY, useCache } from '@/hooks/web/useCache'\n\nconst { wsCache } = useCache()\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  name: undefined,\n  mobile: undefined,\n  image: undefined,\n  images: undefined,\n  address: undefined,\n  addressMap: undefined,\n  lng: undefined,\n  lat: undefined,\n  distance: undefined,\n  minPrice: undefined,\n  deliveryPrice: undefined,\n  notice: undefined,\n  status: undefined,\n  adminId: undefined,\n  uniprintId: undefined,\n  startTime: undefined,\n  endTime: undefined\n})\nconst formRules = reactive({\n  name: [{ required: true, message: '店铺名称不能为空', trigger: 'blur' }],\n  mobile: [{ required: true, message: '店铺电话不能为空', trigger: 'blur' }],\n  image: [{ required: true, message: '图片不能为空', trigger: 'blur' }],\n  images: [{ required: true, message: '多张图片不能为空', trigger: 'blur' }],\n  address: [{ required: true, message: '详细地址不能为空', trigger: 'blur' }],\n  addressMap: [{ required: true, message: '地图定位地址不能为空', trigger: 'blur' }],\n  lng: [{ required: true, message: '经度不能为空', trigger: 'blur' }],\n  lat: [{ required: true, message: '纬度不能为空', trigger: 'blur' }],\n  distance: [{ required: true, message: '外卖配送距离,单位为千米。0表示不送外卖不能为空', trigger: 'blur' }],\n  minPrice: [{ required: true, message: '起送价钱不能为空', trigger: 'blur' }],\n  deliveryPrice: [{ required: true, message: '配送价格不能为空', trigger: 'blur' }],\n  notice: [{ required: true, message: '公告不能为空', trigger: 'blur' }],\n  status: [{ required: true, message: '是否营业:0=否,1=是不能为空', trigger: 'blur' }],\n  adminId: [{ required: true, message: '管理员id不能为空', trigger: 'blur' }],\n  startTime: [{ required: true, message: '营业开始时间不能为空', trigger: 'blur' }],\n  endTime: [{ required: true, message: '营业结束时间不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\nconst adminUsers = ref([])\nconst isShow = ref(true)\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  const user = wsCache.get(CACHE_KEY.USER)\n\n  if(user.user.shopId > 0) {\n    isShow.value = false\n  }\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  getList()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await ShopApi.getShop(id)\n      formData.value.adminId = formData.value.adminId.map(Number)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as unknown as ShopApi.ShopVO\n    if (formType.value === 'create') {\n      await ShopApi.createShop(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await ShopApi.updateShop(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\nconst formRef1 = ref() // 表单 Ref\nconst openForm = () => {\n  formRef1.value.open()\n}\n\nconst mapConfirm = (mapResult) => {\n  console.log('mapResult:',mapResult)\n  formData.value.addressMap = mapResult.address\n  formData.value.lng = mapResult.point.lng\n  formData.value.lat = mapResult.point.lat\n}\n\nconst getList = async () => {\n  try {\n    const data = await UserApi.getSimpleUserList()\n    let arr = data.filter(elem => elem.id !== 1);\n    adminUsers.value = arr\n\n  } finally {\n    \n  }\n}\n\n\n\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    name: undefined,\n    mobile: undefined,\n    image: undefined,\n    images: undefined,\n    address: undefined,\n    addressMap: undefined,\n    lng: undefined,\n    lat: undefined,\n    distance: undefined,\n    minPrice: undefined,\n    deliveryPrice: undefined,\n    notice: undefined,\n    status: 1,\n    adminId: undefined,\n    uniprintId: undefined,\n    startTime: undefined,\n    endTime: undefined\n  }\n  formRef.value?.resetFields()\n}\n</script>\n<style scoped>\n.map {\n  width: 100%;\n  height: 300px;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mall/store/shop/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <!-- 搜索工作栏 -->\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"店铺名称\" prop=\"name\">\n        <el-input\n          v-model=\"queryParams.name\"\n          placeholder=\"请输入店铺名称\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"店铺电话\" prop=\"mobile\">\n        <el-input\n          v-model=\"queryParams.mobile\"\n          placeholder=\"请输入店铺电话\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n        <el-button type=\"primary\" @click=\"openForm('create')\" v-hasPermi=\"['store:shop:create']\">\n          <Icon icon=\"ep:plus\" class=\"mr-5px\" /> 新增\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column label=\"id\" align=\"center\" prop=\"id\" />\n      <el-table-column label=\"店铺名称\" align=\"center\" prop=\"name\" width=\"150\" />\n      <el-table-column label=\"店铺电话\" align=\"center\" prop=\"mobile\" width=\"150\" />\n      <el-table-column label=\"门店图片\" align=\"center\" prop=\"image\" width=\"150\">\n        <template #default=\"scope\">\n          <el-image style=\"width: 100px; height: 100px\" :src=\"scope.row.image\"  />\n        </template>\n      </el-table-column>\n      <el-table-column label=\"经度\" align=\"center\" prop=\"lng\" width=\"150\" />\n      <el-table-column label=\"纬度\" align=\"center\" prop=\"lat\"  width=\"150\" />\n      <el-table-column label=\"外卖配送距离\" align=\"center\" prop=\"distance\" width=\"150\">\n        <template #default=\"scope\">\n          <span v-if=\"scope.row.distance == 0\">\n            到店消费\n          </span>\n          <span v-else>\n            {{ scope.row.distance }}\n          </span>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"起送价钱\" align=\"center\" prop=\"minPrice\" />\n      <el-table-column label=\"配送价格\" align=\"center\" prop=\"deliveryPrice\" />\n      <el-table-column label=\"是否营业\" align=\"center\" prop=\"status\">\n        <template #default=\"scope\">\n          <span v-if=\"scope.row.status == 1\">是</span>\n          <span v-else>否</span>\n         </template>\n      </el-table-column>\n      <el-table-column\n        label=\"营业开始时间\"\n        align=\"center\"\n        prop=\"startTime\"\n        :formatter=\"dateFormatter2\"\n        width=\"150\"\n      />\n      <el-table-column\n        label=\"营业结束时间\"\n        align=\"center\"\n        prop=\"endTime\"\n        :formatter=\"dateFormatter2\"\n        width=\"150\"\n      />\n      <el-table-column\n        label=\"添加时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        :formatter=\"dateFormatter\"\n        width=\"170\"\n      />\n      <el-table-column label=\"操作\" align=\"center\" fixed=\"right\" width=\"150\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n            v-hasPermi=\"['store:shop:update']\"\n          >\n            编辑\n          </el-button>\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n            v-hasPermi=\"['store:shop:delete']\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <ShopForm ref=\"formRef\" @success=\"getList\" />\n</template>\n\n<script setup lang=\"ts\" name=\"StoreShop\">\nimport { dateFormatter, dateFormatter2 } from '@/utils/formatTime'\nimport download from '@/utils/download'\nimport * as ShopApi from '@/api/mall/store/shop'\nimport ShopForm from './ShopForm.vue'\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  name: null,\n  mobile: null,\n  image: null,\n  images: null,\n  address: null,\n  addressMap: null,\n  lng: null,\n  lat: null,\n  distance: null,\n  minPrice: null,\n  deliveryPrice: null,\n  notice: null,\n  status: null,\n  adminId: null,\n  uniprintId: null,\n  createTime: [],\n  startTime: [],\n  endTime: []\n})\nconst queryFormRef = ref() // 搜索的表单\nconst exportLoading = ref(false) // 导出的加载中\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await ShopApi.getShopPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await ShopApi.deleteShop(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 导出按钮操作 */\nconst handleExport = async () => {\n  try {\n    // 导出的二次确认\n    await message.exportConfirm()\n    // 发起导出\n    exportLoading.value = true\n    const data = await ShopApi.exportShop(queryParams)\n    download.excel(data, '门店管理.xls')\n  } catch {\n  } finally {\n    exportLoading.value = false\n  }\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mall/store/shop/map.vue",
    "content": "<template>\n  <Dialog :title=\"dialogTitle\" v-model=\"dialogVisible\" >\n    <baidu-map\n      style=\"height: 400px;width: 100%\"\n      :ak=\"ak\"\n      :map-click=\"false\"\n      :center=\"center\"\n      :zoom=\"zoom\"\n      :scroll-wheel-zoom=\"true\"\n      @click=\"getClickInfo\"\n      @moving=\"syncCenterAndZoom\"\n      @moveend=\"syncCenterAndZoom\"\n      @ready=\"onBaiduMapReady\"\n      @zoomend=\"syncCenterAndZoom\"\n    >\n      <bm-view style=\"width: 100%; height: 100%;\" />\n      <bm-marker :position=\"{lng: center.lng, lat: center.lat}\" :dragging=\"true\" animation=\"BMAP_ANIMATION_BOUNCE\" />\n      <bm-control :offset=\"{width: '10px', height: '10px'}\">\n        <bm-auto-complete v-model=\"keyword\" :sug-style=\"{zIndex: 999999}\">\n          <el-input v-model=\"keyword\" type=\"text\" placeholder=\"请输入地名关键字\" clearable />\n        </bm-auto-complete>\n      </bm-control>\n      <bm-geolocation anchor=\"BMAP_ANCHOR_BOTTOM_RIGHT\" show-address-bar auto-location />\n      <bm-local-search :keyword=\"keyword\" :auto-viewport=\"true\" :panel=\"false\" />\n    </baidu-map>\n\n    <template #footer>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n      <el-button icon=\"el-icon-place\" type=\"primary\" @click=\"confirm\">确 定</el-button>\n    </template>\n  </Dialog>\n</template>\n<script setup lang=\"ts\">\nimport { BaiduMap, BmControl, BmAutoComplete, BmLocalSearch, BmMarker, BmGeolocation } from 'vue-baidu-map-3x'\n\n// const { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\nconst ak = ref(import.meta.env.VITE_BAIDU_MAP_AK)\nconst zoom = ref(15)\nconst BMap = ref({})\nconst center = ref({ lng: 116.404, lat: 39.915 })\nconst choosedLocation = ref({ point: {}, address: '',province: '', city: '', district: '', addr: '' })\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('地图选择') // 弹窗的标题\nconst keyword = ref('')// 地区搜索的信息\n\n\n/** 打开弹窗 */\nconst open = async () => {\n  dialogVisible.value = true\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n// 初始化地图\nconst onBaiduMapReady = (e) => {\n  BMap.value = e.BMap\n  if (BMap.value) {\n    // 获取定位地址信息并赋值给声明变量\n    const geolocation = new BMap.value.Geolocation()\n    // 通过 getCurrentPosition() 方法：获取当前的位置信息\n    geolocation.getCurrentPosition(res => {\n      console.log('返回详细地址和经纬度', res)\n      if(res){\n        const { lng, lat } = res.point\n        const { province, city, district, street, street_number } = res.address\n\n        center.value = { lng, lat }\n        choosedLocation.value = { province, city, district, addr: street + street_number, lng, lat }\n      }\n    \n    })\n  }\n}\n\n/** *\n * 地图点击事件。\n */\nconst getClickInfo = (e) => {\n  // 调整地图中心位置\n  center.value.lng = e.point.lng\n  center.value.lat = e.point.lat\n\n  // 此时已经可以获取到BMap类\n  if (BMap.value) {\n    // 创建地址解析器的实例\n    const geoCoder = new BMap.value.Geocoder()\n    // getLocation() 类--利用坐标获取地址的详细信息\n    // getPoint() 类--获取位置对应的坐标\n    geoCoder.getLocation(e.point, function(res) {\n      console.log('获取经纬度', e.point, '获取详细地址', res)\n      const addrComponent = res.addressComponents\n      const surroundingPois = res.surroundingPois\n      const province = addrComponent.province\n      const city = addrComponent.city\n      const district = addrComponent.district\n      const point = e.point\n      const address =  res.address\n      console.log('获取经纬度1', point, '获取详细地址1', address)\n      let addr = addrComponent.street\n      if (surroundingPois.length > 0 && surroundingPois[0].title) {\n        if (addr) {\n          addr += `-${surroundingPois[0].title}`\n        } else {\n          addr += `${surroundingPois[0].title}`\n        }\n      } else {\n        addr += addrComponent.streetNumber\n      }\n      choosedLocation.value = { point,address,province, city, district, addr, ...center.value }\n\n      console.log('aa:',choosedLocation.value)\n    })\n  }\n}\n\nconst syncCenterAndZoom = (e) => {\n  // 返回地图当前的缩放级别\n  zoom .value= e.target.getZoom()\n}\n\n/** *\n * 确认\n */\nconst emit = defineEmits(['mapConfirm']) \nconst confirm = () => {\n  console.log('choosedLocation.value', choosedLocation.value.address)\n  if(choosedLocation.value.address == '') {\n    return message.error('请选择定位地址')\n  }\n  dialogVisible.value = false\n    // 发送操作成功的事件\n  emit('mapConfirm',choosedLocation.value)\n}\n\n\n\n</script>\n<style scoped>\n.map {\n  width: 100%;\n  height: 300px;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/message/wechatTemplate/WechatTemplateForm.vue",
    "content": "<template>\n  <Dialog :title=\"dialogTitle\" v-model=\"dialogVisible\">\n    <el-form\n      ref=\"formRef\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"100px\"\n      v-loading=\"formLoading\"\n    >\n     <el-form-item label=\"消息类型\" prop=\"type\">\n        <el-select v-model=\"formData.type\" placeholder=\"请选择类型\">\n          <el-option label=\"请选消息类型\" value=\"\" />\n          <el-option label=\"模板消息\" value=\"template\" />\n          <el-option label=\"订阅消息\" value=\"subscribe\" />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"模板编号\" prop=\"tempkey\">\n        <el-input v-model=\"formData.tempkey\" placeholder=\"请输入模板编号\" />\n      </el-form-item>\n      <el-form-item label=\"模板名\" prop=\"name\">\n        <el-input v-model=\"formData.name\" placeholder=\"请输入模板名\" />\n      </el-form-item>\n      <el-form-item label=\"模板ID\" prop=\"tempid\">\n        <el-input v-model=\"formData.tempid\" placeholder=\"请输入模板ID\" />\n      </el-form-item>\n    \n      <el-form-item label=\"状态\" prop=\"status\">\n        <el-radio-group v-model=\"formData.status\">\n          <el-radio :label=\"1\">开启</el-radio>\n          <el-radio :label=\"0\">关闭</el-radio>\n        </el-radio-group>\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button @click=\"submitForm\" type=\"primary\" :disabled=\"formLoading\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script setup lang=\"ts\">\nimport * as WechatTemplateApi from '@/api/message/wechatTemplate'\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  tempkey: undefined,\n  name: undefined,\n  content: undefined,\n  tempid: undefined,\n  status: undefined,\n  type: undefined\n})\nconst formRules = reactive({\n  tempkey: [{ required: true, message: '模板编号不能为空', trigger: 'blur' }],\n  name: [{ required: true, message: '模板名不能为空', trigger: 'blur' }],\n  tempid: [{ required: true, message: '模板ID不能为空', trigger: 'blur' }],\n  type: [{ required: true, message: '消息类型不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await WechatTemplateApi.getWechatTemplate(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as unknown as WechatTemplateApi.WechatTemplateVO\n    if (formType.value === 'create') {\n      await WechatTemplateApi.createWechatTemplate(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await WechatTemplateApi.updateWechatTemplate(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    tempkey: undefined,\n    name: undefined,\n    content: undefined,\n    tempid: undefined,\n    status: 1,\n    type: undefined\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/message/wechatTemplate/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <!-- 搜索工作栏 -->\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"模板名\" prop=\"name\">\n        <el-input\n          v-model=\"queryParams.name\"\n          placeholder=\"请输入模板名\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n        <el-button\n          type=\"primary\"\n          plain\n          @click=\"openForm('create')\"\n          v-hasPermi=\"['message:wechat-template:create']\"\n        >\n          <Icon icon=\"ep:plus\" class=\"mr-5px\" /> 新增\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column label=\"模板id\" align=\"center\" prop=\"id\" />\n      <el-table-column label=\"模板编号\" align=\"center\" prop=\"tempkey\" />\n      <el-table-column label=\"模板名\" align=\"center\" prop=\"name\" />\n      <el-table-column label=\"模板ID\" align=\"center\" prop=\"tempid\" />\n      <el-table-column label=\"类型\" align=\"center\" prop=\"type\">\n        <template #default=\"scope\">\n           <span v-if=\"scope.row.type=='template'\">模板消息</span>\n           <span v-if=\"scope.row.type=='subscribe'\">订阅消息</span>\n           <span v-else></span>\n        </template>\n      </el-table-column>\n      <el-table-column\n        label=\"添加时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        :formatter=\"dateFormatter\"\n      />\n      <el-table-column label=\"操作\" align=\"center\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n            v-hasPermi=\"['message:wechat-template:update']\"\n          >\n            编辑\n          </el-button>\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n            v-hasPermi=\"['message:wechat-template:delete']\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <WechatTemplateForm ref=\"formRef\" @success=\"getList\" />\n</template>\n\n<script setup lang=\"ts\" name=\"WechatTemplate\">\nimport { dateFormatter } from '@/utils/formatTime'\nimport download from '@/utils/download'\nimport * as WechatTemplateApi from '@/api/message/wechatTemplate'\nimport WechatTemplateForm from './WechatTemplateForm.vue'\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  tempkey: null,\n  name: null,\n  content: null,\n  tempid: null,\n  createTime: [],\n  status: null,\n  type: null\n})\nconst queryFormRef = ref() // 搜索的表单\nconst exportLoading = ref(false) // 导出的加载中\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await WechatTemplateApi.getWechatTemplatePage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await WechatTemplateApi.deleteWechatTemplate(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 导出按钮操作 */\nconst handleExport = async () => {\n  try {\n    // 导出的二次确认\n    await message.exportConfirm()\n    // 发起导出\n    exportLoading.value = true\n    const data = await WechatTemplateApi.exportWechatTemplate(queryParams)\n    download.excel(data, '微信模板.xls')\n  } catch {\n  } finally {\n    exportLoading.value = false\n  }\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/account/AccountForm.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" :title=\"dialogTitle\">\n    <el-form\n      ref=\"formRef\"\n      v-loading=\"formLoading\"\n      :model=\"formData\"\n      :rules=\"rules\"\n      label-width=\"120px\"\n    >\n      <el-form-item label=\"名称\" prop=\"name\">\n        <el-input v-model=\"formData.name\" placeholder=\"请输入名称\" />\n      </el-form-item>\n      <el-form-item label=\"微信号\" prop=\"account\">\n        <template #label>\n          <span>\n            <el-tooltip\n              content=\"在微信公众平台（mp.weixin.qq.com）的菜单 [设置与开发 - 公众号设置 - 账号详情] 中能找到「微信号」\"\n              placement=\"top\"\n            >\n              <Icon icon=\"ep:question-filled\" style=\"vertical-align: middle\" />\n            </el-tooltip>\n            微信号\n          </span>\n        </template>\n        <el-input v-model=\"formData.account\" placeholder=\"请输入微信号\" />\n      </el-form-item>\n      <el-form-item label=\"appId\" prop=\"appId\">\n        <template #label>\n          <span>\n            <el-tooltip\n              content=\"在微信公众平台（mp.weixin.qq.com）的菜单 [设置与开发 - 公众号设置 - 基本设置] 中能找到「开发者ID(AppID)」\"\n              placement=\"top\"\n            >\n              <Icon icon=\"ep:question-filled\" style=\"vertical-align: middle\" />\n            </el-tooltip>\n            appId\n          </span>\n        </template>\n        <el-input v-model=\"formData.appId\" placeholder=\"请输入公众号 appId\" />\n      </el-form-item>\n      <el-form-item label=\"appSecret\" prop=\"appSecret\">\n        <template #label>\n          <span>\n            <el-tooltip\n              content=\"在微信公众平台（mp.weixin.qq.com）的菜单 [设置与开发 - 公众号设置 - 基本设置] 中能找到「开发者密码(AppSecret)」\"\n              placement=\"top\"\n            >\n              <Icon icon=\"ep:question-filled\" style=\"vertical-align: middle\" />\n            </el-tooltip>\n            appSecret\n          </span>\n        </template>\n        <el-input v-model=\"formData.appSecret\" placeholder=\"请输入公众号 appSecret\" />\n      </el-form-item>\n      <el-form-item label=\"token\" prop=\"token\">\n        <el-input v-model=\"formData.token\" placeholder=\"请输入公众号token\" />\n      </el-form-item>\n      <el-form-item label=\"消息加解密密钥\" prop=\"aesKey\">\n        <el-input v-model=\"formData.aesKey\" placeholder=\"请输入消息加解密密钥\" />\n      </el-form-item>\n      <el-form-item label=\"备注\" prop=\"remark\">\n        <el-input v-model=\"formData.remark\" placeholder=\"请输入备注\" type=\"textarea\" />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button :disabled=\"formLoading\" type=\"primary\" @click=\"submitForm\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport * as AccountApi from '@/api/mp/account'\n\ndefineOptions({ name: 'MpAccountForm' })\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  name: '',\n  account: '',\n  appId: '',\n  appSecret: '',\n  token: '',\n  aesKey: '',\n  remark: ''\n})\nconst rules = reactive({\n  name: [{ required: true, message: '名称不能为空', trigger: 'blur' }],\n  account: [{ required: true, message: '公众号账号不能为空', trigger: 'blur' }],\n  appId: [{ required: true, message: '公众号 appId 不能为空', trigger: 'blur' }],\n  appSecret: [{ required: true, message: '公众号密钥不能为空', trigger: 'blur' }],\n  token: [{ required: true, message: '公众号 token 不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await AccountApi.getAccount(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value\n    if (formType.value === 'create') {\n      await AccountApi.createAccount(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await AccountApi.updateAccount(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 表单重置 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    name: '',\n    account: '',\n    appId: '',\n    appSecret: '',\n    token: '',\n    aesKey: '',\n    remark: ''\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/account/index.vue",
    "content": "<template>\n  <!-- 搜索工作栏 -->\n  <ContentWrap>\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"名称\" prop=\"name\">\n        <el-input\n          v-model=\"queryParams.name\"\n          placeholder=\"请输入名称\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" />搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" />重置</el-button>\n        <el-button type=\"primary\" @click=\"openForm('create')\" v-hasPermi=\"['mp:account:create']\">\n          <Icon icon=\"ep:plus\" class=\"mr-5px\" /> 新增\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column label=\"名称\" align=\"center\" prop=\"name\" />\n      <el-table-column label=\"微信号\" align=\"center\" prop=\"account\" width=\"180\" />\n      <el-table-column label=\"appId\" align=\"center\" prop=\"appId\" width=\"180\" />\n      <el-table-column label=\"服务器地址(URL)\" align=\"center\" prop=\"appId\" width=\"360\">\n        <template #default=\"scope\">\n          {{ 'http://服务端地址/mp/open/' + scope.row.appId }}\n        </template>\n      </el-table-column>\n      <el-table-column label=\"二维码\" align=\"center\" prop=\"qrCodeUrl\">\n        <template #default=\"scope\">\n          <img\n            v-if=\"scope.row.qrCodeUrl\"\n            :src=\"scope.row.qrCodeUrl\"\n            alt=\"二维码\"\n            style=\"display: inline-block; height: 100px\"\n          />\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"handleGenerateQrCode(scope.row)\"\n            v-hasPermi=\"['mp:account:qr-code']\"\n          >\n            生成二维码\n          </el-button>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"是否是主账户\" align=\"center\" prop=\"isMain\">\n        <template #default=\"scope\">\n          <span v-if=\"scope.row.isMain == 1\" style=\"color:red\">是</span>\n          <span v-else>否</span>\n         </template>\n      </el-table-column>\n      <el-table-column label=\"备注\" align=\"center\" prop=\"remark\" />\n      <el-table-column label=\"操作\" align=\"center\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n            v-hasPermi=\"['mp:account:update']\"\n          >\n            编辑\n          </el-button>\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n            v-hasPermi=\"['mp:account:delete']\"\n          >\n            删除\n          </el-button>\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleCleanQuota(scope.row)\"\n            v-hasPermi=\"['mp:account:clear-quota']\"\n          >\n            清空 API 配额\n          </el-button>\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"setMain(scope.row)\"\n            v-hasPermi=\"['mp:account:set-main']\"\n          >\n            设置为主账户\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 对话框(添加 / 修改) -->\n  <AccountForm ref=\"formRef\" @success=\"getList\" />\n</template>\n<script setup lang=\"ts\" name=\"MpAccount\">\nimport * as AccountApi from '@/api/mp/account'\nimport AccountForm from './AccountForm.vue'\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  name: null,\n  account: null,\n  appId: null\n})\nconst queryFormRef = ref() // 搜索的表单\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await AccountApi.getAccountPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await AccountApi.deleteAccount(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 生成二维码的按钮操作 */\nconst handleGenerateQrCode = async (row) => {\n  try {\n    // 生成二维码的二次确认\n    await message.confirm('是否确认生成公众号账号编号为\"' + row.name + '\"的二维码?')\n    // 发起生成二维码\n    await AccountApi.generateAccountQrCode(row.id)\n    message.success('生成二维码成功')\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 清空二维码 API 配额的按钮操作 */\nconst handleCleanQuota = async (row) => {\n  try {\n    // 清空 API 配额的二次确认\n    await message.confirm('是否确认清空生成公众号账号编号为\"' + row.name + '\"的 API 配额?')\n    // 发起清空 API 配额\n    await AccountApi.clearAccountQuota(row.id)\n    message.success('清空 API 配额成功')\n  } catch {}\n}\n\nconst setMain = async (row) => {\n  try {\n    // 清空 API 配额的二次确认\n    await message.confirm('是否确认设置公众号账号编号为\"' + row.name + '\"主账户?')\n    // 发起清空 API 配额\n    await AccountApi.setAccountMain(row.id)\n    message.success('设置成功')\n    getList()\n  } catch {}\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/account2/AccountForm.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" :title=\"dialogTitle\">\n    <el-form\n      ref=\"formRef\"\n      v-loading=\"formLoading\"\n      :model=\"formData\"\n      :rules=\"rules\"\n      label-width=\"120px\"\n    >\n      <el-form-item label=\"名称\" prop=\"name\">\n        <el-input v-model=\"formData.name\" placeholder=\"请输入名称\" />\n      </el-form-item>\n      <el-form-item label=\"appId\" prop=\"appId\">\n        <template #label>\n          <span>\n            <el-tooltip\n              content=\"在微信公众平台（mp.weixin.qq.com）的菜单 [设置与开发 - 小程序设置 - 基本设置] 中能找到「开发者ID(AppID)」\"\n              placement=\"top\"\n            >\n              <Icon icon=\"ep:question-filled\" style=\"vertical-align: middle\" />\n            </el-tooltip>\n            appId\n          </span>\n        </template>\n        <el-input v-model=\"formData.appId\" placeholder=\"请输入小程序 appId\" />\n      </el-form-item>\n      <el-form-item label=\"appSecret\" prop=\"appSecret\">\n        <template #label>\n          <span>\n            <el-tooltip\n              content=\"在微信公众平台（mp.weixin.qq.com）的菜单 [设置与开发 - 小程序设置 - 基本设置] 中能找到「开发者密码(AppSecret)」\"\n              placement=\"top\"\n            >\n              <Icon icon=\"ep:question-filled\" style=\"vertical-align: middle\" />\n            </el-tooltip>\n            appSecret\n          </span>\n        </template>\n        <el-input v-model=\"formData.appSecret\" placeholder=\"请输入小程序 appSecret\" />\n      </el-form-item>\n      <el-form-item label=\"备注\" prop=\"remark\">\n        <el-input v-model=\"formData.remark\" placeholder=\"请输入备注\" type=\"textarea\" />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button :disabled=\"formLoading\" type=\"primary\" @click=\"submitForm\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script lang=\"ts\" name=\"MpAccountForm\" setup>\nimport * as AccountApi from '@/api/mp/account2'\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  name: '',\n  account: '',\n  appId: '',\n  appSecret: '',\n  token: '',\n  aesKey: '',\n  remark: ''\n})\nconst rules = reactive({\n  name: [{ required: true, message: '名称不能为空', trigger: 'blur' }],\n  appId: [{ required: true, message: '公众号 appId 不能为空', trigger: 'blur' }],\n  appSecret: [{ required: true, message: '公众号密钥不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await AccountApi.getAccount(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value\n    if (formType.value === 'create') {\n      await AccountApi.createAccount(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await AccountApi.updateAccount(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 表单重置 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    name: '',\n    account: '',\n    appId: '',\n    appSecret: '',\n    token: '',\n    aesKey: '',\n    remark: ''\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/account2/index.vue",
    "content": "<template>\n  <!-- 搜索工作栏 -->\n  <ContentWrap>\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"名称\" prop=\"name\">\n        <el-input\n          v-model=\"queryParams.name\"\n          placeholder=\"请输入名称\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" />搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" />重置</el-button>\n        <el-button type=\"primary\" @click=\"openForm('create')\" v-hasPermi=\"['ma:account:create']\">\n          <Icon icon=\"ep:plus\" class=\"mr-5px\" /> 新增\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column label=\"名称\" align=\"center\" prop=\"name\" />\n      <el-table-column label=\"appId\" align=\"center\" prop=\"appId\" width=\"180\" />\n      <el-table-column label=\"是否是主账户\" align=\"center\" prop=\"isMain\">\n        <template #default=\"scope\">\n          <span v-if=\"scope.row.isMain == 1\" style=\"color:red\">是</span>\n          <span v-else>否</span>\n         </template>\n      </el-table-column>\n      <el-table-column label=\"备注\" align=\"center\" prop=\"remark\" />\n      <el-table-column label=\"操作\" align=\"center\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n            v-hasPermi=\"['ma:account:update']\"\n          >\n            编辑\n          </el-button>\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n            v-hasPermi=\"['ma:account:delete']\"\n          >\n            删除\n          </el-button>\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"setMain(scope.row)\"\n            v-hasPermi=\"['ma:account:set-main']\"\n          >\n            设置为主账户\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 对话框(添加 / 修改) -->\n  <AccountForm ref=\"formRef\" @success=\"getList\" />\n</template>\n<script setup lang=\"ts\" name=\"MpAccount\">\nimport * as AccountApi from '@/api/mp/account2'\nimport AccountForm from './AccountForm.vue'\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  name: null,\n  account: null,\n  appId: null\n})\nconst queryFormRef = ref() // 搜索的表单\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await AccountApi.getAccountPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await AccountApi.deleteAccount(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n\nconst setMain = async (row) => {\n  try {\n    // 清空 API 配额的二次确认\n    await message.confirm('是否确认设置公众号账号编号为\"' + row.name + '\"主账户?')\n    // 发起清空 API 配额\n    await AccountApi.setAccountMain(row.id)\n    message.success('设置成功')\n    getList()\n  } catch {}\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/autoReply/components/ReplyForm.vue",
    "content": "<template>\n  <div>\n    <el-form ref=\"formRef\" :model=\"replyForm\" :rules=\"rules\" label-width=\"80px\">\n      <el-form-item label=\"消息类型\" prop=\"requestMessageType\" v-if=\"msgType === MsgType.Message\">\n        <el-select v-model=\"replyForm.requestMessageType\" placeholder=\"请选择\">\n          <template v-for=\"dict in getDictOptions(DICT_TYPE.MP_MESSAGE_TYPE)\" :key=\"dict.value\">\n            <el-option\n              v-if=\"RequestMessageTypes.includes(dict.value)\"\n              :label=\"dict.label\"\n              :value=\"dict.value\"\n            />\n          </template>\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"匹配类型\" prop=\"requestMatch\" v-if=\"msgType === MsgType.Keyword\">\n        <el-select v-model=\"replyForm.requestMatch\" placeholder=\"请选择匹配类型\" clearable>\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.MP_AUTO_REPLY_REQUEST_MATCH)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"关键词\" prop=\"requestKeyword\" v-if=\"msgType === MsgType.Keyword\">\n        <el-input v-model=\"replyForm.requestKeyword\" placeholder=\"请输入内容\" clearable />\n      </el-form-item>\n      <el-form-item label=\"回复消息\">\n        <WxReplySelect v-model=\"reply\" />\n      </el-form-item>\n    </el-form>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport WxReplySelect, { type Reply } from '@/views/mp/components/wx-reply'\nimport type { FormInstance } from 'element-plus'\nimport { MsgType } from './types'\nimport { DICT_TYPE, getDictOptions, getIntDictOptions } from '@/utils/dict'\n\ndefineOptions({ name: 'ReplyForm' })\n\nconst props = defineProps<{\n  modelValue: any\n  reply: Reply\n  msgType: MsgType\n}>()\n\nconst emit = defineEmits<{\n  (e: 'update:reply', v: Reply)\n  (e: 'update:modelValue', v: any)\n}>()\n\nconst reply = computed<Reply>({\n  get: () => props.reply,\n  set: (val) => emit('update:reply', val)\n})\n\nconst replyForm = computed<any>({\n  get: () => props.modelValue,\n  set: (val) => emit('update:modelValue', val)\n})\n\nconst formRef = ref<FormInstance | null>(null) // 表单 ref\n\nconst RequestMessageTypes = ['text', 'image', 'voice', 'video', 'shortvideo', 'location', 'link'] // 允许选择的请求消息类型\n\n// 表单校验\nconst rules = {\n  requestKeyword: [{ required: true, message: '请求的关键字不能为空', trigger: 'blur' }],\n  requestMatch: [{ required: true, message: '请求的关键字的匹配不能为空', trigger: 'blur' }]\n}\n\ndefineExpose({\n  resetFields: () => formRef.value?.resetFields(),\n  validate: async () => formRef.value?.validate()\n})\n</script>\n\n<style scoped></style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/autoReply/components/ReplyTable.vue",
    "content": "<template>\n  <el-table v-loading=\"props.loading\" :data=\"props.list\">\n    <el-table-column\n      label=\"请求消息类型\"\n      align=\"center\"\n      prop=\"requestMessageType\"\n      v-if=\"msgType === MsgType.Message\"\n    />\n    <el-table-column\n      label=\"关键词\"\n      align=\"center\"\n      prop=\"requestKeyword\"\n      v-if=\"msgType === MsgType.Keyword\"\n    />\n    <el-table-column\n      label=\"匹配类型\"\n      align=\"center\"\n      prop=\"requestMatch\"\n      v-if=\"msgType === MsgType.Keyword\"\n    >\n      <template #default=\"scope\">\n        <dict-tag :type=\"DICT_TYPE.MP_AUTO_REPLY_REQUEST_MATCH\" :value=\"scope.row.requestMatch\" />\n      </template>\n    </el-table-column>\n    <el-table-column label=\"回复消息类型\" align=\"center\">\n      <template #default=\"scope\">\n        <dict-tag :type=\"DICT_TYPE.MP_MESSAGE_TYPE\" :value=\"scope.row.responseMessageType\" />\n      </template>\n    </el-table-column>\n    <el-table-column label=\"回复内容\" align=\"center\">\n      <template #default=\"scope\">\n        <div v-if=\"scope.row.responseMessageType === 'text'\">{{ scope.row.responseContent }}</div>\n        <div v-else-if=\"scope.row.responseMessageType === 'voice'\">\n          <WxVoicePlayer v-if=\"scope.row.responseMediaUrl\" :url=\"scope.row.responseMediaUrl\" />\n        </div>\n        <div v-else-if=\"scope.row.responseMessageType === 'image'\">\n          <a target=\"_blank\" :href=\"scope.row.responseMediaUrl\">\n            <img :src=\"scope.row.responseMediaUrl\" style=\"width: 100px\" />\n          </a>\n        </div>\n        <div\n          v-else-if=\"\n            scope.row.responseMessageType === 'video' ||\n            scope.row.responseMessageType === 'shortvideo'\n          \"\n        >\n          <WxVideoPlayer\n            v-if=\"scope.row.responseMediaUrl\"\n            :url=\"scope.row.responseMediaUrl\"\n            style=\"margin-top: 10px\"\n          />\n        </div>\n        <div v-else-if=\"scope.row.responseMessageType === 'news'\">\n          <WxNews :articles=\"scope.row.responseArticles\" />\n        </div>\n        <div v-else-if=\"scope.row.responseMessageType === 'music'\">\n          <WxMusic\n            :title=\"scope.row.responseTitle\"\n            :description=\"scope.row.responseDescription\"\n            :thumb-media-url=\"scope.row.responseThumbMediaUrl\"\n            :music-url=\"scope.row.responseMusicUrl\"\n            :hq-music-url=\"scope.row.responseHqMusicUrl\"\n          />\n        </div>\n      </template>\n    </el-table-column>\n    <el-table-column\n      label=\"创建时间\"\n      align=\"center\"\n      prop=\"createTime\"\n      :formatter=\"dateFormatter\"\n      width=\"180\"\n    />\n    <el-table-column label=\"操作\" align=\"center\">\n      <template #default=\"scope\">\n        <el-button\n          type=\"primary\"\n          link\n          @click=\"emit('on-update', scope.row.id)\"\n          v-hasPermi=\"['mp:auto-reply:update']\"\n        >\n          修改\n        </el-button>\n        <el-button\n          type=\"danger\"\n          link\n          @click=\"emit('on-delete', scope.row.id)\"\n          v-hasPermi=\"['mp:auto-reply:delete']\"\n        >\n          删除\n        </el-button>\n      </template>\n    </el-table-column>\n  </el-table>\n</template>\n<script lang=\"ts\" setup>\nimport WxVideoPlayer from '@/views/mp/components/wx-video-play'\nimport WxVoicePlayer from '@/views/mp/components/wx-voice-play'\nimport WxMusic from '@/views/mp/components/wx-music'\nimport WxNews from '@/views/mp/components/wx-news'\nimport { dateFormatter } from '@/utils/formatTime'\nimport { DICT_TYPE } from '@/utils/dict'\nimport { MsgType } from './types'\n\nconst props = defineProps<{\n  loading: boolean\n  list: any[]\n  msgType: MsgType\n}>()\n\nconst emit = defineEmits<{\n  (e: 'on-update', v: number)\n  (e: 'on-delete', v: number)\n}>()\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/autoReply/components/types.ts",
    "content": "// 消息类型（Follow: 关注时回复；Message: 消息回复；Keyword: 关键词回复）\n// 作为 tab.name，enum 的数字不能随意修改，与 api 参数相关\nexport enum MsgType {\n  Follow = 1,\n  Message = 2,\n  Keyword = 3\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/autoReply/index.vue",
    "content": "<template>\n  <!-- 搜索工作栏 -->\n  <ContentWrap>\n    <el-form class=\"-mb-15px\" :model=\"queryParams\" :inline=\"true\" label-width=\"68px\">\n      <el-form-item label=\"公众号\" prop=\"accountId\">\n        <WxAccountSelect @change=\"onAccountChanged\" />\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- tab 切换 -->\n  <ContentWrap>\n    <el-tabs v-model=\"msgType\" @tab-change=\"onTabChange\">\n      <!-- 操作工具栏 -->\n      <el-row :gutter=\"10\" class=\"mb8\">\n        <el-col :span=\"1.5\">\n          <el-button\n            type=\"primary\"\n            plain\n            @click=\"onCreate\"\n            v-hasPermi=\"['mp:auto-reply:create']\"\n            v-if=\"msgType !== MsgType.Follow || list.length <= 0\"\n          >\n            <Icon icon=\"ep:plus\" />新增\n          </el-button>\n        </el-col>\n      </el-row>\n      <!-- tab 项 -->\n      <el-tab-pane :name=\"MsgType.Follow\">\n        <template #label>\n          <el-row align=\"middle\"><Icon icon=\"ep:star\" class=\"mr-2px\" /> 关注时回复</el-row>\n        </template>\n      </el-tab-pane>\n      <el-tab-pane :name=\"MsgType.Message\">\n        <template #label>\n          <el-row align=\"middle\"><Icon icon=\"ep:chat-line-round\" class=\"mr-2px\" /> 消息回复</el-row>\n        </template>\n      </el-tab-pane>\n      <el-tab-pane :name=\"MsgType.Keyword\">\n        <template #label>\n          <el-row align=\"middle\"><Icon icon=\"fa:newspaper-o\" class=\"mr-2px\" /> 关键词回复</el-row>\n        </template>\n      </el-tab-pane>\n    </el-tabs>\n    <!-- 列表 -->\n    <ReplyTable\n      :loading=\"loading\"\n      :list=\"list\"\n      :msg-type=\"msgType\"\n      @on-update=\"onUpdate\"\n      @on-delete=\"onDelete\"\n    />\n\n    <el-dialog\n      :title=\"isCreating ? '新增自动回复' : '修改自动回复'\"\n      v-model=\"showDialog\"\n      width=\"800px\"\n      destroy-on-close\n    >\n      <ReplyForm v-model=\"replyForm\" v-model:reply=\"reply\" :msg-type=\"msgType\" ref=\"formRef\" />\n      <template #footer>\n        <el-button @click=\"cancel\">取 消</el-button>\n        <el-button type=\"primary\" @click=\"onSubmit\">确 定</el-button>\n      </template>\n    </el-dialog>\n  </ContentWrap>\n</template>\n<script lang=\"ts\" setup>\nimport ReplyForm from '@/views/mp/autoReply/components/ReplyForm.vue'\nimport { type Reply, ReplyType } from '@/views/mp/components/wx-reply'\nimport WxAccountSelect from '@/views/mp/components/wx-account-select'\nimport * as MpAutoReplyApi from '@/api/mp/autoReply'\nimport { ContentWrap } from '@/components/ContentWrap'\nimport type { TabPaneName } from 'element-plus'\nimport ReplyTable from './components/ReplyTable.vue'\nimport { MsgType } from './components/types'\n\ndefineOptions({ name: 'MpAutoReply' })\n\nconst message = useMessage() // 消息\n\nconst accountId = ref(-1) // 公众号ID\nconst msgType = ref<MsgType>(MsgType.Keyword) // 消息类型\nconst loading = ref(true) // 遮罩层\nconst total = ref(0) // 总条数\nconst list = ref<any[]>([]) // 自动回复列表\nconst formRef = ref<InstanceType<typeof ReplyForm> | null>(null) // 表单 ref\n// 查询参数\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  accountId: accountId\n})\n\nconst isCreating = ref(false) // 是否新建（否则编辑）\nconst showDialog = ref(false) // 是否显示弹出层\nconst replyForm = ref<any>({}) // 表单参数\n// 回复消息\nconst reply = ref<Reply>({\n  type: ReplyType.Text,\n  accountId: -1\n})\n\n/** 侦听账号变化 */\nconst onAccountChanged = (id: number) => {\n  accountId.value = id\n  reply.value.accountId = id\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await MpAutoReplyApi.getAutoReplyPage({\n      ...queryParams,\n      type: msgType.value\n    })\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\nconst onTabChange = (tabName: TabPaneName) => {\n  msgType.value = tabName as MsgType\n  handleQuery()\n}\n\n/** 新增按钮操作 */\nconst onCreate = () => {\n  reset()\n  // 打开表单，并设置初始化\n  reply.value = {\n    type: ReplyType.Text,\n    accountId: queryParams.accountId\n  }\n\n  isCreating.value = true\n  showDialog.value = true\n}\n\n/** 修改按钮操作 */\nconst onUpdate = async (id: number) => {\n  reset()\n\n  const data = await MpAutoReplyApi.getAutoReply(id)\n  // 设置属性\n  replyForm.value = { ...data }\n  delete replyForm.value['responseMessageType']\n  delete replyForm.value['responseContent']\n  delete replyForm.value['responseMediaId']\n  delete replyForm.value['responseMediaUrl']\n  delete replyForm.value['responseDescription']\n  delete replyForm.value['responseArticles']\n  reply.value = {\n    type: data.responseMessageType,\n    accountId: queryParams.accountId,\n    content: data.responseContent,\n    mediaId: data.responseMediaId,\n    url: data.responseMediaUrl,\n    title: data.responseTitle,\n    description: data.responseDescription,\n    thumbMediaId: data.responseThumbMediaId,\n    thumbMediaUrl: data.responseThumbMediaUrl,\n    articles: data.responseArticles,\n    musicUrl: data.responseMusicUrl,\n    hqMusicUrl: data.responseHqMusicUrl\n  }\n\n  // 打开表单\n  isCreating.value = false\n  showDialog.value = true\n}\n\n/** 删除按钮操作 */\nconst onDelete = async (id: number) => {\n  await message.confirm('是否确认删除此数据?')\n  await MpAutoReplyApi.deleteAutoReply(id)\n  await getList()\n  message.success('删除成功')\n}\n\nconst onSubmit = async () => {\n  await formRef.value?.validate()\n\n  // 处理回复消息\n  const submitForm: any = { ...replyForm.value }\n  submitForm.responseMessageType = reply.value.type\n  submitForm.responseContent = reply.value.content\n  submitForm.responseMediaId = reply.value.mediaId\n  submitForm.responseMediaUrl = reply.value.url\n  submitForm.responseTitle = reply.value.title\n  submitForm.responseDescription = reply.value.description\n  submitForm.responseThumbMediaId = reply.value.thumbMediaId\n  submitForm.responseThumbMediaUrl = reply.value.thumbMediaUrl\n  submitForm.responseArticles = reply.value.articles\n  submitForm.responseMusicUrl = reply.value.musicUrl\n  submitForm.responseHqMusicUrl = reply.value.hqMusicUrl\n\n  if (replyForm.value.id !== undefined) {\n    await MpAutoReplyApi.updateAutoReply(submitForm)\n    message.success('修改成功')\n  } else {\n    await MpAutoReplyApi.createAutoReply(submitForm)\n    message.success('新增成功')\n  }\n\n  showDialog.value = false\n  await getList()\n}\n\n// 表单重置\nconst reset = () => {\n  replyForm.value = {\n    id: undefined,\n    accountId: queryParams.accountId,\n    type: msgType.value,\n    requestKeyword: undefined,\n    requestMatch: msgType.value === MsgType.Keyword ? 1 : undefined,\n    requestMessageType: undefined\n  }\n  formRef.value?.resetFields()\n}\n\n// 取消按钮\nconst cancel = () => {\n  showDialog.value = false\n  reset()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/components/wx-account-select/index.ts",
    "content": "import WxAccountSelect from './main.vue'\n\nexport default WxAccountSelect\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/components/wx-account-select/main.vue",
    "content": "<template>\n  <el-select v-model=\"account.id\" placeholder=\"请选择公众号\" class=\"!w-240px\" @change=\"onChanged\">\n    <el-option v-for=\"item in accountList\" :key=\"item.id\" :label=\"item.name\" :value=\"item.id\" />\n  </el-select>\n</template>\n\n<script lang=\"ts\" setup>\nimport * as MpAccountApi from '@/api/mp/account'\n\ndefineOptions({ name: 'WxAccountSelect' })\n\nconst account: MpAccountApi.AccountVO = reactive({\n  id: -1,\n  name: ''\n})\n\nconst accountList = ref<MpAccountApi.AccountVO[]>([])\n\nconst emit = defineEmits<{\n  (e: 'change', id: number, name: string)\n}>()\n\nconst handleQuery = async () => {\n  accountList.value = await MpAccountApi.getSimpleAccountList()\n  // 默认选中第一个\n  if (accountList.value.length > 0) {\n    account.id = accountList.value[0].id\n    if (account.id) {\n      account.name = accountList.value[0].name\n      emit('change', account.id, account.name)\n    }\n  }\n}\n\nconst onChanged = (id?: number) => {\n  const found = accountList.value.find((v) => v.id === id)\n  if (account.id) {\n    account.name = found ? found.name : ''\n    emit('change', account.id, account.name)\n  }\n}\n\n/** 初始化 */\nonMounted(() => {\n  handleQuery()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/components/wx-location/index.ts",
    "content": "import WxLocation from './main.vue'\n\nexport default WxLocation\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/components/wx-location/main.vue",
    "content": "<!--\n  【微信消息 - 定位】TODO @Dhb52 目前未启用\n-->\n<template>\n  <div>\n    <el-link\n      type=\"primary\"\n      target=\"_blank\"\n      :href=\"\n        'https://map.qq.com/?type=marker&isopeninfowin=1&markertype=1&pointx=' +\n        locationY +\n        '&pointy=' +\n        locationX +\n        '&name=' +\n        label +\n        '&ref=yudao'\n      \"\n    >\n      <el-col>\n        <el-row>\n          <img\n            :src=\"\n              'https://apis.map.qq.com/ws/staticmap/v2/?zoom=10&markers=color:blue|label:A|' +\n              locationX +\n              ',' +\n              locationY +\n              '&key=' +\n              qqMapKey +\n              '&size=250*180'\n            \"\n          />\n        </el-row>\n        <el-row>\n          <Icon icon=\"ep:location\" />\n          {{ label }}\n        </el-row>\n      </el-col>\n    </el-link>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\ndefineOptions({ name: 'WxLocation' })\n\nconst props = defineProps({\n  locationX: {\n    required: true,\n    type: Number\n  },\n  locationY: {\n    required: true,\n    type: Number\n  },\n  label: {\n    // 地名\n    required: true,\n    type: String\n  },\n  qqMapKey: {\n    // QQ 地图的密钥 https://lbs.qq.com/service/staticV2/staticGuide/staticDoc\n    required: false,\n    type: String,\n    default: 'TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E' // 需要自定义\n  }\n})\n\ndefineExpose({\n  locationX: props.locationX,\n  locationY: props.locationY,\n  label: props.label,\n  qqMapKey: props.qqMapKey\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/components/wx-material-select/index.ts",
    "content": "import WxMaterialSelect from './main.vue'\nimport { NewsType, MaterialType } from './types'\n\nexport { NewsType, MaterialType }\n\nexport default WxMaterialSelect\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/components/wx-material-select/main.vue",
    "content": "<!--\n  - Copyright (C) 2018-2019\n  - All rights reserved, Designed By www.joolun.com\n  yshop源码：\n  ① 移除 avue 组件，使用 ElementUI 原生组件\n-->\n<template>\n  <div class=\"pb-30px\">\n    <!-- 类型：image -->\n    <div v-if=\"props.type === 'image'\">\n      <div class=\"waterfall\" v-loading=\"loading\">\n        <div class=\"waterfall-item\" v-for=\"item in list\" :key=\"item.mediaId\">\n          <img class=\"material-img\" :src=\"item.url\" />\n          <p class=\"item-name\">{{ item.name }}</p>\n          <el-row class=\"ope-row\">\n            <el-button type=\"success\" @click=\"selectMaterialFun(item)\">\n              选择\n              <Icon icon=\"ep:circle-check\" />\n            </el-button>\n          </el-row>\n        </div>\n      </div>\n      <!-- 分页组件 -->\n      <Pagination\n        :total=\"total\"\n        v-model:page=\"queryParams.pageNo\"\n        v-model:limit=\"queryParams.pageSize\"\n        @pagination=\"getMaterialPageFun\"\n      />\n    </div>\n    <!-- 类型：voice -->\n    <div v-else-if=\"props.type === 'voice'\">\n      <!-- 列表 -->\n      <el-table v-loading=\"loading\" :data=\"list\">\n        <el-table-column label=\"编号\" align=\"center\" prop=\"mediaId\" />\n        <el-table-column label=\"文件名\" align=\"center\" prop=\"name\" />\n        <el-table-column label=\"语音\" align=\"center\">\n          <template #default=\"scope\">\n            <WxVoicePlayer :url=\"scope.row.url\" />\n          </template>\n        </el-table-column>\n        <el-table-column\n          label=\"上传时间\"\n          align=\"center\"\n          prop=\"createTime\"\n          width=\"180\"\n          :formatter=\"dateFormatter\"\n        />\n        <el-table-column label=\"操作\" align=\"center\" fixed=\"right\">\n          <template #default=\"scope\">\n            <el-button type=\"primary\" link @click=\"selectMaterialFun(scope.row)\"\n              >选择\n              <Icon icon=\"ep:plus\" />\n            </el-button>\n          </template>\n        </el-table-column>\n      </el-table>\n      <!-- 分页组件 -->\n      <Pagination\n        :total=\"total\"\n        v-model:page=\"queryParams.pageNo\"\n        v-model:limit=\"queryParams.pageSize\"\n        @pagination=\"getPage\"\n      />\n    </div>\n    <!-- 类型：video -->\n    <div v-else-if=\"props.type === 'video'\">\n      <!-- 列表 -->\n      <el-table v-loading=\"loading\" :data=\"list\">\n        <el-table-column label=\"编号\" align=\"center\" prop=\"mediaId\" />\n        <el-table-column label=\"文件名\" align=\"center\" prop=\"name\" />\n        <el-table-column label=\"标题\" align=\"center\" prop=\"title\" />\n        <el-table-column label=\"介绍\" align=\"center\" prop=\"introduction\" />\n        <el-table-column label=\"视频\" align=\"center\">\n          <template #default=\"scope\">\n            <WxVideoPlayer :url=\"scope.row.url\" />\n          </template>\n        </el-table-column>\n        <el-table-column\n          label=\"上传时间\"\n          align=\"center\"\n          prop=\"createTime\"\n          width=\"180\"\n          :formatter=\"dateFormatter\"\n        />\n        <el-table-column\n          label=\"操作\"\n          align=\"center\"\n          fixed=\"right\"\n          class-name=\"small-padding fixed-width\"\n        >\n          <template #default=\"scope\">\n            <el-button type=\"primary\" link @click=\"selectMaterialFun(scope.row)\"\n              >选择\n              <Icon icon=\"akar-icons:circle-plus\" />\n            </el-button>\n          </template>\n        </el-table-column>\n      </el-table>\n      <!-- 分页组件 -->\n      <Pagination\n        :total=\"total\"\n        v-model:page=\"queryParams.pageNo\"\n        v-model:limit=\"queryParams.pageSize\"\n        @pagination=\"getMaterialPageFun\"\n      />\n    </div>\n    <!-- 类型：news -->\n    <div v-else-if=\"props.type === 'news'\">\n      <div class=\"waterfall\" v-loading=\"loading\">\n        <div class=\"waterfall-item\" v-for=\"item in list\" :key=\"item.mediaId\">\n          <div v-if=\"item.content && item.content.newsItem\">\n            <WxNews :articles=\"item.content.newsItem\" />\n            <el-row class=\"ope-row\">\n              <el-button type=\"success\" @click=\"selectMaterialFun(item)\">\n                选择\n                <Icon icon=\"ep:circle-check\" />\n              </el-button>\n            </el-row>\n          </div>\n        </div>\n      </div>\n      <!-- 分页组件 -->\n      <Pagination\n        :total=\"total\"\n        v-model:page=\"queryParams.pageNo\"\n        v-model:limit=\"queryParams.pageSize\"\n        @pagination=\"getMaterialPageFun\"\n      />\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport WxNews from '@/views/mp/components/wx-news'\nimport WxVoicePlayer from '@/views/mp/components/wx-voice-play'\nimport WxVideoPlayer from '@/views/mp/components/wx-video-play'\nimport { NewsType } from './types'\nimport * as MpMaterialApi from '@/api/mp/material'\nimport * as MpFreePublishApi from '@/api/mp/freePublish'\nimport * as MpDraftApi from '@/api/mp/draft'\nimport { dateFormatter } from '@/utils/formatTime'\n\ndefineOptions({ name: 'WxMaterialSelect' })\n\nconst props = withDefaults(\n  defineProps<{\n    type: string\n    accountId: number\n    newsType?: NewsType\n  }>(),\n  {\n    newsType: NewsType.Published\n  }\n)\n\nconst emit = defineEmits(['select-material'])\n\n// 遮罩层\nconst loading = ref(false)\n// 总条数\nconst total = ref(0)\n// 数据列表\nconst list = ref<any[]>([])\n// 查询参数\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  accountId: props.accountId\n})\n\nconst selectMaterialFun = (item) => {\n  emit('select-material', item)\n}\n\nconst getPage = async () => {\n  loading.value = true\n  try {\n    if (props.type === 'news' && props.newsType === NewsType.Published) {\n      // 【图文】+ 【已发布】\n      await getFreePublishPageFun()\n    } else if (props.type === 'news' && props.newsType === NewsType.Draft) {\n      // 【图文】+ 【草稿】\n      await getDraftPageFun()\n    } else {\n      // 【素材】\n      await getMaterialPageFun()\n    }\n  } finally {\n    loading.value = false\n  }\n}\n\nconst getMaterialPageFun = async () => {\n  const data = await MpMaterialApi.getMaterialPage({\n    ...queryParams,\n    type: props.type\n  })\n  list.value = data.list\n  total.value = data.total\n}\n\nconst getFreePublishPageFun = async () => {\n  const data = await MpFreePublishApi.getFreePublishPage(queryParams)\n  data.list.forEach((item: any) => {\n    const articles = item.content.newsItem\n    articles.forEach((article: any) => {\n      article.picUrl = article.thumbUrl\n    })\n  })\n  list.value = data.list\n  total.value = data.total\n}\n\nconst getDraftPageFun = async () => {\n  const data = await MpDraftApi.getDraftPage(queryParams)\n  data.list.forEach((draft: any) => {\n    const articles = draft.content.newsItem\n    articles.forEach((article: any) => {\n      article.picUrl = article.thumbUrl\n    })\n  })\n  list.value = data.list\n  total.value = data.total\n}\n\nonMounted(async () => {\n  getPage()\n})\n</script>\n<style lang=\"scss\" scoped>\n@media (width >= 992px) and (width <= 1300px) {\n  .waterfall {\n    column-count: 3;\n  }\n\n  p {\n    color: red;\n  }\n}\n\n@media (width >= 768px) and (width <= 991px) {\n  .waterfall {\n    column-count: 2;\n  }\n\n  p {\n    color: orange;\n  }\n}\n\n@media (width <= 767px) {\n  .waterfall {\n    column-count: 1;\n  }\n}\n\n.waterfall {\n  width: 100%;\n  column-gap: 10px;\n  column-count: 5;\n  margin: 0 auto;\n}\n\n.waterfall-item {\n  padding: 10px;\n  margin-bottom: 10px;\n  break-inside: avoid;\n  border: 1px solid #eaeaea;\n}\n\n.material-img {\n  width: 100%;\n}\n\np {\n  line-height: 30px;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/components/wx-material-select/types.ts",
    "content": "export enum NewsType {\n  Draft = '2',\n  Published = '1'\n}\n\nexport enum MaterialType {\n  Image = 'image',\n  Voice = 'voice',\n  Video = 'video',\n  News = 'news'\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/components/wx-msg/card.scss",
    "content": ".avue-card {\n  &__item {\n    margin-bottom: 16px;\n    border: 1px solid #e8e8e8;\n    background-color: #fff;\n    box-sizing: border-box;\n    color: rgba(0, 0, 0, 0.65);\n    font-size: 14px;\n    font-variant: tabular-nums;\n    line-height: 1.5;\n    list-style: none;\n    font-feature-settings: 'tnum';\n    cursor: pointer;\n    height: 200px;\n\n    &:hover {\n      border-color: rgba(0, 0, 0, 0.09);\n      box-shadow: 0 2px 8px rgba(0, 0, 0, 0.09);\n    }\n\n    &--add {\n      border: 1px dashed #000;\n      width: 100%;\n      color: rgba(0, 0, 0, 0.45);\n      background-color: #fff;\n      border-color: #d9d9d9;\n      border-radius: 2px;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      font-size: 16px;\n\n      i {\n        margin-right: 10px;\n      }\n\n      &:hover {\n        color: #40a9ff;\n        background-color: #fff;\n        border-color: #40a9ff;\n      }\n    }\n  }\n\n  &__body {\n    display: flex;\n    padding: 24px;\n  }\n\n  &__detail {\n    flex: 1;\n  }\n\n  &__avatar {\n    width: 48px;\n    height: 48px;\n    border-radius: 48px;\n    overflow: hidden;\n    margin-right: 12px;\n\n    img {\n      width: 100%;\n      height: 100%;\n    }\n  }\n\n  &__title {\n    color: rgba(0, 0, 0, 0.85);\n    margin-bottom: 12px;\n    font-size: 16px;\n\n    &:hover {\n      color: #1890ff;\n    }\n  }\n\n  &__info {\n    color: rgba(0, 0, 0, 0.45);\n    display: -webkit-box;\n    -webkit-box-orient: vertical;\n    -webkit-line-clamp: 3;\n    overflow: hidden;\n    height: 64px;\n  }\n\n  &__menu {\n    display: flex;\n    justify-content: space-around;\n    height: 50px;\n    background: #f7f9fa;\n    color: rgba(0, 0, 0, 0.45);\n    text-align: center;\n    line-height: 50px;\n\n    &:hover {\n      color: #1890ff;\n    }\n  }\n}\n\n/** joolun 额外加的 */\n.avue-comment__main {\n  flex: unset !important;\n  border-radius: 5px !important;\n  margin: 0 8px !important;\n}\n\n.avue-comment__header {\n  border-top-left-radius: 5px;\n  border-top-right-radius: 5px;\n}\n\n.avue-comment__body {\n  border-bottom-right-radius: 5px;\n  border-bottom-left-radius: 5px;\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/components/wx-msg/comment.scss",
    "content": "/* 来自 https://github.com/nmxiaowei/avue/blob/master/styles/src/element-ui/comment.scss  */\n.avue-comment {\n  margin-bottom: 30px;\n  display: flex;\n  align-items: flex-start;\n\n  &--reverse {\n    flex-direction: row-reverse;\n\n    .avue-comment__main {\n      &:before,\n      &:after {\n        left: auto;\n        right: -8px;\n        border-width: 8px 0 8px 8px;\n      }\n\n      &:before {\n        border-left-color: #dedede;\n      }\n\n      &:after {\n        border-left-color: #f8f8f8;\n        margin-right: 1px;\n        margin-left: auto;\n      }\n    }\n  }\n\n  &__avatar {\n    width: 48px;\n    height: 48px;\n    border-radius: 50%;\n    border: 1px solid transparent;\n    box-sizing: border-box;\n    vertical-align: middle;\n  }\n\n  &__header {\n    padding: 5px 15px;\n    background: #f8f8f8;\n    border-bottom: 1px solid #eee;\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n  }\n\n  &__author {\n    font-weight: 700;\n    font-size: 14px;\n    color: #999;\n  }\n\n  &__main {\n    flex: 1;\n    margin: 0 20px;\n    position: relative;\n    border: 1px solid #dedede;\n    border-radius: 2px;\n\n    &:before,\n    &:after {\n      position: absolute;\n      top: 10px;\n      left: -8px;\n      right: 100%;\n      width: 0;\n      height: 0;\n      display: block;\n      content: ' ';\n      border-color: transparent;\n      border-style: solid solid outset;\n      border-width: 8px 8px 8px 0;\n      pointer-events: none;\n    }\n\n    &:before {\n      border-right-color: #dedede;\n      z-index: 1;\n    }\n\n    &:after {\n      border-right-color: #f8f8f8;\n      margin-left: 1px;\n      z-index: 2;\n    }\n  }\n\n  &__body {\n    padding: 15px;\n    overflow: hidden;\n    background: #fff;\n    font-family:\n      Segoe UI,\n      Lucida Grande,\n      Helvetica,\n      Arial,\n      Microsoft YaHei,\n      FreeSans,\n      Arimo,\n      Droid Sans,\n      wenquanyi micro hei,\n      Hiragino Sans GB,\n      Hiragino Sans GB W3,\n      FontAwesome,\n      sans-serif;\n    color: #333;\n    font-size: 14px;\n  }\n\n  blockquote {\n    margin: 0;\n    font-family:\n      Georgia,\n      Times New Roman,\n      Times,\n      Kai,\n      Kaiti SC,\n      KaiTi,\n      BiauKai,\n      FontAwesome,\n      serif;\n    padding: 1px 0 1px 15px;\n    border-left: 4px solid #ddd;\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/components/wx-msg/components/Msg.vue",
    "content": "<template>\n  <div>\n    <MsgEvent v-if=\"item.type === MsgType.Event\" :item=\"item\" />\n\n    <div v-else-if=\"item.type === MsgType.Text\">{{ item.content }}</div>\n\n    <div v-else-if=\"item.type === MsgType.Voice\">\n      <WxVoicePlayer :url=\"item.mediaUrl\" :content=\"item.recognition\" />\n    </div>\n\n    <div v-else-if=\"item.type === MsgType.Image\">\n      <a target=\"_blank\" :href=\"item.mediaUrl\">\n        <img :src=\"item.mediaUrl\" style=\"width: 100px\" />\n      </a>\n    </div>\n\n    <div\n      v-else-if=\"item.type === MsgType.Video || item.type === 'shortvideo'\"\n      style=\"text-align: center\"\n    >\n      <WxVideoPlayer :url=\"item.mediaUrl\" />\n    </div>\n\n    <div v-else-if=\"item.type === MsgType.Link\" class=\"avue-card__detail\">\n      <el-link type=\"success\" :underline=\"false\" target=\"_blank\" :href=\"item.url\">\n        <div class=\"avue-card__title\"><i class=\"el-icon-link\"></i>{{ item.title }}</div>\n      </el-link>\n      <div class=\"avue-card__info\" style=\"height: unset\">{{ item.description }}</div>\n    </div>\n\n    <div v-else-if=\"item.type === MsgType.Location\">\n      <WxLocation :label=\"item.label\" :location-y=\"item.locationY\" :location-x=\"item.locationX\" />\n    </div>\n\n    <div v-else-if=\"item.type === MsgType.News\" style=\"width: 300px\">\n      <WxNews :articles=\"item.articles\" />\n    </div>\n\n    <div v-else-if=\"item.type === MsgType.Music\">\n      <WxMusic\n        :title=\"item.title\"\n        :description=\"item.description\"\n        :thumb-media-url=\"item.thumbMediaUrl\"\n        :music-url=\"item.musicUrl\"\n        :hq-music-url=\"item.hqMusicUrl\"\n      />\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport WxVideoPlayer from '@/views/mp/components/wx-video-play'\nimport WxVoicePlayer from '@/views/mp/components/wx-voice-play'\nimport WxNews from '@/views/mp/components/wx-news'\nimport WxLocation from '@/views/mp/components/wx-location'\nimport WxMusic from '@/views/mp/components/wx-music'\nimport MsgEvent from './MsgEvent.vue'\nimport { MsgType } from '../types'\n\ndefineOptions({ name: 'Msg' })\n\nconst props = defineProps<{\n  item: any\n}>()\n\nconst item = ref<any>(props.item)\n</script>\n\n<style scoped></style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/components/wx-msg/components/MsgEvent.vue",
    "content": "<template>\n  <div>\n    <div v-if=\"item.event === 'subscribe'\">\n      <el-tag type=\"success\">关注</el-tag>\n    </div>\n    <div v-else-if=\"item.event === 'unsubscribe'\">\n      <el-tag type=\"danger\">取消关注</el-tag>\n    </div>\n    <div v-else-if=\"item.event === 'CLICK'\">\n      <el-tag>点击菜单</el-tag>\n      【{{ item.eventKey }}】\n    </div>\n    <div v-else-if=\"item.event === 'VIEW'\">\n      <el-tag>点击菜单链接</el-tag>\n      【{{ item.eventKey }}】\n    </div>\n    <div v-else-if=\"item.event === 'scancode_waitmsg'\">\n      <el-tag>扫码结果</el-tag>\n      【{{ item.eventKey }}】\n    </div>\n    <div v-else-if=\"item.event === 'scancode_push'\">\n      <el-tag>扫码结果</el-tag>\n      【{{ item.eventKey }}】\n    </div>\n    <div v-else-if=\"item.event === 'pic_sysphoto'\">\n      <el-tag>系统拍照发图</el-tag>\n    </div>\n    <div v-else-if=\"item.event === 'pic_photo_or_album'\">\n      <el-tag>拍照或者相册</el-tag>\n    </div>\n    <div v-else-if=\"item.event === 'pic_weixin'\">\n      <el-tag>微信相册</el-tag>\n    </div>\n    <div v-else-if=\"item.event === 'location_select'\">\n      <el-tag>选择地理位置</el-tag>\n    </div>\n    <div v-else>\n      <el-tag type=\"danger\">未知事件类型</el-tag>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nconst props = defineProps<{\n  item: any\n}>()\n\nconst item = ref(props.item)\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/components/wx-msg/components/MsgList.vue",
    "content": "<template>\n  <div class=\"execution\" v-for=\"item in props.list\" :key=\"item.id\">\n    <div\n      class=\"avue-comment\"\n      :class=\"{ 'avue-comment--reverse': item.sendFrom === SendFrom.MpBot }\"\n    >\n      <div class=\"avatar-div\">\n        <img :src=\"getAvatar(item.sendFrom)\" class=\"avue-comment__avatar\" />\n        <div class=\"avue-comment__author\">\n          {{ getNickname(item.sendFrom) }}\n        </div>\n      </div>\n      <div class=\"avue-comment__main\">\n        <div class=\"avue-comment__header\">\n          <div class=\"avue-comment__create_time\">{{ formatDate(item.createTime) }}</div>\n        </div>\n        <div\n          class=\"avue-comment__body\"\n          :style=\"item.sendFrom === SendFrom.MpBot ? 'background: #6BED72;' : ''\"\n        >\n          <Msg :item=\"item\" />\n        </div>\n      </div>\n    </div>\n  </div>\n</template>\n<script lang=\"ts\" setup>\nimport Msg from './Msg.vue'\nimport { formatDate } from '@/utils/formatTime'\nimport { User } from '../types'\nimport avatarWechat from '@/assets/imgs/wechat.png'\n\ndefineOptions({ name: 'MsgList' })\n\nconst props = defineProps<{\n  list: any[]\n  accountId: number\n  user: User\n}>()\n\nenum SendFrom {\n  User = 1,\n  MpBot = 2\n}\n\nconst getAvatar = (sendFrom: SendFrom) =>\n  sendFrom === SendFrom.User ? props.user.avatar : avatarWechat\n\nconst getNickname = (sendFrom: SendFrom) =>\n  sendFrom === SendFrom.User ? props.user.nickname : '公众号'\n</script>\n\n<style lang=\"scss\" scoped>\n/* 因为 joolun 实现依赖 avue 组件，该页面使用了 comment.scss、card.scc  */\n@import url('../comment.scss');\n@import url('../card.scss');\n\n.avatar-div {\n  width: 80px;\n  text-align: center;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/components/wx-msg/index.ts",
    "content": "import WxMsg from './main.vue'\nimport { MsgType } from './types'\n\nexport { MsgType }\n\nexport default WxMsg\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/components/wx-msg/main.vue",
    "content": "<!--\n  - Copyright (C) 2018-2019\n  - All rights reserved, Designed By www.joolun.com\n  yshop源码：\n  ① 移除暂时用不到的 websocket\n  ② 代码优化，补充注释，提升阅读性\n-->\n<template>\n  <ContentWrap>\n    <div class=\"msg-div\" ref=\"msgDivRef\">\n      <!-- 加载更多 -->\n      <div v-loading=\"loading\"></div>\n      <div v-if=\"!loading\">\n        <div class=\"el-table__empty-block\" v-if=\"hasMore\" @click=\"loadMore\"\n          ><span class=\"el-table__empty-text\">点击加载更多</span></div\n        >\n        <div class=\"el-table__empty-block\" v-if=\"!hasMore\"\n          ><span class=\"el-table__empty-text\">没有更多了</span></div\n        >\n      </div>\n\n      <!-- 消息列表 -->\n      <MsgList :list=\"list\" :account-id=\"accountId\" :user=\"user\" />\n    </div>\n\n    <div class=\"msg-send\" v-loading=\"sendLoading\">\n      <WxReplySelect ref=\"replySelectRef\" v-model=\"reply\" />\n      <el-button type=\"success\" class=\"send-but\" @click=\"sendMsg\">发送(S)</el-button>\n    </div>\n  </ContentWrap>\n</template>\n\n<script lang=\"ts\" setup>\nimport WxReplySelect, { Reply, ReplyType } from '@/views/mp/components/wx-reply'\nimport MsgList from './components/MsgList.vue'\nimport { getMessagePage, sendMessage } from '@/api/mp/message'\nimport { getUser } from '@/api/mp/user'\nimport profile from '@/assets/imgs/profile.jpg'\nimport { User } from './types'\n\ndefineOptions({ name: 'WxMsg' })\n\nconst message = useMessage() // 消息弹窗\n\nconst props = defineProps({\n  userId: {\n    type: Number,\n    required: true\n  }\n})\n\nconst accountId = ref(-1) // 公众号ID，需要通过userId初始化\nconst loading = ref(false) // 消息列表是否正在加载中\nconst hasMore = ref(true) // 是否可以加载更多\nconst list = ref<any[]>([]) // 消息列表\nconst queryParams = reactive({\n  pageNo: 1, // 当前页数\n  pageSize: 14, // 每页显示多少条\n  accountId: accountId\n})\n\n// 由于微信不再提供昵称，直接使用“用户”展示\nconst user: User = reactive({\n  nickname: '用户',\n  avatar: profile,\n  accountId: accountId // 公众号账号编号\n})\n\n// ========= 消息发送 =========\nconst sendLoading = ref(false) // 发送消息是否加载中\n// 微信发送消息\nconst reply = ref<Reply>({\n  type: ReplyType.Text,\n  accountId: -1,\n  articles: []\n})\n\nconst replySelectRef = ref<InstanceType<typeof WxReplySelect> | null>(null) // WxReplySelect组件ref，用于消息发送成功后清除内容\nconst msgDivRef = ref<HTMLDivElement | null>(null) // 消息显示窗口ref，用于滚动到底部\n\n/** 完成加载 */\nonMounted(async () => {\n  const data = await getUser(props.userId)\n  user.nickname = data.nickname?.length > 0 ? data.nickname : user.nickname\n  user.avatar = user.avatar?.length > 0 ? data.avatar : user.avatar\n  accountId.value = data.accountId\n  reply.value.accountId = data.accountId\n\n  refreshChange()\n})\n\n// 执行发送\nconst sendMsg = async () => {\n  if (!unref(reply)) {\n    return\n  }\n  // 公众号限制：客服消息，公众号只允许发送一条\n  if (\n    reply.value.type === ReplyType.News &&\n    reply.value.articles &&\n    reply.value.articles.length > 1\n  ) {\n    reply.value.articles = [reply.value.articles[0]]\n    message.success('图文消息条数限制在 1 条以内，已默认发送第一条')\n  }\n\n  const data = await sendMessage({ userId: props.userId, ...reply.value })\n  sendLoading.value = false\n\n  list.value = [...list.value, ...[data]]\n  await scrollToBottom()\n\n  // 发送后清空数据\n  replySelectRef.value?.clear()\n}\n\nconst loadMore = () => {\n  queryParams.pageNo++\n  getPage(queryParams, null)\n}\n\nconst getPage = async (page: any, params: any = null) => {\n  loading.value = true\n  let dataTemp = await getMessagePage(\n    Object.assign(\n      {\n        pageNo: page.pageNo,\n        pageSize: page.pageSize,\n        userId: props.userId,\n        accountId: page.accountId\n      },\n      params\n    )\n  )\n\n  const scrollHeight = msgDivRef.value?.scrollHeight ?? 0\n  // 处理数据\n  const data = dataTemp.list.reverse()\n  list.value = [...data, ...list.value]\n  loading.value = false\n  if (data.length < queryParams.pageSize || data.length === 0) {\n    hasMore.value = false\n  }\n  queryParams.pageNo = page.pageNo\n  queryParams.pageSize = page.pageSize\n  // 滚动到原来的位置\n  if (queryParams.pageNo === 1) {\n    // 定位到消息底部\n    await scrollToBottom()\n  } else if (data.length !== 0) {\n    // 定位滚动条\n    await nextTick()\n    if (scrollHeight !== 0) {\n      if (msgDivRef.value) {\n        msgDivRef.value.scrollTop = msgDivRef.value.scrollHeight - scrollHeight - 100\n      }\n    }\n  }\n}\n\nconst refreshChange = () => {\n  getPage(queryParams)\n}\n\n/** 定位到消息底部 */\nconst scrollToBottom = async () => {\n  await nextTick()\n  if (msgDivRef.value) {\n    msgDivRef.value.scrollTop = msgDivRef.value.scrollHeight\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.msg-div {\n  height: 50vh;\n  margin-right: 10px;\n  margin-left: 10px;\n  overflow: auto;\n  background-color: #eaeaea;\n}\n\n.msg-send {\n  padding: 10px;\n}\n\n.send-but {\n  float: right;\n  margin-top: 8px;\n  margin-bottom: 8px;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/components/wx-msg/types.ts",
    "content": "export enum MsgType {\n  Event = 'event',\n  Text = 'text',\n  Voice = 'voice',\n  Image = 'image',\n  Video = 'video',\n  Link = 'link',\n  Location = 'location',\n  Music = 'music',\n  News = 'news'\n}\n\nexport interface User {\n  nickname: string\n  avatar: string\n  accountId: number\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/components/wx-music/index.ts",
    "content": "import WxMusic from './main.vue'\n\nexport default WxMusic\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/components/wx-music/main.vue",
    "content": "<!--\n  【微信消息 - 音乐】\n-->\n<template>\n  <div>\n    <el-link\n      type=\"success\"\n      :underline=\"false\"\n      target=\"_blank\"\n      :href=\"hqMusicUrl ? hqMusicUrl : musicUrl\"\n    >\n      <div\n        class=\"avue-card__body\"\n        style=\"padding: 10px; background-color: #fff; border-radius: 5px\"\n      >\n        <div class=\"avue-card__avatar\">\n          <img :src=\"thumbMediaUrl\" alt=\"\" />\n        </div>\n        <div class=\"avue-card__detail\">\n          <div class=\"avue-card__title\" style=\"margin-bottom: unset\">{{ title }}</div>\n          <div class=\"avue-card__info\" style=\"height: unset\">{{ description }}</div>\n        </div>\n      </div>\n    </el-link>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\ndefineOptions({ name: 'WxMusic' })\n\nconst props = defineProps({\n  title: {\n    required: false,\n    type: String\n  },\n  description: {\n    required: false,\n    type: String\n  },\n  musicUrl: {\n    required: false,\n    type: String\n  },\n  hqMusicUrl: {\n    required: false,\n    type: String\n  },\n  thumbMediaUrl: {\n    required: true,\n    type: String\n  }\n})\n\ndefineExpose({\n  musicUrl: props.musicUrl\n})\n</script>\n\n<style lang=\"scss\" scoped>\n/* 因为 joolun 实现依赖 avue 组件，该页面使用了 card.scss  */\n@import url('../wx-msg/card.scss');\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/components/wx-news/index.ts",
    "content": "import WxNews from './main.vue'\n\nexport default WxNews\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/components/wx-news/main.vue",
    "content": "<!--\n  - Copyright (C) 2018-2019\n  - All rights reserved, Designed By www.joolun.com\n  【微信消息 - 图文】\n  yshop源码：\n  ① 代码优化，补充注释，提升阅读性\n-->\n<template>\n  <div class=\"news-home\">\n    <div v-for=\"(article, index) in articles\" :key=\"index\" class=\"news-div\">\n      <!-- 头条 -->\n      <a v-if=\"index === 0\" :href=\"article.url\" target=\"_blank\">\n        <div class=\"news-main\">\n          <div class=\"news-content\">\n            <el-image\n              :src=\"article.picUrl\"\n              class=\"material-img\"\n              style=\"width: 100%; height: 120px\"\n            />\n            <div class=\"news-content-title\">\n              <span>{{ article.title }}</span>\n            </div>\n          </div>\n        </div>\n      </a>\n      <!-- 二条/三条等等 -->\n      <a v-else :href=\"article.url\" target=\"_blank\">\n        <div class=\"news-main-item\">\n          <div class=\"news-content-item\">\n            <div class=\"news-content-item-title\">{{ article.title }}</div>\n            <div class=\"news-content-item-img\">\n              <img :src=\"article.picUrl\" class=\"material-img\" height=\"100%\" />\n            </div>\n          </div>\n        </div>\n      </a>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\ndefineOptions({ name: 'WxNews' })\n\nconst props = withDefaults(\n  defineProps<{\n    articles: any[] | null\n  }>(),\n  {\n    articles: null\n  }\n)\n\ndefineExpose({\n  articles: props.articles\n})\n</script>\n\n<style lang=\"scss\" scoped>\n.news-home {\n  width: 100%;\n  margin: auto;\n  background-color: #fff;\n}\n\n.news-main {\n  width: 100%;\n  margin: auto;\n}\n\n.news-content {\n  position: relative;\n  width: 100%;\n  background-color: #acadae;\n}\n\n.news-content-title {\n  position: absolute;\n  bottom: 0;\n  left: 0;\n  display: inline-block;\n  width: 98%;\n  padding: 1%;\n  font-size: 12px;\n  color: #fff;\n  white-space: normal;\n  background-color: black;\n  opacity: 0.65;\n  box-sizing: unset !important;\n}\n\n.news-main-item {\n  padding: 5px 0;\n  background-color: #fff;\n  border-top: 1px solid #eaeaea;\n}\n\n.news-content-item {\n  position: relative;\n}\n\n.news-content-item-title {\n  display: inline-block;\n  width: 70%;\n  margin-left: 1%;\n  font-size: 10px;\n  white-space: normal;\n}\n\n.news-content-item-img {\n  display: inline-block;\n  width: 25%;\n  margin-right: 1%;\n  background-color: #acadae;\n}\n\n.material-img {\n  width: 100%;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/components/wx-reply/components/TabImage.vue",
    "content": "<template>\n  <div>\n    <!-- 情况一：已经选择好素材、或者上传好图片 -->\n    <div class=\"select-item\" v-if=\"reply.url\">\n      <img class=\"material-img\" :src=\"reply.url\" />\n      <p class=\"item-name\" v-if=\"reply.name\">{{ reply.name }}</p>\n      <el-row class=\"ope-row\" justify=\"center\">\n        <el-button type=\"danger\" circle @click=\"onDelete\">\n          <Icon icon=\"ep:delete\" />\n        </el-button>\n      </el-row>\n    </div>\n    <!-- 情况二：未做完上述操作 -->\n    <el-row v-else style=\"text-align: center\" align=\"middle\">\n      <!-- 选择素材 -->\n      <el-col :span=\"12\" class=\"col-select\">\n        <el-button type=\"success\" @click=\"showDialog = true\">\n          素材库选择 <Icon icon=\"ep:circle-check\" />\n        </el-button>\n        <el-dialog\n          title=\"选择图片\"\n          v-model=\"showDialog\"\n          width=\"90%\"\n          append-to-body\n          destroy-on-close\n        >\n          <WxMaterialSelect\n            type=\"image\"\n            :account-id=\"reply.accountId\"\n            @select-material=\"selectMaterial\"\n          />\n        </el-dialog>\n      </el-col>\n      <!-- 文件上传 -->\n      <el-col :span=\"12\" class=\"col-add\">\n        <el-upload\n          :action=\"UPLOAD_URL\"\n          :headers=\"HEADERS\"\n          multiple\n          :limit=\"1\"\n          :file-list=\"fileList\"\n          :data=\"uploadData\"\n          :before-upload=\"beforeImageUpload\"\n          :on-success=\"onUploadSuccess\"\n        >\n          <el-button type=\"primary\">上传图片</el-button>\n          <template #tip>\n            <span>\n              <div class=\"el-upload__tip\">支持 bmp/png/jpeg/jpg/gif 格式，大小不超过 2M</div>\n            </span>\n          </template>\n        </el-upload>\n      </el-col>\n    </el-row>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport WxMaterialSelect from '@/views/mp/components/wx-material-select'\nimport { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload'\nimport type { UploadRawFile } from 'element-plus'\nimport { getAccessToken } from '@/utils/auth'\nimport { Reply } from './types'\nconst message = useMessage()\n\nconst UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-temporary'\nconst HEADERS = { Authorization: 'Bearer ' + getAccessToken() } // 设置上传的请求头部\n\nconst props = defineProps<{\n  modelValue: Reply\n}>()\nconst emit = defineEmits<{\n  (e: 'update:modelValue', v: Reply)\n}>()\nconst reply = computed<Reply>({\n  get: () => props.modelValue,\n  set: (val) => emit('update:modelValue', val)\n})\n\nconst showDialog = ref(false)\nconst fileList = ref([])\nconst uploadData = reactive({\n  accountId: reply.value.accountId,\n  type: 'image',\n  title: '',\n  introduction: ''\n})\n\nconst beforeImageUpload = (rawFile: UploadRawFile) => useBeforeUpload(UploadType.Image, 2)(rawFile)\n\nconst onUploadSuccess = (res: any) => {\n  if (res.code !== 0) {\n    message.error('上传出错：' + res.msg)\n    return false\n  }\n\n  // 清空上传时的各种数据\n  fileList.value = []\n  uploadData.title = ''\n  uploadData.introduction = ''\n\n  // 上传好的文件，本质是个素材，所以可以进行选中\n  selectMaterial(res.data)\n}\n\nconst onDelete = () => {\n  reply.value.mediaId = null\n  reply.value.url = null\n  reply.value.name = null\n}\n\nconst selectMaterial = (item) => {\n  showDialog.value = false\n\n  // reply.value.type = 'image'\n  reply.value.mediaId = item.mediaId\n  reply.value.url = item.url\n  reply.value.name = item.name\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.select-item {\n  width: 280px;\n  padding: 10px;\n  margin: 0 auto 10px;\n  border: 1px solid #eaeaea;\n\n  .material-img {\n    width: 100%;\n  }\n\n  .item-name {\n    overflow: hidden;\n    font-size: 12px;\n    text-align: center;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n\n    .item-infos {\n      width: 30%;\n      margin: auto;\n    }\n\n    .ope-row {\n      padding-top: 10px;\n      text-align: center;\n    }\n  }\n\n  .col-select {\n    width: 49.5%;\n    height: 160px;\n    padding: 50px 0;\n    border: 1px solid rgb(234 234 234);\n  }\n\n  .col-add {\n    float: right;\n    width: 49.5%;\n    height: 160px;\n    padding: 50px 0;\n    border: 1px solid rgb(234 234 234);\n\n    .el-upload__tip {\n      line-height: 18px;\n      text-align: center;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/components/wx-reply/components/TabMusic.vue",
    "content": "<template>\n  <div>\n    <el-row align=\"middle\" justify=\"center\">\n      <el-col :span=\"6\">\n        <el-row align=\"middle\" justify=\"center\" class=\"thumb-div\">\n          <el-col :span=\"24\">\n            <el-row align=\"middle\" justify=\"center\">\n              <img style=\"width: 100px\" v-if=\"reply.thumbMediaUrl\" :src=\"reply.thumbMediaUrl\" />\n              <icon v-else icon=\"ep:plus\" />\n            </el-row>\n            <el-row align=\"middle\" justify=\"center\" style=\"margin-top: 2%\">\n              <div class=\"thumb-but\">\n                <el-upload\n                  :action=\"UPLOAD_URL\"\n                  :headers=\"HEADERS\"\n                  multiple\n                  :limit=\"1\"\n                  :file-list=\"fileList\"\n                  :data=\"uploadData\"\n                  :before-upload=\"beforeImageUpload\"\n                  :on-success=\"onUploadSuccess\"\n                >\n                  <template #trigger>\n                    <el-button type=\"primary\" link>本地上传</el-button>\n                  </template>\n                  <el-button type=\"primary\" link @click=\"showDialog = true\" style=\"margin-left: 5px\"\n                    >素材库选择\n                  </el-button>\n                </el-upload>\n              </div>\n            </el-row>\n          </el-col>\n        </el-row>\n        <el-dialog\n          title=\"选择图片\"\n          v-model=\"showDialog\"\n          width=\"80%\"\n          append-to-body\n          destroy-on-close\n        >\n          <WxMaterialSelect\n            type=\"image\"\n            :account-id=\"reply.accountId\"\n            @select-material=\"selectMaterial\"\n          />\n        </el-dialog>\n      </el-col>\n      <el-col :span=\"18\">\n        <el-input v-model=\"reply.title\" placeholder=\"请输入标题\" />\n        <div style=\"margin: 20px 0\"></div>\n        <el-input v-model=\"reply.description\" placeholder=\"请输入描述\" />\n      </el-col>\n    </el-row>\n    <div style=\"margin: 20px 0\"></div>\n    <el-input v-model=\"reply.musicUrl\" placeholder=\"请输入音乐链接\" />\n    <div style=\"margin: 20px 0\"></div>\n    <el-input v-model=\"reply.hqMusicUrl\" placeholder=\"请输入高质量音乐链接\" />\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport WxMaterialSelect from '@/views/mp/components/wx-material-select'\nimport type { UploadRawFile } from 'element-plus'\nimport { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload'\nimport { getAccessToken } from '@/utils/auth'\nimport { Reply } from './types'\n\nconst message = useMessage()\n\nconst UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-temporary'\nconst HEADERS = { Authorization: 'Bearer ' + getAccessToken() } // 设置上传的请求头部\n\nconst props = defineProps<{\n  modelValue: Reply\n}>()\nconst emit = defineEmits<{\n  (e: 'update:modelValue', v: Reply)\n}>()\nconst reply = computed<Reply>({\n  get: () => props.modelValue,\n  set: (val) => emit('update:modelValue', val)\n})\n\nconst showDialog = ref(false)\nconst fileList = ref([])\nconst uploadData = reactive({\n  accountId: reply.value.accountId,\n  type: 'thumb', // 音乐类型为thumb\n  title: '',\n  introduction: ''\n})\n\nconst beforeImageUpload = (rawFile: UploadRawFile) => useBeforeUpload(UploadType.Image, 2)(rawFile)\n\nconst onUploadSuccess = (res: any) => {\n  if (res.code !== 0) {\n    message.error('上传出错：' + res.msg)\n    return false\n  }\n\n  // 清空上传时的各种数据\n  fileList.value = []\n  uploadData.title = ''\n  uploadData.introduction = ''\n\n  // 上传好的文件，本质是个素材，所以可以进行选中\n  selectMaterial(res.data)\n}\n\nconst selectMaterial = (item: any) => {\n  showDialog.value = false\n\n  reply.value.thumbMediaId = item.mediaId\n  reply.value.thumbMediaUrl = item.url\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/components/wx-reply/components/TabNews.vue",
    "content": "<template>\n  <div>\n    <el-row>\n      <div class=\"select-item\" v-if=\"reply.articles && reply.articles.length > 0\">\n        <WxNews :articles=\"reply.articles\" />\n        <el-col class=\"ope-row\">\n          <el-button type=\"danger\" circle @click=\"onDelete\">\n            <Icon icon=\"ep:delete\" />\n          </el-button>\n        </el-col>\n      </div>\n      <!-- 选择素材 -->\n      <el-col :span=\"24\" v-if=\"!reply.content\">\n        <el-row style=\"text-align: center\" align=\"middle\">\n          <el-col :span=\"24\">\n            <el-button type=\"success\" @click=\"showDialog = true\">\n              {{ newsType === NewsType.Published ? '选择已发布图文' : '选择草稿箱图文' }}\n              <Icon icon=\"ep:circle-check\" />\n            </el-button>\n          </el-col>\n        </el-row>\n      </el-col>\n      <el-dialog title=\"选择图文\" v-model=\"showDialog\" width=\"90%\" append-to-body destroy-on-close>\n        <WxMaterialSelect\n          type=\"news\"\n          :account-id=\"reply.accountId\"\n          :newsType=\"newsType\"\n          @select-material=\"selectMaterial\"\n        />\n      </el-dialog>\n    </el-row>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport WxNews from '@/views/mp/components/wx-news'\nimport WxMaterialSelect from '@/views/mp/components/wx-material-select'\nimport { Reply, NewsType } from './types'\n\nconst props = defineProps<{\n  modelValue: Reply\n  newsType: NewsType\n}>()\nconst emit = defineEmits<{\n  (e: 'update:modelValue', v: Reply)\n}>()\nconst reply = computed<Reply>({\n  get: () => props.modelValue,\n  set: (val) => emit('update:modelValue', val)\n})\n\nconst showDialog = ref(false)\n\nconst selectMaterial = (item: any) => {\n  showDialog.value = false\n  reply.value.articles = item.content.newsItem\n}\n\nconst onDelete = () => {\n  reply.value.articles = []\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.select-item {\n  width: 280px;\n  padding: 10px;\n  margin: 0 auto 10px;\n  border: 1px solid #eaeaea;\n\n  .ope-row {\n    padding-top: 10px;\n    text-align: center;\n  }\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/components/wx-reply/components/TabText.vue",
    "content": "<template>\n  <el-input type=\"textarea\" :rows=\"5\" placeholder=\"请输入内容\" v-model=\"content\" />\n</template>\n\n<script lang=\"ts\" setup>\nconst props = defineProps<{\n  modelValue?: string | null\n}>()\n\nconst emit = defineEmits<{\n  (e: 'update:modelValue', v: string | null)\n  (e: 'input', v: string | null)\n}>()\n\nconst content = computed<string | null | undefined>({\n  get: () => props.modelValue,\n  set: (val: string | null) => {\n    emit('update:modelValue', val)\n    emit('input', val)\n  }\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/components/wx-reply/components/TabVideo.vue",
    "content": "<template>\n  <div>\n    <el-row>\n      <el-input v-model=\"reply.title\" class=\"input-margin-bottom\" placeholder=\"请输入标题\" />\n      <el-input class=\"input-margin-bottom\" v-model=\"reply.description\" placeholder=\"请输入描述\" />\n      <el-row class=\"ope-row\" justify=\"center\">\n        <WxVideoPlayer v-if=\"reply.url\" :url=\"reply.url\" />\n      </el-row>\n      <el-col>\n        <el-row style=\"text-align: center\" align=\"middle\">\n          <!-- 选择素材 -->\n          <el-col :span=\"12\">\n            <el-button type=\"success\" @click=\"showDialog = true\">\n              素材库选择 <Icon icon=\"ep:circle-check\" />\n            </el-button>\n            <el-dialog\n              title=\"选择视频\"\n              v-model=\"showDialog\"\n              width=\"90%\"\n              append-to-body\n              destroy-on-close\n            >\n              <WxMaterialSelect\n                type=\"video\"\n                :account-id=\"reply.accountId\"\n                @select-material=\"selectMaterial\"\n              />\n            </el-dialog>\n          </el-col>\n          <!-- 文件上传 -->\n          <el-col :span=\"12\">\n            <el-upload\n              :action=\"UPLOAD_URL\"\n              :headers=\"HEADERS\"\n              multiple\n              :limit=\"1\"\n              :file-list=\"fileList\"\n              :data=\"uploadData\"\n              :before-upload=\"beforeVideoUpload\"\n              :on-success=\"onUploadSuccess\"\n            >\n              <el-button type=\"primary\">新建视频 <Icon icon=\"ep:upload\" /></el-button>\n            </el-upload>\n          </el-col>\n        </el-row>\n      </el-col>\n    </el-row>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport WxVideoPlayer from '@/views/mp/components/wx-video-play'\nimport WxMaterialSelect from '@/views/mp/components/wx-material-select'\nimport type { UploadRawFile } from 'element-plus'\nimport { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload'\nimport { getAccessToken } from '@/utils/auth'\nimport { Reply } from './types'\n\nconst message = useMessage()\n\nconst UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-temporary'\nconst HEADERS = { Authorization: 'Bearer ' + getAccessToken() }\n\nconst props = defineProps<{\n  modelValue: Reply\n}>()\nconst emit = defineEmits<{\n  (e: 'update:modelValue', v: Reply)\n}>()\nconst reply = computed<Reply>({\n  get: () => props.modelValue,\n  set: (val) => emit('update:modelValue', val)\n})\n\nconst showDialog = ref(false)\nconst fileList = ref([])\nconst uploadData = reactive({\n  accountId: reply.value.accountId,\n  type: 'video',\n  title: '',\n  introduction: ''\n})\n\nconst beforeVideoUpload = (rawFile: UploadRawFile) => useBeforeUpload(UploadType.Video, 10)(rawFile)\n\nconst onUploadSuccess = (res: any) => {\n  if (res.code !== 0) {\n    message.error('上传出错：' + res.msg)\n    return false\n  }\n\n  // 清空上传时的各种数据\n  fileList.value = []\n  uploadData.title = ''\n  uploadData.introduction = ''\n\n  selectMaterial(res.data)\n}\n\n/** 选择素材后设置 */\nconst selectMaterial = (item: any) => {\n  showDialog.value = false\n\n  reply.value.mediaId = item.mediaId\n  reply.value.url = item.url\n  reply.value.name = item.name\n\n  // title、introduction：从 item 到 tempObjItem，因为素材里有 title、introduction\n  if (item.title) {\n    reply.value.title = item.title || ''\n  }\n  if (item.introduction) {\n    reply.value.description = item.introduction || ''\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.input-margin-bottom {\n  margin-bottom: 2%;\n}\n\n.ope-row {\n  width: 100%;\n  padding-top: 10px;\n  text-align: center;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/components/wx-reply/components/TabVoice.vue",
    "content": "<template>\n  <div>\n    <div class=\"select-item2\" v-if=\"reply.url\">\n      <p class=\"item-name\">{{ reply.name }}</p>\n      <el-row class=\"ope-row\" justify=\"center\">\n        <WxVoicePlayer :url=\"reply.url\" />\n      </el-row>\n      <el-row class=\"ope-row\" justify=\"center\">\n        <el-button type=\"danger\" circle @click=\"onDelete\"><Icon icon=\"ep:delete\" /></el-button>\n      </el-row>\n    </div>\n    <el-row v-else style=\"text-align: center\">\n      <!-- 选择素材 -->\n      <el-col :span=\"12\" class=\"col-select\">\n        <el-button type=\"success\" @click=\"showDialog = true\">\n          素材库选择<Icon icon=\"ep:circle-check\" />\n        </el-button>\n        <el-dialog\n          title=\"选择语音\"\n          v-model=\"showDialog\"\n          width=\"90%\"\n          append-to-body\n          destroy-on-close\n        >\n          <WxMaterialSelect\n            type=\"voice\"\n            :account-id=\"reply.accountId\"\n            @select-material=\"selectMaterial\"\n          />\n        </el-dialog>\n      </el-col>\n      <!-- 文件上传 -->\n      <el-col :span=\"12\" class=\"col-add\">\n        <el-upload\n          :action=\"UPLOAD_URL\"\n          :headers=\"HEADERS\"\n          multiple\n          :limit=\"1\"\n          :file-list=\"fileList\"\n          :data=\"uploadData\"\n          :before-upload=\"beforeVoiceUpload\"\n          :on-success=\"onUploadSuccess\"\n        >\n          <el-button type=\"primary\">点击上传</el-button>\n          <template #tip>\n            <div class=\"el-upload__tip\">\n              格式支持 mp3/wma/wav/amr，文件大小不超过 2M，播放长度不超过 60s\n            </div>\n          </template>\n        </el-upload>\n      </el-col>\n    </el-row>\n  </div>\n</template>\n<script lang=\"ts\" setup>\nimport WxMaterialSelect from '@/views/mp/components/wx-material-select'\nimport WxVoicePlayer from '@/views/mp/components/wx-voice-play'\nimport { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload'\nimport type { UploadRawFile } from 'element-plus'\nimport { getAccessToken } from '@/utils/auth'\nimport { Reply } from './types'\nconst message = useMessage()\n\nconst UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-temporary'\nconst HEADERS = { Authorization: 'Bearer ' + getAccessToken() } // 设置上传的请求头部\n\nconst props = defineProps<{\n  modelValue: Reply\n}>()\nconst emit = defineEmits<{\n  (e: 'update:modelValue', v: Reply)\n}>()\nconst reply = computed<Reply>({\n  get: () => props.modelValue,\n  set: (val) => emit('update:modelValue', val)\n})\n\nconst showDialog = ref(false)\nconst fileList = ref([])\nconst uploadData = reactive({\n  accountId: reply.value.accountId,\n  type: 'voice',\n  title: '',\n  introduction: ''\n})\n\nconst beforeVoiceUpload = (rawFile: UploadRawFile) => useBeforeUpload(UploadType.Voice, 10)(rawFile)\n\nconst onUploadSuccess = (res: any) => {\n  if (res.code !== 0) {\n    message.error('上传出错：' + res.msg)\n    return false\n  }\n\n  // 清空上传时的各种数据\n  fileList.value = []\n  uploadData.title = ''\n  uploadData.introduction = ''\n\n  // 上传好的文件，本质是个素材，所以可以进行选中\n  selectMaterial(res.data)\n}\n\nconst onDelete = () => {\n  reply.value.mediaId = null\n  reply.value.url = null\n  reply.value.name = null\n}\n\nconst selectMaterial = (item: Reply) => {\n  showDialog.value = false\n\n  // reply.value.type = ReplyType.Voice\n  reply.value.mediaId = item.mediaId\n  reply.value.url = item.url\n  reply.value.name = item.name\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.select-item2 {\n  padding: 10px;\n  margin: 0 auto 10px;\n  border: 1px solid #eaeaea;\n\n  .item-name {\n    overflow: hidden;\n    font-size: 12px;\n    text-align: center;\n    text-overflow: ellipsis;\n    white-space: nowrap;\n\n    .ope-row {\n      width: 100%;\n      padding-top: 10px;\n      text-align: center;\n    }\n  }\n\n  .col-select {\n    width: 49.5%;\n    height: 160px;\n    padding: 50px 0;\n    border: 1px solid rgb(234 234 234);\n  }\n\n  .col-add {\n    float: right;\n    width: 49.5%;\n    height: 160px;\n    padding: 50px 0;\n    border: 1px solid rgb(234 234 234);\n\n    .el-upload__tip {\n      line-height: 18px;\n      text-align: center;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/components/wx-reply/components/types.ts",
    "content": "enum ReplyType {\n  News = 'news',\n  Image = 'image',\n  Voice = 'voice',\n  Video = 'video',\n  Music = 'music',\n  Text = 'text'\n}\n\ninterface _Reply {\n  accountId: number\n  type: ReplyType\n  name?: string | null\n  content?: string | null\n  mediaId?: string | null\n  url?: string | null\n  title?: string | null\n  description?: string | null\n  thumbMediaId?: string | null\n  thumbMediaUrl?: string | null\n  musicUrl?: string | null\n  hqMusicUrl?: string | null\n  introduction?: string | null\n  articles?: any[]\n}\n\ntype Reply = _Reply //Partial<_Reply>\n\nenum NewsType {\n  Published = '1',\n  Draft = '2'\n}\n\n/** 利用旧的reply[accountId, type]初始化新的Reply */\nconst createEmptyReply = (old: Reply | Ref<Reply>): Reply => {\n  return {\n    accountId: unref(old).accountId,\n    type: unref(old).type,\n    name: null,\n    content: null,\n    mediaId: null,\n    url: null,\n    title: null,\n    description: null,\n    thumbMediaId: null,\n    thumbMediaUrl: null,\n    musicUrl: null,\n    hqMusicUrl: null,\n    introduction: null,\n    articles: []\n  }\n}\n\nexport { Reply, NewsType, ReplyType, createEmptyReply }\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/components/wx-reply/index.ts",
    "content": "import { Reply, NewsType, ReplyType, createEmptyReply } from './components/types'\n\nimport WxReplySelect from './main.vue'\n\nexport type { Reply }\nexport { createEmptyReply, NewsType, ReplyType }\nexport default WxReplySelect\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/components/wx-reply/main.vue",
    "content": "<!--\n  - Copyright (C) 2018-2019\n  - All rights reserved, Designed By www.joolun.com\n  yshop源码：\n  ① 移除多余的 rep 为前缀的变量，让 message 消息更简单\n  ② 代码优化，补充注释，提升阅读性\n  ③ 优化消息的临时缓存策略，发送消息时，只清理被发送消息的 tab，不会强制切回到 text 输入\n  ④ 支持发送【视频】消息时，支持新建视频\n-->\n<template>\n  <el-tabs type=\"border-card\" v-model=\"currentTab\">\n    <!-- 类型 1：文本 -->\n    <el-tab-pane :name=\"ReplyType.Text\">\n      <template #label>\n        <el-row align=\"middle\"><Icon icon=\"ep:document\" /> 文本</el-row>\n      </template>\n      <TabText v-model=\"reply.content\" />\n    </el-tab-pane>\n\n    <!-- 类型 2：图片 -->\n    <el-tab-pane :name=\"ReplyType.Image\">\n      <template #label>\n        <el-row align=\"middle\"><Icon icon=\"ep:picture\" class=\"mr-5px\" /> 图片</el-row>\n      </template>\n      <TabImage v-model=\"reply\" />\n    </el-tab-pane>\n\n    <!-- 类型 3：语音 -->\n    <el-tab-pane :name=\"ReplyType.Voice\">\n      <template #label>\n        <el-row align=\"middle\"><Icon icon=\"ep:phone\" /> 语音</el-row>\n      </template>\n      <TabVoice v-model=\"reply\" />\n    </el-tab-pane>\n\n    <!-- 类型 4：视频 -->\n    <el-tab-pane :name=\"ReplyType.Video\">\n      <template #label>\n        <el-row align=\"middle\"><Icon icon=\"ep:share\" /> 视频</el-row>\n      </template>\n      <TabVideo v-model=\"reply\" />\n    </el-tab-pane>\n\n    <!-- 类型 5：图文 -->\n    <el-tab-pane :name=\"ReplyType.News\">\n      <template #label>\n        <el-row align=\"middle\"><Icon icon=\"ep:reading\" /> 图文</el-row>\n      </template>\n      <TabNews v-model=\"reply\" :news-type=\"newsType\" />\n    </el-tab-pane>\n\n    <!-- 类型 6：音乐 -->\n    <el-tab-pane :name=\"ReplyType.Music\">\n      <template #label>\n        <el-row align=\"middle\"><Icon icon=\"ep:service\" />音乐</el-row>\n      </template>\n      <TabMusic v-model=\"reply\" />\n    </el-tab-pane>\n  </el-tabs>\n</template>\n\n<script lang=\"ts\" setup>\nimport { Reply, NewsType, ReplyType, createEmptyReply } from './components/types'\nimport TabText from './components/TabText.vue'\nimport TabImage from './components/TabImage.vue'\nimport TabVoice from './components/TabVoice.vue'\nimport TabVideo from './components/TabVideo.vue'\nimport TabNews from './components/TabNews.vue'\nimport TabMusic from './components/TabMusic.vue'\n\ndefineOptions({ name: 'WxReplySelect' })\n\ninterface Props {\n  modelValue: Reply\n  newsType?: NewsType\n}\nconst props = withDefaults(defineProps<Props>(), {\n  newsType: () => NewsType.Published\n})\nconst emit = defineEmits<{\n  (e: 'update:modelValue', v: Reply)\n}>()\n\nconst reply = computed<Reply>({\n  get: () => props.modelValue,\n  set: (val) => emit('update:modelValue', val)\n})\n// 作为多个标签保存各自Reply的缓存\nconst tabCache = new Map<ReplyType, Reply>()\n// 采用独立的ref来保存当前tab，避免在watch标签变化，对reply进行赋值会产生了循环调用\nconst currentTab = ref<ReplyType>(props.modelValue.type || ReplyType.Text)\n\nwatch(\n  currentTab,\n  (newTab, oldTab) => {\n    // 第一次进入：oldTab 为 undefined\n    // 判断 newTab 是因为 Reply 为 Partial\n    if (oldTab === undefined || newTab === undefined) {\n      return\n    }\n\n    tabCache.set(oldTab, unref(reply))\n\n    // 从缓存里面取出新tab内容，有则覆盖Reply，没有则创建空Reply\n    const temp = tabCache.get(newTab)\n    if (temp) {\n      reply.value = temp\n    } else {\n      let newData = createEmptyReply(reply)\n      newData.type = newTab\n      reply.value = newData\n    }\n  },\n  {\n    immediate: true\n  }\n)\n\n/** 清除除了`type`, `accountId`的字段 */\nconst clear = () => {\n  reply.value = createEmptyReply(reply)\n}\n\ndefineExpose({\n  clear\n})\n</script>\n\n<style lang=\"scss\" scoped>\n.select-item {\n  width: 280px;\n  padding: 10px;\n  margin: 0 auto 10px;\n  border: 1px solid #eaeaea;\n}\n\n.select-item2 {\n  padding: 10px;\n  margin: 0 auto 10px;\n  border: 1px solid #eaeaea;\n}\n\n.ope-row {\n  padding-top: 10px;\n  text-align: center;\n}\n\n.input-margin-bottom {\n  margin-bottom: 2%;\n}\n\n.item-name {\n  overflow: hidden;\n  font-size: 12px;\n  text-align: center;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n.el-form-item__content {\n  line-height: unset !important;\n}\n\n.col-select {\n  width: 49.5%;\n  height: 160px;\n  padding: 50px 0;\n  border: 1px solid rgb(234 234 234);\n}\n\n.col-select2 {\n  height: 160px;\n  padding: 50px 0;\n  border: 1px solid rgb(234 234 234);\n}\n\n.col-add {\n  float: right;\n  width: 49.5%;\n  height: 160px;\n  padding: 50px 0;\n  border: 1px solid rgb(234 234 234);\n}\n\n.avatar-uploader-icon {\n  width: 100px !important;\n  height: 100px !important;\n  font-size: 28px;\n  line-height: 100px !important;\n  color: #8c939d;\n  text-align: center;\n  border: 1px solid #d9d9d9;\n}\n\n.material-img {\n  width: 100%;\n}\n\n.thumb-div {\n  display: inline-block;\n  text-align: center;\n}\n\n.item-infos {\n  width: 30%;\n  margin: auto;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/components/wx-video-play/index.ts",
    "content": "import WxVideoPlayer from './main.vue'\n\nexport default WxVideoPlayer\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/components/wx-video-play/main.vue",
    "content": "<!--\n  - Copyright (C) 2018-2019\n  - All rights reserved, Designed By www.joolun.com\n  【微信消息 - 视频】\n  yshop源码：\n  ① bug 修复：\n    1）joolun 的做法：使用 mediaId 从微信公众号，下载对应的 mp4 素材，从而播放内容；\n      存在的问题：mediaId 有效期是 3 天，超过时间后无法播放\n    2）重构后的做法：后端接收到微信公众号的视频消息后，将视频消息的 media_id 的文件内容保存到文件服务器中，这样前端可以直接使用 URL 播放。\n  ② 体验优化：弹窗关闭后，自动暂停视频的播放\n\n-->\n<template>\n  <div @click=\"playVideo()\">\n    <!-- 提示 -->\n    <div>\n      <Icon icon=\"ep:video-play\" :size=\"32\" class=\"mr-5px\" />\n      <p class=\"text-sm\">点击播放视频</p>\n    </div>\n\n    <!-- 弹窗播放 -->\n    <el-dialog v-model=\"dialogVideo\" title=\"视频播放\" append-to-body>\n      <video-player\n        v-if=\"dialogVideo\"\n        class=\"video-player vjs-big-play-centered\"\n        :src=\"props.url\"\n        poster=\"\"\n        crossorigin=\"anonymous\"\n        controls\n        playsinline\n        :volume=\"0.6\"\n        :width=\"800\"\n        :playback-rates=\"[0.7, 1.0, 1.5, 2.0]\"\n      />\n      <!--     事件，暫時沒用\n      @mounted=\"handleMounted\"-->\n      <!--        @ready=\"handleEvent($event)\"-->\n      <!--        @play=\"handleEvent($event)\"-->\n      <!--        @pause=\"handleEvent($event)\"-->\n      <!--        @ended=\"handleEvent($event)\"-->\n      <!--        @loadeddata=\"handleEvent($event)\"-->\n      <!--        @waiting=\"handleEvent($event)\"-->\n      <!--        @playing=\"handleEvent($event)\"-->\n      <!--        @canplay=\"handleEvent($event)\"-->\n      <!--        @canplaythrough=\"handleEvent($event)\"-->\n      <!--        @timeupdate=\"handleEvent(player?.currentTime())\"-->\n    </el-dialog>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport 'video.js/dist/video-js.css'\nimport { VideoPlayer } from '@videojs-player/vue'\n\ndefineOptions({ name: 'WxVideoPlayer' })\n\nconst props = defineProps({\n  url: {\n    type: String,\n    required: true\n  }\n})\n\nconst dialogVideo = ref(false)\n\n// const handleEvent = (log) => {\n//   console.log('Basic player event', log)\n// }\n\nconst playVideo = () => {\n  dialogVideo.value = true\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/components/wx-voice-play/index.ts",
    "content": "import WxVoicePlayer from './main.vue'\n\nexport default WxVoicePlayer\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/components/wx-voice-play/main.vue",
    "content": "<!--\n  - Copyright (C) 2018-2019\n  - All rights reserved, Designed By www.joolun.com\n  【微信消息 - 语音】\n   yshop源码：\n  ① bug 修复：\n    1）joolun 的做法：使用 mediaId 从微信公众号，下载对应的 mp4 素材，从而播放内容；\n      存在的问题：mediaId 有效期是 3 天，超过时间后无法播放\n    2）重构后的做法：后端接收到微信公众号的视频消息后，将视频消息的 media_id 的文件内容保存到文件服务器中，这样前端可以直接使用 URL 播放。\n  ② 代码优化：将 props 中的 reply 调成为 data 中对应的属性，并补充相关注释\n-->\n<template>\n  <div class=\"wx-voice-div\" @click=\"playVoice\">\n    <el-icon>\n      <Icon v-if=\"playing !== true\" icon=\"ep:video-play\" :size=\"32\" />\n      <Icon v-else icon=\"ep:video-pause\" :size=\"32\" />\n      <span class=\"amr-duration\" v-if=\"duration\">{{ duration }} 秒</span>\n    </el-icon>\n    <div v-if=\"content\">\n      <el-tag type=\"success\" size=\"small\">语音识别</el-tag>\n      {{ content }}\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\n// 因为微信语音是 amr 格式，所以需要用到 amr 解码器：https://www.npmjs.com/package/benz-amr-recorder\nimport BenzAMRRecorder from 'benz-amr-recorder'\n\ndefineOptions({ name: 'WxVoicePlayer' })\n\nconst props = defineProps({\n  url: {\n    type: String, // 语音地址，例如说：https://www.yixiang.co/xxx.amr\n    required: true\n  },\n  content: {\n    type: String, // 语音文本\n    required: false\n  }\n})\n\nconst amr = ref()\nconst playing = ref(false)\nconst duration = ref()\n\n/** 处理点击，播放或暂停 */\nconst playVoice = () => {\n  // 情况一：未初始化，则创建 BenzAMRRecorder\n  if (amr.value === undefined) {\n    amrInit()\n    return\n  }\n  // 情况二：已经初始化，则根据情况播放或暂时\n  if (amr.value.isPlaying()) {\n    amrStop()\n  } else {\n    amrPlay()\n  }\n}\n\n/** 音频初始化 */\nconst amrInit = () => {\n  amr.value = new BenzAMRRecorder()\n  // 设置播放\n  amr.value.initWithUrl(props.url).then(function () {\n    amrPlay()\n    duration.value = amr.value.getDuration()\n  })\n  // 监听暂停\n  amr.value.onEnded(function () {\n    playing.value = false\n  })\n}\n\n/** 音频播放 */\nconst amrPlay = () => {\n  playing.value = true\n  amr.value.play()\n}\n\n/** 音频暂停 */\nconst amrStop = () => {\n  playing.value = false\n  amr.value.stop()\n}\n// TODO 芋艿：下面样式有点问题\n</script>\n<style lang=\"scss\" scoped>\n.wx-voice-div {\n  display: flex;\n  width: 120px;\n  height: 50px;\n  padding: 5px;\n  background-color: #eaeaea;\n  border-radius: 10px;\n  justify-content: center;\n  align-items: center;\n}\n\n.amr-duration {\n  margin-left: 5px;\n  font-size: 11px;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/draft/components/CoverSelect.vue",
    "content": "<template>\n  <div>\n    <p>封面:</p>\n    <div class=\"thumb-div\">\n      <el-image\n        v-if=\"newsItem.thumbUrl\"\n        style=\"width: 300px; max-height: 300px\"\n        :src=\"newsItem.thumbUrl\"\n        fit=\"contain\"\n      />\n      <Icon\n        v-else\n        icon=\"ep:plus\"\n        class=\"avatar-uploader-icon\"\n        :class=\"isFirst ? 'avatar' : 'avatar1'\"\n      />\n      <div class=\"thumb-but\">\n        <el-upload\n          :action=\"UPLOAD_URL\"\n          :headers=\"HEADERS\"\n          multiple\n          :limit=\"1\"\n          :file-list=\"fileList\"\n          :data=\"uploadData\"\n          :before-upload=\"onBeforeUpload\"\n          :on-error=\"onUploadError\"\n          :on-success=\"onUploadSuccess\"\n        >\n          <template #trigger>\n            <el-button size=\"small\" type=\"primary\">本地上传</el-button>\n          </template>\n          <el-button\n            size=\"small\"\n            type=\"primary\"\n            @click=\"showImageDialog = true\"\n            style=\"margin-left: 5px\"\n          >\n            素材库选择\n          </el-button>\n          <template #tip>\n            <div class=\"el-upload__tip\">支持 bmp/png/jpeg/jpg/gif 格式，大小不超过 2M</div>\n          </template>\n        </el-upload>\n      </div>\n      <el-dialog\n        title=\"选择图片\"\n        v-model=\"showImageDialog\"\n        width=\"80%\"\n        append-to-body\n        destroy-on-close\n      >\n        <WxMaterialSelect\n          type=\"image\"\n          :account-id=\"accountId!\"\n          @select-material=\"onMaterialSelected\"\n        />\n      </el-dialog>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport WxMaterialSelect from '@/views/mp/components/wx-material-select'\nimport { getAccessToken } from '@/utils/auth'\nimport type { UploadFiles, UploadProps, UploadRawFile } from 'element-plus'\nimport { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload'\nimport { NewsItem } from './types'\nconst message = useMessage()\n\nconst UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-permanent' // 上传永久素材的地址\nconst HEADERS = { Authorization: 'Bearer ' + getAccessToken() } // 设置上传的请求头部\n\nconst props = defineProps<{\n  modelValue: NewsItem\n  isFirst: boolean\n}>()\n\nconst emit = defineEmits<{\n  (e: 'update:modelValue', v: NewsItem)\n}>()\nconst newsItem = computed<NewsItem>({\n  get() {\n    return props.modelValue\n  },\n  set(val) {\n    emit('update:modelValue', val)\n  }\n})\n\nconst accountId = inject<number>('accountId')\nconst showImageDialog = ref(false)\n\nconst fileList = ref<UploadFiles>([])\ninterface UploadData {\n  type: UploadType\n  accountId: number\n}\nconst uploadData: UploadData = reactive({\n  type: UploadType.Image,\n  accountId: accountId!\n})\n\n/** 素材选择完成事件*/\nconst onMaterialSelected = (item: any) => {\n  showImageDialog.value = false\n  newsItem.value.thumbMediaId = item.mediaId\n  newsItem.value.thumbUrl = item.url\n}\n\nconst onBeforeUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) =>\n  useBeforeUpload(UploadType.Image, 2)(rawFile)\n\nconst onUploadSuccess: UploadProps['onSuccess'] = (res: any) => {\n  if (res.code !== 0) {\n    message.error('上传出错：' + res.msg)\n    return false\n  }\n\n  // 重置上传文件的表单\n  fileList.value = []\n\n  // 设置草稿的封面字段\n  newsItem.value.thumbMediaId = res.data.mediaId\n  newsItem.value.thumbUrl = res.data.url\n}\n\nconst onUploadError = (err: Error) => {\n  message.error('上传失败: ' + err.message)\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.el-upload__tip {\n  margin-left: 5px;\n}\n\n.thumb-div {\n  display: inline-block;\n  width: 100%;\n  text-align: center;\n\n  .avatar-uploader-icon {\n    width: 120px;\n    height: 120px;\n    font-size: 28px;\n    line-height: 120px;\n    color: #8c939d;\n    text-align: center;\n    border: 1px solid #d9d9d9;\n  }\n\n  .avatar {\n    width: 230px;\n    height: 120px;\n  }\n\n  .avatar1 {\n    width: 120px;\n    height: 120px;\n  }\n\n  .thumb-but {\n    margin: 5px;\n  }\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/draft/components/DraftTable.vue",
    "content": "<template>\n  <div class=\"waterfall\" v-loading=\"props.loading\">\n    <template v-for=\"item in props.list\" :key=\"item.articleId\">\n      <div class=\"waterfall-item\" v-if=\"item.content && item.content.newsItem\">\n        <WxNews :articles=\"item.content.newsItem\" />\n        <!-- 操作按钮 -->\n        <el-row>\n          <el-button\n            type=\"success\"\n            circle\n            @click=\"emit('publish', item)\"\n            v-hasPermi=\"['mp:free-publish:submit']\"\n          >\n            <Icon icon=\"fa:upload\" />\n          </el-button>\n          <el-button\n            type=\"primary\"\n            circle\n            @click=\"emit('update', item)\"\n            v-hasPermi=\"['mp:draft:update']\"\n          >\n            <Icon icon=\"ep:edit\" />\n          </el-button>\n          <el-button\n            type=\"danger\"\n            circle\n            @click=\"emit('delete', item)\"\n            v-hasPermi=\"['mp:draft:delete']\"\n          >\n            <Icon icon=\"ep:delete\" />\n          </el-button>\n        </el-row>\n      </div>\n    </template>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport WxNews from '@/views/mp/components/wx-news'\n\nimport { Article } from './types'\n\nconst props = defineProps<{\n  list: Article[]\n  loading: boolean\n}>()\n\nconst emit = defineEmits<{\n  (e: 'publish', v: Article)\n  (e: 'update', v: Article)\n  (e: 'delete', v: Article)\n}>()\n</script>\n\n<style lang=\"scss\" scoped>\n.waterfall {\n  width: 100%;\n  column-gap: 10px;\n  column-count: 5;\n  margin: 0 auto;\n\n  .waterfall-item {\n    padding: 10px;\n    margin-bottom: 10px;\n    break-inside: avoid;\n    border: 1px solid #eaeaea;\n  }\n}\n\n@media (width >= 992px) and (width <= 1300px) {\n  .waterfall {\n    column-count: 3;\n  }\n}\n\n@media (width >= 768px) and (width <= 991px) {\n  .waterfall {\n    column-count: 2;\n  }\n}\n\n@media (width <= 767px) {\n  .waterfall {\n    column-count: 1;\n  }\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/draft/components/NewsForm.vue",
    "content": "<template>\n  <el-container>\n    <el-aside width=\"40%\">\n      <div class=\"select-item\">\n        <div v-for=\"(news, index) in newsList\" :key=\"index\">\n          <div\n            class=\"news-main father\"\n            v-if=\"index === 0\"\n            :class=\"{ activeAddNews: activeNewsIndex === index }\"\n            @click=\"activeNewsIndex = index\"\n          >\n            <div class=\"news-content\">\n              <img class=\"material-img\" :src=\"news.thumbUrl\" />\n              <div class=\"news-content-title\">{{ news.title }}</div>\n            </div>\n            <div class=\"child\" v-if=\"newsList.length > 1\">\n              <el-button type=\"info\" circle size=\"small\" @click=\"() => moveDownNews(index)\">\n                <Icon icon=\"ep:arrow-down-bold\" />\n              </el-button>\n              <el-button\n                v-if=\"isCreating\"\n                type=\"danger\"\n                circle\n                size=\"small\"\n                @click=\"() => removeNews(index)\"\n              >\n                <Icon icon=\"ep:delete\" />\n              </el-button>\n            </div>\n          </div>\n          <div\n            class=\"news-main-item father\"\n            v-if=\"index > 0\"\n            :class=\"{ activeAddNews: activeNewsIndex === index }\"\n            @click=\"activeNewsIndex = index\"\n          >\n            <div class=\"news-content-item\">\n              <div class=\"news-content-item-title\">{{ news.title }}</div>\n              <div class=\"news-content-item-img\">\n                <img class=\"material-img\" :src=\"news.thumbUrl\" width=\"100%\" />\n              </div>\n            </div>\n            <div class=\"child\">\n              <el-button\n                v-if=\"newsList.length > index + 1\"\n                circle\n                type=\"info\"\n                size=\"small\"\n                @click=\"() => moveDownNews(index)\"\n              >\n                <Icon icon=\"ep:arrow-down-bold\" />\n              </el-button>\n              <el-button\n                v-if=\"index > 0\"\n                type=\"info\"\n                circle\n                size=\"small\"\n                @click=\"() => moveUpNews(index)\"\n              >\n                <Icon icon=\"ep:arrow-up-bold\" />\n              </el-button>\n              <el-button\n                v-if=\"isCreating\"\n                type=\"danger\"\n                size=\"small\"\n                circle\n                @click=\"() => removeNews(index)\"\n              >\n                <Icon icon=\"ep:delete\" />\n              </el-button>\n            </div>\n          </div>\n        </div>\n        <el-row justify=\"center\" class=\"ope-row\">\n          <el-button\n            type=\"primary\"\n            circle\n            @click=\"plusNews\"\n            v-if=\"newsList.length < 8 && isCreating\"\n          >\n            <Icon icon=\"ep:plus\" />\n          </el-button>\n        </el-row>\n      </div>\n    </el-aside>\n    <el-main>\n      <div v-if=\"newsList.length > 0\">\n        <!-- 标题、作者、原文地址 -->\n        <el-row :gutter=\"20\">\n          <el-input v-model=\"activeNewsItem.title\" placeholder=\"请输入标题（必填）\" />\n          <el-input\n            v-model=\"activeNewsItem.author\"\n            placeholder=\"请输入作者\"\n            style=\"margin-top: 5px\"\n          />\n          <el-input\n            v-model=\"activeNewsItem.contentSourceUrl\"\n            placeholder=\"请输入原文地址\"\n            style=\"margin-top: 5px\"\n          />\n        </el-row>\n        <!-- 封面和摘要 -->\n        <el-row :gutter=\"20\">\n          <el-col :span=\"12\">\n            <CoverSelect v-model=\"activeNewsItem\" :is-first=\"activeNewsIndex === 0\" />\n          </el-col>\n          <el-col :span=\"12\">\n            <p>摘要:</p>\n            <el-input\n              :rows=\"8\"\n              type=\"textarea\"\n              v-model=\"activeNewsItem.digest\"\n              placeholder=\"请输入摘要\"\n              class=\"digest\"\n              maxlength=\"120\"\n            />\n          </el-col>\n        </el-row>\n        <!--富文本编辑器组件-->\n        <el-row>\n          <Editor v-model=\"activeNewsItem.content\" :editor-config=\"editorConfig\" />\n        </el-row>\n      </div>\n    </el-main>\n  </el-container>\n</template>\n\n<script lang=\"ts\" setup>\nimport { Editor } from '@/components/Editor'\nimport { createEditorConfig } from '../editor-config'\nimport CoverSelect from './CoverSelect.vue'\nimport { type NewsItem, createEmptyNewsItem } from './types'\n\ndefineOptions({ name: 'NewsForm' })\n\nconst message = useMessage()\n\nconst props = defineProps<{\n  isCreating: boolean\n  modelValue: NewsItem[] | null\n}>()\n\nconst accountId = inject<number>('accountId')\n\n// ========== 文件上传 ==========\nconst UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-permanent' // 上传永久素材的地址\nconst editorConfig = createEditorConfig(UPLOAD_URL, accountId)\n\n// v-model=newsList\nconst emit = defineEmits<{\n  (e: 'update:modelValue', v: NewsItem[])\n}>()\nconst newsList = computed<NewsItem[]>({\n  get() {\n    return props.modelValue === null ? [createEmptyNewsItem()] : props.modelValue\n  },\n  set(val) {\n    emit('update:modelValue', val)\n  }\n})\n\nconst activeNewsIndex = ref(0)\nconst activeNewsItem = computed<NewsItem>(() => newsList.value[activeNewsIndex.value])\n\n// 将图文向下移动\nconst moveDownNews = (index: number) => {\n  const temp = newsList.value[index]\n  newsList.value[index] = newsList.value[index + 1]\n  newsList.value[index + 1] = temp\n  activeNewsIndex.value = index + 1\n}\n\n// 将图文向上移动\nconst moveUpNews = (index: number) => {\n  const temp = newsList.value[index]\n  newsList.value[index] = newsList.value[index - 1]\n  newsList.value[index - 1] = temp\n  activeNewsIndex.value = index - 1\n}\n\n// 删除指定 index 的图文\nconst removeNews = async (index: number) => {\n  try {\n    await message.confirm('确定删除该图文吗?')\n    newsList.value.splice(index, 1)\n    if (activeNewsIndex.value === index) {\n      activeNewsIndex.value = 0\n    }\n  } catch {}\n}\n\n// 添加一个图文\nconst plusNews = () => {\n  newsList.value.push(createEmptyNewsItem())\n  activeNewsIndex.value = newsList.value.length - 1\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.ope-row {\n  padding-top: 5px;\n  margin-top: 5px;\n  text-align: center;\n  border-top: 1px solid #eaeaea;\n}\n\n.el-row {\n  margin-bottom: 20px;\n}\n\n.el-row:last-child {\n  margin-bottom: 0;\n}\n\n.digest {\n  display: inline-block;\n  width: 100%;\n  vertical-align: top;\n}\n\n/* 新增图文 */\n.news-main {\n  width: 100%;\n  height: 120px;\n  margin: auto;\n  background-color: #fff;\n}\n\n.news-content {\n  position: relative;\n  width: 100%;\n  height: 120px;\n  background-color: #acadae;\n}\n\n.news-content-title {\n  position: absolute;\n  bottom: 0;\n  left: 0;\n  display: inline-block;\n  width: 98%;\n  height: 25px;\n  padding: 1%;\n  overflow: hidden;\n  font-size: 15px;\n  color: #fff;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n  background-color: black;\n  opacity: 0.65;\n}\n\n.news-main-item {\n  width: 100%;\n  padding: 5px 0;\n  margin: auto;\n  background-color: #fff;\n  border-top: 1px solid #eaeaea;\n}\n\n.news-content-item {\n  position: relative;\n  margin-left: -3px;\n}\n\n.news-content-item-title {\n  display: inline-block;\n  width: 70%;\n  font-size: 12px;\n}\n\n.news-content-item-img {\n  display: inline-block;\n  width: 25%;\n  background-color: #acadae;\n}\n\n.select-item {\n  width: 60%;\n  padding: 10px;\n  margin: 0 auto 10px;\n  border: 1px solid #eaeaea;\n\n  .activeAddNews {\n    border: 5px solid #2bb673;\n  }\n}\n\n.father .child {\n  position: relative;\n  bottom: 25px;\n  display: none;\n  text-align: center;\n}\n\n.father:hover .child {\n  display: block;\n}\n\n.material-img {\n  width: 100%;\n  height: 100%;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/draft/components/index.ts",
    "content": "import type { Article, NewsItem, NewsItemList } from './types'\nimport { createEmptyNewsItem } from './types'\nimport DraftTable from './DraftTable.vue'\nimport NewsForm from './NewsForm.vue'\n\nexport { DraftTable, NewsForm, createEmptyNewsItem }\nexport type { Article, NewsItem, NewsItemList }\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/draft/components/types.ts",
    "content": "interface NewsItem {\n  title: string\n  thumbMediaId: string\n  author: string\n  digest: string\n  showCoverPic: string\n  content: string\n  contentSourceUrl: string\n  needOpenComment: string\n  onlyFansCanComment: string\n  thumbUrl: string\n}\n\ninterface NewsItemList {\n  newsItem: NewsItem[]\n}\n\ninterface Article {\n  mediaId: string\n  content: NewsItemList\n  updateTime: number\n}\n\nconst createEmptyNewsItem = (): NewsItem => {\n  return {\n    title: '',\n    thumbMediaId: '',\n    author: '',\n    digest: '',\n    showCoverPic: '',\n    content: '',\n    contentSourceUrl: '',\n    needOpenComment: '',\n    onlyFansCanComment: '',\n    thumbUrl: ''\n  }\n}\n\nexport type { Article, NewsItem, NewsItemList }\nexport { createEmptyNewsItem }\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/draft/editor-config.ts",
    "content": "import { IEditorConfig } from '@wangeditor/editor'\nimport { getAccessToken, getTenantId } from '@/utils/auth'\n\nconst message = useMessage()\n\ntype InsertFnType = (url: string, alt: string, href: string) => void\n\nexport const createEditorConfig = (\n  server: string,\n  accountId: number | undefined\n): Partial<IEditorConfig> => {\n  return {\n    MENU_CONF: {\n      ['uploadImage']: {\n        server,\n        // 单个文件的最大体积限制，默认为 2M\n        maxFileSize: 5 * 1024 * 1024,\n        // 最多可上传几个文件，默认为 100\n        maxNumberOfFiles: 10,\n        // 选择文件时的类型限制，默认为 ['image/*'] 。如不想限制，则设置为 []\n        allowedFileTypes: ['image/*'],\n\n        // 自定义上传参数，例如传递验证的 token 等。参数会被添加到 formData 中，一起上传到服务端。\n        meta: {\n          accountId: accountId,\n          type: 'image'\n        },\n        // 将 meta 拼接到 url 参数中，默认 false\n        metaWithUrl: true,\n\n        // 自定义增加 http  header\n        headers: {\n          Accept: '*',\n          Authorization: 'Bearer ' + getAccessToken(),\n          'tenant-id': getTenantId()\n        },\n\n        // 跨域是否传递 cookie ，默认为 false\n        withCredentials: true,\n\n        // 超时时间，默认为 10 秒\n        timeout: 5 * 1000, // 5 秒\n\n        // form-data fieldName，后端接口参数名称，默认值wangeditor-uploaded-image\n        fieldName: 'file',\n\n        // 上传之前触发\n        onBeforeUpload(file: File) {\n          console.log(file)\n          return file\n        },\n        // 上传进度的回调函数\n        onProgress(progress: number) {\n          // progress 是 0-100 的数字\n          console.log('progress', progress)\n        },\n        onSuccess(file: File, res: any) {\n          console.log('onSuccess', file, res)\n        },\n        onFailed(file: File, res: any) {\n          message.alertError(res.message)\n          console.log('onFailed', file, res)\n        },\n        onError(file: File, err: any, res: any) {\n          message.alertError(err.message)\n          console.error('onError', file, err, res)\n        },\n        // 自定义插入图片\n        customInsert(res: any, insertFn: InsertFnType) {\n          insertFn(res.data.url, 'image', res.data.url)\n        }\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/draft/index.vue",
    "content": "<template>\n  <!-- 搜索工作栏 -->\n  <ContentWrap>\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"公众号\" prop=\"accountId\">\n        <WxAccountSelect @change=\"onAccountChanged\" />\n      </el-form-item>\n      <el-form-item>\n        <el-button\n          type=\"primary\"\n          plain\n          @click=\"handleAdd\"\n          v-hasPermi=\"['mp:draft:create']\"\n          :disabled=\"accountId === 0\"\n        >\n          <Icon icon=\"ep:plus\" />新增\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <DraftTable\n      :loading=\"loading\"\n      :list=\"list\"\n      @update=\"onUpdate\"\n      @delete=\"onDelete\"\n      @publish=\"onPublish\"\n    />\n    <!-- 分页记录 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 添加或修改草稿对话框 -->\n  <el-dialog\n    :title=\"isCreating ? '新建图文' : '修改图文'\"\n    width=\"80%\"\n    v-model=\"showDialog\"\n    :before-close=\"onBeforeDialogClose\"\n    destroy-on-close\n  >\n    <NewsForm v-model=\"newsList\" v-loading=\"isSubmitting\" :is-creating=\"isCreating\" />\n    <template #footer>\n      <el-button @click=\"showDialog = false\">取 消</el-button>\n      <el-button type=\"primary\" @click=\"onSubmitNewsItem\">提 交</el-button>\n    </template>\n  </el-dialog>\n</template>\n\n<script lang=\"ts\" setup>\nimport WxAccountSelect from '@/views/mp/components/wx-account-select'\nimport * as MpDraftApi from '@/api/mp/draft'\nimport * as MpFreePublishApi from '@/api/mp/freePublish'\nimport {\n  type Article,\n  type NewsItem,\n  NewsForm,\n  DraftTable,\n  createEmptyNewsItem\n} from './components/'\n// import drafts from './mock' // 可以用改本地数据模拟，避免API调用超限\n\ndefineOptions({ name: 'MpDraft' })\n\nconst message = useMessage() // 消息\n\nconst accountId = ref(-1)\nprovide('accountId', accountId)\n\nconst loading = ref(true) // 列表的加载中\nconst list = ref<any[]>([]) // 列表的数据\nconst total = ref(0) // 列表的总页数\n\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  accountId: accountId\n})\n\n// ========== 草稿新建 or 修改 ==========\nconst showDialog = ref(false)\nconst newsList = ref<NewsItem[]>([])\nconst mediaId = ref('')\nconst isCreating = ref(true)\nconst isSubmitting = ref(false)\n\n/** 侦听公众号变化 **/\nconst onAccountChanged = (id: number) => {\n  accountId.value = id\n  queryParams.pageNo = 1\n  getList()\n}\n\n// 关闭弹窗\nconst onBeforeDialogClose = async (onDone: () => {}) => {\n  try {\n    await message.confirm('修改内容可能还未保存，确定关闭吗?')\n    onDone()\n  } catch {}\n}\n\n// ======================== 列表查询 ========================\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const drafts = await MpDraftApi.getDraftPage(queryParams)\n    drafts.list.forEach((draft) => {\n      const newsList = draft.content.newsItem\n      // 将 thumbUrl 转成 picUrl，保证 wx-news 组件可以预览封面\n      newsList.forEach((item) => {\n        item.picUrl = item.thumbUrl\n      })\n    })\n    list.value = drafts.list\n    total.value = drafts.total\n  } finally {\n    loading.value = false\n  }\n}\n\n// ======================== 新增/修改草稿 ========================\n/** 新增按钮操作 */\nconst handleAdd = () => {\n  isCreating.value = true\n  newsList.value = [createEmptyNewsItem()]\n  showDialog.value = true\n}\n\n/** 更新按钮操作 */\nconst onUpdate = (item: Article) => {\n  mediaId.value = item.mediaId\n  newsList.value = JSON.parse(JSON.stringify(item.content.newsItem))\n  isCreating.value = false\n  showDialog.value = true\n}\n\n/** 提交按钮 */\nconst onSubmitNewsItem = async () => {\n  isSubmitting.value = true\n  try {\n    if (isCreating.value) {\n      await MpDraftApi.createDraft(accountId.value, newsList.value)\n      message.notifySuccess('新增成功')\n    } else {\n      await MpDraftApi.updateDraft(accountId.value, mediaId.value, newsList.value)\n      message.notifySuccess('更新成功')\n    }\n  } finally {\n    showDialog.value = false\n    isSubmitting.value = false\n    await getList()\n  }\n}\n\n// ======================== 草稿箱发布 ========================\nconst onPublish = async (item: Article) => {\n  const mediaId = item.mediaId\n  const content =\n    '你正在通过发布的方式发表内容。 发布不占用群发次数，一天可多次发布。' +\n    '已发布内容不会推送给用户，也不会展示在公众号主页中。 ' +\n    '发布后，你可以前往发表记录获取链接，也可以将发布内容添加到自定义菜单、自动回复、话题和页面模板中。'\n  try {\n    await message.confirm(content)\n    await MpFreePublishApi.submitFreePublish(accountId.value, mediaId)\n    message.notifySuccess('发布成功')\n    await getList()\n  } catch {}\n}\n\n/** 删除按钮操作 */\nconst onDelete = async (item: Article) => {\n  const mediaId = item.mediaId\n  try {\n    await message.confirm('此操作将永久删除该草稿, 是否继续?')\n    await MpDraftApi.deleteDraft(accountId.value, mediaId)\n    message.notifySuccess('删除成功')\n    await getList()\n  } catch {}\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.pagination {\n  float: right;\n  margin-right: 25px;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/draft/mock.js",
    "content": "export default {\n  list: [\n    {\n      mediaId: 'r6ryvl6LrxBU0miaST4Y-q-G9pdsmZw0OYG4FzHQkKfpLfEwIH51wy2bxisx8PvW',\n      content: {\n        newsItem: [\n          {\n            title: '我是标题（OOO）',\n            author: '我是作者',\n            digest: '我是摘要',\n            content: '我是内容',\n            contentSourceUrl: 'https://www.yixiang.co',\n            thumbMediaId: 'r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn',\n            showCoverPic: 0,\n            needOpenComment: 0,\n            onlyFansCanComment: 0,\n            url: 'http://mp.weixin.qq.com/s?__biz=MzA3NjM4MzQzOQ==&tempkey=MTIxMl9XaFphcmtJVFh3VEc4Q1MxQWwxQ3R5R0JGTXBDM1Q0N2ZFQm8zeUphOFlwNEpXSWxTYm9RQnJ6cHVuN2QxTE56SFBCYXc2RE9NcUxIeS1CQjJuUHhTWjBlN2VOeGRpRi1fZUhwN1FNQjdrQV9yRU9EU0hibHREZmZoVW5acnZrN3ZjaWsxejR3RGpKczBzTHFIM0dFNFZWVkpBc0dWWlAzUEhlVmpnfn4%3D&chksm=1f6354802814dd969ef83c0f3babe555c614270b30bc383beaf7ffd13b0257f0fe5ced9af694#rd',\n            thumbUrl:\n              'http://test.yudao.yixiang.co/r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn.png'\n          },\n          {\n            title: '我是标题（XXX）',\n            author: '我是作者',\n            digest: '我是摘要',\n            content: '我是内容',\n            contentSourceUrl: 'https://www.yixiang.co',\n            thumbMediaId: 'r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn',\n            showCoverPic: 0,\n            needOpenComment: 0,\n            onlyFansCanComment: 0,\n            url: 'http://mp.weixin.qq.com/s?__biz=MzA3NjM4MzQzOQ==&tempkey=MTIxMl9yTlYwOEs1clpwcE5OUEhCQWwxQ3R5R0JGTXBDM1Q0N2ZFQm8zeUphOFlwNEpXSWxTYm9RQnJ6cHVuN0NSMjFqN3N1aUZMbFNVLTZHN2ZDME9qOGp2THk2RFNlSTlKZ3Y1czFVZDdQQm5IeUg3dEppSUtpQUh5SExOOTRkT3dHNUdBdHdWSWlOendlREV3dS1jUEVQbFpiVTZmVW5iRWhZcGdkNTFRfn4%3D&chksm=1f6354802814dd96a403151cd44c7da4eecf0e475d25423e46ecd795b513bafd829a75daef9b#rd',\n            thumbUrl:\n              'http://test.yudao.yixiang.co/r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn.png'\n          }\n        ]\n      },\n      updateTime: 1673655730\n    },\n    {\n      mediaId: 'r6ryvl6LrxBU0miaST4Y-jGpXnO73ihN0lsNXknCRQHapp2xgHMRxHKG50LituFe',\n      content: {\n        newsItem: [\n          {\n            title: '我是标题（修改）',\n            author: '我是作者',\n            digest: '我是摘要',\n            content: '我是内容',\n            contentSourceUrl: 'https://www.yixiang.co',\n            thumbMediaId: 'r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn',\n            showCoverPic: 0,\n            needOpenComment: 0,\n            onlyFansCanComment: 0,\n            url: 'http://mp.weixin.qq.com/s?__biz=MzA3NjM4MzQzOQ==&tempkey=MTIxMl95WVFXYndIZnZJd0t5cjgvQWwxQ3R5R0JGTXBDM1Q0N2ZFQm8zeUphOFlwNEpXSWxTYm9RQnJ6cHVuN1dlNURPbWswbEF4RDd5dVJTdjQ4cm9Cc0Q1TWhpMUh6SE1hVEE3ZHljaHhlZjZYSGF5N2JNSHpDTlh6ajNZbkpGTGpTcUQ4M3NMdW41ZUpXNFZZQ1VKbVlaMVp5ekxEV1czREdsY1dOYTZnfn4%3D&chksm=1f6354be2814dda8e6238037c2ebd52b1c8e80e93249a861ad80e4d40e5ca7207233475ca689#rd',\n            thumbUrl:\n              'http://test.yudao.yixiang.co/r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn.png'\n          }\n        ]\n      },\n      updateTime: 1673655584\n    },\n    {\n      mediaId: 'r6ryvl6LrxBU0miaST4Y-v5SrbNCPpD6M_p3TmSrYwTjKogs-0DMJgmjMyNZPeMO',\n      content: {\n        newsItem: [\n          {\n            title: '1321',\n            author: '3232',\n            digest: '1333',\n            content: '<p>444</p>',\n            contentSourceUrl: 'http://www.yixiang.co',\n            thumbMediaId: 'r6ryvl6LrxBU0miaST4Y-tlQmcl3RdC-Jcgns6IQtf7zenGy3b86WLT7GzUcrb1T',\n            showCoverPic: 0,\n            needOpenComment: 0,\n            onlyFansCanComment: 0,\n            url: 'http://mp.weixin.qq.com/s?__biz=MzA3NjM4MzQzOQ==&tempkey=MTIxMl9jelJiaDAzbmdpSkJOZ2M2QWwxQ3R5R0JGTXBDM1Q0N2ZFQm8zeUphOFlwNEpXSWxTYm9RQnJ6cHVuNDNXVVc2ZDRYeTY0Zm1weXR6dE9vQWh1TzEwbEpUVnRfVzJyaGFDNXBkZ0ZXM2JFOTNaRHNhOHRUeFdEanhMeS01X01kMUNWQ1BpRER3cjYwTl9pMnpFLUJhZXFucVVfM1pDUXlTUEl1S25nfn4%3D&chksm=1f6354bc2814ddaa56a90ad5bc3d078601c8d1589ba01827a8170587bc830ff9747b5f59c3a0#rd',\n            thumbUrl:\n              'http://mmbiz.qpic.cn/mmbiz_png/btUmCVHwbJUoicwBiacjVeQbu6QxgBVrukfSJXz509boa21SpH8OVHAqXCJiaiaAaHQJNxwwsa0gHRXVr0G5EZYamw/0?wx_fmt=png'\n          }\n        ]\n      },\n      updateTime: 1673628969\n    },\n    {\n      mediaId: 'r6ryvl6LrxBU0miaST4Y-vdWrisK5EZbk4Y3tzh8P0PG0eEUbnQrh0BcsEb3WNP0',\n      content: {\n        newsItem: [\n          {\n            title: 'tudou',\n            author: 'haha',\n            digest: '312',\n            content: '<p>132312</p>',\n            contentSourceUrl: 'http://www.yixiang.co',\n            thumbMediaId: 'r6ryvl6LrxBU0miaST4Y-pgFtUNLu1foMSAMkoOsrQrTZ8EtTMssBLfTtzP0dfjG',\n            showCoverPic: 0,\n            needOpenComment: 0,\n            onlyFansCanComment: 0,\n            url: 'http://mp.weixin.qq.com/s?__biz=MzA3NjM4MzQzOQ==&tempkey=MTIxMl9qdkJ1ZjBoUmg2Uk9TS3RlQWwxQ3R5R0JGTXBDM1Q0N2ZFQm8zeUphOFlwNEpXSWxTYm9RQnJ6cHVuNVg2aTJsaC1fMkU2eXNacUplN3VDTTZFZkhtMjhuTUZvWkxsNDBRSXExY2tiVXRHb09TaHgtREhzY3doZ0JYeC1TSTZ5eWZldXJsOWtfbV8yMi1aYkcyZ2pOY0haM0Ntb3VSWEtxUGVFRlNBfn4%3D&chksm=1f6354ba2814ddacf0184b24d310483641ef190b1faac098c285eb416c70017e2f54decfa1af#rd',\n            thumbUrl:\n              'http://test.yudao.yixiang.co/r6ryvl6LrxBU0miaST4Y-pgFtUNLu1foMSAMkoOsrQrTZ8EtTMssBLfTtzP0dfjG.png'\n          }\n        ]\n      },\n      updateTime: 1673628760\n    },\n    {\n      mediaId: 'r6ryvl6LrxBU0miaST4Y-u9kTIm1DhWZDdXyxsxUVv2Z5DAB99IPxkIRTUUD206k',\n      content: {\n        newsItem: [\n          {\n            title: '12',\n            author: '333',\n            digest: '123',\n            content: '123',\n            contentSourceUrl: 'https://www.yixiang.co',\n            thumbMediaId: 'r6ryvl6LrxBU0miaST4Y-jVixJGgnBnkBPRbuVptOW0CHYuQFyiOVNtamctS8xU8',\n            showCoverPic: 0,\n            needOpenComment: 0,\n            onlyFansCanComment: 0,\n            url: 'http://mp.weixin.qq.com/s?__biz=MzA3NjM4MzQzOQ==&tempkey=MTIxMl9qVVhpSDZUaFJWTzBBWWRVQWwxQ3R5R0JGTXBDM1Q0N2ZFQm8zeUphOFlwNEpXSWxTYm9RQnJ6cHVuNWRnTDJWYmF2NER0clV1bThmQ0xUR3hqQnJkZ3BJSUNmNDJmc0lCZ1dadkVnZ3Z5bkN4YWtVUjhoaWZWYzZURUR4NnpMd0Y4Z3U5aUdib0lkMzI4Rjg3SG9JX2FycTMxbUctOHplaTlQVVhnfn4%3D&chksm=1f6354b62814dda076c778af33f06580165d8aa81f7798d55cfabb1886b5c74d9b2124a3535c#rd',\n            thumbUrl:\n              'http://test.yudao.yixiang.co/r6ryvl6LrxBU0miaST4Y-jVixJGgnBnkBPRbuVptOW0CHYuQFyiOVNtamctS8xU8.jpg'\n          }\n        ]\n      },\n      updateTime: 1673626494\n    },\n    {\n      mediaId: 'r6ryvl6LrxBU0miaST4Y-sO24upobaENDmeByfBTfaozB3aOqSMAV0lGy-UkHXE7',\n      content: {\n        newsItem: [\n          {\n            title: '我是标题',\n            author: '我是作者',\n            digest: '我是摘要',\n            content: '我是内容',\n            contentSourceUrl: 'https://www.yixiang.co',\n            thumbMediaId: 'r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn',\n            showCoverPic: 0,\n            needOpenComment: 0,\n            onlyFansCanComment: 0,\n            url: 'http://mp.weixin.qq.com/s?__biz=MzA3NjM4MzQzOQ==&tempkey=MTIxMl9LT2dqRnpMNUpsR0hjYWtBQWwxQ3R5R0JGTXBDM1Q0N2ZFQm8zeUphOFlwNEpXSWxTYm9RQnJ6cHVuNGNmazZTdlE5WkxvU0tfX2V5cjV2WjJiR0xjQUhyREFSZWo2eWNrUW9EYVh6ZkpWRXBLR3FmTEV6YldBMno3Q2ZvVXBSdzlaVDc3aFhndEpQWUwzWmFMUWt0YVVURE1VZ1FsQTdPMlRtc3JBfn4%3D&chksm=1f6354aa2814ddbcc2637382f963a8742993ac38ebcebe6e3411df5ac82ac7bbdb391be6494a#rd',\n            thumbUrl:\n              'http://test.yudao.yixiang.co/r6ryvl6LrxBU0miaST4Y-pIcmK-zAAId-9TGgy-DrSLhjVuWbuT3ZBjk9K1yQ0Dn.png'\n          }\n        ]\n      },\n      updateTime: 1673534279\n    }\n  ],\n  total: 6\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/freePublish/index.vue",
    "content": "<template>\n  <!-- 搜索工作栏 -->\n  <ContentWrap>\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"公众号\" prop=\"accountId\">\n        <WxAccountSelect @change=\"onAccountChanged\" />\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <div class=\"waterfall\" v-loading=\"loading\">\n      <div\n        class=\"waterfall-item\"\n        v-show=\"item.content && item.content.newsItem\"\n        v-for=\"item in list\"\n        :key=\"item.articleId\"\n      >\n        <wx-news :articles=\"item.content.newsItem\" />\n        <el-row justify=\"center\" class=\"ope-row\">\n          <el-button\n            type=\"danger\"\n            circle\n            @click=\"handleDelete(item)\"\n            v-hasPermi=\"['mp:free-publish:delete']\"\n          >\n            <Icon icon=\"ep:delete\" />\n          </el-button>\n        </el-row>\n      </div>\n    </div>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n</template>\n\n<script lang=\"ts\" setup>\nimport * as FreePublishApi from '@/api/mp/freePublish'\nimport WxNews from '@/views/mp/components/wx-news'\nimport WxAccountSelect from '@/views/mp/components/wx-account-select'\n\ndefineOptions({ name: 'MpFreePublish' })\n\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref<any[]>([]) // 列表的数据\n\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  accountId: -1\n})\n\n/** 侦听公众号变化 **/\nconst onAccountChanged = (id: number) => {\n  queryParams.accountId = id\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 查询列表 */\nconst getList = async () => {\n  try {\n    loading.value = true\n    const data = await FreePublishApi.getFreePublishPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (item: any) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm('删除后用户将无法访问此页面，确定删除？')\n    // 发起删除\n    await FreePublishApi.deleteFreePublish(queryParams.accountId, item.articleId)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n</script>\n<style lang=\"scss\" scoped>\n@media (width >= 992px) and (width <= 1300px) {\n  .waterfall {\n    column-count: 3;\n  }\n\n  p {\n    color: red;\n  }\n}\n\n@media (width >= 768px) and (width <= 991px) {\n  .waterfall {\n    column-count: 2;\n  }\n\n  p {\n    color: orange;\n  }\n}\n\n@media (width <= 767px) {\n  .waterfall {\n    column-count: 1;\n  }\n}\n\n.ope-row {\n  padding-top: 5px;\n  margin-top: 5px;\n  text-align: center;\n  border-top: 1px solid #eaeaea;\n}\n\n.item-name {\n  overflow: hidden;\n  font-size: 12px;\n  text-align: center;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n.el-upload__tip {\n  margin-left: 5px;\n}\n\n/* 新增图文 */\n.left {\n  display: inline-block;\n  width: 35%;\n  margin-top: 200px;\n  vertical-align: top;\n}\n\n.right {\n  display: inline-block;\n  width: 60%;\n  margin-top: -40px;\n}\n\n.avatar-uploader {\n  display: inline-block;\n  width: 20%;\n}\n\n.avatar-uploader .el-upload {\n  position: relative;\n  overflow: hidden;\n  text-align: unset !important;\n  cursor: pointer;\n  border-radius: 6px;\n}\n\n.avatar-uploader .el-upload:hover {\n  border-color: #165dff;\n}\n\n.avatar-uploader-icon {\n  width: 120px;\n  height: 120px;\n  font-size: 28px;\n  line-height: 120px;\n  color: #8c939d;\n  text-align: center;\n  border: 1px solid #d9d9d9;\n}\n\n.avatar {\n  width: 230px;\n  height: 120px;\n}\n\n.avatar1 {\n  width: 120px;\n  height: 120px;\n}\n\n.digest {\n  display: inline-block;\n  width: 60%;\n  vertical-align: top;\n}\n\n/* 新增图文 */\n\n/* 瀑布流样式 */\n.waterfall {\n  width: 100%;\n  column-gap: 10px;\n  column-count: 5;\n  margin: 0 auto;\n}\n\n.waterfall-item {\n  padding: 10px;\n  margin-bottom: 10px;\n  break-inside: avoid;\n  border: 1px solid #eaeaea;\n}\n\np {\n  line-height: 30px;\n}\n\n/* 瀑布流样式 */\n.news-main {\n  width: 100%;\n  height: 120px;\n  margin: auto;\n  background-color: #fff;\n}\n\n.news-content {\n  position: relative;\n  width: 100%;\n  height: 120px;\n  background-color: #acadae;\n}\n\n.news-content-title {\n  position: absolute;\n  bottom: 0;\n  left: 0;\n  display: inline-block;\n  width: 98%;\n  height: 25px;\n  padding: 1%;\n  overflow: hidden;\n  font-size: 15px;\n  color: #fff;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n  background-color: black;\n  opacity: 0.65;\n}\n\n.news-main-item {\n  width: 100%;\n  padding: 5px 0;\n  margin: auto;\n  background-color: #fff;\n  border-top: 1px solid #eaeaea;\n}\n\n.news-content-item {\n  position: relative;\n  margin-left: -3px;\n}\n\n.news-content-item-title {\n  display: inline-block;\n  width: 70%;\n  font-size: 12px;\n}\n\n.news-content-item-img {\n  display: inline-block;\n  width: 25%;\n  background-color: #acadae;\n}\n\n.input-tt {\n  padding: 5px;\n}\n\n.activeAddNews {\n  border: 5px solid #2bb673;\n}\n\n.news-main-plus {\n  width: 280px;\n  height: 50px;\n  margin: auto;\n  text-align: center;\n}\n\n.icon-plus {\n  margin: 10px;\n  font-size: 25px;\n}\n\n.select-item {\n  width: 60%;\n  padding: 10px;\n  margin: 0 auto 10px;\n  border: 1px solid #eaeaea;\n}\n\n.father .child {\n  position: relative;\n  bottom: 25px;\n  display: none;\n  text-align: center;\n}\n\n.father:hover .child {\n  display: block;\n}\n\n.thumb-div {\n  display: inline-block;\n  width: 30%;\n  text-align: center;\n}\n\n.thumb-but {\n  margin: 5px;\n}\n\n.material-img {\n  width: 100%;\n  height: 100%;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/hooks/useUpload.ts",
    "content": "import type { UploadRawFile } from 'element-plus'\n\nconst message = useMessage() // 消息\n\nenum UploadType {\n  Image = 'image',\n  Voice = 'voice',\n  Video = 'video'\n}\n\nconst useBeforeUpload = (type: UploadType, maxSizeMB: number) => {\n  const fn = (rawFile: UploadRawFile): boolean => {\n    let allowTypes: string[] = []\n    let name = ''\n\n    switch (type) {\n      case UploadType.Image:\n        allowTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/bmp', 'image/jpg']\n        maxSizeMB = 2\n        name = '图片'\n        break\n      case UploadType.Voice:\n        allowTypes = ['audio/mp3', 'audio/mpeg', 'audio/wma', 'audio/wav', 'audio/amr']\n        maxSizeMB = 2\n        name = '语音'\n        break\n      case UploadType.Video:\n        allowTypes = ['video/mp4']\n        maxSizeMB = 10\n        name = '视频'\n        break\n    }\n    // 格式不正确\n    if (!allowTypes.includes(rawFile.type)) {\n      message.error(`上传${name}格式不对!`)\n      return false\n    }\n    // 大小不正确\n    if (rawFile.size / 1024 / 1024 > maxSizeMB) {\n      message.error(`上传${name}大小不能超过${maxSizeMB}M!`)\n      return false\n    }\n\n    return true\n  }\n\n  return fn\n}\n\nexport { UploadType, useBeforeUpload }\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/material/components/ImageTable.vue",
    "content": "<template>\n  <div class=\"waterfall\" v-loading=\"props.loading\">\n    <div class=\"waterfall-item\" v-for=\"item in props.list\" :key=\"item.id\">\n      <a target=\"_blank\" :href=\"item.url\">\n        <img class=\"material-img\" :src=\"item.url\" />\n        <div class=\"item-name\">{{ item.name }}</div>\n      </a>\n      <el-row justify=\"center\">\n        <el-button\n          type=\"danger\"\n          circle\n          @click=\"emit('delete', item.id)\"\n          v-hasPermi=\"['mp:material:delete']\"\n        >\n          <Icon icon=\"ep:delete\" />\n        </el-button>\n      </el-row>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nconst props = defineProps<{\n  list: any[]\n  loading: boolean\n}>()\n\nconst emit = defineEmits<{\n  (e: 'delete', v: number)\n}>()\n</script>\n\n<style lang=\"scss\" scoped>\n@media (width >= 992px) and (width <= 1300px) {\n  .waterfall {\n    column-count: 3;\n  }\n\n  p {\n    color: red;\n  }\n}\n\n@media (width >= 768px) and (width <= 991px) {\n  .waterfall {\n    column-count: 2;\n  }\n\n  p {\n    color: orange;\n  }\n}\n\n@media (width <= 767px) {\n  .waterfall {\n    column-count: 1;\n  }\n}\n\n.waterfall {\n  width: 100%;\n  column-gap: 10px;\n  column-count: 5;\n  margin-top: 10px;\n\n  /* yshop源码：增加 10px，避免顶着上面 */\n}\n\n.waterfall-item {\n  padding: 10px;\n  margin-bottom: 10px;\n  break-inside: avoid;\n  border: 1px solid #eaeaea;\n}\n\n.material-img {\n  width: 100%;\n}\n\np {\n  line-height: 30px;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/material/components/UploadFile.vue",
    "content": "<template>\n  <el-upload\n    :action=\"UPLOAD_URL\"\n    :headers=\"HEADERS\"\n    multiple\n    :limit=\"1\"\n    :file-list=\"fileList\"\n    :data=\"uploadData\"\n    :on-error=\"onUploadError\"\n    :before-upload=\"onBeforeUpload\"\n    :on-success=\"onUploadSuccess\"\n  >\n    <el-button type=\"primary\" plain> 点击上传 </el-button>\n    <template #tip>\n      <span class=\"el-upload__tip\" style=\"margin-left: 5px\">\n        <slot></slot>\n      </span>\n    </template>\n  </el-upload>\n</template>\n<script lang=\"ts\" setup>\nimport type { UploadProps, UploadUserFile } from 'element-plus'\nimport {\n  HEADERS,\n  UPLOAD_URL,\n  UploadData,\n  UploadType,\n  beforeImageUpload,\n  beforeVoiceUpload\n} from './upload'\n\nconst message = useMessage()\n\nconst props = defineProps<{ type: UploadType }>()\n\nconst accountId = inject<number>('accountId')\n\nconst fileList = ref<UploadUserFile[]>([])\nconst emit = defineEmits<{\n  (e: 'uploaded', v: void)\n}>()\n\nconst uploadData: UploadData = reactive({\n  type: UploadType.Image,\n  title: '',\n  introduction: '',\n  accountId: accountId!\n})\n\n/** 上传前检查 */\nconst onBeforeUpload = props.type === UploadType.Image ? beforeImageUpload : beforeVoiceUpload\n\n/** 上传成功处理 */\nconst onUploadSuccess: UploadProps['onSuccess'] = (res: any) => {\n  if (res.code !== 0) {\n    message.alertError('上传出错：' + res.msg)\n    return false\n  }\n\n  // 清空上传时的各种数据\n  fileList.value = []\n  uploadData.title = ''\n  uploadData.introduction = ''\n\n  message.notifySuccess('上传成功')\n  emit('uploaded')\n}\n\n/** 上传失败处理 */\nconst onUploadError = (err: Error) => message.error('上传失败: ' + err.message)\n</script>\n\n<style lang=\"scss\" scoped>\n.el-upload__tip {\n  margin-left: 5px;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/material/components/UploadVideo.vue",
    "content": "<template>\n  <el-dialog title=\"新建视频\" v-model=\"showDialog\" width=\"600px\">\n    <el-upload\n      :action=\"UPLOAD_URL\"\n      :headers=\"HEADERS\"\n      multiple\n      :limit=\"1\"\n      :file-list=\"fileList\"\n      :data=\"uploadData\"\n      :before-upload=\"beforeVideoUpload\"\n      :on-error=\"onUploadError\"\n      :on-success=\"onUploadSuccess\"\n      ref=\"uploadVideoRef\"\n      :auto-upload=\"false\"\n      class=\"mb-5\"\n    >\n      <template #trigger>\n        <el-button type=\"primary\" plain>选择视频</el-button>\n      </template>\n      <template #tip>\n        <span class=\"el-upload__tip\" style=\"margin-left: 10px\"\n          >格式支持 MP4，文件大小不超过 10MB</span\n        >\n      </template>\n    </el-upload>\n    <el-divider />\n    <el-form :model=\"uploadData\" :rules=\"uploadRules\" ref=\"uploadFormRef\">\n      <el-form-item label=\"标题\" prop=\"title\">\n        <el-input\n          v-model=\"uploadData.title\"\n          placeholder=\"标题将展示在相关播放页面，建议填写清晰、准确、生动的标题\"\n        />\n      </el-form-item>\n      <el-form-item label=\"描述\" prop=\"introduction\">\n        <el-input\n          :rows=\"3\"\n          type=\"textarea\"\n          v-model=\"uploadData.introduction\"\n          placeholder=\"介绍语将展示在相关播放页面，建议填写简洁明确、有信息量的内容\"\n        />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button @click=\"showDialog = false\">取 消</el-button>\n      <el-button type=\"primary\" @click=\"submitVideo\">提 交</el-button>\n    </template>\n  </el-dialog>\n</template>\n\n<script lang=\"ts\" setup>\nimport type {\n  FormInstance,\n  FormRules,\n  UploadInstance,\n  UploadProps,\n  UploadUserFile\n} from 'element-plus'\nimport { HEADERS, UploadData, UPLOAD_URL, UploadType, beforeVideoUpload } from './upload'\n\nconst message = useMessage()\n\nconst accountId = inject<number>('accountId')\n\nconst uploadRules: FormRules = {\n  title: [{ required: true, message: '请输入标题', trigger: 'blur' }],\n  introduction: [{ required: true, message: '请输入描述', trigger: 'blur' }]\n}\n\nconst props = defineProps({\n  modelValue: {\n    type: Boolean,\n    default: false\n  }\n})\nconst emit = defineEmits<{\n  (e: 'update:modelValue', v: boolean)\n  (e: 'uploaded', v: void)\n}>()\n\nconst showDialog = computed<boolean>({\n  get() {\n    return props.modelValue\n  },\n  set(val) {\n    emit('update:modelValue', val)\n  }\n})\n\nconst fileList = ref<UploadUserFile[]>([])\n\nconst uploadData: UploadData = reactive({\n  type: UploadType.Video,\n  title: '',\n  introduction: '',\n  accountId: accountId!\n})\n\nconst uploadFormRef = ref<FormInstance | null>(null)\nconst uploadVideoRef = ref<UploadInstance | null>(null)\n\nconst submitVideo = () => {\n  uploadFormRef.value?.validate((valid) => {\n    if (!valid) {\n      return false\n    }\n    uploadVideoRef.value?.submit()\n  })\n}\n\n/** 上传成功处理 */\nconst onUploadSuccess: UploadProps['onSuccess'] = (res: any) => {\n  if (res.code !== 0) {\n    message.error('上传出错：' + res.msg)\n    return false\n  }\n\n  // 清空上传时的各种数据\n  fileList.value = []\n  uploadData.title = ''\n  uploadData.introduction = ''\n\n  showDialog.value = false\n  message.notifySuccess('上传成功')\n  emit('uploaded')\n}\n\n/** 上传失败处理 */\nconst onUploadError = (err: Error) => message.error(`上传失败: ${err.message}`)\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/material/components/VideoTable.vue",
    "content": "<template>\n  <el-table :data=\"props.list\" stripe border v-loading=\"props.loading\" style=\"margin-top: 10px\">\n    <el-table-column label=\"编号\" align=\"center\" prop=\"mediaId\" />\n    <el-table-column label=\"文件名\" align=\"center\" prop=\"name\" />\n    <el-table-column label=\"标题\" align=\"center\" prop=\"title\" />\n    <el-table-column label=\"介绍\" align=\"center\" prop=\"introduction\" />\n    <el-table-column label=\"视频\" align=\"center\">\n      <template #default=\"scope\">\n        <WxVideoPlayer v-if=\"scope.row.url\" :url=\"scope.row.url\" />\n      </template>\n    </el-table-column>\n    <el-table-column\n      label=\"上传时间\"\n      align=\"center\"\n      :formatter=\"dateFormatter\"\n      prop=\"createTime\"\n      width=\"180\"\n    >\n      <template #default=\"scope\">\n        <span>{{ scope.row.createTime }}</span>\n      </template>\n    </el-table-column>\n    <el-table-column label=\"操作\" align=\"center\" fixed=\"right\">\n      <template #default=\"scope\">\n        <el-button type=\"primary\" link @click=\"handleDownload(scope.row.url)\">\n          <Icon icon=\"ep:download\" />下载\n        </el-button>\n        <el-button\n          type=\"primary\"\n          link\n          @click=\"emit('delete', scope.row.id)\"\n          v-hasPermi=\"['mp:material:delete']\"\n        >\n          <Icon icon=\"ep:delete\" />删除\n        </el-button>\n      </template>\n    </el-table-column>\n  </el-table>\n</template>\n\n<script lang=\"ts\" setup>\nimport WxVideoPlayer from '@/views/mp/components/wx-video-play'\nimport { dateFormatter } from '@/utils/formatTime'\n\nconst props = defineProps<{\n  list: any[]\n  loading: boolean\n}>()\n\nconst emit = defineEmits<{\n  (e: 'delete', v: number)\n  (e: 'download', v: string)\n}>()\n\n// 下载文件\nconst handleDownload = (url: string) => {\n  window.open(url, '_blank')\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/material/components/VoiceTable.vue",
    "content": "<template>\n  <el-table :data=\"props.list\" stripe border v-loading=\"props.loading\" style=\"margin-top: 10px\">\n    <el-table-column label=\"编号\" align=\"center\" prop=\"mediaId\" />\n    <el-table-column label=\"文件名\" align=\"center\" prop=\"name\" />\n    <el-table-column label=\"语音\" align=\"center\">\n      <template #default=\"scope\">\n        <WxVoicePlayer v-if=\"scope.row.url\" :url=\"scope.row.url\" />\n      </template>\n    </el-table-column>\n    <el-table-column\n      label=\"上传时间\"\n      align=\"center\"\n      prop=\"createTime\"\n      :formatter=\"dateFormatter\"\n      width=\"180\"\n    >\n      <template #default=\"scope\">\n        <span>{{ scope.row.createTime }}</span>\n      </template>\n    </el-table-column>\n    <el-table-column label=\"操作\" align=\"center\" class-name=\"small-padding fixed-width\">\n      <template #default=\"scope\">\n        <el-button type=\"primary\" link @click=\"emit('delete', scope.row.id)\">\n          <Icon icon=\"ep:download\" />下载\n        </el-button>\n        <el-button\n          type=\"primary\"\n          link\n          @click=\"emit('delete', scope.row.id)\"\n          v-hasPermi=\"['mp:material:delete']\"\n        >\n          <Icon icon=\"ep:delete\" />删除\n        </el-button>\n      </template>\n    </el-table-column>\n  </el-table>\n</template>\n\n<script lang=\"ts\" setup>\nimport WxVoicePlayer from '@/views/mp/components/wx-voice-play'\nimport { dateFormatter } from '@/utils/formatTime'\n\nconst props = defineProps<{\n  list: any[]\n  loading: boolean\n}>()\n\nconst emit = defineEmits<{\n  (e: 'delete', v: number)\n}>()\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/material/components/upload.ts",
    "content": "import type { UploadProps, UploadRawFile } from 'element-plus'\nimport { getAccessToken } from '@/utils/auth'\nimport { UploadType, useBeforeUpload } from '@/views/mp/hooks/useUpload'\n\nconst HEADERS = { Authorization: 'Bearer ' + getAccessToken() } // 请求头\nconst UPLOAD_URL = import.meta.env.VITE_BASE_URL + '/admin-api/mp/material/upload-permanent' // 上传地址\n\ninterface UploadData {\n  type: UploadType\n  title: string\n  introduction: string\n  accountId: number\n}\n\nconst beforeImageUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) =>\n  useBeforeUpload(UploadType.Image, 2)(rawFile)\n\nconst beforeVoiceUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) =>\n  useBeforeUpload(UploadType.Voice, 2)(rawFile)\n\nconst beforeVideoUpload: UploadProps['beforeUpload'] = (rawFile: UploadRawFile) =>\n  useBeforeUpload(UploadType.Video, 10)(rawFile)\n\nexport {\n  HEADERS,\n  UPLOAD_URL,\n  UploadType,\n  UploadData,\n  beforeImageUpload,\n  beforeVoiceUpload,\n  beforeVideoUpload\n}\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/material/index.vue",
    "content": "<template>\n  <!-- 搜索工作栏 -->\n  <ContentWrap>\n    <el-form class=\"-mb-15px\" :inline=\"true\" label-width=\"68px\">\n      <el-form-item label=\"公众号\" prop=\"accountId\">\n        <WxAccountSelect @change=\"onAccountChanged\" />\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <ContentWrap>\n    <el-tabs v-model=\"type\" @tab-change=\"onTabChange\">\n      <!-- tab 1：图片  -->\n      <el-tab-pane :name=\"UploadType.Image\">\n        <template #label>\n          <el-row align=\"middle\"> <Icon icon=\"ep:picture\" />图片 </el-row>\n        </template>\n        <UploadFile\n          v-hasPermi=\"['mp:material:upload-permanent']\"\n          :type=\"UploadType.Image\"\n          @uploaded=\"getList\"\n        >\n          支持 bmp/png/jpeg/jpg/gif 格式，大小不超过 2M\n        </UploadFile>\n        <!-- 列表 -->\n        <ImageTable :loading=\"loading\" :list=\"list\" @delete=\"handleDelete\" />\n        <!-- 分页组件 -->\n        <Pagination\n          :total=\"total\"\n          v-model:page=\"queryParams.pageNo\"\n          v-model:limit=\"queryParams.pageSize\"\n          @pagination=\"getList\"\n        />\n      </el-tab-pane>\n\n      <!-- tab 2：语音  -->\n      <el-tab-pane :name=\"UploadType.Voice\">\n        <template #label>\n          <el-row align=\"middle\"> <Icon icon=\"ep:microphone\" />语音 </el-row>\n        </template>\n        <UploadFile\n          v-hasPermi=\"['mp:material:upload-permanent']\"\n          :type=\"UploadType.Voice\"\n          @uploaded=\"getList\"\n        >\n          格式支持 mp3/wma/wav/amr，文件大小不超过 2M，播放长度不超过 60s\n        </UploadFile>\n        <!-- 列表 -->\n        <VoiceTable :list=\"list\" :loading=\"loading\" @delete=\"handleDelete\" />\n        <!-- 分页组件 -->\n        <Pagination\n          :total=\"total\"\n          v-model:page=\"queryParams.pageNo\"\n          v-model:limit=\"queryParams.pageSize\"\n          @pagination=\"getList\"\n        />\n      </el-tab-pane>\n\n      <!-- tab 3：视频 -->\n      <el-tab-pane :name=\"UploadType.Video\">\n        <template #label>\n          <el-row align=\"middle\"> <Icon icon=\"ep:video-play\" /> 视频 </el-row>\n        </template>\n        <el-button\n          v-hasPermi=\"['mp:material:upload-permanent']\"\n          type=\"primary\"\n          plain\n          @click=\"showCreateVideo = true\"\n          >新建视频</el-button\n        >\n        <!-- 新建视频的弹窗 -->\n        <UploadVideo v-model=\"showCreateVideo\" />\n        <!-- 列表 -->\n        <VideoTable :list=\"list\" :loading=\"loading\" @delete=\"handleDelete\" />\n        <!-- 分页组件 -->\n        <Pagination\n          :total=\"total\"\n          v-model:page=\"queryParams.pageNo\"\n          v-model:limit=\"queryParams.pageSize\"\n          @pagination=\"getList\"\n        />\n      </el-tab-pane>\n    </el-tabs>\n  </ContentWrap>\n</template>\n<script lang=\"ts\" setup name=\"MpMaterial\">\nimport WxAccountSelect from '@/views/mp/components/wx-account-select'\nimport ImageTable from './components/ImageTable.vue'\nimport VoiceTable from './components/VoiceTable.vue'\nimport VideoTable from './components/VideoTable.vue'\nimport UploadFile from './components/UploadFile.vue'\nimport UploadVideo from './components/UploadVideo.vue'\nimport { UploadType } from './components/upload'\nimport * as MpMaterialApi from '@/api/mp/material'\nconst message = useMessage() // 消息\n\nconst type = ref<UploadType>(UploadType.Image) // 素材类型\nconst loading = ref(false) // 遮罩层\nconst list = ref<any[]>([]) // 总条数\nconst total = ref(0) // 数据列表\n\nconst accountId = ref(-1)\nprovide('accountId', accountId)\n\n// 查询参数\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  accountId: accountId,\n  permanent: true\n})\nconst showCreateVideo = ref(false) // 是否新建视频的弹窗\n\n/** 侦听公众号变化 **/\nconst onAccountChanged = (id: number) => {\n  accountId.value = id\n  queryParams.accountId = id\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await MpMaterialApi.getMaterialPage({\n      ...queryParams,\n      type: type.value\n    })\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 处理 table 切换 */\nconst onTabChange = () => {\n  // 提前情况数据，避免 tab 切换后显示垃圾数据\n  list.value = []\n  total.value = 0\n  // 从第一页开始查询\n  handleQuery()\n}\n\n/** 处理删除操作 */\nconst handleDelete = async (id: number) => {\n  await message.confirm('此操作将永久删除该文件, 是否继续?')\n  await MpMaterialApi.deletePermanentMaterial(id)\n  message.alertSuccess('删除成功')\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/menu/components/MenuEditor.vue",
    "content": "<template>\n  <div>\n    <div class=\"configure_page\">\n      <div class=\"delete_btn\">\n        <el-button type=\"danger\" @click=\"emit('delete')\">\n          <Icon icon=\"ep:delete\" />\n          删除当前菜单\n        </el-button>\n      </div>\n      <div>\n        <span>菜单名称：</span>\n        <el-input\n          class=\"input_width\"\n          v-model=\"menu.name\"\n          placeholder=\"请输入菜单名称\"\n          :maxlength=\"isParent ? 4 : 7\"\n          clearable\n        />\n      </div>\n      <div v-if=\"isLeave\">\n        <div class=\"menu_content\">\n          <span>菜单标识：</span>\n          <el-input\n            class=\"input_width\"\n            v-model=\"menu.menuKey\"\n            placeholder=\"请输入菜单 KEY\"\n            clearable\n          />\n        </div>\n        <div class=\"menu_content\">\n          <span>菜单内容：</span>\n          <el-select v-model=\"menu.type\" clearable placeholder=\"请选择\" class=\"menu_option\">\n            <el-option\n              v-for=\"item in menuOptions\"\n              :label=\"item.label\"\n              :value=\"item.value\"\n              :key=\"item.value\"\n            />\n          </el-select>\n        </div>\n        <div class=\"configur_content\" v-if=\"menu.type === 'view'\">\n          <span>跳转链接：</span>\n          <el-input class=\"input_width\" v-model=\"menu.url\" placeholder=\"请输入链接\" clearable />\n        </div>\n        <div class=\"configur_content\" v-if=\"menu.type === 'miniprogram'\">\n          <div class=\"applet\">\n            <span>小程序的 appid ：</span>\n            <el-input\n              class=\"input_width\"\n              v-model=\"menu.miniProgramAppId\"\n              placeholder=\"请输入小程序的appid\"\n              clearable\n            />\n          </div>\n          <div class=\"applet\">\n            <span>小程序的页面路径：</span>\n            <el-input\n              class=\"input_width\"\n              v-model=\"menu.miniProgramPagePath\"\n              placeholder=\"请输入小程序的页面路径，如：pages/index\"\n              clearable\n            />\n          </div>\n          <div class=\"applet\">\n            <span>小程序的备用网页：</span>\n            <el-input\n              class=\"input_width\"\n              v-model=\"menu.url\"\n              placeholder=\"不支持小程序的老版本客户端将打开本网页\"\n              clearable\n            />\n          </div>\n          <p class=\"blue\">tips:需要和公众号进行关联才可以把小程序绑定带微信菜单上哟！</p>\n        </div>\n        <div class=\"configur_content\" v-if=\"menu.type === 'article_view_limited'\">\n          <el-row>\n            <div class=\"select-item\" v-if=\"menu && menu.replyArticles\">\n              <WxNews :articles=\"menu.replyArticles\" />\n              <el-row class=\"ope-row\" justify=\"center\" align=\"middle\">\n                <el-button type=\"danger\" circle @click=\"deleteMaterial\">\n                  <icon icon=\"ep:delete\" />\n                </el-button>\n              </el-row>\n            </div>\n            <div v-else>\n              <el-row justify=\"center\">\n                <el-col :span=\"24\" style=\"text-align: center\">\n                  <el-button type=\"success\" @click=\"showNewsDialog = true\">\n                    素材库选择\n                    <Icon icon=\"ep:circle-check\" />\n                  </el-button>\n                </el-col>\n              </el-row>\n            </div>\n            <el-dialog title=\"选择图文\" v-model=\"showNewsDialog\" width=\"80%\" destroy-on-close>\n              <WxMaterialSelect\n                type=\"news\"\n                :account-id=\"props.accountId\"\n                @select-material=\"selectMaterial\"\n              />\n            </el-dialog>\n          </el-row>\n        </div>\n        <div\n          class=\"configur_content\"\n          v-if=\"menu.type === 'click' || menu.type === 'scancode_waitmsg'\"\n        >\n          <WxReplySelect v-if=\"hackResetWxReplySelect\" v-model=\"menu.reply\" />\n        </div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport WxReplySelect from '@/views/mp/components/wx-reply'\nimport WxNews from '@/views/mp/components/wx-news'\nimport WxMaterialSelect from '@/views/mp/components/wx-material-select'\nimport menuOptions from './menuOptions'\n\nconst message = useMessage()\n\nconst props = defineProps<{\n  accountId: number\n  modelValue: any\n  isParent: boolean\n}>()\n\nconst emit = defineEmits<{\n  (e: 'delete', v: void)\n  (e: 'update:modelValue', v: any)\n}>()\n\nconst menu = computed({\n  get() {\n    return props.modelValue\n  },\n  set(val) {\n    emit('update:modelValue', val)\n  }\n})\nconst showNewsDialog = ref(false)\nconst hackResetWxReplySelect = ref(false)\nconst isLeave = computed<boolean>(() => !(menu.value.children?.length > 0))\n\nwatch(menu, () => {\n  hackResetWxReplySelect.value = false // 销毁组件\n  nextTick(() => {\n    hackResetWxReplySelect.value = true // 重建组件\n  })\n})\n\n// ======================== 菜单编辑（素材选择） ========================\nconst selectMaterial = (item: any) => {\n  const articleId = item.articleId\n  const articles = item.content.newsItem\n  // 提示，针对多图文\n  if (articles.length > 1) {\n    message.alertWarning('您选择的是多图文，将默认跳转第一篇')\n  }\n  showNewsDialog.value = false\n\n  // 设置菜单的回复\n  menu.value.articleId = articleId\n  menu.value.replyArticles = []\n  articles.forEach((article) => {\n    menu.value.replyArticles.push({\n      title: article.title,\n      description: article.digest,\n      picUrl: article.picUrl,\n      url: article.url\n    })\n  })\n}\n\nconst deleteMaterial = () => {\n  delete menu.value['articleId']\n  delete menu.value['replyArticles']\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.el-input {\n  width: 70%;\n  margin-right: 2%;\n}\n\n.configure_page {\n  .delete_btn {\n    margin-bottom: 15px;\n    text-align: right;\n  }\n\n  .menu_content {\n    margin-top: 20px;\n  }\n\n  .configur_content {\n    padding: 20px 10px;\n    margin-top: 20px;\n    background-color: #fff;\n    border-radius: 5px;\n\n    .select-item {\n      width: 280px;\n      padding: 10px;\n      margin: 0 auto 10px;\n      border: 1px solid #eaeaea;\n\n      .ope-row {\n        padding-top: 10px;\n        text-align: center;\n      }\n    }\n  }\n\n  .blue {\n    margin-top: 10px;\n    color: #29b6f6;\n  }\n\n  .applet {\n    margin-bottom: 20px;\n\n    span {\n      width: 20%;\n    }\n  }\n\n  .input_width {\n    width: 40%;\n  }\n\n  .material {\n    .input_width {\n      width: 30%;\n    }\n\n    .el-textarea {\n      width: 80%;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/menu/components/MenuPreviewer.vue",
    "content": "<template>\n  <draggable\n    v-model=\"menuList\"\n    item-key=\"id\"\n    ghost-class=\"draggable-ghost\"\n    :animation=\"400\"\n    @end=\"onParentDragEnd\"\n  >\n    <template #item=\"{ element: parent, index: x }\">\n      <div class=\"menu_bottom\">\n        <!-- 一级菜单 -->\n        <div\n          @click=\"menuClicked(parent, x)\"\n          class=\"menu_item\"\n          :class=\"{ active: props.activeIndex === `${x}` }\"\n        >\n          <Icon icon=\"ep:fold\" color=\"black\" />{{ parent.name }}\n        </div>\n        <!-- 以下为二级菜单-->\n        <div class=\"submenu\" v-if=\"props.parentIndex === x && parent.children\">\n          <draggable\n            v-model=\"parent.children\"\n            item-key=\"id\"\n            ghost-class=\"draggable-ghost\"\n            :animation=\"400\"\n            @end=\"onChildDragEnd\"\n          >\n            <template #item=\"{ element: child, index: y }\">\n              <div class=\"menu_bottom subtitle\">\n                <div\n                  class=\"menu_subItem\"\n                  v-if=\"parent.children\"\n                  :class=\"{ active: props.activeIndex === `${x}-${y}` }\"\n                  @click=\"subMenuClicked(child, x, y)\"\n                >\n                  {{ child.name }}\n                </div>\n              </div>\n            </template>\n          </draggable>\n          <!-- 二级菜单加号， 当长度 小于 5 才显示二级菜单的加号  -->\n          <div\n            class=\"menu_bottom menu_addicon\"\n            v-if=\"!parent.children || parent.children.length < 5\"\n            @click=\"addSubMenu(x, parent)\"\n          >\n            <Icon icon=\"ep:plus\" class=\"plus\" />\n          </div>\n        </div>\n      </div>\n    </template>\n  </draggable>\n\n  <!-- 一级菜单加号 -->\n  <div class=\"menu_bottom menu_addicon\" v-if=\"menuList.length < 3\" @click=\"addMenu\">\n    <Icon icon=\"ep:plus\" class=\"plus\" />\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { Menu } from './types'\nimport draggable from 'vuedraggable'\n\nconst props = defineProps<{\n  modelValue: Menu[]\n  activeIndex: string\n  parentIndex: number\n  accountId: number\n}>()\n\nconst emit = defineEmits<{\n  (e: 'update:modelValue', v: Menu[])\n  (e: 'menu-clicked', parent: Menu, x: number)\n  (e: 'submenu-clicked', child: Menu, x: number, y: number)\n}>()\n\nconst menuList = computed<Menu[]>({\n  get: () => props.modelValue,\n  set: (val) => emit('update:modelValue', val)\n})\n\n// 添加横向一级菜单\nconst addMenu = () => {\n  const index = menuList.value.length\n  const menu = {\n    name: '菜单名称',\n    children: [],\n    reply: {\n      // 用于存储回复内容\n      type: 'text',\n      accountId: props.accountId // 保证组件里，可以使用到对应的公众号\n    }\n  }\n  menuList.value[index] = menu\n  menuClicked(menu, index - 1)\n}\n\n// 添加横向二级菜单；parent 表示要操作的父菜单\nconst addSubMenu = (i: number, parent: any) => {\n  const subMenuKeyLength = parent.children.length // 获取二级菜单key长度\n  const addButton = {\n    name: '子菜单名称',\n    reply: {\n      // 用于存储回复内容\n      type: 'text',\n      accountId: props.accountId // 保证组件里，可以使用到对应的公众号\n    }\n  }\n  parent.children[subMenuKeyLength] = addButton\n  subMenuClicked(parent.children[subMenuKeyLength], i, subMenuKeyLength)\n}\n\nconst menuClicked = (parent: Menu, x: number) => {\n  emit('menu-clicked', parent, x)\n}\n\nconst subMenuClicked = (child: Menu, x: number, y: number) => {\n  emit('submenu-clicked', child, x, y)\n}\n\n/**\n * 处理一级菜单展开后被拖动，激活(展开)原来活动的一级菜单\n *\n * @param oldIndex: 一级菜单拖动前的位置\n * @param newIndex: 一级菜单拖动后的位置\n */\nconst onParentDragEnd = ({ oldIndex, newIndex }) => {\n  // 二级菜单没有展开，直接返回\n  if (props.activeIndex === '__MENU_NOT_SELECTED__') {\n    return\n  }\n\n  // 使用一个辅助数组来模拟菜单移动，然后找到展开的二级菜单的新下标`newParent`\n  let positions = new Array<boolean>(menuList.value.length).fill(false)\n  positions[props.parentIndex] = true\n  const [out] = positions.splice(oldIndex, 1) // 移出菜单，保存到变量out\n  positions.splice(newIndex, 0, out) // 把out变量插入被移出的菜单\n  const newParentIndex = positions.indexOf(true)\n\n  // 找到菜单元素，触发一级菜单点击\n  const parent = menuList.value[newParentIndex]\n  emit('menu-clicked', parent, newParentIndex)\n}\n\n/**\n * 处理二级菜单展开后被拖动，激活被拖动的菜单\n *\n * @param newIndex 二级菜单拖动后的位置\n */\nconst onChildDragEnd = ({ newIndex }) => {\n  const x = props.parentIndex\n  const y = newIndex\n  const children = menuList.value[x]?.children\n  if (children && children?.length > 0) {\n    const child = children[y]\n    emit('submenu-clicked', child, x, y)\n  }\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.menu_bottom {\n  position: relative;\n  display: block;\n  float: left;\n  width: 85.5px;\n  text-align: center;\n  cursor: pointer;\n  background-color: #fff;\n  border: 1px solid #ebedee;\n  box-sizing: border-box;\n\n  &.menu_addicon {\n    height: 46px;\n    line-height: 46px;\n\n    .plus {\n      color: #2bb673;\n    }\n  }\n\n  .menu_item {\n    display: flex;\n    width: 100%;\n    height: 44px;\n    line-height: 44px;\n    // text-align: center;\n    box-sizing: border-box;\n    align-items: center;\n    justify-content: center;\n\n    &.active {\n      border: 1px solid #2bb673;\n    }\n  }\n\n  .menu_subItem {\n    height: 44px;\n    line-height: 44px;\n    text-align: center;\n    box-sizing: border-box;\n\n    &.active {\n      border: 1px solid #2bb673;\n    }\n  }\n}\n\n/* 第二级菜单 */\n.submenu {\n  position: absolute;\n  bottom: 45px;\n  width: 85.5px;\n\n  .subtitle {\n    background-color: #fff;\n    box-sizing: border-box;\n  }\n}\n\n.draggable-ghost {\n  background: #f7fafc;\n  border: 1px solid #4299e1;\n  opacity: 0.5;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/menu/components/menuOptions.ts",
    "content": "export default [\n  {\n    value: 'view',\n    label: '跳转网页'\n  },\n  {\n    value: 'miniprogram',\n    label: '跳转小程序'\n  },\n  {\n    value: 'click',\n    label: '点击回复'\n  },\n  {\n    value: 'article_view_limited',\n    label: '跳转图文消息'\n  },\n  {\n    value: 'scancode_push',\n    label: '扫码直接返回结果'\n  },\n  {\n    value: 'scancode_waitmsg',\n    label: '扫码回复'\n  },\n  {\n    value: 'pic_sysphoto',\n    label: '系统拍照发图'\n  },\n  {\n    value: 'pic_photo_or_album',\n    label: '拍照或者相册'\n  },\n  {\n    value: 'pic_weixin',\n    label: '微信相册'\n  },\n  {\n    value: 'location_select',\n    label: '选择地理位置'\n  }\n]\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/menu/components/types.ts",
    "content": "export interface Replay {\n  title: string\n  description: string\n  picUrl: string\n  url: string\n}\n\nexport type MenuType =\n  | ''\n  | 'click'\n  | 'view'\n  | 'scancode_waitmsg'\n  | 'scancode_push'\n  | 'pic_sysphoto'\n  | 'pic_photo_or_album'\n  | 'pic_weixin'\n  | 'location_select'\n  | 'article_view_limited'\n\ninterface _RawMenu {\n  // db\n  id: number\n  parentId: number\n  accountId: number\n  appId: string\n  createTime: number\n\n  // mp-native\n  name: string\n  menuKey: string\n  type: MenuType\n  url: string\n  miniProgramAppId: string\n  miniProgramPagePath: string\n  articleId: string\n  replyMessageType: string\n  replyContent: string\n  replyMediaId: string\n  replyMediaUrl: string\n  replyThumbMediaId: string\n  replyThumbMediaUrl: string\n  replyTitle: string\n  replyDescription: string\n  replyArticles: Replay\n  replyMusicUrl: string\n  replyHqMusicUrl: string\n}\n\nexport type RawMenu = Partial<_RawMenu>\n\ninterface _Reply {\n  type: string\n  accountId: number\n  content: string\n  mediaId: string\n  url: string\n  thumbMediaId: string\n  thumbMediaUrl: string\n  title: string\n  description: string\n  articles: null | Replay[]\n  musicUrl: string\n  hqMusicUrl: string\n}\n\nexport type Reply = Partial<_Reply>\n\ninterface _Menu extends RawMenu {\n  children: _Menu[]\n  reply: Reply\n}\n\nexport type Menu = Partial<_Menu>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/menu/index.vue",
    "content": "<template>\n  <!-- 搜索工作栏 -->\n  <ContentWrap>\n    <el-form class=\"-mb-15px\" ref=\"queryFormRef\" :inline=\"true\" label-width=\"68px\">\n      <el-form-item label=\"公众号\" prop=\"accountId\">\n        <WxAccountSelect @change=\"onAccountChanged\" />\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <ContentWrap>\n    <div class=\"clearfix public-account-management\" v-loading=\"loading\">\n      <!--左边配置菜单-->\n      <div class=\"left\">\n        <div class=\"weixin-hd\">\n          <div class=\"weixin-title\">{{ accountName }}</div>\n        </div>\n        <div class=\"clearfix weixin-menu\">\n          <MenuPreviewer\n            v-model=\"menuList\"\n            :account-id=\"accountId\"\n            :active-index=\"activeIndex\"\n            :parent-index=\"parentIndex\"\n            @menu-clicked=\"(parent, x) => menuClicked(parent, x)\"\n            @submenu-clicked=\"(child, x, y) => subMenuClicked(child, x, y)\"\n          />\n        </div>\n        <div class=\"save_div\">\n          <el-button class=\"save_btn\" type=\"success\" @click=\"onSave\" v-hasPermi=\"['mp:menu:save']\"\n            >保存并发布菜单</el-button\n          >\n          <el-button class=\"save_btn\" type=\"danger\" @click=\"onClear\" v-hasPermi=\"['mp:menu:delete']\"\n            >清空菜单</el-button\n          >\n        </div>\n      </div>\n      <!--右边配置-->\n      <div class=\"right\" v-if=\"showRightPanel\">\n        <MenuEditor\n          :account-id=\"accountId\"\n          :is-parent=\"isParent\"\n          v-model=\"activeMenu\"\n          @delete=\"onDeleteMenu\"\n        />\n      </div>\n      <!-- 一进页面就显示的默认页面，当点击左边按钮的时候，就不显示了-->\n      <div v-else class=\"right\">\n        <p>请选择菜单配置</p>\n      </div>\n    </div>\n  </ContentWrap>\n</template>\n\n<script lang=\"ts\" setup>\nimport WxAccountSelect from '@/views/mp/components/wx-account-select'\nimport MenuEditor from './components/MenuEditor.vue'\nimport MenuPreviewer from './components/MenuPreviewer.vue'\nimport * as MpMenuApi from '@/api/mp/menu'\nimport * as UtilsTree from '@/utils/tree'\nimport { RawMenu, Menu } from './components/types'\n\ndefineOptions({ name: 'MpMenu' })\n\nconst message = useMessage() // 消息\nconst MENU_NOT_SELECTED = '__MENU_NOT_SELECTED__'\n\n// ======================== 列表查询 ========================\nconst loading = ref(false) // 遮罩层\nconst accountId = ref(-1)\nconst accountName = ref<string>('')\nconst menuList = ref<Menu[]>([])\n\n// ======================== 菜单操作 ========================\n// 当前选中菜单编码：\n//  * 一级（'x'）\n//  * 二级（'x-y'）\n//  * 未选中（MENU_NOT_SELECTED）\nconst activeIndex = ref<string>(MENU_NOT_SELECTED)\n// 二级菜单显示标志: 归属的一级菜单index\n// * 未初始化：-1\n// * 初始化：x\nconst parentIndex = ref(-1)\n\n// ======================== 菜单编辑 ========================\nconst showRightPanel = ref(false) // 右边配置显示默认详情还是配置详情\nconst isParent = ref<boolean>(true) // 是否一级菜单，控制MenuEditor中name字段长度\nconst activeMenu = ref<Menu>({}) // 选中菜单，MenuEditor的modelValue\n\n// 一些临时值放在这里进行判断，如果放在 activeMenu，由于引用关系，menu 也会多了多余的参数\nenum Level {\n  Undefined = '0',\n  Parent = '1',\n  Child = '2'\n}\nconst tempSelfObj = ref<{\n  grand: Level\n  x: number\n  y: number\n}>({\n  grand: Level.Undefined,\n  x: 0,\n  y: 0\n})\nconst dialogNewsVisible = ref(false) // 跳转图文时的素材选择弹窗\n\n/** 侦听公众号变化 **/\nconst onAccountChanged = (id: number, name: string) => {\n  accountId.value = id\n  accountName.value = name\n  getList()\n}\n\n/** 查询并转换菜单 **/\nconst getList = async () => {\n  loading.value = false\n  try {\n    const data = await MpMenuApi.getMenuList(accountId.value)\n    const menuData = menuListToFrontend(data)\n    menuList.value = UtilsTree.handleTree(menuData, 'id')\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  resetForm()\n  getList()\n}\n\n// 将后端返回的 menuList，转换成前端的 menuList\nconst menuListToFrontend = (list: any[]) => {\n  if (!list) return []\n\n  const result: RawMenu[] = []\n  list.forEach((item: RawMenu) => {\n    const menu: any = {\n      ...item\n    }\n    menu.reply = {\n      type: item.replyMessageType,\n      accountId: item.accountId,\n      content: item.replyContent,\n      mediaId: item.replyMediaId,\n      url: item.replyMediaUrl,\n      title: item.replyTitle,\n      description: item.replyDescription,\n      thumbMediaId: item.replyThumbMediaId,\n      thumbMediaUrl: item.replyThumbMediaUrl,\n      articles: item.replyArticles,\n      musicUrl: item.replyMusicUrl,\n      hqMusicUrl: item.replyHqMusicUrl\n    }\n    result.push(menu as RawMenu)\n  })\n  return result\n}\n\n// 重置表单，清空表单数据\nconst resetForm = () => {\n  // 菜单操作\n  activeIndex.value = MENU_NOT_SELECTED\n  parentIndex.value = -1\n\n  // 菜单编辑\n  showRightPanel.value = false\n  activeMenu.value = {}\n  tempSelfObj.value = { grand: Level.Undefined, x: 0, y: 0 }\n  dialogNewsVisible.value = false\n}\n\n// ======================== 菜单操作 ========================\n// 一级菜单点击事件\nconst menuClicked = (parent: Menu, x: number) => {\n  // 右侧的表单相关\n  showRightPanel.value = true // 右边菜单\n  activeMenu.value = parent // 这个如果放在顶部，flag 会没有。因为重新赋值了。\n  tempSelfObj.value.grand = Level.Parent // 表示一级菜单\n  tempSelfObj.value.x = x // 表示一级菜单索引\n  isParent.value = true\n\n  // 左侧的选中\n  activeIndex.value = `${x}` // 菜单选中样式\n  parentIndex.value = x // 二级菜单显示标志\n}\n\n// 二级菜单点击事件\nconst subMenuClicked = (child: Menu, x: number, y: number) => {\n  // 右侧的表单相关\n  showRightPanel.value = true // 右边菜单\n  activeMenu.value = child // 将点击的数据放到临时变量，对象有引用作用\n  tempSelfObj.value.grand = Level.Child // 表示二级菜单\n  tempSelfObj.value.x = x // 表示一级菜单索引\n  tempSelfObj.value.y = y // 表示二级菜单索引\n  isParent.value = false\n\n  // 左侧的选中\n  activeIndex.value = `${x}-${y}`\n}\n\n// 删除当前菜单\nconst onDeleteMenu = async () => {\n  try {\n    await message.confirm('确定要删除吗?')\n    if (tempSelfObj.value.grand === Level.Parent) {\n      // 一级菜单的删除方法\n      menuList.value.splice(tempSelfObj.value.x, 1)\n    } else if (tempSelfObj.value.grand === Level.Child) {\n      // 二级菜单的删除方法\n      menuList.value[tempSelfObj.value.x].children?.splice(tempSelfObj.value.y, 1)\n    }\n    // 提示\n    message.notifySuccess('删除成功')\n\n    // 处理菜单的选中\n    activeMenu.value = {}\n    showRightPanel.value = false\n    activeIndex.value = MENU_NOT_SELECTED\n  } catch {}\n}\n\n// ======================== 菜单编辑 ========================\nconst onSave = async () => {\n  try {\n    await message.confirm('确定要保存吗?')\n    loading.value = true\n    await MpMenuApi.saveMenu(accountId.value, menuListToBackend())\n    getList()\n    message.notifySuccess('发布成功')\n  } finally {\n    loading.value = false\n  }\n}\n\nconst onClear = async () => {\n  try {\n    await message.confirm('确定要删除吗?')\n    loading.value = true\n    await MpMenuApi.deleteMenu(accountId.value)\n    handleQuery()\n    message.notifySuccess('清空成功')\n  } finally {\n    loading.value = false\n  }\n}\n\n// 将前端的 menuList，转换成后端接收的 menuList\nconst menuListToBackend = () => {\n  const result: any[] = []\n  menuList.value.forEach((item) => {\n    const menu = menuToBackend(item)\n    result.push(menu)\n\n    // 处理子菜单\n    if (!item.children || item.children.length <= 0) {\n      return\n    }\n    menu.children = []\n    item.children.forEach((subItem) => {\n      menu.children.push(menuToBackend(subItem))\n    })\n  })\n  return result\n}\n\n// 将前端的 menu，转换成后端接收的 menu\n// TODO: @芋艿，需要根据后台API删除不需要的字段\nconst menuToBackend = (menu: any) => {\n  let result = {\n    ...menu,\n    children: undefined, // 不处理子节点\n    reply: undefined // 稍后复制\n  }\n  result.replyMessageType = menu.reply.type\n  result.replyContent = menu.reply.content\n  result.replyMediaId = menu.reply.mediaId\n  result.replyMediaUrl = menu.reply.url\n  result.replyTitle = menu.reply.title\n  result.replyDescription = menu.reply.description\n  result.replyThumbMediaId = menu.reply.thumbMediaId\n  result.replyThumbMediaUrl = menu.reply.thumbMediaUrl\n  result.replyArticles = menu.reply.articles\n  result.replyMusicUrl = menu.reply.musicUrl\n  result.replyHqMusicUrl = menu.reply.hqMusicUrl\n\n  return result\n}\n</script>\n\n<!--本组件样式-->\n<style lang=\"scss\" scoped=\"scoped\">\n/* 公共颜色变量 */\n.clearfix {\n  *zoom: 1;\n}\n\n.clearfix::after {\n  display: table;\n  clear: both;\n  content: '';\n}\n\ndiv {\n  text-align: left;\n}\n\n.weixin-hd {\n  position: relative;\n  bottom: 426px;\n  left: 0;\n  width: 300px;\n  height: 64px;\n  color: #fff;\n  text-align: center;\n  background: transparent url('./assets/menu_head.png') no-repeat 0 0;\n  background-position: 0 0;\n  background-size: 100%;\n}\n\n.weixin-title {\n  position: absolute;\n  top: 33px;\n  left: 0;\n  width: 100%;\n  font-size: 14px;\n  color: #fff;\n  text-align: center;\n}\n\n.weixin-menu {\n  padding-left: 43px;\n  font-size: 12px;\n  background: transparent url('./assets/menu_foot.png') no-repeat 0 0;\n}\n\n.public-account-management {\n  width: 1200px;\n  // min-width: 1200px;\n  margin: 0 auto;\n\n  .left {\n    position: relative;\n    display: block;\n    float: left;\n    width: 350px;\n    height: 715px;\n    padding: 518px 25px 88px;\n    background: url('./assets/iphone_backImg.png') no-repeat;\n    background-size: 100% auto;\n    box-sizing: border-box;\n\n    .save_div {\n      margin-top: 15px;\n      text-align: center;\n\n      .save_btn {\n        bottom: 20px;\n        left: 100px;\n      }\n    }\n  }\n\n  /* 右边菜单内容 */\n  .right {\n    float: left;\n    width: 63%;\n    padding: 20px;\n    margin-left: 20px;\n    background-color: #e8e7e7;\n    box-sizing: border-box;\n  }\n}\n</style>\n<!--素材样式-->\n<style lang=\"scss\" scoped>\n.pagination {\n  margin-right: 25px;\n  text-align: right;\n}\n\n.select-item {\n  width: 280px;\n  padding: 10px;\n  margin: 0 auto 10px;\n  border: 1px solid #eaeaea;\n}\n\n.ope-row {\n  padding-top: 10px;\n  text-align: center;\n}\n\n.item-name {\n  overflow: hidden;\n  font-size: 12px;\n  text-align: center;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/message/MessageTable.vue",
    "content": "<template>\n  <div>\n    <el-table v-loading=\"props.loading\" :data=\"props.list\">\n      <el-table-column\n        label=\"发送时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        width=\"180\"\n        :formatter=\"dateFormatter\"\n      />\n      <el-table-column label=\"消息类型\" align=\"center\" prop=\"type\" width=\"80\" />\n      <el-table-column label=\"发送方\" align=\"center\" prop=\"sendFrom\" width=\"80\">\n        <template #default=\"scope\">\n          <el-tag v-if=\"scope.row.sendFrom === 1\" type=\"success\">粉丝</el-tag>\n          <el-tag v-else type=\"info\">公众号</el-tag>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"用户标识\" align=\"center\" prop=\"openid\" width=\"300\" />\n      <el-table-column label=\"内容\" prop=\"content\">\n        <template #default=\"scope\">\n          <!-- 【事件】区域 -->\n          <div v-if=\"scope.row.type === MsgType.Event && scope.row.event === 'subscribe'\">\n            <el-tag type=\"success\">关注</el-tag>\n          </div>\n          <div v-else-if=\"scope.row.type === MsgType.Event && scope.row.event === 'unsubscribe'\">\n            <el-tag type=\"danger\">取消关注</el-tag>\n          </div>\n          <div v-else-if=\"scope.row.type === MsgType.Event && scope.row.event === 'CLICK'\">\n            <el-tag>点击菜单</el-tag>\n            【{{ scope.row.eventKey }}】\n          </div>\n          <div v-else-if=\"scope.row.type === MsgType.Event && scope.row.event === 'VIEW'\">\n            <el-tag>点击菜单链接</el-tag>\n            【{{ scope.row.eventKey }}】\n          </div>\n          <div\n            v-else-if=\"scope.row.type === MsgType.Event && scope.row.event === 'scancode_waitmsg'\"\n          >\n            <el-tag>扫码结果</el-tag>\n            【{{ scope.row.eventKey }}】\n          </div>\n          <div v-else-if=\"scope.row.type === MsgType.Event && scope.row.event === 'scancode_push'\">\n            <el-tag>扫码结果</el-tag>\n            【{{ scope.row.eventKey }}】\n          </div>\n          <div v-else-if=\"scope.row.type === MsgType.Event && scope.row.event === 'pic_sysphoto'\">\n            <el-tag>系统拍照发图</el-tag>\n          </div>\n          <div\n            v-else-if=\"scope.row.type === MsgType.Event && scope.row.event === 'pic_photo_or_album'\"\n          >\n            <el-tag>拍照或者相册</el-tag>\n          </div>\n          <div v-else-if=\"scope.row.type === MsgType.Event && scope.row.event === 'pic_weixin'\">\n            <el-tag>微信相册</el-tag>\n          </div>\n          <div\n            v-else-if=\"scope.row.type === MsgType.Event && scope.row.event === 'location_select'\"\n          >\n            <el-tag>选择地理位置</el-tag>\n          </div>\n          <div v-else-if=\"scope.row.type === MsgType.Event\">\n            <el-tag type=\"danger\">未知事件类型</el-tag>\n          </div>\n          <!-- 【消息】区域 -->\n          <div v-else-if=\"scope.row.type === MsgType.Text\">{{ scope.row.content }}</div>\n          <div v-else-if=\"scope.row.type === MsgType.Voice\">\n            <wx-voice-player :url=\"scope.row.mediaUrl\" :content=\"scope.row.recognition\" />\n          </div>\n          <div v-else-if=\"scope.row.type === MsgType.Image\">\n            <a target=\"_blank\" :href=\"scope.row.mediaUrl\">\n              <img :src=\"scope.row.mediaUrl\" style=\"width: 100px\" />\n            </a>\n          </div>\n          <div v-else-if=\"scope.row.type === MsgType.Video || scope.row.type === 'shortvideo'\">\n            <wx-video-player :url=\"scope.row.mediaUrl\" style=\"margin-top: 10px\" />\n          </div>\n          <div v-else-if=\"scope.row.type === MsgType.Link\">\n            <el-tag>链接</el-tag>\n            ：\n            <a :href=\"scope.row.url\" target=\"_blank\">{{ scope.row.title }}</a>\n          </div>\n          <div v-else-if=\"scope.row.type === MsgType.Location\">\n            <WxLocation\n              :label=\"scope.row.label\"\n              :location-y=\"scope.row.locationY\"\n              :location-x=\"scope.row.locationX\"\n            />\n          </div>\n          <div v-else-if=\"scope.row.type === MsgType.Music\">\n            <WxMusic\n              :title=\"scope.row.title\"\n              :description=\"scope.row.description\"\n              :thumb-media-url=\"scope.row.thumbMediaUrl\"\n              :music-url=\"scope.row.musicUrl\"\n              :hq-music-url=\"scope.row.hqMusicUrl\"\n            />\n          </div>\n          <div v-else-if=\"scope.row.type === MsgType.News\">\n            <WxNews :articles=\"scope.row.articles\" />\n          </div>\n          <div v-else>\n            <el-tag type=\"danger\">未知消息类型</el-tag>\n          </div>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"操作\" align=\"center\" class-name=\"small-padding fixed-width\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"emit('send', scope.row.userId)\"\n            v-hasPermi=\"['mp:message:send']\"\n          >\n            消息\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页组件 -->\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport WxVideoPlayer from '@/views/mp/components/wx-video-play'\nimport WxVoicePlayer from '@/views/mp/components/wx-voice-play'\nimport WxLocation from '@/views/mp/components/wx-location'\nimport WxMusic from '@/views/mp/components/wx-music'\nimport WxNews from '@/views/mp/components/wx-news'\nimport { dateFormatter } from '@/utils/formatTime'\nimport { MsgType } from '@/views/mp/components/wx-msg/types'\n\nconst props = defineProps({\n  list: {\n    type: Array,\n    required: true\n  },\n  loading: {\n    type: Boolean,\n    required: true\n  }\n})\n\nconst emit = defineEmits<{ (e: 'send', v: number) }>()\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/message/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <!-- 搜索工作栏 -->\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"公众号\" prop=\"accountId\">\n        <WxAccountSelect @change=\"onAccountChanged\" />\n      </el-form-item>\n      <el-form-item label=\"消息类型\" prop=\"type\">\n        <el-select v-model=\"queryParams.type\" placeholder=\"请选择消息类型\" class=\"!w-240px\">\n          <el-option\n            v-for=\"dict in getStrDictOptions(DICT_TYPE.MP_MESSAGE_TYPE)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"用户标识\" prop=\"openid\">\n        <el-input\n          v-model=\"queryParams.openid\"\n          placeholder=\"请输入用户标识\"\n          clearable\n          :v-on=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"创建时间\" prop=\"createTime\">\n        <el-date-picker\n          v-model=\"queryParams.createTime\"\n          style=\"width: 240px\"\n          value-format=\"YYYY-MM-DD HH:mm:ss\"\n          type=\"daterange\"\n          range-separator=\"-\"\n          start-placeholder=\"开始日期\"\n          end-placeholder=\"结束日期\"\n          :default-time=\"['00:00:00', '23:59:59']\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\">\n          <Icon icon=\"ep:search\" class=\"mr-5px\" />\n          搜索\n        </el-button>\n        <el-button @click=\"resetQuery\">\n          <Icon icon=\"ep:refresh\" class=\"mr-5px\" />\n          重置\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <MessageTable :list=\"list\" :loading=\"loading\" @send=\"handleSend\" />\n    <Pagination\n      v-show=\"total > 0\"\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 发送消息的弹窗 -->\n  <el-dialog\n    title=\"粉丝消息列表\"\n    v-model=\"messageBox.show\"\n    @click=\"messageBox.show = true\"\n    width=\"50%\"\n    destroy-on-close\n  >\n    <WxMsg :user-id=\"messageBox.userId\" />\n  </el-dialog>\n</template>\n<script lang=\"ts\" setup>\nimport * as MpMessageApi from '@/api/mp/message'\nimport WxMsg from '@/views/mp/components/wx-msg'\nimport WxAccountSelect from '@/views/mp/components/wx-account-select'\nimport MessageTable from './MessageTable.vue'\nimport { DICT_TYPE, getStrDictOptions } from '@/utils/dict'\nimport { MsgType } from '@/views/mp/components/wx-msg/types'\nimport type { FormInstance } from 'element-plus'\n\ndefineOptions({ name: 'MpMessage' })\n\nconst loading = ref(false)\nconst total = ref(0) // 数据的总页数\nconst list = ref<any[]>([]) // 当前页的列表数据\n\n// 搜索参数\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  openid: '',\n  accountId: -1,\n  type: MsgType.Text,\n  createTime: []\n})\nconst queryFormRef = ref<FormInstance | null>(null) // 搜索的表单\n\n// 消息对话框\nconst messageBox = reactive({\n  show: false,\n  userId: 0\n})\n\n/** 侦听accountId */\nconst onAccountChanged = (id: number) => {\n  queryParams.accountId = id\n  queryParams.pageNo = 1\n  handleQuery()\n}\n\n/** 查询列表 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\nconst getList = async () => {\n  try {\n    loading.value = true\n    const data = await MpMessageApi.getMessagePage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 重置按钮操作 */\nconst resetQuery = async () => {\n  // 暂存 accountId，并在 reset 后恢复\n  const accountId = queryParams.accountId\n  queryFormRef.value?.resetFields()\n  queryParams.accountId = accountId\n  handleQuery()\n}\n\n/** 打开消息发送窗口 */\nconst handleSend = async (userId: number) => {\n  messageBox.userId = userId\n  messageBox.show = true\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/statistics/index.vue",
    "content": "<template>\n  <!-- 搜索工作栏 -->\n  <ContentWrap>\n    <el-form class=\"-mb-15px\" ref=\"queryForm\" :inline=\"true\" label-width=\"68px\">\n      <el-form-item label=\"公众号\" prop=\"accountId\">\n        <el-select v-model=\"accountId\" @change=\"getSummary\" class=\"!w-240px\">\n          <el-option\n            v-for=\"item in accountList\"\n            :key=\"item.id\"\n            :label=\"item.name\"\n            :value=\"item.id\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"时间范围\" prop=\"dateRange\">\n        <el-date-picker\n          v-model=\"dateRange\"\n          type=\"daterange\"\n          start-placeholder=\"开始日期\"\n          end-placeholder=\"结束日期\"\n          :default-time=\"[new Date('1 00:00:00'), new Date('1 23:59:59')]\"\n          @change=\"getSummary\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 图表 -->\n  <ContentWrap>\n    <el-row>\n      <el-col :span=\"12\" class=\"card-box\">\n        <el-card>\n          <template #header>\n            <div>\n              <span>用户增减数据</span>\n            </div>\n          </template>\n          <Echart :options=\"userSummaryOption\" :height=\"420\" />\n        </el-card>\n      </el-col>\n      <el-col :span=\"12\" class=\"card-box\">\n        <el-card>\n          <template #header>\n            <div>\n              <span>累计用户数据</span>\n            </div>\n          </template>\n          <Echart :options=\"userCumulateOption\" :height=\"420\" />\n        </el-card>\n      </el-col>\n      <el-col :span=\"12\" class=\"card-box\">\n        <el-card>\n          <template #header>\n            <div>\n              <span>消息概况数据</span>\n            </div>\n          </template>\n          <Echart :options=\"upstreamMessageOption\" :height=\"420\" />\n        </el-card>\n      </el-col>\n      <el-col :span=\"12\" class=\"card-box\">\n        <el-card>\n          <template #header>\n            <div>\n              <span>接口分析数据</span>\n            </div>\n          </template>\n          <Echart :options=\"interfaceSummaryOption\" :height=\"420\" />\n        </el-card>\n      </el-col>\n    </el-row>\n  </ContentWrap>\n</template>\n\n<script lang=\"ts\" setup>\nimport { formatDate, addTime, betweenDay, beginOfDay, endOfDay } from '@/utils/formatTime'\nimport * as StatisticsApi from '@/api/mp/statistics'\nimport * as MpAccountApi from '@/api/mp/account'\n\ndefineOptions({ name: 'MpStatistics' })\n\nconst message = useMessage() // 消息弹窗\n\n// 默认开始时间是当前日期-7，结束时间是当前日期-1\nconst dateRange = ref([\n  beginOfDay(new Date(new Date().getTime() - 3600 * 1000 * 24 * 7)),\n  endOfDay(new Date(new Date().getTime() - 3600 * 1000 * 24))\n])\nconst accountId = ref(-1) // 选中的公众号编号\nconst accountList = ref<MpAccountApi.AccountVO[]>([]) // 公众号账号列表\n\nconst xAxisDate = ref([] as any[]) // X 轴的日期范围\n// 用户增减数据图表配置项\nconst userSummaryOption = reactive({\n  color: ['#67C23A', '#E5323E'],\n  legend: {\n    data: ['新增用户', '取消关注的用户']\n  },\n  tooltip: {},\n  xAxis: {\n    data: [] as any[] // X 轴的日期范围\n  },\n  yAxis: {\n    minInterval: 1\n  },\n  series: [\n    {\n      name: '新增用户',\n      type: 'bar',\n      label: {\n        show: true\n      },\n      barGap: 0,\n      data: [] as any[] // 新增用户的数据\n    },\n    {\n      name: '取消关注的用户',\n      type: 'bar',\n      label: {\n        show: true\n      },\n      data: [] as any[] // 取消关注的用户的数据\n    }\n  ]\n})\n// 累计用户数据图表配置项\nconst userCumulateOption = reactive({\n  legend: {\n    data: ['累计用户量']\n  },\n  xAxis: {\n    type: 'category',\n    data: [] as any[]\n  },\n  yAxis: {\n    minInterval: 1\n  },\n  series: [\n    {\n      name: '累计用户量',\n      data: [] as any[], // 累计用户量的数据\n      type: 'line',\n      smooth: true,\n      label: {\n        show: true\n      }\n    }\n  ]\n})\n// 消息发送概况数据图表配置项\nconst upstreamMessageOption = reactive({\n  color: ['#67C23A', '#E5323E'],\n  legend: {\n    data: ['用户发送人数', '用户发送条数']\n  },\n  tooltip: {},\n  xAxis: {\n    data: [] as any[] // X 轴的日期范围\n  },\n  yAxis: {\n    minInterval: 1\n  },\n  series: [\n    {\n      name: '用户发送人数',\n      type: 'line',\n      smooth: true,\n      label: {\n        show: true\n      },\n      data: [] as any[] // 用户发送人数的数据\n    },\n    {\n      name: '用户发送条数',\n      type: 'line',\n      smooth: true,\n      label: {\n        show: true\n      },\n      data: [] as any[] // 用户发送条数的数据\n    }\n  ]\n})\n// 接口分析况数据图表配置项\nconst interfaceSummaryOption = reactive({\n  color: ['#67C23A', '#E5323E', '#E6A23C', '#409EFF'],\n  legend: {\n    data: ['被动回复用户消息的次数', '失败次数', '最大耗时', '总耗时']\n  },\n  tooltip: {},\n  xAxis: {\n    data: [] as any[] // X 轴的日期范围\n  },\n  yAxis: {},\n  series: [\n    {\n      name: '被动回复用户消息的次数',\n      type: 'bar',\n      label: {\n        show: true\n      },\n      barGap: 0,\n      data: [] as any[] // 被动回复用户消息的次数的数据\n    },\n    {\n      name: '失败次数',\n      type: 'bar',\n      label: {\n        show: true\n      },\n      data: [] as any[] // 失败次数的数据\n    },\n    {\n      name: '最大耗时',\n      type: 'bar',\n      label: {\n        show: true\n      },\n      data: [] as any[] // 最大耗时的数据\n    },\n    {\n      name: '总耗时',\n      type: 'bar',\n      label: {\n        show: true\n      },\n      data: [] as any[] // 总耗时的数据\n    }\n  ]\n})\n\n/** 加载公众号账号的列表 */\nconst getAccountList = async () => {\n  accountList.value = await MpAccountApi.getSimpleAccountList()\n  // 默认选中第一个\n  if (accountList.value.length > 0) {\n    accountId.value = accountList.value[0].id!\n  }\n}\n\n/** 加载数据 */\nconst getSummary = () => {\n  // 如果没有选中公众号账号，则进行提示。\n  if (!accountId) {\n    message.error('未选中公众号，无法统计数据')\n    return false\n  }\n  // 必须选择 7 天内，因为公众号有时间跨度限制为 7\n  if (betweenDay(dateRange.value[0], dateRange.value[1]) >= 7) {\n    message.error('时间间隔 7 天以内，请重新选择')\n    return false\n  }\n  // 清空横坐标日期\n  xAxisDate.value = []\n  // 横坐标加载日期数据\n  const days = betweenDay(dateRange.value[0], dateRange.value[1]) // 相差天数\n  for (let i = 0; i <= days; i++) {\n    xAxisDate.value.push(\n      formatDate(addTime(dateRange.value[0], 3600 * 1000 * 24 * i), 'YYYY-MM-DD')\n    )\n  }\n  // 初始化图表\n  initUserSummaryChart()\n  initUserCumulateChart()\n  initUpstreamMessageChart()\n  interfaceSummaryChart()\n}\n\n/** 用户增减数据 */\nconst initUserSummaryChart = async () => {\n  userSummaryOption.xAxis.data = []\n  userSummaryOption.series[0].data = []\n  userSummaryOption.series[1].data = []\n  try {\n    // 用户增减数据\n    const data = await StatisticsApi.getUserSummary({\n      accountId: accountId.value,\n      date: [formatDate(dateRange.value[0]), formatDate(dateRange.value[1])]\n    })\n    // 横坐标\n    userSummaryOption.xAxis.data = xAxisDate.value\n    // 处理数据\n    xAxisDate.value.forEach((date, index) => {\n      data.forEach((item) => {\n        // 匹配日期\n        const refDate = formatDate(new Date(item.refDate), 'YYYY-MM-DD')\n        if (refDate.indexOf(date) === -1) {\n          return\n        }\n        // 设置数据到对应的位置\n        userSummaryOption.series[0].data[index] = item.newUser\n        userSummaryOption.series[1].data[index] = item.cancelUser\n      })\n    })\n  } catch {}\n}\n\n/** 累计用户数据 */\nconst initUserCumulateChart = async () => {\n  userCumulateOption.xAxis.data = []\n  userCumulateOption.series[0].data = []\n  // 发起请求\n  try {\n    const data = await StatisticsApi.getUserCumulate({\n      accountId: accountId.value,\n      date: [formatDate(dateRange.value[0]), formatDate(dateRange.value[1])]\n    })\n    userCumulateOption.xAxis.data = xAxisDate.value\n    // 处理数据\n    data.forEach((item, index) => {\n      userCumulateOption.series[0].data[index] = item.cumulateUser\n    })\n  } catch {}\n}\n\n/** 消息概况数据 */\nconst initUpstreamMessageChart = async () => {\n  upstreamMessageOption.xAxis.data = []\n  upstreamMessageOption.series[0].data = []\n  upstreamMessageOption.series[1].data = []\n  // 发起请求\n  try {\n    const data = await StatisticsApi.getUpstreamMessage({\n      accountId: accountId.value,\n      date: [formatDate(dateRange.value[0]), formatDate(dateRange.value[1])]\n    })\n    upstreamMessageOption.xAxis.data = xAxisDate.value\n    // 处理数据\n    data.forEach((item, index) => {\n      upstreamMessageOption.series[0].data[index] = item.messageUser\n      upstreamMessageOption.series[1].data[index] = item.messageCount\n    })\n  } catch {}\n}\n\n/** 接口分析数据 */\nconst interfaceSummaryChart = async () => {\n  interfaceSummaryOption.xAxis.data = []\n  interfaceSummaryOption.series[0].data = []\n  interfaceSummaryOption.series[1].data = []\n  interfaceSummaryOption.series[2].data = []\n  interfaceSummaryOption.series[3].data = []\n  // 发起请求\n  try {\n    const data = await StatisticsApi.getInterfaceSummary({\n      accountId: accountId.value,\n      date: [formatDate(dateRange.value[0]), formatDate(dateRange.value[1])]\n    })\n    interfaceSummaryOption.xAxis.data = xAxisDate.value\n    // 处理数据\n    data.forEach((item, index) => {\n      interfaceSummaryOption.series[0].data[index] = item.callbackCount\n      interfaceSummaryOption.series[1].data[index] = item.failCount\n      interfaceSummaryOption.series[2].data[index] = item.maxTimeCost\n      interfaceSummaryOption.series[3].data[index] = item.totalTimeCost\n    })\n  } catch {}\n}\n\n/** 初始化 */\nonMounted(async () => {\n  // 获取公众号下拉列表\n  await getAccountList()\n  // 加载数据\n  getSummary()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/tag/TagForm.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" :title=\"dialogTitle\">\n    <el-form\n      ref=\"formRef\"\n      v-loading=\"formLoading\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"80px\"\n    >\n      <el-form-item label=\"标签名称\" prop=\"name\">\n        <el-input v-model=\"formData.name\" placeholder=\"请输入标签名称\" />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button :disabled=\"formLoading\" type=\"primary\" @click=\"submitForm\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport * as MpTagApi from '@/api/mp/tag'\nimport type { FormInstance, FormRules } from 'element-plus'\n\ndefineOptions({ name: 'MpTagForm' })\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref<'create' | 'update' | ''>('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  accountId: -1,\n  name: ''\n})\nconst formRules: FormRules = {\n  name: [{ required: true, message: '请输入标签名称', trigger: 'blur' }]\n}\nconst formRef = ref<FormInstance | null>(null) // 表单 Ref\n\nconst emit = defineEmits<{\n  (e: 'success'): void\n}>()\n\n/** 打开弹窗 */\nconst open = async (type: 'create' | 'update', accountId: number, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  formData.value.accountId = accountId\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await MpTagApi.getTag(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value?.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as MpTagApi.TagVO\n    if (formType.value === 'create') {\n      await MpTagApi.createTag(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await MpTagApi.updateTag(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    accountId: -1,\n    name: ''\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/tag/index.vue",
    "content": "<template>\n  <!-- 搜索工作栏 -->\n  <ContentWrap>\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"公众号\" prop=\"accountId\">\n        <WxAccountSelect @change=\"onAccountChanged\" />\n      </el-form-item>\n      <el-form-item>\n        <el-button\n          type=\"primary\"\n          plain\n          @click=\"openForm('create')\"\n          v-hasPermi=\"['mp:tag:create']\"\n          :disabled=\"queryParams.accountId === 0\"\n        >\n          <Icon icon=\"ep:plus\" class=\"mr-5px\" /> 新增\n        </el-button>\n        <el-button\n          type=\"success\"\n          plain\n          @click=\"handleSync\"\n          v-hasPermi=\"['mp:tag:sync']\"\n          :disabled=\"queryParams.accountId === 0\"\n        >\n          <Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 同步\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column label=\"编号\" align=\"center\" prop=\"id\" />\n      <el-table-column label=\"标签名称\" align=\"center\" prop=\"name\" />\n      <el-table-column label=\"粉丝数\" align=\"center\" prop=\"count\" />\n      <el-table-column\n        label=\"创建时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        width=\"180\"\n        :formatter=\"dateFormatter\"\n      />\n      <el-table-column label=\"操作\" align=\"center\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n            v-hasPermi=\"['mp:tag:update']\"\n          >\n            修改\n          </el-button>\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n            v-hasPermi=\"['mp:tag:delete']\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <TagForm ref=\"formRef\" @success=\"getList\" />\n</template>\n<script lang=\"ts\" setup>\nimport { dateFormatter } from '@/utils/formatTime'\nimport * as MpTagApi from '@/api/mp/tag'\nimport TagForm from './TagForm.vue'\nimport WxAccountSelect from '@/views/mp/components/wx-account-select'\n\ndefineOptions({ name: 'MpTag' })\n\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref<any[]>([]) // 列表的数据\n\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  accountId: -1\n})\n\nconst formRef = ref<InstanceType<typeof TagForm> | null>(null)\n\n/** 侦听公众号变化 **/\nconst onAccountChanged = (id: number) => {\n  queryParams.accountId = id\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 查询列表 */\nconst getList = async () => {\n  try {\n    loading.value = true\n    const data = await MpTagApi.getTagPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 添加/修改操作 */\nconst openForm = (type: 'create' | 'update', id?: number) => {\n  formRef.value?.open(type, queryParams.accountId, id)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await MpTagApi.deleteTag(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 同步操作 */\nconst handleSync = async () => {\n  try {\n    await message.confirm('是否确认同步标签？')\n    await MpTagApi.syncTag(queryParams.accountId as number)\n    message.success('同步标签成功')\n    await getList()\n  } catch {}\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/user/UserForm.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" title=\"修改\">\n    <el-form\n      ref=\"formRef\"\n      v-loading=\"formLoading\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"80px\"\n    >\n      <el-form-item label=\"昵称\" prop=\"nickname\">\n        <el-input v-model=\"formData.nickname\" placeholder=\"请输入昵称\" />\n      </el-form-item>\n      <el-form-item label=\"备注\" prop=\"remark\">\n        <el-input v-model=\"formData.remark\" placeholder=\"请输入备注\" />\n      </el-form-item>\n      <el-form-item label=\"标签\" prop=\"tagIds\">\n        <el-select v-model=\"formData.tagIds\" clearable multiple placeholder=\"请选择标签\">\n          <el-option\n            v-for=\"item in tagList\"\n            :key=\"item.tagId\"\n            :label=\"item.name\"\n            :value=\"item.tagId\"\n          />\n        </el-select>\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button :disabled=\"formLoading\" type=\"primary\" @click=\"submitForm\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport * as MpTagApi from '@/api/mp/tag'\nimport * as MpUserApi from '@/api/mp/user'\n\ndefineOptions({ name: 'MpUserForm' })\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst formLoading = ref(false) // 表单的加载中\nconst formData = ref({\n  id: undefined,\n  nickname: undefined,\n  remark: undefined,\n  tagIds: []\n})\nconst formRules = reactive({}) // 表单的校验\nconst formRef = ref() // 表单 Ref\nconst tagList = ref([]) // 公众号标签列表\n\n/** 打开弹窗 */\nconst open = async (id: number) => {\n  dialogVisible.value = true\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await MpUserApi.getUser(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n  // 加载标签\n  tagList.value = await MpTagApi.getSimpleTagList()\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    await MpUserApi.updateUser(formData.value)\n    message.success(t('common.updateSuccess'))\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    nickname: undefined,\n    remark: undefined,\n    tagIds: []\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/mp/user/index.vue",
    "content": "<template>\n  <!-- 搜索工作栏 -->\n  <ContentWrap>\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"公众号\" prop=\"accountId\">\n        <WxAccountSelect @change=\"onAccountChanged\" />\n      </el-form-item>\n      <el-form-item label=\"用户标识\" prop=\"openid\">\n        <el-input\n          v-model=\"queryParams.openid\"\n          placeholder=\"请输入用户标识\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"昵称\" prop=\"nickname\">\n        <el-input\n          v-model=\"queryParams.nickname\"\n          placeholder=\"请输入昵称\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"> <Icon icon=\"ep:search\" />搜索 </el-button>\n        <el-button @click=\"resetQuery\"> <Icon icon=\"ep:refresh\" />重置 </el-button>\n        <el-button\n          type=\"success\"\n          plain\n          @click=\"handleSync\"\n          v-hasPermi=\"['mp:user:sync']\"\n          :disabled=\"queryParams.accountId === 0\"\n        >\n          <Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 同步\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column label=\"编号\" align=\"center\" prop=\"id\" />\n      <el-table-column label=\"用户标识\" align=\"center\" prop=\"openid\" width=\"260\" />\n      <el-table-column label=\"昵称\" align=\"center\" prop=\"nickname\" />\n      <el-table-column label=\"备注\" align=\"center\" prop=\"remark\" />\n      <el-table-column label=\"标签\" align=\"center\" prop=\"tagIds\" width=\"200\">\n        <template #default=\"scope\">\n          <span v-for=\"(tagId, index) in scope.row.tagIds\" :key=\"index\">\n            <el-tag>{{ tagList.find((tag) => tag.tagId === tagId)?.name }} </el-tag>&nbsp;\n          </span>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"订阅状态\" align=\"center\" prop=\"subscribeStatus\">\n        <template #default=\"scope\">\n          <el-tag v-if=\"scope.row.subscribeStatus === 0\" type=\"success\">已订阅</el-tag>\n          <el-tag v-else type=\"danger\">未订阅</el-tag>\n        </template>\n      </el-table-column>\n      <el-table-column\n        label=\"订阅时间\"\n        align=\"center\"\n        prop=\"subscribeTime\"\n        width=\"180\"\n        :formatter=\"dateFormatter\"\n      />\n      <el-table-column label=\"操作\" align=\"center\">\n        <template #default=\"scope\">\n          <el-button\n            type=\"primary\"\n            link\n            @click=\"openForm(scope.row.id)\"\n            v-hasPermi=\"['mp:user:update']\"\n          >\n            修改\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：修改 -->\n  <UserForm ref=\"formRef\" @success=\"getList\" />\n</template>\n<script lang=\"ts\" setup>\nimport { dateFormatter } from '@/utils/formatTime'\nimport * as MpUserApi from '@/api/mp/user'\nimport * as MpTagApi from '@/api/mp/tag'\nimport WxAccountSelect from '@/views/mp/components/wx-account-select'\nimport type { FormInstance } from 'element-plus'\nimport UserForm from './UserForm.vue'\n\ndefineOptions({ name: 'MpUser' })\n\nconst message = useMessage() // 消息\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref<any[]>([]) // 列表的数据\n\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  accountId: -1,\n  openid: '',\n  nickname: ''\n})\nconst queryFormRef = ref<FormInstance | null>(null) // 搜索的表单\nconst tagList = ref<any[]>([]) // 公众号标签列表\n\n/** 侦听公众号变化 **/\nconst onAccountChanged = (id: number) => {\n  queryParams.accountId = id\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 查询列表 */\nconst getList = async () => {\n  try {\n    loading.value = true\n    const data = await MpUserApi.getUserPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  const accountId = queryParams.accountId\n  queryFormRef.value?.resetFields()\n  queryParams.accountId = accountId\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref<InstanceType<typeof UserForm> | null>(null)\nconst openForm = (id: number) => {\n  formRef.value?.open(id)\n}\n\n/** 同步标签 */\nconst handleSync = async () => {\n  try {\n    await message.confirm('是否确认同步粉丝？')\n    await MpUserApi.syncUser(queryParams.accountId)\n    message.success('开始从微信公众号同步粉丝信息，同步需要一段时间，建议稍后再查询')\n    await getList()\n  } catch {}\n}\n\n/** 初始化 */\nonMounted(async () => {\n  tagList.value = await MpTagApi.getSimpleTagList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/pay/merchantDetails/MerchantDetailsForm.vue",
    "content": "<template>\n  <Dialog :title=\"dialogTitle\" v-model=\"dialogVisible\">\n    <el-form\n      ref=\"formRef\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"150px\"\n      v-loading=\"formLoading\"\n    >\n      <el-form-item label=\"支付类型(支付渠道)\" prop=\"payType\">\n        <el-select v-model=\"formData.payType\">\n          <el-option label=\"支付宝支付\" value=\"aliPay\" />\n          <el-option label=\"微信支付\" value=\"wxPay\" />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"支付id\" prop=\"detailsId\">\n        <el-select v-model=\"formData.detailsId\">\n          <!-- <el-option  :value=\"tenantId\" /> -->\n          <el-option label=\"微信支付小程序\" :value=\"'wx_miniapp'\" />\n          <el-option label=\"微信支付公众号\" :value=\"'wx_wechat'\" />\n          <el-option label=\"微信支付H5\" :value=\"'wx_h5'\" />\n          <el-option label=\"支付宝H5\" :value=\"'ali_h5'\" />\n        </el-select>\n      </el-form-item>\n      <!-- <el-form-item label=\"支付id\" prop=\"detailsId\">\n        <el-input v-model=\"formData.detailsId\" placeholder=\"请输入支付id\" />\n      </el-form-item> -->\n      <el-form-item label=\"应用id\" prop=\"appid\">\n        <el-input v-model=\"formData.appid\" placeholder=\"请输入应用id\" />\n      </el-form-item>\n      <el-form-item label=\"微信商户id\" prop=\"mchId\">\n        <el-input v-model=\"formData.mchId\" placeholder=\"请输入微信商户id\" />\n      </el-form-item>\n      <el-form-item label=\"支付宝商户id\" prop=\"seller\">\n        <el-input v-model=\"formData.seller\" placeholder=\"请输入支付宝商户id\" />\n      </el-form-item>\n      <el-form-item label=\"证书存储类型\" prop=\"certStoreType\">\n        <el-select v-model=\"formData.certStoreType\" placeholder=\"请选择类型\" clearable>\n          <el-option label=\"PATH\" value=\"PATH\" />\n          <el-option label=\"STR\" value=\"STR\" />\n          <el-option label=\"INPUT_STREAM\" value=\"INPUT_STREAM\" />\n          <el-option label=\"CLASS_PATH\" value=\"CLASS_PATH\" />\n          <el-option label=\"URL\" value=\"URL\" />\n        </el-select>\n        <div style=\"color: red;\">注意：需要证书的选择不需要不选择</div>\n      </el-form-item>\n      <el-form-item label=\"私钥或私钥证书\" prop=\"keyPrivate\">\n        <el-input v-model=\"formData.keyPrivate\" placeholder=\"请输入私钥或私钥证书\" />\n      </el-form-item>\n      <el-form-item label=\"公钥或公钥证书\" prop=\"keyPublic\">\n        <el-input v-model=\"formData.keyPublic\" placeholder=\"请输入公钥或公钥证书\" />\n      </el-form-item>\n      <el-form-item label=\"key证书\" prop=\"keyCert\">\n        <el-input v-model=\"formData.keyCert\" placeholder=\"请输入key证书,附加证书使用，如SSL证书，或者银联根级证书方面\" />\n      </el-form-item>\n      <el-form-item label=\"证书的密码\" prop=\"keyCertPwd\">\n        <el-input v-model=\"formData.keyCertPwd\" placeholder=\"请输入私钥证书或key证书的密码\" />\n      </el-form-item>\n      <el-form-item label=\"异步回调地址\" prop=\"notifyUrl\">\n        <el-input v-model=\"formData.notifyUrl\" placeholder=\"请输入异步回调\" />\n      </el-form-item>\n      <el-form-item label=\"同步回调地址\" prop=\"returnUrl\">\n        <el-input v-model=\"formData.returnUrl\" placeholder=\"请输入同步回调地址，大部分用于付款成功后页面转跳\" />\n      </el-form-item>\n      <el-form-item label=\"签名方式\" prop=\"signType\">\n        <el-select v-model=\"formData.signType\" placeholder=\"请选择签名方式MD5,RSA等等\">\n          <el-option label=\"RSA\" value=\"RSA\" />\n          <el-option label=\"RSA2\" value=\"RSA2\" />\n          <el-option label=\"MD5\" value=\"MD5\" />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"子appid\" prop=\"subAppId\">\n        <el-input v-model=\"formData.subAppId\" placeholder=\"请输入子appid\" />\n      </el-form-item>\n      <el-form-item label=\"子商户id\" prop=\"subMchId\">\n        <el-input v-model=\"formData.subMchId\" placeholder=\"请输入子商户id\" />\n      </el-form-item>\n      <el-form-item label=\"是否为测试环境\" prop=\"isTest\">\n        <el-radio-group v-model=\"formData.isTest\">\n          <el-radio :label=\"1\">是</el-radio>\n          <el-radio :label=\"0\">否</el-radio>\n        </el-radio-group>\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button @click=\"submitForm\" type=\"primary\" :disabled=\"formLoading\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script setup lang=\"ts\">\nimport * as MerchantDetailsApi from '@/api/pay/merchantDetails'\nimport { getTenantId } from '@/utils/auth'\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst tenantId = ref(getTenantId())\n\nconsole.log('tenantId:',tenantId.value)\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  detailsId: undefined,\n  payType: undefined,\n  appid: undefined,\n  mchId: undefined,\n  certStoreType: undefined,\n  keyPrivate: undefined,\n  keyPublic: undefined,\n  keyCert: undefined,\n  keyCertPwd: undefined,\n  notifyUrl: undefined,\n  returnUrl: undefined,\n  signType: undefined,\n  seller: undefined,\n  subAppId: undefined,\n  subMchId: undefined,\n  inputCharset: undefined,\n  isTest: undefined\n})\nconst formRules = reactive({\n  payType: [{ required: true, message: '支付类型(支付渠道)不能为空', trigger: 'change' }],\n  detailsId: [{ required: true, message: '支付id不能为空', trigger: 'change' }],\n  appid: [{ required: true, message: '应用id不能为空', trigger: 'change' }],\n  signType: [{ required: true, message: '签名方式不能为空', trigger: 'change' }],\n  notifyUrl: [{ required: true, message: '异步回调地址不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await MerchantDetailsApi.getMerchantDetails(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as unknown as MerchantDetailsApi.MerchantDetailsVO\n    if (formType.value === 'create') {\n      await MerchantDetailsApi.createMerchantDetails(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await MerchantDetailsApi.updateMerchantDetails(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    detailsId: undefined,\n    payType: undefined,\n    appid: undefined,\n    mchId: undefined,\n    certStoreType: undefined,\n    keyPrivate: undefined,\n    keyPublic: undefined,\n    keyCert: undefined,\n    keyCertPwd: undefined,\n    notifyUrl: undefined,\n    returnUrl: undefined,\n    signType: undefined,\n    seller: undefined,\n    subAppId: undefined,\n    subMchId: undefined,\n    inputCharset: undefined,\n    isTest: 1\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/pay/merchantDetails/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <!-- 搜索工作栏 -->\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item>\n        <el-button\n          type=\"primary\"\n          plain\n          @click=\"openForm('create')\"\n          v-hasPermi=\"['pay:merchant-details:create']\"\n        >\n          <Icon icon=\"ep:plus\" class=\"mr-5px\" /> 新增\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column label=\"id\" align=\"center\" prop=\"detailsId\" />\n      <el-table-column label=\"支付类型(支付渠道)\" align=\"center\" prop=\"payType\" />\n      <el-table-column label=\"应用id\" align=\"center\" prop=\"appid\" />\n      <el-table-column label=\"商户id\" align=\"center\" prop=\"mchId\" />\n      <el-table-column label=\"异步回调地址\" align=\"center\" prop=\"notifyUrl\" />\n      <el-table-column label=\"同步回调地址\" align=\"center\" prop=\"returnUrl\" />\n      <el-table-column label=\"签名方式\" align=\"center\" prop=\"signType\" />\n      <el-table-column label=\"是否为测试环境\" align=\"center\" prop=\"isTest\" />\n      <el-table-column\n        label=\"创建时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        :formatter=\"dateFormatter\"\n        width=\"120\"\n      />\n      <el-table-column label=\"操作\" align=\"center\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.detailsId)\"\n            v-hasPermi=\"['pay:merchant-details:update']\"\n          >\n            编辑\n          </el-button>\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.detailsId)\"\n            v-hasPermi=\"['pay:merchant-details:delete']\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <MerchantDetailsForm ref=\"formRef\" @success=\"getList\" />\n</template>\n\n<script setup lang=\"ts\" name=\"MerchantDetails\">\nimport * as MerchantDetailsApi from '@/api/pay/merchantDetails'\nimport MerchantDetailsForm from './MerchantDetailsForm.vue'\nimport { dateFormatter } from '@/utils/formatTime'\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  payType: null,\n  appid: null,\n  mchId: null,\n  certStoreType: null,\n  keyPrivate: null,\n  keyPublic: null,\n  keyCert: null,\n  keyCertPwd: null,\n  notifyUrl: null,\n  returnUrl: null,\n  signType: null,\n  seller: null,\n  subAppId: null,\n  subMchId: null,\n  inputCharset: null,\n  isTest: null\n})\nconst queryFormRef = ref() // 搜索的表单\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await MerchantDetailsApi.getMerchantDetailsPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await MerchantDetailsApi.deleteMerchantDetails(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/score/order/OrderDetail.vue",
    "content": "<template>\n <el-drawer v-model=\"drawer\" :title=\"dialogTitle\" size=\"40%\">\n    <div>\n      <el-descriptions title=\"收货信息\" :column=\"2\">\n        <el-descriptions-item label=\"用户昵称\">{{ nickname }}</el-descriptions-item>\n        <el-descriptions-item label=\"收货人\">{{ DetailData.customerName }}</el-descriptions-item>\n        <el-descriptions-item label=\"联系电话\">{{ DetailData.customerPhone }}</el-descriptions-item>\n        <el-descriptions-item label=\"收货地址\">{{ DetailData.customerAddress }}</el-descriptions-item>\n      </el-descriptions>\n      <el-descriptions title=\"商品明细\" :column=\"2\">\n        <el-descriptions-item label=\"商品图片\">\n          <el-image style=\"width: 40px; height: 40px\" :src=\"product.image\" :fit=\"fit\" />\n        </el-descriptions-item>\n        <el-descriptions-item label=\"商品名称\">{{ product.title }}</el-descriptions-item>\n        <el-descriptions-item label=\"商品价格\">{{ product.score }}积分</el-descriptions-item>\n      </el-descriptions>\n      <el-descriptions title=\"订单信息\" :column=\"2\">\n        <el-descriptions-item label=\"订单号\">{{ DetailData.orderId }}</el-descriptions-item>\n        <el-descriptions-item label=\"数量\">{{ DetailData.number }}</el-descriptions-item>\n        <el-descriptions-item label=\"花费积分\">{{ DetailData.totalScore }}</el-descriptions-item>\n        <el-descriptions-item label=\"是否支付\">\n           <span v-if=\"DetailData.havePaid == 0\" style=\"color:#F56C6C\">否</span>\n           <span v-if=\"DetailData.havePaid == 1\" style=\"color:#409EFF\">是</span>\n        </el-descriptions-item>\n        <el-descriptions-item label=\"是否发货\">\n           <span v-if=\"DetailData.haveDelivered == 0\" style=\"color:#F56C6C\">否</span>\n           <span v-if=\"DetailData.haveDelivered == 1\" style=\"color:#409EFF\">是</span>\n        </el-descriptions-item>\n        <el-descriptions-item label=\"是否收货\">\n           <span v-if=\"DetailData.haveReceived == 0\" style=\"color:#F56C6C\">否</span>\n           <span v-if=\"DetailData.haveReceived == 1\" style=\"color:#409EFF\">是</span>\n        </el-descriptions-item>\n        <el-descriptions-item label=\"ip\">{{ DetailData.ip }}</el-descriptions-item>\n        <el-descriptions-item label=\"创建时间\">{{ formatDate(DetailData.createTime)}}</el-descriptions-item>\n      </el-descriptions>\n      <el-descriptions title=\"物流信息\" :column=\"2\">\n        <el-descriptions-item label=\"快递公司\">{{ DetailData.expressCompany }}</el-descriptions-item>\n        <el-descriptions-item label=\"快递单号\">\n          {{ DetailData.expressNumber }} \n          <el-button type=\"primary\" @click=\"getLogistic(DetailData.expressSn,DetailData.expressNumber)\">物流追踪</el-button>\n        </el-descriptions-item>\n      </el-descriptions>\n      <el-timeline>\n        <el-timeline-item\n          v-for=\"(activity, index) in logisticResult\"\n          :key=\"index\"\n          :timestamp=\"activity.acceptTime\"\n        >\n          {{ activity.acceptStation }}\n        </el-timeline-item>\n      </el-timeline>\n    </div>\n  </el-drawer>\n</template>\n<script setup lang=\"ts\">\nimport * as OrderApi from '@/api/score/order'\nimport { formatDate } from '@/utils/formatTime'\n\nconst { t } = useI18n() // 国际化\nconst dialogTitle = ref('') // 弹窗的标题\nconst drawer = ref(false)\nconst DetailData = ref({})\nconst nickname = ref('')\nconst logisticResult = ref({})\nconst product = ref({})\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  drawer.value = true\n  dialogTitle.value = t('action.' + type)\n  let data  = await OrderApi.getOrder(id)\n  DetailData.value = data\n  nickname.value = DetailData.value.userRespVO.nickname\n  product.value = data.scoreProductRespVO\n  console.log('aa:',product.value )\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\nconst getLogistic = async(deliverySn,deliveryId) => {\n  const res  = await OrderApi.getLogistic(deliverySn, deliveryId)\n  if (res.success == \"false\") {\n    message.error(res.reason)\n  }\n  logisticResult.value = res.traces\n}\n</script>\n<style scoped>\n</style>"
  },
  {
    "path": "yshop-drink-vue3/src/views/score/order/OrderForm.vue",
    "content": "<template>\n  <Dialog :title=\"dialogTitle\" v-model=\"dialogVisible\">\n    <el-form\n      ref=\"formRef\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"100px\"\n      v-loading=\"formLoading\"\n    >\n    <el-form-item label=\"选择类型\" prop=\"orderType\">\n         <el-radio-group model-value=\"send\">\n          <el-radio label=\"send\" >发货</el-radio>\n        </el-radio-group>\n      </el-form-item>\n      <el-form-item label=\"发货类型\" prop=\"deliveryType\">\n         <el-radio-group model-value=\"normal\">\n          <el-radio label=\"normal\">手动填写</el-radio>\n        </el-radio-group>\n      </el-form-item>\n      <el-form-item label=\"快递公司\" prop=\"expressSn\">\n          <el-select v-model=\"formData.expressSn\" placeholder=\"选择快递公司\" @change=\"selectExpress\" >\n            <el-option label=\"选择快递公司\" value=\"\" />\n            <el-option\n              v-for=\"item in express\"\n              :key=\"item.code\"\n              :label=\"item.name\"\n              :value=\"item.code\"\n            />\n          </el-select>\n      </el-form-item>\n      <el-form-item label=\"快递单号\" prop=\"expressNumber\">\n        <el-input v-model=\"formData.expressNumber\" placeholder=\"请输入快递单号\" class=\"input-width\" />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button @click=\"submitForm\" type=\"primary\" :disabled=\"formLoading\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script setup lang=\"ts\">\nimport * as OrderApi from '@/api/score/order'\nimport * as ExpressApi from '@/api/express'\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  userId: undefined,\n  productId: undefined,\n  number: undefined,\n  score: undefined,\n  totalScore: undefined,\n  ip: undefined,\n  expressSn: undefined,\n  expressNumber: undefined,\n  expressCompany: undefined,\n  customerName: undefined,\n  customerPhone: undefined,\n  customerAddress: undefined,\n  status: undefined,\n  havePaid: undefined,\n  haveDelivered: undefined,\n  haveReceived: undefined\n})\nconst express = ref([])\nconst formRules = reactive({\n  expressSn: [{ required: true, message: '请选择快递公司', trigger: 'blur' }],\n  expressNumber: [{ required: true, message: '快递单号不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  express.value = await ExpressApi.getExpressList()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await OrderApi.getOrder(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as unknown as OrderApi.OrderVO\n    await OrderApi.updateOrder(data)\n    message.success(t('common.updateSuccess'))\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\nconst selectExpress = (val) => {\n  let obj = {};\n  obj = express.value.find((item)=>{ // 这里的userList就是上面遍历的数据源\n      return item.code === val; // 筛选出匹配数据\n  })\n  formData.value.expressCompany = obj.name\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    userId: undefined,\n    productId: undefined,\n    number: undefined,\n    score: undefined,\n    totalScore: undefined,\n    ip: undefined,\n    expressNumber: undefined,\n    expressCompany: undefined,\n    customerName: undefined,\n    customerPhone: undefined,\n    customerAddress: undefined,\n    status: undefined,\n    havePaid: undefined,\n    haveDelivered: undefined,\n    haveReceived: undefined\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/score/order/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <el-tabs v-model=\"activeName\">\n      <el-tab-pane label=\"全部订单\" name=\"first\"/>\n    </el-tabs>\n    <el-form-item label=\"订单状态：\" >\n      <el-radio-group v-model=\"orderStatus\" size=\"large\"  fill=\"#DC143C\" @change=\"queryOrderStatus\">\n        <el-radio-button label=\"-1\">全部</el-radio-button>\n        <el-radio-button label=\"0\">待发货</el-radio-button>\n        <el-radio-button label=\"1\">待收货</el-radio-button>\n        <el-radio-button label=\"2\">已完成</el-radio-button>\n      </el-radio-group>\n    </el-form-item>\n    <!-- 搜索工作栏 -->\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"用户名\" prop=\"userId\">\n        <el-input\n          v-model=\"queryParams.customerPhone\"\n          placeholder=\"请输入电话\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n\n      <el-form-item label=\"订单号\" prop=\"number\">\n        <el-input\n          v-model=\"queryParams.orderId\"\n          placeholder=\"请输入订单号\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column label=\"id\" align=\"center\" prop=\"id\" />\n      <el-table-column label=\"订单号\" align=\"center\" prop=\"orderId\" width=\"190\" />\n      <el-table-column label=\"用户id|昵称\" align=\"center\" prop=\"uid\" width=\"150\">\n        <template #default=\"scope\">\n          <span>{{ scope.row.uid }}|{{ scope.row.userRespVO ? scope.row.userRespVO.nickname : '--' }}</span>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"商品信息\" align=\"center\" prop=\"productId\" width=\"350\">\n        <template #default=\"scope\">\n          <div class=\"tabBox\">\n              <div class=\"tabBox_img\">\n                  <img :src=\"scope.row.scoreProductRespVO.image\" />\n              </div>\n              <span class=\"tabBox_tit\">{{ scope.row.scoreProductRespVO.title }}</span>\n              <span class=\"tabBox_pice\">{{ '积分'+ scope.row.scoreProductRespVO.score + ' x '+ scope.row.number}}</span>\n          </div>\n        </template>\n      </el-table-column>\n      <!-- <el-table-column label=\"数量\" align=\"center\" prop=\"number\" /> -->\n      <el-table-column label=\"总消耗积分\" align=\"center\" prop=\"totalScore\" />\n      <el-table-column label=\"是否支付\" align=\"center\" prop=\"havePaid\">\n        <template #default=\"scope\">\n           <span v-if=\"scope.row.havePaid == 0\" style=\"color:#F56C6C\">否</span>\n           <span v-if=\"scope.row.havePaid == 1\" style=\"color:#409EFF\">是</span>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"是否发货\" align=\"center\" prop=\"haveDelivered\">\n        <template #default=\"scope\">\n           <span v-if=\"scope.row.haveDelivered == 0\" style=\"color:#F56C6C\">否</span>\n           <span v-if=\"scope.row.haveDelivered == 1\" style=\"color:#409EFF\">是</span>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"是否收货\" align=\"center\" prop=\"haveReceived\">\n        <template #default=\"scope\">\n           <span v-if=\"scope.row.haveReceived == 0\" style=\"color:#F56C6C\">否</span>\n           <span v-if=\"scope.row.haveReceived == 1\" style=\"color:#409EFF\">是</span>\n        </template>\n      </el-table-column>\n      <el-table-column\n        label=\"添加时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        :formatter=\"dateFormatter\"\n        width=\"170\"\n      />\n      <el-table-column label=\"操作\" align=\"center\" fixed=\"right\" width=\"150\">\n        <template #default=\"scope\">\n          <div class=\"flex justify-center items-center\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openForm('orderSend', scope.row.id)\"\n            v-if = \"scope.row.haveDelivered == 0 && scope.row.havePaid == 1\"\n            v-hasPermi=\"['score:order:update']\"\n          >\n            发货\n          </el-button>\n          <el-dropdown>\n            <el-button type=\"primary\" link><Icon icon=\"ep:d-arrow-right\" /> 更多</el-button>\n            <template #dropdown>\n              <el-dropdown-menu>\n                <el-dropdown-item @click=\"openForm('orderDetail', scope.row.id)\">订单详情</el-dropdown-item>\n                <el-dropdown-item @click=\"handleDelete(scope.row.id)\">删除订单</el-dropdown-item>\n                <el-dropdown-item v-if = \"scope.row.haveDelivered == 1 && scope.row.haveReceived == 0\" @click=\"handleTake(scope.row.id)\">后台收货</el-dropdown-item>\n              </el-dropdown-menu>\n            </template>\n          </el-dropdown>\n        </div>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <OrderForm ref=\"formRef\" @success=\"getList\" />\n  <OrderDetail ref=\"formRef4\" />\n</template>\n\n<script setup lang=\"ts\" name=\"ScoreOrder\">\nimport { dateFormatter } from '@/utils/formatTime'\nimport download from '@/utils/download'\nimport * as OrderApi from '@/api/score/order'\nimport OrderForm from './OrderForm.vue'\nimport OrderDetail from './OrderDetail.vue'\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  orderId: \"\",\n  type: -1,\n  customerName: null,\n  customerPhone: null,\n  customerAddress: null,\n})\nconst queryFormRef = ref() // 搜索的表单\nconst exportLoading = ref(false) // 导出的加载中\nconst activeName = ref('first')\nconst orderStatus = ref('-1')\n\nconst queryOrderStatus = (value) => {\n  queryParams.type = value\n  getList()\n}\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await OrderApi.getOrderPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst formRef4 = ref()\nconst openForm = (type: string, id?: number) => {\n  if (type == 'orderSend') {\n    formRef.value.open(type, id)\n  }else if (type == 'orderDetail') {\n    formRef4.value.open(type, id)\n  }\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await OrderApi.deleteOrder(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 导出按钮操作 */\nconst handleExport = async () => {\n  try {\n    // 导出的二次确认\n    await message.exportConfirm()\n    // 发起导出\n    exportLoading.value = true\n    const data = await OrderApi.exportOrder(queryParams)\n    download.excel(data, '积分商城订单.xls')\n  } catch {\n  } finally {\n    exportLoading.value = false\n  }\n}\n\n/** 确认收货按钮操作 */\nconst handleTake = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.confirm('修改收货状态')\n    // 发起删除\n    await OrderApi.takeStoreOrder(id)\n    message.success(t('common.updateSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n<style>\n  .tabBox{\n    width: 100%;\n    height: 100%;\n    display: flex;\n    align-items: center\n    }\n.tabBox_img{\n    width: 36px;\n    height: 36px;\n  }\n.tabBox_img img{\n  width: 100%;\n  height: 100%;\n}\n.tabBox_tit{\n    /* width :40%; */\n    font-size: 12px !important;\n    margin: 0 2px 0 10px;\n    letter-spacing: 1px;\n    padding: 5px 0;\n    box-sizing: border-box;\n    text-align: left;\n  }\n  .tabBox_pice{\n    /* width :30%; */\n    font-size: 12px !important;\n    margin: 0 2px 0 10px;\n    letter-spacing: 1px;\n    padding: 5px 0;\n    box-sizing: border-box;\n    text-align: left;\n  }\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/score/product/ProductForm.vue",
    "content": "<template>\n  <Dialog :title=\"dialogTitle\" v-model=\"dialogVisible\" width=\"60%\">\n    <el-form\n      ref=\"formRef\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"100px\"\n      v-loading=\"formLoading\"\n    >\n      <el-form-item label=\"产品标题\" prop=\"title\">\n        <el-input v-model=\"formData.title\" placeholder=\"请输入产品标题\" />\n      </el-form-item>\n      <el-form-item label=\"主图\" prop=\"image\">\n          <Materials v-model=\"formData.image\" num=\"1\" type=\"image\" />\n      </el-form-item>\n      <el-form-item label=\"组图\" prop=\"images\">\n        <Materials v-model=\"formData.images\" num=\"5\" type=\"image\" />\n      </el-form-item>\n      <el-form-item label=\"详情\" prop=\"desc\">\n        <vue-ueditor-wrap v-model=\"formData.desc\" :config=\"myConfig\" @before-init=\"addCustomDialog\"    style=\"width: 90%;\" />\n      </el-form-item>\n      <el-form-item label=\"消耗积分\" prop=\"score\">\n        <el-input v-model=\"formData.score\" placeholder=\"请输入消耗积分\" />\n      </el-form-item>\n      <el-form-item label=\"库存\" prop=\"stock\">\n        <el-input v-model=\"formData.stock\" placeholder=\"请输入库存\" />\n      </el-form-item>\n      <el-form-item label=\"权重\" prop=\"weigh\">\n        <el-input v-model=\"formData.weigh\" placeholder=\"请输入权重\" />\n      </el-form-item>\n      <el-form-item label=\"是否上架\" prop=\"isSwitch\">\n        <el-radio-group v-model=\"formData.isSwitch\">\n          <el-radio :label=\"1\">上架</el-radio>\n          <el-radio :label=\"0\">下架</el-radio>\n        </el-radio-group>\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button @click=\"submitForm\" type=\"primary\" :disabled=\"formLoading\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script setup lang=\"ts\">\nimport * as ProductApi from '@/api/score/product'\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  title: undefined,\n  image: undefined,\n  images: undefined,\n  desc: undefined,\n  score: undefined,\n  weigh: undefined,\n  stock: undefined,\n  sales: undefined,\n  switch: undefined\n})\nconst formRules = reactive({\n  title: [{ required: true, message: '产品标题不能为空', trigger: 'blur' }],\n  image: [{ required: true, message: '主图不能为空', trigger: 'blur' }],\n  images: [{ required: true, message: '组图不能为空', trigger: 'blur' }],\n  desc: [{ required: true, message: '详情不能为空', trigger: 'blur' }],\n  score: [{ required: true, message: '消耗积分不能为空', trigger: 'blur' }],\n  stock: [{ required: true, message: '库存不能为空', trigger: 'blur' }]\n})\nconst myConfig = reactive( {\n      autoHeightEnabled: false, // 编辑器不自动被内容撑高\n      initialFrameHeight: 500, // 初始容器高度\n      initialFrameWidth: '100%', // 初始容器宽度\n      UEDITOR_HOME_URL: '/UEditor/',\n      serverUrl: '',\n      zIndex: 9999,\n      toolbars: [\n        [\n          'undo',\n          'redo',\n          '|',\n          'bold',\n          'italic',\n          'underline',\n          'fontborder',\n          'strikethrough',\n          'superscript',\n          'subscript',\n          'removeformat',\n          'formatmatch',\n          'autotypeset',\n          'blockquote',\n          'pasteplain',\n          '|',\n          'forecolor',\n          'backcolor',\n          'insertorderedlist',\n          'insertunorderedlist',\n          'selectall',\n          'cleardoc',\n          '|',\n          'rowspacingtop',\n          'rowspacingbottom',\n          'lineheight',\n          '|',\n          'customstyle',\n          'paragraph',\n          'fontfamily',\n          'fontsize',\n          '|',\n          'directionalityltr',\n          'directionalityrtl',\n          'indent',\n          '|',\n          'justifyleft',\n          'justifycenter',\n          'justifyright',\n          'justifyjustify',\n          '|',\n          'touppercase',\n          'tolowercase',\n          '|',\n          'imagenone',\n          'imageleft',\n          'imageright',\n          'imagecenter',\n        ],\n      ],\n})\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await ProductApi.getProduct(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as unknown as ProductApi.ProductVO\n    if (formType.value === 'create') {\n      await ProductApi.createProduct(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await ProductApi.updateProduct(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\nconst addCustomDialog  = () => {\n      window.UE.registerUI('yshop', function (editor, uiName) {\n        let dialog = new window.UE.ui.Dialog({\n          iframeUrl: '/yshop/materia/index',\n          editor: editor,\n          name: uiName,\n          title: '上传图片',\n          cssRules: 'width:1200px;height:650px;padding:20px;'\n        });\n        this.dialog = dialog;\n\n        var btn = new window.UE.ui.Button({\n          name: 'dialog-button',\n          title: '上传图片',\n          cssRules: `background-image: url(../../../assets/imgs/icons.png);background-position: -726px -77px;`,\n          onclick: function () {\n            dialog.render();\n            dialog.open();\n          }\n        });\n\n        return btn;\n      }, 37);\n}\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    title: undefined,\n    image: undefined,\n    images: undefined,\n    desc: undefined,\n    score: undefined,\n    weigh: undefined,\n    stock: undefined,\n    sales: undefined,\n    switch: undefined\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/score/product/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <!-- 搜索工作栏 -->\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"产品标题\" prop=\"title\">\n        <el-input\n          v-model=\"queryParams.title\"\n          placeholder=\"请输入产品标题\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n        <el-button\n          type=\"primary\"\n          plain\n          @click=\"openForm('create')\"\n          v-hasPermi=\"['score:product:create']\"\n        >\n          <Icon icon=\"ep:plus\" class=\"mr-5px\" /> 新增\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column label=\"id\" align=\"center\" prop=\"id\" />\n      <el-table-column label=\"产品标题\" align=\"center\" prop=\"title\" />\n      <el-table-column label=\"主图\" align=\"center\" prop=\"image\" >\n        <template #default=\"scope\">\n          <el-image style=\"width: 100px; height: 100px\" :src=\"scope.row.image\"  />\n        </template>\n      </el-table-column>\n      <el-table-column label=\"消耗积分\" align=\"center\" prop=\"score\" />\n      <el-table-column label=\"权重\" align=\"center\" prop=\"weigh\" />\n      <el-table-column label=\"库存\" align=\"center\" prop=\"stock\" />\n      <el-table-column label=\"销售量\" align=\"center\" prop=\"sales\" />\n      <el-table-column label=\"是否上架\" align=\"center\" prop=\"isSwitch\">\n        <template #default=\"scope\">\n          <span v-if=\"scope.row.isSwitch == 1\">上架</span>\n          <span v-else>下架</span>\n         </template>\n      </el-table-column>\n      <el-table-column\n        label=\"添加时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        :formatter=\"dateFormatter\"\n        width=\"170\"\n      />\n      <el-table-column label=\"操作\" align=\"center\" fixed=\"right\" width=\"150\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n            v-hasPermi=\"['score:product:update']\"\n          >\n            编辑\n          </el-button>\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n            v-hasPermi=\"['score:product:delete']\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <ProductForm ref=\"formRef\" @success=\"getList\" />\n</template>\n\n<script setup lang=\"ts\" name=\"ScoreProduct\">\nimport { dateFormatter } from '@/utils/formatTime'\nimport download from '@/utils/download'\nimport * as ProductApi from '@/api/score/product'\nimport ProductForm from './ProductForm.vue'\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  title: null,\n  image: null,\n  images: null,\n  desc: null,\n  score: null,\n  weigh: null,\n  stock: null,\n  sales: null,\n  switch: null,\n  createTime: []\n})\nconst queryFormRef = ref() // 搜索的表单\nconst exportLoading = ref(false) // 导出的加载中\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await ProductApi.getProductPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await ProductApi.deleteProduct(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 导出按钮操作 */\nconst handleExport = async () => {\n  try {\n    // 导出的二次确认\n    await message.exportConfirm()\n    // 发起导出\n    exportLoading.value = true\n    const data = await ProductApi.exportProduct(queryParams)\n    download.excel(data, '积分产品.xls')\n  } catch {\n  } finally {\n    exportLoading.value = false\n  }\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/area/AreaForm.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" title=\"IP 查询\">\n    <el-form\n      ref=\"formRef\"\n      v-loading=\"formLoading\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"80px\"\n    >\n      <el-form-item label=\"IP\" prop=\"ip\">\n        <el-input v-model=\"formData.ip\" placeholder=\"请输入 IP 地址\" />\n      </el-form-item>\n      <el-form-item label=\"地址\" prop=\"result\">\n        <el-input v-model=\"formData.result\" placeholder=\"展示查询 IP 结果\" readonly />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button :disabled=\"formLoading\" type=\"primary\" @click=\"submitForm\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport * as AreaApi from '@/api/system/area'\n\ndefineOptions({ name: 'SystemAreaForm' })\n\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst formLoading = ref(false) // 表单的加载中：提交的按钮禁用\nconst formData = ref({\n  ip: '',\n  result: undefined\n})\nconst formRules = reactive({\n  ip: [{ required: true, message: 'IP 地址不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async () => {\n  dialogVisible.value = true\n  resetForm()\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    formData.value.result = await AreaApi.getAreaByIp(formData.value.ip!.trim())\n    message.success('查询成功')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    ip: '',\n    result: undefined\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/area/index.vue",
    "content": "<template>\n  <!-- 操作栏 -->\n  <ContentWrap>\n    <el-button type=\"primary\" plain @click=\"openForm()\">\n      <Icon icon=\"ep:plus\" class=\"mr-5px\" /> IP 查询\n    </el-button>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <div style=\"width: 100%; height: 700px\">\n      <!-- AutoResizer 自动调节大小 -->\n      <el-auto-resizer>\n        <template #default=\"{ height, width }\">\n          <!-- Virtualized Table 虚拟化表格：高性能，解决表格在大数据量下的卡顿问题 -->\n          <el-table-v2\n            :columns=\"columns\"\n            :data=\"list\"\n            :width=\"width\"\n            :height=\"height\"\n            expand-column-key=\"id\"\n          />\n        </template>\n      </el-auto-resizer>\n    </div>\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <AreaForm ref=\"formRef\" />\n</template>\n<script setup lang=\"tsx\">\nimport type { Column } from 'element-plus'\nimport AreaForm from './AreaForm.vue'\nimport * as AreaApi from '@/api/system/area'\n\ndefineOptions({ name: 'SystemArea' })\n\n// 表格的 column 字段\nconst columns: Column[] = [\n  {\n    dataKey: 'id', // 需要渲染当前列的数据字段。例如说：{id:9527, name:'Mike'}，则填 id\n    title: '编号', // 显示在单元格表头的文本\n    width: 400, // 当前列的宽度，必须设置\n    fixed: true, // 是否固定列\n    key: 'id' // 树形展开对应的 key\n  },\n  {\n    dataKey: 'name',\n    title: '地名',\n    width: 200\n  }\n]\n// 表格的数据\nconst list = ref([])\n\n/**\n * 获得数据列表\n */\nconst getList = async () => {\n  list.value = await AreaApi.getAreaTree()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = () => {\n  formRef.value.open()\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/dept/DeptForm.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" :title=\"dialogTitle\">\n    <el-form\n      ref=\"formRef\"\n      v-loading=\"formLoading\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"80px\"\n    >\n      <el-form-item label=\"上级部门\" prop=\"parentId\">\n        <el-tree-select\n          v-model=\"formData.parentId\"\n          :data=\"deptTree\"\n          :props=\"defaultProps\"\n          check-strictly\n          default-expand-all\n          placeholder=\"请选择上级部门\"\n          value-key=\"deptId\"\n        />\n      </el-form-item>\n      <el-form-item label=\"部门名称\" prop=\"name\">\n        <el-input v-model=\"formData.name\" placeholder=\"请输入部门名称\" />\n      </el-form-item>\n      <el-form-item label=\"显示排序\" prop=\"sort\">\n        <el-input-number v-model=\"formData.sort\" :min=\"0\" controls-position=\"right\" />\n      </el-form-item>\n      <el-form-item label=\"负责人\" prop=\"leaderUserId\">\n        <el-select v-model=\"formData.leaderUserId\" clearable placeholder=\"请输入负责人\">\n          <el-option\n            v-for=\"item in userList\"\n            :key=\"item.id\"\n            :label=\"item.nickname\"\n            :value=\"item.id\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"联系电话\" prop=\"phone\">\n        <el-input v-model=\"formData.phone\" maxlength=\"11\" placeholder=\"请输入联系电话\" />\n      </el-form-item>\n      <el-form-item label=\"邮箱\" prop=\"email\">\n        <el-input v-model=\"formData.email\" maxlength=\"50\" placeholder=\"请输入邮箱\" />\n      </el-form-item>\n      <el-form-item label=\"状态\" prop=\"status\">\n        <el-select v-model=\"formData.status\" clearable placeholder=\"请选择状态\">\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button type=\"primary\" @click=\"submitForm\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getIntDictOptions } from '@/utils/dict'\nimport { defaultProps, handleTree } from '@/utils/tree'\nimport * as DeptApi from '@/api/system/dept'\nimport * as UserApi from '@/api/system/user'\nimport { CommonStatusEnum } from '@/utils/constants'\nimport { FormRules } from 'element-plus'\n\ndefineOptions({ name: 'SystemDeptForm' })\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  title: '',\n  parentId: undefined,\n  name: undefined,\n  sort: undefined,\n  leaderUserId: undefined,\n  phone: undefined,\n  email: undefined,\n  status: CommonStatusEnum.ENABLE\n})\nconst formRules = reactive<FormRules>({\n  parentId: [{ required: true, message: '上级部门不能为空', trigger: 'blur' }],\n  name: [{ required: true, message: '部门名称不能为空', trigger: 'blur' }],\n  sort: [{ required: true, message: '显示排序不能为空', trigger: 'blur' }],\n  email: [{ type: 'email', message: '请输入正确的邮箱地址', trigger: ['blur', 'change'] }],\n  phone: [\n    { pattern: /^1[3|4|5|6|7|8|9][0-9]\\d{8}$/, message: '请输入正确的手机号码', trigger: 'blur' }\n  ],\n  status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\nconst deptTree = ref() // 树形结构\nconst userList = ref<UserApi.UserVO[]>([]) // 用户列表\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await DeptApi.getDept(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n  // 获得用户列表\n  userList.value = await UserApi.getSimpleUserList()\n  // 获得部门树\n  await getTree()\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as unknown as DeptApi.DeptVO\n    if (formType.value === 'create') {\n      await DeptApi.createDept(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await DeptApi.updateDept(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    title: '',\n    parentId: undefined,\n    name: undefined,\n    sort: undefined,\n    leaderUserId: undefined,\n    phone: undefined,\n    email: undefined,\n    status: CommonStatusEnum.ENABLE\n  }\n  formRef.value?.resetFields()\n}\n\n/** 获得部门树 */\nconst getTree = async () => {\n  deptTree.value = []\n  const data = await DeptApi.getSimpleDeptList()\n  let dept: Tree = { id: 0, name: '顶级部门', children: [] }\n  dept.children = handleTree(data)\n  deptTree.value.push(dept)\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/dept/index.vue",
    "content": "<template>\n  <!-- 搜索工作栏 -->\n  <ContentWrap>\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"部门名称\" prop=\"name\">\n        <el-input\n          v-model=\"queryParams.name\"\n          placeholder=\"请输入部门名称\"\n          clearable\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"部门状态\" prop=\"status\">\n        <el-select\n          v-model=\"queryParams.status\"\n          placeholder=\"请选择部门状态\"\n          clearable\n          class=\"!w-240px\"\n        >\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n        <el-button\n          type=\"primary\"\n          plain\n          @click=\"openForm('create')\"\n          v-hasPermi=\"['system:dept:create']\"\n        >\n          <Icon icon=\"ep:plus\" class=\"mr-5px\" /> 新增\n        </el-button>\n        <el-button type=\"danger\" plain @click=\"toggleExpandAll\">\n          <Icon icon=\"ep:sort\" class=\"mr-5px\" /> 展开/折叠\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table\n      v-loading=\"loading\"\n      :data=\"list\"\n      row-key=\"id\"\n      :default-expand-all=\"isExpandAll\"\n      v-if=\"refreshTable\"\n    >\n      <el-table-column prop=\"name\" label=\"部门名称\" />\n      <el-table-column prop=\"leader\" label=\"负责人\">\n        <template #default=\"scope\">\n          {{ userList.find((user) => user.id === scope.row.leaderUserId)?.nickname }}\n        </template>\n      </el-table-column>\n      <el-table-column prop=\"sort\" label=\"排序\" />\n      <el-table-column prop=\"status\" label=\"状态\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.COMMON_STATUS\" :value=\"scope.row.status\" />\n        </template>\n      </el-table-column>\n      <el-table-column\n        label=\"创建时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        width=\"180\"\n        :formatter=\"dateFormatter\"\n      />\n      <el-table-column label=\"操作\" align=\"center\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n            v-hasPermi=\"['system:dept:update']\"\n          >\n            修改\n          </el-button>\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n            v-hasPermi=\"['system:dept:delete']\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <DeptForm ref=\"formRef\" @success=\"getList\" />\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getIntDictOptions } from '@/utils/dict'\nimport { dateFormatter } from '@/utils/formatTime'\nimport { handleTree } from '@/utils/tree'\nimport * as DeptApi from '@/api/system/dept'\nimport DeptForm from './DeptForm.vue'\nimport * as UserApi from '@/api/system/user'\n\ndefineOptions({ name: 'SystemDept' })\n\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst list = ref() // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 100,\n  name: undefined,\n  status: undefined\n})\nconst queryFormRef = ref() // 搜索的表单\nconst isExpandAll = ref(true) // 是否展开，默认全部展开\nconst refreshTable = ref(true) // 重新渲染表格状态\nconst userList = ref<UserApi.UserVO[]>([]) // 用户列表\n\n/** 查询部门列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await DeptApi.getDeptPage(queryParams)\n    list.value = handleTree(data)\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 展开/折叠操作 */\nconst toggleExpandAll = () => {\n  refreshTable.value = false\n  isExpandAll.value = !isExpandAll.value\n  nextTick(() => {\n    refreshTable.value = true\n  })\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryParams.pageNo = 1\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await DeptApi.deleteDept(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 初始化 **/\nonMounted(async () => {\n  await getList()\n  // 获取用户列表\n  userList.value = await UserApi.getSimpleUserList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/dict/DictTypeForm.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" :title=\"dialogTitle\">\n    <el-form\n      ref=\"formRef\"\n      v-loading=\"formLoading\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"80px\"\n    >\n      <el-form-item label=\"字典名称\" prop=\"name\">\n        <el-input v-model=\"formData.name\" placeholder=\"请输入字典名称\" />\n      </el-form-item>\n      <el-form-item label=\"字典类型\" prop=\"type\">\n        <el-input\n          v-model=\"formData.type\"\n          :disabled=\"typeof formData.id !== 'undefined'\"\n          placeholder=\"请输入参数名称\"\n        />\n      </el-form-item>\n      <el-form-item label=\"状态\" prop=\"status\">\n        <el-radio-group v-model=\"formData.status\">\n          <el-radio\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)\"\n            :key=\"dict.value\"\n            :label=\"dict.value\"\n          >\n            {{ dict.label }}\n          </el-radio>\n        </el-radio-group>\n      </el-form-item>\n      <el-form-item label=\"备注\" prop=\"remark\">\n        <el-input v-model=\"formData.remark\" placeholder=\"请输入内容\" type=\"textarea\" />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button :disabled=\"formLoading\" type=\"primary\" @click=\"submitForm\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getIntDictOptions } from '@/utils/dict'\nimport * as DictTypeApi from '@/api/system/dict/dict.type'\nimport { CommonStatusEnum } from '@/utils/constants'\n\ndefineOptions({ name: 'SystemDictTypeForm' })\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  name: '',\n  type: '',\n  status: CommonStatusEnum.ENABLE,\n  remark: ''\n})\nconst formRules = reactive({\n  name: [{ required: true, message: '字典名称不能为空', trigger: 'blur' }],\n  type: [{ required: true, message: '字典类型不能为空', trigger: 'blur' }],\n  status: [{ required: true, message: '状态不能为空', trigger: 'change' }]\n})\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await DictTypeApi.getDictType(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as DictTypeApi.DictTypeVO\n    if (formType.value === 'create') {\n      await DictTypeApi.createDictType(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await DictTypeApi.updateDictType(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    type: '',\n    name: '',\n    status: CommonStatusEnum.ENABLE,\n    remark: ''\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/dict/data/DictDataForm.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" :title=\"dialogTitle\">\n    <el-form\n      ref=\"formRef\"\n      v-loading=\"formLoading\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"80px\"\n    >\n      <el-form-item label=\"字典类型\" prop=\"type\">\n        <el-input\n          v-model=\"formData.dictType\"\n          :disabled=\"typeof formData.id !== 'undefined'\"\n          placeholder=\"请输入参数名称\"\n        />\n      </el-form-item>\n      <el-form-item label=\"数据标签\" prop=\"label\">\n        <el-input v-model=\"formData.label\" placeholder=\"请输入数据标签\" />\n      </el-form-item>\n      <el-form-item label=\"数据键值\" prop=\"value\">\n        <el-input v-model=\"formData.value\" placeholder=\"请输入数据键值\" />\n      </el-form-item>\n      <el-form-item label=\"显示排序\" prop=\"sort\">\n        <el-input-number v-model=\"formData.sort\" :min=\"0\" controls-position=\"right\" />\n      </el-form-item>\n      <el-form-item label=\"状态\" prop=\"status\">\n        <el-radio-group v-model=\"formData.status\">\n          <el-radio\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)\"\n            :key=\"dict.value\"\n            :label=\"dict.value\"\n          >\n            {{ dict.label }}\n          </el-radio>\n        </el-radio-group>\n      </el-form-item>\n      <el-form-item label=\"颜色类型\" prop=\"colorType\">\n        <el-select v-model=\"formData.colorType\">\n          <el-option\n            v-for=\"item in colorTypeOptions\"\n            :key=\"item.value\"\n            :label=\"item.label + '(' + item.value + ')'\"\n            :value=\"item.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"CSS Class\" prop=\"cssClass\">\n        <el-input v-model=\"formData.cssClass\" placeholder=\"请输入 CSS Class\" />\n      </el-form-item>\n      <el-form-item label=\"备注\" prop=\"remark\">\n        <el-input v-model=\"formData.remark\" placeholder=\"请输入内容\" type=\"textarea\" />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button :disabled=\"formLoading\" type=\"primary\" @click=\"submitForm\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getIntDictOptions } from '@/utils/dict'\nimport * as DictDataApi from '@/api/system/dict/dict.data'\nimport { CommonStatusEnum } from '@/utils/constants'\n\ndefineOptions({ name: 'SystemDictDataForm' })\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  sort: undefined,\n  label: '',\n  value: '',\n  dictType: '',\n  status: CommonStatusEnum.ENABLE,\n  colorType: '',\n  cssClass: '',\n  remark: ''\n})\nconst formRules = reactive({\n  label: [{ required: true, message: '数据标签不能为空', trigger: 'blur' }],\n  value: [{ required: true, message: '数据键值不能为空', trigger: 'blur' }],\n  sort: [{ required: true, message: '数据顺序不能为空', trigger: 'blur' }],\n  status: [{ required: true, message: '状态不能为空', trigger: 'change' }]\n})\nconst formRef = ref() // 表单 Ref\n\n// 数据标签回显样式\nconst colorTypeOptions = readonly([\n  {\n    value: 'default',\n    label: '默认'\n  },\n  {\n    value: 'primary',\n    label: '主要'\n  },\n  {\n    value: 'success',\n    label: '成功'\n  },\n  {\n    value: 'info',\n    label: '信息'\n  },\n  {\n    value: 'warning',\n    label: '警告'\n  },\n  {\n    value: 'danger',\n    label: '危险'\n  }\n])\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number, dictType?: string) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  if (dictType) {\n    formData.value.dictType = dictType\n  }\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await DictDataApi.getDictData(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as DictDataApi.DictDataVO\n    if (formType.value === 'create') {\n      await DictDataApi.createDictData(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await DictDataApi.updateDictData(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    sort: undefined,\n    label: '',\n    value: '',\n    dictType: '',\n    status: CommonStatusEnum.ENABLE,\n    colorType: '',\n    cssClass: '',\n    remark: ''\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/dict/data/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"字典名称\" prop=\"dictType\">\n        <el-select v-model=\"queryParams.dictType\" class=\"!w-240px\">\n          <el-option\n            v-for=\"item in dictTypeList\"\n            :key=\"item.type\"\n            :label=\"item.name\"\n            :value=\"item.type\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"字典标签\" prop=\"label\">\n        <el-input\n          v-model=\"queryParams.label\"\n          placeholder=\"请输入字典标签\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"状态\" prop=\"status\">\n        <el-select v-model=\"queryParams.status\" placeholder=\"数据状态\" clearable class=\"!w-240px\">\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n        <el-button\n          type=\"primary\"\n          plain\n          @click=\"openForm('create')\"\n          v-hasPermi=\"['system:dict:create']\"\n        >\n          <Icon icon=\"ep:plus\" class=\"mr-5px\" /> 新增\n        </el-button>\n        <el-button\n          type=\"success\"\n          plain\n          @click=\"handleExport\"\n          :loading=\"exportLoading\"\n          v-hasPermi=\"['system:dict:export']\"\n        >\n          <Icon icon=\"ep:download\" class=\"mr-5px\" /> 导出\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column label=\"字典编码\" align=\"center\" prop=\"id\" />\n      <el-table-column label=\"字典标签\" align=\"center\" prop=\"label\" />\n      <el-table-column label=\"字典键值\" align=\"center\" prop=\"value\" />\n      <el-table-column label=\"字典排序\" align=\"center\" prop=\"sort\" />\n      <el-table-column label=\"状态\" align=\"center\" prop=\"status\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.COMMON_STATUS\" :value=\"scope.row.status\" />\n        </template>\n      </el-table-column>\n      <el-table-column label=\"颜色类型\" align=\"center\" prop=\"colorType\" />\n      <el-table-column label=\"CSS Class\" align=\"center\" prop=\"cssClass\" />\n      <el-table-column label=\"备注\" align=\"center\" prop=\"remark\" show-overflow-tooltip />\n      <el-table-column\n        label=\"创建时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        width=\"180\"\n        :formatter=\"dateFormatter\"\n      />\n      <el-table-column label=\"操作\" align=\"center\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n            v-hasPermi=\"['system:dict:update']\"\n          >\n            修改\n          </el-button>\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n            v-hasPermi=\"['system:dict:delete']\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <DictDataForm ref=\"formRef\" @success=\"getList\" />\n</template>\n<script lang=\"ts\" setup>\nimport { getIntDictOptions, DICT_TYPE } from '@/utils/dict'\nimport { dateFormatter } from '@/utils/formatTime'\nimport download from '@/utils/download'\nimport * as DictDataApi from '@/api/system/dict/dict.data'\nimport * as DictTypeApi from '@/api/system/dict/dict.type'\nimport DictDataForm from './DictDataForm.vue'\n\ndefineOptions({ name: 'SystemDictData' })\n\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\nconst route = useRoute() // 路由\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  label: '',\n  status: undefined,\n  dictType: route.params.dictType\n})\nconst queryFormRef = ref() // 搜索的表单\nconst exportLoading = ref(false) // 导出的加载中\nconst dictTypeList = ref<DictTypeApi.DictTypeVO[]>() // 字典类型的列表\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await DictDataApi.getDictDataPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id, queryParams.dictType)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await DictDataApi.deleteDictData(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 导出按钮操作 */\nconst handleExport = async () => {\n  try {\n    // 导出的二次确认\n    await message.exportConfirm()\n    // 发起导出\n    exportLoading.value = true\n    const data = await DictDataApi.exportDictData(queryParams)\n    download.excel(data, '字典数据.xls')\n  } catch {\n  } finally {\n    exportLoading.value = false\n  }\n}\n\n/** 初始化 **/\nonMounted(async () => {\n  await getList()\n  // 查询字典（精简)列表\n  dictTypeList.value = await DictTypeApi.getSimpleDictTypeList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/dict/index.vue",
    "content": "<template>\n  <!-- 搜索工作栏 -->\n  <ContentWrap>\n    <el-form\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      :model=\"queryParams\"\n      class=\"-mb-15px\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"字典名称\" prop=\"name\">\n        <el-input\n          v-model=\"queryParams.name\"\n          class=\"!w-240px\"\n          clearable\n          placeholder=\"请输入字典名称\"\n          @keyup.enter=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"字典类型\" prop=\"type\">\n        <el-input\n          v-model=\"queryParams.type\"\n          class=\"!w-240px\"\n          clearable\n          placeholder=\"请输入字典类型\"\n          @keyup.enter=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"状态\" prop=\"status\">\n        <el-select\n          v-model=\"queryParams.status\"\n          class=\"!w-240px\"\n          clearable\n          placeholder=\"请选择字典状态\"\n        >\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"创建时间\" prop=\"createTime\">\n        <el-date-picker\n          v-model=\"queryParams.createTime\"\n          :default-time=\"[new Date('1 00:00:00'), new Date('1 23:59:59')]\"\n          class=\"!w-240px\"\n          end-placeholder=\"结束日期\"\n          start-placeholder=\"开始日期\"\n          type=\"daterange\"\n          value-format=\"YYYY-MM-DD HH:mm:ss\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\">\n          <Icon class=\"mr-5px\" icon=\"ep:search\" />\n          搜索\n        </el-button>\n        <el-button @click=\"resetQuery\">\n          <Icon class=\"mr-5px\" icon=\"ep:refresh\" />\n          重置\n        </el-button>\n        <el-button\n          v-hasPermi=\"['system:dict:create']\"\n          plain\n          type=\"primary\"\n          @click=\"openForm('create')\"\n        >\n          <Icon class=\"mr-5px\" icon=\"ep:plus\" />\n          新增\n        </el-button>\n        <el-button\n          v-hasPermi=\"['system:dict:export']\"\n          :loading=\"exportLoading\"\n          plain\n          type=\"success\"\n          @click=\"handleExport\"\n        >\n          <Icon class=\"mr-5px\" icon=\"ep:download\" />\n          导出\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column align=\"center\" label=\"字典编号\" prop=\"id\" />\n      <el-table-column align=\"center\" label=\"字典名称\" prop=\"name\" show-overflow-tooltip />\n      <el-table-column align=\"center\" label=\"字典类型\" prop=\"type\" width=\"300\" />\n      <el-table-column align=\"center\" label=\"状态\" prop=\"status\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.COMMON_STATUS\" :value=\"scope.row.status\" />\n        </template>\n      </el-table-column>\n      <el-table-column align=\"center\" label=\"备注\" prop=\"remark\" />\n      <el-table-column\n        :formatter=\"dateFormatter\"\n        align=\"center\"\n        label=\"创建时间\"\n        prop=\"createTime\"\n        width=\"180\"\n      />\n      <el-table-column align=\"center\" label=\"操作\">\n        <template #default=\"scope\">\n          <el-button\n            v-hasPermi=\"['system:dict:update']\"\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n          >\n            修改\n          </el-button>\n          <router-link :to=\"'/dict/type/data/' + scope.row.type\">\n            <el-button link type=\"primary\">数据</el-button>\n          </router-link>\n          <el-button\n            v-hasPermi=\"['system:dict:delete']\"\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      v-model:limit=\"queryParams.pageSize\"\n      v-model:page=\"queryParams.pageNo\"\n      :total=\"total\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <DictTypeForm ref=\"formRef\" @success=\"getList\" />\n</template>\n\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getIntDictOptions } from '@/utils/dict'\nimport { dateFormatter } from '@/utils/formatTime'\nimport * as DictTypeApi from '@/api/system/dict/dict.type'\nimport DictTypeForm from './DictTypeForm.vue'\nimport download from '@/utils/download'\n\ndefineOptions({ name: 'SystemDictType' })\n\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 字典表格数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  name: '',\n  type: '',\n  status: undefined,\n  createTime: []\n})\nconst queryFormRef = ref() // 搜索的表单\nconst exportLoading = ref(false) // 导出的加载中\n\n/** 查询字典类型列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await DictTypeApi.getDictTypePage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await DictTypeApi.deleteDictType(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 导出按钮操作 */\nconst handleExport = async () => {\n  try {\n    // 导出的二次确认\n    await message.exportConfirm()\n    // 发起导出\n    exportLoading.value = true\n    const data = await DictTypeApi.exportDictType(queryParams)\n    download.excel(data, '字典类型.xls')\n  } catch {\n  } finally {\n    exportLoading.value = false\n  }\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/loginlog/LoginLogDetail.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" title=\"详情\" width=\"800\">\n    <el-descriptions :column=\"1\" border>\n      <el-descriptions-item label=\"日志编号\" min-width=\"120\">\n        {{ detailData.id }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"操作类型\">\n        <dict-tag :type=\"DICT_TYPE.SYSTEM_LOGIN_TYPE\" :value=\"detailData.logType\" />\n      </el-descriptions-item>\n      <el-descriptions-item label=\"用户名称\">\n        {{ detailData.username }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"登录地址\">\n        {{ detailData.userIp }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"浏览器\">\n        {{ detailData.userAgent }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"登陆结果\">\n        <dict-tag :type=\"DICT_TYPE.SYSTEM_LOGIN_RESULT\" :value=\"detailData.result\" />\n      </el-descriptions-item>\n      <el-descriptions-item label=\"登录日期\">\n        {{ formatDate(detailData.createTime) }}\n      </el-descriptions-item>\n    </el-descriptions>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE } from '@/utils/dict'\nimport { formatDate } from '@/utils/formatTime'\nimport * as LoginLogApi from '@/api/system/loginLog'\n\ndefineOptions({ name: 'SystemLoginLogDetail' })\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst detailLoading = ref(false) // 表单的加载中\nconst detailData = ref({} as LoginLogApi.LoginLogVO) // 详情数据\n\n/** 打开弹窗 */\nconst open = async (data: LoginLogApi.LoginLogVO) => {\n  dialogVisible.value = true\n  // 设置数据\n  detailLoading.value = true\n  try {\n    detailData.value = data\n  } finally {\n    detailLoading.value = false\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/loginlog/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <!-- 搜索工作栏 -->\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"用户名称\" prop=\"username\">\n        <el-input\n          v-model=\"queryParams.username\"\n          placeholder=\"请输入用户名称\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"登录地址\" prop=\"userIp\">\n        <el-input\n          v-model=\"queryParams.userIp\"\n          placeholder=\"请输入登录地址\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"登录日期\" prop=\"createTime\">\n        <el-date-picker\n          v-model=\"queryParams.createTime\"\n          value-format=\"YYYY-MM-DD HH:mm:ss\"\n          type=\"daterange\"\n          start-placeholder=\"开始日期\"\n          end-placeholder=\"结束日期\"\n          :default-time=\"[new Date('1 00:00:00'), new Date('1 23:59:59')]\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n        <el-button\n          type=\"success\"\n          plain\n          @click=\"handleExport\"\n          :loading=\"exportLoading\"\n          v-hasPermi=\"['infra:login-log:export']\"\n        >\n          <Icon icon=\"ep:download\" class=\"mr-5px\" /> 导出\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column label=\"日志编号\" align=\"center\" prop=\"id\" />\n      <el-table-column label=\"操作类型\" align=\"center\" prop=\"logType\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.SYSTEM_LOGIN_TYPE\" :value=\"scope.row.logType\" />\n        </template>\n      </el-table-column>\n      <el-table-column label=\"用户名称\" align=\"center\" prop=\"username\" width=\"180\" />\n      <el-table-column label=\"登录地址\" align=\"center\" prop=\"userIp\" width=\"180\" />\n      <el-table-column label=\"浏览器\" align=\"center\" prop=\"userAgent\" />\n      <el-table-column label=\"登陆结果\" align=\"center\" prop=\"result\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.SYSTEM_LOGIN_RESULT\" :value=\"scope.row.result\" />\n        </template>\n      </el-table-column>\n      <el-table-column\n        label=\"登录日期\"\n        align=\"center\"\n        prop=\"createTime\"\n        width=\"180\"\n        :formatter=\"dateFormatter\"\n      />\n      <el-table-column label=\"操作\" align=\"center\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openDetail(scope.row)\"\n            v-hasPermi=\"['infra:login-log:query']\"\n          >\n            详情\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：详情 -->\n  <LoginLogDetail ref=\"detailRef\" />\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE } from '@/utils/dict'\nimport { dateFormatter } from '@/utils/formatTime'\nimport download from '@/utils/download'\nimport * as LoginLogApi from '@/api/system/loginLog'\nimport LoginLogDetail from './LoginLogDetail.vue'\n\ndefineOptions({ name: 'SystemLoginLog' })\n\nconst message = useMessage() // 消息弹窗\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  username: undefined,\n  userIp: undefined,\n  createTime: []\n})\nconst queryFormRef = ref() // 搜索的表单\nconst exportLoading = ref(false) // 导出的加载中\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await LoginLogApi.getLoginLogPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 详情操作 */\nconst detailRef = ref()\nconst openDetail = (data: LoginLogApi.LoginLogVO) => {\n  detailRef.value.open(data)\n}\n\n/** 导出按钮操作 */\nconst handleExport = async () => {\n  try {\n    // 导出的二次确认\n    await message.exportConfirm()\n    // 发起导出\n    exportLoading.value = true\n    const data = await LoginLogApi.exportLoginLog(queryParams)\n    download.excel(data, '登录日志.xls')\n  } catch {\n  } finally {\n    exportLoading.value = false\n  }\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/mail/account/MailAccountDetail.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" title=\"详情\">\n    <Descriptions :data=\"detailData\" :schema=\"allSchemas.detailSchema\" />\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport * as MailAccountApi from '@/api/system/mail/account'\nimport { allSchemas } from './account.data'\n\ndefineOptions({ name: 'SystemMailAccountDetail' })\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst detailLoading = ref(false) // 表单的加载中\nconst detailData = ref() // 详情数据\n\n/** 打开弹窗 */\nconst open = async (id: number) => {\n  dialogVisible.value = true\n  // 设置数据\n  detailLoading.value = true\n  try {\n    detailData.value = await MailAccountApi.getMailAccount(id)\n  } finally {\n    detailLoading.value = false\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/mail/account/MailAccountForm.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" :title=\"dialogTitle\">\n    <Form ref=\"formRef\" v-loading=\"formLoading\" :rules=\"rules\" :schema=\"allSchemas.formSchema\" />\n    <template #footer>\n      <el-button :disabled=\"formLoading\" type=\"primary\" @click=\"submitForm\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport * as MailAccountApi from '@/api/system/mail/account'\nimport { allSchemas, rules } from './account.data'\n\ndefineOptions({ name: 'SystemMailAccountForm' })\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      const data = await MailAccountApi.getMailAccount(id)\n      formRef.value.setValues(data)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.getElFormRef().validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formRef.value.formModel as MailAccountApi.MailAccountVO\n    if (formType.value === 'create') {\n      await MailAccountApi.createMailAccount(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await MailAccountApi.updateMailAccount(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/mail/account/account.data.ts",
    "content": "import type { CrudSchema } from '@/hooks/web/useCrudSchemas'\nimport { dateFormatter } from '@/utils/formatTime'\nconst { t } = useI18n() // 国际化\n\n// 表单校验\nexport const rules = reactive({\n  mail: [\n    { required: true, message: t('profile.rules.mail'), trigger: 'blur' },\n    {\n      type: 'email',\n      message: t('profile.rules.truemail'),\n      trigger: ['blur', 'change']\n    }\n  ],\n  username: [required],\n  password: [required],\n  host: [required],\n  port: [required],\n  sslEnable: [required],\n  starttlsEnable: [required]\n})\n\nconst crudSchemas = reactive<CrudSchema[]>([\n  {\n    label: '邮箱',\n    field: 'mail',\n    isSearch: true\n  },\n  {\n    label: '用户名',\n    field: 'username',\n    isSearch: true\n  },\n  {\n    label: '密码',\n    field: 'password',\n    isTable: false\n  },\n  {\n    label: 'SMTP 服务器域名',\n    field: 'host'\n  },\n  {\n    label: 'SMTP 服务器端口',\n    field: 'port',\n    form: {\n      component: 'InputNumber',\n      value: 465\n    }\n  },\n  {\n    label: '是否开启 SSL',\n    field: 'sslEnable',\n    dictType: DICT_TYPE.INFRA_BOOLEAN_STRING,\n    dictClass: 'boolean',\n    form: {\n      component: 'Radio'\n    }\n  },\n  {\n    label: '是否开启 STARTTLS',\n    field: 'starttlsEnable',\n    dictType: DICT_TYPE.INFRA_BOOLEAN_STRING,\n    dictClass: 'boolean',\n    form: {\n      component: 'Radio'\n    }\n  },\n  {\n    label: '创建时间',\n    field: 'createTime',\n    isForm: false,\n    formatter: dateFormatter,\n    detail: {\n      dateFormat: 'YYYY-MM-DD HH:mm:ss'\n    }\n  },\n  {\n    label: '操作',\n    field: 'action',\n    isForm: false,\n    isDetail: false\n  }\n])\nexport const { allSchemas } = useCrudSchemas(crudSchemas)\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/mail/account/index.vue",
    "content": "<template>\n  <!-- 搜索工作栏 -->\n  <ContentWrap>\n    <Search :schema=\"allSchemas.searchSchema\" @search=\"setSearchParams\" @reset=\"setSearchParams\">\n      <!-- 新增等操作按钮 -->\n      <template #actionMore>\n        <el-button\n          type=\"primary\"\n          plain\n          @click=\"openForm('create')\"\n          v-hasPermi=\"['system:mail-account:create']\"\n        >\n          <Icon icon=\"ep:plus\" class=\"mr-5px\" /> 新增\n        </el-button>\n      </template>\n    </Search>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <Table\n      :columns=\"allSchemas.tableColumns\"\n      :data=\"tableObject.tableList\"\n      :loading=\"tableObject.loading\"\n      :pagination=\"{\n        total: tableObject.total\n      }\"\n      v-model:pageSize=\"tableObject.pageSize\"\n      v-model:currentPage=\"tableObject.currentPage\"\n    >\n      <template #action=\"{ row }\">\n        <el-button\n          link\n          type=\"primary\"\n          @click=\"openForm('update', row.id)\"\n          v-hasPermi=\"['system:mail-account:update']\"\n        >\n          编辑\n        </el-button>\n        <el-button\n          link\n          type=\"primary\"\n          @click=\"openDetail(row.id)\"\n          v-hasPermi=\"['system:mail-account:query']\"\n        >\n          详情\n        </el-button>\n        <el-button\n          link\n          type=\"danger\"\n          v-hasPermi=\"['system:mail-account:delete']\"\n          @click=\"handleDelete(row.id)\"\n        >\n          删除\n        </el-button>\n      </template>\n    </Table>\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <MailAccountForm ref=\"formRef\" @success=\"getList\" />\n  <!-- 详情弹窗 -->\n  <MailAccountDetail ref=\"detailRef\" />\n</template>\n<script lang=\"ts\" setup>\nimport { allSchemas } from './account.data'\nimport * as MailAccountApi from '@/api/system/mail/account'\nimport MailAccountForm from './MailAccountForm.vue'\nimport MailAccountDetail from './MailAccountDetail.vue'\n\ndefineOptions({ name: 'SystemMailAccount' })\n\n// tableObject：表格的属性对象，可获得分页大小、条数等属性\n// tableMethods：表格的操作对象，可进行获得分页、删除记录等操作\nconst { tableObject, tableMethods } = useTable({\n  getListApi: MailAccountApi.getMailAccountPage, // 分页接口\n  delListApi: MailAccountApi.deleteMailAccount // 删除接口\n})\n// 获得表格的各种操作\nconst { getList, setSearchParams } = tableMethods\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 详情操作 */\nconst detailRef = ref()\nconst openDetail = (id: number) => {\n  detailRef.value.open(id)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = (id: number) => {\n  tableMethods.delList(id, false)\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/mail/log/MailLogDetail.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" :max-height=\"500\" :scroll=\"true\" title=\"详情\">\n    <Descriptions :data=\"detailData\" :schema=\"allSchemas.detailSchema\">\n      <!-- 展示 HTML 内容 -->\n      <template #templateContent=\"{ row }\">\n        <div v-dompurify-html=\"row.templateContent\"></div>\n      </template>\n    </Descriptions>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport * as MailLogApi from '@/api/system/mail/log'\nimport { allSchemas } from './log.data'\n\ndefineOptions({ name: 'SystemMailLogDetail' })\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst detailLoading = ref(false) // 表单的加载中\nconst detailData = ref() // 详情数据\n\n/** 打开弹窗 */\nconst open = async (id: number) => {\n  dialogVisible.value = true\n  // 设置数据\n  detailLoading.value = true\n  try {\n    detailData.value = await MailLogApi.getMailLog(id)\n  } finally {\n    detailLoading.value = false\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/mail/log/index.vue",
    "content": "<template>\n  <!-- 搜索工作栏 -->\n  <ContentWrap>\n    <Search :schema=\"allSchemas.searchSchema\" @search=\"setSearchParams\" @reset=\"setSearchParams\" />\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <Table\n      :columns=\"allSchemas.tableColumns\"\n      :data=\"tableObject.tableList\"\n      :loading=\"tableObject.loading\"\n      :pagination=\"{\n        total: tableObject.total\n      }\"\n      v-model:pageSize=\"tableObject.pageSize\"\n      v-model:currentPage=\"tableObject.currentPage\"\n    >\n      <template #action=\"{ row }\">\n        <el-button\n          link\n          type=\"primary\"\n          @click=\"openDetail(row.id)\"\n          v-hasPermi=\"['system:mail-log:query']\"\n        >\n          详情\n        </el-button>\n      </template>\n    </Table>\n  </ContentWrap>\n\n  <!-- 表单弹窗：详情 -->\n  <mail-log-detail ref=\"detailRef\" />\n</template>\n<script lang=\"ts\" setup>\nimport { allSchemas } from './log.data'\nimport * as MailLogApi from '@/api/system/mail/log'\nimport MailLogDetail from './MailLogDetail.vue'\n\ndefineOptions({ name: 'SystemMailLog' })\n\n// tableObject：表格的属性对象，可获得分页大小、条数等属性\n// tableMethods：表格的操作对象，可进行获得分页、删除记录等操作\nconst { tableObject, tableMethods } = useTable({\n  getListApi: MailLogApi.getMailLogPage // 分页接口\n})\n// 获得表格的各种操作\nconst { getList, setSearchParams } = tableMethods\n\n/** 详情操作 */\nconst detailRef = ref()\nconst openDetail = (id: number) => {\n  detailRef.value.open(id)\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/mail/log/log.data.ts",
    "content": "import type { CrudSchema } from '@/hooks/web/useCrudSchemas'\nimport { dateFormatter } from '@/utils/formatTime'\nimport * as MailAccountApi from '@/api/system/mail/account'\n\n// 邮箱账号的列表\nconst accountList = await MailAccountApi.getSimpleMailAccountList()\n\nconst crudSchemas = reactive<CrudSchema[]>([\n  {\n    label: '编号',\n    field: 'id'\n  },\n  {\n    label: '发送时间',\n    field: 'sendTime',\n    formatter: dateFormatter,\n    search: {\n      show: true,\n      component: 'DatePicker',\n      componentProps: {\n        valueFormat: 'YYYY-MM-DD HH:mm:ss',\n        type: 'daterange',\n        defaultTime: [new Date('1 00:00:00'), new Date('1 23:59:59')]\n      }\n    },\n    detail: {\n      dateFormat: 'YYYY-MM-DD HH:mm:ss'\n    }\n  },\n  {\n    label: '接收邮箱',\n    field: 'toMail'\n  },\n  {\n    label: '用户编号',\n    field: 'userId',\n    isSearch: true,\n    isTable: false\n  },\n  {\n    label: '用户类型',\n    field: 'userType',\n    dictType: DICT_TYPE.USER_TYPE,\n    dictClass: 'number',\n    isSearch: true,\n    isTable: false\n  },\n  {\n    label: '邮件标题',\n    field: 'templateTitle'\n  },\n  {\n    label: '邮件内容',\n    field: 'templateContent',\n    isTable: false\n  },\n  {\n    label: '邮箱参数',\n    field: 'templateParams',\n    isTable: false\n  },\n  {\n    label: '发送状态',\n    field: 'sendStatus',\n    dictType: DICT_TYPE.SYSTEM_MAIL_SEND_STATUS,\n    dictClass: 'string',\n    isSearch: true\n  },\n  {\n    label: '邮箱账号',\n    field: 'accountId',\n    isTable: false,\n    search: {\n      show: true,\n      component: 'Select',\n      api: () => accountList,\n      componentProps: {\n        optionsAlias: {\n          labelField: 'mail',\n          valueField: 'id'\n        }\n      }\n    }\n  },\n  {\n    label: '发送邮箱地址',\n    field: 'fromMail',\n    table: {\n      label: '邮箱账号'\n    }\n  },\n  {\n    label: '模板编号',\n    field: 'templateId',\n    isSearch: true\n  },\n  {\n    label: '模板编码',\n    field: 'templateCode',\n    isTable: false\n  },\n  {\n    label: '模版发送人名称',\n    field: 'templateNickname',\n    isTable: false\n  },\n  {\n    label: '发送返回的消息编号',\n    field: 'sendMessageId',\n    isTable: false\n  },\n  {\n    label: '发送异常',\n    field: 'sendException',\n    isTable: false\n  },\n  {\n    label: '创建时间',\n    field: 'createTime',\n    isTable: false,\n    formatter: dateFormatter,\n    detail: {\n      dateFormat: 'YYYY-MM-DD HH:mm:ss'\n    }\n  },\n  {\n    label: '操作',\n    field: 'action',\n    isDetail: false\n  }\n])\nexport const { allSchemas } = useCrudSchemas(crudSchemas)\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/mail/template/MailTemplateForm.vue",
    "content": "<template>\n  <Dialog\n    v-model=\"dialogVisible\"\n    :max-height=\"500\"\n    :scroll=\"true\"\n    :title=\"dialogTitle\"\n    :width=\"800\"\n  >\n    <Form ref=\"formRef\" v-loading=\"formLoading\" :rules=\"rules\" :schema=\"allSchemas.formSchema\" />\n    <template #footer>\n      <el-button :disabled=\"formLoading\" type=\"primary\" @click=\"submitForm\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport * as MailTemplateApi from '@/api/system/mail/template'\nimport { allSchemas, rules } from './template.data'\n\ndefineOptions({ name: 'SystemMailTemplateForm' })\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      const data = await MailTemplateApi.getMailTemplate(id)\n      formRef.value.setValues(data)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.getElFormRef().validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formRef.value.formModel as MailTemplateApi.MailTemplateVO\n    if (formType.value === 'create') {\n      await MailTemplateApi.createMailTemplate(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await MailTemplateApi.updateMailTemplate(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/mail/template/MailTemplateSendForm.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" title=\"测试\">\n    <el-form\n      ref=\"formRef\"\n      v-loading=\"formLoading\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"120px\"\n    >\n      <el-form-item label=\"模板内容\" prop=\"content\">\n        <Editor :model-value=\"formData.content\" height=\"150px\" readonly />\n      </el-form-item>\n      <el-form-item label=\"收件邮箱\" prop=\"mail\">\n        <el-input v-model=\"formData.mail\" placeholder=\"请输入收件邮箱\" />\n      </el-form-item>\n      <el-form-item\n        v-for=\"param in formData.params\"\n        :key=\"param\"\n        :label=\"'参数 {' + param + '}'\"\n        :prop=\"'templateParams.' + param\"\n      >\n        <el-input\n          v-model=\"formData.templateParams[param]\"\n          :placeholder=\"'请输入 ' + param + ' 参数'\"\n        />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button :disabled=\"formLoading\" type=\"primary\" @click=\"submitForm\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport * as MailTemplateApi from '@/api/system/mail/template'\n\ndefineOptions({ name: 'SystemMailTemplateSendForm' })\n\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formData = ref({\n  content: '',\n  params: {},\n  mail: '',\n  templateCode: '',\n  templateParams: new Map()\n})\nconst formRules = reactive({\n  mail: [{ required: true, message: '邮箱不能为空', trigger: 'blur' }],\n  templateCode: [{ required: true, message: '模版编号不能为空', trigger: 'blur' }],\n  templateParams: {}\n})\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async (id: number) => {\n  dialogVisible.value = true\n  resetForm()\n  // 设置数据\n  formLoading.value = true\n  try {\n    const data = await MailTemplateApi.getMailTemplate(id)\n    // 设置动态表单\n    formData.value.content = data.content\n    formData.value.params = data.params\n    formData.value.templateCode = data.code\n    formData.value.templateParams = data.params.reduce((obj, item) => {\n      obj[item] = '' // 给每个动态属性赋值，避免无法读取\n      return obj\n    }, {})\n    formRules.templateParams = data.params.reduce((obj, item) => {\n      obj[item] = { required: true, message: '参数 ' + item + ' 不能为空', trigger: 'blur' }\n      return obj\n    }, {})\n  } finally {\n    formLoading.value = false\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as MailTemplateApi.MailSendReqVO\n    const logId = await MailTemplateApi.sendMail(data)\n    if (logId) {\n      message.success('提交发送成功！发送结果，见发送日志编号：' + logId)\n    }\n    dialogVisible.value = false\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    content: '',\n    params: {},\n    mail: '',\n    templateCode: '',\n    templateParams: new Map()\n  }\n  formRules.templateParams = {}\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/mail/template/index.vue",
    "content": "<template>\n  <!-- 搜索工作栏 -->\n  <ContentWrap>\n    <Search :schema=\"allSchemas.searchSchema\" @search=\"setSearchParams\" @reset=\"setSearchParams\">\n      <!-- 新增等操作按钮 -->\n      <template #actionMore>\n        <el-button\n          type=\"primary\"\n          plain\n          @click=\"openForm('create')\"\n          v-hasPermi=\"['system:mail-template:create']\"\n        >\n          <Icon icon=\"ep:plus\" class=\"mr-5px\" /> 新增\n        </el-button>\n      </template>\n    </Search>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <Table\n      :columns=\"allSchemas.tableColumns\"\n      :data=\"tableObject.tableList\"\n      :loading=\"tableObject.loading\"\n      :pagination=\"{\n        total: tableObject.total\n      }\"\n      v-model:pageSize=\"tableObject.pageSize\"\n      v-model:currentPage=\"tableObject.currentPage\"\n    >\n      <template #action=\"{ row }\">\n        <el-button\n          link\n          type=\"primary\"\n          @click=\"openSendForm(row.id)\"\n          v-hasPermi=\"['system:mail-template:send-mail']\"\n        >\n          测试\n        </el-button>\n        <el-button\n          link\n          type=\"primary\"\n          @click=\"openForm('update', row.id)\"\n          v-hasPermi=\"['system:mail-template:update']\"\n        >\n          编辑\n        </el-button>\n        <el-button\n          link\n          type=\"danger\"\n          v-hasPermi=\"['system:mail-template:delete']\"\n          @click=\"handleDelete(row.id)\"\n        >\n          删除\n        </el-button>\n      </template>\n    </Table>\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <MailTemplateForm ref=\"formRef\" @success=\"getList\" />\n\n  <!-- 表单弹窗：发送测试 -->\n  <MailTemplateSendForm ref=\"sendFormRef\" />\n</template>\n<script lang=\"ts\" setup>\nimport { allSchemas } from './template.data'\nimport * as MailTemplateApi from '@/api/system/mail/template'\nimport MailTemplateForm from './MailTemplateForm.vue'\nimport MailTemplateSendForm from './MailTemplateSendForm.vue'\n\ndefineOptions({ name: 'SystemMailTemplate' })\n\n// tableObject：表格的属性对象，可获得分页大小、条数等属性\n// tableMethods：表格的操作对象，可进行获得分页、删除记录等操作\nconst { tableObject, tableMethods } = useTable({\n  getListApi: MailTemplateApi.getMailTemplatePage, // 分页接口\n  delListApi: MailTemplateApi.deleteMailTemplate // 删除接口\n})\n// 获得表格的各种操作\nconst { getList, setSearchParams } = tableMethods\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = (id: number) => {\n  tableMethods.delList(id, false)\n}\n\n/** 发送测试操作 */\nconst sendFormRef = ref()\nconst openSendForm = (id: number) => {\n  sendFormRef.value.open(id)\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/mail/template/template.data.ts",
    "content": "import type { CrudSchema } from '@/hooks/web/useCrudSchemas'\nimport { dateFormatter } from '@/utils/formatTime'\nimport { TableColumn } from '@/types/table'\nimport * as MailAccountApi from '@/api/system/mail/account'\n\n// 邮箱账号的列表\nconst accountList = await MailAccountApi.getSimpleMailAccountList()\n\n// 表单校验\nexport const rules = reactive({\n  name: [required],\n  code: [required],\n  accountId: [required],\n  label: [required],\n  content: [required],\n  params: [required],\n  status: [required]\n})\n\nconst crudSchemas = reactive<CrudSchema[]>([\n  {\n    label: '模板编码',\n    field: 'code',\n    isSearch: true\n  },\n  {\n    label: '模板名称',\n    field: 'name',\n    isSearch: true\n  },\n  {\n    label: '模板标题',\n    field: 'title'\n  },\n  {\n    label: '模板内容',\n    field: 'content',\n    form: {\n      component: 'Editor',\n      componentProps: {\n        valueHtml: '',\n        height: 200\n      }\n    }\n  },\n  {\n    label: '邮箱账号',\n    field: 'accountId',\n    width: '200px',\n    formatter: (_: Recordable, __: TableColumn, cellValue: number) => {\n      return accountList.find((account) => account.id === cellValue)?.mail\n    },\n    search: {\n      show: true,\n      component: 'Select',\n      api: () => accountList,\n      componentProps: {\n        optionsAlias: {\n          labelField: 'mail',\n          valueField: 'id'\n        }\n      }\n    },\n    form: {\n      component: 'Select',\n      api: () => accountList,\n      componentProps: {\n        optionsAlias: {\n          labelField: 'mail',\n          valueField: 'id'\n        }\n      }\n    }\n  },\n  {\n    label: '发送人名称',\n    field: 'nickname'\n  },\n  {\n    label: '开启状态',\n    field: 'status',\n    isSearch: true,\n    dictType: DICT_TYPE.COMMON_STATUS,\n    dictClass: 'number'\n  },\n  {\n    label: '备注',\n    field: 'remark',\n    isTable: false\n  },\n  {\n    label: '创建时间',\n    field: 'createTime',\n    isForm: false,\n    formatter: dateFormatter,\n    search: {\n      show: true,\n      component: 'DatePicker',\n      componentProps: {\n        valueFormat: 'YYYY-MM-DD HH:mm:ss',\n        type: 'daterange',\n        defaultTime: [new Date('1 00:00:00'), new Date('1 23:59:59')]\n      }\n    }\n  },\n  {\n    label: '操作',\n    field: 'action',\n    isForm: false\n  }\n])\nexport const { allSchemas } = useCrudSchemas(crudSchemas)\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/menu/MenuForm.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" :title=\"dialogTitle\">\n    <el-form\n      ref=\"formRef\"\n      v-loading=\"formLoading\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"100px\"\n    >\n      <el-form-item label=\"上级菜单\">\n        <el-tree-select\n          v-model=\"formData.parentId\"\n          :data=\"menuTree\"\n          :default-expanded-keys=\"[0]\"\n          :props=\"defaultProps\"\n          check-strictly\n          node-key=\"id\"\n        />\n      </el-form-item>\n      <el-form-item label=\"菜单名称\" prop=\"name\">\n        <el-input v-model=\"formData.name\" clearable placeholder=\"请输入菜单名称\" />\n      </el-form-item>\n      <el-form-item label=\"菜单类型\" prop=\"type\">\n        <el-radio-group v-model=\"formData.type\">\n          <el-radio-button\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.SYSTEM_MENU_TYPE)\"\n            :key=\"dict.label\"\n            :label=\"dict.value\"\n          >\n            {{ dict.label }}\n          </el-radio-button>\n        </el-radio-group>\n      </el-form-item>\n      <el-form-item v-if=\"formData.type !== 3\" label=\"菜单图标\">\n        <IconSelect v-model=\"formData.icon\" clearable />\n      </el-form-item>\n      <el-form-item v-if=\"formData.type !== 3\" label=\"路由地址\" prop=\"path\">\n        <template #label>\n          <Tooltip\n            message=\"访问的路由地址，如：`user`。如需外网地址时，则以 `http(s)://` 开头\"\n            title=\"路由地址\"\n          />\n        </template>\n        <el-input v-model=\"formData.path\" clearable placeholder=\"请输入路由地址\" />\n      </el-form-item>\n      <el-form-item v-if=\"formData.type === 2\" label=\"组件地址\" prop=\"component\">\n        <el-input v-model=\"formData.component\" clearable placeholder=\"例如说：system/user/index\" />\n      </el-form-item>\n      <el-form-item v-if=\"formData.type === 2\" label=\"组件名字\" prop=\"componentName\">\n        <el-input v-model=\"formData.componentName\" clearable placeholder=\"例如说：SystemUser\" />\n      </el-form-item>\n      <el-form-item v-if=\"formData.type !== 1\" label=\"权限标识\" prop=\"permission\">\n        <template #label>\n          <Tooltip\n            message=\"Controller 方法上的权限字符，如：@PreAuthorize(`@ss.hasPermission('system:user:list')`)\"\n            title=\"权限标识\"\n          />\n        </template>\n        <el-input v-model=\"formData.permission\" clearable placeholder=\"请输入权限标识\" />\n      </el-form-item>\n      <el-form-item label=\"显示排序\" prop=\"sort\">\n        <el-input-number v-model=\"formData.sort\" :min=\"0\" clearable controls-position=\"right\" />\n      </el-form-item>\n      <el-form-item label=\"菜单状态\" prop=\"status\">\n        <el-radio-group v-model=\"formData.status\">\n          <el-radio\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)\"\n            :key=\"dict.label\"\n            :label=\"dict.value\"\n          >\n            {{ dict.label }}\n          </el-radio>\n        </el-radio-group>\n      </el-form-item>\n      <el-form-item v-if=\"formData.type !== 3\" label=\"显示状态\" prop=\"visible\">\n        <template #label>\n          <Tooltip message=\"选择隐藏时，路由将不会出现在侧边栏，但仍然可以访问\" title=\"显示状态\" />\n        </template>\n        <el-radio-group v-model=\"formData.visible\">\n          <el-radio key=\"true\" :label=\"true\" border>显示</el-radio>\n          <el-radio key=\"false\" :label=\"false\" border>隐藏</el-radio>\n        </el-radio-group>\n      </el-form-item>\n      <el-form-item v-if=\"formData.type !== 3\" label=\"总是显示\" prop=\"alwaysShow\">\n        <template #label>\n          <Tooltip\n            message=\"选择不是时，当该菜单只有一个子菜单时，不展示自己，直接展示子菜单\"\n            title=\"总是显示\"\n          />\n        </template>\n        <el-radio-group v-model=\"formData.alwaysShow\">\n          <el-radio key=\"true\" :label=\"true\" border>总是</el-radio>\n          <el-radio key=\"false\" :label=\"false\" border>不是</el-radio>\n        </el-radio-group>\n      </el-form-item>\n      <el-form-item v-if=\"formData.type === 2\" label=\"缓存状态\" prop=\"keepAlive\">\n        <template #label>\n          <Tooltip\n            message=\"选择缓存时，则会被 `keep-alive` 缓存，必须填写「组件名称」字段\"\n            title=\"缓存状态\"\n          />\n        </template>\n        <el-radio-group v-model=\"formData.keepAlive\">\n          <el-radio key=\"true\" :label=\"true\" border>缓存</el-radio>\n          <el-radio key=\"false\" :label=\"false\" border>不缓存</el-radio>\n        </el-radio-group>\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button :disabled=\"formLoading\" type=\"primary\" @click=\"submitForm\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getIntDictOptions } from '@/utils/dict'\nimport * as MenuApi from '@/api/system/menu'\nimport { CACHE_KEY, useCache } from '@/hooks/web/useCache'\nimport { CommonStatusEnum, SystemMenuTypeEnum } from '@/utils/constants'\nimport { defaultProps, handleTree } from '@/utils/tree'\n\ndefineOptions({ name: 'SystemMenuForm' })\n\nconst { wsCache } = useCache()\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  name: '',\n  permission: '',\n  type: SystemMenuTypeEnum.DIR,\n  sort: Number(undefined),\n  parentId: 0,\n  path: '',\n  icon: '',\n  component: '',\n  componentName: '',\n  status: CommonStatusEnum.ENABLE,\n  visible: true,\n  keepAlive: true,\n  alwaysShow: true\n})\nconst formRules = reactive({\n  name: [{ required: true, message: '菜单名称不能为空', trigger: 'blur' }],\n  sort: [{ required: true, message: '菜单顺序不能为空', trigger: 'blur' }],\n  path: [{ required: true, message: '路由地址不能为空', trigger: 'blur' }],\n  status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number, parentId?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  if (parentId) {\n    formData.value.parentId = parentId\n  }\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await MenuApi.getMenu(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n  // 获得菜单列表\n  await getTree()\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    if (\n      formData.value.type === SystemMenuTypeEnum.DIR ||\n      formData.value.type === SystemMenuTypeEnum.MENU\n    ) {\n      if (!isExternal(formData.value.path)) {\n        if (formData.value.parentId === 0 && formData.value.path.charAt(0) !== '/') {\n          message.error('路径必须以 / 开头')\n          return\n        } else if (formData.value.parentId !== 0 && formData.value.path.charAt(0) === '/') {\n          message.error('路径不能以 / 开头')\n          return\n        }\n      }\n    }\n    const data = formData.value as unknown as MenuApi.MenuVO\n    if (formType.value === 'create') {\n      await MenuApi.createMenu(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await MenuApi.updateMenu(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n    // 清空，从而触发刷新\n    wsCache.delete(CACHE_KEY.ROLE_ROUTERS)\n  }\n}\n\n/** 获取下拉框[上级菜单]的数据  */\nconst menuTree = ref<Tree[]>([]) // 树形结构\nconst getTree = async () => {\n  menuTree.value = []\n  const res = await MenuApi.getSimpleMenusList()\n  let menu: Tree = { id: 0, name: '主类目', children: [] }\n  menu.children = handleTree(res)\n  menuTree.value.push(menu)\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    name: '',\n    permission: '',\n    type: SystemMenuTypeEnum.DIR,\n    sort: Number(undefined),\n    parentId: 0,\n    path: '',\n    icon: '',\n    component: '',\n    componentName: '',\n    status: CommonStatusEnum.ENABLE,\n    visible: true,\n    keepAlive: true,\n    alwaysShow: true\n  }\n  formRef.value?.resetFields()\n}\n\n/** 判断 path 是不是外部的 HTTP 等链接 */\nconst isExternal = (path: string) => {\n  return /^(https?:|mailto:|tel:)/.test(path)\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/menu/index.vue",
    "content": "<template>\n  <!-- 搜索工作栏 -->\n  <ContentWrap>\n    <el-form\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      :model=\"queryParams\"\n      class=\"-mb-15px\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"菜单名称\" prop=\"name\">\n        <el-input\n          v-model=\"queryParams.name\"\n          class=\"!w-240px\"\n          clearable\n          placeholder=\"请输入菜单名称\"\n          @keyup.enter=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"状态\" prop=\"status\">\n        <el-select\n          v-model=\"queryParams.status\"\n          class=\"!w-240px\"\n          clearable\n          placeholder=\"请选择菜单状态\"\n        >\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\">\n          <Icon class=\"mr-5px\" icon=\"ep:search\" />\n          搜索\n        </el-button>\n        <el-button @click=\"resetQuery\">\n          <Icon class=\"mr-5px\" icon=\"ep:refresh\" />\n          重置\n        </el-button>\n        <el-button\n          v-hasPermi=\"['system:menu:create']\"\n          plain\n          type=\"primary\"\n          @click=\"openForm('create')\"\n        >\n          <Icon class=\"mr-5px\" icon=\"ep:plus\" />\n          新增\n        </el-button>\n        <el-button plain type=\"danger\" @click=\"toggleExpandAll\">\n          <Icon class=\"mr-5px\" icon=\"ep:sort\" />\n          展开/折叠\n        </el-button>\n        <el-button plain @click=\"refreshMenu\">\n          <Icon class=\"mr-5px\" icon=\"ep:refresh\" />\n          刷新菜单缓存\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table\n      v-if=\"refreshTable\"\n      v-loading=\"loading\"\n      :data=\"list\"\n      :default-expand-all=\"isExpandAll\"\n      row-key=\"id\"\n    >\n      <el-table-column :show-overflow-tooltip=\"true\" label=\"菜单名称\" prop=\"name\" width=\"250\" />\n      <el-table-column align=\"center\" label=\"图标\" prop=\"icon\" width=\"100\">\n        <template #default=\"scope\">\n          <Icon :icon=\"scope.row.icon\" />\n        </template>\n      </el-table-column>\n      <el-table-column label=\"排序\" prop=\"sort\" width=\"60\" />\n      <el-table-column :show-overflow-tooltip=\"true\" label=\"权限标识\" prop=\"permission\" />\n      <el-table-column :show-overflow-tooltip=\"true\" label=\"组件路径\" prop=\"component\" />\n      <el-table-column :show-overflow-tooltip=\"true\" label=\"组件名称\" prop=\"componentName\" />\n      <el-table-column label=\"状态\" prop=\"status\" width=\"80\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.COMMON_STATUS\" :value=\"scope.row.status\" />\n        </template>\n      </el-table-column>\n      <el-table-column align=\"center\" label=\"操作\">\n        <template #default=\"scope\">\n          <el-button\n            v-hasPermi=\"['system:menu:update']\"\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n          >\n            修改\n          </el-button>\n          <el-button\n            v-hasPermi=\"['system:menu:create']\"\n            link\n            type=\"primary\"\n            @click=\"openForm('create', undefined, scope.row.id)\"\n          >\n            新增\n          </el-button>\n          <el-button\n            v-hasPermi=\"['system:menu:delete']\"\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <MenuForm ref=\"formRef\" @success=\"getList\" />\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getIntDictOptions } from '@/utils/dict'\nimport { handleTree } from '@/utils/tree'\nimport * as MenuApi from '@/api/system/menu'\nimport MenuForm from './MenuForm.vue'\nimport { CACHE_KEY, useCache } from '@/hooks/web/useCache'\n\ndefineOptions({ name: 'SystemMenu' })\n\nconst { wsCache } = useCache()\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst loading = ref(true) // 列表的加载中\nconst list = ref<any>([]) // 列表的数据\nconst queryParams = reactive({\n  name: undefined,\n  status: undefined\n})\nconst queryFormRef = ref() // 搜索的表单\nconst isExpandAll = ref(false) // 是否展开，默认全部折叠\nconst refreshTable = ref(true) // 重新渲染表格状态\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await MenuApi.getMenuList(queryParams)\n    list.value = handleTree(data)\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number, parentId?: number) => {\n  formRef.value.open(type, id, parentId)\n}\n\n/** 展开/折叠操作 */\nconst toggleExpandAll = () => {\n  refreshTable.value = false\n  isExpandAll.value = !isExpandAll.value\n  nextTick(() => {\n    refreshTable.value = true\n  })\n}\n\n/** 刷新菜单缓存按钮操作 */\nconst refreshMenu = async () => {\n  try {\n    await message.confirm('即将更新缓存刷新浏览器！', '刷新菜单缓存')\n    // 清空，从而触发刷新\n    wsCache.delete(CACHE_KEY.USER)\n    wsCache.delete(CACHE_KEY.ROLE_ROUTERS)\n    // 刷新浏览器\n    location.reload()\n  } catch {}\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await MenuApi.deleteMenu(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/notice/NoticeForm.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" :title=\"dialogTitle\" width=\"800\">\n    <el-form\n      ref=\"formRef\"\n      v-loading=\"formLoading\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"80px\"\n    >\n      <el-form-item label=\"公告标题\" prop=\"title\">\n        <el-input v-model=\"formData.title\" placeholder=\"请输入公告标题\" />\n      </el-form-item>\n      <el-form-item label=\"公告内容\" prop=\"content\">\n        <Editor v-model=\"formData.content\" height=\"150px\" />\n      </el-form-item>\n      <el-form-item label=\"公告类型\" prop=\"type\">\n        <el-select v-model=\"formData.type\" clearable placeholder=\"请选择公告类型\">\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.SYSTEM_NOTICE_TYPE)\"\n            :key=\"parseInt(dict.value as any)\"\n            :label=\"dict.label\"\n            :value=\"parseInt(dict.value as any)\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"状态\" prop=\"status\">\n        <el-select v-model=\"formData.status\" clearable placeholder=\"请选择状态\">\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)\"\n            :key=\"parseInt(dict.value as any)\"\n            :label=\"dict.label\"\n            :value=\"parseInt(dict.value as any)\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"备注\" prop=\"remark\">\n        <el-input v-model=\"formData.remark\" placeholder=\"请输备注\" type=\"textarea\" />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button :disabled=\"formLoading\" type=\"primary\" @click=\"submitForm\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getIntDictOptions } from '@/utils/dict'\nimport { CommonStatusEnum } from '@/utils/constants'\nimport * as NoticeApi from '@/api/system/notice'\n\ndefineOptions({ name: 'SystemNoticeForm' })\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  title: '',\n  type: undefined,\n  content: '',\n  status: CommonStatusEnum.ENABLE,\n  remark: ''\n})\nconst formRules = reactive({\n  title: [{ required: true, message: '公告标题不能为空', trigger: 'blur' }],\n  type: [{ required: true, message: '公告类型不能为空', trigger: 'change' }],\n  status: [{ required: true, message: '状态不能为空', trigger: 'change' }],\n  content: [{ required: true, message: '公告内容不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await NoticeApi.getNotice(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as unknown as NoticeApi.NoticeVO\n    if (formType.value === 'create') {\n      await NoticeApi.createNotice(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await NoticeApi.updateNotice(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    title: '',\n    type: undefined,\n    content: '',\n    status: CommonStatusEnum.ENABLE,\n    remark: ''\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/notice/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <!-- 搜索工作栏 -->\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"公告标题\" prop=\"title\">\n        <el-input\n          v-model=\"queryParams.title\"\n          placeholder=\"请输入公告标题\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"公告状态\" prop=\"status\">\n        <el-select\n          v-model=\"queryParams.status\"\n          placeholder=\"请选择公告状态\"\n          clearable\n          class=\"!w-240px\"\n        >\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n        <el-button\n          type=\"primary\"\n          plain\n          @click=\"openForm('create')\"\n          v-hasPermi=\"['system:notice:create']\"\n        >\n          <Icon icon=\"ep:plus\" class=\"mr-5px\" /> 新增\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column label=\"公告编号\" align=\"center\" prop=\"id\" />\n      <el-table-column label=\"公告标题\" align=\"center\" prop=\"title\" />\n      <el-table-column label=\"公告类型\" align=\"center\" prop=\"type\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.SYSTEM_NOTICE_TYPE\" :value=\"scope.row.type\" />\n        </template>\n      </el-table-column>\n      <el-table-column label=\"状态\" align=\"center\" prop=\"status\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.COMMON_STATUS\" :value=\"scope.row.status\" />\n        </template>\n      </el-table-column>\n      <el-table-column\n        label=\"创建时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        width=\"180\"\n        :formatter=\"dateFormatter\"\n      />\n      <el-table-column label=\"操作\" align=\"center\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n            v-hasPermi=\"['system:notice:update']\"\n          >\n            编辑\n          </el-button>\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n            v-hasPermi=\"['system:notice:delete']\"\n          >\n            删除\n          </el-button>\n          <el-button link @click=\"handlePush(scope.row.id)\" v-hasPermi=\"['system:notice:update']\">\n            推送\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <NoticeForm ref=\"formRef\" @success=\"getList\" />\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getIntDictOptions } from '@/utils/dict'\nimport { dateFormatter } from '@/utils/formatTime'\nimport * as NoticeApi from '@/api/system/notice'\nimport NoticeForm from './NoticeForm.vue'\n\ndefineOptions({ name: 'SystemNotice' })\n\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  title: '',\n  type: undefined,\n  status: undefined\n})\nconst queryFormRef = ref() // 搜索的表单\n\n/** 查询公告列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await NoticeApi.getNoticePage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await NoticeApi.deleteNotice(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 推送按钮操作 */\nconst handlePush = async (id: number) => {\n  try {\n    // 推送的二次确认\n    await message.confirm('是否推送所选中通知？')\n    // 发起推送\n    await NoticeApi.pushNotice(id)\n    message.success(t('推送成功'))\n  } catch {}\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/notify/message/NotifyMessageDetail.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" :max-height=\"500\" :scroll=\"true\" title=\"详情\">\n    <el-descriptions :column=\"1\" border>\n      <el-descriptions-item label=\"编号\" min-width=\"120\">\n        {{ detailData.id }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"用户类型\">\n        <dict-tag :type=\"DICT_TYPE.USER_TYPE\" :value=\"detailData.userType\" />\n      </el-descriptions-item>\n      <el-descriptions-item label=\"用户编号\">\n        {{ detailData.userId }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"模版编号\">\n        {{ detailData.templateId }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"模板编码\">\n        {{ detailData.templateCode }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"发送人名称\">\n        {{ detailData.templateNickname }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"模版内容\">\n        {{ detailData.templateContent }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"模版参数\">\n        {{ detailData.templateParams }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"模版类型\">\n        <dict-tag :type=\"DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE\" :value=\"detailData.templateType\" />\n      </el-descriptions-item>\n      <el-descriptions-item label=\"是否已读\">\n        <dict-tag :type=\"DICT_TYPE.INFRA_BOOLEAN_STRING\" :value=\"detailData.readStatus\" />\n      </el-descriptions-item>\n      <el-descriptions-item label=\"阅读时间\">\n        {{ formatDate(detailData.readTime) }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"创建时间\">\n        {{ formatDate(detailData.createTime) }}\n      </el-descriptions-item>\n    </el-descriptions>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE } from '@/utils/dict'\nimport { formatDate } from '@/utils/formatTime'\nimport * as NotifyMessageApi from '@/api/system/notify/message'\n\ndefineOptions({ name: 'SystemNotifyMessageDetail' })\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst detailLoading = ref(false) // 表单的加载中\nconst detailData = ref({} as NotifyMessageApi.NotifyMessageVO) // 详情数据\n\n/** 打开弹窗 */\nconst open = async (data: NotifyMessageApi.NotifyMessageVO) => {\n  dialogVisible.value = true\n  // 设置数据\n  detailLoading.value = true\n  try {\n    detailData.value = data\n  } finally {\n    detailLoading.value = false\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/notify/message/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <!-- 搜索工作栏 -->\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"用户编号\" prop=\"userId\">\n        <el-input\n          v-model=\"queryParams.userId\"\n          placeholder=\"请输入用户编号\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"用户类型\" prop=\"userType\">\n        <el-select\n          v-model=\"queryParams.userType\"\n          placeholder=\"请选择用户类型\"\n          clearable\n          class=\"!w-240px\"\n        >\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.USER_TYPE)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"模板编码\" prop=\"templateCode\">\n        <el-input\n          v-model=\"queryParams.templateCode\"\n          placeholder=\"请输入模板编码\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"模版类型\" prop=\"templateType\">\n        <el-select\n          v-model=\"queryParams.templateType\"\n          placeholder=\"请选择模版类型\"\n          clearable\n          class=\"!w-240px\"\n        >\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"创建时间\" prop=\"createTime\">\n        <el-date-picker\n          v-model=\"queryParams.createTime\"\n          value-format=\"YYYY-MM-DD HH:mm:ss\"\n          type=\"daterange\"\n          start-placeholder=\"开始日期\"\n          end-placeholder=\"结束日期\"\n          :default-time=\"[new Date('1 00:00:00'), new Date('1 23:59:59')]\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column label=\"编号\" align=\"center\" prop=\"id\" />\n      <el-table-column label=\"用户类型\" align=\"center\" prop=\"userType\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.USER_TYPE\" :value=\"scope.row.userType\" />\n        </template>\n      </el-table-column>\n      <el-table-column label=\"用户编号\" align=\"center\" prop=\"userId\" width=\"80\" />\n      <el-table-column label=\"模板编码\" align=\"center\" prop=\"templateCode\" width=\"80\" />\n      <el-table-column label=\"发送人名称\" align=\"center\" prop=\"templateNickname\" width=\"180\" />\n      <el-table-column\n        label=\"模版内容\"\n        align=\"center\"\n        prop=\"templateContent\"\n        width=\"200\"\n        show-overflow-tooltip\n      />\n      <el-table-column\n        label=\"模版参数\"\n        align=\"center\"\n        prop=\"templateParams\"\n        width=\"180\"\n        show-overflow-tooltip\n      >\n        <template #default=\"scope\"> {{ scope.row.templateParams }}</template>\n      </el-table-column>\n      <el-table-column label=\"模版类型\" align=\"center\" prop=\"templateType\" width=\"120\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE\" :value=\"scope.row.templateType\" />\n        </template>\n      </el-table-column>\n      <el-table-column label=\"是否已读\" align=\"center\" prop=\"readStatus\" width=\"100\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.INFRA_BOOLEAN_STRING\" :value=\"scope.row.readStatus\" />\n        </template>\n      </el-table-column>\n      <el-table-column\n        label=\"阅读时间\"\n        align=\"center\"\n        prop=\"readTime\"\n        width=\"180\"\n        :formatter=\"dateFormatter\"\n      />\n      <el-table-column\n        label=\"创建时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        width=\"180\"\n        :formatter=\"dateFormatter\"\n      />\n      <el-table-column label=\"操作\" align=\"center\" fixed=\"right\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openDetail(scope.row)\"\n            v-hasPermi=\"['system:notify-message:query']\"\n          >\n            详情\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：详情 -->\n  <NotifyMessageDetail ref=\"detailRef\" />\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getIntDictOptions } from '@/utils/dict'\nimport { dateFormatter } from '@/utils/formatTime'\nimport * as NotifyMessageApi from '@/api/system/notify/message'\nimport NotifyMessageDetail from './NotifyMessageDetail.vue'\n\ndefineOptions({ name: 'SystemNotifyMessage' })\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  userType: undefined,\n  userId: undefined,\n  templateCode: undefined,\n  templateType: undefined,\n  createTime: []\n})\nconst queryFormRef = ref() // 搜索的表单\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await NotifyMessageApi.getNotifyMessagePage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 详情操作 */\nconst detailRef = ref()\nconst openDetail = (data: NotifyMessageApi.NotifyMessageVO) => {\n  detailRef.value.open(data)\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/notify/my/MyNotifyMessageDetail.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" :max-height=\"500\" :scroll=\"true\" title=\"消息详情\">\n    <el-descriptions :column=\"1\" border>\n      <el-descriptions-item label=\"发送人\">\n        {{ detailData.templateNickname }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"发送时间\">\n        {{ formatDate(detailData.createTime) }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"消息类型\">\n        <dict-tag :type=\"DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE\" :value=\"detailData.templateType\" />\n      </el-descriptions-item>\n      <el-descriptions-item label=\"是否已读\">\n        <dict-tag :type=\"DICT_TYPE.INFRA_BOOLEAN_STRING\" :value=\"detailData.readStatus\" />\n      </el-descriptions-item>\n      <el-descriptions-item v-if=\"detailData.readStatus\" label=\"阅读时间\">\n        {{ formatDate(detailData.readTime) }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"内容\">\n        {{ detailData.templateContent }}\n      </el-descriptions-item>\n    </el-descriptions>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE } from '@/utils/dict'\nimport { formatDate } from '@/utils/formatTime'\nimport * as NotifyMessageApi from '@/api/system/notify/message'\n\ndefineOptions({ name: 'MyNotifyMessageDetailDetail' })\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst detailLoading = ref(false) // 表单的加载中\nconst detailData = ref({} as NotifyMessageApi.NotifyMessageVO) // 详情数据\n\n/** 打开弹窗 */\nconst open = async (data: NotifyMessageApi.NotifyMessageVO) => {\n  dialogVisible.value = true\n  // 设置数据\n  detailLoading.value = true\n  try {\n    detailData.value = data\n  } finally {\n    detailLoading.value = false\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/notify/my/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <!-- 搜索工作栏 -->\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"是否已读\" prop=\"readStatus\">\n        <el-select\n          v-model=\"queryParams.readStatus\"\n          placeholder=\"请选择状态\"\n          clearable\n          class=\"!w-240px\"\n        >\n          <el-option\n            v-for=\"dict in getBoolDictOptions(DICT_TYPE.INFRA_BOOLEAN_STRING)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"发送时间\" prop=\"createTime\">\n        <el-date-picker\n          v-model=\"queryParams.createTime\"\n          value-format=\"YYYY-MM-DD HH:mm:ss\"\n          type=\"daterange\"\n          start-placeholder=\"开始日期\"\n          end-placeholder=\"结束日期\"\n          :default-time=\"[new Date('1 00:00:00'), new Date('1 23:59:59')]\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n        <el-button @click=\"handleUpdateList\">\n          <Icon icon=\"ep:reading\" class=\"mr-5px\" /> 标记已读\n        </el-button>\n        <el-button @click=\"handleUpdateAll\">\n          <Icon icon=\"ep:reading\" class=\"mr-5px\" /> 全部已读\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table\n      v-loading=\"loading\"\n      :data=\"list\"\n      ref=\"tableRef\"\n      row-key=\"id\"\n      @selection-change=\"handleSelectionChange\"\n    >\n      <el-table-column type=\"selection\" :selectable=\"selectable\" :reserve-selection=\"true\" />\n      <el-table-column label=\"发送人\" align=\"center\" prop=\"templateNickname\" width=\"180\" />\n      <el-table-column\n        label=\"发送时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        width=\"200\"\n        :formatter=\"dateFormatter\"\n      />\n      <el-table-column label=\"类型\" align=\"center\" prop=\"templateType\" width=\"180\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE\" :value=\"scope.row.templateType\" />\n        </template>\n      </el-table-column>\n      <el-table-column\n        label=\"消息内容\"\n        align=\"center\"\n        prop=\"templateContent\"\n        show-overflow-tooltip\n      />\n      <el-table-column label=\"是否已读\" align=\"center\" prop=\"readStatus\" width=\"160\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.INFRA_BOOLEAN_STRING\" :value=\"scope.row.readStatus\" />\n        </template>\n      </el-table-column>\n      <el-table-column\n        label=\"阅读时间\"\n        align=\"center\"\n        prop=\"readTime\"\n        width=\"200\"\n        :formatter=\"dateFormatter\"\n      />\n      <el-table-column label=\"操作\" align=\"center\" width=\"160\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            :type=\"scope.row.readStatus ? 'primary' : 'warning'\"\n            @click=\"openDetail(scope.row)\"\n          >\n            {{ scope.row.readStatus ? '详情' : '已读' }}\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：详情 -->\n  <MyNotifyMessageDetail ref=\"detailRef\" />\n</template>\n\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getBoolDictOptions } from '@/utils/dict'\nimport { dateFormatter } from '@/utils/formatTime'\nimport * as NotifyMessageApi from '@/api/system/notify/message'\nimport MyNotifyMessageDetail from './MyNotifyMessageDetail.vue'\n\ndefineOptions({ name: 'SystemMyNotify' })\n\nconst message = useMessage() // 消息\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  readStatus: undefined,\n  createTime: []\n})\nconst queryFormRef = ref() // 搜索的表单\nconst tableRef = ref() // 表格的 Ref\nconst selectedIds = ref<number[]>([]) // 表格的选中 ID 数组\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await NotifyMessageApi.getMyNotifyMessagePage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  tableRef.value.clearSelection()\n  handleQuery()\n}\n\n/** 详情操作 */\nconst detailRef = ref()\nconst openDetail = (data: NotifyMessageApi.NotifyMessageVO) => {\n  if (!data.readStatus) {\n    handleReadOne(data.id)\n  }\n  detailRef.value.open(data)\n}\n\n/** 标记一条站内信已读 */\nconst handleReadOne = async (id) => {\n  await NotifyMessageApi.updateNotifyMessageRead(id)\n  await getList()\n}\n\n/** 标记全部站内信已读 **/\nconst handleUpdateAll = async () => {\n  await NotifyMessageApi.updateAllNotifyMessageRead()\n  message.success('全部已读成功！')\n  tableRef.value.clearSelection()\n  await getList()\n}\n\n/** 标记一些站内信已读 **/\nconst handleUpdateList = async () => {\n  if (selectedIds.value.length === 0) {\n    return\n  }\n  await NotifyMessageApi.updateNotifyMessageRead(selectedIds.value)\n  message.success('批量已读成功！')\n  tableRef.value.clearSelection()\n  await getList()\n}\n\n/** 某一行，是否允许选中 */\nconst selectable = (row) => {\n  return !row.readStatus\n}\n\n/** 当表格选择项发生变化时会触发该事件  */\nconst handleSelectionChange = (array: NotifyMessageApi.NotifyMessageVO[]) => {\n  selectedIds.value = []\n  if (!array) {\n    return\n  }\n  array.forEach((row) => selectedIds.value.push(row.id))\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/notify/template/NotifyTemplateForm.vue",
    "content": "<template>\n  <Dialog :title=\"dialogTitle\" v-model=\"dialogVisible\">\n    <el-form\n      ref=\"formRef\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"140px\"\n      v-loading=\"formLoading\"\n    >\n      <el-form-item label=\"模版编码\" prop=\"code\">\n        <el-input v-model=\"formData.code\" placeholder=\"请输入模版编码\" />\n      </el-form-item>\n      <el-form-item label=\"模板名称\" prop=\"name\">\n        <el-input v-model=\"formData.name\" placeholder=\"请输入模版名称\" />\n      </el-form-item>\n      <el-form-item label=\"发件人名称\" prop=\"nickname\">\n        <el-input v-model=\"formData.nickname\" placeholder=\"请输入发件人名称\" />\n      </el-form-item>\n      <el-form-item label=\"模板内容\" prop=\"content\">\n        <el-input type=\"textarea\" v-model=\"formData.content\" placeholder=\"请输入模板内容\" />\n      </el-form-item>\n      <el-form-item label=\"类型\" prop=\"type\">\n        <el-select v-model=\"formData.type\" placeholder=\"请选择类型\">\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"开启状态\" prop=\"status\">\n        <el-radio-group v-model=\"formData.status\">\n          <el-radio\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)\"\n            :key=\"dict.value\"\n            :label=\"dict.value\"\n          >\n            {{ dict.label }}\n          </el-radio>\n        </el-radio-group>\n      </el-form-item>\n      <el-form-item label=\"备注\" prop=\"remark\">\n        <el-input v-model=\"formData.remark\" placeholder=\"请输入备注\" />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button @click=\"submitForm\" type=\"primary\" :disabled=\"formLoading\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getIntDictOptions } from '@/utils/dict'\nimport * as NotifyTemplateApi from '@/api/system/notify/template'\nimport { CommonStatusEnum } from '@/utils/constants'\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型\nconst formData = ref<NotifyTemplateApi.NotifyTemplateVO>({\n  id: undefined,\n  name: '',\n  nickname: '',\n  code: '',\n  content: '',\n  type: undefined,\n  params: '',\n  status: CommonStatusEnum.ENABLE,\n  remark: ''\n})\nconst formRules = reactive({\n  type: [{ required: true, message: '消息类型不能为空', trigger: 'change' }],\n  status: [{ required: true, message: '开启状态不能为空', trigger: 'blur' }],\n  code: [{ required: true, message: '模板编码不能为空', trigger: 'blur' }],\n  name: [{ required: true, message: '模板名称不能为空', trigger: 'blur' }],\n  nickname: [{ required: true, message: '发件人姓名不能为空', trigger: 'blur' }],\n  content: [{ required: true, message: '模板内容不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = type\n  formType.value = type\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await NotifyTemplateApi.getNotifyTemplate(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  formLoading.value = true\n  try {\n    const data = formData.value as unknown as NotifyTemplateApi.NotifyTemplateVO\n    if (formType.value === 'create') {\n      await NotifyTemplateApi.createNotifyTemplate(data)\n      message.success('新增成功')\n    } else {\n      await NotifyTemplateApi.updateNotifyTemplate(data)\n      message.success('修改成功')\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    name: '',\n    nickname: '',\n    code: '',\n    content: '',\n    type: undefined,\n    params: '',\n    status: CommonStatusEnum.ENABLE,\n    remark: ''\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/notify/template/NotifyTemplateSendForm.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" title=\"测试发送\" :max-height=\"500\">\n    <el-form\n      ref=\"formRef\"\n      v-loading=\"formLoading\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"140px\"\n    >\n      <el-form-item label=\"模板内容\" prop=\"content\">\n        <el-input\n          v-model=\"formData.content\"\n          placeholder=\"请输入模板内容\"\n          readonly\n          type=\"textarea\"\n        />\n      </el-form-item>\n      <el-form-item label=\"用户类型\" prop=\"userType\">\n        <el-radio-group v-model=\"formData.userType\">\n          <el-radio\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.USER_TYPE)\"\n            :key=\"dict.value\"\n            :label=\"dict.value\"\n          >\n            {{ dict.label }}\n          </el-radio>\n        </el-radio-group>\n      </el-form-item>\n      <el-form-item v-show=\"formData.userType === 1\" label=\"接收人ID\" prop=\"userId\">\n        <el-input v-model=\"formData.userId\" style=\"width: 160px\" />\n      </el-form-item>\n      <el-form-item v-show=\"formData.userType === 2\" label=\"接收人\" prop=\"userId\">\n        <el-select v-model=\"formData.userId\" placeholder=\"请选择接收人\">\n          <el-option\n            v-for=\"item in userOption\"\n            :key=\"item.id\"\n            :label=\"item.nickname\"\n            :value=\"item.id\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item\n        v-for=\"param in formData.params\"\n        :key=\"param\"\n        :label=\"'参数 {' + param + '}'\"\n        :prop=\"'templateParams.' + param\"\n      >\n        <el-input\n          v-model=\"formData.templateParams[param]\"\n          :placeholder=\"'请输入 ' + param + ' 参数'\"\n        />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button :disabled=\"formLoading\" type=\"primary\" @click=\"submitForm\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport * as UserApi from '@/api/system/user'\nimport * as NotifyTemplateApi from '@/api/system/notify/template'\nimport { DICT_TYPE, getIntDictOptions } from '@/utils/dict'\n\ndefineOptions({ name: 'SystemNotifyTemplateSendForm' })\n\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formData = ref({\n  content: '',\n  params: {},\n  userId: undefined,\n  userType: 1,\n  templateCode: '',\n  templateParams: new Map()\n})\nconst formRules = reactive({\n  userId: [{ required: true, message: '用户编号不能为空', trigger: 'change' }],\n  templateCode: [{ required: true, message: '模版编号不能为空', trigger: 'blur' }],\n  templateParams: {}\n})\nconst formRef = ref() // 表单 Ref\nconst userOption = ref<UserApi.UserVO[]>([])\n\nconst open = async (id: number) => {\n  dialogVisible.value = true\n  resetForm()\n  // 设置数据\n  formLoading.value = true\n  try {\n    const data = await NotifyTemplateApi.getNotifyTemplate(id)\n    // 设置动态表单\n    formData.value.content = data.content\n    formData.value.params = data.params\n    formData.value.templateCode = data.code\n    formData.value.templateParams = data.params.reduce((obj, item) => {\n      obj[item] = '' // 给每个动态属性赋值，避免无法读取\n      return obj\n    }, {})\n    formRules.templateParams = data.params.reduce((obj, item) => {\n      obj[item] = { required: true, message: '参数 ' + item + ' 不能为空', trigger: 'blur' }\n      return obj\n    }, {})\n  } finally {\n    formLoading.value = false\n  }\n  // 加载用户列表\n  userOption.value = await UserApi.getSimpleUserList()\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as unknown as NotifyTemplateApi.NotifySendReqVO\n    const logId = await NotifyTemplateApi.sendNotify(data)\n    if (logId) {\n      message.success('提交发送成功！发送结果，见发送日志编号：' + logId)\n    }\n    dialogVisible.value = false\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    content: '',\n    params: {},\n    mobile: '',\n    templateCode: '',\n    templateParams: new Map(),\n    userType: 1\n  } as any\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/notify/template/index.vue",
    "content": "<template>\n  <!-- 搜索工作栏 -->\n  <ContentWrap>\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"模板名称\" prop=\"name\">\n        <el-input\n          v-model=\"queryParams.name\"\n          placeholder=\"请输入模板名称\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"模板编号\" prop=\"code\">\n        <el-input\n          v-model=\"queryParams.code\"\n          placeholder=\"请输入模版编码\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"状态\" prop=\"status\">\n        <el-select\n          v-model=\"queryParams.status\"\n          placeholder=\"请选择开启状态\"\n          clearable\n          class=\"!w-240px\"\n        >\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"创建时间\" prop=\"createTime\">\n        <el-date-picker\n          v-model=\"queryParams.createTime\"\n          value-format=\"YYYY-MM-DD HH:mm:ss\"\n          type=\"daterange\"\n          start-placeholder=\"开始日期\"\n          end-placeholder=\"结束日期\"\n          :default-time=\"[new Date('1 00:00:00'), new Date('1 23:59:59')]\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n        <el-button\n          type=\"primary\"\n          plain\n          @click=\"openForm('create')\"\n          v-hasPermi=\"['system:notify-template:create']\"\n        >\n          <Icon icon=\"ep:plus\" class=\"mr-5px\" />新增\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column\n        label=\"模板编码\"\n        align=\"center\"\n        prop=\"code\"\n        width=\"120\"\n        :show-overflow-tooltip=\"true\"\n      />\n      <el-table-column\n        label=\"模板名称\"\n        align=\"center\"\n        prop=\"name\"\n        width=\"120\"\n        :show-overflow-tooltip=\"true\"\n      />\n      <el-table-column label=\"类型\" align=\"center\" prop=\"type\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.SYSTEM_NOTIFY_TEMPLATE_TYPE\" :value=\"scope.row.type\" />\n        </template>\n      </el-table-column>\n      <el-table-column label=\"发送人名称\" align=\"center\" prop=\"nickname\" />\n      <el-table-column\n        label=\"模板内容\"\n        align=\"center\"\n        prop=\"content\"\n        width=\"200\"\n        :show-overflow-tooltip=\"true\"\n      />\n      <el-table-column label=\"开启状态\" align=\"center\" prop=\"status\" width=\"80\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.COMMON_STATUS\" :value=\"scope.row.status\" />\n        </template>\n      </el-table-column>\n      <el-table-column label=\"备注\" align=\"center\" prop=\"remark\" />\n      <el-table-column\n        label=\"创建时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        width=\"180\"\n        :formatter=\"dateFormatter\"\n      />\n      <el-table-column label=\"操作\" align=\"center\" width=\"210\" fixed=\"right\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n            v-hasPermi=\"['system:notify-template:update']\"\n          >\n            修改\n          </el-button>\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openSendForm(scope.row)\"\n            v-hasPermi=\"['system:notify-template:send-notify']\"\n          >\n            测试\n          </el-button>\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n            v-hasPermi=\"['system:notify-template:delete']\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <NotifyTemplateForm ref=\"formRef\" @success=\"getList\" />\n  <!-- 表单弹窗：测试发送 -->\n  <NotifyTemplateSendForm ref=\"sendFormRef\" />\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getIntDictOptions } from '@/utils/dict'\nimport { dateFormatter } from '@/utils/formatTime'\nimport * as NotifyTemplateApi from '@/api/system/notify/template'\nimport NotifyTemplateForm from './NotifyTemplateForm.vue'\nimport NotifyTemplateSendForm from './NotifyTemplateSendForm.vue'\n\ndefineOptions({ name: 'NotifySmsTemplate' })\n\nconst message = useMessage() // 消息弹窗\n\nconst loading = ref(false) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  name: undefined,\n  status: undefined,\n  code: undefined,\n  createTime: []\n})\nconst queryFormRef = ref() // 搜索的表单\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await NotifyTemplateApi.getNotifyTemplatePage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await NotifyTemplateApi.deleteNotifyTemplate(id)\n    message.success('删除成功')\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 发送站内信按钮 */\nconst sendFormRef = ref() // 表单 Ref\nconst openSendForm = (row: NotifyTemplateApi.NotifyTemplateVO) => {\n  sendFormRef.value.open(row.id)\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/oauth2/client/ClientForm.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" :title=\"dialogTitle\" max-height=\"500px\" scroll>\n    <el-form\n      ref=\"formRef\"\n      v-loading=\"formLoading\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"160px\"\n    >\n      <el-form-item label=\"客户端编号\" prop=\"secret\">\n        <el-input v-model=\"formData.clientId\" placeholder=\"请输入客户端编号\" />\n      </el-form-item>\n      <el-form-item label=\"客户端密钥\" prop=\"secret\">\n        <el-input v-model=\"formData.secret\" placeholder=\"请输入客户端密钥\" />\n      </el-form-item>\n      <el-form-item label=\"应用名\" prop=\"name\">\n        <el-input v-model=\"formData.name\" placeholder=\"请输入应用名\" />\n      </el-form-item>\n      <el-form-item label=\"应用图标\">\n        <UploadImg v-model=\"formData.logo\" :limit=\"1\" />\n      </el-form-item>\n      <el-form-item label=\"应用描述\">\n        <el-input v-model=\"formData.description\" placeholder=\"请输入应用名\" type=\"textarea\" />\n      </el-form-item>\n      <el-form-item label=\"状态\" prop=\"status\">\n        <el-radio-group v-model=\"formData.status\">\n          <el-radio\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)\"\n            :key=\"dict.value\"\n            :label=\"dict.value\"\n          >\n            {{ dict.label }}\n          </el-radio>\n        </el-radio-group>\n      </el-form-item>\n      <el-form-item label=\"访问令牌的有效期\" prop=\"accessTokenValiditySeconds\">\n        <el-input-number v-model=\"formData.accessTokenValiditySeconds\" placeholder=\"单位：秒\" />\n      </el-form-item>\n      <el-form-item label=\"刷新令牌的有效期\" prop=\"refreshTokenValiditySeconds\">\n        <el-input-number v-model=\"formData.refreshTokenValiditySeconds\" placeholder=\"单位：秒\" />\n      </el-form-item>\n      <el-form-item label=\"授权类型\" prop=\"authorizedGrantTypes\">\n        <el-select\n          v-model=\"formData.authorizedGrantTypes\"\n          filterable\n          multiple\n          placeholder=\"请输入授权类型\"\n          style=\"width: 500px\"\n        >\n          <el-option\n            v-for=\"dict in getDictOptions(DICT_TYPE.SYSTEM_OAUTH2_GRANT_TYPE)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"授权范围\" prop=\"scopes\">\n        <el-select\n          v-model=\"formData.scopes\"\n          filterable\n          multiple\n          allow-create\n          placeholder=\"请输入授权范围\"\n          style=\"width: 500px\"\n        >\n          <el-option v-for=\"scope in formData.scopes\" :key=\"scope\" :label=\"scope\" :value=\"scope\" />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"自动授权范围\" prop=\"autoApproveScopes\">\n        <el-select\n          v-model=\"formData.autoApproveScopes\"\n          filterable\n          multiple\n          placeholder=\"请输入授权范围\"\n          style=\"width: 500px\"\n        >\n          <el-option v-for=\"scope in formData.scopes\" :key=\"scope\" :label=\"scope\" :value=\"scope\" />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"可重定向的 URI 地址\" prop=\"redirectUris\">\n        <el-select\n          v-model=\"formData.redirectUris\"\n          allow-create\n          filterable\n          multiple\n          placeholder=\"请输入可重定向的 URI 地址\"\n          style=\"width: 500px\"\n        >\n          <el-option\n            v-for=\"redirectUri in formData.redirectUris\"\n            :key=\"redirectUri\"\n            :label=\"redirectUri\"\n            :value=\"redirectUri\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"权限\" prop=\"authorities\">\n        <el-select\n          v-model=\"formData.authorities\"\n          allow-create\n          filterable\n          multiple\n          placeholder=\"请输入权限\"\n          style=\"width: 500px\"\n        >\n          <el-option\n            v-for=\"authority in formData.authorities\"\n            :key=\"authority\"\n            :label=\"authority\"\n            :value=\"authority\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"资源\" prop=\"resourceIds\">\n        <el-select\n          v-model=\"formData.resourceIds\"\n          allow-create\n          filterable\n          multiple\n          placeholder=\"请输入资源\"\n          style=\"width: 500px\"\n        >\n          <el-option\n            v-for=\"resourceId in formData.resourceIds\"\n            :key=\"resourceId\"\n            :label=\"resourceId\"\n            :value=\"resourceId\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"附加信息\" prop=\"additionalInformation\">\n        <el-input\n          v-model=\"formData.additionalInformation\"\n          placeholder=\"请输入附加信息，JSON 格式数据\"\n          type=\"textarea\"\n        />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button :disabled=\"formLoading\" type=\"primary\" @click=\"submitForm\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getDictOptions, getIntDictOptions } from '@/utils/dict'\nimport { CommonStatusEnum } from '@/utils/constants'\nimport * as ClientApi from '@/api/system/oauth2/client'\n\ndefineOptions({ name: 'SystemOAuth2ClientForm' })\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  clientId: undefined,\n  secret: undefined,\n  name: undefined,\n  logo: undefined,\n  description: undefined,\n  status: CommonStatusEnum.ENABLE,\n  accessTokenValiditySeconds: 30 * 60,\n  refreshTokenValiditySeconds: 30 * 24 * 60,\n  redirectUris: [],\n  authorizedGrantTypes: [],\n  scopes: [],\n  autoApproveScopes: [],\n  authorities: [],\n  resourceIds: [],\n  additionalInformation: undefined\n})\nconst formRules = reactive({\n  clientId: [{ required: true, message: '客户端编号不能为空', trigger: 'blur' }],\n  secret: [{ required: true, message: '客户端密钥不能为空', trigger: 'blur' }],\n  name: [{ required: true, message: '应用名不能为空', trigger: 'blur' }],\n  logo: [{ required: true, message: '应用图标不能为空', trigger: 'blur' }],\n  status: [{ required: true, message: '状态不能为空', trigger: 'blur' }],\n  accessTokenValiditySeconds: [\n    { required: true, message: '访问令牌的有效期不能为空', trigger: 'blur' }\n  ],\n  refreshTokenValiditySeconds: [\n    { required: true, message: '刷新令牌的有效期不能为空', trigger: 'blur' }\n  ],\n  redirectUris: [{ required: true, message: '可重定向的 URI 地址不能为空', trigger: 'blur' }],\n  authorizedGrantTypes: [{ required: true, message: '授权类型不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await ClientApi.getOAuth2Client(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as unknown as ClientApi.OAuth2ClientVO\n    if (formType.value === 'create') {\n      await ClientApi.createOAuth2Client(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await ClientApi.updateOAuth2Client(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    clientId: undefined,\n    secret: undefined,\n    name: undefined,\n    logo: undefined,\n    description: undefined,\n    status: CommonStatusEnum.ENABLE,\n    accessTokenValiditySeconds: 30 * 60,\n    refreshTokenValiditySeconds: 30 * 24 * 60,\n    redirectUris: [],\n    authorizedGrantTypes: [],\n    scopes: [],\n    autoApproveScopes: [],\n    authorities: [],\n    resourceIds: [],\n    additionalInformation: undefined\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/oauth2/client/index.vue",
    "content": "<template>\n  <!-- 搜索 -->\n  <ContentWrap>\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"应用名\" prop=\"name\">\n        <el-input\n          v-model=\"queryParams.name\"\n          placeholder=\"请输入应用名\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"状态\" prop=\"status\">\n        <el-select v-model=\"queryParams.status\" placeholder=\"请选择状态\" clearable class=\"!w-240px\">\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n        <el-button\n          plain\n          type=\"primary\"\n          @click=\"openForm('create')\"\n          v-hasPermi=\"['system:oauth2-client:create']\"\n        >\n          <Icon icon=\"ep:plus\" class=\"mr-5px\" /> 新增\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column label=\"客户端编号\" align=\"center\" prop=\"clientId\" />\n      <el-table-column label=\"客户端密钥\" align=\"center\" prop=\"secret\" />\n      <el-table-column label=\"应用名\" align=\"center\" prop=\"name\" />\n      <el-table-column label=\"应用图标\" align=\"center\" prop=\"logo\">\n        <template #default=\"scope\">\n          <img width=\"40px\" height=\"40px\" :src=\"scope.row.logo\" />\n        </template>\n      </el-table-column>\n      <el-table-column label=\"状态\" align=\"center\" prop=\"status\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.COMMON_STATUS\" :value=\"scope.row.status\" />\n        </template>\n      </el-table-column>\n      <el-table-column label=\"访问令牌的有效期\" align=\"center\" prop=\"accessTokenValiditySeconds\">\n        <template #default=\"scope\">{{ scope.row.accessTokenValiditySeconds }} 秒</template>\n      </el-table-column>\n      <el-table-column label=\"刷新令牌的有效期\" align=\"center\" prop=\"refreshTokenValiditySeconds\">\n        <template #default=\"scope\">{{ scope.row.refreshTokenValiditySeconds }} 秒</template>\n      </el-table-column>\n      <el-table-column label=\"授权类型\" align=\"center\" prop=\"authorizedGrantTypes\">\n        <template #default=\"scope\">\n          <el-tag\n            :disable-transitions=\"true\"\n            :key=\"index\"\n            v-for=\"(authorizedGrantType, index) in scope.row.authorizedGrantTypes\"\n            :index=\"index\"\n            class=\"mr-5px\"\n          >\n            {{ authorizedGrantType }}\n          </el-tag>\n        </template>\n      </el-table-column>\n      <el-table-column\n        label=\"创建时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        width=\"180\"\n        :formatter=\"dateFormatter\"\n      />\n      <el-table-column label=\"操作\" align=\"center\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n            v-hasPermi=\"['system:oauth2-client:update']\"\n          >\n            编辑\n          </el-button>\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n            v-hasPermi=\"['system:oauth2-client:delete']\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <ClientForm ref=\"formRef\" @success=\"getList\" />\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getIntDictOptions } from '@/utils/dict'\nimport { dateFormatter } from '@/utils/formatTime'\nimport * as ClientApi from '@/api/system/oauth2/client'\nimport ClientForm from './ClientForm.vue'\n\ndefineOptions({ name: 'SystemOAuth2Client' })\n\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  name: null,\n  status: undefined\n})\nconst queryFormRef = ref() // 搜索的表单\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await ClientApi.getOAuth2ClientPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await ClientApi.deleteOAuth2Client(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/oauth2/token/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <!-- 搜索工作栏 -->\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"90px\"\n    >\n      <el-form-item label=\"用户编号\" prop=\"userId\">\n        <el-input\n          v-model=\"queryParams.userId\"\n          placeholder=\"请输入用户编号\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"用户类型\" prop=\"userType\">\n        <el-select\n          v-model=\"queryParams.userType\"\n          placeholder=\"请选择用户类型\"\n          clearable\n          class=\"!w-240px\"\n        >\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.USER_TYPE)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"客户端编号\" prop=\"clientId\">\n        <el-input\n          v-model=\"queryParams.clientId\"\n          placeholder=\"请输入客户端编号\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column label=\"访问令牌\" align=\"center\" prop=\"accessToken\" width=\"300\" />\n      <el-table-column label=\"刷新令牌\" align=\"center\" prop=\"refreshToken\" width=\"300\" />\n      <el-table-column label=\"用户编号\" align=\"center\" prop=\"userId\" />\n      <el-table-column label=\"用户类型\" align=\"center\" prop=\"userType\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.USER_TYPE\" :value=\"scope.row.userType\" />\n        </template>\n      </el-table-column>\n      <el-table-column\n        label=\"过期时间\"\n        align=\"center\"\n        prop=\"expiresTime\"\n        :formatter=\"dateFormatter\"\n        width=\"180\"\n      />\n      <el-table-column\n        label=\"创建时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        :formatter=\"dateFormatter\"\n        width=\"180\"\n      />\n      <el-table-column label=\"操作\" align=\"center\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleForceLogout(scope.row.accessToken)\"\n            v-hasPermi=\"['system:oauth2-token:delete']\"\n          >\n            强退\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n</template>\n\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getIntDictOptions } from '@/utils/dict'\nimport { dateFormatter } from '@/utils/formatTime'\nimport * as OAuth2AccessTokenApi from '@/api/system/oauth2/token'\n\ndefineOptions({ name: 'SystemTokenClient' })\n\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  userId: null,\n  userType: undefined,\n  clientId: null\n})\nconst queryFormRef = ref() // 搜索的表单\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await OAuth2AccessTokenApi.getAccessTokenPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 强制退出操作 */\nconst handleForceLogout = async (accessToken: string) => {\n  try {\n    // 删除的二次确认\n    await message.confirm('是否要强制退出用户')\n    // 发起删除\n    await OAuth2AccessTokenApi.deleteAccessToken(accessToken)\n    message.success(t('common.success'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/operatelog/OperateLogDetail.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" :max-height=\"500\" :scroll=\"true\" title=\"详情\" width=\"800\">\n    <el-descriptions :column=\"1\" border>\n      <el-descriptions-item label=\"日志主键\" min-width=\"120\">\n        {{ detailData.id }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"链路追踪\" v-if=\"detailData.traceId\">\n        {{ detailData.traceId }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"操作人编号\">\n        {{ detailData.userId }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"操作人名字\">\n        {{ detailData.userName }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"操作人 IP\">\n        {{ detailData.userIp }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"操作人 UA\">\n        {{ detailData.userAgent }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"操作模块\">\n        {{ detailData.type }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"操作名\">\n        {{ detailData.subType }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"操作内容\">\n        {{ detailData.action }}\n      </el-descriptions-item>\n      <el-descriptions-item v-if=\"detailData.extra\" label=\"操作拓展参数\">\n        {{ detailData.extra }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"请求 URL\">\n        {{ detailData.requestMethod }} {{ detailData.requestUrl }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"操作时间\">\n        {{ formatDate(detailData.createTime) }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"业务编号\">\n        {{ detailData.bizId }}\n      </el-descriptions-item>\n    </el-descriptions>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport { formatDate } from '@/utils/formatTime'\nimport * as OperateLogApi from '@/api/system/operatelog'\n\ndefineOptions({ name: 'SystemOperateLogDetail' })\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst detailLoading = ref(false) // 表单的加载中\nconst detailData = ref({} as OperateLogApi.OperateLogVO) // 详情数据\n\n/** 打开弹窗 */\nconst open = async (data: OperateLogApi.OperateLogVO) => {\n  dialogVisible.value = true\n  // 设置数据\n  detailLoading.value = true\n  try {\n    detailData.value = data\n  } finally {\n    detailLoading.value = false\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/operatelog/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <!-- 搜索工作栏 -->\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"操作人\" prop=\"userId\">\n        <el-select\n          v-model=\"queryParams.userId\"\n          multiple\n          placeholder=\"请输入操作人员\"\n          class=\"!w-240px\"\n        >\n          <el-option\n            v-for=\"user in userList\"\n            :key=\"user.id\"\n            :label=\"user.nickname\"\n            :value=\"user.id\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"操作模块\" prop=\"type\">\n        <el-input\n          v-model=\"queryParams.type\"\n          placeholder=\"请输入操作模块\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"操作模块\" prop=\"subType\">\n        <el-input\n          v-model=\"queryParams.subType\"\n          placeholder=\"请输入操作模块\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"操作内容\" prop=\"action\">\n        <el-input\n          v-model=\"queryParams.action\"\n          placeholder=\"请输入操作名\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"操作时间\" prop=\"createTime\">\n        <el-date-picker\n          v-model=\"queryParams.createTime\"\n          value-format=\"YYYY-MM-DD HH:mm:ss\"\n          type=\"daterange\"\n          start-placeholder=\"开始日期\"\n          end-placeholder=\"结束日期\"\n          :default-time=\"[new Date('1 00:00:00'), new Date('1 23:59:59')]\"\n          class=\"!w-220px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"业务编号\" prop=\"bizId\">\n        <el-input\n          v-model=\"queryParams.bizId\"\n          placeholder=\"请输入业务编号\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n        <el-button\n          type=\"success\"\n          plain\n          @click=\"handleExport\"\n          :loading=\"exportLoading\"\n          v-hasPermi=\"['infra:operate-log:export']\"\n        >\n          <Icon icon=\"ep:download\" class=\"mr-5px\" /> 导出\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column label=\"日志编号\" align=\"center\" prop=\"id\" width=\"100\" />\n      <el-table-column label=\"操作人\" align=\"center\" prop=\"userName\" width=\"120\" />\n      <el-table-column label=\"操作模块\" align=\"center\" prop=\"type\" width=\"120\" />\n      <el-table-column label=\"操作名\" align=\"center\" prop=\"subType\" width=\"160\" />\n      <el-table-column label=\"操作内容\" align=\"center\" prop=\"action\" />\n      <el-table-column\n        label=\"操作时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        width=\"180\"\n        :formatter=\"dateFormatter\"\n      />\n      <el-table-column label=\"业务编号\" align=\"center\" prop=\"bizId\" width=\"120\" />\n      <el-table-column label=\"IP\" align=\"center\" prop=\"userIp\" width=\"120\" />\n      <el-table-column label=\"操作\" align=\"center\" fixed=\"right\" width=\"60\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openDetail(scope.row)\"\n            v-hasPermi=\"['infra:operate-log:query']\"\n          >\n            详情\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：详情 -->\n  <OperateLogDetail ref=\"detailRef\" />\n</template>\n<script lang=\"ts\" setup>\nimport { dateFormatter } from '@/utils/formatTime'\nimport download from '@/utils/download'\nimport * as OperateLogApi from '@/api/system/operatelog'\nimport OperateLogDetail from './OperateLogDetail.vue'\nimport * as UserApi from '@/api/system/user'\nconst userList = ref<UserApi.UserVO[]>([]) // 用户列表\n\ndefineOptions({ name: 'SystemOperateLog' })\n\nconst message = useMessage() // 消息弹窗\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  userId: undefined,\n  type: undefined,\n  subType: undefined,\n  action: undefined,\n  createTime: [],\n  bizId: undefined\n})\nconst queryFormRef = ref() // 搜索的表单\nconst exportLoading = ref(false) // 导出的加载中\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await OperateLogApi.getOperateLogPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 详情操作 */\nconst detailRef = ref()\nconst openDetail = (data: OperateLogApi.OperateLogVO) => {\n  detailRef.value.open(data)\n}\n\n/** 导出按钮操作 */\nconst handleExport = async () => {\n  try {\n    // 导出的二次确认\n    await message.exportConfirm()\n    // 发起导出\n    exportLoading.value = true\n    const data = await OperateLogApi.exportOperateLog(queryParams)\n    download.excel(data, '操作日志.xls')\n  } catch {\n  } finally {\n    exportLoading.value = false\n  }\n}\n\n/** 初始化 **/\nonMounted(async () => {\n  await getList()\n  // 获得用户列表\n  userList.value = await UserApi.getSimpleUserList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/post/PostForm.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" :title=\"dialogTitle\" width=\"800\">\n    <el-form\n      ref=\"formRef\"\n      v-loading=\"formLoading\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"80px\"\n    >\n      <el-form-item label=\"岗位标题\" prop=\"name\">\n        <el-input v-model=\"formData.name\" placeholder=\"请输入岗位标题\" />\n      </el-form-item>\n      <el-form-item label=\"岗位编码\" prop=\"code\">\n        <el-input v-model=\"formData.code\" placeholder=\"请输入岗位编码\" />\n      </el-form-item>\n      <el-form-item label=\"岗位顺序\" prop=\"sort\">\n        <el-input v-model=\"formData.sort\" placeholder=\"请输入岗位顺序\" />\n      </el-form-item>\n      <el-form-item label=\"状态\" prop=\"status\">\n        <el-select v-model=\"formData.status\" clearable placeholder=\"请选择状态\">\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"备注\" prop=\"remark\">\n        <el-input v-model=\"formData.remark\" placeholder=\"请输备注\" type=\"textarea\" />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button :disabled=\"formLoading\" type=\"primary\" @click=\"submitForm\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getIntDictOptions } from '@/utils/dict'\nimport { CommonStatusEnum } from '@/utils/constants'\nimport * as PostApi from '@/api/system/post'\n\ndefineOptions({ name: 'SystemPostForm' })\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  name: '',\n  code: '',\n  sort: 0,\n  status: CommonStatusEnum.ENABLE,\n  remark: ''\n})\nconst formRules = reactive({\n  name: [{ required: true, message: '岗位标题不能为空', trigger: 'blur' }],\n  code: [{ required: true, message: '岗位编码不能为空', trigger: 'change' }],\n  status: [{ required: true, message: '岗位状态不能为空', trigger: 'change' }],\n  remark: [{ required: false, message: '岗位内容不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await PostApi.getPost(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as unknown as PostApi.PostVO\n    if (formType.value === 'create') {\n      await PostApi.createPost(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await PostApi.updatePost(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    name: '',\n    code: '',\n    sort: undefined,\n    status: CommonStatusEnum.ENABLE,\n    remark: ''\n  } as any\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/post/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <!-- 搜索工作栏 -->\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"岗位名称\" prop=\"name\">\n        <el-input\n          v-model=\"queryParams.name\"\n          placeholder=\"请输入岗位名称\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"岗位编码\" prop=\"code\">\n        <el-input\n          v-model=\"queryParams.code\"\n          placeholder=\"请输入岗位编码\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"状态\" prop=\"status\">\n        <el-select v-model=\"queryParams.status\" placeholder=\"请选择状态\" clearable>\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n        <el-button\n          type=\"primary\"\n          plain\n          @click=\"openForm('create')\"\n          v-hasPermi=\"['system:post:create']\"\n        >\n          <Icon icon=\"ep:plus\" class=\"mr-5px\" /> 新增\n        </el-button>\n        <el-button\n          type=\"success\"\n          plain\n          @click=\"handleExport\"\n          :loading=\"exportLoading\"\n          v-hasPermi=\"['system:post:export']\"\n        >\n          <Icon icon=\"ep:download\" class=\"mr-5px\" /> 导出\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column label=\"岗位编号\" align=\"center\" prop=\"id\" />\n      <el-table-column label=\"岗位名称\" align=\"center\" prop=\"name\" />\n      <el-table-column label=\"岗位编码\" align=\"center\" prop=\"code\" />\n      <el-table-column label=\"岗位顺序\" align=\"center\" prop=\"sort\" />\n      <el-table-column label=\"岗位备注\" align=\"center\" prop=\"remark\" />\n      <el-table-column label=\"状态\" align=\"center\" prop=\"status\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.COMMON_STATUS\" :value=\"scope.row.status\" />\n        </template>\n      </el-table-column>\n      <el-table-column\n        label=\"创建时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        width=\"180\"\n        :formatter=\"dateFormatter\"\n      />\n      <el-table-column label=\"操作\" align=\"center\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n            v-hasPermi=\"['system:post:update']\"\n          >\n            编辑\n          </el-button>\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n            v-hasPermi=\"['system:post:delete']\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <PostForm ref=\"formRef\" @success=\"getList\" />\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getIntDictOptions } from '@/utils/dict'\nimport { dateFormatter } from '@/utils/formatTime'\nimport download from '@/utils/download'\nimport * as PostApi from '@/api/system/post'\nimport PostForm from './PostForm.vue'\n\ndefineOptions({ name: 'SystemPost' })\n\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  code: '',\n  name: '',\n  status: undefined\n})\nconst queryFormRef = ref() // 搜索的表单\nconst exportLoading = ref(false) // 导出的加载中\n\n/** 查询岗位列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await PostApi.getPostPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await PostApi.deletePost(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 导出按钮操作 */\nconst handleExport = async () => {\n  try {\n    // 导出的二次确认\n    await message.exportConfirm()\n    // 发起导出\n    exportLoading.value = true\n    const data = await PostApi.exportPost(queryParams)\n    download.excel(data, '岗位列表.xls')\n  } catch {\n  } finally {\n    exportLoading.value = false\n  }\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/role/RoleAssignMenuForm.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" title=\"菜单权限\">\n    <el-form ref=\"formRef\" v-loading=\"formLoading\" :model=\"formData\" label-width=\"80px\">\n      <el-form-item label=\"角色名称\">\n        <el-tag>{{ formData.name }}</el-tag>\n      </el-form-item>\n      <el-form-item label=\"角色标识\">\n        <el-tag>{{ formData.code }}</el-tag>\n      </el-form-item>\n      <el-form-item label=\"菜单权限\">\n        <el-card class=\"cardHeight\">\n          <template #header>\n            全选/全不选:\n            <el-switch\n              v-model=\"treeNodeAll\"\n              active-text=\"是\"\n              inactive-text=\"否\"\n              inline-prompt\n              @change=\"handleCheckedTreeNodeAll\"\n            />\n            全部展开/折叠:\n            <el-switch\n              v-model=\"menuExpand\"\n              active-text=\"展开\"\n              inactive-text=\"折叠\"\n              inline-prompt\n              @change=\"handleCheckedTreeExpand\"\n            />\n          </template>\n          <el-tree\n            ref=\"treeRef\"\n            :data=\"menuOptions\"\n            :props=\"defaultProps\"\n            empty-text=\"加载中，请稍候\"\n            node-key=\"id\"\n            show-checkbox\n          />\n        </el-card>\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button :disabled=\"formLoading\" type=\"primary\" @click=\"submitForm\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport { defaultProps, handleTree } from '@/utils/tree'\nimport * as RoleApi from '@/api/system/role'\nimport * as MenuApi from '@/api/system/menu'\nimport * as PermissionApi from '@/api/system/permission'\n\ndefineOptions({ name: 'SystemRoleAssignMenuForm' })\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formData = reactive({\n  id: undefined,\n  name: '',\n  code: '',\n  menuIds: []\n})\nconst formRef = ref() // 表单 Ref\nconst menuOptions = ref<any[]>([]) // 菜单树形结构\nconst menuExpand = ref(false) // 展开/折叠\nconst treeRef = ref() // 菜单树组件 Ref\nconst treeNodeAll = ref(false) // 全选/全不选\n\n/** 打开弹窗 */\nconst open = async (row: RoleApi.RoleVO) => {\n  dialogVisible.value = true\n  resetForm()\n  // 加载 Menu 列表。注意，必须放在前面，不然下面 setChecked 没数据节点\n  menuOptions.value = handleTree(await MenuApi.getSimpleMenusList())\n  // 设置数据\n  formData.id = row.id\n  formData.name = row.name\n  formData.code = row.code\n  formLoading.value = true\n  try {\n    formData.value.menuIds = await PermissionApi.getRoleMenuList(row.id)\n    // 设置选中\n    formData.value.menuIds.forEach((menuId: number) => {\n      treeRef.value.setChecked(menuId, true, false)\n    })\n  } finally {\n    formLoading.value = false\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = {\n      roleId: formData.id,\n      menuIds: [\n        ...(treeRef.value.getCheckedKeys(false) as unknown as Array<number>), // 获得当前选中节点\n        ...(treeRef.value.getHalfCheckedKeys() as unknown as Array<number>) // 获得半选中的父节点\n      ]\n    }\n    await PermissionApi.assignRoleMenu(data)\n    message.success(t('common.updateSuccess'))\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  // 重置选项\n  treeNodeAll.value = false\n  menuExpand.value = false\n  // 重置表单\n  formData.value = {\n    id: undefined,\n    name: '',\n    code: '',\n    menuIds: []\n  }\n  treeRef.value?.setCheckedNodes([])\n  formRef.value?.resetFields()\n}\n\n/** 全选/全不选 */\nconst handleCheckedTreeNodeAll = () => {\n  treeRef.value.setCheckedNodes(treeNodeAll.value ? menuOptions.value : [])\n}\n\n/** 展开/折叠全部 */\nconst handleCheckedTreeExpand = () => {\n  const nodes = treeRef.value?.store.nodesMap\n  for (let node in nodes) {\n    if (nodes[node].expanded === menuExpand.value) {\n      continue\n    }\n    nodes[node].expanded = menuExpand.value\n  }\n}\n</script>\n<style lang=\"scss\" scoped>\n.cardHeight {\n  width: 100%;\n  max-height: 400px;\n  overflow-y: scroll;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/role/RoleDataPermissionForm.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" title=\"菜单权限\" width=\"800\">\n    <el-form ref=\"formRef\" v-loading=\"formLoading\" :model=\"formData\" label-width=\"80px\">\n      <el-form-item label=\"角色名称\">\n        <el-tag>{{ formData.name }}</el-tag>\n      </el-form-item>\n      <el-form-item label=\"角色标识\">\n        <el-tag>{{ formData.code }}</el-tag>\n      </el-form-item>\n      <el-form-item label=\"权限范围\">\n        <el-select v-model=\"formData.dataScope\">\n          <el-option\n            v-for=\"item in getIntDictOptions(DICT_TYPE.SYSTEM_DATA_SCOPE)\"\n            :key=\"item.value\"\n            :label=\"item.label\"\n            :value=\"item.value\"\n          />\n        </el-select>\n      </el-form-item>\n    </el-form>\n    <el-form-item\n      v-if=\"formData.dataScope === SystemDataScopeEnum.DEPT_CUSTOM\"\n      label=\"权限范围\"\n      style=\"display: flex\"\n    >\n      <el-card class=\"card\" shadow=\"never\">\n        <template #header>\n          全选/全不选:\n          <el-switch\n            v-model=\"treeNodeAll\"\n            active-text=\"是\"\n            inactive-text=\"否\"\n            inline-prompt\n            @change=\"handleCheckedTreeNodeAll()\"\n          />\n          全部展开/折叠:\n          <el-switch\n            v-model=\"deptExpand\"\n            active-text=\"展开\"\n            inactive-text=\"折叠\"\n            inline-prompt\n            @change=\"handleCheckedTreeExpand\"\n          />\n          父子联动(选中父节点，自动选择子节点):\n          <el-switch v-model=\"checkStrictly\" active-text=\"是\" inactive-text=\"否\" inline-prompt />\n        </template>\n        <el-tree\n          ref=\"treeRef\"\n          :check-strictly=\"!checkStrictly\"\n          :data=\"deptOptions\"\n          :props=\"defaultProps\"\n          default-expand-all\n          empty-text=\"加载中，请稍后\"\n          node-key=\"id\"\n          show-checkbox\n        />\n      </el-card>\n    </el-form-item>\n    <template #footer>\n      <el-button :disabled=\"formLoading\" type=\"primary\" @click=\"submitForm\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getIntDictOptions } from '@/utils/dict'\nimport { defaultProps, handleTree } from '@/utils/tree'\nimport { SystemDataScopeEnum } from '@/utils/constants'\nimport * as RoleApi from '@/api/system/role'\nimport * as DeptApi from '@/api/system/dept'\nimport * as PermissionApi from '@/api/system/permission'\n\ndefineOptions({ name: 'SystemRoleDataPermissionForm' })\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formData = reactive({\n  id: undefined,\n  name: '',\n  code: '',\n  dataScope: undefined,\n  dataScopeDeptIds: []\n})\nconst formRef = ref() // 表单 Ref\nconst deptOptions = ref<any[]>([]) // 部门树形结构\nconst deptExpand = ref(true) // 展开/折叠\nconst treeRef = ref() // 菜单树组件 Ref\nconst treeNodeAll = ref(false) // 全选/全不选\nconst checkStrictly = ref(true) // 是否严格模式，即父子不关联\n\n/** 打开弹窗 */\nconst open = async (row: RoleApi.RoleVO) => {\n  dialogVisible.value = true\n  resetForm()\n  // 加载 Dept 列表。注意，必须放在前面，不然下面 setChecked 没数据节点\n  deptOptions.value = handleTree(await DeptApi.getSimpleDeptList())\n  // 设置数据\n  formData.id = row.id\n  formData.name = row.name\n  formData.code = row.code\n  formData.dataScope = row.dataScope\n  await nextTick()\n  // 需要在 DOM 渲染完成后，再设置选中状态\n  row.dataScopeDeptIds?.forEach((deptId: number): void => {\n    treeRef.value.setChecked(deptId, true, false)\n  })\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  formLoading.value = true\n  try {\n    const data = {\n      roleId: formData.id,\n      dataScope: formData.dataScope,\n      dataScopeDeptIds:\n        formData.dataScope !== SystemDataScopeEnum.DEPT_CUSTOM\n          ? []\n          : treeRef.value.getCheckedKeys(false)\n    }\n    await PermissionApi.assignRoleDataScope(data)\n    message.success(t('common.updateSuccess'))\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  // 重置选项\n  treeNodeAll.value = false\n  deptExpand.value = true\n  checkStrictly.value = true\n  // 重置表单\n  formData.value = {\n    id: undefined,\n    name: '',\n    code: '',\n    dataScope: undefined,\n    dataScopeDeptIds: []\n  }\n  treeRef.value?.setCheckedNodes([])\n  formRef.value?.resetFields()\n}\n\n/** 全选/全不选 */\nconst handleCheckedTreeNodeAll = () => {\n  treeRef.value.setCheckedNodes(treeNodeAll.value ? deptOptions.value : [])\n}\n\n/** 展开/折叠全部 */\nconst handleCheckedTreeExpand = () => {\n  const nodes = treeRef.value?.store.nodesMap\n  for (let node in nodes) {\n    if (nodes[node].expanded === deptExpand.value) {\n      continue\n    }\n    nodes[node].expanded = deptExpand.value\n  }\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/role/RoleForm.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" :title=\"dialogTitle\">\n    <el-form\n      ref=\"formRef\"\n      v-loading=\"formLoading\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"80px\"\n    >\n      <el-form-item label=\"角色名称\" prop=\"name\">\n        <el-input v-model=\"formData.name\" placeholder=\"请输入角色名称\" />\n      </el-form-item>\n      <el-form-item label=\"角色标识\" prop=\"code\">\n        <el-input v-model=\"formData.code\" placeholder=\"请输入角色标识\" />\n      </el-form-item>\n      <el-form-item label=\"显示顺序\" prop=\"sort\">\n        <el-input v-model=\"formData.sort\" placeholder=\"请输入显示顺序\" />\n      </el-form-item>\n      <el-form-item label=\"状态\" prop=\"status\">\n        <el-select v-model=\"formData.status\" clearable placeholder=\"请选择状态\">\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"备注\" prop=\"remark\">\n        <el-input v-model=\"formData.remark\" placeholder=\"请输备注\" type=\"textarea\" />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button :disabled=\"formLoading\" type=\"primary\" @click=\"submitForm\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getIntDictOptions } from '@/utils/dict'\nimport { CommonStatusEnum } from '@/utils/constants'\nimport * as RoleApi from '@/api/system/role'\n\ndefineOptions({ name: 'SystemRoleForm' })\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  name: '',\n  code: '',\n  sort: undefined,\n  status: CommonStatusEnum.ENABLE,\n  remark: ''\n})\nconst formRules = reactive({\n  name: [{ required: true, message: '角色名称不能为空', trigger: 'blur' }],\n  code: [{ required: true, message: '角色标识不能为空', trigger: 'change' }],\n  sort: [{ required: true, message: '显示顺序不能为空', trigger: 'change' }],\n  status: [{ required: true, message: '状态不能为空', trigger: 'change' }],\n  remark: [{ required: false, message: '备注不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await RoleApi.getRole(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    name: '',\n    code: '',\n    sort: undefined,\n    status: CommonStatusEnum.ENABLE,\n    remark: ''\n  }\n  formRef.value?.resetFields()\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as unknown as RoleApi.RoleVO\n    if (formType.value === 'create') {\n      await RoleApi.createRole(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await RoleApi.updateRole(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/role/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <!-- 搜索工作栏 -->\n    <el-form\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      :model=\"queryParams\"\n      class=\"-mb-15px\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"角色名称\" prop=\"name\">\n        <el-input\n          v-model=\"queryParams.name\"\n          class=\"!w-240px\"\n          clearable\n          placeholder=\"请输入角色名称\"\n          @keyup.enter=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"角色标识\" prop=\"code\">\n        <el-input\n          v-model=\"queryParams.code\"\n          class=\"!w-240px\"\n          clearable\n          placeholder=\"请输入角色标识\"\n          @keyup.enter=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"状态\" prop=\"status\">\n        <el-select v-model=\"queryParams.status\" class=\"!w-240px\" clearable placeholder=\"请选择状态\">\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"创建时间\" prop=\"createTime\">\n        <el-date-picker\n          v-model=\"queryParams.createTime\"\n          :default-time=\"[new Date('1 00:00:00'), new Date('1 23:59:59')]\"\n          class=\"!w-240px\"\n          end-placeholder=\"结束日期\"\n          start-placeholder=\"开始日期\"\n          type=\"daterange\"\n          value-format=\"YYYY-MM-DD HH:mm:ss\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\">\n          <Icon class=\"mr-5px\" icon=\"ep:search\" />\n          搜索\n        </el-button>\n        <el-button @click=\"resetQuery\">\n          <Icon class=\"mr-5px\" icon=\"ep:refresh\" />\n          重置\n        </el-button>\n        <el-button\n          v-hasPermi=\"['system:role:create']\"\n          plain\n          type=\"primary\"\n          @click=\"openForm('create')\"\n        >\n          <Icon class=\"mr-5px\" icon=\"ep:plus\" />\n          新增\n        </el-button>\n        <el-button\n          v-hasPermi=\"['system:role:export']\"\n          :loading=\"exportLoading\"\n          plain\n          type=\"success\"\n          @click=\"handleExport\"\n        >\n          <Icon class=\"mr-5px\" icon=\"ep:download\" />\n          导出\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column align=\"center\" label=\"角色编号\" prop=\"id\" />\n      <el-table-column align=\"center\" label=\"角色名称\" prop=\"name\" />\n      <el-table-column label=\"角色类型\" align=\"center\" prop=\"type\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.SYSTEM_ROLE_TYPE\" :value=\"scope.row.type\" />\n        </template>\n      </el-table-column>\n      <el-table-column align=\"center\" label=\"角色标识\" prop=\"code\" />\n      <el-table-column align=\"center\" label=\"显示顺序\" prop=\"sort\" />\n      <el-table-column align=\"center\" label=\"备注\" prop=\"remark\" />\n      <el-table-column align=\"center\" label=\"状态\" prop=\"status\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.COMMON_STATUS\" :value=\"scope.row.status\" />\n        </template>\n      </el-table-column>\n      <el-table-column\n        :formatter=\"dateFormatter\"\n        align=\"center\"\n        label=\"创建时间\"\n        prop=\"createTime\"\n        width=\"180\"\n      />\n      <el-table-column :width=\"300\" align=\"center\" label=\"操作\">\n        <template #default=\"scope\">\n          <el-button\n            v-hasPermi=\"['system:role:update']\"\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n          >\n            编辑\n          </el-button>\n          <el-button\n            v-hasPermi=\"['system:permission:assign-role-menu']\"\n            link\n            preIcon=\"ep:basketball\"\n            title=\"菜单权限\"\n            type=\"primary\"\n            @click=\"openAssignMenuForm(scope.row)\"\n          >\n            菜单权限\n          </el-button>\n          <el-button\n            v-hasPermi=\"['system:permission:assign-role-data-scope']\"\n            link\n            preIcon=\"ep:coin\"\n            title=\"数据权限\"\n            type=\"primary\"\n            @click=\"openDataPermissionForm(scope.row)\"\n          >\n            数据权限\n          </el-button>\n          <el-button\n            v-hasPermi=\"['system:role:delete']\"\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      v-model:limit=\"queryParams.pageSize\"\n      v-model:page=\"queryParams.pageNo\"\n      :total=\"total\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <RoleForm ref=\"formRef\" @success=\"getList\" />\n  <!-- 表单弹窗：菜单权限 -->\n  <RoleAssignMenuForm ref=\"assignMenuFormRef\" @success=\"getList\" />\n  <!-- 表单弹窗：数据权限 -->\n  <RoleDataPermissionForm ref=\"dataPermissionFormRef\" @success=\"getList\" />\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getIntDictOptions } from '@/utils/dict'\nimport { dateFormatter } from '@/utils/formatTime'\nimport download from '@/utils/download'\nimport * as RoleApi from '@/api/system/role'\nimport RoleForm from './RoleForm.vue'\nimport RoleAssignMenuForm from './RoleAssignMenuForm.vue'\nimport RoleDataPermissionForm from './RoleDataPermissionForm.vue'\n\ndefineOptions({ name: 'SystemRole' })\n\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  code: '',\n  name: '',\n  status: undefined,\n  createTime: []\n})\nconst queryFormRef = ref() // 搜索的表单\nconst exportLoading = ref(false) // 导出的加载中\n\n/** 查询角色列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await RoleApi.getRolePage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 数据权限操作 */\nconst dataPermissionFormRef = ref()\nconst openDataPermissionForm = async (row: RoleApi.RoleVO) => {\n  dataPermissionFormRef.value.open(row)\n}\n\n/** 菜单权限操作 */\nconst assignMenuFormRef = ref()\nconst openAssignMenuForm = async (row: RoleApi.RoleVO) => {\n  assignMenuFormRef.value.open(row)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await RoleApi.deleteRole(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 导出按钮操作 */\nconst handleExport = async () => {\n  try {\n    // 导出的二次确认\n    await message.exportConfirm()\n    // 发起导出\n    exportLoading.value = true\n    const data = await RoleApi.exportRole(queryParams)\n    download.excel(data, '角色列表.xls')\n  } catch {\n  } finally {\n    exportLoading.value = false\n  }\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/sms/channel/SmsChannelForm.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" :title=\"dialogTitle\">\n    <el-form\n      ref=\"formRef\"\n      v-loading=\"formLoading\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"130px\"\n    >\n      <el-form-item label=\"短信签名\" prop=\"signature\">\n        <el-input v-model=\"formData.signature\" placeholder=\"请输入短信签名\" />\n      </el-form-item>\n      <el-form-item label=\"渠道编码\" prop=\"code\">\n        <el-select v-model=\"formData.code\" clearable placeholder=\"请选择渠道编码\">\n          <el-option\n            v-for=\"dict in getStrDictOptions(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"启用状态\">\n        <el-radio-group v-model=\"formData.status\">\n          <el-radio\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)\"\n            :key=\"dict.value\"\n            :label=\"dict.value\"\n          >\n            {{ dict.label }}\n          </el-radio>\n        </el-radio-group>\n      </el-form-item>\n      <el-form-item label=\"备注\" prop=\"remark\">\n        <el-input v-model=\"formData.remark\" placeholder=\"请输入备注\" />\n      </el-form-item>\n      <el-form-item label=\"短信 API 的账号\" prop=\"apiKey\">\n        <el-input v-model=\"formData.apiKey\" placeholder=\"请输入短信 API 的账号\" />\n      </el-form-item>\n      <el-form-item label=\"短信 API 的密钥\" prop=\"apiSecret\">\n        <el-input v-model=\"formData.apiSecret\" placeholder=\"请输入短信 API 的密钥\" />\n      </el-form-item>\n      <el-form-item label=\"短信发送回调 URL\" prop=\"callbackUrl\">\n        <el-input v-model=\"formData.callbackUrl\" placeholder=\"请输入短信发送回调 URL\" />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button :disabled=\"formLoading\" type=\"primary\" @click=\"submitForm\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getIntDictOptions, getStrDictOptions } from '@/utils/dict'\nimport * as SmsChannelApi from '@/api/system/sms/smsChannel'\nimport { CommonStatusEnum } from '@/utils/constants'\n\ndefineOptions({ name: 'SystemSmsChannelForm' })\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  signature: '',\n  code: '',\n  status: CommonStatusEnum.ENABLE,\n  remark: '',\n  apiKey: '',\n  apiSecret: '',\n  callbackUrl: ''\n})\nconst formRules = reactive({\n  signature: [{ required: true, message: '短信签名不能为空', trigger: 'blur' }],\n  code: [{ required: true, message: '渠道编码不能为空', trigger: 'blur' }],\n  status: [{ required: true, message: '启用状态不能为空', trigger: 'blur' }],\n  apiKey: [{ required: true, message: '短信 API 的账号不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await SmsChannelApi.getSmsChannel(id)\n      console.log(formData)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as unknown as SmsChannelApi.SmsChannelVO\n    if (formType.value === 'create') {\n      await SmsChannelApi.createSmsChannel(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await SmsChannelApi.updateSmsChannel(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    signature: '',\n    code: '',\n    status: CommonStatusEnum.ENABLE,\n    remark: '',\n    apiKey: '',\n    apiSecret: '',\n    callbackUrl: ''\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/sms/channel/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"短信签名\" prop=\"signature\">\n        <el-input\n          v-model=\"queryParams.signature\"\n          placeholder=\"请输入短信签名\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"启用状态\" prop=\"status\">\n        <el-select v-model=\"queryParams.status\" placeholder=\"请选择启用状态\" clearable>\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"创建时间\" prop=\"createTime\">\n        <el-date-picker\n          v-model=\"queryParams.createTime\"\n          value-format=\"YYYY-MM-DD HH:mm:ss\"\n          type=\"daterange\"\n          start-placeholder=\"开始日期\"\n          end-placeholder=\"结束日期\"\n          :default-time=\"[new Date('1 00:00:00'), new Date('1 23:59:59')]\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n        <el-button\n          type=\"primary\"\n          plain\n          @click=\"openForm('create')\"\n          v-hasPermi=\"['system:sms-channel:create']\"\n        >\n          <Icon icon=\"ep:plus\" class=\"mr-5px\" /> 新增</el-button\n        >\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column label=\"编号\" align=\"center\" prop=\"id\" />\n      <el-table-column label=\"短信签名\" align=\"center\" prop=\"signature\" />\n      <el-table-column label=\"渠道编码\" align=\"center\" prop=\"code\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE\" :value=\"scope.row.code\" />\n        </template>\n      </el-table-column>\n      <el-table-column label=\"启用状态\" align=\"center\" prop=\"status\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.COMMON_STATUS\" :value=\"scope.row.status\" />\n        </template>\n      </el-table-column>\n      <el-table-column label=\"备注\" align=\"center\" prop=\"remark\" :show-overflow-tooltip=\"true\" />\n      <el-table-column\n        label=\"短信 API 的账号\"\n        align=\"center\"\n        prop=\"apiKey\"\n        :show-overflow-tooltip=\"true\"\n        width=\"180\"\n      />\n      <el-table-column\n        label=\"短信 API 的密钥\"\n        align=\"center\"\n        prop=\"apiSecret\"\n        :show-overflow-tooltip=\"true\"\n        width=\"180\"\n      />\n      <el-table-column\n        label=\"短信发送回调 URL\"\n        align=\"center\"\n        prop=\"callbackUrl\"\n        :show-overflow-tooltip=\"true\"\n        width=\"180\"\n      />\n      <el-table-column\n        label=\"创建时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        width=\"180\"\n        :formatter=\"dateFormatter\"\n      />\n      <el-table-column label=\"操作\" align=\"center\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n            v-hasPermi=\"['system:sms-channel:update']\"\n          >\n            编辑\n          </el-button>\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n            v-hasPermi=\"['system:sms-channel:delete']\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <SmsChannelForm ref=\"formRef\" @success=\"getList\" />\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getIntDictOptions } from '@/utils/dict'\nimport { dateFormatter } from '@/utils/formatTime'\nimport * as SmsChannelApi from '@/api/system/sms/smsChannel'\nimport SmsChannelForm from './SmsChannelForm.vue'\n\ndefineOptions({ name: 'SystemSmsChannel' })\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst loading = ref(false) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryFormRef = ref() // 搜索的表单\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  signature: undefined,\n  status: undefined,\n  createTime: []\n})\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await SmsChannelApi.getSmsChannelPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await SmsChannelApi.deleteSmsChannel(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/sms/log/SmsLogDetail.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" :max-height=\"500\" :scroll=\"true\" title=\"详情\" width=\"800\">\n    <el-descriptions :column=\"1\" border>\n      <el-descriptions-item label=\"日志主键\" min-width=\"120\">\n        {{ detailData.id }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"短信渠道\">\n        {{ channelList.find((channel) => channel.id === detailData.channelId)?.signature }}\n        <dict-tag :type=\"DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE\" :value=\"detailData.channelCode\" />\n      </el-descriptions-item>\n      <el-descriptions-item label=\"短信模板\">\n        {{ detailData.templateId }} | {{ detailData.templateCode }}\n        <dict-tag :type=\"DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE\" :value=\"detailData.templateType\" />\n      </el-descriptions-item>\n      <el-descriptions-item label=\"API 的模板编号\">\n        {{ detailData.apiTemplateId }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"用户信息\">\n        {{ detailData.mobile }}\n        <span v-if=\"detailData.userType && detailData.userId\">\n          <dict-tag :type=\"DICT_TYPE.USER_TYPE\" :value=\"detailData.userType\" />\n          ({{ detailData.userId }})\n        </span>\n      </el-descriptions-item>\n      <el-descriptions-item label=\"短信内容\">\n        {{ detailData.templateContent }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"短信参数\">\n        {{ detailData.templateParams }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"创建时间\">\n        {{ formatDate(detailData.createTime) }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"发送状态\">\n        <dict-tag :type=\"DICT_TYPE.SYSTEM_SMS_SEND_STATUS\" :value=\"detailData.sendStatus\" />\n      </el-descriptions-item>\n      <el-descriptions-item label=\"发送时间\">\n        {{ formatDate(detailData.sendTime) }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"API 发送结果\">\n        {{ detailData.apiSendCode }} | {{ detailData.apiSendMsg }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"API 短信编号\">\n        {{ detailData.apiSerialNo }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"API 请求编号\">\n        {{ detailData.apiRequestId }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"API 接收状态\">\n        <dict-tag :type=\"DICT_TYPE.SYSTEM_SMS_RECEIVE_STATUS\" :value=\"detailData.receiveStatus\" />\n        {{ formatDate(detailData.receiveTime) }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"API 接收结果\">\n        {{ detailData.apiReceiveCode }} | {{ detailData.apiReceiveMsg }}\n      </el-descriptions-item>\n    </el-descriptions>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE } from '@/utils/dict'\nimport { formatDate } from '@/utils/formatTime'\nimport * as SmsLogApi from '@/api/system/sms/smsLog'\nimport * as SmsChannelApi from '@/api/system/sms/smsChannel'\n\ndefineOptions({ name: 'SystemSmsLogDetail' })\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst detailLoading = ref(false) // 表单的加载中\nconst detailData = ref() // 详情数据\nconst channelList = ref([]) // 短信渠道列表\n\n/** 打开弹窗 */\nconst open = async (data: SmsLogApi.SmsLogVO) => {\n  dialogVisible.value = true\n  // 设置数据\n  detailLoading.value = true\n  try {\n    detailData.value = data\n  } finally {\n    detailLoading.value = false\n  }\n  // 加载渠道列表\n  channelList.value = await SmsChannelApi.getSimpleSmsChannelList()\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/sms/log/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <!-- 搜索工作栏 -->\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"100px\"\n    >\n      <el-form-item label=\"手机号\" prop=\"mobile\">\n        <el-input\n          v-model=\"queryParams.mobile\"\n          placeholder=\"请输入手机号\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"短信渠道\" prop=\"channelId\">\n        <el-select\n          v-model=\"queryParams.channelId\"\n          placeholder=\"请选择短信渠道\"\n          clearable\n          class=\"!w-240px\"\n        >\n          <el-option\n            v-for=\"channel in channelList\"\n            :key=\"channel.id\"\n            :value=\"channel.id\"\n            :label=\"\n              channel.signature +\n              `【 ${getDictLabel(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE, channel.code)}】`\n            \"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"模板编号\" prop=\"templateId\">\n        <el-input\n          v-model=\"queryParams.templateId\"\n          placeholder=\"请输入模板编号\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"发送状态\" prop=\"sendStatus\">\n        <el-select\n          v-model=\"queryParams.sendStatus\"\n          placeholder=\"请选择发送状态\"\n          clearable\n          class=\"!w-240px\"\n        >\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.SYSTEM_SMS_SEND_STATUS)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"发送时间\" prop=\"sendTime\">\n        <el-date-picker\n          v-model=\"queryParams.sendTime\"\n          value-format=\"YYYY-MM-DD HH:mm:ss\"\n          type=\"daterange\"\n          start-placeholder=\"开始日期\"\n          end-placeholder=\"结束日期\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"接收状态\" prop=\"receiveStatus\">\n        <el-select\n          v-model=\"queryParams.receiveStatus\"\n          placeholder=\"请选择接收状态\"\n          clearable\n          class=\"!w-240px\"\n        >\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.SYSTEM_SMS_RECEIVE_STATUS)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"接收时间\" prop=\"receiveTime\">\n        <el-date-picker\n          v-model=\"queryParams.receiveTime\"\n          value-format=\"YYYY-MM-DD HH:mm:ss\"\n          type=\"daterange\"\n          start-placeholder=\"开始日期\"\n          end-placeholder=\"结束日期\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n        <el-button\n          type=\"success\"\n          plain\n          @click=\"handleExport\"\n          :loading=\"exportLoading\"\n          v-hasPermi=\"['system:sms-log:export']\"\n        >\n          <Icon icon=\"ep:download\" class=\"mr-5px\" /> 导出\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column label=\"编号\" align=\"center\" prop=\"id\" />\n      <el-table-column\n        label=\"创建时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        width=\"180\"\n        :formatter=\"dateFormatter\"\n      />\n      <el-table-column label=\"手机号\" align=\"center\" prop=\"mobile\" width=\"120\">\n        <template #default=\"scope\">\n          <div>{{ scope.row.mobile }}</div>\n          <div v-if=\"scope.row.userType && scope.row.userId\">\n            <dict-tag :type=\"DICT_TYPE.USER_TYPE\" :value=\"scope.row.userType\" />\n            {{ '(' + scope.row.userId + ')' }}\n          </div>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"短信内容\" align=\"center\" prop=\"templateContent\" width=\"300\" />\n      <el-table-column label=\"发送状态\" align=\"center\" width=\"180\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.SYSTEM_SMS_SEND_STATUS\" :value=\"scope.row.sendStatus\" />\n          <div>{{ formatDate(scope.row.sendTime) }}</div>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"接收状态\" align=\"center\" width=\"180\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.SYSTEM_SMS_RECEIVE_STATUS\" :value=\"scope.row.receiveStatus\" />\n          <div>{{ formatDate(scope.row.receiveTime) }}</div>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"短信渠道\" align=\"center\" width=\"120\">\n        <template #default=\"scope\">\n          <div>\n            {{ channelList.find((channel) => channel.id === scope.row.channelId)?.signature }}\n          </div>\n          <dict-tag :type=\"DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE\" :value=\"scope.row.channelCode\" />\n        </template>\n      </el-table-column>\n      <el-table-column label=\"模板编号\" align=\"center\" prop=\"templateId\" />\n      <el-table-column label=\"短信类型\" align=\"center\" prop=\"templateType\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE\" :value=\"scope.row.templateType\" />\n        </template>\n      </el-table-column>\n      <el-table-column label=\"操作\" align=\"center\" fixed=\"right\" class-name=\"fixed-width\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openDetail(scope.row)\"\n            v-hasPermi=\"['system:sms-log:query']\"\n          >\n            详情\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：详情 -->\n  <SmsLogDetail ref=\"detailRef\" />\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getIntDictOptions, getDictLabel } from '@/utils/dict'\nimport { dateFormatter, formatDate } from '@/utils/formatTime'\nimport download from '@/utils/download'\nimport * as SmsChannelApi from '@/api/system/sms/smsChannel'\nimport * as SmsLogApi from '@/api/system/sms/smsLog'\nimport SmsLogDetail from './SmsLogDetail.vue'\n\ndefineOptions({ name: 'SystemSmsLog' })\n\nconst message = useMessage() // 消息弹窗\n\nconst loading = ref(false) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryFormRef = ref() // 搜索的表单\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  channelId: null,\n  templateId: null,\n  mobile: '',\n  sendStatus: null,\n  receiveStatus: null,\n  sendTime: [],\n  receiveTime: []\n})\nconst exportLoading = ref(false) // 导出的加载中\nconst channelList = ref([]) // 短信渠道列表\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await SmsLogApi.getSmsLogPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 导出按钮操作 */\nconst handleExport = async () => {\n  try {\n    // 导出的二次确认\n    await message.exportConfirm()\n    // 发起导出\n    exportLoading.value = true\n    const data = await SmsLogApi.exportSmsLog(queryParams)\n    download.excel(data, '短信日志.xls')\n  } catch {\n  } finally {\n    exportLoading.value = false\n  }\n}\n\n/** 详情操作 */\nconst detailRef = ref()\nconst openDetail = (data: SmsLogApi.SmsLogVO) => {\n  detailRef.value.open(data)\n}\n\n/** 初始化 **/\nonMounted(async () => {\n  await getList()\n  // 加载渠道列表\n  channelList.value = await SmsChannelApi.getSimpleSmsChannelList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/sms/template/SmsTemplateForm.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" :title=\"dialogTitle\">\n    <el-form\n      ref=\"formRef\"\n      v-loading=\"formLoading\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"140px\"\n    >\n      <el-form-item label=\"短信渠道编号\" prop=\"channelId\">\n        <el-select v-model=\"formData.channelId\" placeholder=\"请选择短信渠道编号\">\n          <el-option\n            v-for=\"channel in channelList\"\n            :key=\"channel.id\"\n            :label=\"\n              channel.signature +\n              `【 ${getDictLabel(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE, channel.code)}】`\n            \"\n            :value=\"channel.id\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"短信类型\" prop=\"type\">\n        <el-select v-model=\"formData.type\" placeholder=\"请选择短信类型\">\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"模板编号\" prop=\"code\">\n        <el-input v-model=\"formData.code\" placeholder=\"请输入模板编号\" />\n      </el-form-item>\n      <el-form-item label=\"模板名称\" prop=\"name\">\n        <el-input v-model=\"formData.name\" placeholder=\"请输入模板名称\" />\n      </el-form-item>\n      <el-form-item label=\"模板内容\" prop=\"content\">\n        <el-input v-model=\"formData.content\" placeholder=\"请输入模板内容\" type=\"textarea\" />\n      </el-form-item>\n      <el-form-item label=\"开启状态\" prop=\"status\">\n        <el-radio-group v-model=\"formData.status\">\n          <el-radio\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)\"\n            :key=\"dict.value\"\n            :label=\"dict.value\"\n          >\n            {{ dict.label }}\n          </el-radio>\n        </el-radio-group>\n      </el-form-item>\n      <el-form-item label=\"短信 API 模板编号\" prop=\"apiTemplateId\">\n        <el-input v-model=\"formData.apiTemplateId\" placeholder=\"请输入短信 API 的模板编号\" />\n      </el-form-item>\n      <el-form-item label=\"备注\" prop=\"remark\">\n        <el-input v-model=\"formData.remark\" placeholder=\"请输入备注\" />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button :disabled=\"formLoading\" type=\"primary\" @click=\"submitForm\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getDictLabel, getIntDictOptions } from '@/utils/dict'\nimport * as SmsTemplateApi from '@/api/system/sms/smsTemplate'\nimport * as SmsChannelApi from '@/api/system/sms/smsChannel'\nimport { CommonStatusEnum } from '@/utils/constants'\n\ndefineOptions({ name: 'SystemSmsTemplateForm' })\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型\nconst formData = ref<SmsTemplateApi.SmsTemplateVO>({\n  id: undefined,\n  type: undefined,\n  status: CommonStatusEnum.ENABLE,\n  code: '',\n  name: '',\n  content: '',\n  remark: '',\n  apiTemplateId: '',\n  channelId: undefined\n})\nconst formRules = reactive({\n  type: [{ required: true, message: '短信类型不能为空', trigger: 'change' }],\n  status: [{ required: true, message: '开启状态不能为空', trigger: 'blur' }],\n  code: [{ required: true, message: '模板编码不能为空', trigger: 'blur' }],\n  name: [{ required: true, message: '模板名称不能为空', trigger: 'blur' }],\n  content: [{ required: true, message: '模板内容不能为空', trigger: 'blur' }],\n  apiTemplateId: [{ required: true, message: '短信 API 的模板编号不能为空', trigger: 'blur' }],\n  channelId: [{ required: true, message: '短信渠道编号不能为空', trigger: 'change' }]\n})\nconst formRef = ref() // 表单 Ref\nconst channelList = ref<SmsChannelApi.SmsChannelVO[]>([]) // 短信渠道列表\n\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await SmsTemplateApi.getSmsTemplate(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n  // 加载渠道列表\n  channelList.value = await SmsChannelApi.getSimpleSmsChannelList()\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  formLoading.value = true\n  try {\n    const data = formData.value as SmsTemplateApi.SmsTemplateVO\n    if (formType.value === 'create') {\n      await SmsTemplateApi.createSmsTemplate(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await SmsTemplateApi.updateSmsTemplate(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    type: undefined,\n    status: CommonStatusEnum.ENABLE,\n    code: '',\n    name: '',\n    content: '',\n    remark: '',\n    apiTemplateId: '',\n    channelId: undefined\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/sms/template/SmsTemplateSendForm.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" title=\"测试\">\n    <el-form\n      ref=\"formRef\"\n      v-loading=\"formLoading\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"140px\"\n    >\n      <el-form-item label=\"模板内容\" prop=\"content\">\n        <el-input\n          v-model=\"formData.content\"\n          placeholder=\"请输入模板内容\"\n          readonly\n          type=\"textarea\"\n        />\n      </el-form-item>\n      <el-form-item label=\"手机号\" prop=\"mobile\">\n        <el-input v-model=\"formData.mobile\" placeholder=\"请输入手机号\" />\n      </el-form-item>\n      <el-form-item\n        v-for=\"param in formData.params\"\n        :key=\"param\"\n        :label=\"'参数 {' + param + '}'\"\n        :prop=\"'templateParams.' + param\"\n      >\n        <el-input\n          v-model=\"formData.templateParams[param]\"\n          :placeholder=\"'请输入 ' + param + ' 参数'\"\n        />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button :disabled=\"formLoading\" type=\"primary\" @click=\"submitForm\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport * as SmsTemplateApi from '@/api/system/sms/smsTemplate'\n\ndefineOptions({ name: 'SystemSmsTemplateSendForm' })\n\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\n\n// 发送短信表单相关\nconst formData = ref({\n  content: '',\n  params: {},\n  mobile: '',\n  templateCode: '',\n  templateParams: new Map()\n})\nconst formRules = reactive({\n  mobile: [{ required: true, message: '手机不能为空', trigger: 'blur' }],\n  templateCode: [{ required: true, message: '模版编码不能为空', trigger: 'blur' }],\n  templateParams: {}\n})\nconst formRef = ref() // 表单 Ref\n\nconst open = async (id: number) => {\n  dialogVisible.value = true\n  resetForm()\n  // 设置数据\n  formLoading.value = true\n  try {\n    const data = await SmsTemplateApi.getSmsTemplate(id)\n    // 设置动态表单\n    formData.value.content = data.content\n    formData.value.params = data.params\n    formData.value.templateCode = data.code\n    formData.value.templateParams = data.params.reduce((obj, item) => {\n      obj[item] = '' // 给每个动态属性赋值，避免无法读取\n      return obj\n    }, {})\n    formRules.templateParams = data.params.reduce((obj, item) => {\n      obj[item] = { required: true, message: '参数 ' + item + ' 不能为空', trigger: 'blur' }\n      return obj\n    }, {})\n  } finally {\n    formLoading.value = false\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as SmsTemplateApi.SendSmsReqVO\n    const logId = await SmsTemplateApi.sendSms(data)\n    if (logId) {\n      message.success('提交发送成功！发送结果，见发送日志编号：' + logId)\n    }\n    dialogVisible.value = false\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    content: '',\n    params: {},\n    mobile: '',\n    templateCode: '',\n    templateParams: new Map()\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/sms/template/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <!-- 搜索工作栏 -->\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"150px\"\n    >\n      <el-form-item label=\"短信类型\" prop=\"type\">\n        <el-select\n          v-model=\"queryParams.type\"\n          placeholder=\"请选择短信类型\"\n          clearable\n          class=\"!w-240px\"\n        >\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"开启状态\" prop=\"status\">\n        <el-select\n          v-model=\"queryParams.status\"\n          placeholder=\"请选择开启状态\"\n          clearable\n          class=\"!w-240px\"\n        >\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"模板编码\" prop=\"code\">\n        <el-input\n          v-model=\"queryParams.code\"\n          placeholder=\"请输入模板编码\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"短信 API 的模板编号\" prop=\"apiTemplateId\">\n        <el-input\n          v-model=\"queryParams.apiTemplateId\"\n          placeholder=\"请输入短信 API 的模板编号\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"短信渠道\" prop=\"channelId\">\n        <el-select\n          v-model=\"queryParams.channelId\"\n          placeholder=\"请选择短信渠道\"\n          clearable\n          class=\"!w-240px\"\n        >\n          <el-option\n            v-for=\"channel in channelList\"\n            :key=\"channel.id\"\n            :value=\"channel.id\"\n            :label=\"\n              channel.signature +\n              `【 ${getDictLabel(DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE, channel.code)}】`\n            \"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"创建时间\" prop=\"createTime\">\n        <el-date-picker\n          v-model=\"queryParams.createTime\"\n          style=\"width: 240px\"\n          type=\"daterange\"\n          value-format=\"YYYY-MM-DD HH:mm:ss\"\n          start-placeholder=\"开始日期\"\n          end-placeholder=\"结束日期\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n        <el-button\n          type=\"primary\"\n          plain\n          @click=\"openForm('create')\"\n          v-hasPermi=\"['system:sms-template:create']\"\n        >\n          <Icon icon=\"ep:plus\" class=\"mr-5px\" />新增\n        </el-button>\n        <el-button\n          type=\"success\"\n          plain\n          @click=\"handleExport\"\n          :loading=\"exportLoading\"\n          v-hasPermi=\"['system:sms-template:export']\"\n        >\n          <Icon icon=\"ep:download\" class=\"mr-5px\" /> 导出\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column\n        label=\"模板编码\"\n        align=\"center\"\n        prop=\"code\"\n        width=\"120\"\n        :show-overflow-tooltip=\"true\"\n      />\n      <el-table-column\n        label=\"模板名称\"\n        align=\"center\"\n        prop=\"name\"\n        width=\"120\"\n        :show-overflow-tooltip=\"true\"\n      />\n      <el-table-column\n        label=\"模板内容\"\n        align=\"center\"\n        prop=\"content\"\n        width=\"200\"\n        :show-overflow-tooltip=\"true\"\n      />\n      <el-table-column label=\"短信类型\" align=\"center\" prop=\"type\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.SYSTEM_SMS_TEMPLATE_TYPE\" :value=\"scope.row.type\" />\n        </template>\n      </el-table-column>\n      <el-table-column label=\"状态\" align=\"center\" prop=\"status\" width=\"80\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.COMMON_STATUS\" :value=\"scope.row.status\" />\n        </template>\n      </el-table-column>\n      <el-table-column label=\"备注\" align=\"center\" prop=\"remark\" />\n      <el-table-column\n        label=\"短信 API 的模板编号\"\n        align=\"center\"\n        prop=\"apiTemplateId\"\n        width=\"200\"\n        :show-overflow-tooltip=\"true\"\n      />\n      <el-table-column label=\"短信渠道\" align=\"center\" width=\"120\">\n        <template #default=\"scope\">\n          <div>\n            {{ channelList.find((channel) => channel.id === scope.row.channelId)?.signature }}\n          </div>\n          <dict-tag :type=\"DICT_TYPE.SYSTEM_SMS_CHANNEL_CODE\" :value=\"scope.row.channelCode\" />\n        </template>\n      </el-table-column>\n      <el-table-column\n        label=\"创建时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        width=\"180\"\n        :formatter=\"dateFormatter\"\n      />\n      <el-table-column label=\"操作\" align=\"center\" width=\"210\" fixed=\"right\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n            v-hasPermi=\"['system:sms-template:update']\"\n          >\n            修改\n          </el-button>\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openSendForm(scope.row.id)\"\n            v-hasPermi=\"['system:sms-template:send-sms']\"\n          >\n            测试\n          </el-button>\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n            v-hasPermi=\"['system:sms-template:delete']\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <SmsTemplateForm ref=\"formRef\" @success=\"getList\" />\n  <!-- 表单弹窗：测试发送 -->\n  <SmsTemplateSendForm ref=\"sendFormRef\" />\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getIntDictOptions, getDictLabel } from '@/utils/dict'\nimport { dateFormatter } from '@/utils/formatTime'\nimport * as SmsTemplateApi from '@/api/system/sms/smsTemplate'\nimport * as SmsChannelApi from '@/api/system/sms/smsChannel'\nimport download from '@/utils/download'\nimport SmsTemplateForm from './SmsTemplateForm.vue'\nimport SmsTemplateSendForm from './SmsTemplateSendForm.vue'\n\ndefineOptions({ name: 'SystemSmsTemplate' })\n\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(false) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryFormRef = ref() // 搜索的表单\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  type: undefined,\n  status: undefined,\n  code: '',\n  content: '',\n  apiTemplateId: '',\n  channelId: undefined,\n  createTime: []\n})\nconst exportLoading = ref(false) // 导出的加载中\nconst channelList = ref<SmsChannelApi.SmsChannelVO[]>([]) // 短信渠道列表\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await SmsTemplateApi.getSmsTemplatePage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 发送短信按钮 */\nconst sendFormRef = ref()\nconst openSendForm = (id: number) => {\n  sendFormRef.value.open(id)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await SmsTemplateApi.deleteSmsTemplate(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 导出按钮操作 */\nconst handleExport = async () => {\n  try {\n    // 导出的二次确认\n    await message.exportConfirm()\n    // 发起导出\n    exportLoading.value = true\n    const data = await SmsTemplateApi.exportSmsTemplate(queryParams)\n    download.excel(data, '短信模板.xls')\n  } catch {\n  } finally {\n    exportLoading.value = false\n  }\n}\n\n/** 初始化 **/\nonMounted(async () => {\n  await getList()\n  // 加载渠道列表\n  channelList.value = await SmsChannelApi.getSimpleSmsChannelList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/social/client/SocialClientForm.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" :title=\"dialogTitle\">\n    <el-form\n      ref=\"formRef\"\n      v-loading=\"formLoading\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"120px\"\n    >\n      <el-form-item label=\"应用名\" prop=\"name\">\n        <el-input v-model=\"formData.name\" placeholder=\"请输入应用名\" />\n      </el-form-item>\n      <el-form-item label=\"社交平台\" prop=\"socialType\">\n        <el-radio-group v-model=\"formData.socialType\">\n          <el-radio\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.SYSTEM_SOCIAL_TYPE)\"\n            :key=\"dict.value\"\n            :label=\"dict.value\"\n          >\n            {{ dict.label }}\n          </el-radio>\n        </el-radio-group>\n      </el-form-item>\n      <el-form-item label=\"用户类型\" prop=\"userType\">\n        <el-radio-group v-model=\"formData.userType\">\n          <el-radio\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.USER_TYPE)\"\n            :key=\"dict.value\"\n            :label=\"dict.value\"\n          >\n            {{ dict.label }}\n          </el-radio>\n        </el-radio-group>\n      </el-form-item>\n      <el-form-item label=\"客户端编号\" prop=\"clientId\">\n        <el-input v-model=\"formData.clientId\" placeholder=\"请输入客户端编号,对应各平台的appKey\" />\n      </el-form-item>\n      <el-form-item label=\"客户端密钥\" prop=\"clientSecret\">\n        <el-input\n          v-model=\"formData.clientSecret\"\n          placeholder=\"请输入客户端密钥,对应各平台的appSecret\"\n        />\n      </el-form-item>\n      <el-form-item label=\"agentId\" prop=\"agentId\" v-if=\"formData!.socialType === 30\">\n        <el-input v-model=\"formData.agentId\" placeholder=\"授权方的网页应用 ID，有则填\" />\n      </el-form-item>\n      <el-form-item label=\"状态\" prop=\"status\">\n        <el-radio-group v-model=\"formData.status\">\n          <el-radio\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)\"\n            :key=\"dict.value\"\n            :label=\"dict.value\"\n          >\n            {{ dict.label }}\n          </el-radio>\n        </el-radio-group>\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button :disabled=\"formLoading\" type=\"primary\" @click=\"submitForm\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getIntDictOptions } from '@/utils/dict'\nimport * as SocialClientApi from '@/api/system/social/client'\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  name: undefined,\n  socialType: undefined,\n  userType: undefined,\n  clientId: undefined,\n  clientSecret: undefined,\n  agentId: undefined,\n  status: 0\n})\nconst formRules = reactive({\n  name: [{ required: true, message: '应用名不能为空', trigger: 'blur' }],\n  socialType: [{ required: true, message: '社交平台不能为空', trigger: 'blur' }],\n  userType: [{ required: true, message: '用户类型不能为空', trigger: 'blur' }],\n  clientId: [{ required: true, message: '客户端编号不能为空', trigger: 'blur' }],\n  clientSecret: [{ required: true, message: '客户端密钥不能为空', trigger: 'blur' }],\n  status: [{ required: true, message: '状态不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await SocialClientApi.getSocialClient(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as unknown as SocialClientApi.SocialClientVO\n    if (formType.value === 'create') {\n      await SocialClientApi.createSocialClient(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await SocialClientApi.updateSocialClient(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    name: undefined,\n    socialType: undefined,\n    userType: undefined,\n    clientId: undefined,\n    clientSecret: undefined,\n    agentId: undefined,\n    status: 0\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/social/client/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <!-- 搜索工作栏 -->\n    <el-form\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      :model=\"queryParams\"\n      class=\"-mb-15px\"\n      label-width=\"130px\"\n    >\n      <el-form-item label=\"应用名\" prop=\"name\">\n        <el-input\n          v-model=\"queryParams.name\"\n          class=\"!w-240px\"\n          clearable\n          placeholder=\"请输入应用名\"\n          @keyup.enter=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"社交平台\" prop=\"socialType\">\n        <el-select\n          v-model=\"queryParams.socialType\"\n          class=\"!w-240px\"\n          clearable\n          placeholder=\"请选择社交平台\"\n        >\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.SYSTEM_SOCIAL_TYPE)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"用户类型\" prop=\"userType\">\n        <el-select\n          v-model=\"queryParams.userType\"\n          class=\"!w-240px\"\n          clearable\n          placeholder=\"请选择用户类型\"\n        >\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.USER_TYPE)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"客户端编号\" prop=\"clientId\">\n        <el-input\n          v-model=\"queryParams.clientId\"\n          class=\"!w-240px\"\n          clearable\n          placeholder=\"请输入客户端编号\"\n          @keyup.enter=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"状态\" prop=\"status\">\n        <el-select v-model=\"queryParams.status\" class=\"!w-240px\" clearable placeholder=\"请选择状态\">\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\">\n          <Icon class=\"mr-5px\" icon=\"ep:search\" />\n          搜索\n        </el-button>\n        <el-button @click=\"resetQuery\">\n          <Icon class=\"mr-5px\" icon=\"ep:refresh\" />\n          重置\n        </el-button>\n        <el-button\n          v-hasPermi=\"['system:social-client:create']\"\n          plain\n          type=\"primary\"\n          @click=\"openForm('create')\"\n        >\n          <Icon class=\"mr-5px\" icon=\"ep:plus\" />\n          新增\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\" :show-overflow-tooltip=\"true\" :stripe=\"true\">\n      <el-table-column align=\"center\" label=\"编号\" prop=\"id\" />\n      <el-table-column align=\"center\" label=\"应用名\" prop=\"name\" />\n      <el-table-column align=\"center\" label=\"社交平台\" prop=\"socialType\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.SYSTEM_SOCIAL_TYPE\" :value=\"scope.row.socialType\" />\n        </template>\n      </el-table-column>\n      <el-table-column align=\"center\" label=\"用户类型\" prop=\"userType\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.USER_TYPE\" :value=\"scope.row.userType\" />\n        </template>\n      </el-table-column>\n      <el-table-column align=\"center\" label=\"客户端编号\" prop=\"clientId\" width=\"180px\" />\n      <el-table-column align=\"center\" label=\"状态\" prop=\"status\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.COMMON_STATUS\" :value=\"scope.row.status\" />\n        </template>\n      </el-table-column>\n      <el-table-column\n        :formatter=\"dateFormatter\"\n        align=\"center\"\n        label=\"创建时间\"\n        prop=\"createTime\"\n        width=\"180px\"\n      />\n      <el-table-column align=\"center\" label=\"操作\">\n        <template #default=\"scope\">\n          <el-button\n            v-hasPermi=\"['system:social-client:update']\"\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n          >\n            编辑\n          </el-button>\n          <el-button\n            v-hasPermi=\"['system:social-client:delete']\"\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      v-model:limit=\"queryParams.pageSize\"\n      v-model:page=\"queryParams.pageNo\"\n      :total=\"total\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <SocialClientForm ref=\"formRef\" @success=\"getList\" />\n</template>\n\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getIntDictOptions } from '@/utils/dict'\nimport { dateFormatter } from '@/utils/formatTime'\nimport * as SocialClientApi from '@/api/system/social/client'\nimport SocialClientForm from './SocialClientForm.vue'\n\ndefineOptions({ name: 'SocialClient' })\n\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  name: undefined,\n  socialType: undefined,\n  userType: undefined,\n  clientId: undefined,\n  status: undefined\n})\nconst queryFormRef = ref() // 搜索的表单\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await SocialClientApi.getSocialClientPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await SocialClientApi.deleteSocialClient(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/social/user/SocialUserDetail.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" title=\"详情\" width=\"800\">\n    <el-descriptions :column=\"1\" border>\n      <el-descriptions-item label=\"社交平台\" min-width=\"160\">\n        <dict-tag :type=\"DICT_TYPE.SYSTEM_SOCIAL_TYPE\" :value=\"detailData.type\" />\n      </el-descriptions-item>\n      <el-descriptions-item label=\"用户昵称\" min-width=\"120\">\n        {{ detailData.nickname }}\n      </el-descriptions-item>\n      <el-descriptions label=\"用户头像\" min-width=\"120\">\n        <el-image :src=\"detailData.avatar\" class=\"h-30px w-30px\" />\n      </el-descriptions>\n      <el-descriptions-item label=\"社交 token\" min-width=\"120\">\n        {{ detailData.token }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"原始 Token 数据\" min-width=\"120\">\n        <el-input\n          v-model=\"detailData.rawTokenInfo\"\n          :autosize=\"{ maxRows: 20 }\"\n          :readonly=\"true\"\n          type=\"textarea\"\n        />\n      </el-descriptions-item>\n      <el-descriptions-item label=\"原始 User 数据\" min-width=\"120\">\n        <el-input\n          v-model=\"detailData.rawUserInfo\"\n          :autosize=\"{ maxRows: 20 }\"\n          :readonly=\"true\"\n          type=\"textarea\"\n        />\n      </el-descriptions-item>\n      <el-descriptions-item label=\"最后一次的认证 code\" min-width=\"120\">\n        {{ detailData.code }}\n      </el-descriptions-item>\n      <el-descriptions-item label=\"最后一次的认证 state\" min-width=\"120\">\n        {{ detailData.state }}\n      </el-descriptions-item>\n    </el-descriptions>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE } from '@/utils/dict'\nimport * as SocialUserApi from '@/api/system/social/user'\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst detailLoading = ref(false) // 表单的加载中\nconst detailData = ref({} as SocialUserApi.SocialUserVO) // 详情数据\n\n/** 打开弹窗 */\nconst open = async (id: number) => {\n  dialogVisible.value = true\n  // 设置数据\n  try {\n    detailData.value = await SocialUserApi.getSocialUser(id)\n  } finally {\n    detailLoading.value = false\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/social/user/index.vue",
    "content": "<template>\n  <ContentWrap>\n    <!-- 搜索工作栏 -->\n    <el-form\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      :model=\"queryParams\"\n      class=\"-mb-15px\"\n      label-width=\"120px\"\n    >\n      <el-form-item label=\"社交平台\" prop=\"type\">\n        <el-select\n          v-model=\"queryParams.type\"\n          class=\"!w-240px\"\n          clearable\n          placeholder=\"请选择社交平台\"\n        >\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.SYSTEM_SOCIAL_TYPE)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"用户昵称\" prop=\"nickname\">\n        <el-input\n          v-model=\"queryParams.nickname\"\n          class=\"!w-240px\"\n          clearable\n          placeholder=\"请输入用户昵称\"\n          @keyup.enter=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"社交 openid\" prop=\"openid\">\n        <el-input\n          v-model=\"queryParams.openid\"\n          class=\"!w-240px\"\n          clearable\n          placeholder=\"请输入社交 openid\"\n          @keyup.enter=\"handleQuery\"\n        />\n      </el-form-item>\n      <el-form-item label=\"创建时间\" prop=\"createTime\">\n        <el-date-picker\n          v-model=\"queryParams.createTime\"\n          :default-time=\"[new Date('1 00:00:00'), new Date('1 23:59:59')]\"\n          class=\"!w-240px\"\n          end-placeholder=\"结束日期\"\n          start-placeholder=\"开始日期\"\n          type=\"daterange\"\n          value-format=\"YYYY-MM-DD HH:mm:ss\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\">\n          <Icon class=\"mr-5px\" icon=\"ep:search\" />\n          搜索\n        </el-button>\n        <el-button @click=\"resetQuery\">\n          <Icon class=\"mr-5px\" icon=\"ep:refresh\" />\n          重置\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\" :show-overflow-tooltip=\"true\" :stripe=\"true\">\n      <el-table-column align=\"center\" label=\"社交平台\" prop=\"type\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.SYSTEM_SOCIAL_TYPE\" :value=\"scope.row.type\" />\n        </template>\n      </el-table-column>\n      <el-table-column align=\"center\" label=\"社交 openid\" prop=\"openid\" />\n      <el-table-column align=\"center\" label=\"用户昵称\" prop=\"nickname\" />\n      <el-table-column align=\"center\" label=\"用户头像\" prop=\"avatar\">\n        <template #default=\"{ row }\">\n          <el-image :src=\"row.avatar\" class=\"h-30px w-30px\" @click=\"imagePreview(row.avatar)\" />\n        </template>\n      </el-table-column>\n      <el-table-column\n        :formatter=\"dateFormatter\"\n        align=\"center\"\n        label=\"创建时间\"\n        prop=\"createTime\"\n        width=\"180px\"\n      />\n      <el-table-column\n        :formatter=\"dateFormatter\"\n        align=\"center\"\n        label=\"更新时间\"\n        prop=\"updateTime\"\n        width=\"180px\"\n      />\n      <el-table-column align=\"center\" fixed=\"right\" label=\"操作\">\n        <template #default=\"scope\">\n          <el-button\n            v-hasPermi=\"['system:social-user:query']\"\n            link\n            type=\"primary\"\n            @click=\"openDetail(scope.row.id)\"\n          >\n            详情\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      v-model:limit=\"queryParams.pageSize\"\n      v-model:page=\"queryParams.pageNo\"\n      :total=\"total\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：详情 -->\n  <SocialUserDetail ref=\"detailRef\" />\n</template>\n\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getIntDictOptions } from '@/utils/dict'\nimport { dateFormatter } from '@/utils/formatTime'\nimport * as SocialUserApi from '@/api/system/social/user'\nimport SocialUserDetail from './SocialUserDetail.vue'\nimport { createImageViewer } from '@/components/ImageViewer'\n\ndefineOptions({ name: 'SocialUser' })\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  type: undefined,\n  openid: undefined,\n  nickname: undefined,\n  createTime: []\n})\nconst queryFormRef = ref() // 搜索的表单\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await SocialUserApi.getSocialUserPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\nconst imagePreview = (imgUrl: string) => {\n  createImageViewer({\n    urlList: [imgUrl]\n  })\n}\n\n/** 详情操作 */\nconst detailRef = ref()\nconst openDetail = (id: number) => {\n  detailRef.value.open(id)\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/tenant/TenantForm.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" :title=\"dialogTitle\" width=\"50%\">\n    <el-form\n      ref=\"formRef\"\n      v-loading=\"formLoading\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"80px\"\n    >\n      <el-form-item label=\"租户名\" prop=\"name\">\n        <el-input v-model=\"formData.name\" placeholder=\"请输入租户名\" />\n      </el-form-item>\n      <el-form-item label=\"租户套餐\" prop=\"packageId\">\n        <el-select v-model=\"formData.packageId\" clearable placeholder=\"请选择租户套餐\">\n          <el-option\n            v-for=\"item in packageList\"\n            :key=\"item.id\"\n            :label=\"item.name\"\n            :value=\"item.id\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"联系人\" prop=\"contactName\">\n        <el-input v-model=\"formData.contactName\" placeholder=\"请输入联系人\" />\n      </el-form-item>\n      <el-form-item label=\"联系手机\" prop=\"contactMobile\">\n        <el-input v-model=\"formData.contactMobile\" placeholder=\"请输入联系手机\" />\n      </el-form-item>\n      <el-form-item v-if=\"formData.id === undefined\" label=\"用户名称\" prop=\"username\">\n        <el-input v-model=\"formData.username\" placeholder=\"请输入用户名称\" />\n      </el-form-item>\n      <el-form-item v-if=\"formData.id === undefined\" label=\"用户密码\" prop=\"password\">\n        <el-input\n          v-model=\"formData.password\"\n          placeholder=\"请输入用户密码\"\n          show-password\n          type=\"password\"\n        />\n      </el-form-item>\n      <el-form-item label=\"账号额度\" prop=\"accountCount\">\n        <el-input-number\n          v-model=\"formData.accountCount\"\n          :min=\"0\"\n          controls-position=\"right\"\n          placeholder=\"请输入账号额度\"\n        />\n      </el-form-item>\n      <el-form-item label=\"过期时间\" prop=\"expireTime\">\n        <el-date-picker\n          v-model=\"formData.expireTime\"\n          clearable\n          placeholder=\"请选择过期时间\"\n          type=\"date\"\n          value-format=\"x\"\n        />\n      </el-form-item>\n      <el-form-item label=\"绑定域名\" prop=\"website\">\n        <el-input v-model=\"formData.website\" placeholder=\"请输入绑定域名\" />\n      </el-form-item>\n      <el-form-item label=\"租户状态\" prop=\"status\">\n        <el-radio-group v-model=\"formData.status\">\n          <el-radio\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)\"\n            :key=\"dict.value\"\n            :label=\"dict.value\"\n          >\n            {{ dict.label }}\n          </el-radio>\n        </el-radio-group>\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button :disabled=\"formLoading\" type=\"primary\" @click=\"submitForm\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getIntDictOptions } from '@/utils/dict'\nimport * as TenantApi from '@/api/system/tenant'\nimport { CommonStatusEnum } from '@/utils/constants'\nimport * as TenantPackageApi from '@/api/system/tenantPackage'\n\ndefineOptions({ name: 'SystemTenantForm' })\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: undefined,\n  name: undefined,\n  packageId: undefined,\n  contactName: undefined,\n  contactMobile: undefined,\n  accountCount: undefined,\n  expireTime: undefined,\n  website: undefined,\n  status: CommonStatusEnum.ENABLE,\n  // 新增专属\n  username: undefined,\n  password: undefined\n})\nconst formRules = reactive({\n  name: [{ required: true, message: '租户名不能为空', trigger: 'blur' }],\n  packageId: [{ required: true, message: '租户套餐不能为空', trigger: 'blur' }],\n  contactName: [{ required: true, message: '联系人不能为空', trigger: 'blur' }],\n  status: [{ required: true, message: '租户状态不能为空', trigger: 'blur' }],\n  accountCount: [{ required: true, message: '账号额度不能为空', trigger: 'blur' }],\n  expireTime: [{ required: true, message: '过期时间不能为空', trigger: 'blur' }],\n  website: [{ required: true, message: '绑定域名不能为空', trigger: 'blur' }],\n  username: [{ required: true, message: '用户名称不能为空', trigger: 'blur' }],\n  password: [{ required: true, message: '用户密码不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\nconst packageList = ref([] as TenantPackageApi.TenantPackageVO[]) // 租户套餐\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await TenantApi.getTenant(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n  // 加载套餐列表\n  packageList.value = await TenantPackageApi.getTenantPackageList()\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as unknown as TenantApi.TenantVO\n    if (formType.value === 'create') {\n      await TenantApi.createTenant(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await TenantApi.updateTenant(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: undefined,\n    name: undefined,\n    packageId: undefined,\n    contactName: undefined,\n    contactMobile: undefined,\n    accountCount: undefined,\n    expireTime: undefined,\n    website: undefined,\n    status: CommonStatusEnum.ENABLE,\n    username: undefined,\n    password: undefined\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/tenant/index.vue",
    "content": "<template>\n  <!-- 搜索 -->\n  <ContentWrap>\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"租户名\" prop=\"name\">\n        <el-input\n          v-model=\"queryParams.name\"\n          placeholder=\"请输入租户名\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"联系人\" prop=\"contactName\">\n        <el-input\n          v-model=\"queryParams.contactName\"\n          placeholder=\"请输入联系人\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"联系手机\" prop=\"contactMobile\">\n        <el-input\n          v-model=\"queryParams.contactMobile\"\n          placeholder=\"请输入联系手机\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"租户状态\" prop=\"status\">\n        <el-select\n          v-model=\"queryParams.status\"\n          placeholder=\"请选择租户状态\"\n          clearable\n          class=\"!w-240px\"\n        >\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"创建时间\" prop=\"createTime\">\n        <el-date-picker\n          v-model=\"queryParams.createTime\"\n          value-format=\"YYYY-MM-DD HH:mm:ss\"\n          type=\"daterange\"\n          start-placeholder=\"开始日期\"\n          end-placeholder=\"结束日期\"\n          :default-time=\"[new Date('1 00:00:00'), new Date('1 23:59:59')]\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n\n      <el-form-item>\n        <el-button @click=\"handleQuery\">\n          <Icon icon=\"ep:search\" class=\"mr-5px\" />\n          搜索\n        </el-button>\n        <el-button @click=\"resetQuery\">\n          <Icon icon=\"ep:refresh\" class=\"mr-5px\" />\n          重置\n        </el-button>\n        <el-button\n          type=\"primary\"\n          plain\n          @click=\"openForm('create')\"\n          v-hasPermi=\"['system:tenant:create']\"\n        >\n          <Icon icon=\"ep:plus\" class=\"mr-5px\" />\n          新增\n        </el-button>\n        <el-button\n          type=\"success\"\n          plain\n          @click=\"handleExport\"\n          :loading=\"exportLoading\"\n          v-hasPermi=\"['system:tenant:export']\"\n        >\n          <Icon icon=\"ep:download\" class=\"mr-5px\" />\n          导出\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column label=\"租户编号\" align=\"center\" prop=\"id\" />\n      <el-table-column label=\"租户名\" align=\"center\" prop=\"name\" />\n      <el-table-column label=\"租户套餐\" align=\"center\" prop=\"packageId\">\n        <template #default=\"scope\">\n          <el-tag v-if=\"scope.row.packageId === 0\" type=\"danger\">系统租户</el-tag>\n          <template v-else v-for=\"item in packageList\">\n            <el-tag type=\"success\" :key=\"item.id\" v-if=\"item.id === scope.row.packageId\">\n              {{ item.name }}\n            </el-tag>\n          </template>\n        </template>\n      </el-table-column>\n      <el-table-column label=\"联系人\" align=\"center\" prop=\"contactName\" />\n      <el-table-column label=\"联系手机\" align=\"center\" prop=\"contactMobile\" />\n      <el-table-column label=\"账号额度\" align=\"center\" prop=\"accountCount\">\n        <template #default=\"scope\">\n          <el-tag>{{ scope.row.accountCount }}</el-tag>\n        </template>\n      </el-table-column>\n      <el-table-column\n        label=\"过期时间\"\n        align=\"center\"\n        prop=\"expireTime\"\n        width=\"180\"\n        :formatter=\"dateFormatter\"\n      />\n      <el-table-column label=\"绑定域名\" align=\"center\" prop=\"website\" width=\"180\" />\n      <el-table-column label=\"租户状态\" align=\"center\" prop=\"status\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.COMMON_STATUS\" :value=\"scope.row.status\" />\n        </template>\n      </el-table-column>\n      <el-table-column\n        label=\"创建时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        width=\"180\"\n        :formatter=\"dateFormatter\"\n      />\n      <el-table-column label=\"操作\" align=\"center\" min-width=\"110\" fixed=\"right\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n            v-hasPermi=\"['system:tenant:update']\"\n          >\n            编辑\n          </el-button>\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n            v-hasPermi=\"['system:tenant:delete']\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <TenantForm ref=\"formRef\" @success=\"getList\" />\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getIntDictOptions } from '@/utils/dict'\nimport { dateFormatter } from '@/utils/formatTime'\nimport download from '@/utils/download'\nimport * as TenantApi from '@/api/system/tenant'\nimport * as TenantPackageApi from '@/api/system/tenantPackage'\nimport TenantForm from './TenantForm.vue'\n\ndefineOptions({ name: 'SystemTenant' })\n\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  name: undefined,\n  contactName: undefined,\n  contactMobile: undefined,\n  status: undefined,\n  createTime: []\n})\nconst queryFormRef = ref() // 搜索的表单\nconst exportLoading = ref(false) // 导出的加载中\nconst packageList = ref([] as TenantPackageApi.TenantPackageVO[]) //租户套餐列表\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await TenantApi.getTenantPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value.resetFields()\n  handleQuery()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await TenantApi.deleteTenant(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 导出按钮操作 */\nconst handleExport = async () => {\n  try {\n    // 导出的二次确认\n    await message.exportConfirm()\n    // 发起导出\n    exportLoading.value = true\n    const data = await TenantApi.exportTenant(queryParams)\n    download.excel(data, '租户列表.xls')\n  } catch {\n  } finally {\n    exportLoading.value = false\n  }\n}\n\n/** 初始化 **/\nonMounted(async () => {\n  await getList()\n  packageList.value = await TenantPackageApi.getTenantPackageList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/tenantPackage/TenantPackageForm.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" :title=\"dialogTitle\">\n    <el-form\n      ref=\"formRef\"\n      v-loading=\"formLoading\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"80px\"\n    >\n      <el-form-item label=\"套餐名\" prop=\"name\">\n        <el-input v-model=\"formData.name\" placeholder=\"请输入套餐名\" />\n      </el-form-item>\n      <el-form-item label=\"菜单权限\">\n        <el-card class=\"cardHeight\">\n          <template #header>\n            全选/全不选:\n            <el-switch\n              v-model=\"treeNodeAll\"\n              active-text=\"是\"\n              inactive-text=\"否\"\n              inline-prompt\n              @change=\"handleCheckedTreeNodeAll\"\n            />\n            全部展开/折叠:\n            <el-switch\n              v-model=\"menuExpand\"\n              active-text=\"展开\"\n              inactive-text=\"折叠\"\n              inline-prompt\n              @change=\"handleCheckedTreeExpand\"\n            />\n          </template>\n          <el-tree\n            ref=\"treeRef\"\n            :data=\"menuOptions\"\n            :props=\"defaultProps\"\n            empty-text=\"加载中，请稍候\"\n            node-key=\"id\"\n            show-checkbox\n          />\n        </el-card>\n      </el-form-item>\n      <el-form-item label=\"状态\" prop=\"status\">\n        <el-radio-group v-model=\"formData.status\">\n          <el-radio\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)\"\n            :key=\"dict.value\"\n            :label=\"dict.value\"\n          >\n            {{ dict.label }}\n          </el-radio>\n        </el-radio-group>\n      </el-form-item>\n      <el-form-item label=\"备注\" prop=\"remark\">\n        <el-input v-model=\"formData.remark\" placeholder=\"请输入备注\" />\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button :disabled=\"formLoading\" type=\"primary\" @click=\"submitForm\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getIntDictOptions } from '@/utils/dict'\nimport { CommonStatusEnum } from '@/utils/constants'\nimport { defaultProps, handleTree } from '@/utils/tree'\nimport * as TenantPackageApi from '@/api/system/tenantPackage'\nimport * as MenuApi from '@/api/system/menu'\nimport { ElTree } from 'element-plus'\n\ndefineOptions({ name: 'SystemTenantPackageForm' })\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  id: null,\n  name: null,\n  remark: null,\n  menuIds: [],\n  status: CommonStatusEnum.ENABLE\n})\nconst formRules = reactive({\n  name: [{ required: true, message: '套餐名不能为空', trigger: 'blur' }],\n  status: [{ required: true, message: '状态不能为空', trigger: 'blur' }],\n  menuIds: [{ required: true, message: '关联的菜单编号不能为空', trigger: 'blur' }]\n})\nconst formRef = ref() // 表单 Ref\nconst menuOptions = ref<any[]>([]) // 树形结构数据\nconst menuExpand = ref(false) // 展开/折叠\nconst treeRef = ref<InstanceType<typeof ElTree>>() // 树组件 Ref\nconst treeNodeAll = ref(false) // 全选/全不选\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  // 加载 Menu 列表。注意，必须放在前面，不然下面 setChecked 没数据节点\n  menuOptions.value = handleTree(await MenuApi.getSimpleMenusList())\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      const res = await TenantPackageApi.getTenantPackage(id)\n      // 设置选中\n      formData.value = res\n      // 设置选中\n      res.menuIds.forEach((menuId: number) => {\n        treeRef.value!.setChecked(menuId, true, false)\n      })\n    } finally {\n      formLoading.value = false\n    }\n  }\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as unknown as TenantPackageApi.TenantPackageVO\n    data.menuIds = [\n      ...(treeRef.value!.getCheckedKeys(false) as unknown as Array<number>), // 获得当前选中节点\n      ...(treeRef.value!.getHalfCheckedKeys() as unknown as Array<number>) // 获得半选中的父节点\n    ]\n    if (formType.value === 'create') {\n      await TenantPackageApi.createTenantPackage(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await TenantPackageApi.updateTenantPackage(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  // 重置选项\n  treeNodeAll.value = false\n  menuExpand.value = false\n  // 重置表单\n  formData.value = {\n    id: null,\n    name: null,\n    remark: null,\n    menuIds: [],\n    status: CommonStatusEnum.ENABLE\n  }\n  treeRef.value?.setCheckedNodes([])\n  formRef.value?.resetFields()\n}\n\n/** 全选/全不选 */\nconst handleCheckedTreeNodeAll = () => {\n  treeRef.value!.setCheckedNodes(treeNodeAll.value ? menuOptions.value : [])\n}\n\n/** 展开/折叠全部 */\nconst handleCheckedTreeExpand = () => {\n  const nodes = treeRef.value?.store.nodesMap\n  for (let node in nodes) {\n    if (nodes[node].expanded === menuExpand.value) {\n      continue\n    }\n    nodes[node].expanded = menuExpand.value\n  }\n}\n</script>\n<style lang=\"scss\" scoped>\n.cardHeight {\n  width: 100%;\n  max-height: 400px;\n  overflow-y: scroll;\n}\n</style>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/tenantPackage/index.vue",
    "content": "<template>\n  <!-- 搜索 -->\n  <ContentWrap>\n    <el-form\n      class=\"-mb-15px\"\n      :model=\"queryParams\"\n      ref=\"queryFormRef\"\n      :inline=\"true\"\n      label-width=\"68px\"\n    >\n      <el-form-item label=\"套餐名\" prop=\"name\">\n        <el-input\n          v-model=\"queryParams.name\"\n          placeholder=\"请输入套餐名\"\n          clearable\n          @keyup.enter=\"handleQuery\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item label=\"状态\" prop=\"status\">\n        <el-select v-model=\"queryParams.status\" placeholder=\"请选择状态\" clearable class=\"!w-240px\">\n          <el-option\n            v-for=\"dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)\"\n            :key=\"dict.value\"\n            :label=\"dict.label\"\n            :value=\"dict.value\"\n          />\n        </el-select>\n      </el-form-item>\n      <el-form-item label=\"创建时间\" prop=\"createTime\">\n        <el-date-picker\n          v-model=\"queryParams.createTime\"\n          type=\"daterange\"\n          value-format=\"YYYY-MM-DD HH:mm:ss\"\n          start-placeholder=\"开始日期\"\n          end-placeholder=\"结束日期\"\n          class=\"!w-240px\"\n        />\n      </el-form-item>\n      <el-form-item>\n        <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" class=\"mr-5px\" /> 搜索</el-button>\n        <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" class=\"mr-5px\" /> 重置</el-button>\n        <el-button\n          type=\"primary\"\n          plain\n          @click=\"openForm('create')\"\n          v-hasPermi=\"['system:tenant-package:create']\"\n        >\n          <Icon icon=\"ep:plus\" class=\"mr-5px\" />\n          新增\n        </el-button>\n      </el-form-item>\n    </el-form>\n  </ContentWrap>\n\n  <!-- 列表 -->\n  <ContentWrap>\n    <el-table v-loading=\"loading\" :data=\"list\">\n      <el-table-column label=\"套餐编号\" align=\"center\" prop=\"id\" width=\"120\" />\n      <el-table-column label=\"套餐名\" align=\"center\" prop=\"name\" />\n      <el-table-column label=\"状态\" align=\"center\" prop=\"status\" width=\"100\">\n        <template #default=\"scope\">\n          <dict-tag :type=\"DICT_TYPE.COMMON_STATUS\" :value=\"scope.row.status\" />\n        </template>\n      </el-table-column>\n      <el-table-column label=\"备注\" align=\"center\" prop=\"remark\" />\n      <el-table-column\n        label=\"创建时间\"\n        align=\"center\"\n        prop=\"createTime\"\n        width=\"180\"\n        :formatter=\"dateFormatter\"\n      />\n      <el-table-column label=\"操作\" align=\"center\" class-name=\"small-padding fixed-width\">\n        <template #default=\"scope\">\n          <el-button\n            link\n            type=\"primary\"\n            @click=\"openForm('update', scope.row.id)\"\n            v-hasPermi=\"['system:tenant-package:update']\"\n          >\n            修改\n          </el-button>\n          <el-button\n            link\n            type=\"danger\"\n            @click=\"handleDelete(scope.row.id)\"\n            v-hasPermi=\"['system:tenant-package:delete']\"\n          >\n            删除\n          </el-button>\n        </template>\n      </el-table-column>\n    </el-table>\n    <!-- 分页 -->\n    <Pagination\n      :total=\"total\"\n      v-model:page=\"queryParams.pageNo\"\n      v-model:limit=\"queryParams.pageSize\"\n      @pagination=\"getList\"\n    />\n  </ContentWrap>\n\n  <!-- 表单弹窗：添加/修改 -->\n  <TenantPackageForm ref=\"formRef\" @success=\"getList\" />\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getIntDictOptions } from '@/utils/dict'\nimport { dateFormatter } from '@/utils/formatTime'\nimport * as TenantPackageApi from '@/api/system/tenantPackage'\nimport TenantPackageForm from './TenantPackageForm.vue'\n\ndefineOptions({ name: 'SystemTenantPackage' })\n\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数据\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  name: undefined,\n  status: undefined,\n  remark: undefined,\n  createTime: []\n})\nconst queryFormRef = ref() // 搜索的表单\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await TenantPackageApi.getTenantPackagePage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value?.resetFields()\n  getList()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await TenantPackageApi.deleteTenantPackage(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 初始化 **/\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/user/DeptTree.vue",
    "content": "<template>\n  <div class=\"head-container\">\n    <el-input v-model=\"deptName\" class=\"mb-20px\" clearable placeholder=\"请输入部门名称\">\n      <template #prefix>\n        <Icon icon=\"ep:search\" />\n      </template>\n    </el-input>\n  </div>\n  <div class=\"head-container\">\n    <el-tree\n      ref=\"treeRef\"\n      :data=\"deptList\"\n      :expand-on-click-node=\"false\"\n      :filter-node-method=\"filterNode\"\n      :props=\"defaultProps\"\n      default-expand-all\n      highlight-current\n      node-key=\"id\"\n      @node-click=\"handleNodeClick\"\n    />\n  </div>\n</template>\n\n<script lang=\"ts\" setup>\nimport { ElTree } from 'element-plus'\nimport * as DeptApi from '@/api/system/dept'\nimport { defaultProps, handleTree } from '@/utils/tree'\n\ndefineOptions({ name: 'SystemUserDeptTree' })\n\nconst deptName = ref('')\nconst deptList = ref<Tree[]>([]) // 树形结构\nconst treeRef = ref<InstanceType<typeof ElTree>>()\n\n/** 获得部门树 */\nconst getTree = async () => {\n  const res = await DeptApi.getSimpleDeptList()\n  deptList.value = []\n  deptList.value.push(...handleTree(res))\n}\n\n/** 基于名字过滤 */\nconst filterNode = (name: string, data: Tree) => {\n  if (!name) return true\n  return data.name.includes(name)\n}\n\n/** 处理部门被点击 */\nconst handleNodeClick = async (row: { [key: string]: any }) => {\n  emits('node-click', row)\n}\nconst emits = defineEmits(['node-click'])\n\n/** 监听deptName */\nwatch(deptName, (val) => {\n  treeRef.value!.filter(val)\n})\n\n/** 初始化 */\nonMounted(async () => {\n  await getTree()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/user/UserAssignRoleForm.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" title=\"分配角色\">\n    <el-form ref=\"formRef\" v-loading=\"formLoading\" :model=\"formData\" label-width=\"80px\">\n      <el-form-item label=\"用户名称\">\n        <el-input v-model=\"formData.username\" :disabled=\"true\" />\n      </el-form-item>\n      <el-form-item label=\"用户昵称\">\n        <el-input v-model=\"formData.nickname\" :disabled=\"true\" />\n      </el-form-item>\n      <el-form-item label=\"角色\">\n        <el-select v-model=\"formData.roleIds\" multiple placeholder=\"请选择角色\">\n          <el-option v-for=\"item in roleList\" :key=\"item.id\" :label=\"item.name\" :value=\"item.id\" />\n        </el-select>\n      </el-form-item>\n    </el-form>\n    <template #footer>\n      <el-button :disabled=\"formLoading\" type=\"primary\" @click=\"submitForm\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport * as PermissionApi from '@/api/system/permission'\nimport * as UserApi from '@/api/system/user'\nimport * as RoleApi from '@/api/system/role'\n\ndefineOptions({ name: 'SystemUserAssignRoleForm' })\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formData = ref({\n  id: -1,\n  nickname: '',\n  username: '',\n  roleIds: []\n})\nconst formRef = ref() // 表单 Ref\nconst roleList = ref([] as RoleApi.RoleVO[]) // 角色的列表\n\n/** 打开弹窗 */\nconst open = async (row: UserApi.UserVO) => {\n  dialogVisible.value = true\n  resetForm()\n  // 设置数据\n  formData.value.id = row.id\n  formData.value.username = row.username\n  formData.value.nickname = row.nickname\n  // 获得角色拥有的菜单集合\n  formLoading.value = true\n  try {\n    formData.value.roleIds = await PermissionApi.getUserRoleList(row.id)\n  } finally {\n    formLoading.value = false\n  }\n  // 获得角色列表\n  roleList.value = await RoleApi.getSimpleRoleList()\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    await PermissionApi.assignUserRole({\n      userId: formData.value.id,\n      roleIds: formData.value.roleIds\n    })\n    message.success(t('common.updateSuccess'))\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success', true)\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    id: -1,\n    nickname: '',\n    username: '',\n    roleIds: []\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/user/UserForm.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" :title=\"dialogTitle\">\n    <el-form\n      ref=\"formRef\"\n      v-loading=\"formLoading\"\n      :model=\"formData\"\n      :rules=\"formRules\"\n      label-width=\"80px\"\n    >\n      <el-row>\n        <el-col :span=\"12\">\n          <el-form-item label=\"用户昵称\" prop=\"nickname\">\n            <el-input v-model=\"formData.nickname\" placeholder=\"请输入用户昵称\" />\n          </el-form-item>\n        </el-col>\n        <el-col :span=\"12\">\n          <el-form-item label=\"归属部门\" prop=\"deptId\">\n            <el-tree-select\n              v-model=\"formData.deptId\"\n              :data=\"deptList\"\n              :props=\"defaultProps\"\n              check-strictly\n              node-key=\"id\"\n              placeholder=\"请选择归属部门\"\n            />\n          </el-form-item>\n        </el-col>\n      </el-row>\n      <el-row>\n        <el-col :span=\"12\">\n          <el-form-item label=\"手机号码\" prop=\"mobile\">\n            <el-input v-model=\"formData.mobile\" maxlength=\"11\" placeholder=\"请输入手机号码\" />\n          </el-form-item>\n        </el-col>\n        <el-col :span=\"12\">\n          <el-form-item label=\"邮箱\" prop=\"email\">\n            <el-input v-model=\"formData.email\" maxlength=\"50\" placeholder=\"请输入邮箱\" />\n          </el-form-item>\n        </el-col>\n      </el-row>\n      <el-row>\n        <el-col :span=\"12\">\n          <el-form-item v-if=\"formData.id === undefined\" label=\"用户名称\" prop=\"username\">\n            <el-input v-model=\"formData.username\" placeholder=\"请输入用户名称\" />\n          </el-form-item>\n        </el-col>\n        <el-col :span=\"12\">\n          <el-form-item v-if=\"formData.id === undefined\" label=\"用户密码\" prop=\"password\">\n            <el-input\n              v-model=\"formData.password\"\n              placeholder=\"请输入用户密码\"\n              show-password\n              type=\"password\"\n            />\n          </el-form-item>\n        </el-col>\n      </el-row>\n      <el-row>\n        <el-col :span=\"12\">\n          <el-form-item label=\"用户性别\">\n            <el-select v-model=\"formData.sex\" placeholder=\"请选择\">\n              <el-option\n                v-for=\"dict in getIntDictOptions(DICT_TYPE.SYSTEM_USER_SEX)\"\n                :key=\"dict.value\"\n                :label=\"dict.label\"\n                :value=\"dict.value\"\n              />\n            </el-select>\n          </el-form-item>\n        </el-col>\n        <el-col :span=\"12\">\n          <el-form-item label=\"岗位\">\n            <el-select v-model=\"formData.postIds\" multiple placeholder=\"请选择\">\n              <el-option\n                v-for=\"item in postList\"\n                :key=\"item.id\"\n                :label=\"item.name\"\n                :value=\"item.id!\"\n              />\n            </el-select>\n          </el-form-item>\n        </el-col>\n      </el-row>\n      <el-row>\n        <el-col :span=\"24\">\n          <el-form-item label=\"备注\">\n            <el-input v-model=\"formData.remark\" placeholder=\"请输入内容\" type=\"textarea\" />\n          </el-form-item>\n        </el-col>\n      </el-row>\n    </el-form>\n    <template #footer>\n      <el-button :disabled=\"formLoading\" type=\"primary\" @click=\"submitForm\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getIntDictOptions } from '@/utils/dict'\nimport { CommonStatusEnum } from '@/utils/constants'\nimport { defaultProps, handleTree } from '@/utils/tree'\nimport * as PostApi from '@/api/system/post'\nimport * as DeptApi from '@/api/system/dept'\nimport * as UserApi from '@/api/system/user'\nimport { FormRules } from 'element-plus'\n\ndefineOptions({ name: 'SystemUserForm' })\n\nconst { t } = useI18n() // 国际化\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst dialogTitle = ref('') // 弹窗的标题\nconst formLoading = ref(false) // 表单的加载中：1）修改时的数据加载；2）提交的按钮禁用\nconst formType = ref('') // 表单的类型：create - 新增；update - 修改\nconst formData = ref({\n  nickname: '',\n  deptId: '',\n  mobile: '',\n  email: '',\n  id: undefined,\n  username: '',\n  password: '',\n  sex: undefined,\n  postIds: [],\n  remark: '',\n  status: CommonStatusEnum.ENABLE,\n  roleIds: []\n})\nconst formRules = reactive<FormRules>({\n  username: [{ required: true, message: '用户名称不能为空', trigger: 'blur' }],\n  nickname: [{ required: true, message: '用户昵称不能为空', trigger: 'blur' }],\n  deptId: [{ required: true, message: '请选择部门', trigger: 'blur' }],\n  password: [{ required: true, message: '用户密码不能为空', trigger: 'blur' }],\n  email: [\n    {\n      type: 'email',\n      message: '请输入正确的邮箱地址',\n      trigger: ['blur', 'change']\n    }\n  ],\n  mobile: [\n    {\n      pattern: /^(?:(?:\\+|00)86)?1(?:3[\\d]|4[5-79]|5[0-35-9]|6[5-7]|7[0-8]|8[\\d]|9[189])\\d{8}$/,\n      message: '请输入正确的手机号码',\n      trigger: 'blur'\n    }\n  ]\n})\nconst formRef = ref() // 表单 Ref\nconst deptList = ref<Tree[]>([]) // 树形结构\nconst postList = ref([] as PostApi.PostVO[]) // 岗位列表\n\n/** 打开弹窗 */\nconst open = async (type: string, id?: number) => {\n  dialogVisible.value = true\n  dialogTitle.value = t('action.' + type)\n  formType.value = type\n  resetForm()\n  // 修改时，设置数据\n  if (id) {\n    formLoading.value = true\n    try {\n      formData.value = await UserApi.getUser(id)\n    } finally {\n      formLoading.value = false\n    }\n  }\n  // 加载部门树\n  deptList.value = handleTree(await DeptApi.getSimpleDeptList())\n  // 加载岗位列表\n  postList.value = await PostApi.getSimplePostList()\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst emit = defineEmits(['success']) // 定义 success 事件，用于操作成功后的回调\nconst submitForm = async () => {\n  // 校验表单\n  if (!formRef) return\n  const valid = await formRef.value.validate()\n  if (!valid) return\n  // 提交请求\n  formLoading.value = true\n  try {\n    const data = formData.value as unknown as UserApi.UserVO\n    if (formType.value === 'create') {\n      await UserApi.createUser(data)\n      message.success(t('common.createSuccess'))\n    } else {\n      await UserApi.updateUser(data)\n      message.success(t('common.updateSuccess'))\n    }\n    dialogVisible.value = false\n    // 发送操作成功的事件\n    emit('success')\n  } finally {\n    formLoading.value = false\n  }\n}\n\n/** 重置表单 */\nconst resetForm = () => {\n  formData.value = {\n    nickname: '',\n    deptId: '',\n    mobile: '',\n    email: '',\n    id: undefined,\n    username: '',\n    password: '',\n    sex: undefined,\n    postIds: [],\n    remark: '',\n    status: CommonStatusEnum.ENABLE,\n    roleIds: []\n  }\n  formRef.value?.resetFields()\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/user/UserImportForm.vue",
    "content": "<template>\n  <Dialog v-model=\"dialogVisible\" title=\"用户导入\" width=\"400\">\n    <el-upload\n      ref=\"uploadRef\"\n      v-model:file-list=\"fileList\"\n      :action=\"importUrl + '?updateSupport=' + updateSupport\"\n      :auto-upload=\"false\"\n      :disabled=\"formLoading\"\n      :headers=\"uploadHeaders\"\n      :limit=\"1\"\n      :on-error=\"submitFormError\"\n      :on-exceed=\"handleExceed\"\n      :on-success=\"submitFormSuccess\"\n      accept=\".xlsx, .xls\"\n      drag\n    >\n      <Icon icon=\"ep:upload\" />\n      <div class=\"el-upload__text\">将文件拖到此处，或<em>点击上传</em></div>\n      <template #tip>\n        <div class=\"el-upload__tip text-center\">\n          <div class=\"el-upload__tip\">\n            <el-checkbox v-model=\"updateSupport\" />\n            是否更新已经存在的用户数据\n          </div>\n          <span>仅允许导入 xls、xlsx 格式文件。</span>\n          <el-link\n            :underline=\"false\"\n            style=\"font-size: 12px; vertical-align: baseline\"\n            type=\"primary\"\n            @click=\"importTemplate\"\n          >\n            下载模板\n          </el-link>\n        </div>\n      </template>\n    </el-upload>\n    <template #footer>\n      <el-button :disabled=\"formLoading\" type=\"primary\" @click=\"submitForm\">确 定</el-button>\n      <el-button @click=\"dialogVisible = false\">取 消</el-button>\n    </template>\n  </Dialog>\n</template>\n<script lang=\"ts\" setup>\nimport * as UserApi from '@/api/system/user'\nimport { getAccessToken, getTenantId } from '@/utils/auth'\nimport download from '@/utils/download'\n\ndefineOptions({ name: 'SystemUserImportForm' })\n\nconst message = useMessage() // 消息弹窗\n\nconst dialogVisible = ref(false) // 弹窗的是否展示\nconst formLoading = ref(false) // 表单的加载中\nconst uploadRef = ref()\nconst importUrl =\n  import.meta.env.VITE_BASE_URL + import.meta.env.VITE_API_URL + '/system/user/import'\nconst uploadHeaders = ref() // 上传 Header 头\nconst fileList = ref([]) // 文件列表\nconst updateSupport = ref(0) // 是否更新已经存在的用户数据\n\n/** 打开弹窗 */\nconst open = () => {\n  dialogVisible.value = true\n  updateSupport.value = 0\n  fileList.value = []\n  resetForm()\n}\ndefineExpose({ open }) // 提供 open 方法，用于打开弹窗\n\n/** 提交表单 */\nconst submitForm = async () => {\n  if (fileList.value.length == 0) {\n    message.error('请上传文件')\n    return\n  }\n  // 提交请求\n  uploadHeaders.value = {\n    Authorization: 'Bearer ' + getAccessToken(),\n    'tenant-id': getTenantId()\n  }\n  formLoading.value = true\n  uploadRef.value!.submit()\n}\n\n/** 文件上传成功 */\nconst emits = defineEmits(['success'])\nconst submitFormSuccess = (response: any) => {\n  if (response.code !== 0) {\n    message.error(response.msg)\n    formLoading.value = false\n    return\n  }\n  // 拼接提示语\n  const data = response.data\n  let text = '上传成功数量：' + data.createUsernames.length + ';'\n  for (let username of data.createUsernames) {\n    text += '< ' + username + ' >'\n  }\n  text += '更新成功数量：' + data.updateUsernames.length + ';'\n  for (const username of data.updateUsernames) {\n    text += '< ' + username + ' >'\n  }\n  text += '更新失败数量：' + Object.keys(data.failureUsernames).length + ';'\n  for (const username in data.failureUsernames) {\n    text += '< ' + username + ': ' + data.failureUsernames[username] + ' >'\n  }\n  message.alert(text)\n  formLoading.value = false\n  dialogVisible.value = false\n  // 发送操作成功的事件\n  emits('success')\n}\n\n/** 上传错误提示 */\nconst submitFormError = (): void => {\n  message.error('上传失败，请您重新上传！')\n  formLoading.value = false\n}\n\n/** 重置表单 */\nconst resetForm = async (): Promise<void> => {\n  // 重置上传状态和文件\n  formLoading.value = false\n  await nextTick()\n  uploadRef.value?.clearFiles()\n}\n\n/** 文件数超出提示 */\nconst handleExceed = (): void => {\n  message.error('最多只能上传一个文件！')\n}\n\n/** 下载模板操作 */\nconst importTemplate = async () => {\n  const res = await UserApi.importUserTemplate()\n  download.excel(res, '用户导入模版.xls')\n}\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/src/views/system/user/index.vue",
    "content": "<template>\n  <el-row :gutter=\"20\">\n    <!-- 左侧部门树 -->\n    <el-col :span=\"4\" :xs=\"24\">\n      <ContentWrap class=\"h-1/1\">\n        <DeptTree @node-click=\"handleDeptNodeClick\" />\n      </ContentWrap>\n    </el-col>\n    <el-col :span=\"20\" :xs=\"24\">\n      <!-- 搜索 -->\n      <ContentWrap>\n        <el-form\n          class=\"-mb-15px\"\n          :model=\"queryParams\"\n          ref=\"queryFormRef\"\n          :inline=\"true\"\n          label-width=\"68px\"\n        >\n          <el-form-item label=\"用户名称\" prop=\"username\">\n            <el-input\n              v-model=\"queryParams.username\"\n              placeholder=\"请输入用户名称\"\n              clearable\n              @keyup.enter=\"handleQuery\"\n              class=\"!w-240px\"\n            />\n          </el-form-item>\n          <el-form-item label=\"手机号码\" prop=\"mobile\">\n            <el-input\n              v-model=\"queryParams.mobile\"\n              placeholder=\"请输入手机号码\"\n              clearable\n              @keyup.enter=\"handleQuery\"\n              class=\"!w-240px\"\n            />\n          </el-form-item>\n          <el-form-item label=\"状态\" prop=\"status\">\n            <el-select\n              v-model=\"queryParams.status\"\n              placeholder=\"用户状态\"\n              clearable\n              class=\"!w-240px\"\n            >\n              <el-option\n                v-for=\"dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)\"\n                :key=\"dict.value\"\n                :label=\"dict.label\"\n                :value=\"dict.value\"\n              />\n            </el-select>\n          </el-form-item>\n          <el-form-item label=\"创建时间\" prop=\"createTime\">\n            <el-date-picker\n              v-model=\"queryParams.createTime\"\n              value-format=\"YYYY-MM-DD HH:mm:ss\"\n              type=\"datetimerange\"\n              start-placeholder=\"开始日期\"\n              end-placeholder=\"结束日期\"\n              class=\"!w-240px\"\n            />\n          </el-form-item>\n          <el-form-item>\n            <el-button @click=\"handleQuery\"><Icon icon=\"ep:search\" />搜索</el-button>\n            <el-button @click=\"resetQuery\"><Icon icon=\"ep:refresh\" />重置</el-button>\n            <el-button\n              type=\"primary\"\n              plain\n              @click=\"openForm('create')\"\n              v-hasPermi=\"['system:user:create']\"\n            >\n              <Icon icon=\"ep:plus\" /> 新增\n            </el-button>\n            <el-button\n              type=\"warning\"\n              plain\n              @click=\"handleImport\"\n              v-hasPermi=\"['system:user:import']\"\n            >\n              <Icon icon=\"ep:upload\" /> 导入\n            </el-button>\n            <el-button\n              type=\"success\"\n              plain\n              @click=\"handleExport\"\n              :loading=\"exportLoading\"\n              v-hasPermi=\"['system:user:export']\"\n            >\n              <Icon icon=\"ep:download\" />导出\n            </el-button>\n          </el-form-item>\n        </el-form>\n      </ContentWrap>\n      <ContentWrap>\n        <el-table v-loading=\"loading\" :data=\"list\">\n          <el-table-column label=\"用户编号\" align=\"center\" key=\"id\" prop=\"id\" />\n          <el-table-column\n            label=\"用户名称\"\n            align=\"center\"\n            prop=\"username\"\n            :show-overflow-tooltip=\"true\"\n          />\n          <el-table-column\n            label=\"用户昵称\"\n            align=\"center\"\n            prop=\"nickname\"\n            :show-overflow-tooltip=\"true\"\n          />\n          <el-table-column\n            label=\"部门\"\n            align=\"center\"\n            key=\"deptName\"\n            prop=\"deptName\"\n            :show-overflow-tooltip=\"true\"\n          />\n          <el-table-column label=\"手机号码\" align=\"center\" prop=\"mobile\" width=\"120\" />\n          <el-table-column label=\"状态\" key=\"status\">\n            <template #default=\"scope\">\n              <el-switch\n                v-model=\"scope.row.status\"\n                :active-value=\"0\"\n                :inactive-value=\"1\"\n                @change=\"handleStatusChange(scope.row)\"\n              />\n            </template>\n          </el-table-column>\n          <el-table-column\n            label=\"创建时间\"\n            align=\"center\"\n            prop=\"createTime\"\n            :formatter=\"dateFormatter\"\n            width=\"180\"\n          />\n          <el-table-column label=\"操作\" align=\"center\" width=\"160\">\n            <template #default=\"scope\">\n              <div class=\"flex items-center justify-center\">\n                <el-button\n                  type=\"primary\"\n                  link\n                  @click=\"openForm('update', scope.row.id)\"\n                  v-hasPermi=\"['system:user:update']\"\n                >\n                  <Icon icon=\"ep:edit\" />修改\n                </el-button>\n                <el-dropdown\n                  @command=\"(command) => handleCommand(command, scope.row)\"\n                  v-hasPermi=\"[\n                    'system:user:delete',\n                    'system:user:update-password',\n                    'system:permission:assign-user-role'\n                  ]\"\n                >\n                  <el-button type=\"primary\" link><Icon icon=\"ep:d-arrow-right\" /> 更多</el-button>\n                  <template #dropdown>\n                    <el-dropdown-menu>\n                      <el-dropdown-item\n                        command=\"handleDelete\"\n                        v-if=\"checkPermi(['system:user:delete'])\"\n                      >\n                        <Icon icon=\"ep:delete\" />删除\n                      </el-dropdown-item>\n                      <el-dropdown-item\n                        command=\"handleResetPwd\"\n                        v-if=\"checkPermi(['system:user:update-password'])\"\n                      >\n                        <Icon icon=\"ep:key\" />重置密码\n                      </el-dropdown-item>\n                      <el-dropdown-item\n                        command=\"handleRole\"\n                        v-if=\"checkPermi(['system:permission:assign-user-role'])\"\n                      >\n                        <Icon icon=\"ep:circle-check\" />分配角色\n                      </el-dropdown-item>\n                    </el-dropdown-menu>\n                  </template>\n                </el-dropdown>\n              </div>\n            </template>\n          </el-table-column>\n        </el-table>\n        <Pagination\n          :total=\"total\"\n          v-model:page=\"queryParams.pageNo\"\n          v-model:limit=\"queryParams.pageSize\"\n          @pagination=\"getList\"\n        />\n      </ContentWrap>\n    </el-col>\n  </el-row>\n\n  <!-- 添加或修改用户对话框 -->\n  <UserForm ref=\"formRef\" @success=\"getList\" />\n  <!-- 用户导入对话框 -->\n  <UserImportForm ref=\"importFormRef\" @success=\"getList\" />\n  <!-- 分配角色 -->\n  <UserAssignRoleForm ref=\"assignRoleFormRef\" @success=\"getList\" />\n</template>\n<script lang=\"ts\" setup>\nimport { DICT_TYPE, getIntDictOptions } from '@/utils/dict'\nimport { checkPermi } from '@/utils/permission'\nimport { dateFormatter } from '@/utils/formatTime'\nimport download from '@/utils/download'\nimport { CommonStatusEnum } from '@/utils/constants'\nimport * as UserApi from '@/api/system/user'\nimport UserForm from './UserForm.vue'\nimport UserImportForm from './UserImportForm.vue'\nimport UserAssignRoleForm from './UserAssignRoleForm.vue'\nimport DeptTree from './DeptTree.vue'\n\ndefineOptions({ name: 'SystemUser' })\n\nconst message = useMessage() // 消息弹窗\nconst { t } = useI18n() // 国际化\n\nconst loading = ref(true) // 列表的加载中\nconst total = ref(0) // 列表的总页数\nconst list = ref([]) // 列表的数\nconst queryParams = reactive({\n  pageNo: 1,\n  pageSize: 10,\n  username: undefined,\n  mobile: undefined,\n  status: undefined,\n  deptId: undefined,\n  createTime: []\n})\nconst queryFormRef = ref() // 搜索的表单\n\n/** 查询列表 */\nconst getList = async () => {\n  loading.value = true\n  try {\n    const data = await UserApi.getUserPage(queryParams)\n    list.value = data.list\n    total.value = data.total\n  } finally {\n    loading.value = false\n  }\n}\n\n/** 搜索按钮操作 */\nconst handleQuery = () => {\n  queryParams.pageNo = 1\n  getList()\n}\n\n/** 重置按钮操作 */\nconst resetQuery = () => {\n  queryFormRef.value?.resetFields()\n  handleQuery()\n}\n\n/** 处理部门被点击 */\nconst handleDeptNodeClick = async (row) => {\n  queryParams.deptId = row.id\n  await getList()\n}\n\n/** 添加/修改操作 */\nconst formRef = ref()\nconst openForm = (type: string, id?: number) => {\n  formRef.value.open(type, id)\n}\n\n/** 用户导入 */\nconst importFormRef = ref()\nconst handleImport = () => {\n  importFormRef.value.open()\n}\n\n/** 修改用户状态 */\nconst handleStatusChange = async (row: UserApi.UserVO) => {\n  try {\n    // 修改状态的二次确认\n    const text = row.status === CommonStatusEnum.ENABLE ? '启用' : '停用'\n    await message.confirm('确认要\"' + text + '\"\"' + row.username + '\"用户吗?')\n    // 发起修改状态\n    await UserApi.updateUserStatus(row.id, row.status)\n    // 刷新列表\n    await getList()\n  } catch {\n    // 取消后，进行恢复按钮\n    row.status =\n      row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE\n  }\n}\n\n/** 导出按钮操作 */\nconst exportLoading = ref(false)\nconst handleExport = async () => {\n  try {\n    // 导出的二次确认\n    await message.exportConfirm()\n    // 发起导出\n    exportLoading.value = true\n    const data = await UserApi.exportUser(queryParams)\n    download.excel(data, '用户数据.xls')\n  } catch {\n  } finally {\n    exportLoading.value = false\n  }\n}\n\n/** 操作分发 */\nconst handleCommand = (command: string, row: UserApi.UserVO) => {\n  switch (command) {\n    case 'handleDelete':\n      handleDelete(row.id)\n      break\n    case 'handleResetPwd':\n      handleResetPwd(row)\n      break\n    case 'handleRole':\n      handleRole(row)\n      break\n    default:\n      break\n  }\n}\n\n/** 删除按钮操作 */\nconst handleDelete = async (id: number) => {\n  try {\n    // 删除的二次确认\n    await message.delConfirm()\n    // 发起删除\n    await UserApi.deleteUser(id)\n    message.success(t('common.delSuccess'))\n    // 刷新列表\n    await getList()\n  } catch {}\n}\n\n/** 重置密码 */\nconst handleResetPwd = async (row: UserApi.UserVO) => {\n  try {\n    // 重置的二次确认\n    const result = await message.prompt(\n      '请输入\"' + row.username + '\"的新密码',\n      t('common.reminder')\n    )\n    const password = result.value\n    // 发起重置\n    await UserApi.resetUserPwd(row.id, password)\n    message.success('修改成功，新密码是：' + password)\n  } catch {}\n}\n\n/** 分配角色 */\nconst assignRoleFormRef = ref()\nconst handleRole = (row: UserApi.UserVO) => {\n  assignRoleFormRef.value.open(row)\n}\n\n/** 初始化 */\nonMounted(() => {\n  getList()\n})\n</script>\n"
  },
  {
    "path": "yshop-drink-vue3/stylelint.config.js",
    "content": "module.exports = {\n  root: true,\n  plugins: ['stylelint-order'],\n  customSyntax: 'postcss-html',\n  extends: ['stylelint-config-standard'],\n  rules: {\n    'selector-pseudo-class-no-unknown': [\n      true,\n      {\n        ignorePseudoClasses: ['global', 'deep']\n      }\n    ],\n    'at-rule-no-unknown': [\n      true,\n      {\n        ignoreAtRules: ['function', 'if', 'each', 'include', 'mixin']\n      }\n    ],\n    'media-query-no-invalid': null,\n    'function-no-unknown': null,\n    'no-empty-source': null,\n    'named-grid-areas-no-invalid': null,\n    'unicode-bom': 'never',\n    'no-descending-specificity': null,\n    'font-family-no-missing-generic-family-keyword': null,\n    'declaration-colon-space-after': 'always-single-line',\n    'declaration-colon-space-before': 'never',\n    'declaration-block-trailing-semicolon': null,\n    'rule-empty-line-before': [\n      'always',\n      {\n        ignore: ['after-comment', 'first-nested']\n      }\n    ],\n    'unit-no-unknown': [\n      true,\n      {\n        ignoreUnits: ['rpx']\n      }\n    ],\n    'order/order': [\n      [\n        'dollar-variables',\n        'custom-properties',\n        'at-rules',\n        'declarations',\n        {\n          type: 'at-rule',\n          name: 'supports'\n        },\n        {\n          type: 'at-rule',\n          name: 'media'\n        },\n        'rules'\n      ],\n      {\n        severity: 'warning'\n      }\n    ],\n    // Specify the alphabetical order of the attributes in the declaration block\n    'order/properties-order': [\n      'position',\n      'top',\n      'right',\n      'bottom',\n      'left',\n      'z-index',\n      'display',\n      'float',\n      'width',\n      'height',\n      'max-width',\n      'max-height',\n      'min-width',\n      'min-height',\n      'padding',\n      'padding-top',\n      'padding-right',\n      'padding-bottom',\n      'padding-left',\n      'margin',\n      'margin-top',\n      'margin-right',\n      'margin-bottom',\n      'margin-left',\n      'margin-collapse',\n      'margin-top-collapse',\n      'margin-right-collapse',\n      'margin-bottom-collapse',\n      'margin-left-collapse',\n      'overflow',\n      'overflow-x',\n      'overflow-y',\n      'clip',\n      'clear',\n      'font',\n      'font-family',\n      'font-size',\n      'font-smoothing',\n      'osx-font-smoothing',\n      'font-style',\n      'font-weight',\n      'hyphens',\n      'src',\n      'line-height',\n      'letter-spacing',\n      'word-spacing',\n      'color',\n      'text-align',\n      'text-decoration',\n      'text-indent',\n      'text-overflow',\n      'text-rendering',\n      'text-size-adjust',\n      'text-shadow',\n      'text-transform',\n      'word-break',\n      'word-wrap',\n      'white-space',\n      'vertical-align',\n      'list-style',\n      'list-style-type',\n      'list-style-position',\n      'list-style-image',\n      'pointer-events',\n      'cursor',\n      'background',\n      'background-attachment',\n      'background-color',\n      'background-image',\n      'background-position',\n      'background-repeat',\n      'background-size',\n      'border',\n      'border-collapse',\n      'border-top',\n      'border-right',\n      'border-bottom',\n      'border-left',\n      'border-color',\n      'border-image',\n      'border-top-color',\n      'border-right-color',\n      'border-bottom-color',\n      'border-left-color',\n      'border-spacing',\n      'border-style',\n      'border-top-style',\n      'border-right-style',\n      'border-bottom-style',\n      'border-left-style',\n      'border-width',\n      'border-top-width',\n      'border-right-width',\n      'border-bottom-width',\n      'border-left-width',\n      'border-radius',\n      'border-top-right-radius',\n      'border-bottom-right-radius',\n      'border-bottom-left-radius',\n      'border-top-left-radius',\n      'border-radius-topright',\n      'border-radius-bottomright',\n      'border-radius-bottomleft',\n      'border-radius-topleft',\n      'content',\n      'quotes',\n      'outline',\n      'outline-offset',\n      'opacity',\n      'filter',\n      'visibility',\n      'size',\n      'zoom',\n      'transform',\n      'box-align',\n      'box-flex',\n      'box-orient',\n      'box-pack',\n      'box-shadow',\n      'box-sizing',\n      'table-layout',\n      'animation',\n      'animation-delay',\n      'animation-duration',\n      'animation-iteration-count',\n      'animation-name',\n      'animation-play-state',\n      'animation-timing-function',\n      'animation-fill-mode',\n      'transition',\n      'transition-delay',\n      'transition-duration',\n      'transition-property',\n      'transition-timing-function',\n      'background-clip',\n      'backface-visibility',\n      'resize',\n      'appearance',\n      'user-select',\n      'interpolation-mode',\n      'direction',\n      'marks',\n      'page',\n      'set-link-source',\n      'unicode-bidi',\n      'speak'\n    ]\n  },\n  ignoreFiles: ['**/*.js', '**/*.jsx', '**/*.tsx', '**/*.ts'],\n  overrides: [\n    {\n      files: ['*.vue', '**/*.vue', '*.html', '**/*.html'],\n      extends: ['stylelint-config-recommended', 'stylelint-config-html'],\n      rules: {\n        'keyframes-name-pattern': null,\n        'selector-class-pattern': null,\n        'no-duplicate-selectors': null,\n        'selector-pseudo-class-no-unknown': [\n          true,\n          {\n            ignorePseudoClasses: ['deep', 'global']\n          }\n        ],\n        'selector-pseudo-element-no-unknown': [\n          true,\n          {\n            ignorePseudoElements: ['v-deep', 'v-global', 'v-slotted']\n          }\n        ]\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "yshop-drink-vue3/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"esnext\",\n    \"useDefineForClassFields\": true,\n    \"module\": \"esnext\",\n    \"moduleResolution\": \"node\",\n    \"strict\": true,\n    \"jsx\": \"preserve\",\n    \"sourceMap\": true,\n    \"resolveJsonModule\": true,\n    \"esModuleInterop\": true,\n    \"lib\": [\"esnext\", \"dom\"],\n    \"baseUrl\": \"./\",\n    \"allowJs\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"allowSyntheticDefaultImports\": true,\n    \"strictFunctionTypes\": false,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"experimentalDecorators\": true,\n    \"noImplicitAny\": false,\n    \"skipLibCheck\": true,\n    \"paths\": {\n      \"@/*\": [\"src/*\"]\n    },\n    \"types\": [\n      \"@intlify/unplugin-vue-i18n/types\",\n      \"vite/client\",\n      \"element-plus/global\",\n      \"@types/qrcode\",\n      \"vite-plugin-svg-icons/client\"\n    ],\n    \"outDir\": \"target\", // 请保留这个属性，防止tsconfig.json文件报错\n    \"typeRoots\": [\"./node_modules/@types/\", \"./types\"]\n  },\n  \"include\": [\n    \"src\",\n    \"types/**/*.d.ts\",\n    \"src/types/auto-imports.d.ts\",\n    \"src/types/auto-components.d.ts\"\n  ],\n  \"exclude\": [\"dist\", \"target\", \"node_modules\"]\n}\n"
  },
  {
    "path": "yshop-drink-vue3/types/components.d.ts",
    "content": "declare module 'vue' {\n  export interface GlobalComponents {\n    Icon: typeof import('@/components/Icon')['Icon']\n    DictTag: typeof import('@/components/DictTag')['DictTag']\n  }\n}\n\nexport {}\n"
  },
  {
    "path": "yshop-drink-vue3/types/custom-types.d.ts",
    "content": "import { SlateDescendant } from '@wangeditor/editor'\n\ndeclare module 'slate' {\n  interface CustomTypes {\n    // 扩展 text\n    Text: {\n      text: string\n      bold?: boolean\n      italic?: boolean\n      code?: boolean\n      through?: boolean\n      underline?: boolean\n      sup?: boolean\n      sub?: boolean\n      color?: string\n      bgColor?: string\n      fontSize?: string\n      fontFamily?: string\n    }\n\n    // 扩展 Element 的 type 属性\n    Element: {\n      type: string\n      children: SlateDescendant[]\n    }\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/types/env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n\ndeclare module '*.vue' {\n  import { DefineComponent } from 'vue'\n  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types\n  const component: DefineComponent<{}, {}, any>\n  export default component\n}\n\ninterface ImportMetaEnv {\n  readonly VITE_APP_TITLE: string\n  readonly VITE_PORT: number\n  readonly VITE_OPEN: string\n  readonly VITE_DEV: string\n  readonly VITE_APP_CAPTCHA_ENABLE: string\n  readonly VITE_APP_TENANT_ENABLE: string\n  readonly VITE_APP_DOCALERT_ENABLE: string\n  readonly VITE_BASE_URL: string\n  readonly VITE_UPLOAD_URL: string\n  readonly VITE_API_URL: string\n  readonly VITE_BASE_PATH: string\n  readonly VITE_DROP_DEBUGGER: string\n  readonly VITE_DROP_CONSOLE: string\n  readonly VITE_SOURCEMAP: string\n  readonly VITE_OUT_DIR: string\n}\n\ndeclare global {\n  interface ImportMeta {\n    readonly env: ImportMetaEnv\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/types/global.d.ts",
    "content": "export {}\ndeclare global {\n  interface Fn<T = any> {\n    (...arg: T[]): T\n  }\n\n  type Nullable<T> = T | null\n\n  type ElRef<T extends HTMLElement = HTMLDivElement> = Nullable<T>\n\n  type Recordable<T = any, K = string> = Record<K extends null | undefined ? string : K, T>\n\n  type ComponentRef<T> = InstanceType<T>\n\n  type LocaleType = 'zh-CN' | 'en'\n\n  declare type TimeoutHandle = ReturnType<typeof setTimeout>\n  declare type IntervalHandle = ReturnType<typeof setInterval>\n\n  type AxiosHeaders =\n    | 'application/json'\n    | 'application/x-www-form-urlencoded'\n    | 'multipart/form-data'\n\n  type AxiosMethod = 'get' | 'post' | 'delete' | 'put' | 'GET' | 'POST' | 'DELETE' | 'PUT'\n\n  type AxiosResponseType = 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' | 'stream'\n\n  interface AxiosConfig {\n    params?: any\n    data?: any\n    url?: string\n    method?: AxiosMethod\n    headersType?: string\n    responseType?: AxiosResponseType\n  }\n\n  interface IResponse<T = any> {\n    code: string\n    data: T extends any ? T : T & any\n  }\n\n  interface PageParam {\n    pageSize?: number\n    pageNo?: number\n  }\n\n  interface Tree {\n    id: number\n    name: string\n    children?: Tree[] | any[]\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/types/router.d.ts",
    "content": "import type { RouteRecordRaw } from 'vue-router'\nimport { defineComponent } from 'vue'\n\n/**\n* redirect: noredirect        当设置 noredirect 的时候该路由在面包屑导航中不可被点击\n* name:'router-name'          设定路由的名字，一定要填写不然使用<keep-alive>时会出现各种问题\n* meta : {\n    hidden: true              当设置 true 的时候该路由不会再侧边栏出现 如404，login等页面(默认 false)\n\n    alwaysShow: true          当你一个路由下面的 children 声明的路由大于1个时，自动会变成嵌套的模式，\n                              只有一个时，会将那个子路由当做根路由显示在侧边栏，\n                              若你想不管路由下面的 children 声明的个数都显示你的根路由，\n                              你可以设置 alwaysShow: true，这样它就会忽略之前定义的规则，\n                              一直显示根路由(默认 false)\n\n    title: 'title'            设置该路由在侧边栏和面包屑中展示的名字\n\n    icon: 'svg-name'          设置该路由的图标\n\n    noCache: true             如果设置为true，则不会被 <keep-alive> 缓存(默认 false)\n\n    breadcrumb: false         如果设置为false，则不会在breadcrumb面包屑中显示(默认 true)\n\n    affix: true               如果设置为true，则会一直固定在tag项中(默认 false)\n\n    noTagsView: true          如果设置为true，则不会出现在tag中(默认 false)\n\n    activeMenu: '/home'  显示高亮的路由路径\n\n    followAuth: '/home'  跟随哪个路由进行权限过滤\n\n    canTo: true               设置为true即使hidden为true，也依然可以进行路由跳转(默认 false)\n  }\n**/\ndeclare module 'vue-router' {\n  interface RouteMeta extends Record<string | number | symbol, unknown> {\n    hidden?: boolean\n    alwaysShow?: boolean\n    title?: string\n    icon?: string\n    noCache?: boolean\n    breadcrumb?: boolean\n    affix?: boolean\n    activeMenu?: string\n    noTagsView?: boolean\n    followAuth?: string\n    canTo?: boolean\n  }\n}\n\ntype Component<T = any> =\n  | ReturnType<typeof defineComponent>\n  | (() => Promise<typeof import('*.vue')>)\n  | (() => Promise<T>)\n\ndeclare global {\n  interface AppRouteRecordRaw extends Omit<RouteRecordRaw, 'meta'> {\n    name: string\n    meta: RouteMeta\n    component?: Component | string\n    children?: AppRouteRecordRaw[]\n    props?: Recordable\n    fullPath?: string\n    keepAlive?: boolean\n  }\n\n  interface AppCustomRouteRecordRaw extends Omit<RouteRecordRaw, 'meta'> {\n    icon: any\n    name: string\n    meta: RouteMeta\n    component: string\n    componentName?: string\n    path: string\n    redirect: string\n    children?: AppCustomRouteRecordRaw[]\n    keepAlive?: boolean\n    visible?: boolean\n    parentId?: number\n    alwaysShow?: boolean\n  }\n}\n"
  },
  {
    "path": "yshop-drink-vue3/uno.config.ts",
    "content": "import { defineConfig, toEscapedSelector as e, presetUno } from 'unocss'\n// import transformerVariantGroup from '@unocss/transformer-variant-group'\n\nexport default defineConfig({\n  // ...UnoCSS options\n  rules: [\n    [\n      /^custom-hover$/,\n      ([], { rawSelector }) => {\n        const selector = e(rawSelector)\n        return `\n${selector} {\n  display: flex;\n  height: 100%;\n  padding: 1px 10px 0;\n  cursor: pointer;\n  align-items: center;\n  transition: background var(--transition-time-02);\n}\n/* you can have multiple rules */\n${selector}:hover {\n  background-color: var(--top-header-hover-color);\n}\n.dark ${selector}:hover {\n  background-color: var(--el-bg-color-overlay);\n}\n`\n      }\n    ],\n    [\n      /^layout-border__left$/,\n      ([], { rawSelector }) => {\n        const selector = e(rawSelector)\n        return `\n${selector}:before {\n  content: \"\";\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 1px;\n  height: 100%;\n  background-color: var(--el-border-color);\n  z-index: 3;\n}\n`\n      }\n    ],\n    [\n      /^layout-border__right$/,\n      ([], { rawSelector }) => {\n        const selector = e(rawSelector)\n        return `\n${selector}:after {\n  content: \"\";\n  position: absolute;\n  top: 0;\n  right: 0;\n  width: 1px;\n  height: 100%;\n  background-color: var(--el-border-color);\n  z-index: 3;\n}\n`\n      }\n    ],\n    [\n      /^layout-border__top$/,\n      ([], { rawSelector }) => {\n        const selector = e(rawSelector)\n        return `\n${selector}:before {\n  content: \"\";\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 1px;\n  background-color: var(--el-border-color);\n  z-index: 3;\n}\n`\n      }\n    ],\n    [\n      /^layout-border__bottom$/,\n      ([], { rawSelector }) => {\n        const selector = e(rawSelector)\n        return `\n${selector}:after {\n  content: \"\";\n  position: absolute;\n  bottom: 0;\n  left: 0;\n  width: 100%;\n  height: 1px;\n  background-color: var(--el-border-color);\n  z-index: 3;\n}\n`\n      }\n    ]\n  ],\n  presets: [presetUno({ dark: 'class', attributify: false })],\n  // transformers: [transformerVariantGroup()],\n  shortcuts: {\n    'wh-full': 'w-full h-full'\n  }\n})\n"
  },
  {
    "path": "yshop-drink-vue3/vite.config.ts",
    "content": "import { resolve } from 'path'\nimport { loadEnv } from 'vite'\nimport type { UserConfig, ConfigEnv } from 'vite'\nimport { createVitePlugins } from './build/vite'\nimport { include, exclude } from \"./build/vite/optimize\"\n// 当前执行node命令时文件夹的地址(工作目录)\nconst root = process.cwd()\n\n// 路径查找\nfunction pathResolve(dir: string) {\n  return resolve(root, '.', dir)\n}\n\n// https://vitejs.dev/config/\nexport default ({ command, mode }: ConfigEnv): UserConfig => {\n  let env = {} as any\n  const isBuild = command === 'build'\n  if (!isBuild) {\n    env = loadEnv((process.argv[3] === '--mode' ? process.argv[4] : process.argv[3]), root)\n  } else {\n    env = loadEnv(mode, root)\n  }\n  return {\n    base: env.VITE_BASE_PATH,\n    root: root,\n    // 服务端渲染\n    server: {\n      port: env.VITE_PORT, // 端口号\n      host: \"0.0.0.0\",\n      open: env.VITE_OPEN === 'true',\n      // 本地跨域代理. 目前注释的原因：暂时没有用途，server 端已经支持跨域\n      // proxy: {\n      //   ['/admin-api']: {\n      //     target: env.VITE_BASE_URL,\n      //     ws: false,\n      //     changeOrigin: true,\n      //     rewrite: (path) => path.replace(new RegExp(`^/admin-api`), ''),\n      //   },\n      // },\n    },\n    // 项目使用的vite插件。 单独提取到build/vite/plugin中管理\n    plugins: createVitePlugins(),\n    css: {\n      preprocessorOptions: {\n        scss: {\n          additionalData: '@import \"./src/styles/variables.scss\";',\n          javascriptEnabled: true\n        }\n      }\n    },\n    resolve: {\n      extensions: ['.mjs', '.js', '.ts', '.jsx', '.tsx', '.json', '.scss', '.css'],\n      alias: [\n        {\n          find: 'vue-i18n',\n          replacement: 'vue-i18n/dist/vue-i18n.cjs.js'\n        },\n        {\n          find: /\\@\\//,\n          replacement: `${pathResolve('src')}/`\n        }\n      ]\n    },\n    build: {\n      minify: 'terser',\n      outDir: env.VITE_OUT_DIR || 'dist',\n      sourcemap: env.VITE_SOURCEMAP === 'true' ? 'inline' : false,\n      // brotliSize: false,\n      terserOptions: {\n        compress: {\n          drop_debugger: env.VITE_DROP_DEBUGGER === 'true',\n          drop_console: env.VITE_DROP_CONSOLE === 'true'\n        }\n      }\n    },\n    optimizeDeps: { include, exclude }\n  }\n}\n"
  }
]